diff --git a/.cargo/config.toml b/.cargo/config.toml index 66b28b3485d8..4796a2c26965 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -29,4 +29,5 @@ rustflags = [ "-Aclippy::needless_option_as_deref", # false positives "-Aclippy::derivable_impls", # false positives "-Aclippy::stable_sort_primitive", # prefer stable sort + "-Aclippy::extra-unused-type-parameters", # stylistic ] diff --git a/.config/nextest.toml b/.config/nextest.toml new file mode 100644 index 000000000000..eb0ed09cad92 --- /dev/null +++ b/.config/nextest.toml @@ -0,0 +1,124 @@ +# This is the default config used by nextest. It is embedded in the binary at +# build time. It may be used as a template for .config/nextest.toml. + +[store] +# The directory under the workspace root at which nextest-related files are +# written. Profile-specific storage is currently written to dir/. +dir = "target/nextest" + +# This section defines the default nextest profile. Custom profiles are layered +# on top of the default profile. +[profile.default] +# "retries" defines the number of times a test should be retried. If set to a +# non-zero value, tests that succeed on a subsequent attempt will be marked as +# non-flaky. Can be overridden through the `--retries` option. +# Examples +# * retries = 3 +# * retries = { backoff = "fixed", count = 2, delay = "1s" } +# * retries = { backoff = "exponential", count = 10, delay = "1s", jitter = true, max-delay = "10s" } +retries = 5 + +# The number of threads to run tests with. Supported values are either an integer or +# the string "num-cpus". Can be overridden through the `--test-threads` option. +test-threads = "num-cpus" + +# The number of threads required for each test. This is generally used in overrides to +# mark certain tests as heavier than others. However, it can also be set as a global parameter. +threads-required = 1 + +# Show these test statuses in the output. +# +# The possible values this can take are: +# * none: no output +# * fail: show failed (including exec-failed) tests +# * retry: show flaky and retried tests +# * slow: show slow tests +# * pass: show passed tests +# * skip: show skipped tests (most useful for CI) +# * all: all of the above +# +# Each value includes all the values above it; for example, "slow" includes +# failed and retried tests. +# +# Can be overridden through the `--status-level` flag. +status-level = "pass" + +# Similar to status-level, show these test statuses at the end of the run. +final-status-level = "flaky" + +# "failure-output" defines when standard output and standard error for failing tests are produced. +# Accepted values are +# * "immediate": output failures as soon as they happen +# * "final": output failures at the end of the test run +# * "immediate-final": output failures as soon as they happen and at the end of +# the test run; combination of "immediate" and "final" +# * "never": don't output failures at all +# +# For large test suites and CI it is generally useful to use "immediate-final". +# +# Can be overridden through the `--failure-output` option. +failure-output = "immediate" + +# "success-output" controls production of standard output and standard error on success. This should +# generally be set to "never". +success-output = "never" + +# Cancel the test run on the first failure. For CI runs, consider setting this +# to false. +fail-fast = true + +# Treat a test that takes longer than the configured 'period' as slow, and print a message. +# See for more information. +# +# Optional: specify the parameter 'terminate-after' with a non-zero integer, +# which will cause slow tests to be terminated after the specified number of +# periods have passed. +# Example: slow-timeout = { period = "60s", terminate-after = 2 } +slow-timeout = { period = "60s" } + +# Treat a test as leaky if after the process is shut down, standard output and standard error +# aren't closed within this duration. +# +# This usually happens in case of a test that creates a child process and lets it inherit those +# handles, but doesn't clean the child process up (especially when it fails). +# +# See for more information. +leak-timeout = "100ms" + +[profile.default.junit] +# Output a JUnit report into the given file inside 'store.dir/'. +# If unspecified, JUnit is not written out. + +path = "junit.xml" + +# The name of the top-level "report" element in JUnit report. If aggregating +# reports across different test runs, it may be useful to provide separate names +# for each report. +report-name = "substrate" + +# Whether standard output and standard error for passing tests should be stored in the JUnit report. +# Output is stored in the and elements of the element. +store-success-output = false + +# Whether standard output and standard error for failing tests should be stored in the JUnit report. +# Output is stored in the and elements of the element. +# +# Note that if a description can be extracted from the output, it is always stored in the +# element. +store-failure-output = true + +# This profile is activated if MIRI_SYSROOT is set. +[profile.default-miri] +# Miri tests take up a lot of memory, so only run 1 test at a time by default. +test-threads = 1 + +# Mutual exclusion of tests with `cargo build` invocation as a lock to avoid multiple +# simultaneous invocations clobbering each other. +[test-groups] +serial-integration = { max-threads = 1 } + +# Running UI tests sequentially +# More info can be found here: https://github.com/paritytech/ci_cd/issues/754 +[[profile.default.overrides]] +filter = 'test(/(^ui$|_ui|ui_)/)' +test-group = 'serial-integration' diff --git a/.gitattributes b/.gitattributes index 9bd26362b0e8..a77c52fccdb7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,4 @@ Cargo.lock linguist-generated=true +/.gitlab-ci.yml filter=ci-prettier +/scripts/ci/gitlab/pipeline/*.yml filter=ci-prettier +frame/**/src/weights.rs linguist-generated=true diff --git a/.github/ISSUE_TEMPLATE/feature.yaml b/.github/ISSUE_TEMPLATE/feature.yaml index 92b2fea3e88d..6a59522ab4b4 100644 --- a/.github/ISSUE_TEMPLATE/feature.yaml +++ b/.github/ISSUE_TEMPLATE/feature.yaml @@ -1,7 +1,9 @@ name: Feature Request description: Submit your requests and suggestions to improve! +labels: ["J0-enhancement"] body: - type: checkboxes + id: existing attributes: label: Is there an existing issue? description: Please search to see if an issue already exists and leave a comment that you also experienced this issue or add your specifics that are related to an existing issue. @@ -9,6 +11,7 @@ body: - label: I have searched the existing issues required: true - type: checkboxes + id: stackexchange attributes: label: Experiencing problems? Have you tried our Stack Exchange first? description: Please search to see if an post already exists, and ask if not. Please do not file support issues here. @@ -16,7 +19,7 @@ body: - label: This is not a support question. required: true - type: textarea - id: content + id: motivation attributes: label: Motivation description: Please give precedence as to what lead you to file this issue. @@ -24,7 +27,7 @@ body: validations: required: false - type: textarea - id: content + id: request attributes: label: Request description: Please describe what is needed. @@ -32,7 +35,7 @@ body: validations: required: true - type: textarea - id: content + id: solution attributes: label: Solution description: If possible, please describe what a solution could be. diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cca9219e6c5e..04cf0d1e1a5a 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -2,11 +2,11 @@ version: 2 updates: - package-ecosystem: "cargo" directory: "/" - labels: ["A2-insubstantial", "B0-silent", "C1-low 📌"] + labels: ["A2-insubstantial", "B0-silent", "C1-low", "E2-dependencies"] schedule: interval: "daily" - package-ecosystem: github-actions directory: '/' - labels: ["A2-insubstantial", "B0-silent", "C1-low 📌", "E3-dependencies"] + labels: ["A2-insubstantial", "B0-silent", "C1-low", "E2-dependencies"] schedule: interval: daily diff --git a/.github/pr-custom-review.yml b/.github/pr-custom-review.yml index 769ac1e110d3..059f4a283af0 100644 --- a/.github/pr-custom-review.yml +++ b/.github/pr-custom-review.yml @@ -8,16 +8,28 @@ rules: check_type: changed_files condition: include: .* - # excluding files from 'CI team' rules - exclude: ^\.gitlab-ci\.yml|^scripts/ci/.*|^\.github/.* + # excluding files from 'CI team' and 'FRAME coders' rules + exclude: ^\.gitlab-ci\.yml|^scripts/ci/.*|^\.github/.*|^\.config/nextest.toml|^frame/(?!.*(nfts/.*|uniques/.*|babe/.*|grandpa/.*|beefy|merkle-mountain-range/.*|contracts/.*|election|nomination-pools/.*|staking/.*|aura/.*)) min_approvals: 2 teams: - core-devs + - name: FRAME coders + check_type: changed_files + condition: + include: ^frame/(?!.*(nfts/.*|uniques/.*|babe/.*|grandpa/.*|beefy|merkle-mountain-range/.*|contracts/.*|election|nomination-pools/.*|staking/.*|aura/.*)) + all: + - min_approvals: 2 + teams: + - core-devs + - min_approvals: 1 + teams: + - frame-coders + - name: CI team check_type: changed_files condition: - include: ^\.gitlab-ci\.yml|^scripts/ci/.*|^\.github/.* + include: ^\.gitlab-ci\.yml|^scripts/ci/.*|^\.github/.*|^\.config/nextest.toml min_approvals: 2 teams: - ci diff --git a/.github/workflows/auto-label-issues.yml b/.github/workflows/auto-label-issues.yml index 2633bf55f078..629966ed3961 100644 --- a/.github/workflows/auto-label-issues.yml +++ b/.github/workflows/auto-label-issues.yml @@ -14,4 +14,4 @@ jobs: uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 # 1.0.4 if: github.event.issue.author_association == 'NONE' with: - add-labels: 'Z0-unconfirmed' + add-labels: "J2-unconfirmed" diff --git a/.github/workflows/auto-label-prs.yml b/.github/workflows/auto-label-prs.yml deleted file mode 100644 index 50539b80b98b..000000000000 --- a/.github/workflows/auto-label-prs.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Label PRs -on: - pull_request: - types: [opened,ready_for_review] - -jobs: - label-new-prs: - runs-on: ubuntu-latest - steps: - - name: Label drafts - uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 # 1.0.4 - if: github.event.pull_request.draft == true - with: - add-labels: 'A3-inprogress' - remove-labels: 'A0-pleasereview' - - name: Label PRs - uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 # 1.0.4 - if: github.event.pull_request.draft == false && ! contains(github.event.pull_request.labels.*.name, 'A2-insubstantial') - with: - add-labels: 'A0-pleasereview' - remove-labels: 'A3-inprogress' diff --git a/.github/workflows/check-D-labels.yml b/.github/workflows/check-D-labels.yml new file mode 100644 index 000000000000..7bb358ce1182 --- /dev/null +++ b/.github/workflows/check-D-labels.yml @@ -0,0 +1,48 @@ +name: Check D labels + +on: + pull_request: + types: [labeled, opened, synchronize, unlabeled] + paths: + - frame/** + - primitives/** + +env: + IMAGE: paritytech/ruled_labels:0.4.0 + +jobs: + check-labels: + runs-on: ubuntu-latest + steps: + - name: Pull image + run: docker pull $IMAGE + + - name: Check labels + env: + MOUNT: /work + GITHUB_PR: ${{ github.event.pull_request.number }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + API_BASE: https://api.github.com/repos + REPO: ${{ github.repository }} + RULES_PATH: labels/ruled_labels + CHECK_SPECS: specs_substrate.yaml + run: | + echo "REPO: ${REPO}" + echo "GITHUB_PR: ${GITHUB_PR}" + # Clone repo with labels specs + git clone https://github.com/paritytech/labels + # Fetch the labels for the PR under test + labels=$( curl -H "Authorization: token ${GITHUB_TOKEN}" -s "$API_BASE/${REPO}/pulls/${GITHUB_PR}" | jq '.labels | .[] | .name' | tr "\n" ",") + + if [ -z "${labels}" ]; then + docker run --rm -i -v $PWD/${RULES_PATH}/:$MOUNT $IMAGE check $MOUNT/$CHECK_SPECS --tags audit --no-label + fi + + labels_args=${labels: :-1} + printf "Checking labels: %s\n" "${labels_args}" + + # Prevent the shell from splitting labels with spaces + IFS="," + + # --dev is more useful to debug mode to debug + docker run --rm -i -v $PWD/${RULES_PATH}/:$MOUNT $IMAGE check $MOUNT/$CHECK_SPECS --labels ${labels_args} --dev --tags audit diff --git a/.github/workflows/check-labels.yml b/.github/workflows/check-labels.yml index de204ce9d377..55b8f7389fa7 100644 --- a/.github/workflows/check-labels.yml +++ b/.github/workflows/check-labels.yml @@ -4,18 +4,42 @@ on: pull_request: types: [labeled, opened, synchronize, unlabeled] +env: + IMAGE: paritytech/ruled_labels:0.4.0 + jobs: check-labels: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 - with: - fetch-depth: 0 - ref: ${{ github.event.pull_request.head.ref }} - repository: ${{ github.event.pull_request.head.repo.full_name }} + - name: Pull image + run: docker pull $IMAGE + - name: Check labels - run: bash ${{ github.workspace }}/scripts/ci/github/check_labels.sh env: + MOUNT: /work GITHUB_PR: ${{ github.event.pull_request.number }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - HEAD_SHA: ${{ github.event.pull_request.head.sha }} + API_BASE: https://api.github.com/repos + REPO: ${{ github.repository }} + RULES_PATH: labels/ruled_labels + CHECK_SPECS: specs_substrate.yaml + run: | + echo "REPO: ${REPO}" + echo "GITHUB_PR: ${GITHUB_PR}" + # Clone repo with labels specs + git clone https://github.com/paritytech/labels + # Fetch the labels for the PR under test + labels=$( curl -H "Authorization: token ${GITHUB_TOKEN}" -s "$API_BASE/${REPO}/pulls/${GITHUB_PR}" | jq '.labels | .[] | .name' | tr "\n" ",") + + if [ -z "${labels}" ]; then + docker run --rm -i -v $PWD/${RULES_PATH}/:$MOUNT $IMAGE check $MOUNT/$CHECK_SPECS --tags PR --no-label + fi + + labels_args=${labels: :-1} + printf "Checking labels: %s\n" "${labels_args}" + + # Prevent the shell from splitting labels with spaces + IFS="," + + # --dev is more useful to debug mode to debug + docker run --rm -i -v $PWD/${RULES_PATH}/:$MOUNT $IMAGE check $MOUNT/$CHECK_SPECS --labels ${labels_args} --dev --tags PR diff --git a/.github/workflows/monthly-tag.yml b/.github/workflows/monthly-tag.yml index 6bec03d27e7b..055207d85a4d 100644 --- a/.github/workflows/monthly-tag.yml +++ b/.github/workflows/monthly-tag.yml @@ -13,8 +13,8 @@ jobs: - name: Get the tags by date id: tags run: | - echo "::set-output name=new::$(date +'monthly-%Y-%m')" - echo "::set-output name=old::$(date -d'1 month ago' +'monthly-%Y-%m')" + echo "new=$(date +'monthly-%Y-%m')" >> $GITHUB_OUTPUT + echo "old=$(date -d'1 month ago' +'monthly-%Y-%m')" >> $GITHUB_OUTPUT - name: Checkout branch "master" uses: actions/checkout@v3 with: diff --git a/.github/workflows/trigger-review-pipeline.yml b/.github/workflows/trigger-review-pipeline.yml deleted file mode 100644 index af54ec4358b4..000000000000 --- a/.github/workflows/trigger-review-pipeline.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Trigger pipeline for review - -on: - pull_request: - types: [ready_for_review] - -jobs: - trigger: - runs-on: ubuntu-latest - - steps: - - name: Trigger pipeline - run: | - curl -X POST \ - -F token="$TOKEN" \ - -F ref="$REF" \ - https://gitlab.parity.io/api/v4/projects/145/trigger/pipeline - env: - REF: ${{ github.event.number }} - TOKEN: ${{ secrets.GITLAB_TRIGGER_TOKEN }} diff --git a/.gitignore b/.gitignore index 5cd013e054e4..65059279f3a5 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,7 @@ rls*.log .cargo-remote.toml *.bin *.iml -scripts/ci/node-template-release/Cargo.lock +bin/node-template/Cargo.lock +substrate.code-workspace +.direnv/ +/.envrc diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ae25a2c5953f..f00836528973 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -33,9 +33,9 @@ stages: - test - build - publish + - notify - zombienet - deploy - - notify workflow: rules: @@ -43,57 +43,49 @@ workflow: - if: $CI_COMMIT_BRANCH variables: - GIT_STRATEGY: fetch - GIT_DEPTH: 100 - CARGO_INCREMENTAL: 0 - DOCKER_OS: "debian:stretch" - ARCH: "x86_64" - CI_IMAGE: "paritytech/ci-linux:production" - BUILDAH_IMAGE: "quay.io/buildah/stable:v1.27" - RUSTY_CACHIER_SINGLE_BRANCH: master + GIT_STRATEGY: fetch + GIT_DEPTH: 100 + CARGO_INCREMENTAL: 0 + DOCKER_OS: "debian:bullseye" + ARCH: "x86_64" + CI_IMAGE: !reference [.ci-unified, variables, CI_IMAGE] + BUILDAH_IMAGE: "quay.io/buildah/stable:v1.29" + BUILDAH_COMMAND: "buildah --storage-driver overlay2" + RELENG_SCRIPTS_BRANCH: "master" + + RUSTY_CACHIER_SINGLE_BRANCH: master RUSTY_CACHIER_DONT_OPERATE_ON_MAIN_BRANCH: "true" + RUSTY_CACHIER_MINIO_ALIAS: rustycachier_gcs + RUSTY_CACHIER_MINIO_BUCKET: parity-build-rusty-cachier RUSTY_CACHIER_COMPRESSION_METHOD: zstd - NEXTEST_FAILURE_OUTPUT: immediate-final - NEXTEST_SUCCESS_OUTPUT: final - ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.22" -.shared-default: &shared-default + NEXTEST_FAILURE_OUTPUT: immediate-final + NEXTEST_SUCCESS_OUTPUT: final + ZOMBIENET_IMAGE: "docker.io/paritytech/zombienet:v1.3.55" + +default: retry: max: 2 when: - runner_system_failure - unknown_failure - api_failure - cache: {} - -.default-pipeline-definitions: - default: - <<: *shared-default - interruptible: true - -.crate-publishing-pipeline-definitions: - default: - <<: *shared-default - # The crate-publishing pipeline defaults to `interruptible: false` so that we'll be able to - # reach and run the publishing jobs despite the "Auto-cancel redundant pipelines" CI setting. - # The setting is relevant because the crate-publishing pipeline runs on `master`, thus future - # pipelines on `master` (e.g. created for new commits or other schedules) might unintendedly - # cancel the publishing jobs or its dependencies before we get to actually publish the crates. - interruptible: false + cache: {} + interruptible: true .collect-artifacts: artifacts: - name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" - when: on_success - expire_in: 7 days + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" + when: on_success + expire_in: 7 days paths: - artifacts/ .collect-artifacts-short: artifacts: - name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" - when: on_success - expire_in: 3 hours + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" + when: on_success + expire_in: 3 hours paths: - artifacts/ @@ -105,14 +97,17 @@ variables: # $WASM_BUILD_WORKSPACE_HINT enables wasm-builder to find the Cargo.lock from within generated # packages - export WASM_BUILD_WORKSPACE_HINT="$PWD" + # ensure that RUSTFLAGS are set correctly + - echo $RUSTFLAGS .job-switcher: before_script: - if echo "$CI_DISABLED_JOBS" | grep -xF "$CI_JOB_NAME"; then echo "The job has been cancelled in CI settings"; exit 0; fi .kubernetes-env: - image: "${CI_IMAGE}" + image: "${CI_IMAGE}" before_script: + - !reference [.timestamp, before_script] - !reference [.job-switcher, before_script] - !reference [.prepare-env, before_script] tags: @@ -139,8 +134,9 @@ variables: dotenv: pipeline-stopper.env .docker-env: - image: "${CI_IMAGE}" + image: "${CI_IMAGE}" before_script: + - !reference [.timestamp, before_script] - !reference [.job-switcher, before_script] - !reference [.prepare-env, before_script] - !reference [.rust-info-script, script] @@ -149,7 +145,7 @@ variables: after_script: - !reference [.rusty-cachier, after_script] tags: - - linux-docker + - linux-docker-vm-c2 # rusty-cachier's hidden job. Parts of this job are used to instrument the pipeline's other real jobs with rusty-cachier # Description of the commands is available here - https://gitlab.parity.io/parity/infrastructure/ci_cd/rusty-cachier/client#description @@ -167,20 +163,20 @@ variables: - if: $CI_PIPELINE_SOURCE == "web" - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME == "master" - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 # handle the specific case where benches could store incorrect bench data because of the downstream staging runs # exclude cargo-check-benches from such runs .test-refs-check-benches: rules: - - if: $CI_COMMIT_REF_NAME == "master" && $CI_PIPELINE_SOURCE == "parent_pipeline" && $CI_IMAGE =~ /staging$/ + - if: $CI_COMMIT_REF_NAME == "master" && $CI_PIPELINE_SOURCE == "pipeline" && $CI_IMAGE =~ /staging$/ when: never - if: $CI_PIPELINE_SOURCE == "web" - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME == "master" - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 .test-refs-no-trigger: rules: @@ -189,8 +185,8 @@ variables: - if: $CI_PIPELINE_SOURCE == "web" - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME == "master" - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ .test-refs-no-trigger-prs-only: @@ -199,7 +195,7 @@ variables: when: never - if: $CI_PIPELINE_SOURCE == "web" - if: $CI_PIPELINE_SOURCE == "schedule" - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs .publish-refs: rules: @@ -208,7 +204,7 @@ variables: - if: $CI_PIPELINE_SOURCE == "web" - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME == "master" - - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 .build-refs: # publish-refs + PRs @@ -218,25 +214,20 @@ variables: - if: $CI_PIPELINE_SOURCE == "web" - if: $CI_PIPELINE_SOURCE == "schedule" - if: $CI_COMMIT_REF_NAME == "master" - - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs .zombienet-refs: extends: .build-refs -.nightly-pipeline: - rules: - # this job runs only on nightly pipeline with the mentioned variable, against `master` branch - - if: $CI_COMMIT_REF_NAME == "master" && $CI_PIPELINE_SOURCE == "schedule" && $PIPELINE == "nightly" - .crates-publishing-variables: variables: - CRATESIO_CRATES_OWNER: parity-crate-owner - REPO: substrate - REPO_OWNER: paritytech + CRATESIO_CRATES_OWNER: parity-crate-owner + REPO: substrate + REPO_OWNER: paritytech .crates-publishing-pipeline: - extends: .crates-publishing-variables + extends: .crates-publishing-variables rules: - if: $CI_PIPELINE_SOURCE == "schedule" && $CI_COMMIT_REF_NAME == "master" && $PIPELINE == "automatic-crate-publishing" @@ -247,42 +238,48 @@ variables: # collect artifacts even on failure so that we know how the crates were generated (they'll be # generated to the artifacts folder according to SPUB_TMP further down) artifacts: - name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" - when: always - expire_in: 7 days + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" + when: always + expire_in: 7 days paths: - artifacts/ variables: - SPUB_TMP: artifacts + SPUB_TMP: artifacts + # disable timestamping for the crate publishing jobs, they leave stray child processes behind + # which don't interact well with the timestamping script + CI_DISABLE_TIMESTAMP: 1 #### stage: .pre -skip-if-draft: - extends: .kubernetes-env - variables: - CI_IMAGE: "paritytech/tools:latest" - stage: .pre - rules: - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs - script: - - echo "Commit message is ${CI_COMMIT_MESSAGE}" - - echo "Ref is ${CI_COMMIT_REF_NAME}" - - echo "pipeline source is ${CI_PIPELINE_SOURCE}" - - ./scripts/ci/gitlab/skip_if_draft.sh - allow_failure: true - check-crates-publishing-pipeline: - stage: .pre + stage: .pre extends: - .kubernetes-env - .crates-publishing-pipeline script: - git clone - --depth 1 - --branch "$RELENG_SCRIPTS_BRANCH" - https://github.com/paritytech/releng-scripts.git + --depth 1 + --branch "$RELENG_SCRIPTS_BRANCH" + https://github.com/paritytech/releng-scripts.git - ONLY_CHECK_PIPELINE=true ./releng-scripts/publish-crates +# By default our pipelines are interruptible, but some special pipelines shouldn't be interrupted: +# * multi-project pipelines such as the ones triggered by the scripts repo +# * the scheduled automatic-crate-publishing pipeline +# +# In those cases, we add an uninterruptible .pre job; once that one has started, +# the entire pipeline becomes uninterruptible +uninterruptible-pipeline: + extends: .kubernetes-env + variables: + CI_IMAGE: "paritytech/tools:latest" + stage: .pre + interruptible: false + rules: + - if: $CI_PIPELINE_SOURCE == "pipeline" + - if: $CI_PIPELINE_SOURCE == "schedule" && $PIPELINE == "automatic-crate-publishing" + script: "true" + include: # check jobs - scripts/ci/gitlab/pipeline/check.yml @@ -303,35 +300,13 @@ include: # pipeline is made uninterruptible to ensure that test jobs also get a chance to run to # completion, because the publishing jobs depends on them AS INTENDED: crates should not be # published before their source code is checked. - - local: scripts/ci/gitlab/crate-publishing-pipeline.yml - rules: - - if: $PIPELINE == "automatic-crate-publishing" - # For normal pipelines: run it with defaults + `interruptible: true` - - local: scripts/ci/gitlab/default-pipeline.yml - rules: - - if: $PIPELINE != "automatic-crate-publishing" - -#### stage: deploy - -deploy-prometheus-alerting-rules: - stage: deploy - needs: - - job: test-prometheus-alerting-rules - artifacts: false - allow_failure: true - trigger: - project: parity/infrastructure/cloud-infra - variables: - SUBSTRATE_CI_COMMIT_NAME: "${CI_COMMIT_REF_NAME}" - SUBSTRATE_CI_COMMIT_REF: "${CI_COMMIT_SHORT_SHA}" - UPSTREAM_TRIGGER_PROJECT: "${CI_PROJECT_PATH}" - rules: - - if: $CI_PIPELINE_SOURCE == "pipeline" - when: never - - if: $CI_COMMIT_REF_NAME == "master" - changes: - - .gitlab-ci.yml - - ./scripts/ci/monitoring/**/* + - project: parity/infrastructure/ci_cd/shared + ref: main + file: /common/timestamp.yml + - project: parity/infrastructure/ci_cd/shared + ref: main + file: /common/ci-unified.yml + #### stage: notify @@ -339,12 +314,12 @@ deploy-prometheus-alerting-rules: # This info is later used for the cache distribution and an overlay creation. # Note that we don't use any .rusty-cachier references as we assume that a pipeline has reached this stage with working rusty-cachier. rusty-cachier-notify: - stage: notify - extends: .kubernetes-env + stage: notify + extends: .kubernetes-env variables: - CI_IMAGE: paritytech/rusty-cachier-env:latest - GIT_STRATEGY: none - dependencies: [] + CI_IMAGE: paritytech/rusty-cachier-env:latest + GIT_STRATEGY: none + dependencies: [] script: - curl -s https://gitlab-ci-token:${CI_JOB_TOKEN}@gitlab.parity.io/parity/infrastructure/ci_cd/rusty-cachier/client/-/raw/release/util/install.sh | bash - rusty-cachier cache notify @@ -355,83 +330,83 @@ rusty-cachier-notify: # In a DAG, every jobs chain is executed independently of others. The `fail_fast` principle suggests # to fail the pipeline as soon as possible to shorten the feedback loop. .cancel-pipeline-template: - stage: .post + stage: .post rules: - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs when: on_failure variables: - PROJECT_ID: "${CI_PROJECT_ID}" - PROJECT_NAME: "${CI_PROJECT_NAME}" - PIPELINE_ID: "${CI_PIPELINE_ID}" - FAILED_JOB_URL: "${FAILED_JOB_URL}" - FAILED_JOB_NAME: "${FAILED_JOB_NAME}" - PR_NUM: "${PR_NUM}" + PROJECT_ID: "${CI_PROJECT_ID}" + PROJECT_NAME: "${CI_PROJECT_NAME}" + PIPELINE_ID: "${CI_PIPELINE_ID}" + FAILED_JOB_URL: "${FAILED_JOB_URL}" + FAILED_JOB_NAME: "${FAILED_JOB_NAME}" + PR_NUM: "${PR_NUM}" trigger: - project: "parity/infrastructure/ci_cd/pipeline-stopper" + project: "parity/infrastructure/ci_cd/pipeline-stopper" remove-cancel-pipeline-message: stage: .post rules: - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ # PRs variables: - PROJECT_ID: "${CI_PROJECT_ID}" - PROJECT_NAME: "${CI_PROJECT_NAME}" - PIPELINE_ID: "${CI_PIPELINE_ID}" - FAILED_JOB_URL: "https://gitlab.com" - FAILED_JOB_NAME: "nope" - PR_NUM: "${CI_COMMIT_REF_NAME}" + PROJECT_ID: "${CI_PROJECT_ID}" + PROJECT_NAME: "${CI_PROJECT_NAME}" + PIPELINE_ID: "${CI_PIPELINE_ID}" + FAILED_JOB_URL: "https://gitlab.com" + FAILED_JOB_NAME: "nope" + PR_NUM: "${CI_COMMIT_REF_NAME}" trigger: - project: "parity/infrastructure/ci_cd/pipeline-stopper" - branch: "as-improve" + project: "parity/infrastructure/ci_cd/pipeline-stopper" + branch: "as-improve" # need to copy jobs this way because otherwise gitlab will wait # for all 3 jobs to finish instead of cancelling if one fails cancel-pipeline-test-linux-stable1: - extends: .cancel-pipeline-template + extends: .cancel-pipeline-template needs: - - job: "test-linux-stable 1/3" + - job: "test-linux-stable 1/3" cancel-pipeline-test-linux-stable2: - extends: .cancel-pipeline-template + extends: .cancel-pipeline-template needs: - - job: "test-linux-stable 2/3" + - job: "test-linux-stable 2/3" cancel-pipeline-test-linux-stable3: - extends: .cancel-pipeline-template + extends: .cancel-pipeline-template needs: - - job: "test-linux-stable 3/3" + - job: "test-linux-stable 3/3" cancel-pipeline-cargo-check-benches1: - extends: .cancel-pipeline-template + extends: .cancel-pipeline-template needs: - - job: "cargo-check-benches 1/2" + - job: "cargo-check-benches 1/2" cancel-pipeline-cargo-check-benches2: - extends: .cancel-pipeline-template + extends: .cancel-pipeline-template needs: - - job: "cargo-check-benches 2/2" + - job: "cargo-check-benches 2/2" cancel-pipeline-test-linux-stable-int: - extends: .cancel-pipeline-template + extends: .cancel-pipeline-template needs: - - job: test-linux-stable-int + - job: test-linux-stable-int cancel-pipeline-cargo-check-each-crate-1: - extends: .cancel-pipeline-template + extends: .cancel-pipeline-template needs: - - job: "cargo-check-each-crate 1/2" + - job: "cargo-check-each-crate 1/2" cancel-pipeline-cargo-check-each-crate-2: - extends: .cancel-pipeline-template + extends: .cancel-pipeline-template needs: - - job: "cargo-check-each-crate 2/2" + - job: "cargo-check-each-crate 2/2" cancel-pipeline-cargo-check-each-crate-macos: - extends: .cancel-pipeline-template + extends: .cancel-pipeline-template needs: - - job: cargo-check-each-crate-macos + - job: cargo-check-each-crate-macos cancel-pipeline-check-tracing: - extends: .cancel-pipeline-template + extends: .cancel-pipeline-template needs: - - job: check-tracing + - job: check-tracing diff --git a/.maintain/frame-weight-template.hbs b/.maintain/frame-weight-template.hbs index 9c9e29780086..4ab509f8e446 100644 --- a/.maintain/frame-weight-template.hbs +++ b/.maintain/frame-weight-template.hbs @@ -2,9 +2,10 @@ //! 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}}` +//! DATE: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: `{{cmd.repeat}}`, LOW RANGE: `{{cmd.lowest_range_values}}`, HIGH RANGE: `{{cmd.highest_range_values}}` +//! WORST CASE MAP SIZE: `{{cmd.worst_case_map_values}}` //! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}` -//! EXECUTION: {{cmd.execution}}, WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}} +//! EXECUTION: `{{cmd.execution}}`, WASM-EXECUTION: `{{cmd.wasm_execution}}`, CHAIN: `{{cmd.chain}}`, DB CACHE: `{{cmd.db_cache}}` // Executed Command: {{#each args as |arg|}} @@ -14,9 +15,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for {{pallet}}. pub trait WeightInfo { @@ -38,7 +40,7 @@ impl WeightInfo for SubstrateWeight { {{/if}} {{#each benchmarks as |benchmark|}} {{#each benchmark.comments as |comment|}} - // {{comment}} + /// {{comment}} {{/each}} {{#each benchmark.component_ranges as |range|}} /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. @@ -48,33 +50,39 @@ impl WeightInfo for SubstrateWeight { {{~#each benchmark.components as |c| ~}} {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} ) -> Weight { - // Minimum execution time: {{underscore benchmark.min_execution_time}} nanoseconds. - Weight::from_ref_time({{underscore benchmark.base_weight}}) + // Proof Size summary in bytes: + // Measured: `{{benchmark.base_recorded_proof_size}}{{#each benchmark.component_recorded_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` + // Estimated: `{{benchmark.base_calculated_proof_size}}{{#each benchmark.component_calculated_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` + // Minimum execution time: {{underscore benchmark.min_execution_time}}_000 picoseconds. + Weight::from_parts({{underscore benchmark.base_weight}}, {{benchmark.base_calculated_proof_size}}) {{#each benchmark.component_weight as |cw|}} // Standard Error: {{underscore cw.error}} - .saturating_add(Weight::from_ref_time({{underscore cw.slope}}).saturating_mul({{cw.name}}.into())) + .saturating_add(Weight::from_parts({{underscore cw.slope}}, 0).saturating_mul({{cw.name}}.into())) {{/each}} {{#if (ne benchmark.base_reads "0")}} - .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}})) + .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}}_u64)) {{/if}} {{#each benchmark.component_reads as |cr|}} .saturating_add(T::DbWeight::get().reads(({{cr.slope}}_u64).saturating_mul({{cr.name}}.into()))) {{/each}} {{#if (ne benchmark.base_writes "0")}} - .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}})) + .saturating_add(T::DbWeight::get().writes({{benchmark.base_writes}}_u64)) {{/if}} {{#each benchmark.component_writes as |cw|}} .saturating_add(T::DbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) {{/each}} + {{#each benchmark.component_calculated_proof_size as |cp|}} + .saturating_add(Weight::from_parts(0, {{cp.slope}}).saturating_mul({{cp.name}}.into())) + {{/each}} } {{/each}} } -// For backwards compatibility and tests +// For backwards compatibility and tests. impl WeightInfo for () { {{#each benchmarks as |benchmark|}} {{#each benchmark.comments as |comment|}} - // {{comment}} + /// {{comment}} {{/each}} {{#each benchmark.component_ranges as |range|}} /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. @@ -84,24 +92,30 @@ impl WeightInfo for () { {{~#each benchmark.components as |c| ~}} {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} ) -> Weight { - // Minimum execution time: {{underscore benchmark.min_execution_time}} nanoseconds. - Weight::from_ref_time({{underscore benchmark.base_weight}}) + // Proof Size summary in bytes: + // Measured: `{{benchmark.base_recorded_proof_size}}{{#each benchmark.component_recorded_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` + // Estimated: `{{benchmark.base_calculated_proof_size}}{{#each benchmark.component_calculated_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` + // Minimum execution time: {{underscore benchmark.min_execution_time}}_000 picoseconds. + Weight::from_parts({{underscore benchmark.base_weight}}, {{benchmark.base_calculated_proof_size}}) {{#each benchmark.component_weight as |cw|}} // Standard Error: {{underscore cw.error}} - .saturating_add(Weight::from_ref_time({{underscore cw.slope}}).saturating_mul({{cw.name}}.into())) + .saturating_add(Weight::from_parts({{underscore cw.slope}}, 0).saturating_mul({{cw.name}}.into())) {{/each}} {{#if (ne benchmark.base_reads "0")}} - .saturating_add(RocksDbWeight::get().reads({{benchmark.base_reads}})) + .saturating_add(RocksDbWeight::get().reads({{benchmark.base_reads}}_u64)) {{/if}} {{#each benchmark.component_reads as |cr|}} .saturating_add(RocksDbWeight::get().reads(({{cr.slope}}_u64).saturating_mul({{cr.name}}.into()))) {{/each}} {{#if (ne benchmark.base_writes "0")}} - .saturating_add(RocksDbWeight::get().writes({{benchmark.base_writes}})) + .saturating_add(RocksDbWeight::get().writes({{benchmark.base_writes}}_u64)) {{/if}} {{#each benchmark.component_writes as |cw|}} .saturating_add(RocksDbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) {{/each}} + {{#each benchmark.component_calculated_proof_size as |cp|}} + .saturating_add(Weight::from_parts(0, {{cp.slope}}).saturating_mul({{cp.name}}.into())) + {{/each}} } {{/each}} } diff --git a/.maintain/update-copyright.sh b/.maintain/update-copyright.sh deleted file mode 100755 index d67cab7c1e15..000000000000 --- a/.maintain/update-copyright.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -SINGLE_DATES=$(grep -lr "// Copyright (C) [0-9]* Parity Technologies (UK) Ltd.") -YEAR=$(date +%Y) - -for file in $SINGLE_DATES; do - FILE_YEAR=$(cat $file | sed -n "s|// Copyright (C) \([[:digit:]][[:digit:]][[:digit:]][[:digit:]]\) Parity Technologies (UK) Ltd.|\1|p") - if [ $YEAR -ne $FILE_YEAR ]; then - sed -i -e "s|// Copyright (C) \([[:digit:]][[:digit:]][[:digit:]][[:digit:]]\) Parity Technologies (UK) Ltd.|// Copyright (C) \1-$YEAR Parity Technologies (UK) Ltd.|g" $file - fi -done - -grep -lr "// Copyright (C) [0-9]*-[0-9]* Parity Technologies (UK) Ltd." | - xargs sed -i -e "s|// Copyright (C) \([[:digit:]][[:digit:]][[:digit:]][[:digit:]]\)-[[:digit:]][[:digit:]][[:digit:]][[:digit:]] Parity Technologies (UK) Ltd.|// Copyright (C) \1-$YEAR Parity Technologies (UK) Ltd.|g" diff --git a/Cargo.lock b/Cargo.lock index 43f67695faa1..c8a757544e62 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12,22 +12,13 @@ dependencies = [ "regex", ] -[[package]] -name = "addr2line" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" -dependencies = [ - "gimli 0.26.2", -] - [[package]] name = "addr2line" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97" dependencies = [ - "gimli 0.27.0", + "gimli", ] [[package]] @@ -42,7 +33,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fc95d1bdb8e6666b2b217308eeeb09f2d6728d104be3e31916cc74d15420331" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] @@ -51,10 +42,20 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", "rand_core 0.6.4", ] +[[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.6.0" @@ -79,17 +80,14 @@ dependencies = [ ] [[package]] -name = "aes-gcm" -version = "0.8.0" +name = "aes" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5278b5fabbb9bd46e24aa69b2fdea62c99088e0a950a9be40e3e0101298f88da" +checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" dependencies = [ - "aead 0.3.2", - "aes 0.6.0", - "cipher 0.2.5", - "ctr 0.6.0", - "ghash 0.3.1", - "subtle", + "cfg-if", + "cipher 0.4.4", + "cpufeatures", ] [[package]] @@ -106,6 +104,20 @@ dependencies = [ "subtle", ] +[[package]] +name = "aes-gcm" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209b47e8954a928e1d72e86eca7000ebb6655fe1436d33eefc2201cad027e237" +dependencies = [ + "aead 0.5.2", + "aes 0.8.2", + "cipher 0.4.4", + "ctr 0.9.2", + "ghash 0.5.0", + "subtle", +] + [[package]] name = "aes-soft" version = "0.6.4" @@ -132,19 +144,19 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.9", "once_cell", "version_check", ] [[package]] name = "ahash" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107" +checksum = "2c99f64d1e06488f620f932677e24bc6e2897582980441ae90a671415bd7ec2f" dependencies = [ "cfg-if", - "getrandom 0.2.8", + "getrandom 0.2.9", "once_cell", "version_check", ] @@ -158,6 +170,27 @@ dependencies = [ "memchr", ] +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" + +[[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" @@ -167,6 +200,12 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + [[package]] name = "ansi_term" version = "0.12.1" @@ -176,11 +215,60 @@ dependencies = [ "winapi", ] +[[package]] +name = "anstream" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ca84f3628370c59db74ee214b3263d58f9aadd9b4fe7e711fd87dc452b7f163" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed9a86bf92ae6580e0a31281f65a1b1d867c0cc68d5346e2ae128dddfa6a7d" + +[[package]] +name = "anstyle-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e765fd216e48e067936442276d1d57399e37bce53c264d6fefbe298080cb57ee" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" +dependencies = [ + "windows-sys 0.48.0", +] + +[[package]] +name = "anstyle-wincon" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180abfa45703aebe0093f79badacc01b8fd4ea2e35118747e5811127f926e188" +dependencies = [ + "anstyle", + "windows-sys 0.48.0", +] + [[package]] name = "anyhow" -version = "1.0.68" +version = "1.0.71" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" [[package]] name = "approx" @@ -191,11 +279,25 @@ dependencies = [ "num-traits", ] +[[package]] +name = "aquamarine" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df752953c49ce90719c7bf1fc587bc8227aed04732ea0c0f85e5397d7fdbd1a1" +dependencies = [ + "include_dir", + "itertools", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "arbitrary" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29d47fbf90d5149a107494b15a7dc8d69b351be2db3bb9691740e88ec17fd880" +checksum = "e2d098ff73c1ca148721f37baad5ea6a465a13f9573aba8641fbbbae8164a54e" [[package]] name = "arc-swap" @@ -203,17 +305,243 @@ version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" +[[package]] +name = "ark-algebra-test-templates" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "400bd3a79c741b1832f1416d4373ae077ef82ca14a8b4cee1248a2f11c8b9172" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", + "hex", + "num-bigint", + "num-integer", + "num-traits", + "serde", + "serde_derive", + "serde_json", + "sha2 0.10.6", +] + +[[package]] +name = "ark-bls12-377" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-bls12-381" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", +] + +[[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", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools", + "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", + "ark-ff", + "ark-std", +] + +[[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", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools", + "num-bigint", + "num-traits", + "paste", + "rustc_version 0.4.0", + "zeroize", +] + +[[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-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-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-r1cs-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de1d1472e5cb020cb3405ce2567c91c8d43f21b674aef37b0202f5c3304761db" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-relations", + "ark-std", + "derivative", + "num-bigint", + "num-integer", + "num-traits", + "tracing", +] + +[[package]] +name = "ark-relations" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00796b6efc05a3f48225e59cb6a2cda78881e7c390872d5786aaf112f31fb4f0" +dependencies = [ + "ark-ff", + "ark-std", + "tracing", + "tracing-subscriber 0.2.25", +] + +[[package]] +name = "ark-scale" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d028cd1025d002fa88c10cd644d29028a7b40806579b608c6ba843b937bbb23" +dependencies = [ + "ark-ec", + "ark-serialize", + "ark-std", + "parity-scale-codec", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "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-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.5", +] + [[package]] name = "array-bytes" -version = "4.2.0" +version = "6.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52f63c5c1316a16a4b35eaac8b76a98248961a533f061684cb2a7cb0eafb6c6" +checksum = "d9b1c5a481ec30a5abd8dfbd94ab5cf1bb4e9a66be7f1b3b322f2f1170c200fd" [[package]] name = "arrayref" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" +checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" [[package]] name = "arrayvec" @@ -240,14 +568,14 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time 0.3.17", + "time 0.3.21", ] [[package]] name = "asn1-rs" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf6690c370453db30743b373a60ba498fc0d6d83b11f4abfd87a84a075db5dd4" +checksum = "7f6fd5ddaf0351dff5b8da21b2fb4ff8e08ddd02857f0bf69c47639106c0fff0" dependencies = [ "asn1-rs-derive 0.4.0", "asn1-rs-impl", @@ -256,7 +584,7 @@ dependencies = [ "num-traits", "rusticata-macros", "thiserror", - "time 0.3.17", + "time 0.3.21", ] [[package]] @@ -267,7 +595,7 @@ checksum = "db8b7511298d5b7784b40b092d9e9dcd3a627a5707e4b5e507931ab0d44eeebf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] @@ -279,7 +607,7 @@ checksum = "726535892e8eae7e70657b4c8ea93d26b8553afb1ce617caee529ef96d7dee6c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] @@ -291,24 +619,19 @@ checksum = "2777730b2039ac0f95f093556e61b6d26cebed5393ca6f152717777cec3a42ed" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] -[[package]] -name = "asn1_der" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22d1f4b888c298a027c99dc9048015fac177587de20fc30232a057dfbe24a21" - [[package]] name = "assert_cmd" -version = "2.0.7" +version = "2.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa3d466004a8b4cb1bc34044240a2fd29d17607e2e3bd613eb44fd48e8100da3" +checksum = "86d6b683edf8d1119fe420a94f8a7e389239666aa72e65495d91c00462510151" dependencies = [ - "bstr 1.1.0", + "anstyle", + "bstr", "doc-comment", - "predicates", + "predicates 3.0.3", "predicates-core", "predicates-tree", "wait-timeout", @@ -320,66 +643,88 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +[[package]] +name = "async-channel" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf46fee83e5ccffc220104713af3292ff9bc7c64c7de289f66dae8e38d826833" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + [[package]] name = "async-io" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c374dda1ed3e7d8f0d9ba58715f924862c63eae6849c92d3a18e7fbde9e2794" +checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock", "autocfg", + "cfg-if", "concurrent-queue", "futures-lite", - "libc", "log", "parking", "polling", + "rustix 0.37.19", "slab", "socket2", "waker-fn", - "windows-sys 0.42.0", ] [[package]] name = "async-lock" -version = "2.6.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8101efe8695a6c17e02911402145357e718ac92d3ff88ae8419e84b1707b685" +checksum = "fa24f727524730b077666307f2734b4a1a1c57acb79193127dcc8914d5242dd7" dependencies = [ "event-listener", - "futures-lite", +] + +[[package]] +name = "async-recursion" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e97ce7de6cf12de5d7226c73f5ba9811622f4db3a5b91b55c53e987e5f91cba" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", ] [[package]] name = "async-stream" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" +checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" dependencies = [ "async-stream-impl", "futures-core", + "pin-project-lite 0.2.9", ] [[package]] name = "async-stream-impl" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" +checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "async-trait" -version = "0.1.60" +version = "0.1.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d1d8ab452a3936018a687b20e6f7cf5363d713b732b8884001317b0e48aa3" +checksum = "b9ccdd8f2a161be9bd5c023df56f1b2a0bd1d83872ae53b71a84a12c9bf6e842" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -397,9 +742,9 @@ dependencies = [ [[package]] name = "atomic-waker" -version = "1.0.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" +checksum = "1181e1e0d1fce796a03db1ae795d67167da795f9cf4a39c37589e85ef57f26d3" [[package]] name = "atty" @@ -424,12 +769,12 @@ version = "0.3.67" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca" dependencies = [ - "addr2line 0.19.0", + "addr2line", "cc", "cfg-if", "libc", - "miniz_oxide", - "object 0.30.0", + "miniz_oxide 0.6.2", + "object", "rustc-demangle", ] @@ -446,10 +791,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "349a06037c7bf932dd7e7d1f653678b2038b9ad46a74102f1fc7bd7872678cce" [[package]] -name = "base58" +name = "base16ct" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6107fe1be6682a68940da878d9e9f5e90ca5745b3dec9fd1bb393c8777d4f581" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" [[package]] name = "base64" @@ -458,93 +803,44 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" [[package]] -name = "base64ct" -version = "1.5.3" +name = "base64" +version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b645a089122eccb6111b4f81cbc1a49f5900ac4666bb93ac027feaecf15607bf" +checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" [[package]] -name = "beef" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" -dependencies = [ - "serde", -] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" [[package]] -name = "beefy-gadget" -version = "4.0.0-dev" +name = "basic-toml" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c0de75129aa8d0cceaf750b89013f0e08804d6ec61416da787b35ad0d7cddf1" dependencies = [ - "array-bytes", - "async-trait", - "fnv", - "futures", - "log", - "parity-scale-codec", - "parking_lot 0.12.1", - "sc-block-builder", - "sc-client-api", - "sc-consensus", - "sc-keystore", - "sc-network", - "sc-network-common", - "sc-network-gossip", - "sc-network-test", - "sc-utils", "serde", - "sp-api", - "sp-application-crypto", - "sp-arithmetic", - "sp-beefy", - "sp-blockchain", - "sp-consensus", - "sp-core", - "sp-finality-grandpa", - "sp-keyring", - "sp-keystore", - "sp-mmr-primitives", - "sp-runtime", - "sp-tracing", - "strum", - "substrate-prometheus-endpoint", - "substrate-test-runtime-client", - "tempfile", - "thiserror", - "tokio", - "wasm-timer", ] [[package]] -name = "beefy-gadget-rpc" -version = "4.0.0-dev" +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" dependencies = [ - "beefy-gadget", - "futures", - "jsonrpsee", - "log", - "parity-scale-codec", - "parking_lot 0.12.1", - "sc-rpc", "serde", - "serde_json", - "sp-beefy", - "sp-core", - "sp-runtime", - "substrate-test-runtime-client", - "thiserror", - "tokio", ] [[package]] -name = "beefy-merkle-tree" +name = "binary-merkle-tree" version = "4.0.0-dev" dependencies = [ "array-bytes", - "env_logger", + "env_logger 0.9.3", + "hash-db", "log", - "sp-api", - "sp-beefy", + "sp-core", "sp-runtime", ] @@ -559,9 +855,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.60.1" +version = "0.65.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "062dddbc1ba4aca46de6338e2bf87771414c335f7b2f2036e8f3e9befebf88e6" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" dependencies = [ "bitflags", "cexpr", @@ -569,11 +865,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", + "prettyplease 0.2.6", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", + "syn 2.0.18", ] [[package]] @@ -600,29 +898,29 @@ version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] name = "blake2b_simd" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72936ee4afc7f8f736d1c38383b56480b5497b4617b4a77bdbf1d2ababc76127" +checksum = "3c2f0dc9a68c6317d884f97cc36cf5a3d20ba14ce404227df55e1af708ab04bc" dependencies = [ "arrayref", "arrayvec 0.7.2", - "constant_time_eq 0.1.5", + "constant_time_eq", ] [[package]] name = "blake2s_simd" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db539cc2b5f6003621f1cd9ef92d7ded8ea5232c7de0f9faa2de251cd98730d4" +checksum = "6637f448b9e61dfadbdcbae9a885fadee1f3eaffb1f8d3c1965d3ade8bdfd44f" dependencies = [ "arrayref", "arrayvec 0.7.2", - "constant_time_eq 0.1.5", + "constant_time_eq", ] [[package]] @@ -635,7 +933,7 @@ dependencies = [ "arrayvec 0.7.2", "cc", "cfg-if", - "constant_time_eq 0.2.4", + "constant_time_eq", ] [[package]] @@ -656,16 +954,16 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] name = "block-buffer" -version = "0.10.3" +version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] @@ -694,28 +992,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae" [[package]] -name = "bs58" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "771fe0050b883fcc3ea2359b1a96bcfbc090b7116eae7c3c512c7a083fdf23d3" - -[[package]] -name = "bstr" -version = "0.2.17" +name = "bounded-collections" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +checksum = "eb5b05133427c07c4776906f673ccf36c21b102c9829c641a5b56bd151d44fd6" dependencies = [ - "lazy_static", - "memchr", - "regex-automata", + "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 = "bstr" -version = "1.1.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45ea9b00a7b3f2988e9a65ad3917e62123c38dba709b666506207be96d1790b" +checksum = "a246e68bb43f6cd9db24bea052a53e40405417c5fb372e3d1a8a7f770a564ef5" dependencies = [ "memchr", "once_cell", @@ -734,9 +1032,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.11.1" +version = "3.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" +checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1" [[package]] name = "byte-slice-cast" @@ -750,6 +1048,12 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" +[[package]] +name = "bytemuck" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17febce684fd15d89027105661fec94afb475cb995fbc59d2865198446ba2eea" + [[package]] name = "byteorder" version = "1.4.3" @@ -758,9 +1062,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "bzip2-sys" @@ -775,9 +1079,9 @@ dependencies = [ [[package]] name = "camino" -version = "1.1.1" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ad0e1e3e88dd237a156ab9f571021b8a158caa0ae44b1968a241efb5144c1e" +checksum = "c530edf18f37068ac2d977409ed5cd50d53d73bc653c7647b48eb78976ac9ae2" dependencies = [ "serde", ] @@ -793,15 +1097,16 @@ dependencies = [ [[package]] name = "cargo_metadata" -version = "0.14.2" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" +checksum = "eee4243f1f26fc7a42710e7439c149e2b10b05472f88090acce52632f231a73a" dependencies = [ "camino", "cargo-platform", - "semver 1.0.16", + "semver 1.0.17", "serde", "serde_json", + "thiserror", ] [[package]] @@ -812,9 +1117,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.78" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" dependencies = [ "jobserver", ] @@ -841,9 +1146,9 @@ dependencies = [ [[package]] name = "cfg-expr" -version = "0.10.3" +version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aacacf4d96c24b2ad6eb8ee6df040e4f27b0d0b39a5710c30091baa830485db" +checksum = "e70d3ad08698a0568b0562f22710fe6bfc1f4a61a367c77d0398c562eadd453a" dependencies = [ "smallvec", ] @@ -890,7 +1195,7 @@ name = "chain-spec-builder" version = "2.0.0" dependencies = [ "ansi_term", - "clap 4.0.32", + "clap 4.3.2", "node-cli", "rand 0.8.5", "sc-chain-spec", @@ -901,24 +1206,51 @@ dependencies = [ [[package]] name = "chrono" -version = "0.4.23" +version = "0.4.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" +checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5" dependencies = [ + "android-tzdata", "iana-time-zone", "js-sys", - "num-integer", "num-traits", "time 0.1.45", "wasm-bindgen", "winapi", ] +[[package]] +name = "ciborium" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "effd91f6c78e5a4ace8a5d3c0b6bfaec9e2baaef55f3efc00e45fb2e477ee926" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdf919175532b369853f5d5e20b26b43112613fd6fe7aee757e35f7a44642656" + +[[package]] +name = "ciborium-ll" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defaa24ecc093c77630e6c15e17c51f5e187bf35ee514f4e2d67baaa96dae22b" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "cid" -version = "0.8.6" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ed9c8b2d17acb8110c46f1da5bf4a696d745e1474a16db0cd2b49cd0249bf2" +checksum = "b9b68e3193982cd54187d71afdb2a271ad4cf8af157858e9cb911b91321de143" dependencies = [ "core2", "multibase", @@ -933,7 +1265,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "12f8e7987cbd042a63249497f41aed09f8e65add917ea6566effbc56578d6801" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] @@ -942,7 +1274,17 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7" dependencies = [ - "generic-array 0.14.6", + "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", ] [[package]] @@ -956,9 +1298,9 @@ dependencies = [ [[package]] name = "clang-sys" -version = "1.4.0" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa2e27ae6ab525c3d369ded447057bca5438d86dc3a68f6faafb8269ba82ebf3" +checksum = "c688fc74432808e3eb684cae8830a86be1d66a2bd58e1f248ed0960a590baf6f" dependencies = [ "glob", "libc", @@ -967,61 +1309,76 @@ dependencies = [ [[package]] name = "clap" -version = "2.34.0" +version = "3.2.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" dependencies = [ "bitflags", + "clap_lex 0.2.4", + "indexmap", "textwrap", - "unicode-width", ] [[package]] name = "clap" -version = "4.0.32" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7db700bc935f9e43e88d00b0850dae18a63773cfbec6d8e070fccf7fef89a39" +checksum = "401a4694d2bf92537b6867d94de48c4842089645fdcdf6c71865b175d836e9c2" dependencies = [ - "bitflags", + "clap_builder", "clap_derive", - "clap_lex", - "is-terminal", "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72394f3339a76daf211e57d4bcb374410f3965dcc606dd0e03738c7888766980" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex 0.5.0", "strsim", - "termcolor", ] [[package]] name = "clap_complete" -version = "4.0.7" +version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10861370d2ba66b0f5989f83ebf35db6421713fd92351790e7fdd6c36774c56b" +checksum = "7f6b5c519bab3ea61843a7923d074b04245624bb84a64a8c150f5deb014e388b" dependencies = [ - "clap 4.0.32", + "clap 4.3.2", ] [[package]] name = "clap_derive" -version = "4.0.21" +version = "4.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0177313f9f02afc995627906bbd8967e2be069f5261954222dac78290c2b9014" +checksum = "b8cd2b2a819ad6eec39e8f1d6b53001af1e5469f8c177579cdaeb313115b825f" dependencies = [ "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "clap_lex" -version = "0.3.0" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d4198f73e42b4936b35b5bb248d81d2b595ecb170da0bac7655c54eedfa8da8" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" dependencies = [ "os_str_bytes", ] +[[package]] +name = "clap_lex" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" + [[package]] name = "codespan-reporting" version = "0.11.1" @@ -1032,43 +1389,68 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "colorchoice" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" + [[package]] name = "comfy-table" -version = "6.1.4" +version = "7.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e7b787b0dc42e8111badfdbe4c3059158ccb2db8780352fa1b01e8ccf45cc4d" +checksum = "f9e1f7e5d046697d34b593bdba8ee31f4649366e452a2ccabb3baf3511e503d1" dependencies = [ "strum", "strum_macros", "unicode-width", ] +[[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.0.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd7bef69dc86e3c610e4e7aed41035e2a7ed12e72dd7530f61327a6579a4390b" +checksum = "62ec6771ecfa0762d24683ee5a32ad78487a3d3afdc0fb8cae19d2c5deb50b7c" dependencies = [ "crossbeam-utils", ] +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.45.0", +] + [[package]] name = "const-oid" -version = "0.9.1" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cec318a675afcb6a1ea1d4340e2d377e56e47c266f28043ceccbf4412ddfdd3b" +checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" [[package]] name = "constant_time_eq" -version = "0.1.5" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "13418e745008f7349ec7e449155f419a61b92b58a99cc3616942b926825ec76b" [[package]] -name = "constant_time_eq" -version = "0.2.4" +name = "constcat" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3ad85c1f65dc7b37604eb0e89748faf0b9653065f2a8ef69f96a687ec1e9279" +checksum = "f272d0c4cf831b4fa80ee529c7707f76585986e910e1fbce1d7921970bc1a241" [[package]] name = "core-foundation" @@ -1082,9 +1464,9 @@ dependencies = [ [[package]] name = "core-foundation-sys" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" +checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa" [[package]] name = "core2" @@ -1106,42 +1488,36 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.5" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +checksum = "3e4c1eaa2012c47becbbad2ab175484c2a84d1185b566fb2cc5b8707343dfe58" dependencies = [ "libc", ] -[[package]] -name = "cpuid-bool" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" - [[package]] name = "cranelift-bforest" -version = "0.88.2" +version = "0.95.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52056f6d0584484b57fa6c1a65c1fcb15f3780d8b6a758426d9e3084169b2ddd" +checksum = "1277fbfa94bc82c8ec4af2ded3e639d49ca5f7f3c7eeab2c66accd135ece4e70" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.88.2" +version = "0.95.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fed94c8770dc25d01154c3ffa64ed0b3ba9d583736f305fed7beebe5d9cf74" +checksum = "c6e8c31ad3b2270e9aeec38723888fe1b0ace3bea2b06b3f749ccf46661d3220" dependencies = [ - "arrayvec 0.7.2", "bumpalo", "cranelift-bforest", "cranelift-codegen-meta", "cranelift-codegen-shared", "cranelift-entity", "cranelift-isle", - "gimli 0.26.2", + "gimli", + "hashbrown 0.13.2", "log", "regalloc2", "smallvec", @@ -1150,33 +1526,33 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.88.2" +version = "0.95.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c451b81faf237d11c7e4f3165eeb6bac61112762c5cfe7b4c0fb7241474358f" +checksum = "c8ac5ac30d62b2d66f12651f6b606dbdfd9c2cfd0908de6b387560a277c5c9da" dependencies = [ "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.88.2" +version = "0.95.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c940133198426d26128f08be2b40b0bd117b84771fd36798969c4d712d81fc" +checksum = "dd82b8b376247834b59ed9bdc0ddeb50f517452827d4a11bccf5937b213748b8" [[package]] name = "cranelift-entity" -version = "0.88.2" +version = "0.95.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87a0f1b2fdc18776956370cf8d9b009ded3f855350c480c1c52142510961f352" +checksum = "40099d38061b37e505e63f89bab52199037a72b931ad4868d9089ff7268660b0" dependencies = [ "serde", ] [[package]] name = "cranelift-frontend" -version = "0.88.2" +version = "0.95.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34897538b36b216cc8dd324e73263596d51b8cf610da6498322838b2546baf8a" +checksum = "64a25d9d0a0ae3079c463c34115ec59507b4707175454f0eee0891e83e30e82d" dependencies = [ "cranelift-codegen", "log", @@ -1186,15 +1562,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.88.2" +version = "0.95.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b2629a569fae540f16a76b70afcc87ad7decb38dc28fa6c648ac73b51e78470" +checksum = "80de6a7d0486e4acbd5f9f87ec49912bf4c8fb6aea00087b989685460d4469ba" [[package]] name = "cranelift-native" -version = "0.88.2" +version = "0.95.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20937dab4e14d3e225c5adfc9c7106bafd4ac669bdb43027b911ff794c6fb318" +checksum = "bb6b03e0e03801c4b3fd8ce0758a94750c07a44e7944cc0ffbf0d3f2e7c79b00" dependencies = [ "cranelift-codegen", "libc", @@ -1203,9 +1579,9 @@ dependencies = [ [[package]] name = "cranelift-wasm" -version = "0.88.2" +version = "0.95.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80fc2288957a94fd342a015811479de1837850924166d1f1856d8406e6f3609b" +checksum = "ff3220489a3d928ad91e59dd7aeaa8b3de18afb554a6211213673a71c90737ac" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -1219,18 +1595,18 @@ dependencies = [ [[package]] name = "crc" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3" +checksum = "86ec7a15cbe22e59248fc7eadb1907dab5ba09372595da4d73dd805ed4417dfe" dependencies = [ "crc-catalog", ] [[package]] name = "crc-catalog" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff" +checksum = "9cace84e55f07e7301bae1c519df89cdad8cc3cd868413d3fdbdeca9ff3db484" [[package]] name = "crc32fast" @@ -1243,15 +1619,16 @@ dependencies = [ [[package]] name = "criterion" -version = "0.3.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b01d6de93b2b6c65e17c634a26653a29d107b3c98c607c765bf38d041531cd8f" +checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" dependencies = [ + "anes", "atty", "cast", - "clap 2.34.0", + "ciborium", + "clap 3.2.25", "criterion-plot", - "csv", "futures", "itertools", "lazy_static", @@ -1261,7 +1638,6 @@ dependencies = [ "rayon", "regex", "serde", - "serde_cbor", "serde_derive", "serde_json", "tinytemplate", @@ -1271,9 +1647,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.4.5" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2673cc8207403546f45f5fd319a974b1e6983ad1a3ee7e6041650013be041876" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", "itertools", @@ -1281,9 +1657,9 @@ dependencies = [ [[package]] name = "crossbeam-channel" -version = "0.5.6" +version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +checksum = "a33c2bf77f2df06183c3aa30d1e96c0695a313d4f9c453cc3762a6db39f99200" dependencies = [ "cfg-if", "crossbeam-utils", @@ -1291,9 +1667,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" dependencies = [ "cfg-if", "crossbeam-epoch", @@ -1302,22 +1678,22 @@ dependencies = [ [[package]] name = "crossbeam-epoch" -version = "0.9.13" +version = "0.9.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +checksum = "46bd5f3f85273295a9d14aedfb86f6aadbff6d8f5295c4a9edb08e819dcf5695" dependencies = [ "autocfg", "cfg-if", "crossbeam-utils", - "memoffset 0.7.1", + "memoffset 0.8.0", "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.14" +version = "0.8.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +checksum = "3c063cd8cc95f5c377ed0d4b49a4b21f632396ff690e8470c29b3359b346984b" dependencies = [ "cfg-if", ] @@ -1334,7 +1710,19 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef2b4b23cddf68b89b8f8069890e8c270d54e2d5fe1b143820234805e4cb17ef" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4c2f4e1afd912bc40bfd6fed5d9dc1f288e0ba01bfcc835cc5bc3eb13efe15" +dependencies = [ + "generic-array 0.14.7", "rand_core 0.6.4", "subtle", "zeroize", @@ -1346,7 +1734,8 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", + "rand_core 0.6.4", "typenum", ] @@ -1356,17 +1745,7 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" dependencies = [ - "generic-array 0.14.6", - "subtle", -] - -[[package]] -name = "crypto-mac" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff07008ec701e8028e2ceb8f83f0e4274ee62bd2dbdc4fefff2e9a91824081a" -dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", "subtle", ] @@ -1376,32 +1755,10 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", "subtle", ] -[[package]] -name = "csv" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" -dependencies = [ - "bstr 0.2.17", - "csv-core", - "itoa 0.4.8", - "ryu", - "serde", -] - -[[package]] -name = "csv-core" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b2466559f260f48ad25fe6317b3c8dac77b5bdb5763ac7d9d6103530663bc90" -dependencies = [ - "memchr", -] - [[package]] name = "ctor" version = "0.1.26" @@ -1409,25 +1766,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "ctr" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb4a30d54f7443bf3d6191dcd486aca19e67cb3c49fa7a06a319966346707e7f" +checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" dependencies = [ - "cipher 0.2.5", + "cipher 0.3.0", ] [[package]] name = "ctr" -version = "0.8.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "049bb91fb4aaf0e3c7efa6cd5ef877dbbbd15b39dad06d9948de4ec8a75761ea" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" dependencies = [ - "cipher 0.3.0", + "cipher 0.4.4", ] [[package]] @@ -1458,23 +1815,23 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.0.0-pre.5" +version = "4.0.0-rc.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67bc65846be335cb20f4e52d49a437b773a2c1fdb42b19fc84e79e6f6771536f" +checksum = "8d4ba9852b42210c7538b75484f9daa0655e9a3ac04f693747bb0f02cf3cfe16" dependencies = [ "cfg-if", "fiat-crypto", "packed_simd_2", - "platforms 3.0.2", + "platforms", "subtle", "zeroize", ] [[package]] name = "cxx" -version = "1.0.85" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5add3fc1717409d029b20c5b6903fc0c0b02fa6741d820054f4a2efa5e5816fd" +checksum = "109308c20e8445959c2792e81871054c6a17e6976489a93d2769641a2ba5839c" dependencies = [ "cc", "cxxbridge-flags", @@ -1484,9 +1841,9 @@ dependencies = [ [[package]] name = "cxx-build" -version = "1.0.85" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c87959ba14bc6fbc61df77c3fcfe180fc32b93538c4f1031dd802ccb5f2ff0" +checksum = "daf4c6755cdf10798b97510e0e2b3edb9573032bd9379de8fffa59d68165494f" dependencies = [ "cc", "codespan-reporting", @@ -1494,31 +1851,31 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 2.0.18", ] [[package]] name = "cxxbridge-flags" -version = "1.0.85" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69a3e162fde4e594ed2b07d0f83c6c67b745e7f28ce58c6df5e6b6bef99dfb59" +checksum = "882074421238e84fe3b4c65d0081de34e5b323bf64555d3e61991f76eb64a7bb" [[package]] name = "cxxbridge-macro" -version = "1.0.85" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" +checksum = "4a076022ece33e7686fb76513518e219cca4fce5750a8ae6d1ce6c0f48fd1af9" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "darling" -version = "0.14.2" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0dd3cd20dc6b5a876612a6e5accfe7f3dd883db6d07acfbf14c128f61550dfa" +checksum = "7b750cb3417fd1b327431a470f388520309479ab0bf5e323505daf0290cd3850" dependencies = [ "darling_core", "darling_macro", @@ -1526,40 +1883,40 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.14.2" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a784d2ccaf7c98501746bf0be29b2022ba41fd62a2e622af997a03e9f972859f" +checksum = "109c1ca6e6b7f82cc233a97004ea8ed7ca123a9af07a8230878fcfda9b158bf0" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim", - "syn", + "syn 1.0.109", ] [[package]] name = "darling_macro" -version = "0.14.2" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7618812407e9402654622dd402b0a89dff9ba93badd6540781526117b92aab7e" +checksum = "a4aab4dbc9f7611d8b55048a3a16d2d010c2c8334e46304b40ac1cc14bf3b48e" dependencies = [ "darling_core", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "data-encoding" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" +checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308" [[package]] name = "data-encoding-macro" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86927b7cd2fe88fa698b87404b287ab98d1a0063a34071d92e575b72d3029aca" +checksum = "c904b33cc60130e1aeea4956ab803d08a3f4a0ca82d64ed757afac3891f2bb99" dependencies = [ "data-encoding", "data-encoding-macro-internal", @@ -1567,12 +1924,12 @@ dependencies = [ [[package]] name = "data-encoding-macro-internal" -version = "0.1.10" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5bbed42daaa95e780b60a50546aa345b8413a1e46f9a40a12907d3598f038db" +checksum = "8fdf3fce3ce863539ec1d7fd1b6dcc3c645663376b43ed376bbf887733e4f772" dependencies = [ "data-encoding", - "syn", + "syn 1.0.109", ] [[package]] @@ -1586,6 +1943,16 @@ dependencies = [ "zeroize", ] +[[package]] +name = "der" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56acb310e15652100da43d130af8d97b509e95af61aab1c5a7939ef24337ee17" +dependencies = [ + "const-oid", + "zeroize", +] + [[package]] name = "der-parser" version = "7.0.0" @@ -1602,11 +1969,11 @@ dependencies = [ [[package]] name = "der-parser" -version = "8.1.0" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42d4bc9b0db0a0df9ae64634ac5bdefb7afcb534e182275ca0beadbe486701c1" +checksum = "dbd676fbbab537128ef0278adb5576cf363cff6aa22a7b24effe97347cfab61e" dependencies = [ - "asn1-rs 0.5.1", + "asn1-rs 0.5.2", "displaydoc", "nom", "num-bigint", @@ -1614,6 +1981,17 @@ dependencies = [ "rusticata-macros", ] +[[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" @@ -1622,7 +2000,7 @@ checksum = "e79116f119dd1dba1abf1f3405f03b9b0e79a27a3883864bfebded8a3dc768cd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1643,7 +2021,7 @@ dependencies = [ "darling", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1653,7 +2031,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68" dependencies = [ "derive_builder_core", - "syn", + "syn 1.0.109", ] [[package]] @@ -1664,7 +2042,7 @@ checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1694,16 +2072,17 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", ] [[package]] name = "digest" -version = "0.10.6" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ - "block-buffer 0.10.3", + "block-buffer 0.10.4", + "const-oid", "crypto-common", "subtle", ] @@ -1751,20 +2130,20 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bf95dc3f046b9da4f2d51833c0d3547d8564ef6910f5c1ed130306a75b92886" +checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "dissimilar" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd5f0c7e4bd266b8ab2550e6238d2e74977c23c15536ac7be45e9c95e2e3fbbb" +checksum = "210ec60ae7d710bed8683e333e9d2855a8a56a3e9892b38bad3bb0d4d29b0d5e" [[package]] name = "doc-comment" @@ -1772,6 +2151,32 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" +[[package]] +name = "docify" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6491709f76fb7ceb951244daf624d480198b427556084391d6e3c33d3ae74b9" +dependencies = [ + "docify_macros", +] + +[[package]] +name = "docify_macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc5338a9f72ce29a81377d9039798fcc926fb471b2004666caf48e446dffbbf" +dependencies = [ + "common-path", + "derive-syn-parse", + "once_cell", + "proc-macro2", + "quote", + "regex", + "syn 2.0.18", + "termcolor", + "walkdir", +] + [[package]] name = "downcast" version = "0.11.0" @@ -1786,9 +2191,9 @@ checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" [[package]] name = "dtoa" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c00704156a7de8df8da0911424e30c2049957b0a714542a44e05fe693dd85313" +checksum = "65d09067bfacaa79114679b279d7f5885b53295b1e2cfb4e79c8e4bd3d633169" [[package]] name = "dyn-clonable" @@ -1808,14 +2213,14 @@ checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "dyn-clone" -version = "1.0.10" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b0705efd4599c15a38151f4721f7bc388306f61084d3bfd50bd07fbca5cb60" +checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" [[package]] name = "ecdsa" @@ -1823,19 +2228,33 @@ version = "0.14.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "413301934810f597c1d19ca71c8710e99a3f1ba28a0d2ebc01551a2daeea3c5c" dependencies = [ - "der", - "elliptic-curve", - "rfc6979", - "signature", + "der 0.6.1", + "elliptic-curve 0.12.3", + "rfc6979 0.3.1", + "signature 1.6.4", +] + +[[package]] +name = "ecdsa" +version = "0.16.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0997c976637b606099b9985693efa3581e84e41f5c11ba5255f88711058ad428" +dependencies = [ + "der 0.7.6", + "digest 0.10.7", + "elliptic-curve 0.13.5", + "rfc6979 0.4.0", + "signature 2.1.0", + "spki 0.7.2", ] [[package]] name = "ed25519" -version = "1.5.2" +version = "1.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" +checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" dependencies = [ - "signature", + "signature 1.6.4", ] [[package]] @@ -1868,9 +2287,9 @@ dependencies = [ [[package]] name = "either" -version = "1.8.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "elliptic-curve" @@ -1878,22 +2297,47 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7bb888ab5300a19b8e5bceef25ac745ad065f3c9f7efc6de1b91958110891d3" dependencies = [ - "base16ct", - "crypto-bigint", - "der", - "digest 0.10.6", - "ff", - "generic-array 0.14.6", - "group", + "base16ct 0.1.1", + "crypto-bigint 0.4.9", + "der 0.6.1", + "digest 0.10.7", + "ff 0.12.1", + "generic-array 0.14.7", + "group 0.12.1", "hkdf", "pem-rfc7468", - "pkcs8", + "pkcs8 0.9.0", + "rand_core 0.6.4", + "sec1 0.3.0", + "subtle", + "zeroize", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "968405c8fdc9b3bf4df0a6638858cc0b52462836ab6b1c87377785dd09cf1c0b" +dependencies = [ + "base16ct 0.2.0", + "crypto-bigint 0.5.2", + "digest 0.10.7", + "ff 0.13.0", + "generic-array 0.14.7", + "group 0.13.0", + "pkcs8 0.10.2", "rand_core 0.6.4", - "sec1", + "sec1 0.7.2", "subtle", "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" @@ -1903,27 +2347,27 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "enumflags2" -version = "0.7.5" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e75d4cd21b95383444831539909fbb14b9dc3fdceb2a6f5d36577329a1f55ccb" +checksum = "c041f5090df68b32bcd905365fd51769c8b9d553fe87fde0b683534f10c01bd2" dependencies = [ "enumflags2_derive", ] [[package]] name = "enumflags2_derive" -version = "0.7.4" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f58dc3c5e468259f19f2d46304a6b28f1c3d034442e14b322d2b850e36f6d5ae" +checksum = "5e9a1f9f7d83e59740248a6e14ecf93929ade55027844dfcea78beafccc15745" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -1939,6 +2383,19 @@ dependencies = [ "termcolor", ] +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + [[package]] name = "environmental" version = "1.1.4" @@ -1947,13 +2404,13 @@ checksum = "e48c92028aaa870e83d51c64e5d4e0b6981b360c522198c23959f219a4e1b15b" [[package]] name = "errno" -version = "0.2.8" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a" dependencies = [ "errno-dragonfly", "libc", - "winapi", + "windows-sys 0.48.0", ] [[package]] @@ -1981,6 +2438,19 @@ dependencies = [ "futures", ] +[[package]] +name = "expander" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f86a749cf851891866c10515ef6c299b5c69661465e9c3bbe7e07a2b77fb0f7" +dependencies = [ + "blake2", + "fs-err", + "proc-macro2", + "quote", + "syn 2.0.18", +] + [[package]] name = "fake-simd" version = "0.1.2" @@ -1995,9 +2465,9 @@ checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" [[package]] name = "fastrand" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be" dependencies = [ "instant", ] @@ -2021,39 +2491,49 @@ dependencies = [ "subtle", ] +[[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", +] + [[package]] name = "fiat-crypto" -version = "0.1.17" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a214f5bb88731d436478f3ae1f8a277b62124089ba9fb67f4f93fb100ef73c90" +checksum = "e825f6987101665dea6ec934c09ec6d721de7bc1bf92248e1d5810c8cd636b77" [[package]] name = "file-per-thread-logger" -version = "0.1.5" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e16290574b39ee41c71aeb90ae960c504ebaf1e2a1c87bd52aa56ed6e1a02f" +checksum = "84f2e425d9790201ba4af4630191feac6dcc98765b118d4d18e91d23c2353866" dependencies = [ - "env_logger", + "env_logger 0.10.0", "log", ] [[package]] name = "filetime" -version = "0.2.19" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e884668cd0c7480504233e951174ddc3b382f7c2666e3b7310b5c4e7b0c37f9" +checksum = "5cbc844cecaee9d4443931972e1289c8ff485cb4cc2767cb03ca139ed6885153" dependencies = [ "cfg-if", "libc", - "redox_syscall", - "windows-sys 0.42.0", + "redox_syscall 0.2.16", + "windows-sys 0.48.0", ] [[package]] name = "finality-grandpa" -version = "0.16.1" +version = "0.16.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24e6c429951433ccb7c87fd528c60084834dcd14763182c1f83291bcde24c34" +checksum = "36530797b9bf31cd4ff126dcfee8170f86b00cfdcea3269d73133cc0415945c3" dependencies = [ "either", "futures", @@ -2086,13 +2566,13 @@ checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" [[package]] name = "flate2" -version = "1.0.25" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +checksum = "3b9429470923de8e8cbd4d2dc513535400b4b3fef0319fb5c4e1f520a7bef743" dependencies = [ "crc32fast", "libz-sys", - "miniz_oxide", + "miniz_oxide 0.7.1", ] [[package]] @@ -2119,13 +2599,23 @@ dependencies = [ [[package]] name = "form_urlencoded" -version = "1.1.0" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" dependencies = [ "percent-encoding", ] +[[package]] +name = "fraction" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3027ae1df8d41b4bed2241c8fdad4acc1e7af60c8e17743534b545e77182d678" +dependencies = [ + "lazy_static", + "num", +] + [[package]] name = "fragile" version = "2.0.0" @@ -2138,6 +2628,7 @@ version = "4.0.0-dev" dependencies = [ "array-bytes", "frame-support", + "frame-support-procedural", "frame-system", "linregress", "log", @@ -2155,6 +2646,7 @@ dependencies = [ "sp-runtime-interface", "sp-std", "sp-storage", + "static_assertions", ] [[package]] @@ -2164,7 +2656,7 @@ dependencies = [ "Inflector", "array-bytes", "chrono", - "clap 4.0.32", + "clap 4.3.2", "comfy-table", "frame-benchmarking", "frame-support", @@ -2193,16 +2685,31 @@ dependencies = [ "sp-database", "sp-externalities", "sp-inherents", + "sp-io", "sp-keystore", "sp-runtime", "sp-state-machine", - "sp-std", "sp-storage", "sp-trie", + "sp-wasm-interface", "thiserror", "thousands", ] +[[package]] +name = "frame-benchmarking-pallet-pov" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "frame-election-provider-solution-type" version = "4.0.0-dev" @@ -2215,7 +2722,7 @@ dependencies = [ "quote", "scale-info", "sp-arithmetic", - "syn", + "syn 2.0.18", "trybuild", ] @@ -2241,7 +2748,7 @@ dependencies = [ name = "frame-election-solution-type-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.0.32", + "clap 4.3.2", "frame-election-provider-solution-type", "frame-election-provider-support", "frame-support", @@ -2277,9 +2784,9 @@ dependencies = [ [[package]] name = "frame-metadata" -version = "15.0.0" +version = "16.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df6bb8542ef006ef0de09a5c4420787d79823c0ed7924225822362fd2bf2ff2d" +checksum = "87cf1549fba25a6fcac22785b61698317d958e96cac72a59102ea45b9ae64692" dependencies = [ "cfg-if", "parity-scale-codec", @@ -2291,8 +2798,11 @@ dependencies = [ name = "frame-remote-externalities" version = "0.10.0-dev" dependencies = [ + "async-recursion", "frame-support", "futures", + "indicatif", + "jsonrpsee", "log", "pallet-elections-phragmen", "parity-scale-codec", @@ -2300,24 +2810,29 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime", + "spinners", "substrate-rpc-client", "tokio", - "tracing-subscriber 0.3.16", + "tokio-retry", + "tracing-subscriber 0.3.17", ] [[package]] name = "frame-support" version = "4.0.0-dev" dependencies = [ + "aquamarine", + "array-bytes", "assert_matches", "bitflags", + "environmental", "frame-metadata", "frame-support-procedural", "frame-system", "impl-trait-for-tuples", "k256", "log", - "once_cell", + "macro_magic", "parity-scale-codec", "paste", "pretty_assertions", @@ -2329,6 +2844,7 @@ dependencies = [ "sp-arithmetic", "sp-core", "sp-core-hashing-proc-macro", + "sp-debug-derive", "sp-inherents", "sp-io", "sp-runtime", @@ -2337,7 +2853,6 @@ dependencies = [ "sp-std", "sp-tracing", "sp-weights", - "static_assertions", "tt-call", ] @@ -2348,11 +2863,14 @@ dependencies = [ "Inflector", "cfg-expr", "derive-syn-parse", + "expander", "frame-support-procedural-tools", "itertools", + "macro_magic", + "proc-macro-warning", "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -2363,7 +2881,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -2372,7 +2890,7 @@ version = "3.0.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -2380,6 +2898,7 @@ name = "frame-support-test" version = "3.0.0" dependencies = [ "frame-benchmarking", + "frame-executive", "frame-support", "frame-support-test-pallet", "frame-system", @@ -2388,6 +2907,7 @@ dependencies = [ "rustversion", "scale-info", "serde", + "sp-api", "sp-arithmetic", "sp-core", "sp-io", @@ -2395,6 +2915,7 @@ dependencies = [ "sp-state-machine", "sp-std", "sp-version", + "static_assertions", "trybuild", ] @@ -2419,12 +2940,15 @@ dependencies = [ "frame-system", "parity-scale-codec", "scale-info", + "serde", + "sp-runtime", ] [[package]] name = "frame-system" version = "4.0.0-dev" dependencies = [ + "cfg-if", "criterion", "frame-support", "log", @@ -2451,9 +2975,11 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-core", + "sp-externalities", "sp-io", "sp-runtime", "sp-std", + "sp-version", ] [[package]] @@ -2475,6 +3001,12 @@ dependencies = [ "sp-std", ] +[[package]] +name = "fs-err" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0845fa252299212f0389d64ba26f34fa32cfe41588355f21ed507c59a0f64541" + [[package]] name = "fs2" version = "0.4.3" @@ -2485,11 +3017,21 @@ dependencies = [ "winapi", ] +[[package]] +name = "fs4" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7672706608ecb74ab2e055c68327ffc25ae4cac1e12349204fd5fb0f3487cce2" +dependencies = [ + "rustix 0.37.19", + "windows-sys 0.48.0", +] + [[package]] name = "fs_extra" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2022715d62ab30faffd124d40b76f4134a550a87792276512b18d63272333394" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" [[package]] name = "funty" @@ -2499,9 +3041,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" dependencies = [ "futures-channel", "futures-core", @@ -2514,9 +3056,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", "futures-sink", @@ -2524,15 +3066,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" [[package]] name = "futures-executor" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" dependencies = [ "futures-core", "futures-task", @@ -2542,15 +3084,15 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" -version = "1.12.0" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand", "futures-core", @@ -2563,13 +3105,13 @@ dependencies = [ [[package]] name = "futures-macro" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -2579,21 +3121,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2411eed028cdf8c8034eaf21f9915f956b6c3abec4d4c7949ee67f0721127bd" dependencies = [ "futures-io", - "rustls 0.20.7", + "rustls 0.20.8", "webpki 0.22.0", ] [[package]] name = "futures-sink" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-timer" @@ -2603,9 +3145,9 @@ checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" [[package]] name = "futures-util" -version = "0.3.25" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ "futures-channel", "futures-core", @@ -2636,9 +3178,9 @@ dependencies = [ "frame-election-provider-support", "frame-support", "frame-system", - "git2", "num-format", "pallet-staking", + "sp-staking", ] [[package]] @@ -2652,12 +3194,13 @@ dependencies = [ [[package]] name = "generic-array" -version = "0.14.6" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -2683,79 +3226,62 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] name = "ghash" -version = "0.3.1" +version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97304e4cd182c3846f7575ced3890c53012ce534ad9114046b0a9e00bb30a375" +checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" dependencies = [ "opaque-debug 0.3.0", - "polyval 0.4.5", + "polyval 0.5.3", ] [[package]] name = "ghash" -version = "0.4.4" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1583cc1656d7839fd3732b80cf4f38850336cdb9b8ded1cd399ca62958de3c99" +checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" dependencies = [ "opaque-debug 0.3.0", - "polyval 0.5.3", + "polyval 0.6.0", ] [[package]] name = "gimli" -version = "0.26.2" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4" dependencies = [ "fallible-iterator", "indexmap", "stable_deref_trait", ] -[[package]] -name = "gimli" -version = "0.27.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dec7af912d60cdbd3677c1af9352ebae6fb8394d165568a2234df0fa00f87793" - -[[package]] -name = "git2" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be36bc9e0546df253c0cc41fd0af34f5e92845ad8509462ec76672fac6997f5b" -dependencies = [ - "bitflags", - "libc", - "libgit2-sys", - "log", - "url", -] - [[package]] name = "glob" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] name = "globset" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" dependencies = [ - "aho-corasick", - "bstr 0.2.17", + "aho-corasick 0.7.20", + "bstr", "fnv", "log", "regex", @@ -2767,16 +3293,27 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ - "ff", + "ff 0.12.1", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.0", "rand_core 0.6.4", "subtle", ] [[package]] name = "h2" -version = "0.3.15" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782" dependencies = [ "bytes", "fnv", @@ -2799,9 +3336,9 @@ checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" [[package]] name = "handlebars" -version = "4.3.6" +version = "4.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "035ef95d03713f2c347a72547b7cd38cbc9af7cd51e6099fb62d586d4a6dee3a" +checksum = "83c3372087601b532857d332f5957cbae686da52bb7810bf038c3e3c3cc2fa0d" dependencies = [ "log", "pest", @@ -2813,9 +3350,9 @@ dependencies = [ [[package]] name = "hash-db" -version = "0.15.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23bd4e7b5eda0d0f3a307e8b381fdc8ba9000f26fbe912250c0a4cc3956364a" +checksum = "8e7d7786361d7425ae2fe4f9e407eb0efaa0840f5212d109cc018c40c35c6ab4" [[package]] name = "hash256-std-hasher" @@ -2840,12 +3377,25 @@ name = "hashbrown" version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash 0.8.3", +] + +[[package]] +name = "hashbrown" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +dependencies = [ + "ahash 0.8.3", + "allocator-api2", +] [[package]] name = "heck" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" [[package]] name = "hermit-abi" @@ -2865,12 +3415,24 @@ dependencies = [ "libc", ] +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "hex-literal" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebdb29d2ea9ed0083cd8cece49bbd968021bd99b0849edb4a9a7ee0fdf6a4e0" + [[package]] name = "hkdf" version = "0.12.3" @@ -2890,16 +3452,6 @@ dependencies = [ "digest 0.9.0", ] -[[package]] -name = "hmac" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1441c6b1e930e2817404b5046f1f989899143a12bf92de603b69f4e0aee1e15" -dependencies = [ - "crypto-mac 0.10.1", - "digest 0.9.0", -] - [[package]] name = "hmac" version = "0.11.0" @@ -2916,7 +3468,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -2926,7 +3478,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" dependencies = [ "digest 0.9.0", - "generic-array 0.14.6", + "generic-array 0.14.7", "hmac 0.8.1", ] @@ -2955,13 +3507,13 @@ dependencies = [ [[package]] name = "http" -version = "0.2.8" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes", "fnv", - "itoa 1.0.5", + "itoa", ] [[package]] @@ -3001,9 +3553,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.23" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", @@ -3014,7 +3566,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.5", + "itoa", "pin-project-lite 0.2.9", "socket2", "tokio", @@ -3032,34 +3584,49 @@ dependencies = [ "http", "hyper", "log", - "rustls 0.20.7", + "rustls 0.20.8", + "rustls-native-certs", + "tokio", + "tokio-rustls 0.23.4", + "webpki-roots", +] + +[[package]] +name = "hyper-rustls" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0646026eb1b3eea4cd9ba47912ea5ce9cc07713d105b1a14698f4e6433d348b7" +dependencies = [ + "http", + "hyper", + "log", + "rustls 0.21.1", "rustls-native-certs", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.0", ] [[package]] name = "iana-time-zone" -version = "0.1.53" +version = "0.1.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" +checksum = "0722cd7114b7de04316e7ea5456a0bbb20e4adb46fd27a3697adb812cff0f37c" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "winapi", + "windows 0.48.0", ] [[package]] name = "iana-time-zone-haiku" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ - "cxx", - "cxx-build", + "cc", ] [[package]] @@ -3081,9 +3648,9 @@ dependencies = [ [[package]] name = "idna" -version = "0.3.0" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" dependencies = [ "unicode-bidi", "unicode-normalization", @@ -3101,9 +3668,9 @@ dependencies = [ [[package]] name = "if-watch" -version = "3.0.0" +version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba7abdbb86e485125dad06c2691e1e393bf3b08c7b743b43aa162a00fd39062e" +checksum = "a9465340214b296cd17a0009acdb890d6160010b8adf8f78a00d0d7ab270f79f" dependencies = [ "async-io", "core-foundation", @@ -3115,7 +3682,7 @@ dependencies = [ "rtnetlink", "system-configuration", "tokio", - "windows", + "windows 0.34.0", ] [[package]] @@ -3127,6 +3694,17 @@ 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.4.0" @@ -3140,18 +3718,37 @@ dependencies = [ name = "impl-trait-for-tuples" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "include_dir" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18762faeff7122e89e0857b02f7ce6fcc0d101d5e9ad2ad7846cc01d61b7f19e" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b139284b5cf57ecfa712bcc66950bb635b31aff41c188e8a4cfc758eca374a3f" dependencies = [ "proc-macro2", "quote", - "syn", ] [[package]] name = "indexmap" -version = "1.9.2" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", "hashbrown 0.12.3", @@ -3164,6 +3761,28 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" +[[package]] +name = "indicatif" +version = "0.17.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ff8cc23a7393a397ed1d7f56e6365cba772aba9f9912ab968b03043c395d057" +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.12" @@ -3202,19 +3821,20 @@ dependencies = [ ] [[package]] -name = "io-lifetimes" -version = "0.7.5" +name = "intx" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59ce5ef949d49ee85593fc4d3f3f95ad61657076395cbbce23e2121fc5542074" +checksum = "f6f38a50a899dc47a6d0ed5508e7f601a2e34c3a85303514b5d137f3c10a0c75" [[package]] name = "io-lifetimes" -version = "1.0.3" +version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46112a93252b123d31a119a8d1a1ac19deac4fac6e0e8b0df58f0d4e5870e63c" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" dependencies = [ + "hermit-abi 0.3.1", "libc", - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -3237,20 +3857,20 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.7.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11b0d96e660696543b251e58030cf9787df56da39dab19ad60eae7353040917e" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" [[package]] name = "is-terminal" -version = "0.4.2" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189" +checksum = "adcf93614601c8129ddf72e2d5633df827ba6551541c6d8c59520a371475be1f" dependencies = [ - "hermit-abi 0.2.6", - "io-lifetimes 1.0.3", - "rustix 0.36.6", - "windows-sys 0.42.0", + "hermit-abi 0.3.1", + "io-lifetimes", + "rustix 0.37.19", + "windows-sys 0.48.0", ] [[package]] @@ -3264,30 +3884,24 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" [[package]] name = "jobserver" -version = "0.1.25" +version = "0.1.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2" dependencies = [ "libc", ] [[package]] name = "js-sys" -version = "0.3.60" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" +checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790" dependencies = [ "wasm-bindgen", ] @@ -3299,6 +3913,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d291e3a5818a2384645fd9756362e6d89cf0541b0b916fa7702ea4a9833608e" dependencies = [ "jsonrpsee-core", + "jsonrpsee-http-client", "jsonrpsee-proc-macros", "jsonrpsee-server", "jsonrpsee-types", @@ -3321,7 +3936,7 @@ dependencies = [ "soketto", "thiserror", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.4", "tokio-util", "tracing", "webpki-roots", @@ -3355,6 +3970,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "jsonrpsee-http-client" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc345b0a43c6bc49b947ebeb936e886a419ee3d894421790c969cc56040542ad" +dependencies = [ + "async-trait", + "hyper", + "hyper-rustls 0.23.2", + "jsonrpsee-core", + "jsonrpsee-types", + "rustc-hash", + "serde", + "serde_json", + "thiserror", + "tokio", + "tracing", +] + [[package]] name = "jsonrpsee-proc-macros" version = "0.16.2" @@ -3365,7 +3999,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3418,30 +4052,31 @@ dependencies = [ [[package]] name = "k256" -version = "0.11.6" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72c1e0b51e7ec0a97369623508396067a486bd0cbed95a2659a4b863d28cfc8b" +checksum = "cadb76004ed8e97623117f3df85b17aaa6626ab0b0831e6573f104df16cd1bcc" dependencies = [ "cfg-if", - "ecdsa", - "elliptic-curve", + "ecdsa 0.16.7", + "elliptic-curve 0.13.5", + "once_cell", "sha2 0.10.6", ] [[package]] name = "keccak" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3afef3b6eff9ce9d8ff9b3601125eec7f0c8cbac7abd14f355d053fa56c98768" +checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" dependencies = [ "cpufeatures", ] [[package]] name = "keccak-hasher" -version = "0.15.3" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711adba9940a039f4374fc5724c0a5eaca84a2d558cce62256bfe26f0dbef05e" +checksum = "19ea4653859ca2266a86419d3f592d3f22e7a854b482f99180d2498507902048" dependencies = [ "hash-db", "hash256-std-hasher", @@ -3453,6 +4088,7 @@ name = "kitchensink-runtime" version = "3.0.0-dev" dependencies = [ "frame-benchmarking", + "frame-benchmarking-pallet-pov", "frame-election-provider-support", "frame-executive", "frame-support", @@ -3463,6 +4099,9 @@ dependencies = [ "log", "node-primitives", "pallet-alliance", + "pallet-asset-conversion", + "pallet-asset-conversion-tx-payment", + "pallet-asset-rate", "pallet-asset-tx-payment", "pallet-assets", "pallet-authority-discovery", @@ -3476,21 +4115,26 @@ dependencies = [ "pallet-contracts", "pallet-contracts-primitives", "pallet-conviction-voting", + "pallet-core-fellowship", "pallet-democracy", "pallet-election-provider-multi-phase", "pallet-election-provider-support-benchmarking", "pallet-elections-phragmen", "pallet-fast-unstake", + "pallet-glutton", "pallet-grandpa", "pallet-identity", "pallet-im-online", "pallet-indices", + "pallet-insecure-randomness-collective-flip", "pallet-lottery", "pallet-membership", "pallet-message-queue", "pallet-mmr", "pallet-multisig", + "pallet-nft-fractionalization", "pallet-nfts", + "pallet-nfts-runtime-api", "pallet-nis", "pallet-nomination-pools", "pallet-nomination-pools-benchmarking", @@ -3499,19 +4143,21 @@ dependencies = [ "pallet-offences-benchmarking", "pallet-preimage", "pallet-proxy", - "pallet-randomness-collective-flip", "pallet-ranked-collective", "pallet-recovery", "pallet-referenda", "pallet-remark", "pallet-root-testing", + "pallet-salary", "pallet-scheduler", "pallet-session", "pallet-session-benchmarking", "pallet-society", "pallet-staking", "pallet-staking-reward-curve", + "pallet-staking-runtime-api", "pallet-state-trie-migration", + "pallet-statement", "pallet-sudo", "pallet-timestamp", "pallet-tips", @@ -3524,11 +4170,13 @@ dependencies = [ "pallet-vesting", "pallet-whitelist", "parity-scale-codec", + "primitive-types", "scale-info", "sp-api", "sp-authority-discovery", "sp-block-builder", "sp-consensus-babe", + "sp-consensus-grandpa", "sp-core", "sp-inherents", "sp-io", @@ -3536,6 +4184,7 @@ dependencies = [ "sp-runtime", "sp-session", "sp-staking", + "sp-statement-store", "sp-std", "sp-transaction-pool", "sp-version", @@ -3564,9 +4213,9 @@ dependencies = [ [[package]] name = "kvdb-rocksdb" -version = "0.17.0" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2182b8219fee6bd83aacaab7344e840179ae079d5216aa4e249b4d704646a844" +checksum = "b644c70b92285f66bfc2032922a79000ea30af7bc2ab31902992a5dcb9b434f6" dependencies = [ "kvdb", "num_cpus", @@ -3596,21 +4245,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.139" +version = "0.2.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" - -[[package]] -name = "libgit2-sys" -version = "0.14.1+1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a07fb2692bc3593bda59de45a502bb3071659f2c515e28c71e728306b038e17" -dependencies = [ - "cc", - "libc", - "libz-sys", - "pkg-config", -] +checksum = "fc86cde3ff845662b8f4ef6cb50ea0e20c524eb3d29ae048287e06a1b3fa6a81" [[package]] name = "libloading" @@ -3630,28 +4267,30 @@ checksum = "7fc7aa29613bd6a620df431842069224d8bc9011086b1db4c0e0cd47fa03ec9a" [[package]] name = "libm" -version = "0.2.6" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" [[package]] name = "libp2p" -version = "0.50.0" +version = "0.51.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e0a0d2f693675f49ded13c5d510c48b78069e23cbd9108d7ccd59f6dc568819" +checksum = "f210d259724eae82005b5c48078619b7745edb7b76de370b03f8ba59ea103097" dependencies = [ "bytes", "futures", "futures-timer", - "getrandom 0.2.8", + "getrandom 0.2.9", "instant", + "libp2p-allow-block-list", + "libp2p-connection-limits", "libp2p-core", "libp2p-dns", "libp2p-identify", + "libp2p-identity", "libp2p-kad", "libp2p-mdns", "libp2p-metrics", - "libp2p-mplex", "libp2p-noise", "libp2p-ping", "libp2p-quic", @@ -3663,25 +4302,45 @@ dependencies = [ "libp2p-websocket", "libp2p-yamux", "multiaddr", - "parking_lot 0.12.1", "pin-project", - "smallvec", +] + +[[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.38.0" +version = "0.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6a8fcd392ff67af6cc3f03b1426c41f7f26b6b9aff2dc632c1c56dd649e571f" +checksum = "3c1df63c0b582aa434fb09b2d86897fa2b419ffeccf934b36f87fcedc8e835c2" dependencies = [ - "asn1_der", - "bs58", - "ed25519-dalek", "either", "fnv", "futures", "futures-timer", "instant", + "libp2p-identity", "log", "multiaddr", "multihash", @@ -3689,24 +4348,20 @@ dependencies = [ "once_cell", "parking_lot 0.12.1", "pin-project", - "prost", - "prost-build", + "quick-protobuf", "rand 0.8.5", "rw-stream-sink", - "sec1", - "sha2 0.10.6", "smallvec", "thiserror", "unsigned-varint", "void", - "zeroize", ] [[package]] name = "libp2p-dns" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e42a271c1b49f789b92f7fc87749fa79ce5c7bdc88cbdfacb818a4bca47fec5" +checksum = "146ff7034daae62077c415c2376b8057368042df6ab95f5432ad5e88568b1554" dependencies = [ "futures", "libp2p-core", @@ -3718,30 +4373,49 @@ dependencies = [ [[package]] name = "libp2p-identify" -version = "0.41.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c052d0026f4817b44869bfb6810f4e1112f43aec8553f2cb38881c524b563abf" +checksum = "5455f472243e63b9c497ff320ded0314254a9eb751799a39c283c6f20b793f3c" dependencies = [ "asynchronous-codec", + "either", "futures", "futures-timer", "libp2p-core", + "libp2p-identity", "libp2p-swarm", "log", "lru", - "prost", - "prost-build", - "prost-codec", + "quick-protobuf", + "quick-protobuf-codec", "smallvec", "thiserror", "void", ] +[[package]] +name = "libp2p-identity" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2d584751cecb2aabaa56106be6be91338a60a0f4e420cf2af639204f596fc1" +dependencies = [ + "bs58", + "ed25519-dalek", + "log", + "multiaddr", + "multihash", + "quick-protobuf", + "rand 0.8.5", + "sha2 0.10.6", + "thiserror", + "zeroize", +] + [[package]] name = "libp2p-kad" -version = "0.42.1" +version = "0.43.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2766dcd2be8c87d5e1f35487deb22d765f49c6ae1251b3633efe3b25698bd3d2" +checksum = "39d5ef876a2b2323d63c258e63c2f8e36f205fe5a11f0b3095d59635650790ff" dependencies = [ "arrayvec 0.7.2", "asynchronous-codec", @@ -3752,10 +4426,10 @@ dependencies = [ "futures-timer", "instant", "libp2p-core", + "libp2p-identity", "libp2p-swarm", "log", - "prost", - "prost-build", + "quick-protobuf", "rand 0.8.5", "sha2 0.10.6", "smallvec", @@ -3767,14 +4441,15 @@ dependencies = [ [[package]] name = "libp2p-mdns" -version = "0.42.0" +version = "0.43.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04f378264aade9872d6ccd315c0accc18be3a35d15fc1b9c36e5b6f983b62b5b" +checksum = "19983e1f949f979a928f2c603de1cf180cc0dc23e4ac93a62651ccb18341460b" dependencies = [ "data-encoding", "futures", "if-watch", "libp2p-core", + "libp2p-identity", "libp2p-swarm", "log", "rand 0.8.5", @@ -3787,9 +4462,9 @@ dependencies = [ [[package]] name = "libp2p-metrics" -version = "0.11.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ad8a64f29da86005c86a4d2728b8a0719e9b192f4092b609fd8790acb9dec55" +checksum = "a42ec91e227d7d0dafa4ce88b333cdf5f277253873ab087555c92798db2ddd46" dependencies = [ "libp2p-core", "libp2p-identify", @@ -3799,38 +4474,20 @@ dependencies = [ "prometheus-client", ] -[[package]] -name = "libp2p-mplex" -version = "0.38.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03805b44107aa013e7cbbfa5627b31c36cbedfdfb00603c0311998882bc4bace" -dependencies = [ - "asynchronous-codec", - "bytes", - "futures", - "libp2p-core", - "log", - "nohash-hasher", - "parking_lot 0.12.1", - "rand 0.8.5", - "smallvec", - "unsigned-varint", -] - [[package]] name = "libp2p-noise" -version = "0.41.0" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a978cb57efe82e892ec6f348a536bfbd9fee677adbe5689d7a93ad3a9bffbf2e" +checksum = "9c3673da89d29936bc6435bafc638e2f184180d554ce844db65915113f86ec5e" dependencies = [ "bytes", "curve25519-dalek 3.2.0", "futures", "libp2p-core", + "libp2p-identity", "log", "once_cell", - "prost", - "prost-build", + "quick-protobuf", "rand 0.8.5", "sha2 0.10.6", "snow", @@ -3842,10 +4499,11 @@ dependencies = [ [[package]] name = "libp2p-ping" -version = "0.41.0" +version = "0.42.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "929fcace45a112536e22b3dcfd4db538723ef9c3cb79f672b98be2cc8e25f37f" +checksum = "3e57759c19c28a73ef1eb3585ca410cefb72c1a709fcf6de1612a378e4219202" dependencies = [ + "either", "futures", "futures-timer", "instant", @@ -3858,48 +4516,47 @@ dependencies = [ [[package]] name = "libp2p-quic" -version = "0.7.0-alpha" +version = "0.7.0-alpha.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01e7c867e95c8130667b24409d236d37598270e6da69b3baf54213ba31ffca59" +checksum = "c6b26abd81cd2398382a1edfe739b539775be8a90fa6914f39b2ab49571ec735" dependencies = [ "bytes", "futures", "futures-timer", "if-watch", "libp2p-core", + "libp2p-identity", "libp2p-tls", "log", "parking_lot 0.12.1", "quinn-proto", "rand 0.8.5", - "rustls 0.20.7", + "rustls 0.20.8", "thiserror", "tokio", ] [[package]] name = "libp2p-request-response" -version = "0.23.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3236168796727bfcf4927f766393415361e2c644b08bedb6a6b13d957c9a4884" +checksum = "7ffdb374267d42dc5ed5bc53f6e601d4a64ac5964779c6e40bb9e4f14c1e30d5" dependencies = [ "async-trait", - "bytes", "futures", "instant", "libp2p-core", + "libp2p-identity", "libp2p-swarm", - "log", "rand 0.8.5", "smallvec", - "unsigned-varint", ] [[package]] name = "libp2p-swarm" -version = "0.41.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a35472fe3276b3855c00f1c032ea8413615e030256429ad5349cdf67c6e1a0" +checksum = "903b3d592d7694e56204d211f29d31bc004be99386644ba8731fc3e3ef27b296" dependencies = [ "either", "fnv", @@ -3907,32 +4564,31 @@ dependencies = [ "futures-timer", "instant", "libp2p-core", + "libp2p-identity", "libp2p-swarm-derive", "log", - "pin-project", "rand 0.8.5", "smallvec", - "thiserror", "tokio", "void", ] [[package]] name = "libp2p-swarm-derive" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d527d5827582abd44a6d80c07ff8b50b4ee238a8979e05998474179e79dc400" +checksum = "0fba456131824ab6acd4c7bf61e9c0f0a3014b5fc9868ccb8e10d344594cdc4f" dependencies = [ "heck", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "libp2p-tcp" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b257baf6df8f2df39678b86c578961d48cc8b68642a12f0f763f56c8e5858d" +checksum = "33d33698596d7722d85d3ab0c86c2c322254fce1241e91208e3679b4eb3026cf" dependencies = [ "futures", "futures-timer", @@ -3946,16 +4602,17 @@ dependencies = [ [[package]] name = "libp2p-tls" -version = "0.1.0-alpha" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7905ce0d040576634e8a3229a7587cc8beab83f79db6023800f1792895defa8" +checksum = "ff08d13d0dc66e5e9ba6279c1de417b84fa0d0adc3b03e5732928c180ec02781" dependencies = [ "futures", "futures-rustls", "libp2p-core", + "libp2p-identity", "rcgen 0.10.0", "ring", - "rustls 0.20.7", + "rustls 0.20.8", "thiserror", "webpki 0.22.0", "x509-parser 0.14.0", @@ -3964,9 +4621,9 @@ dependencies = [ [[package]] name = "libp2p-wasm-ext" -version = "0.38.0" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bb1a35299860e0d4b3c02a3e74e3b293ad35ae0cee8a056363b0c862d082069" +checksum = "77dff9d32353a5887adb86c8afc1de1a94d9e8c3bc6df8b2201d7cdf5c848f43" dependencies = [ "futures", "js-sys", @@ -3978,9 +4635,9 @@ dependencies = [ [[package]] name = "libp2p-webrtc" -version = "0.4.0-alpha" +version = "0.4.0-alpha.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb6cd86dd68cba72308ea05de1cebf3ba0ae6e187c40548167955d4e3970f6a" +checksum = "dba48592edbc2f60b4bc7c10d65445b0c3964c07df26fdf493b6880d33be36f8" dependencies = [ "async-trait", "asynchronous-codec", @@ -3990,12 +4647,12 @@ dependencies = [ "hex", "if-watch", "libp2p-core", + "libp2p-identity", "libp2p-noise", "log", "multihash", - "prost", - "prost-build", - "prost-codec", + "quick-protobuf", + "quick-protobuf-codec", "rand 0.8.5", "rcgen 0.9.3", "serde", @@ -4009,9 +4666,9 @@ dependencies = [ [[package]] name = "libp2p-websocket" -version = "0.40.0" +version = "0.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d705506030d5c0aaf2882437c70dab437605f21c5f9811978f694e6917a3b54" +checksum = "111273f7b3d3510524c752e8b7a5314b7f7a1fee7e68161c01a7d72cbb06db9f" dependencies = [ "either", "futures", @@ -4028,23 +4685,22 @@ dependencies = [ [[package]] name = "libp2p-yamux" -version = "0.42.0" +version = "0.43.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f63594a0aa818642d9d4915c791945053877253f08a3626f13416b5cd928a29" +checksum = "4dcd21d950662700a385d4c6d68e2f5f54d778e97068cdd718522222ef513bda" dependencies = [ "futures", "libp2p-core", "log", - "parking_lot 0.12.1", "thiserror", "yamux", ] [[package]] name = "librocksdb-sys" -version = "0.8.0+7.4.4" +version = "0.11.0+8.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "611804e4666a25136fcc5f8cf425ab4d26c7f74ea245ffe92ea23b85b6420b5d" +checksum = "d3386f101bcb4bd252d8e9d2fb41ec3b0862a15a62b478c355b2982efa469e3e" dependencies = [ "bindgen", "bzip2-sys", @@ -4062,7 +4718,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" dependencies = [ "arrayref", - "base64", + "base64 0.13.1", "digest 0.9.0", "hmac-drbg", "libsecp256k1-core", @@ -4105,12 +4761,11 @@ dependencies = [ [[package]] name = "libz-sys" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9702761c3935f8cc2f101793272e202c72b99da8f4224a19ddcf1279a6450bbf" +checksum = "56ee889ecc9568871456d42f603d6a0ce59ff328d291063a45cbdf0036baf6db" dependencies = [ "cc", - "libc", "pkg-config", "vcpkg", ] @@ -4141,25 +4796,24 @@ dependencies = [ [[package]] name = "linregress" -version = "0.4.4" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c601a85f5ecd1aba625247bca0031585fb1c446461b142878a16f8245ddeb8" +checksum = "475015a7f8f017edb28d2e69813be23500ad4b32cfe3421c4148efc97324ee52" dependencies = [ "nalgebra", - "statrs", ] [[package]] name = "linux-raw-sys" -version = "0.0.46" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d2456c373231a208ad294c33dc5bff30051eafd954cd4caae83a712b12854d" +checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" [[package]] name = "linux-raw-sys" -version = "0.1.4" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4" +checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "lite-json" @@ -4181,9 +4835,9 @@ dependencies = [ [[package]] name = "lock_api" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" dependencies = [ "autocfg", "scopeguard", @@ -4191,20 +4845,17 @@ dependencies = [ [[package]] name = "log" -version = "0.4.17" +version = "0.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" -dependencies = [ - "cfg-if", -] +checksum = "518ef76f2f87365916b142844c16d8fefd85039bc5699050210a7778ee1cd1de" [[package]] name = "lru" -version = "0.8.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6e8aaa3f231bb4bd57b84b2d5dc3ae7f350265df8aa96492e0bc394a1571909" +checksum = "03f1160296536f10c833a82dca22267d5486734230d47bf00bf435885814ba1e" dependencies = [ - "hashbrown 0.12.3", + "hashbrown 0.13.2", ] [[package]] @@ -4245,6 +4896,59 @@ dependencies = [ "libc", ] +[[package]] +name = "macro_magic" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614b1304ab7877b499925b4dcc5223ff480f2646ad4db1ee7065badb8d530439" +dependencies = [ + "macro_magic_core", + "macro_magic_macros", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "macro_magic_core" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8d72c1b662d07b8e482c80d3a7fc4168e058b3bef4c573e94feb714b670f406" +dependencies = [ + "derive-syn-parse", + "macro_magic_core_macros", + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "macro_magic_core_macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d7d9e6e234c040dafc745c7592738d56a03ad04b1fa04ab60821deb597466a" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "macro_magic_macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffd19f13cfd2bfbd83692adfef8c244fe5109b3eb822a1fb4e0a6253b406cd81" +dependencies = [ + "macro_magic_core", + "quote", + "syn 2.0.18", +] + +[[package]] +name = "maplit" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" + [[package]] name = "match_cfg" version = "0.1.0" @@ -4271,16 +4975,17 @@ dependencies = [ [[package]] name = "matches" -version = "0.1.9" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" [[package]] name = "matrixmultiply" -version = "0.3.2" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84" +checksum = "090126dc04f95dc0d1c1c91f61bdd474b3930ca064c1edc8a849da2c6cbe1e77" dependencies = [ + "autocfg", "rawpointer", ] @@ -4290,7 +4995,7 @@ version = "0.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6365506850d44bff6e2fbcb5176cf63650e48bd45ef2fe2665ae1570e0f4b9ca" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -4301,18 +5006,18 @@ checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] name = "memfd" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b20a59d985586e4a5aef64564ac77299f8586d8be6cf9106a5a40207e8908efb" +checksum = "ffc89ccdc6e10d6907450f753537ebc5c5d3460d2e4e62ea74bd571db62c0f9e" dependencies = [ - "rustix 0.36.6", + "rustix 0.37.19", ] [[package]] name = "memmap2" -version = "0.5.8" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b182332558b18d807c4ce1ca8ca983b34c3ee32765e47b3f0f69b90355cc1dc" +checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" dependencies = [ "libc", ] @@ -4336,20 +5041,22 @@ dependencies = [ ] [[package]] -name = "memory-db" -version = "0.31.0" +name = "memoffset" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e0c7cba9ce19ac7ffd2053ac9f49843bbd3f4318feedfd74e85c19d5fb0ba66" +checksum = "d61c719bcfbcf5d62b3a09efa6088de8c54bc0bfcd3ea7ae39fcc186108b8de1" dependencies = [ - "hash-db", - "hashbrown 0.12.3", + "autocfg", ] [[package]] -name = "memory_units" -version = "0.4.0" +name = "memory-db" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +checksum = "808b50db46293432a45e63bc15ea51e0ab4c0a1647b8eb114e31a3e698dd6fbe" +dependencies = [ + "hash-db", +] [[package]] name = "merlin" @@ -4378,16 +5085,24 @@ dependencies = [ "adler", ] +[[package]] +name = "miniz_oxide" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" +dependencies = [ + "adler", +] + [[package]] name = "mio" -version = "0.8.5" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" dependencies = [ "libc", - "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.42.0", + "windows-sys 0.48.0", ] [[package]] @@ -4402,9 +5117,9 @@ dependencies = [ "sc-client-api", "sc-offchain", "sp-api", - "sp-beefy", "sp-blockchain", "sp-consensus", + "sp-consensus-beefy", "sp-core", "sp-mmr-primitives", "sp-runtime", @@ -4431,40 +5146,41 @@ dependencies = [ [[package]] name = "mockall" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50e4a1c770583dac7ab5e2f6c139153b783a53a1bbee9729613f193e59828326" +checksum = "4c84490118f2ee2d74570d114f3d0493cbf02790df303d2707606c3e14e07c96" dependencies = [ "cfg-if", "downcast", "fragile", "lazy_static", "mockall_derive", - "predicates", + "predicates 2.1.5", "predicates-tree", ] [[package]] name = "mockall_derive" -version = "0.11.3" +version = "0.11.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "832663583d5fa284ca8810bf7015e46c9fff9622d3cf34bd1eea5003fec06dd0" +checksum = "22ce75669015c4f47b289fd4d4f56e894e4c96003ffdf3ac51313126f94c6cbb" dependencies = [ "cfg-if", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "multiaddr" -version = "0.16.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4aebdb21e90f81d13ed01dc84123320838e53963c2ca94b60b305d3fa64f31e" +checksum = "2b36f567c7099511fa8612bbbb52dda2419ce0bdbacf31714e3a5ffdb766d3bd" dependencies = [ "arrayref", "byteorder", "data-encoding", + "log", "multibase", "multihash", "percent-encoding", @@ -4487,15 +5203,15 @@ dependencies = [ [[package]] name = "multihash" -version = "0.16.3" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c346cf9999c631f002d8f977c4eaeaa0e6386f16007202308d0b3757522c2cc" +checksum = "835d6ff01d610179fbce3de1694d007e500bf33a7f29689838941d6bf783ae40" dependencies = [ "blake2b_simd", "blake2s_simd", "blake3", "core2", - "digest 0.10.6", + "digest 0.10.7", "multihash-derive", "sha2 0.10.6", "sha3", @@ -4504,15 +5220,15 @@ dependencies = [ [[package]] name = "multihash-derive" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc076939022111618a5026d3be019fd8b366e76314538ff9a1b59ffbcbf98bcd" +checksum = "1d6d4752e6230d8ef7adf7bd5d8c4b1f6561c1014c5ba9a37445ccefe18aa1db" dependencies = [ "proc-macro-crate", "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] @@ -4538,9 +5254,9 @@ dependencies = [ [[package]] name = "nalgebra" -version = "0.27.1" +version = "0.32.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "462fffe4002f4f2e1f6a9dcf12cc1a6fc0e15989014efc02a941d3e0f5dc2120" +checksum = "d68d47bba83f9e2006d117a9a33af1524e655516b8919caac694427a6fb1e511" dependencies = [ "approx", "matrixmultiply", @@ -4548,21 +5264,19 @@ dependencies = [ "num-complex", "num-rational", "num-traits", - "rand 0.8.5", - "rand_distr", "simba", "typenum", ] [[package]] name = "nalgebra-macros" -version = "0.1.0" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218" +checksum = "d232c68884c0c99810a5a4d333ef7e47689cfd0edc85efc9e54e1e6bf5212766" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4602,9 +5316,9 @@ dependencies = [ [[package]] name = "netlink-packet-utils" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25af9cf0dc55498b7bd94a1508af7a78706aa0ab715a73c5169273e03c84845e" +checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" dependencies = [ "anyhow", "byteorder", @@ -4629,9 +5343,9 @@ dependencies = [ [[package]] name = "netlink-sys" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92b654097027250401127914afb37cb1f311df6610a9891ff07a757e94199027" +checksum = "6471bf08e7ac0135876a9581bf3217ef0333c191c128d34878079f42ee150411" dependencies = [ "bytes", "futures", @@ -4654,9 +5368,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46a58d1d356c6597d08cde02c2f09d785b09e28711837b1ed667dc652c08a694" +checksum = "bfdda3d196821d6af13126e40375cdf7da646a96114af134d5f417a9a1dc8e1a" dependencies = [ "bitflags", "cfg-if", @@ -4671,7 +5385,7 @@ name = "node-bench" version = "0.9.0-dev" dependencies = [ "array-bytes", - "clap 4.0.32", + "clap 4.3.2", "derive_more", "fs_extra", "futures", @@ -4708,7 +5422,7 @@ version = "3.0.0-dev" dependencies = [ "array-bytes", "assert_cmd", - "clap 4.0.32", + "clap 4.3.2", "clap_complete", "criterion", "frame-benchmarking-cli", @@ -4718,19 +5432,19 @@ dependencies = [ "jsonrpsee", "kitchensink-runtime", "log", - "nix 0.26.1", + "nix 0.26.2", "node-executor", "node-inspect", "node-primitives", "node-rpc", + "pallet-asset-conversion-tx-payment", "pallet-asset-tx-payment", "pallet-assets", "pallet-balances", "pallet-im-online", "pallet-timestamp", - "pallet-transaction-payment", "parity-scale-codec", - "platforms 2.0.0", + "platforms", "rand 0.8.5", "regex", "sc-authority-discovery", @@ -4743,16 +5457,19 @@ dependencies = [ "sc-consensus", "sc-consensus-babe", "sc-consensus-epochs", + "sc-consensus-grandpa", "sc-consensus-slots", - "sc-consensus-uncles", "sc-executor", - "sc-finality-grandpa", "sc-keystore", "sc-network", "sc-network-common", + "sc-network-statement", + "sc-network-sync", + "sc-offchain", "sc-rpc", "sc-service", "sc-service-test", + "sc-statement-store", "sc-storage-monitor", "sc-sync-state-rpc", "sc-sysinfo", @@ -4764,22 +5481,22 @@ dependencies = [ "soketto", "sp-api", "sp-authority-discovery", - "sp-authorship", "sp-blockchain", "sp-consensus", "sp-consensus-babe", + "sp-consensus-grandpa", "sp-core", - "sp-finality-grandpa", "sp-inherents", "sp-io", "sp-keyring", "sp-keystore", "sp-runtime", + "sp-statement-store", "sp-timestamp", "sp-tracing", - "sp-transaction-pool", "sp-transaction-storage-proof", "substrate-build-script-utils", + "substrate-cli-test-utils", "substrate-frame-cli", "substrate-rpc-client", "tempfile", @@ -4803,6 +5520,7 @@ dependencies = [ "node-testing", "pallet-balances", "pallet-contracts", + "pallet-glutton", "pallet-im-online", "pallet-root-testing", "pallet-sudo", @@ -4820,6 +5538,7 @@ dependencies = [ "sp-keystore", "sp-runtime", "sp-state-machine", + "sp-statement-store", "sp-tracing", "sp-trie", "wat", @@ -4829,11 +5548,10 @@ dependencies = [ name = "node-inspect" version = "0.9.0-dev" dependencies = [ - "clap 4.0.32", + "clap 4.3.2", "parity-scale-codec", "sc-cli", "sc-client-api", - "sc-executor", "sc-service", "sp-blockchain", "sp-core", @@ -4845,10 +5563,6 @@ dependencies = [ name = "node-primitives" version = "2.0.0" dependencies = [ - "frame-system", - "parity-scale-codec", - "scale-info", - "sp-application-crypto", "sp-core", "sp-runtime", ] @@ -4865,9 +5579,8 @@ dependencies = [ "sc-client-api", "sc-consensus-babe", "sc-consensus-babe-rpc", - "sc-consensus-epochs", - "sc-finality-grandpa", - "sc-finality-grandpa-rpc", + "sc-consensus-grandpa", + "sc-consensus-grandpa-rpc", "sc-rpc", "sc-rpc-api", "sc-rpc-spec-v2", @@ -4880,6 +5593,7 @@ dependencies = [ "sp-consensus-babe", "sp-keystore", "sp-runtime", + "sp-statement-store", "substrate-frame-rpc-system", "substrate-state-trie-migration-rpc", ] @@ -4888,7 +5602,7 @@ dependencies = [ name = "node-runtime-generate-bags" version = "3.0.0" dependencies = [ - "clap 4.0.32", + "clap 4.3.2", "generate-bags", "kitchensink-runtime", ] @@ -4897,7 +5611,7 @@ dependencies = [ name = "node-template" version = "4.0.0-dev" dependencies = [ - "clap 4.0.32", + "clap 4.3.2", "frame-benchmarking", "frame-benchmarking-cli", "frame-system", @@ -4911,10 +5625,10 @@ dependencies = [ "sc-client-api", "sc-consensus", "sc-consensus-aura", + "sc-consensus-grandpa", "sc-executor", - "sc-finality-grandpa", - "sc-keystore", - "sc-rpc", + "sc-network", + "sc-offchain", "sc-rpc-api", "sc-service", "sc-telemetry", @@ -4923,10 +5637,9 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-blockchain", - "sp-consensus", "sp-consensus-aura", + "sp-consensus-grandpa", "sp-core", - "sp-finality-grandpa", "sp-inherents", "sp-io", "sp-keyring", @@ -4937,6 +5650,20 @@ dependencies = [ "try-runtime-cli", ] +[[package]] +name = "node-template-release" +version = "3.0.0" +dependencies = [ + "clap 4.3.2", + "flate2", + "fs_extra", + "glob", + "itertools", + "tar", + "tempfile", + "toml_edit", +] + [[package]] name = "node-template-runtime" version = "4.0.0-dev" @@ -4951,7 +5678,6 @@ dependencies = [ "pallet-aura", "pallet-balances", "pallet-grandpa", - "pallet-randomness-collective-flip", "pallet-sudo", "pallet-template", "pallet-timestamp", @@ -4962,6 +5688,7 @@ dependencies = [ "sp-api", "sp-block-builder", "sp-consensus-aura", + "sp-consensus-grandpa", "sp-core", "sp-inherents", "sp-offchain", @@ -4984,9 +5711,10 @@ dependencies = [ "log", "node-executor", "node-primitives", + "pallet-asset-conversion", + "pallet-asset-conversion-tx-payment", "pallet-asset-tx-payment", "pallet-assets", - "pallet-transaction-payment", "parity-scale-codec", "sc-block-builder", "sc-client-api", @@ -5016,9 +5744,9 @@ checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" [[package]] name = "nom" -version = "7.1.2" +version = "7.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5507769c4919c998e69e49c839d9dc6e693ede4cc4290d6ad8b41d4f09c548c" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" dependencies = [ "memchr", "minimal-lexical", @@ -5040,6 +5768,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + [[package]] name = "num-bigint" version = "0.4.3" @@ -5053,9 +5795,9 @@ dependencies = [ [[package]] name = "num-complex" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ae39348c8bc5fbd7f40c727a9925f03517afd2ab27d46702108b6a7e5414c19" +checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d" dependencies = [ "num-traits", ] @@ -5067,7 +5809,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" dependencies = [ "arrayvec 0.7.2", - "itoa 1.0.5", + "itoa", ] [[package]] @@ -5080,6 +5822,17 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -5099,7 +5852,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" dependencies = [ "autocfg", - "libm 0.2.6", + "libm 0.2.7", ] [[package]] @@ -5113,23 +5866,20 @@ dependencies = [ ] [[package]] -name = "object" -version = "0.29.0" +name = "number_prefix" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" -dependencies = [ - "crc32fast", - "hashbrown 0.12.3", - "indexmap", - "memchr", -] +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" [[package]] name = "object" -version = "0.30.0" +version = "0.30.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239da7f290cfa979f43f85a8efeee9a8a76d0827c356d37f9d3d7254d6b537fb" +checksum = "03b4680b86d9cfafba8fc491dc9b6df26b68cf40e9e6cd73909194759a63c385" dependencies = [ + "crc32fast", + "hashbrown 0.13.2", + "indexmap", "memchr", ] @@ -5148,14 +5898,14 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bedf36ffb6ba96c2eb7144ef6270557b52e54b20c0a8e1eb2ff99a6c6959bff" dependencies = [ - "asn1-rs 0.5.1", + "asn1-rs 0.5.2", ] [[package]] name = "once_cell" -version = "1.17.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "oorandom" @@ -5183,9 +5933,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "os_str_bytes" -version = "6.4.1" +version = "6.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +checksum = "ceedf44fb00f2d1984b0bc98102627ce622e083e49a5bacdb3e514fa4238e267" [[package]] name = "output_vt100" @@ -5208,8 +5958,8 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51f44edd08f51e2ade572f141051021c5af22677e42b7dd28a88155151c33594" dependencies = [ - "ecdsa", - "elliptic-curve", + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", "sha2 0.10.6", ] @@ -5219,8 +5969,8 @@ version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc8c5bf642dde52bb9e87c0ecd8ca5a76faac2eeed98dedb7c717997e1080aa" dependencies = [ - "ecdsa", - "elliptic-curve", + "ecdsa 0.14.8", + "elliptic-curve 0.12.3", "sha2 0.10.6", ] @@ -5248,7 +5998,62 @@ dependencies = [ "pallet-identity", "parity-scale-codec", "scale-info", - "sha2 0.10.6", + "sp-core", + "sp-core-hashing", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-asset-conversion" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-assets", + "pallet-balances", + "parity-scale-codec", + "primitive-types", + "scale-info", + "sp-api", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-asset-conversion-tx-payment" +version = "4.0.0-dev" +dependencies = [ + "frame-support", + "frame-system", + "pallet-asset-conversion", + "pallet-assets", + "pallet-balances", + "pallet-transaction-payment", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-storage", +] + +[[package]] +name = "pallet-asset-rate" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", "sp-runtime", @@ -5351,7 +6156,6 @@ dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", "scale-info", - "sp-authorship", "sp-core", "sp-io", "sp-runtime", @@ -5378,7 +6182,6 @@ dependencies = [ "scale-info", "sp-application-crypto", "sp-consensus-babe", - "sp-consensus-vrf", "sp-core", "sp-io", "sp-runtime", @@ -5444,6 +6247,7 @@ dependencies = [ "log", "pallet-transaction-payment", "parity-scale-codec", + "paste", "scale-info", "sp-core", "sp-io", @@ -5455,16 +6259,24 @@ dependencies = [ name = "pallet-beefy" version = "4.0.0-dev" dependencies = [ + "frame-election-provider-support", "frame-support", "frame-system", + "pallet-authorship", + "pallet-balances", + "pallet-offences", "pallet-session", + "pallet-staking", + "pallet-staking-reward-curve", + "pallet-timestamp", "parity-scale-codec", "scale-info", "serde", - "sp-beefy", + "sp-consensus-beefy", "sp-core", "sp-io", "sp-runtime", + "sp-session", "sp-staking", "sp-std", ] @@ -5474,7 +6286,7 @@ name = "pallet-beefy-mmr" version = "4.0.0-dev" dependencies = [ "array-bytes", - "beefy-merkle-tree", + "binary-merkle-tree", "frame-support", "frame-system", "log", @@ -5484,7 +6296,8 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-beefy", + "sp-api", + "sp-consensus-beefy", "sp-core", "sp-io", "sp-runtime", @@ -5552,7 +6365,8 @@ dependencies = [ "array-bytes", "assert_matches", "bitflags", - "env_logger", + "env_logger 0.9.3", + "environmental", "frame-benchmarking", "frame-support", "frame-system", @@ -5561,7 +6375,8 @@ dependencies = [ "pallet-balances", "pallet-contracts-primitives", "pallet-contracts-proc-macro", - "pallet-randomness-collective-flip", + "pallet-insecure-randomness-collective-flip", + "pallet-proxy", "pallet-timestamp", "pallet-utility", "parity-scale-codec", @@ -5578,17 +6393,17 @@ dependencies = [ "sp-runtime", "sp-std", "wasm-instrument 0.4.0", - "wasmi 0.20.0", - "wasmparser-nostd", + "wasmi", "wat", ] [[package]] name = "pallet-contracts-primitives" -version = "7.0.0" +version = "24.0.0" dependencies = [ "bitflags", "parity-scale-codec", + "scale-info", "sp-runtime", "sp-std", "sp-weights", @@ -5600,7 +6415,7 @@ version = "4.0.0-dev" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -5622,6 +6437,37 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-core-fellowship" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-default-config-example" +version = "4.0.0-dev" +dependencies = [ + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-democracy" version = "4.0.0-dev" @@ -5642,6 +6488,48 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-dev-mode" +version = "4.0.0-dev" +dependencies = [ + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + +[[package]] +name = "pallet-election-provider-e2e-test" +version = "1.0.0" +dependencies = [ + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-bags-list", + "pallet-balances", + "pallet-election-provider-multi-phase", + "pallet-session", + "pallet-staking", + "pallet-timestamp", + "parity-scale-codec", + "parking_lot 0.12.1", + "scale-info", + "sp-core", + "sp-io", + "sp-npos-elections", + "sp-runtime", + "sp-staking", + "sp-std", + "sp-tracing", +] + [[package]] name = "pallet-election-provider-multi-phase" version = "4.0.0-dev" @@ -5694,6 +6582,7 @@ dependencies = [ "sp-io", "sp-npos-elections", "sp-runtime", + "sp-staking", "sp-std", "sp-tracing", "substrate-test-utils", @@ -5716,6 +6605,23 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-example-kitchensink" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-example-offchain-worker" version = "4.0.0-dev" @@ -5733,10 +6639,38 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-example-split" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-std", +] + +[[package]] +name = "pallet-examples" +version = "4.0.0-dev" +dependencies = [ + "pallet-default-config-example", + "pallet-dev-mode", + "pallet-example-basic", + "pallet-example-kitchensink", + "pallet-example-offchain-worker", + "pallet-example-split", +] + [[package]] name = "pallet-fast-unstake" version = "4.0.0-dev" dependencies = [ + "docify", "frame-benchmarking", "frame-election-provider-support", "frame-support", @@ -5757,6 +6691,24 @@ dependencies = [ "substrate-test-utils", ] +[[package]] +name = "pallet-glutton" +version = "4.0.0-dev" +dependencies = [ + "blake2", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-grandpa" version = "4.0.0-dev" @@ -5777,8 +6729,8 @@ dependencies = [ "parity-scale-codec", "scale-info", "sp-application-crypto", + "sp-consensus-grandpa", "sp-core", - "sp-finality-grandpa", "sp-io", "sp-keyring", "sp-runtime", @@ -5816,27 +6768,42 @@ dependencies = [ "pallet-session", "parity-scale-codec", "scale-info", - "sp-application-crypto", + "sp-application-crypto", + "sp-core", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", +] + +[[package]] +name = "pallet-indices" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "parity-scale-codec", + "scale-info", "sp-core", "sp-io", + "sp-keyring", "sp-runtime", - "sp-staking", "sp-std", ] [[package]] -name = "pallet-indices" +name = "pallet-insecure-randomness-collective-flip" version = "4.0.0-dev" dependencies = [ - "frame-benchmarking", "frame-support", "frame-system", - "pallet-balances", "parity-scale-codec", + "safe-mix", "scale-info", "sp-core", "sp-io", - "sp-keyring", "sp-runtime", "sp-std", ] @@ -5901,7 +6868,7 @@ name = "pallet-mmr" version = "4.0.0-dev" dependencies = [ "array-bytes", - "env_logger", + "env_logger 0.9.3", "frame-benchmarking", "frame-support", "frame-system", @@ -5932,6 +6899,25 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-nft-fractionalization" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "pallet-assets", + "pallet-balances", + "pallet-nfts", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-nfts" version = "4.0.0-dev" @@ -5946,10 +6932,21 @@ dependencies = [ "scale-info", "sp-core", "sp-io", + "sp-keystore", "sp-runtime", "sp-std", ] +[[package]] +name = "pallet-nfts-runtime-api" +version = "4.0.0-dev" +dependencies = [ + "frame-support", + "pallet-nfts", + "parity-scale-codec", + "sp-api", +] + [[package]] name = "pallet-nicks" version = "4.0.0-dev" @@ -6058,6 +7055,7 @@ dependencies = [ name = "pallet-nomination-pools-runtime-api" version = "1.0.0-dev" dependencies = [ + "pallet-nomination-pools", "parity-scale-codec", "sp-api", "sp-std", @@ -6166,21 +7164,6 @@ dependencies = [ "sp-std", ] -[[package]] -name = "pallet-randomness-collective-flip" -version = "4.0.0-dev" -dependencies = [ - "frame-support", - "frame-system", - "parity-scale-codec", - "safe-mix", - "scale-info", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - [[package]] name = "pallet-ranked-collective" version = "4.0.0-dev" @@ -6287,6 +7270,23 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-salary" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-scheduler" version = "4.0.0-dev" @@ -6368,13 +7368,17 @@ dependencies = [ name = "pallet-society" version = "4.0.0-dev" dependencies = [ + "frame-benchmarking", "frame-support", "frame-support-test", "frame-system", + "hex-literal", + "log", "pallet-balances", "parity-scale-codec", "rand_chacha 0.2.2", "scale-info", + "sp-arithmetic", "sp-core", "sp-io", "sp-runtime", @@ -6419,7 +7423,7 @@ dependencies = [ "proc-macro2", "quote", "sp-runtime", - "syn", + "syn 2.0.18", ] [[package]] @@ -6430,6 +7434,14 @@ dependencies = [ "sp-arithmetic", ] +[[package]] +name = "pallet-staking-runtime-api" +version = "4.0.0-dev" +dependencies = [ + "parity-scale-codec", + "sp-api", +] + [[package]] name = "pallet-state-trie-migration" version = "4.0.0-dev" @@ -6452,13 +7464,32 @@ dependencies = [ "substrate-state-trie-migration-rpc", "thousands", "tokio", - "zstd", + "zstd 0.12.3+zstd.1.5.2", +] + +[[package]] +name = "pallet-statement" +version = "4.0.0-dev" +dependencies = [ + "frame-support", + "frame-system", + "log", + "pallet-balances", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-core", + "sp-io", + "sp-runtime", + "sp-statement-store", + "sp-std", ] [[package]] name = "pallet-sudo" version = "4.0.0-dev" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", "parity-scale-codec", @@ -6594,6 +7625,7 @@ dependencies = [ "frame-system", "impl-trait-for-tuples", "pallet-balances", + "pallet-utility", "parity-scale-codec", "scale-info", "serde", @@ -6676,9 +7708,9 @@ dependencies = [ [[package]] name = "parity-db" -version = "0.4.3" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd684a725651d9588ef21f140a328b6b4f64e646b2e931f3e6f14f75eedf9980" +checksum = "4890dcb9556136a4ec2b0c51fa4a08c8b733b829506af8fff2e853f3a065985b" dependencies = [ "blake2", "crc32fast", @@ -6690,14 +7722,15 @@ dependencies = [ "memmap2", "parking_lot 0.12.1", "rand 0.8.5", + "siphasher", "snap", ] [[package]] name = "parity-scale-codec" -version = "3.2.2" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ab01d0f889e957861bc65888d5ccbe82c158d0270136ba46820d43837cdf72" +checksum = "2287753623c76f953acd29d15d8100bcab84d29db78fb6f352adb3c53e83b967" dependencies = [ "arrayvec 0.7.2", "bitvec", @@ -6710,14 +7743,14 @@ dependencies = [ [[package]] name = "parity-scale-codec-derive" -version = "3.1.4" +version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b26a931f824dd4eca30b3e43bb4f31cd5f0d3a403c5f5ff27106b805bfde7b" +checksum = "2b6937b5e67bfba3351b87b040d48352a2fcb6ad72f81855412ce97b45c8f110" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -6734,9 +7767,9 @@ checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" [[package]] name = "parking" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" +checksum = "14f2252c834a40ed9bb5422029649578e63aa341ac401f74e719dd1afda8394e" [[package]] name = "parking_lot" @@ -6756,7 +7789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" dependencies = [ "lock_api", - "parking_lot_core 0.9.5", + "parking_lot_core 0.9.8", ] [[package]] @@ -6768,29 +7801,35 @@ dependencies = [ "cfg-if", "instant", "libc", - "redox_syscall", + "redox_syscall 0.2.16", "smallvec", "winapi", ] [[package]] name = "parking_lot_core" -version = "0.9.5" +version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" +checksum = "93f00c865fe7cabf650081affecd3871070f26767e7b2070a3ffae14c654b447" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.3.5", "smallvec", - "windows-sys 0.42.0", + "windows-targets 0.48.0", ] +[[package]] +name = "partial_sort" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7924d1d0ad836f665c9065e26d016c673ece3993f30d340068b16f282afc1156" + [[package]] name = "paste" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" +checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" [[package]] name = "pbkdf2" @@ -6807,7 +7846,7 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -6818,11 +7857,11 @@ checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" [[package]] name = "pem" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03c64931a1a212348ec4f3b4362585eca7159d0d09cbdf4a7f74f02173596fd4" +checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" dependencies = [ - "base64", + "base64 0.13.1", ] [[package]] @@ -6836,15 +7875,15 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" [[package]] name = "pest" -version = "2.5.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f6e86fb9e7026527a0d46bc308b841d73170ef8f443e1807f6ef88526a816d4" +checksum = "e68e84bfb01f0507134eac1e9b410a12ba379d064eab48c50ba4ce329a527b70" dependencies = [ "thiserror", "ucd-trie", @@ -6852,9 +7891,9 @@ dependencies = [ [[package]] name = "pest_derive" -version = "2.5.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96504449aa860c8dcde14f9fba5c58dc6658688ca1fe363589d6327b8662c603" +checksum = "6b79d4c71c865a25a4322296122e3924d30bc8ee0834c8bfc8b95f7f054afbfb" dependencies = [ "pest", "pest_generator", @@ -6862,33 +7901,33 @@ dependencies = [ [[package]] name = "pest_generator" -version = "2.5.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "798e0220d1111ae63d66cb66a5dcb3fc2d986d520b98e49e1852bfdb11d7c5e7" +checksum = "6c435bf1076437b851ebc8edc3a18442796b30f1728ffea6262d59bbe28b077e" dependencies = [ "pest", "pest_meta", "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "pest_meta" -version = "2.5.2" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "984298b75898e30a843e278a9f2452c31e349a073a0ce6fd950a12a74464e065" +checksum = "745a452f8eb71e39ffd8ee32b3c5f51d03845f99786fa9b68db6ff509c505411" dependencies = [ "once_cell", "pest", - "sha1", + "sha2 0.10.6", ] [[package]] name = "petgraph" -version = "0.6.2" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6d5014253a1331579ce62aa67443b4a658c5e7dd03d4bc6d302b94474888143" +checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4" dependencies = [ "fixedbitset", "indexmap", @@ -6896,22 +7935,22 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.0.12" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -6938,21 +7977,25 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9eca2c590a5f85da82668fa685c09ce2888b9430e83299debf1f34b65fd4a4ba" dependencies = [ - "der", - "spki", + "der 0.6.1", + "spki 0.6.0", ] [[package]] -name = "pkg-config" -version = "0.3.26" +name = "pkcs8" +version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der 0.7.6", + "spki 0.7.2", +] [[package]] -name = "platforms" -version = "2.0.0" +name = "pkg-config" +version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8d0eef3571242013a0d5dc84861c3ae4a652e56e12adf8bdc26ff5f8cb34c94" +checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "platforms" @@ -6990,16 +8033,18 @@ dependencies = [ [[package]] name = "polling" -version = "2.5.2" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22122d5ec4f9fe1b3916419b76be1e80bcb93f618d071d2edf841b137b2a2bd6" +checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce" dependencies = [ "autocfg", + "bitflags", "cfg-if", + "concurrent-queue", "libc", "log", - "wepoll-ffi", - "windows-sys 0.42.0", + "pin-project-lite 0.2.9", + "windows-sys 0.48.0", ] [[package]] @@ -7010,32 +8055,39 @@ checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" dependencies = [ "cpufeatures", "opaque-debug 0.3.0", - "universal-hash", + "universal-hash 0.4.1", ] [[package]] name = "polyval" -version = "0.4.5" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eebcc4aa140b9abd2bc40d9c3f7ccec842679cd79045ac3a7ac698c1a064b7cd" +checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" dependencies = [ - "cpuid-bool", + "cfg-if", + "cpufeatures", "opaque-debug 0.3.0", - "universal-hash", + "universal-hash 0.4.1", ] [[package]] name = "polyval" -version = "0.5.3" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8419d2b623c7c0896ff2d5d96e2cb4ede590fed28fcc34934f4c33c036e620a1" +checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" dependencies = [ "cfg-if", "cpufeatures", "opaque-debug 0.3.0", - "universal-hash", + "universal-hash 0.5.1", ] +[[package]] +name = "portable-atomic" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "767eb9f07d4a5ebcb39bbf2d452058a93c011373abf6832e24194a1c3f004794" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -7056,17 +8108,29 @@ dependencies = [ "regex", ] +[[package]] +name = "predicates" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09963355b9f467184c04017ced4a2ba2d75cbcb4e7462690d388233253d4b1a9" +dependencies = [ + "anstyle", + "difflib", + "itertools", + "predicates-core", +] + [[package]] name = "predicates-core" -version = "1.0.5" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f883590242d3c6fc5bf50299011695fa6590c2c70eac95ee1bdb9a733ad1a2" +checksum = "b794032607612e7abeb4db69adb4e33590fa6cf1149e95fd7cb00e634b92f174" [[package]] name = "predicates-tree" -version = "1.0.7" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54ff541861505aabf6ea722d2131ee980b8276e10a1297b94e896dd8b621850d" +checksum = "368ba315fb8c5052ab692e68a0eefec6ec57b23a36959c14496f0b0df2c0cecf" dependencies = [ "predicates-core", "termtree", @@ -7086,12 +8150,22 @@ dependencies = [ [[package]] name = "prettyplease" -version = "0.1.22" +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.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c8992a85d8e93a28bdf76137db888d3874e3b230dee5ed8bebac4c9f7617773" +checksum = "3b69d39aab54d069e7f2fe8cb970493e7834601ca2d8c65fd7bbd183578080d1" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.18", ] [[package]] @@ -7102,6 +8176,7 @@ checksum = "9f3486ccba82358b11a77516035647c34ba167dfa53312630de83b12bd4f3d66" dependencies = [ "fixed-hash", "impl-codec", + "impl-num-traits", "impl-serde", "scale-info", "uint", @@ -7109,13 +8184,12 @@ dependencies = [ [[package]] name = "proc-macro-crate" -version = "1.2.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eda0fc3b0fb7c975631757e14d9049da17374063edb6ebbcbc54d880d4fe94e9" +checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ - "once_cell", "thiserror", - "toml", + "toml 0.5.11", ] [[package]] @@ -7127,7 +8201,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -7142,11 +8216,22 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-warning" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70550716265d1ec349c41f70dd4f964b4fd88394efe4405f0c1da679c4799a07" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", +] + [[package]] name = "proc-macro2" -version = "1.0.50" +version = "1.0.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "dec2b086b7a862cf4de201096214fa870344cf922b2b30c167badb3af3195406" dependencies = [ "unicode-ident", ] @@ -7167,32 +8252,32 @@ dependencies = [ [[package]] name = "prometheus-client" -version = "0.18.1" +version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83cd1b99916654a69008fd66b4f9397fbe08e6e51dfe23d4417acf5d3b8cb87c" +checksum = "5d6fa99d535dd930d1249e6c79cb3c2915f9172a540fe2b02a4c8f9ca954721e" dependencies = [ "dtoa", - "itoa 1.0.5", + "itoa", "parking_lot 0.12.1", - "prometheus-client-derive-text-encode", + "prometheus-client-derive-encode", ] [[package]] -name = "prometheus-client-derive-text-encode" -version = "0.3.0" +name = "prometheus-client-derive-encode" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66a455fbcb954c1a7decf3c586e860fd7889cddf4b8e164be736dbac95a953cd" +checksum = "72b6a5217beb0ad503ee7fa752d451c905113d70721b937126158f3106a48cc1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "prost" -version = "0.11.5" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c01db6702aa05baa3f57dec92b8eeeeb4cb19e894e73996b32a4093289e54592" +checksum = "0b82eaa1d779e9a4bc1c3217db8ffbeabaae1dca241bf70183242128d48681cd" dependencies = [ "bytes", "prost-derive", @@ -7200,9 +8285,9 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.11.5" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb5320c680de74ba083512704acb90fe00f28f79207286a848e730c45dd73ed6" +checksum = "119533552c9a7ffacc21e099c24a0ac8bb19c2a2a3f363de84cd9b844feab270" dependencies = [ "bytes", "heck", @@ -7211,48 +8296,34 @@ dependencies = [ "log", "multimap", "petgraph", - "prettyplease", + "prettyplease 0.1.25", "prost", "prost-types", "regex", - "syn", + "syn 1.0.109", "tempfile", "which", ] -[[package]] -name = "prost-codec" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dc34979ff898b6e141106178981ce2596c387ea6e62533facfc61a37fc879c0" -dependencies = [ - "asynchronous-codec", - "bytes", - "prost", - "thiserror", - "unsigned-varint", -] - [[package]] name = "prost-derive" -version = "0.11.5" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8842bad1a5419bca14eac663ba798f6bc19c413c2fdceb5f3ba3b0932d96720" +checksum = "e5d2d8d10f3c6ded6da8b05b5fb3b8a5082514344d56c9f871412d29b4e075b4" dependencies = [ "anyhow", "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "prost-types" -version = "0.11.5" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "017f79637768cde62820bc2d4fe0e45daaa027755c323ad077767c6c5f173091" +checksum = "213622a1460818959ac1181aaeb2dc9c7f63df720db7d788b3e24eacd1983e13" dependencies = [ - "bytes", "prost", ] @@ -7271,6 +8342,28 @@ 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 = "quickcheck" version = "1.0.3" @@ -7293,15 +8386,15 @@ dependencies = [ [[package]] name = "quinn-proto" -version = "0.9.2" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72ef4ced82a24bb281af338b9e8f94429b6eca01b4e66d899f40031f074e74c9" +checksum = "67c10f662eee9c94ddd7135043e544f3c82fa839a1e7b865911331961b53186c" dependencies = [ "bytes", "rand 0.8.5", "ring", "rustc-hash", - "rustls 0.20.7", + "rustls 0.20.8", "slab", "thiserror", "tinyvec", @@ -7311,9 +8404,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.23" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488" dependencies = [ "proc-macro2", ] @@ -7383,7 +8476,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.9", ] [[package]] @@ -7422,9 +8515,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" [[package]] name = "rayon" -version = "1.6.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" +checksum = "1d2df5196e37bcc87abebc0053e20787d73847bb33134a69841207dd0a47f03b" dependencies = [ "either", "rayon-core", @@ -7432,9 +8525,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.10.1" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cac410af5d00ab6884528b4ab69d1e8e146e8d471201800fa1b4524126de6ad3" +checksum = "4b8f95bd6966f5c87776639160a66bd8ab9895d9d4ab01ddba9fc60661aebe8d" dependencies = [ "crossbeam-channel", "crossbeam-deque", @@ -7450,7 +8543,7 @@ checksum = "6413f3de1edee53342e6138e75b56d32e7bc6e332b3bd62d497b1929d4cfbcdd" dependencies = [ "pem", "ring", - "time 0.3.17", + "time 0.3.21", "x509-parser 0.13.2", "yasna", ] @@ -7463,7 +8556,7 @@ checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" dependencies = [ "pem", "ring", - "time 0.3.17", + "time 0.3.21", "yasna", ] @@ -7476,42 +8569,51 @@ dependencies = [ "bitflags", ] +[[package]] +name = "redox_syscall" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom 0.2.8", - "redox_syscall", + "getrandom 0.2.9", + "redox_syscall 0.2.16", "thiserror", ] [[package]] name = "ref-cast" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c78fb8c9293bcd48ef6fce7b4ca950ceaf21210de6e105a883ee280c0f7b9ed" +checksum = "f43faa91b1c8b36841ee70e97188a869d37ae21759da6846d4be66de5bf7b12c" dependencies = [ "ref-cast-impl", ] [[package]] name = "ref-cast-impl" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f9c0c92af03644e4806106281fe2e068ac5bc0ae74a707266d06ea27bccee5f" +checksum = "8d2275aab483050ab2a7364c1a46604865ee7d6906684e08db0f090acf74f9e7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "regalloc2" -version = "0.3.2" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d43a209257d978ef079f3d446331d0f1794f5e0fc19b306a199983857833a779" +checksum = "80535183cae11b149d618fbd3c37e38d7cda589d82d7769e196ca9a9042d7621" dependencies = [ "fxhash", "log", @@ -7521,13 +8623,13 @@ dependencies = [ [[package]] name = "regex" -version = "1.7.0" +version = "1.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" dependencies = [ - "aho-corasick", + "aho-corasick 1.0.2", "memchr", - "regex-syntax", + "regex-syntax 0.7.2", ] [[package]] @@ -7536,23 +8638,20 @@ version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" dependencies = [ - "regex-syntax", + "regex-syntax 0.6.29", ] [[package]] name = "regex-syntax" -version = "0.6.28" +version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] -name = "remove_dir_all" -version = "0.5.3" +name = "regex-syntax" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" [[package]] name = "resolv-conf" @@ -7570,11 +8669,21 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7743f17af12fa0b03b803ba12cd6a8d9483a587e89c69445e3909655c0b9fabb" dependencies = [ - "crypto-bigint", + "crypto-bigint 0.4.9", "hmac 0.12.1", "zeroize", ] +[[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", +] + [[package]] name = "ring" version = "0.16.20" @@ -7592,9 +8701,9 @@ dependencies = [ [[package]] name = "rocksdb" -version = "0.19.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e9562ea1d70c0cc63a34a22d977753b50cca91cc6b6527750463bd5dd8697bc" +checksum = "bb6f170a4041d50a0ce04b0d2e14916d6ca863ea2e422689a5b694395d299ffe" dependencies = [ "libc", "librocksdb-sys", @@ -7663,9 +8772,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.21" +version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" [[package]] name = "rustc-hash" @@ -7694,7 +8803,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" dependencies = [ - "semver 1.0.16", + "semver 1.0.17", ] [[package]] @@ -7708,30 +8817,30 @@ dependencies = [ [[package]] name = "rustix" -version = "0.35.13" +version = "0.36.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "727a1a6d65f786ec22df8a81ca3121107f235970dc1705ed681d3e6e8b9cd5f9" +checksum = "14e4d67015953998ad0eb82887a0eb0129e18a7e2f3b7b0f6c422fddcd503d62" dependencies = [ "bitflags", "errno", - "io-lifetimes 0.7.5", + "io-lifetimes", "libc", - "linux-raw-sys 0.0.46", - "windows-sys 0.42.0", + "linux-raw-sys 0.1.4", + "windows-sys 0.45.0", ] [[package]] name = "rustix" -version = "0.36.6" +version = "0.37.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4feacf7db682c6c329c4ede12649cd36ecab0f3be5b7d74e6a20304725db4549" +checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d" dependencies = [ "bitflags", "errno", - "io-lifetimes 1.0.3", + "io-lifetimes", "libc", - "linux-raw-sys 0.1.4", - "windows-sys 0.42.0", + "linux-raw-sys 0.3.8", + "windows-sys 0.48.0", ] [[package]] @@ -7740,7 +8849,7 @@ version = "0.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7" dependencies = [ - "base64", + "base64 0.13.1", "log", "ring", "sct 0.6.1", @@ -7749,9 +8858,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.20.7" +version = "0.20.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "539a2bfe908f471bfa933876bd1eb6a19cf2176d375f82ef7f99530a40e48c2c" +checksum = "fff78fc74d175294f4e83b28343315ffcfb114b156f0185e9741cb5570f50e2f" dependencies = [ "log", "ring", @@ -7759,6 +8868,18 @@ dependencies = [ "webpki 0.22.0", ] +[[package]] +name = "rustls" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c911ba11bc8433e811ce56fde130ccf32f5127cab0e0194e9c68c5a5b671791e" +dependencies = [ + "log", + "ring", + "rustls-webpki", + "sct 0.7.0", +] + [[package]] name = "rustls-native-certs" version = "0.6.2" @@ -7773,18 +8894,28 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "1.0.1" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d194b56d58803a43635bdc398cd17e383d6f71f9182b9a192c127ca42494a59b" +dependencies = [ + "base64 0.21.2", +] + +[[package]] +name = "rustls-webpki" +version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0864aeff53f8c05aa08d86e5ef839d3dfcf07aeba2db32f12db0ef716e87bd55" +checksum = "d6207cd5ed3d8dca7816f8f3725513a34609c0c765bf652b8c3cb4cfd87db46b" dependencies = [ - "base64", + "ring", + "untrusted", ] [[package]] name = "rustversion" -version = "1.0.11" +version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" +checksum = "4f3208ce4d8448b3f3e7d168a73f5e0c43a61e32930de3bceeccedb388b6bf06" [[package]] name = "rusty-fork" @@ -7810,9 +8941,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" [[package]] name = "safe-mix" @@ -7823,6 +8954,15 @@ dependencies = [ "rustc_version 0.2.3", ] +[[package]] +name = "safe_arch" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794821e4ccb0d9f979512f9c1973480123f9bd62a90d74ab0f9426fcf8f4a529" +dependencies = [ + "bytemuck", +] + [[package]] name = "same-file" version = "1.0.6" @@ -7852,13 +8992,14 @@ dependencies = [ "ip_network", "libp2p", "log", + "multihash", "parity-scale-codec", "prost", "prost-build", "quickcheck", "rand 0.8.5", "sc-client-api", - "sc-network-common", + "sc-network", "sp-api", "sp-authority-discovery", "sp-blockchain", @@ -7918,12 +9059,16 @@ version = "4.0.0-dev" dependencies = [ "memmap2", "sc-chain-spec-derive", - "sc-network-common", + "sc-client-api", + "sc-executor", + "sc-network", "sc-telemetry", "serde", "serde_json", + "sp-blockchain", "sp-core", "sp-runtime", + "sp-state-machine", ] [[package]] @@ -7933,7 +9078,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -7942,11 +9087,11 @@ version = "0.10.0-dev" dependencies = [ "array-bytes", "chrono", - "clap 4.0.32", + "clap 4.3.2", "fdlimit", "futures", "futures-timer", - "libp2p", + "libp2p-identity", "log", "names", "parity-scale-codec", @@ -7957,7 +9102,6 @@ dependencies = [ "sc-client-db", "sc-keystore", "sc-network", - "sc-network-common", "sc-service", "sc-telemetry", "sc-tracing", @@ -7996,9 +9140,9 @@ dependencies = [ "sp-core", "sp-database", "sp-externalities", - "sp-keystore", "sp-runtime", "sp-state-machine", + "sp-statement-store", "sp-storage", "sp-test-primitives", "substrate-prometheus-endpoint", @@ -8010,6 +9154,7 @@ dependencies = [ name = "sc-client-db" version = "0.10.0-dev" dependencies = [ + "array-bytes", "criterion", "hash-db", "kitchensink-runtime", @@ -8045,7 +9190,7 @@ dependencies = [ "async-trait", "futures", "futures-timer", - "libp2p", + "libp2p-identity", "log", "mockall", "parking_lot 0.12.1", @@ -8109,7 +9254,6 @@ dependencies = [ "fork-tree", "futures", "log", - "merlin", "num-bigint", "num-rational", "num-traits", @@ -8121,11 +9265,11 @@ dependencies = [ "sc-consensus", "sc-consensus-epochs", "sc-consensus-slots", - "sc-keystore", "sc-network", "sc-network-test", "sc-telemetry", - "schnorrkel", + "sc-transaction-pool-api", + "scale-info", "sp-api", "sp-application-crypto", "sp-block-builder", @@ -8133,7 +9277,6 @@ dependencies = [ "sp-consensus", "sp-consensus-babe", "sp-consensus-slots", - "sp-consensus-vrf", "sp-core", "sp-inherents", "sp-keyring", @@ -8158,6 +9301,7 @@ dependencies = [ "sc-consensus-epochs", "sc-keystore", "sc-rpc-api", + "sc-transaction-pool-api", "serde", "serde_json", "sp-api", @@ -8170,9 +9314,71 @@ dependencies = [ "sp-keystore", "sp-runtime", "substrate-test-runtime-client", + "thiserror", + "tokio", +] + +[[package]] +name = "sc-consensus-beefy" +version = "4.0.0-dev" +dependencies = [ + "array-bytes", + "async-channel", + "async-trait", + "fnv", + "futures", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-block-builder", + "sc-client-api", + "sc-consensus", + "sc-network", + "sc-network-gossip", + "sc-network-sync", + "sc-network-test", + "sc-utils", + "serde", + "sp-api", + "sp-application-crypto", + "sp-arithmetic", + "sp-blockchain", + "sp-consensus", + "sp-consensus-beefy", + "sp-consensus-grandpa", + "sp-core", + "sp-keyring", + "sp-keystore", + "sp-mmr-primitives", + "sp-runtime", + "sp-tracing", + "substrate-prometheus-endpoint", + "substrate-test-runtime-client", "tempfile", "thiserror", "tokio", + "wasm-timer", +] + +[[package]] +name = "sc-consensus-beefy-rpc" +version = "4.0.0-dev" +dependencies = [ + "futures", + "jsonrpsee", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "sc-consensus-beefy", + "sc-rpc", + "serde", + "serde_json", + "sp-consensus-beefy", + "sp-core", + "sp-runtime", + "substrate-test-runtime-client", + "thiserror", + "tokio", ] [[package]] @@ -8187,6 +9393,77 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "sc-consensus-grandpa" +version = "0.10.0-dev" +dependencies = [ + "ahash 0.8.3", + "array-bytes", + "assert_matches", + "async-trait", + "dyn-clone", + "finality-grandpa", + "fork-tree", + "futures", + "futures-timer", + "log", + "parity-scale-codec", + "parking_lot 0.12.1", + "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-test", + "sc-telemetry", + "sc-transaction-pool-api", + "sc-utils", + "serde", + "serde_json", + "sp-api", + "sp-application-crypto", + "sp-arithmetic", + "sp-blockchain", + "sp-consensus", + "sp-consensus-grandpa", + "sp-core", + "sp-keyring", + "sp-keystore", + "sp-runtime", + "sp-tracing", + "substrate-prometheus-endpoint", + "substrate-test-runtime-client", + "thiserror", + "tokio", +] + +[[package]] +name = "sc-consensus-grandpa-rpc" +version = "0.10.0-dev" +dependencies = [ + "finality-grandpa", + "futures", + "jsonrpsee", + "log", + "parity-scale-codec", + "sc-block-builder", + "sc-client-api", + "sc-consensus-grandpa", + "sc-rpc", + "serde", + "sp-blockchain", + "sp-consensus-grandpa", + "sp-core", + "sp-keyring", + "sp-runtime", + "substrate-test-runtime-client", + "thiserror", + "tokio", +] + [[package]] name = "sc-consensus-manual-seal" version = "0.10.0-dev" @@ -8194,6 +9471,7 @@ dependencies = [ "assert_matches", "async-trait", "futures", + "futures-timer", "jsonrpsee", "log", "parity-scale-codec", @@ -8271,16 +9549,6 @@ dependencies = [ "substrate-test-runtime-client", ] -[[package]] -name = "sc-consensus-uncles" -version = "0.10.0-dev" -dependencies = [ - "sc-client-api", - "sp-authorship", - "sp-runtime", - "thiserror", -] - [[package]] name = "sc-executor" version = "0.10.0-dev" @@ -8288,18 +9556,17 @@ dependencies = [ "array-bytes", "assert_matches", "criterion", - "env_logger", - "lru", + "env_logger 0.9.3", "num_cpus", "parity-scale-codec", "parking_lot 0.12.1", "paste", "regex", "sc-executor-common", - "sc-executor-wasmi", "sc-executor-wasmtime", "sc-runtime-test", "sc-tracing", + "schnellru", "sp-api", "sp-core", "sp-externalities", @@ -8309,131 +9576,49 @@ dependencies = [ "sp-runtime", "sp-runtime-interface", "sp-state-machine", + "sp-tracing", "sp-trie", "sp-version", "sp-wasm-interface", "substrate-test-runtime", "tempfile", - "tracing", - "tracing-subscriber 0.2.25", - "wasmi 0.13.2", - "wat", -] - -[[package]] -name = "sc-executor-common" -version = "0.10.0-dev" -dependencies = [ - "sc-allocator", - "sp-maybe-compressed-blob", - "sp-wasm-interface", - "thiserror", - "wasm-instrument 0.3.0", - "wasmi 0.13.2", -] - -[[package]] -name = "sc-executor-wasmi" -version = "0.10.0-dev" -dependencies = [ - "log", - "sc-allocator", - "sc-executor-common", - "sp-runtime-interface", - "sp-wasm-interface", - "wasmi 0.13.2", -] - -[[package]] -name = "sc-executor-wasmtime" -version = "0.10.0-dev" -dependencies = [ - "cfg-if", - "libc", - "log", - "once_cell", - "parity-scale-codec", - "paste", - "rustix 0.35.13", - "sc-allocator", - "sc-executor-common", - "sc-runtime-test", - "sp-io", - "sp-runtime-interface", - "sp-wasm-interface", - "tempfile", - "wasmtime", - "wat", -] - -[[package]] -name = "sc-finality-grandpa" -version = "0.10.0-dev" -dependencies = [ - "ahash 0.8.2", - "array-bytes", - "assert_matches", - "async-trait", - "dyn-clone", - "finality-grandpa", - "fork-tree", - "futures", - "futures-timer", - "log", - "parity-scale-codec", - "parking_lot 0.12.1", - "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-test", - "sc-telemetry", - "sc-utils", - "serde", - "serde_json", - "sp-api", - "sp-application-crypto", - "sp-arithmetic", - "sp-blockchain", - "sp-consensus", - "sp-core", - "sp-finality-grandpa", - "sp-keyring", - "sp-keystore", - "sp-runtime", - "sp-tracing", - "substrate-prometheus-endpoint", - "substrate-test-runtime-client", + "tracing", + "tracing-subscriber 0.2.25", + "wat", +] + +[[package]] +name = "sc-executor-common" +version = "0.10.0-dev" +dependencies = [ + "sc-allocator", + "sp-maybe-compressed-blob", + "sp-wasm-interface", "thiserror", - "tokio", + "wasm-instrument 0.3.0", ] [[package]] -name = "sc-finality-grandpa-rpc" +name = "sc-executor-wasmtime" version = "0.10.0-dev" dependencies = [ - "finality-grandpa", - "futures", - "jsonrpsee", + "anyhow", + "cargo_metadata", + "cfg-if", + "libc", "log", "parity-scale-codec", - "sc-block-builder", - "sc-client-api", - "sc-finality-grandpa", - "sc-rpc", - "serde", - "sp-blockchain", - "sp-core", - "sp-finality-grandpa", - "sp-keyring", - "sp-runtime", - "substrate-test-runtime-client", - "thiserror", - "tokio", + "paste", + "rustix 0.36.14", + "sc-allocator", + "sc-executor-common", + "sc-runtime-test", + "sp-io", + "sp-runtime-interface", + "sp-wasm-interface", + "tempfile", + "wasmtime", + "wat", ] [[package]] @@ -8445,6 +9630,7 @@ dependencies = [ "futures-timer", "log", "sc-client-api", + "sc-network", "sc-network-common", "sp-blockchain", "sp-runtime", @@ -8455,7 +9641,6 @@ name = "sc-keystore" version = "4.0.0-dev" dependencies = [ "array-bytes", - "async-trait", "parking_lot 0.12.1", "serde_json", "sp-application-crypto", @@ -8471,9 +9656,9 @@ version = "0.10.0-dev" dependencies = [ "array-bytes", "assert_matches", + "async-channel", "async-trait", "asynchronous-codec", - "backtrace", "bytes", "either", "fnv", @@ -8481,26 +9666,25 @@ dependencies = [ "futures-timer", "ip_network", "libp2p", + "linked_hash_set", "log", - "lru", + "mockall", + "multistream-select", "parity-scale-codec", "parking_lot 0.12.1", + "partial_sort", "pin-project", "rand 0.8.5", - "sc-block-builder", "sc-client-api", - "sc-consensus", "sc-network-common", "sc-network-light", "sc-network-sync", - "sc-peerset", "sc-utils", "serde", "serde_json", "smallvec", "sp-arithmetic", "sp-blockchain", - "sp-consensus", "sp-core", "sp-runtime", "sp-test-primitives", @@ -8511,8 +9695,10 @@ dependencies = [ "tempfile", "thiserror", "tokio", + "tokio-test", "tokio-util", "unsigned-varint", + "wasm-timer", "zeroize", ] @@ -8520,16 +9706,17 @@ dependencies = [ name = "sc-network-bitswap" version = "0.10.0-dev" dependencies = [ + "async-channel", "cid", "futures", - "libp2p", + "libp2p-identity", "log", "prost", "prost-build", "sc-block-builder", "sc-client-api", "sc-consensus", - "sc-network-common", + "sc-network", "sp-blockchain", "sp-consensus", "sp-core", @@ -8547,38 +9734,30 @@ version = "0.10.0-dev" dependencies = [ "async-trait", "bitflags", - "bytes", "futures", - "futures-timer", - "libp2p", - "linked_hash_set", + "libp2p-identity", "parity-scale-codec", "prost-build", "sc-consensus", - "sc-peerset", - "serde", - "smallvec", - "sp-blockchain", "sp-consensus", - "sp-finality-grandpa", + "sp-consensus-grandpa", "sp-runtime", - "substrate-prometheus-endpoint", - "thiserror", + "tempfile", ] [[package]] name = "sc-network-gossip" version = "0.10.0-dev" dependencies = [ - "ahash 0.8.2", + "ahash 0.8.3", "futures", "futures-timer", "libp2p", "log", - "lru", "quickcheck", + "sc-network", "sc-network-common", - "sc-peerset", + "schnellru", "sp-runtime", "substrate-prometheus-endpoint", "substrate-test-runtime-client", @@ -8591,32 +9770,50 @@ name = "sc-network-light" version = "0.10.0-dev" dependencies = [ "array-bytes", + "async-channel", "futures", - "libp2p", + "libp2p-identity", "log", "parity-scale-codec", "prost", "prost-build", "sc-client-api", - "sc-network-common", - "sc-peerset", + "sc-network", "sp-blockchain", "sp-core", "sp-runtime", "thiserror", ] +[[package]] +name = "sc-network-statement" +version = "0.10.0-dev" +dependencies = [ + "array-bytes", + "async-channel", + "futures", + "libp2p", + "log", + "parity-scale-codec", + "sc-network", + "sc-network-common", + "sp-consensus", + "sp-statement-store", + "substrate-prometheus-endpoint", +] + [[package]] name = "sc-network-sync" version = "0.10.0-dev" dependencies = [ "array-bytes", + "async-channel", "async-trait", "fork-tree", "futures", + "futures-timer", "libp2p", "log", - "lru", "mockall", "parity-scale-codec", "prost", @@ -8625,15 +9822,16 @@ dependencies = [ "sc-block-builder", "sc-client-api", "sc-consensus", + "sc-network", "sc-network-common", - "sc-peerset", "sc-utils", + "schnellru", "smallvec", "sp-arithmetic", "sp-blockchain", "sp-consensus", + "sp-consensus-grandpa", "sp-core", - "sp-finality-grandpa", "sp-runtime", "sp-test-primitives", "sp-tracing", @@ -8662,9 +9860,9 @@ dependencies = [ "sc-network-light", "sc-network-sync", "sc-service", + "sc-utils", "sp-blockchain", "sp-consensus", - "sp-consensus-babe", "sp-core", "sp-runtime", "sp-tracing", @@ -8682,9 +9880,8 @@ dependencies = [ "libp2p", "log", "parity-scale-codec", - "pin-project", + "sc-network", "sc-network-common", - "sc-peerset", "sc-utils", "sp-consensus", "sp-runtime", @@ -8701,9 +9898,10 @@ dependencies = [ "futures", "futures-timer", "hyper", - "hyper-rustls", + "hyper-rustls 0.24.0", "lazy_static", "libp2p", + "log", "num_cpus", "once_cell", "parity-scale-codec", @@ -8712,14 +9910,16 @@ dependencies = [ "sc-block-builder", "sc-client-api", "sc-client-db", + "sc-network", "sc-network-common", - "sc-peerset", "sc-transaction-pool", "sc-transaction-pool-api", "sc-utils", "sp-api", "sp-consensus", "sp-core", + "sp-externalities", + "sp-keystore", "sp-offchain", "sp-runtime", "sp-tracing", @@ -8729,19 +9929,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "sc-peerset" -version = "4.0.0-dev" -dependencies = [ - "futures", - "libp2p", - "log", - "rand 0.8.5", - "sc-utils", - "serde_json", - "wasm-timer", -] - [[package]] name = "sc-proposer-metrics" version = "0.10.0-dev" @@ -8755,12 +9942,13 @@ name = "sc-rpc" version = "4.0.0-dev" dependencies = [ "assert_matches", - "env_logger", + "env_logger 0.9.3", "futures", "jsonrpsee", "log", "parity-scale-codec", "parking_lot 0.12.1", + "pretty_assertions", "sc-block-builder", "sc-chain-spec", "sc-client-api", @@ -8782,6 +9970,7 @@ dependencies = [ "sp-rpc", "sp-runtime", "sp-session", + "sp-statement-store", "sp-version", "substrate-test-runtime-client", "tokio", @@ -8832,10 +10021,13 @@ dependencies = [ "log", "parity-scale-codec", "parking_lot 0.12.1", + "pretty_assertions", "sc-block-builder", "sc-chain-spec", "sc-client-api", + "sc-service", "sc-transaction-pool-api", + "sc-utils", "serde", "serde_json", "sp-api", @@ -8893,11 +10085,9 @@ dependencies = [ "sc-network-light", "sc-network-sync", "sc-network-transactions", - "sc-offchain", "sc-rpc", "sc-rpc-server", "sc-rpc-spec-v2", - "sc-storage-monitor", "sc-sysinfo", "sc-telemetry", "sc-tracing", @@ -8936,6 +10126,7 @@ name = "sc-service-test" version = "2.0.0" dependencies = [ "array-bytes", + "async-channel", "fdlimit", "futures", "log", @@ -8947,7 +10138,7 @@ dependencies = [ "sc-consensus", "sc-executor", "sc-network", - "sc-network-common", + "sc-network-sync", "sc-service", "sc-transaction-pool-api", "sp-api", @@ -8976,16 +10167,34 @@ dependencies = [ "sp-core", ] +[[package]] +name = "sc-statement-store" +version = "4.0.0-dev" +dependencies = [ + "env_logger 0.9.3", + "log", + "parity-db", + "parking_lot 0.12.1", + "sc-client-api", + "sc-keystore", + "sp-api", + "sp-blockchain", + "sp-core", + "sp-runtime", + "sp-statement-store", + "substrate-prometheus-endpoint", + "tempfile", + "tokio", +] + [[package]] name = "sc-storage-monitor" version = "0.1.0" dependencies = [ - "clap 4.0.32", - "futures", + "clap 4.3.2", + "fs4", "log", - "nix 0.26.1", "sc-client-db", - "sc-utils", "sp-core", "thiserror", "tokio", @@ -9001,7 +10210,7 @@ dependencies = [ "sc-client-api", "sc-consensus-babe", "sc-consensus-epochs", - "sc-finality-grandpa", + "sc-consensus-grandpa", "serde", "serde_json", "sp-blockchain", @@ -9057,12 +10266,10 @@ dependencies = [ "lazy_static", "libc", "log", - "once_cell", "parking_lot 0.12.1", "regex", "rustc-hash", "sc-client-api", - "sc-rpc-server", "sc-tracing-proc-macro", "serde", "sp-api", @@ -9084,7 +10291,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -9099,7 +10306,6 @@ dependencies = [ "futures-timer", "linked-hash-map", "log", - "num-traits", "parity-scale-codec", "parking_lot 0.12.1", "sc-block-builder", @@ -9128,9 +10334,11 @@ dependencies = [ "async-trait", "futures", "log", + "parity-scale-codec", "serde", "serde_json", "sp-blockchain", + "sp-core", "sp-runtime", "thiserror", ] @@ -9139,21 +10347,22 @@ dependencies = [ name = "sc-utils" version = "4.0.0-dev" dependencies = [ - "backtrace", + "async-channel", "futures", "futures-timer", "lazy_static", "log", "parking_lot 0.12.1", "prometheus", + "sp-arithmetic", "tokio-test", ] [[package]] name = "scale-info" -version = "2.3.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "001cf62ece89779fd16105b5f515ad0e5cedcd5440d3dd806bb067978e7c3608" +checksum = "b569c32c806ec3abdf3b5869fb8bf1e0d275a7c1c9b0b05603d9464632649edf" dependencies = [ "bitvec", "cfg-if", @@ -9165,24 +10374,23 @@ dependencies = [ [[package]] name = "scale-info-derive" -version = "2.3.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "303959cf613a6f6efd19ed4b4ad5bf79966a13352716299ad532cfb115f4205c" +checksum = "53012eae69e5aa5c14671942a5dd47de59d4cdcff8532a6dd0e081faf1119482" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "schannel" -version = "0.1.20" +version = "0.1.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88d6731146462ea25d9244b2ed5fd1d716d25c52e4d54aa4fb0f3c4e9854dbe2" +checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88" dependencies = [ - "lazy_static", - "windows-sys 0.36.1", + "windows-sys 0.48.0", ] [[package]] @@ -9191,7 +10399,7 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "772575a524feeb803e5b0fcbc6dd9f367e579488197c94c6e4023aad2305774d" dependencies = [ - "ahash 0.8.2", + "ahash 0.8.3", "cfg-if", "hashbrown 0.13.2", ] @@ -9222,9 +10430,9 @@ checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" [[package]] name = "scratch" -version = "1.0.3" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ddccb15bcce173023b3fedd9436f882a0739b8dfb45e4f6b6002bee5929f61b2" +checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" [[package]] name = "sct" @@ -9264,19 +10472,33 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3be24c1842290c45df0a7bf069e0c268a747ad05a192f2fd7dcfdbc1cba40928" dependencies = [ - "base16ct", - "der", - "generic-array 0.14.6", - "pkcs8", + "base16ct 0.1.1", + "der 0.6.1", + "generic-array 0.14.7", + "pkcs8 0.9.0", + "subtle", + "zeroize", +] + +[[package]] +name = "sec1" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0aec48e813d6b90b15f0b8948af3c63483992dee44c03e9930b3eebdabe046e" +dependencies = [ + "base16ct 0.2.0", + "der 0.7.6", + "generic-array 0.14.7", + "pkcs8 0.10.2", "subtle", "zeroize", ] [[package]] name = "secp256k1" -version = "0.24.2" +version = "0.24.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9512ffd81e3a3503ed401f79c33168b9148c75038956039166cd750eaa037c3" +checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ "secp256k1-sys", ] @@ -9301,9 +10523,9 @@ dependencies = [ [[package]] name = "security-framework" -version = "2.7.0" +version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bc1bb97804af6631813c55739f771071e0f2ed33ee20b68c86ec505d906356c" +checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8" dependencies = [ "bitflags", "core-foundation", @@ -9314,9 +10536,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.6.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" +checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7" dependencies = [ "core-foundation-sys", "libc", @@ -9342,9 +10564,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.16" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +checksum = "bebd363326d05ec3e2f532ab7660680f3b02130d780c299bca73469d521bc0ed" dependencies = [ "serde", ] @@ -9357,45 +10579,44 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "serde" -version = "1.0.152" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "9e8c8cf938e98f769bc164923b06dce91cea1751522f46f8466461af04c9027d" dependencies = [ "serde_derive", ] -[[package]] -name = "serde_cbor" -version = "0.11.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bef2ebfde456fb76bbcf9f59315333decc4fda0b2b44b420243c11e0f5ec1f5" -dependencies = [ - "half", - "serde", -] - [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.164" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "serde_json" -version = "1.0.91" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" dependencies = [ - "itoa 1.0.5", + "itoa", "ryu", "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93107647184f6027e3b7dcb2e11034cf95ffa1e3a682c67951963ac69c1c007d" +dependencies = [ + "serde", +] + [[package]] name = "sha-1" version = "0.9.8" @@ -9417,7 +10638,7 @@ checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] @@ -9453,16 +10674,16 @@ checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.10.6", + "digest 0.10.7", ] [[package]] name = "sha3" -version = "0.10.6" +version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdf0c33fae925bdc080598b84bc15c55e7b9a4a43b3c704da051f977469691c9" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", "keccak", ] @@ -9483,9 +10704,9 @@ checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" [[package]] name = "signal-hook-registry" -version = "1.4.0" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" dependencies = [ "libc", ] @@ -9496,36 +10717,53 @@ version = "1.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" dependencies = [ - "digest 0.10.6", + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "signature" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e1788eed21689f9cf370582dfc467ef36ed9c707f073528ddafa8d83e3b8500" +dependencies = [ + "digest 0.10.7", "rand_core 0.6.4", ] [[package]] name = "simba" -version = "0.5.1" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e82063457853d00243beda9952e910b82593e4b07ae9f721b9278a99a0d3d5c" +checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" dependencies = [ "approx", "num-complex", "num-traits", "paste", + "wide", ] +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slab" -version = "0.4.7" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] [[package]] name = "slice-group-by" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" +checksum = "826167069c09b99d56f31e9ae5c99049e932a98c9dc2dac47645b08dbbf76ba7" [[package]] name = "smallvec" @@ -9541,14 +10779,14 @@ checksum = "5e9f0ab6ef7eb7353d9119c170a436d1bf248eea575ac42d19d12f4e34130831" [[package]] name = "snow" -version = "0.9.0" +version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "774d05a3edae07ce6d68ea6984f3c05e9bba8927e3dd591e3b479e5b03213d0d" +checksum = "5ccba027ba85743e09d15c03296797cad56395089b832b48b5a5217880f57733" dependencies = [ "aes-gcm 0.9.4", "blake2", "chacha20poly1305", - "curve25519-dalek 4.0.0-pre.5", + "curve25519-dalek 4.0.0-rc.1", "rand_core 0.6.4", "ring", "rustc_version 0.4.0", @@ -9558,9 +10796,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", "winapi", @@ -9572,7 +10810,7 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "41d1c5305e39e09653383c2c7244f2f78b3bcae37cf50c64cb4789c9f5096ec2" dependencies = [ - "base64", + "base64 0.13.1", "bytes", "flate2", "futures", @@ -9590,8 +10828,11 @@ dependencies = [ "hash-db", "log", "parity-scale-codec", + "scale-info", "sp-api-proc-macro", "sp-core", + "sp-externalities", + "sp-metadata-ir", "sp-runtime", "sp-state-machine", "sp-std", @@ -9605,11 +10846,14 @@ dependencies = [ name = "sp-api-proc-macro" version = "4.0.0-dev" dependencies = [ + "Inflector", + "assert_matches", "blake2", + "expander", "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -9622,6 +10866,7 @@ dependencies = [ "parity-scale-codec", "rustversion", "sc-block-builder", + "scale-info", "sp-api", "sp-consensus", "sp-core", @@ -9629,13 +10874,14 @@ dependencies = [ "sp-state-machine", "sp-tracing", "sp-version", + "static_assertions", "substrate-test-runtime-client", "trybuild", ] [[package]] name = "sp-application-crypto" -version = "7.0.0" +version = "23.0.0" dependencies = [ "parity-scale-codec", "scale-info", @@ -9653,13 +10899,12 @@ dependencies = [ "sp-application-crypto", "sp-core", "sp-keystore", - "sp-runtime", "substrate-test-runtime-client", ] [[package]] name = "sp-arithmetic" -version = "6.0.0" +version = "16.0.0" dependencies = [ "criterion", "integer-sqrt", @@ -9678,49 +10923,115 @@ dependencies = [ name = "sp-arithmetic-fuzzer" version = "2.0.0" dependencies = [ + "arbitrary", + "fraction", "honggfuzz", "num-bigint", - "primitive-types", "sp-arithmetic", ] [[package]] -name = "sp-authority-discovery" -version = "4.0.0-dev" +name = "sp-ark-bls12-377" +version = "0.4.0-beta" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e61a06f286f4e8565a67865ef52e83edabf447881898c94527ffc7b839177" dependencies = [ + "ark-bls12-377", + "ark-ff", + "ark-r1cs-std", + "ark-scale", + "ark-std", "parity-scale-codec", - "scale-info", - "sp-api", - "sp-application-crypto", - "sp-runtime", - "sp-std", + "sp-ark-models", ] [[package]] -name = "sp-authorship" -version = "4.0.0-dev" +name = "sp-ark-bls12-381" +version = "0.4.0-beta" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3352feef6c9c34022fa766a0c9a86a88a83d280a3e5b34781a1a9af98377a130" dependencies = [ - "async-trait", + "ark-bls12-381", + "ark-ff", + "ark-scale", + "ark-serialize", + "ark-std", "parity-scale-codec", - "sp-inherents", - "sp-runtime", - "sp-std", + "sp-ark-models", +] + +[[package]] +name = "sp-ark-bw6-761" +version = "0.4.0-beta" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf069165e230aef3c4680edea2d8ab3caa89c039e0b61fad2b8e061fb393668" +dependencies = [ + "ark-bw6-761", + "ark-ff", + "ark-scale", + "ark-std", + "parity-scale-codec", + "sp-ark-models", +] + +[[package]] +name = "sp-ark-ed-on-bls12-377" +version = "0.4.0-beta" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e63f1fe8e7e87cb0258d61212b019d4d0fd230293ec42a564eb671c83d437497" +dependencies = [ + "ark-ed-on-bls12-377", + "ark-ff", + "ark-r1cs-std", + "ark-scale", + "ark-serialize", + "ark-std", + "parity-scale-codec", + "sp-ark-models", ] [[package]] -name = "sp-beefy" +name = "sp-ark-ed-on-bls12-381-bandersnatch" +version = "0.4.0-beta" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "838ddc5508aff3e89f930e7e7f3565d0786ac27868cfd61587afe681011e1140" +dependencies = [ + "ark-ec", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ff", + "ark-r1cs-std", + "ark-scale", + "ark-std", + "parity-scale-codec", + "sp-ark-bls12-381", + "sp-ark-models", +] + +[[package]] +name = "sp-ark-models" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28fa906b809d7a346b2aa32a4bd0c884a75f9f588f9a4a07272f63eaf8a10765" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "getrandom 0.2.9", + "itertools", + "num-traits", + "zeroize", +] + +[[package]] +name = "sp-authority-discovery" version = "4.0.0-dev" dependencies = [ - "array-bytes", "parity-scale-codec", "scale-info", - "serde", "sp-api", "sp-application-crypto", - "sp-core", - "sp-io", - "sp-keystore", - "sp-mmr-primitives", "sp-runtime", "sp-std", ] @@ -9729,7 +11040,6 @@ dependencies = [ name = "sp-block-builder" version = "4.0.0-dev" dependencies = [ - "parity-scale-codec", "sp-api", "sp-inherents", "sp-runtime", @@ -9742,9 +11052,9 @@ version = "4.0.0-dev" dependencies = [ "futures", "log", - "lru", "parity-scale-codec", "parking_lot 0.12.1", + "schnellru", "sp-api", "sp-consensus", "sp-database", @@ -9760,14 +11070,11 @@ dependencies = [ "async-trait", "futures", "log", - "parity-scale-codec", "sp-core", "sp-inherents", "sp-runtime", "sp-state-machine", - "sp-std", "sp-test-primitives", - "sp-version", "thiserror", ] @@ -9780,7 +11087,6 @@ dependencies = [ "scale-info", "sp-api", "sp-application-crypto", - "sp-consensus", "sp-consensus-slots", "sp-inherents", "sp-runtime", @@ -9793,71 +11099,94 @@ name = "sp-consensus-babe" version = "0.10.0-dev" dependencies = [ "async-trait", - "merlin", "parity-scale-codec", "scale-info", "serde", "sp-api", "sp-application-crypto", - "sp-consensus", "sp-consensus-slots", - "sp-consensus-vrf", "sp-core", "sp-inherents", - "sp-keystore", "sp-runtime", "sp-std", "sp-timestamp", ] [[package]] -name = "sp-consensus-pow" -version = "0.10.0-dev" +name = "sp-consensus-beefy" +version = "4.0.0-dev" dependencies = [ + "array-bytes", + "lazy_static", "parity-scale-codec", + "scale-info", + "serde", "sp-api", + "sp-application-crypto", "sp-core", + "sp-io", + "sp-keystore", + "sp-mmr-primitives", "sp-runtime", "sp-std", + "strum", ] [[package]] -name = "sp-consensus-slots" -version = "0.10.0-dev" +name = "sp-consensus-grandpa" +version = "4.0.0-dev" dependencies = [ + "finality-grandpa", + "log", "parity-scale-codec", "scale-info", "serde", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-keystore", + "sp-runtime", "sp-std", - "sp-timestamp", ] [[package]] -name = "sp-consensus-vrf" +name = "sp-consensus-pow" version = "0.10.0-dev" dependencies = [ "parity-scale-codec", - "scale-info", - "schnorrkel", + "sp-api", "sp-core", "sp-runtime", "sp-std", ] +[[package]] +name = "sp-consensus-slots" +version = "0.10.0-dev" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-std", + "sp-timestamp", +] + [[package]] name = "sp-core" -version = "7.0.0" +version = "21.0.0" dependencies = [ "array-bytes", - "base58", "bitflags", "blake2", + "bounded-collections", + "bs58", "criterion", "dyn-clonable", "ed25519-zebra", "futures", "hash-db", "hash256-std-hasher", + "hex-literal", "impl-serde", "lazy_static", "libsecp256k1", @@ -9865,6 +11194,7 @@ dependencies = [ "merlin", "parity-scale-codec", "parking_lot 0.12.1", + "paste", "primitive-types", "rand 0.8.5", "regex", @@ -9879,37 +11209,63 @@ dependencies = [ "sp-debug-derive", "sp-externalities", "sp-runtime-interface", - "sp-serializer", "sp-std", "sp-storage", "ss58-registry", "substrate-bip39", "thiserror", "tiny-bip39", + "tracing", + "w3f-bls", "zeroize", ] [[package]] name = "sp-core-hashing" -version = "5.0.0" +version = "9.0.0" dependencies = [ - "blake2", + "blake2b_simd", "byteorder", - "digest 0.10.6", + "digest 0.10.7", "sha2 0.10.6", "sha3", - "sp-std", "twox-hash", ] [[package]] name = "sp-core-hashing-proc-macro" -version = "5.0.0" +version = "9.0.0" dependencies = [ - "proc-macro2", "quote", "sp-core-hashing", - "syn", + "syn 2.0.18", +] + +[[package]] +name = "sp-crypto-ec-utils" +version = "0.4.0" +dependencies = [ + "ark-algebra-test-templates", + "ark-bls12-377", + "ark-bls12-381", + "ark-bw6-761", + "ark-ec", + "ark-ed-on-bls12-377", + "ark-ed-on-bls12-381-bandersnatch", + "ark-ff", + "ark-scale", + "ark-serialize", + "ark-std", + "parity-scale-codec", + "sp-ark-bls12-377", + "sp-ark-bls12-381", + "sp-ark-bw6-761", + "sp-ark-ed-on-bls12-377", + "sp-ark-ed-on-bls12-381-bandersnatch", + "sp-ark-models", + "sp-io", + "sp-runtime-interface", + "sp-std", ] [[package]] @@ -9922,16 +11278,16 @@ dependencies = [ [[package]] name = "sp-debug-derive" -version = "5.0.0" +version = "8.0.0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "sp-externalities" -version = "0.13.0" +version = "0.19.0" dependencies = [ "environmental", "parity-scale-codec", @@ -9940,18 +11296,11 @@ dependencies = [ ] [[package]] -name = "sp-finality-grandpa" -version = "4.0.0-dev" +name = "sp-genesis-builder" +version = "0.1.0" dependencies = [ - "finality-grandpa", - "log", - "parity-scale-codec", - "scale-info", - "serde", + "serde_json", "sp-api", - "sp-application-crypto", - "sp-core", - "sp-keystore", "sp-runtime", "sp-std", ] @@ -9964,7 +11313,7 @@ dependencies = [ "futures", "impl-trait-for-tuples", "parity-scale-codec", - "sp-core", + "scale-info", "sp-runtime", "sp-std", "thiserror", @@ -9972,15 +11321,15 @@ dependencies = [ [[package]] name = "sp-io" -version = "7.0.0" +version = "23.0.0" dependencies = [ "bytes", "ed25519", "ed25519-dalek", - "futures", "libsecp256k1", "log", "parity-scale-codec", + "rustversion", "secp256k1", "sp-core", "sp-externalities", @@ -9996,7 +11345,7 @@ dependencies = [ [[package]] name = "sp-keyring" -version = "7.0.0" +version = "24.0.0" dependencies = [ "lazy_static", "sp-core", @@ -10006,17 +11355,12 @@ dependencies = [ [[package]] name = "sp-keystore" -version = "0.13.0" +version = "0.27.0" dependencies = [ - "async-trait", - "futures", - "merlin", "parity-scale-codec", "parking_lot 0.12.1", "rand 0.7.3", "rand_chacha 0.2.2", - "schnorrkel", - "serde", "sp-core", "sp-externalities", "thiserror", @@ -10027,7 +11371,17 @@ name = "sp-maybe-compressed-blob" version = "4.1.0-dev" dependencies = [ "thiserror", - "zstd", + "zstd 0.12.3+zstd.1.5.2", +] + +[[package]] +name = "sp-metadata-ir" +version = "0.1.0" +dependencies = [ + "frame-metadata", + "parity-scale-codec", + "scale-info", + "sp-std", ] [[package]] @@ -10067,11 +11421,9 @@ dependencies = [ name = "sp-npos-elections-fuzzer" version = "2.0.0-alpha.5" dependencies = [ - "clap 4.0.32", + "clap 4.3.2", "honggfuzz", - "parity-scale-codec", "rand 0.8.5", - "scale-info", "sp-npos-elections", "sp-runtime", ] @@ -10087,7 +11439,7 @@ dependencies = [ [[package]] name = "sp-panic-handler" -version = "5.0.0" +version = "8.0.0" dependencies = [ "backtrace", "lazy_static", @@ -10106,7 +11458,7 @@ dependencies = [ [[package]] name = "sp-runtime" -version = "7.0.0" +version = "24.0.0" dependencies = [ "either", "hash256-std-hasher", @@ -10128,12 +11480,12 @@ dependencies = [ "sp-tracing", "sp-weights", "substrate-test-runtime-client", - "zstd", + "zstd 0.12.3+zstd.1.5.2", ] [[package]] name = "sp-runtime-interface" -version = "7.0.0" +version = "17.0.0" dependencies = [ "bytes", "impl-trait-for-tuples", @@ -10156,13 +11508,13 @@ dependencies = [ [[package]] name = "sp-runtime-interface-proc-macro" -version = "6.0.0" +version = "11.0.0" dependencies = [ "Inflector", "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -10200,18 +11552,9 @@ dependencies = [ "sp-core", "sp-io", "sp-runtime-interface", - "sp-std", "substrate-wasm-builder", ] -[[package]] -name = "sp-serializer" -version = "4.0.0-dev" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "sp-session" version = "4.0.0-dev" @@ -10220,6 +11563,7 @@ dependencies = [ "scale-info", "sp-api", "sp-core", + "sp-keystore", "sp-runtime", "sp-staking", "sp-std", @@ -10229,8 +11573,10 @@ dependencies = [ name = "sp-staking" version = "4.0.0-dev" dependencies = [ + "impl-trait-for-tuples", "parity-scale-codec", "scale-info", + "serde", "sp-core", "sp-runtime", "sp-std", @@ -10238,7 +11584,7 @@ dependencies = [ [[package]] name = "sp-state-machine" -version = "0.13.0" +version = "0.28.0" dependencies = [ "array-bytes", "assert_matches", @@ -10260,13 +11606,36 @@ dependencies = [ "trie-db", ] +[[package]] +name = "sp-statement-store" +version = "4.0.0-dev" +dependencies = [ + "aes-gcm 0.10.2", + "curve25519-dalek 3.2.0", + "ed25519-dalek", + "hkdf", + "parity-scale-codec", + "rand 0.8.5", + "scale-info", + "sha2 0.10.6", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-externalities", + "sp-runtime", + "sp-runtime-interface", + "sp-std", + "thiserror", + "x25519-dalek 2.0.0-pre.1", +] + [[package]] name = "sp-std" -version = "5.0.0" +version = "8.0.0" [[package]] name = "sp-storage" -version = "7.0.0" +version = "13.0.0" dependencies = [ "impl-serde", "parity-scale-codec", @@ -10281,10 +11650,12 @@ name = "sp-test-primitives" version = "2.0.0" dependencies = [ "parity-scale-codec", + "scale-info", "serde", "sp-application-crypto", "sp-core", "sp-runtime", + "sp-std", ] [[package]] @@ -10292,8 +11663,6 @@ name = "sp-timestamp" version = "4.0.0-dev" dependencies = [ "async-trait", - "futures-timer", - "log", "parity-scale-codec", "sp-inherents", "sp-runtime", @@ -10303,7 +11672,7 @@ dependencies = [ [[package]] name = "sp-tracing" -version = "6.0.0" +version = "10.0.0" dependencies = [ "parity-scale-codec", "sp-std", @@ -10325,7 +11694,6 @@ name = "sp-transaction-storage-proof" version = "4.0.0-dev" dependencies = [ "async-trait", - "log", "parity-scale-codec", "scale-info", "sp-core", @@ -10337,13 +11705,13 @@ dependencies = [ [[package]] name = "sp-trie" -version = "7.0.0" +version = "22.0.0" dependencies = [ - "ahash 0.8.2", + "ahash 0.8.3", "array-bytes", "criterion", "hash-db", - "hashbrown 0.12.3", + "hashbrown 0.14.0", "lazy_static", "memory-db", "nohash-hasher", @@ -10364,7 +11732,7 @@ dependencies = [ [[package]] name = "sp-version" -version = "5.0.0" +version = "22.0.0" dependencies = [ "impl-serde", "parity-scale-codec", @@ -10380,30 +11748,30 @@ dependencies = [ [[package]] name = "sp-version-proc-macro" -version = "4.0.0-dev" +version = "8.0.0" dependencies = [ "parity-scale-codec", "proc-macro2", "quote", "sp-version", - "syn", + "syn 2.0.18", ] [[package]] name = "sp-wasm-interface" -version = "7.0.0" +version = "14.0.0" dependencies = [ + "anyhow", "impl-trait-for-tuples", "log", "parity-scale-codec", "sp-std", - "wasmi 0.13.2", "wasmtime", ] [[package]] name = "sp-weights" -version = "4.0.0" +version = "20.0.0" dependencies = [ "parity-scale-codec", "scale-info", @@ -10423,9 +11791,20 @@ checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] name = "spin" -version = "0.9.4" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spinners" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" +checksum = "08615eea740067d9899969bc2891c68a19c315cb1f66640af9a9ecb91b13bcab" +dependencies = [ + "lazy_static", + "maplit", + "strum", +] [[package]] name = "spki" @@ -10434,14 +11813,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67cf02bbac7a337dc36e4f5a693db6c21e7863f45070f7064577eb4367a3212b" dependencies = [ "base64ct", - "der", + "der 0.6.1", +] + +[[package]] +name = "spki" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1e996ef02c474957d681f1b05213dfb0abab947b446a62d37770b23500184a" +dependencies = [ + "base64ct", + "der 0.7.6", ] [[package]] name = "ss58-registry" -version = "1.36.0" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d92659e7d18d82b803824a9ba5a6022cff101c3491d027c1c1d8d30e749284" +checksum = "eb47a8ad42e5fc72d5b1eb104a5546937eaf39843499948bb666d6e93c62423b" dependencies = [ "Inflector", "num-format", @@ -10489,20 +11878,7 @@ dependencies = [ "memchr", "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "statrs" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05bdbb8e4e78216a85785a85d3ec3183144f98d0097b9281802c019bb07a6f05" -dependencies = [ - "approx", - "lazy_static", - "nalgebra", - "num-traits", - "rand 0.8.5", + "syn 1.0.109", ] [[package]] @@ -10530,7 +11906,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", + "syn 1.0.109", ] [[package]] @@ -10539,7 +11915,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7e94b1ec00bad60e6410e058b52f1c66de3dc5fe4d62d09b3e52bb7d3b73e25" dependencies = [ - "base64", + "base64 0.13.1", "crc", "lazy_static", "md-5", @@ -10554,9 +11930,9 @@ dependencies = [ [[package]] name = "subkey" -version = "2.0.2" +version = "3.0.0" dependencies = [ - "clap 4.0.32", + "clap 4.3.2", "sc-cli", ] @@ -10576,15 +11952,26 @@ dependencies = [ [[package]] name = "substrate-build-script-utils" version = "3.0.0" + +[[package]] +name = "substrate-cli-test-utils" +version = "0.1.0" dependencies = [ - "platforms 2.0.0", + "assert_cmd", + "futures", + "nix 0.26.2", + "node-primitives", + "regex", + "sp-rpc", + "substrate-rpc-client", + "tokio", ] [[package]] name = "substrate-frame-cli" version = "4.0.0-dev" dependencies = [ - "clap 4.0.32", + "clap 4.3.2", "frame-support", "frame-system", "sc-cli", @@ -10662,11 +12049,9 @@ name = "substrate-state-trie-migration-rpc" version = "4.0.0-dev" dependencies = [ "jsonrpsee", - "log", "parity-scale-codec", "sc-client-api", "sc-rpc-api", - "scale-info", "serde", "serde_json", "sp-core", @@ -10705,41 +12090,39 @@ dependencies = [ name = "substrate-test-runtime" version = "2.0.0" dependencies = [ - "beefy-merkle-tree", - "cfg-if", + "array-bytes", + "frame-executive", "frame-support", "frame-system", "frame-system-rpc-runtime-api", "futures", "log", - "memory-db", "pallet-babe", + "pallet-balances", "pallet-timestamp", "parity-scale-codec", "sc-block-builder", "sc-executor", "sc-service", "scale-info", - "serde", "sp-api", "sp-application-crypto", - "sp-beefy", "sp-block-builder", "sp-consensus", "sp-consensus-aura", "sp-consensus-babe", + "sp-consensus-grandpa", "sp-core", "sp-externalities", - "sp-finality-grandpa", "sp-inherents", "sp-io", "sp-keyring", "sp-offchain", "sp-runtime", - "sp-runtime-interface", "sp-session", "sp-state-machine", "sp-std", + "sp-tracing", "sp-transaction-pool", "sp-trie", "sp-version", @@ -10753,7 +12136,6 @@ name = "substrate-test-runtime-client" version = "2.0.0" dependencies = [ "futures", - "parity-scale-codec", "sc-block-builder", "sc-client-api", "sc-consensus", @@ -10799,7 +12181,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -10819,10 +12201,11 @@ dependencies = [ "build-helper", "cargo_metadata", "filetime", + "parity-wasm", "sp-maybe-compressed-blob", "strum", "tempfile", - "toml", + "toml 0.7.4", "walkdir", "wasm-opt", ] @@ -10844,9 +12227,20 @@ checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" [[package]] name = "syn" -version = "1.0.107" +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.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e" dependencies = [ "proc-macro2", "quote", @@ -10861,15 +12255,15 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "unicode-xid", ] [[package]] name = "system-configuration" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75182f12f490e953596550b65ee31bda7c8e043d9386174b353bda50838c3fd" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags", "core-foundation", @@ -10892,68 +12286,76 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "target-lexicon" -version = "0.12.5" +version = "0.12.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" +checksum = "fd1ba337640d60c3e96bc6f0638a939b9c9a7f2c316a1598c279828b3d1dc8c5" [[package]] name = "tempfile" -version = "3.3.0" +version = "3.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6" dependencies = [ + "autocfg", "cfg-if", "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi", + "redox_syscall 0.3.5", + "rustix 0.37.19", + "windows-sys 0.48.0", ] [[package]] name = "termcolor" -version = "1.1.3" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ "winapi-util", ] [[package]] name = "termtree" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95059e91184749cb66be6dc994f67f182b6d897cb3df74a5bf66b5e709295fd8" +checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" [[package]] name = "textwrap" -version = "0.11.0" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" -dependencies = [ - "unicode-width", -] +checksum = "222a222a5bfe1bba4a77b45ec488a741b3cb8872e5e499451fd7d0129c9c7c3d" [[package]] name = "thiserror" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.38" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] @@ -10964,10 +12366,11 @@ checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" [[package]] name = "thread_local" -version = "1.1.4" +version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" dependencies = [ + "cfg-if", "once_cell", ] @@ -10982,12 +12385,11 @@ dependencies = [ [[package]] name = "tikv-jemalloc-sys" -version = "0.5.2+5.3.0-patched" +version = "0.5.3+5.3.0-patched" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec45c14da997d0925c7835883e4d5c181f196fa142f8c19d7643d1e9af2592c3" +checksum = "a678df20055b43e57ef8cddde41cdfda9a3c1a060b67f4c5836dfb1d78543ba8" dependencies = [ "cc", - "fs_extra", "libc", ] @@ -11004,11 +12406,11 @@ dependencies = [ [[package]] name = "time" -version = "0.3.17" +version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "8f3403384eaacbca9923fa06940178ac13e4edb725486d70e8e15881d0c836cc" dependencies = [ - "itoa 1.0.5", + "itoa", "serde", "time-core", "time-macros", @@ -11016,15 +12418,15 @@ dependencies = [ [[package]] name = "time-core" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" +checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "372950940a5f07bf38dbe211d7283c9e6d7327df53794992d293e534c733d09b" dependencies = [ "time-core", ] @@ -11078,20 +12480,19 @@ dependencies = [ [[package]] name = "tinyvec_macros" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.23.0" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ "autocfg", "bytes", "libc", - "memchr", "mio", "num_cpus", "parking_lot 0.12.1", @@ -11099,18 +12500,29 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys 0.42.0", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.18", ] [[package]] -name = "tokio-macros" -version = "1.8.2" +name = "tokio-retry" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" dependencies = [ - "proc-macro2", - "quote", - "syn", + "pin-project", + "rand 0.8.5", + "tokio", ] [[package]] @@ -11119,16 +12531,26 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls 0.20.7", + "rustls 0.20.8", "tokio", "webpki 0.22.0", ] +[[package]] +name = "tokio-rustls" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0d409377ff5b1e3ca6437aa86c1eb7d40c134bfec254e44c830defa92669db5" +dependencies = [ + "rustls 0.21.1", + "tokio", +] + [[package]] name = "tokio-stream" -version = "0.1.11" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +checksum = "397c988d37662c7dda6d2208364a706264bf3d6138b11d436cbac0ad38832842" dependencies = [ "futures-core", "pin-project-lite 0.2.9", @@ -11151,9 +12573,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.4" +version = "0.7.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" dependencies = [ "bytes", "futures-core", @@ -11166,11 +12588,45 @@ dependencies = [ [[package]] name = "toml" -version = "0.5.10" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6135d499e69981f9ff0ef2167955a5333c35e36f6937d382974566b3d5b94ec" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a76a9312f5ba4c2dec6b9161fdf25d87ad8a09256ccea5a556fef03c706a10f" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" +checksum = "2380d56e8670370eee6566b0bfd4265f65b3f432e8c6d85623f728d4fa31f739" dependencies = [ + "indexmap", "serde", + "serde_spanned", + "toml_datetime", + "winnow", ] [[package]] @@ -11186,9 +12642,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.3.5" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +checksum = "5d1d42a9b3f3ec46ba828e8d376aec14592ea199f70a06a548587ecd1c4ab658" dependencies = [ "bitflags", "bytes", @@ -11229,20 +12685,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +checksum = "0f57e3ca2a01450b1a921183a9c9cbfda207fd822cef4ccb00a65402cbba7a74" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", ] [[package]] name = "tracing-core" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", "valuable", @@ -11304,9 +12760,9 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.16" +version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +checksum = "30a651bc37f915e81f087d86e62a18eec5f79550c7faff886f7090b4ea757c77" dependencies = [ "matchers 0.1.0", "nu-ansi-term", @@ -11322,9 +12778,9 @@ dependencies = [ [[package]] name = "trie-bench" -version = "0.33.0" +version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5b26bd2cdd7641c5beb476b314c0cb1f629832bf21a6235f545e2d47bc9d05a" +checksum = "4f54b4f9d51d368e62cf7e0730c7c1e18fc658cc84333656bab5b328f44aa964" dependencies = [ "criterion", "hash-db", @@ -11338,12 +12794,12 @@ dependencies = [ [[package]] name = "trie-db" -version = "0.24.0" +version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "004e1e8f92535694b4cb1444dc5a8073ecf0815e3357f729638b9f8fc4062908" +checksum = "767abe6ffed88a1889671a102c2861ae742726f52e0a5a425b92c9fbfa7e9c85" dependencies = [ "hash-db", - "hashbrown 0.12.3", + "hashbrown 0.13.2", "log", "rustc-hex", "smallvec", @@ -11351,18 +12807,18 @@ dependencies = [ [[package]] name = "trie-root" -version = "0.17.0" +version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a36c5ca3911ed3c9a5416ee6c679042064b93fc637ded67e25f92e68d783891" +checksum = "d4ed310ef5ab98f5fa467900ed906cb9232dd5376597e00fd4cba2a449d06c0b" dependencies = [ "hash-db", ] [[package]] name = "trie-standardmap" -version = "0.15.2" +version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3161ba520ab28cd8e6b68e1126f1009f6e335339d1a73b978139011703264c8" +checksum = "684aafb332fae6f83d7fe10b3fbfdbe39a1b3234c4e2a618f030815838519516" dependencies = [ "hash-db", "keccak-hasher", @@ -11416,47 +12872,58 @@ dependencies = [ [[package]] name = "try-lock" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" [[package]] name = "try-runtime-cli" version = "0.10.0-dev" dependencies = [ - "clap 4.0.32", + "assert_cmd", + "async-trait", + "clap 4.3.2", "frame-remote-externalities", "frame-try-runtime", "hex", "log", + "node-primitives", "parity-scale-codec", + "regex", "sc-cli", "sc-executor", - "sc-service", "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-cli-test-utils", "substrate-rpc-client", + "tempfile", "tokio", - "zstd", + "zstd 0.12.3+zstd.1.5.2", ] [[package]] name = "trybuild" -version = "1.0.74" +version = "1.0.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "654bfc024d30963fce210f22f98956407fe8c8365eb85a1fa530f6f8844137ed" +checksum = "501dbdbb99861e4ab6b60eb6a7493956a9defb644fd034bc4a5ef27c693c8a3a" dependencies = [ + "basic-toml", "dissimilar", "glob", "once_cell", @@ -11464,7 +12931,6 @@ dependencies = [ "serde_derive", "serde_json", "termcolor", - "toml", ] [[package]] @@ -11480,7 +12946,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4712ee30d123ec7ae26d1e1b218395a16c87cdbaf4b3925d170d684af62ea5e8" dependencies = [ "async-trait", - "base64", + "base64 0.13.1", "futures", "log", "md-5", @@ -11499,7 +12965,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ "cfg-if", - "digest 0.10.6", + "digest 0.10.7", "rand 0.8.5", "static_assertions", ] @@ -11530,15 +12996,15 @@ dependencies = [ [[package]] name = "unicode-bidi" -version = "0.3.8" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" [[package]] name = "unicode-ident" -version = "1.0.6" +version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" [[package]] name = "unicode-normalization" @@ -11567,7 +13033,17 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" dependencies = [ - "generic-array 0.14.6", + "generic-array 0.14.7", + "subtle", +] + +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", "subtle", ] @@ -11591,22 +13067,28 @@ checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" [[package]] name = "url" -version = "2.3.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +checksum = "50bff7831e19200a85b17131d085c25d7811bc4e186efdaf54bbd132994a88cb" dependencies = [ "form_urlencoded", - "idna 0.3.0", + "idna 0.4.0", "percent-encoding", ] +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + [[package]] name = "uuid" -version = "1.2.2" +version = "1.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "422ee0de9031b5b948b97a8fc04e3aa35230001a722ddd27943e0be31564ce4c" +checksum = "345444e32442451b267fc254ae85a209c64be56d2890e601a0c37ff0c3c5ecd2" dependencies = [ - "getrandom 0.2.8", + "getrandom 0.2.9", ] [[package]] @@ -11633,6 +13115,30 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +[[package]] +name = "w3f-bls" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7335e4c132c28cc43caef6adb339789e599e39adbe78da0c4d547fad48cbc331" +dependencies = [ + "ark-bls12-377", + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-serialize-derive", + "arrayref", + "constcat", + "digest 0.10.7", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "sha2 0.10.6", + "sha3", + "thiserror", + "zeroize", +] + [[package]] name = "wait-timeout" version = "0.2.0" @@ -11659,12 +13165,11 @@ checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "walkdir" -version = "2.3.2" +version = "2.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +checksum = "36df944cda56c7d8d8b7496af378e6b16de9284591917d307c9b4d313c44e698" dependencies = [ "same-file", - "winapi", "winapi-util", ] @@ -11698,9 +13203,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.83" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" +checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73" dependencies = [ "cfg-if", "wasm-bindgen-macro", @@ -11708,24 +13213,24 @@ dependencies = [ [[package]] name = "wasm-bindgen-backend" -version = "0.2.83" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" +checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.18", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.33" +version = "0.4.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23639446165ca5a5de86ae1d8896b737ae80319560fbaa4c2887b7da6e7ebd7d" +checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e" dependencies = [ "cfg-if", "js-sys", @@ -11735,9 +13240,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.83" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" +checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -11745,28 +13250,28 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.83" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" +checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.18", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.83" +version = "0.2.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" +checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93" [[package]] name = "wasm-encoder" -version = "0.20.0" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05632e0a66a6ed8cca593c24223aabd6262f256c3693ad9822c315285f010614" +checksum = "18c41dbd92eaebf3612a39be316540b8377c871cb9bde6b064af962984912881" dependencies = [ "leb128", ] @@ -11791,9 +13296,9 @@ dependencies = [ [[package]] name = "wasm-opt" -version = "0.111.0" +version = "0.112.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84a303793cbc01fb96551badfc7367db6007396bba6bac97936b3c8b6f7fdb41" +checksum = "87fef6d0d508f08334e0ab0e6877feb4c0ecb3956bcf2cb950699b22fedf3e9c" dependencies = [ "anyhow", "libc", @@ -11807,9 +13312,9 @@ dependencies = [ [[package]] name = "wasm-opt-cxx-sys" -version = "0.111.0" +version = "0.112.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9c9deb56f8a9f2ec177b3bd642a8205621835944ed5da55f2388ef216aca5a4" +checksum = "bc816bbc1596c8f2e8127e137a760c798023ef3d378f2ae51f0f1840e2dfa445" dependencies = [ "anyhow", "cxx", @@ -11819,15 +13324,14 @@ dependencies = [ [[package]] name = "wasm-opt-sys" -version = "0.111.0" +version = "0.112.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4432e28b542738a9776cedf92e8a99d8991c7b4667ee2c7ccddfb479dd2856a7" +checksum = "40199e4f68ef1071b3c6d0bd8026a12b481865d4b9e49c156932ea9a6234dd14" dependencies = [ "anyhow", "cc", "cxx", "cxx-build", - "regex", ] [[package]] @@ -11847,89 +13351,60 @@ dependencies = [ [[package]] name = "wasmi" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06c326c93fbf86419608361a2c925a31754cf109da1b8b55737070b4d6669422" -dependencies = [ - "parity-wasm", - "wasmi-validation", - "wasmi_core 0.2.1", -] - -[[package]] -name = "wasmi" -version = "0.20.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01bf50edb2ea9d922aa75a7bf3c15e26a6c9e2d18c56e862b49737a582901729" +checksum = "e51fb5c61993e71158abf5bb863df2674ca3ec39ed6471c64f07aeaf751d67b4" dependencies = [ - "spin 0.9.4", + "intx", + "smallvec", + "spin 0.9.8", "wasmi_arena", - "wasmi_core 0.5.0", + "wasmi_core", "wasmparser-nostd", ] -[[package]] -name = "wasmi-validation" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ff416ad1ff0c42e5a926ed5d5fab74c0f098749aa0ad8b2a34b982ce0e867b" -dependencies = [ - "parity-wasm", -] - [[package]] name = "wasmi_arena" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ea379cbb0b41f3a9f0bf7b47036d036aae7f43383d8cc487d4deccf40dee0a" - -[[package]] -name = "wasmi_core" -version = "0.2.1" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57d20cb3c59b788653d99541c646c561c9dd26506f25c0cebfe810659c54c6d7" -dependencies = [ - "downcast-rs", - "libm 0.2.6", - "memory_units", - "num-rational", - "num-traits", -] +checksum = "401c1f35e413fac1846d4843745589d9ec678977ab35a384db8ae7830525d468" [[package]] name = "wasmi_core" -version = "0.5.0" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c5bf998ab792be85e20e771fe14182b4295571ad1d4f89d3da521c1bef5f597a" +checksum = "624e6333e861ef49095d2d678b76ebf30b06bf37effca845be7e5b87c90071b7" dependencies = [ "downcast-rs", - "libm 0.2.6", + "libm 0.2.7", "num-traits", + "paste", ] [[package]] name = "wasmparser" -version = "0.89.1" +version = "0.102.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab5d3e08b13876f96dd55608d03cd4883a0545884932d5adf11925876c96daef" +checksum = "48134de3d7598219ab9eaf6b91b15d8e50d31da76b8519fe4ecfcec2cf35104b" dependencies = [ "indexmap", + "url", ] [[package]] name = "wasmparser-nostd" -version = "0.91.0" +version = "0.100.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c37f310b5a62bfd5ae7c0f1d8e6f98af16a5d6d84ba764e9c36439ec14e318b" +checksum = "9157cab83003221bfd385833ab587a039f5d6fa7304854042ba358a3b09e0724" dependencies = [ "indexmap-nostd", ] [[package]] name = "wasmtime" -version = "1.0.2" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ad5af6ba38311282f2a21670d96e78266e8c8e2f38cbcd52c254df6ccbc7731" +checksum = "f907fdead3153cb9bfb7a93bbd5b62629472dc06dee83605358c64c52ed3dda9" dependencies = [ "anyhow", "bincode", @@ -11937,7 +13412,7 @@ dependencies = [ "indexmap", "libc", "log", - "object 0.29.0", + "object", "once_cell", "paste", "psm", @@ -11950,43 +13425,43 @@ dependencies = [ "wasmtime-environ", "wasmtime-jit", "wasmtime-runtime", - "windows-sys 0.36.1", + "windows-sys 0.45.0", ] [[package]] name = "wasmtime-asm-macros" -version = "1.0.2" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45de63ddfc8b9223d1adc8f7b2ee5f35d1f6d112833934ad7ea66e4f4339e597" +checksum = "d3b9daa7c14cd4fa3edbf69de994408d5f4b7b0959ac13fa69d465f6597f810d" dependencies = [ "cfg-if", ] [[package]] name = "wasmtime-cache" -version = "1.0.2" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcd849399d17d2270141cfe47fa0d91ee52d5f8ea9b98cf7ddde0d53e5f79882" +checksum = "c86437fa68626fe896e5afc69234bb2b5894949083586535f200385adfd71213" dependencies = [ "anyhow", - "base64", + "base64 0.21.2", "bincode", "directories-next", "file-per-thread-logger", "log", - "rustix 0.35.13", + "rustix 0.36.14", "serde", - "sha2 0.9.9", - "toml", - "windows-sys 0.36.1", - "zstd", + "sha2 0.10.6", + "toml 0.5.11", + "windows-sys 0.45.0", + "zstd 0.11.2+zstd.1.5.2", ] [[package]] name = "wasmtime-cranelift" -version = "1.0.2" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bd91339b742ff20bfed4532a27b73c86b5bcbfedd6bea2dcdf2d64471e1b5c6" +checksum = "b1cefde0cce8cb700b1b21b6298a3837dba46521affd7b8c38a9ee2c869eee04" dependencies = [ "anyhow", "cranelift-codegen", @@ -11994,27 +13469,43 @@ dependencies = [ "cranelift-frontend", "cranelift-native", "cranelift-wasm", - "gimli 0.26.2", + "gimli", "log", - "object 0.29.0", + "object", "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", + "object", + "target-lexicon", "wasmtime-environ", ] [[package]] name = "wasmtime-environ" -version = "1.0.2" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebb881c61f4f627b5d45c54e629724974f8a8890d455bcbe634330cc27309644" +checksum = "a990198cee4197423045235bf89d3359e69bd2ea031005f4c2d901125955c949" dependencies = [ "anyhow", "cranelift-entity", - "gimli 0.26.2", + "gimli", "indexmap", "log", - "object 0.29.0", + "object", "serde", "target-lexicon", "thiserror", @@ -12024,45 +13515,55 @@ dependencies = [ [[package]] name = "wasmtime-jit" -version = "1.0.2" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1985c628011fe26adf5e23a5301bdc79b245e0e338f14bb58b39e4e25e4d8681" +checksum = "0de48df552cfca1c9b750002d3e07b45772dd033b0b206d5c0968496abf31244" dependencies = [ - "addr2line 0.17.0", + "addr2line", "anyhow", "bincode", "cfg-if", "cpp_demangle", - "gimli 0.26.2", + "gimli", "log", - "object 0.29.0", + "object", "rustc-demangle", - "rustix 0.35.13", "serde", "target-lexicon", - "thiserror", "wasmtime-environ", "wasmtime-jit-debug", + "wasmtime-jit-icache-coherence", "wasmtime-runtime", - "windows-sys 0.36.1", + "windows-sys 0.45.0", ] [[package]] name = "wasmtime-jit-debug" -version = "1.0.2" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f671b588486f5ccec8c5a3dba6b4c07eac2e66ab8c60e6f4e53717c77f709731" +checksum = "6e0554b84c15a27d76281d06838aed94e13a77d7bf604bbbaf548aa20eb93846" dependencies = [ - "object 0.29.0", + "object", "once_cell", - "rustix 0.35.13", + "rustix 0.36.14", +] + +[[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 = "1.0.2" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee8f92ad4b61736339c29361da85769ebc200f184361959d1792832e592a1afd" +checksum = "658cf6f325232b6760e202e5255d823da5e348fdea827eff0a2a22319000b441" dependencies = [ "anyhow", "cc", @@ -12072,22 +13573,21 @@ dependencies = [ "log", "mach", "memfd", - "memoffset 0.6.5", + "memoffset 0.8.0", "paste", "rand 0.8.5", - "rustix 0.35.13", - "thiserror", + "rustix 0.36.14", "wasmtime-asm-macros", "wasmtime-environ", "wasmtime-jit-debug", - "windows-sys 0.36.1", + "windows-sys 0.45.0", ] [[package]] name = "wasmtime-types" -version = "1.0.2" +version = "8.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d23d61cb4c46e837b431196dd06abb11731541021916d03476a178b54dc07aeb" +checksum = "a4f6fffd2a1011887d57f07654dd112791e872e3ff4a2e626aee8059ee17f06f" dependencies = [ "cranelift-entity", "serde", @@ -12097,9 +13597,9 @@ dependencies = [ [[package]] name = "wast" -version = "50.0.0" +version = "60.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2cbb59d4ac799842791fe7e806fa5dbbf6b5554d538e51cc8e176db6ff0ae34" +checksum = "bd06cc744b536e30387e72a48fdd492105b9c938bb4f415c39c616a7a0a697ad" dependencies = [ "leb128", "memchr", @@ -12109,18 +13609,18 @@ dependencies = [ [[package]] name = "wat" -version = "1.0.52" +version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "584aaf7a1ecf4d383bbe1a25eeab0cbb8ff96acc6796707ff65cde48f4632f15" +checksum = "5abe520f0ab205366e9ac7d3e6b2fc71de44e32a2b58f2ec871b6b575bdcea3b" dependencies = [ "wast", ] [[package]] name = "web-sys" -version = "0.3.60" +version = "0.3.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" +checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2" dependencies = [ "js-sys", "wasm-bindgen", @@ -12181,7 +13681,7 @@ dependencies = [ "sha2 0.10.6", "stun", "thiserror", - "time 0.3.17", + "time 0.3.21", "tokio", "turn", "url", @@ -12213,22 +13713,22 @@ dependencies = [ [[package]] name = "webrtc-dtls" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7021987ae0a2ed6c8cd33f68e98e49bb6e74ffe9543310267b48a1bbe3900e5f" +checksum = "942be5bd85f072c3128396f6e5a9bfb93ca8c1939ded735d177b7bcba9a13d05" dependencies = [ "aes 0.6.0", - "aes-gcm 0.8.0", + "aes-gcm 0.10.2", "async-trait", "bincode", "block-modes", "byteorder", "ccm", "curve25519-dalek 3.2.0", - "der-parser 8.1.0", - "elliptic-curve", + "der-parser 8.2.0", + "elliptic-curve 0.12.3", "hkdf", - "hmac 0.10.1", + "hmac 0.12.1", "log", "oid-registry 0.6.1", "p256", @@ -12238,11 +13738,11 @@ dependencies = [ "rcgen 0.9.3", "ring", "rustls 0.19.1", - "sec1", + "sec1 0.3.0", "serde", - "sha-1", - "sha2 0.9.9", - "signature", + "sha1", + "sha2 0.10.6", + "signature 1.6.4", "subtle", "thiserror", "tokio", @@ -12254,9 +13754,9 @@ dependencies = [ [[package]] name = "webrtc-ice" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "494483fbb2f5492620871fdc78b084aed8807377f6e3fe88b2e49f0a9c9c41d7" +checksum = "465a03cc11e9a7d7b4f9f99870558fe37a102b65b93f8045392fef7c67b39e80" dependencies = [ "arc-swap", "async-trait", @@ -12291,18 +13791,15 @@ dependencies = [ [[package]] name = "webrtc-media" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2a3c157a040324e5049bcbd644ffc9079e6738fa2cfab2bcff64e5cc4c00d7" +checksum = "f72e1650a8ae006017d1a5280efb49e2610c19ccc3c0905b03b648aee9554991" dependencies = [ "byteorder", "bytes", - "derive_builder", - "displaydoc", "rand 0.8.5", "rtp", "thiserror", - "webrtc-util", ] [[package]] @@ -12368,23 +13865,24 @@ dependencies = [ ] [[package]] -name = "wepoll-ffi" -version = "0.1.2" +name = "which" +version = "4.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +checksum = "2441c784c52b289a054b7201fc93253e288f094e2f4be9058343127c4226a269" dependencies = [ - "cc", + "either", + "libc", + "once_cell", ] [[package]] -name = "which" -version = "4.3.0" +name = "wide" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c831fbbee9e129a8cf93e7747a82da9d95ba8e16621cae60ec2cdc849bacb7b" +checksum = "5cd0496a71f3cc6bc4bf0ed91346426a5099e93d89807e663162dc5a1069ff65" dependencies = [ - "either", - "libc", - "once_cell", + "bytemuck", + "safe_arch", ] [[package]] @@ -12437,39 +13935,74 @@ dependencies = [ "windows_x86_64_msvc 0.34.0", ] +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.0", +] + [[package]] name = "windows-sys" -version = "0.36.1" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", + "windows-targets 0.42.2", ] [[package]] name = "windows-sys" -version = "0.42.0" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.0", +] + +[[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.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc 0.42.0", - "windows_i686_gnu 0.42.0", - "windows_i686_msvc 0.42.0", - "windows_x86_64_gnu 0.42.0", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc 0.42.0", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.42.0" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" @@ -12479,15 +14012,15 @@ checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" [[package]] name = "windows_aarch64_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" [[package]] name = "windows_aarch64_msvc" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" @@ -12497,15 +14030,15 @@ checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" [[package]] name = "windows_i686_gnu" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" [[package]] name = "windows_i686_gnu" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" @@ -12515,15 +14048,15 @@ checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" [[package]] name = "windows_i686_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" [[package]] name = "windows_i686_msvc" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" @@ -12533,21 +14066,27 @@ checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" [[package]] name = "windows_x86_64_gnu" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" [[package]] name = "windows_x86_64_gnu" -version = "0.42.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.42.0" +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.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" @@ -12557,15 +14096,24 @@ checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" [[package]] name = "windows_x86_64_msvc" -version = "0.36.1" +version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" [[package]] name = "windows_x86_64_msvc" -version = "0.42.0" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" + +[[package]] +name = "winnow" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" +checksum = "61de7bac303dc551fe038e2b3cef0f571087a47571ea6e79a87692ac99b99699" +dependencies = [ + "memchr", +] [[package]] name = "winreg" @@ -12614,7 +14162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb9bace5b5589ffead1afb76e43e34cff39cd0f3ce7e170ae0c29e53b88eb1c" dependencies = [ "asn1-rs 0.3.1", - "base64", + "base64 0.13.1", "data-encoding", "der-parser 7.0.0", "lazy_static", @@ -12623,7 +14171,7 @@ dependencies = [ "ring", "rusticata-macros", "thiserror", - "time 0.3.17", + "time 0.3.21", ] [[package]] @@ -12632,16 +14180,25 @@ version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0ecbeb7b67ce215e40e3cc7f2ff902f94a223acf44995934763467e7b1febc8" dependencies = [ - "asn1-rs 0.5.1", - "base64", + "asn1-rs 0.5.2", + "base64 0.13.1", "data-encoding", - "der-parser 8.1.0", + "der-parser 8.2.0", "lazy_static", "nom", "oid-registry 0.6.1", "rusticata-macros", "thiserror", - "time 0.3.17", + "time 0.3.21", +] + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", ] [[package]] @@ -12666,32 +14223,31 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "yasna" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aed2e7a52e3744ab4d0c05c20aa065258e84c49fd4226f5191b2ed29712710b4" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" dependencies = [ - "time 0.3.17", + "time 0.3.21", ] [[package]] name = "zeroize" -version = "1.5.7" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c394b5bd0c6f669e7275d9c20aa90ae064cb22e75a1cad54e1b34088034b149f" +checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.3.3" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" dependencies = [ "proc-macro2", "quote", - "syn", - "synstructure", + "syn 2.0.18", ] [[package]] @@ -12700,7 +14256,16 @@ version = "0.11.2+zstd.1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" dependencies = [ - "zstd-safe", + "zstd-safe 5.0.2+zstd.1.5.2", +] + +[[package]] +name = "zstd" +version = "0.12.3+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76eea132fb024e0e13fd9c2f5d5d595d8a967aa72382ac2f9d39fcc95afd0806" +dependencies = [ + "zstd-safe 6.0.5+zstd.1.5.4", ] [[package]] @@ -12713,12 +14278,23 @@ dependencies = [ "zstd-sys", ] +[[package]] +name = "zstd-safe" +version = "6.0.5+zstd.1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56d9e60b4b1758206c238a10165fbcae3ca37b01744e394c463463f6529d23b" +dependencies = [ + "libc", + "zstd-sys", +] + [[package]] name = "zstd-sys" -version = "2.0.4+zstd.1.5.2" +version = "2.0.8+zstd.1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fa202f2ef00074143e219d15b62ffc317d17cc33909feac471c044087cad7b0" +checksum = "5556e6ee25d32df2586c098bbfa278803692a20d0ab9565e049480d52707ec8c" dependencies = [ "cc", "libc", + "pkg-config", ] diff --git a/Cargo.toml b/Cargo.toml index f7eedadd1e8d..95acf0b9702c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,8 +18,6 @@ members = [ "client/api", "client/authority-discovery", "client/basic-authorship", - "client/beefy", - "client/beefy/rpc", "client/block-builder", "client/chain-spec", "client/chain-spec/derive", @@ -27,26 +25,27 @@ members = [ "client/consensus/aura", "client/consensus/babe", "client/consensus/babe/rpc", + "client/consensus/beefy", + "client/consensus/beefy/rpc", "client/consensus/common", "client/consensus/epochs", + "client/consensus/grandpa", + "client/consensus/grandpa/rpc", "client/consensus/manual-seal", "client/consensus/pow", "client/consensus/slots", - "client/consensus/uncles", "client/db", "client/executor", "client/executor/common", "client/executor/runtime-test", - "client/executor/wasmi", "client/executor/wasmtime", - "client/finality-grandpa", - "client/finality-grandpa/rpc", "client/informant", "client/keystore", "client/merkle-mountain-range", "client/merkle-mountain-range/rpc", "client/network", "client/network/transactions", + "client/network/statement", "client/network-gossip", "client/network/bitswap", "client/network/common", @@ -54,7 +53,6 @@ members = [ "client/network/sync", "client/network/test", "client/offchain", - "client/peerset", "client/allocator", "client/proposer-metrics", "client/rpc", @@ -64,6 +62,7 @@ members = [ "client/service", "client/service/test", "client/state-db", + "client/statement-store", "client/storage-monitor", "client/sysinfo", "client/sync-state-rpc", @@ -74,6 +73,7 @@ members = [ "client/transaction-pool/api", "client/utils", "frame/alliance", + "frame/asset-conversion", "frame/assets", "frame/atomic-swap", "frame/aura", @@ -86,8 +86,8 @@ members = [ "frame/balances", "frame/beefy", "frame/beefy-mmr", - "frame/beefy-mmr/primitives", "frame/benchmarking", + "frame/benchmarking/pov", "frame/bounties", "frame/child-bounties", "frame/collective", @@ -95,17 +95,24 @@ members = [ "frame/contracts/proc-macro", "frame/contracts/primitives", "frame/conviction-voting", + "frame/core-fellowship", "frame/democracy", "frame/fast-unstake", "frame/try-runtime", "frame/elections-phragmen", "frame/election-provider-multi-phase", + "frame/election-provider-multi-phase/test-staking-e2e", "frame/election-provider-support", "frame/election-provider-support/benchmarking", "frame/election-provider-support/solution-type", "frame/election-provider-support/solution-type/fuzzer", + "frame/examples", "frame/examples/basic", "frame/examples/offchain-worker", + "frame/examples/kitchensink", + "frame/examples/dev-mode", + "frame/examples/split", + "frame/examples/default-config", "frame/executive", "frame/nis", "frame/grandpa", @@ -124,16 +131,19 @@ members = [ "frame/proxy", "frame/message-queue", "frame/nfts", + "frame/nfts/runtime-api", + "frame/nft-fractionalization", "frame/nomination-pools", "frame/nomination-pools/fuzzer", "frame/nomination-pools/benchmarking", "frame/nomination-pools/test-staking", "frame/nomination-pools/runtime-api", - "frame/randomness-collective-flip", + "frame/insecure-randomness-collective-flip", "frame/ranked-collective", "frame/recovery", "frame/referenda", "frame/remark", + "frame/salary", "frame/scheduler", "frame/scored-pool", "frame/session", @@ -142,10 +152,12 @@ members = [ "frame/staking", "frame/staking/reward-curve", "frame/staking/reward-fn", + "frame/staking/runtime-api", "frame/state-trie-migration", "frame/sudo", "frame/root-offences", "frame/root-testing", + "frame/statement", "frame/support", "frame/support/procedural", "frame/support/procedural/tools", @@ -158,15 +170,18 @@ members = [ "frame/system/rpc/runtime-api", "frame/timestamp", "frame/transaction-payment", + "frame/transaction-payment/asset-conversion-tx-payment", "frame/transaction-payment/asset-tx-payment", "frame/transaction-payment/rpc", "frame/transaction-payment/rpc/runtime-api", "frame/transaction-storage", "frame/treasury", + "frame/asset-rate", "frame/tips", "frame/uniques", "frame/utility", "frame/vesting", + "frame/glutton", "frame/whitelist", "primitives/api", "primitives/api/proc-macro", @@ -176,29 +191,30 @@ members = [ "primitives/arithmetic", "primitives/arithmetic/fuzzer", "primitives/authority-discovery", - "primitives/authorship", - "primitives/beefy", "primitives/block-builder", "primitives/blockchain", "primitives/consensus/aura", "primitives/consensus/babe", + "primitives/consensus/beefy", "primitives/consensus/common", + "primitives/consensus/grandpa", "primitives/consensus/pow", "primitives/consensus/slots", - "primitives/consensus/vrf", "primitives/core", "primitives/core/hashing", "primitives/core/hashing/proc-macro", + "primitives/crypto/ec-utils", "primitives/database", "primitives/debug-derive", "primitives/externalities", - "primitives/finality-grandpa", + "primitives/genesis-builder", "primitives/inherents", "primitives/io", "primitives/keyring", "primitives/keystore", "primitives/maybe-compressed-blob", "primitives/merkle-mountain-range", + "primitives/metadata-ir", "primitives/npos-elections", "primitives/npos-elections/fuzzer", "primitives/offchain", @@ -210,10 +226,10 @@ members = [ "primitives/runtime-interface/test", "primitives/runtime-interface/test-wasm", "primitives/runtime-interface/test-wasm-deprecated", - "primitives/serializer", "primitives/session", "primitives/staking", "primitives/state-machine", + "primitives/statement-store", "primitives/std", "primitives/storage", "primitives/test-primitives", @@ -226,7 +242,9 @@ members = [ "primitives/version/proc-macro", "primitives/wasm-interface", "primitives/weights", + "scripts/ci/node-template-release", "test-utils", + "test-utils/cli", "test-utils/client", "test-utils/derive", "test-utils/runtime", @@ -247,6 +265,7 @@ members = [ "utils/frame/rpc/client", "utils/prometheus", "utils/wasm-builder", + "utils/binary-merkle-tree", ] # The list of dependencies below (which can be both direct and indirect dependencies) are crates diff --git a/HEADER-APACHE2 b/HEADER-APACHE2 index 403497a86907..ecd364a6d62e 100644 --- a/HEADER-APACHE2 +++ b/HEADER-APACHE2 @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/HEADER-GPL3 b/HEADER-GPL3 index ed7daba72327..b46a8f75295f 100644 --- a/HEADER-GPL3 +++ b/HEADER-GPL3 @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/README.md b/README.md index 7d8c7e575581..361f410ab279 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Substrate is a next-generation framework for blockchain innovation 🚀. Head to [docs.substrate.io](https://docs.substrate.io) and follow the [installation](https://docs.substrate.io/install/) instructions. Then try out one of the [tutorials](https://docs.substrate.io/tutorials/). +Refer to the [Docker instructions](./docker/README.md) to quickly run Substrate, Substrate Node Template, Subkey, or to build a chain spec. ## Community & Support diff --git a/bin/node-template/.envrc b/bin/node-template/.envrc new file mode 100644 index 000000000000..3550a30f2de3 --- /dev/null +++ b/bin/node-template/.envrc @@ -0,0 +1 @@ +use flake diff --git a/bin/node-template/LICENSE b/bin/node-template/LICENSE index cf1ab25da034..ffa0b3f2df03 100644 --- a/bin/node-template/LICENSE +++ b/bin/node-template/LICENSE @@ -1,24 +1,16 @@ -This is free and unencumbered software released into the public domain. +MIT No Attribution -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. +Copyright Parity Technologies (UK) Ltd. -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. +Permission is hereby granted, free of charge, to any person obtaining a copy of this +software and associated documentation files (the "Software"), to deal in the Software +without restriction, including without limitation the rights to use, copy, modify, +merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so. -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 +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 OR COPYRIGHT +HOLDERS 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. \ No newline at end of file diff --git a/bin/node-template/README.md b/bin/node-template/README.md index 0f6fd9450aee..337facaaf089 100644 --- a/bin/node-template/README.md +++ b/bin/node-template/README.md @@ -1,38 +1,22 @@ # Substrate Node Template -[![Try on playground](https://img.shields.io/badge/Playground-Node_Template-brightgreen?logo=Parity%20Substrate)](https://docs.substrate.io/playground/) [![Matrix](https://img.shields.io/matrix/substrate-technical:matrix.org)](https://matrix.to/#/#substrate-technical:matrix.org) +A fresh [Substrate](https://substrate.io/) node, ready for hacking :rocket: -A fresh FRAME-based [Substrate](https://www.substrate.io/) node, ready for hacking :rocket: +A standalone version of this template is available for each release of Polkadot in the [Substrate Developer Hub Parachain Template](https://github.com/substrate-developer-hub/substrate-parachain-template/) repository. +The parachain template is generated directly at each Polkadot release branch from the [Node Template in Substrate](https://github.com/paritytech/substrate/tree/master/bin/node-template) upstream -## Getting Started - -Follow the steps below to get started with the Node Template, or get it up and running right from -your browser in just a few clicks using -the [Substrate Playground](https://docs.substrate.io/playground/) :hammer_and_wrench: - -### Using Nix - -Install [nix](https://nixos.org/) and optionally [direnv](https://github.com/direnv/direnv) and -[lorri](https://github.com/nix-community/lorri) for a fully plug and play experience for setting up -the development environment. To get all the correct dependencies activate direnv `direnv allow` and -lorri `lorri shell`. +It is usually best to use the stand-alone version to start a new project. +All bugs, suggestions, and feature requests should be made upstream in the [Substrate](https://github.com/paritytech/substrate/tree/master/bin/node-template) repository. -### Rust Setup - -First, complete the [basic Rust setup instructions](./docs/rust-setup.md). - -### Run +## Getting Started -Use Rust's native `cargo` command to build and launch the template node: - -```sh -cargo run --release -- --dev -``` +Depending on your operating system and Rust version, there might be additional packages required to compile this template. +Check the [Install](https://docs.substrate.io/install/) instructions for your platform for the most common dependencies. +Alternatively, you can use one of the [alternative installation](#alternatives-installations) options. ### Build -The `cargo run` command will perform an initial build. Use the following command to build the node -without launching it: +Use the following command to build the node without launching it: ```sh cargo build --release @@ -40,54 +24,49 @@ cargo build --release ### Embedded Docs -Once the project has been built, the following command can be used to explore all parameters and -subcommands: +After you build the project, you can use the following command to explore its parameters and subcommands: ```sh ./target/release/node-template -h ``` -## Run +You can generate and view the [Rust Docs](https://doc.rust-lang.org/cargo/commands/cargo-doc.html) for this template with this command: -The provided `cargo run` command will launch a temporary node and its state will be discarded after -you terminate the process. After the project has been built, there are other ways to launch the -node. +```sh +cargo +nightly doc --open +``` ### Single-Node Development Chain -This command will start the single-node development chain with non-persistent state: +The following command starts a single-node development chain that doesn't persist state: -```bash +```sh ./target/release/node-template --dev ``` -Purge the development chain's state: +To purge the development chain's state, run the following command: -```bash +```sh ./target/release/node-template purge-chain --dev ``` -Start the development chain with detailed logging: +To start the development chain with detailed logging, run the following command: -```bash +```sh RUST_BACKTRACE=1 ./target/release/node-template -ldebug --dev ``` -> Development chain means that the state of our chain will be in a tmp folder while the nodes are -> running. Also, **alice** account will be authority and sudo account as declared in the -> [genesis state](https://github.com/substrate-developer-hub/substrate-node-template/blob/main/node/src/chain_spec.rs#L49). -> At the same time the following accounts will be pre-funded: -> - Alice -> - Bob -> - Alice//stash -> - Bob//stash - -In case of being interested in maintaining the chain' state between runs a base path must be added -so the db can be stored in the provided folder instead of a temporal one. We could use this folder -to store different chain databases, as a different folder will be created per different chain that -is ran. The following commands shows how to use a newly created folder as our db base path. - -```bash +Development chains: + +- Maintain state in a `tmp` folder while the node is running. +- Use the **Alice** and **Bob** accounts as default validator authorities. +- Use the **Alice** account as the default `sudo` account. +- Are preconfigured with a genesis state (`/node/src/chain_spec.rs`) that includes several prefunded development accounts. + + +To persist chain state between runs, specify a base path by running a command similar to the following: + +```sh // Create a folder to use as the db base path $ mkdir my-chain-state @@ -103,23 +82,19 @@ $ ls ./my-chain-state/chains/dev db keystore network ``` +### Connect with Polkadot-JS Apps Front-End -### Connect with Polkadot-JS Apps Front-end - -Once the node template is running locally, you can connect it with **Polkadot-JS Apps** front-end -to interact with your chain. [Click -here](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) connecting the Apps to your -local node template. +After you start the node template locally, you can interact with it using the hosted version of the [Polkadot/Substrate Portal](https://polkadot.js.org/apps/#/explorer?rpc=ws://localhost:9944) front-end by connecting to the local node endpoint. +A hosted version is also available on [IPFS (redirect) here](https://dotapps.io/) or [IPNS (direct) here](ipns://dotapps.io/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/explorer). +You can also find the source code and instructions for hosting your own instance on the [polkadot-js/apps](https://github.com/polkadot-js/apps) repository. ### Multi-Node Local Testnet -If you want to see the multi-node consensus algorithm in action, refer to our -[Simulate a network tutorial](https://docs.substrate.io/tutorials/get-started/simulate-network/). +If you want to see the multi-node consensus algorithm in action, see [Simulate a network](https://docs.substrate.io/tutorials/build-a-blockchain/simulate-network/). ## Template Structure -A Substrate project such as this consists of a number of components that are spread across a few -directories. +A Substrate project such as this consists of a number of components that are spread across a few directories. ### Node @@ -128,104 +103,62 @@ Substrate-based blockchain nodes expose a number of capabilities: - Networking: Substrate nodes use the [`libp2p`](https://libp2p.io/) networking stack to allow the nodes in the network to communicate with one another. -- Consensus: Blockchains must have a way to come to - [consensus](https://docs.substrate.io/main-docs/fundamentals/consensus/) on the state of the - network. Substrate makes it possible to supply custom consensus engines and also ships with - several consensus mechanisms that have been built on top of - [Web3 Foundation research](https://research.web3.foundation/en/latest/polkadot/NPoS/index.html). +- Consensus: Blockchains must have a way to come to [consensus](https://docs.substrate.io/fundamentals/consensus/) on the state of the network. + Substrate makes it possible to supply custom consensus engines and also ships with several consensus mechanisms that have been built on top of [Web3 Foundation research](https://research.web3.foundation/en/latest/polkadot/NPoS/index.html). - RPC Server: A remote procedure call (RPC) server is used to interact with Substrate nodes. -There are several files in the `node` directory - take special note of the following: - -- [`chain_spec.rs`](./node/src/chain_spec.rs): A - [chain specification](https://docs.substrate.io/main-docs/build/chain-spec/) is a - source code file that defines a Substrate chain's initial (genesis) state. Chain specifications - are useful for development and testing, and critical when architecting the launch of a - production chain. Take note of the `development_config` and `testnet_genesis` functions, which - are used to define the genesis state for the local development chain configuration. These - functions identify some - [well-known accounts](https://docs.substrate.io/reference/command-line-tools/subkey/) - and use them to configure the blockchain's initial state. -- [`service.rs`](./node/src/service.rs): This file defines the node implementation. Take note of - the libraries that this file imports and the names of the functions it invokes. In particular, - there are references to consensus-related topics, such as the - [block finalization and forks](https://docs.substrate.io/main-docs/fundamentals/consensus/#finalization-and-forks) - and other [consensus mechanisms](https://docs.substrate.io/main-docs/fundamentals/consensus/#default-consensus-models) - such as Aura for block authoring and GRANDPA for finality. - -After the node has been [built](#build), refer to the embedded documentation to learn more about the -capabilities and configuration parameters that it exposes: - -```shell -./target/release/node-template --help -``` +There are several files in the `node` directory. +Take special note of the following: + +- [`chain_spec.rs`](./node/src/chain_spec.rs): A [chain specification](https://docs.substrate.io/build/chain-spec/) is a source code file that defines a Substrate chain's initial (genesis) state. + Chain specifications are useful for development and testing, and critical when architecting the launch of a production chain. + Take note of the `development_config` and `testnet_genesis` functions,. + These functions are used to define the genesis state for the local development chain configuration. + These functions identify some [well-known accounts](https://docs.substrate.io/reference/command-line-tools/subkey/) and use them to configure the blockchain's initial state. +- [`service.rs`](./node/src/service.rs): This file defines the node implementation. + Take note of the libraries that this file imports and the names of the functions it invokes. + In particular, there are references to consensus-related topics, such as the [block finalization and forks](https://docs.substrate.io/fundamentals/consensus/#finalization-and-forks) and other [consensus mechanisms](https://docs.substrate.io/fundamentals/consensus/#default-consensus-models) such as Aura for block authoring and GRANDPA for finality. + + ### Runtime -In Substrate, the terms -"runtime" and "state transition function" -are analogous - they refer to the core logic of the blockchain that is responsible for validating -blocks and executing the state changes they define. The Substrate project in this repository uses -[FRAME](https://docs.substrate.io/main-docs/fundamentals/runtime-intro/#frame) to construct a -blockchain runtime. FRAME allows runtime developers to declare domain-specific logic in modules -called "pallets". At the heart of FRAME is a helpful -[macro language](https://docs.substrate.io/reference/frame-macros/) that makes it easy to -create pallets and flexibly compose them to create blockchains that can address -[a variety of needs](https://substrate.io/ecosystem/projects/). - -Review the [FRAME runtime implementation](./runtime/src/lib.rs) included in this template and note -the following: - -- This file configures several pallets to include in the runtime. Each pallet configuration is - defined by a code block that begins with `impl $PALLET_NAME::Config for Runtime`. -- The pallets are composed into a single runtime by way of the - [`construct_runtime!`](https://crates.parity.io/frame_support/macro.construct_runtime.html) - macro, which is part of the core - FRAME Support [system](https://docs.substrate.io/reference/frame-pallets/#system-pallets) library. +In Substrate, the terms "runtime" and "state transition function" are analogous. +Both terms refer to the core logic of the blockchain that is responsible for validating blocks and executing the state changes they define. +The Substrate project in this repository uses [FRAME](https://docs.substrate.io/learn/runtime-development/#frame) to construct a blockchain runtime. +FRAME allows runtime developers to declare domain-specific logic in modules called "pallets". +At the heart of FRAME is a helpful [macro language](https://docs.substrate.io/reference/frame-macros/) that makes it easy to create pallets and flexibly compose them to create blockchains that can address [a variety of needs](https://substrate.io/ecosystem/projects/). + +Review the [FRAME runtime implementation](./runtime/src/lib.rs) included in this template and note the following: + +- This file configures several pallets to include in the runtime. + Each pallet configuration is defined by a code block that begins with `impl $PALLET_NAME::Config for Runtime`. +- The pallets are composed into a single runtime by way of the [`construct_runtime!`](https://paritytech.github.io/substrate/master/frame_support/macro.construct_runtime.html) macro, which is part of the [core FRAME pallet library](https://docs.substrate.io/reference/frame-pallets/#system-pallets). ### Pallets -The runtime in this project is constructed using many FRAME pallets that ship with the -[core Substrate repository](https://github.com/paritytech/substrate/tree/master/frame) and a -template pallet that is [defined in the `pallets`](./pallets/template/src/lib.rs) directory. +The runtime in this project is constructed using many FRAME pallets that ship with [the Substrate repository](https://github.com/paritytech/substrate/tree/master/frame) and a template pallet that is [defined in the `pallets`](./pallets/template/src/lib.rs) directory. -A FRAME pallet is compromised of a number of blockchain primitives: +A FRAME pallet is comprised of a number of blockchain primitives, including: -- Storage: FRAME defines a rich set of powerful - [storage abstractions](https://docs.substrate.io/main-docs/build/runtime-storage/) that makes - it easy to use Substrate's efficient key-value database to manage the evolving state of a - blockchain. -- Dispatchables: FRAME pallets define special types of functions that can be invoked (dispatched) - from outside of the runtime in order to update its state. -- Events: Substrate uses [events and errors](https://docs.substrate.io/main-docs/build/events-errors/) - to notify users of important changes in the runtime. +- Storage: FRAME defines a rich set of powerful [storage abstractions](https://docs.substrate.io/build/runtime-storage/) that makes it easy to use Substrate's efficient key-value database to manage the evolving state of a blockchain. +- Dispatchables: FRAME pallets define special types of functions that can be invoked (dispatched) from outside of the runtime in order to update its state. +- Events: Substrate uses [events](https://docs.substrate.io/build/events-and-errors/) to notify users of significant state changes. - Errors: When a dispatchable fails, it returns an error. -- Config: The `Config` configuration interface is used to define the types and parameters upon - which a FRAME pallet depends. -### Run in Docker +Each pallet has its own `Config` trait which serves as a configuration interface to generically define the types and parameters it depends on. -First, install [Docker](https://docs.docker.com/get-docker/) and -[Docker Compose](https://docs.docker.com/compose/install/). +## Alternatives Installations -Then run the following command to start a single node development chain. +Instead of installing dependencies and building this source directly, consider the following alternatives. -```bash -./scripts/docker_run.sh -``` - -This command will firstly compile your code, and then start a local development network. You can -also replace the default command -(`cargo build --release && ./target/release/node-template --dev --ws-external`) -by appending your own. A few useful ones are as follow. +### Nix -```bash -# Run Substrate node without re-compiling -./scripts/docker_run.sh ./target/release/node-template --dev --ws-external +Install [nix](https://nixos.org/) and +[nix-direnv](https://github.com/nix-community/nix-direnv) for a fully plug-and-play +experience for setting up the development environment. +To get all the correct dependencies, activate direnv `direnv allow`. -# Purge the local dev chain -./scripts/docker_run.sh ./target/release/node-template purge-chain --dev +### Docker -# Check whether the code is compilable -./scripts/docker_run.sh cargo check -``` +Please follow the [Substrate Docker instructions here](https://github.com/paritytech/substrate/blob/master/docker/README.md) to build the Docker container with the Substrate Node Template binary. diff --git a/bin/node-template/docker-compose.yml b/bin/node-template/docker-compose.yml deleted file mode 100644 index bc1922f47d96..000000000000 --- a/bin/node-template/docker-compose.yml +++ /dev/null @@ -1,17 +0,0 @@ -version: "3.2" - -services: - dev: - container_name: node-template - image: paritytech/ci-linux:production - working_dir: /var/www/node-template - ports: - - "9944:9944" - environment: - - CARGO_HOME=/var/www/node-template/.cargo - volumes: - - .:/var/www/node-template - - type: bind - source: ./.local - target: /root/.local - command: bash -c "cargo build --release && ./target/release/node-template --dev --ws-external" diff --git a/bin/node-template/flake.lock b/bin/node-template/flake.lock new file mode 100644 index 000000000000..60819f675d21 --- /dev/null +++ b/bin/node-template/flake.lock @@ -0,0 +1,43 @@ +{ + "nodes": { + "flake-utils": { + "locked": { + "lastModified": 1678901627, + "narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1679262748, + "narHash": "sha256-DQCrrAFrkxijC6haUzOC5ZoFqpcv/tg2WxnyW3np1Cc=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "60c1d71f2ba4c80178ec84523c2ca0801522e0a6", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/bin/node-template/flake.nix b/bin/node-template/flake.nix new file mode 100644 index 000000000000..428efd09484d --- /dev/null +++ b/bin/node-template/flake.nix @@ -0,0 +1,22 @@ +{ + inputs = { + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + flake-utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, flake-utils, ... }: flake-utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + in + { + devShells.default = pkgs.mkShell { + packages = with pkgs; [ + rustup + clang + protobuf + ]; + + LIBCLANG_PATH = "${pkgs.libclang.lib}/lib"; + }; + }); +} diff --git a/bin/node-template/node/Cargo.toml b/bin/node-template/node/Cargo.toml index 364cfa25d3c6..ea7989be454f 100644 --- a/bin/node-template/node/Cargo.toml +++ b/bin/node-template/node/Cargo.toml @@ -5,7 +5,7 @@ description = "A fresh FRAME-based Substrate node, ready for hacking." authors = ["Substrate DevHub "] homepage = "https://substrate.io/" edition = "2021" -license = "Unlicense" +license = "MIT-0" publish = false repository = "https://github.com/substrate-developer-hub/substrate-node-template/" build = "build.rs" @@ -17,35 +17,34 @@ targets = ["x86_64-unknown-linux-gnu"] name = "node-template" [dependencies] -clap = { version = "4.0.9", features = ["derive"] } +clap = { version = "4.2.5", features = ["derive"] } futures = { version = "0.3.21", features = ["thread-pool"]} sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } +sc-network = { version = "0.10.0-dev", path = "../../../client/network" } sc-service = { version = "0.10.0-dev", path = "../../../client/service" } sc-telemetry = { version = "4.0.0-dev", path = "../../../client/telemetry" } -sc-keystore = { version = "4.0.0-dev", path = "../../../client/keystore" } sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } +sc-offchain = { version = "4.0.0-dev", path = "../../../client/offchain" } sc-consensus-aura = { version = "0.10.0-dev", path = "../../../client/consensus/aura" } sp-consensus-aura = { version = "0.10.0-dev", path = "../../../primitives/consensus/aura" } -sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } -sc-finality-grandpa = { version = "0.10.0-dev", path = "../../../client/finality-grandpa" } -sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } +sc-consensus-grandpa = { version = "0.10.0-dev", path = "../../../client/consensus/grandpa" } +sp-consensus-grandpa = { version = "4.0.0-dev", path = "../../../primitives/consensus/grandpa" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } -sp-io = { version = "7.0.0", path = "../../../primitives/io" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } +sp-io = { version = "23.0.0", path = "../../../primitives/io" } sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } +sp-keyring = { version = "24.0.0", path = "../../../primitives/keyring" } frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment" } # These dependencies are used for the node template's RPCs jsonrpsee = { version = "0.16.2", features = ["server"] } -sc-rpc = { version = "4.0.0-dev", path = "../../../client/rpc" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../client/rpc-api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } diff --git a/bin/node-template/node/src/benchmarking.rs b/bin/node-template/node/src/benchmarking.rs index 37e0e465969d..6e29ad1a1231 100644 --- a/bin/node-template/node/src/benchmarking.rs +++ b/bin/node-template/node/src/benchmarking.rs @@ -82,11 +82,8 @@ impl frame_benchmarking_cli::ExtrinsicBuilder for TransferKeepAliveBuilder { let extrinsic: OpaqueExtrinsic = create_benchmark_extrinsic( self.client.as_ref(), acc, - BalancesCall::transfer_keep_alive { - dest: self.dest.clone().into(), - value: self.value.into(), - } - .into(), + BalancesCall::transfer_keep_alive { dest: self.dest.clone().into(), value: self.value } + .into(), nonce, ) .into(); @@ -143,10 +140,10 @@ pub fn create_benchmark_extrinsic( let signature = raw_payload.using_encoded(|e| sender.sign(e)); runtime::UncheckedExtrinsic::new_signed( - call.clone(), + call, sp_runtime::AccountId32::from(sender.public()).into(), - runtime::Signature::Sr25519(signature.clone()), - extra.clone(), + runtime::Signature::Sr25519(signature), + extra, ) } diff --git a/bin/node-template/node/src/chain_spec.rs b/bin/node-template/node/src/chain_spec.rs index ef34ec369a77..2cd2d0729302 100644 --- a/bin/node-template/node/src/chain_spec.rs +++ b/bin/node-template/node/src/chain_spec.rs @@ -1,18 +1,18 @@ use node_template_runtime::{ - AccountId, AuraConfig, BalancesConfig, GenesisConfig, GrandpaConfig, Signature, SudoConfig, - SystemConfig, WASM_BINARY, + AccountId, AuraConfig, BalancesConfig, GrandpaConfig, RuntimeGenesisConfig, Signature, + SudoConfig, SystemConfig, WASM_BINARY, }; use sc_service::ChainType; use sp_consensus_aura::sr25519::AuthorityId as AuraId; +use sp_consensus_grandpa::AuthorityId as GrandpaId; use sp_core::{sr25519, Pair, Public}; -use sp_finality_grandpa::AuthorityId as GrandpaId; use sp_runtime::traits::{IdentifyAccount, Verify}; // 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; +pub type ChainSpec = sc_service::GenericChainSpec; /// Generate a crypto pair from seed. pub fn get_from_seed(seed: &str) -> ::Public { @@ -131,11 +131,12 @@ fn testnet_genesis( root_key: AccountId, endowed_accounts: Vec, _enable_println: bool, -) -> GenesisConfig { - GenesisConfig { +) -> RuntimeGenesisConfig { + RuntimeGenesisConfig { system: SystemConfig { // Add Wasm runtime to storage. code: wasm_binary.to_vec(), + ..Default::default() }, balances: BalancesConfig { // Configure endowed accounts with initial balance of 1 << 60. @@ -146,6 +147,7 @@ fn testnet_genesis( }, grandpa: GrandpaConfig { authorities: initial_authorities.iter().map(|x| (x.1.clone(), 1)).collect(), + ..Default::default() }, sudo: SudoConfig { // Assign network admin rights. diff --git a/bin/node-template/node/src/cli.rs b/bin/node-template/node/src/cli.rs index dd610477ac46..15ceaa06230d 100644 --- a/bin/node-template/node/src/cli.rs +++ b/bin/node-template/node/src/cli.rs @@ -10,6 +10,7 @@ pub struct Cli { } #[derive(Debug, clap::Subcommand)] +#[allow(clippy::large_enum_variant)] pub enum Subcommand { /// Key management cli utilities #[command(subcommand)] diff --git a/bin/node-template/node/src/command.rs b/bin/node-template/node/src/command.rs index 15cd69b34b5b..8fc697b5f9c1 100644 --- a/bin/node-template/node/src/command.rs +++ b/bin/node-template/node/src/command.rs @@ -6,10 +6,13 @@ use crate::{ }; use frame_benchmarking_cli::{BenchmarkCmd, ExtrinsicFactory, SUBSTRATE_REFERENCE_HARDWARE}; use node_template_runtime::{Block, EXISTENTIAL_DEPOSIT}; -use sc_cli::{ChainSpec, RuntimeVersion, SubstrateCli}; +use sc_cli::SubstrateCli; use sc_service::PartialComponents; use sp_keyring::Sr25519Keyring; +#[cfg(feature = "try-runtime")] +use try_runtime_cli::block_building_info::timestamp_with_aura_info; + impl SubstrateCli for Cli { fn impl_name() -> String { "Substrate Node".into() @@ -43,10 +46,6 @@ impl SubstrateCli for Cli { Box::new(chain_spec::ChainSpec::from_json_file(std::path::PathBuf::from(path))?), }) } - - fn native_runtime_version(_: &Box) -> &'static RuntimeVersion { - &node_template_runtime::VERSION - } } /// Parse and run command line arguments @@ -99,7 +98,7 @@ pub fn run() -> sc_cli::Result<()> { let PartialComponents { client, task_manager, backend, .. } = service::new_partial(&config)?; let aux_revert = Box::new(|client, _, blocks| { - sc_finality_grandpa::revert(client, blocks)?; + sc_consensus_grandpa::revert(client, blocks)?; Ok(()) }); Ok((cmd.run(client, backend, Some(aux_revert)), task_manager)) @@ -121,7 +120,7 @@ pub fn run() -> sc_cli::Result<()> { ) } - cmd.run::(config) + cmd.run::(config) }, BenchmarkCmd::Block(cmd) => { let PartialComponents { client, .. } = service::new_partial(&config)?; @@ -184,11 +183,13 @@ pub fn run() -> sc_cli::Result<()> { let task_manager = sc_service::TaskManager::new(config.tokio_handle.clone(), registry) .map_err(|e| sc_cli::Error::Service(sc_service::Error::Prometheus(e)))?; + let info_provider = timestamp_with_aura_info(6000); + Ok(( cmd.run::::ExtendHostFunctions, - >>(), + >, _>(Some(info_provider)), task_manager, )) }) diff --git a/bin/node-template/node/src/rpc.rs b/bin/node-template/node/src/rpc.rs index 981f375d0b46..f4f1540f732f 100644 --- a/bin/node-template/node/src/rpc.rs +++ b/bin/node-template/node/src/rpc.rs @@ -8,7 +8,7 @@ use std::sync::Arc; use jsonrpsee::RpcModule; -use node_template_runtime::{opaque::Block, AccountId, Balance, Index}; +use node_template_runtime::{opaque::Block, AccountId, Balance, Nonce}; use sc_transaction_pool_api::TransactionPool; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; @@ -34,7 +34,7 @@ where C: ProvideRuntimeApi, C: HeaderBackend + HeaderMetadata + 'static, C: Send + Sync + 'static, - C::Api: substrate_frame_rpc_system::AccountNonceApi, + C::Api: substrate_frame_rpc_system::AccountNonceApi, C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, C::Api: BlockBuilder, P: TransactionPool + 'static, @@ -45,7 +45,7 @@ where let mut module = RpcModule::new(()); let FullDeps { client, pool, deny_unsafe } = deps; - module.merge(System::new(client.clone(), pool.clone(), deny_unsafe).into_rpc())?; + module.merge(System::new(client.clone(), pool, deny_unsafe).into_rpc())?; module.merge(TransactionPayment::new(client).into_rpc())?; // Extend this RPC with a custom API by using the following syntax. diff --git a/bin/node-template/node/src/service.rs b/bin/node-template/node/src/service.rs index ee8464688c79..36be0b1a938b 100644 --- a/bin/node-template/node/src/service.rs +++ b/bin/node-template/node/src/service.rs @@ -1,13 +1,14 @@ //! Service and ServiceFactory implementation. Specialized wrapper over substrate service. +use futures::FutureExt; use node_template_runtime::{self, opaque::Block, RuntimeApi}; -use sc_client_api::BlockBackend; +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_finality_grandpa::SharedVoterState; -use sc_keystore::LocalKeystore; -use sc_service::{error::Error as ServiceError, Configuration, TaskManager}; +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}; @@ -36,6 +37,11 @@ pub(crate) type FullClient = 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< @@ -46,22 +52,18 @@ pub fn new_partial( sc_consensus::DefaultImportQueue, sc_transaction_pool::FullPool, ( - sc_finality_grandpa::GrandpaBlockImport< + sc_consensus_grandpa::GrandpaBlockImport< FullBackend, Block, FullClient, FullSelectChain, >, - sc_finality_grandpa::LinkHalf, + sc_consensus_grandpa::LinkHalf, Option, ), >, ServiceError, > { - if config.keystore_remote.is_some() { - return Err(ServiceError::Other("Remote Keystores are not supported.".into())) - } - let telemetry = config .telemetry_endpoints .clone() @@ -73,13 +75,7 @@ pub fn new_partial( }) .transpose()?; - let executor = NativeElseWasmExecutor::::new( - config.wasm_method, - config.default_heap_pages, - config.max_runtime_instances, - config.runtime_cache_size, - ); - + let executor = sc_service::new_native_or_wasm_executor(config); let (client, backend, keystore_container, task_manager) = sc_service::new_full_parts::( config, @@ -103,9 +99,10 @@ pub fn new_partial( client.clone(), ); - let (grandpa_block_import, grandpa_link) = sc_finality_grandpa::block_import( + let (grandpa_block_import, grandpa_link) = sc_consensus_grandpa::block_import( client.clone(), - &(client.clone() as Arc<_>), + GRANDPA_JUSTIFICATION_PERIOD, + &client, select_chain.clone(), telemetry.as_ref().map(|x| x.handle()), )?; @@ -147,68 +144,65 @@ pub fn new_partial( }) } -fn remote_keystore(_url: &String) -> Result, &'static str> { - // FIXME: here would the concrete keystore be built, - // must return a concrete type (NOT `LocalKeystore`) that - // implements `CryptoStore` and `SyncCryptoStore` - Err("Remote Keystore not supported.") -} - /// Builds a new service for a full client. -pub fn new_full(mut config: Configuration) -> Result { +pub fn new_full(config: Configuration) -> Result { let sc_service::PartialComponents { client, backend, mut task_manager, import_queue, - mut keystore_container, + keystore_container, select_chain, transaction_pool, other: (block_import, grandpa_link, mut telemetry), } = new_partial(&config)?; - if let Some(url) = &config.keystore_remote { - match remote_keystore(url) { - Ok(k) => keystore_container.set_remote_keystore(k), - Err(e) => - return Err(ServiceError::Other(format!( - "Error hooking up remote keystore for {}: {}", - url, e - ))), - }; - } - let grandpa_protocol_name = sc_finality_grandpa::protocol_standard_name( + 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, ); + net_config.add_notification_protocol(sc_consensus_grandpa::grandpa_peers_set_config( + grandpa_protocol_name.clone(), + )); - config - .network - .extra_sets - .push(sc_finality_grandpa::grandpa_peers_set_config(grandpa_protocol_name.clone())); - let warp_sync = Arc::new(sc_finality_grandpa::warp_proof::NetworkProvider::new( + 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) = + 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: Some(warp_sync), + warp_sync_params: Some(WarpSyncParams::WithProvider(warp_sync)), })?; if config.offchain_worker.enabled { - sc_service::build_offchain_workers( - &config, - task_manager.spawn_handle(), - client.clone(), - network.clone(), + 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(), ); } @@ -233,13 +227,14 @@ pub fn new_full(mut config: Configuration) -> Result let _rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { network: network.clone(), client: client.clone(), - keystore: keystore_container.sync_keystore(), + 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(), })?; @@ -248,7 +243,7 @@ pub fn new_full(mut config: Configuration) -> Result let proposer_factory = sc_basic_authorship::ProposerFactory::new( task_manager.spawn_handle(), client.clone(), - transaction_pool, + transaction_pool.clone(), prometheus_registry.as_ref(), telemetry.as_ref().map(|x| x.handle()), ); @@ -275,9 +270,9 @@ pub fn new_full(mut config: Configuration) -> Result }, force_authoring, backoff_authoring_blocks, - keystore: keystore_container.sync_keystore(), - sync_oracle: network.clone(), - justification_sync_link: network.clone(), + 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()), @@ -295,13 +290,12 @@ pub fn new_full(mut config: Configuration) -> Result 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.sync_keystore()) } else { None }; + let keystore = if role.is_authority() { Some(keystore_container.keystore()) } else { None }; - let grandpa_config = sc_finality_grandpa::Config { + let grandpa_config = sc_consensus_grandpa::Config { // FIXME #1578 make this available through chainspec gossip_duration: Duration::from_millis(333), - justification_period: 512, + justification_generation_period: GRANDPA_JUSTIFICATION_PERIOD, name: Some(name), observer_enabled: false, keystore, @@ -316,14 +310,16 @@ pub fn new_full(mut config: Configuration) -> Result // 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_finality_grandpa::GrandpaParams { + let grandpa_config = sc_consensus_grandpa::GrandpaParams { config: grandpa_config, link: grandpa_link, network, - voting_rule: sc_finality_grandpa::VotingRulesBuilder::default().build(), + sync: Arc::new(sync_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. @@ -331,7 +327,7 @@ pub fn new_full(mut config: Configuration) -> Result task_manager.spawn_essential_handle().spawn_blocking( "grandpa-voter", None, - sc_finality_grandpa::run_grandpa_voter(grandpa_config)?, + sc_consensus_grandpa::run_grandpa_voter(grandpa_config)?, ); } diff --git a/bin/node-template/pallets/template/Cargo.toml b/bin/node-template/pallets/template/Cargo.toml index 6dec5c8da6e5..ac933eb6e551 100644 --- a/bin/node-template/pallets/template/Cargo.toml +++ b/bin/node-template/pallets/template/Cargo.toml @@ -5,7 +5,7 @@ description = "FRAME pallet template for defining custom runtime logic." authors = ["Substrate DevHub "] homepage = "https://substrate.io" edition = "2021" -license = "Unlicense" +license = "MIT-0" publish = false repository = "https://github.com/substrate-developer-hub/substrate-node-template/" @@ -13,18 +13,18 @@ repository = "https://github.com/substrate-developer-hub/substrate-node-template targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../../../../frame/benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../../../frame/support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../../frame/system" } [dev-dependencies] -sp-core = { version = "7.0.0", default-features = false, path = "../../../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../../primitives/runtime" } +sp-core = { version = "21.0.0", path = "../../../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../../../primitives/io" } +sp-runtime = { version = "24.0.0", path = "../../../../primitives/runtime" } [features] default = ["std"] diff --git a/bin/node-template/pallets/template/README.md b/bin/node-template/pallets/template/README.md index 8d751a42207d..d0d59537c12d 100644 --- a/bin/node-template/pallets/template/README.md +++ b/bin/node-template/pallets/template/README.md @@ -1 +1 @@ -License: Unlicense \ No newline at end of file +License: MIT-0 \ No newline at end of file diff --git a/bin/node-template/pallets/template/src/benchmarking.rs b/bin/node-template/pallets/template/src/benchmarking.rs index d496a9fc89b1..5a262417629c 100644 --- a/bin/node-template/pallets/template/src/benchmarking.rs +++ b/bin/node-template/pallets/template/src/benchmarking.rs @@ -1,19 +1,34 @@ //! Benchmarking setup for pallet-template - +#![cfg(feature = "runtime-benchmarks")] use super::*; #[allow(unused)] use crate::Pallet as Template; -use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_benchmarking::v2::*; use frame_system::RawOrigin; -benchmarks! { - do_something { - let s in 0 .. 100; +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn do_something() { + let value = 100u32.into(); let caller: T::AccountId = whitelisted_caller(); - }: _(RawOrigin::Signed(caller), s) - verify { - assert_eq!(Something::::get(), Some(s)); + #[extrinsic_call] + do_something(RawOrigin::Signed(caller), value); + + assert_eq!(Something::::get(), Some(value)); + } + + #[benchmark] + fn cause_error() { + Something::::put(100u32); + let caller: T::AccountId = whitelisted_caller(); + #[extrinsic_call] + cause_error(RawOrigin::Signed(caller)); + + assert_eq!(Something::::get(), Some(101u32)); } impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test); diff --git a/bin/node-template/pallets/template/src/lib.rs b/bin/node-template/pallets/template/src/lib.rs index 4630e344add3..9550d3d546cc 100644 --- a/bin/node-template/pallets/template/src/lib.rs +++ b/bin/node-template/pallets/template/src/lib.rs @@ -13,14 +13,16 @@ mod tests; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; +pub mod weights; +pub use weights::*; #[frame_support::pallet] pub mod pallet { + use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); /// Configure the pallet by specifying the parameters and types on which it depends. @@ -28,6 +30,8 @@ pub mod pallet { 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>; + /// Type representing the weight of this pallet + type WeightInfo: WeightInfo; } // The pallet's runtime storage items. @@ -65,7 +69,7 @@ pub mod pallet { /// An example dispatchable that takes a singles value as a parameter, writes the value to /// storage and emits an event. This function must be dispatched by a signed extrinsic. #[pallet::call_index(0)] - #[pallet::weight(10_000 + T::DbWeight::get().writes(1).ref_time())] + #[pallet::weight(T::WeightInfo::do_something())] pub fn do_something(origin: OriginFor, something: u32) -> DispatchResult { // Check that the extrinsic was signed and get the signer. // This function will return an error if the extrinsic is not signed. @@ -83,7 +87,7 @@ pub mod pallet { /// An example dispatchable that may throw a custom error. #[pallet::call_index(1)] - #[pallet::weight(10_000 + T::DbWeight::get().reads_writes(1,1).ref_time())] + #[pallet::weight(T::WeightInfo::cause_error())] pub fn cause_error(origin: OriginFor) -> DispatchResult { let _who = ensure_signed(origin)?; diff --git a/bin/node-template/pallets/template/src/mock.rs b/bin/node-template/pallets/template/src/mock.rs index 989681fa59a0..244ae1b37859 100644 --- a/bin/node-template/pallets/template/src/mock.rs +++ b/bin/node-template/pallets/template/src/mock.rs @@ -1,41 +1,35 @@ use crate as pallet_template; use frame_support::traits::{ConstU16, ConstU64}; -use frame_system as system; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { System: frame_system, TemplateModule: pallet_template, } ); -impl system::Config for Test { +impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -51,9 +45,10 @@ impl system::Config for Test { impl pallet_template::Config for Test { type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); } // Build genesis storage according to the mock runtime. pub fn new_test_ext() -> sp_io::TestExternalities { - system::GenesisConfig::default().build_storage::().unwrap().into() + frame_system::GenesisConfig::::default().build_storage().unwrap().into() } diff --git a/bin/node-template/pallets/template/src/weights.rs b/bin/node-template/pallets/template/src/weights.rs new file mode 100644 index 000000000000..7c42936e09f2 --- /dev/null +++ b/bin/node-template/pallets/template/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/bin/node-template/runtime/Cargo.toml b/bin/node-template/runtime/Cargo.toml index 5560eb672137..52b40989b2f9 100644 --- a/bin/node-template/runtime/Cargo.toml +++ b/bin/node-template/runtime/Cargo.toml @@ -5,7 +5,7 @@ description = "A fresh FRAME-based Substrate node, ready for hacking." authors = ["Substrate DevHub "] homepage = "https://substrate.io/" edition = "2021" -license = "Unlicense" +license = "MIT-0" publish = false repository = "https://github.com/substrate-developer-hub/substrate-node-template/" @@ -13,14 +13,13 @@ repository = "https://github.com/substrate-developer-hub/substrate-node-template targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } pallet-aura = { version = "4.0.0-dev", default-features = false, path = "../../../frame/aura" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../../frame/balances" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../../frame/support" } pallet-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../../frame/grandpa" } -pallet-randomness-collective-flip = { version = "4.0.0-dev", default-features = false, path = "../../../frame/randomness-collective-flip" } pallet-sudo = { version = "4.0.0-dev", default-features = false, path = "../../../frame/sudo" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../frame/system" } frame-try-runtime = { version = "0.10.0-dev", default-features = false, path = "../../../frame/try-runtime", optional = true } @@ -30,14 +29,15 @@ frame-executive = { version = "4.0.0-dev", default-features = false, path = "../ sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } sp-block-builder = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/block-builder"} sp-consensus-aura = { version = "0.10.0-dev", default-features = false, path = "../../../primitives/consensus/aura" } -sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } +sp-consensus-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/consensus/grandpa" } +sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/inherents"} sp-offchain = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/offchain" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } sp-session = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/session" } -sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } sp-transaction-pool = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/transaction-pool" } -sp-version = { version = "5.0.0", default-features = false, path = "../../../primitives/version" } +sp-version = { version = "22.0.0", default-features = false, path = "../../../primitives/version" } # Used for the node template's RPCs frame-system-rpc-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../../frame/system/rpc/runtime-api/" } @@ -69,7 +69,6 @@ std = [ "pallet-aura/std", "pallet-balances/std", "pallet-grandpa/std", - "pallet-randomness-collective-flip/std", "pallet-sudo/std", "pallet-template/std", "pallet-timestamp/std", @@ -78,6 +77,7 @@ std = [ "sp-api/std", "sp-block-builder/std", "sp-consensus-aura/std", + "sp-consensus-grandpa/std", "sp-core/std", "sp-inherents/std", "sp-offchain/std", @@ -95,6 +95,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-balances/runtime-benchmarks", "pallet-grandpa/runtime-benchmarks", + "pallet-sudo/runtime-benchmarks", "pallet-template/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "sp-runtime/runtime-benchmarks", @@ -107,7 +108,6 @@ try-runtime = [ "pallet-aura/try-runtime", "pallet-balances/try-runtime", "pallet-grandpa/try-runtime", - "pallet-randomness-collective-flip/try-runtime", "pallet-sudo/try-runtime", "pallet-template/try-runtime", "pallet-timestamp/try-runtime", diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index baba5d9b05e5..8fae73ef0857 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -6,9 +6,7 @@ #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); -use pallet_grandpa::{ - fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, -}; +use pallet_grandpa::AuthorityId as GrandpaId; use sp_api::impl_runtime_apis; use sp_consensus_aura::sr25519::AuthorityId as AuraId; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; @@ -29,7 +27,8 @@ use sp_version::RuntimeVersion; pub use frame_support::{ construct_runtime, parameter_types, traits::{ - ConstU128, ConstU32, ConstU64, ConstU8, KeyOwnerProofSystem, Randomness, StorageInfo, + ConstBool, ConstU128, ConstU32, ConstU64, ConstU8, KeyOwnerProofSystem, Randomness, + StorageInfo, }, weights::{ constants::{ @@ -64,7 +63,7 @@ pub type AccountId = <::Signer as IdentifyAccount>::Account pub type Balance = u128; /// Index of a transaction in the chain. -pub type Index = u32; +pub type Nonce = u32; /// A hash of some data used by the chain. pub type Hash = sp_core::H256; @@ -156,6 +155,8 @@ parameter_types! { impl frame_system::Config for Runtime { /// The basic call filter to use in dispatchable. type BaseCallFilter = frame_support::traits::Everything; + /// The block type for the runtime. + type Block = Block; /// Block & extrinsics weights: base values and limits. type BlockWeights = BlockWeights; /// The maximum length of a block (in bytes). @@ -166,16 +167,12 @@ impl frame_system::Config for Runtime { type RuntimeCall = RuntimeCall; /// The lookup mechanism to get account ID from whatever is passed in dispatchers. type Lookup = AccountIdLookup; - /// The index type for storing how many extrinsics an account has signed. - type Index = Index; - /// The index type for blocks. - type BlockNumber = BlockNumber; + /// The 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 header type. - type Header = generic::Header; /// The ubiquitous event type. type RuntimeEvent = RuntimeEvent; /// The ubiquitous origin type. @@ -205,31 +202,23 @@ impl frame_system::Config for Runtime { type MaxConsumers = frame_support::traits::ConstU32<16>; } -impl pallet_randomness_collective_flip::Config for Runtime {} - impl pallet_aura::Config for Runtime { type AuthorityId = AuraId; type DisabledValidators = (); type MaxAuthorities = ConstU32<32>; + type AllowMultipleBlocksPerSlot = ConstBool; } impl pallet_grandpa::Config for Runtime { type RuntimeEvent = RuntimeEvent; - type KeyOwnerProofSystem = (); - - type KeyOwnerProof = - >::Proof; - - type KeyOwnerIdentification = >::IdentificationTuple; - - type HandleEquivocation = (); - type WeightInfo = (); type MaxAuthorities = ConstU32<32>; + type MaxNominators = ConstU32<0>; + type MaxSetIdSessionEntries = ConstU64<0>; + + type KeyOwnerProof = sp_core::Void; + type EquivocationReportSystem = (); } impl pallet_timestamp::Config for Runtime { @@ -255,6 +244,10 @@ impl pallet_balances::Config for Runtime { type ExistentialDeposit = ConstU128; type AccountStore = System; type WeightInfo = pallet_balances::weights::SubstrateWeight; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } parameter_types! { @@ -273,23 +266,19 @@ impl pallet_transaction_payment::Config for Runtime { impl pallet_sudo::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; + type WeightInfo = pallet_sudo::weights::SubstrateWeight; } /// Configure the pallet-template in pallets/template. impl pallet_template::Config for Runtime { type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_template::weights::SubstrateWeight; } // Create the runtime by composing the FRAME pallets that were previously configured. construct_runtime!( - pub struct Runtime - where - Block = Block, - NodeBlock = opaque::Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { + pub struct Runtime { System: frame_system, - RandomnessCollectiveFlip: pallet_randomness_collective_flip, Timestamp: pallet_timestamp, Aura: pallet_aura, Grandpa: pallet_grandpa, @@ -344,6 +333,7 @@ mod benches { [frame_system, SystemBench::] [pallet_balances, Balances] [pallet_timestamp, Timestamp] + [pallet_sudo, Sudo] [pallet_template, TemplateModule] ); } @@ -367,6 +357,14 @@ impl_runtime_apis! { 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 { @@ -428,29 +426,29 @@ impl_runtime_apis! { } } - impl fg_primitives::GrandpaApi for Runtime { - fn grandpa_authorities() -> GrandpaAuthorityList { + impl sp_consensus_grandpa::GrandpaApi for Runtime { + fn grandpa_authorities() -> sp_consensus_grandpa::AuthorityList { Grandpa::grandpa_authorities() } - fn current_set_id() -> fg_primitives::SetId { + fn current_set_id() -> sp_consensus_grandpa::SetId { Grandpa::current_set_id() } fn submit_report_equivocation_unsigned_extrinsic( - _equivocation_proof: fg_primitives::EquivocationProof< + _equivocation_proof: sp_consensus_grandpa::EquivocationProof< ::Hash, NumberFor, >, - _key_owner_proof: fg_primitives::OpaqueKeyOwnershipProof, + _key_owner_proof: sp_consensus_grandpa::OpaqueKeyOwnershipProof, ) -> Option<()> { None } fn generate_key_ownership_proof( - _set_id: fg_primitives::SetId, + _set_id: sp_consensus_grandpa::SetId, _authority_id: GrandpaId, - ) -> Option { + ) -> 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). @@ -458,8 +456,8 @@ impl_runtime_apis! { } } - impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { - fn account_nonce(account: AccountId) -> Index { + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Nonce { System::account_nonce(account) } } @@ -571,40 +569,3 @@ impl_runtime_apis! { } } } - -#[cfg(test)] -mod tests { - use super::*; - use frame_support::traits::WhitelistedStorageKeys; - use sp_core::hexdisplay::HexDisplay; - use std::collections::HashSet; - - #[test] - fn check_whitelist() { - let whitelist: HashSet = AllPalletsWithSystem::whitelisted_storage_keys() - .iter() - .map(|e| HexDisplay::from(&e.key).to_string()) - .collect(); - - // Block Number - assert!( - whitelist.contains("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac") - ); - // Total Issuance - assert!( - whitelist.contains("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80") - ); - // Execution Phase - assert!( - whitelist.contains("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a") - ); - // Event Count - assert!( - whitelist.contains("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850") - ); - // System Events - assert!( - whitelist.contains("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7") - ); - } -} diff --git a/bin/node-template/rust-toolchain.toml b/bin/node-template/rust-toolchain.toml new file mode 100644 index 000000000000..64daeff68360 --- /dev/null +++ b/bin/node-template/rust-toolchain.toml @@ -0,0 +1,14 @@ +[toolchain] +channel = "nightly" +components = [ + "cargo", + "clippy", + "rust-analyzer", + "rust-src", + "rust-std", + "rustc-dev", + "rustc", + "rustfmt", +] +targets = [ "wasm32-unknown-unknown" ] +profile = "minimal" diff --git a/bin/node-template/scripts/docker_run.sh b/bin/node-template/scripts/docker_run.sh deleted file mode 100644 index 0bac44b4cfb3..000000000000 --- a/bin/node-template/scripts/docker_run.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/usr/bin/env bash -# This script is meant to be run on Unix/Linux based systems -set -e - -echo "*** Start Substrate node template ***" - -cd $(dirname ${BASH_SOURCE[0]})/.. - -docker-compose down --remove-orphans -docker-compose run --rm --service-ports dev $@ diff --git a/bin/node-template/shell.nix b/bin/node-template/shell.nix deleted file mode 100644 index c08005c1630e..000000000000 --- a/bin/node-template/shell.nix +++ /dev/null @@ -1,35 +0,0 @@ -let - mozillaOverlay = - import (builtins.fetchGit { - url = "https://github.com/mozilla/nixpkgs-mozilla.git"; - rev = "57c8084c7ef41366993909c20491e359bbb90f54"; - }); - pinned = builtins.fetchGit { - # Descriptive name to make the store path easier to identify - url = "https://github.com/nixos/nixpkgs/"; - # Commit hash for nixos-unstable as of 2020-04-26 - # `git ls-remote https://github.com/nixos/nixpkgs nixos-unstable` - ref = "refs/heads/nixos-unstable"; - rev = "1fe6ed37fd9beb92afe90671c0c2a662a03463dd"; - }; - nixpkgs = import pinned { overlays = [ mozillaOverlay ]; }; - toolchain = with nixpkgs; (rustChannelOf { date = "2021-09-14"; channel = "nightly"; }); - rust-wasm = toolchain.rust.override { - targets = [ "wasm32-unknown-unknown" ]; - }; -in -with nixpkgs; pkgs.mkShell { - buildInputs = [ - clang - pkg-config - rust-wasm - ] ++ stdenv.lib.optionals stdenv.isDarwin [ - darwin.apple_sdk.frameworks.Security - ]; - - LIBCLANG_PATH = "${llvmPackages.libclang}/lib"; - PROTOC = "${protobuf}/bin/protoc"; - RUST_SRC_PATH = "${toolchain.rust-src}/lib/rustlib/src/rust/library/"; - ROCKSDB_LIB_DIR = "${rocksdb}/lib"; - -} diff --git a/bin/node/bench/Cargo.toml b/bin/node/bench/Cargo.toml index 0097b51ab008..7703f8ed2e4e 100644 --- a/bin/node/bench/Cargo.toml +++ b/bin/node/bench/Cargo.toml @@ -12,33 +12,33 @@ publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -array-bytes = "4.1" -clap = { version = "4.0.9", features = ["derive"] } +array-bytes = "6.1" +clap = { version = "4.2.5", features = ["derive"] } log = "0.4.17" node-primitives = { version = "2.0.0", path = "../primitives" } node-testing = { version = "3.0.0-dev", path = "../testing" } kitchensink-runtime = { version = "3.0.0-dev", path = "../runtime" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api/" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } -sp-state-machine = { version = "0.13.0", path = "../../../primitives/state-machine" } -serde = "1.0.136" +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } +sp-state-machine = { version = "0.28.0", path = "../../../primitives/state-machine" } +serde = "1.0.163" serde_json = "1.0.85" derive_more = { version = "0.99.17", default-features = false, features = ["display"] } kvdb = "0.13.0" -kvdb-rocksdb = "0.17.0" -sp-trie = { version = "7.0.0", path = "../../../primitives/trie" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } +kvdb-rocksdb = "0.19.0" +sp-trie = { version = "22.0.0", path = "../../../primitives/trie" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sc-basic-authorship = { version = "0.10.0-dev", path = "../../../client/basic-authorship" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/timestamp" } -sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } -hash-db = "0.15.2" +sp-tracing = { version = "10.0.0", path = "../../../primitives/tracing" } +hash-db = "0.16.0" tempfile = "3.1.0" fs_extra = "1" rand = { version = "0.8.5", features = ["small_rng"] } lazy_static = "1.4.0" -parity-db = "0.4.3" +parity-db = "0.4.8" sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } futures = { version = "0.3.21", features = ["thread-pool"] } diff --git a/bin/node/bench/src/common.rs b/bin/node/bench/src/common.rs index 839e26f9f6d1..46c9d0417fec 100644 --- a/bin/node/bench/src/common.rs +++ b/bin/node/bench/src/common.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/node/bench/src/construct.rs b/bin/node/bench/src/construct.rs index f994b29b5e84..4f3ca07f86b9 100644 --- a/bin/node/bench/src/construct.rs +++ b/bin/node/bench/src/construct.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -28,7 +28,7 @@ use futures::Future; use std::{borrow::Cow, collections::HashMap, pin::Pin, sync::Arc}; use node_primitives::Block; -use node_testing::bench::{BenchDb, BlockType, DatabaseType, KeyTypes, Profile}; +use node_testing::bench::{BenchDb, BlockType, DatabaseType, KeyTypes}; use sc_transaction_pool_api::{ ImportNotificationStream, PoolFuture, PoolStatus, ReadyTransactions, TransactionFor, TransactionSource, TransactionStatusStreamFor, TxHash, @@ -43,7 +43,6 @@ use crate::{ }; pub struct ConstructionBenchmarkDescription { - pub profile: Profile, pub key_types: KeyTypes, pub block_type: BlockType, pub size: SizeType, @@ -51,7 +50,6 @@ pub struct ConstructionBenchmarkDescription { } pub struct ConstructionBenchmark { - profile: Profile, database: BenchDb, transactions: Transactions, } @@ -60,11 +58,6 @@ impl core::BenchmarkDescription for ConstructionBenchmarkDescription { fn path(&self) -> Path { let mut path = Path::new(&["node", "proposer"]); - match self.profile { - Profile::Wasm => path.push("wasm"), - Profile::Native => path.push("native"), - } - match self.key_types { KeyTypes::Sr25519 => path.push("sr25519"), KeyTypes::Ed25519 => path.push("ed25519"), @@ -99,7 +92,6 @@ impl core::BenchmarkDescription for ConstructionBenchmarkDescription { } Box::new(ConstructionBenchmark { - profile: self.profile, database: bench_db, transactions: Transactions(extrinsics), }) @@ -107,8 +99,8 @@ impl core::BenchmarkDescription for ConstructionBenchmarkDescription { fn name(&self) -> Cow<'static, str> { format!( - "Block construction ({:?}/{}, {:?}, {:?} backend)", - self.block_type, self.size, self.profile, self.database_type, + "Block construction ({:?}/{}, {:?} backend)", + self.block_type, self.size, self.database_type, ) .into() } @@ -116,11 +108,11 @@ impl core::BenchmarkDescription for ConstructionBenchmarkDescription { impl core::Benchmark for ConstructionBenchmark { fn run(&mut self, mode: Mode) -> std::time::Duration { - let context = self.database.create_context(self.profile); + let context = self.database.create_context(); let _ = context .client - .runtime_version_at(&BlockId::Number(0)) + .runtime_version_at(context.client.chain_info().genesis_hash) .expect("Failed to get runtime version") .spec_version; diff --git a/bin/node/bench/src/core.rs b/bin/node/bench/src/core.rs index b6ad3ecd8006..1f22d3db6926 100644 --- a/bin/node/bench/src/core.rs +++ b/bin/node/bench/src/core.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -72,24 +72,13 @@ pub struct NsFormatter(pub u64); impl fmt::Display for NsFormatter { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let v = self.0; - - if v < 100 { - return write!(f, "{} ns", v) - } - - if self.0 < 100_000 { - return write!(f, "{:.1} µs", v as f64 / 1000.0) - } - - if self.0 < 1_000_000 { - return write!(f, "{:.4} ms", v as f64 / 1_000_000.0) - } - - if self.0 < 100_000_000 { - return write!(f, "{:.1} ms", v as f64 / 1_000_000.0) + match v { + v if v < 100 => write!(f, "{} ns", v), + v if v < 100_000 => write!(f, "{:.1} µs", v as f64 / 1000.0), + v if v < 1_000_000 => write!(f, "{:.4} ms", v as f64 / 1_000_000.0), + v if v < 100_000_000 => write!(f, "{:.1} ms", v as f64 / 1_000_000.0), + _ => write!(f, "{:.4} s", v as f64 / 1_000_000_000.0), } - - write!(f, "{:.4} s", v as f64 / 1_000_000_000.0) } } diff --git a/bin/node/bench/src/generator.rs b/bin/node/bench/src/generator.rs index 76bd3a3240c5..0fe0826028f5 100644 --- a/bin/node/bench/src/generator.rs +++ b/bin/node/bench/src/generator.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/node/bench/src/import.rs b/bin/node/bench/src/import.rs index 7d59e94a44a0..78b280076e0b 100644 --- a/bin/node/bench/src/import.rs +++ b/bin/node/bench/src/import.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -33,9 +33,8 @@ use std::borrow::Cow; use node_primitives::Block; -use node_testing::bench::{BenchDb, BlockType, DatabaseType, KeyTypes, Profile}; +use node_testing::bench::{BenchDb, BlockType, DatabaseType, KeyTypes}; use sc_client_api::backend::Backend; -use sp_runtime::generic::BlockId; use sp_state_machine::InspectState; use crate::{ @@ -44,7 +43,6 @@ use crate::{ }; pub struct ImportBenchmarkDescription { - pub profile: Profile, pub key_types: KeyTypes, pub block_type: BlockType, pub size: SizeType, @@ -52,7 +50,6 @@ pub struct ImportBenchmarkDescription { } pub struct ImportBenchmark { - profile: Profile, database: BenchDb, block: Block, block_type: BlockType, @@ -62,11 +59,6 @@ impl core::BenchmarkDescription for ImportBenchmarkDescription { fn path(&self) -> Path { let mut path = Path::new(&["node", "import"]); - match self.profile { - Profile::Wasm => path.push("wasm"), - Profile::Native => path.push("native"), - } - match self.key_types { KeyTypes::Sr25519 => path.push("sr25519"), KeyTypes::Ed25519 => path.push("ed25519"), @@ -89,21 +81,15 @@ impl core::BenchmarkDescription for ImportBenchmarkDescription { } fn setup(self: Box) -> Box { - let profile = self.profile; let mut bench_db = BenchDb::with_key_types(self.database_type, 50_000, self.key_types); let block = bench_db.generate_block(self.block_type.to_content(self.size.transactions())); - Box::new(ImportBenchmark { - database: bench_db, - block_type: self.block_type, - block, - profile, - }) + Box::new(ImportBenchmark { database: bench_db, block_type: self.block_type, block }) } fn name(&self) -> Cow<'static, str> { format!( - "Block import ({:?}/{}, {:?}, {:?} backend)", - self.block_type, self.size, self.profile, self.database_type, + "Block import ({:?}/{}, {:?} backend)", + self.block_type, self.size, self.database_type, ) .into() } @@ -111,11 +97,11 @@ impl core::BenchmarkDescription for ImportBenchmarkDescription { impl core::Benchmark for ImportBenchmark { fn run(&mut self, mode: Mode) -> std::time::Duration { - let mut context = self.database.create_context(self.profile); + let mut context = self.database.create_context(); let _ = context .client - .runtime_version_at(&BlockId::Number(0)) + .runtime_version_at(context.client.chain_info().genesis_hash) .expect("Failed to get runtime version") .spec_version; diff --git a/bin/node/bench/src/main.rs b/bin/node/bench/src/main.rs index 8a5d99640eb1..1f69c9769580 100644 --- a/bin/node/bench/src/main.rs +++ b/bin/node/bench/src/main.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -30,7 +30,7 @@ mod txpool; use clap::Parser; -use node_testing::bench::{BlockType, DatabaseType as BenchDataBaseType, KeyTypes, Profile}; +use node_testing::bench::{BlockType, DatabaseType as BenchDataBaseType, KeyTypes}; use crate::{ common::SizeType, @@ -85,31 +85,28 @@ fn main() { let mut import_benchmarks = Vec::new(); - for profile in [Profile::Wasm, Profile::Native] { - for size in [ - SizeType::Empty, - SizeType::Small, - SizeType::Medium, - SizeType::Large, - SizeType::Full, - SizeType::Custom(opt.transactions.unwrap_or(0)), + for size in [ + SizeType::Empty, + SizeType::Small, + SizeType::Medium, + SizeType::Large, + SizeType::Full, + SizeType::Custom(opt.transactions.unwrap_or(0)), + ] { + for block_type in [ + BlockType::RandomTransfersKeepAlive, + BlockType::RandomTransfersReaping, + BlockType::Noop, ] { - for block_type in [ - BlockType::RandomTransfersKeepAlive, - BlockType::RandomTransfersReaping, - BlockType::Noop, - ] { - for database_type in [BenchDataBaseType::RocksDb, BenchDataBaseType::ParityDb] { - import_benchmarks.push((profile, size, block_type, database_type)); - } + for database_type in [BenchDataBaseType::RocksDb, BenchDataBaseType::ParityDb] { + import_benchmarks.push((size, block_type, database_type)); } } } let benchmarks = matrix!( - (profile, size, block_type, database_type) in import_benchmarks.into_iter() => + (size, block_type, database_type) in import_benchmarks.into_iter() => ImportBenchmarkDescription { - profile, key_types: KeyTypes::Sr25519, size, block_type, @@ -138,14 +135,12 @@ fn main() { .iter().map(move |db_type| (size, db_type))) => TrieWriteBenchmarkDescription { database_size: *size, database_type: *db_type }, ConstructionBenchmarkDescription { - profile: Profile::Wasm, key_types: KeyTypes::Sr25519, block_type: BlockType::RandomTransfersKeepAlive, size: SizeType::Medium, database_type: BenchDataBaseType::RocksDb, }, ConstructionBenchmarkDescription { - profile: Profile::Wasm, key_types: KeyTypes::Sr25519, block_type: BlockType::RandomTransfersKeepAlive, size: SizeType::Large, diff --git a/bin/node/bench/src/simple_trie.rs b/bin/node/bench/src/simple_trie.rs index aa9c96a1cbd3..6d5072358d23 100644 --- a/bin/node/bench/src/simple_trie.rs +++ b/bin/node/bench/src/simple_trie.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/node/bench/src/state_sizes.rs b/bin/node/bench/src/state_sizes.rs index 5387850666b6..12e0f6eae191 100644 --- a/bin/node/bench/src/state_sizes.rs +++ b/bin/node/bench/src/state_sizes.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/node/bench/src/tempdb.rs b/bin/node/bench/src/tempdb.rs index 82895ddfab69..f3fd693d21fe 100644 --- a/bin/node/bench/src/tempdb.rs +++ b/bin/node/bench/src/tempdb.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -90,7 +90,7 @@ impl TempDatabase { }, DatabaseType::ParityDb => Arc::new(ParityDbWrapper({ let mut options = parity_db::Options::with_columns(self.0.path(), 1); - let mut column_options = &mut options.columns[0]; + let column_options = &mut options.columns[0]; column_options.ref_counted = true; column_options.preimage = true; column_options.uniform = true; diff --git a/bin/node/bench/src/trie.rs b/bin/node/bench/src/trie.rs index de49a6fe7b6d..09ab405c03b2 100644 --- a/bin/node/bench/src/trie.rs +++ b/bin/node/bench/src/trie.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/node/bench/src/txpool.rs b/bin/node/bench/src/txpool.rs index 322dc352e897..a3524ac5bc89 100644 --- a/bin/node/bench/src/txpool.rs +++ b/bin/node/bench/src/txpool.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -23,7 +23,7 @@ use std::borrow::Cow; -use node_testing::bench::{BenchDb, BlockType, DatabaseType, KeyTypes, Profile}; +use node_testing::bench::{BenchDb, BlockType, DatabaseType, KeyTypes}; use sc_transaction_pool::BasicPool; use sc_transaction_pool_api::{TransactionPool, TransactionSource}; @@ -57,11 +57,11 @@ impl core::BenchmarkDescription for PoolBenchmarkDescription { impl core::Benchmark for PoolBenchmark { fn run(&mut self, mode: Mode) -> std::time::Duration { - let context = self.database.create_context(Profile::Wasm); + let context = self.database.create_context(); let _ = context .client - .runtime_version_at(&BlockId::Number(0)) + .runtime_version_at(context.client.chain_info().genesis_hash) .expect("Failed to get runtime version") .spec_version; diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index b55b35c05652..032ba271f3c8 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -35,10 +35,10 @@ crate-type = ["cdylib", "rlib"] [dependencies] # third-party dependencies -array-bytes = "4.1" -clap = { version = "4.0.9", features = ["derive"], optional = true } -codec = { package = "parity-scale-codec", version = "3.2.2" } -serde = { version = "1.0.136", features = ["derive"] } +array-bytes = "6.1" +clap = { version = "4.2.5", features = ["derive"], optional = true } +codec = { package = "parity-scale-codec", version = "3.6.1" } +serde = { version = "1.0.163", features = ["derive"] } jsonrpsee = { version = "0.16.2", features = ["server"] } futures = "0.3.21" log = "0.4.17" @@ -47,19 +47,18 @@ rand = "0.8" # primitives sp-authority-discovery = { version = "4.0.0-dev", path = "../../../primitives/authority-discovery" } sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } -grandpa-primitives = { version = "4.0.0-dev", package = "sp-finality-grandpa", path = "../../../primitives/finality-grandpa" } +grandpa-primitives = { version = "4.0.0-dev", package = "sp-consensus-grandpa", path = "../../../primitives/consensus/grandpa" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } -sp-authorship = { version = "4.0.0-dev", path = "../../../primitives/authorship" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } -sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } +sp-keyring = { version = "24.0.0", path = "../../../primitives/keyring" } +sp-keystore = { version = "0.27.0", path = "../../../primitives/keystore" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sp-transaction-pool = { version = "4.0.0-dev", path = "../../../primitives/transaction-pool" } sp-transaction-storage-proof = { version = "4.0.0-dev", path = "../../../primitives/transaction-storage-proof" } sp-io = { path = "../../../primitives/io" } +sp-statement-store = { path = "../../../primitives/statement-store" } # client dependencies sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } @@ -67,12 +66,14 @@ sc-chain-spec = { version = "4.0.0-dev", path = "../../../client/chain-spec" } sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } +sc-statement-store = { version = "4.0.0-dev", path = "../../../client/statement-store" } sc-network = { version = "0.10.0-dev", path = "../../../client/network" } sc-network-common = { version = "0.10.0-dev", path = "../../../client/network/common" } +sc-network-sync = { version = "0.10.0-dev", path = "../../../client/network/sync" } +sc-network-statement = { version = "0.10.0-dev", path = "../../../client/network/statement" } sc-consensus-slots = { version = "0.10.0-dev", path = "../../../client/consensus/slots" } sc-consensus-babe = { version = "0.10.0-dev", path = "../../../client/consensus/babe" } -sc-consensus-uncles = { version = "0.10.0-dev", path = "../../../client/consensus/uncles" } -grandpa = { version = "0.10.0-dev", package = "sc-finality-grandpa", path = "../../../client/finality-grandpa" } +grandpa = { version = "0.10.0-dev", package = "sc-consensus-grandpa", path = "../../../client/consensus/grandpa" } sc-rpc = { version = "4.0.0-dev", path = "../../../client/rpc" } sc-basic-authorship = { version = "0.10.0-dev", path = "../../../client/basic-authorship" } sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../client/service" } @@ -82,13 +83,14 @@ sc-authority-discovery = { version = "0.10.0-dev", path = "../../../client/autho sc-sync-state-rpc = { version = "0.10.0-dev", path = "../../../client/sync-state-rpc" } sc-sysinfo = { version = "6.0.0-dev", path = "../../../client/sysinfo" } sc-storage-monitor = { version = "0.1.0", path = "../../../client/storage-monitor" } +sc-offchain = { version = "4.0.0-dev", path = "../../../client/offchain" } # frame dependencies frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } frame-system-rpc-runtime-api = { version = "4.0.0-dev", path = "../../../frame/system/rpc/runtime-api" } -pallet-transaction-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment" } pallet-assets = { version = "4.0.0-dev", path = "../../../frame/assets/" } -pallet-asset-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/asset-tx-payment/" } +pallet-asset-conversion-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/asset-conversion-tx-payment" } +pallet-asset-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/asset-tx-payment" } pallet-im-online = { version = "4.0.0-dev", default-features = false, path = "../../../frame/im-online" } # node-specific dependencies @@ -112,7 +114,7 @@ sc-consensus-babe = { version = "0.10.0-dev", path = "../../../client/consensus/ sc-consensus-epochs = { version = "0.10.0-dev", path = "../../../client/consensus/epochs" } sc-service-test = { version = "2.0.0", path = "../../../client/service/test" } sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } -sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } +sp-tracing = { version = "10.0.0", path = "../../../primitives/tracing" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } futures = "0.3.21" tempfile = "3.1.0" @@ -120,17 +122,18 @@ assert_cmd = "2.0.2" nix = { version = "0.26.1", features = ["signal"] } serde_json = "1.0" regex = "1.6.0" -platforms = "2.0" +platforms = "3.0" soketto = "0.7.1" -criterion = { version = "0.3.5", features = ["async_tokio"] } +criterion = { version = "0.4.0", features = ["async_tokio"] } tokio = { version = "1.22.0", features = ["macros", "time", "parking_lot"] } tokio-util = { version = "0.7.4", features = ["compat"] } wait-timeout = "0.2" substrate-rpc-client = { path = "../../../utils/frame/rpc/client" } pallet-timestamp = { version = "4.0.0-dev", path = "../../../frame/timestamp" } +substrate-cli-test-utils = { path = "../../../test-utils/cli" } [build-dependencies] -clap = { version = "4.0.9", optional = true } +clap = { version = "4.2.5", optional = true } clap_complete = { version = "4.0.2", optional = true } node-inspect = { version = "0.9.0-dev", optional = true, path = "../inspect" } frame-benchmarking-cli = { version = "4.0.0-dev", optional = true, path = "../../../utils/frame/benchmarking-cli" } diff --git a/bin/node/cli/benches/block_production.rs b/bin/node/cli/benches/block_production.rs index 4fcebb123d9e..5ee538d18d6a 100644 --- a/bin/node/cli/benches/block_production.rs +++ b/bin/node/cli/benches/block_production.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -21,7 +21,6 @@ use criterion::{criterion_group, criterion_main, BatchSize, Criterion, Throughpu use kitchensink_runtime::{constants::currency::*, BalancesCall}; use node_cli::service::{create_extrinsic, FullClient}; use sc_block_builder::{BlockBuilderProvider, BuiltBlock, RecordProof}; -use sc_client_api::execution_extensions::ExecutionStrategies; use sc_consensus::{ block_import::{BlockImportParams, ForkChoiceStrategy}, BlockImport, StateAction, @@ -37,7 +36,6 @@ use sp_blockchain::{ApplyExtrinsicFailed::Validity, Error::ApplyExtrinsicFailed} use sp_consensus::BlockOrigin; use sp_keyring::Sr25519Keyring; use sp_runtime::{ - generic::BlockId, transaction_validity::{InvalidTransaction, TransactionValidityError}, AccountId32, MultiAddress, OpaqueExtrinsic, }; @@ -57,9 +55,6 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { let spec = Box::new(node_cli::chain_spec::development_config()); - // NOTE: We enforce the use of the WASM runtime to benchmark block production using WASM. - let execution_strategy = sc_client_api::ExecutionStrategy::AlwaysWasm; - let config = Configuration { impl_name: "BenchmarkImpl".into(), impl_version: "1.0".into(), @@ -70,7 +65,6 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { transaction_pool: Default::default(), network: network_config, keystore: KeystoreConfig::InMemory, - keystore_remote: Default::default(), database: DatabaseSource::RocksDb { path: root.join("db"), cache_size: 128 }, trie_cache_maximum_size: Some(64 * 1024 * 1024), state_pruning: Some(PruningMode::ArchiveAll), @@ -79,25 +73,15 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { wasm_method: WasmExecutionMethod::Compiled { instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, }, - execution_strategies: ExecutionStrategies { - syncing: execution_strategy, - importing: execution_strategy, - block_construction: execution_strategy, - offchain_worker: execution_strategy, - other: execution_strategy, - }, - rpc_http: None, - rpc_ws: None, - rpc_ipc: None, - rpc_ws_max_connections: None, + rpc_addr: None, + rpc_max_connections: Default::default(), rpc_cors: None, rpc_methods: Default::default(), - rpc_max_payload: None, - rpc_max_request_size: None, - rpc_max_response_size: None, - rpc_id_provider: None, - rpc_max_subs_per_conn: None, - ws_max_out_buffer_capacity: None, + rpc_max_request_size: Default::default(), + rpc_max_response_size: Default::default(), + rpc_id_provider: Default::default(), + rpc_max_subs_per_conn: Default::default(), + rpc_port: 9944, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, @@ -110,7 +94,8 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { max_runtime_instances: 8, runtime_cache_size: 2, announce_block: true, - base_path: Some(base_path), + data_path: base_path.path().into(), + base_path, informant_output_format: Default::default(), wasm_runtime_overrides: None, }; @@ -138,7 +123,7 @@ fn import_block( params.state_action = StateAction::ApplyChanges(sc_consensus::StorageChanges::Changes(built.storage_changes)); params.fork_choice = Some(ForkChoiceStrategy::LongestChain); - futures::executor::block_on(client.import_block(params, Default::default())) + futures::executor::block_on(client.import_block(params)) .expect("importing a block doesn't fail"); } @@ -163,7 +148,7 @@ fn prepare_benchmark(client: &FullClient) -> (usize, Vec) { let extrinsic: OpaqueExtrinsic = create_extrinsic( client, src.clone(), - BalancesCall::transfer { dest: dst.clone(), value: 1 * DOLLARS }, + BalancesCall::transfer_allow_death { dest: dst.clone(), value: 1 * DOLLARS }, Some(nonce), ) .into(); @@ -206,14 +191,14 @@ fn block_production(c: &mut Criterion) { group.sample_size(10); group.throughput(Throughput::Elements(max_transfer_count as u64)); - let block_id = BlockId::Hash(client.chain_info().best_hash); + let best_hash = client.chain_info().best_hash; group.bench_function(format!("{} transfers (no proof)", max_transfer_count), |b| { b.iter_batched( || extrinsics.clone(), |extrinsics| { let mut block_builder = - client.new_block_at(&block_id, Default::default(), RecordProof::No).unwrap(); + client.new_block_at(best_hash, Default::default(), RecordProof::No).unwrap(); for extrinsic in extrinsics { block_builder.push(extrinsic).unwrap(); } @@ -228,7 +213,7 @@ fn block_production(c: &mut Criterion) { || extrinsics.clone(), |extrinsics| { let mut block_builder = - client.new_block_at(&block_id, Default::default(), RecordProof::Yes).unwrap(); + client.new_block_at(best_hash, Default::default(), RecordProof::Yes).unwrap(); for extrinsic in extrinsics { block_builder.push(extrinsic).unwrap(); } diff --git a/bin/node/cli/benches/transaction_pool.rs b/bin/node/cli/benches/transaction_pool.rs index a8839642ddc2..d3e8c02a958f 100644 --- a/bin/node/cli/benches/transaction_pool.rs +++ b/bin/node/cli/benches/transaction_pool.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -23,11 +23,10 @@ use futures::{future, StreamExt}; use kitchensink_runtime::{constants::currency::*, BalancesCall, SudoCall}; use node_cli::service::{create_extrinsic, fetch_nonce, FullClient, TransactionPool}; use node_primitives::AccountId; -use sc_client_api::execution_extensions::ExecutionStrategies; use sc_service::{ config::{ BlocksPruning, DatabaseSource, KeystoreConfig, NetworkConfiguration, OffchainWorkerConfig, - PruningMode, TransactionPoolOptions, WasmExecutionMethod, + PruningMode, TransactionPoolOptions, }, BasePath, Configuration, Role, }; @@ -64,33 +63,21 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { }, network: network_config, keystore: KeystoreConfig::InMemory, - keystore_remote: Default::default(), database: DatabaseSource::RocksDb { path: root.join("db"), cache_size: 128 }, trie_cache_maximum_size: Some(64 * 1024 * 1024), state_pruning: Some(PruningMode::ArchiveAll), blocks_pruning: BlocksPruning::KeepAll, chain_spec: spec, - wasm_method: WasmExecutionMethod::Interpreted, - // NOTE: we enforce the use of the native runtime to make the errors more debuggable - execution_strategies: ExecutionStrategies { - syncing: sc_client_api::ExecutionStrategy::NativeWhenPossible, - importing: sc_client_api::ExecutionStrategy::NativeWhenPossible, - block_construction: sc_client_api::ExecutionStrategy::NativeWhenPossible, - offchain_worker: sc_client_api::ExecutionStrategy::NativeWhenPossible, - other: sc_client_api::ExecutionStrategy::NativeWhenPossible, - }, - rpc_http: None, - rpc_ws: None, - rpc_ipc: None, - rpc_ws_max_connections: None, + wasm_method: Default::default(), + rpc_addr: None, + rpc_max_connections: Default::default(), rpc_cors: None, rpc_methods: Default::default(), - rpc_max_payload: None, - rpc_max_request_size: None, - rpc_max_response_size: None, - rpc_id_provider: None, - rpc_max_subs_per_conn: None, - ws_max_out_buffer_capacity: None, + rpc_max_request_size: Default::default(), + rpc_max_response_size: Default::default(), + rpc_id_provider: Default::default(), + rpc_max_subs_per_conn: Default::default(), + rpc_port: 9944, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, @@ -103,7 +90,8 @@ fn new_node(tokio_handle: Handle) -> node_cli::service::NewFullBase { max_runtime_instances: 8, runtime_cache_size: 2, announce_block: true, - base_path: Some(base_path), + data_path: base_path.path().into(), + base_path, informant_output_format: Default::default(), wasm_runtime_overrides: None, }; @@ -140,10 +128,9 @@ fn create_account_extrinsics( Sr25519Keyring::Alice.pair(), SudoCall::sudo { call: Box::new( - BalancesCall::set_balance { + BalancesCall::force_set_balance { who: AccountId::from(a.public()).into(), new_free: 0, - new_reserved: 0, } .into(), ), @@ -156,10 +143,9 @@ fn create_account_extrinsics( Sr25519Keyring::Alice.pair(), SudoCall::sudo { call: Box::new( - BalancesCall::set_balance { + BalancesCall::force_set_balance { who: AccountId::from(a.public()).into(), new_free: 1_000_000 * DOLLARS, - new_reserved: 0, } .into(), ), @@ -184,7 +170,7 @@ fn create_benchmark_extrinsics( create_extrinsic( client, account.clone(), - BalancesCall::transfer { + BalancesCall::transfer_allow_death { dest: Sr25519Keyring::Bob.to_account_id().into(), value: 1 * DOLLARS, }, diff --git a/bin/node/cli/bin/main.rs b/bin/node/cli/bin/main.rs index 3ae295754561..4b434a3e6dad 100644 --- a/bin/node/cli/bin/main.rs +++ b/bin/node/cli/bin/main.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/node/cli/build.rs b/bin/node/cli/build.rs index e8142b297f1b..18860a1afafb 100644 --- a/bin/node/cli/build.rs +++ b/bin/node/cli/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/node/cli/src/benchmarking.rs b/bin/node/cli/src/benchmarking.rs index 16ea9109d0c1..333f855f2d7b 100644 --- a/bin/node/cli/src/benchmarking.rs +++ b/bin/node/cli/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index 1e4e806fd273..a56c4e7b4016 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -20,11 +20,10 @@ use grandpa_primitives::AuthorityId as GrandpaId; use kitchensink_runtime::{ - constants::currency::*, wasm_binary_unwrap, AuthorityDiscoveryConfig, BabeConfig, - BalancesConfig, Block, CouncilConfig, DemocracyConfig, ElectionsConfig, GrandpaConfig, - ImOnlineConfig, IndicesConfig, MaxNominations, NominationPoolsConfig, SessionConfig, - SessionKeys, SocietyConfig, StakerStatus, StakingConfig, SudoConfig, SystemConfig, - TechnicalCommitteeConfig, + constants::currency::*, wasm_binary_unwrap, BabeConfig, BalancesConfig, Block, CouncilConfig, + DemocracyConfig, ElectionsConfig, ImOnlineConfig, IndicesConfig, MaxNominations, + NominationPoolsConfig, SessionConfig, SessionKeys, SocietyConfig, StakerStatus, StakingConfig, + SudoConfig, SystemConfig, TechnicalCommitteeConfig, }; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use sc_chain_spec::ChainSpecExtension; @@ -39,7 +38,7 @@ use sp_runtime::{ Perbill, }; -pub use kitchensink_runtime::GenesisConfig; +pub use kitchensink_runtime::RuntimeGenesisConfig; pub use node_primitives::{AccountId, Balance, Signature}; type AccountPublic = ::Signer; @@ -62,7 +61,7 @@ pub struct Extensions { } /// Specialized `ChainSpec`. -pub type ChainSpec = sc_service::GenericChainSpec; +pub type ChainSpec = sc_service::GenericChainSpec; /// Flaming Fir testnet generator pub fn flaming_fir_config() -> Result { ChainSpec::from_json_bytes(&include_bytes!("../res/flaming-fir.json")[..]) @@ -77,7 +76,7 @@ fn session_keys( SessionKeys { grandpa, babe, im_online, authority_discovery } } -fn staging_testnet_config_genesis() -> GenesisConfig { +fn staging_testnet_config_genesis() -> RuntimeGenesisConfig { #[rustfmt::skip] // stash, controller, session-key // generated with secret: @@ -229,7 +228,7 @@ pub fn authority_keys_from_seed( ) } -/// Helper function to create GenesisConfig for testing +/// Helper function to create RuntimeGenesisConfig for testing pub fn testnet_genesis( initial_authorities: Vec<( AccountId, @@ -242,7 +241,7 @@ pub fn testnet_genesis( initial_nominators: Vec, root_key: AccountId, endowed_accounts: Option>, -) -> GenesisConfig { +) -> RuntimeGenesisConfig { let mut endowed_accounts: Vec = endowed_accounts.unwrap_or_else(|| { vec![ get_account_id_from_seed::("Alice"), @@ -274,7 +273,7 @@ pub fn testnet_genesis( let mut rng = rand::thread_rng(); let stakers = initial_authorities .iter() - .map(|x| (x.0.clone(), x.1.clone(), STASH, StakerStatus::Validator)) + .map(|x| (x.0.clone(), x.0.clone(), STASH, StakerStatus::Validator)) .chain(initial_nominators.iter().map(|x| { use rand::{seq::SliceRandom, Rng}; let limit = (MaxNominations::get() as usize).min(initial_authorities.len()); @@ -294,8 +293,8 @@ pub fn testnet_genesis( const ENDOWMENT: Balance = 10_000_000 * DOLLARS; const STASH: Balance = ENDOWMENT / 1000; - GenesisConfig { - system: SystemConfig { code: wasm_binary_unwrap().to_vec() }, + RuntimeGenesisConfig { + system: SystemConfig { code: wasm_binary_unwrap().to_vec(), ..Default::default() }, balances: BalancesConfig { balances: endowed_accounts.iter().cloned().map(|x| (x, ENDOWMENT)).collect(), }, @@ -340,29 +339,22 @@ pub fn testnet_genesis( }, sudo: SudoConfig { key: Some(root_key) }, babe: BabeConfig { - authorities: vec![], epoch_config: Some(kitchensink_runtime::BABE_GENESIS_EPOCH_CONFIG), + ..Default::default() }, im_online: ImOnlineConfig { keys: vec![] }, - authority_discovery: AuthorityDiscoveryConfig { keys: vec![] }, - grandpa: GrandpaConfig { authorities: vec![] }, + authority_discovery: Default::default(), + grandpa: Default::default(), technical_membership: Default::default(), treasury: Default::default(), - society: SocietyConfig { - members: endowed_accounts - .iter() - .take((num_endowed_accounts + 1) / 2) - .cloned() - .collect(), - pot: 0, - max_members: 999, - }, + society: SocietyConfig { pot: 0 }, vesting: Default::default(), assets: pallet_assets::GenesisConfig { // This asset is used by the NIS pallet as counterpart currency. assets: vec![(9, get_account_id_from_seed::("Alice"), true, 1)], ..Default::default() }, + pool_assets: Default::default(), transaction_storage: Default::default(), transaction_payment: Default::default(), alliance: Default::default(), @@ -372,10 +364,11 @@ pub fn testnet_genesis( min_join_bond: 1 * DOLLARS, ..Default::default() }, + glutton: Default::default(), } } -fn development_config_genesis() -> GenesisConfig { +fn development_config_genesis() -> RuntimeGenesisConfig { testnet_genesis( vec![authority_keys_from_seed("Alice")], vec![], @@ -400,7 +393,7 @@ pub fn development_config() -> ChainSpec { ) } -fn local_testnet_genesis() -> GenesisConfig { +fn local_testnet_genesis() -> RuntimeGenesisConfig { testnet_genesis( vec![authority_keys_from_seed("Alice"), authority_keys_from_seed("Bob")], vec![], @@ -432,7 +425,7 @@ pub(crate) mod tests { use sc_service_test; use sp_runtime::BuildStorage; - fn local_testnet_genesis_instant_single() -> GenesisConfig { + fn local_testnet_genesis_instant_single() -> RuntimeGenesisConfig { testnet_genesis( vec![authority_keys_from_seed("Alice")], vec![], @@ -479,12 +472,13 @@ pub(crate) mod tests { sp_tracing::try_init_simple(); sc_service_test::connectivity(integration_test_config_with_two_authorities(), |config| { - let NewFullBase { task_manager, client, network, transaction_pool, .. } = + let NewFullBase { task_manager, client, network, sync, transaction_pool, .. } = new_full_base(config, false, |_, _| ())?; Ok(sc_service_test::TestNetComponents::new( task_manager, client, network, + sync, transaction_pool, )) }); diff --git a/bin/node/cli/src/cli.rs b/bin/node/cli/src/cli.rs index 7bea336c8e41..35b949831141 100644 --- a/bin/node/cli/src/cli.rs +++ b/bin/node/cli/src/cli.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/node/cli/src/command.rs b/bin/node/cli/src/command.rs index 2ed8a2c75401..b138b397eff6 100644 --- a/bin/node/cli/src/command.rs +++ b/bin/node/cli/src/command.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -26,12 +26,18 @@ use frame_benchmarking_cli::*; use kitchensink_runtime::{ExistentialDeposit, RuntimeApi}; use node_executor::ExecutorDispatch; use node_primitives::Block; -use sc_cli::{ChainSpec, Result, RuntimeVersion, SubstrateCli}; +use sc_cli::{Result, SubstrateCli}; use sc_service::PartialComponents; use sp_keyring::Sr25519Keyring; use std::sync::Arc; +#[cfg(feature = "try-runtime")] +use { + kitchensink_runtime::constants::time::SLOT_DURATION, + try_runtime_cli::block_building_info::substrate_info, +}; + impl SubstrateCli for Cli { fn impl_name() -> String { "Substrate Node".into() @@ -73,10 +79,6 @@ impl SubstrateCli for Cli { }; Ok(spec) } - - fn native_runtime_version(_: &Box) -> &'static RuntimeVersion { - &kitchensink_runtime::VERSION - } } /// Parse command line arguments into service configuration. @@ -111,7 +113,7 @@ pub fn run() -> Result<()> { ) } - cmd.run::(config) + cmd.run::(config) }, BenchmarkCmd::Block(cmd) => { // ensure that we keep the task manager alive @@ -236,11 +238,13 @@ pub fn run() -> Result<()> { sc_service::TaskManager::new(config.tokio_handle.clone(), registry) .map_err(|e| sc_cli::Error::Service(sc_service::Error::Prometheus(e)))?; + let info_provider = substrate_info(SLOT_DURATION); + Ok(( cmd.run::::ExtendHostFunctions, - >>(), + >, _>(Some(info_provider)), task_manager, )) }) diff --git a/bin/node/cli/src/lib.rs b/bin/node/cli/src/lib.rs index 13c074268e50..2fe238ef316e 100644 --- a/bin/node/cli/src/lib.rs +++ b/bin/node/cli/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/node/cli/src/service.rs b/bin/node/cli/src/service.rs index e329087947c9..99608c04d38b 100644 --- a/bin/node/cli/src/service.rs +++ b/bin/node/cli/src/service.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -28,13 +28,16 @@ use futures::prelude::*; use kitchensink_runtime::RuntimeApi; use node_executor::ExecutorDispatch; use node_primitives::Block; -use sc_client_api::BlockBackend; +use sc_client_api::{Backend, BlockBackend}; use sc_consensus_babe::{self, SlotProportion}; use sc_executor::NativeElseWasmExecutor; -use sc_network::NetworkService; -use sc_network_common::{protocol::event::Event, service::NetworkEventStream}; +use sc_network::{event::Event, NetworkEventStream, NetworkService}; +use sc_network_common::sync::warp::WarpSyncParams; +use sc_network_sync::SyncingService; use sc_service::{config::Configuration, error::Error as ServiceError, RpcHandlers, TaskManager}; +use sc_statement_store::Store as StatementStore; use sc_telemetry::{Telemetry, TelemetryWorker}; +use sc_transaction_pool_api::OffchainTransactionPoolFactory; use sp_api::ProvideRuntimeApi; use sp_core::crypto::Pair; use sp_runtime::{generic, traits::Block as BlockT, SaturatedConversion}; @@ -51,6 +54,10 @@ type FullGrandpaBlockImport = /// The transaction pool type defintion. pub type TransactionPool = sc_transaction_pool::FullPool; +/// The minimum period of blocks on which justifications will be +/// imported and generated. +const GRANDPA_JUSTIFICATION_PERIOD: u32 = 512; + /// Fetch the nonce of the given `account` from the chain state. /// /// Note: Should only be used for tests. @@ -58,7 +65,7 @@ pub fn fetch_nonce(client: &FullClient, account: sp_core::sr25519::Pair) -> u32 let best_hash = client.chain_info().best_hash; client .runtime_api() - .account_nonce(&generic::BlockId::Hash(best_hash), account.public().into()) + .account_nonce(best_hash, account.public().into()) .expect("Fetching account nonce works; qed") } @@ -96,7 +103,7 @@ pub fn create_extrinsic( )), frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), - pallet_asset_tx_payment::ChargeAssetTxPayment::::from( + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::::from( tip, None, ), ); @@ -147,6 +154,7 @@ pub fn new_partial( ), grandpa::SharedVoterState, Option, + Arc, ), >, ServiceError, @@ -162,12 +170,7 @@ pub fn new_partial( }) .transpose()?; - let executor = NativeElseWasmExecutor::::new( - config.wasm_method, - config.default_heap_pages, - config.max_runtime_instances, - config.runtime_cache_size, - ); + let executor = sc_service::new_native_or_wasm_executor(&config); let (client, backend, keystore_container, task_manager) = sc_service::new_full_parts::( @@ -194,6 +197,7 @@ pub fn new_partial( let (grandpa_block_import, grandpa_link) = grandpa::block_import( client.clone(), + GRANDPA_JUSTIFICATION_PERIOD, &(client.clone() as Arc<_>), select_chain.clone(), telemetry.as_ref().map(|x| x.handle()), @@ -207,35 +211,44 @@ pub fn new_partial( )?; let slot_duration = babe_link.config().slot_duration(); - let import_queue = sc_consensus_babe::import_queue( - babe_link.clone(), - block_import.clone(), - Some(Box::new(justification_import)), - client.clone(), - select_chain.clone(), - move |_, ()| async move { - let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); + let (import_queue, babe_worker_handle) = + sc_consensus_babe::import_queue(sc_consensus_babe::ImportQueueParams { + link: babe_link.clone(), + block_import: block_import.clone(), + justification_import: Some(Box::new(justification_import)), + client: client.clone(), + select_chain: select_chain.clone(), + create_inherent_data_providers: move |_, ()| async move { + let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); - let slot = + let slot = sp_consensus_babe::inherents::InherentDataProvider::from_timestamp_and_slot_duration( *timestamp, slot_duration, ); - let uncles = - sp_authorship::InherentDataProvider::<::Header>::check_inherents(); - - Ok((slot, timestamp, uncles)) - }, - &task_manager.spawn_essential_handle(), - config.prometheus_registry(), - telemetry.as_ref().map(|x| x.handle()), - )?; + Ok((slot, timestamp)) + }, + spawner: &task_manager.spawn_essential_handle(), + registry: config.prometheus_registry(), + telemetry: telemetry.as_ref().map(|x| x.handle()), + offchain_tx_pool_factory: OffchainTransactionPoolFactory::new(transaction_pool.clone()), + })?; let import_setup = (block_import, grandpa_link, babe_link); + let statement_store = sc_statement_store::Store::new_shared( + &config.data_path, + Default::default(), + client.clone(), + keystore_container.local_keystore(), + config.prometheus_registry(), + &task_manager.spawn_handle(), + ) + .map_err(|e| ServiceError::Other(format!("Statement store error: {:?}", e)))?; + let (rpc_extensions_builder, rpc_setup) = { - let (_, grandpa_link, babe_link) = &import_setup; + let (_, grandpa_link, _) = &import_setup; let justification_stream = grandpa_link.justification_stream(); let shared_authority_set = grandpa_link.shared_authority_set().clone(); @@ -247,16 +260,14 @@ pub fn new_partial( Some(shared_authority_set.clone()), ); - let babe_config = babe_link.config().clone(); - let shared_epoch_changes = babe_link.epoch_changes().clone(); - let client = client.clone(); let pool = transaction_pool.clone(); let select_chain = select_chain.clone(); - let keystore = keystore_container.sync_keystore(); + let keystore = keystore_container.keystore(); let chain_spec = config.chain_spec.cloned_box(); let rpc_backend = backend.clone(); + let rpc_statement_store = statement_store.clone(); let rpc_extensions_builder = move |deny_unsafe, subscription_executor| { let deps = node_rpc::FullDeps { client: client.clone(), @@ -265,9 +276,8 @@ pub fn new_partial( chain_spec: chain_spec.cloned_box(), deny_unsafe, babe: node_rpc::BabeDeps { - babe_config: babe_config.clone(), - shared_epoch_changes: shared_epoch_changes.clone(), keystore: keystore.clone(), + babe_worker_handle: babe_worker_handle.clone(), }, grandpa: node_rpc::GrandpaDeps { shared_voter_state: shared_voter_state.clone(), @@ -276,9 +286,11 @@ pub fn new_partial( subscription_executor, finality_provider: finality_proof_provider.clone(), }, + statement_store: rpc_statement_store.clone(), + backend: rpc_backend.clone(), }; - node_rpc::create_full(deps, rpc_backend.clone()).map_err(Into::into) + node_rpc::create_full(deps).map_err(Into::into) }; (rpc_extensions_builder, shared_voter_state2) @@ -292,7 +304,7 @@ pub fn new_partial( select_chain, import_queue, transaction_pool, - other: (rpc_extensions_builder, import_setup, rpc_setup, telemetry), + other: (rpc_extensions_builder, import_setup, rpc_setup, telemetry, statement_store), }) } @@ -304,6 +316,8 @@ pub struct NewFullBase { pub client: Arc, /// The networking service of the node. pub network: Arc::Hash>>, + /// The syncing service of the node. + pub sync: Arc>, /// The transaction pool of the node. pub transaction_pool: Arc, /// The rpc handlers of the node. @@ -312,7 +326,7 @@ pub struct NewFullBase { /// Creates a full service from the configuration. pub fn new_full_base( - mut config: Configuration, + config: Configuration, disable_hardware_benchmarks: bool, with_startup_data: impl FnOnce( &sc_consensus_babe::BabeBlockImport, @@ -334,46 +348,49 @@ pub fn new_full_base( keystore_container, select_chain, transaction_pool, - other: (rpc_builder, import_setup, rpc_setup, mut telemetry), + other: (rpc_builder, import_setup, rpc_setup, mut telemetry, statement_store), } = new_partial(&config)?; let shared_voter_state = rpc_setup; let auth_disc_publish_non_global_ips = config.network.allow_non_globals_in_dht; + let mut net_config = sc_network::config::FullNetworkConfiguration::new(&config.network); + let grandpa_protocol_name = grandpa::protocol_standard_name( &client.block_hash(0).ok().flatten().expect("Genesis block exists; qed"), &config.chain_spec, ); + net_config.add_notification_protocol(grandpa::grandpa_peers_set_config( + grandpa_protocol_name.clone(), + )); + + let statement_handler_proto = sc_network_statement::StatementHandlerPrototype::new( + client + .block_hash(0u32.into()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + config.chain_spec.fork_id(), + ); + net_config.add_notification_protocol(statement_handler_proto.set_config()); - config - .network - .extra_sets - .push(grandpa::grandpa_peers_set_config(grandpa_protocol_name.clone())); let warp_sync = Arc::new(grandpa::warp_proof::NetworkProvider::new( backend.clone(), import_setup.1.shared_authority_set().clone(), Vec::default(), )); - let (network, system_rpc_tx, tx_handler_controller, network_starter) = + 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: Some(warp_sync), + warp_sync_params: Some(WarpSyncParams::WithProvider(warp_sync)), })?; - if config.offchain_worker.enabled { - sc_service::build_offchain_workers( - &config, - task_manager.spawn_handle(), - client.clone(), - network.clone(), - ); - } - let role = config.role.clone(); let force_authoring = config.force_authoring; let backoff_authoring_blocks = @@ -381,18 +398,20 @@ pub fn new_full_base( let name = config.network.node_name.clone(); let enable_grandpa = !config.disable_grandpa; let prometheus_registry = config.prometheus_registry().cloned(); + let enable_offchain_worker = config.offchain_worker.enabled; let rpc_handlers = sc_service::spawn_tasks(sc_service::SpawnTasksParams { config, - backend, + backend: backend.clone(), client: client.clone(), - keystore: keystore_container.sync_keystore(), + keystore: keystore_container.keystore(), network: network.clone(), rpc_builder: Box::new(rpc_builder), transaction_pool: transaction_pool.clone(), task_manager: &mut task_manager, system_rpc_tx, tx_handler_controller, + sync_service: sync_service.clone(), telemetry: telemetry.as_mut(), })?; @@ -430,21 +449,16 @@ pub fn new_full_base( let client_clone = client.clone(); let slot_duration = babe_link.config().slot_duration(); let babe_config = sc_consensus_babe::BabeParams { - keystore: keystore_container.sync_keystore(), + keystore: keystore_container.keystore(), client: client.clone(), select_chain, env: proposer, block_import, - sync_oracle: network.clone(), - justification_sync_link: network.clone(), + sync_oracle: sync_service.clone(), + justification_sync_link: sync_service.clone(), create_inherent_data_providers: move |parent, ()| { let client_clone = client_clone.clone(); async move { - let uncles = sc_consensus_uncles::create_uncles_inherent_data_provider( - &*client_clone, - parent, - )?; - let timestamp = sp_timestamp::InherentDataProvider::from_system_time(); let slot = @@ -459,7 +473,7 @@ pub fn new_full_base( &parent, )?; - Ok((slot, timestamp, uncles, storage_proof)) + Ok((slot, timestamp, storage_proof)) } }, force_authoring, @@ -511,17 +525,16 @@ pub fn new_full_base( // 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.sync_keystore()) } else { None }; + let keystore = if role.is_authority() { Some(keystore_container.keystore()) } else { None }; - let config = grandpa::Config { + let grandpa_config = grandpa::Config { // FIXME #1578 make this available through chainspec gossip_duration: std::time::Duration::from_millis(333), - justification_period: 512, + justification_generation_period: GRANDPA_JUSTIFICATION_PERIOD, name: Some(name), observer_enabled: false, keystore, - local_role: role, + local_role: role.clone(), telemetry: telemetry.as_ref().map(|x| x.handle()), protocol_name: grandpa_protocol_name, }; @@ -534,13 +547,15 @@ pub fn new_full_base( // been tested extensively yet and having most nodes in a network run it // could lead to finality stalls. let grandpa_config = grandpa::GrandpaParams { - config, + config: grandpa_config, link: grandpa_link, network: network.clone(), + sync: Arc::new(sync_service.clone()), telemetry: telemetry.as_ref().map(|x| x.handle()), voting_rule: grandpa::VotingRulesBuilder::default().build(), - prometheus_registry, + prometheus_registry: prometheus_registry.clone(), shared_voter_state, + offchain_tx_pool_factory: OffchainTransactionPoolFactory::new(transaction_pool.clone()), }; // the GRANDPA voter task is considered infallible, i.e. @@ -552,8 +567,58 @@ pub fn new_full_base( ); } + // Spawn statement protocol worker + let statement_protocol_executor = { + let spawn_handle = task_manager.spawn_handle(); + Box::new(move |fut| { + spawn_handle.spawn("network-statement-validator", Some("networking"), fut); + }) + }; + let statement_handler = statement_handler_proto.build( + network.clone(), + sync_service.clone(), + statement_store.clone(), + prometheus_registry.as_ref(), + statement_protocol_executor, + )?; + task_manager.spawn_handle().spawn( + "network-statement-handler", + Some("networking"), + statement_handler.run(), + ); + + if enable_offchain_worker { + task_manager.spawn_handle().spawn( + "offchain-workers-runner", + "offchain-work", + sc_offchain::OffchainWorkers::new(sc_offchain::OffchainWorkerOptions { + runtime_api_provider: client.clone(), + keystore: Some(keystore_container.keystore()), + offchain_db: backend.offchain_storage(), + transaction_pool: Some(OffchainTransactionPoolFactory::new( + transaction_pool.clone(), + )), + network_provider: network.clone(), + is_validator: role.is_authority(), + enable_http_requests: true, + custom_extensions: move |_| { + vec![Box::new(statement_store.clone().as_statement_store_ext()) as Box<_>] + }, + }) + .run(client.clone(), task_manager.spawn_handle()) + .boxed(), + ); + } + network_starter.start_network(); - Ok(NewFullBase { task_manager, client, network, transaction_pool, rpc_handlers }) + Ok(NewFullBase { + task_manager, + client, + network, + sync: sync_service, + transaction_pool, + rpc_handlers, + }) } /// Builds a new service for a full client. @@ -566,7 +631,8 @@ pub fn new_full(config: Configuration, cli: Cli) -> Result::babe_seal(signature); + let signature = keystore + .sr25519_sign(sp_consensus_babe::AuthorityId::ID, alice.as_ref(), &to_sign) + .unwrap() + .unwrap(); + let item = ::babe_seal(signature.into()); slot += 1; let mut params = BlockImportParams::new(BlockOrigin::File, new_header); @@ -752,7 +814,7 @@ mod tests { ); params.fork_choice = Some(ForkChoiceStrategy::LongestChain); - futures::executor::block_on(block_import.import_block(params, Default::default())) + futures::executor::block_on(block_import.import_block(params)) .expect("error importing test block"); }, |service, _| { @@ -760,14 +822,14 @@ mod tests { let to: Address = AccountPublic::from(bob.public()).into_account().into(); let from: Address = AccountPublic::from(charlie.public()).into_account().into(); let genesis_hash = service.client().block_hash(0).unwrap().unwrap(); - let best_block_id = BlockId::number(service.client().chain_info().best_number); + let best_hash = service.client().chain_info().best_hash; let (spec_version, transaction_version) = { - let version = service.client().runtime_version_at(&best_block_id).unwrap(); + let version = service.client().runtime_version_at(best_hash).unwrap(); (version.spec_version, version.transaction_version) }; let signer = charlie.clone(); - let function = RuntimeCall::Balances(BalancesCall::transfer { + let function = RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: to.into(), value: amount, }); @@ -779,7 +841,8 @@ mod tests { let check_era = frame_system::CheckEra::from(Era::Immortal); let check_nonce = frame_system::CheckNonce::from(index); let check_weight = frame_system::CheckWeight::new(); - let tx_payment = pallet_asset_tx_payment::ChargeAssetTxPayment::from(0, None); + let tx_payment = + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(0, None); let extra = ( check_non_zero_sender, check_spec_version, @@ -812,12 +875,13 @@ mod tests { sc_service_test::consensus( crate::chain_spec::tests::integration_test_config_with_two_authorities(), |config| { - let NewFullBase { task_manager, client, network, transaction_pool, .. } = + let NewFullBase { task_manager, client, network, sync, transaction_pool, .. } = new_full_base(config, false, |_, _| ())?; Ok(sc_service_test::TestNetComponents::new( task_manager, client, network, + sync, transaction_pool, )) }, diff --git a/bin/node/cli/tests/benchmark_block_works.rs b/bin/node/cli/tests/benchmark_block_works.rs index 65cb474ea88d..19a074081c55 100644 --- a/bin/node/cli/tests/benchmark_block_works.rs +++ b/bin/node/cli/tests/benchmark_block_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -23,7 +23,7 @@ use assert_cmd::cargo::cargo_bin; use std::process::Command; use tempfile::tempdir; -pub mod common; +use substrate_cli_test_utils as common; /// `benchmark block` works for the dev runtime using the wasm executor. #[tokio::test] @@ -39,7 +39,7 @@ async fn benchmark_block_works() { .arg(base_dir.path()) .args(["--from", "1", "--to", "1"]) .args(["--repeat", "1"]) - .args(["--execution", "wasm", "--wasm-execution", "compiled"]) + .args(["--wasm-execution=compiled"]) .status() .unwrap(); diff --git a/bin/node/cli/tests/benchmark_extrinsic_works.rs b/bin/node/cli/tests/benchmark_extrinsic_works.rs index 69800ad3c6c1..8e6363b7d066 100644 --- a/bin/node/cli/tests/benchmark_extrinsic_works.rs +++ b/bin/node/cli/tests/benchmark_extrinsic_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -39,6 +39,7 @@ fn benchmark_extrinsic(pallet: &str, extrinsic: &str) { .args(&["--pallet", pallet, "--extrinsic", extrinsic]) // Run with low repeats for faster execution. .args(["--warmup=10", "--repeat=10", "--max-ext-per-block=10"]) + .args(["--wasm-execution=compiled"]) .status() .unwrap(); diff --git a/bin/node/cli/tests/benchmark_machine_works.rs b/bin/node/cli/tests/benchmark_machine_works.rs index 193e6b701ec9..2cdadb64603e 100644 --- a/bin/node/cli/tests/benchmark_machine_works.rs +++ b/bin/node/cli/tests/benchmark_machine_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/node/cli/tests/benchmark_overhead_works.rs b/bin/node/cli/tests/benchmark_overhead_works.rs index 44dcebfbc0c3..3273e6f86811 100644 --- a/bin/node/cli/tests/benchmark_overhead_works.rs +++ b/bin/node/cli/tests/benchmark_overhead_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -36,6 +36,7 @@ fn benchmark_overhead_works() { .args(["--warmup", "10", "--repeat", "10"]) .args(["--add", "100", "--mul", "1.2", "--metric", "p75"]) .args(["--max-ext-per-block", "10"]) + .args(["--wasm-execution=compiled"]) .status() .unwrap(); assert!(status.success()); diff --git a/bin/node/cli/tests/benchmark_pallet_works.rs b/bin/node/cli/tests/benchmark_pallet_works.rs index bf29c0e308bc..9c3e7e299539 100644 --- a/bin/node/cli/tests/benchmark_pallet_works.rs +++ b/bin/node/cli/tests/benchmark_pallet_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,11 +16,11 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#![cfg(feature = "runtime-benchmarks")] + use assert_cmd::cargo::cargo_bin; use std::process::Command; -pub mod common; - /// `benchmark pallet` works for the different combinations of `steps` and `repeat`. #[test] fn benchmark_pallet_works() { @@ -34,16 +34,20 @@ fn benchmark_pallet_works() { } fn benchmark_pallet(steps: u32, repeat: u32, should_work: bool) { - let output = Command::new(cargo_bin("substrate")) + let status = Command::new(cargo_bin("substrate")) .args(["benchmark", "pallet", "--dev"]) // Use the `addition` benchmark since is the fastest. .args(["--pallet", "frame-benchmarking", "--extrinsic", "addition"]) .args(["--steps", &format!("{}", steps), "--repeat", &format!("{}", repeat)]) - .output() + .args([ + "--wasm-execution=compiled", + "--no-storage-info", + "--no-median-slopes", + "--no-min-squares", + "--heap-pages=4096", + ]) + .status() .unwrap(); - if output.status.success() != should_work { - let log = String::from_utf8_lossy(&output.stderr).to_string(); - panic!("Test failed:\n{}", log); - } + assert_eq!(status.success(), should_work); } diff --git a/bin/node/cli/tests/benchmark_storage_works.rs b/bin/node/cli/tests/benchmark_storage_works.rs index 82d1c943ae7a..953c07ca7f0d 100644 --- a/bin/node/cli/tests/benchmark_storage_works.rs +++ b/bin/node/cli/tests/benchmark_storage_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,6 +16,8 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +#![cfg(feature = "runtime-benchmarks")] + use assert_cmd::cargo::cargo_bin; use std::{ path::Path, diff --git a/bin/node/cli/tests/build_spec_works.rs b/bin/node/cli/tests/build_spec_works.rs index aecabed60c84..dc5d36184f0c 100644 --- a/bin/node/cli/tests/build_spec_works.rs +++ b/bin/node/cli/tests/build_spec_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/node/cli/tests/check_block_works.rs b/bin/node/cli/tests/check_block_works.rs index d4afc530bbcb..67bc5e6031ea 100644 --- a/bin/node/cli/tests/check_block_works.rs +++ b/bin/node/cli/tests/check_block_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -22,7 +22,7 @@ use assert_cmd::cargo::cargo_bin; use std::process::Command; use tempfile::tempdir; -pub mod common; +use substrate_cli_test_utils as common; #[tokio::test] async fn check_block_works() { diff --git a/bin/node/cli/tests/common.rs b/bin/node/cli/tests/common.rs deleted file mode 100644 index c8f9e2b3d3c8..000000000000 --- a/bin/node/cli/tests/common.rs +++ /dev/null @@ -1,180 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. -// 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 . - -#![cfg(unix)] - -use assert_cmd::cargo::cargo_bin; -use nix::{ - sys::signal::{kill, Signal::SIGINT}, - unistd::Pid, -}; -use node_primitives::{Hash, Header}; -use std::{ - io::{BufRead, BufReader, Read}, - ops::{Deref, DerefMut}, - path::Path, - process::{self, Child, Command, ExitStatus}, - time::Duration, -}; -use tokio::time::timeout; - -/// Wait for the given `child` the given number of `secs`. -/// -/// Returns the `Some(exit status)` or `None` if the process did not finish in the given time. -pub fn wait_for(child: &mut Child, secs: u64) -> Result { - let result = wait_timeout::ChildExt::wait_timeout(child, Duration::from_secs(5.min(secs))) - .map_err(|_| ())?; - if let Some(exit_status) = result { - Ok(exit_status) - } else { - if secs > 5 { - eprintln!("Child process taking over 5 seconds to exit gracefully"); - let result = wait_timeout::ChildExt::wait_timeout(child, Duration::from_secs(secs - 5)) - .map_err(|_| ())?; - if let Some(exit_status) = result { - return Ok(exit_status) - } - } - eprintln!("Took too long to exit (> {} seconds). Killing...", secs); - let _ = child.kill(); - child.wait().unwrap(); - Err(()) - } -} - -/// Wait for at least n blocks to be finalized within a specified time. -pub async fn wait_n_finalized_blocks( - n: usize, - timeout_secs: u64, - url: &str, -) -> Result<(), tokio::time::error::Elapsed> { - timeout(Duration::from_secs(timeout_secs), wait_n_finalized_blocks_from(n, url)).await -} - -/// Wait for at least n blocks to be finalized from a specified node -pub async fn wait_n_finalized_blocks_from(n: usize, url: &str) { - use substrate_rpc_client::{ws_client, ChainApi}; - - let mut built_blocks = std::collections::HashSet::new(); - let mut interval = tokio::time::interval(Duration::from_secs(2)); - let rpc = ws_client(url).await.unwrap(); - - loop { - if let Ok(block) = ChainApi::<(), Hash, Header, ()>::finalized_head(&rpc).await { - built_blocks.insert(block); - if built_blocks.len() > n { - break - } - }; - interval.tick().await; - } -} - -/// Run the node for a while (3 blocks) -pub async fn run_node_for_a_while(base_path: &Path, args: &[&str]) { - let mut cmd = Command::new(cargo_bin("substrate")) - .stdout(process::Stdio::piped()) - .stderr(process::Stdio::piped()) - .args(args) - .arg("-d") - .arg(base_path) - .spawn() - .unwrap(); - - let stderr = cmd.stderr.take().unwrap(); - - let mut child = KillChildOnDrop(cmd); - - let (ws_url, _) = find_ws_url_from_output(stderr); - - // Let it produce some blocks. - let _ = wait_n_finalized_blocks(3, 30, &ws_url).await; - - assert!(child.try_wait().unwrap().is_none(), "the process should still be running"); - - // Stop the process - kill(Pid::from_raw(child.id().try_into().unwrap()), SIGINT).unwrap(); - assert!(wait_for(&mut child, 40).map(|x| x.success()).unwrap()); -} - -/// Run the node asserting that it fails with an error -pub fn run_node_assert_fail(base_path: &Path, args: &[&str]) { - let mut cmd = Command::new(cargo_bin("substrate")); - - let mut child = KillChildOnDrop(cmd.args(args).arg("-d").arg(base_path).spawn().unwrap()); - - // Let it produce some blocks, but it should die within 10 seconds. - assert_ne!( - wait_timeout::ChildExt::wait_timeout(&mut *child, Duration::from_secs(10)).unwrap(), - None, - "the process should not be running anymore" - ); -} - -pub struct KillChildOnDrop(pub Child); - -impl Drop for KillChildOnDrop { - fn drop(&mut self) { - let _ = self.0.kill(); - } -} - -impl Deref for KillChildOnDrop { - type Target = Child; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for KillChildOnDrop { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -/// Read the WS address from the output. -/// -/// This is hack to get the actual binded sockaddr because -/// substrate assigns a random port if the specified port was already binded. -pub fn find_ws_url_from_output(read: impl Read + Send) -> (String, String) { - let mut data = String::new(); - - let ws_url = BufReader::new(read) - .lines() - .find_map(|line| { - let line = - line.expect("failed to obtain next line from stdout for WS address discovery"); - data.push_str(&line); - data.push_str("\n"); - - // does the line contain our port (we expect this specific output from substrate). - let sock_addr = match line.split_once("Running JSON-RPC WS server: addr=") { - None => return None, - Some((_, after)) => after.split_once(",").unwrap().0, - }; - - Some(format!("ws://{}", sock_addr)) - }) - .unwrap_or_else(|| { - eprintln!("Observed node output:\n{}", data); - panic!("We should get a WebSocket address") - }); - - (ws_url, data) -} diff --git a/bin/node/cli/tests/export_import_flow.rs b/bin/node/cli/tests/export_import_flow.rs index 750b4f7acc12..b5785f99ea81 100644 --- a/bin/node/cli/tests/export_import_flow.rs +++ b/bin/node/cli/tests/export_import_flow.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -23,7 +23,7 @@ use regex::Regex; use std::{fs, path::PathBuf, process::Command}; use tempfile::{tempdir, TempDir}; -pub mod common; +use substrate_cli_test_utils as common; fn contains_error(logged_output: &str) -> bool { logged_output.contains("Error") diff --git a/bin/node/cli/tests/inspect_works.rs b/bin/node/cli/tests/inspect_works.rs index 849fb913a18d..3695c318a8df 100644 --- a/bin/node/cli/tests/inspect_works.rs +++ b/bin/node/cli/tests/inspect_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -22,7 +22,7 @@ use assert_cmd::cargo::cargo_bin; use std::process::Command; use tempfile::tempdir; -pub mod common; +use substrate_cli_test_utils as common; #[tokio::test] async fn inspect_works() { diff --git a/bin/node/cli/tests/purge_chain_works.rs b/bin/node/cli/tests/purge_chain_works.rs index 811762a714a9..77421f865a0d 100644 --- a/bin/node/cli/tests/purge_chain_works.rs +++ b/bin/node/cli/tests/purge_chain_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -20,7 +20,7 @@ use assert_cmd::cargo::cargo_bin; use std::process::Command; use tempfile::tempdir; -pub mod common; +use substrate_cli_test_utils as common; #[tokio::test] #[cfg(unix)] diff --git a/bin/node/cli/tests/remember_state_pruning_works.rs b/bin/node/cli/tests/remember_state_pruning_works.rs index 5b8e34cc7a00..e28b2ef55ef6 100644 --- a/bin/node/cli/tests/remember_state_pruning_works.rs +++ b/bin/node/cli/tests/remember_state_pruning_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,7 +18,7 @@ use tempfile::tempdir; -pub mod common; +use substrate_cli_test_utils as common; #[tokio::test] #[cfg(unix)] diff --git a/bin/node/cli/tests/running_the_node_and_interrupt.rs b/bin/node/cli/tests/running_the_node_and_interrupt.rs index 6d4a4b40425c..1308067da025 100644 --- a/bin/node/cli/tests/running_the_node_and_interrupt.rs +++ b/bin/node/cli/tests/running_the_node_and_interrupt.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,94 +18,72 @@ #![cfg(unix)] use assert_cmd::cargo::cargo_bin; -use nix::{ - sys::signal::{ - kill, - Signal::{self, SIGINT, SIGTERM}, - }, - unistd::Pid, +use nix::sys::signal::Signal::{self, SIGINT, SIGTERM}; +use std::{ + process::{self, Command}, + time::Duration, }; -use std::process::{self, Child, Command}; use tempfile::tempdir; -pub mod common; +use substrate_cli_test_utils as common; #[tokio::test] async fn running_the_node_works_and_can_be_interrupted() { - async fn run_command_and_kill(signal: Signal) { - let base_path = tempdir().expect("could not create a temp dir"); - let mut cmd = common::KillChildOnDrop( - Command::new(cargo_bin("substrate")) - .stdout(process::Stdio::piped()) - .stderr(process::Stdio::piped()) - .args(&["--dev", "-d"]) - .arg(base_path.path()) - .arg("--db=paritydb") - .arg("--no-hardware-benchmarks") - .spawn() - .unwrap(), - ); - - let stderr = cmd.stderr.take().unwrap(); - - let (ws_url, _) = common::find_ws_url_from_output(stderr); - - common::wait_n_finalized_blocks(3, 30, &ws_url) - .await - .expect("Blocks are produced in time"); - assert!(cmd.try_wait().unwrap().is_none(), "the process should still be running"); - kill(Pid::from_raw(cmd.id().try_into().unwrap()), signal).unwrap(); - assert_eq!( - common::wait_for(&mut cmd, 30).map(|x| x.success()), - Ok(true), - "the process must exit gracefully after signal {}", - signal, - ); - // Check if the database was closed gracefully. If it was not, - // there may exist a ref cycle that prevents the Client from being dropped properly. - // - // parity-db only writes the stats file on clean shutdown. - let stats_file = base_path.path().join("chains/dev/paritydb/full/stats.txt"); - assert!(std::path::Path::exists(&stats_file)); - } - - run_command_and_kill(SIGINT).await; - run_command_and_kill(SIGTERM).await; + common::run_with_timeout(Duration::from_secs(60 * 10), async move { + async fn run_command_and_kill(signal: Signal) { + let base_path = tempdir().expect("could not create a temp dir"); + let mut cmd = common::KillChildOnDrop( + Command::new(cargo_bin("substrate")) + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()) + .args(&["--dev", "-d"]) + .arg(base_path.path()) + .arg("--db=paritydb") + .arg("--no-hardware-benchmarks") + .spawn() + .unwrap(), + ); + + let stderr = cmd.stderr.take().unwrap(); + + let ws_url = common::extract_info_from_output(stderr).0.ws_url; + + common::wait_n_finalized_blocks(3, &ws_url).await; + + cmd.assert_still_running(); + + cmd.stop_with_signal(signal); + + // Check if the database was closed gracefully. If it was not, + // there may exist a ref cycle that prevents the Client from being dropped properly. + // + // parity-db only writes the stats file on clean shutdown. + let stats_file = base_path.path().join("chains/dev/paritydb/full/stats.txt"); + assert!(std::path::Path::exists(&stats_file)); + } + + run_command_and_kill(SIGINT).await; + run_command_and_kill(SIGTERM).await; + }) + .await; } #[tokio::test] async fn running_two_nodes_with_the_same_ws_port_should_work() { - fn start_node() -> Child { - Command::new(cargo_bin("substrate")) - .stdout(process::Stdio::piped()) - .stderr(process::Stdio::piped()) - .args(&["--dev", "--tmp", "--ws-port=45789", "--no-hardware-benchmarks"]) - .spawn() - .unwrap() - } - - let mut first_node = common::KillChildOnDrop(start_node()); - let mut second_node = common::KillChildOnDrop(start_node()); - - let stderr = first_node.stderr.take().unwrap(); - let (ws_url, _) = common::find_ws_url_from_output(stderr); - - common::wait_n_finalized_blocks(3, 30, &ws_url).await.unwrap(); - - assert!(first_node.try_wait().unwrap().is_none(), "The first node should still be running"); - assert!(second_node.try_wait().unwrap().is_none(), "The second node should still be running"); - - kill(Pid::from_raw(first_node.id().try_into().unwrap()), SIGINT).unwrap(); - kill(Pid::from_raw(second_node.id().try_into().unwrap()), SIGINT).unwrap(); - - assert_eq!( - common::wait_for(&mut first_node, 30).map(|x| x.success()), - Ok(true), - "The first node must exit gracefully", - ); - assert_eq!( - common::wait_for(&mut second_node, 30).map(|x| x.success()), - Ok(true), - "The second node must exit gracefully", - ); + common::run_with_timeout(Duration::from_secs(60 * 10), async move { + let mut first_node = common::KillChildOnDrop(common::start_node()); + let mut second_node = common::KillChildOnDrop(common::start_node()); + + let stderr = first_node.stderr.take().unwrap(); + let ws_url = common::extract_info_from_output(stderr).0.ws_url; + + common::wait_n_finalized_blocks(3, &ws_url).await; + + first_node.assert_still_running(); + second_node.assert_still_running(); + + first_node.stop(); + second_node.stop(); + }) + .await; } diff --git a/bin/node/cli/tests/telemetry.rs b/bin/node/cli/tests/telemetry.rs index 98cf0b3af32b..176d2e81ad06 100644 --- a/bin/node/cli/tests/telemetry.rs +++ b/bin/node/cli/tests/telemetry.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -17,78 +17,76 @@ // along with this program. If not, see . use assert_cmd::cargo::cargo_bin; -use nix::{ - sys::signal::{kill, Signal::SIGINT}, - unistd::Pid, -}; -use std::process; +use std::{process, time::Duration}; -pub mod common; +use crate::common::KillChildOnDrop; + +use substrate_cli_test_utils as common; pub mod websocket_server; #[tokio::test] async fn telemetry_works() { - let config = websocket_server::Config { - capacity: 1, - max_frame_size: 1048 * 1024, - send_buffer_len: 32, - bind_address: "127.0.0.1:0".parse().unwrap(), - }; - let mut server = websocket_server::WsServer::new(config).await.unwrap(); - - let addr = server.local_addr().unwrap(); - - let server_task = tokio::spawn(async move { - loop { - use websocket_server::Event; - match server.next_event().await { - // New connection on the listener. - Event::ConnectionOpen { address } => { - println!("New connection from {:?}", address); - server.accept(); - }, - - // Received a message from a connection. - Event::BinaryFrame { message, .. } => { - let json: serde_json::Value = serde_json::from_slice(&message).unwrap(); - let object = - json.as_object().unwrap().get("payload").unwrap().as_object().unwrap(); - if matches!(object.get("best"), Some(serde_json::Value::String(_))) { - break - } - }, - - Event::TextFrame { .. } => panic!("Got a TextFrame over the socket, this is a bug"), - - // Connection has been closed. - Event::ConnectionError { .. } => {}, + common::run_with_timeout(Duration::from_secs(60 * 10), async move { + let config = websocket_server::Config { + capacity: 1, + max_frame_size: 1048 * 1024, + send_buffer_len: 32, + bind_address: "127.0.0.1:0".parse().unwrap(), + }; + let mut server = websocket_server::WsServer::new(config).await.unwrap(); + + let addr = server.local_addr().unwrap(); + + let server_task = tokio::spawn(async move { + loop { + use websocket_server::Event; + match server.next_event().await { + // New connection on the listener. + Event::ConnectionOpen { address } => { + println!("New connection from {:?}", address); + server.accept(); + }, + + // Received a message from a connection. + Event::BinaryFrame { message, .. } => { + let json: serde_json::Value = serde_json::from_slice(&message).unwrap(); + let object = + json.as_object().unwrap().get("payload").unwrap().as_object().unwrap(); + if matches!(object.get("best"), Some(serde_json::Value::String(_))) { + break + } + }, + + Event::TextFrame { .. } => { + panic!("Got a TextFrame over the socket, this is a bug") + }, + + // Connection has been closed. + Event::ConnectionError { .. } => {}, + } } - } - }); - - let mut substrate = process::Command::new(cargo_bin("substrate")); - - let mut substrate = substrate - .args(&["--dev", "--tmp", "--telemetry-url"]) - .arg(format!("ws://{} 10", addr)) - .arg("--no-hardware-benchmarks") - .stdout(process::Stdio::piped()) - .stderr(process::Stdio::piped()) - .stdin(process::Stdio::null()) - .spawn() - .unwrap(); - - server_task.await.expect("server task panicked"); - - assert!(substrate.try_wait().unwrap().is_none(), "the process should still be running"); - - // Stop the process - kill(Pid::from_raw(substrate.id().try_into().unwrap()), SIGINT).unwrap(); - assert!(common::wait_for(&mut substrate, 40).map(|x| x.success()).unwrap_or_default()); - - let output = substrate.wait_with_output().unwrap(); - - println!("{}", String::from_utf8(output.stdout).unwrap()); - eprintln!("{}", String::from_utf8(output.stderr).unwrap()); - assert!(output.status.success()); + }); + + let mut substrate = process::Command::new(cargo_bin("substrate")); + + let mut substrate = KillChildOnDrop( + substrate + .args(&["--dev", "--tmp", "--telemetry-url"]) + .arg(format!("ws://{} 10", addr)) + .arg("--no-hardware-benchmarks") + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()) + .stdin(process::Stdio::null()) + .spawn() + .unwrap(), + ); + + server_task.await.expect("server task panicked"); + + substrate.assert_still_running(); + + // Stop the process + substrate.stop(); + }) + .await; } diff --git a/bin/node/cli/tests/temp_base_path_works.rs b/bin/node/cli/tests/temp_base_path_works.rs index 98422a21f530..fdcd9e23dde5 100644 --- a/bin/node/cli/tests/temp_base_path_works.rs +++ b/bin/node/cli/tests/temp_base_path_works.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -19,45 +19,44 @@ #![cfg(unix)] use assert_cmd::cargo::cargo_bin; -use nix::{ - sys::signal::{kill, Signal::SIGINT}, - unistd::Pid, -}; -use regex::Regex; use std::{ - io::Read, - path::PathBuf, process::{Command, Stdio}, + time::Duration, }; -pub mod common; +use substrate_cli_test_utils as common; -#[tokio::test] +#[allow(dead_code)] +// Apparently `#[ignore]` doesn't actually work to disable this one. +//#[tokio::test] async fn temp_base_path_works() { - let mut cmd = Command::new(cargo_bin("substrate")); - let mut child = common::KillChildOnDrop( - cmd.args(&["--dev", "--tmp", "--no-hardware-benchmarks"]) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(), - ); - - let mut stderr = child.stderr.take().unwrap(); - let (ws_url, mut data) = common::find_ws_url_from_output(&mut stderr); - - // Let it produce some blocks. - common::wait_n_finalized_blocks(3, 30, &ws_url).await.unwrap(); - assert!(child.try_wait().unwrap().is_none(), "the process should still be running"); - - // Stop the process - kill(Pid::from_raw(child.id().try_into().unwrap()), SIGINT).unwrap(); - assert!(common::wait_for(&mut child, 40).map(|x| x.success()).unwrap_or_default()); - - // Ensure the database has been deleted - stderr.read_to_string(&mut data).unwrap(); - let re = Regex::new(r"Database: .+ at (\S+)").unwrap(); - let db_path = PathBuf::from(re.captures(data.as_str()).unwrap().get(1).unwrap().as_str()); - - assert!(!db_path.exists()); + common::run_with_timeout(Duration::from_secs(60 * 10), async move { + let mut cmd = Command::new(cargo_bin("substrate")); + let mut child = common::KillChildOnDrop( + cmd.args(&["--dev", "--tmp", "--no-hardware-benchmarks"]) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(), + ); + + let mut stderr = child.stderr.take().unwrap(); + let node_info = common::extract_info_from_output(&mut stderr).0; + + // Let it produce some blocks. + common::wait_n_finalized_blocks(3, &node_info.ws_url).await; + + // Ensure the db path exists while the node is running + assert!(node_info.db_path.exists()); + + child.assert_still_running(); + + // Stop the process + child.stop(); + + if node_info.db_path.exists() { + panic!("Database path `{}` wasn't deleted!", node_info.db_path.display()); + } + }) + .await; } diff --git a/bin/node/cli/tests/version.rs b/bin/node/cli/tests/version.rs index f5a6f3a53d59..e239277c9b0e 100644 --- a/bin/node/cli/tests/version.rs +++ b/bin/node/cli/tests/version.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/node/cli/tests/websocket_server.rs b/bin/node/cli/tests/websocket_server.rs index 1e7450995230..432a4871cd37 100644 --- a/bin/node/cli/tests/websocket_server.rs +++ b/bin/node/cli/tests/websocket_server.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/node/executor/Cargo.toml b/bin/node/executor/Cargo.toml index edcc4df9c817..b24414482952 100644 --- a/bin/node/executor/Cargo.toml +++ b/bin/node/executor/Cargo.toml @@ -13,20 +13,21 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2" } -scale-info = { version = "2.1.1", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1" } +scale-info = { version = "2.5.0", features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", path = "../../../frame/benchmarking" } node-primitives = { version = "2.0.0", path = "../primitives" } kitchensink-runtime = { version = "3.0.0-dev", path = "../runtime" } sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } -sp-state-machine = { version = "0.13.0", path = "../../../primitives/state-machine" } -sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } -sp-trie = { version = "7.0.0", path = "../../../primitives/trie" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-keystore = { version = "0.27.0", path = "../../../primitives/keystore" } +sp-state-machine = { version = "0.28.0", path = "../../../primitives/state-machine" } +sp-tracing = { version = "10.0.0", path = "../../../primitives/tracing" } +sp-trie = { version = "22.0.0", path = "../../../primitives/trie" } +sp-statement-store = { version = "4.0.0-dev", path = "../../../primitives/statement-store" } [dev-dependencies] -criterion = "0.3.0" +criterion = "0.4.0" futures = "0.3.21" wat = "1.0" frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } @@ -35,16 +36,17 @@ node-testing = { version = "3.0.0-dev", path = "../testing" } pallet-balances = { version = "4.0.0-dev", path = "../../../frame/balances" } pallet-contracts = { version = "4.0.0-dev", path = "../../../frame/contracts" } pallet-im-online = { version = "4.0.0-dev", path = "../../../frame/im-online" } +pallet-glutton = { version = "4.0.0-dev", path = "../../../frame/glutton" } pallet-sudo = { version = "4.0.0-dev", path = "../../../frame/sudo" } pallet-timestamp = { version = "4.0.0-dev", path = "../../../frame/timestamp" } pallet-treasury = { version = "4.0.0-dev", path = "../../../frame/treasury" } pallet-transaction-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment" } -sp-application-crypto = { version = "7.0.0", path = "../../../primitives/application-crypto" } +sp-application-crypto = { version = "23.0.0", path = "../../../primitives/application-crypto" } pallet-root-testing = { version = "1.0.0-dev", path = "../../../frame/root-testing" } sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } -sp-externalities = { version = "0.13.0", path = "../../../primitives/externalities" } -sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-externalities = { version = "0.19.0", path = "../../../primitives/externalities" } +sp-keyring = { version = "24.0.0", path = "../../../primitives/keyring" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } [features] stress-test = [] diff --git a/bin/node/executor/benches/bench.rs b/bin/node/executor/benches/bench.rs index 4cbd95471b86..1c9c002492cf 100644 --- a/bin/node/executor/benches/bench.rs +++ b/bin/node/executor/benches/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,19 +19,19 @@ use codec::{Decode, Encode}; use criterion::{criterion_group, criterion_main, BatchSize, Criterion}; use frame_support::Hashable; use kitchensink_runtime::{ - constants::currency::*, Block, BuildStorage, CheckedExtrinsic, GenesisConfig, Header, - RuntimeCall, UncheckedExtrinsic, + constants::currency::*, Block, BuildStorage, CheckedExtrinsic, Header, RuntimeCall, + RuntimeGenesisConfig, UncheckedExtrinsic, }; use node_executor::ExecutorDispatch; use node_primitives::{BlockNumber, Hash}; use node_testing::keyring::*; use sc_executor::{ - Externalities, NativeElseWasmExecutor, RuntimeVersionOf, WasmExecutionMethod, + Externalities, NativeElseWasmExecutor, RuntimeVersionOf, WasmExecutionMethod, WasmExecutor, WasmtimeInstantiationStrategy, }; use sp_core::{ storage::well_known_keys, - traits::{CodeExecutor, RuntimeCode}, + traits::{CallContext, CodeExecutor, RuntimeCode}, }; use sp_runtime::traits::BlakeTwo256; use sp_state_machine::TestExternalities as CoreTestExternalities; @@ -67,7 +67,7 @@ fn sign(xt: CheckedExtrinsic) -> UncheckedExtrinsic { node_testing::keyring::sign(xt, SPEC_VERSION, TRANSACTION_VERSION, GENESIS_HASH) } -fn new_test_ext(genesis_config: &GenesisConfig) -> TestExternalities { +fn new_test_ext(genesis_config: &RuntimeGenesisConfig) -> TestExternalities { let mut test_ext = TestExternalities::new_with_code( compact_code_unwrap(), genesis_config.build_storage().unwrap(), @@ -112,20 +112,41 @@ fn construct_block( // execute the block to get the real header. executor - .call(ext, &runtime_code, "Core_initialize_block", &header.encode(), true) + .call( + ext, + &runtime_code, + "Core_initialize_block", + &header.encode(), + true, + CallContext::Offchain, + ) .0 .unwrap(); for i in extrinsics.iter() { executor - .call(ext, &runtime_code, "BlockBuilder_apply_extrinsic", &i.encode(), true) + .call( + ext, + &runtime_code, + "BlockBuilder_apply_extrinsic", + &i.encode(), + true, + CallContext::Offchain, + ) .0 .unwrap(); } let header = Header::decode( &mut &executor - .call(ext, &runtime_code, "BlockBuilder_finalize_block", &[0u8; 0], true) + .call( + ext, + &runtime_code, + "BlockBuilder_finalize_block", + &[0u8; 0], + true, + CallContext::Offchain, + ) .0 .unwrap()[..], ) @@ -136,7 +157,7 @@ fn construct_block( } fn test_blocks( - genesis_config: &GenesisConfig, + genesis_config: &RuntimeGenesisConfig, executor: &NativeElseWasmExecutor, ) -> Vec<(Vec, Hash)> { let mut test_ext = new_test_ext(genesis_config); @@ -146,7 +167,7 @@ fn test_blocks( }]; block1_extrinsics.extend((0..20).map(|i| CheckedExtrinsic { signed: Some((alice(), signed_extra(i, 0))), - function: RuntimeCall::Balances(pallet_balances::Call::transfer { + function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { dest: bob().into(), value: 1 * DOLLARS, }), @@ -161,7 +182,6 @@ fn bench_execute_block(c: &mut Criterion) { let mut group = c.benchmark_group("execute blocks"); let execution_methods = vec![ ExecutionMethod::Native, - ExecutionMethod::Wasm(WasmExecutionMethod::Interpreted), ExecutionMethod::Wasm(WasmExecutionMethod::Compiled { instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, }), @@ -170,12 +190,13 @@ fn bench_execute_block(c: &mut Criterion) { for strategy in execution_methods { group.bench_function(format!("{:?}", strategy), |b| { let genesis_config = node_testing::genesis::config(Some(compact_code_unwrap())); - let (use_native, wasm_method) = match strategy { - ExecutionMethod::Native => (true, WasmExecutionMethod::Interpreted), - ExecutionMethod::Wasm(wasm_method) => (false, wasm_method), + let use_native = match strategy { + ExecutionMethod::Native => true, + ExecutionMethod::Wasm(..) => false, }; - let executor = NativeElseWasmExecutor::new(wasm_method, None, 8, 2); + let executor = + NativeElseWasmExecutor::new_with_wasm_executor(WasmExecutor::builder().build()); let runtime_code = RuntimeCode { code_fetcher: &sp_core::traits::WrappedRuntimeCode(compact_code_unwrap().into()), hash: vec![1, 2, 3], @@ -201,6 +222,7 @@ fn bench_execute_block(c: &mut Criterion) { "Core_execute_block", &block.0, use_native, + CallContext::Offchain, ) .0 .unwrap(); diff --git a/bin/node/executor/src/lib.rs b/bin/node/executor/src/lib.rs index c4edf5ad22f4..3557a16740b8 100644 --- a/bin/node/executor/src/lib.rs +++ b/bin/node/executor/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +25,10 @@ pub use sc_executor::NativeElseWasmExecutor; pub struct ExecutorDispatch; impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { - type ExtendHostFunctions = frame_benchmarking::benchmarking::HostFunctions; + type ExtendHostFunctions = ( + frame_benchmarking::benchmarking::HostFunctions, + sp_statement_store::runtime_api::HostFunctions, + ); fn dispatch(method: &str, data: &[u8]) -> Option> { kitchensink_runtime::api::dispatch(method, data) diff --git a/bin/node/executor/tests/basic.rs b/bin/node/executor/tests/basic.rs index 02b2a8787b5d..a2f46e9fdbe9 100644 --- a/bin/node/executor/tests/basic.rs +++ b/bin/node/executor/tests/basic.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -89,7 +89,7 @@ fn changes_trie_block() -> (Vec, Hash) { }, CheckedExtrinsic { signed: Some((alice(), signed_extra(0, 0))), - function: RuntimeCall::Balances(pallet_balances::Call::transfer { + function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { dest: bob().into(), value: 69 * DOLLARS, }), @@ -116,7 +116,7 @@ fn blocks() -> ((Vec, Hash), (Vec, Hash)) { }, CheckedExtrinsic { signed: Some((alice(), signed_extra(0, 0))), - function: RuntimeCall::Balances(pallet_balances::Call::transfer { + function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { dest: bob().into(), value: 69 * DOLLARS, }), @@ -136,14 +136,14 @@ fn blocks() -> ((Vec, Hash), (Vec, Hash)) { }, CheckedExtrinsic { signed: Some((bob(), signed_extra(0, 0))), - function: RuntimeCall::Balances(pallet_balances::Call::transfer { + function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { dest: alice().into(), value: 5 * DOLLARS, }), }, CheckedExtrinsic { signed: Some((alice(), signed_extra(1, 0))), - function: RuntimeCall::Balances(pallet_balances::Call::transfer { + function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { dest: bob().into(), value: 15 * DOLLARS, }), @@ -183,7 +183,12 @@ fn panic_execution_with_foreign_code_gives_error() { let mut t = new_test_ext(bloaty_code_unwrap()); t.insert( >::hashed_key_for(alice()), - (69u128, 0u32, 0u128, 0u128, 0u128).encode(), + AccountInfo::<::Nonce, _> { + providers: 1, + data: (69u128, 0u128, 0u128, 1u128 << 127), + ..Default::default() + } + .encode(), ); t.insert(>::hashed_key().to_vec(), 69_u128.encode()); t.insert(>::hashed_key_for(0), vec![0u8; 32]); @@ -204,9 +209,14 @@ fn bad_extrinsic_with_native_equivalent_code_gives_error() { let mut t = new_test_ext(compact_code_unwrap()); t.insert( >::hashed_key_for(alice()), - (0u32, 0u32, 0u32, 69u128, 0u128, 0u128, 0u128).encode(), + AccountInfo::<::Nonce, _> { + providers: 1, + data: (69u128, 0u128, 0u128, 1u128 << 127), + ..Default::default() + } + .encode(), ); - t.insert(>::hashed_key().to_vec(), 69_u128.encode()); + t.insert(>::hashed_key().to_vec(), 69u128.encode()); t.insert(>::hashed_key_for(0), vec![0u8; 32]); let r = @@ -225,18 +235,19 @@ fn successful_execution_with_native_equivalent_code_gives_ok() { let mut t = new_test_ext(compact_code_unwrap()); t.insert( >::hashed_key_for(alice()), - AccountInfo::<::Index, _> { - data: (111 * DOLLARS, 0u128, 0u128, 0u128), + AccountInfo::<::Nonce, _> { + providers: 1, + data: (111 * DOLLARS, 0u128, 0u128, 1u128 << 127), ..Default::default() } .encode(), ); t.insert( >::hashed_key_for(bob()), - AccountInfo::<::Index, _> { - data: (0 * DOLLARS, 0u128, 0u128, 0u128), - ..Default::default() - } + AccountInfo::< + ::Nonce, + ::AccountData, + >::default() .encode(), ); t.insert( @@ -266,18 +277,19 @@ fn successful_execution_with_foreign_code_gives_ok() { let mut t = new_test_ext(bloaty_code_unwrap()); t.insert( >::hashed_key_for(alice()), - AccountInfo::<::Index, _> { - data: (111 * DOLLARS, 0u128, 0u128, 0u128), + AccountInfo::<::Nonce, _> { + providers: 1, + data: (111 * DOLLARS, 0u128, 0u128, 1u128 << 127), ..Default::default() } .encode(), ); t.insert( >::hashed_key_for(bob()), - AccountInfo::<::Index, _> { - data: (0 * DOLLARS, 0u128, 0u128, 0u128), - ..Default::default() - } + AccountInfo::< + ::Nonce, + ::AccountData, + >::default() .encode(), ); t.insert( @@ -682,7 +694,7 @@ fn deploying_wasm_contract_should_work() { Runtime, > { value: 0, - gas_limit: Weight::from_ref_time(500_000_000), + gas_limit: Weight::from_parts(500_000_000, 0), storage_deposit_limit: None, code: transfer_code, data: Vec::new(), @@ -694,7 +706,7 @@ fn deploying_wasm_contract_should_work() { function: RuntimeCall::Contracts(pallet_contracts::Call::call:: { dest: sp_runtime::MultiAddress::Id(addr.clone()), value: 10, - gas_limit: Weight::from_ref_time(500_000_000), + gas_limit: Weight::from_parts(500_000_000, 0), storage_deposit_limit: None, data: vec![0x00, 0x01, 0x02, 0x03], }), @@ -710,11 +722,7 @@ fn deploying_wasm_contract_should_work() { t.execute_with(|| { // Verify that the contract does exist by querying some of its storage items // It does not matter that the storage item itself does not exist. - assert!(&pallet_contracts::Pallet::::get_storage( - addr, - pallet_contracts::StorageKey::::default().to_vec() - ) - .is_ok()); + assert!(&pallet_contracts::Pallet::::get_storage(addr, vec![]).is_ok()); }); } @@ -758,7 +766,7 @@ fn panic_execution_gives_error() { let mut t = new_test_ext(bloaty_code_unwrap()); t.insert( >::hashed_key_for(alice()), - AccountInfo::<::Index, _> { + AccountInfo::<::Nonce, _> { data: (0 * DOLLARS, 0u128, 0u128, 0u128), ..Default::default() } @@ -787,18 +795,19 @@ fn successful_execution_gives_ok() { let mut t = new_test_ext(compact_code_unwrap()); t.insert( >::hashed_key_for(alice()), - AccountInfo::<::Index, _> { - data: (111 * DOLLARS, 0u128, 0u128, 0u128), + AccountInfo::<::Nonce, _> { + providers: 1, + data: (111 * DOLLARS, 0u128, 0u128, 1u128 << 127), ..Default::default() } .encode(), ); t.insert( >::hashed_key_for(bob()), - AccountInfo::<::Index, _> { - data: (0 * DOLLARS, 0u128, 0u128, 0u128), - ..Default::default() - } + AccountInfo::< + ::Nonce, + ::AccountData, + >::default() .encode(), ); t.insert( diff --git a/bin/node/executor/tests/common.rs b/bin/node/executor/tests/common.rs index 803ec78329ee..6ce9ea3a0109 100644 --- a/bin/node/executor/tests/common.rs +++ b/bin/node/executor/tests/common.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ use codec::{Decode, Encode}; use frame_support::Hashable; use frame_system::offchain::AppCrypto; -use sc_executor::{error::Result, NativeElseWasmExecutor, WasmExecutionMethod}; +use sc_executor::{error::Result, NativeElseWasmExecutor, WasmExecutor}; use sp_consensus_babe::{ digests::{PreDigest, SecondaryPlainPreDigest}, Slot, BABE_ENGINE_ID, @@ -26,7 +26,7 @@ use sp_consensus_babe::{ use sp_core::{ crypto::KeyTypeId, sr25519::Signature, - traits::{CodeExecutor, RuntimeCode}, + traits::{CallContext, CodeExecutor, RuntimeCode}, }; use sp_runtime::{ traits::{BlakeTwo256, Header as HeaderT}, @@ -87,7 +87,10 @@ pub fn sign(xt: CheckedExtrinsic) -> UncheckedExtrinsic { } pub fn default_transfer_call() -> pallet_balances::Call { - pallet_balances::Call::::transfer { dest: bob().into(), value: 69 * DOLLARS } + pallet_balances::Call::::transfer_allow_death { + dest: bob().into(), + value: 69 * DOLLARS, + } } pub fn from_block_number(n: u32) -> Header { @@ -95,7 +98,7 @@ pub fn from_block_number(n: u32) -> Header { } pub fn executor() -> NativeElseWasmExecutor { - NativeElseWasmExecutor::new(WasmExecutionMethod::Interpreted, None, 8, 2) + NativeElseWasmExecutor::new_with_wasm_executor(WasmExecutor::builder().build()) } pub fn executor_call( @@ -114,7 +117,7 @@ pub fn executor_call( heap_pages: heap_pages.and_then(|hp| Decode::decode(&mut &hp[..]).ok()), }; sp_tracing::try_init_simple(); - executor().call(&mut t, &runtime_code, method, data, use_native) + executor().call(&mut t, &runtime_code, method, data, use_native, CallContext::Onchain) } pub fn new_test_ext(code: &[u8]) -> TestExternalities { diff --git a/bin/node/executor/tests/fees.rs b/bin/node/executor/tests/fees.rs index 3c696d595040..7519ce6e8b1b 100644 --- a/bin/node/executor/tests/fees.rs +++ b/bin/node/executor/tests/fees.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -120,9 +120,9 @@ fn new_account_info(free_dollars: u128) -> Vec { frame_system::AccountInfo { nonce: 0u32, consumers: 0, - providers: 0, + providers: 1, sufficients: 0, - data: (free_dollars * DOLLARS, 0 * DOLLARS, 0 * DOLLARS, 0 * DOLLARS), + data: (free_dollars * DOLLARS, 0 * DOLLARS, 0 * DOLLARS, 1u128 << 127), } .encode() } @@ -196,7 +196,7 @@ fn transaction_fee_is_correct() { fn block_weight_capacity_report() { // Just report how many transfer calls you could fit into a block. The number should at least // be a few hundred (250 at the time of writing but can change over time). Runs until panic. - use node_primitives::Index; + use node_primitives::Nonce; // execution ext. let mut t = new_test_ext(compact_code_unwrap()); @@ -205,7 +205,7 @@ fn block_weight_capacity_report() { let factor = 50; let mut time = 10; - let mut nonce: Index = 0; + let mut nonce: Nonce = 0; let mut block_number = 1; let mut previous_hash: node_primitives::Hash = GENESIS_HASH.into(); @@ -213,8 +213,8 @@ fn block_weight_capacity_report() { let num_transfers = block_number * factor; let mut xts = (0..num_transfers) .map(|i| CheckedExtrinsic { - signed: Some((charlie(), signed_extra(nonce + i as Index, 0))), - function: RuntimeCall::Balances(pallet_balances::Call::transfer { + signed: Some((charlie(), signed_extra(nonce + i as Nonce, 0))), + function: RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { dest: bob().into(), value: 0, }), @@ -266,7 +266,7 @@ fn block_length_capacity_report() { // Just report how big a block can get. Executes until panic. Should be ignored unless if // manually inspected. The number should at least be a few megabytes (5 at the time of // writing but can change over time). - use node_primitives::Index; + use node_primitives::Nonce; // execution ext. let mut t = new_test_ext(compact_code_unwrap()); @@ -275,7 +275,7 @@ fn block_length_capacity_report() { let factor = 256 * 1024; let mut time = 10; - let mut nonce: Index = 0; + let mut nonce: Nonce = 0; let mut block_number = 1; let mut previous_hash: node_primitives::Hash = GENESIS_HASH.into(); diff --git a/bin/node/executor/tests/submit_transaction.rs b/bin/node/executor/tests/submit_transaction.rs index be43f3c78674..7678a3c6e5a9 100644 --- a/bin/node/executor/tests/submit_transaction.rs +++ b/bin/node/executor/tests/submit_transaction.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,11 +18,10 @@ use codec::Decode; use frame_system::offchain::{SendSignedTransaction, Signer, SubmitTransaction}; use kitchensink_runtime::{Executive, Indices, Runtime, UncheckedExtrinsic}; -use sp_application_crypto::AppKey; +use sp_application_crypto::AppCrypto; use sp_core::offchain::{testing::TestTransactionPoolExt, TransactionPoolExt}; use sp_keyring::sr25519::Keyring::Alice; -use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStore}; -use std::sync::Arc; +use sp_keystore::{testing::MemoryKeystore, Keystore, KeystoreExt}; pub mod common; use self::common::*; @@ -38,7 +37,6 @@ fn should_submit_unsigned_transaction() { pallet_im_online::sr25519::AuthoritySignature::try_from(vec![0; 64]).unwrap(); let heartbeat_data = pallet_im_online::Heartbeat { block_number: 1, - network_state: Default::default(), session_index: 1, authority_index: 0, validators_len: 0, @@ -62,31 +60,22 @@ fn should_submit_signed_transaction() { let (pool, state) = TestTransactionPoolExt::new(); t.register_extension(TransactionPoolExt::new(pool)); - let keystore = KeyStore::new(); - SyncCryptoStore::sr25519_generate_new( - &keystore, - sr25519::AuthorityId::ID, - Some(&format!("{}/hunter1", PHRASE)), - ) - .unwrap(); - SyncCryptoStore::sr25519_generate_new( - &keystore, - sr25519::AuthorityId::ID, - Some(&format!("{}/hunter2", PHRASE)), - ) - .unwrap(); - SyncCryptoStore::sr25519_generate_new( - &keystore, - sr25519::AuthorityId::ID, - Some(&format!("{}/hunter3", PHRASE)), - ) - .unwrap(); - t.register_extension(KeystoreExt(Arc::new(keystore))); + let keystore = MemoryKeystore::new(); + keystore + .sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter1", PHRASE))) + .unwrap(); + keystore + .sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter2", PHRASE))) + .unwrap(); + keystore + .sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter3", PHRASE))) + .unwrap(); + t.register_extension(KeystoreExt::new(keystore)); t.execute_with(|| { let results = Signer::::all_accounts().send_signed_transaction(|_| { - pallet_balances::Call::transfer { + pallet_balances::Call::transfer_allow_death { dest: Alice.to_account_id().into(), value: Default::default(), } @@ -105,25 +94,19 @@ fn should_submit_signed_twice_from_the_same_account() { let (pool, state) = TestTransactionPoolExt::new(); t.register_extension(TransactionPoolExt::new(pool)); - let keystore = KeyStore::new(); - SyncCryptoStore::sr25519_generate_new( - &keystore, - sr25519::AuthorityId::ID, - Some(&format!("{}/hunter1", PHRASE)), - ) - .unwrap(); - SyncCryptoStore::sr25519_generate_new( - &keystore, - sr25519::AuthorityId::ID, - Some(&format!("{}/hunter2", PHRASE)), - ) - .unwrap(); - t.register_extension(KeystoreExt(Arc::new(keystore))); + let keystore = MemoryKeystore::new(); + keystore + .sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter1", PHRASE))) + .unwrap(); + keystore + .sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter2", PHRASE))) + .unwrap(); + t.register_extension(KeystoreExt::new(keystore)); t.execute_with(|| { let result = Signer::::any_account().send_signed_transaction(|_| { - pallet_balances::Call::transfer { + pallet_balances::Call::transfer_allow_death { dest: Alice.to_account_id().into(), value: Default::default(), } @@ -135,7 +118,7 @@ fn should_submit_signed_twice_from_the_same_account() { // submit another one from the same account. The nonce should be incremented. let result = Signer::::any_account().send_signed_transaction(|_| { - pallet_balances::Call::transfer { + pallet_balances::Call::transfer_allow_death { dest: Alice.to_account_id().into(), value: Default::default(), } @@ -162,19 +145,19 @@ fn should_submit_signed_twice_from_all_accounts() { let (pool, state) = TestTransactionPoolExt::new(); t.register_extension(TransactionPoolExt::new(pool)); - let keystore = KeyStore::new(); + let keystore = MemoryKeystore::new(); keystore .sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter1", PHRASE))) .unwrap(); keystore .sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter2", PHRASE))) .unwrap(); - t.register_extension(KeystoreExt(Arc::new(keystore))); + t.register_extension(KeystoreExt::new(keystore)); t.execute_with(|| { let results = Signer::::all_accounts() .send_signed_transaction(|_| { - pallet_balances::Call::transfer { dest: Alice.to_account_id().into(), value: Default::default() } + pallet_balances::Call::transfer_allow_death { dest: Alice.to_account_id().into(), value: Default::default() } }); let len = results.len(); @@ -185,7 +168,7 @@ fn should_submit_signed_twice_from_all_accounts() { // submit another one from the same account. The nonce should be incremented. let results = Signer::::all_accounts() .send_signed_transaction(|_| { - pallet_balances::Call::transfer { dest: Alice.to_account_id().into(), value: Default::default() } + pallet_balances::Call::transfer_allow_death { dest: Alice.to_account_id().into(), value: Default::default() } }); let len = results.len(); @@ -226,19 +209,16 @@ fn submitted_transaction_should_be_valid() { let (pool, state) = TestTransactionPoolExt::new(); t.register_extension(TransactionPoolExt::new(pool)); - let keystore = KeyStore::new(); - SyncCryptoStore::sr25519_generate_new( - &keystore, - sr25519::AuthorityId::ID, - Some(&format!("{}/hunter1", PHRASE)), - ) - .unwrap(); - t.register_extension(KeystoreExt(Arc::new(keystore))); + let keystore = MemoryKeystore::new(); + keystore + .sr25519_generate_new(sr25519::AuthorityId::ID, Some(&format!("{}/hunter1", PHRASE))) + .unwrap(); + t.register_extension(KeystoreExt::new(keystore)); t.execute_with(|| { let results = Signer::::all_accounts().send_signed_transaction(|_| { - pallet_balances::Call::transfer { + pallet_balances::Call::transfer_allow_death { dest: Alice.to_account_id().into(), value: Default::default(), } diff --git a/bin/node/inspect/Cargo.toml b/bin/node/inspect/Cargo.toml index d6a8755179f3..41894fb60e81 100644 --- a/bin/node/inspect/Cargo.toml +++ b/bin/node/inspect/Cargo.toml @@ -13,13 +13,12 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.0.9", features = ["derive"] } -codec = { package = "parity-scale-codec", version = "3.2.2" } +clap = { version = "4.2.5", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1" } thiserror = "1.0" sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } -sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../client/service" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } diff --git a/bin/node/inspect/src/cli.rs b/bin/node/inspect/src/cli.rs index fa6344ac346e..c02a9c61d1ef 100644 --- a/bin/node/inspect/src/cli.rs +++ b/bin/node/inspect/src/cli.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/node/inspect/src/command.rs b/bin/node/inspect/src/command.rs index ce164e0768fb..dcecfd788264 100644 --- a/bin/node/inspect/src/command.rs +++ b/bin/node/inspect/src/command.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -23,41 +23,32 @@ use crate::{ Inspector, }; use sc_cli::{CliConfiguration, ImportParams, Result, SharedParams}; -use sc_executor::NativeElseWasmExecutor; -use sc_service::{new_full_client, Configuration, NativeExecutionDispatch}; +use sc_service::{Configuration, NativeExecutionDispatch}; use sp_runtime::traits::Block; -use std::str::FromStr; impl InspectCmd { /// Run the inspect command, passing the inspector. - pub fn run(&self, config: Configuration) -> Result<()> + pub fn run(&self, config: Configuration) -> Result<()> where B: Block, - B::Hash: FromStr, RA: Send + Sync + 'static, - EX: NativeExecutionDispatch + 'static, + D: NativeExecutionDispatch + 'static, { - let executor = NativeElseWasmExecutor::::new( - config.wasm_method, - config.default_heap_pages, - config.max_runtime_instances, - config.runtime_cache_size, - ); - - let client = new_full_client::(&config, None, executor)?; + let executor = sc_service::new_native_or_wasm_executor::(&config); + let client = sc_service::new_full_client::(&config, None, executor)?; let inspect = Inspector::::new(client); match &self.command { InspectSubCmd::Block { input } => { let input = input.parse()?; - let res = inspect.block(input).map_err(|e| format!("{}", e))?; - println!("{}", res); + let res = inspect.block(input).map_err(|e| e.to_string())?; + println!("{res}"); Ok(()) }, InspectSubCmd::Extrinsic { input } => { let input = input.parse()?; - let res = inspect.extrinsic(input).map_err(|e| format!("{}", e))?; - println!("{}", res); + let res = inspect.extrinsic(input).map_err(|e| e.to_string())?; + println!("{res}"); Ok(()) }, } diff --git a/bin/node/inspect/src/lib.rs b/bin/node/inspect/src/lib.rs index 3b9d7b2b1ff9..5764e0f05c17 100644 --- a/bin/node/inspect/src/lib.rs +++ b/bin/node/inspect/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. // -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/node/primitives/Cargo.toml b/bin/node/primitives/Cargo.toml index 1f515f3e3a5b..ac178c67f718 100644 --- a/bin/node/primitives/Cargo.toml +++ b/bin/node/primitives/Cargo.toml @@ -13,22 +13,12 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ - "derive", -] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../frame/system" } -sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../../primitives/application-crypto" } -sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } [features] default = ["std"] std = [ - "codec/std", - "frame-system/std", - "scale-info/std", - "sp-application-crypto/std", "sp-core/std", "sp-runtime/std", ] diff --git a/bin/node/primitives/src/lib.rs b/bin/node/primitives/src/lib.rs index feb9ee60d311..24a67cbdd8f7 100644 --- a/bin/node/primitives/src/lib.rs +++ b/bin/node/primitives/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -46,7 +46,7 @@ pub type Balance = u128; pub type Moment = u64; /// Index of a transaction in the chain. -pub type Index = u32; +pub type Nonce = u32; /// A hash of some data used by the chain. pub type Hash = sp_core::H256; diff --git a/bin/node/rpc/Cargo.toml b/bin/node/rpc/Cargo.toml index f34922a287df..9f5d12e22d37 100644 --- a/bin/node/rpc/Cargo.toml +++ b/bin/node/rpc/Cargo.toml @@ -21,9 +21,8 @@ sc-chain-spec = { version = "4.0.0-dev", path = "../../../client/chain-spec" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } sc-consensus-babe = { version = "0.10.0-dev", path = "../../../client/consensus/babe" } sc-consensus-babe-rpc = { version = "0.10.0-dev", path = "../../../client/consensus/babe/rpc" } -sc-consensus-epochs = { version = "0.10.0-dev", path = "../../../client/consensus/epochs" } -sc-finality-grandpa = { version = "0.10.0-dev", path = "../../../client/finality-grandpa" } -sc-finality-grandpa-rpc = { version = "0.10.0-dev", path = "../../../client/finality-grandpa/rpc" } +sc-consensus-grandpa = { version = "0.10.0-dev", path = "../../../client/consensus/grandpa" } +sc-consensus-grandpa-rpc = { version = "0.10.0-dev", path = "../../../client/consensus/grandpa/rpc" } sc-rpc = { version = "4.0.0-dev", path = "../../../client/rpc" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../client/rpc-api" } sc-rpc-spec-v2 = { version = "0.10.0-dev", path = "../../../client/rpc-spec-v2" } @@ -34,7 +33,8 @@ sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-bu sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } -sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-keystore = { version = "0.27.0", path = "../../../primitives/keystore" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } +sp-statement-store = { version = "4.0.0-dev", path = "../../../primitives/statement-store" } substrate-frame-rpc-system = { version = "4.0.0-dev", path = "../../../utils/frame/rpc/system" } substrate-state-trie-migration-rpc = { version = "4.0.0-dev", path = "../../../utils/frame/rpc/state-trie-migration-rpc/" } diff --git a/bin/node/rpc/src/lib.rs b/bin/node/rpc/src/lib.rs index 0dc5ba4039b0..a2ae46904dbf 100644 --- a/bin/node/rpc/src/lib.rs +++ b/bin/node/rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,11 +34,10 @@ use std::sync::Arc; use jsonrpsee::RpcModule; -use node_primitives::{AccountId, Balance, Block, BlockNumber, Hash, Index}; +use node_primitives::{AccountId, Balance, Block, BlockNumber, Hash, Nonce}; use sc_client_api::AuxStore; -use sc_consensus_babe::{BabeConfiguration, Epoch}; -use sc_consensus_epochs::SharedEpochChanges; -use sc_finality_grandpa::{ +use sc_consensus_babe::BabeWorkerHandle; +use sc_consensus_grandpa::{ FinalityProofProvider, GrandpaJustificationStream, SharedAuthoritySet, SharedVoterState, }; use sc_rpc::SubscriptionTaskExecutor; @@ -49,16 +48,14 @@ use sp_block_builder::BlockBuilder; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; use sp_consensus::SelectChain; use sp_consensus_babe::BabeApi; -use sp_keystore::SyncCryptoStorePtr; +use sp_keystore::KeystorePtr; /// Extra dependencies for BABE. pub struct BabeDeps { - /// BABE protocol config. - pub babe_config: BabeConfiguration, - /// BABE pending epoch changes. - pub shared_epoch_changes: SharedEpochChanges, + /// A handle to the BABE worker for issuing requests. + pub babe_worker_handle: BabeWorkerHandle, /// The keystore that manages the keys of the node. - pub keystore: SyncCryptoStorePtr, + pub keystore: KeystorePtr, } /// Extra dependencies for GRANDPA @@ -91,12 +88,25 @@ pub struct FullDeps { pub babe: BabeDeps, /// GRANDPA specific dependencies. pub grandpa: GrandpaDeps, + /// Shared statement store reference. + pub statement_store: Arc, + /// The backend used by the node. + pub backend: Arc, } /// Instantiate all Full RPC extensions. pub fn create_full( - deps: FullDeps, - backend: Arc, + FullDeps { + client, + pool, + select_chain, + chain_spec, + deny_unsafe, + babe, + grandpa, + statement_store, + backend, + }: FullDeps, ) -> Result, Box> where C: ProvideRuntimeApi @@ -107,7 +117,7 @@ where + Sync + Send + 'static, - C::Api: substrate_frame_rpc_system::AccountNonceApi, + C::Api: substrate_frame_rpc_system::AccountNonceApi, C::Api: mmr_rpc::MmrRuntimeApi::Hash, BlockNumber>, C::Api: pallet_transaction_payment_rpc::TransactionPaymentRuntimeApi, C::Api: BabeApi, @@ -120,17 +130,19 @@ where use mmr_rpc::{Mmr, MmrApiServer}; use pallet_transaction_payment_rpc::{TransactionPayment, TransactionPaymentApiServer}; use sc_consensus_babe_rpc::{Babe, BabeApiServer}; - use sc_finality_grandpa_rpc::{Grandpa, GrandpaApiServer}; - use sc_rpc::dev::{Dev, DevApiServer}; + use sc_consensus_grandpa_rpc::{Grandpa, GrandpaApiServer}; + use sc_rpc::{ + dev::{Dev, DevApiServer}, + statement::StatementApiServer, + }; use sc_rpc_spec_v2::chain_spec::{ChainSpec, ChainSpecApiServer}; use sc_sync_state_rpc::{SyncState, SyncStateApiServer}; use substrate_frame_rpc_system::{System, SystemApiServer}; use substrate_state_trie_migration_rpc::{StateMigration, StateMigrationApiServer}; let mut io = RpcModule::new(()); - let FullDeps { client, pool, select_chain, chain_spec, deny_unsafe, babe, grandpa } = deps; - let BabeDeps { keystore, babe_config, shared_epoch_changes } = babe; + let BabeDeps { keystore, babe_worker_handle } = babe; let GrandpaDeps { shared_voter_state, shared_authority_set, @@ -148,19 +160,20 @@ where // Making synchronous calls in light client freezes the browser currently, // more context: https://github.com/paritytech/substrate/pull/3480 // These RPCs should use an asynchronous caller instead. - io.merge(Mmr::new(client.clone()).into_rpc())?; - io.merge(TransactionPayment::new(client.clone()).into_rpc())?; io.merge( - Babe::new( + Mmr::new( client.clone(), - shared_epoch_changes.clone(), - keystore, - babe_config, - select_chain, - deny_unsafe, + backend + .offchain_storage() + .ok_or_else(|| "Backend doesn't provide an offchain storage")?, ) .into_rpc(), )?; + io.merge(TransactionPayment::new(client.clone()).into_rpc())?; + io.merge( + Babe::new(client.clone(), babe_worker_handle.clone(), keystore, select_chain, deny_unsafe) + .into_rpc(), + )?; io.merge( Grandpa::new( subscription_executor, @@ -173,12 +186,15 @@ where )?; io.merge( - SyncState::new(chain_spec, client.clone(), shared_authority_set, shared_epoch_changes)? + SyncState::new(chain_spec, client.clone(), shared_authority_set, babe_worker_handle)? .into_rpc(), )?; io.merge(StateMigration::new(client.clone(), backend, deny_unsafe).into_rpc())?; io.merge(Dev::new(client, deny_unsafe).into_rpc())?; + let statement_store = + sc_rpc::statement::StatementStore::new(statement_store, deny_unsafe).into_rpc(); + io.merge(statement_store)?; Ok(io) } diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 76fca730ef7d..30f0052c6eaa 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -16,41 +16,49 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # third-party dependencies -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", "max-encoded-len", ] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } static_assertions = "1.1.0" log = { version = "0.4.17", default-features = false } +# pallet-asset-conversion: turn on "num-traits" feature +primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "scale-info", "num-traits"] } + # primitives sp-authority-discovery = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/authority-discovery" } sp-consensus-babe = { version = "0.10.0-dev", default-features = false, path = "../../../primitives/consensus/babe" } +sp-consensus-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/consensus/grandpa" } sp-block-builder = { path = "../../../primitives/block-builder", default-features = false, version = "4.0.0-dev" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/inherents" } node-primitives = { version = "2.0.0", default-features = false, path = "../primitives" } sp-offchain = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/offchain" } -sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } -sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } sp-session = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/session" } sp-transaction-pool = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/transaction-pool" } -sp-version = { version = "5.0.0", default-features = false, path = "../../../primitives/version" } -sp-io = { version = "7.0.0", default-features = false, path = "../../../primitives/io" } +sp-statement-store = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/statement-store" } +sp-version = { version = "22.0.0", default-features = false, path = "../../../primitives/version" } +sp-io = { version = "23.0.0", default-features = false, path = "../../../primitives/io" } # frame dependencies frame-executive = { version = "4.0.0-dev", default-features = false, path = "../../../frame/executive" } -frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/benchmarking", optional = true } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../../../frame/support" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/benchmarking" } +frame-benchmarking-pallet-pov = { version = "4.0.0-dev", default-features = false, path = "../../../frame/benchmarking/pov" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../../frame/support", features = ["tuples-96"] } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../frame/system" } frame-system-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/system/benchmarking", optional = true } frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-support" } frame-system-rpc-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../../frame/system/rpc/runtime-api/" } frame-try-runtime = { version = "0.10.0-dev", default-features = false, path = "../../../frame/try-runtime", optional = true } pallet-alliance = { version = "4.0.0-dev", default-features = false, path = "../../../frame/alliance" } +pallet-asset-conversion = { version = "4.0.0-dev", default-features = false, path = "../../../frame/asset-conversion" } +pallet-asset-rate = { version = "4.0.0-dev", default-features = false, path = "../../../frame/asset-rate" } pallet-assets = { version = "4.0.0-dev", default-features = false, path = "../../../frame/assets" } pallet-authority-discovery = { version = "4.0.0-dev", default-features = false, path = "../../../frame/authority-discovery" } pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../../../frame/authorship" } @@ -61,8 +69,9 @@ pallet-bounties = { version = "4.0.0-dev", default-features = false, path = "../ pallet-child-bounties = { version = "4.0.0-dev", default-features = false, path = "../../../frame/child-bounties" } pallet-collective = { version = "4.0.0-dev", default-features = false, path = "../../../frame/collective" } pallet-contracts = { version = "4.0.0-dev", default-features = false, path = "../../../frame/contracts" } -pallet-contracts-primitives = { version = "7.0.0", default-features = false, path = "../../../frame/contracts/primitives/" } +pallet-contracts-primitives = { version = "24.0.0", default-features = false, path = "../../../frame/contracts/primitives/" } pallet-conviction-voting = { version = "4.0.0-dev", default-features = false, path = "../../../frame/conviction-voting" } +pallet-core-fellowship = { version = "4.0.0-dev", default-features = false, path = "../../../frame/core-fellowship" } pallet-democracy = { version = "4.0.0-dev", default-features = false, path = "../../../frame/democracy" } pallet-election-provider-multi-phase = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-multi-phase" } pallet-election-provider-support-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/election-provider-support/benchmarking", optional = true } @@ -79,24 +88,30 @@ pallet-message-queue = { version = "7.0.0-dev", default-features = false, path = pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../../../frame/merkle-mountain-range" } pallet-multisig = { version = "4.0.0-dev", default-features = false, path = "../../../frame/multisig" } pallet-nfts = { version = "4.0.0-dev", default-features = false, path = "../../../frame/nfts" } +pallet-nfts-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../../frame/nfts/runtime-api" } +pallet-nft-fractionalization = { version = "4.0.0-dev", default-features = false, path = "../../../frame/nft-fractionalization" } pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../../../frame/nomination-pools"} pallet-nomination-pools-benchmarking = { version = "1.0.0", default-features = false, optional = true, path = "../../../frame/nomination-pools/benchmarking" } pallet-nomination-pools-runtime-api = { version = "1.0.0-dev", default-features = false, path = "../../../frame/nomination-pools/runtime-api" } pallet-offences = { version = "4.0.0-dev", default-features = false, path = "../../../frame/offences" } pallet-offences-benchmarking = { version = "4.0.0-dev", path = "../../../frame/offences/benchmarking", default-features = false, optional = true } +pallet-glutton = { version = "4.0.0-dev", default-features = false, path = "../../../frame/glutton" } pallet-preimage = { version = "4.0.0-dev", default-features = false, path = "../../../frame/preimage" } pallet-proxy = { version = "4.0.0-dev", default-features = false, path = "../../../frame/proxy" } -pallet-randomness-collective-flip = { version = "4.0.0-dev", default-features = false, path = "../../../frame/randomness-collective-flip" } +pallet-insecure-randomness-collective-flip = { version = "4.0.0-dev", default-features = false, path = "../../../frame/insecure-randomness-collective-flip" } pallet-ranked-collective = { version = "4.0.0-dev", default-features = false, path = "../../../frame/ranked-collective" } pallet-recovery = { version = "4.0.0-dev", default-features = false, path = "../../../frame/recovery" } pallet-referenda = { version = "4.0.0-dev", default-features = false, path = "../../../frame/referenda" } pallet-remark = { version = "4.0.0-dev", default-features = false, path = "../../../frame/remark" } pallet-root-testing = { version = "1.0.0-dev", default-features = false, path = "../../../frame/root-testing" } +pallet-salary = { version = "4.0.0-dev", default-features = false, path = "../../../frame/salary" } pallet-session = { version = "4.0.0-dev", features = [ "historical" ], path = "../../../frame/session", default-features = false } pallet-session-benchmarking = { version = "4.0.0-dev", path = "../../../frame/session/benchmarking", default-features = false, optional = true } pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking" } pallet-staking-reward-curve = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking/reward-curve" } +pallet-staking-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking/runtime-api" } pallet-state-trie-migration = { version = "4.0.0-dev", default-features = false, path = "../../../frame/state-trie-migration" } +pallet-statement = { version = "4.0.0-dev", default-features = false, path = "../../../frame/statement" } pallet-scheduler = { version = "4.0.0-dev", default-features = false, path = "../../../frame/scheduler" } pallet-society = { version = "4.0.0-dev", default-features = false, path = "../../../frame/society" } pallet-sudo = { version = "4.0.0-dev", default-features = false, path = "../../../frame/sudo" } @@ -106,7 +121,8 @@ pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../ pallet-utility = { version = "4.0.0-dev", default-features = false, path = "../../../frame/utility" } pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment" } pallet-transaction-payment-rpc-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment/rpc/runtime-api/" } -pallet-asset-tx-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment/asset-tx-payment/" } +pallet-asset-conversion-tx-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment/asset-conversion-tx-payment" } +pallet-asset-tx-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment/asset-tx-payment" } pallet-transaction-storage = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-storage" } pallet-uniques = { version = "4.0.0-dev", default-features = false, path = "../../../frame/uniques" } pallet-vesting = { version = "4.0.0-dev", default-features = false, path = "../../../frame/vesting" } @@ -122,14 +138,17 @@ std = [ "pallet-whitelist/std", "pallet-offences-benchmarking?/std", "pallet-election-provider-support-benchmarking?/std", + "pallet-asset-conversion-tx-payment/std", "pallet-asset-tx-payment/std", "frame-system-benchmarking?/std", "frame-election-provider-support/std", "sp-authority-discovery/std", + "pallet-asset-conversion/std", "pallet-assets/std", "pallet-authority-discovery/std", "pallet-authorship/std", "sp-consensus-babe/std", + "sp-consensus-grandpa/std", "pallet-babe/std", "pallet-bags-list/std", "pallet-balances/std", @@ -141,6 +160,7 @@ std = [ "pallet-contracts/std", "pallet-contracts-primitives/std", "pallet-conviction-voting/std", + "pallet-core-fellowship/std", "pallet-democracy/std", "pallet-elections-phragmen/std", "pallet-fast-unstake/std", @@ -163,10 +183,11 @@ std = [ "node-primitives/std", "sp-offchain/std", "pallet-offences/std", + "pallet-glutton/std", "pallet-preimage/std", "pallet-proxy/std", "sp-core/std", - "pallet-randomness-collective-flip/std", + "pallet-insecure-randomness-collective-flip/std", "sp-std/std", "pallet-session/std", "pallet-session-benchmarking?/std", @@ -174,11 +195,15 @@ std = [ "sp-runtime/std", "sp-staking/std", "pallet-staking/std", + "pallet-staking-runtime-api/std", "pallet-state-trie-migration/std", + "pallet-statement/std", + "pallet-salary/std", "sp-session/std", "pallet-sudo/std", "frame-support/std", - "frame-benchmarking?/std", + "frame-benchmarking/std", + "frame-benchmarking-pallet-pov/std", "frame-system-rpc-runtime-api/std", "frame-system/std", "pallet-election-provider-multi-phase/std", @@ -188,7 +213,9 @@ std = [ "pallet-transaction-payment/std", "pallet-transaction-storage/std", "pallet-treasury/std", + "pallet-asset-rate/std", "sp-transaction-pool/std", + "sp-statement-store/std", "pallet-utility/std", "sp-version/std", "pallet-society/std", @@ -199,6 +226,8 @@ std = [ "pallet-recovery/std", "pallet-uniques/std", "pallet-nfts/std", + "pallet-nfts-runtime-api/std", + "pallet-nft-fractionalization/std", "pallet-vesting/std", "log/std", "frame-try-runtime?/std", @@ -209,10 +238,12 @@ std = [ ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", + "frame-benchmarking-pallet-pov/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "pallet-alliance/runtime-benchmarks", + "pallet-asset-conversion/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-babe/runtime-benchmarks", "pallet-bags-list/runtime-benchmarks", @@ -222,6 +253,7 @@ runtime-benchmarks = [ "pallet-collective/runtime-benchmarks", "pallet-contracts/runtime-benchmarks", "pallet-conviction-voting/runtime-benchmarks", + "pallet-core-fellowship/runtime-benchmarks", "pallet-democracy/runtime-benchmarks", "pallet-election-provider-multi-phase/runtime-benchmarks", "pallet-election-provider-support-benchmarking/runtime-benchmarks", @@ -239,6 +271,7 @@ runtime-benchmarks = [ "pallet-multisig/runtime-benchmarks", "pallet-nomination-pools-benchmarking/runtime-benchmarks", "pallet-offences-benchmarking/runtime-benchmarks", + "pallet-glutton/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "pallet-scheduler/runtime-benchmarks", @@ -246,27 +279,33 @@ runtime-benchmarks = [ "pallet-referenda/runtime-benchmarks", "pallet-recovery/runtime-benchmarks", "pallet-remark/runtime-benchmarks", + "pallet-salary/runtime-benchmarks", "pallet-session-benchmarking/runtime-benchmarks", "pallet-society/runtime-benchmarks", "pallet-staking/runtime-benchmarks", "pallet-state-trie-migration/runtime-benchmarks", + "pallet-sudo/runtime-benchmarks", "pallet-timestamp/runtime-benchmarks", "pallet-tips/runtime-benchmarks", "pallet-transaction-storage/runtime-benchmarks", "pallet-treasury/runtime-benchmarks", + "pallet-asset-rate/runtime-benchmarks", "pallet-utility/runtime-benchmarks", "pallet-uniques/runtime-benchmarks", "pallet-nfts/runtime-benchmarks", + "pallet-nft-fractionalization/runtime-benchmarks", "pallet-vesting/runtime-benchmarks", "pallet-whitelist/runtime-benchmarks", "frame-system-benchmarking/runtime-benchmarks", ] try-runtime = [ "frame-try-runtime/try-runtime", + "frame-benchmarking-pallet-pov/try-runtime", "frame-executive/try-runtime", "frame-system/try-runtime", "frame-support/try-runtime", "pallet-alliance/try-runtime", + "pallet-asset-conversion/try-runtime", "pallet-assets/try-runtime", "pallet-authority-discovery/try-runtime", "pallet-authorship/try-runtime", @@ -278,6 +317,7 @@ try-runtime = [ "pallet-collective/try-runtime", "pallet-contracts/try-runtime", "pallet-conviction-voting/try-runtime", + "pallet-core-fellowship/try-runtime", "pallet-democracy/try-runtime", "pallet-election-provider-multi-phase/try-runtime", "pallet-elections-phragmen/try-runtime", @@ -294,29 +334,35 @@ try-runtime = [ "pallet-multisig/try-runtime", "pallet-nomination-pools/try-runtime", "pallet-offences/try-runtime", + "pallet-glutton/try-runtime", "pallet-preimage/try-runtime", "pallet-proxy/try-runtime", - "pallet-randomness-collective-flip/try-runtime", + "pallet-insecure-randomness-collective-flip/try-runtime", "pallet-ranked-collective/try-runtime", "pallet-recovery/try-runtime", "pallet-referenda/try-runtime", "pallet-remark/try-runtime", "pallet-root-testing/try-runtime", + "pallet-salary/try-runtime", "pallet-session/try-runtime", "pallet-staking/try-runtime", "pallet-state-trie-migration/try-runtime", + "pallet-statement/try-runtime", "pallet-scheduler/try-runtime", "pallet-society/try-runtime", "pallet-sudo/try-runtime", "pallet-timestamp/try-runtime", "pallet-tips/try-runtime", "pallet-treasury/try-runtime", + "pallet-asset-rate/try-runtime", "pallet-utility/try-runtime", "pallet-transaction-payment/try-runtime", + "pallet-asset-conversion-tx-payment/try-runtime", "pallet-asset-tx-payment/try-runtime", "pallet-transaction-storage/try-runtime", "pallet-uniques/try-runtime", "pallet-nfts/try-runtime", + "pallet-nft-fractionalization/try-runtime", "pallet-vesting/try-runtime", "pallet-whitelist/try-runtime", ] diff --git a/bin/node/runtime/build.rs b/bin/node/runtime/build.rs index 70f84bc00a81..b7676a70dfe8 100644 --- a/bin/node/runtime/build.rs +++ b/bin/node/runtime/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/bin/node/runtime/src/assets_api.rs b/bin/node/runtime/src/assets_api.rs new file mode 100644 index 000000000000..cf1a663d7030 --- /dev/null +++ b/bin/node/runtime/src/assets_api.rs @@ -0,0 +1,34 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// 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 . + +//! Runtime API definition for assets. + +use codec::Codec; +use sp_std::vec::Vec; + +sp_api::decl_runtime_apis! { + pub trait AssetsApi + where + AccountId: Codec, + AssetBalance: Codec, + AssetId: Codec, + { + /// Returns the list of `AssetId`s and corresponding balance that an `AccountId` has. + fn account_balances(account: AccountId) -> Vec<(AssetId, AssetBalance)>; + } +} diff --git a/bin/node/runtime/src/constants.rs b/bin/node/runtime/src/constants.rs index 23fb13cfb049..e4fafbf0fa47 100644 --- a/bin/node/runtime/src/constants.rs +++ b/bin/node/runtime/src/constants.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/bin/node/runtime/src/impls.rs b/bin/node/runtime/src/impls.rs index b3f58ea5d24a..430a1ac2824b 100644 --- a/bin/node/runtime/src/impls.rs +++ b/bin/node/runtime/src/impls.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,14 +17,10 @@ //! Some configurable implementations as associated type for the substrate runtime. -use crate::{ - AccountId, AllianceMotion, Assets, Authorship, Balances, Hash, NegativeImbalance, Runtime, - RuntimeCall, -}; use frame_support::{ pallet_prelude::*, traits::{ - fungibles::{Balanced, CreditOf}, + fungibles::{Balanced, Credit}, Currency, OnUnbalanced, }, }; @@ -32,6 +28,11 @@ use pallet_alliance::{IdentityVerifier, ProposalIndex, ProposalProvider}; use pallet_asset_tx_payment::HandleCredit; use sp_std::prelude::*; +use crate::{ + AccountId, AllianceMotion, Assets, Authorship, Balances, Hash, NegativeImbalance, Runtime, + RuntimeCall, +}; + pub struct Author; impl OnUnbalanced for Author { fn on_nonzero_unbalanced(amount: NegativeImbalance) { @@ -45,7 +46,7 @@ impl OnUnbalanced for Author { /// Will drop and burn the assets in case the transfer fails. pub struct CreditToBlockAuthor; impl HandleCredit for CreditToBlockAuthor { - fn handle_credit(credit: CreditOf) { + fn handle_credit(credit: Credit) { if let Some(author) = pallet_authorship::Pallet::::author() { // Drop the result which will trigger the `OnDrop` of the imbalance in case of error. let _ = Assets::resolve(&author, credit); @@ -61,15 +62,13 @@ impl IdentityVerifier for AllianceIdentityVerifier { fn has_good_judgement(who: &AccountId) -> bool { use pallet_identity::Judgement; - if let Some(judgements) = - crate::Identity::identity(who).map(|registration| registration.judgements) - { - judgements - .iter() - .any(|(_, j)| matches!(j, Judgement::KnownGood | Judgement::Reasonable)) - } else { - false - } + crate::Identity::identity(who) + .map(|registration| registration.judgements) + .map_or(false, |judgements| { + judgements + .iter() + .any(|(_, j)| matches!(j, Judgement::KnownGood | Judgement::Reasonable)) + }) } fn super_account_id(who: &AccountId) -> Option { @@ -113,11 +112,15 @@ impl ProposalProvider for AllianceProposalProvider #[cfg(test)] mod multiplier_tests { + use frame_support::{ + dispatch::DispatchClass, + weights::{Weight, WeightToFee}, + }; use pallet_transaction_payment::{Multiplier, TargetedFeeAdjustment}; use sp_runtime::{ assert_eq_error_rate, traits::{Convert, One, Zero}, - FixedPointNumber, + BuildStorage, FixedPointNumber, }; use crate::{ @@ -125,10 +128,6 @@ mod multiplier_tests { AdjustmentVariable, MaximumMultiplier, MinimumMultiplier, Runtime, RuntimeBlockWeights as BlockWeights, System, TargetBlockFullness, TransactionPayment, }; - use frame_support::{ - dispatch::DispatchClass, - weights::{Weight, WeightToFee}, - }; fn max_normal() -> Weight { BlockWeights::get() @@ -163,14 +162,28 @@ mod multiplier_tests { // bump if it is zero. let previous_float = previous_float.max(min_multiplier().into_inner() as f64 / accuracy); + let max_normal = max_normal(); + let target_weight = target(); + let normalized_weight_dimensions = ( + block_weight.ref_time() as f64 / max_normal.ref_time() as f64, + block_weight.proof_size() as f64 / max_normal.proof_size() as f64, + ); + + let (normal, max, target) = + if normalized_weight_dimensions.0 < normalized_weight_dimensions.1 { + (block_weight.proof_size(), max_normal.proof_size(), target_weight.proof_size()) + } else { + (block_weight.ref_time(), max_normal.ref_time(), target_weight.ref_time()) + }; + // maximum tx weight - let m = max_normal().ref_time() as f64; + let m = max as f64; // block weight always truncated to max weight - let block_weight = (block_weight.ref_time() as f64).min(m); + let block_weight = (normal as f64).min(m); let v: f64 = AdjustmentVariable::get().to_float(); // Ideal saturation in terms of weight - let ss = target().ref_time() as f64; + let ss = target as f64; // Current saturation in terms of weight let s = block_weight; @@ -184,8 +197,8 @@ mod multiplier_tests { where F: Fn() -> (), { - let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::default() - .build_storage::() + let mut t: sp_io::TestExternalities = frame_system::GenesisConfig::::default() + .build_storage() .unwrap() .into(); t.execute_with(|| { @@ -199,8 +212,8 @@ mod multiplier_tests { let fm = Multiplier::saturating_from_rational(1, 2); let test_set = vec![ (Weight::zero(), fm), - (Weight::from_ref_time(100), fm), - (Weight::from_ref_time(1000), fm), + (Weight::from_parts(100, 0), fm), + (Weight::from_parts(1000, 0), fm), (target(), fm), (max_normal() / 2, fm), (max_normal(), fm), @@ -220,10 +233,16 @@ mod multiplier_tests { #[test] fn multiplier_can_grow_from_zero() { // if the min is too small, then this will not change, and we are doomed forever. - // the weight is 1/100th bigger than target. + // the block ref time is 1/100th bigger than target. run_with_system_weight(target().set_ref_time(target().ref_time() * 101 / 100), || { let next = runtime_multiplier_update(min_multiplier()); - assert!(next > min_multiplier(), "{:?} !>= {:?}", next, min_multiplier()); + assert!(next > min_multiplier(), "{:?} !> {:?}", next, min_multiplier()); + }); + + // the block proof size is 1/100th bigger than target. + run_with_system_weight(target().set_proof_size((target().proof_size() / 100) * 101), || { + let next = runtime_multiplier_update(min_multiplier()); + assert!(next > min_multiplier(), "{:?} !> {:?}", next, min_multiplier()); }) } @@ -285,7 +304,7 @@ mod multiplier_tests { // almost full. The entire quota of normal transactions is taken. let block_weight = BlockWeights::get().get(DispatchClass::Normal).max_total.unwrap() - - Weight::from_ref_time(100); + Weight::from_parts(100, 0); // Default substrate weight. let tx_weight = frame_support::weights::constants::ExtrinsicBaseWeight::get(); @@ -409,23 +428,33 @@ mod multiplier_tests { #[test] fn weight_to_fee_should_not_overflow_on_large_weights() { - let kb = Weight::from_ref_time(1024); - let mb = 1024u64 * kb; + let kb_time = Weight::from_parts(1024, 0); + let kb_size = Weight::from_parts(0, 1024); + let mb_time = 1024u64 * kb_time; let max_fm = Multiplier::saturating_from_integer(i128::MAX); // check that for all values it can compute, correctly. vec![ Weight::zero(), - Weight::from_ref_time(1), - Weight::from_ref_time(10), - Weight::from_ref_time(1000), - kb, - 10u64 * kb, - 100u64 * kb, - mb, - 10u64 * mb, - Weight::from_ref_time(2147483647), - Weight::from_ref_time(4294967295), + // testcases ignoring proof size part of the weight. + Weight::from_parts(1, 0), + Weight::from_parts(10, 0), + Weight::from_parts(1000, 0), + kb_time, + 10u64 * kb_time, + 100u64 * kb_time, + mb_time, + 10u64 * mb_time, + Weight::from_parts(2147483647, 0), + Weight::from_parts(4294967295, 0), + // testcases ignoring ref time part of the weight. + Weight::from_parts(0, 100000000000), + 1000000u64 * kb_size, + 1000000000u64 * kb_size, + Weight::from_parts(0, 18014398509481983), + Weight::from_parts(0, 9223372036854775807), + // test cases with both parts of the weight. + BlockWeights::get().max_block / 1024, BlockWeights::get().max_block / 2, BlockWeights::get().max_block, Weight::MAX / 2, @@ -442,7 +471,14 @@ mod multiplier_tests { // Some values that are all above the target and will cause an increase. let t = target(); - vec![t + Weight::from_ref_time(100), t * 2, t * 4].into_iter().for_each(|i| { + vec![ + t + Weight::from_parts(100, 0), + t + Weight::from_parts(0, t.proof_size() * 2), + t * 2, + t * 4, + ] + .into_iter() + .for_each(|i| { run_with_system_weight(i, || { let fm = runtime_multiplier_update(max_fm); // won't grow. The convert saturates everything. diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 0121f1ee3a8c..e6c52216b791 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -19,8 +19,8 @@ //! The Substrate runtime. This can be compiled with `#[no_std]`, ready for Wasm. #![cfg_attr(not(feature = "std"), no_std)] -// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 512. -#![recursion_limit = "512"] +// `construct_runtime!` does a lot of recursion and requires us to increase the limits. +#![recursion_limit = "1024"] use codec::{Decode, Encode, MaxEncodedLen}; use frame_election_provider_support::{ @@ -29,13 +29,16 @@ use frame_election_provider_support::{ use frame_support::{ construct_runtime, dispatch::DispatchClass, + instances::{Instance1, Instance2}, + ord_parameter_types, pallet_prelude::Get, parameter_types, traits::{ - fungible::ItemOf, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, - Currency, EitherOfDiverse, EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, - KeyOwnerProofSystem, LockIdentifier, Nothing, OnUnbalanced, U128CurrencyToVote, - WithdrawReasons, + fungible::ItemOf, + tokens::{nonfungibles_v2::Inspect, GetSalary, PayFromAccount}, + AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Currency, EitherOfDiverse, + EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem, + LockIdentifier, Nothing, OnUnbalanced, WithdrawReasons, }, weights::{ constants::{ @@ -43,26 +46,27 @@ use frame_support::{ }, ConstantMultiplier, IdentityFee, Weight, }, - PalletId, RuntimeDebug, + BoundedVec, PalletId, RuntimeDebug, }; use frame_system::{ limits::{BlockLength, BlockWeights}, - EnsureRoot, EnsureRootWithSuccess, EnsureSigned, EnsureWithSuccess, + EnsureRoot, EnsureRootWithSuccess, EnsureSigned, EnsureSignedBy, EnsureWithSuccess, }; pub use node_primitives::{AccountId, Signature}; -use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Index, Moment}; +use node_primitives::{AccountIndex, Balance, BlockNumber, Hash, Moment, Nonce}; +use pallet_asset_conversion::{NativeOrAssetId, NativeOrAssetIdConverter}; +#[cfg(feature = "runtime-benchmarks")] +use pallet_contracts::NoopMigration; use pallet_election_provider_multi_phase::SolutionAccuracyOf; -use pallet_grandpa::{ - fg_primitives, AuthorityId as GrandpaId, AuthorityList as GrandpaAuthorityList, -}; use pallet_im_online::sr25519::AuthorityId as ImOnlineId; use pallet_nfts::PalletFeatures; use pallet_nis::WithMaximumOf; -use pallet_session::historical::{self as pallet_session_historical}; +use pallet_session::historical as pallet_session_historical; pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment}; use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo}; use sp_api::impl_runtime_apis; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; +use sp_consensus_grandpa::AuthorityId as GrandpaId; use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; use sp_inherents::{CheckInherentsResult, InherentData}; use sp_runtime::{ @@ -70,8 +74,8 @@ use sp_runtime::{ curve::PiecewiseLinear, generic, impl_opaque_keys, traits::{ - self, BlakeTwo256, Block as BlockT, Bounded, ConvertInto, NumberFor, OpaqueKeys, - SaturatedConversion, StaticLookup, + self, AccountIdConversion, BlakeTwo256, Block as BlockT, Bounded, ConvertInto, NumberFor, + OpaqueKeys, SaturatedConversion, StaticLookup, }, transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity}, ApplyExtrinsicResult, FixedPointNumber, FixedU128, Perbill, Percent, Permill, Perquintill, @@ -107,10 +111,18 @@ use sp_runtime::generic::Era; /// Generated voter bag information. mod voter_bags; +/// Runtime API definition for assets. +pub mod assets_api; + // Make the WASM binary available. #[cfg(feature = "std")] include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); +/// Max size for serialized extrinsic params for this testing runtime. +/// This is a quite arbitrary but empirically battle tested value. +#[cfg(test)] +pub const CALL_PARAMS_MAX_SIZE: usize = 208; + /// Wasm binary unwrapped. If built with `SKIP_WASM_BUILD`, the function panics. #[cfg(feature = "std")] pub fn wasm_binary_unwrap() -> &'static [u8] { @@ -202,6 +214,7 @@ parameter_types! { }) .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) .build_or_panic(); + pub MaxCollectivesProposalWeight: Weight = Perbill::from_percent(50) * RuntimeBlockWeights::get().max_block; } const_assert!(NORMAL_DISPATCH_RATIO.deconstruct() >= AVERAGE_ON_INITIALIZE_RATIO.deconstruct()); @@ -213,13 +226,12 @@ impl frame_system::Config for Runtime { type DbWeight = RocksDbWeight; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = Index; - type BlockNumber = BlockNumber; + type Nonce = Nonce; type Hash = Hash; type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = Indices; - type Header = generic::Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = BlockHashCount; type Version = Version; @@ -233,7 +245,7 @@ impl frame_system::Config for Runtime { type MaxConsumers = ConstU32<16>; } -impl pallet_randomness_collective_flip::Config for Runtime {} +impl pallet_insecure_randomness_collective_flip::Config for Runtime {} impl pallet_utility::Config for Runtime { type RuntimeEvent = RuntimeEvent; @@ -315,7 +327,9 @@ impl InstanceFilter for ProxyType { RuntimeCall::Elections(..) | RuntimeCall::Treasury(..) ), - ProxyType::Staking => matches!(c, RuntimeCall::Staking(..)), + ProxyType::Staking => { + matches!(c, RuntimeCall::Staking(..) | RuntimeCall::FastUnstake(..)) + }, } } fn is_superset(&self, o: &Self) -> bool { @@ -356,12 +370,21 @@ impl pallet_scheduler::Config for Runtime { type RuntimeCall = RuntimeCall; type MaximumWeight = MaximumSchedulerWeight; type ScheduleOrigin = EnsureRoot; + #[cfg(feature = "runtime-benchmarks")] type MaxScheduledPerBlock = ConstU32<512>; + #[cfg(not(feature = "runtime-benchmarks"))] + type MaxScheduledPerBlock = ConstU32<50>; type WeightInfo = pallet_scheduler::weights::SubstrateWeight; type OriginPrivilegeCmp = EqualPrivilegeOnly; type Preimages = Preimage; } +impl pallet_glutton::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type AdminOrigin = EnsureRoot; + type WeightInfo = pallet_glutton::weights::SubstrateWeight; +} + parameter_types! { pub const PreimageMaxSize: u32 = 4096 * 1024; pub const PreimageBaseDeposit: Balance = 1 * DOLLARS; @@ -392,24 +415,13 @@ impl pallet_babe::Config for Runtime { type ExpectedBlockTime = ExpectedBlockTime; type EpochChangeTrigger = pallet_babe::ExternalTrigger; type DisabledValidators = Session; - - type KeyOwnerProofSystem = Historical; - - type KeyOwnerProof = >::Proof; - - type KeyOwnerIdentification = >::IdentificationTuple; - - type HandleEquivocation = - pallet_babe::EquivocationHandler; - type WeightInfo = (); type MaxAuthorities = MaxAuthorities; + type MaxNominators = MaxNominatorRewardedPerValidator; + type KeyOwnerProof = + >::Proof; + type EquivocationReportSystem = + pallet_babe::EquivocationReportSystem; } parameter_types! { @@ -442,6 +454,10 @@ impl pallet_balances::Config for Runtime { type ExistentialDeposit = ExistentialDeposit; type AccountStore = frame_system::Pallet; type WeightInfo = pallet_balances::weights::SubstrateWeight; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = RuntimeHoldReason; + type MaxHolds = ConstU32<2>; } parameter_types! { @@ -472,11 +488,18 @@ impl pallet_asset_tx_payment::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Fungibles = Assets; type OnChargeAssetTransaction = pallet_asset_tx_payment::FungiblesAdapter< - pallet_assets::BalanceToAssetBalance, + pallet_assets::BalanceToAssetBalance, CreditToBlockAuthor, >; } +impl pallet_asset_conversion_tx_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Fungibles = Assets; + type OnChargeAssetTransaction = + pallet_asset_conversion_tx_payment::AssetConversionAdapter; +} + parameter_types! { pub const MinimumPeriod: Moment = SLOT_DURATION / 2; } @@ -488,14 +511,8 @@ impl pallet_timestamp::Config for Runtime { type WeightInfo = pallet_timestamp::weights::SubstrateWeight; } -parameter_types! { - pub const UncleGenerations: BlockNumber = 5; -} - impl pallet_authorship::Config for Runtime { type FindAuthor = pallet_session::FindAccountFromAuthorIndex; - type UncleGenerations = UncleGenerations; - type FilterUncle = (); type EventHandler = (Staking, ImOnline); } @@ -558,7 +575,7 @@ impl pallet_staking::Config for Runtime { type Currency = Balances; type CurrencyBalance = Balance; type UnixTime = Timestamp; - type CurrencyToVote = U128CurrencyToVote; + type CurrencyToVote = sp_staking::currency_to_vote::U128CurrencyToVote; type RewardRemainder = Treasury; type RuntimeEvent = RuntimeEvent; type Slash = Treasury; // send the slashed funds to the treasury. @@ -583,7 +600,7 @@ impl pallet_staking::Config for Runtime { type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type HistoryDepth = HistoryDepth; - type OnStakerSlash = NominationPools; + type EventListeners = NominationPools; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; } @@ -708,6 +725,7 @@ impl pallet_election_provider_multi_phase::MinerConfig for Runtime { type Solution = NposSolution16; type MaxVotesPerVoter = <::DataProvider as ElectionDataProvider>::MaxVotesPerVoter; + type MaxWinners = MaxActiveValidators; // The unsigned submissions have to respect the weight of the submit_unsigned call, thus their // weight estimate function is wired to this call's weight. @@ -949,6 +967,7 @@ impl pallet_democracy::Config for Runtime { /// (NTB) vote. type ExternalDefaultOrigin = pallet_collective::EnsureProportionAtLeast; + type SubmitOrigin = EnsureSigned; /// Two thirds of the technical committee can have an ExternalMajority/ExternalDefault vote /// be tabled immediately and with a shorter voting/enactment period. type FastTrackOrigin = @@ -998,6 +1017,8 @@ impl pallet_collective::Config for Runtime { type MaxMembers = CouncilMaxMembers; type DefaultVote = pallet_collective::PrimeDefaultVote; type WeightInfo = pallet_collective::weights::SubstrateWeight; + type SetMembersOrigin = EnsureRoot; + type MaxProposalWeight = MaxCollectivesProposalWeight; } parameter_types! { @@ -1009,8 +1030,9 @@ parameter_types! { pub const TermDuration: BlockNumber = 7 * DAYS; pub const DesiredMembers: u32 = 13; pub const DesiredRunnersUp: u32 = 7; - pub const MaxVoters: u32 = 10 * 1000; - pub const MaxCandidates: u32 = 1000; + pub const MaxVotesPerVoter: u32 = 16; + pub const MaxVoters: u32 = 512; + pub const MaxCandidates: u32 = 64; pub const ElectionsPhragmenPalletId: LockIdentifier = *b"phrelect"; } @@ -1025,7 +1047,7 @@ impl pallet_elections_phragmen::Config for Runtime { // NOTE: this implies that council's genesis members cannot be set directly and must come from // this module. type InitializeMembers = Council; - type CurrencyToVote = U128CurrencyToVote; + type CurrencyToVote = sp_staking::currency_to_vote::U128CurrencyToVote; type CandidacyBond = CandidacyBond; type VotingBondBase = VotingBondBase; type VotingBondFactor = VotingBondFactor; @@ -1035,6 +1057,7 @@ impl pallet_elections_phragmen::Config for Runtime { type DesiredRunnersUp = DesiredRunnersUp; type TermDuration = TermDuration; type MaxVoters = MaxVoters; + type MaxVotesPerVoter = MaxVotesPerVoter; type MaxCandidates = MaxCandidates; type WeightInfo = pallet_elections_phragmen::weights::SubstrateWeight; } @@ -1055,6 +1078,8 @@ impl pallet_collective::Config for Runtime { type MaxMembers = TechnicalMaxMembers; type DefaultVote = pallet_collective::PrimeDefaultVote; type WeightInfo = pallet_collective::weights::SubstrateWeight; + type SetMembersOrigin = EnsureRoot; + type MaxProposalWeight = MaxCollectivesProposalWeight; } type EnsureRootOrHalfCouncil = EitherOfDiverse< @@ -1114,6 +1139,17 @@ impl pallet_treasury::Config for Runtime { type SpendOrigin = EnsureWithSuccess, AccountId, MaxBalance>; } +impl pallet_asset_rate::Config for Runtime { + type CreateOrigin = EnsureRoot; + type RemoveOrigin = EnsureRoot; + type UpdateOrigin = EnsureRoot; + type Balance = Balance; + type Currency = Balances; + type AssetId = u32; + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_asset_rate::weights::SubstrateWeight; +} + parameter_types! { pub const BountyCuratorDeposit: Permill = Permill::from_percent(50); pub const BountyValueMinimum: Balance = 5 * DOLLARS; @@ -1151,9 +1187,10 @@ impl pallet_message_queue::Config for Runtime { type RuntimeEvent = RuntimeEvent; type WeightInfo = (); /// NOTE: Always set this to `NoopMessageProcessor` for benchmarking. - type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor; + type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor; type Size = u32; type QueueChangeHandler = (); + type QueuePausedQuery = (); type HeapSize = ConstU32<{ 64 * 1024 }>; type MaxStale = ConstU32<128>; type ServiceWeight = MessageQueueServiceWeight; @@ -1184,13 +1221,7 @@ impl pallet_tips::Config for Runtime { parameter_types! { pub const DepositPerItem: Balance = deposit(1, 0); pub const DepositPerByte: Balance = deposit(0, 1); - pub const DeletionQueueDepth: u32 = 128; - // The lazy deletion runs inside on_initialize. - pub DeletionWeightLimit: Weight = RuntimeBlockWeights::get() - .per_class - .get(DispatchClass::Normal) - .max_total - .unwrap_or(RuntimeBlockWeights::get().max_block); + pub const DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024); pub Schedule: pallet_contracts::Schedule = Default::default(); } @@ -1209,23 +1240,27 @@ impl pallet_contracts::Config for Runtime { type CallFilter = Nothing; type DepositPerItem = DepositPerItem; type DepositPerByte = DepositPerByte; + type DefaultDepositLimit = DefaultDepositLimit; type CallStack = [pallet_contracts::Frame; 5]; type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_contracts::weights::SubstrateWeight; type ChainExtension = (); - type DeletionQueueDepth = DeletionQueueDepth; - type DeletionWeightLimit = DeletionWeightLimit; type Schedule = Schedule; type AddressGenerator = pallet_contracts::DefaultAddressGenerator; type MaxCodeLen = ConstU32<{ 123 * 1024 }>; type MaxStorageKeyLen = ConstU32<128>; type UnsafeUnstableInterface = ConstBool; type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; + #[cfg(not(feature = "runtime-benchmarks"))] + type Migrations = (); + #[cfg(feature = "runtime-benchmarks")] + type Migrations = (NoopMigration<1>, NoopMigration<2>); } impl pallet_sudo::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; + type WeightInfo = pallet_sudo::weights::SubstrateWeight; } parameter_types! { @@ -1235,7 +1270,6 @@ parameter_types! { pub const MaxAuthorities: u32 = 100; pub const MaxKeys: u32 = 10_000; pub const MaxPeerInHeartbeats: u32 = 10_000; - pub const MaxPeerDataEncodingSize: u32 = 1_000; } impl frame_system::offchain::CreateSignedTransaction for Runtime @@ -1246,7 +1280,7 @@ where call: RuntimeCall, public: ::Signer, account: AccountId, - nonce: Index, + nonce: Nonce, ) -> Option<(RuntimeCall, ::SignaturePayload)> { let tip = 0; // take the biggest period possible. @@ -1266,7 +1300,7 @@ where frame_system::CheckEra::::from(era), frame_system::CheckNonce::::from(nonce), frame_system::CheckWeight::::new(), - pallet_asset_tx_payment::ChargeAssetTxPayment::::from(tip, None), + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::::from(tip, None), ); let raw_payload = SignedPayload::new(call, extra) .map_err(|e| { @@ -1303,7 +1337,6 @@ impl pallet_im_online::Config for Runtime { type WeightInfo = pallet_im_online::weights::SubstrateWeight; type MaxKeys = MaxKeys; type MaxPeerInHeartbeats = MaxPeerInHeartbeats; - type MaxPeerDataEncodingSize = MaxPeerDataEncodingSize; } impl pallet_offences::Config for Runtime { @@ -1316,27 +1349,19 @@ impl pallet_authority_discovery::Config for Runtime { type MaxAuthorities = MaxAuthorities; } +parameter_types! { + pub const MaxSetIdSessionEntries: u32 = BondingDuration::get() * SessionsPerEra::get(); +} + impl pallet_grandpa::Config for Runtime { type RuntimeEvent = RuntimeEvent; - - type KeyOwnerProofSystem = Historical; - - type KeyOwnerProof = - >::Proof; - - type KeyOwnerIdentification = >::IdentificationTuple; - - type HandleEquivocation = pallet_grandpa::EquivocationHandler< - Self::KeyOwnerIdentification, - Offences, - ReportLongevity, - >; - type WeightInfo = (); type MaxAuthorities = MaxAuthorities; + type MaxNominators = MaxNominatorRewardedPerValidator; + type MaxSetIdSessionEntries = MaxSetIdSessionEntries; + type KeyOwnerProof = >::Proof; + type EquivocationReportSystem = + pallet_grandpa::EquivocationReportSystem; } parameter_types! { @@ -1382,14 +1407,14 @@ impl pallet_recovery::Config for Runtime { } parameter_types! { - pub const CandidateDeposit: Balance = 10 * DOLLARS; - pub const WrongSideDeduction: Balance = 2 * DOLLARS; - pub const MaxStrikes: u32 = 10; - pub const RotationPeriod: BlockNumber = 80 * HOURS; + pub const GraceStrikes: u32 = 10; + pub const SocietyVotingPeriod: BlockNumber = 80 * HOURS; + pub const ClaimPeriod: BlockNumber = 80 * HOURS; pub const PeriodSpend: Balance = 500 * DOLLARS; pub const MaxLockDuration: BlockNumber = 36 * 30 * DAYS; pub const ChallengePeriod: BlockNumber = 7 * DAYS; - pub const MaxCandidateIntake: u32 = 10; + pub const MaxPayouts: u32 = 10; + pub const MaxBids: u32 = 10; pub const SocietyPalletId: PalletId = PalletId(*b"py/socie"); } @@ -1398,18 +1423,17 @@ impl pallet_society::Config for Runtime { type PalletId = SocietyPalletId; type Currency = Balances; type Randomness = RandomnessCollectiveFlip; - type CandidateDeposit = CandidateDeposit; - type WrongSideDeduction = WrongSideDeduction; - type MaxStrikes = MaxStrikes; + type GraceStrikes = GraceStrikes; type PeriodSpend = PeriodSpend; - type MembershipChanged = (); - type RotationPeriod = RotationPeriod; + type VotingPeriod = SocietyVotingPeriod; + type ClaimPeriod = ClaimPeriod; type MaxLockDuration = MaxLockDuration; type FounderSetOrigin = pallet_collective::EnsureProportionMoreThan; - type SuspensionJudgementOrigin = pallet_society::EnsureFounder; - type MaxCandidateIntake = MaxCandidateIntake; type ChallengePeriod = ChallengePeriod; + type MaxPayouts = MaxPayouts; + type MaxBids = MaxBids; + type WeightInfo = pallet_society::weights::SubstrateWeight; } parameter_types! { @@ -1433,7 +1457,6 @@ impl pallet_vesting::Config for Runtime { impl pallet_mmr::Config for Runtime { const INDEXING_PREFIX: &'static [u8] = b"mmr"; type Hashing = ::Hashing; - type Hash = ::Hash; type LeafData = pallet_mmr::ParentNumberAndHash; type OnNewRoot = (); type WeightInfo = (); @@ -1466,7 +1489,7 @@ parameter_types! { pub const MetadataDepositPerByte: Balance = 1 * DOLLARS; } -impl pallet_assets::Config for Runtime { +impl pallet_assets::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Balance = u128; type AssetId = u32; @@ -1489,8 +1512,67 @@ impl pallet_assets::Config for Runtime { type BenchmarkHelper = (); } +ord_parameter_types! { + pub const AssetConversionOrigin: AccountId = AccountIdConversion::::into_account_truncating(&AssetConversionPalletId::get()); +} + +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = u128; + type AssetId = u32; + type AssetIdParameter = codec::Compact; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = EnsureRoot; + type AssetDeposit = AssetDeposit; + type AssetAccountDeposit = ConstU128; + type MetadataDepositBase = MetadataDepositBase; + type MetadataDepositPerByte = MetadataDepositPerByte; + type ApprovalDeposit = ApprovalDeposit; + type StringLimit = StringLimit; + type Freezer = (); + type Extra = (); + type WeightInfo = pallet_assets::weights::SubstrateWeight; + type RemoveItemsLimit = ConstU32<1000>; + type CallbackHandle = (); + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +parameter_types! { + pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon"); + pub AllowMultiAssetPools: bool = true; + pub const PoolSetupFee: Balance = 1 * DOLLARS; // should be more or equal to the existential deposit + pub const MintMinLiquidity: Balance = 100; // 100 is good enough when the main currency has 10-12 decimals. + pub const LiquidityWithdrawalFee: Permill = Permill::from_percent(0); // should be non-zero if AllowMultiAssetPools is true, otherwise can be zero. +} + +impl pallet_asset_conversion::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type AssetBalance = ::Balance; + type HigherPrecisionBalance = sp_core::U256; + type Assets = Assets; + type Balance = u128; + type PoolAssets = PoolAssets; + type AssetId = >::AssetId; + type MultiAssetId = NativeOrAssetId; + type PoolAssetId = >::AssetId; + type PalletId = AssetConversionPalletId; + type LPFee = ConstU32<3>; // means 0.3% + type PoolSetupFee = PoolSetupFee; + type PoolSetupFeeReceiver = AssetConversionOrigin; + type LiquidityWithdrawalFee = LiquidityWithdrawalFee; + type WeightInfo = pallet_asset_conversion::weights::SubstrateWeight; + type AllowMultiAssetPools = AllowMultiAssetPools; + type MaxSwapPathLength = ConstU32<4>; + type MintMinLiquidity = MintMinLiquidity; + type MultiAssetIdConverter = NativeOrAssetIdConverter; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + parameter_types! { - pub IgnoredIssuance: Balance = Treasury::pot(); pub const QueueCount: u32 = 300; pub const MaxQueueLen: u32 = 1000; pub const FifoQueueLen: u32 = 500; @@ -1502,7 +1584,6 @@ parameter_types! { pub const ThawThrottle: (Perquintill, BlockNumber) = (Perquintill::from_percent(25), 5); pub Target: Perquintill = Perquintill::zero(); pub const NisPalletId: PalletId = PalletId(*b"py/nis "); - pub const NisReserveId: [u8; 8] = *b"py/nis "; } impl pallet_nis::Config for Runtime { @@ -1514,7 +1595,7 @@ impl pallet_nis::Config for Runtime { type Counterpart = ItemOf, AccountId>; type CounterpartAmount = WithMaximumOf>; type Deficit = (); - type IgnoredIssuance = IgnoredIssuance; + type IgnoredIssuance = (); type Target = Target; type PalletId = NisPalletId; type QueueCount = QueueCount; @@ -1526,7 +1607,7 @@ impl pallet_nis::Config for Runtime { type IntakePeriod = IntakePeriod; type MaxIntakeWeight = MaxIntakeWeight; type ThawThrottle = ThawThrottle; - type ReserveId = NisReserveId; + type RuntimeHoldReason = RuntimeHoldReason; } parameter_types! { @@ -1561,8 +1642,70 @@ impl pallet_uniques::Config for Runtime { type Locker = (); } +parameter_types! { + pub const Budget: Balance = 10_000 * DOLLARS; + pub TreasuryAccount: AccountId = Treasury::account_id(); +} + +pub struct SalaryForRank; +impl GetSalary for SalaryForRank { + fn get_salary(a: u16, _: &AccountId) -> Balance { + Balance::from(a) * 1000 * DOLLARS + } +} + +impl pallet_salary::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Paymaster = PayFromAccount; + type Members = RankedCollective; + type Salary = SalaryForRank; + type RegistrationPeriod = ConstU32<200>; + type PayoutPeriod = ConstU32<200>; + type Budget = Budget; +} + +impl pallet_core_fellowship::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Members = RankedCollective; + type Balance = Balance; + type ParamsOrigin = frame_system::EnsureRoot; + type InductOrigin = pallet_core_fellowship::EnsureInducted; + type ApproveOrigin = frame_system::EnsureRootWithSuccess>; + type PromoteOrigin = frame_system::EnsureRootWithSuccess>; + type EvidenceSize = ConstU32<16_384>; +} + +parameter_types! { + pub const NftFractionalizationPalletId: PalletId = PalletId(*b"fraction"); + pub NewAssetSymbol: BoundedVec = (*b"FRAC").to_vec().try_into().unwrap(); + pub NewAssetName: BoundedVec = (*b"Frac").to_vec().try_into().unwrap(); +} + +impl pallet_nft_fractionalization::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Deposit = AssetDeposit; + type Currency = Balances; + type NewAssetSymbol = NewAssetSymbol; + type NewAssetName = NewAssetName; + type StringLimit = StringLimit; + type NftCollectionId = ::CollectionId; + type NftId = ::ItemId; + type AssetBalance = ::Balance; + type AssetId = >::AssetId; + type Assets = Assets; + type Nfts = Nfts; + type PalletId = NftFractionalizationPalletId; + type WeightInfo = pallet_nft_fractionalization::weights::SubstrateWeight; + type RuntimeHoldReason = RuntimeHoldReason; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + parameter_types! { pub Features: PalletFeatures = PalletFeatures::all_enabled(); + pub const MaxAttributesPerCall: u32 = 10; } impl pallet_nfts::Config for Runtime { @@ -1583,7 +1726,10 @@ impl pallet_nfts::Config for Runtime { type ItemAttributesApprovalsLimit = ItemAttributesApprovalsLimit; type MaxTips = MaxTips; type MaxDeadlineDuration = MaxDeadlineDuration; + type MaxAttributesPerCall = MaxAttributesPerCall; type Features = Features; + type OffchainSignature = Signature; + type OffchainPublic = ::Signer; type WeightInfo = pallet_nfts::weights::SubstrateWeight; #[cfg(feature = "runtime-benchmarks")] type Helper = (); @@ -1651,6 +1797,8 @@ impl pallet_collective::Config for Runtime { type MaxMembers = AllianceMaxMembers; type DefaultVote = pallet_collective::PrimeDefaultVote; type WeightInfo = pallet_collective::weights::SubstrateWeight; + type SetMembersOrigin = EnsureRoot; + type MaxProposalWeight = MaxCollectivesProposalWeight; } parameter_types! { @@ -1696,11 +1844,32 @@ impl pallet_alliance::Config for Runtime { type RetirementPeriod = RetirementPeriod; } +impl frame_benchmarking_pallet_pov::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + +parameter_types! { + pub StatementCost: Balance = 1 * DOLLARS; + pub StatementByteCost: Balance = 100 * MILLICENTS; + pub const MinAllowedStatements: u32 = 4; + pub const MaxAllowedStatements: u32 = 10; + pub const MinAllowedBytes: u32 = 1024; + pub const MaxAllowedBytes: u32 = 4096; +} + +impl pallet_statement::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type StatementCost = StatementCost; + type ByteCost = StatementByteCost; + type MinAllowedStatements = MinAllowedStatements; + type MaxAllowedStatements = MaxAllowedStatements; + type MinAllowedBytes = MinAllowedBytes; + type MaxAllowedBytes = MaxAllowedBytes; +} + construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = node_primitives::Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { System: frame_system, Utility: pallet_utility, @@ -1713,6 +1882,7 @@ construct_runtime!( Balances: pallet_balances, TransactionPayment: pallet_transaction_payment, AssetTxPayment: pallet_asset_tx_payment, + AssetConversionTxPayment: pallet_asset_conversion_tx_payment, ElectionProviderMultiPhase: pallet_election_provider_multi_phase, Staking: pallet_staking, Session: pallet_session, @@ -1723,29 +1893,35 @@ construct_runtime!( TechnicalMembership: pallet_membership::, Grandpa: pallet_grandpa, Treasury: pallet_treasury, + AssetRate: pallet_asset_rate, Contracts: pallet_contracts, Sudo: pallet_sudo, ImOnline: pallet_im_online, AuthorityDiscovery: pallet_authority_discovery, Offences: pallet_offences, Historical: pallet_session_historical::{Pallet}, - RandomnessCollectiveFlip: pallet_randomness_collective_flip, + RandomnessCollectiveFlip: pallet_insecure_randomness_collective_flip, Identity: pallet_identity, Society: pallet_society, Recovery: pallet_recovery, Vesting: pallet_vesting, Scheduler: pallet_scheduler, + Glutton: pallet_glutton, Preimage: pallet_preimage, Proxy: pallet_proxy, Multisig: pallet_multisig, Bounties: pallet_bounties, Tips: pallet_tips, - Assets: pallet_assets, + Assets: pallet_assets::, + PoolAssets: pallet_assets::, Mmr: pallet_mmr, Lottery: pallet_lottery, Nis: pallet_nis, Uniques: pallet_uniques, Nfts: pallet_nfts, + NftFractionalization: pallet_nft_fractionalization, + Salary: pallet_salary, + CoreFellowship: pallet_core_fellowship, TransactionStorage: pallet_transaction_storage, VoterList: pallet_bags_list::, StateTrieMigration: pallet_state_trie_migration, @@ -1760,8 +1936,11 @@ construct_runtime!( NominationPools: pallet_nomination_pools, RankedPolls: pallet_referenda::, RankedCollective: pallet_ranked_collective, + AssetConversion: pallet_asset_conversion, FastUnstake: pallet_fast_unstake, MessageQueue: pallet_message_queue, + Pov: frame_benchmarking_pallet_pov, + Statement: pallet_statement, } ); @@ -1788,7 +1967,7 @@ pub type SignedExtra = ( frame_system::CheckEra, frame_system::CheckNonce, frame_system::CheckWeight, - pallet_asset_tx_payment::ChargeAssetTxPayment, + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment, ); /// Unchecked extrinsic type as expected by this runtime. @@ -1816,13 +1995,18 @@ type Migrations = ( pallet_contracts::Migration, ); +type EventRecord = frame_system::EventRecord< + ::RuntimeEvent, + ::Hash, +>; + /// MMR helper types. mod mmr { use super::Runtime; pub use pallet_mmr::primitives::*; pub type Leaf = <::LeafData as LeafDataProvider>::LeafData; - pub type Hash = ::Hash; + pub type Hash = ::Output; pub type Hashing = ::Hashing; } @@ -1830,6 +2014,7 @@ mod mmr { mod benches { frame_benchmarking::define_benchmarks!( [frame_benchmarking, BaselineBench::] + [frame_benchmarking_pallet_pov, Pov] [pallet_alliance, Alliance] [pallet_assets, Assets] [pallet_babe, Babe] @@ -1840,7 +2025,9 @@ mod benches { [pallet_collective, Council] [pallet_conviction_voting, ConvictionVoting] [pallet_contracts, Contracts] + [pallet_core_fellowship, CoreFellowship] [pallet_democracy, Democracy] + [pallet_asset_conversion, AssetConversion] [pallet_election_provider_multi_phase, ElectionProviderMultiPhase] [pallet_election_provider_support_benchmarking, EPSBench::] [pallet_elections_phragmen, Elections] @@ -1863,17 +2050,23 @@ mod benches { [pallet_referenda, Referenda] [pallet_recovery, Recovery] [pallet_remark, Remark] + [pallet_salary, Salary] [pallet_scheduler, Scheduler] + [pallet_glutton, Glutton] [pallet_session, SessionBench::] + [pallet_society, Society] [pallet_staking, Staking] [pallet_state_trie_migration, StateTrieMigration] + [pallet_sudo, Sudo] [frame_system, SystemBench::] [pallet_timestamp, Timestamp] [pallet_tips, Tips] [pallet_transaction_storage, TransactionStorage] [pallet_treasury, Treasury] + [pallet_asset_rate, AssetRate] [pallet_uniques, Uniques] [pallet_nfts, Nfts] + [pallet_nft_fractionalization, NftFractionalization] [pallet_utility, Utility] [pallet_vesting, Vesting] [pallet_whitelist, Whitelist] @@ -1899,6 +2092,14 @@ impl_runtime_apis! { 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 { @@ -1929,27 +2130,36 @@ impl_runtime_apis! { } } + impl sp_statement_store::runtime_api::ValidateStatement for Runtime { + fn validate_statement( + source: sp_statement_store::runtime_api::StatementSource, + statement: sp_statement_store::Statement, + ) -> Result { + Statement::validate_statement(source, statement) + } + } + impl sp_offchain::OffchainWorkerApi for Runtime { fn offchain_worker(header: &::Header) { Executive::offchain_worker(header) } } - impl fg_primitives::GrandpaApi for Runtime { - fn grandpa_authorities() -> GrandpaAuthorityList { + impl sp_consensus_grandpa::GrandpaApi for Runtime { + fn grandpa_authorities() -> sp_consensus_grandpa::AuthorityList { Grandpa::grandpa_authorities() } - fn current_set_id() -> fg_primitives::SetId { + fn current_set_id() -> sp_consensus_grandpa::SetId { Grandpa::current_set_id() } fn submit_report_equivocation_unsigned_extrinsic( - equivocation_proof: fg_primitives::EquivocationProof< + equivocation_proof: sp_consensus_grandpa::EquivocationProof< ::Hash, NumberFor, >, - key_owner_proof: fg_primitives::OpaqueKeyOwnershipProof, + key_owner_proof: sp_consensus_grandpa::OpaqueKeyOwnershipProof, ) -> Option<()> { let key_owner_proof = key_owner_proof.decode()?; @@ -1960,20 +2170,34 @@ impl_runtime_apis! { } fn generate_key_ownership_proof( - _set_id: fg_primitives::SetId, + _set_id: sp_consensus_grandpa::SetId, authority_id: GrandpaId, - ) -> Option { + ) -> Option { use codec::Encode; - Historical::prove((fg_primitives::KEY_TYPE, authority_id)) + Historical::prove((sp_consensus_grandpa::KEY_TYPE, authority_id)) .map(|p| p.encode()) - .map(fg_primitives::OpaqueKeyOwnershipProof::new) + .map(sp_consensus_grandpa::OpaqueKeyOwnershipProof::new) } } impl pallet_nomination_pools_runtime_api::NominationPoolsApi for Runtime { - fn pending_rewards(member_account: AccountId) -> Balance { - NominationPools::pending_rewards(member_account).unwrap_or_default() + fn pending_rewards(who: AccountId) -> Balance { + NominationPools::api_pending_rewards(who).unwrap_or_default() + } + + fn points_to_balance(pool_id: pallet_nomination_pools::PoolId, points: Balance) -> Balance { + NominationPools::api_points_to_balance(pool_id, points) + } + + fn balance_to_points(pool_id: pallet_nomination_pools::PoolId, new_funds: Balance) -> Balance { + NominationPools::api_balance_to_points(pool_id, new_funds) + } + } + + impl pallet_staking_runtime_api::StakingApi for Runtime { + fn nominations_quota(balance: Balance) -> u32 { + Staking::api_nominations_quota(balance) } } @@ -2032,13 +2256,25 @@ impl_runtime_apis! { } } - impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { - fn account_nonce(account: AccountId) -> Index { + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Nonce { System::account_nonce(account) } } - impl pallet_contracts::ContractsApi for Runtime + impl assets_api::AssetsApi< + Block, + AccountId, + Balance, + u32, + > for Runtime + { + fn account_balances(account: AccountId) -> Vec<(u32, Balance)> { + Assets::account_balances(account) + } + } + + impl pallet_contracts::ContractsApi for Runtime { fn call( origin: AccountId, @@ -2047,7 +2283,7 @@ impl_runtime_apis! { gas_limit: Option, storage_deposit_limit: Option, input_data: Vec, - ) -> pallet_contracts_primitives::ContractExecResult { + ) -> pallet_contracts_primitives::ContractExecResult { let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block); Contracts::bare_call( origin, @@ -2056,8 +2292,9 @@ impl_runtime_apis! { gas_limit, storage_deposit_limit, input_data, - true, - pallet_contracts::Determinism::Deterministic, + pallet_contracts::DebugInfo::UnsafeDebug, + pallet_contracts::CollectEvents::UnsafeCollect, + pallet_contracts::Determinism::Enforced, ) } @@ -2069,7 +2306,7 @@ impl_runtime_apis! { code: pallet_contracts_primitives::Code, data: Vec, salt: Vec, - ) -> pallet_contracts_primitives::ContractInstantiateResult + ) -> pallet_contracts_primitives::ContractInstantiateResult { let gas_limit = gas_limit.unwrap_or(RuntimeBlockWeights::get().max_block); Contracts::bare_instantiate( @@ -2080,7 +2317,8 @@ impl_runtime_apis! { code, data, salt, - true + pallet_contracts::DebugInfo::UnsafeDebug, + pallet_contracts::CollectEvents::UnsafeCollect, ) } @@ -2128,6 +2366,26 @@ impl_runtime_apis! { } } + impl pallet_asset_conversion::AssetConversionApi< + Block, + Balance, + u128, + NativeOrAssetId + > for Runtime + { + fn quote_price_exact_tokens_for_tokens(asset1: NativeOrAssetId, asset2: NativeOrAssetId, amount: u128, include_fee: bool) -> Option { + AssetConversion::quote_price_exact_tokens_for_tokens(asset1, asset2, amount, include_fee) + } + + fn quote_price_tokens_for_exact_tokens(asset1: NativeOrAssetId, asset2: NativeOrAssetId, amount: u128, include_fee: bool) -> Option { + AssetConversion::quote_price_tokens_for_exact_tokens(asset1, asset2, amount, include_fee) + } + + fn get_reserves(asset1: NativeOrAssetId, asset2: NativeOrAssetId) -> Option<(Balance, Balance)> { + AssetConversion::get_reserves(&asset1, &asset2).ok() + } + } + impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi for Runtime { @@ -2145,6 +2403,50 @@ impl_runtime_apis! { } } + impl pallet_nfts_runtime_api::NftsApi for Runtime { + fn owner(collection: u32, item: u32) -> Option { + >::owner(&collection, &item) + } + + fn collection_owner(collection: u32) -> Option { + >::collection_owner(&collection) + } + + fn attribute( + collection: u32, + item: u32, + key: Vec, + ) -> Option> { + >::attribute(&collection, &item, &key) + } + + fn custom_attribute( + account: AccountId, + collection: u32, + item: u32, + key: Vec, + ) -> Option> { + >::custom_attribute( + &account, + &collection, + &item, + &key, + ) + } + + fn system_attribute( + collection: u32, + item: u32, + key: Vec, + ) -> Option> { + >::system_attribute(&collection, &item, &key) + } + + fn collection_attribute(collection: u32, key: Vec) -> Option> { + >::collection_attribute(&collection, &key) + } + } + impl pallet_mmr::primitives::MmrApi< Block, mmr::Hash, @@ -2299,44 +2601,8 @@ impl_runtime_apis! { mod tests { use super::*; use frame_election_provider_support::NposSolution; - use frame_support::traits::WhitelistedStorageKeys; use frame_system::offchain::CreateSignedTransaction; - use sp_core::hexdisplay::HexDisplay; use sp_runtime::UpperOf; - use std::collections::HashSet; - - #[test] - fn check_whitelist() { - let whitelist: HashSet = AllPalletsWithSystem::whitelisted_storage_keys() - .iter() - .map(|e| HexDisplay::from(&e.key).to_string()) - .collect(); - - // Block Number - assert!( - whitelist.contains("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac") - ); - // Total Issuance - assert!( - whitelist.contains("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80") - ); - // Execution Phase - assert!( - whitelist.contains("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a") - ); - // Event Count - assert!( - whitelist.contains("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850") - ); - // System Events - assert!( - whitelist.contains("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7") - ); - // System BlockWeight - assert!( - whitelist.contains("26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96") - ); - } #[test] fn validate_transaction_submitter_bounds() { @@ -2364,10 +2630,10 @@ mod tests { fn call_size() { let size = core::mem::size_of::(); assert!( - size <= 208, - "size of RuntimeCall {} is more than 208 bytes: some calls have too big arguments, use Box to reduce the - size of RuntimeCall. - If the limit is too strong, maybe consider increase the limit to 300.", + size <= CALL_PARAMS_MAX_SIZE, + "size of RuntimeCall {} is more than {CALL_PARAMS_MAX_SIZE} bytes. + Some calls have too big arguments, use Box to reduce the size of RuntimeCall. + If the limit is too strong, maybe consider increase the limit.", size, ); } diff --git a/bin/node/runtime/src/voter_bags.rs b/bin/node/runtime/src/voter_bags.rs index eb540c27abcc..bf18097ddf53 100644 --- a/bin/node/runtime/src/voter_bags.rs +++ b/bin/node/runtime/src/voter_bags.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/bin/node/testing/Cargo.toml b/bin/node/testing/Cargo.toml index a43b2b9ba13e..711c48c34329 100644 --- a/bin/node/testing/Cargo.toml +++ b/bin/node/testing/Cargo.toml @@ -13,7 +13,7 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } fs_extra = "1" futures = "0.3.21" log = "0.4.17" @@ -22,9 +22,10 @@ frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } node-executor = { version = "3.0.0-dev", path = "../executor" } node-primitives = { version = "2.0.0", path = "../primitives" } kitchensink-runtime = { version = "3.0.0-dev", path = "../runtime" } +pallet-asset-conversion = { version = "4.0.0-dev", path = "../../../frame/asset-conversion" } pallet-assets = { version = "4.0.0-dev", path = "../../../frame/assets" } +pallet-asset-conversion-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/asset-conversion-tx-payment" } pallet-asset-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/asset-tx-payment" } -pallet-transaction-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment" } sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } sc-client-db = { version = "0.10.0-dev", features = ["rocksdb"], path = "../../../client/db" } @@ -38,10 +39,10 @@ sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-io = { version = "7.0.0", path = "../../../primitives/io" } -sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-io = { version = "23.0.0", path = "../../../primitives/io" } +sp-keyring = { version = "24.0.0", path = "../../../primitives/keyring" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/timestamp" } substrate-test-client = { version = "2.0.0", path = "../../../test-utils/client" } diff --git a/bin/node/testing/src/bench.rs b/bin/node/testing/src/bench.rs index 2151a61a6ca7..f1ab2212239b 100644 --- a/bin/node/testing/src/bench.rs +++ b/bin/node/testing/src/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -40,21 +40,17 @@ use kitchensink_runtime::{ }; use node_primitives::Block; use sc_block_builder::BlockBuilderProvider; -use sc_client_api::{ - execution_extensions::{ExecutionExtensions, ExecutionStrategies}, - BlockBackend, ExecutionStrategy, -}; +use sc_client_api::execution_extensions::ExecutionExtensions; use sc_client_db::PruningMode; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy, ImportResult, ImportedAux}; use sc_executor::{NativeElseWasmExecutor, WasmExecutionMethod, WasmtimeInstantiationStrategy}; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder; use sp_consensus::BlockOrigin; -use sp_core::{blake2_256, ed25519, sr25519, traits::SpawnNamed, ExecutionContext, Pair, Public}; +use sp_core::{blake2_256, ed25519, sr25519, traits::SpawnNamed, Pair, Public}; use sp_inherents::InherentData; use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, IdentifyAccount, Verify, Zero}, + traits::{Block as BlockT, IdentifyAccount, Verify}, OpaqueExtrinsic, }; @@ -273,15 +269,11 @@ pub struct BlockContentIterator<'a> { impl<'a> BlockContentIterator<'a> { fn new(content: BlockContent, keyring: &'a BenchKeyring, client: &Client) -> Self { + let genesis_hash = client.chain_info().genesis_hash; let runtime_version = client - .runtime_version_at(&BlockId::number(0)) + .runtime_version_at(genesis_hash) .expect("There should be runtime version at 0"); - let genesis_hash = client - .block_hash(Zero::zero()) - .expect("Database error?") - .expect("Genesis block always exists; qed"); - BlockContentIterator { iteration: 0, content, keyring, runtime_version, genesis_hash } } } @@ -313,7 +305,7 @@ impl<'a> Iterator for BlockContentIterator<'a> { value: kitchensink_runtime::ExistentialDeposit::get() + 1, }), BlockType::RandomTransfersReaping => { - RuntimeCall::Balances(BalancesCall::transfer { + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: sp_runtime::MultiAddress::Id(receiver), // Transfer so that ending balance would be 1 less than existential // deposit so that we kill the sender account. @@ -359,7 +351,7 @@ impl BenchDb { dir.path().to_string_lossy(), ); let (_client, _backend, _task_executor) = - Self::bench_client(database_type, dir.path(), Profile::Native, &keyring); + Self::bench_client(database_type, dir.path(), &keyring); let directory_guard = Guard(dir); BenchDb { keyring, directory_guard, database_type } @@ -385,7 +377,6 @@ impl BenchDb { fn bench_client( database_type: DatabaseType, dir: &std::path::Path, - profile: Profile, keyring: &BenchKeyring, ) -> (Client, std::sync::Arc, TaskExecutor) { let db_config = sc_client_db::DatabaseSettings { @@ -397,14 +388,14 @@ impl BenchDb { let task_executor = TaskExecutor::new(); let backend = sc_service::new_db_backend(db_config).expect("Should not fail"); - let executor = NativeElseWasmExecutor::new( - WasmExecutionMethod::Compiled { - instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, - }, - None, - 8, - 2, + let executor = NativeElseWasmExecutor::new_with_wasm_executor( + sc_executor::WasmExecutor::builder() + .with_execution_method(WasmExecutionMethod::Compiled { + instantiation_strategy: WasmtimeInstantiationStrategy::PoolingCopyOnWrite, + }) + .build(), ); + let client_config = sc_service::ClientConfig::default(); let genesis_block_builder = sc_service::GenesisBlockBuilder::new( &keyring.generate_genesis(), @@ -416,11 +407,11 @@ impl BenchDb { let client = sc_service::new_client( backend.clone(), - executor, + executor.clone(), genesis_block_builder, None, None, - ExecutionExtensions::new(profile.into_execution_strategies(), None, None), + ExecutionExtensions::new(None, Arc::new(executor)), Box::new(task_executor.clone()), None, None, @@ -444,11 +435,7 @@ impl BenchDb { client .runtime_api() - .inherent_extrinsics_with_context( - &BlockId::number(0), - ExecutionContext::BlockConstruction, - inherent_data, - ) + .inherent_extrinsics(client.chain_info().genesis_hash, inherent_data) .expect("Get inherents failed") } @@ -459,12 +446,8 @@ impl BenchDb { /// Get cliet for this database operations. pub fn client(&mut self) -> Client { - let (client, _backend, _task_executor) = Self::bench_client( - self.database_type, - self.directory_guard.path(), - Profile::Wasm, - &self.keyring, - ); + let (client, _backend, _task_executor) = + Self::bench_client(self.database_type, self.directory_guard.path(), &self.keyring); client } @@ -507,10 +490,10 @@ impl BenchDb { } /// Clone this database and create context for testing/benchmarking. - pub fn create_context(&self, profile: Profile) -> BenchContext { + pub fn create_context(&self) -> BenchContext { let BenchDb { directory_guard, keyring, database_type } = self.clone(); let (client, backend, task_executor) = - Self::bench_client(database_type, directory_guard.path(), profile, &keyring); + Self::bench_client(database_type, directory_guard.path(), &keyring); BenchContext { client: Arc::new(client), @@ -603,7 +586,7 @@ impl BenchKeyring { } /// Generate genesis with accounts from this keyring endowed with some balance. - pub fn generate_genesis(&self) -> kitchensink_runtime::GenesisConfig { + pub fn generate_genesis(&self) -> kitchensink_runtime::RuntimeGenesisConfig { crate::genesis::config_endowed( Some(kitchensink_runtime::wasm_binary_unwrap()), self.collect_account_ids(), @@ -611,36 +594,6 @@ impl BenchKeyring { } } -/// Profile for exetion strategies. -#[derive(Clone, Copy, Debug)] -pub enum Profile { - /// As native as possible. - Native, - /// As wasm as possible. - Wasm, -} - -impl Profile { - fn into_execution_strategies(self) -> ExecutionStrategies { - match self { - Profile::Wasm => ExecutionStrategies { - syncing: ExecutionStrategy::AlwaysWasm, - importing: ExecutionStrategy::AlwaysWasm, - block_construction: ExecutionStrategy::AlwaysWasm, - offchain_worker: ExecutionStrategy::AlwaysWasm, - other: ExecutionStrategy::AlwaysWasm, - }, - Profile::Native => ExecutionStrategies { - syncing: ExecutionStrategy::NativeElseWasm, - importing: ExecutionStrategy::NativeElseWasm, - block_construction: ExecutionStrategy::NativeElseWasm, - offchain_worker: ExecutionStrategy::NativeElseWasm, - other: ExecutionStrategy::NativeElseWasm, - }, - } - } -} - struct Guard(tempfile::TempDir); impl Guard { @@ -687,10 +640,8 @@ impl BenchContext { assert_eq!(self.client.chain_info().best_number, 0); assert_eq!( - futures::executor::block_on( - self.client.import_block(import_params, Default::default()) - ) - .expect("Failed to import block"), + futures::executor::block_on(self.client.import_block(import_params)) + .expect("Failed to import block"), ImportResult::Imported(ImportedAux { header_only: false, clear_justification_requests: false, diff --git a/bin/node/testing/src/client.rs b/bin/node/testing/src/client.rs index 590304bdd52a..01495525a9c8 100644 --- a/bin/node/testing/src/client.rs +++ b/bin/node/testing/src/client.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,7 +18,6 @@ //! Utilities to build a `TestClient` for `kitchensink-runtime`. -use sc_service::client; use sp_runtime::BuildStorage; /// Re-export test-client utilities. pub use substrate_test_client::*; diff --git a/bin/node/testing/src/genesis.rs b/bin/node/testing/src/genesis.rs index b207fc7f98ab..8063b8ef4570 100644 --- a/bin/node/testing/src/genesis.rs +++ b/bin/node/testing/src/genesis.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -21,20 +21,21 @@ use crate::keyring::*; use kitchensink_runtime::{ constants::currency::*, wasm_binary_unwrap, AccountId, AssetsConfig, BabeConfig, - BalancesConfig, GenesisConfig, GrandpaConfig, IndicesConfig, SessionConfig, SocietyConfig, - StakerStatus, StakingConfig, SystemConfig, BABE_GENESIS_EPOCH_CONFIG, + BalancesConfig, GluttonConfig, GrandpaConfig, IndicesConfig, RuntimeGenesisConfig, + SessionConfig, SocietyConfig, StakerStatus, StakingConfig, SystemConfig, + BABE_GENESIS_EPOCH_CONFIG, }; use sp_keyring::{Ed25519Keyring, Sr25519Keyring}; use sp_runtime::Perbill; /// Create genesis runtime configuration for tests. -pub fn config(code: Option<&[u8]>) -> GenesisConfig { +pub fn config(code: Option<&[u8]>) -> RuntimeGenesisConfig { config_endowed(code, Default::default()) } /// Create genesis runtime configuration for tests with some extra /// endowed accounts. -pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> GenesisConfig { +pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> RuntimeGenesisConfig { let mut endowed = vec![ (alice(), 111 * DOLLARS), (bob(), 100 * DOLLARS), @@ -46,9 +47,10 @@ pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> Gen endowed.extend(extra_endowed.into_iter().map(|endowed| (endowed, 100 * DOLLARS))); - GenesisConfig { + RuntimeGenesisConfig { system: SystemConfig { code: code.map(|x| x.to_vec()).unwrap_or_else(|| wasm_binary_unwrap().to_vec()), + ..Default::default() }, indices: IndicesConfig { indices: vec![] }, balances: BalancesConfig { balances: endowed }, @@ -65,9 +67,9 @@ pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> Gen }, staking: StakingConfig { stakers: vec![ - (dave(), alice(), 111 * DOLLARS, StakerStatus::Validator), - (eve(), bob(), 100 * DOLLARS, StakerStatus::Validator), - (ferdie(), charlie(), 100 * DOLLARS, StakerStatus::Validator), + (dave(), dave(), 111 * DOLLARS, StakerStatus::Validator), + (eve(), eve(), 100 * DOLLARS, StakerStatus::Validator), + (ferdie(), ferdie(), 100 * DOLLARS, StakerStatus::Validator), ], validator_count: 3, minimum_validator_count: 0, @@ -75,8 +77,12 @@ pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> Gen invulnerables: vec![alice(), bob(), charlie()], ..Default::default() }, - babe: BabeConfig { authorities: vec![], epoch_config: Some(BABE_GENESIS_EPOCH_CONFIG) }, - grandpa: GrandpaConfig { authorities: vec![] }, + babe: BabeConfig { + authorities: vec![], + epoch_config: Some(BABE_GENESIS_EPOCH_CONFIG), + ..Default::default() + }, + grandpa: GrandpaConfig { authorities: vec![], _config: Default::default() }, im_online: Default::default(), authority_discovery: Default::default(), democracy: Default::default(), @@ -86,13 +92,20 @@ pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> Gen elections: Default::default(), sudo: Default::default(), treasury: Default::default(), - society: SocietyConfig { members: vec![alice(), bob()], pot: 0, max_members: 999 }, + society: SocietyConfig { pot: 0 }, vesting: Default::default(), assets: AssetsConfig { assets: vec![(9, alice(), true, 1)], ..Default::default() }, + pool_assets: Default::default(), transaction_storage: Default::default(), transaction_payment: Default::default(), alliance: Default::default(), alliance_motion: Default::default(), nomination_pools: Default::default(), + glutton: GluttonConfig { + compute: Default::default(), + storage: Default::default(), + trash_data_count: Default::default(), + ..Default::default() + }, } } diff --git a/bin/node/testing/src/keyring.rs b/bin/node/testing/src/keyring.rs index 88d9dc56b053..b4b714d9083d 100644 --- a/bin/node/testing/src/keyring.rs +++ b/bin/node/testing/src/keyring.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -20,7 +20,7 @@ use codec::Encode; use kitchensink_runtime::{CheckedExtrinsic, SessionKeys, SignedExtra, UncheckedExtrinsic}; -use node_primitives::{AccountId, Balance, Index}; +use node_primitives::{AccountId, Balance, Nonce}; use sp_keyring::{AccountKeyring, Ed25519Keyring, Sr25519Keyring}; use sp_runtime::generic::Era; @@ -68,7 +68,7 @@ pub fn to_session_keys( } /// Returns transaction extra. -pub fn signed_extra(nonce: Index, extra_fee: Balance) -> SignedExtra { +pub fn signed_extra(nonce: Nonce, extra_fee: Balance) -> SignedExtra { ( frame_system::CheckNonZeroSender::new(), frame_system::CheckSpecVersion::new(), @@ -77,7 +77,7 @@ pub fn signed_extra(nonce: Index, extra_fee: Balance) -> SignedExtra { frame_system::CheckEra::from(Era::mortal(256, 0)), frame_system::CheckNonce::from(nonce), frame_system::CheckWeight::new(), - pallet_asset_tx_payment::ChargeAssetTxPayment::from(extra_fee, None), + pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(extra_fee, None), ) } diff --git a/bin/node/testing/src/lib.rs b/bin/node/testing/src/lib.rs index ce3471e5b1f8..14e540906fc9 100644 --- a/bin/node/testing/src/lib.rs +++ b/bin/node/testing/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/utils/chain-spec-builder/Cargo.toml b/bin/utils/chain-spec-builder/Cargo.toml index e1d720f673ae..412b41009340 100644 --- a/bin/utils/chain-spec-builder/Cargo.toml +++ b/bin/utils/chain-spec-builder/Cargo.toml @@ -15,10 +15,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] ansi_term = "0.12.1" -clap = { version = "4.0.9", features = ["derive"] } +clap = { version = "4.2.5", features = ["derive"] } rand = "0.8" node-cli = { version = "3.0.0-dev", path = "../../node/cli" } sc-chain-spec = { version = "4.0.0-dev", path = "../../../client/chain-spec" } sc-keystore = { version = "4.0.0-dev", path = "../../../client/keystore" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-keystore = { version = "0.27.0", path = "../../../primitives/keystore" } diff --git a/bin/utils/chain-spec-builder/build.rs b/bin/utils/chain-spec-builder/build.rs index b700b28e322c..a68cb706e8fb 100644 --- a/bin/utils/chain-spec-builder/build.rs +++ b/bin/utils/chain-spec-builder/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/utils/chain-spec-builder/src/main.rs b/bin/utils/chain-spec-builder/src/main.rs index 7dfcaed773f4..deb191a67cfb 100644 --- a/bin/utils/chain-spec-builder/src/main.rs +++ b/bin/utils/chain-spec-builder/src/main.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -19,7 +19,6 @@ use std::{ fs, path::{Path, PathBuf}, - sync::Arc, }; use ansi_term::Style; @@ -32,7 +31,7 @@ use sp_core::{ crypto::{ByteArray, Ss58Codec}, sr25519, }; -use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; +use sp_keystore::KeystorePtr; /// A utility to easily create a testnet chain spec definition with a given set /// of authorities and endowed accounts and/or generate random accounts. @@ -101,7 +100,7 @@ fn genesis_constructor( nominator_accounts: &[AccountId], endowed_accounts: &[AccountId], sudo_account: &AccountId, -) -> chain_spec::GenesisConfig { +) -> chain_spec::RuntimeGenesisConfig { let authorities = authority_seeds .iter() .map(AsRef::as_ref) @@ -164,16 +163,17 @@ fn generate_chain_spec( fn generate_authority_keys_and_store(seeds: &[String], keystore_path: &Path) -> Result<(), String> { for (n, seed) in seeds.iter().enumerate() { - let keystore: SyncCryptoStorePtr = Arc::new( + let keystore: KeystorePtr = LocalKeystore::open(keystore_path.join(format!("auth-{}", n)), None) - .map_err(|err| err.to_string())?, - ); + .map_err(|err| err.to_string())? + .into(); let (_, _, grandpa, babe, im_online, authority_discovery) = chain_spec::authority_keys_from_seed(seed); let insert_key = |key_type, public| { - SyncCryptoStore::insert_unknown(&*keystore, key_type, &format!("//{}", seed), public) + keystore + .insert(key_type, &format!("//{}", seed), public) .map_err(|_| format!("Failed to insert key: {}", grandpa)) }; diff --git a/bin/utils/subkey/Cargo.toml b/bin/utils/subkey/Cargo.toml index 77c323821a50..9e3abb804f3b 100644 --- a/bin/utils/subkey/Cargo.toml +++ b/bin/utils/subkey/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "subkey" -version = "2.0.2" +version = "3.0.0" authors = ["Parity Technologies "] edition = "2021" license = "GPL-3.0-or-later WITH Classpath-exception-2.0" @@ -17,5 +17,5 @@ path = "src/main.rs" name = "subkey" [dependencies] -clap = { version = "4.0.9", features = ["derive"] } +clap = { version = "4.2.5", features = ["derive"] } sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } diff --git a/bin/utils/subkey/README.md b/bin/utils/subkey/README.md index e762a42c3e79..d19ccefb59aa 100644 --- a/bin/utils/subkey/README.md +++ b/bin/utils/subkey/README.md @@ -6,11 +6,11 @@ Subkey is a commandline utility included with Substrate. It allows generating an You can see the full list of commands with `subkey --help`. Most commands have additional help available with for instance `subkey generate --help` for the `generate` command. -## Satefy first +## Safety first `subkey` does not need an internet connection to work. Indeed, for the best security, you should be using `subkey` on a machine that is **not connected** to the internet. -`subkey` deals with **seeds** and **private keys**. Make sure to use `subkey` in a safe environment (ie. no one looking over your shoulder) and on a safe computer (ie. no one able to check you commands history). +`subkey` deals with **seeds** and **private keys**. Make sure to use `subkey` in a safe environment (ie. no one looking over your shoulder) and on a safe computer (ie. no one able to check your command history). If you save any output of `subkey` into a file, make sure to apply proper permissions and/or delete the file as soon as possible. @@ -25,10 +25,9 @@ You will need to have the Substrate build dependencies to install Subkey. Use th Command: ```bash -# Use the `--fast` flag to get the dependencies without needing to install the Substrate and Subkey binary -curl https://getsubstrate.io -sSf | bash -s -- --fast # Install only `subkey`, at a specific version of the subkey crate cargo install --force subkey --git https://github.com/paritytech/substrate --version --locked +# If you run into issues building, you likely are missing deps defined in https://docs.substrate.io/install/ ``` ### Run in a container @@ -69,7 +68,7 @@ The output above also show the **public key** and the **Account ID**. Those are The **SS58 address** (or **Public Address**) of a new account is a reprensentation of the public keys of an account for a given network (for instance Kusama or Polkadot). -You can read more about the SS58 format in the [Substrate Docs](https://docs.substrate.io/v3/advanced/ss58/) and see the list of reserved prefixes in the [SS58 Registry](https://github.com/paritytech/ss58-registry). +You can read more about the [SS58 format in the Substrate Docs](https://docs.substrate.io/reference/address-formats/) and see the list of reserved prefixes in the [SS58 Registry](https://github.com/paritytech/ss58-registry). For instance, considering the previous seed `0xa05c75731970cc7868a2fb7cb577353cd5b31f62dccced92c441acd8fee0c92d` the SS58 addresses are: @@ -255,7 +254,7 @@ Secret Key URI `0x8c9a73097f235b84021a446bc2826a00c690ea0be3e0d81a84931cb4146d66 SS58 Address: 1bobYxBPjZWRPbVo35aSwci1u5Zmq8P6J2jpa4kkudBZMqE ``` -`Bob` now got a nice address starting with his name: 1**bob**YxBPjZWRPbVo35aSwci1u5Zmq8P6J2jpa4kkudBZMqE. +`Bob` now got a nice address starting with their name: 1**bob**YxBPjZWRPbVo35aSwci1u5Zmq8P6J2jpa4kkudBZMqE. **Note**: While `Bob`, having a short name (3 chars), got a result rather quickly, it will take much longer for `Alice` who has a much longer name, thus the chances to generate a random address that contains the chain `alice` will be much smaller. diff --git a/bin/utils/subkey/src/lib.rs b/bin/utils/subkey/src/lib.rs index 280b90848fbf..201d4d25f84a 100644 --- a/bin/utils/subkey/src/lib.rs +++ b/bin/utils/subkey/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/bin/utils/subkey/src/main.rs b/bin/utils/subkey/src/main.rs index 271388549bcf..d836a9b8f8cc 100644 --- a/bin/utils/subkey/src/main.rs +++ b/bin/utils/subkey/src/main.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/allocator/Cargo.toml b/client/allocator/Cargo.toml index 729decb5ebb3..d7e1f5198255 100644 --- a/client/allocator/Cargo.toml +++ b/client/allocator/Cargo.toml @@ -16,5 +16,5 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = "0.4.17" thiserror = "1.0.30" -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-wasm-interface = { version = "7.0.0", path = "../../primitives/wasm-interface" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-wasm-interface = { version = "14.0.0", path = "../../primitives/wasm-interface" } diff --git a/client/allocator/src/error.rs b/client/allocator/src/error.rs index d9fc483224ad..08d84317b650 100644 --- a/client/allocator/src/error.rs +++ b/client/allocator/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +16,7 @@ // limitations under the License. /// The error type used by the allocators. -#[derive(thiserror::Error, Debug)] +#[derive(thiserror::Error, Debug, PartialEq)] pub enum Error { /// Someone tried to allocate more memory than the allowed maximum per allocation. #[error("Requested allocation size is too large")] diff --git a/client/allocator/src/freeing_bump.rs b/client/allocator/src/freeing_bump.rs index e81d1b79e74e..144c0764540d 100644 --- a/client/allocator/src/freeing_bump.rs +++ b/client/allocator/src/freeing_bump.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -67,10 +67,11 @@ //! wasted. This is more pronounced (in terms of absolute heap amounts) with larger allocation //! sizes. -use crate::Error; +use crate::{Error, Memory, MAX_WASM_PAGES, PAGE_SIZE}; pub use sp_core::MAX_POSSIBLE_ALLOCATION; use sp_wasm_interface::{Pointer, WordSize}; use std::{ + cmp::{max, min}, mem, ops::{Index, IndexMut, Range}, }; @@ -237,7 +238,7 @@ impl Header { /// /// Returns an error if the `header_ptr` is out of bounds of the linear memory or if the read /// header is corrupted (e.g. the order is incorrect). - fn read_from(memory: &M, header_ptr: u32) -> Result { + fn read_from(memory: &impl Memory, header_ptr: u32) -> Result { let raw_header = memory.read_le_u64(header_ptr)?; // Check if the header represents an occupied or free allocation and extract the header data @@ -255,7 +256,7 @@ impl Header { /// Write out this header to memory. /// /// Returns an error if the `header_ptr` is out of bounds of the linear memory. - fn write_into(&self, memory: &mut M, header_ptr: u32) -> Result<(), Error> { + fn write_into(&self, memory: &mut impl Memory, header_ptr: u32) -> Result<(), Error> { let (header_data, occupied_mask) = match *self { Self::Occupied(order) => (order.into_raw(), 0x00000001_00000000), Self::Free(link) => (link.into_raw(), 0x00000000_00000000), @@ -343,6 +344,15 @@ pub struct AllocationStats { pub address_space_used: u32, } +/// Convert the given `size` in bytes into the number of pages. +/// +/// The returned number of pages is ensured to be big enough to hold memory with the given `size`. +/// +/// Returns `None` if the number of pages to not fit into `u32`. +fn pages_from_size(size: u64) -> Option { + u32::try_from((size + PAGE_SIZE as u64 - 1) / PAGE_SIZE as u64).ok() +} + /// An implementation of freeing bump allocator. /// /// Refer to the module-level documentation for further details. @@ -351,7 +361,7 @@ pub struct FreeingBumpHeapAllocator { bumper: u32, free_lists: FreeLists, poisoned: bool, - last_observed_memory_size: u32, + last_observed_memory_size: u64, stats: AllocationStats, } @@ -395,9 +405,9 @@ impl FreeingBumpHeapAllocator { /// /// - `mem` - a slice representing the linear memory on which this allocator operates. /// - `size` - size in bytes of the allocation request - pub fn allocate( + pub fn allocate( &mut self, - mem: &mut M, + mem: &mut impl Memory, size: WordSize, ) -> Result, Error> { if self.poisoned { @@ -411,11 +421,11 @@ impl FreeingBumpHeapAllocator { let header_ptr: u32 = match self.free_lists[order] { Link::Ptr(header_ptr) => { - assert!( - header_ptr + order.size() + HEADER_SIZE <= mem.size(), - "Pointer is looked up in list of free entries, into which - only valid values are inserted; qed" - ); + if (u64::from(header_ptr) + u64::from(order.size()) + u64::from(HEADER_SIZE)) > + mem.size() + { + return Err(error("Invalid header pointer detected")) + } // Remove this header from the free list. let next_free = Header::read_from(mem, header_ptr)? @@ -427,7 +437,7 @@ impl FreeingBumpHeapAllocator { }, Link::Nil => { // Corresponding free list is empty. Allocate a new item. - Self::bump(&mut self.bumper, order.size() + HEADER_SIZE, mem.size())? + Self::bump(&mut self.bumper, order.size() + HEADER_SIZE, mem)? }, }; @@ -437,7 +447,7 @@ impl FreeingBumpHeapAllocator { self.stats.bytes_allocated += order.size() + HEADER_SIZE; self.stats.bytes_allocated_sum += u128::from(order.size() + HEADER_SIZE); self.stats.bytes_allocated_peak = - std::cmp::max(self.stats.bytes_allocated_peak, self.stats.bytes_allocated); + max(self.stats.bytes_allocated_peak, self.stats.bytes_allocated); self.stats.address_space_used = self.bumper - self.original_heap_base; log::trace!(target: LOG_TARGET, "after allocation: {:?}", self.stats); @@ -457,11 +467,7 @@ impl FreeingBumpHeapAllocator { /// /// - `mem` - a slice representing the linear memory on which this allocator operates. /// - `ptr` - pointer to the allocated chunk - pub fn deallocate( - &mut self, - mem: &mut M, - ptr: Pointer, - ) -> Result<(), Error> { + pub fn deallocate(&mut self, mem: &mut impl Memory, ptr: Pointer) -> Result<(), Error> { if self.poisoned { return Err(error("the allocator has been poisoned")) } @@ -503,15 +509,52 @@ impl FreeingBumpHeapAllocator { /// /// Returns the `bumper` from before the increase. Returns an `Error::AllocatorOutOfSpace` if /// the operation would exhaust the heap. - fn bump(bumper: &mut u32, size: u32, heap_end: u32) -> Result { - if *bumper + size > heap_end { - log::error!( - target: LOG_TARGET, - "running out of space with current bumper {}, mem size {}", - bumper, - heap_end + fn bump(bumper: &mut u32, size: u32, memory: &mut impl Memory) -> Result { + let required_size = u64::from(*bumper) + u64::from(size); + + if required_size > memory.size() { + let required_pages = + pages_from_size(required_size).ok_or_else(|| Error::AllocatorOutOfSpace)?; + + let current_pages = memory.pages(); + let max_pages = memory.max_pages().unwrap_or(MAX_WASM_PAGES); + debug_assert!( + current_pages < required_pages, + "current pages {current_pages} < required pages {required_pages}" ); - return Err(Error::AllocatorOutOfSpace) + + if current_pages >= max_pages { + log::debug!( + target: LOG_TARGET, + "Wasm pages ({current_pages}) are already at the maximum.", + ); + + return Err(Error::AllocatorOutOfSpace) + } else if required_pages > max_pages { + log::debug!( + target: LOG_TARGET, + "Failed to grow memory from {current_pages} pages to at least {required_pages}\ + pages due to the maximum limit of {max_pages} pages", + ); + return Err(Error::AllocatorOutOfSpace) + } + + // Ideally we want to double our current number of pages, + // as long as it's less than the absolute maximum we can have. + let next_pages = min(current_pages * 2, max_pages); + // ...but if even more pages are required then try to allocate that many. + let next_pages = max(next_pages, required_pages); + + if memory.grow(next_pages - current_pages).is_err() { + log::error!( + target: LOG_TARGET, + "Failed to grow memory from {current_pages} pages to {next_pages} pages", + ); + + return Err(Error::AllocatorOutOfSpace) + } + + debug_assert_eq!(memory.pages(), next_pages, "Number of pages should have increased!"); } let res = *bumper; @@ -519,9 +562,9 @@ impl FreeingBumpHeapAllocator { Ok(res) } - fn observe_memory_size( - last_observed_memory_size: &mut u32, - mem: &mut M, + fn observe_memory_size( + last_observed_memory_size: &mut u64, + mem: &mut impl Memory, ) -> Result<(), Error> { if mem.size() < *last_observed_memory_size { return Err(Error::MemoryShrinked) @@ -538,38 +581,42 @@ impl FreeingBumpHeapAllocator { /// accessible up to the reported size. /// /// The linear memory can grow in size with the wasm page granularity (64KiB), but it cannot shrink. -pub trait Memory { +trait MemoryExt: Memory { /// Read a u64 from the heap in LE form. Returns an error if any of the bytes read are out of /// bounds. - fn read_le_u64(&self, ptr: u32) -> Result; - /// Write a u64 to the heap in LE form. Returns an error if any of the bytes written are out of - /// bounds. - fn write_le_u64(&mut self, ptr: u32, val: u64) -> Result<(), Error>; - /// Returns the full size of the memory in bytes. - fn size(&self) -> u32; -} - -impl Memory for [u8] { fn read_le_u64(&self, ptr: u32) -> Result { - let range = - heap_range(ptr, 8, self.len()).ok_or_else(|| error("read out of heap bounds"))?; - let bytes = self[range] - .try_into() - .expect("[u8] slice of length 8 must be convertible to [u8; 8]"); - Ok(u64::from_le_bytes(bytes)) + self.with_access(|memory| { + let range = + heap_range(ptr, 8, memory.len()).ok_or_else(|| error("read out of heap bounds"))?; + let bytes = memory[range] + .try_into() + .expect("[u8] slice of length 8 must be convertible to [u8; 8]"); + Ok(u64::from_le_bytes(bytes)) + }) } + + /// Write a u64 to the heap in LE form. Returns an error if any of the bytes written are out of + /// bounds. fn write_le_u64(&mut self, ptr: u32, val: u64) -> Result<(), Error> { - let range = - heap_range(ptr, 8, self.len()).ok_or_else(|| error("write out of heap bounds"))?; - let bytes = val.to_le_bytes(); - self[range].copy_from_slice(&bytes[..]); - Ok(()) + self.with_access_mut(|memory| { + let range = heap_range(ptr, 8, memory.len()) + .ok_or_else(|| error("write out of heap bounds"))?; + let bytes = val.to_le_bytes(); + memory[range].copy_from_slice(&bytes[..]); + Ok(()) + }) } - fn size(&self) -> u32 { - u32::try_from(self.len()).expect("size of Wasm linear memory is <2^32; qed") + + /// Returns the full size of the memory in bytes. + fn size(&self) -> u64 { + debug_assert!(self.pages() <= MAX_WASM_PAGES); + + self.pages() as u64 * PAGE_SIZE as u64 } } +impl MemoryExt for T {} + fn heap_range(offset: u32, length: u32, heap_len: usize) -> Option> { let start = offset as usize; let end = offset.checked_add(length)? as usize; @@ -601,21 +648,72 @@ impl<'a> Drop for PoisonBomb<'a> { mod tests { use super::*; - const PAGE_SIZE: u32 = 65536; - /// Makes a pointer out of the given address. fn to_pointer(address: u32) -> Pointer { Pointer::new(address) } + #[derive(Debug)] + struct MemoryInstance { + data: Vec, + max_wasm_pages: u32, + } + + impl MemoryInstance { + fn with_pages(pages: u32) -> Self { + Self { data: vec![0; (pages * PAGE_SIZE) as usize], max_wasm_pages: MAX_WASM_PAGES } + } + + fn set_max_wasm_pages(&mut self, max_pages: u32) { + self.max_wasm_pages = max_pages; + } + } + + impl Memory for MemoryInstance { + fn with_access(&self, run: impl FnOnce(&[u8]) -> R) -> R { + run(&self.data) + } + + fn with_access_mut(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R { + run(&mut self.data) + } + + fn pages(&self) -> u32 { + pages_from_size(self.data.len() as u64).unwrap() + } + + fn max_pages(&self) -> Option { + Some(self.max_wasm_pages) + } + + fn grow(&mut self, pages: u32) -> Result<(), ()> { + if self.pages() + pages > self.max_wasm_pages { + Err(()) + } else { + self.data.resize(((self.pages() + pages) * PAGE_SIZE) as usize, 0); + Ok(()) + } + } + } + + #[test] + fn test_pages_from_size() { + assert_eq!(pages_from_size(0).unwrap(), 0); + assert_eq!(pages_from_size(1).unwrap(), 1); + assert_eq!(pages_from_size(65536).unwrap(), 1); + assert_eq!(pages_from_size(65536 + 1).unwrap(), 2); + assert_eq!(pages_from_size(2 * 65536).unwrap(), 2); + assert_eq!(pages_from_size(2 * 65536 + 1).unwrap(), 3); + } + #[test] fn should_allocate_properly() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); // when - let ptr = heap.allocate(&mut mem[..], 1).unwrap(); + let ptr = heap.allocate(&mut mem, 1).unwrap(); // then // returned pointer must start right after `HEADER_SIZE` @@ -625,11 +723,11 @@ mod tests { #[test] fn should_always_align_pointers_to_multiples_of_8() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(13); // when - let ptr = heap.allocate(&mut mem[..], 1).unwrap(); + let ptr = heap.allocate(&mut mem, 1).unwrap(); // then // the pointer must start at the next multiple of 8 from 13 @@ -640,13 +738,13 @@ mod tests { #[test] fn should_increment_pointers_properly() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); // when - let ptr1 = heap.allocate(&mut mem[..], 1).unwrap(); - let ptr2 = heap.allocate(&mut mem[..], 9).unwrap(); - let ptr3 = heap.allocate(&mut mem[..], 1).unwrap(); + let ptr1 = heap.allocate(&mut mem, 1).unwrap(); + let ptr2 = heap.allocate(&mut mem, 9).unwrap(); + let ptr3 = heap.allocate(&mut mem, 1).unwrap(); // then // a prefix of 8 bytes is prepended to each pointer @@ -663,18 +761,18 @@ mod tests { #[test] fn should_free_properly() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); - let ptr1 = heap.allocate(&mut mem[..], 1).unwrap(); + let ptr1 = heap.allocate(&mut mem, 1).unwrap(); // the prefix of 8 bytes is prepended to the pointer assert_eq!(ptr1, to_pointer(HEADER_SIZE)); - let ptr2 = heap.allocate(&mut mem[..], 1).unwrap(); + let ptr2 = heap.allocate(&mut mem, 1).unwrap(); // the prefix of 8 bytes + the content of ptr 1 is prepended to the pointer assert_eq!(ptr2, to_pointer(24)); // when - heap.deallocate(&mut mem[..], ptr2).unwrap(); + heap.deallocate(&mut mem, ptr2).unwrap(); // then // then the heads table should contain a pointer to the @@ -685,23 +783,23 @@ mod tests { #[test] fn should_deallocate_and_reallocate_properly() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::with_pages(1); let padded_offset = 16; let mut heap = FreeingBumpHeapAllocator::new(13); - let ptr1 = heap.allocate(&mut mem[..], 1).unwrap(); + let ptr1 = heap.allocate(&mut mem, 1).unwrap(); // the prefix of 8 bytes is prepended to the pointer assert_eq!(ptr1, to_pointer(padded_offset + HEADER_SIZE)); - let ptr2 = heap.allocate(&mut mem[..], 9).unwrap(); + let ptr2 = heap.allocate(&mut mem, 9).unwrap(); // the padded_offset + the previously allocated ptr (8 bytes prefix + // 8 bytes content) + the prefix of 8 bytes which is prepended to the // current pointer assert_eq!(ptr2, to_pointer(padded_offset + 16 + HEADER_SIZE)); // when - heap.deallocate(&mut mem[..], ptr2).unwrap(); - let ptr3 = heap.allocate(&mut mem[..], 9).unwrap(); + heap.deallocate(&mut mem, ptr2).unwrap(); + let ptr3 = heap.allocate(&mut mem, 9).unwrap(); // then // should have re-allocated @@ -712,22 +810,22 @@ mod tests { #[test] fn should_build_linked_list_of_free_areas_properly() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); - let ptr1 = heap.allocate(&mut mem[..], 8).unwrap(); - let ptr2 = heap.allocate(&mut mem[..], 8).unwrap(); - let ptr3 = heap.allocate(&mut mem[..], 8).unwrap(); + let ptr1 = heap.allocate(&mut mem, 8).unwrap(); + let ptr2 = heap.allocate(&mut mem, 8).unwrap(); + let ptr3 = heap.allocate(&mut mem, 8).unwrap(); // when - heap.deallocate(&mut mem[..], ptr1).unwrap(); - heap.deallocate(&mut mem[..], ptr2).unwrap(); - heap.deallocate(&mut mem[..], ptr3).unwrap(); + heap.deallocate(&mut mem, ptr1).unwrap(); + heap.deallocate(&mut mem, ptr2).unwrap(); + heap.deallocate(&mut mem, ptr3).unwrap(); // then assert_eq!(heap.free_lists.heads[0], Link::Ptr(u32::from(ptr3) - HEADER_SIZE)); - let ptr4 = heap.allocate(&mut mem[..], 8).unwrap(); + let ptr4 = heap.allocate(&mut mem, 8).unwrap(); assert_eq!(ptr4, ptr3); assert_eq!(heap.free_lists.heads[0], Link::Ptr(u32::from(ptr2) - HEADER_SIZE)); @@ -736,29 +834,28 @@ mod tests { #[test] fn should_not_allocate_if_too_large() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::with_pages(1); + mem.set_max_wasm_pages(1); let mut heap = FreeingBumpHeapAllocator::new(13); // when - let ptr = heap.allocate(&mut mem[..], PAGE_SIZE - 13); + let ptr = heap.allocate(&mut mem, PAGE_SIZE - 13); // then - match ptr.unwrap_err() { - Error::AllocatorOutOfSpace => {}, - e => panic!("Expected allocator out of space error, got: {:?}", e), - } + assert_eq!(Error::AllocatorOutOfSpace, ptr.unwrap_err()); } #[test] fn should_not_allocate_if_full() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::with_pages(1); + mem.set_max_wasm_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); - let ptr1 = heap.allocate(&mut mem[..], (PAGE_SIZE / 2) - HEADER_SIZE).unwrap(); + let ptr1 = heap.allocate(&mut mem, (PAGE_SIZE / 2) - HEADER_SIZE).unwrap(); assert_eq!(ptr1, to_pointer(HEADER_SIZE)); // when - let ptr2 = heap.allocate(&mut mem[..], PAGE_SIZE / 2); + let ptr2 = heap.allocate(&mut mem, PAGE_SIZE / 2); // then // there is no room for another half page incl. its 8 byte prefix @@ -771,11 +868,11 @@ mod tests { #[test] fn should_allocate_max_possible_allocation_size() { // given - let mut mem = vec![0u8; (MAX_POSSIBLE_ALLOCATION + PAGE_SIZE) as usize]; + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); // when - let ptr = heap.allocate(&mut mem[..], MAX_POSSIBLE_ALLOCATION).unwrap(); + let ptr = heap.allocate(&mut mem, MAX_POSSIBLE_ALLOCATION).unwrap(); // then assert_eq!(ptr, to_pointer(HEADER_SIZE)); @@ -784,60 +881,62 @@ mod tests { #[test] fn should_not_allocate_if_requested_size_too_large() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); // when - let ptr = heap.allocate(&mut mem[..], MAX_POSSIBLE_ALLOCATION + 1); + let ptr = heap.allocate(&mut mem, MAX_POSSIBLE_ALLOCATION + 1); // then - match ptr.unwrap_err() { - Error::RequestedAllocationTooLarge => {}, - e => panic!("Expected allocation size too large error, got: {:?}", e), - } + assert_eq!(Error::RequestedAllocationTooLarge, ptr.unwrap_err()); } #[test] fn should_return_error_when_bumper_greater_than_heap_size() { // given - let mut mem = [0u8; 64]; + let mut mem = MemoryInstance::with_pages(1); + mem.set_max_wasm_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); - let ptr1 = heap.allocate(&mut mem[..], 32).unwrap(); - assert_eq!(ptr1, to_pointer(HEADER_SIZE)); - heap.deallocate(&mut mem[..], ptr1).expect("failed freeing ptr1"); - assert_eq!(heap.stats.bytes_allocated, 0); - assert_eq!(heap.bumper, 40); + let mut ptrs = Vec::new(); + for _ in 0..(PAGE_SIZE as usize / 40) { + ptrs.push(heap.allocate(&mut mem, 32).expect("Allocate 32 byte")); + } + + assert_eq!(heap.stats.bytes_allocated, PAGE_SIZE - 16); + assert_eq!(heap.bumper, PAGE_SIZE - 16); + + ptrs.into_iter() + .for_each(|ptr| heap.deallocate(&mut mem, ptr).expect("Deallocate 32 byte")); - let ptr2 = heap.allocate(&mut mem[..], 16).unwrap(); - assert_eq!(ptr2, to_pointer(48)); - heap.deallocate(&mut mem[..], ptr2).expect("failed freeing ptr2"); assert_eq!(heap.stats.bytes_allocated, 0); - assert_eq!(heap.bumper, 64); + assert_eq!(heap.stats.bytes_allocated_peak, PAGE_SIZE - 16); + assert_eq!(heap.bumper, PAGE_SIZE - 16); + + // Allocate another 8 byte to use the full heap. + heap.allocate(&mut mem, 8).expect("Allocate 8 byte"); // when // the `bumper` value is equal to `size` here and any // further allocation which would increment the bumper must fail. // we try to allocate 8 bytes here, which will increment the - // bumper since no 8 byte item has been allocated+freed before. - let ptr = heap.allocate(&mut mem[..], 8); + // bumper since no 8 byte item has been freed before. + assert_eq!(heap.bumper as u64, mem.size()); + let ptr = heap.allocate(&mut mem, 8); // then - match ptr.unwrap_err() { - Error::AllocatorOutOfSpace => {}, - e => panic!("Expected allocator out of space error, got: {:?}", e), - } + assert_eq!(Error::AllocatorOutOfSpace, ptr.unwrap_err()); } #[test] fn should_include_prefixes_in_total_heap_size() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(1); // when // an item size of 16 must be used then - heap.allocate(&mut mem[..], 9).unwrap(); + heap.allocate(&mut mem, 9).unwrap(); // then assert_eq!(heap.stats.bytes_allocated, HEADER_SIZE + 16); @@ -846,13 +945,13 @@ mod tests { #[test] fn should_calculate_total_heap_size_to_zero() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(13); // when - let ptr = heap.allocate(&mut mem[..], 42).unwrap(); + let ptr = heap.allocate(&mut mem, 42).unwrap(); assert_eq!(ptr, to_pointer(16 + HEADER_SIZE)); - heap.deallocate(&mut mem[..], ptr).unwrap(); + heap.deallocate(&mut mem, ptr).unwrap(); // then assert_eq!(heap.stats.bytes_allocated, 0); @@ -861,13 +960,13 @@ mod tests { #[test] fn should_calculate_total_size_of_zero() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(19); // when for _ in 1..10 { - let ptr = heap.allocate(&mut mem[..], 42).unwrap(); - heap.deallocate(&mut mem[..], ptr).unwrap(); + let ptr = heap.allocate(&mut mem, 42).unwrap(); + heap.deallocate(&mut mem, ptr).unwrap(); } // then @@ -877,13 +976,13 @@ mod tests { #[test] fn should_read_and_write_u64_correctly() { // given - let mut mem = [0u8; PAGE_SIZE as usize]; + let mut mem = MemoryInstance::with_pages(1); // when - Memory::write_le_u64(mem.as_mut(), 40, 4480113).unwrap(); + mem.write_le_u64(40, 4480113).unwrap(); // then - let value = Memory::read_le_u64(mem.as_mut(), 40).unwrap(); + let value = MemoryExt::read_le_u64(&mut mem, 40).unwrap(); assert_eq!(value, 4480113); } @@ -913,24 +1012,25 @@ mod tests { #[test] fn deallocate_needs_to_maintain_linked_list() { - let mut mem = [0u8; 8 * 2 * 4 + ALIGNMENT as usize]; + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); // Allocate and free some pointers - let ptrs = (0..4).map(|_| heap.allocate(&mut mem[..], 8).unwrap()).collect::>(); - ptrs.into_iter().for_each(|ptr| heap.deallocate(&mut mem[..], ptr).unwrap()); + let ptrs = (0..4).map(|_| heap.allocate(&mut mem, 8).unwrap()).collect::>(); + ptrs.iter().rev().for_each(|ptr| heap.deallocate(&mut mem, *ptr).unwrap()); - // Second time we should be able to allocate all of them again. - let _ = (0..4).map(|_| heap.allocate(&mut mem[..], 8).unwrap()).collect::>(); + // Second time we should be able to allocate all of them again and get the same pointers! + let new_ptrs = (0..4).map(|_| heap.allocate(&mut mem, 8).unwrap()).collect::>(); + assert_eq!(ptrs, new_ptrs); } #[test] fn header_read_write() { let roundtrip = |header: Header| { - let mut memory = [0u8; 32]; - header.write_into(memory.as_mut(), 0).unwrap(); + let mut memory = MemoryInstance::with_pages(1); + header.write_into(&mut memory, 0).unwrap(); - let read_header = Header::read_from(memory.as_mut(), 0).unwrap(); + let read_header = Header::read_from(&memory, 0).unwrap(); assert_eq!(header, read_header); }; @@ -944,18 +1044,18 @@ mod tests { #[test] fn poison_oom() { // given - // a heap of 32 bytes. Should be enough for two allocations. - let mut mem = [0u8; 32]; + let mut mem = MemoryInstance::with_pages(1); + mem.set_max_wasm_pages(1); + let mut heap = FreeingBumpHeapAllocator::new(0); // when - assert!(heap.allocate(mem.as_mut(), 8).is_ok()); - let alloc_ptr = heap.allocate(mem.as_mut(), 8).unwrap(); - assert!(heap.allocate(mem.as_mut(), 8).is_err()); + let alloc_ptr = heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap(); + assert_eq!(Error::AllocatorOutOfSpace, heap.allocate(&mut mem, PAGE_SIZE).unwrap_err()); // then assert!(heap.poisoned); - assert!(heap.deallocate(mem.as_mut(), alloc_ptr).is_err()); + assert!(heap.deallocate(&mut mem, alloc_ptr).is_err()); } #[test] @@ -969,36 +1069,62 @@ mod tests { #[test] fn accepts_growing_memory() { - const ITEM_SIZE: u32 = 16; - const ITEM_ON_HEAP_SIZE: usize = 16 + HEADER_SIZE as usize; - - let mut mem = vec![0u8; ITEM_ON_HEAP_SIZE * 2]; + let mut mem = MemoryInstance::with_pages(1); let mut heap = FreeingBumpHeapAllocator::new(0); - let _ = heap.allocate(&mut mem[..], ITEM_SIZE).unwrap(); - let _ = heap.allocate(&mut mem[..], ITEM_SIZE).unwrap(); + heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap(); + heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap(); - mem.extend_from_slice(&[0u8; ITEM_ON_HEAP_SIZE]); + mem.grow(1).unwrap(); - let _ = heap.allocate(&mut mem[..], ITEM_SIZE).unwrap(); + heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap(); } #[test] fn doesnt_accept_shrinking_memory() { - const ITEM_SIZE: u32 = 16; - const ITEM_ON_HEAP_SIZE: usize = 16 + HEADER_SIZE as usize; - - let initial_size = ITEM_ON_HEAP_SIZE * 3; - let mut mem = vec![0u8; initial_size]; + let mut mem = MemoryInstance::with_pages(2); let mut heap = FreeingBumpHeapAllocator::new(0); - let _ = heap.allocate(&mut mem[..], ITEM_SIZE).unwrap(); + heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap(); - mem.truncate(initial_size - 1); + mem.data.truncate(PAGE_SIZE as usize); - match heap.allocate(&mut mem[..], ITEM_SIZE).unwrap_err() { + match heap.allocate(&mut mem, PAGE_SIZE / 2).unwrap_err() { Error::MemoryShrinked => (), _ => panic!(), } } + + #[test] + fn should_grow_memory_when_running_out_of_memory() { + let mut mem = MemoryInstance::with_pages(1); + let mut heap = FreeingBumpHeapAllocator::new(0); + + assert_eq!(1, mem.pages()); + + heap.allocate(&mut mem, PAGE_SIZE * 2).unwrap(); + + assert_eq!(3, mem.pages()); + } + + #[test] + fn modifying_the_header_leads_to_an_error() { + let mut mem = MemoryInstance::with_pages(1); + let mut heap = FreeingBumpHeapAllocator::new(0); + + let ptr = heap.allocate(&mut mem, 5).unwrap(); + + heap.deallocate(&mut mem, ptr).unwrap(); + + Header::Free(Link::Ptr(u32::MAX - 1)) + .write_into(&mut mem, u32::from(ptr) - HEADER_SIZE) + .unwrap(); + + heap.allocate(&mut mem, 5).unwrap(); + assert!(heap + .allocate(&mut mem, 5) + .unwrap_err() + .to_string() + .contains("Invalid header pointer")); + } } diff --git a/client/allocator/src/lib.rs b/client/allocator/src/lib.rs index 2fe63a1ec392..e50d7d54c8e9 100644 --- a/client/allocator/src/lib.rs +++ b/client/allocator/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,3 +27,35 @@ mod freeing_bump; pub use error::Error; pub use freeing_bump::{AllocationStats, FreeingBumpHeapAllocator}; + +/// The size of one wasm page in bytes. +/// +/// The wasm memory is divided into pages, meaning the minimum size of a memory is one page. +const PAGE_SIZE: u32 = 65536; + +/// The maximum number of wasm pages that can be allocated. +/// +/// 4GiB / [`PAGE_SIZE`]. +const MAX_WASM_PAGES: u32 = (4u64 * 1024 * 1024 * 1024 / PAGE_SIZE as u64) as u32; + +/// Grants access to the memory for the allocator. +/// +/// Memory of wasm is allocated in pages. A page has a constant size of 64KiB. The maximum allowed +/// memory size as defined in the wasm specification is 4GiB (65536 pages). +pub trait Memory { + /// Run the given closure `run` and grant it write access to the raw memory. + fn with_access_mut(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R; + /// Run the given closure `run` and grant it read access to the raw memory. + fn with_access(&self, run: impl FnOnce(&[u8]) -> R) -> R; + /// Grow the memory by `additional` pages. + fn grow(&mut self, additional: u32) -> Result<(), ()>; + /// Returns the current number of pages this memory has allocated. + fn pages(&self) -> u32; + /// Returns the maximum number of pages this memory is allowed to allocate. + /// + /// The returned number needs to be smaller or equal to `65536`. The returned number needs to be + /// bigger or equal to [`Self::pages`]. + /// + /// If `None` is returned, there is no maximum (besides the maximum defined in the wasm spec). + fn max_pages(&self) -> Option; +} diff --git a/client/api/Cargo.toml b/client/api/Cargo.toml index f49420085272..17f9747c39bc 100644 --- a/client/api/Cargo.toml +++ b/client/api/Cargo.toml @@ -14,7 +14,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } fnv = "1.0.6" @@ -28,13 +28,13 @@ sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } sp-database = { version = "4.0.0-dev", path = "../../primitives/database" } -sp-externalities = { version = "0.13.0", path = "../../primitives/externalities" } -sp-keystore = { version = "0.13.0", default-features = false, path = "../../primitives/keystore" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } -sp-storage = { version = "7.0.0", path = "../../primitives/storage" } +sp-externalities = { version = "0.19.0", path = "../../primitives/externalities" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-state-machine = { version = "0.28.0", path = "../../primitives/state-machine" } +sp-statement-store = { version = "4.0.0-dev", path = "../../primitives/statement-store" } +sp-storage = { version = "13.0.0", path = "../../primitives/storage" } [dev-dependencies] thiserror = "1.0.30" diff --git a/client/api/src/backend.rs b/client/api/src/backend.rs index 4ef609bdd452..b88feafb6ca3 100644 --- a/client/api/src/backend.rs +++ b/client/api/src/backend.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,12 +18,10 @@ //! Substrate Client data backend -use crate::{ - blockchain::{well_known_cache_keys, Backend as BlockchainBackend}, - UsageInfo, -}; +use std::collections::HashSet; + use parking_lot::RwLock; -use sp_blockchain; + use sp_consensus::BlockOrigin; use sp_core::offchain::OffchainStorage; use sp_runtime::{ @@ -31,14 +29,14 @@ use sp_runtime::{ Justification, Justifications, StateVersion, Storage, }; use sp_state_machine::{ - backend::AsTrieBackend, ChildStorageCollection, IndexOperation, OffchainChangesCollection, - StorageCollection, + backend::AsTrieBackend, ChildStorageCollection, IndexOperation, IterArgs, + OffchainChangesCollection, StorageCollection, StorageIterator, }; use sp_storage::{ChildInfo, StorageData, StorageKey}; -use std::collections::{HashMap, HashSet}; + +use crate::{blockchain::Backend as BlockchainBackend, UsageInfo}; pub use sp_state_machine::{Backend as StateBackend, KeyValueStates}; -use std::marker::PhantomData; /// Extracts the state backend type for the given backend. pub type StateBackendFor = >::State; @@ -49,6 +47,19 @@ pub type TransactionForSB = >>::Trans /// Extracts the transaction for the given backend. pub type TransactionFor = TransactionForSB, Block>; +/// Describes which block import notification stream should be notified. +#[derive(Debug, Clone, Copy)] +pub enum ImportNotificationAction { + /// Notify only when the node has synced to the tip or there is a re-org. + RecentBlock, + /// Notify for every single block no matter what the sync state is. + EveryBlock, + /// Both block import notifications above should be fired. + Both, + /// No block import notification should be fired. + None, +} + /// Import operation summary. /// /// Contains information about the block that just got imported, @@ -68,6 +79,8 @@ pub struct ImportSummary { /// /// If `None`, there was no re-org while importing. pub tree_route: Option>, + /// What notify action to take for this import. + pub import_notification_action: ImportNotificationAction, } /// Finalization operation summary. @@ -165,9 +178,6 @@ pub trait BlockImportOperation { state: NewBlockState, ) -> sp_blockchain::Result<()>; - /// Update cached data. - fn update_cache(&mut self, cache: HashMap>); - /// Inject storage data into the database. fn update_db_storage( &mut self, @@ -303,32 +313,52 @@ pub trait AuxStore { } /// An `Iterator` that iterates keys in a given block under a prefix. -pub struct KeyIterator { +pub struct KeysIter +where + State: StateBackend>, + Block: BlockT, +{ + inner: >>::RawIter, state: State, - child_storage: Option, - prefix: Option, - current_key: Vec, - _phantom: PhantomData, } -impl KeyIterator { - /// create a KeyIterator instance - pub fn new(state: State, prefix: Option, current_key: Vec) -> Self { - Self { state, child_storage: None, prefix, current_key, _phantom: PhantomData } +impl KeysIter +where + State: StateBackend>, + Block: BlockT, +{ + /// Create a new iterator over storage keys. + pub fn new( + state: State, + prefix: Option<&StorageKey>, + start_at: Option<&StorageKey>, + ) -> Result { + let mut args = IterArgs::default(); + args.prefix = prefix.as_ref().map(|prefix| prefix.0.as_slice()); + args.start_at = start_at.as_ref().map(|start_at| start_at.0.as_slice()); + args.start_at_exclusive = true; + + Ok(Self { inner: state.raw_iter(args)?, state }) } - /// Create a `KeyIterator` instance for a child storage. + /// Create a new iterator over a child storage's keys. pub fn new_child( state: State, child_info: ChildInfo, - prefix: Option, - current_key: Vec, - ) -> Self { - Self { state, child_storage: Some(child_info), prefix, current_key, _phantom: PhantomData } + prefix: Option<&StorageKey>, + start_at: Option<&StorageKey>, + ) -> Result { + let mut args = IterArgs::default(); + args.prefix = prefix.as_ref().map(|prefix| prefix.0.as_slice()); + args.start_at = start_at.as_ref().map(|start_at| start_at.0.as_slice()); + args.child_info = Some(child_info); + args.start_at_exclusive = true; + + Ok(Self { inner: state.raw_iter(args)?, state }) } } -impl Iterator for KeyIterator +impl Iterator for KeysIter where Block: BlockT, State: StateBackend>, @@ -336,25 +366,56 @@ where type Item = StorageKey; fn next(&mut self) -> Option { - let next_key = if let Some(child_info) = self.child_storage.as_ref() { - self.state.next_child_storage_key(child_info, &self.current_key) - } else { - self.state.next_storage_key(&self.current_key) - } - .ok() - .flatten()?; - // this terminates the iterator the first time it fails. - if let Some(ref prefix) = self.prefix { - if !next_key.starts_with(&prefix.0[..]) { - return None - } - } - self.current_key = next_key.clone(); - Some(StorageKey(next_key)) + self.inner.next_key(&self.state)?.ok().map(StorageKey) } } -/// Provides acess to storage primitives +/// An `Iterator` that iterates keys and values in a given block under a prefix. +pub struct PairsIter +where + State: StateBackend>, + Block: BlockT, +{ + inner: >>::RawIter, + state: State, +} + +impl Iterator for PairsIter +where + Block: BlockT, + State: StateBackend>, +{ + type Item = (StorageKey, StorageData); + + fn next(&mut self) -> Option { + self.inner + .next_pair(&self.state)? + .ok() + .map(|(key, value)| (StorageKey(key), StorageData(value))) + } +} + +impl PairsIter +where + State: StateBackend>, + Block: BlockT, +{ + /// Create a new iterator over storage key and value pairs. + pub fn new( + state: State, + prefix: Option<&StorageKey>, + start_at: Option<&StorageKey>, + ) -> Result { + let mut args = IterArgs::default(); + args.prefix = prefix.as_ref().map(|prefix| prefix.0.as_slice()); + args.start_at = start_at.as_ref().map(|start_at| start_at.0.as_slice()); + args.start_at_exclusive = true; + + Ok(Self { inner: state.raw_iter(args)?, state }) + } +} + +/// Provides access to storage primitives pub trait StorageProvider> { /// Given a block's `Hash` and a key, return the value under the key in that block. fn storage( @@ -363,13 +424,6 @@ pub trait StorageProvider> { key: &StorageKey, ) -> sp_blockchain::Result>; - /// Given a block's `Hash` and a key prefix, return the matching storage keys in that block. - fn storage_keys( - &self, - hash: Block::Hash, - key_prefix: &StorageKey, - ) -> sp_blockchain::Result>; - /// Given a block's `Hash` and a key, return the value under the hash in that block. fn storage_hash( &self, @@ -377,22 +431,23 @@ pub trait StorageProvider> { key: &StorageKey, ) -> sp_blockchain::Result>; - /// Given a block's `Hash` and a key prefix, return the matching child storage keys and values - /// in that block. - fn storage_pairs( + /// Given a block's `Hash` and a key prefix, returns a `KeysIter` iterates matching storage + /// keys in that block. + fn storage_keys( &self, hash: Block::Hash, - key_prefix: &StorageKey, - ) -> sp_blockchain::Result>; + prefix: Option<&StorageKey>, + start_key: Option<&StorageKey>, + ) -> sp_blockchain::Result>; - /// Given a block's `Hash` and a key prefix, return a `KeyIterator` iterates matching storage - /// keys in that block. - fn storage_keys_iter( + /// Given a block's `Hash` and a key prefix, returns an iterator over the storage keys and + /// values in that block. + fn storage_pairs( &self, - hash: Block::Hash, + hash: ::Hash, prefix: Option<&StorageKey>, start_key: Option<&StorageKey>, - ) -> sp_blockchain::Result>; + ) -> sp_blockchain::Result>; /// Given a block's `Hash`, a key and a child storage key, return the value under the key in /// that block. @@ -403,24 +458,15 @@ pub trait StorageProvider> { key: &StorageKey, ) -> sp_blockchain::Result>; - /// Given a block's `Hash`, a key prefix, and a child storage key, return the matching child - /// storage keys. - fn child_storage_keys( - &self, - hash: Block::Hash, - child_info: &ChildInfo, - key_prefix: &StorageKey, - ) -> sp_blockchain::Result>; - /// Given a block's `Hash` and a key `prefix` and a child storage key, - /// return a `KeyIterator` that iterates matching storage keys in that block. - fn child_storage_keys_iter( + /// returns a `KeysIter` that iterates matching storage keys in that block. + fn child_storage_keys( &self, hash: Block::Hash, child_info: ChildInfo, prefix: Option<&StorageKey>, start_key: Option<&StorageKey>, - ) -> sp_blockchain::Result>; + ) -> sp_blockchain::Result>; /// Given a block's `Hash`, a key and a child storage key, return the hash under the key in that /// block. diff --git a/client/api/src/call_executor.rs b/client/api/src/call_executor.rs index 74a2b8b8557c..cecc1053d1b9 100644 --- a/client/api/src/call_executor.rs +++ b/client/api/src/call_executor.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -19,12 +19,14 @@ //! A method call executor interface. use sc_executor::{RuntimeVersion, RuntimeVersionOf}; +use sp_core::traits::CallContext; +use sp_externalities::Extensions; use sp_runtime::traits::Block as BlockT; -use sp_state_machine::{ExecutionStrategy, OverlayedChanges, StorageProof}; +use sp_state_machine::{OverlayedChanges, StorageProof}; use std::cell::RefCell; use crate::execution_extensions::ExecutionExtensions; -use sp_api::{ExecutionContext, ProofRecorder, StorageTransactionCache}; +use sp_api::{ProofRecorder, StorageTransactionCache}; /// Executor Provider pub trait ExecutorProvider { @@ -57,7 +59,7 @@ pub trait CallExecutor: RuntimeVersionOf { at_hash: B::Hash, method: &str, call_data: &[u8], - strategy: ExecutionStrategy, + context: CallContext, ) -> Result, sp_blockchain::Error>; /// Execute a contextual call on top of state in a block of a given hash. @@ -77,7 +79,8 @@ pub trait CallExecutor: RuntimeVersionOf { >, >, proof_recorder: &Option>, - context: ExecutionContext, + call_context: CallContext, + extensions: &RefCell, ) -> sp_blockchain::Result>; /// Extract RuntimeVersion of given block diff --git a/client/api/src/client.rs b/client/api/src/client.rs index 8e7ceb68704b..e334f2f9fb4f 100644 --- a/client/api/src/client.rs +++ b/client/api/src/client.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -59,10 +59,16 @@ pub trait BlockOf { /// A source of blockchain events. pub trait BlockchainEvents { - /// Get block import event stream. Not guaranteed to be fired for every - /// imported block. + /// Get block import event stream. + /// + /// Not guaranteed to be fired for every imported block, only fired when the node + /// has synced to the tip or there is a re-org. Use `every_import_notification_stream()` + /// if you want a notification of every imported block regardless. fn import_notification_stream(&self) -> ImportNotifications; + /// Get a stream of every imported block. + fn every_import_notification_stream(&self) -> ImportNotifications; + /// Get a stream of finality notifications. Not guaranteed to be fired for every /// finalized block. fn finality_notification_stream(&self) -> FinalityNotifications; diff --git a/client/api/src/execution_extensions.rs b/client/api/src/execution_extensions.rs index 58c085a29a94..6f927105df0b 100644 --- a/client/api/src/execution_extensions.rs +++ b/client/api/src/execution_extensions.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -22,52 +22,11 @@ //! strategy for the runtime calls and provide the right `Externalities` //! extensions to support APIs for particular execution context & capabilities. -use codec::Decode; use parking_lot::RwLock; -use sc_transaction_pool_api::OffchainSubmitTransaction; -use sp_core::{ - offchain::{self, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt}, - ExecutionContext, -}; +use sp_core::traits::{ReadRuntimeVersion, ReadRuntimeVersionExt}; use sp_externalities::{Extension, Extensions}; -use sp_keystore::{KeystoreExt, SyncCryptoStorePtr}; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, NumberFor}, -}; -pub use sp_state_machine::ExecutionStrategy; -use sp_state_machine::{DefaultHandler, ExecutionManager}; -use std::{ - marker::PhantomData, - sync::{Arc, Weak}, -}; - -/// Execution strategies settings. -#[derive(Debug, Clone)] -pub struct ExecutionStrategies { - /// Execution strategy used when syncing. - pub syncing: ExecutionStrategy, - /// Execution strategy used when importing blocks. - pub importing: ExecutionStrategy, - /// Execution strategy used when constructing blocks. - pub block_construction: ExecutionStrategy, - /// Execution strategy used for offchain workers. - pub offchain_worker: ExecutionStrategy, - /// Execution strategy used in other cases. - pub other: ExecutionStrategy, -} - -impl Default for ExecutionStrategies { - fn default() -> ExecutionStrategies { - ExecutionStrategies { - syncing: ExecutionStrategy::NativeElseWasm, - importing: ExecutionStrategy::NativeElseWasm, - block_construction: ExecutionStrategy::AlwaysWasm, - offchain_worker: ExecutionStrategy::NativeWhenPossible, - other: ExecutionStrategy::NativeElseWasm, - } - } -} +use sp_runtime::traits::{Block as BlockT, NumberFor}; +use std::{marker::PhantomData, sync::Arc}; /// Generate the starting set of [`Extensions`]. /// @@ -77,22 +36,12 @@ pub trait ExtensionsFactory: Send + Sync { /// /// - `block_hash`: The hash of the block in the context that extensions will be used. /// - `block_number`: The number of the block in the context that extensions will be used. - /// - `capabilities`: The capabilities - fn extensions_for( - &self, - block_hash: Block::Hash, - block_number: NumberFor, - capabilities: offchain::Capabilities, - ) -> Extensions; + fn extensions_for(&self, block_hash: Block::Hash, block_number: NumberFor) + -> Extensions; } impl ExtensionsFactory for () { - fn extensions_for( - &self, - _: Block::Hash, - _: NumberFor, - _capabilities: offchain::Capabilities, - ) -> Extensions { + fn extensions_for(&self, _: Block::Hash, _: NumberFor) -> Extensions { Extensions::new() } } @@ -102,10 +51,9 @@ impl> ExtensionsFactory for Ve &self, block_hash: Block::Hash, block_number: NumberFor, - capabilities: offchain::Capabilities, ) -> Extensions { let mut exts = Extensions::new(); - exts.extend(self.iter().map(|e| e.extensions_for(block_hash, block_number, capabilities))); + exts.extend(self.iter().map(|e| e.extensions_for(block_hash, block_number))); exts } } @@ -128,12 +76,7 @@ impl ExtensionBeforeBlock { impl ExtensionsFactory for ExtensionBeforeBlock { - fn extensions_for( - &self, - _: Block::Hash, - block_number: NumberFor, - _: offchain::Capabilities, - ) -> Extensions { + fn extensions_for(&self, _: Block::Hash, block_number: NumberFor) -> Extensions { let mut exts = Extensions::new(); if block_number < self.before { @@ -144,175 +87,47 @@ impl ExtensionsFactory } } -/// Create a Offchain DB accessor object. -pub trait DbExternalitiesFactory: Send + Sync { - /// Create [`offchain::DbExternalities`] instance. - fn create(&self) -> Box; -} - -impl DbExternalitiesFactory for T { - fn create(&self) -> Box { - Box::new(self.clone()) - } -} - /// A producer of execution extensions for offchain calls. /// /// This crate aggregates extensions available for the offchain calls /// and is responsible for producing a correct `Extensions` object. /// for each call, based on required `Capabilities`. pub struct ExecutionExtensions { - strategies: ExecutionStrategies, - keystore: Option, - offchain_db: Option>, - // FIXME: these two are only RwLock because of https://github.com/paritytech/substrate/issues/4587 - // remove when fixed. - // To break retain cycle between `Client` and `TransactionPool` we require this - // extension to be a `Weak` reference. - // That's also the reason why it's being registered lazily instead of - // during initialization. - transaction_pool: RwLock>>>, extensions_factory: RwLock>>, -} - -impl Default for ExecutionExtensions { - fn default() -> Self { - Self { - strategies: Default::default(), - keystore: None, - offchain_db: None, - transaction_pool: RwLock::new(None), - extensions_factory: RwLock::new(Box::new(())), - } - } + read_runtime_version: Arc, } impl ExecutionExtensions { - /// Create new `ExecutionExtensions` given a `keystore` and `ExecutionStrategies`. + /// Create new `ExecutionExtensions` given an `extensions_factory`. pub fn new( - strategies: ExecutionStrategies, - keystore: Option, - offchain_db: Option>, + extensions_factory: Option>>, + read_runtime_version: Arc, ) -> Self { - let transaction_pool = RwLock::new(None); - let extensions_factory = Box::new(()); Self { - strategies, - keystore, - offchain_db, - extensions_factory: RwLock::new(extensions_factory), - transaction_pool, + extensions_factory: extensions_factory + .map(RwLock::new) + .unwrap_or_else(|| RwLock::new(Box::new(()))), + read_runtime_version, } } - /// Get a reference to the execution strategies. - pub fn strategies(&self) -> &ExecutionStrategies { - &self.strategies - } - /// Set the new extensions_factory pub fn set_extensions_factory(&self, maker: impl ExtensionsFactory + 'static) { *self.extensions_factory.write() = Box::new(maker); } - /// Register transaction pool extension. - pub fn register_transaction_pool(&self, pool: &Arc) - where - T: OffchainSubmitTransaction + 'static, - { - *self.transaction_pool.write() = Some(Arc::downgrade(pool) as _); - } - /// Based on the execution context and capabilities it produces /// the extensions object to support desired set of APIs. pub fn extensions( &self, block_hash: Block::Hash, block_number: NumberFor, - context: ExecutionContext, ) -> Extensions { - let capabilities = context.capabilities(); - let mut extensions = - self.extensions_factory - .read() - .extensions_for(block_hash, block_number, capabilities); + self.extensions_factory.read().extensions_for(block_hash, block_number); - if capabilities.contains(offchain::Capabilities::KEYSTORE) { - if let Some(ref keystore) = self.keystore { - extensions.register(KeystoreExt(keystore.clone())); - } - } - - if capabilities.contains(offchain::Capabilities::TRANSACTION_POOL) { - if let Some(pool) = self.transaction_pool.read().as_ref().and_then(|x| x.upgrade()) { - extensions.register(TransactionPoolExt(Box::new(TransactionPoolAdapter { - at: BlockId::Hash(block_hash), - pool, - }) as _)); - } - } - - if capabilities.contains(offchain::Capabilities::OFFCHAIN_DB_READ) || - capabilities.contains(offchain::Capabilities::OFFCHAIN_DB_WRITE) - { - if let Some(offchain_db) = self.offchain_db.as_ref() { - extensions.register(OffchainDbExt::new(offchain::LimitedExternalities::new( - capabilities, - offchain_db.create(), - ))); - } - } - - if let ExecutionContext::OffchainCall(Some(ext)) = context { - extensions.register(OffchainWorkerExt::new(offchain::LimitedExternalities::new( - capabilities, - ext.0, - ))); - } + extensions.register(ReadRuntimeVersionExt::new(self.read_runtime_version.clone())); extensions } - - /// Create `ExecutionManager` and `Extensions` for given offchain call. - /// - /// Based on the execution context and capabilities it produces - /// the right manager and extensions object to support desired set of APIs. - pub fn manager_and_extensions( - &self, - block_hash: Block::Hash, - block_number: NumberFor, - context: ExecutionContext, - ) -> (ExecutionManager>, Extensions) { - let manager = match context { - ExecutionContext::BlockConstruction => self.strategies.block_construction.get_manager(), - ExecutionContext::Syncing => self.strategies.syncing.get_manager(), - ExecutionContext::Importing => self.strategies.importing.get_manager(), - ExecutionContext::OffchainCall(Some((_, capabilities))) if capabilities.is_all() => - self.strategies.offchain_worker.get_manager(), - ExecutionContext::OffchainCall(_) => self.strategies.other.get_manager(), - }; - - (manager, self.extensions(block_hash, block_number, context)) - } -} - -/// A wrapper type to pass `BlockId` to the actual transaction pool. -struct TransactionPoolAdapter { - at: BlockId, - pool: Arc>, -} - -impl offchain::TransactionPool for TransactionPoolAdapter { - fn submit_transaction(&mut self, data: Vec) -> Result<(), ()> { - let xt = match Block::Extrinsic::decode(&mut &*data) { - Ok(xt) => xt, - Err(e) => { - log::warn!("Unable to decode extrinsic: {:?}: {}", data, e); - return Err(()) - }, - }; - - self.pool.submit_at(&self.at, xt) - } } diff --git a/client/api/src/in_mem.rs b/client/api/src/in_mem.rs index 5e82757e7d9c..9ea5169d7883 100644 --- a/client/api/src/in_mem.rs +++ b/client/api/src/in_mem.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -40,7 +40,7 @@ use std::{ use crate::{ backend::{self, NewBlockState}, - blockchain::{self, well_known_cache_keys::Id as CacheKeyId, BlockStatus, HeaderBackend}, + blockchain::{self, BlockStatus, HeaderBackend}, leaves::LeafSet, UsageInfo, }; @@ -114,6 +114,7 @@ struct BlockchainStorage { } /// In-memory blockchain. Supports concurrent reads. +#[derive(Clone)] pub struct Blockchain { storage: Arc>>, } @@ -124,13 +125,6 @@ impl Default for Blockchain { } } -impl Clone for Blockchain { - fn clone(&self) -> Self { - let storage = Arc::new(RwLock::new(self.storage.read().clone())); - Blockchain { storage } - } -} - impl Blockchain { /// Get header hash of given block. pub fn id(&self, id: BlockId) -> Option { @@ -493,10 +487,7 @@ pub struct BlockImportOperation { set_head: Option, } -impl BlockImportOperation -where - Block::Hash: Ord, -{ +impl BlockImportOperation { fn apply_storage( &mut self, storage: Storage, @@ -525,10 +516,7 @@ where } } -impl backend::BlockImportOperation for BlockImportOperation -where - Block::Hash: Ord, -{ +impl backend::BlockImportOperation for BlockImportOperation { type State = InMemoryBackend>; fn state(&self) -> sp_blockchain::Result> { @@ -549,8 +537,6 @@ where Ok(()) } - fn update_cache(&mut self, _cache: HashMap>) {} - fn update_db_storage( &mut self, update: > as StateBackend>>::Transaction, @@ -619,33 +605,40 @@ where /// /// > **Warning**: Doesn't support all the features necessary for a proper database. Only use this /// > struct for testing purposes. Do **NOT** use in production. -pub struct Backend -where - Block::Hash: Ord, -{ +pub struct Backend { states: RwLock>>>, blockchain: Blockchain, import_lock: RwLock<()>, + pinned_blocks: RwLock>, } -impl Backend -where - Block::Hash: Ord, -{ +impl Backend { /// Create a new instance of in-mem backend. + /// + /// # Warning + /// + /// For testing purposes only! pub fn new() -> Self { Backend { states: RwLock::new(HashMap::new()), blockchain: Blockchain::new(), import_lock: Default::default(), + pinned_blocks: Default::default(), } } + + /// Return the number of references active for a pinned block. + /// + /// # Warning + /// + /// For testing purposes only! + pub fn pin_refs(&self, hash: &::Hash) -> Option { + let blocks = self.pinned_blocks.read(); + blocks.get(hash).map(|value| *value) + } } -impl backend::AuxStore for Backend -where - Block::Hash: Ord, -{ +impl backend::AuxStore for Backend { fn insert_aux< 'a, 'b: 'a, @@ -665,10 +658,7 @@ where } } -impl backend::Backend for Backend -where - Block::Hash: Ord, -{ +impl backend::Backend for Backend { type BlockImportOperation = BlockImportOperation; type Blockchain = Blockchain; type State = InMemoryBackend>; @@ -789,14 +779,19 @@ where false } - fn pin_block(&self, _: ::Hash) -> blockchain::Result<()> { + fn pin_block(&self, hash: ::Hash) -> blockchain::Result<()> { + let mut blocks = self.pinned_blocks.write(); + *blocks.entry(hash).or_default() += 1; Ok(()) } - fn unpin_block(&self, _: ::Hash) {} + fn unpin_block(&self, hash: ::Hash) { + let mut blocks = self.pinned_blocks.write(); + blocks.entry(hash).and_modify(|counter| *counter -= 1).or_insert(-1); + } } -impl backend::LocalBackend for Backend where Block::Hash: Ord {} +impl backend::LocalBackend for Backend {} /// Check that genesis storage is valid. pub fn check_genesis_storage(storage: &Storage) -> sp_blockchain::Result<()> { diff --git a/client/api/src/leaves.rs b/client/api/src/leaves.rs index cdcb80a110b7..a8a988771e2f 100644 --- a/client/api/src/leaves.rs +++ b/client/api/src/leaves.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/api/src/lib.rs b/client/api/src/lib.rs index 3d21f12f6940..faadf3663a59 100644 --- a/client/api/src/lib.rs +++ b/client/api/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -36,7 +36,7 @@ pub use proof_provider::*; pub use sp_blockchain as blockchain; pub use sp_blockchain::HeaderBackend; -pub use sp_state_machine::{CompactProof, ExecutionStrategy, StorageProof}; +pub use sp_state_machine::{CompactProof, StorageProof}; pub use sp_storage::{ChildInfo, PrefixedStorageKey, StorageData, StorageKey}; /// Usage Information Provider interface diff --git a/client/api/src/notifications.rs b/client/api/src/notifications.rs index 4dd23581d262..c16cefe21da0 100644 --- a/client/api/src/notifications.rs +++ b/client/api/src/notifications.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/api/src/notifications/registry.rs b/client/api/src/notifications/registry.rs index 882d6ed40be6..10c6673110ad 100644 --- a/client/api/src/notifications/registry.rs +++ b/client/api/src/notifications/registry.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/api/src/notifications/tests.rs b/client/api/src/notifications/tests.rs index 2c728de7428d..fba829b1cf90 100644 --- a/client/api/src/notifications/tests.rs +++ b/client/api/src/notifications/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/api/src/proof_provider.rs b/client/api/src/proof_provider.rs index 01e35df1dec1..7f60f856ae80 100644 --- a/client/api/src/proof_provider.rs +++ b/client/api/src/proof_provider.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/authority-discovery/Cargo.toml b/client/authority-discovery/Cargo.toml index b37507662bfe..0324cac46691 100644 --- a/client/authority-discovery/Cargo.toml +++ b/client/authority-discovery/Cargo.toml @@ -17,27 +17,28 @@ targets = ["x86_64-unknown-linux-gnu"] prost-build = "0.11" [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } futures = "0.3.21" futures-timer = "3.0.1" ip_network = "0.4.1" -libp2p = { version = "0.50.0", features = ["kad"] } +libp2p = { version = "0.51.3", features = ["kad", "ed25519"] } +multihash = { version = "0.17.0", default-features = false, features = ["std", "sha2"] } log = "0.4.17" prost = "0.11" rand = "0.8.5" thiserror = "1.0" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-client-api = { version = "4.0.0-dev", path = "../api" } -sc-network-common = { version = "0.10.0-dev", path = "../network/common" } +sc-network = { version = "0.10.0-dev", path = "../network/" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-authority-discovery = { version = "4.0.0-dev", path = "../../primitives/authority-discovery" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-keystore = { version = "0.27.0", path = "../../primitives/keystore" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } async-trait = "0.1.56" [dev-dependencies] quickcheck = { version = "1.0.3", default-features = false } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } diff --git a/client/authority-discovery/src/error.rs b/client/authority-discovery/src/error.rs index 285a2714b81f..ca685115d497 100644 --- a/client/authority-discovery/src/error.rs +++ b/client/authority-discovery/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,13 +18,12 @@ //! Authority discovery errors. -use sp_core::crypto::CryptoTypePublicPair; - /// AuthorityDiscovery Result. pub type Result = std::result::Result; /// Error type for the authority discovery module. #[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] pub enum Error { #[error("Received dht value found event with records with different keys.")] ReceivingDhtValueFoundEventWithDifferentKeys, @@ -57,13 +56,10 @@ pub enum Error { ParsingMultiaddress(#[from] libp2p::core::multiaddr::Error), #[error("Failed to parse a libp2p key.")] - ParsingLibp2pIdentity(#[from] libp2p::identity::error::DecodingError), - - #[error("Failed to sign using a specific public key.")] - MissingSignature(CryptoTypePublicPair), + ParsingLibp2pIdentity(#[from] libp2p::identity::DecodingError), - #[error("Failed to sign using all public keys.")] - Signing, + #[error("Failed to sign: {0}.")] + CannotSign(String), #[error("Failed to register Prometheus metric.")] Prometheus(#[from] prometheus_endpoint::PrometheusError), @@ -76,4 +72,7 @@ pub enum Error { #[error("Received authority record without a valid signature for the remote peer id.")] MissingPeerIdSignature, + + #[error("Unable to fetch best block.")] + BestBlockFetchingError, } diff --git a/client/authority-discovery/src/interval.rs b/client/authority-discovery/src/interval.rs index 5ddf81fbccc3..23c7ce266e3b 100644 --- a/client/authority-discovery/src/interval.rs +++ b/client/authority-discovery/src/interval.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/authority-discovery/src/lib.rs b/client/authority-discovery/src/lib.rs index db3802b168fe..6bb12804cada 100644 --- a/client/authority-discovery/src/lib.rs +++ b/client/authority-discovery/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -28,6 +28,7 @@ //! See [`Worker`] and [`Service`] for more documentation. pub use crate::{ + error::Error, service::Service, worker::{AuthorityDiscovery, NetworkProvider, Role, Worker}, }; @@ -40,7 +41,7 @@ use futures::{ }; use libp2p::{Multiaddr, PeerId}; -use sc_network_common::protocol::event::DhtEvent; +use sc_network::event::DhtEvent; use sp_authority_discovery::AuthorityId; use sp_blockchain::HeaderBackend; use sp_runtime::traits::Block as BlockT; @@ -148,7 +149,7 @@ pub fn new_worker_and_service_with_config + HeaderBackend + 'static, + Client: AuthorityDiscovery + 'static, DhtEventStream: Stream + Unpin, { let (to_worker, from_service) = mpsc::channel(0); diff --git a/client/authority-discovery/src/service.rs b/client/authority-discovery/src/service.rs index df09b6ea4321..89ae058d17f7 100644 --- a/client/authority-discovery/src/service.rs +++ b/client/authority-discovery/src/service.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/authority-discovery/src/tests.rs b/client/authority-discovery/src/tests.rs index 208440b7ab1e..4fbc196c5ecd 100644 --- a/client/authority-discovery/src/tests.rs +++ b/client/authority-discovery/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -25,15 +25,16 @@ use crate::{ }; use futures::{channel::mpsc::channel, executor::LocalPool, task::LocalSpawn}; -use libp2p::core::{ - multiaddr::{Multiaddr, Protocol}, +use libp2p::{ + core::multiaddr::{Multiaddr, Protocol}, + identity::ed25519, PeerId, }; use std::{collections::HashSet, sync::Arc}; use sp_authority_discovery::AuthorityId; use sp_core::crypto::key_types; -use sp_keystore::{testing::KeyStore, CryptoStore}; +use sp_keystore::{testing::MemoryKeystore, Keystore}; #[test] fn get_addresses_and_authority_id() { @@ -42,12 +43,11 @@ fn get_addresses_and_authority_id() { let mut pool = LocalPool::new(); - let key_store = KeyStore::new(); + let key_store = MemoryKeystore::new(); let remote_authority_id: AuthorityId = pool.run_until(async { key_store .sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None) - .await .unwrap() .into() }); @@ -87,18 +87,16 @@ fn get_addresses_and_authority_id() { fn cryptos_are_compatible() { use sp_core::crypto::Pair; - let libp2p_secret = libp2p::identity::Keypair::generate_ed25519(); - let libp2p_public = libp2p_secret.public(); + let libp2p_keypair = ed25519::Keypair::generate(); + let libp2p_public = libp2p_keypair.public(); - let sp_core_secret = { - let libp2p::identity::Keypair::Ed25519(libp2p_ed_secret) = libp2p_secret.clone(); - sp_core::ed25519::Pair::from_seed_slice(&libp2p_ed_secret.secret().as_ref()).unwrap() - }; + let sp_core_secret = + { sp_core::ed25519::Pair::from_seed_slice(&libp2p_keypair.secret().as_ref()).unwrap() }; let sp_core_public = sp_core_secret.public(); let message = b"we are more powerful than not to be better"; - let libp2p_signature = libp2p_secret.sign(message).unwrap(); + let libp2p_signature = libp2p_keypair.sign(message); let sp_core_signature = sp_core_secret.sign(message); // no error expected... assert!(sp_core::ed25519::Pair::verify( diff --git a/client/authority-discovery/src/worker.rs b/client/authority-discovery/src/worker.rs index 4121b64e00b9..a29e74df9acc 100644 --- a/client/authority-discovery/src/worker.rs +++ b/client/authority-discovery/src/worker.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -32,30 +32,27 @@ use std::{ use futures::{channel::mpsc, future, stream::Fuse, FutureExt, Stream, StreamExt}; use addr_cache::AddrCache; -use codec::Decode; +use codec::{Decode, Encode}; use ip_network::IpNetwork; -use libp2p::{ - core::multiaddr, - multihash::{Multihash, MultihashDigest}, - Multiaddr, PeerId, -}; +use libp2p::{core::multiaddr, identity::PublicKey, multihash::Multihash, Multiaddr, PeerId}; +use multihash::{Code, MultihashDigest}; + use log::{debug, error, log_enabled}; use prometheus_endpoint::{register, Counter, CounterVec, Gauge, Opts, U64}; use prost::Message; use rand::{seq::SliceRandom, thread_rng}; -use sc_network_common::{ - protocol::event::DhtEvent, - service::{KademliaKey, NetworkDHTProvider, NetworkSigner, NetworkStateInfo, Signature}, + +use sc_network::{ + event::DhtEvent, KademliaKey, NetworkDHTProvider, NetworkSigner, NetworkStateInfo, Signature, }; use sp_api::{ApiError, ProvideRuntimeApi}; use sp_authority_discovery::{ AuthorityDiscoveryApi, AuthorityId, AuthorityPair, AuthoritySignature, }; use sp_blockchain::HeaderBackend; - -use sp_core::crypto::{key_types, CryptoTypePublicPair, Pair}; -use sp_keystore::CryptoStore; -use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use sp_core::crypto::{key_types, ByteArray, Pair}; +use sp_keystore::{Keystore, KeystorePtr}; +use sp_runtime::traits::Block as BlockT; mod addr_cache; /// Dht payload schemas generated from Protobuf definitions via Prost crate in build.rs. @@ -79,7 +76,7 @@ const MAX_IN_FLIGHT_LOOKUPS: usize = 8; /// Role an authority discovery [`Worker`] can run as. pub enum Role { /// Publish own addresses and discover addresses of others. - PublishAndDiscover(Arc), + PublishAndDiscover(KeystorePtr), /// Discover addresses of others. Discover, } @@ -128,7 +125,7 @@ pub struct Worker { publish_if_changed_interval: ExpIncInterval, /// List of keys onto which addresses have been published at the latest publication. /// Used to check whether they have changed. - latest_published_keys: HashSet, + latest_published_keys: HashSet, /// Same value as in the configuration. publish_non_global_ips: bool, /// Same value as in the configuration. @@ -158,12 +155,15 @@ pub trait AuthorityDiscovery { /// Retrieve authority identifiers of the current and next authority set. async fn authorities(&self, at: Block::Hash) -> std::result::Result, ApiError>; + + /// Retrieve best block hash + async fn best_hash(&self) -> std::result::Result; } #[async_trait::async_trait] impl AuthorityDiscovery for T where - T: ProvideRuntimeApi + Send + Sync, + T: ProvideRuntimeApi + HeaderBackend + Send + Sync, T::Api: AuthorityDiscoveryApi, Block: BlockT, { @@ -171,7 +171,11 @@ where &self, at: Block::Hash, ) -> std::result::Result, ApiError> { - self.runtime_api().authorities(&BlockId::Hash(at)) + self.runtime_api().authorities(at) + } + + async fn best_hash(&self) -> std::result::Result { + Ok(self.info().best_hash) } } @@ -179,7 +183,7 @@ impl Worker + HeaderBackend + 'static, + Client: AuthorityDiscovery + 'static, DhtEventStream: Stream + Unpin, { /// Construct a [`Worker`]. @@ -340,7 +344,7 @@ where let keys = Worker::::get_own_public_keys_within_authority_set( key_store.clone(), self.client.as_ref(), - ).await?.into_iter().map(Into::into).collect::>(); + ).await?.into_iter().collect::>(); if only_if_changed && keys == self.latest_published_keys { return Ok(()) @@ -365,8 +369,7 @@ where Some(peer_signature), key_store.as_ref(), keys_vec, - ) - .await?; + )?; for (key, value) in kv_pairs.into_iter() { self.network.put_value(key, value); @@ -378,12 +381,11 @@ where } async fn refill_pending_lookups_queue(&mut self) -> Result<()> { - let best_hash = self.client.info().best_hash; + let best_hash = self.client.best_hash().await?; let local_keys = match &self.role { Role::PublishAndDiscover(key_store) => key_store .sr25519_public_keys(key_types::AUTHORITY_DISCOVERY) - .await .into_iter() .collect::>(), Role::Discover => HashSet::new(), @@ -547,10 +549,8 @@ where // properly signed by the owner of the PeerId if let Some(peer_signature) = peer_signature { - let public_key = libp2p::identity::PublicKey::from_protobuf_encoding( - &peer_signature.public_key, - ) - .map_err(Error::ParsingLibp2pIdentity)?; + let public_key = PublicKey::try_decode_protobuf(&peer_signature.public_key) + .map_err(Error::ParsingLibp2pIdentity)?; let signature = Signature { public_key, bytes: peer_signature.signature }; if !signature.verify(record, &remote_peer_id) { @@ -589,16 +589,15 @@ where // next authority set with two keys. The function does not return all of the local authority // discovery public keys, but only the ones intersecting with the current or next authority set. async fn get_own_public_keys_within_authority_set( - key_store: Arc, + key_store: KeystorePtr, client: &Client, ) -> Result> { let local_pub_keys = key_store .sr25519_public_keys(key_types::AUTHORITY_DISCOVERY) - .await .into_iter() .collect::>(); - let best_hash = client.info().best_hash; + let best_hash = client.best_hash().await?; let authorities = client .authorities(best_hash) .await @@ -622,7 +621,7 @@ pub trait NetworkProvider: NetworkDHTProvider + NetworkStateInfo + NetworkSigner impl NetworkProvider for T where T: NetworkDHTProvider + NetworkStateInfo + NetworkSigner {} fn hash_authority_id(id: &[u8]) -> KademliaKey { - KademliaKey::new(&libp2p::multihash::Code::Sha2_256.digest(id).digest()) + KademliaKey::new(&Code::Sha2_256.digest(id).digest()) } // Makes sure all values are the same and returns it @@ -658,39 +657,39 @@ fn sign_record_with_peer_id( ) -> Result { let signature = network .sign_with_local_identity(serialized_record) - .map_err(|_| Error::Signing)?; - let public_key = signature.public_key.to_protobuf_encoding(); + .map_err(|e| Error::CannotSign(format!("{} (network packet)", e)))?; + let public_key = signature.public_key.encode_protobuf(); let signature = signature.bytes; Ok(schema::PeerSignature { signature, public_key }) } -async fn sign_record_with_authority_ids( +fn sign_record_with_authority_ids( serialized_record: Vec, peer_signature: Option, - key_store: &dyn CryptoStore, - keys: Vec, + key_store: &dyn Keystore, + keys: Vec, ) -> Result)>> { - let signatures = key_store - .sign_with_all(key_types::AUTHORITY_DISCOVERY, keys.clone(), &serialized_record) - .await - .map_err(|_| Error::Signing)?; - - let mut result = vec![]; - for (sign_result, key) in signatures.into_iter().zip(keys.iter()) { - let mut signed_record = vec![]; - - // Verify that all signatures exist for all provided keys. - let auth_signature = - sign_result.ok().flatten().ok_or_else(|| Error::MissingSignature(key.clone()))?; - schema::SignedAuthorityRecord { + let mut result = Vec::with_capacity(keys.len()); + + for key in keys.iter() { + let auth_signature = key_store + .sr25519_sign(key_types::AUTHORITY_DISCOVERY, key.as_ref(), &serialized_record) + .map_err(|e| Error::CannotSign(format!("{}. Key: {:?}", e, key)))? + .ok_or_else(|| { + Error::CannotSign(format!("Could not find key in keystore. Key: {:?}", key)) + })?; + + // Scale encode + let auth_signature = auth_signature.encode(); + + let signed_record = schema::SignedAuthorityRecord { record: serialized_record.clone(), auth_signature, peer_signature: peer_signature.clone(), } - .encode(&mut signed_record) - .map_err(Error::EncodingProto)?; + .encode_to_vec(); - result.push((hash_authority_id(key.1.as_ref()), signed_record)); + result.push((hash_authority_id(key.as_slice()), signed_record)); } Ok(result) diff --git a/client/authority-discovery/src/worker/addr_cache.rs b/client/authority-discovery/src/worker/addr_cache.rs index 4859413a8cef..8084b7f0a6df 100644 --- a/client/authority-discovery/src/worker/addr_cache.rs +++ b/client/authority-discovery/src/worker/addr_cache.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/authority-discovery/src/worker/schema/tests.rs b/client/authority-discovery/src/worker/schema/tests.rs index 60147d6762e5..c765e4e5384d 100644 --- a/client/authority-discovery/src/worker/schema/tests.rs +++ b/client/authority-discovery/src/worker/schema/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -21,7 +21,7 @@ mod schema_v1 { } use super::*; -use libp2p::{multiaddr::Multiaddr, PeerId}; +use libp2p::{identity::Keypair, multiaddr::Multiaddr, PeerId}; use prost::Message; #[test] @@ -55,7 +55,7 @@ fn v2_decodes_v1() { #[test] fn v1_decodes_v2() { - let peer_secret = libp2p::identity::Keypair::generate_ed25519(); + let peer_secret = Keypair::generate_ed25519(); let peer_public = peer_secret.public(); let peer_id = peer_public.to_peer_id(); let multiaddress: Multiaddr = @@ -67,7 +67,7 @@ fn v1_decodes_v2() { let record_v2 = AuthorityRecord { addresses: vec_addresses.clone() }; let mut vec_record_v2 = vec![]; record_v2.encode(&mut vec_record_v2).unwrap(); - let vec_peer_public = peer_public.to_protobuf_encoding(); + let vec_peer_public = peer_public.encode_protobuf(); let peer_signature_v2 = PeerSignature { public_key: vec_peer_public, signature: vec_peer_signature }; let signed_record_v2 = SignedAuthorityRecord { diff --git a/client/authority-discovery/src/worker/tests.rs b/client/authority-discovery/src/worker/tests.rs index ce55728a1bfa..c29120881940 100644 --- a/client/authority-discovery/src/worker/tests.rs +++ b/client/authority-discovery/src/worker/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -29,13 +29,18 @@ use futures::{ sink::SinkExt, task::LocalSpawn, }; -use libp2p::{core::multiaddr, identity::Keypair, PeerId}; +use libp2p::{ + core::multiaddr, + identity::{Keypair, SigningError}, + kad::record::Key as KademliaKey, + PeerId, +}; use prometheus_endpoint::prometheus::default_registry; use sc_client_api::HeaderBackend; -use sc_network_common::service::{KademliaKey, Signature, SigningError}; +use sc_network::Signature; use sp_api::{ApiRef, ProvideRuntimeApi}; -use sp_keystore::{testing::KeyStore, CryptoStore}; +use sp_keystore::{testing::MemoryKeystore, Keystore}; use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; use substrate_test_runtime_client::runtime::Block; @@ -184,6 +189,10 @@ impl NetworkStateInfo for TestNetwork { fn external_addresses(&self) -> Vec { self.external_addresses.clone() } + + fn listen_addresses(&self) -> Vec { + self.external_addresses.clone() + } } struct TestSigner<'a> { @@ -199,10 +208,10 @@ impl<'a> NetworkSigner for TestSigner<'a> { } } -async fn build_dht_event( +fn build_dht_event( addresses: Vec, public_key: AuthorityId, - key_store: &dyn CryptoStore, + key_store: &MemoryKeystore, network: Option<&Signer>, ) -> Vec<(KademliaKey, Vec)> { let serialized_record = @@ -215,7 +224,6 @@ async fn build_dht_event( key_store, vec![public_key.into()], ) - .await .unwrap(); // There is always a single item in it, because we signed it with a single key kv_pairs @@ -225,7 +233,7 @@ async fn build_dht_event( fn new_registers_metrics() { let (_dht_event_tx, dht_event_rx) = mpsc::channel(1000); let network: Arc = Arc::new(Default::default()); - let key_store = KeyStore::new(); + let key_store = MemoryKeystore::new(); let test_api = Arc::new(TestApi { authorities: vec![] }); let registry = prometheus_endpoint::Registry::new(); @@ -257,7 +265,7 @@ fn triggers_dht_get_query() { let test_api = Arc::new(TestApi { authorities: authorities.clone() }); let network = Arc::new(TestNetwork::default()); - let key_store = KeyStore::new(); + let key_store = MemoryKeystore::new(); let (_to_worker, from_service) = mpsc::channel(0); let mut worker = Worker::new( @@ -289,14 +297,12 @@ fn publish_discover_cycle() { let network: Arc = Arc::new(Default::default()); - let key_store = KeyStore::new(); + let key_store = MemoryKeystore::new(); let _ = pool.spawner().spawn_local_obj( async move { - let node_a_public = key_store - .sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None) - .await - .unwrap(); + let node_a_public = + key_store.sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None).unwrap(); let test_api = Arc::new(TestApi { authorities: vec![node_a_public.into()] }); let (_to_worker, from_service) = mpsc::channel(0); @@ -328,7 +334,7 @@ fn publish_discover_cycle() { authorities: vec![node_a_public.into()], }); let network: Arc = Arc::new(Default::default()); - let key_store = KeyStore::new(); + let key_store = MemoryKeystore::new(); let (_to_worker, from_service) = mpsc::channel(0); let mut worker = Worker::new( @@ -362,7 +368,7 @@ fn publish_discover_cycle() { fn terminate_when_event_stream_terminates() { let (dht_event_tx, dht_event_rx) = channel(1000); let network: Arc = Arc::new(Default::default()); - let key_store = KeyStore::new(); + let key_store = MemoryKeystore::new(); let test_api = Arc::new(TestApi { authorities: vec![] }); let (to_worker, from_service) = mpsc::channel(0); @@ -411,11 +417,11 @@ fn dont_stop_polling_dht_event_stream_after_bogus_event() { address.with(multiaddr::Protocol::P2p(peer_id.into())) }; - let remote_key_store = KeyStore::new(); - let remote_public_key: AuthorityId = - block_on(remote_key_store.sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None)) - .unwrap() - .into(); + let remote_key_store = MemoryKeystore::new(); + let remote_public_key: AuthorityId = remote_key_store + .sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None) + .unwrap() + .into(); let (mut dht_event_tx, dht_event_rx) = channel(1); let (network, mut network_events) = { @@ -424,7 +430,7 @@ fn dont_stop_polling_dht_event_stream_after_bogus_event() { (Arc::new(n), r) }; - let key_store = KeyStore::new(); + let key_store = MemoryKeystore::new(); let test_api = Arc::new(TestApi { authorities: vec![remote_public_key.clone()] }); let mut pool = LocalPool::new(); @@ -471,8 +477,7 @@ fn dont_stop_polling_dht_event_stream_after_bogus_event() { remote_public_key.clone(), &remote_key_store, None, - ) - .await; + ); DhtEvent::ValueFound(kv_pairs) }; dht_event_tx.send(dht_event).await.expect("Channel has capacity of 1."); @@ -489,7 +494,7 @@ fn dont_stop_polling_dht_event_stream_after_bogus_event() { } struct DhtValueFoundTester { - pub remote_key_store: KeyStore, + pub remote_key_store: MemoryKeystore, pub remote_authority_public: sp_core::sr25519::Public, pub remote_node_key: Keypair, pub local_worker: Option< @@ -507,10 +512,10 @@ struct DhtValueFoundTester { impl DhtValueFoundTester { fn new() -> Self { - let remote_key_store = KeyStore::new(); - let remote_authority_public = - block_on(remote_key_store.sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None)) - .unwrap(); + let remote_key_store = MemoryKeystore::new(); + let remote_authority_public = remote_key_store + .sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None) + .unwrap(); let remote_node_key = Keypair::generate_ed25519(); Self { remote_key_store, remote_authority_public, remote_node_key, local_worker: None } @@ -533,7 +538,7 @@ impl DhtValueFoundTester { let local_test_api = Arc::new(TestApi { authorities: vec![self.remote_authority_public.into()] }); let local_network: Arc = Arc::new(Default::default()); - let local_key_store = KeyStore::new(); + let local_key_store = MemoryKeystore::new(); let (_to_worker, from_service) = mpsc::channel(0); let mut local_worker = Worker::new( @@ -567,12 +572,12 @@ fn limit_number_of_addresses_added_to_cache_per_authority() { let mut tester = DhtValueFoundTester::new(); assert!(MAX_ADDRESSES_PER_AUTHORITY < 100); let addresses = (1..100).map(|i| tester.multiaddr_with_peer_id(i)).collect(); - let kv_pairs = block_on(build_dht_event::( + let kv_pairs = build_dht_event::( addresses, tester.remote_authority_public.into(), &tester.remote_key_store, None, - )); + ); let cached_remote_addresses = tester.process_value_found(false, kv_pairs); assert_eq!(MAX_ADDRESSES_PER_AUTHORITY, cached_remote_addresses.unwrap().len()); @@ -582,12 +587,12 @@ fn limit_number_of_addresses_added_to_cache_per_authority() { fn strict_accept_address_with_peer_signature() { let mut tester = DhtValueFoundTester::new(); let addr = tester.multiaddr_with_peer_id(1); - let kv_pairs = block_on(build_dht_event( + let kv_pairs = build_dht_event( vec![addr.clone()], tester.remote_authority_public.into(), &tester.remote_key_store, Some(&TestSigner { keypair: &tester.remote_node_key }), - )); + ); let cached_remote_addresses = tester.process_value_found(true, kv_pairs); @@ -602,12 +607,12 @@ fn strict_accept_address_with_peer_signature() { fn reject_address_with_rogue_peer_signature() { let mut tester = DhtValueFoundTester::new(); let rogue_remote_node_key = Keypair::generate_ed25519(); - let kv_pairs = block_on(build_dht_event( + let kv_pairs = build_dht_event( vec![tester.multiaddr_with_peer_id(1)], tester.remote_authority_public.into(), &tester.remote_key_store, Some(&TestSigner { keypair: &rogue_remote_node_key }), - )); + ); let cached_remote_addresses = tester.process_value_found(false, kv_pairs); @@ -620,12 +625,12 @@ fn reject_address_with_rogue_peer_signature() { #[test] fn reject_address_with_invalid_peer_signature() { let mut tester = DhtValueFoundTester::new(); - let mut kv_pairs = block_on(build_dht_event( + let mut kv_pairs = build_dht_event( vec![tester.multiaddr_with_peer_id(1)], tester.remote_authority_public.into(), &tester.remote_key_store, Some(&TestSigner { keypair: &tester.remote_node_key }), - )); + ); // tamper with the signature let mut record = schema::SignedAuthorityRecord::decode(kv_pairs[0].1.as_slice()).unwrap(); record.peer_signature.as_mut().map(|p| p.signature[1] = !p.signature[1]); @@ -642,12 +647,12 @@ fn reject_address_with_invalid_peer_signature() { #[test] fn reject_address_without_peer_signature() { let mut tester = DhtValueFoundTester::new(); - let kv_pairs = block_on(build_dht_event::( + let kv_pairs = build_dht_event::( vec![tester.multiaddr_with_peer_id(1)], tester.remote_authority_public.into(), &tester.remote_key_store, None, - )); + ); let cached_remote_addresses = tester.process_value_found(true, kv_pairs); @@ -660,12 +665,12 @@ fn do_not_cache_addresses_without_peer_id() { let multiaddr_with_peer_id = tester.multiaddr_with_peer_id(1); let multiaddr_without_peer_id: Multiaddr = "/ip6/2001:db8:0:0:0:0:0:2/tcp/30333".parse().unwrap(); - let kv_pairs = block_on(build_dht_event::( + let kv_pairs = build_dht_event::( vec![multiaddr_with_peer_id.clone(), multiaddr_without_peer_id], tester.remote_authority_public.into(), &tester.remote_key_store, None, - )); + ); let cached_remote_addresses = tester.process_value_found(false, kv_pairs); @@ -692,7 +697,7 @@ fn addresses_to_publish_adds_p2p() { Arc::new(TestApi { authorities: vec![] }), network.clone(), Box::pin(dht_event_rx), - Role::PublishAndDiscover(Arc::new(KeyStore::new())), + Role::PublishAndDiscover(MemoryKeystore::new().into()), Some(prometheus_endpoint::Registry::new()), Default::default(), ); @@ -726,7 +731,7 @@ fn addresses_to_publish_respects_existing_p2p_protocol() { Arc::new(TestApi { authorities: vec![] }), network.clone(), Box::pin(dht_event_rx), - Role::PublishAndDiscover(Arc::new(KeyStore::new())), + Role::PublishAndDiscover(MemoryKeystore::new().into()), Some(prometheus_endpoint::Registry::new()), Default::default(), ); @@ -746,10 +751,11 @@ fn lookup_throttling() { address.with(multiaddr::Protocol::P2p(peer_id.into())) }; - let remote_key_store = KeyStore::new(); + let remote_key_store = MemoryKeystore::new(); let remote_public_keys: Vec = (0..20) .map(|_| { - block_on(remote_key_store.sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None)) + remote_key_store + .sr25519_generate_new(key_types::AUTHORITY_DISCOVERY, None) .unwrap() .into() }) @@ -809,8 +815,7 @@ fn lookup_throttling() { remote_key, &remote_key_store, None, - ) - .await; + ); DhtEvent::ValueFound(kv_pairs) }; dht_event_tx.send(dht_event).await.expect("Channel has capacity of 1."); diff --git a/client/basic-authorship/Cargo.toml b/client/basic-authorship/Cargo.toml index c4f1d2e245f9..dde2e15bd647 100644 --- a/client/basic-authorship/Cargo.toml +++ b/client/basic-authorship/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" futures-timer = "3.0.1" log = "0.4.17" @@ -26,9 +26,9 @@ sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../client/transact sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } [dev-dependencies] parking_lot = "0.12.1" diff --git a/client/basic-authorship/src/basic_authorship.rs b/client/basic-authorship/src/basic_authorship.rs index c39d07a14f0f..d1921b3413cb 100644 --- a/client/basic-authorship/src/basic_authorship.rs +++ b/client/basic-authorship/src/basic_authorship.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -38,7 +38,6 @@ use sp_consensus::{DisableProofRecording, EnableProofRecording, ProofRecording, use sp_core::traits::SpawnNamed; use sp_inherents::InherentData; use sp_runtime::{ - generic::BlockId, traits::{BlakeTwo256, Block as BlockT, Hash as HashT, Header as HeaderT}, Digest, Percent, SaturatedConversion, }; @@ -58,6 +57,8 @@ pub const DEFAULT_BLOCK_SIZE_LIMIT: usize = 4 * 1024 * 1024 + 512; const DEFAULT_SOFT_DEADLINE_PERCENT: Percent = Percent::from_percent(50); +const LOG_TARGET: &'static str = "basic-authorship"; + /// [`Proposer`] factory. pub struct ProposerFactory { spawn_handle: Box, @@ -196,14 +197,12 @@ where ) -> Proposer { let parent_hash = parent_header.hash(); - let id = BlockId::hash(parent_hash); - info!("🙌 Starting consensus session on top of parent {:?}", parent_hash); let proposer = Proposer::<_, _, _, _, PR> { spawn_handle: self.spawn_handle.clone(), client: self.client.clone(), - parent_id: id, + parent_hash, parent_number: *parent_header.number(), transaction_pool: self.transaction_pool.clone(), now, @@ -247,7 +246,7 @@ where pub struct Proposer { spawn_handle: Box, client: Arc, - parent_id: BlockId, + parent_hash: Block::Hash, parent_number: <::Header as HeaderT>::Number, transaction_pool: Arc, now: Box time::Instant + Send + Sync>, @@ -305,7 +304,7 @@ where .propose_with(inherent_data, inherent_digests, deadline, block_size_limit) .await; if tx.send(res).is_err() { - trace!("Could not send block production result to proposer!"); + trace!(target: LOG_TARGET, "Could not send block production result to proposer!"); } }), ); @@ -342,10 +341,34 @@ where block_size_limit: Option, ) -> Result, PR::Proof>, sp_blockchain::Error> { - let propose_with_start = time::Instant::now(); + let propose_with_timer = time::Instant::now(); let mut block_builder = - self.client.new_block_at(&self.parent_id, inherent_digests, PR::ENABLED)?; + self.client.new_block_at(self.parent_hash, inherent_digests, PR::ENABLED)?; + + self.apply_inherents(&mut block_builder, inherent_data)?; + // TODO call `after_inherents` and check if we should apply extrinsincs here + // + + let block_timer = time::Instant::now(); + let end_reason = + self.apply_extrinsics(&mut block_builder, deadline, block_size_limit).await?; + let (block, storage_changes, proof) = block_builder.build()?.into_inner(); + let block_took = block_timer.elapsed(); + + let proof = + PR::into_proof(proof).map_err(|e| sp_blockchain::Error::Application(Box::new(e)))?; + + self.print_summary(&block, end_reason, block_took, propose_with_timer.elapsed()); + Ok(Proposal { block, proof, storage_changes }) + } + + /// Apply all inherents to the block. + fn apply_inherents( + &self, + block_builder: &mut sc_block_builder::BlockBuilder<'_, Block, C, B>, + inherent_data: InherentData, + ) -> Result<(), sp_blockchain::Error> { let create_inherents_start = time::Instant::now(); let inherents = block_builder.create_inherents(inherent_data)?; let create_inherents_end = time::Instant::now(); @@ -361,7 +384,7 @@ where for inherent in inherents { match block_builder.push(inherent) { Err(ApplyExtrinsicFailed(Validity(e))) if e.exhausted_resources() => { - warn!("⚠️ Dropping non-mandatory inherent from overweight block.") + warn!(target: LOG_TARGET, "⚠️ Dropping non-mandatory inherent from overweight block.") }, Err(ApplyExtrinsicFailed(Validity(e))) if e.was_mandatory() => { error!( @@ -370,12 +393,21 @@ where return Err(ApplyExtrinsicFailed(Validity(e))) }, Err(e) => { - warn!("❗️ Inherent extrinsic returned unexpected error: {}. Dropping.", e); + warn!(target: LOG_TARGET, "❗️ Inherent extrinsic returned unexpected error: {}. Dropping.", e); }, Ok(_) => {}, } } + Ok(()) + } + /// Apply as many extrinsics as possible to the block. + async fn apply_extrinsics( + &self, + block_builder: &mut sc_block_builder::BlockBuilder<'_, Block, C, B>, + deadline: time::Instant, + block_size_limit: Option, + ) -> Result { // proceed with transactions // We calculate soft deadline used only in case we start skipping transactions. let now = (self.now)(); @@ -383,7 +415,6 @@ where let left_micros: u64 = left.as_micros().saturated_into(); let soft_deadline = now + time::Duration::from_micros(self.soft_deadline_percent.mul_floor(left_micros)); - let block_timer = time::Instant::now(); let mut skipped = 0; let mut unqueue_invalid = Vec::new(); @@ -394,7 +425,7 @@ where let mut pending_iterator = select! { res = t1 => res, _ = t2 => { - log::warn!( + warn!(target: LOG_TARGET, "Timeout fired waiting for transaction pool at block #{}. \ Proceeding with production.", self.parent_number, @@ -405,8 +436,8 @@ where let block_size_limit = block_size_limit.unwrap_or(self.default_block_size_limit); - debug!("Attempting to push transactions from the pool."); - debug!("Pool status: {:?}", self.transaction_pool.status()); + debug!(target: LOG_TARGET, "Attempting to push transactions from the pool."); + debug!(target: LOG_TARGET, "Pool status: {:?}", self.transaction_pool.status()); let mut transaction_pushed = false; let end_reason = loop { @@ -418,7 +449,7 @@ where let now = (self.now)(); if now > deadline { - debug!( + debug!(target: LOG_TARGET, "Consensus deadline reached when pushing block transactions, \ proceeding with proposing." ); @@ -434,95 +465,104 @@ where pending_iterator.report_invalid(&pending_tx); if skipped < MAX_SKIPPED_TRANSACTIONS { skipped += 1; - debug!( + debug!(target: LOG_TARGET, "Transaction would overflow the block size limit, \ but will try {} more transactions before quitting.", MAX_SKIPPED_TRANSACTIONS - skipped, ); continue } else if now < soft_deadline { - debug!( + debug!(target: LOG_TARGET, "Transaction would overflow the block size limit, \ but we still have time before the soft deadline, so \ we will try a bit more." ); continue } else { - debug!("Reached block size limit, proceeding with proposing."); + debug!(target: LOG_TARGET, "Reached block size limit, proceeding with proposing."); break EndProposingReason::HitBlockSizeLimit } } - trace!("[{:?}] Pushing to the block.", pending_tx_hash); - match sc_block_builder::BlockBuilder::push(&mut block_builder, pending_tx_data) { + trace!(target: LOG_TARGET, "[{:?}] Pushing to the block.", pending_tx_hash); + match sc_block_builder::BlockBuilder::push(block_builder, pending_tx_data) { Ok(()) => { transaction_pushed = true; - debug!("[{:?}] Pushed to the block.", pending_tx_hash); + debug!(target: LOG_TARGET, "[{:?}] Pushed to the block.", pending_tx_hash); }, Err(ApplyExtrinsicFailed(Validity(e))) if e.exhausted_resources() => { pending_iterator.report_invalid(&pending_tx); if skipped < MAX_SKIPPED_TRANSACTIONS { skipped += 1; - debug!( + debug!(target: LOG_TARGET, "Block seems full, but will try {} more transactions before quitting.", MAX_SKIPPED_TRANSACTIONS - skipped, ); } else if (self.now)() < soft_deadline { - debug!( + debug!(target: LOG_TARGET, "Block seems full, but we still have time before the soft deadline, \ so we will try a bit more before quitting." ); } else { - debug!("Reached block weight limit, proceeding with proposing."); + debug!(target: LOG_TARGET, "Reached block weight limit, proceeding with proposing."); break EndProposingReason::HitBlockWeightLimit } }, - Err(e) if skipped > 0 => { - pending_iterator.report_invalid(&pending_tx); - trace!( - "[{:?}] Ignoring invalid transaction when skipping: {}", - pending_tx_hash, - e - ); - }, Err(e) => { pending_iterator.report_invalid(&pending_tx); - debug!("[{:?}] Invalid transaction: {}", pending_tx_hash, e); + debug!(target: LOG_TARGET, "[{:?}] Invalid transaction: {}", pending_tx_hash, e); unqueue_invalid.push(pending_tx_hash); }, } }; if matches!(end_reason, EndProposingReason::HitBlockSizeLimit) && !transaction_pushed { - warn!( + warn!(target: LOG_TARGET, "Hit block size limit of `{}` without including any transaction!", block_size_limit, ); } self.transaction_pool.remove_invalid(&unqueue_invalid); + Ok(end_reason) + } - let (block, storage_changes, proof) = block_builder.build()?.into_inner(); - + /// Prints a summary and does telemetry + metrics. + fn print_summary( + &self, + block: &Block, + end_reason: EndProposingReason, + block_took: time::Duration, + propose_with_took: time::Duration, + ) { + let extrinsics = block.extrinsics(); self.metrics.report(|metrics| { - metrics.number_of_transactions.set(block.extrinsics().len() as u64); - metrics.block_constructed.observe(block_timer.elapsed().as_secs_f64()); - + metrics.number_of_transactions.set(extrinsics.len() as u64); + metrics.block_constructed.observe(block_took.as_secs_f64()); metrics.report_end_proposing_reason(end_reason); + metrics.create_block_proposal_time.observe(propose_with_took.as_secs_f64()); }); + let extrinsics_summary = if extrinsics.is_empty() { + "no extrinsics".to_string() + } else { + format!( + "extrinsics ({}): [{}]", + extrinsics.len(), + extrinsics + .iter() + .map(|xt| BlakeTwo256::hash_of(xt).to_string()) + .collect::>() + .join(", ") + ) + }; + info!( - "🎁 Prepared block for proposing at {} ({} ms) [hash: {:?}; parent_hash: {}; extrinsics ({}): [{}]]", + "🎁 Prepared block for proposing at {} ({} ms) [hash: {:?}; parent_hash: {}; {extrinsics_summary}", block.header().number(), - block_timer.elapsed().as_millis(), + block_took.as_millis(), ::Hash::from(block.header().hash()), block.header().parent_hash(), - block.extrinsics().len(), - block.extrinsics() - .iter() - .map(|xt| BlakeTwo256::hash_of(xt).to_string()) - .collect::>() - .join(", ") ); telemetry!( self.telemetry; @@ -531,18 +571,6 @@ where "number" => ?block.header().number(), "hash" => ?::Hash::from(block.header().hash()), ); - - let proof = - PR::into_proof(proof).map_err(|e| sp_blockchain::Error::Application(Box::new(e)))?; - - let propose_with_end = time::Instant::now(); - self.metrics.report(|metrics| { - metrics.create_block_proposal_time.observe( - propose_with_end.saturating_duration_since(propose_with_start).as_secs_f64(), - ); - }); - - Ok(Proposal { block, proof, storage_changes }) } } @@ -558,37 +586,29 @@ mod tests { use sp_api::Core; use sp_blockchain::HeaderBackend; use sp_consensus::{BlockOrigin, Environment, Proposer}; - use sp_core::Pair; - use sp_runtime::traits::NumberFor; + use sp_runtime::{generic::BlockId, traits::NumberFor, Perbill}; use substrate_test_runtime_client::{ prelude::*, - runtime::{Extrinsic, Transfer}, + runtime::{Block as TestBlock, Extrinsic, ExtrinsicBuilder, Transfer}, TestClientBuilder, TestClientBuilderExt, }; const SOURCE: TransactionSource = TransactionSource::External; - fn extrinsic(nonce: u64) -> Extrinsic { - Transfer { - amount: Default::default(), - nonce, - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), - } - .into_signed_tx() - } + // Note: + // Maximum normal extrinsic size for `substrate_test_runtime` is ~65% of max_block (refer to + // `substrate_test_runtime::RuntimeBlockWeights` for details). + // This extrinsic sizing allows for: + // - one huge xts + a lot of tiny dust + // - one huge, no medium, + // - two medium xts + // This is widely exploited in following tests. + const HUGE: u32 = 649000000; + const MEDIUM: u32 = 250000000; + const TINY: u32 = 1000; - fn exhausts_resources_extrinsic_from(who: usize) -> Extrinsic { - let pair = AccountKeyring::numeric(who); - let transfer = Transfer { - // increase the amount to bump priority - amount: 1, - nonce: 0, - from: pair.public(), - to: AccountKeyring::Bob.into(), - }; - let signature = pair.sign(&transfer.encode()).into(); - Extrinsic::Transfer { transfer, signature, exhaust_resources_when_not_first: true } + fn extrinsic(nonce: u64) -> Extrinsic { + ExtrinsicBuilder::new_fill_block(Perbill::from_parts(TINY)).nonce(nonce).build() } fn chain_event(header: B::Header) -> ChainEvent @@ -731,7 +751,7 @@ mod tests { assert_eq!(proposal.block.extrinsics().len(), 1); let api = client.runtime_api(); - api.execute_block(&BlockId::Hash(genesis_hash), proposal.block).unwrap(); + api.execute_block(genesis_hash, proposal.block).unwrap(); let state = backend.state_at(genesis_hash).unwrap(); @@ -743,10 +763,13 @@ mod tests { ); } + // This test ensures that if one transaction of a user was rejected, because for example + // the weight limit was hit, we don't mark the other transactions of the user as invalid because + // the nonce is not matching. #[test] - fn should_not_remove_invalid_transactions_when_skipping() { + fn should_not_remove_invalid_transactions_from_the_same_sender_after_one_was_invalid() { // given - let mut client = Arc::new(substrate_test_runtime_client::new()); + let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); let txpool = BasicPool::new_full( Default::default(), @@ -756,38 +779,29 @@ mod tests { client.clone(), ); + let medium = |nonce| { + ExtrinsicBuilder::new_fill_block(Perbill::from_parts(MEDIUM)) + .nonce(nonce) + .build() + }; + let huge = |nonce| { + ExtrinsicBuilder::new_fill_block(Perbill::from_parts(HUGE)).nonce(nonce).build() + }; + block_on(txpool.submit_at( &BlockId::number(0), SOURCE, - vec![ - extrinsic(0), - extrinsic(1), - Transfer { - amount: Default::default(), - nonce: 2, - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), - }.into_resources_exhausting_tx(), - extrinsic(3), - Transfer { - amount: Default::default(), - nonce: 4, - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), - }.into_resources_exhausting_tx(), - extrinsic(5), - extrinsic(6), - ], + vec![medium(0), medium(1), huge(2), medium(3), huge(4), medium(5), medium(6)], )) .unwrap(); let mut proposer_factory = ProposerFactory::new(spawner.clone(), client.clone(), txpool.clone(), None, None); let mut propose_block = |client: &TestClient, - number, + parent_number, expected_block_extrinsics, expected_pool_transactions| { - let hash = client.expect_block_hash_from_id(&BlockId::Number(number)).unwrap(); + let hash = client.expect_block_hash_from_id(&BlockId::Number(parent_number)).unwrap(); let proposer = proposer_factory.init_with_now( &client.expect_header(hash).unwrap(), Box::new(move || time::Instant::now()), @@ -802,12 +816,30 @@ mod tests { // then // block should have some extrinsics although we have some more in the pool. - assert_eq!(txpool.ready().count(), expected_pool_transactions); - assert_eq!(block.extrinsics().len(), expected_block_extrinsics); + assert_eq!( + txpool.ready().count(), + expected_pool_transactions, + "at block: {}", + block.header.number + ); + assert_eq!( + block.extrinsics().len(), + expected_block_extrinsics, + "at block: {}", + block.header.number + ); block }; + let import_and_maintain = |mut client: Arc, block: TestBlock| { + let hash = block.hash(); + block_on(client.import(BlockOrigin::Own, block)).unwrap(); + block_on(txpool.maintain(chain_event( + client.expect_header(hash).expect("there should be header"), + ))); + }; + block_on( txpool.maintain(chain_event( client @@ -819,19 +851,28 @@ mod tests { // let's create one block and import it let block = propose_block(&client, 0, 2, 7); - let hashof1 = block.hash(); - block_on(client.import(BlockOrigin::Own, block)).unwrap(); - - block_on( - txpool.maintain(chain_event( - client.expect_header(hashof1).expect("there should be header"), - )), - ); + import_and_maintain(client.clone(), block); assert_eq!(txpool.ready().count(), 5); // now let's make sure that we can still make some progress - let block = propose_block(&client, 1, 2, 5); - block_on(client.import(BlockOrigin::Own, block)).unwrap(); + let block = propose_block(&client, 1, 1, 5); + import_and_maintain(client.clone(), block); + assert_eq!(txpool.ready().count(), 4); + + // again let's make sure that we can still make some progress + let block = propose_block(&client, 2, 1, 4); + import_and_maintain(client.clone(), block); + assert_eq!(txpool.ready().count(), 3); + + // again let's make sure that we can still make some progress + let block = propose_block(&client, 3, 1, 3); + import_and_maintain(client.clone(), block); + assert_eq!(txpool.ready().count(), 2); + + // again let's make sure that we can still make some progress + let block = propose_block(&client, 4, 2, 2); + import_and_maintain(client.clone(), block); + assert_eq!(txpool.ready().count(), 0); } #[test] @@ -857,9 +898,9 @@ mod tests { amount: 100, nonce: 0, } - .into_signed_tx(), + .into_unchecked_extrinsic(), ) - .chain((0..extrinsics_num - 1).map(|v| Extrinsic::IncludeData(vec![v as u8; 10]))) + .chain((1..extrinsics_num as u64).map(extrinsic)) .collect::>(); let block_limit = genesis_header.encoded_size() + @@ -870,7 +911,7 @@ mod tests { .sum::() + Vec::::new().encoded_size(); - block_on(txpool.submit_at(&BlockId::number(0), SOURCE, extrinsics)).unwrap(); + block_on(txpool.submit_at(&BlockId::number(0), SOURCE, extrinsics.clone())).unwrap(); block_on(txpool.maintain(chain_event(genesis_header.clone()))); @@ -913,7 +954,13 @@ mod tests { let proposer = block_on(proposer_factory.init(&genesis_header)).unwrap(); - // Give it enough time + // Exact block_limit, which includes: + // 99 (header_size) + 718 (proof@initialize_block) + 246 (one Transfer extrinsic) + let block_limit = { + let builder = + client.new_block_at(genesis_header.hash(), Default::default(), true).unwrap(); + builder.estimate_block_size(true) + extrinsics[0].encoded_size() + }; let block = block_on(proposer.propose( Default::default(), Default::default(), @@ -923,7 +970,7 @@ mod tests { .map(|r| r.block) .unwrap(); - // The block limit didn't changed, but we now include the proof in the estimation of the + // The block limit was increased, but we now include the proof in the estimation of the // block size and thus, only the `Transfer` will fit into the block. It reads more data // than we have reserved in the block limit. assert_eq!(block.extrinsics().len(), 1); @@ -942,6 +989,15 @@ mod tests { client.clone(), ); + let tiny = |nonce| { + ExtrinsicBuilder::new_fill_block(Perbill::from_parts(TINY)).nonce(nonce).build() + }; + let huge = |who| { + ExtrinsicBuilder::new_fill_block(Perbill::from_parts(HUGE)) + .signer(AccountKeyring::numeric(who)) + .build() + }; + block_on( txpool.submit_at( &BlockId::number(0), @@ -949,9 +1005,9 @@ mod tests { // add 2 * MAX_SKIPPED_TRANSACTIONS that exhaust resources (0..MAX_SKIPPED_TRANSACTIONS * 2) .into_iter() - .map(|i| exhausts_resources_extrinsic_from(i)) + .map(huge) // and some transactions that are okay. - .chain((0..MAX_SKIPPED_TRANSACTIONS).into_iter().map(|i| extrinsic(i as _))) + .chain((0..MAX_SKIPPED_TRANSACTIONS as u64).into_iter().map(tiny)) .collect(), ), ) @@ -1005,15 +1061,27 @@ mod tests { client.clone(), ); + let tiny = |who| { + ExtrinsicBuilder::new_fill_block(Perbill::from_parts(TINY)) + .signer(AccountKeyring::numeric(who)) + .nonce(1) + .build() + }; + let huge = |who| { + ExtrinsicBuilder::new_fill_block(Perbill::from_parts(HUGE)) + .signer(AccountKeyring::numeric(who)) + .build() + }; + block_on( txpool.submit_at( &BlockId::number(0), SOURCE, (0..MAX_SKIPPED_TRANSACTIONS + 2) .into_iter() - .map(|i| exhausts_resources_extrinsic_from(i)) + .map(huge) // and some transactions that are okay. - .chain((0..MAX_SKIPPED_TRANSACTIONS).into_iter().map(|i| extrinsic(i as _))) + .chain((0..MAX_SKIPPED_TRANSACTIONS + 2).into_iter().map(tiny)) .collect(), ), ) @@ -1026,7 +1094,7 @@ mod tests { .expect("there should be header"), )), ); - assert_eq!(txpool.ready().count(), MAX_SKIPPED_TRANSACTIONS * 2 + 2); + assert_eq!(txpool.ready().count(), MAX_SKIPPED_TRANSACTIONS * 2 + 4); let mut proposer_factory = ProposerFactory::new(spawner.clone(), client.clone(), txpool.clone(), None, None); @@ -1057,8 +1125,13 @@ mod tests { .map(|r| r.block) .unwrap(); - // then the block should have no transactions despite some in the pool - assert_eq!(block.extrinsics().len(), 1); + // then the block should have one or two transactions. This maybe random as they are + // processed in parallel. The same signer and consecutive nonces for huge and tiny + // transactions guarantees that max two transactions will get to the block. + assert!( + (1..3).contains(&block.extrinsics().len()), + "Block shall contain one or two extrinsics." + ); assert!( cell2.lock().0 > MAX_SKIPPED_TRANSACTIONS, "Not enough calls to current time, which indicates the test might have ended because of deadline, not soft deadline" diff --git a/client/basic-authorship/src/lib.rs b/client/basic-authorship/src/lib.rs index e7f0d4496ede..8f47c2ea00e6 100644 --- a/client/basic-authorship/src/lib.rs +++ b/client/basic-authorship/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -26,7 +26,7 @@ //! # use sp_runtime::generic::BlockId; //! # use std::{sync::Arc, time::Duration}; //! # use substrate_test_runtime_client::{ -//! # runtime::{Extrinsic, Transfer}, AccountKeyring, +//! # runtime::Transfer, AccountKeyring, //! # DefaultTestClientBuilderExt, TestClientBuilderExt, //! # }; //! # use sc_transaction_pool::{BasicPool, FullChainApi}; diff --git a/client/beefy/Cargo.toml b/client/beefy/Cargo.toml deleted file mode 100644 index d81ff2587cf2..000000000000 --- a/client/beefy/Cargo.toml +++ /dev/null @@ -1,50 +0,0 @@ -[package] -name = "beefy-gadget" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2021" -license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -repository = "https://github.com/paritytech/substrate" -description = "BEEFY Client gadget for substrate" -homepage = "https://substrate.io" - -[dependencies] -array-bytes = "4.1" -async-trait = "0.1.57" -codec = { package = "parity-scale-codec", version = "3.2.2", features = ["derive"] } -fnv = "1.0.6" -futures = "0.3" -log = "0.4" -parking_lot = "0.12.1" -thiserror = "1.0" -wasm-timer = "0.2.5" -beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy", package = "sp-beefy" } -prometheus = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } -sc-client-api = { version = "4.0.0-dev", path = "../api" } -sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } -sc-keystore = { version = "4.0.0-dev", path = "../keystore" } -sc-network = { version = "0.10.0-dev", path = "../network" } -sc-network-common = { version = "0.10.0-dev", path = "../network/common" } -sc-network-gossip = { version = "0.10.0-dev", path = "../network-gossip" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-application-crypto = { version = "7.0.0", path = "../../primitives/application-crypto" } -sp-arithmetic = { version = "6.0.0", path = "../../primitives/arithmetic" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } -sp-mmr-primitives = { version = "4.0.0-dev", path = "../../primitives/merkle-mountain-range" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } - -[dev-dependencies] -serde = "1.0.136" -strum = { version = "0.24.1", features = ["derive"] } -tempfile = "3.1.0" -tokio = "1.22.0" -sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } -sc-network-test = { version = "0.8.0", path = "../network/test" } -sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" } -sp-keyring = { version = "7.0.0", path = "../../primitives/keyring" } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } diff --git a/client/beefy/src/communication/gossip.rs b/client/beefy/src/communication/gossip.rs deleted file mode 100644 index 116a6286ed54..000000000000 --- a/client/beefy/src/communication/gossip.rs +++ /dev/null @@ -1,473 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. -// 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 std::{collections::BTreeMap, sync::Arc, time::Duration}; - -use sc_network::PeerId; -use sc_network_gossip::{MessageIntent, ValidationResult, Validator, ValidatorContext}; -use sp_core::hashing::twox_64; -use sp_runtime::traits::{Block, Hash, Header, NumberFor}; - -use codec::{Decode, Encode}; -use log::{debug, trace}; -use parking_lot::{Mutex, RwLock}; -use wasm_timer::Instant; - -use crate::{communication::peers::KnownPeers, keystore::BeefyKeystore, LOG_TARGET}; -use beefy_primitives::{ - crypto::{Public, Signature}, - VoteMessage, -}; - -// Timeout for rebroadcasting messages. -const REBROADCAST_AFTER: Duration = Duration::from_secs(60 * 5); - -/// Gossip engine messages topic -pub(crate) fn topic() -> B::Hash -where - B: Block, -{ - <::Hashing as Hash>::hash(b"beefy") -} - -/// A type that represents hash of the message. -pub type MessageHash = [u8; 8]; - -struct KnownVotes { - last_done: Option>, - live: BTreeMap, fnv::FnvHashSet>, -} - -impl KnownVotes { - pub fn new() -> Self { - Self { last_done: None, live: BTreeMap::new() } - } - - /// Create new round votes set if not already present. - fn insert(&mut self, round: NumberFor) { - self.live.entry(round).or_default(); - } - - /// Remove `round` and older from live set, update `last_done` accordingly. - fn conclude(&mut self, round: NumberFor) { - self.live.retain(|&number, _| number > round); - self.last_done = self.last_done.max(Some(round)); - } - - /// Return true if `round` is newer than previously concluded rounds. - /// - /// Latest concluded round is still considered alive to allow proper gossiping for it. - fn is_live(&self, round: &NumberFor) -> bool { - Some(*round) >= self.last_done - } - - /// Add new _known_ `hash` to the round's known votes. - fn add_known(&mut self, round: &NumberFor, hash: MessageHash) { - self.live.get_mut(round).map(|known| known.insert(hash)); - } - - /// Check if `hash` is already part of round's known votes. - fn is_known(&self, round: &NumberFor, hash: &MessageHash) -> bool { - self.live.get(round).map(|known| known.contains(hash)).unwrap_or(false) - } -} - -/// BEEFY gossip validator -/// -/// Validate BEEFY gossip messages and limit the number of live BEEFY voting rounds. -/// -/// Allows messages for 'rounds >= last concluded' to flow, everything else gets -/// rejected/expired. -/// -///All messaging is handled in a single BEEFY global topic. -pub(crate) struct GossipValidator -where - B: Block, -{ - topic: B::Hash, - known_votes: RwLock>, - next_rebroadcast: Mutex, - known_peers: Arc>>, -} - -impl GossipValidator -where - B: Block, -{ - pub fn new(known_peers: Arc>>) -> GossipValidator { - GossipValidator { - topic: topic::(), - known_votes: RwLock::new(KnownVotes::new()), - next_rebroadcast: Mutex::new(Instant::now() + REBROADCAST_AFTER), - known_peers, - } - } - - /// Note a voting round. - /// - /// Noting round will start a live `round`. - pub(crate) fn note_round(&self, round: NumberFor) { - debug!(target: LOG_TARGET, "🥩 About to note gossip round #{}", round); - self.known_votes.write().insert(round); - } - - /// Conclude a voting round. - /// - /// This can be called once round is complete so we stop gossiping for it. - pub(crate) fn conclude_round(&self, round: NumberFor) { - debug!(target: LOG_TARGET, "🥩 About to drop gossip round #{}", round); - self.known_votes.write().conclude(round); - } -} - -impl Validator for GossipValidator -where - B: Block, -{ - fn peer_disconnected(&self, _context: &mut dyn ValidatorContext, who: &PeerId) { - self.known_peers.lock().remove(who); - } - - fn validate( - &self, - _context: &mut dyn ValidatorContext, - sender: &PeerId, - mut data: &[u8], - ) -> ValidationResult { - if let Ok(msg) = VoteMessage::, Public, Signature>::decode(&mut data) { - let msg_hash = twox_64(data); - let round = msg.commitment.block_number; - - // Verify general usefulness of the message. - // We are going to discard old votes right away (without verification) - // Also we keep track of already received votes to avoid verifying duplicates. - { - let known_votes = self.known_votes.read(); - - if !known_votes.is_live(&round) { - return ValidationResult::Discard - } - - if known_votes.is_known(&round, &msg_hash) { - return ValidationResult::ProcessAndKeep(self.topic) - } - } - - if BeefyKeystore::verify(&msg.id, &msg.signature, &msg.commitment.encode()) { - self.known_votes.write().add_known(&round, msg_hash); - self.known_peers.lock().note_vote_for(*sender, round); - return ValidationResult::ProcessAndKeep(self.topic) - } else { - // TODO: report peer - debug!( - target: LOG_TARGET, - "🥩 Bad signature on message: {:?}, from: {:?}", msg, sender - ); - } - } - - ValidationResult::Discard - } - - fn message_expired<'a>(&'a self) -> Box bool + 'a> { - let known_votes = self.known_votes.read(); - Box::new(move |_topic, mut data| { - let msg = match VoteMessage::, Public, Signature>::decode(&mut data) { - Ok(vote) => vote, - Err(_) => return true, - }; - - let round = msg.commitment.block_number; - let expired = !known_votes.is_live(&round); - - trace!(target: LOG_TARGET, "🥩 Message for round #{} expired: {}", round, expired); - - expired - }) - } - - fn message_allowed<'a>( - &'a self, - ) -> Box bool + 'a> { - let do_rebroadcast = { - let now = Instant::now(); - let mut next_rebroadcast = self.next_rebroadcast.lock(); - if now >= *next_rebroadcast { - *next_rebroadcast = now + REBROADCAST_AFTER; - true - } else { - false - } - }; - - let known_votes = self.known_votes.read(); - Box::new(move |_who, intent, _topic, mut data| { - if let MessageIntent::PeriodicRebroadcast = intent { - return do_rebroadcast - } - - let msg = match VoteMessage::, Public, Signature>::decode(&mut data) { - Ok(vote) => vote, - Err(_) => return false, - }; - - let round = msg.commitment.block_number; - let allowed = known_votes.is_live(&round); - - trace!(target: LOG_TARGET, "🥩 Message for round #{} allowed: {}", round, allowed); - - allowed - }) - } -} - -#[cfg(test)] -mod tests { - use sc_keystore::LocalKeystore; - use sc_network_test::Block; - use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; - - use crate::keystore::{tests::Keyring, BeefyKeystore}; - use beefy_primitives::{ - crypto::Signature, known_payloads, Commitment, MmrRootHash, Payload, VoteMessage, KEY_TYPE, - }; - - use super::*; - - #[test] - fn known_votes_insert_remove() { - let mut kv = KnownVotes::::new(); - - kv.insert(1); - kv.insert(1); - kv.insert(2); - assert_eq!(kv.live.len(), 2); - - let mut kv = KnownVotes::::new(); - kv.insert(1); - kv.insert(2); - kv.insert(3); - - assert!(kv.last_done.is_none()); - kv.conclude(2); - assert_eq!(kv.live.len(), 1); - assert!(!kv.live.contains_key(&2)); - assert_eq!(kv.last_done, Some(2)); - - kv.conclude(1); - assert_eq!(kv.last_done, Some(2)); - - kv.conclude(3); - assert_eq!(kv.last_done, Some(3)); - assert!(kv.live.is_empty()); - } - - #[test] - fn note_and_drop_round_works() { - let gv = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); - - gv.note_round(1u64); - - assert!(gv.known_votes.read().is_live(&1u64)); - - gv.note_round(3u64); - gv.note_round(7u64); - gv.note_round(10u64); - - assert_eq!(gv.known_votes.read().live.len(), 4); - - gv.conclude_round(7u64); - - let votes = gv.known_votes.read(); - - // rounds 1 and 3 are outdated, don't gossip anymore - assert!(!votes.is_live(&1u64)); - assert!(!votes.is_live(&3u64)); - // latest concluded round is still gossiped - assert!(votes.is_live(&7u64)); - // round 10 is alive and in-progress - assert!(votes.is_live(&10u64)); - } - - #[test] - fn note_same_round_twice() { - let gv = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); - - gv.note_round(3u64); - gv.note_round(7u64); - gv.note_round(10u64); - - assert_eq!(gv.known_votes.read().live.len(), 3); - - // note round #7 again -> should not change anything - gv.note_round(7u64); - - let votes = gv.known_votes.read(); - - assert_eq!(votes.live.len(), 3); - - assert!(votes.is_live(&3u64)); - assert!(votes.is_live(&7u64)); - assert!(votes.is_live(&10u64)); - } - - struct TestContext; - impl ValidatorContext for TestContext { - fn broadcast_topic(&mut self, _topic: B::Hash, _force: bool) { - todo!() - } - - fn broadcast_message(&mut self, _topic: B::Hash, _message: Vec, _force: bool) { - todo!() - } - - fn send_message(&mut self, _who: &sc_network::PeerId, _message: Vec) { - todo!() - } - - fn send_topic(&mut self, _who: &sc_network::PeerId, _topic: B::Hash, _force: bool) { - todo!() - } - } - - fn sign_commitment(who: &Keyring, commitment: &Commitment) -> Signature { - let store: SyncCryptoStorePtr = std::sync::Arc::new(LocalKeystore::in_memory()); - SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&who.to_seed())).unwrap(); - let beefy_keystore: BeefyKeystore = Some(store).into(); - - beefy_keystore.sign(&who.public(), &commitment.encode()).unwrap() - } - - fn dummy_vote(block_number: u64) -> VoteMessage { - let payload = Payload::from_single_entry( - known_payloads::MMR_ROOT_ID, - MmrRootHash::default().encode(), - ); - let commitment = Commitment { payload, block_number, validator_set_id: 0 }; - let signature = sign_commitment(&Keyring::Alice, &commitment); - - VoteMessage { commitment, id: Keyring::Alice.public(), signature } - } - - #[test] - fn should_avoid_verifying_signatures_twice() { - let gv = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); - let sender = sc_network::PeerId::random(); - let mut context = TestContext; - - let vote = dummy_vote(3); - - gv.note_round(3u64); - gv.note_round(7u64); - gv.note_round(10u64); - - // first time the cache should be populated - let res = gv.validate(&mut context, &sender, &vote.encode()); - - assert!(matches!(res, ValidationResult::ProcessAndKeep(_))); - assert_eq!( - gv.known_votes.read().live.get(&vote.commitment.block_number).map(|x| x.len()), - Some(1) - ); - - // second time we should hit the cache - let res = gv.validate(&mut context, &sender, &vote.encode()); - - assert!(matches!(res, ValidationResult::ProcessAndKeep(_))); - - // next we should quickly reject if the round is not live - gv.conclude_round(7_u64); - - assert!(!gv.known_votes.read().is_live(&vote.commitment.block_number)); - - let res = gv.validate(&mut context, &sender, &vote.encode()); - - assert!(matches!(res, ValidationResult::Discard)); - } - - #[test] - fn messages_allowed_and_expired() { - let gv = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); - let sender = sc_network::PeerId::random(); - let topic = Default::default(); - let intent = MessageIntent::Broadcast; - - // note round 2 and 3, then conclude 2 - gv.note_round(2u64); - gv.note_round(3u64); - gv.conclude_round(2u64); - let mut allowed = gv.message_allowed(); - let mut expired = gv.message_expired(); - - // check bad vote format - assert!(!allowed(&sender, intent, &topic, &mut [0u8; 16])); - assert!(expired(topic, &mut [0u8; 16])); - - // inactive round 1 -> expired - let vote = dummy_vote(1); - let mut encoded_vote = vote.encode(); - assert!(!allowed(&sender, intent, &topic, &mut encoded_vote)); - assert!(expired(topic, &mut encoded_vote)); - - // active round 2 -> !expired - concluded but still gossiped - let vote = dummy_vote(2); - let mut encoded_vote = vote.encode(); - assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); - assert!(!expired(topic, &mut encoded_vote)); - - // in progress round 3 -> !expired - let vote = dummy_vote(3); - let mut encoded_vote = vote.encode(); - assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); - assert!(!expired(topic, &mut encoded_vote)); - - // unseen round 4 -> !expired - let vote = dummy_vote(3); - let mut encoded_vote = vote.encode(); - assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); - assert!(!expired(topic, &mut encoded_vote)); - } - - #[test] - fn messages_rebroadcast() { - let gv = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); - let sender = sc_network::PeerId::random(); - let topic = Default::default(); - - let vote = dummy_vote(1); - let mut encoded_vote = vote.encode(); - - // re-broadcasting only allowed at `REBROADCAST_AFTER` intervals - let intent = MessageIntent::PeriodicRebroadcast; - let mut allowed = gv.message_allowed(); - - // rebroadcast not allowed so soon after GossipValidator creation - assert!(!allowed(&sender, intent, &topic, &mut encoded_vote)); - - // hack the inner deadline to be `now` - *gv.next_rebroadcast.lock() = Instant::now(); - - // still not allowed on old `allowed` closure result - assert!(!allowed(&sender, intent, &topic, &mut encoded_vote)); - - // renew closure result - let mut allowed = gv.message_allowed(); - // rebroadcast should be allowed now - assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); - } -} diff --git a/client/beefy/src/metrics.rs b/client/beefy/src/metrics.rs deleted file mode 100644 index 71e34e24c4fa..000000000000 --- a/client/beefy/src/metrics.rs +++ /dev/null @@ -1,107 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. -// 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 . - -//! BEEFY Prometheus metrics definition - -use prometheus::{register, Counter, Gauge, PrometheusError, Registry, U64}; - -/// BEEFY metrics exposed through Prometheus -pub(crate) struct Metrics { - /// Current active validator set id - pub beefy_validator_set_id: Gauge, - /// Total number of votes sent by this node - pub beefy_votes_sent: Counter, - /// Most recent concluded voting round - pub beefy_round_concluded: Gauge, - /// Best block finalized by BEEFY - pub beefy_best_block: Gauge, - /// Next block BEEFY should vote on - pub beefy_should_vote_on: Gauge, - /// Number of sessions with lagging signed commitment on mandatory block - pub beefy_lagging_sessions: Counter, -} - -impl Metrics { - pub(crate) fn register(registry: &Registry) -> Result { - Ok(Self { - beefy_validator_set_id: register( - Gauge::new( - "substrate_beefy_validator_set_id", - "Current BEEFY active validator set id.", - )?, - registry, - )?, - beefy_votes_sent: register( - Counter::new("substrate_beefy_votes_sent", "Number of votes sent by this node")?, - registry, - )?, - beefy_round_concluded: register( - Gauge::new( - "substrate_beefy_round_concluded", - "Voting round, that has been concluded", - )?, - registry, - )?, - beefy_best_block: register( - Gauge::new("substrate_beefy_best_block", "Best block finalized by BEEFY")?, - registry, - )?, - beefy_should_vote_on: register( - Gauge::new("substrate_beefy_should_vote_on", "Next block, BEEFY should vote on")?, - registry, - )?, - beefy_lagging_sessions: register( - Counter::new( - "substrate_beefy_lagging_sessions", - "Number of sessions with lagging signed commitment on mandatory block", - )?, - registry, - )?, - }) - } -} - -// Note: we use the `format` macro to convert an expr into a `u64`. This will fail, -// if expr does not derive `Display`. -#[macro_export] -macro_rules! metric_set { - ($self:ident, $m:ident, $v:expr) => {{ - let val: u64 = format!("{}", $v).parse().unwrap(); - - if let Some(metrics) = $self.metrics.as_ref() { - metrics.$m.set(val); - } - }}; -} - -#[macro_export] -macro_rules! metric_inc { - ($self:ident, $m:ident) => {{ - if let Some(metrics) = $self.metrics.as_ref() { - metrics.$m.inc(); - } - }}; -} - -#[cfg(test)] -#[macro_export] -macro_rules! metric_get { - ($self:ident, $m:ident) => {{ - $self.metrics.as_ref().map(|metrics| metrics.$m.clone()) - }}; -} diff --git a/client/beefy/src/round.rs b/client/beefy/src/round.rs deleted file mode 100644 index 647d42110e92..000000000000 --- a/client/beefy/src/round.rs +++ /dev/null @@ -1,456 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. -// 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 crate::LOG_TARGET; - -use beefy_primitives::{ - crypto::{Public, Signature}, - ValidatorSet, ValidatorSetId, -}; -use codec::{Decode, Encode}; -use log::{debug, trace}; -use sp_runtime::traits::{Block, NumberFor}; -use std::{collections::BTreeMap, hash::Hash}; - -/// Tracks for each round which validators have voted/signed and -/// whether the local `self` validator has voted/signed. -/// -/// Does not do any validation on votes or signatures, layers above need to handle that (gossip). -#[derive(Debug, Decode, Default, Encode, PartialEq)] -struct RoundTracker { - self_vote: bool, - votes: BTreeMap, -} - -impl RoundTracker { - fn add_vote(&mut self, vote: (Public, Signature), self_vote: bool) -> bool { - if self.votes.contains_key(&vote.0) { - return false - } - - self.self_vote = self.self_vote || self_vote; - self.votes.insert(vote.0, vote.1); - true - } - - fn has_self_vote(&self) -> bool { - self.self_vote - } - - fn is_done(&self, threshold: usize) -> bool { - self.votes.len() >= threshold - } -} - -/// Minimum size of `authorities` subset that produced valid signatures for a block to finalize. -pub fn threshold(authorities: usize) -> usize { - let faulty = authorities.saturating_sub(1) / 3; - authorities - faulty -} - -/// Keeps track of all voting rounds (block numbers) within a session. -/// Only round numbers > `best_done` are of interest, all others are considered stale. -/// -/// Does not do any validation on votes or signatures, layers above need to handle that (gossip). -#[derive(Debug, Decode, Encode, PartialEq)] -pub(crate) struct Rounds { - rounds: BTreeMap<(Payload, NumberFor), RoundTracker>, - session_start: NumberFor, - validator_set: ValidatorSet, - mandatory_done: bool, - best_done: Option>, -} - -impl Rounds -where - P: Ord + Hash + Clone, - B: Block, -{ - pub(crate) fn new(session_start: NumberFor, validator_set: ValidatorSet) -> Self { - Rounds { - rounds: BTreeMap::new(), - session_start, - validator_set, - mandatory_done: false, - best_done: None, - } - } - - pub(crate) fn validator_set(&self) -> &ValidatorSet { - &self.validator_set - } - - pub(crate) fn validator_set_id(&self) -> ValidatorSetId { - self.validator_set.id() - } - - pub(crate) fn validators(&self) -> &[Public] { - self.validator_set.validators() - } - - pub(crate) fn session_start(&self) -> NumberFor { - self.session_start - } - - pub(crate) fn mandatory_done(&self) -> bool { - self.mandatory_done - } - - pub(crate) fn should_self_vote(&self, round: &(P, NumberFor)) -> bool { - Some(round.1) > self.best_done && - self.rounds.get(round).map(|tracker| !tracker.has_self_vote()).unwrap_or(true) - } - - pub(crate) fn add_vote( - &mut self, - round: &(P, NumberFor), - vote: (Public, Signature), - self_vote: bool, - ) -> bool { - let num = round.1; - if num < self.session_start || Some(num) <= self.best_done { - debug!(target: LOG_TARGET, "🥩 received vote for old stale round {:?}, ignoring", num); - false - } else if !self.validators().iter().any(|id| vote.0 == *id) { - debug!( - target: LOG_TARGET, - "🥩 received vote {:?} from validator that is not in the validator set, ignoring", - vote - ); - false - } else { - self.rounds.entry(round.clone()).or_default().add_vote(vote, self_vote) - } - } - - pub(crate) fn should_conclude( - &mut self, - round: &(P, NumberFor), - ) -> Option>> { - let done = self - .rounds - .get(round) - .map(|tracker| tracker.is_done(threshold(self.validator_set.len()))) - .unwrap_or(false); - trace!(target: LOG_TARGET, "🥩 Round #{} done: {}", round.1, done); - - if done { - let signatures = self.rounds.remove(round)?.votes; - Some( - self.validators() - .iter() - .map(|authority_id| signatures.get(authority_id).cloned()) - .collect(), - ) - } else { - None - } - } - - pub(crate) fn conclude(&mut self, round_num: NumberFor) { - // Remove this and older (now stale) rounds. - self.rounds.retain(|&(_, number), _| number > round_num); - self.mandatory_done = self.mandatory_done || round_num == self.session_start; - self.best_done = self.best_done.max(Some(round_num)); - debug!(target: LOG_TARGET, "🥩 Concluded round #{}", round_num); - } -} - -#[cfg(test)] -mod tests { - use sc_network_test::Block; - use sp_core::H256; - - use beefy_primitives::{crypto::Public, ValidatorSet}; - - use super::{threshold, Block as BlockT, Hash, RoundTracker, Rounds}; - use crate::keystore::tests::Keyring; - - impl Rounds - where - P: Ord + Hash + Clone, - B: BlockT, - { - pub(crate) fn test_set_mandatory_done(&mut self, done: bool) { - self.mandatory_done = done; - } - } - - #[test] - fn round_tracker() { - let mut rt = RoundTracker::default(); - let bob_vote = (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")); - let threshold = 2; - - // self vote not added yet - assert!(!rt.has_self_vote()); - - // adding new vote allowed - assert!(rt.add_vote(bob_vote.clone(), false)); - // adding existing vote not allowed - assert!(!rt.add_vote(bob_vote, false)); - - // self vote still not added yet - assert!(!rt.has_self_vote()); - - // vote is not done - assert!(!rt.is_done(threshold)); - - let alice_vote = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")); - // adding new vote (self vote this time) allowed - assert!(rt.add_vote(alice_vote, true)); - - // self vote registered - assert!(rt.has_self_vote()); - // vote is now done - assert!(rt.is_done(threshold)); - } - - #[test] - fn vote_threshold() { - assert_eq!(threshold(1), 1); - assert_eq!(threshold(2), 2); - assert_eq!(threshold(3), 3); - assert_eq!(threshold(4), 3); - assert_eq!(threshold(100), 67); - assert_eq!(threshold(300), 201); - } - - #[test] - fn new_rounds() { - sp_tracing::try_init_simple(); - - let validators = ValidatorSet::::new( - vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], - 42, - ) - .unwrap(); - - let session_start = 1u64.into(); - let rounds = Rounds::::new(session_start, validators); - - assert_eq!(42, rounds.validator_set_id()); - assert_eq!(1, rounds.session_start()); - assert_eq!( - &vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], - rounds.validators() - ); - } - - #[test] - fn add_and_conclude_votes() { - sp_tracing::try_init_simple(); - - let validators = ValidatorSet::::new( - vec![ - Keyring::Alice.public(), - Keyring::Bob.public(), - Keyring::Charlie.public(), - Keyring::Eve.public(), - ], - Default::default(), - ) - .unwrap(); - let round = (H256::from_low_u64_le(1), 1); - - let session_start = 1u64.into(); - let mut rounds = Rounds::::new(session_start, validators); - - // no self vote yet, should self vote - assert!(rounds.should_self_vote(&round)); - - // add 1st good vote - assert!(rounds.add_vote( - &round, - (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")), - true - )); - // round not concluded - assert!(rounds.should_conclude(&round).is_none()); - // self vote already present, should not self vote - assert!(!rounds.should_self_vote(&round)); - - // double voting not allowed - assert!(!rounds.add_vote( - &round, - (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")), - true - )); - - // invalid vote (Dave is not a validator) - assert!(!rounds.add_vote( - &round, - (Keyring::Dave.public(), Keyring::Dave.sign(b"I am committed")), - false - )); - assert!(rounds.should_conclude(&round).is_none()); - - // add 2nd good vote - assert!(rounds.add_vote( - &round, - (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")), - false - )); - // round not concluded - assert!(rounds.should_conclude(&round).is_none()); - - // add 3rd good vote - assert!(rounds.add_vote( - &round, - (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am committed")), - false - )); - // round concluded - assert!(rounds.should_conclude(&round).is_some()); - rounds.conclude(round.1); - - // Eve is a validator, but round was concluded, adding vote disallowed - assert!(!rounds.add_vote( - &round, - (Keyring::Eve.public(), Keyring::Eve.sign(b"I am committed")), - false - )); - } - - #[test] - fn old_rounds_not_accepted() { - sp_tracing::try_init_simple(); - - let validators = ValidatorSet::::new( - vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], - 42, - ) - .unwrap(); - let alice = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")); - - let session_start = 10u64.into(); - let mut rounds = Rounds::::new(session_start, validators); - - let mut vote = (H256::from_low_u64_le(1), 9); - // add vote for previous session, should fail - assert!(!rounds.add_vote(&vote, alice.clone(), true)); - // no votes present - assert!(rounds.rounds.is_empty()); - - // simulate 11 was concluded - rounds.best_done = Some(11); - // add votes for current session, but already concluded rounds, should fail - vote.1 = 10; - assert!(!rounds.add_vote(&vote, alice.clone(), true)); - vote.1 = 11; - assert!(!rounds.add_vote(&vote, alice.clone(), true)); - // no votes present - assert!(rounds.rounds.is_empty()); - - // add good vote - vote.1 = 12; - assert!(rounds.add_vote(&vote, alice, true)); - // good vote present - assert_eq!(rounds.rounds.len(), 1); - } - - #[test] - fn multiple_rounds() { - sp_tracing::try_init_simple(); - - let validators = ValidatorSet::::new( - vec![ - Keyring::Alice.public(), - Keyring::Bob.public(), - Keyring::Charlie.public(), - Keyring::Dave.public(), - ], - Default::default(), - ) - .unwrap(); - - let session_start = 1u64.into(); - let mut rounds = Rounds::::new(session_start, validators); - - // round 1 - assert!(rounds.add_vote( - &(H256::from_low_u64_le(1), 1), - (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")), - true, - )); - assert!(rounds.add_vote( - &(H256::from_low_u64_le(1), 1), - (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")), - false, - )); - assert!(rounds.add_vote( - &(H256::from_low_u64_le(1), 1), - (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am committed")), - false, - )); - - // round 2 - assert!(rounds.add_vote( - &(H256::from_low_u64_le(2), 2), - (Keyring::Alice.public(), Keyring::Alice.sign(b"I am again committed")), - true, - )); - assert!(rounds.add_vote( - &(H256::from_low_u64_le(2), 2), - (Keyring::Bob.public(), Keyring::Bob.sign(b"I am again committed")), - false, - )); - assert!(rounds.add_vote( - &(H256::from_low_u64_le(2), 2), - (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am again committed")), - false, - )); - - // round 3 - assert!(rounds.add_vote( - &(H256::from_low_u64_le(3), 3), - (Keyring::Alice.public(), Keyring::Alice.sign(b"I am still committed")), - true, - )); - assert!(rounds.add_vote( - &(H256::from_low_u64_le(3), 3), - (Keyring::Bob.public(), Keyring::Bob.sign(b"I am still committed")), - false, - )); - assert!(rounds.add_vote( - &(H256::from_low_u64_le(3), 3), - (Keyring::Charlie.public(), Keyring::Charlie.sign(b"I am still committed")), - false, - )); - assert_eq!(3, rounds.rounds.len()); - - // conclude unknown round - assert!(rounds.should_conclude(&(H256::from_low_u64_le(5), 5)).is_none()); - assert_eq!(3, rounds.rounds.len()); - - // conclude round 2 - let signatures = rounds.should_conclude(&(H256::from_low_u64_le(2), 2)).unwrap(); - rounds.conclude(2); - assert_eq!(1, rounds.rounds.len()); - - assert_eq!( - signatures, - vec![ - Some(Keyring::Alice.sign(b"I am again committed")), - Some(Keyring::Bob.sign(b"I am again committed")), - Some(Keyring::Charlie.sign(b"I am again committed")), - None - ] - ); - } -} diff --git a/client/block-builder/Cargo.toml b/client/block-builder/Cargo.toml index 3b4bea9818c9..18141b044188 100644 --- a/client/block-builder/Cargo.toml +++ b/client/block-builder/Cargo.toml @@ -13,17 +13,17 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", features = [ "derive", ] } sc-client-api = { version = "4.0.0-dev", path = "../api" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-block-builder = { version = "4.0.0-dev", path = "../../primitives/block-builder" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } -sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } [dev-dependencies] +sp-state-machine = { version = "0.28.0", path = "../../primitives/state-machine" } substrate-test-runtime-client = { path = "../../test-utils/runtime/client" } diff --git a/client/block-builder/src/lib.rs b/client/block-builder/src/lib.rs index b6c2ac3ba5d6..7c57c77f3dad 100644 --- a/client/block-builder/src/lib.rs +++ b/client/block-builder/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -32,17 +32,15 @@ use sp_api::{ ApiExt, ApiRef, Core, ProvideRuntimeApi, StorageChanges, StorageProof, TransactionOutcome, }; use sp_blockchain::{ApplyExtrinsicFailed, Error}; -use sp_core::ExecutionContext; +use sp_core::traits::CallContext; use sp_runtime::{ - generic::BlockId, legacy, traits::{Block as BlockT, Hash, HashFor, Header as HeaderT, NumberFor, One}, Digest, }; -pub use sp_block_builder::BlockBuilder as BlockBuilderApi; - use sc_client_api::backend; +pub use sp_block_builder::BlockBuilder as BlockBuilderApi; /// Used as parameter to [`BlockBuilderProvider`] to express if proof recording should be enabled. /// @@ -120,7 +118,7 @@ where /// output of this block builder without having access to the full storage. fn new_block_at>( &self, - parent: &BlockId, + parent: Block::Hash, inherent_digests: Digest, record_proof: R, ) -> sp_blockchain::Result>; @@ -137,7 +135,6 @@ pub struct BlockBuilder<'a, Block: BlockT, A: ProvideRuntimeApi, B> { extrinsics: Vec, api: ApiRef<'a, A::Api>, version: u32, - block_id: BlockId, parent_hash: Block::Hash, backend: &'a B, /// The estimated size of the block header. @@ -181,12 +178,12 @@ where api.record_proof(); } - let block_id = BlockId::Hash(parent_hash); + api.set_call_context(CallContext::Onchain); - api.initialize_block_with_context(&block_id, ExecutionContext::BlockConstruction, &header)?; + api.initialize_block(parent_hash, &header)?; let version = api - .api_version::>(&block_id)? + .api_version::>(parent_hash)? .ok_or_else(|| Error::VersionInvalid("BlockBuilderApi".to_string()))?; Ok(Self { @@ -194,7 +191,6 @@ where extrinsics: Vec::new(), api, version, - block_id, backend, estimated_header_size, }) @@ -204,25 +200,17 @@ where /// /// This will ensure the extrinsic can be validly executed (by executing it). pub fn push(&mut self, xt: ::Extrinsic) -> Result<(), Error> { - let block_id = &self.block_id; + let parent_hash = self.parent_hash; let extrinsics = &mut self.extrinsics; let version = self.version; self.api.execute_in_transaction(|api| { let res = if version < 6 { #[allow(deprecated)] - api.apply_extrinsic_before_version_6_with_context( - block_id, - ExecutionContext::BlockConstruction, - xt.clone(), - ) - .map(legacy::byte_sized_error::convert_to_latest) + api.apply_extrinsic_before_version_6(parent_hash, xt.clone()) + .map(legacy::byte_sized_error::convert_to_latest) } else { - api.apply_extrinsic_with_context( - block_id, - ExecutionContext::BlockConstruction, - xt.clone(), - ) + api.apply_extrinsic(parent_hash, xt.clone()) }; match res { @@ -244,9 +232,7 @@ where /// supplied by `self.api`, combined as [`BuiltBlock`]. /// The storage proof will be `Some(_)` when proof recording was enabled. pub fn build(mut self) -> Result>, Error> { - let header = self - .api - .finalize_block_with_context(&self.block_id, ExecutionContext::BlockConstruction)?; + let header = self.api.finalize_block(self.parent_hash)?; debug_assert_eq!( header.extrinsics_root().clone(), @@ -279,16 +265,12 @@ where &mut self, inherent_data: sp_inherents::InherentData, ) -> Result, Error> { - let block_id = self.block_id; + let parent_hash = self.parent_hash; self.api .execute_in_transaction(move |api| { // `create_inherents` should not change any state, to ensure this we always rollback // the transaction. - TransactionOutcome::Rollback(api.inherent_extrinsics_with_context( - &block_id, - ExecutionContext::BlockConstruction, - inherent_data, - )) + TransactionOutcome::Rollback(api.inherent_extrinsics(parent_hash, inherent_data)) }) .map_err(|e| Error::Application(Box::new(e))) } @@ -314,7 +296,9 @@ mod tests { use sp_blockchain::HeaderBackend; use sp_core::Blake2Hasher; use sp_state_machine::Backend; - use substrate_test_runtime_client::{DefaultTestClientBuilderExt, TestClientBuilderExt}; + use substrate_test_runtime_client::{ + runtime::ExtrinsicBuilder, DefaultTestClientBuilderExt, TestClientBuilderExt, + }; #[test] fn block_building_storage_proof_does_not_include_runtime_by_default() { @@ -322,9 +306,11 @@ mod tests { let backend = builder.backend(); let client = builder.build(); + let genesis_hash = client.info().best_hash; + let block = BlockBuilder::new( &client, - client.info().best_hash, + genesis_hash, client.info().best_number, RecordProof::Yes, Default::default(), @@ -335,16 +321,73 @@ mod tests { .unwrap(); let proof = block.proof.expect("Proof is build on request"); + let genesis_state_root = client.header(genesis_hash).unwrap().unwrap().state_root; - let backend = sp_state_machine::create_proof_check_backend::( - block.storage_changes.transaction_storage_root, - proof, - ) - .unwrap(); + let backend = + sp_state_machine::create_proof_check_backend::(genesis_state_root, proof) + .unwrap(); assert!(backend .storage(&sp_core::storage::well_known_keys::CODE) .unwrap_err() .contains("Database missing expected key"),); } + + #[test] + fn failing_extrinsic_rolls_back_changes_in_storage_proof() { + let builder = substrate_test_runtime_client::TestClientBuilder::new(); + let backend = builder.backend(); + let client = builder.build(); + + let mut block_builder = BlockBuilder::new( + &client, + client.info().best_hash, + client.info().best_number, + RecordProof::Yes, + Default::default(), + &*backend, + ) + .unwrap(); + + block_builder.push(ExtrinsicBuilder::new_read_and_panic(8).build()).unwrap_err(); + + let block = block_builder.build().unwrap(); + + let proof_with_panic = block.proof.expect("Proof is build on request").encoded_size(); + + let mut block_builder = BlockBuilder::new( + &client, + client.info().best_hash, + client.info().best_number, + RecordProof::Yes, + Default::default(), + &*backend, + ) + .unwrap(); + + block_builder.push(ExtrinsicBuilder::new_read(8).build()).unwrap(); + + let block = block_builder.build().unwrap(); + + let proof_without_panic = block.proof.expect("Proof is build on request").encoded_size(); + + let block = BlockBuilder::new( + &client, + client.info().best_hash, + client.info().best_number, + RecordProof::Yes, + Default::default(), + &*backend, + ) + .unwrap() + .build() + .unwrap(); + + let proof_empty_block = block.proof.expect("Proof is build on request").encoded_size(); + + // Ensure that we rolled back the changes of the panicked transaction. + assert!(proof_without_panic > proof_with_panic); + assert!(proof_without_panic > proof_empty_block); + assert_eq!(proof_empty_block, proof_with_panic); + } } diff --git a/client/chain-spec/Cargo.toml b/client/chain-spec/Cargo.toml index 0a4e54e8d6a6..1b4ce6e44605 100644 --- a/client/chain-spec/Cargo.toml +++ b/client/chain-spec/Cargo.toml @@ -14,10 +14,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] memmap2 = "0.5.0" -serde = { version = "1.0.136", features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.85" +sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-chain-spec-derive = { version = "4.0.0-dev", path = "./derive" } -sc-network-common = { version = "0.10.0-dev", path = "../network/common" } +sc-executor = { version = "0.10.0-dev", path = "../executor" } +sc-network = { version = "0.10.0-dev", path = "../network" } sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } +sp-state-machine = { version = "0.28.0", path = "../../primitives/state-machine" } diff --git a/client/chain-spec/derive/Cargo.toml b/client/chain-spec/derive/Cargo.toml index d733bda9b169..537f8aee6ab6 100644 --- a/client/chain-spec/derive/Cargo.toml +++ b/client/chain-spec/derive/Cargo.toml @@ -16,6 +16,6 @@ proc-macro = true [dependencies] proc-macro-crate = "1.1.3" -proc-macro2 = "1.0.37" -quote = "1.0.10" -syn = "1.0.98" +proc-macro2 = "1.0.56" +quote = "1.0.28" +syn = "2.0.16" diff --git a/client/chain-spec/derive/src/impls.rs b/client/chain-spec/derive/src/impls.rs index 7af403d46ad1..c0624897c133 100644 --- a/client/chain-spec/derive/src/impls.rs +++ b/client/chain-spec/derive/src/impls.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -35,7 +35,7 @@ pub fn extension_derive(ast: &DeriveInput) -> proc_macro::TokenStream { .named .iter() .find_map(|f| { - if f.attrs.iter().any(|attr| attr.path.is_ident(ATTRIBUTE_NAME)) { + if f.attrs.iter().any(|attr| attr.path().is_ident(ATTRIBUTE_NAME)) { let typ = &f.ty; Some(quote! { #typ }) } else { diff --git a/client/chain-spec/derive/src/lib.rs b/client/chain-spec/derive/src/lib.rs index 75356a225046..7607c6fdcff4 100644 --- a/client/chain-spec/derive/src/lib.rs +++ b/client/chain-spec/derive/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/chain-spec/src/chain_spec.rs b/client/chain-spec/src/chain_spec.rs index 0b951200b069..96e36d8399ed 100644 --- a/client/chain-spec/src/chain_spec.rs +++ b/client/chain-spec/src/chain_spec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -20,7 +20,7 @@ #![warn(missing_docs)] use crate::{extension::GetExtension, ChainType, Properties, RuntimeGenesis}; -use sc_network_common::config::MultiaddrWithPeerId; +use sc_network::config::MultiaddrWithPeerId; use sc_telemetry::TelemetryEndpoints; use serde::{Deserialize, Serialize}; use serde_json as json; diff --git a/client/chain-spec/src/extension.rs b/client/chain-spec/src/extension.rs index c0b3e15a2df4..25ab011a05b3 100644 --- a/client/chain-spec/src/extension.rs +++ b/client/chain-spec/src/extension.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/service/src/client/genesis.rs b/client/chain-spec/src/genesis.rs similarity index 67% rename from client/service/src/client/genesis.rs rename to client/chain-spec/src/genesis.rs index 54ef1182b7e7..6aa156a620a7 100644 --- a/client/service/src/client/genesis.rs +++ b/client/chain-spec/src/genesis.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,20 +18,56 @@ //! Tool for creating the genesis block. +use std::{collections::hash_map::DefaultHasher, marker::PhantomData, sync::Arc}; + use sc_client_api::{backend::Backend, BlockImportOperation}; use sc_executor::RuntimeVersionOf; -use sp_core::storage::Storage; +use sp_core::storage::{well_known_keys, StateVersion, Storage}; use sp_runtime::{ traits::{Block as BlockT, Hash as HashT, Header as HeaderT, Zero}, BuildStorage, }; -use std::{marker::PhantomData, sync::Arc}; + +/// Return the state version given the genesis storage and executor. +pub fn resolve_state_version_from_wasm( + storage: &Storage, + executor: &E, +) -> sp_blockchain::Result +where + E: RuntimeVersionOf, +{ + if let Some(wasm) = storage.top.get(well_known_keys::CODE) { + let mut ext = sp_state_machine::BasicExternalities::new_empty(); // just to read runtime version. + + let code_fetcher = sp_core::traits::WrappedRuntimeCode(wasm.as_slice().into()); + let runtime_code = sp_core::traits::RuntimeCode { + code_fetcher: &code_fetcher, + heap_pages: None, + hash: { + use std::hash::{Hash, Hasher}; + let mut state = DefaultHasher::new(); + wasm.hash(&mut state); + state.finish().to_le_bytes().to_vec() + }, + }; + let runtime_version = RuntimeVersionOf::runtime_version(executor, &mut ext, &runtime_code) + .map_err(|e| sp_blockchain::Error::VersionInvalid(e.to_string()))?; + Ok(runtime_version.state_version()) + } else { + Err(sp_blockchain::Error::VersionInvalid( + "Runtime missing from initial storage, could not read state version.".to_string(), + )) + } +} /// Create a genesis block, given the initial storage. -pub fn construct_genesis_block(state_root: Block::Hash) -> Block { +pub fn construct_genesis_block( + state_root: Block::Hash, + state_version: StateVersion, +) -> Block { let extrinsics_root = <<::Header as HeaderT>::Hashing as HashT>::trie_root( Vec::new(), - sp_runtime::StateVersion::V0, + state_version, ); Block::new( @@ -93,12 +129,11 @@ impl, E: RuntimeVersionOf> BuildGenesisBlock sp_blockchain::Result<(Block, Self::BlockImportOperation)> { let Self { genesis_storage, commit_genesis_state, backend, executor, _phantom } = self; - let genesis_state_version = - crate::resolve_state_version_from_wasm(&genesis_storage, &executor)?; + let genesis_state_version = resolve_state_version_from_wasm(&genesis_storage, &executor)?; let mut op = backend.begin_operation()?; let state_root = op.set_genesis_state(genesis_storage, commit_genesis_state, genesis_state_version)?; - let genesis_block = construct_genesis_block::(state_root); + let genesis_block = construct_genesis_block::(state_root, genesis_state_version); Ok((genesis_block, op)) } diff --git a/client/chain-spec/src/lib.rs b/client/chain-spec/src/lib.rs index 9d2cc728b828..6239eb7326b7 100644 --- a/client/chain-spec/src/lib.rs +++ b/client/chain-spec/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -177,14 +177,19 @@ mod chain_spec; mod extension; +mod genesis; -pub use chain_spec::{ChainSpec as GenericChainSpec, NoExtension}; -pub use extension::{ - get_extension, get_extension_mut, Extension, Fork, Forks, GetExtension, Group, +pub use self::{ + chain_spec::{ChainSpec as GenericChainSpec, NoExtension}, + extension::{get_extension, get_extension_mut, Extension, Fork, Forks, GetExtension, Group}, + genesis::{ + construct_genesis_block, resolve_state_version_from_wasm, BuildGenesisBlock, + GenesisBlockBuilder, + }, }; pub use sc_chain_spec_derive::{ChainSpecExtension, ChainSpecGroup}; -use sc_network_common::config::MultiaddrWithPeerId; +use sc_network::config::MultiaddrWithPeerId; use sc_telemetry::TelemetryEndpoints; use serde::{de::DeserializeOwned, Serialize}; use sp_core::storage::Storage; diff --git a/client/cli/Cargo.toml b/client/cli/Cargo.toml index c0da53ad129d..61750766fa3f 100644 --- a/client/cli/Cargo.toml +++ b/client/cli/Cargo.toml @@ -13,19 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -array-bytes = "4.1" +array-bytes = "6.1" chrono = "0.4.10" -clap = { version = "4.0.9", features = ["derive", "string"] } +clap = { version = "4.2.5", features = ["derive", "string"] } fdlimit = "0.2.1" futures = "0.3.21" -libp2p = "0.50.0" +libp2p-identity = { version = "0.1.2", features = ["peerid", "ed25519"]} log = "0.4.17" names = { version = "0.13.0", default-features = false } -parity-scale-codec = "3.2.2" +parity-scale-codec = "3.6.1" rand = "0.8.5" regex = "1.6.0" rpassword = "7.0.0" -serde = "1.0.136" +serde = "1.0.163" serde_json = "1.0.85" thiserror = "1.0.30" tiny-bip39 = "1.0.0" @@ -34,23 +34,22 @@ sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../db" } sc-keystore = { version = "4.0.0-dev", path = "../keystore" } sc-network = { version = "0.10.0-dev", path = "../network" } -sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-service = { version = "0.10.0-dev", default-features = false, path = "../service" } sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } sc-tracing = { version = "4.0.0-dev", path = "../tracing" } sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-keyring = { version = "7.0.0", path = "../../primitives/keyring" } -sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } -sp-panic-handler = { version = "5.0.0", path = "../../primitives/panic-handler" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } -sp-version = { version = "5.0.0", path = "../../primitives/version" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-keyring = { version = "24.0.0", path = "../../primitives/keyring" } +sp-keystore = { version = "0.27.0", path = "../../primitives/keystore" } +sp-panic-handler = { version = "8.0.0", path = "../../primitives/panic-handler" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } +sp-version = { version = "22.0.0", path = "../../primitives/version" } [dev-dependencies] tempfile = "3.1.0" futures-timer = "3.0.1" -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } [features] default = ["rocksdb"] diff --git a/client/cli/src/arg_enums.rs b/client/cli/src/arg_enums.rs index 372e8645e30c..40d86fd97988 100644 --- a/client/cli/src/arg_enums.rs +++ b/client/cli/src/arg_enums.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -54,7 +54,7 @@ pub const DEFAULT_WASMTIME_INSTANTIATION_STRATEGY: WasmtimeInstantiationStrategy #[derive(Debug, Clone, Copy, ValueEnum)] #[value(rename_all = "kebab-case")] pub enum WasmExecutionMethod { - /// Uses an interpreter. + /// Uses an interpreter which now is deprecated. #[clap(name = "interpreted-i-know-what-i-do")] Interpreted, /// Uses a compiled runtime. @@ -74,23 +74,26 @@ impl std::fmt::Display for WasmExecutionMethod { /// into an execution method which can be used internally. pub fn execution_method_from_cli( execution_method: WasmExecutionMethod, - _instantiation_strategy: WasmtimeInstantiationStrategy, + instantiation_strategy: WasmtimeInstantiationStrategy, ) -> sc_service::config::WasmExecutionMethod { - match execution_method { - WasmExecutionMethod::Interpreted => sc_service::config::WasmExecutionMethod::Interpreted, - WasmExecutionMethod::Compiled => sc_service::config::WasmExecutionMethod::Compiled { - instantiation_strategy: match _instantiation_strategy { - WasmtimeInstantiationStrategy::PoolingCopyOnWrite => - sc_service::config::WasmtimeInstantiationStrategy::PoolingCopyOnWrite, - WasmtimeInstantiationStrategy::RecreateInstanceCopyOnWrite => - sc_service::config::WasmtimeInstantiationStrategy::RecreateInstanceCopyOnWrite, - WasmtimeInstantiationStrategy::Pooling => - sc_service::config::WasmtimeInstantiationStrategy::Pooling, - WasmtimeInstantiationStrategy::RecreateInstance => - sc_service::config::WasmtimeInstantiationStrategy::RecreateInstance, - WasmtimeInstantiationStrategy::LegacyInstanceReuse => - sc_service::config::WasmtimeInstantiationStrategy::LegacyInstanceReuse, - }, + if let WasmExecutionMethod::Interpreted = execution_method { + log::warn!( + "`interpreted-i-know-what-i-do` is deprecated and will be removed in the future. Defaults to `compiled` execution mode." + ); + } + + sc_service::config::WasmExecutionMethod::Compiled { + instantiation_strategy: match instantiation_strategy { + WasmtimeInstantiationStrategy::PoolingCopyOnWrite => + sc_service::config::WasmtimeInstantiationStrategy::PoolingCopyOnWrite, + WasmtimeInstantiationStrategy::RecreateInstanceCopyOnWrite => + sc_service::config::WasmtimeInstantiationStrategy::RecreateInstanceCopyOnWrite, + WasmtimeInstantiationStrategy::Pooling => + sc_service::config::WasmtimeInstantiationStrategy::Pooling, + WasmtimeInstantiationStrategy::RecreateInstance => + sc_service::config::WasmtimeInstantiationStrategy::RecreateInstance, + WasmtimeInstantiationStrategy::LegacyInstanceReuse => + sc_service::config::WasmtimeInstantiationStrategy::LegacyInstanceReuse, }, } } @@ -158,17 +161,6 @@ pub enum ExecutionStrategy { NativeElseWasm, } -impl Into for ExecutionStrategy { - fn into(self) -> sc_client_api::ExecutionStrategy { - match self { - ExecutionStrategy::Native => sc_client_api::ExecutionStrategy::NativeWhenPossible, - ExecutionStrategy::Wasm => sc_client_api::ExecutionStrategy::AlwaysWasm, - ExecutionStrategy::Both => sc_client_api::ExecutionStrategy::Both, - ExecutionStrategy::NativeElseWasm => sc_client_api::ExecutionStrategy::NativeElseWasm, - } - } -} - /// Available RPC methods. #[allow(missing_docs)] #[derive(Debug, Copy, Clone, PartialEq, ValueEnum)] @@ -255,24 +247,15 @@ impl Into for SyncMode { fn into(self) -> sc_network::config::SyncMode { match self { SyncMode::Full => sc_network::config::SyncMode::Full, - SyncMode::Fast => - sc_network::config::SyncMode::Fast { skip_proofs: false, storage_chain_mode: false }, - SyncMode::FastUnsafe => - sc_network::config::SyncMode::Fast { skip_proofs: true, storage_chain_mode: false }, + SyncMode::Fast => sc_network::config::SyncMode::LightState { + skip_proofs: false, + storage_chain_mode: false, + }, + SyncMode::FastUnsafe => sc_network::config::SyncMode::LightState { + skip_proofs: true, + storage_chain_mode: false, + }, SyncMode::Warp => sc_network::config::SyncMode::Warp, } } } - -/// Default value for the `--execution-syncing` parameter. -pub const DEFAULT_EXECUTION_SYNCING: ExecutionStrategy = ExecutionStrategy::Wasm; -/// Default value for the `--execution-import-block` parameter. -pub const DEFAULT_EXECUTION_IMPORT_BLOCK: ExecutionStrategy = ExecutionStrategy::Wasm; -/// Default value for the `--execution-import-block` parameter when the node is a validator. -pub const DEFAULT_EXECUTION_IMPORT_BLOCK_VALIDATOR: ExecutionStrategy = ExecutionStrategy::Wasm; -/// Default value for the `--execution-block-construction` parameter. -pub const DEFAULT_EXECUTION_BLOCK_CONSTRUCTION: ExecutionStrategy = ExecutionStrategy::Wasm; -/// Default value for the `--execution-offchain-worker` parameter. -pub const DEFAULT_EXECUTION_OFFCHAIN_WORKER: ExecutionStrategy = ExecutionStrategy::Wasm; -/// Default value for the `--execution-other` parameter. -pub const DEFAULT_EXECUTION_OTHER: ExecutionStrategy = ExecutionStrategy::Wasm; diff --git a/client/cli/src/commands/build_spec_cmd.rs b/client/cli/src/commands/build_spec_cmd.rs index 5ab3ce9e88a0..aa5314f9cf5a 100644 --- a/client/cli/src/commands/build_spec_cmd.rs +++ b/client/cli/src/commands/build_spec_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -38,7 +38,6 @@ pub struct BuildSpecCmd { pub raw: bool, /// Disable adding the default bootnode to the specification. - /// /// By default the `/ip4/127.0.0.1/tcp/30333/p2p/NODE_PEER_ID` bootnode is added to the /// specification when no bootnode exists. #[arg(long)] diff --git a/client/cli/src/commands/chain_info_cmd.rs b/client/cli/src/commands/chain_info_cmd.rs index cbc22cc4d52d..002d7893d9f3 100644 --- a/client/cli/src/commands/chain_info_cmd.rs +++ b/client/cli/src/commands/chain_info_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/cli/src/commands/check_block_cmd.rs b/client/cli/src/commands/check_block_cmd.rs index 3e25eab2c435..dc63d46bd19a 100644 --- a/client/cli/src/commands/check_block_cmd.rs +++ b/client/cli/src/commands/check_block_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -29,12 +29,11 @@ use std::{fmt::Debug, str::FromStr, sync::Arc}; /// The `check-block` command used to validate blocks. #[derive(Debug, Clone, Parser)] pub struct CheckBlockCmd { - /// Block hash or number + /// Block hash or number. #[arg(value_name = "HASH or NUMBER")] pub input: BlockNumberOrHash, /// The default number of 64KB pages to ever allocate for Wasm execution. - /// /// Don't alter this unless you know what you're doing. #[arg(long, value_name = "COUNT")] pub default_heap_pages: Option, @@ -55,7 +54,6 @@ impl CheckBlockCmd { B: BlockT + for<'de> serde::Deserialize<'de>, C: BlockBackend + HeaderBackend + Send + Sync + 'static, IQ: sc_service::ImportQueue + 'static, - B::Hash: FromStr, ::Err: Debug, <::Number as FromStr>::Err: Debug, { diff --git a/client/cli/src/commands/export_blocks_cmd.rs b/client/cli/src/commands/export_blocks_cmd.rs index 76fedff4d3ad..120d7889878e 100644 --- a/client/cli/src/commands/export_blocks_cmd.rs +++ b/client/cli/src/commands/export_blocks_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -36,13 +36,11 @@ pub struct ExportBlocksCmd { pub output: Option, /// Specify starting block number. - /// /// Default is 1. #[arg(long, value_name = "BLOCK")] pub from: Option, /// Specify last block number. - /// /// Default is best block. #[arg(long, value_name = "BLOCK")] pub to: Option, diff --git a/client/cli/src/commands/export_state_cmd.rs b/client/cli/src/commands/export_state_cmd.rs index 04bce0c1d707..4f9e625d702e 100644 --- a/client/cli/src/commands/export_state_cmd.rs +++ b/client/cli/src/commands/export_state_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -59,7 +59,6 @@ impl ExportStateCmd { B: BlockT, C: UsageProvider + StorageProvider + HeaderBackend, BA: sc_client_api::backend::Backend, - B::Hash: FromStr, ::Err: Debug, <::Number as FromStr>::Err: Debug, { diff --git a/client/cli/src/commands/generate.rs b/client/cli/src/commands/generate.rs index 461cb98bc2e5..93b83fcbef51 100644 --- a/client/cli/src/commands/generate.rs +++ b/client/cli/src/commands/generate.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/client/cli/src/commands/generate_node_key.rs b/client/cli/src/commands/generate_node_key.rs index e84b4a71d6d7..43851dc1af5c 100644 --- a/client/cli/src/commands/generate_node_key.rs +++ b/client/cli/src/commands/generate_node_key.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ use crate::Error; use clap::Parser; -use libp2p::identity::{ed25519 as libp2p_ed25519, PublicKey}; +use libp2p_identity::{ed25519, Keypair}; use std::{ fs, io::{self, Write}, @@ -35,13 +35,11 @@ use std::{ )] pub struct GenerateNodeKeyCmd { /// Name of file to save secret key to. - /// /// If not given, the secret key is printed to stdout. #[arg(long)] file: Option, /// The output is in raw binary format. - /// /// If not given, the output is written as an hex encoded string. #[arg(long)] bin: bool, @@ -50,14 +48,14 @@ pub struct GenerateNodeKeyCmd { impl GenerateNodeKeyCmd { /// Run the command pub fn run(&self) -> Result<(), Error> { - let keypair = libp2p_ed25519::Keypair::generate(); + let keypair = ed25519::Keypair::generate(); let secret = keypair.secret(); let file_data = if self.bin { secret.as_ref().to_owned() } else { - array_bytes::bytes2hex("", secret.as_ref()).into_bytes() + array_bytes::bytes2hex("", secret).into_bytes() }; match &self.file { @@ -65,7 +63,7 @@ impl GenerateNodeKeyCmd { None => io::stdout().lock().write_all(&file_data)?, } - eprintln!("{}", PublicKey::Ed25519(keypair.public()).to_peer_id()); + eprintln!("{}", Keypair::from(keypair).public().to_peer_id()); Ok(()) } diff --git a/client/cli/src/commands/import_blocks_cmd.rs b/client/cli/src/commands/import_blocks_cmd.rs index debc697242dd..815c6ab18aa6 100644 --- a/client/cli/src/commands/import_blocks_cmd.rs +++ b/client/cli/src/commands/import_blocks_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -41,7 +41,6 @@ pub struct ImportBlocksCmd { pub input: Option, /// The default number of 64KB pages to ever allocate for Wasm execution. - /// /// Don't alter this unless you know what you're doing. #[arg(long, value_name = "COUNT")] pub default_heap_pages: Option, diff --git a/client/cli/src/commands/insert_key.rs b/client/cli/src/commands/insert_key.rs index 7d66a680df8c..732d874319a8 100644 --- a/client/cli/src/commands/insert_key.rs +++ b/client/cli/src/commands/insert_key.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,8 +24,7 @@ use clap::Parser; use sc_keystore::LocalKeystore; use sc_service::config::{BasePath, KeystoreConfig}; use sp_core::crypto::{KeyTypeId, SecretString}; -use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; -use std::sync::Arc; +use sp_keystore::KeystorePtr; /// The `insert` command #[derive(Debug, Clone, Parser)] @@ -37,7 +36,7 @@ pub struct InsertKeyCmd { #[arg(long)] suri: Option, - /// Key type, examples: "gran", or "imon" + /// Key type, examples: "gran", or "imon". #[arg(long)] key_type: String, @@ -67,9 +66,9 @@ impl InsertKeyCmd { let config_dir = base_path.config_dir(chain_spec.id()); let (keystore, public) = match self.keystore_params.keystore_config(&config_dir)? { - (_, KeystoreConfig::Path { path, password }) => { + KeystoreConfig::Path { path, password } => { let public = with_crypto_scheme!(self.scheme, to_vec(&suri, password.clone()))?; - let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::open(path, password)?); + let keystore: KeystorePtr = LocalKeystore::open(path, password)?.into(); (keystore, public) }, _ => unreachable!("keystore_config always returns path and password; qed"), @@ -78,8 +77,9 @@ impl InsertKeyCmd { let key_type = KeyTypeId::try_from(self.key_type.as_str()).map_err(|_| Error::KeyTypeInvalid)?; - SyncCryptoStore::insert_unknown(&*keystore, key_type, &suri, &public[..]) - .map_err(|_| Error::KeyStoreOperation)?; + keystore + .insert(key_type, &suri, &public[..]) + .map_err(|_| Error::KeystoreOperation)?; Ok(()) } @@ -95,6 +95,7 @@ mod tests { use super::*; use sc_service::{ChainSpec, ChainType, GenericChainSpec, NoExtension}; use sp_core::{sr25519::Pair, ByteArray, Pair as _}; + use sp_keystore::Keystore; use tempfile::TempDir; struct Cli; @@ -124,10 +125,6 @@ mod tests { "test".into() } - fn native_runtime_version(_: &Box) -> &'static sp_version::RuntimeVersion { - unimplemented!("Not required in tests") - } - fn load_spec(&self, _: &str) -> std::result::Result, String> { Ok(Box::new(GenericChainSpec::from_genesis( "test", diff --git a/client/cli/src/commands/inspect_key.rs b/client/cli/src/commands/inspect_key.rs index 369fd10926dc..5aa8b0bdcaa6 100644 --- a/client/cli/src/commands/inspect_key.rs +++ b/client/cli/src/commands/inspect_key.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,12 +34,9 @@ use std::str::FromStr; pub struct InspectKeyCmd { /// A Key URI to be inspected. May be a secret seed, secret URI /// (with derivation paths and password), SS58, public URI or a hex encoded public key. - /// /// If it is a hex encoded public key, `--public` needs to be given as argument. - /// /// If the given value is a file, the file content will be used /// as URI. - /// /// If omitted, you will be prompted for the URI. uri: Option, @@ -64,12 +61,10 @@ pub struct InspectKeyCmd { pub crypto_scheme: CryptoSchemeFlag, /// Expect that `--uri` has the given public key/account-id. - /// /// If `--uri` has any derivations, the public key is checked against the base `uri`, i.e. the /// `uri` without any derivation applied. However, if `uri` has a password or there is one /// given by `--password`, it will be used to decrypt `uri` before comparing the public /// key/account-id. - /// /// If there is no derivation in `--uri`, the public key will be checked against the public key /// of `--uri` directly. #[arg(long, conflicts_with = "public")] diff --git a/client/cli/src/commands/inspect_node_key.rs b/client/cli/src/commands/inspect_node_key.rs index 9300007cb6bf..19b5a31ca12c 100644 --- a/client/cli/src/commands/inspect_node_key.rs +++ b/client/cli/src/commands/inspect_node_key.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ use crate::Error; use clap::Parser; -use libp2p::identity::{ed25519, PublicKey}; +use libp2p_identity::Keypair; use std::{ fs, io::{self, Read}, @@ -34,13 +34,11 @@ use std::{ )] pub struct InspectNodeKeyCmd { /// Name of file to read the secret key from. - /// /// If not given, the secret key is read from stdin (up to EOF). #[arg(long)] file: Option, /// The input is in raw binary format. - /// /// If not given, the input is read as an hex encoded string. #[arg(long)] bin: bool, @@ -70,12 +68,10 @@ impl InspectNodeKeyCmd { .map_err(|_| "failed to decode secret as hex")?; } - let secret = - ed25519::SecretKey::from_bytes(&mut file_data).map_err(|_| "Bad node key file")?; + let keypair = + Keypair::ed25519_from_bytes(&mut file_data).map_err(|_| "Bad node key file")?; - let keypair = ed25519::Keypair::from(secret); - - println!("{}", PublicKey::Ed25519(keypair.public()).to_peer_id()); + println!("{}", keypair.public().to_peer_id()); Ok(()) } diff --git a/client/cli/src/commands/key.rs b/client/cli/src/commands/key.rs index 59cca554bfa3..d49b7e4072c8 100644 --- a/client/cli/src/commands/key.rs +++ b/client/cli/src/commands/key.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/client/cli/src/commands/mod.rs b/client/cli/src/commands/mod.rs index 8e84afa34e24..d004fc1beb09 100644 --- a/client/cli/src/commands/mod.rs +++ b/client/cli/src/commands/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -31,6 +31,7 @@ mod purge_chain_cmd; mod revert_cmd; mod run_cmd; mod sign; +mod test; pub mod utils; mod vanity; mod verify; diff --git a/client/cli/src/commands/purge_chain_cmd.rs b/client/cli/src/commands/purge_chain_cmd.rs index 9a3aeee50e94..2ff3d4b9a04c 100644 --- a/client/cli/src/commands/purge_chain_cmd.rs +++ b/client/cli/src/commands/purge_chain_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/cli/src/commands/revert_cmd.rs b/client/cli/src/commands/revert_cmd.rs index 8477630cf940..34e8c1036c59 100644 --- a/client/cli/src/commands/revert_cmd.rs +++ b/client/cli/src/commands/revert_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,7 +18,7 @@ use crate::{ error, - params::{GenericNumber, PruningParams, SharedParams}, + params::{DatabaseParams, GenericNumber, PruningParams, SharedParams}, CliConfiguration, }; use clap::Parser; @@ -41,6 +41,10 @@ pub struct RevertCmd { #[allow(missing_docs)] #[clap(flatten)] pub pruning_params: PruningParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub database_params: DatabaseParams, } /// Revert handler for auxiliary data (e.g. consensus). @@ -79,4 +83,8 @@ impl CliConfiguration for RevertCmd { fn pruning_params(&self) -> Option<&PruningParams> { Some(&self.pruning_params) } + + fn database_params(&self) -> Option<&DatabaseParams> { + Some(&self.database_params) + } } diff --git a/client/cli/src/commands/run_cmd.rs b/client/cli/src/commands/run_cmd.rs index 35181d83f805..5dda488b1333 100644 --- a/client/cli/src/commands/run_cmd.rs +++ b/client/cli/src/commands/run_cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -23,7 +23,9 @@ use crate::{ ImportParams, KeystoreParams, NetworkParams, OffchainWorkerParams, SharedParams, TransactionPoolParams, }, - CliConfiguration, + CliConfiguration, PrometheusParams, RuntimeParams, TelemetryParams, + RPC_DEFAULT_MAX_CONNECTIONS, RPC_DEFAULT_MAX_REQUEST_SIZE_MB, RPC_DEFAULT_MAX_RESPONSE_SIZE_MB, + RPC_DEFAULT_MAX_SUBS_PER_CONN, }; use clap::Parser; use regex::Regex; @@ -38,7 +40,6 @@ use std::net::{IpAddr, Ipv4Addr, SocketAddr}; #[derive(Debug, Clone, Parser)] pub struct RunCmd { /// Enable validator mode. - /// /// The node will be started with the authority role and actively /// participate in any consensus task that it can (e.g. depending on /// availability of local keys). @@ -51,7 +52,6 @@ pub struct RunCmd { pub no_grandpa: bool, /// Listen to all RPC interfaces. - /// /// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use an RPC /// proxy server to filter out dangerous methods. More details: /// . @@ -60,16 +60,14 @@ pub struct RunCmd { pub rpc_external: bool, /// Listen to all RPC interfaces. - /// /// Same as `--rpc-external`. #[arg(long)] pub unsafe_rpc_external: bool, /// RPC methods to expose. - /// /// - `unsafe`: Exposes every RPC method. /// - `safe`: Exposes only a safe subset of RPC methods, denying unsafe RPC methods. - /// - `auto`: Acts as `safe` if RPC is served externally, e.g. when `--{rpc,ws}-external` is + /// - `auto`: Acts as `safe` if RPC is served externally, e.g. when `--rpc--external` is /// passed, otherwise acts as `unsafe`. #[arg( long, @@ -81,69 +79,27 @@ pub struct RunCmd { )] pub rpc_methods: RpcMethods, - /// Listen to all Websocket interfaces. - /// - /// Default is local. Note: not all RPC methods are safe to be exposed publicly. Use an RPC - /// proxy server to filter out dangerous methods. More details: - /// . - /// Use `--unsafe-ws-external` to suppress the warning if you understand the risks. - #[arg(long)] - pub ws_external: bool, - - /// Listen to all Websocket interfaces. - /// - /// Same as `--ws-external` but doesn't warn you about it. - #[arg(long)] - pub unsafe_ws_external: bool, - - /// DEPRECATED, this has no affect anymore. Use `rpc_max_request_size` or - /// `rpc_max_response_size` instead. - #[arg(long)] - pub rpc_max_payload: Option, - /// Set the the maximum RPC request payload size for both HTTP and WS in megabytes. - /// Default is 15MiB. - #[arg(long)] - pub rpc_max_request_size: Option, + #[arg(long, default_value_t = RPC_DEFAULT_MAX_REQUEST_SIZE_MB)] + pub rpc_max_request_size: u32, /// Set the the maximum RPC response payload size for both HTTP and WS in megabytes. - /// Default is 15MiB. - #[arg(long)] - pub rpc_max_response_size: Option, + #[arg(long, default_value_t = RPC_DEFAULT_MAX_RESPONSE_SIZE_MB)] + pub rpc_max_response_size: u32, /// Set the the maximum concurrent subscriptions per connection. - /// Default is 1024. - #[arg(long)] - pub rpc_max_subscriptions_per_connection: Option, - - /// Expose Prometheus exporter on all interfaces. - /// - /// Default is local. - #[arg(long)] - pub prometheus_external: bool, - - /// DEPRECATED, IPC support has been removed. - #[arg(long, value_name = "PATH")] - pub ipc_path: Option, + #[arg(long, default_value_t = RPC_DEFAULT_MAX_SUBS_PER_CONN)] + pub rpc_max_subscriptions_per_connection: u32, - /// Specify HTTP RPC server TCP port. + /// Specify JSON-RPC server TCP port. #[arg(long, value_name = "PORT")] pub rpc_port: Option, - /// Specify WebSockets RPC server TCP port. - #[arg(long, value_name = "PORT")] - pub ws_port: Option, - - /// Maximum number of WS RPC server connections. - #[arg(long, value_name = "COUNT")] - pub ws_max_connections: Option, - - /// DEPRECATED, this has no affect anymore. Use `rpc_max_response_size` instead. - #[arg(long)] - pub ws_max_out_buffer_capacity: Option, + /// Maximum number of RPC server connections. + #[arg(long, value_name = "COUNT", default_value_t = RPC_DEFAULT_MAX_CONNECTIONS)] + pub rpc_max_connections: u32, /// Specify browser Origins allowed to access the HTTP & WS RPC servers. - /// /// A comma-separated list of origins (protocol://domain or special `null` /// value). Value of `all` will disable origin validation. Default is to /// allow localhost and origins. When running in @@ -151,36 +107,22 @@ pub struct RunCmd { #[arg(long, value_name = "ORIGINS", value_parser = parse_cors)] pub rpc_cors: Option, - /// Specify Prometheus exporter TCP Port. - #[arg(long, value_name = "PORT")] - pub prometheus_port: Option, - - /// Do not expose a Prometheus exporter endpoint. - /// - /// Prometheus metric endpoint is enabled by default. - #[arg(long)] - pub no_prometheus: bool, - /// The human-readable name for this node. - /// - /// The node name will be reported to the telemetry server, if enabled. + /// It's used as network node name. #[arg(long, value_name = "NAME")] pub name: Option, - /// Disable connecting to the Substrate telemetry server. - /// - /// Telemetry is on by default on global chains. - #[arg(long)] - pub no_telemetry: bool, + #[allow(missing_docs)] + #[clap(flatten)] + pub telemetry_params: TelemetryParams, + + #[allow(missing_docs)] + #[clap(flatten)] + pub prometheus_params: PrometheusParams, - /// The URL of the telemetry server to connect to. - /// - /// This flag can be passed multiple times as a means to specify multiple - /// telemetry endpoints. Verbosity levels range from 0-9, with 0 denoting - /// the least verbosity. - /// Expected format is 'URL VERBOSITY', e.g. `--telemetry-url 'wss://foo/bar 0'`. - #[arg(long = "telemetry-url", value_name = "URL VERBOSITY", value_parser = parse_telemetry_endpoints)] - pub telemetry_endpoints: Vec<(String, u8)>, + #[allow(missing_docs)] + #[clap(flatten)] + pub runtime_params: RuntimeParams, #[allow(missing_docs)] #[clap(flatten)] @@ -202,6 +144,10 @@ pub struct RunCmd { #[clap(flatten)] pub pool_config: TransactionPoolParams, + #[allow(missing_docs)] + #[clap(flatten)] + pub keystore_params: KeystoreParams, + /// Shortcut for `--name Alice --validator` with session keys for `Alice` added to keystore. #[arg(long, conflicts_with_all = &["bob", "charlie", "dave", "eve", "ferdie", "one", "two"])] pub alice: bool, @@ -239,28 +185,11 @@ pub struct RunCmd { #[arg(long)] pub force_authoring: bool, - #[allow(missing_docs)] - #[clap(flatten)] - pub keystore_params: KeystoreParams, - - /// The size of the instances cache for each runtime. - /// - /// The default value is 8 and the values higher than 256 are ignored. - #[arg(long)] - pub max_runtime_instances: Option, - - /// Maximum number of different runtimes that can be cached. - #[arg(long, default_value_t = 2)] - pub runtime_cache_size: u8, - /// Run a temporary node. - /// /// A temporary directory will be created to store the configuration and will be deleted /// at the end of the process. - /// /// Note: the directory is random per process execution. This directory is used as base path /// which includes: database, node key and keystore. - /// /// When `--dev` is given and no explicit `--base-path`, this option is implied. #[arg(long, conflicts_with = "base_path")] pub tmp: bool, @@ -345,11 +274,12 @@ impl CliConfiguration for RunCmd { &self, chain_spec: &Box, ) -> Result> { - Ok(if self.no_telemetry { + let params = &self.telemetry_params; + Ok(if params.no_telemetry { None - } else if !self.telemetry_endpoints.is_empty() { + } else if !params.telemetry_endpoints.is_empty() { Some( - TelemetryEndpoints::new(self.telemetry_endpoints.clone()) + TelemetryEndpoints::new(params.telemetry_endpoints.clone()) .map_err(|e| e.to_string())?, ) } else { @@ -361,7 +291,7 @@ impl CliConfiguration for RunCmd { let keyring = self.get_keyring(); let is_authority = self.validator || is_dev || keyring.is_some(); - Ok(if is_authority { sc_service::Role::Authority } else { sc_service::Role::Full }) + Ok(if is_authority { Role::Authority } else { Role::Full }) } fn force_authoring(&self) -> Result { @@ -374,28 +304,17 @@ impl CliConfiguration for RunCmd { default_listen_port: u16, chain_spec: &Box, ) -> Result> { - Ok(if self.no_prometheus { - None - } else { - let interface = - if self.prometheus_external { Ipv4Addr::UNSPECIFIED } else { Ipv4Addr::LOCALHOST }; - - Some(PrometheusConfig::new_with_default_registry( - SocketAddr::new( - interface.into(), - self.prometheus_port.unwrap_or(default_listen_port), - ), - chain_spec.id().into(), - )) - }) + Ok(self + .prometheus_params + .prometheus_config(default_listen_port, chain_spec.id().to_string())) } fn disable_grandpa(&self) -> Result { Ok(self.no_grandpa) } - fn rpc_ws_max_connections(&self) -> Result> { - Ok(self.ws_max_connections) + fn rpc_max_connections(&self) -> Result { + Ok(self.rpc_max_connections) } fn rpc_cors(&self, is_dev: bool) -> Result>> { @@ -419,7 +338,7 @@ impl CliConfiguration for RunCmd { .into()) } - fn rpc_http(&self, default_listen_port: u16) -> Result> { + fn rpc_addr(&self, default_listen_port: u16) -> Result> { let interface = rpc_interface( self.rpc_external, self.unsafe_rpc_external, @@ -430,55 +349,32 @@ impl CliConfiguration for RunCmd { Ok(Some(SocketAddr::new(interface, self.rpc_port.unwrap_or(default_listen_port)))) } - fn rpc_ipc(&self) -> Result> { - Ok(self.ipc_path.clone()) - } - - fn rpc_ws(&self, default_listen_port: u16) -> Result> { - let interface = rpc_interface( - self.ws_external, - self.unsafe_ws_external, - self.rpc_methods, - self.validator, - )?; - - Ok(Some(SocketAddr::new(interface, self.ws_port.unwrap_or(default_listen_port)))) - } - fn rpc_methods(&self) -> Result { Ok(self.rpc_methods.into()) } - fn rpc_max_payload(&self) -> Result> { - Ok(self.rpc_max_payload) - } - - fn rpc_max_request_size(&self) -> Result> { + fn rpc_max_request_size(&self) -> Result { Ok(self.rpc_max_request_size) } - fn rpc_max_response_size(&self) -> Result> { + fn rpc_max_response_size(&self) -> Result { Ok(self.rpc_max_response_size) } - fn rpc_max_subscriptions_per_connection(&self) -> Result> { + fn rpc_max_subscriptions_per_connection(&self) -> Result { Ok(self.rpc_max_subscriptions_per_connection) } - fn ws_max_out_buffer_capacity(&self) -> Result> { - Ok(self.ws_max_out_buffer_capacity) - } - fn transaction_pool(&self, is_dev: bool) -> Result { Ok(self.pool_config.transaction_pool(is_dev)) } fn max_runtime_instances(&self) -> Result> { - Ok(self.max_runtime_instances.map(|x| x.min(256))) + Ok(Some(self.runtime_params.max_runtime_instances)) } fn runtime_cache_size(&self) -> Result { - Ok(self.runtime_cache_size) + Ok(self.runtime_params.runtime_cache_size) } fn base_path(&self) -> Result> { @@ -498,6 +394,11 @@ impl CliConfiguration for RunCmd { /// Check whether a node name is considered as valid. pub fn is_node_name_valid(_name: &str) -> std::result::Result<(), &str> { let name = _name.to_string(); + + if name.is_empty() { + return Err("Node name cannot be empty") + } + if name.chars().count() >= crate::NODE_NAME_MAX_LENGTH { return Err("Node name too long") } @@ -508,7 +409,7 @@ pub fn is_node_name_valid(_name: &str) -> std::result::Result<(), &str> { return Err("Node name should not contain invalid chars such as '.' and '@'") } - let invalid_patterns = r"(https?:\\/+)?(www)+"; + let invalid_patterns = r"^https?:"; let re = Regex::new(invalid_patterns).unwrap(); if re.is_match(&name) { return Err("Node name should not contain urls") @@ -525,7 +426,7 @@ fn rpc_interface( ) -> Result { if is_external && is_validator && rpc_methods != RpcMethods::Unsafe { return Err(Error::Input( - "--rpc-external and --ws-external options shouldn't be used if the node is running as \ + "--rpc-external option shouldn't be used if the node is running as \ a validator. Use `--unsafe-rpc-external` or `--rpc-methods=unsafe` if you understand \ the risks. See the options description for more information." .to_owned(), @@ -546,36 +447,6 @@ fn rpc_interface( } } -#[derive(Debug)] -enum TelemetryParsingError { - MissingVerbosity, - VerbosityParsingError(std::num::ParseIntError), -} - -impl std::error::Error for TelemetryParsingError {} - -impl std::fmt::Display for TelemetryParsingError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - TelemetryParsingError::MissingVerbosity => write!(f, "Verbosity level missing"), - TelemetryParsingError::VerbosityParsingError(e) => write!(f, "{}", e), - } - } -} - -fn parse_telemetry_endpoints(s: &str) -> std::result::Result<(String, u8), TelemetryParsingError> { - let pos = s.find(' '); - match pos { - None => Err(TelemetryParsingError::MissingVerbosity), - Some(pos_) => { - let url = s[..pos_].to_string(); - let verbosity = - s[pos_ + 1..].parse().map_err(TelemetryParsingError::VerbosityParsingError)?; - Ok((url, verbosity)) - }, - } -} - /// CORS setting /// /// The type is introduced to overcome `Option>` handling of `clap`. @@ -624,18 +495,32 @@ mod tests { #[test] fn tests_node_name_good() { assert!(is_node_name_valid("short name").is_ok()); + assert!(is_node_name_valid("www").is_ok()); + assert!(is_node_name_valid("aawww").is_ok()); + assert!(is_node_name_valid("wwwaa").is_ok()); + assert!(is_node_name_valid("www aa").is_ok()); } #[test] fn tests_node_name_bad() { + assert!(is_node_name_valid("").is_err()); assert!(is_node_name_valid( "very very long names are really not very cool for the ui at all, really they're not" ) .is_err()); assert!(is_node_name_valid("Dots.not.Ok").is_err()); - assert!(is_node_name_valid("http://visit.me").is_err()); - assert!(is_node_name_valid("https://visit.me").is_err()); + // NOTE: the urls below don't include a domain otherwise + // they'd get filtered for including a `.` + assert!(is_node_name_valid("http://visitme").is_err()); + assert!(is_node_name_valid("http:/visitme").is_err()); + assert!(is_node_name_valid("http:visitme").is_err()); + assert!(is_node_name_valid("https://visitme").is_err()); + assert!(is_node_name_valid("https:/visitme").is_err()); + assert!(is_node_name_valid("https:visitme").is_err()); assert!(is_node_name_valid("www.visit.me").is_err()); + assert!(is_node_name_valid("www.visit").is_err()); + assert!(is_node_name_valid("hello\\world").is_err()); + assert!(is_node_name_valid("visit.www").is_err()); assert!(is_node_name_valid("email@domain").is_err()); } } diff --git a/client/cli/src/commands/sign.rs b/client/cli/src/commands/sign.rs index 2c3ff3a1575f..91b0651f0521 100644 --- a/client/cli/src/commands/sign.rs +++ b/client/cli/src/commands/sign.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -17,9 +17,13 @@ // along with this program. If not, see . //! Implementation of the `sign` subcommand -use crate::{error, utils, with_crypto_scheme, CryptoSchemeFlag, KeystoreParams}; +use crate::{ + error, params::MessageParams, utils, with_crypto_scheme, CryptoSchemeFlag, KeystoreParams, +}; +use array_bytes::bytes2hex; use clap::Parser; use sp_core::crypto::SecretString; +use std::io::{BufRead, Write}; /// The `sign` command #[derive(Debug, Clone, Parser)] @@ -31,14 +35,9 @@ pub struct SignCmd { #[arg(long)] suri: Option, - /// Message to sign, if not provided you will be prompted to - /// pass the message via STDIN - #[arg(long)] - message: Option, - - /// The message on STDIN is hex-encoded data - #[arg(long)] - hex: bool, + #[allow(missing_docs)] + #[clap(flatten)] + pub message_params: MessageParams, #[allow(missing_docs)] #[clap(flatten)] @@ -52,15 +51,26 @@ pub struct SignCmd { impl SignCmd { /// Run the command pub fn run(&self) -> error::Result<()> { - let message = utils::read_message(self.message.as_ref(), self.hex)?; + let sig = self.sign(|| std::io::stdin().lock())?; + std::io::stdout().lock().write_all(sig.as_bytes())?; + Ok(()) + } + + /// Sign a message. + /// + /// The message can either be provided as immediate argument via CLI or otherwise read from the + /// reader created by `create_reader`. The reader will only be created in case that the message + /// is not passed as immediate. + pub(crate) fn sign(&self, create_reader: F) -> error::Result + where + R: BufRead, + F: FnOnce() -> R, + { + let message = self.message_params.message_from(create_reader)?; let suri = utils::read_uri(self.suri.as_ref())?; let password = self.keystore_params.read_password()?; - let signature = - with_crypto_scheme!(self.crypto_scheme.scheme, sign(&suri, password, message))?; - - println!("{}", signature); - Ok(()) + with_crypto_scheme!(self.crypto_scheme.scheme, sign(&suri, password, message)) } } @@ -70,26 +80,47 @@ fn sign( message: Vec, ) -> error::Result { let pair = utils::pair_from_suri::

(suri, password)?; - Ok(array_bytes::bytes2hex("", pair.sign(&message).as_ref())) + Ok(bytes2hex("0x", pair.sign(&message).as_ref())) } #[cfg(test)] mod test { use super::*; + const SEED: &str = "0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a"; + #[test] - fn sign() { - let seed = "0xad1fb77243b536b90cfe5f0d351ab1b1ac40e3890b41dc64f766ee56340cfca5"; + fn sign_arg() { + let cmd = SignCmd::parse_from(&[ + "sign", + "--suri", + &SEED, + "--message", + &SEED, + "--password", + "12345", + "--hex", + ]); + let sig = cmd.sign(|| std::io::stdin().lock()).expect("Must sign"); + + assert!(sig.starts_with("0x"), "Signature must start with 0x"); + assert!(array_bytes::hex2bytes(&sig).is_ok(), "Signature is valid hex"); + } - let sign = SignCmd::parse_from(&[ + #[test] + fn sign_stdin() { + let cmd = SignCmd::parse_from(&[ "sign", "--suri", - seed, + SEED, "--message", - &seed[2..], + &SEED, "--password", "12345", ]); - assert!(sign.run().is_ok()); + let sig = cmd.sign(|| SEED.as_bytes()).expect("Must sign"); + + assert!(sig.starts_with("0x"), "Signature must start with 0x"); + assert!(array_bytes::hex2bytes(&sig).is_ok(), "Signature is valid hex"); } } diff --git a/client/cli/src/commands/test/mod.rs b/client/cli/src/commands/test/mod.rs new file mode 100644 index 000000000000..9b5d0ee897a9 --- /dev/null +++ b/client/cli/src/commands/test/mod.rs @@ -0,0 +1,21 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +//! Integration tests for subkey commands. + +mod sig_verify; diff --git a/client/cli/src/commands/test/sig_verify.rs b/client/cli/src/commands/test/sig_verify.rs new file mode 100644 index 000000000000..bffd7dbc9fc0 --- /dev/null +++ b/client/cli/src/commands/test/sig_verify.rs @@ -0,0 +1,152 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +#![cfg(test)] + +//! Integration test that the `sign` and `verify` sub-commands work together. + +use crate::*; +use clap::Parser; + +const SEED: &str = "0xe5be9a5092b81bca64be81d212e7f2f9eba183bb7a90954f7b76361f6edb5c0a"; +const ALICE: &str = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"; +const BOB: &str = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty"; + +/// Sign a valid UFT-8 message which can be `hex` and passed either via `stdin` or as an argument. +fn sign(msg: &str, hex: bool, stdin: bool) -> String { + sign_raw(msg.as_bytes(), hex, stdin) +} + +/// Sign a raw message which can be `hex` and passed either via `stdin` or as an argument. +fn sign_raw(msg: &[u8], hex: bool, stdin: bool) -> String { + let mut args = vec!["sign", "--suri", SEED]; + if !stdin { + args.push("--message"); + args.push(std::str::from_utf8(msg).expect("Can only pass valid UTF-8 as arg")); + } + if hex { + args.push("--hex"); + } + let cmd = SignCmd::parse_from(&args); + cmd.sign(|| msg).expect("Static data is good; Must sign; qed") +} + +/// Verify a valid UFT-8 message which can be `hex` and passed either via `stdin` or as an argument. +fn verify(msg: &str, hex: bool, stdin: bool, who: &str, sig: &str) -> bool { + verify_raw(msg.as_bytes(), hex, stdin, who, sig) +} + +/// Verify a raw message which can be `hex` and passed either via `stdin` or as an argument. +fn verify_raw(msg: &[u8], hex: bool, stdin: bool, who: &str, sig: &str) -> bool { + let mut args = vec!["verify", sig, who]; + if !stdin { + args.push("--message"); + args.push(std::str::from_utf8(msg).expect("Can only pass valid UTF-8 as arg")); + } + if hex { + args.push("--hex"); + } + let cmd = VerifyCmd::parse_from(&args); + cmd.verify(|| msg).is_ok() +} + +/// Test that sig/verify works with UTF-8 bytes passed as arg. +#[test] +fn sig_verify_arg_utf8_work() { + let sig = sign("Something", false, false); + + assert!(verify("Something", false, false, ALICE, &sig)); + assert!(!verify("Something", false, false, BOB, &sig)); + + assert!(!verify("Wrong", false, false, ALICE, &sig)); + assert!(!verify("Not hex", true, false, ALICE, &sig)); + assert!(!verify("0x1234", true, false, ALICE, &sig)); + assert!(!verify("Wrong", false, false, BOB, &sig)); + assert!(!verify("Not hex", true, false, BOB, &sig)); + assert!(!verify("0x1234", true, false, BOB, &sig)); +} + +/// Test that sig/verify works with UTF-8 bytes passed via stdin. +#[test] +fn sig_verify_stdin_utf8_work() { + let sig = sign("Something", false, true); + + assert!(verify("Something", false, true, ALICE, &sig)); + assert!(!verify("Something", false, true, BOB, &sig)); + + assert!(!verify("Wrong", false, true, ALICE, &sig)); + assert!(!verify("Not hex", true, true, ALICE, &sig)); + assert!(!verify("0x1234", true, true, ALICE, &sig)); + assert!(!verify("Wrong", false, true, BOB, &sig)); + assert!(!verify("Not hex", true, true, BOB, &sig)); + assert!(!verify("0x1234", true, true, BOB, &sig)); +} + +/// Test that sig/verify works with hex bytes passed as arg. +#[test] +fn sig_verify_arg_hex_work() { + let sig = sign("0xaabbcc", true, false); + + assert!(verify("0xaabbcc", true, false, ALICE, &sig)); + assert!(verify("aabBcc", true, false, ALICE, &sig)); + assert!(verify("0xaAbbCC", true, false, ALICE, &sig)); + assert!(!verify("0xaabbcc", true, false, BOB, &sig)); + + assert!(!verify("0xaabbcc", false, false, ALICE, &sig)); +} + +/// Test that sig/verify works with hex bytes passed via stdin. +#[test] +fn sig_verify_stdin_hex_work() { + let sig = sign("0xaabbcc", true, true); + + assert!(verify("0xaabbcc", true, true, ALICE, &sig)); + assert!(verify("aabBcc", true, true, ALICE, &sig)); + assert!(verify("0xaAbbCC", true, true, ALICE, &sig)); + assert!(!verify("0xaabbcc", true, true, BOB, &sig)); + + assert!(!verify("0xaabbcc", false, true, ALICE, &sig)); +} + +/// Test that sig/verify works with random bytes. +#[test] +fn sig_verify_stdin_non_utf8_work() { + use rand::RngCore; + let mut rng = rand::thread_rng(); + + for _ in 0..100 { + let mut raw = [0u8; 32]; + rng.fill_bytes(&mut raw); + let sig = sign_raw(&raw, false, true); + + assert!(verify_raw(&raw, false, true, ALICE, &sig)); + assert!(!verify_raw(&raw, false, true, BOB, &sig)); + } +} + +/// Test that sig/verify works with invalid UTF-8 bytes. +#[test] +fn sig_verify_stdin_invalid_utf8_work() { + let raw = vec![192u8, 193]; + assert!(String::from_utf8(raw.clone()).is_err(), "Must be invalid UTF-8"); + + let sig = sign_raw(&raw, false, true); + + assert!(verify_raw(&raw, false, true, ALICE, &sig)); + assert!(!verify_raw(&raw, false, true, BOB, &sig)); +} diff --git a/client/cli/src/commands/utils.rs b/client/cli/src/commands/utils.rs index 1ce2b2322169..ff159909b879 100644 --- a/client/cli/src/commands/utils.rs +++ b/client/cli/src/commands/utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -31,7 +31,7 @@ use sp_core::{ Pair, }; use sp_runtime::{traits::IdentifyAccount, MultiSigner}; -use std::{io::Read, path::PathBuf}; +use std::path::PathBuf; /// Public key type for Runtime pub type PublicFor

=

::Public; @@ -273,23 +273,6 @@ where format!("0x{}", HexDisplay::from(&public_key.into().into_account().as_ref())) } -/// checks if message is Some, otherwise reads message from stdin and optionally decodes hex -pub fn read_message(msg: Option<&String>, should_decode: bool) -> Result, Error> { - let mut message = vec![]; - match msg { - Some(m) => { - message = array_bytes::hex2bytes(m.as_str())?; - }, - None => { - std::io::stdin().lock().read_to_end(&mut message)?; - if should_decode { - message = array_bytes::hex2bytes(array_bytes::hex_bytes2hex_str(&message)?)?; - } - }, - } - Ok(message) -} - /// Allows for calling $method with appropriate crypto impl. #[macro_export] macro_rules! with_crypto_scheme { diff --git a/client/cli/src/commands/vanity.rs b/client/cli/src/commands/vanity.rs index ae0007ac7964..ce7516132989 100644 --- a/client/cli/src/commands/vanity.rs +++ b/client/cli/src/commands/vanity.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/cli/src/commands/verify.rs b/client/cli/src/commands/verify.rs index 82554fbf268f..c6d280de3fcf 100644 --- a/client/cli/src/commands/verify.rs +++ b/client/cli/src/commands/verify.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,9 +18,10 @@ //! implementation of the `verify` subcommand -use crate::{error, utils, with_crypto_scheme, CryptoSchemeFlag}; +use crate::{error, params::MessageParams, utils, with_crypto_scheme, CryptoSchemeFlag}; use clap::Parser; use sp_core::crypto::{ByteArray, Ss58Codec}; +use std::io::BufRead; /// The `verify` command #[derive(Debug, Clone, Parser)] @@ -37,14 +38,9 @@ pub struct VerifyCmd { /// If not given, you will be prompted for the URI. uri: Option, - /// Message to verify, if not provided you will be prompted to - /// pass the message via STDIN - #[arg(long)] - message: Option, - - /// The message on STDIN is hex-encoded data - #[arg(long)] - hex: bool, + #[allow(missing_docs)] + #[clap(flatten)] + pub message_params: MessageParams, #[allow(missing_docs)] #[clap(flatten)] @@ -54,7 +50,20 @@ pub struct VerifyCmd { impl VerifyCmd { /// Run the command pub fn run(&self) -> error::Result<()> { - let message = utils::read_message(self.message.as_ref(), self.hex)?; + self.verify(|| std::io::stdin().lock()) + } + + /// Verify a signature for a message. + /// + /// The message can either be provided as immediate argument via CLI or otherwise read from the + /// reader created by `create_reader`. The reader will only be created in case that the message + /// is not passed as immediate. + pub(crate) fn verify(&self, create_reader: F) -> error::Result<()> + where + R: BufRead, + F: FnOnce() -> R, + { + let message = self.message_params.message_from(create_reader)?; let sig_data = array_bytes::hex2bytes(&self.sig)?; let uri = utils::read_uri(self.uri.as_ref())?; let uri = if let Some(uri) = uri.strip_prefix("0x") { uri } else { &uri }; @@ -86,3 +95,43 @@ where Ok(()) } + +#[cfg(test)] +mod test { + use super::*; + + const ALICE: &str = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"; + const SIG1: &str = "0x4eb25a2285a82374888880af0024eb30c3a21ce086eae3862888d345af607f0ad6fb081312f11730932564f24a9f8ebcee2d46861413ae61307eca58db2c3e81"; + const SIG2: &str = "0x026342225155056ea797118c1c8c8b3cc002aa2020c36f4217fa3c302783a572ad3dcd38c231cbaf86cadb93984d329c963ceac0685cc1ee4c1ed50fa443a68f"; + + // Verify work with `--message` argument. + #[test] + fn verify_immediate() { + let cmd = VerifyCmd::parse_from(&["verify", SIG1, ALICE, "--message", "test message"]); + assert!(cmd.run().is_ok(), "Alice' signature should verify"); + } + + // Verify work without `--message` argument. + #[test] + fn verify_stdin() { + let cmd = VerifyCmd::parse_from(&["verify", SIG1, ALICE]); + let message = "test message"; + assert!(cmd.verify(|| message.as_bytes()).is_ok(), "Alice' signature should verify"); + } + + // Verify work with `--message` argument for hex message. + #[test] + fn verify_immediate_hex() { + let cmd = VerifyCmd::parse_from(&["verify", SIG2, ALICE, "--message", "0xaabbcc", "--hex"]); + assert!(cmd.run().is_ok(), "Alice' signature should verify"); + } + + // Verify work without `--message` argument for hex message. + #[test] + fn verify_stdin_hex() { + let cmd = VerifyCmd::parse_from(&["verify", SIG2, ALICE, "--hex"]); + assert!(cmd.verify(|| "0xaabbcc".as_bytes()).is_ok()); + assert!(cmd.verify(|| "aabbcc".as_bytes()).is_ok()); + assert!(cmd.verify(|| "0xaABBcC".as_bytes()).is_ok()); + } +} diff --git a/client/cli/src/config.rs b/client/cli/src/config.rs index 77689708a231..4d218da6aa89 100644 --- a/client/cli/src/config.rs +++ b/client/cli/src/config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -24,7 +24,6 @@ use crate::{ }; use log::warn; use names::{Generator, Name}; -use sc_client_api::execution_extensions::ExecutionStrategies; use sc_service::{ config::{ BasePath, Configuration, DatabaseSource, KeystoreConfig, NetworkConfiguration, @@ -45,6 +44,17 @@ pub(crate) const DEFAULT_NETWORK_CONFIG_PATH: &str = "network"; /// The recommended open file descriptor limit to be configured for the process. const RECOMMENDED_OPEN_FILE_DESCRIPTOR_LIMIT: u64 = 10_000; +/// The default port. +pub const RPC_DEFAULT_PORT: u16 = 9944; +/// The default max number of subscriptions per connection. +pub const RPC_DEFAULT_MAX_SUBS_PER_CONN: u32 = 1024; +/// The default max request size in MB. +pub const RPC_DEFAULT_MAX_REQUEST_SIZE_MB: u32 = 15; +/// The default max response size in MB. +pub const RPC_DEFAULT_MAX_RESPONSE_SIZE_MB: u32 = 15; +/// The default number of connection.. +pub const RPC_DEFAULT_MAX_CONNECTIONS: u32 = 100; + /// Default configuration values used by Substrate /// /// These values will be used by [`CliConfiguration`] to set @@ -57,18 +67,11 @@ pub trait DefaultConfigurationValues { 30333 } - /// The port Substrate should listen on for websocket connections. + /// The port Substrate should listen on for JSON-RPC connections. /// /// By default this is `9944`. - fn rpc_ws_listen_port() -> u16 { - 9944 - } - - /// The port Substrate should listen on for http connections. - /// - /// By default this is `9933`. - fn rpc_http_listen_port() -> u16 { - 9933 + fn rpc_listen_port() -> u16 { + RPC_DEFAULT_PORT } /// The port Substrate should listen on for prometheus connections. @@ -185,10 +188,10 @@ pub trait CliConfiguration: Sized { /// /// By default this is retrieved from `KeystoreParams` if it is available. Otherwise it uses /// `KeystoreConfig::InMemory`. - fn keystore_config(&self, config_dir: &PathBuf) -> Result<(Option, KeystoreConfig)> { + fn keystore_config(&self, config_dir: &PathBuf) -> Result { self.keystore_params() .map(|x| x.keystore_config(config_dir)) - .unwrap_or_else(|| Ok((None, KeystoreConfig::InMemory))) + .unwrap_or_else(|| Ok(KeystoreConfig::InMemory)) } /// Get the database cache size. @@ -287,55 +290,22 @@ pub trait CliConfiguration: Sized { self.import_params().map(|x| x.wasm_runtime_overrides()).unwrap_or_default() } - /// Get the execution strategies. - /// - /// By default this is retrieved from `ImportParams` if it is available. Otherwise its - /// `ExecutionStrategies::default()`. - fn execution_strategies( - &self, - is_dev: bool, - is_validator: bool, - ) -> Result { - Ok(self - .import_params() - .map(|x| x.execution_strategies(is_dev, is_validator)) - .unwrap_or_default()) - } - - /// Get the RPC HTTP address (`None` if disabled). - /// - /// By default this is `None`. - fn rpc_http(&self, _default_listen_port: u16) -> Result> { - Ok(None) - } - - /// Get the RPC IPC path (`None` if disabled). - /// - /// By default this is `None`. - fn rpc_ipc(&self) -> Result> { - Ok(None) - } - - /// Get the RPC websocket address (`None` if disabled). - /// - /// By default this is `None`. - fn rpc_ws(&self, _default_listen_port: u16) -> Result> { + /// Get the RPC address. + fn rpc_addr(&self, _default_listen_port: u16) -> Result> { Ok(None) } /// Returns the RPC method set to expose. /// /// By default this is `RpcMethods::Auto` (unsafe RPCs are denied iff - /// `{rpc,ws}_external` returns true, respectively). + /// `rpc_external` returns true, respectively). fn rpc_methods(&self) -> Result { Ok(Default::default()) } - /// Get the RPC websockets maximum connections (`None` if unlimited). - /// - /// By default this is `None`. - fn rpc_ws_max_connections(&self) -> Result> { - Ok(None) + /// Get the maximum number of RPC server connections. + fn rpc_max_connections(&self) -> Result { + Ok(RPC_DEFAULT_MAX_CONNECTIONS) } /// Get the RPC cors (`None` if disabled) @@ -345,29 +315,19 @@ pub trait CliConfiguration: Sized { Ok(Some(Vec::new())) } - /// Get maximum RPC payload. - fn rpc_max_payload(&self) -> Result> { - Ok(None) - } - /// Get maximum RPC request payload size. - fn rpc_max_request_size(&self) -> Result> { - Ok(None) + fn rpc_max_request_size(&self) -> Result { + Ok(RPC_DEFAULT_MAX_REQUEST_SIZE_MB) } /// Get maximum RPC response payload size. - fn rpc_max_response_size(&self) -> Result> { - Ok(None) + fn rpc_max_response_size(&self) -> Result { + Ok(RPC_DEFAULT_MAX_RESPONSE_SIZE_MB) } /// Get maximum number of subscriptions per connection. - fn rpc_max_subscriptions_per_connection(&self) -> Result> { - Ok(None) - } - - /// Get maximum WS output buffer capacity. - fn ws_max_out_buffer_capacity(&self) -> Result> { - Ok(None) + fn rpc_max_subscriptions_per_connection(&self) -> Result { + Ok(RPC_DEFAULT_MAX_SUBS_PER_CONN) } /// Get the prometheus configuration (`None` if disabled) @@ -505,7 +465,7 @@ pub trait CliConfiguration: Sized { let role = self.role(is_dev)?; let max_runtime_instances = self.max_runtime_instances()?.unwrap_or(8); let is_validator = role.is_authority(); - let (keystore_remote, keystore) = self.keystore_config(&config_dir)?; + let keystore = self.keystore_config(&config_dir)?; let telemetry_endpoints = self.telemetry_endpoints(&chain_spec)?; let runtime_cache_size = self.runtime_cache_size()?; @@ -524,27 +484,23 @@ pub trait CliConfiguration: Sized { node_key, DCV::p2p_listen_port(), )?, - keystore_remote, keystore, database: self.database_config(&config_dir, database_cache_size, database)?, + data_path: config_dir, trie_cache_maximum_size: self.trie_cache_maximum_size()?, state_pruning: self.state_pruning()?, blocks_pruning: self.blocks_pruning()?, wasm_method: self.wasm_method()?, wasm_runtime_overrides: self.wasm_runtime_overrides(), - execution_strategies: self.execution_strategies(is_dev, is_validator)?, - rpc_http: self.rpc_http(DCV::rpc_http_listen_port())?, - rpc_ws: self.rpc_ws(DCV::rpc_ws_listen_port())?, - rpc_ipc: self.rpc_ipc()?, + rpc_addr: self.rpc_addr(DCV::rpc_listen_port())?, rpc_methods: self.rpc_methods()?, - rpc_ws_max_connections: self.rpc_ws_max_connections()?, + rpc_max_connections: self.rpc_max_connections()?, rpc_cors: self.rpc_cors(is_dev)?, - rpc_max_payload: self.rpc_max_payload()?, rpc_max_request_size: self.rpc_max_request_size()?, rpc_max_response_size: self.rpc_max_response_size()?, rpc_id_provider: None, rpc_max_subs_per_conn: self.rpc_max_subscriptions_per_connection()?, - ws_max_out_buffer_capacity: self.ws_max_out_buffer_capacity()?, + rpc_port: DCV::rpc_listen_port(), prometheus_config: self .prometheus_config(DCV::prometheus_listen_port(), &chain_spec)?, telemetry_endpoints, @@ -559,7 +515,7 @@ pub trait CliConfiguration: Sized { max_runtime_instances, announce_block: self.announce_block()?, role, - base_path: Some(base_path), + base_path, informant_output_format: Default::default(), runtime_cache_size, }) diff --git a/client/cli/src/error.rs b/client/cli/src/error.rs index a0f843e73bf5..6c0cfca4932e 100644 --- a/client/cli/src/error.rs +++ b/client/cli/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -64,7 +64,7 @@ pub enum Error { SignatureInvalid, #[error("Key store operation failed")] - KeyStoreOperation, + KeystoreOperation, #[error("Key storage issue encountered")] KeyStorage(#[from] sc_keystore::Error), diff --git a/client/cli/src/lib.rs b/client/cli/src/lib.rs index b6d0d47e52c1..0ef2ee7b1828 100644 --- a/client/cli/src/lib.rs +++ b/client/cli/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -31,6 +31,7 @@ mod config; mod error; mod params; mod runner; +mod signals; pub use arg_enums::*; pub use clap; @@ -41,6 +42,7 @@ pub use params::*; pub use runner::*; pub use sc_service::{ChainSpec, Role}; pub use sc_tracing::logging::LoggerBuilder; +pub use signals::Signals; pub use sp_version::RuntimeVersion; /// Substrate client CLI @@ -197,10 +199,15 @@ pub trait SubstrateCli: Sized { command: &T, ) -> error::Result> { let tokio_runtime = build_runtime()?; + + // `capture` needs to be called in a tokio context. + // Also capture them as early as possible. + let signals = tokio_runtime.block_on(async { Signals::capture() })?; + let config = command.create_configuration(self, tokio_runtime.handle().clone())?; command.init(&Self::support_url(), &Self::impl_version(), |_, _| {}, &config)?; - Runner::new(config, tokio_runtime) + Runner::new(config, tokio_runtime, signals) } /// Create a runner for the command provided in argument. The `logger_hook` can be used to setup @@ -231,11 +238,14 @@ pub trait SubstrateCli: Sized { F: FnOnce(&mut LoggerBuilder, &Configuration), { let tokio_runtime = build_runtime()?; + + // `capture` needs to be called in a tokio context. + // Also capture them as early as possible. + let signals = tokio_runtime.block_on(async { Signals::capture() })?; + let config = command.create_configuration(self, tokio_runtime.handle().clone())?; command.init(&Self::support_url(), &Self::impl_version(), logger_hook, &config)?; - Runner::new(config, tokio_runtime) + Runner::new(config, tokio_runtime, signals) } - /// Native runtime version. - fn native_runtime_version(chain_spec: &Box) -> &'static RuntimeVersion; } diff --git a/client/cli/src/params/database_params.rs b/client/cli/src/params/database_params.rs index 06a154fd6086..cbc602cb877b 100644 --- a/client/cli/src/params/database_params.rs +++ b/client/cli/src/params/database_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/cli/src/params/import_params.rs b/client/cli/src/params/import_params.rs index b7ccbf1c8ed5..bfa54a35058f 100644 --- a/client/cli/src/params/import_params.rs +++ b/client/cli/src/params/import_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -19,15 +19,11 @@ use crate::{ arg_enums::{ ExecutionStrategy, WasmExecutionMethod, WasmtimeInstantiationStrategy, - DEFAULT_EXECUTION_BLOCK_CONSTRUCTION, DEFAULT_EXECUTION_IMPORT_BLOCK, - DEFAULT_EXECUTION_IMPORT_BLOCK_VALIDATOR, DEFAULT_EXECUTION_OFFCHAIN_WORKER, - DEFAULT_EXECUTION_OTHER, DEFAULT_EXECUTION_SYNCING, DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, DEFAULT_WASM_EXECUTION_METHOD, }, params::{DatabaseParams, PruningParams}, }; use clap::Args; -use sc_client_api::execution_extensions::ExecutionStrategies; use std::path::PathBuf; /// Parameters for block import. @@ -52,15 +48,11 @@ pub struct ImportParams { pub wasm_method: WasmExecutionMethod, /// The WASM instantiation method to use. - /// /// Only has an effect when `wasm-execution` is set to `compiled`. - /// /// The copy-on-write strategies are only supported on Linux. /// If the copy-on-write variant of a strategy is unsupported /// the executor will fall back to the non-CoW equivalent. - /// /// The fastest (and the default) strategy available is `pooling-copy-on-write`. - /// /// The `legacy-instance-reuse` strategy is deprecated and will /// be removed in the future. It should only be used in case of /// issues with the default instantiation strategy. @@ -73,7 +65,6 @@ pub struct ImportParams { pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy, /// Specify the path where local WASM runtimes are stored. - /// /// These runtimes will override on-chain runtimes when the version matches. #[arg(long, value_name = "PATH")] pub wasm_runtime_overrides: Option, @@ -83,13 +74,11 @@ pub struct ImportParams { pub execution_strategies: ExecutionStrategiesParams, /// Specify the state cache size. - /// /// Providing `0` will disable the cache. #[arg(long, value_name = "Bytes", default_value_t = 67108864)] pub trie_cache_size: usize, /// DEPRECATED - /// /// Switch to `--trie-cache-size`. #[arg(long)] state_cache_size: Option, @@ -111,6 +100,8 @@ impl ImportParams { /// Get the WASM execution method from the parameters pub fn wasm_method(&self) -> sc_service::config::WasmExecutionMethod { + self.execution_strategies.check_usage_and_print_deprecation_warning(); + crate::execution_method_from_cli(self.wasm_method, self.wasmtime_instantiation_strategy) } @@ -119,36 +110,6 @@ impl ImportParams { pub fn wasm_runtime_overrides(&self) -> Option { self.wasm_runtime_overrides.clone() } - - /// Get execution strategies for the parameters - pub fn execution_strategies(&self, is_dev: bool, is_validator: bool) -> ExecutionStrategies { - let exec = &self.execution_strategies; - let exec_all_or = |strat: Option, default: ExecutionStrategy| { - let default = if is_dev { ExecutionStrategy::Native } else { default }; - - exec.execution.unwrap_or_else(|| strat.unwrap_or(default)).into() - }; - - let default_execution_import_block = if is_validator { - DEFAULT_EXECUTION_IMPORT_BLOCK_VALIDATOR - } else { - DEFAULT_EXECUTION_IMPORT_BLOCK - }; - - ExecutionStrategies { - syncing: exec_all_or(exec.execution_syncing, DEFAULT_EXECUTION_SYNCING), - importing: exec_all_or(exec.execution_import_block, default_execution_import_block), - block_construction: exec_all_or( - exec.execution_block_construction, - DEFAULT_EXECUTION_BLOCK_CONSTRUCTION, - ), - offchain_worker: exec_all_or( - exec.execution_offchain_worker, - DEFAULT_EXECUTION_OFFCHAIN_WORKER, - ), - other: exec_all_or(exec.execution_other, DEFAULT_EXECUTION_OTHER), - } - } } /// Execution strategies parameters. @@ -193,3 +154,23 @@ pub struct ExecutionStrategiesParams { )] pub execution: Option, } + +impl ExecutionStrategiesParams { + /// Check if one of the parameters is still passed and print a warning if so. + fn check_usage_and_print_deprecation_warning(&self) { + for (param, name) in [ + (&self.execution_syncing, "execution-syncing"), + (&self.execution_import_block, "execution-import-block"), + (&self.execution_block_construction, "execution-block-construction"), + (&self.execution_offchain_worker, "execution-offchain-worker"), + (&self.execution_other, "execution-other"), + (&self.execution, "execution"), + ] { + if param.is_some() { + eprintln!( + "CLI parameter `--{name}` has no effect anymore and will be removed in the future!" + ); + } + } + } +} diff --git a/client/cli/src/params/keystore_params.rs b/client/cli/src/params/keystore_params.rs index d6c3c35d8241..a2fdd6b2218c 100644 --- a/client/cli/src/params/keystore_params.rs +++ b/client/cli/src/params/keystore_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -61,16 +61,14 @@ pub struct KeystoreParams { pub password_filename: Option, } -/// Parse a sercret string, returning a displayable error. +/// Parse a secret string, returning a displayable error. pub fn secret_string_from_str(s: &str) -> std::result::Result { std::str::FromStr::from_str(s).map_err(|_| "Could not get SecretString".to_string()) } impl KeystoreParams { /// Get the keystore configuration for the parameters - /// - /// Returns a vector of remote-urls and the local Keystore configuration - pub fn keystore_config(&self, config_dir: &Path) -> Result<(Option, KeystoreConfig)> { + pub fn keystore_config(&self, config_dir: &Path) -> Result { let password = if self.password_interactive { Some(SecretString::new(input_keystore_password()?)) } else if let Some(ref file) = self.password_filename { @@ -85,7 +83,7 @@ impl KeystoreParams { .clone() .unwrap_or_else(|| config_dir.join(DEFAULT_KEYSTORE_CONFIG_PATH)); - Ok((self.keystore_uri.clone(), KeystoreConfig::Path { path, password })) + Ok(KeystoreConfig::Path { path, password }) } /// helper method to fetch password from `KeyParams` or read from stdin diff --git a/client/cli/src/params/message_params.rs b/client/cli/src/params/message_params.rs new file mode 100644 index 000000000000..3fcb6f2c6e8c --- /dev/null +++ b/client/cli/src/params/message_params.rs @@ -0,0 +1,120 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +//! Params to configure how a message should be passed into a command. + +use crate::error::Error; +use array_bytes::{hex2bytes, hex_bytes2hex_str}; +use clap::Args; +use std::io::BufRead; + +/// Params to configure how a message should be passed into a command. +#[derive(Debug, Clone, Args)] +pub struct MessageParams { + /// Message to process. Will be read from STDIN otherwise. + /// The message is assumed to be raw bytes per default. Use `--hex` for hex input. Can + /// optionally be prefixed with `0x` in the hex case. + #[arg(long)] + message: Option, + + /// The message is hex-encoded data. + #[arg(long)] + hex: bool, +} + +impl MessageParams { + /// Produces the message by either using its immediate value or reading from stdin. + /// + /// This function should only be called once and the result cached. + pub(crate) fn message_from(&self, create_reader: F) -> Result, Error> + where + R: BufRead, + F: FnOnce() -> R, + { + let raw = match &self.message { + Some(raw) => raw.as_bytes().to_vec(), + None => { + let mut raw = vec![]; + create_reader().read_to_end(&mut raw)?; + raw + }, + }; + if self.hex { + hex2bytes(hex_bytes2hex_str(&raw)?).map_err(Into::into) + } else { + Ok(raw) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + /// Test that decoding an immediate message works. + #[test] + fn message_decode_immediate() { + for (name, input, hex, output) in test_closures() { + println!("Testing: immediate_{}", name); + let params = MessageParams { message: Some(input.into()), hex }; + let message = params.message_from(|| std::io::stdin().lock()); + + match output { + Some(output) => { + let message = message.expect(&format!("{}: should decode but did not", name)); + assert_eq!(message, output, "{}: decoded a wrong message", name); + }, + None => { + message.err().expect(&format!("{}: should not decode but did", name)); + }, + } + } + } + + /// Test that decoding a message from a stream works. + #[test] + fn message_decode_stream() { + for (name, input, hex, output) in test_closures() { + println!("Testing: stream_{}", name); + let params = MessageParams { message: None, hex }; + let message = params.message_from(|| input.as_bytes()); + + match output { + Some(output) => { + let message = message.expect(&format!("{}: should decode but did not", name)); + assert_eq!(message, output, "{}: decoded a wrong message", name); + }, + None => { + message.err().expect(&format!("{}: should not decode but did", name)); + }, + } + } + } + + /// Returns (test_name, input, hex, output). + fn test_closures() -> Vec<(&'static str, &'static str, bool, Option<&'static [u8]>)> { + vec![ + ("decode_no_hex_works", "Hello this is not hex", false, Some(b"Hello this is not hex")), + ("decode_no_hex_with_hex_string_works", "0xffffffff", false, Some(b"0xffffffff")), + ("decode_hex_works", "0x00112233", true, Some(&[0, 17, 34, 51])), + ("decode_hex_without_prefix_works", "00112233", true, Some(&[0, 17, 34, 51])), + ("decode_hex_uppercase_works", "0xaAbbCCDd", true, Some(&[170, 187, 204, 221])), + ("decode_hex_wrong_len_errors", "0x0011223", true, None), + ] + } +} diff --git a/client/cli/src/params/mod.rs b/client/cli/src/params/mod.rs index 3197deb101bc..a73bd8844fec 100644 --- a/client/cli/src/params/mod.rs +++ b/client/cli/src/params/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,11 +18,15 @@ mod database_params; mod import_params; mod keystore_params; +mod message_params; mod network_params; mod node_key_params; mod offchain_worker_params; +mod prometheus_params; mod pruning_params; +mod runtime_params; mod shared_params; +mod telemetry_params; mod transaction_pool_params; use crate::arg_enums::{CryptoScheme, OutputType}; @@ -35,9 +39,9 @@ use sp_runtime::{ use std::{fmt::Debug, str::FromStr}; pub use crate::params::{ - database_params::*, import_params::*, keystore_params::*, network_params::*, - node_key_params::*, offchain_worker_params::*, pruning_params::*, shared_params::*, - transaction_pool_params::*, + database_params::*, import_params::*, keystore_params::*, message_params::*, network_params::*, + node_key_params::*, offchain_worker_params::*, prometheus_params::*, pruning_params::*, + runtime_params::*, shared_params::*, telemetry_params::*, transaction_pool_params::*, }; /// Parse Ss58AddressFormat @@ -109,7 +113,6 @@ impl BlockNumberOrHash { /// Parse the inner value as `BlockId`. pub fn parse(&self) -> Result, String> where - B::Hash: FromStr, ::Err: std::fmt::Debug, NumberFor: FromStr, as FromStr>::Err: std::fmt::Debug, diff --git a/client/cli/src/params/network_params.rs b/client/cli/src/params/network_params.rs index 984789da752d..84db218cc51d 100644 --- a/client/cli/src/params/network_params.rs +++ b/client/cli/src/params/network_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -19,15 +19,16 @@ use crate::{arg_enums::SyncMode, params::node_key_params::NodeKeyParams}; use clap::Args; use sc_network::{ - config::{NetworkConfiguration, NodeKeyConfig}, + config::{ + NetworkConfiguration, NodeKeyConfig, NonReservedPeerMode, SetConfig, TransportConfig, + }, multiaddr::Protocol, }; -use sc_network_common::config::{NonReservedPeerMode, SetConfig, TransportConfig}; use sc_service::{ config::{Multiaddr, MultiaddrWithPeerId}, ChainSpec, ChainType, }; -use std::{borrow::Cow, path::PathBuf}; +use std::{borrow::Cow, num::NonZeroUsize, path::PathBuf}; /// Parameters used to create the network configuration. #[derive(Debug, Clone, Args)] @@ -41,9 +42,7 @@ pub struct NetworkParams { pub reserved_nodes: Vec, /// Whether to only synchronize the chain with reserved nodes. - /// /// Also disables automatic peer discovery. - /// /// TCP connections might still be established with non-reserved nodes. /// In particular, if you are a validator your node might still connect to other /// validator nodes and collator nodes regardless of whether they are defined as @@ -82,11 +81,11 @@ pub struct NetworkParams { pub allow_private_ip: bool, /// Specify the number of outgoing connections we're trying to maintain. - #[arg(long, value_name = "COUNT", default_value_t = 15)] + #[arg(long, value_name = "COUNT", default_value_t = 8)] pub out_peers: u32, /// Maximum number of inbound full nodes peers. - #[arg(long, value_name = "COUNT", default_value_t = 25)] + #[arg(long, value_name = "COUNT", default_value_t = 32)] pub in_peers: u32, /// Maximum number of inbound light nodes peers. @@ -94,14 +93,12 @@ pub struct NetworkParams { pub in_peers_light: u32, /// Disable mDNS discovery. - /// /// By default, the network will use mDNS to discover other nodes on the /// local network. This disables it. Automatically implied when using --dev. #[arg(long)] pub no_mdns: bool, /// Maximum number of peers from which to ask for the same blocks in parallel. - /// /// This allows downloading announced blocks from multiple peers. Decrease to save /// traffic and risk increased latency. #[arg(long, value_name = "COUNT", default_value_t = 5)] @@ -112,7 +109,6 @@ pub struct NetworkParams { pub node_key_params: NodeKeyParams, /// Enable peer discovery on local networks. - /// /// By default this option is `true` for `--dev` or when the chain type is /// `Local`/`Development` and false otherwise. #[arg(long)] @@ -120,22 +116,23 @@ pub struct NetworkParams { /// Require iterative Kademlia DHT queries to use disjoint paths for increased resiliency in /// the presence of potentially adversarial nodes. - /// /// See the S/Kademlia paper for more information on the high level design as well as its /// security improvements. #[arg(long)] pub kademlia_disjoint_query_paths: bool, + /// Kademlia replication factor determines to how many closest peers a record is replicated to. + /// + /// Discovery mechanism requires successful replication to all + /// `kademlia_replication_factor` peers to consider record successfully put. + #[arg(long, default_value = "20")] + pub kademlia_replication_factor: NonZeroUsize, + /// Join the IPFS network and serve transactions over bitswap protocol. #[arg(long)] pub ipfs_server: bool, /// Blockchain syncing mode. - /// - /// - `full`: Download and validate full blockchain history. - /// - `fast`: Download blocks and the latest state only. - /// - `fast-unsafe`: Same as `fast`, but skip downloading state proofs. - /// - `warp`: Download the latest state and proof. #[arg( long, value_enum, @@ -145,6 +142,13 @@ pub struct NetworkParams { verbatim_doc_comment )] pub sync: SyncMode, + + /// Maximum number of blocks per request. + /// + /// Try reducing this number from the default value if you have a slow network connection + /// and observe block requests timing out. + #[arg(long, value_name = "COUNT", default_value_t = 64)] + pub max_blocks_per_request: u32, } impl NetworkParams { @@ -224,8 +228,6 @@ impl NetworkParams { default_peers_set_num_full: self.in_peers + self.out_peers, listen_addresses, public_addresses, - extra_sets: Vec::new(), - request_response_protocols: Vec::new(), node_key, node_name: node_name.to_string(), client_version: client_id.to_string(), @@ -234,9 +236,11 @@ impl NetworkParams { allow_private_ip, }, max_parallel_downloads: self.max_parallel_downloads, + max_blocks_per_request: self.max_blocks_per_request, enable_dht_random_walk: !self.reserved_only, allow_non_globals_in_dht, kademlia_disjoint_query_paths: self.kademlia_disjoint_query_paths, + kademlia_replication_factor: self.kademlia_replication_factor, yamux_window_size: None, ipfs_server: self.ipfs_server, sync_mode: self.sync.into(), diff --git a/client/cli/src/params/node_key_params.rs b/client/cli/src/params/node_key_params.rs index 2346455c26a3..8c5579eaec49 100644 --- a/client/cli/src/params/node_key_params.rs +++ b/client/cli/src/params/node_key_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -33,16 +33,12 @@ const NODE_KEY_ED25519_FILE: &str = "secret_ed25519"; #[derive(Debug, Clone, Args)] pub struct NodeKeyParams { /// The secret key to use for libp2p networking. - /// /// The value is a string that is parsed according to the choice of /// `--node-key-type` as follows: - /// /// `ed25519`: /// The value is parsed as a hex-encoded Ed25519 32 byte secret key, /// i.e. 64 hex characters. - /// /// The value of this option takes precedence over `--node-key-file`. - /// /// WARNING: Secrets provided as command-line arguments are easily exposed. /// Use of this option should be limited to development and testing. To use /// an externally managed secret key, use `--node-key-file` instead. @@ -50,33 +46,25 @@ pub struct NodeKeyParams { pub node_key: Option, /// The type of secret key to use for libp2p networking. - /// /// The secret key of the node is obtained as follows: - /// /// * If the `--node-key` option is given, the value is parsed as a secret key according to /// the type. See the documentation for `--node-key`. - /// /// * If the `--node-key-file` option is given, the secret key is read from the specified /// file. See the documentation for `--node-key-file`. - /// /// * Otherwise, the secret key is read from a file with a predetermined, type-specific name /// from the chain-specific network config directory inside the base directory specified by /// `--base-dir`. If this file does not exist, it is created with a newly generated secret /// key of the chosen type. - /// /// The node's secret key determines the corresponding public key and hence the /// node's peer ID in the context of libp2p. #[arg(long, value_name = "TYPE", value_enum, ignore_case = true, default_value_t = NodeKeyType::Ed25519)] pub node_key_type: NodeKeyType, /// The file from which to read the node's secret key to use for libp2p networking. - /// /// The contents of the file are parsed according to the choice of `--node-key-type` /// as follows: - /// /// `ed25519`: /// The file must contain an unencoded 32 byte or hex encoded Ed25519 secret key. - /// /// If the file does not exist, it is created with a newly generated secret key of /// the chosen type. #[arg(long, value_name = "FILE")] @@ -113,7 +101,7 @@ fn invalid_node_key(e: impl std::fmt::Display) -> error::Error { /// Parse a Ed25519 secret key from a hex string into a `sc_network::Secret`. fn parse_ed25519_secret(hex: &str) -> error::Result { H256::from_str(hex).map_err(invalid_node_key).and_then(|bytes| { - ed25519::SecretKey::from_bytes(bytes) + ed25519::SecretKey::try_from_bytes(bytes) .map(sc_network::config::Secret::Input) .map_err(invalid_node_key) }) @@ -123,7 +111,7 @@ fn parse_ed25519_secret(hex: &str) -> error::Result {}, - _ => panic!("Invalid key"), + if let Ok(pair) = node_key.try_into_ed25519() { + if pair.secret().as_ref() != key.as_ref() { + panic!("Invalid key") + } + } else { + panic!("Invalid key") } } diff --git a/client/cli/src/params/offchain_worker_params.rs b/client/cli/src/params/offchain_worker_params.rs index b18e036c06de..d1fedab4cb2e 100644 --- a/client/cli/src/params/offchain_worker_params.rs +++ b/client/cli/src/params/offchain_worker_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -33,7 +33,6 @@ use crate::{error, OffchainWorkerEnabled}; #[derive(Debug, Clone, Args)] pub struct OffchainWorkerParams { /// Should execute offchain workers on every block. - /// /// By default it's only enabled for nodes that are authoring new blocks. #[arg( long = "offchain-worker", @@ -45,9 +44,7 @@ pub struct OffchainWorkerParams { pub enabled: OffchainWorkerEnabled, /// Enable Offchain Indexing API, which allows block import to write to Offchain DB. - /// - /// Enables a runtime to write directly to a offchain workers - /// DB during block import. + /// Enables a runtime to write directly to a offchain workers DB during block import. #[arg(long = "enable-offchain-indexing", value_name = "ENABLE_OFFCHAIN_INDEXING", default_value_t = false, action = ArgAction::Set)] pub indexing_enabled: bool, } diff --git a/client/cli/src/params/prometheus_params.rs b/client/cli/src/params/prometheus_params.rs new file mode 100644 index 000000000000..4d234ea33c20 --- /dev/null +++ b/client/cli/src/params/prometheus_params.rs @@ -0,0 +1,61 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 clap::Args; +use sc_service::config::PrometheusConfig; +use std::net::{Ipv4Addr, SocketAddr}; + +/// Parameters used to config prometheus. +#[derive(Debug, Clone, Args)] +pub struct PrometheusParams { + /// Specify Prometheus exporter TCP Port. + #[arg(long, value_name = "PORT")] + pub prometheus_port: Option, + /// Expose Prometheus exporter on all interfaces. + /// Default is local. + #[arg(long)] + pub prometheus_external: bool, + /// Do not expose a Prometheus exporter endpoint. + /// Prometheus metric endpoint is enabled by default. + #[arg(long)] + pub no_prometheus: bool, +} + +impl PrometheusParams { + /// Creates [`PrometheusConfig`]. + pub fn prometheus_config( + &self, + default_listen_port: u16, + chain_id: String, + ) -> Option { + if self.no_prometheus { + None + } else { + let interface = + if self.prometheus_external { Ipv4Addr::UNSPECIFIED } else { Ipv4Addr::LOCALHOST }; + + Some(PrometheusConfig::new_with_default_registry( + SocketAddr::new( + interface.into(), + self.prometheus_port.unwrap_or(default_listen_port), + ), + chain_id, + )) + } + } +} diff --git a/client/cli/src/params/pruning_params.rs b/client/cli/src/params/pruning_params.rs index fd01ba67bab7..1b5bf247d942 100644 --- a/client/cli/src/params/pruning_params.rs +++ b/client/cli/src/params/pruning_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -21,50 +21,29 @@ use clap::Args; use sc_service::{BlocksPruning, PruningMode}; /// Parameters to define the pruning mode -#[derive(Debug, Clone, PartialEq, Args)] +#[derive(Debug, Clone, Args)] pub struct PruningParams { /// Specify the state pruning mode. - /// /// This mode specifies when the block's state (ie, storage) /// should be pruned (ie, removed) from the database. - /// /// This setting can only be set on the first creation of the database. Every subsequent run /// will load the pruning mode from the database and will error if the stored mode doesn't /// match this CLI value. It is fine to drop this CLI flag for subsequent runs. - /// /// Possible values: - /// - /// - archive: - /// - /// Keep the state of all blocks. - /// - /// - 'archive-canonical' - /// - /// Keep only the state of finalized blocks. - /// - /// - number - /// - /// Keep the state of the last number of finalized blocks. - /// + /// - archive: Keep the state of all blocks. + /// - 'archive-canonical' Keep only the state of finalized blocks. + /// - number Keep the state of the last number of finalized blocks. /// [default: 256] #[arg(alias = "pruning", long, value_name = "PRUNING_MODE")] pub state_pruning: Option, + /// Specify the blocks pruning mode. - /// /// This mode specifies when the block's body (including justifications) /// should be pruned (ie, removed) from the database. - /// /// Possible values: - /// - 'archive' - /// - /// Keep all blocks. - /// - /// - 'archive-canonical' - /// - /// Keep only finalized blocks. - /// + /// - 'archive' Keep all blocks. + /// - 'archive-canonical' Keep only finalized blocks. /// - number - /// /// Keep the last `number` of finalized blocks. #[arg( alias = "keep-blocks", diff --git a/client/cli/src/params/runtime_params.rs b/client/cli/src/params/runtime_params.rs new file mode 100644 index 000000000000..79035fc7d4c5 --- /dev/null +++ b/client/cli/src/params/runtime_params.rs @@ -0,0 +1,43 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 clap::Args; +use std::str::FromStr; + +/// Parameters used to config runtime. +#[derive(Debug, Clone, Args)] +pub struct RuntimeParams { + /// The size of the instances cache for each runtime. The values higher than 256 are illegal. + #[arg(long, default_value_t = 8, value_parser = parse_max_runtime_instances)] + pub max_runtime_instances: usize, + + /// Maximum number of different runtimes that can be cached. + #[arg(long, default_value_t = 2)] + pub runtime_cache_size: u8, +} + +fn parse_max_runtime_instances(s: &str) -> Result { + let max_runtime_instances = usize::from_str(s) + .map_err(|_err| format!("Illegal `--max-runtime-instances` value: {s}"))?; + + if max_runtime_instances > 256 { + Err(format!("Illegal `--max-runtime-instances` value: {max_runtime_instances} is more than the allowed maximum of `256` ")) + } else { + Ok(max_runtime_instances) + } +} diff --git a/client/cli/src/params/shared_params.rs b/client/cli/src/params/shared_params.rs index 5cbb6dbad54a..3d20ca504a69 100644 --- a/client/cli/src/params/shared_params.rs +++ b/client/cli/src/params/shared_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -22,17 +22,15 @@ use sc_service::config::BasePath; use std::path::PathBuf; /// Shared parameters used by all `CoreParams`. -#[derive(Debug, Clone, PartialEq, Args)] +#[derive(Debug, Clone, Args)] pub struct SharedParams { /// Specify the chain specification. - /// /// It can be one of the predefined ones (dev, local, or staging) or it can be a path to a file /// with the chainspec (such as one exported by the `build-spec` subcommand). #[arg(long, value_name = "CHAIN_SPEC")] pub chain: Option, /// Specify the development chain. - /// /// This flag sets `--chain=dev`, `--force-authoring`, `--rpc-cors=all`, /// `--alice`, and `--tmp` flags, unless explicitly overridden. #[arg(long, conflicts_with_all = &["chain"])] @@ -43,16 +41,13 @@ pub struct SharedParams { pub base_path: Option, /// Sets a custom logging filter. Syntax is `=`, e.g. -lsync=debug. - /// /// Log levels (least to most verbose) are error, warn, info, debug, and trace. /// By default, all targets log `info`. The global log level can be set with `-l`. #[arg(short = 'l', long, value_name = "LOG_PATTERN", num_args = 1..)] pub log: Vec, /// Enable detailed log output. - /// /// This includes displaying the log target, log level and thread name. - /// /// This is automatically enabled when something is logged with any higher level than `info`. #[arg(long)] pub detailed_log_output: bool, @@ -62,10 +57,8 @@ pub struct SharedParams { pub disable_log_color: bool, /// Enable feature to dynamically update and reload the log filter. - /// /// Be aware that enabling this feature can lead to a performance decrease up to factor six or /// more. Depending on the global logging level the performance decrease changes. - /// /// The `system_addLogFilter` and `system_resetLogFilter` RPCs will have no effect with this /// option not being set. #[arg(long)] diff --git a/client/cli/src/params/telemetry_params.rs b/client/cli/src/params/telemetry_params.rs new file mode 100644 index 000000000000..67f441071410 --- /dev/null +++ b/client/cli/src/params/telemetry_params.rs @@ -0,0 +1,66 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 clap::Args; + +/// Parameters used to config telemetry. +#[derive(Debug, Clone, Args)] +pub struct TelemetryParams { + /// Disable connecting to the Substrate telemetry server. + /// Telemetry is on by default on global chains. + #[arg(long)] + pub no_telemetry: bool, + + /// The URL of the telemetry server to connect to. + /// This flag can be passed multiple times as a means to specify multiple + /// telemetry endpoints. Verbosity levels range from 0-9, with 0 denoting + /// the least verbosity. + /// Expected format is 'URL VERBOSITY', e.g. `--telemetry-url 'wss://foo/bar 0'`. + #[arg(long = "telemetry-url", value_name = "URL VERBOSITY", value_parser = parse_telemetry_endpoints)] + pub telemetry_endpoints: Vec<(String, u8)>, +} + +#[derive(Debug)] +enum TelemetryParsingError { + MissingVerbosity, + VerbosityParsingError(std::num::ParseIntError), +} + +impl std::error::Error for TelemetryParsingError {} + +impl std::fmt::Display for TelemetryParsingError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + TelemetryParsingError::MissingVerbosity => write!(f, "Verbosity level missing"), + TelemetryParsingError::VerbosityParsingError(e) => write!(f, "{}", e), + } + } +} + +fn parse_telemetry_endpoints(s: &str) -> Result<(String, u8), TelemetryParsingError> { + let pos = s.find(' '); + match pos { + None => Err(TelemetryParsingError::MissingVerbosity), + Some(pos_) => { + let url = s[..pos_].to_string(); + let verbosity = + s[pos_ + 1..].parse().map_err(TelemetryParsingError::VerbosityParsingError)?; + Ok((url, verbosity)) + }, + } +} diff --git a/client/cli/src/params/transaction_pool_params.rs b/client/cli/src/params/transaction_pool_params.rs index 6b3a2d8a97a0..b2bf0b9b364c 100644 --- a/client/cli/src/params/transaction_pool_params.rs +++ b/client/cli/src/params/transaction_pool_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/cli/src/runner.rs b/client/cli/src/runner.rs index c7a6f1f3c0f9..59f53200a192 100644 --- a/client/cli/src/runner.rs +++ b/client/cli/src/runner.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,62 +16,15 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{error::Error as CliError, Result, SubstrateCli}; +use crate::{error::Error as CliError, Result, Signals, SubstrateCli}; use chrono::prelude::*; -use futures::{future, future::FutureExt, pin_mut, select, Future}; +use futures::{future::FutureExt, Future}; use log::info; use sc_service::{Configuration, Error as ServiceError, TaskManager}; use sc_utils::metrics::{TOKIO_THREADS_ALIVE, TOKIO_THREADS_TOTAL}; use std::{marker::PhantomData, time::Duration}; -#[cfg(target_family = "unix")] -async fn main(func: F) -> std::result::Result<(), E> -where - F: Future> + future::FusedFuture, - E: std::error::Error + Send + Sync + 'static + From, -{ - use tokio::signal::unix::{signal, SignalKind}; - - let mut stream_int = signal(SignalKind::interrupt()).map_err(ServiceError::Io)?; - let mut stream_term = signal(SignalKind::terminate()).map_err(ServiceError::Io)?; - - let t1 = stream_int.recv().fuse(); - let t2 = stream_term.recv().fuse(); - let t3 = func; - - pin_mut!(t1, t2, t3); - - select! { - _ = t1 => {}, - _ = t2 => {}, - res = t3 => res?, - } - - Ok(()) -} - -#[cfg(not(unix))] -async fn main(func: F) -> std::result::Result<(), E> -where - F: Future> + future::FusedFuture, - E: std::error::Error + Send + Sync + 'static + From, -{ - use tokio::signal::ctrl_c; - - let t1 = ctrl_c().fuse(); - let t2 = func; - - pin_mut!(t1, t2); - - select! { - _ = t1 => {}, - res = t2 => res?, - } - - Ok(()) -} - -/// Build a tokio runtime with all features +/// Build a tokio runtime with all features. pub fn build_runtime() -> std::result::Result { tokio::runtime::Builder::new_multi_thread() .on_thread_start(|| { @@ -85,35 +38,22 @@ pub fn build_runtime() -> std::result::Result( - tokio_runtime: tokio::runtime::Runtime, - future: F, - task_manager: TaskManager, -) -> std::result::Result<(), E> -where - F: Future> + future::Future, - E: std::error::Error + Send + Sync + 'static + From, -{ - let f = future.fuse(); - pin_mut!(f); - - tokio_runtime.block_on(main(f))?; - drop(task_manager); - - Ok(()) -} - /// A Substrate CLI runtime that can be used to run a node or a command pub struct Runner { config: Configuration, tokio_runtime: tokio::runtime::Runtime, + signals: Signals, phantom: PhantomData, } impl Runner { /// Create a new runtime with the command provided in argument - pub fn new(config: Configuration, tokio_runtime: tokio::runtime::Runtime) -> Result> { - Ok(Runner { config, tokio_runtime, phantom: PhantomData }) + pub fn new( + config: Configuration, + tokio_runtime: tokio::runtime::Runtime, + signals: Signals, + ) -> Result> { + Ok(Runner { config, tokio_runtime, signals, phantom: PhantomData }) } /// Log information about the node itself. @@ -147,7 +87,10 @@ impl Runner { self.print_node_infos(); let mut task_manager = self.tokio_runtime.block_on(initialize(self.config))?; - let res = self.tokio_runtime.block_on(main(task_manager.future().fuse())); + + let res = self + .tokio_runtime + .block_on(self.signals.run_until_signal(task_manager.future().fuse())); // We need to drop the task manager here to inform all tasks that they should shut down. // // This is important to be done before we instruct the tokio runtime to shutdown. Otherwise @@ -210,7 +153,11 @@ impl Runner { E: std::error::Error + Send + Sync + 'static + From + From, { let (future, task_manager) = runner(self.config)?; - run_until_exit::<_, E>(self.tokio_runtime, future, task_manager) + self.tokio_runtime.block_on(self.signals.run_until_signal(future.fuse()))?; + // Drop the task manager before dropping the rest, to ensure that all futures were informed + // about the shut down. + drop(task_manager); + Ok(()) } /// Get an immutable reference to the node Configuration @@ -240,23 +187,18 @@ pub fn print_node_infos(config: &Configuration) { .path() .map_or_else(|| "".to_owned(), |p| p.display().to_string()) ); - info!("⛓ Native runtime: {}", C::native_runtime_version(&config.chain_spec)); } #[cfg(test)] mod tests { + use super::*; + use sc_network::config::NetworkConfiguration; + use sc_service::{Arc, ChainType, GenericChainSpec, NoExtension}; use std::{ path::PathBuf, sync::atomic::{AtomicU64, Ordering}, }; - use sc_network::config::NetworkConfiguration; - use sc_service::{Arc, ChainType, GenericChainSpec, NoExtension}; - use sp_runtime::create_runtime_str; - use sp_version::create_apis_vec; - - use super::*; - struct Cli; impl SubstrateCli for Cli { @@ -290,28 +232,12 @@ mod tests { ) -> std::result::Result, String> { Err("nope".into()) } - - fn native_runtime_version( - _: &Box, - ) -> &'static sp_version::RuntimeVersion { - const VERSION: sp_version::RuntimeVersion = sp_version::RuntimeVersion { - spec_name: create_runtime_str!("spec"), - impl_name: create_runtime_str!("name"), - authoring_version: 0, - spec_version: 0, - impl_version: 0, - apis: create_apis_vec!([]), - transaction_version: 2, - state_version: 0, - }; - - &VERSION - } } fn create_runner() -> Runner { let runtime = build_runtime().unwrap(); + let root = PathBuf::from("db"); let runner = Runner::new( Configuration { impl_name: "spec".into(), @@ -321,8 +247,7 @@ mod tests { transaction_pool: Default::default(), network: NetworkConfiguration::new_memory(), keystore: sc_service::config::KeystoreConfig::InMemory, - keystore_remote: None, - database: sc_client_db::DatabaseSource::ParityDb { path: PathBuf::from("db") }, + database: sc_client_db::DatabaseSource::ParityDb { path: root.clone() }, trie_cache_maximum_size: None, state_pruning: None, blocks_pruning: sc_client_db::BlocksPruning::KeepAll, @@ -340,19 +265,15 @@ mod tests { )), wasm_method: Default::default(), wasm_runtime_overrides: None, - execution_strategies: Default::default(), - rpc_http: None, - rpc_ws: None, - rpc_ipc: None, - rpc_ws_max_connections: None, + rpc_addr: None, + rpc_max_connections: Default::default(), rpc_cors: None, rpc_methods: Default::default(), - rpc_max_payload: None, - rpc_max_request_size: None, - rpc_max_response_size: None, - rpc_id_provider: None, - rpc_max_subs_per_conn: None, - ws_max_out_buffer_capacity: None, + rpc_max_request_size: Default::default(), + rpc_max_response_size: Default::default(), + rpc_id_provider: Default::default(), + rpc_max_subs_per_conn: Default::default(), + rpc_port: 9944, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, @@ -364,11 +285,13 @@ mod tests { tracing_receiver: Default::default(), max_runtime_instances: 8, announce_block: true, - base_path: None, + base_path: sc_service::BasePath::new(root.clone()), + data_path: root, informant_output_format: Default::default(), runtime_cache_size: 2, }, runtime, + Signals::dummy(), ) .unwrap(); @@ -477,7 +400,7 @@ mod tests { }, ); - let Some(output) = output else { return } ; + let Some(output) = output else { return }; let stderr = dbg!(String::from_utf8(output.stderr).unwrap()); diff --git a/client/cli/src/signals.rs b/client/cli/src/signals.rs new file mode 100644 index 000000000000..4b6a6f957a76 --- /dev/null +++ b/client/cli/src/signals.rs @@ -0,0 +1,92 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 futures::{ + future::{self, BoxFuture, FutureExt}, + pin_mut, select, Future, +}; + +use sc_service::Error as ServiceError; + +/// Abstraction over OS signals to handle the shutdown of the node smoothly. +/// +/// On `unix` this represents `SigInt` and `SigTerm`. +pub struct Signals(BoxFuture<'static, ()>); + +impl Signals { + /// Return the signals future. + pub fn future(self) -> BoxFuture<'static, ()> { + self.0 + } + + /// Capture the relevant signals to handle shutdown of the node smoothly. + /// + /// Needs to be called in a Tokio context to have access to the tokio reactor. + #[cfg(target_family = "unix")] + pub fn capture() -> std::result::Result { + use tokio::signal::unix::{signal, SignalKind}; + + let mut stream_int = signal(SignalKind::interrupt()).map_err(ServiceError::Io)?; + let mut stream_term = signal(SignalKind::terminate()).map_err(ServiceError::Io)?; + + Ok(Signals( + async move { + future::select(stream_int.recv().boxed(), stream_term.recv().boxed()).await; + } + .boxed(), + )) + } + + /// Capture the relevant signals to handle shutdown of the node smoothly. + /// + /// Needs to be called in a Tokio context to have access to the tokio reactor. + #[cfg(not(unix))] + pub fn capture() -> Result { + use tokio::signal::ctrl_c; + + Ok(Signals( + async move { + let _ = ctrl_c().await; + } + .boxed(), + )) + } + + /// A dummy signal that never returns. + pub fn dummy() -> Self { + Self(future::pending().boxed()) + } + + /// Run a future task until receive a signal. + pub async fn run_until_signal(self, func: F) -> Result<(), E> + where + F: Future> + future::FusedFuture, + E: std::error::Error + Send + Sync + 'static, + { + let signals = self.future().fuse(); + + pin_mut!(func, signals); + + select! { + _ = signals => {}, + res = func => res?, + } + + Ok(()) + } +} diff --git a/client/consensus/aura/Cargo.toml b/client/consensus/aura/Cargo.toml index 4c0305e9f66e..70581e8c80d5 100644 --- a/client/consensus/aura/Cargo.toml +++ b/client/consensus/aura/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = "0.1.57" -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" log = "0.4.17" thiserror = "1.0" @@ -25,16 +25,16 @@ sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/commo sc-consensus-slots = { version = "0.10.0-dev", path = "../slots" } sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -sp-application-crypto = { version = "7.0.0", path = "../../../primitives/application-crypto" } +sp-application-crypto = { version = "23.0.0", path = "../../../primitives/application-crypto" } sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-consensus-aura = { version = "0.10.0-dev", path = "../../../primitives/consensus/aura" } sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-keystore = { version = "0.27.0", path = "../../../primitives/keystore" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } [dev-dependencies] parking_lot = "0.12.1" @@ -42,8 +42,8 @@ tempfile = "3.1.0" sc-keystore = { version = "4.0.0-dev", path = "../../keystore" } sc-network = { version = "0.10.0-dev", path = "../../network" } sc-network-test = { version = "0.8.0", path = "../../network/test" } -sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } +sp-keyring = { version = "24.0.0", path = "../../../primitives/keyring" } sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } -sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } +sp-tracing = { version = "10.0.0", path = "../../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } tokio = { version = "1.22.0" } diff --git a/client/consensus/aura/src/import_queue.rs b/client/consensus/aura/src/import_queue.rs index 3ac26345db25..0b9ceb4fe776 100644 --- a/client/consensus/aura/src/import_queue.rs +++ b/client/consensus/aura/src/import_queue.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -19,7 +19,7 @@ //! Module implementing the logic for verifying and importing AuRa blocks. use crate::{ - aura_err, authorities, find_pre_digest, slot_author, AuthorityId, CompatibilityMode, Error, + authorities, standalone::SealVerificationError, AuthorityId, CompatibilityMode, Error, LOG_TARGET, }; use codec::{Codec, Decode, Encode}; @@ -34,14 +34,13 @@ use sc_consensus_slots::{check_equivocation, CheckedHeader, InherentDataProvider use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_TRACE}; use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_block_builder::BlockBuilder as BlockBuilderApi; -use sp_blockchain::{well_known_cache_keys::Id as CacheKeyId, HeaderBackend}; +use sp_blockchain::HeaderBackend; use sp_consensus::Error as ConsensusError; -use sp_consensus_aura::{digests::CompatibleDigestItem, inherents::AuraInherentData, AuraApi}; +use sp_consensus_aura::{inherents::AuraInherentData, AuraApi}; use sp_consensus_slots::Slot; -use sp_core::{crypto::Pair, ExecutionContext}; +use sp_core::crypto::Pair; use sp_inherents::{CreateInherentDataProviders, InherentDataProvider as _}; use sp_runtime::{ - generic::BlockId, traits::{Block as BlockT, Header, NumberFor}, DigestItem, }; @@ -55,7 +54,7 @@ use std::{fmt::Debug, hash::Hash, marker::PhantomData, sync::Arc}; fn check_header( client: &C, slot_now: Slot, - mut header: B::Header, + header: B::Header, hash: B::Hash, authorities: &[AuthorityId

], check_for_equivocation: CheckForEquivocation, @@ -65,27 +64,16 @@ where C: sc_client_api::backend::AuxStore, P::Public: Encode + Decode + PartialEq + Clone, { - let seal = header.digest_mut().pop().ok_or(Error::HeaderUnsealed(hash))?; - - let sig = seal.as_aura_seal().ok_or_else(|| aura_err(Error::HeaderBadSeal(hash)))?; - - let slot = find_pre_digest::(&header)?; - - if slot > slot_now { - header.digest_mut().push(seal); - Ok(CheckedHeader::Deferred(header, slot)) - } else { - // check the signature is valid under the expected authority and - // chain state. - let expected_author = - slot_author::

(slot, authorities).ok_or(Error::SlotAuthorNotFound)?; - - let pre_hash = header.hash(); - - if P::verify(&sig, pre_hash.as_ref(), expected_author) { - if check_for_equivocation.check_for_equivocation() { + let check_result = + crate::standalone::check_header_slot_and_seal::(slot_now, header, authorities); + + match check_result { + Ok((header, slot, seal)) => { + let expected_author = crate::standalone::slot_author::

(slot, &authorities); + let should_equiv_check = check_for_equivocation.check_for_equivocation(); + if let (true, Some(expected)) = (should_equiv_check, expected_author) { if let Some(equivocation_proof) = - check_equivocation(client, slot_now, slot, &header, expected_author) + check_equivocation(client, slot_now, slot, &header, expected) .map_err(Error::Client)? { info!( @@ -99,9 +87,14 @@ where } Ok(CheckedHeader::Checked(header, (slot, seal))) - } else { - Err(Error::BadSignature(hash)) - } + }, + Err(SealVerificationError::Deferred(header, slot)) => + Ok(CheckedHeader::Deferred(header, slot)), + Err(SealVerificationError::Unsealed) => Err(Error::HeaderUnsealed(hash)), + Err(SealVerificationError::BadSeal) => Err(Error::HeaderBadSeal(hash)), + Err(SealVerificationError::BadSignature) => Err(Error::BadSignature(hash)), + Err(SealVerificationError::SlotAuthorNotFound) => Err(Error::SlotAuthorNotFound), + Err(SealVerificationError::InvalidPreDigest(e)) => Err(Error::from(e)), } } @@ -142,10 +135,9 @@ where async fn check_inherents( &self, block: B, - block_id: BlockId, + at_hash: B::Hash, inherent_data: sp_inherents::InherentData, create_inherent_data_providers: CIDP::InherentDataProviders, - execution_context: ExecutionContext, ) -> Result<(), Error> where C: ProvideRuntimeApi, @@ -155,7 +147,7 @@ where let inherent_res = self .client .runtime_api() - .check_inherents_with_context(&block_id, execution_context, block, inherent_data) + .check_inherents(at_hash, block, inherent_data) .map_err(|e| Error::Client(e.into()))?; if !inherent_res.ok() { @@ -185,18 +177,17 @@ where async fn verify( &mut self, mut block: BlockImportParams, - ) -> Result<(BlockImportParams, Option)>>), String> { - // When importing whole state we don't verify the seal as the state is not available. - if block.with_state() { - return Ok((block, Default::default())) - } - - // Skip checks that include execution, if being told so. + ) -> Result, String> { + // Skip checks that include execution, if being told so or when importing only state. // // This is done for example when gap syncing and it is expected that the block after the gap // was checked/chosen properly, e.g. by warp syncing to this block using a finality proof. - if block.state_action.skip_execution_checks() { - return Ok((block, Default::default())) + // Or when we are importing state only and can not verify the seal. + if block.with_state() || block.state_action.skip_execution_checks() { + // When we are importing only the state of a block, it will be the best block. + block.fork_choice = Some(ForkChoiceStrategy::Custom(block.with_state())); + + return Ok(block) } let hash = block.header.hash(); @@ -249,18 +240,14 @@ where if self .client .runtime_api() - .has_api_with::, _>( - &BlockId::Hash(parent_hash), - |v| v >= 2, - ) + .has_api_with::, _>(parent_hash, |v| v >= 2) .map_err(|e| e.to_string())? { self.check_inherents( new_block.clone(), - BlockId::Hash(parent_hash), + parent_hash, inherent_data, create_inherent_data_providers, - block.origin.into(), ) .await .map_err(|e| e.to_string())?; @@ -283,7 +270,7 @@ where block.fork_choice = Some(ForkChoiceStrategy::LongestChain); block.post_hash = Some(hash); - Ok((block, None)) + Ok(block) }, CheckedHeader::Deferred(a, b) => { debug!(target: LOG_TARGET, "Checking {:?} failed; {:?}, {:?}.", hash, a, b); diff --git a/client/consensus/aura/src/lib.rs b/client/consensus/aura/src/lib.rs index 6ef6bdc2ed1f..1dc364283d5b 100644 --- a/client/consensus/aura/src/lib.rs +++ b/client/consensus/aura/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -33,11 +33,10 @@ use std::{fmt::Debug, hash::Hash, marker::PhantomData, pin::Pin, sync::Arc}; use futures::prelude::*; -use log::{debug, trace}; use codec::{Codec, Decode, Encode}; -use sc_client_api::{backend::AuxStore, BlockOf, UsageProvider}; +use sc_client_api::{backend::AuxStore, BlockOf}; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy, StateAction}; use sc_consensus_slots::{ BackoffAuthoringBlocksStrategy, InherentDataProviderExt, SimpleSlotWorkerToSlotWorker, @@ -45,21 +44,19 @@ use sc_consensus_slots::{ }; use sc_telemetry::TelemetryHandle; use sp_api::{Core, ProvideRuntimeApi}; -use sp_application_crypto::{AppKey, AppPublic}; -use sp_blockchain::{HeaderBackend, Result as CResult}; +use sp_application_crypto::AppPublic; +use sp_blockchain::HeaderBackend; use sp_consensus::{BlockOrigin, Environment, Error as ConsensusError, Proposer, SelectChain}; use sp_consensus_slots::Slot; -use sp_core::crypto::{ByteArray, Pair, Public}; +use sp_core::crypto::{Pair, Public}; use sp_inherents::CreateInherentDataProviders; -use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, Header, Member, NumberFor, Zero}, - DigestItem, -}; +use sp_keystore::KeystorePtr; +use sp_runtime::traits::{Block as BlockT, Header, Member, NumberFor}; mod import_queue; +pub mod standalone; +pub use crate::standalone::{find_pre_digest, slot_duration}; pub use import_queue::{ build_verifier, import_queue, AuraVerifier, BuildVerifierParams, CheckForEquivocation, ImportQueueParams, @@ -113,37 +110,6 @@ impl Default for CompatibilityMode { } } -/// Get the slot duration for Aura. -pub fn slot_duration(client: &C) -> CResult -where - A: Codec, - B: BlockT, - C: AuxStore + ProvideRuntimeApi + UsageProvider, - C::Api: AuraApi, -{ - let best_block_id = BlockId::Hash(client.usage_info().chain.best_hash); - client.runtime_api().slot_duration(&best_block_id).map_err(|err| err.into()) -} - -/// Get slot author for given block along with authorities. -fn slot_author(slot: Slot, authorities: &[AuthorityId

]) -> Option<&AuthorityId

> { - if authorities.is_empty() { - return None - } - - let idx = *slot % (authorities.len() as u64); - assert!( - idx <= usize::MAX as u64, - "It is impossible to have a vector with length beyond the address space; qed", - ); - - let current_author = authorities.get(idx as usize).expect( - "authorities not empty; index constrained to list length;this is a valid index; qed", - ); - - Some(current_author) -} - /// Parameters of [`start_aura`]. pub struct StartAuraParams { /// The duration of a slot. @@ -167,7 +133,7 @@ pub struct StartAuraParams { /// The backoff strategy when we miss slots. pub backoff_authoring_blocks: Option, /// The keystore used by the node. - pub keystore: SyncCryptoStorePtr, + pub keystore: KeystorePtr, /// The proportion of the slot dedicated to proposing. /// /// The block proposing will be limited to this proportion of the slot from the starting of the @@ -204,7 +170,7 @@ pub fn start_aura( telemetry, compatibility_mode, }: StartAuraParams>, -) -> Result, sp_consensus::Error> +) -> Result, ConsensusError> where P: Pair + Send + Sync, P::Public: AppPublic + Hash + Member + Encode + Decode, @@ -221,7 +187,7 @@ where CIDP: CreateInherentDataProviders + Send + 'static, CIDP::InherentDataProviders: InherentDataProviderExt + Send, BS: BackoffAuthoringBlocksStrategy> + Send + Sync + 'static, - Error: std::error::Error + Send + From + 'static, + Error: std::error::Error + Send + From + 'static, { let worker = build_aura_worker::(BuildAuraWorkerParams { client, @@ -264,7 +230,7 @@ pub struct BuildAuraWorkerParams { /// The backoff strategy when we miss slots. pub backoff_authoring_blocks: Option, /// The keystore used by the node. - pub keystore: SyncCryptoStorePtr, + pub keystore: KeystorePtr, /// The proportion of the slot dedicated to proposing. /// /// The block proposing will be limited to this proportion of the slot from the starting of the @@ -319,7 +285,7 @@ where P::Public: AppPublic + Hash + Member + Encode + Decode, P::Signature: TryFrom> + Hash + Member + Encode + Decode, I: BlockImport> + Send + Sync + 'static, - Error: std::error::Error + Send + From + 'static, + Error: std::error::Error + Send + From + 'static, SO: SyncOracle + Send + Sync + Clone, L: sc_consensus::JustificationSyncLink, BS: BackoffAuthoringBlocksStrategy> + Send + Sync + 'static, @@ -345,7 +311,7 @@ struct AuraWorker { client: Arc, block_import: I, env: E, - keystore: SyncCryptoStorePtr, + keystore: KeystorePtr, sync_oracle: SO, justification_sync_link: L, force_authoring: bool, @@ -373,13 +339,13 @@ where SO: SyncOracle + Send + Clone + Sync, L: sc_consensus::JustificationSyncLink, BS: BackoffAuthoringBlocksStrategy> + Send + Sync + 'static, - Error: std::error::Error + Send + From + 'static, + Error: std::error::Error + Send + From + 'static, { type BlockImport = I; type SyncOracle = SO; type JustificationSyncLink = L; type CreateProposer = - Pin> + Send + 'static>>; + Pin> + Send + 'static>>; type Proposer = E::Proposer; type Claim = P::Public; type AuxData = Vec>; @@ -392,11 +358,7 @@ where &mut self.block_import } - fn aux_data( - &self, - header: &B::Header, - _slot: Slot, - ) -> Result { + fn aux_data(&self, header: &B::Header, _slot: Slot) -> Result { authorities( self.client.as_ref(), header.hash(), @@ -405,31 +367,21 @@ where ) } - fn authorities_len(&self, epoch_data: &Self::AuxData) -> Option { - Some(epoch_data.len()) + fn authorities_len(&self, authorities: &Self::AuxData) -> Option { + Some(authorities.len()) } async fn claim_slot( &self, _header: &B::Header, slot: Slot, - epoch_data: &Self::AuxData, + authorities: &Self::AuxData, ) -> Option { - let expected_author = slot_author::

(slot, epoch_data); - expected_author.and_then(|p| { - if SyncCryptoStore::has_keys( - &*self.keystore, - &[(p.to_raw_vec(), sp_application_crypto::key_types::AURA)], - ) { - Some(p.clone()) - } else { - None - } - }) + crate::standalone::claim_slot::

(slot, authorities, &self.keystore).await } fn pre_digest_data(&self, slot: Slot, _claim: &Self::Claim) -> Vec { - vec![>::aura_pre_digest(slot)] + vec![crate::standalone::pre_digest::

(slot)] } async fn block_import_params( @@ -439,35 +391,13 @@ where body: Vec, storage_changes: StorageChanges<>::Transaction, B>, public: Self::Claim, - _epoch: Self::AuxData, + _authorities: Self::AuxData, ) -> Result< sc_consensus::BlockImportParams>::Transaction>, - sp_consensus::Error, + ConsensusError, > { - // sign the pre-sealed hash of the block and then - // add it to a digest item. - let public_type_pair = public.to_public_crypto_pair(); - let public = public.to_raw_vec(); - let signature = SyncCryptoStore::sign_with( - &*self.keystore, - as AppKey>::ID, - &public_type_pair, - header_hash.as_ref(), - ) - .map_err(|e| sp_consensus::Error::CannotSign(public.clone(), e.to_string()))? - .ok_or_else(|| { - sp_consensus::Error::CannotSign( - public.clone(), - "Could not find key in keystore.".into(), - ) - })?; - let signature = signature - .clone() - .try_into() - .map_err(|_| sp_consensus::Error::InvalidSignature(signature, public))?; - let signature_digest_item = - >::aura_seal(signature); + crate::standalone::seal::<_, P>(header_hash, &public, &self.keystore)?; let mut import_block = BlockImportParams::new(BlockOrigin::Own, header); import_block.post_digests.push(signature_digest_item); @@ -509,7 +439,7 @@ where fn proposer(&mut self, block: &B::Header) -> Self::CreateProposer { self.env .init(block) - .map_err(|e| sp_consensus::Error::ClientImport(format!("{:?}", e))) + .map_err(|e| ConsensusError::ClientImport(format!("{:?}", e))) .boxed() } @@ -531,11 +461,6 @@ where } } -fn aura_err(error: Error) -> Error { - debug!(target: LOG_TARGET, "{}", error); - error -} - /// Aura Errors #[derive(Debug, thiserror::Error)] pub enum Error { @@ -574,22 +499,13 @@ impl From> for String { } } -/// Get pre-digests from the header -pub fn find_pre_digest(header: &B::Header) -> Result> { - if header.number().is_zero() { - return Ok(0.into()) - } - - let mut pre_digest: Option = None; - for log in header.digest().logs() { - trace!(target: LOG_TARGET, "Checking log {:?}", log); - match (CompatibleDigestItem::::as_aura_pre_digest(log), pre_digest.is_some()) { - (Some(_), true) => return Err(aura_err(Error::MultipleHeaders)), - (None, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"), - (s, false) => pre_digest = s, +impl From for Error { + fn from(e: crate::standalone::PreDigestLookupError) -> Self { + match e { + crate::standalone::PreDigestLookupError::MultipleHeaders => Error::MultipleHeaders, + crate::standalone::PreDigestLookupError::NoDigestFound => Error::NoDigestFound, } } - pre_digest.ok_or_else(|| aura_err(Error::NoDigestFound)) } fn authorities( @@ -613,7 +529,7 @@ where if *until > context_block_number { runtime_api .initialize_block( - &BlockId::Hash(parent_hash), + parent_hash, &B::Header::new( context_block_number, Default::default(), @@ -622,14 +538,14 @@ where Default::default(), ), ) - .map_err(|_| sp_consensus::Error::InvalidAuthoritiesSet)?; + .map_err(|_| ConsensusError::InvalidAuthoritiesSet)?; }, } runtime_api - .authorities(&BlockId::Hash(parent_hash)) + .authorities(parent_hash) .ok() - .ok_or(sp_consensus::Error::InvalidAuthoritiesSet) + .ok_or(ConsensusError::InvalidAuthoritiesSet) } #[cfg(test)] @@ -642,11 +558,12 @@ mod tests { use sc_consensus_slots::{BackoffAuthoringOnFinalizedHeadLagging, SimpleSlotWorker}; use sc_keystore::LocalKeystore; use sc_network_test::{Block as TestBlock, *}; - use sp_application_crypto::key_types::AURA; + use sp_application_crypto::{key_types::AURA, AppCrypto}; use sp_consensus::{DisableProofRecording, NoNetwork as DummyOracle, Proposal}; use sp_consensus_aura::sr25519::AuthorityPair; use sp_inherents::InherentData; use sp_keyring::sr25519::Keyring; + use sp_keystore::Keystore; use sp_runtime::{ traits::{Block as BlockT, Header as _}, Digest, @@ -765,6 +682,11 @@ mod tests { fn peers(&self) -> &Vec { &self.peers } + + fn peers_mut(&mut self) -> &mut Vec { + &mut self.peers + } + fn mut_peers)>(&mut self, closure: F) { closure(&mut self.peers); } @@ -792,7 +714,8 @@ mod tests { LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore."), ); - SyncCryptoStore::sr25519_generate_new(&*keystore, AURA, Some(&key.to_seed())) + keystore + .sr25519_generate_new(AURA, Some(&key.to_seed())) .expect("Creates authority key"); keystore_paths.push(keystore_path); @@ -849,22 +772,6 @@ mod tests { .await; } - #[test] - fn authorities_call_works() { - let client = substrate_test_runtime_client::new(); - - assert_eq!(client.chain_info().best_number, 0); - assert_eq!( - authorities(&client, client.chain_info().best_hash, 1, &CompatibilityMode::None) - .unwrap(), - vec![ - Keyring::Alice.public().into(), - Keyring::Bob.public().into(), - Keyring::Charlie.public().into() - ] - ); - } - #[tokio::test] async fn current_node_authority_should_claim_slot() { let net = AuraTestNet::new(4); @@ -877,7 +784,8 @@ mod tests { let keystore_path = tempfile::tempdir().expect("Creates keystore path"); let keystore = LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore."); - let public = SyncCryptoStore::sr25519_generate_new(&keystore, AuthorityPair::ID, None) + let public = keystore + .sr25519_generate_new(AuthorityPair::ID, None) .expect("Key should be created"); authorities.push(public.into()); @@ -927,12 +835,9 @@ mod tests { let keystore_path = tempfile::tempdir().expect("Creates keystore path"); let keystore = LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore."); - SyncCryptoStore::sr25519_generate_new( - &keystore, - AuthorityPair::ID, - Some(&Keyring::Alice.to_seed()), - ) - .expect("Key should be created"); + keystore + .sr25519_generate_new(AuthorityPair::ID, Some(&Keyring::Alice.to_seed())) + .expect("Key should be created"); let net = Arc::new(Mutex::new(net)); diff --git a/client/consensus/aura/src/standalone.rs b/client/consensus/aura/src/standalone.rs new file mode 100644 index 000000000000..0f9b8668d447 --- /dev/null +++ b/client/consensus/aura/src/standalone.rs @@ -0,0 +1,357 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +//! Standalone functions used within the implementation of Aura. + +use std::fmt::Debug; + +use log::trace; + +use codec::Codec; + +use sc_client_api::{backend::AuxStore, UsageProvider}; +use sp_api::{Core, ProvideRuntimeApi}; +use sp_application_crypto::{AppCrypto, AppPublic}; +use sp_blockchain::Result as CResult; +use sp_consensus::Error as ConsensusError; +use sp_consensus_slots::Slot; +use sp_core::crypto::{ByteArray, Pair}; +use sp_keystore::KeystorePtr; +use sp_runtime::{ + traits::{Block as BlockT, Header, NumberFor, Zero}, + DigestItem, +}; + +pub use sc_consensus_slots::check_equivocation; + +use super::{ + AuraApi, AuthorityId, CompatibilityMode, CompatibleDigestItem, SlotDuration, LOG_TARGET, +}; + +/// Get the slot duration for Aura by reading from a runtime API at the best block's state. +pub fn slot_duration(client: &C) -> CResult +where + A: Codec, + B: BlockT, + C: AuxStore + ProvideRuntimeApi + UsageProvider, + C::Api: AuraApi, +{ + slot_duration_at(client, client.usage_info().chain.best_hash) +} + +/// Get the slot duration for Aura by reading from a runtime API at a given block's state. +pub fn slot_duration_at(client: &C, block_hash: B::Hash) -> CResult +where + A: Codec, + B: BlockT, + C: AuxStore + ProvideRuntimeApi, + C::Api: AuraApi, +{ + client.runtime_api().slot_duration(block_hash).map_err(|err| err.into()) +} + +/// Get the slot author for given block along with authorities. +pub fn slot_author(slot: Slot, authorities: &[AuthorityId

]) -> Option<&AuthorityId

> { + if authorities.is_empty() { + return None + } + + let idx = *slot % (authorities.len() as u64); + assert!( + idx <= usize::MAX as u64, + "It is impossible to have a vector with length beyond the address space; qed", + ); + + let current_author = authorities.get(idx as usize).expect( + "authorities not empty; index constrained to list length;this is a valid index; qed", + ); + + Some(current_author) +} + +/// Attempt to claim a slot using a keystore. +/// +/// This returns `None` if the slot author is not locally controlled, and `Some` if it is, +/// with the public key of the slot author. +pub async fn claim_slot( + slot: Slot, + authorities: &[AuthorityId

], + keystore: &KeystorePtr, +) -> Option { + let expected_author = slot_author::

(slot, authorities); + expected_author.and_then(|p| { + if keystore.has_keys(&[(p.to_raw_vec(), sp_application_crypto::key_types::AURA)]) { + Some(p.clone()) + } else { + None + } + }) +} + +/// Produce the pre-runtime digest containing the slot info. +/// +/// This is intended to be put into the block header prior to runtime execution, +/// so the runtime can read the slot in this way. +pub fn pre_digest(slot: Slot) -> sp_runtime::DigestItem +where + P::Signature: Codec, +{ + >::aura_pre_digest(slot) +} + +/// Produce the seal digest item by signing the hash of a block. +/// +/// Note that after this is added to a block header, the hash of the block will change. +pub fn seal( + header_hash: &Hash, + public: &P::Public, + keystore: &KeystorePtr, +) -> Result +where + Hash: AsRef<[u8]>, + P: Pair, + P::Signature: Codec + TryFrom>, + P::Public: AppPublic, +{ + let signature = keystore + .sign_with( + as AppCrypto>::ID, + as AppCrypto>::CRYPTO_ID, + public.as_slice(), + header_hash.as_ref(), + ) + .map_err(|e| ConsensusError::CannotSign(format!("{}. Key: {:?}", e, public)))? + .ok_or_else(|| { + ConsensusError::CannotSign(format!("Could not find key in keystore. Key: {:?}", public)) + })?; + + let signature = signature + .clone() + .try_into() + .map_err(|_| ConsensusError::InvalidSignature(signature, public.to_raw_vec()))?; + + let signature_digest_item = + >::aura_seal(signature); + + Ok(signature_digest_item) +} + +/// Errors in pre-digest lookup. +#[derive(Debug, thiserror::Error)] +pub enum PreDigestLookupError { + /// Multiple Aura pre-runtime headers + #[error("Multiple Aura pre-runtime headers")] + MultipleHeaders, + /// No Aura pre-runtime digest found + #[error("No Aura pre-runtime digest found")] + NoDigestFound, +} + +/// Extract a pre-digest from a block header. +/// +/// This fails if there is no pre-digest or there are multiple. +/// +/// Returns the `slot` stored in the pre-digest or an error if no pre-digest was found. +pub fn find_pre_digest( + header: &B::Header, +) -> Result { + if header.number().is_zero() { + return Ok(0.into()) + } + + let mut pre_digest: Option = None; + for log in header.digest().logs() { + trace!(target: LOG_TARGET, "Checking log {:?}", log); + match (CompatibleDigestItem::::as_aura_pre_digest(log), pre_digest.is_some()) { + (Some(_), true) => return Err(PreDigestLookupError::MultipleHeaders), + (None, _) => trace!(target: LOG_TARGET, "Ignoring digest not meant for us"), + (s, false) => pre_digest = s, + } + } + pre_digest.ok_or_else(|| PreDigestLookupError::NoDigestFound) +} + +/// Fetch the current set of authorities from the runtime at a specific block. +/// +/// The compatibility mode and context block number informs this function whether +/// to initialize the hypothetical block created by the runtime API as backwards compatibility +/// for older chains. +pub fn fetch_authorities_with_compatibility_mode( + client: &C, + parent_hash: B::Hash, + context_block_number: NumberFor, + compatibility_mode: &CompatibilityMode>, +) -> Result, ConsensusError> +where + A: Codec + Debug, + B: BlockT, + C: ProvideRuntimeApi, + C::Api: AuraApi, +{ + let runtime_api = client.runtime_api(); + + match compatibility_mode { + CompatibilityMode::None => {}, + // Use `initialize_block` until we hit the block that should disable the mode. + CompatibilityMode::UseInitializeBlock { until } => + if *until > context_block_number { + runtime_api + .initialize_block( + parent_hash, + &B::Header::new( + context_block_number, + Default::default(), + Default::default(), + parent_hash, + Default::default(), + ), + ) + .map_err(|_| ConsensusError::InvalidAuthoritiesSet)?; + }, + } + + runtime_api + .authorities(parent_hash) + .ok() + .ok_or(ConsensusError::InvalidAuthoritiesSet) +} + +/// Load the current set of authorities from a runtime at a specific block. +pub fn fetch_authorities( + client: &C, + parent_hash: B::Hash, +) -> Result, ConsensusError> +where + A: Codec + Debug, + B: BlockT, + C: ProvideRuntimeApi, + C::Api: AuraApi, +{ + client + .runtime_api() + .authorities(parent_hash) + .ok() + .ok_or(ConsensusError::InvalidAuthoritiesSet) +} + +/// Errors in slot and seal verification. +#[derive(Debug, thiserror::Error)] +pub enum SealVerificationError

{ + /// Header is deferred to the future. + #[error("Header slot is in the future")] + Deferred(Header, Slot), + + /// The header has no seal digest. + #[error("Header is unsealed.")] + Unsealed, + + /// The header has a malformed seal. + #[error("Header has a malformed seal")] + BadSeal, + + /// The header has a bad signature. + #[error("Header has a bad signature")] + BadSignature, + + /// No slot author found. + #[error("No slot author for provided slot")] + SlotAuthorNotFound, + + /// Header has no valid slot pre-digest. + #[error("Header has no valid slot pre-digest")] + InvalidPreDigest(PreDigestLookupError), +} + +/// Check a header has been signed by the right key. If the slot is too far in the future, an error +/// will be returned. If it's successful, returns the pre-header (i.e. without the seal), +/// the slot, and the digest item containing the seal. +/// +/// Note that this does not check for equivocations, and [`check_equivocation`] is recommended +/// for that purpose. +/// +/// This digest item will always return `Some` when used with `as_aura_seal`. +pub fn check_header_slot_and_seal( + slot_now: Slot, + mut header: B::Header, + authorities: &[AuthorityId

], +) -> Result<(B::Header, Slot, DigestItem), SealVerificationError> +where + P::Signature: Codec, + P::Public: Codec + PartialEq + Clone, +{ + let seal = header.digest_mut().pop().ok_or(SealVerificationError::Unsealed)?; + + let sig = seal.as_aura_seal().ok_or(SealVerificationError::BadSeal)?; + + let slot = find_pre_digest::(&header) + .map_err(SealVerificationError::InvalidPreDigest)?; + + if slot > slot_now { + header.digest_mut().push(seal); + return Err(SealVerificationError::Deferred(header, slot)) + } else { + // check the signature is valid under the expected authority and + // chain state. + let expected_author = + slot_author::

(slot, authorities).ok_or(SealVerificationError::SlotAuthorNotFound)?; + + let pre_hash = header.hash(); + + if P::verify(&sig, pre_hash.as_ref(), expected_author) { + Ok((header, slot, seal)) + } else { + Err(SealVerificationError::BadSignature) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sp_keyring::sr25519::Keyring; + + #[test] + fn authorities_call_works() { + let client = substrate_test_runtime_client::new(); + + assert_eq!(client.chain_info().best_number, 0); + assert_eq!( + fetch_authorities_with_compatibility_mode( + &client, + client.chain_info().best_hash, + 1, + &CompatibilityMode::None + ) + .unwrap(), + vec![ + Keyring::Alice.public().into(), + Keyring::Bob.public().into(), + Keyring::Charlie.public().into() + ] + ); + + assert_eq!( + fetch_authorities(&client, client.chain_info().best_hash).unwrap(), + vec![ + Keyring::Alice.public().into(), + Keyring::Bob.public().into(), + Keyring::Charlie.public().into() + ] + ); + } +} diff --git a/client/consensus/babe/Cargo.toml b/client/consensus/babe/Cargo.toml index f0ff0080fd61..e036ff1e64cd 100644 --- a/client/consensus/babe/Cargo.toml +++ b/client/consensus/babe/Cargo.toml @@ -15,15 +15,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = "0.1.57" -codec = { package = "parity-scale-codec", version = "3.2.2", features = ["derive"] } +scale-info = { version = "2.5.0", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" log = "0.4.17" -merlin = "2.0" num-bigint = "0.4.3" num-rational = "0.4.1" num-traits = "0.2.8" parking_lot = "0.12.1" -schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated"] } thiserror = "1.0" fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } @@ -31,28 +30,27 @@ sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } sc-consensus-epochs = { version = "0.10.0-dev", path = "../epochs" } sc-consensus-slots = { version = "0.10.0-dev", path = "../slots" } -sc-keystore = { version = "4.0.0-dev", path = "../../keystore" } sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } +sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../transaction-pool/api" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } -sp-application-crypto = { version = "7.0.0", path = "../../../primitives/application-crypto" } +sp-application-crypto = { version = "23.0.0", path = "../../../primitives/application-crypto" } sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-builder" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } -sp-consensus-vrf = { version = "0.10.0-dev", path = "../../../primitives/consensus/vrf" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-keystore = { version = "0.27.0", path = "../../../primitives/keystore" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } [dev-dependencies] rand_chacha = "0.2.2" sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } -sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } +sp-keyring = { version = "24.0.0", path = "../../../primitives/keyring" } sc-network = { version = "0.10.0-dev", path = "../../network" } sc-network-test = { version = "0.8.0", path = "../../network/test" } sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } -sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } +sp-tracing = { version = "10.0.0", path = "../../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } tokio = "1.22.0" diff --git a/client/consensus/babe/rpc/Cargo.toml b/client/consensus/babe/rpc/Cargo.toml index 4f5aaf85494b..7b16ea84c436 100644 --- a/client/consensus/babe/rpc/Cargo.toml +++ b/client/consensus/babe/rpc/Cargo.toml @@ -15,25 +15,25 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } futures = "0.3.21" -serde = { version = "1.0.136", features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"] } thiserror = "1.0" sc-consensus-babe = { version = "0.10.0-dev", path = "../" } sc-consensus-epochs = { version = "0.10.0-dev", path = "../../epochs" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../rpc-api" } sp-api = { version = "4.0.0-dev", path = "../../../../primitives/api" } -sp-application-crypto = { version = "7.0.0", path = "../../../../primitives/application-crypto" } +sp-application-crypto = { version = "23.0.0", path = "../../../../primitives/application-crypto" } sp-blockchain = { version = "4.0.0-dev", path = "../../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../../primitives/consensus/common" } sp-consensus-babe = { version = "0.10.0-dev", path = "../../../../primitives/consensus/babe" } -sp-core = { version = "7.0.0", path = "../../../../primitives/core" } -sp-keystore = { version = "0.13.0", path = "../../../../primitives/keystore" } -sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } +sp-core = { version = "21.0.0", path = "../../../../primitives/core" } +sp-keystore = { version = "0.27.0", path = "../../../../primitives/keystore" } +sp-runtime = { version = "24.0.0", path = "../../../../primitives/runtime" } [dev-dependencies] serde_json = "1.0.85" -tempfile = "3.1.0" tokio = "1.22.0" sc-consensus = { version = "0.10.0-dev", path = "../../../consensus/common" } sc-keystore = { version = "4.0.0-dev", path = "../../../keystore" } -sp-keyring = { version = "7.0.0", path = "../../../../primitives/keyring" } +sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../transaction-pool/api" } +sp-keyring = { version = "24.0.0", path = "../../../../primitives/keyring" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../../test-utils/runtime/client" } diff --git a/client/consensus/babe/rpc/src/lib.rs b/client/consensus/babe/rpc/src/lib.rs index 288f852a5c98..bffe026ea6ef 100644 --- a/client/consensus/babe/rpc/src/lib.rs +++ b/client/consensus/babe/rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,28 +18,29 @@ //! RPC api for babe. +use std::{collections::HashMap, sync::Arc}; + use futures::TryFutureExt; use jsonrpsee::{ core::{async_trait, Error as JsonRpseeError, RpcResult}, proc_macros::rpc, types::{error::CallError, ErrorObject}, }; +use serde::{Deserialize, Serialize}; -use sc_consensus_babe::{authorship, Epoch}; -use sc_consensus_epochs::{descendent_query, Epoch as EpochT, SharedEpochChanges}; +use sc_consensus_babe::{authorship, BabeWorkerHandle}; +use sc_consensus_epochs::Epoch as EpochT; use sc_rpc_api::DenyUnsafe; -use serde::{Deserialize, Serialize}; -use sp_api::{BlockId, ProvideRuntimeApi}; -use sp_application_crypto::AppKey; +use sp_api::ProvideRuntimeApi; +use sp_application_crypto::AppCrypto; use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; use sp_consensus::{Error as ConsensusError, SelectChain}; -use sp_consensus_babe::{ - digests::PreDigest, AuthorityId, BabeApi as BabeRuntimeApi, BabeConfiguration, -}; +use sp_consensus_babe::{digests::PreDigest, AuthorityId, BabeApi as BabeRuntimeApi}; use sp_core::crypto::ByteArray; -use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; +use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, Header as _}; -use std::{collections::HashMap, sync::Arc}; + +const BABE_ERROR: i32 = 9000; /// Provides rpc methods for interacting with Babe. #[rpc(client, server)] @@ -54,12 +55,10 @@ pub trait BabeApi { pub struct Babe { /// shared reference to the client. client: Arc, - /// shared reference to EpochChanges - shared_epoch_changes: SharedEpochChanges, + /// A handle to the BABE worker for issuing requests. + babe_worker_handle: BabeWorkerHandle, /// shared reference to the Keystore - keystore: SyncCryptoStorePtr, - /// config (actually holds the slot duration) - babe_config: BabeConfiguration, + keystore: KeystorePtr, /// The SelectChain strategy select_chain: SC, /// Whether to deny unsafe calls @@ -70,13 +69,12 @@ impl Babe { /// Creates a new instance of the Babe Rpc handler. pub fn new( client: Arc, - shared_epoch_changes: SharedEpochChanges, - keystore: SyncCryptoStorePtr, - babe_config: BabeConfiguration, + babe_worker_handle: BabeWorkerHandle, + keystore: KeystorePtr, select_chain: SC, deny_unsafe: DenyUnsafe, ) -> Self { - Self { client, shared_epoch_changes, keystore, babe_config, select_chain, deny_unsafe } + Self { client, babe_worker_handle, keystore, select_chain, deny_unsafe } } } @@ -93,21 +91,21 @@ where { async fn epoch_authorship(&self) -> RpcResult> { self.deny_unsafe.check_if_safe()?; - let header = self.select_chain.best_chain().map_err(Error::Consensus).await?; + + let best_header = self.select_chain.best_chain().map_err(Error::SelectChain).await?; + let epoch_start = self .client .runtime_api() - .current_epoch_start(&BlockId::Hash(header.hash())) - .map_err(|err| Error::StringError(format!("{:?}", err)))?; - - let epoch = epoch_data( - &self.shared_epoch_changes, - &self.client, - &self.babe_config, - *epoch_start, - &self.select_chain, - ) - .await?; + .current_epoch_start(best_header.hash()) + .map_err(|_| Error::FetchEpoch)?; + + let epoch = self + .babe_worker_handle + .epoch_data_for_child_of(best_header.hash(), *best_header.number(), epoch_start) + .await + .map_err(|_| Error::FetchEpoch)?; + let (epoch_start, epoch_end) = (epoch.start_slot(), epoch.end_slot()); let mut claims: HashMap = HashMap::new(); @@ -117,10 +115,7 @@ where .iter() .enumerate() .filter_map(|(i, a)| { - if SyncCryptoStore::has_keys( - &*self.keystore, - &[(a.0.to_raw_vec(), AuthorityId::ID)], - ) { + if self.keystore.has_keys(&[(a.0.to_raw_vec(), AuthorityId::ID)]) { Some((a.0.clone(), i)) } else { None @@ -162,81 +157,52 @@ pub struct EpochAuthorship { secondary_vrf: Vec, } -/// Errors encountered by the RPC +/// Top-level error type for the RPC handler. #[derive(Debug, thiserror::Error)] pub enum Error { - /// Consensus error - #[error(transparent)] - Consensus(#[from] ConsensusError), - /// Errors that can be formatted as a String - #[error("{0}")] - StringError(String), + /// Failed to fetch the current best header. + #[error("Failed to fetch the current best header: {0}")] + SelectChain(ConsensusError), + /// Failed to fetch epoch data. + #[error("Failed to fetch epoch data")] + FetchEpoch, } impl From for JsonRpseeError { fn from(error: Error) -> Self { + let error_code = match error { + Error::SelectChain(_) => 1, + Error::FetchEpoch => 2, + }; + JsonRpseeError::Call(CallError::Custom(ErrorObject::owned( - 1234, + BABE_ERROR + error_code, error.to_string(), - None::<()>, + Some(format!("{:?}", error)), ))) } } -/// Fetches the epoch data for a given slot. -async fn epoch_data( - epoch_changes: &SharedEpochChanges, - client: &Arc, - babe_config: &BabeConfiguration, - slot: u64, - select_chain: &SC, -) -> Result -where - B: BlockT, - C: HeaderBackend + HeaderMetadata + 'static, - SC: SelectChain, -{ - let parent = select_chain.best_chain().await?; - epoch_changes - .shared_data() - .epoch_data_for_child_of( - descendent_query(&**client), - &parent.hash(), - *parent.number(), - slot.into(), - |slot| Epoch::genesis(babe_config, slot), - ) - .map_err(|e| Error::Consensus(ConsensusError::ChainLookup(e.to_string())))? - .ok_or(Error::Consensus(ConsensusError::InvalidAuthoritiesSet)) -} - #[cfg(test)] mod tests { use super::*; - use sc_keystore::LocalKeystore; - use sp_application_crypto::AppPair; - use sp_core::crypto::key_types::BABE; + use sc_consensus_babe::ImportQueueParams; + use sc_transaction_pool_api::{OffchainTransactionPoolFactory, RejectAllTxPool}; + use sp_consensus_babe::inherents::InherentDataProvider; + use sp_core::{crypto::key_types::BABE, testing::TaskExecutor}; use sp_keyring::Sr25519Keyring; - use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; + use sp_keystore::{testing::MemoryKeystore, Keystore}; use substrate_test_runtime_client::{ runtime::Block, Backend, DefaultTestClientBuilderExt, TestClient, TestClientBuilder, TestClientBuilderExt, }; - use sc_consensus_babe::{block_import, AuthorityPair}; - use std::sync::Arc; - - /// creates keystore backed by a temp file - fn create_temp_keystore( - authority: Sr25519Keyring, - ) -> (SyncCryptoStorePtr, tempfile::TempDir) { - let keystore_path = tempfile::tempdir().expect("Creates keystore path"); - let keystore = - Arc::new(LocalKeystore::open(keystore_path.path(), None).expect("Creates keystore")); - SyncCryptoStore::sr25519_generate_new(&*keystore, BABE, Some(&authority.to_seed())) + fn create_keystore(authority: Sr25519Keyring) -> KeystorePtr { + let keystore = MemoryKeystore::new(); + keystore + .sr25519_generate_new(BABE, Some(&authority.to_seed())) .expect("Creates authority key"); - - (keystore, keystore_path) + keystore.into() } fn test_babe_rpc_module( @@ -245,14 +211,38 @@ mod tests { let builder = TestClientBuilder::new(); let (client, longest_chain) = builder.build_with_longest_chain(); let client = Arc::new(client); - let config = sc_consensus_babe::configuration(&*client).expect("config available"); - let (_, link) = block_import(config.clone(), client.clone(), client.clone()) - .expect("can initialize block-import"); + let task_executor = TaskExecutor::new(); + let keystore = create_keystore(Sr25519Keyring::Alice); - let epoch_changes = link.epoch_changes().clone(); - let keystore = create_temp_keystore::(Sr25519Keyring::Alice).0; - - Babe::new(client.clone(), epoch_changes, keystore, config, longest_chain, deny_unsafe) + let config = sc_consensus_babe::configuration(&*client).expect("config available"); + let slot_duration = config.slot_duration(); + + let (block_import, link) = + sc_consensus_babe::block_import(config.clone(), client.clone(), client.clone()) + .expect("can initialize block-import"); + + let (_, babe_worker_handle) = sc_consensus_babe::import_queue(ImportQueueParams { + link: link.clone(), + block_import: block_import.clone(), + justification_import: None, + client: client.clone(), + select_chain: longest_chain.clone(), + create_inherent_data_providers: move |_, _| async move { + Ok((InherentDataProvider::from_timestamp_and_slot_duration( + 0.into(), + slot_duration, + ),)) + }, + spawner: &task_executor, + registry: None, + telemetry: None, + offchain_tx_pool_factory: OffchainTransactionPoolFactory::new( + RejectAllTxPool::default(), + ), + }) + .unwrap(); + + Babe::new(client.clone(), babe_worker_handle, keystore, longest_chain, deny_unsafe) } #[tokio::test] diff --git a/client/consensus/babe/src/authorship.rs b/client/consensus/babe/src/authorship.rs index 3a92aa5c6b22..758d5321a94c 100644 --- a/client/consensus/babe/src/authorship.rs +++ b/client/consensus/babe/src/authorship.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,18 +18,20 @@ //! BABE authority selection and slot claiming. -use super::Epoch; +use super::{Epoch, AUTHORING_SCORE_LENGTH, AUTHORING_SCORE_VRF_CONTEXT}; use codec::Encode; use sc_consensus_epochs::Epoch as EpochT; -use schnorrkel::{keys::PublicKey, vrf::VRFInOut}; -use sp_application_crypto::AppKey; +use sp_application_crypto::AppCrypto; use sp_consensus_babe::{ digests::{PreDigest, PrimaryPreDigest, SecondaryPlainPreDigest, SecondaryVRFPreDigest}, - make_transcript, make_transcript_data, AuthorityId, BabeAuthorityWeight, Slot, BABE_VRF_PREFIX, + make_vrf_sign_data, AuthorityId, BabeAuthorityWeight, Randomness, Slot, }; -use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; -use sp_core::{blake2_256, crypto::ByteArray, U256}; -use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; +use sp_core::{ + blake2_256, + crypto::{ByteArray, Wraps}, + U256, +}; +use sp_keystore::KeystorePtr; /// Calculates the primary selection threshold for a given authority, taking /// into account `c` (`1 - c` represents the probability of a slot being empty). @@ -95,19 +97,13 @@ pub(super) fn calculate_primary_threshold( ) } -/// Returns true if the given VRF output is lower than the given threshold, -/// false otherwise. -pub(super) fn check_primary_threshold(inout: &VRFInOut, threshold: u128) -> bool { - u128::from_le_bytes(inout.make_bytes::<[u8; 16]>(BABE_VRF_PREFIX)) < threshold -} - /// Get the expected secondary author for the given slot and with given /// authorities. This should always assign the slot to some authority unless the /// authorities list is empty. pub(super) fn secondary_slot_author( slot: Slot, authorities: &[(AuthorityId, BabeAuthorityWeight)], - randomness: [u8; 32], + randomness: Randomness, ) -> Option<&AuthorityId> { if authorities.is_empty() { return None @@ -133,7 +129,7 @@ fn claim_secondary_slot( slot: Slot, epoch: &Epoch, keys: &[(AuthorityId, usize)], - keystore: &SyncCryptoStorePtr, + keystore: &KeystorePtr, author_secondary_vrf: bool, ) -> Option<(PreDigest, AuthorityId)> { let Epoch { authorities, randomness, mut epoch_index, .. } = epoch; @@ -152,27 +148,19 @@ fn claim_secondary_slot( for (authority_id, authority_index) in keys { if authority_id == expected_author { let pre_digest = if author_secondary_vrf { - let transcript_data = make_transcript_data(randomness, slot, epoch_index); - let result = SyncCryptoStore::sr25519_vrf_sign( - &**keystore, - AuthorityId::ID, - authority_id.as_ref(), - transcript_data, - ); - if let Ok(Some(signature)) = result { + let data = make_vrf_sign_data(randomness, slot, epoch_index); + let result = + keystore.sr25519_vrf_sign(AuthorityId::ID, authority_id.as_ref(), &data); + if let Ok(Some(vrf_signature)) = result { Some(PreDigest::SecondaryVRF(SecondaryVRFPreDigest { slot, - vrf_output: VRFOutput(signature.output), - vrf_proof: VRFProof(signature.proof), authority_index: *authority_index as u32, + vrf_signature, })) } else { None } - } else if SyncCryptoStore::has_keys( - &**keystore, - &[(authority_id.to_raw_vec(), AuthorityId::ID)], - ) { + } else if keystore.has_keys(&[(authority_id.to_raw_vec(), AuthorityId::ID)]) { Some(PreDigest::SecondaryPlain(SecondaryPlainPreDigest { slot, authority_index: *authority_index as u32, @@ -197,7 +185,7 @@ fn claim_secondary_slot( pub fn claim_slot( slot: Slot, epoch: &Epoch, - keystore: &SyncCryptoStorePtr, + keystore: &KeystorePtr, ) -> Option<(PreDigest, AuthorityId)> { let authorities = epoch .authorities @@ -213,7 +201,7 @@ pub fn claim_slot( pub fn claim_slot_using_keys( slot: Slot, epoch: &Epoch, - keystore: &SyncCryptoStorePtr, + keystore: &KeystorePtr, keys: &[(AuthorityId, usize)], ) -> Option<(PreDigest, AuthorityId)> { claim_primary_slot(slot, epoch, epoch.config.c, keystore, keys).or_else(|| { @@ -241,7 +229,7 @@ fn claim_primary_slot( slot: Slot, epoch: &Epoch, c: (u64, u64), - keystore: &SyncCryptoStorePtr, + keystore: &KeystorePtr, keys: &[(AuthorityId, usize)], ) -> Option<(PreDigest, AuthorityId)> { let Epoch { authorities, randomness, mut epoch_index, .. } = epoch; @@ -251,29 +239,28 @@ fn claim_primary_slot( epoch_index = epoch.clone_for_slot(slot).epoch_index; } - for (authority_id, authority_index) in keys { - let transcript = make_transcript(randomness, slot, epoch_index); - let transcript_data = make_transcript_data(randomness, slot, epoch_index); - let result = SyncCryptoStore::sr25519_vrf_sign( - &**keystore, - AuthorityId::ID, - authority_id.as_ref(), - transcript_data, - ); - if let Ok(Some(signature)) = result { - let public = PublicKey::from_bytes(&authority_id.to_raw_vec()).ok()?; - let inout = match signature.output.attach_input_hash(&public, transcript) { - Ok(inout) => inout, - Err(_) => continue, - }; + let data = make_vrf_sign_data(randomness, slot, epoch_index); + for (authority_id, authority_index) in keys { + let result = keystore.sr25519_vrf_sign(AuthorityId::ID, authority_id.as_ref(), &data); + if let Ok(Some(vrf_signature)) = result { let threshold = calculate_primary_threshold(c, authorities, *authority_index); - if check_primary_threshold(&inout, threshold) { + + let can_claim = authority_id + .as_inner_ref() + .make_bytes::( + AUTHORING_SCORE_VRF_CONTEXT, + &data.as_ref(), + &vrf_signature.output, + ) + .map(|bytes| u128::from_le_bytes(bytes) < threshold) + .unwrap_or_default(); + + if can_claim { let pre_digest = PreDigest::Primary(PrimaryPreDigest { slot, - vrf_output: VRFOutput(signature.output), - vrf_proof: VRFProof(signature.proof), authority_index: *authority_index as u32, + vrf_signature, }); return Some((pre_digest, authority_id.clone())) @@ -287,20 +274,16 @@ fn claim_primary_slot( #[cfg(test)] mod tests { use super::*; - use sc_keystore::LocalKeystore; use sp_consensus_babe::{AllowedSlots, AuthorityId, BabeEpochConfiguration}; use sp_core::{crypto::Pair as _, sr25519::Pair}; - use std::sync::Arc; + use sp_keystore::testing::MemoryKeystore; #[test] fn claim_secondary_plain_slot_works() { - let keystore: SyncCryptoStorePtr = Arc::new(LocalKeystore::in_memory()); - let valid_public_key = SyncCryptoStore::sr25519_generate_new( - &*keystore, - AuthorityId::ID, - Some(sp_core::crypto::DEV_PHRASE), - ) - .unwrap(); + let keystore: KeystorePtr = MemoryKeystore::new().into(); + let valid_public_key = keystore + .sr25519_generate_new(AuthorityId::ID, Some(sp_core::crypto::DEV_PHRASE)) + .unwrap(); let authorities = vec![ (AuthorityId::from(Pair::generate().0.public()), 5), diff --git a/client/consensus/babe/src/aux_schema.rs b/client/consensus/babe/src/aux_schema.rs index 2a09aa738f4e..a87b7c9a0d03 100644 --- a/client/consensus/babe/src/aux_schema.rs +++ b/client/consensus/babe/src/aux_schema.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/consensus/babe/src/lib.rs b/client/consensus/babe/src/lib.rs index 5ba523665b4e..76bd670c2004 100644 --- a/client/consensus/babe/src/lib.rs +++ b/client/consensus/babe/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -67,7 +67,7 @@ #![warn(missing_docs)] use std::{ - collections::{HashMap, HashSet}, + collections::HashSet, future::Future, pin::Pin, sync::Arc, @@ -86,11 +86,10 @@ use futures::{ use log::{debug, info, log, trace, warn}; use parking_lot::Mutex; use prometheus_endpoint::Registry; -use schnorrkel::SignatureError; use sc_client_api::{ - backend::AuxStore, AuxDataOperations, Backend as BackendT, BlockchainEvents, - FinalityNotification, PreCommitActions, ProvideUncles, UsageProvider, + backend::AuxStore, AuxDataOperations, Backend as BackendT, FinalityNotification, + PreCommitActions, UsageProvider, }; use sc_consensus::{ block_import::{ @@ -107,23 +106,22 @@ use sc_consensus_slots::{ SlotInfo, StorageChanges, }; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_TRACE}; +use sc_transaction_pool_api::OffchainTransactionPoolFactory; use sp_api::{ApiExt, ProvideRuntimeApi}; -use sp_application_crypto::AppKey; +use sp_application_crypto::AppCrypto; use sp_block_builder::BlockBuilder as BlockBuilderApi; use sp_blockchain::{ Backend as _, BlockStatus, Error as ClientError, ForkBackend, HeaderBackend, HeaderMetadata, Result as ClientResult, }; -use sp_consensus::{ - BlockOrigin, CacheKeyId, Environment, Error as ConsensusError, Proposer, SelectChain, -}; +use sp_consensus::{BlockOrigin, Environment, Error as ConsensusError, Proposer, SelectChain}; use sp_consensus_babe::inherents::BabeInherentData; use sp_consensus_slots::Slot; -use sp_core::{crypto::ByteArray, ExecutionContext}; +use sp_core::traits::SpawnEssentialNamed; use sp_inherents::{CreateInherentDataProviders, InherentData, InherentDataProvider}; -use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; +use sp_keystore::KeystorePtr; use sp_runtime::{ - generic::{BlockId, OpaqueDigestItemId}, + generic::OpaqueDigestItemId, traits::{Block as BlockT, Header, NumberFor, SaturatedConversion, Zero}, DigestItem, }; @@ -136,7 +134,7 @@ pub use sp_consensus_babe::{ PrimaryPreDigest, SecondaryPlainPreDigest, }, AuthorityId, AuthorityPair, AuthoritySignature, BabeApi, BabeAuthorityWeight, BabeBlockWeight, - BabeConfiguration, BabeEpochConfiguration, ConsensusLog, BABE_ENGINE_ID, VRF_OUTPUT_LENGTH, + BabeConfiguration, BabeEpochConfiguration, ConsensusLog, Randomness, BABE_ENGINE_ID, }; pub use aux_schema::load_block_weight as block_weight; @@ -151,8 +149,14 @@ mod tests; const LOG_TARGET: &str = "babe"; +/// VRF context used for slots claiming lottery. +const AUTHORING_SCORE_VRF_CONTEXT: &[u8] = b"substrate-babe-vrf"; + +/// VRF output length for slots claiming lottery. +const AUTHORING_SCORE_LENGTH: usize = 16; + /// BABE epoch information -#[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] +#[derive(Decode, Encode, PartialEq, Eq, Clone, Debug, scale_info::TypeInfo)] pub struct Epoch { /// The epoch index. pub epoch_index: u64, @@ -163,7 +167,7 @@ pub struct Epoch { /// The authorities and their weights. pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, /// Randomness for this epoch. - pub randomness: [u8; VRF_OUTPUT_LENGTH], + pub randomness: Randomness, /// Configuration of the epoch. pub config: BabeEpochConfiguration, } @@ -276,7 +280,7 @@ pub enum Error { MultipleConfigChangeDigests, /// Could not extract timestamp and slot #[error("Could not extract timestamp and slot: {0}")] - Extraction(sp_consensus::Error), + Extraction(ConsensusError), /// Could not fetch epoch #[error("Could not fetch epoch at {0:?}")] FetchEpoch(B::Hash), @@ -310,12 +314,12 @@ pub enum Error { /// No secondary author expected. #[error("No secondary author expected.")] NoSecondaryAuthorExpected, - /// VRF verification of block by author failed - #[error("VRF verification of block by author {0:?} failed: threshold {1} exceeded")] - VRFVerificationOfBlockFailed(AuthorityId, u128), /// VRF verification failed - #[error("VRF verification failed: {0:?}")] - VRFVerificationFailed(SignatureError), + #[error("VRF verification failed")] + VrfVerificationFailed, + /// Primary slot threshold too low + #[error("VRF output rejected, threshold {0} exceeded")] + VrfThresholdExceeded(u128), /// Could not fetch parent header #[error("Could not fetch parent header: {0}")] FetchParentHeader(sp_blockchain::Error), @@ -340,6 +344,9 @@ pub enum Error { /// Create inherents error. #[error("Creating inherents failed: {0}")] CreateInherents(sp_inherents::Error), + /// Background worker is not running and therefore requests cannot be answered. + #[error("Background worker is not running")] + BackgroundWorkerTerminated, /// Client error #[error(transparent)] Client(sp_blockchain::Error), @@ -377,24 +384,24 @@ where C: AuxStore + ProvideRuntimeApi + UsageProvider, C::Api: BabeApi, { - let block_id = if client.usage_info().chain.finalized_state.is_some() { - BlockId::Hash(client.usage_info().chain.best_hash) + let at_hash = if client.usage_info().chain.finalized_state.is_some() { + client.usage_info().chain.best_hash } else { debug!(target: LOG_TARGET, "No finalized state is available. Reading config from genesis"); - BlockId::Hash(client.usage_info().chain.genesis_hash) + client.usage_info().chain.genesis_hash }; let runtime_api = client.runtime_api(); - let version = runtime_api.api_version::>(&block_id)?; + let version = runtime_api.api_version::>(at_hash)?; let config = match version { Some(1) => { #[allow(deprecated)] { - runtime_api.configuration_before_version_2(&block_id)?.into() + runtime_api.configuration_before_version_2(at_hash)?.into() } }, - Some(2) => runtime_api.configuration(&block_id)?, + Some(2) => runtime_api.configuration(at_hash)?, _ => return Err(sp_blockchain::Error::VersionInvalid( "Unsupported or invalid BabeApi version".to_string(), @@ -406,7 +413,7 @@ where /// Parameters for BABE. pub struct BabeParams { /// The keystore that manages the keys of the node. - pub keystore: SyncCryptoStorePtr, + pub keystore: KeystorePtr, /// The client to use pub client: Arc, @@ -473,13 +480,10 @@ pub fn start_babe( max_block_proposal_slot_portion, telemetry, }: BabeParams, -) -> Result, sp_consensus::Error> +) -> Result, ConsensusError> where B: BlockT, C: ProvideRuntimeApi - + ProvideUncles - + BlockchainEvents - + PreCommitActions + HeaderBackend + HeaderMetadata + Send @@ -500,8 +504,6 @@ where BS: BackoffAuthoringBlocksStrategy> + Send + Sync + 'static, Error: std::error::Error + Send + From + From + 'static, { - const HANDLE_BUFFER_SIZE: usize = 1024; - let slot_notification_sinks = Arc::new(Mutex::new(Vec::new())); let worker = BabeSlotWorker { @@ -531,17 +533,7 @@ where create_inherent_data_providers, ); - let (worker_tx, worker_rx) = channel(HANDLE_BUFFER_SIZE); - - let answer_requests = - answer_requests(worker_rx, babe_link.config, client, babe_link.epoch_changes); - - let inner = future::select(Box::pin(slot_worker), Box::pin(answer_requests)); - Ok(BabeWorker { - inner: Box::pin(inner.map(|_| ())), - slot_notification_sinks, - handle: BabeWorkerHandle(worker_tx), - }) + Ok(BabeWorker { inner: Box::pin(slot_worker), slot_notification_sinks }) } // Remove obsolete block's weight data by leveraging finality notifications. @@ -558,8 +550,9 @@ fn aux_storage_cleanup + HeaderBackend, Block: B Ok(meta) => { hashes.insert(meta.parent); }, - Err(err) => - warn!(target: LOG_TARGET, "Failed to lookup metadata for block `{:?}`: {}", first, err,), + Err(err) => { + warn!(target: LOG_TARGET, "Failed to lookup metadata for block `{:?}`: {}", first, err,) + }, } // Cleans data for finalized block's ancestors @@ -594,42 +587,26 @@ async fn answer_requests( client: Arc, epoch_changes: SharedEpochChanges, ) where - C: ProvideRuntimeApi - + ProvideUncles - + BlockchainEvents - + HeaderBackend - + HeaderMetadata - + Send - + Sync - + 'static, + C: HeaderBackend + HeaderMetadata, { while let Some(request) = request_rx.next().await { match request { - BabeRequest::EpochForChild(parent_hash, parent_number, slot_number, response) => { + BabeRequest::EpochData(response) => { + let _ = response.send(epoch_changes.shared_data().clone()); + }, + BabeRequest::EpochDataForChildOf(parent_hash, parent_number, slot, response) => { let lookup = || { let epoch_changes = epoch_changes.shared_data(); - let epoch_descriptor = epoch_changes - .epoch_descriptor_for_child_of( + epoch_changes + .epoch_data_for_child_of( descendent_query(&*client), &parent_hash, parent_number, - slot_number, + slot, + |slot| Epoch::genesis(&config, slot), ) .map_err(|e| Error::::ForkTree(Box::new(e)))? - .ok_or(Error::::FetchEpoch(parent_hash))?; - - let viable_epoch = epoch_changes - .viable_epoch(&epoch_descriptor, |slot| Epoch::genesis(&config, slot)) - .ok_or(Error::::FetchEpoch(parent_hash))?; - - Ok(sp_consensus_babe::Epoch { - epoch_index: viable_epoch.as_ref().epoch_index, - start_slot: viable_epoch.as_ref().start_slot, - duration: viable_epoch.as_ref().duration, - authorities: viable_epoch.as_ref().authorities.clone(), - randomness: viable_epoch.as_ref().randomness, - config: viable_epoch.as_ref().config.clone(), - }) + .ok_or(Error::::FetchEpoch(parent_hash)) }; let _ = response.send(lookup()); @@ -639,17 +616,13 @@ async fn answer_requests( } /// Requests to the BABE service. -#[non_exhaustive] -pub enum BabeRequest { +enum BabeRequest { + /// Request all available epoch data. + EpochData(oneshot::Sender>), /// Request the epoch that a child of the given block, with the given slot number would have. /// /// The parent block is identified by its hash and number. - EpochForChild( - B::Hash, - NumberFor, - Slot, - oneshot::Sender>>, - ), + EpochDataForChildOf(B::Hash, NumberFor, Slot, oneshot::Sender>>), } /// A handle to the BABE worker for issuing requests. @@ -657,11 +630,41 @@ pub enum BabeRequest { pub struct BabeWorkerHandle(Sender>); impl BabeWorkerHandle { - /// Send a request to the BABE service. - pub async fn send(&mut self, request: BabeRequest) { - // Failure to send means that the service is down. - // This will manifest as the receiver of the request being dropped. - let _ = self.0.send(request).await; + async fn send_request(&self, request: BabeRequest) -> Result<(), Error> { + match self.0.clone().send(request).await { + Err(err) if err.is_disconnected() => return Err(Error::BackgroundWorkerTerminated), + Err(err) => warn!( + target: LOG_TARGET, + "Unhandled error when sending request to worker: {:?}", err + ), + _ => {}, + } + + Ok(()) + } + + /// Fetch all available epoch data. + pub async fn epoch_data(&self) -> Result, Error> { + let (tx, rx) = oneshot::channel(); + self.send_request(BabeRequest::EpochData(tx)).await?; + + rx.await.or(Err(Error::BackgroundWorkerTerminated)) + } + + /// Fetch the epoch that a child of the given block, with the given slot number would have. + /// + /// The parent block is identified by its hash and number. + pub async fn epoch_data_for_child_of( + &self, + parent_hash: B::Hash, + parent_number: NumberFor, + slot: Slot, + ) -> Result> { + let (tx, rx) = oneshot::channel(); + self.send_request(BabeRequest::EpochDataForChildOf(parent_hash, parent_number, slot, tx)) + .await?; + + rx.await.or(Err(Error::BackgroundWorkerTerminated))? } } @@ -670,7 +673,6 @@ impl BabeWorkerHandle { pub struct BabeWorker { inner: Pin + Send + 'static>>, slot_notification_sinks: SlotNotificationSinks, - handle: BabeWorkerHandle, } impl BabeWorker { @@ -685,11 +687,6 @@ impl BabeWorker { self.slot_notification_sinks.lock().push(sink); stream } - - /// Get a handle to the worker. - pub fn handle(&self) -> BabeWorkerHandle { - self.handle.clone() - } } impl Future for BabeWorker { @@ -713,7 +710,7 @@ struct BabeSlotWorker { justification_sync_link: L, force_authoring: bool, backoff_authoring_blocks: Option, - keystore: SyncCryptoStorePtr, + keystore: KeystorePtr, epoch_changes: SharedEpochChanges, slot_notification_sinks: SlotNotificationSinks, config: BabeConfiguration, @@ -741,7 +738,7 @@ where type SyncOracle = SO; type JustificationSyncLink = L; type CreateProposer = - Pin> + Send + 'static>>; + Pin> + Send + 'static>>; type Proposer = E::Proposer; type BlockImport = I; type AuxData = ViableEpochDescriptor, Epoch>; @@ -764,7 +761,7 @@ where slot, ) .map_err(|e| ConsensusError::ChainLookup(e.to_string()))? - .ok_or(sp_consensus::Error::InvalidAuthoritiesSet) + .ok_or(ConsensusError::InvalidAuthoritiesSet) } fn authorities_len(&self, epoch_descriptor: &Self::AuxData) -> Option { @@ -829,31 +826,21 @@ where (_, public): Self::Claim, epoch_descriptor: Self::AuxData, ) -> Result< - sc_consensus::BlockImportParams>::Transaction>, - sp_consensus::Error, + BlockImportParams>::Transaction>, + ConsensusError, > { - // sign the pre-sealed hash of the block and then - // add it to a digest item. - let public_type_pair = public.clone().into(); - let public = public.to_raw_vec(); - let signature = SyncCryptoStore::sign_with( - &*self.keystore, - ::ID, - &public_type_pair, - header_hash.as_ref(), - ) - .map_err(|e| sp_consensus::Error::CannotSign(public.clone(), e.to_string()))? - .ok_or_else(|| { - sp_consensus::Error::CannotSign( - public.clone(), - "Could not find key in keystore.".into(), - ) - })?; - let signature: AuthoritySignature = signature - .clone() - .try_into() - .map_err(|_| sp_consensus::Error::InvalidSignature(signature, public))?; - let digest_item = ::babe_seal(signature); + let signature = self + .keystore + .sr25519_sign(::ID, public.as_ref(), header_hash.as_ref()) + .map_err(|e| ConsensusError::CannotSign(format!("{}. Key: {:?}", e, public)))? + .ok_or_else(|| { + ConsensusError::CannotSign(format!( + "Could not find key in keystore. Key: {:?}", + public + )) + })?; + + let digest_item = ::babe_seal(signature.into()); let mut import_block = BlockImportParams::new(BlockOrigin::Own, header); import_block.post_digests.push(digest_item); @@ -896,11 +883,7 @@ where } fn proposer(&mut self, block: &B::Header) -> Self::CreateProposer { - Box::pin( - self.env - .init(block) - .map_err(|e| sp_consensus::Error::ClientImport(format!("{:?}", e))), - ) + Box::pin(self.env.init(block).map_err(|e| ConsensusError::ClientImport(e.to_string()))) } fn telemetry(&self) -> Option { @@ -1010,6 +993,7 @@ pub struct BabeVerifier { config: BabeConfiguration, epoch_changes: SharedEpochChanges, telemetry: Option, + offchain_tx_pool_factory: OffchainTransactionPoolFactory, } impl BabeVerifier @@ -1023,15 +1007,14 @@ where async fn check_inherents( &self, block: Block, - block_id: BlockId, + at_hash: Block::Hash, inherent_data: InherentData, create_inherent_data_providers: CIDP::InherentDataProviders, - execution_context: ExecutionContext, ) -> Result<(), Error> { let inherent_res = self .client .runtime_api() - .check_inherents_with_context(&block_id, execution_context, block, inherent_data) + .check_inherents(at_hash, block, inherent_data) .map_err(Error::RuntimeApi)?; if !inherent_res.ok() { @@ -1078,32 +1061,32 @@ where ); // get the best block on which we will build and send the equivocation report. - let best_id = self + let best_hash = self .select_chain .best_chain() .await - .map(|h| BlockId::Hash(h.hash())) + .map(|h| h.hash()) .map_err(|e| Error::Client(e.into()))?; // generate a key ownership proof. we start by trying to generate the - // key owernship proof at the parent of the equivocating header, this + // key ownership proof at the parent of the equivocating header, this // will make sure that proof generation is successful since it happens // during the on-going session (i.e. session keys are available in the // state to be able to generate the proof). this might fail if the // equivocation happens on the first block of the session, in which case // its parent would be on the previous session. if generation on the // parent header fails we try with best block as well. - let generate_key_owner_proof = |block_id: &BlockId| { + let generate_key_owner_proof = |at_hash: Block::Hash| { self.client .runtime_api() - .generate_key_ownership_proof(block_id, slot, equivocation_proof.offender.clone()) + .generate_key_ownership_proof(at_hash, slot, equivocation_proof.offender.clone()) .map_err(Error::RuntimeApi) }; - let parent_id = BlockId::Hash(*header.parent_hash()); - let key_owner_proof = match generate_key_owner_proof(&parent_id)? { + let parent_hash = *header.parent_hash(); + let key_owner_proof = match generate_key_owner_proof(parent_hash)? { Some(proof) => proof, - None => match generate_key_owner_proof(&best_id)? { + None => match generate_key_owner_proof(best_hash)? { Some(proof) => proof, None => { debug!( @@ -1116,10 +1099,15 @@ where }; // submit equivocation report at best block. - self.client - .runtime_api() + let mut runtime_api = self.client.runtime_api(); + + // Register the offchain tx pool to be able to use it from the runtime. + runtime_api + .register_extension(self.offchain_tx_pool_factory.offchain_transaction_pool(best_hash)); + + runtime_api .submit_report_equivocation_unsigned_extrinsic( - &best_id, + best_hash, equivocation_proof, key_owner_proof, ) @@ -1131,9 +1119,6 @@ where } } -type BlockVerificationResult = - Result<(BlockImportParams, Option)>>), String>; - #[async_trait::async_trait] impl Verifier for BabeVerifier @@ -1153,7 +1138,7 @@ where async fn verify( &mut self, mut block: BlockImportParams, - ) -> BlockVerificationResult { + ) -> Result, String> { trace!( target: LOG_TARGET, "Verifying origin: {:?} header: {:?} justification(s): {:?} body: {:?}", @@ -1173,11 +1158,11 @@ where // Verification for imported blocks is skipped in two cases: // 1. When importing blocks below the last finalized block during network initial // synchronization. - // 2. When importing whole state we don't calculate epoch descriptor, but rather - // read it from the state after import. We also skip all verifications - // because there's no parent state and we trust the sync module to verify - // that the state is correct and finalized. - return Ok((block, Default::default())) + // 2. When importing whole state we don't calculate epoch descriptor, but rather read it + // from the state after import. We also skip all verifications because there's no + // parent state and we trust the sync module to verify that the state is correct and + // finalized. + return Ok(block) } debug!( @@ -1190,7 +1175,7 @@ where .create_inherent_data_providers .create_inherent_data_providers(parent_hash, ()) .await - .map_err(|e| Error::::Client(sp_consensus::Error::from(e).into()))?; + .map_err(|e| Error::::Client(ConsensusError::from(e).into()))?; let slot_now = create_inherent_data_providers.slot(); @@ -1268,10 +1253,9 @@ where self.check_inherents( new_block.clone(), - BlockId::Hash(parent_hash), + parent_hash, inherent_data, create_inherent_data_providers, - block.origin.into(), ) .await?; } @@ -1296,7 +1280,7 @@ where ); block.post_hash = Some(hash); - Ok((block, Default::default())) + Ok(block) }, CheckedHeader::Deferred(a, b) => { debug!(target: LOG_TARGET, "Checking {:?} failed; {:?}, {:?}.", hash, a, b); @@ -1368,7 +1352,6 @@ where async fn import_state( &mut self, mut block: BlockImportParams>, - new_cache: HashMap>, ) -> Result { let hash = block.post_hash(); let parent_hash = *block.header.parent_hash(); @@ -1383,7 +1366,7 @@ where }); // First make the client import the state. - let import_result = self.inner.import_block(block, new_cache).await; + let import_result = self.inner.import_block(block).await; let aux = match import_result { Ok(ImportResult::Imported(aux)) => aux, Ok(r) => @@ -1395,11 +1378,10 @@ where }; // Read epoch info from the imported state. - let block_id = BlockId::hash(hash); - let current_epoch = self.client.runtime_api().current_epoch(&block_id).map_err(|e| { + let current_epoch = self.client.runtime_api().current_epoch(hash).map_err(|e| { ConsensusError::ClientImport(babe_err::(Error::RuntimeApi(e)).into()) })?; - let next_epoch = self.client.runtime_api().next_epoch(&block_id).map_err(|e| { + let next_epoch = self.client.runtime_api().next_epoch(hash).map_err(|e| { ConsensusError::ClientImport(babe_err::(Error::RuntimeApi(e)).into()) })?; @@ -1434,7 +1416,6 @@ where async fn import_block( &mut self, mut block: BlockImportParams, - new_cache: HashMap>, ) -> Result { let hash = block.post_hash(); let number = *block.header.number(); @@ -1455,11 +1436,11 @@ where // In case of initial sync intermediates should not be present... let _ = block.remove_intermediate::>(INTERMEDIATE_KEY); block.fork_choice = Some(ForkChoiceStrategy::Custom(false)); - return self.inner.import_block(block, new_cache).await.map_err(Into::into) + return self.inner.import_block(block).await.map_err(Into::into) } if block.with_state() { - return self.import_state(block, new_cache).await + return self.import_state(block).await } let pre_digest = find_pre_digest::(&block.header).expect( @@ -1494,7 +1475,7 @@ where // this way we can revert it if there's any error let mut old_epoch_changes = None; - // Use an extra scope to make the compiler happy, because otherwise he complains about the + // Use an extra scope to make the compiler happy, because otherwise it complains about the // mutex, even if we dropped it... let mut epoch_changes = { let mut epoch_changes = self.epoch_changes.shared_data_locked(); @@ -1695,7 +1676,7 @@ where epoch_changes.release_mutex() }; - let import_result = self.inner.import_block(block, new_cache).await; + let import_result = self.inner.import_block(block).await; // revert to the original epoch changes in case there's an error // importing the block @@ -1792,6 +1773,38 @@ where Ok((import, link)) } +/// Parameters passed to [`import_queue`]. +pub struct ImportQueueParams<'a, Block: BlockT, BI, Client, CIDP, SelectChain, Spawn> { + /// The BABE link that is created by [`block_import`]. + pub link: BabeLink, + /// The block import that should be wrapped. + pub block_import: BI, + /// Optional justification import. + pub justification_import: Option>, + /// The client to interact with the internals of the node. + pub client: Arc, + /// A [`SelectChain`] implementation. + /// + /// Used to determine the best block that should be used as basis when sending an equivocation + /// report. + pub select_chain: SelectChain, + /// Used to crate the inherent data providers. + /// + /// These inherent data providers are then used to create the inherent data that is + /// passed to the `check_inherents` runtime call. + pub create_inherent_data_providers: CIDP, + /// Spawner for spawning futures. + pub spawner: &'a Spawn, + /// Registry for prometheus metrics. + pub registry: Option<&'a Registry>, + /// Optional telemetry handle to report telemetry events. + pub telemetry: Option, + /// The offchain transaction pool factory. + /// + /// Will be used when sending equivocation reports. + pub offchain_tx_pool_factory: OffchainTransactionPoolFactory, +} + /// Start an import queue for the BABE consensus algorithm. /// /// This method returns the import queue, some data that needs to be passed to the block authoring @@ -1801,19 +1814,22 @@ where /// /// The block import object provided must be the `BabeBlockImport` or a wrapper /// of it, otherwise crucial import logic will be omitted. -pub fn import_queue( - babe_link: BabeLink, - block_import: Inner, - justification_import: Option>, - client: Arc, - select_chain: SelectChain, - create_inherent_data_providers: CIDP, - spawner: &impl sp_core::traits::SpawnEssentialNamed, - registry: Option<&Registry>, - telemetry: Option, -) -> ClientResult> +pub fn import_queue( + ImportQueueParams { + link: babe_link, + block_import, + justification_import, + client, + select_chain, + create_inherent_data_providers, + spawner, + registry, + telemetry, + offchain_tx_pool_factory, + }: ImportQueueParams<'_, Block, BI, Client, CIDP, SelectChain, Spawn>, +) -> ClientResult<(DefaultImportQueue, BabeWorkerHandle)> where - Inner: BlockImport< + BI: BlockImport< Block, Error = ConsensusError, Transaction = sp_api::TransactionFor, @@ -1831,17 +1847,31 @@ where SelectChain: sp_consensus::SelectChain + 'static, CIDP: CreateInherentDataProviders + Send + Sync + 'static, CIDP::InherentDataProviders: InherentDataProviderExt + Send + Sync, + Spawn: SpawnEssentialNamed, { + const HANDLE_BUFFER_SIZE: usize = 1024; + let verifier = BabeVerifier { select_chain, create_inherent_data_providers, - config: babe_link.config, - epoch_changes: babe_link.epoch_changes, + config: babe_link.config.clone(), + epoch_changes: babe_link.epoch_changes.clone(), telemetry, - client, + client: client.clone(), + offchain_tx_pool_factory, }; - Ok(BasicQueue::new(verifier, Box::new(block_import), justification_import, spawner, registry)) + let (worker_tx, worker_rx) = channel(HANDLE_BUFFER_SIZE); + + let answer_requests = + answer_requests(worker_rx, babe_link.config, client, babe_link.epoch_changes); + + spawner.spawn_essential("babe-worker", Some("babe"), answer_requests.boxed()); + + Ok(( + BasicQueue::new(verifier, Box::new(block_import), justification_import, spawner, registry), + BabeWorkerHandle(worker_tx), + )) } /// Reverts protocol aux data to at most the last finalized block. diff --git a/client/consensus/babe/src/migration.rs b/client/consensus/babe/src/migration.rs index 23413aa6a7b1..2b1396c41c26 100644 --- a/client/consensus/babe/src/migration.rs +++ b/client/consensus/babe/src/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,7 +18,7 @@ use crate::{ AuthorityId, BabeAuthorityWeight, BabeConfiguration, BabeEpochConfiguration, Epoch, - NextEpochDescriptor, VRF_OUTPUT_LENGTH, + NextEpochDescriptor, Randomness, }; use codec::{Decode, Encode}; use sc_consensus_epochs::Epoch as EpochT; @@ -36,7 +36,7 @@ pub struct EpochV0 { /// The authorities and their weights. pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, /// Randomness for this epoch. - pub randomness: [u8; VRF_OUTPUT_LENGTH], + pub randomness: Randomness, } impl EpochT for EpochV0 { diff --git a/client/consensus/babe/src/tests.rs b/client/consensus/babe/src/tests.rs index 0dd0b59dd69c..384e45228b59 100644 --- a/client/consensus/babe/src/tests.rs +++ b/client/consensus/babe/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -20,30 +20,23 @@ use super::*; use authorship::claim_slot; -use rand_chacha::{ - rand_core::{RngCore, SeedableRng}, - ChaChaRng, -}; use sc_block_builder::{BlockBuilder, BlockBuilderProvider}; use sc_client_api::{backend::TransactionFor, BlockchainEvents, Finalizer}; use sc_consensus::{BoxBlockImport, BoxJustificationImport}; use sc_consensus_epochs::{EpochIdentifier, EpochIdentifierPosition}; use sc_consensus_slots::BackoffAuthoringOnFinalizedHeadLagging; use sc_network_test::{Block as TestBlock, *}; +use sc_transaction_pool_api::RejectAllTxPool; use sp_application_crypto::key_types::BABE; use sp_consensus::{DisableProofRecording, NoNetwork as DummyOracle, Proposal}; use sp_consensus_babe::{ - inherents::InherentDataProvider, make_transcript, make_transcript_data, AllowedSlots, - AuthorityId, AuthorityPair, Slot, + inherents::InherentDataProvider, make_vrf_sign_data, AllowedSlots, AuthorityId, AuthorityPair, + Slot, }; use sp_consensus_slots::SlotDuration; -use sp_consensus_vrf::schnorrkel::VRFOutput; use sp_core::crypto::Pair; use sp_keyring::Sr25519Keyring; -use sp_keystore::{ - testing::KeyStore as TestKeyStore, vrf::make_transcript as transcript_from_data, - SyncCryptoStore, -}; +use sp_keystore::{testing::MemoryKeystore, Keystore}; use sp_runtime::{ generic::{Digest, DigestItem}, traits::Block as BlockT, @@ -79,15 +72,12 @@ const SLOT_DURATION_MS: u64 = 1000; struct DummyFactory { client: Arc, epoch_changes: SharedEpochChanges, - config: BabeConfiguration, mutator: Mutator, } struct DummyProposer { factory: DummyFactory, parent_hash: Hash, - parent_number: u64, - parent_slot: Slot, } impl Environment for DummyFactory { @@ -96,15 +86,9 @@ impl Environment for DummyFactory { type Error = Error; fn init(&mut self, parent_header: &::Header) -> Self::CreateProposer { - let parent_slot = crate::find_pre_digest::(parent_header) - .expect("parent header has a pre-digest") - .slot(); - future::ready(Ok(DummyProposer { factory: self.clone(), parent_hash: parent_header.hash(), - parent_number: *parent_header.number(), - parent_slot, })) } } @@ -123,50 +107,14 @@ impl DummyProposer { Error, >, > { - let block_builder = self - .factory - .client - .new_block_at(&BlockId::Hash(self.parent_hash), pre_digests, false) - .unwrap(); + let block_builder = + self.factory.client.new_block_at(self.parent_hash, pre_digests, false).unwrap(); let mut block = match block_builder.build().map_err(|e| e.into()) { Ok(b) => b.block, Err(e) => return future::ready(Err(e)), }; - let this_slot = crate::find_pre_digest::(block.header()) - .expect("baked block has valid pre-digest") - .slot(); - - // figure out if we should add a consensus digest, since the test runtime - // doesn't. - let epoch_changes = self.factory.epoch_changes.shared_data(); - let epoch = epoch_changes - .epoch_data_for_child_of( - descendent_query(&*self.factory.client), - &self.parent_hash, - self.parent_number, - this_slot, - |slot| Epoch::genesis(&self.factory.config, slot), - ) - .expect("client has data to find epoch") - .expect("can compute epoch for baked block"); - - let first_in_epoch = self.parent_slot < epoch.start_slot; - if first_in_epoch { - // push a `Consensus` digest signalling next change. - // we just reuse the same randomness and authorities as the prior - // epoch. this will break when we add light client support, since - // that will re-check the randomness logic off-chain. - let digest_data = ConsensusLog::NextEpochData(NextEpochDescriptor { - authorities: epoch.authorities.clone(), - randomness: epoch.randomness, - }) - .encode(); - let digest = DigestItem::Consensus(BABE_ENGINE_ID, digest_data); - block.header.digest_mut().push(digest) - } - // mutate the block header according to the mutator. (self.factory.mutator)(&mut block.header, Stage::PreSeal); @@ -212,9 +160,8 @@ where async fn import_block( &mut self, block: BlockImportParams, - new_cache: HashMap>, ) -> Result { - Ok(self.0.import_block(block, new_cache).await.expect("importing block failed")) + Ok(self.0.import_block(block).await.expect("importing block failed")) } async fn check_block( @@ -261,7 +208,7 @@ impl Verifier for TestVerifier { async fn verify( &mut self, mut block: BlockImportParams, - ) -> Result<(BlockImportParams, Option)>>), String> { + ) -> Result, String> { // apply post-sealing mutations (i.e. stripping seal, if desired). (self.mutator)(&mut block.header, Stage::PostSeal); self.inner.verify(block).await @@ -337,6 +284,9 @@ impl TestNetFactory for BabeTestNet { config: data.link.config.clone(), epoch_changes: data.link.epoch_changes.clone(), telemetry: None, + offchain_tx_pool_factory: OffchainTransactionPoolFactory::new( + RejectAllTxPool::default(), + ), }, mutator: MUTATOR.with(|m| m.borrow().clone()), } @@ -352,13 +302,18 @@ impl TestNetFactory for BabeTestNet { &self.peers } + fn peers_mut(&mut self) -> &mut Vec { + trace!(target: "babe", "Retrieving peers, mutable"); + &mut self.peers + } + fn mut_peers)>(&mut self, closure: F) { closure(&mut self.peers); } } #[tokio::test] -#[should_panic] +#[should_panic(expected = "No BABE pre-runtime digest found")] async fn rejects_empty_block() { sp_tracing::try_init_simple(); let mut net = BabeTestNet::new(3); @@ -368,11 +323,12 @@ async fn rejects_empty_block() { }) } -fn create_keystore(authority: Sr25519Keyring) -> SyncCryptoStorePtr { - let keystore = Arc::new(TestKeyStore::new()); - SyncCryptoStore::sr25519_generate_new(&*keystore, BABE, Some(&authority.to_seed())) - .expect("Generates authority key"); +fn create_keystore(authority: Sr25519Keyring) -> KeystorePtr { + let keystore = MemoryKeystore::new(); keystore + .sr25519_generate_new(BABE, Some(&authority.to_seed())) + .expect("Generates authority key"); + keystore.into() } async fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + 'static) { @@ -404,7 +360,6 @@ async fn run_one_test(mutator: impl Fn(&mut TestHeader, Stage) + Send + Sync + ' let environ = DummyFactory { client: client.clone(), - config: data.link.config.clone(), epoch_changes: data.link.epoch_changes.clone(), mutator: mutator.clone(), }; @@ -489,7 +444,7 @@ async fn authoring_blocks() { } #[tokio::test] -#[should_panic] +#[should_panic(expected = "valid babe headers must contain a predigest")] async fn rejects_missing_inherent_digest() { run_one_test(|header: &mut TestHeader, stage| { let v = std::mem::take(&mut header.digest_mut().logs); @@ -502,7 +457,7 @@ async fn rejects_missing_inherent_digest() { } #[tokio::test] -#[should_panic] +#[should_panic(expected = "has a bad seal")] async fn rejects_missing_seals() { run_one_test(|header: &mut TestHeader, stage| { let v = std::mem::take(&mut header.digest_mut().logs); @@ -515,7 +470,7 @@ async fn rejects_missing_seals() { } #[tokio::test] -#[should_panic] +#[should_panic(expected = "Expected epoch change to happen")] async fn rejects_missing_consensus_digests() { run_one_test(|header: &mut TestHeader, stage| { let v = std::mem::take(&mut header.digest_mut().logs); @@ -636,22 +591,18 @@ fn claim_vrf_check() { PreDigest::Primary(d) => d, v => panic!("Unexpected pre-digest variant {:?}", v), }; - let transcript = make_transcript_data(&epoch.randomness.clone(), 0.into(), epoch.epoch_index); - let sign = SyncCryptoStore::sr25519_vrf_sign(&*keystore, AuthorityId::ID, &public, transcript) - .unwrap() - .unwrap(); - assert_eq!(pre_digest.vrf_output, VRFOutput(sign.output)); + let data = make_vrf_sign_data(&epoch.randomness.clone(), 0.into(), epoch.epoch_index); + let sign = keystore.sr25519_vrf_sign(AuthorityId::ID, &public, &data).unwrap().unwrap(); + assert_eq!(pre_digest.vrf_signature.output, sign.output); // We expect a SecondaryVRF claim for slot 1 let pre_digest = match claim_slot(1.into(), &epoch, &keystore).unwrap().0 { PreDigest::SecondaryVRF(d) => d, v => panic!("Unexpected pre-digest variant {:?}", v), }; - let transcript = make_transcript_data(&epoch.randomness.clone(), 1.into(), epoch.epoch_index); - let sign = SyncCryptoStore::sr25519_vrf_sign(&*keystore, AuthorityId::ID, &public, transcript) - .unwrap() - .unwrap(); - assert_eq!(pre_digest.vrf_output, VRFOutput(sign.output)); + let data = make_vrf_sign_data(&epoch.randomness.clone(), 1.into(), epoch.epoch_index); + let sign = keystore.sr25519_vrf_sign(AuthorityId::ID, &public, &data).unwrap().unwrap(); + assert_eq!(pre_digest.vrf_signature.output, sign.output); // Check that correct epoch index has been used if epochs are skipped (primary VRF) let slot = Slot::from(103); @@ -660,12 +611,10 @@ fn claim_vrf_check() { v => panic!("Unexpected claim variant {:?}", v), }; let fixed_epoch = epoch.clone_for_slot(slot); - let transcript = make_transcript_data(&epoch.randomness.clone(), slot, fixed_epoch.epoch_index); - let sign = SyncCryptoStore::sr25519_vrf_sign(&*keystore, AuthorityId::ID, &public, transcript) - .unwrap() - .unwrap(); + let data = make_vrf_sign_data(&epoch.randomness.clone(), slot, fixed_epoch.epoch_index); + let sign = keystore.sr25519_vrf_sign(AuthorityId::ID, &public, &data).unwrap().unwrap(); assert_eq!(fixed_epoch.epoch_index, 11); - assert_eq!(claim.vrf_output, VRFOutput(sign.output)); + assert_eq!(claim.vrf_signature.output, sign.output); // Check that correct epoch index has been used if epochs are skipped (secondary VRF) let slot = Slot::from(100); @@ -674,12 +623,10 @@ fn claim_vrf_check() { v => panic!("Unexpected claim variant {:?}", v), }; let fixed_epoch = epoch.clone_for_slot(slot); - let transcript = make_transcript_data(&epoch.randomness.clone(), slot, fixed_epoch.epoch_index); - let sign = SyncCryptoStore::sr25519_vrf_sign(&*keystore, AuthorityId::ID, &public, transcript) - .unwrap() - .unwrap(); + let data = make_vrf_sign_data(&epoch.randomness.clone(), slot, fixed_epoch.epoch_index); + let sign = keystore.sr25519_vrf_sign(AuthorityId::ID, &public, &data).unwrap().unwrap(); assert_eq!(fixed_epoch.epoch_index, 11); - assert_eq!(pre_digest.vrf_output, VRFOutput(sign.output)); + assert_eq!(pre_digest.vrf_signature.output, sign.output); } // Propose and import a new BABE block on top of the given parent. @@ -741,7 +688,7 @@ async fn propose_and_import_block( import .insert_intermediate(INTERMEDIATE_KEY, BabeIntermediate:: { epoch_descriptor }); import.fork_choice = Some(ForkChoiceStrategy::LongestChain); - let import_result = block_import.import_block(import, Default::default()).await.unwrap(); + let import_result = block_import.import_block(import).await.unwrap(); match import_result { ImportResult::Imported(_) => {}, @@ -784,7 +731,6 @@ async fn importing_block_one_sets_genesis_epoch() { let mut proposer_factory = DummyFactory { client: client.clone(), - config: data.link.config.clone(), epoch_changes: data.link.epoch_changes.clone(), mutator: Arc::new(|_, _| ()), }; @@ -828,7 +774,6 @@ async fn revert_prunes_epoch_changes_and_removes_weights() { let mut proposer_factory = DummyFactory { client: client.clone(), - config: data.link.config.clone(), epoch_changes: data.link.epoch_changes.clone(), mutator: Arc::new(|_, _| ()), }; @@ -916,7 +861,6 @@ async fn revert_not_allowed_for_finalized() { let mut proposer_factory = DummyFactory { client: client.clone(), - config: data.link.config.clone(), epoch_changes: data.link.epoch_changes.clone(), mutator: Arc::new(|_, _| ()), }; @@ -957,7 +901,6 @@ async fn importing_epoch_change_block_prunes_tree() { let mut proposer_factory = DummyFactory { client: client.clone(), - config: data.link.config.clone(), epoch_changes: data.link.epoch_changes.clone(), mutator: Arc::new(|_, _| ()), }; @@ -1044,7 +987,7 @@ async fn importing_epoch_change_block_prunes_tree() { } #[tokio::test] -#[should_panic] +#[should_panic(expected = "Slot number must increase: parent slot: 999, this slot: 999")] async fn verify_slots_are_strictly_increasing() { let mut net = BabeTestNet::new(1); @@ -1056,7 +999,6 @@ async fn verify_slots_are_strictly_increasing() { let mut proposer_factory = DummyFactory { client: client.clone(), - config: data.link.config.clone(), epoch_changes: data.link.epoch_changes.clone(), mutator: Arc::new(|_, _| ()), }; @@ -1079,36 +1021,6 @@ async fn verify_slots_are_strictly_increasing() { propose_and_import_block(&b1, Some(999.into()), &mut proposer_factory, &mut block_import).await; } -#[test] -fn babe_transcript_generation_match() { - sp_tracing::try_init_simple(); - - let authority = Sr25519Keyring::Alice; - let _keystore = create_keystore(authority); - - let epoch = Epoch { - start_slot: 0.into(), - authorities: vec![(authority.public().into(), 1)], - randomness: [0; 32], - epoch_index: 1, - duration: 100, - config: BabeEpochConfiguration { - c: (3, 10), - allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots, - }, - }; - - let orig_transcript = make_transcript(&epoch.randomness.clone(), 1.into(), epoch.epoch_index); - let new_transcript = make_transcript_data(&epoch.randomness, 1.into(), epoch.epoch_index); - - let test = |t: merlin::Transcript| -> [u8; 16] { - let mut b = [0u8; 16]; - t.build_rng().finalize(&mut ChaChaRng::from_seed([0u8; 32])).fill_bytes(&mut b); - b - }; - debug_assert!(test(orig_transcript) == test(transcript_from_data(new_transcript))); -} - #[tokio::test] async fn obsolete_blocks_aux_data_cleanup() { let mut net = BabeTestNet::new(1); @@ -1126,7 +1038,6 @@ async fn obsolete_blocks_aux_data_cleanup() { let mut proposer_factory = DummyFactory { client: client.clone(), - config: data.link.config.clone(), epoch_changes: data.link.epoch_changes.clone(), mutator: Arc::new(|_, _| ()), }; @@ -1212,7 +1123,6 @@ async fn allows_skipping_epochs() { let mut proposer_factory = DummyFactory { client: client.clone(), - config: data.link.config.clone(), epoch_changes: data.link.epoch_changes.clone(), mutator: Arc::new(|_, _| ()), }; @@ -1342,7 +1252,6 @@ async fn allows_skipping_epochs_on_some_forks() { let mut proposer_factory = DummyFactory { client: client.clone(), - config: data.link.config.clone(), epoch_changes: data.link.epoch_changes.clone(), mutator: Arc::new(|_, _| ()), }; diff --git a/client/consensus/babe/src/verification.rs b/client/consensus/babe/src/verification.rs index 8e8f5d998f88..3de5eacc2c51 100644 --- a/client/consensus/babe/src/verification.rs +++ b/client/consensus/babe/src/verification.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,8 +18,9 @@ //! Verification for BABE headers. use crate::{ - authorship::{calculate_primary_threshold, check_primary_threshold, secondary_slot_author}, - babe_err, find_pre_digest, BlockT, Epoch, Error, LOG_TARGET, + authorship::{calculate_primary_threshold, secondary_slot_author}, + babe_err, find_pre_digest, BlockT, Epoch, Error, AUTHORING_SCORE_LENGTH, + AUTHORING_SCORE_VRF_CONTEXT, LOG_TARGET, }; use log::{debug, trace}; use sc_consensus_epochs::Epoch as EpochT; @@ -29,10 +30,13 @@ use sp_consensus_babe::{ CompatibleDigestItem, PreDigest, PrimaryPreDigest, SecondaryPlainPreDigest, SecondaryVRFPreDigest, }, - make_transcript, AuthorityId, AuthorityPair, AuthoritySignature, + make_vrf_sign_data, AuthorityId, AuthorityPair, AuthoritySignature, }; use sp_consensus_slots::Slot; -use sp_core::{ByteArray, Pair}; +use sp_core::{ + crypto::{VrfPublic, Wraps}, + Pair, +}; use sp_runtime::{traits::Header, DigestItem}; /// BABE verification parameters @@ -155,7 +159,7 @@ fn check_primary_header( epoch: &Epoch, c: (u64, u64), ) -> Result<(), Error> { - let author = &epoch.authorities[pre_digest.authority_index as usize].0; + let authority_id = &epoch.authorities[pre_digest.authority_index as usize].0; let mut epoch_index = epoch.epoch_index; if epoch.end_slot() <= pre_digest.slot { @@ -163,28 +167,34 @@ fn check_primary_header( epoch_index = epoch.clone_for_slot(pre_digest.slot).epoch_index; } - if AuthorityPair::verify(&signature, pre_hash, author) { - let (inout, _) = { - let transcript = make_transcript(&epoch.randomness, pre_digest.slot, epoch_index); - - schnorrkel::PublicKey::from_bytes(author.as_slice()) - .and_then(|p| { - p.vrf_verify(transcript, &pre_digest.vrf_output, &pre_digest.vrf_proof) - }) - .map_err(|s| babe_err(Error::VRFVerificationFailed(s)))? - }; + if !AuthorityPair::verify(&signature, pre_hash, authority_id) { + return Err(babe_err(Error::BadSignature(pre_hash))) + } - let threshold = - calculate_primary_threshold(c, &epoch.authorities, pre_digest.authority_index as usize); + let data = make_vrf_sign_data(&epoch.randomness, pre_digest.slot, epoch_index); - if !check_primary_threshold(&inout, threshold) { - return Err(babe_err(Error::VRFVerificationOfBlockFailed(author.clone(), threshold))) - } + if !authority_id.as_inner_ref().vrf_verify(&data, &pre_digest.vrf_signature) { + return Err(babe_err(Error::VrfVerificationFailed)) + } - Ok(()) - } else { - Err(babe_err(Error::BadSignature(pre_hash))) + let threshold = + calculate_primary_threshold(c, &epoch.authorities, pre_digest.authority_index as usize); + + let score = authority_id + .as_inner_ref() + .make_bytes::( + AUTHORING_SCORE_VRF_CONTEXT, + &data.as_ref(), + &pre_digest.vrf_signature.output, + ) + .map(u128::from_le_bytes) + .map_err(|_| babe_err(Error::VrfVerificationFailed))?; + + if score >= threshold { + return Err(babe_err(Error::VrfThresholdExceeded(threshold))) } + + Ok(()) } /// Check a secondary slot proposal header. We validate that the given header is @@ -197,8 +207,7 @@ fn check_secondary_plain_header( signature: AuthoritySignature, epoch: &Epoch, ) -> Result<(), Error> { - // check the signature is valid under the expected authority and - // chain state. + // check the signature is valid under the expected authority and chain state. let expected_author = secondary_slot_author(pre_digest.slot, &epoch.authorities, epoch.randomness) .ok_or(Error::NoSecondaryAuthorExpected)?; @@ -209,11 +218,11 @@ fn check_secondary_plain_header( return Err(Error::InvalidAuthor(expected_author.clone(), author.clone())) } - if AuthorityPair::verify(&signature, pre_hash.as_ref(), author) { - Ok(()) - } else { - Err(Error::BadSignature(pre_hash)) + if !AuthorityPair::verify(&signature, pre_hash.as_ref(), author) { + return Err(Error::BadSignature(pre_hash)) } + + Ok(()) } /// Check a secondary VRF slot proposal header. @@ -223,8 +232,7 @@ fn check_secondary_vrf_header( signature: AuthoritySignature, epoch: &Epoch, ) -> Result<(), Error> { - // check the signature is valid under the expected authority and - // chain state. + // check the signature is valid under the expected authority and chain state. let expected_author = secondary_slot_author(pre_digest.slot, &epoch.authorities, epoch.randomness) .ok_or(Error::NoSecondaryAuthorExpected)?; @@ -241,15 +249,15 @@ fn check_secondary_vrf_header( epoch_index = epoch.clone_for_slot(pre_digest.slot).epoch_index; } - if AuthorityPair::verify(&signature, pre_hash.as_ref(), author) { - let transcript = make_transcript(&epoch.randomness, pre_digest.slot, epoch_index); + if !AuthorityPair::verify(&signature, pre_hash.as_ref(), author) { + return Err(Error::BadSignature(pre_hash)) + } - schnorrkel::PublicKey::from_bytes(author.as_slice()) - .and_then(|p| p.vrf_verify(transcript, &pre_digest.vrf_output, &pre_digest.vrf_proof)) - .map_err(|s| babe_err(Error::VRFVerificationFailed(s)))?; + let data = make_vrf_sign_data(&epoch.randomness, pre_digest.slot, epoch_index); - Ok(()) - } else { - Err(Error::BadSignature(pre_hash)) + if !author.as_inner_ref().vrf_verify(&data, &pre_digest.vrf_signature) { + return Err(Error::VrfVerificationFailed) } + + Ok(()) } diff --git a/client/consensus/beefy/Cargo.toml b/client/consensus/beefy/Cargo.toml new file mode 100644 index 000000000000..aec605c6bf11 --- /dev/null +++ b/client/consensus/beefy/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "sc-consensus-beefy" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +repository = "https://github.com/paritytech/substrate" +description = "BEEFY Client gadget for substrate" +homepage = "https://substrate.io" + +[dependencies] +array-bytes = "6.1" +async-channel = "1.8.0" +async-trait = "0.1.57" +codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } +fnv = "1.0.6" +futures = "0.3" +log = "0.4" +parking_lot = "0.12.1" +thiserror = "1.0" +wasm-timer = "0.2.5" +prometheus = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } +sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } +sc-network = { version = "0.10.0-dev", path = "../../network" } +sc-network-gossip = { version = "0.10.0-dev", path = "../../network-gossip" } +sc-network-sync = { version = "0.10.0-dev", path = "../../network/sync" } +sc-utils = { version = "4.0.0-dev", path = "../../utils" } +sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } +sp-application-crypto = { version = "23.0.0", path = "../../../primitives/application-crypto" } +sp-arithmetic = { version = "16.0.0", path = "../../../primitives/arithmetic" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-consensus-beefy = { version = "4.0.0-dev", path = "../../../primitives/consensus/beefy" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-keystore = { version = "0.27.0", path = "../../../primitives/keystore" } +sp-mmr-primitives = { version = "4.0.0-dev", path = "../../../primitives/merkle-mountain-range" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } + +[dev-dependencies] +serde = "1.0.163" +tempfile = "3.1.0" +tokio = "1.22.0" +sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } +sc-network-test = { version = "0.8.0", path = "../../network/test" } +sp-consensus-grandpa = { version = "4.0.0-dev", path = "../../../primitives/consensus/grandpa" } +sp-keyring = { version = "24.0.0", path = "../../../primitives/keyring" } +sp-tracing = { version = "10.0.0", path = "../../../primitives/tracing" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/client/beefy/README.md b/client/consensus/beefy/README.md similarity index 100% rename from client/beefy/README.md rename to client/consensus/beefy/README.md diff --git a/client/beefy/rpc/Cargo.toml b/client/consensus/beefy/rpc/Cargo.toml similarity index 50% rename from client/beefy/rpc/Cargo.toml rename to client/consensus/beefy/rpc/Cargo.toml index ab3e6921f3cf..4f6e0d8c84b6 100644 --- a/client/beefy/rpc/Cargo.toml +++ b/client/consensus/beefy/rpc/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "beefy-gadget-rpc" +name = "sc-consensus-beefy-rpc" version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" @@ -9,23 +9,21 @@ description = "RPC for the BEEFY Client gadget for substrate" homepage = "https://substrate.io" [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } log = "0.4" parking_lot = "0.12.1" -serde = { version = "1.0.136", features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"] } thiserror = "1.0" -beefy-gadget = { version = "4.0.0-dev", path = "../." } -beefy-primitives = { version = "4.0.0-dev", path = "../../../primitives/beefy", package = "sp-beefy" } -sc-rpc = { version = "4.0.0-dev", path = "../../rpc" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sc-consensus-beefy = { version = "4.0.0-dev", path = "../" } +sp-consensus-beefy = { version = "4.0.0-dev", path = "../../../../primitives/consensus/beefy" } +sc-rpc = { version = "4.0.0-dev", path = "../../../rpc" } +sp-core = { version = "21.0.0", path = "../../../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../../../primitives/runtime" } [dev-dependencies] serde_json = "1.0.85" -sc-rpc = { version = "4.0.0-dev", features = [ - "test-helpers", -], path = "../../rpc" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } +sc-rpc = { version = "4.0.0-dev", features = ["test-helpers"], path = "../../../rpc" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../../../test-utils/runtime/client" } tokio = { version = "1.22.0", features = ["macros"] } diff --git a/client/beefy/rpc/src/lib.rs b/client/consensus/beefy/rpc/src/lib.rs similarity index 97% rename from client/beefy/rpc/src/lib.rs rename to client/consensus/beefy/rpc/src/lib.rs index 4fea98c6eb24..f5c0ff32627d 100644 --- a/client/beefy/rpc/src/lib.rs +++ b/client/consensus/beefy/rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -35,7 +35,7 @@ use jsonrpsee::{ }; use log::warn; -use beefy_gadget::communication::notification::{ +use sc_consensus_beefy::communication::notification::{ BeefyBestBlockStream, BeefyVersionedFinalityProofStream, }; @@ -166,13 +166,13 @@ where mod tests { use super::*; - use beefy_gadget::{ + use codec::{Decode, Encode}; + use jsonrpsee::{types::EmptyServerParams as EmptyParams, RpcModule}; + use sc_consensus_beefy::{ communication::notification::BeefyVersionedFinalityProofSender, justification::BeefyVersionedFinalityProof, }; - use beefy_primitives::{known_payloads, Payload, SignedCommitment}; - use codec::{Decode, Encode}; - use jsonrpsee::{types::EmptyServerParams as EmptyParams, RpcModule}; + use sp_consensus_beefy::{known_payloads, Payload, SignedCommitment}; use sp_runtime::traits::{BlakeTwo256, Hash}; use substrate_test_runtime_client::runtime::Block; @@ -269,7 +269,7 @@ mod tests { let payload = Payload::from_single_entry(known_payloads::MMR_ROOT_ID, "Hello World!".encode()); BeefyVersionedFinalityProof::::V1(SignedCommitment { - commitment: beefy_primitives::Commitment { + commitment: sp_consensus_beefy::Commitment { payload, block_number: 5, validator_set_id: 0, diff --git a/client/beefy/rpc/src/notification.rs b/client/consensus/beefy/rpc/src/notification.rs similarity index 87% rename from client/beefy/rpc/src/notification.rs rename to client/consensus/beefy/rpc/src/notification.rs index a815425644d5..690c511b999a 100644 --- a/client/beefy/rpc/src/notification.rs +++ b/client/consensus/beefy/rpc/src/notification.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -23,13 +23,13 @@ use sp_runtime::traits::Block as BlockT; /// An encoded finality proof proving that the given header has been finalized. /// The given bytes should be the SCALE-encoded representation of a -/// `beefy_primitives::VersionedFinalityProof`. +/// `sp_consensus_beefy::VersionedFinalityProof`. #[derive(Clone, Serialize, Deserialize)] pub struct EncodedVersionedFinalityProof(sp_core::Bytes); impl EncodedVersionedFinalityProof { pub fn new( - finality_proof: beefy_gadget::justification::BeefyVersionedFinalityProof, + finality_proof: sc_consensus_beefy::justification::BeefyVersionedFinalityProof, ) -> Self where Block: BlockT, diff --git a/client/beefy/src/aux_schema.rs b/client/consensus/beefy/src/aux_schema.rs similarity index 81% rename from client/beefy/src/aux_schema.rs rename to client/consensus/beefy/src/aux_schema.rs index fafa9948c544..409eb30d09ab 100644 --- a/client/beefy/src/aux_schema.rs +++ b/client/consensus/beefy/src/aux_schema.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -26,25 +26,25 @@ use sp_blockchain::{Error as ClientError, Result as ClientResult}; use sp_runtime::traits::Block as BlockT; const VERSION_KEY: &[u8] = b"beefy_auxschema_version"; -const WORKER_STATE: &[u8] = b"beefy_voter_state"; +const WORKER_STATE_KEY: &[u8] = b"beefy_voter_state"; -const CURRENT_VERSION: u32 = 1; +const CURRENT_VERSION: u32 = 4; -pub(crate) fn write_current_version(backend: &B) -> ClientResult<()> { +pub(crate) fn write_current_version(backend: &BE) -> ClientResult<()> { info!(target: LOG_TARGET, "🥩 write aux schema version {:?}", CURRENT_VERSION); AuxStore::insert_aux(backend, &[(VERSION_KEY, CURRENT_VERSION.encode().as_slice())], &[]) } /// Write voter state. -pub(crate) fn write_voter_state( - backend: &B, - state: &PersistedState, +pub(crate) fn write_voter_state( + backend: &BE, + state: &PersistedState, ) -> ClientResult<()> { trace!(target: LOG_TARGET, "🥩 persisting {:?}", state); - backend.insert_aux(&[(WORKER_STATE, state.encode().as_slice())], &[]) + AuxStore::insert_aux(backend, &[(WORKER_STATE_KEY, state.encode().as_slice())], &[]) } -fn load_decode(backend: &B, key: &[u8]) -> ClientResult> { +fn load_decode(backend: &BE, key: &[u8]) -> ClientResult> { match backend.get_aux(key)? { None => Ok(None), Some(t) => T::decode(&mut &t[..]) @@ -63,7 +63,8 @@ where match version { None => (), - Some(1) => return load_decode::<_, PersistedState>(backend, WORKER_STATE), + Some(1) | Some(2) | Some(3) => (), // versions 1, 2 & 3 are obsolete and should be ignored + Some(4) => return load_decode::<_, PersistedState>(backend, WORKER_STATE_KEY), other => return Err(ClientError::Backend(format!("Unsupported BEEFY DB version: {:?}", other))), } diff --git a/client/consensus/beefy/src/communication/gossip.rs b/client/consensus/beefy/src/communication/gossip.rs new file mode 100644 index 000000000000..9be648f8796c --- /dev/null +++ b/client/consensus/beefy/src/communication/gossip.rs @@ -0,0 +1,810 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 std::{collections::BTreeMap, sync::Arc, time::Duration}; + +use sc_network::{PeerId, ReputationChange}; +use sc_network_gossip::{MessageIntent, ValidationResult, Validator, ValidatorContext}; +use sp_core::hashing::twox_64; +use sp_runtime::traits::{Block, Hash, Header, NumberFor}; + +use codec::{Decode, Encode}; +use log::{debug, trace}; +use parking_lot::{Mutex, RwLock}; +use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; +use wasm_timer::Instant; + +use crate::{ + communication::{ + benefit, cost, + peers::{KnownPeers, PeerReport}, + }, + justification::{ + proof_block_num_and_set_id, verify_with_validator_set, BeefyVersionedFinalityProof, + }, + keystore::BeefyKeystore, + LOG_TARGET, +}; +use sp_consensus_beefy::{ + crypto::{AuthorityId, Signature}, + ValidatorSet, ValidatorSetId, VoteMessage, +}; + +// Timeout for rebroadcasting messages. +#[cfg(not(test))] +const REBROADCAST_AFTER: Duration = Duration::from_secs(60); +#[cfg(test)] +const REBROADCAST_AFTER: Duration = Duration::from_secs(5); + +#[derive(Debug, PartialEq)] +pub(super) enum Action { + // repropagate under given topic, to the given peers, applying cost/benefit to originator. + Keep(H, ReputationChange), + // discard, applying cost/benefit to originator. + Discard(ReputationChange), +} + +/// An outcome of examining a message. +#[derive(Debug, PartialEq, Clone, Copy)] +enum Consider { + /// Accept the message. + Accept, + /// Message is too early. Reject. + RejectPast, + /// Message is from the future. Reject. + RejectFuture, + /// Message cannot be evaluated. Reject. + RejectOutOfScope, +} + +/// BEEFY gossip message type that gets encoded and sent on the network. +#[derive(Debug, Encode, Decode)] +pub(crate) enum GossipMessage { + /// BEEFY message with commitment and single signature. + Vote(VoteMessage, AuthorityId, Signature>), + /// BEEFY justification with commitment and signatures. + FinalityProof(BeefyVersionedFinalityProof), +} + +impl GossipMessage { + /// Return inner vote if this message is a Vote. + pub fn unwrap_vote(self) -> Option, AuthorityId, Signature>> { + match self { + GossipMessage::Vote(vote) => Some(vote), + GossipMessage::FinalityProof(_) => None, + } + } + + /// Return inner finality proof if this message is a FinalityProof. + pub fn unwrap_finality_proof(self) -> Option> { + match self { + GossipMessage::Vote(_) => None, + GossipMessage::FinalityProof(proof) => Some(proof), + } + } +} + +/// Gossip engine votes messages topic +pub(crate) fn votes_topic() -> B::Hash +where + B: Block, +{ + <::Hashing as Hash>::hash(b"beefy-votes") +} + +/// Gossip engine justifications messages topic +pub(crate) fn proofs_topic() -> B::Hash +where + B: Block, +{ + <::Hashing as Hash>::hash(b"beefy-justifications") +} + +/// A type that represents hash of the message. +pub type MessageHash = [u8; 8]; + +#[derive(Clone, Debug)] +pub(crate) struct GossipFilterCfg<'a, B: Block> { + pub start: NumberFor, + pub end: NumberFor, + pub validator_set: &'a ValidatorSet, +} + +#[derive(Clone, Debug)] +struct FilterInner { + pub start: NumberFor, + pub end: NumberFor, + pub validator_set: ValidatorSet, +} + +struct Filter { + inner: Option>, + live_votes: BTreeMap, fnv::FnvHashSet>, +} + +impl Filter { + pub fn new() -> Self { + Self { inner: None, live_votes: BTreeMap::new() } + } + + /// Update filter to new `start` and `set_id`. + fn update(&mut self, cfg: GossipFilterCfg) { + self.live_votes.retain(|&round, _| round >= cfg.start && round <= cfg.end); + // only clone+overwrite big validator_set if set_id changed + match self.inner.as_mut() { + Some(f) if f.validator_set.id() == cfg.validator_set.id() => { + f.start = cfg.start; + f.end = cfg.end; + }, + _ => + self.inner = Some(FilterInner { + start: cfg.start, + end: cfg.end, + validator_set: cfg.validator_set.clone(), + }), + } + } + + /// Accept if `max(session_start, best_beefy) <= round <= best_grandpa`, + /// and vote `set_id` matches session set id. + /// + /// Latest concluded round is still considered alive to allow proper gossiping for it. + fn consider_vote(&self, round: NumberFor, set_id: ValidatorSetId) -> Consider { + self.inner + .as_ref() + .map(|f| + // only from current set and only [filter.start, filter.end] + if set_id < f.validator_set.id() { + Consider::RejectPast + } else if set_id > f.validator_set.id() { + Consider::RejectFuture + } else if round < f.start { + Consider::RejectPast + } else if round > f.end { + Consider::RejectFuture + } else { + Consider::Accept + }) + .unwrap_or(Consider::RejectOutOfScope) + } + + /// Return true if `round` is >= than `max(session_start, best_beefy)`, + /// and proof `set_id` matches session set id. + /// + /// Latest concluded round is still considered alive to allow proper gossiping for it. + fn consider_finality_proof(&self, round: NumberFor, set_id: ValidatorSetId) -> Consider { + self.inner + .as_ref() + .map(|f| + // only from current set and only >= filter.start + if round < f.start || set_id < f.validator_set.id() { + Consider::RejectPast + } else if set_id > f.validator_set.id() { + Consider::RejectFuture + } else { + Consider::Accept + } + ) + .unwrap_or(Consider::RejectOutOfScope) + } + + /// Add new _known_ `hash` to the round's known votes. + fn add_known_vote(&mut self, round: NumberFor, hash: MessageHash) { + self.live_votes.entry(round).or_default().insert(hash); + } + + /// Check if `hash` is already part of round's known votes. + fn is_known_vote(&self, round: NumberFor, hash: &MessageHash) -> bool { + self.live_votes.get(&round).map(|known| known.contains(hash)).unwrap_or(false) + } + + fn validator_set(&self) -> Option<&ValidatorSet> { + self.inner.as_ref().map(|f| &f.validator_set) + } +} + +/// BEEFY gossip validator +/// +/// Validate BEEFY gossip messages and limit the number of live BEEFY voting rounds. +/// +/// Allows messages for 'rounds >= last concluded' to flow, everything else gets +/// rejected/expired. +/// +///All messaging is handled in a single BEEFY global topic. +pub(crate) struct GossipValidator +where + B: Block, +{ + votes_topic: B::Hash, + justifs_topic: B::Hash, + gossip_filter: RwLock>, + next_rebroadcast: Mutex, + known_peers: Arc>>, + report_sender: TracingUnboundedSender, +} + +impl GossipValidator +where + B: Block, +{ + pub(crate) fn new( + known_peers: Arc>>, + ) -> (GossipValidator, TracingUnboundedReceiver) { + let (tx, rx) = tracing_unbounded("mpsc_beefy_gossip_validator", 10_000); + let val = GossipValidator { + votes_topic: votes_topic::(), + justifs_topic: proofs_topic::(), + gossip_filter: RwLock::new(Filter::new()), + next_rebroadcast: Mutex::new(Instant::now() + REBROADCAST_AFTER), + known_peers, + report_sender: tx, + }; + (val, rx) + } + + /// Update gossip validator filter. + /// + /// Only votes for `set_id` and rounds `start <= round <= end` will be accepted. + pub(crate) fn update_filter(&self, filter: GossipFilterCfg) { + debug!(target: LOG_TARGET, "🥩 New gossip filter {:?}", filter); + self.gossip_filter.write().update(filter); + } + + fn report(&self, who: PeerId, cost_benefit: ReputationChange) { + let _ = self.report_sender.unbounded_send(PeerReport { who, cost_benefit }); + } + + fn validate_vote( + &self, + vote: VoteMessage, AuthorityId, Signature>, + sender: &PeerId, + data: &[u8], + ) -> Action { + let msg_hash = twox_64(data); + let round = vote.commitment.block_number; + let set_id = vote.commitment.validator_set_id; + self.known_peers.lock().note_vote_for(*sender, round); + + // Verify general usefulness of the message. + // We are going to discard old votes right away (without verification) + // Also we keep track of already received votes to avoid verifying duplicates. + { + let filter = self.gossip_filter.read(); + + match filter.consider_vote(round, set_id) { + Consider::RejectPast => return Action::Discard(cost::OUTDATED_MESSAGE), + Consider::RejectFuture => return Action::Discard(cost::FUTURE_MESSAGE), + Consider::RejectOutOfScope => return Action::Discard(cost::OUT_OF_SCOPE_MESSAGE), + Consider::Accept => {}, + } + + if filter.is_known_vote(round, &msg_hash) { + return Action::Keep(self.votes_topic, benefit::KNOWN_VOTE_MESSAGE) + } + + // ensure authority is part of the set. + if !filter + .validator_set() + .map(|set| set.validators().contains(&vote.id)) + .unwrap_or(false) + { + debug!(target: LOG_TARGET, "Message from voter not in validator set: {}", vote.id); + return Action::Discard(cost::UNKNOWN_VOTER) + } + } + + if BeefyKeystore::verify(&vote.id, &vote.signature, &vote.commitment.encode()) { + self.gossip_filter.write().add_known_vote(round, msg_hash); + Action::Keep(self.votes_topic, benefit::VOTE_MESSAGE) + } else { + debug!( + target: LOG_TARGET, + "🥩 Bad signature on message: {:?}, from: {:?}", vote, sender + ); + Action::Discard(cost::BAD_SIGNATURE) + } + } + + fn validate_finality_proof( + &self, + proof: BeefyVersionedFinalityProof, + sender: &PeerId, + ) -> Action { + let (round, set_id) = proof_block_num_and_set_id::(&proof); + self.known_peers.lock().note_vote_for(*sender, round); + + let guard = self.gossip_filter.read(); + // Verify general usefulness of the justification. + match guard.consider_finality_proof(round, set_id) { + Consider::RejectPast => return Action::Discard(cost::OUTDATED_MESSAGE), + Consider::RejectFuture => return Action::Discard(cost::FUTURE_MESSAGE), + Consider::RejectOutOfScope => return Action::Discard(cost::OUT_OF_SCOPE_MESSAGE), + Consider::Accept => {}, + } + // Verify justification signatures. + guard + .validator_set() + .map(|validator_set| { + if let Err((_, signatures_checked)) = + verify_with_validator_set::(round, validator_set, &proof) + { + debug!( + target: LOG_TARGET, + "🥩 Bad signatures on message: {:?}, from: {:?}", proof, sender + ); + let mut cost = cost::INVALID_PROOF; + cost.value += + cost::PER_SIGNATURE_CHECKED.saturating_mul(signatures_checked as i32); + Action::Discard(cost) + } else { + Action::Keep(self.justifs_topic, benefit::VALIDATED_PROOF) + } + }) + .unwrap_or(Action::Discard(cost::OUT_OF_SCOPE_MESSAGE)) + } +} + +impl Validator for GossipValidator +where + B: Block, +{ + fn peer_disconnected(&self, _context: &mut dyn ValidatorContext, who: &PeerId) { + self.known_peers.lock().remove(who); + } + + fn validate( + &self, + context: &mut dyn ValidatorContext, + sender: &PeerId, + mut data: &[u8], + ) -> ValidationResult { + let raw = data; + let action = match GossipMessage::::decode(&mut data) { + Ok(GossipMessage::Vote(msg)) => self.validate_vote(msg, sender, raw), + Ok(GossipMessage::FinalityProof(proof)) => self.validate_finality_proof(proof, sender), + Err(e) => { + debug!(target: LOG_TARGET, "Error decoding message: {}", e); + let bytes = raw.len().min(i32::MAX as usize) as i32; + let cost = ReputationChange::new( + bytes.saturating_mul(cost::PER_UNDECODABLE_BYTE), + "BEEFY: Bad packet", + ); + Action::Discard(cost) + }, + }; + match action { + Action::Keep(topic, cb) => { + self.report(*sender, cb); + context.broadcast_message(topic, data.to_vec(), false); + ValidationResult::ProcessAndKeep(topic) + }, + Action::Discard(cb) => { + self.report(*sender, cb); + ValidationResult::Discard + }, + } + } + + fn message_expired<'a>(&'a self) -> Box bool + 'a> { + let filter = self.gossip_filter.read(); + Box::new(move |_topic, mut data| match GossipMessage::::decode(&mut data) { + Ok(GossipMessage::Vote(msg)) => { + let round = msg.commitment.block_number; + let set_id = msg.commitment.validator_set_id; + let expired = filter.consider_vote(round, set_id) != Consider::Accept; + trace!(target: LOG_TARGET, "🥩 Vote for round #{} expired: {}", round, expired); + expired + }, + Ok(GossipMessage::FinalityProof(proof)) => { + let (round, set_id) = proof_block_num_and_set_id::(&proof); + let expired = filter.consider_finality_proof(round, set_id) != Consider::Accept; + trace!( + target: LOG_TARGET, + "🥩 Finality proof for round #{} expired: {}", + round, + expired + ); + expired + }, + Err(_) => true, + }) + } + + fn message_allowed<'a>( + &'a self, + ) -> Box bool + 'a> { + let do_rebroadcast = { + let now = Instant::now(); + let mut next_rebroadcast = self.next_rebroadcast.lock(); + if now >= *next_rebroadcast { + trace!(target: LOG_TARGET, "🥩 Gossip rebroadcast"); + *next_rebroadcast = now + REBROADCAST_AFTER; + true + } else { + false + } + }; + + let filter = self.gossip_filter.read(); + Box::new(move |_who, intent, _topic, mut data| { + if let MessageIntent::PeriodicRebroadcast = intent { + return do_rebroadcast + } + + match GossipMessage::::decode(&mut data) { + Ok(GossipMessage::Vote(msg)) => { + let round = msg.commitment.block_number; + let set_id = msg.commitment.validator_set_id; + let allowed = filter.consider_vote(round, set_id) == Consider::Accept; + trace!(target: LOG_TARGET, "🥩 Vote for round #{} allowed: {}", round, allowed); + allowed + }, + Ok(GossipMessage::FinalityProof(proof)) => { + let (round, set_id) = proof_block_num_and_set_id::(&proof); + let allowed = filter.consider_finality_proof(round, set_id) == Consider::Accept; + trace!( + target: LOG_TARGET, + "🥩 Finality proof for round #{} allowed: {}", + round, + allowed + ); + allowed + }, + Err(_) => false, + } + }) + } +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + use crate::keystore::BeefyKeystore; + use sc_network_test::Block; + use sp_consensus_beefy::{ + crypto::Signature, known_payloads, Commitment, Keyring, MmrRootHash, Payload, + SignedCommitment, VoteMessage, KEY_TYPE, + }; + use sp_keystore::{testing::MemoryKeystore, Keystore}; + + #[test] + fn known_votes_insert_remove() { + let mut filter = Filter::::new(); + let msg_hash = twox_64(b"data"); + let keys = vec![Keyring::Alice.public()]; + let validator_set = ValidatorSet::::new(keys.clone(), 1).unwrap(); + + filter.add_known_vote(1, msg_hash); + filter.add_known_vote(1, msg_hash); + filter.add_known_vote(2, msg_hash); + assert_eq!(filter.live_votes.len(), 2); + + filter.add_known_vote(3, msg_hash); + assert!(filter.is_known_vote(3, &msg_hash)); + assert!(!filter.is_known_vote(3, &twox_64(b"other"))); + assert!(!filter.is_known_vote(4, &msg_hash)); + assert_eq!(filter.live_votes.len(), 3); + + assert!(filter.inner.is_none()); + assert_eq!(filter.consider_vote(1, 1), Consider::RejectOutOfScope); + + filter.update(GossipFilterCfg { start: 3, end: 10, validator_set: &validator_set }); + assert_eq!(filter.live_votes.len(), 1); + assert!(filter.live_votes.contains_key(&3)); + assert_eq!(filter.consider_vote(2, 1), Consider::RejectPast); + assert_eq!(filter.consider_vote(3, 1), Consider::Accept); + assert_eq!(filter.consider_vote(4, 1), Consider::Accept); + assert_eq!(filter.consider_vote(20, 1), Consider::RejectFuture); + assert_eq!(filter.consider_vote(4, 2), Consider::RejectFuture); + + let validator_set = ValidatorSet::::new(keys, 2).unwrap(); + filter.update(GossipFilterCfg { start: 5, end: 10, validator_set: &validator_set }); + assert!(filter.live_votes.is_empty()); + } + + struct TestContext; + impl ValidatorContext for TestContext { + fn broadcast_topic(&mut self, _topic: B::Hash, _force: bool) { + todo!() + } + + fn broadcast_message(&mut self, _topic: B::Hash, _message: Vec, _force: bool) {} + + fn send_message(&mut self, _who: &sc_network::PeerId, _message: Vec) { + todo!() + } + + fn send_topic(&mut self, _who: &sc_network::PeerId, _topic: B::Hash, _force: bool) { + todo!() + } + } + + pub fn sign_commitment(who: &Keyring, commitment: &Commitment) -> Signature { + let store = MemoryKeystore::new(); + store.ecdsa_generate_new(KEY_TYPE, Some(&who.to_seed())).unwrap(); + let beefy_keystore: BeefyKeystore = Some(store.into()).into(); + beefy_keystore.sign(&who.public(), &commitment.encode()).unwrap() + } + + fn dummy_vote(block_number: u64) -> VoteMessage { + let payload = Payload::from_single_entry( + known_payloads::MMR_ROOT_ID, + MmrRootHash::default().encode(), + ); + let commitment = Commitment { payload, block_number, validator_set_id: 0 }; + let signature = sign_commitment(&Keyring::Alice, &commitment); + + VoteMessage { commitment, id: Keyring::Alice.public(), signature } + } + + pub fn dummy_proof( + block_number: u64, + validator_set: &ValidatorSet, + ) -> BeefyVersionedFinalityProof { + let payload = Payload::from_single_entry( + known_payloads::MMR_ROOT_ID, + MmrRootHash::default().encode(), + ); + let commitment = Commitment { payload, block_number, validator_set_id: validator_set.id() }; + let signatures = validator_set + .validators() + .iter() + .map(|validator: &AuthorityId| { + Some(sign_commitment(&Keyring::from_public(validator).unwrap(), &commitment)) + }) + .collect(); + + BeefyVersionedFinalityProof::::V1(SignedCommitment { commitment, signatures }) + } + + #[test] + fn should_validate_messages() { + let keys = vec![Keyring::Alice.public()]; + let validator_set = ValidatorSet::::new(keys.clone(), 0).unwrap(); + let (gv, mut report_stream) = + GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); + let sender = PeerId::random(); + let mut context = TestContext; + + // reject message, decoding error + let bad_encoding = b"0000000000".as_slice(); + let expected_cost = ReputationChange::new( + (bad_encoding.len() as i32).saturating_mul(cost::PER_UNDECODABLE_BYTE), + "BEEFY: Bad packet", + ); + let mut expected_report = PeerReport { who: sender, cost_benefit: expected_cost }; + let res = gv.validate(&mut context, &sender, bad_encoding); + assert!(matches!(res, ValidationResult::Discard)); + assert_eq!(report_stream.try_recv().unwrap(), expected_report); + + // verify votes validation + + let vote = dummy_vote(3); + let encoded = GossipMessage::::Vote(vote.clone()).encode(); + + // filter not initialized + let res = gv.validate(&mut context, &sender, &encoded); + assert!(matches!(res, ValidationResult::Discard)); + expected_report.cost_benefit = cost::OUT_OF_SCOPE_MESSAGE; + assert_eq!(report_stream.try_recv().unwrap(), expected_report); + + gv.update_filter(GossipFilterCfg { start: 0, end: 10, validator_set: &validator_set }); + // nothing in cache first time + let res = gv.validate(&mut context, &sender, &encoded); + assert!(matches!(res, ValidationResult::ProcessAndKeep(_))); + expected_report.cost_benefit = benefit::VOTE_MESSAGE; + assert_eq!(report_stream.try_recv().unwrap(), expected_report); + assert_eq!( + gv.gossip_filter + .read() + .live_votes + .get(&vote.commitment.block_number) + .map(|x| x.len()), + Some(1) + ); + + // second time we should hit the cache + let res = gv.validate(&mut context, &sender, &encoded); + assert!(matches!(res, ValidationResult::ProcessAndKeep(_))); + expected_report.cost_benefit = benefit::KNOWN_VOTE_MESSAGE; + assert_eq!(report_stream.try_recv().unwrap(), expected_report); + + // reject vote, voter not in validator set + let mut bad_vote = vote.clone(); + bad_vote.id = Keyring::Bob.public(); + let bad_vote = GossipMessage::::Vote(bad_vote).encode(); + let res = gv.validate(&mut context, &sender, &bad_vote); + assert!(matches!(res, ValidationResult::Discard)); + expected_report.cost_benefit = cost::UNKNOWN_VOTER; + assert_eq!(report_stream.try_recv().unwrap(), expected_report); + + // reject if the round is not GRANDPA finalized + gv.update_filter(GossipFilterCfg { start: 1, end: 2, validator_set: &validator_set }); + let number = vote.commitment.block_number; + let set_id = vote.commitment.validator_set_id; + assert_eq!(gv.gossip_filter.read().consider_vote(number, set_id), Consider::RejectFuture); + let res = gv.validate(&mut context, &sender, &encoded); + assert!(matches!(res, ValidationResult::Discard)); + expected_report.cost_benefit = cost::FUTURE_MESSAGE; + assert_eq!(report_stream.try_recv().unwrap(), expected_report); + + // reject if the round is not live anymore + gv.update_filter(GossipFilterCfg { start: 7, end: 10, validator_set: &validator_set }); + let number = vote.commitment.block_number; + let set_id = vote.commitment.validator_set_id; + assert_eq!(gv.gossip_filter.read().consider_vote(number, set_id), Consider::RejectPast); + let res = gv.validate(&mut context, &sender, &encoded); + assert!(matches!(res, ValidationResult::Discard)); + expected_report.cost_benefit = cost::OUTDATED_MESSAGE; + assert_eq!(report_stream.try_recv().unwrap(), expected_report); + + // now verify proofs validation + + // reject old proof + let proof = dummy_proof(5, &validator_set); + let encoded_proof = GossipMessage::::FinalityProof(proof).encode(); + let res = gv.validate(&mut context, &sender, &encoded_proof); + assert!(matches!(res, ValidationResult::Discard)); + expected_report.cost_benefit = cost::OUTDATED_MESSAGE; + assert_eq!(report_stream.try_recv().unwrap(), expected_report); + + // accept next proof with good set_id + let proof = dummy_proof(7, &validator_set); + let encoded_proof = GossipMessage::::FinalityProof(proof).encode(); + let res = gv.validate(&mut context, &sender, &encoded_proof); + assert!(matches!(res, ValidationResult::ProcessAndKeep(_))); + expected_report.cost_benefit = benefit::VALIDATED_PROOF; + assert_eq!(report_stream.try_recv().unwrap(), expected_report); + + // accept future proof with good set_id + let proof = dummy_proof(20, &validator_set); + let encoded_proof = GossipMessage::::FinalityProof(proof).encode(); + let res = gv.validate(&mut context, &sender, &encoded_proof); + assert!(matches!(res, ValidationResult::ProcessAndKeep(_))); + expected_report.cost_benefit = benefit::VALIDATED_PROOF; + assert_eq!(report_stream.try_recv().unwrap(), expected_report); + + // reject proof, future set_id + let bad_validator_set = ValidatorSet::::new(keys, 1).unwrap(); + let proof = dummy_proof(20, &bad_validator_set); + let encoded_proof = GossipMessage::::FinalityProof(proof).encode(); + let res = gv.validate(&mut context, &sender, &encoded_proof); + assert!(matches!(res, ValidationResult::Discard)); + expected_report.cost_benefit = cost::FUTURE_MESSAGE; + assert_eq!(report_stream.try_recv().unwrap(), expected_report); + + // reject proof, bad signatures (Bob instead of Alice) + let bad_validator_set = + ValidatorSet::::new(vec![Keyring::Bob.public()], 0).unwrap(); + let proof = dummy_proof(20, &bad_validator_set); + let encoded_proof = GossipMessage::::FinalityProof(proof).encode(); + let res = gv.validate(&mut context, &sender, &encoded_proof); + assert!(matches!(res, ValidationResult::Discard)); + expected_report.cost_benefit = cost::INVALID_PROOF; + expected_report.cost_benefit.value += cost::PER_SIGNATURE_CHECKED; + assert_eq!(report_stream.try_recv().unwrap(), expected_report); + } + + #[test] + fn messages_allowed_and_expired() { + let keys = vec![Keyring::Alice.public()]; + let validator_set = ValidatorSet::::new(keys.clone(), 0).unwrap(); + let (gv, _) = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); + gv.update_filter(GossipFilterCfg { start: 0, end: 10, validator_set: &validator_set }); + let sender = sc_network::PeerId::random(); + let topic = Default::default(); + let intent = MessageIntent::Broadcast; + + // conclude 2 + gv.update_filter(GossipFilterCfg { start: 2, end: 10, validator_set: &validator_set }); + let mut allowed = gv.message_allowed(); + let mut expired = gv.message_expired(); + + // check bad vote format + assert!(!allowed(&sender, intent, &topic, &mut [0u8; 16])); + assert!(expired(topic, &mut [0u8; 16])); + + // inactive round 1 -> expired + let vote = dummy_vote(1); + let mut encoded_vote = GossipMessage::::Vote(vote).encode(); + assert!(!allowed(&sender, intent, &topic, &mut encoded_vote)); + assert!(expired(topic, &mut encoded_vote)); + let proof = dummy_proof(1, &validator_set); + let mut encoded_proof = GossipMessage::::FinalityProof(proof).encode(); + assert!(!allowed(&sender, intent, &topic, &mut encoded_proof)); + assert!(expired(topic, &mut encoded_proof)); + + // active round 2 -> !expired - concluded but still gossiped + let vote = dummy_vote(2); + let mut encoded_vote = GossipMessage::::Vote(vote).encode(); + assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); + assert!(!expired(topic, &mut encoded_vote)); + let proof = dummy_proof(2, &validator_set); + let mut encoded_proof = GossipMessage::::FinalityProof(proof).encode(); + assert!(allowed(&sender, intent, &topic, &mut encoded_proof)); + assert!(!expired(topic, &mut encoded_proof)); + // using wrong set_id -> !allowed, expired + let bad_validator_set = ValidatorSet::::new(keys.clone(), 1).unwrap(); + let proof = dummy_proof(2, &bad_validator_set); + let mut encoded_proof = GossipMessage::::FinalityProof(proof).encode(); + assert!(!allowed(&sender, intent, &topic, &mut encoded_proof)); + assert!(expired(topic, &mut encoded_proof)); + + // in progress round 3 -> !expired + let vote = dummy_vote(3); + let mut encoded_vote = GossipMessage::::Vote(vote).encode(); + assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); + assert!(!expired(topic, &mut encoded_vote)); + let proof = dummy_proof(3, &validator_set); + let mut encoded_proof = GossipMessage::::FinalityProof(proof).encode(); + assert!(allowed(&sender, intent, &topic, &mut encoded_proof)); + assert!(!expired(topic, &mut encoded_proof)); + + // unseen round 4 -> !expired + let vote = dummy_vote(4); + let mut encoded_vote = GossipMessage::::Vote(vote).encode(); + assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); + assert!(!expired(topic, &mut encoded_vote)); + let proof = dummy_proof(4, &validator_set); + let mut encoded_proof = GossipMessage::::FinalityProof(proof).encode(); + assert!(allowed(&sender, intent, &topic, &mut encoded_proof)); + assert!(!expired(topic, &mut encoded_proof)); + + // future round 11 -> expired + let vote = dummy_vote(11); + let mut encoded_vote = GossipMessage::::Vote(vote).encode(); + assert!(!allowed(&sender, intent, &topic, &mut encoded_vote)); + assert!(expired(topic, &mut encoded_vote)); + // future proofs allowed while same set_id -> allowed + let proof = dummy_proof(11, &validator_set); + let mut encoded_proof = GossipMessage::::FinalityProof(proof).encode(); + assert!(allowed(&sender, intent, &topic, &mut encoded_proof)); + assert!(!expired(topic, &mut encoded_proof)); + } + + #[test] + fn messages_rebroadcast() { + let keys = vec![Keyring::Alice.public()]; + let validator_set = ValidatorSet::::new(keys.clone(), 0).unwrap(); + let (gv, _) = GossipValidator::::new(Arc::new(Mutex::new(KnownPeers::new()))); + gv.update_filter(GossipFilterCfg { start: 0, end: 10, validator_set: &validator_set }); + let sender = sc_network::PeerId::random(); + let topic = Default::default(); + + let vote = dummy_vote(1); + let mut encoded_vote = vote.encode(); + + // re-broadcasting only allowed at `REBROADCAST_AFTER` intervals + let intent = MessageIntent::PeriodicRebroadcast; + let mut allowed = gv.message_allowed(); + + // rebroadcast not allowed so soon after GossipValidator creation + assert!(!allowed(&sender, intent, &topic, &mut encoded_vote)); + + // hack the inner deadline to be `now` + *gv.next_rebroadcast.lock() = Instant::now(); + + // still not allowed on old `allowed` closure result + assert!(!allowed(&sender, intent, &topic, &mut encoded_vote)); + + // renew closure result + let mut allowed = gv.message_allowed(); + // rebroadcast should be allowed now + assert!(allowed(&sender, intent, &topic, &mut encoded_vote)); + } +} diff --git a/client/beefy/src/communication/mod.rs b/client/consensus/beefy/src/communication/mod.rs similarity index 62% rename from client/beefy/src/communication/mod.rs rename to client/consensus/beefy/src/communication/mod.rs index 91798d4ae0d3..7f9535bfc23f 100644 --- a/client/beefy/src/communication/mod.rs +++ b/client/consensus/beefy/src/communication/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -29,7 +29,7 @@ pub(crate) mod beefy_protocol_name { use sc_network::ProtocolName; /// BEEFY votes gossip protocol name suffix. - const GOSSIP_NAME: &str = "/beefy/1"; + const GOSSIP_NAME: &str = "/beefy/2"; /// BEEFY justifications protocol name suffix. const JUSTIFICATIONS_NAME: &str = "/beefy/justifications/1"; @@ -63,17 +63,49 @@ pub(crate) mod beefy_protocol_name { } /// Returns the configuration value to put in -/// [`sc_network::config::NetworkConfiguration::extra_sets`]. +/// [`sc_network::config::FullNetworkConfiguration`]. /// For standard protocol name see [`beefy_protocol_name::gossip_protocol_name`]. pub fn beefy_peers_set_config( gossip_protocol_name: sc_network::ProtocolName, -) -> sc_network_common::config::NonDefaultSetConfig { - let mut cfg = - sc_network_common::config::NonDefaultSetConfig::new(gossip_protocol_name, 1024 * 1024); +) -> sc_network::config::NonDefaultSetConfig { + let mut cfg = sc_network::config::NonDefaultSetConfig::new(gossip_protocol_name, 1024 * 1024); cfg.allow_non_reserved(25, 25); cfg } +// cost scalars for reporting peers. +mod cost { + use sc_network::ReputationChange as Rep; + // Message that's for an outdated round. + pub(super) const OUTDATED_MESSAGE: Rep = Rep::new(-50, "BEEFY: Past message"); + // Message that's from the future relative to our current set-id. + pub(super) const FUTURE_MESSAGE: Rep = Rep::new(-100, "BEEFY: Future message"); + // Vote message containing bad signature. + pub(super) const BAD_SIGNATURE: Rep = Rep::new(-100, "BEEFY: Bad signature"); + // Message received with vote from voter not in validator set. + pub(super) const UNKNOWN_VOTER: Rep = Rep::new(-150, "BEEFY: Unknown voter"); + // A message received that cannot be evaluated relative to our current state. + pub(super) const OUT_OF_SCOPE_MESSAGE: Rep = Rep::new(-500, "BEEFY: Out-of-scope message"); + // Message containing invalid proof. + pub(super) const INVALID_PROOF: Rep = Rep::new(-5000, "BEEFY: Invalid commit"); + // Reputation cost per signature checked for invalid proof. + pub(super) const PER_SIGNATURE_CHECKED: i32 = -25; + // Reputation cost per byte for un-decodable message. + pub(super) const PER_UNDECODABLE_BYTE: i32 = -5; + // On-demand request was refused by peer. + pub(super) const REFUSAL_RESPONSE: Rep = Rep::new(-100, "BEEFY: Proof request refused"); + // On-demand request for a proof that can't be found in the backend. + pub(super) const UNKOWN_PROOF_REQUEST: Rep = Rep::new(-150, "BEEFY: Unknown proof request"); +} + +// benefit scalars for reporting peers. +mod benefit { + use sc_network::ReputationChange as Rep; + pub(super) const VOTE_MESSAGE: Rep = Rep::new(100, "BEEFY: Round vote message"); + pub(super) const KNOWN_VOTE_MESSAGE: Rep = Rep::new(50, "BEEFY: Known vote"); + pub(super) const VALIDATED_PROOF: Rep = Rep::new(100, "BEEFY: Justification"); +} + #[cfg(test)] mod tests { use super::*; @@ -85,9 +117,9 @@ mod tests { use beefy_protocol_name::{gossip_protocol_name, justifications_protocol_name}; // Create protocol name using random genesis hash. let genesis_hash = H256::random(); - let genesis_hex = array_bytes::bytes2hex("", genesis_hash.as_ref()); + let genesis_hex = array_bytes::bytes2hex("", genesis_hash); - let expected_gossip_name = format!("/{}/beefy/1", genesis_hex); + let expected_gossip_name = format!("/{}/beefy/2", genesis_hex); let gossip_proto_name = gossip_protocol_name(&genesis_hash, None); assert_eq!(gossip_proto_name.to_string(), expected_gossip_name); @@ -102,7 +134,7 @@ mod tests { ]; let genesis_hex = "32043c7b3a6ad8f6c2bc8bc121d4caab09377b5e082b0cfbbb39ad13bc4acd93"; - let expected_gossip_name = format!("/{}/beefy/1", genesis_hex); + let expected_gossip_name = format!("/{}/beefy/2", genesis_hex); let gossip_proto_name = gossip_protocol_name(&genesis_hash, None); assert_eq!(gossip_proto_name.to_string(), expected_gossip_name); diff --git a/client/beefy/src/communication/notification.rs b/client/consensus/beefy/src/communication/notification.rs similarity index 97% rename from client/beefy/src/communication/notification.rs rename to client/consensus/beefy/src/communication/notification.rs index c673115e487f..a4486e523c30 100644 --- a/client/beefy/src/communication/notification.rs +++ b/client/consensus/beefy/src/communication/notification.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/beefy/src/communication/peers.rs b/client/consensus/beefy/src/communication/peers.rs similarity index 74% rename from client/beefy/src/communication/peers.rs rename to client/consensus/beefy/src/communication/peers.rs index d7927ea72e66..4704b8dcf457 100644 --- a/client/beefy/src/communication/peers.rs +++ b/client/consensus/beefy/src/communication/peers.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,13 +18,17 @@ //! Logic for keeping track of BEEFY peers. -// TODO (issue #12296): replace this naive peer tracking with generic one that infers data -// from multiple network protocols. - -use sc_network::PeerId; +use sc_network::{PeerId, ReputationChange}; use sp_runtime::traits::{Block, NumberFor, Zero}; use std::collections::{HashMap, VecDeque}; +/// Report specifying a reputation change for a given peer. +#[derive(Debug, PartialEq)] +pub(crate) struct PeerReport { + pub who: PeerId, + pub cost_benefit: ReputationChange, +} + struct PeerData { last_voted_on: NumberFor, } @@ -57,11 +61,11 @@ impl KnownPeers { self.live.remove(peer); } - /// Return _filtered and cloned_ list of peers that have voted on `block` or higher. - pub fn at_least_at_block(&self, block: NumberFor) -> VecDeque { + /// Return _filtered and cloned_ list of peers that have voted on higher than `block`. + pub fn further_than(&self, block: NumberFor) -> VecDeque { self.live .iter() - .filter_map(|(k, v)| (v.last_voted_on >= block).then_some(k)) + .filter_map(|(k, v)| (v.last_voted_on > block).then_some(k)) .cloned() .collect() } @@ -92,22 +96,22 @@ mod tests { assert!(peers.contains(&bob)); assert!(peers.contains(&charlie)); - // Get peers at block >= 5 - let at_5 = peers.at_least_at_block(5); + // Get peers at block > 4 + let further_than_4 = peers.further_than(4); // Should be Bob and Charlie - assert_eq!(at_5.len(), 2); - assert!(at_5.contains(&bob)); - assert!(at_5.contains(&charlie)); + assert_eq!(further_than_4.len(), 2); + assert!(further_than_4.contains(&bob)); + assert!(further_than_4.contains(&charlie)); // 'Tracked' Alice seen voting for 10. peers.note_vote_for(alice, 10); - // Get peers at block >= 9 - let at_9 = peers.at_least_at_block(9); + // Get peers at block > 9 + let further_than_9 = peers.further_than(9); // Should be Charlie and Alice - assert_eq!(at_9.len(), 2); - assert!(at_9.contains(&charlie)); - assert!(at_9.contains(&alice)); + assert_eq!(further_than_9.len(), 2); + assert!(further_than_9.contains(&charlie)); + assert!(further_than_9.contains(&alice)); // Remove Alice peers.remove(&alice); @@ -115,9 +119,9 @@ mod tests { assert!(!peers.contains(&alice)); // Get peers at block >= 9 - let at_9 = peers.at_least_at_block(9); + let further_than_9 = peers.further_than(9); // Now should be just Charlie - assert_eq!(at_9.len(), 1); - assert!(at_9.contains(&charlie)); + assert_eq!(further_than_9.len(), 1); + assert!(further_than_9.contains(&charlie)); } } diff --git a/client/beefy/src/communication/request_response/incoming_requests_handler.rs b/client/consensus/beefy/src/communication/request_response/incoming_requests_handler.rs similarity index 69% rename from client/beefy/src/communication/request_response/incoming_requests_handler.rs rename to client/consensus/beefy/src/communication/request_response/incoming_requests_handler.rs index d3be73dc06a4..46056ac3042f 100644 --- a/client/beefy/src/communication/request_response/incoming_requests_handler.rs +++ b/client/consensus/beefy/src/communication/request_response/incoming_requests_handler.rs @@ -1,4 +1,4 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify @@ -16,21 +16,28 @@ //! Helper for handling (i.e. answering) BEEFY justifications requests from a remote peer. -use beefy_primitives::BEEFY_ENGINE_ID; use codec::Decode; -use futures::{ - channel::{mpsc, oneshot}, - StreamExt, -}; -use log::{debug, trace}; +use futures::{channel::oneshot, StreamExt}; +use log::{debug, error, trace}; use sc_client_api::BlockBackend; -use sc_network::{config as netconfig, config::RequestResponseConfig, PeerId, ReputationChange}; -use sc_network_common::protocol::ProtocolName; +use sc_network::{ + config as netconfig, config::RequestResponseConfig, types::ProtocolName, PeerId, + ReputationChange, +}; +use sp_consensus_beefy::BEEFY_ENGINE_ID; use sp_runtime::traits::Block; use std::{marker::PhantomData, sync::Arc}; -use crate::communication::request_response::{ - on_demand_justifications_protocol_config, Error, JustificationRequest, BEEFY_SYNC_LOG_TARGET, +use crate::{ + communication::{ + cost, + request_response::{ + on_demand_justifications_protocol_config, Error, JustificationRequest, + BEEFY_SYNC_LOG_TARGET, + }, + }, + metric_inc, + metrics::{register_metrics, OnDemandIncomingRequestsMetrics}, }; /// A request coming in, including a sender for sending responses. @@ -62,17 +69,20 @@ impl IncomingRequest { /// Params: /// - The raw request to decode /// - Reputation changes to apply for the peer in case decoding fails. - pub fn try_from_raw( + pub fn try_from_raw( raw: netconfig::IncomingRequest, - reputation_changes: Vec, - ) -> Result { + reputation_changes_on_err: F, + ) -> Result + where + F: FnOnce(usize) -> Vec, + { let netconfig::IncomingRequest { payload, peer, pending_response } = raw; let payload = match JustificationRequest::decode(&mut payload.as_ref()) { Ok(payload) => payload, Err(err) => { let response = netconfig::OutgoingResponse { result: Err(()), - reputation_changes, + reputation_changes: reputation_changes_on_err(payload.len()), sent_feedback: None, }; if let Err(_) = pending_response.send(response) { @@ -89,11 +99,11 @@ impl IncomingRequest { /// /// Takes care of decoding and handling of invalid encoded requests. pub(crate) struct IncomingRequestReceiver { - raw: mpsc::Receiver, + raw: async_channel::Receiver, } impl IncomingRequestReceiver { - pub fn new(inner: mpsc::Receiver) -> Self { + pub fn new(inner: async_channel::Receiver) -> Self { Self { raw: inner } } @@ -104,11 +114,11 @@ impl IncomingRequestReceiver { pub async fn recv(&mut self, reputation_changes: F) -> Result, Error> where B: Block, - F: FnOnce() -> Vec, + F: FnOnce(usize) -> Vec, { let req = match self.raw.next().await { None => return Err(Error::RequestChannelExhausted), - Some(raw) => IncomingRequest::::try_from_raw(raw, reputation_changes())?, + Some(raw) => IncomingRequest::::try_from_raw(raw, reputation_changes)?, }; Ok(req) } @@ -119,6 +129,7 @@ pub struct BeefyJustifsRequestHandler { pub(crate) request_receiver: IncomingRequestReceiver, pub(crate) justif_protocol_name: ProtocolName, pub(crate) client: Arc, + pub(crate) metrics: Option, pub(crate) _block: PhantomData, } @@ -132,12 +143,16 @@ where genesis_hash: Hash, fork_id: Option<&str>, client: Arc, + prometheus_registry: Option, ) -> (Self, RequestResponseConfig) { let (request_receiver, config) = on_demand_justifications_protocol_config(genesis_hash, fork_id); let justif_protocol_name = config.name.clone(); - - (Self { request_receiver, justif_protocol_name, client, _block: PhantomData }, config) + let metrics = register_metrics(prometheus_registry); + ( + Self { request_receiver, justif_protocol_name, client, metrics, _block: PhantomData }, + config, + ) } /// Network request-response protocol name used by this handler. @@ -147,26 +162,20 @@ where // Sends back justification response if justification found in client backend. fn handle_request(&self, request: IncomingRequest) -> Result<(), Error> { - // TODO (issue #12293): validate `request` and change peer reputation for invalid requests. - - let maybe_encoded_proof = if let Some(hash) = - self.client.block_hash(request.payload.begin).map_err(Error::Client)? - { - self.client - .justifications(hash) - .map_err(Error::Client)? - .and_then(|justifs| justifs.get(BEEFY_ENGINE_ID).cloned()) - // No BEEFY justification present. - .ok_or(()) - } else { - Err(()) - }; - + let mut reputation_changes = vec![]; + let maybe_encoded_proof = self + .client + .block_hash(request.payload.begin) + .ok() + .flatten() + .and_then(|hash| self.client.justifications(hash).ok().flatten()) + .and_then(|justifs| justifs.get(BEEFY_ENGINE_ID).cloned()) + .ok_or_else(|| reputation_changes.push(cost::UNKOWN_PROOF_REQUEST)); request .pending_response .send(netconfig::OutgoingResponse { result: maybe_encoded_proof, - reputation_changes: Vec::new(), + reputation_changes, sent_feedback: None, }) .map_err(|_| Error::SendResponse) @@ -176,17 +185,29 @@ where pub async fn run(mut self) { trace!(target: BEEFY_SYNC_LOG_TARGET, "🥩 Running BeefyJustifsRequestHandler"); - while let Ok(request) = self.request_receiver.recv(|| vec![]).await { + while let Ok(request) = self + .request_receiver + .recv(|bytes| { + let bytes = bytes.min(i32::MAX as usize) as i32; + vec![ReputationChange::new( + bytes.saturating_mul(cost::PER_UNDECODABLE_BYTE), + "BEEFY: Bad request payload", + )] + }) + .await + { let peer = request.peer; match self.handle_request(request) { Ok(()) => { + metric_inc!(self, beefy_successful_justification_responses); debug!( target: BEEFY_SYNC_LOG_TARGET, "🥩 Handled BEEFY justification request from {:?}.", peer ) }, Err(e) => { - // TODO (issue #12293): apply reputation changes here based on error type. + // peer reputation changes already applied in `self.handle_request()` + metric_inc!(self, beefy_failed_justification_responses); debug!( target: BEEFY_SYNC_LOG_TARGET, "🥩 Failed to handle BEEFY justification request from {:?}: {}", peer, e, @@ -194,5 +215,9 @@ where }, } } + error!( + target: crate::LOG_TARGET, + "🥩 On-demand requests receiver stream terminated, closing worker." + ); } } diff --git a/client/beefy/src/communication/request_response/mod.rs b/client/consensus/beefy/src/communication/request_response/mod.rs similarity index 91% rename from client/beefy/src/communication/request_response/mod.rs rename to client/consensus/beefy/src/communication/request_response/mod.rs index 22a20c247086..1801512fa542 100644 --- a/client/beefy/src/communication/request_response/mod.rs +++ b/client/consensus/beefy/src/communication/request_response/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -23,14 +23,13 @@ pub(crate) mod outgoing_requests_engine; pub use incoming_requests_handler::BeefyJustifsRequestHandler; -use futures::channel::mpsc; use std::time::Duration; use codec::{Decode, Encode, Error as CodecError}; use sc_network::{config::RequestResponseConfig, PeerId}; use sp_runtime::traits::{Block, NumberFor}; -use crate::communication::beefy_protocol_name::justifications_protocol_name; +use crate::communication::{beefy_protocol_name::justifications_protocol_name, peers::PeerReport}; use incoming_requests_handler::IncomingRequestReceiver; // 10 seems reasonable, considering justifs are explicitly requested only @@ -54,7 +53,7 @@ pub(crate) fn on_demand_justifications_protocol_config>( ) -> (IncomingRequestReceiver, RequestResponseConfig) { let name = justifications_protocol_name(genesis_hash, fork_id); let fallback_names = vec![]; - let (tx, rx) = mpsc::channel(JUSTIF_CHANNEL_SIZE); + let (tx, rx) = async_channel::bounded(JUSTIF_CHANNEL_SIZE); let rx = IncomingRequestReceiver::new(rx); let cfg = RequestResponseConfig { name, @@ -76,7 +75,7 @@ pub struct JustificationRequest { } #[derive(Debug, thiserror::Error)] -pub enum Error { +pub(crate) enum Error { #[error(transparent)] Client(#[from] sp_blockchain::Error), @@ -99,5 +98,8 @@ pub enum Error { SendResponse, #[error("Received invalid response.")] - InvalidResponse, + InvalidResponse(PeerReport), + + #[error("Internal error while getting response.")] + ResponseError, } diff --git a/client/beefy/src/communication/request_response/outgoing_requests_engine.rs b/client/consensus/beefy/src/communication/request_response/outgoing_requests_engine.rs similarity index 70% rename from client/beefy/src/communication/request_response/outgoing_requests_engine.rs rename to client/consensus/beefy/src/communication/request_response/outgoing_requests_engine.rs index c7dc269b495c..10105ff2d417 100644 --- a/client/beefy/src/communication/request_response/outgoing_requests_engine.rs +++ b/client/consensus/beefy/src/communication/request_response/outgoing_requests_engine.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,22 +18,27 @@ //! Generating request logic for request/response protocol for syncing BEEFY justifications. -use beefy_primitives::{crypto::AuthorityId, ValidatorSet}; use codec::Encode; use futures::channel::{oneshot, oneshot::Canceled}; use log::{debug, warn}; use parking_lot::Mutex; -use sc_network::{PeerId, ProtocolName}; -use sc_network_common::{ +use sc_network::{ request_responses::{IfDisconnected, RequestFailure}, - service::NetworkRequest, + NetworkRequest, PeerId, ProtocolName, }; +use sp_consensus_beefy::{crypto::AuthorityId, ValidatorSet}; use sp_runtime::traits::{Block, NumberFor}; use std::{collections::VecDeque, result::Result, sync::Arc}; use crate::{ - communication::request_response::{Error, JustificationRequest, BEEFY_SYNC_LOG_TARGET}, + communication::{ + benefit, cost, + peers::PeerReport, + request_response::{Error, JustificationRequest, BEEFY_SYNC_LOG_TARGET}, + }, justification::{decode_and_verify_finality_proof, BeefyVersionedFinalityProof}, + metric_inc, + metrics::{register_metrics, OnDemandOutgoingRequestsMetrics}, KnownPeers, }; @@ -53,6 +58,16 @@ enum State { AwaitingResponse(PeerId, RequestInfo, ResponseReceiver), } +/// Possible engine responses. +pub(crate) enum ResponseInfo { + /// No peer response available yet. + Pending, + /// Valid justification provided alongside peer reputation changes. + ValidProof(BeefyVersionedFinalityProof, PeerReport), + /// No justification yet, only peer reputation changes. + PeerReport(PeerReport), +} + pub struct OnDemandJustificationsEngine { network: Arc, protocol_name: ProtocolName, @@ -61,6 +76,7 @@ pub struct OnDemandJustificationsEngine { peers_cache: VecDeque, state: State, + metrics: Option, } impl OnDemandJustificationsEngine { @@ -68,23 +84,24 @@ impl OnDemandJustificationsEngine { network: Arc, protocol_name: ProtocolName, live_peers: Arc>>, + prometheus_registry: Option, ) -> Self { + let metrics = register_metrics(prometheus_registry); Self { network, protocol_name, live_peers, peers_cache: VecDeque::new(), state: State::Idle, + metrics, } } fn reset_peers_cache_for_block(&mut self, block: NumberFor) { - // TODO (issue #12296): replace peer selection with generic one that involves all protocols. - self.peers_cache = self.live_peers.lock().at_least_at_block(block); + self.peers_cache = self.live_peers.lock().further_than(block); } fn try_next_peer(&mut self) -> Option { - // TODO (issue #12296): replace peer selection with generic one that involves all protocols. let live = self.live_peers.lock(); while let Some(peer) = self.peers_cache.pop_front() { if live.contains(&peer) { @@ -130,6 +147,7 @@ impl OnDemandJustificationsEngine { if let Some(peer) = self.try_next_peer() { self.request_from_peer(peer, RequestInfo { block, active_set }); } else { + metric_inc!(self, beefy_on_demand_justification_no_peer_to_request_from); debug!( target: BEEFY_SYNC_LOG_TARGET, "🥩 no good peers to request justif #{:?} from", block @@ -153,7 +171,7 @@ impl OnDemandJustificationsEngine { fn process_response( &mut self, - peer: PeerId, + peer: &PeerId, req_info: &RequestInfo, response: Result, ) -> Result, Error> { @@ -161,12 +179,9 @@ impl OnDemandJustificationsEngine { .map_err(|e| { debug!( target: BEEFY_SYNC_LOG_TARGET, - "🥩 for on demand justification #{:?}, peer {:?} hung up: {:?}", - req_info.block, - peer, - e + "🥩 on-demand sc-network channel sender closed, err: {:?}", e ); - Error::InvalidResponse + Error::ResponseError })? .map_err(|e| { debug!( @@ -176,7 +191,18 @@ impl OnDemandJustificationsEngine { peer, e ); - Error::InvalidResponse + match e { + RequestFailure::Refused => { + metric_inc!(self, beefy_on_demand_justification_peer_refused); + let peer_report = + PeerReport { who: *peer, cost_benefit: cost::REFUSAL_RESPONSE }; + Error::InvalidResponse(peer_report) + }, + _ => { + metric_inc!(self, beefy_on_demand_justification_peer_error); + Error::ResponseError + }, + } }) .and_then(|encoded| { decode_and_verify_finality_proof::( @@ -184,23 +210,26 @@ impl OnDemandJustificationsEngine { req_info.block, &req_info.active_set, ) - .map_err(|e| { + .map_err(|(err, signatures_checked)| { + metric_inc!(self, beefy_on_demand_justification_invalid_proof); debug!( target: BEEFY_SYNC_LOG_TARGET, "🥩 for on demand justification #{:?}, peer {:?} responded with invalid proof: {:?}", - req_info.block, peer, e + req_info.block, peer, err ); - Error::InvalidResponse + let mut cost = cost::INVALID_PROOF; + cost.value += + cost::PER_SIGNATURE_CHECKED.saturating_mul(signatures_checked as i32); + Error::InvalidResponse(PeerReport { who: *peer, cost_benefit: cost }) }) }) } - pub async fn next(&mut self) -> Option> { + pub(crate) async fn next(&mut self) -> ResponseInfo { let (peer, req_info, resp) = match &mut self.state { State::Idle => { - futures::pending!(); - // Doesn't happen as 'futures::pending!()' is an 'await' barrier that never passes. - return None + futures::future::pending::<()>().await; + return ResponseInfo::Pending }, State::AwaitingResponse(peer, req_info, receiver) => { let resp = receiver.await; @@ -212,8 +241,8 @@ impl OnDemandJustificationsEngine { self.state = State::Idle; let block = req_info.block; - self.process_response(peer, &req_info, resp) - .map_err(|_| { + match self.process_response(&peer, &req_info, resp) { + Err(err) => { // No valid justification received, try next peer in our set. if let Some(peer) = self.try_next_peer() { self.request_from_peer(peer, req_info); @@ -223,14 +252,22 @@ impl OnDemandJustificationsEngine { "🥩 ran out of peers to request justif #{:?} from", block ); } - }) - .map(|proof| { + // Report peer based on error type. + if let Error::InvalidResponse(peer_report) = err { + ResponseInfo::PeerReport(peer_report) + } else { + ResponseInfo::Pending + } + }, + Ok(proof) => { + metric_inc!(self, beefy_on_demand_justification_good_proof); debug!( target: BEEFY_SYNC_LOG_TARGET, "🥩 received valid on-demand justif #{:?} from {:?}", block, peer ); - proof - }) - .ok() + let peer_report = PeerReport { who: peer, cost_benefit: benefit::VALIDATED_PROOF }; + ResponseInfo::ValidProof(proof, peer_report) + }, + } } } diff --git a/client/consensus/beefy/src/error.rs b/client/consensus/beefy/src/error.rs new file mode 100644 index 000000000000..08b9960f41a1 --- /dev/null +++ b/client/consensus/beefy/src/error.rs @@ -0,0 +1,54 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +//! BEEFY gadget specific errors +//! +//! Used for BEEFY gadget internal error handling only + +use std::fmt::Debug; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("Backend: {0}")] + Backend(String), + #[error("Keystore error: {0}")] + Keystore(String), + #[error("Runtime api error: {0}")] + RuntimeApi(sp_api::ApiError), + #[error("Signature error: {0}")] + Signature(String), + #[error("Session uninitialized")] + UninitSession, + #[error("pallet-beefy was reset, please restart voter")] + ConsensusReset, +} + +#[cfg(test)] +impl PartialEq for Error { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Error::Backend(s1), Error::Backend(s2)) => s1 == s2, + (Error::Keystore(s1), Error::Keystore(s2)) => s1 == s2, + (Error::RuntimeApi(_), Error::RuntimeApi(_)) => true, + (Error::Signature(s1), Error::Signature(s2)) => s1 == s2, + (Error::UninitSession, Error::UninitSession) => true, + (Error::ConsensusReset, Error::ConsensusReset) => true, + _ => false, + } + } +} diff --git a/client/beefy/src/import.rs b/client/consensus/beefy/src/import.rs similarity index 72% rename from client/beefy/src/import.rs rename to client/consensus/beefy/src/import.rs index f0fe3bfa024d..bda8169d9501 100644 --- a/client/beefy/src/import.rs +++ b/client/consensus/beefy/src/import.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,15 +16,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use beefy_primitives::{BeefyApi, BEEFY_ENGINE_ID}; +use std::sync::Arc; + use log::debug; -use std::{collections::HashMap, sync::Arc}; use sp_api::{ProvideRuntimeApi, TransactionFor}; -use sp_blockchain::well_known_cache_keys; use sp_consensus::Error as ConsensusError; +use sp_consensus_beefy::{BeefyApi, BEEFY_ENGINE_ID}; use sp_runtime::{ - generic::BlockId, traits::{Block as BlockT, Header as HeaderT, NumberFor}, EncodedJustification, }; @@ -35,6 +34,8 @@ use sc_consensus::{BlockCheckParams, BlockImport, BlockImportParams, ImportResul use crate::{ communication::notification::BeefyVersionedFinalityProofSender, justification::{decode_and_verify_finality_proof, BeefyVersionedFinalityProof}, + metric_inc, + metrics::BlockImportMetrics, LOG_TARGET, }; @@ -49,6 +50,7 @@ pub struct BeefyBlockImport { runtime: Arc, inner: I, justification_sender: BeefyVersionedFinalityProofSender, + metrics: Option, } impl Clone for BeefyBlockImport { @@ -58,6 +60,7 @@ impl Clone for BeefyBlockImport BeefyBlockImport { runtime: Arc, inner: I, justification_sender: BeefyVersionedFinalityProofSender, + metrics: Option, ) -> BeefyBlockImport { - BeefyBlockImport { backend, runtime, inner, justification_sender } + BeefyBlockImport { backend, runtime, inner, justification_sender, metrics } } } @@ -87,15 +91,25 @@ where number: NumberFor, hash: ::Hash, ) -> Result, ConsensusError> { - let block_id = BlockId::hash(hash); + use ConsensusError::ClientImport as ImportError; + let beefy_genesis = self + .runtime + .runtime_api() + .beefy_genesis(hash) + .map_err(|e| ImportError(e.to_string()))? + .ok_or_else(|| ImportError("Unknown BEEFY genesis".to_string()))?; + if number < beefy_genesis { + return Err(ImportError("BEEFY genesis is set for future block".to_string())) + } let validator_set = self .runtime .runtime_api() - .validator_set(&block_id) - .map_err(|e| ConsensusError::ClientImport(e.to_string()))? - .ok_or_else(|| ConsensusError::ClientImport("Unknown validator set".to_string()))?; + .validator_set(hash) + .map_err(|e| ImportError(e.to_string()))? + .ok_or_else(|| ImportError("Unknown validator set".to_string()))?; decode_and_verify_finality_proof::(&encoded[..], number, &validator_set) + .map_err(|(err, _)| err) } } @@ -119,7 +133,6 @@ where async fn import_block( &mut self, mut block: BlockImportParams, - new_cache: HashMap>, ) -> Result { let hash = block.post_hash(); let number = *block.header.number(); @@ -133,27 +146,32 @@ where }); // Run inner block import. - let inner_import_result = self.inner.import_block(block, new_cache).await?; + let inner_import_result = self.inner.import_block(block).await?; match (beefy_encoded, &inner_import_result) { (Some(encoded), ImportResult::Imported(_)) => { - if let Ok(proof) = self.decode_and_verify(&encoded, number, hash) { - // The proof is valid and the block is imported and final, we can import. - debug!( - target: LOG_TARGET, - "🥩 import justif {:?} for block number {:?}.", proof, number - ); - // Send the justification to the BEEFY voter for processing. - self.justification_sender - .notify(|| Ok::<_, ()>(proof)) - .expect("forwards closure result; the closure always returns Ok; qed."); - } else { - debug!( - target: LOG_TARGET, - "🥩 error decoding justification: {:?} for imported block {:?}", - encoded, - number, - ); + match self.decode_and_verify(&encoded, number, hash) { + Ok(proof) => { + // The proof is valid and the block is imported and final, we can import. + debug!( + target: LOG_TARGET, + "🥩 import justif {:?} for block number {:?}.", proof, number + ); + // Send the justification to the BEEFY voter for processing. + self.justification_sender + .notify(|| Ok::<_, ()>(proof)) + .expect("the closure always returns Ok; qed."); + metric_inc!(self, beefy_good_justification_imports); + }, + Err(err) => { + debug!( + target: LOG_TARGET, + "🥩 error importing BEEFY justification for block {:?}: {:?}", + number, + err, + ); + metric_inc!(self, beefy_bad_justification_imports); + }, } }, _ => (), diff --git a/client/beefy/src/justification.rs b/client/consensus/beefy/src/justification.rs similarity index 75% rename from client/beefy/src/justification.rs rename to client/consensus/beefy/src/justification.rs index 7243c692727f..731acdfa6338 100644 --- a/client/beefy/src/justification.rs +++ b/client/consensus/beefy/src/justification.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -17,42 +17,51 @@ // along with this program. If not, see . use crate::keystore::BeefyKeystore; -use beefy_primitives::{ - crypto::{AuthorityId, Signature}, - ValidatorSet, VersionedFinalityProof, -}; use codec::{Decode, Encode}; use sp_consensus::Error as ConsensusError; +use sp_consensus_beefy::{ + crypto::{AuthorityId, Signature}, + ValidatorSet, ValidatorSetId, VersionedFinalityProof, +}; use sp_runtime::traits::{Block as BlockT, NumberFor}; /// A finality proof with matching BEEFY authorities' signatures. -pub type BeefyVersionedFinalityProof = - beefy_primitives::VersionedFinalityProof, Signature>; +pub type BeefyVersionedFinalityProof = VersionedFinalityProof, Signature>; + +pub(crate) fn proof_block_num_and_set_id( + proof: &BeefyVersionedFinalityProof, +) -> (NumberFor, ValidatorSetId) { + match proof { + VersionedFinalityProof::V1(sc) => + (sc.commitment.block_number, sc.commitment.validator_set_id), + } +} /// Decode and verify a Beefy FinalityProof. pub(crate) fn decode_and_verify_finality_proof( encoded: &[u8], target_number: NumberFor, validator_set: &ValidatorSet, -) -> Result, ConsensusError> { +) -> Result, (ConsensusError, u32)> { let proof = >::decode(&mut &*encoded) - .map_err(|_| ConsensusError::InvalidJustification)?; + .map_err(|_| (ConsensusError::InvalidJustification, 0))?; verify_with_validator_set::(target_number, validator_set, &proof).map(|_| proof) } /// Verify the Beefy finality proof against the validator set at the block it was generated. -fn verify_with_validator_set( +pub(crate) fn verify_with_validator_set( target_number: NumberFor, validator_set: &ValidatorSet, proof: &BeefyVersionedFinalityProof, -) -> Result<(), ConsensusError> { +) -> Result<(), (ConsensusError, u32)> { + let mut signatures_checked = 0u32; match proof { VersionedFinalityProof::V1(signed_commitment) => { if signed_commitment.signatures.len() != validator_set.len() || signed_commitment.commitment.validator_set_id != validator_set.id() || signed_commitment.commitment.block_number != target_number { - return Err(ConsensusError::InvalidJustification) + return Err((ConsensusError::InvalidJustification, 0)) } // Arrangement of signatures in the commitment should be in the same order @@ -65,14 +74,17 @@ fn verify_with_validator_set( .filter(|(id, signature)| { signature .as_ref() - .map(|sig| BeefyKeystore::verify(id, sig, &message[..])) + .map(|sig| { + signatures_checked += 1; + BeefyKeystore::verify(id, sig, &message[..]) + }) .unwrap_or(false) }) .count(); if valid_signatures >= crate::round::threshold(validator_set.len()) { Ok(()) } else { - Err(ConsensusError::InvalidJustification) + Err((ConsensusError::InvalidJustification, signatures_checked)) } }, } @@ -80,13 +92,13 @@ fn verify_with_validator_set( #[cfg(test)] pub(crate) mod tests { - use beefy_primitives::{ - known_payloads, Commitment, Payload, SignedCommitment, VersionedFinalityProof, + use sp_consensus_beefy::{ + known_payloads, Commitment, Keyring, Payload, SignedCommitment, VersionedFinalityProof, }; use substrate_test_runtime_client::runtime::Block; use super::*; - use crate::{keystore::tests::Keyring, tests::make_beefy_ids}; + use crate::tests::make_beefy_ids; pub(crate) fn new_finality_proof( block_num: NumberFor, @@ -119,16 +131,16 @@ pub(crate) mod tests { // wrong block number -> should fail verification let good_proof = proof.clone().into(); match verify_with_validator_set::(block_num + 1, &validator_set, &good_proof) { - Err(ConsensusError::InvalidJustification) => (), - _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), + Err((ConsensusError::InvalidJustification, 0)) => (), + e => assert!(false, "Got unexpected {:?}", e), }; // wrong validator set id -> should fail verification let good_proof = proof.clone().into(); let other = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); match verify_with_validator_set::(block_num, &other, &good_proof) { - Err(ConsensusError::InvalidJustification) => (), - _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), + Err((ConsensusError::InvalidJustification, 0)) => (), + e => assert!(false, "Got unexpected {:?}", e), }; // wrong signatures length -> should fail verification @@ -139,8 +151,8 @@ pub(crate) mod tests { }; bad_signed_commitment.signatures.pop().flatten().unwrap(); match verify_with_validator_set::(block_num + 1, &validator_set, &bad_proof.into()) { - Err(ConsensusError::InvalidJustification) => (), - _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), + Err((ConsensusError::InvalidJustification, 0)) => (), + e => assert!(false, "Got unexpected {:?}", e), }; // not enough signatures -> should fail verification @@ -150,9 +162,9 @@ pub(crate) mod tests { }; // remove a signature (but same length) *bad_signed_commitment.signatures.first_mut().unwrap() = None; - match verify_with_validator_set::(block_num + 1, &validator_set, &bad_proof.into()) { - Err(ConsensusError::InvalidJustification) => (), - _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), + match verify_with_validator_set::(block_num, &validator_set, &bad_proof.into()) { + Err((ConsensusError::InvalidJustification, 2)) => (), + e => assert!(false, "Got unexpected {:?}", e), }; // not enough _correct_ signatures -> should fail verification @@ -163,9 +175,9 @@ pub(crate) mod tests { // change a signature to a different key *bad_signed_commitment.signatures.first_mut().unwrap() = Some(Keyring::Dave.sign(&bad_signed_commitment.commitment.encode())); - match verify_with_validator_set::(block_num + 1, &validator_set, &bad_proof.into()) { - Err(ConsensusError::InvalidJustification) => (), - _ => assert!(false, "Expected Err(ConsensusError::InvalidJustification)"), + match verify_with_validator_set::(block_num, &validator_set, &bad_proof.into()) { + Err((ConsensusError::InvalidJustification, 3)) => (), + e => assert!(false, "Got unexpected {:?}", e), }; } diff --git a/client/beefy/src/keystore.rs b/client/consensus/beefy/src/keystore.rs similarity index 72% rename from client/beefy/src/keystore.rs rename to client/consensus/beefy/src/keystore.rs index d1f5615a9370..795d4cc8ade1 100644 --- a/client/beefy/src/keystore.rs +++ b/client/consensus/beefy/src/keystore.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,22 +18,24 @@ use sp_application_crypto::RuntimeAppPublic; use sp_core::keccak_256; -use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; -use sp_runtime::traits::Keccak256; +use sp_keystore::KeystorePtr; use log::warn; -use beefy_primitives::{ +use sp_consensus_beefy::{ crypto::{Public, Signature}, BeefyAuthorityId, KEY_TYPE, }; use crate::{error, LOG_TARGET}; +/// Hasher used for BEEFY signatures. +pub(crate) type BeefySignatureHasher = sp_runtime::traits::Keccak256; + /// A BEEFY specific keystore implemented as a `Newtype`. This is basically a -/// wrapper around [`sp_keystore::SyncCryptoStore`] and allows to customize +/// wrapper around [`sp_keystore::Keystore`] and allows to customize /// common cryptographic functionality. -pub(crate) struct BeefyKeystore(Option); +pub(crate) struct BeefyKeystore(Option); impl BeefyKeystore { /// Check if the keystore contains a private key for one of the public keys @@ -48,7 +50,7 @@ impl BeefyKeystore { // we do check for multiple private keys as a key store sanity check. let public: Vec = keys .iter() - .filter(|k| SyncCryptoStore::has_keys(&*store, &[(k.to_raw_vec(), KEY_TYPE)])) + .filter(|k| store.has_keys(&[(k.to_raw_vec(), KEY_TYPE)])) .cloned() .collect(); @@ -75,7 +77,8 @@ impl BeefyKeystore { let msg = keccak_256(message); let public = public.as_ref(); - let sig = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, public, &msg) + let sig = store + .ecdsa_sign_prehashed(KEY_TYPE, public, &msg) .map_err(|e| error::Error::Keystore(e.to_string()))? .ok_or_else(|| error::Error::Signature("ecdsa_sign_prehashed() failed".to_string()))?; @@ -87,15 +90,13 @@ impl BeefyKeystore { Ok(sig) } - /// Returns a vector of [`beefy_primitives::crypto::Public`] keys which are currently supported - /// (i.e. found in the keystore). + /// Returns a vector of [`sp_consensus_beefy::crypto::Public`] keys which are currently + /// supported (i.e. found in the keystore). pub fn public_keys(&self) -> Result, error::Error> { let store = self.0.clone().ok_or_else(|| error::Error::Keystore("no Keystore".into()))?; - let pk: Vec = SyncCryptoStore::ecdsa_public_keys(&*store, KEY_TYPE) - .drain(..) - .map(Public::from) - .collect(); + let pk: Vec = + store.ecdsa_public_keys(KEY_TYPE).drain(..).map(Public::from).collect(); Ok(pk) } @@ -104,80 +105,27 @@ impl BeefyKeystore { /// /// Return `true` if the signature is authentic, `false` otherwise. pub fn verify(public: &Public, sig: &Signature, message: &[u8]) -> bool { - BeefyAuthorityId::::verify(public, sig, message) + BeefyAuthorityId::::verify(public, sig, message) } } -impl From> for BeefyKeystore { - fn from(store: Option) -> BeefyKeystore { +impl From> for BeefyKeystore { + fn from(store: Option) -> BeefyKeystore { BeefyKeystore(store) } } #[cfg(test)] pub mod tests { - use std::sync::Arc; - - use sc_keystore::LocalKeystore; - use sp_core::{ecdsa, keccak_256, Pair}; - use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; + use sp_consensus_beefy::{crypto, Keyring}; + use sp_core::{ecdsa, Pair}; + use sp_keystore::testing::MemoryKeystore; - use beefy_primitives::{crypto, KEY_TYPE}; - - use super::BeefyKeystore; + use super::*; use crate::error::Error; - /// Set of test accounts using [`beefy_primitives::crypto`] types. - #[allow(missing_docs)] - #[derive(Debug, Clone, Copy, PartialEq, Eq, strum::Display, strum::EnumIter)] - pub(crate) enum Keyring { - Alice, - Bob, - Charlie, - Dave, - Eve, - Ferdie, - One, - Two, - } - - impl Keyring { - /// Sign `msg`. - pub fn sign(self, msg: &[u8]) -> crypto::Signature { - let msg = keccak_256(msg); - ecdsa::Pair::from(self).sign_prehashed(&msg).into() - } - - /// Return key pair. - pub fn pair(self) -> crypto::Pair { - ecdsa::Pair::from_string(self.to_seed().as_str(), None).unwrap().into() - } - - /// Return public key. - pub fn public(self) -> crypto::Public { - self.pair().public() - } - - /// Return seed string. - pub fn to_seed(self) -> String { - format!("//{}", self) - } - } - - impl From for crypto::Pair { - fn from(k: Keyring) -> Self { - k.pair() - } - } - - impl From for ecdsa::Pair { - fn from(k: Keyring) -> Self { - k.pair().into() - } - } - - fn keystore() -> SyncCryptoStorePtr { - Arc::new(LocalKeystore::in_memory()) + fn keystore() -> KeystorePtr { + MemoryKeystore::new().into() } #[test] @@ -245,11 +193,11 @@ pub mod tests { fn authority_id_works() { let store = keystore(); - let alice: crypto::Public = - SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); + let alice: crypto::Public = store + .ecdsa_generate_new(KEY_TYPE, Some(&Keyring::Alice.to_seed())) + .ok() + .unwrap() + .into(); let bob = Keyring::Bob.public(); let charlie = Keyring::Charlie.public(); @@ -271,11 +219,11 @@ pub mod tests { fn sign_works() { let store = keystore(); - let alice: crypto::Public = - SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); + let alice: crypto::Public = store + .ecdsa_generate_new(KEY_TYPE, Some(&Keyring::Alice.to_seed())) + .ok() + .unwrap() + .into(); let store: BeefyKeystore = Some(store).into(); @@ -291,10 +239,7 @@ pub mod tests { fn sign_error() { let store = keystore(); - let _ = - SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&Keyring::Bob.to_seed())) - .ok() - .unwrap(); + store.ecdsa_generate_new(KEY_TYPE, Some(&Keyring::Bob.to_seed())).ok().unwrap(); let store: BeefyKeystore = Some(store).into(); @@ -323,11 +268,11 @@ pub mod tests { fn verify_works() { let store = keystore(); - let alice: crypto::Public = - SyncCryptoStore::ecdsa_generate_new(&*store, KEY_TYPE, Some(&Keyring::Alice.to_seed())) - .ok() - .unwrap() - .into(); + let alice: crypto::Public = store + .ecdsa_generate_new(KEY_TYPE, Some(&Keyring::Alice.to_seed())) + .ok() + .unwrap() + .into(); let store: BeefyKeystore = Some(store).into(); @@ -349,9 +294,8 @@ pub mod tests { let store = keystore(); - let add_key = |key_type, seed: Option<&str>| { - SyncCryptoStore::ecdsa_generate_new(&*store, key_type, seed).unwrap() - }; + let add_key = + |key_type, seed: Option<&str>| store.ecdsa_generate_new(key_type, seed).unwrap(); // test keys let _ = add_key(TEST_TYPE, Some(Keyring::Alice.to_seed().as_str())); diff --git a/client/beefy/src/lib.rs b/client/consensus/beefy/src/lib.rs similarity index 69% rename from client/beefy/src/lib.rs rename to client/consensus/beefy/src/lib.rs index 185f3b1ad502..c55849ff7722 100644 --- a/client/beefy/src/lib.rs +++ b/client/consensus/beefy/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -28,34 +28,34 @@ use crate::{ }, }, import::BeefyBlockImport, + metrics::register_metrics, round::Rounds, worker::PersistedState, }; -use beefy_primitives::{ - crypto::AuthorityId, BeefyApi, MmrRootHash, PayloadProvider, ValidatorSet, BEEFY_ENGINE_ID, - GENESIS_AUTHORITY_SET_ID, -}; use futures::{stream::Fuse, StreamExt}; -use log::{debug, error, info}; +use log::{error, info}; use parking_lot::Mutex; use prometheus::Registry; use sc_client_api::{Backend, BlockBackend, BlockchainEvents, FinalityNotifications, Finalizer}; use sc_consensus::BlockImport; -use sc_network::ProtocolName; -use sc_network_common::service::NetworkRequest; -use sc_network_gossip::{GossipEngine, Network as GossipNetwork}; +use sc_network::{NetworkRequest, ProtocolName}; +use sc_network_gossip::{GossipEngine, Network as GossipNetwork, Syncing as GossipSyncing}; use sp_api::{HeaderT, NumberFor, ProvideRuntimeApi}; use sp_blockchain::{ Backend as BlockchainBackend, Error as ClientError, HeaderBackend, Result as ClientResult, }; use sp_consensus::{Error as ConsensusError, SyncOracle}; -use sp_keystore::SyncCryptoStorePtr; +use sp_consensus_beefy::{ + crypto::AuthorityId, BeefyApi, MmrRootHash, PayloadProvider, ValidatorSet, BEEFY_ENGINE_ID, +}; +use sp_keystore::KeystorePtr; use sp_mmr_primitives::MmrApi; -use sp_runtime::{ - generic::BlockId, - traits::{Block, One, Zero}, +use sp_runtime::traits::{Block, Zero}; +use std::{ + collections::{BTreeMap, VecDeque}, + marker::PhantomData, + sync::Arc, }; -use std::{collections::VecDeque, marker::PhantomData, sync::Arc}; mod aux_schema; mod error; @@ -133,6 +133,7 @@ pub fn beefy_block_import_and_links( wrapped_block_import: I, backend: Arc, runtime: Arc, + prometheus_registry: Option, ) -> (BeefyBlockImport, BeefyVoterLinks, BeefyRPCLinks) where B: Block, @@ -152,10 +153,16 @@ where // BlockImport -> Voter links let (to_voter_justif_sender, from_block_import_justif_stream) = BeefyVersionedFinalityProofStream::::channel(); + let metrics = register_metrics(prometheus_registry); // BlockImport - let import = - BeefyBlockImport::new(backend, runtime, wrapped_block_import, to_voter_justif_sender); + let import = BeefyBlockImport::new( + backend, + runtime, + wrapped_block_import, + to_voter_justif_sender, + metrics, + ); let voter_links = BeefyVoterLinks { from_block_import_justif_stream, to_rpc_justif_sender, @@ -167,9 +174,11 @@ where } /// BEEFY gadget network parameters. -pub struct BeefyNetworkParams { +pub struct BeefyNetworkParams { /// Network implementing gossip, requests and sync-oracle. pub network: Arc, + /// Syncing service implementing a sync oracle and an event stream for peers. + pub sync: Arc, /// Chain specific BEEFY gossip protocol name. See /// [`communication::beefy_protocol_name::gossip_protocol_name`]. pub gossip_protocol_name: ProtocolName, @@ -181,7 +190,7 @@ pub struct BeefyNetworkParams { } /// BEEFY gadget initialization parameters. -pub struct BeefyParams { +pub struct BeefyParams { /// BEEFY client pub client: Arc, /// Client Backend @@ -191,9 +200,9 @@ pub struct BeefyParams { /// Runtime Api Provider pub runtime: Arc, /// Local key store - pub key_store: Option, + pub key_store: Option, /// BEEFY voter network params - pub network_params: BeefyNetworkParams, + pub network_params: BeefyNetworkParams, /// Minimal delta between blocks, BEEFY should vote for pub min_block_delta: u32, /// Prometheus metric registry @@ -207,15 +216,17 @@ pub struct BeefyParams { /// Start the BEEFY gadget. /// /// This is a thin shim around running and awaiting a BEEFY worker. -pub async fn start_beefy_gadget(beefy_params: BeefyParams) -where +pub async fn start_beefy_gadget( + beefy_params: BeefyParams, +) where B: Block, BE: Backend, C: Client + BlockBackend, P: PayloadProvider, R: ProvideRuntimeApi, R::Api: BeefyApi + MmrApi>, - N: GossipNetwork + NetworkRequest + SyncOracle + Send + Sync + 'static, + N: GossipNetwork + NetworkRequest + Send + Sync + 'static, + S: GossipSyncing + SyncOracle + 'static, { let BeefyParams { client, @@ -230,40 +241,37 @@ where on_demand_justifications_handler, } = beefy_params; - let BeefyNetworkParams { network, gossip_protocol_name, justifications_protocol_name, .. } = - network_params; + let BeefyNetworkParams { + network, + sync, + gossip_protocol_name, + justifications_protocol_name, + .. + } = network_params; let known_peers = Arc::new(Mutex::new(KnownPeers::new())); - let gossip_validator = - Arc::new(communication::gossip::GossipValidator::new(known_peers.clone())); - let mut gossip_engine = sc_network_gossip::GossipEngine::new( + // Default votes filter is to discard everything. + // Validator is updated later with correct starting round and set id. + let (gossip_validator, gossip_report_stream) = + communication::gossip::GossipValidator::new(known_peers.clone()); + let gossip_validator = Arc::new(gossip_validator); + let mut gossip_engine = GossipEngine::new( network.clone(), + sync.clone(), gossip_protocol_name, gossip_validator.clone(), None, ); + let metrics = register_metrics(prometheus_registry.clone()); // The `GossipValidator` adds and removes known peers based on valid votes and network events. let on_demand_justifications = OnDemandJustificationsEngine::new( network.clone(), justifications_protocol_name, known_peers, + prometheus_registry.clone(), ); - let metrics = - prometheus_registry.as_ref().map(metrics::Metrics::register).and_then( - |result| match result { - Ok(metrics) => { - debug!(target: LOG_TARGET, "🥩 Registered metrics"); - Some(metrics) - }, - Err(err) => { - debug!(target: LOG_TARGET, "🥩 Failed to register metrics: {:?}", err); - None - }, - }, - ); - // Subscribe to finality notifications and justifications before waiting for runtime pallet and // reuse the streams, so we don't miss notifications while waiting for pallet to be available. let mut finality_notifications = client.finality_notification_stream().fuse(); @@ -273,8 +281,14 @@ where let persisted_state = match wait_for_runtime_pallet(&*runtime, &mut gossip_engine, &mut finality_notifications) .await - .and_then(|best_grandpa| { - load_or_init_voter_state(&*backend, &*runtime, best_grandpa, min_block_delta) + .and_then(|(beefy_genesis, best_grandpa)| { + load_or_init_voter_state( + &*backend, + &*runtime, + beefy_genesis, + best_grandpa, + min_block_delta, + ) }) { Ok(state) => state, Err(e) => { @@ -282,25 +296,34 @@ where return }, }; + // Update the gossip validator with the right starting round and set id. + if let Err(e) = persisted_state + .gossip_filter_config() + .map(|f| gossip_validator.update_filter(f)) + { + error!(target: LOG_TARGET, "Error: {:?}. Terminating.", e); + return + } - let worker_params = worker::WorkerParams { + let worker = worker::BeefyWorker { backend, payload_provider, - network, + runtime, + sync, key_store: key_store.into(), gossip_engine, gossip_validator, + gossip_report_stream, on_demand_justifications, links, metrics, + pending_justifications: BTreeMap::new(), persisted_state, }; - let worker = worker::BeefyWorker::<_, _, _, _>::new(worker_params); - - futures::future::join( - worker.run(block_import_justif, finality_notifications), - on_demand_justifications_handler.run(), + futures::future::select( + Box::pin(worker.run(block_import_justif, finality_notifications)), + Box::pin(on_demand_justifications_handler.run()), ) .await; } @@ -308,6 +331,7 @@ where fn load_or_init_voter_state( backend: &BE, runtime: &R, + beefy_genesis: NumberFor, best_grandpa: ::Header, min_block_delta: u32, ) -> ClientResult> @@ -317,17 +341,22 @@ where R: ProvideRuntimeApi, R::Api: BeefyApi, { - // Initialize voter state from AUX DB or from pallet genesis. - if let Some(mut state) = crate::aux_schema::load_persistent(backend)? { - // Overwrite persisted state with current best GRANDPA block. - state.set_best_grandpa(best_grandpa); - // Overwrite persisted data with newly provided `min_block_delta`. - state.set_min_block_delta(min_block_delta); - info!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db: {:?}.", state); - Ok(state) - } else { - initialize_voter_state(backend, runtime, best_grandpa, min_block_delta) - } + // Initialize voter state from AUX DB if compatible. + crate::aux_schema::load_persistent(backend)? + // Verify state pallet genesis matches runtime. + .filter(|state| state.pallet_genesis() == beefy_genesis) + .and_then(|mut state| { + // Overwrite persisted state with current best GRANDPA block. + state.set_best_grandpa(best_grandpa.clone()); + // Overwrite persisted data with newly provided `min_block_delta`. + state.set_min_block_delta(min_block_delta); + info!(target: LOG_TARGET, "🥩 Loading BEEFY voter state from db: {:?}.", state); + Some(Ok(state)) + }) + // No valid voter-state persisted, re-initialize from pallet genesis. + .unwrap_or_else(|| { + initialize_voter_state(backend, runtime, beefy_genesis, best_grandpa, min_block_delta) + }) } // If no persisted state present, walk back the chain from first GRANDPA notification to either: @@ -337,6 +366,7 @@ where fn initialize_voter_state( backend: &BE, runtime: &R, + beefy_genesis: NumberFor, best_grandpa: ::Header, min_block_delta: u32, ) -> ClientResult> @@ -346,6 +376,13 @@ where R: ProvideRuntimeApi, R::Api: BeefyApi, { + let beefy_genesis = runtime + .runtime_api() + .beefy_genesis(best_grandpa.hash()) + .ok() + .flatten() + .filter(|genesis| *genesis == beefy_genesis) + .ok_or_else(|| ClientError::Backend("BEEFY pallet expected to be active.".into()))?; // Walk back the imported blocks and initialize voter either, at the last block with // a BEEFY justification, or at pallet genesis block; voter will resume from there. let blockchain = backend.blockchain(); @@ -366,34 +403,43 @@ where let best_beefy = *header.number(); // If no session boundaries detected so far, just initialize new rounds here. if sessions.is_empty() { - let active_set = expect_validator_set(runtime, BlockId::hash(header.hash()))?; + let active_set = expect_validator_set(runtime, header.hash())?; let mut rounds = Rounds::new(best_beefy, active_set); // Mark the round as already finalized. rounds.conclude(best_beefy); sessions.push_front(rounds); } - let state = - PersistedState::checked_new(best_grandpa, best_beefy, sessions, min_block_delta) - .ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))?; + let state = PersistedState::checked_new( + best_grandpa, + best_beefy, + sessions, + min_block_delta, + beefy_genesis, + ) + .ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))?; break state } - if *header.number() == One::one() { - // We've reached chain genesis, initialize voter here. - let genesis_num = *header.number(); - let genesis_set = expect_validator_set(runtime, BlockId::hash(header.hash())) - .and_then(genesis_set_sanity_check)?; + if *header.number() == beefy_genesis { + // We've reached BEEFY genesis, initialize voter here. + let genesis_set = expect_validator_set(runtime, header.hash())?; info!( target: LOG_TARGET, "🥩 Loading BEEFY voter state from genesis on what appears to be first startup. \ Starting voting rounds at block {:?}, genesis validator set {:?}.", - genesis_num, + beefy_genesis, genesis_set, ); - sessions.push_front(Rounds::new(genesis_num, genesis_set)); - break PersistedState::checked_new(best_grandpa, Zero::zero(), sessions, min_block_delta) - .ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))? + sessions.push_front(Rounds::new(beefy_genesis, genesis_set)); + break PersistedState::checked_new( + best_grandpa, + Zero::zero(), + sessions, + min_block_delta, + beefy_genesis, + ) + .ok_or_else(|| ClientError::Backend("Invalid BEEFY chain".into()))? } if let Some(active) = worker::find_authorities_change::(&header) { @@ -407,16 +453,11 @@ where // Check if state is still available if we move up the chain. let parent_hash = *header.parent_hash(); - runtime - .runtime_api() - .validator_set(&BlockId::hash(parent_hash)) - .ok() - .flatten() - .ok_or_else(|| { - let msg = format!("{}. Could not initialize BEEFY voter.", parent_hash); - error!(target: LOG_TARGET, "🥩 {}", msg); - ClientError::Consensus(sp_consensus::Error::StateUnavailable(msg)) - })?; + runtime.runtime_api().validator_set(parent_hash).ok().flatten().ok_or_else(|| { + let msg = format!("{}. Could not initialize BEEFY voter.", parent_hash); + error!(target: LOG_TARGET, "🥩 {}", msg); + ClientError::Consensus(sp_consensus::Error::StateUnavailable(msg)) + })?; // Move up the chain. header = blockchain.expect_header(parent_hash)?; @@ -433,7 +474,7 @@ async fn wait_for_runtime_pallet( runtime: &R, mut gossip_engine: &mut GossipEngine, finality: &mut Fuse>, -) -> ClientResult<::Header> +) -> ClientResult<(NumberFor, ::Header)> where B: Block, R: ProvideRuntimeApi, @@ -447,14 +488,17 @@ where Some(notif) => notif, None => break }; - let at = BlockId::hash(notif.header.hash()); - if let Some(active) = runtime.runtime_api().validator_set(&at).ok().flatten() { - // Beefy pallet available, return best grandpa at the time. - info!( - target: LOG_TARGET, "🥩 BEEFY pallet available: block {:?} validator set {:?}", - notif.header.number(), active - ); - return Ok(notif.header) + let at = notif.header.hash(); + if let Some(start) = runtime.runtime_api().beefy_genesis(at).ok().flatten() { + if *notif.header.number() >= start { + // Beefy pallet available, return header for best grandpa at the time. + info!( + target: LOG_TARGET, + "🥩 BEEFY pallet available: block {:?} beefy genesis {:?}", + notif.header.number(), start + ); + return Ok((start, notif.header)) + } } }, _ = gossip_engine => { @@ -467,20 +511,9 @@ where Err(ClientError::Backend(err_msg)) } -fn genesis_set_sanity_check( - active: ValidatorSet, -) -> ClientResult> { - if active.id() == GENESIS_AUTHORITY_SET_ID { - Ok(active) - } else { - error!(target: LOG_TARGET, "🥩 Unexpected ID for genesis validator set {:?}.", active); - Err(ClientError::Backend("BEEFY Genesis sanity check failed.".into())) - } -} - fn expect_validator_set( runtime: &R, - at: BlockId, + at_hash: B::Hash, ) -> ClientResult> where B: Block, @@ -489,7 +522,7 @@ where { runtime .runtime_api() - .validator_set(&at) + .validator_set(at_hash) .ok() .flatten() .ok_or_else(|| ClientError::Backend("BEEFY pallet expected to be active.".into())) diff --git a/client/consensus/beefy/src/metrics.rs b/client/consensus/beefy/src/metrics.rs new file mode 100644 index 000000000000..031748bdceab --- /dev/null +++ b/client/consensus/beefy/src/metrics.rs @@ -0,0 +1,345 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +//! BEEFY Prometheus metrics definition + +use crate::LOG_TARGET; +use log::{debug, error}; +use prometheus::{register, Counter, Gauge, PrometheusError, Registry, U64}; + +/// Helper trait for registering BEEFY metrics to Prometheus registry. +pub(crate) trait PrometheusRegister: Sized { + const DESCRIPTION: &'static str; + fn register(registry: &Registry) -> Result; +} + +/// BEEFY voting-related metrics exposed through Prometheus +#[derive(Clone, Debug)] +pub struct VoterMetrics { + /// Current active validator set id + pub beefy_validator_set_id: Gauge, + /// Total number of votes sent by this node + pub beefy_votes_sent: Counter, + /// Best block finalized by BEEFY + pub beefy_best_block: Gauge, + /// Best block BEEFY voted on + pub beefy_best_voted: Gauge, + /// Next block BEEFY should vote on + pub beefy_should_vote_on: Gauge, + /// Number of sessions with lagging signed commitment on mandatory block + pub beefy_lagging_sessions: Counter, + /// Number of times no Authority public key found in store + pub beefy_no_authority_found_in_store: Counter, + /// Number of good votes successfully handled + pub beefy_good_votes_processed: Counter, + /// Number of equivocation votes received + pub beefy_equivocation_votes: Counter, + /// Number of invalid votes received + pub beefy_invalid_votes: Counter, + /// Number of valid but stale votes received + pub beefy_stale_votes: Counter, + /// Number of currently buffered justifications + pub beefy_buffered_justifications: Gauge, + /// Number of valid but stale justifications received + pub beefy_stale_justifications: Counter, + /// Number of valid justifications successfully imported + pub beefy_imported_justifications: Counter, + /// Number of justifications dropped due to full buffers + pub beefy_buffered_justifications_dropped: Counter, +} + +impl PrometheusRegister for VoterMetrics { + const DESCRIPTION: &'static str = "voter"; + fn register(registry: &Registry) -> Result { + Ok(Self { + beefy_validator_set_id: register( + Gauge::new( + "substrate_beefy_validator_set_id", + "Current BEEFY active validator set id.", + )?, + registry, + )?, + beefy_votes_sent: register( + Counter::new("substrate_beefy_votes_sent", "Number of votes sent by this node")?, + registry, + )?, + beefy_best_block: register( + Gauge::new("substrate_beefy_best_block", "Best block finalized by BEEFY")?, + registry, + )?, + beefy_best_voted: register( + Gauge::new("substrate_beefy_best_voted", "Best block voted on by BEEFY")?, + registry, + )?, + beefy_should_vote_on: register( + Gauge::new("substrate_beefy_should_vote_on", "Next block, BEEFY should vote on")?, + registry, + )?, + beefy_lagging_sessions: register( + Counter::new( + "substrate_beefy_lagging_sessions", + "Number of sessions with lagging signed commitment on mandatory block", + )?, + registry, + )?, + beefy_no_authority_found_in_store: register( + Counter::new( + "substrate_beefy_no_authority_found_in_store", + "Number of times no Authority public key found in store", + )?, + registry, + )?, + beefy_good_votes_processed: register( + Counter::new( + "substrate_beefy_successful_handled_votes", + "Number of good votes successfully handled", + )?, + registry, + )?, + beefy_equivocation_votes: register( + Counter::new( + "substrate_beefy_equivocation_votes", + "Number of equivocation votes received", + )?, + registry, + )?, + beefy_invalid_votes: register( + Counter::new("substrate_beefy_invalid_votes", "Number of invalid votes received")?, + registry, + )?, + beefy_stale_votes: register( + Counter::new( + "substrate_beefy_stale_votes", + "Number of valid but stale votes received", + )?, + registry, + )?, + beefy_buffered_justifications: register( + Gauge::new( + "substrate_beefy_buffered_justifications", + "Number of currently buffered justifications", + )?, + registry, + )?, + beefy_stale_justifications: register( + Counter::new( + "substrate_beefy_stale_justifications", + "Number of valid but stale justifications received", + )?, + registry, + )?, + beefy_imported_justifications: register( + Counter::new( + "substrate_beefy_imported_justifications", + "Number of valid justifications successfully imported", + )?, + registry, + )?, + beefy_buffered_justifications_dropped: register( + Counter::new( + "substrate_beefy_buffered_justifications_dropped", + "Number of justifications dropped due to full buffers", + )?, + registry, + )?, + }) + } +} + +/// BEEFY block-import-related metrics exposed through Prometheus +#[derive(Clone, Debug)] +pub struct BlockImportMetrics { + /// Number of Good Justification imports + pub beefy_good_justification_imports: Counter, + /// Number of Bad Justification imports + pub beefy_bad_justification_imports: Counter, +} + +impl PrometheusRegister for BlockImportMetrics { + const DESCRIPTION: &'static str = "block-import"; + fn register(registry: &Registry) -> Result { + Ok(Self { + beefy_good_justification_imports: register( + Counter::new( + "substrate_beefy_good_justification_imports", + "Number of good justifications on block-import", + )?, + registry, + )?, + beefy_bad_justification_imports: register( + Counter::new( + "substrate_beefy_bad_justification_imports", + "Number of bad justifications on block-import", + )?, + registry, + )?, + }) + } +} + +/// BEEFY on-demand-justifications-related metrics exposed through Prometheus +#[derive(Clone, Debug)] +pub struct OnDemandIncomingRequestsMetrics { + /// Number of Successful Justification responses + pub beefy_successful_justification_responses: Counter, + /// Number of Failed Justification responses + pub beefy_failed_justification_responses: Counter, +} + +impl PrometheusRegister for OnDemandIncomingRequestsMetrics { + const DESCRIPTION: &'static str = "on-demand incoming justification requests"; + fn register(registry: &Registry) -> Result { + Ok(Self { + beefy_successful_justification_responses: register( + Counter::new( + "substrate_beefy_successful_justification_responses", + "Number of Successful Justification responses", + )?, + registry, + )?, + beefy_failed_justification_responses: register( + Counter::new( + "substrate_beefy_failed_justification_responses", + "Number of Failed Justification responses", + )?, + registry, + )?, + }) + } +} + +/// BEEFY on-demand-justifications-related metrics exposed through Prometheus +#[derive(Clone, Debug)] +pub struct OnDemandOutgoingRequestsMetrics { + /// Number of times there was no good peer to request justification from + pub beefy_on_demand_justification_no_peer_to_request_from: Counter, + /// Number of on-demand justification peer refused valid requests + pub beefy_on_demand_justification_peer_refused: Counter, + /// Number of on-demand justification peer error + pub beefy_on_demand_justification_peer_error: Counter, + /// Number of on-demand justification invalid proof + pub beefy_on_demand_justification_invalid_proof: Counter, + /// Number of on-demand justification good proof + pub beefy_on_demand_justification_good_proof: Counter, +} + +impl PrometheusRegister for OnDemandOutgoingRequestsMetrics { + const DESCRIPTION: &'static str = "on-demand outgoing justification requests"; + fn register(registry: &Registry) -> Result { + Ok(Self { + beefy_on_demand_justification_no_peer_to_request_from: register( + Counter::new( + "substrate_beefy_on_demand_justification_no_peer_to_request_from", + "Number of times there was no good peer to request justification from", + )?, + registry, + )?, + beefy_on_demand_justification_peer_refused: register( + Counter::new( + "beefy_on_demand_justification_peer_refused", + "Number of on-demand justification peer refused valid requests", + )?, + registry, + )?, + beefy_on_demand_justification_peer_error: register( + Counter::new( + "substrate_beefy_on_demand_justification_peer_error", + "Number of on-demand justification peer error", + )?, + registry, + )?, + beefy_on_demand_justification_invalid_proof: register( + Counter::new( + "substrate_beefy_on_demand_justification_invalid_proof", + "Number of on-demand justification invalid proof", + )?, + registry, + )?, + beefy_on_demand_justification_good_proof: register( + Counter::new( + "substrate_beefy_on_demand_justification_good_proof", + "Number of on-demand justification good proof", + )?, + registry, + )?, + }) + } +} + +pub(crate) fn register_metrics( + prometheus_registry: Option, +) -> Option { + prometheus_registry.as_ref().map(T::register).and_then(|result| match result { + Ok(metrics) => { + debug!(target: LOG_TARGET, "🥩 Registered {} metrics", T::DESCRIPTION); + Some(metrics) + }, + Err(err) => { + error!( + target: LOG_TARGET, + "🥩 Failed to register {} metrics: {:?}", + T::DESCRIPTION, + err + ); + None + }, + }) +} + +// Note: we use the `format` macro to convert an expr into a `u64`. This will fail, +// if expr does not derive `Display`. +#[macro_export] +macro_rules! metric_set { + ($self:ident, $m:ident, $v:expr) => {{ + let val: u64 = format!("{}", $v).parse().unwrap(); + + if let Some(metrics) = $self.metrics.as_ref() { + metrics.$m.set(val); + } + }}; +} + +#[macro_export] +macro_rules! metric_inc { + ($self:ident, $m:ident) => {{ + if let Some(metrics) = $self.metrics.as_ref() { + metrics.$m.inc(); + } + }}; +} + +#[macro_export] +macro_rules! metric_get { + ($self:ident, $m:ident) => {{ + $self.metrics.as_ref().map(|metrics| metrics.$m.clone()) + }}; +} + +#[cfg(test)] +pub(crate) mod tests { + use super::*; + + #[test] + fn should_register_metrics() { + let registry = Some(Registry::new()); + assert!(register_metrics::(registry.clone()).is_some()); + assert!(register_metrics::(registry.clone()).is_some()); + assert!(register_metrics::(registry.clone()).is_some()); + assert!(register_metrics::(registry.clone()).is_some()); + } +} diff --git a/client/consensus/beefy/src/round.rs b/client/consensus/beefy/src/round.rs new file mode 100644 index 000000000000..d8948ff98c55 --- /dev/null +++ b/client/consensus/beefy/src/round.rs @@ -0,0 +1,499 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 crate::LOG_TARGET; + +use codec::{Decode, Encode}; +use log::debug; +use sp_consensus_beefy::{ + crypto::{AuthorityId, Signature}, + Commitment, EquivocationProof, SignedCommitment, ValidatorSet, ValidatorSetId, VoteMessage, +}; +use sp_runtime::traits::{Block, NumberFor}; +use std::collections::BTreeMap; + +/// Tracks for each round which validators have voted/signed and +/// whether the local `self` validator has voted/signed. +/// +/// Does not do any validation on votes or signatures, layers above need to handle that (gossip). +#[derive(Debug, Decode, Default, Encode, PartialEq)] +pub(crate) struct RoundTracker { + votes: BTreeMap, +} + +impl RoundTracker { + fn add_vote(&mut self, vote: (AuthorityId, Signature)) -> bool { + if self.votes.contains_key(&vote.0) { + return false + } + + self.votes.insert(vote.0, vote.1); + true + } + + fn is_done(&self, threshold: usize) -> bool { + self.votes.len() >= threshold + } +} + +/// Minimum size of `authorities` subset that produced valid signatures for a block to finalize. +pub fn threshold(authorities: usize) -> usize { + let faulty = authorities.saturating_sub(1) / 3; + authorities - faulty +} + +#[derive(Debug, PartialEq)] +pub enum VoteImportResult { + Ok, + RoundConcluded(SignedCommitment, Signature>), + Equivocation(EquivocationProof, AuthorityId, Signature>), + Invalid, + Stale, +} + +/// Keeps track of all voting rounds (block numbers) within a session. +/// Only round numbers > `best_done` are of interest, all others are considered stale. +/// +/// Does not do any validation on votes or signatures, layers above need to handle that (gossip). +#[derive(Debug, Decode, Encode, PartialEq)] +pub(crate) struct Rounds { + rounds: BTreeMap>, RoundTracker>, + previous_votes: + BTreeMap<(AuthorityId, NumberFor), VoteMessage, AuthorityId, Signature>>, + session_start: NumberFor, + validator_set: ValidatorSet, + mandatory_done: bool, + best_done: Option>, +} + +impl Rounds +where + B: Block, +{ + pub(crate) fn new( + session_start: NumberFor, + validator_set: ValidatorSet, + ) -> Self { + Rounds { + rounds: BTreeMap::new(), + previous_votes: BTreeMap::new(), + session_start, + validator_set, + mandatory_done: false, + best_done: None, + } + } + + pub(crate) fn validator_set(&self) -> &ValidatorSet { + &self.validator_set + } + + pub(crate) fn validator_set_id(&self) -> ValidatorSetId { + self.validator_set.id() + } + + pub(crate) fn validators(&self) -> &[AuthorityId] { + self.validator_set.validators() + } + + pub(crate) fn session_start(&self) -> NumberFor { + self.session_start + } + + pub(crate) fn mandatory_done(&self) -> bool { + self.mandatory_done + } + + pub(crate) fn add_vote( + &mut self, + vote: VoteMessage, AuthorityId, Signature>, + ) -> VoteImportResult { + let num = vote.commitment.block_number; + let vote_key = (vote.id.clone(), num); + + if num < self.session_start || Some(num) <= self.best_done { + debug!(target: LOG_TARGET, "🥩 received vote for old stale round {:?}, ignoring", num); + return VoteImportResult::Stale + } else if vote.commitment.validator_set_id != self.validator_set_id() { + debug!( + target: LOG_TARGET, + "🥩 expected set_id {:?}, ignoring vote {:?}.", + self.validator_set_id(), + vote, + ); + return VoteImportResult::Invalid + } else if !self.validators().iter().any(|id| &vote.id == id) { + debug!( + target: LOG_TARGET, + "🥩 received vote {:?} from validator that is not in the validator set, ignoring", + vote + ); + return VoteImportResult::Invalid + } + + if let Some(previous_vote) = self.previous_votes.get(&vote_key) { + // is the same public key voting for a different payload? + if previous_vote.commitment.payload != vote.commitment.payload { + debug!( + target: LOG_TARGET, + "🥩 detected equivocated vote: 1st: {:?}, 2nd: {:?}", previous_vote, vote + ); + return VoteImportResult::Equivocation(EquivocationProof { + first: previous_vote.clone(), + second: vote, + }) + } + } else { + // this is the first vote sent by `id` for `num`, all good + self.previous_votes.insert(vote_key, vote.clone()); + } + + // add valid vote + let round = self.rounds.entry(vote.commitment.clone()).or_default(); + if round.add_vote((vote.id, vote.signature)) && + round.is_done(threshold(self.validator_set.len())) + { + if let Some(round) = self.rounds.remove_entry(&vote.commitment) { + return VoteImportResult::RoundConcluded(self.signed_commitment(round)) + } + } + VoteImportResult::Ok + } + + fn signed_commitment( + &mut self, + round: (Commitment>, RoundTracker), + ) -> SignedCommitment, Signature> { + let votes = round.1.votes; + let signatures = self + .validators() + .iter() + .map(|authority_id| votes.get(authority_id).cloned()) + .collect(); + SignedCommitment { commitment: round.0, signatures } + } + + pub(crate) fn conclude(&mut self, round_num: NumberFor) { + // Remove this and older (now stale) rounds. + self.rounds.retain(|commitment, _| commitment.block_number > round_num); + self.previous_votes.retain(|&(_, number), _| number > round_num); + self.mandatory_done = self.mandatory_done || round_num == self.session_start; + self.best_done = self.best_done.max(Some(round_num)); + debug!(target: LOG_TARGET, "🥩 Concluded round #{}", round_num); + } +} + +#[cfg(test)] +mod tests { + use sc_network_test::Block; + + use sp_consensus_beefy::{ + known_payloads::MMR_ROOT_ID, Commitment, EquivocationProof, Keyring, Payload, + SignedCommitment, ValidatorSet, VoteMessage, + }; + + use super::{threshold, AuthorityId, Block as BlockT, RoundTracker, Rounds}; + use crate::round::VoteImportResult; + + impl Rounds + where + B: BlockT, + { + pub(crate) fn test_set_mandatory_done(&mut self, done: bool) { + self.mandatory_done = done; + } + } + + #[test] + fn round_tracker() { + let mut rt = RoundTracker::default(); + let bob_vote = (Keyring::Bob.public(), Keyring::Bob.sign(b"I am committed")); + let threshold = 2; + + // adding new vote allowed + assert!(rt.add_vote(bob_vote.clone())); + // adding existing vote not allowed + assert!(!rt.add_vote(bob_vote)); + + // vote is not done + assert!(!rt.is_done(threshold)); + + let alice_vote = (Keyring::Alice.public(), Keyring::Alice.sign(b"I am committed")); + // adding new vote (self vote this time) allowed + assert!(rt.add_vote(alice_vote)); + + // vote is now done + assert!(rt.is_done(threshold)); + } + + #[test] + fn vote_threshold() { + assert_eq!(threshold(1), 1); + assert_eq!(threshold(2), 2); + assert_eq!(threshold(3), 3); + assert_eq!(threshold(4), 3); + assert_eq!(threshold(100), 67); + assert_eq!(threshold(300), 201); + } + + #[test] + fn new_rounds() { + sp_tracing::try_init_simple(); + + let validators = ValidatorSet::::new( + vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], + 42, + ) + .unwrap(); + + let session_start = 1u64.into(); + let rounds = Rounds::::new(session_start, validators); + + assert_eq!(42, rounds.validator_set_id()); + assert_eq!(1, rounds.session_start()); + assert_eq!( + &vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], + rounds.validators() + ); + } + + #[test] + fn add_and_conclude_votes() { + sp_tracing::try_init_simple(); + + let validators = ValidatorSet::::new( + vec![ + Keyring::Alice.public(), + Keyring::Bob.public(), + Keyring::Charlie.public(), + Keyring::Eve.public(), + ], + Default::default(), + ) + .unwrap(); + let validator_set_id = validators.id(); + + let session_start = 1u64.into(); + let mut rounds = Rounds::::new(session_start, validators); + + let payload = Payload::from_single_entry(MMR_ROOT_ID, vec![]); + let block_number = 1; + let commitment = Commitment { block_number, payload, validator_set_id }; + let mut vote = VoteMessage { + id: Keyring::Alice.public(), + commitment: commitment.clone(), + signature: Keyring::Alice.sign(b"I am committed"), + }; + // add 1st good vote + assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok); + + // double voting (same vote), ok, no effect + assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok); + + vote.id = Keyring::Dave.public(); + vote.signature = Keyring::Dave.sign(b"I am committed"); + // invalid vote (Dave is not a validator) + assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Invalid); + + vote.id = Keyring::Bob.public(); + vote.signature = Keyring::Bob.sign(b"I am committed"); + // add 2nd good vote + assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Ok); + + vote.id = Keyring::Charlie.public(); + vote.signature = Keyring::Charlie.sign(b"I am committed"); + // add 3rd good vote -> round concluded -> signatures present + assert_eq!( + rounds.add_vote(vote.clone()), + VoteImportResult::RoundConcluded(SignedCommitment { + commitment, + signatures: vec![ + Some(Keyring::Alice.sign(b"I am committed")), + Some(Keyring::Bob.sign(b"I am committed")), + Some(Keyring::Charlie.sign(b"I am committed")), + None, + ] + }) + ); + rounds.conclude(block_number); + + vote.id = Keyring::Eve.public(); + vote.signature = Keyring::Eve.sign(b"I am committed"); + // Eve is a validator, but round was concluded, adding vote disallowed + assert_eq!(rounds.add_vote(vote), VoteImportResult::Stale); + } + + #[test] + fn old_rounds_not_accepted() { + sp_tracing::try_init_simple(); + + let validators = ValidatorSet::::new( + vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], + 42, + ) + .unwrap(); + let validator_set_id = validators.id(); + + // active rounds starts at block 10 + let session_start = 10u64.into(); + let mut rounds = Rounds::::new(session_start, validators); + + // vote on round 9 + let block_number = 9; + let payload = Payload::from_single_entry(MMR_ROOT_ID, vec![]); + let commitment = Commitment { block_number, payload, validator_set_id }; + let mut vote = VoteMessage { + id: Keyring::Alice.public(), + commitment, + signature: Keyring::Alice.sign(b"I am committed"), + }; + // add vote for previous session, should fail + assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Stale); + // no votes present + assert!(rounds.rounds.is_empty()); + + // simulate 11 was concluded + rounds.best_done = Some(11); + // add votes for current session, but already concluded rounds, should fail + vote.commitment.block_number = 10; + assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Stale); + vote.commitment.block_number = 11; + assert_eq!(rounds.add_vote(vote.clone()), VoteImportResult::Stale); + // no votes present + assert!(rounds.rounds.is_empty()); + + // add vote for active round 12 + vote.commitment.block_number = 12; + assert_eq!(rounds.add_vote(vote), VoteImportResult::Ok); + // good vote present + assert_eq!(rounds.rounds.len(), 1); + } + + #[test] + fn multiple_rounds() { + sp_tracing::try_init_simple(); + + let validators = ValidatorSet::::new( + vec![Keyring::Alice.public(), Keyring::Bob.public(), Keyring::Charlie.public()], + Default::default(), + ) + .unwrap(); + let validator_set_id = validators.id(); + + let session_start = 1u64.into(); + let mut rounds = Rounds::::new(session_start, validators); + + let payload = Payload::from_single_entry(MMR_ROOT_ID, vec![]); + let commitment = Commitment { block_number: 1, payload, validator_set_id }; + let mut alice_vote = VoteMessage { + id: Keyring::Alice.public(), + commitment: commitment.clone(), + signature: Keyring::Alice.sign(b"I am committed"), + }; + let mut bob_vote = VoteMessage { + id: Keyring::Bob.public(), + commitment: commitment.clone(), + signature: Keyring::Bob.sign(b"I am committed"), + }; + let mut charlie_vote = VoteMessage { + id: Keyring::Charlie.public(), + commitment, + signature: Keyring::Charlie.sign(b"I am committed"), + }; + let expected_signatures = vec![ + Some(Keyring::Alice.sign(b"I am committed")), + Some(Keyring::Bob.sign(b"I am committed")), + Some(Keyring::Charlie.sign(b"I am committed")), + ]; + + // round 1 - only 2 out of 3 vote + assert_eq!(rounds.add_vote(alice_vote.clone()), VoteImportResult::Ok); + assert_eq!(rounds.add_vote(charlie_vote.clone()), VoteImportResult::Ok); + // should be 1 active round + assert_eq!(1, rounds.rounds.len()); + + // round 2 - only Charlie votes + charlie_vote.commitment.block_number = 2; + assert_eq!(rounds.add_vote(charlie_vote.clone()), VoteImportResult::Ok); + // should be 2 active rounds + assert_eq!(2, rounds.rounds.len()); + + // round 3 - all validators vote -> round is concluded + alice_vote.commitment.block_number = 3; + bob_vote.commitment.block_number = 3; + charlie_vote.commitment.block_number = 3; + assert_eq!(rounds.add_vote(alice_vote.clone()), VoteImportResult::Ok); + assert_eq!(rounds.add_vote(bob_vote.clone()), VoteImportResult::Ok); + assert_eq!( + rounds.add_vote(charlie_vote.clone()), + VoteImportResult::RoundConcluded(SignedCommitment { + commitment: charlie_vote.commitment, + signatures: expected_signatures + }) + ); + // should be only 2 active since this one auto-concluded + assert_eq!(2, rounds.rounds.len()); + + // conclude round 2 + rounds.conclude(2); + // should be no more active rounds since 2 was officially concluded and round "1" is stale + assert!(rounds.rounds.is_empty()); + + // conclude round 3 + rounds.conclude(3); + assert!(rounds.previous_votes.is_empty()); + } + + #[test] + fn should_provide_equivocation_proof() { + sp_tracing::try_init_simple(); + + let validators = ValidatorSet::::new( + vec![Keyring::Alice.public(), Keyring::Bob.public()], + Default::default(), + ) + .unwrap(); + let validator_set_id = validators.id(); + let session_start = 1u64.into(); + let mut rounds = Rounds::::new(session_start, validators); + + let payload1 = Payload::from_single_entry(MMR_ROOT_ID, vec![1, 1, 1, 1]); + let payload2 = Payload::from_single_entry(MMR_ROOT_ID, vec![2, 2, 2, 2]); + let commitment1 = Commitment { block_number: 1, payload: payload1, validator_set_id }; + let commitment2 = Commitment { block_number: 1, payload: payload2, validator_set_id }; + + let alice_vote1 = VoteMessage { + id: Keyring::Alice.public(), + commitment: commitment1, + signature: Keyring::Alice.sign(b"I am committed"), + }; + let mut alice_vote2 = alice_vote1.clone(); + alice_vote2.commitment = commitment2; + + let expected_result = VoteImportResult::Equivocation(EquivocationProof { + first: alice_vote1.clone(), + second: alice_vote2.clone(), + }); + + // vote on one payload - ok + assert_eq!(rounds.add_vote(alice_vote1), VoteImportResult::Ok); + + // vote on _another_ commitment/payload -> expected equivocation proof + assert_eq!(rounds.add_vote(alice_vote2), expected_result); + } +} diff --git a/client/beefy/src/tests.rs b/client/consensus/beefy/src/tests.rs similarity index 52% rename from client/beefy/src/tests.rs rename to client/consensus/beefy/src/tests.rs index 66d3a210d7eb..6bb6b1e54855 100644 --- a/client/beefy/src/tests.rs +++ b/client/consensus/beefy/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -21,23 +21,19 @@ use crate::{ aux_schema::{load_persistent, tests::verify_persisted_version}, beefy_block_import_and_links, - communication::request_response::{ - on_demand_justifications_protocol_config, BeefyJustifsRequestHandler, + communication::{ + gossip::{ + proofs_topic, tests::sign_commitment, votes_topic, GossipFilterCfg, GossipMessage, + GossipValidator, + }, + request_response::{on_demand_justifications_protocol_config, BeefyJustifsRequestHandler}, }, gossip_protocol_name, justification::*, - keystore::tests::Keyring as BeefyKeyring, load_or_init_voter_state, wait_for_runtime_pallet, BeefyRPCLinks, BeefyVoterLinks, KnownPeers, PersistedState, }; -use beefy_primitives::{ - crypto::{AuthorityId, Signature}, - known_payloads, - mmr::MmrRootProvider, - BeefyApi, Commitment, ConsensusLog, MmrRootHash, Payload, SignedCommitment, ValidatorSet, - VersionedFinalityProof, BEEFY_ENGINE_ID, KEY_TYPE as BeefyKeyType, -}; -use futures::{future, stream::FuturesUnordered, Future, StreamExt}; +use futures::{future, stream::FuturesUnordered, Future, FutureExt, StreamExt}; use parking_lot::Mutex; use sc_client_api::{Backend as BackendT, BlockchainEvents, FinalityNotifications, HeaderBackend}; use sc_consensus::{ @@ -53,17 +49,24 @@ use sc_utils::notification::NotificationReceiver; use serde::{Deserialize, Serialize}; use sp_api::{ApiRef, ProvideRuntimeApi}; use sp_consensus::BlockOrigin; +use sp_consensus_beefy::{ + crypto::{AuthorityId, Signature}, + known_payloads, + mmr::{find_mmr_root_digest, MmrRootProvider}, + BeefyApi, Commitment, ConsensusLog, EquivocationProof, Keyring as BeefyKeyring, MmrRootHash, + OpaqueKeyOwnershipProof, Payload, SignedCommitment, ValidatorSet, ValidatorSetId, + VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, KEY_TYPE as BeefyKeyType, +}; use sp_core::H256; -use sp_keystore::{testing::KeyStore as TestKeystore, SyncCryptoStore, SyncCryptoStorePtr}; -use sp_mmr_primitives::{EncodableOpaqueLeaf, Error as MmrError, MmrApi, Proof}; +use sp_keystore::{testing::MemoryKeystore, Keystore, KeystorePtr}; +use sp_mmr_primitives::{Error as MmrError, MmrApi}; use sp_runtime::{ - codec::Encode, - generic::BlockId, + codec::{Decode, Encode}, traits::{Header as HeaderT, NumberFor}, - BuildStorage, DigestItem, Justifications, Storage, + BuildStorage, DigestItem, EncodedJustification, Justifications, Storage, }; -use std::{collections::HashMap, marker::PhantomData, sync::Arc, task::Poll}; -use substrate_test_runtime_client::{runtime::Header, ClientExt}; +use std::{marker::PhantomData, sync::Arc, task::Poll}; +use substrate_test_runtime_client::{BlockBuilderExt, ClientExt}; use tokio::time::Duration; const GENESIS_HASH: H256 = H256::zero(); @@ -73,12 +76,13 @@ fn beefy_gossip_proto_name() -> ProtocolName { const GOOD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0xbf); const BAD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0x42); +const ALTERNATE_BAD_MMR_ROOT: MmrRootHash = MmrRootHash::repeat_byte(0x13); type BeefyBlockImport = crate::BeefyBlockImport< Block, substrate_test_runtime_client::Backend, - two_validators::TestApi, - BlockImportAdapter>, + TestApi, + BlockImportAdapter>, >; pub(crate) type BeefyValidatorSet = ValidatorSet; @@ -106,11 +110,13 @@ pub(crate) struct PeerData { #[derive(Default)] pub(crate) struct BeefyTestNet { peers: Vec, + pub beefy_genesis: NumberFor, } impl BeefyTestNet { pub(crate) fn new(n_authority: usize) -> Self { - let mut net = BeefyTestNet { peers: Vec::with_capacity(n_authority) }; + let beefy_genesis = 1; + let mut net = BeefyTestNet { peers: Vec::with_capacity(n_authority), beefy_genesis }; for i in 0..n_authority { let (rx, cfg) = on_demand_justifications_protocol_config(GENESIS_HASH, None); @@ -124,6 +130,7 @@ impl BeefyTestNet { justif_protocol_name, client, _block: PhantomData, + metrics: None, }; *net.peers[i].data.beefy_justif_req_handler.lock() = Some(justif_handler); } @@ -151,27 +158,29 @@ impl BeefyTestNet { ) -> Vec { let mut all_hashes = Vec::with_capacity(count + 1); - // make sure genesis is the only block in network, so we can insert genesis at he beginning + // make sure genesis is the only block in network, so we can insert genesis at the beginning // of hashes, otherwise indexing would be broken assert!(self.peer(0).client().as_backend().blockchain().hash(1).unwrap().is_none()); // push genesis to make indexing human readable (index equals to block number) all_hashes.push(self.peer(0).client().info().genesis_hash); - let built_hashes = self.peer(0).generate_blocks(count, BlockOrigin::File, |builder| { - let mut block = builder.build().unwrap().block; - + let mut block_num: NumberFor = self.peer(0).client().info().best_number; + let built_hashes = self.peer(0).generate_blocks(count, BlockOrigin::File, |mut builder| { + block_num = block_num.saturating_add(1).try_into().unwrap(); if include_mmr_digest { - let block_num = *block.header.number(); let num_byte = block_num.to_le_bytes().into_iter().next().unwrap(); let mmr_root = MmrRootHash::repeat_byte(num_byte); - add_mmr_digest(&mut block.header, mmr_root); + add_mmr_digest(&mut builder, mmr_root); } - if *block.header.number() % session_length == 0 { - add_auth_change_digest(&mut block.header, validator_set.clone()); + if block_num % session_length == 0 { + add_auth_change_digest(&mut builder, validator_set.clone()); } + let block = builder.build().unwrap().block; + assert_eq!(block.header.number, block_num); + block }); all_hashes.extend(built_hashes); @@ -198,12 +207,12 @@ impl TestNetFactory for BeefyTestNet { Option>, Self::PeerData, ) { + let keys = &[BeefyKeyring::Alice, BeefyKeyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let api = Arc::new(TestApi::new(self.beefy_genesis, &validator_set, GOOD_MMR_ROOT)); let inner = BlockImportAdapter::new(client.clone()); - let (block_import, voter_links, rpc_links) = beefy_block_import_and_links( - inner, - client.as_backend(), - Arc::new(two_validators::TestApi {}), - ); + let (block_import, voter_links, rpc_links) = + beefy_block_import_and_links(inner, client.as_backend(), api, None); let peer_data = PeerData { beefy_rpc_links: Mutex::new(Some(rpc_links)), beefy_voter_links: Mutex::new(Some(voter_links)), @@ -220,6 +229,10 @@ impl TestNetFactory for BeefyTestNet { &self.peers } + fn peers_mut(&mut self) -> &mut Vec { + &mut self.peers + } + fn mut_peers)>(&mut self, closure: F) { closure(&mut self.peers); } @@ -230,124 +243,139 @@ impl TestNetFactory for BeefyTestNet { } } -macro_rules! create_test_api { - ( $api_name:ident, mmr_root: $mmr_root:expr, $($inits:expr),+ ) => { - pub(crate) mod $api_name { - use super::*; +#[derive(Clone)] +pub(crate) struct TestApi { + pub beefy_genesis: u64, + pub validator_set: BeefyValidatorSet, + pub mmr_root_hash: MmrRootHash, + pub reported_equivocations: + Option, AuthorityId, Signature>>>>>, +} - #[derive(Clone, Default)] - pub(crate) struct TestApi {} +impl TestApi { + pub fn new( + beefy_genesis: u64, + validator_set: &BeefyValidatorSet, + mmr_root_hash: MmrRootHash, + ) -> Self { + TestApi { + beefy_genesis, + validator_set: validator_set.clone(), + mmr_root_hash, + reported_equivocations: None, + } + } - // compiler gets confused and warns us about unused inner - #[allow(dead_code)] - pub(crate) struct RuntimeApi { - inner: TestApi, - } + pub fn with_validator_set(validator_set: &BeefyValidatorSet) -> Self { + TestApi { + beefy_genesis: 1, + validator_set: validator_set.clone(), + mmr_root_hash: GOOD_MMR_ROOT, + reported_equivocations: None, + } + } - impl ProvideRuntimeApi for TestApi { - type Api = RuntimeApi; - fn runtime_api<'a>(&'a self) -> ApiRef<'a, Self::Api> { - RuntimeApi { inner: self.clone() }.into() - } - } - sp_api::mock_impl_runtime_apis! { - impl BeefyApi for RuntimeApi { - fn validator_set() -> Option { - BeefyValidatorSet::new(make_beefy_ids(&[$($inits),+]), 0) - } - } + pub fn allow_equivocations(&mut self) { + self.reported_equivocations = Some(Arc::new(Mutex::new(vec![]))); + } +} - impl MmrApi> for RuntimeApi { - fn mmr_root() -> Result { - Ok($mmr_root) - } - - fn generate_proof( - _block_numbers: Vec, - _best_known_block_number: Option - ) -> Result<(Vec, Proof), MmrError> { - unimplemented!() - } - - fn verify_proof(_leaves: Vec, _proof: Proof) -> Result<(), MmrError> { - unimplemented!() - } - - fn verify_proof_stateless( - _root: MmrRootHash, - _leaves: Vec, - _proof: Proof - ) -> Result<(), MmrError> { - unimplemented!() - } - } +// compiler gets confused and warns us about unused inner +#[allow(dead_code)] +pub(crate) struct RuntimeApi { + inner: TestApi, +} + +impl ProvideRuntimeApi for TestApi { + type Api = RuntimeApi; + fn runtime_api(&self) -> ApiRef { + RuntimeApi { inner: self.clone() }.into() + } +} +sp_api::mock_impl_runtime_apis! { + impl BeefyApi for RuntimeApi { + fn beefy_genesis() -> Option> { + Some(self.inner.beefy_genesis) + } + + fn validator_set() -> Option { + Some(self.inner.validator_set.clone()) + } + + fn submit_report_equivocation_unsigned_extrinsic( + proof: EquivocationProof, AuthorityId, Signature>, + _dummy: OpaqueKeyOwnershipProof, + ) -> Option<()> { + if let Some(equivocations_buf) = self.inner.reported_equivocations.as_ref() { + equivocations_buf.lock().push(proof); + None + } else { + panic!("Equivocations not expected, but following proof was reported: {:?}", proof); } } - }; + + fn generate_key_ownership_proof( + _dummy1: ValidatorSetId, + _dummy2: AuthorityId, + ) -> Option { Some(OpaqueKeyOwnershipProof::new(vec![])) } + } + + impl MmrApi> for RuntimeApi { + fn mmr_root() -> Result { + Ok(self.inner.mmr_root_hash) + } + } } -create_test_api!(two_validators, mmr_root: GOOD_MMR_ROOT, BeefyKeyring::Alice, BeefyKeyring::Bob); -create_test_api!( - four_validators, - mmr_root: GOOD_MMR_ROOT, - BeefyKeyring::Alice, - BeefyKeyring::Bob, - BeefyKeyring::Charlie, - BeefyKeyring::Dave -); -create_test_api!( - bad_four_validators, - mmr_root: BAD_MMR_ROOT, - BeefyKeyring::Alice, - BeefyKeyring::Bob, - BeefyKeyring::Charlie, - BeefyKeyring::Dave -); - -fn add_mmr_digest(header: &mut Header, mmr_hash: MmrRootHash) { - header.digest_mut().push(DigestItem::Consensus( - BEEFY_ENGINE_ID, - ConsensusLog::::MmrRoot(mmr_hash).encode(), - )); +fn add_mmr_digest(builder: &mut impl BlockBuilderExt, mmr_hash: MmrRootHash) { + builder + .push_deposit_log_digest_item(DigestItem::Consensus( + BEEFY_ENGINE_ID, + ConsensusLog::::MmrRoot(mmr_hash).encode(), + )) + .unwrap(); } -fn add_auth_change_digest(header: &mut Header, new_auth_set: BeefyValidatorSet) { - header.digest_mut().push(DigestItem::Consensus( - BEEFY_ENGINE_ID, - ConsensusLog::::AuthoritiesChange(new_auth_set).encode(), - )); +fn add_auth_change_digest(builder: &mut impl BlockBuilderExt, new_auth_set: BeefyValidatorSet) { + builder + .push_deposit_log_digest_item(DigestItem::Consensus( + BEEFY_ENGINE_ID, + ConsensusLog::::AuthoritiesChange(new_auth_set).encode(), + )) + .unwrap(); } pub(crate) fn make_beefy_ids(keys: &[BeefyKeyring]) -> Vec { keys.iter().map(|&key| key.public().into()).collect() } -pub(crate) fn create_beefy_keystore(authority: BeefyKeyring) -> SyncCryptoStorePtr { - let keystore = Arc::new(TestKeystore::new()); - SyncCryptoStore::ecdsa_generate_new(&*keystore, BeefyKeyType, Some(&authority.to_seed())) - .expect("Creates authority key"); +pub(crate) fn create_beefy_keystore(authority: BeefyKeyring) -> KeystorePtr { + let keystore = MemoryKeystore::new(); keystore + .ecdsa_generate_new(BeefyKeyType, Some(&authority.to_seed())) + .expect("Creates authority key"); + keystore.into() } -fn voter_init_setup( +async fn voter_init_setup( net: &mut BeefyTestNet, finality: &mut futures::stream::Fuse>, + api: &TestApi, ) -> sp_blockchain::Result> { let backend = net.peer(0).client().as_backend(); - let api = Arc::new(crate::tests::two_validators::TestApi {}); let known_peers = Arc::new(Mutex::new(KnownPeers::new())); - let gossip_validator = - Arc::new(crate::communication::gossip::GossipValidator::new(known_peers)); + let (gossip_validator, _) = GossipValidator::new(known_peers); + let gossip_validator = Arc::new(gossip_validator); let mut gossip_engine = sc_network_gossip::GossipEngine::new( net.peer(0).network_service().clone(), + net.peer(0).sync_service().clone(), "/beefy/whatever", gossip_validator, None, ); - let best_grandpa = - futures::executor::block_on(wait_for_runtime_pallet(&*api, &mut gossip_engine, finality)) - .unwrap(); - load_or_init_voter_state(&*backend, &*api, best_grandpa, 1) + let (beefy_genesis, best_grandpa) = + wait_for_runtime_pallet(api, &mut gossip_engine, finality).await.unwrap(); + load_or_init_voter_state(&*backend, api, beefy_genesis, best_grandpa, 1) } // Spawns beefy voters. Returns a future to spawn on the runtime. @@ -357,7 +385,7 @@ fn initialize_beefy( min_block_delta: u32, ) -> impl Future where - API: ProvideRuntimeApi + Default + Sync + Send, + API: ProvideRuntimeApi + Sync + Send, API::Api: BeefyApi + MmrApi>, { let tasks = FuturesUnordered::new(); @@ -378,6 +406,7 @@ where let network_params = crate::BeefyNetworkParams { network: peer.network_service().clone(), + sync: peer.sync_service().clone(), gossip_protocol_name: beefy_gossip_proto_name(), justifications_protocol_name: on_demand_justif_handler.protocol_name(), _phantom: PhantomData, @@ -396,7 +425,7 @@ where prometheus_registry: None, on_demand_justifications_handler: on_demand_justif_handler, }; - let task = crate::start_beefy_gadget::<_, _, _, _, _, _>(beefy_params); + let task = crate::start_beefy_gadget::<_, _, _, _, _, _, _>(beefy_params); fn assert_send(_: &T) {} assert_send(&task); @@ -473,7 +502,7 @@ async fn wait_for_beefy_signed_commitments( let expected = expected.next(); async move { let signed_commitment = match versioned_finality_proof { - beefy_primitives::VersionedFinalityProof::V1(sc) => sc, + sp_consensus_beefy::VersionedFinalityProof::V1(sc) => sc, }; let commitment_block_num = signed_commitment.commitment.block_number; assert_eq!(expected, Some(commitment_block_num).as_ref()); @@ -485,16 +514,15 @@ async fn wait_for_beefy_signed_commitments( run_until(wait_for, net).await; } -async fn streams_empty_after_timeout( +async fn streams_empty_after_future( streams: Vec>, - net: &Arc>, - timeout: Option, + future: Option, ) where T: std::fmt::Debug, T: std::cmp::PartialEq, { - if let Some(timeout) = timeout { - run_for(timeout, net).await; + if let Some(future) = future { + future.await; } for mut stream in streams.into_iter() { future::poll_fn(move |cx| { @@ -505,21 +533,31 @@ async fn streams_empty_after_timeout( } } +async fn streams_empty_after_timeout( + streams: Vec>, + net: &Arc>, + timeout: Option, +) where + T: std::fmt::Debug, + T: std::cmp::PartialEq, +{ + let timeout = timeout.map(|timeout| Box::pin(run_for(timeout, net))); + streams_empty_after_future(streams, timeout).await; +} + async fn finalize_block_and_wait_for_beefy( net: &Arc>, // peer index and key peers: impl Iterator + Clone, - finalize_targets: &[H256], + finalize_target: &H256, expected_beefy: &[u64], ) { let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); - for block in finalize_targets { - peers.clone().for_each(|(index, _)| { - let client = net.lock().peer(index).client().as_client(); - client.finalize_block(*block, None).unwrap(); - }) - } + peers.clone().for_each(|(index, _)| { + let client = net.lock().peer(index).client().as_client(); + client.finalize_block(*finalize_target, None).unwrap(); + }); if expected_beefy.is_empty() { // run for quarter second then verify no new best beefy block available @@ -544,7 +582,7 @@ async fn beefy_finalizing_blocks() { let mut net = BeefyTestNet::new(2); - let api = Arc::new(two_validators::TestApi {}); + let api = Arc::new(TestApi::with_validator_set(&validator_set)); let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); @@ -557,32 +595,33 @@ async fn beefy_finalizing_blocks() { let peers = peers.into_iter().enumerate(); // finalize block #5 -> BEEFY should finalize #1 (mandatory) and #5 from diff-power-of-two rule. - finalize_block_and_wait_for_beefy(&net, peers.clone(), &[hashes[1], hashes[5]], &[1, 5]).await; + finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[1], &[1]).await; + finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[5], &[5]).await; // GRANDPA finalize #10 -> BEEFY finalize #10 (mandatory) - finalize_block_and_wait_for_beefy(&net, peers.clone(), &[hashes[10]], &[10]).await; + finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[10], &[10]).await; // GRANDPA finalize #18 -> BEEFY finalize #14, then #18 (diff-power-of-two rule) - finalize_block_and_wait_for_beefy(&net, peers.clone(), &[hashes[18]], &[14, 18]).await; + finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[18], &[14, 18]).await; // GRANDPA finalize #20 -> BEEFY finalize #20 (mandatory) - finalize_block_and_wait_for_beefy(&net, peers.clone(), &[hashes[20]], &[20]).await; + finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[20], &[20]).await; // GRANDPA finalize #21 -> BEEFY finalize nothing (yet) because min delta is 4 - finalize_block_and_wait_for_beefy(&net, peers, &[hashes[21]], &[]).await; + finalize_block_and_wait_for_beefy(&net, peers, &hashes[21], &[]).await; } #[tokio::test] async fn lagging_validators() { sp_tracing::try_init_simple(); - let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob]; + let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie]; let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 0).unwrap(); let session_len = 30; let min_block_delta = 1; - let mut net = BeefyTestNet::new(2); - let api = Arc::new(two_validators::TestApi {}); + let mut net = BeefyTestNet::new(3); + let api = Arc::new(TestApi::with_validator_set(&validator_set)); let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); @@ -594,55 +633,47 @@ async fn lagging_validators() { let peers = peers.into_iter().enumerate(); // finalize block #15 -> BEEFY should finalize #1 (mandatory) and #9, #13, #14, #15 from // diff-power-of-two rule. - finalize_block_and_wait_for_beefy( - &net, - peers.clone(), - &[hashes[1], hashes[15]], - &[1, 9, 13, 14, 15], - ) - .await; - - // Alice finalizes #25, Bob lags behind + finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[1], &[1]).await; + finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[15], &[9, 13, 14, 15]).await; + + // Alice and Bob finalize #25, Charlie lags behind let finalize = hashes[25]; let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); + net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); // verify nothing gets finalized by BEEFY - let timeout = Some(Duration::from_millis(250)); + let timeout = Some(Duration::from_millis(100)); streams_empty_after_timeout(best_blocks, &net, timeout).await; streams_empty_after_timeout(versioned_finality_proof, &net, None).await; - // Bob catches up and also finalizes #25 + // Charlie catches up and also finalizes #25 let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); - net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); - // expected beefy finalizes block #17 from diff-power-of-two + net.lock().peer(2).client().as_client().finalize_block(finalize, None).unwrap(); + // expected beefy finalizes blocks 23, 24, 25 from diff-power-of-two wait_for_best_beefy_blocks(best_blocks, &net, &[23, 24, 25]).await; wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &[23, 24, 25]).await; // Both finalize #30 (mandatory session) and #32 -> BEEFY finalize #30 (mandatory), #31, #32 - finalize_block_and_wait_for_beefy( - &net, - peers.clone(), - &[hashes[30], hashes[32]], - &[30, 31, 32], - ) - .await; + finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[30], &[30]).await; + finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[32], &[31, 32]).await; // Verify that session-boundary votes get buffered by client and only processed once // session-boundary block is GRANDPA-finalized (this guarantees authenticity for the new session // validator set). - // Alice finalizes session-boundary mandatory block #60, Bob lags behind + // Alice and Bob finalize session-boundary mandatory block #60, Charlie lags behind let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); let finalize = hashes[60]; net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); + net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); // verify nothing gets finalized by BEEFY - let timeout = Some(Duration::from_millis(250)); + let timeout = Some(Duration::from_millis(100)); streams_empty_after_timeout(best_blocks, &net, timeout).await; streams_empty_after_timeout(versioned_finality_proof, &net, None).await; - // Bob catches up and also finalizes #60 (and should have buffered Alice's vote on #60) + // Charlie catches up and also finalizes #60 (and should have buffered Alice's vote on #60) let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers); - net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); + net.lock().peer(2).client().as_client().finalize_block(finalize, None).unwrap(); // verify beefy skips intermediary votes, and successfully finalizes mandatory block #60 wait_for_best_beefy_blocks(best_blocks, &net, &[60]).await; wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &[60]).await; @@ -660,7 +691,7 @@ async fn correct_beefy_payload() { let mut net = BeefyTestNet::new(4); // Alice, Bob, Charlie will vote on good payloads - let good_api = Arc::new(four_validators::TestApi {}); + let good_api = Arc::new(TestApi::new(1, &validator_set, GOOD_MMR_ROOT)); let good_peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie] .iter() .enumerate() @@ -669,7 +700,7 @@ async fn correct_beefy_payload() { tokio::spawn(initialize_beefy(&mut net, good_peers, min_block_delta)); // Dave will vote on bad mmr roots - let bad_api = Arc::new(bad_four_validators::TestApi {}); + let bad_api = Arc::new(TestApi::new(1, &validator_set, BAD_MMR_ROOT)); let bad_peers = vec![(3, &BeefyKeyring::Dave, bad_api)]; tokio::spawn(initialize_beefy(&mut net, bad_peers, min_block_delta)); @@ -679,7 +710,8 @@ async fn correct_beefy_payload() { let net = Arc::new(Mutex::new(net)); let peers = peers.into_iter().enumerate(); // with 3 good voters and 1 bad one, consensus should happen and best blocks produced. - finalize_block_and_wait_for_beefy(&net, peers, &[hashes[1], hashes[10]], &[1, 9]).await; + finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[1], &[1]).await; + finalize_block_and_wait_for_beefy(&net, peers, &hashes[10], &[9]).await; let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), [(0, BeefyKeyring::Alice)].into_iter()); @@ -691,7 +723,7 @@ async fn correct_beefy_payload() { net.lock().peer(3).client().as_client().finalize_block(hashof11, None).unwrap(); // verify consensus is _not_ reached - let timeout = Some(Duration::from_millis(250)); + let timeout = Some(Duration::from_millis(100)); streams_empty_after_timeout(best_blocks, &net, timeout).await; streams_empty_after_timeout(versioned_finality_proof, &net, None).await; @@ -706,7 +738,7 @@ async fn correct_beefy_payload() { } #[tokio::test] -async fn beefy_importing_blocks() { +async fn beefy_importing_justifications() { use futures::{future::poll_fn, task::Poll}; use sc_block_builder::BlockBuilderProvider; use sc_client_api::BlockBackend; @@ -714,11 +746,17 @@ async fn beefy_importing_blocks() { sp_tracing::try_init_simple(); let mut net = BeefyTestNet::new(2); + let keys = &[BeefyKeyring::Alice, BeefyKeyring::Bob]; + let good_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + // Set BEEFY genesis to block 3. + net.beefy_genesis = 3; let client = net.peer(0).client().clone(); + let full_client = client.as_client(); let (mut block_import, _, peer_data) = net.make_block_import(client.clone()); let PeerData { beefy_voter_links, .. } = peer_data; let justif_stream = beefy_voter_links.lock().take().unwrap().from_block_import_justif_stream; + let mut justif_recv = justif_stream.subscribe(100_000); let params = |block: Block, justifications: Option| { let mut import = BlockImportParams::new(BlockOrigin::File, block.header); @@ -728,36 +766,53 @@ async fn beefy_importing_blocks() { import.fork_choice = Some(ForkChoiceStrategy::LongestChain); import }; + let backend_justif_for = |block_hash: H256| -> Option { + full_client + .justifications(block_hash) + .unwrap() + .and_then(|j| j.get(BEEFY_ENGINE_ID).cloned()) + }; - let full_client = client.as_client(); - let parent_id = BlockId::Number(0); - let builder = full_client.new_block_at(&parent_id, Default::default(), false).unwrap(); + let builder = full_client + .new_block_at(full_client.chain_info().genesis_hash, Default::default(), false) + .unwrap(); let block = builder.build().unwrap().block; let hashof1 = block.header.hash(); - // Import without justifications. - let mut justif_recv = justif_stream.subscribe(100_000); + // Import block 1 without justifications. assert_eq!( - block_import - .import_block(params(block.clone(), None), HashMap::new()) - .await - .unwrap(), + block_import.import_block(params(block.clone(), None)).await.unwrap(), ImportResult::Imported(ImportedAux { is_new_best: true, ..Default::default() }), ); assert_eq!( - block_import.import_block(params(block, None), HashMap::new()).await.unwrap(), + block_import.import_block(params(block, None)).await.unwrap(), ImportResult::AlreadyInChain, ); - // Verify no BEEFY justifications present: + + // Import block 2 with "valid" justification (beefy pallet genesis block not yet reached). + let block_num = 2; + let builder = full_client.new_block_at(hashof1, Default::default(), false).unwrap(); + let block = builder.build().unwrap().block; + let hashof2 = block.header.hash(); + + let proof = crate::justification::tests::new_finality_proof(block_num, &good_set, keys); + let versioned_proof: VersionedFinalityProof, Signature> = proof.into(); + let encoded = versioned_proof.encode(); + let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded))); + assert_eq!( + block_import.import_block(params(block, justif)).await.unwrap(), + ImportResult::Imported(ImportedAux { + bad_justification: false, + is_new_best: true, + ..Default::default() + }), + ); + + // Verify no BEEFY justifications present (for either block 1 or 2): { // none in backend, - assert_eq!( - full_client - .justifications(hashof1) - .unwrap() - .and_then(|j| j.get(BEEFY_ENGINE_ID).cloned()), - None - ); + assert_eq!(backend_justif_for(hashof1), None); + assert_eq!(backend_justif_for(hashof2), None); // and none sent to BEEFY worker. poll_fn(move |cx| { assert_eq!(justif_recv.poll_next_unpin(cx), Poll::Pending); @@ -766,22 +821,18 @@ async fn beefy_importing_blocks() { .await; } - // Import with valid justification. - let parent_id = BlockId::Number(1); - let block_num = 2; - let keys = &[BeefyKeyring::Alice, BeefyKeyring::Bob]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let proof = crate::justification::tests::new_finality_proof(block_num, &validator_set, keys); + // Import block 3 with valid justification. + let block_num = 3; + let builder = full_client.new_block_at(hashof2, Default::default(), false).unwrap(); + let block = builder.build().unwrap().block; + let hashof3 = block.header.hash(); + let proof = crate::justification::tests::new_finality_proof(block_num, &good_set, keys); let versioned_proof: VersionedFinalityProof, Signature> = proof.into(); let encoded = versioned_proof.encode(); let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded))); - - let builder = full_client.new_block_at(&parent_id, Default::default(), false).unwrap(); - let block = builder.build().unwrap().block; - let hashof2 = block.header.hash(); let mut justif_recv = justif_stream.subscribe(100_000); assert_eq!( - block_import.import_block(params(block, justif), HashMap::new()).await.unwrap(), + block_import.import_block(params(block, justif)).await.unwrap(), ImportResult::Imported(ImportedAux { bad_justification: false, is_new_best: true, @@ -791,13 +842,7 @@ async fn beefy_importing_blocks() { // Verify BEEFY justification successfully imported: { // still not in backend (worker is responsible for appending to backend), - assert_eq!( - full_client - .justifications(hashof2) - .unwrap() - .and_then(|j| j.get(BEEFY_ENGINE_ID).cloned()), - None - ); + assert_eq!(backend_justif_for(hashof3), None); // but sent to BEEFY worker // (worker will append it to backend when all previous mandatory justifs are there as well). poll_fn(move |cx| { @@ -810,22 +855,20 @@ async fn beefy_importing_blocks() { .await; } - // Import with invalid justification (incorrect validator set). - let parent_id = BlockId::Number(2); - let block_num = 3; + // Import block 4 with invalid justification (incorrect validator set). + let block_num = 4; + let builder = full_client.new_block_at(hashof3, Default::default(), false).unwrap(); + let block = builder.build().unwrap().block; + let hashof4 = block.header.hash(); let keys = &[BeefyKeyring::Alice]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); - let proof = crate::justification::tests::new_finality_proof(block_num, &validator_set, keys); + let bad_set = ValidatorSet::new(make_beefy_ids(keys), 1).unwrap(); + let proof = crate::justification::tests::new_finality_proof(block_num, &bad_set, keys); let versioned_proof: VersionedFinalityProof, Signature> = proof.into(); let encoded = versioned_proof.encode(); let justif = Some(Justifications::from((BEEFY_ENGINE_ID, encoded))); - - let builder = full_client.new_block_at(&parent_id, Default::default(), false).unwrap(); - let block = builder.build().unwrap().block; - let hashof3 = block.header.hash(); let mut justif_recv = justif_stream.subscribe(100_000); assert_eq!( - block_import.import_block(params(block, justif), HashMap::new()).await.unwrap(), + block_import.import_block(params(block, justif)).await.unwrap(), ImportResult::Imported(ImportedAux { // Still `false` because we don't want to fail import on bad BEEFY justifications. bad_justification: false, @@ -836,13 +879,7 @@ async fn beefy_importing_blocks() { // Verify bad BEEFY justifications was not imported: { // none in backend, - assert_eq!( - full_client - .justifications(hashof3) - .unwrap() - .and_then(|j| j.get(BEEFY_ENGINE_ID).cloned()), - None - ); + assert_eq!(backend_justif_for(hashof4), None); // and none sent to BEEFY worker. poll_fn(move |cx| { assert_eq!(justif_recv.poll_next_unpin(cx), Poll::Pending); @@ -852,39 +889,6 @@ async fn beefy_importing_blocks() { } } -#[tokio::test] -async fn voter_initialization() { - sp_tracing::try_init_simple(); - // Regression test for voter initialization where finality notifications were dropped - // after waiting for BEEFY pallet availability. - - let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob]; - let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 0).unwrap(); - let session_len = 5; - // Should vote on all mandatory blocks no matter the `min_block_delta`. - let min_block_delta = 10; - - let mut net = BeefyTestNet::new(2); - let api = Arc::new(two_validators::TestApi {}); - let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); - tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); - - // push 26 blocks - let hashes = net.generate_blocks_and_sync(26, session_len, &validator_set, false).await; - let net = Arc::new(Mutex::new(net)); - - // Finalize multiple blocks at once to get a burst of finality notifications right from start. - // Need to finalize at least one block in each session, choose randomly. - // Expect voters to pick up all of them and BEEFY-finalize the mandatory blocks of each session. - finalize_block_and_wait_for_beefy( - &net, - peers.into_iter().enumerate(), - &[hashes[1], hashes[6], hashes[10], hashes[17], hashes[24], hashes[26]], - &[1, 5, 10, 15, 20, 25], - ) - .await; -} - #[tokio::test] async fn on_demand_beefy_justification_sync() { sp_tracing::try_init_simple(); @@ -893,12 +897,12 @@ async fn on_demand_beefy_justification_sync() { [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie, BeefyKeyring::Dave]; let validator_set = ValidatorSet::new(make_beefy_ids(&all_peers), 0).unwrap(); let session_len = 5; - let min_block_delta = 5; + let min_block_delta = 4; let mut net = BeefyTestNet::new(4); // Alice, Bob, Charlie start first and make progress through voting. - let api = Arc::new(four_validators::TestApi {}); + let api = Arc::new(TestApi::with_validator_set(&validator_set)); let fast_peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie]; let voting_peers = fast_peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); @@ -912,24 +916,31 @@ async fn on_demand_beefy_justification_sync() { let dave_index = 3; // push 30 blocks - let hashes = net.generate_blocks_and_sync(30, session_len, &validator_set, false).await; + let mut hashes = net.generate_blocks_and_sync(30, session_len, &validator_set, false).await; let fast_peers = fast_peers.into_iter().enumerate(); let net = Arc::new(Mutex::new(net)); // With 3 active voters and one inactive, consensus should happen and blocks BEEFY-finalized. // Need to finalize at least one block in each session, choose randomly. - finalize_block_and_wait_for_beefy( - &net, - fast_peers.clone(), - &[hashes[1], hashes[6], hashes[10], hashes[17], hashes[24]], - &[1, 5, 10, 15, 20], - ) - .await; - - // Spawn Dave, he's now way behind voting and can only catch up through on-demand justif sync. + finalize_block_and_wait_for_beefy(&net, fast_peers.clone(), &hashes[1], &[1]).await; + finalize_block_and_wait_for_beefy(&net, fast_peers.clone(), &hashes[6], &[5]).await; + finalize_block_and_wait_for_beefy(&net, fast_peers.clone(), &hashes[10], &[10]).await; + finalize_block_and_wait_for_beefy(&net, fast_peers.clone(), &hashes[17], &[15]).await; + finalize_block_and_wait_for_beefy(&net, fast_peers.clone(), &hashes[24], &[20]).await; + + // Spawn Dave, they are now way behind voting and can only catch up through on-demand justif + // sync. tokio::spawn(dave_task); - // give Dave a chance to spawn and init. - run_for(Duration::from_millis(400), &net).await; + // Dave pushes and syncs 4 more blocks just to make sure he gets included in gossip. + { + let mut net_guard = net.lock(); + let built_hashes = + net_guard + .peer(dave_index) + .generate_blocks(4, BlockOrigin::File, |builder| builder.build().unwrap().block); + hashes.extend(built_hashes); + net_guard.run_until_sync().await; + } let (dave_best_blocks, _) = get_beefy_streams(&mut net.lock(), [(dave_index, BeefyKeyring::Dave)].into_iter()); @@ -937,28 +948,29 @@ async fn on_demand_beefy_justification_sync() { client.finalize_block(hashes[1], None).unwrap(); // Give Dave task some cpu cycles to process the finality notification, run_for(Duration::from_millis(100), &net).await; - // freshly spun up Dave now needs to listen for gossip to figure out the state of his peers. + // freshly spun up Dave now needs to listen for gossip to figure out the state of their peers. // Have the other peers do some gossip so Dave finds out about their progress. - finalize_block_and_wait_for_beefy(&net, fast_peers, &[hashes[25]], &[25]).await; + finalize_block_and_wait_for_beefy(&net, fast_peers.clone(), &hashes[25], &[25]).await; + finalize_block_and_wait_for_beefy(&net, fast_peers, &hashes[29], &[29]).await; - // Now verify Dave successfully finalized #1 (through on-demand justification request). + // Kick Dave's async loop by finalizing another block. + client.finalize_block(hashes[2], None).unwrap(); + + // And verify Dave successfully finalized #1 (through on-demand justification request). wait_for_best_beefy_blocks(dave_best_blocks, &net, &[1]).await; - // Give Dave all tasks some cpu cycles to burn through their events queues, + // Give all tasks some cpu cycles to burn through their events queues, run_for(Duration::from_millis(100), &net).await; // then verify Dave catches up through on-demand justification requests. - finalize_block_and_wait_for_beefy( - &net, - [(dave_index, BeefyKeyring::Dave)].into_iter(), - &[hashes[6], hashes[10], hashes[17], hashes[24], hashes[26]], - &[5, 10, 15, 20, 25], - ) - .await; - - let all_peers = all_peers.into_iter().enumerate(); - // Now that Dave has caught up, sanity check voting works for all of them. - finalize_block_and_wait_for_beefy(&net, all_peers, &[hashes[30]], &[30]).await; + let (dave_best_blocks, _) = + get_beefy_streams(&mut net.lock(), [(dave_index, BeefyKeyring::Dave)].into_iter()); + client.finalize_block(hashes[6], None).unwrap(); + client.finalize_block(hashes[10], None).unwrap(); + client.finalize_block(hashes[17], None).unwrap(); + client.finalize_block(hashes[24], None).unwrap(); + client.finalize_block(hashes[26], None).unwrap(); + wait_for_best_beefy_blocks(dave_best_blocks, &net, &[5, 10, 15, 20, 25]).await; } #[tokio::test] @@ -970,14 +982,13 @@ async fn should_initialize_voter_at_genesis() { // push 15 blocks with `AuthorityChange` digests every 10 blocks let hashes = net.generate_blocks_and_sync(15, 10, &validator_set, false).await; - let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse(); - // finalize 13 without justifications net.peer(0).client().as_client().finalize_block(hashes[13], None).unwrap(); - // load persistent state - nothing in DB, should init at session boundary - let persisted_state = voter_init_setup(&mut net, &mut finality).unwrap(); + let api = TestApi::with_validator_set(&validator_set); + // load persistent state - nothing in DB, should init at genesis + let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap(); // Test initialization at session boundary. // verify voter initialized with two sessions starting at blocks 1 and 10 @@ -991,18 +1002,83 @@ async fn should_initialize_voter_at_genesis() { // verify next vote target is mandatory block 1 assert_eq!(persisted_state.best_beefy_block(), 0); - assert_eq!(persisted_state.best_grandpa_block(), 13); - assert_eq!( - persisted_state - .voting_oracle() - .voting_target(persisted_state.best_beefy_block(), 13), - Some(1) - ); + assert_eq!(persisted_state.best_grandpa_number(), 13); + assert_eq!(persisted_state.voting_oracle().voting_target(), Some(1)); + + // verify state also saved to db + assert!(verify_persisted_version(&*backend)); + let state = load_persistent(&*backend).unwrap().unwrap(); + assert_eq!(state, persisted_state); +} + +#[tokio::test] +async fn should_initialize_voter_at_custom_genesis() { + let keys = &[BeefyKeyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + let mut net = BeefyTestNet::new(1); + let backend = net.peer(0).client().as_backend(); + // custom pallet genesis is block number 7 + let custom_pallet_genesis = 7; + let api = TestApi::new(custom_pallet_genesis, &validator_set, GOOD_MMR_ROOT); + + // push 15 blocks with `AuthorityChange` digests every 15 blocks + let hashes = net.generate_blocks_and_sync(15, 15, &validator_set, false).await; + let mut finality = net.peer(0).client().as_client().finality_notification_stream().fuse(); + // finalize 3, 5, 8 without justifications + net.peer(0).client().as_client().finalize_block(hashes[3], None).unwrap(); + net.peer(0).client().as_client().finalize_block(hashes[5], None).unwrap(); + net.peer(0).client().as_client().finalize_block(hashes[8], None).unwrap(); + + // load persistent state - nothing in DB, should init at genesis + let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap(); + + // Test initialization at session boundary. + // verify voter initialized with single session starting at block `custom_pallet_genesis` (7) + let sessions = persisted_state.voting_oracle().sessions(); + assert_eq!(sessions.len(), 1); + assert_eq!(sessions[0].session_start(), custom_pallet_genesis); + let rounds = persisted_state.active_round().unwrap(); + assert_eq!(rounds.session_start(), custom_pallet_genesis); + assert_eq!(rounds.validator_set_id(), validator_set.id()); + + // verify next vote target is mandatory block 7 + assert_eq!(persisted_state.best_beefy_block(), 0); + assert_eq!(persisted_state.best_grandpa_number(), 8); + assert_eq!(persisted_state.voting_oracle().voting_target(), Some(custom_pallet_genesis)); // verify state also saved to db assert!(verify_persisted_version(&*backend)); let state = load_persistent(&*backend).unwrap().unwrap(); assert_eq!(state, persisted_state); + + // now re-init after genesis changes + + // should ignore existing aux db state and reinit at new genesis + let new_validator_set = ValidatorSet::new(make_beefy_ids(keys), 42).unwrap(); + let new_pallet_genesis = 10; + let api = TestApi::new(new_pallet_genesis, &new_validator_set, GOOD_MMR_ROOT); + + net.peer(0).client().as_client().finalize_block(hashes[10], None).unwrap(); + // load persistent state - state preset in DB, but with different pallet genesis + let new_persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap(); + + // verify voter initialized with single session starting at block `new_pallet_genesis` (10) + let sessions = new_persisted_state.voting_oracle().sessions(); + assert_eq!(sessions.len(), 1); + assert_eq!(sessions[0].session_start(), new_pallet_genesis); + let rounds = new_persisted_state.active_round().unwrap(); + assert_eq!(rounds.session_start(), new_pallet_genesis); + assert_eq!(rounds.validator_set_id(), new_validator_set.id()); + + // verify next vote target is mandatory block 10 + assert_eq!(new_persisted_state.best_beefy_block(), 0); + assert_eq!(new_persisted_state.best_grandpa_number(), 10); + assert_eq!(new_persisted_state.voting_oracle().voting_target(), Some(new_pallet_genesis)); + + // verify state also saved to db + assert!(verify_persisted_version(&*backend)); + let state = load_persistent(&*backend).unwrap().unwrap(); + assert_eq!(state, new_persisted_state); } #[tokio::test] @@ -1037,8 +1113,9 @@ async fn should_initialize_voter_when_last_final_is_session_boundary() { // Test corner-case where session boundary == last beefy finalized, // expect rounds initialized at last beefy finalized 10. + let api = TestApi::with_validator_set(&validator_set); // load persistent state - nothing in DB, should init at session boundary - let persisted_state = voter_init_setup(&mut net, &mut finality).unwrap(); + let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap(); // verify voter initialized with single session starting at block 10 assert_eq!(persisted_state.voting_oracle().sessions().len(), 1); @@ -1048,14 +1125,9 @@ async fn should_initialize_voter_when_last_final_is_session_boundary() { // verify block 10 is correctly marked as finalized assert_eq!(persisted_state.best_beefy_block(), 10); - assert_eq!(persisted_state.best_grandpa_block(), 13); + assert_eq!(persisted_state.best_grandpa_number(), 13); // verify next vote target is diff-power-of-two block 12 - assert_eq!( - persisted_state - .voting_oracle() - .voting_target(persisted_state.best_beefy_block(), 13), - Some(12) - ); + assert_eq!(persisted_state.voting_oracle().voting_target(), Some(12)); // verify state also saved to db assert!(verify_persisted_version(&*backend)); @@ -1094,8 +1166,9 @@ async fn should_initialize_voter_at_latest_finalized() { // Test initialization at last BEEFY finalized. + let api = TestApi::with_validator_set(&validator_set); // load persistent state - nothing in DB, should init at last BEEFY finalized - let persisted_state = voter_init_setup(&mut net, &mut finality).unwrap(); + let persisted_state = voter_init_setup(&mut net, &mut finality, &api).await.unwrap(); // verify voter initialized with single session starting at block 12 assert_eq!(persisted_state.voting_oracle().sessions().len(), 1); @@ -1105,16 +1178,257 @@ async fn should_initialize_voter_at_latest_finalized() { // verify next vote target is 13 assert_eq!(persisted_state.best_beefy_block(), 12); - assert_eq!(persisted_state.best_grandpa_block(), 13); - assert_eq!( - persisted_state - .voting_oracle() - .voting_target(persisted_state.best_beefy_block(), 13), - Some(13) - ); + assert_eq!(persisted_state.best_grandpa_number(), 13); + assert_eq!(persisted_state.voting_oracle().voting_target(), Some(13)); // verify state also saved to db assert!(verify_persisted_version(&*backend)); let state = load_persistent(&*backend).unwrap().unwrap(); assert_eq!(state, persisted_state); } + +#[tokio::test] +async fn beefy_finalizing_after_pallet_genesis() { + sp_tracing::try_init_simple(); + + let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 14).unwrap(); + let session_len = 10; + let min_block_delta = 1; + let pallet_genesis = 15; + + let mut net = BeefyTestNet::new(2); + + let api = Arc::new(TestApi::new(pallet_genesis, &validator_set, GOOD_MMR_ROOT)); + let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); + tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); + + // push 42 blocks including `AuthorityChange` digests every 10 blocks. + let hashes = net.generate_blocks_and_sync(42, session_len, &validator_set, true).await; + + let net = Arc::new(Mutex::new(net)); + let peers = peers.into_iter().enumerate(); + + // Minimum BEEFY block delta is 1. + + // GRANDPA finalize blocks leading up to BEEFY pallet genesis -> BEEFY should finalize nothing. + finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[14], &[]).await; + + // GRANDPA finalize block #16 -> BEEFY should finalize #15 (genesis mandatory) and #16. + finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[16], &[15, 16]).await; + + // GRANDPA finalize #21 -> BEEFY finalize #20 (mandatory) and #21 + finalize_block_and_wait_for_beefy(&net, peers.clone(), &hashes[21], &[20, 21]).await; +} + +#[tokio::test] +async fn beefy_reports_equivocations() { + sp_tracing::try_init_simple(); + + let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie]; + let validator_set = ValidatorSet::new(make_beefy_ids(&peers), 0).unwrap(); + let session_len = 10; + let min_block_delta = 4; + + let mut net = BeefyTestNet::new(3); + + // Alice votes on good MMR roots, equivocations are allowed/expected. + let mut api_alice = TestApi::with_validator_set(&validator_set); + api_alice.allow_equivocations(); + let api_alice = Arc::new(api_alice); + let alice = (0, &BeefyKeyring::Alice, api_alice.clone()); + tokio::spawn(initialize_beefy(&mut net, vec![alice], min_block_delta)); + + // Bob votes on bad MMR roots, equivocations are allowed/expected. + let mut api_bob = TestApi::new(1, &validator_set, BAD_MMR_ROOT); + api_bob.allow_equivocations(); + let api_bob = Arc::new(api_bob); + let bob = (1, &BeefyKeyring::Bob, api_bob.clone()); + tokio::spawn(initialize_beefy(&mut net, vec![bob], min_block_delta)); + + // We spawn another node voting with Bob key, on alternate bad MMR roots (equivocating). + // Equivocations are allowed/expected. + let mut api_bob_prime = TestApi::new(1, &validator_set, ALTERNATE_BAD_MMR_ROOT); + api_bob_prime.allow_equivocations(); + let api_bob_prime = Arc::new(api_bob_prime); + let bob_prime = (2, &BeefyKeyring::Bob, api_bob_prime.clone()); + tokio::spawn(initialize_beefy(&mut net, vec![bob_prime], min_block_delta)); + + // push 42 blocks including `AuthorityChange` digests every 10 blocks. + let hashes = net.generate_blocks_and_sync(42, session_len, &validator_set, false).await; + + let net = Arc::new(Mutex::new(net)); + + // Minimum BEEFY block delta is 4. + + let peers = peers.into_iter().enumerate(); + // finalize block #1 -> BEEFY should not finalize anything (each node votes on different MMR). + let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); + peers.clone().for_each(|(index, _)| { + let client = net.lock().peer(index).client().as_client(); + client.finalize_block(hashes[1], None).unwrap(); + }); + + // run for up to 5 seconds waiting for Alice's report of Bob/Bob_Prime equivocation. + for wait_ms in [250, 500, 1250, 3000] { + run_for(Duration::from_millis(wait_ms), &net).await; + if !api_alice.reported_equivocations.as_ref().unwrap().lock().is_empty() { + break + } + } + + // Verify expected equivocation + let alice_reported_equivocations = api_alice.reported_equivocations.as_ref().unwrap().lock(); + assert_eq!(alice_reported_equivocations.len(), 1); + let equivocation_proof = alice_reported_equivocations.get(0).unwrap(); + assert_eq!(equivocation_proof.first.id, BeefyKeyring::Bob.public()); + assert_eq!(equivocation_proof.first.commitment.block_number, 1); + + // Verify neither Bob or Bob_Prime report themselves as equivocating. + assert!(api_bob.reported_equivocations.as_ref().unwrap().lock().is_empty()); + assert!(api_bob_prime.reported_equivocations.as_ref().unwrap().lock().is_empty()); + + // sanity verify no new blocks have been finalized by BEEFY + streams_empty_after_timeout(best_blocks, &net, None).await; + streams_empty_after_timeout(versioned_finality_proof, &net, None).await; +} + +#[tokio::test] +async fn gossipped_finality_proofs() { + sp_tracing::try_init_simple(); + + let validators = [BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie]; + // Only Alice and Bob are running the voter -> finality threshold not reached + let peers = [BeefyKeyring::Alice, BeefyKeyring::Bob]; + let validator_set = ValidatorSet::new(make_beefy_ids(&validators), 0).unwrap(); + let session_len = 30; + let min_block_delta = 1; + + let mut net = BeefyTestNet::new(3); + let api = Arc::new(TestApi::with_validator_set(&validator_set)); + let beefy_peers = peers.iter().enumerate().map(|(id, key)| (id, key, api.clone())).collect(); + + let charlie = &net.peers[2]; + let known_peers = Arc::new(Mutex::new(KnownPeers::::new())); + // Charlie will run just the gossip engine and not the full voter. + let (gossip_validator, _) = GossipValidator::new(known_peers); + let charlie_gossip_validator = Arc::new(gossip_validator); + charlie_gossip_validator.update_filter(GossipFilterCfg:: { + start: 1, + end: 10, + validator_set: &validator_set, + }); + let mut charlie_gossip_engine = sc_network_gossip::GossipEngine::new( + charlie.network_service().clone(), + charlie.sync_service().clone(), + beefy_gossip_proto_name(), + charlie_gossip_validator.clone(), + None, + ); + + // Alice and Bob run full voter. + tokio::spawn(initialize_beefy(&mut net, beefy_peers, min_block_delta)); + + let net = Arc::new(Mutex::new(net)); + + // Pump net + Charlie gossip to see peers. + let timeout = Box::pin(tokio::time::sleep(Duration::from_millis(200))); + let gossip_engine_pump = &mut charlie_gossip_engine; + let pump_with_timeout = future::select(gossip_engine_pump, timeout); + run_until(pump_with_timeout, &net).await; + + // push 10 blocks + let hashes = net.lock().generate_blocks_and_sync(10, session_len, &validator_set, true).await; + + let peers = peers.into_iter().enumerate(); + + // Alice, Bob and Charlie finalize #1, Alice and Bob vote on it, but not Charlie. + let finalize = hashes[1]; + let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); + net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); + net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); + net.lock().peer(2).client().as_client().finalize_block(finalize, None).unwrap(); + // verify nothing gets finalized by BEEFY + let timeout = Box::pin(tokio::time::sleep(Duration::from_millis(100))); + let pump_net = futures::future::poll_fn(|cx| { + net.lock().poll(cx); + Poll::<()>::Pending + }); + let pump_gossip = &mut charlie_gossip_engine; + let pump_with_timeout = future::select(pump_gossip, future::select(pump_net, timeout)); + streams_empty_after_future(best_blocks, Some(pump_with_timeout)).await; + streams_empty_after_timeout(versioned_finality_proof, &net, None).await; + + let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); + // Charlie gossips finality proof for #1 -> Alice and Bob also finalize. + let proof = crate::communication::gossip::tests::dummy_proof(1, &validator_set); + let gossip_proof = GossipMessage::::FinalityProof(proof); + let encoded_proof = gossip_proof.encode(); + charlie_gossip_engine.gossip_message(proofs_topic::(), encoded_proof, true); + // Expect #1 is finalized. + wait_for_best_beefy_blocks(best_blocks, &net, &[1]).await; + wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &[1]).await; + + // Code above verifies gossipped finality proofs are correctly imported and consumed by voters. + // Next, let's verify finality proofs are correctly generated and gossipped by voters. + + // Everyone finalizes #2 + let block_number = 2u64; + let finalize = hashes[block_number as usize]; + let (best_blocks, versioned_finality_proof) = get_beefy_streams(&mut net.lock(), peers.clone()); + net.lock().peer(0).client().as_client().finalize_block(finalize, None).unwrap(); + net.lock().peer(1).client().as_client().finalize_block(finalize, None).unwrap(); + net.lock().peer(2).client().as_client().finalize_block(finalize, None).unwrap(); + + // Simulate Charlie vote on #2 + let header = net.lock().peer(2).client().as_client().expect_header(finalize).unwrap(); + let mmr_root = find_mmr_root_digest::(&header).unwrap(); + let payload = Payload::from_single_entry(known_payloads::MMR_ROOT_ID, mmr_root.encode()); + let commitment = Commitment { payload, block_number, validator_set_id: validator_set.id() }; + let signature = sign_commitment(&BeefyKeyring::Charlie, &commitment); + let vote_message = VoteMessage { commitment, id: BeefyKeyring::Charlie.public(), signature }; + let encoded_vote = GossipMessage::::Vote(vote_message).encode(); + charlie_gossip_engine.gossip_message(votes_topic::(), encoded_vote, true); + + // Expect #2 is finalized. + wait_for_best_beefy_blocks(best_blocks, &net, &[2]).await; + wait_for_beefy_signed_commitments(versioned_finality_proof, &net, &[2]).await; + + // Now verify Charlie also sees the gossipped proof generated by either Alice or Bob. + let mut charlie_gossip_proofs = Box::pin( + charlie_gossip_engine + .messages_for(proofs_topic::()) + .filter_map(|notification| async move { + GossipMessage::::decode(&mut ¬ification.message[..]).ok().and_then( + |message| match message { + GossipMessage::::Vote(_) => unreachable!(), + GossipMessage::::FinalityProof(proof) => Some(proof), + }, + ) + }) + .fuse(), + ); + loop { + let pump_net = futures::future::poll_fn(|cx| { + net.lock().poll(cx); + Poll::<()>::Pending + }); + let mut gossip_engine = &mut charlie_gossip_engine; + futures::select! { + // pump gossip engine + _ = gossip_engine => unreachable!(), + // pump network + _ = pump_net.fuse() => unreachable!(), + // verify finality proof has been gossipped + proof = charlie_gossip_proofs.next() => { + let proof = proof.unwrap(); + let (round, _) = proof_block_num_and_set_id::(&proof); + match round { + 1 => continue, // finality proof generated by Charlie in the previous round + 2 => break, // finality proof generated by Alice or Bob and gossiped to Charlie + _ => panic!("Charlie got unexpected finality proof"), + } + }, + } + } +} diff --git a/client/beefy/src/worker.rs b/client/consensus/beefy/src/worker.rs similarity index 61% rename from client/beefy/src/worker.rs rename to client/consensus/beefy/src/worker.rs index 19ab52f52022..91b00ddb2cef 100644 --- a/client/beefy/src/worker.rs +++ b/client/consensus/beefy/src/worker.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,47 +18,44 @@ use crate::{ communication::{ - gossip::{topic, GossipValidator}, - request_response::outgoing_requests_engine::OnDemandJustificationsEngine, + gossip::{proofs_topic, votes_topic, GossipFilterCfg, GossipMessage, GossipValidator}, + peers::PeerReport, + request_response::outgoing_requests_engine::{OnDemandJustificationsEngine, ResponseInfo}, }, error::Error, justification::BeefyVersionedFinalityProof, - keystore::BeefyKeystore, + keystore::{BeefyKeystore, BeefySignatureHasher}, metric_inc, metric_set, - metrics::Metrics, - round::Rounds, + metrics::VoterMetrics, + round::{Rounds, VoteImportResult}, BeefyVoterLinks, LOG_TARGET, }; -use beefy_primitives::{ - crypto::{AuthorityId, Signature}, - Commitment, ConsensusLog, Payload, PayloadProvider, SignedCommitment, ValidatorSet, - VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, -}; use codec::{Codec, Decode, Encode}; use futures::{stream::Fuse, FutureExt, StreamExt}; use log::{debug, error, info, log_enabled, trace, warn}; use sc_client_api::{Backend, FinalityNotification, FinalityNotifications, HeaderBackend}; -use sc_network_common::service::{NetworkEventStream, NetworkRequest}; use sc_network_gossip::GossipEngine; -use sc_utils::notification::NotificationReceiver; -use sp_api::BlockId; +use sc_utils::{mpsc::TracingUnboundedReceiver, notification::NotificationReceiver}; +use sp_api::{BlockId, ProvideRuntimeApi}; use sp_arithmetic::traits::{AtLeast32Bit, Saturating}; use sp_consensus::SyncOracle; +use sp_consensus_beefy::{ + check_equivocation_proof, + crypto::{AuthorityId, Signature}, + BeefyApi, Commitment, ConsensusLog, EquivocationProof, PayloadProvider, ValidatorSet, + VersionedFinalityProof, VoteMessage, BEEFY_ENGINE_ID, +}; use sp_runtime::{ generic::OpaqueDigestItemId, - traits::{Block, ConstU32, Header, NumberFor, Zero}, - BoundedVec, SaturatedConversion, + traits::{Block, Header, NumberFor, Zero}, + SaturatedConversion, }; use std::{ collections::{BTreeMap, BTreeSet, VecDeque}, fmt::Debug, - marker::PhantomData, sync::Arc, }; -/// Bound for the number of buffered future voting rounds. -const MAX_BUFFERED_VOTE_ROUNDS: usize = 600; -/// Bound for the number of buffered votes per round number. -const MAX_BUFFERED_VOTES_PER_ROUND: u32 = 1000; + /// Bound for the number of pending justifications - use 2400 - the max number /// of justifications possible in a single session. const MAX_BUFFERED_JUSTIFICATIONS: usize = 2400; @@ -72,40 +69,51 @@ pub(crate) enum RoundAction { /// Responsible for the voting strategy. /// It chooses which incoming votes to accept and which votes to generate. /// Keeps track of voting seen for current and future rounds. +/// +/// Note: this is part of `PersistedState` so any changes here should also bump +/// aux-db schema version. #[derive(Debug, Decode, Encode, PartialEq)] pub(crate) struct VoterOracle { /// Queue of known sessions. Keeps track of voting rounds (block numbers) within each session. /// /// There are three voter states coresponding to three queue states: /// 1. voter uninitialized: queue empty, - /// 2. up-to-date - all mandatory blocks leading up to current GRANDPA finalized: - /// queue has ONE element, the 'current session' where `mandatory_done == true`, + /// 2. up-to-date - all mandatory blocks leading up to current GRANDPA finalized: queue has ONE + /// element, the 'current session' where `mandatory_done == true`, /// 3. lagging behind GRANDPA: queue has [1, N] elements, where all `mandatory_done == false`. - /// In this state, everytime a session gets its mandatory block BEEFY finalized, it's - /// popped off the queue, eventually getting to state `2. up-to-date`. - sessions: VecDeque>, + /// In this state, everytime a session gets its mandatory block BEEFY finalized, it's popped + /// off the queue, eventually getting to state `2. up-to-date`. + sessions: VecDeque>, /// Min delta in block numbers between two blocks, BEEFY should vote on. min_block_delta: u32, + /// Best block we received a GRANDPA finality for. + best_grandpa_block_header: ::Header, + /// Best block a BEEFY voting round has been concluded for. + best_beefy_block: NumberFor, } impl VoterOracle { /// Verify provided `sessions` satisfies requirements, then build `VoterOracle`. pub fn checked_new( - sessions: VecDeque>, + sessions: VecDeque>, min_block_delta: u32, + grandpa_header: ::Header, + best_beefy: NumberFor, ) -> Option { let mut prev_start = Zero::zero(); let mut prev_validator_id = None; // verifies the let mut validate = || -> bool { - if sessions.is_empty() { + let best_grandpa = *grandpa_header.number(); + if sessions.is_empty() || best_beefy > best_grandpa { return false } for (idx, session) in sessions.iter().enumerate() { + let start = session.session_start(); if session.validators().is_empty() { return false } - if session.session_start() <= prev_start { + if start > best_grandpa || start <= prev_start { return false } #[cfg(not(test))] @@ -127,23 +135,35 @@ impl VoterOracle { sessions, // Always target at least one block better than current best beefy. min_block_delta: min_block_delta.max(1), + best_grandpa_block_header: grandpa_header, + best_beefy_block: best_beefy, }) } else { - error!(target: LOG_TARGET, "🥩 Invalid sessions queue: {:?}.", sessions); + error!( + target: LOG_TARGET, + "🥩 Invalid sessions queue: {:?}; best-beefy {:?} best-grandpa-header {:?}.", + sessions, + best_beefy, + grandpa_header + ); None } } // Return reference to rounds pertaining to first session in the queue. // Voting will always happen at the head of the queue. - fn active_rounds(&self) -> Option<&Rounds> { - self.sessions.front() + fn active_rounds(&self) -> Result<&Rounds, Error> { + self.sessions.front().ok_or(Error::UninitSession) } // Return mutable reference to rounds pertaining to first session in the queue. // Voting will always happen at the head of the queue. - fn active_rounds_mut(&mut self) -> Option<&mut Rounds> { - self.sessions.front_mut() + fn active_rounds_mut(&mut self) -> Result<&mut Rounds, Error> { + self.sessions.front_mut().ok_or(Error::UninitSession) + } + + fn current_validator_set(&self) -> Result<&ValidatorSet, Error> { + self.active_rounds().map(|r| r.validator_set()) } // Prune the sessions queue to keep the Oracle in one of the expected three states. @@ -157,7 +177,7 @@ impl VoterOracle { } /// Add new observed session to the Oracle. - pub fn add_session(&mut self, rounds: Rounds) { + pub fn add_session(&mut self, rounds: Rounds) { self.sessions.push_back(rounds); // Once we add a new session we can drop/prune previous session if it's been finalized. self.try_prune(); @@ -166,7 +186,7 @@ impl VoterOracle { /// Finalize a particular block. pub fn finalize(&mut self, block: NumberFor) -> Result<(), Error> { // Conclude voting round for this block. - self.active_rounds_mut().ok_or(Error::UninitSession)?.conclude(block); + self.active_rounds_mut()?.conclude(block); // Prune any now "finalized" sessions from queue. self.try_prune(); Ok(()) @@ -184,30 +204,26 @@ impl VoterOracle { } /// Return `(A, B)` tuple representing inclusive [A, B] interval of votes to accept. - pub fn accepted_interval( - &self, - best_grandpa: NumberFor, - ) -> Result<(NumberFor, NumberFor), Error> { + pub fn accepted_interval(&self) -> Result<(NumberFor, NumberFor), Error> { let rounds = self.sessions.front().ok_or(Error::UninitSession)?; if rounds.mandatory_done() { // There's only one session active and its mandatory is done. - // Accept any GRANDPA finalized vote. - Ok((rounds.session_start(), best_grandpa.into())) + // Accept any vote for a GRANDPA finalized block in a better round. + Ok(( + rounds.session_start().max(self.best_beefy_block), + (*self.best_grandpa_block_header.number()).into(), + )) } else { - // There's at least one session with mandatory not done. + // Current session has mandatory not done. // Only accept votes for the mandatory block in the front of queue. Ok((rounds.session_start(), rounds.session_start())) } } /// Utility function to quickly decide what to do for each round. - pub fn triage_round( - &self, - round: NumberFor, - best_grandpa: NumberFor, - ) -> Result { - let (start, end) = self.accepted_interval(best_grandpa)?; + pub fn triage_round(&self, round: NumberFor) -> Result { + let (start, end) = self.accepted_interval()?; if start <= round && round <= end { Ok(RoundAction::Process) } else if round > end { @@ -219,17 +235,15 @@ impl VoterOracle { /// Return `Some(number)` if we should be voting on block `number`, /// return `None` if there is no block we should vote on. - pub fn voting_target( - &self, - best_beefy: NumberFor, - best_grandpa: NumberFor, - ) -> Option> { + pub fn voting_target(&self) -> Option> { let rounds = if let Some(r) = self.sessions.front() { r } else { debug!(target: LOG_TARGET, "🥩 No voting round started"); return None }; + let best_grandpa = *self.best_grandpa_block_header.number(); + let best_beefy = self.best_beefy_block; // `target` is guaranteed > `best_beefy` since `min_block_delta` is at least `1`. let target = @@ -245,143 +259,106 @@ impl VoterOracle { } } -pub(crate) struct WorkerParams { - pub backend: Arc, - pub payload_provider: P, - pub network: N, - pub key_store: BeefyKeystore, - pub gossip_engine: GossipEngine, - pub gossip_validator: Arc>, - pub on_demand_justifications: OnDemandJustificationsEngine, - pub links: BeefyVoterLinks, - pub metrics: Option, - pub persisted_state: PersistedState, -} - +/// BEEFY voter state persisted in aux DB. +/// +/// Note: Any changes here should also bump aux-db schema version. #[derive(Debug, Decode, Encode, PartialEq)] pub(crate) struct PersistedState { - /// Best block we received a GRANDPA finality for. - best_grandpa_block_header: ::Header, - /// Best block a BEEFY voting round has been concluded for. - best_beefy_block: NumberFor, + /// Best block we voted on. + best_voted: NumberFor, /// Chooses which incoming votes to accept and which votes to generate. /// Keeps track of voting seen for current and future rounds. voting_oracle: VoterOracle, + /// Pallet-beefy genesis block - block number when BEEFY consensus started for this chain. + pallet_genesis: NumberFor, } impl PersistedState { pub fn checked_new( grandpa_header: ::Header, best_beefy: NumberFor, - sessions: VecDeque>, + sessions: VecDeque>, min_block_delta: u32, + pallet_genesis: NumberFor, ) -> Option { - VoterOracle::checked_new(sessions, min_block_delta).map(|voting_oracle| PersistedState { - best_grandpa_block_header: grandpa_header, - best_beefy_block: best_beefy, - voting_oracle, - }) + VoterOracle::checked_new(sessions, min_block_delta, grandpa_header, best_beefy).map( + |voting_oracle| PersistedState { + best_voted: Zero::zero(), + voting_oracle, + pallet_genesis, + }, + ) + } + + pub fn pallet_genesis(&self) -> NumberFor { + self.pallet_genesis } pub(crate) fn set_min_block_delta(&mut self, min_block_delta: u32) { self.voting_oracle.min_block_delta = min_block_delta.max(1); } + pub(crate) fn set_best_beefy(&mut self, best_beefy: NumberFor) { + self.voting_oracle.best_beefy_block = best_beefy; + } + pub(crate) fn set_best_grandpa(&mut self, best_grandpa: ::Header) { - self.best_grandpa_block_header = best_grandpa; + self.voting_oracle.best_grandpa_block_header = best_grandpa; + } + + pub(crate) fn gossip_filter_config(&self) -> Result, Error> { + let (start, end) = self.voting_oracle.accepted_interval()?; + let validator_set = self.voting_oracle.current_validator_set()?; + Ok(GossipFilterCfg { start, end, validator_set }) } } /// A BEEFY worker plays the BEEFY protocol -pub(crate) struct BeefyWorker { +pub(crate) struct BeefyWorker { // utilities - backend: Arc, - payload_provider: P, - network: N, - key_store: BeefyKeystore, + pub backend: Arc, + pub payload_provider: P, + pub runtime: Arc, + pub sync: Arc, + pub key_store: BeefyKeystore, // communication - gossip_engine: GossipEngine, - gossip_validator: Arc>, - on_demand_justifications: OnDemandJustificationsEngine, + pub gossip_engine: GossipEngine, + pub gossip_validator: Arc>, + pub gossip_report_stream: TracingUnboundedReceiver, + pub on_demand_justifications: OnDemandJustificationsEngine, // channels /// Links between the block importer, the background voter and the RPC layer. - links: BeefyVoterLinks, + pub links: BeefyVoterLinks, // voter state /// BEEFY client metrics. - metrics: Option, - /// Buffer holding votes for future processing. - pending_votes: BTreeMap< - NumberFor, - BoundedVec< - VoteMessage, AuthorityId, Signature>, - ConstU32, - >, - >, + pub metrics: Option, /// Buffer holding justifications for future processing. - pending_justifications: BTreeMap, BeefyVersionedFinalityProof>, + pub pending_justifications: BTreeMap, BeefyVersionedFinalityProof>, /// Persisted voter state. - persisted_state: PersistedState, + pub persisted_state: PersistedState, } -impl BeefyWorker +impl BeefyWorker where B: Block + Codec, BE: Backend, P: PayloadProvider, - N: NetworkEventStream + NetworkRequest + SyncOracle + Send + Sync + Clone + 'static, + S: SyncOracle, + R: ProvideRuntimeApi, + R::Api: BeefyApi, { - /// Return a new BEEFY worker instance. - /// - /// Note that a BEEFY worker is only fully functional if a corresponding - /// BEEFY pallet has been deployed on-chain. - /// - /// The BEEFY pallet is needed in order to keep track of the BEEFY authority set. - pub(crate) fn new(worker_params: WorkerParams) -> Self { - let WorkerParams { - backend, - payload_provider, - key_store, - network, - gossip_engine, - gossip_validator, - on_demand_justifications, - links, - metrics, - persisted_state, - } = worker_params; - - BeefyWorker { - backend, - payload_provider, - network, - key_store, - gossip_engine, - gossip_validator, - on_demand_justifications, - links, - metrics, - pending_votes: BTreeMap::new(), - pending_justifications: BTreeMap::new(), - persisted_state, - } - } - fn best_grandpa_block(&self) -> NumberFor { - *self.persisted_state.best_grandpa_block_header.number() - } - - fn best_beefy_block(&self) -> NumberFor { - self.persisted_state.best_beefy_block + *self.persisted_state.voting_oracle.best_grandpa_block_header.number() } fn voting_oracle(&self) -> &VoterOracle { &self.persisted_state.voting_oracle } - fn active_rounds(&mut self) -> Option<&Rounds> { + fn active_rounds(&mut self) -> Result<&Rounds, Error> { self.persisted_state.voting_oracle.active_rounds() } @@ -406,6 +383,7 @@ where if store.intersection(&active).count() == 0 { let msg = "no authority public key found in store".to_string(); debug!(target: LOG_TARGET, "🥩 for block {:?} {}", block, msg); + metric_inc!(self, beefy_no_authority_found_in_store); Err(Error::Keystore(msg)) } else { Ok(()) @@ -421,7 +399,7 @@ where debug!(target: LOG_TARGET, "🥩 New active validator set: {:?}", validator_set); // BEEFY should finalize a mandatory block during each session. - if let Some(active_session) = self.active_rounds() { + if let Ok(active_session) = self.active_rounds() { if !active_session.mandatory_done() { debug!( target: LOG_TARGET, @@ -451,13 +429,33 @@ where ); } - fn handle_finality_notification(&mut self, notification: &FinalityNotification) { - debug!(target: LOG_TARGET, "🥩 Finality notification: {:?}", notification); + fn handle_finality_notification( + &mut self, + notification: &FinalityNotification, + ) -> Result<(), Error> { + debug!( + target: LOG_TARGET, + "🥩 Finality notification: header {:?} tree_route {:?}", + notification.header, + notification.tree_route, + ); let header = ¬ification.header; + self.runtime + .runtime_api() + .beefy_genesis(header.hash()) + .ok() + .flatten() + .filter(|genesis| *genesis == self.persisted_state.pallet_genesis) + .ok_or_else(|| { + let err = Error::ConsensusReset; + error!(target: LOG_TARGET, "🥩 Error: {}", err); + err + })?; + if *header.number() > self.best_grandpa_block() { // update best GRANDPA finalized block we have seen - self.persisted_state.best_grandpa_block_header = header.clone(); + self.persisted_state.set_best_grandpa(header.clone()); // Check all (newly) finalized blocks for new session(s). let backend = self.backend.clone(); @@ -476,37 +474,35 @@ where self.init_session_at(new_validator_set, *header.number()); } } + + // Update gossip validator votes filter. + if let Err(e) = self + .persisted_state + .gossip_filter_config() + .map(|filter| self.gossip_validator.update_filter(filter)) + { + error!(target: LOG_TARGET, "🥩 Voter error: {:?}", e); + } } + + Ok(()) } - /// Based on [VoterOracle] this vote is either processed here or enqueued for later. + /// Based on [VoterOracle] this vote is either processed here or discarded. fn triage_incoming_vote( &mut self, vote: VoteMessage, AuthorityId, Signature>, ) -> Result<(), Error> { let block_num = vote.commitment.block_number; - let best_grandpa = self.best_grandpa_block(); - match self.voting_oracle().triage_round(block_num, best_grandpa)? { - RoundAction::Process => self.handle_vote( - (vote.commitment.payload, vote.commitment.block_number), - (vote.id, vote.signature), - false, - )?, - RoundAction::Enqueue => { - debug!(target: LOG_TARGET, "🥩 Buffer vote for round: {:?}.", block_num); - if self.pending_votes.len() < MAX_BUFFERED_VOTE_ROUNDS { - let votes_vec = self.pending_votes.entry(block_num).or_default(); - if votes_vec.try_push(vote).is_err() { - warn!( - target: LOG_TARGET, - "🥩 Buffer vote dropped for round: {:?}", block_num - ) - } - } else { - warn!(target: LOG_TARGET, "🥩 Buffer vote dropped for round: {:?}.", block_num); - } - }, - RoundAction::Drop => (), + match self.voting_oracle().triage_round(block_num)? { + RoundAction::Process => + if let Some(finality_proof) = self.handle_vote(vote)? { + let gossip_proof = GossipMessage::::FinalityProof(finality_proof); + let encoded_proof = gossip_proof.encode(); + self.gossip_engine.gossip_message(proofs_topic::(), encoded_proof, true); + }, + RoundAction::Drop => metric_inc!(self, beefy_stale_votes), + RoundAction::Enqueue => error!(target: LOG_TARGET, "🥩 unexpected vote: {:?}.", vote), }; Ok(()) } @@ -522,82 +518,71 @@ where VersionedFinalityProof::V1(ref sc) => sc, }; let block_num = signed_commitment.commitment.block_number; - let best_grandpa = self.best_grandpa_block(); - match self.voting_oracle().triage_round(block_num, best_grandpa)? { + match self.voting_oracle().triage_round(block_num)? { RoundAction::Process => { debug!(target: LOG_TARGET, "🥩 Process justification for round: {:?}.", block_num); + metric_inc!(self, beefy_imported_justifications); self.finalize(justification)? }, RoundAction::Enqueue => { debug!(target: LOG_TARGET, "🥩 Buffer justification for round: {:?}.", block_num); if self.pending_justifications.len() < MAX_BUFFERED_JUSTIFICATIONS { self.pending_justifications.entry(block_num).or_insert(justification); + metric_inc!(self, beefy_buffered_justifications); } else { + metric_inc!(self, beefy_buffered_justifications_dropped); warn!( target: LOG_TARGET, "🥩 Buffer justification dropped for round: {:?}.", block_num ); } }, - RoundAction::Drop => (), + RoundAction::Drop => metric_inc!(self, beefy_stale_justifications), }; Ok(()) } fn handle_vote( &mut self, - round: (Payload, NumberFor), - vote: (AuthorityId, Signature), - self_vote: bool, - ) -> Result<(), Error> { - self.gossip_validator.note_round(round.1); - - let rounds = self - .persisted_state - .voting_oracle - .active_rounds_mut() - .ok_or(Error::UninitSession)?; - - if rounds.add_vote(&round, vote, self_vote) { - if let Some(signatures) = rounds.should_conclude(&round) { - self.gossip_validator.conclude_round(round.1); - - let block_num = round.1; - let commitment = Commitment { - payload: round.0, - block_number: block_num, - validator_set_id: rounds.validator_set_id(), - }; - - let finality_proof = - VersionedFinalityProof::V1(SignedCommitment { commitment, signatures }); - - metric_set!(self, beefy_round_concluded, block_num); + vote: VoteMessage, AuthorityId, Signature>, + ) -> Result>, Error> { + let rounds = self.persisted_state.voting_oracle.active_rounds_mut()?; + let block_number = vote.commitment.block_number; + match rounds.add_vote(vote) { + VoteImportResult::RoundConcluded(signed_commitment) => { + let finality_proof = VersionedFinalityProof::V1(signed_commitment); info!( target: LOG_TARGET, - "🥩 Round #{} concluded, finality_proof: {:?}.", round.1, finality_proof + "🥩 Round #{} concluded, finality_proof: {:?}.", block_number, finality_proof ); - // We created the `finality_proof` and know to be valid. // New state is persisted after finalization. - self.finalize(finality_proof)?; - } else { - let mandatory_round = self + self.finalize(finality_proof.clone())?; + metric_inc!(self, beefy_good_votes_processed); + return Ok(Some(finality_proof)) + }, + VoteImportResult::Ok => { + // Persist state after handling mandatory block vote. + if self .voting_oracle() .mandatory_pending() - .map(|p| p.0 == round.1) - .unwrap_or(false); - // Persist state after handling self vote to avoid double voting in case - // of voter restarts. - // Also persist state after handling mandatory block vote. - if self_vote || mandatory_round { + .map(|(mandatory_num, _)| mandatory_num == block_number) + .unwrap_or(false) + { crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) .map_err(|e| Error::Backend(e.to_string()))?; } - } - } - Ok(()) + metric_inc!(self, beefy_good_votes_processed); + }, + VoteImportResult::Equivocation(proof) => { + metric_inc!(self, beefy_equivocation_votes); + self.report_equivocation(proof)?; + }, + VoteImportResult::Invalid => metric_inc!(self, beefy_invalid_votes), + VoteImportResult::Stale => metric_inc!(self, beefy_stale_votes), + }; + Ok(None) } /// Provide BEEFY finality for block based on `finality_proof`: @@ -606,7 +591,7 @@ where /// 3. Persist voter state, /// 4. Send best block hash and `finality_proof` to RPC worker. /// - /// Expects `finality proof` to be valid. + /// Expects `finality proof` to be valid and for a block > current-best-beefy. fn finalize(&mut self, finality_proof: BeefyVersionedFinalityProof) -> Result<(), Error> { let block_num = match finality_proof { VersionedFinalityProof::V1(ref sc) => sc.commitment.block_number, @@ -615,95 +600,68 @@ where // Finalize inner round and update voting_oracle state. self.persisted_state.voting_oracle.finalize(block_num)?; - if block_num > self.best_beefy_block() { - // Set new best BEEFY block number. - self.persisted_state.best_beefy_block = block_num; - crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) - .map_err(|e| Error::Backend(e.to_string()))?; - - metric_set!(self, beefy_best_block, block_num); + // Set new best BEEFY block number. + self.persisted_state.set_best_beefy(block_num); + crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) + .map_err(|e| Error::Backend(e.to_string()))?; - self.on_demand_justifications.cancel_requests_older_than(block_num); + metric_set!(self, beefy_best_block, block_num); - if let Err(e) = self - .backend - .blockchain() - .expect_block_hash_from_id(&BlockId::Number(block_num)) - .and_then(|hash| { - self.links - .to_rpc_best_block_sender - .notify(|| Ok::<_, ()>(hash)) - .expect("forwards closure result; the closure always returns Ok; qed."); - - self.backend - .append_justification(hash, (BEEFY_ENGINE_ID, finality_proof.encode())) - }) { - error!( - target: LOG_TARGET, - "🥩 Error {:?} on appending justification: {:?}", e, finality_proof - ); - } + self.on_demand_justifications.cancel_requests_older_than(block_num); - self.links - .to_rpc_justif_sender - .notify(|| Ok::<_, ()>(finality_proof)) - .expect("forwards closure result; the closure always returns Ok; qed."); - } else { - debug!(target: LOG_TARGET, "🥩 Can't set best beefy to older: {}", block_num); + if let Err(e) = self + .backend + .blockchain() + .expect_block_hash_from_id(&BlockId::Number(block_num)) + .and_then(|hash| { + self.links + .to_rpc_best_block_sender + .notify(|| Ok::<_, ()>(hash)) + .expect("forwards closure result; the closure always returns Ok; qed."); + + self.backend + .append_justification(hash, (BEEFY_ENGINE_ID, finality_proof.encode())) + }) { + error!( + target: LOG_TARGET, + "🥩 Error {:?} on appending justification: {:?}", e, finality_proof + ); } + + self.links + .to_rpc_justif_sender + .notify(|| Ok::<_, ()>(finality_proof)) + .expect("forwards closure result; the closure always returns Ok; qed."); + + // Update gossip validator votes filter. + self.persisted_state + .gossip_filter_config() + .map(|filter| self.gossip_validator.update_filter(filter))?; Ok(()) } - /// Handle previously buffered justifications and votes that now land in the voting interval. - fn try_pending_justif_and_votes(&mut self) -> Result<(), Error> { - let best_grandpa = self.best_grandpa_block(); - let _ph = PhantomData::::default(); - - fn to_process_for( - pending: &mut BTreeMap, T>, - (start, end): (NumberFor, NumberFor), - _: PhantomData, - ) -> BTreeMap, T> { + /// Handle previously buffered justifications, that now land in the voting interval. + fn try_pending_justififactions(&mut self) -> Result<(), Error> { + // Interval of blocks for which we can process justifications and votes right now. + let (start, end) = self.voting_oracle().accepted_interval()?; + // Process pending justifications. + if !self.pending_justifications.is_empty() { // These are still pending. - let still_pending = pending.split_off(&end.saturating_add(1u32.into())); + let still_pending = + self.pending_justifications.split_off(&end.saturating_add(1u32.into())); // These can be processed. - let to_handle = pending.split_off(&start); + let justifs_to_process = self.pending_justifications.split_off(&start); // The rest can be dropped. - *pending = still_pending; - // Return ones to process. - to_handle - } - // Interval of blocks for which we can process justifications and votes right now. - let mut interval = self.voting_oracle().accepted_interval(best_grandpa)?; + self.pending_justifications = still_pending; - // Process pending justifications. - if !self.pending_justifications.is_empty() { - let justifs_to_handle = to_process_for(&mut self.pending_justifications, interval, _ph); - for (num, justification) in justifs_to_handle.into_iter() { + for (num, justification) in justifs_to_process.into_iter() { debug!(target: LOG_TARGET, "🥩 Handle buffered justification for: {:?}.", num); + metric_inc!(self, beefy_imported_justifications); if let Err(err) = self.finalize(justification) { error!(target: LOG_TARGET, "🥩 Error finalizing block: {}", err); } } - // Possibly new interval after processing justifications. - interval = self.voting_oracle().accepted_interval(best_grandpa)?; - } - - // Process pending votes. - if !self.pending_votes.is_empty() { - let votes_to_handle = to_process_for(&mut self.pending_votes, interval, _ph); - for (num, votes) in votes_to_handle.into_iter() { - debug!(target: LOG_TARGET, "🥩 Handle buffered votes for: {:?}.", num); - for v in votes.into_iter() { - if let Err(err) = self.handle_vote( - (v.commitment.payload, v.commitment.block_number), - (v.id, v.signature), - false, - ) { - error!(target: LOG_TARGET, "🥩 Error handling buffered vote: {}", err); - }; - } - } + metric_set!(self, beefy_buffered_justifications, self.pending_justifications.len()); } Ok(()) } @@ -711,12 +669,11 @@ where /// Decide if should vote, then vote.. or don't.. fn try_to_vote(&mut self) -> Result<(), Error> { // Vote if there's now a new vote target. - if let Some(target) = self - .voting_oracle() - .voting_target(self.best_beefy_block(), self.best_grandpa_block()) - { + if let Some(target) = self.voting_oracle().voting_target() { metric_set!(self, beefy_should_vote_on, target); - self.do_vote(target)?; + if target > self.persisted_state.best_voted { + self.do_vote(target)?; + } } Ok(()) } @@ -730,7 +687,7 @@ where // Most of the time we get here, `target` is actually `best_grandpa`, // avoid getting header from backend in that case. let target_header = if target_number == self.best_grandpa_block() { - self.persisted_state.best_grandpa_block_header.clone() + self.persisted_state.voting_oracle.best_grandpa_block_header.clone() } else { let hash = self .backend @@ -761,18 +718,7 @@ where return Ok(()) }; - let rounds = self - .persisted_state - .voting_oracle - .active_rounds_mut() - .ok_or(Error::UninitSession)?; - if !rounds.should_self_vote(&(payload.clone(), target_number)) { - debug!( - target: LOG_TARGET, - "🥩 Don't double vote for block number: {:?}", target_number - ); - return Ok(()) - } + let rounds = self.persisted_state.voting_oracle.active_rounds_mut()?; let (validators, validator_set_id) = (rounds.validators(), rounds.validator_set_id()); let authority_id = if let Some(id) = self.key_store.authority_id(validators) { @@ -804,35 +750,35 @@ where BeefyKeystore::verify(&authority_id, &signature, &encoded_commitment) ); - let message = VoteMessage { commitment, id: authority_id, signature }; - - let encoded_message = message.encode(); - - metric_inc!(self, beefy_votes_sent); - - debug!(target: LOG_TARGET, "🥩 Sent vote message: {:?}", message); - - if let Err(err) = self.handle_vote( - (message.commitment.payload, message.commitment.block_number), - (message.id, message.signature), - true, - ) { + let vote = VoteMessage { commitment, id: authority_id, signature }; + if let Some(finality_proof) = self.handle_vote(vote.clone()).map_err(|err| { error!(target: LOG_TARGET, "🥩 Error handling self vote: {}", err); + err + })? { + let encoded_proof = GossipMessage::::FinalityProof(finality_proof).encode(); + self.gossip_engine.gossip_message(proofs_topic::(), encoded_proof, true); + } else { + metric_inc!(self, beefy_votes_sent); + debug!(target: LOG_TARGET, "🥩 Sent vote message: {:?}", vote); + let encoded_vote = GossipMessage::::Vote(vote).encode(); + self.gossip_engine.gossip_message(votes_topic::(), encoded_vote, false); } - self.gossip_engine.gossip_message(topic::(), encoded_message, false); - - Ok(()) + // Persist state after vote to avoid double voting in case of voter restarts. + self.persisted_state.best_voted = target_number; + metric_set!(self, beefy_best_voted, target_number); + crate::aux_schema::write_voter_state(&*self.backend, &self.persisted_state) + .map_err(|e| Error::Backend(e.to_string())) } fn process_new_state(&mut self) { // Handle pending justifications and/or votes for now GRANDPA finalized blocks. - if let Err(err) = self.try_pending_justif_and_votes() { + if let Err(err) = self.try_pending_justififactions() { debug!(target: LOG_TARGET, "🥩 {}", err); } // Don't bother voting or requesting justifications during major sync. - if !self.network.is_major_syncing() { + if !self.sync.is_major_syncing() { // There were external events, 'state' is changed, author a vote if needed/possible. if let Err(err) = self.try_to_vote() { debug!(target: LOG_TARGET, "🥩 {}", err); @@ -848,8 +794,7 @@ where /// Main loop for BEEFY worker. /// - /// Wait for BEEFY runtime pallet to be available, then start the main async loop - /// which is driven by finality notifications and gossiped votes. + /// Run the main async loop which is driven by finality notifications and gossiped votes. pub(crate) async fn run( mut self, mut block_import_justif: Fuse>>, @@ -863,14 +808,25 @@ where let mut votes = Box::pin( self.gossip_engine - .messages_for(topic::()) + .messages_for(votes_topic::()) .filter_map(|notification| async move { - trace!(target: LOG_TARGET, "🥩 Got vote message: {:?}", notification); - - VoteMessage::, AuthorityId, Signature>::decode( - &mut ¬ification.message[..], - ) - .ok() + let vote = GossipMessage::::decode(&mut ¬ification.message[..]) + .ok() + .and_then(|message| message.unwrap_vote()); + trace!(target: LOG_TARGET, "🥩 Got vote message: {:?}", vote); + vote + }) + .fuse(), + ); + let mut gossip_proofs = Box::pin( + self.gossip_engine + .messages_for(proofs_topic::()) + .filter_map(|notification| async move { + let proof = GossipMessage::::decode(&mut ¬ification.message[..]) + .ok() + .and_then(|message| message.unwrap_finality_proof()); + trace!(target: LOG_TARGET, "🥩 Got gossip proof message: {:?}", proof); + proof }) .fuse(), ); @@ -879,32 +835,42 @@ where // Act on changed 'state'. self.process_new_state(); + // Mutable reference used to drive the gossip engine. let mut gossip_engine = &mut self.gossip_engine; + // Use temp val and report after async section, + // to avoid having to Mutex-wrap `gossip_engine`. + let mut gossip_report: Option = None; + // Wait for, and handle external events. // The branches below only change 'state', actual voting happens afterwards, // based on the new resulting 'state'. futures::select_biased! { // Use `select_biased!` to prioritize order below. - // Make sure to pump gossip engine. - _ = gossip_engine => { - error!(target: LOG_TARGET, "🥩 Gossip engine has terminated, closing worker."); - return; - }, // Process finality notifications first since these drive the voter. notification = finality_notifications.next() => { - if let Some(notification) = notification { - self.handle_finality_notification(¬ification); - } else { + if notification.and_then(|notif| { + self.handle_finality_notification(¬if).ok() + }).is_none() { error!(target: LOG_TARGET, "🥩 Finality stream terminated, closing worker."); return; } }, + // Make sure to pump gossip engine. + _ = gossip_engine => { + error!(target: LOG_TARGET, "🥩 Gossip engine has terminated, closing worker."); + return; + }, // Process incoming justifications as these can make some in-flight votes obsolete. - justif = self.on_demand_justifications.next().fuse() => { - if let Some(justif) = justif { - if let Err(err) = self.triage_incoming_justif(justif) { - debug!(target: LOG_TARGET, "🥩 {}", err); - } + response_info = self.on_demand_justifications.next().fuse() => { + match response_info { + ResponseInfo::ValidProof(justif, peer_report) => { + if let Err(err) = self.triage_incoming_justif(justif) { + debug!(target: LOG_TARGET, "🥩 {}", err); + } + gossip_report = Some(peer_report); + }, + ResponseInfo::PeerReport(peer_report) => gossip_report = Some(peer_report), + ResponseInfo::Pending => (), } }, justif = block_import_justif.next() => { @@ -919,6 +885,20 @@ where return; } }, + justif = gossip_proofs.next() => { + if let Some(justif) = justif { + // Gossiped justifications have already been verified by `GossipValidator`. + if let Err(err) = self.triage_incoming_justif(justif) { + debug!(target: LOG_TARGET, "🥩 {}", err); + } + } else { + error!( + target: LOG_TARGET, + "🥩 Finality proofs gossiping stream terminated, closing worker." + ); + return; + } + }, // Finally process incoming votes. vote = votes.next() => { if let Some(vote) = vote { @@ -927,13 +907,83 @@ where debug!(target: LOG_TARGET, "🥩 {}", err); } } else { - error!(target: LOG_TARGET, "🥩 Votes gossiping stream terminated, closing worker."); + error!( + target: LOG_TARGET, + "🥩 Votes gossiping stream terminated, closing worker." + ); return; } }, + // Process peer reports. + report = self.gossip_report_stream.next() => { + gossip_report = report; + }, + } + if let Some(PeerReport { who, cost_benefit }) = gossip_report { + self.gossip_engine.report(who, cost_benefit); } } } + + /// Report the given equivocation to the BEEFY runtime module. This method + /// generates a session membership proof of the offender and then submits an + /// extrinsic to report the equivocation. In particular, the session membership + /// proof must be generated at the block at which the given set was active which + /// isn't necessarily the best block if there are pending authority set changes. + pub(crate) fn report_equivocation( + &self, + proof: EquivocationProof, AuthorityId, Signature>, + ) -> Result<(), Error> { + let rounds = self.persisted_state.voting_oracle.active_rounds()?; + let (validators, validator_set_id) = (rounds.validators(), rounds.validator_set_id()); + let offender_id = proof.offender_id().clone(); + + if !check_equivocation_proof::<_, _, BeefySignatureHasher>(&proof) { + debug!(target: LOG_TARGET, "🥩 Skip report for bad equivocation {:?}", proof); + return Ok(()) + } else if let Some(local_id) = self.key_store.authority_id(validators) { + if offender_id == local_id { + debug!(target: LOG_TARGET, "🥩 Skip equivocation report for own equivocation"); + return Ok(()) + } + } + + let number = *proof.round_number(); + let hash = self + .backend + .blockchain() + .expect_block_hash_from_id(&BlockId::Number(number)) + .map_err(|err| { + let err_msg = format!( + "Couldn't get hash for block #{:?} (error: {:?}), skipping report for equivocation", + number, err + ); + Error::Backend(err_msg) + })?; + let runtime_api = self.runtime.runtime_api(); + // generate key ownership proof at that block + let key_owner_proof = match runtime_api + .generate_key_ownership_proof(hash, validator_set_id, offender_id) + .map_err(Error::RuntimeApi)? + { + Some(proof) => proof, + None => { + debug!( + target: LOG_TARGET, + "🥩 Equivocation offender not part of the authority set." + ); + return Ok(()) + }, + }; + + // submit equivocation report at **best** block + let best_block_hash = self.backend.blockchain().info().best_hash; + runtime_api + .submit_report_equivocation_unsigned_extrinsic(best_block_hash, proof, key_owner_proof) + .map_err(Error::RuntimeApi)?; + + Ok(()) + } } /// Scan the `header` digest log for a BEEFY validator set change. Return either the new @@ -992,24 +1042,26 @@ pub(crate) mod tests { use super::*; use crate::{ communication::notification::{BeefyBestBlockStream, BeefyVersionedFinalityProofStream}, - keystore::tests::Keyring, tests::{ - create_beefy_keystore, get_beefy_streams, make_beefy_ids, two_validators::TestApi, - BeefyPeer, BeefyTestNet, + create_beefy_keystore, get_beefy_streams, make_beefy_ids, BeefyPeer, BeefyTestNet, + TestApi, }, BeefyRPCLinks, KnownPeers, }; - use beefy_primitives::{known_payloads, mmr::MmrRootProvider}; use futures::{future::poll_fn, task::Poll}; use parking_lot::Mutex; use sc_client_api::{Backend as BackendT, HeaderBackend}; - use sc_network::NetworkService; + use sc_network_sync::SyncingService; use sc_network_test::TestNetFactory; use sp_api::HeaderT; use sp_blockchain::Backend as BlockchainBackendT; - use sp_runtime::traits::{One, Zero}; + use sp_consensus_beefy::{ + generate_equivocation_proof, known_payloads, known_payloads::MMR_ROOT_ID, + mmr::MmrRootProvider, Keyring, Payload, SignedCommitment, + }; + use sp_runtime::traits::One; use substrate_test_runtime_client::{ - runtime::{Block, Digest, DigestItem, Header, H256}, + runtime::{Block, Digest, DigestItem, Header}, Backend, }; @@ -1018,27 +1070,27 @@ pub(crate) mod tests { &self.voting_oracle } - pub fn active_round(&self) -> Option<&Rounds> { + pub fn active_round(&self) -> Result<&Rounds, Error> { self.voting_oracle.active_rounds() } pub fn best_beefy_block(&self) -> NumberFor { - self.best_beefy_block + self.voting_oracle.best_beefy_block } - pub fn best_grandpa_block(&self) -> NumberFor { - *self.best_grandpa_block_header.number() + pub fn best_grandpa_number(&self) -> NumberFor { + *self.voting_oracle.best_grandpa_block_header.number() } } impl VoterOracle { - pub fn sessions(&self) -> &VecDeque> { + pub fn sessions(&self) -> &VecDeque> { &self.sessions } } fn create_beefy_worker( - peer: &BeefyPeer, + peer: &mut BeefyPeer, key: &Keyring, min_block_delta: u32, genesis_validator_set: ValidatorSet, @@ -1046,7 +1098,8 @@ pub(crate) mod tests { Block, Backend, MmrRootProvider, - Arc>, + TestApi, + Arc>, > { let keystore = create_beefy_keystore(*key); @@ -1068,42 +1121,58 @@ pub(crate) mod tests { }; let backend = peer.client().as_backend(); - let api = Arc::new(TestApi {}); + let beefy_genesis = 1; + let api = Arc::new(TestApi::with_validator_set(&genesis_validator_set)); let network = peer.network_service().clone(); + let sync = peer.sync_service().clone(); let known_peers = Arc::new(Mutex::new(KnownPeers::new())); - let gossip_validator = Arc::new(GossipValidator::new(known_peers.clone())); - let gossip_engine = - GossipEngine::new(network.clone(), "/beefy/1", gossip_validator.clone(), None); + let (gossip_validator, gossip_report_stream) = GossipValidator::new(known_peers.clone()); + let gossip_validator = Arc::new(gossip_validator); + let gossip_engine = GossipEngine::new( + network.clone(), + sync.clone(), + "/beefy/1", + gossip_validator.clone(), + None, + ); + let metrics = None; let on_demand_justifications = OnDemandJustificationsEngine::new( network.clone(), "/beefy/justifs/1".into(), known_peers, + None, ); - let genesis_header = backend + // Push 1 block - will start first session. + let hashes = peer.push_blocks(1, false); + backend.finalize_block(hashes[0], None).unwrap(); + let first_header = backend .blockchain() - .expect_header(backend.blockchain().info().genesis_hash) + .expect_header(backend.blockchain().info().best_hash) .unwrap(); let persisted_state = PersistedState::checked_new( - genesis_header, + first_header, Zero::zero(), vec![Rounds::new(One::one(), genesis_validator_set)].into(), min_block_delta, + beefy_genesis, ) .unwrap(); - let payload_provider = MmrRootProvider::new(api); - let worker_params = crate::worker::WorkerParams { + let payload_provider = MmrRootProvider::new(api.clone()); + BeefyWorker { backend, payload_provider, + runtime: api, key_store: Some(keystore).into(), links, gossip_engine, gossip_validator, - metrics: None, - network, + gossip_report_stream, + metrics, + sync: Arc::new(sync), on_demand_justifications, + pending_justifications: BTreeMap::new(), persisted_state, - }; - BeefyWorker::<_, _, _, _>::new(worker_params) + } } #[test] @@ -1200,10 +1269,30 @@ pub(crate) mod tests { #[test] fn should_vote_target() { - let mut oracle = VoterOracle:: { min_block_delta: 1, sessions: VecDeque::new() }; + let header = Header::new( + 1u32.into(), + Default::default(), + Default::default(), + Default::default(), + Digest::default(), + ); + let mut oracle = VoterOracle:: { + best_beefy_block: 0, + best_grandpa_block_header: header, + min_block_delta: 1, + sessions: VecDeque::new(), + }; + let voting_target_with = |oracle: &mut VoterOracle, + best_beefy: NumberFor, + best_grandpa: NumberFor| + -> Option> { + oracle.best_beefy_block = best_beefy; + oracle.best_grandpa_block_header.number = best_grandpa; + oracle.voting_target() + }; // rounds not initialized -> should vote: `None` - assert_eq!(oracle.voting_target(0, 1), None); + assert_eq!(voting_target_with(&mut oracle, 0, 1), None); let keys = &[Keyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); @@ -1212,29 +1301,29 @@ pub(crate) mod tests { // under min delta oracle.min_block_delta = 4; - assert_eq!(oracle.voting_target(1, 1), None); - assert_eq!(oracle.voting_target(2, 5), None); + assert_eq!(voting_target_with(&mut oracle, 1, 1), None); + assert_eq!(voting_target_with(&mut oracle, 2, 5), None); // vote on min delta - assert_eq!(oracle.voting_target(4, 9), Some(8)); + assert_eq!(voting_target_with(&mut oracle, 4, 9), Some(8)); oracle.min_block_delta = 8; - assert_eq!(oracle.voting_target(10, 18), Some(18)); + assert_eq!(voting_target_with(&mut oracle, 10, 18), Some(18)); // vote on power of two oracle.min_block_delta = 1; - assert_eq!(oracle.voting_target(1000, 1008), Some(1004)); - assert_eq!(oracle.voting_target(1000, 1016), Some(1008)); + assert_eq!(voting_target_with(&mut oracle, 1000, 1008), Some(1004)); + assert_eq!(voting_target_with(&mut oracle, 1000, 1016), Some(1008)); // nothing new to vote on - assert_eq!(oracle.voting_target(1000, 1000), None); + assert_eq!(voting_target_with(&mut oracle, 1000, 1000), None); // vote on mandatory oracle.sessions.clear(); oracle.add_session(Rounds::new(1000, validator_set.clone())); - assert_eq!(oracle.voting_target(0, 1008), Some(1000)); + assert_eq!(voting_target_with(&mut oracle, 0, 1008), Some(1000)); oracle.sessions.clear(); oracle.add_session(Rounds::new(1001, validator_set.clone())); - assert_eq!(oracle.voting_target(1000, 1008), Some(1001)); + assert_eq!(voting_target_with(&mut oracle, 1000, 1008), Some(1001)); } #[test] @@ -1242,16 +1331,34 @@ pub(crate) mod tests { let keys = &[Keyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); - let mut oracle = VoterOracle:: { min_block_delta: 1, sessions: VecDeque::new() }; + let header = Header::new( + 1u32.into(), + Default::default(), + Default::default(), + Default::default(), + Digest::default(), + ); + let mut oracle = VoterOracle:: { + best_beefy_block: 0, + best_grandpa_block_header: header, + min_block_delta: 1, + sessions: VecDeque::new(), + }; + let accepted_interval_with = |oracle: &mut VoterOracle, + best_grandpa: NumberFor| + -> Result<(NumberFor, NumberFor), Error> { + oracle.best_grandpa_block_header.number = best_grandpa; + oracle.accepted_interval() + }; // rounds not initialized -> should accept votes: `None` - assert!(oracle.accepted_interval(1).is_err()); + assert!(accepted_interval_with(&mut oracle, 1).is_err()); let session_one = 1; oracle.add_session(Rounds::new(session_one, validator_set.clone())); // mandatory not done, only accept mandatory for i in 0..15 { - assert_eq!(oracle.accepted_interval(i), Ok((session_one, session_one))); + assert_eq!(accepted_interval_with(&mut oracle, i), Ok((session_one, session_one))); } // add more sessions, nothing changes @@ -1261,7 +1368,7 @@ pub(crate) mod tests { oracle.add_session(Rounds::new(session_three, validator_set.clone())); // mandatory not done, should accept mandatory for session_one for i in session_three..session_three + 15 { - assert_eq!(oracle.accepted_interval(i), Ok((session_one, session_one))); + assert_eq!(accepted_interval_with(&mut oracle, i), Ok((session_one, session_one))); } // simulate finish mandatory for session one, prune oracle @@ -1269,7 +1376,7 @@ pub(crate) mod tests { oracle.try_prune(); // session_one pruned, should accept mandatory for session_two for i in session_three..session_three + 15 { - assert_eq!(oracle.accepted_interval(i), Ok((session_two, session_two))); + assert_eq!(accepted_interval_with(&mut oracle, i), Ok((session_two, session_two))); } // simulate finish mandatory for session two, prune oracle @@ -1277,26 +1384,29 @@ pub(crate) mod tests { oracle.try_prune(); // session_two pruned, should accept mandatory for session_three for i in session_three..session_three + 15 { - assert_eq!(oracle.accepted_interval(i), Ok((session_three, session_three))); + assert_eq!(accepted_interval_with(&mut oracle, i), Ok((session_three, session_three))); } // simulate finish mandatory for session three oracle.sessions.front_mut().unwrap().test_set_mandatory_done(true); // verify all other blocks in this session are now open to voting for i in session_three..session_three + 15 { - assert_eq!(oracle.accepted_interval(i), Ok((session_three, i))); + assert_eq!(accepted_interval_with(&mut oracle, i), Ok((session_three, i))); } // pruning does nothing in this case oracle.try_prune(); for i in session_three..session_three + 15 { - assert_eq!(oracle.accepted_interval(i), Ok((session_three, i))); + assert_eq!(accepted_interval_with(&mut oracle, i), Ok((session_three, i))); } // adding new session automatically prunes "finalized" previous session let session_four = 31; oracle.add_session(Rounds::new(session_four, validator_set.clone())); assert_eq!(oracle.sessions.front().unwrap().session_start(), session_four); - assert_eq!(oracle.accepted_interval(session_four + 10), Ok((session_four, session_four))); + assert_eq!( + accepted_interval_with(&mut oracle, session_four + 10), + Ok((session_four, session_four)) + ); } #[test] @@ -1330,7 +1440,7 @@ pub(crate) mod tests { let keys = &[Keyring::Alice]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); let mut net = BeefyTestNet::new(1); - let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1, validator_set.clone()); + let mut worker = create_beefy_worker(net.peer(0), &keys[0], 1, validator_set.clone()); // keystore doesn't contain other keys than validators' assert_eq!(worker.verify_validator_set(&1, &validator_set), Ok(())); @@ -1354,7 +1464,7 @@ pub(crate) mod tests { let validator_set = ValidatorSet::new(make_beefy_ids(&keys), 0).unwrap(); let mut net = BeefyTestNet::new(1); let backend = net.peer(0).client().as_backend(); - let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1, validator_set.clone()); + let mut worker = create_beefy_worker(net.peer(0), &keys[0], 1, validator_set.clone()); // remove default session, will manually add custom one. worker.persisted_state.voting_oracle.sessions.clear(); @@ -1374,7 +1484,7 @@ pub(crate) mod tests { }; // no 'best beefy block' or finality proofs - assert_eq!(worker.best_beefy_block(), 0); + assert_eq!(worker.persisted_state.best_beefy_block(), 0); poll_fn(move |cx| { assert_eq!(best_block_stream.poll_next_unpin(cx), Poll::Pending); assert_eq!(finality_proof.poll_next_unpin(cx), Poll::Pending); @@ -1382,6 +1492,7 @@ pub(crate) mod tests { }) .await; + let client = net.peer(0).client().as_client(); // unknown hash for block #1 let (mut best_block_streams, mut finality_proofs) = get_beefy_streams(&mut net, keys.clone()); @@ -1396,10 +1507,16 @@ pub(crate) mod tests { // try to finalize block #1 worker.finalize(justif.clone()).unwrap(); // verify block finalized - assert_eq!(worker.best_beefy_block(), 1); + assert_eq!(worker.persisted_state.best_beefy_block(), 1); poll_fn(move |cx| { - // unknown hash -> nothing streamed - assert_eq!(best_block_stream.poll_next_unpin(cx), Poll::Pending); + // expect Some(hash-of-block-1) + match best_block_stream.poll_next_unpin(cx) { + Poll::Ready(Some(hash)) => { + let block_num = client.number(hash).unwrap(); + assert_eq!(block_num, Some(1)); + }, + v => panic!("unexpected value: {:?}", v), + } // commitment streamed match finality_proof.poll_next_unpin(cx) { // expect justification @@ -1413,11 +1530,9 @@ pub(crate) mod tests { // generate 2 blocks, try again expect success let (mut best_block_streams, _) = get_beefy_streams(&mut net, keys); let mut best_block_stream = best_block_streams.drain(..).next().unwrap(); - let hashes = net.peer(0).push_blocks(2, false); + let hashes = net.peer(0).push_blocks(1, false); // finalize 1 and 2 without justifications (hashes does not contain genesis) - let hashof1 = hashes[0]; - let hashof2 = hashes[1]; - backend.finalize_block(hashof1, None).unwrap(); + let hashof2 = hashes[0]; backend.finalize_block(hashof2, None).unwrap(); let justif = create_finality_proof(2); @@ -1429,7 +1544,7 @@ pub(crate) mod tests { // new session starting at #2 is in front assert_eq!(worker.active_rounds().unwrap().session_start(), 2); // verify block finalized - assert_eq!(worker.best_beefy_block(), 2); + assert_eq!(worker.persisted_state.best_beefy_block(), 2); poll_fn(move |cx| { match best_block_stream.poll_next_unpin(cx) { // expect Some(hash-of-block-2) @@ -1453,7 +1568,7 @@ pub(crate) mod tests { let keys = &[Keyring::Alice, Keyring::Bob]; let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); let mut net = BeefyTestNet::new(1); - let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1, validator_set.clone()); + let mut worker = create_beefy_worker(net.peer(0), &keys[0], 1, validator_set.clone()); let worker_rounds = worker.active_rounds().unwrap(); assert_eq!(worker_rounds.session_start(), 1); @@ -1480,73 +1595,66 @@ pub(crate) mod tests { } #[tokio::test] - async fn should_triage_votes_and_process_later() { - let keys = &[Keyring::Alice, Keyring::Bob]; - let validator_set = ValidatorSet::new(make_beefy_ids(keys), 0).unwrap(); + async fn should_not_report_bad_old_or_self_equivocations() { + let block_num = 1; + let set_id = 1; + let keys = [Keyring::Alice]; + let validator_set = ValidatorSet::new(make_beefy_ids(&keys), set_id).unwrap(); + // Alice votes on good MMR roots, equivocations are allowed/expected + let mut api_alice = TestApi::with_validator_set(&validator_set); + api_alice.allow_equivocations(); + let api_alice = Arc::new(api_alice); + let mut net = BeefyTestNet::new(1); - let mut worker = create_beefy_worker(&net.peer(0), &keys[0], 1, validator_set.clone()); - // remove default session, will manually add custom one. - worker.persisted_state.voting_oracle.sessions.clear(); + let mut worker = create_beefy_worker(net.peer(0), &keys[0], 1, validator_set.clone()); + worker.runtime = api_alice.clone(); - fn new_vote( - block_number: NumberFor, - ) -> VoteMessage, AuthorityId, Signature> { - let commitment = Commitment { - payload: Payload::from_single_entry(*b"BF", vec![]), - block_number, - validator_set_id: 0, - }; - VoteMessage { - commitment, - id: Keyring::Alice.public(), - signature: Keyring::Alice.sign(b"I am committed"), - } - } + // let there be a block with num = 1: + let _ = net.peer(0).push_blocks(1, false); - // best grandpa is 20 - let best_grandpa_header = Header::new( - 20u32.into(), - Default::default(), - Default::default(), - Default::default(), - Digest::default(), - ); + let payload1 = Payload::from_single_entry(MMR_ROOT_ID, vec![42]); + let payload2 = Payload::from_single_entry(MMR_ROOT_ID, vec![128]); - worker - .persisted_state - .voting_oracle - .add_session(Rounds::new(10, validator_set.clone())); - worker.persisted_state.best_grandpa_block_header = best_grandpa_header; - - // triage votes for blocks 10..13 - worker.triage_incoming_vote(new_vote(10)).unwrap(); - worker.triage_incoming_vote(new_vote(11)).unwrap(); - worker.triage_incoming_vote(new_vote(12)).unwrap(); - // triage votes for blocks 20..23 - worker.triage_incoming_vote(new_vote(20)).unwrap(); - worker.triage_incoming_vote(new_vote(21)).unwrap(); - worker.triage_incoming_vote(new_vote(22)).unwrap(); - - // vote for 10 should have been handled, while the rest buffered for later processing - let mut votes = worker.pending_votes.values(); - assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 11); - assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 12); - assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 20); - assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 21); - assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 22); - assert!(votes.next().is_none()); - - // simulate mandatory done, and retry buffered votes - worker - .persisted_state - .voting_oracle - .active_rounds_mut() - .unwrap() - .test_set_mandatory_done(true); - worker.try_pending_justif_and_votes().unwrap(); - // all blocks <= grandpa finalized should have been handled, rest still buffered - let mut votes = worker.pending_votes.values(); - assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 21); - assert_eq!(votes.next().unwrap().first().unwrap().commitment.block_number, 22); + // generate an equivocation proof, with Bob as perpetrator + let good_proof = generate_equivocation_proof( + (block_num, payload1.clone(), set_id, &Keyring::Bob), + (block_num, payload2.clone(), set_id, &Keyring::Bob), + ); + { + // expect voter (Alice) to successfully report it + assert_eq!(worker.report_equivocation(good_proof.clone()), Ok(())); + // verify Alice reports Bob equivocation to runtime + let reported = api_alice.reported_equivocations.as_ref().unwrap().lock(); + assert_eq!(reported.len(), 1); + assert_eq!(*reported.get(0).unwrap(), good_proof); + } + api_alice.reported_equivocations.as_ref().unwrap().lock().clear(); + + // now let's try with a bad proof + let mut bad_proof = good_proof.clone(); + bad_proof.first.id = Keyring::Charlie.public(); + // bad proofs are simply ignored + assert_eq!(worker.report_equivocation(bad_proof), Ok(())); + // verify nothing reported to runtime + assert!(api_alice.reported_equivocations.as_ref().unwrap().lock().is_empty()); + + // now let's try with old set it + let mut old_proof = good_proof.clone(); + old_proof.first.commitment.validator_set_id = 0; + old_proof.second.commitment.validator_set_id = 0; + // old proofs are simply ignored + assert_eq!(worker.report_equivocation(old_proof), Ok(())); + // verify nothing reported to runtime + assert!(api_alice.reported_equivocations.as_ref().unwrap().lock().is_empty()); + + // now let's try reporting a self-equivocation + let self_proof = generate_equivocation_proof( + (block_num, payload1.clone(), set_id, &Keyring::Alice), + (block_num, payload2.clone(), set_id, &Keyring::Alice), + ); + // equivocations done by 'self' are simply ignored (not reported) + assert_eq!(worker.report_equivocation(self_proof), Ok(())); + // verify nothing reported to runtime + assert!(api_alice.reported_equivocations.as_ref().unwrap().lock().is_empty()); } } diff --git a/client/consensus/common/Cargo.toml b/client/consensus/common/Cargo.toml index 87b052d434e9..adb6f0920bee 100644 --- a/client/consensus/common/Cargo.toml +++ b/client/consensus/common/Cargo.toml @@ -16,9 +16,9 @@ targets = ["x86_64-unknown-linux-gnu"] async-trait = "0.1.57" futures = { version = "0.3.21", features = ["thread-pool"] } futures-timer = "3.0.1" -libp2p = "0.50.0" +libp2p-identity = { version = "0.1.2", features = ["peerid", "ed25519"] } log = "0.4.17" -mockall = "0.11.2" +mockall = "0.11.3" parking_lot = "0.12.1" serde = { version = "1.0", features = ["derive"] } thiserror = "1.0.30" @@ -28,9 +28,9 @@ sc-utils = { version = "4.0.0-dev", path = "../../utils" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } -sp-state-machine = { version = "0.13.0", path = "../../../primitives/state-machine" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } +sp-state-machine = { version = "0.28.0", path = "../../../primitives/state-machine" } [dev-dependencies] sp-test-primitives = { version = "2.0.0", path = "../../../primitives/test-primitives" } diff --git a/client/consensus/common/src/block_import.rs b/client/consensus/common/src/block_import.rs index f888176addd2..70bf0283af2d 100644 --- a/client/consensus/common/src/block_import.rs +++ b/client/consensus/common/src/block_import.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -25,7 +25,7 @@ use sp_runtime::{ }; use std::{any::Any, borrow::Cow, collections::HashMap, sync::Arc}; -use sp_consensus::{BlockOrigin, CacheKeyId, Error}; +use sp_consensus::{BlockOrigin, Error}; /// Block import result. #[derive(Debug, PartialEq, Eq)] @@ -348,12 +348,9 @@ pub trait BlockImport { ) -> Result; /// Import a block. - /// - /// Cached data can be accessed through the blockchain cache. async fn import_block( &mut self, block: BlockImportParams, - cache: HashMap>, ) -> Result; } @@ -374,14 +371,11 @@ where } /// Import a block. - /// - /// Cached data can be accessed through the blockchain cache. async fn import_block( &mut self, block: BlockImportParams, - cache: HashMap>, ) -> Result { - (**self).import_block(block, cache).await + (**self).import_block(block).await } } @@ -405,9 +399,8 @@ where async fn import_block( &mut self, block: BlockImportParams, - cache: HashMap>, ) -> Result { - (&**self).import_block(block, cache).await + (&**self).import_block(block).await } } diff --git a/client/consensus/common/src/import_queue.rs b/client/consensus/common/src/import_queue.rs index 02309cc6a365..11ebbd4036a2 100644 --- a/client/consensus/common/src/import_queue.rs +++ b/client/consensus/common/src/import_queue.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -27,9 +27,9 @@ //! instantiated. The `BasicQueue` and `BasicVerifier` traits allow serial //! queues to be instantiated simply. -use std::{collections::HashMap, iter::FromIterator}; - use log::{debug, trace}; + +use sp_consensus::{error::Error as ConsensusError, BlockOrigin}; use sp_runtime::{ traits::{Block as BlockT, Header as _, NumberFor}, Justifications, @@ -42,8 +42,8 @@ use crate::{ }, metrics::Metrics, }; + pub use basic_queue::BasicQueue; -use sp_consensus::{error::Error as ConsensusError, BlockOrigin, CacheKeyId}; const LOG_TARGET: &str = "sync::import-queue"; @@ -66,7 +66,7 @@ pub type BoxJustificationImport = Box + Send + Sync>; /// Maps to the RuntimeOrigin used by the network. -pub type RuntimeOrigin = libp2p::PeerId; +pub type RuntimeOrigin = libp2p_identity::PeerId; /// Block data used by the queue. #[derive(Debug, PartialEq, Eq, Clone)] @@ -96,13 +96,12 @@ pub struct IncomingBlock { /// Verify a justification of a block #[async_trait::async_trait] pub trait Verifier: Send + Sync { - /// Verify the given data and return the BlockImportParams and an optional - /// new set of validators to import. If not, err with an Error-Message - /// presented to the User in the logs. + /// Verify the given block data and return the `BlockImportParams` to + /// continue the block import process. async fn verify( &mut self, block: BlockImportParams, - ) -> Result<(BlockImportParams, Option)>>), String>; + ) -> Result, String>; } /// Blocks import queue API. @@ -328,7 +327,7 @@ pub(crate) async fn import_single_block_metered< import_block.state_action = StateAction::ExecuteIfPossible; } - let (import_block, maybe_keys) = verifier.verify(import_block).await.map_err(|msg| { + let import_block = verifier.verify(import_block).await.map_err(|msg| { if let Some(ref peer) = peer { trace!( target: LOG_TARGET, @@ -351,9 +350,8 @@ pub(crate) async fn import_single_block_metered< metrics.report_verification(true, started.elapsed()); } - let cache = HashMap::from_iter(maybe_keys.unwrap_or_default()); let import_block = import_block.clear_storage_changes_and_mutate(); - let imported = import_handle.import_block(import_block, cache).await; + let imported = import_handle.import_block(import_block).await; if let Some(metrics) = metrics.as_ref() { metrics.report_verification_and_import(started.elapsed()); } diff --git a/client/consensus/common/src/import_queue/basic_queue.rs b/client/consensus/common/src/import_queue/basic_queue.rs index a96b0a47e57c..b93913703d39 100644 --- a/client/consensus/common/src/import_queue/basic_queue.rs +++ b/client/consensus/common/src/import_queue/basic_queue.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -118,8 +118,8 @@ impl BasicQueueHandle { } pub fn close(&mut self) { - self.justification_sender.close_channel(); - self.block_import_sender.close_channel(); + self.justification_sender.close(); + self.block_import_sender.close(); } } @@ -504,19 +504,18 @@ mod tests { block_import::{ BlockCheckParams, BlockImport, BlockImportParams, ImportResult, JustificationImport, }, - import_queue::{CacheKeyId, Verifier}, + import_queue::Verifier, }; use futures::{executor::block_on, Future}; use sp_test_primitives::{Block, BlockNumber, Extrinsic, Hash, Header}; - use std::collections::HashMap; #[async_trait::async_trait] impl Verifier for () { async fn verify( &mut self, block: BlockImportParams, - ) -> Result<(BlockImportParams, Option)>>), String> { - Ok((BlockImportParams::new(block.origin, block.header), None)) + ) -> Result, String> { + Ok(BlockImportParams::new(block.origin, block.header)) } } @@ -535,7 +534,6 @@ mod tests { async fn import_block( &mut self, _block: BlockImportParams, - _cache: HashMap>, ) -> Result { Ok(ImportResult::imported(true)) } @@ -597,11 +595,11 @@ mod tests { fn prioritizes_finality_work_over_block_import() { let (result_sender, mut result_port) = buffered_link::buffered_link(100_000); - let (worker, mut finality_sender, mut block_import_sender) = + let (worker, finality_sender, block_import_sender) = BlockImportWorker::new(result_sender, (), Box::new(()), Some(Box::new(())), None); futures::pin_mut!(worker); - let mut import_block = |n| { + let import_block = |n| { let header = Header { parent_hash: Hash::random(), number: n, @@ -612,35 +610,37 @@ mod tests { let hash = header.hash(); - block_on(block_import_sender.send(worker_messages::ImportBlocks( - BlockOrigin::Own, - vec![IncomingBlock { - hash, - header: Some(header), - body: None, - indexed_body: None, - justifications: None, - origin: None, - allow_missing_state: false, - import_existing: false, - state: None, - skip_execution: false, - }], - ))) - .unwrap(); + block_import_sender + .unbounded_send(worker_messages::ImportBlocks( + BlockOrigin::Own, + vec![IncomingBlock { + hash, + header: Some(header), + body: None, + indexed_body: None, + justifications: None, + origin: None, + allow_missing_state: false, + import_existing: false, + state: None, + skip_execution: false, + }], + )) + .unwrap(); hash }; - let mut import_justification = || { + let import_justification = || { let hash = Hash::random(); - block_on(finality_sender.send(worker_messages::ImportJustification( - libp2p::PeerId::random(), - hash, - 1, - (*b"TEST", Vec::new()), - ))) - .unwrap(); + finality_sender + .unbounded_send(worker_messages::ImportJustification( + libp2p_identity::PeerId::random(), + hash, + 1, + (*b"TEST", Vec::new()), + )) + .unwrap(); hash }; diff --git a/client/consensus/common/src/import_queue/buffered_link.rs b/client/consensus/common/src/import_queue/buffered_link.rs index 71adcf2dc2ea..c23a4b0d5d0a 100644 --- a/client/consensus/common/src/import_queue/buffered_link.rs +++ b/client/consensus/common/src/import_queue/buffered_link.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -53,7 +53,7 @@ use super::BlockImportResult; /// can be used to buffer commands, and the receiver can be used to poll said commands and transfer /// them to another link. `queue_size_warning` sets the warning threshold of the channel queue size. pub fn buffered_link( - queue_size_warning: i64, + queue_size_warning: usize, ) -> (BufferedLinkSender, BufferedLinkReceiver) { let (tx, rx) = tracing_unbounded("mpsc_buffered_link", queue_size_warning); let tx = BufferedLinkSender { tx }; @@ -166,7 +166,7 @@ impl BufferedLinkReceiver { } /// Close the channel. - pub fn close(&mut self) { + pub fn close(&mut self) -> bool { self.rx.get_mut().close() } } diff --git a/client/consensus/common/src/import_queue/mock.rs b/client/consensus/common/src/import_queue/mock.rs index 67deee9514a1..64ac532ded85 100644 --- a/client/consensus/common/src/import_queue/mock.rs +++ b/client/consensus/common/src/import_queue/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/consensus/common/src/lib.rs b/client/consensus/common/src/lib.rs index f291cc270481..6bf1ed0b48b4 100644 --- a/client/consensus/common/src/lib.rs +++ b/client/consensus/common/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/consensus/common/src/longest_chain.rs b/client/consensus/common/src/longest_chain.rs index fab2c3a4c06f..f27cde4982de 100644 --- a/client/consensus/common/src/longest_chain.rs +++ b/client/consensus/common/src/longest_chain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -21,7 +21,7 @@ use sc_client_api::backend; use sp_blockchain::{Backend, HeaderBackend}; use sp_consensus::{Error as ConsensusError, SelectChain}; -use sp_runtime::traits::{Block as BlockT, NumberFor}; +use sp_runtime::traits::{Block as BlockT, Header, NumberFor}; use std::{marker::PhantomData, sync::Arc}; /// Implement Longest Chain Select implementation @@ -48,15 +48,19 @@ where LongestChain { backend, _phantom: Default::default() } } - fn best_block_header(&self) -> sp_blockchain::Result<::Header> { + fn best_hash(&self) -> sp_blockchain::Result<::Hash> { let info = self.backend.blockchain().info(); let import_lock = self.backend.get_import_lock(); let best_hash = self .backend .blockchain() - .best_containing(info.best_hash, None, import_lock)? + .longest_containing(info.best_hash, import_lock)? .unwrap_or(info.best_hash); + Ok(best_hash) + } + fn best_header(&self) -> sp_blockchain::Result<::Header> { + let best_hash = self.best_hash()?; Ok(self .backend .blockchain() @@ -64,6 +68,65 @@ where .expect("given block hash was fetched from block in db; qed")) } + /// Returns the highest descendant of the given block that is a valid + /// candidate to be finalized. + /// + /// In this context, being a valid target means being an ancestor of + /// the best chain according to the `best_header` method. + /// + /// If `maybe_max_number` is `Some(max_block_number)` the search is + /// limited to block `number <= max_block_number`. In other words + /// as if there were no blocks greater than `max_block_number`. + fn finality_target( + &self, + base_hash: Block::Hash, + maybe_max_number: Option>, + ) -> sp_blockchain::Result { + use sp_blockchain::Error::{Application, MissingHeader}; + let blockchain = self.backend.blockchain(); + + let mut current_head = self.best_header()?; + let mut best_hash = current_head.hash(); + + let base_header = blockchain + .header(base_hash)? + .ok_or_else(|| MissingHeader(base_hash.to_string()))?; + let base_number = *base_header.number(); + + if let Some(max_number) = maybe_max_number { + if max_number < base_number { + let msg = format!( + "Requested a finality target using max number {} below the base number {}", + max_number, base_number + ); + return Err(Application(msg.into())) + } + + while current_head.number() > &max_number { + best_hash = *current_head.parent_hash(); + current_head = blockchain + .header(best_hash)? + .ok_or_else(|| MissingHeader(format!("{best_hash:?}")))?; + } + } + + while current_head.hash() != base_hash { + if *current_head.number() < base_number { + let msg = format!( + "Requested a finality target using a base {:?} not in the best chain {:?}", + base_hash, best_hash, + ); + return Err(Application(msg.into())) + } + let current_hash = *current_head.parent_hash(); + current_head = blockchain + .header(current_hash)? + .ok_or_else(|| MissingHeader(format!("{best_hash:?}")))?; + } + + Ok(best_hash) + } + fn leaves(&self) -> Result::Hash>, sp_blockchain::Error> { self.backend.blockchain().leaves() } @@ -80,20 +143,15 @@ where } async fn best_chain(&self) -> Result<::Header, ConsensusError> { - LongestChain::best_block_header(self) - .map_err(|e| ConsensusError::ChainLookup(e.to_string())) + LongestChain::best_header(self).map_err(|e| ConsensusError::ChainLookup(e.to_string())) } async fn finality_target( &self, - target_hash: Block::Hash, + base_hash: Block::Hash, maybe_max_number: Option>, ) -> Result { - let import_lock = self.backend.get_import_lock(); - self.backend - .blockchain() - .best_containing(target_hash, maybe_max_number, import_lock) - .map(|maybe_hash| maybe_hash.unwrap_or(target_hash)) + LongestChain::finality_target(self, base_hash, maybe_max_number) .map_err(|e| ConsensusError::ChainLookup(e.to_string())) } } diff --git a/client/consensus/common/src/metrics.rs b/client/consensus/common/src/metrics.rs index 8314049238b8..e807189d4616 100644 --- a/client/consensus/common/src/metrics.rs +++ b/client/consensus/common/src/metrics.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/consensus/common/src/shared_data.rs b/client/consensus/common/src/shared_data.rs index fb04966b5db6..efd41d098545 100644 --- a/client/consensus/common/src/shared_data.rs +++ b/client/consensus/common/src/shared_data.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/consensus/epochs/Cargo.toml b/client/consensus/epochs/Cargo.toml index 89588cc7d4c5..6ee4597541d2 100644 --- a/client/consensus/epochs/Cargo.toml +++ b/client/consensus/epochs/Cargo.toml @@ -13,9 +13,9 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-consensus = { version = "0.10.0-dev", path = "../common" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } diff --git a/client/consensus/epochs/src/lib.rs b/client/consensus/epochs/src/lib.rs index 7784a9943762..29bb18e147c2 100644 --- a/client/consensus/epochs/src/lib.rs +++ b/client/consensus/epochs/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -1039,7 +1039,7 @@ mod tests { let mut nodes: Vec<_> = epoch_changes.tree().iter().map(|(h, _, _)| h).collect(); nodes.sort(); - assert_eq!(nodes, vec![b"A", b"B", b"C", b"E", b"F", b"G"]); + assert_eq!(nodes, vec![b"A", b"B", b"C", b"F", b"G"]); // Finalize block y @ number 35, slot 330 // This should prune all nodes imported by blocks with a number < 35 that are not @@ -1050,7 +1050,7 @@ mod tests { let mut nodes: Vec<_> = epoch_changes.tree().iter().map(|(h, _, _)| h).collect(); nodes.sort(); - assert_eq!(nodes, vec![b"B", b"C", b"F", b"G"]); + assert_eq!(nodes, vec![b"B", b"C", b"G"]); } #[test] diff --git a/client/consensus/epochs/src/migration.rs b/client/consensus/epochs/src/migration.rs index 052d0b7be4a5..8838dbb4605b 100644 --- a/client/consensus/epochs/src/migration.rs +++ b/client/consensus/epochs/src/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/consensus/grandpa/Cargo.toml b/client/consensus/grandpa/Cargo.toml new file mode 100644 index 000000000000..af39c640122b --- /dev/null +++ b/client/consensus/grandpa/Cargo.toml @@ -0,0 +1,61 @@ +[package] +name = "sc-consensus-grandpa" +version = "0.10.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Integration of the GRANDPA finality gadget into substrate." +documentation = "https://docs.rs/sc-consensus-grandpa" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +ahash = "0.8.2" +array-bytes = "6.1" +async-trait = "0.1.57" +dyn-clone = "1.0" +finality-grandpa = { version = "0.16.2", features = ["derive-codec"] } +futures = "0.3.21" +futures-timer = "3.0.1" +log = "0.4.17" +parity-scale-codec = { version = "3.6.1", features = ["derive"] } +parking_lot = "0.12.1" +rand = "0.8.5" +serde_json = "1.0.85" +thiserror = "1.0" +fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } +sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } +sc-chain-spec = { version = "4.0.0-dev", path = "../../../client/chain-spec" } +sc-client-api = { version = "4.0.0-dev", path = "../../api" } +sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../transaction-pool/api" } +sc-consensus = { version = "0.10.0-dev", path = "../common" } +sc-network = { version = "0.10.0-dev", path = "../../network" } +sc-network-gossip = { version = "0.10.0-dev", path = "../../network-gossip" } +sc-network-common = { version = "0.10.0-dev", path = "../../network/common" } +sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } +sc-utils = { version = "4.0.0-dev", path = "../../utils" } +sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } +sp-application-crypto = { version = "23.0.0", path = "../../../primitives/application-crypto" } +sp-arithmetic = { version = "16.0.0", path = "../../../primitives/arithmetic" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-consensus-grandpa = { version = "4.0.0-dev", path = "../../../primitives/consensus/grandpa" } +sp-keystore = { version = "0.27.0", path = "../../../primitives/keystore" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } + +[dev-dependencies] +assert_matches = "1.3.0" +finality-grandpa = { version = "0.16.2", features = ["derive-codec", "test-helpers"] } +serde = "1.0.163" +tokio = "1.22.0" +sc-network = { version = "0.10.0-dev", path = "../../network" } +sc-network-test = { version = "0.8.0", path = "../../network/test" } +sp-keyring = { version = "24.0.0", path = "../../../primitives/keyring" } +sp-tracing = { version = "10.0.0", path = "../../../primitives/tracing" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/client/finality-grandpa/README.md b/client/consensus/grandpa/README.md similarity index 100% rename from client/finality-grandpa/README.md rename to client/consensus/grandpa/README.md diff --git a/client/consensus/grandpa/rpc/Cargo.toml b/client/consensus/grandpa/rpc/Cargo.toml new file mode 100644 index 000000000000..5c3b5a171b92 --- /dev/null +++ b/client/consensus/grandpa/rpc/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "sc-consensus-grandpa-rpc" +version = "0.10.0-dev" +authors = ["Parity Technologies "] +description = "RPC extensions for the GRANDPA finality gadget" +repository = "https://github.com/paritytech/substrate/" +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +readme = "README.md" +homepage = "https://substrate.io" + +[dependencies] +finality-grandpa = { version = "0.16.2", features = ["derive-codec"] } +futures = "0.3.16" +jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } +log = "0.4.8" +parity-scale-codec = { version = "3.6.1", features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"] } +thiserror = "1.0" +sc-client-api = { version = "4.0.0-dev", path = "../../../api" } +sc-consensus-grandpa = { version = "0.10.0-dev", path = "../" } +sc-rpc = { version = "4.0.0-dev", path = "../../../rpc" } +sp-blockchain = { version = "4.0.0-dev", path = "../../../../primitives/blockchain" } +sp-core = { version = "21.0.0", path = "../../../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../../../primitives/runtime" } + +[dev-dependencies] +sc-block-builder = { version = "0.10.0-dev", path = "../../../block-builder" } +sc-rpc = { version = "4.0.0-dev", features = ["test-helpers"], path = "../../../rpc" } +sp-core = { version = "21.0.0", path = "../../../../primitives/core" } +sp-consensus-grandpa = { version = "4.0.0-dev", path = "../../../../primitives/consensus/grandpa" } +sp-keyring = { version = "24.0.0", path = "../../../../primitives/keyring" } +substrate-test-runtime-client = { version = "2.0.0", path = "../../../../test-utils/runtime/client" } +tokio = { version = "1.22.0", features = ["macros"] } diff --git a/client/finality-grandpa/rpc/README.md b/client/consensus/grandpa/rpc/README.md similarity index 100% rename from client/finality-grandpa/rpc/README.md rename to client/consensus/grandpa/rpc/README.md diff --git a/client/finality-grandpa/rpc/src/error.rs b/client/consensus/grandpa/rpc/src/error.rs similarity index 95% rename from client/finality-grandpa/rpc/src/error.rs rename to client/consensus/grandpa/rpc/src/error.rs index 197c0b8a7210..4884380cd22d 100644 --- a/client/finality-grandpa/rpc/src/error.rs +++ b/client/consensus/grandpa/rpc/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -35,7 +35,7 @@ pub enum Error { VoterStateReportsUnreasonablyLargeNumbers, /// GRANDPA prove finality failed. #[error("GRANDPA prove finality rpc failed: {0}")] - ProveFinalityFailed(#[from] sc_finality_grandpa::FinalityProofError), + ProveFinalityFailed(#[from] sc_consensus_grandpa::FinalityProofError), } /// The error codes returned by jsonrpc. diff --git a/client/finality-grandpa/rpc/src/finality.rs b/client/consensus/grandpa/rpc/src/finality.rs similarity index 85% rename from client/finality-grandpa/rpc/src/finality.rs rename to client/consensus/grandpa/rpc/src/finality.rs index f2be6d674b2f..f8ec01781ac6 100644 --- a/client/finality-grandpa/rpc/src/finality.rs +++ b/client/consensus/grandpa/rpc/src/finality.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,7 +18,7 @@ use serde::{Deserialize, Serialize}; -use sc_finality_grandpa::FinalityProofProvider; +use sc_consensus_grandpa::FinalityProofProvider; use sp_runtime::traits::{Block as BlockT, NumberFor}; #[derive(Serialize, Deserialize)] @@ -31,7 +31,7 @@ pub trait RpcFinalityProofProvider { fn rpc_prove_finality( &self, block: NumberFor, - ) -> Result, sc_finality_grandpa::FinalityProofError>; + ) -> Result, sc_consensus_grandpa::FinalityProofError>; } impl RpcFinalityProofProvider for FinalityProofProvider @@ -43,7 +43,7 @@ where fn rpc_prove_finality( &self, block: NumberFor, - ) -> Result, sc_finality_grandpa::FinalityProofError> { + ) -> Result, sc_consensus_grandpa::FinalityProofError> { self.prove_finality(block).map(|x| x.map(|y| EncodedFinalityProof(y.into()))) } } diff --git a/client/finality-grandpa/rpc/src/lib.rs b/client/consensus/grandpa/rpc/src/lib.rs similarity index 96% rename from client/finality-grandpa/rpc/src/lib.rs rename to client/consensus/grandpa/rpc/src/lib.rs index 70ff7ed17686..c6298bff969b 100644 --- a/client/finality-grandpa/rpc/src/lib.rs +++ b/client/consensus/grandpa/rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -35,7 +35,7 @@ mod finality; mod notification; mod report; -use sc_finality_grandpa::GrandpaJustificationStream; +use sc_consensus_grandpa::GrandpaJustificationStream; use sc_rpc::SubscriptionTaskExecutor; use sp_runtime::traits::{Block as BlockT, NumberFor}; @@ -105,7 +105,7 @@ where fn subscribe_justifications(&self, mut sink: SubscriptionSink) -> SubscriptionResult { let stream = self.justification_stream.subscribe(100_000).map( - |x: sc_finality_grandpa::GrandpaJustification| { + |x: sc_consensus_grandpa::GrandpaJustification| { JustificationNotification::from(x) }, ); @@ -143,7 +143,7 @@ mod tests { }; use parity_scale_codec::{Decode, Encode}; use sc_block_builder::{BlockBuilder, RecordProof}; - use sc_finality_grandpa::{ + use sc_consensus_grandpa::{ report, AuthorityId, FinalityProof, GrandpaJustification, GrandpaJustificationSender, }; use sp_blockchain::HeaderBackend; @@ -200,7 +200,7 @@ mod tests { fn rpc_prove_finality( &self, _block: NumberFor, - ) -> Result, sc_finality_grandpa::FinalityProofError> { + ) -> Result, sc_consensus_grandpa::FinalityProofError> { Ok(Some(EncodedFinalityProof( self.finality_proof .as_ref() @@ -216,7 +216,7 @@ mod tests { let voter_id_1 = AuthorityId::from_slice(&[1; 32]).unwrap(); let voters_best: HashSet<_> = vec![voter_id_1].into_iter().collect(); - let best_round_state = sc_finality_grandpa::report::RoundState { + let best_round_state = sc_consensus_grandpa::report::RoundState { total_weight: 100_u64.try_into().unwrap(), threshold_weight: 67_u64.try_into().unwrap(), prevote_current_weight: 50.into(), @@ -225,7 +225,7 @@ mod tests { precommit_ids: HashSet::new(), }; - let past_round_state = sc_finality_grandpa::report::RoundState { + let past_round_state = sc_consensus_grandpa::report::RoundState { total_weight: 100_u64.try_into().unwrap(), threshold_weight: 67_u64.try_into().unwrap(), prevote_current_weight: 100.into(), @@ -364,7 +364,7 @@ mod tests { }; let msg = finality_grandpa::Message::Precommit(precommit.clone()); - let encoded = sp_finality_grandpa::localized_payload(round, set_id, &msg); + let encoded = sp_consensus_grandpa::localized_payload(round, set_id, &msg); let signature = peers[0].sign(&encoded[..]).into(); let precommit = finality_grandpa::SignedPrecommit { diff --git a/client/finality-grandpa/rpc/src/notification.rs b/client/consensus/grandpa/rpc/src/notification.rs similarity index 92% rename from client/finality-grandpa/rpc/src/notification.rs rename to client/consensus/grandpa/rpc/src/notification.rs index 6fb138357935..42b9123ed8c1 100644 --- a/client/finality-grandpa/rpc/src/notification.rs +++ b/client/consensus/grandpa/rpc/src/notification.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -17,7 +17,7 @@ // along with this program. If not, see . use parity_scale_codec::Encode; -use sc_finality_grandpa::GrandpaJustification; +use sc_consensus_grandpa::GrandpaJustification; use serde::{Deserialize, Serialize}; use sp_runtime::traits::Block as BlockT; diff --git a/client/finality-grandpa/rpc/src/report.rs b/client/consensus/grandpa/rpc/src/report.rs similarity index 96% rename from client/finality-grandpa/rpc/src/report.rs rename to client/consensus/grandpa/rpc/src/report.rs index 8c04ca28ef87..ae4fd76d2857 100644 --- a/client/finality-grandpa/rpc/src/report.rs +++ b/client/consensus/grandpa/rpc/src/report.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -24,7 +24,7 @@ use std::{ use serde::{Deserialize, Serialize}; -use sc_finality_grandpa::{report, AuthorityId, SharedAuthoritySet, SharedVoterState}; +use sc_consensus_grandpa::{report, AuthorityId, SharedAuthoritySet, SharedVoterState}; use crate::error::Error; diff --git a/client/finality-grandpa/src/authorities.rs b/client/consensus/grandpa/src/authorities.rs similarity index 99% rename from client/finality-grandpa/src/authorities.rs rename to client/consensus/grandpa/src/authorities.rs index a61c66979bb5..623223e41eb8 100644 --- a/client/finality-grandpa/src/authorities.rs +++ b/client/consensus/grandpa/src/authorities.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -27,7 +27,7 @@ use parity_scale_codec::{Decode, Encode}; use parking_lot::MappedMutexGuard; use sc_consensus::shared_data::{SharedData, SharedDataLocked}; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_INFO}; -use sp_finality_grandpa::{AuthorityId, AuthorityList}; +use sp_consensus_grandpa::{AuthorityId, AuthorityList}; use crate::{SetId, LOG_TARGET}; diff --git a/client/finality-grandpa/src/aux_schema.rs b/client/consensus/grandpa/src/aux_schema.rs similarity index 99% rename from client/finality-grandpa/src/aux_schema.rs rename to client/consensus/grandpa/src/aux_schema.rs index a7357a7fa5e4..97a8bc660317 100644 --- a/client/finality-grandpa/src/aux_schema.rs +++ b/client/consensus/grandpa/src/aux_schema.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -27,7 +27,7 @@ use parity_scale_codec::{Decode, Encode}; use fork_tree::ForkTree; use sc_client_api::backend::AuxStore; use sp_blockchain::{Error as ClientError, Result as ClientResult}; -use sp_finality_grandpa::{AuthorityList, RoundNumber, SetId}; +use sp_consensus_grandpa::{AuthorityList, RoundNumber, SetId}; use sp_runtime::traits::{Block as BlockT, NumberFor}; use crate::{ @@ -501,8 +501,8 @@ pub(crate) fn load_authorities( #[cfg(test)] mod test { use super::*; + use sp_consensus_grandpa::AuthorityId; use sp_core::{crypto::UncheckedFrom, H256}; - use sp_finality_grandpa::AuthorityId; use substrate_test_runtime_client::{self, runtime::Block}; fn dummy_id() -> AuthorityId { diff --git a/client/finality-grandpa/src/communication/gossip.rs b/client/consensus/grandpa/src/communication/gossip.rs similarity index 95% rename from client/finality-grandpa/src/communication/gossip.rs rename to client/consensus/grandpa/src/communication/gossip.rs index cbcafc727d43..c4c885d48e9b 100644 --- a/client/finality-grandpa/src/communication/gossip.rs +++ b/client/consensus/grandpa/src/communication/gossip.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -91,11 +91,11 @@ use parity_scale_codec::{Decode, Encode}; use prometheus_endpoint::{register, CounterVec, Opts, PrometheusError, Registry, U64}; use rand::seq::SliceRandom; use sc_network::{PeerId, ReputationChange}; -use sc_network_common::protocol::role::ObservedRole; +use sc_network_common::role::ObservedRole; use sc_network_gossip::{MessageIntent, ValidatorContext}; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG}; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; -use sp_finality_grandpa::AuthorityId; +use sp_consensus_grandpa::AuthorityId; use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; use super::{benefit, cost, Round, SetId, NEIGHBOR_REBROADCAST_PERIOD}; @@ -791,68 +791,68 @@ impl Inner { /// Note a round in the current set has started. Does nothing if the last /// call to the function was with the same `round`. fn note_round(&mut self, round: Round) -> MaybeMessage { - { - let local_view = match self.local_view { - None => return None, - Some(ref mut v) => - if v.round == round { - // Do not send neighbor packets out if `round` has not changed --- - // such behavior is punishable. - return None - } else { - v - }, - }; + let local_view = self.local_view.as_mut()?; + if local_view.round == round { + // Do not send neighbor packets out if `round` has not changed --- + // such behavior is punishable. + return None + } - let set_id = local_view.set_id; + let set_id = local_view.set_id; - debug!( - target: LOG_TARGET, - "Voter {} noting beginning of round {:?} to network.", - self.config.name(), - (round, set_id) - ); + debug!( + target: LOG_TARGET, + "Voter {} noting beginning of round {:?} to network.", + self.config.name(), + (round, set_id) + ); - local_view.update_round(round); + local_view.update_round(round); - self.live_topics.push(round, set_id); - self.peers.reshuffle(); - } - self.multicast_neighbor_packet() + self.live_topics.push(round, set_id); + self.peers.reshuffle(); + + self.multicast_neighbor_packet(false) } /// Note that a voter set with given ID has started. Does nothing if the last /// call to the function was with the same `set_id`. fn note_set(&mut self, set_id: SetId, authorities: Vec) -> MaybeMessage { - { - let local_view = match self.local_view { - ref mut x @ None => x.get_or_insert(LocalView::new(set_id, Round(1))), - Some(ref mut v) => - if v.set_id == set_id { - let diff_authorities = self.authorities.iter().collect::>() != - authorities.iter().collect::>(); - - if diff_authorities { - debug!(target: LOG_TARGET, - "Gossip validator noted set {:?} twice with different authorities. \ - Was the authority set hard forked?", - set_id, - ); - self.authorities = authorities; - } - // Do not send neighbor packets out if the `set_id` has not changed --- - // such behavior is punishable. - return None - } else { - v - }, - }; + let local_view = match self.local_view { + ref mut x @ None => x.get_or_insert(LocalView::new(set_id, Round(1))), + Some(ref mut v) => { + if v.set_id == set_id { + let diff_authorities = self.authorities.iter().collect::>() != + authorities.iter().collect::>(); + + if diff_authorities { + debug!( + target: LOG_TARGET, + "Gossip validator noted set {:?} twice with different authorities. \ + Was the authority set hard forked?", + set_id, + ); + + self.authorities = authorities; + } - local_view.update_set(set_id); - self.live_topics.push(Round(1), set_id); - self.authorities = authorities; - } - self.multicast_neighbor_packet() + // Do not send neighbor packets out if the `set_id` has not changed --- + // such behavior is punishable. + return None + } else { + v + } + }, + }; + + local_view.update_set(set_id); + self.live_topics.push(Round(1), set_id); + self.authorities = authorities; + + // when transitioning to a new set we also want to send neighbor packets to light clients, + // this is so that they know who to ask justifications from in order to finalize the last + // block in the previous set. + self.multicast_neighbor_packet(true) } /// Note that we've imported a commit finalizing a given block. Does nothing if the last @@ -864,19 +864,14 @@ impl Inner { set_id: SetId, finalized: NumberFor, ) -> MaybeMessage { - { - match self.local_view { - None => return None, - Some(ref mut v) => - if v.last_commit_height() < Some(&finalized) { - v.last_commit = Some((finalized, round, set_id)); - } else { - return None - }, - }; + let local_view = self.local_view.as_mut()?; + if local_view.last_commit_height() < Some(&finalized) { + local_view.last_commit = Some((finalized, round, set_id)); + } else { + return None } - self.multicast_neighbor_packet() + self.multicast_neighbor_packet(false) } fn consider_vote(&self, round: Round, set_id: SetId) -> Consider { @@ -931,7 +926,7 @@ impl Inner { return Action::Discard(cost::UNKNOWN_VOTER) } - if !sp_finality_grandpa::check_message_signature( + if !sp_consensus_grandpa::check_message_signature( &full.message.message, &full.message.id, &full.message.signature, @@ -1193,7 +1188,7 @@ impl Inner { (neighbor_topics, action, catch_up, report) } - fn multicast_neighbor_packet(&self) -> MaybeMessage { + fn multicast_neighbor_packet(&self, force_light: bool) -> MaybeMessage { self.local_view.as_ref().map(|local_view| { let packet = NeighborPacket { round: local_view.round, @@ -1207,8 +1202,9 @@ impl Inner { .iter() .filter_map(|(id, info)| { // light clients don't participate in the full GRANDPA voter protocol - // and therefore don't need to be informed about view updates - if info.roles.is_light() { + // and therefore don't need to be informed about all view updates unless + // we explicitly require it (e.g. when transitioning to a new set) + if info.roles.is_light() && !force_light { None } else { Some(id) @@ -1704,7 +1700,7 @@ mod tests { fn config() -> crate::Config { crate::Config { gossip_duration: Duration::from_millis(10), - justification_period: 256, + justification_generation_period: 256, keystore: None, name: None, local_role: Role::Authority, @@ -2612,4 +2608,52 @@ mod tests { assert_eq!(val.inner().read().authorities, a2); } + + #[test] + fn sends_neighbor_packets_to_non_light_peers_when_starting_a_new_round() { + let (val, _) = GossipValidator::::new(config(), voter_set_state(), None, None); + + // initialize the validator to a stable set id + val.note_set(SetId(1), Vec::new(), |_, _| {}); + + let authority_peer = PeerId::random(); + let full_peer = PeerId::random(); + let light_peer = PeerId::random(); + + val.inner.write().peers.new_peer(authority_peer, ObservedRole::Authority); + val.inner.write().peers.new_peer(full_peer, ObservedRole::Full); + val.inner.write().peers.new_peer(light_peer, ObservedRole::Light); + + val.note_round(Round(2), |peers, message| { + assert_eq!(peers.len(), 2); + assert!(peers.contains(&authority_peer)); + assert!(peers.contains(&full_peer)); + assert!(!peers.contains(&light_peer)); + assert!(matches!(message, NeighborPacket { set_id: SetId(1), round: Round(2), .. })); + }); + } + + #[test] + fn sends_neighbor_packets_to_all_peers_when_starting_a_new_set() { + let (val, _) = GossipValidator::::new(config(), voter_set_state(), None, None); + + // initialize the validator to a stable set id + val.note_set(SetId(1), Vec::new(), |_, _| {}); + + let authority_peer = PeerId::random(); + let full_peer = PeerId::random(); + let light_peer = PeerId::random(); + + val.inner.write().peers.new_peer(authority_peer, ObservedRole::Authority); + val.inner.write().peers.new_peer(full_peer, ObservedRole::Full); + val.inner.write().peers.new_peer(light_peer, ObservedRole::Light); + + val.note_set(SetId(2), Vec::new(), |peers, message| { + assert_eq!(peers.len(), 3); + assert!(peers.contains(&authority_peer)); + assert!(peers.contains(&full_peer)); + assert!(peers.contains(&light_peer)); + assert!(matches!(message, NeighborPacket { set_id: SetId(2), round: Round(1), .. })); + }); + } } diff --git a/client/finality-grandpa/src/communication/mod.rs b/client/consensus/grandpa/src/communication/mod.rs similarity index 94% rename from client/finality-grandpa/src/communication/mod.rs rename to client/consensus/grandpa/src/communication/mod.rs index e7e3c12989c9..9d90035d71cb 100644 --- a/client/finality-grandpa/src/communication/mod.rs +++ b/client/consensus/grandpa/src/communication/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -46,10 +46,10 @@ use finality_grandpa::{ Message::{Precommit, Prevote, PrimaryPropose}, }; use parity_scale_codec::{Decode, Encode}; -use sc_network::ReputationChange; +use sc_network::{NetworkBlock, NetworkSyncForkRequest, ReputationChange}; use sc_network_gossip::{GossipEngine, Network as GossipNetwork}; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO}; -use sp_keystore::SyncCryptoStorePtr; +use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT, NumberFor}; use crate::{ @@ -59,9 +59,9 @@ use crate::{ use gossip::{ FullCatchUpMessage, FullCommitMessage, GossipMessage, GossipValidator, PeerReport, VoteMessage, }; -use sc_network_common::service::{NetworkBlock, NetworkSyncForkRequest}; +use sc_network_common::sync::SyncEventStream; use sc_utils::mpsc::TracingUnboundedReceiver; -use sp_finality_grandpa::{AuthorityId, AuthoritySignature, RoundNumber, SetId as SetIdNumber}; +use sp_consensus_grandpa::{AuthorityId, AuthoritySignature, RoundNumber, SetId as SetIdNumber}; pub mod gossip; mod periodic; @@ -74,7 +74,7 @@ pub(crate) const NEIGHBOR_REBROADCAST_PERIOD: Duration = Duration::from_secs(2 * pub mod grandpa_protocol_name { use sc_chain_spec::ChainSpec; - use sc_network_common::protocol::ProtocolName; + use sc_network::types::ProtocolName; pub(crate) const NAME: &str = "/grandpa/1"; /// Old names for the notifications protocol, used for backward compatibility. @@ -136,7 +136,7 @@ mod benefit { /// A type that ties together our local authority id and a keystore where it is /// available for signing. -pub struct LocalIdKeystore((AuthorityId, SyncCryptoStorePtr)); +pub struct LocalIdKeystore((AuthorityId, KeystorePtr)); impl LocalIdKeystore { /// Returns a reference to our local authority id. @@ -145,13 +145,13 @@ impl LocalIdKeystore { } /// Returns a reference to the keystore. - fn keystore(&self) -> SyncCryptoStorePtr { + fn keystore(&self) -> KeystorePtr { (self.0).1.clone() } } -impl From<(AuthorityId, SyncCryptoStorePtr)> for LocalIdKeystore { - fn from(inner: (AuthorityId, SyncCryptoStorePtr)) -> LocalIdKeystore { +impl From<(AuthorityId, KeystorePtr)> for LocalIdKeystore { + fn from(inner: (AuthorityId, KeystorePtr)) -> LocalIdKeystore { LocalIdKeystore(inner) } } @@ -163,24 +163,35 @@ const TELEMETRY_VOTERS_LIMIT: usize = 10; /// A handle to the network. /// -/// Something that provides both the capabilities needed for the `gossip_network::Network` trait as -/// well as the ability to set a fork sync request for a particular block. -pub trait Network: +/// Something that provides the capabilities needed for the `gossip_network::Network` trait. +pub trait Network: GossipNetwork + Clone + Send + 'static {} + +impl Network for T +where + Block: BlockT, + T: GossipNetwork + Clone + Send + 'static, +{ +} + +/// A handle to syncing-related services. +/// +/// Something that provides the ability to set a fork sync request for a particular block. +pub trait Syncing: NetworkSyncForkRequest> + NetworkBlock> - + GossipNetwork + + SyncEventStream + Clone + Send + 'static { } -impl Network for T +impl Syncing for T where Block: BlockT, T: NetworkSyncForkRequest> + NetworkBlock> - + GossipNetwork + + SyncEventStream + Clone + Send + 'static, @@ -198,8 +209,9 @@ pub(crate) fn global_topic(set_id: SetIdNumber) -> B::Hash { } /// Bridge between the underlying network service, gossiping consensus messages and Grandpa -pub(crate) struct NetworkBridge> { +pub(crate) struct NetworkBridge, S: Syncing> { service: N, + sync: S, gossip_engine: Arc>>, validator: Arc>, @@ -225,15 +237,16 @@ pub(crate) struct NetworkBridge> { telemetry: Option, } -impl> Unpin for NetworkBridge {} +impl, S: Syncing> Unpin for NetworkBridge {} -impl> NetworkBridge { +impl, S: Syncing> NetworkBridge { /// Create a new NetworkBridge to the given NetworkService. Returns the service /// handle. /// On creation it will register previous rounds' votes with the gossip /// service taken from the VoterSetState. pub(crate) fn new( service: N, + sync: S, config: crate::Config, set_state: crate::environment::SharedVoterSetState, prometheus_registry: Option<&Registry>, @@ -246,6 +259,7 @@ impl> NetworkBridge { let validator = Arc::new(validator); let gossip_engine = Arc::new(Mutex::new(GossipEngine::new( service.clone(), + sync.clone(), protocol, validator.clone(), prometheus_registry, @@ -290,6 +304,7 @@ impl> NetworkBridge { NetworkBridge { service, + sync, gossip_engine, validator, neighbor_sender: neighbor_packet_sender, @@ -475,11 +490,11 @@ impl> NetworkBridge { hash: B::Hash, number: NumberFor, ) { - self.service.set_sync_fork_request(peers, hash, number) + self.sync.set_sync_fork_request(peers, hash, number) } } -impl> Future for NetworkBridge { +impl, S: Syncing> Future for NetworkBridge { type Output = Result<(), Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { @@ -661,10 +676,11 @@ fn incoming_global( }) } -impl> Clone for NetworkBridge { +impl, S: Syncing> Clone for NetworkBridge { fn clone(&self) -> Self { NetworkBridge { service: self.service.clone(), + sync: self.sync.clone(), gossip_engine: self.gossip_engine.clone(), validator: Arc::clone(&self.validator), neighbor_sender: self.neighbor_sender.clone(), @@ -739,7 +755,7 @@ impl Sink> for OutgoingMessages { // when locals exist, sign messages on import if let Some(ref keystore) = self.keystore { let target_hash = *(msg.target().0); - let signed = sp_finality_grandpa::sign_message( + let signed = sp_consensus_grandpa::sign_message( keystore.keystore(), msg, keystore.local_id().clone(), @@ -836,13 +852,11 @@ fn check_compact_commit( // check signatures on all contained precommits. let mut buf = Vec::new(); - for (i, (precommit, &(ref sig, ref id))) in - msg.precommits.iter().zip(&msg.auth_data).enumerate() - { + for (i, (precommit, (sig, id))) in msg.precommits.iter().zip(&msg.auth_data).enumerate() { use crate::communication::gossip::Misbehavior; use finality_grandpa::Message as GrandpaMessage; - if !sp_finality_grandpa::check_message_signature_with_buffer( + if !sp_consensus_grandpa::check_message_signature_with_buffer( &GrandpaMessage::Precommit(precommit.clone()), id, sig, @@ -934,7 +948,7 @@ fn check_catch_up( for (msg, id, sig) in messages { signatures_checked += 1; - if !sp_finality_grandpa::check_message_signature_with_buffer( + if !sp_consensus_grandpa::check_message_signature_with_buffer( &msg, id, sig, round, set_id, buf, ) { debug!(target: LOG_TARGET, "Bad catch up message signature {}", id); diff --git a/client/finality-grandpa/src/communication/periodic.rs b/client/consensus/grandpa/src/communication/periodic.rs similarity index 98% rename from client/finality-grandpa/src/communication/periodic.rs rename to client/consensus/grandpa/src/communication/periodic.rs index c00fed129651..f3f7572864e5 100644 --- a/client/finality-grandpa/src/communication/periodic.rs +++ b/client/consensus/grandpa/src/communication/periodic.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/finality-grandpa/src/communication/tests.rs b/client/consensus/grandpa/src/communication/tests.rs similarity index 90% rename from client/finality-grandpa/src/communication/tests.rs rename to client/consensus/grandpa/src/communication/tests.rs index 839b2d52be65..504fde74be60 100644 --- a/client/finality-grandpa/src/communication/tests.rs +++ b/client/consensus/grandpa/src/communication/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -25,19 +25,22 @@ use super::{ use crate::{communication::grandpa_protocol_name, environment::SharedVoterSetState}; use futures::prelude::*; use parity_scale_codec::Encode; -use sc_network::{config::Role, Multiaddr, PeerId, ReputationChange}; +use sc_network::{ + config::{MultiaddrWithPeerId, Role}, + event::Event as NetworkEvent, + types::ProtocolName, + Multiaddr, NetworkBlock, NetworkEventStream, NetworkNotification, NetworkPeers, + NetworkSyncForkRequest, NotificationSenderError, NotificationSenderT as NotificationSender, + PeerId, ReputationChange, +}; use sc_network_common::{ - config::MultiaddrWithPeerId, - protocol::{event::Event as NetworkEvent, role::ObservedRole, ProtocolName}, - service::{ - NetworkBlock, NetworkEventStream, NetworkNotification, NetworkPeers, - NetworkSyncForkRequest, NotificationSender, NotificationSenderError, - }, + role::ObservedRole, + sync::{SyncEvent as SyncStreamEvent, SyncEventStream}, }; use sc_network_gossip::Validator; use sc_network_test::{Block, Hash}; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; -use sp_finality_grandpa::AuthorityList; +use sp_consensus_grandpa::AuthorityList; use sp_keyring::Ed25519Keyring; use sp_runtime::traits::NumberFor; use std::{ @@ -113,18 +116,6 @@ impl NetworkPeers for TestNetwork { fn remove_peers_from_reserved_set(&self, _protocol: ProtocolName, _peers: Vec) {} - fn add_to_peers_set( - &self, - _protocol: ProtocolName, - _peers: HashSet, - ) -> Result<(), String> { - unimplemented!(); - } - - fn remove_from_peers_set(&self, _protocol: ProtocolName, _peers: Vec) { - unimplemented!(); - } - fn sync_num_connected(&self) -> usize { unimplemented!(); } @@ -153,6 +144,10 @@ impl NetworkNotification for TestNetwork { ) -> Result, NotificationSenderError> { unimplemented!(); } + + fn set_notification_handshake(&self, _protocol: ProtocolName, _handshake: Vec) { + unimplemented!(); + } } impl NetworkBlock> for TestNetwork { @@ -186,8 +181,34 @@ impl sc_network_gossip::ValidatorContext for TestNetwork { fn send_topic(&mut self, _: &PeerId, _: Hash, _: bool) {} } +#[derive(Clone)] +pub(crate) struct TestSync; + +impl SyncEventStream for TestSync { + fn event_stream( + &self, + _name: &'static str, + ) -> Pin + Send>> { + Box::pin(futures::stream::pending()) + } +} + +impl NetworkBlock> for TestSync { + fn announce_block(&self, _hash: Hash, _data: Option>) { + unimplemented!(); + } + + fn new_best_block_imported(&self, _hash: Hash, _number: NumberFor) { + unimplemented!(); + } +} + +impl NetworkSyncForkRequest> for TestSync { + fn set_sync_fork_request(&self, _peers: Vec, _hash: Hash, _number: NumberFor) {} +} + pub(crate) struct Tester { - pub(crate) net_handle: super::NetworkBridge, + pub(crate) net_handle: super::NetworkBridge, gossip_validator: Arc>, pub(crate) events: TracingUnboundedReceiver, } @@ -223,7 +244,7 @@ impl Tester { fn config() -> crate::Config { crate::Config { gossip_duration: std::time::Duration::from_millis(10), - justification_period: 256, + justification_generation_period: 256, keystore: None, name: None, local_role: Role::Authority, @@ -237,8 +258,8 @@ fn config() -> crate::Config { fn voter_set_state() -> SharedVoterSetState { use crate::{authorities::AuthoritySet, environment::VoterSetState}; use finality_grandpa::round::State as RoundState; + use sp_consensus_grandpa::AuthorityId; use sp_core::{crypto::ByteArray, H256}; - use sp_finality_grandpa::AuthorityId; let state = RoundState::genesis((H256::zero(), 0)); let base = state.prevote_ghost.unwrap(); @@ -255,6 +276,7 @@ fn voter_set_state() -> SharedVoterSetState { pub(crate) fn make_test_network() -> (impl Future, TestNetwork) { let (tx, rx) = tracing_unbounded("test", 100_000); let net = TestNetwork { sender: tx }; + let sync = TestSync {}; #[derive(Clone)] struct Exit; @@ -267,7 +289,8 @@ pub(crate) fn make_test_network() -> (impl Future, TestNetwork) } } - let bridge = super::NetworkBridge::new(net.clone(), config(), voter_set_state(), None, None); + let bridge = + super::NetworkBridge::new(net.clone(), sync, config(), voter_set_state(), None, None); ( futures::future::ready(Tester { @@ -306,7 +329,7 @@ fn good_commit_leads_to_relay() { let target_number = 500; let precommit = finality_grandpa::Precommit { target_hash, target_number }; - let payload = sp_finality_grandpa::localized_payload( + let payload = sp_consensus_grandpa::localized_payload( round, set_id, &finality_grandpa::Message::Precommit(precommit.clone()), @@ -318,7 +341,7 @@ fn good_commit_leads_to_relay() { for (i, key) in private.iter().enumerate() { precommits.push(precommit.clone()); - let signature = sp_finality_grandpa::AuthoritySignature::from(key.sign(&payload[..])); + let signature = sp_consensus_grandpa::AuthoritySignature::from(key.sign(&payload[..])); auth_data.push((signature, public[i].0.clone())) } @@ -370,6 +393,7 @@ fn good_commit_leads_to_relay() { protocol: grandpa_protocol_name::NAME.into(), negotiated_fallback: None, role: ObservedRole::Full, + received_handshake: vec![], }); let _ = sender.unbounded_send(NetworkEvent::NotificationsReceived { @@ -387,6 +411,7 @@ fn good_commit_leads_to_relay() { protocol: grandpa_protocol_name::NAME.into(), negotiated_fallback: None, role: ObservedRole::Full, + received_handshake: vec![], }); // Announce its local set has being on the current set id through a neighbor @@ -456,7 +481,7 @@ fn bad_commit_leads_to_report() { let target_number = 500; let precommit = finality_grandpa::Precommit { target_hash, target_number }; - let payload = sp_finality_grandpa::localized_payload( + let payload = sp_consensus_grandpa::localized_payload( round, set_id, &finality_grandpa::Message::Precommit(precommit.clone()), @@ -468,7 +493,7 @@ fn bad_commit_leads_to_report() { for (i, key) in private.iter().enumerate() { precommits.push(precommit.clone()); - let signature = sp_finality_grandpa::AuthoritySignature::from(key.sign(&payload[..])); + let signature = sp_consensus_grandpa::AuthoritySignature::from(key.sign(&payload[..])); auth_data.push((signature, public[i].0.clone())) } @@ -519,6 +544,7 @@ fn bad_commit_leads_to_report() { protocol: grandpa_protocol_name::NAME.into(), negotiated_fallback: None, role: ObservedRole::Full, + received_handshake: vec![], }); let _ = sender.unbounded_send(NetworkEvent::NotificationsReceived { remote: sender_id, @@ -631,7 +657,7 @@ fn local_chain_spec() -> Box { } } let chain_spec = GenericChainSpec::::from_json_bytes( - &include_bytes!("../../../chain-spec/res/chain_spec.json")[..], + &include_bytes!("../../../../chain-spec/res/chain_spec.json")[..], ) .unwrap(); chain_spec.cloned_box() @@ -643,7 +669,7 @@ fn grandpa_protocol_name() { // Create protocol name using random genesis hash. let genesis_hash = sp_core::H256::random(); - let expected = format!("/{}/grandpa/1", array_bytes::bytes2hex("", genesis_hash.as_ref())); + let expected = format!("/{}/grandpa/1", array_bytes::bytes2hex("", genesis_hash)); let proto_name = grandpa_protocol_name::standard_name(&genesis_hash, &chain_spec); assert_eq!(proto_name.to_string(), expected); diff --git a/client/finality-grandpa/src/environment.rs b/client/consensus/grandpa/src/environment.rs similarity index 92% rename from client/finality-grandpa/src/environment.rs rename to client/consensus/grandpa/src/environment.rs index 40ec720c4e7f..d3e2beb84e79 100644 --- a/client/finality-grandpa/src/environment.rs +++ b/client/consensus/grandpa/src/environment.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -40,20 +40,19 @@ use sc_client_api::{ utils::is_descendent_of, }; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO}; +use sc_transaction_pool_api::OffchainTransactionPoolFactory; +use sp_api::ApiExt; use sp_blockchain::HeaderMetadata; use sp_consensus::SelectChain as SelectChainT; -use sp_finality_grandpa::{ +use sp_consensus_grandpa::{ AuthorityId, AuthoritySignature, Equivocation, EquivocationProof, GrandpaApi, RoundNumber, SetId, GRANDPA_ENGINE_ID, }; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero}, -}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero}; use crate::{ authorities::{AuthoritySet, SharedAuthoritySet}, - communication::Network as NetworkT, + communication::{Network as NetworkT, Syncing as SyncingT}, justification::GrandpaJustification, local_authority_id, notification::GrandpaJustificationSender, @@ -426,23 +425,34 @@ impl Metrics { } /// The environment we run GRANDPA in. -pub(crate) struct Environment, SC, VR> { +pub(crate) struct Environment< + Backend, + Block: BlockT, + C, + N: NetworkT, + S: SyncingT, + SC, + VR, +> { pub(crate) client: Arc, pub(crate) select_chain: SC, pub(crate) voters: Arc>, pub(crate) config: Config, pub(crate) authority_set: SharedAuthoritySet>, - pub(crate) network: crate::communication::NetworkBridge, + pub(crate) network: crate::communication::NetworkBridge, pub(crate) set_id: SetId, pub(crate) voter_set_state: SharedVoterSetState, pub(crate) voting_rule: VR, pub(crate) metrics: Option, pub(crate) justification_sender: Option>, pub(crate) telemetry: Option, + pub(crate) offchain_tx_pool_factory: OffchainTransactionPoolFactory, pub(crate) _phantom: PhantomData, } -impl, SC, VR> Environment { +impl, S: SyncingT, SC, VR> + Environment +{ /// Updates the voter set state using the given closure. The write lock is /// held during evaluation of the closure and the environment's voter set /// state is set to its result if successful. @@ -472,13 +482,14 @@ impl, SC, VR> Environment Environment +impl Environment where Block: BlockT, BE: BackendT, C: ClientForGrandpa, C::Api: GrandpaApi, N: NetworkT, + S: SyncingT, SC: SelectChainT, { /// Report the given equivocation to the GRANDPA runtime module. This method @@ -543,7 +554,7 @@ where .client .runtime_api() .generate_key_ownership_proof( - &BlockId::Hash(current_set_latest_hash), + current_set_latest_hash, authority_set.set_id, equivocation.offender().clone(), ) @@ -562,10 +573,15 @@ where // submit equivocation report at **best** block let equivocation_proof = EquivocationProof::new(authority_set.set_id, equivocation); - self.client - .runtime_api() + let mut runtime_api = self.client.runtime_api(); + + runtime_api.register_extension( + self.offchain_tx_pool_factory.offchain_transaction_pool(best_block_hash), + ); + + runtime_api .submit_report_equivocation_unsigned_extrinsic( - &BlockId::Hash(best_block_hash), + best_block_hash, equivocation_proof, key_owner_proof, ) @@ -575,13 +591,14 @@ where } } -impl finality_grandpa::Chain> - for Environment +impl finality_grandpa::Chain> + for Environment where Block: BlockT, BE: BackendT, C: ClientForGrandpa, N: NetworkT, + S: SyncingT, SC: SelectChainT, VR: VotingRuleT, NumberFor: BlockNumberOps, @@ -633,14 +650,15 @@ where Ok(tree_route.retracted().iter().skip(1).map(|e| e.hash).collect()) } -impl voter::Environment> - for Environment +impl voter::Environment> + for Environment where Block: BlockT, B: BackendT, C: ClientForGrandpa + 'static, C::Api: GrandpaApi, N: NetworkT, + S: SyncingT, SC: SelectChainT + 'static, VR: VotingRuleT + Clone + 'static, NumberFor: BlockNumberOps, @@ -1084,7 +1102,7 @@ where finalize_block( self.client.clone(), &self.authority_set, - Some(self.config.justification_period.into()), + Some(self.config.justification_generation_period), hash, number, (round, commit).into(), @@ -1213,7 +1231,7 @@ where // NOTE: this is purposefully done after `finality_target` to prevent a case // where in-between these two requests there is a block import and // `finality_target` returns something higher than `best_chain`. - let best_header = match select_chain.best_chain().await { + let mut best_header = match select_chain.best_chain().await { Ok(best_header) => best_header, Err(err) => { warn!( @@ -1227,12 +1245,30 @@ where }, }; - if target_header.number() > best_header.number() { - return Err(Error::Safety( - "SelectChain returned a finality target higher than its best block".into(), - )) + let is_descendent_of = is_descendent_of(&*client, None); + + if target_header.number() > best_header.number() || + target_header.number() == best_header.number() && + target_header.hash() != best_header.hash() || + !is_descendent_of(&target_header.hash(), &best_header.hash())? + { + debug!( + target: LOG_TARGET, + "SelectChain returned a finality target inconsistent with its best block. Restricting best block to target block" + ); + + best_header = target_header.clone(); } + debug!( + target: LOG_TARGET, + "SelectChain: finality target: #{} ({}), best block: #{} ({})", + target_header.number(), + target_header.hash(), + best_header.number(), + best_header.hash(), + ); + // check if our vote is currently being limited due to a pending change, // in which case we will restrict our target header to the given limit if let Some(target_number) = limit.filter(|limit| limit < target_header.number()) { @@ -1254,6 +1290,13 @@ where .header(*target_header.parent_hash())? .expect("Header known to exist after `finality_target` call; qed"); } + + debug!( + target: LOG_TARGET, + "Finality target restricted to #{} ({}) due to pending authority set change", + target_header.number(), + target_header.hash() + ) } // restrict vote according to the given voting rule, if the voting rule @@ -1272,6 +1315,38 @@ where .or_else(|| Some((target_header.hash(), *target_header.number())))) } +/// Whether we should process a justification for the given block. +/// +/// This can be used to decide whether to import a justification (when +/// importing a block), or whether to generate a justification from a +/// commit (when validating). Justifications for blocks that change the +/// authority set will always be processed, otherwise we'll only process +/// justifications if the last one was `justification_period` blocks ago. +pub(crate) fn should_process_justification( + client: &Client, + justification_period: u32, + number: NumberFor, + enacts_change: bool, +) -> bool +where + Block: BlockT, + BE: BackendT, + Client: ClientForGrandpa, +{ + if enacts_change { + return true + } + + let last_finalized_number = client.info().finalized_number; + + // keep the first justification before reaching the justification period + if last_finalized_number.is_zero() { + return true + } + + last_finalized_number / justification_period.into() != number / justification_period.into() +} + /// Finalize the given block and apply any authority set changes. If an /// authority set change is enacted then a justification is created (if not /// given) and stored with the block when finalizing it. @@ -1279,7 +1354,7 @@ where pub(crate) fn finalize_block( client: Arc, authority_set: &SharedAuthoritySet>, - justification_period: Option>, + justification_generation_period: Option, hash: Block::Hash, number: NumberFor, justification_or_commit: JustificationOrCommit, @@ -1350,22 +1425,13 @@ where let (justification_required, justification) = match justification_or_commit { JustificationOrCommit::Justification(justification) => (true, justification), JustificationOrCommit::Commit((round_number, commit)) => { - let mut justification_required = - // justification is always required when block that enacts new authorities - // set is finalized - status.new_set_block.is_some(); - - // justification is required every N blocks to be able to prove blocks - // finalization to remote nodes - if !justification_required { - if let Some(justification_period) = justification_period { - let last_finalized_number = client.info().finalized_number; - justification_required = (!last_finalized_number.is_zero() || - number - last_finalized_number == justification_period) && - (last_finalized_number / justification_period != - number / justification_period); - } - } + let enacts_change = status.new_set_block.is_some(); + + let justification_required = justification_generation_period + .map(|period| { + should_process_justification(&*client, period, number, enacts_change) + }) + .unwrap_or(enacts_change); let justification = GrandpaJustification::from_commit(&client, round_number, commit)?; diff --git a/client/finality-grandpa/src/finality_proof.rs b/client/consensus/grandpa/src/finality_proof.rs similarity index 86% rename from client/finality-grandpa/src/finality_proof.rs rename to client/consensus/grandpa/src/finality_proof.rs index 416e71478ab4..8a8a688583e3 100644 --- a/client/finality-grandpa/src/finality_proof.rs +++ b/client/consensus/grandpa/src/finality_proof.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -42,7 +42,7 @@ use std::sync::Arc; use parity_scale_codec::{Decode, Encode}; use sc_client_api::backend::Backend; use sp_blockchain::{Backend as BlockchainBackend, HeaderBackend}; -use sp_finality_grandpa::GRANDPA_ENGINE_ID; +use sp_consensus_grandpa::GRANDPA_ENGINE_ID; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Header as HeaderT, NumberFor, One}, @@ -58,6 +58,7 @@ use crate::{ const MAX_UNKNOWN_HEADERS: usize = 100_000; /// Finality proof provider for serving network requests. +#[derive(Clone)] pub struct FinalityProofProvider { backend: Arc, shared_authority_set: Option>>, @@ -99,11 +100,24 @@ where B: Backend, { /// Prove finality for the given block number by returning a Justification for the last block of - /// the authority set. + /// the authority set in bytes. pub fn prove_finality( &self, block: NumberFor, ) -> Result>, FinalityProofError> { + Ok(self.prove_finality_proof(block, true)?.map(|proof| proof.encode())) + } + + /// Prove finality for the given block number by returning a Justification for the last block of + /// the authority set. + /// + /// If `collect_unknown_headers` is true, the finality proof will include all headers from the + /// requested block until the block the justification refers to. + pub fn prove_finality_proof( + &self, + block: NumberFor, + collect_unknown_headers: bool, + ) -> Result>, FinalityProofError> { let authority_set_changes = if let Some(changes) = self .shared_authority_set .as_ref() @@ -114,7 +128,7 @@ where return Ok(None) }; - prove_finality(&*self.backend, authority_set_changes, block) + prove_finality(&*self.backend, authority_set_changes, block, collect_unknown_headers) } } @@ -146,22 +160,29 @@ pub enum FinalityProofError { Client(#[from] sp_blockchain::Error), } +/// Prove finality for the given block number by returning a justification for the last block of +/// the authority set of which the given block is part of, or a justification for the latest +/// finalized block if the given block is part of the current authority set. +/// +/// If `collect_unknown_headers` is true, the finality proof will include all headers from the +/// requested block until the block the justification refers to. fn prove_finality( backend: &B, authority_set_changes: AuthoritySetChanges>, block: NumberFor, -) -> Result>, FinalityProofError> + collect_unknown_headers: bool, +) -> Result>, FinalityProofError> where Block: BlockT, B: Backend, { // Early-return if we are sure that there are no blocks finalized that cover the requested // block. - let info = backend.blockchain().info(); - if info.finalized_number < block { + let finalized_number = backend.blockchain().info().finalized_number; + if finalized_number < block { let err = format!( "Requested finality proof for descendant of #{} while we only have finalized #{}.", - block, info.finalized_number, + block, finalized_number, ); trace!(target: LOG_TARGET, "{}", &err); return Err(FinalityProofError::BlockNotYetFinalized) @@ -214,9 +235,9 @@ where }, }; - // Collect all headers from the requested block until the last block of the set - let unknown_headers = { - let mut headers = Vec::new(); + let mut headers = Vec::new(); + if collect_unknown_headers { + // Collect all headers from the requested block until the last block of the set let mut current = block + One::one(); loop { if current > just_block || headers.len() >= MAX_UNKNOWN_HEADERS { @@ -226,17 +247,13 @@ where headers.push(backend.blockchain().expect_header(hash)?); current += One::one(); } - headers }; - Ok(Some( - FinalityProof { - block: backend.blockchain().expect_block_hash_from_id(&BlockId::Number(just_block))?, - justification, - unknown_headers, - } - .encode(), - )) + Ok(Some(FinalityProof { + block: backend.blockchain().expect_block_hash_from_id(&BlockId::Number(just_block))?, + justification, + unknown_headers: headers, + })) } #[cfg(test)] @@ -247,8 +264,8 @@ mod tests { use sc_block_builder::BlockBuilderProvider; use sc_client_api::{apply_aux, LockImportRun}; use sp_consensus::BlockOrigin; + use sp_consensus_grandpa::GRANDPA_ENGINE_ID as ID; use sp_core::crypto::UncheckedFrom; - use sp_finality_grandpa::GRANDPA_ENGINE_ID as ID; use sp_keyring::Ed25519Keyring; use substrate_test_runtime_client::{ runtime::{Block, Header, H256}, @@ -262,7 +279,7 @@ mod tests { /// AND if at least one of those headers is invalid, all other MUST be considered invalid. fn check_finality_proof( current_set_id: SetId, - current_authorities: sp_finality_grandpa::AuthorityList, + current_authorities: sp_consensus_grandpa::AuthorityList, remote_proof: Vec, ) -> sp_blockchain::Result> where @@ -334,7 +351,7 @@ mod tests { let authority_set_changes = AuthoritySetChanges::empty(); // The last finalized block is 4, so we cannot provide further justifications. - let proof_of_5 = prove_finality(&*backend, authority_set_changes, 5); + let proof_of_5 = prove_finality(&*backend, authority_set_changes, 5, true); assert!(matches!(proof_of_5, Err(FinalityProofError::BlockNotYetFinalized))); } @@ -347,7 +364,7 @@ mod tests { // Block 4 is finalized without justification // => we can't prove finality of 3 - let proof_of_3 = prove_finality(&*backend, authority_set_changes, 3).unwrap(); + let proof_of_3 = prove_finality(&*backend, authority_set_changes, 3, true).unwrap(); assert_eq!(proof_of_3, None); } @@ -385,7 +402,7 @@ mod tests { }; let grandpa_just: GrandpaJustification = - sp_finality_grandpa::GrandpaJustification::

{ + sp_consensus_grandpa::GrandpaJustification::
{ round: 8, votes_ancestries: Vec::new(), commit, @@ -425,7 +442,7 @@ mod tests { }; let msg = finality_grandpa::Message::Precommit(precommit.clone()); - let encoded = sp_finality_grandpa::localized_payload(round, set_id, &msg); + let encoded = sp_consensus_grandpa::localized_payload(round, set_id, &msg); let signature = voter.sign(&encoded[..]).into(); let signed_precommit = finality_grandpa::SignedPrecommit { @@ -478,7 +495,7 @@ mod tests { let mut authority_set_changes = AuthoritySetChanges::empty(); authority_set_changes.append(1, 8); - let proof_of_6 = prove_finality(&*backend, authority_set_changes, 6); + let proof_of_6 = prove_finality(&*backend, authority_set_changes, 6, true); assert!(matches!(proof_of_6, Err(FinalityProofError::BlockNotInAuthoritySetChanges))); } @@ -502,10 +519,11 @@ mod tests { authority_set_changes.append(0, 5); authority_set_changes.append(1, 8); - let proof_of_6: FinalityProof = Decode::decode( - &mut &prove_finality(&*backend, authority_set_changes.clone(), 6).unwrap().unwrap()[..], - ) - .unwrap(); + let proof_of_6: FinalityProof = + prove_finality(&*backend, authority_set_changes.clone(), 6, true) + .unwrap() + .unwrap(); + assert_eq!( proof_of_6, FinalityProof { @@ -514,6 +532,20 @@ mod tests { unknown_headers: vec![block7.header().clone(), block8.header().clone()], }, ); + + let proof_of_6_without_unknown: FinalityProof = + prove_finality(&*backend, authority_set_changes.clone(), 6, false) + .unwrap() + .unwrap(); + + assert_eq!( + proof_of_6_without_unknown, + FinalityProof { + block: block8.hash(), + justification: grandpa_just8.encode(), + unknown_headers: vec![], + }, + ); } #[test] @@ -525,7 +557,7 @@ mod tests { let mut authority_set_changes = AuthoritySetChanges::empty(); authority_set_changes.append(0, 5); - assert!(matches!(prove_finality(&*backend, authority_set_changes, 6), Ok(None))); + assert!(matches!(prove_finality(&*backend, authority_set_changes, 6, true), Ok(None))); } #[test] @@ -544,10 +576,9 @@ mod tests { let mut authority_set_changes = AuthoritySetChanges::empty(); authority_set_changes.append(0, 5); - let proof_of_6: FinalityProof = Decode::decode( - &mut &prove_finality(&*backend, authority_set_changes, 6).unwrap().unwrap()[..], - ) - .unwrap(); + let proof_of_6: FinalityProof = + prove_finality(&*backend, authority_set_changes, 6, true).unwrap().unwrap(); + assert_eq!( proof_of_6, FinalityProof { diff --git a/client/finality-grandpa/src/import.rs b/client/consensus/grandpa/src/import.rs similarity index 94% rename from client/finality-grandpa/src/import.rs rename to client/consensus/grandpa/src/import.rs index ed6e4a0fa2e9..760cb2da0484 100644 --- a/client/finality-grandpa/src/import.rs +++ b/client/consensus/grandpa/src/import.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -29,19 +29,19 @@ use sc_consensus::{ use sc_telemetry::TelemetryHandle; use sc_utils::mpsc::TracingUnboundedSender; use sp_api::{Core, RuntimeApiInfo, TransactionFor}; -use sp_blockchain::{well_known_cache_keys, BlockStatus}; +use sp_blockchain::BlockStatus; use sp_consensus::{BlockOrigin, Error as ConsensusError, SelectChain}; +use sp_consensus_grandpa::{ConsensusLog, GrandpaApi, ScheduledChange, SetId, GRANDPA_ENGINE_ID}; use sp_core::hashing::twox_128; -use sp_finality_grandpa::{ConsensusLog, GrandpaApi, ScheduledChange, SetId, GRANDPA_ENGINE_ID}; use sp_runtime::{ - generic::{BlockId, OpaqueDigestItemId}, + generic::OpaqueDigestItemId, traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero}, Justification, }; use crate::{ authorities::{AuthoritySet, DelayKind, PendingChange, SharedAuthoritySet}, - environment::finalize_block, + environment, justification::GrandpaJustification, notification::GrandpaJustificationSender, AuthoritySetChanges, ClientForGrandpa, CommandOrError, Error, NewAuthoritySet, VoterCommand, @@ -59,6 +59,7 @@ use crate::{ /// object. pub struct GrandpaBlockImport { inner: Arc, + justification_import_period: u32, select_chain: SC, authority_set: SharedAuthoritySet>, send_voter_commands: TracingUnboundedSender>>, @@ -74,6 +75,7 @@ impl Clone fn clone(&self) -> Self { GrandpaBlockImport { inner: self.inner.clone(), + justification_import_period: self.justification_import_period, select_chain: self.select_chain.clone(), authority_set: self.authority_set.clone(), send_voter_commands: self.send_voter_commands.clone(), @@ -424,8 +426,7 @@ where /// Read current set id form a given state. fn current_set_id(&self, hash: Block::Hash) -> Result { - let id = &BlockId::hash(hash); - let runtime_version = self.inner.runtime_api().version(id).map_err(|e| { + let runtime_version = self.inner.runtime_api().version(hash).map_err(|e| { ConsensusError::ClientImport(format!( "Unable to retrieve current runtime version. {}", e @@ -452,7 +453,7 @@ where } else { self.inner .runtime_api() - .current_set_id(id) + .current_set_id(hash) .map_err(|e| ConsensusError::ClientImport(e.to_string())) } } @@ -461,13 +462,12 @@ where async fn import_state( &mut self, mut block: BlockImportParams>, - new_cache: HashMap>, ) -> Result { let hash = block.post_hash(); let number = *block.header.number(); // Force imported state finality. block.finalized = true; - let import_result = (&*self.inner).import_block(block, new_cache).await; + let import_result = (&*self.inner).import_block(block).await; match import_result { Ok(ImportResult::Imported(aux)) => { // We've just imported a new state. We trust the sync module has verified @@ -477,7 +477,7 @@ where let authorities = self .inner .runtime_api() - .grandpa_authorities(&BlockId::hash(hash)) + .grandpa_authorities(hash) .map_err(|e| ConsensusError::ClientImport(e.to_string()))?; let set_id = self.current_set_id(hash)?; let authority_set = AuthoritySet::new( @@ -527,7 +527,6 @@ where async fn import_block( &mut self, mut block: BlockImportParams, - new_cache: HashMap>, ) -> Result { let hash = block.post_hash(); let number = *block.header.number(); @@ -538,14 +537,14 @@ where Ok(BlockStatus::InChain) => { // Strip justifications when re-importing an existing block. let _justifications = block.justifications.take(); - return (&*self.inner).import_block(block, new_cache).await + return (&*self.inner).import_block(block).await }, Ok(BlockStatus::Unknown) => {}, Err(e) => return Err(ConsensusError::ClientImport(e.to_string())), } if block.with_state() { - return self.import_state(block, new_cache).await + return self.import_state(block).await } if number <= self.inner.info().finalized_number { @@ -571,7 +570,7 @@ where }, ); } - return (&*self.inner).import_block(block, new_cache).await + return (&*self.inner).import_block(block).await } // on initial sync we will restrict logging under info to avoid spam. @@ -581,7 +580,7 @@ where // we don't want to finalize on `inner.import_block` let mut justifications = block.justifications.take(); - let import_result = (&*self.inner).import_block(block, new_cache).await; + let import_result = (&*self.inner).import_block(block).await; let mut imported_aux = { match import_result { @@ -651,26 +650,39 @@ where match grandpa_justification { Some(justification) => { - let import_res = self.import_justification( - hash, + if environment::should_process_justification( + &*self.inner, + self.justification_import_period, number, - (GRANDPA_ENGINE_ID, justification), needs_justification, - initial_sync, - ); + ) { + let import_res = self.import_justification( + hash, + number, + (GRANDPA_ENGINE_ID, justification), + needs_justification, + initial_sync, + ); - import_res.unwrap_or_else(|err| { - if needs_justification { - debug!( - target: LOG_TARGET, - "Requesting justification from peers due to imported block #{} that enacts authority set change with invalid justification: {}", - number, - err - ); - imported_aux.bad_justification = true; - imported_aux.needs_justification = true; - } - }); + import_res.unwrap_or_else(|err| { + if needs_justification { + debug!( + target: LOG_TARGET, + "Requesting justification from peers due to imported block #{} that enacts authority set change with invalid justification: {}", + number, + err + ); + imported_aux.bad_justification = true; + imported_aux.needs_justification = true; + } + }); + } else { + debug!( + target: LOG_TARGET, + "Ignoring unnecessary justification for block #{}", + number, + ); + } }, None => if needs_justification { @@ -698,6 +710,7 @@ where impl GrandpaBlockImport { pub(crate) fn new( inner: Arc, + justification_import_period: u32, select_chain: SC, authority_set: SharedAuthoritySet>, send_voter_commands: TracingUnboundedSender>>, @@ -736,6 +749,7 @@ impl GrandpaBlockImport justification, }; - let result = finalize_block( + let result = environment::finalize_block( self.inner.clone(), &self.authority_set, None, diff --git a/client/finality-grandpa/src/justification.rs b/client/consensus/grandpa/src/justification.rs similarity index 93% rename from client/finality-grandpa/src/justification.rs rename to client/consensus/grandpa/src/justification.rs index 96c31d4c0cc2..c300a3d7ac43 100644 --- a/client/finality-grandpa/src/justification.rs +++ b/client/consensus/grandpa/src/justification.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -25,7 +25,7 @@ use std::{ use finality_grandpa::{voter_set::VoterSet, Error as GrandpaError}; use parity_scale_codec::{Decode, Encode}; use sp_blockchain::{Error as ClientError, HeaderBackend}; -use sp_finality_grandpa::AuthorityId; +use sp_consensus_grandpa::AuthorityId; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; use crate::{AuthorityList, Commit, Error}; @@ -41,22 +41,22 @@ use crate::{AuthorityList, Commit, Error}; #[derive(Clone, Encode, Decode, PartialEq, Eq, Debug)] pub struct GrandpaJustification { /// The GRANDPA justification for block finality. - pub justification: sp_finality_grandpa::GrandpaJustification, + pub justification: sp_consensus_grandpa::GrandpaJustification, _block: PhantomData, } -impl From> +impl From> for GrandpaJustification { - fn from(justification: sp_finality_grandpa::GrandpaJustification) -> Self { + fn from(justification: sp_consensus_grandpa::GrandpaJustification) -> Self { Self { justification, _block: Default::default() } } } -impl Into> +impl Into> for GrandpaJustification { - fn into(self) -> sp_finality_grandpa::GrandpaJustification { + fn into(self) -> sp_consensus_grandpa::GrandpaJustification { self.justification } } @@ -122,7 +122,7 @@ impl GrandpaJustification { } } - Ok(sp_finality_grandpa::GrandpaJustification { round, commit, votes_ancestries }.into()) + Ok(sp_consensus_grandpa::GrandpaJustification { round, commit, votes_ancestries }.into()) } /// Decode a GRANDPA justification and validate the commit and the votes' @@ -205,7 +205,7 @@ impl GrandpaJustification { let mut buf = Vec::new(); let mut visited_hashes = HashSet::new(); for signed in self.justification.commit.precommits.iter() { - if !sp_finality_grandpa::check_message_signature_with_buffer( + if !sp_consensus_grandpa::check_message_signature_with_buffer( &finality_grandpa::Message::Precommit(signed.precommit.clone()), &signed.id, &signed.signature, diff --git a/client/finality-grandpa/src/lib.rs b/client/consensus/grandpa/src/lib.rs similarity index 91% rename from client/finality-grandpa/src/lib.rs rename to client/consensus/grandpa/src/lib.rs index 15d689e917d2..ff0412aeb314 100644 --- a/client/finality-grandpa/src/lib.rs +++ b/client/consensus/grandpa/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -64,22 +64,23 @@ use prometheus_endpoint::{PrometheusError, Registry}; use sc_client_api::{ backend::{AuxStore, Backend}, utils::is_descendent_of, - BlockchainEvents, CallExecutor, ExecutionStrategy, ExecutorProvider, Finalizer, LockImportRun, - StorageProvider, TransactionFor, + BlockchainEvents, CallExecutor, ExecutorProvider, Finalizer, LockImportRun, StorageProvider, + TransactionFor, }; use sc_consensus::BlockImport; -use sc_network_common::protocol::ProtocolName; +use sc_network::types::ProtocolName; use sc_telemetry::{telemetry, TelemetryHandle, CONSENSUS_DEBUG, CONSENSUS_INFO}; +use sc_transaction_pool_api::OffchainTransactionPoolFactory; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver}; use sp_api::ProvideRuntimeApi; -use sp_application_crypto::AppKey; +use sp_application_crypto::AppCrypto; use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata, Result as ClientResult}; use sp_consensus::SelectChain; -use sp_core::crypto::ByteArray; -use sp_finality_grandpa::{ +use sp_consensus_grandpa::{ AuthorityList, AuthoritySignature, SetId, CLIENT_LOG_TARGET as LOG_TARGET, }; -use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; +use sp_core::{crypto::ByteArray, traits::CallContext}; +use sp_keystore::KeystorePtr; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, NumberFor, Zero}, @@ -141,12 +142,12 @@ pub use voting_rule::{ }; use aux_schema::PersistentData; -use communication::{Network as NetworkT, NetworkBridge}; +use communication::{Network as NetworkT, NetworkBridge, Syncing as SyncingT}; use environment::{Environment, VoterSetState}; use until_imported::UntilGlobalMessageBlocksImported; // Re-export these two because it's just so damn convenient. -pub use sp_finality_grandpa::{ +pub use sp_consensus_grandpa::{ AuthorityId, AuthorityPair, CatchUp, Commit, CompactCommit, GrandpaApi, Message, Precommit, Prevote, PrimaryPropose, ScheduledChange, SignedMessage, }; @@ -214,10 +215,10 @@ impl Clone for SharedVoterState { pub struct Config { /// The expected duration for a message to be gossiped across the network. pub gossip_duration: Duration, - /// Justification generation period (in blocks). GRANDPA will try to generate justifications - /// at least every justification_period blocks. There are some other events which might cause - /// justification generation. - pub justification_period: u32, + /// Justification generation period (in blocks). GRANDPA will try to generate + /// justifications at least every justification_generation_period blocks. There + /// are some other events which might cause justification generation. + pub justification_generation_period: u32, /// Whether the GRANDPA observer protocol is live on the network and thereby /// a full-node not running as a validator is running the GRANDPA observer /// protocol (we will only issue catch-up requests to authorities when the @@ -228,7 +229,7 @@ pub struct Config { /// Some local identifier of the voter. pub name: Option, /// The keystore that manages the keys of this node. - pub keystore: Option, + pub keystore: Option, /// TelemetryHandle instance. pub telemetry: Option, /// Chain specific GRANDPA protocol name. See [`crate::protocol_standard_name`]. @@ -349,10 +350,11 @@ pub(crate) trait BlockSyncRequester { ); } -impl BlockSyncRequester for NetworkBridge +impl BlockSyncRequester for NetworkBridge where Block: BlockT, Network: NetworkT, + Syncing: SyncingT, { fn set_sync_fork_request( &self, @@ -478,7 +480,7 @@ where self.expect_block_hash_from_id(&BlockId::Number(Zero::zero()))?, "GrandpaApi_grandpa_authorities", &[], - ExecutionStrategy::NativeElseWasm, + CallContext::Offchain, ) .and_then(|call_result| { Decode::decode(&mut &call_result[..]).map_err(|err| { @@ -493,8 +495,16 @@ where /// Make block importer and link half necessary to tie the background voter /// to it. +/// +/// The `justification_import_period` sets the minimum period on which +/// justifications will be imported. When importing a block, if it includes a +/// justification it will only be processed if it fits within this period, +/// otherwise it will be ignored (and won't be validated). This is to avoid +/// slowing down sync by a peer serving us unnecessary justifications which +/// aren't trivial to validate. pub fn block_import( client: Arc, + justification_import_period: u32, genesis_authorities_provider: &dyn GenesisAuthoritySetProvider, select_chain: SC, telemetry: Option, @@ -506,6 +516,7 @@ where { block_import_with_authority_set_hard_forks( client, + justification_import_period, genesis_authorities_provider, select_chain, Default::default(), @@ -538,6 +549,7 @@ pub struct AuthoritySetHardFork { /// given static authorities. pub fn block_import_with_authority_set_hard_forks( client: Arc, + justification_import_period: u32, genesis_authorities_provider: &dyn GenesisAuthoritySetProvider, select_chain: SC, authority_set_hard_forks: Vec>, @@ -597,6 +609,7 @@ where Ok(( GrandpaBlockImport::new( client.clone(), + justification_import_period, select_chain.clone(), persistent_data.authority_set.clone(), voter_commands_tx, @@ -616,12 +629,12 @@ where )) } -fn global_communication( +fn global_communication( set_id: SetId, voters: &Arc>, client: Arc, - network: &NetworkBridge, - keystore: Option<&SyncCryptoStorePtr>, + network: &NetworkBridge, + keystore: Option<&KeystorePtr>, metrics: Option, ) -> ( impl Stream< @@ -639,6 +652,7 @@ where BE: Backend + 'static, C: ClientForGrandpa + 'static, N: NetworkT, + S: SyncingT, NumberFor: BlockNumberOps, { let is_voter = local_authority_id(voters, keystore).is_some(); @@ -664,7 +678,7 @@ where } /// Parameters used to run Grandpa. -pub struct GrandpaParams { +pub struct GrandpaParams { /// Configuration for the GRANDPA service. pub config: Config, /// A link to the block import worker. @@ -675,6 +689,8 @@ pub struct GrandpaParams { /// `sc_network` crate, it is assumed that the Grandpa notifications protocol has been passed /// to the configuration of the networking. See [`grandpa_peers_set_config`]. pub network: N, + /// Event stream for syncing-related events. + pub sync: S, /// A voting rule used to potentially restrict target votes. pub voting_rule: VR, /// The prometheus metrics registry. @@ -683,39 +699,44 @@ pub struct GrandpaParams { pub shared_voter_state: SharedVoterState, /// TelemetryHandle instance. pub telemetry: Option, + /// Offchain transaction pool factory. + /// + /// This will be used to create an offchain transaction pool instance for sending an + /// equivocation report from the runtime. + pub offchain_tx_pool_factory: OffchainTransactionPoolFactory, } /// Returns the configuration value to put in -/// [`sc_network::config::NetworkConfiguration::extra_sets`]. +/// [`sc_network::config::FullNetworkConfiguration`]. /// For standard protocol name see [`crate::protocol_standard_name`]. pub fn grandpa_peers_set_config( protocol_name: ProtocolName, -) -> sc_network_common::config::NonDefaultSetConfig { +) -> sc_network::config::NonDefaultSetConfig { use communication::grandpa_protocol_name; - sc_network_common::config::NonDefaultSetConfig { + sc_network::config::NonDefaultSetConfig { notifications_protocol: protocol_name, fallback_names: grandpa_protocol_name::LEGACY_NAMES.iter().map(|&n| n.into()).collect(), // Notifications reach ~256kiB in size at the time of writing on Kusama and Polkadot. max_notification_size: 1024 * 1024, handshake: None, - set_config: sc_network_common::config::SetConfig { + set_config: sc_network::config::SetConfig { in_peers: 0, out_peers: 0, reserved_nodes: Vec::new(), - non_reserved_mode: sc_network_common::config::NonReservedPeerMode::Deny, + non_reserved_mode: sc_network::config::NonReservedPeerMode::Deny, }, } } /// Run a GRANDPA voter as a task. Provide configuration and a link to a /// block import worker that has already been instantiated with `block_import`. -pub fn run_grandpa_voter( - grandpa_params: GrandpaParams, +pub fn run_grandpa_voter( + grandpa_params: GrandpaParams, ) -> sp_blockchain::Result + Send> where - Block::Hash: Ord, BE: Backend + 'static, N: NetworkT + Sync + 'static, + S: SyncingT + Sync + 'static, SC: SelectChain + 'static, VR: VotingRule + Clone + 'static, NumberFor: BlockNumberOps, @@ -726,10 +747,12 @@ where mut config, link, network, + sync, voting_rule, prometheus_registry, shared_voter_state, telemetry, + offchain_tx_pool_factory, } = grandpa_params; // NOTE: we have recently removed `run_grandpa_observer` from the public @@ -750,6 +773,7 @@ where let network = NetworkBridge::new( network, + sync, config.clone(), persistent_data.set_state.clone(), prometheus_registry.as_ref(), @@ -803,6 +827,7 @@ where shared_voter_state, justification_sender, telemetry, + offchain_tx_pool_factory, ); let voter_work = voter_work.map(|res| match res { @@ -835,26 +860,27 @@ impl Metrics { /// Future that powers the voter. #[must_use] -struct VoterWork, SC, VR> { +struct VoterWork, S: SyncingT, SC, VR> { voter: Pin< Box>>> + Send>, >, shared_voter_state: SharedVoterState, - env: Arc>, + env: Arc>, voter_commands_rx: TracingUnboundedReceiver>>, - network: NetworkBridge, + network: NetworkBridge, telemetry: Option, /// Prometheus metrics. metrics: Option, } -impl VoterWork +impl VoterWork where Block: BlockT, B: Backend + 'static, C: ClientForGrandpa + 'static, C::Api: GrandpaApi, N: NetworkT + Sync, + S: SyncingT + Sync, NumberFor: BlockNumberOps, SC: SelectChain + 'static, VR: VotingRule + Clone + 'static, @@ -862,7 +888,7 @@ where fn new( client: Arc, config: Config, - network: NetworkBridge, + network: NetworkBridge, select_chain: SC, voting_rule: VR, persistent_data: PersistentData, @@ -871,6 +897,7 @@ where shared_voter_state: SharedVoterState, justification_sender: GrandpaJustificationSender, telemetry: Option, + offchain_tx_pool_factory: OffchainTransactionPoolFactory, ) -> Self { let metrics = match prometheus_registry.as_ref().map(Metrics::register) { Some(Ok(metrics)) => Some(metrics), @@ -895,6 +922,7 @@ where metrics: metrics.as_ref().map(|m| m.environment.clone()), justification_sender: Some(justification_sender), telemetry: telemetry.clone(), + offchain_tx_pool_factory, _phantom: PhantomData, }); @@ -1046,6 +1074,7 @@ where metrics: self.env.metrics.clone(), justification_sender: self.env.justification_sender.clone(), telemetry: self.telemetry.clone(), + offchain_tx_pool_factory: self.env.offchain_tx_pool_factory.clone(), _phantom: PhantomData, }); @@ -1071,11 +1100,12 @@ where } } -impl Future for VoterWork +impl Future for VoterWork where Block: BlockT, B: Backend + 'static, N: NetworkT + Sync, + S: SyncingT + Sync, NumberFor: BlockNumberOps, SC: SelectChain + 'static, C: ClientForGrandpa + 'static, @@ -1090,7 +1120,7 @@ where Poll::Ready(Ok(())) => { // voters don't conclude naturally return Poll::Ready(Err(Error::Safety( - "finality-grandpa inner voter has concluded.".into(), + "consensus-grandpa inner voter has concluded.".into(), ))) }, Poll::Ready(Err(CommandOrError::Error(e))) => { @@ -1126,14 +1156,12 @@ where /// available. fn local_authority_id( voters: &VoterSet, - keystore: Option<&SyncCryptoStorePtr>, + keystore: Option<&KeystorePtr>, ) -> Option { keystore.and_then(|keystore| { voters .iter() - .find(|(p, _)| { - SyncCryptoStore::has_keys(&**keystore, &[(p.to_raw_vec(), AuthorityId::ID)]) - }) + .find(|(p, _)| keystore.has_keys(&[(p.to_raw_vec(), AuthorityId::ID)])) .map(|(p, _)| p.clone()) }) } diff --git a/client/finality-grandpa/src/notification.rs b/client/consensus/grandpa/src/notification.rs similarity index 96% rename from client/finality-grandpa/src/notification.rs rename to client/consensus/grandpa/src/notification.rs index 1d6e25e55dc6..de1fba09ea3d 100644 --- a/client/finality-grandpa/src/notification.rs +++ b/client/consensus/grandpa/src/notification.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/finality-grandpa/src/observer.rs b/client/consensus/grandpa/src/observer.rs similarity index 95% rename from client/finality-grandpa/src/observer.rs rename to client/consensus/grandpa/src/observer.rs index 96101a8eda0a..8541baa822bb 100644 --- a/client/finality-grandpa/src/observer.rs +++ b/client/consensus/grandpa/src/observer.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -32,14 +32,14 @@ use sc_telemetry::TelemetryHandle; use sc_utils::mpsc::TracingUnboundedReceiver; use sp_blockchain::HeaderMetadata; use sp_consensus::SelectChain; -use sp_finality_grandpa::AuthorityId; -use sp_keystore::SyncCryptoStorePtr; +use sp_consensus_grandpa::AuthorityId; +use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, NumberFor}; use crate::{ authorities::SharedAuthoritySet, aux_schema::PersistentData, - communication::{Network as NetworkT, NetworkBridge}, + communication::{Network as NetworkT, NetworkBridge, Syncing as SyncingT}, environment, global_communication, notification::GrandpaJustificationSender, ClientForGrandpa, CommandOrError, CommunicationIn, Config, Error, LinkHalf, VoterCommand, @@ -163,14 +163,16 @@ where /// already been instantiated with `block_import`. /// NOTE: this is currently not part of the crate's public API since we don't consider /// it stable enough to use on a live network. -pub fn run_grandpa_observer( +pub fn run_grandpa_observer( config: Config, link: LinkHalf, network: N, + sync: S, ) -> sp_blockchain::Result + Send> where BE: Backend + Unpin + 'static, N: NetworkT, + S: SyncingT, SC: SelectChain, NumberFor: BlockNumberOps, Client: ClientForGrandpa + 'static, @@ -186,6 +188,7 @@ where let network = NetworkBridge::new( network, + sync, config.clone(), persistent_data.set_state.clone(), None, @@ -211,32 +214,33 @@ where /// Future that powers the observer. #[must_use] -struct ObserverWork> { +struct ObserverWork, S: SyncingT> { observer: Pin>>> + Send>>, client: Arc, - network: NetworkBridge, + network: NetworkBridge, persistent_data: PersistentData, - keystore: Option, + keystore: Option, voter_commands_rx: TracingUnboundedReceiver>>, justification_sender: Option>, telemetry: Option, _phantom: PhantomData, } -impl ObserverWork +impl ObserverWork where B: BlockT, BE: Backend + 'static, Client: ClientForGrandpa + 'static, Network: NetworkT, + Syncing: SyncingT, NumberFor: BlockNumberOps, { fn new( client: Arc, - network: NetworkBridge, + network: NetworkBridge, persistent_data: PersistentData, - keystore: Option, + keystore: Option, voter_commands_rx: TracingUnboundedReceiver>>, justification_sender: Option>, telemetry: Option, @@ -347,12 +351,13 @@ where } } -impl Future for ObserverWork +impl Future for ObserverWork where B: BlockT, BE: Backend + Unpin + 'static, C: ClientForGrandpa + 'static, N: NetworkT, + S: SyncingT, NumberFor: BlockNumberOps, { type Output = Result<(), Error>; diff --git a/client/finality-grandpa/src/tests.rs b/client/consensus/grandpa/src/tests.rs similarity index 80% rename from client/finality-grandpa/src/tests.rs rename to client/consensus/grandpa/src/tests.rs index 07ea4649148f..0175f7d1b473 100644 --- a/client/finality-grandpa/src/tests.rs +++ b/client/consensus/grandpa/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -33,25 +33,23 @@ use sc_network_test::{ Block, BlockImportAdapter, FullPeerConfig, Hash, PassThroughVerifier, Peer, PeersClient, PeersFullClient, TestClient, TestNetFactory, }; +use sc_transaction_pool_api::RejectAllTxPool; use sp_api::{ApiRef, ProvideRuntimeApi}; use sp_consensus::{BlockOrigin, Error as ConsensusError, SelectChain}; -use sp_core::H256; -use sp_finality_grandpa::{ +use sp_consensus_grandpa::{ AuthorityList, EquivocationProof, GrandpaApi, OpaqueKeyOwnershipProof, GRANDPA_ENGINE_ID, }; +use sp_core::H256; use sp_keyring::Ed25519Keyring; -use sp_keystore::{testing::KeyStore as TestKeyStore, SyncCryptoStore, SyncCryptoStorePtr}; +use sp_keystore::{testing::MemoryKeystore, Keystore, KeystorePtr}; use sp_runtime::{ codec::Encode, generic::{BlockId, DigestItem}, traits::{Block as BlockT, Header as HeaderT}, Justifications, }; -use std::{ - collections::{HashMap, HashSet}, - pin::Pin, -}; -use substrate_test_runtime_client::runtime::BlockNumber; +use std::{collections::HashSet, pin::Pin}; +use substrate_test_runtime_client::{runtime::BlockNumber, BlockBuilderExt}; use tokio::runtime::Handle; use authorities::AuthoritySet; @@ -71,6 +69,8 @@ type GrandpaBlockImport = crate::GrandpaBlockImport< LongestChain, >; +const JUSTIFICATION_IMPORT_PERIOD: u32 = 32; + #[derive(Default)] struct GrandpaTestNet { peers: Vec, @@ -128,6 +128,7 @@ impl TestNetFactory for GrandpaTestNet { let (client, backend) = (client.as_client(), client.as_backend()); let (import, link) = block_import( client.clone(), + JUSTIFICATION_IMPORT_PERIOD, &self.test_config, LongestChain::new(backend.clone()), None, @@ -145,6 +146,10 @@ impl TestNetFactory for GrandpaTestNet { &self.peers } + fn peers_mut(&mut self) -> &mut Vec { + &mut self.peers + } + fn mut_peers)>(&mut self, closure: F) { closure(&mut self.peers); } @@ -237,24 +242,54 @@ impl SelectChain for MockSelectChain { async fn finality_target( &self, - _target_hash: Hash, + _base_hash: Hash, _maybe_max_number: Option>, ) -> Result { Ok(self.finality_target.lock().take().unwrap()) } } +// A mock voting rule that allows asserting an expected value for best block +#[derive(Clone, Default)] +struct AssertBestBlock(Arc>>); + +impl VotingRule for AssertBestBlock +where + B: HeaderBackend, +{ + fn restrict_vote( + &self, + _backend: Arc, + _base: &::Header, + best_target: &::Header, + _current_target: &::Header, + ) -> VotingRuleResult { + if let Some(expected) = *self.0.lock() { + assert_eq!(best_target.hash(), expected); + } + + Box::pin(std::future::ready(None)) + } +} + +impl AssertBestBlock { + fn set_expected_best_block(&self, hash: Hash) { + *self.0.lock() = Some(hash); + } +} + const TEST_GOSSIP_DURATION: Duration = Duration::from_millis(500); fn make_ids(keys: &[Ed25519Keyring]) -> AuthorityList { keys.iter().map(|&key| key.public().into()).map(|id| (id, 1)).collect() } -fn create_keystore(authority: Ed25519Keyring) -> SyncCryptoStorePtr { - let keystore = Arc::new(TestKeyStore::new()); - SyncCryptoStore::ed25519_generate_new(&*keystore, GRANDPA, Some(&authority.to_seed())) - .expect("Creates authority key"); +fn create_keystore(authority: Ed25519Keyring) -> KeystorePtr { + let keystore = MemoryKeystore::new(); keystore + .ed25519_generate_new(GRANDPA, Some(&authority.to_seed())) + .expect("Creates authority key"); + keystore.into() } async fn run_until_complete(future: impl Future + Unpin, net: &Arc>) { @@ -281,11 +316,12 @@ fn initialize_grandpa( net.peers[peer_id].data.lock().take().expect("link initialized at startup; qed"); (net.peers[peer_id].network_service().clone(), link) }; + let sync = net.peers[peer_id].sync_service().clone(); let grandpa_params = GrandpaParams { config: Config { gossip_duration: TEST_GOSSIP_DURATION, - justification_period: 32, + justification_generation_period: 32, keystore: Some(keystore), name: Some(format!("peer#{}", peer_id)), local_role: Role::Authority, @@ -295,9 +331,13 @@ fn initialize_grandpa( }, link, network: net_service, + sync, voting_rule: (), prometheus_registry: None, shared_voter_state: SharedVoterState::empty(), + offchain_tx_pool_factory: OffchainTransactionPoolFactory::new( + RejectAllTxPool::default(), + ), telemetry: None, }; let voter = @@ -366,22 +406,27 @@ async fn run_to_completion( run_to_completion_with(blocks, net, peers, |_| None).await } -fn add_scheduled_change(block: &mut Block, change: ScheduledChange) { - block.header.digest_mut().push(DigestItem::Consensus( - GRANDPA_ENGINE_ID, - sp_finality_grandpa::ConsensusLog::ScheduledChange(change).encode(), - )); +fn add_scheduled_change(builder: &mut impl BlockBuilderExt, change: ScheduledChange) { + builder + .push_deposit_log_digest_item(DigestItem::Consensus( + GRANDPA_ENGINE_ID, + sp_consensus_grandpa::ConsensusLog::ScheduledChange(change).encode(), + )) + .unwrap(); } fn add_forced_change( - block: &mut Block, + builder: &mut impl BlockBuilderExt, median_last_finalized: BlockNumber, change: ScheduledChange, ) { - block.header.digest_mut().push(DigestItem::Consensus( - GRANDPA_ENGINE_ID, - sp_finality_grandpa::ConsensusLog::ForcedChange(median_last_finalized, change).encode(), - )); + builder + .push_deposit_log_digest_item(DigestItem::Consensus( + GRANDPA_ENGINE_ID, + sp_consensus_grandpa::ConsensusLog::ForcedChange(median_last_finalized, change) + .encode(), + )) + .unwrap(); } #[tokio::test] @@ -404,11 +449,14 @@ async fn finalize_3_voters_no_observers() { let net = Arc::new(Mutex::new(net)); run_to_completion(20, net.clone(), peers).await; - // normally there's no justification for finalized blocks - assert!( - net.lock().peer(0).client().justifications(hashof20).unwrap().is_none(), - "Extra justification for block#1", - ); + // all peers should have stored the justification for the best finalized block #20 + for peer_id in 0..3 { + let client = net.lock().peers[peer_id].client().as_client(); + let justification = + crate::aux_schema::best_justification::<_, Block>(&*client).unwrap().unwrap(); + + assert_eq!(justification.justification.commit.target_number, 20); + } } #[tokio::test] @@ -422,12 +470,13 @@ async fn finalize_3_voters_1_full_observer() { tokio::spawn({ let peer_id = 3; let net_service = net.peers[peer_id].network_service().clone(); + let sync = net.peers[peer_id].sync_service().clone(); let link = net.peers[peer_id].data.lock().take().expect("link initialized at startup; qed"); let grandpa_params = GrandpaParams { config: Config { gossip_duration: TEST_GOSSIP_DURATION, - justification_period: 32, + justification_generation_period: 32, keystore: None, name: Some(format!("peer#{}", peer_id)), local_role: Role::Authority, @@ -437,10 +486,14 @@ async fn finalize_3_voters_1_full_observer() { }, link, network: net_service, + sync, voting_rule: (), prometheus_registry: None, shared_voter_state: SharedVoterState::empty(), telemetry: None, + offchain_tx_pool_factory: OffchainTransactionPoolFactory::new( + RejectAllTxPool::default(), + ), }; run_grandpa_voter(grandpa_params).expect("all in order with client and network") @@ -504,17 +557,21 @@ async fn transition_3_voters_twice_1_full_observer() { for (peer_id, local_key) in all_peers.clone().into_iter().enumerate() { let keystore = create_keystore(local_key); - let (net_service, link) = { + let (net_service, link, sync) = { let net = net.lock(); let link = net.peers[peer_id].data.lock().take().expect("link initialized at startup; qed"); - (net.peers[peer_id].network_service().clone(), link) + ( + net.peers[peer_id].network_service().clone(), + link, + net.peers[peer_id].sync_service().clone(), + ) }; let grandpa_params = GrandpaParams { config: Config { gossip_duration: TEST_GOSSIP_DURATION, - justification_period: 32, + justification_generation_period: 32, keystore: Some(keystore), name: Some(format!("peer#{}", peer_id)), local_role: Role::Authority, @@ -524,10 +581,14 @@ async fn transition_3_voters_twice_1_full_observer() { }, link, network: net_service, + sync, voting_rule: (), prometheus_registry: None, shared_voter_state: SharedVoterState::empty(), telemetry: None, + offchain_tx_pool_factory: OffchainTransactionPoolFactory::new( + RejectAllTxPool::default(), + ), }; voters @@ -565,28 +626,24 @@ async fn transition_3_voters_twice_1_full_observer() { }, 14 => { // generate transition at block 15, applied at 20. - net.lock().peer(0).generate_blocks(1, BlockOrigin::File, |builder| { - let mut block = builder.build().unwrap().block; + net.lock().peer(0).generate_blocks(1, BlockOrigin::File, |mut builder| { add_scheduled_change( - &mut block, + &mut builder, ScheduledChange { next_authorities: make_ids(peers_b), delay: 4 }, ); - - block + builder.build().unwrap().block }); net.lock().peer(0).push_blocks(5, false); }, 20 => { // at block 21 we do another transition, but this time instant. // add more until we have 30. - net.lock().peer(0).generate_blocks(1, BlockOrigin::File, |builder| { - let mut block = builder.build().unwrap().block; + net.lock().peer(0).generate_blocks(1, BlockOrigin::File, |mut builder| { add_scheduled_change( - &mut block, + &mut builder, ScheduledChange { next_authorities: make_ids(&peers_c), delay: 0 }, ); - - block + builder.build().unwrap().block }); net.lock().peer(0).push_blocks(9, false); }, @@ -644,8 +701,8 @@ async fn justification_is_generated_periodically() { let net = Arc::new(Mutex::new(net)); run_to_completion(32, net.clone(), peers).await; - // when block#32 (justification_period) is finalized, justification - // is required => generated + // when block#32 (justification_generation_period) is finalized, + // justification is required => generated for i in 0..3 { assert!(net.lock().peer(i).client().justifications(hashof32).unwrap().is_some()); } @@ -668,13 +725,12 @@ async fn sync_justifications_on_change_blocks() { // at block 21 we do add a transition which is instant let hashof21 = net .peer(0) - .generate_blocks(1, BlockOrigin::File, |builder| { - let mut block = builder.build().unwrap().block; + .generate_blocks(1, BlockOrigin::File, |mut builder| { add_scheduled_change( - &mut block, + &mut builder, ScheduledChange { next_authorities: make_ids(peers_b), delay: 0 }, ); - block + builder.build().unwrap().block }) .pop() .unwrap(); @@ -738,26 +794,24 @@ async fn finalizes_multiple_pending_changes_in_order() { net.peer(0).push_blocks(20, false); // at block 21 we do add a transition which is instant - net.peer(0).generate_blocks(1, BlockOrigin::File, |builder| { - let mut block = builder.build().unwrap().block; + net.peer(0).generate_blocks(1, BlockOrigin::File, |mut builder| { add_scheduled_change( - &mut block, + &mut builder, ScheduledChange { next_authorities: make_ids(peers_b), delay: 0 }, ); - block + builder.build().unwrap().block }); // add more blocks on top of it (until we have 25) net.peer(0).push_blocks(4, false); // at block 26 we add another which is enacted at block 30 - net.peer(0).generate_blocks(1, BlockOrigin::File, |builder| { - let mut block = builder.build().unwrap().block; + net.peer(0).generate_blocks(1, BlockOrigin::File, |mut builder| { add_scheduled_change( - &mut block, + &mut builder, ScheduledChange { next_authorities: make_ids(peers_c), delay: 4 }, ); - block + builder.build().unwrap().block }); // add more blocks on top of it (until we have 30) @@ -793,23 +847,21 @@ async fn force_change_to_new_set() { let voters_future = initialize_grandpa(&mut net, peers_a); let net = Arc::new(Mutex::new(net)); - net.lock().peer(0).generate_blocks(1, BlockOrigin::File, |builder| { - let mut block = builder.build().unwrap().block; - + net.lock().peer(0).generate_blocks(1, BlockOrigin::File, |mut builder| { // add a forced transition at block 12. add_forced_change( - &mut block, + &mut builder, 0, ScheduledChange { next_authorities: voters.clone(), delay: 10 }, ); // add a normal transition too to ensure that forced changes take priority. add_scheduled_change( - &mut block, + &mut builder, ScheduledChange { next_authorities: make_ids(genesis_authorities), delay: 5 }, ); - block + builder.build().unwrap().block }); net.lock().peer(0).push_blocks(25, false); @@ -845,14 +897,15 @@ async fn allows_reimporting_change_blocks() { let (mut block_import, ..) = net.make_block_import(client.clone()); let full_client = client.as_client(); - let builder = full_client - .new_block_at(&BlockId::Number(0), Default::default(), false) + let mut builder = full_client + .new_block_at(full_client.chain_info().genesis_hash, Default::default(), false) .unwrap(); - let mut block = builder.build().unwrap().block; + add_scheduled_change( - &mut block, + &mut builder, ScheduledChange { next_authorities: make_ids(peers_b), delay: 0 }, ); + let block = builder.build().unwrap().block; let block = || { let block = block.clone(); @@ -864,7 +917,7 @@ async fn allows_reimporting_change_blocks() { }; assert_eq!( - block_import.import_block(block(), HashMap::new()).await.unwrap(), + block_import.import_block(block()).await.unwrap(), ImportResult::Imported(ImportedAux { needs_justification: true, clear_justification_requests: false, @@ -874,10 +927,7 @@ async fn allows_reimporting_change_blocks() { }), ); - assert_eq!( - block_import.import_block(block(), HashMap::new()).await.unwrap(), - ImportResult::AlreadyInChain - ); + assert_eq!(block_import.import_block(block()).await.unwrap(), ImportResult::AlreadyInChain); } #[tokio::test] @@ -892,16 +942,17 @@ async fn test_bad_justification() { let (mut block_import, ..) = net.make_block_import(client.clone()); let full_client = client.as_client(); - let builder = full_client - .new_block_at(&BlockId::Number(0), Default::default(), false) + let mut builder = full_client + .new_block_at(full_client.chain_info().genesis_hash, Default::default(), false) .unwrap(); - let mut block = builder.build().unwrap().block; add_scheduled_change( - &mut block, + &mut builder, ScheduledChange { next_authorities: make_ids(peers_b), delay: 0 }, ); + let block = builder.build().unwrap().block; + let block = || { let block = block.clone(); let mut import = BlockImportParams::new(BlockOrigin::File, block.header); @@ -913,7 +964,7 @@ async fn test_bad_justification() { }; assert_eq!( - block_import.import_block(block(), HashMap::new()).await.unwrap(), + block_import.import_block(block()).await.unwrap(), ImportResult::Imported(ImportedAux { needs_justification: true, clear_justification_requests: false, @@ -923,10 +974,7 @@ async fn test_bad_justification() { }), ); - assert_eq!( - block_import.import_block(block(), HashMap::new()).await.unwrap(), - ImportResult::AlreadyInChain - ); + assert_eq!(block_import.import_block(block()).await.unwrap(), ImportResult::AlreadyInChain); } #[tokio::test] @@ -951,7 +999,7 @@ async fn voter_persists_its_votes() { let bob_network = { let config = Config { gossip_duration: TEST_GOSSIP_DURATION, - justification_period: 32, + justification_generation_period: 32, keystore: Some(bob_keystore.clone()), name: Some(format!("peer#{}", 1)), local_role: Role::Authority, @@ -970,6 +1018,7 @@ async fn voter_persists_its_votes() { communication::NetworkBridge::new( net.peers[1].network_service().clone(), + net.peers[1].sync_service().clone(), config.clone(), set_state, None, @@ -987,11 +1036,12 @@ async fn voter_persists_its_votes() { let link = net.peers[0].data.lock().take().expect("link initialized at startup; qed"); (net.peers[0].network_service().clone(), link) }; + let sync = net.peers[0].sync_service().clone(); let grandpa_params = GrandpaParams { config: Config { gossip_duration: TEST_GOSSIP_DURATION, - justification_period: 32, + justification_generation_period: 32, keystore: Some(keystore), name: Some(format!("peer#{}", 0)), local_role: Role::Authority, @@ -1001,10 +1051,14 @@ async fn voter_persists_its_votes() { }, link, network: net_service, + sync, voting_rule: VotingRulesBuilder::default().build(), prometheus_registry: None, shared_voter_state: SharedVoterState::empty(), telemetry: None, + offchain_tx_pool_factory: OffchainTransactionPoolFactory::new( + RejectAllTxPool::default(), + ), }; run_grandpa_voter(grandpa_params).expect("all in order with client and network") @@ -1021,6 +1075,7 @@ async fn voter_persists_its_votes() { // the network service of this new peer net.add_authority_peer(); let net_service = net.peers[2].network_service().clone(); + let sync = net.peers[2].sync_service().clone(); // but we'll reuse the client from the first peer (alice_voter1) // since we want to share the same database, so that we can // read the persisted state after aborting alice_voter1. @@ -1032,7 +1087,7 @@ async fn voter_persists_its_votes() { let grandpa_params = GrandpaParams { config: Config { gossip_duration: TEST_GOSSIP_DURATION, - justification_period: 32, + justification_generation_period: 32, keystore: Some(keystore), name: Some(format!("peer#{}", 0)), local_role: Role::Authority, @@ -1042,10 +1097,14 @@ async fn voter_persists_its_votes() { }, link, network: net_service, + sync, voting_rule: VotingRulesBuilder::default().build(), prometheus_registry: None, shared_voter_state: SharedVoterState::empty(), telemetry: None, + offchain_tx_pool_factory: OffchainTransactionPoolFactory::new( + RejectAllTxPool::default(), + ), }; run_grandpa_voter(grandpa_params) @@ -1193,7 +1252,7 @@ async fn finalize_3_voters_1_light_observer() { let observer = observer::run_grandpa_observer( Config { gossip_duration: TEST_GOSSIP_DURATION, - justification_period: 32, + justification_generation_period: 32, keystore: None, name: Some("observer".to_string()), local_role: Role::Full, @@ -1203,6 +1262,7 @@ async fn finalize_3_voters_1_light_observer() { }, net.peers[3].data.lock().take().expect("link initialized at startup; qed"), net.peers[3].network_service().clone(), + net.peers[3].sync_service().clone(), ) .unwrap(); net.peer(0).push_blocks(20, false); @@ -1236,10 +1296,11 @@ async fn voter_catches_up_to_latest_round_when_behind() { link, net: Arc>| -> Pin + Send>> { + let mut net = net.lock(); let grandpa_params = GrandpaParams { config: Config { gossip_duration: TEST_GOSSIP_DURATION, - justification_period: 32, + justification_generation_period: 32, keystore, name: Some(format!("peer#{}", peer_id)), local_role: Role::Authority, @@ -1248,11 +1309,15 @@ async fn voter_catches_up_to_latest_round_when_behind() { protocol_name: grandpa_protocol_name::NAME.into(), }, link, - network: net.lock().peer(peer_id).network_service().clone(), + network: net.peer(peer_id).network_service().clone(), + sync: net.peer(peer_id).sync_service().clone(), voting_rule: (), prometheus_registry: None, shared_voter_state: SharedVoterState::empty(), telemetry: None, + offchain_tx_pool_factory: OffchainTransactionPoolFactory::new( + RejectAllTxPool::default(), + ), }; Box::pin(run_grandpa_voter(grandpa_params).expect("all in order with client and network")) @@ -1330,25 +1395,27 @@ async fn voter_catches_up_to_latest_round_when_behind() { future::select(test, drive_to_completion).await; } -type TestEnvironment = - Environment; +type TestEnvironment = + Environment; -fn test_environment_with_select_chain( +fn test_environment_with_select_chain( link: &TestLinkHalf, - keystore: Option, + keystore: Option, network_service: N, + sync_service: S, select_chain: SC, voting_rule: VR, -) -> TestEnvironment +) -> TestEnvironment where N: NetworkT, + S: SyncingT, VR: VotingRule, { let PersistentData { ref authority_set, ref set_state, .. } = link.persistent_data; let config = Config { gossip_duration: TEST_GOSSIP_DURATION, - justification_period: 32, + justification_generation_period: 32, keystore, name: None, local_role: Role::Authority, @@ -1357,8 +1424,14 @@ where protocol_name: grandpa_protocol_name::NAME.into(), }; - let network = - NetworkBridge::new(network_service.clone(), config.clone(), set_state.clone(), None, None); + let network = NetworkBridge::new( + network_service.clone(), + sync_service, + config.clone(), + set_state.clone(), + None, + None, + ); Environment { authority_set: authority_set.clone(), @@ -1374,23 +1447,27 @@ where justification_sender: None, telemetry: None, _phantom: PhantomData, + offchain_tx_pool_factory: OffchainTransactionPoolFactory::new(RejectAllTxPool::default()), } } -fn test_environment( +fn test_environment( link: &TestLinkHalf, - keystore: Option, + keystore: Option, network_service: N, + sync_service: S, voting_rule: VR, -) -> TestEnvironment, VR> +) -> TestEnvironment, VR> where N: NetworkT, + S: SyncingT, VR: VotingRule, { test_environment_with_select_chain( link, keystore, network_service, + sync_service, link.select_chain.clone(), voting_rule, ) @@ -1406,19 +1483,22 @@ async fn grandpa_environment_respects_voting_rules() { let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0); let peer = net.peer(0); let network_service = peer.network_service().clone(); + let sync_service = peer.sync_service().clone(); let link = peer.data.lock().take().unwrap(); // add 21 blocks let hashes = peer.push_blocks(21, false); // create an environment with no voting rule restrictions - let unrestricted_env = test_environment(&link, None, network_service.clone(), ()); + let unrestricted_env = + test_environment(&link, None, network_service.clone(), sync_service.clone(), ()); // another with 3/4 unfinalized chain voting rule restriction let three_quarters_env = test_environment( &link, None, network_service.clone(), + sync_service.clone(), voting_rule::ThreeQuartersOfTheUnfinalizedChain, ); @@ -1428,6 +1508,7 @@ async fn grandpa_environment_respects_voting_rules() { &link, None, network_service.clone(), + sync_service, VotingRulesBuilder::default().build(), ); @@ -1520,6 +1601,7 @@ async fn grandpa_environment_passes_actual_best_block_to_voting_rules() { let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0); let peer = net.peer(0); let network_service = peer.network_service().clone(); + let sync_service = peer.sync_service().clone(); let link = peer.data.lock().take().unwrap(); let client = peer.client().as_client().clone(); let select_chain = MockSelectChain::default(); @@ -1533,6 +1615,7 @@ async fn grandpa_environment_passes_actual_best_block_to_voting_rules() { &link, None, network_service.clone(), + sync_service, select_chain.clone(), voting_rule::BeforeBestBlockBy(5), ); @@ -1566,16 +1649,117 @@ async fn grandpa_environment_passes_actual_best_block_to_voting_rules() { .1, 10, ); +} - // returning a finality target that's higher than the best block is an - // inconsistent state that should be handled - let hashof42 = client.expect_block_hash_from_id(&BlockId::Number(42)).unwrap(); - select_chain.set_best_chain(client.expect_header(hashof21).unwrap()); - select_chain.set_finality_target(client.expect_header(hashof42).unwrap().hash()); +#[tokio::test] +async fn grandpa_environment_checks_if_best_block_is_descendent_of_finality_target() { + sp_tracing::try_init_simple(); + use finality_grandpa::voter::Environment; + + let peers = &[Ed25519Keyring::Alice]; + let voters = make_ids(peers); + + let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0); + let peer = net.peer(0); + let network_service = peer.network_service().clone(); + let sync_service = peer.sync_service().clone(); + let link = peer.data.lock().take().unwrap(); + let client = peer.client().as_client().clone(); + let select_chain = MockSelectChain::default(); + let voting_rule = AssertBestBlock::default(); + let env = test_environment_with_select_chain( + &link, + None, + network_service.clone(), + sync_service.clone(), + select_chain.clone(), + voting_rule.clone(), + ); + + // create a chain that is 10 blocks long + peer.push_blocks(10, false); + + let hashof5_a = client.expect_block_hash_from_id(&BlockId::Number(5)).unwrap(); + let hashof10_a = client.expect_block_hash_from_id(&BlockId::Number(10)).unwrap(); + + // create a fork starting at block 4 that is 6 blocks long + let fork = peer.generate_blocks_at( + BlockId::Number(4), + 6, + BlockOrigin::File, + |mut builder| { + builder.push_deposit_log_digest_item(DigestItem::Other(vec![1])).unwrap(); + builder.build().unwrap().block + }, + false, + false, + true, + ForkChoiceStrategy::LongestChain, + ); + + let hashof5_b = *fork.first().unwrap(); + let hashof10_b = *fork.last().unwrap(); - assert_matches!( - env.best_chain_containing(peer.client().info().finalized_hash).await, - Err(CommandOrError::Error(Error::Safety(_))) + // returning a finality target that's higher than the best block is inconsistent, + // therefore the best block should be set to be the same block as the target + select_chain.set_best_chain(client.expect_header(hashof5_a).unwrap()); + select_chain.set_finality_target(client.expect_header(hashof10_a).unwrap().hash()); + voting_rule.set_expected_best_block(hashof10_a); + + // the voting rule will internally assert that the best block that was passed was `hashof10_a`, + // instead of the one returned by `SelectChain` + assert_eq!( + env.best_chain_containing(peer.client().info().finalized_hash) + .await + .unwrap() + .unwrap() + .0, + hashof10_a, + ); + + // best block and finality target are blocks at the same height but on different forks, + // we should override the initial best block (#5B) with the target block (#5A) + select_chain.set_best_chain(client.expect_header(hashof5_b).unwrap()); + select_chain.set_finality_target(client.expect_header(hashof5_a).unwrap().hash()); + voting_rule.set_expected_best_block(hashof5_a); + + assert_eq!( + env.best_chain_containing(peer.client().info().finalized_hash) + .await + .unwrap() + .unwrap() + .0, + hashof5_a, + ); + + // best block is higher than finality target but it's on a different fork, + // we should override the initial best block (#5A) with the target block (#5B) + select_chain.set_best_chain(client.expect_header(hashof10_b).unwrap()); + select_chain.set_finality_target(client.expect_header(hashof5_a).unwrap().hash()); + voting_rule.set_expected_best_block(hashof5_a); + + assert_eq!( + env.best_chain_containing(peer.client().info().finalized_hash) + .await + .unwrap() + .unwrap() + .0, + hashof5_a, + ); + + // best block is higher than finality target and it's on the same fork, + // the best block passed to the voting rule should not be overriden + select_chain.set_best_chain(client.expect_header(hashof10_a).unwrap()); + select_chain.set_finality_target(client.expect_header(hashof5_a).unwrap().hash()); + voting_rule.set_expected_best_block(hashof10_a); + + assert_eq!( + env.best_chain_containing(peer.client().info().finalized_hash) + .await + .unwrap() + .unwrap() + .0, + hashof5_a, ); } @@ -1589,10 +1773,12 @@ async fn grandpa_environment_never_overwrites_round_voter_state() { let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0); let peer = net.peer(0); let network_service = peer.network_service().clone(); + let sync_service = peer.sync_service().clone(); let link = peer.data.lock().take().unwrap(); let keystore = create_keystore(peers[0]); - let environment = test_environment(&link, Some(keystore), network_service.clone(), ()); + let environment = + test_environment(&link, Some(keystore), network_service.clone(), sync_service, ()); let round_state = || finality_grandpa::round::State::genesis(Default::default()); let base = || Default::default(); @@ -1670,7 +1856,7 @@ async fn justification_with_equivocation() { let precommit = finality_grandpa::Precommit { target_hash, target_number }; let msg = finality_grandpa::Message::Precommit(precommit.clone()); - let encoded = sp_finality_grandpa::localized_payload(round, set_id, &msg); + let encoded = sp_consensus_grandpa::localized_payload(round, set_id, &msg); let precommit = finality_grandpa::SignedPrecommit { precommit: precommit.clone(), @@ -1723,27 +1909,22 @@ async fn imports_justification_for_regular_blocks_on_import() { let client = net.peer(0).client().clone(); let (mut block_import, ..) = net.make_block_import(client.clone()); - let full_client = client.as_client(); - let builder = full_client - .new_block_at(&BlockId::Number(0), Default::default(), false) - .unwrap(); - let block = builder.build().unwrap().block; - let block_hash = block.hash(); + // create a new block (without importing it) + let generate_block = |parent| { + let builder = full_client.new_block_at(parent, Default::default(), false).unwrap(); + builder.build().unwrap().block + }; // create a valid justification, with one precommit targeting the block - let justification = { - let round = 1; + let make_justification = |round, hash, number| { let set_id = 0; - let precommit = finality_grandpa::Precommit { - target_hash: block_hash, - target_number: *block.header.number(), - }; + let precommit = finality_grandpa::Precommit { target_hash: hash, target_number: number }; let msg = finality_grandpa::Message::Precommit(precommit.clone()); - let encoded = sp_finality_grandpa::localized_payload(round, set_id, &msg); + let encoded = sp_consensus_grandpa::localized_payload(round, set_id, &msg); let signature = peers[0].sign(&encoded[..]).into(); let precommit = finality_grandpa::SignedPrecommit { @@ -1753,33 +1934,59 @@ async fn imports_justification_for_regular_blocks_on_import() { }; let commit = finality_grandpa::Commit { - target_hash: block_hash, - target_number: *block.header.number(), + target_hash: hash, + target_number: number, precommits: vec![precommit], }; GrandpaJustification::from_commit(&full_client, round, commit).unwrap() }; - // we import the block with justification attached - let mut import = BlockImportParams::new(BlockOrigin::File, block.header); - import.justifications = Some((GRANDPA_ENGINE_ID, justification.encode()).into()); - import.body = Some(block.extrinsics); - import.fork_choice = Some(ForkChoiceStrategy::LongestChain); + let mut generate_and_import_block_with_justification = |parent| { + // we import the block with justification attached + let block = generate_block(parent); + let block_hash = block.hash(); + let justification = make_justification(1, block_hash, *block.header.number()); - assert_eq!( - block_import.import_block(import, HashMap::new()).await.unwrap(), - ImportResult::Imported(ImportedAux { - needs_justification: false, - clear_justification_requests: false, - bad_justification: false, - is_new_best: true, - ..Default::default() - }), - ); + let mut import = BlockImportParams::new(BlockOrigin::File, block.header); + import.justifications = Some((GRANDPA_ENGINE_ID, justification.encode()).into()); + import.body = Some(block.extrinsics); + import.fork_choice = Some(ForkChoiceStrategy::LongestChain); + + assert_eq!( + // NOTE: we use `block_on` here because async closures are + // unsupported and it doesn't matter if we block in a test + futures::executor::block_on(block_import.import_block(import)).unwrap(), + ImportResult::Imported(ImportedAux { + needs_justification: false, + clear_justification_requests: false, + bad_justification: false, + is_new_best: true, + ..Default::default() + }), + ); + + block_hash + }; + + let block1 = + generate_and_import_block_with_justification(full_client.chain_info().genesis_hash); // the justification should be imported and available from the client - assert!(client.justifications(block_hash).unwrap().is_some()); + assert!(client.justifications(block1).unwrap().is_some()); + + // subsequent justifications should be ignored and not imported + let mut parent = block1; + for _ in 2..JUSTIFICATION_IMPORT_PERIOD { + parent = generate_and_import_block_with_justification(parent); + assert!(client.justifications(parent).unwrap().is_none()); + } + + let block32 = generate_and_import_block_with_justification(parent); + + // until we reach a block in the next justification import period, at + // which point we should import it + assert!(client.justifications(block32).unwrap().is_some()); } #[tokio::test] @@ -1793,9 +2000,10 @@ async fn grandpa_environment_doesnt_send_equivocation_reports_for_itself() { let mut net = GrandpaTestNet::new(TestApi::new(voters), 1, 0); let peer = net.peer(0); let network_service = peer.network_service().clone(); + let sync_service = peer.sync_service().clone(); let link = peer.data.lock().take().unwrap(); let keystore = create_keystore(alice); - test_environment(&link, Some(keystore), network_service.clone(), ()) + test_environment(&link, Some(keystore), network_service.clone(), sync_service, ()) }; let signed_prevote = { @@ -1818,14 +2026,14 @@ async fn grandpa_environment_doesnt_send_equivocation_reports_for_itself() { // reporting the equivocation should fail since the offender is a local // authority (i.e. we have keys in our keystore for the given id) - let equivocation_proof = sp_finality_grandpa::Equivocation::Prevote(equivocation.clone()); + let equivocation_proof = sp_consensus_grandpa::Equivocation::Prevote(equivocation.clone()); assert!(matches!(environment.report_equivocation(equivocation_proof), Err(Error::Safety(_)))); // if we set the equivocation offender to another id for which we don't have // keys it should work equivocation.identity = TryFrom::try_from(&[1; 32][..]).unwrap(); - let equivocation_proof = sp_finality_grandpa::Equivocation::Prevote(equivocation); - assert!(environment.report_equivocation(equivocation_proof).is_ok()); + let equivocation_proof = sp_consensus_grandpa::Equivocation::Prevote(equivocation); + environment.report_equivocation(equivocation_proof).unwrap(); } #[tokio::test] @@ -1836,13 +2044,12 @@ async fn revert_prunes_authority_changes() { type TestBlockBuilder<'a> = BlockBuilder<'a, Block, PeersFullClient, substrate_test_runtime_client::Backend>; - let edit_block = |builder: TestBlockBuilder| { - let mut block = builder.build().unwrap().block; + let edit_block = |mut builder: TestBlockBuilder| { add_scheduled_change( - &mut block, + &mut builder, ScheduledChange { next_authorities: make_ids(peers), delay: 0 }, ); - block + builder.build().unwrap().block }; let api = TestApi::new(make_ids(peers)); @@ -1884,10 +2091,9 @@ async fn revert_prunes_authority_changes() { BlockId::Number(23), 3, BlockOrigin::File, - |builder| { - let mut block = builder.build().unwrap().block; - block.header.digest_mut().push(DigestItem::Other(vec![1])); - block + |mut builder| { + builder.push_deposit_log_digest_item(DigestItem::Other(vec![1])).unwrap(); + builder.build().unwrap().block }, false, false, @@ -1916,10 +2122,9 @@ async fn revert_prunes_authority_changes() { BlockId::Number(25), 3, BlockOrigin::File, - |builder| { - let mut block = builder.build().unwrap().block; - block.header.digest_mut().push(DigestItem::Other(vec![2])); - block + |mut builder| { + builder.push_deposit_log_digest_item(DigestItem::Other(vec![2])).unwrap(); + builder.build().unwrap().block }, false, false, diff --git a/client/finality-grandpa/src/until_imported.rs b/client/consensus/grandpa/src/until_imported.rs similarity index 99% rename from client/finality-grandpa/src/until_imported.rs rename to client/consensus/grandpa/src/until_imported.rs index 3bca77ae8393..14f32ecc8836 100644 --- a/client/finality-grandpa/src/until_imported.rs +++ b/client/consensus/grandpa/src/until_imported.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -38,7 +38,7 @@ use parking_lot::Mutex; use prometheus_endpoint::{register, Gauge, PrometheusError, Registry, U64}; use sc_client_api::{BlockImportNotification, ImportNotifications}; use sc_utils::mpsc::TracingUnboundedReceiver; -use sp_finality_grandpa::AuthorityId; +use sp_consensus_grandpa::AuthorityId; use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; use std::{ diff --git a/client/finality-grandpa/src/voting_rule.rs b/client/consensus/grandpa/src/voting_rule.rs similarity index 99% rename from client/finality-grandpa/src/voting_rule.rs rename to client/consensus/grandpa/src/voting_rule.rs index 6f15a9cdd349..27a91d547837 100644 --- a/client/finality-grandpa/src/voting_rule.rs +++ b/client/consensus/grandpa/src/voting_rule.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/finality-grandpa/src/warp_proof.rs b/client/consensus/grandpa/src/warp_proof.rs similarity index 94% rename from client/finality-grandpa/src/warp_proof.rs rename to client/consensus/grandpa/src/warp_proof.rs index c65a705d3e84..ec2d25c328bf 100644 --- a/client/finality-grandpa/src/warp_proof.rs +++ b/client/consensus/grandpa/src/warp_proof.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify @@ -25,7 +25,7 @@ use crate::{ use sc_client_api::Backend as ClientBackend; use sc_network_common::sync::warp::{EncodedProof, VerificationResult, WarpSyncProvider}; use sp_blockchain::{Backend as BlockchainBackend, HeaderBackend}; -use sp_finality_grandpa::{AuthorityList, SetId, GRANDPA_ENGINE_ID}; +use sp_consensus_grandpa::{AuthorityList, SetId, GRANDPA_ENGINE_ID}; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Header as HeaderT, NumberFor, One}, @@ -135,11 +135,7 @@ impl WarpSyncProof { let justification = blockchain .justifications(header.hash())? .and_then(|just| just.into_justification(GRANDPA_ENGINE_ID)) - .expect( - "header is last in set and contains standard change signal; \ - must have justification; \ - qed.", - ); + .ok_or_else(|| Error::MissingData)?; let justification = GrandpaJustification::::decode(&mut &justification[..])?; @@ -328,13 +324,12 @@ mod tests { use sc_block_builder::BlockBuilderProvider; use sp_blockchain::HeaderBackend; use sp_consensus::BlockOrigin; - use sp_finality_grandpa::GRANDPA_ENGINE_ID; + use sp_consensus_grandpa::GRANDPA_ENGINE_ID; use sp_keyring::Ed25519Keyring; - use sp_runtime::traits::Header as _; use std::sync::Arc; use substrate_test_runtime_client::{ - ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt, TestClientBuilder, - TestClientBuilderExt, + BlockBuilderExt, ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt, + TestClientBuilder, TestClientBuilderExt, }; #[test] @@ -352,8 +347,7 @@ mod tests { let mut authority_set_changes = Vec::new(); for n in 1..=100 { - let mut block = client.new_block(Default::default()).unwrap().build().unwrap().block; - + let mut builder = client.new_block(Default::default()).unwrap(); let mut new_authorities = None; // we will trigger an authority set change every 10 blocks @@ -373,16 +367,18 @@ mod tests { .collect::>(); let digest = sp_runtime::generic::DigestItem::Consensus( - sp_finality_grandpa::GRANDPA_ENGINE_ID, - sp_finality_grandpa::ConsensusLog::ScheduledChange( - sp_finality_grandpa::ScheduledChange { delay: 0u64, next_authorities }, + sp_consensus_grandpa::GRANDPA_ENGINE_ID, + sp_consensus_grandpa::ConsensusLog::ScheduledChange( + sp_consensus_grandpa::ScheduledChange { delay: 0u64, next_authorities }, ) .encode(), ); - block.header.digest_mut().logs.push(digest); + builder.push_deposit_log_digest_item(digest).unwrap(); } + let block = builder.build().unwrap().block; + futures::executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); if let Some(new_authorities) = new_authorities { @@ -398,7 +394,7 @@ mod tests { let precommit = finality_grandpa::Precommit { target_hash, target_number }; let msg = finality_grandpa::Message::Precommit(precommit.clone()); - let encoded = sp_finality_grandpa::localized_payload(42, current_set_id, &msg); + let encoded = sp_consensus_grandpa::localized_payload(42, current_set_id, &msg); let signature = keyring.sign(&encoded[..]).into(); let precommit = finality_grandpa::SignedPrecommit { diff --git a/client/consensus/manual-seal/Cargo.toml b/client/consensus/manual-seal/Cargo.toml index 19c4b22247e0..4bc389ac3f4c 100644 --- a/client/consensus/manual-seal/Cargo.toml +++ b/client/consensus/manual-seal/Cargo.toml @@ -16,8 +16,9 @@ targets = ["x86_64-unknown-linux-gnu"] jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } assert_matches = "1.3.0" async-trait = "0.1.57" -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" +futures-timer = "3.0.1" log = "0.4.17" serde = { version = "1.0", features = ["derive"] } thiserror = "1.0" @@ -35,10 +36,10 @@ sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/c sp-consensus-aura = { version = "0.10.0-dev", path = "../../../primitives/consensus/aura" } sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-keystore = { version = "0.27.0", path = "../../../primitives/keystore" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } sp-timestamp = { version = "4.0.0-dev", path = "../../../primitives/timestamp" } [dev-dependencies] diff --git a/client/consensus/manual-seal/src/consensus.rs b/client/consensus/manual-seal/src/consensus.rs index a812bb028c7f..b54ec5e41b75 100644 --- a/client/consensus/manual-seal/src/consensus.rs +++ b/client/consensus/manual-seal/src/consensus.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/consensus/manual-seal/src/consensus/aura.rs b/client/consensus/manual-seal/src/consensus/aura.rs index 065b78732cdc..92203f91826f 100644 --- a/client/consensus/manual-seal/src/consensus/aura.rs +++ b/client/consensus/manual-seal/src/consensus/aura.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/consensus/manual-seal/src/consensus/babe.rs b/client/consensus/manual-seal/src/consensus/babe.rs index c5bb3b78fce3..2485bd603e78 100644 --- a/client/consensus/manual-seal/src/consensus/babe.rs +++ b/client/consensus/manual-seal/src/consensus/babe.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -29,13 +29,12 @@ use sc_consensus_babe::{ use sc_consensus_epochs::{ descendent_query, EpochHeader, SharedEpochChanges, ViableEpochDescriptor, }; -use sp_keystore::SyncCryptoStorePtr; +use sp_keystore::KeystorePtr; use std::{marker::PhantomData, sync::Arc}; use sc_consensus::{BlockImportParams, ForkChoiceStrategy, Verifier}; use sp_api::{ProvideRuntimeApi, TransactionFor}; use sp_blockchain::{HeaderBackend, HeaderMetadata}; -use sp_consensus::CacheKeyId; use sp_consensus_babe::{ digests::{NextEpochDescriptor, PreDigest, SecondaryPlainPreDigest}, inherents::BabeInherentData, @@ -54,7 +53,7 @@ use sp_timestamp::TimestampInherentData; /// Intended for use with BABE runtimes. pub struct BabeConsensusDataProvider { /// shared reference to keystore - keystore: SyncCryptoStorePtr, + keystore: KeystorePtr, /// Shared reference to the client. client: Arc, @@ -99,7 +98,7 @@ where async fn verify( &mut self, mut import_params: BlockImportParams, - ) -> Result<(BlockImportParams, Option)>>), String> { + ) -> Result, String> { import_params.finalized = false; import_params.fork_choice = Some(ForkChoiceStrategy::LongestChain); @@ -128,7 +127,7 @@ where import_params .insert_intermediate(INTERMEDIATE_KEY, BabeIntermediate:: { epoch_descriptor }); - Ok((import_params, None)) + Ok(import_params) } } @@ -144,7 +143,7 @@ where { pub fn new( client: Arc, - keystore: SyncCryptoStorePtr, + keystore: KeystorePtr, epoch_changes: SharedEpochChanges, authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, ) -> Result { diff --git a/client/consensus/manual-seal/src/consensus/timestamp.rs b/client/consensus/manual-seal/src/consensus/timestamp.rs index b3c2d780c35d..dbffb2fbba82 100644 --- a/client/consensus/manual-seal/src/consensus/timestamp.rs +++ b/client/consensus/manual-seal/src/consensus/timestamp.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/consensus/manual-seal/src/error.rs b/client/consensus/manual-seal/src/error.rs index a056c541c3ce..eeae1d153e81 100644 --- a/client/consensus/manual-seal/src/error.rs +++ b/client/consensus/manual-seal/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/consensus/manual-seal/src/finalize_block.rs b/client/consensus/manual-seal/src/finalize_block.rs index cee4d59b6d6e..06883b2291bd 100644 --- a/client/consensus/manual-seal/src/finalize_block.rs +++ b/client/consensus/manual-seal/src/finalize_block.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/consensus/manual-seal/src/lib.rs b/client/consensus/manual-seal/src/lib.rs index 5c86382a3ab7..03c9418b5c56 100644 --- a/client/consensus/manual-seal/src/lib.rs +++ b/client/consensus/manual-seal/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -20,17 +20,22 @@ //! This is suitable for a testing environment. use futures::prelude::*; +use futures_timer::Delay; use prometheus_endpoint::Registry; -use sc_client_api::backend::{Backend as ClientBackend, Finalizer}; +use sc_client_api::{ + backend::{Backend as ClientBackend, Finalizer}, + client::BlockchainEvents, +}; use sc_consensus::{ block_import::{BlockImport, BlockImportParams, ForkChoiceStrategy}, import_queue::{BasicQueue, BoxBlockImport, Verifier}, }; use sp_blockchain::HeaderBackend; -use sp_consensus::{CacheKeyId, Environment, Proposer, SelectChain}; +use sp_consensus::{Environment, Proposer, SelectChain}; +use sp_core::traits::SpawnNamed; use sp_inherents::CreateInherentDataProviders; use sp_runtime::{traits::Block as BlockT, ConsensusEngineId}; -use std::{marker::PhantomData, sync::Arc}; +use std::{marker::PhantomData, sync::Arc, time::Duration}; mod error; mod finalize_block; @@ -62,10 +67,10 @@ impl Verifier for ManualSealVerifier { async fn verify( &mut self, mut block: BlockImportParams, - ) -> Result<(BlockImportParams, Option)>>), String> { + ) -> Result, String> { block.finalized = false; block.fork_choice = Some(ForkChoiceStrategy::LongestChain); - Ok((block, None)) + Ok(block) } } @@ -84,7 +89,7 @@ where /// Params required to start the instant sealing authorship task. pub struct ManualSealParams, TP, SC, CS, CIDP, P> { - /// Block import instance for well. importing blocks. + /// Block import instance. pub block_import: BI, /// The environment we are producing blocks for. @@ -136,7 +141,19 @@ pub struct InstantSealParams, TP, SC, pub create_inherent_data_providers: CIDP, } -/// Creates the background authorship task for the manual seal engine. +/// Params required to start the delayed finalization task. +pub struct DelayedFinalizeParams { + /// Block import instance. + pub client: Arc, + + /// Handle for spawning delayed finalization tasks. + pub spawn_handle: S, + + /// The delay in seconds before a block is finalized. + pub delay_sec: u64, +} + +/// Creates the background authorship task for the manually seal engine. pub async fn run_manual_seal( ManualSealParams { mut block_import, @@ -303,6 +320,44 @@ pub async fn run_instant_seal_and_finalize( .await } +/// Creates a future for delayed finalization of manual sealed blocks. +/// +/// The future needs to be spawned in the background alongside the +/// [`run_manual_seal`]/[`run_instant_seal`] future. It is required that +/// [`EngineCommand::SealNewBlock`] is send with `finalize = false` to not finalize blocks directly +/// after building them. This also means that delayed finality can not be used with +/// [`run_instant_seal_and_finalize`]. +pub async fn run_delayed_finalize( + DelayedFinalizeParams { client, spawn_handle, delay_sec }: DelayedFinalizeParams, +) where + B: BlockT + 'static, + CB: ClientBackend + 'static, + C: HeaderBackend + Finalizer + ProvideRuntimeApi + BlockchainEvents + 'static, + S: SpawnNamed, +{ + let mut block_import_stream = client.import_notification_stream(); + + while let Some(notification) = block_import_stream.next().await { + let delay = Delay::new(Duration::from_secs(delay_sec)); + let cloned_client = client.clone(); + spawn_handle.spawn( + "delayed-finalize", + None, + Box::pin(async move { + delay.await; + finalize_block(FinalizeBlockParams { + hash: notification.hash, + sender: None, + justification: None, + finalizer: cloned_client, + _phantom: PhantomData, + }) + .await + }), + ); + } +} + #[cfg(test)] mod tests { use super::*; @@ -428,6 +483,101 @@ mod tests { assert_eq!(client.header(created_block.hash).unwrap().unwrap().number, 1) } + #[tokio::test] + async fn instant_seal_delayed_finalize() { + let builder = TestClientBuilder::new(); + let (client, select_chain) = builder.build_with_longest_chain(); + let client = Arc::new(client); + let spawner = sp_core::testing::TaskExecutor::new(); + let genesis_hash = client.info().genesis_hash; + let pool = Arc::new(BasicPool::with_revalidation_type( + Options::default(), + true.into(), + api(), + None, + RevalidationType::Full, + spawner.clone(), + 0, + genesis_hash, + genesis_hash, + )); + let env = ProposerFactory::new(spawner.clone(), client.clone(), pool.clone(), None, None); + // this test checks that blocks are created as soon as transactions are imported into the + // pool. + let (sender, receiver) = futures::channel::oneshot::channel(); + let mut sender = Arc::new(Some(sender)); + let commands_stream = + pool.pool().validated_pool().import_notification_stream().map(move |_| { + // we're only going to submit one tx so this fn will only be called once. + let mut_sender = Arc::get_mut(&mut sender).unwrap(); + let sender = std::mem::take(mut_sender); + EngineCommand::SealNewBlock { + create_empty: false, + // set to `false`, expecting to be finalized by delayed finalize + finalize: false, + parent_hash: None, + sender, + } + }); + + let future_instant_seal = run_manual_seal(ManualSealParams { + block_import: client.clone(), + commands_stream, + env, + client: client.clone(), + pool: pool.clone(), + select_chain, + create_inherent_data_providers: |_, _| async { Ok(()) }, + consensus_data_provider: None, + }); + std::thread::spawn(|| { + let rt = tokio::runtime::Runtime::new().unwrap(); + // spawn the background authorship task + rt.block_on(future_instant_seal); + }); + + let delay_sec = 5; + let future_delayed_finalize = run_delayed_finalize(DelayedFinalizeParams { + client: client.clone(), + delay_sec, + spawn_handle: spawner, + }); + std::thread::spawn(|| { + let rt = tokio::runtime::Runtime::new().unwrap(); + // spawn the background authorship task + rt.block_on(future_delayed_finalize); + }); + + let mut finality_stream = client.finality_notification_stream(); + // submit a transaction to pool. + let result = pool.submit_one(&BlockId::Number(0), SOURCE, uxt(Alice, 0)).await; + // assert that it was successfully imported + assert!(result.is_ok()); + // assert that the background task returns ok + let created_block = receiver.await.unwrap().unwrap(); + assert_eq!( + created_block, + CreatedBlock { + hash: created_block.hash, + aux: ImportedAux { + header_only: false, + clear_justification_requests: false, + needs_justification: false, + bad_justification: false, + is_new_best: true, + } + } + ); + // assert that there's a new block in the db. + assert!(client.header(created_block.hash).unwrap().is_some()); + assert_eq!(client.header(created_block.hash).unwrap().unwrap().number, 1); + + assert_eq!(client.info().finalized_hash, client.info().genesis_hash); + + let finalized = finality_stream.select_next_some().await; + assert_eq!(finalized.hash, created_block.hash); + } + #[tokio::test] async fn manual_seal_and_finalization() { let builder = TestClientBuilder::new(); diff --git a/client/consensus/manual-seal/src/rpc.rs b/client/consensus/manual-seal/src/rpc.rs index b9bb06551f81..85abcdc08574 100644 --- a/client/consensus/manual-seal/src/rpc.rs +++ b/client/consensus/manual-seal/src/rpc.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -160,10 +160,11 @@ pub fn send_result( } } } else { - // instant seal doesn't report errors over rpc, simply log them. + // Sealing/Finalization with no RPC sender such as instant seal or delayed finalize doesn't + // report errors over rpc, simply log them. match result { - Ok(r) => log::info!("Instant Seal success: {:?}", r), - Err(e) => log::error!("Instant Seal encountered an error: {}", e), + Ok(r) => log::info!("Consensus with no RPC sender success: {:?}", r), + Err(e) => log::error!("Consensus with no RPC sender encountered an error: {}", e), } } } diff --git a/client/consensus/manual-seal/src/seal_block.rs b/client/consensus/manual-seal/src/seal_block.rs index 2cafdae503dc..e6133bccae88 100644 --- a/client/consensus/manual-seal/src/seal_block.rs +++ b/client/consensus/manual-seal/src/seal_block.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -27,7 +27,7 @@ use sp_blockchain::HeaderBackend; use sp_consensus::{self, BlockOrigin, Environment, Proposer, SelectChain}; use sp_inherents::{CreateInherentDataProviders, InherentDataProvider}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; -use std::{collections::HashMap, sync::Arc, time::Duration}; +use std::{sync::Arc, time::Duration}; /// max duration for creating a proposal in secs pub const MAX_PROPOSAL_DURATION: u64 = 10; @@ -153,7 +153,7 @@ pub async fn seal_block( let mut post_header = header.clone(); post_header.digest_mut().logs.extend(params.post_digests.iter().cloned()); - match block_import.import_block(params, HashMap::new()).await? { + match block_import.import_block(params).await? { ImportResult::Imported(aux) => Ok(CreatedBlock { hash: ::Header::hash(&post_header), aux }), other => Err(other.into()), diff --git a/client/consensus/pow/Cargo.toml b/client/consensus/pow/Cargo.toml index b5454e35f994..91c975450859 100644 --- a/client/consensus/pow/Cargo.toml +++ b/client/consensus/pow/Cargo.toml @@ -14,7 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = "0.1.57" -codec = { package = "parity-scale-codec", version = "3.2.2", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" futures-timer = "3.0.1" log = "0.4.17" @@ -28,6 +28,6 @@ sp-block-builder = { version = "4.0.0-dev", path = "../../../primitives/block-bu sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-consensus-pow = { version = "0.10.0-dev", path = "../../../primitives/consensus/pow" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } diff --git a/client/consensus/pow/src/lib.rs b/client/consensus/pow/src/lib.rs index ace00a34459a..763cf10e6cd4 100644 --- a/client/consensus/pow/src/lib.rs +++ b/client/consensus/pow/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -55,17 +55,16 @@ use sc_consensus::{ }; use sp_api::ProvideRuntimeApi; use sp_block_builder::BlockBuilder as BlockBuilderApi; -use sp_blockchain::{well_known_cache_keys::Id as CacheKeyId, HeaderBackend}; +use sp_blockchain::HeaderBackend; use sp_consensus::{Environment, Error as ConsensusError, Proposer, SelectChain, SyncOracle}; use sp_consensus_pow::{Seal, TotalDifficulty, POW_ENGINE_ID}; -use sp_core::ExecutionContext; use sp_inherents::{CreateInherentDataProviders, InherentDataProvider}; use sp_runtime::{ generic::{BlockId, Digest, DigestItem}, traits::{Block as BlockT, Header as HeaderT}, RuntimeString, }; -use std::{cmp::Ordering, collections::HashMap, marker::PhantomData, sync::Arc, time::Duration}; +use std::{cmp::Ordering, marker::PhantomData, sync::Arc, time::Duration}; const LOG_TARGET: &str = "pow"; @@ -267,9 +266,8 @@ where async fn check_inherents( &self, block: B, - block_id: BlockId, + at_hash: B::Hash, inherent_data_providers: CIDP::InherentDataProviders, - execution_context: ExecutionContext, ) -> Result<(), Error> { if *block.header().number() < self.check_inherents_after { return Ok(()) @@ -283,7 +281,7 @@ where let inherent_res = self .client .runtime_api() - .check_inherents_with_context(&block_id, execution_context, block, inherent_data) + .check_inherents(at_hash, block, inherent_data) .map_err(|e| Error::Client(e.into()))?; if !inherent_res.ok() { @@ -325,13 +323,13 @@ where async fn import_block( &mut self, mut block: BlockImportParams, - new_cache: HashMap>, ) -> Result { let best_header = self .select_chain .best_chain() .await - .map_err(|e| format!("Fetch best chain failed via select chain: {}", e))?; + .map_err(|e| format!("Fetch best chain failed via select chain: {}", e)) + .map_err(ConsensusError::ChainLookup)?; let best_hash = best_header.hash(); let parent_hash = *block.header.parent_hash(); @@ -344,11 +342,10 @@ where if !block.state_action.skip_execution_checks() { self.check_inherents( check_block.clone(), - BlockId::Hash(parent_hash), + parent_hash, self.create_inherent_data_providers .create_inherent_data_providers(parent_hash, ()) .await?, - block.origin.into(), ) .await?; } @@ -398,7 +395,7 @@ where )); } - self.inner.import_block(block, new_cache).await.map_err(Into::into) + self.inner.import_block(block).await.map_err(Into::into) } } @@ -448,7 +445,7 @@ where async fn verify( &mut self, mut block: BlockImportParams, - ) -> Result<(BlockImportParams, Option)>>), String> { + ) -> Result, String> { let hash = block.header.hash(); let (checked_header, seal) = self.check_header(block.header)?; @@ -458,7 +455,7 @@ where block.insert_intermediate(INTERMEDIATE_KEY, intermediate); block.post_hash = Some(hash); - Ok((block, None)) + Ok(block) } } diff --git a/client/consensus/pow/src/worker.rs b/client/consensus/pow/src/worker.rs index b53227bb3ca5..3cb5dfcc0926 100644 --- a/client/consensus/pow/src/worker.rs +++ b/client/consensus/pow/src/worker.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -32,7 +32,6 @@ use sp_runtime::{ DigestItem, }; use std::{ - collections::HashMap, pin::Pin, sync::{ atomic::{AtomicUsize, Ordering}, @@ -203,7 +202,7 @@ where let header = import_block.post_header(); let mut block_import = self.block_import.lock(); - match block_import.import_block(import_block, HashMap::default()).await { + match block_import.import_block(import_block).await { Ok(res) => { res.handle_justification( &header.hash(), diff --git a/client/consensus/slots/Cargo.toml b/client/consensus/slots/Cargo.toml index 5cacf4f47628..67eeae5317ab 100644 --- a/client/consensus/slots/Cargo.toml +++ b/client/consensus/slots/Cargo.toml @@ -15,21 +15,21 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = "0.1.57" -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" futures-timer = "3.0.1" log = "0.4.17" sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } sc-telemetry = { version = "4.0.0-dev", path = "../../telemetry" } -sp-arithmetic = { version = "6.0.0", path = "../../../primitives/arithmetic" } +sp-arithmetic = { version = "16.0.0", path = "../../../primitives/arithmetic" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } sp-consensus-slots = { version = "0.10.0-dev", path = "../../../primitives/consensus/slots" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } -sp-state-machine = { version = "0.13.0", path = "../../../primitives/state-machine" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } +sp-state-machine = { version = "0.28.0", path = "../../../primitives/state-machine" } [dev-dependencies] substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/client/consensus/slots/build.rs b/client/consensus/slots/build.rs index b700b28e322c..a68cb706e8fb 100644 --- a/client/consensus/slots/build.rs +++ b/client/consensus/slots/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/consensus/slots/src/aux_schema.rs b/client/consensus/slots/src/aux_schema.rs index c1d01500ffe4..9c6bc0ad0cdc 100644 --- a/client/consensus/slots/src/aux_schema.rs +++ b/client/consensus/slots/src/aux_schema.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/consensus/slots/src/lib.rs b/client/consensus/slots/src/lib.rs index 30d2c1761c71..5057e7858a0b 100644 --- a/client/consensus/slots/src/lib.rs +++ b/client/consensus/slots/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -150,7 +150,7 @@ pub trait SimpleSlotWorker { body: Vec, storage_changes: StorageChanges<>::Transaction, B>, public: Self::Claim, - epoch: Self::AuxData, + aux_data: Self::AuxData, ) -> Result< sc_consensus::BlockImportParams>::Transaction>, sp_consensus::Error, @@ -190,7 +190,7 @@ pub trait SimpleSlotWorker { proposer: Self::Proposer, claim: &Self::Claim, slot_info: SlotInfo, - proposing_remaining: Delay, + end_proposing_at: Instant, ) -> Option< Proposal< B, @@ -202,9 +202,11 @@ pub trait SimpleSlotWorker { let telemetry = self.telemetry(); let log_target = self.logging_target(); - let inherent_data = Self::create_inherent_data(&slot_info, &log_target).await?; + let inherent_data = + Self::create_inherent_data(&slot_info, &log_target, end_proposing_at).await?; - let proposing_remaining_duration = self.proposing_remaining_duration(&slot_info); + let proposing_remaining_duration = + end_proposing_at.saturating_duration_since(Instant::now()); let logs = self.pre_digest_data(slot, claim); // deadline our production to 98% of the total time left for proposing. As we deadline @@ -215,11 +217,16 @@ pub trait SimpleSlotWorker { inherent_data, sp_runtime::generic::Digest { logs }, proposing_remaining_duration.mul_f32(0.98), - None, + slot_info.block_size_limit, ) .map_err(|e| sp_consensus::Error::ClientImport(e.to_string())); - let proposal = match futures::future::select(proposing, proposing_remaining).await { + let proposal = match futures::future::select( + proposing, + Delay::new(proposing_remaining_duration), + ) + .await + { Either::Left((Ok(p), _)) => p, Either::Left((Err(err), _)) => { warn!(target: log_target, "Proposing failed: {}", err); @@ -255,8 +262,9 @@ pub trait SimpleSlotWorker { async fn create_inherent_data( slot_info: &SlotInfo, logging_target: &str, + end_proposing_at: Instant, ) -> Option { - let remaining_duration = slot_info.ends_at.saturating_duration_since(Instant::now()); + let remaining_duration = end_proposing_at.saturating_duration_since(Instant::now()); let delay = Delay::new(remaining_duration); let cid = slot_info.create_inherent_data.create_inherent_data(); let inherent_data = match futures::future::select(delay, cid).await { @@ -300,7 +308,7 @@ pub trait SimpleSlotWorker { let proposing_remaining_duration = self.proposing_remaining_duration(&slot_info); - let proposing_remaining = if proposing_remaining_duration == Duration::default() { + let end_proposing_at = if proposing_remaining_duration == Duration::default() { debug!( target: logging_target, "Skipping proposal slot {} since there's no time left to propose", slot, @@ -308,7 +316,7 @@ pub trait SimpleSlotWorker { return None } else { - Delay::new(proposing_remaining_duration) + Instant::now() + proposing_remaining_duration }; let aux_data = match self.aux_data(&slot_info.chain_head, slot) { @@ -379,7 +387,7 @@ pub trait SimpleSlotWorker { }, }; - let proposal = self.propose(proposer, &claim, slot_info, proposing_remaining).await?; + let proposal = self.propose(proposer, &claim, slot_info, end_proposing_at).await?; let (block, storage_proof) = (proposal.block, proposal.proof); let (header, body) = block.deconstruct(); @@ -424,7 +432,7 @@ pub trait SimpleSlotWorker { ); let header = block_import_params.post_header(); - match self.block_import().import_block(block_import_params, Default::default()).await { + match self.block_import().import_block(block_import_params).await { Ok(res) => { res.handle_justification( &header.hash(), diff --git a/client/consensus/slots/src/slots.rs b/client/consensus/slots/src/slots.rs index 7bb263b2bdba..203764310601 100644 --- a/client/consensus/slots/src/slots.rs +++ b/client/consensus/slots/src/slots.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/consensus/uncles/Cargo.toml b/client/consensus/uncles/Cargo.toml deleted file mode 100644 index 0be48659f9c7..000000000000 --- a/client/consensus/uncles/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "sc-consensus-uncles" -version = "0.10.0-dev" -authors = ["Parity Technologies "] -description = "Generic uncle inclusion utilities for consensus" -edition = "2021" -license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.io" -repository = "https://github.com/paritytech/substrate/" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -thiserror = "1.0.30" -sc-client-api = { version = "4.0.0-dev", path = "../../api" } -sp-authorship = { version = "4.0.0-dev", path = "../../../primitives/authorship" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } diff --git a/client/consensus/uncles/README.md b/client/consensus/uncles/README.md deleted file mode 100644 index 1b6fed5b9772..000000000000 --- a/client/consensus/uncles/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Uncles functionality for Substrate. - -License: GPL-3.0-or-later WITH Classpath-exception-2.0 \ No newline at end of file diff --git a/client/consensus/uncles/src/lib.rs b/client/consensus/uncles/src/lib.rs deleted file mode 100644 index d03c2f8aa6b4..000000000000 --- a/client/consensus/uncles/src/lib.rs +++ /dev/null @@ -1,45 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. -// 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 . - -//! Uncles functionality for Substrate. - -use sc_client_api::ProvideUncles; -use sp_runtime::{generic::BlockId, traits::Block as BlockT}; - -#[derive(Debug, thiserror::Error)] -pub enum Error { - #[error("Could not retrieve the block hash for block id: {0:?}")] - NoHashForBlockId(BlockId), -} - -/// Maximum uncles generations we may provide to the runtime. -const MAX_UNCLE_GENERATIONS: u32 = 8; - -/// Create a new [`sp_authorship::InherentDataProvider`] at the given block. -pub fn create_uncles_inherent_data_provider( - client: &C, - parent: B::Hash, -) -> Result, sc_client_api::blockchain::Error> -where - B: BlockT, - C: ProvideUncles, -{ - let uncles = client.uncles(parent, MAX_UNCLE_GENERATIONS.into())?; - - Ok(sp_authorship::InherentDataProvider::new(uncles)) -} diff --git a/client/db/Cargo.toml b/client/db/Cargo.toml index a9f5e28c7315..a038cf802a7a 100644 --- a/client/db/Cargo.toml +++ b/client/db/Cargo.toml @@ -13,37 +13,38 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", features = [ "derive", ] } -hash-db = "0.15.2" +hash-db = "0.16.0" kvdb = "0.13.0" kvdb-memorydb = "0.13.0" -kvdb-rocksdb = { version = "0.17.0", optional = true } +kvdb-rocksdb = { version = "0.19.0", optional = true } linked-hash-map = "0.5.4" log = "0.4.17" -parity-db = "0.4.3" +parity-db = "0.4.8" parking_lot = "0.12.1" sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-state-db = { version = "0.10.0-dev", path = "../state-db" } schnellru = "0.2.1" -sp-arithmetic = { version = "6.0.0", path = "../../primitives/arithmetic" } +sp-arithmetic = { version = "16.0.0", path = "../../primitives/arithmetic" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } sp-database = { version = "4.0.0-dev", path = "../../primitives/database" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } -sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } -sp-trie = { version = "7.0.0", path = "../../primitives/trie" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } +sp-state-machine = { version = "0.28.0", path = "../../primitives/state-machine" } +sp-trie = { version = "22.0.0", path = "../../primitives/trie" } [dev-dependencies] -criterion = "0.3.3" -kvdb-rocksdb = "0.17.0" +criterion = "0.4.0" +kvdb-rocksdb = "0.19.0" rand = "0.8.5" tempfile = "3.1.0" quickcheck = { version = "1.0.3", default-features = false } kitchensink-runtime = { path = "../../bin/node/runtime" } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } +array-bytes = "6.1" [features] default = [] diff --git a/client/db/benches/state_access.rs b/client/db/benches/state_access.rs index bab79fe7c90d..e47559e710df 100644 --- a/client/db/benches/state_access.rs +++ b/client/db/benches/state_access.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/db/src/bench.rs b/client/db/src/bench.rs index e53209f27706..5bec03cb1088 100644 --- a/client/db/src/bench.rs +++ b/client/db/src/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -22,6 +22,7 @@ use crate::{DbState, DbStateBuilder}; use hash_db::{Hasher, Prefix}; use kvdb::{DBTransaction, KeyValueDB}; use linked_hash_map::LinkedHashMap; +use parking_lot::Mutex; use sp_core::{ hexdisplay::HexDisplay, storage::{ChildInfo, TrackedStorageKey}, @@ -31,7 +32,8 @@ use sp_runtime::{ StateVersion, Storage, }; use sp_state_machine::{ - backend::Backend as StateBackend, ChildStorageCollection, DBValue, StorageCollection, + backend::Backend as StateBackend, ChildStorageCollection, DBValue, IterArgs, StorageCollection, + StorageIterator, StorageKey, StorageValue, }; use sp_trie::{ cache::{CacheSize, SharedTrieCache}, @@ -59,6 +61,19 @@ impl sp_state_machine::Storage> for StorageDb, TrackedStorageKey>, + /// Key tracker for keys in a child trie. + /// Child trie are identified by their storage key (i.e. `ChildInfo::storage_key()`) + /// We track the total number of reads and writes to these keys, + /// not de-duplicated for repeats. + child_keys: LinkedHashMap, LinkedHashMap, TrackedStorageKey>>, +} + /// State that manages the backend database reference. Allows runtime to control the database. pub struct BenchmarkingState { root: Cell, @@ -67,22 +82,52 @@ pub struct BenchmarkingState { db: Cell>>, genesis: HashMap, (Vec, i32)>, record: Cell>>, - /// Key tracker for keys in the main trie. - /// We track the total number of reads and writes to these keys, - /// not de-duplicated for repeats. - main_key_tracker: RefCell, TrackedStorageKey>>, - /// Key tracker for keys in a child trie. - /// Child trie are identified by their storage key (i.e. `ChildInfo::storage_key()`) - /// We track the total number of reads and writes to these keys, - /// not de-duplicated for repeats. - child_key_tracker: RefCell, LinkedHashMap, TrackedStorageKey>>>, + key_tracker: Arc>, whitelist: RefCell>, proof_recorder: Option>>, proof_recorder_root: Cell, - enable_tracking: bool, shared_trie_cache: SharedTrieCache>, } +/// A raw iterator over the `BenchmarkingState`. +pub struct RawIter { + inner: as StateBackend>>::RawIter, + child_trie: Option>, + key_tracker: Arc>, +} + +impl StorageIterator> for RawIter { + type Backend = BenchmarkingState; + type Error = String; + + fn next_key(&mut self, backend: &Self::Backend) -> Option> { + match self.inner.next_key(backend.state.borrow().as_ref()?) { + Some(Ok(key)) => { + self.key_tracker.lock().add_read_key(self.child_trie.as_deref(), &key); + Some(Ok(key)) + }, + result => result, + } + } + + fn next_pair( + &mut self, + backend: &Self::Backend, + ) -> Option> { + match self.inner.next_pair(backend.state.borrow().as_ref()?) { + Some(Ok((key, value))) => { + self.key_tracker.lock().add_read_key(self.child_trie.as_deref(), &key); + Some(Ok((key, value))) + }, + result => result, + } + } + + fn was_complete(&self) -> bool { + self.inner.was_complete() + } +} + impl BenchmarkingState { /// Create a new instance that creates a database in a temporary dir. pub fn new( @@ -103,12 +148,14 @@ impl BenchmarkingState { genesis: Default::default(), genesis_root: Default::default(), record: Default::default(), - main_key_tracker: Default::default(), - child_key_tracker: Default::default(), + key_tracker: Arc::new(Mutex::new(KeyTracker { + main_keys: Default::default(), + child_keys: Default::default(), + enable_tracking, + })), whitelist: Default::default(), proof_recorder: record_proof.then(Default::default), proof_recorder_root: Cell::new(root), - enable_tracking, // Enable the cache, but do not sync anything to the shared state. shared_trie_cache: SharedTrieCache::new(CacheSize::new(0)), }; @@ -123,7 +170,7 @@ impl BenchmarkingState { ) }); let (root, transaction): (B::Hash, _) = - state.state.borrow_mut().as_mut().unwrap().full_storage_root( + state.state.borrow().as_ref().unwrap().full_storage_root( genesis.top.iter().map(|(k, v)| (k.as_ref(), Some(v.as_ref()))), child_delta, state_version, @@ -157,36 +204,51 @@ impl BenchmarkingState { } fn add_whitelist_to_tracker(&self) { - let mut main_key_tracker = self.main_key_tracker.borrow_mut(); + self.key_tracker.lock().add_whitelist(&self.whitelist.borrow()); + } + + fn wipe_tracker(&self) { + let mut key_tracker = self.key_tracker.lock(); + key_tracker.main_keys = LinkedHashMap::new(); + key_tracker.child_keys = LinkedHashMap::new(); + key_tracker.add_whitelist(&self.whitelist.borrow()); + } + + fn add_read_key(&self, childtrie: Option<&[u8]>, key: &[u8]) { + self.key_tracker.lock().add_read_key(childtrie, key); + } + + fn add_write_key(&self, childtrie: Option<&[u8]>, key: &[u8]) { + self.key_tracker.lock().add_write_key(childtrie, key); + } - let whitelist = self.whitelist.borrow(); + fn all_trackers(&self) -> Vec { + self.key_tracker.lock().all_trackers() + } +} +impl KeyTracker { + fn add_whitelist(&mut self, whitelist: &[TrackedStorageKey]) { whitelist.iter().for_each(|key| { let mut whitelisted = TrackedStorageKey::new(key.key.clone()); whitelisted.whitelist(); - main_key_tracker.insert(key.key.clone(), whitelisted); + self.main_keys.insert(key.key.clone(), whitelisted); }); } - fn wipe_tracker(&self) { - *self.main_key_tracker.borrow_mut() = LinkedHashMap::new(); - *self.child_key_tracker.borrow_mut() = LinkedHashMap::new(); - self.add_whitelist_to_tracker(); - } - // Childtrie is identified by its storage key (i.e. `ChildInfo::storage_key`) - fn add_read_key(&self, childtrie: Option<&[u8]>, key: &[u8]) { + fn add_read_key(&mut self, childtrie: Option<&[u8]>, key: &[u8]) { if !self.enable_tracking { return } - let mut child_key_tracker = self.child_key_tracker.borrow_mut(); - let mut main_key_tracker = self.main_key_tracker.borrow_mut(); + let child_key_tracker = &mut self.child_keys; + let main_key_tracker = &mut self.main_keys; let key_tracker = if let Some(childtrie) = childtrie { child_key_tracker.entry(childtrie.to_vec()).or_insert_with(LinkedHashMap::new) } else { - &mut main_key_tracker + main_key_tracker }; let should_log = match key_tracker.get_mut(key) { @@ -216,18 +278,18 @@ impl BenchmarkingState { } // Childtrie is identified by its storage key (i.e. `ChildInfo::storage_key`) - fn add_write_key(&self, childtrie: Option<&[u8]>, key: &[u8]) { + fn add_write_key(&mut self, childtrie: Option<&[u8]>, key: &[u8]) { if !self.enable_tracking { return } - let mut child_key_tracker = self.child_key_tracker.borrow_mut(); - let mut main_key_tracker = self.main_key_tracker.borrow_mut(); + let child_key_tracker = &mut self.child_keys; + let main_key_tracker = &mut self.main_keys; let key_tracker = if let Some(childtrie) = childtrie { child_key_tracker.entry(childtrie.to_vec()).or_insert_with(LinkedHashMap::new) } else { - &mut main_key_tracker + main_key_tracker }; // If we have written to the key, we also consider that we have read from it. @@ -261,11 +323,11 @@ impl BenchmarkingState { fn all_trackers(&self) -> Vec { let mut all_trackers = Vec::new(); - self.main_key_tracker.borrow().iter().for_each(|(_, tracker)| { + self.main_keys.iter().for_each(|(_, tracker)| { all_trackers.push(tracker.clone()); }); - self.child_key_tracker.borrow().iter().for_each(|(_, child_tracker)| { + self.child_keys.iter().for_each(|(_, child_tracker)| { child_tracker.iter().for_each(|(_, tracker)| { all_trackers.push(tracker.clone()); }); @@ -283,6 +345,7 @@ impl StateBackend> for BenchmarkingState { type Error = as StateBackend>>::Error; type Transaction = as StateBackend>>::Transaction; type TrieBackendStorage = as StateBackend>>::TrieBackendStorage; + type RawIter = RawIter; fn storage(&self, key: &[u8]) -> Result>, Self::Error> { self.add_read_key(None, key); @@ -356,66 +419,11 @@ impl StateBackend> for BenchmarkingState { .next_child_storage_key(child_info, key) } - fn for_keys_with_prefix(&self, prefix: &[u8], f: F) { - if let Some(ref state) = *self.state.borrow() { - state.for_keys_with_prefix(prefix, f) - } - } - - fn for_key_values_with_prefix(&self, prefix: &[u8], f: F) { - if let Some(ref state) = *self.state.borrow() { - state.for_key_values_with_prefix(prefix, f) - } - } - - fn apply_to_key_values_while, Vec) -> bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - start_at: Option<&[u8]>, - f: F, - allow_missing: bool, - ) -> Result { - self.state.borrow().as_ref().ok_or_else(state_err)?.apply_to_key_values_while( - child_info, - prefix, - start_at, - f, - allow_missing, - ) - } - - fn apply_to_keys_while bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - start_at: Option<&[u8]>, - f: F, - ) { - if let Some(ref state) = *self.state.borrow() { - state.apply_to_keys_while(child_info, prefix, start_at, f) - } - } - - fn for_child_keys_with_prefix( - &self, - child_info: &ChildInfo, - prefix: &[u8], - f: F, - ) { - if let Some(ref state) = *self.state.borrow() { - state.for_child_keys_with_prefix(child_info, prefix, f) - } - } - fn storage_root<'a>( &self, delta: impl Iterator)>, state_version: StateVersion, - ) -> (B::Hash, Self::Transaction) - where - B::Hash: Ord, - { + ) -> (B::Hash, Self::Transaction) { self.state .borrow() .as_ref() @@ -427,29 +435,26 @@ impl StateBackend> for BenchmarkingState { child_info: &ChildInfo, delta: impl Iterator)>, state_version: StateVersion, - ) -> (B::Hash, bool, Self::Transaction) - where - B::Hash: Ord, - { + ) -> (B::Hash, bool, Self::Transaction) { self.state .borrow() .as_ref() .map_or(Default::default(), |s| s.child_storage_root(child_info, delta, state_version)) } - fn pairs(&self) -> Vec<(Vec, Vec)> { - self.state.borrow().as_ref().map_or(Default::default(), |s| s.pairs()) - } - - fn keys(&self, prefix: &[u8]) -> Vec> { - self.state.borrow().as_ref().map_or(Default::default(), |s| s.keys(prefix)) - } - - fn child_keys(&self, child_info: &ChildInfo, prefix: &[u8]) -> Vec> { + fn raw_iter(&self, args: IterArgs) -> Result { + let child_trie = + args.child_info.as_ref().map(|child_info| child_info.storage_key().to_vec()); self.state .borrow() .as_ref() - .map_or(Default::default(), |s| s.child_keys(child_info, prefix)) + .map(|s| s.raw_iter(args)) + .unwrap_or(Ok(Default::default())) + .map(|raw_iter| RawIter { + inner: raw_iter, + key_tracker: self.key_tracker.clone(), + child_trie, + }) } fn commit( @@ -587,7 +592,7 @@ impl StateBackend> for BenchmarkingState { } fn register_overlay_stats(&self, stats: &sp_state_machine::StateMachineStats) { - self.state.borrow_mut().as_mut().map(|s| s.register_overlay_stats(stats)); + self.state.borrow().as_ref().map(|s| s.register_overlay_stats(stats)); } fn usage_info(&self) -> sp_state_machine::UsageInfo { @@ -606,10 +611,14 @@ impl StateBackend> for BenchmarkingState { let proof_recorder_root = self.proof_recorder_root.get(); if proof_recorder_root == Default::default() || proof_size == 1 { // empty trie + log::debug!(target: "benchmark", "Some proof size: {}", &proof_size); proof_size } else { if let Some(size) = proof.encoded_compact_size::>(proof_recorder_root) { size as u32 + } else if proof_recorder_root == self.root.get() { + log::debug!(target: "benchmark", "No changes - no proof"); + 0 } else { panic!( "proof rec root {:?}, root {:?}, genesis {:?}, rec_len {:?}", @@ -635,6 +644,29 @@ mod test { use crate::bench::BenchmarkingState; use sp_state_machine::backend::Backend as _; + fn hex(hex: &str) -> Vec { + array_bytes::hex2bytes(hex).unwrap() + } + + #[test] + fn iteration_is_also_counted_in_rw_counts() { + let storage = sp_runtime::Storage { + top: vec![( + hex("ce6e1397e668c7fcf47744350dc59688455a2c2dbd2e2a649df4e55d93cd7158"), + hex("0102030405060708"), + )] + .into_iter() + .collect(), + ..sp_runtime::Storage::default() + }; + let bench_state = + BenchmarkingState::::new(storage, None, false, true).unwrap(); + + assert_eq!(bench_state.read_write_count(), (0, 0, 0, 0)); + assert_eq!(bench_state.keys(Default::default()).unwrap().count(), 1); + assert_eq!(bench_state.read_write_count(), (1, 0, 0, 0)); + } + #[test] fn read_to_main_and_child_tries() { let bench_state = diff --git a/client/db/src/children.rs b/client/db/src/children.rs index 538e51851f08..067137653718 100644 --- a/client/db/src/children.rs +++ b/client/db/src/children.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/db/src/lib.rs b/client/db/src/lib.rs index f217eb6480ab..21a03582ca74 100644 --- a/client/db/src/lib.rs +++ b/client/db/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -68,8 +68,8 @@ use sc_client_api::{ use sc_state_db::{IsPruned, LastCanonicalized, StateDb}; use sp_arithmetic::traits::Saturating; use sp_blockchain::{ - well_known_cache_keys, Backend as _, CachedHeaderMetadata, Error as ClientError, HeaderBackend, - HeaderMetadata, HeaderMetadataCache, Result as ClientResult, + Backend as _, CachedHeaderMetadata, Error as ClientError, HeaderBackend, HeaderMetadata, + HeaderMetadataCache, Result as ClientResult, }; use sp_core::{ offchain::OffchainOverlayedChange, @@ -86,8 +86,9 @@ use sp_runtime::{ }; use sp_state_machine::{ backend::{AsTrieBackend, Backend as StateBackend}, - ChildStorageCollection, DBValue, IndexOperation, OffchainChangesCollection, StateMachineStats, - StorageCollection, UsageInfo as StateUsageInfo, + ChildStorageCollection, DBValue, IndexOperation, IterArgs, OffchainChangesCollection, + StateMachineStats, StorageCollection, StorageIterator, StorageKey, StorageValue, + UsageInfo as StateUsageInfo, }; use sp_trie::{cache::SharedTrieCache, prefixed_key, MemoryDB, PrefixedMemoryDB}; @@ -159,10 +160,36 @@ impl std::fmt::Debug for RefTrackingState { } } +/// A raw iterator over the `RefTrackingState`. +pub struct RawIter { + inner: as StateBackend>>::RawIter, +} + +impl StorageIterator> for RawIter { + type Backend = RefTrackingState; + type Error = as StateBackend>>::Error; + + fn next_key(&mut self, backend: &Self::Backend) -> Option> { + self.inner.next_key(&backend.state) + } + + fn next_pair( + &mut self, + backend: &Self::Backend, + ) -> Option> { + self.inner.next_pair(&backend.state) + } + + fn was_complete(&self) -> bool { + self.inner.was_complete() + } +} + impl StateBackend> for RefTrackingState { type Error = as StateBackend>>::Error; type Transaction = as StateBackend>>::Transaction; type TrieBackendStorage = as StateBackend>>::TrieBackendStorage; + type RawIter = RawIter; fn storage(&self, key: &[u8]) -> Result>, Self::Error> { self.state.storage(key) @@ -212,53 +239,11 @@ impl StateBackend> for RefTrackingState { self.state.next_child_storage_key(child_info, key) } - fn for_keys_with_prefix(&self, prefix: &[u8], f: F) { - self.state.for_keys_with_prefix(prefix, f) - } - - fn for_key_values_with_prefix(&self, prefix: &[u8], f: F) { - self.state.for_key_values_with_prefix(prefix, f) - } - - fn apply_to_key_values_while, Vec) -> bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - start_at: Option<&[u8]>, - f: F, - allow_missing: bool, - ) -> Result { - self.state - .apply_to_key_values_while(child_info, prefix, start_at, f, allow_missing) - } - - fn apply_to_keys_while bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - start_at: Option<&[u8]>, - f: F, - ) { - self.state.apply_to_keys_while(child_info, prefix, start_at, f) - } - - fn for_child_keys_with_prefix( - &self, - child_info: &ChildInfo, - prefix: &[u8], - f: F, - ) { - self.state.for_child_keys_with_prefix(child_info, prefix, f) - } - fn storage_root<'a>( &self, delta: impl Iterator)>, state_version: StateVersion, - ) -> (B::Hash, Self::Transaction) - where - B::Hash: Ord, - { + ) -> (B::Hash, Self::Transaction) { self.state.storage_root(delta, state_version) } @@ -267,23 +252,12 @@ impl StateBackend> for RefTrackingState { child_info: &ChildInfo, delta: impl Iterator)>, state_version: StateVersion, - ) -> (B::Hash, bool, Self::Transaction) - where - B::Hash: Ord, - { + ) -> (B::Hash, bool, Self::Transaction) { self.state.child_storage_root(child_info, delta, state_version) } - fn pairs(&self) -> Vec<(Vec, Vec)> { - self.state.pairs() - } - - fn keys(&self, prefix: &[u8]) -> Vec> { - self.state.keys(prefix) - } - - fn child_keys(&self, child_info: &ChildInfo, prefix: &[u8]) -> Vec> { - self.state.child_keys(child_info, prefix) + fn raw_iter(&self, args: IterArgs) -> Result { + self.state.raw_iter(args).map(|inner| RawIter { inner }) } fn register_overlay_stats(&self, stats: &StateMachineStats) { @@ -933,10 +907,6 @@ impl sc_client_api::backend::BlockImportOperation Ok(()) } - fn update_cache(&mut self, _cache: HashMap>) { - // Currently cache isn't implemented on full nodes. - } - fn update_db_storage(&mut self, update: PrefixedMemoryDB>) -> ClientResult<()> { self.db_updates = update; Ok(()) @@ -1154,6 +1124,13 @@ impl Backend { Self::from_database(db as Arc<_>, canonicalization_delay, &db_config, needs_init) } + /// Reset the shared trie cache. + pub fn reset_trie_cache(&self) { + if let Some(cache) = &self.shared_trie_cache { + cache.reset(); + } + } + /// Create new memory-backed client backend for tests. #[cfg(any(test, feature = "test-helpers"))] pub fn new_test(blocks_pruning: u32, canonicalization_delay: u64) -> Self { @@ -1806,7 +1783,13 @@ impl Backend { } let new_displaced = self.blockchain.leaves.write().finalize_height(f_num); - self.prune_blocks(transaction, f_num, &new_displaced, current_transaction_justifications)?; + self.prune_blocks( + transaction, + f_num, + f_hash, + &new_displaced, + current_transaction_justifications, + )?; Ok(()) } @@ -1815,6 +1798,7 @@ impl Backend { &self, transaction: &mut Transaction, finalized_number: NumberFor, + finalized_hash: Block::Hash, displaced: &FinalizationOutcome>, current_transaction_justifications: &mut HashMap, ) -> ClientResult<()> { @@ -1843,10 +1827,10 @@ impl Backend { self.prune_block(transaction, BlockId::::number(number))?; } - self.prune_displaced_branches(transaction, finalized_number, displaced)?; + self.prune_displaced_branches(transaction, finalized_hash, displaced)?; }, BlocksPruning::KeepFinalized => { - self.prune_displaced_branches(transaction, finalized_number, displaced)?; + self.prune_displaced_branches(transaction, finalized_hash, displaced)?; }, } Ok(()) @@ -1855,28 +1839,21 @@ impl Backend { fn prune_displaced_branches( &self, transaction: &mut Transaction, - finalized: NumberFor, + finalized: Block::Hash, displaced: &FinalizationOutcome>, ) -> ClientResult<()> { // Discard all blocks from displaced branches for h in displaced.leaves() { - let mut number = finalized; - let mut hash = *h; - // Follow displaced chains back until we reach a finalized block. - // Since leaves are discarded due to finality, they can't have parents - // that are canonical, but not yet finalized. So we stop deleting as soon as - // we reach canonical chain. - while self.blockchain.hash(number)? != Some(hash) { - match self.blockchain.header(hash)? { - Some(header) => { - self.blockchain.insert_persisted_body_if_pinned(hash)?; - - self.prune_block(transaction, BlockId::::hash(hash))?; - number = header.number().saturating_sub(One::one()); - hash = *header.parent_hash(); + match sp_blockchain::tree_route(&self.blockchain, *h, finalized) { + Ok(tree_route) => + for r in tree_route.retracted() { + self.blockchain.insert_persisted_body_if_pinned(r.hash)?; + self.prune_block(transaction, BlockId::::hash(r.hash))?; }, - None => break, - } + Err(sp_blockchain::Error::UnknownBlock(_)) => { + // Sometimes routes can't be calculated. E.g. after warp sync. + }, + Err(e) => Err(e)?, } } Ok(()) @@ -1929,13 +1906,13 @@ impl Backend { Ok(()) } - fn empty_state(&self) -> ClientResult, Block>> { + fn empty_state(&self) -> RecordStatsState, Block> { let root = EmptyStorage::::new().0; // Empty trie let db_state = DbStateBuilder::::new(self.storage.clone(), root) .with_optional_cache(self.shared_trie_cache.as_ref().map(|c| c.local_cache())) .build(); let state = RefTrackingState::new(db_state, self.storage.clone(), None); - Ok(RecordStatsState::new(state, None, self.state_usage.clone())) + RecordStatsState::new(state, None, self.state_usage.clone()) } } @@ -2063,7 +2040,7 @@ impl sc_client_api::backend::Backend for Backend { fn begin_operation(&self) -> ClientResult { Ok(BlockImportOperation { pending_block: None, - old_state: self.empty_state()?, + old_state: self.empty_state(), db_updates: PrefixedMemoryDB::default(), storage_updates: Default::default(), child_storage_updates: Default::default(), @@ -2082,7 +2059,7 @@ impl sc_client_api::backend::Backend for Backend { block: Block::Hash, ) -> ClientResult<()> { if block == Default::default() { - operation.old_state = self.empty_state()?; + operation.old_state = self.empty_state(); } else { operation.old_state = self.state_at(block)?; } @@ -2439,6 +2416,7 @@ impl sc_client_api::backend::Backend for Backend { .unwrap_or(None) .is_some() }; + if let Ok(()) = self.storage.state_db.pin(&hash, hdr.number.saturated_into::(), hint) { @@ -2593,25 +2571,30 @@ pub(crate) mod tests { use sp_runtime::testing::Digest; let digest = Digest::default(); - let header = Header { - number, - parent_hash, - state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), - digest, - extrinsics_root, - }; - let header_hash = header.hash(); + let mut header = + Header { number, parent_hash, state_root: Default::default(), digest, extrinsics_root }; let block_hash = if number == 0 { Default::default() } else { parent_hash }; let mut op = backend.begin_operation().unwrap(); backend.begin_state_operation(&mut op, block_hash).unwrap(); - op.set_block_data(header, Some(body), None, None, NewBlockState::Best).unwrap(); if let Some(index) = transaction_index { op.update_transaction_index(index).unwrap(); } + + // Insert some fake data to ensure that the block can be found in the state column. + let (root, overlay) = op.old_state.storage_root( + vec![(block_hash.as_ref(), Some(block_hash.as_ref()))].into_iter(), + StateVersion::V1, + ); + op.update_db_storage(overlay).unwrap(); + header.state_root = root.into(); + + op.set_block_data(header.clone(), Some(body), None, None, NewBlockState::Best) + .unwrap(); + backend.commit_operation(op)?; - Ok(header_hash) + Ok(header.hash()) } pub fn insert_header_no_head( @@ -2623,18 +2606,31 @@ pub(crate) mod tests { use sp_runtime::testing::Digest; let digest = Digest::default(); - let header = Header { - number, - parent_hash, - state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), - digest, - extrinsics_root, - }; - let header_hash = header.hash(); + let mut header = + Header { number, parent_hash, state_root: Default::default(), digest, extrinsics_root }; let mut op = backend.begin_operation().unwrap(); - op.set_block_data(header, None, None, None, NewBlockState::Normal).unwrap(); + + let root = backend + .state_at(parent_hash) + .unwrap_or_else(|_| { + if parent_hash == Default::default() { + backend.empty_state() + } else { + panic!("Unknown block: {parent_hash:?}") + } + }) + .storage_root( + vec![(parent_hash.as_ref(), Some(parent_hash.as_ref()))].into_iter(), + StateVersion::V1, + ) + .0; + header.state_root = root.into(); + + op.set_block_data(header.clone(), None, None, None, NewBlockState::Normal) + .unwrap(); backend.commit_operation(op).unwrap(); - header_hash + + header.hash() } #[test] @@ -3369,204 +3365,175 @@ pub(crate) mod tests { #[test] fn prune_blocks_on_finalize() { - let backend = Backend::::new_test_with_tx_storage(BlocksPruning::Some(2), 0); - let mut blocks = Vec::new(); - let mut prev_hash = Default::default(); - for i in 0..5 { - let hash = insert_block( - &backend, - i, - prev_hash, - None, - Default::default(), - vec![i.into()], - None, - ) - .unwrap(); - blocks.push(hash); - prev_hash = hash; - } + let pruning_modes = + vec![BlocksPruning::Some(2), BlocksPruning::KeepFinalized, BlocksPruning::KeepAll]; - { - let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, blocks[4]).unwrap(); - for i in 1..5 { - op.mark_finalized(blocks[i], None).unwrap(); + for pruning_mode in pruning_modes { + let backend = Backend::::new_test_with_tx_storage(pruning_mode, 0); + let mut blocks = Vec::new(); + let mut prev_hash = Default::default(); + for i in 0..5 { + let hash = insert_block( + &backend, + i, + prev_hash, + None, + Default::default(), + vec![i.into()], + None, + ) + .unwrap(); + blocks.push(hash); + prev_hash = hash; } - backend.commit_operation(op).unwrap(); - } - let bc = backend.blockchain(); - assert_eq!(None, bc.body(blocks[0]).unwrap()); - assert_eq!(None, bc.body(blocks[1]).unwrap()); - assert_eq!(None, bc.body(blocks[2]).unwrap()); - assert_eq!(Some(vec![3.into()]), bc.body(blocks[3]).unwrap()); - assert_eq!(Some(vec![4.into()]), bc.body(blocks[4]).unwrap()); - } - #[test] - fn prune_blocks_on_finalize_in_keep_all() { - let backend = Backend::::new_test_with_tx_storage(BlocksPruning::KeepAll, 0); - let mut blocks = Vec::new(); - let mut prev_hash = Default::default(); - for i in 0..5 { - let hash = insert_block( - &backend, - i, - prev_hash, - None, - Default::default(), - vec![i.into()], - None, - ) - .unwrap(); - blocks.push(hash); - prev_hash = hash; - } + { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, blocks[4]).unwrap(); + for i in 1..5 { + op.mark_finalized(blocks[i], None).unwrap(); + } + backend.commit_operation(op).unwrap(); + } + let bc = backend.blockchain(); - let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, blocks[4]).unwrap(); - for i in 1..3 { - op.mark_finalized(blocks[i], None).unwrap(); + if matches!(pruning_mode, BlocksPruning::Some(_)) { + assert_eq!(None, bc.body(blocks[0]).unwrap()); + assert_eq!(None, bc.body(blocks[1]).unwrap()); + assert_eq!(None, bc.body(blocks[2]).unwrap()); + assert_eq!(Some(vec![3.into()]), bc.body(blocks[3]).unwrap()); + assert_eq!(Some(vec![4.into()]), bc.body(blocks[4]).unwrap()); + } else { + for i in 0..5 { + assert_eq!(Some(vec![(i as u64).into()]), bc.body(blocks[i]).unwrap()); + } + } } - backend.commit_operation(op).unwrap(); - - let bc = backend.blockchain(); - assert_eq!(Some(vec![0.into()]), bc.body(blocks[0]).unwrap()); - assert_eq!(Some(vec![1.into()]), bc.body(blocks[1]).unwrap()); - assert_eq!(Some(vec![2.into()]), bc.body(blocks[2]).unwrap()); - assert_eq!(Some(vec![3.into()]), bc.body(blocks[3]).unwrap()); - assert_eq!(Some(vec![4.into()]), bc.body(blocks[4]).unwrap()); } #[test] - fn prune_blocks_on_finalize_with_fork_in_keep_all() { - let backend = Backend::::new_test_with_tx_storage(BlocksPruning::KeepAll, 10); - let mut blocks = Vec::new(); - let mut prev_hash = Default::default(); - for i in 0..5 { - let hash = insert_block( + fn prune_blocks_on_finalize_with_fork() { + sp_tracing::try_init_simple(); + + let pruning_modes = + vec![BlocksPruning::Some(2), BlocksPruning::KeepFinalized, BlocksPruning::KeepAll]; + + for pruning in pruning_modes { + let backend = Backend::::new_test_with_tx_storage(pruning, 10); + let mut blocks = Vec::new(); + let mut prev_hash = Default::default(); + for i in 0..5 { + let hash = insert_block( + &backend, + i, + prev_hash, + None, + Default::default(), + vec![i.into()], + None, + ) + .unwrap(); + blocks.push(hash); + prev_hash = hash; + } + + // insert a fork at block 2 + let fork_hash_root = + insert_block(&backend, 2, blocks[1], None, H256::random(), vec![2.into()], None) + .unwrap(); + insert_block( &backend, - i, - prev_hash, + 3, + fork_hash_root, None, - Default::default(), - vec![i.into()], + H256::random(), + vec![3.into(), 11.into()], None, ) .unwrap(); - blocks.push(hash); - prev_hash = hash; - } + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, blocks[4]).unwrap(); + op.mark_head(blocks[4]).unwrap(); + backend.commit_operation(op).unwrap(); - // insert a fork at block 2 - let fork_hash_root = insert_block( - &backend, - 2, - blocks[1], - None, - sp_core::H256::random(), - vec![2.into()], - None, - ) - .unwrap(); - insert_block( - &backend, - 3, - fork_hash_root, - None, - H256::random(), - vec![3.into(), 11.into()], - None, - ) - .unwrap(); + let bc = backend.blockchain(); + assert_eq!(Some(vec![2.into()]), bc.body(fork_hash_root).unwrap()); - let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, blocks[4]).unwrap(); - op.mark_head(blocks[4]).unwrap(); - backend.commit_operation(op).unwrap(); + for i in 1..5 { + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, blocks[4]).unwrap(); + op.mark_finalized(blocks[i], None).unwrap(); + backend.commit_operation(op).unwrap(); + } - let bc = backend.blockchain(); - assert_eq!(Some(vec![2.into()]), bc.body(fork_hash_root).unwrap()); + if matches!(pruning, BlocksPruning::Some(_)) { + assert_eq!(None, bc.body(blocks[0]).unwrap()); + assert_eq!(None, bc.body(blocks[1]).unwrap()); + assert_eq!(None, bc.body(blocks[2]).unwrap()); - for i in 1..5 { - let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, blocks[i]).unwrap(); - op.mark_finalized(blocks[i], None).unwrap(); - backend.commit_operation(op).unwrap(); - } + assert_eq!(Some(vec![3.into()]), bc.body(blocks[3]).unwrap()); + assert_eq!(Some(vec![4.into()]), bc.body(blocks[4]).unwrap()); + } else { + for i in 0..5 { + assert_eq!(Some(vec![(i as u64).into()]), bc.body(blocks[i]).unwrap()); + } + } - assert_eq!(Some(vec![0.into()]), bc.body(blocks[0]).unwrap()); - assert_eq!(Some(vec![1.into()]), bc.body(blocks[1]).unwrap()); - assert_eq!(Some(vec![2.into()]), bc.body(blocks[2]).unwrap()); - assert_eq!(Some(vec![3.into()]), bc.body(blocks[3]).unwrap()); - assert_eq!(Some(vec![4.into()]), bc.body(blocks[4]).unwrap()); + if matches!(pruning, BlocksPruning::KeepAll) { + assert_eq!(Some(vec![2.into()]), bc.body(fork_hash_root).unwrap()); + } else { + assert_eq!(None, bc.body(fork_hash_root).unwrap()); + } - assert_eq!(Some(vec![2.into()]), bc.body(fork_hash_root).unwrap()); - assert_eq!(bc.info().best_number, 4); - for i in 0..5 { - assert!(bc.hash(i).unwrap().is_some()); + assert_eq!(bc.info().best_number, 4); + for i in 0..5 { + assert!(bc.hash(i).unwrap().is_some()); + } } } #[test] - fn prune_blocks_on_finalize_with_fork() { - let backend = Backend::::new_test_with_tx_storage(BlocksPruning::Some(2), 10); - let mut blocks = Vec::new(); - let mut prev_hash = Default::default(); - for i in 0..5 { - let hash = insert_block( - &backend, - i, - prev_hash, - None, - Default::default(), - vec![i.into()], - None, - ) - .unwrap(); - blocks.push(hash); - prev_hash = hash; - } + fn prune_blocks_on_finalize_and_reorg() { + // 0 - 1b + // \ - 1a - 2a - 3a + // \ - 2b - // insert a fork at block 2 - let fork_hash_root = insert_block( - &backend, - 2, - blocks[1], - None, - sp_core::H256::random(), - vec![2.into()], - None, - ) - .unwrap(); - insert_block( - &backend, - 3, - fork_hash_root, - None, - H256::random(), - vec![3.into(), 11.into()], - None, - ) - .unwrap(); + let backend = Backend::::new_test_with_tx_storage(BlocksPruning::Some(10), 10); + + let make_block = |index, parent, val: u64| { + insert_block(&backend, index, parent, None, H256::random(), vec![val.into()], None) + .unwrap() + }; + + let block_0 = make_block(0, Default::default(), 0x00); + let block_1a = make_block(1, block_0, 0x1a); + let block_1b = make_block(1, block_0, 0x1b); + let block_2a = make_block(2, block_1a, 0x2a); + let block_2b = make_block(2, block_1a, 0x2b); + let block_3a = make_block(3, block_2a, 0x3a); + + // Make sure 1b is head let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, blocks[4]).unwrap(); - op.mark_head(blocks[4]).unwrap(); + backend.begin_state_operation(&mut op, block_0).unwrap(); + op.mark_head(block_1b).unwrap(); backend.commit_operation(op).unwrap(); - for i in 1..5 { - let mut op = backend.begin_operation().unwrap(); - backend.begin_state_operation(&mut op, blocks[4]).unwrap(); - op.mark_finalized(blocks[i], None).unwrap(); - backend.commit_operation(op).unwrap(); - } + // Finalize 3a + let mut op = backend.begin_operation().unwrap(); + backend.begin_state_operation(&mut op, block_0).unwrap(); + op.mark_head(block_3a).unwrap(); + op.mark_finalized(block_1a, None).unwrap(); + op.mark_finalized(block_2a, None).unwrap(); + op.mark_finalized(block_3a, None).unwrap(); + backend.commit_operation(op).unwrap(); let bc = backend.blockchain(); - assert_eq!(None, bc.body(blocks[0]).unwrap()); - assert_eq!(None, bc.body(blocks[1]).unwrap()); - assert_eq!(None, bc.body(blocks[2]).unwrap()); - assert_eq!(Some(vec![3.into()]), bc.body(blocks[3]).unwrap()); - assert_eq!(Some(vec![4.into()]), bc.body(blocks[4]).unwrap()); + assert_eq!(None, bc.body(block_1b).unwrap()); + assert_eq!(None, bc.body(block_2b).unwrap()); + assert_eq!(Some(vec![0x00.into()]), bc.body(block_0).unwrap()); + assert_eq!(Some(vec![0x1a.into()]), bc.body(block_1a).unwrap()); + assert_eq!(Some(vec![0x2a.into()]), bc.body(block_2a).unwrap()); + assert_eq!(Some(vec![0x3a.into()]), bc.body(block_3a).unwrap()); } #[test] @@ -3797,13 +3764,7 @@ pub(crate) mod tests { assert!(matches!(backend.commit_operation(op), Err(sp_blockchain::Error::SetHeadTooOld))); // Insert 2 as best again. - let header = Header { - number: 2, - parent_hash: block1, - state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), - digest: Default::default(), - extrinsics_root: Default::default(), - }; + let header = backend.blockchain().header(block2).unwrap().unwrap(); let mut op = backend.begin_operation().unwrap(); op.set_block_data(header, None, None, None, NewBlockState::Best).unwrap(); backend.commit_operation(op).unwrap(); @@ -3820,13 +3781,7 @@ pub(crate) mod tests { assert_eq!(backend.blockchain().info().finalized_hash, block0); // Insert 1 as final again. - let header = Header { - number: 1, - parent_hash: block0, - state_root: BlakeTwo256::trie_root(Vec::new(), StateVersion::V1), - digest: Default::default(), - extrinsics_root: Default::default(), - }; + let header = backend.blockchain().header(block1).unwrap().unwrap(); let mut op = backend.begin_operation().unwrap(); op.set_block_data(header, None, None, None, NewBlockState::Final).unwrap(); @@ -3959,6 +3914,38 @@ pub(crate) mod tests { assert_eq!(1, backend.blockchain.leaves.read().highest_leaf().unwrap().0); } + #[test] + fn revert_finalized_blocks() { + let pruning_modes = [BlocksPruning::Some(10), BlocksPruning::KeepAll]; + + // we will create a chain with 11 blocks, finalize block #8 and then + // attempt to revert 5 blocks. + for pruning_mode in pruning_modes { + let backend = Backend::::new_test_with_tx_storage(pruning_mode, 1); + + let mut parent = Default::default(); + for i in 0..=10 { + parent = insert_block(&backend, i, parent, None, Default::default(), vec![], None) + .unwrap(); + } + + assert_eq!(backend.blockchain().info().best_number, 10); + + let block8 = backend.blockchain().hash(8).unwrap().unwrap(); + backend.finalize_block(block8, None).unwrap(); + backend.revert(5, true).unwrap(); + + match pruning_mode { + // we can only revert to blocks for which we have state, if pruning is enabled + // then the last state available will be that of the latest finalized block + BlocksPruning::Some(_) => + assert_eq!(backend.blockchain().info().finalized_number, 8), + // otherwise if we're not doing state pruning we can revert past finalized blocks + _ => assert_eq!(backend.blockchain().info().finalized_number, 5), + } + } + } + #[test] fn test_no_duplicated_leaves_allowed() { let backend: Backend = Backend::new_test(10, 10); @@ -3977,7 +3964,8 @@ pub(crate) mod tests { #[test] fn force_delayed_canonicalize_waiting_for_blocks_to_be_finalized() { - let pruning_modes = [BlocksPruning::Some(10), BlocksPruning::KeepAll]; + let pruning_modes = + [BlocksPruning::Some(10), BlocksPruning::KeepAll, BlocksPruning::KeepFinalized]; for pruning_mode in pruning_modes { eprintln!("Running with pruning mode: {:?}", pruning_mode); diff --git a/client/db/src/offchain.rs b/client/db/src/offchain.rs index 030a410981b2..dc7202ff6d29 100644 --- a/client/db/src/offchain.rs +++ b/client/db/src/offchain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/db/src/parity_db.rs b/client/db/src/parity_db.rs index 4adacbf6f041..b7068f2430ef 100644 --- a/client/db/src/parity_db.rs +++ b/client/db/src/parity_db.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -54,16 +54,16 @@ pub fn open>( ]; for i in compressed { - let mut column = &mut config.columns[i as usize]; + let column = &mut config.columns[i as usize]; column.compression = parity_db::CompressionType::Lz4; } - let mut state_col = &mut config.columns[columns::STATE as usize]; + let state_col = &mut config.columns[columns::STATE as usize]; state_col.ref_counted = true; state_col.preimage = true; state_col.uniform = true; - let mut tx_col = &mut config.columns[columns::TRANSACTION as usize]; + let tx_col = &mut config.columns[columns::TRANSACTION as usize]; tx_col.ref_counted = true; tx_col.preimage = true; tx_col.uniform = true; diff --git a/client/db/src/pinned_blocks_cache.rs b/client/db/src/pinned_blocks_cache.rs index 39ff1c527787..46c9287fb19a 100644 --- a/client/db/src/pinned_blocks_cache.rs +++ b/client/db/src/pinned_blocks_cache.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2023 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -149,8 +149,9 @@ impl PinnedBlocksCache { self.cache.len() ); }, - None => - log::warn!(target: LOG_TARGET, "Unable to bump reference count. hash = {}", hash), + None => { + log::warn!(target: LOG_TARGET, "Unable to bump reference count. hash = {}", hash) + }, }; } @@ -167,7 +168,7 @@ impl PinnedBlocksCache { /// Attach body to an existing cache item pub fn insert_body(&mut self, hash: Block::Hash, extrinsics: Option>) { match self.cache.peek_mut(&hash) { - Some(mut entry) => { + Some(entry) => { entry.body = Some(extrinsics); log::trace!( target: LOG_TARGET, @@ -191,7 +192,7 @@ impl PinnedBlocksCache { justifications: Option, ) { match self.cache.peek_mut(&hash) { - Some(mut entry) => { + Some(entry) => { entry.justifications = Some(justifications); log::trace!( target: LOG_TARGET, diff --git a/client/db/src/record_stats_state.rs b/client/db/src/record_stats_state.rs index 0b51d3fef212..f794717902d5 100644 --- a/client/db/src/record_stats_state.rs +++ b/client/db/src/record_stats_state.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -26,7 +26,7 @@ use sp_runtime::{ }; use sp_state_machine::{ backend::{AsTrieBackend, Backend as StateBackend}, - TrieBackend, + IterArgs, StorageIterator, StorageKey, StorageValue, TrieBackend, }; use std::sync::Arc; @@ -73,10 +73,43 @@ impl>, B: BlockT> RecordStatsState { } } +pub struct RawIter +where + S: StateBackend>, + B: BlockT, +{ + inner: >>::RawIter, +} + +impl StorageIterator> for RawIter +where + S: StateBackend>, + B: BlockT, +{ + type Backend = RecordStatsState; + type Error = S::Error; + + fn next_key(&mut self, backend: &Self::Backend) -> Option> { + self.inner.next_key(&backend.state) + } + + fn next_pair( + &mut self, + backend: &Self::Backend, + ) -> Option> { + self.inner.next_pair(&backend.state) + } + + fn was_complete(&self) -> bool { + self.inner.was_complete() + } +} + impl>, B: BlockT> StateBackend> for RecordStatsState { type Error = S::Error; type Transaction = S::Transaction; type TrieBackendStorage = S::TrieBackendStorage; + type RawIter = RawIter; fn storage(&self, key: &[u8]) -> Result>, Self::Error> { let value = self.state.storage(key)?; @@ -122,28 +155,6 @@ impl>, B: BlockT> StateBackend> for Record self.state.exists_child_storage(child_info, key) } - fn apply_to_key_values_while, Vec) -> bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - start_at: Option<&[u8]>, - f: F, - allow_missing: bool, - ) -> Result { - self.state - .apply_to_key_values_while(child_info, prefix, start_at, f, allow_missing) - } - - fn apply_to_keys_while bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - start_at: Option<&[u8]>, - f: F, - ) { - self.state.apply_to_keys_while(child_info, prefix, start_at, f) - } - fn next_storage_key(&self, key: &[u8]) -> Result>, Self::Error> { self.state.next_storage_key(key) } @@ -156,31 +167,11 @@ impl>, B: BlockT> StateBackend> for Record self.state.next_child_storage_key(child_info, key) } - fn for_keys_with_prefix(&self, prefix: &[u8], f: F) { - self.state.for_keys_with_prefix(prefix, f) - } - - fn for_key_values_with_prefix(&self, prefix: &[u8], f: F) { - self.state.for_key_values_with_prefix(prefix, f) - } - - fn for_child_keys_with_prefix( - &self, - child_info: &ChildInfo, - prefix: &[u8], - f: F, - ) { - self.state.for_child_keys_with_prefix(child_info, prefix, f) - } - fn storage_root<'a>( &self, delta: impl Iterator)>, state_version: StateVersion, - ) -> (B::Hash, Self::Transaction) - where - B::Hash: Ord, - { + ) -> (B::Hash, Self::Transaction) { self.state.storage_root(delta, state_version) } @@ -189,23 +180,12 @@ impl>, B: BlockT> StateBackend> for Record child_info: &ChildInfo, delta: impl Iterator)>, state_version: StateVersion, - ) -> (B::Hash, bool, Self::Transaction) - where - B::Hash: Ord, - { + ) -> (B::Hash, bool, Self::Transaction) { self.state.child_storage_root(child_info, delta, state_version) } - fn pairs(&self) -> Vec<(Vec, Vec)> { - self.state.pairs() - } - - fn keys(&self, prefix: &[u8]) -> Vec> { - self.state.keys(prefix) - } - - fn child_keys(&self, child_info: &ChildInfo, prefix: &[u8]) -> Vec> { - self.state.child_keys(child_info, prefix) + fn raw_iter(&self, args: IterArgs) -> Result { + self.state.raw_iter(args).map(|inner| RawIter { inner }) } fn register_overlay_stats(&self, stats: &sp_state_machine::StateMachineStats) { diff --git a/client/db/src/stats.rs b/client/db/src/stats.rs index f6c14568236e..1609ef9d5dad 100644 --- a/client/db/src/stats.rs +++ b/client/db/src/stats.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/db/src/upgrade.rs b/client/db/src/upgrade.rs index 51750bf68975..f1e503867dfc 100644 --- a/client/db/src/upgrade.rs +++ b/client/db/src/upgrade.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -190,7 +190,7 @@ fn version_file_path(path: &Path) -> PathBuf { file_path } -#[cfg(test)] +#[cfg(all(test, feature = "rocksdb"))] mod tests { use super::*; use crate::{tests::Block, DatabaseSource}; diff --git a/client/db/src/utils.rs b/client/db/src/utils.rs index 567950d089e1..abf9c4629cee 100644 --- a/client/db/src/utils.rs +++ b/client/db/src/utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -583,12 +583,12 @@ mod tests { use super::*; use codec::Input; use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper}; - use std::path::PathBuf; type Block = RawBlock>; - #[cfg(any(feature = "rocksdb", test))] + #[cfg(feature = "rocksdb")] #[test] fn database_type_subdir_migration() { + use std::path::PathBuf; type Block = RawBlock>; fn check_dir_for_db_type( @@ -685,6 +685,7 @@ mod tests { assert_eq!(joined.remaining_len().unwrap(), Some(0)); } + #[cfg(feature = "rocksdb")] #[test] fn test_open_database_auto_new() { let db_dir = tempfile::TempDir::new().unwrap(); @@ -730,6 +731,7 @@ mod tests { } } + #[cfg(feature = "rocksdb")] #[test] fn test_open_database_rocksdb_new() { let db_dir = tempfile::TempDir::new().unwrap(); @@ -780,6 +782,7 @@ mod tests { } } + #[cfg(feature = "rocksdb")] #[test] fn test_open_database_paritydb_new() { let db_dir = tempfile::TempDir::new().unwrap(); diff --git a/client/executor/Cargo.toml b/client/executor/Cargo.toml index 8bedcd3edcff..449fd22e3ffc 100644 --- a/client/executor/Cargo.toml +++ b/client/executor/Cargo.toml @@ -14,39 +14,38 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -lru = "0.8.1" parking_lot = "0.12.1" +schnellru = "0.2.1" tracing = "0.1.29" -wasmi = "0.13" -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } sc-executor-common = { version = "0.10.0-dev", path = "common" } -sc-executor-wasmi = { version = "0.10.0-dev", path = "wasmi" } sc-executor-wasmtime = { version = "0.10.0-dev", path = "wasmtime" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-externalities = { version = "0.13.0", path = "../../primitives/externalities" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } -sp-panic-handler = { version = "5.0.0", path = "../../primitives/panic-handler" } -sp-runtime-interface = { version = "7.0.0", path = "../../primitives/runtime-interface" } -sp-trie = { version = "7.0.0", path = "../../primitives/trie" } -sp-version = { version = "5.0.0", path = "../../primitives/version" } -sp-wasm-interface = { version = "7.0.0", path = "../../primitives/wasm-interface" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-externalities = { version = "0.19.0", path = "../../primitives/externalities" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } +sp-panic-handler = { version = "8.0.0", path = "../../primitives/panic-handler" } +sp-runtime-interface = { version = "17.0.0", path = "../../primitives/runtime-interface" } +sp-trie = { version = "22.0.0", path = "../../primitives/trie" } +sp-version = { version = "22.0.0", path = "../../primitives/version" } +sp-wasm-interface = { version = "14.0.0", path = "../../primitives/wasm-interface" } [dev-dependencies] -array-bytes = "4.1" +array-bytes = "6.1" assert_matches = "1.3.0" wat = "1.0" sc-runtime-test = { version = "2.0.0", path = "runtime-test" } substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime" } -sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-state-machine = { version = "0.28.0", path = "../../primitives/state-machine" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../primitives/maybe-compressed-blob" } sc-tracing = { version = "4.0.0-dev", path = "../tracing" } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } tracing-subscriber = "0.2.19" paste = "1.0" regex = "1.6.0" -criterion = "0.3" +criterion = "0.4.0" env_logger = "0.9" num_cpus = "1.13.1" tempfile = "3.3.0" diff --git a/client/executor/benches/bench.rs b/client/executor/benches/bench.rs index a282cdfbdd33..66a82a175221 100644 --- a/client/executor/benches/bench.rs +++ b/client/executor/benches/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,11 +21,10 @@ use codec::Encode; use sc_executor_common::{ runtime_blob::RuntimeBlob, - wasm_runtime::{WasmInstance, WasmModule}, + wasm_runtime::{WasmInstance, WasmModule, DEFAULT_HEAP_ALLOC_STRATEGY}, }; use sc_executor_wasmtime::InstantiationStrategy; use sc_runtime_test::wasm_binary_unwrap as test_runtime; -use sp_wasm_interface::HostFunctions as _; use std::sync::{ atomic::{AtomicBool, AtomicUsize, Ordering}, Arc, @@ -33,7 +32,6 @@ use std::sync::{ #[derive(Clone)] enum Method { - Interpreted, Compiled { instantiation_strategy: InstantiationStrategy, precompile: bool }, } @@ -48,31 +46,26 @@ fn initialize( _tmpdir: &mut Option, runtime: &[u8], method: Method, -) -> Arc { +) -> Box { let blob = RuntimeBlob::uncompress_if_needed(runtime).unwrap(); - let host_functions = sp_io::SubstrateHostFunctions::host_functions(); - let heap_pages = 2048; + let allow_missing_func_imports = true; match method { - Method::Interpreted => sc_executor_wasmi::create_runtime( - blob, - heap_pages, - host_functions, - allow_missing_func_imports, - ) - .map(|runtime| -> Arc { Arc::new(runtime) }), Method::Compiled { instantiation_strategy, precompile } => { let config = sc_executor_wasmtime::Config { allow_missing_func_imports, cache_path: None, semantics: sc_executor_wasmtime::Semantics { - extra_heap_pages: heap_pages, + heap_alloc_strategy: DEFAULT_HEAP_ALLOC_STRATEGY, instantiation_strategy, deterministic_stack_limit: None, canonicalize_nans: false, parallel_compilation: true, - max_memory_size: None, + wasm_multi_value: false, + wasm_bulk_memory: false, + wasm_reference_types: false, + wasm_simd: false, }, }; @@ -96,7 +89,7 @@ fn initialize( } else { sc_executor_wasmtime::create_runtime::(blob, config) } - .map(|runtime| -> Arc { Arc::new(runtime) }) + .map(|runtime| -> Box { Box::new(runtime) }) }, } .unwrap() @@ -186,12 +179,19 @@ fn bench_call_instance(c: &mut Criterion) { }, ), ( - "pooling_vanilla", + "pooling_vanilla_fresh", Method::Compiled { instantiation_strategy: InstantiationStrategy::Pooling, precompile: false, }, ), + ( + "pooling_vanilla_precompiled", + Method::Compiled { + instantiation_strategy: InstantiationStrategy::Pooling, + precompile: true, + }, + ), ( "pooling_cow_fresh", Method::Compiled { @@ -206,7 +206,6 @@ fn bench_call_instance(c: &mut Criterion) { precompile: true, }, ), - ("interpreted", Method::Interpreted), ]; let runtimes = [("kusama_runtime", kusama_runtime()), ("test_runtime", test_runtime())]; diff --git a/client/executor/common/Cargo.toml b/client/executor/common/Cargo.toml index 648e937d371f..dfde1902631c 100644 --- a/client/executor/common/Cargo.toml +++ b/client/executor/common/Cargo.toml @@ -16,10 +16,9 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] thiserror = "1.0.30" wasm-instrument = "0.3" -wasmi = "0.13" sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../../primitives/maybe-compressed-blob" } -sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" } +sp-wasm-interface = { version = "14.0.0", path = "../../../primitives/wasm-interface" } [features] default = [] diff --git a/client/executor/common/src/error.rs b/client/executor/common/src/error.rs index c35a874b7796..2a0dc364b410 100644 --- a/client/executor/common/src/error.rs +++ b/client/executor/common/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,8 +18,6 @@ //! Rust executor possible errors. -use wasmi; - /// Result type alias. pub type Result = std::result::Result; @@ -27,9 +25,6 @@ pub type Result = std::result::Result; #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { - #[error(transparent)] - Wasmi(#[from] wasmi::Error), - #[error("Error calling api function: {0}")] ApiError(Box), @@ -48,9 +43,6 @@ pub enum Error { #[error("Invalid type returned (should be u64)")] InvalidReturn, - #[error("Runtime error")] - Runtime, - #[error("Runtime panicked: {0}")] RuntimePanicked(String), @@ -104,9 +96,10 @@ pub enum Error { #[error("Execution aborted due to trap: {0}")] AbortedDueToTrap(MessageWithBacktrace), -} -impl wasmi::HostError for Error {} + #[error("Output exceeds bounds of wasm memory")] + OutputExceedsBounds, +} impl From<&'static str> for Error { fn from(err: &'static str) -> Error { @@ -153,7 +146,7 @@ pub enum WasmError { Instantiation(String), /// Other error happenend. - #[error("{0}")] + #[error("Other error happened while constructing the runtime: {0}")] Other(String), } diff --git a/client/executor/common/src/lib.rs b/client/executor/common/src/lib.rs index 79bb74b62a41..751801fb30da 100644 --- a/client/executor/common/src/lib.rs +++ b/client/executor/common/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/executor/common/src/runtime_blob/data_segments_snapshot.rs b/client/executor/common/src/runtime_blob/data_segments_snapshot.rs index e65fc32f637a..3fd546ce4457 100644 --- a/client/executor/common/src/runtime_blob/data_segments_snapshot.rs +++ b/client/executor/common/src/runtime_blob/data_segments_snapshot.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/executor/common/src/runtime_blob/globals_snapshot.rs b/client/executor/common/src/runtime_blob/globals_snapshot.rs index 207fa751bc40..9ba6fc55e49c 100644 --- a/client/executor/common/src/runtime_blob/globals_snapshot.rs +++ b/client/executor/common/src/runtime_blob/globals_snapshot.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/executor/common/src/runtime_blob/mod.rs b/client/executor/common/src/runtime_blob/mod.rs index 4b163bbaaf32..07a0945cc2b6 100644 --- a/client/executor/common/src/runtime_blob/mod.rs +++ b/client/executor/common/src/runtime_blob/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -26,9 +26,6 @@ //! //! To give you some examples: //! -//! - wasmi allows reaching to non-exported mutable globals so that we could reset them. Wasmtime -//! doesn’t support that. -//! //! We need to reset the globals because when we //! execute the Substrate Runtime, we do not drop and create the instance anew, instead //! we restore some selected parts of the state. diff --git a/client/executor/common/src/runtime_blob/runtime_blob.rs b/client/executor/common/src/runtime_blob/runtime_blob.rs index 08df4b32d59e..24dc7e393a4b 100644 --- a/client/executor/common/src/runtime_blob/runtime_blob.rs +++ b/client/executor/common/src/runtime_blob/runtime_blob.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,7 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::error::WasmError; +use crate::{error::WasmError, wasm_runtime::HeapAllocStrategy}; use wasm_instrument::{ export_mutable_globals, parity_wasm::elements::{ @@ -157,18 +157,13 @@ impl RuntimeBlob { Ok(()) } - /// Increases the number of memory pages requested by the WASM blob by - /// the given amount of `extra_heap_pages`. + /// Modifies the blob's memory section according to the given `heap_alloc_strategy`. /// /// Will return an error in case there is no memory section present, /// or if the memory section is empty. - /// - /// Only modifies the initial size of the memory; the maximum is unmodified - /// unless it's smaller than the initial size, in which case it will be increased - /// so that it's at least as big as the initial size. - pub fn add_extra_heap_pages_to_memory_section( + pub fn setup_memory_according_to_heap_alloc_strategy( &mut self, - extra_heap_pages: u32, + heap_alloc_strategy: HeapAllocStrategy, ) -> Result<(), WasmError> { let memory_section = self .raw_module @@ -179,8 +174,17 @@ impl RuntimeBlob { return Err(WasmError::Other("memory section is empty".into())) } for memory_ty in memory_section.entries_mut() { - let min = memory_ty.limits().initial().saturating_add(extra_heap_pages); - let max = memory_ty.limits().maximum().map(|max| std::cmp::max(min, max)); + let initial = memory_ty.limits().initial(); + let (min, max) = match heap_alloc_strategy { + HeapAllocStrategy::Dynamic { maximum_pages } => { + // Ensure `initial <= maximum_pages` + (maximum_pages.map(|m| m.min(initial)).unwrap_or(initial), maximum_pages) + }, + HeapAllocStrategy::Static { extra_pages } => { + let pages = initial.saturating_add(extra_pages); + (pages, Some(pages)) + }, + }; *memory_ty = MemoryType::new(min, max); } Ok(()) diff --git a/client/executor/common/src/util.rs b/client/executor/common/src/util.rs index fbae01b556fb..34967f86595d 100644 --- a/client/executor/common/src/util.rs +++ b/client/executor/common/src/util.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -26,11 +26,7 @@ use std::ops::Range; /// Returns None if the end of the range would exceed some maximum offset. pub fn checked_range(offset: usize, len: usize, max: usize) -> Option> { let end = offset.checked_add(len)?; - if end <= max { - Some(offset..end) - } else { - None - } + (end <= max).then(|| offset..end) } /// Provides safe memory access interface using an external buffer diff --git a/client/executor/common/src/wasm_runtime.rs b/client/executor/common/src/wasm_runtime.rs index d0cc8926144b..5dac77e59fa7 100644 --- a/client/executor/common/src/wasm_runtime.rs +++ b/client/executor/common/src/wasm_runtime.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -23,6 +23,13 @@ use sp_wasm_interface::Value; pub use sc_allocator::AllocationStats; +/// Default heap allocation strategy. +pub const DEFAULT_HEAP_ALLOC_STRATEGY: HeapAllocStrategy = + HeapAllocStrategy::Static { extra_pages: DEFAULT_HEAP_ALLOC_PAGES }; + +/// Default heap allocation pages. +pub const DEFAULT_HEAP_ALLOC_PAGES: u32 = 2048; + /// A method to be used to find the entrypoint when calling into the runtime /// /// Contains variants on how to resolve wasm function that will be invoked. @@ -119,3 +126,29 @@ pub trait WasmInstance: Send { None } } + +/// Defines the heap pages allocation strategy the wasm runtime should use. +/// +/// A heap page is defined as 64KiB of memory. +#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)] +pub enum HeapAllocStrategy { + /// Allocate a static number of heap pages. + /// + /// The total number of allocated heap pages is the initial number of heap pages requested by + /// the wasm file plus the `extra_pages`. + Static { + /// The number of pages that will be added on top of the initial heap pages requested by + /// the wasm file. + extra_pages: u32, + }, + /// Allocate the initial heap pages as requested by the wasm file and then allow it to grow + /// dynamically. + Dynamic { + /// The absolute maximum size of the linear memory (in pages). + /// + /// When `Some(_)` the linear memory will be allowed to grow up to this limit. + /// When `None` the linear memory will be allowed to grow up to the maximum limit supported + /// by WASM (4GB). + maximum_pages: Option, + }, +} diff --git a/client/executor/runtime-test/Cargo.toml b/client/executor/runtime-test/Cargo.toml index 07626c234336..50a1bf5f824c 100644 --- a/client/executor/runtime-test/Cargo.toml +++ b/client/executor/runtime-test/Cargo.toml @@ -13,11 +13,11 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, features = ["improved_panic_error_reporting"], path = "../../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime-interface" } -sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, features = ["improved_panic_error_reporting"], path = "../../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-runtime-interface = { version = "17.0.0", default-features = false, path = "../../../primitives/runtime-interface" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder", optional = true } diff --git a/client/executor/runtime-test/build.rs b/client/executor/runtime-test/build.rs index d3aceedf9ed9..088c93110d85 100644 --- a/client/executor/runtime-test/build.rs +++ b/client/executor/runtime-test/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -24,6 +24,7 @@ fn main() { .with_current_project() .export_heap_base() .import_memory() + .disable_runtime_version_section_check() .build(); } @@ -36,6 +37,7 @@ fn main() { .import_memory() .set_file_name("wasm_binary_with_tracing.rs") .append_to_rust_flags(r#"--cfg feature="with-tracing""#) + .disable_runtime_version_section_check() .build(); } } diff --git a/client/executor/runtime-test/src/lib.rs b/client/executor/runtime-test/src/lib.rs index 0aa30e4bc967..2bd2aeeb606e 100644 --- a/client/executor/runtime-test/src/lib.rs +++ b/client/executor/runtime-test/src/lib.rs @@ -29,8 +29,6 @@ use sp_runtime::{ print, traits::{BlakeTwo256, Hash}, }; -#[cfg(not(feature = "std"))] -use sp_runtime_interface::pack_ptr_and_len; extern "C" { #[allow(dead_code)] @@ -330,6 +328,15 @@ sp_core::wasm_export_functions! { assert_eq!(value, -66); } + fn allocate_two_gigabyte() -> u32 { + let mut data = Vec::new(); + for _ in 0..205 { + data.push(Vec::::with_capacity(10 * 1024 * 1024)); + } + + data.iter().map(|d| d.capacity() as u32).sum() + } + fn test_abort_on_panic() { sp_io::panic_handler::abort_on_panic("test_abort_on_panic called"); } @@ -344,31 +351,48 @@ sp_core::wasm_export_functions! { } } -// Returns a huge len. It should result in an error, and not an allocation. -#[no_mangle] -#[cfg(not(feature = "std"))] -pub extern "C" fn test_return_huge_len(_params: *const u8, _len: usize) -> u64 { - pack_ptr_and_len(0, u32::MAX) -} +// Tests that check output validity. We explicitly return the ptr and len, so we avoid using the +// `wasm_export_functions` macro. +mod output_validity { + #[cfg(not(feature = "std"))] + use super::WASM_PAGE_SIZE; -// Returns an offset right at the edge of the wasm memory boundary. With length 0, it should -// succeed. -#[no_mangle] -#[cfg(not(feature = "std"))] -pub extern "C" fn test_return_max_memory_offset(_params: *const u8, _len: usize) -> u64 { - pack_ptr_and_len((core::arch::wasm32::memory_size(0) * WASM_PAGE_SIZE) as u32, 0) -} + #[cfg(not(feature = "std"))] + use sp_runtime_interface::pack_ptr_and_len; -// Returns an offset right at the edge of the wasm memory boundary. With length 1, it should fail. -#[no_mangle] -#[cfg(not(feature = "std"))] -pub extern "C" fn test_return_max_memory_offset_plus_one(_params: *const u8, _len: usize) -> u64 { - pack_ptr_and_len((core::arch::wasm32::memory_size(0) * WASM_PAGE_SIZE) as u32, 1) -} + // Returns a huge len. It should result in an error, and not an allocation. + #[no_mangle] + #[cfg(not(feature = "std"))] + pub extern "C" fn test_return_huge_len(_params: *const u8, _len: usize) -> u64 { + pack_ptr_and_len(0, u32::MAX) + } -// Returns an output that overflows the u32 range. It should result in an error. -#[no_mangle] -#[cfg(not(feature = "std"))] -pub extern "C" fn test_return_overflow(_params: *const u8, _len: usize) -> u64 { - pack_ptr_and_len(u32::MAX, 1) + // Returns an offset right before the edge of the wasm memory boundary. It should succeed. + #[no_mangle] + #[cfg(not(feature = "std"))] + pub extern "C" fn test_return_max_memory_offset(_params: *const u8, _len: usize) -> u64 { + let output_ptr = (core::arch::wasm32::memory_size(0) * WASM_PAGE_SIZE) as u32 - 1; + let ptr = output_ptr as *mut u8; + unsafe { + ptr.write(u8::MAX); + } + pack_ptr_and_len(output_ptr, 1) + } + + // Returns an offset right after the edge of the wasm memory boundary. It should fail. + #[no_mangle] + #[cfg(not(feature = "std"))] + pub extern "C" fn test_return_max_memory_offset_plus_one( + _params: *const u8, + _len: usize, + ) -> u64 { + pack_ptr_and_len((core::arch::wasm32::memory_size(0) * WASM_PAGE_SIZE) as u32, 1) + } + + // Returns an output that overflows the u32 range. It should result in an error. + #[no_mangle] + #[cfg(not(feature = "std"))] + pub extern "C" fn test_return_overflow(_params: *const u8, _len: usize) -> u64 { + pack_ptr_and_len(u32::MAX, 1) + } } diff --git a/client/executor/src/native_executor.rs b/client/executor/src/executor.rs similarity index 60% rename from client/executor/src/native_executor.rs rename to client/executor/src/executor.rs index 0eabffb8c87d..7c292a83da08 100644 --- a/client/executor/src/native_executor.rs +++ b/client/executor/src/executor.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -32,15 +32,14 @@ use std::{ use codec::Encode; use sc_executor_common::{ runtime_blob::RuntimeBlob, - wasm_runtime::{AllocationStats, WasmInstance, WasmModule}, + wasm_runtime::{ + AllocationStats, HeapAllocStrategy, WasmInstance, WasmModule, DEFAULT_HEAP_ALLOC_STRATEGY, + }, }; -use sp_core::traits::{CodeExecutor, Externalities, RuntimeCode}; +use sp_core::traits::{CallContext, CodeExecutor, Externalities, RuntimeCode}; use sp_version::{GetNativeVersion, NativeVersion, RuntimeVersion}; use sp_wasm_interface::{ExtendedHostFunctions, HostFunctions}; -/// Default num of pages for the heap -const DEFAULT_HEAP_PAGES: u64 = 2048; - /// Set up the externalities and safe calling environment to execute runtime calls. /// /// If the inner closure panics, it will be caught and return an error. @@ -79,13 +78,155 @@ pub trait NativeExecutionDispatch: Send + Sync { fn native_version() -> NativeVersion; } +fn unwrap_heap_pages(pages: Option) -> HeapAllocStrategy { + pages.unwrap_or_else(|| DEFAULT_HEAP_ALLOC_STRATEGY) +} + +/// Builder for creating a [`WasmExecutor`] instance. +pub struct WasmExecutorBuilder { + _phantom: PhantomData, + method: WasmExecutionMethod, + onchain_heap_alloc_strategy: Option, + offchain_heap_alloc_strategy: Option, + ignore_onchain_heap_pages: bool, + max_runtime_instances: usize, + cache_path: Option, + allow_missing_host_functions: bool, + runtime_cache_size: u8, +} + +impl WasmExecutorBuilder { + /// Create a new instance of `Self` + /// + /// - `method`: The wasm execution method that should be used by the executor. + pub fn new() -> Self { + Self { + _phantom: PhantomData, + method: WasmExecutionMethod::default(), + onchain_heap_alloc_strategy: None, + offchain_heap_alloc_strategy: None, + ignore_onchain_heap_pages: false, + max_runtime_instances: 2, + runtime_cache_size: 4, + allow_missing_host_functions: false, + cache_path: None, + } + } + + /// Create the wasm executor with execution method that should be used by the executor. + pub fn with_execution_method(mut self, method: WasmExecutionMethod) -> Self { + self.method = method; + self + } + + /// Create the wasm executor with the given number of `heap_alloc_strategy` for onchain runtime + /// calls. + pub fn with_onchain_heap_alloc_strategy( + mut self, + heap_alloc_strategy: HeapAllocStrategy, + ) -> Self { + self.onchain_heap_alloc_strategy = Some(heap_alloc_strategy); + self + } + + /// Create the wasm executor with the given number of `heap_alloc_strategy` for offchain runtime + /// calls. + pub fn with_offchain_heap_alloc_strategy( + mut self, + heap_alloc_strategy: HeapAllocStrategy, + ) -> Self { + self.offchain_heap_alloc_strategy = Some(heap_alloc_strategy); + self + } + + /// Create the wasm executor and follow/ignore onchain heap pages value. + /// + /// By default this the onchain heap pages value is followed. + pub fn with_ignore_onchain_heap_pages(mut self, ignore_onchain_heap_pages: bool) -> Self { + self.ignore_onchain_heap_pages = ignore_onchain_heap_pages; + self + } + + /// Create the wasm executor with the given maximum number of `instances`. + /// + /// The number of `instances` defines how many different instances of a runtime the cache is + /// storing. + /// + /// By default the maximum number of `instances` is `2`. + pub fn with_max_runtime_instances(mut self, instances: usize) -> Self { + self.max_runtime_instances = instances; + self + } + + /// Create the wasm executor with the given `cache_path`. + /// + /// The `cache_path` is A path to a directory where the executor can place its files for + /// purposes of caching. This may be important in cases when there are many different modules + /// with the compiled execution method is used. + /// + /// By default there is no `cache_path` given. + pub fn with_cache_path(mut self, cache_path: impl Into) -> Self { + self.cache_path = Some(cache_path.into()); + self + } + + /// Create the wasm executor and allow/forbid missing host functions. + /// + /// If missing host functions are forbidden, the instantiation of a wasm blob will fail + /// for imported host functions that the executor is not aware of. If they are allowed, + /// a stub is generated that will return an error when being called while executing the wasm. + /// + /// By default missing host functions are forbidden. + pub fn with_allow_missing_host_functions(mut self, allow: bool) -> Self { + self.allow_missing_host_functions = allow; + self + } + + /// Create the wasm executor with the given `runtime_cache_size`. + /// + /// Defines the number of different runtimes/instantiated wasm blobs the cache stores. + /// Runtimes/wasm blobs are differentiated based on the hash and the number of heap pages. + /// + /// By default this value is set to `4`. + pub fn with_runtime_cache_size(mut self, runtime_cache_size: u8) -> Self { + self.runtime_cache_size = runtime_cache_size; + self + } + + /// Build the configured [`WasmExecutor`]. + pub fn build(self) -> WasmExecutor { + WasmExecutor { + method: self.method, + default_offchain_heap_alloc_strategy: unwrap_heap_pages( + self.offchain_heap_alloc_strategy, + ), + default_onchain_heap_alloc_strategy: unwrap_heap_pages( + self.onchain_heap_alloc_strategy, + ), + ignore_onchain_heap_pages: self.ignore_onchain_heap_pages, + cache: Arc::new(RuntimeCache::new( + self.max_runtime_instances, + self.cache_path.clone(), + self.runtime_cache_size, + )), + cache_path: self.cache_path, + allow_missing_host_functions: self.allow_missing_host_functions, + phantom: PhantomData, + } + } +} + /// An abstraction over Wasm code executor. Supports selecting execution backend and /// manages runtime cache. pub struct WasmExecutor { /// Method used to execute fallback Wasm code. method: WasmExecutionMethod, - /// The number of 64KB pages to allocate for Wasm execution. - default_heap_pages: u64, + /// The heap allocation strategy for onchain Wasm calls. + default_onchain_heap_alloc_strategy: HeapAllocStrategy, + /// The heap allocation strategy for offchain Wasm calls. + default_offchain_heap_alloc_strategy: HeapAllocStrategy, + /// Ignore onchain heap pages value. + ignore_onchain_heap_pages: bool, /// WASM runtime cache. cache: Arc, /// The path to a directory which the executor can leverage for a file cache, e.g. put there @@ -100,7 +241,9 @@ impl Clone for WasmExecutor { fn clone(&self) -> Self { Self { method: self.method, - default_heap_pages: self.default_heap_pages, + default_onchain_heap_alloc_strategy: self.default_onchain_heap_alloc_strategy, + default_offchain_heap_alloc_strategy: self.default_offchain_heap_alloc_strategy, + ignore_onchain_heap_pages: self.ignore_onchain_heap_pages, cache: self.cache.clone(), cache_path: self.cache_path.clone(), allow_missing_host_functions: self.allow_missing_host_functions, @@ -119,8 +262,10 @@ where /// /// `method` - Method used to execute Wasm code. /// - /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. - /// Defaults to `DEFAULT_HEAP_PAGES` if `None` is provided. + /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. Internally this + /// will be mapped as [`HeapAllocStrategy::Static`] where `default_heap_pages` represent the + /// static number of heap pages to allocate. Defaults to `DEFAULT_HEAP_ALLOC_STRATEGY` if `None` + /// is provided. /// /// `max_runtime_instances` - The number of runtime instances to keep in memory ready for reuse. /// @@ -129,6 +274,7 @@ where /// compiled execution method is used. /// /// `runtime_cache_size` - The capacity of runtime cache. + #[deprecated(note = "use `Self::builder` method instead of it")] pub fn new( method: WasmExecutionMethod, default_heap_pages: Option, @@ -138,7 +284,13 @@ where ) -> Self { WasmExecutor { method, - default_heap_pages: default_heap_pages.unwrap_or(DEFAULT_HEAP_PAGES), + default_onchain_heap_alloc_strategy: unwrap_heap_pages( + default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }), + ), + default_offchain_heap_alloc_strategy: unwrap_heap_pages( + default_heap_pages.map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }), + ), + ignore_onchain_heap_pages: false, cache: Arc::new(RuntimeCache::new( max_runtime_instances, cache_path.clone(), @@ -150,7 +302,13 @@ where } } + /// Instantiate a builder for creating an instance of `Self`. + pub fn builder() -> WasmExecutorBuilder { + WasmExecutorBuilder::new() + } + /// Ignore missing function imports if set true. + #[deprecated(note = "use `Self::builder` method instead of it")] pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) { self.allow_missing_host_functions = allow_missing_host_functions } @@ -172,11 +330,12 @@ where &self, runtime_code: &RuntimeCode, ext: &mut dyn Externalities, + heap_alloc_strategy: HeapAllocStrategy, f: F, ) -> Result where F: FnOnce( - AssertUnwindSafe<&Arc>, + AssertUnwindSafe<&dyn WasmModule>, AssertUnwindSafe<&mut dyn WasmInstance>, Option<&RuntimeVersion>, AssertUnwindSafe<&mut dyn Externalities>, @@ -186,7 +345,7 @@ where runtime_code, ext, self.method, - self.default_heap_pages, + heap_alloc_strategy, self.allow_missing_host_functions, |module, instance, version, ext| { let module = AssertUnwindSafe(module); @@ -259,7 +418,7 @@ where ) -> std::result::Result, Error> { let module = crate::wasm_runtime::create_wasm_runtime_with_code::( self.method, - self.default_heap_pages, + self.default_onchain_heap_alloc_strategy, runtime_blob, allow_missing_host_functions, self.cache_path.as_deref(), @@ -334,6 +493,7 @@ where method: &str, data: &[u8], _use_native: bool, + context: CallContext, ) -> (Result>, bool) { tracing::trace!( target: "executor", @@ -341,10 +501,29 @@ where "Executing function", ); - let result = - self.with_instance(runtime_code, ext, |_, mut instance, _onchain_version, mut ext| { + let on_chain_heap_alloc_strategy = if self.ignore_onchain_heap_pages { + self.default_onchain_heap_alloc_strategy + } else { + runtime_code + .heap_pages + .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }) + .unwrap_or_else(|| self.default_onchain_heap_alloc_strategy) + }; + + let heap_alloc_strategy = match context { + CallContext::Offchain => self.default_offchain_heap_alloc_strategy, + CallContext::Onchain => on_chain_heap_alloc_strategy, + }; + + let result = self.with_instance( + runtime_code, + ext, + heap_alloc_strategy, + |_, mut instance, _onchain_version, mut ext| { with_externalities_safe(&mut **ext, move || instance.call_export(method, data)) - }); + }, + ); + (result, false) } } @@ -358,20 +537,29 @@ where ext: &mut dyn Externalities, runtime_code: &RuntimeCode, ) -> Result { - self.with_instance(runtime_code, ext, |_module, _instance, version, _ext| { - Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into()))) - }) + let on_chain_heap_pages = if self.ignore_onchain_heap_pages { + self.default_onchain_heap_alloc_strategy + } else { + runtime_code + .heap_pages + .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }) + .unwrap_or_else(|| self.default_onchain_heap_alloc_strategy) + }; + + self.with_instance( + runtime_code, + ext, + on_chain_heap_pages, + |_module, _instance, version, _ext| { + Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into()))) + }, + ) } } /// A generic `CodeExecutor` implementation that uses a delegate to determine wasm code equivalence /// and dispatch to native code when possible, falling back on `WasmExecutor` when not. -pub struct NativeElseWasmExecutor -where - D: NativeExecutionDispatch, -{ - /// Dummy field to avoid the compiler complaining about us not using `D`. - _dummy: PhantomData, +pub struct NativeElseWasmExecutor { /// Native runtime version info. native_version: NativeVersion, /// Fallback wasm executor. @@ -380,40 +568,53 @@ where } impl NativeElseWasmExecutor { + /// /// Create new instance. /// /// # Parameters /// /// `fallback_method` - Method used to execute fallback Wasm code. /// - /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. - /// Defaults to `DEFAULT_HEAP_PAGES` if `None` is provided. + /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. Internally this + /// will be mapped as [`HeapAllocStrategy::Static`] where `default_heap_pages` represent the + /// static number of heap pages to allocate. Defaults to `DEFAULT_HEAP_ALLOC_STRATEGY` if `None` + /// is provided. /// /// `max_runtime_instances` - The number of runtime instances to keep in memory ready for reuse. /// /// `runtime_cache_size` - The capacity of runtime cache. + #[deprecated(note = "use `Self::new_with_wasm_executor` method instead of it")] pub fn new( fallback_method: WasmExecutionMethod, default_heap_pages: Option, max_runtime_instances: usize, runtime_cache_size: u8, ) -> Self { - let wasm = WasmExecutor::new( - fallback_method, - default_heap_pages, - max_runtime_instances, - None, - runtime_cache_size, - ); + let heap_pages = default_heap_pages.map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |h| { + HeapAllocStrategy::Static { extra_pages: h as _ } + }); + let wasm = WasmExecutor::builder() + .with_execution_method(fallback_method) + .with_onchain_heap_alloc_strategy(heap_pages) + .with_offchain_heap_alloc_strategy(heap_pages) + .with_max_runtime_instances(max_runtime_instances) + .with_runtime_cache_size(runtime_cache_size) + .build(); + + NativeElseWasmExecutor { native_version: D::native_version(), wasm } + } - NativeElseWasmExecutor { - _dummy: Default::default(), - native_version: D::native_version(), - wasm, - } + /// Create a new instance using the given [`WasmExecutor`]. + pub fn new_with_wasm_executor( + executor: WasmExecutor< + ExtendedHostFunctions, + >, + ) -> Self { + Self { native_version: D::native_version(), wasm: executor } } /// Ignore missing function imports if set true. + #[deprecated(note = "use `Self::new_with_wasm_executor` method instead of it")] pub fn allow_missing_host_functions(&mut self, allow_missing_host_functions: bool) { self.wasm.allow_missing_host_functions = allow_missing_host_functions } @@ -425,9 +626,7 @@ impl RuntimeVersionOf for NativeElseWasmExecutor ext: &mut dyn Externalities, runtime_code: &RuntimeCode, ) -> Result { - self.wasm.with_instance(runtime_code, ext, |_module, _instance, version, _ext| { - Ok(version.cloned().ok_or_else(|| Error::ApiError("Unknown version".into()))) - }) + self.wasm.runtime_version(ext, runtime_code) } } @@ -447,6 +646,7 @@ impl CodeExecutor for NativeElseWasmExecut method: &str, data: &[u8], use_native: bool, + context: CallContext, ) -> (Result>, bool) { tracing::trace!( target: "executor", @@ -454,10 +654,25 @@ impl CodeExecutor for NativeElseWasmExecut "Executing function", ); + let on_chain_heap_alloc_strategy = if self.wasm.ignore_onchain_heap_pages { + self.wasm.default_onchain_heap_alloc_strategy + } else { + runtime_code + .heap_pages + .map(|h| HeapAllocStrategy::Static { extra_pages: h as _ }) + .unwrap_or_else(|| self.wasm.default_onchain_heap_alloc_strategy) + }; + + let heap_alloc_strategy = match context { + CallContext::Offchain => self.wasm.default_offchain_heap_alloc_strategy, + CallContext::Onchain => on_chain_heap_alloc_strategy, + }; + let mut used_native = false; let result = self.wasm.with_instance( runtime_code, ext, + heap_alloc_strategy, |_, mut instance, onchain_version, mut ext| { let onchain_version = onchain_version.ok_or_else(|| Error::ApiError("Unknown version".into()))?; @@ -496,11 +711,7 @@ impl CodeExecutor for NativeElseWasmExecut impl Clone for NativeElseWasmExecutor { fn clone(&self) -> Self { - NativeElseWasmExecutor { - _dummy: Default::default(), - native_version: D::native_version(), - wasm: self.wasm.clone(), - } + NativeElseWasmExecutor { native_version: D::native_version(), wasm: self.wasm.clone() } } } @@ -542,11 +753,8 @@ mod tests { #[test] fn native_executor_registers_custom_interface() { - let executor = NativeElseWasmExecutor::::new( - WasmExecutionMethod::Interpreted, - None, - 8, - 2, + let executor = NativeElseWasmExecutor::::new_with_wasm_executor( + WasmExecutor::builder().build(), ); fn extract_host_functions( diff --git a/client/executor/src/integration_tests/linux.rs b/client/executor/src/integration_tests/linux.rs index efac4e74bb8e..68ac37e9011a 100644 --- a/client/executor/src/integration_tests/linux.rs +++ b/client/executor/src/integration_tests/linux.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,28 +18,44 @@ //! Tests that are only relevant for Linux. +mod smaps; + use super::mk_test_runtime; use crate::WasmExecutionMethod; use codec::Encode as _; - -mod smaps; +use sc_executor_common::wasm_runtime::DEFAULT_HEAP_ALLOC_STRATEGY; use self::smaps::Smaps; #[test] fn memory_consumption_compiled() { + let _ = sp_tracing::try_init_simple(); + + if std::env::var("RUN_TEST").is_ok() { + memory_consumption(WasmExecutionMethod::Compiled { + instantiation_strategy: + sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse, + }); + } else { + // We need to run the test in isolation, to not getting interfered by the other tests. + let executable = std::env::current_exe().unwrap(); + let status = std::process::Command::new(executable) + .env("RUN_TEST", "1") + .args(&["--nocapture", "memory_consumption_compiled"]) + .status() + .unwrap(); + + assert!(status.success()); + } +} + +fn memory_consumption(wasm_method: WasmExecutionMethod) { // This aims to see if linear memory stays backed by the physical memory after a runtime call. // // For that we make a series of runtime calls, probing the RSS for the VMA matching the linear // memory. After the call we expect RSS to be equal to 0. - let runtime = mk_test_runtime( - WasmExecutionMethod::Compiled { - instantiation_strategy: - sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse, - }, - 1024, - ); + let runtime = mk_test_runtime(wasm_method, DEFAULT_HEAP_ALLOC_STRATEGY); let mut instance = runtime.new_instance().unwrap(); let heap_base = instance diff --git a/client/executor/src/integration_tests/linux/smaps.rs b/client/executor/src/integration_tests/linux/smaps.rs index 665155ff81ea..1ac570dd8d5f 100644 --- a/client/executor/src/integration_tests/linux/smaps.rs +++ b/client/executor/src/integration_tests/linux/smaps.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/executor/src/integration_tests/mod.rs b/client/executor/src/integration_tests/mod.rs index 22e651abe98e..37aed8eef96a 100644 --- a/client/executor/src/integration_tests/mod.rs +++ b/client/executor/src/integration_tests/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -22,9 +22,9 @@ mod linux; use assert_matches::assert_matches; use codec::{Decode, Encode}; use sc_executor_common::{ - error::{Error, WasmError}, + error::Error, runtime_blob::RuntimeBlob, - wasm_runtime::WasmModule, + wasm_runtime::{HeapAllocStrategy, WasmModule}, }; use sc_runtime_test::wasm_binary_unwrap; use sp_core::{ @@ -50,13 +50,9 @@ type HostFunctions = sp_io::SubstrateHostFunctions; macro_rules! test_wasm_execution { ($method_name:ident) => { paste::item! { - #[test] - fn [<$method_name _interpreted>]() { - $method_name(WasmExecutionMethod::Interpreted); - } - #[test] fn [<$method_name _compiled_recreate_instance_cow>]() { + let _ = sp_tracing::try_init_simple(); $method_name(WasmExecutionMethod::Compiled { instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstanceCopyOnWrite }); @@ -64,6 +60,7 @@ macro_rules! test_wasm_execution { #[test] fn [<$method_name _compiled_recreate_instance_vanilla>]() { + let _ = sp_tracing::try_init_simple(); $method_name(WasmExecutionMethod::Compiled { instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::RecreateInstance }); @@ -71,6 +68,7 @@ macro_rules! test_wasm_execution { #[test] fn [<$method_name _compiled_pooling_cow>]() { + let _ = sp_tracing::try_init_simple(); $method_name(WasmExecutionMethod::Compiled { instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite }); @@ -78,6 +76,7 @@ macro_rules! test_wasm_execution { #[test] fn [<$method_name _compiled_pooling_vanilla>]() { + let _ = sp_tracing::try_init_simple(); $method_name(WasmExecutionMethod::Compiled { instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::Pooling }); @@ -85,21 +84,13 @@ macro_rules! test_wasm_execution { #[test] fn [<$method_name _compiled_legacy_instance_reuse>]() { + let _ = sp_tracing::try_init_simple(); $method_name(WasmExecutionMethod::Compiled { instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::LegacyInstanceReuse }); } } }; - - (interpreted_only $method_name:ident) => { - paste::item! { - #[test] - fn [<$method_name _interpreted>]() { - $method_name(WasmExecutionMethod::Interpreted); - } - } - }; } fn call_in_wasm( @@ -108,8 +99,10 @@ fn call_in_wasm( execution_method: WasmExecutionMethod, ext: &mut E, ) -> Result, Error> { - let executor = - crate::WasmExecutor::::new(execution_method, Some(1024), 8, None, 2); + let executor = crate::WasmExecutor::::builder() + .with_execution_method(execution_method) + .build(); + executor.uncached_call( RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()).unwrap(), ext, @@ -136,8 +129,8 @@ fn call_not_existing_function(wasm_method: WasmExecutionMethod) { match call_in_wasm("test_calling_missing_external", &[], wasm_method, &mut ext).unwrap_err() { Error::AbortedDueToTrap(error) => { let expected = match wasm_method { - WasmExecutionMethod::Interpreted => "Other: Function `missing_external` is only a stub. Calling a stub is not allowed.", - WasmExecutionMethod::Compiled { .. } => "call to a missing function env:missing_external" + WasmExecutionMethod::Compiled { .. } => + "call to a missing function env:missing_external", }; assert_eq!(error.message, expected); }, @@ -155,8 +148,8 @@ fn call_yet_another_not_existing_function(wasm_method: WasmExecutionMethod) { { Error::AbortedDueToTrap(error) => { let expected = match wasm_method { - WasmExecutionMethod::Interpreted => "Other: Function `yet_another_missing_external` is only a stub. Calling a stub is not allowed.", - WasmExecutionMethod::Compiled { .. } => "call to a missing function env:yet_another_missing_external" + WasmExecutionMethod::Compiled { .. } => + "call to a missing function env:yet_another_missing_external", }; assert_eq!(error.message, expected); }, @@ -440,13 +433,11 @@ test_wasm_execution!(should_trap_when_heap_exhausted); fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) { let mut ext = TestExternalities::default(); - let executor = crate::WasmExecutor::::new( - wasm_method, - Some(17), // `17` is the initial number of pages compiled into the binary. - 8, - None, - 2, - ); + let executor = crate::WasmExecutor::::builder() + .with_execution_method(wasm_method) + // `17` is the initial number of pages compiled into the binary. + .with_onchain_heap_alloc_strategy(HeapAllocStrategy::Static { extra_pages: 17 }) + .build(); let err = executor .uncached_call( @@ -467,14 +458,14 @@ fn should_trap_when_heap_exhausted(wasm_method: WasmExecutionMethod) { r#"host code panicked while being called by the runtime: Failed to allocate memory: "Allocator ran out of space""# ); }, - Error::RuntimePanicked(error) if wasm_method == WasmExecutionMethod::Interpreted => { - assert_eq!(error, r#"Failed to allocate memory: "Allocator ran out of space""#); - }, error => panic!("unexpected error: {:?}", error), } } -fn mk_test_runtime(wasm_method: WasmExecutionMethod, pages: u64) -> Arc { +fn mk_test_runtime( + wasm_method: WasmExecutionMethod, + pages: HeapAllocStrategy, +) -> Box { let blob = RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()) .expect("failed to create a runtime blob out of test runtime"); @@ -490,7 +481,8 @@ fn mk_test_runtime(wasm_method: WasmExecutionMethod, pages: u64) -> Arc::new( - wasm_method, - Some(1024), - 8, - None, - 2, - )); + let executor = Arc::new( + crate::WasmExecutor::::builder() + .with_execution_method(wasm_method) + .build(), + ); let threads: Vec<_> = (0..8) .map(|_| { let executor = executor.clone(); @@ -651,6 +626,15 @@ fn wasm_tracing_should_work(wasm_method: WasmExecutionMethod) { assert_eq!(len, 2); } +test_wasm_execution!(allocate_two_gigabyte); +fn allocate_two_gigabyte(wasm_method: WasmExecutionMethod) { + let runtime = mk_test_runtime(wasm_method, HeapAllocStrategy::Dynamic { maximum_pages: None }); + + let mut instance = runtime.new_instance().unwrap(); + let res = instance.call_export("allocate_two_gigabyte", &[0]).unwrap(); + assert_eq!(10 * 1024 * 1024 * 205, u32::decode(&mut &res[..]).unwrap()); +} + test_wasm_execution!(memory_is_cleared_between_invocations); fn memory_is_cleared_between_invocations(wasm_method: WasmExecutionMethod) { // This is based on the code generated by compiling a runtime *without* @@ -713,7 +697,7 @@ fn memory_is_cleared_between_invocations(wasm_method: WasmExecutionMethod) { let runtime = crate::wasm_runtime::create_wasm_runtime_with_code::( wasm_method, - 1024, + HeapAllocStrategy::Dynamic { maximum_pages: Some(1024) }, RuntimeBlob::uncompress_if_needed(&binary[..]).unwrap(), true, None, @@ -766,7 +750,6 @@ fn unreachable_intrinsic(wasm_method: WasmExecutionMethod) { match call_in_wasm("test_unreachable_intrinsic", &[], wasm_method, &mut ext).unwrap_err() { Error::AbortedDueToTrap(error) => { let expected = match wasm_method { - WasmExecutionMethod::Interpreted => "unreachable", WasmExecutionMethod::Compiled { .. } => "wasm trap: wasm `unreachable` instruction executed", }; @@ -793,12 +776,8 @@ fn return_huge_len(wasm_method: WasmExecutionMethod) { let mut ext = ext.ext(); match call_in_wasm("test_return_huge_len", &[], wasm_method, &mut ext).unwrap_err() { - Error::Runtime => { - assert_matches!(wasm_method, WasmExecutionMethod::Interpreted); - }, - Error::RuntimeConstruction(WasmError::Other(error)) => { + Error::OutputExceedsBounds => { assert_matches!(wasm_method, WasmExecutionMethod::Compiled { .. }); - assert_eq!(error, "output exceeds bounds of wasm memory"); }, error => panic!("unexpected error: {:?}", error), } @@ -811,7 +790,7 @@ fn return_max_memory_offset(wasm_method: WasmExecutionMethod) { assert_eq!( call_in_wasm("test_return_max_memory_offset", &[], wasm_method, &mut ext).unwrap(), - ().encode() + (u8::MAX).encode() ); } @@ -823,12 +802,8 @@ fn return_max_memory_offset_plus_one(wasm_method: WasmExecutionMethod) { match call_in_wasm("test_return_max_memory_offset_plus_one", &[], wasm_method, &mut ext) .unwrap_err() { - Error::Runtime => { - assert_matches!(wasm_method, WasmExecutionMethod::Interpreted); - }, - Error::RuntimeConstruction(WasmError::Other(error)) => { + Error::OutputExceedsBounds => { assert_matches!(wasm_method, WasmExecutionMethod::Compiled { .. }); - assert_eq!(error, "output exceeds bounds of wasm memory"); }, error => panic!("unexpected error: {:?}", error), } @@ -840,12 +815,8 @@ fn return_overflow(wasm_method: WasmExecutionMethod) { let mut ext = ext.ext(); match call_in_wasm("test_return_overflow", &[], wasm_method, &mut ext).unwrap_err() { - Error::Runtime => { - assert_matches!(wasm_method, WasmExecutionMethod::Interpreted); - }, - Error::RuntimeConstruction(WasmError::Other(error)) => { + Error::OutputExceedsBounds => { assert_matches!(wasm_method, WasmExecutionMethod::Compiled { .. }); - assert_eq!(error, "output exceeds bounds of wasm memory"); }, error => panic!("unexpected error: {:?}", error), } diff --git a/client/executor/src/lib.rs b/client/executor/src/lib.rs index 0670b840949d..6ee0ab3512ac 100644 --- a/client/executor/src/lib.rs +++ b/client/executor/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -32,24 +32,28 @@ #![recursion_limit = "128"] #[macro_use] -mod native_executor; +mod executor; #[cfg(test)] mod integration_tests; mod wasm_runtime; -pub use codec::Codec; -pub use native_executor::{ - with_externalities_safe, NativeElseWasmExecutor, NativeExecutionDispatch, WasmExecutor, +pub use self::{ + executor::{ + with_externalities_safe, NativeElseWasmExecutor, NativeExecutionDispatch, WasmExecutor, + }, + wasm_runtime::{read_embedded_version, WasmExecutionMethod}, }; +pub use codec::Codec; #[doc(hidden)] pub use sp_core::traits::Externalities; pub use sp_version::{NativeVersion, RuntimeVersion}; #[doc(hidden)] pub use sp_wasm_interface; -pub use wasm_runtime::{read_embedded_version, WasmExecutionMethod}; -pub use wasmi; -pub use sc_executor_common::error; +pub use sc_executor_common::{ + error, + wasm_runtime::{HeapAllocStrategy, DEFAULT_HEAP_ALLOC_PAGES, DEFAULT_HEAP_ALLOC_STRATEGY}, +}; pub use sc_executor_wasmtime::InstantiationStrategy as WasmtimeInstantiationStrategy; /// Extracts the runtime version of a given runtime code. @@ -74,13 +78,7 @@ mod tests { let mut ext = TestExternalities::default(); let mut ext = ext.ext(); - let executor = WasmExecutor::::new( - WasmExecutionMethod::Interpreted, - Some(8), - 8, - None, - 2, - ); + let executor = WasmExecutor::::builder().build(); let res = executor .uncached_call( RuntimeBlob::uncompress_if_needed(wasm_binary_unwrap()).unwrap(), diff --git a/client/executor/src/wasm_runtime.rs b/client/executor/src/wasm_runtime.rs index 5576fff186bb..6dec3abdb20c 100644 --- a/client/executor/src/wasm_runtime.rs +++ b/client/executor/src/wasm_runtime.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -22,29 +22,27 @@ //! components of the runtime that are expensive to initialize. use crate::error::{Error, WasmError}; + use codec::Decode; -use lru::LruCache; use parking_lot::Mutex; use sc_executor_common::{ runtime_blob::RuntimeBlob, - wasm_runtime::{WasmInstance, WasmModule}, + wasm_runtime::{HeapAllocStrategy, WasmInstance, WasmModule}, }; +use schnellru::{ByLength, LruMap}; use sp_core::traits::{Externalities, FetchRuntimeCode, RuntimeCode}; use sp_version::RuntimeVersion; +use sp_wasm_interface::HostFunctions; + use std::{ - num::NonZeroUsize, panic::AssertUnwindSafe, path::{Path, PathBuf}, sync::Arc, }; -use sp_wasm_interface::HostFunctions; - /// Specification of different methods of executing the runtime Wasm code. #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] pub enum WasmExecutionMethod { - /// Uses the Wasmi interpreter. - Interpreted, /// Uses the Wasmtime compiled runtime. Compiled { /// The instantiation strategy to use. @@ -53,8 +51,10 @@ pub enum WasmExecutionMethod { } impl Default for WasmExecutionMethod { - fn default() -> WasmExecutionMethod { - WasmExecutionMethod::Interpreted + fn default() -> Self { + Self::Compiled { + instantiation_strategy: sc_executor_wasmtime::InstantiationStrategy::PoolingCopyOnWrite, + } } } @@ -64,21 +64,21 @@ struct VersionedRuntimeId { code_hash: Vec, /// Wasm runtime type. wasm_method: WasmExecutionMethod, - /// The number of WebAssembly heap pages this instance was created with. - heap_pages: u64, + /// The heap allocation strategy this runtime was created with. + heap_alloc_strategy: HeapAllocStrategy, } /// A Wasm runtime object along with its cached runtime version. struct VersionedRuntime { /// Shared runtime that can spawn instances. - module: Arc, + module: Box, /// Runtime version according to `Core_version` if any. version: Option, // TODO: Remove this once the legacy instance reuse instantiation strategy // for `wasmtime` is gone, as this only makes sense with that particular strategy. /// Cached instance pool. - instances: Arc>>>>, + instances: Vec>>>, } impl VersionedRuntime { @@ -86,7 +86,7 @@ impl VersionedRuntime { fn with_instance(&self, ext: &mut dyn Externalities, f: F) -> Result where F: FnOnce( - &Arc, + &dyn WasmModule, &mut dyn WasmInstance, Option<&RuntimeVersion>, &mut dyn Externalities, @@ -106,7 +106,7 @@ impl VersionedRuntime { .map(|r| Ok((r, false))) .unwrap_or_else(|| self.module.new_instance().map(|i| (i, true)))?; - let result = f(&self.module, &mut *instance, self.version.as_ref(), ext); + let result = f(&*self.module, &mut *instance, self.version.as_ref(), ext); if let Err(e) = &result { if new_inst { tracing::warn!( @@ -142,7 +142,7 @@ impl VersionedRuntime { // Allocate a new instance let mut instance = self.module.new_instance()?; - f(&self.module, &mut *instance, self.version.as_ref(), ext) + f(&*self.module, &mut *instance, self.version.as_ref(), ext) }, } } @@ -163,7 +163,7 @@ pub struct RuntimeCache { /// A cache of runtimes along with metadata. /// /// Runtimes sorted by recent usage. The most recently used is at the front. - runtimes: Mutex>>, + runtimes: Mutex>>, /// The size of the instances cache for each runtime. max_runtime_instances: usize, cache_path: Option, @@ -185,9 +185,8 @@ impl RuntimeCache { cache_path: Option, runtime_cache_size: u8, ) -> RuntimeCache { - let cap = - NonZeroUsize::new(runtime_cache_size.max(1) as usize).expect("cache size is not zero"); - RuntimeCache { runtimes: Mutex::new(LruCache::new(cap)), max_runtime_instances, cache_path } + let cap = ByLength::new(runtime_cache_size.max(1) as u32); + RuntimeCache { runtimes: Mutex::new(LruMap::new(cap)), max_runtime_instances, cache_path } } /// Prepares a WASM module instance and executes given function for it. @@ -197,10 +196,12 @@ impl RuntimeCache { /// /// `runtime_code` - The runtime wasm code used setup the runtime. /// - /// `default_heap_pages` - Number of 64KB pages to allocate for Wasm execution. + /// `ext` - The externalities to access the state. /// /// `wasm_method` - Type of WASM backend to use. /// + /// `heap_alloc_strategy` - The heap allocation strategy to use. + /// /// `allow_missing_func_imports` - Ignore missing function imports. /// /// `f` - Function to execute. @@ -219,24 +220,23 @@ impl RuntimeCache { runtime_code: &'c RuntimeCode<'c>, ext: &mut dyn Externalities, wasm_method: WasmExecutionMethod, - default_heap_pages: u64, + heap_alloc_strategy: HeapAllocStrategy, allow_missing_func_imports: bool, f: F, ) -> Result, Error> where H: HostFunctions, F: FnOnce( - &Arc, + &dyn WasmModule, &mut dyn WasmInstance, Option<&RuntimeVersion>, &mut dyn Externalities, ) -> Result, { let code_hash = &runtime_code.hash; - let heap_pages = runtime_code.heap_pages.unwrap_or(default_heap_pages); let versioned_runtime_id = - VersionedRuntimeId { code_hash: code_hash.clone(), heap_pages, wasm_method }; + VersionedRuntimeId { code_hash: code_hash.clone(), heap_alloc_strategy, wasm_method }; let mut runtimes = self.runtimes.lock(); // this must be released prior to calling f let versioned_runtime = if let Some(versioned_runtime) = runtimes.get(&versioned_runtime_id) @@ -251,7 +251,7 @@ impl RuntimeCache { &code, ext, wasm_method, - heap_pages, + heap_alloc_strategy, allow_missing_func_imports, self.max_runtime_instances, self.cache_path.as_deref(), @@ -274,7 +274,7 @@ impl RuntimeCache { let versioned_runtime = Arc::new(result?); // Save new versioned wasm runtime in cache - runtimes.put(versioned_runtime_id, versioned_runtime.clone()); + runtimes.insert(versioned_runtime_id, versioned_runtime.clone()); versioned_runtime }; @@ -289,30 +289,15 @@ impl RuntimeCache { /// Create a wasm runtime with the given `code`. pub fn create_wasm_runtime_with_code( wasm_method: WasmExecutionMethod, - heap_pages: u64, + heap_alloc_strategy: HeapAllocStrategy, blob: RuntimeBlob, allow_missing_func_imports: bool, cache_path: Option<&Path>, -) -> Result, WasmError> +) -> Result, WasmError> where H: HostFunctions, { match wasm_method { - WasmExecutionMethod::Interpreted => { - // Wasmi doesn't have any need in a cache directory. - // - // We drop the cache_path here to silence warnings that cache_path is not used if - // compiling without the `wasmtime` flag. - let _ = cache_path; - - sc_executor_wasmi::create_runtime( - blob, - heap_pages, - H::host_functions(), - allow_missing_func_imports, - ) - .map(|runtime| -> Arc { Arc::new(runtime) }) - }, WasmExecutionMethod::Compiled { instantiation_strategy } => sc_executor_wasmtime::create_runtime::( blob, @@ -320,16 +305,19 @@ where allow_missing_func_imports, cache_path: cache_path.map(ToOwned::to_owned), semantics: sc_executor_wasmtime::Semantics { - extra_heap_pages: heap_pages, + heap_alloc_strategy, instantiation_strategy, deterministic_stack_limit: None, canonicalize_nans: false, parallel_compilation: true, - max_memory_size: None, + wasm_multi_value: false, + wasm_bulk_memory: false, + wasm_reference_types: false, + wasm_simd: false, }, }, ) - .map(|runtime| -> Arc { Arc::new(runtime) }), + .map(|runtime| -> Box { Box::new(runtime) }), } } @@ -393,7 +381,7 @@ fn create_versioned_wasm_runtime( code: &[u8], ext: &mut dyn Externalities, wasm_method: WasmExecutionMethod, - heap_pages: u64, + heap_alloc_strategy: HeapAllocStrategy, allow_missing_func_imports: bool, max_instances: usize, cache_path: Option<&Path>, @@ -408,11 +396,11 @@ where // Use the runtime blob to scan if there is any metadata embedded into the wasm binary // pertaining to runtime version. We do it before consuming the runtime blob for creating the // runtime. - let mut version: Option<_> = read_embedded_version(&blob)?; + let mut version = read_embedded_version(&blob)?; let runtime = create_wasm_runtime_with_code::( wasm_method, - heap_pages, + heap_alloc_strategy, blob, allow_missing_func_imports, cache_path, @@ -430,7 +418,7 @@ where // The following unwind safety assertion is OK because if the method call panics, the // runtime will be dropped. let runtime = AssertUnwindSafe(runtime.as_ref()); - crate::native_executor::with_externalities_safe(&mut **ext, move || { + crate::executor::with_externalities_safe(&mut **ext, move || { runtime.new_instance()?.call("Core_version".into(), &[]) }) .map_err(|_| WasmError::Instantiation("panic in call to get runtime version".into()))? @@ -444,7 +432,7 @@ where let mut instances = Vec::with_capacity(max_instances); instances.resize_with(max_instances, || Mutex::new(None)); - Ok(VersionedRuntime { module: runtime, version, instances: Arc::new(instances) }) + Ok(VersionedRuntime { module: runtime, version, instances }) } #[cfg(test)] diff --git a/client/executor/wasmi/Cargo.toml b/client/executor/wasmi/Cargo.toml deleted file mode 100644 index 4235440023c8..000000000000 --- a/client/executor/wasmi/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "sc-executor-wasmi" -version = "0.10.0-dev" -authors = ["Parity Technologies "] -edition = "2021" -license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.io" -repository = "https://github.com/paritytech/substrate/" -description = "This crate provides an implementation of `WasmRuntime` that is baked by wasmi." -documentation = "https://docs.rs/sc-executor-wasmi" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -log = "0.4.17" -wasmi = "0.13" -sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } -sc-executor-common = { version = "0.10.0-dev", path = "../common" } -sp-runtime-interface = { version = "7.0.0", path = "../../../primitives/runtime-interface" } -sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" } diff --git a/client/executor/wasmi/README.md b/client/executor/wasmi/README.md deleted file mode 100644 index ad613aa1245e..000000000000 --- a/client/executor/wasmi/README.md +++ /dev/null @@ -1,3 +0,0 @@ -This crate provides an implementation of `WasmModule` that is baked by wasmi. - -License: GPL-3.0-or-later WITH Classpath-exception-2.0 \ No newline at end of file diff --git a/client/executor/wasmi/src/lib.rs b/client/executor/wasmi/src/lib.rs deleted file mode 100644 index 6eb38146946b..000000000000 --- a/client/executor/wasmi/src/lib.rs +++ /dev/null @@ -1,604 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. -// 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 . - -//! This crate provides an implementation of `WasmModule` that is baked by wasmi. - -use std::{cell::RefCell, str, sync::Arc}; - -use log::{debug, error, trace}; -use wasmi::{ - memory_units::Pages, - FuncInstance, ImportsBuilder, MemoryInstance, MemoryRef, Module, ModuleInstance, ModuleRef, - RuntimeValue::{self, I32, I64}, - TableRef, -}; - -use sc_allocator::AllocationStats; -use sc_executor_common::{ - error::{Error, MessageWithBacktrace, WasmError}, - runtime_blob::{DataSegmentsSnapshot, RuntimeBlob}, - wasm_runtime::{InvokeMethod, WasmInstance, WasmModule}, -}; -use sp_runtime_interface::unpack_ptr_and_len; -use sp_wasm_interface::{Function, FunctionContext, Pointer, Result as WResult, WordSize}; - -struct FunctionExecutor { - heap: RefCell, - memory: MemoryRef, - host_functions: Arc>, - allow_missing_func_imports: bool, - missing_functions: Arc>, - panic_message: Option, -} - -impl FunctionExecutor { - fn new( - m: MemoryRef, - heap_base: u32, - host_functions: Arc>, - allow_missing_func_imports: bool, - missing_functions: Arc>, - ) -> Result { - Ok(FunctionExecutor { - heap: RefCell::new(sc_allocator::FreeingBumpHeapAllocator::new(heap_base)), - memory: m, - host_functions, - allow_missing_func_imports, - missing_functions, - panic_message: None, - }) - } -} - -impl FunctionContext for FunctionExecutor { - fn read_memory_into(&self, address: Pointer, dest: &mut [u8]) -> WResult<()> { - self.memory.get_into(address.into(), dest).map_err(|e| e.to_string()) - } - - fn write_memory(&mut self, address: Pointer, data: &[u8]) -> WResult<()> { - self.memory.set(address.into(), data).map_err(|e| e.to_string()) - } - - fn allocate_memory(&mut self, size: WordSize) -> WResult> { - let heap = &mut self.heap.borrow_mut(); - self.memory - .with_direct_access_mut(|mem| heap.allocate(mem, size).map_err(|e| e.to_string())) - } - - fn deallocate_memory(&mut self, ptr: Pointer) -> WResult<()> { - let heap = &mut self.heap.borrow_mut(); - self.memory - .with_direct_access_mut(|mem| heap.deallocate(mem, ptr).map_err(|e| e.to_string())) - } - - fn register_panic_error_message(&mut self, message: &str) { - self.panic_message = Some(message.to_owned()); - } -} - -/// Will be used on initialization of a module to resolve function and memory imports. -struct Resolver<'a> { - /// All the hot functions that we export for the WASM blob. - host_functions: &'a [&'static dyn Function], - /// Should we allow missing function imports? - /// - /// If `true`, we return a stub that will return an error when being called. - allow_missing_func_imports: bool, - /// All the names of functions for that we did not provide a host function. - missing_functions: RefCell>, - /// Will be used as initial and maximum size of the imported memory. - heap_pages: usize, - /// By default, runtimes should import memory and this is `Some(_)` after - /// resolving. However, to be backwards compatible, we also support memory - /// exported by the WASM blob (this will be `None` after resolving). - import_memory: RefCell>, -} - -impl<'a> Resolver<'a> { - fn new( - host_functions: &'a [&'static dyn Function], - allow_missing_func_imports: bool, - heap_pages: usize, - ) -> Resolver<'a> { - Resolver { - host_functions, - allow_missing_func_imports, - missing_functions: RefCell::new(Vec::new()), - heap_pages, - import_memory: Default::default(), - } - } -} - -impl<'a> wasmi::ModuleImportResolver for Resolver<'a> { - fn resolve_func( - &self, - name: &str, - signature: &wasmi::Signature, - ) -> std::result::Result { - let signature = sp_wasm_interface::Signature::from(signature); - for (function_index, function) in self.host_functions.iter().enumerate() { - if name == function.name() { - if signature == function.signature() { - return Ok(wasmi::FuncInstance::alloc_host(signature.into(), function_index)) - } else { - return Err(wasmi::Error::Instantiation(format!( - "Invalid signature for function `{}` expected `{:?}`, got `{:?}`", - function.name(), - signature, - function.signature(), - ))) - } - } - } - - if self.allow_missing_func_imports { - trace!(target: "wasm-executor", "Could not find function `{}`, a stub will be provided instead.", name); - let id = self.missing_functions.borrow().len() + self.host_functions.len(); - self.missing_functions.borrow_mut().push(name.to_string()); - - Ok(wasmi::FuncInstance::alloc_host(signature.into(), id)) - } else { - Err(wasmi::Error::Instantiation(format!("Export {} not found", name))) - } - } - - fn resolve_memory( - &self, - field_name: &str, - memory_type: &wasmi::MemoryDescriptor, - ) -> Result { - if field_name == "memory" { - match &mut *self.import_memory.borrow_mut() { - Some(_) => - Err(wasmi::Error::Instantiation("Memory can not be imported twice!".into())), - memory_ref @ None => { - if memory_type - .maximum() - .map(|m| m.saturating_sub(memory_type.initial())) - .map(|m| self.heap_pages > m as usize) - .unwrap_or(false) - { - Err(wasmi::Error::Instantiation(format!( - "Heap pages ({}) is greater than imported memory maximum ({}).", - self.heap_pages, - memory_type - .maximum() - .map(|m| m.saturating_sub(memory_type.initial())) - .expect("Maximum is set, checked above; qed"), - ))) - } else { - let memory = MemoryInstance::alloc( - Pages(memory_type.initial() as usize + self.heap_pages), - Some(Pages(memory_type.initial() as usize + self.heap_pages)), - )?; - *memory_ref = Some(memory.clone()); - Ok(memory) - } - }, - } - } else { - Err(wasmi::Error::Instantiation(format!( - "Unknown memory reference with name: {}", - field_name - ))) - } - } -} - -impl wasmi::Externals for FunctionExecutor { - fn invoke_index( - &mut self, - index: usize, - args: wasmi::RuntimeArgs, - ) -> Result, wasmi::Trap> { - let mut args = args.as_ref().iter().copied().map(Into::into); - - if let Some(function) = self.host_functions.clone().get(index) { - function - .execute(self, &mut args) - .map_err(|msg| Error::FunctionExecution(function.name().to_string(), msg)) - .map_err(wasmi::Trap::from) - .map(|v| v.map(Into::into)) - } else if self.allow_missing_func_imports && - index >= self.host_functions.len() && - index < self.host_functions.len() + self.missing_functions.len() - { - Err(Error::from(format!( - "Function `{}` is only a stub. Calling a stub is not allowed.", - self.missing_functions[index - self.host_functions.len()], - )) - .into()) - } else { - Err(Error::from(format!("Could not find host function with index: {}", index)).into()) - } - } -} - -fn get_mem_instance(module: &ModuleRef) -> Result { - Ok(module - .export_by_name("memory") - .ok_or(Error::InvalidMemoryReference)? - .as_memory() - .ok_or(Error::InvalidMemoryReference)? - .clone()) -} - -/// Find the global named `__heap_base` in the given wasm module instance and -/// tries to get its value. -fn get_heap_base(module: &ModuleRef) -> Result { - let heap_base_val = module - .export_by_name("__heap_base") - .ok_or(Error::HeapBaseNotFoundOrInvalid)? - .as_global() - .ok_or(Error::HeapBaseNotFoundOrInvalid)? - .get(); - - match heap_base_val { - wasmi::RuntimeValue::I32(v) => Ok(v as u32), - _ => Err(Error::HeapBaseNotFoundOrInvalid), - } -} - -/// Call a given method in the given wasm-module runtime. -fn call_in_wasm_module( - module_instance: &ModuleRef, - memory: &MemoryRef, - method: InvokeMethod, - data: &[u8], - host_functions: Arc>, - allow_missing_func_imports: bool, - missing_functions: Arc>, - allocation_stats: &mut Option, -) -> Result, Error> { - // Initialize FunctionExecutor. - let table: Option = module_instance - .export_by_name("__indirect_function_table") - .and_then(|e| e.as_table().cloned()); - let heap_base = get_heap_base(module_instance)?; - - let mut function_executor = FunctionExecutor::new( - memory.clone(), - heap_base, - host_functions, - allow_missing_func_imports, - missing_functions, - )?; - - // Write the call data - let offset = function_executor.allocate_memory(data.len() as u32)?; - function_executor.write_memory(offset, data)?; - - fn convert_trap(executor: &mut FunctionExecutor, trap: wasmi::Trap) -> Error { - if let Some(message) = executor.panic_message.take() { - Error::AbortedDueToPanic(MessageWithBacktrace { message, backtrace: None }) - } else { - Error::AbortedDueToTrap(MessageWithBacktrace { - message: trap.to_string(), - backtrace: None, - }) - } - } - - let result = match method { - InvokeMethod::Export(method) => module_instance - .invoke_export( - method, - &[I32(u32::from(offset) as i32), I32(data.len() as i32)], - &mut function_executor, - ) - .map_err(|error| { - if let wasmi::Error::Trap(trap) = error { - convert_trap(&mut function_executor, trap) - } else { - error.into() - } - }), - InvokeMethod::Table(func_ref) => { - let func = table - .ok_or(Error::NoTable)? - .get(func_ref)? - .ok_or(Error::NoTableEntryWithIndex(func_ref))?; - FuncInstance::invoke( - &func, - &[I32(u32::from(offset) as i32), I32(data.len() as i32)], - &mut function_executor, - ) - .map_err(|trap| convert_trap(&mut function_executor, trap)) - }, - InvokeMethod::TableWithWrapper { dispatcher_ref, func } => { - let dispatcher = table - .ok_or(Error::NoTable)? - .get(dispatcher_ref)? - .ok_or(Error::NoTableEntryWithIndex(dispatcher_ref))?; - - FuncInstance::invoke( - &dispatcher, - &[I32(func as _), I32(u32::from(offset) as i32), I32(data.len() as i32)], - &mut function_executor, - ) - .map_err(|trap| convert_trap(&mut function_executor, trap)) - }, - }; - - *allocation_stats = Some(function_executor.heap.borrow().stats()); - - match result { - Ok(Some(I64(r))) => { - let (ptr, length) = unpack_ptr_and_len(r as u64); - #[allow(deprecated)] - memory.get(ptr, length as usize).map_err(|_| Error::Runtime) - }, - Err(e) => { - trace!( - target: "wasm-executor", - "Failed to execute code with {} pages", - memory.current_size().0, - ); - Err(e) - }, - _ => Err(Error::InvalidReturn), - } -} - -/// Prepare module instance -fn instantiate_module( - heap_pages: usize, - module: &Module, - host_functions: &[&'static dyn Function], - allow_missing_func_imports: bool, -) -> Result<(ModuleRef, Vec, MemoryRef), Error> { - let resolver = Resolver::new(host_functions, allow_missing_func_imports, heap_pages); - // start module instantiation. Don't run 'start' function yet. - let intermediate_instance = - ModuleInstance::new(module, &ImportsBuilder::new().with_resolver("env", &resolver))?; - - // Verify that the module has the heap base global variable. - let _ = get_heap_base(intermediate_instance.not_started_instance())?; - - // Get the memory reference. Runtimes should import memory, but to be backwards - // compatible we also support exported memory. - let memory = match resolver.import_memory.into_inner() { - Some(memory) => memory, - None => { - debug!( - target: "wasm-executor", - "WASM blob does not imports memory, falling back to exported memory", - ); - - let memory = get_mem_instance(intermediate_instance.not_started_instance())?; - memory.grow(Pages(heap_pages)).map_err(|_| Error::Runtime)?; - - memory - }, - }; - - if intermediate_instance.has_start() { - // Runtime is not allowed to have the `start` function. - Err(Error::RuntimeHasStartFn) - } else { - Ok(( - intermediate_instance.assert_no_start(), - resolver.missing_functions.into_inner(), - memory, - )) - } -} - -/// A state snapshot of an instance taken just after instantiation. -/// -/// It is used for restoring the state of the module after execution. -#[derive(Clone)] -struct GlobalValsSnapshot { - /// The list of all global mutable variables of the module in their sequential order. - global_mut_values: Vec, -} - -impl GlobalValsSnapshot { - // Returns `None` if instance is not valid. - fn take(module_instance: &ModuleRef) -> Self { - // Collect all values of mutable globals. - let global_mut_values = module_instance - .globals() - .iter() - .filter(|g| g.is_mutable()) - .map(|g| g.get()) - .collect(); - Self { global_mut_values } - } - - /// Reset the runtime instance to the initial version by restoring - /// the preserved memory and globals. - /// - /// Returns `Err` if applying the snapshot is failed. - fn apply(&self, instance: &ModuleRef) -> Result<(), WasmError> { - for (global_ref, global_val) in instance - .globals() - .iter() - .filter(|g| g.is_mutable()) - .zip(self.global_mut_values.iter()) - { - // the instance should be the same as used for preserving and - // we iterate the same way it as we do it for preserving values that means that the - // types should be the same and all the values are mutable. So no error is expected/ - global_ref.set(*global_val).map_err(|_| WasmError::ApplySnapshotFailed)?; - } - Ok(()) - } -} - -/// A runtime along with initial copy of data segments. -pub struct WasmiRuntime { - /// A wasm module. - module: Module, - /// The host functions registered for this instance. - host_functions: Arc>, - /// Enable stub generation for functions that are not available in `host_functions`. - /// These stubs will error when the wasm blob tries to call them. - allow_missing_func_imports: bool, - /// Numer of heap pages this runtime uses. - heap_pages: u64, - - global_vals_snapshot: GlobalValsSnapshot, - data_segments_snapshot: DataSegmentsSnapshot, -} - -impl WasmModule for WasmiRuntime { - fn new_instance(&self) -> Result, Error> { - // Instantiate this module. - let (instance, missing_functions, memory) = instantiate_module( - self.heap_pages as usize, - &self.module, - &self.host_functions, - self.allow_missing_func_imports, - ) - .map_err(|e| WasmError::Instantiation(e.to_string()))?; - - Ok(Box::new(WasmiInstance { - instance, - memory, - global_vals_snapshot: self.global_vals_snapshot.clone(), - data_segments_snapshot: self.data_segments_snapshot.clone(), - host_functions: self.host_functions.clone(), - allow_missing_func_imports: self.allow_missing_func_imports, - missing_functions: Arc::new(missing_functions), - })) - } -} - -/// Create a new `WasmiRuntime` given the code. This function loads the module and -/// stores it in the instance. -pub fn create_runtime( - blob: RuntimeBlob, - heap_pages: u64, - host_functions: Vec<&'static dyn Function>, - allow_missing_func_imports: bool, -) -> Result { - let data_segments_snapshot = - DataSegmentsSnapshot::take(&blob).map_err(|e| WasmError::Other(e.to_string()))?; - - let module = - Module::from_parity_wasm_module(blob.into_inner()).map_err(|_| WasmError::InvalidModule)?; - - let global_vals_snapshot = { - let (instance, _, _) = instantiate_module( - heap_pages as usize, - &module, - &host_functions, - allow_missing_func_imports, - ) - .map_err(|e| WasmError::Instantiation(e.to_string()))?; - GlobalValsSnapshot::take(&instance) - }; - - Ok(WasmiRuntime { - module, - data_segments_snapshot, - global_vals_snapshot, - host_functions: Arc::new(host_functions), - allow_missing_func_imports, - heap_pages, - }) -} - -/// Wasmi instance wrapper along with the state snapshot. -pub struct WasmiInstance { - /// A wasm module instance. - instance: ModuleRef, - /// The memory instance of used by the wasm module. - memory: MemoryRef, - /// The snapshot of global variable values just after instantiation. - global_vals_snapshot: GlobalValsSnapshot, - /// The snapshot of data segments. - data_segments_snapshot: DataSegmentsSnapshot, - /// The host functions registered for this instance. - host_functions: Arc>, - /// Enable stub generation for functions that are not available in `host_functions`. - /// These stubs will error when the wasm blob trie to call them. - allow_missing_func_imports: bool, - /// List of missing functions detected during function resolution - missing_functions: Arc>, -} - -// This is safe because `WasmiInstance` does not leak any references to `self.memory` and -// `self.instance` -unsafe impl Send for WasmiInstance {} - -impl WasmiInstance { - fn call_impl( - &mut self, - method: InvokeMethod, - data: &[u8], - allocation_stats: &mut Option, - ) -> Result, Error> { - // We reuse a single wasm instance for multiple calls and a previous call (if any) - // altered the state. Therefore, we need to restore the instance to original state. - - // First, zero initialize the linear memory. - self.memory.erase().map_err(|e| { - // Snapshot restoration failed. This is pretty unexpected since this can happen - // if some invariant is broken or if the system is under extreme memory pressure - // (so erasing fails). - error!(target: "wasm-executor", "snapshot restoration failed: {}", e); - WasmError::ErasingFailed(e.to_string()) - })?; - - // Second, reapply data segments into the linear memory. - self.data_segments_snapshot - .apply(|offset, contents| self.memory.set(offset, contents))?; - - // Third, restore the global variables to their initial values. - self.global_vals_snapshot.apply(&self.instance)?; - - call_in_wasm_module( - &self.instance, - &self.memory, - method, - data, - self.host_functions.clone(), - self.allow_missing_func_imports, - self.missing_functions.clone(), - allocation_stats, - ) - } -} - -impl WasmInstance for WasmiInstance { - fn call_with_allocation_stats( - &mut self, - method: InvokeMethod, - data: &[u8], - ) -> (Result, Error>, Option) { - let mut allocation_stats = None; - let result = self.call_impl(method, data, &mut allocation_stats); - (result, allocation_stats) - } - - fn get_global_const(&mut self, name: &str) -> Result, Error> { - match self.instance.export_by_name(name) { - Some(global) => Ok(Some( - global - .as_global() - .ok_or_else(|| format!("`{}` is not a global", name))? - .get() - .into(), - )), - None => Ok(None), - } - } -} diff --git a/client/executor/wasmtime/Cargo.toml b/client/executor/wasmtime/Cargo.toml index 983ca3c44fba..3e669e7c9e70 100644 --- a/client/executor/wasmtime/Cargo.toml +++ b/client/executor/wasmtime/Cargo.toml @@ -13,24 +13,24 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] +log = "0.4.17" cfg-if = "1.0" libc = "0.2.121" -log = "0.4.17" # When bumping wasmtime do not forget to also bump rustix # to exactly the same version as used by wasmtime! -wasmtime = { version = "1.0.0", default-features = false, features = [ +wasmtime = { version = "8.0.1", default-features = false, features = [ "cache", "cranelift", "jitdump", "parallel-compilation", - "memory-init-cow", - "pooling-allocator", + "pooling-allocator" ] } +anyhow = "1.0.68" sc-allocator = { version = "4.1.0-dev", path = "../../allocator" } sc-executor-common = { version = "0.10.0-dev", path = "../common" } -sp-runtime-interface = { version = "7.0.0", path = "../../../primitives/runtime-interface" } -sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interface" } +sp-runtime-interface = { version = "17.0.0", path = "../../../primitives/runtime-interface" } +sp-wasm-interface = { version = "14.0.0", path = "../../../primitives/wasm-interface", features = ["wasmtime"] } # Here we include the rustix crate in the exactly same semver-compatible version as used by # wasmtime and enable its 'use-libc' flag. @@ -38,13 +38,13 @@ sp-wasm-interface = { version = "7.0.0", path = "../../../primitives/wasm-interf # By default rustix directly calls the appropriate syscalls completely bypassing libc; # this doesn't have any actual benefits for us besides making it harder to debug memory # problems (since then `mmap` etc. cannot be easily hooked into). -rustix = { version = "0.35.9", default-features = false, features = ["std", "mm", "fs", "param", "use-libc"] } -once_cell = "1.12.0" +rustix = { version = "0.36.7", default-features = false, features = ["std", "mm", "fs", "param", "use-libc"] } [dev-dependencies] wat = "1.0" sc-runtime-test = { version = "2.0.0", path = "../runtime-test" } -sp-io = { version = "7.0.0", path = "../../../primitives/io" } +sp-io = { version = "23.0.0", path = "../../../primitives/io" } tempfile = "3.3.0" paste = "1.0" -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } +cargo_metadata = "0.15.4" diff --git a/client/executor/wasmtime/build.rs b/client/executor/wasmtime/build.rs index c514b0041bc3..a68cb706e8fb 100644 --- a/client/executor/wasmtime/build.rs +++ b/client/executor/wasmtime/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/executor/wasmtime/src/host.rs b/client/executor/wasmtime/src/host.rs index 0d9eac875170..9bd3ca3dade5 100644 --- a/client/executor/wasmtime/src/host.rs +++ b/client/executor/wasmtime/src/host.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -24,20 +24,25 @@ use wasmtime::Caller; use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator}; use sp_wasm_interface::{Pointer, WordSize}; -use crate::{runtime::StoreData, util}; +use crate::{instance_wrapper::MemoryWrapper, runtime::StoreData, util}; /// The state required to construct a HostContext context. The context only lasts for one host /// call, whereas the state is maintained for the duration of a Wasm runtime call, which may make /// many different host calls that must share state. pub struct HostState { - allocator: FreeingBumpHeapAllocator, + /// The allocator instance to keep track of allocated memory. + /// + /// This is stored as an `Option` as we need to temporarly set this to `None` when we are + /// allocating/deallocating memory. The problem being that we can only mutable access `caller` + /// once. + allocator: Option, panic_message: Option, } impl HostState { /// Constructs a new `HostState`. pub fn new(allocator: FreeingBumpHeapAllocator) -> Self { - HostState { allocator, panic_message: None } + HostState { allocator: Some(allocator), panic_message: None } } /// Takes the error message out of the host state, leaving a `None` in its place. @@ -46,7 +51,9 @@ impl HostState { } pub(crate) fn allocation_stats(&self) -> AllocationStats { - self.allocator.stats() + self.allocator.as_ref() + .expect("Allocator is always set and only unavailable when doing an allocation/deallocation; qed") + .stats() } } @@ -81,22 +88,38 @@ impl<'a> sp_wasm_interface::FunctionContext for HostContext<'a> { fn allocate_memory(&mut self, size: WordSize) -> sp_wasm_interface::Result> { let memory = self.caller.data().memory(); - let (memory, data) = memory.data_and_store_mut(&mut self.caller); - data.host_state_mut() - .expect("host state is not empty when calling a function in wasm; qed") + let mut allocator = self + .host_state_mut() .allocator - .allocate(memory, size) - .map_err(|e| e.to_string()) + .take() + .expect("allocator is not empty when calling a function in wasm; qed"); + + // We can not return on error early, as we need to store back allocator. + let res = allocator + .allocate(&mut MemoryWrapper(&memory, &mut self.caller), size) + .map_err(|e| e.to_string()); + + self.host_state_mut().allocator = Some(allocator); + + res } fn deallocate_memory(&mut self, ptr: Pointer) -> sp_wasm_interface::Result<()> { let memory = self.caller.data().memory(); - let (memory, data) = memory.data_and_store_mut(&mut self.caller); - data.host_state_mut() - .expect("host state is not empty when calling a function in wasm; qed") + let mut allocator = self + .host_state_mut() .allocator - .deallocate(memory, ptr) - .map_err(|e| e.to_string()) + .take() + .expect("allocator is not empty when calling a function in wasm; qed"); + + // We can not return on error early, as we need to store back allocator. + let res = allocator + .deallocate(&mut MemoryWrapper(&memory, &mut self.caller), ptr) + .map_err(|e| e.to_string()); + + self.host_state_mut().allocator = Some(allocator); + + res } fn register_panic_error_message(&mut self, message: &str) { diff --git a/client/executor/wasmtime/src/imports.rs b/client/executor/wasmtime/src/imports.rs index c80952a2541c..fccc121fa088 100644 --- a/client/executor/wasmtime/src/imports.rs +++ b/client/executor/wasmtime/src/imports.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -20,7 +20,7 @@ use crate::{host::HostContext, runtime::StoreData}; use sc_executor_common::error::WasmError; use sp_wasm_interface::{FunctionContext, HostFunctions}; use std::collections::HashMap; -use wasmtime::{ExternType, FuncType, ImportType, Linker, Module, Trap}; +use wasmtime::{ExternType, FuncType, ImportType, Linker, Module}; /// Goes over all imports of a module and prepares the given linker for instantiation of the module. /// Returns an error if there are imports that cannot be satisfied. @@ -67,7 +67,7 @@ where log::debug!("Missing import: '{}' {:?}", name, func_ty); linker .func_new("env", &name, func_ty.clone(), move |_, _, _| { - Err(Trap::new(error.clone())) + Err(anyhow::Error::msg(error.clone())) }) .expect("adding a missing import stub can only fail when the item already exists, and it is missing here; qed"); } diff --git a/client/executor/wasmtime/src/instance_wrapper.rs b/client/executor/wasmtime/src/instance_wrapper.rs index feded4008068..6d319cce509e 100644 --- a/client/executor/wasmtime/src/instance_wrapper.rs +++ b/client/executor/wasmtime/src/instance_wrapper.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -26,8 +26,7 @@ use sc_executor_common::{ }; use sp_wasm_interface::{Pointer, Value, WordSize}; use wasmtime::{ - AsContext, AsContextMut, Engine, Extern, Func, Global, Instance, InstancePre, Memory, Table, - Val, + AsContext, AsContextMut, Engine, Extern, Instance, InstancePre, Memory, Table, Val, }; /// Invoked entrypoint format. @@ -76,27 +75,17 @@ impl EntryPoint { .as_mut() .expect("host state cannot be empty while a function is being called; qed"); - // The logic to print out a backtrace is somewhat complicated, - // so let's get wasmtime to print it out for us. - let mut backtrace_string = trap.to_string(); - let suffix = "\nwasm backtrace:"; - if let Some(index) = backtrace_string.find(suffix) { - // Get rid of the error message and just grab the backtrace, - // since we're storing the error message ourselves separately. - backtrace_string.replace_range(0..index + suffix.len(), ""); - } + let backtrace = trap.downcast_ref::().map(|backtrace| { + // The logic to print out a backtrace is somewhat complicated, + // so let's get wasmtime to print it out for us. + Backtrace { backtrace_string: backtrace.to_string() } + }); - let backtrace = Backtrace { backtrace_string }; - if let Some(error) = host_state.take_panic_message() { - Error::AbortedDueToPanic(MessageWithBacktrace { - message: error, - backtrace: Some(backtrace), - }) + if let Some(message) = host_state.take_panic_message() { + Error::AbortedDueToPanic(MessageWithBacktrace { message, backtrace }) } else { - Error::AbortedDueToTrap(MessageWithBacktrace { - message: trap.display_reason().to_string(), - backtrace: Some(backtrace), - }) + let message = trap.root_cause().to_string(); + Error::AbortedDueToTrap(MessageWithBacktrace { message, backtrace }) } }) } @@ -106,7 +95,7 @@ impl EntryPoint { ctx: impl AsContext, ) -> std::result::Result { let entrypoint = func - .typed::<(u32, u32), u64, _>(ctx) + .typed::<(u32, u32), u64>(ctx) .map_err(|_| "Invalid signature for direct entry point")?; Ok(Self { call_type: EntryPointType::Direct { entrypoint } }) } @@ -117,72 +106,64 @@ impl EntryPoint { ctx: impl AsContext, ) -> std::result::Result { let dispatcher = dispatcher - .typed::<(u32, u32, u32), u64, _>(ctx) + .typed::<(u32, u32, u32), u64>(ctx) .map_err(|_| "Invalid signature for wrapped entry point")?; Ok(Self { call_type: EntryPointType::Wrapped { func, dispatcher } }) } } -/// Wrap the given WebAssembly Instance of a wasm module with Substrate-runtime. -/// -/// This struct is a handy wrapper around a wasmtime `Instance` that provides substrate specific -/// routines. -pub struct InstanceWrapper { - instance: Instance, - memory: Memory, - store: Store, -} +/// Wrapper around [`Memory`] that implements [`sc_allocator::Memory`]. +pub(crate) struct MemoryWrapper<'a, C>(pub &'a wasmtime::Memory, pub &'a mut C); -fn extern_memory(extern_: &Extern) -> Option<&Memory> { - match extern_ { - Extern::Memory(mem) => Some(mem), - _ => None, +impl sc_allocator::Memory for MemoryWrapper<'_, C> { + fn with_access(&self, run: impl FnOnce(&[u8]) -> R) -> R { + run(self.0.data(&self.1)) } -} -fn extern_global(extern_: &Extern) -> Option<&Global> { - match extern_ { - Extern::Global(glob) => Some(glob), - _ => None, + fn with_access_mut(&mut self, run: impl FnOnce(&mut [u8]) -> R) -> R { + run(self.0.data_mut(&mut self.1)) } -} -fn extern_table(extern_: &Extern) -> Option<&Table> { - match extern_ { - Extern::Table(table) => Some(table), - _ => None, + fn grow(&mut self, additional: u32) -> std::result::Result<(), ()> { + self.0 + .grow(&mut self.1, additional as u64) + .map_err(|e| { + log::error!( + target: "wasm-executor", + "Failed to grow memory by {} pages: {}", + additional, + e, + ) + }) + .map(drop) } -} -fn extern_func(extern_: &Extern) -> Option<&Func> { - match extern_ { - Extern::Func(func) => Some(func), - _ => None, + fn pages(&self) -> u32 { + self.0.size(&self.1) as u32 } -} -pub(crate) fn create_store(engine: &wasmtime::Engine, max_memory_size: Option) -> Store { - let limits = if let Some(max_memory_size) = max_memory_size { - wasmtime::StoreLimitsBuilder::new().memory_size(max_memory_size).build() - } else { - Default::default() - }; - - let mut store = - Store::new(engine, StoreData { limits, host_state: None, memory: None, table: None }); - if max_memory_size.is_some() { - store.limiter(|s| &mut s.limits); + fn max_pages(&self) -> Option { + self.0.ty(&self.1).maximum().map(|p| p as _) } - store +} + +/// Wrap the given WebAssembly Instance of a wasm module with Substrate-runtime. +/// +/// This struct is a handy wrapper around a wasmtime `Instance` that provides substrate specific +/// routines. +pub struct InstanceWrapper { + instance: Instance, + /// The memory instance of the `instance`. + /// + /// It is important to make sure that we don't make any copies of this to make it easier to + /// proof + memory: Memory, + store: Store, } impl InstanceWrapper { - pub(crate) fn new( - engine: &Engine, - instance_pre: &InstancePre, - max_memory_size: Option, - ) -> Result { - let mut store = create_store(engine, max_memory_size); + pub(crate) fn new(engine: &Engine, instance_pre: &InstancePre) -> Result { + let mut store = Store::new(engine, Default::default()); let instance = instance_pre.instantiate(&mut store).map_err(|error| { WasmError::Other(format!( "failed to instantiate a new WASM module instance: {:#}", @@ -211,9 +192,10 @@ impl InstanceWrapper { self.instance.get_export(&mut self.store, method).ok_or_else(|| { Error::from(format!("Exported method {} is not found", method)) })?; - let func = extern_func(&export) + let func = export + .into_func() .ok_or_else(|| Error::from(format!("Export {} is not a function", method)))?; - EntryPoint::direct(*func, &self.store).map_err(|_| { + EntryPoint::direct(func, &self.store).map_err(|_| { Error::from(format!("Exported function '{}' has invalid signature.", method)) })? }, @@ -269,7 +251,8 @@ impl InstanceWrapper { .get_export(&mut self.store, "__heap_base") .ok_or_else(|| Error::from("__heap_base is not found"))?; - let heap_base_global = extern_global(&heap_base_export) + let heap_base_global = heap_base_export + .into_global() .ok_or_else(|| Error::from("__heap_base is not a global"))?; let heap_base = heap_base_global @@ -287,7 +270,7 @@ impl InstanceWrapper { None => return Ok(None), }; - let global = extern_global(&global).ok_or_else(|| format!("`{}` is not a global", name))?; + let global = global.into_global().ok_or_else(|| format!("`{}` is not a global", name))?; match global.get(&mut self.store) { Val::I32(val) => Ok(Some(Value::I32(val))), @@ -310,7 +293,8 @@ fn get_linear_memory(instance: &Instance, ctx: impl AsContextMut) -> Result Option { instance .get_export(ctx, "__indirect_function_table") .as_ref() - .and_then(extern_table) .cloned() + .and_then(Extern::into_table) } /// Functions related to memory. @@ -412,9 +396,8 @@ fn decommit_works() { let code = wat::parse_str("(module (memory (export \"memory\") 1 4))").unwrap(); let module = wasmtime::Module::new(&engine, code).unwrap(); let linker = wasmtime::Linker::new(&engine); - let mut store = create_store(&engine, None); - let instance_pre = linker.instantiate_pre(&mut store, &module).unwrap(); - let mut wrapper = InstanceWrapper::new(&engine, &instance_pre, None).unwrap(); + let instance_pre = linker.instantiate_pre(&module).unwrap(); + let mut wrapper = InstanceWrapper::new(&engine, &instance_pre).unwrap(); unsafe { *wrapper.memory.data_ptr(&wrapper.store) = 42 }; assert_eq!(unsafe { *wrapper.memory.data_ptr(&wrapper.store) }, 42); wrapper.decommit(); diff --git a/client/executor/wasmtime/src/lib.rs b/client/executor/wasmtime/src/lib.rs index 4d0e4c37c947..82e62b4a5dd3 100644 --- a/client/executor/wasmtime/src/lib.rs +++ b/client/executor/wasmtime/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -37,6 +37,7 @@ mod util; mod tests; pub use runtime::{ - create_runtime, create_runtime_from_artifact, prepare_runtime_artifact, Config, - DeterministicStackLimit, InstantiationStrategy, Semantics, + create_runtime, create_runtime_from_artifact, create_runtime_from_artifact_bytes, + prepare_runtime_artifact, Config, DeterministicStackLimit, InstantiationStrategy, Semantics, + WasmtimeRuntime, }; diff --git a/client/executor/wasmtime/src/runtime.rs b/client/executor/wasmtime/src/runtime.rs index a45dfa59b954..23b069870aa3 100644 --- a/client/executor/wasmtime/src/runtime.rs +++ b/client/executor/wasmtime/src/runtime.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -20,18 +20,18 @@ use crate::{ host::HostState, - instance_wrapper::{EntryPoint, InstanceWrapper}, + instance_wrapper::{EntryPoint, InstanceWrapper, MemoryWrapper}, util::{self, replace_strategy_if_broken}, }; use sc_allocator::{AllocationStats, FreeingBumpHeapAllocator}; use sc_executor_common::{ - error::{Result, WasmError}, + error::{Error, Result, WasmError}, runtime_blob::{ self, DataSegmentsSnapshot, ExposedMutableGlobalsSet, GlobalsSnapshot, RuntimeBlob, }, util::checked_range, - wasm_runtime::{InvokeMethod, WasmInstance, WasmModule}, + wasm_runtime::{HeapAllocStrategy, InvokeMethod, WasmInstance, WasmModule}, }; use sp_runtime_interface::unpack_ptr_and_len; use sp_wasm_interface::{HostFunctions, Pointer, Value, WordSize}; @@ -42,12 +42,10 @@ use std::{ Arc, }, }; -use wasmtime::{AsContext, Engine, Memory, StoreLimits, Table}; +use wasmtime::{AsContext, Engine, Memory, Table}; +#[derive(Default)] pub(crate) struct StoreData { - /// The limits we apply to the store. We need to store it here to return a reference to this - /// object when we have the limits enabled. - pub(crate) limits: StoreLimits, /// This will only be set when we call into the runtime. pub(crate) host_state: Option, /// This will be always set once the store is initialized. @@ -83,12 +81,11 @@ enum Strategy { struct InstanceCreator { engine: wasmtime::Engine, instance_pre: Arc>, - max_memory_size: Option, } impl InstanceCreator { fn instantiate(&mut self) -> Result { - InstanceWrapper::new(&self.engine, &self.instance_pre, self.max_memory_size) + InstanceWrapper::new(&self.engine, &self.instance_pre) } } @@ -128,18 +125,13 @@ pub struct WasmtimeRuntime { engine: wasmtime::Engine, instance_pre: Arc>, instantiation_strategy: InternalInstantiationStrategy, - config: Config, } impl WasmModule for WasmtimeRuntime { fn new_instance(&self) -> Result> { let strategy = match self.instantiation_strategy { InternalInstantiationStrategy::LegacyInstanceReuse(ref snapshot_data) => { - let mut instance_wrapper = InstanceWrapper::new( - &self.engine, - &self.instance_pre, - self.config.semantics.max_memory_size, - )?; + let mut instance_wrapper = InstanceWrapper::new(&self.engine, &self.instance_pre)?; let heap_base = instance_wrapper.extract_heap_base()?; // This function panics if the instance was created from a runtime blob different @@ -161,7 +153,6 @@ impl WasmModule for WasmtimeRuntime { InternalInstantiationStrategy::Builtin => Strategy::RecreateInstance(InstanceCreator { engine: self.engine.clone(), instance_pre: self.instance_pre.clone(), - max_memory_size: self.config.semantics.max_memory_size, }), }; @@ -298,6 +289,11 @@ fn common_config(semantics: &Semantics) -> std::result::Result wasmtime::ProfilingStrategy::JitDump, None => wasmtime::ProfilingStrategy::None, @@ -329,10 +325,10 @@ fn common_config(semantics: &Semantics) -> std::result::Result std::result::Result (false, false), }; + const WASM_PAGE_SIZE: u64 = 65536; + config.memory_init_cow(use_cow); - config.memory_guaranteed_dense_image_size( - semantics.max_memory_size.map(|max| max as u64).unwrap_or(u64::MAX), - ); + config.memory_guaranteed_dense_image_size(match semantics.heap_alloc_strategy { + HeapAllocStrategy::Dynamic { maximum_pages } => + maximum_pages.map(|p| p as u64 * WASM_PAGE_SIZE).unwrap_or(u64::MAX), + HeapAllocStrategy::Static { .. } => u64::MAX, + }); if use_pooling { - const WASM_PAGE_SIZE: u64 = 65536; const MAX_WASM_PAGES: u64 = 0x10000; - let memory_pages = if let Some(max_memory_size) = semantics.max_memory_size { - let max_memory_size = max_memory_size as u64; - let mut pages = max_memory_size / WASM_PAGE_SIZE; - if max_memory_size % WASM_PAGE_SIZE != 0 { - pages += 1; - } - - std::cmp::min(MAX_WASM_PAGES, pages) - } else { - MAX_WASM_PAGES + let memory_pages = match semantics.heap_alloc_strategy { + HeapAllocStrategy::Dynamic { maximum_pages } => + maximum_pages.map(|p| p as u64).unwrap_or(MAX_WASM_PAGES), + HeapAllocStrategy::Static { .. } => MAX_WASM_PAGES, }; - config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling { - strategy: wasmtime::PoolingAllocationStrategy::ReuseAffinity, - + let mut pooling_config = wasmtime::PoolingAllocationConfig::default(); + pooling_config + .max_unused_warm_slots(4) // Pooling needs a bunch of hard limits to be set; if we go over // any of these then the instantiation will fail. - instance_limits: wasmtime::InstanceLimits { - // Current minimum values for kusama (as of 2022-04-14): - // size: 32384 - // table_elements: 1249 - // memory_pages: 2070 - size: 64 * 1024, - table_elements: 3072, - memory_pages, - - // We can only have a single of those. - tables: 1, - memories: 1, - - // This determines how many instances of the module can be - // instantiated in parallel from the same `Module`. - count: 32, - }, - }); + // + // Current minimum values for kusama (as of 2022-04-14): + // size: 32384 + // table_elements: 1249 + // memory_pages: 2070 + .instance_size(128 * 1024) + .instance_table_elements(8192) + .instance_memory_pages(memory_pages) + // We can only have a single of those. + .instance_tables(1) + .instance_memories(1) + // This determines how many instances of the module can be + // instantiated in parallel from the same `Module`. + .instance_count(32); + + config.allocation_strategy(wasmtime::InstanceAllocationStrategy::Pooling(pooling_config)); } Ok(config) @@ -444,7 +435,7 @@ pub struct DeterministicStackLimit { /// All of the CoW strategies (with `CopyOnWrite` suffix) are only supported when either: /// a) we're running on Linux, /// b) we're running on an Unix-like system and we're precompiling -/// our module beforehand. +/// our module beforehand and instantiating from a file. /// /// If the CoW variant of a strategy is unsupported the executor will /// fall back to the non-CoW equivalent. @@ -511,25 +502,20 @@ pub struct Semantics { /// Configures wasmtime to use multiple threads for compiling. pub parallel_compilation: bool, - /// The number of extra WASM pages which will be allocated - /// on top of what is requested by the WASM blob itself. - pub extra_heap_pages: u64, + /// The heap allocation strategy to use. + pub heap_alloc_strategy: HeapAllocStrategy, - /// The total amount of memory in bytes an instance can request. - /// - /// If specified, the runtime will be able to allocate only that much of wasm memory. - /// This is the total number and therefore the [`Semantics::extra_heap_pages`] is accounted - /// for. - /// - /// That means that the initial number of pages of a linear memory plus the - /// [`Semantics::extra_heap_pages`] multiplied by the wasm page size (64KiB) should be less - /// than or equal to `max_memory_size`, otherwise the instance won't be created. - /// - /// Moreover, `memory.grow` will fail (return -1) if the sum of sizes of currently mounted - /// and additional pages exceeds `max_memory_size`. - /// - /// The default is `None`. - pub max_memory_size: Option, + /// Enables WASM Multi-Value proposal + pub wasm_multi_value: bool, + + /// Enables WASM Bulk Memory Operations proposal + pub wasm_bulk_memory: bool, + + /// Enables WASM Reference Types proposal + pub wasm_reference_types: bool, + + /// Enables WASM Fixed-Width SIMD proposal + pub wasm_simd: bool, } #[derive(Clone)] @@ -551,7 +537,7 @@ enum CodeSupplyMode<'a> { /// The runtime is instantiated using the given runtime blob. Fresh(RuntimeBlob), - /// The runtime is instantiated using a precompiled module. + /// The runtime is instantiated using a precompiled module at the given path. /// /// This assumes that the code is already prepared for execution and the same `Config` was /// used. @@ -559,6 +545,12 @@ enum CodeSupplyMode<'a> { /// We use a `Path` here instead of simply passing a byte slice to allow `wasmtime` to /// map the runtime's linear memory on supported platforms in a copy-on-write fashion. Precompiled(&'a Path), + + /// The runtime is instantiated using a precompiled module with the given bytes. + /// + /// This assumes that the code is already prepared for execution and the same `Config` was + /// used. + PrecompiledBytes(&'a [u8]), } /// Create a new `WasmtimeRuntime` given the code. This function performs translation from Wasm to @@ -603,6 +595,31 @@ where do_create_runtime::(CodeSupplyMode::Precompiled(compiled_artifact_path), config) } +/// The same as [`create_runtime`] but takes the bytes of a precompiled artifact, +/// which makes this function considerably faster than [`create_runtime`], +/// but slower than the more optimized [`create_runtime_from_artifact`]. +/// This is especially slow on non-Linux Unix systems. Useful in very niche cases. +/// +/// # Safety +/// +/// The caller must ensure that the compiled artifact passed here was: +/// 1) produced by [`prepare_runtime_artifact`], +/// 2) was not modified, +/// +/// Failure to adhere to these requirements might lead to crashes and arbitrary code execution. +/// +/// It is ok though if the compiled artifact was created by code of another version or with +/// different configuration flags. In such case the caller will receive an `Err` deterministically. +pub unsafe fn create_runtime_from_artifact_bytes( + compiled_artifact_bytes: &[u8], + config: Config, +) -> std::result::Result +where + H: HostFunctions, +{ + do_create_runtime::(CodeSupplyMode::PrecompiledBytes(compiled_artifact_bytes), config) +} + /// # Safety /// /// This is only unsafe if called with [`CodeSupplyMode::Artifact`]. See @@ -677,23 +694,32 @@ where (module, InternalInstantiationStrategy::Builtin) }, + CodeSupplyMode::PrecompiledBytes(compiled_artifact_bytes) => { + if let InstantiationStrategy::LegacyInstanceReuse = + config.semantics.instantiation_strategy + { + return Err(WasmError::Other("the legacy instance reuse instantiation strategy is incompatible with precompiled modules".into())); + } + + // SAFETY: The unsafety of `deserialize` is covered by this function. The + // responsibilities to maintain the invariants are passed to the caller. + // + // See [`create_runtime_from_artifact_bytes`] for more details. + let module = wasmtime::Module::deserialize(&engine, compiled_artifact_bytes) + .map_err(|e| WasmError::Other(format!("cannot deserialize module: {:#}", e)))?; + + (module, InternalInstantiationStrategy::Builtin) + }, }; let mut linker = wasmtime::Linker::new(&engine); crate::imports::prepare_imports::(&mut linker, &module, config.allow_missing_func_imports)?; - let mut store = - crate::instance_wrapper::create_store(module.engine(), config.semantics.max_memory_size); let instance_pre = linker - .instantiate_pre(&mut store, &module) + .instantiate_pre(&module) .map_err(|e| WasmError::Other(format!("cannot preinstantiate module: {:#}", e)))?; - Ok(WasmtimeRuntime { - engine, - instance_pre: Arc::new(instance_pre), - instantiation_strategy, - config, - }) + Ok(WasmtimeRuntime { engine, instance_pre: Arc::new(instance_pre), instantiation_strategy }) } fn prepare_blob_for_compilation( @@ -716,12 +742,7 @@ fn prepare_blob_for_compilation( // now automatically take care of creating the memory for us, and it is also necessary // to enable `wasmtime`'s instance pooling. (Imported memories are ineligible for pooling.) blob.convert_memory_import_into_export()?; - blob.add_extra_heap_pages_to_memory_section( - semantics - .extra_heap_pages - .try_into() - .map_err(|e| WasmError::Other(format!("invalid `extra_heap_pages`: {}", e)))?, - )?; + blob.setup_memory_according_to_heap_alloc_strategy(semantics.heap_alloc_strategy)?; Ok(blob) } @@ -782,9 +803,8 @@ fn inject_input_data( ) -> Result<(Pointer, WordSize)> { let mut ctx = instance.store_mut(); let memory = ctx.data().memory(); - let memory = memory.data_mut(&mut ctx); let data_len = data.len() as WordSize; - let data_ptr = allocator.allocate(memory, data_len)?; + let data_ptr = allocator.allocate(&mut MemoryWrapper(&memory, &mut ctx), data_len)?; util::write_memory_from(instance.store_mut(), data_ptr, data)?; Ok((data_ptr, data_len)) } @@ -803,7 +823,7 @@ fn extract_output_data( // Get the size of the WASM memory in bytes. let memory_size = ctx.as_context().data().memory().data_size(ctx); if checked_range(output_ptr as usize, output_len as usize, memory_size).is_none() { - Err(WasmError::Other("output exceeds bounds of wasm memory".into()))? + Err(Error::OutputExceedsBounds)? } let mut output = vec![0; output_len as usize]; diff --git a/client/executor/wasmtime/src/tests.rs b/client/executor/wasmtime/src/tests.rs index 9126cb336bde..65093687822d 100644 --- a/client/executor/wasmtime/src/tests.rs +++ b/client/executor/wasmtime/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -17,7 +17,11 @@ // along with this program. If not, see . use codec::{Decode as _, Encode as _}; -use sc_executor_common::{error::Error, runtime_blob::RuntimeBlob, wasm_runtime::WasmModule}; +use sc_executor_common::{ + error::Error, + runtime_blob::RuntimeBlob, + wasm_runtime::{HeapAllocStrategy, WasmModule, DEFAULT_HEAP_ALLOC_STRATEGY}, +}; use sc_runtime_test::wasm_binary_unwrap; use crate::InstantiationStrategy; @@ -77,8 +81,7 @@ struct RuntimeBuilder { instantiation_strategy: InstantiationStrategy, canonicalize_nans: bool, deterministic_stack: bool, - extra_heap_pages: u64, - max_memory_size: Option, + heap_pages: HeapAllocStrategy, precompile_runtime: bool, tmpdir: Option, } @@ -90,8 +93,7 @@ impl RuntimeBuilder { instantiation_strategy, canonicalize_nans: false, deterministic_stack: false, - extra_heap_pages: 1024, - max_memory_size: None, + heap_pages: DEFAULT_HEAP_ALLOC_STRATEGY, precompile_runtime: false, tmpdir: None, } @@ -117,8 +119,8 @@ impl RuntimeBuilder { self } - fn max_memory_size(mut self, max_memory_size: Option) -> Self { - self.max_memory_size = max_memory_size; + fn heap_alloc_strategy(mut self, heap_pages: HeapAllocStrategy) -> Self { + self.heap_pages = heap_pages; self } @@ -152,8 +154,11 @@ impl RuntimeBuilder { }, canonicalize_nans: self.canonicalize_nans, parallel_compilation: true, - extra_heap_pages: self.extra_heap_pages, - max_memory_size: self.max_memory_size, + heap_alloc_strategy: self.heap_pages, + wasm_multi_value: false, + wasm_bulk_memory: false, + wasm_reference_types: false, + wasm_simd: false, }, }; @@ -226,8 +231,8 @@ fn deep_call_stack_wat(depth: usize) -> String { // We need two limits here since depending on whether the code is compiled in debug // or in release mode the maximum call depth is slightly different. -const CALL_DEPTH_LOWER_LIMIT: usize = 65478; -const CALL_DEPTH_UPPER_LIMIT: usize = 65514; +const CALL_DEPTH_LOWER_LIMIT: usize = 65455; +const CALL_DEPTH_UPPER_LIMIT: usize = 65509; test_wasm_execution!(test_consume_under_1mb_of_stack_does_not_trap); fn test_consume_under_1mb_of_stack_does_not_trap(instantiation_strategy: InstantiationStrategy) { @@ -344,29 +349,25 @@ fn test_max_memory_pages( import_memory: bool, precompile_runtime: bool, ) { - fn try_instantiate( - max_memory_size: Option, + fn call( + heap_alloc_strategy: HeapAllocStrategy, wat: String, instantiation_strategy: InstantiationStrategy, precompile_runtime: bool, ) -> Result<(), Box> { let mut builder = RuntimeBuilder::new(instantiation_strategy) .use_wat(wat) - .max_memory_size(max_memory_size) + .heap_alloc_strategy(heap_alloc_strategy) .precompile_runtime(precompile_runtime); let runtime = builder.build(); - let mut instance = runtime.new_instance()?; + let mut instance = runtime.new_instance().unwrap(); let _ = instance.call_export("main", &[])?; Ok(()) } - fn memory(initial: u32, maximum: Option, import: bool) -> String { - let memory = if let Some(maximum) = maximum { - format!("(memory $0 {} {})", initial, maximum) - } else { - format!("(memory $0 {})", initial) - }; + fn memory(initial: u32, maximum: u32, import: bool) -> String { + let memory = format!("(memory $0 {} {})", initial, maximum); if import { format!("(import \"env\" \"memory\" {})", memory) @@ -375,152 +376,90 @@ fn test_max_memory_pages( } } - const WASM_PAGE_SIZE: usize = 65536; - - // check the old behavior if preserved. That is, if no limit is set we allow 4 GiB of memory. - try_instantiate( - None, - format!( - r#" - (module - {} - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - (i64.const 0) - ) - ) - "#, - /* - We want to allocate the maximum number of pages supported in wasm for this test. - However, due to a bug in wasmtime (I think wasmi is also affected) it is only possible - to allocate 65536 - 1 pages. - - Then, during creation of the Substrate Runtime instance, 1024 (heap_pages) pages are - mounted. - - Thus 65535 = 64511 + 1024 - */ - memory(64511, None, import_memory) - ), - instantiation_strategy, - precompile_runtime, - ) - .unwrap(); - - // max is not specified, therefore it's implied to be 65536 pages (4 GiB). - // - // max_memory_size = (1 (initial) + 1024 (heap_pages)) * WASM_PAGE_SIZE - try_instantiate( - Some((1 + 1024) * WASM_PAGE_SIZE), - format!( - r#" - (module - {} - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - (i64.const 0) - ) - ) - "#, - // 1 initial, max is not specified. - memory(1, None, import_memory) - ), - instantiation_strategy, - precompile_runtime, - ) - .unwrap(); - - // max is specified explicitly to 2048 pages. - try_instantiate( - Some((1 + 1024) * WASM_PAGE_SIZE), - format!( - r#" - (module - {} - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - (i64.const 0) - ) - ) - "#, - // Max is 2048. - memory(1, Some(2048), import_memory) - ), - instantiation_strategy, - precompile_runtime, - ) - .unwrap(); - - // memory grow should work as long as it doesn't exceed 1025 pages in total. - try_instantiate( - Some((0 + 1024 + 25) * WASM_PAGE_SIZE), - format!( - r#" - (module - {} - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - - ;; assert(memory.grow returns != -1) - (if - (i32.eq - (memory.grow - (i32.const 25) + let assert_grow_ok = |alloc_strategy: HeapAllocStrategy, initial_pages: u32, max_pages: u32| { + eprintln!("assert_grow_ok({alloc_strategy:?}, {initial_pages}, {max_pages})"); + + call( + alloc_strategy, + format!( + r#" + (module + {} + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "main") + (param i32 i32) (result i64) + + ;; assert(memory.grow returns != -1) + (if + (i32.eq + (memory.grow + (i32.const 1) + ) + (i32.const -1) + ) + (unreachable) ) - (i32.const -1) + + (i64.const 0) ) - (unreachable) ) - - (i64.const 0) - ) - ) "#, - // Zero starting pages. - memory(0, None, import_memory) - ), - instantiation_strategy, - precompile_runtime, - ) - .unwrap(); + memory(initial_pages, max_pages, import_memory) + ), + instantiation_strategy, + precompile_runtime, + ) + .unwrap() + }; - // We start with 1025 pages and try to grow at least one. - try_instantiate( - Some((1 + 1024) * WASM_PAGE_SIZE), - format!( - r#" - (module - {} - (global (export "__heap_base") i32 (i32.const 0)) - (func (export "main") - (param i32 i32) (result i64) - - ;; assert(memory.grow returns == -1) - (if - (i32.ne - (memory.grow - (i32.const 1) + let assert_grow_fail = + |alloc_strategy: HeapAllocStrategy, initial_pages: u32, max_pages: u32| { + eprintln!("assert_grow_fail({alloc_strategy:?}, {initial_pages}, {max_pages})"); + + call( + alloc_strategy, + format!( + r#" + (module + {} + (global (export "__heap_base") i32 (i32.const 0)) + (func (export "main") + (param i32 i32) (result i64) + + ;; assert(memory.grow returns == -1) + (if + (i32.ne + (memory.grow + (i32.const 1) + ) + (i32.const -1) + ) + (unreachable) + ) + + (i64.const 0) ) - (i32.const -1) ) - (unreachable) - ) - - (i64.const 0) - ) + "#, + memory(initial_pages, max_pages, import_memory) + ), + instantiation_strategy, + precompile_runtime, ) - "#, - // Initial=1, meaning after heap pages mount the total will be already 1025. - memory(1, None, import_memory) - ), - instantiation_strategy, - precompile_runtime, - ) - .unwrap(); + .unwrap() + }; + + assert_grow_ok(HeapAllocStrategy::Dynamic { maximum_pages: Some(10) }, 1, 10); + assert_grow_ok(HeapAllocStrategy::Dynamic { maximum_pages: Some(10) }, 9, 10); + assert_grow_fail(HeapAllocStrategy::Dynamic { maximum_pages: Some(10) }, 10, 10); + + assert_grow_ok(HeapAllocStrategy::Dynamic { maximum_pages: None }, 1, 10); + assert_grow_ok(HeapAllocStrategy::Dynamic { maximum_pages: None }, 9, 10); + assert_grow_ok(HeapAllocStrategy::Dynamic { maximum_pages: None }, 10, 10); + + assert_grow_fail(HeapAllocStrategy::Static { extra_pages: 10 }, 1, 10); + assert_grow_fail(HeapAllocStrategy::Static { extra_pages: 10 }, 9, 10); + assert_grow_fail(HeapAllocStrategy::Static { extra_pages: 10 }, 10, 10); } // This test takes quite a while to execute in a debug build (over 6 minutes on a TR 3970x) @@ -538,8 +477,11 @@ fn test_instances_without_reuse_are_not_leaked() { deterministic_stack_limit: None, canonicalize_nans: false, parallel_compilation: true, - extra_heap_pages: 2048, - max_memory_size: None, + heap_alloc_strategy: DEFAULT_HEAP_ALLOC_STRATEGY, + wasm_multi_value: false, + wasm_bulk_memory: false, + wasm_reference_types: false, + wasm_simd: false, }, }, ) @@ -555,3 +497,35 @@ fn test_instances_without_reuse_are_not_leaked() { instance.call_export("test_empty_return", &[0]).unwrap(); } } + +#[test] +fn test_rustix_version_matches_with_wasmtime() { + let metadata = cargo_metadata::MetadataCommand::new().exec().unwrap(); + + let wasmtime_rustix = metadata + .packages + .iter() + .find(|pkg| pkg.name == "wasmtime-runtime") + .unwrap() + .dependencies + .iter() + .find(|dep| dep.name == "rustix") + .unwrap(); + let our_rustix = metadata + .packages + .iter() + .find(|pkg| pkg.name == "sc-executor-wasmtime") + .unwrap() + .dependencies + .iter() + .find(|dep| dep.name == "rustix") + .unwrap(); + + if wasmtime_rustix.req != our_rustix.req { + panic!( + "our version of rustix ({0}) doesn't match wasmtime's ({1}); \ + bump the version in `sc-executor-wasmtime`'s `Cargo.toml' to '{1}' and try again", + our_rustix.req, wasmtime_rustix.req, + ); + } +} diff --git a/client/executor/wasmtime/src/util.rs b/client/executor/wasmtime/src/util.rs index 15f62e475033..c38d969ce9dc 100644 --- a/client/executor/wasmtime/src/util.rs +++ b/client/executor/wasmtime/src/util.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -144,8 +144,8 @@ pub(crate) fn replace_strategy_if_broken(strategy: &mut InstantiationStrategy) { InstantiationStrategy::LegacyInstanceReuse => InstantiationStrategy::RecreateInstance, }; - use once_cell::sync::OnceCell; - static IS_OK: OnceCell = OnceCell::new(); + use std::sync::OnceLock; + static IS_OK: OnceLock = OnceLock::new(); let is_ok = IS_OK.get_or_init(|| { let is_ok = match is_madvise_working() { diff --git a/client/finality-grandpa/Cargo.toml b/client/finality-grandpa/Cargo.toml deleted file mode 100644 index 8a4e0449f2d1..000000000000 --- a/client/finality-grandpa/Cargo.toml +++ /dev/null @@ -1,60 +0,0 @@ -[package] -name = "sc-finality-grandpa" -version = "0.10.0-dev" -authors = ["Parity Technologies "] -edition = "2021" -license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -homepage = "https://substrate.io" -repository = "https://github.com/paritytech/substrate/" -description = "Integration of the GRANDPA finality gadget into substrate." -documentation = "https://docs.rs/sc-finality-grandpa" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -ahash = "0.8.2" -array-bytes = "4.1" -async-trait = "0.1.57" -dyn-clone = "1.0" -finality-grandpa = { version = "0.16.1", features = ["derive-codec"] } -futures = "0.3.21" -futures-timer = "3.0.1" -log = "0.4.17" -parity-scale-codec = { version = "3.2.2", features = ["derive"] } -parking_lot = "0.12.1" -rand = "0.8.5" -serde_json = "1.0.85" -thiserror = "1.0" -fork-tree = { version = "3.0.0", path = "../../utils/fork-tree" } -prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } -sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } -sc-chain-spec = { version = "4.0.0-dev", path = "../../client/chain-spec" } -sc-client-api = { version = "4.0.0-dev", path = "../api" } -sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } -sc-network = { version = "0.10.0-dev", path = "../network" } -sc-network-gossip = { version = "0.10.0-dev", path = "../network-gossip" } -sc-network-common = { version = "0.10.0-dev", path = "../network/common" } -sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } -sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-application-crypto = { version = "7.0.0", path = "../../primitives/application-crypto" } -sp-arithmetic = { version = "6.0.0", path = "../../primitives/arithmetic" } -sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-finality-grandpa = { version = "4.0.0-dev", path = "../../primitives/finality-grandpa" } -sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } - -[dev-dependencies] -assert_matches = "1.3.0" -finality-grandpa = { version = "0.16.1", features = ["derive-codec", "test-helpers"] } -serde = "1.0.136" -tokio = "1.22.0" -sc-network = { version = "0.10.0-dev", path = "../network" } -sc-network-test = { version = "0.8.0", path = "../network/test" } -sp-keyring = { version = "7.0.0", path = "../../primitives/keyring" } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } diff --git a/client/finality-grandpa/rpc/Cargo.toml b/client/finality-grandpa/rpc/Cargo.toml deleted file mode 100644 index e20ff174d41c..000000000000 --- a/client/finality-grandpa/rpc/Cargo.toml +++ /dev/null @@ -1,36 +0,0 @@ -[package] -name = "sc-finality-grandpa-rpc" -version = "0.10.0-dev" -authors = ["Parity Technologies "] -description = "RPC extensions for the GRANDPA finality gadget" -repository = "https://github.com/paritytech/substrate/" -edition = "2021" -license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -readme = "README.md" -homepage = "https://substrate.io" - -[dependencies] -finality-grandpa = { version = "0.16.1", features = ["derive-codec"] } -futures = "0.3.16" -jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } -log = "0.4.8" -parity-scale-codec = { version = "3.2.2", features = ["derive"] } -serde = { version = "1.0.105", features = ["derive"] } -thiserror = "1.0" -sc-client-api = { version = "4.0.0-dev", path = "../../api" } -sc-finality-grandpa = { version = "0.10.0-dev", path = "../" } -sc-rpc = { version = "4.0.0-dev", path = "../../rpc" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } - -[dev-dependencies] -sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } -sc-rpc = { version = "4.0.0-dev", features = [ - "test-helpers", -], path = "../../rpc" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } -sp-keyring = { version = "7.0.0", path = "../../../primitives/keyring" } -substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } -tokio = { version = "1.22.0", features = ["macros"] } diff --git a/client/informant/Cargo.toml b/client/informant/Cargo.toml index ca0ae03af326..eb6e854ee358 100644 --- a/client/informant/Cargo.toml +++ b/client/informant/Cargo.toml @@ -19,5 +19,6 @@ futures-timer = "3.0.1" log = "0.4.17" sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-network-common = { version = "0.10.0-dev", path = "../network/common" } +sc-network = { version = "0.10.0-dev", path = "../network" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } diff --git a/client/informant/src/display.rs b/client/informant/src/display.rs index 3d585a998513..722cf56d778d 100644 --- a/client/informant/src/display.rs +++ b/client/informant/src/display.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -20,12 +20,10 @@ use crate::OutputFormat; use ansi_term::Colour; use log::info; use sc_client_api::ClientInfo; -use sc_network_common::{ - service::NetworkStatus, - sync::{ - warp::{WarpSyncPhase, WarpSyncProgress}, - SyncState, - }, +use sc_network::NetworkStatus; +use sc_network_common::sync::{ + warp::{WarpSyncPhase, WarpSyncProgress}, + SyncState, SyncStatus, }; use sp_runtime::traits::{Block as BlockT, CheckedDiv, NumberFor, Saturating, Zero}; use std::{fmt, time::Instant}; @@ -69,11 +67,16 @@ impl InformantDisplay { } /// Displays the informant by calling `info!`. - pub fn display(&mut self, info: &ClientInfo, net_status: NetworkStatus) { + pub fn display( + &mut self, + info: &ClientInfo, + net_status: NetworkStatus, + sync_status: SyncStatus, + ) { let best_number = info.chain.best_number; let best_hash = info.chain.best_hash; let finalized_number = info.chain.finalized_number; - let num_connected_peers = net_status.num_connected_peers; + let num_connected_peers = sync_status.num_connected_peers; let speed = speed::(best_number, self.last_number, self.last_update); let total_bytes_inbound = net_status.total_bytes_inbound; let total_bytes_outbound = net_status.total_bytes_outbound; @@ -94,21 +97,37 @@ impl InformantDisplay { }; let (level, status, target) = - match (net_status.sync_state, net_status.state_sync, net_status.warp_sync) { + match (sync_status.state, sync_status.state_sync, sync_status.warp_sync) { + // Do not set status to "Block history" when we are doing a major sync. + // + // A node could for example have been warp synced to the tip of the chain and + // shutdown. At the next start we still need to download the block history, but + // first will sync to the tip of the chain. ( - _, + sync_status, _, Some(WarpSyncProgress { phase: WarpSyncPhase::DownloadingBlocks(n), .. }), - ) => ("⏩", "Block history".into(), format!(", #{}", n)), - (_, _, Some(warp)) => ( - "⏩", - "Warping".into(), - format!( - ", {}, {:.2} Mib", + ) if !sync_status.is_major_syncing() => ("⏩", "Block history".into(), format!(", #{}", n)), + ( + _, + _, + Some(WarpSyncProgress { phase: WarpSyncPhase::AwaitingTargetBlock, .. }), + ) => ("⏩", "Waiting for pending target block".into(), "".into()), + // Handle all phases besides the two phases we already handle above. + (_, _, Some(warp)) + if !matches!( warp.phase, - (warp.total_bytes as f32) / (1024f32 * 1024f32) + WarpSyncPhase::AwaitingTargetBlock | WarpSyncPhase::DownloadingBlocks(_) + ) => + ( + "⏩", + "Warping".into(), + format!( + ", {}, {:.2} Mib", + warp.phase, + (warp.total_bytes as f32) / (1024f32 * 1024f32) + ), ), - ), (_, Some(state), _) => ( "⚙️ ", "Downloading state".into(), diff --git a/client/informant/src/lib.rs b/client/informant/src/lib.rs index b03b92686e2a..03f9075055e2 100644 --- a/client/informant/src/lib.rs +++ b/client/informant/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -23,7 +23,8 @@ use futures::prelude::*; use futures_timer::Delay; use log::{debug, info, trace}; use sc_client_api::{BlockchainEvents, UsageProvider}; -use sc_network_common::service::NetworkStatusProvider; +use sc_network::NetworkStatusProvider; +use sc_network_common::sync::SyncStatusProvider; use sp_blockchain::HeaderMetadata; use sp_runtime::traits::{Block as BlockT, Header}; use std::{collections::VecDeque, fmt::Display, sync::Arc, time::Duration}; @@ -51,9 +52,10 @@ impl Default for OutputFormat { } /// Builds the informant and returns a `Future` that drives the informant. -pub async fn build(client: Arc, network: N, format: OutputFormat) +pub async fn build(client: Arc, network: N, syncing: S, format: OutputFormat) where - N: NetworkStatusProvider, + N: NetworkStatusProvider, + S: SyncStatusProvider, C: UsageProvider + HeaderMetadata + BlockchainEvents, >::Error: Display, { @@ -63,10 +65,15 @@ where let display_notifications = interval(Duration::from_millis(5000)) .filter_map(|_| async { - let status = network.status().await; - status.ok() + let net_status = network.status().await; + let sync_status = syncing.status().await; + + match (net_status.ok(), sync_status.ok()) { + (Some(net), Some(sync)) => Some((net, sync)), + _ => None, + } }) - .for_each(move |net_status| { + .for_each(move |(net_status, sync_status)| { let info = client_1.usage_info(); if let Some(ref usage) = info.usage { trace!(target: "usage", "Usage statistics: {}", usage); @@ -76,7 +83,7 @@ where "Usage statistics not displayed as backend does not provide it", ) } - display.display(&info, net_status); + display.display(&info, net_status, sync_status); future::ready(()) }); diff --git a/client/keystore/Cargo.toml b/client/keystore/Cargo.toml index 8766ee80157e..0ecdb896199c 100644 --- a/client/keystore/Cargo.toml +++ b/client/keystore/Cargo.toml @@ -14,14 +14,21 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -array-bytes = "4.1" -async-trait = "0.1.57" +array-bytes = "6.1" parking_lot = "0.12.1" serde_json = "1.0.85" thiserror = "1.0" -sp-application-crypto = { version = "7.0.0", path = "../../primitives/application-crypto" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } +sp-application-crypto = { version = "23.0.0", path = "../../primitives/application-crypto" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-keystore = { version = "0.27.0", path = "../../primitives/keystore" } [dev-dependencies] tempfile = "3.1.0" + +[features] +# This feature adds BLS crypto primitives. It should not be used in production since +# the BLS implementation and interface may still be subject to significant change. +bls-experimental = [ + "sp-core/bls-experimental", + "sp-keystore/bls-experimental", +] diff --git a/client/keystore/src/lib.rs b/client/keystore/src/lib.rs index cf94a16f08d8..2d353f3ceba5 100644 --- a/client/keystore/src/lib.rs +++ b/client/keystore/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -26,6 +26,7 @@ use std::io; /// Local keystore implementation mod local; pub use local::LocalKeystore; +pub use sp_keystore::Keystore; /// Keystore error. #[derive(Debug, thiserror::Error)] diff --git a/client/keystore/src/local.rs b/client/keystore/src/local.rs index 54ff6a5b164a..4167e486ecf6 100644 --- a/client/keystore/src/local.rs +++ b/client/keystore/src/local.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,22 +17,17 @@ // //! Local keystore implementation -use async_trait::async_trait; use parking_lot::RwLock; -use sp_application_crypto::{ecdsa, ed25519, sr25519, AppKey, AppPair, IsWrappedBy}; +use sp_application_crypto::{AppCrypto, AppPair, IsWrappedBy}; +#[cfg(feature = "bls-experimental")] +use sp_core::{bls377, bls381}; use sp_core::{ - crypto::{ - ByteArray, CryptoTypePublicPair, ExposeSecret, KeyTypeId, Pair as PairT, SecretString, - }, - sr25519::{Pair as Sr25519Pair, Public as Sr25519Public}, - Encode, -}; -use sp_keystore::{ - vrf::{make_transcript, VRFSignature, VRFTranscriptData}, - CryptoStore, Error as TraitError, SyncCryptoStore, SyncCryptoStorePtr, + crypto::{ByteArray, ExposeSecret, KeyTypeId, Pair as CorePair, SecretString, VrfSecret}, + ecdsa, ed25519, sr25519, }; +use sp_keystore::{Error as TraitError, Keystore, KeystorePtr}; use std::{ - collections::{HashMap, HashSet}, + collections::HashMap, fs::{self, File}, io::Write, path::PathBuf, @@ -63,258 +58,247 @@ impl LocalKeystore { /// `Err(_)` when something failed. pub fn key_pair( &self, - public: &::Public, + public: &::Public, ) -> Result> { self.0.read().key_pair::(public) } -} - -#[async_trait] -impl CryptoStore for LocalKeystore { - async fn keys( - &self, - id: KeyTypeId, - ) -> std::result::Result, TraitError> { - SyncCryptoStore::keys(self, id) - } - async fn sr25519_public_keys(&self, id: KeyTypeId) -> Vec { - SyncCryptoStore::sr25519_public_keys(self, id) + fn public_keys(&self, key_type: KeyTypeId) -> Vec { + self.0 + .read() + .raw_public_keys(key_type) + .map(|v| { + v.into_iter().filter_map(|k| T::Public::from_slice(k.as_slice()).ok()).collect() + }) + .unwrap_or_default() } - async fn sr25519_generate_new( + fn generate_new( &self, - id: KeyTypeId, + key_type: KeyTypeId, seed: Option<&str>, - ) -> std::result::Result { - SyncCryptoStore::sr25519_generate_new(self, id, seed) - } - - async fn ed25519_public_keys(&self, id: KeyTypeId) -> Vec { - SyncCryptoStore::ed25519_public_keys(self, id) + ) -> std::result::Result { + let pair = match seed { + Some(seed) => self.0.write().insert_ephemeral_from_seed_by_type::(seed, key_type), + None => self.0.write().generate_by_type::(key_type), + } + .map_err(|e| -> TraitError { e.into() })?; + Ok(pair.public()) } - async fn ed25519_generate_new( + fn sign( &self, - id: KeyTypeId, - seed: Option<&str>, - ) -> std::result::Result { - SyncCryptoStore::ed25519_generate_new(self, id, seed) - } - - async fn ecdsa_public_keys(&self, id: KeyTypeId) -> Vec { - SyncCryptoStore::ecdsa_public_keys(self, id) + key_type: KeyTypeId, + public: &T::Public, + msg: &[u8], + ) -> std::result::Result, TraitError> { + let signature = self + .0 + .read() + .key_pair_by_type::(public, key_type)? + .map(|pair| pair.sign(msg)); + Ok(signature) } - async fn ecdsa_generate_new( + fn vrf_sign( &self, - id: KeyTypeId, - seed: Option<&str>, - ) -> std::result::Result { - SyncCryptoStore::ecdsa_generate_new(self, id, seed) + key_type: KeyTypeId, + public: &T::Public, + data: &T::VrfSignData, + ) -> std::result::Result, TraitError> { + let sig = self + .0 + .read() + .key_pair_by_type::(public, key_type)? + .map(|pair| pair.vrf_sign(data)); + Ok(sig) } - async fn insert_unknown( + fn vrf_output( &self, - id: KeyTypeId, - suri: &str, - public: &[u8], - ) -> std::result::Result<(), ()> { - SyncCryptoStore::insert_unknown(self, id, suri, public) + key_type: KeyTypeId, + public: &T::Public, + input: &T::VrfInput, + ) -> std::result::Result, TraitError> { + let preout = self + .0 + .read() + .key_pair_by_type::(public, key_type)? + .map(|pair| pair.vrf_output(input)); + Ok(preout) } +} - async fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool { - SyncCryptoStore::has_keys(self, public_keys) +impl Keystore for LocalKeystore { + fn sr25519_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) } - async fn supported_keys( + /// Generate a new pair compatible with the 'ed25519' signature scheme. + /// + /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. + fn sr25519_generate_new( &self, - id: KeyTypeId, - keys: Vec, - ) -> std::result::Result, TraitError> { - SyncCryptoStore::supported_keys(self, id, keys) + key_type: KeyTypeId, + seed: Option<&str>, + ) -> std::result::Result { + self.generate_new::(key_type, seed) } - async fn sign_with( + fn sr25519_sign( &self, - id: KeyTypeId, - key: &CryptoTypePublicPair, + key_type: KeyTypeId, + public: &sr25519::Public, msg: &[u8], - ) -> std::result::Result>, TraitError> { - SyncCryptoStore::sign_with(self, id, key, msg) + ) -> std::result::Result, TraitError> { + self.sign::(key_type, public, msg) } - async fn sr25519_vrf_sign( + fn sr25519_vrf_sign( &self, key_type: KeyTypeId, public: &sr25519::Public, - transcript_data: VRFTranscriptData, - ) -> std::result::Result, TraitError> { - SyncCryptoStore::sr25519_vrf_sign(self, key_type, public, transcript_data) + data: &sr25519::vrf::VrfSignData, + ) -> std::result::Result, TraitError> { + self.vrf_sign::(key_type, public, data) } - async fn ecdsa_sign_prehashed( + fn sr25519_vrf_output( &self, - id: KeyTypeId, - public: &ecdsa::Public, - msg: &[u8; 32], - ) -> std::result::Result, TraitError> { - SyncCryptoStore::ecdsa_sign_prehashed(self, id, public, msg) + key_type: KeyTypeId, + public: &sr25519::Public, + input: &sr25519::vrf::VrfInput, + ) -> std::result::Result, TraitError> { + self.vrf_output::(key_type, public, input) } -} -impl SyncCryptoStore for LocalKeystore { - fn keys(&self, id: KeyTypeId) -> std::result::Result, TraitError> { - let raw_keys = self.0.read().raw_public_keys(id)?; - Ok(raw_keys.into_iter().fold(Vec::new(), |mut v, k| { - v.push(CryptoTypePublicPair(sr25519::CRYPTO_ID, k.clone())); - v.push(CryptoTypePublicPair(ed25519::CRYPTO_ID, k.clone())); - v.push(CryptoTypePublicPair(ecdsa::CRYPTO_ID, k)); - v - })) + fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) } - fn supported_keys( + /// Generate a new pair compatible with the 'sr25519' signature scheme. + /// + /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. + fn ed25519_generate_new( &self, - id: KeyTypeId, - keys: Vec, - ) -> std::result::Result, TraitError> { - let all_keys = SyncCryptoStore::keys(self, id)?.into_iter().collect::>(); - Ok(keys.into_iter().filter(|key| all_keys.contains(key)).collect::>()) + key_type: KeyTypeId, + seed: Option<&str>, + ) -> std::result::Result { + self.generate_new::(key_type, seed) } - fn sign_with( + fn ed25519_sign( &self, - id: KeyTypeId, - key: &CryptoTypePublicPair, + key_type: KeyTypeId, + public: &ed25519::Public, msg: &[u8], - ) -> std::result::Result>, TraitError> { - match key.0 { - ed25519::CRYPTO_ID => { - let pub_key = ed25519::Public::from_slice(key.1.as_slice()).map_err(|()| { - TraitError::Other("Corrupted public key - Invalid size".into()) - })?; - let key_pair = self - .0 - .read() - .key_pair_by_type::(&pub_key, id) - .map_err(TraitError::from)?; - key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() - }, - sr25519::CRYPTO_ID => { - let pub_key = sr25519::Public::from_slice(key.1.as_slice()).map_err(|()| { - TraitError::Other("Corrupted public key - Invalid size".into()) - })?; - let key_pair = self - .0 - .read() - .key_pair_by_type::(&pub_key, id) - .map_err(TraitError::from)?; - key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() - }, - ecdsa::CRYPTO_ID => { - let pub_key = ecdsa::Public::from_slice(key.1.as_slice()).map_err(|()| { - TraitError::Other("Corrupted public key - Invalid size".into()) - })?; - let key_pair = self - .0 - .read() - .key_pair_by_type::(&pub_key, id) - .map_err(TraitError::from)?; - key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() - }, - _ => Err(TraitError::KeyNotSupported(id)), - } + ) -> std::result::Result, TraitError> { + self.sign::(key_type, public, msg) } - fn sr25519_public_keys(&self, key_type: KeyTypeId) -> Vec { - self.0 - .read() - .raw_public_keys(key_type) - .map(|v| { - v.into_iter() - .filter_map(|k| sr25519::Public::from_slice(k.as_slice()).ok()) - .collect() - }) - .unwrap_or_default() + fn ecdsa_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) } - fn sr25519_generate_new( + /// Generate a new pair compatible with the 'ecdsa' signature scheme. + /// + /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. + fn ecdsa_generate_new( &self, - id: KeyTypeId, + key_type: KeyTypeId, seed: Option<&str>, - ) -> std::result::Result { - let pair = match seed { - Some(seed) => - self.0.write().insert_ephemeral_from_seed_by_type::(seed, id), - None => self.0.write().generate_by_type::(id), - } - .map_err(|e| -> TraitError { e.into() })?; + ) -> std::result::Result { + self.generate_new::(key_type, seed) + } - Ok(pair.public()) + fn ecdsa_sign( + &self, + key_type: KeyTypeId, + public: &ecdsa::Public, + msg: &[u8], + ) -> std::result::Result, TraitError> { + self.sign::(key_type, public, msg) } - fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec { - self.0 + fn ecdsa_sign_prehashed( + &self, + key_type: KeyTypeId, + public: &ecdsa::Public, + msg: &[u8; 32], + ) -> std::result::Result, TraitError> { + let sig = self + .0 .read() - .raw_public_keys(key_type) - .map(|v| { - v.into_iter() - .filter_map(|k| ed25519::Public::from_slice(k.as_slice()).ok()) - .collect() - }) - .unwrap_or_default() + .key_pair_by_type::(public, key_type)? + .map(|pair| pair.sign_prehashed(msg)); + Ok(sig) } - fn ed25519_generate_new( + #[cfg(feature = "bls-experimental")] + fn bls381_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) + } + + #[cfg(feature = "bls-experimental")] + /// Generate a new pair compatible with the 'bls381' signature scheme. + /// + /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. + fn bls381_generate_new( &self, - id: KeyTypeId, + key_type: KeyTypeId, seed: Option<&str>, - ) -> std::result::Result { - let pair = match seed { - Some(seed) => - self.0.write().insert_ephemeral_from_seed_by_type::(seed, id), - None => self.0.write().generate_by_type::(id), - } - .map_err(|e| -> TraitError { e.into() })?; + ) -> std::result::Result { + self.generate_new::(key_type, seed) + } - Ok(pair.public()) + #[cfg(feature = "bls-experimental")] + fn bls381_sign( + &self, + key_type: KeyTypeId, + public: &bls381::Public, + msg: &[u8], + ) -> std::result::Result, TraitError> { + self.sign::(key_type, public, msg) } - fn ecdsa_public_keys(&self, key_type: KeyTypeId) -> Vec { - self.0 - .read() - .raw_public_keys(key_type) - .map(|v| { - v.into_iter() - .filter_map(|k| ecdsa::Public::from_slice(k.as_slice()).ok()) - .collect() - }) - .unwrap_or_default() + #[cfg(feature = "bls-experimental")] + fn bls377_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) } - fn ecdsa_generate_new( + #[cfg(feature = "bls-experimental")] + /// Generate a new pair compatible with the 'bls377' signature scheme. + /// + /// If `[seed]` is `Some` then the key will be ephemeral and stored in memory. + fn bls377_generate_new( &self, - id: KeyTypeId, + key_type: KeyTypeId, seed: Option<&str>, - ) -> std::result::Result { - let pair = match seed { - Some(seed) => - self.0.write().insert_ephemeral_from_seed_by_type::(seed, id), - None => self.0.write().generate_by_type::(id), - } - .map_err(|e| -> TraitError { e.into() })?; + ) -> std::result::Result { + self.generate_new::(key_type, seed) + } - Ok(pair.public()) + #[cfg(feature = "bls-experimental")] + fn bls377_sign( + &self, + key_type: KeyTypeId, + public: &bls377::Public, + msg: &[u8], + ) -> std::result::Result, TraitError> { + self.sign::(key_type, public, msg) } - fn insert_unknown( + fn insert( &self, key_type: KeyTypeId, suri: &str, public: &[u8], ) -> std::result::Result<(), ()> { - self.0.write().insert_unknown(key_type, suri, public).map_err(|_| ()) + self.0.write().insert(key_type, suri, public).map_err(|_| ()) + } + + fn keys(&self, key_type: KeyTypeId) -> std::result::Result>, TraitError> { + self.0.read().raw_public_keys(key_type).map_err(|e| e.into()) } fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool { @@ -322,44 +306,10 @@ impl SyncCryptoStore for LocalKeystore { .iter() .all(|(p, t)| self.0.read().key_phrase_by_type(p, *t).ok().flatten().is_some()) } - - fn sr25519_vrf_sign( - &self, - key_type: KeyTypeId, - public: &Sr25519Public, - transcript_data: VRFTranscriptData, - ) -> std::result::Result, TraitError> { - let transcript = make_transcript(transcript_data); - let pair = self.0.read().key_pair_by_type::(public, key_type)?; - - if let Some(pair) = pair { - let (inout, proof, _) = pair.as_ref().vrf_sign(transcript); - Ok(Some(VRFSignature { output: inout.to_output(), proof })) - } else { - Ok(None) - } - } - - fn ecdsa_sign_prehashed( - &self, - id: KeyTypeId, - public: &ecdsa::Public, - msg: &[u8; 32], - ) -> std::result::Result, TraitError> { - let pair = self.0.read().key_pair_by_type::(public, id)?; - - pair.map(|k| k.sign_prehashed(msg)).map(Ok).transpose() - } } -impl Into for LocalKeystore { - fn into(self) -> SyncCryptoStorePtr { - Arc::new(self) - } -} - -impl Into> for LocalKeystore { - fn into(self) -> Arc { +impl Into for LocalKeystore { + fn into(self) -> KeystorePtr { Arc::new(self) } } @@ -406,7 +356,12 @@ impl KeystoreInner { /// Insert the given public/private key pair with the given key type. /// /// Does not place it into the file system store. - fn insert_ephemeral_pair(&mut self, pair: &Pair, seed: &str, key_type: KeyTypeId) { + fn insert_ephemeral_pair( + &mut self, + pair: &Pair, + seed: &str, + key_type: KeyTypeId, + ) { let key = (key_type, pair.public().to_raw_vec()); self.additional.insert(key, seed.into()); } @@ -414,7 +369,7 @@ impl KeystoreInner { /// Insert a new key with anonymous crypto. /// /// Places it into the file system store, if a path is configured. - fn insert_unknown(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<()> { + fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<()> { if let Some(path) = self.key_file_path(public, key_type) { Self::write_to_file(path, suri)?; } @@ -426,7 +381,7 @@ impl KeystoreInner { /// /// Places it into the file system store, if a path is configured. Otherwise insert /// it into the memory cache only. - fn generate_by_type(&mut self, key_type: KeyTypeId) -> Result { + fn generate_by_type(&mut self, key_type: KeyTypeId) -> Result { let (pair, phrase, _) = Pair::generate_with_phrase(self.password()); if let Some(path) = self.key_file_path(pair.public().as_slice(), key_type) { Self::write_to_file(path, &phrase)?; @@ -455,7 +410,7 @@ impl KeystoreInner { /// Create a new key from seed. /// /// Does not place it into the file system store. - fn insert_ephemeral_from_seed_by_type( + fn insert_ephemeral_from_seed_by_type( &mut self, seed: &str, key_type: KeyTypeId, @@ -487,7 +442,7 @@ impl KeystoreInner { } /// Get a key pair for the given public key and key type. - fn key_pair_by_type( + fn key_pair_by_type( &self, public: &Pair::Public, key_type: KeyTypeId, @@ -519,12 +474,12 @@ impl KeystoreInner { } /// Returns a list of raw public keys filtered by `KeyTypeId` - fn raw_public_keys(&self, id: KeyTypeId) -> Result>> { + fn raw_public_keys(&self, key_type: KeyTypeId) -> Result>> { let mut public_keys: Vec> = self .additional .keys() .into_iter() - .filter_map(|k| if k.0 == id { Some(k.1.clone()) } else { None }) + .filter_map(|k| if k.0 == key_type { Some(k.1.clone()) } else { None }) .collect(); if let Some(path) = &self.path { @@ -536,7 +491,7 @@ impl KeystoreInner { if let Some(name) = path.file_name().and_then(|n| n.to_str()) { match array_bytes::hex2bytes(name) { Ok(ref hex) if hex.len() > 4 => { - if hex[0..4] != id.0 { + if hex[0..4] != key_type.0 { continue } let public = hex[4..].to_vec(); @@ -557,7 +512,7 @@ impl KeystoreInner { /// when something failed. pub fn key_pair( &self, - public: &::Public, + public: &::Public, ) -> Result> { self.key_pair_by_type::(IsWrappedBy::from_ref(public), Pair::ID) .map(|v| v.map(Into::into)) @@ -614,23 +569,14 @@ mod tests { let key: ed25519::AppPair = store.0.write().generate().unwrap(); let key2 = ed25519::Pair::generate().0; - assert!(!SyncCryptoStore::has_keys( - &store, - &[(key2.public().to_vec(), ed25519::AppPublic::ID)] - )); + assert!(!store.has_keys(&[(key2.public().to_vec(), ed25519::AppPublic::ID)])); - assert!(!SyncCryptoStore::has_keys( - &store, - &[ - (key2.public().to_vec(), ed25519::AppPublic::ID), - (key.public().to_raw_vec(), ed25519::AppPublic::ID), - ], - )); + assert!(!store.has_keys(&[ + (key2.public().to_vec(), ed25519::AppPublic::ID), + (key.public().to_raw_vec(), ed25519::AppPublic::ID), + ],)); - assert!(SyncCryptoStore::has_keys( - &store, - &[(key.public().to_raw_vec(), ed25519::AppPublic::ID)] - )); + assert!(store.has_keys(&[(key.public().to_raw_vec(), ed25519::AppPublic::ID)])); } #[test] @@ -723,7 +669,7 @@ mod tests { let key_pair = sr25519::AppPair::from_string(secret_uri, None).expect("Generates key pair"); store - .insert_unknown(SR25519, secret_uri, key_pair.public().as_ref()) + .insert(SR25519, secret_uri, key_pair.public().as_ref()) .expect("Inserts unknown key"); let store_key_pair = store @@ -742,31 +688,30 @@ mod tests { let file_name = temp_dir.path().join(array_bytes::bytes2hex("", &SR25519.0[..2])); fs::write(file_name, "test").expect("Invalid file is written"); - assert!(SyncCryptoStore::sr25519_public_keys(&store, SR25519).is_empty()); + assert!(store.sr25519_public_keys(SR25519).is_empty()); } #[test] fn generate_with_seed_is_not_stored() { let temp_dir = TempDir::new().unwrap(); let store = LocalKeystore::open(temp_dir.path(), None).unwrap(); - let _alice_tmp_key = - SyncCryptoStore::sr25519_generate_new(&store, TEST_KEY_TYPE, Some("//Alice")).unwrap(); + let _alice_tmp_key = store.sr25519_generate_new(TEST_KEY_TYPE, Some("//Alice")).unwrap(); - assert_eq!(SyncCryptoStore::sr25519_public_keys(&store, TEST_KEY_TYPE).len(), 1); + assert_eq!(store.sr25519_public_keys(TEST_KEY_TYPE).len(), 1); drop(store); let store = LocalKeystore::open(temp_dir.path(), None).unwrap(); - assert_eq!(SyncCryptoStore::sr25519_public_keys(&store, TEST_KEY_TYPE).len(), 0); + assert_eq!(store.sr25519_public_keys(TEST_KEY_TYPE).len(), 0); } #[test] fn generate_can_be_fetched_in_memory() { let store = LocalKeystore::in_memory(); - SyncCryptoStore::sr25519_generate_new(&store, TEST_KEY_TYPE, Some("//Alice")).unwrap(); + store.sr25519_generate_new(TEST_KEY_TYPE, Some("//Alice")).unwrap(); - assert_eq!(SyncCryptoStore::sr25519_public_keys(&store, TEST_KEY_TYPE).len(), 1); - SyncCryptoStore::sr25519_generate_new(&store, TEST_KEY_TYPE, None).unwrap(); - assert_eq!(SyncCryptoStore::sr25519_public_keys(&store, TEST_KEY_TYPE).len(), 2); + assert_eq!(store.sr25519_public_keys(TEST_KEY_TYPE).len(), 1); + store.sr25519_generate_new(TEST_KEY_TYPE, None).unwrap(); + assert_eq!(store.sr25519_public_keys(TEST_KEY_TYPE).len(), 2); } #[test] @@ -777,7 +722,7 @@ mod tests { let temp_dir = TempDir::new().unwrap(); let store = LocalKeystore::open(temp_dir.path(), None).unwrap(); - let public = SyncCryptoStore::sr25519_generate_new(&store, TEST_KEY_TYPE, None).unwrap(); + let public = store.sr25519_generate_new(TEST_KEY_TYPE, None).unwrap(); let path = store.0.read().key_file_path(public.as_ref(), TEST_KEY_TYPE).unwrap(); let permissions = File::open(path).unwrap().metadata().unwrap().permissions(); diff --git a/client/merkle-mountain-range/Cargo.toml b/client/merkle-mountain-range/Cargo.toml index 90fb65453ba1..7270cfbda016 100644 --- a/client/merkle-mountain-range/Cargo.toml +++ b/client/merkle-mountain-range/Cargo.toml @@ -11,22 +11,22 @@ homepage = "https://substrate.io" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3" log = "0.4" -beefy-primitives = { version = "4.0.0-dev", path = "../../primitives/beefy", package = "sp-beefy" } -sc-client-api = { version = "4.0.0-dev", path = "../api" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sc-client-api = { version = "4.0.0-dev", path = "../api" } +sp-consensus-beefy = { version = "4.0.0-dev", path = "../../primitives/consensus/beefy" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } sp-mmr-primitives = { version = "4.0.0-dev", path = "../../primitives/merkle-mountain-range" } sc-offchain = { version = "4.0.0-dev", path = "../offchain" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } [dev-dependencies] parking_lot = "0.12.1" sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } tokio = "1.17.0" diff --git a/client/merkle-mountain-range/rpc/Cargo.toml b/client/merkle-mountain-range/rpc/Cargo.toml index ce71158808e1..c6f8652c7e84 100644 --- a/client/merkle-mountain-range/rpc/Cargo.toml +++ b/client/merkle-mountain-range/rpc/Cargo.toml @@ -12,14 +12,14 @@ description = "Node-specific RPC methods for interaction with Merkle Mountain Ra targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } -serde = { version = "1.0.136", features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"] } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } sp-mmr-primitives = { version = "4.0.0-dev", path = "../../../primitives/merkle-mountain-range" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } anyhow = "1" [dev-dependencies] diff --git a/client/merkle-mountain-range/rpc/src/lib.rs b/client/merkle-mountain-range/rpc/src/lib.rs index 6e520b3bf130..5be82b600d91 100644 --- a/client/merkle-mountain-range/rpc/src/lib.rs +++ b/client/merkle-mountain-range/rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,11 +30,14 @@ use jsonrpsee::{ }; use serde::{Deserialize, Serialize}; -use sp_api::{NumberFor, ProvideRuntimeApi}; +use sp_api::{ApiExt, NumberFor, ProvideRuntimeApi}; use sp_blockchain::HeaderBackend; -use sp_core::Bytes; +use sp_core::{ + offchain::{storage::OffchainDb, OffchainDbExt, OffchainStorage}, + Bytes, +}; use sp_mmr_primitives::{Error as MmrError, Proof}; -use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use sp_runtime::traits::Block as BlockT; pub use sp_mmr_primitives::MmrApi as MmrRuntimeApi; @@ -127,26 +130,28 @@ pub trait MmrApi { } /// MMR RPC methods. -pub struct Mmr { +pub struct Mmr { client: Arc, + offchain_db: OffchainDb, _marker: PhantomData, } -impl Mmr { +impl Mmr { /// Create new `Mmr` with the given reference to the client. - pub fn new(client: Arc) -> Self { - Self { client, _marker: Default::default() } + pub fn new(client: Arc, offchain_storage: S) -> Self { + Self { client, _marker: Default::default(), offchain_db: OffchainDb::new(offchain_storage) } } } #[async_trait] -impl MmrApiServer<::Hash, NumberFor, MmrHash> - for Mmr +impl MmrApiServer<::Hash, NumberFor, MmrHash> + for Mmr where Block: BlockT, Client: Send + Sync + 'static + ProvideRuntimeApi + HeaderBackend, Client::Api: MmrRuntimeApi>, MmrHash: Codec + Send + Sync + 'static, + S: OffchainStorage + 'static, { fn mmr_root(&self, at: Option<::Hash>) -> RpcResult { let block_hash = at.unwrap_or_else(|| @@ -154,7 +159,7 @@ where self.client.info().best_hash); let api = self.client.runtime_api(); let mmr_root = api - .mmr_root(&BlockId::Hash(block_hash)) + .mmr_root(block_hash) .map_err(runtime_error_into_rpc_error)? .map_err(mmr_error_into_rpc_error)?; Ok(mmr_root) @@ -166,18 +171,15 @@ where best_known_block_number: Option>, at: Option<::Hash>, ) -> RpcResult::Hash>> { - let api = self.client.runtime_api(); + let mut api = self.client.runtime_api(); let block_hash = at.unwrap_or_else(|| // If the block hash is not supplied assume the best block. self.client.info().best_hash); + api.register_extension(OffchainDbExt::new(self.offchain_db.clone())); + let (leaves, proof) = api - .generate_proof_with_context( - &BlockId::hash(block_hash), - sp_core::ExecutionContext::OffchainCall(None), - block_numbers, - best_known_block_number, - ) + .generate_proof(block_hash, block_numbers, best_known_block_number) .map_err(runtime_error_into_rpc_error)? .map_err(mmr_error_into_rpc_error)?; @@ -185,7 +187,7 @@ where } fn verify_proof(&self, proof: LeavesProof<::Hash>) -> RpcResult { - let api = self.client.runtime_api(); + let mut api = self.client.runtime_api(); let leaves = Decode::decode(&mut &proof.leaves.0[..]) .map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?; @@ -193,14 +195,11 @@ where let decoded_proof = Decode::decode(&mut &proof.proof.0[..]) .map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?; - api.verify_proof_with_context( - &BlockId::hash(proof.block_hash), - sp_core::ExecutionContext::OffchainCall(None), - leaves, - decoded_proof, - ) - .map_err(runtime_error_into_rpc_error)? - .map_err(mmr_error_into_rpc_error)?; + api.register_extension(OffchainDbExt::new(self.offchain_db.clone())); + + api.verify_proof(proof.block_hash, leaves, decoded_proof) + .map_err(runtime_error_into_rpc_error)? + .map_err(mmr_error_into_rpc_error)?; Ok(true) } @@ -218,14 +217,9 @@ where let decoded_proof = Decode::decode(&mut &proof.proof.0[..]) .map_err(|e| CallError::InvalidParams(anyhow::Error::new(e)))?; - api.verify_proof_stateless( - &BlockId::hash(proof.block_hash), - mmr_root, - leaves, - decoded_proof, - ) - .map_err(runtime_error_into_rpc_error)? - .map_err(mmr_error_into_rpc_error)?; + api.verify_proof_stateless(proof.block_hash, mmr_root, leaves, decoded_proof) + .map_err(runtime_error_into_rpc_error)? + .map_err(mmr_error_into_rpc_error)?; Ok(true) } diff --git a/client/merkle-mountain-range/src/aux_schema.rs b/client/merkle-mountain-range/src/aux_schema.rs index e6c53d406798..436252267f10 100644 --- a/client/merkle-mountain-range/src/aux_schema.rs +++ b/client/merkle-mountain-range/src/aux_schema.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/merkle-mountain-range/src/lib.rs b/client/merkle-mountain-range/src/lib.rs index 568c4b94c1e0..e174af7068ab 100644 --- a/client/merkle-mountain-range/src/lib.rs +++ b/client/merkle-mountain-range/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -43,18 +43,15 @@ mod offchain_mmr; pub mod test_utils; use crate::offchain_mmr::OffchainMmr; -use beefy_primitives::MmrRootHash; use futures::StreamExt; use log::{debug, error, trace, warn}; use sc_client_api::{Backend, BlockchainEvents, FinalityNotification, FinalityNotifications}; use sc_offchain::OffchainDb; use sp_api::ProvideRuntimeApi; use sp_blockchain::{HeaderBackend, HeaderMetadata}; +use sp_consensus_beefy::MmrRootHash; use sp_mmr_primitives::{utils, LeafIndex, MmrApi}; -use sp_runtime::{ - generic::BlockId, - traits::{Block, Header, NumberFor}, -}; +use sp_runtime::traits::{Block, Header, NumberFor}; use std::{marker::PhantomData, sync::Arc}; /// Logging target for the mmr gadget. @@ -71,15 +68,16 @@ where { /// Get the block number where the mmr pallet was added to the runtime. fn first_mmr_block_num(&self, notification: &FinalityNotification) -> Option> { - let best_block = *notification.header.number(); - match self.runtime_api().mmr_leaf_count(&BlockId::number(best_block)) { + let best_block_hash = notification.header.hash(); + let best_block_number = *notification.header.number(); + match self.runtime_api().mmr_leaf_count(best_block_hash) { Ok(Ok(mmr_leaf_count)) => { - match utils::first_mmr_block_num::(best_block, mmr_leaf_count) { + match utils::first_mmr_block_num::(best_block_number, mmr_leaf_count) { Ok(first_mmr_block) => { debug!( target: LOG_TARGET, "pallet-mmr detected at block {:?} with genesis at block {:?}", - best_block, + best_block_number, first_mmr_block ); Some(first_mmr_block) @@ -97,7 +95,7 @@ where trace!( target: LOG_TARGET, "pallet-mmr not detected at block {:?} ... (best finalized {:?})", - best_block, + best_block_number, notification.header.number() ); None diff --git a/client/merkle-mountain-range/src/offchain_mmr.rs b/client/merkle-mountain-range/src/offchain_mmr.rs index bbb53cf3a934..3c3f0beb6c6a 100644 --- a/client/merkle-mountain-range/src/offchain_mmr.rs +++ b/client/merkle-mountain-range/src/offchain_mmr.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -22,11 +22,11 @@ #![warn(missing_docs)] use crate::{aux_schema, MmrClient, LOG_TARGET}; -use beefy_primitives::MmrRootHash; use log::{debug, error, info, warn}; use sc_client_api::{Backend, FinalityNotification}; use sc_offchain::OffchainDb; use sp_blockchain::{CachedHeaderMetadata, ForkBackend}; +use sp_consensus_beefy::MmrRootHash; use sp_core::offchain::{DbExternalities, StorageKind}; use sp_mmr_primitives::{utils, utils::NodesUtils, MmrApi, NodeIndex}; use sp_runtime::{ diff --git a/client/merkle-mountain-range/src/test_utils.rs b/client/merkle-mountain-range/src/test_utils.rs index e5a6673483db..010b48bb3d7d 100644 --- a/client/merkle-mountain-range/src/test_utils.rs +++ b/client/merkle-mountain-range/src/test_utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -124,7 +124,8 @@ impl MockClient { ) -> MmrBlock { let mut client = self.client.lock(); - let mut block_builder = client.new_block_at(at, Default::default(), false).unwrap(); + let hash = client.expect_block_hash_from_id(&at).unwrap(); + let mut block_builder = client.new_block_at(hash, Default::default(), false).unwrap(); // Make sure the block has a different hash than its siblings block_builder .push_storage_change(b"name".to_vec(), Some(name.to_vec())) @@ -264,6 +265,10 @@ impl BlockchainEvents for MockClient { unimplemented!() } + fn every_import_notification_stream(&self) -> ImportNotifications { + unimplemented!() + } + fn finality_notification_stream(&self) -> FinalityNotifications { self.client.lock().finality_notification_stream() } diff --git a/client/network-gossip/Cargo.toml b/client/network-gossip/Cargo.toml index 7811e86c095b..e25a769587da 100644 --- a/client/network-gossip/Cargo.toml +++ b/client/network-gossip/Cargo.toml @@ -17,14 +17,14 @@ targets = ["x86_64-unknown-linux-gnu"] ahash = "0.8.2" futures = "0.3.21" futures-timer = "3.0.1" -libp2p = "0.50.0" +libp2p = "0.51.3" log = "0.4.17" -lru = "0.8.1" +schnellru = "0.2.1" tracing = "0.1.29" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } +sc-network = { version = "0.10.0-dev", path = "../network/" } sc-network-common = { version = "0.10.0-dev", path = "../network/common" } -sc-peerset = { version = "4.0.0-dev", path = "../peerset" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } [dev-dependencies] tokio = "1.22.0" diff --git a/client/network-gossip/src/bridge.rs b/client/network-gossip/src/bridge.rs index 5d7fd96c13e5..2cd4e1817156 100644 --- a/client/network-gossip/src/bridge.rs +++ b/client/network-gossip/src/bridge.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,11 +18,11 @@ use crate::{ state_machine::{ConsensusGossip, TopicNotification, PERIODIC_MAINTENANCE_INTERVAL}, - Network, Validator, + Network, Syncing, Validator, }; -use sc_network_common::protocol::{event::Event, ProtocolName}; -use sc_peerset::ReputationChange; +use sc_network::{event::Event, types::ProtocolName, ReputationChange}; +use sc_network_common::sync::SyncEvent; use futures::{ channel::mpsc::{channel, Receiver, Sender}, @@ -44,11 +44,14 @@ use std::{ pub struct GossipEngine { state_machine: ConsensusGossip, network: Box + Send>, + sync: Box>, periodic_maintenance_interval: futures_timer::Delay, protocol: ProtocolName, /// Incoming events from the network. network_event_stream: Pin + Send>>, + /// Incoming events from the syncing service. + sync_event_stream: Pin + Send>>, /// Outgoing events to the consumer. message_sinks: HashMap>>, /// Buffered messages (see [`ForwardingState`]). @@ -75,25 +78,31 @@ impl Unpin for GossipEngine {} impl GossipEngine { /// Create a new instance. - pub fn new + Send + Clone + 'static>( + pub fn new( network: N, + sync: S, protocol: impl Into, validator: Arc>, metrics_registry: Option<&Registry>, ) -> Self where B: 'static, + N: Network + Send + Clone + 'static, + S: Syncing + Send + Clone + 'static, { let protocol = protocol.into(); let network_event_stream = network.event_stream("network-gossip"); + let sync_event_stream = sync.event_stream("network-gossip"); GossipEngine { state_machine: ConsensusGossip::new(validator, protocol.clone(), metrics_registry), network: Box::new(network), + sync: Box::new(sync), periodic_maintenance_interval: futures_timer::Delay::new(PERIODIC_MAINTENANCE_INTERVAL), protocol, network_event_stream, + sync_event_stream, message_sinks: HashMap::new(), forwarding_state: ForwardingState::Idle, @@ -162,7 +171,7 @@ impl GossipEngine { /// Note: this method isn't strictly related to gossiping and should eventually be moved /// somewhere else. pub fn announce(&self, block: B::Hash, associated_data: Option>) { - self.network.announce_block(block, associated_data); + self.sync.announce_block(block, associated_data); } } @@ -175,28 +184,24 @@ impl Future for GossipEngine { 'outer: loop { match &mut this.forwarding_state { ForwardingState::Idle => { - match this.network_event_stream.poll_next_unpin(cx) { + let net_event_stream = this.network_event_stream.poll_next_unpin(cx); + let sync_event_stream = this.sync_event_stream.poll_next_unpin(cx); + + if net_event_stream.is_pending() && sync_event_stream.is_pending() { + break + } + + match net_event_stream { Poll::Ready(Some(event)) => match event { - Event::SyncConnected { remote } => { - this.network.add_set_reserved(remote, this.protocol.clone()); - }, - Event::SyncDisconnected { remote } => { - this.network.remove_peers_from_reserved_set( - this.protocol.clone(), - vec![remote], - ); - }, - Event::NotificationStreamOpened { remote, protocol, role, .. } => { - if protocol != this.protocol { - continue - } - this.state_machine.new_peer(&mut *this.network, remote, role); - }, + Event::NotificationStreamOpened { remote, protocol, role, .. } => + if protocol == this.protocol { + this.state_machine.new_peer(&mut *this.network, remote, role); + }, Event::NotificationStreamClosed { remote, protocol } => { - if protocol != this.protocol { - continue + if protocol == this.protocol { + this.state_machine + .peer_disconnected(&mut *this.network, remote); } - this.state_machine.peer_disconnected(&mut *this.network, remote); }, Event::NotificationsReceived { remote, messages } => { let messages = messages @@ -225,7 +230,25 @@ impl Future for GossipEngine { self.is_terminated = true; return Poll::Ready(()) }, - Poll::Pending => break, + Poll::Pending => {}, + } + + match sync_event_stream { + Poll::Ready(Some(event)) => match event { + SyncEvent::PeerConnected(remote) => + this.network.add_set_reserved(remote, this.protocol.clone()), + SyncEvent::PeerDisconnected(remote) => + this.network.remove_peers_from_reserved_set( + this.protocol.clone(), + vec![remote], + ), + }, + // The sync event stream closed. Do the same for [`GossipValidator`]. + Poll::Ready(None) => { + self.is_terminated = true; + return Poll::Ready(()) + }, + Poll::Pending => {}, } }, ForwardingState::Busy(to_forward) => { @@ -314,14 +337,11 @@ mod tests { future::poll_fn, }; use quickcheck::{Arbitrary, Gen, QuickCheck}; - use sc_network_common::{ - config::MultiaddrWithPeerId, - protocol::role::ObservedRole, - service::{ - NetworkBlock, NetworkEventStream, NetworkNotification, NetworkPeers, - NotificationSender, NotificationSenderError, - }, + use sc_network::{ + config::MultiaddrWithPeerId, NetworkBlock, NetworkEventStream, NetworkNotification, + NetworkPeers, NotificationSenderError, NotificationSenderT as NotificationSender, }; + use sc_network_common::{role::ObservedRole, sync::SyncEventStream}; use sp_runtime::{ testing::H256, traits::{Block as BlockT, NumberFor}, @@ -395,18 +415,6 @@ mod tests { fn remove_peers_from_reserved_set(&self, _protocol: ProtocolName, _peers: Vec) {} - fn add_to_peers_set( - &self, - _protocol: ProtocolName, - _peers: HashSet, - ) -> Result<(), String> { - unimplemented!(); - } - - fn remove_from_peers_set(&self, _protocol: ProtocolName, _peers: Vec) { - unimplemented!(); - } - fn sync_num_connected(&self) -> usize { unimplemented!(); } @@ -433,6 +441,10 @@ mod tests { ) -> Result, NotificationSenderError> { unimplemented!(); } + + fn set_notification_handshake(&self, _protocol: ProtocolName, _handshake: Vec) { + unimplemented!(); + } } impl NetworkBlock<::Hash, NumberFor> for TestNetwork { @@ -449,6 +461,42 @@ mod tests { } } + #[derive(Clone, Default)] + struct TestSync { + inner: Arc>, + } + + #[derive(Clone, Default)] + struct TestSyncInner { + event_senders: Vec>, + } + + impl SyncEventStream for TestSync { + fn event_stream( + &self, + _name: &'static str, + ) -> Pin + Send>> { + let (tx, rx) = unbounded(); + self.inner.lock().unwrap().event_senders.push(tx); + + Box::pin(rx) + } + } + + impl NetworkBlock<::Hash, NumberFor> for TestSync { + fn announce_block(&self, _hash: ::Hash, _data: Option>) { + unimplemented!(); + } + + fn new_best_block_imported( + &self, + _hash: ::Hash, + _number: NumberFor, + ) { + unimplemented!(); + } + } + struct AllowAll; impl Validator for AllowAll { fn validate( @@ -468,8 +516,10 @@ mod tests { #[test] fn returns_when_network_event_stream_closes() { let network = TestNetwork::default(); + let sync = Arc::new(TestSync::default()); let mut gossip_engine = GossipEngine::::new( network.clone(), + sync, "/my_protocol", Arc::new(AllowAll {}), None, @@ -495,9 +545,11 @@ mod tests { let protocol = ProtocolName::from("/my_protocol"); let remote_peer = PeerId::random(); let network = TestNetwork::default(); + let sync = Arc::new(TestSync::default()); let mut gossip_engine = GossipEngine::::new( network.clone(), + sync.clone(), protocol.clone(), Arc::new(AllowAll {}), None, @@ -512,6 +564,7 @@ mod tests { protocol: protocol.clone(), negotiated_fallback: None, role: ObservedRole::Authority, + received_handshake: vec![], }) .expect("Event stream is unbounded; qed."); @@ -614,6 +667,7 @@ mod tests { let protocol = ProtocolName::from("/my_protocol"); let remote_peer = PeerId::random(); let network = TestNetwork::default(); + let sync = Arc::new(TestSync::default()); let num_channels_per_topic = channels.iter().fold( HashMap::new(), @@ -640,6 +694,7 @@ mod tests { let mut gossip_engine = GossipEngine::::new( network.clone(), + sync.clone(), protocol.clone(), Arc::new(TestValidator {}), None, @@ -674,6 +729,7 @@ mod tests { protocol: protocol.clone(), negotiated_fallback: None, role: ObservedRole::Authority, + received_handshake: vec![], }) .expect("Event stream is unbounded; qed."); diff --git a/client/network-gossip/src/lib.rs b/client/network-gossip/src/lib.rs index 1c8fb8ba05ce..ef87dd599e01 100644 --- a/client/network-gossip/src/lib.rs +++ b/client/network-gossip/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -68,10 +68,10 @@ pub use self::{ }; use libp2p::{multiaddr, PeerId}; -use sc_network_common::{ - protocol::ProtocolName, - service::{NetworkBlock, NetworkEventStream, NetworkNotification, NetworkPeers}, +use sc_network::{ + types::ProtocolName, NetworkBlock, NetworkEventStream, NetworkNotification, NetworkPeers, }; +use sc_network_common::sync::SyncEventStream; use sp_runtime::traits::{Block as BlockT, NumberFor}; use std::iter; @@ -80,9 +80,7 @@ mod state_machine; mod validator; /// Abstraction over a network. -pub trait Network: - NetworkPeers + NetworkEventStream + NetworkNotification + NetworkBlock> -{ +pub trait Network: NetworkPeers + NetworkEventStream + NetworkNotification { fn add_set_reserved(&self, who: PeerId, protocol: ProtocolName) { let addr = iter::once(multiaddr::Protocol::P2p(who.into())).collect::(); @@ -93,10 +91,9 @@ pub trait Network: } } -impl Network for T where - T: NetworkPeers - + NetworkEventStream - + NetworkNotification - + NetworkBlock> -{ -} +impl Network for T where T: NetworkPeers + NetworkEventStream + NetworkNotification {} + +/// Abstraction over the syncing subsystem. +pub trait Syncing: SyncEventStream + NetworkBlock> {} + +impl Syncing for T where T: SyncEventStream + NetworkBlock> {} diff --git a/client/network-gossip/src/state_machine.rs b/client/network-gossip/src/state_machine.rs index 001f2c6136a0..f874a5c15b38 100644 --- a/client/network-gossip/src/state_machine.rs +++ b/client/network-gossip/src/state_machine.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -20,11 +20,13 @@ use crate::{MessageIntent, Network, ValidationResult, Validator, ValidatorContex use ahash::AHashSet; use libp2p::PeerId; -use lru::LruCache; +use schnellru::{ByLength, LruMap}; + use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; -use sc_network_common::protocol::{role::ObservedRole, ProtocolName}; +use sc_network::types::ProtocolName; +use sc_network_common::role::ObservedRole; use sp_runtime::traits::{Block as BlockT, Hash, HashFor}; -use std::{collections::HashMap, iter, num::NonZeroUsize, sync::Arc, time, time::Instant}; +use std::{collections::HashMap, iter, sync::Arc, time, time::Instant}; // FIXME: Add additional spam/DoS attack protection: https://github.com/paritytech/substrate/issues/1115 // NOTE: The current value is adjusted based on largest production network deployment (Kusama) and @@ -35,14 +37,14 @@ use std::{collections::HashMap, iter, num::NonZeroUsize, sync::Arc, time, time:: // // Assuming that each known message is tracked with a 32 byte hash (common for `Block::Hash`), then // this cache should take about 256 KB of memory. -const KNOWN_MESSAGES_CACHE_SIZE: usize = 8192; +const KNOWN_MESSAGES_CACHE_SIZE: u32 = 8192; const REBROADCAST_INTERVAL: time::Duration = time::Duration::from_millis(750); pub(crate) const PERIODIC_MAINTENANCE_INTERVAL: time::Duration = time::Duration::from_millis(1100); mod rep { - use sc_peerset::ReputationChange as Rep; + use sc_network::ReputationChange as Rep; /// Reputation change when a peer sends us a gossip message that we didn't know about. pub const GOSSIP_SUCCESS: Rep = Rep::new(1 << 4, "Successful gossip"); /// Reputation change when a peer sends us a gossip message that we already knew about. @@ -154,7 +156,7 @@ where pub struct ConsensusGossip { peers: HashMap>, messages: Vec>, - known_messages: LruCache, + known_messages: LruMap, protocol: ProtocolName, validator: Arc>, next_broadcast: Instant, @@ -180,11 +182,7 @@ impl ConsensusGossip { ConsensusGossip { peers: HashMap::new(), messages: Default::default(), - known_messages: { - let cap = NonZeroUsize::new(KNOWN_MESSAGES_CACHE_SIZE) - .expect("cache capacity is not zero"); - LruCache::new(cap) - }, + known_messages: { LruMap::new(ByLength::new(KNOWN_MESSAGES_CACHE_SIZE)) }, protocol, validator, next_broadcast: Instant::now() + REBROADCAST_INTERVAL, @@ -215,7 +213,7 @@ impl ConsensusGossip { message: Vec, sender: Option, ) { - if self.known_messages.put(message_hash, ()).is_none() { + if self.known_messages.insert(message_hash, ()) { self.messages.push(MessageEntry { message_hash, topic, message, sender }); if let Some(ref metrics) = self.metrics { @@ -312,7 +310,7 @@ impl ConsensusGossip { ); for (_, ref mut peer) in self.peers.iter_mut() { - peer.known_messages.retain(|h| known_messages.contains(h)); + peer.known_messages.retain(|h| known_messages.get(h).is_some()); } } @@ -347,14 +345,22 @@ impl ConsensusGossip { for message in messages { let message_hash = HashFor::::hash(&message[..]); - if self.known_messages.contains(&message_hash) { + if self.known_messages.get(&message_hash).is_some() { tracing::trace!( target: "gossip", %who, protocol = %self.protocol, "Ignored already known message", ); - network.report_peer(who, rep::DUPLICATE_GOSSIP); + + // If the peer already send us the message once, let's report them. + if self + .peers + .get_mut(&who) + .map_or(false, |p| !p.known_messages.insert(message_hash)) + { + network.report_peer(who, rep::DUPLICATE_GOSSIP); + } continue } @@ -517,15 +523,11 @@ mod tests { use super::*; use crate::multiaddr::Multiaddr; use futures::prelude::*; - use sc_network_common::{ - config::MultiaddrWithPeerId, - protocol::event::Event, - service::{ - NetworkBlock, NetworkEventStream, NetworkNotification, NetworkPeers, - NotificationSender, NotificationSenderError, - }, + use sc_network::{ + config::MultiaddrWithPeerId, event::Event, NetworkBlock, NetworkEventStream, + NetworkNotification, NetworkPeers, NotificationSenderError, + NotificationSenderT as NotificationSender, ReputationChange, }; - use sc_peerset::ReputationChange; use sp_runtime::{ testing::{Block as RawBlock, ExtrinsicWrapper, H256}, traits::NumberFor, @@ -540,7 +542,7 @@ mod tests { macro_rules! push_msg { ($consensus:expr, $topic:expr, $hash: expr, $m:expr) => { - if $consensus.known_messages.put($hash, ()).is_none() { + if $consensus.known_messages.insert($hash, ()) { $consensus.messages.push(MessageEntry { message_hash: $hash, topic: $topic, @@ -640,18 +642,6 @@ mod tests { fn remove_peers_from_reserved_set(&self, _protocol: ProtocolName, _peers: Vec) {} - fn add_to_peers_set( - &self, - _protocol: ProtocolName, - _peers: HashSet, - ) -> Result<(), String> { - unimplemented!(); - } - - fn remove_from_peers_set(&self, _protocol: ProtocolName, _peers: Vec) { - unimplemented!(); - } - fn sync_num_connected(&self) -> usize { unimplemented!(); } @@ -675,6 +665,10 @@ mod tests { ) -> Result, NotificationSenderError> { unimplemented!(); } + + fn set_notification_handshake(&self, _protocol: ProtocolName, _handshake: Vec) { + unimplemented!(); + } } impl NetworkBlock<::Hash, NumberFor> for NoOpNetwork { @@ -723,8 +717,8 @@ mod tests { push_msg!(consensus, prev_hash, m1_hash, m1); push_msg!(consensus, best_hash, m2_hash, m2); - consensus.known_messages.put(m1_hash, ()); - consensus.known_messages.put(m2_hash, ()); + consensus.known_messages.insert(m1_hash, ()); + consensus.known_messages.insert(m2_hash, ()); consensus.collect_garbage(); assert_eq!(consensus.messages.len(), 2); @@ -737,7 +731,7 @@ mod tests { assert_eq!(consensus.messages.len(), 1); // known messages are only pruned based on size. assert_eq!(consensus.known_messages.len(), 2); - assert!(consensus.known_messages.contains(&m2_hash)); + assert!(consensus.known_messages.get(&m2_hash).is_some()); } #[test] @@ -814,4 +808,30 @@ mod tests { to_forward, ); } + + // Two peers can send us the same gossip message. We should not report the second peer + // sending the gossip message as long as its the first time the peer send us this message. + #[test] + fn do_not_report_peer_for_first_time_duplicate_gossip_message() { + let mut consensus = ConsensusGossip::::new(Arc::new(AllowAll), "/foo".into(), None); + + let mut network = NoOpNetwork::default(); + + let peer_id = PeerId::random(); + consensus.new_peer(&mut network, peer_id, ObservedRole::Full); + assert!(consensus.peers.contains_key(&peer_id)); + + let peer_id2 = PeerId::random(); + consensus.new_peer(&mut network, peer_id2, ObservedRole::Full); + assert!(consensus.peers.contains_key(&peer_id2)); + + let message = vec![vec![1, 2, 3]]; + consensus.on_incoming(&mut network, peer_id, message.clone()); + consensus.on_incoming(&mut network, peer_id2, message.clone()); + + assert_eq!( + vec![(peer_id, rep::GOSSIP_SUCCESS)], + network.inner.lock().unwrap().peer_reports + ); + } } diff --git a/client/network-gossip/src/validator.rs b/client/network-gossip/src/validator.rs index 77dcc3bdc879..2272efba5065 100644 --- a/client/network-gossip/src/validator.rs +++ b/client/network-gossip/src/validator.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -17,7 +17,7 @@ // along with this program. If not, see . use libp2p::PeerId; -use sc_network_common::protocol::role::ObservedRole; +use sc_network_common::role::ObservedRole; use sp_runtime::traits::Block as BlockT; /// Validates consensus messages. diff --git a/client/network/Cargo.toml b/client/network/Cargo.toml index 44a7973b3365..786aa68d808a 100644 --- a/client/network/Cargo.toml +++ b/client/network/Cargo.toml @@ -14,51 +14,54 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -array-bytes = "4.1" +array-bytes = "6.1" +async-channel = "1.8.0" async-trait = "0.1" asynchronous-codec = "0.6" -backtrace = "0.3.67" bytes = "1" -codec = { package = "parity-scale-codec", version = "3.2.2", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } either = "1.5.3" fnv = "1.0.6" futures = "0.3.21" futures-timer = "3.0.2" ip_network = "0.4.1" -libp2p = { version = "0.50.0", features = ["dns", "identify", "kad", "macros", "mdns", "mplex", "noise", "ping", "tcp", "tokio", "yamux", "websocket"] } +libp2p = { version = "0.51.3", features = ["dns", "identify", "kad", "macros", "mdns", "noise", "ping", "tcp", "tokio", "yamux", "websocket", "request-response"] } +linked_hash_set = "0.1.3" log = "0.4.17" -lru = "0.8.1" +mockall = "0.11.3" parking_lot = "0.12.1" +partial_sort = "0.2.0" pin-project = "1.0.12" rand = "0.8.5" -serde = { version = "1.0.136", features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.85" smallvec = "1.8.0" thiserror = "1.0" unsigned-varint = { version = "0.7.1", features = ["futures", "asynchronous_codec"] } zeroize = "1.4.3" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } -sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sc-client-api = { version = "4.0.0-dev", path = "../api" } -sc-consensus = { version = "0.10.0-dev", path = "../consensus/common" } sc-network-common = { version = "0.10.0-dev", path = "./common" } -sc-peerset = { version = "4.0.0-dev", path = "../peerset" } sc-utils = { version = "4.0.0-dev", path = "../utils" } -sp-arithmetic = { version = "6.0.0", path = "../../primitives/arithmetic" } +sp-arithmetic = { version = "16.0.0", path = "../../primitives/arithmetic" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } +wasm-timer = "0.2" [dev-dependencies] assert_matches = "1.3" +mockall = "0.11.3" +multistream-select = "0.12.1" +rand = "0.8.5" tempfile = "3.1.0" tokio = { version = "1.22.0", features = ["macros"] } tokio-util = { version = "0.7.4", features = ["compat"] } +tokio-test = "0.4.2" sc-network-light = { version = "0.10.0-dev", path = "./light" } sc-network-sync = { version = "0.10.0-dev", path = "./sync" } sp-test-primitives = { version = "2.0.0", path = "../../primitives/test-primitives" } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } diff --git a/client/network/README.md b/client/network/README.md index c361bc9249f7..cad46d059092 100644 --- a/client/network/README.md +++ b/client/network/README.md @@ -66,8 +66,6 @@ negotiated and applied. The exact handshake protocol is experimental and is subj The following multiplexing protocols are supported: -- [Mplex](https://github.com/libp2p/specs/tree/master/mplex). Support for mplex will likely -be deprecated in the future. - [Yamux](https://github.com/hashicorp/yamux/blob/master/spec.md). ## Substreams @@ -190,7 +188,7 @@ The API of `sc-network` allows one to register user-defined notification protoco `sc-network` automatically tries to open a substream towards each node for which the legacy Substream substream is open. The handshake is then performed automatically. -For example, the `sc-finality-grandpa` crate registers the `/paritytech/grandpa/1` +For example, the `sc-consensus-grandpa` crate registers the `/paritytech/grandpa/1` notifications protocol. At the moment, for backwards-compatibility, notification protocols are tied to the legacy @@ -223,7 +221,7 @@ For each peer the sync maintains the number of our common best block with that p whenever peer announce new blocks or our best block advances. This allows to keep track of peers that have new block data and request new information as soon as it is announced. In keep-up mode, we also track peers that announce blocks on all branches and not just the best branch. The sync algorithm tries to be greedy and download -All data that's announced. +all data that's announced. ## Fast sync @@ -262,8 +260,8 @@ After the latest state has been imported the node is fully operational, but is s data. I.e. it is unable to serve bock bodies and headers other than the most recent one. To make sure all nodes have block history available, a background sync process is started that downloads all the missing blocks. It is run in parallel with the keep-up sync and does not interfere with downloading of the recent blocks. -During this download we also import GRANPA justifications for blocks with authority set changes, so that -The warp-synced node has all the data to serve for other nodes nodes that might want to sync from it with +During this download we also import GRANDPA justifications for blocks with authority set changes, so that +the warp-synced node has all the data to serve for other nodes nodes that might want to sync from it with any method. # Usage diff --git a/client/network/bitswap/Cargo.toml b/client/network/bitswap/Cargo.toml index 0bbb5ff09e4b..bdc9e18f7457 100644 --- a/client/network/bitswap/Cargo.toml +++ b/client/network/bitswap/Cargo.toml @@ -16,23 +16,24 @@ targets = ["x86_64-unknown-linux-gnu"] prost-build = "0.11" [dependencies] -cid = "0.8.6" +async-channel = "1.8.0" +cid = "0.9.0" futures = "0.3.21" -libp2p = "0.50.0" +libp2p-identity = { version = "0.1.2", features = ["peerid"] } log = "0.4.17" prost = "0.11" thiserror = "1.0" unsigned-varint = { version = "0.7.1", features = ["futures", "asynchronous_codec"] } sc-client-api = { version = "4.0.0-dev", path = "../../api" } -sc-network-common = { version = "0.10.0-dev", path = "../common" } +sc-network = { version = "0.10.0-dev", path = "../" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } [dev-dependencies] tokio = { version = "1.22.0", features = ["full"] } sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } substrate-test-runtime = { version = "2.0.0", path = "../../../test-utils/runtime" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/client/network/bitswap/src/lib.rs b/client/network/bitswap/src/lib.rs index 62a18b18c839..beaaa8fd0fde 100644 --- a/client/network/bitswap/src/lib.rs +++ b/client/network/bitswap/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify @@ -21,14 +21,14 @@ //! CID is expected to reference 256-bit Blake2b transaction hash. use cid::{self, Version}; -use futures::{channel::mpsc, StreamExt}; -use libp2p::core::PeerId; +use futures::StreamExt; +use libp2p_identity::PeerId; use log::{debug, error, trace}; use prost::Message; use sc_client_api::BlockBackend; -use sc_network_common::{ - protocol::ProtocolName, +use sc_network::{ request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, + types::ProtocolName, }; use schema::bitswap::{ message::{wantlist::WantType, Block as MessageBlock, BlockPresence, BlockPresenceType}, @@ -93,13 +93,13 @@ impl Prefix { /// Bitswap request handler pub struct BitswapRequestHandler { client: Arc + Send + Sync>, - request_receiver: mpsc::Receiver, + request_receiver: async_channel::Receiver, } impl BitswapRequestHandler { /// Create a new [`BitswapRequestHandler`]. pub fn new(client: Arc + Send + Sync>) -> (Self, ProtocolConfig) { - let (tx, request_receiver) = mpsc::channel(MAX_REQUEST_QUEUE); + let (tx, request_receiver) = async_channel::bounded(MAX_REQUEST_QUEUE); let config = ProtocolConfig { name: ProtocolName::from(PROTOCOL_NAME), @@ -127,8 +127,9 @@ impl BitswapRequestHandler { }; match pending_response.send(response) { - Ok(()) => - trace!(target: LOG_TARGET, "Handled bitswap request from {peer}.",), + Ok(()) => { + trace!(target: LOG_TARGET, "Handled bitswap request from {peer}.",) + }, Err(_) => debug!( target: LOG_TARGET, "Failed to handle light client request from {peer}: {}", @@ -288,7 +289,7 @@ pub enum BitswapError { #[cfg(test)] mod tests { use super::*; - use futures::{channel::oneshot, SinkExt}; + use futures::channel::oneshot; use sc_block_builder::BlockBuilderProvider; use schema::bitswap::{ message::{wantlist::Entry, Wantlist}, @@ -296,7 +297,7 @@ mod tests { }; use sp_consensus::BlockOrigin; use sp_runtime::codec::Encode; - use substrate_test_runtime::Extrinsic; + use substrate_test_runtime::ExtrinsicBuilder; use substrate_test_runtime_client::{self, prelude::*, TestClientBuilder}; #[tokio::test] @@ -469,7 +470,9 @@ mod tests { let mut client = TestClientBuilder::with_tx_storage(u32::MAX).build(); let mut block_builder = client.new_block(Default::default()).unwrap(); - let ext = Extrinsic::Store(vec![0x13, 0x37, 0x13, 0x38]); + // encoded extrinsic: [161, .. , 2, 6, 16, 19, 55, 19, 56] + let ext = ExtrinsicBuilder::new_indexed_call(vec![0x13, 0x37, 0x13, 0x38]).build(); + let pattern_index = ext.encoded_size() - 4; block_builder.push(ext.clone()).unwrap(); let block = block_builder.build().unwrap().block; @@ -493,7 +496,7 @@ mod tests { 0x70, cid::multihash::Multihash::wrap( u64::from(cid::multihash::Code::Blake2b256), - &sp_core::hashing::blake2_256(&ext.encode()[2..]), + &sp_core::hashing::blake2_256(&ext.encode()[pattern_index..]), ) .unwrap(), ) diff --git a/client/network/bitswap/src/schema.rs b/client/network/bitswap/src/schema.rs index 362e59aca68f..1d62847c21d0 100644 --- a/client/network/bitswap/src/schema.rs +++ b/client/network/bitswap/src/schema.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/network/common/Cargo.toml b/client/network/common/Cargo.toml index 2aaada021f0f..c2fee608d79b 100644 --- a/client/network/common/Cargo.toml +++ b/client/network/common/Cargo.toml @@ -18,21 +18,15 @@ prost-build = "0.11" [dependencies] async-trait = "0.1.57" bitflags = "1.3.2" -bytes = "1" -codec = { package = "parity-scale-codec", version = "3.2.2", features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", features = [ "derive", ] } futures = "0.3.21" -futures-timer = "3.0.2" -libp2p = { version = "0.50.0", features = ["request-response", "kad"] } -linked_hash_set = "0.1.3" -prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } -smallvec = "1.8.0" +libp2p-identity = { version = "0.1.2", features = ["peerid"] } sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } -sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } -serde = { version = "1.0.136", features = ["derive"] } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } -sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -thiserror = "1.0" +sp-consensus-grandpa = { version = "4.0.0-dev", path = "../../../primitives/consensus/grandpa" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } + +[dev-dependencies] +tempfile = "3.1.0" diff --git a/client/network/common/src/config.rs b/client/network/common/src/config.rs deleted file mode 100644 index c35d8a42a8c0..000000000000 --- a/client/network/common/src/config.rs +++ /dev/null @@ -1,333 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. -// 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 . - -//! Configuration of the networking layer. - -use crate::protocol; - -use codec::Encode; -use libp2p::{multiaddr, Multiaddr, PeerId}; -use std::{fmt, str, str::FromStr}; - -/// Protocol name prefix, transmitted on the wire for legacy protocol names. -/// I.e., `dot` in `/dot/sync/2`. Should be unique for each chain. Always UTF-8. -/// Deprecated in favour of genesis hash & fork ID based protocol names. -#[derive(Clone, PartialEq, Eq, Hash)] -pub struct ProtocolId(smallvec::SmallVec<[u8; 6]>); - -impl<'a> From<&'a str> for ProtocolId { - fn from(bytes: &'a str) -> ProtocolId { - Self(bytes.as_bytes().into()) - } -} - -impl AsRef for ProtocolId { - fn as_ref(&self) -> &str { - str::from_utf8(&self.0[..]) - .expect("the only way to build a ProtocolId is through a UTF-8 String; qed") - } -} - -impl fmt::Debug for ProtocolId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Debug::fmt(self.as_ref(), f) - } -} - -/// Parses a string address and splits it into Multiaddress and PeerId, if -/// valid. -/// -/// # Example -/// -/// ``` -/// # use libp2p::{Multiaddr, PeerId}; -/// # use sc_network_common::config::parse_str_addr; -/// let (peer_id, addr) = parse_str_addr( -/// "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV" -/// ).unwrap(); -/// assert_eq!(peer_id, "QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".parse::().unwrap()); -/// assert_eq!(addr, "/ip4/198.51.100.19/tcp/30333".parse::().unwrap()); -/// ``` -pub fn parse_str_addr(addr_str: &str) -> Result<(PeerId, Multiaddr), ParseErr> { - let addr: Multiaddr = addr_str.parse()?; - parse_addr(addr) -} - -/// Splits a Multiaddress into a Multiaddress and PeerId. -pub fn parse_addr(mut addr: Multiaddr) -> Result<(PeerId, Multiaddr), ParseErr> { - let who = match addr.pop() { - Some(multiaddr::Protocol::P2p(key)) => - PeerId::from_multihash(key).map_err(|_| ParseErr::InvalidPeerId)?, - _ => return Err(ParseErr::PeerIdMissing), - }; - - Ok((who, addr)) -} - -/// Address of a node, including its identity. -/// -/// This struct represents a decoded version of a multiaddress that ends with `/p2p/`. -/// -/// # Example -/// -/// ``` -/// # use libp2p::{Multiaddr, PeerId}; -/// # use sc_network_common::config::MultiaddrWithPeerId; -/// let addr: MultiaddrWithPeerId = -/// "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".parse().unwrap(); -/// assert_eq!(addr.peer_id.to_base58(), "QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"); -/// assert_eq!(addr.multiaddr.to_string(), "/ip4/198.51.100.19/tcp/30333"); -/// ``` -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] -#[serde(try_from = "String", into = "String")] -pub struct MultiaddrWithPeerId { - /// Address of the node. - pub multiaddr: Multiaddr, - /// Its identity. - pub peer_id: PeerId, -} - -impl MultiaddrWithPeerId { - /// Concatenates the multiaddress and peer ID into one multiaddress containing both. - pub fn concat(&self) -> Multiaddr { - let proto = multiaddr::Protocol::P2p(From::from(self.peer_id)); - self.multiaddr.clone().with(proto) - } -} - -impl fmt::Display for MultiaddrWithPeerId { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.concat(), f) - } -} - -impl FromStr for MultiaddrWithPeerId { - type Err = ParseErr; - - fn from_str(s: &str) -> Result { - let (peer_id, multiaddr) = parse_str_addr(s)?; - Ok(Self { peer_id, multiaddr }) - } -} - -impl From for String { - fn from(ma: MultiaddrWithPeerId) -> String { - format!("{}", ma) - } -} - -impl TryFrom for MultiaddrWithPeerId { - type Error = ParseErr; - fn try_from(string: String) -> Result { - string.parse() - } -} - -/// Error that can be generated by `parse_str_addr`. -#[derive(Debug)] -pub enum ParseErr { - /// Error while parsing the multiaddress. - MultiaddrParse(multiaddr::Error), - /// Multihash of the peer ID is invalid. - InvalidPeerId, - /// The peer ID is missing from the address. - PeerIdMissing, -} - -impl fmt::Display for ParseErr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::MultiaddrParse(err) => write!(f, "{}", err), - Self::InvalidPeerId => write!(f, "Peer id at the end of the address is invalid"), - Self::PeerIdMissing => write!(f, "Peer id is missing from the address"), - } - } -} - -impl std::error::Error for ParseErr { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::MultiaddrParse(err) => Some(err), - Self::InvalidPeerId => None, - Self::PeerIdMissing => None, - } - } -} - -impl From for ParseErr { - fn from(err: multiaddr::Error) -> ParseErr { - Self::MultiaddrParse(err) - } -} - -/// Configuration for a set of nodes. -#[derive(Clone, Debug)] -pub struct SetConfig { - /// Maximum allowed number of incoming substreams related to this set. - pub in_peers: u32, - /// Number of outgoing substreams related to this set that we're trying to maintain. - pub out_peers: u32, - /// List of reserved node addresses. - pub reserved_nodes: Vec, - /// Whether nodes that aren't in [`SetConfig::reserved_nodes`] are accepted or automatically - /// refused. - pub non_reserved_mode: NonReservedPeerMode, -} - -impl Default for SetConfig { - fn default() -> Self { - Self { - in_peers: 25, - out_peers: 75, - reserved_nodes: Vec::new(), - non_reserved_mode: NonReservedPeerMode::Accept, - } - } -} - -/// Custom handshake for the notification protocol -#[derive(Debug, Clone)] -pub struct NotificationHandshake(Vec); - -impl NotificationHandshake { - /// Create new `NotificationHandshake` from an object that implements `Encode` - pub fn new(handshake: H) -> Self { - Self(handshake.encode()) - } - - /// Create new `NotificationHandshake` from raw bytes - pub fn from_bytes(bytes: Vec) -> Self { - Self(bytes) - } -} - -impl std::ops::Deref for NotificationHandshake { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -/// Extension to [`SetConfig`] for sets that aren't the default set. -/// -/// > **Note**: As new fields might be added in the future, please consider using the `new` method -/// > and modifiers instead of creating this struct manually. -#[derive(Clone, Debug)] -pub struct NonDefaultSetConfig { - /// Name of the notifications protocols of this set. A substream on this set will be - /// considered established once this protocol is open. - /// - /// > **Note**: This field isn't present for the default set, as this is handled internally - /// > by the networking code. - pub notifications_protocol: protocol::ProtocolName, - /// If the remote reports that it doesn't support the protocol indicated in the - /// `notifications_protocol` field, then each of these fallback names will be tried one by - /// one. - /// - /// If a fallback is used, it will be reported in - /// `sc_network::protocol::event::Event::NotificationStreamOpened::negotiated_fallback` - pub fallback_names: Vec, - /// Handshake of the protocol - /// - /// NOTE: Currently custom handshakes are not fully supported. See issue #5685 for more - /// details. This field is temporarily used to allow moving the hardcoded block announcement - /// protocol out of `protocol.rs`. - pub handshake: Option, - /// Maximum allowed size of single notifications. - pub max_notification_size: u64, - /// Base configuration. - pub set_config: SetConfig, -} - -impl NonDefaultSetConfig { - /// Creates a new [`NonDefaultSetConfig`]. Zero slots and accepts only reserved nodes. - pub fn new(notifications_protocol: protocol::ProtocolName, max_notification_size: u64) -> Self { - Self { - notifications_protocol, - max_notification_size, - fallback_names: Vec::new(), - handshake: None, - set_config: SetConfig { - in_peers: 0, - out_peers: 0, - reserved_nodes: Vec::new(), - non_reserved_mode: NonReservedPeerMode::Deny, - }, - } - } - - /// Modifies the configuration to allow non-reserved nodes. - pub fn allow_non_reserved(&mut self, in_peers: u32, out_peers: u32) { - self.set_config.in_peers = in_peers; - self.set_config.out_peers = out_peers; - self.set_config.non_reserved_mode = NonReservedPeerMode::Accept; - } - - /// Add a node to the list of reserved nodes. - pub fn add_reserved(&mut self, peer: MultiaddrWithPeerId) { - self.set_config.reserved_nodes.push(peer); - } - - /// Add a list of protocol names used for backward compatibility. - /// - /// See the explanations in [`NonDefaultSetConfig::fallback_names`]. - pub fn add_fallback_names(&mut self, fallback_names: Vec) { - self.fallback_names.extend(fallback_names); - } -} - -/// Configuration for the transport layer. -#[derive(Clone, Debug)] -pub enum TransportConfig { - /// Normal transport mode. - Normal { - /// If true, the network will use mDNS to discover other libp2p nodes on the local network - /// and connect to them if they support the same chain. - enable_mdns: bool, - - /// If true, allow connecting to private IPv4/IPv6 addresses (as defined in - /// [RFC1918](https://tools.ietf.org/html/rfc1918)). Irrelevant for addresses that have - /// been passed in `::sc_network::config::NetworkConfiguration::boot_nodes`. - allow_private_ip: bool, - }, - - /// Only allow connections within the same process. - /// Only addresses of the form `/memory/...` will be supported. - MemoryOnly, -} - -/// The policy for connections to non-reserved peers. -#[derive(Clone, Debug, PartialEq, Eq)] -pub enum NonReservedPeerMode { - /// Accept them. This is the default. - Accept, - /// Deny them. - Deny, -} - -impl NonReservedPeerMode { - /// Attempt to parse the peer mode from a string. - pub fn parse(s: &str) -> Option { - match s { - "accept" => Some(Self::Accept), - "deny" => Some(Self::Deny), - _ => None, - } - } -} diff --git a/client/network/common/src/lib.rs b/client/network/common/src/lib.rs index 36e67f11e5cf..8149f5ea708c 100644 --- a/client/network/common/src/lib.rs +++ b/client/network/common/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,14 +18,10 @@ //! Common data structures of the networking layer. -pub mod config; -pub mod error; pub mod message; -pub mod protocol; -pub mod request_responses; -pub mod service; +pub mod role; pub mod sync; -pub mod utils; +pub mod types; /// Minimum Requirements for a Hash within Networking pub trait ExHashT: std::hash::Hash + Eq + std::fmt::Debug + Clone + Send + Sync + 'static {} diff --git a/client/network/common/src/message.rs b/client/network/common/src/message.rs index 930fe5ca5284..12d2a6800490 100644 --- a/client/network/common/src/message.rs +++ b/client/network/common/src/message.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/network/common/src/request_responses.rs b/client/network/common/src/request_responses.rs deleted file mode 100644 index 1a8d48e11be5..000000000000 --- a/client/network/common/src/request_responses.rs +++ /dev/null @@ -1,155 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. -// 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 . - -//! Collection of generic data structures for request-response protocols. - -use crate::protocol::ProtocolName; -use futures::channel::{mpsc, oneshot}; -use libp2p::{request_response::OutboundFailure, PeerId}; -use sc_peerset::ReputationChange; -use std::time::Duration; - -/// Configuration for a single request-response protocol. -#[derive(Debug, Clone)] -pub struct ProtocolConfig { - /// Name of the protocol on the wire. Should be something like `/foo/bar`. - pub name: ProtocolName, - - /// Fallback on the wire protocol names to support. - pub fallback_names: Vec, - - /// Maximum allowed size, in bytes, of a request. - /// - /// Any request larger than this value will be declined as a way to avoid allocating too - /// much memory for it. - pub max_request_size: u64, - - /// Maximum allowed size, in bytes, of a response. - /// - /// Any response larger than this value will be declined as a way to avoid allocating too - /// much memory for it. - pub max_response_size: u64, - - /// Duration after which emitted requests are considered timed out. - /// - /// If you expect the response to come back quickly, you should set this to a smaller duration. - pub request_timeout: Duration, - - /// Channel on which the networking service will send incoming requests. - /// - /// Every time a peer sends a request to the local node using this protocol, the networking - /// service will push an element on this channel. The receiving side of this channel then has - /// to pull this element, process the request, and send back the response to send back to the - /// peer. - /// - /// The size of the channel has to be carefully chosen. If the channel is full, the networking - /// service will discard the incoming request send back an error to the peer. Consequently, - /// the channel being full is an indicator that the node is overloaded. - /// - /// You can typically set the size of the channel to `T / d`, where `T` is the - /// `request_timeout` and `d` is the expected average duration of CPU and I/O it takes to - /// build a response. - /// - /// Can be `None` if the local node does not support answering incoming requests. - /// If this is `None`, then the local node will not advertise support for this protocol towards - /// other peers. If this is `Some` but the channel is closed, then the local node will - /// advertise support for this protocol, but any incoming request will lead to an error being - /// sent back. - pub inbound_queue: Option>, -} - -/// A single request received by a peer on a request-response protocol. -#[derive(Debug)] -pub struct IncomingRequest { - /// Who sent the request. - pub peer: PeerId, - - /// Request sent by the remote. Will always be smaller than - /// [`ProtocolConfig::max_request_size`]. - pub payload: Vec, - - /// Channel to send back the response. - /// - /// There are two ways to indicate that handling the request failed: - /// - /// 1. Drop `pending_response` and thus not changing the reputation of the peer. - /// - /// 2. Sending an `Err(())` via `pending_response`, optionally including reputation changes for - /// the given peer. - pub pending_response: oneshot::Sender, -} - -/// Response for an incoming request to be send by a request protocol handler. -#[derive(Debug)] -pub struct OutgoingResponse { - /// The payload of the response. - /// - /// `Err(())` if none is available e.g. due an error while handling the request. - pub result: Result, ()>, - - /// Reputation changes accrued while handling the request. To be applied to the reputation of - /// the peer sending the request. - pub reputation_changes: Vec, - - /// If provided, the `oneshot::Sender` will be notified when the request has been sent to the - /// peer. - /// - /// > **Note**: Operating systems typically maintain a buffer of a few dozen kilobytes of - /// > outgoing data for each TCP socket, and it is not possible for a user - /// > application to inspect this buffer. This channel here is not actually notified - /// > when the response has been fully sent out, but rather when it has fully been - /// > written to the buffer managed by the operating system. - pub sent_feedback: Option>, -} - -/// When sending a request, what to do on a disconnected recipient. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum IfDisconnected { - /// Try to connect to the peer. - TryConnect, - /// Just fail if the destination is not yet connected. - ImmediateError, -} - -/// Convenience functions for `IfDisconnected`. -impl IfDisconnected { - /// Shall we connect to a disconnected peer? - pub fn should_connect(self) -> bool { - match self { - Self::TryConnect => true, - Self::ImmediateError => false, - } - } -} - -/// Error in a request. -#[derive(Debug, thiserror::Error)] -#[allow(missing_docs)] -pub enum RequestFailure { - #[error("We are not currently connected to the requested peer.")] - NotConnected, - #[error("Given protocol hasn't been registered.")] - UnknownProtocol, - #[error("Remote has closed the substream before answering, thereby signaling that it considers the request as valid, but refused to answer it.")] - Refused, - #[error("The remote replied, but the local node is no longer interested in the response.")] - Obsolete, - /// Problem on the network. - #[error("Problem on the network: {0}")] - Network(OutboundFailure), -} diff --git a/client/network/common/src/protocol/role.rs b/client/network/common/src/role.rs similarity index 98% rename from client/network/common/src/protocol/role.rs rename to client/network/common/src/role.rs index ed22830fd717..cd43f6655b72 100644 --- a/client/network/common/src/protocol/role.rs +++ b/client/network/common/src/role.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/network/common/src/sync.rs b/client/network/common/src/sync.rs index 802cf75fc470..b142925aeb10 100644 --- a/client/network/common/src/sync.rs +++ b/client/network/common/src/sync.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -22,7 +22,11 @@ pub mod message; pub mod metrics; pub mod warp; -use libp2p::PeerId; +use crate::{role::Roles, types::ReputationChange}; +use futures::Stream; + +use libp2p_identity::PeerId; + use message::{BlockAnnounce, BlockData, BlockRequest, BlockResponse}; use sc_consensus::{import_queue::RuntimeOrigin, IncomingBlock}; use sp_consensus::BlockOrigin; @@ -30,9 +34,10 @@ use sp_runtime::{ traits::{Block as BlockT, NumberFor}, Justifications, }; -use std::{any::Any, fmt, fmt::Formatter, task::Poll}; use warp::WarpSyncProgress; +use std::{any::Any, fmt, fmt::Formatter, pin::Pin, sync::Arc, task::Poll}; + /// The sync status of a peer we are trying to sync with #[derive(Debug)] pub struct PeerInfo { @@ -42,6 +47,17 @@ pub struct PeerInfo { pub best_number: NumberFor, } +/// Info about a peer's known state (both full and light). +#[derive(Clone, Debug)] +pub struct ExtendedPeerInfo { + /// Roles + pub roles: Roles, + /// Peer best block hash + pub best_hash: B::Hash, + /// Peer best block number + pub best_number: NumberFor, +} + /// Reported sync state. #[derive(Clone, Eq, PartialEq, Debug)] pub enum SyncState { @@ -78,6 +94,8 @@ pub struct SyncStatus { pub best_seen_block: Option>, /// Number of peers participating in syncing. pub num_peers: u32, + /// Number of peers known to `SyncingEngine` (both full and light). + pub num_connected_peers: u32, /// Number of blocks queued for import pub queued_blocks: u32, /// State sync status in progress, if any. @@ -88,7 +106,7 @@ pub struct SyncStatus { /// A peer did not behave as expected and should be reported. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct BadPeer(pub PeerId, pub sc_peerset::ReputationChange); +pub struct BadPeer(pub PeerId, pub ReputationChange); impl fmt::Display for BadPeer { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { @@ -167,32 +185,43 @@ pub enum PollBlockAnnounceValidation { /// The announcement. announce: BlockAnnounce, }, - /// The announcement header should be imported. - ImportHeader { - /// Who sent the processed block announcement? - who: PeerId, - /// Was this their new best block? - is_best: bool, - /// The announcement. - announce: BlockAnnounce, - }, /// The block announcement should be skipped. Skip, } -/// Operation mode. -#[derive(Debug, PartialEq, Eq)] +/// Sync operation mode. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum SyncMode { - // Sync headers only - Light, - // Sync headers and block bodies + /// Full block download and verification. Full, - // Sync headers and the last finalied state - LightState { storage_chain_mode: bool, skip_proofs: bool }, - // Warp sync mode. + /// Download blocks and the latest state. + LightState { + /// Skip state proof download and verification. + skip_proofs: bool, + /// Download indexed transactions for recent blocks. + storage_chain_mode: bool, + }, + /// Warp sync - verify authority set transitions and the latest state. Warp, } +impl SyncMode { + /// Returns `true` if `self` is [`Self::Warp`]. + pub fn is_warp(&self) -> bool { + matches!(self, Self::Warp) + } + + /// Returns `true` if `self` is [`Self::LightState`]. + pub fn light_state(&self) -> bool { + matches!(self, Self::LightState { .. }) + } +} + +impl Default for SyncMode { + fn default() -> Self { + Self::Full + } +} #[derive(Debug)] pub struct Metrics { pub queued_blocks: u32, @@ -251,6 +280,49 @@ impl fmt::Debug for OpaqueBlockResponse { } } +/// Provides high-level status of syncing. +#[async_trait::async_trait] +pub trait SyncStatusProvider: Send + Sync { + /// Get high-level view of the syncing status. + async fn status(&self) -> Result, ()>; +} + +#[async_trait::async_trait] +impl SyncStatusProvider for Arc +where + T: ?Sized, + T: SyncStatusProvider, + Block: BlockT, +{ + async fn status(&self) -> Result, ()> { + T::status(self).await + } +} + +/// Syncing-related events that other protocols can subscribe to. +pub enum SyncEvent { + /// Peer that the syncing implementation is tracking connected. + PeerConnected(PeerId), + + /// Peer that the syncing implementation was tracking disconnected. + PeerDisconnected(PeerId), +} + +pub trait SyncEventStream: Send + Sync { + /// Subscribe to syncing-related events. + fn event_stream(&self, name: &'static str) -> Pin + Send>>; +} + +impl SyncEventStream for Arc +where + T: ?Sized, + T: SyncEventStream, +{ + fn event_stream(&self, name: &'static str) -> Pin + Send>> { + T::event_stream(self, name) + } +} + /// Something that represents the syncing strategy to download past and future blocks of the chain. pub trait ChainSync: Send { /// Returns the state of the sync of the given peer. @@ -315,12 +387,6 @@ pub trait ChainSync: Send { response: BlockResponse, ) -> Result, BadPeer>; - /// Procss received block data. - fn process_block_response_data( - &mut self, - blocks_to_import: Result, BadPeer>, - ); - /// Handle a response from the remote to a justification request that we made. /// /// `request` must be the original request that triggered `response`. @@ -360,12 +426,9 @@ pub trait ChainSync: Send { /// [`ChainSync::push_block_announce_validation`]. /// /// This should be polled until it returns [`Poll::Pending`]. - /// - /// If [`PollBlockAnnounceValidation::ImportHeader`] is returned, then the caller MUST try to - /// import passed header (call `on_block_data`). The network request isn't sent in this case. - fn poll_block_announce_validation<'a>( + fn poll_block_announce_validation( &mut self, - cx: &mut std::task::Context<'a>, + cx: &mut std::task::Context<'_>, ) -> Poll>; /// Call when a peer has disconnected. diff --git a/client/network/common/src/sync/message.rs b/client/network/common/src/sync/message.rs index 346f1dbce9bc..7cdb14172885 100644 --- a/client/network/common/src/sync/message.rs +++ b/client/network/common/src/sync/message.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -19,7 +19,7 @@ //! Network packet message types. These get serialized and put into the lower level protocol //! payload. -use crate::protocol::role::Roles; +use crate::role::Roles; use bitflags::bitflags; use codec::{Decode, Encode, Error, Input, Output}; @@ -185,7 +185,7 @@ pub mod generic { pub blocks: Vec>, } - /// Announce a new complete relay chain block on the network. + /// Announce a new complete block on the network. #[derive(Debug, PartialEq, Eq, Clone)] pub struct BlockAnnounce { /// New block header. diff --git a/client/network/common/src/sync/metrics.rs b/client/network/common/src/sync/metrics.rs index 15ff090a8cca..455f57ec3933 100644 --- a/client/network/common/src/sync/metrics.rs +++ b/client/network/common/src/sync/metrics.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/network/common/src/sync/warp.rs b/client/network/common/src/sync/warp.rs index c9b903754238..37a6e62c53b4 100644 --- a/client/network/common/src/sync/warp.rs +++ b/client/network/common/src/sync/warp.rs @@ -1,4 +1,4 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify @@ -15,9 +15,10 @@ // along with Substrate. If not, see . use codec::{Decode, Encode}; -pub use sp_finality_grandpa::{AuthorityList, SetId}; +use futures::channel::oneshot; +pub use sp_consensus_grandpa::{AuthorityList, SetId}; use sp_runtime::traits::{Block as BlockT, NumberFor}; -use std::fmt; +use std::{fmt, sync::Arc}; /// Scale-encoded warp sync proof response. pub struct EncodedProof(pub Vec); @@ -29,6 +30,16 @@ pub struct WarpProofRequest { pub begin: B::Hash, } +/// The different types of warp syncing. +pub enum WarpSyncParams { + /// Standard warp sync for the chain. + WithProvider(Arc>), + /// Skip downloading proofs and wait for a header of the state that should be downloaded. + /// + /// It is expected that the header provider ensures that the header is trusted. + WaitForTarget(oneshot::Receiver<::Header>), +} + /// Proof verification result. pub enum VerificationResult { /// Proof is valid, but the target was not reached. @@ -37,7 +48,7 @@ pub enum VerificationResult { Complete(SetId, AuthorityList, Block::Header), } -/// Warp sync backend. Handles retrieveing and verifying warp sync proofs. +/// Warp sync backend. Handles retrieving and verifying warp sync proofs. pub trait WarpSyncProvider: Send + Sync { /// Generate proof starting at given block hash. The proof is accumulated until maximum proof /// size is reached. @@ -61,7 +72,9 @@ pub trait WarpSyncProvider: Send + Sync { #[derive(Clone, Eq, PartialEq, Debug)] pub enum WarpSyncPhase { /// Waiting for peers to connect. - AwaitingPeers, + AwaitingPeers { required_peers: usize }, + /// Waiting for target block to be received. + AwaitingTargetBlock, /// Downloading and verifying grandpa warp proofs. DownloadingWarpProofs, /// Downloading target block. @@ -77,7 +90,9 @@ pub enum WarpSyncPhase { impl fmt::Display for WarpSyncPhase { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Self::AwaitingPeers => write!(f, "Waiting for peers"), + Self::AwaitingPeers { required_peers } => + write!(f, "Waiting for {required_peers} peers to be connected"), + Self::AwaitingTargetBlock => write!(f, "Waiting for target block to be received"), Self::DownloadingWarpProofs => write!(f, "Downloading finality proofs"), Self::DownloadingTargetBlock => write!(f, "Downloading target block"), Self::DownloadingState => write!(f, "Downloading state"), diff --git a/client/network/common/src/types.rs b/client/network/common/src/types.rs new file mode 100644 index 000000000000..d23a2236d556 --- /dev/null +++ b/client/network/common/src/types.rs @@ -0,0 +1,38 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +/// Description of a reputation adjustment for a node. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct ReputationChange { + /// Reputation delta. + pub value: i32, + /// Reason for reputation change. + pub reason: &'static str, +} + +impl ReputationChange { + /// New reputation change with given delta and reason. + pub const fn new(value: i32, reason: &'static str) -> ReputationChange { + Self { value, reason } + } + + /// New reputation change that forces minimum possible reputation. + pub const fn new_fatal(reason: &'static str) -> ReputationChange { + Self { value: i32::MIN, reason } + } +} diff --git a/client/network/light/Cargo.toml b/client/network/light/Cargo.toml index f3e94790df61..8bdb640d5558 100644 --- a/client/network/light/Cargo.toml +++ b/client/network/light/Cargo.toml @@ -16,18 +16,18 @@ targets = ["x86_64-unknown-linux-gnu"] prost-build = "0.11" [dependencies] -array-bytes = "4.1" -codec = { package = "parity-scale-codec", version = "3.2.2", features = [ +async-channel = "1.8.0" +array-bytes = "6.1" +codec = { package = "parity-scale-codec", version = "3.6.1", features = [ "derive", ] } futures = "0.3.21" -libp2p = "0.50.0" +libp2p-identity = { version = "0.1.2", features = ["peerid"] } log = "0.4.16" prost = "0.11" sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } -sc-network-common = { version = "0.10.0-dev", path = "../common" } -sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sc-network = { version = "0.10.0-dev", path = "../" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } thiserror = "1.0" diff --git a/client/network/light/src/lib.rs b/client/network/light/src/lib.rs index 2b7cf226f90d..656f658d4c92 100644 --- a/client/network/light/src/lib.rs +++ b/client/network/light/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/network/light/src/light_client_requests.rs b/client/network/light/src/light_client_requests.rs index 61b549d0f098..4d2a301c00e6 100644 --- a/client/network/light/src/light_client_requests.rs +++ b/client/network/light/src/light_client_requests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,13 +18,13 @@ //! Helpers for outgoing and incoming light client requests. -/// For incoming light client requests. -pub mod handler; - -use sc_network_common::{config::ProtocolId, request_responses::ProtocolConfig}; +use sc_network::{config::ProtocolId, request_responses::ProtocolConfig}; use std::time::Duration; +/// For incoming light client requests. +pub mod handler; + /// Generate the light client protocol name from the genesis hash and fork id. fn generate_protocol_name>(genesis_hash: Hash, fork_id: Option<&str>) -> String { let genesis_hash = genesis_hash.as_ref(); diff --git a/client/network/light/src/light_client_requests/handler.rs b/client/network/light/src/light_client_requests/handler.rs index abf012b82f9d..8f2bcc7384b3 100644 --- a/client/network/light/src/light_client_requests/handler.rs +++ b/client/network/light/src/light_client_requests/handler.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -24,16 +24,16 @@ use crate::schema; use codec::{self, Decode, Encode}; -use futures::{channel::mpsc, prelude::*}; -use libp2p::PeerId; +use futures::prelude::*; +use libp2p_identity::PeerId; use log::{debug, trace}; use prost::Message; use sc_client_api::{BlockBackend, ProofProvider}; -use sc_network_common::{ +use sc_network::{ config::ProtocolId, request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, + ReputationChange, }; -use sc_peerset::ReputationChange; use sp_core::{ hexdisplay::HexDisplay, storage::{ChildInfo, ChildType, PrefixedStorageKey}, @@ -43,9 +43,13 @@ use std::{marker::PhantomData, sync::Arc}; const LOG_TARGET: &str = "light-client-request-handler"; +/// Incoming requests bounded queue size. For now due to lack of data on light client request +/// handling in production systems, this value is chosen to match the block request limit. +const MAX_LIGHT_REQUEST_QUEUE: usize = 20; + /// Handler for incoming light client requests from a remote peer. pub struct LightClientRequestHandler { - request_receiver: mpsc::Receiver, + request_receiver: async_channel::Receiver, /// Blockchain client. client: Arc, _block: PhantomData, @@ -62,9 +66,7 @@ where fork_id: Option<&str>, client: Arc, ) -> (Self, ProtocolConfig) { - // For now due to lack of data on light client request handling in production systems, this - // value is chosen to match the block request limit. - let (tx, request_receiver) = mpsc::channel(20); + let (tx, request_receiver) = async_channel::bounded(MAX_LIGHT_REQUEST_QUEUE); let mut protocol_config = super::generate_protocol_config( protocol_id, diff --git a/client/network/light/src/schema.rs b/client/network/light/src/schema.rs index 1fc91659a5bb..0ef9bac4df97 100644 --- a/client/network/light/src/schema.rs +++ b/client/network/light/src/schema.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/network/src/behaviour.rs b/client/network/src/behaviour.rs index eac74f7e6e37..a4c19c47c3a8 100644 --- a/client/network/src/behaviour.rs +++ b/client/network/src/behaviour.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,30 +18,23 @@ use crate::{ discovery::{DiscoveryBehaviour, DiscoveryConfig, DiscoveryOut}, + event::DhtEvent, peer_info, + peerset::PeersetHandle, protocol::{CustomMessageOutcome, NotificationsSink, Protocol}, - request_responses, + request_responses::{self, IfDisconnected, ProtocolConfig, RequestFailure}, + types::ProtocolName, + ReputationChange, }; use bytes::Bytes; use futures::channel::oneshot; use libp2p::{ - core::{Multiaddr, PeerId, PublicKey}, - identify::Info as IdentifyInfo, - kad::record, - swarm::NetworkBehaviour, + core::Multiaddr, identify::Info as IdentifyInfo, identity::PublicKey, kad::RecordKey, + swarm::NetworkBehaviour, PeerId, }; -use sc_network_common::{ - protocol::{ - event::DhtEvent, - role::{ObservedRole, Roles}, - ProtocolName, - }, - request_responses::{IfDisconnected, ProtocolConfig, RequestFailure}, -}; -use sc_peerset::{PeersetHandle, ReputationChange}; -use sp_blockchain::HeaderBackend; +use sc_network_common::role::{ObservedRole, Roles}; use sp_runtime::traits::Block as BlockT; use std::{collections::HashSet, time::Duration}; @@ -50,13 +43,9 @@ pub use crate::request_responses::{InboundFailure, OutboundFailure, RequestId, R /// General behaviour of the network. Combines all protocols together. #[derive(NetworkBehaviour)] #[behaviour(out_event = "BehaviourOut")] -pub struct Behaviour -where - B: BlockT, - Client: HeaderBackend + 'static, -{ +pub struct Behaviour { /// All the substrate-specific protocols. - substrate: Protocol, + substrate: Protocol, /// Periodically pings and identifies the nodes we are connected to, and store information in a /// cache. peer_info: peer_info::PeerInfoBehaviour, @@ -118,6 +107,8 @@ pub enum BehaviourOut { notifications_sink: NotificationsSink, /// Role of the remote. role: ObservedRole, + /// Received handshake. + received_handshake: Vec, }, /// The [`NotificationsSink`] object used to send notifications with the given peer must be @@ -151,12 +142,6 @@ pub enum BehaviourOut { messages: Vec<(ProtocolName, Bytes)>, }, - /// Now connected to a new peer for syncing purposes. - SyncConnected(PeerId), - - /// No longer connected to a peer for syncing purposes. - SyncDisconnected(PeerId), - /// We have obtained identity information from a peer, including the addresses it is listening /// on. PeerIdentify { @@ -177,14 +162,10 @@ pub enum BehaviourOut { None, } -impl Behaviour -where - B: BlockT, - Client: HeaderBackend + 'static, -{ +impl Behaviour { /// Builds a new `Behaviour`. pub fn new( - substrate: Protocol, + substrate: Protocol, user_agent: String, local_public_key: PublicKey, disco_config: DiscoveryConfig, @@ -252,12 +233,12 @@ where } /// Returns a shared reference to the user protocol. - pub fn user_protocol(&self) -> &Protocol { + pub fn user_protocol(&self) -> &Protocol { &self.substrate } /// Returns a mutable reference to the user protocol. - pub fn user_protocol_mut(&mut self) -> &mut Protocol { + pub fn user_protocol_mut(&mut self) -> &mut Protocol { &mut self.substrate } @@ -274,13 +255,13 @@ where /// Start querying a record from the DHT. Will later produce either a `ValueFound` or a /// `ValueNotFound` event. - pub fn get_value(&mut self, key: record::Key) { + pub fn get_value(&mut self, key: RecordKey) { self.discovery.get_value(key); } /// Starts putting a record into DHT. Will later produce either a `ValuePut` or a /// `ValuePutFailed` event. - pub fn put_value(&mut self, key: record::Key, value: Vec) { + pub fn put_value(&mut self, key: RecordKey, value: Vec) { self.discovery.put_value(key, value); } } @@ -295,20 +276,22 @@ fn reported_roles_to_observed_role(roles: Roles) -> ObservedRole { } } -impl From> for BehaviourOut { - fn from(event: CustomMessageOutcome) -> Self { +impl From for BehaviourOut { + fn from(event: CustomMessageOutcome) -> Self { match event { CustomMessageOutcome::NotificationStreamOpened { remote, protocol, negotiated_fallback, roles, + received_handshake, notifications_sink, } => BehaviourOut::NotificationStreamOpened { remote, protocol, negotiated_fallback, role: reported_roles_to_observed_role(roles), + received_handshake, notifications_sink, }, CustomMessageOutcome::NotificationStreamReplaced { @@ -320,10 +303,6 @@ impl From> for BehaviourOut { BehaviourOut::NotificationStreamClosed { remote, protocol }, CustomMessageOutcome::NotificationsReceived { remote, messages } => BehaviourOut::NotificationsReceived { remote, messages }, - CustomMessageOutcome::PeerNewBest(_peer_id, _number) => BehaviourOut::None, - CustomMessageOutcome::SyncConnected(peer_id) => BehaviourOut::SyncConnected(peer_id), - CustomMessageOutcome::SyncDisconnected(peer_id) => - BehaviourOut::SyncDisconnected(peer_id), CustomMessageOutcome::None => BehaviourOut::None, } } diff --git a/client/network/src/config.rs b/client/network/src/config.rs index 8ca8d75f1681..4c1247f786fe 100644 --- a/client/network/src/config.rs +++ b/client/network/src/config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -21,247 +21,259 @@ //! The [`Params`] struct is the struct that must be passed in order to initialize the networking. //! See the documentation of [`Params`]. -pub use sc_network_common::{ - config::ProtocolId, - protocol::role::Role, +pub use crate::{ + discovery::DEFAULT_KADEMLIA_REPLICATION_FACTOR, + protocol::NotificationsSink, request_responses::{ IncomingRequest, OutgoingResponse, ProtocolConfig as RequestResponseConfig, }, - sync::warp::WarpSyncProvider, - ExHashT, + types::ProtocolName, }; -pub use libp2p::{build_multiaddr, core::PublicKey, identity}; +pub use libp2p::{identity::Keypair, multiaddr, Multiaddr, PeerId}; -use crate::ChainSyncInterface; -use core::{fmt, iter}; -use libp2p::{ - identity::{ed25519, Keypair}, - multiaddr, Multiaddr, -}; +use codec::Encode; use prometheus_endpoint::Registry; -use sc_network_common::{ - config::{MultiaddrWithPeerId, NonDefaultSetConfig, SetConfig, TransportConfig}, - sync::ChainSync, +use zeroize::Zeroize; + +pub use sc_network_common::{ + role::{Role, Roles}, + sync::{warp::WarpSyncProvider, SyncMode}, + ExHashT, }; +use sc_utils::mpsc::TracingUnboundedSender; use sp_runtime::traits::Block as BlockT; + use std::{ error::Error, - fs, + fmt, fs, future::Future, io::{self, Write}, + iter, net::Ipv4Addr, + num::NonZeroUsize, path::{Path, PathBuf}, pin::Pin, - sync::Arc, + str::{self, FromStr}, }; -use zeroize::Zeroize; -/// Network initialization parameters. -pub struct Params -where - B: BlockT + 'static, -{ - /// Assigned role for our node (full, light, ...). - pub role: Role, - - /// How to spawn background tasks. - pub executor: Box + Send>>) + Send>, +pub use libp2p::{ + build_multiaddr, + identity::{self, ed25519}, +}; - /// Network layer configuration. - pub network_config: NetworkConfiguration, +/// Protocol name prefix, transmitted on the wire for legacy protocol names. +/// I.e., `dot` in `/dot/sync/2`. Should be unique for each chain. Always UTF-8. +/// Deprecated in favour of genesis hash & fork ID based protocol names. +#[derive(Clone, PartialEq, Eq, Hash)] +pub struct ProtocolId(smallvec::SmallVec<[u8; 6]>); - /// Client that contains the blockchain. - pub chain: Arc, +impl<'a> From<&'a str> for ProtocolId { + fn from(bytes: &'a str) -> ProtocolId { + Self(bytes.as_bytes().into()) + } +} - /// Legacy name of the protocol to use on the wire. Should be different for each chain. - pub protocol_id: ProtocolId, +impl AsRef for ProtocolId { + fn as_ref(&self) -> &str { + str::from_utf8(&self.0[..]) + .expect("the only way to build a ProtocolId is through a UTF-8 String; qed") + } +} - /// Fork ID to distinguish protocols of different hard forks. Part of the standard protocol - /// name on the wire. - pub fork_id: Option, +impl fmt::Debug for ProtocolId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Debug::fmt(self.as_ref(), f) + } +} - /// Instance of chain sync implementation. - pub chain_sync: Box>, +/// Parses a string address and splits it into Multiaddress and PeerId, if +/// valid. +/// +/// # Example +/// +/// ``` +/// # use libp2p::{Multiaddr, PeerId}; +/// use sc_network::config::parse_str_addr; +/// let (peer_id, addr) = parse_str_addr( +/// "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV" +/// ).unwrap(); +/// assert_eq!(peer_id, "QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".parse::().unwrap()); +/// assert_eq!(addr, "/ip4/198.51.100.19/tcp/30333".parse::().unwrap()); +/// ``` +pub fn parse_str_addr(addr_str: &str) -> Result<(PeerId, Multiaddr), ParseErr> { + let addr: Multiaddr = addr_str.parse()?; + parse_addr(addr) +} - /// Interface that can be used to delegate syncing-related function calls to `ChainSync` - pub chain_sync_service: Box>, +/// Splits a Multiaddress into a Multiaddress and PeerId. +pub fn parse_addr(mut addr: Multiaddr) -> Result<(PeerId, Multiaddr), ParseErr> { + let who = match addr.pop() { + Some(multiaddr::Protocol::P2p(key)) => + PeerId::from_multihash(key).map_err(|_| ParseErr::InvalidPeerId)?, + _ => return Err(ParseErr::PeerIdMissing), + }; - /// Registry for recording prometheus metrics to. - pub metrics_registry: Option, + Ok((who, addr)) +} - /// Block announce protocol configuration - pub block_announce_config: NonDefaultSetConfig, +/// Address of a node, including its identity. +/// +/// This struct represents a decoded version of a multiaddress that ends with `/p2p/`. +/// +/// # Example +/// +/// ``` +/// # use libp2p::{Multiaddr, PeerId}; +/// use sc_network::config::MultiaddrWithPeerId; +/// let addr: MultiaddrWithPeerId = +/// "/ip4/198.51.100.19/tcp/30333/p2p/QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV".parse().unwrap(); +/// assert_eq!(addr.peer_id.to_base58(), "QmSk5HQbn6LhUwDiNMseVUjuRYhEtYj4aUZ6WfWoGURpdV"); +/// assert_eq!(addr.multiaddr.to_string(), "/ip4/198.51.100.19/tcp/30333"); +/// ``` +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] +#[serde(try_from = "String", into = "String")] +pub struct MultiaddrWithPeerId { + /// Address of the node. + pub multiaddr: Multiaddr, + /// Its identity. + pub peer_id: PeerId, +} - /// Request response protocol configurations - pub request_response_protocol_configs: Vec, -} - -/// Sync operation mode. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum SyncMode { - /// Full block download and verification. - Full, - /// Download blocks and the latest state. - Fast { - /// Skip state proof download and verification. - skip_proofs: bool, - /// Download indexed transactions for recent blocks. - storage_chain_mode: bool, - }, - /// Warp sync - verify authority set transitions and the latest state. - Warp, +impl MultiaddrWithPeerId { + /// Concatenates the multiaddress and peer ID into one multiaddress containing both. + pub fn concat(&self) -> Multiaddr { + let proto = multiaddr::Protocol::P2p(From::from(self.peer_id)); + self.multiaddr.clone().with(proto) + } } -impl SyncMode { - /// Returns if `self` is [`Self::Warp`]. - pub fn is_warp(&self) -> bool { - matches!(self, Self::Warp) +impl fmt::Display for MultiaddrWithPeerId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::Display::fmt(&self.concat(), f) } +} + +impl FromStr for MultiaddrWithPeerId { + type Err = ParseErr; - /// Returns if `self` is [`Self::Fast`]. - pub fn is_fast(&self) -> bool { - matches!(self, Self::Fast { .. }) + fn from_str(s: &str) -> Result { + let (peer_id, multiaddr) = parse_str_addr(s)?; + Ok(Self { peer_id, multiaddr }) } } -impl Default for SyncMode { - fn default() -> Self { - Self::Full +impl From for String { + fn from(ma: MultiaddrWithPeerId) -> String { + format!("{}", ma) } } -/// Network service configuration. -#[derive(Clone, Debug)] -pub struct NetworkConfiguration { - /// Directory path to store network-specific configuration. None means nothing will be saved. - pub net_config_path: Option, - /// Multiaddresses to listen for incoming connections. - pub listen_addresses: Vec, - /// Multiaddresses to advertise. Detected automatically if empty. - pub public_addresses: Vec, - /// List of initial node addresses - pub boot_nodes: Vec, - /// The node key configuration, which determines the node's network identity keypair. - pub node_key: NodeKeyConfig, - /// List of request-response protocols that the node supports. - pub request_response_protocols: Vec, - /// Configuration for the default set of nodes used for block syncing and transactions. - pub default_peers_set: SetConfig, - /// Number of substreams to reserve for full nodes for block syncing and transactions. - /// Any other slot will be dedicated to light nodes. - /// - /// This value is implicitly capped to `default_set.out_peers + default_set.in_peers`. - pub default_peers_set_num_full: u32, - /// Configuration for extra sets of nodes. - pub extra_sets: Vec, - /// Client identifier. Sent over the wire for debugging purposes. - pub client_version: String, - /// Name of the node. Sent over the wire for debugging purposes. - pub node_name: String, - /// Configuration for the transport layer. - pub transport: TransportConfig, - /// Maximum number of peers to ask the same blocks in parallel. - pub max_parallel_downloads: u32, - /// Initial syncing mode. - pub sync_mode: SyncMode, +impl TryFrom for MultiaddrWithPeerId { + type Error = ParseErr; + fn try_from(string: String) -> Result { + string.parse() + } +} - /// True if Kademlia random discovery should be enabled. - /// - /// If true, the node will automatically randomly walk the DHT in order to find new peers. - pub enable_dht_random_walk: bool, +/// Error that can be generated by `parse_str_addr`. +#[derive(Debug)] +pub enum ParseErr { + /// Error while parsing the multiaddress. + MultiaddrParse(multiaddr::Error), + /// Multihash of the peer ID is invalid. + InvalidPeerId, + /// The peer ID is missing from the address. + PeerIdMissing, +} - /// Should we insert non-global addresses into the DHT? - pub allow_non_globals_in_dht: bool, +impl fmt::Display for ParseErr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::MultiaddrParse(err) => write!(f, "{}", err), + Self::InvalidPeerId => write!(f, "Peer id at the end of the address is invalid"), + Self::PeerIdMissing => write!(f, "Peer id is missing from the address"), + } + } +} - /// Require iterative Kademlia DHT queries to use disjoint paths for increased resiliency in - /// the presence of potentially adversarial nodes. - pub kademlia_disjoint_query_paths: bool, - /// Enable serving block data over IPFS bitswap. - pub ipfs_server: bool, +impl std::error::Error for ParseErr { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::MultiaddrParse(err) => Some(err), + Self::InvalidPeerId => None, + Self::PeerIdMissing => None, + } + } +} - /// Size of Yamux receive window of all substreams. `None` for the default (256kiB). - /// Any value less than 256kiB is invalid. - /// - /// # Context - /// - /// By design, notifications substreams on top of Yamux connections only allow up to `N` bytes - /// to be transferred at a time, where `N` is the Yamux receive window size configurable here. - /// This means, in practice, that every `N` bytes must be acknowledged by the receiver before - /// the sender can send more data. The maximum bandwidth of each notifications substream is - /// therefore `N / round_trip_time`. - /// - /// It is recommended to leave this to `None`, and use a request-response protocol instead if - /// a large amount of data must be transferred. The reason why the value is configurable is - /// that some Substrate users mis-use notification protocols to send large amounts of data. - /// As such, this option isn't designed to stay and will likely get removed in the future. - /// - /// Note that configuring a value here isn't a modification of the Yamux protocol, but rather - /// a modification of the way the implementation works. Different nodes with different - /// configured values remain compatible with each other. - pub yamux_window_size: Option, +impl From for ParseErr { + fn from(err: multiaddr::Error) -> ParseErr { + Self::MultiaddrParse(err) + } } -impl NetworkConfiguration { - /// Create new default configuration - pub fn new, SV: Into>( - node_name: SN, - client_version: SV, - node_key: NodeKeyConfig, - net_config_path: Option, - ) -> Self { - let default_peers_set = SetConfig::default(); - Self { - net_config_path, - listen_addresses: Vec::new(), - public_addresses: Vec::new(), - boot_nodes: Vec::new(), - node_key, - request_response_protocols: Vec::new(), - default_peers_set_num_full: default_peers_set.in_peers + default_peers_set.out_peers, - default_peers_set, - extra_sets: Vec::new(), - client_version: client_version.into(), - node_name: node_name.into(), - transport: TransportConfig::Normal { enable_mdns: false, allow_private_ip: true }, - max_parallel_downloads: 5, - sync_mode: SyncMode::Full, - enable_dht_random_walk: true, - allow_non_globals_in_dht: false, - kademlia_disjoint_query_paths: false, - yamux_window_size: None, - ipfs_server: false, - } +/// Custom handshake for the notification protocol +#[derive(Debug, Clone)] +pub struct NotificationHandshake(Vec); + +impl NotificationHandshake { + /// Create new `NotificationHandshake` from an object that implements `Encode` + pub fn new(handshake: H) -> Self { + Self(handshake.encode()) } - /// Create new default configuration for localhost-only connection with random port (useful for - /// testing) - pub fn new_local() -> NetworkConfiguration { - let mut config = - NetworkConfiguration::new("test-node", "test-client", Default::default(), None); + /// Create new `NotificationHandshake` from raw bytes + pub fn from_bytes(bytes: Vec) -> Self { + Self(bytes) + } +} - config.listen_addresses = - vec![iter::once(multiaddr::Protocol::Ip4(Ipv4Addr::new(127, 0, 0, 1))) - .chain(iter::once(multiaddr::Protocol::Tcp(0))) - .collect()]; +impl std::ops::Deref for NotificationHandshake { + type Target = Vec; - config.allow_non_globals_in_dht = true; - config + fn deref(&self) -> &Self::Target { + &self.0 } +} - /// Create new default configuration for localhost-only connection with random port (useful for - /// testing) - pub fn new_memory() -> NetworkConfiguration { - let mut config = - NetworkConfiguration::new("test-node", "test-client", Default::default(), None); +/// Configuration for the transport layer. +#[derive(Clone, Debug)] +pub enum TransportConfig { + /// Normal transport mode. + Normal { + /// If true, the network will use mDNS to discover other libp2p nodes on the local network + /// and connect to them if they support the same chain. + enable_mdns: bool, + + /// If true, allow connecting to private IPv4/IPv6 addresses (as defined in + /// [RFC1918](https://tools.ietf.org/html/rfc1918)). Irrelevant for addresses that have + /// been passed in `::sc_network::config::NetworkConfiguration::boot_nodes`. + allow_private_ip: bool, + }, - config.listen_addresses = - vec![iter::once(multiaddr::Protocol::Ip4(Ipv4Addr::new(127, 0, 0, 1))) - .chain(iter::once(multiaddr::Protocol::Tcp(0))) - .collect()]; + /// Only allow connections within the same process. + /// Only addresses of the form `/memory/...` will be supported. + MemoryOnly, +} - config.allow_non_globals_in_dht = true; - config +/// The policy for connections to non-reserved peers. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum NonReservedPeerMode { + /// Accept them. This is the default. + Accept, + /// Deny them. + Deny, +} + +impl NonReservedPeerMode { + /// Attempt to parse the peer mode from a string. + pub fn parse(s: &str) -> Option { + match s { + "accept" => Some(Self::Accept), + "deny" => Some(Self::Deny), + _ => None, + } } } @@ -324,7 +336,7 @@ impl NodeKeyConfig { match self { Ed25519(Secret::New) => Ok(Keypair::generate_ed25519()), - Ed25519(Secret::Input(k)) => Ok(Keypair::Ed25519(k.into())), + Ed25519(Secret::Input(k)) => Ok(ed25519::Keypair::from(k).into()), Ed25519(Secret::File(f)) => get_secret( f, @@ -335,14 +347,14 @@ impl NodeKeyConfig { None } }) { - Some(s) => ed25519::SecretKey::from_bytes(s), - _ => ed25519::SecretKey::from_bytes(&mut b), + Some(s) => ed25519::SecretKey::try_from_bytes(s), + _ => ed25519::SecretKey::try_from_bytes(&mut b), }, ed25519::SecretKey::generate, |b| b.as_ref().to_vec(), ) .map(ed25519::Keypair::from) - .map(Keypair::Ed25519), + .map(Keypair::from), } } } @@ -404,6 +416,327 @@ where fs::OpenOptions::new().write(true).create_new(true).open(path) } +/// Configuration for a set of nodes. +#[derive(Clone, Debug)] +pub struct SetConfig { + /// Maximum allowed number of incoming substreams related to this set. + pub in_peers: u32, + + /// Number of outgoing substreams related to this set that we're trying to maintain. + pub out_peers: u32, + + /// List of reserved node addresses. + pub reserved_nodes: Vec, + + /// Whether nodes that aren't in [`SetConfig::reserved_nodes`] are accepted or automatically + /// refused. + pub non_reserved_mode: NonReservedPeerMode, +} + +impl Default for SetConfig { + fn default() -> Self { + Self { + in_peers: 25, + out_peers: 75, + reserved_nodes: Vec::new(), + non_reserved_mode: NonReservedPeerMode::Accept, + } + } +} + +/// Extension to [`SetConfig`] for sets that aren't the default set. +/// +/// > **Note**: As new fields might be added in the future, please consider using the `new` method +/// > and modifiers instead of creating this struct manually. +#[derive(Clone, Debug)] +pub struct NonDefaultSetConfig { + /// Name of the notifications protocols of this set. A substream on this set will be + /// considered established once this protocol is open. + /// + /// > **Note**: This field isn't present for the default set, as this is handled internally + /// > by the networking code. + pub notifications_protocol: ProtocolName, + + /// If the remote reports that it doesn't support the protocol indicated in the + /// `notifications_protocol` field, then each of these fallback names will be tried one by + /// one. + /// + /// If a fallback is used, it will be reported in + /// `sc_network::protocol::event::Event::NotificationStreamOpened::negotiated_fallback` + pub fallback_names: Vec, + + /// Handshake of the protocol + /// + /// NOTE: Currently custom handshakes are not fully supported. See issue #5685 for more + /// details. This field is temporarily used to allow moving the hardcoded block announcement + /// protocol out of `protocol.rs`. + pub handshake: Option, + + /// Maximum allowed size of single notifications. + pub max_notification_size: u64, + + /// Base configuration. + pub set_config: SetConfig, +} + +impl NonDefaultSetConfig { + /// Creates a new [`NonDefaultSetConfig`]. Zero slots and accepts only reserved nodes. + pub fn new(notifications_protocol: ProtocolName, max_notification_size: u64) -> Self { + Self { + notifications_protocol, + max_notification_size, + fallback_names: Vec::new(), + handshake: None, + set_config: SetConfig { + in_peers: 0, + out_peers: 0, + reserved_nodes: Vec::new(), + non_reserved_mode: NonReservedPeerMode::Deny, + }, + } + } + + /// Modifies the configuration to allow non-reserved nodes. + pub fn allow_non_reserved(&mut self, in_peers: u32, out_peers: u32) { + self.set_config.in_peers = in_peers; + self.set_config.out_peers = out_peers; + self.set_config.non_reserved_mode = NonReservedPeerMode::Accept; + } + + /// Add a node to the list of reserved nodes. + pub fn add_reserved(&mut self, peer: MultiaddrWithPeerId) { + self.set_config.reserved_nodes.push(peer); + } + + /// Add a list of protocol names used for backward compatibility. + /// + /// See the explanations in [`NonDefaultSetConfig::fallback_names`]. + pub fn add_fallback_names(&mut self, fallback_names: Vec) { + self.fallback_names.extend(fallback_names); + } +} + +/// Network service configuration. +#[derive(Clone, Debug)] +pub struct NetworkConfiguration { + /// Directory path to store network-specific configuration. None means nothing will be saved. + pub net_config_path: Option, + + /// Multiaddresses to listen for incoming connections. + pub listen_addresses: Vec, + + /// Multiaddresses to advertise. Detected automatically if empty. + pub public_addresses: Vec, + + /// List of initial node addresses + pub boot_nodes: Vec, + + /// The node key configuration, which determines the node's network identity keypair. + pub node_key: NodeKeyConfig, + + /// Configuration for the default set of nodes used for block syncing and transactions. + pub default_peers_set: SetConfig, + + /// Number of substreams to reserve for full nodes for block syncing and transactions. + /// Any other slot will be dedicated to light nodes. + /// + /// This value is implicitly capped to `default_set.out_peers + default_set.in_peers`. + pub default_peers_set_num_full: u32, + + /// Client identifier. Sent over the wire for debugging purposes. + pub client_version: String, + + /// Name of the node. Sent over the wire for debugging purposes. + pub node_name: String, + + /// Configuration for the transport layer. + pub transport: TransportConfig, + + /// Maximum number of peers to ask the same blocks in parallel. + pub max_parallel_downloads: u32, + + /// Maximum number of blocks per request. + pub max_blocks_per_request: u32, + + /// Initial syncing mode. + pub sync_mode: SyncMode, + + /// True if Kademlia random discovery should be enabled. + /// + /// If true, the node will automatically randomly walk the DHT in order to find new peers. + pub enable_dht_random_walk: bool, + + /// Should we insert non-global addresses into the DHT? + pub allow_non_globals_in_dht: bool, + + /// Require iterative Kademlia DHT queries to use disjoint paths for increased resiliency in + /// the presence of potentially adversarial nodes. + pub kademlia_disjoint_query_paths: bool, + + /// Kademlia replication factor determines to how many closest peers a record is replicated to. + /// + /// Discovery mechanism requires successful replication to all + /// `kademlia_replication_factor` peers to consider record successfully put. + pub kademlia_replication_factor: NonZeroUsize, + + /// Enable serving block data over IPFS bitswap. + pub ipfs_server: bool, + + /// Size of Yamux receive window of all substreams. `None` for the default (256kiB). + /// Any value less than 256kiB is invalid. + /// + /// # Context + /// + /// By design, notifications substreams on top of Yamux connections only allow up to `N` bytes + /// to be transferred at a time, where `N` is the Yamux receive window size configurable here. + /// This means, in practice, that every `N` bytes must be acknowledged by the receiver before + /// the sender can send more data. The maximum bandwidth of each notifications substream is + /// therefore `N / round_trip_time`. + /// + /// It is recommended to leave this to `None`, and use a request-response protocol instead if + /// a large amount of data must be transferred. The reason why the value is configurable is + /// that some Substrate users mis-use notification protocols to send large amounts of data. + /// As such, this option isn't designed to stay and will likely get removed in the future. + /// + /// Note that configuring a value here isn't a modification of the Yamux protocol, but rather + /// a modification of the way the implementation works. Different nodes with different + /// configured values remain compatible with each other. + pub yamux_window_size: Option, +} + +impl NetworkConfiguration { + /// Create new default configuration + pub fn new, SV: Into>( + node_name: SN, + client_version: SV, + node_key: NodeKeyConfig, + net_config_path: Option, + ) -> Self { + let default_peers_set = SetConfig::default(); + Self { + net_config_path, + listen_addresses: Vec::new(), + public_addresses: Vec::new(), + boot_nodes: Vec::new(), + node_key, + default_peers_set_num_full: default_peers_set.in_peers + default_peers_set.out_peers, + default_peers_set, + client_version: client_version.into(), + node_name: node_name.into(), + transport: TransportConfig::Normal { enable_mdns: false, allow_private_ip: true }, + max_parallel_downloads: 5, + max_blocks_per_request: 64, + sync_mode: SyncMode::Full, + enable_dht_random_walk: true, + allow_non_globals_in_dht: false, + kademlia_disjoint_query_paths: false, + kademlia_replication_factor: NonZeroUsize::new(DEFAULT_KADEMLIA_REPLICATION_FACTOR) + .expect("value is a constant; constant is non-zero; qed."), + yamux_window_size: None, + ipfs_server: false, + } + } + + /// Create new default configuration for localhost-only connection with random port (useful for + /// testing) + pub fn new_local() -> NetworkConfiguration { + let mut config = + NetworkConfiguration::new("test-node", "test-client", Default::default(), None); + + config.listen_addresses = + vec![iter::once(multiaddr::Protocol::Ip4(Ipv4Addr::new(127, 0, 0, 1))) + .chain(iter::once(multiaddr::Protocol::Tcp(0))) + .collect()]; + + config.allow_non_globals_in_dht = true; + config + } + + /// Create new default configuration for localhost-only connection with random port (useful for + /// testing) + pub fn new_memory() -> NetworkConfiguration { + let mut config = + NetworkConfiguration::new("test-node", "test-client", Default::default(), None); + + config.listen_addresses = + vec![iter::once(multiaddr::Protocol::Ip4(Ipv4Addr::new(127, 0, 0, 1))) + .chain(iter::once(multiaddr::Protocol::Tcp(0))) + .collect()]; + + config.allow_non_globals_in_dht = true; + config + } +} + +/// Network initialization parameters. +pub struct Params { + /// Assigned role for our node (full, light, ...). + pub role: Role, + + /// How to spawn background tasks. + pub executor: Box + Send>>) + Send>, + + /// Network layer configuration. + pub network_config: FullNetworkConfiguration, + + /// Legacy name of the protocol to use on the wire. Should be different for each chain. + pub protocol_id: ProtocolId, + + /// Genesis hash of the chain + pub genesis_hash: Block::Hash, + + /// Fork ID to distinguish protocols of different hard forks. Part of the standard protocol + /// name on the wire. + pub fork_id: Option, + + /// Registry for recording prometheus metrics to. + pub metrics_registry: Option, + + /// Block announce protocol configuration + pub block_announce_config: NonDefaultSetConfig, + + /// TX channel for direct communication with `SyncingEngine` and `Protocol`. + pub tx: TracingUnboundedSender>, +} + +/// Full network configuration. +pub struct FullNetworkConfiguration { + /// Installed notification protocols. + pub(crate) notification_protocols: Vec, + + /// List of request-response protocols that the node supports. + pub(crate) request_response_protocols: Vec, + + /// Network configuration. + pub network_config: NetworkConfiguration, +} + +impl FullNetworkConfiguration { + /// Create new [`FullNetworkConfiguration`]. + pub fn new(network_config: &NetworkConfiguration) -> Self { + Self { + notification_protocols: Vec::new(), + request_response_protocols: Vec::new(), + network_config: network_config.clone(), + } + } + + /// Add a notification protocol. + pub fn add_notification_protocol(&mut self, config: NonDefaultSetConfig) { + self.notification_protocols.push(config); + } + + /// Get reference to installed notification protocols. + pub fn notification_protocols(&self) -> &Vec { + &self.notification_protocols + } + + /// Add a request-response protocol. + pub fn add_request_response_protocol(&mut self, config: RequestResponseConfig) { + self.request_response_protocols.push(config); + } +} + #[cfg(test)] mod tests { use super::*; @@ -413,9 +746,14 @@ mod tests { tempfile::Builder::new().prefix(prefix).tempdir().unwrap() } - fn secret_bytes(kp: &Keypair) -> Vec { - let Keypair::Ed25519(p) = kp; - p.secret().as_ref().iter().cloned().collect() + fn secret_bytes(kp: Keypair) -> Vec { + kp.try_into_ed25519() + .expect("ed25519 keypair") + .secret() + .as_ref() + .iter() + .cloned() + .collect() } #[test] @@ -425,7 +763,7 @@ mod tests { let file = tmp.path().join("x").to_path_buf(); let kp1 = NodeKeyConfig::Ed25519(Secret::File(file.clone())).into_keypair().unwrap(); let kp2 = NodeKeyConfig::Ed25519(Secret::File(file.clone())).into_keypair().unwrap(); - assert!(file.is_file() && secret_bytes(&kp1) == secret_bytes(&kp2)) + assert!(file.is_file() && secret_bytes(kp1) == secret_bytes(kp2)) } #[test] @@ -433,13 +771,13 @@ mod tests { let sk = ed25519::SecretKey::generate(); let kp1 = NodeKeyConfig::Ed25519(Secret::Input(sk.clone())).into_keypair().unwrap(); let kp2 = NodeKeyConfig::Ed25519(Secret::Input(sk)).into_keypair().unwrap(); - assert!(secret_bytes(&kp1) == secret_bytes(&kp2)); + assert!(secret_bytes(kp1) == secret_bytes(kp2)); } #[test] fn test_secret_new() { let kp1 = NodeKeyConfig::Ed25519(Secret::New).into_keypair().unwrap(); let kp2 = NodeKeyConfig::Ed25519(Secret::New).into_keypair().unwrap(); - assert!(secret_bytes(&kp1) != secret_bytes(&kp2)); + assert!(secret_bytes(kp1) != secret_bytes(kp2)); } } diff --git a/client/network/src/discovery.rs b/client/network/src/discovery.rs index 7329ebf15cf0..d9fad5384434 100644 --- a/client/network/src/discovery.rs +++ b/client/network/src/discovery.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -46,38 +46,37 @@ //! active mechanism that asks nodes for the addresses they are listening on. Whenever we learn //! of a node's address, you must call `add_self_reported_address`. +use crate::{config::ProtocolId, utils::LruHashSet}; + use array_bytes::bytes2hex; use futures::prelude::*; use futures_timer::Delay; use ip_network::IpNetwork; use libp2p::{ - core::{connection::ConnectionId, Multiaddr, PeerId, PublicKey}, + core::{Endpoint, Multiaddr}, kad::{ - handler::KademliaHandlerProto, - record::{ - self, - store::{MemoryStore, RecordStore}, - }, + handler::KademliaHandler, + record::store::{MemoryStore, RecordStore}, GetClosestPeersError, GetRecordOk, Kademlia, KademliaBucketInserts, KademliaConfig, - KademliaEvent, QueryId, QueryResult, Quorum, Record, + KademliaEvent, QueryId, QueryResult, Quorum, Record, RecordKey, }, mdns::{self, tokio::Behaviour as TokioMdns}, multiaddr::Protocol, swarm::{ behaviour::{ - toggle::{Toggle, ToggleIntoConnectionHandler}, + toggle::{Toggle, ToggleConnectionHandler}, DialFailure, FromSwarm, NewExternalAddr, }, - ConnectionHandler, DialError, IntoConnectionHandler, NetworkBehaviour, - NetworkBehaviourAction, PollParameters, + ConnectionDenied, ConnectionId, DialError, NetworkBehaviour, PollParameters, THandler, + THandlerInEvent, THandlerOutEvent, ToSwarm, }, + PeerId, }; use log::{debug, info, trace, warn}; -use sc_network_common::{config::ProtocolId, utils::LruHashSet}; use sp_core::hexdisplay::HexDisplay; use std::{ cmp, - collections::{HashMap, HashSet, VecDeque}, + collections::{hash_map::Entry, HashMap, HashSet, VecDeque}, num::NonZeroUsize, task::{Context, Poll}, time::Duration, @@ -88,6 +87,10 @@ use std::{ /// a given address. const MAX_KNOWN_EXTERNAL_ADDRESSES: usize = 32; +/// Default value for Kademlia replication factor which determines to how many closest peers a +/// record is replicated to. +pub const DEFAULT_KADEMLIA_REPLICATION_FACTOR: usize = 20; + /// `DiscoveryBehaviour` configuration. /// /// Note: In order to discover nodes or load and store values via Kademlia one has to add @@ -102,13 +105,14 @@ pub struct DiscoveryConfig { enable_mdns: bool, kademlia_disjoint_query_paths: bool, kademlia_protocols: Vec>, + kademlia_replication_factor: NonZeroUsize, } impl DiscoveryConfig { /// Create a default configuration with the given public key. - pub fn new(local_public_key: PublicKey) -> Self { + pub fn new(local_peer_id: PeerId) -> Self { Self { - local_peer_id: local_public_key.to_peer_id(), + local_peer_id, permanent_addresses: Vec::new(), dht_random_walk: true, allow_private_ip: true, @@ -117,6 +121,8 @@ impl DiscoveryConfig { enable_mdns: false, kademlia_disjoint_query_paths: false, kademlia_protocols: Vec::new(), + kademlia_replication_factor: NonZeroUsize::new(DEFAULT_KADEMLIA_REPLICATION_FACTOR) + .expect("value is a constant; constant is non-zero; qed."), } } @@ -183,6 +189,12 @@ impl DiscoveryConfig { self } + /// Sets Kademlia replication factor. + pub fn with_kademlia_replication_factor(&mut self, value: NonZeroUsize) -> &mut Self { + self.kademlia_replication_factor = value; + self + } + /// Create a `DiscoveryBehaviour` from this config. pub fn finish(self) -> DiscoveryBehaviour { let Self { @@ -195,10 +207,13 @@ impl DiscoveryConfig { enable_mdns, kademlia_disjoint_query_paths, kademlia_protocols, + kademlia_replication_factor, } = self; let kademlia = if !kademlia_protocols.is_empty() { let mut config = KademliaConfig::default(); + + config.set_replication_factor(kademlia_replication_factor); config.set_protocol_names(kademlia_protocols.into_iter().map(Into::into).collect()); // By default Kademlia attempts to insert all peers into its routing table once a // dialing attempt succeeds. In order to control which peer is added, disable the @@ -234,7 +249,7 @@ impl DiscoveryConfig { allow_private_ip, discovery_only_if_under_num, mdns: if enable_mdns { - match TokioMdns::new(mdns::Config::default()) { + match TokioMdns::new(mdns::Config::default(), local_peer_id) { Ok(mdns) => Some(mdns), Err(err) => { warn!(target: "sub-libp2p", "Failed to initialize mDNS: {:?}", err); @@ -318,14 +333,16 @@ impl DiscoveryBehaviour { /// If we didn't know this address before, also generates a `Discovered` event. pub fn add_known_address(&mut self, peer_id: PeerId, addr: Multiaddr) { let addrs_list = self.ephemeral_addresses.entry(peer_id).or_default(); - if !addrs_list.iter().any(|a| *a == addr) { - if let Some(k) = self.kademlia.as_mut() { - k.add_address(&peer_id, addr.clone()); - } + if addrs_list.contains(&addr) { + return + } - self.pending_events.push_back(DiscoveryOut::Discovered(peer_id)); - addrs_list.push(addr); + if let Some(k) = self.kademlia.as_mut() { + k.add_address(&peer_id, addr.clone()); } + + self.pending_events.push_back(DiscoveryOut::Discovered(peer_id)); + addrs_list.push(addr); } /// Add a self-reported address of a remote peer to the k-buckets of the DHT @@ -371,7 +388,7 @@ impl DiscoveryBehaviour { /// Start fetching a record from the DHT. /// /// A corresponding `ValueFound` or `ValueNotFound` event will later be generated. - pub fn get_value(&mut self, key: record::Key) { + pub fn get_value(&mut self, key: RecordKey) { if let Some(k) = self.kademlia.as_mut() { k.get_record(key.clone()); } @@ -381,7 +398,7 @@ impl DiscoveryBehaviour { /// `get_value`. /// /// A corresponding `ValuePut` or `ValuePutFailed` event will later be generated. - pub fn put_value(&mut self, key: record::Key, value: Vec) { + pub fn put_value(&mut self, key: RecordKey, value: Vec) { if let Some(k) = self.kademlia.as_mut() { if let Err(e) = k.put_record(Record::new(key.clone(), value.clone()), Quorum::All) { warn!(target: "sub-libp2p", "Libp2p => Failed to put record: {:?}", e); @@ -456,23 +473,23 @@ pub enum DiscoveryOut { /// The DHT yielded results for the record request. /// - /// Returning the result grouped in (key, value) pairs as well as the request duration.. - ValueFound(Vec<(record::Key, Vec)>, Duration), + /// Returning the result grouped in (key, value) pairs as well as the request duration. + ValueFound(Vec<(RecordKey, Vec)>, Duration), /// The record requested was not found in the DHT. /// /// Returning the corresponding key as well as the request duration. - ValueNotFound(record::Key, Duration), + ValueNotFound(RecordKey, Duration), /// The record with a given key was successfully inserted into the DHT. /// /// Returning the corresponding key as well as the request duration. - ValuePut(record::Key, Duration), + ValuePut(RecordKey, Duration), /// Inserting a value into the DHT failed. /// /// Returning the corresponding key as well as the request duration. - ValuePutFailed(record::Key, Duration), + ValuePutFailed(RecordKey, Duration), /// Started a random Kademlia query. /// @@ -481,29 +498,83 @@ pub enum DiscoveryOut { } impl NetworkBehaviour for DiscoveryBehaviour { - type ConnectionHandler = ToggleIntoConnectionHandler>; + type ConnectionHandler = ToggleConnectionHandler>; type OutEvent = DiscoveryOut; - fn new_handler(&mut self) -> Self::ConnectionHandler { - self.kademlia.new_handler() + fn handle_established_inbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result, ConnectionDenied> { + self.kademlia.handle_established_inbound_connection( + connection_id, + peer, + local_addr, + remote_addr, + ) } - fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec { + fn handle_established_outbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + addr: &Multiaddr, + role_override: Endpoint, + ) -> Result, ConnectionDenied> { + self.kademlia.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + ) + } + + fn handle_pending_inbound_connection( + &mut self, + connection_id: ConnectionId, + local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result<(), ConnectionDenied> { + self.kademlia + .handle_pending_inbound_connection(connection_id, local_addr, remote_addr) + } + + fn handle_pending_outbound_connection( + &mut self, + connection_id: ConnectionId, + maybe_peer: Option, + addresses: &[Multiaddr], + effective_role: Endpoint, + ) -> Result, ConnectionDenied> { + let Some(peer_id) = maybe_peer else { return Ok(Vec::new()) }; + let mut list = self .permanent_addresses .iter() - .filter_map(|(p, a)| if p == peer_id { Some(a.clone()) } else { None }) + .filter_map(|(p, a)| (*p == peer_id).then_some(a.clone())) .collect::>(); - if let Some(ephemeral_addresses) = self.ephemeral_addresses.get(peer_id) { + if let Some(ephemeral_addresses) = self.ephemeral_addresses.get(&peer_id) { list.extend(ephemeral_addresses.clone()); } { - let mut list_to_filter = self.kademlia.addresses_of_peer(peer_id); + let mut list_to_filter = self.kademlia.handle_pending_outbound_connection( + connection_id, + maybe_peer, + addresses, + effective_role, + )?; if let Some(ref mut mdns) = self.mdns { - list_to_filter.extend(mdns.addresses_of_peer(peer_id)); + list_to_filter.extend(mdns.handle_pending_outbound_connection( + connection_id, + maybe_peer, + addresses, + effective_role, + )?); } if !self.allow_private_ip { @@ -519,7 +590,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { trace!(target: "sub-libp2p", "Addresses of {:?}: {:?}", peer_id, list); - list + Ok(list) } fn on_swarm_event(&mut self, event: FromSwarm) { @@ -535,9 +606,13 @@ impl NetworkBehaviour for DiscoveryBehaviour { FromSwarm::DialFailure(e @ DialFailure { peer_id, error, .. }) => { if let Some(peer_id) = peer_id { if let DialError::Transport(errors) = error { - if let Some(list) = self.ephemeral_addresses.get_mut(&peer_id) { + if let Entry::Occupied(mut entry) = self.ephemeral_addresses.entry(peer_id) + { for (addr, _error) in errors { - list.retain(|a| a != addr); + entry.get_mut().retain(|a| a != addr); + } + if entry.get().is_empty() { + entry.remove(); } } } @@ -588,6 +663,10 @@ impl NetworkBehaviour for DiscoveryBehaviour { }, FromSwarm::NewListenAddr(e) => { self.kademlia.on_swarm_event(FromSwarm::NewListenAddr(e)); + + if let Some(ref mut mdns) = self.mdns { + mdns.on_swarm_event(FromSwarm::NewListenAddr(e)); + } }, } } @@ -596,8 +675,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { &mut self, peer_id: PeerId, connection_id: ConnectionId, - event: <::Handler as - ConnectionHandler>::OutEvent, + event: THandlerOutEvent, ) { self.kademlia.on_connection_handler_event(peer_id, connection_id, event); } @@ -606,10 +684,10 @@ impl NetworkBehaviour for DiscoveryBehaviour { &mut self, cx: &mut Context, params: &mut impl PollParameters, - ) -> Poll> { + ) -> Poll>> { // Immediately process the content of `discovered`. if let Some(ev) = self.pending_events.pop_front() { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) + return Poll::Ready(ToSwarm::GenerateEvent(ev)) } // Poll the stream that fires when we need to start a random Kademlia query. @@ -643,7 +721,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { if actually_started { let ev = DiscoveryOut::RandomKademliaStarted; - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) + return Poll::Ready(ToSwarm::GenerateEvent(ev)) } } } @@ -651,18 +729,18 @@ impl NetworkBehaviour for DiscoveryBehaviour { while let Poll::Ready(ev) = self.kademlia.poll(cx, params) { match ev { - NetworkBehaviourAction::GenerateEvent(ev) => match ev { + ToSwarm::GenerateEvent(ev) => match ev { KademliaEvent::RoutingUpdated { peer, .. } => { let ev = DiscoveryOut::Discovered(peer); - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) + return Poll::Ready(ToSwarm::GenerateEvent(ev)) }, KademliaEvent::UnroutablePeer { peer, .. } => { let ev = DiscoveryOut::UnroutablePeer(peer); - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) + return Poll::Ready(ToSwarm::GenerateEvent(ev)) }, KademliaEvent::RoutablePeer { peer, .. } => { let ev = DiscoveryOut::Discovered(peer); - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) + return Poll::Ready(ToSwarm::GenerateEvent(ev)) }, KademliaEvent::PendingRoutablePeer { .. } | KademliaEvent::InboundRequest { .. } => { @@ -770,7 +848,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { ) }, }; - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) + return Poll::Ready(ToSwarm::GenerateEvent(ev)) }, KademliaEvent::OutboundQueryProgressed { result: QueryResult::PutRecord(res), @@ -792,7 +870,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { ) }, }; - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) + return Poll::Ready(ToSwarm::GenerateEvent(ev)) }, KademliaEvent::OutboundQueryProgressed { result: QueryResult::RepublishRecord(res), @@ -814,24 +892,13 @@ impl NetworkBehaviour for DiscoveryBehaviour { warn!(target: "sub-libp2p", "Libp2p => Unhandled Kademlia event: {:?}", e) }, }, - NetworkBehaviourAction::Dial { opts, handler } => - return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }), - NetworkBehaviourAction::NotifyHandler { peer_id, handler, event } => - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { - peer_id, - handler, - event, - }), - NetworkBehaviourAction::ReportObservedAddr { address, score } => - return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { - address, - score, - }), - NetworkBehaviourAction::CloseConnection { peer_id, connection } => - return Poll::Ready(NetworkBehaviourAction::CloseConnection { - peer_id, - connection, - }), + ToSwarm::Dial { opts } => return Poll::Ready(ToSwarm::Dial { opts }), + ToSwarm::NotifyHandler { peer_id, handler, event } => + return Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, event }), + ToSwarm::ReportObservedAddr { address, score } => + return Poll::Ready(ToSwarm::ReportObservedAddr { address, score }), + ToSwarm::CloseConnection { peer_id, connection } => + return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), } } @@ -839,7 +906,7 @@ impl NetworkBehaviour for DiscoveryBehaviour { if let Some(ref mut mdns) = self.mdns { while let Poll::Ready(ev) = mdns.poll(cx, params) { match ev { - NetworkBehaviourAction::GenerateEvent(event) => match event { + ToSwarm::GenerateEvent(event) => match event { mdns::Event::Discovered(list) => { if self.num_connections >= self.discovery_only_if_under_num { continue @@ -848,25 +915,21 @@ impl NetworkBehaviour for DiscoveryBehaviour { self.pending_events .extend(list.map(|(peer_id, _)| DiscoveryOut::Discovered(peer_id))); if let Some(ev) = self.pending_events.pop_front() { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) + return Poll::Ready(ToSwarm::GenerateEvent(ev)) } }, mdns::Event::Expired(_) => {}, }, - NetworkBehaviourAction::Dial { .. } => { + ToSwarm::Dial { .. } => { unreachable!("mDNS never dials!"); }, - NetworkBehaviourAction::NotifyHandler { event, .. } => match event {}, /* `event` is an enum with no variant */ - NetworkBehaviourAction::ReportObservedAddr { address, score } => - return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { - address, - score, - }), - NetworkBehaviourAction::CloseConnection { peer_id, connection } => - return Poll::Ready(NetworkBehaviourAction::CloseConnection { - peer_id, - connection, - }), + ToSwarm::NotifyHandler { event, .. } => match event {}, /* `event` is an */ + // enum with no + // variant + ToSwarm::ReportObservedAddr { address, score } => + return Poll::Ready(ToSwarm::ReportObservedAddr { address, score }), + ToSwarm::CloseConnection { peer_id, connection } => + return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), } } } @@ -898,18 +961,18 @@ mod tests { use super::{ kademlia_protocol_name, legacy_kademlia_protocol_name, DiscoveryConfig, DiscoveryOut, }; + use crate::config::ProtocolId; use futures::prelude::*; use libp2p::{ core::{ transport::{MemoryTransport, Transport}, upgrade, }, - identity::{ed25519, Keypair}, + identity::Keypair, noise, - swarm::{Executor, Swarm, SwarmEvent}, + swarm::{Executor, Swarm, SwarmBuilder, SwarmEvent}, yamux, Multiaddr, }; - use sc_network_common::config::ProtocolId; use sp_core::hash::H256; use std::{collections::HashSet, pin::Pin, task::Poll}; @@ -934,17 +997,14 @@ mod tests { .map(|i| { let keypair = Keypair::generate_ed25519(); - let noise_keys = - noise::Keypair::::new().into_authentic(&keypair).unwrap(); - let transport = MemoryTransport::new() .upgrade(upgrade::Version::V1) - .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated()) - .multiplex(yamux::YamuxConfig::default()) + .authenticate(noise::Config::new(&keypair).unwrap()) + .multiplex(yamux::Config::default()) .boxed(); let behaviour = { - let mut config = DiscoveryConfig::new(keypair.public()); + let mut config = DiscoveryConfig::new(keypair.public().to_peer_id()); config .with_permanent_addresses(first_swarm_peer_id_and_addr.clone()) .allow_private_ip(true) @@ -956,12 +1016,13 @@ mod tests { }; let runtime = tokio::runtime::Runtime::new().unwrap(); - let mut swarm = Swarm::with_executor( + let mut swarm = SwarmBuilder::with_executor( transport, behaviour, keypair.public().to_peer_id(), TokioExecutor(runtime), - ); + ) + .build(); let listen_addr: Multiaddr = format!("/memory/{}", rand::random::()).parse().unwrap(); @@ -1063,7 +1124,7 @@ mod tests { let mut discovery = { let keypair = Keypair::generate_ed25519(); - let mut config = DiscoveryConfig::new(keypair.public()); + let mut config = DiscoveryConfig::new(keypair.public().to_peer_id()); config .allow_private_ip(true) .allow_non_globals_in_dht(true) @@ -1073,11 +1134,7 @@ mod tests { }; let predictable_peer_id = |bytes: &[u8; 32]| { - Keypair::Ed25519(ed25519::Keypair::from( - ed25519::SecretKey::from_bytes(bytes.to_owned()).unwrap(), - )) - .public() - .to_peer_id() + Keypair::ed25519_from_bytes(bytes.to_owned()).unwrap().public().to_peer_id() }; let remote_peer_id = predictable_peer_id(b"00000000000000000000000000000001"); diff --git a/client/network/common/src/error.rs b/client/network/src/error.rs similarity index 95% rename from client/network/common/src/error.rs rename to client/network/src/error.rs index 4326b1af5283..f0828fb821f3 100644 --- a/client/network/common/src/error.rs +++ b/client/network/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,7 +18,8 @@ //! Substrate network possible errors. -use crate::{config::TransportConfig, protocol::ProtocolName}; +use crate::{config::TransportConfig, types::ProtocolName}; + use libp2p::{Multiaddr, PeerId}; use std::fmt; diff --git a/client/network/common/src/protocol/event.rs b/client/network/src/event.rs similarity index 66% rename from client/network/common/src/protocol/event.rs rename to client/network/src/event.rs index 236913df1b12..9c1034ea3dc6 100644 --- a/client/network/common/src/protocol/event.rs +++ b/client/network/src/event.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -19,10 +19,14 @@ //! Network event types. These are are not the part of the protocol, but rather //! events that happen on the network like DHT get/put results received. -use super::ProtocolName; -use crate::protocol::role::ObservedRole; +use crate::{types::ProtocolName, NotificationsSink}; + use bytes::Bytes; -use libp2p::{core::PeerId, kad::record::Key}; +use futures::channel::oneshot; +use libp2p::{kad::record::Key, PeerId}; + +use sc_network_common::{role::ObservedRole, sync::message::BlockAnnouncesHandshake}; +use sp_runtime::traits::Block as BlockT; /// Events generated by DHT as a response to get_value and put_value requests. #[derive(Debug, Clone)] @@ -48,18 +52,6 @@ pub enum Event { /// Event generated by a DHT. Dht(DhtEvent), - /// Now connected to a new peer for syncing purposes. - SyncConnected { - /// Node we are now syncing from. - remote: PeerId, - }, - - /// Now disconnected from a peer for syncing purposes. - SyncDisconnected { - /// Node we are no longer syncing from. - remote: PeerId, - }, - /// Opened a substream with the given node with the given notifications protocol. /// /// The protocol is always one of the notification protocols that have been registered. @@ -79,6 +71,8 @@ pub enum Event { negotiated_fallback: Option, /// Role of the remote. role: ObservedRole, + /// Received handshake. + received_handshake: Vec, }, /// Closed a substream with the given node. Always matches a corresponding previous @@ -98,3 +92,44 @@ pub enum Event { messages: Vec<(ProtocolName, Bytes)>, }, } + +/// Event sent to `SyncingEngine` +// TODO: remove once `NotificationService` is implemented. +pub enum SyncEvent { + /// Opened a substream with the given node with the given notifications protocol. + /// + /// The protocol is always one of the notification protocols that have been registered. + NotificationStreamOpened { + /// Node we opened the substream with. + remote: PeerId, + /// Received handshake. + received_handshake: BlockAnnouncesHandshake, + /// Notification sink. + sink: NotificationsSink, + /// Channel for reporting accept/reject of the substream. + tx: oneshot::Sender, + }, + + /// Closed a substream with the given node. Always matches a corresponding previous + /// `NotificationStreamOpened` message. + NotificationStreamClosed { + /// Node we closed the substream with. + remote: PeerId, + }, + + /// Notification sink was replaced. + NotificationSinkReplaced { + /// Node we closed the substream with. + remote: PeerId, + /// Notification sink. + sink: NotificationsSink, + }, + + /// Received one or more messages from the given node using the given protocol. + NotificationsReceived { + /// Node we received the message from. + remote: PeerId, + /// Concerned protocol and associated message. + messages: Vec, + }, +} diff --git a/client/network/src/lib.rs b/client/network/src/lib.rs index f185458e0dac..9f528b8bec38 100644 --- a/client/network/src/lib.rs +++ b/client/network/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -87,8 +87,6 @@ //! //! The following multiplexing protocols are supported: //! -//! - [Mplex](https://github.com/libp2p/specs/tree/master/mplex). Support for mplex will likely -//! be deprecated in the future. //! - [Yamux](https://github.com/hashicorp/yamux/blob/master/spec.md). //! //! ## Substreams @@ -212,7 +210,7 @@ //! `sc-network` automatically tries to open a substream towards each node for which the legacy //! Substream substream is open. The handshake is then performed automatically. //! -//! For example, the `sc-finality-grandpa` crate registers the `/paritytech/grandpa/1` +//! For example, the `sc-consensus-grandpa` crate registers the `/paritytech/grandpa/1` //! notifications protocol. //! //! At the moment, for backwards-compatibility, notification protocols are tied to the legacy @@ -245,44 +243,47 @@ //! More precise usage details are still being worked on and will likely change in the future. mod behaviour; -mod discovery; -mod peer_info; +mod peer_store; +mod peerset; mod protocol; -mod request_responses; +mod protocol_controller; mod service; -mod transport; pub mod config; +pub mod discovery; +pub mod error; +pub mod event; pub mod network_state; +pub mod peer_info; +pub mod request_responses; +pub mod transport; +pub mod types; +pub mod utils; +pub use event::{DhtEvent, Event, SyncEvent}; #[doc(inline)] pub use libp2p::{multiaddr, Multiaddr, PeerId}; -pub use protocol::PeerInfo; -use sc_consensus::{JustificationSyncLink, Link}; +pub use request_responses::{Config, IfDisconnected, RequestFailure}; pub use sc_network_common::{ - protocol::{ - event::{DhtEvent, Event}, - role::ObservedRole, - ProtocolName, - }, - request_responses::{IfDisconnected, RequestFailure}, - service::{ - KademliaKey, NetworkBlock, NetworkDHTProvider, NetworkRequest, NetworkSigner, - NetworkStateInfo, NetworkStatus, NetworkStatusProvider, NetworkSyncForkRequest, Signature, - SigningError, - }, + role::ObservedRole, sync::{ warp::{WarpSyncPhase, WarpSyncProgress}, - StateDownloadProgress, SyncState, + ExtendedPeerInfo, StateDownloadProgress, SyncEventStream, SyncState, SyncStatusProvider, }, + types::ReputationChange, }; pub use service::{ - DecodingError, Keypair, NetworkService, NetworkWorker, NotificationSender, - NotificationSenderReady, OutboundFailure, PublicKey, + signature::Signature, + traits::{ + KademliaKey, NetworkBlock, NetworkDHTProvider, NetworkEventStream, NetworkNotification, + NetworkPeers, NetworkRequest, NetworkSigner, NetworkStateInfo, NetworkStatus, + NetworkStatusProvider, NetworkSyncForkRequest, NotificationSender as NotificationSenderT, + NotificationSenderError, NotificationSenderReady, + }, + DecodingError, Keypair, NetworkService, NetworkWorker, NotificationSender, NotificationsSink, + OutboundFailure, PublicKey, }; -use sp_runtime::traits::{Block as BlockT, NumberFor}; - -pub use sc_peerset::ReputationChange; +pub use types::ProtocolName; /// The maximum allowed number of established connections per peer. /// @@ -295,18 +296,3 @@ const MAX_CONNECTIONS_PER_PEER: usize = 2; /// The maximum number of concurrent established connections that were incoming. const MAX_CONNECTIONS_ESTABLISHED_INCOMING: u32 = 10_000; - -/// Abstraction over syncing-related services -pub trait ChainSyncInterface: - NetworkSyncForkRequest> + JustificationSyncLink + Link + Send + Sync -{ -} - -impl ChainSyncInterface for T where - T: NetworkSyncForkRequest> - + JustificationSyncLink - + Link - + Send - + Sync -{ -} diff --git a/client/network/src/network_state.rs b/client/network/src/network_state.rs index 57073c57afa6..cf8b8b55a7ff 100644 --- a/client/network/src/network_state.rs +++ b/client/network/src/network_state.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/network/src/peer_info.rs b/client/network/src/peer_info.rs index f3402c0af110..aab3fc9487e8 100644 --- a/client/network/src/peer_info.rs +++ b/client/network/src/peer_info.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,28 +16,35 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +//! [`PeerInfoBehaviour`] is implementation of `NetworkBehaviour` that holds information about peers +//! in cache. + +use crate::utils::interval; +use either::Either; + use fnv::FnvHashMap; use futures::prelude::*; use libp2p::{ - core::{connection::ConnectionId, either::EitherOutput, ConnectedPoint, PeerId, PublicKey}, + core::{ConnectedPoint, Endpoint}, identify::{ Behaviour as Identify, Config as IdentifyConfig, Event as IdentifyEvent, Info as IdentifyInfo, }, + identity::PublicKey, ping::{Behaviour as Ping, Config as PingConfig, Event as PingEvent, Success as PingSuccess}, swarm::{ behaviour::{ AddressChange, ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm, ListenFailure, }, - ConnectionHandler, IntoConnectionHandler, IntoConnectionHandlerSelect, NetworkBehaviour, - NetworkBehaviourAction, PollParameters, + ConnectionDenied, ConnectionHandler, ConnectionId, IntoConnectionHandlerSelect, + NetworkBehaviour, PollParameters, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }, - Multiaddr, + Multiaddr, PeerId, }; use log::{debug, error, trace}; -use sc_network_common::utils::interval; use smallvec::SmallVec; + use std::{ collections::hash_map::Entry, pin::Pin, @@ -180,14 +187,72 @@ impl NetworkBehaviour for PeerInfoBehaviour { >; type OutEvent = PeerInfoEvent; - fn new_handler(&mut self) -> Self::ConnectionHandler { - IntoConnectionHandler::select(self.ping.new_handler(), self.identify.new_handler()) + fn handle_pending_inbound_connection( + &mut self, + connection_id: ConnectionId, + local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result<(), ConnectionDenied> { + self.ping + .handle_pending_inbound_connection(connection_id, local_addr, remote_addr)?; + self.identify + .handle_pending_inbound_connection(connection_id, local_addr, remote_addr) + } + + fn handle_pending_outbound_connection( + &mut self, + _connection_id: ConnectionId, + _maybe_peer: Option, + _addresses: &[Multiaddr], + _effective_role: Endpoint, + ) -> Result, ConnectionDenied> { + // Only `Discovery::handle_pending_outbound_connection` must be returning addresses to + // ensure that we don't return unwanted addresses. + Ok(Vec::new()) + } + + fn handle_established_inbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result, ConnectionDenied> { + let ping_handler = self.ping.handle_established_inbound_connection( + connection_id, + peer, + local_addr, + remote_addr, + )?; + let identify_handler = self.identify.handle_established_inbound_connection( + connection_id, + peer, + local_addr, + remote_addr, + )?; + Ok(ping_handler.select(identify_handler)) } - fn addresses_of_peer(&mut self, _: &PeerId) -> Vec { - // Only `Discovery::addresses_of_peer` must be returning addresses to ensure that we - // don't return unwanted addresses. - Vec::new() + fn handle_established_outbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + addr: &Multiaddr, + role_override: Endpoint, + ) -> Result, ConnectionDenied> { + let ping_handler = self.ping.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + )?; + let identify_handler = self.identify.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + )?; + Ok(ping_handler.select(identify_handler)) } fn on_swarm_event(&mut self, event: FromSwarm) { @@ -247,34 +312,39 @@ impl NetworkBehaviour for PeerInfoBehaviour { "Unknown connection to {:?} closed: {:?}", peer_id, endpoint); } }, - FromSwarm::DialFailure(DialFailure { peer_id, handler, error }) => { - let (ping_handler, identity_handler) = handler.into_inner(); + FromSwarm::DialFailure(DialFailure { peer_id, error, connection_id }) => { self.ping.on_swarm_event(FromSwarm::DialFailure(DialFailure { peer_id, - handler: ping_handler, error, + connection_id, })); self.identify.on_swarm_event(FromSwarm::DialFailure(DialFailure { peer_id, - handler: identity_handler, error, + connection_id, })); }, FromSwarm::ListenerClosed(e) => { self.ping.on_swarm_event(FromSwarm::ListenerClosed(e)); self.identify.on_swarm_event(FromSwarm::ListenerClosed(e)); }, - FromSwarm::ListenFailure(ListenFailure { local_addr, send_back_addr, handler }) => { - let (ping_handler, identity_handler) = handler.into_inner(); + FromSwarm::ListenFailure(ListenFailure { + local_addr, + send_back_addr, + error, + connection_id, + }) => { self.ping.on_swarm_event(FromSwarm::ListenFailure(ListenFailure { local_addr, send_back_addr, - handler: ping_handler, + error, + connection_id, })); self.identify.on_swarm_event(FromSwarm::ListenFailure(ListenFailure { local_addr, send_back_addr, - handler: identity_handler, + error, + connection_id, })); }, FromSwarm::ListenerError(e) => { @@ -324,13 +394,12 @@ impl NetworkBehaviour for PeerInfoBehaviour { &mut self, peer_id: PeerId, connection_id: ConnectionId, - event: <::Handler as - ConnectionHandler>::OutEvent, + event: THandlerOutEvent, ) { match event { - EitherOutput::First(event) => + Either::Left(event) => self.ping.on_connection_handler_event(peer_id, connection_id, event), - EitherOutput::Second(event) => + Either::Right(event) => self.identify.on_connection_handler_event(peer_id, connection_id, event), } } @@ -339,47 +408,37 @@ impl NetworkBehaviour for PeerInfoBehaviour { &mut self, cx: &mut Context, params: &mut impl PollParameters, - ) -> Poll> { + ) -> Poll>> { loop { match self.ping.poll(cx, params) { Poll::Pending => break, - Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) => { + Poll::Ready(ToSwarm::GenerateEvent(ev)) => { if let PingEvent { peer, result: Ok(PingSuccess::Ping { rtt }) } = ev { self.handle_ping_report(&peer, rtt) } }, - Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) => { - let handler = - IntoConnectionHandler::select(handler, self.identify.new_handler()); - return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) - }, - Poll::Ready(NetworkBehaviourAction::NotifyHandler { peer_id, handler, event }) => - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { + Poll::Ready(ToSwarm::Dial { opts }) => return Poll::Ready(ToSwarm::Dial { opts }), + Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, event }) => + return Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, - event: EitherOutput::First(event), - }), - Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { address, score }) => - return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { - address, - score, - }), - Poll::Ready(NetworkBehaviourAction::CloseConnection { peer_id, connection }) => - return Poll::Ready(NetworkBehaviourAction::CloseConnection { - peer_id, - connection, + event: Either::Left(event), }), + Poll::Ready(ToSwarm::ReportObservedAddr { address, score }) => + return Poll::Ready(ToSwarm::ReportObservedAddr { address, score }), + Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }) => + return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), } } loop { match self.identify.poll(cx, params) { Poll::Pending => break, - Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)) => match event { + Poll::Ready(ToSwarm::GenerateEvent(event)) => match event { IdentifyEvent::Received { peer_id, info, .. } => { self.handle_identify_report(&peer_id, &info); let event = PeerInfoEvent::Identified { peer_id, info }; - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(event)) + return Poll::Ready(ToSwarm::GenerateEvent(event)) }, IdentifyEvent::Error { peer_id, error } => { debug!(target: "sub-libp2p", "Identification with peer {:?} failed => {}", peer_id, error) @@ -387,26 +446,17 @@ impl NetworkBehaviour for PeerInfoBehaviour { IdentifyEvent::Pushed { .. } => {}, IdentifyEvent::Sent { .. } => {}, }, - Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) => { - let handler = IntoConnectionHandler::select(self.ping.new_handler(), handler); - return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) - }, - Poll::Ready(NetworkBehaviourAction::NotifyHandler { peer_id, handler, event }) => - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { + Poll::Ready(ToSwarm::Dial { opts }) => return Poll::Ready(ToSwarm::Dial { opts }), + Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, event }) => + return Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, - event: EitherOutput::Second(event), - }), - Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { address, score }) => - return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { - address, - score, - }), - Poll::Ready(NetworkBehaviourAction::CloseConnection { peer_id, connection }) => - return Poll::Ready(NetworkBehaviourAction::CloseConnection { - peer_id, - connection, + event: Either::Right(event), }), + Poll::Ready(ToSwarm::ReportObservedAddr { address, score }) => + return Poll::Ready(ToSwarm::ReportObservedAddr { address, score }), + Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }) => + return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), } } diff --git a/client/network/src/peer_store.rs b/client/network/src/peer_store.rs new file mode 100644 index 000000000000..59886c335784 --- /dev/null +++ b/client/network/src/peer_store.rs @@ -0,0 +1,407 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 libp2p::PeerId; +use log::trace; +use parking_lot::Mutex; +use partial_sort::PartialSort; +use sc_network_common::types::ReputationChange; +use std::{ + cmp::{Ord, Ordering, PartialOrd}, + collections::{hash_map::Entry, HashMap, HashSet}, + fmt::Debug, + sync::Arc, + time::{Duration, Instant}, +}; +use wasm_timer::Delay; + +use crate::protocol_controller::ProtocolHandle; + +/// Log target for this file. +pub const LOG_TARGET: &str = "peerset"; + +/// We don't accept nodes whose reputation is under this value. +pub const BANNED_THRESHOLD: i32 = 82 * (i32::MIN / 100); +/// Reputation change for a node when we get disconnected from it. +const DISCONNECT_REPUTATION_CHANGE: i32 = -256; +/// Relative decrement of a reputation value that is applied every second. I.e., for inverse +/// decrement of 50 we decrease absolute value of the reputation by 1/50. This corresponds to a +/// factor of `k = 0.98`. It takes ~ `ln(0.5) / ln(k)` seconds to reduce the reputation by half, +/// or 34.3 seconds for the values above. In this setup the maximum allowed absolute value of +/// `i32::MAX` becomes 0 in ~1100 seconds (actually less due to integer arithmetic). +const INVERSE_DECREMENT: i32 = 50; +/// Amount of time between the moment we last updated the [`PeerStore`] entry and the moment we +/// remove it, once the reputation value reaches 0. +const FORGET_AFTER: Duration = Duration::from_secs(3600); + +pub trait PeerStoreProvider: Debug + Send { + /// Check whether the peer is banned. + fn is_banned(&self, peer_id: &PeerId) -> bool; + + /// Register a protocol handle to disconnect peers whose reputation drops below the threshold. + fn register_protocol(&self, protocol_handle: ProtocolHandle); + + /// Report peer disconnection for reputation adjustment. + fn report_disconnect(&mut self, peer_id: PeerId); + + /// Adjust peer reputation. + fn report_peer(&mut self, peer_id: PeerId, change: ReputationChange); + + /// Get peer reputation. + fn peer_reputation(&self, peer_id: &PeerId) -> i32; + + /// Get candidates with highest reputations for initiating outgoing connections. + fn outgoing_candidates(&self, count: usize, ignored: HashSet<&PeerId>) -> Vec; +} + +#[derive(Debug, Clone)] +pub struct PeerStoreHandle { + inner: Arc>, +} + +impl PeerStoreProvider for PeerStoreHandle { + fn is_banned(&self, peer_id: &PeerId) -> bool { + self.inner.lock().is_banned(peer_id) + } + + fn register_protocol(&self, protocol_handle: ProtocolHandle) { + self.inner.lock().register_protocol(protocol_handle); + } + + fn report_disconnect(&mut self, peer_id: PeerId) { + self.inner.lock().report_disconnect(peer_id) + } + + fn report_peer(&mut self, peer_id: PeerId, change: ReputationChange) { + self.inner.lock().report_peer(peer_id, change) + } + + fn peer_reputation(&self, peer_id: &PeerId) -> i32 { + self.inner.lock().peer_reputation(peer_id) + } + + fn outgoing_candidates(&self, count: usize, ignored: HashSet<&PeerId>) -> Vec { + self.inner.lock().outgoing_candidates(count, ignored) + } +} + +impl PeerStoreHandle { + /// Get the number of known peers. + /// + /// This number might not include some connected peers in rare cases when their reputation + /// was not updated for one hour, because their entries in [`PeerStore`] were dropped. + pub fn num_known_peers(&self) -> usize { + self.inner.lock().peers.len() + } + + /// Add known peer. + pub fn add_known_peer(&mut self, peer_id: PeerId) { + self.inner.lock().add_known_peer(peer_id); + } +} + +#[derive(Debug, Clone, Copy)] +struct PeerInfo { + reputation: i32, + last_updated: Instant, +} + +impl Default for PeerInfo { + fn default() -> Self { + Self { reputation: 0, last_updated: Instant::now() } + } +} + +impl PartialEq for PeerInfo { + fn eq(&self, other: &Self) -> bool { + self.reputation == other.reputation + } +} + +impl Eq for PeerInfo {} + +impl Ord for PeerInfo { + // We define reverse order by reputation values. + fn cmp(&self, other: &Self) -> Ordering { + self.reputation.cmp(&other.reputation).reverse() + } +} + +impl PartialOrd for PeerInfo { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PeerInfo { + fn is_banned(&self) -> bool { + self.reputation < BANNED_THRESHOLD + } + + fn add_reputation(&mut self, increment: i32) { + self.reputation = self.reputation.saturating_add(increment); + self.bump_last_updated(); + } + + fn decay_reputation(&mut self, seconds_passed: u64) { + // Note that decaying the reputation value happens "on its own", + // so we don't do `bump_last_updated()`. + for _ in 0..seconds_passed { + let mut diff = self.reputation / INVERSE_DECREMENT; + if diff == 0 && self.reputation < 0 { + diff = -1; + } else if diff == 0 && self.reputation > 0 { + diff = 1; + } + + self.reputation = self.reputation.saturating_sub(diff); + + if self.reputation == 0 { + break + } + } + } + + fn bump_last_updated(&mut self) { + self.last_updated = Instant::now(); + } +} + +#[derive(Debug)] +struct PeerStoreInner { + peers: HashMap, + protocols: Vec, +} + +impl PeerStoreInner { + fn is_banned(&self, peer_id: &PeerId) -> bool { + self.peers.get(peer_id).map_or(false, |info| info.is_banned()) + } + + fn register_protocol(&mut self, protocol_handle: ProtocolHandle) { + self.protocols.push(protocol_handle); + } + + fn report_disconnect(&mut self, peer_id: PeerId) { + let peer_info = self.peers.entry(peer_id).or_default(); + peer_info.add_reputation(DISCONNECT_REPUTATION_CHANGE); + + log::trace!( + target: LOG_TARGET, + "Peer {} disconnected, reputation: {:+} to {}", + peer_id, + DISCONNECT_REPUTATION_CHANGE, + peer_info.reputation, + ); + } + + fn report_peer(&mut self, peer_id: PeerId, change: ReputationChange) { + let peer_info = self.peers.entry(peer_id).or_default(); + peer_info.add_reputation(change.value); + + if peer_info.reputation < BANNED_THRESHOLD { + self.protocols.iter().for_each(|handle| handle.disconnect_peer(peer_id)); + + log::trace!( + target: LOG_TARGET, + "Report {}: {:+} to {}. Reason: {}. Banned, disconnecting.", + peer_id, + change.value, + peer_info.reputation, + change.reason, + ); + } else { + log::trace!( + target: LOG_TARGET, + "Report {}: {:+} to {}. Reason: {}.", + peer_id, + change.value, + peer_info.reputation, + change.reason, + ); + } + } + + fn peer_reputation(&self, peer_id: &PeerId) -> i32 { + self.peers.get(peer_id).map_or(0, |info| info.reputation) + } + + fn outgoing_candidates(&self, count: usize, ignored: HashSet<&PeerId>) -> Vec { + let mut candidates = self + .peers + .iter() + .filter_map(|(peer_id, info)| { + (!info.is_banned() && !ignored.contains(peer_id)).then_some((*peer_id, *info)) + }) + .collect::>(); + let count = std::cmp::min(count, candidates.len()); + candidates.partial_sort(count, |(_, info1), (_, info2)| info1.cmp(info2)); + candidates.iter().take(count).map(|(peer_id, _)| *peer_id).collect() + + // TODO: keep the peers sorted (in a "bi-multi-map"?) to not repeat sorting every time. + } + + fn progress_time(&mut self, seconds_passed: u64) { + if seconds_passed == 0 { + return + } + + // Drive reputation values towards 0. + self.peers + .iter_mut() + .for_each(|(_, info)| info.decay_reputation(seconds_passed)); + + // Retain only entries with non-zero reputation values or not expired ones. + let now = Instant::now(); + self.peers + .retain(|_, info| info.reputation != 0 || info.last_updated + FORGET_AFTER > now); + } + + fn add_known_peer(&mut self, peer_id: PeerId) { + match self.peers.entry(peer_id) { + Entry::Occupied(mut e) => { + trace!( + target: LOG_TARGET, + "Trying to add an already known peer {peer_id}, bumping `last_updated`.", + ); + e.get_mut().bump_last_updated(); + }, + Entry::Vacant(e) => { + trace!(target: LOG_TARGET, "Adding a new known peer {peer_id}."); + e.insert(PeerInfo::default()); + }, + } + } +} + +#[derive(Debug)] +pub struct PeerStore { + inner: Arc>, +} + +impl PeerStore { + /// Create a new peer store from the list of bootnodes. + pub fn new(bootnodes: Vec) -> Self { + PeerStore { + inner: Arc::new(Mutex::new(PeerStoreInner { + peers: bootnodes + .into_iter() + .map(|peer_id| (peer_id, PeerInfo::default())) + .collect(), + protocols: Vec::new(), + })), + } + } + + /// Get `PeerStoreHandle`. + pub fn handle(&self) -> PeerStoreHandle { + PeerStoreHandle { inner: self.inner.clone() } + } + + /// Drive the `PeerStore`, decaying reputation values over time and removing expired entries. + pub async fn run(self) { + let started = Instant::now(); + let mut latest_time_update = started; + + loop { + let now = Instant::now(); + // We basically do `(now - self.latest_update).as_secs()`, except that by the way we do + // it we know that we're not going to miss seconds because of rounding to integers. + let seconds_passed = { + let elapsed_latest = latest_time_update - started; + let elapsed_now = now - started; + latest_time_update = now; + elapsed_now.as_secs() - elapsed_latest.as_secs() + }; + + self.inner.lock().progress_time(seconds_passed); + let _ = Delay::new(Duration::from_secs(1)).await; + } + } +} + +#[cfg(test)] +mod tests { + use super::PeerInfo; + + #[test] + fn decaying_zero_reputation_yields_zero() { + let mut peer_info = PeerInfo::default(); + assert_eq!(peer_info.reputation, 0); + + peer_info.decay_reputation(1); + assert_eq!(peer_info.reputation, 0); + + peer_info.decay_reputation(100_000); + assert_eq!(peer_info.reputation, 0); + } + + #[test] + fn decaying_positive_reputation_decreases_it() { + const INITIAL_REPUTATION: i32 = 100; + + let mut peer_info = PeerInfo::default(); + peer_info.reputation = INITIAL_REPUTATION; + + peer_info.decay_reputation(1); + assert!(peer_info.reputation >= 0); + assert!(peer_info.reputation < INITIAL_REPUTATION); + } + + #[test] + fn decaying_negative_reputation_increases_it() { + const INITIAL_REPUTATION: i32 = -100; + + let mut peer_info = PeerInfo::default(); + peer_info.reputation = INITIAL_REPUTATION; + + peer_info.decay_reputation(1); + assert!(peer_info.reputation <= 0); + assert!(peer_info.reputation > INITIAL_REPUTATION); + } + + #[test] + fn decaying_max_reputation_finally_yields_zero() { + const INITIAL_REPUTATION: i32 = i32::MAX; + const SECONDS: u64 = 1000; + + let mut peer_info = PeerInfo::default(); + peer_info.reputation = INITIAL_REPUTATION; + + peer_info.decay_reputation(SECONDS / 2); + assert!(peer_info.reputation > 0); + + peer_info.decay_reputation(SECONDS / 2); + assert_eq!(peer_info.reputation, 0); + } + + #[test] + fn decaying_min_reputation_finally_yields_zero() { + const INITIAL_REPUTATION: i32 = i32::MIN; + const SECONDS: u64 = 1000; + + let mut peer_info = PeerInfo::default(); + peer_info.reputation = INITIAL_REPUTATION; + + peer_info.decay_reputation(SECONDS / 2); + assert!(peer_info.reputation < 0); + + peer_info.decay_reputation(SECONDS / 2); + assert_eq!(peer_info.reputation, 0); + } +} diff --git a/client/network/src/peerset.rs b/client/network/src/peerset.rs new file mode 100644 index 000000000000..fd57175dd77c --- /dev/null +++ b/client/network/src/peerset.rs @@ -0,0 +1,394 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +//! Peer Set Manager (PSM). Contains the strategy for choosing which nodes the network should be +//! connected to. +//! +//! The PSM handles *sets* of nodes. A set of nodes is defined as the nodes that are believed to +//! support a certain capability, such as handling blocks and transactions of a specific chain, +//! or collating a certain parachain. +//! +//! For each node in each set, the peerset holds a flag specifying whether the node is +//! connected to us or not. +//! +//! This connected/disconnected status is specific to the node and set combination, and it is for +//! example possible for a node to be connected through a specific set but not another. +//! +//! In addition, for each, set, the peerset also holds a list of reserved nodes towards which it +//! will at all time try to maintain a connection with. + +use crate::{ + peer_store::{PeerStore, PeerStoreHandle, PeerStoreProvider}, + protocol_controller::{ProtocolController, ProtocolHandle}, +}; + +use futures::{ + channel::oneshot, + future::{join_all, BoxFuture, JoinAll}, + prelude::*, + stream::Stream, +}; +use log::debug; +use sc_network_common::types::ReputationChange; +use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; +use serde_json::json; +use std::{ + collections::HashSet, + pin::Pin, + task::{Context, Poll}, +}; + +use libp2p::PeerId; + +pub const LOG_TARGET: &str = "peerset"; + +#[derive(Debug)] +enum Action { + AddReservedPeer(SetId, PeerId), + RemoveReservedPeer(SetId, PeerId), + SetReservedPeers(SetId, HashSet), + SetReservedOnly(SetId, bool), + ReportPeer(PeerId, ReputationChange), + AddKnownPeer(PeerId), + PeerReputation(PeerId, oneshot::Sender), +} + +/// Identifier of a set in the peerset. +/// +/// Can be constructed using the `From` trait implementation based on the index of the set +/// within [`PeersetConfig::sets`]. For example, the first element of [`PeersetConfig::sets`] is +/// later referred to with `SetId::from(0)`. It is intended that the code responsible for building +/// the [`PeersetConfig`] is also responsible for constructing the [`SetId`]s. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct SetId(usize); + +impl SetId { + pub const fn from(id: usize) -> Self { + Self(id) + } +} + +impl From for SetId { + fn from(id: usize) -> Self { + Self(id) + } +} + +impl From for usize { + fn from(id: SetId) -> Self { + id.0 + } +} + +/// Shared handle to the peer set manager (PSM). Distributed around the code. +#[derive(Debug, Clone)] +pub struct PeersetHandle { + tx: TracingUnboundedSender, +} + +impl PeersetHandle { + /// Adds a new reserved peer. The peerset will make an effort to always remain connected to + /// this peer. + /// + /// Has no effect if the node was already a reserved peer. + /// + /// > **Note**: Keep in mind that the networking has to know an address for this node, + /// > otherwise it will not be able to connect to it. + pub fn add_reserved_peer(&self, set_id: SetId, peer_id: PeerId) { + let _ = self.tx.unbounded_send(Action::AddReservedPeer(set_id, peer_id)); + } + + /// Remove a previously-added reserved peer. + /// + /// Has no effect if the node was not a reserved peer. + pub fn remove_reserved_peer(&self, set_id: SetId, peer_id: PeerId) { + let _ = self.tx.unbounded_send(Action::RemoveReservedPeer(set_id, peer_id)); + } + + /// Sets whether or not the peerset only has connections with nodes marked as reserved for + /// the given set. + pub fn set_reserved_only(&self, set_id: SetId, reserved: bool) { + let _ = self.tx.unbounded_send(Action::SetReservedOnly(set_id, reserved)); + } + + /// Set reserved peers to the new set. + pub fn set_reserved_peers(&self, set_id: SetId, peer_ids: HashSet) { + let _ = self.tx.unbounded_send(Action::SetReservedPeers(set_id, peer_ids)); + } + + /// Reports an adjustment to the reputation of the given peer. + pub fn report_peer(&self, peer_id: PeerId, score_diff: ReputationChange) { + let _ = self.tx.unbounded_send(Action::ReportPeer(peer_id, score_diff)); + } + + /// Add a peer to the list of known peers. + pub fn add_known_peer(&self, peer_id: PeerId) { + let _ = self.tx.unbounded_send(Action::AddKnownPeer(peer_id)); + } + + /// Returns the reputation value of the peer. + pub async fn peer_reputation(self, peer_id: PeerId) -> Result { + let (tx, rx) = oneshot::channel(); + + let _ = self.tx.unbounded_send(Action::PeerReputation(peer_id, tx)); + + // The channel can only be closed if the peerset no longer exists. + rx.await.map_err(|_| ()) + } +} + +/// Message that can be sent by the peer set manager (PSM). +#[derive(Debug, PartialEq)] +pub enum Message { + /// Request to open a connection to the given peer. From the point of view of the PSM, we are + /// immediately connected. + Connect { + /// Set id to connect on. + set_id: SetId, + /// Peer to connect to. + peer_id: PeerId, + }, + + /// Drop the connection to the given peer, or cancel the connection attempt after a `Connect`. + Drop { + /// Set id to disconnect on. + set_id: SetId, + /// Peer to disconnect from. + peer_id: PeerId, + }, + + /// Equivalent to `Connect` for the peer corresponding to this incoming index. + Accept(IncomingIndex), + + /// Equivalent to `Drop` for the peer corresponding to this incoming index. + Reject(IncomingIndex), +} + +/// Opaque identifier for an incoming connection. Allocated by the network. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct IncomingIndex(pub u64); + +impl From for IncomingIndex { + fn from(val: u64) -> Self { + Self(val) + } +} + +/// Configuration to pass when creating the peer set manager. +#[derive(Debug)] +pub struct PeersetConfig { + /// List of sets of nodes the peerset manages. + pub sets: Vec, +} + +/// Configuration for a single set of nodes. +#[derive(Debug)] +pub struct SetConfig { + /// Maximum number of ingoing links to peers. + pub in_peers: u32, + + /// Maximum number of outgoing links to peers. + pub out_peers: u32, + + /// List of bootstrap nodes to initialize the set with. + /// + /// > **Note**: Keep in mind that the networking has to know an address for these nodes, + /// > otherwise it will not be able to connect to them. + pub bootnodes: Vec, + + /// Lists of nodes we should always be connected to. + /// + /// > **Note**: Keep in mind that the networking has to know an address for these nodes, + /// > otherwise it will not be able to connect to them. + pub reserved_nodes: HashSet, + + /// If true, we only accept nodes in [`SetConfig::reserved_nodes`]. + pub reserved_only: bool, +} + +/// Side of the peer set manager owned by the network. In other words, the "receiving" side. +/// +/// Implements the `Stream` trait and can be polled for messages. The `Stream` never ends and never +/// errors. +pub struct Peerset { + /// Peer reputation store handle. + peer_store_handle: PeerStoreHandle, + /// Peer reputation store. + peer_store_future: BoxFuture<'static, ()>, + /// Protocol handles. + protocol_handles: Vec, + /// Protocol controllers responsible for connections, per `SetId`. + protocol_controller_futures: JoinAll>, + /// Commands sent from protocol controllers to `Notifications`. The size of this vector never + /// changes. + from_controllers: TracingUnboundedReceiver, + /// Receiver for messages from the `PeersetHandle` and from `to_self`. + from_handle: TracingUnboundedReceiver, +} + +impl Peerset { + /// Builds a new peerset from the given configuration. + pub fn from_config(config: PeersetConfig) -> (Peerset, PeersetHandle) { + let default_set_config = &config.sets[0]; + let peer_store = PeerStore::new(default_set_config.bootnodes.clone()); + + let (to_notifications, from_controllers) = + tracing_unbounded("mpsc_protocol_controllers_to_notifications", 10_000); + + let controllers = config + .sets + .into_iter() + .enumerate() + .map(|(set, set_config)| { + ProtocolController::new( + SetId::from(set), + set_config, + to_notifications.clone(), + Box::new(peer_store.handle()), + ) + }) + .collect::>(); + + let (protocol_handles, protocol_controllers): (Vec, Vec<_>) = + controllers.into_iter().unzip(); + + let (tx, from_handle) = tracing_unbounded("mpsc_peerset_messages", 10_000); + + let handle = PeersetHandle { tx }; + + let protocol_controller_futures = + join_all(protocol_controllers.into_iter().map(|c| c.run().boxed())); + + let peerset = Peerset { + peer_store_handle: peer_store.handle(), + peer_store_future: peer_store.run().boxed(), + protocol_handles, + protocol_controller_futures, + from_controllers, + from_handle, + }; + + (peerset, handle) + } + + /// Returns the list of reserved peers. + pub fn reserved_peers(&self, set_id: SetId, pending_response: oneshot::Sender>) { + self.protocol_handles[set_id.0].reserved_peers(pending_response); + } + + /// Indicate that we received an incoming connection. Must be answered either with + /// a corresponding `Accept` or `Reject`, except if we were already connected to this peer. + /// + /// Note that this mechanism is orthogonal to `Connect`/`Drop`. Accepting an incoming + /// connection implicitly means `Connect`, but incoming connections aren't cancelled by + /// `dropped`. + // Implementation note: because of concurrency issues, it is possible that we push a `Connect` + // message to the output channel with a `PeerId`, and that `incoming` gets called with the same + // `PeerId` before that message has been read by the user. In this situation we must not answer. + pub fn incoming(&mut self, set_id: SetId, peer_id: PeerId, index: IncomingIndex) { + self.protocol_handles[set_id.0].incoming_connection(peer_id, index); + } + + /// Indicate that we dropped an active connection with a peer, or that we failed to connect. + /// + /// Must only be called after the PSM has either generated a `Connect` message with this + /// `PeerId`, or accepted an incoming connection with this `PeerId`. + pub fn dropped(&mut self, set_id: SetId, peer_id: PeerId, _reason: DropReason) { + self.protocol_handles[set_id.0].dropped(peer_id); + } + + /// Produces a JSON object containing the state of the peerset manager, for debugging purposes. + pub fn debug_info(&mut self) -> serde_json::Value { + // TODO: Check what info we can include here. + // Issue reference: https://github.com/paritytech/substrate/issues/14160. + json!("unimplemented") + } + + /// Returns the number of peers that we have discovered. + pub fn num_discovered_peers(&self) -> usize { + self.peer_store_handle.num_known_peers() + } +} + +impl Stream for Peerset { + type Item = Message; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { + if let Poll::Ready(msg) = self.from_controllers.poll_next_unpin(cx) { + if let Some(msg) = msg { + return Poll::Ready(Some(msg)) + } else { + debug!( + target: LOG_TARGET, + "All `ProtocolController`s have terminated, terminating `Peerset`." + ); + return Poll::Ready(None) + } + } + + while let Poll::Ready(action) = self.from_handle.poll_next_unpin(cx) { + if let Some(action) = action { + match action { + Action::AddReservedPeer(set_id, peer_id) => + self.protocol_handles[set_id.0].add_reserved_peer(peer_id), + Action::RemoveReservedPeer(set_id, peer_id) => + self.protocol_handles[set_id.0].remove_reserved_peer(peer_id), + Action::SetReservedPeers(set_id, peer_ids) => + self.protocol_handles[set_id.0].set_reserved_peers(peer_ids), + Action::SetReservedOnly(set_id, reserved_only) => + self.protocol_handles[set_id.0].set_reserved_only(reserved_only), + Action::ReportPeer(peer_id, score_diff) => + self.peer_store_handle.report_peer(peer_id, score_diff), + Action::AddKnownPeer(peer_id) => self.peer_store_handle.add_known_peer(peer_id), + Action::PeerReputation(peer_id, pending_response) => { + let _ = + pending_response.send(self.peer_store_handle.peer_reputation(&peer_id)); + }, + } + } else { + debug!(target: LOG_TARGET, "`PeersetHandle` was dropped, terminating `Peerset`."); + return Poll::Ready(None) + } + } + + if let Poll::Ready(()) = self.peer_store_future.poll_unpin(cx) { + debug!(target: LOG_TARGET, "`PeerStore` has terminated, terminating `PeerSet`."); + return Poll::Ready(None) + } + + if let Poll::Ready(_) = self.protocol_controller_futures.poll_unpin(cx) { + debug!( + target: LOG_TARGET, + "All `ProtocolHandle`s have terminated, terminating `PeerSet`." + ); + return Poll::Ready(None) + } + + Poll::Pending + } +} + +/// Reason for calling [`Peerset::dropped`]. +#[derive(Debug)] +pub enum DropReason { + /// Substream or connection has been closed for an unknown reason. + Unknown, + /// Substream or connection has been explicitly refused by the target. In other words, the + /// peer doesn't actually belong to this set. + Refused, +} diff --git a/client/network/src/protocol.rs b/client/network/src/protocol.rs index cd232334e3b6..e57bc3e520be 100644 --- a/client/network/src/protocol.rs +++ b/client/network/src/protocol.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,153 +16,70 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::config; +use crate::{ + config::{self, NonReservedPeerMode}, + error, + types::ProtocolName, + ReputationChange, +}; use bytes::Bytes; -use codec::{Decode, DecodeAll, Encode}; -use futures::prelude::*; +use codec::{DecodeAll, Encode}; +use futures::{channel::oneshot, stream::FuturesUnordered, StreamExt}; use libp2p::{ - core::connection::ConnectionId, + core::Endpoint, swarm::{ - behaviour::FromSwarm, ConnectionHandler, IntoConnectionHandler, NetworkBehaviour, - NetworkBehaviourAction, PollParameters, + behaviour::FromSwarm, ConnectionDenied, ConnectionId, NetworkBehaviour, PollParameters, + THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }, Multiaddr, PeerId, }; -use log::{debug, error, log, trace, warn, Level}; -use lru::LruCache; -use message::{generic::Message as GenericMessage, Message}; -use notifications::{Notifications, NotificationsOut}; -use prometheus_endpoint::{register, Gauge, GaugeVec, Opts, PrometheusError, Registry, U64}; -use sc_client_api::HeaderBackend; -use sc_network_common::{ - config::NonReservedPeerMode, - error, - protocol::{role::Roles, ProtocolName}, - sync::{ - message::{BlockAnnounce, BlockAnnouncesHandshake, BlockData, BlockResponse, BlockState}, - BadPeer, ChainSync, PollBlockAnnounceValidation, SyncStatus, - }, - utils::{interval, LruHashSet}, -}; -use sp_arithmetic::traits::SaturatedConversion; -use sp_runtime::traits::{Block as BlockT, CheckedSub, Header as HeaderT, NumberFor, Zero}; +use log::{debug, error, warn}; + +use sc_network_common::{role::Roles, sync::message::BlockAnnouncesHandshake}; +use sc_utils::mpsc::TracingUnboundedSender; +use sp_runtime::traits::Block as BlockT; + use std::{ - collections::{HashMap, HashSet, VecDeque}, + collections::{HashMap, HashSet}, + future::Future, iter, - num::NonZeroUsize, pin::Pin, - sync::Arc, task::Poll, - time, }; -mod notifications; - -pub mod message; +use message::{generic::Message as GenericMessage, Message}; +use notifications::{Notifications, NotificationsOut}; pub use notifications::{NotificationsSink, NotifsHandlerError, Ready}; -/// Interval at which we perform time based maintenance -const TICK_TIMEOUT: time::Duration = time::Duration::from_millis(1100); +mod notifications; -/// Maximum number of known block hashes to keep for a peer. -const MAX_KNOWN_BLOCKS: usize = 1024; // ~32kb per peer + LruHashSet overhead +pub mod message; /// Maximum size used for notifications in the block announce and transaction protocols. // Must be equal to `max(MAX_BLOCK_ANNOUNCE_SIZE, MAX_TRANSACTIONS_SIZE)`. pub(crate) const BLOCK_ANNOUNCES_TRANSACTIONS_SUBSTREAM_SIZE: u64 = 16 * 1024 * 1024; /// Identifier of the peerset for the block announces protocol. -const HARDCODED_PEERSETS_SYNC: sc_peerset::SetId = sc_peerset::SetId::from(0); +const HARDCODED_PEERSETS_SYNC: crate::peerset::SetId = crate::peerset::SetId::from(0); /// Number of hardcoded peersets (the constants right above). Any set whose identifier is equal or /// superior to this value corresponds to a user-defined protocol. const NUM_HARDCODED_PEERSETS: usize = 1; -/// When light node connects to the full node and the full node is behind light node -/// for at least `LIGHT_MAXIMAL_BLOCKS_DIFFERENCE` blocks, we consider it not useful -/// and disconnect to free connection slot. -const LIGHT_MAXIMAL_BLOCKS_DIFFERENCE: u64 = 8192; - mod rep { - use sc_peerset::ReputationChange as Rep; - /// Reputation change when we are a light client and a peer is behind us. - pub const PEER_BEHIND_US_LIGHT: Rep = Rep::new(-(1 << 8), "Useless for a light peer"); + use crate::ReputationChange as Rep; /// We received a message that failed to decode. pub const BAD_MESSAGE: Rep = Rep::new(-(1 << 12), "Bad message"); - /// Peer has different genesis. - pub const GENESIS_MISMATCH: Rep = Rep::new_fatal("Genesis mismatch"); - /// Peer role does not match (e.g. light peer connecting to another light peer). - pub const BAD_ROLE: Rep = Rep::new_fatal("Unsupported role"); - /// Peer send us a block announcement that failed at validation. - pub const BAD_BLOCK_ANNOUNCEMENT: Rep = Rep::new(-(1 << 12), "Bad block announcement"); -} - -struct Metrics { - peers: Gauge, - queued_blocks: Gauge, - fork_targets: Gauge, - justifications: GaugeVec, } -impl Metrics { - fn register(r: &Registry) -> Result { - Ok(Self { - peers: { - let g = Gauge::new("substrate_sync_peers", "Number of peers we sync with")?; - register(g, r)? - }, - queued_blocks: { - let g = - Gauge::new("substrate_sync_queued_blocks", "Number of blocks in import queue")?; - register(g, r)? - }, - fork_targets: { - let g = Gauge::new("substrate_sync_fork_targets", "Number of fork sync targets")?; - register(g, r)? - }, - justifications: { - let g = GaugeVec::new( - Opts::new( - "substrate_sync_extra_justifications", - "Number of extra justifications requests", - ), - &["status"], - )?; - register(g, r)? - }, - }) - } -} +type PendingSyncSubstreamValidation = + Pin> + Send>>; // Lock must always be taken in order declared here. -pub struct Protocol { - /// Interval at which we call `tick`. - tick_timeout: Pin + Send>>, - /// Pending list of messages to return from `poll` as a priority. - pending_messages: VecDeque>, - /// Assigned roles. - roles: Roles, - genesis_hash: B::Hash, - /// State machine that handles the list of in-progress requests. Only full node peers are - /// registered. - chain_sync: Box>, - // All connected peers. Contains both full and light node peers. - peers: HashMap>, - chain: Arc, - /// List of nodes for which we perform additional logging because they are important for the - /// user. - important_peers: HashSet, - /// List of nodes that should never occupy peer slots. - default_peers_set_no_slot_peers: HashSet, - /// Actual list of connected no-slot nodes. - default_peers_set_no_slot_connected_peers: HashSet, - /// Value that was passed as part of the configuration. Used to cap the number of full nodes. - default_peers_set_num_full: usize, - /// Number of slots to allocate to light nodes. - default_peers_set_num_light: usize, +pub struct Protocol { /// Used to report reputation changes. - peerset_handle: sc_peerset::PeersetHandle, + peerset_handle: crate::peerset::PeersetHandle, /// Handles opening the unique substream and sending and receiving raw messages. behaviour: Notifications, /// List of notifications protocols that have been registered. @@ -173,91 +90,28 @@ pub struct Protocol { /// event to the outer layers, we also shouldn't propagate this "substream closed" event. To /// solve this, an entry is added to this map whenever an invalid handshake is received. /// Entries are removed when the corresponding "substream closed" is later received. - bad_handshake_substreams: HashSet<(PeerId, sc_peerset::SetId)>, - /// Prometheus metrics. - metrics: Option, - /// The `PeerId`'s of all boot nodes. - boot_node_ids: HashSet, - /// A cache for the data that was associated to a block announcement. - block_announce_data_cache: LruCache>, -} - -/// Peer information -#[derive(Debug)] -struct Peer { - info: PeerInfo, - /// Holds a set of blocks known to this peer. - known_blocks: LruHashSet, + bad_handshake_substreams: HashSet<(PeerId, crate::peerset::SetId)>, + /// Connected peers. + peers: HashMap, + sync_substream_validations: FuturesUnordered, + tx: TracingUnboundedSender>, + _marker: std::marker::PhantomData, } -/// Info about a peer's known state. -#[derive(Clone, Debug)] -pub struct PeerInfo { - /// Roles - pub roles: Roles, - /// Peer best block hash - pub best_hash: B::Hash, - /// Peer best block number - pub best_number: ::Number, -} - -impl Protocol -where - B: BlockT, - Client: HeaderBackend + 'static, -{ +impl Protocol { /// Create a new instance. pub fn new( roles: Roles, - chain: Arc, network_config: &config::NetworkConfiguration, - metrics_registry: Option<&Registry>, - chain_sync: Box>, - block_announces_protocol: sc_network_common::config::NonDefaultSetConfig, - ) -> error::Result<(Self, sc_peerset::PeersetHandle, Vec<(PeerId, Multiaddr)>)> { - let info = chain.info(); - - let boot_node_ids = { - let mut list = HashSet::new(); - for node in &network_config.boot_nodes { - list.insert(node.peer_id); - } - list.shrink_to_fit(); - list - }; - - let important_peers = { - let mut imp_p = HashSet::new(); - for reserved in &network_config.default_peers_set.reserved_nodes { - imp_p.insert(reserved.peer_id); - } - for reserved in network_config - .extra_sets - .iter() - .flat_map(|s| s.set_config.reserved_nodes.iter()) - { - imp_p.insert(reserved.peer_id); - } - imp_p.shrink_to_fit(); - imp_p - }; - - let default_peers_set_no_slot_peers = { - let mut no_slot_p: HashSet = network_config - .default_peers_set - .reserved_nodes - .iter() - .map(|reserved| reserved.peer_id) - .collect(); - no_slot_p.shrink_to_fit(); - no_slot_p - }; - + notification_protocols: Vec, + block_announces_protocol: config::NonDefaultSetConfig, + tx: TracingUnboundedSender>, + ) -> error::Result<(Self, crate::peerset::PeersetHandle, Vec<(PeerId, Multiaddr)>)> { let mut known_addresses = Vec::new(); let (peerset, peerset_handle) = { let mut sets = - Vec::with_capacity(NUM_HARDCODED_PEERSETS + network_config.extra_sets.len()); + Vec::with_capacity(NUM_HARDCODED_PEERSETS + notification_protocols.len()); let mut default_sets_reserved = HashSet::new(); for reserved in network_config.default_peers_set.reserved_nodes.iter() { @@ -274,7 +128,7 @@ where } // Set number 0 is used for block announces. - sets.push(sc_peerset::SetConfig { + sets.push(crate::peerset::SetConfig { in_peers: network_config.default_peers_set.in_peers, out_peers: network_config.default_peers_set.out_peers, bootnodes, @@ -283,7 +137,7 @@ where NonReservedPeerMode::Deny, }); - for set_cfg in &network_config.extra_sets { + for set_cfg in ¬ification_protocols { let mut reserved_nodes = HashSet::new(); for reserved in set_cfg.set_config.reserved_nodes.iter() { reserved_nodes.insert(reserved.peer_id); @@ -293,7 +147,7 @@ where let reserved_only = set_cfg.set_config.non_reserved_mode == NonReservedPeerMode::Deny; - sets.push(sc_peerset::SetConfig { + sets.push(crate::peerset::SetConfig { in_peers: set_cfg.set_config.in_peers, out_peers: set_cfg.set_config.out_peers, bootnodes: Vec::new(), @@ -302,7 +156,7 @@ where }); } - sc_peerset::Peerset::from_config(sc_peerset::PeersetConfig { sets }) + crate::peerset::Peerset::from_config(crate::peerset::PeersetConfig { sets }) }; let behaviour = { @@ -317,7 +171,7 @@ where handshake: block_announces_protocol.handshake.as_ref().unwrap().to_vec(), max_notification_size: block_announces_protocol.max_notification_size, }) - .chain(network_config.extra_sets.iter().map(|s| notifications::ProtocolConfig { + .chain(notification_protocols.iter().map(|s| notifications::ProtocolConfig { name: s.notifications_protocol.clone(), fallback_names: s.fallback_names.clone(), handshake: s.handshake.as_ref().map_or(roles.encode(), |h| (*h).to_vec()), @@ -326,44 +180,18 @@ where ) }; - let cache_capacity = NonZeroUsize::new( - (network_config.default_peers_set.in_peers as usize + - network_config.default_peers_set.out_peers as usize) - .max(1), - ) - .expect("cache capacity is not zero"); - let block_announce_data_cache = LruCache::new(cache_capacity); - let protocol = Self { - tick_timeout: Box::pin(interval(TICK_TIMEOUT)), - pending_messages: VecDeque::new(), - roles, - peers: HashMap::new(), - chain, - genesis_hash: info.genesis_hash, - chain_sync, - important_peers, - default_peers_set_no_slot_peers, - default_peers_set_no_slot_connected_peers: HashSet::new(), - default_peers_set_num_full: network_config.default_peers_set_num_full as usize, - default_peers_set_num_light: { - let total = network_config.default_peers_set.out_peers + - network_config.default_peers_set.in_peers; - total.saturating_sub(network_config.default_peers_set_num_full) as usize - }, peerset_handle: peerset_handle.clone(), behaviour, notification_protocols: iter::once(block_announces_protocol.notifications_protocol) - .chain(network_config.extra_sets.iter().map(|s| s.notifications_protocol.clone())) + .chain(notification_protocols.iter().map(|s| s.notifications_protocol.clone())) .collect(), bad_handshake_substreams: Default::default(), - metrics: if let Some(r) = metrics_registry { - Some(Metrics::register(r)?) - } else { - None - }, - boot_node_ids, - block_announce_data_cache, + peers: HashMap::new(), + sync_substream_validations: FuturesUnordered::new(), + tx, + // TODO: remove when `BlockAnnouncesHandshake` is moved away from `Protocol` + _marker: Default::default(), }; Ok((protocol, peerset_handle, known_addresses)) @@ -383,7 +211,8 @@ where pub fn disconnect_peer(&mut self, peer_id: &PeerId, protocol_name: ProtocolName) { if let Some(position) = self.notification_protocols.iter().position(|p| *p == protocol_name) { - self.behaviour.disconnect_peer(peer_id, sc_peerset::SetId::from(position)); + self.behaviour.disconnect_peer(peer_id, crate::peerset::SetId::from(position)); + self.peers.remove(peer_id); } else { warn!(target: "sub-libp2p", "disconnect_peer() with invalid protocol name") } @@ -399,391 +228,23 @@ where self.peers.len() } - /// Returns the number of peers we're connected to and that are being queried. - pub fn num_active_peers(&self) -> usize { - self.chain_sync.num_active_peers() - } - - /// Current global sync state. - pub fn sync_state(&self) -> SyncStatus { - self.chain_sync.status() - } - - /// Target sync block number. - pub fn best_seen_block(&self) -> Option> { - self.chain_sync.status().best_seen_block - } - - /// Number of peers participating in syncing. - pub fn num_sync_peers(&self) -> u32 { - self.chain_sync.status().num_peers - } - - /// Number of blocks in the import queue. - pub fn num_queued_blocks(&self) -> u32 { - self.chain_sync.status().queued_blocks - } - - /// Number of downloaded blocks. - pub fn num_downloaded_blocks(&self) -> usize { - self.chain_sync.num_downloaded_blocks() - } - - /// Number of active sync requests. - pub fn num_sync_requests(&self) -> usize { - self.chain_sync.num_sync_requests() - } - - /// Inform sync about new best imported block. - pub fn new_best_block_imported(&mut self, hash: B::Hash, number: NumberFor) { - debug!(target: "sync", "New best block imported {:?}/#{}", hash, number); - - self.chain_sync.update_chain_info(&hash, number); - - self.behaviour.set_notif_protocol_handshake( - HARDCODED_PEERSETS_SYNC, - BlockAnnouncesHandshake::::build(self.roles, number, hash, self.genesis_hash) - .encode(), - ); - } - - fn update_peer_info(&mut self, who: &PeerId) { - if let Some(info) = self.chain_sync.peer_info(who) { - if let Some(ref mut peer) = self.peers.get_mut(who) { - peer.info.best_hash = info.best_hash; - peer.info.best_number = info.best_number; - } - } - } - - /// Returns information about all the peers we are connected to after the handshake message. - pub fn peers_info(&self) -> impl Iterator)> { - self.peers.iter().map(|(id, peer)| (id, &peer.info)) - } - - /// Called by peer when it is disconnecting. - /// - /// Returns a result if the handshake of this peer was indeed accepted. - pub fn on_sync_peer_disconnected(&mut self, peer: PeerId) -> Result<(), ()> { - if self.important_peers.contains(&peer) { - warn!(target: "sync", "Reserved peer {} disconnected", peer); - } else { - debug!(target: "sync", "{} disconnected", peer); - } - - if let Some(_peer_data) = self.peers.remove(&peer) { - self.chain_sync.peer_disconnected(&peer); - self.default_peers_set_no_slot_connected_peers.remove(&peer); - Ok(()) - } else { - Err(()) - } - } - /// Adjusts the reputation of a node. - pub fn report_peer(&self, who: PeerId, reputation: sc_peerset::ReputationChange) { + pub fn report_peer(&self, who: PeerId, reputation: ReputationChange) { self.peerset_handle.report_peer(who, reputation) } - /// Perform time based maintenance. - /// - /// > **Note**: This method normally doesn't have to be called except for testing purposes. - pub fn tick(&mut self) { - self.report_metrics() - } - - /// Called on the first connection between two peers on the default set, after their exchange - /// of handshake. - /// - /// Returns `Ok` if the handshake is accepted and the peer added to the list of peers we sync - /// from. - fn on_sync_peer_connected( - &mut self, - who: PeerId, - status: BlockAnnouncesHandshake, - ) -> Result<(), ()> { - trace!(target: "sync", "New peer {} {:?}", who, status); - - if self.peers.contains_key(&who) { - error!(target: "sync", "Called on_sync_peer_connected with already connected peer {}", who); - debug_assert!(false); - return Err(()) - } - - if status.genesis_hash != self.genesis_hash { - log!( - target: "sync", - if self.important_peers.contains(&who) { Level::Warn } else { Level::Debug }, - "Peer is on different chain (our genesis: {} theirs: {})", - self.genesis_hash, status.genesis_hash - ); - self.peerset_handle.report_peer(who, rep::GENESIS_MISMATCH); - self.behaviour.disconnect_peer(&who, HARDCODED_PEERSETS_SYNC); - - if self.boot_node_ids.contains(&who) { - error!( - target: "sync", - "Bootnode with peer id `{}` is on a different chain (our genesis: {} theirs: {})", - who, - self.genesis_hash, - status.genesis_hash, - ); - } - - return Err(()) - } - - if self.roles.is_light() { - // we're not interested in light peers - if status.roles.is_light() { - debug!(target: "sync", "Peer {} is unable to serve light requests", who); - self.peerset_handle.report_peer(who, rep::BAD_ROLE); - self.behaviour.disconnect_peer(&who, HARDCODED_PEERSETS_SYNC); - return Err(()) - } - - // we don't interested in peers that are far behind us - let self_best_block = self.chain.info().best_number; - let blocks_difference = self_best_block - .checked_sub(&status.best_number) - .unwrap_or_else(Zero::zero) - .saturated_into::(); - if blocks_difference > LIGHT_MAXIMAL_BLOCKS_DIFFERENCE { - debug!(target: "sync", "Peer {} is far behind us and will unable to serve light requests", who); - self.peerset_handle.report_peer(who, rep::PEER_BEHIND_US_LIGHT); - self.behaviour.disconnect_peer(&who, HARDCODED_PEERSETS_SYNC); - return Err(()) - } - } - - let no_slot_peer = self.default_peers_set_no_slot_peers.contains(&who); - let this_peer_reserved_slot: usize = if no_slot_peer { 1 } else { 0 }; - - if status.roles.is_full() && - self.chain_sync.num_peers() >= - self.default_peers_set_num_full + - self.default_peers_set_no_slot_connected_peers.len() + - this_peer_reserved_slot - { - debug!(target: "sync", "Too many full nodes, rejecting {}", who); - self.behaviour.disconnect_peer(&who, HARDCODED_PEERSETS_SYNC); - return Err(()) - } - - if status.roles.is_light() && - (self.peers.len() - self.chain_sync.num_peers()) >= self.default_peers_set_num_light - { - // Make sure that not all slots are occupied by light clients. - debug!(target: "sync", "Too many light nodes, rejecting {}", who); - self.behaviour.disconnect_peer(&who, HARDCODED_PEERSETS_SYNC); - return Err(()) - } - - let peer = Peer { - info: PeerInfo { - roles: status.roles, - best_hash: status.best_hash, - best_number: status.best_number, - }, - known_blocks: LruHashSet::new( - NonZeroUsize::new(MAX_KNOWN_BLOCKS).expect("Constant is nonzero"), - ), - }; - - let req = if peer.info.roles.is_full() { - match self.chain_sync.new_peer(who, peer.info.best_hash, peer.info.best_number) { - Ok(req) => req, - Err(BadPeer(id, repu)) => { - self.behaviour.disconnect_peer(&id, HARDCODED_PEERSETS_SYNC); - self.peerset_handle.report_peer(id, repu); - return Err(()) - }, - } + /// Set handshake for the notification protocol. + pub fn set_notification_handshake(&mut self, protocol: ProtocolName, handshake: Vec) { + if let Some(index) = self.notification_protocols.iter().position(|p| *p == protocol) { + self.behaviour + .set_notif_protocol_handshake(crate::peerset::SetId::from(index), handshake); } else { - None - }; - - debug!(target: "sync", "Connected {}", who); - - self.peers.insert(who, peer); - if no_slot_peer { - self.default_peers_set_no_slot_connected_peers.insert(who); - } - self.pending_messages - .push_back(CustomMessageOutcome::PeerNewBest(who, status.best_number)); - - if let Some(req) = req { - self.chain_sync.send_block_request(who, req); - } - - Ok(()) - } - - /// Make sure an important block is propagated to peers. - /// - /// In chain-based consensus, we often need to make sure non-best forks are - /// at least temporarily synced. - pub fn announce_block(&mut self, hash: B::Hash, data: Option>) { - let header = match self.chain.header(hash) { - Ok(Some(header)) => header, - Ok(None) => { - warn!("Trying to announce unknown block: {}", hash); - return - }, - Err(e) => { - warn!("Error reading block header {}: {}", hash, e); - return - }, - }; - - // don't announce genesis block since it will be ignored - if header.number().is_zero() { - return - } - - let is_best = self.chain.info().best_hash == hash; - debug!(target: "sync", "Reannouncing block {:?} is_best: {}", hash, is_best); - - let data = data - .or_else(|| self.block_announce_data_cache.get(&hash).cloned()) - .unwrap_or_default(); - - for (who, ref mut peer) in self.peers.iter_mut() { - let inserted = peer.known_blocks.insert(hash); - if inserted { - trace!(target: "sync", "Announcing block {:?} to {}", hash, who); - let message = BlockAnnounce { - header: header.clone(), - state: if is_best { Some(BlockState::Best) } else { Some(BlockState::Normal) }, - data: Some(data.clone()), - }; - - self.behaviour - .write_notification(who, HARDCODED_PEERSETS_SYNC, message.encode()); - } - } - } - - /// Push a block announce validation. - /// - /// It is required that [`ChainSync::poll_block_announce_validation`] is - /// called later to check for finished validations. The result of the validation - /// needs to be passed to [`Protocol::process_block_announce_validation_result`] - /// to finish the processing. - /// - /// # Note - /// - /// This will internally create a future, but this future will not be registered - /// in the task before being polled once. So, it is required to call - /// [`ChainSync::poll_block_announce_validation`] to ensure that the future is - /// registered properly and will wake up the task when being ready. - fn push_block_announce_validation(&mut self, who: PeerId, announce: BlockAnnounce) { - let hash = announce.header.hash(); - - let peer = match self.peers.get_mut(&who) { - Some(p) => p, - None => { - log::error!(target: "sync", "Received block announce from disconnected peer {}", who); - debug_assert!(false); - return - }, - }; - - peer.known_blocks.insert(hash); - - let is_best = match announce.state.unwrap_or(BlockState::Best) { - BlockState::Best => true, - BlockState::Normal => false, - }; - - if peer.info.roles.is_full() { - self.chain_sync.push_block_announce_validation(who, hash, announce, is_best); - } - } - - /// Process the result of the block announce validation. - fn process_block_announce_validation_result( - &mut self, - validation_result: PollBlockAnnounceValidation, - ) -> CustomMessageOutcome { - let (header, is_best, who) = match validation_result { - PollBlockAnnounceValidation::Skip => return CustomMessageOutcome::None, - PollBlockAnnounceValidation::Nothing { is_best, who, announce } => { - self.update_peer_info(&who); - - if let Some(data) = announce.data { - if !data.is_empty() { - self.block_announce_data_cache.put(announce.header.hash(), data); - } - } - - // `on_block_announce` returns `OnBlockAnnounce::ImportHeader` - // when we have all data required to import the block - // in the BlockAnnounce message. This is only when: - // 1) we're on light client; - // AND - // 2) parent block is already imported and not pruned. - if is_best { - return CustomMessageOutcome::PeerNewBest(who, *announce.header.number()) - } else { - return CustomMessageOutcome::None - } - }, - PollBlockAnnounceValidation::ImportHeader { announce, is_best, who } => { - self.update_peer_info(&who); - - if let Some(data) = announce.data { - if !data.is_empty() { - self.block_announce_data_cache.put(announce.header.hash(), data); - } - } - - (announce.header, is_best, who) - }, - PollBlockAnnounceValidation::Failure { who, disconnect } => { - if disconnect { - self.behaviour.disconnect_peer(&who, HARDCODED_PEERSETS_SYNC); - } - - self.report_peer(who, rep::BAD_BLOCK_ANNOUNCEMENT); - return CustomMessageOutcome::None - }, - }; - - let number = *header.number(); - - // to import header from announced block let's construct response to request that normally - // would have been sent over network (but it is not in our case) - let blocks_to_import = self.chain_sync.on_block_data( - &who, - None, - BlockResponse:: { - id: 0, - blocks: vec![BlockData:: { - hash: header.hash(), - header: Some(header), - body: None, - indexed_body: None, - receipt: None, - message_queue: None, - justification: None, - justifications: None, - }], - }, - ); - self.chain_sync.process_block_response_data(blocks_to_import); - - if is_best { - self.pending_messages.push_back(CustomMessageOutcome::PeerNewBest(who, number)); + error!( + target: "sub-libp2p", + "set_notification_handshake with unknown protocol: {}", + protocol + ); } - - CustomMessageOutcome::None - } - - /// Call this when a block has been finalized. The sync layer may have some additional - /// requesting to perform. - pub fn on_block_finalized(&mut self, hash: B::Hash, header: &B::Header) { - self.chain_sync.on_block_finalized(&hash, *header.number()) } /// Set whether the syncing peers set is in reserved-only mode. @@ -797,8 +258,8 @@ where } /// Returns the list of reserved peers. - pub fn reserved_peers(&self) -> impl Iterator { - self.behaviour.reserved_peers(HARDCODED_PEERSETS_SYNC) + pub fn reserved_peers(&self, pending_response: oneshot::Sender>) { + self.behaviour.reserved_peers(HARDCODED_PEERSETS_SYNC, pending_response); } /// Adds a `PeerId` to the list of reserved peers for syncing purposes. @@ -814,7 +275,8 @@ where /// Sets the list of reserved peers for the given protocol/peerset. pub fn set_reserved_peerset_peers(&self, protocol: ProtocolName, peers: HashSet) { if let Some(index) = self.notification_protocols.iter().position(|p| *p == protocol) { - self.peerset_handle.set_reserved_peers(sc_peerset::SetId::from(index), peers); + self.peerset_handle + .set_reserved_peers(crate::peerset::SetId::from(index), peers); } else { error!( target: "sub-libp2p", @@ -827,7 +289,8 @@ where /// Removes a `PeerId` from the list of reserved peers. pub fn remove_set_reserved_peer(&self, protocol: ProtocolName, peer: PeerId) { if let Some(index) = self.notification_protocols.iter().position(|p| *p == protocol) { - self.peerset_handle.remove_reserved_peer(sc_peerset::SetId::from(index), peer); + self.peerset_handle + .remove_reserved_peer(crate::peerset::SetId::from(index), peer); } else { error!( target: "sub-libp2p", @@ -840,7 +303,7 @@ where /// Adds a `PeerId` to the list of reserved peers. pub fn add_set_reserved_peer(&self, protocol: ProtocolName, peer: PeerId) { if let Some(index) = self.notification_protocols.iter().position(|p| *p == protocol) { - self.peerset_handle.add_reserved_peer(sc_peerset::SetId::from(index), peer); + self.peerset_handle.add_reserved_peer(crate::peerset::SetId::from(index), peer); } else { error!( target: "sub-libp2p", @@ -850,75 +313,20 @@ where } } - /// Notify the protocol that we have learned about the existence of nodes on the default set. + /// Notify the protocol that we have learned about the existence of some peer. /// - /// Can be called multiple times with the same `PeerId`s. - pub fn add_default_set_discovered_nodes(&mut self, peer_ids: impl Iterator) { - for peer_id in peer_ids { - self.peerset_handle.add_to_peers_set(HARDCODED_PEERSETS_SYNC, peer_id); - } - } - - /// Add a peer to a peers set. - pub fn add_to_peers_set(&self, protocol: ProtocolName, peer: PeerId) { - if let Some(index) = self.notification_protocols.iter().position(|p| *p == protocol) { - self.peerset_handle.add_to_peers_set(sc_peerset::SetId::from(index), peer); - } else { - error!( - target: "sub-libp2p", - "add_to_peers_set with unknown protocol: {}", - protocol - ); - } - } - - /// Remove a peer from a peers set. - pub fn remove_from_peers_set(&self, protocol: ProtocolName, peer: PeerId) { - if let Some(index) = self.notification_protocols.iter().position(|p| *p == protocol) { - self.peerset_handle.remove_from_peers_set(sc_peerset::SetId::from(index), peer); - } else { - error!( - target: "sub-libp2p", - "remove_from_peers_set with unknown protocol: {}", - protocol - ); - } - } - - fn report_metrics(&self) { - if let Some(metrics) = &self.metrics { - let n = u64::try_from(self.peers.len()).unwrap_or(std::u64::MAX); - metrics.peers.set(n); - - let m = self.chain_sync.metrics(); - - metrics.fork_targets.set(m.fork_targets.into()); - metrics.queued_blocks.set(m.queued_blocks.into()); - - metrics - .justifications - .with_label_values(&["pending"]) - .set(m.justifications.pending_requests.into()); - metrics - .justifications - .with_label_values(&["active"]) - .set(m.justifications.active_requests.into()); - metrics - .justifications - .with_label_values(&["failed"]) - .set(m.justifications.failed_requests.into()); - metrics - .justifications - .with_label_values(&["importing"]) - .set(m.justifications.importing_requests.into()); - } + /// Can be called multiple times with the same `PeerId`. + pub fn add_known_peer(&mut self, peer_id: PeerId) { + // TODO: get rid of this function and call `Peerset`/`PeerStore` directly + // from `NetworkWorker`. + self.peerset_handle.add_known_peer(peer_id); } } /// Outcome of an incoming custom message. #[derive(Debug)] #[must_use] -pub enum CustomMessageOutcome { +pub enum CustomMessageOutcome { /// Notification protocols have been opened with a remote. NotificationStreamOpened { remote: PeerId, @@ -926,6 +334,7 @@ pub enum CustomMessageOutcome { /// See [`crate::Event::NotificationStreamOpened::negotiated_fallback`]. negotiated_fallback: Option, roles: Roles, + received_handshake: Vec, notifications_sink: NotificationsSink, }, /// The [`NotificationsSink`] of some notification protocols need an update. @@ -935,40 +344,57 @@ pub enum CustomMessageOutcome { notifications_sink: NotificationsSink, }, /// Notification protocols have been closed with a remote. - NotificationStreamClosed { - remote: PeerId, - protocol: ProtocolName, - }, + NotificationStreamClosed { remote: PeerId, protocol: ProtocolName }, /// Messages have been received on one or more notifications protocols. - NotificationsReceived { - remote: PeerId, - messages: Vec<(ProtocolName, Bytes)>, - }, - /// Peer has a reported a new head of chain. - PeerNewBest(PeerId, NumberFor), + NotificationsReceived { remote: PeerId, messages: Vec<(ProtocolName, Bytes)> }, /// Now connected to a new peer for syncing purposes. - SyncConnected(PeerId), - /// No longer connected to a peer for syncing purposes. - SyncDisconnected(PeerId), None, } -impl NetworkBehaviour for Protocol -where - B: BlockT, - Client: HeaderBackend + 'static, -{ +impl NetworkBehaviour for Protocol { type ConnectionHandler = ::ConnectionHandler; - type OutEvent = CustomMessageOutcome; + type OutEvent = CustomMessageOutcome; - fn new_handler(&mut self) -> Self::ConnectionHandler { - self.behaviour.new_handler() + fn handle_established_inbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result, ConnectionDenied> { + self.behaviour.handle_established_inbound_connection( + connection_id, + peer, + local_addr, + remote_addr, + ) } - fn addresses_of_peer(&mut self, _: &PeerId) -> Vec { - // Only `Discovery::addresses_of_peer` must be returning addresses to ensure that we - // don't return unwanted addresses. - Vec::new() + fn handle_established_outbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + addr: &Multiaddr, + role_override: Endpoint, + ) -> Result, ConnectionDenied> { + self.behaviour.handle_established_outbound_connection( + connection_id, + peer, + addr, + role_override, + ) + } + + fn handle_pending_outbound_connection( + &mut self, + _connection_id: ConnectionId, + _maybe_peer: Option, + _addresses: &[Multiaddr], + _effective_role: Endpoint, + ) -> Result, ConnectionDenied> { + // Only `Discovery::handle_pending_outbound_connection` must be returning addresses to + // ensure that we don't return unwanted addresses. + Ok(Vec::new()) } fn on_swarm_event(&mut self, event: FromSwarm) { @@ -979,8 +405,7 @@ where &mut self, peer_id: PeerId, connection_id: ConnectionId, - event: <::Handler as - ConnectionHandler>::OutEvent, + event: THandlerOutEvent, ) { self.behaviour.on_connection_handler_event(peer_id, connection_id, event); } @@ -989,45 +414,34 @@ where &mut self, cx: &mut std::task::Context, params: &mut impl PollParameters, - ) -> Poll> { - if let Some(message) = self.pending_messages.pop_front() { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(message)) - } - - // Advance the state of `ChainSync` - // - // Process any received requests received from `NetworkService` and - // check if there is any block announcement validation finished. - while let Poll::Ready(result) = self.chain_sync.poll(cx) { - match self.process_block_announce_validation_result(result) { - CustomMessageOutcome::None => {}, - outcome => self.pending_messages.push_back(outcome), + ) -> Poll>> { + while let Poll::Ready(Some(validation_result)) = + self.sync_substream_validations.poll_next_unpin(cx) + { + match validation_result { + Ok((peer, roles)) => { + self.peers.insert(peer, roles); + }, + Err(peer) => { + log::debug!( + target: "sub-libp2p", + "`SyncingEngine` rejected stream" + ); + self.behaviour.disconnect_peer(&peer, HARDCODED_PEERSETS_SYNC); + }, } } - while let Poll::Ready(Some(())) = self.tick_timeout.poll_next_unpin(cx) { - self.tick(); - } - - if let Some(message) = self.pending_messages.pop_front() { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(message)) - } - let event = match self.behaviour.poll(cx, params) { Poll::Pending => return Poll::Pending, - Poll::Ready(NetworkBehaviourAction::GenerateEvent(ev)) => ev, - Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) => - return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }), - Poll::Ready(NetworkBehaviourAction::NotifyHandler { peer_id, handler, event }) => - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { - peer_id, - handler, - event, - }), - Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { address, score }) => - return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { address, score }), - Poll::Ready(NetworkBehaviourAction::CloseConnection { peer_id, connection }) => - return Poll::Ready(NetworkBehaviourAction::CloseConnection { peer_id, connection }), + Poll::Ready(ToSwarm::GenerateEvent(ev)) => ev, + Poll::Ready(ToSwarm::Dial { opts }) => return Poll::Ready(ToSwarm::Dial { opts }), + Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, event }) => + return Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, event }), + Poll::Ready(ToSwarm::ReportObservedAddr { address, score }) => + return Poll::Ready(ToSwarm::ReportObservedAddr { address, score }), + Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }) => + return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), }; let outcome = match event { @@ -1045,18 +459,36 @@ where // announces substream. match as DecodeAll>::decode_all(&mut &received_handshake[..]) { Ok(GenericMessage::Status(handshake)) => { - let handshake = BlockAnnouncesHandshake { + let roles = handshake.roles; + let handshake = BlockAnnouncesHandshake:: { roles: handshake.roles, best_number: handshake.best_number, best_hash: handshake.best_hash, genesis_hash: handshake.genesis_hash, }; - if self.on_sync_peer_connected(peer_id, handshake).is_ok() { - CustomMessageOutcome::SyncConnected(peer_id) - } else { - CustomMessageOutcome::None - } + let (tx, rx) = oneshot::channel(); + let _ = self.tx.unbounded_send( + crate::SyncEvent::NotificationStreamOpened { + remote: peer_id, + received_handshake: handshake, + sink: notifications_sink, + tx, + }, + ); + self.sync_substream_validations.push(Box::pin(async move { + match rx.await { + Ok(accepted) => + if accepted { + Ok((peer_id, roles)) + } else { + Err(peer_id) + }, + Err(_) => Err(peer_id), + } + })); + + CustomMessageOutcome::None }, Ok(msg) => { debug!( @@ -1073,14 +505,32 @@ where &mut &received_handshake[..], ) { Ok(handshake) => { - if self.on_sync_peer_connected(peer_id, handshake).is_ok() { - CustomMessageOutcome::SyncConnected(peer_id) - } else { - CustomMessageOutcome::None - } + let roles = handshake.roles; + + let (tx, rx) = oneshot::channel(); + let _ = self.tx.unbounded_send( + crate::SyncEvent::NotificationStreamOpened { + remote: peer_id, + received_handshake: handshake, + sink: notifications_sink, + tx, + }, + ); + self.sync_substream_validations.push(Box::pin(async move { + match rx.await { + Ok(accepted) => + if accepted { + Ok((peer_id, roles)) + } else { + Err(peer_id) + }, + Err(_) => Err(peer_id), + } + })); + CustomMessageOutcome::None }, Err(err2) => { - debug!( + log::debug!( target: "sync", "Couldn't decode handshake sent by {}: {:?}: {} & {}", peer_id, @@ -1104,9 +554,10 @@ where protocol: self.notification_protocols[usize::from(set_id)].clone(), negotiated_fallback, roles, + received_handshake, notifications_sink, }, - (Err(_), Some(peer)) if received_handshake.is_empty() => { + (Err(_), Some(roles)) if received_handshake.is_empty() => { // As a convenience, we allow opening substreams for "external" // notification protocols with an empty handshake. This fetches the // roles from the locally-known roles. @@ -1115,7 +566,8 @@ where remote: peer_id, protocol: self.notification_protocols[usize::from(set_id)].clone(), negotiated_fallback, - roles: peer.info.roles, + roles: *roles, + received_handshake, notifications_sink, } }, @@ -1124,15 +576,20 @@ where self.bad_handshake_substreams.insert((peer_id, set_id)); self.behaviour.disconnect_peer(&peer_id, set_id); self.peerset_handle.report_peer(peer_id, rep::BAD_MESSAGE); + self.peers.remove(&peer_id); CustomMessageOutcome::None }, } } }, NotificationsOut::CustomProtocolReplaced { peer_id, notifications_sink, set_id } => - if set_id == HARDCODED_PEERSETS_SYNC || - self.bad_handshake_substreams.contains(&(peer_id, set_id)) - { + if self.bad_handshake_substreams.contains(&(peer_id, set_id)) { + CustomMessageOutcome::None + } else if set_id == HARDCODED_PEERSETS_SYNC { + let _ = self.tx.unbounded_send(crate::SyncEvent::NotificationSinkReplaced { + remote: peer_id, + sink: notifications_sink, + }); CustomMessageOutcome::None } else { CustomMessageOutcome::NotificationStreamReplaced { @@ -1142,23 +599,17 @@ where } }, NotificationsOut::CustomProtocolClosed { peer_id, set_id } => { - // Set number 0 is hardcoded the default set of peers we sync from. - if set_id == HARDCODED_PEERSETS_SYNC { - if self.on_sync_peer_disconnected(peer_id).is_ok() { - CustomMessageOutcome::SyncDisconnected(peer_id) - } else { - log::trace!( - target: "sync", - "Disconnected peer which had earlier been refused by on_sync_peer_connected {}", - peer_id - ); - CustomMessageOutcome::None - } - } else if self.bad_handshake_substreams.remove(&(peer_id, set_id)) { + if self.bad_handshake_substreams.remove(&(peer_id, set_id)) { // The substream that has just been closed had been opened with a bad // handshake. The outer layers have never received an opening event about this // substream, and consequently shouldn't receive a closing event either. CustomMessageOutcome::None + } else if set_id == HARDCODED_PEERSETS_SYNC { + let _ = self.tx.unbounded_send(crate::SyncEvent::NotificationStreamClosed { + remote: peer_id, + }); + self.peers.remove(&peer_id); + CustomMessageOutcome::None } else { CustomMessageOutcome::NotificationStreamClosed { remote: peer_id, @@ -1166,50 +617,27 @@ where } } }, - NotificationsOut::Notification { peer_id, set_id, message } => match set_id { - HARDCODED_PEERSETS_SYNC if self.peers.contains_key(&peer_id) => { - if let Ok(announce) = BlockAnnounce::decode(&mut message.as_ref()) { - self.push_block_announce_validation(peer_id, announce); - - // Make sure that the newly added block announce validation future was - // polled once to be registered in the task. - if let Poll::Ready(res) = self.chain_sync.poll_block_announce_validation(cx) - { - self.process_block_announce_validation_result(res) - } else { - CustomMessageOutcome::None - } - } else { - warn!(target: "sub-libp2p", "Failed to decode block announce"); - CustomMessageOutcome::None - } - }, - HARDCODED_PEERSETS_SYNC => { - trace!( - target: "sync", - "Received sync for peer earlier refused by sync layer: {}", - peer_id - ); + NotificationsOut::Notification { peer_id, set_id, message } => { + if self.bad_handshake_substreams.contains(&(peer_id, set_id)) { CustomMessageOutcome::None - }, - _ if self.bad_handshake_substreams.contains(&(peer_id, set_id)) => - CustomMessageOutcome::None, - _ => { + } else if set_id == HARDCODED_PEERSETS_SYNC { + let _ = self.tx.unbounded_send(crate::SyncEvent::NotificationsReceived { + remote: peer_id, + messages: vec![message.freeze()], + }); + CustomMessageOutcome::None + } else { let protocol_name = self.notification_protocols[usize::from(set_id)].clone(); CustomMessageOutcome::NotificationsReceived { remote: peer_id, messages: vec![(protocol_name, message.freeze())], } - }, + } }, }; - if !matches!(outcome, CustomMessageOutcome::::None) { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(outcome)) - } - - if let Some(message) = self.pending_messages.pop_front() { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(message)) + if !matches!(outcome, CustomMessageOutcome::None) { + return Poll::Ready(ToSwarm::GenerateEvent(outcome)) } // This block can only be reached if an event was pulled from the behaviour and that diff --git a/client/network/src/protocol/message.rs b/client/network/src/protocol/message.rs index ef652387d2c7..66dca2975375 100644 --- a/client/network/src/protocol/message.rs +++ b/client/network/src/protocol/message.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -61,7 +61,7 @@ pub mod generic { use sc_client_api::StorageProof; use sc_network_common::{ message::RequestId, - protocol::role::Roles, + role::Roles, sync::message::{ generic::{BlockRequest, BlockResponse}, BlockAnnounce, diff --git a/client/network/src/protocol/notifications.rs b/client/network/src/protocol/notifications.rs index bf183abf160c..aa49cfcf9d44 100644 --- a/client/network/src/protocol/notifications.rs +++ b/client/network/src/protocol/notifications.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/network/src/protocol/notifications/behaviour.rs b/client/network/src/protocol/notifications/behaviour.rs index 2db46867128a..1659626cae4c 100644 --- a/client/network/src/protocol/notifications/behaviour.rs +++ b/client/network/src/protocol/notifications/behaviour.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,27 +16,29 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::protocol::notifications::handler::{ - self, NotificationsSink, NotifsHandlerIn, NotifsHandlerOut, NotifsHandlerProto, +use crate::{ + peerset::DropReason, + protocol::notifications::handler::{ + self, NotificationsSink, NotifsHandler, NotifsHandlerIn, NotifsHandlerOut, + }, + types::ProtocolName, }; use bytes::BytesMut; use fnv::FnvHashMap; -use futures::prelude::*; +use futures::{channel::oneshot, prelude::*}; use libp2p::{ - core::{connection::ConnectionId, Multiaddr, PeerId}, + core::{ConnectedPoint, Endpoint, Multiaddr}, swarm::{ behaviour::{ConnectionClosed, ConnectionEstablished, DialFailure, FromSwarm}, - handler::ConnectionHandler, - DialError, IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, - PollParameters, + ConnectionDenied, ConnectionId, DialError, NetworkBehaviour, NotifyHandler, PollParameters, + THandler, THandlerInEvent, THandlerOutEvent, ToSwarm, }, + PeerId, }; -use log::{error, trace, warn}; +use log::{debug, error, info, trace, warn}; use parking_lot::RwLock; use rand::distributions::{Distribution as _, Uniform}; -use sc_network_common::protocol::ProtocolName; -use sc_peerset::DropReason; use smallvec::SmallVec; use std::{ cmp, @@ -82,19 +84,17 @@ use std::{ /// the API of this behaviour and towards the peerset manager is aggregated in /// the following way: /// -/// 1. The enabled/disabled status is the same across all connections, as -/// decided by the peerset manager. -/// 2. `send_packet` and `write_notification` always send all data over -/// the same connection to preserve the ordering provided by the transport, -/// as long as that connection is open. If it closes, a second open -/// connection may take over, if one exists, but that case should be no -/// different than a single connection failing and being re-established -/// in terms of potential reordering and dropped messages. Messages can -/// be received on any connection. -/// 3. The behaviour reports `NotificationsOut::CustomProtocolOpen` when the -/// first connection reports `NotifsHandlerOut::OpenResultOk`. -/// 4. The behaviour reports `NotificationsOut::CustomProtocolClosed` when the -/// last connection reports `NotifsHandlerOut::ClosedResult`. +/// 1. The enabled/disabled status is the same across all connections, as decided by the peerset +/// manager. +/// 2. `send_packet` and `write_notification` always send all data over the same connection to +/// preserve the ordering provided by the transport, as long as that connection is open. If it +/// closes, a second open connection may take over, if one exists, but that case should be no +/// different than a single connection failing and being re-established in terms of potential +/// reordering and dropped messages. Messages can be received on any connection. +/// 3. The behaviour reports `NotificationsOut::CustomProtocolOpen` when the first connection +/// reports `NotifsHandlerOut::OpenResultOk`. +/// 4. The behaviour reports `NotificationsOut::CustomProtocolClosed` when the last connection +/// reports `NotifsHandlerOut::ClosedResult`. /// /// In this way, the number of actual established connections to the peer is /// an implementation detail of this behaviour. Note that, in practice and at @@ -106,10 +106,10 @@ pub struct Notifications { notif_protocols: Vec, /// Receiver for instructions about who to connect to or disconnect from. - peerset: sc_peerset::Peerset, + peerset: crate::peerset::Peerset, /// List of peers in our state. - peers: FnvHashMap<(PeerId, sc_peerset::SetId), PeerState>, + peers: FnvHashMap<(PeerId, crate::peerset::SetId), PeerState>, /// The elements in `peers` occasionally contain `Delay` objects that we would normally have /// to be polled one by one. In order to avoid doing so, as an optimization, every `Delay` is @@ -119,7 +119,7 @@ pub struct Notifications { /// By design, we never remove elements from this list. Elements are removed only when the /// `Delay` triggers. As such, this stream may produce obsolete elements. delays: stream::FuturesUnordered< - Pin + Send>>, + Pin + Send>>, >, /// [`DelayId`] to assign to the next delay. @@ -131,10 +131,10 @@ pub struct Notifications { /// We generate indices to identify incoming connections. This is the next value for the index /// to use when a connection is incoming. - next_incoming_index: sc_peerset::IncomingIndex, + next_incoming_index: crate::peerset::IncomingIndex, /// Events to produce from `poll()`. - events: VecDeque>, + events: VecDeque>, } /// Configuration for a notifications protocol. @@ -229,6 +229,9 @@ enum PeerState { /// If `Some`, any dial attempts to this peer are delayed until the given `Instant`. backoff_until: Option, + /// Incoming index tracking this connection. + incoming_index: crate::peerset::IncomingIndex, + /// List of connections with this peer, and their state. connections: SmallVec<[(ConnectionId, ConnectionState); crate::MAX_CONNECTIONS_PER_PEER]>, }, @@ -291,12 +294,12 @@ struct IncomingPeer { /// Id of the remote peer of the incoming substream. peer_id: PeerId, /// Id of the set the incoming substream would belong to. - set_id: sc_peerset::SetId, + set_id: crate::peerset::SetId, /// If true, this "incoming" still corresponds to an actual connection. If false, then the /// connection corresponding to it has been closed or replaced already. alive: bool, /// Id that the we sent to the peerset. - incoming_id: sc_peerset::IncomingIndex, + incoming_id: crate::peerset::IncomingIndex, } /// Event that can be emitted by the `Notifications`. @@ -307,7 +310,7 @@ pub enum NotificationsOut { /// Id of the peer we are connected to. peer_id: PeerId, /// Peerset set ID the substream is tied to. - set_id: sc_peerset::SetId, + set_id: crate::peerset::SetId, /// If `Some`, a fallback protocol name has been used rather the main protocol name. /// Always matches one of the fallback names passed at initialization. negotiated_fallback: Option, @@ -327,7 +330,7 @@ pub enum NotificationsOut { /// Id of the peer we are connected to. peer_id: PeerId, /// Peerset set ID the substream is tied to. - set_id: sc_peerset::SetId, + set_id: crate::peerset::SetId, /// Replacement for the previous [`NotificationsSink`]. notifications_sink: NotificationsSink, }, @@ -338,7 +341,7 @@ pub enum NotificationsOut { /// Id of the peer we were connected to. peer_id: PeerId, /// Peerset set ID the substream was tied to. - set_id: sc_peerset::SetId, + set_id: crate::peerset::SetId, }, /// Receives a message on a custom protocol substream. @@ -348,7 +351,7 @@ pub enum NotificationsOut { /// Id of the peer the message came from. peer_id: PeerId, /// Peerset set ID the substream is tied to. - set_id: sc_peerset::SetId, + set_id: crate::peerset::SetId, /// Message that has been received. message: BytesMut, }, @@ -357,7 +360,7 @@ pub enum NotificationsOut { impl Notifications { /// Creates a `CustomProtos`. pub fn new( - peerset: sc_peerset::Peerset, + peerset: crate::peerset::Peerset, notif_protocols: impl Iterator, ) -> Self { let notif_protocols = notif_protocols @@ -378,7 +381,7 @@ impl Notifications { delays: Default::default(), next_delay_id: DelayId(0), incoming: SmallVec::new(), - next_incoming_index: sc_peerset::IncomingIndex(0), + next_incoming_index: crate::peerset::IncomingIndex(0), events: VecDeque::new(), } } @@ -386,7 +389,7 @@ impl Notifications { /// Modifies the handshake of the given notifications protocol. pub fn set_notif_protocol_handshake( &mut self, - set_id: sc_peerset::SetId, + set_id: crate::peerset::SetId, handshake_message: impl Into>, ) { if let Some(p) = self.notif_protocols.get_mut(usize::from(set_id)) { @@ -408,24 +411,18 @@ impl Notifications { } /// Returns true if we have an open substream to the given peer. - pub fn is_open(&self, peer_id: &PeerId, set_id: sc_peerset::SetId) -> bool { + pub fn is_open(&self, peer_id: &PeerId, set_id: crate::peerset::SetId) -> bool { self.peers.get(&(*peer_id, set_id)).map(|p| p.is_open()).unwrap_or(false) } /// Disconnects the given peer if we are connected to it. - pub fn disconnect_peer(&mut self, peer_id: &PeerId, set_id: sc_peerset::SetId) { + pub fn disconnect_peer(&mut self, peer_id: &PeerId, set_id: crate::peerset::SetId) { trace!(target: "sub-libp2p", "External API => Disconnect({}, {:?})", peer_id, set_id); - self.disconnect_peer_inner(peer_id, set_id, None); + self.disconnect_peer_inner(peer_id, set_id); } - /// Inner implementation of `disconnect_peer`. If `ban` is `Some`, we ban the peer - /// for the specific duration. - fn disconnect_peer_inner( - &mut self, - peer_id: &PeerId, - set_id: sc_peerset::SetId, - ban: Option, - ) { + /// Inner implementation of `disconnect_peer`. + fn disconnect_peer_inner(&mut self, peer_id: &PeerId, set_id: crate::peerset::SetId) { let mut entry = if let Entry::Occupied(entry) = self.peers.entry((*peer_id, set_id)) { entry } else { @@ -443,12 +440,8 @@ impl Notifications { PeerState::DisabledPendingEnable { connections, timer_deadline, timer: _ } => { trace!(target: "sub-libp2p", "PSM <= Dropped({}, {:?})", peer_id, set_id); self.peerset.dropped(set_id, *peer_id, DropReason::Unknown); - let backoff_until = Some(if let Some(ban) = ban { - cmp::max(timer_deadline, Instant::now() + ban) - } else { - timer_deadline - }); - *entry.into_mut() = PeerState::Disabled { connections, backoff_until } + *entry.into_mut() = + PeerState::Disabled { connections, backoff_until: Some(timer_deadline) } }, // Enabled => Disabled. @@ -462,14 +455,14 @@ impl Notifications { trace!(target: "sub-libp2p", "External API <= Closed({}, {:?})", peer_id, set_id); let event = NotificationsOut::CustomProtocolClosed { peer_id: *peer_id, set_id }; - self.events.push_back(NetworkBehaviourAction::GenerateEvent(event)); + self.events.push_back(ToSwarm::GenerateEvent(event)); } for (connec_id, connec_state) in connections.iter_mut().filter(|(_, s)| matches!(s, ConnectionState::Open(_))) { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Close({:?})", peer_id, *connec_id, set_id); - self.events.push_back(NetworkBehaviourAction::NotifyHandler { + self.events.push_back(ToSwarm::NotifyHandler { peer_id: *peer_id, handler: NotifyHandler::One(*connec_id), event: NotifsHandlerIn::Close { protocol_index: set_id.into() }, @@ -481,7 +474,7 @@ impl Notifications { connections.iter_mut().filter(|(_, s)| matches!(s, ConnectionState::Opening)) { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Close({:?})", peer_id, *connec_id, set_id); - self.events.push_back(NetworkBehaviourAction::NotifyHandler { + self.events.push_back(ToSwarm::NotifyHandler { peer_id: *peer_id, handler: NotifyHandler::One(*connec_id), event: NotifsHandlerIn::Close { protocol_index: set_id.into() }, @@ -496,13 +489,12 @@ impl Notifications { .iter() .any(|(_, s)| matches!(s, ConnectionState::Opening))); - let backoff_until = ban.map(|dur| Instant::now() + dur); - *entry.into_mut() = PeerState::Disabled { connections, backoff_until } + *entry.into_mut() = PeerState::Disabled { connections, backoff_until: None } }, // Incoming => Disabled. // Ongoing opening requests from the remote are rejected. - PeerState::Incoming { mut connections, backoff_until } => { + PeerState::Incoming { mut connections, backoff_until, .. } => { let inc = if let Some(inc) = self .incoming .iter_mut() @@ -524,7 +516,7 @@ impl Notifications { .filter(|(_, s)| matches!(s, ConnectionState::OpenDesiredByRemote)) { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Close({:?})", peer_id, *connec_id, set_id); - self.events.push_back(NetworkBehaviourAction::NotifyHandler { + self.events.push_back(ToSwarm::NotifyHandler { peer_id: *peer_id, handler: NotifyHandler::One(*connec_id), event: NotifsHandlerIn::Close { protocol_index: set_id.into() }, @@ -532,13 +524,6 @@ impl Notifications { *connec_state = ConnectionState::Closing; } - let backoff_until = match (backoff_until, ban) { - (Some(a), Some(b)) => Some(cmp::max(a, Instant::now() + b)), - (Some(a), None) => Some(a), - (None, Some(b)) => Some(Instant::now() + b), - (None, None) => None, - }; - debug_assert!(!connections .iter() .any(|(_, s)| matches!(s, ConnectionState::OpenDesiredByRemote))); @@ -552,50 +537,12 @@ impl Notifications { } /// Returns the list of reserved peers. - pub fn reserved_peers(&self, set_id: sc_peerset::SetId) -> impl Iterator { - self.peerset.reserved_peers(set_id) - } - - /// Sends a notification to a peer. - /// - /// Has no effect if the custom protocol is not open with the given peer. - /// - /// Also note that even if we have a valid open substream, it may in fact be already closed - /// without us knowing, in which case the packet will not be received. - /// - /// The `fallback` parameter is used for backwards-compatibility reason if the remote doesn't - /// support our protocol. One needs to pass the equivalent of what would have been passed - /// with `send_packet`. - pub fn write_notification( - &mut self, - target: &PeerId, - set_id: sc_peerset::SetId, - message: impl Into>, + pub fn reserved_peers( + &self, + set_id: crate::peerset::SetId, + pending_response: oneshot::Sender>, ) { - let notifs_sink = match self.peers.get(&(*target, set_id)).and_then(|p| p.get_open()) { - None => { - trace!( - target: "sub-libp2p", - "Tried to sent notification to {:?} without an open channel.", - target, - ); - return - }, - Some(sink) => sink, - }; - - let message = message.into(); - - trace!( - target: "sub-libp2p", - "External API => Notification({:?}, {:?}, {} bytes)", - target, - set_id, - message.len(), - ); - trace!(target: "sub-libp2p", "Handler({:?}) <= Sync notification", target); - - notifs_sink.send_sync_notification(message); + self.peerset.reserved_peers(set_id, pending_response); } /// Returns the state of the peerset manager, for debugging purposes. @@ -604,9 +551,8 @@ impl Notifications { } /// Function that is called when the peerset wants us to connect to a peer. - fn peerset_report_connect(&mut self, peer_id: PeerId, set_id: sc_peerset::SetId) { + fn peerset_report_connect(&mut self, peer_id: PeerId, set_id: crate::peerset::SetId) { // If `PeerId` is unknown to us, insert an entry, start dialing, and return early. - let handler = self.new_handler(); let mut occ_entry = match self.peers.entry((peer_id, set_id)) { Entry::Occupied(entry) => entry, Entry::Vacant(entry) => { @@ -618,10 +564,7 @@ impl Notifications { set_id, ); trace!(target: "sub-libp2p", "Libp2p <= Dial {}", entry.key().0); - self.events.push_back(NetworkBehaviourAction::Dial { - opts: entry.key().0.into(), - handler, - }); + self.events.push_back(ToSwarm::Dial { opts: entry.key().0.into() }); entry.insert(PeerState::Requested); return }, @@ -653,10 +596,7 @@ impl Notifications { set_id, ); trace!(target: "sub-libp2p", "Libp2p <= Dial {:?}", occ_entry.key()); - self.events.push_back(NetworkBehaviourAction::Dial { - opts: occ_entry.key().0.into(), - handler, - }); + self.events.push_back(ToSwarm::Dial { opts: occ_entry.key().0.into() }); *occ_entry.into_mut() = PeerState::Requested; }, @@ -704,7 +644,7 @@ impl Notifications { trace!(target: "sub-libp2p", "PSM => Connect({}, {:?}): Enabling connections.", occ_entry.key().0, set_id); trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Open({:?})", peer_id, *connec_id, set_id); - self.events.push_back(NetworkBehaviourAction::NotifyHandler { + self.events.push_back(ToSwarm::NotifyHandler { peer_id, handler: NotifyHandler::One(*connec_id), event: NotifsHandlerIn::Open { protocol_index: set_id.into() }, @@ -751,65 +691,34 @@ impl Notifications { }; } }, - - // Incoming => Enabled - PeerState::Incoming { mut connections, .. } => { - trace!(target: "sub-libp2p", "PSM => Connect({}, {:?}): Enabling connections.", - occ_entry.key().0, set_id); - if let Some(inc) = self - .incoming - .iter_mut() - .find(|i| i.peer_id == occ_entry.key().0 && i.set_id == set_id && i.alive) - { - inc.alive = false; - } else { - error!( - target: "sub-libp2p", - "State mismatch in libp2p: no entry in incoming for incoming peer", - ) - } - - debug_assert!(connections - .iter() - .any(|(_, s)| matches!(s, ConnectionState::OpenDesiredByRemote))); - for (connec_id, connec_state) in connections - .iter_mut() - .filter(|(_, s)| matches!(s, ConnectionState::OpenDesiredByRemote)) - { - trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Open({:?})", - occ_entry.key(), *connec_id, set_id); - self.events.push_back(NetworkBehaviourAction::NotifyHandler { - peer_id: occ_entry.key().0, - handler: NotifyHandler::One(*connec_id), - event: NotifsHandlerIn::Open { protocol_index: set_id.into() }, - }); - *connec_state = ConnectionState::Opening; - } - - *occ_entry.into_mut() = PeerState::Enabled { connections }; + // Incoming => Incoming + st @ PeerState::Incoming { .. } => { + debug!( + target: "sub-libp2p", + "PSM => Connect({}, {:?}): Ignoring obsolete connect, we are awaiting accept/reject.", + occ_entry.key().0, set_id + ); + *occ_entry.into_mut() = st; }, // Other states are kept as-is. st @ PeerState::Enabled { .. } => { - warn!(target: "sub-libp2p", + debug!(target: "sub-libp2p", "PSM => Connect({}, {:?}): Already connected.", occ_entry.key().0, set_id); *occ_entry.into_mut() = st; - debug_assert!(false); }, st @ PeerState::DisabledPendingEnable { .. } => { - warn!(target: "sub-libp2p", + debug!(target: "sub-libp2p", "PSM => Connect({}, {:?}): Already pending enabling.", occ_entry.key().0, set_id); *occ_entry.into_mut() = st; - debug_assert!(false); }, st @ PeerState::Requested { .. } | st @ PeerState::PendingRequest { .. } => { - warn!(target: "sub-libp2p", + debug!(target: "sub-libp2p", "PSM => Connect({}, {:?}): Duplicate request.", occ_entry.key().0, set_id); *occ_entry.into_mut() = st; - debug_assert!(false); }, PeerState::Poisoned => { @@ -820,7 +729,7 @@ impl Notifications { } /// Function that is called when the peerset wants us to disconnect from a peer. - fn peerset_report_disconnect(&mut self, peer_id: PeerId, set_id: sc_peerset::SetId) { + fn peerset_report_disconnect(&mut self, peer_id: PeerId, set_id: crate::peerset::SetId) { let mut entry = match self.peers.entry((peer_id, set_id)) { Entry::Occupied(entry) => entry, Entry::Vacant(entry) => { @@ -861,7 +770,7 @@ impl Notifications { trace!(target: "sub-libp2p", "External API <= Closed({}, {:?})", entry.key().0, set_id); let event = NotificationsOut::CustomProtocolClosed { peer_id: entry.key().0, set_id }; - self.events.push_back(NetworkBehaviourAction::GenerateEvent(event)); + self.events.push_back(ToSwarm::GenerateEvent(event)); } for (connec_id, connec_state) in @@ -869,7 +778,7 @@ impl Notifications { { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Close({:?})", entry.key(), *connec_id, set_id); - self.events.push_back(NetworkBehaviourAction::NotifyHandler { + self.events.push_back(ToSwarm::NotifyHandler { peer_id: entry.key().0, handler: NotifyHandler::One(*connec_id), event: NotifsHandlerIn::Close { protocol_index: set_id.into() }, @@ -882,7 +791,7 @@ impl Notifications { { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Close({:?})", entry.key(), *connec_id, set_id); - self.events.push_back(NetworkBehaviourAction::NotifyHandler { + self.events.push_back(ToSwarm::NotifyHandler { peer_id: entry.key().0, handler: NotifyHandler::One(*connec_id), event: NotifsHandlerIn::Close { protocol_index: set_id.into() }, @@ -912,10 +821,12 @@ impl Notifications { // Invalid state transitions. st @ PeerState::Incoming { .. } => { - error!(target: "sub-libp2p", "PSM => Drop({}, {:?}): Not enabled (Incoming).", - entry.key().0, set_id); + info!( + target: "sub-libp2p", + "PSM => Drop({}, {:?}): Ignoring obsolete disconnect, we are awaiting accept/reject.", + entry.key().0, set_id, + ); *entry.into_mut() = st; - debug_assert!(false); }, PeerState::Poisoned => { error!(target: "sub-libp2p", "State of {:?} is poisoned", entry.key()); @@ -926,7 +837,7 @@ impl Notifications { /// Function that is called when the peerset wants us to accept a connection /// request from a peer. - fn peerset_report_accept(&mut self, index: sc_peerset::IncomingIndex) { + fn peerset_report_accept(&mut self, index: crate::peerset::IncomingIndex) { let incoming = if let Some(pos) = self.incoming.iter().position(|i| i.incoming_id == index) { self.incoming.remove(pos) @@ -960,7 +871,24 @@ impl Notifications { match mem::replace(state, PeerState::Poisoned) { // Incoming => Enabled - PeerState::Incoming { mut connections, .. } => { + PeerState::Incoming { mut connections, incoming_index, .. } => { + if index < incoming_index { + warn!( + target: "sub-libp2p", + "PSM => Accept({:?}, {}, {:?}): Ignoring obsolete incoming index, we are already awaiting {:?}.", + index, incoming.peer_id, incoming.set_id, incoming_index + ); + return + } else if index > incoming_index { + error!( + target: "sub-libp2p", + "PSM => Accept({:?}, {}, {:?}): Ignoring incoming index from the future, we are awaiting {:?}.", + index, incoming.peer_id, incoming.set_id, incoming_index + ); + debug_assert!(false); + return + } + trace!(target: "sub-libp2p", "PSM => Accept({:?}, {}, {:?}): Enabling connections.", index, incoming.peer_id, incoming.set_id); @@ -973,7 +901,7 @@ impl Notifications { { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Open({:?})", incoming.peer_id, *connec_id, incoming.set_id); - self.events.push_back(NetworkBehaviourAction::NotifyHandler { + self.events.push_back(ToSwarm::NotifyHandler { peer_id: incoming.peer_id, handler: NotifyHandler::One(*connec_id), event: NotifsHandlerIn::Open { protocol_index: incoming.set_id.into() }, @@ -995,7 +923,7 @@ impl Notifications { } /// Function that is called when the peerset wants us to reject an incoming peer. - fn peerset_report_reject(&mut self, index: sc_peerset::IncomingIndex) { + fn peerset_report_reject(&mut self, index: crate::peerset::IncomingIndex) { let incoming = if let Some(pos) = self.incoming.iter().position(|i| i.incoming_id == index) { self.incoming.remove(pos) @@ -1020,7 +948,24 @@ impl Notifications { match mem::replace(state, PeerState::Poisoned) { // Incoming => Disabled - PeerState::Incoming { mut connections, backoff_until } => { + PeerState::Incoming { mut connections, backoff_until, incoming_index } => { + if index < incoming_index { + warn!( + target: "sub-libp2p", + "PSM => Reject({:?}, {}, {:?}): Ignoring obsolete incoming index, we are already awaiting {:?}.", + index, incoming.peer_id, incoming.set_id, incoming_index + ); + return + } else if index > incoming_index { + error!( + target: "sub-libp2p", + "PSM => Reject({:?}, {}, {:?}): Ignoring incoming index from the future, we are awaiting {:?}.", + index, incoming.peer_id, incoming.set_id, incoming_index + ); + debug_assert!(false); + return + } + trace!(target: "sub-libp2p", "PSM => Reject({:?}, {}, {:?}): Rejecting connections.", index, incoming.peer_id, incoming.set_id); @@ -1033,7 +978,7 @@ impl Notifications { { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Close({:?})", incoming.peer_id, connec_id, incoming.set_id); - self.events.push_back(NetworkBehaviourAction::NotifyHandler { + self.events.push_back(ToSwarm::NotifyHandler { peer_id: incoming.peer_id, handler: NotifyHandler::One(*connec_id), event: NotifsHandlerIn::Close { protocol_index: incoming.set_id.into() }, @@ -1051,15 +996,57 @@ impl Notifications { } impl NetworkBehaviour for Notifications { - type ConnectionHandler = NotifsHandlerProto; + type ConnectionHandler = NotifsHandler; type OutEvent = NotificationsOut; - fn new_handler(&mut self) -> Self::ConnectionHandler { - NotifsHandlerProto::new(self.notif_protocols.clone()) + fn handle_pending_inbound_connection( + &mut self, + _connection_id: ConnectionId, + _local_addr: &Multiaddr, + _remote_addr: &Multiaddr, + ) -> Result<(), ConnectionDenied> { + Ok(()) + } + + fn handle_pending_outbound_connection( + &mut self, + _connection_id: ConnectionId, + _maybe_peer: Option, + _addresses: &[Multiaddr], + _effective_role: Endpoint, + ) -> Result, ConnectionDenied> { + Ok(Vec::new()) + } + + fn handle_established_inbound_connection( + &mut self, + _connection_id: ConnectionId, + peer: PeerId, + local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result, ConnectionDenied> { + Ok(NotifsHandler::new( + peer, + ConnectedPoint::Listener { + local_addr: local_addr.clone(), + send_back_addr: remote_addr.clone(), + }, + self.notif_protocols.clone(), + )) } - fn addresses_of_peer(&mut self, _: &PeerId) -> Vec { - Vec::new() + fn handle_established_outbound_connection( + &mut self, + _connection_id: ConnectionId, + peer: PeerId, + addr: &Multiaddr, + role_override: Endpoint, + ) -> Result, ConnectionDenied> { + Ok(NotifsHandler::new( + peer, + ConnectedPoint::Dialer { address: addr.clone(), role_override }, + self.notif_protocols.clone(), + )) } fn on_swarm_event(&mut self, event: FromSwarm) { @@ -1070,7 +1057,7 @@ impl NetworkBehaviour for Notifications { connection_id, .. }) => { - for set_id in (0..self.notif_protocols.len()).map(sc_peerset::SetId::from) { + for set_id in (0..self.notif_protocols.len()).map(crate::peerset::SetId::from) { match self.peers.entry((peer_id, set_id)).or_insert(PeerState::Poisoned) { // Requested | PendingRequest => Enabled st @ &mut PeerState::Requested | @@ -1080,7 +1067,7 @@ impl NetworkBehaviour for Notifications { peer_id, set_id, endpoint ); trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Open({:?})", peer_id, connection_id, set_id); - self.events.push_back(NetworkBehaviourAction::NotifyHandler { + self.events.push_back(ToSwarm::NotifyHandler { peer_id, handler: NotifyHandler::One(connection_id), event: NotifsHandlerIn::Open { protocol_index: set_id.into() }, @@ -1124,7 +1111,7 @@ impl NetworkBehaviour for Notifications { } }, FromSwarm::ConnectionClosed(ConnectionClosed { peer_id, connection_id, .. }) => { - for set_id in (0..self.notif_protocols.len()).map(sc_peerset::SetId::from) { + for set_id in (0..self.notif_protocols.len()).map(crate::peerset::SetId::from) { let mut entry = if let Entry::Occupied(entry) = self.peers.entry((peer_id, set_id)) { @@ -1218,7 +1205,7 @@ impl NetworkBehaviour for Notifications { }, // Incoming => Incoming | Disabled | Backoff | Ø - PeerState::Incoming { mut connections, backoff_until } => { + PeerState::Incoming { mut connections, backoff_until, incoming_index } => { trace!( target: "sub-libp2p", "Libp2p => Disconnected({}, {:?}, {:?}): OpenDesiredByRemote.", @@ -1246,8 +1233,9 @@ impl NetworkBehaviour for Notifications { // If no connection is `OpenDesiredByRemote` anymore, clean up the // peerset incoming request. if no_desired_left { - // In the incoming state, we don't report "Dropped". Instead we will - // just ignore the corresponding Accept/Reject. + // In the incoming state, we don't report "Dropped" straight away. + // Instead we will report "Dropped" if receive the corresponding + // "Accept". if let Some(state) = self .incoming .iter_mut() @@ -1292,8 +1280,11 @@ impl NetworkBehaviour for Notifications { *entry.get_mut() = PeerState::Disabled { connections, backoff_until }; } else { - *entry.get_mut() = - PeerState::Incoming { connections, backoff_until }; + *entry.get_mut() = PeerState::Incoming { + connections, + backoff_until, + incoming_index, + }; } }, @@ -1334,9 +1325,7 @@ impl NetworkBehaviour for Notifications { set_id, notifications_sink: replacement_sink, }; - self.events.push_back( - NetworkBehaviourAction::GenerateEvent(event), - ); + self.events.push_back(ToSwarm::GenerateEvent(event)); } } else { trace!( @@ -1347,9 +1336,7 @@ impl NetworkBehaviour for Notifications { peer_id, set_id, }; - self.events.push_back( - NetworkBehaviourAction::GenerateEvent(event), - ); + self.events.push_back(ToSwarm::GenerateEvent(event)); } } } else { @@ -1417,7 +1404,7 @@ impl NetworkBehaviour for Notifications { if let Some(peer_id) = peer_id { trace!(target: "sub-libp2p", "Libp2p => Dial failure for {:?}", peer_id); - for set_id in (0..self.notif_protocols.len()).map(sc_peerset::SetId::from) { + for set_id in (0..self.notif_protocols.len()).map(crate::peerset::SetId::from) { if let Entry::Occupied(mut entry) = self.peers.entry((peer_id, set_id)) { match mem::replace(entry.get_mut(), PeerState::Poisoned) { // The peer is not in our list. @@ -1492,12 +1479,11 @@ impl NetworkBehaviour for Notifications { &mut self, peer_id: PeerId, connection_id: ConnectionId, - event: <::Handler as - ConnectionHandler>::OutEvent, + event: THandlerOutEvent, ) { match event { NotifsHandlerOut::OpenDesiredByRemote { protocol_index } => { - let set_id = sc_peerset::SetId::from(protocol_index); + let set_id = crate::peerset::SetId::from(protocol_index); trace!(target: "sub-libp2p", "Handler({:?}, {:?}]) => OpenDesiredByRemote({:?})", @@ -1517,7 +1503,7 @@ impl NetworkBehaviour for Notifications { match mem::replace(entry.get_mut(), PeerState::Poisoned) { // Incoming => Incoming - PeerState::Incoming { mut connections, backoff_until } => { + PeerState::Incoming { mut connections, backoff_until, incoming_index } => { debug_assert!(connections .iter() .any(|(_, s)| matches!(s, ConnectionState::OpenDesiredByRemote))); @@ -1545,7 +1531,8 @@ impl NetworkBehaviour for Notifications { debug_assert!(false); } - *entry.into_mut() = PeerState::Incoming { connections, backoff_until }; + *entry.into_mut() = + PeerState::Incoming { connections, backoff_until, incoming_index }; }, PeerState::Enabled { mut connections } => { @@ -1560,7 +1547,7 @@ impl NetworkBehaviour for Notifications { if let ConnectionState::Closed = *connec_state { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Open({:?})", peer_id, connection_id, set_id); - self.events.push_back(NetworkBehaviourAction::NotifyHandler { + self.events.push_back(ToSwarm::NotifyHandler { peer_id, handler: NotifyHandler::One(connection_id), event: NotifsHandlerIn::Open { protocol_index: set_id.into() }, @@ -1600,8 +1587,8 @@ impl NetworkBehaviour for Notifications { let incoming_id = self.next_incoming_index; self.next_incoming_index.0 += 1; - trace!(target: "sub-libp2p", "PSM <= Incoming({}, {:?}).", - peer_id, incoming_id); + trace!(target: "sub-libp2p", "PSM <= Incoming({}, {:?}, {:?}).", + peer_id, set_id, incoming_id); self.peerset.incoming(set_id, peer_id, incoming_id); self.incoming.push(IncomingPeer { peer_id, @@ -1610,8 +1597,11 @@ impl NetworkBehaviour for Notifications { incoming_id, }); - *entry.into_mut() = - PeerState::Incoming { connections, backoff_until }; + *entry.into_mut() = PeerState::Incoming { + connections, + backoff_until, + incoming_index: incoming_id, + }; } else { // Connections in `OpeningThenClosing` and `Closing` state can be // in a Closed phase, and as such can emit `OpenDesiredByRemote` @@ -1641,7 +1631,7 @@ impl NetworkBehaviour for Notifications { if let ConnectionState::Closed = *connec_state { trace!(target: "sub-libp2p", "Handler({:?}, {:?}) <= Open({:?})", peer_id, connection_id, set_id); - self.events.push_back(NetworkBehaviourAction::NotifyHandler { + self.events.push_back(ToSwarm::NotifyHandler { peer_id, handler: NotifyHandler::One(connection_id), event: NotifsHandlerIn::Open { protocol_index: set_id.into() }, @@ -1683,7 +1673,7 @@ impl NetworkBehaviour for Notifications { }, NotifsHandlerOut::CloseDesired { protocol_index } => { - let set_id = sc_peerset::SetId::from(protocol_index); + let set_id = crate::peerset::SetId::from(protocol_index); trace!(target: "sub-libp2p", "Handler({}, {:?}) => CloseDesired({:?})", @@ -1726,7 +1716,7 @@ impl NetworkBehaviour for Notifications { connections[pos].1 = ConnectionState::Closing; trace!(target: "sub-libp2p", "Handler({}, {:?}) <= Close({:?})", peer_id, connection_id, set_id); - self.events.push_back(NetworkBehaviourAction::NotifyHandler { + self.events.push_back(ToSwarm::NotifyHandler { peer_id, handler: NotifyHandler::One(connection_id), event: NotifsHandlerIn::Close { protocol_index: set_id.into() }, @@ -1738,13 +1728,13 @@ impl NetworkBehaviour for Notifications { _ => None, }) { if pos <= replacement_pos { - trace!(target: "sub-libp2p", "External API <= Sink replaced({:?})", peer_id); + trace!(target: "sub-libp2p", "External API <= Sink replaced({:?}, {:?})", peer_id, set_id); let event = NotificationsOut::CustomProtocolReplaced { peer_id, set_id, notifications_sink: replacement_sink, }; - self.events.push_back(NetworkBehaviourAction::GenerateEvent(event)); + self.events.push_back(ToSwarm::GenerateEvent(event)); } *entry.into_mut() = PeerState::Enabled { connections }; @@ -1764,7 +1754,7 @@ impl NetworkBehaviour for Notifications { trace!(target: "sub-libp2p", "External API <= Closed({}, {:?})", peer_id, set_id); let event = NotificationsOut::CustomProtocolClosed { peer_id, set_id }; - self.events.push_back(NetworkBehaviourAction::GenerateEvent(event)); + self.events.push_back(ToSwarm::GenerateEvent(event)); } }, @@ -1783,7 +1773,7 @@ impl NetworkBehaviour for Notifications { }, NotifsHandlerOut::CloseResult { protocol_index } => { - let set_id = sc_peerset::SetId::from(protocol_index); + let set_id = crate::peerset::SetId::from(protocol_index); trace!(target: "sub-libp2p", "Handler({}, {:?}) => CloseResult({:?})", @@ -1822,7 +1812,7 @@ impl NetworkBehaviour for Notifications { notifications_sink, .. } => { - let set_id = sc_peerset::SetId::from(protocol_index); + let set_id = crate::peerset::SetId::from(protocol_index); trace!(target: "sub-libp2p", "Handler({}, {:?}) => OpenResultOk({:?})", peer_id, connection_id, set_id); @@ -1848,7 +1838,7 @@ impl NetworkBehaviour for Notifications { received_handshake, notifications_sink: notifications_sink.clone(), }; - self.events.push_back(NetworkBehaviourAction::GenerateEvent(event)); + self.events.push_back(ToSwarm::GenerateEvent(event)); } *connec_state = ConnectionState::Open(notifications_sink); } else if let Some((_, connec_state)) = @@ -1888,7 +1878,7 @@ impl NetworkBehaviour for Notifications { }, NotifsHandlerOut::OpenResultErr { protocol_index } => { - let set_id = sc_peerset::SetId::from(protocol_index); + let set_id = crate::peerset::SetId::from(protocol_index); trace!(target: "sub-libp2p", "Handler({:?}, {:?}) => OpenResultErr({:?})", peer_id, connection_id, set_id); @@ -1928,7 +1918,7 @@ impl NetworkBehaviour for Notifications { if !connections.iter().any(|(_, s)| { matches!(s, ConnectionState::Opening | ConnectionState::Open(_)) }) { - trace!(target: "sub-libp2p", "PSM <= Dropped({:?})", peer_id); + trace!(target: "sub-libp2p", "PSM <= Dropped({:?}, {:?})", peer_id, set_id); self.peerset.dropped(set_id, peer_id, DropReason::Refused); let ban_dur = Uniform::new(5, 10).sample(&mut rand::thread_rng()); @@ -1977,7 +1967,7 @@ impl NetworkBehaviour for Notifications { }, NotifsHandlerOut::Notification { protocol_index, message } => { - let set_id = sc_peerset::SetId::from(protocol_index); + let set_id = crate::peerset::SetId::from(protocol_index); if self.is_open(&peer_id, set_id) { trace!( target: "sub-libp2p", @@ -1995,7 +1985,7 @@ impl NetworkBehaviour for Notifications { ); let event = NotificationsOut::Notification { peer_id, set_id, message }; - self.events.push_back(NetworkBehaviourAction::GenerateEvent(event)); + self.events.push_back(ToSwarm::GenerateEvent(event)); } else { trace!( target: "sub-libp2p", @@ -2014,7 +2004,7 @@ impl NetworkBehaviour for Notifications { &mut self, cx: &mut Context, _params: &mut impl PollParameters, - ) -> Poll> { + ) -> Poll>> { if let Some(event) = self.events.pop_front() { return Poll::Ready(event) } @@ -2023,16 +2013,16 @@ impl NetworkBehaviour for Notifications { // Note that the peerset is a *best effort* crate, and we have to use defensive programming. loop { match futures::Stream::poll_next(Pin::new(&mut self.peerset), cx) { - Poll::Ready(Some(sc_peerset::Message::Accept(index))) => { + Poll::Ready(Some(crate::peerset::Message::Accept(index))) => { self.peerset_report_accept(index); }, - Poll::Ready(Some(sc_peerset::Message::Reject(index))) => { + Poll::Ready(Some(crate::peerset::Message::Reject(index))) => { self.peerset_report_reject(index); }, - Poll::Ready(Some(sc_peerset::Message::Connect { peer_id, set_id, .. })) => { + Poll::Ready(Some(crate::peerset::Message::Connect { peer_id, set_id, .. })) => { self.peerset_report_connect(peer_id, set_id); }, - Poll::Ready(Some(sc_peerset::Message::Drop { peer_id, set_id, .. })) => { + Poll::Ready(Some(crate::peerset::Message::Drop { peer_id, set_id, .. })) => { self.peerset_report_disconnect(peer_id, set_id); }, Poll::Ready(None) => { @@ -2046,8 +2036,6 @@ impl NetworkBehaviour for Notifications { while let Poll::Ready(Some((delay_id, peer_id, set_id))) = Pin::new(&mut self.delays).poll_next(cx) { - let handler = self.new_handler(); - let peer_state = match self.peers.get_mut(&(peer_id, set_id)) { Some(s) => s, // We intentionally never remove elements from `delays`, and it may @@ -2057,14 +2045,13 @@ impl NetworkBehaviour for Notifications { match peer_state { PeerState::Backoff { timer, .. } if *timer == delay_id => { - trace!(target: "sub-libp2p", "Libp2p <= Clean up ban of {:?} from the state", peer_id); + trace!(target: "sub-libp2p", "Libp2p <= Clean up ban of {:?} from the state ({:?})", peer_id, set_id); self.peers.remove(&(peer_id, set_id)); }, PeerState::PendingRequest { timer, .. } if *timer == delay_id => { - trace!(target: "sub-libp2p", "Libp2p <= Dial {:?} now that ban has expired", peer_id); - self.events - .push_back(NetworkBehaviourAction::Dial { opts: peer_id.into(), handler }); + trace!(target: "sub-libp2p", "Libp2p <= Dial {:?} now that ban has expired ({:?})", peer_id, set_id); + self.events.push_back(ToSwarm::Dial { opts: peer_id.into() }); *peer_state = PeerState::Requested; }, @@ -2077,7 +2064,7 @@ impl NetworkBehaviour for Notifications { { trace!(target: "sub-libp2p", "Handler({}, {:?}) <= Open({:?}) (ban expired)", peer_id, *connec_id, set_id); - self.events.push_back(NetworkBehaviourAction::NotifyHandler { + self.events.push_back(ToSwarm::NotifyHandler { peer_id, handler: NotifyHandler::One(*connec_id), event: NotifsHandlerIn::Open { protocol_index: set_id.into() }, @@ -2111,3 +2098,2465 @@ impl NetworkBehaviour for Notifications { Poll::Pending } } + +#[cfg(test)] +#[allow(deprecated)] +mod tests { + use super::*; + use crate::{peerset::IncomingIndex, protocol::notifications::handler::tests::*}; + use libp2p::swarm::AddressRecord; + use std::{collections::HashSet, iter}; + + impl PartialEq for ConnectionState { + fn eq(&self, other: &ConnectionState) -> bool { + match (self, other) { + (ConnectionState::Closed, ConnectionState::Closed) => true, + (ConnectionState::Closing, ConnectionState::Closing) => true, + (ConnectionState::Opening, ConnectionState::Opening) => true, + (ConnectionState::OpeningThenClosing, ConnectionState::OpeningThenClosing) => true, + (ConnectionState::OpenDesiredByRemote, ConnectionState::OpenDesiredByRemote) => + true, + (ConnectionState::Open(_), ConnectionState::Open(_)) => true, + _ => false, + } + } + } + + #[derive(Clone)] + struct MockPollParams { + peer_id: PeerId, + addr: Multiaddr, + } + + impl PollParameters for MockPollParams { + type SupportedProtocolsIter = std::vec::IntoIter>; + type ListenedAddressesIter = std::vec::IntoIter; + type ExternalAddressesIter = std::vec::IntoIter; + + fn supported_protocols(&self) -> Self::SupportedProtocolsIter { + vec![].into_iter() + } + + fn listened_addresses(&self) -> Self::ListenedAddressesIter { + vec![self.addr.clone()].into_iter() + } + + fn external_addresses(&self) -> Self::ExternalAddressesIter { + vec![].into_iter() + } + + fn local_peer_id(&self) -> &PeerId { + &self.peer_id + } + } + + fn development_notifs() -> (Notifications, crate::peerset::PeersetHandle) { + let (peerset, peerset_handle) = { + let mut sets = Vec::with_capacity(1); + + sets.push(crate::peerset::SetConfig { + in_peers: 25, + out_peers: 25, + bootnodes: Vec::new(), + reserved_nodes: HashSet::new(), + reserved_only: false, + }); + + crate::peerset::Peerset::from_config(crate::peerset::PeersetConfig { sets }) + }; + + ( + Notifications::new( + peerset, + iter::once(ProtocolConfig { + name: "/foo".into(), + fallback_names: Vec::new(), + handshake: vec![1, 2, 3, 4], + max_notification_size: u64::MAX, + }), + ), + peerset_handle, + ) + } + + #[test] + fn update_handshake() { + let (mut notif, _peerset) = development_notifs(); + + let inner = notif.notif_protocols.get_mut(0).unwrap().handshake.read().clone(); + assert_eq!(inner, vec![1, 2, 3, 4]); + + notif.set_notif_protocol_handshake(0.into(), vec![5, 6, 7, 8]); + + let inner = notif.notif_protocols.get_mut(0).unwrap().handshake.read().clone(); + assert_eq!(inner, vec![5, 6, 7, 8]); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn update_unknown_handshake() { + let (mut notif, _peerset) = development_notifs(); + + notif.set_notif_protocol_handshake(1337.into(), vec![5, 6, 7, 8]); + } + + #[test] + fn disconnect_backoff_peer() { + let (mut notif, _peerset) = development_notifs(); + + let peer = PeerId::random(); + notif.peers.insert( + (peer, 0.into()), + PeerState::Backoff { timer: DelayId(0), timer_deadline: Instant::now() }, + ); + notif.disconnect_peer(&peer, 0.into()); + + assert!(std::matches!( + notif.peers.get(&(peer, 0.into())), + Some(PeerState::Backoff { timer: DelayId(0), .. }) + )); + } + + #[test] + fn disconnect_pending_request() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + + notif.peers.insert( + (peer, 0.into()), + PeerState::PendingRequest { timer: DelayId(0), timer_deadline: Instant::now() }, + ); + notif.disconnect_peer(&peer, 0.into()); + + assert!(std::matches!( + notif.peers.get(&(peer, 0.into())), + Some(PeerState::PendingRequest { timer: DelayId(0), .. }) + )); + } + + #[test] + fn disconnect_requested_peer() { + let (mut notif, _peerset) = development_notifs(); + + let peer = PeerId::random(); + notif.peers.insert((peer, 0.into()), PeerState::Requested); + notif.disconnect_peer(&peer, 0.into()); + + assert!(std::matches!(notif.peers.get(&(peer, 0.into())), Some(PeerState::Requested))); + } + + #[test] + fn disconnect_disabled_peer() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + notif.peers.insert( + (peer, 0.into()), + PeerState::Disabled { backoff_until: None, connections: SmallVec::new() }, + ); + notif.disconnect_peer(&peer, 0.into()); + + assert!(std::matches!( + notif.peers.get(&(peer, 0.into())), + Some(PeerState::Disabled { backoff_until: None, .. }) + )); + } + + #[test] + fn remote_opens_connection_and_substream() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + + if let Some(&PeerState::Disabled { ref connections, backoff_until: None }) = + notif.peers.get(&(peer, 0.into())) + { + assert_eq!(connections[0], (conn, ConnectionState::Closed)); + } else { + panic!("invalid state"); + } + + // remote opens a substream, verify that peer state is updated to `Incoming` + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + + if let Some(&PeerState::Incoming { ref connections, backoff_until: None, .. }) = + notif.peers.get(&(peer, 0.into())) + { + assert_eq!(connections.len(), 1); + assert_eq!(connections[0], (conn, ConnectionState::OpenDesiredByRemote)); + } else { + panic!("invalid state"); + } + + assert!(std::matches!( + notif.incoming.pop(), + Some(IncomingPeer { alive: true, incoming_id: crate::peerset::IncomingIndex(0), .. }), + )); + } + + #[tokio::test] + async fn disconnect_remote_substream_before_handled_by_peerset() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + notif.disconnect_peer(&peer, 0.into()); + + if let Some(&PeerState::Disabled { ref connections, backoff_until: None }) = + notif.peers.get(&(peer, 0.into())) + { + assert_eq!(connections.len(), 1); + assert_eq!(connections[0], (conn, ConnectionState::Closing)); + } else { + panic!("invalid state"); + } + } + + #[test] + fn peerset_report_connect_backoff() { + let (mut notif, _peerset) = development_notifs(); + let set_id = crate::peerset::SetId::from(0); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // manually add backoff for the entry + // + // there is not straight-forward way of adding backoff to `PeerState::Disabled` + // so manually adjust the value in order to progress on to the next stage. + // This modification together with `ConnectionClosed` will conver the peer + // state into `PeerState::Backoff`. + if let Some(PeerState::Disabled { ref mut backoff_until, .. }) = + notif.peers.get_mut(&(peer, set_id)) + { + *backoff_until = + Some(Instant::now().checked_add(std::time::Duration::from_secs(5)).unwrap()); + } + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + + let timer = if let Some(&PeerState::Backoff { timer_deadline, .. }) = + notif.peers.get(&(peer, set_id)) + { + timer_deadline + } else { + panic!("invalid state"); + }; + + // attempt to connect the backed-off peer and verify that the request is pending + notif.peerset_report_connect(peer, set_id); + + if let Some(&PeerState::PendingRequest { timer_deadline, .. }) = + notif.peers.get(&(peer, set_id)) + { + assert_eq!(timer, timer_deadline); + } else { + panic!("invalid state"); + } + } + + #[test] + fn peerset_connect_incoming() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // remote opens a substream, verify that peer state is updated to `Incoming` + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + + // attempt to connect to the peer and verify that the peer state is `Enabled`; + // we rely on implementation detail that incoming indices are counted from 0 + // to not mock the `Peerset` + notif.peerset_report_accept(IncomingIndex(0)); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); + } + + #[test] + fn peerset_disconnect_disable_pending_enable() { + let (mut notif, _peerset) = development_notifs(); + let set_id = crate::peerset::SetId::from(0); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // manually add backoff for the entry + if let Some(PeerState::Disabled { ref mut backoff_until, .. }) = + notif.peers.get_mut(&(peer, set_id)) + { + *backoff_until = + Some(Instant::now().checked_add(std::time::Duration::from_secs(5)).unwrap()); + } + + // switch state to `DisabledPendingEnable` + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!( + notif.peers.get(&(peer, set_id)), + Some(&PeerState::DisabledPendingEnable { .. }) + )); + + notif.peerset_report_disconnect(peer, set_id); + + if let Some(PeerState::Disabled { backoff_until, .. }) = notif.peers.get(&(peer, set_id)) { + assert!(backoff_until.is_some()); + assert!(backoff_until.unwrap() > Instant::now()); + } else { + panic!("invalid state"); + } + } + + #[test] + fn peerset_disconnect_enabled() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + // Set peer into `Enabled` state. + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + // we rely on the implementation detail that incoming indices are counted from 0 + // to not mock the `Peerset` + notif.peerset_report_accept(IncomingIndex(0)); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); + + // disconnect peer and verify that the state is `Disabled` + notif.peerset_report_disconnect(peer, set_id); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + } + + #[test] + fn peerset_disconnect_requested() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let set_id = crate::peerset::SetId::from(0); + + // Set peer into `Requested` state. + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Requested))); + + // disconnect peer and verify that the state is `Disabled` + notif.peerset_report_disconnect(peer, set_id); + assert!(notif.peers.get(&(peer, set_id)).is_none()); + } + + #[test] + fn peerset_disconnect_pending_request() { + let (mut notif, _peerset) = development_notifs(); + let set_id = crate::peerset::SetId::from(0); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // manually add backoff for the entry + if let Some(PeerState::Disabled { ref mut backoff_until, .. }) = + notif.peers.get_mut(&(peer, set_id)) + { + *backoff_until = + Some(Instant::now().checked_add(std::time::Duration::from_secs(5)).unwrap()); + } + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Backoff { .. }))); + + // attempt to connect the backed-off peer and verify that the request is pending + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!( + notif.peers.get(&(peer, set_id)), + Some(&PeerState::PendingRequest { .. }) + )); + + // attempt to disconnect the backed-off peer and verify that the request is pending + notif.peerset_report_disconnect(peer, set_id); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Backoff { .. }))); + } + + #[test] + fn peerset_accept_peer_not_alive() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // remote opens a substream, verify that peer state is updated to `Incoming` + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + + assert!(std::matches!( + notif.incoming[0], + IncomingPeer { alive: true, incoming_id: crate::peerset::IncomingIndex(0), .. }, + )); + + notif.disconnect_peer(&peer, set_id); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + assert!(std::matches!( + notif.incoming[0], + IncomingPeer { alive: false, incoming_id: crate::peerset::IncomingIndex(0), .. }, + )); + + notif.peerset_report_accept(crate::peerset::IncomingIndex(0)); + assert_eq!(notif.incoming.len(), 0); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(PeerState::Disabled { .. }))); + } + + #[test] + fn secondary_connection_peer_state_incoming() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let conn2 = ConnectionId::new_unchecked(1); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + if let Some(PeerState::Incoming { connections, .. }) = notif.peers.get(&(peer, set_id)) { + assert_eq!(connections.len(), 1); + assert_eq!(connections[0], (conn, ConnectionState::OpenDesiredByRemote)); + } else { + panic!("invalid state"); + } + + // add another connection + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn2, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + + if let Some(PeerState::Incoming { connections, .. }) = notif.peers.get(&(peer, set_id)) { + assert_eq!(connections.len(), 2); + assert_eq!(connections[0], (conn, ConnectionState::OpenDesiredByRemote)); + assert_eq!(connections[1], (conn2, ConnectionState::Closed)); + } else { + panic!("invalid state"); + } + } + + #[test] + fn close_connection_for_disabled_peer() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + assert!(notif.peers.get(&(peer, set_id)).is_none()); + } + + #[test] + fn close_connection_for_incoming_peer_one_connection() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + assert!(notif.peers.get(&(peer, set_id)).is_none()); + assert!(std::matches!( + notif.incoming[0], + IncomingPeer { alive: false, incoming_id: crate::peerset::IncomingIndex(0), .. }, + )); + } + + #[test] + fn close_connection_for_incoming_peer_two_connections() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let conn1 = ConnectionId::new_unchecked(1); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + let mut conns = SmallVec::< + [(ConnectionId, ConnectionState); crate::MAX_CONNECTIONS_PER_PEER], + >::from(vec![(conn, ConnectionState::Closed)]); + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn1, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + conns.push((conn1, ConnectionState::Closed)); + + if let Some(PeerState::Incoming { ref connections, .. }) = notif.peers.get(&(peer, set_id)) + { + assert_eq!(connections.len(), 2); + assert_eq!(connections[0], (conn, ConnectionState::OpenDesiredByRemote)); + assert_eq!(connections[1], (conn1, ConnectionState::Closed)); + } + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + + if let Some(PeerState::Disabled { connections, .. }) = notif.peers.get(&(peer, set_id)) { + assert_eq!(connections.len(), 1); + assert_eq!(connections[0], (conn1, ConnectionState::Closed)); + } else { + panic!("invalid state"); + } + } + + #[test] + fn connection_and_substream_open() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + let mut conn_yielder = ConnectionYielder::new(); + + // move the peer to `Enabled` state + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + + // We rely on the implementation detail that incoming indices are counted + // from 0 to not mock the `Peerset`. + notif.peerset_report_accept(crate::peerset::IncomingIndex(0)); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); + + // open new substream + let event = conn_yielder.open_substream(peer, 0, connected, vec![1, 2, 3, 4]); + + notif.on_connection_handler_event(peer, conn, event); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); + + if let Some(PeerState::Enabled { ref connections, .. }) = notif.peers.get(&(peer, set_id)) { + assert_eq!(connections.len(), 1); + assert_eq!(connections[0].0, conn); + assert!(std::matches!(connections[0].1, ConnectionState::Open(_))); + } + + assert!(std::matches!( + notif.events[notif.events.len() - 1], + ToSwarm::GenerateEvent(NotificationsOut::CustomProtocolOpen { .. }) + )); + } + + #[test] + fn connection_closed_sink_replaced() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn1 = ConnectionId::new_unchecked(0); + let conn2 = ConnectionId::new_unchecked(1); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + let mut conn_yielder = ConnectionYielder::new(); + + // open two connections + for conn_id in vec![conn1, conn2] { + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn_id, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + } + + if let Some(PeerState::Disabled { connections, .. }) = notif.peers.get(&(peer, set_id)) { + assert_eq!(connections[0], (conn1, ConnectionState::Closed)); + assert_eq!(connections[1], (conn2, ConnectionState::Closed)); + } else { + panic!("invalid state"); + } + + // open substreams on both active connections + notif.peerset_report_connect(peer, set_id); + notif.on_connection_handler_event( + peer, + conn2, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + + if let Some(PeerState::Enabled { connections, .. }) = notif.peers.get(&(peer, set_id)) { + assert_eq!(connections[0], (conn1, ConnectionState::Opening)); + assert_eq!(connections[1], (conn2, ConnectionState::Opening)); + } else { + panic!("invalid state"); + } + + // add two new substreams, one for each connection and verify that both are in open state + for conn in vec![conn1, conn2].iter() { + notif.on_connection_handler_event( + peer, + *conn, + conn_yielder.open_substream(peer, 0, connected.clone(), vec![1, 2, 3, 4]), + ); + } + + if let Some(PeerState::Enabled { ref connections, .. }) = notif.peers.get(&(peer, set_id)) { + assert_eq!(connections[0].0, conn1); + assert!(std::matches!(connections[0].1, ConnectionState::Open(_))); + assert_eq!(connections[1].0, conn2); + assert!(std::matches!(connections[1].1, ConnectionState::Open(_))); + } else { + panic!("invalid state"); + } + + // check peer information + assert_eq!(notif.open_peers().collect::>(), vec![&peer],); + assert_eq!(notif.num_discovered_peers(), 0usize); + + // close the other connection and verify that notification replacement event is emitted + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn1, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + + if let Some(PeerState::Enabled { ref connections, .. }) = notif.peers.get(&(peer, set_id)) { + assert_eq!(connections.len(), 1); + assert_eq!(connections[0].0, conn2); + assert!(std::matches!(connections[0].1, ConnectionState::Open(_))); + } else { + panic!("invalid state"); + } + + assert!(std::matches!( + notif.events[notif.events.len() - 1], + ToSwarm::GenerateEvent(NotificationsOut::CustomProtocolReplaced { .. }) + )); + } + + #[test] + fn dial_failure_for_requested_peer() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let set_id = crate::peerset::SetId::from(0); + + // Set peer into `Requested` state. + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Requested))); + + notif.on_swarm_event(FromSwarm::DialFailure(libp2p::swarm::behaviour::DialFailure { + peer_id: Some(peer), + error: &libp2p::swarm::DialError::Banned, + connection_id: ConnectionId::new_unchecked(1337), + })); + + if let Some(PeerState::Backoff { timer_deadline, .. }) = notif.peers.get(&(peer, set_id)) { + assert!(timer_deadline > &Instant::now()); + } else { + panic!("invalid state"); + } + } + + #[tokio::test] + async fn write_notification() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + let mut conn_yielder = ConnectionYielder::new(); + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); + + notif.on_connection_handler_event( + peer, + conn, + conn_yielder.open_substream(peer, 0, connected, vec![1, 2, 3, 4]), + ); + + if let Some(PeerState::Enabled { ref connections, .. }) = notif.peers.get(&(peer, set_id)) { + assert_eq!(connections[0].0, conn); + assert!(std::matches!(connections[0].1, ConnectionState::Open(_))); + } else { + panic!("invalid state"); + } + + notif + .peers + .get(&(peer, set_id)) + .unwrap() + .get_open() + .unwrap() + .send_sync_notification(vec![1, 3, 3, 7]); + assert_eq!(conn_yielder.get_next_event(peer, set_id.into()).await, Some(vec![1, 3, 3, 7])); + } + + #[test] + fn peerset_report_connect_backoff_expired() { + let (mut notif, _peerset) = development_notifs(); + let set_id = crate::peerset::SetId::from(0); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + let backoff_duration = Duration::from_millis(100); + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // manually add backoff for the entry + if let Some(PeerState::Disabled { ref mut backoff_until, .. }) = + notif.peers.get_mut(&(peer, set_id)) + { + *backoff_until = Some(Instant::now().checked_add(backoff_duration).unwrap()); + } + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + + // wait until the backoff time has passed + std::thread::sleep(backoff_duration * 2); + + // attempt to connect the backed-off peer and verify that the request is pending + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Requested { .. }))) + } + + #[test] + fn peerset_report_disconnect_disabled() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let set_id = crate::peerset::SetId::from(0); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + notif.peerset_report_disconnect(peer, set_id); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + } + + #[test] + fn peerset_report_disconnect_backoff() { + let (mut notif, _peerset) = development_notifs(); + let set_id = crate::peerset::SetId::from(0); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + let backoff_duration = Duration::from_secs(2); + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // manually add backoff for the entry + if let Some(PeerState::Disabled { ref mut backoff_until, .. }) = + notif.peers.get_mut(&(peer, set_id)) + { + *backoff_until = Some(Instant::now().checked_add(backoff_duration).unwrap()); + } + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Backoff { .. }))); + + notif.peerset_report_disconnect(peer, set_id); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Backoff { .. }))); + } + + #[test] + fn peer_is_backed_off_if_both_connections_get_closed_while_peer_is_disabled_with_back_off() { + let (mut notif, _peerset) = development_notifs(); + let set_id = crate::peerset::SetId::from(0); + let peer = PeerId::random(); + let conn1 = ConnectionId::new_unchecked(0); + let conn2 = ConnectionId::new_unchecked(1); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn1, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn2, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // manually add backoff for the entry + if let Some(PeerState::Disabled { ref mut backoff_until, .. }) = + notif.peers.get_mut(&(peer, set_id)) + { + *backoff_until = + Some(Instant::now().checked_add(std::time::Duration::from_secs(5)).unwrap()); + } + + // switch state to `DisabledPendingEnable` + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!( + notif.peers.get(&(peer, set_id)), + Some(&PeerState::DisabledPendingEnable { .. }) + )); + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn1, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected.clone(), vec![]), + remaining_established: 0usize, + }, + )); + assert!(std::matches!( + notif.peers.get(&(peer, set_id)), + Some(&PeerState::DisabledPendingEnable { .. }) + )); + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn2, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Backoff { .. }))); + } + + #[test] + fn inject_connection_closed_incoming_with_backoff() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let set_id = crate::peerset::SetId::from(0); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // remote opens a substream, verify that peer state is updated to `Incoming` + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + + // manually add backoff for the entry + if let Some(&mut PeerState::Incoming { ref mut backoff_until, .. }) = + notif.peers.get_mut(&(peer, 0.into())) + { + *backoff_until = + Some(Instant::now().checked_add(std::time::Duration::from_secs(5)).unwrap()); + } else { + panic!("invalid state"); + } + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Backoff { .. }))); + } + + #[test] + fn two_connections_inactive_connection_gets_closed_peer_state_is_still_incoming() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn1 = ConnectionId::new_unchecked(0); + let conn2 = ConnectionId::new_unchecked(1); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + // open two connections + for conn_id in vec![conn1, conn2] { + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn_id, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + } + + if let Some(PeerState::Disabled { connections, .. }) = notif.peers.get(&(peer, set_id)) { + assert_eq!(connections[0], (conn1, ConnectionState::Closed)); + assert_eq!(connections[1], (conn2, ConnectionState::Closed)); + } else { + panic!("invalid state"); + } + + // remote opens a substream, verify that peer state is updated to `Incoming` + notif.on_connection_handler_event( + peer, + conn1, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!( + notif.peers.get_mut(&(peer, 0.into())), + Some(&mut PeerState::Incoming { .. }) + )); + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn2, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + } + + #[test] + fn two_connections_active_connection_gets_closed_peer_state_is_disabled() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn1 = ConnectionId::new_unchecked(0); + let conn2 = ConnectionId::new_unchecked(1); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + // open two connections + for conn_id in vec![conn1, conn2] { + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn_id, + endpoint: &ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }, + failed_addresses: &[], + other_established: 0usize, + }, + )); + } + + if let Some(PeerState::Disabled { connections, .. }) = notif.peers.get(&(peer, set_id)) { + assert_eq!(connections[0], (conn1, ConnectionState::Closed)); + assert_eq!(connections[1], (conn2, ConnectionState::Closed)); + } else { + panic!("invalid state"); + } + + // remote opens a substream, verify that peer state is updated to `Incoming` + notif.on_connection_handler_event( + peer, + conn1, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!( + notif.peers.get_mut(&(peer, 0.into())), + Some(PeerState::Incoming { .. }) + )); + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn1, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + } + + #[test] + fn inject_connection_closed_for_active_connection() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn1 = ConnectionId::new_unchecked(0); + let conn2 = ConnectionId::new_unchecked(1); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + let mut conn_yielder = ConnectionYielder::new(); + + // open two connections + for conn_id in vec![conn1, conn2] { + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn_id, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + } + + if let Some(PeerState::Disabled { connections, .. }) = notif.peers.get(&(peer, set_id)) { + assert_eq!(connections[0], (conn1, ConnectionState::Closed)); + assert_eq!(connections[1], (conn2, ConnectionState::Closed)); + } else { + panic!("invalid state"); + } + + // open substreams on both active connections + notif.peerset_report_connect(peer, set_id); + + if let Some(PeerState::Enabled { connections, .. }) = notif.peers.get(&(peer, set_id)) { + assert_eq!(connections[0], (conn1, ConnectionState::Opening)); + assert_eq!(connections[1], (conn2, ConnectionState::Closed)); + } else { + panic!("invalid state"); + } + + notif.on_connection_handler_event( + peer, + conn1, + conn_yielder.open_substream(peer, 0, connected.clone(), vec![1, 2, 3, 4]), + ); + + if let Some(PeerState::Enabled { ref connections, .. }) = notif.peers.get(&(peer, set_id)) { + assert!(std::matches!(connections[0].1, ConnectionState::Open(_))); + assert_eq!(connections[0].0, conn1); + assert_eq!(connections[1], (conn2, ConnectionState::Closed)); + } else { + panic!("invalid state"); + } + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn1, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + } + + #[test] + fn inject_dial_failure_for_pending_request() { + let (mut notif, _peerset) = development_notifs(); + let set_id = crate::peerset::SetId::from(0); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // manually add backoff for the entry + if let Some(PeerState::Disabled { ref mut backoff_until, .. }) = + notif.peers.get_mut(&(peer, set_id)) + { + *backoff_until = + Some(Instant::now().checked_add(std::time::Duration::from_secs(5)).unwrap()); + } + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Backoff { .. }))); + + // attempt to connect the backed-off peer and verify that the request is pending + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!( + notif.peers.get(&(peer, set_id)), + Some(&PeerState::PendingRequest { .. }) + )); + + let now = Instant::now(); + notif.on_swarm_event(FromSwarm::DialFailure(libp2p::swarm::behaviour::DialFailure { + peer_id: Some(peer), + error: &libp2p::swarm::DialError::Banned, + connection_id: ConnectionId::new_unchecked(0), + })); + + if let Some(PeerState::PendingRequest { ref timer_deadline, .. }) = + notif.peers.get(&(peer, set_id)) + { + assert!(timer_deadline > &(now + std::time::Duration::from_secs(5))); + } + } + + #[test] + fn peerstate_incoming_open_desired_by_remote() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let set_id = crate::peerset::SetId::from(0); + let conn1 = ConnectionId::new_unchecked(0); + let conn2 = ConnectionId::new_unchecked(1); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn1, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn2, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // remote opens a substream, verify that peer state is updated to `Incoming` + notif.on_connection_handler_event( + peer, + conn1, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + + // add another open event from remote + notif.on_connection_handler_event( + peer, + conn2, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + + if let Some(PeerState::Incoming { ref connections, .. }) = notif.peers.get(&(peer, set_id)) + { + assert_eq!(connections[0], (conn1, ConnectionState::OpenDesiredByRemote)); + assert_eq!(connections[1], (conn2, ConnectionState::OpenDesiredByRemote)); + } + } + + #[tokio::test] + async fn remove_backoff_peer_after_timeout() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let set_id = crate::peerset::SetId::from(0); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + + if let Some(&mut PeerState::Disabled { ref mut backoff_until, .. }) = + notif.peers.get_mut(&(peer, 0.into())) + { + *backoff_until = + Some(Instant::now().checked_add(std::time::Duration::from_millis(100)).unwrap()); + } else { + panic!("invalid state"); + } + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + + let until = if let Some(&PeerState::Backoff { timer_deadline, .. }) = + notif.peers.get(&(peer, set_id)) + { + timer_deadline + } else { + panic!("invalid state"); + }; + + if until > Instant::now() { + std::thread::sleep(until - Instant::now()); + } + + assert!(notif.peers.get(&(peer, set_id)).is_some()); + + if tokio::time::timeout(Duration::from_secs(5), async { + let mut params = MockPollParams { peer_id: PeerId::random(), addr: Multiaddr::empty() }; + + loop { + futures::future::poll_fn(|cx| { + let _ = notif.poll(cx, &mut params); + Poll::Ready(()) + }) + .await; + + if notif.peers.get(&(peer, set_id)).is_none() { + break + } + } + }) + .await + .is_err() + { + panic!("backoff peer was not removed in time"); + } + + assert!(notif.peers.get(&(peer, set_id)).is_none()); + } + + #[tokio::test] + async fn reschedule_disabled_pending_enable_when_connection_not_closed() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + let mut conn_yielder = ConnectionYielder::new(); + + // move the peer to `Enabled` state + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // open substream + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + + // we rely on the implementation detail that incoming indices are counted from 0 + // to not mock the `Peerset` + notif.peerset_report_accept(IncomingIndex(0)); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); + + let event = conn_yielder.open_substream(peer, 0, connected, vec![1, 2, 3, 4]); + + notif.on_connection_handler_event(peer, conn, event); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); + + if let Some(PeerState::Enabled { ref connections, .. }) = notif.peers.get(&(peer, set_id)) { + assert!(std::matches!(connections[0], (_, ConnectionState::Open(_)))); + assert_eq!(connections[0].0, conn); + } else { + panic!("invalid state"); + } + + notif.peerset_report_disconnect(peer, set_id); + + if let Some(PeerState::Disabled { ref connections, ref mut backoff_until }) = + notif.peers.get_mut(&(peer, set_id)) + { + assert!(std::matches!(connections[0], (_, ConnectionState::Closing))); + assert_eq!(connections[0].0, conn); + + *backoff_until = + Some(Instant::now().checked_add(std::time::Duration::from_secs(2)).unwrap()); + } else { + panic!("invalid state"); + } + + notif.peerset_report_connect(peer, set_id); + + let prev_instant = + if let Some(PeerState::DisabledPendingEnable { + ref connections, timer_deadline, .. + }) = notif.peers.get(&(peer, set_id)) + { + assert!(std::matches!(connections[0], (_, ConnectionState::Closing))); + assert_eq!(connections[0].0, conn); + + *timer_deadline + } else { + panic!("invalid state"); + }; + + // one of the peers has an active backoff timer so poll the notifications code until + // the timer has expired. Because the connection is still in the state of `Closing`, + // verify that the code continues to keep the peer disabled by resetting the timer + // after the first one expired. + if tokio::time::timeout(Duration::from_secs(5), async { + let mut params = MockPollParams { peer_id: PeerId::random(), addr: Multiaddr::empty() }; + + loop { + futures::future::poll_fn(|cx| { + let _ = notif.poll(cx, &mut params); + Poll::Ready(()) + }) + .await; + + if let Some(PeerState::DisabledPendingEnable { + timer_deadline, connections, .. + }) = notif.peers.get(&(peer, set_id)) + { + assert!(std::matches!(connections[0], (_, ConnectionState::Closing))); + + if timer_deadline != &prev_instant { + break + } + } else { + panic!("invalid state"); + } + } + }) + .await + .is_err() + { + panic!("backoff peer was not removed in time"); + } + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn peerset_report_connect_with_enabled_peer() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + let mut conn_yielder = ConnectionYielder::new(); + + // move the peer to `Enabled` state + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); + + let event = conn_yielder.open_substream(peer, 0, connected, vec![1, 2, 3, 4]); + + notif.on_connection_handler_event(peer, conn, event); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); + + if let Some(PeerState::Enabled { ref connections, .. }) = notif.peers.get(&(peer, set_id)) { + assert!(std::matches!(connections[0], (_, ConnectionState::Open(_)))); + assert_eq!(connections[0].0, conn); + } else { + panic!("invalid state"); + } + + notif.peerset_report_connect(peer, set_id); + } + + #[test] + #[cfg(debug_assertions)] + fn peerset_report_connect_with_disabled_pending_enable_peer() { + let (mut notif, _peerset) = development_notifs(); + let set_id = crate::peerset::SetId::from(0); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // manually add backoff for the entry + if let Some(PeerState::Disabled { ref mut backoff_until, .. }) = + notif.peers.get_mut(&(peer, set_id)) + { + *backoff_until = + Some(Instant::now().checked_add(std::time::Duration::from_secs(5)).unwrap()); + } + + // switch state to `DisabledPendingEnable` + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!( + notif.peers.get(&(peer, set_id)), + Some(&PeerState::DisabledPendingEnable { .. }) + )); + + // duplicate "connect" must not change the state + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!( + notif.peers.get(&(peer, set_id)), + Some(&PeerState::DisabledPendingEnable { .. }) + )); + } + + #[test] + #[cfg(debug_assertions)] + fn peerset_report_connect_with_requested_peer() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let set_id = crate::peerset::SetId::from(0); + + // Set peer into `Requested` state. + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Requested))); + + // Duplicate "connect" must not change the state. + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Requested))); + } + + #[test] + #[cfg(debug_assertions)] + fn peerset_report_connect_with_pending_requested() { + let (mut notif, _peerset) = development_notifs(); + let set_id = crate::peerset::SetId::from(0); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // manually add backoff for the entry + if let Some(PeerState::Disabled { ref mut backoff_until, .. }) = + notif.peers.get_mut(&(peer, set_id)) + { + *backoff_until = + Some(Instant::now().checked_add(std::time::Duration::from_secs(5)).unwrap()); + } + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Backoff { .. }))); + + // attempt to connect the backed-off peer and verify that the request is pending + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!( + notif.peers.get(&(peer, set_id)), + Some(&PeerState::PendingRequest { .. }) + )); + + // duplicate "connect" must not change the state + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!( + notif.peers.get(&(peer, set_id)), + Some(&PeerState::PendingRequest { .. }) + )); + } + + #[test] + #[cfg(debug_assertions)] + fn peerset_report_connect_with_incoming_peer() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let set_id = crate::peerset::SetId::from(0); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // remote opens a substream, verify that peer state is updated to `Incoming` + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + } + + #[test] + #[cfg(debug_assertions)] + fn peerset_report_disconnect_with_incoming_peer() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let set_id = crate::peerset::SetId::from(0); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // remote opens a substream, verify that peer state is updated to `Incoming` + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + + notif.peerset_report_disconnect(peer, set_id); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn peerset_report_accept_incoming_peer() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // remote opens a substream, verify that peer state is updated to `Incoming` + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + + assert!(std::matches!( + notif.incoming[0], + IncomingPeer { alive: true, incoming_id: crate::peerset::IncomingIndex(0), .. }, + )); + + notif.peers.remove(&(peer, set_id)); + notif.peerset_report_accept(crate::peerset::IncomingIndex(0)); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn peerset_report_accept_not_incoming_peer() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + let mut conn_yielder = ConnectionYielder::new(); + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // remote opens a substream, verify that peer state is updated to `Incoming` + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + + assert!(std::matches!( + notif.incoming[0], + IncomingPeer { alive: true, incoming_id: crate::peerset::IncomingIndex(0), .. }, + )); + + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); + + let event = conn_yielder.open_substream(peer, 0, connected, vec![1, 2, 3, 4]); + notif.on_connection_handler_event(peer, conn, event); + + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); + notif.incoming[0].alive = true; + notif.peerset_report_accept(crate::peerset::IncomingIndex(0)); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn inject_connection_closed_non_existent_peer() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let endpoint = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: ConnectionId::new_unchecked(0), + endpoint: &endpoint.clone(), + handler: NotifsHandler::new(peer, endpoint, vec![]), + remaining_established: 0usize, + }, + )); + } + + #[test] + fn disconnect_non_existent_peer() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let set_id = crate::peerset::SetId::from(0); + + notif.peerset_report_disconnect(peer, set_id); + + assert!(notif.peers.is_empty()); + assert!(notif.incoming.is_empty()); + } + + #[test] + fn accept_non_existent_connection() { + let (mut notif, _peerset) = development_notifs(); + + notif.peerset_report_accept(0.into()); + + assert!(notif.peers.is_empty()); + assert!(notif.incoming.is_empty()); + } + + #[test] + fn reject_non_existent_connection() { + let (mut notif, _peerset) = development_notifs(); + + notif.peerset_report_reject(0.into()); + + assert!(notif.peers.is_empty()); + assert!(notif.incoming.is_empty()); + } + + #[test] + fn reject_non_active_connection() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // remote opens a substream, verify that peer state is updated to `Incoming` + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + + notif.incoming[0].alive = false; + notif.peerset_report_reject(0.into()); + + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn reject_non_existent_peer_but_alive_connection() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // remote opens a substream, verify that peer state is updated to `Incoming` + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + assert!(std::matches!( + notif.incoming[0], + IncomingPeer { alive: true, incoming_id: crate::peerset::IncomingIndex(0), .. }, + )); + + notif.peers.remove(&(peer, set_id)); + notif.peerset_report_reject(0.into()); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn inject_non_existent_connection_closed_for_incoming_peer() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // remote opens a substream, verify that peer state is updated to `Incoming` + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: ConnectionId::new_unchecked(1337), + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn inject_non_existent_connection_closed_for_disabled_peer() { + let (mut notif, _peerset) = development_notifs(); + let set_id = crate::peerset::SetId::from(0); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: ConnectionId::new_unchecked(1337), + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn inject_non_existent_connection_closed_for_disabled_pending_enable() { + let (mut notif, _peerset) = development_notifs(); + let set_id = crate::peerset::SetId::from(0); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // manually add backoff for the entry + if let Some(PeerState::Disabled { ref mut backoff_until, .. }) = + notif.peers.get_mut(&(peer, set_id)) + { + *backoff_until = + Some(Instant::now().checked_add(std::time::Duration::from_secs(5)).unwrap()); + } + + // switch state to `DisabledPendingEnable` + notif.peerset_report_connect(peer, set_id); + + assert!(std::matches!( + notif.peers.get(&(peer, set_id)), + Some(&PeerState::DisabledPendingEnable { .. }) + )); + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: ConnectionId::new_unchecked(1337), + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn inject_connection_closed_for_incoming_peer_state_mismatch() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // remote opens a substream, verify that peer state is updated to `Incoming` + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + notif.incoming[0].alive = false; + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn inject_connection_closed_for_enabled_state_mismatch() { + let (mut notif, _peerset) = development_notifs(); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let set_id = crate::peerset::SetId::from(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // remote opens a substream, verify that peer state is updated to `Incoming` + notif.on_connection_handler_event( + peer, + conn, + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + ); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Incoming { .. }))); + + // attempt to connect to the peer and verify that the peer state is `Enabled` + notif.peerset_report_connect(peer, set_id); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Enabled { .. }))); + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: ConnectionId::new_unchecked(1337), + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn inject_connection_closed_for_backoff_peer() { + let (mut notif, _peerset) = development_notifs(); + let set_id = crate::peerset::SetId::from(0); + let peer = PeerId::random(); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + + notif.on_swarm_event(FromSwarm::ConnectionEstablished( + libp2p::swarm::behaviour::ConnectionEstablished { + peer_id: peer, + connection_id: conn, + endpoint: &connected, + failed_addresses: &[], + other_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Disabled { .. }))); + + // manually add backoff for the entry + if let Some(PeerState::Disabled { ref mut backoff_until, .. }) = + notif.peers.get_mut(&(peer, set_id)) + { + *backoff_until = + Some(Instant::now().checked_add(std::time::Duration::from_secs(5)).unwrap()); + } + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected.clone(), vec![]), + remaining_established: 0usize, + }, + )); + assert!(std::matches!(notif.peers.get(&(peer, set_id)), Some(&PeerState::Backoff { .. }))); + + notif.on_swarm_event(FromSwarm::ConnectionClosed( + libp2p::swarm::behaviour::ConnectionClosed { + peer_id: peer, + connection_id: conn, + endpoint: &connected.clone(), + handler: NotifsHandler::new(peer, connected, vec![]), + remaining_established: 0usize, + }, + )); + } + + #[test] + #[should_panic] + #[cfg(debug_assertions)] + fn open_result_ok_non_existent_peer() { + let (mut notif, _peerset) = development_notifs(); + let conn = ConnectionId::new_unchecked(0); + let connected = ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }; + let mut conn_yielder = ConnectionYielder::new(); + + notif.on_connection_handler_event( + PeerId::random(), + conn, + conn_yielder.open_substream(PeerId::random(), 0, connected, vec![1, 2, 3, 4]), + ); + } +} diff --git a/client/network/src/protocol/notifications/handler.rs b/client/network/src/protocol/notifications/handler.rs index ea09cb76edce..0ac2e250a2ea 100644 --- a/client/network/src/protocol/notifications/handler.rs +++ b/client/network/src/protocol/notifications/handler.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -57,9 +57,12 @@ //! It is illegal to send a [`NotifsHandlerIn::Open`] before a previously-emitted //! [`NotifsHandlerIn::Open`] has gotten an answer. -use crate::protocol::notifications::upgrade::{ - NotificationsHandshakeError, NotificationsIn, NotificationsInSubstream, NotificationsOut, - NotificationsOutSubstream, UpgradeCollec, +use crate::{ + protocol::notifications::upgrade::{ + NotificationsIn, NotificationsInSubstream, NotificationsOut, NotificationsOutSubstream, + UpgradeCollec, + }, + types::ProtocolName, }; use bytes::BytesMut; @@ -69,18 +72,15 @@ use futures::{ prelude::*, }; use libp2p::{ - core::{ - upgrade::{InboundUpgrade, OutboundUpgrade}, - ConnectedPoint, PeerId, - }, + core::ConnectedPoint, swarm::{ - ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, IntoConnectionHandler, - KeepAlive, NegotiatedSubstream, SubstreamProtocol, + handler::ConnectionEvent, ConnectionHandler, ConnectionHandlerEvent, KeepAlive, + NegotiatedSubstream, SubstreamProtocol, }, + PeerId, }; use log::error; use parking_lot::{Mutex, RwLock}; -use sc_network_common::protocol::ProtocolName; use std::{ collections::VecDeque, mem, @@ -106,19 +106,6 @@ const OPEN_TIMEOUT: Duration = Duration::from_secs(10); /// open substreams. const INITIAL_KEEPALIVE_TIME: Duration = Duration::from_secs(5); -/// Implements the `IntoConnectionHandler` trait of libp2p. -/// -/// Every time a connection with a remote starts, an instance of this struct is created and -/// sent to a background task dedicated to this connection. Once the connection is established, -/// it is turned into a [`NotifsHandler`]. -/// -/// See the documentation at the module level for more information. -pub struct NotifsHandlerProto { - /// Name of protocols, prototypes for upgrades for inbound substreams, and the message we - /// send or respond with in the handshake. - protocols: Vec, -} - /// The actual handler once the connection has been established. /// /// See the documentation at the module level for more information. @@ -141,6 +128,30 @@ pub struct NotifsHandler { >, } +impl NotifsHandler { + /// Creates new [`NotifsHandler`]. + pub fn new(peer_id: PeerId, endpoint: ConnectedPoint, protocols: Vec) -> Self { + Self { + protocols: protocols + .into_iter() + .map(|config| { + let in_upgrade = NotificationsIn::new( + config.name.clone(), + config.fallback_names.clone(), + config.max_notification_size, + ); + + Protocol { config, in_upgrade, state: State::Closed { pending_opening: false } } + }) + .collect(), + peer_id, + endpoint, + when_connection_open: Instant::now(), + events_queue: VecDeque::with_capacity(16), + } + } +} + /// Configuration for a notifications protocol. #[derive(Debug, Clone)] pub struct ProtocolConfig { @@ -224,45 +235,6 @@ enum State { }, } -impl IntoConnectionHandler for NotifsHandlerProto { - type Handler = NotifsHandler; - - fn inbound_protocol(&self) -> UpgradeCollec { - self.protocols - .iter() - .map(|cfg| { - NotificationsIn::new( - cfg.name.clone(), - cfg.fallback_names.clone(), - cfg.max_notification_size, - ) - }) - .collect::>() - } - - fn into_handler(self, peer_id: &PeerId, connected_point: &ConnectedPoint) -> Self::Handler { - NotifsHandler { - protocols: self - .protocols - .into_iter() - .map(|config| { - let in_upgrade = NotificationsIn::new( - config.name.clone(), - config.fallback_names.clone(), - config.max_notification_size, - ); - - Protocol { config, in_upgrade, state: State::Closed { pending_opening: false } } - }) - .collect(), - peer_id: *peer_id, - endpoint: connected_point.clone(), - when_connection_open: Instant::now(), - events_queue: VecDeque::with_capacity(16), - } - } -} - /// Event that can be received by a `NotifsHandler`. #[derive(Debug, Clone)] pub enum NotifsHandlerIn { @@ -462,18 +434,6 @@ pub enum NotifsHandlerError { SyncNotificationsClogged, } -impl NotifsHandlerProto { - /// Builds a new handler. - /// - /// `list` is a list of notification protocols names, the message to send as part of the - /// handshake, and the maximum allowed size of a notification. At the moment, the message - /// is always the same whether we open a substream ourselves or respond to handshake from - /// the remote. - pub fn new(list: impl Into>) -> Self { - Self { protocols: list.into() } - } -} - impl ConnectionHandler for NotifsHandler { type InEvent = NotifsHandlerIn; type OutEvent = NotifsHandlerOut; @@ -494,98 +454,128 @@ impl ConnectionHandler for NotifsHandler { SubstreamProtocol::new(protocols, ()) } - fn inject_fully_negotiated_inbound( + fn on_connection_event( &mut self, - (mut in_substream_open, protocol_index): >::Output, - (): (), + event: ConnectionEvent< + '_, + Self::InboundProtocol, + Self::OutboundProtocol, + Self::InboundOpenInfo, + Self::OutboundOpenInfo, + >, ) { - let mut protocol_info = &mut self.protocols[protocol_index]; - match protocol_info.state { - State::Closed { pending_opening } => { - self.events_queue.push_back(ConnectionHandlerEvent::Custom( - NotifsHandlerOut::OpenDesiredByRemote { protocol_index }, - )); + match event { + ConnectionEvent::FullyNegotiatedInbound(inbound) => { + let (mut in_substream_open, protocol_index) = inbound.protocol; + let protocol_info = &mut self.protocols[protocol_index]; - protocol_info.state = State::OpenDesiredByRemote { - in_substream: in_substream_open.substream, - pending_opening, - }; - }, - State::OpenDesiredByRemote { .. } => { - // If a substream already exists, silently drop the new one. - // Note that we drop the substream, which will send an equivalent to a - // TCP "RST" to the remote and force-close the substream. It might - // seem like an unclean way to get rid of a substream. However, keep - // in mind that it is invalid for the remote to open multiple such - // substreams, and therefore sending a "RST" is the most correct thing - // to do. - return - }, - State::Opening { ref mut in_substream, .. } | - State::Open { ref mut in_substream, .. } => { - if in_substream.is_some() { - // Same remark as above. - return - } + match protocol_info.state { + State::Closed { pending_opening } => { + self.events_queue.push_back(ConnectionHandlerEvent::Custom( + NotifsHandlerOut::OpenDesiredByRemote { protocol_index }, + )); - // Create `handshake_message` on a separate line to be sure that the - // lock is released as soon as possible. - let handshake_message = protocol_info.config.handshake.read().clone(); - in_substream_open.substream.send_handshake(handshake_message); - *in_substream = Some(in_substream_open.substream); - }, - } - } + protocol_info.state = State::OpenDesiredByRemote { + in_substream: in_substream_open.substream, + pending_opening, + }; + }, + State::OpenDesiredByRemote { .. } => { + // If a substream already exists, silently drop the new one. + // Note that we drop the substream, which will send an equivalent to a + // TCP "RST" to the remote and force-close the substream. It might + // seem like an unclean way to get rid of a substream. However, keep + // in mind that it is invalid for the remote to open multiple such + // substreams, and therefore sending a "RST" is the most correct thing + // to do. + return + }, + State::Opening { ref mut in_substream, .. } | + State::Open { ref mut in_substream, .. } => { + if in_substream.is_some() { + // Same remark as above. + return + } - fn inject_fully_negotiated_outbound( - &mut self, - new_open: >::Output, - protocol_index: Self::OutboundOpenInfo, - ) { - match self.protocols[protocol_index].state { - State::Closed { ref mut pending_opening } | - State::OpenDesiredByRemote { ref mut pending_opening, .. } => { - debug_assert!(*pending_opening); - *pending_opening = false; - }, - State::Open { .. } => { - error!(target: "sub-libp2p", "☎️ State mismatch in notifications handler"); - debug_assert!(false); + // Create `handshake_message` on a separate line to be sure that the + // lock is released as soon as possible. + let handshake_message = protocol_info.config.handshake.read().clone(); + in_substream_open.substream.send_handshake(handshake_message); + *in_substream = Some(in_substream_open.substream); + }, + } }, - State::Opening { ref mut in_substream } => { - let (async_tx, async_rx) = mpsc::channel(ASYNC_NOTIFICATIONS_BUFFER_SIZE); - let (sync_tx, sync_rx) = mpsc::channel(SYNC_NOTIFICATIONS_BUFFER_SIZE); - let notifications_sink = NotificationsSink { - inner: Arc::new(NotificationsSinkInner { - peer_id: self.peer_id, - async_channel: FuturesMutex::new(async_tx), - sync_channel: Mutex::new(Some(sync_tx)), - }), - }; - - self.protocols[protocol_index].state = State::Open { - notifications_sink_rx: stream::select(async_rx.fuse(), sync_rx.fuse()) - .peekable(), - out_substream: Some(new_open.substream), - in_substream: in_substream.take(), - }; + ConnectionEvent::FullyNegotiatedOutbound(outbound) => { + let (new_open, protocol_index) = (outbound.protocol, outbound.info); - self.events_queue.push_back(ConnectionHandlerEvent::Custom( - NotifsHandlerOut::OpenResultOk { - protocol_index, - negotiated_fallback: new_open.negotiated_fallback, - endpoint: self.endpoint.clone(), - received_handshake: new_open.handshake, - notifications_sink, + match self.protocols[protocol_index].state { + State::Closed { ref mut pending_opening } | + State::OpenDesiredByRemote { ref mut pending_opening, .. } => { + debug_assert!(*pending_opening); + *pending_opening = false; }, - )); + State::Open { .. } => { + error!(target: "sub-libp2p", "☎️ State mismatch in notifications handler"); + debug_assert!(false); + }, + State::Opening { ref mut in_substream } => { + let (async_tx, async_rx) = mpsc::channel(ASYNC_NOTIFICATIONS_BUFFER_SIZE); + let (sync_tx, sync_rx) = mpsc::channel(SYNC_NOTIFICATIONS_BUFFER_SIZE); + let notifications_sink = NotificationsSink { + inner: Arc::new(NotificationsSinkInner { + peer_id: self.peer_id, + async_channel: FuturesMutex::new(async_tx), + sync_channel: Mutex::new(Some(sync_tx)), + }), + }; + + self.protocols[protocol_index].state = State::Open { + notifications_sink_rx: stream::select(async_rx.fuse(), sync_rx.fuse()) + .peekable(), + out_substream: Some(new_open.substream), + in_substream: in_substream.take(), + }; + + self.events_queue.push_back(ConnectionHandlerEvent::Custom( + NotifsHandlerOut::OpenResultOk { + protocol_index, + negotiated_fallback: new_open.negotiated_fallback, + endpoint: self.endpoint.clone(), + received_handshake: new_open.handshake, + notifications_sink, + }, + )); + }, + } }, + ConnectionEvent::AddressChange(_address_change) => {}, + ConnectionEvent::DialUpgradeError(dial_upgrade_error) => match self.protocols + [dial_upgrade_error.info] + .state + { + State::Closed { ref mut pending_opening } | + State::OpenDesiredByRemote { ref mut pending_opening, .. } => { + debug_assert!(*pending_opening); + *pending_opening = false; + }, + + State::Opening { .. } => { + self.protocols[dial_upgrade_error.info].state = + State::Closed { pending_opening: false }; + + self.events_queue.push_back(ConnectionHandlerEvent::Custom( + NotifsHandlerOut::OpenResultErr { protocol_index: dial_upgrade_error.info }, + )); + }, + + // No substream is being open when already `Open`. + State::Open { .. } => debug_assert!(false), + }, + ConnectionEvent::ListenUpgradeError(_listen_upgrade_error) => {}, } } - fn inject_event(&mut self, message: NotifsHandlerIn) { + fn on_behaviour_event(&mut self, message: NotifsHandlerIn) { match message { NotifsHandlerIn::Open { protocol_index } => { let protocol_info = &mut self.protocols[protocol_index]; @@ -676,31 +666,6 @@ impl ConnectionHandler for NotifsHandler { } } - fn inject_dial_upgrade_error( - &mut self, - num: usize, - _: ConnectionHandlerUpgrErr, - ) { - match self.protocols[num].state { - State::Closed { ref mut pending_opening } | - State::OpenDesiredByRemote { ref mut pending_opening, .. } => { - debug_assert!(*pending_opening); - *pending_opening = false; - }, - - State::Opening { .. } => { - self.protocols[num].state = State::Closed { pending_opening: false }; - - self.events_queue.push_back(ConnectionHandlerEvent::Custom( - NotifsHandlerOut::OpenResultErr { protocol_index: num }, - )); - }, - - // No substream is being open when already `Open`. - State::Open { .. } => debug_assert!(false), - } - } - fn connection_keep_alive(&self) -> KeepAlive { // `Yes` if any protocol has some activity. if self.protocols.iter().any(|p| !matches!(p.state, State::Closed { .. })) { @@ -850,3 +815,792 @@ impl ConnectionHandler for NotifsHandler { Poll::Pending } } + +#[cfg(test)] +pub mod tests { + use super::*; + use crate::protocol::notifications::upgrade::{ + NotificationsInOpen, NotificationsInSubstreamHandshake, NotificationsOutOpen, + }; + use asynchronous_codec::Framed; + use libp2p::{ + core::muxing::SubstreamBox, + swarm::{handler, ConnectionHandlerUpgrErr}, + Multiaddr, + }; + use multistream_select::{dialer_select_proto, listener_select_proto, Negotiated, Version}; + use std::{ + collections::HashMap, + io::{Error, IoSlice, IoSliceMut}, + }; + use tokio::sync::mpsc; + use unsigned_varint::codec::UviBytes; + + struct OpenSubstream { + notifications: stream::Peekable< + stream::Select< + stream::Fuse>, + stream::Fuse>, + >, + >, + _in_substream: MockSubstream, + _out_substream: MockSubstream, + } + + pub struct ConnectionYielder { + connections: HashMap<(PeerId, usize), OpenSubstream>, + } + + impl ConnectionYielder { + /// Create new [`ConnectionYielder`]. + pub fn new() -> Self { + Self { connections: HashMap::new() } + } + + /// Open a new substream for peer. + pub fn open_substream( + &mut self, + peer: PeerId, + protocol_index: usize, + endpoint: ConnectedPoint, + received_handshake: Vec, + ) -> NotifsHandlerOut { + let (async_tx, async_rx) = + futures::channel::mpsc::channel(ASYNC_NOTIFICATIONS_BUFFER_SIZE); + let (sync_tx, sync_rx) = + futures::channel::mpsc::channel(SYNC_NOTIFICATIONS_BUFFER_SIZE); + let notifications_sink = NotificationsSink { + inner: Arc::new(NotificationsSinkInner { + peer_id: peer, + async_channel: FuturesMutex::new(async_tx), + sync_channel: Mutex::new(Some(sync_tx)), + }), + }; + let (in_substream, out_substream) = MockSubstream::new(); + + self.connections.insert( + (peer, protocol_index), + OpenSubstream { + notifications: stream::select(async_rx.fuse(), sync_rx.fuse()).peekable(), + _in_substream: in_substream, + _out_substream: out_substream, + }, + ); + + NotifsHandlerOut::OpenResultOk { + protocol_index, + negotiated_fallback: None, + endpoint, + received_handshake, + notifications_sink, + } + } + + /// Attempt to get next pending event from one of the notification sinks. + pub async fn get_next_event(&mut self, peer: PeerId, set: usize) -> Option> { + let substream = if let Some(info) = self.connections.get_mut(&(peer, set)) { + info + } else { + return None + }; + + futures::future::poll_fn(|cx| match substream.notifications.poll_next_unpin(cx) { + Poll::Ready(Some(NotificationsSinkMessage::Notification { message })) => + Poll::Ready(Some(message)), + Poll::Pending => Poll::Ready(None), + Poll::Ready(Some(NotificationsSinkMessage::ForceClose)) | Poll::Ready(None) => { + panic!("sink closed") + }, + }) + .await + } + } + struct MockSubstream { + pub rx: mpsc::Receiver>, + pub tx: mpsc::Sender>, + rx_buffer: BytesMut, + } + + impl MockSubstream { + /// Create new substream pair. + pub fn new() -> (Self, Self) { + let (tx1, rx1) = mpsc::channel(32); + let (tx2, rx2) = mpsc::channel(32); + + ( + Self { rx: rx1, tx: tx2, rx_buffer: BytesMut::with_capacity(512) }, + Self { rx: rx2, tx: tx1, rx_buffer: BytesMut::with_capacity(512) }, + ) + } + + /// Create new negotiated substream pair. + pub async fn negotiated() -> (Negotiated, Negotiated) { + let (socket1, socket2) = Self::new(); + let socket1 = SubstreamBox::new(socket1); + let socket2 = SubstreamBox::new(socket2); + + let protos = vec![b"/echo/1.0.0", b"/echo/2.5.0"]; + let (res1, res2) = tokio::join!( + dialer_select_proto(socket1, protos.clone(), Version::V1), + listener_select_proto(socket2, protos), + ); + + (res1.unwrap().1, res2.unwrap().1) + } + } + + impl AsyncWrite for MockSubstream { + fn poll_write<'a>( + self: Pin<&mut Self>, + _cx: &mut Context<'a>, + buf: &[u8], + ) -> Poll> { + match self.tx.try_send(buf.to_vec()) { + Ok(_) => Poll::Ready(Ok(buf.len())), + Err(_) => Poll::Ready(Err(std::io::ErrorKind::UnexpectedEof.into())), + } + } + + fn poll_flush<'a>(self: Pin<&mut Self>, _cx: &mut Context<'a>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_close<'a>(self: Pin<&mut Self>, _cx: &mut Context<'a>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_write_vectored<'a, 'b>( + self: Pin<&mut Self>, + _cx: &mut Context<'a>, + _bufs: &[IoSlice<'b>], + ) -> Poll> { + unimplemented!(); + } + } + + impl AsyncRead for MockSubstream { + fn poll_read<'a>( + mut self: Pin<&mut Self>, + cx: &mut Context<'a>, + buf: &mut [u8], + ) -> Poll> { + match self.rx.poll_recv(cx) { + Poll::Ready(Some(data)) => self.rx_buffer.extend_from_slice(&data), + Poll::Ready(None) => + return Poll::Ready(Err(std::io::ErrorKind::UnexpectedEof.into())), + _ => {}, + } + + let nsize = std::cmp::min(self.rx_buffer.len(), buf.len()); + let data = self.rx_buffer.split_to(nsize); + buf[..nsize].copy_from_slice(&data[..]); + + if nsize > 0 { + return Poll::Ready(Ok(nsize)) + } + + Poll::Pending + } + + fn poll_read_vectored<'a, 'b>( + self: Pin<&mut Self>, + _cx: &mut Context<'a>, + _bufs: &mut [IoSliceMut<'b>], + ) -> Poll> { + unimplemented!(); + } + } + + /// Create new [`NotifsHandler`]. + fn notifs_handler() -> NotifsHandler { + let proto = Protocol { + config: ProtocolConfig { + name: "/foo".into(), + fallback_names: vec![], + handshake: Arc::new(RwLock::new(b"hello, world".to_vec())), + max_notification_size: u64::MAX, + }, + in_upgrade: NotificationsIn::new("/foo", Vec::new(), u64::MAX), + state: State::Closed { pending_opening: false }, + }; + + NotifsHandler { + protocols: vec![proto], + when_connection_open: Instant::now(), + endpoint: ConnectedPoint::Listener { + local_addr: Multiaddr::empty(), + send_back_addr: Multiaddr::empty(), + }, + peer_id: PeerId::random(), + events_queue: VecDeque::new(), + } + } + + // verify that if another substream is attempted to be opened by remote while an inbound + // substream already exists, the new inbound stream is rejected and closed by the local node. + #[tokio::test] + async fn second_open_desired_by_remote_rejected() { + let mut handler = notifs_handler(); + let (io, mut io2) = MockSubstream::negotiated().await; + let mut codec = UviBytes::default(); + codec.set_max_len(usize::MAX); + + let notif_in = NotificationsInOpen { + handshake: b"hello, world".to_vec(), + negotiated_fallback: None, + substream: NotificationsInSubstream::new( + Framed::new(io, codec), + NotificationsInSubstreamHandshake::NotSent, + ), + }; + + handler.on_connection_event(handler::ConnectionEvent::FullyNegotiatedInbound( + handler::FullyNegotiatedInbound { protocol: (notif_in, 0), info: () }, + )); + + // verify that the substream is in (partly) opened state + assert!(std::matches!(handler.protocols[0].state, State::OpenDesiredByRemote { .. })); + futures::future::poll_fn(|cx| { + let mut buf = Vec::with_capacity(512); + assert!(std::matches!(Pin::new(&mut io2).poll_read(cx, &mut buf), Poll::Pending)); + Poll::Ready(()) + }) + .await; + + // attempt to open another inbound substream and verify that it is rejected + let (io, mut io2) = MockSubstream::negotiated().await; + let mut codec = UviBytes::default(); + codec.set_max_len(usize::MAX); + + let notif_in = NotificationsInOpen { + handshake: b"hello, world".to_vec(), + negotiated_fallback: None, + substream: NotificationsInSubstream::new( + Framed::new(io, codec), + NotificationsInSubstreamHandshake::NotSent, + ), + }; + + handler.on_connection_event(handler::ConnectionEvent::FullyNegotiatedInbound( + handler::FullyNegotiatedInbound { protocol: (notif_in, 0), info: () }, + )); + + // verify that the new substream is rejected and closed + futures::future::poll_fn(|cx| { + let mut buf = Vec::with_capacity(512); + + if let Poll::Ready(Err(err)) = Pin::new(&mut io2).poll_read(cx, &mut buf) { + assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof,); + } + + Poll::Ready(()) + }) + .await; + } + + #[tokio::test] + async fn open_rejected_if_substream_is_opening() { + let mut handler = notifs_handler(); + let (io, mut io2) = MockSubstream::negotiated().await; + let mut codec = UviBytes::default(); + codec.set_max_len(usize::MAX); + + let notif_in = NotificationsInOpen { + handshake: b"hello, world".to_vec(), + negotiated_fallback: None, + substream: NotificationsInSubstream::new( + Framed::new(io, codec), + NotificationsInSubstreamHandshake::NotSent, + ), + }; + + handler.on_connection_event(handler::ConnectionEvent::FullyNegotiatedInbound( + handler::FullyNegotiatedInbound { protocol: (notif_in, 0), info: () }, + )); + + // verify that the substream is in (partly) opened state + assert!(std::matches!(handler.protocols[0].state, State::OpenDesiredByRemote { .. })); + futures::future::poll_fn(|cx| { + let mut buf = Vec::with_capacity(512); + assert!(std::matches!(Pin::new(&mut io2).poll_read(cx, &mut buf), Poll::Pending)); + Poll::Ready(()) + }) + .await; + + // move the handler state to 'Opening' + handler.on_behaviour_event(NotifsHandlerIn::Open { protocol_index: 0 }); + assert!(std::matches!( + handler.protocols[0].state, + State::Opening { in_substream: Some(_) } + )); + + // remote now tries to open another substream, verify that it is rejected and closed + let (io, mut io2) = MockSubstream::negotiated().await; + let mut codec = UviBytes::default(); + codec.set_max_len(usize::MAX); + + let notif_in = NotificationsInOpen { + handshake: b"hello, world".to_vec(), + negotiated_fallback: None, + substream: NotificationsInSubstream::new( + Framed::new(io, codec), + NotificationsInSubstreamHandshake::NotSent, + ), + }; + + handler.on_connection_event(handler::ConnectionEvent::FullyNegotiatedInbound( + handler::FullyNegotiatedInbound { protocol: (notif_in, 0), info: () }, + )); + + // verify that the new substream is rejected and closed but that the first substream is + // still in correct state + futures::future::poll_fn(|cx| { + let mut buf = Vec::with_capacity(512); + + if let Poll::Ready(Err(err)) = Pin::new(&mut io2).poll_read(cx, &mut buf) { + assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof,); + } else { + panic!("unexpected result"); + } + + Poll::Ready(()) + }) + .await; + assert!(std::matches!( + handler.protocols[0].state, + State::Opening { in_substream: Some(_) } + )); + } + + #[tokio::test] + async fn open_rejected_if_substream_already_open() { + let mut handler = notifs_handler(); + let (io, mut io2) = MockSubstream::negotiated().await; + let mut codec = UviBytes::default(); + codec.set_max_len(usize::MAX); + + let notif_in = NotificationsInOpen { + handshake: b"hello, world".to_vec(), + negotiated_fallback: None, + substream: NotificationsInSubstream::new( + Framed::new(io, codec), + NotificationsInSubstreamHandshake::NotSent, + ), + }; + handler.on_connection_event(handler::ConnectionEvent::FullyNegotiatedInbound( + handler::FullyNegotiatedInbound { protocol: (notif_in, 0), info: () }, + )); + + // verify that the substream is in (partly) opened state + assert!(std::matches!(handler.protocols[0].state, State::OpenDesiredByRemote { .. })); + futures::future::poll_fn(|cx| { + let mut buf = Vec::with_capacity(512); + assert!(std::matches!(Pin::new(&mut io2).poll_read(cx, &mut buf), Poll::Pending)); + Poll::Ready(()) + }) + .await; + + // move the handler state to 'Opening' + handler.on_behaviour_event(NotifsHandlerIn::Open { protocol_index: 0 }); + assert!(std::matches!( + handler.protocols[0].state, + State::Opening { in_substream: Some(_) } + )); + + // accept the substream and move its state to `Open` + let (io, _io2) = MockSubstream::negotiated().await; + let mut codec = UviBytes::default(); + codec.set_max_len(usize::MAX); + + let notif_out = NotificationsOutOpen { + handshake: b"hello, world".to_vec(), + negotiated_fallback: None, + substream: NotificationsOutSubstream::new(Framed::new(io, codec)), + }; + handler.on_connection_event(handler::ConnectionEvent::FullyNegotiatedOutbound( + handler::FullyNegotiatedOutbound { protocol: notif_out, info: 0 }, + )); + + assert!(std::matches!( + handler.protocols[0].state, + State::Open { in_substream: Some(_), .. } + )); + + // remote now tries to open another substream, verify that it is rejected and closed + let (io, mut io2) = MockSubstream::negotiated().await; + let mut codec = UviBytes::default(); + codec.set_max_len(usize::MAX); + let notif_in = NotificationsInOpen { + handshake: b"hello, world".to_vec(), + negotiated_fallback: None, + substream: NotificationsInSubstream::new( + Framed::new(io, codec), + NotificationsInSubstreamHandshake::NotSent, + ), + }; + handler.on_connection_event(handler::ConnectionEvent::FullyNegotiatedInbound( + handler::FullyNegotiatedInbound { protocol: (notif_in, 0), info: () }, + )); + + // verify that the new substream is rejected and closed but that the first substream is + // still in correct state + futures::future::poll_fn(|cx| { + let mut buf = Vec::with_capacity(512); + + if let Poll::Ready(Err(err)) = Pin::new(&mut io2).poll_read(cx, &mut buf) { + assert_eq!(err.kind(), std::io::ErrorKind::UnexpectedEof); + } else { + panic!("unexpected result"); + } + + Poll::Ready(()) + }) + .await; + assert!(std::matches!( + handler.protocols[0].state, + State::Open { in_substream: Some(_), .. } + )); + } + + #[tokio::test] + async fn fully_negotiated_resets_state_for_closed_substream() { + let mut handler = notifs_handler(); + let (io, mut io2) = MockSubstream::negotiated().await; + let mut codec = UviBytes::default(); + codec.set_max_len(usize::MAX); + + let notif_in = NotificationsInOpen { + handshake: b"hello, world".to_vec(), + negotiated_fallback: None, + substream: NotificationsInSubstream::new( + Framed::new(io, codec), + NotificationsInSubstreamHandshake::NotSent, + ), + }; + handler.on_connection_event(handler::ConnectionEvent::FullyNegotiatedInbound( + handler::FullyNegotiatedInbound { protocol: (notif_in, 0), info: () }, + )); + + // verify that the substream is in (partly) opened state + assert!(std::matches!(handler.protocols[0].state, State::OpenDesiredByRemote { .. })); + futures::future::poll_fn(|cx| { + let mut buf = Vec::with_capacity(512); + assert!(std::matches!(Pin::new(&mut io2).poll_read(cx, &mut buf), Poll::Pending)); + Poll::Ready(()) + }) + .await; + + // first instruct the handler to open a connection and then close it right after + // so the handler is in state `Closed { pending_opening: true }` + handler.on_behaviour_event(NotifsHandlerIn::Open { protocol_index: 0 }); + assert!(std::matches!( + handler.protocols[0].state, + State::Opening { in_substream: Some(_) } + )); + + handler.on_behaviour_event(NotifsHandlerIn::Close { protocol_index: 0 }); + assert!(std::matches!(handler.protocols[0].state, State::Closed { pending_opening: true })); + + // verify that if the the outbound substream is successfully negotiated, the state is not + // changed as the substream was commanded to be closed by the handler. + let (io, _io2) = MockSubstream::negotiated().await; + let mut codec = UviBytes::default(); + codec.set_max_len(usize::MAX); + + let notif_out = NotificationsOutOpen { + handshake: b"hello, world".to_vec(), + negotiated_fallback: None, + substream: NotificationsOutSubstream::new(Framed::new(io, codec)), + }; + handler.on_connection_event(handler::ConnectionEvent::FullyNegotiatedOutbound( + handler::FullyNegotiatedOutbound { protocol: notif_out, info: 0 }, + )); + + assert!(std::matches!( + handler.protocols[0].state, + State::Closed { pending_opening: false } + )); + } + + #[tokio::test] + async fn fully_negotiated_resets_state_for_open_desired_substream() { + let mut handler = notifs_handler(); + let (io, mut io2) = MockSubstream::negotiated().await; + let mut codec = UviBytes::default(); + codec.set_max_len(usize::MAX); + + let notif_in = NotificationsInOpen { + handshake: b"hello, world".to_vec(), + negotiated_fallback: None, + substream: NotificationsInSubstream::new( + Framed::new(io, codec), + NotificationsInSubstreamHandshake::NotSent, + ), + }; + handler.on_connection_event(handler::ConnectionEvent::FullyNegotiatedInbound( + handler::FullyNegotiatedInbound { protocol: (notif_in, 0), info: () }, + )); + + // verify that the substream is in (partly) opened state + assert!(std::matches!(handler.protocols[0].state, State::OpenDesiredByRemote { .. })); + futures::future::poll_fn(|cx| { + let mut buf = Vec::with_capacity(512); + assert!(std::matches!(Pin::new(&mut io2).poll_read(cx, &mut buf), Poll::Pending)); + Poll::Ready(()) + }) + .await; + + // first instruct the handler to open a connection and then close it right after + // so the handler is in state `Closed { pending_opening: true }` + handler.on_behaviour_event(NotifsHandlerIn::Open { protocol_index: 0 }); + assert!(std::matches!( + handler.protocols[0].state, + State::Opening { in_substream: Some(_) } + )); + + handler.on_behaviour_event(NotifsHandlerIn::Close { protocol_index: 0 }); + assert!(std::matches!(handler.protocols[0].state, State::Closed { pending_opening: true })); + + // attempt to open another inbound substream and verify that it is rejected + let (io, _io2) = MockSubstream::negotiated().await; + let mut codec = UviBytes::default(); + codec.set_max_len(usize::MAX); + + let notif_in = NotificationsInOpen { + handshake: b"hello, world".to_vec(), + negotiated_fallback: None, + substream: NotificationsInSubstream::new( + Framed::new(io, codec), + NotificationsInSubstreamHandshake::NotSent, + ), + }; + handler.on_connection_event(handler::ConnectionEvent::FullyNegotiatedInbound( + handler::FullyNegotiatedInbound { protocol: (notif_in, 0), info: () }, + )); + + assert!(std::matches!( + handler.protocols[0].state, + State::OpenDesiredByRemote { pending_opening: true, .. } + )); + + // verify that if the the outbound substream is successfully negotiated, the state is not + // changed as the substream was commanded to be closed by the handler. + let (io, _io2) = MockSubstream::negotiated().await; + let mut codec = UviBytes::default(); + codec.set_max_len(usize::MAX); + + let notif_out = NotificationsOutOpen { + handshake: b"hello, world".to_vec(), + negotiated_fallback: None, + substream: NotificationsOutSubstream::new(Framed::new(io, codec)), + }; + + handler.on_connection_event(handler::ConnectionEvent::FullyNegotiatedOutbound( + handler::FullyNegotiatedOutbound { protocol: notif_out, info: 0 }, + )); + + assert!(std::matches!( + handler.protocols[0].state, + State::OpenDesiredByRemote { pending_opening: false, .. } + )); + } + + #[tokio::test] + async fn dial_upgrade_error_resets_closed_outbound_state() { + let mut handler = notifs_handler(); + let (io, mut io2) = MockSubstream::negotiated().await; + let mut codec = UviBytes::default(); + codec.set_max_len(usize::MAX); + + let notif_in = NotificationsInOpen { + handshake: b"hello, world".to_vec(), + negotiated_fallback: None, + substream: NotificationsInSubstream::new( + Framed::new(io, codec), + NotificationsInSubstreamHandshake::NotSent, + ), + }; + handler.on_connection_event(handler::ConnectionEvent::FullyNegotiatedInbound( + handler::FullyNegotiatedInbound { protocol: (notif_in, 0), info: () }, + )); + + // verify that the substream is in (partly) opened state + assert!(std::matches!(handler.protocols[0].state, State::OpenDesiredByRemote { .. })); + futures::future::poll_fn(|cx| { + let mut buf = Vec::with_capacity(512); + assert!(std::matches!(Pin::new(&mut io2).poll_read(cx, &mut buf), Poll::Pending)); + Poll::Ready(()) + }) + .await; + + // first instruct the handler to open a connection and then close it right after + // so the handler is in state `Closed { pending_opening: true }` + handler.on_behaviour_event(NotifsHandlerIn::Open { protocol_index: 0 }); + assert!(std::matches!( + handler.protocols[0].state, + State::Opening { in_substream: Some(_) } + )); + + handler.on_behaviour_event(NotifsHandlerIn::Close { protocol_index: 0 }); + assert!(std::matches!(handler.protocols[0].state, State::Closed { pending_opening: true })); + + // inject dial failure to an already closed substream and verify outbound state is reset + handler.on_connection_event(handler::ConnectionEvent::DialUpgradeError( + handler::DialUpgradeError { info: 0, error: ConnectionHandlerUpgrErr::Timeout }, + )); + assert!(std::matches!( + handler.protocols[0].state, + State::Closed { pending_opening: false } + )); + } + + #[tokio::test] + async fn dial_upgrade_error_resets_open_desired_state() { + let mut handler = notifs_handler(); + let (io, mut io2) = MockSubstream::negotiated().await; + let mut codec = UviBytes::default(); + codec.set_max_len(usize::MAX); + + let notif_in = NotificationsInOpen { + handshake: b"hello, world".to_vec(), + negotiated_fallback: None, + substream: NotificationsInSubstream::new( + Framed::new(io, codec), + NotificationsInSubstreamHandshake::NotSent, + ), + }; + handler.on_connection_event(handler::ConnectionEvent::FullyNegotiatedInbound( + handler::FullyNegotiatedInbound { protocol: (notif_in, 0), info: () }, + )); + + // verify that the substream is in (partly) opened state + assert!(std::matches!(handler.protocols[0].state, State::OpenDesiredByRemote { .. })); + futures::future::poll_fn(|cx| { + let mut buf = Vec::with_capacity(512); + assert!(std::matches!(Pin::new(&mut io2).poll_read(cx, &mut buf), Poll::Pending)); + Poll::Ready(()) + }) + .await; + + // first instruct the handler to open a connection and then close it right after + // so the handler is in state `Closed { pending_opening: true }` + handler.on_behaviour_event(NotifsHandlerIn::Open { protocol_index: 0 }); + assert!(std::matches!( + handler.protocols[0].state, + State::Opening { in_substream: Some(_) } + )); + + handler.on_behaviour_event(NotifsHandlerIn::Close { protocol_index: 0 }); + assert!(std::matches!(handler.protocols[0].state, State::Closed { pending_opening: true })); + + let (io, _io2) = MockSubstream::negotiated().await; + let mut codec = UviBytes::default(); + codec.set_max_len(usize::MAX); + + let notif_in = NotificationsInOpen { + handshake: b"hello, world".to_vec(), + negotiated_fallback: None, + substream: NotificationsInSubstream::new( + Framed::new(io, codec), + NotificationsInSubstreamHandshake::NotSent, + ), + }; + handler.on_connection_event(handler::ConnectionEvent::FullyNegotiatedInbound( + handler::FullyNegotiatedInbound { protocol: (notif_in, 0), info: () }, + )); + + assert!(std::matches!( + handler.protocols[0].state, + State::OpenDesiredByRemote { pending_opening: true, .. } + )); + + // inject dial failure to an already closed substream and verify outbound state is reset + handler.on_connection_event(handler::ConnectionEvent::DialUpgradeError( + handler::DialUpgradeError { info: 0, error: ConnectionHandlerUpgrErr::Timeout }, + )); + assert!(std::matches!( + handler.protocols[0].state, + State::OpenDesiredByRemote { pending_opening: false, .. } + )); + } + + #[tokio::test] + async fn sync_notifications_clogged() { + let mut handler = notifs_handler(); + let (io, _) = MockSubstream::negotiated().await; + let codec = UviBytes::default(); + + let (async_tx, async_rx) = futures::channel::mpsc::channel(ASYNC_NOTIFICATIONS_BUFFER_SIZE); + let (sync_tx, sync_rx) = futures::channel::mpsc::channel(1); + let notifications_sink = NotificationsSink { + inner: Arc::new(NotificationsSinkInner { + peer_id: PeerId::random(), + async_channel: FuturesMutex::new(async_tx), + sync_channel: Mutex::new(Some(sync_tx)), + }), + }; + + handler.protocols[0].state = State::Open { + notifications_sink_rx: stream::select(async_rx.fuse(), sync_rx.fuse()).peekable(), + out_substream: Some(NotificationsOutSubstream::new(Framed::new(io, codec))), + in_substream: None, + }; + + notifications_sink.send_sync_notification(vec![1, 3, 3, 7]); + notifications_sink.send_sync_notification(vec![1, 3, 3, 8]); + notifications_sink.send_sync_notification(vec![1, 3, 3, 9]); + notifications_sink.send_sync_notification(vec![1, 3, 4, 0]); + + futures::future::poll_fn(|cx| { + assert!(std::matches!( + handler.poll(cx), + Poll::Ready(ConnectionHandlerEvent::Close( + NotifsHandlerError::SyncNotificationsClogged, + )) + )); + Poll::Ready(()) + }) + .await; + } + + #[tokio::test] + async fn close_desired_by_remote() { + let mut handler = notifs_handler(); + let (io, io2) = MockSubstream::negotiated().await; + let mut codec = UviBytes::default(); + codec.set_max_len(usize::MAX); + + let notif_in = NotificationsInOpen { + handshake: b"hello, world".to_vec(), + negotiated_fallback: None, + substream: NotificationsInSubstream::new( + Framed::new(io, codec), + NotificationsInSubstreamHandshake::PendingSend(vec![1, 2, 3, 4]), + ), + }; + + // add new inbound substream but close it immediately and verify that correct events are + // emitted + handler.on_connection_event(handler::ConnectionEvent::FullyNegotiatedInbound( + handler::FullyNegotiatedInbound { protocol: (notif_in, 0), info: () }, + )); + drop(io2); + + futures::future::poll_fn(|cx| { + assert!(std::matches!( + handler.poll(cx), + Poll::Ready(ConnectionHandlerEvent::Custom( + NotifsHandlerOut::OpenDesiredByRemote { protocol_index: 0 }, + )) + )); + assert!(std::matches!( + handler.poll(cx), + Poll::Ready(ConnectionHandlerEvent::Custom(NotifsHandlerOut::CloseDesired { + protocol_index: 0 + },)) + )); + Poll::Ready(()) + }) + .await; + } +} diff --git a/client/network/src/protocol/notifications/tests.rs b/client/network/src/protocol/notifications/tests.rs index 6e858697486e..0c2eb89262f9 100644 --- a/client/network/src/protocol/notifications/tests.rs +++ b/client/network/src/protocol/notifications/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -22,11 +22,12 @@ use crate::protocol::notifications::{Notifications, NotificationsOut, ProtocolCo use futures::prelude::*; use libp2p::{ - core::{connection::ConnectionId, transport::MemoryTransport, upgrade}, + core::{transport::MemoryTransport, upgrade, Endpoint}, identity, noise, swarm::{ - behaviour::FromSwarm, ConnectionHandler, Executor, IntoConnectionHandler, NetworkBehaviour, - NetworkBehaviourAction, PollParameters, Swarm, SwarmEvent, + behaviour::FromSwarm, ConnectionDenied, ConnectionId, Executor, NetworkBehaviour, + PollParameters, Swarm, SwarmBuilder, SwarmEvent, THandler, THandlerInEvent, + THandlerOutEvent, ToSwarm, }, yamux, Multiaddr, PeerId, Transport, }; @@ -57,29 +58,31 @@ fn build_nodes() -> (Swarm, Swarm) { for index in 0..2 { let keypair = keypairs[index].clone(); - let noise_keys = - noise::Keypair::::new().into_authentic(&keypair).unwrap(); - let transport = MemoryTransport::new() .upgrade(upgrade::Version::V1) - .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated()) - .multiplex(yamux::YamuxConfig::default()) + .authenticate(noise::Config::new(&keypair).unwrap()) + .multiplex(yamux::Config::default()) .timeout(Duration::from_secs(20)) .boxed(); - let (peerset, _) = sc_peerset::Peerset::from_config(sc_peerset::PeersetConfig { - sets: vec![sc_peerset::SetConfig { - in_peers: 25, - out_peers: 25, - bootnodes: if index == 0 { - keypairs.iter().skip(1).map(|keypair| keypair.public().to_peer_id()).collect() - } else { - vec![] - }, - reserved_nodes: Default::default(), - reserved_only: false, - }], - }); + let (peerset, handle) = + crate::peerset::Peerset::from_config(crate::peerset::PeersetConfig { + sets: vec![crate::peerset::SetConfig { + in_peers: 25, + out_peers: 25, + bootnodes: if index == 0 { + keypairs + .iter() + .skip(1) + .map(|keypair| keypair.public().to_peer_id()) + .collect() + } else { + vec![] + }, + reserved_nodes: Default::default(), + reserved_only: false, + }], + }); let behaviour = CustomProtoWithAddr { inner: Notifications::new( @@ -91,6 +94,7 @@ fn build_nodes() -> (Swarm, Swarm) { max_notification_size: 1024 * 1024, }), ), + _peerset_handle: handle, addrs: addrs .iter() .enumerate() @@ -105,12 +109,13 @@ fn build_nodes() -> (Swarm, Swarm) { }; let runtime = tokio::runtime::Runtime::new().unwrap(); - let mut swarm = Swarm::with_executor( + let mut swarm = SwarmBuilder::with_executor( transport, behaviour, keypairs[index].public().to_peer_id(), TokioExecutor(runtime), - ); + ) + .build(); swarm.listen_on(addrs[index].clone()).unwrap(); out.push(swarm); } @@ -125,6 +130,8 @@ fn build_nodes() -> (Swarm, Swarm) { /// Wraps around the `CustomBehaviour` network behaviour, and adds hardcoded node addresses to it. struct CustomProtoWithAddr { inner: Notifications, + // We need to keep `PeersetHandle` for `Peerset` not to shut down. + _peerset_handle: crate::peerset::PeersetHandle, addrs: Vec<(PeerId, Multiaddr)>, } @@ -146,18 +153,63 @@ impl NetworkBehaviour for CustomProtoWithAddr { type ConnectionHandler = ::ConnectionHandler; type OutEvent = ::OutEvent; - fn new_handler(&mut self) -> Self::ConnectionHandler { - self.inner.new_handler() + fn handle_pending_inbound_connection( + &mut self, + connection_id: ConnectionId, + local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result<(), ConnectionDenied> { + self.inner + .handle_pending_inbound_connection(connection_id, local_addr, remote_addr) } - fn addresses_of_peer(&mut self, peer_id: &PeerId) -> Vec { - let mut list = self.inner.addresses_of_peer(peer_id); - for (p, a) in self.addrs.iter() { - if p == peer_id { - list.push(a.clone()); + fn handle_pending_outbound_connection( + &mut self, + connection_id: ConnectionId, + maybe_peer: Option, + addresses: &[Multiaddr], + effective_role: Endpoint, + ) -> Result, ConnectionDenied> { + let mut list = self.inner.handle_pending_outbound_connection( + connection_id, + maybe_peer, + addresses, + effective_role, + )?; + if let Some(peer_id) = maybe_peer { + for (p, a) in self.addrs.iter() { + if *p == peer_id { + list.push(a.clone()); + } } } - list + Ok(list) + } + + fn handle_established_inbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result, ConnectionDenied> { + self.inner.handle_established_inbound_connection( + connection_id, + peer, + local_addr, + remote_addr, + ) + } + + fn handle_established_outbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + addr: &Multiaddr, + role_override: Endpoint, + ) -> Result, ConnectionDenied> { + self.inner + .handle_established_outbound_connection(connection_id, peer, addr, role_override) } fn on_swarm_event(&mut self, event: FromSwarm) { @@ -168,8 +220,7 @@ impl NetworkBehaviour for CustomProtoWithAddr { &mut self, peer_id: PeerId, connection_id: ConnectionId, - event: <::Handler as - ConnectionHandler>::OutEvent, + event: THandlerOutEvent, ) { self.inner.on_connection_handler_event(peer_id, connection_id, event); } @@ -178,7 +229,7 @@ impl NetworkBehaviour for CustomProtoWithAddr { &mut self, cx: &mut Context, params: &mut impl PollParameters, - ) -> Poll> { + ) -> Poll>> { self.inner.poll(cx, params) } } @@ -223,7 +274,7 @@ fn reconnect_after_disconnect() { if service2_state == ServiceState::FirstConnec { service1.behaviour_mut().disconnect_peer( Swarm::local_peer_id(&service2), - sc_peerset::SetId::from(0), + crate::peerset::SetId::from(0), ); } }, @@ -246,7 +297,7 @@ fn reconnect_after_disconnect() { if service1_state == ServiceState::FirstConnec { service1.behaviour_mut().disconnect_peer( Swarm::local_peer_id(&service2), - sc_peerset::SetId::from(0), + crate::peerset::SetId::from(0), ); } }, @@ -264,8 +315,20 @@ fn reconnect_after_disconnect() { _ => {}, } + // Due to the bug in `Notifications`, the disconnected node does not always detect that + // it was disconnected. The closed inbound substream is tolerated by design, and the + // closed outbound substream is not detected until something is sent into it. + // See [PR #13396](https://github.com/paritytech/substrate/pull/13396). + // This happens if the disconnecting node reconnects to it fast enough. + // In this case the disconnected node does not transit via `ServiceState::NotConnected` + // and stays in `ServiceState::FirstConnec`. + // TODO: update this once the fix is finally merged. if service1_state == ServiceState::ConnectedAgain && - service2_state == ServiceState::ConnectedAgain + service2_state == ServiceState::ConnectedAgain || + service1_state == ServiceState::ConnectedAgain && + service2_state == ServiceState::FirstConnec || + service1_state == ServiceState::FirstConnec && + service2_state == ServiceState::ConnectedAgain { break } diff --git a/client/network/src/protocol/notifications/upgrade.rs b/client/network/src/protocol/notifications/upgrade.rs index c273361acabd..70c6023623f5 100644 --- a/client/network/src/protocol/notifications/upgrade.rs +++ b/client/network/src/protocol/notifications/upgrade.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -20,8 +20,8 @@ pub use self::{ collec::UpgradeCollec, notifications::{ NotificationsHandshakeError, NotificationsIn, NotificationsInOpen, - NotificationsInSubstream, NotificationsOut, NotificationsOutError, NotificationsOutOpen, - NotificationsOutSubstream, + NotificationsInSubstream, NotificationsInSubstreamHandshake, NotificationsOut, + NotificationsOutError, NotificationsOutOpen, NotificationsOutSubstream, }, }; diff --git a/client/network/src/protocol/notifications/upgrade/collec.rs b/client/network/src/protocol/notifications/upgrade/collec.rs index db9850c8da74..791821b3f75d 100644 --- a/client/network/src/protocol/notifications/upgrade/collec.rs +++ b/client/network/src/protocol/notifications/upgrade/collec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -73,7 +73,7 @@ where } /// Groups a `ProtocolName` with a `usize`. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct ProtoNameWithUsize(T, usize); impl ProtocolName for ProtoNameWithUsize { @@ -99,3 +99,81 @@ impl>, O, E> Future for FutWithUsize { } } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::types::ProtocolName as ProtoName; + use libp2p::core::upgrade::{ProtocolName, UpgradeInfo}; + + // TODO: move to mocks + mockall::mock! { + pub ProtocolUpgrade {} + + impl UpgradeInfo for ProtocolUpgrade { + type Info = T; + type InfoIter = vec::IntoIter; + fn protocol_info(&self) -> vec::IntoIter; + } + } + + #[test] + fn protocol_info() { + let upgrades = (1..=3) + .map(|i| { + let mut upgrade = MockProtocolUpgrade::>::new(); + upgrade.expect_protocol_info().return_once(move || { + vec![ProtoNameWithUsize(ProtoName::from(format!("protocol{i}")), i)].into_iter() + }); + upgrade + }) + .collect::>(); + + let upgrade: UpgradeCollec<_> = upgrades.into_iter().collect::>(); + let protos = vec![ + ProtoNameWithUsize(ProtoNameWithUsize(ProtoName::from("protocol1".to_string()), 1), 0), + ProtoNameWithUsize(ProtoNameWithUsize(ProtoName::from("protocol2".to_string()), 2), 1), + ProtoNameWithUsize(ProtoNameWithUsize(ProtoName::from("protocol3".to_string()), 3), 2), + ]; + let upgrades = upgrade.protocol_info().collect::>(); + + assert_eq!(upgrades, protos,); + } + + #[test] + fn nested_protocol_info() { + let mut upgrades = (1..=2) + .map(|i| { + let mut upgrade = MockProtocolUpgrade::>::new(); + upgrade.expect_protocol_info().return_once(move || { + vec![ProtoNameWithUsize(ProtoName::from(format!("protocol{i}")), i)].into_iter() + }); + upgrade + }) + .collect::>(); + + upgrades.push({ + let mut upgrade = MockProtocolUpgrade::>::new(); + upgrade.expect_protocol_info().return_once(move || { + vec![ + ProtoNameWithUsize(ProtoName::from("protocol22".to_string()), 1), + ProtoNameWithUsize(ProtoName::from("protocol33".to_string()), 2), + ProtoNameWithUsize(ProtoName::from("protocol44".to_string()), 3), + ] + .into_iter() + }); + upgrade + }); + + let upgrade: UpgradeCollec<_> = upgrades.into_iter().collect::>(); + let protos = vec![ + ProtoNameWithUsize(ProtoNameWithUsize(ProtoName::from("protocol1".to_string()), 1), 0), + ProtoNameWithUsize(ProtoNameWithUsize(ProtoName::from("protocol2".to_string()), 2), 1), + ProtoNameWithUsize(ProtoNameWithUsize(ProtoName::from("protocol22".to_string()), 1), 2), + ProtoNameWithUsize(ProtoNameWithUsize(ProtoName::from("protocol33".to_string()), 2), 2), + ProtoNameWithUsize(ProtoNameWithUsize(ProtoName::from("protocol44".to_string()), 3), 2), + ]; + let upgrades = upgrade.protocol_info().collect::>(); + assert_eq!(upgrades, protos,); + } +} diff --git a/client/network/src/protocol/notifications/upgrade/notifications.rs b/client/network/src/protocol/notifications/upgrade/notifications.rs index 5a4ec832dbea..4e1c033f33b6 100644 --- a/client/network/src/protocol/notifications/upgrade/notifications.rs +++ b/client/network/src/protocol/notifications/upgrade/notifications.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,7 +16,6 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use asynchronous_codec::Framed; /// Notifications protocol. /// /// The Substrate notifications protocol consists in the following: @@ -35,11 +34,15 @@ use asynchronous_codec::Framed; /// /// Notification substreams are unidirectional. If A opens a substream with B, then B is /// encouraged but not required to open a substream to A as well. +use crate::types::ProtocolName; + +use asynchronous_codec::Framed; use bytes::BytesMut; use futures::prelude::*; use libp2p::core::{upgrade, InboundUpgrade, OutboundUpgrade, UpgradeInfo}; use log::{error, warn}; -use sc_network_common::protocol::ProtocolName; +use unsigned_varint::codec::UviBytes; + use std::{ convert::Infallible, io, mem, @@ -47,7 +50,6 @@ use std::{ task::{Context, Poll}, vec, }; -use unsigned_varint::codec::UviBytes; /// Maximum allowed size of the two handshake messages, in bytes. const MAX_HANDSHAKE_SIZE: usize = 1024; @@ -88,7 +90,8 @@ pub struct NotificationsInSubstream { } /// State of the handshake sending back process. -enum NotificationsInSubstreamHandshake { +#[derive(Debug)] +pub enum NotificationsInSubstreamHandshake { /// Waiting for the user to give us the handshake message. NotSent, /// User gave us the handshake message. Trying to push it in the socket. @@ -111,6 +114,13 @@ pub struct NotificationsOutSubstream { socket: Framed>>>, } +#[cfg(test)] +impl NotificationsOutSubstream { + pub fn new(socket: Framed>>>) -> Self { + Self { socket } + } +} + impl NotificationsIn { /// Builds a new potential upgrade. pub fn new( @@ -193,6 +203,14 @@ impl NotificationsInSubstream where TSubstream: AsyncRead + AsyncWrite + Unpin, { + #[cfg(test)] + pub fn new( + socket: Framed>>>, + handshake: NotificationsInSubstreamHandshake, + ) -> Self { + Self { socket, handshake } + } + /// Sends the handshake in order to inform the remote that we accept the substream. pub fn send_handshake(&mut self, message: impl Into>) { if !matches!(self.handshake, NotificationsInSubstreamHandshake::NotSent) { diff --git a/client/network/src/protocol_controller.rs b/client/network/src/protocol_controller.rs new file mode 100644 index 000000000000..5b421e1485d6 --- /dev/null +++ b/client/network/src/protocol_controller.rs @@ -0,0 +1,1979 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +//! Protocol Controller. Generic implementation of peer management for protocols. +//! Responsible for accepting/rejecting incoming connections and initiating outgoing connections, +//! respecting the inbound and outbound peer slot counts. Communicates with `PeerStore` to get and +//! update peer reputation values and sends commands to `Notifications`. +//! +//! Due to asynchronous nature of communication between `ProtocolController` and `Notifications`, +//! `ProtocolController` has an imperfect view of the states of the peers. To reduce this +//! desynchronization, the following measures are taken: +//! +//! 1. Network peer events from `Notifications` are prioritized over actions from external API and +//! internal actions by `ProtocolController` (like slot allocation). +//! 2. `Notifications` ignores all commands from `ProtocolController` after sending "incoming" +//! request until receiving the answer to this "incoming" request. +//! 3. After sending a "connect" message, `ProtocolController` switches the state of the peer from +//! `Outbound` to `Inbound` if it receives an "incoming" request from `Notifications` for this +//! peer. +//! +//! These measures do not eliminate confusing commands from `ProtocolController` completely, +//! so `Notifications` must correctly handle seemingly inconsistent commands, like a "connect" +//! command for the peer it thinks is already connected, and a "drop" command for a peer that +//! was previously dropped. +//! +//! Even though this does not guarantee that `ProtocolController` and `Notifications` have the same +//! view of the peers' states at any given moment, the eventual consistency is maintained. + +use futures::{channel::oneshot, future::Either, FutureExt, StreamExt}; +use libp2p::PeerId; +use log::{debug, error, trace, warn}; +use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; +use sp_arithmetic::traits::SaturatedConversion; +use std::{ + collections::{HashMap, HashSet}, + time::{Duration, Instant}, +}; +use wasm_timer::Delay; + +use crate::{ + peer_store::PeerStoreProvider, + peerset::{IncomingIndex, Message, SetConfig, SetId}, +}; + +/// Log target for this file. +pub const LOG_TARGET: &str = "peerset"; + +/// External API actions. +#[derive(Debug)] +enum Action { + /// Add a reserved peer or mark already connected peer as reserved. + AddReservedPeer(PeerId), + /// Remove a reserved peer. + RemoveReservedPeer(PeerId), + /// Update reserved peers to match the provided set. + SetReservedPeers(HashSet), + /// Set/unset reserved-only mode. + SetReservedOnly(bool), + /// Disconnect a peer. + DisconnectPeer(PeerId), + /// Get the list of reserved peers. + GetReservedPeers(oneshot::Sender>), +} + +/// Network events from `Notifications`. +#[derive(Debug)] +enum Event { + /// Incoming connection from the peer. + IncomingConnection(PeerId, IncomingIndex), + /// Connection with the peer dropped. + Dropped(PeerId), +} + +/// Shared handle to [`ProtocolController`]. Distributed around the code outside of the +/// protocol implementation. +#[derive(Debug, Clone)] +pub struct ProtocolHandle { + /// Actions from outer API. + actions_tx: TracingUnboundedSender, + /// Connection events from `Notifications`. We prioritize them over actions. + events_tx: TracingUnboundedSender, +} + +impl ProtocolHandle { + /// Adds a new reserved peer. [`ProtocolController`] will make an effort + /// to always remain connected to this peer. + /// + /// Has no effect if the node was already a reserved peer. + /// + /// > **Note**: Keep in mind that the networking has to know an address for this node, + /// > otherwise it will not be able to connect to it. + pub fn add_reserved_peer(&self, peer_id: PeerId) { + let _ = self.actions_tx.unbounded_send(Action::AddReservedPeer(peer_id)); + } + + /// Demotes reserved peer to non-reserved. Does not disconnect the peer. + /// + /// Has no effect if the node was not a reserved peer. + pub fn remove_reserved_peer(&self, peer_id: PeerId) { + let _ = self.actions_tx.unbounded_send(Action::RemoveReservedPeer(peer_id)); + } + + /// Set reserved peers to the new set. + pub fn set_reserved_peers(&self, peer_ids: HashSet) { + let _ = self.actions_tx.unbounded_send(Action::SetReservedPeers(peer_ids)); + } + + /// Sets whether or not [`ProtocolController`] only has connections with nodes marked + /// as reserved for the given set. + pub fn set_reserved_only(&self, reserved: bool) { + let _ = self.actions_tx.unbounded_send(Action::SetReservedOnly(reserved)); + } + + /// Disconnect peer. You should remove the `PeerId` from the `PeerStore` first + /// to not connect to the peer again during the next slot allocation. + pub fn disconnect_peer(&self, peer_id: PeerId) { + let _ = self.actions_tx.unbounded_send(Action::DisconnectPeer(peer_id)); + } + + /// Get the list of reserved peers. + pub fn reserved_peers(&self, pending_response: oneshot::Sender>) { + let _ = self.actions_tx.unbounded_send(Action::GetReservedPeers(pending_response)); + } + + /// Notify about incoming connection. [`ProtocolController`] will either accept or reject it. + pub fn incoming_connection(&self, peer_id: PeerId, incoming_index: IncomingIndex) { + let _ = self + .events_tx + .unbounded_send(Event::IncomingConnection(peer_id, incoming_index)); + } + + /// Notify that connection was dropped (either refused or disconnected). + pub fn dropped(&self, peer_id: PeerId) { + let _ = self.events_tx.unbounded_send(Event::Dropped(peer_id)); + } +} + +/// Direction of a connection +#[derive(Clone, Copy, Debug)] +enum Direction { + Inbound, + Outbound, +} + +/// Status of a connection with a peer. +#[derive(Clone, Debug)] +enum PeerState { + /// We are connected to the peer. + Connected(Direction), + /// We are not connected. + NotConnected, +} + +impl PeerState { + /// Returns true if we are connected with the node. + fn is_connected(&self) -> bool { + matches!(self, PeerState::Connected(_)) + } +} + +impl Default for PeerState { + fn default() -> PeerState { + PeerState::NotConnected + } +} + +/// Side of [`ProtocolHandle`] responsible for all the logic. Currently all instances are +/// owned by [`crate::Peerset`], but they should eventually be moved to corresponding protocols. +#[derive(Debug)] +pub struct ProtocolController { + /// Set id to use when sending connect/drop requests to `Notifications`. + // Will likely be replaced by `ProtocolName` in the future. + set_id: SetId, + /// Receiver for outer API messages from [`ProtocolHandle`]. + actions_rx: TracingUnboundedReceiver, + /// Receiver for connection events from `Notifications` sent via [`ProtocolHandle`]. + events_rx: TracingUnboundedReceiver, + /// Number of occupied slots for incoming connections (not counting reserved nodes). + num_in: u32, + /// Number of occupied slots for outgoing connections (not counting reserved nodes). + num_out: u32, + /// Maximum number of slots for incoming connections (not counting reserved nodes). + max_in: u32, + /// Maximum number of slots for outgoing connections (not counting reserved nodes). + max_out: u32, + /// Connected regular nodes. + nodes: HashMap, + /// Reserved nodes. Should be always connected and do not occupy peer slots. + reserved_nodes: HashMap, + /// Connect only to reserved nodes. + reserved_only: bool, + /// Next time to allocate slots. This is done once per second. + next_periodic_alloc_slots: Instant, + /// Outgoing channel for messages to `Notifications`. + to_notifications: TracingUnboundedSender, + /// `PeerStore` handle for checking peer reputation values and getting connection candidates + /// with highest reputation. + peer_store: Box, +} + +impl ProtocolController { + /// Construct new [`ProtocolController`]. + pub fn new( + set_id: SetId, + config: SetConfig, + to_notifications: TracingUnboundedSender, + peer_store: Box, + ) -> (ProtocolHandle, ProtocolController) { + let (actions_tx, actions_rx) = tracing_unbounded("mpsc_api_protocol", 10_000); + let (events_tx, events_rx) = tracing_unbounded("mpsc_notifications_protocol", 10_000); + let handle = ProtocolHandle { actions_tx, events_tx }; + peer_store.register_protocol(handle.clone()); + let reserved_nodes = + config.reserved_nodes.iter().map(|p| (*p, PeerState::NotConnected)).collect(); + let controller = ProtocolController { + set_id, + actions_rx, + events_rx, + num_in: 0, + num_out: 0, + max_in: config.in_peers, + max_out: config.out_peers, + nodes: HashMap::new(), + reserved_nodes, + reserved_only: config.reserved_only, + next_periodic_alloc_slots: Instant::now(), + to_notifications, + peer_store, + }; + (handle, controller) + } + + /// Drive [`ProtocolController`]. This function returns when all instances of + /// [`ProtocolHandle`] are dropped. + pub async fn run(mut self) { + while self.next_action().await {} + } + + /// Perform one action. Returns `true` if it should be called again. + /// + /// Intended for tests only. Use `run` for driving [`ProtocolController`]. + pub async fn next_action(&mut self) -> bool { + let either = loop { + let mut next_alloc_slots = Delay::new_at(self.next_periodic_alloc_slots).fuse(); + + // See the module doc for why we use `select_biased!`. + futures::select_biased! { + event = self.events_rx.next() => match event { + Some(event) => break Either::Left(event), + None => return false, + }, + action = self.actions_rx.next() => match action { + Some(action) => break Either::Right(action), + None => return false, + }, + _ = next_alloc_slots => { + self.alloc_slots(); + self.next_periodic_alloc_slots = Instant::now() + Duration::new(1, 0); + }, + } + }; + + match either { + Either::Left(event) => self.process_event(event), + Either::Right(action) => self.process_action(action), + } + + true + } + + /// Process connection event. + fn process_event(&mut self, event: Event) { + match event { + Event::IncomingConnection(peer_id, index) => + self.on_incoming_connection(peer_id, index), + Event::Dropped(peer_id) => self.on_peer_dropped(peer_id), + } + } + + /// Process action command. + fn process_action(&mut self, action: Action) { + match action { + Action::AddReservedPeer(peer_id) => self.on_add_reserved_peer(peer_id), + Action::RemoveReservedPeer(peer_id) => self.on_remove_reserved_peer(peer_id), + Action::SetReservedPeers(peer_ids) => self.on_set_reserved_peers(peer_ids), + Action::SetReservedOnly(reserved_only) => self.on_set_reserved_only(reserved_only), + Action::DisconnectPeer(peer_id) => self.on_disconnect_peer(peer_id), + Action::GetReservedPeers(pending_response) => + self.on_get_reserved_peers(pending_response), + } + } + + /// Send "accept" message to `Notifications`. + fn accept_connection(&mut self, peer_id: PeerId, incoming_index: IncomingIndex) { + trace!( + target: LOG_TARGET, + "Accepting {peer_id} ({incoming_index:?}) on {:?} ({}/{} num_in/max_in).", + self.set_id, + self.num_in, + self.max_in, + ); + + let _ = self.to_notifications.unbounded_send(Message::Accept(incoming_index)); + } + + /// Send "reject" message to `Notifications`. + fn reject_connection(&mut self, peer_id: PeerId, incoming_index: IncomingIndex) { + trace!( + target: LOG_TARGET, + "Rejecting {peer_id} ({incoming_index:?}) on {:?} ({}/{} num_in/max_in).", + self.set_id, + self.num_in, + self.max_in, + ); + + let _ = self.to_notifications.unbounded_send(Message::Reject(incoming_index)); + } + + /// Send "connect" message to `Notifications`. + fn start_connection(&mut self, peer_id: PeerId) { + trace!( + target: LOG_TARGET, + "Connecting to {peer_id} on {:?} ({}/{} num_out/max_out).", + self.set_id, + self.num_out, + self.max_out, + ); + + let _ = self + .to_notifications + .unbounded_send(Message::Connect { set_id: self.set_id, peer_id }); + } + + /// Send "drop" message to `Notifications`. + fn drop_connection(&mut self, peer_id: PeerId) { + trace!( + target: LOG_TARGET, + "Dropping {peer_id} on {:?} ({}/{} num_in/max_in, {}/{} num_out/max_out).", + self.set_id, + self.num_in, + self.max_in, + self.num_out, + self.max_out, + ); + + let _ = self + .to_notifications + .unbounded_send(Message::Drop { set_id: self.set_id, peer_id }); + } + + /// Report peer disconnect event to `PeerStore` for it to update peer's reputation accordingly. + /// Should only be called if the remote node disconnected us, not the other way around. + fn report_disconnect(&mut self, peer_id: PeerId) { + self.peer_store.report_disconnect(peer_id); + } + + /// Ask `Peerset` if the peer has a reputation value not sufficent for connection with it. + fn is_banned(&self, peer_id: &PeerId) -> bool { + self.peer_store.is_banned(peer_id) + } + + /// Add the peer to the set of reserved peers. [`ProtocolController`] will try to always + /// maintain connections with such peers. + fn on_add_reserved_peer(&mut self, peer_id: PeerId) { + if self.reserved_nodes.contains_key(&peer_id) { + warn!( + target: LOG_TARGET, + "Trying to add an already reserved node {peer_id} as reserved on {:?}.", + self.set_id, + ); + return + } + + // Get the peer out of non-reserved peers if it's there. + let state = match self.nodes.remove(&peer_id) { + Some(direction) => { + trace!( + target: LOG_TARGET, + "Marking previously connected node {} ({:?}) as reserved on {:?}.", + peer_id, + direction, + self.set_id + ); + PeerState::Connected(direction) + }, + None => { + trace!( + target: LOG_TARGET, + "Adding reserved node {peer_id} on {:?}.", + self.set_id, + ); + PeerState::NotConnected + }, + }; + + self.reserved_nodes.insert(peer_id, state.clone()); + + // Discount occupied slots or connect to the node. + match state { + PeerState::Connected(Direction::Inbound) => self.num_in -= 1, + PeerState::Connected(Direction::Outbound) => self.num_out -= 1, + PeerState::NotConnected => self.alloc_slots(), + } + } + + /// Remove the peer from the set of reserved peers. The peer is moved to the set of regular + /// nodes. + fn on_remove_reserved_peer(&mut self, peer_id: PeerId) { + let state = match self.reserved_nodes.remove(&peer_id) { + Some(state) => state, + None => { + warn!( + target: LOG_TARGET, + "Trying to remove unknown reserved node {peer_id} from {:?}.", + self.set_id, + ); + return + }, + }; + + if let PeerState::Connected(direction) = state { + if self.reserved_only { + // Disconnect the node. + trace!( + target: LOG_TARGET, + "Disconnecting previously reserved node {peer_id} ({direction:?}) on {:?}.", + self.set_id, + ); + self.drop_connection(peer_id); + } else { + // Count connections as of regular node. + trace!( + target: LOG_TARGET, + "Making a connected reserved node {peer_id} ({:?}) on {:?} a regular one.", + direction, + self.set_id, + ); + + match direction { + Direction::Inbound => self.num_in += 1, + Direction::Outbound => self.num_out += 1, + } + + // Put the node into the list of regular nodes. + let prev = self.nodes.insert(peer_id, direction); + assert!(prev.is_none(), "Corrupted state: reserved node was also non-reserved."); + } + } else { + trace!( + target: LOG_TARGET, + "Removed disconnected reserved node {peer_id} from {:?}.", + self.set_id, + ); + } + } + + /// Replace the set of reserved peers. + fn on_set_reserved_peers(&mut self, peer_ids: HashSet) { + // Determine the difference between the current group and the new list. + let current = self.reserved_nodes.keys().cloned().collect(); + let to_insert = peer_ids.difference(¤t).cloned().collect::>(); + let to_remove = current.difference(&peer_ids).cloned().collect::>(); + + for node in to_insert { + self.on_add_reserved_peer(node); + } + + for node in to_remove { + self.on_remove_reserved_peer(node); + } + } + + /// Change "reserved only" flag. In "reserved only" mode we connect and accept connections to + /// reserved nodes only. + fn on_set_reserved_only(&mut self, reserved_only: bool) { + trace!(target: LOG_TARGET, "Set reserved only to `{reserved_only}` on {:?}", self.set_id); + + self.reserved_only = reserved_only; + + if !reserved_only { + return self.alloc_slots() + } + + // Disconnect all non-reserved peers. + self.nodes + .iter() + .map(|(k, v)| (*k, *v)) + .collect::>() + .iter() + .for_each(|(peer_id, direction)| { + // Update counters in the loop for `drop_connection` to report the correct number. + match direction { + Direction::Inbound => self.num_in -= 1, + Direction::Outbound => self.num_out -= 1, + } + self.drop_connection(*peer_id) + }); + self.nodes.clear(); + } + + /// Get the list of reserved peers. + fn on_get_reserved_peers(&self, pending_response: oneshot::Sender>) { + let _ = pending_response.send(self.reserved_nodes.keys().cloned().collect()); + } + + /// Disconnect the peer. + fn on_disconnect_peer(&mut self, peer_id: PeerId) { + // Don't do anything if the node is reserved. + if self.reserved_nodes.contains_key(&peer_id) { + debug!( + target: LOG_TARGET, + "Ignoring request to disconnect reserved peer {peer_id} from {:?}.", + self.set_id, + ); + return + } + + match self.nodes.remove(&peer_id) { + Some(direction) => { + trace!( + target: LOG_TARGET, + "Disconnecting peer {peer_id} ({direction:?}) from {:?}.", + self.set_id + ); + match direction { + Direction::Inbound => self.num_in -= 1, + Direction::Outbound => self.num_out -= 1, + } + self.drop_connection(peer_id); + }, + None => { + debug!( + target: LOG_TARGET, + "Trying to disconnect unknown peer {peer_id} from {:?}.", + self.set_id, + ); + }, + } + } + + /// Indicate that we received an incoming connection. Must be answered either with + /// a corresponding `Accept` or `Reject`, except if we were already connected to this peer. + /// + /// Note that this mechanism is orthogonal to `Connect`/`Drop`. Accepting an incoming + /// connection implicitly means `Connect`, but incoming connections aren't cancelled by + /// `dropped`. + // Implementation note: because of concurrency issues, `ProtocolController` has an imperfect + // view of the peers' states, and may issue commands for a peer after `Notifications` received + // an incoming request for that peer. In this case, `Notifications` ignores all the commands + // until it receives a response for the incoming request to `ProtocolController`, so we must + // ensure we handle this incoming request correctly. + fn on_incoming_connection(&mut self, peer_id: PeerId, incoming_index: IncomingIndex) { + trace!( + target: LOG_TARGET, + "Incoming connection from peer {peer_id} ({incoming_index:?}) on {:?}.", + self.set_id, + ); + + if self.reserved_only && !self.reserved_nodes.contains_key(&peer_id) { + self.reject_connection(peer_id, incoming_index); + return + } + + // Check if the node is reserved first. + if let Some(state) = self.reserved_nodes.get_mut(&peer_id) { + match state { + PeerState::Connected(ref mut direction) => { + // We are accepting an incoming connection, so ensure the direction is inbound. + // (See the implementation note above.) + *direction = Direction::Inbound; + self.accept_connection(peer_id, incoming_index); + }, + PeerState::NotConnected => + if self.peer_store.is_banned(&peer_id) { + self.reject_connection(peer_id, incoming_index); + } else { + *state = PeerState::Connected(Direction::Inbound); + self.accept_connection(peer_id, incoming_index); + }, + } + return + } + + // If we're already connected, pretend we are not connected and decide on the node again. + // (See the note above.) + if let Some(direction) = self.nodes.remove(&peer_id) { + trace!( + target: LOG_TARGET, + "Handling incoming connection from peer {} we think we already connected as {:?} on {:?}.", + peer_id, + direction, + self.set_id + ); + match direction { + Direction::Inbound => self.num_in -= 1, + Direction::Outbound => self.num_out -= 1, + } + } + + if self.num_in >= self.max_in { + self.reject_connection(peer_id, incoming_index); + return + } + + if self.is_banned(&peer_id) { + self.reject_connection(peer_id, incoming_index); + return + } + + self.num_in += 1; + self.nodes.insert(peer_id, Direction::Inbound); + self.accept_connection(peer_id, incoming_index); + } + + /// Indicate that a connection with the peer was dropped. + fn on_peer_dropped(&mut self, peer_id: PeerId) { + self.on_peer_dropped_inner(peer_id).unwrap_or_else(|peer_id| { + // We do not assert here, because due to asynchronous nature of communication + // between `ProtocolController` and `Notifications` we can receive `Action::Dropped` + // for a peer we already disconnected ourself. + trace!( + target: LOG_TARGET, + "Received `Action::Dropped` for not connected peer {peer_id} on {:?}.", + self.set_id, + ) + }); + } + + /// Indicate that a connection with the peer was dropped. + /// Returns `Err(PeerId)` if the peer wasn't connected or is not known to us. + fn on_peer_dropped_inner(&mut self, peer_id: PeerId) -> Result<(), PeerId> { + if self.drop_reserved_peer(&peer_id)? || self.drop_regular_peer(&peer_id) { + // The peer found and disconnected. + self.report_disconnect(peer_id); + Ok(()) + } else { + // The peer was not found in neither regular or reserved lists. + Err(peer_id) + } + } + + /// Try dropping the peer as a reserved peer. Return `Ok(true)` if the peer was found and + /// disconnected, `Ok(false)` if it wasn't found, `Err(PeerId)`, if the peer found, but not in + /// connected state. + fn drop_reserved_peer(&mut self, peer_id: &PeerId) -> Result { + let Some(state) = self.reserved_nodes.get_mut(peer_id) else { return Ok(false) }; + + if let PeerState::Connected(direction) = state { + trace!( + target: LOG_TARGET, + "Reserved peer {peer_id} ({direction:?}) dropped from {:?}.", + self.set_id, + ); + *state = PeerState::NotConnected; + Ok(true) + } else { + Err(*peer_id) + } + } + + /// Try dropping the peer as a regular peer. Return `true` if the peer was found and + /// disconnected, `false` if it wasn't found. + fn drop_regular_peer(&mut self, peer_id: &PeerId) -> bool { + let Some(direction) = self.nodes.remove(peer_id) else { return false }; + + trace!( + target: LOG_TARGET, + "Peer {peer_id} ({direction:?}) dropped from {:?}.", + self.set_id, + ); + + match direction { + Direction::Inbound => self.num_in -= 1, + Direction::Outbound => self.num_out -= 1, + } + + true + } + + /// Initiate outgoing connections trying to connect all reserved nodes and fill in all outgoing + /// slots. + fn alloc_slots(&mut self) { + // Try connecting to reserved nodes first, ignoring nodes with outstanding events/actions. + self.reserved_nodes + .iter_mut() + .filter_map(|(peer_id, state)| { + (!state.is_connected() && !self.peer_store.is_banned(peer_id)).then(|| { + *state = PeerState::Connected(Direction::Outbound); + peer_id + }) + }) + .cloned() + .collect::>() + .into_iter() + .for_each(|peer_id| { + self.start_connection(peer_id); + }); + + // Nothing more to do if we're in reserved-only mode or don't have slots available. + if self.reserved_only || self.num_out >= self.max_out { + return + } + + // Fill available slots. + let available_slots = (self.max_out - self.num_out).saturated_into(); + + // Ignore reserved nodes (connected above), already connected nodes, and nodes with + // outstanding events/actions. + let ignored = self + .reserved_nodes + .keys() + .collect::>() + .union(&self.nodes.keys().collect::>()) + .cloned() + .collect(); + + let candidates = self + .peer_store + .outgoing_candidates(available_slots, ignored) + .into_iter() + .filter_map(|peer_id| { + (!self.reserved_nodes.contains_key(&peer_id) && !self.nodes.contains_key(&peer_id)) + .then_some(peer_id) + .or_else(|| { + error!( + target: LOG_TARGET, + "`PeerStore` returned a node we asked to ignore: {peer_id}.", + ); + debug_assert!(false, "`PeerStore` returned a node we asked to ignore."); + None + }) + }) + .collect::>(); + + if candidates.len() > available_slots { + error!( + target: LOG_TARGET, + "`PeerStore` returned more nodes than there are slots available.", + ); + debug_assert!(false, "`PeerStore` returned more nodes than there are slots available."); + } + + candidates.into_iter().take(available_slots).for_each(|peer_id| { + self.num_out += 1; + self.nodes.insert(peer_id, Direction::Outbound); + self.start_connection(peer_id); + }) + } +} + +#[cfg(test)] +mod tests { + use super::{Direction, PeerState, ProtocolController, ProtocolHandle}; + use crate::{ + peer_store::PeerStoreProvider, + peerset::{IncomingIndex, Message, SetConfig, SetId}, + ReputationChange, + }; + use libp2p::PeerId; + use sc_utils::mpsc::{tracing_unbounded, TryRecvError}; + use std::collections::HashSet; + + mockall::mock! { + #[derive(Debug)] + pub PeerStoreHandle {} + + impl PeerStoreProvider for PeerStoreHandle { + fn is_banned(&self, peer_id: &PeerId) -> bool; + fn register_protocol(&self, protocol_handle: ProtocolHandle); + fn report_disconnect(&mut self, peer_id: PeerId); + fn report_peer(&mut self, peer_id: PeerId, change: ReputationChange); + fn peer_reputation(&self, peer_id: &PeerId) -> i32; + fn outgoing_candidates<'a>(&self, count: usize, ignored: HashSet<&'a PeerId>) -> Vec; + } + } + + #[test] + fn reserved_nodes_are_connected_dropped_and_accepted() { + let reserved1 = PeerId::random(); + let reserved2 = PeerId::random(); + + // Add first reserved node via config. + let config = SetConfig { + in_peers: 0, + out_peers: 0, + bootnodes: Vec::new(), + reserved_nodes: std::iter::once(reserved1).collect(), + reserved_only: true, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().times(4).return_const(false); + peer_store.expect_report_disconnect().times(2).return_const(()); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Add second reserved node at runtime (this currently calls `alloc_slots` internally). + controller.on_add_reserved_peer(reserved2); + + // Initiate connections (currently, `alloc_slots` is also called internally in + // `on_add_reserved_peer` above). + controller.alloc_slots(); + + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + assert_eq!(messages.len(), 2); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: reserved1 })); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: reserved2 })); + + // Reserved peers do not occupy slots. + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + + // Drop connections to be able to accept reserved nodes. + controller.on_peer_dropped(reserved1); + controller.on_peer_dropped(reserved2); + + // Incoming connection from `reserved1`. + let incoming1 = IncomingIndex(1); + controller.on_incoming_connection(reserved1, incoming1); + assert_eq!(rx.try_recv().unwrap(), Message::Accept(incoming1)); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + + // Incoming connection from `reserved2`. + let incoming2 = IncomingIndex(2); + controller.on_incoming_connection(reserved2, incoming2); + assert_eq!(rx.try_recv().unwrap(), Message::Accept(incoming2)); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + + // Reserved peers do not occupy slots. + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + } + + #[test] + fn banned_reserved_nodes_are_not_connected_and_not_accepted() { + let reserved1 = PeerId::random(); + let reserved2 = PeerId::random(); + + // Add first reserved node via config. + let config = SetConfig { + in_peers: 0, + out_peers: 0, + bootnodes: Vec::new(), + reserved_nodes: std::iter::once(reserved1).collect(), + reserved_only: true, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().times(6).return_const(true); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Add second reserved node at runtime (this currently calls `alloc_slots` internally). + controller.on_add_reserved_peer(reserved2); + + // Initiate connections. + controller.alloc_slots(); + + // No slots occupied. + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + + // No commands are generated. + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + + // Incoming connection from `reserved1`. + let incoming1 = IncomingIndex(1); + controller.on_incoming_connection(reserved1, incoming1); + assert_eq!(rx.try_recv().unwrap(), Message::Reject(incoming1)); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + + // Incoming connection from `reserved2`. + let incoming2 = IncomingIndex(2); + controller.on_incoming_connection(reserved2, incoming2); + assert_eq!(rx.try_recv().unwrap(), Message::Reject(incoming2)); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + + // No slots occupied. + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + } + + #[test] + fn we_try_to_reconnect_to_dropped_reserved_nodes() { + let reserved1 = PeerId::random(); + let reserved2 = PeerId::random(); + + // Add first reserved node via config. + let config = SetConfig { + in_peers: 0, + out_peers: 0, + bootnodes: Vec::new(), + reserved_nodes: std::iter::once(reserved1).collect(), + reserved_only: true, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().times(4).return_const(false); + peer_store.expect_report_disconnect().times(2).return_const(()); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Add second reserved node at runtime (this calls `alloc_slots` internally). + controller.on_add_reserved_peer(reserved2); + + // Initiate connections (actually redundant, see previous comment). + controller.alloc_slots(); + + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + + assert_eq!(messages.len(), 2); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: reserved1 })); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: reserved2 })); + + // Drop both reserved nodes. + controller.on_peer_dropped(reserved1); + controller.on_peer_dropped(reserved2); + + // Initiate connections. + controller.alloc_slots(); + + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + + assert_eq!(messages.len(), 2); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: reserved1 })); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: reserved2 })); + + // No slots occupied. + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + } + + #[test] + fn nodes_supplied_by_peer_store_are_connected() { + let peer1 = PeerId::random(); + let peer2 = PeerId::random(); + let candidates = vec![peer1, peer2]; + + let config = SetConfig { + in_peers: 0, + // Less slots than candidates. + out_peers: 2, + bootnodes: Vec::new(), + reserved_nodes: HashSet::new(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_outgoing_candidates().once().return_const(candidates); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Initiate connections. + controller.alloc_slots(); + + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + + // Only first two peers are connected (we only have 2 slots). + assert_eq!(messages.len(), 2); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: peer1 })); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: peer2 })); + + // Outgoing slots occupied. + assert_eq!(controller.num_out, 2); + assert_eq!(controller.num_in, 0); + + // No more nodes are connected. + controller.alloc_slots(); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + + // No more slots occupied. + assert_eq!(controller.num_out, 2); + assert_eq!(controller.num_in, 0); + } + + #[test] + fn both_reserved_nodes_and_nodes_supplied_by_peer_store_are_connected() { + let reserved1 = PeerId::random(); + let reserved2 = PeerId::random(); + let regular1 = PeerId::random(); + let regular2 = PeerId::random(); + let outgoing_candidates = vec![regular1, regular2]; + let reserved_nodes = [reserved1, reserved2].iter().cloned().collect(); + + let config = SetConfig { + in_peers: 10, + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes, + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().times(2).return_const(false); + peer_store.expect_outgoing_candidates().once().return_const(outgoing_candidates); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Initiate connections. + controller.alloc_slots(); + + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + assert_eq!(messages.len(), 4); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: reserved1 })); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: reserved2 })); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: regular1 })); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: regular2 })); + assert_eq!(controller.num_out, 2); + assert_eq!(controller.num_in, 0); + } + + #[test] + fn if_slots_are_freed_we_try_to_allocate_them_again() { + let peer1 = PeerId::random(); + let peer2 = PeerId::random(); + let peer3 = PeerId::random(); + let candidates1 = vec![peer1, peer2]; + let candidates2 = vec![peer3]; + + let config = SetConfig { + in_peers: 0, + // Less slots than candidates. + out_peers: 2, + bootnodes: Vec::new(), + reserved_nodes: HashSet::new(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_outgoing_candidates().once().return_const(candidates1); + peer_store.expect_outgoing_candidates().once().return_const(candidates2); + peer_store.expect_report_disconnect().times(2).return_const(()); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Initiate connections. + controller.alloc_slots(); + + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + + // Only first two peers are connected (we only have 2 slots). + assert_eq!(messages.len(), 2); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: peer1 })); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: peer2 })); + + // Outgoing slots occupied. + assert_eq!(controller.num_out, 2); + assert_eq!(controller.num_in, 0); + + // No more nodes are connected. + controller.alloc_slots(); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + + // No more slots occupied. + assert_eq!(controller.num_out, 2); + assert_eq!(controller.num_in, 0); + + // Drop peers. + controller.on_peer_dropped(peer1); + controller.on_peer_dropped(peer2); + + // Slots are freed. + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + + // Initiate connections. + controller.alloc_slots(); + + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + + // Peers are connected. + assert_eq!(messages.len(), 1); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: peer3 })); + + // Outgoing slots occupied. + assert_eq!(controller.num_out, 1); + assert_eq!(controller.num_in, 0); + } + + #[test] + fn in_reserved_only_mode_no_peers_are_requested_from_peer_store_and_connected() { + let config = SetConfig { + in_peers: 0, + // Make sure we have slots available. + out_peers: 2, + bootnodes: Vec::new(), + reserved_nodes: HashSet::new(), + reserved_only: true, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Initiate connections. + controller.alloc_slots(); + + // No nodes are connected. + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + } + + #[test] + fn in_reserved_only_mode_no_regular_peers_are_accepted() { + let config = SetConfig { + // Make sure we have slots available. + in_peers: 2, + out_peers: 0, + bootnodes: Vec::new(), + reserved_nodes: HashSet::new(), + reserved_only: true, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + let peer = PeerId::random(); + let incoming_index = IncomingIndex(1); + controller.on_incoming_connection(peer, incoming_index); + + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + + // Peer is rejected. + assert_eq!(messages.len(), 1); + assert!(messages.contains(&Message::Reject(incoming_index))); + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + } + + #[test] + fn disabling_reserved_only_mode_allows_to_connect_to_peers() { + let peer1 = PeerId::random(); + let peer2 = PeerId::random(); + let candidates = vec![peer1, peer2]; + + let config = SetConfig { + in_peers: 0, + // Make sure we have slots available. + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes: HashSet::new(), + reserved_only: true, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_outgoing_candidates().once().return_const(candidates); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Initiate connections. + controller.alloc_slots(); + + // No nodes are connected. + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + + // Disable reserved-only mode (this also connects to peers). + controller.on_set_reserved_only(false); + + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + + assert_eq!(messages.len(), 2); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: peer1 })); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: peer2 })); + assert_eq!(controller.num_out, 2); + assert_eq!(controller.num_in, 0); + } + + #[test] + fn enabling_reserved_only_mode_disconnects_regular_peers() { + let reserved1 = PeerId::random(); + let reserved2 = PeerId::random(); + let regular1 = PeerId::random(); + let regular2 = PeerId::random(); + let outgoing_candidates = vec![regular1]; + + let config = SetConfig { + in_peers: 10, + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes: [reserved1, reserved2].iter().cloned().collect(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().times(3).return_const(false); + peer_store.expect_outgoing_candidates().once().return_const(outgoing_candidates); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + + // Connect `regular1` as outbound. + controller.alloc_slots(); + + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + assert_eq!(messages.len(), 3); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: reserved1 })); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: reserved2 })); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: regular1 })); + assert_eq!(controller.num_out, 1); + assert_eq!(controller.num_in, 0); + + // Connect `regular2` as inbound. + let incoming_index = IncomingIndex(1); + controller.on_incoming_connection(regular2, incoming_index); + assert_eq!(rx.try_recv().unwrap(), Message::Accept(incoming_index)); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert_eq!(controller.num_out, 1); + assert_eq!(controller.num_in, 1); + + // Switch to reserved-only mode. + controller.on_set_reserved_only(true); + + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + assert_eq!(messages.len(), 2); + assert!(messages.contains(&Message::Drop { set_id: SetId::from(0), peer_id: regular1 })); + assert!(messages.contains(&Message::Drop { set_id: SetId::from(0), peer_id: regular2 })); + assert_eq!(controller.nodes.len(), 0); + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + } + + #[test] + fn removed_disconnected_reserved_node_is_forgotten() { + let reserved1 = PeerId::random(); + let reserved2 = PeerId::random(); + + let config = SetConfig { + in_peers: 10, + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes: [reserved1, reserved2].iter().cloned().collect(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + assert_eq!(controller.reserved_nodes.len(), 2); + assert_eq!(controller.nodes.len(), 0); + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + + controller.on_remove_reserved_peer(reserved1); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert_eq!(controller.reserved_nodes.len(), 1); + assert!(!controller.reserved_nodes.contains_key(&reserved1)); + assert_eq!(controller.nodes.len(), 0); + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + } + + #[test] + fn removed_connected_reserved_node_is_disconnected_in_reserved_only_mode() { + let reserved1 = PeerId::random(); + let reserved2 = PeerId::random(); + + let config = SetConfig { + in_peers: 10, + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes: [reserved1, reserved2].iter().cloned().collect(), + reserved_only: true, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().times(2).return_const(false); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Initiate connections. + controller.alloc_slots(); + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + assert_eq!(messages.len(), 2); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: reserved1 })); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: reserved2 })); + assert_eq!(controller.reserved_nodes.len(), 2); + assert!(controller.reserved_nodes.contains_key(&reserved1)); + assert!(controller.reserved_nodes.contains_key(&reserved2)); + assert!(controller.nodes.is_empty()); + + // Remove reserved node + controller.on_remove_reserved_peer(reserved1); + assert_eq!( + rx.try_recv().unwrap(), + Message::Drop { set_id: SetId::from(0), peer_id: reserved1 } + ); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert_eq!(controller.reserved_nodes.len(), 1); + assert!(controller.reserved_nodes.contains_key(&reserved2)); + assert!(controller.nodes.is_empty()); + } + + #[test] + fn removed_connected_reserved_nodes_become_regular_in_non_reserved_mode() { + let peer1 = PeerId::random(); + let peer2 = PeerId::random(); + + let config = SetConfig { + in_peers: 10, + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes: [peer1, peer2].iter().cloned().collect(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().times(2).return_const(false); + peer_store.expect_outgoing_candidates().once().return_const(Vec::new()); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Connect `peer1` as inbound, `peer2` as outbound. + controller.on_incoming_connection(peer1, IncomingIndex(1)); + controller.alloc_slots(); + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + assert_eq!(messages.len(), 2); + assert!(messages.contains(&Message::Accept(IncomingIndex(1)))); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: peer2 })); + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + + // Remove reserved nodes (and make them regular) + controller.on_remove_reserved_peer(peer1); + controller.on_remove_reserved_peer(peer2); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert_eq!(controller.nodes.len(), 2); + assert!(matches!(controller.nodes.get(&peer1), Some(Direction::Inbound))); + assert!(matches!(controller.nodes.get(&peer2), Some(Direction::Outbound))); + assert_eq!(controller.num_out, 1); + assert_eq!(controller.num_in, 1); + } + + #[test] + fn regular_nodes_stop_occupying_slots_when_become_reserved() { + let peer1 = PeerId::random(); + let peer2 = PeerId::random(); + let outgoing_candidates = vec![peer1]; + + let config = SetConfig { + in_peers: 10, + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes: HashSet::new(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().once().return_const(false); + peer_store.expect_outgoing_candidates().once().return_const(outgoing_candidates); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Connect `peer1` as outbound & `peer2` as inbound. + controller.alloc_slots(); + controller.on_incoming_connection(peer2, IncomingIndex(1)); + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + assert_eq!(messages.len(), 2); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: peer1 })); + assert!(messages.contains(&Message::Accept(IncomingIndex(1)))); + assert_eq!(controller.num_in, 1); + assert_eq!(controller.num_out, 1); + + controller.on_add_reserved_peer(peer1); + controller.on_add_reserved_peer(peer2); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert_eq!(controller.num_in, 0); + assert_eq!(controller.num_out, 0); + } + + #[test] + fn disconnecting_regular_peers_work() { + let peer1 = PeerId::random(); + let peer2 = PeerId::random(); + let outgoing_candidates = vec![peer1]; + + let config = SetConfig { + in_peers: 10, + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes: HashSet::new(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().once().return_const(false); + peer_store.expect_outgoing_candidates().once().return_const(outgoing_candidates); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Connect `peer1` as outbound & `peer2` as inbound. + controller.alloc_slots(); + controller.on_incoming_connection(peer2, IncomingIndex(1)); + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + assert_eq!(messages.len(), 2); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: peer1 })); + assert!(messages.contains(&Message::Accept(IncomingIndex(1)))); + assert_eq!(controller.nodes.len(), 2); + assert!(matches!(controller.nodes.get(&peer1), Some(Direction::Outbound))); + assert!(matches!(controller.nodes.get(&peer2), Some(Direction::Inbound))); + assert_eq!(controller.num_in, 1); + assert_eq!(controller.num_out, 1); + + controller.on_disconnect_peer(peer1); + assert_eq!( + rx.try_recv().unwrap(), + Message::Drop { set_id: SetId::from(0), peer_id: peer1 } + ); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert_eq!(controller.nodes.len(), 1); + assert!(!controller.nodes.contains_key(&peer1)); + assert_eq!(controller.num_in, 1); + assert_eq!(controller.num_out, 0); + + controller.on_disconnect_peer(peer2); + assert_eq!( + rx.try_recv().unwrap(), + Message::Drop { set_id: SetId::from(0), peer_id: peer2 } + ); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert_eq!(controller.nodes.len(), 0); + assert_eq!(controller.num_in, 0); + assert_eq!(controller.num_out, 0); + } + + #[test] + fn disconnecting_reserved_peers_is_a_noop() { + let reserved1 = PeerId::random(); + let reserved2 = PeerId::random(); + + let config = SetConfig { + in_peers: 10, + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes: [reserved1, reserved2].iter().cloned().collect(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().times(2).return_const(false); + peer_store.expect_outgoing_candidates().once().return_const(Vec::new()); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Connect `reserved1` as inbound & `reserved2` as outbound. + controller.on_incoming_connection(reserved1, IncomingIndex(1)); + controller.alloc_slots(); + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + assert_eq!(messages.len(), 2); + assert!(messages.contains(&Message::Accept(IncomingIndex(1)))); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: reserved2 })); + assert!(matches!( + controller.reserved_nodes.get(&reserved1), + Some(PeerState::Connected(Direction::Inbound)) + )); + assert!(matches!( + controller.reserved_nodes.get(&reserved2), + Some(PeerState::Connected(Direction::Outbound)) + )); + + controller.on_disconnect_peer(reserved1); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert!(matches!( + controller.reserved_nodes.get(&reserved1), + Some(PeerState::Connected(Direction::Inbound)) + )); + + controller.on_disconnect_peer(reserved2); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert!(matches!( + controller.reserved_nodes.get(&reserved2), + Some(PeerState::Connected(Direction::Outbound)) + )); + } + + #[test] + fn dropping_regular_peers_work() { + let peer1 = PeerId::random(); + let peer2 = PeerId::random(); + let outgoing_candidates = vec![peer1]; + + let config = SetConfig { + in_peers: 10, + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes: HashSet::new(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().once().return_const(false); + peer_store.expect_outgoing_candidates().once().return_const(outgoing_candidates); + peer_store.expect_report_disconnect().times(2).return_const(()); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Connect `peer1` as outbound & `peer2` as inbound. + controller.alloc_slots(); + controller.on_incoming_connection(peer2, IncomingIndex(1)); + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + assert_eq!(messages.len(), 2); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: peer1 })); + assert!(messages.contains(&Message::Accept(IncomingIndex(1)))); + assert_eq!(controller.nodes.len(), 2); + assert!(matches!(controller.nodes.get(&peer1), Some(Direction::Outbound))); + assert!(matches!(controller.nodes.get(&peer2), Some(Direction::Inbound))); + assert_eq!(controller.num_in, 1); + assert_eq!(controller.num_out, 1); + + controller.on_peer_dropped(peer1); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert_eq!(controller.nodes.len(), 1); + assert!(!controller.nodes.contains_key(&peer1)); + assert_eq!(controller.num_in, 1); + assert_eq!(controller.num_out, 0); + + controller.on_peer_dropped(peer2); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert_eq!(controller.nodes.len(), 0); + assert_eq!(controller.num_in, 0); + assert_eq!(controller.num_out, 0); + } + + #[test] + fn incoming_request_for_connected_reserved_node_switches_it_to_inbound() { + let reserved1 = PeerId::random(); + let reserved2 = PeerId::random(); + + let config = SetConfig { + in_peers: 10, + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes: [reserved1, reserved2].iter().cloned().collect(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().times(2).return_const(false); + peer_store.expect_outgoing_candidates().once().return_const(Vec::new()); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Connect `reserved1` as inbound & `reserved2` as outbound. + controller.on_incoming_connection(reserved1, IncomingIndex(1)); + controller.alloc_slots(); + let mut messages = Vec::new(); + while let Some(message) = rx.try_recv().ok() { + messages.push(message); + } + assert_eq!(messages.len(), 2); + assert!(messages.contains(&Message::Accept(IncomingIndex(1)))); + assert!(messages.contains(&Message::Connect { set_id: SetId::from(0), peer_id: reserved2 })); + assert!(matches!( + controller.reserved_nodes.get(&reserved1), + Some(PeerState::Connected(Direction::Inbound)) + )); + assert!(matches!( + controller.reserved_nodes.get(&reserved2), + Some(PeerState::Connected(Direction::Outbound)) + )); + + // Incoming request for `reserved1`. + controller.on_incoming_connection(reserved1, IncomingIndex(2)); + assert_eq!(rx.try_recv().ok().unwrap(), Message::Accept(IncomingIndex(2))); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert!(matches!( + controller.reserved_nodes.get(&reserved1), + Some(PeerState::Connected(Direction::Inbound)) + )); + + // Incoming request for `reserved2`. + controller.on_incoming_connection(reserved2, IncomingIndex(3)); + assert_eq!(rx.try_recv().ok().unwrap(), Message::Accept(IncomingIndex(3))); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert!(matches!( + controller.reserved_nodes.get(&reserved2), + Some(PeerState::Connected(Direction::Inbound)) + )); + } + + #[test] + fn incoming_request_for_connected_regular_node_switches_it_to_inbound() { + let regular1 = PeerId::random(); + let regular2 = PeerId::random(); + let outgoing_candidates = vec![regular1]; + + let config = SetConfig { + in_peers: 10, + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes: HashSet::new(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().times(3).return_const(false); + peer_store.expect_outgoing_candidates().once().return_const(outgoing_candidates); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + + // Connect `regular1` as outbound. + controller.alloc_slots(); + assert_eq!( + rx.try_recv().ok().unwrap(), + Message::Connect { set_id: SetId::from(0), peer_id: regular1 } + ); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert!(matches!(controller.nodes.get(®ular1).unwrap(), Direction::Outbound,)); + + // Connect `regular2` as inbound. + controller.on_incoming_connection(regular2, IncomingIndex(0)); + assert_eq!(rx.try_recv().ok().unwrap(), Message::Accept(IncomingIndex(0))); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert!(matches!(controller.nodes.get(®ular2).unwrap(), Direction::Inbound,)); + + // Incoming request for `regular1`. + controller.on_incoming_connection(regular1, IncomingIndex(1)); + assert_eq!(rx.try_recv().ok().unwrap(), Message::Accept(IncomingIndex(1))); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert!(matches!(controller.nodes.get(®ular1).unwrap(), Direction::Inbound,)); + + // Incoming request for `regular2`. + controller.on_incoming_connection(regular2, IncomingIndex(2)); + assert_eq!(rx.try_recv().ok().unwrap(), Message::Accept(IncomingIndex(2))); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert!(matches!(controller.nodes.get(®ular2).unwrap(), Direction::Inbound,)); + } + + #[test] + fn incoming_request_for_connected_node_is_rejected_if_its_banned() { + let regular1 = PeerId::random(); + let regular2 = PeerId::random(); + let outgoing_candidates = vec![regular1]; + + let config = SetConfig { + in_peers: 10, + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes: HashSet::new(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().once().return_const(false); + peer_store.expect_is_banned().times(2).return_const(true); + peer_store.expect_outgoing_candidates().once().return_const(outgoing_candidates); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + + // Connect `regular1` as outbound. + controller.alloc_slots(); + assert_eq!( + rx.try_recv().ok().unwrap(), + Message::Connect { set_id: SetId::from(0), peer_id: regular1 } + ); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert!(matches!(controller.nodes.get(®ular1).unwrap(), Direction::Outbound,)); + + // Connect `regular2` as inbound. + controller.on_incoming_connection(regular2, IncomingIndex(0)); + assert_eq!(rx.try_recv().ok().unwrap(), Message::Accept(IncomingIndex(0))); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert!(matches!(controller.nodes.get(®ular2).unwrap(), Direction::Inbound,)); + + // Incoming request for `regular1`. + controller.on_incoming_connection(regular1, IncomingIndex(1)); + assert_eq!(rx.try_recv().ok().unwrap(), Message::Reject(IncomingIndex(1))); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert!(!controller.nodes.contains_key(®ular1)); + + // Incoming request for `regular2`. + controller.on_incoming_connection(regular2, IncomingIndex(2)); + assert_eq!(rx.try_recv().ok().unwrap(), Message::Reject(IncomingIndex(2))); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert!(!controller.nodes.contains_key(®ular2)); + } + + #[test] + fn incoming_request_for_connected_node_is_rejected_if_no_slots_available() { + let regular1 = PeerId::random(); + let regular2 = PeerId::random(); + let outgoing_candidates = vec![regular1]; + + let config = SetConfig { + in_peers: 1, + out_peers: 1, + bootnodes: Vec::new(), + reserved_nodes: HashSet::new(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().once().return_const(false); + peer_store.expect_outgoing_candidates().once().return_const(outgoing_candidates); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + assert_eq!(controller.num_out, 0); + assert_eq!(controller.num_in, 0); + + // Connect `regular1` as outbound. + controller.alloc_slots(); + assert_eq!( + rx.try_recv().ok().unwrap(), + Message::Connect { set_id: SetId::from(0), peer_id: regular1 } + ); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert!(matches!(controller.nodes.get(®ular1).unwrap(), Direction::Outbound,)); + + // Connect `regular2` as inbound. + controller.on_incoming_connection(regular2, IncomingIndex(0)); + assert_eq!(rx.try_recv().ok().unwrap(), Message::Accept(IncomingIndex(0))); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert!(matches!(controller.nodes.get(®ular2).unwrap(), Direction::Inbound,)); + + controller.max_in = 0; + + // Incoming request for `regular1`. + controller.on_incoming_connection(regular1, IncomingIndex(1)); + assert_eq!(rx.try_recv().ok().unwrap(), Message::Reject(IncomingIndex(1))); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert!(!controller.nodes.contains_key(®ular1)); + + // Incoming request for `regular2`. + controller.on_incoming_connection(regular2, IncomingIndex(2)); + assert_eq!(rx.try_recv().ok().unwrap(), Message::Reject(IncomingIndex(2))); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + assert!(!controller.nodes.contains_key(®ular2)); + } + + #[test] + fn incoming_peers_that_exceed_slots_are_rejected() { + let peer1 = PeerId::random(); + let peer2 = PeerId::random(); + + let config = SetConfig { + in_peers: 1, + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes: HashSet::new(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().once().return_const(false); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Connect `peer1` as inbound. + controller.on_incoming_connection(peer1, IncomingIndex(1)); + assert_eq!(rx.try_recv().unwrap(), Message::Accept(IncomingIndex(1))); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + + // Incoming requests for `peer2`. + controller.on_incoming_connection(peer2, IncomingIndex(2)); + assert_eq!(rx.try_recv().unwrap(), Message::Reject(IncomingIndex(2))); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + } + + #[test] + fn banned_regular_incoming_node_is_rejected() { + let peer1 = PeerId::random(); + + let config = SetConfig { + in_peers: 10, + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes: HashSet::new(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().once().return_const(true); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + + // Incoming request. + controller.on_incoming_connection(peer1, IncomingIndex(1)); + assert_eq!(rx.try_recv().unwrap(), Message::Reject(IncomingIndex(1))); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + } + + #[test] + fn banned_reserved_incoming_node_is_rejected() { + let reserved1 = PeerId::random(); + + let config = SetConfig { + in_peers: 10, + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes: std::iter::once(reserved1).collect(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().once().return_const(true); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + assert!(controller.reserved_nodes.contains_key(&reserved1)); + + // Incoming request. + controller.on_incoming_connection(reserved1, IncomingIndex(1)); + assert_eq!(rx.try_recv().unwrap(), Message::Reject(IncomingIndex(1))); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + } + + #[test] + fn we_dont_connect_to_banned_reserved_node() { + let reserved1 = PeerId::random(); + + let config = SetConfig { + in_peers: 10, + out_peers: 10, + bootnodes: Vec::new(), + reserved_nodes: std::iter::once(reserved1).collect(), + reserved_only: false, + }; + let (tx, mut rx) = tracing_unbounded("mpsc_test_to_notifications", 100); + + let mut peer_store = MockPeerStoreHandle::new(); + peer_store.expect_register_protocol().once().return_const(()); + peer_store.expect_is_banned().once().return_const(true); + peer_store.expect_outgoing_candidates().once().return_const(Vec::new()); + + let (_handle, mut controller) = + ProtocolController::new(SetId::from(0), config, tx, Box::new(peer_store)); + assert!(matches!(controller.reserved_nodes.get(&reserved1), Some(PeerState::NotConnected))); + + // Initiate connectios + controller.alloc_slots(); + assert!(matches!(controller.reserved_nodes.get(&reserved1), Some(PeerState::NotConnected))); + assert_eq!(rx.try_recv().unwrap_err(), TryRecvError::Empty); + } +} diff --git a/client/network/src/request_responses.rs b/client/network/src/request_responses.rs index 45f103040090..44e6f588ab52 100644 --- a/client/network/src/request_responses.rs +++ b/client/network/src/request_responses.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,8 +18,8 @@ //! Collection of request-response protocols. //! -//! The [`RequestResponse`] struct defined in this module provides support for zero or more -//! so-called "request-response" protocols. +//! The [`RequestResponsesBehaviour`] struct defined in this module provides support for zero or +//! more so-called "request-response" protocols. //! //! A request-response protocol works in the following way: //! @@ -34,30 +34,23 @@ //! - If provided, a ["requests processing"](ProtocolConfig::inbound_queue) channel //! is used to handle incoming requests. -use crate::ReputationChange; -use futures::{ - channel::{mpsc, oneshot}, - prelude::*, +use crate::{ + peer_store::BANNED_THRESHOLD, peerset::PeersetHandle, types::ProtocolName, ReputationChange, }; + +use futures::{channel::oneshot, prelude::*}; use libp2p::{ - core::{connection::ConnectionId, Multiaddr, PeerId}, - request_response::{ - handler::RequestResponseHandler, ProtocolSupport, RequestResponse, RequestResponseCodec, - RequestResponseConfig, RequestResponseEvent, RequestResponseMessage, ResponseChannel, - }, + core::{Endpoint, Multiaddr}, + request_response::{self, Behaviour, Codec, Message, ProtocolSupport, ResponseChannel}, swarm::{ - behaviour::{ConnectionClosed, DialFailure, FromSwarm, ListenFailure}, + behaviour::{ConnectionClosed, FromSwarm}, handler::multi::MultiHandler, - ConnectionHandler, IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, - PollParameters, - }, -}; -use sc_network_common::{ - protocol::ProtocolName, - request_responses::{ - IfDisconnected, IncomingRequest, OutgoingResponse, ProtocolConfig, RequestFailure, + ConnectionDenied, ConnectionId, NetworkBehaviour, PollParameters, THandler, + THandlerInEvent, THandlerOutEvent, ToSwarm, }, + PeerId, }; + use std::{ collections::{hash_map::Entry, HashMap}, io, iter, @@ -66,8 +59,136 @@ use std::{ time::{Duration, Instant}, }; -pub use libp2p::request_response::{InboundFailure, OutboundFailure, RequestId}; -use sc_peerset::{PeersetHandle, BANNED_THRESHOLD}; +pub use libp2p::request_response::{Config, InboundFailure, OutboundFailure, RequestId}; + +/// Error in a request. +#[derive(Debug, thiserror::Error)] +#[allow(missing_docs)] +pub enum RequestFailure { + #[error("We are not currently connected to the requested peer.")] + NotConnected, + #[error("Given protocol hasn't been registered.")] + UnknownProtocol, + #[error("Remote has closed the substream before answering, thereby signaling that it considers the request as valid, but refused to answer it.")] + Refused, + #[error("The remote replied, but the local node is no longer interested in the response.")] + Obsolete, + #[error("Problem on the network: {0}")] + Network(OutboundFailure), +} + +/// Configuration for a single request-response protocol. +#[derive(Debug, Clone)] +pub struct ProtocolConfig { + /// Name of the protocol on the wire. Should be something like `/foo/bar`. + pub name: ProtocolName, + + /// Fallback on the wire protocol names to support. + pub fallback_names: Vec, + + /// Maximum allowed size, in bytes, of a request. + /// + /// Any request larger than this value will be declined as a way to avoid allocating too + /// much memory for it. + pub max_request_size: u64, + + /// Maximum allowed size, in bytes, of a response. + /// + /// Any response larger than this value will be declined as a way to avoid allocating too + /// much memory for it. + pub max_response_size: u64, + + /// Duration after which emitted requests are considered timed out. + /// + /// If you expect the response to come back quickly, you should set this to a smaller duration. + pub request_timeout: Duration, + + /// Channel on which the networking service will send incoming requests. + /// + /// Every time a peer sends a request to the local node using this protocol, the networking + /// service will push an element on this channel. The receiving side of this channel then has + /// to pull this element, process the request, and send back the response to send back to the + /// peer. + /// + /// The size of the channel has to be carefully chosen. If the channel is full, the networking + /// service will discard the incoming request send back an error to the peer. Consequently, + /// the channel being full is an indicator that the node is overloaded. + /// + /// You can typically set the size of the channel to `T / d`, where `T` is the + /// `request_timeout` and `d` is the expected average duration of CPU and I/O it takes to + /// build a response. + /// + /// Can be `None` if the local node does not support answering incoming requests. + /// If this is `None`, then the local node will not advertise support for this protocol towards + /// other peers. If this is `Some` but the channel is closed, then the local node will + /// advertise support for this protocol, but any incoming request will lead to an error being + /// sent back. + pub inbound_queue: Option>, +} + +/// A single request received by a peer on a request-response protocol. +#[derive(Debug)] +pub struct IncomingRequest { + /// Who sent the request. + pub peer: PeerId, + + /// Request sent by the remote. Will always be smaller than + /// [`ProtocolConfig::max_request_size`]. + pub payload: Vec, + + /// Channel to send back the response. + /// + /// There are two ways to indicate that handling the request failed: + /// + /// 1. Drop `pending_response` and thus not changing the reputation of the peer. + /// + /// 2. Sending an `Err(())` via `pending_response`, optionally including reputation changes for + /// the given peer. + pub pending_response: oneshot::Sender, +} + +/// Response for an incoming request to be send by a request protocol handler. +#[derive(Debug)] +pub struct OutgoingResponse { + /// The payload of the response. + /// + /// `Err(())` if none is available e.g. due an error while handling the request. + pub result: Result, ()>, + + /// Reputation changes accrued while handling the request. To be applied to the reputation of + /// the peer sending the request. + pub reputation_changes: Vec, + + /// If provided, the `oneshot::Sender` will be notified when the request has been sent to the + /// peer. + /// + /// > **Note**: Operating systems typically maintain a buffer of a few dozen kilobytes of + /// > outgoing data for each TCP socket, and it is not possible for a user + /// > application to inspect this buffer. This channel here is not actually notified + /// > when the response has been fully sent out, but rather when it has fully been + /// > written to the buffer managed by the operating system. + pub sent_feedback: Option>, +} + +/// When sending a request, what to do on a disconnected recipient. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum IfDisconnected { + /// Try to connect to the peer. + TryConnect, + /// Just fail if the destination is not yet connected. + ImmediateError, +} + +/// Convenience functions for `IfDisconnected`. +impl IfDisconnected { + /// Shall we connect to a disconnected peer? + pub fn should_connect(self) -> bool { + match self { + Self::TryConnect => true, + Self::ImmediateError => false, + } + } +} /// Event generated by the [`RequestResponsesBehaviour`]. #[derive(Debug)] @@ -103,7 +224,12 @@ pub enum Event { }, /// A request protocol handler issued reputation changes for the given peer. - ReputationChanges { peer: PeerId, changes: Vec }, + ReputationChanges { + /// Peer whose reputation needs to be adjust. + peer: PeerId, + /// Reputation changes. + changes: Vec, + }, } /// Combination of a protocol name and a request id. @@ -127,14 +253,15 @@ impl From<(ProtocolName, RequestId)> for ProtocolRequestId { /// Implementation of `NetworkBehaviour` that provides support for request-response protocols. pub struct RequestResponsesBehaviour { /// The multiple sub-protocols, by name. - /// Contains the underlying libp2p `RequestResponse` behaviour, plus an optional + /// + /// Contains the underlying libp2p request-response [`Behaviour`], plus an optional /// "response builder" used to build responses for incoming requests. protocols: HashMap< ProtocolName, - (RequestResponse, Option>), + (Behaviour, Option>), >, - /// Pending requests, passed down to a [`RequestResponse`] behaviour, awaiting a reply. + /// Pending requests, passed down to a request-response [`Behaviour`], awaiting a reply. pending_requests: HashMap, RequestFailure>>)>, @@ -167,7 +294,10 @@ struct MessageRequest { request: Vec, channel: ResponseChannel, ()>>, protocol: ProtocolName, - resp_builder: Option>, + // A builder used for building responses for incoming requests. Note that we use + // `async_channel` and not `mpsc` on purpose, because `mpsc::channel` allocates an extra + // message slot for every cloned `Sender` and this breaks a back-pressure mechanism. + resp_builder: Option>, // Once we get incoming request we save all params, create an async call to Peerset // to get the reputation of the peer. get_peer_reputation: Pin> + Send>>, @@ -191,7 +321,7 @@ impl RequestResponsesBehaviour { ) -> Result { let mut protocols = HashMap::new(); for protocol in list { - let mut cfg = RequestResponseConfig::default(); + let mut cfg = Config::default(); cfg.set_connection_keep_alive(Duration::from_secs(10)); cfg.set_request_timeout(protocol.request_timeout); @@ -201,7 +331,7 @@ impl RequestResponsesBehaviour { ProtocolSupport::Outbound }; - let rq_rp = RequestResponse::new( + let rq_rp = Behaviour::new( GenericCodec { max_request_size: protocol.max_request_size, max_response_size: protocol.max_response_size, @@ -268,50 +398,79 @@ impl RequestResponsesBehaviour { ); } } - - fn new_handler_with_replacement( - &mut self, - protocol: String, - handler: RequestResponseHandler, - ) -> ::ConnectionHandler { - let mut handlers: HashMap<_, _> = self - .protocols - .iter_mut() - .map(|(p, (r, _))| (p.to_string(), NetworkBehaviour::new_handler(r))) - .collect(); - - if let Some(h) = handlers.get_mut(&protocol) { - *h = handler - } - - MultiHandler::try_from_iter(handlers).expect( - "Protocols are in a HashMap and there can be at most one handler per protocol name, \ - which is the only possible error; qed", - ) - } } impl NetworkBehaviour for RequestResponsesBehaviour { - type ConnectionHandler = MultiHandler< - String, - as NetworkBehaviour>::ConnectionHandler, - >; + type ConnectionHandler = + MultiHandler as NetworkBehaviour>::ConnectionHandler>; type OutEvent = Event; - fn new_handler(&mut self) -> Self::ConnectionHandler { - let iter = self - .protocols - .iter_mut() - .map(|(p, (r, _))| (p.to_string(), NetworkBehaviour::new_handler(r))); + fn handle_pending_inbound_connection( + &mut self, + _connection_id: ConnectionId, + _local_addr: &Multiaddr, + _remote_addr: &Multiaddr, + ) -> Result<(), ConnectionDenied> { + Ok(()) + } + + fn handle_pending_outbound_connection( + &mut self, + _connection_id: ConnectionId, + _maybe_peer: Option, + _addresses: &[Multiaddr], + _effective_role: Endpoint, + ) -> Result, ConnectionDenied> { + Ok(Vec::new()) + } - MultiHandler::try_from_iter(iter).expect( + fn handle_established_inbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + local_addr: &Multiaddr, + remote_addr: &Multiaddr, + ) -> Result, ConnectionDenied> { + let iter = self.protocols.iter_mut().filter_map(|(p, (r, _))| { + if let Ok(handler) = r.handle_established_inbound_connection( + connection_id, + peer, + local_addr, + remote_addr, + ) { + Some((p.to_string(), handler)) + } else { + None + } + }); + + Ok(MultiHandler::try_from_iter(iter).expect( "Protocols are in a HashMap and there can be at most one handler per protocol name, \ which is the only possible error; qed", - ) + )) } - fn addresses_of_peer(&mut self, _: &PeerId) -> Vec { - Vec::new() + fn handle_established_outbound_connection( + &mut self, + connection_id: ConnectionId, + peer: PeerId, + addr: &Multiaddr, + role_override: Endpoint, + ) -> Result, ConnectionDenied> { + let iter = self.protocols.iter_mut().filter_map(|(p, (r, _))| { + if let Ok(handler) = + r.handle_established_outbound_connection(connection_id, peer, addr, role_override) + { + Some((p.to_string(), handler)) + } else { + None + } + }); + + Ok(MultiHandler::try_from_iter(iter).expect( + "Protocols are in a HashMap and there can be at most one handler per protocol name, \ + which is the only possible error; qed", + )) } fn on_swarm_event(&mut self, event: FromSwarm) { @@ -344,41 +503,17 @@ impl NetworkBehaviour for RequestResponsesBehaviour { ) } }, - FromSwarm::DialFailure(DialFailure { peer_id, error, handler }) => - for (p_name, p_handler) in handler.into_iter() { - if let Some((proto, _)) = self.protocols.get_mut(p_name.as_str()) { - proto.on_swarm_event(FromSwarm::DialFailure(DialFailure { - peer_id, - handler: p_handler, - error, - })); - } else { - log::error!( - target: "sub-libp2p", - "on_swarm_event/dial_failure: no request-response instance registered for protocol {:?}", - p_name, - ) - } + FromSwarm::DialFailure(e) => + for (p, _) in self.protocols.values_mut() { + NetworkBehaviour::on_swarm_event(p, FromSwarm::DialFailure(e)); }, FromSwarm::ListenerClosed(e) => for (p, _) in self.protocols.values_mut() { NetworkBehaviour::on_swarm_event(p, FromSwarm::ListenerClosed(e)); }, - FromSwarm::ListenFailure(ListenFailure { local_addr, send_back_addr, handler }) => - for (p_name, p_handler) in handler.into_iter() { - if let Some((proto, _)) = self.protocols.get_mut(p_name.as_str()) { - proto.on_swarm_event(FromSwarm::ListenFailure(ListenFailure { - local_addr, - send_back_addr, - handler: p_handler, - })); - } else { - log::error!( - target: "sub-libp2p", - "on_swarm_event/listen_failure: no request-response instance registered for protocol {:?}", - p_name, - ) - } + FromSwarm::ListenFailure(e) => + for (p, _) in self.protocols.values_mut() { + NetworkBehaviour::on_swarm_event(p, FromSwarm::ListenFailure(e)); }, FromSwarm::ListenerError(e) => for (p, _) in self.protocols.values_mut() { @@ -415,25 +550,25 @@ impl NetworkBehaviour for RequestResponsesBehaviour { &mut self, peer_id: PeerId, connection_id: ConnectionId, - (p_name, event): <::Handler as - ConnectionHandler>::OutEvent, + event: THandlerOutEvent, ) { - if let Some((proto, _)) = self.protocols.get_mut(&*p_name) { - return proto.on_connection_handler_event(peer_id, connection_id, event) + let p_name = event.0; + if let Some((proto, _)) = self.protocols.get_mut(p_name.as_str()) { + return proto.on_connection_handler_event(peer_id, connection_id, event.1) + } else { + log::warn!( + target: "sub-libp2p", + "on_connection_handler_event: no request-response instance registered for protocol {:?}", + p_name + ); } - - log::warn!( - target: "sub-libp2p", - "on_connection_handler_event: no request-response instance registered for protocol {:?}", - p_name - ); } fn poll( &mut self, cx: &mut Context, params: &mut impl PollParameters, - ) -> Poll> { + ) -> Poll>> { 'poll_all: loop { if let Some(message_request) = self.message_request.take() { // Now we can can poll `MessageRequest` until we get the reputation @@ -485,10 +620,12 @@ impl NetworkBehaviour for RequestResponsesBehaviour { // Submit the request to the "response builder" passed by the user at // initialization. - if let Some(mut resp_builder) = resp_builder { + if let Some(resp_builder) = resp_builder { // If the response builder is too busy, silently drop `tx`. This - // will be reported by the corresponding `RequestResponse` through - // an `InboundFailure::Omission` event. + // will be reported by the corresponding request-response [`Behaviour`] + // through an `InboundFailure::Omission` event. + // Note that we use `async_channel::bounded` and not `mpsc::channel` + // because the latter allocates an extra slot for every cloned sender. let _ = resp_builder.try_send(IncomingRequest { peer, payload: request, @@ -540,7 +677,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { if let Some((protocol, _)) = self.protocols.get_mut(&*protocol_name) { if protocol.send_response(inner_channel, Ok(payload)).is_err() { // Note: Failure is handled further below when receiving - // `InboundFailure` event from `RequestResponse` behaviour. + // `InboundFailure` event from request-response [`Behaviour`]. log::debug!( target: "sub-libp2p", "Failed to send response for {:?} on protocol {:?} due to a \ @@ -556,9 +693,10 @@ impl NetworkBehaviour for RequestResponsesBehaviour { } if !reputation_changes.is_empty() { - return Poll::Ready(NetworkBehaviourAction::GenerateEvent( - Event::ReputationChanges { peer, changes: reputation_changes }, - )) + return Poll::Ready(ToSwarm::GenerateEvent(Event::ReputationChanges { + peer, + changes: reputation_changes, + })) } } @@ -567,44 +705,35 @@ impl NetworkBehaviour for RequestResponsesBehaviour { while let Poll::Ready(ev) = behaviour.poll(cx, params) { let ev = match ev { // Main events we are interested in. - NetworkBehaviourAction::GenerateEvent(ev) => ev, + ToSwarm::GenerateEvent(ev) => ev, // Other events generated by the underlying behaviour are transparently // passed through. - NetworkBehaviourAction::Dial { opts, handler } => { + ToSwarm::Dial { opts } => { if opts.get_peer_id().is_none() { log::error!( "The request-response isn't supposed to start dialing addresses" ); } - let protocol = protocol.to_string(); - let handler = self.new_handler_with_replacement(protocol, handler); - return Poll::Ready(NetworkBehaviourAction::Dial { opts, handler }) + return Poll::Ready(ToSwarm::Dial { opts }) }, - NetworkBehaviourAction::NotifyHandler { peer_id, handler, event } => - return Poll::Ready(NetworkBehaviourAction::NotifyHandler { + ToSwarm::NotifyHandler { peer_id, handler, event } => + return Poll::Ready(ToSwarm::NotifyHandler { peer_id, handler, event: ((*protocol).to_string(), event), }), - NetworkBehaviourAction::ReportObservedAddr { address, score } => - return Poll::Ready(NetworkBehaviourAction::ReportObservedAddr { - address, - score, - }), - NetworkBehaviourAction::CloseConnection { peer_id, connection } => - return Poll::Ready(NetworkBehaviourAction::CloseConnection { - peer_id, - connection, - }), + ToSwarm::ReportObservedAddr { address, score } => + return Poll::Ready(ToSwarm::ReportObservedAddr { address, score }), + ToSwarm::CloseConnection { peer_id, connection } => + return Poll::Ready(ToSwarm::CloseConnection { peer_id, connection }), }; match ev { // Received a request from a remote. - RequestResponseEvent::Message { + request_response::Event::Message { peer, - message: - RequestResponseMessage::Request { request_id, request, channel, .. }, + message: Message::Request { request_id, request, channel, .. }, } => { self.pending_responses_arrival_time .insert((protocol.clone(), request_id).into(), Instant::now()); @@ -631,9 +760,9 @@ impl NetworkBehaviour for RequestResponsesBehaviour { }, // Received a response from a remote to one of our requests. - RequestResponseEvent::Message { + request_response::Event::Message { peer, - message: RequestResponseMessage::Response { request_id, response }, + message: Message::Response { request_id, response }, .. } => { let (started, delivered) = match self @@ -664,12 +793,15 @@ impl NetworkBehaviour for RequestResponsesBehaviour { result: delivered, }; - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(out)) + return Poll::Ready(ToSwarm::GenerateEvent(out)) }, // One of our requests has failed. - RequestResponseEvent::OutboundFailure { - peer, request_id, error, .. + request_response::Event::OutboundFailure { + peer, + request_id, + error, + .. } => { let started = match self .pending_requests @@ -707,12 +839,12 @@ impl NetworkBehaviour for RequestResponsesBehaviour { result: Err(RequestFailure::Network(error)), }; - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(out)) + return Poll::Ready(ToSwarm::GenerateEvent(out)) }, // An inbound request failed, either while reading the request or due to // failing to send a response. - RequestResponseEvent::InboundFailure { + request_response::Event::InboundFailure { request_id, peer, error, .. } => { self.pending_responses_arrival_time @@ -723,11 +855,11 @@ impl NetworkBehaviour for RequestResponsesBehaviour { protocol: protocol.clone(), result: Err(ResponseFailure::Network(error)), }; - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(out)) + return Poll::Ready(ToSwarm::GenerateEvent(out)) }, // A response to an inbound request has been sent. - RequestResponseEvent::ResponseSent { request_id, peer } => { + request_response::Event::ResponseSent { request_id, peer } => { let arrival_time = self .pending_responses_arrival_time .remove(&(protocol.clone(), request_id).into()) @@ -752,7 +884,7 @@ impl NetworkBehaviour for RequestResponsesBehaviour { result: Ok(arrival_time), }; - return Poll::Ready(NetworkBehaviourAction::GenerateEvent(out)) + return Poll::Ready(ToSwarm::GenerateEvent(out)) }, }; } @@ -779,7 +911,7 @@ pub enum ResponseFailure { Network(InboundFailure), } -/// Implements the libp2p [`RequestResponseCodec`] trait. Defines how streams of bytes are turned +/// Implements the libp2p [`Codec`] trait. Defines how streams of bytes are turned /// into requests and responses and vice-versa. #[derive(Debug, Clone)] #[doc(hidden)] // Needs to be public in order to satisfy the Rust compiler. @@ -789,7 +921,7 @@ pub struct GenericCodec { } #[async_trait::async_trait] -impl RequestResponseCodec for GenericCodec { +impl Codec for GenericCodec { type Protocol = Vec; type Request = Vec; type Response = Result, ()>; @@ -908,11 +1040,8 @@ impl RequestResponseCodec for GenericCodec { mod tests { use super::*; - use futures::{ - channel::{mpsc, oneshot}, - executor::LocalPool, - task::Spawn, - }; + use crate::peerset::{Peerset, PeersetConfig, SetConfig}; + use futures::{channel::oneshot, executor::LocalPool, task::Spawn}; use libp2p::{ core::{ transport::{MemoryTransport, Transport}, @@ -920,10 +1049,9 @@ mod tests { }, identity::Keypair, noise, - swarm::{Executor, Swarm, SwarmEvent}, + swarm::{Executor, Swarm, SwarmBuilder, SwarmEvent}, Multiaddr, }; - use sc_peerset::{Peerset, PeersetConfig, SetConfig}; use std::{iter, time::Duration}; struct TokioExecutor(tokio::runtime::Runtime); @@ -938,13 +1066,10 @@ mod tests { ) -> (Swarm, Multiaddr, Peerset) { let keypair = Keypair::generate_ed25519(); - let noise_keys = - noise::Keypair::::new().into_authentic(&keypair).unwrap(); - let transport = MemoryTransport::new() .upgrade(upgrade::Version::V1) - .authenticate(noise::NoiseConfig::xx(noise_keys).into_authenticated()) - .multiplex(libp2p::yamux::YamuxConfig::default()) + .authenticate(noise::Config::new(&keypair).unwrap()) + .multiplex(libp2p::yamux::Config::default()) .boxed(); let config = PeersetConfig { @@ -962,12 +1087,13 @@ mod tests { let behaviour = RequestResponsesBehaviour::new(list, handle).unwrap(); let runtime = tokio::runtime::Runtime::new().unwrap(); - let mut swarm = Swarm::with_executor( + let mut swarm = SwarmBuilder::with_executor( transport, behaviour, keypair.public().to_peer_id(), TokioExecutor(runtime), - ); + ) + .build(); let listen_addr: Multiaddr = format!("/memory/{}", rand::random::()).parse().unwrap(); swarm.listen_on(listen_addr.clone()).unwrap(); @@ -983,10 +1109,10 @@ mod tests { let protocol_name = "/test/req-resp/1"; let mut pool = LocalPool::new(); - // Build swarms whose behaviour is `RequestResponsesBehaviour`. + // Build swarms whose behaviour is [`RequestResponsesBehaviour`]. let mut swarms = (0..2) .map(|_| { - let (tx, mut rx) = mpsc::channel::(64); + let (tx, mut rx) = async_channel::bounded::(64); pool.spawner() .spawn_obj( @@ -1086,10 +1212,10 @@ mod tests { let protocol_name = "/test/req-resp/1"; let mut pool = LocalPool::new(); - // Build swarms whose behaviour is `RequestResponsesBehaviour`. + // Build swarms whose behaviour is [`RequestResponsesBehaviour`]. let mut swarms = (0..2) .map(|_| { - let (tx, mut rx) = mpsc::channel::(64); + let (tx, mut rx) = async_channel::bounded::(64); pool.spawner() .spawn_obj( @@ -1188,10 +1314,10 @@ mod tests { } /// A [`RequestId`] is a unique identifier among either all inbound or all outbound requests for - /// a single [`RequestResponse`] behaviour. It is not guaranteed to be unique across multiple - /// [`RequestResponse`] behaviours. Thus when handling [`RequestId`] in the context of multiple - /// [`RequestResponse`] behaviours, one needs to couple the protocol name with the [`RequestId`] - /// to get a unique request identifier. + /// a single [`RequestResponsesBehaviour`] behaviour. It is not guaranteed to be unique across + /// multiple [`RequestResponsesBehaviour`] behaviours. Thus when handling [`RequestId`] in the + /// context of multiple [`RequestResponsesBehaviour`] behaviours, one needs to couple the + /// protocol name with the [`RequestId`] to get a unique request identifier. /// /// This test ensures that two requests on different protocols can be handled concurrently /// without a [`RequestId`] collision. @@ -1227,8 +1353,8 @@ mod tests { }; let (mut swarm_2, mut swarm_2_handler_1, mut swarm_2_handler_2, listen_add_2, peerset) = { - let (tx_1, rx_1) = mpsc::channel(64); - let (tx_2, rx_2) = mpsc::channel(64); + let (tx_1, rx_1) = async_channel::bounded(64); + let (tx_2, rx_2) = async_channel::bounded(64); let protocol_configs = vec![ ProtocolConfig { diff --git a/client/network/src/service.rs b/client/network/src/service.rs index f3afb58e8e0e..b6fda5ab079c 100644 --- a/client/network/src/service.rs +++ b/client/network/src/service.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -19,62 +19,64 @@ //! Main entry point of the sc-network crate. //! //! There are two main structs in this module: [`NetworkWorker`] and [`NetworkService`]. -//! The [`NetworkWorker`] *is* the network and implements the `Future` trait. It must be polled in -//! order for the network to advance. +//! The [`NetworkWorker`] *is* the network. Network is driven by [`NetworkWorker::run`] future that +//! terminates only when all instances of the control handles [`NetworkService`] were dropped. //! The [`NetworkService`] is merely a shared version of the [`NetworkWorker`]. You can obtain an //! `Arc` by calling [`NetworkWorker::service`]. //! //! The methods of the [`NetworkService`] are implemented by sending a message over a channel, -//! which is then processed by [`NetworkWorker::poll`]. +//! which is then processed by [`NetworkWorker::next_action`]. use crate::{ behaviour::{self, Behaviour, BehaviourOut}, - config::Params, + config::{parse_addr, FullNetworkConfiguration, MultiaddrWithPeerId, Params, TransportConfig}, discovery::DiscoveryConfig, + error::Error, + event::{DhtEvent, Event}, network_state::{ NetworkState, NotConnectedPeer as NetworkStateNotConnectedPeer, Peer as NetworkStatePeer, }, - protocol::{self, NotificationsSink, NotifsHandlerError, PeerInfo, Protocol, Ready}, - transport, ChainSyncInterface, ReputationChange, + peerset::PeersetHandle, + protocol::{self, NotifsHandlerError, Protocol, Ready}, + request_responses::{IfDisconnected, RequestFailure}, + service::{ + signature::{Signature, SigningError}, + traits::{ + NetworkDHTProvider, NetworkEventStream, NetworkNotification, NetworkPeers, + NetworkRequest, NetworkSigner, NetworkStateInfo, NetworkStatus, NetworkStatusProvider, + NotificationSender as NotificationSenderT, NotificationSenderError, + NotificationSenderReady as NotificationSenderReadyT, + }, + }, + transport, + types::ProtocolName, + ReputationChange, }; +use either::Either; use futures::{channel::oneshot, prelude::*}; +#[allow(deprecated)] use libp2p::{ - core::{either::EitherError, upgrade, ConnectedPoint}, + connection_limits::Exceeded, + core::{upgrade, ConnectedPoint, Endpoint}, identify::Info as IdentifyInfo, kad::record::Key as KademliaKey, multiaddr, ping::Failure as PingFailure, swarm::{ - AddressScore, ConnectionError, ConnectionLimits, DialError, Executor, NetworkBehaviour, - PendingConnectionError, Swarm, SwarmBuilder, SwarmEvent, + AddressScore, ConnectionError, ConnectionId, ConnectionLimits, DialError, Executor, + ListenError, NetworkBehaviour, Swarm, SwarmBuilder, SwarmEvent, THandlerErr, }, Multiaddr, PeerId, }; use log::{debug, error, info, trace, warn}; use metrics::{Histogram, HistogramVec, MetricSources, Metrics}; use parking_lot::Mutex; -use sc_network_common::{ - config::{MultiaddrWithPeerId, TransportConfig}, - error::Error, - protocol::{ - event::{DhtEvent, Event}, - ProtocolName, - }, - request_responses::{IfDisconnected, RequestFailure}, - service::{ - NetworkDHTProvider, NetworkEventStream, NetworkNotification, NetworkPeers, NetworkSigner, - NetworkStateInfo, NetworkStatus, NetworkStatusProvider, NetworkSyncForkRequest, - NotificationSender as NotificationSenderT, NotificationSenderError, - NotificationSenderReady as NotificationSenderReadyT, Signature, SigningError, - }, - sync::SyncStatus, - ExHashT, -}; -use sc_peerset::PeersetHandle; + +use sc_network_common::ExHashT; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; -use sp_blockchain::HeaderBackend; -use sp_runtime::traits::{Block as BlockT, NumberFor, Zero}; +use sp_runtime::traits::Block as BlockT; + use std::{ cmp, collections::{HashMap, HashSet}, @@ -84,21 +86,20 @@ use std::{ pin::Pin, str, sync::{ - atomic::{AtomicBool, AtomicUsize, Ordering}, + atomic::{AtomicUsize, Ordering}, Arc, }, - task::Poll, }; pub use behaviour::{InboundFailure, OutboundFailure, ResponseFailure}; +pub use libp2p::identity::{DecodingError, Keypair, PublicKey}; +pub use protocol::NotificationsSink; mod metrics; mod out_events; -#[cfg(test)] -mod tests; -pub use libp2p::identity::{error::DecodingError, Keypair, PublicKey}; -use sc_network_common::service::{NetworkBlock, NetworkRequest}; +pub mod signature; +pub mod traits; /// Substrate network service. Handles network IO and manages connectivity. pub struct NetworkService { @@ -106,8 +107,8 @@ pub struct NetworkService { num_connected: Arc, /// The local external addresses. external_addresses: Arc>>, - /// Are we actively catching up with the chain? - is_major_syncing: Arc, + /// Listen addresses. Do **NOT** include a trailing `/p2p/` with our `PeerId`. + listen_addresses: Arc>>, /// Local copy of the `PeerId` of the local node. local_peer_id: PeerId, /// The `KeyPair` that defines the `PeerId` of the local node. @@ -118,9 +119,7 @@ pub struct NetworkService { /// nodes it should be connected to or not. peerset: PeersetHandle, /// Channel that sends messages to the actual worker. - to_worker: TracingUnboundedSender>, - /// Interface that can be used to delegate calls to `ChainSync` - chain_sync_service: Box>, + to_worker: TracingUnboundedSender, /// For each peer and protocol combination, an object that allows sending notifications to /// that peer. Updated by the [`NetworkWorker`]. peers_notifications_sinks: Arc>>, @@ -130,38 +129,38 @@ pub struct NetworkService { /// Marker to pin the `H` generic. Serves no purpose except to not break backwards /// compatibility. _marker: PhantomData, + /// Marker for block type + _block: PhantomData, } -impl NetworkWorker +impl NetworkWorker where B: BlockT + 'static, H: ExHashT, - Client: HeaderBackend + 'static, { /// Creates the network service. /// /// Returns a `NetworkWorker` that implements `Future` and must be regularly polled in order /// for the network processing to advance. From it, you can extract a `NetworkService` using /// `worker.service()`. The `NetworkService` can be shared through the codebase. - pub fn new(mut params: Params) -> Result { + pub fn new(params: Params) -> Result { + let FullNetworkConfiguration { + notification_protocols, + request_response_protocols, + mut network_config, + } = params.network_config; + // Private and public keys configuration. - let local_identity = params.network_config.node_key.clone().into_keypair()?; + let local_identity = network_config.node_key.clone().into_keypair()?; let local_public = local_identity.public(); let local_peer_id = local_public.to_peer_id(); - params - .network_config - .request_response_protocols - .extend(params.request_response_protocol_configs); - - params.network_config.boot_nodes = params - .network_config + network_config.boot_nodes = network_config .boot_nodes .into_iter() .filter(|boot_node| boot_node.peer_id != local_peer_id) .collect(); - params.network_config.default_peers_set.reserved_nodes = params - .network_config + network_config.default_peers_set.reserved_nodes = network_config .default_peers_set .reserved_nodes .into_iter() @@ -181,36 +180,31 @@ where // Ensure the listen addresses are consistent with the transport. ensure_addresses_consistent_with_transport( - params.network_config.listen_addresses.iter(), - ¶ms.network_config.transport, + network_config.listen_addresses.iter(), + &network_config.transport, )?; ensure_addresses_consistent_with_transport( - params.network_config.boot_nodes.iter().map(|x| &x.multiaddr), - ¶ms.network_config.transport, + network_config.boot_nodes.iter().map(|x| &x.multiaddr), + &network_config.transport, )?; ensure_addresses_consistent_with_transport( - params - .network_config - .default_peers_set - .reserved_nodes - .iter() - .map(|x| &x.multiaddr), - ¶ms.network_config.transport, + network_config.default_peers_set.reserved_nodes.iter().map(|x| &x.multiaddr), + &network_config.transport, )?; - for extra_set in ¶ms.network_config.extra_sets { + for notification_protocol in ¬ification_protocols { ensure_addresses_consistent_with_transport( - extra_set.set_config.reserved_nodes.iter().map(|x| &x.multiaddr), - ¶ms.network_config.transport, + notification_protocol.set_config.reserved_nodes.iter().map(|x| &x.multiaddr), + &network_config.transport, )?; } ensure_addresses_consistent_with_transport( - params.network_config.public_addresses.iter(), - ¶ms.network_config.transport, + network_config.public_addresses.iter(), + &network_config.transport, )?; let (to_worker, from_service) = tracing_unbounded("mpsc_network_worker", 100_000); - if let Some(path) = ¶ms.network_config.net_config_path { + if let Some(path) = &network_config.net_config_path { fs::create_dir_all(path)?; } @@ -220,30 +214,79 @@ where local_peer_id.to_base58(), ); + let (transport, bandwidth) = { + let config_mem = match network_config.transport { + TransportConfig::MemoryOnly => true, + TransportConfig::Normal { .. } => false, + }; + + // The yamux buffer size limit is configured to be equal to the maximum frame size + // of all protocols. 10 bytes are added to each limit for the length prefix that + // is not included in the upper layer protocols limit but is still present in the + // yamux buffer. These 10 bytes correspond to the maximum size required to encode + // a variable-length-encoding 64bits number. In other words, we make the + // assumption that no notification larger than 2^64 will ever be sent. + let yamux_maximum_buffer_size = { + let requests_max = request_response_protocols + .iter() + .map(|cfg| usize::try_from(cfg.max_request_size).unwrap_or(usize::MAX)); + let responses_max = request_response_protocols + .iter() + .map(|cfg| usize::try_from(cfg.max_response_size).unwrap_or(usize::MAX)); + let notifs_max = notification_protocols + .iter() + .map(|cfg| usize::try_from(cfg.max_notification_size).unwrap_or(usize::MAX)); + + // A "default" max is added to cover all the other protocols: ping, identify, + // kademlia, block announces, and transactions. + let default_max = cmp::max( + 1024 * 1024, + usize::try_from(protocol::BLOCK_ANNOUNCES_TRANSACTIONS_SUBSTREAM_SIZE) + .unwrap_or(usize::MAX), + ); + + iter::once(default_max) + .chain(requests_max) + .chain(responses_max) + .chain(notifs_max) + .max() + .expect("iterator known to always yield at least one element; qed") + .saturating_add(10) + }; + + transport::build_transport( + local_identity.clone(), + config_mem, + network_config.yamux_window_size, + yamux_maximum_buffer_size, + ) + }; + let (protocol, peerset_handle, mut known_addresses) = Protocol::new( From::from(¶ms.role), - params.chain.clone(), - ¶ms.network_config, - params.metrics_registry.as_ref(), - params.chain_sync, + &network_config, + notification_protocols, params.block_announce_config, + params.tx, )?; // List of multiaddresses that we know in the network. - let mut boot_node_ids = HashSet::new(); + let mut boot_node_ids = HashMap::>::new(); // Process the bootnodes. - for bootnode in params.network_config.boot_nodes.iter() { - boot_node_ids.insert(bootnode.peer_id); + for bootnode in network_config.boot_nodes.iter() { + boot_node_ids + .entry(bootnode.peer_id) + .or_default() + .push(bootnode.multiaddr.clone()); known_addresses.push((bootnode.peer_id, bootnode.multiaddr.clone())); } let boot_node_ids = Arc::new(boot_node_ids); // Check for duplicate bootnodes. - params.network_config.boot_nodes.iter().try_for_each(|bootnode| { - if let Some(other) = params - .network_config + network_config.boot_nodes.iter().try_for_each(|bootnode| { + if let Some(other) = network_config .boot_nodes .iter() .filter(|o| o.multiaddr == bootnode.multiaddr) @@ -260,35 +303,29 @@ where })?; let num_connected = Arc::new(AtomicUsize::new(0)); - let is_major_syncing = Arc::new(AtomicBool::new(false)); // Build the swarm. - let (mut swarm, bandwidth): (Swarm>, _) = { - let user_agent = format!( - "{} ({})", - params.network_config.client_version, params.network_config.node_name - ); + let (mut swarm, bandwidth): (Swarm>, _) = { + let user_agent = + format!("{} ({})", network_config.client_version, network_config.node_name); let discovery_config = { - let mut config = DiscoveryConfig::new(local_public.clone()); + let mut config = DiscoveryConfig::new(local_public.to_peer_id()); config.with_permanent_addresses(known_addresses); - config.discovery_limit( - u64::from(params.network_config.default_peers_set.out_peers) + 15, + config.discovery_limit(u64::from(network_config.default_peers_set.out_peers) + 15); + config.with_kademlia( + params.genesis_hash, + params.fork_id.as_deref(), + ¶ms.protocol_id, ); - let genesis_hash = params - .chain - .hash(Zero::zero()) - .ok() - .flatten() - .expect("Genesis block exists; qed"); - config.with_kademlia(genesis_hash, params.fork_id.as_deref(), ¶ms.protocol_id); - config.with_dht_random_walk(params.network_config.enable_dht_random_walk); - config.allow_non_globals_in_dht(params.network_config.allow_non_globals_in_dht); + config.with_dht_random_walk(network_config.enable_dht_random_walk); + config.allow_non_globals_in_dht(network_config.allow_non_globals_in_dht); config.use_kademlia_disjoint_query_paths( - params.network_config.kademlia_disjoint_query_paths, + network_config.kademlia_disjoint_query_paths, ); + config.with_kademlia_replication_factor(network_config.kademlia_replication_factor); - match params.network_config.transport { + match network_config.transport { TransportConfig::MemoryOnly => { config.with_mdns(false); config.allow_private_ip(false); @@ -306,64 +343,13 @@ where config }; - let (transport, bandwidth) = { - let config_mem = match params.network_config.transport { - TransportConfig::MemoryOnly => true, - TransportConfig::Normal { .. } => false, - }; - - // The yamux buffer size limit is configured to be equal to the maximum frame size - // of all protocols. 10 bytes are added to each limit for the length prefix that - // is not included in the upper layer protocols limit but is still present in the - // yamux buffer. These 10 bytes correspond to the maximum size required to encode - // a variable-length-encoding 64bits number. In other words, we make the - // assumption that no notification larger than 2^64 will ever be sent. - let yamux_maximum_buffer_size = { - let requests_max = params - .network_config - .request_response_protocols - .iter() - .map(|cfg| usize::try_from(cfg.max_request_size).unwrap_or(usize::MAX)); - let responses_max = - params.network_config.request_response_protocols.iter().map(|cfg| { - usize::try_from(cfg.max_response_size).unwrap_or(usize::MAX) - }); - let notifs_max = params.network_config.extra_sets.iter().map(|cfg| { - usize::try_from(cfg.max_notification_size).unwrap_or(usize::MAX) - }); - - // A "default" max is added to cover all the other protocols: ping, identify, - // kademlia, block announces, and transactions. - let default_max = cmp::max( - 1024 * 1024, - usize::try_from(protocol::BLOCK_ANNOUNCES_TRANSACTIONS_SUBSTREAM_SIZE) - .unwrap_or(usize::MAX), - ); - - iter::once(default_max) - .chain(requests_max) - .chain(responses_max) - .chain(notifs_max) - .max() - .expect("iterator known to always yield at least one element; qed") - .saturating_add(10) - }; - - transport::build_transport( - local_identity.clone(), - config_mem, - params.network_config.yamux_window_size, - yamux_maximum_buffer_size, - ) - }; - let behaviour = { let result = Behaviour::new( protocol, user_agent, local_public, discovery_config, - params.network_config.request_response_protocols, + request_response_protocols, peerset_handle.clone(), ); @@ -388,6 +374,7 @@ where SpawnImpl(params.executor), ) }; + #[allow(deprecated)] let builder = builder .connection_limits( ConnectionLimits::default() @@ -398,7 +385,9 @@ where ) .substream_upgrade_protocol_override(upgrade::Version::V1Lazy) .notify_handler_buffer_size(NonZeroUsize::new(32).expect("32 != 0; qed")) - .connection_event_buffer_size(1024) + // NOTE: 24 is somewhat arbitrary and should be tuned in the future if necessary. + // See + .per_connection_event_buffer_size(24) .max_negotiating_inbound_streams(2048); (builder.build(), bandwidth) @@ -410,7 +399,6 @@ where registry, MetricSources { bandwidth: bandwidth.clone(), - major_syncing: is_major_syncing.clone(), connected_peers: num_connected.clone(), }, )?), @@ -418,15 +406,15 @@ where }; // Listen on multiaddresses. - for addr in ¶ms.network_config.listen_addresses { - if let Err(err) = Swarm::>::listen_on(&mut swarm, addr.clone()) { + for addr in &network_config.listen_addresses { + if let Err(err) = Swarm::>::listen_on(&mut swarm, addr.clone()) { warn!(target: "sub-libp2p", "Can't listen on {} because: {:?}", addr, err) } } // Add external addresses. - for addr in ¶ms.network_config.public_addresses { - Swarm::>::add_external_address( + for addr in &network_config.public_addresses { + Swarm::>::add_external_address( &mut swarm, addr.clone(), AddressScore::Infinite, @@ -434,29 +422,30 @@ where } let external_addresses = Arc::new(Mutex::new(Vec::new())); + let listen_addresses = Arc::new(Mutex::new(Vec::new())); let peers_notifications_sinks = Arc::new(Mutex::new(HashMap::new())); let service = Arc::new(NetworkService { bandwidth, external_addresses: external_addresses.clone(), + listen_addresses: listen_addresses.clone(), num_connected: num_connected.clone(), - is_major_syncing: is_major_syncing.clone(), peerset: peerset_handle, local_peer_id, local_identity, to_worker, - chain_sync_service: params.chain_sync_service, peers_notifications_sinks: peers_notifications_sinks.clone(), notifications_sizes_metric: metrics .as_ref() .map(|metrics| metrics.notifications_sizes.clone()), _marker: PhantomData, + _block: Default::default(), }); Ok(NetworkWorker { external_addresses, + listen_addresses, num_connected, - is_major_syncing, network_service: swarm, service, from_service, @@ -464,23 +453,18 @@ where peers_notifications_sinks, metrics, boot_node_ids, + reported_invalid_boot_nodes: Default::default(), _marker: Default::default(), + _block: Default::default(), }) } /// High-level network status information. - pub fn status(&self) -> NetworkStatus { - let status = self.sync_state(); + pub fn status(&self) -> NetworkStatus { NetworkStatus { - sync_state: status.state, - best_seen_block: self.best_seen_block(), - num_sync_peers: self.num_sync_peers(), num_connected_peers: self.num_connected_peers(), - num_active_peers: self.num_active_peers(), total_bytes_inbound: self.total_bytes_inbound(), total_bytes_outbound: self.total_bytes_outbound(), - state_sync: status.state_sync, - warp_sync: status.warp_sync, } } @@ -499,41 +483,6 @@ where self.network_service.behaviour().user_protocol().num_connected_peers() } - /// Returns the number of peers we're connected to and that are being queried. - pub fn num_active_peers(&self) -> usize { - self.network_service.behaviour().user_protocol().num_active_peers() - } - - /// Current global sync state. - pub fn sync_state(&self) -> SyncStatus { - self.network_service.behaviour().user_protocol().sync_state() - } - - /// Target sync block number. - pub fn best_seen_block(&self) -> Option> { - self.network_service.behaviour().user_protocol().best_seen_block() - } - - /// Number of peers participating in syncing. - pub fn num_sync_peers(&self) -> u32 { - self.network_service.behaviour().user_protocol().num_sync_peers() - } - - /// Number of blocks in the import queue. - pub fn num_queued_blocks(&self) -> u32 { - self.network_service.behaviour().user_protocol().num_queued_blocks() - } - - /// Returns the number of downloaded blocks. - pub fn num_downloaded_blocks(&self) -> usize { - self.network_service.behaviour().user_protocol().num_downloaded_blocks() - } - - /// Number of active sync requests. - pub fn num_sync_requests(&self) -> usize { - self.network_service.behaviour().user_protocol().num_sync_requests() - } - /// Adds an address for a node. pub fn add_known_address(&mut self, peer_id: PeerId, addr: Multiaddr) { self.network_service.behaviour_mut().add_known_address(peer_id, addr); @@ -545,32 +494,16 @@ where &self.service } - /// You must call this when a new block is finalized by the client. - pub fn on_block_finalized(&mut self, hash: B::Hash, header: B::Header) { - self.network_service - .behaviour_mut() - .user_protocol_mut() - .on_block_finalized(hash, &header); - } - - /// Inform the network service about new best imported block. - pub fn new_best_block_imported(&mut self, hash: B::Hash, number: NumberFor) { - self.network_service - .behaviour_mut() - .user_protocol_mut() - .new_best_block_imported(hash, number); - } - /// Returns the local `PeerId`. pub fn local_peer_id(&self) -> &PeerId { - Swarm::>::local_peer_id(&self.network_service) + Swarm::>::local_peer_id(&self.network_service) } /// Returns the list of addresses we are listening on. /// /// Does **NOT** include a trailing `/p2p/` with our `PeerId`. pub fn listen_addresses(&self) -> impl Iterator { - Swarm::>::listeners(&self.network_service) + Swarm::>::listeners(&self.network_service) } /// Get network state. @@ -580,15 +513,23 @@ where pub fn network_state(&mut self) -> NetworkState { let swarm = &mut self.network_service; let open = swarm.behaviour_mut().user_protocol().open_peers().cloned().collect::>(); - let connected_peers = { let swarm = &mut *swarm; open.iter() .filter_map(move |peer_id| { - let known_addresses = - NetworkBehaviour::addresses_of_peer(swarm.behaviour_mut(), peer_id) - .into_iter() - .collect(); + let known_addresses = if let Ok(addrs) = + NetworkBehaviour::handle_pending_outbound_connection( + swarm.behaviour_mut(), + ConnectionId::new_unchecked(0), // dummy value + Some(*peer_id), + &vec![], + Endpoint::Listener, + ) { + addrs.into_iter().collect() + } else { + error!(target: "sub-libp2p", "Was not able to get known addresses for {:?}", peer_id); + return None + }; let endpoint = if let Some(e) = swarm.behaviour_mut().node(peer_id).and_then(|i| i.endpoint()) @@ -627,6 +568,20 @@ where .into_iter() .filter(|p| open.iter().all(|n| n != p)) .map(move |peer_id| { + let known_addresses = if let Ok(addrs) = + NetworkBehaviour::handle_pending_outbound_connection( + swarm.behaviour_mut(), + ConnectionId::new_unchecked(0), // dummy value + Some(peer_id), + &vec![], + Endpoint::Listener, + ) { + addrs.into_iter().collect() + } else { + error!(target: "sub-libp2p", "Was not able to get known addresses for {:?}", peer_id); + Default::default() + }; + ( peer_id.to_base58(), NetworkStateNotConnectedPeer { @@ -638,19 +593,14 @@ where .behaviour_mut() .node(&peer_id) .and_then(|i| i.latest_ping()), - known_addresses: NetworkBehaviour::addresses_of_peer( - swarm.behaviour_mut(), - &peer_id, - ) - .into_iter() - .collect(), + known_addresses, }, ) }) .collect() }; - let peer_id = Swarm::>::local_peer_id(swarm).to_base58(); + let peer_id = Swarm::>::local_peer_id(swarm).to_base58(); let listened_addresses = swarm.listeners().cloned().collect(); let external_addresses = swarm.external_addresses().map(|r| &r.addr).cloned().collect(); @@ -664,16 +614,6 @@ where } } - /// Get currently connected peers. - pub fn peers_debug_info(&mut self) -> Vec<(PeerId, PeerInfo)> { - self.network_service - .behaviour_mut() - .user_protocol_mut() - .peers_info() - .map(|(id, info)| (*id, info.clone())) - .collect() - } - /// Removes a `PeerId` from the list of reserved peers. pub fn remove_reserved_peer(&self, peer: PeerId) { self.service.remove_reserved_peer(peer); @@ -685,8 +625,11 @@ where } /// Returns the list of reserved peers. - pub fn reserved_peers(&self) -> impl Iterator { - self.network_service.behaviour().user_protocol().reserved_peers() + fn reserved_peers(&self, pending_response: oneshot::Sender>) { + self.network_service + .behaviour() + .user_protocol() + .reserved_peers(pending_response); } } @@ -711,6 +654,20 @@ impl NetworkService { } } + /// Get the list of reserved peers. + /// + /// Returns an error if the `NetworkWorker` is no longer running. + pub async fn reserved_peers(&self) -> Result, ()> { + let (tx, rx) = oneshot::channel(); + + let _ = self + .to_worker + .unbounded_send(ServiceToWorkerMsg::ReservedPeers { pending_response: tx }); + + // The channel can only be closed if the network worker no longer exists. + rx.await.map_err(|_| ()) + } + /// Utility function to extract `PeerId` from each `Multiaddr` for peer set updates. /// /// Returns an `Err` if one of the given addresses is invalid or contains an @@ -740,30 +697,6 @@ impl NetworkService { } } -impl sp_consensus::SyncOracle for NetworkService { - fn is_major_syncing(&self) -> bool { - self.is_major_syncing.load(Ordering::Relaxed) - } - - fn is_offline(&self) -> bool { - self.num_connected.load(Ordering::Relaxed) == 0 - } -} - -impl sc_consensus::JustificationSyncLink for NetworkService { - /// Request a justification for the given block from the network. - /// - /// On success, the justification will be passed to the import queue that was part at - /// initialization as part of the configuration. - fn request_justification(&self, hash: &B::Hash, number: NumberFor) { - let _ = self.chain_sync_service.request_justification(hash, number); - } - - fn clear_justification_requests(&self) { - let _ = self.chain_sync_service.clear_justification_requests(); - } -} - impl NetworkStateInfo for NetworkService where B: sp_runtime::traits::Block, @@ -774,6 +707,11 @@ where self.external_addresses.lock().clone() } + /// Returns the listener addresses (without trailing `/p2p/` with our `PeerId`). + fn listen_addresses(&self) -> Vec { + self.listen_addresses.lock().clone() + } + /// Returns the local Peer ID. fn local_peer_id(&self) -> PeerId { self.local_peer_id @@ -812,29 +750,13 @@ where } } -impl NetworkSyncForkRequest> for NetworkService -where - B: BlockT + 'static, - H: ExHashT, -{ - /// Configure an explicit fork sync request. - /// Note that this function should not be used for recent blocks. - /// Sync should be able to download all the recent forks normally. - /// `set_sync_fork_request` should only be used if external code detects that there's - /// a stale fork missing. - /// Passing empty `peers` set effectively removes the sync request. - fn set_sync_fork_request(&self, peers: Vec, hash: B::Hash, number: NumberFor) { - self.chain_sync_service.set_sync_fork_request(peers, hash, number); - } -} - #[async_trait::async_trait] -impl NetworkStatusProvider for NetworkService +impl NetworkStatusProvider for NetworkService where B: BlockT + 'static, H: ExHashT, { - async fn status(&self) -> Result, ()> { + async fn status(&self) -> Result { let (tx, rx) = oneshot::channel(); let _ = self @@ -968,40 +890,6 @@ where } } - fn add_to_peers_set( - &self, - protocol: ProtocolName, - peers: HashSet, - ) -> Result<(), String> { - let peers = self.split_multiaddr_and_peer_id(peers)?; - - for (peer_id, addr) in peers.into_iter() { - // Make sure the local peer ID is never added to the PSM. - if peer_id == self.local_peer_id { - return Err("Local peer ID cannot be added as a reserved peer.".to_string()) - } - - if !addr.is_empty() { - let _ = self - .to_worker - .unbounded_send(ServiceToWorkerMsg::AddKnownAddress(peer_id, addr)); - } - let _ = self - .to_worker - .unbounded_send(ServiceToWorkerMsg::AddToPeersSet(protocol.clone(), peer_id)); - } - - Ok(()) - } - - fn remove_from_peers_set(&self, protocol: ProtocolName, peers: Vec) { - for peer_id in peers.into_iter() { - let _ = self - .to_worker - .unbounded_send(ServiceToWorkerMsg::RemoveFromPeersSet(protocol.clone(), peer_id)); - } - } - fn sync_num_connected(&self) -> usize { self.num_connected.load(Ordering::Relaxed) } @@ -1081,6 +969,12 @@ where Ok(Box::new(NotificationSender { sink, protocol_name: protocol, notification_size_metric })) } + + fn set_notification_handshake(&self, protocol: ProtocolName, handshake: Vec) { + let _ = self + .to_worker + .unbounded_send(ServiceToWorkerMsg::SetNotificationHandshake(protocol, handshake)); + } } #[async_trait::async_trait] @@ -1127,22 +1021,6 @@ where } } -impl NetworkBlock> for NetworkService -where - B: BlockT + 'static, - H: ExHashT, -{ - fn announce_block(&self, hash: B::Hash, data: Option>) { - let _ = self.to_worker.unbounded_send(ServiceToWorkerMsg::AnnounceBlock(hash, data)); - } - - fn new_best_block_imported(&self, hash: B::Hash, number: NumberFor) { - let _ = self - .to_worker - .unbounded_send(ServiceToWorkerMsg::NewBestBlockImported(hash, number)); - } -} - /// A `NotificationSender` allows for sending notifications to a peer with a chosen protocol. #[must_use] pub struct NotificationSender { @@ -1213,8 +1091,7 @@ impl<'a> NotificationSenderReadyT for NotificationSenderReady<'a> { /// Messages sent from the `NetworkService` to the `NetworkWorker`. /// /// Each entry corresponds to a method of `NetworkService`. -enum ServiceToWorkerMsg { - AnnounceBlock(B::Hash, Option>), +enum ServiceToWorkerMsg { GetValue(KademliaKey), PutValue(KademliaKey, Vec), AddKnownAddress(PeerId, Multiaddr), @@ -1225,8 +1102,6 @@ enum ServiceToWorkerMsg { SetPeersetReserved(ProtocolName, HashSet), AddSetReserved(ProtocolName, PeerId), RemoveSetReserved(ProtocolName, PeerId), - AddToPeersSet(ProtocolName, PeerId), - RemoveFromPeersSet(ProtocolName, PeerId), EventStream(out_events::Sender), Request { target: PeerId, @@ -1236,690 +1111,638 @@ enum ServiceToWorkerMsg { connect: IfDisconnected, }, NetworkStatus { - pending_response: oneshot::Sender, RequestFailure>>, + pending_response: oneshot::Sender>, }, NetworkState { pending_response: oneshot::Sender>, }, DisconnectPeer(PeerId, ProtocolName), - NewBestBlockImported(B::Hash, NumberFor), + SetNotificationHandshake(ProtocolName, Vec), + ReservedPeers { + pending_response: oneshot::Sender>, + }, } /// Main network worker. Must be polled in order for the network to advance. /// /// You are encouraged to poll this in a separate background thread or task. #[must_use = "The NetworkWorker must be polled in order for the network to advance"] -pub struct NetworkWorker +pub struct NetworkWorker where B: BlockT + 'static, H: ExHashT, - Client: HeaderBackend + 'static, { /// Updated by the `NetworkWorker` and loaded by the `NetworkService`. external_addresses: Arc>>, /// Updated by the `NetworkWorker` and loaded by the `NetworkService`. - num_connected: Arc, + listen_addresses: Arc>>, /// Updated by the `NetworkWorker` and loaded by the `NetworkService`. - is_major_syncing: Arc, + num_connected: Arc, /// The network service that can be extracted and shared through the codebase. service: Arc>, /// The *actual* network. - network_service: Swarm>, + network_service: Swarm>, /// Messages from the [`NetworkService`] that must be processed. - from_service: TracingUnboundedReceiver>, + from_service: TracingUnboundedReceiver, /// Senders for events that happen on the network. event_streams: out_events::OutChannels, /// Prometheus network metrics. metrics: Option, - /// The `PeerId`'s of all boot nodes. - boot_node_ids: Arc>, + /// The `PeerId`'s of all boot nodes mapped to the registered addresses. + boot_node_ids: Arc>>, + /// Boot nodes that we already have reported as invalid. + reported_invalid_boot_nodes: HashSet, /// For each peer and protocol combination, an object that allows sending notifications to /// that peer. Shared with the [`NetworkService`]. peers_notifications_sinks: Arc>>, /// Marker to pin the `H` generic. Serves no purpose except to not break backwards /// compatibility. _marker: PhantomData, + /// Marker for block type + _block: PhantomData, } -impl Future for NetworkWorker +impl NetworkWorker where B: BlockT + 'static, H: ExHashT, - Client: HeaderBackend + 'static, { - type Output = (); - - fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context) -> Poll { - let this = &mut *self; - - // At the time of writing of this comment, due to a high volume of messages, the network - // worker sometimes takes a long time to process the loop below. When that happens, the - // rest of the polling is frozen. In order to avoid negative side-effects caused by this - // freeze, a limit to the number of iterations is enforced below. If the limit is reached, - // the task is interrupted then scheduled again. - // - // This allows for a more even distribution in the time taken by each sub-part of the - // polling. - let mut num_iterations = 0; - loop { - num_iterations += 1; - if num_iterations >= 100 { - cx.waker().wake_by_ref(); - break - } + /// Run the network. + pub async fn run(mut self) { + while self.next_action().await {} + } - // Process the next message coming from the `NetworkService`. - let msg = match this.from_service.poll_next_unpin(cx) { - Poll::Ready(Some(msg)) => msg, - Poll::Ready(None) => return Poll::Ready(()), - Poll::Pending => break, - }; - match msg { - ServiceToWorkerMsg::AnnounceBlock(hash, data) => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .announce_block(hash, data), - ServiceToWorkerMsg::GetValue(key) => - this.network_service.behaviour_mut().get_value(key), - ServiceToWorkerMsg::PutValue(key, value) => - this.network_service.behaviour_mut().put_value(key, value), - ServiceToWorkerMsg::SetReservedOnly(reserved_only) => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .set_reserved_only(reserved_only), - ServiceToWorkerMsg::SetReserved(peers) => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .set_reserved_peers(peers), - ServiceToWorkerMsg::SetPeersetReserved(protocol, peers) => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .set_reserved_peerset_peers(protocol, peers), - ServiceToWorkerMsg::AddReserved(peer_id) => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .add_reserved_peer(peer_id), - ServiceToWorkerMsg::RemoveReserved(peer_id) => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .remove_reserved_peer(peer_id), - ServiceToWorkerMsg::AddSetReserved(protocol, peer_id) => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .add_set_reserved_peer(protocol, peer_id), - ServiceToWorkerMsg::RemoveSetReserved(protocol, peer_id) => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .remove_set_reserved_peer(protocol, peer_id), - ServiceToWorkerMsg::AddKnownAddress(peer_id, addr) => - this.network_service.behaviour_mut().add_known_address(peer_id, addr), - ServiceToWorkerMsg::AddToPeersSet(protocol, peer_id) => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .add_to_peers_set(protocol, peer_id), - ServiceToWorkerMsg::RemoveFromPeersSet(protocol, peer_id) => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .remove_from_peers_set(protocol, peer_id), - ServiceToWorkerMsg::EventStream(sender) => this.event_streams.push(sender), - ServiceToWorkerMsg::Request { - target, - protocol, - request, - pending_response, - connect, - } => { - this.network_service.behaviour_mut().send_request( - &target, - &protocol, - request, - pending_response, - connect, - ); - }, - ServiceToWorkerMsg::NetworkStatus { pending_response } => { - let _ = pending_response.send(Ok(this.status())); - }, - ServiceToWorkerMsg::NetworkState { pending_response } => { - let _ = pending_response.send(Ok(this.network_state())); - }, - ServiceToWorkerMsg::DisconnectPeer(who, protocol_name) => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .disconnect_peer(&who, protocol_name), - ServiceToWorkerMsg::NewBestBlockImported(hash, number) => this - .network_service - .behaviour_mut() - .user_protocol_mut() - .new_best_block_imported(hash, number), - } + /// Perform one action on the network. + /// + /// Returns `false` when the worker should be shutdown. + /// Use in tests only. + pub async fn next_action(&mut self) -> bool { + futures::select! { + // Next message from the service. + msg = self.from_service.next() => { + if let Some(msg) = msg { + self.handle_worker_message(msg); + } else { + return false + } + }, + // Next event from `Swarm` (the stream guaranteed to never terminate). + event = self.network_service.select_next_some() => { + self.handle_swarm_event(event); + }, + }; + + // Update the variables shared with the `NetworkService`. + let num_connected_peers = + self.network_service.behaviour_mut().user_protocol_mut().num_connected_peers(); + self.num_connected.store(num_connected_peers, Ordering::Relaxed); + { + let external_addresses = + self.network_service.external_addresses().map(|r| &r.addr).cloned().collect(); + *self.external_addresses.lock() = external_addresses; + + let listen_addresses = + self.network_service.listeners().map(ToOwned::to_owned).collect(); + *self.listen_addresses.lock() = listen_addresses; } - // `num_iterations` serves the same purpose as in the previous loop. - // See the previous loop for explanations. - let mut num_iterations = 0; - loop { - num_iterations += 1; - if num_iterations >= 1000 { - cx.waker().wake_by_ref(); - break + if let Some(metrics) = self.metrics.as_ref() { + if let Some(buckets) = self.network_service.behaviour_mut().num_entries_per_kbucket() { + for (lower_ilog2_bucket_bound, num_entries) in buckets { + metrics + .kbuckets_num_nodes + .with_label_values(&[&lower_ilog2_bucket_bound.to_string()]) + .set(num_entries as u64); + } } + if let Some(num_entries) = self.network_service.behaviour_mut().num_kademlia_records() { + metrics.kademlia_records_count.set(num_entries as u64); + } + if let Some(num_entries) = + self.network_service.behaviour_mut().kademlia_records_total_size() + { + metrics.kademlia_records_sizes_total.set(num_entries as u64); + } + metrics + .peerset_num_discovered + .set(self.network_service.behaviour_mut().user_protocol().num_discovered_peers() + as u64); + metrics.pending_connections.set( + Swarm::network_info(&self.network_service).connection_counters().num_pending() + as u64, + ); + } - // Process the next action coming from the network. - let next_event = this.network_service.select_next_some(); - futures::pin_mut!(next_event); - let poll_value = next_event.poll_unpin(cx); + true + } - match poll_value { - Poll::Pending => break, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::InboundRequest { - protocol, - result, - .. - })) => { - if let Some(metrics) = this.metrics.as_ref() { - match result { - Ok(serve_time) => { - metrics - .requests_in_success_total - .with_label_values(&[&protocol]) - .observe(serve_time.as_secs_f64()); - }, - Err(err) => { - let reason = match err { - ResponseFailure::Network(InboundFailure::Timeout) => "timeout", - ResponseFailure::Network( - InboundFailure::UnsupportedProtocols, - ) => - // `UnsupportedProtocols` is reported for every single - // inbound request whenever a request with an unsupported - // protocol is received. This is not reported in order to - // avoid confusions. - continue, - ResponseFailure::Network(InboundFailure::ResponseOmission) => - "busy-omitted", - ResponseFailure::Network(InboundFailure::ConnectionClosed) => - "connection-closed", - }; + /// Process the next message coming from the `NetworkService`. + fn handle_worker_message(&mut self, msg: ServiceToWorkerMsg) { + match msg { + ServiceToWorkerMsg::GetValue(key) => + self.network_service.behaviour_mut().get_value(key), + ServiceToWorkerMsg::PutValue(key, value) => + self.network_service.behaviour_mut().put_value(key, value), + ServiceToWorkerMsg::SetReservedOnly(reserved_only) => self + .network_service + .behaviour_mut() + .user_protocol_mut() + .set_reserved_only(reserved_only), + ServiceToWorkerMsg::SetReserved(peers) => self + .network_service + .behaviour_mut() + .user_protocol_mut() + .set_reserved_peers(peers), + ServiceToWorkerMsg::SetPeersetReserved(protocol, peers) => self + .network_service + .behaviour_mut() + .user_protocol_mut() + .set_reserved_peerset_peers(protocol, peers), + ServiceToWorkerMsg::AddReserved(peer_id) => self + .network_service + .behaviour_mut() + .user_protocol_mut() + .add_reserved_peer(peer_id), + ServiceToWorkerMsg::RemoveReserved(peer_id) => self + .network_service + .behaviour_mut() + .user_protocol_mut() + .remove_reserved_peer(peer_id), + ServiceToWorkerMsg::AddSetReserved(protocol, peer_id) => self + .network_service + .behaviour_mut() + .user_protocol_mut() + .add_set_reserved_peer(protocol, peer_id), + ServiceToWorkerMsg::RemoveSetReserved(protocol, peer_id) => self + .network_service + .behaviour_mut() + .user_protocol_mut() + .remove_set_reserved_peer(protocol, peer_id), + ServiceToWorkerMsg::AddKnownAddress(peer_id, addr) => + self.network_service.behaviour_mut().add_known_address(peer_id, addr), + ServiceToWorkerMsg::EventStream(sender) => self.event_streams.push(sender), + ServiceToWorkerMsg::Request { + target, + protocol, + request, + pending_response, + connect, + } => { + self.network_service.behaviour_mut().send_request( + &target, + &protocol, + request, + pending_response, + connect, + ); + }, + ServiceToWorkerMsg::NetworkStatus { pending_response } => { + let _ = pending_response.send(Ok(self.status())); + }, + ServiceToWorkerMsg::NetworkState { pending_response } => { + let _ = pending_response.send(Ok(self.network_state())); + }, + ServiceToWorkerMsg::DisconnectPeer(who, protocol_name) => self + .network_service + .behaviour_mut() + .user_protocol_mut() + .disconnect_peer(&who, protocol_name), + ServiceToWorkerMsg::SetNotificationHandshake(protocol, handshake) => self + .network_service + .behaviour_mut() + .user_protocol_mut() + .set_notification_handshake(protocol, handshake), + ServiceToWorkerMsg::ReservedPeers { pending_response } => { + self.reserved_peers(pending_response); + }, + } + } + /// Process the next event coming from `Swarm`. + fn handle_swarm_event(&mut self, event: SwarmEvent>>) { + match event { + SwarmEvent::Behaviour(BehaviourOut::InboundRequest { protocol, result, .. }) => { + if let Some(metrics) = self.metrics.as_ref() { + match result { + Ok(serve_time) => { + metrics + .requests_in_success_total + .with_label_values(&[&protocol]) + .observe(serve_time.as_secs_f64()); + }, + Err(err) => { + let reason = match err { + ResponseFailure::Network(InboundFailure::Timeout) => + Some("timeout"), + ResponseFailure::Network(InboundFailure::UnsupportedProtocols) => + // `UnsupportedProtocols` is reported for every single + // inbound request whenever a request with an unsupported + // protocol is received. This is not reported in order to + // avoid confusions. + None, + ResponseFailure::Network(InboundFailure::ResponseOmission) => + Some("busy-omitted"), + ResponseFailure::Network(InboundFailure::ConnectionClosed) => + Some("connection-closed"), + }; + + if let Some(reason) = reason { metrics .requests_in_failure_total .with_label_values(&[&protocol, reason]) .inc(); - }, - } + } + }, } - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::RequestFinished { - protocol, - duration, - result, - .. - })) => - if let Some(metrics) = this.metrics.as_ref() { - match result { - Ok(_) => { - metrics - .requests_out_success_total - .with_label_values(&[&protocol]) - .observe(duration.as_secs_f64()); - }, - Err(err) => { - let reason = match err { - RequestFailure::NotConnected => "not-connected", - RequestFailure::UnknownProtocol => "unknown-protocol", - RequestFailure::Refused => "refused", - RequestFailure::Obsolete => "obsolete", - RequestFailure::Network(OutboundFailure::DialFailure) => - "dial-failure", - RequestFailure::Network(OutboundFailure::Timeout) => "timeout", - RequestFailure::Network(OutboundFailure::ConnectionClosed) => - "connection-closed", - RequestFailure::Network( - OutboundFailure::UnsupportedProtocols, - ) => "unsupported", - }; + } + }, + SwarmEvent::Behaviour(BehaviourOut::RequestFinished { + protocol, + duration, + result, + .. + }) => + if let Some(metrics) = self.metrics.as_ref() { + match result { + Ok(_) => { + metrics + .requests_out_success_total + .with_label_values(&[&protocol]) + .observe(duration.as_secs_f64()); + }, + Err(err) => { + let reason = match err { + RequestFailure::NotConnected => "not-connected", + RequestFailure::UnknownProtocol => "unknown-protocol", + RequestFailure::Refused => "refused", + RequestFailure::Obsolete => "obsolete", + RequestFailure::Network(OutboundFailure::DialFailure) => + "dial-failure", + RequestFailure::Network(OutboundFailure::Timeout) => "timeout", + RequestFailure::Network(OutboundFailure::ConnectionClosed) => + "connection-closed", + RequestFailure::Network(OutboundFailure::UnsupportedProtocols) => + "unsupported", + }; - metrics - .requests_out_failure_total - .with_label_values(&[&protocol, reason]) - .inc(); - }, - } - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::ReputationChanges { - peer, - changes, - })) => - for change in changes { - this.network_service.behaviour().user_protocol().report_peer(peer, change); - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::PeerIdentify { - peer_id, - info: - IdentifyInfo { - protocol_version, - agent_version, - mut listen_addrs, - protocols, - .. + metrics + .requests_out_failure_total + .with_label_values(&[&protocol, reason]) + .inc(); }, - })) => { - if listen_addrs.len() > 30 { - debug!( - target: "sub-libp2p", - "Node {:?} has reported more than 30 addresses; it is identified by {:?} and {:?}", - peer_id, protocol_version, agent_version - ); - listen_addrs.truncate(30); - } - for addr in listen_addrs { - this.network_service - .behaviour_mut() - .add_self_reported_address_to_dht(&peer_id, &protocols, addr); } - this.network_service - .behaviour_mut() - .user_protocol_mut() - .add_default_set_discovered_nodes(iter::once(peer_id)); }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::Discovered(peer_id))) => { - this.network_service - .behaviour_mut() - .user_protocol_mut() - .add_default_set_discovered_nodes(iter::once(peer_id)); - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::RandomKademliaStarted)) => - if let Some(metrics) = this.metrics.as_ref() { - metrics.kademlia_random_queries_total.inc(); + SwarmEvent::Behaviour(BehaviourOut::ReputationChanges { peer, changes }) => { + for change in changes { + self.network_service.behaviour().user_protocol().report_peer(peer, change); + } + }, + SwarmEvent::Behaviour(BehaviourOut::PeerIdentify { + peer_id, + info: + IdentifyInfo { + protocol_version, agent_version, mut listen_addrs, protocols, .. }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::NotificationStreamOpened { + }) => { + if listen_addrs.len() > 30 { + debug!( + target: "sub-libp2p", + "Node {:?} has reported more than 30 addresses; it is identified by {:?} and {:?}", + peer_id, protocol_version, agent_version + ); + listen_addrs.truncate(30); + } + for addr in listen_addrs { + self.network_service + .behaviour_mut() + .add_self_reported_address_to_dht(&peer_id, &protocols, addr); + } + self.network_service.behaviour_mut().user_protocol_mut().add_known_peer(peer_id); + }, + SwarmEvent::Behaviour(BehaviourOut::Discovered(peer_id)) => { + self.network_service.behaviour_mut().user_protocol_mut().add_known_peer(peer_id); + }, + SwarmEvent::Behaviour(BehaviourOut::RandomKademliaStarted) => { + if let Some(metrics) = self.metrics.as_ref() { + metrics.kademlia_random_queries_total.inc(); + } + }, + SwarmEvent::Behaviour(BehaviourOut::NotificationStreamOpened { + remote, + protocol, + negotiated_fallback, + notifications_sink, + role, + received_handshake, + }) => { + if let Some(metrics) = self.metrics.as_ref() { + metrics + .notifications_streams_opened_total + .with_label_values(&[&protocol]) + .inc(); + } + { + let mut peers_notifications_sinks = self.peers_notifications_sinks.lock(); + let _previous_value = peers_notifications_sinks + .insert((remote, protocol.clone()), notifications_sink); + debug_assert!(_previous_value.is_none()); + } + self.event_streams.send(Event::NotificationStreamOpened { remote, protocol, negotiated_fallback, - notifications_sink, role, - })) => { - if let Some(metrics) = this.metrics.as_ref() { - metrics - .notifications_streams_opened_total - .with_label_values(&[&protocol]) - .inc(); - } - { - let mut peers_notifications_sinks = this.peers_notifications_sinks.lock(); - let _previous_value = peers_notifications_sinks - .insert((remote, protocol.clone()), notifications_sink); - debug_assert!(_previous_value.is_none()); - } - this.event_streams.send(Event::NotificationStreamOpened { - remote, - protocol, - negotiated_fallback, - role, - }); - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::NotificationStreamReplaced { - remote, - protocol, - notifications_sink, - })) => { - let mut peers_notifications_sinks = this.peers_notifications_sinks.lock(); - if let Some(s) = peers_notifications_sinks.get_mut(&(remote, protocol)) { - *s = notifications_sink; - } else { - error!( - target: "sub-libp2p", - "NotificationStreamReplaced for non-existing substream" - ); - debug_assert!(false); - } + received_handshake, + }); + }, + SwarmEvent::Behaviour(BehaviourOut::NotificationStreamReplaced { + remote, + protocol, + notifications_sink, + }) => { + let mut peers_notifications_sinks = self.peers_notifications_sinks.lock(); + if let Some(s) = peers_notifications_sinks.get_mut(&(remote, protocol)) { + *s = notifications_sink; + } else { + error!( + target: "sub-libp2p", + "NotificationStreamReplaced for non-existing substream" + ); + debug_assert!(false); + } - // TODO: Notifications might have been lost as a result of the previous - // connection being dropped, and as a result it would be preferable to notify - // the users of this fact by simulating the substream being closed then - // reopened. - // The code below doesn't compile because `role` is unknown. Propagating the - // handshake of the secondary connections is quite an invasive change and - // would conflict with https://github.com/paritytech/substrate/issues/6403. - // Considering that dropping notifications is generally regarded as - // acceptable, this bug is at the moment intentionally left there and is - // intended to be fixed at the same time as - // https://github.com/paritytech/substrate/issues/6403. - // this.event_streams.send(Event::NotificationStreamClosed { - // remote, - // protocol, - // }); - // this.event_streams.send(Event::NotificationStreamOpened { - // remote, - // protocol, - // role, - // }); - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::NotificationStreamClosed { - remote, - protocol, - })) => { - if let Some(metrics) = this.metrics.as_ref() { - metrics - .notifications_streams_closed_total - .with_label_values(&[&protocol[..]]) - .inc(); - } - this.event_streams.send(Event::NotificationStreamClosed { - remote, - protocol: protocol.clone(), - }); - { - let mut peers_notifications_sinks = this.peers_notifications_sinks.lock(); - let _previous_value = peers_notifications_sinks.remove(&(remote, protocol)); - debug_assert!(_previous_value.is_some()); - } - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::NotificationsReceived { - remote, - messages, - })) => { - if let Some(metrics) = this.metrics.as_ref() { - for (protocol, message) in &messages { - metrics - .notifications_sizes - .with_label_values(&["in", protocol]) - .observe(message.len() as f64); - } - } - this.event_streams.send(Event::NotificationsReceived { remote, messages }); - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::SyncConnected(remote))) => { - this.event_streams.send(Event::SyncConnected { remote }); - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::SyncDisconnected(remote))) => { - this.event_streams.send(Event::SyncDisconnected { remote }); - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::Dht(event, duration))) => { - if let Some(metrics) = this.metrics.as_ref() { - let query_type = match event { - DhtEvent::ValueFound(_) => "value-found", - DhtEvent::ValueNotFound(_) => "value-not-found", - DhtEvent::ValuePut(_) => "value-put", - DhtEvent::ValuePutFailed(_) => "value-put-failed", - }; + // TODO: Notifications might have been lost as a result of the previous + // connection being dropped, and as a result it would be preferable to notify + // the users of this fact by simulating the substream being closed then + // reopened. + // The code below doesn't compile because `role` is unknown. Propagating the + // handshake of the secondary connections is quite an invasive change and + // would conflict with https://github.com/paritytech/substrate/issues/6403. + // Considering that dropping notifications is generally regarded as + // acceptable, this bug is at the moment intentionally left there and is + // intended to be fixed at the same time as + // https://github.com/paritytech/substrate/issues/6403. + // self.event_streams.send(Event::NotificationStreamClosed { + // remote, + // protocol, + // }); + // self.event_streams.send(Event::NotificationStreamOpened { + // remote, + // protocol, + // role, + // }); + }, + SwarmEvent::Behaviour(BehaviourOut::NotificationStreamClosed { remote, protocol }) => { + if let Some(metrics) = self.metrics.as_ref() { + metrics + .notifications_streams_closed_total + .with_label_values(&[&protocol[..]]) + .inc(); + } + self.event_streams + .send(Event::NotificationStreamClosed { remote, protocol: protocol.clone() }); + { + let mut peers_notifications_sinks = self.peers_notifications_sinks.lock(); + let _previous_value = peers_notifications_sinks.remove(&(remote, protocol)); + debug_assert!(_previous_value.is_some()); + } + }, + SwarmEvent::Behaviour(BehaviourOut::NotificationsReceived { remote, messages }) => { + if let Some(metrics) = self.metrics.as_ref() { + for (protocol, message) in &messages { metrics - .kademlia_query_duration - .with_label_values(&[query_type]) - .observe(duration.as_secs_f64()); + .notifications_sizes + .with_label_values(&["in", protocol]) + .observe(message.len() as f64); } + } + self.event_streams.send(Event::NotificationsReceived { remote, messages }); + }, + SwarmEvent::Behaviour(BehaviourOut::Dht(event, duration)) => { + if let Some(metrics) = self.metrics.as_ref() { + let query_type = match event { + DhtEvent::ValueFound(_) => "value-found", + DhtEvent::ValueNotFound(_) => "value-not-found", + DhtEvent::ValuePut(_) => "value-put", + DhtEvent::ValuePutFailed(_) => "value-put-failed", + }; + metrics + .kademlia_query_duration + .with_label_values(&[query_type]) + .observe(duration.as_secs_f64()); + } - this.event_streams.send(Event::Dht(event)); - }, - Poll::Ready(SwarmEvent::Behaviour(BehaviourOut::None)) => { - // Ignored event from lower layers. - }, - Poll::Ready(SwarmEvent::ConnectionEstablished { - peer_id, - endpoint, - num_established, - concurrent_dial_errors, - }) => { - if let Some(errors) = concurrent_dial_errors { - debug!(target: "sub-libp2p", "Libp2p => Connected({:?}) with errors: {:?}", peer_id, errors); - } else { - debug!(target: "sub-libp2p", "Libp2p => Connected({:?})", peer_id); - } + self.event_streams.send(Event::Dht(event)); + }, + SwarmEvent::Behaviour(BehaviourOut::None) => { + // Ignored event from lower layers. + }, + SwarmEvent::ConnectionEstablished { + peer_id, + endpoint, + num_established, + concurrent_dial_errors, + .. + } => { + if let Some(errors) = concurrent_dial_errors { + debug!(target: "sub-libp2p", "Libp2p => Connected({:?}) with errors: {:?}", peer_id, errors); + } else { + debug!(target: "sub-libp2p", "Libp2p => Connected({:?})", peer_id); + } - if let Some(metrics) = this.metrics.as_ref() { - let direction = match endpoint { - ConnectedPoint::Dialer { .. } => "out", - ConnectedPoint::Listener { .. } => "in", - }; - metrics.connections_opened_total.with_label_values(&[direction]).inc(); + if let Some(metrics) = self.metrics.as_ref() { + let direction = match endpoint { + ConnectedPoint::Dialer { .. } => "out", + ConnectedPoint::Listener { .. } => "in", + }; + metrics.connections_opened_total.with_label_values(&[direction]).inc(); - if num_established.get() == 1 { - metrics.distinct_peers_connections_opened_total.inc(); - } + if num_established.get() == 1 { + metrics.distinct_peers_connections_opened_total.inc(); } - }, - Poll::Ready(SwarmEvent::ConnectionClosed { - peer_id, - cause, - endpoint, - num_established, - }) => { - debug!(target: "sub-libp2p", "Libp2p => Disconnected({:?}, {:?})", peer_id, cause); - if let Some(metrics) = this.metrics.as_ref() { - let direction = match endpoint { - ConnectedPoint::Dialer { .. } => "out", - ConnectedPoint::Listener { .. } => "in", - }; - let reason = match cause { - Some(ConnectionError::IO(_)) => "transport-error", - Some(ConnectionError::Handler(EitherError::A(EitherError::A( - EitherError::B(EitherError::A(PingFailure::Timeout)), - )))) => "ping-timeout", - Some(ConnectionError::Handler(EitherError::A(EitherError::A( - EitherError::A(NotifsHandlerError::SyncNotificationsClogged), - )))) => "sync-notifications-clogged", - Some(ConnectionError::Handler(_)) => "protocol-error", - Some(ConnectionError::KeepAliveTimeout) => "keep-alive-timeout", - None => "actively-closed", - }; - metrics - .connections_closed_total - .with_label_values(&[direction, reason]) - .inc(); + } + }, + SwarmEvent::ConnectionClosed { peer_id, cause, endpoint, num_established } => { + debug!(target: "sub-libp2p", "Libp2p => Disconnected({:?}, {:?})", peer_id, cause); + if let Some(metrics) = self.metrics.as_ref() { + let direction = match endpoint { + ConnectedPoint::Dialer { .. } => "out", + ConnectedPoint::Listener { .. } => "in", + }; + let reason = match cause { + Some(ConnectionError::IO(_)) => "transport-error", + Some(ConnectionError::Handler(Either::Left(Either::Left( + Either::Right(Either::Left(PingFailure::Timeout)), + )))) => "ping-timeout", + Some(ConnectionError::Handler(Either::Left(Either::Left( + Either::Left(NotifsHandlerError::SyncNotificationsClogged), + )))) => "sync-notifications-clogged", + Some(ConnectionError::Handler(_)) => "protocol-error", + Some(ConnectionError::KeepAliveTimeout) => "keep-alive-timeout", + None => "actively-closed", + }; + metrics.connections_closed_total.with_label_values(&[direction, reason]).inc(); - // `num_established` represents the number of *remaining* connections. - if num_established == 0 { - metrics.distinct_peers_connections_closed_total.inc(); - } - } - }, - Poll::Ready(SwarmEvent::NewListenAddr { address, .. }) => { - trace!(target: "sub-libp2p", "Libp2p => NewListenAddr({})", address); - if let Some(metrics) = this.metrics.as_ref() { - metrics.listeners_local_addresses.inc(); - } - }, - Poll::Ready(SwarmEvent::ExpiredListenAddr { address, .. }) => { - info!(target: "sub-libp2p", "📪 No longer listening on {}", address); - if let Some(metrics) = this.metrics.as_ref() { - metrics.listeners_local_addresses.dec(); + // `num_established` represents the number of *remaining* connections. + if num_established == 0 { + metrics.distinct_peers_connections_closed_total.inc(); } - }, - Poll::Ready(SwarmEvent::OutgoingConnectionError { peer_id, error }) => { - if let Some(peer_id) = peer_id { - trace!( - target: "sub-libp2p", - "Libp2p => Failed to reach {:?}: {}", - peer_id, error, - ); - - if this.boot_node_ids.contains(&peer_id) { - if let DialError::WrongPeerId { obtained, endpoint } = &error { - if let ConnectedPoint::Dialer { address, role_override: _ } = - endpoint - { + } + }, + SwarmEvent::NewListenAddr { address, .. } => { + trace!(target: "sub-libp2p", "Libp2p => NewListenAddr({})", address); + if let Some(metrics) = self.metrics.as_ref() { + metrics.listeners_local_addresses.inc(); + } + }, + SwarmEvent::ExpiredListenAddr { address, .. } => { + info!(target: "sub-libp2p", "📪 No longer listening on {}", address); + if let Some(metrics) = self.metrics.as_ref() { + metrics.listeners_local_addresses.dec(); + } + }, + SwarmEvent::OutgoingConnectionError { peer_id, error } => { + if let Some(peer_id) = peer_id { + trace!( + target: "sub-libp2p", + "Libp2p => Failed to reach {:?}: {}", + peer_id, error, + ); + + let not_reported = !self.reported_invalid_boot_nodes.contains(&peer_id); + + if let Some(addresses) = + not_reported.then(|| self.boot_node_ids.get(&peer_id)).flatten() + { + if let DialError::WrongPeerId { obtained, endpoint } = &error { + if let ConnectedPoint::Dialer { address, role_override: _ } = endpoint { + let address_without_peer_id = parse_addr(address.clone()) + .map_or_else(|_| address.clone(), |r| r.1); + + // Only report for address of boot node that was added at startup of + // the node and not for any address that the node learned of the + // boot node. + if addresses.iter().any(|a| address_without_peer_id == *a) { warn!( - "💔 The bootnode you want to connect to at `{}` provided a different peer ID `{}` than the one you expect `{}`.", - address, - obtained, - peer_id, + "💔 The bootnode you want to connect to at `{address}` provided a \ + different peer ID `{obtained}` than the one you expect `{peer_id}`.", ); + + self.reported_invalid_boot_nodes.insert(peer_id); } } } } + } - if let Some(metrics) = this.metrics.as_ref() { - let reason = match error { - DialError::ConnectionLimit(_) => Some("limit-reached"), - DialError::InvalidPeerId(_) => Some("invalid-peer-id"), - DialError::Transport(_) | DialError::ConnectionIo(_) => - Some("transport-error"), - DialError::Banned | - DialError::LocalPeerId | - DialError::NoAddresses | - DialError::DialPeerConditionFalse(_) | - DialError::WrongPeerId { .. } | - DialError::Aborted => None, // ignore them - }; - if let Some(reason) = reason { - metrics - .pending_connections_errors_total - .with_label_values(&[reason]) - .inc(); - } - } - }, - Poll::Ready(SwarmEvent::Dialing(peer_id)) => { - trace!(target: "sub-libp2p", "Libp2p => Dialing({:?})", peer_id) - }, - Poll::Ready(SwarmEvent::IncomingConnection { local_addr, send_back_addr }) => { - trace!(target: "sub-libp2p", "Libp2p => IncomingConnection({},{}))", - local_addr, send_back_addr); - if let Some(metrics) = this.metrics.as_ref() { - metrics.incoming_connections_total.inc(); - } - }, - Poll::Ready(SwarmEvent::IncomingConnectionError { - local_addr, - send_back_addr, - error, - }) => { - debug!( - target: "sub-libp2p", - "Libp2p => IncomingConnectionError({},{}): {}", - local_addr, send_back_addr, error, - ); - if let Some(metrics) = this.metrics.as_ref() { - let reason = match error { - PendingConnectionError::ConnectionLimit(_) => Some("limit-reached"), - PendingConnectionError::WrongPeerId { .. } => Some("invalid-peer-id"), - PendingConnectionError::Transport(_) | - PendingConnectionError::IO(_) => Some("transport-error"), - PendingConnectionError::Aborted => None, // ignore it - }; - - if let Some(reason) = reason { - metrics - .incoming_connections_errors_total - .with_label_values(&[reason]) - .inc(); - } + if let Some(metrics) = self.metrics.as_ref() { + #[allow(deprecated)] + let reason = match error { + DialError::Denied { cause } => + if cause.downcast::().is_ok() { + Some("limit-reached") + } else { + None + }, + DialError::ConnectionLimit(_) => Some("limit-reached"), + DialError::InvalidPeerId(_) | + DialError::WrongPeerId { .. } | + DialError::LocalPeerId { .. } => Some("invalid-peer-id"), + DialError::Transport(_) => Some("transport-error"), + DialError::Banned | + DialError::NoAddresses | + DialError::DialPeerConditionFalse(_) | + DialError::Aborted => None, // ignore them + }; + if let Some(reason) = reason { + metrics.pending_connections_errors_total.with_label_values(&[reason]).inc(); } - }, - Poll::Ready(SwarmEvent::BannedPeer { peer_id, endpoint }) => { - debug!( - target: "sub-libp2p", - "Libp2p => BannedPeer({}). Connected via {:?}.", - peer_id, endpoint, - ); - if let Some(metrics) = this.metrics.as_ref() { + } + }, + SwarmEvent::Dialing(peer_id) => { + trace!(target: "sub-libp2p", "Libp2p => Dialing({:?})", peer_id) + }, + SwarmEvent::IncomingConnection { local_addr, send_back_addr } => { + trace!(target: "sub-libp2p", "Libp2p => IncomingConnection({},{}))", + local_addr, send_back_addr); + if let Some(metrics) = self.metrics.as_ref() { + metrics.incoming_connections_total.inc(); + } + }, + SwarmEvent::IncomingConnectionError { local_addr, send_back_addr, error } => { + debug!( + target: "sub-libp2p", + "Libp2p => IncomingConnectionError({},{}): {}", + local_addr, send_back_addr, error, + ); + if let Some(metrics) = self.metrics.as_ref() { + #[allow(deprecated)] + let reason = match error { + ListenError::Denied { cause } => + if cause.downcast::().is_ok() { + Some("limit-reached") + } else { + None + }, + ListenError::ConnectionLimit(_) => Some("limit-reached"), + ListenError::WrongPeerId { .. } | ListenError::LocalPeerId { .. } => + Some("invalid-peer-id"), + ListenError::Transport(_) => Some("transport-error"), + ListenError::Aborted => None, // ignore it + }; + + if let Some(reason) = reason { metrics .incoming_connections_errors_total - .with_label_values(&["banned"]) + .with_label_values(&[reason]) .inc(); } - }, - Poll::Ready(SwarmEvent::ListenerClosed { reason, addresses, .. }) => { - if let Some(metrics) = this.metrics.as_ref() { - metrics.listeners_local_addresses.sub(addresses.len() as u64); - } - let addrs = - addresses.into_iter().map(|a| a.to_string()).collect::>().join(", "); - match reason { - Ok(()) => error!( - target: "sub-libp2p", - "📪 Libp2p listener ({}) closed gracefully", - addrs - ), - Err(e) => error!( - target: "sub-libp2p", - "📪 Libp2p listener ({}) closed: {}", - addrs, e - ), - } - }, - Poll::Ready(SwarmEvent::ListenerError { error, .. }) => { - debug!(target: "sub-libp2p", "Libp2p => ListenerError: {}", error); - if let Some(metrics) = this.metrics.as_ref() { - metrics.listeners_errors_total.inc(); - } - }, - }; - } - - let num_connected_peers = - this.network_service.behaviour_mut().user_protocol_mut().num_connected_peers(); - - // Update the variables shared with the `NetworkService`. - this.num_connected.store(num_connected_peers, Ordering::Relaxed); - { - let external_addresses = - Swarm::>::external_addresses(&this.network_service) - .map(|r| &r.addr) - .cloned() - .collect(); - *this.external_addresses.lock() = external_addresses; - } - - let is_major_syncing = this - .network_service - .behaviour_mut() - .user_protocol_mut() - .sync_state() - .state - .is_major_syncing(); - - this.is_major_syncing.store(is_major_syncing, Ordering::Relaxed); - - if let Some(metrics) = this.metrics.as_ref() { - if let Some(buckets) = this.network_service.behaviour_mut().num_entries_per_kbucket() { - for (lower_ilog2_bucket_bound, num_entries) in buckets { - metrics - .kbuckets_num_nodes - .with_label_values(&[&lower_ilog2_bucket_bound.to_string()]) - .set(num_entries as u64); } - } - if let Some(num_entries) = this.network_service.behaviour_mut().num_kademlia_records() { - metrics.kademlia_records_count.set(num_entries as u64); - } - if let Some(num_entries) = - this.network_service.behaviour_mut().kademlia_records_total_size() - { - metrics.kademlia_records_sizes_total.set(num_entries as u64); - } - metrics - .peerset_num_discovered - .set(this.network_service.behaviour_mut().user_protocol().num_discovered_peers() - as u64); - metrics.pending_connections.set( - Swarm::network_info(&this.network_service).connection_counters().num_pending() - as u64, - ); + }, + #[allow(deprecated)] + SwarmEvent::BannedPeer { peer_id, endpoint } => { + debug!( + target: "sub-libp2p", + "Libp2p => BannedPeer({}). Connected via {:?}.", + peer_id, endpoint, + ); + if let Some(metrics) = self.metrics.as_ref() { + metrics.incoming_connections_errors_total.with_label_values(&["banned"]).inc(); + } + }, + SwarmEvent::ListenerClosed { reason, addresses, .. } => { + if let Some(metrics) = self.metrics.as_ref() { + metrics.listeners_local_addresses.sub(addresses.len() as u64); + } + let addrs = + addresses.into_iter().map(|a| a.to_string()).collect::>().join(", "); + match reason { + Ok(()) => error!( + target: "sub-libp2p", + "📪 Libp2p listener ({}) closed gracefully", + addrs + ), + Err(e) => error!( + target: "sub-libp2p", + "📪 Libp2p listener ({}) closed: {}", + addrs, e + ), + } + }, + SwarmEvent::ListenerError { error, .. } => { + debug!(target: "sub-libp2p", "Libp2p => ListenerError: {}", error); + if let Some(metrics) = self.metrics.as_ref() { + metrics.listeners_errors_total.inc(); + } + }, } - - Poll::Pending } } -impl Unpin for NetworkWorker +impl Unpin for NetworkWorker where B: BlockT + 'static, H: ExHashT, - Client: HeaderBackend + 'static, { } diff --git a/client/network/src/service/metrics.rs b/client/network/src/service/metrics.rs index a099bba716eb..13bc4b4e7aff 100644 --- a/client/network/src/service/metrics.rs +++ b/client/network/src/service/metrics.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -24,7 +24,7 @@ use prometheus_endpoint::{ use std::{ str, sync::{ - atomic::{AtomicBool, AtomicUsize, Ordering}, + atomic::{AtomicUsize, Ordering}, Arc, }, }; @@ -34,7 +34,6 @@ pub use prometheus_endpoint::{Histogram, HistogramVec}; /// Registers all networking metrics with the given registry. pub fn register(registry: &Registry, sources: MetricSources) -> Result { BandwidthCounters::register(registry, sources.bandwidth)?; - MajorSyncingGauge::register(registry, sources.major_syncing)?; NumConnectedGauge::register(registry, sources.connected_peers)?; Metrics::register(registry) } @@ -42,7 +41,6 @@ pub fn register(registry: &Registry, sources: MetricSources) -> Result, - pub major_syncing: Arc, pub connected_peers: Arc, } @@ -266,37 +264,6 @@ impl MetricSource for BandwidthCounters { } } -/// The "major syncing" metric. -#[derive(Clone)] -pub struct MajorSyncingGauge(Arc); - -impl MajorSyncingGauge { - /// Registers the `MajorSyncGauge` metric whose value is - /// obtained from the given `AtomicBool`. - fn register(registry: &Registry, value: Arc) -> Result<(), PrometheusError> { - prometheus::register( - SourcedGauge::new( - &Opts::new( - "substrate_sub_libp2p_is_major_syncing", - "Whether the node is performing a major sync or not.", - ), - MajorSyncingGauge(value), - )?, - registry, - )?; - - Ok(()) - } -} - -impl MetricSource for MajorSyncingGauge { - type N = u64; - - fn collect(&self, mut set: impl FnMut(&[&str], Self::N)) { - set(&[], self.0.load(Ordering::Relaxed) as u64); - } -} - /// The connected peers metric. #[derive(Clone)] pub struct NumConnectedGauge(Arc); diff --git a/client/network/src/service/out_events.rs b/client/network/src/service/out_events.rs index 5e0c8ac6a1a4..ededccd5e323 100644 --- a/client/network/src/service/out_events.rs +++ b/client/network/src/service/out_events.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -31,44 +31,51 @@ //! - Send events by calling [`OutChannels::send`]. Events are cloned for each sender in the //! collection. -use backtrace::Backtrace; -use futures::{channel::mpsc, prelude::*, ready, stream::FusedStream}; -use log::error; -use parking_lot::Mutex; +use crate::event::Event; + +use futures::{prelude::*, ready, stream::FusedStream}; +use log::{debug, error}; use prometheus_endpoint::{register, CounterVec, GaugeVec, Opts, PrometheusError, Registry, U64}; -use sc_network_common::protocol::event::Event; use std::{ + backtrace::Backtrace, cell::RefCell, fmt, pin::Pin, - sync::{ - atomic::{AtomicI64, Ordering}, - Arc, - }, task::{Context, Poll}, }; +/// Log target for this file. +pub const LOG_TARGET: &str = "sub-libp2p::out_events"; + /// Creates a new channel that can be associated to a [`OutChannels`]. /// /// The name is used in Prometheus reports, the queue size threshold is used /// to warn if there are too many unprocessed events in the channel. -pub fn channel(name: &'static str, queue_size_warning: i64) -> (Sender, Receiver) { - let (tx, rx) = mpsc::unbounded(); - let metrics = Arc::new(Mutex::new(None)); - let queue_size = Arc::new(AtomicI64::new(0)); +pub fn channel(name: &'static str, queue_size_warning: usize) -> (Sender, Receiver) { + let (tx, rx) = async_channel::unbounded(); let tx = Sender { inner: tx, name, - queue_size: queue_size.clone(), queue_size_warning, - warning_fired: false, - creation_backtrace: Backtrace::new_unresolved(), - metrics: metrics.clone(), + warning_fired: SenderWarningState::NotFired, + creation_backtrace: Backtrace::force_capture(), + metrics: None, }; - let rx = Receiver { inner: rx, name, queue_size, metrics }; + let rx = Receiver { inner: rx, name, metrics: None }; (tx, rx) } +/// A state of a sender warning that is used to avoid spamming the logs. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum SenderWarningState { + /// The warning has not been fired yet. + NotFired, + /// The warning has been fired, and the channel is full + FiredFull, + /// The warning has been fired and the channel is not full anymore. + FiredFree, +} + /// Sending side of a channel. /// /// Must be associated with an [`OutChannels`] before anything can be sent on it @@ -77,23 +84,19 @@ pub fn channel(name: &'static str, queue_size_warning: i64) -> (Sender, Receiver /// implement the `Clone` trait e.g. in Order to not complicate the logic keeping the metrics in /// sync on drop. If someone adds a `#[derive(Clone)]` below, it is **wrong**. pub struct Sender { - inner: mpsc::UnboundedSender, + inner: async_channel::Sender, /// Name to identify the channel (e.g., in Prometheus and logs). name: &'static str, - /// Number of events in the queue. Clone of [`Receiver::in_transit`]. - // To not bother with ordering and possible underflow errors of the unsigned counter - // we just use `i64` and `Ordering::Relaxed`, and perceive `queue_size` as approximate. - // It can turn < 0 though. - queue_size: Arc, /// Threshold queue size to generate an error message in the logs. - queue_size_warning: i64, - /// We generate the error message only once to not spam the logs. - warning_fired: bool, + queue_size_warning: usize, + /// We generate the error message only once to not spam the logs after the first error. + /// Subsequently we indicate channel fullness on debug level. + warning_fired: SenderWarningState, /// Backtrace of a place where the channel was created. creation_backtrace: Backtrace, /// Clone of [`Receiver::metrics`]. Will be initialized when [`Sender`] is added to /// [`OutChannels`] with `OutChannels::push()`. - metrics: Arc>>>>, + metrics: Option, } impl fmt::Debug for Sender { @@ -104,8 +107,7 @@ impl fmt::Debug for Sender { impl Drop for Sender { fn drop(&mut self) { - let metrics = self.metrics.lock(); - if let Some(Some(metrics)) = metrics.as_ref().map(|m| &**m) { + if let Some(metrics) = self.metrics.as_ref() { metrics.num_channels.with_label_values(&[self.name]).dec(); } } @@ -113,12 +115,11 @@ impl Drop for Sender { /// Receiving side of a channel. pub struct Receiver { - inner: mpsc::UnboundedReceiver, + inner: async_channel::Receiver, name: &'static str, - queue_size: Arc, /// Initially contains `None`, and will be set to a value once the corresponding [`Sender`] /// is assigned to an instance of [`OutChannels`]. - metrics: Arc>>>>, + metrics: Option, } impl Stream for Receiver { @@ -126,14 +127,8 @@ impl Stream for Receiver { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { if let Some(ev) = ready!(Pin::new(&mut self.inner).poll_next(cx)) { - let _ = self.queue_size.fetch_sub(1, Ordering::Relaxed); - let metrics = self.metrics.lock().clone(); - match metrics.as_ref().map(|m| m.as_ref()) { - Some(Some(metrics)) => metrics.event_out(&ev, self.name), - Some(None) => (), // no registry - None => log::warn!( - "Inconsistency in out_events: event happened before sender associated" - ), + if let Some(metrics) = &self.metrics { + metrics.event_out(&ev, self.name); } Poll::Ready(Some(ev)) } else { @@ -162,7 +157,7 @@ pub struct OutChannels { event_streams: Vec, /// The metrics we collect. A clone of this is sent to each [`Receiver`] associated with this /// object. - metrics: Arc>, + metrics: Option, } impl OutChannels { @@ -171,17 +166,15 @@ impl OutChannels { let metrics = if let Some(registry) = registry { Some(Metrics::register(registry)?) } else { None }; - Ok(Self { event_streams: Vec::new(), metrics: Arc::new(metrics) }) + Ok(Self { event_streams: Vec::new(), metrics }) } /// Adds a new [`Sender`] to the collection. - pub fn push(&mut self, sender: Sender) { - let mut metrics = sender.metrics.lock(); - debug_assert!(metrics.is_none()); - *metrics = Some(self.metrics.clone()); - drop(metrics); + pub fn push(&mut self, mut sender: Sender) { + debug_assert!(sender.metrics.is_none()); + sender.metrics = self.metrics.clone(); - if let Some(metrics) = &*self.metrics { + if let Some(metrics) = &self.metrics { metrics.num_channels.with_label_values(&[sender.name]).inc(); } @@ -191,20 +184,42 @@ impl OutChannels { /// Sends an event. pub fn send(&mut self, event: Event) { self.event_streams.retain_mut(|sender| { - let queue_size = sender.queue_size.fetch_add(1, Ordering::Relaxed); - if queue_size == sender.queue_size_warning && !sender.warning_fired { - sender.warning_fired = true; - sender.creation_backtrace.resolve(); - error!( - "The number of unprocessed events in channel `{}` reached {}.\n\ - The channel was created at:\n{:?}", - sender.name, sender.queue_size_warning, sender.creation_backtrace, + let current_pending = sender.inner.len(); + if current_pending >= sender.queue_size_warning { + if sender.warning_fired == SenderWarningState::NotFired { + error!( + "The number of unprocessed events in channel `{}` exceeded {}.\n\ + The channel was created at:\n{:}\n + The last event was sent from:\n{:}", + sender.name, + sender.queue_size_warning, + sender.creation_backtrace, + Backtrace::force_capture(), + ); + } else if sender.warning_fired == SenderWarningState::FiredFree { + // We don't want to spam the logs, so we only log on debug level + debug!( + target: LOG_TARGET, + "Channel `{}` is overflowed again. Number of events: {}", + sender.name, current_pending + ); + } + sender.warning_fired = SenderWarningState::FiredFull; + } else if sender.warning_fired == SenderWarningState::FiredFull && + current_pending < sender.queue_size_warning.wrapping_div(2) + { + sender.warning_fired = SenderWarningState::FiredFree; + debug!( + target: LOG_TARGET, + "Channel `{}` is no longer overflowed. Number of events: {}", + sender.name, current_pending ); } - sender.inner.unbounded_send(event.clone()).is_ok() + + sender.inner.try_send(event.clone()).is_ok() }); - if let Some(metrics) = &*self.metrics { + if let Some(metrics) = &self.metrics { for ev in &self.event_streams { metrics.event_in(&event, ev.name); } @@ -220,6 +235,7 @@ impl fmt::Debug for OutChannels { } } +#[derive(Clone)] struct Metrics { // This list is ordered alphabetically events_total: CounterVec, @@ -278,12 +294,6 @@ impl Metrics { Event::Dht(_) => { self.events_total.with_label_values(&["dht", "sent", name]).inc(); }, - Event::SyncConnected { .. } => { - self.events_total.with_label_values(&["sync-connected", "sent", name]).inc(); - }, - Event::SyncDisconnected { .. } => { - self.events_total.with_label_values(&["sync-disconnected", "sent", name]).inc(); - }, Event::NotificationStreamOpened { protocol, .. } => { format_label("notif-open-", protocol, |protocol_label| { self.events_total.with_label_values(&[protocol_label, "sent", name]).inc(); @@ -311,14 +321,6 @@ impl Metrics { Event::Dht(_) => { self.events_total.with_label_values(&["dht", "received", name]).inc(); }, - Event::SyncConnected { .. } => { - self.events_total.with_label_values(&["sync-connected", "received", name]).inc(); - }, - Event::SyncDisconnected { .. } => { - self.events_total - .with_label_values(&["sync-disconnected", "received", name]) - .inc(); - }, Event::NotificationStreamOpened { protocol, .. } => { format_label("notif-open-", protocol, |protocol_label| { self.events_total.with_label_values(&[protocol_label, "received", name]).inc(); diff --git a/client/network/common/src/service/signature.rs b/client/network/src/service/signature.rs similarity index 94% rename from client/network/common/src/service/signature.rs rename to client/network/src/service/signature.rs index 602ef3d82979..024f60e4c466 100644 --- a/client/network/common/src/service/signature.rs +++ b/client/network/src/service/signature.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. // -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -19,10 +19,12 @@ // If you read this, you are very thorough, congratulations. use libp2p::{ - identity::{error::SigningError, Keypair, PublicKey}, + identity::{Keypair, PublicKey}, PeerId, }; +pub use libp2p::identity::SigningError; + /// A result of signing a message with a network identity. Since `PeerId` is potentially a hash of a /// `PublicKey`, you need to reveal the `PublicKey` next to the signature, so the verifier can check /// if the signature was made by the entity that controls a given `PeerId`. diff --git a/client/network/src/service/tests/chain_sync.rs b/client/network/src/service/tests/chain_sync.rs deleted file mode 100644 index 9d8463ff190a..000000000000 --- a/client/network/src/service/tests/chain_sync.rs +++ /dev/null @@ -1,439 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2022 Parity Technologies (UK) Ltd. -// 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 crate::{ - config, - service::tests::{TestNetworkBuilder, BLOCK_ANNOUNCE_PROTO_NAME}, -}; - -use futures::prelude::*; -use libp2p::PeerId; -use sc_block_builder::BlockBuilderProvider; -use sc_client_api::HeaderBackend; -use sc_consensus::JustificationSyncLink; -use sc_network_common::{ - config::{MultiaddrWithPeerId, ProtocolId, SetConfig}, - protocol::{event::Event, role::Roles, ProtocolName}, - service::NetworkSyncForkRequest, - sync::{SyncState, SyncStatus}, -}; -use sc_network_sync::{mock::MockChainSync, service::mock::MockChainSyncInterface, ChainSync}; -use sp_core::H256; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, Header as _}, -}; -use std::{ - sync::{Arc, RwLock}, - task::Poll, - time::Duration, -}; -use substrate_test_runtime_client::{TestClientBuilder, TestClientBuilderExt as _}; - -fn set_default_expecations_no_peers( - chain_sync: &mut MockChainSync, -) { - chain_sync.expect_poll().returning(|_| Poll::Pending); - chain_sync.expect_status().returning(|| SyncStatus { - state: SyncState::Idle, - best_seen_block: None, - num_peers: 0u32, - queued_blocks: 0u32, - state_sync: None, - warp_sync: None, - }); -} - -#[tokio::test] -async fn normal_network_poll_no_peers() { - // build `ChainSync` and set default expectations for it - let mut chain_sync = - Box::new(MockChainSync::::new()); - set_default_expecations_no_peers(&mut chain_sync); - - // build `ChainSyncInterface` provider and set no expecations for it (i.e., it cannot be - // called) - let chain_sync_service = - Box::new(MockChainSyncInterface::::new()); - - let mut network = TestNetworkBuilder::new() - .with_chain_sync((chain_sync, chain_sync_service)) - .build(); - - // poll the network once - futures::future::poll_fn(|cx| { - let _ = network.network().poll_unpin(cx); - Poll::Ready(()) - }) - .await; -} - -#[tokio::test] -async fn request_justification() { - let hash = H256::random(); - let number = 1337u64; - - // build `ChainSyncInterface` provider and and expect - // `JustificationSyncLink::request_justification() to be called once - let mut chain_sync_service = - Box::new(MockChainSyncInterface::::new()); - - chain_sync_service - .expect_justification_sync_link_request_justification() - .withf(move |in_hash, in_number| &hash == in_hash && &number == in_number) - .once() - .returning(|_, _| ()); - - // build `ChainSync` and set default expecations for it - let mut chain_sync = MockChainSync::::new(); - - set_default_expecations_no_peers(&mut chain_sync); - let mut network = TestNetworkBuilder::new() - .with_chain_sync((Box::new(chain_sync), chain_sync_service)) - .build(); - - // send "request justifiction" message and poll the network - network.service().request_justification(&hash, number); - - futures::future::poll_fn(|cx| { - let _ = network.network().poll_unpin(cx); - Poll::Ready(()) - }) - .await; -} - -#[tokio::test] -async fn clear_justification_requests() { - // build `ChainSyncInterface` provider and expect - // `JustificationSyncLink::clear_justification_requests()` to be called - let mut chain_sync_service = - Box::new(MockChainSyncInterface::::new()); - - chain_sync_service - .expect_justification_sync_link_clear_justification_requests() - .once() - .returning(|| ()); - - // build `ChainSync` and set default expecations for it - let mut chain_sync = - Box::new(MockChainSync::::new()); - - set_default_expecations_no_peers(&mut chain_sync); - let mut network = TestNetworkBuilder::new() - .with_chain_sync((chain_sync, chain_sync_service)) - .build(); - - // send "request justifiction" message and poll the network - network.service().clear_justification_requests(); - - futures::future::poll_fn(|cx| { - let _ = network.network().poll_unpin(cx); - Poll::Ready(()) - }) - .await; -} - -#[tokio::test] -async fn set_sync_fork_request() { - // build `ChainSync` and set default expectations for it - let mut chain_sync = - Box::new(MockChainSync::::new()); - set_default_expecations_no_peers(&mut chain_sync); - - // build `ChainSyncInterface` provider and verify that the `set_sync_fork_request()` - // call is delegated to `ChainSyncInterface` (which eventually forwards it to `ChainSync`) - let mut chain_sync_service = - MockChainSyncInterface::::new(); - - let hash = H256::random(); - let number = 1337u64; - let peers = (0..3).map(|_| PeerId::random()).collect::>(); - let copy_peers = peers.clone(); - - chain_sync_service - .expect_set_sync_fork_request() - .withf(move |in_peers, in_hash, in_number| { - &peers == in_peers && &hash == in_hash && &number == in_number - }) - .once() - .returning(|_, _, _| ()); - - let mut network = TestNetworkBuilder::new() - .with_chain_sync((chain_sync, Box::new(chain_sync_service))) - .build(); - - // send "set sync fork request" message and poll the network - network.service().set_sync_fork_request(copy_peers, hash, number); - - futures::future::poll_fn(|cx| { - let _ = network.network().poll_unpin(cx); - Poll::Ready(()) - }) - .await; -} - -#[tokio::test] -async fn on_block_finalized() { - let client = Arc::new(TestClientBuilder::with_default_backend().build_with_longest_chain().0); - // build `ChainSyncInterface` provider and set no expecations for it (i.e., it cannot be - // called) - let chain_sync_service = - Box::new(MockChainSyncInterface::::new()); - - // build `ChainSync` and verify that call to `on_block_finalized()` is made - let mut chain_sync = - Box::new(MockChainSync::::new()); - - let at = client.header(client.info().best_hash).unwrap().unwrap().hash(); - let block = client - .new_block_at(&BlockId::Hash(at), Default::default(), false) - .unwrap() - .build() - .unwrap() - .block; - let header = block.header.clone(); - let block_number = *header.number(); - let hash = block.hash(); - - chain_sync - .expect_on_block_finalized() - .withf(move |in_hash, in_number| &hash == in_hash && &block_number == in_number) - .once() - .returning(|_, _| ()); - - set_default_expecations_no_peers(&mut chain_sync); - let mut network = TestNetworkBuilder::new() - .with_client(client) - .with_chain_sync((chain_sync, chain_sync_service)) - .build(); - - // send "set sync fork request" message and poll the network - network.network().on_block_finalized(hash, header); - - futures::future::poll_fn(|cx| { - let _ = network.network().poll_unpin(cx); - Poll::Ready(()) - }) - .await; -} - -// report from mock import queue that importing a justification was not successful -// and verify that connection to the peer is closed -#[tokio::test] -async fn invalid_justification_imported() { - struct DummyImportQueueHandle; - - impl - sc_consensus::import_queue::ImportQueueService< - substrate_test_runtime_client::runtime::Block, - > for DummyImportQueueHandle - { - fn import_blocks( - &mut self, - _origin: sp_consensus::BlockOrigin, - _blocks: Vec< - sc_consensus::IncomingBlock, - >, - ) { - } - - fn import_justifications( - &mut self, - _who: sc_consensus::import_queue::RuntimeOrigin, - _hash: substrate_test_runtime_client::runtime::Hash, - _number: sp_runtime::traits::NumberFor, - _justifications: sp_runtime::Justifications, - ) { - } - } - - struct DummyImportQueue( - Arc< - RwLock< - Option<( - PeerId, - substrate_test_runtime_client::runtime::Hash, - sp_runtime::traits::NumberFor, - )>, - >, - >, - DummyImportQueueHandle, - ); - - #[async_trait::async_trait] - impl sc_consensus::ImportQueue for DummyImportQueue { - fn poll_actions( - &mut self, - _cx: &mut futures::task::Context, - link: &mut dyn sc_consensus::Link, - ) { - if let Some((peer, hash, number)) = *self.0.read().unwrap() { - link.justification_imported(peer, &hash, number, false); - } - } - - fn service( - &self, - ) -> Box< - dyn sc_consensus::import_queue::ImportQueueService< - substrate_test_runtime_client::runtime::Block, - >, - > { - Box::new(DummyImportQueueHandle {}) - } - - fn service_ref( - &mut self, - ) -> &mut dyn sc_consensus::import_queue::ImportQueueService< - substrate_test_runtime_client::runtime::Block, - > { - &mut self.1 - } - - async fn run( - self, - _link: Box>, - ) { - } - } - - let justification_info = Arc::new(RwLock::new(None)); - let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - - let (service1, mut event_stream1) = TestNetworkBuilder::new() - .with_import_queue(Box::new(DummyImportQueue( - justification_info.clone(), - DummyImportQueueHandle {}, - ))) - .with_listen_addresses(vec![listen_addr.clone()]) - .build() - .start_network(); - - let (service2, mut event_stream2) = TestNetworkBuilder::new() - .with_set_config(SetConfig { - reserved_nodes: vec![MultiaddrWithPeerId { - multiaddr: listen_addr, - peer_id: service1.local_peer_id, - }], - ..Default::default() - }) - .build() - .start_network(); - - async fn wait_for_events(stream: &mut (impl Stream + std::marker::Unpin)) { - let mut notif_received = false; - let mut sync_received = false; - while !notif_received || !sync_received { - match stream.next().await.unwrap() { - Event::NotificationStreamOpened { .. } => notif_received = true, - Event::SyncConnected { .. } => sync_received = true, - _ => {}, - }; - } - } - - wait_for_events(&mut event_stream1).await; - wait_for_events(&mut event_stream2).await; - - { - let mut info = justification_info.write().unwrap(); - *info = Some((service2.local_peer_id, H256::random(), 1337u64)); - } - - let wait_disconnection = async { - while !std::matches!(event_stream1.next().await, Some(Event::SyncDisconnected { .. })) {} - }; - - if tokio::time::timeout(Duration::from_secs(5), wait_disconnection).await.is_err() { - panic!("did not receive disconnection event in time"); - } -} - -#[tokio::test] -async fn disconnect_peer_using_chain_sync_handle() { - let client = Arc::new(TestClientBuilder::with_default_backend().build_with_longest_chain().0); - let listen_addr = config::build_multiaddr![Memory(rand::random::())]; - - let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); - let (chain_sync_network_provider, chain_sync_network_handle) = - sc_network_sync::service::network::NetworkServiceProvider::new(); - let handle_clone = chain_sync_network_handle.clone(); - - let (chain_sync, chain_sync_service, _) = ChainSync::new( - sc_network_common::sync::SyncMode::Full, - client.clone(), - ProtocolId::from("test-protocol-name"), - &Some(String::from("test-fork-id")), - Roles::from(&config::Role::Full), - Box::new(sp_consensus::block_validation::DefaultBlockAnnounceValidator), - 1u32, - None, - None, - chain_sync_network_handle.clone(), - import_queue, - ProtocolName::from("block-request"), - ProtocolName::from("state-request"), - None, - ) - .unwrap(); - - let (node1, mut event_stream1) = TestNetworkBuilder::new() - .with_listen_addresses(vec![listen_addr.clone()]) - .with_chain_sync((Box::new(chain_sync), Box::new(chain_sync_service))) - .with_chain_sync_network((chain_sync_network_provider, chain_sync_network_handle)) - .with_client(client.clone()) - .build() - .start_network(); - - let (node2, mut event_stream2) = TestNetworkBuilder::new() - .with_set_config(SetConfig { - reserved_nodes: vec![MultiaddrWithPeerId { - multiaddr: listen_addr, - peer_id: node1.local_peer_id, - }], - ..Default::default() - }) - .with_client(client.clone()) - .build() - .start_network(); - - async fn wait_for_events(stream: &mut (impl Stream + std::marker::Unpin)) { - let mut notif_received = false; - let mut sync_received = false; - while !notif_received || !sync_received { - match stream.next().await.unwrap() { - Event::NotificationStreamOpened { .. } => notif_received = true, - Event::SyncConnected { .. } => sync_received = true, - _ => {}, - }; - } - } - - wait_for_events(&mut event_stream1).await; - wait_for_events(&mut event_stream2).await; - - handle_clone.disconnect_peer(node2.local_peer_id, BLOCK_ANNOUNCE_PROTO_NAME.into()); - - let wait_disconnection = async { - while !std::matches!(event_stream1.next().await, Some(Event::SyncDisconnected { .. })) {} - }; - - if tokio::time::timeout(Duration::from_secs(5), wait_disconnection).await.is_err() { - panic!("did not receive disconnection event in time"); - } -} diff --git a/client/network/src/service/tests/mod.rs b/client/network/src/service/tests/mod.rs deleted file mode 100644 index 9c97a7f73837..000000000000 --- a/client/network/src/service/tests/mod.rs +++ /dev/null @@ -1,352 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. -// 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 crate::{config, ChainSyncInterface, NetworkService, NetworkWorker}; - -use futures::prelude::*; -use libp2p::Multiaddr; -use sc_client_api::{BlockBackend, HeaderBackend}; -use sc_consensus::{ImportQueue, Link}; -use sc_network_common::{ - config::{ - NonDefaultSetConfig, NonReservedPeerMode, NotificationHandshake, ProtocolId, SetConfig, - TransportConfig, - }, - protocol::{event::Event, role::Roles}, - service::NetworkEventStream, - sync::{message::BlockAnnouncesHandshake, ChainSync as ChainSyncT}, -}; -use sc_network_light::light_client_requests::handler::LightClientRequestHandler; -use sc_network_sync::{ - block_request_handler::BlockRequestHandler, - service::network::{NetworkServiceHandle, NetworkServiceProvider}, - state_request_handler::StateRequestHandler, - ChainSync, -}; -use sp_runtime::traits::{Block as BlockT, Header as _, Zero}; -use std::sync::Arc; -use substrate_test_runtime_client::{ - runtime::{Block as TestBlock, Hash as TestHash}, - TestClient, TestClientBuilder, TestClientBuilderExt as _, -}; - -#[cfg(test)] -mod chain_sync; -#[cfg(test)] -mod service; - -type TestNetworkWorker = NetworkWorker; -type TestNetworkService = NetworkService; - -const BLOCK_ANNOUNCE_PROTO_NAME: &str = "/block-announces"; -const PROTOCOL_NAME: &str = "/foo"; - -struct TestNetwork { - network: TestNetworkWorker, -} - -impl TestNetwork { - pub fn new(network: TestNetworkWorker) -> Self { - Self { network } - } - - pub fn service(&self) -> &Arc { - &self.network.service() - } - - pub fn network(&mut self) -> &mut TestNetworkWorker { - &mut self.network - } - - pub fn start_network( - self, - ) -> (Arc, (impl Stream + std::marker::Unpin)) { - let worker = self.network; - let service = worker.service().clone(); - let event_stream = service.event_stream("test"); - - tokio::spawn(async move { - futures::pin_mut!(worker); - let _ = worker.await; - }); - - (service, event_stream) - } -} - -struct TestNetworkBuilder { - import_queue: Option>>, - link: Option>>, - client: Option>, - listen_addresses: Vec, - set_config: Option, - chain_sync: Option<(Box>, Box>)>, - chain_sync_network: Option<(NetworkServiceProvider, NetworkServiceHandle)>, - config: Option, -} - -impl TestNetworkBuilder { - pub fn new() -> Self { - Self { - import_queue: None, - link: None, - client: None, - listen_addresses: Vec::new(), - set_config: None, - chain_sync: None, - chain_sync_network: None, - config: None, - } - } - - pub fn with_client(mut self, client: Arc) -> Self { - self.client = Some(client); - self - } - - pub fn with_config(mut self, config: config::NetworkConfiguration) -> Self { - self.config = Some(config); - self - } - - pub fn with_listen_addresses(mut self, addresses: Vec) -> Self { - self.listen_addresses = addresses; - self - } - - pub fn with_set_config(mut self, set_config: SetConfig) -> Self { - self.set_config = Some(set_config); - self - } - - pub fn with_chain_sync( - mut self, - chain_sync: (Box>, Box>), - ) -> Self { - self.chain_sync = Some(chain_sync); - self - } - - pub fn with_chain_sync_network( - mut self, - chain_sync_network: (NetworkServiceProvider, NetworkServiceHandle), - ) -> Self { - self.chain_sync_network = Some(chain_sync_network); - self - } - - pub fn with_import_queue(mut self, import_queue: Box>) -> Self { - self.import_queue = Some(import_queue); - self - } - - pub fn build(mut self) -> TestNetwork { - let client = self.client.as_mut().map_or( - Arc::new(TestClientBuilder::with_default_backend().build_with_longest_chain().0), - |v| v.clone(), - ); - - let network_config = self.config.unwrap_or(config::NetworkConfiguration { - extra_sets: vec![NonDefaultSetConfig { - notifications_protocol: PROTOCOL_NAME.into(), - fallback_names: Vec::new(), - max_notification_size: 1024 * 1024, - handshake: None, - set_config: self.set_config.unwrap_or_default(), - }], - listen_addresses: self.listen_addresses, - transport: TransportConfig::MemoryOnly, - ..config::NetworkConfiguration::new_local() - }); - - #[derive(Clone)] - struct PassThroughVerifier(bool); - - #[async_trait::async_trait] - impl sc_consensus::Verifier for PassThroughVerifier { - async fn verify( - &mut self, - mut block: sc_consensus::BlockImportParams, - ) -> Result< - ( - sc_consensus::BlockImportParams, - Option)>>, - ), - String, - > { - let maybe_keys = block - .header - .digest() - .log(|l| { - l.try_as_raw(sp_runtime::generic::OpaqueDigestItemId::Consensus(b"aura")) - .or_else(|| { - l.try_as_raw(sp_runtime::generic::OpaqueDigestItemId::Consensus( - b"babe", - )) - }) - }) - .map(|blob| { - vec![(sp_blockchain::well_known_cache_keys::AUTHORITIES, blob.to_vec())] - }); - - block.finalized = self.0; - block.fork_choice = Some(sc_consensus::ForkChoiceStrategy::LongestChain); - Ok((block, maybe_keys)) - } - } - - let mut import_queue = - self.import_queue.unwrap_or(Box::new(sc_consensus::BasicQueue::new( - PassThroughVerifier(false), - Box::new(client.clone()), - None, - &sp_core::testing::TaskExecutor::new(), - None, - ))); - - let protocol_id = ProtocolId::from("test-protocol-name"); - let fork_id = Some(String::from("test-fork-id")); - - let block_request_protocol_config = { - let (handler, protocol_config) = - BlockRequestHandler::new(&protocol_id, None, client.clone(), 50); - tokio::spawn(handler.run().boxed()); - protocol_config - }; - - let state_request_protocol_config = { - let (handler, protocol_config) = - StateRequestHandler::new(&protocol_id, None, client.clone(), 50); - tokio::spawn(handler.run().boxed()); - protocol_config - }; - - let light_client_request_protocol_config = { - let (handler, protocol_config) = - LightClientRequestHandler::new(&protocol_id, None, client.clone()); - tokio::spawn(handler.run().boxed()); - protocol_config - }; - - let block_announce_config = NonDefaultSetConfig { - notifications_protocol: BLOCK_ANNOUNCE_PROTO_NAME.into(), - fallback_names: vec![], - max_notification_size: 1024 * 1024, - handshake: Some(NotificationHandshake::new(BlockAnnouncesHandshake::< - substrate_test_runtime_client::runtime::Block, - >::build( - Roles::from(&config::Role::Full), - client.info().best_number, - client.info().best_hash, - client - .block_hash(Zero::zero()) - .ok() - .flatten() - .expect("Genesis block exists; qed"), - ))), - set_config: SetConfig { - in_peers: 0, - out_peers: 0, - reserved_nodes: Vec::new(), - non_reserved_mode: NonReservedPeerMode::Deny, - }, - }; - - let (chain_sync_network_provider, chain_sync_network_handle) = - self.chain_sync_network.unwrap_or(NetworkServiceProvider::new()); - - let (chain_sync, chain_sync_service) = self.chain_sync.unwrap_or({ - let (chain_sync, chain_sync_service, _) = ChainSync::new( - match network_config.sync_mode { - config::SyncMode::Full => sc_network_common::sync::SyncMode::Full, - config::SyncMode::Fast { skip_proofs, storage_chain_mode } => - sc_network_common::sync::SyncMode::LightState { - skip_proofs, - storage_chain_mode, - }, - config::SyncMode::Warp => sc_network_common::sync::SyncMode::Warp, - }, - client.clone(), - protocol_id.clone(), - &fork_id, - Roles::from(&config::Role::Full), - Box::new(sp_consensus::block_validation::DefaultBlockAnnounceValidator), - network_config.max_parallel_downloads, - None, - None, - chain_sync_network_handle, - import_queue.service(), - block_request_protocol_config.name.clone(), - state_request_protocol_config.name.clone(), - None, - ) - .unwrap(); - - if let None = self.link { - self.link = Some(Box::new(chain_sync_service.clone())); - } - (Box::new(chain_sync), Box::new(chain_sync_service)) - }); - let mut link = self - .link - .unwrap_or(Box::new(sc_network_sync::service::mock::MockChainSyncInterface::new())); - - let worker = NetworkWorker::< - substrate_test_runtime_client::runtime::Block, - substrate_test_runtime_client::runtime::Hash, - substrate_test_runtime_client::TestClient, - >::new(config::Params { - block_announce_config, - role: config::Role::Full, - executor: Box::new(|f| { - tokio::spawn(f); - }), - network_config, - chain: client.clone(), - protocol_id, - fork_id, - chain_sync, - chain_sync_service, - metrics_registry: None, - request_response_protocol_configs: [ - block_request_protocol_config, - state_request_protocol_config, - light_client_request_protocol_config, - ] - .to_vec(), - }) - .unwrap(); - - let service = worker.service().clone(); - tokio::spawn(async move { - let _ = chain_sync_network_provider.run(service).await; - }); - tokio::spawn(async move { - loop { - futures::future::poll_fn(|cx| { - import_queue.poll_actions(cx, &mut *link); - std::task::Poll::Ready(()) - }) - .await; - tokio::time::sleep(std::time::Duration::from_millis(250)).await; - } - }); - - TestNetwork::new(worker) - } -} diff --git a/client/network/common/src/service.rs b/client/network/src/service/traits.rs similarity index 88% rename from client/network/common/src/service.rs rename to client/network/src/service/traits.rs index 54d254eac384..bebf5a5713c4 100644 --- a/client/network/common/src/service.rs +++ b/client/network/src/service/traits.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. // -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -20,19 +20,19 @@ use crate::{ config::MultiaddrWithPeerId, - protocol::{event::Event, ProtocolName}, + event::Event, request_responses::{IfDisconnected, RequestFailure}, - sync::{warp::WarpSyncProgress, StateDownloadProgress, SyncState}, + service::signature::Signature, + types::ProtocolName, + ReputationChange, }; + use futures::{channel::oneshot, Stream}; -pub use libp2p::{identity::error::SigningError, kad::record::Key as KademliaKey}; use libp2p::{Multiaddr, PeerId}; -use sc_peerset::ReputationChange; -pub use signature::Signature; -use sp_runtime::traits::{Block as BlockT, NumberFor}; + use std::{collections::HashSet, future::Future, pin::Pin, sync::Arc}; -mod signature; +pub use libp2p::{identity::SigningError, kad::record::Key as KademliaKey}; /// Signer with network identity pub trait NetworkSigner { @@ -96,45 +96,33 @@ where /// Overview status of the network. #[derive(Clone)] -pub struct NetworkStatus { - /// Current global sync state. - pub sync_state: SyncState>, - /// Target sync block number. - pub best_seen_block: Option>, - /// Number of peers participating in syncing. - pub num_sync_peers: u32, - /// Total number of connected peers +pub struct NetworkStatus { + /// Total number of connected peers. pub num_connected_peers: usize, - /// Total number of active peers. - pub num_active_peers: usize, /// The total number of bytes received. pub total_bytes_inbound: u64, /// The total number of bytes sent. pub total_bytes_outbound: u64, - /// State sync in progress. - pub state_sync: Option, - /// Warp sync in progress. - pub warp_sync: Option>, } /// Provides high-level status information about network. #[async_trait::async_trait] -pub trait NetworkStatusProvider { +pub trait NetworkStatusProvider { /// High-level network status information. /// /// Returns an error if the `NetworkWorker` is no longer running. - async fn status(&self) -> Result, ()>; + async fn status(&self) -> Result; } // Manual implementation to avoid extra boxing here -impl NetworkStatusProvider for Arc +impl NetworkStatusProvider for Arc where T: ?Sized, - T: NetworkStatusProvider, + T: NetworkStatusProvider, { fn status<'life0, 'async_trait>( &'life0 self, - ) -> Pin, ()>> + Send + 'async_trait>> + ) -> Pin> + Send + 'async_trait>> where 'life0: 'async_trait, Self: 'async_trait, @@ -167,10 +155,6 @@ pub trait NetworkPeers { /// Disconnect from a node as soon as possible. /// /// This triggers the same effects as if the connection had closed itself spontaneously. - /// - /// See also [`NetworkPeers::remove_from_peers_set`], which has the same effect but also - /// prevents the local node from re-establishing an outgoing substream to this peer until it - /// is added again. fn disconnect_peer(&self, who: PeerId, protocol: ProtocolName); /// Connect to unreserved peers and allow unreserved peers to connect for syncing purposes. @@ -180,13 +164,13 @@ pub trait NetworkPeers { /// purposes. fn deny_unreserved_peers(&self); - /// Adds a `PeerId` and its `Multiaddr` as reserved. + /// Adds a `PeerId` and its `Multiaddr` as reserved for a sync protocol (default peer set). /// /// Returns an `Err` if the given string is not a valid multiaddress /// or contains an invalid peer ID (which includes the local peer ID). fn add_reserved_peer(&self, peer: MultiaddrWithPeerId) -> Result<(), String>; - /// Removes a `PeerId` from the list of reserved peers. + /// Removes a `PeerId` from the list of reserved peers for a sync protocol (default peer set). fn remove_reserved_peer(&self, peer_id: PeerId); /// Sets the reserved set of a protocol to the given set of peers. @@ -227,26 +211,6 @@ pub trait NetworkPeers { /// Remove peers from a peer set. fn remove_peers_from_reserved_set(&self, protocol: ProtocolName, peers: Vec); - /// Add a peer to a set of peers. - /// - /// If the set has slots available, it will try to open a substream with this peer. - /// - /// Each `Multiaddr` must end with a `/p2p/` component containing the `PeerId`. It can also - /// consist of only `/p2p/`. - /// - /// Returns an `Err` if one of the given addresses is invalid or contains an - /// invalid peer ID (which includes the local peer ID). - fn add_to_peers_set( - &self, - protocol: ProtocolName, - peers: HashSet, - ) -> Result<(), String>; - - /// Remove peers from a peer set. - /// - /// If we currently have an open substream with this peer, it will soon be closed. - fn remove_from_peers_set(&self, protocol: ProtocolName, peers: Vec); - /// Returns the number of peers in the sync peer set we're connected to. fn sync_num_connected(&self) -> usize; } @@ -270,6 +234,10 @@ where } fn report_peer(&self, who: PeerId, cost_benefit: ReputationChange) { + // TODO: when we get rid of `Peerset`, we'll likely need to add some kind of async + // interface to `PeerStore`, otherwise we'll have trouble calling functions accepting + // `&mut self` via `Arc`. + // See https://github.com/paritytech/substrate/issues/14170. T::report_peer(self, who, cost_benefit) } @@ -313,18 +281,6 @@ where T::remove_peers_from_reserved_set(self, protocol, peers) } - fn add_to_peers_set( - &self, - protocol: ProtocolName, - peers: HashSet, - ) -> Result<(), String> { - T::add_to_peers_set(self, protocol, peers) - } - - fn remove_from_peers_set(&self, protocol: ProtocolName, peers: Vec) { - T::remove_from_peers_set(self, protocol, peers) - } - fn sync_num_connected(&self) -> usize { T::sync_num_connected(self) } @@ -359,6 +315,9 @@ pub trait NetworkStateInfo { /// Returns the local external addresses. fn external_addresses(&self) -> Vec; + /// Returns the listening addresses (without trailing `/p2p/` with our `PeerId`). + fn listen_addresses(&self) -> Vec; + /// Returns the local Peer ID. fn local_peer_id(&self) -> PeerId; } @@ -372,6 +331,10 @@ where T::external_addresses(self) } + fn listen_addresses(&self) -> Vec { + T::listen_addresses(self) + } + fn local_peer_id(&self) -> PeerId { T::local_peer_id(self) } @@ -439,9 +402,9 @@ pub trait NetworkNotification { /// a receiver. With a `NotificationSender` at hand, sending a notification is done in two /// steps: /// - /// 1. [`NotificationSender::ready`] is used to wait for the sender to become ready + /// 1. [`NotificationSender::ready`] is used to wait for the sender to become ready /// for another notification, yielding a [`NotificationSenderReady`] token. - /// 2. [`NotificationSenderReady::send`] enqueues the notification for sending. This operation + /// 2. [`NotificationSenderReady::send`] enqueues the notification for sending. This operation /// can only fail if the underlying notification substream or connection has suddenly closed. /// /// An error is returned by [`NotificationSenderReady::send`] if there exists no open @@ -504,6 +467,9 @@ pub trait NetworkNotification { target: PeerId, protocol: ProtocolName, ) -> Result, NotificationSenderError>; + + /// Set handshake for the notification protocol. + fn set_notification_handshake(&self, protocol: ProtocolName, handshake: Vec); } impl NetworkNotification for Arc @@ -522,6 +488,10 @@ where ) -> Result, NotificationSenderError> { T::notification_sender(self, target, protocol) } + + fn set_notification_handshake(&self, protocol: ProtocolName, handshake: Vec) { + T::set_notification_handshake(self, protocol, handshake) + } } /// Provides ability to send network requests. diff --git a/client/network/src/transport.rs b/client/network/src/transport.rs index 51a8483e0e4e..4136b34fc0e8 100644 --- a/client/network/src/transport.rs +++ b/client/network/src/transport.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,20 +16,20 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +//! Transport that serves as a common ground for all connections. + +use either::Either; use libp2p::{ - bandwidth, core::{ - self, - either::EitherTransport, muxing::StreamMuxerBox, transport::{Boxed, OptionalTransport}, upgrade, }, - dns, identity, mplex, noise, tcp, websocket, PeerId, Transport, + dns, identity, noise, tcp, websocket, PeerId, Transport, TransportExt, }; use std::{sync::Arc, time::Duration}; -pub use self::bandwidth::BandwidthSinks; +pub use libp2p::bandwidth::BandwidthSinks; /// Builds the transport that serves as a common ground for all connections. /// @@ -59,7 +59,7 @@ pub fn build_transport( let tcp_trans = tcp::tokio::Transport::new(tcp_config.clone()); let dns_init = dns::TokioDnsConfig::system(tcp_trans); - EitherTransport::Left(if let Ok(dns) = dns_init { + Either::Left(if let Ok(dns) = dns_init { // WS + WSS transport // // Main transport can't be used for `/wss` addresses because WSS transport needs @@ -68,47 +68,21 @@ pub fn build_transport( let tcp_trans = tcp::tokio::Transport::new(tcp_config); let dns_for_wss = dns::TokioDnsConfig::system(tcp_trans) .expect("same system_conf & resolver to work"); - EitherTransport::Left(websocket::WsConfig::new(dns_for_wss).or_transport(dns)) + Either::Left(websocket::WsConfig::new(dns_for_wss).or_transport(dns)) } else { // In case DNS can't be constructed, fallback to TCP + WS (WSS won't work) let tcp_trans = tcp::tokio::Transport::new(tcp_config.clone()); let desktop_trans = websocket::WsConfig::new(tcp_trans) .or_transport(tcp::tokio::Transport::new(tcp_config)); - EitherTransport::Right(desktop_trans) + Either::Right(desktop_trans) }) } else { - EitherTransport::Right(OptionalTransport::some( - libp2p::core::transport::MemoryTransport::default(), - )) + Either::Right(OptionalTransport::some(libp2p::core::transport::MemoryTransport::default())) }; - let (transport, bandwidth) = bandwidth::BandwidthLogging::new(transport); - - let authentication_config = - { - // For more information about these two panics, see in "On the Importance of - // Checking Cryptographic Protocols for Faults" by Dan Boneh, Richard A. DeMillo, - // and Richard J. Lipton. - let noise_keypair = noise::Keypair::::new().into_authentic(&keypair) - .expect("can only fail in case of a hardware bug; since this signing is performed only \ - once and at initialization, we're taking the bet that the inconvenience of a very \ - rare panic here is basically zero"); - - // Legacy noise configurations for backward compatibility. - let noise_legacy = - noise::LegacyConfig { recv_legacy_handshake: true, ..Default::default() }; - - let mut xx_config = noise::NoiseConfig::xx(noise_keypair); - xx_config.set_legacy_config(noise_legacy); - xx_config.into_authenticated() - }; - + let authentication_config = noise::Config::new(&keypair).expect("Can create noise config. qed"); let multiplexing_config = { - let mut mplex_config = mplex::MplexConfig::new(); - mplex_config.set_max_buffer_behaviour(mplex::MaxBufferBehaviour::Block); - mplex_config.set_max_buffer_size(usize::MAX); - - let mut yamux_config = libp2p::yamux::YamuxConfig::default(); + let mut yamux_config = libp2p::yamux::Config::default(); // Enable proper flow-control: window updates are only sent when // buffered data has been consumed. yamux_config.set_window_update_mode(libp2p::yamux::WindowUpdateMode::on_read()); @@ -118,7 +92,7 @@ pub fn build_transport( yamux_config.set_receive_window_size(yamux_window_size); } - core::upgrade::SelectUpgrade::new(yamux_config, mplex_config) + yamux_config }; let transport = transport @@ -128,5 +102,5 @@ pub fn build_transport( .timeout(Duration::from_secs(20)) .boxed(); - (transport, bandwidth) + transport.with_bandwidth_logging() } diff --git a/client/network/common/src/protocol.rs b/client/network/src/types.rs similarity index 97% rename from client/network/common/src/protocol.rs rename to client/network/src/types.rs index 04bfaedbcac7..b0e32ae10914 100644 --- a/client/network/common/src/protocol.rs +++ b/client/network/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,6 +16,10 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +//! `sc-network` type definitions + +use libp2p::core::upgrade; + use std::{ borrow::Borrow, fmt, @@ -24,11 +28,6 @@ use std::{ sync::Arc, }; -use libp2p::core::upgrade; - -pub mod event; -pub mod role; - /// The protocol name transmitted on the wire. #[derive(Debug, Clone)] pub enum ProtocolName { diff --git a/client/network/common/src/utils.rs b/client/network/src/utils.rs similarity index 97% rename from client/network/common/src/utils.rs rename to client/network/src/utils.rs index d0e61a0d0475..8db2cf4e7920 100644 --- a/client/network/common/src/utils.rs +++ b/client/network/src/utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,9 +16,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +//! `sc-network` utilities + use futures::{stream::unfold, FutureExt, Stream, StreamExt}; use futures_timer::Delay; use linked_hash_set::LinkedHashSet; + use std::{hash::Hash, num::NonZeroUsize, time::Duration}; /// Creates a stream that returns a new value every `duration`. diff --git a/client/network/statement/Cargo.toml b/client/network/statement/Cargo.toml new file mode 100644 index 000000000000..63377710dc40 --- /dev/null +++ b/client/network/statement/Cargo.toml @@ -0,0 +1,26 @@ +[package] +description = "Substrate statement protocol" +name = "sc-network-statement" +version = "0.10.0-dev" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +authors = ["Parity Technologies "] +edition = "2021" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +documentation = "https://docs.rs/sc-network-statement" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +array-bytes = "6.1" +async-channel = "1.8.0" +codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } +futures = "0.3.21" +libp2p = "0.51.3" +log = "0.4.17" +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } +sc-network-common = { version = "0.10.0-dev", path = "../common" } +sc-network = { version = "0.10.0-dev", path = "../" } +sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } +sp-statement-store = { version = "4.0.0-dev", path = "../../../primitives/statement-store" } diff --git a/client/network/statement/src/config.rs b/client/network/statement/src/config.rs new file mode 100644 index 000000000000..159998a0fe30 --- /dev/null +++ b/client/network/statement/src/config.rs @@ -0,0 +1,33 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +//! Configuration of the statement protocol + +use std::time; + +/// Interval at which we propagate statements; +pub(crate) const PROPAGATE_TIMEOUT: time::Duration = time::Duration::from_millis(1000); + +/// Maximum number of known statement hashes to keep for a peer. +pub(crate) const MAX_KNOWN_STATEMENTS: usize = 10240; + +/// Maximum allowed size for a statement notification. +pub(crate) const MAX_STATEMENT_SIZE: u64 = 256 * 1024; + +/// Maximum number of statement validation request we keep at any moment. +pub(crate) const MAX_PENDING_STATEMENTS: usize = 8192; diff --git a/client/network/statement/src/lib.rs b/client/network/statement/src/lib.rs new file mode 100644 index 000000000000..2c966a346ad8 --- /dev/null +++ b/client/network/statement/src/lib.rs @@ -0,0 +1,486 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +//! Statement handling to plug on top of the network service. +//! +//! Usage: +//! +//! - Use [`StatementHandlerPrototype::new`] to create a prototype. +//! - Pass the return value of [`StatementHandlerPrototype::set_config`] to the network +//! configuration as an extra peers set. +//! - Use [`StatementHandlerPrototype::build`] then [`StatementHandler::run`] to obtain a +//! `Future` that processes statements. + +use crate::config::*; +use codec::{Decode, Encode}; +use futures::{channel::oneshot, prelude::*, stream::FuturesUnordered, FutureExt}; +use libp2p::{multiaddr, PeerId}; +use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; +use sc_network::{ + config::{NonDefaultSetConfig, NonReservedPeerMode, SetConfig}, + error, + event::Event, + types::ProtocolName, + utils::{interval, LruHashSet}, + NetworkEventStream, NetworkNotification, NetworkPeers, +}; +use sc_network_common::{ + role::ObservedRole, + sync::{SyncEvent, SyncEventStream}, +}; +use sp_statement_store::{ + Hash, NetworkPriority, Statement, StatementSource, StatementStore, SubmitResult, +}; +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + iter, + num::NonZeroUsize, + pin::Pin, + sync::Arc, +}; + +pub mod config; + +/// A set of statements. +pub type Statements = Vec; +/// Future resolving to statement import result. +pub type StatementImportFuture = oneshot::Receiver; + +mod rep { + use sc_network::ReputationChange as Rep; + /// Reputation change when a peer sends us any statement. + /// + /// This forces node to verify it, thus the negative value here. Once statement is verified, + /// reputation change should be refunded with `ANY_STATEMENT_REFUND` + pub const ANY_STATEMENT: Rep = Rep::new(-(1 << 4), "Any statement"); + /// Reputation change when a peer sends us any statement that is not invalid. + pub const ANY_STATEMENT_REFUND: Rep = Rep::new(1 << 4, "Any statement (refund)"); + /// Reputation change when a peer sends us an statement that we didn't know about. + pub const GOOD_STATEMENT: Rep = Rep::new(1 << 7, "Good statement"); + /// Reputation change when a peer sends us a bad statement. + pub const BAD_STATEMENT: Rep = Rep::new(-(1 << 12), "Bad statement"); + /// Reputation change when a peer sends us a duplicate statement. + pub const DUPLICATE_STATEMENT: Rep = Rep::new(-(1 << 7), "Duplicate statement"); + /// Reputation change when a peer sends us particularly useful statement + pub const EXCELLENT_STATEMENT: Rep = Rep::new(1 << 8, "High priority statement"); +} + +const LOG_TARGET: &str = "statement-gossip"; + +struct Metrics { + propagated_statements: Counter, +} + +impl Metrics { + fn register(r: &Registry) -> Result { + Ok(Self { + propagated_statements: register( + Counter::new( + "substrate_sync_propagated_statements", + "Number of statements propagated to at least one peer", + )?, + r, + )?, + }) + } +} + +/// Prototype for a [`StatementHandler`]. +pub struct StatementHandlerPrototype { + protocol_name: ProtocolName, +} + +impl StatementHandlerPrototype { + /// Create a new instance. + pub fn new>(genesis_hash: Hash, fork_id: Option<&str>) -> Self { + let genesis_hash = genesis_hash.as_ref(); + let protocol_name = if let Some(fork_id) = fork_id { + format!("/{}/{}/statement/1", array_bytes::bytes2hex("", genesis_hash), fork_id) + } else { + format!("/{}/statement/1", array_bytes::bytes2hex("", genesis_hash)) + }; + + Self { protocol_name: protocol_name.into() } + } + + /// Returns the configuration of the set to put in the network configuration. + pub fn set_config(&self) -> NonDefaultSetConfig { + NonDefaultSetConfig { + notifications_protocol: self.protocol_name.clone(), + fallback_names: Vec::new(), + max_notification_size: MAX_STATEMENT_SIZE, + handshake: None, + set_config: SetConfig { + in_peers: 0, + out_peers: 0, + reserved_nodes: Vec::new(), + non_reserved_mode: NonReservedPeerMode::Deny, + }, + } + } + + /// Turns the prototype into the actual handler. + /// + /// Important: the statements handler is initially disabled and doesn't gossip statements. + /// Gossiping is enabled when major syncing is done. + pub fn build< + N: NetworkPeers + NetworkEventStream + NetworkNotification, + S: SyncEventStream + sp_consensus::SyncOracle, + >( + self, + network: N, + sync: S, + statement_store: Arc, + metrics_registry: Option<&Registry>, + executor: impl Fn(Pin + Send>>) + Send, + ) -> error::Result> { + let net_event_stream = network.event_stream("statement-handler-net"); + let sync_event_stream = sync.event_stream("statement-handler-sync"); + let (queue_sender, mut queue_receiver) = async_channel::bounded(100_000); + + let store = statement_store.clone(); + executor( + async move { + loop { + let task: Option<(Statement, oneshot::Sender)> = + queue_receiver.next().await; + match task { + None => return, + Some((statement, completion)) => { + let result = store.submit(statement, StatementSource::Network); + if completion.send(result).is_err() { + log::debug!( + target: LOG_TARGET, + "Error sending validation completion" + ); + } + }, + } + } + } + .boxed(), + ); + + let handler = StatementHandler { + protocol_name: self.protocol_name, + propagate_timeout: (Box::pin(interval(PROPAGATE_TIMEOUT)) + as Pin + Send>>) + .fuse(), + pending_statements: FuturesUnordered::new(), + pending_statements_peers: HashMap::new(), + network, + sync, + net_event_stream: net_event_stream.fuse(), + sync_event_stream: sync_event_stream.fuse(), + peers: HashMap::new(), + statement_store, + queue_sender, + metrics: if let Some(r) = metrics_registry { + Some(Metrics::register(r)?) + } else { + None + }, + }; + + Ok(handler) + } +} + +/// Handler for statements. Call [`StatementHandler::run`] to start the processing. +pub struct StatementHandler< + N: NetworkPeers + NetworkEventStream + NetworkNotification, + S: SyncEventStream + sp_consensus::SyncOracle, +> { + protocol_name: ProtocolName, + /// Interval at which we call `propagate_statements`. + propagate_timeout: stream::Fuse + Send>>>, + /// Pending statements verification tasks. + pending_statements: + FuturesUnordered)> + Send>>>, + /// As multiple peers can send us the same statement, we group + /// these peers using the statement hash while the statement is + /// imported. This prevents that we import the same statement + /// multiple times concurrently. + pending_statements_peers: HashMap>, + /// Network service to use to send messages and manage peers. + network: N, + /// Syncing service. + sync: S, + /// Stream of networking events. + net_event_stream: stream::Fuse + Send>>>, + /// Receiver for syncing-related events. + sync_event_stream: stream::Fuse + Send>>>, + // All connected peers + peers: HashMap, + statement_store: Arc, + queue_sender: async_channel::Sender<(Statement, oneshot::Sender)>, + /// Prometheus metrics. + metrics: Option, +} + +/// Peer information +#[derive(Debug)] +struct Peer { + /// Holds a set of statements known to this peer. + known_statements: LruHashSet, + role: ObservedRole, +} + +impl StatementHandler +where + N: NetworkPeers + NetworkEventStream + NetworkNotification, + S: SyncEventStream + sp_consensus::SyncOracle, +{ + /// Turns the [`StatementHandler`] into a future that should run forever and not be + /// interrupted. + pub async fn run(mut self) { + loop { + futures::select! { + _ = self.propagate_timeout.next() => { + self.propagate_statements(); + }, + (hash, result) = self.pending_statements.select_next_some() => { + if let Some(peers) = self.pending_statements_peers.remove(&hash) { + if let Some(result) = result { + peers.into_iter().for_each(|p| self.on_handle_statement_import(p, &result)); + } + } else { + log::warn!(target: LOG_TARGET, "Inconsistent state, no peers for pending statement!"); + } + }, + network_event = self.net_event_stream.next() => { + if let Some(network_event) = network_event { + self.handle_network_event(network_event).await; + } else { + // Networking has seemingly closed. Closing as well. + return; + } + }, + sync_event = self.sync_event_stream.next() => { + if let Some(sync_event) = sync_event { + self.handle_sync_event(sync_event); + } else { + // Syncing has seemingly closed. Closing as well. + return; + } + } + } + } + } + + fn handle_sync_event(&mut self, event: SyncEvent) { + match event { + SyncEvent::PeerConnected(remote) => { + let addr = iter::once(multiaddr::Protocol::P2p(remote.into())) + .collect::(); + let result = self.network.add_peers_to_reserved_set( + self.protocol_name.clone(), + iter::once(addr).collect(), + ); + if let Err(err) = result { + log::error!(target: LOG_TARGET, "Add reserved peer failed: {}", err); + } + }, + SyncEvent::PeerDisconnected(remote) => { + self.network.remove_peers_from_reserved_set( + self.protocol_name.clone(), + iter::once(remote).collect(), + ); + }, + } + } + + async fn handle_network_event(&mut self, event: Event) { + match event { + Event::Dht(_) => {}, + Event::NotificationStreamOpened { remote, protocol, role, .. } + if protocol == self.protocol_name => + { + let _was_in = self.peers.insert( + remote, + Peer { + known_statements: LruHashSet::new( + NonZeroUsize::new(MAX_KNOWN_STATEMENTS).expect("Constant is nonzero"), + ), + role, + }, + ); + debug_assert!(_was_in.is_none()); + }, + Event::NotificationStreamClosed { remote, protocol } + if protocol == self.protocol_name => + { + let _peer = self.peers.remove(&remote); + debug_assert!(_peer.is_some()); + }, + + Event::NotificationsReceived { remote, messages } => { + for (protocol, message) in messages { + if protocol != self.protocol_name { + continue + } + // Accept statements only when node is not major syncing + if self.sync.is_major_syncing() { + log::trace!( + target: LOG_TARGET, + "{remote}: Ignoring statements while major syncing or offline" + ); + continue + } + if let Ok(statements) = ::decode(&mut message.as_ref()) { + self.on_statements(remote, statements); + } else { + log::debug!( + target: LOG_TARGET, + "Failed to decode statement list from {remote}" + ); + } + } + }, + + // Not our concern. + Event::NotificationStreamOpened { .. } | Event::NotificationStreamClosed { .. } => {}, + } + } + + /// Called when peer sends us new statements + fn on_statements(&mut self, who: PeerId, statements: Statements) { + log::trace!(target: LOG_TARGET, "Received {} statements from {}", statements.len(), who); + if let Some(ref mut peer) = self.peers.get_mut(&who) { + for s in statements { + if self.pending_statements.len() > MAX_PENDING_STATEMENTS { + log::debug!( + target: LOG_TARGET, + "Ignoring any further statements that exceed `MAX_PENDING_STATEMENTS`({}) limit", + MAX_PENDING_STATEMENTS, + ); + break + } + + let hash = s.hash(); + peer.known_statements.insert(hash); + + self.network.report_peer(who, rep::ANY_STATEMENT); + + match self.pending_statements_peers.entry(hash) { + Entry::Vacant(entry) => { + let (completion_sender, completion_receiver) = oneshot::channel(); + match self.queue_sender.try_send((s, completion_sender)) { + Ok(()) => { + self.pending_statements.push( + async move { + let res = completion_receiver.await; + (hash, res.ok()) + } + .boxed(), + ); + entry.insert(HashSet::from_iter([who])); + }, + Err(async_channel::TrySendError::Full(_)) => { + log::debug!( + target: LOG_TARGET, + "Dropped statement because validation channel is full", + ); + }, + Err(async_channel::TrySendError::Closed(_)) => { + log::trace!( + target: LOG_TARGET, + "Dropped statement because validation channel is closed", + ); + }, + } + }, + Entry::Occupied(mut entry) => { + if !entry.get_mut().insert(who) { + // Already received this from the same peer. + self.network.report_peer(who, rep::DUPLICATE_STATEMENT); + } + }, + } + } + } + } + + fn on_handle_statement_import(&mut self, who: PeerId, import: &SubmitResult) { + match import { + SubmitResult::New(NetworkPriority::High) => + self.network.report_peer(who, rep::EXCELLENT_STATEMENT), + SubmitResult::New(NetworkPriority::Low) => + self.network.report_peer(who, rep::GOOD_STATEMENT), + SubmitResult::Known => self.network.report_peer(who, rep::ANY_STATEMENT_REFUND), + SubmitResult::KnownExpired => {}, + SubmitResult::Ignored => {}, + SubmitResult::Bad(_) => self.network.report_peer(who, rep::BAD_STATEMENT), + SubmitResult::InternalError(_) => {}, + } + } + + /// Propagate one statement. + pub fn propagate_statement(&mut self, hash: &Hash) { + // Accept statements only when node is not major syncing + if self.sync.is_major_syncing() { + return + } + + log::debug!(target: LOG_TARGET, "Propagating statement [{:?}]", hash); + if let Ok(Some(statement)) = self.statement_store.statement(hash) { + self.do_propagate_statements(&[(*hash, statement)]); + } + } + + fn do_propagate_statements(&mut self, statements: &[(Hash, Statement)]) { + let mut propagated_statements = 0; + + for (who, peer) in self.peers.iter_mut() { + // never send statements to light nodes + if matches!(peer.role, ObservedRole::Light) { + continue + } + + let to_send = statements + .iter() + .filter_map(|(hash, stmt)| peer.known_statements.insert(*hash).then(|| stmt)) + .collect::>(); + + propagated_statements += to_send.len(); + + if !to_send.is_empty() { + log::trace!(target: LOG_TARGET, "Sending {} statements to {}", to_send.len(), who); + self.network + .write_notification(*who, self.protocol_name.clone(), to_send.encode()); + } + } + + if let Some(ref metrics) = self.metrics { + metrics.propagated_statements.inc_by(propagated_statements as _) + } + } + + /// Call when we must propagate ready statements to peers. + fn propagate_statements(&mut self) { + // Send out statements only when node is not major syncing + if self.sync.is_major_syncing() { + return + } + + log::debug!(target: LOG_TARGET, "Propagating statements"); + if let Ok(statements) = self.statement_store.statements() { + self.do_propagate_statements(&statements); + } + } +} diff --git a/client/network/sync/Cargo.toml b/client/network/sync/Cargo.toml index ff72b4e99302..1feb1316dbcd 100644 --- a/client/network/sync/Cargo.toml +++ b/client/network/sync/Cargo.toml @@ -16,35 +16,37 @@ targets = ["x86_64-unknown-linux-gnu"] prost-build = "0.11" [dependencies] -array-bytes = "4.1" +array-bytes = "6.1" +async-channel = "1.8.0" async-trait = "0.1.58" -codec = { package = "parity-scale-codec", version = "3.2.2", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" -libp2p = "0.50.0" +futures-timer = "3.0.2" +libp2p = "0.51.3" log = "0.4.17" -lru = "0.8.1" -mockall = "0.11.2" +mockall = "0.11.3" prost = "0.11" +schnellru = "0.2.1" smallvec = "1.8.0" thiserror = "1.0" fork-tree = { version = "3.0.0", path = "../../../utils/fork-tree" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } +sc-network = { version = "0.10.0-dev", path = "../" } sc-network-common = { version = "0.10.0-dev", path = "../common" } -sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } sc-utils = { version = "4.0.0-dev", path = "../../utils" } -sp-arithmetic = { version = "6.0.0", path = "../../../primitives/arithmetic" } +sp-arithmetic = { version = "16.0.0", path = "../../../primitives/arithmetic" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-finality-grandpa = { version = "4.0.0-dev", path = "../../../primitives/finality-grandpa" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-consensus-grandpa = { version = "4.0.0-dev", path = "../../../primitives/consensus/grandpa" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } [dev-dependencies] tokio = { version = "1.22.0", features = ["macros"] } quickcheck = { version = "1.0.3", default-features = false } sc-block-builder = { version = "0.10.0-dev", path = "../../block-builder" } sp-test-primitives = { version = "2.0.0", path = "../../../primitives/test-primitives" } -sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } +sp-tracing = { version = "10.0.0", path = "../../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/client/network/sync/src/block_request_handler.rs b/client/network/sync/src/block_request_handler.rs index 84755453fbea..1c576c618861 100644 --- a/client/network/sync/src/block_request_handler.rs +++ b/client/network/sync/src/block_request_handler.rs @@ -1,4 +1,4 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify @@ -17,42 +17,43 @@ //! Helper for handling (i.e. answering) block requests from a remote peer via the //! `crate::request_responses::RequestResponsesBehaviour`. -use crate::schema::v1::{block_request::FromBlock, BlockResponse, Direction}; -use codec::{Decode, Encode}; -use futures::{ - channel::{mpsc, oneshot}, - stream::StreamExt, +use crate::{ + schema::v1::{block_request::FromBlock, BlockResponse, Direction}, + MAX_BLOCKS_IN_RESPONSE, }; + +use codec::{Decode, Encode}; +use futures::{channel::oneshot, stream::StreamExt}; use libp2p::PeerId; use log::debug; -use lru::LruCache; use prost::Message; +use schnellru::{ByLength, LruMap}; + use sc_client_api::BlockBackend; -use sc_network_common::{ +use sc_network::{ config::ProtocolId, request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, - sync::message::BlockAttributes, }; +use sc_network_common::sync::message::BlockAttributes; use sp_blockchain::HeaderBackend; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Header, One, Zero}, }; + use std::{ cmp::min, hash::{Hash, Hasher}, - num::NonZeroUsize, sync::Arc, time::Duration, }; const LOG_TARGET: &str = "sync"; -const MAX_BLOCKS_IN_RESPONSE: usize = 128; const MAX_BODY_BYTES: usize = 8 * 1024 * 1024; const MAX_NUMBER_OF_SAME_REQUESTS_PER_PEER: usize = 2; mod rep { - use sc_peerset::ReputationChange as Rep; + use sc_network::ReputationChange as Rep; /// Reputation change when a peer sent us the same request multiple times. pub const SAME_REQUEST: Rep = Rep::new_fatal("Same block request multiple times"); @@ -105,7 +106,7 @@ struct SeenRequestsKey { support_multiple_justifications: bool, } -#[allow(clippy::derive_hash_xor_eq)] +#[allow(clippy::derived_hash_with_manual_eq)] impl Hash for SeenRequestsKey { fn hash(&self, state: &mut H) { self.peer.hash(state); @@ -131,11 +132,11 @@ enum SeenRequestsValue { /// Handler for incoming block requests from a remote peer. pub struct BlockRequestHandler { client: Arc, - request_receiver: mpsc::Receiver, + request_receiver: async_channel::Receiver, /// Maps from request to number of times we have seen this request. /// /// This is used to check if a peer is spamming us with the same request. - seen_requests: LruCache, SeenRequestsValue>, + seen_requests: LruMap, SeenRequestsValue>, } impl BlockRequestHandler @@ -152,7 +153,7 @@ where ) -> (Self, ProtocolConfig) { // Reserve enough request slots for one request per peer when we are at the maximum // number of peers. - let (tx, request_receiver) = mpsc::channel(num_peer_hint); + let (tx, request_receiver) = async_channel::bounded(num_peer_hint); let mut protocol_config = generate_protocol_config( protocol_id, @@ -165,9 +166,8 @@ where ); protocol_config.inbound_queue = Some(tx); - let capacity = - NonZeroUsize::new(num_peer_hint.max(1) * 2).expect("cache capacity is not zero"); - let seen_requests = LruCache::new(capacity); + let capacity = ByLength::new(num_peer_hint.max(1) as u32 * 2); + let seen_requests = LruMap::new(capacity); (Self { client, request_receiver, seen_requests }, protocol_config) } @@ -234,7 +234,7 @@ where .difference(BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION) .is_empty(); - match self.seen_requests.get_mut(&key) { + match self.seen_requests.get(&key) { Some(SeenRequestsValue::First) => {}, Some(SeenRequestsValue::Fulfilled(ref mut requests)) => { *requests = requests.saturating_add(1); @@ -248,7 +248,7 @@ where } }, None => { - self.seen_requests.put(key.clone(), SeenRequestsValue::First); + self.seen_requests.insert(key.clone(), SeenRequestsValue::First); }, } @@ -275,7 +275,7 @@ where .iter() .any(|b| !b.header.is_empty() || !b.body.is_empty() || b.is_empty_justification) { - if let Some(value) = self.seen_requests.get_mut(&key) { + if let Some(value) = self.seen_requests.get(&key) { // If this is the first time we have processed this request, we need to change // it to `Fulfilled`. if let SeenRequestsValue::First = value { diff --git a/client/network/sync/src/blocks.rs b/client/network/sync/src/blocks.rs index b8acd61a2009..240c1ca1f8b2 100644 --- a/client/network/sync/src/blocks.rs +++ b/client/network/sync/src/blocks.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -89,7 +89,7 @@ impl BlockCollection { Some(&BlockRangeState::Downloading { .. }) => { trace!(target: "sync", "Inserting block data still marked as being downloaded: {}", start); }, - Some(&BlockRangeState::Complete(ref existing)) if existing.len() >= blocks.len() => { + Some(BlockRangeState::Complete(existing)) if existing.len() >= blocks.len() => { trace!(target: "sync", "Ignored block data already downloaded: {}", start); return }, @@ -109,7 +109,7 @@ impl BlockCollection { pub fn needed_blocks( &mut self, who: PeerId, - count: usize, + count: u32, peer_best: NumberFor, common: NumberFor, max_parallel: u32, diff --git a/client/network/sync/src/engine.rs b/client/network/sync/src/engine.rs new file mode 100644 index 000000000000..1c57e4a27597 --- /dev/null +++ b/client/network/sync/src/engine.rs @@ -0,0 +1,920 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-2023 Parity Technologies (UK) Ltd. +// 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 . + +//! `SyncingEngine` is the actor responsible for syncing Substrate chain +//! to tip and keep the blockchain up to date with network updates. + +use crate::{ + service::{self, chain_sync::ToServiceCommand}, + ChainSync, ClientError, SyncingService, +}; + +use codec::{Decode, Encode}; +use futures::{FutureExt, StreamExt}; +use futures_timer::Delay; +use libp2p::PeerId; +use prometheus_endpoint::{ + register, Gauge, GaugeVec, MetricSource, Opts, PrometheusError, Registry, SourcedGauge, U64, +}; +use schnellru::{ByLength, LruMap}; + +use sc_client_api::{BlockBackend, HeaderBackend, ProofProvider}; +use sc_consensus::import_queue::ImportQueueService; +use sc_network::{ + config::{FullNetworkConfiguration, NonDefaultSetConfig, ProtocolId}, + utils::LruHashSet, + NotificationsSink, ProtocolName, ReputationChange, +}; +use sc_network_common::{ + role::Roles, + sync::{ + message::{BlockAnnounce, BlockAnnouncesHandshake, BlockState}, + warp::WarpSyncParams, + BadPeer, ChainSync as ChainSyncT, ExtendedPeerInfo, PollBlockAnnounceValidation, SyncEvent, + }, +}; +use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; +use sp_blockchain::HeaderMetadata; +use sp_consensus::block_validation::BlockAnnounceValidator; +use sp_runtime::traits::{Block as BlockT, Header, NumberFor, Zero}; + +use std::{ + collections::{HashMap, HashSet}, + num::NonZeroUsize, + sync::{ + atomic::{AtomicBool, AtomicUsize, Ordering}, + Arc, + }, + task::Poll, + time::{Duration, Instant}, +}; + +/// Interval at which we perform time based maintenance +const TICK_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(1100); + +/// Maximum number of known block hashes to keep for a peer. +const MAX_KNOWN_BLOCKS: usize = 1024; // ~32kb per peer + LruHashSet overhead + +/// If the block announces stream to peer has been inactive for 30 seconds meaning local node +/// has not sent or received block announcements to/from the peer, report the node for inactivity, +/// disconnect it and attempt to establish connection to some other peer. +const INACTIVITY_EVICT_THRESHOLD: Duration = Duration::from_secs(30); + +/// When `SyncingEngine` is started, wait two minutes before actually staring to count peers as +/// evicted. +/// +/// Parachain collator may incorrectly get evicted because it's waiting to receive a number of +/// relaychain blocks before it can start creating parachain blocks. During this wait, +/// `SyncingEngine` still counts it as active and as the peer is not sending blocks, it may get +/// evicted if a block is not received within the first 30 secons since the peer connected. +/// +/// To prevent this from happening, define a threshold for how long `SyncingEngine` should wait +/// before it starts evicting peers. +const INITIAL_EVICTION_WAIT_PERIOD: Duration = Duration::from_secs(2 * 60); + +mod rep { + use sc_network::ReputationChange as Rep; + /// Peer has different genesis. + pub const GENESIS_MISMATCH: Rep = Rep::new_fatal("Genesis mismatch"); + /// Peer send us a block announcement that failed at validation. + pub const BAD_BLOCK_ANNOUNCEMENT: Rep = Rep::new(-(1 << 12), "Bad block announcement"); + /// Block announce substream with the peer has been inactive too long + pub const INACTIVE_SUBSTREAM: Rep = Rep::new(-(1 << 10), "Inactive block announce substream"); +} + +struct Metrics { + peers: Gauge, + queued_blocks: Gauge, + fork_targets: Gauge, + justifications: GaugeVec, +} + +impl Metrics { + fn register(r: &Registry, major_syncing: Arc) -> Result { + let _ = MajorSyncingGauge::register(r, major_syncing)?; + Ok(Self { + peers: { + let g = Gauge::new("substrate_sync_peers", "Number of peers we sync with")?; + register(g, r)? + }, + queued_blocks: { + let g = + Gauge::new("substrate_sync_queued_blocks", "Number of blocks in import queue")?; + register(g, r)? + }, + fork_targets: { + let g = Gauge::new("substrate_sync_fork_targets", "Number of fork sync targets")?; + register(g, r)? + }, + justifications: { + let g = GaugeVec::new( + Opts::new( + "substrate_sync_extra_justifications", + "Number of extra justifications requests", + ), + &["status"], + )?; + register(g, r)? + }, + }) + } +} + +/// The "major syncing" metric. +#[derive(Clone)] +pub struct MajorSyncingGauge(Arc); + +impl MajorSyncingGauge { + /// Registers the [`MajorSyncGauge`] metric whose value is + /// obtained from the given `AtomicBool`. + fn register(registry: &Registry, value: Arc) -> Result<(), PrometheusError> { + prometheus_endpoint::register( + SourcedGauge::new( + &Opts::new( + "substrate_sub_libp2p_is_major_syncing", + "Whether the node is performing a major sync or not.", + ), + MajorSyncingGauge(value), + )?, + registry, + )?; + + Ok(()) + } +} + +impl MetricSource for MajorSyncingGauge { + type N = u64; + + fn collect(&self, mut set: impl FnMut(&[&str], Self::N)) { + set(&[], self.0.load(Ordering::Relaxed) as u64); + } +} + +/// Peer information +#[derive(Debug)] +pub struct Peer { + pub info: ExtendedPeerInfo, + /// Holds a set of blocks known to this peer. + pub known_blocks: LruHashSet, + /// Notification sink. + sink: NotificationsSink, + /// Instant when the last notification was sent to peer. + last_notification_sent: Instant, + /// Instant when the last notification was received from peer. + last_notification_received: Instant, +} + +pub struct SyncingEngine { + /// State machine that handles the list of in-progress requests. Only full node peers are + /// registered. + chain_sync: ChainSync, + + /// Blockchain client. + client: Arc, + + /// Number of peers we're connected to. + num_connected: Arc, + + /// Are we actively catching up with the chain? + is_major_syncing: Arc, + + /// Network service. + network_service: service::network::NetworkServiceHandle, + + /// Channel for receiving service commands + service_rx: TracingUnboundedReceiver>, + + /// Channel for receiving inbound connections from `Protocol`. + rx: sc_utils::mpsc::TracingUnboundedReceiver>, + + /// Assigned roles. + roles: Roles, + + /// Genesis hash. + genesis_hash: B::Hash, + + /// Set of channels for other protocols that have subscribed to syncing events. + event_streams: Vec>, + + /// Interval at which we call `tick`. + tick_timeout: Delay, + + /// All connected peers. Contains both full and light node peers. + peers: HashMap>, + + /// Evicted peers + evicted: HashSet, + + /// List of nodes for which we perform additional logging because they are important for the + /// user. + important_peers: HashSet, + + /// Actual list of connected no-slot nodes. + default_peers_set_no_slot_connected_peers: HashSet, + + /// List of nodes that should never occupy peer slots. + default_peers_set_no_slot_peers: HashSet, + + /// Value that was passed as part of the configuration. Used to cap the number of full + /// nodes. + default_peers_set_num_full: usize, + + /// Number of slots to allocate to light nodes. + default_peers_set_num_light: usize, + + /// A cache for the data that was associated to a block announcement. + block_announce_data_cache: LruMap>, + + /// The `PeerId`'s of all boot nodes. + boot_node_ids: HashSet, + + /// Protocol name used for block announcements + block_announce_protocol_name: ProtocolName, + + /// Prometheus metrics. + metrics: Option, + + /// When the syncing was started. + /// + /// Stored as an `Option` so once the initial wait has passed, `SyncingEngine` + /// can reset the peer timers and continue with the normal eviction process. + syncing_started: Option, +} + +impl SyncingEngine +where + B: BlockT, + Client: HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, +{ + pub fn new( + roles: Roles, + client: Arc, + metrics_registry: Option<&Registry>, + net_config: &FullNetworkConfiguration, + protocol_id: ProtocolId, + fork_id: &Option, + block_announce_validator: Box + Send>, + warp_sync_params: Option>, + network_service: service::network::NetworkServiceHandle, + import_queue: Box>, + block_request_protocol_name: ProtocolName, + state_request_protocol_name: ProtocolName, + warp_sync_protocol_name: Option, + rx: sc_utils::mpsc::TracingUnboundedReceiver>, + ) -> Result<(Self, SyncingService, NonDefaultSetConfig), ClientError> { + let mode = net_config.network_config.sync_mode; + let max_parallel_downloads = net_config.network_config.max_parallel_downloads; + let max_blocks_per_request = if net_config.network_config.max_blocks_per_request > + crate::MAX_BLOCKS_IN_RESPONSE as u32 + { + log::info!(target: "sync", "clamping maximum blocks per request to {}", crate::MAX_BLOCKS_IN_RESPONSE); + crate::MAX_BLOCKS_IN_RESPONSE as u32 + } else { + net_config.network_config.max_blocks_per_request + }; + let cache_capacity = (net_config.network_config.default_peers_set.in_peers + + net_config.network_config.default_peers_set.out_peers) + .max(1); + let important_peers = { + let mut imp_p = HashSet::new(); + for reserved in &net_config.network_config.default_peers_set.reserved_nodes { + imp_p.insert(reserved.peer_id); + } + for config in net_config.notification_protocols() { + let peer_ids = config + .set_config + .reserved_nodes + .iter() + .map(|info| info.peer_id) + .collect::>(); + imp_p.extend(peer_ids); + } + + imp_p.shrink_to_fit(); + imp_p + }; + let boot_node_ids = { + let mut list = HashSet::new(); + for node in &net_config.network_config.boot_nodes { + list.insert(node.peer_id); + } + list.shrink_to_fit(); + list + }; + let default_peers_set_no_slot_peers = { + let mut no_slot_p: HashSet = net_config + .network_config + .default_peers_set + .reserved_nodes + .iter() + .map(|reserved| reserved.peer_id) + .collect(); + no_slot_p.shrink_to_fit(); + no_slot_p + }; + let default_peers_set_num_full = + net_config.network_config.default_peers_set_num_full as usize; + let default_peers_set_num_light = { + let total = net_config.network_config.default_peers_set.out_peers + + net_config.network_config.default_peers_set.in_peers; + total.saturating_sub(net_config.network_config.default_peers_set_num_full) as usize + }; + + let (chain_sync, block_announce_config) = ChainSync::new( + mode, + client.clone(), + protocol_id, + fork_id, + roles, + block_announce_validator, + max_parallel_downloads, + max_blocks_per_request, + warp_sync_params, + metrics_registry, + network_service.clone(), + import_queue, + block_request_protocol_name, + state_request_protocol_name, + warp_sync_protocol_name, + )?; + + let block_announce_protocol_name = block_announce_config.notifications_protocol.clone(); + let (tx, service_rx) = tracing_unbounded("mpsc_chain_sync", 100_000); + let num_connected = Arc::new(AtomicUsize::new(0)); + let is_major_syncing = Arc::new(AtomicBool::new(false)); + let genesis_hash = client + .block_hash(0u32.into()) + .ok() + .flatten() + .expect("Genesis block exists; qed"); + + Ok(( + Self { + roles, + client, + chain_sync, + network_service, + peers: HashMap::new(), + evicted: HashSet::new(), + block_announce_data_cache: LruMap::new(ByLength::new(cache_capacity)), + block_announce_protocol_name, + num_connected: num_connected.clone(), + is_major_syncing: is_major_syncing.clone(), + service_rx, + rx, + genesis_hash, + important_peers, + default_peers_set_no_slot_connected_peers: HashSet::new(), + boot_node_ids, + default_peers_set_no_slot_peers, + default_peers_set_num_full, + default_peers_set_num_light, + event_streams: Vec::new(), + tick_timeout: Delay::new(TICK_TIMEOUT), + syncing_started: None, + metrics: if let Some(r) = metrics_registry { + match Metrics::register(r, is_major_syncing.clone()) { + Ok(metrics) => Some(metrics), + Err(err) => { + log::error!(target: "sync", "Failed to register metrics {err:?}"); + None + }, + } + } else { + None + }, + }, + SyncingService::new(tx, num_connected, is_major_syncing), + block_announce_config, + )) + } + + /// Report Prometheus metrics. + pub fn report_metrics(&self) { + if let Some(metrics) = &self.metrics { + let n = u64::try_from(self.peers.len()).unwrap_or(std::u64::MAX); + metrics.peers.set(n); + + let m = self.chain_sync.metrics(); + + metrics.fork_targets.set(m.fork_targets.into()); + metrics.queued_blocks.set(m.queued_blocks.into()); + + metrics + .justifications + .with_label_values(&["pending"]) + .set(m.justifications.pending_requests.into()); + metrics + .justifications + .with_label_values(&["active"]) + .set(m.justifications.active_requests.into()); + metrics + .justifications + .with_label_values(&["failed"]) + .set(m.justifications.failed_requests.into()); + metrics + .justifications + .with_label_values(&["importing"]) + .set(m.justifications.importing_requests.into()); + } + } + + fn update_peer_info(&mut self, who: &PeerId) { + if let Some(info) = self.chain_sync.peer_info(who) { + if let Some(ref mut peer) = self.peers.get_mut(who) { + peer.info.best_hash = info.best_hash; + peer.info.best_number = info.best_number; + } + } + } + + /// Process the result of the block announce validation. + pub fn process_block_announce_validation_result( + &mut self, + validation_result: PollBlockAnnounceValidation, + ) { + match validation_result { + PollBlockAnnounceValidation::Skip => {}, + PollBlockAnnounceValidation::Nothing { is_best: _, who, announce } => { + self.update_peer_info(&who); + + if let Some(data) = announce.data { + if !data.is_empty() { + self.block_announce_data_cache.insert(announce.header.hash(), data); + } + } + }, + PollBlockAnnounceValidation::Failure { who, disconnect } => { + if disconnect { + self.network_service + .disconnect_peer(who, self.block_announce_protocol_name.clone()); + } + + self.network_service.report_peer(who, rep::BAD_BLOCK_ANNOUNCEMENT); + }, + } + } + + /// Push a block announce validation. + /// + /// It is required that [`ChainSync::poll_block_announce_validation`] is + /// called later to check for finished validations. The result of the validation + /// needs to be passed to [`SyncingEngine::process_block_announce_validation_result`] + /// to finish the processing. + /// + /// # Note + /// + /// This will internally create a future, but this future will not be registered + /// in the task before being polled once. So, it is required to call + /// [`ChainSync::poll_block_announce_validation`] to ensure that the future is + /// registered properly and will wake up the task when being ready. + pub fn push_block_announce_validation( + &mut self, + who: PeerId, + announce: BlockAnnounce, + ) { + let hash = announce.header.hash(); + + let peer = match self.peers.get_mut(&who) { + Some(p) => p, + None => { + log::error!(target: "sync", "Received block announce from disconnected peer {}", who); + debug_assert!(false); + return + }, + }; + peer.known_blocks.insert(hash); + peer.last_notification_received = Instant::now(); + + if peer.info.roles.is_full() { + let is_best = match announce.state.unwrap_or(BlockState::Best) { + BlockState::Best => true, + BlockState::Normal => false, + }; + + self.chain_sync.push_block_announce_validation(who, hash, announce, is_best); + } + } + + /// Make sure an important block is propagated to peers. + /// + /// In chain-based consensus, we often need to make sure non-best forks are + /// at least temporarily synced. + pub fn announce_block(&mut self, hash: B::Hash, data: Option>) { + let header = match self.client.header(hash) { + Ok(Some(header)) => header, + Ok(None) => { + log::warn!(target: "sync", "Trying to announce unknown block: {}", hash); + return + }, + Err(e) => { + log::warn!(target: "sync", "Error reading block header {}: {}", hash, e); + return + }, + }; + + // don't announce genesis block since it will be ignored + if header.number().is_zero() { + return + } + + let is_best = self.client.info().best_hash == hash; + log::debug!(target: "sync", "Reannouncing block {:?} is_best: {}", hash, is_best); + + let data = data + .or_else(|| self.block_announce_data_cache.get(&hash).cloned()) + .unwrap_or_default(); + + for (who, ref mut peer) in self.peers.iter_mut() { + let inserted = peer.known_blocks.insert(hash); + if inserted { + log::trace!(target: "sync", "Announcing block {:?} to {}", hash, who); + let message = BlockAnnounce { + header: header.clone(), + state: if is_best { Some(BlockState::Best) } else { Some(BlockState::Normal) }, + data: Some(data.clone()), + }; + + peer.last_notification_sent = Instant::now(); + peer.sink.send_sync_notification(message.encode()); + } + } + } + + /// Inform sync about new best imported block. + pub fn new_best_block_imported(&mut self, hash: B::Hash, number: NumberFor) { + log::debug!(target: "sync", "New best block imported {:?}/#{}", hash, number); + + self.chain_sync.update_chain_info(&hash, number); + self.network_service.set_notification_handshake( + self.block_announce_protocol_name.clone(), + BlockAnnouncesHandshake::::build(self.roles, number, hash, self.genesis_hash) + .encode(), + ) + } + + pub async fn run(mut self) { + self.syncing_started = Some(Instant::now()); + + loop { + futures::future::poll_fn(|cx| self.poll(cx)).await; + } + } + + pub fn poll(&mut self, cx: &mut std::task::Context) -> Poll<()> { + self.num_connected.store(self.peers.len(), Ordering::Relaxed); + self.is_major_syncing + .store(self.chain_sync.status().state.is_major_syncing(), Ordering::Relaxed); + + while let Poll::Ready(()) = self.tick_timeout.poll_unpin(cx) { + self.report_metrics(); + self.tick_timeout.reset(TICK_TIMEOUT); + + // if `SyncingEngine` has just started, don't evict seemingly inactive peers right away + // as they may not have produced blocks not because they've disconnected but because + // they're still waiting to receive enough relaychain blocks to start producing blocks. + if let Some(started) = self.syncing_started { + if started.elapsed() < INITIAL_EVICTION_WAIT_PERIOD { + continue + } + + // reset the peer activity timers so they don't expire right away after + // the initial wait is done. + for info in self.peers.values_mut() { + info.last_notification_received = Instant::now(); + info.last_notification_sent = Instant::now(); + } + + self.syncing_started = None; + } + + // go over all connected peers and check if any of them have been idle for a while. Idle + // in this case means that we haven't sent or received block announcements to/from this + // peer. If that is the case, because of #5685, it could be that the block announces + // substream is not actually open and and this peer is just wasting a slot and is should + // be replaced with some other node that is willing to send us block announcements. + for (id, peer) in self.peers.iter() { + // because of a delay between disconnecting a peer in `SyncingEngine` and getting + // the response back from `Protocol`, a peer might be reported and disconnect + // multiple times. To prevent this from happening (until the underlying issue is + // fixed), keep track of evicted peers and report and disconnect them only once. + if self.evicted.contains(id) { + continue + } + + let last_received_late = + peer.last_notification_received.elapsed() > INACTIVITY_EVICT_THRESHOLD; + let last_sent_late = + peer.last_notification_sent.elapsed() > INACTIVITY_EVICT_THRESHOLD; + + if last_received_late && last_sent_late { + log::debug!(target: "sync", "evict peer {id} since it has been idling for too long"); + self.network_service.report_peer(*id, rep::INACTIVE_SUBSTREAM); + self.network_service + .disconnect_peer(*id, self.block_announce_protocol_name.clone()); + self.evicted.insert(*id); + } + } + } + + while let Poll::Ready(Some(event)) = self.service_rx.poll_next_unpin(cx) { + match event { + ToServiceCommand::SetSyncForkRequest(peers, hash, number) => { + self.chain_sync.set_sync_fork_request(peers, &hash, number); + }, + ToServiceCommand::EventStream(tx) => self.event_streams.push(tx), + ToServiceCommand::RequestJustification(hash, number) => + self.chain_sync.request_justification(&hash, number), + ToServiceCommand::ClearJustificationRequests => + self.chain_sync.clear_justification_requests(), + ToServiceCommand::BlocksProcessed(imported, count, results) => { + for result in self.chain_sync.on_blocks_processed(imported, count, results) { + match result { + Ok((id, req)) => self.chain_sync.send_block_request(id, req), + Err(BadPeer(id, repu)) => { + self.network_service + .disconnect_peer(id, self.block_announce_protocol_name.clone()); + self.network_service.report_peer(id, repu) + }, + } + } + }, + ToServiceCommand::JustificationImported(peer, hash, number, success) => { + self.chain_sync.on_justification_import(hash, number, success); + if !success { + log::info!(target: "sync", "💔 Invalid justification provided by {} for #{}", peer, hash); + self.network_service + .disconnect_peer(peer, self.block_announce_protocol_name.clone()); + self.network_service.report_peer( + peer, + ReputationChange::new_fatal("Invalid justification"), + ); + } + }, + ToServiceCommand::AnnounceBlock(hash, data) => self.announce_block(hash, data), + ToServiceCommand::NewBestBlockImported(hash, number) => + self.new_best_block_imported(hash, number), + ToServiceCommand::Status(tx) => { + let mut status = self.chain_sync.status(); + status.num_connected_peers = self.peers.len() as u32; + let _ = tx.send(status); + }, + ToServiceCommand::NumActivePeers(tx) => { + let _ = tx.send(self.chain_sync.num_active_peers()); + }, + ToServiceCommand::SyncState(tx) => { + let _ = tx.send(self.chain_sync.status()); + }, + ToServiceCommand::BestSeenBlock(tx) => { + let _ = tx.send(self.chain_sync.status().best_seen_block); + }, + ToServiceCommand::NumSyncPeers(tx) => { + let _ = tx.send(self.chain_sync.status().num_peers); + }, + ToServiceCommand::NumQueuedBlocks(tx) => { + let _ = tx.send(self.chain_sync.status().queued_blocks); + }, + ToServiceCommand::NumDownloadedBlocks(tx) => { + let _ = tx.send(self.chain_sync.num_downloaded_blocks()); + }, + ToServiceCommand::NumSyncRequests(tx) => { + let _ = tx.send(self.chain_sync.num_sync_requests()); + }, + ToServiceCommand::PeersInfo(tx) => { + let peers_info = + self.peers.iter().map(|(id, peer)| (*id, peer.info.clone())).collect(); + let _ = tx.send(peers_info); + }, + ToServiceCommand::OnBlockFinalized(hash, header) => + self.chain_sync.on_block_finalized(&hash, *header.number()), + } + } + + while let Poll::Ready(Some(event)) = self.rx.poll_next_unpin(cx) { + match event { + sc_network::SyncEvent::NotificationStreamOpened { + remote, + received_handshake, + sink, + tx, + } => match self.on_sync_peer_connected(remote, &received_handshake, sink) { + Ok(()) => { + let _ = tx.send(true); + }, + Err(()) => { + log::debug!( + target: "sync", + "Failed to register peer {remote:?}: {received_handshake:?}", + ); + let _ = tx.send(false); + }, + }, + sc_network::SyncEvent::NotificationStreamClosed { remote } => { + self.evicted.remove(&remote); + if self.on_sync_peer_disconnected(remote).is_err() { + log::trace!( + target: "sync", + "Disconnected peer which had earlier been refused by on_sync_peer_connected {}", + remote + ); + } + }, + sc_network::SyncEvent::NotificationsReceived { remote, messages } => { + for message in messages { + if self.peers.contains_key(&remote) { + if let Ok(announce) = BlockAnnounce::decode(&mut message.as_ref()) { + self.push_block_announce_validation(remote, announce); + + // Make sure that the newly added block announce validation future + // was polled once to be registered in the task. + if let Poll::Ready(res) = + self.chain_sync.poll_block_announce_validation(cx) + { + self.process_block_announce_validation_result(res) + } + } else { + log::warn!(target: "sub-libp2p", "Failed to decode block announce"); + } + } else { + log::trace!( + target: "sync", + "Received sync for peer earlier refused by sync layer: {}", + remote + ); + } + } + }, + sc_network::SyncEvent::NotificationSinkReplaced { remote, sink } => { + if let Some(peer) = self.peers.get_mut(&remote) { + peer.sink = sink; + } + }, + } + } + + // poll `ChainSync` last because of a block announcement was received through the + // event stream between `SyncingEngine` and `Protocol` and the validation finished + // right after it as queued, the resulting block request (if any) can be sent right away. + while let Poll::Ready(result) = self.chain_sync.poll(cx) { + self.process_block_announce_validation_result(result); + } + + Poll::Pending + } + + /// Called by peer when it is disconnecting. + /// + /// Returns a result if the handshake of this peer was indeed accepted. + pub fn on_sync_peer_disconnected(&mut self, peer: PeerId) -> Result<(), ()> { + if self.peers.remove(&peer).is_some() { + if self.important_peers.contains(&peer) { + log::warn!(target: "sync", "Reserved peer {} disconnected", peer); + } else { + log::debug!(target: "sync", "{} disconnected", peer); + } + + self.chain_sync.peer_disconnected(&peer); + self.default_peers_set_no_slot_connected_peers.remove(&peer); + self.event_streams + .retain(|stream| stream.unbounded_send(SyncEvent::PeerDisconnected(peer)).is_ok()); + Ok(()) + } else { + Err(()) + } + } + + /// Called on the first connection between two peers on the default set, after their exchange + /// of handshake. + /// + /// Returns `Ok` if the handshake is accepted and the peer added to the list of peers we sync + /// from. + pub fn on_sync_peer_connected( + &mut self, + who: PeerId, + status: &BlockAnnouncesHandshake, + sink: NotificationsSink, + ) -> Result<(), ()> { + log::trace!(target: "sync", "New peer {} {:?}", who, status); + + if self.peers.contains_key(&who) { + log::error!(target: "sync", "Called on_sync_peer_connected with already connected peer {}", who); + debug_assert!(false); + return Err(()) + } + + if status.genesis_hash != self.genesis_hash { + self.network_service.report_peer(who, rep::GENESIS_MISMATCH); + + if self.important_peers.contains(&who) { + log::error!( + target: "sync", + "Reserved peer id `{}` is on a different chain (our genesis: {} theirs: {})", + who, + self.genesis_hash, + status.genesis_hash, + ); + } else if self.boot_node_ids.contains(&who) { + log::error!( + target: "sync", + "Bootnode with peer id `{}` is on a different chain (our genesis: {} theirs: {})", + who, + self.genesis_hash, + status.genesis_hash, + ); + } else { + log::debug!( + target: "sync", + "Peer is on different chain (our genesis: {} theirs: {})", + self.genesis_hash, status.genesis_hash + ); + } + + return Err(()) + } + + let no_slot_peer = self.default_peers_set_no_slot_peers.contains(&who); + let this_peer_reserved_slot: usize = if no_slot_peer { 1 } else { 0 }; + + if status.roles.is_full() && + self.chain_sync.num_peers() >= + self.default_peers_set_num_full + + self.default_peers_set_no_slot_connected_peers.len() + + this_peer_reserved_slot + { + log::debug!(target: "sync", "Too many full nodes, rejecting {}", who); + return Err(()) + } + + if status.roles.is_light() && + (self.peers.len() - self.chain_sync.num_peers()) >= self.default_peers_set_num_light + { + // Make sure that not all slots are occupied by light clients. + log::debug!(target: "sync", "Too many light nodes, rejecting {}", who); + return Err(()) + } + + let peer = Peer { + info: ExtendedPeerInfo { + roles: status.roles, + best_hash: status.best_hash, + best_number: status.best_number, + }, + known_blocks: LruHashSet::new( + NonZeroUsize::new(MAX_KNOWN_BLOCKS).expect("Constant is nonzero"), + ), + sink, + last_notification_sent: Instant::now(), + last_notification_received: Instant::now(), + }; + + let req = if peer.info.roles.is_full() { + match self.chain_sync.new_peer(who, peer.info.best_hash, peer.info.best_number) { + Ok(req) => req, + Err(BadPeer(id, repu)) => { + self.network_service.report_peer(id, repu); + return Err(()) + }, + } + } else { + None + }; + + log::debug!(target: "sync", "Connected {}", who); + + self.peers.insert(who, peer); + if no_slot_peer { + self.default_peers_set_no_slot_connected_peers.insert(who); + } + + if let Some(req) = req { + self.chain_sync.send_block_request(who, req); + } + + self.event_streams + .retain(|stream| stream.unbounded_send(SyncEvent::PeerConnected(who)).is_ok()); + + Ok(()) + } +} diff --git a/client/network/sync/src/extra_requests.rs b/client/network/sync/src/extra_requests.rs index 0506bd542ff3..09e6bdb57399 100644 --- a/client/network/sync/src/extra_requests.rs +++ b/client/network/sync/src/extra_requests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/network/sync/src/lib.rs b/client/network/sync/src/lib.rs index 312fc6f5b794..03a22995bb92 100644 --- a/client/network/sync/src/lib.rs +++ b/client/network/sync/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -28,25 +28,13 @@ //! the network, or whenever a block has been successfully verified, call the appropriate method in //! order to update it. -pub mod block_request_handler; -pub mod blocks; -pub mod mock; -mod schema; -pub mod service; -pub mod state; -pub mod state_request_handler; -#[cfg(test)] -mod tests; -pub mod warp; -pub mod warp_request_handler; - use crate::{ blocks::BlockCollection, schema::v1::{StateRequest, StateResponse}, - service::chain_sync::{ChainSyncInterfaceHandle, ToServiceCommand}, state::StateSync, warp::{WarpProofImportResult, WarpSync}, }; + use codec::{Decode, DecodeAll, Encode}; use extra_requests::ExtraRequests; use futures::{ @@ -54,31 +42,34 @@ use futures::{ }; use libp2p::{request_response::OutboundFailure, PeerId}; use log::{debug, error, info, trace, warn}; -use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; use prost::Message; + +use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; use sc_client_api::{BlockBackend, ProofProvider}; use sc_consensus::{ import_queue::ImportQueueService, BlockImportError, BlockImportStatus, IncomingBlock, }; -use sc_network_common::{ +use sc_network::{ config::{ NonDefaultSetConfig, NonReservedPeerMode, NotificationHandshake, ProtocolId, SetConfig, }, - protocol::{role::Roles, ProtocolName}, request_responses::{IfDisconnected, RequestFailure}, + types::ProtocolName, +}; +use sc_network_common::{ + role::Roles, sync::{ message::{ BlockAnnounce, BlockAnnouncesHandshake, BlockAttributes, BlockData, BlockRequest, BlockResponse, Direction, FromBlock, }, - warp::{EncodedProof, WarpProofRequest, WarpSyncPhase, WarpSyncProgress, WarpSyncProvider}, + warp::{EncodedProof, WarpProofRequest, WarpSyncParams, WarpSyncPhase, WarpSyncProgress}, BadPeer, ChainSync as ChainSyncT, ImportResult, Metrics, OnBlockData, OnBlockJustification, OnStateData, OpaqueBlockRequest, OpaqueBlockResponse, OpaqueStateRequest, OpaqueStateResponse, PeerInfo, PeerRequest, PollBlockAnnounceValidation, SyncMode, SyncState, SyncStatus, }, }; -use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver}; use sp_arithmetic::traits::Saturating; use sp_blockchain::{Error as ClientError, HeaderBackend, HeaderMetadata}; use sp_consensus::{ @@ -92,6 +83,7 @@ use sp_runtime::{ }, EncodedJustification, Justifications, }; + use std::{ collections::{hash_map::Entry, HashMap, HashSet}, iter, @@ -99,12 +91,21 @@ use std::{ pin::Pin, sync::Arc, }; -use warp::TargetBlockImportResult; + +pub use service::chain_sync::SyncingService; mod extra_requests; +mod schema; -/// Maximum blocks to request in a single packet. -const MAX_BLOCKS_TO_REQUEST: usize = 64; +pub mod block_request_handler; +pub mod blocks; +pub mod engine; +pub mod mock; +pub mod service; +pub mod state; +pub mod state_request_handler; +pub mod warp; +pub mod warp_request_handler; /// Maximum blocks to store in the import queue. const MAX_IMPORTING_BLOCKS: usize = 2048; @@ -143,8 +144,11 @@ const MIN_PEERS_TO_START_WARP_SYNC: usize = 3; /// Maximum allowed size for a block announce. const MAX_BLOCK_ANNOUNCE_SIZE: u64 = 1024 * 1024; +/// Maximum blocks per response. +pub(crate) const MAX_BLOCKS_IN_RESPONSE: usize = 128; + mod rep { - use sc_peerset::ReputationChange as Rep; + use sc_network::ReputationChange as Rep; /// Reputation change when a peer sent us a message that led to a /// database read error. pub const BLOCKCHAIN_READ_ERROR: Rep = Rep::new(-(1 << 16), "DB Error"); @@ -307,6 +311,8 @@ pub struct ChainSync { block_announce_validator: Box + Send>, /// Maximum number of peers to ask the same blocks in parallel. max_parallel_downloads: u32, + /// Maximum blocks per request. + max_blocks_per_request: u32, /// Total number of downloaded blocks. downloaded_blocks: usize, /// All block announcement that are currently being validated. @@ -318,15 +324,15 @@ pub struct ChainSync { state_sync: Option>, /// Warp sync in progress, if any. warp_sync: Option>, - /// Warp sync provider. - warp_sync_provider: Option>>, + /// Warp sync params. + /// + /// Will be `None` after `self.warp_sync` is `Some(_)`. + warp_sync_params: Option>, /// Enable importing existing blocks. This is used used after the state download to /// catch up to the latest state while re-importing blocks. import_existing: bool, /// Gap download process. gap_sync: Option>, - /// Channel for receiving service commands - service_rx: TracingUnboundedReceiver>, /// Handle for communicating with `NetworkService` network_service: service::network::NetworkServiceHandle, /// Protocol name used for block announcements @@ -338,7 +344,7 @@ pub struct ChainSync { /// Protocol name used to send out warp sync requests warp_sync_protocol_name: Option, /// Pending responses - pending_responses: FuturesUnordered>, + pending_responses: HashMap>, /// Handle to import queue. import_queue: Box>, /// Metrics. @@ -509,8 +515,12 @@ where phase: WarpSyncPhase::DownloadingBlocks(gap_sync.best_queued_number), total_bytes: 0, }), - (None, SyncMode::Warp, _) => - Some(WarpSyncProgress { phase: WarpSyncPhase::AwaitingPeers, total_bytes: 0 }), + (None, SyncMode::Warp, _) => Some(WarpSyncProgress { + phase: WarpSyncPhase::AwaitingPeers { + required_peers: MIN_PEERS_TO_START_WARP_SYNC, + }, + total_bytes: 0, + }), (Some(sync), _, _) => Some(sync.progress()), _ => None, }; @@ -519,6 +529,7 @@ where state: sync_state, best_seen_block, num_peers: self.peers.len() as u32, + num_connected_peers: 0u32, queued_blocks: self.queue_blocks.len() as u32, state_sync: self.state_sync.as_ref().map(|s| s.progress()), warp_sync: warp_sync_progress, @@ -565,6 +576,7 @@ where info!("💔 New peer with unknown genesis hash {} ({}).", best_hash, best_number); return Err(BadPeer(who, rep::GENESIS_MISMATCH)) } + // If there are more than `MAJOR_SYNC_BLOCKS` in the import queue then we have // enough to do in the import queue that it's not worth kicking off // an ancestor search, which is what we do in the next match case below. @@ -630,17 +642,15 @@ where }, ); - if let SyncMode::Warp = &self.mode { + if let SyncMode::Warp = self.mode { if self.peers.len() >= MIN_PEERS_TO_START_WARP_SYNC && self.warp_sync.is_none() { log::debug!(target: "sync", "Starting warp state sync."); - if let Some(provider) = &self.warp_sync_provider { - self.warp_sync = - Some(WarpSync::new(self.client.clone(), provider.clone())); + if let Some(params) = self.warp_sync_params.take() { + self.warp_sync = Some(WarpSync::new(self.client.clone(), params)); } } } - Ok(req) }, Ok(BlockStatus::Queued) | @@ -930,9 +940,9 @@ where match warp_sync.import_target_block( blocks.pop().expect("`blocks` len checked above."), ) { - TargetBlockImportResult::Success => + warp::TargetBlockImportResult::Success => return Ok(OnBlockData::Continue), - TargetBlockImportResult::BadResponse => + warp::TargetBlockImportResult::BadResponse => return Err(BadPeer(*who, rep::VERIFICATION_FAIL)), } } else if blocks.is_empty() { @@ -993,19 +1003,6 @@ where Ok(self.validate_and_queue_blocks(new_blocks, gap)) } - fn process_block_response_data(&mut self, blocks_to_import: Result, BadPeer>) { - match blocks_to_import { - Ok(OnBlockData::Import(origin, blocks)) => self.import_blocks(origin, blocks), - Ok(OnBlockData::Request(peer, req)) => self.send_block_request(peer, req), - Ok(OnBlockData::Continue) => {}, - Err(BadPeer(id, repu)) => { - self.network_service - .disconnect_peer(id, self.block_announce_protocol_name.clone()); - self.network_service.report_peer(id, repu); - }, - } - } - fn on_block_justification( &mut self, who: PeerId, @@ -1014,7 +1011,7 @@ where let peer = if let Some(peer) = self.peers.get_mut(&who) { peer } else { - error!(target: "sync", "💔 Called on_block_justification with a bad peer ID"); + error!(target: "sync", "💔 Called on_block_justification with a peer ID of an unknown peer"); return Ok(OnBlockJustification::Nothing) }; @@ -1232,6 +1229,7 @@ where gap_sync.blocks.clear_peer_download(who) } self.peers.remove(who); + self.pending_responses.remove(who); self.extra_justifications.peer_disconnected(who); self.allowed_requests.set_all(); self.fork_targets.retain(|_, target| { @@ -1325,40 +1323,12 @@ where &mut self, cx: &mut std::task::Context, ) -> Poll> { - while let Poll::Ready(Some(event)) = self.service_rx.poll_next_unpin(cx) { - match event { - ToServiceCommand::SetSyncForkRequest(peers, hash, number) => { - self.set_sync_fork_request(peers, &hash, number); - }, - ToServiceCommand::RequestJustification(hash, number) => - self.request_justification(&hash, number), - ToServiceCommand::ClearJustificationRequests => self.clear_justification_requests(), - ToServiceCommand::BlocksProcessed(imported, count, results) => { - for result in self.on_blocks_processed(imported, count, results) { - match result { - Ok((id, req)) => self.send_block_request(id, req), - Err(BadPeer(id, repu)) => { - self.network_service - .disconnect_peer(id, self.block_announce_protocol_name.clone()); - self.network_service.report_peer(id, repu) - }, - } - } - }, - ToServiceCommand::JustificationImported(peer, hash, number, success) => { - self.on_justification_import(hash, number, success); - if !success { - info!(target: "sync", "💔 Invalid justification provided by {} for #{}", peer, hash); - self.network_service - .disconnect_peer(peer, self.block_announce_protocol_name.clone()); - self.network_service.report_peer( - peer, - sc_peerset::ReputationChange::new_fatal("Invalid justification"), - ); - } - }, - } + // Should be called before `process_outbound_requests` to ensure + // that a potential target block is directly leading to requests. + if let Some(warp_sync) = &mut self.warp_sync { + let _ = warp_sync.poll(cx); } + self.process_outbound_requests(); while let Poll::Ready(result) = self.poll_pending_responses(cx) { @@ -1382,7 +1352,7 @@ where if self.peers.contains_key(&who) { self.pending_responses - .push(Box::pin(async move { (who, PeerRequest::Block(request), rx.await) })); + .insert(who, Box::pin(async move { (who, PeerRequest::Block(request), rx.await) })); } match self.encode_block_request(&opaque_req) { @@ -1427,15 +1397,15 @@ where roles: Roles, block_announce_validator: Box + Send>, max_parallel_downloads: u32, - warp_sync_provider: Option>>, + max_blocks_per_request: u32, + warp_sync_params: Option>, metrics_registry: Option<&Registry>, network_service: service::network::NetworkServiceHandle, import_queue: Box>, block_request_protocol_name: ProtocolName, state_request_protocol_name: ProtocolName, warp_sync_protocol_name: Option, - ) -> Result<(Self, ChainSyncInterfaceHandle, NonDefaultSetConfig), ClientError> { - let (tx, service_rx) = tracing_unbounded("mpsc_chain_sync", 100_000); + ) -> Result<(Self, NonDefaultSetConfig), ClientError> { let block_announce_config = Self::get_block_announce_proto_config( protocol_id, fork_id, @@ -1462,24 +1432,24 @@ where allowed_requests: Default::default(), block_announce_validator, max_parallel_downloads, + max_blocks_per_request, downloaded_blocks: 0, block_announce_validation: Default::default(), block_announce_validation_per_peer_stats: Default::default(), state_sync: None, warp_sync: None, - warp_sync_provider, import_existing: false, gap_sync: None, - service_rx, network_service, block_request_protocol_name, state_request_protocol_name, + warp_sync_params, warp_sync_protocol_name, block_announce_protocol_name: block_announce_config .notifications_protocol .clone() .into(), - pending_responses: Default::default(), + pending_responses: HashMap::new(), import_queue, metrics: if let Some(r) = &metrics_registry { match SyncingMetrics::register(r) { @@ -1495,7 +1465,7 @@ where }; sync.reset_sync_start_point()?; - Ok((sync, ChainSyncInterfaceHandle::new(tx), block_announce_config)) + Ok((sync, block_announce_config)) } /// Returns the median seen block number. @@ -1516,7 +1486,6 @@ where match self.mode { SyncMode::Full => BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION | BlockAttributes::BODY, - SyncMode::Light => BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION, SyncMode::LightState { storage_chain_mode: false, .. } | SyncMode::Warp => BlockAttributes::HEADER | BlockAttributes::JUSTIFICATION | BlockAttributes::BODY, SyncMode::LightState { storage_chain_mode: true, .. } => @@ -1529,7 +1498,6 @@ where fn skip_execution(&self) -> bool { match self.mode { SyncMode::Full => false, - SyncMode::Light => true, SyncMode::LightState { .. } => true, SyncMode::Warp => true, } @@ -1776,18 +1744,6 @@ where return PollBlockAnnounceValidation::Nothing { is_best, who, announce } } - let requires_additional_data = self.mode != SyncMode::Light || !known_parent; - if !requires_additional_data { - trace!( - target: "sync", - "Importing new header announced from {}: {} {:?}", - who, - hash, - announce.header, - ); - return PollBlockAnnounceValidation::ImportHeader { is_best, announce, who } - } - if self.status().state == SyncState::Idle { trace!( target: "sync", @@ -1832,6 +1788,9 @@ where return None } + // since the request is not a justification, remove it from pending responses + self.pending_responses.remove(&id); + // handle peers that were in other states. match self.new_peer(id, p.best_hash, p.best_number) { Ok(None) => None, @@ -2031,7 +1990,7 @@ where if self.peers.contains_key(&who) { self.pending_responses - .push(Box::pin(async move { (who, PeerRequest::State, rx.await) })); + .insert(who, Box::pin(async move { (who, PeerRequest::State, rx.await) })); } match self.encode_state_request(&request) { @@ -2059,7 +2018,7 @@ where if self.peers.contains_key(&who) { self.pending_responses - .push(Box::pin(async move { (who, PeerRequest::WarpProof, rx.await) })); + .insert(who, Box::pin(async move { (who, PeerRequest::WarpProof, rx.await) })); } match &self.warp_sync_protocol_name { @@ -2197,9 +2156,20 @@ where } fn poll_pending_responses(&mut self, cx: &mut std::task::Context) -> Poll> { - while let Poll::Ready(Some((id, request, response))) = - self.pending_responses.poll_next_unpin(cx) - { + let ready_responses = self + .pending_responses + .values_mut() + .filter_map(|future| match future.poll_unpin(cx) { + Poll::Pending => None, + Poll::Ready(result) => Some(result), + }) + .collect::>(); + + for (id, request, response) in ready_responses { + self.pending_responses + .remove(&id) + .expect("Logic error: peer id from pending response is missing in the map."); + match response { Ok(Ok(resp)) => match request { PeerRequest::Block(req) => { @@ -2391,6 +2361,7 @@ where let queue = &self.queue_blocks; let allowed_requests = self.allowed_requests.take(); let max_parallel = if is_major_syncing { 1 } else { self.max_parallel_downloads }; + let max_blocks_per_request = self.max_blocks_per_request; let gap_sync = &mut self.gap_sync; self.peers .iter_mut() @@ -2430,6 +2401,7 @@ where blocks, attrs, max_parallel, + max_blocks_per_request, last_finalized, best_queued, ) { @@ -2456,6 +2428,7 @@ where client.block_status(*hash).unwrap_or(BlockStatus::Unknown) } }, + max_blocks_per_request, ) { trace!(target: "sync", "Downloading fork {:?} from {}", hash, id); peer.state = PeerSyncState::DownloadingStale(hash); @@ -2468,6 +2441,7 @@ where attrs, sync.target, sync.best_queued_number, + max_blocks_per_request, ) }) { peer.state = PeerSyncState::DownloadingGap(range.start); @@ -2711,9 +2685,7 @@ where break } - if result.is_err() { - has_error = true; - } + has_error |= result.is_err(); match result { Ok(BlockImportStatus::ImportedKnown(number, who)) => @@ -2724,9 +2696,7 @@ where if aux.clear_justification_requests { trace!( target: "sync", - "Block imported clears all pending justification requests {}: {:?}", - number, - hash, + "Block imported clears all pending justification requests {number}: {hash:?}", ); self.clear_justification_requests(); } @@ -2734,9 +2704,7 @@ where if aux.needs_justification { trace!( target: "sync", - "Block imported but requires justification {}: {:?}", - number, - hash, + "Block imported but requires justification {number}: {hash:?}", ); self.request_justification(&hash, number); } @@ -2796,25 +2764,26 @@ where output.push(Err(BadPeer(peer, rep::INCOMPLETE_HEADER))); output.extend(self.restart()); }, - Err(BlockImportError::VerificationFailed(who, e)) => + Err(BlockImportError::VerificationFailed(who, e)) => { + let extra_message = + who.map_or_else(|| "".into(), |peer| format!(" received from ({peer})")); + + warn!( + target: "sync", + "💔 Verification failed for block {hash:?}{extra_message}: {e:?}", + ); + if let Some(peer) = who { - warn!( - target: "sync", - "💔 Verification failed for block {:?} received from peer: {}, {:?}", - hash, - peer, - e, - ); output.push(Err(BadPeer(peer, rep::VERIFICATION_FAIL))); - output.extend(self.restart()); - }, + } + + output.extend(self.restart()); + }, Err(BlockImportError::BadBlock(who)) => if let Some(peer) = who { warn!( target: "sync", - "💔 Block {:?} received from peer {} has been blacklisted", - hash, - peer, + "💔 Block {hash:?} received from peer {peer} has been blacklisted", ); output.push(Err(BadPeer(peer, rep::BAD_BLOCK))); }, @@ -2822,10 +2791,10 @@ where // This may happen if the chain we were requesting upon has been discarded // in the meantime because other chain has been finalized. // Don't mark it as bad as it still may be synced if explicitly requested. - trace!(target: "sync", "Obsolete block {:?}", hash); + trace!(target: "sync", "Obsolete block {hash:?}"); }, e @ Err(BlockImportError::UnknownParent) | e @ Err(BlockImportError::Other(_)) => { - warn!(target: "sync", "💔 Error importing block {:?}: {}", hash, e.unwrap_err()); + warn!(target: "sync", "💔 Error importing block {hash:?}: {}", e.unwrap_err()); self.state_sync = None; self.warp_sync = None; output.extend(self.restart()); @@ -2936,6 +2905,7 @@ fn peer_block_request( blocks: &mut BlockCollection, attrs: BlockAttributes, max_parallel_downloads: u32, + max_blocks_per_request: u32, finalized: NumberFor, best_num: NumberFor, ) -> Option<(Range>, BlockRequest)> { @@ -2951,7 +2921,7 @@ fn peer_block_request( } let range = blocks.needed_blocks( *id, - MAX_BLOCKS_TO_REQUEST, + max_blocks_per_request, peer.best_number, peer.common_number, max_parallel_downloads, @@ -2986,10 +2956,11 @@ fn peer_gap_block_request( attrs: BlockAttributes, target: NumberFor, common_number: NumberFor, + max_blocks_per_request: u32, ) -> Option<(Range>, BlockRequest)> { let range = blocks.needed_blocks( *id, - MAX_BLOCKS_TO_REQUEST, + max_blocks_per_request, std::cmp::min(peer.best_number, target), common_number, 1, @@ -3018,6 +2989,7 @@ fn fork_sync_request( finalized: NumberFor, attributes: BlockAttributes, check_block: impl Fn(&B::Hash) -> BlockStatus, + max_blocks_per_request: u32, ) -> Option<(B::Hash, BlockRequest)> { targets.retain(|hash, r| { if r.number <= finalized { @@ -3037,7 +3009,7 @@ fn fork_sync_request( // Download the fork only if it is behind or not too far ahead our tip of the chain // Otherwise it should be downloaded in full sync mode. if r.number <= best_num || - (r.number - best_num).saturated_into::() < MAX_BLOCKS_TO_REQUEST as u32 + (r.number - best_num).saturated_into::() < max_blocks_per_request as u32 { let parent_status = r.parent_hash.as_ref().map_or(BlockStatus::Unknown, check_block); let count = if parent_status == BlockStatus::Unknown { @@ -3193,12 +3165,11 @@ mod test { use futures::{executor::block_on, future::poll_fn}; use sc_block_builder::BlockBuilderProvider; use sc_network_common::{ - protocol::role::Role, + role::Role, sync::message::{BlockData, BlockState, FromBlock}, }; use sp_blockchain::HeaderBackend; use sp_consensus::block_validation::DefaultBlockAnnounceValidator; - use sp_runtime::generic::BlockId; use substrate_test_runtime_client::{ runtime::{Block, Hash, Header}, BlockBuilderExt, ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt, TestClient, @@ -3218,7 +3189,7 @@ mod test { let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); let (_chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); - let (mut sync, _, _) = ChainSync::new( + let (mut sync, _) = ChainSync::new( SyncMode::Full, client.clone(), ProtocolId::from("test-protocol-name"), @@ -3226,6 +3197,7 @@ mod test { Roles::from(&Role::Full), block_announce_validator, 1, + 64, None, None, chain_sync_network_handle, @@ -3284,7 +3256,7 @@ mod test { let (_chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); - let (mut sync, _, _) = ChainSync::new( + let (mut sync, _) = ChainSync::new( SyncMode::Full, client.clone(), ProtocolId::from("test-protocol-name"), @@ -3292,6 +3264,7 @@ mod test { Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 1, + 64, None, None, chain_sync_network_handle, @@ -3435,8 +3408,7 @@ mod test { fn build_block(client: &mut Arc, at: Option, fork: bool) -> Block { let at = at.unwrap_or_else(|| client.info().best_hash); - let mut block_builder = - client.new_block_at(&BlockId::Hash(at), Default::default(), false).unwrap(); + let mut block_builder = client.new_block_at(at, Default::default(), false).unwrap(); if fork { block_builder.push_storage_change(vec![1, 2, 3], Some(vec![4, 5, 6])).unwrap(); @@ -3466,7 +3438,7 @@ mod test { let (_chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); - let (mut sync, _, _) = ChainSync::new( + let (mut sync, _) = ChainSync::new( SyncMode::Full, client.clone(), ProtocolId::from("test-protocol-name"), @@ -3474,6 +3446,7 @@ mod test { Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 5, + 64, None, None, chain_sync_network_handle, @@ -3489,8 +3462,7 @@ mod test { let mut client2 = client.clone(); let mut build_block_at = |at, import| { - let mut block_builder = - client2.new_block_at(&BlockId::Hash(at), Default::default(), false).unwrap(); + let mut block_builder = client2.new_block_at(at, Default::default(), false).unwrap(); // Make sure we generate a different block as fork block_builder.push_storage_change(vec![1, 2, 3], Some(vec![4, 5, 6])).unwrap(); @@ -3593,7 +3565,7 @@ mod test { NetworkServiceProvider::new(); let info = client.info(); - let (mut sync, _, _) = ChainSync::new( + let (mut sync, _) = ChainSync::new( SyncMode::Full, client.clone(), ProtocolId::from("test-protocol-name"), @@ -3601,6 +3573,7 @@ mod test { Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 5, + 64, None, None, chain_sync_network_handle, @@ -3615,6 +3588,7 @@ mod test { let peer_id2 = PeerId::random(); let best_block = blocks.last().unwrap().clone(); + let max_blocks_to_request = sync.max_blocks_per_request; // Connect the node we will sync from sync.new_peer(peer_id1, best_block.hash(), *best_block.header().number()) .unwrap(); @@ -3624,8 +3598,8 @@ mod test { while best_block_num < MAX_DOWNLOAD_AHEAD { let request = get_block_request( &mut sync, - FromBlock::Number(MAX_BLOCKS_TO_REQUEST as u64 + best_block_num as u64), - MAX_BLOCKS_TO_REQUEST as u32, + FromBlock::Number(max_blocks_to_request as u64 + best_block_num as u64), + max_blocks_to_request as u32, &peer_id1, ); @@ -3639,14 +3613,14 @@ mod test { let res = sync.on_block_data(&peer_id1, Some(request), response).unwrap(); assert!(matches!( res, - OnBlockData::Import(_, blocks) if blocks.len() == MAX_BLOCKS_TO_REQUEST + OnBlockData::Import(_, blocks) if blocks.len() == max_blocks_to_request as usize ),); - best_block_num += MAX_BLOCKS_TO_REQUEST as u32; + best_block_num += max_blocks_to_request as u32; let _ = sync.on_blocks_processed( - MAX_BLOCKS_TO_REQUEST as usize, - MAX_BLOCKS_TO_REQUEST as usize, + max_blocks_to_request as usize, + max_blocks_to_request as usize, resp_blocks .iter() .rev() @@ -3704,8 +3678,8 @@ mod test { // peer 2 as well. get_block_request( &mut sync, - FromBlock::Number(peer1_from + MAX_BLOCKS_TO_REQUEST as u64), - MAX_BLOCKS_TO_REQUEST as u32, + FromBlock::Number(peer1_from + max_blocks_to_request as u64), + max_blocks_to_request as u32, &peer_id2, ); } @@ -3749,7 +3723,7 @@ mod test { let info = client.info(); - let (mut sync, _, _) = ChainSync::new( + let (mut sync, _) = ChainSync::new( SyncMode::Full, client.clone(), ProtocolId::from("test-protocol-name"), @@ -3757,6 +3731,7 @@ mod test { Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 5, + 64, None, None, chain_sync_network_handle, @@ -3802,11 +3777,12 @@ mod test { // Now request and import the fork. let mut best_block_num = *finalized_block.header().number() as u32; + let max_blocks_to_request = sync.max_blocks_per_request; while best_block_num < *fork_blocks.last().unwrap().header().number() as u32 - 1 { let request = get_block_request( &mut sync, - FromBlock::Number(MAX_BLOCKS_TO_REQUEST as u64 + best_block_num as u64), - MAX_BLOCKS_TO_REQUEST as u32, + FromBlock::Number(max_blocks_to_request as u64 + best_block_num as u64), + max_blocks_to_request as u32, &peer_id1, ); @@ -3820,14 +3796,14 @@ mod test { let res = sync.on_block_data(&peer_id1, Some(request), response).unwrap(); assert!(matches!( res, - OnBlockData::Import(_, blocks) if blocks.len() == MAX_BLOCKS_TO_REQUEST + OnBlockData::Import(_, blocks) if blocks.len() == sync.max_blocks_per_request as usize ),); - best_block_num += MAX_BLOCKS_TO_REQUEST as u32; + best_block_num += sync.max_blocks_per_request as u32; let _ = sync.on_blocks_processed( - MAX_BLOCKS_TO_REQUEST as usize, - MAX_BLOCKS_TO_REQUEST as usize, + max_blocks_to_request as usize, + max_blocks_to_request as usize, resp_blocks .iter() .rev() @@ -3890,7 +3866,7 @@ mod test { let info = client.info(); - let (mut sync, _, _) = ChainSync::new( + let (mut sync, _) = ChainSync::new( SyncMode::Full, client.clone(), ProtocolId::from("test-protocol-name"), @@ -3898,6 +3874,7 @@ mod test { Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 5, + 64, None, None, chain_sync_network_handle, @@ -3943,10 +3920,12 @@ mod test { // Now request and import the fork. let mut best_block_num = *finalized_block.header().number() as u32; + let max_blocks_to_request = sync.max_blocks_per_request; + let mut request = get_block_request( &mut sync, - FromBlock::Number(MAX_BLOCKS_TO_REQUEST as u64 + best_block_num as u64), - MAX_BLOCKS_TO_REQUEST as u32, + FromBlock::Number(max_blocks_to_request as u64 + best_block_num as u64), + max_blocks_to_request as u32, &peer_id1, ); let last_block_num = *fork_blocks.last().unwrap().header().number() as u32 - 1; @@ -3961,18 +3940,18 @@ mod test { let res = sync.on_block_data(&peer_id1, Some(request.clone()), response).unwrap(); assert!(matches!( res, - OnBlockData::Import(_, blocks) if blocks.len() == MAX_BLOCKS_TO_REQUEST + OnBlockData::Import(_, blocks) if blocks.len() == max_blocks_to_request as usize ),); - best_block_num += MAX_BLOCKS_TO_REQUEST as u32; + best_block_num += max_blocks_to_request as u32; if best_block_num < last_block_num { // make sure we're not getting a duplicate request in the time before the blocks are // processed request = get_block_request( &mut sync, - FromBlock::Number(MAX_BLOCKS_TO_REQUEST as u64 + best_block_num as u64), - MAX_BLOCKS_TO_REQUEST as u32, + FromBlock::Number(max_blocks_to_request as u64 + best_block_num as u64), + max_blocks_to_request as u32, &peer_id1, ); } @@ -3994,16 +3973,17 @@ mod test { // The import queue may send notifications in batches of varying size. So we simulate // this here by splitting the batch into 2 notifications. + let max_blocks_to_request = sync.max_blocks_per_request; let second_batch = notify_imported.split_off(notify_imported.len() / 2); let _ = sync.on_blocks_processed( - MAX_BLOCKS_TO_REQUEST as usize, - MAX_BLOCKS_TO_REQUEST as usize, + max_blocks_to_request as usize, + max_blocks_to_request as usize, notify_imported, ); let _ = sync.on_blocks_processed( - MAX_BLOCKS_TO_REQUEST as usize, - MAX_BLOCKS_TO_REQUEST as usize, + max_blocks_to_request as usize, + max_blocks_to_request as usize, second_batch, ); @@ -4031,7 +4011,7 @@ mod test { let mut client = Arc::new(TestClientBuilder::new().build()); let blocks = (0..3).map(|_| build_block(&mut client, None, false)).collect::>(); - let (mut sync, _, _) = ChainSync::new( + let (mut sync, _) = ChainSync::new( SyncMode::Full, client.clone(), ProtocolId::from("test-protocol-name"), @@ -4039,6 +4019,7 @@ mod test { Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 1, + 64, None, None, chain_sync_network_handle, @@ -4076,7 +4057,7 @@ mod test { let empty_client = Arc::new(TestClientBuilder::new().build()); - let (mut sync, _, _) = ChainSync::new( + let (mut sync, _) = ChainSync::new( SyncMode::Full, empty_client.clone(), ProtocolId::from("test-protocol-name"), @@ -4084,6 +4065,7 @@ mod test { Roles::from(&Role::Full), Box::new(DefaultBlockAnnounceValidator), 1, + 64, None, None, chain_sync_network_handle, @@ -4121,4 +4103,89 @@ mod test { let state = AncestorSearchState::::BinarySearch(1, 3); assert!(handle_ancestor_search_state(&state, 2, true).is_none()); } + + #[test] + fn sync_restart_removes_block_but_not_justification_requests() { + let mut client = Arc::new(TestClientBuilder::new().build()); + let block_announce_validator = Box::new(DefaultBlockAnnounceValidator); + let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); + let (_chain_sync_network_provider, chain_sync_network_handle) = + NetworkServiceProvider::new(); + let (mut sync, _) = ChainSync::new( + SyncMode::Full, + client.clone(), + ProtocolId::from("test-protocol-name"), + &Some(String::from("test-fork-id")), + Roles::from(&Role::Full), + block_announce_validator, + 1, + 64, + None, + None, + chain_sync_network_handle, + import_queue, + ProtocolName::from("block-request"), + ProtocolName::from("state-request"), + None, + ) + .unwrap(); + + let peers = vec![PeerId::random(), PeerId::random()]; + + let mut new_blocks = |n| { + for _ in 0..n { + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + } + + let info = client.info(); + (info.best_hash, info.best_number) + }; + + let (b1_hash, b1_number) = new_blocks(50); + + // add new peer and request blocks from them + sync.new_peer(peers[0], Hash::random(), 42).unwrap(); + + // we wil send block requests to these peers + // for these blocks we don't know about + for (peer, request) in sync.block_requests() { + sync.send_block_request(peer, request); + } + + // add a new peer at a known block + sync.new_peer(peers[1], b1_hash, b1_number).unwrap(); + + // we request a justification for a block we have locally + sync.request_justification(&b1_hash, b1_number); + + // the justification request should be scheduled to the + // new peer which is at the given block + let mut requests = sync.justification_requests().collect::>(); + assert_eq!(requests.len(), 1); + let (peer, request) = requests.remove(0); + sync.send_block_request(peer, request); + + assert!(!std::matches!( + sync.peers.get(&peers[0]).unwrap().state, + PeerSyncState::DownloadingJustification(_), + )); + assert_eq!( + sync.peers.get(&peers[1]).unwrap().state, + PeerSyncState::DownloadingJustification(b1_hash), + ); + assert_eq!(sync.pending_responses.len(), 2); + + let requests = sync.restart().collect::>(); + assert!(requests.iter().any(|res| res.as_ref().unwrap().0 == peers[0])); + + assert_eq!(sync.pending_responses.len(), 1); + assert!(sync.pending_responses.get(&peers[1]).is_some()); + assert_eq!( + sync.peers.get(&peers[1]).unwrap().state, + PeerSyncState::DownloadingJustification(b1_hash), + ); + sync.peer_disconnected(&peers[1]); + assert_eq!(sync.pending_responses.len(), 0); + } } diff --git a/client/network/sync/src/mock.rs b/client/network/sync/src/mock.rs index b59ea7e4fea7..838c6cf7667a 100644 --- a/client/network/sync/src/mock.rs +++ b/client/network/sync/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -59,7 +59,6 @@ mockall::mock! { request: Option>, response: BlockResponse, ) -> Result, BadPeer>; - fn process_block_response_data(&mut self, blocks_to_import: Result, BadPeer>); fn on_block_justification( &mut self, who: PeerId, diff --git a/client/network/sync/src/schema.rs b/client/network/sync/src/schema.rs index b31005360d02..22b7ee592778 100644 --- a/client/network/sync/src/schema.rs +++ b/client/network/sync/src/schema.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/network/sync/src/service/chain_sync.rs b/client/network/sync/src/service/chain_sync.rs index 50ded5b643de..f9e0e401fdf8 100644 --- a/client/network/sync/src/service/chain_sync.rs +++ b/client/network/sync/src/service/chain_sync.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,14 +16,26 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use futures::{channel::oneshot, Stream}; use libp2p::PeerId; + use sc_consensus::{BlockImportError, BlockImportStatus, JustificationSyncLink, Link}; -use sc_network_common::service::NetworkSyncForkRequest; -use sc_utils::mpsc::TracingUnboundedSender; +use sc_network::{NetworkBlock, NetworkSyncForkRequest}; +use sc_network_common::sync::{ + ExtendedPeerInfo, SyncEvent, SyncEventStream, SyncStatus, SyncStatusProvider, +}; +use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender}; use sp_runtime::traits::{Block as BlockT, NumberFor}; +use std::{ + pin::Pin, + sync::{ + atomic::{AtomicBool, AtomicUsize, Ordering}, + Arc, + }, +}; + /// Commands send to `ChainSync` -#[derive(Debug)] pub enum ToServiceCommand { SetSyncForkRequest(Vec, B::Hash, NumberFor), RequestJustification(B::Hash, NumberFor), @@ -34,24 +46,119 @@ pub enum ToServiceCommand { Vec<(Result>, BlockImportError>, B::Hash)>, ), JustificationImported(PeerId, B::Hash, NumberFor, bool), + AnnounceBlock(B::Hash, Option>), + NewBestBlockImported(B::Hash, NumberFor), + EventStream(TracingUnboundedSender), + Status(oneshot::Sender>), + NumActivePeers(oneshot::Sender), + SyncState(oneshot::Sender>), + BestSeenBlock(oneshot::Sender>>), + NumSyncPeers(oneshot::Sender), + NumQueuedBlocks(oneshot::Sender), + NumDownloadedBlocks(oneshot::Sender), + NumSyncRequests(oneshot::Sender), + PeersInfo(oneshot::Sender)>>), + OnBlockFinalized(B::Hash, B::Header), + // Status { + // pending_response: oneshot::Sender>, + // }, } /// Handle for communicating with `ChainSync` asynchronously #[derive(Clone)] -pub struct ChainSyncInterfaceHandle { +pub struct SyncingService { tx: TracingUnboundedSender>, + /// Number of peers we're connected to. + num_connected: Arc, + /// Are we actively catching up with the chain? + is_major_syncing: Arc, } -impl ChainSyncInterfaceHandle { +impl SyncingService { /// Create new handle - pub fn new(tx: TracingUnboundedSender>) -> Self { - Self { tx } + pub fn new( + tx: TracingUnboundedSender>, + num_connected: Arc, + is_major_syncing: Arc, + ) -> Self { + Self { tx, num_connected, is_major_syncing } + } + + /// Get the number of active peers. + pub async fn num_active_peers(&self) -> Result { + let (tx, rx) = oneshot::channel(); + let _ = self.tx.unbounded_send(ToServiceCommand::NumActivePeers(tx)); + + rx.await + } + + /// Get best seen block. + pub async fn best_seen_block(&self) -> Result>, oneshot::Canceled> { + let (tx, rx) = oneshot::channel(); + let _ = self.tx.unbounded_send(ToServiceCommand::BestSeenBlock(tx)); + + rx.await + } + + /// Get the number of sync peers. + pub async fn num_sync_peers(&self) -> Result { + let (tx, rx) = oneshot::channel(); + let _ = self.tx.unbounded_send(ToServiceCommand::NumSyncPeers(tx)); + + rx.await + } + + /// Get the number of queued blocks. + pub async fn num_queued_blocks(&self) -> Result { + let (tx, rx) = oneshot::channel(); + let _ = self.tx.unbounded_send(ToServiceCommand::NumQueuedBlocks(tx)); + + rx.await + } + + /// Get the number of downloaded blocks. + pub async fn num_downloaded_blocks(&self) -> Result { + let (tx, rx) = oneshot::channel(); + let _ = self.tx.unbounded_send(ToServiceCommand::NumDownloadedBlocks(tx)); + + rx.await + } + + /// Get the number of sync requests. + pub async fn num_sync_requests(&self) -> Result { + let (tx, rx) = oneshot::channel(); + let _ = self.tx.unbounded_send(ToServiceCommand::NumSyncRequests(tx)); + + rx.await + } + + /// Get peer information. + pub async fn peers_info( + &self, + ) -> Result)>, oneshot::Canceled> { + let (tx, rx) = oneshot::channel(); + let _ = self.tx.unbounded_send(ToServiceCommand::PeersInfo(tx)); + + rx.await + } + + /// Notify the `SyncingEngine` that a block has been finalized. + pub fn on_block_finalized(&self, hash: B::Hash, header: B::Header) { + let _ = self.tx.unbounded_send(ToServiceCommand::OnBlockFinalized(hash, header)); + } + + /// Get sync status + /// + /// Returns an error if `ChainSync` has terminated. + pub async fn status(&self) -> Result, ()> { + let (tx, rx) = oneshot::channel(); + let _ = self.tx.unbounded_send(ToServiceCommand::Status(tx)); + + rx.await.map_err(|_| ()) } } -impl NetworkSyncForkRequest> - for ChainSyncInterfaceHandle -{ +impl NetworkSyncForkRequest> for SyncingService { /// Configure an explicit fork sync request. /// /// Note that this function should not be used for recent blocks. @@ -67,7 +174,7 @@ impl NetworkSyncForkRequest> } } -impl JustificationSyncLink for ChainSyncInterfaceHandle { +impl JustificationSyncLink for SyncingService { /// Request a justification for the given block from the network. /// /// On success, the justification will be passed to the import queue that was part at @@ -81,7 +188,18 @@ impl JustificationSyncLink for ChainSyncInterfaceHandle { } } -impl Link for ChainSyncInterfaceHandle { +#[async_trait::async_trait] +impl SyncStatusProvider for SyncingService { + /// Get high-level view of the syncing status. + async fn status(&self) -> Result, ()> { + let (rtx, rrx) = oneshot::channel(); + + let _ = self.tx.unbounded_send(ToServiceCommand::Status(rtx)); + rrx.await.map_err(|_| ()) + } +} + +impl Link for SyncingService { fn blocks_processed( &mut self, imported: usize, @@ -109,3 +227,32 @@ impl Link for ChainSyncInterfaceHandle { let _ = self.tx.unbounded_send(ToServiceCommand::RequestJustification(*hash, number)); } } + +impl SyncEventStream for SyncingService { + /// Get syncing event stream. + fn event_stream(&self, name: &'static str) -> Pin + Send>> { + let (tx, rx) = tracing_unbounded(name, 100_000); + let _ = self.tx.unbounded_send(ToServiceCommand::EventStream(tx)); + Box::pin(rx) + } +} + +impl NetworkBlock> for SyncingService { + fn announce_block(&self, hash: B::Hash, data: Option>) { + let _ = self.tx.unbounded_send(ToServiceCommand::AnnounceBlock(hash, data)); + } + + fn new_best_block_imported(&self, hash: B::Hash, number: NumberFor) { + let _ = self.tx.unbounded_send(ToServiceCommand::NewBestBlockImported(hash, number)); + } +} + +impl sp_consensus::SyncOracle for SyncingService { + fn is_major_syncing(&self) -> bool { + self.is_major_syncing.load(Ordering::Relaxed) + } + + fn is_offline(&self) -> bool { + self.num_connected.load(Ordering::Relaxed) == 0 + } +} diff --git a/client/network/sync/src/service/mock.rs b/client/network/sync/src/service/mock.rs index d8aad2fa7bac..b3ef0f328140 100644 --- a/client/network/sync/src/service/mock.rs +++ b/client/network/sync/src/service/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,15 +18,17 @@ use futures::channel::oneshot; use libp2p::{Multiaddr, PeerId}; + use sc_consensus::{BlockImportError, BlockImportStatus}; -use sc_network_common::{ +use sc_network::{ config::MultiaddrWithPeerId, - protocol::ProtocolName, request_responses::{IfDisconnected, RequestFailure}, - service::{NetworkPeers, NetworkRequest, NetworkSyncForkRequest}, + types::ProtocolName, + NetworkNotification, NetworkPeers, NetworkRequest, NetworkSyncForkRequest, + NotificationSenderError, NotificationSenderT, ReputationChange, }; -use sc_peerset::ReputationChange; use sp_runtime::traits::{Block as BlockT, NumberFor}; + use std::collections::HashSet; mockall::mock! { @@ -98,12 +100,6 @@ mockall::mock! { peers: HashSet, ) -> Result<(), String>; fn remove_peers_from_reserved_set(&self, protocol: ProtocolName, peers: Vec); - fn add_to_peers_set( - &self, - protocol: ProtocolName, - peers: HashSet, - ) -> Result<(), String>; - fn remove_from_peers_set(&self, protocol: ProtocolName, peers: Vec); fn sync_num_connected(&self) -> usize; } @@ -125,4 +121,14 @@ mockall::mock! { connect: IfDisconnected, ); } + + impl NetworkNotification for Network { + fn write_notification(&self, target: PeerId, protocol: ProtocolName, message: Vec); + fn notification_sender( + &self, + target: PeerId, + protocol: ProtocolName, + ) -> Result, NotificationSenderError>; + fn set_notification_handshake(&self, protocol: ProtocolName, handshake: Vec); + } } diff --git a/client/network/sync/src/service/mod.rs b/client/network/sync/src/service/mod.rs index 692aa2698545..18331d63ed29 100644 --- a/client/network/sync/src/service/mod.rs +++ b/client/network/sync/src/service/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/network/sync/src/service/network.rs b/client/network/sync/src/service/network.rs index b81a65ae731c..12a47d6a9b54 100644 --- a/client/network/sync/src/service/network.rs +++ b/client/network/sync/src/service/network.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,19 +18,20 @@ use futures::{channel::oneshot, StreamExt}; use libp2p::PeerId; -use sc_network_common::{ - protocol::ProtocolName, + +use sc_network::{ request_responses::{IfDisconnected, RequestFailure}, - service::{NetworkPeers, NetworkRequest}, + types::ProtocolName, + NetworkNotification, NetworkPeers, NetworkRequest, ReputationChange, }; -use sc_peerset::ReputationChange; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; + use std::sync::Arc; /// Network-related services required by `sc-network-sync` -pub trait Network: NetworkPeers + NetworkRequest {} +pub trait Network: NetworkPeers + NetworkRequest + NetworkNotification {} -impl Network for T where T: NetworkPeers + NetworkRequest {} +impl Network for T where T: NetworkPeers + NetworkRequest + NetworkNotification {} /// Network service provider for `ChainSync` /// @@ -56,6 +57,12 @@ pub enum ToServiceCommand { oneshot::Sender, RequestFailure>>, IfDisconnected, ), + + /// Call `NetworkNotification::write_notification()` + WriteNotification(PeerId, ProtocolName, Vec), + + /// Call `NetworkNotification::set_notification_handshake()` + SetNotificationHandshake(ProtocolName, Vec), } /// Handle that is (temporarily) passed to `ChainSync` so it can @@ -94,6 +101,20 @@ impl NetworkServiceHandle { .tx .unbounded_send(ToServiceCommand::StartRequest(who, protocol, request, tx, connect)); } + + /// Send notification to peer + pub fn write_notification(&self, who: PeerId, protocol: ProtocolName, message: Vec) { + let _ = self + .tx + .unbounded_send(ToServiceCommand::WriteNotification(who, protocol, message)); + } + + /// Set handshake for the notification protocol. + pub fn set_notification_handshake(&self, protocol: ProtocolName, handshake: Vec) { + let _ = self + .tx + .unbounded_send(ToServiceCommand::SetNotificationHandshake(protocol, handshake)); + } } impl NetworkServiceProvider { @@ -114,6 +135,10 @@ impl NetworkServiceProvider { service.report_peer(peer, reputation_change), ToServiceCommand::StartRequest(peer, protocol, request, tx, connect) => service.start_request(peer, protocol, request, tx, connect), + ToServiceCommand::WriteNotification(peer, protocol, message) => + service.write_notification(peer, protocol, message), + ToServiceCommand::SetNotificationHandshake(protocol, handshake) => + service.set_notification_handshake(protocol, handshake), } } } @@ -133,7 +158,7 @@ mod tests { let peer = PeerId::random(); let proto = ProtocolName::from("test-protocol"); let proto_clone = proto.clone(); - let change = sc_peerset::ReputationChange::new_fatal("test-change"); + let change = sc_network::ReputationChange::new_fatal("test-change"); let mut mock_network = MockNetwork::new(); mock_network diff --git a/client/network/sync/src/state.rs b/client/network/sync/src/state.rs index 9f64b52334c8..305f0ee6838a 100644 --- a/client/network/sync/src/state.rs +++ b/client/network/sync/src/state.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -150,7 +150,7 @@ where } else { values.key_values }; - let mut entry = self.state.entry(values.state_root).or_default(); + let entry = self.state.entry(values.state_root).or_default(); if entry.0.len() > 0 && entry.1.len() > 1 { // Already imported child_trie with same root. // Warning this will not work with parallel download. diff --git a/client/network/sync/src/state_request_handler.rs b/client/network/sync/src/state_request_handler.rs index 98f1ed34d058..f3af9a384430 100644 --- a/client/network/sync/src/state_request_handler.rs +++ b/client/network/sync/src/state_request_handler.rs @@ -1,4 +1,4 @@ -// Copyright 2020 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify @@ -18,24 +18,23 @@ //! `crate::request_responses::RequestResponsesBehaviour`. use crate::schema::v1::{KeyValueStateEntry, StateEntry, StateRequest, StateResponse}; + use codec::{Decode, Encode}; -use futures::{ - channel::{mpsc, oneshot}, - stream::StreamExt, -}; +use futures::{channel::oneshot, stream::StreamExt}; use libp2p::PeerId; use log::{debug, trace}; -use lru::LruCache; use prost::Message; +use schnellru::{ByLength, LruMap}; + use sc_client_api::{BlockBackend, ProofProvider}; -use sc_network_common::{ +use sc_network::{ config::ProtocolId, request_responses::{IncomingRequest, OutgoingResponse, ProtocolConfig}, }; use sp_runtime::traits::Block as BlockT; + use std::{ hash::{Hash, Hasher}, - num::NonZeroUsize, sync::Arc, time::Duration, }; @@ -45,7 +44,7 @@ const MAX_RESPONSE_BYTES: usize = 2 * 1024 * 1024; // Actual reponse may be bigg const MAX_NUMBER_OF_SAME_REQUESTS_PER_PEER: usize = 2; mod rep { - use sc_peerset::ReputationChange as Rep; + use sc_network::ReputationChange as Rep; /// Reputation change when a peer sent us the same request multiple times. pub const SAME_REQUEST: Rep = Rep::new(i32::MIN, "Same state request multiple times"); @@ -91,7 +90,7 @@ struct SeenRequestsKey { start: Vec>, } -#[allow(clippy::derive_hash_xor_eq)] +#[allow(clippy::derived_hash_with_manual_eq)] impl Hash for SeenRequestsKey { fn hash(&self, state: &mut H) { self.peer.hash(state); @@ -111,11 +110,11 @@ enum SeenRequestsValue { /// Handler for incoming block requests from a remote peer. pub struct StateRequestHandler { client: Arc, - request_receiver: mpsc::Receiver, + request_receiver: async_channel::Receiver, /// Maps from request to number of times we have seen this request. /// /// This is used to check if a peer is spamming us with the same request. - seen_requests: LruCache, SeenRequestsValue>, + seen_requests: LruMap, SeenRequestsValue>, } impl StateRequestHandler @@ -132,7 +131,7 @@ where ) -> (Self, ProtocolConfig) { // Reserve enough request slots for one request per peer when we are at the maximum // number of peers. - let (tx, request_receiver) = mpsc::channel(num_peer_hint); + let (tx, request_receiver) = async_channel::bounded(num_peer_hint); let mut protocol_config = generate_protocol_config( protocol_id, @@ -145,9 +144,8 @@ where ); protocol_config.inbound_queue = Some(tx); - let capacity = - NonZeroUsize::new(num_peer_hint.max(1) * 2).expect("cache capacity is not zero"); - let seen_requests = LruCache::new(capacity); + let capacity = ByLength::new(num_peer_hint.max(1) as u32 * 2); + let seen_requests = LruMap::new(capacity); (Self { client, request_receiver, seen_requests }, protocol_config) } @@ -180,7 +178,7 @@ where let mut reputation_changes = Vec::new(); - match self.seen_requests.get_mut(&key) { + match self.seen_requests.get(&key) { Some(SeenRequestsValue::First) => {}, Some(SeenRequestsValue::Fulfilled(ref mut requests)) => { *requests = requests.saturating_add(1); @@ -190,7 +188,7 @@ where } }, None => { - self.seen_requests.put(key.clone(), SeenRequestsValue::First); + self.seen_requests.insert(key.clone(), SeenRequestsValue::First); }, } @@ -247,7 +245,7 @@ where .last() .map(|e| sp_core::hexdisplay::HexDisplay::from(&e.key))), ); - if let Some(value) = self.seen_requests.get_mut(&key) { + if let Some(value) = self.seen_requests.get(&key) { // If this is the first time we have processed this request, we need to change // it to `Fulfilled`. if let SeenRequestsValue::First = value { diff --git a/client/network/sync/src/tests.rs b/client/network/sync/src/tests.rs deleted file mode 100644 index 61de08443a6c..000000000000 --- a/client/network/sync/src/tests.rs +++ /dev/null @@ -1,78 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. -// 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 crate::{service::network::NetworkServiceProvider, ChainSync, ForkTarget}; - -use libp2p::PeerId; -use sc_network_common::{ - config::ProtocolId, - protocol::{ - role::{Role, Roles}, - ProtocolName, - }, - service::NetworkSyncForkRequest, - sync::ChainSync as ChainSyncT, -}; -use sp_consensus::block_validation::DefaultBlockAnnounceValidator; -use sp_core::H256; -use std::{sync::Arc, task::Poll}; -use substrate_test_runtime_client::{TestClientBuilder, TestClientBuilderExt as _}; - -// verify that the fork target map is empty, then submit a new sync fork request, -// poll `ChainSync` and verify that a new sync fork request has been registered -#[tokio::test] -async fn delegate_to_chainsync() { - let import_queue = Box::new(sc_consensus::import_queue::mock::MockImportQueueHandle::new()); - let (_chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); - let (mut chain_sync, chain_sync_service, _) = ChainSync::new( - sc_network_common::sync::SyncMode::Full, - Arc::new(TestClientBuilder::with_default_backend().build_with_longest_chain().0), - ProtocolId::from("test-protocol-name"), - &Some(String::from("test-fork-id")), - Roles::from(&Role::Full), - Box::new(DefaultBlockAnnounceValidator), - 1u32, - None, - None, - chain_sync_network_handle, - import_queue, - ProtocolName::from("block-request"), - ProtocolName::from("state-request"), - None, - ) - .unwrap(); - - let hash = H256::random(); - let in_number = 1337u64; - let peers = (0..3).map(|_| PeerId::random()).collect::>(); - - assert!(chain_sync.fork_targets.is_empty()); - chain_sync_service.set_sync_fork_request(peers, hash, in_number); - - futures::future::poll_fn(|cx| { - let _ = chain_sync.poll(cx); - Poll::Ready(()) - }) - .await; - - if let Some(ForkTarget { number, .. }) = chain_sync.fork_targets.get(&hash) { - assert_eq!(number, &in_number); - } else { - panic!("expected to contain `ForkTarget`"); - } -} diff --git a/client/network/sync/src/warp.rs b/client/network/sync/src/warp.rs index ab8a7c66b985..912ad78dfdd0 100644 --- a/client/network/sync/src/warp.rs +++ b/client/network/sync/src/warp.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -19,24 +19,35 @@ //! Warp sync support. use crate::{ + oneshot, schema::v1::{StateRequest, StateResponse}, state::{ImportResult, StateSync}, }; +use futures::FutureExt; +use log::error; use sc_client_api::ProofProvider; use sc_network_common::sync::{ message::{BlockAttributes, BlockData, BlockRequest, Direction, FromBlock}, warp::{ - EncodedProof, VerificationResult, WarpProofRequest, WarpSyncPhase, WarpSyncProgress, - WarpSyncProvider, + EncodedProof, VerificationResult, WarpProofRequest, WarpSyncParams, WarpSyncPhase, + WarpSyncProgress, WarpSyncProvider, }, }; use sp_blockchain::HeaderBackend; -use sp_finality_grandpa::{AuthorityList, SetId}; +use sp_consensus_grandpa::{AuthorityList, SetId}; use sp_runtime::traits::{Block as BlockT, Header, NumberFor, Zero}; -use std::sync::Arc; +use std::{sync::Arc, task::Poll}; enum Phase { - WarpProof { set_id: SetId, authorities: AuthorityList, last_hash: B::Hash }, + WarpProof { + set_id: SetId, + authorities: AuthorityList, + last_hash: B::Hash, + warp_sync_provider: Arc>, + }, + PendingTargetBlock { + target_block: Option>, + }, TargetBlock(B::Header), State(StateSync), } @@ -61,7 +72,6 @@ pub enum TargetBlockImportResult { pub struct WarpSync { phase: Phase, client: Arc, - warp_sync_provider: Arc>, total_proof_bytes: u64, } @@ -70,21 +80,56 @@ where B: BlockT, Client: HeaderBackend + ProofProvider + 'static, { - /// Create a new instance. - pub fn new(client: Arc, warp_sync_provider: Arc>) -> Self { + /// Create a new instance. When passing a warp sync provider we will be checking for proof and + /// authorities. Alternatively we can pass a target block when we want to skip downloading + /// proofs, in this case we will continue polling until the target block is known. + pub fn new(client: Arc, warp_sync_params: WarpSyncParams) -> Self { let last_hash = client.hash(Zero::zero()).unwrap().expect("Genesis header always exists"); - let phase = Phase::WarpProof { - set_id: 0, - authorities: warp_sync_provider.current_authorities(), - last_hash, + match warp_sync_params { + WarpSyncParams::WithProvider(warp_sync_provider) => { + let phase = Phase::WarpProof { + set_id: 0, + authorities: warp_sync_provider.current_authorities(), + last_hash, + warp_sync_provider: warp_sync_provider.clone(), + }; + Self { client, phase, total_proof_bytes: 0 } + }, + WarpSyncParams::WaitForTarget(block) => Self { + client, + phase: Phase::PendingTargetBlock { target_block: Some(block) }, + total_proof_bytes: 0, + }, + } + } + + /// Poll to make progress. + /// + /// This only makes progress when `phase = Phase::PendingTargetBlock` and the pending block was + /// sent. + pub fn poll(&mut self, cx: &mut std::task::Context) { + let new_phase = if let Phase::PendingTargetBlock { target_block: Some(target_block) } = + &mut self.phase + { + match target_block.poll_unpin(cx) { + Poll::Ready(Ok(target)) => Phase::TargetBlock(target), + Poll::Ready(Err(e)) => { + error!(target: "sync", "Failed to get target block. Error: {:?}",e); + Phase::PendingTargetBlock { target_block: None } + }, + _ => return, + } + } else { + return }; - Self { client, warp_sync_provider, phase, total_proof_bytes: 0 } + + self.phase = new_phase; } /// Validate and import a state response. pub fn import_state(&mut self, response: StateResponse) -> ImportResult { match &mut self.phase { - Phase::WarpProof { .. } | Phase::TargetBlock(_) => { + Phase::WarpProof { .. } | Phase::TargetBlock(_) | Phase::PendingTargetBlock { .. } => { log::debug!(target: "sync", "Unexpected state response"); ImportResult::BadResponse }, @@ -95,12 +140,12 @@ where /// Validate and import a warp proof response. pub fn import_warp_proof(&mut self, response: EncodedProof) -> WarpProofImportResult { match &mut self.phase { - Phase::State(_) | Phase::TargetBlock(_) => { + Phase::State(_) | Phase::TargetBlock(_) | Phase::PendingTargetBlock { .. } => { log::debug!(target: "sync", "Unexpected warp proof response"); WarpProofImportResult::BadResponse }, - Phase::WarpProof { set_id, authorities, last_hash } => { - match self.warp_sync_provider.verify(&response, *set_id, authorities.clone()) { + Phase::WarpProof { set_id, authorities, last_hash, warp_sync_provider } => + match warp_sync_provider.verify(&response, *set_id, authorities.clone()) { Err(e) => { log::debug!(target: "sync", "Bad warp proof response: {}", e); WarpProofImportResult::BadResponse @@ -119,15 +164,14 @@ where self.phase = Phase::TargetBlock(header); WarpProofImportResult::Success }, - } - }, + }, } } /// Import the target block body. pub fn import_target_block(&mut self, block: BlockData) -> TargetBlockImportResult { match &mut self.phase { - Phase::WarpProof { .. } | Phase::State(_) => { + Phase::WarpProof { .. } | Phase::State(_) | Phase::PendingTargetBlock { .. } => { log::debug!(target: "sync", "Unexpected target block response"); TargetBlockImportResult::BadResponse }, @@ -168,8 +212,8 @@ where /// Produce next state request. pub fn next_state_request(&self) -> Option { match &self.phase { - Phase::WarpProof { .. } => None, - Phase::TargetBlock(_) => None, + Phase::WarpProof { .. } | Phase::TargetBlock(_) | Phase::PendingTargetBlock { .. } => + None, Phase::State(sync) => Some(sync.next_request()), } } @@ -178,15 +222,14 @@ where pub fn next_warp_proof_request(&self) -> Option> { match &self.phase { Phase::WarpProof { last_hash, .. } => Some(WarpProofRequest { begin: *last_hash }), - Phase::TargetBlock(_) => None, - Phase::State(_) => None, + Phase::TargetBlock(_) | Phase::State(_) | Phase::PendingTargetBlock { .. } => None, } } /// Produce next target block request. pub fn next_target_block_request(&self) -> Option<(NumberFor, BlockRequest)> { match &self.phase { - Phase::WarpProof { .. } => None, + Phase::WarpProof { .. } | Phase::State(_) | Phase::PendingTargetBlock { .. } => None, Phase::TargetBlock(header) => { let request = BlockRequest:: { id: 0, @@ -198,15 +241,14 @@ where }; Some((*header.number(), request)) }, - Phase::State(_) => None, } } /// Return target block hash if it is known. pub fn target_block_hash(&self) -> Option { match &self.phase { - Phase::WarpProof { .. } => None, - Phase::TargetBlock(_) => None, + Phase::WarpProof { .. } | Phase::TargetBlock(_) | Phase::PendingTargetBlock { .. } => + None, Phase::State(s) => Some(s.target()), } } @@ -214,7 +256,7 @@ where /// Return target block number if it is known. pub fn target_block_number(&self) -> Option> { match &self.phase { - Phase::WarpProof { .. } => None, + Phase::WarpProof { .. } | Phase::PendingTargetBlock { .. } => None, Phase::TargetBlock(header) => Some(*header.number()), Phase::State(s) => Some(s.target_block_num()), } @@ -223,8 +265,8 @@ where /// Check if the state is complete. pub fn is_complete(&self) -> bool { match &self.phase { - Phase::WarpProof { .. } => false, - Phase::TargetBlock(_) => false, + Phase::WarpProof { .. } | Phase::TargetBlock(_) | Phase::PendingTargetBlock { .. } => + false, Phase::State(sync) => sync.is_complete(), } } @@ -240,6 +282,10 @@ where phase: WarpSyncPhase::DownloadingTargetBlock, total_bytes: self.total_proof_bytes, }, + Phase::PendingTargetBlock { .. } => WarpSyncProgress { + phase: WarpSyncPhase::AwaitingTargetBlock, + total_bytes: self.total_proof_bytes, + }, Phase::State(sync) => WarpSyncProgress { phase: if self.is_complete() { WarpSyncPhase::ImportingState diff --git a/client/network/sync/src/warp_request_handler.rs b/client/network/sync/src/warp_request_handler.rs index e675bf45cad9..a49a65af51d0 100644 --- a/client/network/sync/src/warp_request_handler.rs +++ b/client/network/sync/src/warp_request_handler.rs @@ -1,4 +1,4 @@ -// Copyright 2022 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify @@ -17,23 +17,25 @@ //! Helper for handling (i.e. answering) grandpa warp sync requests from a remote peer. use codec::Decode; -use futures::{ - channel::{mpsc, oneshot}, - stream::StreamExt, -}; +use futures::{channel::oneshot, stream::StreamExt}; use log::debug; -use sc_network_common::{ + +use sc_network::{ config::ProtocolId, request_responses::{ IncomingRequest, OutgoingResponse, ProtocolConfig as RequestResponseConfig, }, - sync::warp::{EncodedProof, WarpProofRequest, WarpSyncProvider}, }; +use sc_network_common::sync::warp::{EncodedProof, WarpProofRequest, WarpSyncProvider}; use sp_runtime::traits::Block as BlockT; + use std::{sync::Arc, time::Duration}; const MAX_RESPONSE_SIZE: u64 = 16 * 1024 * 1024; +/// Incoming warp requests bounded queue size. +const MAX_WARP_REQUEST_QUEUE: usize = 20; + /// Generates a [`RequestResponseConfig`] for the grandpa warp sync request protocol, refusing /// incoming requests. pub fn generate_request_response_config>( @@ -70,7 +72,7 @@ fn generate_legacy_protocol_name(protocol_id: ProtocolId) -> String { /// Handler for incoming grandpa warp sync requests from a remote peer. pub struct RequestHandler { backend: Arc>, - request_receiver: mpsc::Receiver, + request_receiver: async_channel::Receiver, } impl RequestHandler { @@ -81,7 +83,7 @@ impl RequestHandler { fork_id: Option<&str>, backend: Arc>, ) -> (Self, RequestResponseConfig) { - let (tx, request_receiver) = mpsc::channel(20); + let (tx, request_receiver) = async_channel::bounded(MAX_WARP_REQUEST_QUEUE); let mut request_response_config = generate_request_response_config(protocol_id, genesis_hash, fork_id); diff --git a/client/network/test/Cargo.toml b/client/network/test/Cargo.toml index 8368fa278712..ddd218051c48 100644 --- a/client/network/test/Cargo.toml +++ b/client/network/test/Cargo.toml @@ -17,7 +17,7 @@ tokio = "1.22.0" async-trait = "0.1.57" futures = "0.3.21" futures-timer = "3.0.1" -libp2p = "0.50.0" +libp2p = "0.51.3" log = "0.4.17" parking_lot = "0.12.1" rand = "0.8.5" @@ -26,14 +26,14 @@ sc-client-api = { version = "4.0.0-dev", path = "../../api" } sc-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } sc-network = { version = "0.10.0-dev", path = "../" } sc-network-common = { version = "0.10.0-dev", path = "../common" } +sc-utils = { version = "4.0.0-dev", path = "../../utils" } sc-network-light = { version = "0.10.0-dev", path = "../light" } sc-network-sync = { version = "0.10.0-dev", path = "../sync" } sc-service = { version = "0.10.0-dev", default-features = false, features = ["test-helpers"], path = "../../service" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sp-consensus-babe = { version = "0.10.0-dev", path = "../../../primitives/consensus/babe" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } -sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } +sp-tracing = { version = "10.0.0", path = "../../../primitives/tracing" } substrate-test-runtime = { version = "2.0.0", path = "../../../test-utils/runtime" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/client/network/test/src/block_import.rs b/client/network/test/src/block_import.rs index 250f5f5fd102..25e3b9bee87f 100644 --- a/client/network/test/src/block_import.rs +++ b/client/network/test/src/block_import.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/network/test/src/lib.rs b/client/network/test/src/lib.rs index b3653ac7c0f8..c41cdbd5a2c2 100644 --- a/client/network/test/src/lib.rs +++ b/client/network/test/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -20,6 +20,8 @@ #[cfg(test)] mod block_import; #[cfg(test)] +mod service; +#[cfg(test)] mod sync; use std::{ @@ -31,7 +33,7 @@ use std::{ time::Duration, }; -use futures::{future::BoxFuture, prelude::*}; +use futures::{channel::oneshot, future::BoxFuture, pin_mut, prelude::*}; use libp2p::{build_multiaddr, PeerId}; use log::trace; use parking_lot::Mutex; @@ -47,25 +49,30 @@ use sc_consensus::{ LongestChain, Verifier, }; use sc_network::{ - config::{NetworkConfiguration, RequestResponseConfig, Role, SyncMode}, - Multiaddr, NetworkService, NetworkWorker, + config::{ + FullNetworkConfiguration, MultiaddrWithPeerId, NetworkConfiguration, NonDefaultSetConfig, + NonReservedPeerMode, ProtocolId, Role, SyncMode, TransportConfig, + }, + request_responses::ProtocolConfig as RequestResponseConfig, + types::ProtocolName, + Multiaddr, NetworkBlock, NetworkService, NetworkStateInfo, NetworkSyncForkRequest, + NetworkWorker, }; use sc_network_common::{ - config::{ - MultiaddrWithPeerId, NonDefaultSetConfig, NonReservedPeerMode, ProtocolId, TransportConfig, + role::Roles, + sync::warp::{ + AuthorityList, EncodedProof, SetId, VerificationResult, WarpSyncParams, WarpSyncProvider, }, - protocol::{role::Roles, ProtocolName}, - service::{NetworkBlock, NetworkStateInfo, NetworkSyncForkRequest}, - sync::warp::{AuthorityList, EncodedProof, SetId, VerificationResult, WarpSyncProvider}, }; use sc_network_light::light_client_requests::handler::LightClientRequestHandler; use sc_network_sync::{ - block_request_handler::BlockRequestHandler, service::network::NetworkServiceProvider, - state_request_handler::StateRequestHandler, warp_request_handler, ChainSync, + block_request_handler::BlockRequestHandler, + service::{chain_sync::SyncingService, network::NetworkServiceProvider}, + state_request_handler::StateRequestHandler, + warp_request_handler, }; use sc_service::client::Client; use sp_blockchain::{ - well_known_cache_keys::{self, Id as CacheKeyId}, Backend as BlockchainBackend, HeaderBackend, Info as BlockchainInfo, Result as ClientResult, }; use sp_consensus::{ @@ -75,19 +82,17 @@ use sp_consensus::{ use sp_core::H256; use sp_runtime::{ codec::{Decode, Encode}, - generic::{BlockId, OpaqueDigestItemId}, - traits::{Block as BlockT, Header as HeaderT, NumberFor}, + generic::BlockId, + traits::{Block as BlockT, Header as HeaderT, NumberFor, Zero}, Justification, Justifications, }; use substrate_test_runtime_client::AccountKeyring; pub use substrate_test_runtime_client::{ - runtime::{Block, Extrinsic, Hash, Transfer}, + runtime::{Block, ExtrinsicBuilder, Hash, Header, Transfer}, TestClient, TestClientBuilder, TestClientBuilderExt, }; use tokio::time::timeout; -type AuthorityId = sp_consensus_babe::AuthorityId; - /// A Verifier that accepts all blocks and passes them on with the configured /// finality to be imported. #[derive(Clone)] @@ -110,20 +115,12 @@ impl Verifier for PassThroughVerifier { async fn verify( &mut self, mut block: BlockImportParams, - ) -> Result<(BlockImportParams, Option)>>), String> { - let maybe_keys = block - .header - .digest() - .log(|l| { - l.try_as_raw(OpaqueDigestItemId::Consensus(b"aura")) - .or_else(|| l.try_as_raw(OpaqueDigestItemId::Consensus(b"babe"))) - }) - .map(|blob| vec![(well_known_cache_keys::AUTHORITIES, blob.to_vec())]); + ) -> Result, String> { if block.fork_choice.is_none() { block.fork_choice = Some(ForkChoiceStrategy::LongestChain); }; block.finalized = self.finalized; - Ok((block, maybe_keys)) + Ok(block) } } @@ -222,9 +219,8 @@ impl BlockImport for PeersClient { async fn import_block( &mut self, block: BlockImportParams, - cache: HashMap>, ) -> Result { - self.client.import_block(block.clear_storage_changes_and_mutate(), cache).await + self.client.import_block(block.clear_storage_changes_and_mutate()).await } } @@ -239,7 +235,8 @@ pub struct Peer { block_import: BlockImportAdapter, select_chain: Option>, backend: Option>, - network: NetworkWorker::Hash, PeersFullClient>, + network: NetworkWorker::Hash>, + sync_service: Arc>, imported_blocks_stream: Pin> + Send>>, finality_notification_stream: Pin> + Send>>, listen_addr: Multiaddr, @@ -257,7 +254,7 @@ where /// Returns true if we're major syncing. pub fn is_major_syncing(&self) -> bool { - self.network.service().is_major_syncing() + self.sync_service.is_major_syncing() } // Returns a clone of the local SelectChain, only available on full nodes @@ -273,23 +270,23 @@ where } /// Returns the number of downloaded blocks. - pub fn num_downloaded_blocks(&self) -> usize { - self.network.num_downloaded_blocks() + pub async fn num_downloaded_blocks(&self) -> usize { + self.sync_service.num_downloaded_blocks().await.unwrap() } /// Returns true if we have no peer. pub fn is_offline(&self) -> bool { - self.num_peers() == 0 + self.sync_service.is_offline() } /// Request a justification for the given block. pub fn request_justification(&self, hash: &::Hash, number: NumberFor) { - self.network.service().request_justification(hash, number); + self.sync_service.request_justification(hash, number); } /// Announces an important block on the network. pub fn announce_block(&self, hash: ::Hash, data: Option>) { - self.network.service().announce_block(hash, data); + self.sync_service.announce_block(hash, data); } /// Request explicit fork sync. @@ -299,7 +296,7 @@ where hash: ::Hash, number: NumberFor, ) { - self.network.service().set_sync_fork_request(peers, hash, number); + self.sync_service.set_sync_fork_request(peers, hash, number); } /// Add blocks to the peer -- edit the block before adding @@ -375,8 +372,7 @@ where let full_client = self.client.as_client(); let mut at = full_client.block_hash_from_id(&at).unwrap().unwrap(); for _ in 0..count { - let builder = - full_client.new_block_at(&BlockId::Hash(at), Default::default(), false).unwrap(); + let builder = full_client.new_block_at(at, Default::default(), false).unwrap(); let block = edit_block(builder); let hash = block.header.hash(); trace!( @@ -390,25 +386,20 @@ where let mut import_block = BlockImportParams::new(origin, header.clone()); import_block.body = if headers_only { None } else { Some(block.extrinsics) }; import_block.fork_choice = Some(fork_choice); - let (import_block, cache) = + let import_block = futures::executor::block_on(self.verifier.verify(import_block)).unwrap(); - let cache = if let Some(cache) = cache { - cache.into_iter().collect() - } else { - Default::default() - }; - futures::executor::block_on(self.block_import.import_block(import_block, cache)) + futures::executor::block_on(self.block_import.import_block(import_block)) .expect("block_import failed"); if announce_block { - self.network.service().announce_block(hash, None); + self.sync_service.announce_block(hash, None); } hashes.push(hash); at = hash; } if inform_sync_about_new_best_block { - self.network.new_best_block_imported( + self.sync_service.new_best_block_imported( at, *full_client.header(at).ok().flatten().unwrap().number(), ); @@ -481,7 +472,7 @@ where amount: 1, nonce, }; - builder.push(transfer.into_signed_tx()).unwrap(); + builder.push(transfer.into_unchecked_extrinsic()).unwrap(); nonce += 1; builder.build().unwrap().block }, @@ -504,16 +495,6 @@ where } } - pub fn push_authorities_change_block( - &mut self, - new_authorities: Vec, - ) -> Vec { - self.generate_blocks(1, BlockOrigin::File, |mut builder| { - builder.push(Extrinsic::AuthoritiesChange(new_authorities.clone())).unwrap(); - builder.build().unwrap().block - }) - } - /// Get a reference to the client. pub fn client(&self) -> &PeersClient { &self.client @@ -524,8 +505,12 @@ where self.network.service() } + pub fn sync_service(&self) -> &Arc> { + &self.sync_service + } + /// Get a reference to the network worker. - pub fn network(&self) -> &NetworkWorker::Hash, PeersFullClient> { + pub fn network(&self) -> &NetworkWorker::Hash> { &self.network } @@ -627,9 +612,8 @@ where async fn import_block( &mut self, block: BlockImportParams, - cache: HashMap>, ) -> Result { - self.inner.import_block(block.clear_storage_changes_and_mutate(), cache).await + self.inner.import_block(block.clear_storage_changes_and_mutate()).await } } @@ -644,7 +628,7 @@ impl Verifier for VerifierAdapter { async fn verify( &mut self, block: BlockImportParams, - ) -> Result<(BlockImportParams, Option)>>), String> { + ) -> Result, String> { let hash = block.header.hash(); self.verifier.lock().await.verify(block).await.map_err(|e| { self.failed_verifications.lock().insert(hash, e.clone()); @@ -722,16 +706,18 @@ pub struct FullPeerConfig { pub extra_storage: Option, /// Enable transaction indexing. pub storage_chain: bool, + /// Optional target block header to sync to + pub target_block: Option<::Header>, } #[async_trait::async_trait] -pub trait TestNetFactory: Default + Sized +pub trait TestNetFactory: Default + Sized + Send where >::Transaction: Send, { type Verifier: 'static + Verifier; type BlockImport: BlockImport + Clone + Send + Sync + 'static; - type PeerData: Default; + type PeerData: Default + Send; /// This one needs to be implemented! fn make_verifier(&self, client: PeersClient, peer_data: &Self::PeerData) -> Self::Verifier; @@ -739,6 +725,7 @@ where /// Get reference to peer. fn peer(&mut self, i: usize) -> &mut Peer; fn peers(&self) -> &Vec>; + fn peers_mut(&mut self) -> &mut Vec>; fn mut_peers>)>( &mut self, closure: F, @@ -783,7 +770,7 @@ where *genesis_extra_storage = storage; } - if matches!(config.sync_mode, SyncMode::Fast { .. } | SyncMode::Warp) { + if matches!(config.sync_mode, SyncMode::LightState { .. } | SyncMode::Warp) { test_client_builder = test_client_builder.set_no_genesis(); } let backend = test_client_builder.backend(); @@ -813,20 +800,6 @@ where network_config.transport = TransportConfig::MemoryOnly; network_config.listen_addresses = vec![listen_addr.clone()]; network_config.allow_non_globals_in_dht = true; - network_config - .request_response_protocols - .extend(config.request_response_protocols); - network_config.extra_sets = config - .notifications_protocols - .into_iter() - .map(|p| NonDefaultSetConfig { - notifications_protocol: p, - fallback_names: Vec::new(), - max_notification_size: 1024 * 1024, - handshake: None, - set_config: Default::default(), - }) - .collect(); if let Some(connect_to) = config.connect_to_peers { let addrs = connect_to .iter() @@ -839,6 +812,7 @@ where network_config.default_peers_set.reserved_nodes = addrs; network_config.default_peers_set.non_reserved_mode = NonReservedPeerMode::Deny; } + let mut full_net_config = FullNetworkConfiguration::new(&network_config); let protocol_id = ProtocolId::from("test-protocol-name"); @@ -867,6 +841,15 @@ where let warp_sync = Arc::new(TestWarpSyncProvider(client.clone())); + let warp_sync_params = match config.target_block { + Some(target_block) => { + let (sender, receiver) = oneshot::channel::<::Header>(); + let _ = sender.send(target_block); + WarpSyncParams::WaitForTarget(receiver) + }, + _ => WarpSyncParams::WithProvider(warp_sync.clone()), + }; + let warp_protocol_config = { let (handler, protocol_config) = warp_request_handler::RequestHandler::new( protocol_id.clone(), @@ -887,52 +870,65 @@ where .unwrap_or_else(|| Box::new(DefaultBlockAnnounceValidator)); let (chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); - let (chain_sync, chain_sync_service, block_announce_config) = ChainSync::new( - match network_config.sync_mode { - SyncMode::Full => sc_network_common::sync::SyncMode::Full, - SyncMode::Fast { skip_proofs, storage_chain_mode } => - sc_network_common::sync::SyncMode::LightState { - skip_proofs, - storage_chain_mode, - }, - SyncMode::Warp => sc_network_common::sync::SyncMode::Warp, - }, - client.clone(), - protocol_id.clone(), - &fork_id, - Roles::from(if config.is_authority { &Role::Authority } else { &Role::Full }), - block_announce_validator, - network_config.max_parallel_downloads, - Some(warp_sync), - None, - chain_sync_network_handle, - import_queue.service(), - block_request_protocol_config.name.clone(), - state_request_protocol_config.name.clone(), - Some(warp_protocol_config.name.clone()), - ) - .unwrap(); + let (tx, rx) = sc_utils::mpsc::tracing_unbounded("mpsc_syncing_engine_protocol", 100_000); + let (engine, sync_service, block_announce_config) = + sc_network_sync::engine::SyncingEngine::new( + Roles::from(if config.is_authority { &Role::Authority } else { &Role::Full }), + client.clone(), + None, + &full_net_config, + protocol_id.clone(), + &fork_id, + block_announce_validator, + Some(warp_sync_params), + chain_sync_network_handle, + import_queue.service(), + block_request_protocol_config.name.clone(), + state_request_protocol_config.name.clone(), + Some(warp_protocol_config.name.clone()), + rx, + ) + .unwrap(); + let sync_service_import_queue = Box::new(sync_service.clone()); + let sync_service = Arc::new(sync_service.clone()); + + for config in config.request_response_protocols { + full_net_config.add_request_response_protocol(config); + } + for config in [ + block_request_protocol_config, + state_request_protocol_config, + light_client_request_protocol_config, + warp_protocol_config, + ] { + full_net_config.add_request_response_protocol(config); + } + + for protocol in config.notifications_protocols { + full_net_config.add_notification_protocol(NonDefaultSetConfig { + notifications_protocol: protocol, + fallback_names: Vec::new(), + max_notification_size: 1024 * 1024, + handshake: None, + set_config: Default::default(), + }); + } + + let genesis_hash = + client.hash(Zero::zero()).ok().flatten().expect("Genesis block exists; qed"); let network = NetworkWorker::new(sc_network::config::Params { role: if config.is_authority { Role::Authority } else { Role::Full }, executor: Box::new(|f| { tokio::spawn(f); }), - network_config, - chain: client.clone(), + network_config: full_net_config, + genesis_hash, protocol_id, fork_id, - chain_sync: Box::new(chain_sync), - chain_sync_service: Box::new(chain_sync_service.clone()), metrics_registry: None, block_announce_config, - request_response_protocol_configs: [ - block_request_protocol_config, - state_request_protocol_config, - light_client_request_protocol_config, - warp_protocol_config, - ] - .to_vec(), + tx, }) .unwrap(); @@ -942,8 +938,13 @@ where tokio::spawn(async move { chain_sync_network_provider.run(service).await; }); + tokio::spawn(async move { - import_queue.run(Box::new(chain_sync_service)).await; + import_queue.run(sync_service_import_queue).await; + }); + + tokio::spawn(async move { + engine.run().await; }); self.mut_peers(move |peers| { @@ -966,6 +967,7 @@ where block_import, verifier, network, + sync_service, listen_addr, }); }); @@ -976,71 +978,75 @@ where tokio::spawn(f); } - /// Polls the testnet until all nodes are in sync. + /// Polls the testnet until all peers are connected to each other. /// /// Must be executed in a task context. - fn poll_until_sync(&mut self, cx: &mut FutureContext) -> Poll<()> { + fn poll_until_connected(&mut self, cx: &mut FutureContext) -> Poll<()> { self.poll(cx); - // Return `NotReady` if there's a mismatch in the highest block number. + let num_peers = self.peers().len(); + if self.peers().iter().all(|p| p.num_peers() == num_peers - 1) { + return Poll::Ready(()) + } + + Poll::Pending + } + + async fn is_in_sync(&mut self) -> bool { let mut highest = None; - for peer in self.peers().iter() { - if peer.is_major_syncing() || peer.network.num_queued_blocks() != 0 { - return Poll::Pending + let peers = self.peers_mut(); + + for peer in peers { + if peer.sync_service.is_major_syncing() || + peer.sync_service.num_queued_blocks().await.unwrap() != 0 + { + return false } - if peer.network.num_sync_requests() != 0 { - return Poll::Pending + if peer.sync_service.num_sync_requests().await.unwrap() != 0 { + return false } match (highest, peer.client.info().best_hash) { (None, b) => highest = Some(b), (Some(ref a), ref b) if a == b => {}, - (Some(_), _) => return Poll::Pending, + (Some(_), _) => return false, } } - Poll::Ready(()) - } - /// Polls the testnet until theres' no activiy of any kind. - /// - /// Must be executed in a task context. - fn poll_until_idle(&mut self, cx: &mut FutureContext) -> Poll<()> { - self.poll(cx); + true + } - for peer in self.peers().iter() { - if peer.is_major_syncing() || peer.network.num_queued_blocks() != 0 { - return Poll::Pending + async fn is_idle(&mut self) -> bool { + let peers = self.peers_mut(); + for peer in peers { + if peer.sync_service.num_queued_blocks().await.unwrap() != 0 { + return false } - if peer.network.num_sync_requests() != 0 { - return Poll::Pending + if peer.sync_service.num_sync_requests().await.unwrap() != 0 { + return false } } - Poll::Ready(()) + true } - /// Polls the testnet until all peers are connected to each other. + /// Blocks the current thread until we are sync'ed. + /// Wait until we are sync'ed. /// - /// Must be executed in a task context. - fn poll_until_connected(&mut self, cx: &mut FutureContext) -> Poll<()> { - self.poll(cx); - - let num_peers = self.peers().len(); - if self.peers().iter().all(|p| p.num_peers() == num_peers - 1) { - return Poll::Ready(()) - } - - Poll::Pending - } - - /// Run the network until we are sync'ed. - /// - /// Calls `poll_until_sync` repeatedly. /// (If we've not synced within 10 mins then panic rather than hang.) async fn run_until_sync(&mut self) { - timeout( - Duration::from_secs(10 * 60), - futures::future::poll_fn::<(), _>(|cx| self.poll_until_sync(cx)), - ) + timeout(Duration::from_secs(10 * 60), async { + loop { + futures::future::poll_fn::<(), _>(|cx| { + self.poll(cx); + Poll::Ready(()) + }) + .await; + + if self.is_in_sync().await { + break + } + } + }) .await .expect("sync didn't happen within 10 mins"); } @@ -1049,7 +1055,17 @@ where /// /// Calls `poll_until_idle` repeatedly with the runtime passed as parameter. async fn run_until_idle(&mut self) { - futures::future::poll_fn::<(), _>(|cx| self.poll_until_idle(cx)).await; + loop { + futures::future::poll_fn::<(), _>(|cx| { + self.poll(cx); + Poll::Ready(()) + }) + .await; + + if self.is_idle().await { + break + } + } } /// Run the network until all peers are connected to each other. @@ -1064,8 +1080,17 @@ where self.mut_peers(|peers| { for (i, peer) in peers.iter_mut().enumerate() { trace!(target: "sync", "-- Polling {}: {}", i, peer.id()); - if let Poll::Ready(()) = peer.network.poll_unpin(cx) { - panic!("NetworkWorker has terminated unexpectedly.") + loop { + // The code below is not quite correct, because we are polling a different + // instance of the future every time. But as long as + // `NetworkWorker::next_action()` contains just streams polling not interleaved + // with other `.await`s, dropping the future and recreating it works the same as + // polling a single instance. + let net_poll_future = peer.network.next_action(); + pin_mut!(net_poll_future); + if let Poll::Pending = net_poll_future.poll(cx) { + break + } } trace!(target: "sync", "-- Polling complete {}: {}", i, peer.id()); @@ -1073,14 +1098,14 @@ where while let Poll::Ready(Some(notification)) = peer.imported_blocks_stream.as_mut().poll_next(cx) { - peer.network.service().announce_block(notification.hash, None); + peer.sync_service.announce_block(notification.hash, None); } // We poll `finality_notification_stream`. while let Poll::Ready(Some(notification)) = peer.finality_notification_stream.as_mut().poll_next(cx) { - peer.network.on_block_finalized(notification.hash, notification.header); + peer.sync_service.on_block_finalized(notification.hash, notification.header); } } }); @@ -1120,6 +1145,10 @@ impl TestNetFactory for TestNet { &self.peers } + fn peers_mut(&mut self) -> &mut Vec> { + &mut self.peers + } + fn mut_peers>)>(&mut self, closure: F) { closure(&mut self.peers); } @@ -1167,6 +1196,10 @@ impl TestNetFactory for JustificationTestNet { self.0.peers() } + fn peers_mut(&mut self) -> &mut Vec> { + self.0.peers_mut() + } + fn mut_peers>)>( &mut self, closure: F, diff --git a/client/network/test/src/peerset.rs b/client/network/test/src/peerset.rs new file mode 100644 index 000000000000..855d2339eda1 --- /dev/null +++ b/client/network/test/src/peerset.rs @@ -0,0 +1,402 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 futures::prelude::*; +use libp2p_identity::PeerId; +use rand::{ + distributions::{Distribution, Uniform, WeightedIndex}, + seq::IteratorRandom, +}; +use sc_peerset::{ + DropReason, IncomingIndex, Message, Peerset, PeersetConfig, ReputationChange, SetConfig, SetId, +}; +use std::{ + collections::{HashMap, HashSet}, + pin::Pin, + task::Poll, +}; + +/// Peer events as observed by `Notifications` / fuzz test. +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +enum Event { + /// Either API requested to disconnect from the peer, or the peer dropped. + Disconnected, + /// Incoming request. + Incoming, + /// Answer from PSM: accept. + PsmAccept, + /// Answer from PSM: reject. + PsmReject, + /// Command from PSM: connect. + PsmConnect, + /// Command from PSM: drop connection. + PsmDrop, +} + +/// Simplified peer state as thought by `Notifications` / fuzz test. +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +enum State { + /// Peer is not connected. + Disconnected, + /// We have an inbound connection, but have not decided yet whether to accept it. + Incoming(IncomingIndex), + /// Peer is connected via an inbound connection. + Inbound, + /// Peer is connected via an outbound connection. + Outbound, +} + +/// Bare simplified state without incoming index. +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +enum BareState { + /// Peer is not connected. + Disconnected, + /// We have an inbound connection, but have not decided yet whether to accept it. + Incoming, + /// Peer is connected via an inbound connection. + Inbound, + /// Peer is connected via an outbound connection. + Outbound, +} + +fn discard_incoming_index(state: State) -> BareState { + match state { + State::Disconnected => BareState::Disconnected, + State::Incoming(_) => BareState::Incoming, + State::Inbound => BareState::Inbound, + State::Outbound => BareState::Outbound, + } +} + +#[test] +fn run() { + sp_tracing::try_init_simple(); + + for _ in 0..50 { + test_once(); + } +} + +fn test_once() { + // Allowed events that can be received in a specific state. + let allowed_events: HashMap> = [ + ( + BareState::Disconnected, + [Event::Incoming, Event::PsmConnect, Event::PsmDrop /* must be ignored */] + .into_iter() + .collect::>(), + ), + ( + BareState::Incoming, + [Event::PsmAccept, Event::PsmReject].into_iter().collect::>(), + ), + ( + BareState::Inbound, + [Event::Disconnected, Event::PsmDrop, Event::PsmConnect /* must be ignored */] + .into_iter() + .collect::>(), + ), + ( + BareState::Outbound, + [Event::Disconnected, Event::PsmDrop, Event::PsmConnect /* must be ignored */] + .into_iter() + .collect::>(), + ), + ] + .into_iter() + .collect(); + + // PRNG to use. + let mut rng = rand::thread_rng(); + + // Nodes that the peerset knows about. + let mut known_nodes = HashMap::::new(); + // Nodes that we have reserved. Always a subset of `known_nodes`. + let mut reserved_nodes = HashSet::::new(); + + let (mut peerset, peerset_handle) = Peerset::from_config(PeersetConfig { + sets: vec![SetConfig { + bootnodes: (0..Uniform::new_inclusive(0, 4).sample(&mut rng)) + .map(|_| { + let id = PeerId::random(); + known_nodes.insert(id, State::Disconnected); + id + }) + .collect(), + reserved_nodes: { + (0..Uniform::new_inclusive(0, 2).sample(&mut rng)) + .map(|_| { + let id = PeerId::random(); + known_nodes.insert(id, State::Disconnected); + reserved_nodes.insert(id); + id + }) + .collect() + }, + in_peers: Uniform::new_inclusive(0, 25).sample(&mut rng), + out_peers: Uniform::new_inclusive(0, 25).sample(&mut rng), + reserved_only: Uniform::new_inclusive(0, 10).sample(&mut rng) == 0, + }], + }); + + let new_id = PeerId::random(); + known_nodes.insert(new_id, State::Disconnected); + peerset_handle.add_known_peer(new_id); + + futures::executor::block_on(futures::future::poll_fn(move |cx| { + // List of nodes the user of `peerset` assumes it's connected to. Always a subset of + // `known_nodes`. + let mut connected_nodes = HashSet::::new(); + // List of nodes the user of `peerset` called `incoming` with and that haven't been + // accepted or rejected yet. + let mut incoming_nodes = HashMap::::new(); + // Next id for incoming connections. + let mut next_incoming_id = IncomingIndex(0); + + // Perform a certain number of actions while checking that the state is consistent. If we + // reach the end of the loop, the run has succeeded. + // Note that with the ACKing and event postponing mechanism in `ProtocolController` + // the test time grows quadratically with the number of iterations below. + for _ in 0..2500 { + // Peer we are working with. + let mut current_peer = None; + // Current event for event bigrams validation. + let mut current_event = None; + // Last peer state for allowed event validation. + let mut last_state = None; + + // Each of these weights corresponds to an action that we may perform. + let action_weights = [150, 90, 90, 30, 30, 1, 1, 4, 4]; + match WeightedIndex::new(&action_weights).unwrap().sample(&mut rng) { + // If we generate 0, poll the peerset. + 0 => match Stream::poll_next(Pin::new(&mut peerset), cx) { + Poll::Ready(Some(Message::Connect { peer_id, .. })) => { + log::info!("PSM: connecting to peer {}", peer_id); + + let state = known_nodes.get_mut(&peer_id).unwrap(); + if matches!(*state, State::Incoming(_)) { + log::info!( + "Awaiting incoming response, ignoring obsolete Connect from PSM for peer {}", + peer_id, + ); + continue + } + + last_state = Some(*state); + + if *state != State::Inbound { + *state = State::Outbound; + } + + if !connected_nodes.insert(peer_id) { + log::info!("Request to connect to an already connected node {peer_id}"); + } + + current_peer = Some(peer_id); + current_event = Some(Event::PsmConnect); + }, + Poll::Ready(Some(Message::Drop { peer_id, .. })) => { + log::info!("PSM: dropping peer {}", peer_id); + + let state = known_nodes.get_mut(&peer_id).unwrap(); + if matches!(*state, State::Incoming(_)) { + log::info!( + "Awaiting incoming response, ignoring obsolete Drop from PSM for peer {}", + peer_id, + ); + continue + } + + last_state = Some(*state); + *state = State::Disconnected; + + if !connected_nodes.remove(&peer_id) { + log::info!("Ignoring attempt to drop a disconnected peer {}", peer_id); + } + + current_peer = Some(peer_id); + current_event = Some(Event::PsmDrop); + }, + Poll::Ready(Some(Message::Accept(n))) => { + log::info!("PSM: accepting index {}", n.0); + + let peer_id = incoming_nodes.remove(&n).unwrap(); + + let state = known_nodes.get_mut(&peer_id).unwrap(); + match *state { + State::Incoming(incoming_index) => + if n.0 < incoming_index.0 { + log::info!( + "Ignoring obsolete Accept for {:?} while awaiting {:?} for peer {}", + n, incoming_index, peer_id, + ); + continue + } else if n.0 > incoming_index.0 { + panic!( + "Received {:?} while awaiting {:?} for peer {}", + n, incoming_index, peer_id, + ); + }, + _ => {}, + } + + last_state = Some(*state); + *state = State::Inbound; + + assert!(connected_nodes.insert(peer_id)); + + current_peer = Some(peer_id); + current_event = Some(Event::PsmAccept); + }, + Poll::Ready(Some(Message::Reject(n))) => { + log::info!("PSM: rejecting index {}", n.0); + + let peer_id = incoming_nodes.remove(&n).unwrap(); + + let state = known_nodes.get_mut(&peer_id).unwrap(); + match *state { + State::Incoming(incoming_index) => + if n.0 < incoming_index.0 { + log::info!( + "Ignoring obsolete Reject for {:?} while awaiting {:?} for peer {}", + n, incoming_index, peer_id, + ); + continue + } else if n.0 > incoming_index.0 { + panic!( + "Received {:?} while awaiting {:?} for peer {}", + n, incoming_index, peer_id, + ); + }, + _ => {}, + } + + last_state = Some(*state); + *state = State::Disconnected; + + assert!(!connected_nodes.contains(&peer_id)); + + current_peer = Some(peer_id); + current_event = Some(Event::PsmReject); + }, + Poll::Ready(None) => panic!(), + Poll::Pending => {}, + }, + + // If we generate 1, discover a new node. + 1 => { + let new_id = PeerId::random(); + known_nodes.insert(new_id, State::Disconnected); + peerset_handle.add_known_peer(new_id); + }, + + // If we generate 2, adjust a random reputation. + 2 => + if let Some(id) = known_nodes.keys().choose(&mut rng) { + let val = Uniform::new_inclusive(i32::MIN, i32::MAX).sample(&mut rng); + peerset_handle.report_peer(*id, ReputationChange::new(val, "")); + }, + + // If we generate 3, disconnect from a random node. + 3 => + if let Some(id) = connected_nodes.iter().choose(&mut rng).cloned() { + log::info!("Disconnected from {}", id); + connected_nodes.remove(&id); + + let state = known_nodes.get_mut(&id).unwrap(); + last_state = Some(*state); + *state = State::Disconnected; + + peerset.dropped(SetId::from(0), id, DropReason::Unknown); + + current_peer = Some(id); + current_event = Some(Event::Disconnected); + }, + + // If we generate 4, connect to a random node. + 4 => { + if let Some(id) = known_nodes + .keys() + .filter(|n| { + incoming_nodes.values().all(|m| m != *n) && + !connected_nodes.contains(*n) + }) + .choose(&mut rng) + .cloned() + { + log::info!("Incoming connection from {}, index {}", id, next_incoming_id.0); + peerset.incoming(SetId::from(0), id, next_incoming_id); + incoming_nodes.insert(next_incoming_id, id); + + let state = known_nodes.get_mut(&id).unwrap(); + last_state = Some(*state); + *state = State::Incoming(next_incoming_id); + + next_incoming_id.0 += 1; + + current_peer = Some(id); + current_event = Some(Event::Incoming); + } + }, + + // 5 and 6 are the reserved-only mode. + 5 => { + log::info!("Set reserved only"); + peerset_handle.set_reserved_only(SetId::from(0), true); + }, + 6 => { + log::info!("Unset reserved only"); + peerset_handle.set_reserved_only(SetId::from(0), false); + }, + + // 7 and 8 are about switching a random node in or out of reserved mode. + 7 => { + if let Some(id) = + known_nodes.keys().filter(|n| !reserved_nodes.contains(*n)).choose(&mut rng) + { + log::info!("Add reserved: {}", id); + peerset_handle.add_reserved_peer(SetId::from(0), *id); + reserved_nodes.insert(*id); + } + }, + 8 => + if let Some(id) = reserved_nodes.iter().choose(&mut rng).cloned() { + log::info!("Remove reserved: {}", id); + reserved_nodes.remove(&id); + peerset_handle.remove_reserved_peer(SetId::from(0), id); + }, + + _ => unreachable!(), + } + + // Validate event bigrams and state transitions. + if let Some(peer_id) = current_peer { + let event = current_event.unwrap(); + let last_state = discard_incoming_index(last_state.unwrap()); + if !allowed_events.get(&last_state).unwrap().contains(&event) { + panic!( + "Invalid state transition: {:?} x {:?} for peer {}", + last_state, event, peer_id, + ); + } + } + } + + Poll::Ready(()) + })); +} diff --git a/client/network/src/service/tests/service.rs b/client/network/test/src/service.rs similarity index 66% rename from client/network/src/service/tests/service.rs rename to client/network/test/src/service.rs index bdf30096356f..8c15d6b09ea4 100644 --- a/client/network/src/service/tests/service.rs +++ b/client/network/test/src/service.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,25 +16,250 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -use crate::{config, service::tests::TestNetworkBuilder, NetworkService}; - use futures::prelude::*; -use libp2p::PeerId; -use sc_network_common::{ - config::{MultiaddrWithPeerId, NonDefaultSetConfig, SetConfig, TransportConfig}, - protocol::event::Event, - service::{NetworkNotification, NetworkPeers, NetworkStateInfo}, +use libp2p::{Multiaddr, PeerId}; + +use sc_consensus::{ImportQueue, Link}; +use sc_network::{ + config::{self, FullNetworkConfiguration, MultiaddrWithPeerId, ProtocolId, TransportConfig}, + event::Event, + NetworkEventStream, NetworkNotification, NetworkPeers, NetworkService, NetworkStateInfo, + NetworkWorker, +}; +use sc_network_common::role::Roles; +use sc_network_light::light_client_requests::handler::LightClientRequestHandler; +use sc_network_sync::{ + block_request_handler::BlockRequestHandler, + engine::SyncingEngine, + service::network::{NetworkServiceHandle, NetworkServiceProvider}, + state_request_handler::StateRequestHandler, +}; +use sp_blockchain::HeaderBackend; +use sp_runtime::traits::{Block as BlockT, Zero}; +use substrate_test_runtime_client::{ + runtime::{Block as TestBlock, Hash as TestHash}, + TestClientBuilder, TestClientBuilderExt as _, }; + use std::{sync::Arc, time::Duration}; -type TestNetworkService = NetworkService< - substrate_test_runtime_client::runtime::Block, - substrate_test_runtime_client::runtime::Hash, ->; +type TestNetworkWorker = NetworkWorker; +type TestNetworkService = NetworkService; -const BLOCK_ANNOUNCE_PROTO_NAME: &str = "/block-announces"; const PROTOCOL_NAME: &str = "/foo"; +struct TestNetwork { + network: TestNetworkWorker, +} + +impl TestNetwork { + pub fn new(network: TestNetworkWorker) -> Self { + Self { network } + } + + pub fn start_network( + self, + ) -> (Arc, (impl Stream + std::marker::Unpin)) { + let worker = self.network; + let service = worker.service().clone(); + let event_stream = service.event_stream("test"); + + tokio::spawn(worker.run()); + + (service, event_stream) + } +} + +struct TestNetworkBuilder { + import_queue: Option>>, + link: Option>>, + client: Option>, + listen_addresses: Vec, + set_config: Option, + chain_sync_network: Option<(NetworkServiceProvider, NetworkServiceHandle)>, + notification_protocols: Vec, + config: Option, +} + +impl TestNetworkBuilder { + pub fn new() -> Self { + Self { + import_queue: None, + link: None, + client: None, + listen_addresses: Vec::new(), + set_config: None, + chain_sync_network: None, + notification_protocols: Vec::new(), + config: None, + } + } + + pub fn with_config(mut self, config: config::NetworkConfiguration) -> Self { + self.config = Some(config); + self + } + + pub fn with_notification_protocol(mut self, config: config::NonDefaultSetConfig) -> Self { + self.notification_protocols.push(config); + self + } + + pub fn with_listen_addresses(mut self, addresses: Vec) -> Self { + self.listen_addresses = addresses; + self + } + + pub fn with_set_config(mut self, set_config: config::SetConfig) -> Self { + self.set_config = Some(set_config); + self + } + + pub fn build(mut self) -> TestNetwork { + let client = self.client.as_mut().map_or( + Arc::new(TestClientBuilder::with_default_backend().build_with_longest_chain().0), + |v| v.clone(), + ); + + let network_config = self.config.unwrap_or(config::NetworkConfiguration { + listen_addresses: self.listen_addresses, + transport: TransportConfig::MemoryOnly, + ..config::NetworkConfiguration::new_local() + }); + + #[derive(Clone)] + struct PassThroughVerifier(bool); + + #[async_trait::async_trait] + impl sc_consensus::Verifier for PassThroughVerifier { + async fn verify( + &mut self, + mut block: sc_consensus::BlockImportParams, + ) -> Result, String> { + block.finalized = self.0; + block.fork_choice = Some(sc_consensus::ForkChoiceStrategy::LongestChain); + Ok(block) + } + } + + let mut import_queue = + self.import_queue.unwrap_or(Box::new(sc_consensus::BasicQueue::new( + PassThroughVerifier(false), + Box::new(client.clone()), + None, + &sp_core::testing::TaskExecutor::new(), + None, + ))); + + let protocol_id = ProtocolId::from("test-protocol-name"); + let fork_id = Some(String::from("test-fork-id")); + let mut full_net_config = FullNetworkConfiguration::new(&network_config); + + let block_request_protocol_config = { + let (handler, protocol_config) = + BlockRequestHandler::new(&protocol_id, None, client.clone(), 50); + tokio::spawn(handler.run().boxed()); + protocol_config + }; + + let state_request_protocol_config = { + let (handler, protocol_config) = + StateRequestHandler::new(&protocol_id, None, client.clone(), 50); + tokio::spawn(handler.run().boxed()); + protocol_config + }; + + let light_client_request_protocol_config = { + let (handler, protocol_config) = + LightClientRequestHandler::new(&protocol_id, None, client.clone()); + tokio::spawn(handler.run().boxed()); + protocol_config + }; + + let (chain_sync_network_provider, chain_sync_network_handle) = + self.chain_sync_network.unwrap_or(NetworkServiceProvider::new()); + let (tx, rx) = sc_utils::mpsc::tracing_unbounded("mpsc_syncing_engine_protocol", 100_000); + let (engine, chain_sync_service, block_announce_config) = SyncingEngine::new( + Roles::from(&config::Role::Full), + client.clone(), + None, + &full_net_config, + protocol_id.clone(), + &None, + Box::new(sp_consensus::block_validation::DefaultBlockAnnounceValidator), + None, + chain_sync_network_handle, + import_queue.service(), + block_request_protocol_config.name.clone(), + state_request_protocol_config.name.clone(), + None, + rx, + ) + .unwrap(); + let mut link = self.link.unwrap_or(Box::new(chain_sync_service.clone())); + + if !self.notification_protocols.is_empty() { + for config in self.notification_protocols { + full_net_config.add_notification_protocol(config); + } + } else { + full_net_config.add_notification_protocol(config::NonDefaultSetConfig { + notifications_protocol: PROTOCOL_NAME.into(), + fallback_names: Vec::new(), + max_notification_size: 1024 * 1024, + handshake: None, + set_config: self.set_config.unwrap_or_default(), + }); + } + + for config in [ + block_request_protocol_config, + state_request_protocol_config, + light_client_request_protocol_config, + ] { + full_net_config.add_request_response_protocol(config); + } + + let genesis_hash = + client.hash(Zero::zero()).ok().flatten().expect("Genesis block exists; qed"); + let worker = NetworkWorker::< + substrate_test_runtime_client::runtime::Block, + substrate_test_runtime_client::runtime::Hash, + >::new(config::Params:: { + block_announce_config, + role: config::Role::Full, + executor: Box::new(|f| { + tokio::spawn(f); + }), + genesis_hash, + network_config: full_net_config, + protocol_id, + fork_id, + metrics_registry: None, + tx, + }) + .unwrap(); + + let service = worker.service().clone(); + tokio::spawn(async move { + let _ = chain_sync_network_provider.run(service).await; + }); + tokio::spawn(async move { + loop { + futures::future::poll_fn(|cx| { + import_queue.poll_actions(cx, &mut *link); + std::task::Poll::Ready(()) + }) + .await; + tokio::time::sleep(std::time::Duration::from_millis(250)).await; + } + }); + tokio::spawn(engine.run()); + + TestNetwork::new(worker) + } +} + /// Builds two nodes and their associated events stream. /// The nodes are connected together and have the `PROTOCOL_NAME` protocol registered. fn build_nodes_one_proto() -> ( @@ -51,7 +276,7 @@ fn build_nodes_one_proto() -> ( .start_network(); let (node2, events_stream2) = TestNetworkBuilder::new() - .with_set_config(SetConfig { + .with_set_config(config::SetConfig { reserved_nodes: vec![MultiaddrWithPeerId { multiaddr: listen_addr, peer_id: node1.local_peer_id(), @@ -196,10 +421,6 @@ async fn notifications_state_consistent() { }, // Add new events here. - future::Either::Left(Event::SyncConnected { .. }) => {}, - future::Either::Right(Event::SyncConnected { .. }) => {}, - future::Either::Left(Event::SyncDisconnected { .. }) => {}, - future::Either::Right(Event::SyncDisconnected { .. }) => {}, future::Either::Left(Event::Dht(_)) => {}, future::Either::Right(Event::Dht(_)) => {}, }; @@ -208,11 +429,12 @@ async fn notifications_state_consistent() { #[tokio::test] async fn lots_of_incoming_peers_works() { + sp_tracing::try_init_simple(); let listen_addr = config::build_multiaddr![Memory(rand::random::())]; let (main_node, _) = TestNetworkBuilder::new() .with_listen_addresses(vec![listen_addr.clone()]) - .with_set_config(SetConfig { in_peers: u32::MAX, ..Default::default() }) + .with_set_config(config::SetConfig { in_peers: u32::MAX, ..Default::default() }) .build() .start_network(); @@ -224,7 +446,7 @@ async fn lots_of_incoming_peers_works() { for _ in 0..32 { let (_dialing_node, event_stream) = TestNetworkBuilder::new() - .with_set_config(SetConfig { + .with_set_config(config::SetConfig { reserved_nodes: vec![MultiaddrWithPeerId { multiaddr: listen_addr.clone(), peer_id: main_node_peer_id, @@ -241,6 +463,7 @@ async fn lots_of_incoming_peers_works() { let mut timer = futures_timer::Delay::new(Duration::from_secs(3600 * 24 * 7)).fuse(); let mut event_stream = event_stream.fuse(); + let mut sync_protocol_name = None; loop { futures::select! { _ = timer => { @@ -249,15 +472,21 @@ async fn lots_of_incoming_peers_works() { } ev = event_stream.next() => { match ev.unwrap() { - Event::NotificationStreamOpened { remote, .. } => { + Event::NotificationStreamOpened { protocol, remote, .. } => { + if let None = sync_protocol_name { + sync_protocol_name = Some(protocol.clone()); + } + assert_eq!(remote, main_node_peer_id); // Test succeeds after 5 seconds. This timer is here in order to // detect a potential problem after opening. timer = futures_timer::Delay::new(Duration::from_secs(5)).fuse(); } - Event::NotificationStreamClosed { .. } => { - // Test failed. - panic!(); + Event::NotificationStreamClosed { protocol, .. } => { + if Some(protocol) != sync_protocol_name { + // Test failed. + panic!(); + } } _ => {} } @@ -282,10 +511,20 @@ async fn notifications_back_pressure() { let receiver = tokio::spawn(async move { let mut received_notifications = 0; + let mut sync_protocol_name = None; while received_notifications < TOTAL_NOTIFS { match events_stream2.next().await.unwrap() { - Event::NotificationStreamClosed { .. } => panic!(), + Event::NotificationStreamOpened { protocol, .. } => { + if let None = sync_protocol_name { + sync_protocol_name = Some(protocol); + } + }, + Event::NotificationStreamClosed { protocol, .. } => { + if Some(&protocol) != sync_protocol_name.as_ref() { + panic!() + } + }, Event::NotificationsReceived { messages, .. } => for message in messages { assert_eq!(message.0, PROTOCOL_NAME.into()); @@ -331,14 +570,14 @@ async fn fallback_name_working() { let listen_addr = config::build_multiaddr![Memory(rand::random::())]; let (node1, mut events_stream1) = TestNetworkBuilder::new() + .with_notification_protocol(config::NonDefaultSetConfig { + notifications_protocol: NEW_PROTOCOL_NAME.into(), + fallback_names: vec![PROTOCOL_NAME.into()], + max_notification_size: 1024 * 1024, + handshake: None, + set_config: Default::default(), + }) .with_config(config::NetworkConfiguration { - extra_sets: vec![NonDefaultSetConfig { - notifications_protocol: NEW_PROTOCOL_NAME.into(), - fallback_names: vec![PROTOCOL_NAME.into()], - max_notification_size: 1024 * 1024, - handshake: None, - set_config: Default::default(), - }], listen_addresses: vec![listen_addr.clone()], transport: TransportConfig::MemoryOnly, ..config::NetworkConfiguration::new_local() @@ -347,7 +586,7 @@ async fn fallback_name_working() { .start_network(); let (_, mut events_stream2) = TestNetworkBuilder::new() - .with_set_config(SetConfig { + .with_set_config(config::SetConfig { reserved_nodes: vec![MultiaddrWithPeerId { multiaddr: listen_addr, peer_id: node1.local_peer_id(), @@ -387,42 +626,6 @@ async fn fallback_name_working() { receiver.await.unwrap(); } -// Disconnect peer by calling `Protocol::disconnect_peer()` with the supplied block announcement -// protocol name and verify that `SyncDisconnected` event is emitted -#[tokio::test] -async fn disconnect_sync_peer_using_block_announcement_protocol_name() { - let (node1, mut events_stream1, node2, mut events_stream2) = build_nodes_one_proto(); - - async fn wait_for_events(stream: &mut (impl Stream + std::marker::Unpin)) { - let mut notif_received = false; - let mut sync_received = false; - - while !notif_received || !sync_received { - match stream.next().await.unwrap() { - Event::NotificationStreamOpened { .. } => notif_received = true, - Event::SyncConnected { .. } => sync_received = true, - _ => {}, - }; - } - } - - wait_for_events(&mut events_stream1).await; - wait_for_events(&mut events_stream2).await; - - // disconnect peer using `PROTOCOL_NAME`, verify `NotificationStreamClosed` event is emitted - node2.disconnect_peer(node1.local_peer_id(), PROTOCOL_NAME.into()); - assert!(std::matches!( - events_stream2.next().await, - Some(Event::NotificationStreamClosed { .. }) - )); - let _ = events_stream2.next().await; // ignore the reopen event - - // now disconnect using `BLOCK_ANNOUNCE_PROTO_NAME`, verify that `SyncDisconnected` is - // emitted - node2.disconnect_peer(node1.local_peer_id(), BLOCK_ANNOUNCE_PROTO_NAME.into()); - assert!(std::matches!(events_stream2.next().await, Some(Event::SyncDisconnected { .. }))); -} - #[tokio::test] #[should_panic(expected = "don't match the transport")] async fn ensure_listen_addresses_consistent_with_transport_memory() { @@ -524,7 +727,7 @@ async fn ensure_reserved_node_addresses_consistent_with_transport_memory() { .with_config(config::NetworkConfiguration { listen_addresses: vec![listen_addr.clone()], transport: TransportConfig::MemoryOnly, - default_peers_set: SetConfig { + default_peers_set: config::SetConfig { reserved_nodes: vec![reserved_node], ..Default::default() }, @@ -551,7 +754,7 @@ async fn ensure_reserved_node_addresses_consistent_with_transport_not_memory() { let _ = TestNetworkBuilder::new() .with_config(config::NetworkConfiguration { listen_addresses: vec![listen_addr.clone()], - default_peers_set: SetConfig { + default_peers_set: config::SetConfig { reserved_nodes: vec![reserved_node], ..Default::default() }, diff --git a/client/network/test/src/sync.rs b/client/network/test/src/sync.rs index b629574fe987..7c6e341c30fa 100644 --- a/client/network/test/src/sync.rs +++ b/client/network/test/src/sync.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -414,7 +414,7 @@ async fn can_sync_small_non_best_forks() { // poll until the two nodes connect, otherwise announcing the block will not work futures::future::poll_fn::<(), _>(|cx| { net.poll(cx); - if net.peer(0).num_peers() == 0 { + if net.peer(0).num_peers() == 0 || net.peer(1).num_peers() == 0 { Poll::Pending } else { Poll::Ready(()) @@ -652,12 +652,12 @@ async fn imports_stale_once() { // check that NEW block is imported from announce message let new_hash = net.peer(0).push_blocks(1, false).pop().unwrap(); import_with_announce(&mut net, new_hash).await; - assert_eq!(net.peer(1).num_downloaded_blocks(), 1); + assert_eq!(net.peer(1).num_downloaded_blocks().await, 1); // check that KNOWN STALE block is imported from announce message let known_stale_hash = net.peer(0).push_blocks_at(BlockId::Number(0), 1, true).pop().unwrap(); import_with_announce(&mut net, known_stale_hash).await; - assert_eq!(net.peer(1).num_downloaded_blocks(), 2); + assert_eq!(net.peer(1).num_downloaded_blocks().await, 2); } #[tokio::test(flavor = "multi_thread", worker_threads = 2)] @@ -820,7 +820,7 @@ async fn sync_to_tip_requires_that_sync_protocol_is_informed_about_best_block() assert!(!net.peer(1).has_block(block_hash)); // Make sync protocol aware of the best block - net.peer(0).network_service().new_best_block_imported(block_hash, 3); + net.peer(0).sync_service().new_best_block_imported(block_hash, 3); net.run_until_idle().await; // Connect another node that should now sync to the tip @@ -865,8 +865,8 @@ async fn sync_to_tip_when_we_sync_together_with_multiple_peers() { assert!(!net.peer(2).has_block(block_hash)); - net.peer(0).network_service().new_best_block_imported(block_hash, 10_000); - net.peer(0).network_service().announce_block(block_hash, None); + net.peer(0).sync_service().new_best_block_imported(block_hash, 10_000); + net.peer(0).sync_service().announce_block(block_hash, None); while !net.peer(2).has_block(block_hash) && !net.peer(1).has_block(block_hash) { net.run_until_idle().await; @@ -1045,14 +1045,17 @@ async fn syncs_all_forks_from_single_peer() { let branch1 = net.peer(0).push_blocks_at(BlockId::Number(10), 2, true).pop().unwrap(); // Wait till peer 1 starts downloading - futures::future::poll_fn::<(), _>(|cx| { - net.poll(cx); - if net.peer(1).network().best_seen_block() != Some(12) { - return Poll::Pending + loop { + futures::future::poll_fn::<(), _>(|cx| { + net.poll(cx); + Poll::Ready(()) + }) + .await; + + if net.peer(1).sync_service().best_seen_block().await.unwrap() == Some(12) { + break } - Poll::Ready(()) - }) - .await; + } // Peer 0 produces and announces another fork let branch2 = net.peer(0).push_blocks_at(BlockId::Number(10), 2, false).pop().unwrap(); @@ -1129,7 +1132,7 @@ async fn syncs_state() { let mut config_two = FullPeerConfig::default(); config_two.extra_storage = Some(genesis_storage); config_two.sync_mode = - SyncMode::Fast { skip_proofs: *skip_proofs, storage_chain_mode: false }; + SyncMode::LightState { skip_proofs: *skip_proofs, storage_chain_mode: false }; net.add_full_peer_with_config(config_two); let hashes = net.peer(0).push_blocks(64, false); // Wait for peer 1 to sync header chain. @@ -1172,7 +1175,7 @@ async fn syncs_indexed_blocks() { net.add_full_peer_with_config(FullPeerConfig { storage_chain: true, ..Default::default() }); net.add_full_peer_with_config(FullPeerConfig { storage_chain: true, - sync_mode: SyncMode::Fast { skip_proofs: false, storage_chain_mode: true }, + sync_mode: SyncMode::LightState { skip_proofs: false, storage_chain_mode: true }, ..Default::default() }); net.peer(0).generate_blocks_at( @@ -1180,7 +1183,7 @@ async fn syncs_indexed_blocks() { 64, BlockOrigin::Own, |mut builder| { - let ex = Extrinsic::Store(n.to_le_bytes().to_vec()); + let ex = ExtrinsicBuilder::new_indexed_call(n.to_le_bytes().to_vec()).nonce(n).build(); n += 1; builder.push(ex).unwrap(); builder.build().unwrap().block @@ -1249,6 +1252,44 @@ async fn warp_sync() { .await; } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn warp_sync_to_target_block() { + sp_tracing::try_init_simple(); + let mut net = TestNet::new(0); + // Create 3 synced peers and 1 peer trying to warp sync. + net.add_full_peer_with_config(Default::default()); + net.add_full_peer_with_config(Default::default()); + net.add_full_peer_with_config(Default::default()); + + let blocks = net.peer(0).push_blocks(64, false); + let target = blocks[63]; + net.peer(1).push_blocks(64, false); + net.peer(2).push_blocks(64, false); + + let target_block = net.peer(0).client.header(target).unwrap().unwrap(); + + net.add_full_peer_with_config(FullPeerConfig { + sync_mode: SyncMode::Warp, + target_block: Some(target_block), + ..Default::default() + }); + + net.run_until_sync().await; + assert!(net.peer(3).client().has_state_at(&BlockId::Number(64))); + + // Wait for peer 1 download block history + futures::future::poll_fn::<(), _>(|cx| { + net.poll(cx); + let peer = net.peer(3); + if blocks.iter().all(|b| peer.has_body(*b)) { + Poll::Ready(()) + } else { + Poll::Pending + } + }) + .await; +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn syncs_huge_blocks() { use sp_core::storage::well_known_keys::HEAP_PAGES; @@ -1264,11 +1305,13 @@ async fn syncs_huge_blocks() { builder.build().unwrap().block }); + let mut nonce = 0; net.peer(0).generate_blocks(32, BlockOrigin::Own, |mut builder| { // Add 32 extrinsics 32k each = 1MiB total - for _ in 0..32 { - let ex = Extrinsic::IncludeData([42u8; 32 * 1024].to_vec()); + for _ in 0..32u64 { + let ex = ExtrinsicBuilder::new_include_data(vec![42u8; 32 * 1024]).nonce(nonce).build(); builder.push(ex).unwrap(); + nonce += 1; } builder.build().unwrap().block }); diff --git a/client/network/transactions/Cargo.toml b/client/network/transactions/Cargo.toml index cab702fbd7a6..ce62c89e8a55 100644 --- a/client/network/transactions/Cargo.toml +++ b/client/network/transactions/Cargo.toml @@ -13,15 +13,14 @@ documentation = "https://docs.rs/sc-network-transactions" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -array-bytes = "4.1" -codec = { package = "parity-scale-codec", version = "3.2.2", features = ["derive"] } +array-bytes = "6.1" +codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } futures = "0.3.21" -libp2p = "0.50.0" +libp2p = "0.51.3" log = "0.4.17" -pin-project = "1.0.12" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../../utils/prometheus" } +sc-network = { version = "0.10.0-dev", path = "../" } sc-network-common = { version = "0.10.0-dev", path = "../common" } -sc-peerset = { version = "4.0.0-dev", path = "../../peerset" } sc-utils = { version = "4.0.0-dev", path = "../../utils" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } diff --git a/client/network/transactions/src/config.rs b/client/network/transactions/src/config.rs index abb8cccd301a..fdf81fcd9ff4 100644 --- a/client/network/transactions/src/config.rs +++ b/client/network/transactions/src/config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/network/transactions/src/lib.rs b/client/network/transactions/src/lib.rs index a5adb274d29d..78fd432ab0c4 100644 --- a/client/network/transactions/src/lib.rs +++ b/client/network/transactions/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -27,21 +27,29 @@ //! `Future` that processes transactions. use crate::config::*; + use codec::{Decode, Encode}; use futures::{prelude::*, stream::FuturesUnordered}; use libp2p::{multiaddr, PeerId}; use log::{debug, trace, warn}; + use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; -use sc_network_common::{ +use sc_network::{ config::{NonDefaultSetConfig, NonReservedPeerMode, ProtocolId, SetConfig}, error, - protocol::{event::Event, role::ObservedRole, ProtocolName}, - service::{NetworkEventStream, NetworkNotification, NetworkPeers}, + event::Event, + types::ProtocolName, utils::{interval, LruHashSet}, + NetworkEventStream, NetworkNotification, NetworkPeers, +}; +use sc_network_common::{ + role::ObservedRole, + sync::{SyncEvent, SyncEventStream}, ExHashT, }; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; use sp_runtime::traits::Block as BlockT; + use std::{ collections::{hash_map::Entry, HashMap}, iter, @@ -57,7 +65,7 @@ pub mod config; pub type Transactions = Vec; mod rep { - use sc_peerset::ReputationChange as Rep; + use sc_network::ReputationChange as Rep; /// Reputation change when a peer sends us any transaction. /// /// This forces node to verify it, thus the negative value here. Once transaction is verified, @@ -89,21 +97,19 @@ impl Metrics { } } -#[pin_project::pin_project] struct PendingTransaction { - #[pin] validation: TransactionImportFuture, tx_hash: H, } +impl Unpin for PendingTransaction {} + impl Future for PendingTransaction { type Output = (H, TransactionImport); - fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { - let mut this = self.project(); - - if let Poll::Ready(import_result) = Pin::new(&mut this.validation).poll_unpin(cx) { - return Poll::Ready((this.tx_hash.clone(), import_result)) + fn poll(mut self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { + if let Poll::Ready(import_result) = self.validation.poll_unpin(cx) { + return Poll::Ready((self.tx_hash.clone(), import_result)) } Poll::Pending @@ -161,14 +167,17 @@ impl TransactionsHandlerPrototype { pub fn build< B: BlockT + 'static, H: ExHashT, - S: NetworkPeers + NetworkEventStream + NetworkNotification + sp_consensus::SyncOracle, + N: NetworkPeers + NetworkEventStream + NetworkNotification, + S: SyncEventStream + sp_consensus::SyncOracle, >( self, - service: S, + network: N, + sync: S, transaction_pool: Arc>, metrics_registry: Option<&Registry>, - ) -> error::Result<(TransactionsHandler, TransactionsHandlerController)> { - let event_stream = service.event_stream("transactions-handler"); + ) -> error::Result<(TransactionsHandler, TransactionsHandlerController)> { + let net_event_stream = network.event_stream("transactions-handler-net"); + let sync_event_stream = sync.event_stream("transactions-handler-sync"); let (to_handler, from_controller) = tracing_unbounded("mpsc_transactions_handler", 100_000); let handler = TransactionsHandler { @@ -178,8 +187,10 @@ impl TransactionsHandlerPrototype { .fuse(), pending_transactions: FuturesUnordered::new(), pending_transactions_peers: HashMap::new(), - service, - event_stream: event_stream.fuse(), + network, + sync, + net_event_stream: net_event_stream.fuse(), + sync_event_stream: sync_event_stream.fuse(), peers: HashMap::new(), transaction_pool, from_controller, @@ -228,7 +239,8 @@ enum ToHandler { pub struct TransactionsHandler< B: BlockT + 'static, H: ExHashT, - S: NetworkPeers + NetworkEventStream + NetworkNotification + sp_consensus::SyncOracle, + N: NetworkPeers + NetworkEventStream + NetworkNotification, + S: SyncEventStream + sp_consensus::SyncOracle, > { protocol_name: ProtocolName, /// Interval at which we call `propagate_transactions`. @@ -241,9 +253,13 @@ pub struct TransactionsHandler< /// multiple times concurrently. pending_transactions_peers: HashMap>, /// Network service to use to send messages and manage peers. - service: S, + network: N, + /// Syncing service. + sync: S, /// Stream of networking events. - event_stream: stream::Fuse + Send>>>, + net_event_stream: stream::Fuse + Send>>>, + /// Receiver for syncing-related events. + sync_event_stream: stream::Fuse + Send>>>, // All connected peers peers: HashMap>, transaction_pool: Arc>, @@ -260,11 +276,12 @@ struct Peer { role: ObservedRole, } -impl TransactionsHandler +impl TransactionsHandler where B: BlockT + 'static, H: ExHashT, - S: NetworkPeers + NetworkEventStream + NetworkNotification + sp_consensus::SyncOracle, + N: NetworkPeers + NetworkEventStream + NetworkNotification, + S: SyncEventStream + sp_consensus::SyncOracle, { /// Turns the [`TransactionsHandler`] into a future that should run forever and not be /// interrupted. @@ -281,7 +298,7 @@ where warn!(target: "sub-libp2p", "Inconsistent state, no peers for pending transaction!"); } }, - network_event = self.event_stream.next() => { + network_event = self.net_event_stream.next() => { if let Some(network_event) = network_event { self.handle_network_event(network_event).await; } else { @@ -289,6 +306,14 @@ where return; } }, + sync_event = self.sync_event_stream.next() => { + if let Some(sync_event) = sync_event { + self.handle_sync_event(sync_event); + } else { + // Syncing has seemingly closed. Closing as well. + return; + } + } message = self.from_controller.select_next_some() => { match message { ToHandler::PropagateTransaction(hash) => self.propagate_transaction(&hash), @@ -299,13 +324,12 @@ where } } - async fn handle_network_event(&mut self, event: Event) { + fn handle_sync_event(&mut self, event: SyncEvent) { match event { - Event::Dht(_) => {}, - Event::SyncConnected { remote } => { + SyncEvent::PeerConnected(remote) => { let addr = iter::once(multiaddr::Protocol::P2p(remote.into())) .collect::(); - let result = self.service.add_peers_to_reserved_set( + let result = self.network.add_peers_to_reserved_set( self.protocol_name.clone(), iter::once(addr).collect(), ); @@ -313,13 +337,18 @@ where log::error!(target: "sync", "Add reserved peer failed: {}", err); } }, - Event::SyncDisconnected { remote } => { - self.service.remove_peers_from_reserved_set( + SyncEvent::PeerDisconnected(remote) => { + self.network.remove_peers_from_reserved_set( self.protocol_name.clone(), iter::once(remote).collect(), ); }, + } + } + async fn handle_network_event(&mut self, event: Event) { + match event { + Event::Dht(_) => {}, Event::NotificationStreamOpened { remote, protocol, role, .. } if protocol == self.protocol_name => { @@ -365,7 +394,7 @@ where /// Called when peer sends us new transactions fn on_transactions(&mut self, who: PeerId, transactions: Transactions) { // Accept transactions only when node is not major syncing - if self.service.is_major_syncing() { + if self.sync.is_major_syncing() { trace!(target: "sync", "{} Ignoring transactions while major syncing", who); return } @@ -385,7 +414,7 @@ where let hash = self.transaction_pool.hash_of(&t); peer.known_transactions.insert(hash.clone()); - self.service.report_peer(who, rep::ANY_TRANSACTION); + self.network.report_peer(who, rep::ANY_TRANSACTION); match self.pending_transactions_peers.entry(hash.clone()) { Entry::Vacant(entry) => { @@ -406,9 +435,9 @@ where fn on_handle_transaction_import(&mut self, who: PeerId, import: TransactionImport) { match import { TransactionImport::KnownGood => - self.service.report_peer(who, rep::ANY_TRANSACTION_REFUND), - TransactionImport::NewGood => self.service.report_peer(who, rep::GOOD_TRANSACTION), - TransactionImport::Bad => self.service.report_peer(who, rep::BAD_TRANSACTION), + self.network.report_peer(who, rep::ANY_TRANSACTION_REFUND), + TransactionImport::NewGood => self.network.report_peer(who, rep::GOOD_TRANSACTION), + TransactionImport::Bad => self.network.report_peer(who, rep::BAD_TRANSACTION), TransactionImport::None => {}, } } @@ -416,7 +445,7 @@ where /// Propagate one transaction. pub fn propagate_transaction(&mut self, hash: &H) { // Accept transactions only when node is not major syncing - if self.service.is_major_syncing() { + if self.sync.is_major_syncing() { return } @@ -442,7 +471,7 @@ where let (hashes, to_send): (Vec<_>, Vec<_>) = transactions .iter() - .filter(|&(ref hash, _)| peer.known_transactions.insert(hash.clone())) + .filter(|(hash, _)| peer.known_transactions.insert(hash.clone())) .cloned() .unzip(); @@ -453,7 +482,7 @@ where propagated_to.entry(hash).or_default().push(who.to_base58()); } trace!(target: "sync", "Sending {} transactions to {}", to_send.len(), who); - self.service + self.network .write_notification(*who, self.protocol_name.clone(), to_send.encode()); } } @@ -468,7 +497,7 @@ where /// Call when we must propagate ready transactions to peers. fn propagate_transactions(&mut self) { // Accept transactions only when node is not major syncing - if self.service.is_major_syncing() { + if self.sync.is_major_syncing() { return } diff --git a/client/offchain/Cargo.toml b/client/offchain/Cargo.toml index dd6e2e44caf5..f52b0aa2878f 100644 --- a/client/offchain/Cargo.toml +++ b/client/offchain/Cargo.toml @@ -13,15 +13,15 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -array-bytes = "4.1" +array-bytes = "6.1" bytes = "1.1" -codec = { package = "parity-scale-codec", version = "3.2.2", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } fnv = "1.0.6" futures = "0.3.21" futures-timer = "3.0.2" hyper = { version = "0.14.16", features = ["stream", "http2"] } -hyper-rustls = { version = "0.23.0", features = ["http2"] } -libp2p = "0.50.0" +hyper-rustls = { version = "0.24.0", features = ["http2"] } +libp2p = "0.51.3" num_cpus = "1.13" once_cell = "1.8" parking_lot = "0.12.1" @@ -29,13 +29,17 @@ rand = "0.8.5" threadpool = "1.7" tracing = "0.1.29" sc-client-api = { version = "4.0.0-dev", path = "../api" } +sc-network = { version = "0.10.0-dev", path = "../network" } sc-network-common = { version = "0.10.0-dev", path = "../network/common" } -sc-peerset = { version = "4.0.0-dev", path = "../peerset" } +sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } sp-offchain = { version = "4.0.0-dev", path = "../../primitives/offchain" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } +sp-keystore = { version = "0.27.0", path = "../../primitives/keystore" } +sp-externalities = { version = "0.19.0", path = "../../primitives/externalities" } +log = "0.4.17" [dev-dependencies] lazy_static = "1.4.0" @@ -45,7 +49,7 @@ sc-client-db = { version = "0.10.0-dev", default-features = true, path = "../db" sc-transaction-pool = { version = "4.0.0-dev", path = "../transaction-pool" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } [features] diff --git a/client/offchain/src/api.rs b/client/offchain/src/api.rs index 1301ce9fd962..e6b0e30f2037 100644 --- a/client/offchain/src/api.rs +++ b/client/offchain/src/api.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -25,8 +25,8 @@ pub use http::SharedClient; use libp2p::{Multiaddr, PeerId}; use sp_core::{ offchain::{ - self, HttpError, HttpRequestId, HttpRequestStatus, OffchainStorage, OpaqueMultiaddr, - OpaqueNetworkState, StorageKind, Timestamp, + self, HttpError, HttpRequestId, HttpRequestStatus, OpaqueMultiaddr, OpaqueNetworkState, + Timestamp, }, OpaquePeerId, }; @@ -36,110 +36,6 @@ mod http; mod timestamp; -fn unavailable_yet(name: &str) -> R { - tracing::error!( - target: super::LOG_TARGET, - "The {:?} API is not available for offchain workers yet. Follow \ - https://github.com/paritytech/substrate/issues/1458 for details", - name - ); - Default::default() -} - -const LOCAL_DB: &str = "LOCAL (fork-aware) DB"; - -/// Offchain DB reference. -#[derive(Debug, Clone)] -pub struct Db { - /// Persistent storage database. - persistent: Storage, -} - -impl Db { - /// Create new instance of Offchain DB. - pub fn new(persistent: Storage) -> Self { - Self { persistent } - } - - /// Create new instance of Offchain DB, backed by given backend. - pub fn factory_from_backend( - backend: &Backend, - ) -> Option> - where - Backend: sc_client_api::Backend, - Block: sp_runtime::traits::Block, - Storage: 'static, - { - sc_client_api::Backend::offchain_storage(backend).map(|db| Box::new(Self::new(db)) as _) - } -} - -impl offchain::DbExternalities for Db { - fn local_storage_set(&mut self, kind: StorageKind, key: &[u8], value: &[u8]) { - tracing::debug!( - target: "offchain-worker::storage", - ?kind, - key = ?array_bytes::bytes2hex("", key), - value = ?array_bytes::bytes2hex("", value), - "Write", - ); - match kind { - StorageKind::PERSISTENT => self.persistent.set(STORAGE_PREFIX, key, value), - StorageKind::LOCAL => unavailable_yet(LOCAL_DB), - } - } - - fn local_storage_clear(&mut self, kind: StorageKind, key: &[u8]) { - tracing::debug!( - target: "offchain-worker::storage", - ?kind, - key = ?array_bytes::bytes2hex("", key), - "Clear", - ); - match kind { - StorageKind::PERSISTENT => self.persistent.remove(STORAGE_PREFIX, key), - StorageKind::LOCAL => unavailable_yet(LOCAL_DB), - } - } - - fn local_storage_compare_and_set( - &mut self, - kind: StorageKind, - key: &[u8], - old_value: Option<&[u8]>, - new_value: &[u8], - ) -> bool { - tracing::debug!( - target: "offchain-worker::storage", - ?kind, - key = ?array_bytes::bytes2hex("", key), - new_value = ?array_bytes::bytes2hex("", new_value), - old_value = ?old_value.as_ref().map(|s| array_bytes::bytes2hex("", s)), - "CAS", - ); - match kind { - StorageKind::PERSISTENT => - self.persistent.compare_and_set(STORAGE_PREFIX, key, old_value, new_value), - StorageKind::LOCAL => unavailable_yet(LOCAL_DB), - } - } - - fn local_storage_get(&mut self, kind: StorageKind, key: &[u8]) -> Option> { - let result = match kind { - StorageKind::PERSISTENT => self.persistent.get(STORAGE_PREFIX, key), - StorageKind::LOCAL => unavailable_yet(LOCAL_DB), - }; - tracing::debug!( - target: "offchain-worker::storage", - ?kind, - key = ?array_bytes::bytes2hex("", key), - result = ?result.as_ref().map(|s| array_bytes::bytes2hex("", s)), - "Read", - ); - result - } -} - /// Asynchronous offchain API. /// /// NOTE this is done to prevent recursive calls into the runtime @@ -324,15 +220,12 @@ impl AsyncApi { #[cfg(test)] mod tests { use super::*; - use libp2p::PeerId; use sc_client_db::offchain::LocalStorage; - use sc_network_common::{ - config::MultiaddrWithPeerId, - protocol::ProtocolName, - service::{NetworkPeers, NetworkStateInfo}, + use sc_network::{ + config::MultiaddrWithPeerId, types::ProtocolName, NetworkPeers, NetworkStateInfo, + ReputationChange, }; - use sc_peerset::ReputationChange; - use sp_core::offchain::{DbExternalities, Externalities}; + use sp_core::offchain::{storage::OffchainDb, DbExternalities, Externalities, StorageKind}; use std::time::SystemTime; pub(super) struct TestNetwork(); @@ -394,18 +287,6 @@ mod tests { unimplemented!(); } - fn add_to_peers_set( - &self, - _protocol: ProtocolName, - _peers: HashSet, - ) -> Result<(), String> { - unimplemented!(); - } - - fn remove_from_peers_set(&self, _protocol: ProtocolName, _peers: Vec) { - unimplemented!(); - } - fn sync_num_connected(&self) -> usize { unimplemented!(); } @@ -419,6 +300,10 @@ mod tests { fn local_peer_id(&self) -> PeerId { PeerId::random() } + + fn listen_addresses(&self) -> Vec { + Vec::new() + } } fn offchain_api() -> (Api, AsyncApi) { @@ -429,8 +314,8 @@ mod tests { AsyncApi::new(mock, false, shared_client) } - fn offchain_db() -> Db { - Db::new(LocalStorage::new_test()) + fn offchain_db() -> OffchainDb { + OffchainDb::new(LocalStorage::new_test()) } #[test] diff --git a/client/offchain/src/api/http.rs b/client/offchain/src/api/http.rs index a47adb3e8026..e3872614eae4 100644 --- a/client/offchain/src/api/http.rs +++ b/client/offchain/src/api/http.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/offchain/src/api/timestamp.rs b/client/offchain/src/api/timestamp.rs index 4b3f5efddf27..0087ff592e1f 100644 --- a/client/offchain/src/api/timestamp.rs +++ b/client/offchain/src/api/timestamp.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/offchain/src/lib.rs b/client/offchain/src/lib.rs index 6b28d3f8a48e..4c11a5cb7294 100644 --- a/client/offchain/src/lib.rs +++ b/client/offchain/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -35,25 +35,26 @@ #![warn(missing_docs)] -use std::{fmt, marker::PhantomData, sync::Arc}; +use std::{fmt, sync::Arc}; use futures::{ future::{ready, Future}, prelude::*, }; use parking_lot::Mutex; -use sc_network_common::service::{NetworkPeers, NetworkStateInfo}; +use sc_client_api::BlockchainEvents; +use sc_network::{NetworkPeers, NetworkStateInfo}; +use sc_transaction_pool_api::OffchainTransactionPoolFactory; use sp_api::{ApiExt, ProvideRuntimeApi}; -use sp_core::{offchain, traits::SpawnNamed, ExecutionContext}; -use sp_runtime::{ - generic::BlockId, - traits::{self, Header}, -}; +use sp_core::{offchain, traits::SpawnNamed}; +use sp_externalities::Extension; +use sp_keystore::{KeystoreExt, KeystorePtr}; +use sp_runtime::traits::{self, Header}; use threadpool::ThreadPool; mod api; -pub use api::Db as OffchainDb; +pub use sp_core::offchain::storage::OffchainDb; pub use sp_offchain::{OffchainWorkerApi, STORAGE_PREFIX}; const LOG_TARGET: &str = "offchain-worker"; @@ -64,68 +65,163 @@ pub trait NetworkProvider: NetworkStateInfo + NetworkPeers {} impl NetworkProvider for T where T: NetworkStateInfo + NetworkPeers {} +/// Special type that implements [`OffchainStorage`](offchain::OffchainStorage). +/// +/// This type can not be constructed and should only be used when passing `None` as `offchain_db` to +/// [`OffchainWorkerOptions`] to make the compiler happy. +#[derive(Clone)] +pub enum NoOffchainStorage {} + +impl offchain::OffchainStorage for NoOffchainStorage { + fn set(&mut self, _: &[u8], _: &[u8], _: &[u8]) { + unimplemented!("`NoOffchainStorage` can not be constructed!") + } + + fn remove(&mut self, _: &[u8], _: &[u8]) { + unimplemented!("`NoOffchainStorage` can not be constructed!") + } + + fn get(&self, _: &[u8], _: &[u8]) -> Option> { + unimplemented!("`NoOffchainStorage` can not be constructed!") + } + + fn compare_and_set(&mut self, _: &[u8], _: &[u8], _: Option<&[u8]>, _: &[u8]) -> bool { + unimplemented!("`NoOffchainStorage` can not be constructed!") + } +} + /// Options for [`OffchainWorkers`] -pub struct OffchainWorkerOptions { +pub struct OffchainWorkerOptions { + /// Provides access to the runtime api. + pub runtime_api_provider: Arc, + /// Provides access to the keystore. + pub keystore: Option, + /// Provides access to the offchain database. + /// + /// Use [`NoOffchainStorage`] as type when passing `None` to have some type that works. + pub offchain_db: Option, + /// Provides access to the transaction pool. + pub transaction_pool: Option>, + /// Provides access to network information. + pub network_provider: Arc, + /// Is the node running as validator? + pub is_validator: bool, /// Enable http requests from offchain workers? /// /// If not enabled, any http request will panic. pub enable_http_requests: bool, + /// Callback to create custom [`Extension`]s that should be registered for the + /// `offchain_worker` runtime call. + /// + /// These [`Extension`]s are registered along-side the default extensions and are accessible in + /// the host functions. + /// + /// # Example: + /// + /// ```nocompile + /// custom_extensions: |block_hash| { + /// vec![MyCustomExtension::new()] + /// } + /// ``` + pub custom_extensions: CE, } /// An offchain workers manager. -pub struct OffchainWorkers { - client: Arc, - _block: PhantomData, +pub struct OffchainWorkers { + runtime_api_provider: Arc, thread_pool: Mutex, shared_http_client: api::SharedClient, - enable_http: bool, + enable_http_requests: bool, + keystore: Option, + offchain_db: Option>, + transaction_pool: Option>, + network_provider: Arc, + is_validator: bool, + custom_extensions: Box Vec> + Send>, } -impl OffchainWorkers { +impl OffchainWorkers { /// Creates new [`OffchainWorkers`]. - pub fn new(client: Arc) -> Self { - Self::new_with_options(client, OffchainWorkerOptions { enable_http_requests: true }) - } - - /// Creates new [`OffchainWorkers`] using the given `options`. - pub fn new_with_options(client: Arc, options: OffchainWorkerOptions) -> Self { + pub fn new Vec> + Send + 'static>( + OffchainWorkerOptions { + runtime_api_provider, + keystore, + offchain_db, + transaction_pool, + network_provider, + is_validator, + enable_http_requests, + custom_extensions, + }: OffchainWorkerOptions, + ) -> Self { Self { - client, - _block: PhantomData, + runtime_api_provider, thread_pool: Mutex::new(ThreadPool::with_name( "offchain-worker".into(), num_cpus::get(), )), shared_http_client: api::SharedClient::new(), - enable_http: options.enable_http_requests, + enable_http_requests, + keystore, + offchain_db: offchain_db.map(OffchainDb::new), + transaction_pool, + is_validator, + network_provider, + custom_extensions: Box::new(custom_extensions), } } } -impl fmt::Debug for OffchainWorkers { +impl fmt::Debug + for OffchainWorkers +{ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("OffchainWorkers").finish() } } -impl OffchainWorkers +impl OffchainWorkers where Block: traits::Block, - Client: ProvideRuntimeApi + Send + Sync + 'static, - Client::Api: OffchainWorkerApi, + RA: ProvideRuntimeApi + Send + Sync + 'static, + RA::Api: OffchainWorkerApi, + Storage: offchain::OffchainStorage + 'static, { + /// Run the offchain workers on every block import. + pub async fn run>( + self, + import_events: Arc, + spawner: impl SpawnNamed, + ) { + import_events + .import_notification_stream() + .for_each(move |n| { + if n.is_new_best { + spawner.spawn( + "offchain-on-block", + Some("offchain-worker"), + self.on_block_imported(&n.header).boxed(), + ); + } else { + tracing::debug!( + target: LOG_TARGET, + "Skipping offchain workers for non-canon block: {:?}", + n.header, + ) + } + + ready(()) + }) + .await; + } + /// Start the offchain workers after given block. #[must_use] - pub fn on_block_imported( - &self, - header: &Block::Header, - network_provider: Arc, - is_validator: bool, - ) -> impl Future { - let runtime = self.client.runtime_api(); - let at = BlockId::hash(header.hash()); - let has_api_v1 = runtime.has_api_with::, _>(&at, |v| v == 1); - let has_api_v2 = runtime.has_api_with::, _>(&at, |v| v == 2); + fn on_block_imported(&self, header: &Block::Header) -> impl Future { + let runtime = self.runtime_api_provider.runtime_api(); + let hash = header.hash(); + let has_api_v1 = runtime.has_api_with::, _>(hash, |v| v == 1); + let has_api_v2 = runtime.has_api_with::, _>(hash, |v| v == 2); let version = match (has_api_v1, has_api_v2) { (_, Ok(true)) => 2, (Ok(true), _) => 1, @@ -143,41 +239,64 @@ where }; tracing::debug!( target: LOG_TARGET, - "Checking offchain workers at {:?}: version:{}", - at, - version + "Checking offchain workers at {hash:?}: version: {version}", ); + let process = (version > 0).then(|| { - let (api, runner) = - api::AsyncApi::new(network_provider, is_validator, self.shared_http_client.clone()); - tracing::debug!(target: LOG_TARGET, "Spawning offchain workers at {:?}", at); + let (api, runner) = api::AsyncApi::new( + self.network_provider.clone(), + self.is_validator, + self.shared_http_client.clone(), + ); + tracing::debug!(target: LOG_TARGET, "Spawning offchain workers at {hash:?}"); let header = header.clone(); - let client = self.client.clone(); + let client = self.runtime_api_provider.clone(); let mut capabilities = offchain::Capabilities::all(); + capabilities.set(offchain::Capabilities::HTTP, self.enable_http_requests); + + let keystore = self.keystore.clone(); + let db = self.offchain_db.clone(); + let tx_pool = self.transaction_pool.clone(); + let custom_extensions = (*self.custom_extensions)(hash); - capabilities.set(offchain::Capabilities::HTTP, self.enable_http); self.spawn_worker(move || { - let runtime = client.runtime_api(); + let mut runtime = client.runtime_api(); let api = Box::new(api); - tracing::debug!(target: LOG_TARGET, "Running offchain workers at {:?}", at); + tracing::debug!(target: LOG_TARGET, "Running offchain workers at {hash:?}"); + + if let Some(keystore) = keystore { + runtime.register_extension(KeystoreExt(keystore.clone())); + } + + if let Some(pool) = tx_pool { + runtime.register_extension(pool.offchain_transaction_pool(hash)); + } + + if let Some(offchain_db) = db { + runtime.register_extension(offchain::OffchainDbExt::new( + offchain::LimitedExternalities::new(capabilities, offchain_db.clone()), + )); + } + + runtime.register_extension(offchain::OffchainWorkerExt::new( + offchain::LimitedExternalities::new(capabilities, api), + )); + + custom_extensions.into_iter().for_each(|ext| runtime.register_extension(ext)); - let context = ExecutionContext::OffchainCall(Some((api, capabilities))); let run = if version == 2 { - runtime.offchain_worker_with_context(&at, context, &header) + runtime.offchain_worker(hash, &header) } else { #[allow(deprecated)] - runtime.offchain_worker_before_version_2_with_context( - &at, - context, - *header.number(), - ) + runtime.offchain_worker_before_version_2(hash, *header.number()) }; + if let Err(e) = run { tracing::error!( target: LOG_TARGET, "Error running offchain workers at {:?}: {}", - at, + hash, e ); } @@ -204,44 +323,6 @@ where } } -/// Inform the offchain worker about new imported blocks -pub async fn notification_future( - is_validator: bool, - client: Arc, - offchain: Arc>, - spawner: Spawner, - network_provider: Arc, -) where - Block: traits::Block, - Client: - ProvideRuntimeApi + sc_client_api::BlockchainEvents + Send + Sync + 'static, - Client::Api: OffchainWorkerApi, - Spawner: SpawnNamed, -{ - client - .import_notification_stream() - .for_each(move |n| { - if n.is_new_best { - spawner.spawn( - "offchain-on-block", - Some("offchain-worker"), - offchain - .on_block_imported(&n.header, network_provider.clone(), is_validator) - .boxed(), - ); - } else { - tracing::debug!( - target: LOG_TARGET, - "Skipping offchain workers for non-canon block: {:?}", - n.header, - ) - } - - ready(()) - }) - .await; -} - #[cfg(test)] mod tests { use super::*; @@ -249,15 +330,16 @@ mod tests { use libp2p::{Multiaddr, PeerId}; use sc_block_builder::BlockBuilderProvider as _; use sc_client_api::Backend as _; - use sc_network_common::{config::MultiaddrWithPeerId, protocol::ProtocolName}; - use sc_peerset::ReputationChange; - use sc_transaction_pool::{BasicPool, FullChainApi}; + use sc_network::{config::MultiaddrWithPeerId, types::ProtocolName, ReputationChange}; + use sc_transaction_pool::BasicPool; use sc_transaction_pool_api::{InPoolTransaction, TransactionPool}; use sp_consensus::BlockOrigin; use std::{collections::HashSet, sync::Arc}; use substrate_test_runtime_client::{ - runtime::Block, ClientBlockImportExt, DefaultTestClientBuilderExt, TestClient, - TestClientBuilderExt, + runtime::{ + substrate_test_pallet::pallet::Call as PalletCall, ExtrinsicBuilder, RuntimeCall, + }, + ClientBlockImportExt, DefaultTestClientBuilderExt, TestClientBuilderExt, }; struct TestNetwork(); @@ -270,6 +352,10 @@ mod tests { fn local_peer_id(&self) -> PeerId { PeerId::random() } + + fn listen_addresses(&self) -> Vec { + Vec::new() + } } impl NetworkPeers for TestNetwork { @@ -329,61 +415,41 @@ mod tests { unimplemented!(); } - fn add_to_peers_set( - &self, - _protocol: ProtocolName, - _peers: HashSet, - ) -> Result<(), String> { - unimplemented!(); - } - - fn remove_from_peers_set(&self, _protocol: ProtocolName, _peers: Vec) { - unimplemented!(); - } - fn sync_num_connected(&self) -> usize { unimplemented!(); } } - struct TestPool(Arc, Block>>); - - impl sc_transaction_pool_api::OffchainSubmitTransaction for TestPool { - fn submit_at( - &self, - at: &BlockId, - extrinsic: ::Extrinsic, - ) -> Result<(), ()> { - let source = sc_transaction_pool_api::TransactionSource::Local; - futures::executor::block_on(self.0.submit_one(&at, source, extrinsic)) - .map(|_| ()) - .map_err(|_| ()) - } - } - #[test] fn should_call_into_runtime_and_produce_extrinsic() { sp_tracing::try_init_simple(); let client = Arc::new(substrate_test_runtime_client::new()); let spawner = sp_core::testing::TaskExecutor::new(); - let pool = TestPool(BasicPool::new_full( - Default::default(), - true.into(), - None, - spawner, - client.clone(), - )); + let pool = + BasicPool::new_full(Default::default(), true.into(), None, spawner, client.clone()); let network = Arc::new(TestNetwork()); let header = client.header(client.chain_info().genesis_hash).unwrap().unwrap(); // when - let offchain = OffchainWorkers::new(client); - futures::executor::block_on(offchain.on_block_imported(&header, network, false)); + let offchain = OffchainWorkers::new(OffchainWorkerOptions { + runtime_api_provider: client, + keystore: None, + offchain_db: None::, + transaction_pool: Some(OffchainTransactionPoolFactory::new(pool.clone())), + network_provider: network, + is_validator: false, + enable_http_requests: false, + custom_extensions: |_| Vec::new(), + }); + futures::executor::block_on(offchain.on_block_imported(&header)); // then - assert_eq!(pool.0.status().ready, 1); - assert_eq!(pool.0.ready().next().unwrap().is_propagable(), false); + assert_eq!(pool.status().ready, 1); + assert!(matches!( + pool.ready().next().unwrap().data().function, + RuntimeCall::SubstrateTest(PalletCall::storage_change { .. }) + )); } #[test] @@ -401,12 +467,8 @@ mod tests { let key = &b"hello"[..]; let value = &b"world"[..]; let mut block_builder = client.new_block(Default::default()).unwrap(); - block_builder - .push(substrate_test_runtime_client::runtime::Extrinsic::OffchainIndexSet( - key.to_vec(), - value.to_vec(), - )) - .unwrap(); + let ext = ExtrinsicBuilder::new_offchain_index_set(key.to_vec(), value.to_vec()).build(); + block_builder.push(ext).unwrap(); let block = block_builder.build().unwrap().block; block_on(client.import(BlockOrigin::Own, block)).unwrap(); @@ -414,11 +476,8 @@ mod tests { assert_eq!(value, &offchain_db.get(sp_offchain::STORAGE_PREFIX, &key).unwrap()); let mut block_builder = client.new_block(Default::default()).unwrap(); - block_builder - .push(substrate_test_runtime_client::runtime::Extrinsic::OffchainIndexClear( - key.to_vec(), - )) - .unwrap(); + let ext = ExtrinsicBuilder::new_offchain_index_clear(key.to_vec()).nonce(1).build(); + block_builder.push(ext).unwrap(); let block = block_builder.build().unwrap().block; block_on(client.import(BlockOrigin::Own, block)).unwrap(); diff --git a/client/peerset/Cargo.toml b/client/peerset/Cargo.toml deleted file mode 100644 index a09508c83118..000000000000 --- a/client/peerset/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -description = "Connectivity manager based on reputation" -homepage = "https://substrate.io" -license = "GPL-3.0-or-later WITH Classpath-exception-2.0" -name = "sc-peerset" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2021" -repository = "https://github.com/paritytech/substrate/" -documentation = "https://docs.rs/sc-peerset" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -futures = "0.3.21" -libp2p = "0.50.0" -log = "0.4.17" -serde_json = "1.0.85" -wasm-timer = "0.2" -sc-utils = { version = "4.0.0-dev", path = "../utils" } - -[dev-dependencies] -rand = "0.8.5" diff --git a/client/peerset/README.md b/client/peerset/README.md deleted file mode 100644 index 1b54c52001ca..000000000000 --- a/client/peerset/README.md +++ /dev/null @@ -1,4 +0,0 @@ -Peer Set Manager (PSM). Contains the strategy for choosing which nodes the network should be -connected to. - -License: GPL-3.0-or-later WITH Classpath-exception-2.0 \ No newline at end of file diff --git a/client/peerset/src/lib.rs b/client/peerset/src/lib.rs deleted file mode 100644 index 9b1dc6a2d027..000000000000 --- a/client/peerset/src/lib.rs +++ /dev/null @@ -1,999 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. -// 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 . - -//! Peer Set Manager (PSM). Contains the strategy for choosing which nodes the network should be -//! connected to. -//! -//! The PSM handles *sets* of nodes. A set of nodes is defined as the nodes that are believed to -//! support a certain capability, such as handling blocks and transactions of a specific chain, -//! or collating a certain parachain. -//! -//! For each node in each set, the peerset holds a flag specifying whether the node is -//! connected to us or not. -//! -//! This connected/disconnected status is specific to the node and set combination, and it is for -//! example possible for a node to be connected through a specific set but not another. -//! -//! In addition, for each, set, the peerset also holds a list of reserved nodes towards which it -//! will at all time try to maintain a connection with. - -mod peersstate; - -use futures::{channel::oneshot, prelude::*}; -use log::{debug, error, trace}; -use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; -use serde_json::json; -use std::{ - collections::{HashMap, HashSet, VecDeque}, - pin::Pin, - task::{Context, Poll}, - time::{Duration, Instant}, -}; -use wasm_timer::Delay; - -pub use libp2p::PeerId; - -/// We don't accept nodes whose reputation is under this value. -pub const BANNED_THRESHOLD: i32 = 82 * (i32::MIN / 100); -/// Reputation change for a node when we get disconnected from it. -const DISCONNECT_REPUTATION_CHANGE: i32 = -256; -/// Amount of time between the moment we disconnect from a node and the moment we remove it from -/// the list. -const FORGET_AFTER: Duration = Duration::from_secs(3600); - -#[derive(Debug)] -enum Action { - AddReservedPeer(SetId, PeerId), - RemoveReservedPeer(SetId, PeerId), - SetReservedPeers(SetId, HashSet), - SetReservedOnly(SetId, bool), - ReportPeer(PeerId, ReputationChange), - AddToPeersSet(SetId, PeerId), - RemoveFromPeersSet(SetId, PeerId), - PeerReputation(PeerId, oneshot::Sender), -} - -/// Identifier of a set in the peerset. -/// -/// Can be constructed using the `From` trait implementation based on the index of the set -/// within [`PeersetConfig::sets`]. For example, the first element of [`PeersetConfig::sets`] is -/// later referred to with `SetId::from(0)`. It is intended that the code responsible for building -/// the [`PeersetConfig`] is also responsible for constructing the [`SetId`]s. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct SetId(usize); - -impl SetId { - pub const fn from(id: usize) -> Self { - Self(id) - } -} - -impl From for SetId { - fn from(id: usize) -> Self { - Self(id) - } -} - -impl From for usize { - fn from(id: SetId) -> Self { - id.0 - } -} - -/// Description of a reputation adjustment for a node. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct ReputationChange { - /// Reputation delta. - pub value: i32, - /// Reason for reputation change. - pub reason: &'static str, -} - -impl ReputationChange { - /// New reputation change with given delta and reason. - pub const fn new(value: i32, reason: &'static str) -> ReputationChange { - Self { value, reason } - } - - /// New reputation change that forces minimum possible reputation. - pub const fn new_fatal(reason: &'static str) -> ReputationChange { - Self { value: i32::MIN, reason } - } -} - -/// Shared handle to the peer set manager (PSM). Distributed around the code. -#[derive(Debug, Clone)] -pub struct PeersetHandle { - tx: TracingUnboundedSender, -} - -impl PeersetHandle { - /// Adds a new reserved peer. The peerset will make an effort to always remain connected to - /// this peer. - /// - /// Has no effect if the node was already a reserved peer. - /// - /// > **Note**: Keep in mind that the networking has to know an address for this node, - /// > otherwise it will not be able to connect to it. - pub fn add_reserved_peer(&self, set_id: SetId, peer_id: PeerId) { - let _ = self.tx.unbounded_send(Action::AddReservedPeer(set_id, peer_id)); - } - - /// Remove a previously-added reserved peer. - /// - /// Has no effect if the node was not a reserved peer. - pub fn remove_reserved_peer(&self, set_id: SetId, peer_id: PeerId) { - let _ = self.tx.unbounded_send(Action::RemoveReservedPeer(set_id, peer_id)); - } - - /// Sets whether or not the peerset only has connections with nodes marked as reserved for - /// the given set. - pub fn set_reserved_only(&self, set_id: SetId, reserved: bool) { - let _ = self.tx.unbounded_send(Action::SetReservedOnly(set_id, reserved)); - } - - /// Set reserved peers to the new set. - pub fn set_reserved_peers(&self, set_id: SetId, peer_ids: HashSet) { - let _ = self.tx.unbounded_send(Action::SetReservedPeers(set_id, peer_ids)); - } - - /// Reports an adjustment to the reputation of the given peer. - pub fn report_peer(&self, peer_id: PeerId, score_diff: ReputationChange) { - let _ = self.tx.unbounded_send(Action::ReportPeer(peer_id, score_diff)); - } - - /// Add a peer to a set. - pub fn add_to_peers_set(&self, set_id: SetId, peer_id: PeerId) { - let _ = self.tx.unbounded_send(Action::AddToPeersSet(set_id, peer_id)); - } - - /// Remove a peer from a set. - pub fn remove_from_peers_set(&self, set_id: SetId, peer_id: PeerId) { - let _ = self.tx.unbounded_send(Action::RemoveFromPeersSet(set_id, peer_id)); - } - - /// Returns the reputation value of the peer. - pub async fn peer_reputation(self, peer_id: PeerId) -> Result { - let (tx, rx) = oneshot::channel(); - - let _ = self.tx.unbounded_send(Action::PeerReputation(peer_id, tx)); - - // The channel can only be closed if the peerset no longer exists. - rx.await.map_err(|_| ()) - } -} - -/// Message that can be sent by the peer set manager (PSM). -#[derive(Debug, PartialEq)] -pub enum Message { - /// Request to open a connection to the given peer. From the point of view of the PSM, we are - /// immediately connected. - Connect { - set_id: SetId, - /// Peer to connect to. - peer_id: PeerId, - }, - - /// Drop the connection to the given peer, or cancel the connection attempt after a `Connect`. - Drop { - set_id: SetId, - /// Peer to disconnect from. - peer_id: PeerId, - }, - - /// Equivalent to `Connect` for the peer corresponding to this incoming index. - Accept(IncomingIndex), - - /// Equivalent to `Drop` for the peer corresponding to this incoming index. - Reject(IncomingIndex), -} - -/// Opaque identifier for an incoming connection. Allocated by the network. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct IncomingIndex(pub u64); - -impl From for IncomingIndex { - fn from(val: u64) -> Self { - Self(val) - } -} - -/// Configuration to pass when creating the peer set manager. -#[derive(Debug)] -pub struct PeersetConfig { - /// List of sets of nodes the peerset manages. - pub sets: Vec, -} - -/// Configuration for a single set of nodes. -#[derive(Debug)] -pub struct SetConfig { - /// Maximum number of ingoing links to peers. - pub in_peers: u32, - - /// Maximum number of outgoing links to peers. - pub out_peers: u32, - - /// List of bootstrap nodes to initialize the set with. - /// - /// > **Note**: Keep in mind that the networking has to know an address for these nodes, - /// > otherwise it will not be able to connect to them. - pub bootnodes: Vec, - - /// Lists of nodes we should always be connected to. - /// - /// > **Note**: Keep in mind that the networking has to know an address for these nodes, - /// > otherwise it will not be able to connect to them. - pub reserved_nodes: HashSet, - - /// If true, we only accept nodes in [`SetConfig::reserved_nodes`]. - pub reserved_only: bool, -} - -/// Side of the peer set manager owned by the network. In other words, the "receiving" side. -/// -/// Implements the `Stream` trait and can be polled for messages. The `Stream` never ends and never -/// errors. -#[derive(Debug)] -pub struct Peerset { - /// Underlying data structure for the nodes's states. - data: peersstate::PeersState, - /// For each set, lists of nodes that don't occupy slots and that we should try to always be - /// connected to, and whether only reserved nodes are accepted. Is kept in sync with the list - /// of non-slot-occupying nodes in [`Peerset::data`]. - reserved_nodes: Vec<(HashSet, bool)>, - /// Receiver for messages from the `PeersetHandle` and from `tx`. - rx: TracingUnboundedReceiver, - /// Sending side of `rx`. - tx: TracingUnboundedSender, - /// Queue of messages to be emitted when the `Peerset` is polled. - message_queue: VecDeque, - /// When the `Peerset` was created. - created: Instant, - /// Last time when we updated the reputations of connected nodes. - latest_time_update: Instant, - /// Next time to do a periodic call to `alloc_slots` with all sets. This is done once per - /// second, to match the period of the reputation updates. - next_periodic_alloc_slots: Delay, -} - -impl Peerset { - /// Builds a new peerset from the given configuration. - pub fn from_config(config: PeersetConfig) -> (Self, PeersetHandle) { - let (tx, rx) = tracing_unbounded("mpsc_peerset_messages", 10_000); - - let handle = PeersetHandle { tx: tx.clone() }; - - let mut peerset = { - let now = Instant::now(); - - Self { - data: peersstate::PeersState::new(config.sets.iter().map(|set| { - peersstate::SetConfig { in_peers: set.in_peers, out_peers: set.out_peers } - })), - tx, - rx, - reserved_nodes: config - .sets - .iter() - .map(|set| (set.reserved_nodes.clone(), set.reserved_only)) - .collect(), - message_queue: VecDeque::new(), - created: now, - latest_time_update: now, - next_periodic_alloc_slots: Delay::new(Duration::new(0, 0)), - } - }; - - for (set, set_config) in config.sets.into_iter().enumerate() { - for node in set_config.reserved_nodes { - peerset.data.add_no_slot_node(set, node); - } - - for peer_id in set_config.bootnodes { - if let peersstate::Peer::Unknown(entry) = peerset.data.peer(set, &peer_id) { - entry.discover(); - } else { - debug!(target: "peerset", "Duplicate bootnode in config: {:?}", peer_id); - } - } - } - - for set_index in 0..peerset.data.num_sets() { - peerset.alloc_slots(SetId(set_index)); - } - - (peerset, handle) - } - - fn on_add_reserved_peer(&mut self, set_id: SetId, peer_id: PeerId) { - let newly_inserted = self.reserved_nodes[set_id.0].0.insert(peer_id); - if !newly_inserted { - return - } - - self.data.add_no_slot_node(set_id.0, peer_id); - self.alloc_slots(set_id); - } - - fn on_remove_reserved_peer(&mut self, set_id: SetId, peer_id: PeerId) { - if !self.reserved_nodes[set_id.0].0.remove(&peer_id) { - return - } - - self.data.remove_no_slot_node(set_id.0, &peer_id); - - // Nothing more to do if not in reserved-only mode. - if !self.reserved_nodes[set_id.0].1 { - return - } - - // If, however, the peerset is in reserved-only mode, then the removed node needs to be - // disconnected. - if let peersstate::Peer::Connected(peer) = self.data.peer(set_id.0, &peer_id) { - peer.disconnect(); - self.message_queue.push_back(Message::Drop { set_id, peer_id }); - } - } - - fn on_set_reserved_peers(&mut self, set_id: SetId, peer_ids: HashSet) { - // Determine the difference between the current group and the new list. - let (to_insert, to_remove) = { - let to_insert = peer_ids - .difference(&self.reserved_nodes[set_id.0].0) - .cloned() - .collect::>(); - let to_remove = self.reserved_nodes[set_id.0] - .0 - .difference(&peer_ids) - .cloned() - .collect::>(); - (to_insert, to_remove) - }; - - for node in to_insert { - self.on_add_reserved_peer(set_id, node); - } - - for node in to_remove { - self.on_remove_reserved_peer(set_id, node); - } - } - - fn on_set_reserved_only(&mut self, set_id: SetId, reserved_only: bool) { - self.reserved_nodes[set_id.0].1 = reserved_only; - - if reserved_only { - // Disconnect all the nodes that aren't reserved. - for peer_id in - self.data.connected_peers(set_id.0).cloned().collect::>().into_iter() - { - if self.reserved_nodes[set_id.0].0.contains(&peer_id) { - continue - } - - let peer = self.data.peer(set_id.0, &peer_id).into_connected().expect( - "We are enumerating connected peers, therefore the peer is connected; qed", - ); - peer.disconnect(); - self.message_queue.push_back(Message::Drop { set_id, peer_id }); - } - } else { - self.alloc_slots(set_id); - } - } - - /// Returns the list of reserved peers. - pub fn reserved_peers(&self, set_id: SetId) -> impl Iterator { - self.reserved_nodes[set_id.0].0.iter() - } - - /// Adds a node to the given set. The peerset will, if possible and not already the case, - /// try to connect to it. - /// - /// > **Note**: This has the same effect as [`PeersetHandle::add_to_peers_set`]. - pub fn add_to_peers_set(&mut self, set_id: SetId, peer_id: PeerId) { - if let peersstate::Peer::Unknown(entry) = self.data.peer(set_id.0, &peer_id) { - entry.discover(); - self.alloc_slots(set_id); - } - } - - fn on_remove_from_peers_set(&mut self, set_id: SetId, peer_id: PeerId) { - // Don't do anything if node is reserved. - if self.reserved_nodes[set_id.0].0.contains(&peer_id) { - return - } - - match self.data.peer(set_id.0, &peer_id) { - peersstate::Peer::Connected(peer) => { - self.message_queue.push_back(Message::Drop { set_id, peer_id: *peer.peer_id() }); - peer.disconnect().forget_peer(); - }, - peersstate::Peer::NotConnected(peer) => { - peer.forget_peer(); - }, - peersstate::Peer::Unknown(_) => {}, - } - } - - fn on_report_peer(&mut self, peer_id: PeerId, change: ReputationChange) { - // We want reputations to be up-to-date before adjusting them. - self.update_time(); - - let mut reputation = self.data.peer_reputation(peer_id); - reputation.add_reputation(change.value); - if reputation.reputation() >= BANNED_THRESHOLD { - trace!(target: "peerset", "Report {}: {:+} to {}. Reason: {}", - peer_id, change.value, reputation.reputation(), change.reason - ); - return - } - - debug!(target: "peerset", "Report {}: {:+} to {}. Reason: {}, Disconnecting", - peer_id, change.value, reputation.reputation(), change.reason - ); - - drop(reputation); - - for set_index in 0..self.data.num_sets() { - if let peersstate::Peer::Connected(peer) = self.data.peer(set_index, &peer_id) { - let peer = peer.disconnect(); - self.message_queue.push_back(Message::Drop { - set_id: SetId(set_index), - peer_id: peer.into_peer_id(), - }); - - self.alloc_slots(SetId(set_index)); - } - } - } - - fn on_peer_reputation(&mut self, peer_id: PeerId, pending_response: oneshot::Sender) { - let reputation = self.data.peer_reputation(peer_id); - let _ = pending_response.send(reputation.reputation()); - } - - /// Updates the value of `self.latest_time_update` and performs all the updates that happen - /// over time, such as reputation increases for staying connected. - fn update_time(&mut self) { - let now = Instant::now(); - - // We basically do `(now - self.latest_update).as_secs()`, except that by the way we do it - // we know that we're not going to miss seconds because of rounding to integers. - let secs_diff = { - let elapsed_latest = self.latest_time_update - self.created; - let elapsed_now = now - self.created; - self.latest_time_update = now; - elapsed_now.as_secs() - elapsed_latest.as_secs() - }; - - // For each elapsed second, move the node reputation towards zero. - // If we multiply each second the reputation by `k` (where `k` is between 0 and 1), it - // takes `ln(0.5) / ln(k)` seconds to reduce the reputation by half. Use this formula to - // empirically determine a value of `k` that looks correct. - for _ in 0..secs_diff { - for peer_id in self.data.peers().cloned().collect::>() { - // We use `k = 0.98`, so we divide by `50`. With that value, it takes 34.3 seconds - // to reduce the reputation by half. - fn reput_tick(reput: i32) -> i32 { - let mut diff = reput / 50; - if diff == 0 && reput < 0 { - diff = -1; - } else if diff == 0 && reput > 0 { - diff = 1; - } - reput.saturating_sub(diff) - } - - let mut peer_reputation = self.data.peer_reputation(peer_id); - - let before = peer_reputation.reputation(); - let after = reput_tick(before); - trace!(target: "peerset", "Fleeting {}: {} -> {}", peer_id, before, after); - peer_reputation.set_reputation(after); - - if after != 0 { - continue - } - - drop(peer_reputation); - - // If the peer reaches a reputation of 0, and there is no connection to it, - // forget it. - for set_index in 0..self.data.num_sets() { - match self.data.peer(set_index, &peer_id) { - peersstate::Peer::Connected(_) => {}, - peersstate::Peer::NotConnected(peer) => { - if peer.last_connected_or_discovered() + FORGET_AFTER < now { - peer.forget_peer(); - } - }, - peersstate::Peer::Unknown(_) => { - // Happens if this peer does not belong to this set. - }, - } - } - } - } - } - - /// Try to fill available out slots with nodes for the given set. - fn alloc_slots(&mut self, set_id: SetId) { - self.update_time(); - - // Try to connect to all the reserved nodes that we are not connected to. - for reserved_node in &self.reserved_nodes[set_id.0].0 { - let entry = match self.data.peer(set_id.0, reserved_node) { - peersstate::Peer::Unknown(n) => n.discover(), - peersstate::Peer::NotConnected(n) => n, - peersstate::Peer::Connected(_) => continue, - }; - - // Don't connect to nodes with an abysmal reputation, even if they're reserved. - // This is a rather opinionated behaviour, and it wouldn't be fundamentally wrong to - // remove that check. If necessary, the peerset should be refactored to give more - // control over what happens in that situation. - if entry.reputation() < BANNED_THRESHOLD { - break - } - - match entry.try_outgoing() { - Ok(conn) => self - .message_queue - .push_back(Message::Connect { set_id, peer_id: conn.into_peer_id() }), - Err(_) => { - // An error is returned only if no slot is available. Reserved nodes are - // marked in the state machine with a flag saying "doesn't occupy a slot", - // and as such this should never happen. - debug_assert!(false); - log::error!( - target: "peerset", - "Not enough slots to connect to reserved node" - ); - }, - } - } - - // Now, we try to connect to other nodes. - - // Nothing more to do if we're in reserved mode. - if self.reserved_nodes[set_id.0].1 { - return - } - - // Try to grab the next node to attempt to connect to. - // Since `highest_not_connected_peer` is rather expensive to call, check beforehand - // whether we have an available slot. - while self.data.has_free_outgoing_slot(set_id.0) { - let next = match self.data.highest_not_connected_peer(set_id.0) { - Some(n) => n, - None => break, - }; - - // Don't connect to nodes with an abysmal reputation. - if next.reputation() < BANNED_THRESHOLD { - break - } - - match next.try_outgoing() { - Ok(conn) => self - .message_queue - .push_back(Message::Connect { set_id, peer_id: conn.into_peer_id() }), - Err(_) => { - // This branch can only be entered if there is no free slot, which is - // checked above. - debug_assert!(false); - break - }, - } - } - } - - /// Indicate that we received an incoming connection. Must be answered either with - /// a corresponding `Accept` or `Reject`, except if we were already connected to this peer. - /// - /// Note that this mechanism is orthogonal to `Connect`/`Drop`. Accepting an incoming - /// connection implicitly means `Connect`, but incoming connections aren't cancelled by - /// `dropped`. - // Implementation note: because of concurrency issues, it is possible that we push a `Connect` - // message to the output channel with a `PeerId`, and that `incoming` gets called with the same - // `PeerId` before that message has been read by the user. In this situation we must not answer. - pub fn incoming(&mut self, set_id: SetId, peer_id: PeerId, index: IncomingIndex) { - trace!(target: "peerset", "Incoming {:?}", peer_id); - - self.update_time(); - - if self.reserved_nodes[set_id.0].1 && !self.reserved_nodes[set_id.0].0.contains(&peer_id) { - self.message_queue.push_back(Message::Reject(index)); - return - } - - let not_connected = match self.data.peer(set_id.0, &peer_id) { - // If we're already connected, don't answer, as the docs mention. - peersstate::Peer::Connected(_) => return, - peersstate::Peer::NotConnected(mut entry) => { - entry.bump_last_connected_or_discovered(); - entry - }, - peersstate::Peer::Unknown(entry) => entry.discover(), - }; - - if not_connected.reputation() < BANNED_THRESHOLD { - self.message_queue.push_back(Message::Reject(index)); - return - } - - match not_connected.try_accept_incoming() { - Ok(_) => self.message_queue.push_back(Message::Accept(index)), - Err(_) => self.message_queue.push_back(Message::Reject(index)), - } - } - - /// Indicate that we dropped an active connection with a peer, or that we failed to connect. - /// - /// Must only be called after the PSM has either generated a `Connect` message with this - /// `PeerId`, or accepted an incoming connection with this `PeerId`. - pub fn dropped(&mut self, set_id: SetId, peer_id: PeerId, reason: DropReason) { - // We want reputations to be up-to-date before adjusting them. - self.update_time(); - - match self.data.peer(set_id.0, &peer_id) { - peersstate::Peer::Connected(mut entry) => { - // Decrease the node's reputation so that we don't try it again and again and again. - entry.add_reputation(DISCONNECT_REPUTATION_CHANGE); - trace!(target: "peerset", "Dropping {}: {:+} to {}", - peer_id, DISCONNECT_REPUTATION_CHANGE, entry.reputation()); - entry.disconnect(); - }, - peersstate::Peer::NotConnected(_) | peersstate::Peer::Unknown(_) => { - error!(target: "peerset", "Received dropped() for non-connected node") - }, - } - - if let DropReason::Refused = reason { - self.on_remove_from_peers_set(set_id, peer_id); - } - - self.alloc_slots(set_id); - } - - /// Reports an adjustment to the reputation of the given peer. - pub fn report_peer(&mut self, peer_id: PeerId, score_diff: ReputationChange) { - // We don't immediately perform the adjustments in order to have state consistency. We - // don't want the reporting here to take priority over messages sent using the - // `PeersetHandle`. - let _ = self.tx.unbounded_send(Action::ReportPeer(peer_id, score_diff)); - } - - /// Produces a JSON object containing the state of the peerset manager, for debugging purposes. - pub fn debug_info(&mut self) -> serde_json::Value { - self.update_time(); - - json!({ - "sets": (0..self.data.num_sets()).map(|set_index| { - json!({ - "nodes": self.data.peers().cloned().collect::>().into_iter().filter_map(|peer_id| { - let state = match self.data.peer(set_index, &peer_id) { - peersstate::Peer::Connected(entry) => json!({ - "connected": true, - "reputation": entry.reputation() - }), - peersstate::Peer::NotConnected(entry) => json!({ - "connected": false, - "reputation": entry.reputation() - }), - peersstate::Peer::Unknown(_) => return None, - }; - - Some((peer_id.to_base58(), state)) - }).collect::>(), - "reserved_nodes": self.reserved_nodes[set_index].0.iter().map(|peer_id| { - peer_id.to_base58() - }).collect::>(), - "reserved_only": self.reserved_nodes[set_index].1, - }) - }).collect::>(), - "message_queue": self.message_queue.len(), - }) - } - - /// Returns the number of peers that we have discovered. - pub fn num_discovered_peers(&self) -> usize { - self.data.peers().len() - } -} - -impl Stream for Peerset { - type Item = Message; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context) -> Poll> { - loop { - if let Some(message) = self.message_queue.pop_front() { - return Poll::Ready(Some(message)) - } - - if Future::poll(Pin::new(&mut self.next_periodic_alloc_slots), cx).is_ready() { - self.next_periodic_alloc_slots = Delay::new(Duration::new(1, 0)); - - for set_index in 0..self.data.num_sets() { - self.alloc_slots(SetId(set_index)); - } - } - - let action = match Stream::poll_next(Pin::new(&mut self.rx), cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(Some(event)) => event, - Poll::Ready(None) => return Poll::Pending, - }; - - match action { - Action::AddReservedPeer(set_id, peer_id) => - self.on_add_reserved_peer(set_id, peer_id), - Action::RemoveReservedPeer(set_id, peer_id) => - self.on_remove_reserved_peer(set_id, peer_id), - Action::SetReservedPeers(set_id, peer_ids) => - self.on_set_reserved_peers(set_id, peer_ids), - Action::SetReservedOnly(set_id, reserved) => - self.on_set_reserved_only(set_id, reserved), - Action::ReportPeer(peer_id, score_diff) => self.on_report_peer(peer_id, score_diff), - Action::AddToPeersSet(sets_name, peer_id) => - self.add_to_peers_set(sets_name, peer_id), - Action::RemoveFromPeersSet(sets_name, peer_id) => - self.on_remove_from_peers_set(sets_name, peer_id), - Action::PeerReputation(peer_id, pending_response) => - self.on_peer_reputation(peer_id, pending_response), - } - } - } -} - -/// Reason for calling [`Peerset::dropped`]. -pub enum DropReason { - /// Substream or connection has been closed for an unknown reason. - Unknown, - /// Substream or connection has been explicitly refused by the target. In other words, the - /// peer doesn't actually belong to this set. - /// - /// This has the side effect of calling [`PeersetHandle::remove_from_peers_set`]. - Refused, -} - -#[cfg(test)] -mod tests { - use super::{ - IncomingIndex, Message, Peerset, PeersetConfig, ReputationChange, SetConfig, SetId, - BANNED_THRESHOLD, - }; - use futures::prelude::*; - use libp2p::PeerId; - use std::{pin::Pin, task::Poll, thread, time::Duration}; - - fn assert_messages(mut peerset: Peerset, messages: Vec) -> Peerset { - for expected_message in messages { - let (message, p) = next_message(peerset).expect("expected message"); - assert_eq!(message, expected_message); - peerset = p; - } - peerset - } - - fn next_message(mut peerset: Peerset) -> Result<(Message, Peerset), ()> { - let next = futures::executor::block_on_stream(&mut peerset).next(); - let message = next.ok_or(())?; - Ok((message, peerset)) - } - - #[test] - fn test_peerset_add_reserved_peer() { - let bootnode = PeerId::random(); - let reserved_peer = PeerId::random(); - let reserved_peer2 = PeerId::random(); - let config = PeersetConfig { - sets: vec![SetConfig { - in_peers: 0, - out_peers: 2, - bootnodes: vec![bootnode], - reserved_nodes: Default::default(), - reserved_only: true, - }], - }; - - let (peerset, handle) = Peerset::from_config(config); - handle.add_reserved_peer(SetId::from(0), reserved_peer); - handle.add_reserved_peer(SetId::from(0), reserved_peer2); - - assert_messages( - peerset, - vec![ - Message::Connect { set_id: SetId::from(0), peer_id: reserved_peer }, - Message::Connect { set_id: SetId::from(0), peer_id: reserved_peer2 }, - ], - ); - } - - #[test] - fn test_peerset_incoming() { - let bootnode = PeerId::random(); - let incoming = PeerId::random(); - let incoming2 = PeerId::random(); - let incoming3 = PeerId::random(); - let ii = IncomingIndex(1); - let ii2 = IncomingIndex(2); - let ii3 = IncomingIndex(3); - let ii4 = IncomingIndex(3); - let config = PeersetConfig { - sets: vec![SetConfig { - in_peers: 2, - out_peers: 1, - bootnodes: vec![bootnode], - reserved_nodes: Default::default(), - reserved_only: false, - }], - }; - - let (mut peerset, _handle) = Peerset::from_config(config); - peerset.incoming(SetId::from(0), incoming, ii); - peerset.incoming(SetId::from(0), incoming, ii4); - peerset.incoming(SetId::from(0), incoming2, ii2); - peerset.incoming(SetId::from(0), incoming3, ii3); - - assert_messages( - peerset, - vec![ - Message::Connect { set_id: SetId::from(0), peer_id: bootnode }, - Message::Accept(ii), - Message::Accept(ii2), - Message::Reject(ii3), - ], - ); - } - - #[test] - fn test_peerset_reject_incoming_in_reserved_only() { - let incoming = PeerId::random(); - let ii = IncomingIndex(1); - let config = PeersetConfig { - sets: vec![SetConfig { - in_peers: 50, - out_peers: 50, - bootnodes: vec![], - reserved_nodes: Default::default(), - reserved_only: true, - }], - }; - - let (mut peerset, _) = Peerset::from_config(config); - peerset.incoming(SetId::from(0), incoming, ii); - - assert_messages(peerset, vec![Message::Reject(ii)]); - } - - #[test] - fn test_peerset_discovered() { - let bootnode = PeerId::random(); - let discovered = PeerId::random(); - let discovered2 = PeerId::random(); - let config = PeersetConfig { - sets: vec![SetConfig { - in_peers: 0, - out_peers: 2, - bootnodes: vec![bootnode], - reserved_nodes: Default::default(), - reserved_only: false, - }], - }; - - let (mut peerset, _handle) = Peerset::from_config(config); - peerset.add_to_peers_set(SetId::from(0), discovered); - peerset.add_to_peers_set(SetId::from(0), discovered); - peerset.add_to_peers_set(SetId::from(0), discovered2); - - assert_messages( - peerset, - vec![ - Message::Connect { set_id: SetId::from(0), peer_id: bootnode }, - Message::Connect { set_id: SetId::from(0), peer_id: discovered }, - ], - ); - } - - #[test] - fn test_peerset_banned() { - let (mut peerset, handle) = Peerset::from_config(PeersetConfig { - sets: vec![SetConfig { - in_peers: 25, - out_peers: 25, - bootnodes: vec![], - reserved_nodes: Default::default(), - reserved_only: false, - }], - }); - - // We ban a node by setting its reputation under the threshold. - let peer_id = PeerId::random(); - handle.report_peer(peer_id, ReputationChange::new(BANNED_THRESHOLD - 1, "")); - - let fut = futures::future::poll_fn(move |cx| { - // We need one polling for the message to be processed. - assert_eq!(Stream::poll_next(Pin::new(&mut peerset), cx), Poll::Pending); - - // Check that an incoming connection from that node gets refused. - peerset.incoming(SetId::from(0), peer_id, IncomingIndex(1)); - if let Poll::Ready(msg) = Stream::poll_next(Pin::new(&mut peerset), cx) { - assert_eq!(msg.unwrap(), Message::Reject(IncomingIndex(1))); - } else { - panic!() - } - - // Wait a bit for the node's reputation to go above the threshold. - thread::sleep(Duration::from_millis(1500)); - - // Try again. This time the node should be accepted. - peerset.incoming(SetId::from(0), peer_id, IncomingIndex(2)); - while let Poll::Ready(msg) = Stream::poll_next(Pin::new(&mut peerset), cx) { - assert_eq!(msg.unwrap(), Message::Accept(IncomingIndex(2))); - } - - Poll::Ready(()) - }); - - futures::executor::block_on(fut); - } - - #[test] - fn test_relloc_after_banned() { - let (mut peerset, handle) = Peerset::from_config(PeersetConfig { - sets: vec![SetConfig { - in_peers: 25, - out_peers: 25, - bootnodes: vec![], - reserved_nodes: Default::default(), - reserved_only: false, - }], - }); - - // We ban a node by setting its reputation under the threshold. - let peer_id = PeerId::random(); - handle.report_peer(peer_id, ReputationChange::new(BANNED_THRESHOLD - 1, "")); - - let fut = futures::future::poll_fn(move |cx| { - // We need one polling for the message to be processed. - assert_eq!(Stream::poll_next(Pin::new(&mut peerset), cx), Poll::Pending); - - // Check that an incoming connection from that node gets refused. - // This is already tested in other tests, but it is done again here because it doesn't - // hurt. - peerset.incoming(SetId::from(0), peer_id, IncomingIndex(1)); - if let Poll::Ready(msg) = Stream::poll_next(Pin::new(&mut peerset), cx) { - assert_eq!(msg.unwrap(), Message::Reject(IncomingIndex(1))); - } else { - panic!() - } - - // Wait for the peerset to change its mind and actually connect to it. - while let Poll::Ready(msg) = Stream::poll_next(Pin::new(&mut peerset), cx) { - assert_eq!(msg.unwrap(), Message::Connect { set_id: SetId::from(0), peer_id }); - } - - Poll::Ready(()) - }); - - futures::executor::block_on(fut); - } -} diff --git a/client/peerset/src/peersstate.rs b/client/peerset/src/peersstate.rs deleted file mode 100644 index c9af5b8e2ccd..000000000000 --- a/client/peerset/src/peersstate.rs +++ /dev/null @@ -1,737 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. -// 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 . - -//! Reputation and slots allocation system behind the peerset. -//! -//! The [`PeersState`] state machine is responsible for managing the reputation and allocating -//! slots. It holds a list of nodes, each associated with a reputation value, a list of sets the -//! node belongs to, and for each set whether we are connected or not to this node. Thanks to this -//! list, it knows how many slots are occupied. It also holds a list of nodes which don't occupy -//! slots. -//! -//! > Note: This module is purely dedicated to managing slots and reputations. Features such as -//! > for example connecting to some nodes in priority should be added outside of this -//! > module, rather than inside. - -use libp2p::PeerId; -use log::error; -use std::{ - borrow::Cow, - collections::{ - hash_map::{Entry, OccupiedEntry}, - HashMap, HashSet, - }, - time::Instant, -}; - -/// State storage behind the peerset. -/// -/// # Usage -/// -/// This struct is nothing more but a data structure containing a list of nodes, where each node -/// has a reputation and is either connected to us or not. -#[derive(Debug, Clone)] -pub struct PeersState { - /// List of nodes that we know about. - /// - /// > **Note**: This list should really be ordered by decreasing reputation, so that we can - /// > easily select the best node to connect to. As a first draft, however, we don't sort, to - /// > make the logic easier. - nodes: HashMap, - - /// Configuration of each set. The size of this `Vec` is never modified. - sets: Vec, -} - -/// Configuration of a single set. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct SetConfig { - /// Maximum allowed number of slot-occupying nodes for ingoing connections. - pub in_peers: u32, - - /// Maximum allowed number of slot-occupying nodes for outgoing connections. - pub out_peers: u32, -} - -/// State of a single set. -#[derive(Debug, Clone, PartialEq, Eq)] -struct SetInfo { - /// Number of slot-occupying nodes for which the `MembershipState` is `In`. - num_in: u32, - - /// Number of slot-occupying nodes for which the `MembershipState` is `In`. - num_out: u32, - - /// Maximum allowed number of slot-occupying nodes for which the `MembershipState` is `In`. - max_in: u32, - - /// Maximum allowed number of slot-occupying nodes for which the `MembershipState` is `Out`. - max_out: u32, - - /// List of node identities (discovered or not) that don't occupy slots. - /// - /// Note for future readers: this module is purely dedicated to managing slots. If you are - /// considering adding more features, please consider doing so outside of this module rather - /// than inside. - no_slot_nodes: HashSet, -} - -/// State of a single node that we know about. -#[derive(Debug, Clone, PartialEq, Eq)] -struct Node { - /// List of sets the node belongs to. - /// Always has a fixed size equal to the one of [`PeersState::set`]. The various possible sets - /// are indices into this `Vec`. - sets: Vec, - - /// Reputation value of the node, between `i32::MIN` (we hate that node) and - /// `i32::MAX` (we love that node). - reputation: i32, -} - -impl Node { - fn new(num_sets: usize) -> Self { - Self { sets: (0..num_sets).map(|_| MembershipState::NotMember).collect(), reputation: 0 } - } -} - -/// Whether we are connected to a node in the context of a specific set. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -enum MembershipState { - /// Node isn't part of that set. - NotMember, - /// We are connected through an ingoing connection. - In, - /// We are connected through an outgoing connection. - Out, - /// Node is part of that set, but we are not connected to it. - NotConnected { - /// When we were last connected to the node, or if we were never connected when we - /// discovered it. - last_connected: Instant, - }, -} - -impl MembershipState { - /// Returns `true` for [`MembershipState::In`] and [`MembershipState::Out`]. - fn is_connected(self) -> bool { - match self { - Self::In | Self::Out => true, - Self::NotMember | Self::NotConnected { .. } => false, - } - } - - /// Returns `true` for [`MembershipState::NotConnected`]. - fn is_not_connected(self) -> bool { - matches!(self, Self::NotConnected { .. }) - } -} - -impl PeersState { - /// Builds a new empty [`PeersState`]. - pub fn new(sets: impl IntoIterator) -> Self { - Self { - nodes: HashMap::new(), - sets: sets - .into_iter() - .map(|config| SetInfo { - num_in: 0, - num_out: 0, - max_in: config.in_peers, - max_out: config.out_peers, - no_slot_nodes: HashSet::new(), - }) - .collect(), - } - } - - /// Returns the number of sets. - /// - /// Corresponds to the number of elements passed to [`PeersState::new`]. - pub fn num_sets(&self) -> usize { - self.sets.len() - } - - /// Returns an object that grants access to the reputation value of a peer. - pub fn peer_reputation(&mut self, peer_id: PeerId) -> Reputation { - self.nodes.entry(peer_id).or_insert_with(|| Node::new(self.sets.len())); - - let entry = match self.nodes.entry(peer_id) { - Entry::Vacant(_) => unreachable!("guaranteed to be inserted above; qed"), - Entry::Occupied(e) => e, - }; - - Reputation { node: Some(entry) } - } - - /// Returns an object that grants access to the state of a peer in the context of a specific - /// set. - /// - /// # Panic - /// - /// `set` must be within range of the sets passed to [`PeersState::new`]. - pub fn peer<'a>(&'a mut self, set: usize, peer_id: &'a PeerId) -> Peer<'a> { - // The code below will panic anyway if this happens to be false, but this earlier assert - // makes it explicit what is wrong. - assert!(set < self.sets.len()); - - match self.nodes.get_mut(peer_id).map(|p| &p.sets[set]) { - None | Some(MembershipState::NotMember) => - Peer::Unknown(UnknownPeer { parent: self, set, peer_id: Cow::Borrowed(peer_id) }), - Some(MembershipState::In) | Some(MembershipState::Out) => - Peer::Connected(ConnectedPeer { state: self, set, peer_id: Cow::Borrowed(peer_id) }), - Some(MembershipState::NotConnected { .. }) => Peer::NotConnected(NotConnectedPeer { - state: self, - set, - peer_id: Cow::Borrowed(peer_id), - }), - } - } - - /// Returns the list of all the peers we know of. - // Note: this method could theoretically return a `Peer`, but implementing that - // isn't simple. - pub fn peers(&self) -> impl ExactSizeIterator { - self.nodes.keys() - } - - /// Returns the list of peers we are connected to in the context of a specific set. - /// - /// # Panic - /// - /// `set` must be within range of the sets passed to [`PeersState::new`]. - // Note: this method could theoretically return a `ConnectedPeer`, but implementing that - // isn't simple. - pub fn connected_peers(&self, set: usize) -> impl Iterator { - // The code below will panic anyway if this happens to be false, but this earlier assert - // makes it explicit what is wrong. - assert!(set < self.sets.len()); - - self.nodes - .iter() - .filter(move |(_, p)| p.sets[set].is_connected()) - .map(|(p, _)| p) - } - - /// Returns the peer with the highest reputation and that we are not connected to. - /// - /// If multiple nodes have the same reputation, which one is returned is unspecified. - /// - /// # Panic - /// - /// `set` must be within range of the sets passed to [`PeersState::new`]. - pub fn highest_not_connected_peer(&mut self, set: usize) -> Option { - // The code below will panic anyway if this happens to be false, but this earlier assert - // makes it explicit what is wrong. - assert!(set < self.sets.len()); - - let outcome = self - .nodes - .iter_mut() - .filter(|(_, Node { sets, .. })| sets[set].is_not_connected()) - .fold(None::<(&PeerId, &mut Node)>, |mut cur_node, to_try| { - if let Some(cur_node) = cur_node.take() { - if cur_node.1.reputation >= to_try.1.reputation { - return Some(cur_node) - } - } - Some(to_try) - }) - .map(|(peer_id, _)| *peer_id); - - outcome.map(move |peer_id| NotConnectedPeer { - state: self, - set, - peer_id: Cow::Owned(peer_id), - }) - } - - /// Returns `true` if there is a free outgoing slot available related to this set. - pub fn has_free_outgoing_slot(&self, set: usize) -> bool { - self.sets[set].num_out < self.sets[set].max_out - } - - /// Add a node to the list of nodes that don't occupy slots. - /// - /// Has no effect if the node was already in the group. - pub fn add_no_slot_node(&mut self, set: usize, peer_id: PeerId) { - // Reminder: `HashSet::insert` returns false if the node was already in the set - if !self.sets[set].no_slot_nodes.insert(peer_id) { - return - } - - if let Some(peer) = self.nodes.get_mut(&peer_id) { - match peer.sets[set] { - MembershipState::In => self.sets[set].num_in -= 1, - MembershipState::Out => self.sets[set].num_out -= 1, - MembershipState::NotConnected { .. } | MembershipState::NotMember => {}, - } - } - } - - /// Removes a node from the list of nodes that don't occupy slots. - /// - /// Has no effect if the node was not in the group. - pub fn remove_no_slot_node(&mut self, set: usize, peer_id: &PeerId) { - // Reminder: `HashSet::remove` returns false if the node was already not in the set - if !self.sets[set].no_slot_nodes.remove(peer_id) { - return - } - - if let Some(peer) = self.nodes.get_mut(peer_id) { - match peer.sets[set] { - MembershipState::In => self.sets[set].num_in += 1, - MembershipState::Out => self.sets[set].num_out += 1, - MembershipState::NotConnected { .. } | MembershipState::NotMember => {}, - } - } - } -} - -/// Grants access to the state of a peer in the [`PeersState`] in the context of a specific set. -pub enum Peer<'a> { - /// We are connected to this node. - Connected(ConnectedPeer<'a>), - /// We are not connected to this node. - NotConnected(NotConnectedPeer<'a>), - /// We have never heard of this node, or it is not part of the set. - Unknown(UnknownPeer<'a>), -} - -impl<'a> Peer<'a> { - /// If we are the `Connected` variant, returns the inner [`ConnectedPeer`]. Returns `None` - /// otherwise. - pub fn into_connected(self) -> Option> { - match self { - Self::Connected(peer) => Some(peer), - Self::NotConnected(..) | Self::Unknown(..) => None, - } - } - - /// If we are the `NotConnected` variant, returns the inner [`NotConnectedPeer`]. Returns `None` - /// otherwise. - #[cfg(test)] // Feel free to remove this if this function is needed outside of tests - pub fn into_not_connected(self) -> Option> { - match self { - Self::NotConnected(peer) => Some(peer), - Self::Connected(..) | Self::Unknown(..) => None, - } - } - - /// If we are the `Unknown` variant, returns the inner [`UnknownPeer`]. Returns `None` - /// otherwise. - #[cfg(test)] // Feel free to remove this if this function is needed outside of tests - pub fn into_unknown(self) -> Option> { - match self { - Self::Unknown(peer) => Some(peer), - Self::Connected(..) | Self::NotConnected(..) => None, - } - } -} - -/// A peer that is connected to us. -pub struct ConnectedPeer<'a> { - state: &'a mut PeersState, - set: usize, - peer_id: Cow<'a, PeerId>, -} - -impl<'a> ConnectedPeer<'a> { - /// Get the `PeerId` associated to this `ConnectedPeer`. - pub fn peer_id(&self) -> &PeerId { - &self.peer_id - } - - /// Destroys this `ConnectedPeer` and returns the `PeerId` inside of it. - pub fn into_peer_id(self) -> PeerId { - self.peer_id.into_owned() - } - - /// Switches the peer to "not connected". - pub fn disconnect(self) -> NotConnectedPeer<'a> { - let is_no_slot_occupy = self.state.sets[self.set].no_slot_nodes.contains(&*self.peer_id); - if let Some(node) = self.state.nodes.get_mut(&*self.peer_id) { - if !is_no_slot_occupy { - match node.sets[self.set] { - MembershipState::In => self.state.sets[self.set].num_in -= 1, - MembershipState::Out => self.state.sets[self.set].num_out -= 1, - MembershipState::NotMember | MembershipState::NotConnected { .. } => { - debug_assert!( - false, - "State inconsistency: disconnecting a disconnected node" - ) - }, - } - } - node.sets[self.set] = MembershipState::NotConnected { last_connected: Instant::now() }; - } else { - debug_assert!(false, "State inconsistency: disconnecting a disconnected node"); - } - - NotConnectedPeer { state: self.state, set: self.set, peer_id: self.peer_id } - } - - /// Performs an arithmetic addition on the reputation score of that peer. - /// - /// In case of overflow, the value will be capped. - /// - /// > **Note**: Reputation values aren't specific to a set but are global per peer. - pub fn add_reputation(&mut self, modifier: i32) { - if let Some(node) = self.state.nodes.get_mut(&*self.peer_id) { - node.reputation = node.reputation.saturating_add(modifier); - } else { - debug_assert!(false, "State inconsistency: add_reputation on an unknown node"); - } - } - - /// Returns the reputation value of the node. - /// - /// > **Note**: Reputation values aren't specific to a set but are global per peer. - pub fn reputation(&self) -> i32 { - self.state.nodes.get(&*self.peer_id).map_or(0, |p| p.reputation) - } -} - -/// A peer that is not connected to us. -#[derive(Debug)] -pub struct NotConnectedPeer<'a> { - state: &'a mut PeersState, - set: usize, - peer_id: Cow<'a, PeerId>, -} - -impl<'a> NotConnectedPeer<'a> { - /// Destroys this `NotConnectedPeer` and returns the `PeerId` inside of it. - pub fn into_peer_id(self) -> PeerId { - self.peer_id.into_owned() - } - - /// Bumps the value that `last_connected_or_discovered` would return to now, even if we - /// didn't connect or disconnect. - pub fn bump_last_connected_or_discovered(&mut self) { - let state = match self.state.nodes.get_mut(&*self.peer_id) { - Some(s) => s, - None => return, - }; - - if let MembershipState::NotConnected { last_connected } = &mut state.sets[self.set] { - *last_connected = Instant::now(); - } - } - - /// Returns when we were last connected to this peer, or when we discovered it if we were - /// never connected. - /// - /// Guaranteed to be earlier than calling `Instant::now()` after the function returns. - pub fn last_connected_or_discovered(&self) -> Instant { - let state = match self.state.nodes.get(&*self.peer_id) { - Some(s) => s, - None => { - error!( - target: "peerset", - "State inconsistency with {}; not connected after borrow", - self.peer_id - ); - return Instant::now() - }, - }; - - match state.sets[self.set] { - MembershipState::NotConnected { last_connected } => last_connected, - _ => { - error!(target: "peerset", "State inconsistency with {}", self.peer_id); - Instant::now() - }, - } - } - - /// Tries to set the peer as connected as an outgoing connection. - /// - /// If there are enough slots available, switches the node to "connected" and returns `Ok`. If - /// the slots are full, the node stays "not connected" and we return `Err`. - /// - /// Non-slot-occupying nodes don't count towards the number of slots. - pub fn try_outgoing(self) -> Result, Self> { - let is_no_slot_occupy = self.state.sets[self.set].no_slot_nodes.contains(&*self.peer_id); - - // Note that it is possible for num_out to be strictly superior to the max, in case we were - // connected to reserved node then marked them as not reserved. - if !self.state.has_free_outgoing_slot(self.set) && !is_no_slot_occupy { - return Err(self) - } - - if let Some(peer) = self.state.nodes.get_mut(&*self.peer_id) { - peer.sets[self.set] = MembershipState::Out; - if !is_no_slot_occupy { - self.state.sets[self.set].num_out += 1; - } - } else { - debug_assert!(false, "State inconsistency: try_outgoing on an unknown node"); - } - - Ok(ConnectedPeer { state: self.state, set: self.set, peer_id: self.peer_id }) - } - - /// Tries to accept the peer as an incoming connection. - /// - /// If there are enough slots available, switches the node to "connected" and returns `Ok`. If - /// the slots are full, the node stays "not connected" and we return `Err`. - /// - /// Non-slot-occupying nodes don't count towards the number of slots. - pub fn try_accept_incoming(self) -> Result, Self> { - let is_no_slot_occupy = self.state.sets[self.set].no_slot_nodes.contains(&*self.peer_id); - - // Note that it is possible for num_in to be strictly superior to the max, in case we were - // connected to reserved node then marked them as not reserved. - if self.state.sets[self.set].num_in >= self.state.sets[self.set].max_in && - !is_no_slot_occupy - { - return Err(self) - } - - if let Some(peer) = self.state.nodes.get_mut(&*self.peer_id) { - peer.sets[self.set] = MembershipState::In; - if !is_no_slot_occupy { - self.state.sets[self.set].num_in += 1; - } - } else { - debug_assert!(false, "State inconsistency: try_accept_incoming on an unknown node"); - } - - Ok(ConnectedPeer { state: self.state, set: self.set, peer_id: self.peer_id }) - } - - /// Returns the reputation value of the node. - /// - /// > **Note**: Reputation values aren't specific to a set but are global per peer. - pub fn reputation(&self) -> i32 { - self.state.nodes.get(&*self.peer_id).map_or(0, |p| p.reputation) - } - - /// Sets the reputation of the peer. - /// - /// > **Note**: Reputation values aren't specific to a set but are global per peer. - #[cfg(test)] // Feel free to remove this if this function is needed outside of tests - pub fn set_reputation(&mut self, value: i32) { - if let Some(node) = self.state.nodes.get_mut(&*self.peer_id) { - node.reputation = value; - } else { - debug_assert!(false, "State inconsistency: set_reputation on an unknown node"); - } - } - - /// Removes the peer from the list of members of the set. - pub fn forget_peer(self) -> UnknownPeer<'a> { - if let Some(peer) = self.state.nodes.get_mut(&*self.peer_id) { - debug_assert!(!matches!(peer.sets[self.set], MembershipState::NotMember)); - peer.sets[self.set] = MembershipState::NotMember; - - // Remove the peer from `self.state.nodes` entirely if it isn't a member of any set. - if peer.reputation == 0 && - peer.sets.iter().all(|set| matches!(set, MembershipState::NotMember)) - { - self.state.nodes.remove(&*self.peer_id); - } - } else { - debug_assert!(false, "State inconsistency: forget_peer on an unknown node"); - error!( - target: "peerset", - "State inconsistency with {} when forgetting peer", - self.peer_id - ); - }; - - UnknownPeer { parent: self.state, set: self.set, peer_id: self.peer_id } - } -} - -/// A peer that we have never heard of or that isn't part of the set. -pub struct UnknownPeer<'a> { - parent: &'a mut PeersState, - set: usize, - peer_id: Cow<'a, PeerId>, -} - -impl<'a> UnknownPeer<'a> { - /// Inserts the peer identity in our list. - /// - /// The node starts with a reputation of 0. You can adjust these default - /// values using the `NotConnectedPeer` that this method returns. - pub fn discover(self) -> NotConnectedPeer<'a> { - let num_sets = self.parent.sets.len(); - - self.parent - .nodes - .entry(self.peer_id.clone().into_owned()) - .or_insert_with(|| Node::new(num_sets)) - .sets[self.set] = MembershipState::NotConnected { last_connected: Instant::now() }; - - NotConnectedPeer { state: self.parent, set: self.set, peer_id: self.peer_id } - } -} - -/// Access to the reputation of a peer. -pub struct Reputation<'a> { - /// Node entry in [`PeersState::nodes`]. Always `Some` except right before dropping. - node: Option>, -} - -impl<'a> Reputation<'a> { - /// Returns the reputation value of the node. - pub fn reputation(&self) -> i32 { - self.node.as_ref().unwrap().get().reputation - } - - /// Sets the reputation of the peer. - pub fn set_reputation(&mut self, value: i32) { - self.node.as_mut().unwrap().get_mut().reputation = value; - } - - /// Performs an arithmetic addition on the reputation score of that peer. - /// - /// In case of overflow, the value will be capped. - pub fn add_reputation(&mut self, modifier: i32) { - let reputation = &mut self.node.as_mut().unwrap().get_mut().reputation; - *reputation = reputation.saturating_add(modifier); - } -} - -impl<'a> Drop for Reputation<'a> { - fn drop(&mut self) { - if let Some(node) = self.node.take() { - if node.get().reputation == 0 && - node.get().sets.iter().all(|set| matches!(set, MembershipState::NotMember)) - { - node.remove(); - } - } - } -} - -#[cfg(test)] -mod tests { - use super::{Peer, PeersState, SetConfig}; - use libp2p::PeerId; - use std::iter; - - #[test] - fn full_slots_in() { - let mut peers_state = PeersState::new(iter::once(SetConfig { in_peers: 1, out_peers: 1 })); - let id1 = PeerId::random(); - let id2 = PeerId::random(); - - if let Peer::Unknown(e) = peers_state.peer(0, &id1) { - assert!(e.discover().try_accept_incoming().is_ok()); - } - - if let Peer::Unknown(e) = peers_state.peer(0, &id2) { - assert!(e.discover().try_accept_incoming().is_err()); - } - } - - #[test] - fn no_slot_node_doesnt_use_slot() { - let mut peers_state = PeersState::new(iter::once(SetConfig { in_peers: 1, out_peers: 1 })); - let id1 = PeerId::random(); - let id2 = PeerId::random(); - - peers_state.add_no_slot_node(0, id1); - if let Peer::Unknown(p) = peers_state.peer(0, &id1) { - assert!(p.discover().try_accept_incoming().is_ok()); - } else { - panic!() - } - - if let Peer::Unknown(e) = peers_state.peer(0, &id2) { - assert!(e.discover().try_accept_incoming().is_ok()); - } else { - panic!() - } - } - - #[test] - fn disconnecting_frees_slot() { - let mut peers_state = PeersState::new(iter::once(SetConfig { in_peers: 1, out_peers: 1 })); - let id1 = PeerId::random(); - let id2 = PeerId::random(); - - assert!(peers_state - .peer(0, &id1) - .into_unknown() - .unwrap() - .discover() - .try_accept_incoming() - .is_ok()); - assert!(peers_state - .peer(0, &id2) - .into_unknown() - .unwrap() - .discover() - .try_accept_incoming() - .is_err()); - peers_state.peer(0, &id1).into_connected().unwrap().disconnect(); - assert!(peers_state - .peer(0, &id2) - .into_not_connected() - .unwrap() - .try_accept_incoming() - .is_ok()); - } - - #[test] - fn highest_not_connected_peer() { - let mut peers_state = - PeersState::new(iter::once(SetConfig { in_peers: 25, out_peers: 25 })); - let id1 = PeerId::random(); - let id2 = PeerId::random(); - - assert!(peers_state.highest_not_connected_peer(0).is_none()); - peers_state.peer(0, &id1).into_unknown().unwrap().discover().set_reputation(50); - peers_state.peer(0, &id2).into_unknown().unwrap().discover().set_reputation(25); - assert_eq!(peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), Some(id1)); - peers_state.peer(0, &id2).into_not_connected().unwrap().set_reputation(75); - assert_eq!(peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), Some(id2)); - peers_state - .peer(0, &id2) - .into_not_connected() - .unwrap() - .try_accept_incoming() - .unwrap(); - assert_eq!(peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), Some(id1)); - peers_state.peer(0, &id1).into_not_connected().unwrap().set_reputation(100); - peers_state.peer(0, &id2).into_connected().unwrap().disconnect(); - assert_eq!(peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), Some(id1)); - peers_state.peer(0, &id1).into_not_connected().unwrap().set_reputation(-100); - assert_eq!(peers_state.highest_not_connected_peer(0).map(|p| p.into_peer_id()), Some(id2)); - } - - #[test] - fn disconnect_no_slot_doesnt_panic() { - let mut peers_state = PeersState::new(iter::once(SetConfig { in_peers: 1, out_peers: 1 })); - let id = PeerId::random(); - peers_state.add_no_slot_node(0, id); - let peer = peers_state - .peer(0, &id) - .into_unknown() - .unwrap() - .discover() - .try_outgoing() - .unwrap(); - peer.disconnect(); - } -} diff --git a/client/peerset/tests/fuzz.rs b/client/peerset/tests/fuzz.rs deleted file mode 100644 index 48c5cb341c35..000000000000 --- a/client/peerset/tests/fuzz.rs +++ /dev/null @@ -1,176 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. -// 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 futures::prelude::*; -use libp2p::PeerId; -use rand::{ - distributions::{Distribution, Uniform, WeightedIndex}, - seq::IteratorRandom, -}; -use sc_peerset::{ - DropReason, IncomingIndex, Message, Peerset, PeersetConfig, ReputationChange, SetConfig, SetId, -}; -use std::{ - collections::{HashMap, HashSet}, - pin::Pin, - task::Poll, -}; - -#[test] -fn run() { - for _ in 0..50 { - test_once(); - } -} - -fn test_once() { - // PRNG to use. - let mut rng = rand::thread_rng(); - - // Nodes that the peerset knows about. - let mut known_nodes = HashSet::::new(); - // Nodes that we have reserved. Always a subset of `known_nodes`. - let mut reserved_nodes = HashSet::::new(); - - let (mut peerset, peerset_handle) = Peerset::from_config(PeersetConfig { - sets: vec![SetConfig { - bootnodes: (0..Uniform::new_inclusive(0, 4).sample(&mut rng)) - .map(|_| { - let id = PeerId::random(); - known_nodes.insert(id); - id - }) - .collect(), - reserved_nodes: { - (0..Uniform::new_inclusive(0, 2).sample(&mut rng)) - .map(|_| { - let id = PeerId::random(); - known_nodes.insert(id); - reserved_nodes.insert(id); - id - }) - .collect() - }, - in_peers: Uniform::new_inclusive(0, 25).sample(&mut rng), - out_peers: Uniform::new_inclusive(0, 25).sample(&mut rng), - reserved_only: Uniform::new_inclusive(0, 10).sample(&mut rng) == 0, - }], - }); - - futures::executor::block_on(futures::future::poll_fn(move |cx| { - // List of nodes the user of `peerset` assumes it's connected to. Always a subset of - // `known_nodes`. - let mut connected_nodes = HashSet::::new(); - // List of nodes the user of `peerset` called `incoming` with and that haven't been - // accepted or rejected yet. - let mut incoming_nodes = HashMap::::new(); - // Next id for incoming connections. - let mut next_incoming_id = IncomingIndex(0); - - // Perform a certain number of actions while checking that the state is consistent. If we - // reach the end of the loop, the run has succeeded. - for _ in 0..2500 { - // Each of these weights corresponds to an action that we may perform. - let action_weights = [150, 90, 90, 30, 30, 1, 1, 4, 4]; - match WeightedIndex::new(&action_weights).unwrap().sample(&mut rng) { - // If we generate 0, poll the peerset. - 0 => match Stream::poll_next(Pin::new(&mut peerset), cx) { - Poll::Ready(Some(Message::Connect { peer_id, .. })) => { - if let Some(id) = - incoming_nodes.iter().find(|(_, v)| **v == peer_id).map(|(&id, _)| id) - { - incoming_nodes.remove(&id); - } - assert!(connected_nodes.insert(peer_id)); - }, - Poll::Ready(Some(Message::Drop { peer_id, .. })) => { - connected_nodes.remove(&peer_id); - }, - Poll::Ready(Some(Message::Accept(n))) => { - assert!(connected_nodes.insert(incoming_nodes.remove(&n).unwrap())) - }, - Poll::Ready(Some(Message::Reject(n))) => { - assert!(!connected_nodes.contains(&incoming_nodes.remove(&n).unwrap())) - }, - Poll::Ready(None) => panic!(), - Poll::Pending => {}, - }, - - // If we generate 1, discover a new node. - 1 => { - let new_id = PeerId::random(); - known_nodes.insert(new_id); - peerset.add_to_peers_set(SetId::from(0), new_id); - }, - - // If we generate 2, adjust a random reputation. - 2 => - if let Some(id) = known_nodes.iter().choose(&mut rng) { - let val = Uniform::new_inclusive(i32::MIN, i32::MAX).sample(&mut rng); - peerset_handle.report_peer(*id, ReputationChange::new(val, "")); - }, - - // If we generate 3, disconnect from a random node. - 3 => - if let Some(id) = connected_nodes.iter().choose(&mut rng).cloned() { - connected_nodes.remove(&id); - peerset.dropped(SetId::from(0), id, DropReason::Unknown); - }, - - // If we generate 4, connect to a random node. - 4 => { - if let Some(id) = known_nodes - .iter() - .filter(|n| { - incoming_nodes.values().all(|m| m != *n) && - !connected_nodes.contains(*n) - }) - .choose(&mut rng) - { - peerset.incoming(SetId::from(0), *id, next_incoming_id); - incoming_nodes.insert(next_incoming_id, *id); - next_incoming_id.0 += 1; - } - }, - - // 5 and 6 are the reserved-only mode. - 5 => peerset_handle.set_reserved_only(SetId::from(0), true), - 6 => peerset_handle.set_reserved_only(SetId::from(0), false), - - // 7 and 8 are about switching a random node in or out of reserved mode. - 7 => { - if let Some(id) = - known_nodes.iter().filter(|n| !reserved_nodes.contains(*n)).choose(&mut rng) - { - peerset_handle.add_reserved_peer(SetId::from(0), *id); - reserved_nodes.insert(*id); - } - }, - 8 => - if let Some(id) = reserved_nodes.iter().choose(&mut rng).cloned() { - reserved_nodes.remove(&id); - peerset_handle.remove_reserved_peer(SetId::from(0), id); - }, - - _ => unreachable!(), - } - } - - Poll::Ready(()) - })); -} diff --git a/client/proposer-metrics/src/lib.rs b/client/proposer-metrics/src/lib.rs index c27d16ea04d7..012e8ca769a9 100644 --- a/client/proposer-metrics/src/lib.rs +++ b/client/proposer-metrics/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-api/Cargo.toml b/client/rpc-api/Cargo.toml index 261339f13188..5efb10f719c2 100644 --- a/client/rpc-api/Cargo.toml +++ b/client/rpc-api/Cargo.toml @@ -13,15 +13,15 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2" } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1" } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.85" thiserror = "1.0" sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } -sp-version = { version = "5.0.0", path = "../../primitives/version" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } +sp-version = { version = "22.0.0", path = "../../primitives/version" } jsonrpsee = { version = "0.16.2", features = ["server", "client-core", "macros"] } diff --git a/client/rpc-api/src/author/error.rs b/client/rpc-api/src/author/error.rs index 57a27d48de3a..648dbb295d8d 100644 --- a/client/rpc-api/src/author/error.rs +++ b/client/rpc-api/src/author/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -47,7 +47,7 @@ pub enum Error { BadKeyType, /// Some random issue with the key store. Shouldn't happen. #[error("The key store is unavailable")] - KeyStoreUnavailable, + KeystoreUnavailable, /// Invalid session keys encoding. #[error("Session keys are not encoded correctly")] InvalidSessionKeys, @@ -57,7 +57,7 @@ pub enum Error { } /// Base code for all authorship errors. -const BASE_ERROR: i32 = 1000; +const BASE_ERROR: i32 = crate::error::base::AUTHOR; /// Extrinsic has an invalid format. const BAD_FORMAT: i32 = BASE_ERROR + 1; /// Error during transaction verification in runtime. diff --git a/client/rpc-api/src/author/hash.rs b/client/rpc-api/src/author/hash.rs index 453947dd21f2..5c9542ffedcd 100644 --- a/client/rpc-api/src/author/hash.rs +++ b/client/rpc-api/src/author/hash.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-api/src/author/mod.rs b/client/rpc-api/src/author/mod.rs index feba7640e3b9..55881e62152e 100644 --- a/client/rpc-api/src/author/mod.rs +++ b/client/rpc-api/src/author/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-api/src/chain/error.rs b/client/rpc-api/src/chain/error.rs index 670e221cf1cd..652192942588 100644 --- a/client/rpc-api/src/chain/error.rs +++ b/client/rpc-api/src/chain/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -37,7 +37,7 @@ pub enum Error { } /// Base error code for all chain errors. -const BASE_ERROR: i32 = 3000; +const BASE_ERROR: i32 = crate::error::base::CHAIN; impl From for JsonRpseeError { fn from(e: Error) -> Self { diff --git a/client/rpc-api/src/chain/mod.rs b/client/rpc-api/src/chain/mod.rs index c7cc97463983..f215cd978f03 100644 --- a/client/rpc-api/src/chain/mod.rs +++ b/client/rpc-api/src/chain/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -29,7 +29,7 @@ pub trait ChainApi { #[method(name = "chain_getHeader", blocking)] fn header(&self, hash: Option) -> RpcResult>; - /// Get header and body of a relay chain block. + /// Get header and body of a block. #[method(name = "chain_getBlock", blocking)] fn block(&self, hash: Option) -> RpcResult>; diff --git a/client/rpc-api/src/child_state/mod.rs b/client/rpc-api/src/child_state/mod.rs index be279a97463b..a184677a721b 100644 --- a/client/rpc-api/src/child_state/mod.rs +++ b/client/rpc-api/src/child_state/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-api/src/dev/error.rs b/client/rpc-api/src/dev/error.rs index 43fd3325fa59..8e4ddb55e35d 100644 --- a/client/rpc-api/src/dev/error.rs +++ b/client/rpc-api/src/dev/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -44,7 +44,7 @@ pub enum Error { } /// Base error code for all dev errors. -const BASE_ERROR: i32 = 6000; +const BASE_ERROR: i32 = crate::error::base::DEV; impl From for JsonRpseeError { fn from(e: Error) -> Self { diff --git a/client/rpc-api/src/dev/mod.rs b/client/rpc-api/src/dev/mod.rs index afd83272a012..bc7216199dd7 100644 --- a/client/rpc-api/src/dev/mod.rs +++ b/client/rpc-api/src/dev/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-api/src/error.rs b/client/rpc-api/src/error.rs new file mode 100644 index 000000000000..72941e3145b9 --- /dev/null +++ b/client/rpc-api/src/error.rs @@ -0,0 +1,28 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +/// Base error code for RPC modules. +pub mod base { + pub const AUTHOR: i32 = 1000; + pub const SYSTEM: i32 = 2000; + pub const CHAIN: i32 = 3000; + pub const STATE: i32 = 4000; + pub const OFFCHAIN: i32 = 5000; + pub const DEV: i32 = 6000; + pub const STATEMENT: i32 = 7000; +} diff --git a/client/rpc-api/src/lib.rs b/client/rpc-api/src/lib.rs index a0cbbcee80e3..b99c237dc859 100644 --- a/client/rpc-api/src/lib.rs +++ b/client/rpc-api/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -22,6 +22,7 @@ #![warn(missing_docs)] +mod error; mod policy; pub use policy::DenyUnsafe; @@ -32,4 +33,5 @@ pub mod child_state; pub mod dev; pub mod offchain; pub mod state; +pub mod statement; pub mod system; diff --git a/client/rpc-api/src/offchain/error.rs b/client/rpc-api/src/offchain/error.rs index be72e05fc446..679e10008973 100644 --- a/client/rpc-api/src/offchain/error.rs +++ b/client/rpc-api/src/offchain/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -38,7 +38,7 @@ pub enum Error { } /// Base error code for all offchain errors. -const BASE_ERROR: i32 = 5000; +const BASE_ERROR: i32 = crate::error::base::OFFCHAIN; impl From for JsonRpseeError { fn from(e: Error) -> Self { diff --git a/client/rpc-api/src/offchain/mod.rs b/client/rpc-api/src/offchain/mod.rs index d9435d9a875f..cd42d6db3508 100644 --- a/client/rpc-api/src/offchain/mod.rs +++ b/client/rpc-api/src/offchain/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-api/src/policy.rs b/client/rpc-api/src/policy.rs index 69ca8958520a..799898fb7cf5 100644 --- a/client/rpc-api/src/policy.rs +++ b/client/rpc-api/src/policy.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-api/src/state/error.rs b/client/rpc-api/src/state/error.rs index b1df64b4789a..9857784e3545 100644 --- a/client/rpc-api/src/state/error.rs +++ b/client/rpc-api/src/state/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -55,7 +55,7 @@ pub enum Error { } /// Base code for all state errors. -const BASE_ERROR: i32 = 4000; +const BASE_ERROR: i32 = crate::error::base::STATE; impl From for JsonRpseeError { fn from(e: Error) -> Self { diff --git a/client/rpc-api/src/state/helpers.rs b/client/rpc-api/src/state/helpers.rs index 25f07be1c97c..de20ee6f1bdf 100644 --- a/client/rpc-api/src/state/helpers.rs +++ b/client/rpc-api/src/state/helpers.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-api/src/state/mod.rs b/client/rpc-api/src/state/mod.rs index 323e6ad1d41a..dbc2a505456a 100644 --- a/client/rpc-api/src/state/mod.rs +++ b/client/rpc-api/src/state/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -33,7 +33,7 @@ pub use self::helpers::ReadProof; /// Substrate state API #[rpc(client, server)] pub trait StateApi { - /// Call a contract at a block's state. + /// Call a method from the runtime API at a block's state. #[method(name = "state_call", aliases = ["state_callAt"], blocking)] fn call(&self, name: String, bytes: Bytes, hash: Option) -> RpcResult; @@ -85,8 +85,10 @@ pub trait StateApi { /// Query historical storage entries (by key) starting from a block given as the second /// parameter. /// - /// NOTE This first returned result contains the initial state of storage for all keys. + /// NOTE: The first returned result contains the initial state of storage for all keys. /// Subsequent values in the vector represent changes to the previous state (diffs). + /// WARNING: The time complexity of this query is O(|keys|*dist(block, hash)), and the + /// memory complexity is O(dist(block, hash)) -- use with caution. #[method(name = "state_queryStorage", blocking)] fn query_storage( &self, @@ -95,7 +97,9 @@ pub trait StateApi { hash: Option, ) -> RpcResult>>; - /// Query storage entries (by key) starting at block hash given as the second parameter. + /// Query storage entries (by key) at a block hash given as the second parameter. + /// NOTE: Each StorageChangeSet in the result corresponds to exactly one element -- + /// the storage value under an input key at the input block hash. #[method(name = "state_queryStorageAt", blocking)] fn query_storage_at( &self, diff --git a/client/rpc-api/src/statement/error.rs b/client/rpc-api/src/statement/error.rs new file mode 100644 index 000000000000..8438cc3ec9e9 --- /dev/null +++ b/client/rpc-api/src/statement/error.rs @@ -0,0 +1,55 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +//! Statement RPC errors. + +use jsonrpsee::{ + core::Error as JsonRpseeError, + types::error::{CallError, ErrorObject}, +}; + +/// Statement RPC Result type. +pub type Result = std::result::Result; + +/// Statement RPC errors. +#[derive(Debug, thiserror::Error)] +pub enum Error { + /// Statement store internal error. + #[error("Statement store error")] + StatementStore(String), + /// Call to an unsafe RPC was denied. + #[error(transparent)] + UnsafeRpcCalled(#[from] crate::policy::UnsafeRpcError), +} + +/// Base error code for all statement errors. +const BASE_ERROR: i32 = crate::error::base::STATEMENT; + +impl From for JsonRpseeError { + fn from(e: Error) -> Self { + match e { + Error::StatementStore(message) => CallError::Custom(ErrorObject::owned( + BASE_ERROR + 1, + format!("Statement store error: {message}"), + None::<()>, + )) + .into(), + Error::UnsafeRpcCalled(e) => e.into(), + } + } +} diff --git a/client/rpc-api/src/statement/mod.rs b/client/rpc-api/src/statement/mod.rs new file mode 100644 index 000000000000..39ec52cbea01 --- /dev/null +++ b/client/rpc-api/src/statement/mod.rs @@ -0,0 +1,60 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +//! Substrate Statement Store RPC API. + +use jsonrpsee::{core::RpcResult, proc_macros::rpc}; +use sp_core::Bytes; + +pub mod error; + +/// Substrate statement RPC API +#[rpc(client, server)] +pub trait StatementApi { + /// Return all statements, SCALE-encoded. + #[method(name = "statement_dump")] + fn dump(&self) -> RpcResult>; + + /// Return the data of all known statements which include all topics and have no `DecryptionKey` + /// field. + #[method(name = "statement_broadcasts")] + fn broadcasts(&self, match_all_topics: Vec<[u8; 32]>) -> RpcResult>; + + /// Return the data of all known statements whose decryption key is identified as `dest` (this + /// will generally be the public key or a hash thereof for symmetric ciphers, or a hash of the + /// private key for symmetric ciphers). + #[method(name = "statement_posted")] + fn posted(&self, match_all_topics: Vec<[u8; 32]>, dest: [u8; 32]) -> RpcResult>; + + /// Return the decrypted data of all known statements whose decryption key is identified as + /// `dest`. The key must be available to the client. + #[method(name = "statement_postedClear")] + fn posted_clear( + &self, + match_all_topics: Vec<[u8; 32]>, + dest: [u8; 32], + ) -> RpcResult>; + + /// Submit a pre-encoded statement. + #[method(name = "statement_submit")] + fn submit(&self, encoded: Bytes) -> RpcResult<()>; + + /// Remove a statement from the store. + #[method(name = "statement_remove")] + fn remove(&self, statement_hash: [u8; 32]) -> RpcResult<()>; +} diff --git a/client/rpc-api/src/system/error.rs b/client/rpc-api/src/system/error.rs index 777f8c6c6df0..713ade9210d3 100644 --- a/client/rpc-api/src/system/error.rs +++ b/client/rpc-api/src/system/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -39,7 +39,7 @@ pub enum Error { } // Base code for all system errors. -const BASE_ERROR: i32 = 2000; +const BASE_ERROR: i32 = crate::error::base::SYSTEM; // Provided block range couldn't be resolved to a list of blocks. const NOT_HEALTHY_ERROR: i32 = BASE_ERROR + 1; // Peer argument is malformatted. diff --git a/client/rpc-api/src/system/helpers.rs b/client/rpc-api/src/system/helpers.rs index 7ddb3f813c24..ad56dc385009 100644 --- a/client/rpc-api/src/system/helpers.rs +++ b/client/rpc-api/src/system/helpers.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-api/src/system/mod.rs b/client/rpc-api/src/system/mod.rs index 1e12d5be87ee..bf2e92bc27a0 100644 --- a/client/rpc-api/src/system/mod.rs +++ b/client/rpc-api/src/system/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-servers/Cargo.toml b/client/rpc-servers/Cargo.toml index c6228d739048..704799112618 100644 --- a/client/rpc-servers/Cargo.toml +++ b/client/rpc-servers/Cargo.toml @@ -18,6 +18,6 @@ log = "0.4.17" serde_json = "1.0.85" tokio = { version = "1.22.0", features = ["parking_lot"] } prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } -tower-http = { version = "0.3.4", features = ["cors"] } +tower-http = { version = "0.4.0", features = ["cors"] } tower = "0.4.13" http = "0.2.8" diff --git a/client/rpc-servers/src/lib.rs b/client/rpc-servers/src/lib.rs index 1fa2ba81d867..92b31937a0cb 100644 --- a/client/rpc-servers/src/lib.rs +++ b/client/rpc-servers/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -20,6 +20,9 @@ #![warn(missing_docs)] +pub mod middleware; + +use http::header::HeaderValue; use jsonrpsee::{ server::{ middleware::proxy_get_request::ProxyGetRequestLayer, AllowHosts, ServerBuilder, @@ -28,119 +31,62 @@ use jsonrpsee::{ RpcModule, }; use std::{error::Error as StdError, net::SocketAddr}; +use tower_http::cors::{AllowOrigin, CorsLayer}; pub use crate::middleware::RpcMetrics; -use http::header::HeaderValue; pub use jsonrpsee::core::{ id_providers::{RandomIntegerIdProvider, RandomStringIdProvider}, traits::IdProvider, }; -use tower_http::cors::{AllowOrigin, CorsLayer}; - -const MEGABYTE: usize = 1024 * 1024; - -/// Maximal payload accepted by RPC servers. -pub const RPC_MAX_PAYLOAD_DEFAULT: usize = 15 * MEGABYTE; - -/// Default maximum number of connections for WS RPC servers. -const WS_MAX_CONNECTIONS: usize = 100; -/// Default maximum number subscriptions per connection for WS RPC servers. -const WS_MAX_SUBS_PER_CONN: usize = 1024; +const MEGABYTE: u32 = 1024 * 1024; -pub mod middleware; - -/// Type alias JSON-RPC server +/// Type alias for the JSON-RPC server. pub type Server = ServerHandle; -/// Server config. -#[derive(Debug, Clone)] -pub struct WsConfig { +/// RPC server configuration. +#[derive(Debug)] +pub struct Config<'a, M: Send + Sync + 'static> { + /// Socket addresses. + pub addrs: [SocketAddr; 2], + /// CORS. + pub cors: Option<&'a Vec>, /// Maximum connections. - pub max_connections: Option, + pub max_connections: u32, /// Maximum subscriptions per connection. - pub max_subs_per_conn: Option, + pub max_subs_per_conn: u32, /// Maximum rpc request payload size. - pub max_payload_in_mb: Option, + pub max_payload_in_mb: u32, /// Maximum rpc response payload size. - pub max_payload_out_mb: Option, -} - -impl WsConfig { - // Deconstructs the config to get the finalized inner values. - // - // `Payload size` or `max subs per connection` bigger than u32::MAX will be truncated. - fn deconstruct(self) -> (u32, u32, u32, u32) { - let max_conns = self.max_connections.unwrap_or(WS_MAX_CONNECTIONS) as u32; - let max_payload_in_mb = payload_size_or_default(self.max_payload_in_mb) as u32; - let max_payload_out_mb = payload_size_or_default(self.max_payload_out_mb) as u32; - let max_subs_per_conn = self.max_subs_per_conn.unwrap_or(WS_MAX_SUBS_PER_CONN) as u32; - - (max_payload_in_mb, max_payload_out_mb, max_conns, max_subs_per_conn) - } -} - -/// Start HTTP server listening on given address. -pub async fn start_http( - addrs: [SocketAddr; 2], - cors: Option<&Vec>, - max_payload_in_mb: Option, - max_payload_out_mb: Option, - metrics: Option, - rpc_api: RpcModule, - rt: tokio::runtime::Handle, -) -> Result> { - let max_payload_in = payload_size_or_default(max_payload_in_mb) as u32; - let max_payload_out = payload_size_or_default(max_payload_out_mb) as u32; - let host_filter = hosts_filter(cors.is_some(), &addrs); - - let middleware = tower::ServiceBuilder::new() - // Proxy `GET /health` requests to internal `system_health` method. - .layer(ProxyGetRequestLayer::new("/health", "system_health")?) - .layer(try_into_cors(cors)?); - - let builder = ServerBuilder::new() - .max_request_body_size(max_payload_in) - .max_response_body_size(max_payload_out) - .set_host_filtering(host_filter) - .set_middleware(middleware) - .custom_tokio_runtime(rt) - .http_only(); - - let rpc_api = build_rpc_api(rpc_api); - let (handle, addr) = if let Some(metrics) = metrics { - let server = builder.set_logger(metrics).build(&addrs[..]).await?; - let addr = server.local_addr(); - (server.start(rpc_api)?, addr) - } else { - let server = builder.build(&addrs[..]).await?; - let addr = server.local_addr(); - (server.start(rpc_api)?, addr) - }; - - log::info!( - "Running JSON-RPC HTTP server: addr={}, allowed origins={}", - addr.map_or_else(|_| "unknown".to_string(), |a| a.to_string()), - format_cors(cors) - ); - - Ok(handle) + pub max_payload_out_mb: u32, + /// Metrics. + pub metrics: Option, + /// RPC API. + pub rpc_api: RpcModule, + /// Subscription ID provider. + pub id_provider: Option>, + /// Tokio runtime handle. + pub tokio_handle: tokio::runtime::Handle, } -/// Start a JSON-RPC server listening on given address that supports both HTTP and WS. -pub async fn start( - addrs: [SocketAddr; 2], - cors: Option<&Vec>, - ws_config: WsConfig, - metrics: Option, - rpc_api: RpcModule, - rt: tokio::runtime::Handle, - id_provider: Option>, +/// Start RPC server listening on given address. +pub async fn start_server( + config: Config<'_, M>, ) -> Result> { - let (max_payload_in, max_payload_out, max_connections, max_subs_per_conn) = - ws_config.deconstruct(); - - let host_filter = hosts_filter(cors.is_some(), &addrs); + let Config { + addrs, + cors, + max_payload_in_mb, + max_payload_out_mb, + max_connections, + max_subs_per_conn, + metrics, + id_provider, + tokio_handle, + rpc_api, + } = config; + + let host_filter = hosts_filtering(cors.is_some(), &addrs); let middleware = tower::ServiceBuilder::new() // Proxy `GET /health` requests to internal `system_health` method. @@ -148,14 +94,14 @@ pub async fn start( .layer(try_into_cors(cors)?); let mut builder = ServerBuilder::new() - .max_request_body_size(max_payload_in) - .max_response_body_size(max_payload_out) + .max_request_body_size(max_payload_in_mb.saturating_mul(MEGABYTE)) + .max_response_body_size(max_payload_out_mb.saturating_mul(MEGABYTE)) .max_connections(max_connections) .max_subscriptions_per_connection(max_subs_per_conn) .ping_interval(std::time::Duration::from_secs(30)) .set_host_filtering(host_filter) .set_middleware(middleware) - .custom_tokio_runtime(rt); + .custom_tokio_runtime(tokio_handle); if let Some(provider) = id_provider { builder = builder.set_id_provider(provider); @@ -175,7 +121,7 @@ pub async fn start( }; log::info!( - "Running JSON-RPC WS server: addr={}, allowed origins={}", + "Running JSON-RPC server: addr={}, allowed origins={}", addr.map_or_else(|_| "unknown".to_string(), |a| a.to_string()), format_cors(cors) ); @@ -183,6 +129,20 @@ pub async fn start( Ok(handle) } +fn hosts_filtering(enabled: bool, addrs: &[SocketAddr]) -> AllowHosts { + if enabled { + // NOTE The listening addresses are whitelisted by default. + let mut hosts = Vec::with_capacity(addrs.len() * 2); + for addr in addrs { + hosts.push(format!("localhost:{}", addr.port()).into()); + hosts.push(format!("127.0.0.1:{}", addr.port()).into()); + } + AllowHosts::Only(hosts) + } else { + AllowHosts::Any + } +} + fn build_rpc_api(mut rpc_api: RpcModule) -> RpcModule { let mut available_methods = rpc_api.method_names().collect::>(); available_methods.sort(); @@ -198,24 +158,6 @@ fn build_rpc_api(mut rpc_api: RpcModule) -> RpcModu rpc_api } -fn payload_size_or_default(size_mb: Option) -> usize { - size_mb.map_or(RPC_MAX_PAYLOAD_DEFAULT, |mb| mb.saturating_mul(MEGABYTE)) -} - -fn hosts_filter(enabled: bool, addrs: &[SocketAddr]) -> AllowHosts { - if enabled { - // NOTE The listening addresses are whitelisted by default. - let mut hosts = Vec::with_capacity(addrs.len() * 2); - for addr in addrs { - hosts.push(format!("localhost:{}", addr.port()).into()); - hosts.push(format!("127.0.0.1:{}", addr.port()).into()); - } - AllowHosts::Only(hosts) - } else { - AllowHosts::Any - } -} - fn try_into_cors( maybe_cors: Option<&Vec>, ) -> Result> { diff --git a/client/rpc-servers/src/middleware.rs b/client/rpc-servers/src/middleware.rs index 1c25ac1dfd1b..c3e17c7852f1 100644 --- a/client/rpc-servers/src/middleware.rs +++ b/client/rpc-servers/src/middleware.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-spec-v2/Cargo.toml b/client/rpc-spec-v2/Cargo.toml index 43fb189081ba..4f5c11212a9b 100644 --- a/client/rpc-spec-v2/Cargo.toml +++ b/client/rpc-spec-v2/Cargo.toml @@ -18,20 +18,20 @@ jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } # Pool for submitting extrinsics required by "transaction" sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/api" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-version = { version = "5.0.0", path = "../../primitives/version" } +sp-version = { version = "22.0.0", path = "../../primitives/version" } sc-client-api = { version = "4.0.0-dev", path = "../api" } -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } thiserror = "1.0" serde = "1.0" hex = "0.4" futures = "0.3.21" parking_lot = "0.12.1" tokio-stream = { version = "0.1", features = ["sync"] } -array-bytes = "4.1" +array-bytes = "6.1" log = "0.4.17" futures-util = { version = "0.3.19", default-features = false } @@ -43,4 +43,7 @@ substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime" sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../primitives/maybe-compressed-blob" } sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } +sc-service = { version = "0.10.0-dev", features = ["test-helpers"], path = "../service" } +sc-utils = { version = "4.0.0-dev", path = "../utils" } assert_matches = "1.3.0" +pretty_assertions = "1.2.1" diff --git a/client/rpc-spec-v2/src/chain_head/api.rs b/client/rpc-spec-v2/src/chain_head/api.rs index 7e72d6d774b1..ee8caa37bafb 100644 --- a/client/rpc-spec-v2/src/chain_head/api.rs +++ b/client/rpc-spec-v2/src/chain_head/api.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -34,7 +34,7 @@ pub trait ChainHeadApi { unsubscribe = "chainHead_unstable_unfollow", item = FollowEvent, )] - fn chain_head_unstable_follow(&self, runtime_updates: bool); + fn chain_head_unstable_follow(&self, with_runtime: bool); /// Retrieves the body (list of transactions) of a pinned block. /// diff --git a/client/rpc-spec-v2/src/chain_head/chain_head.rs b/client/rpc-spec-v2/src/chain_head/chain_head.rs index 2a9cabaf2b31..a61406b8d53b 100644 --- a/client/rpc-spec-v2/src/chain_head/chain_head.rs +++ b/client/rpc-spec-v2/src/chain_head/chain_head.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -21,46 +21,35 @@ use crate::{ chain_head::{ api::ChainHeadApiServer, + chain_head_follow::ChainHeadFollower, error::Error as ChainHeadRpcError, - event::{ - BestBlockChanged, ChainHeadEvent, ChainHeadResult, ErrorEvent, Finalized, FollowEvent, - Initialized, NetworkConfig, NewBlock, RuntimeEvent, RuntimeVersionEvent, - }, - subscription::{SubscriptionHandle, SubscriptionManagement, SubscriptionManagementError}, + event::{ChainHeadEvent, ChainHeadResult, ErrorEvent, FollowEvent, NetworkConfig}, + subscription::{SubscriptionManagement, SubscriptionManagementError}, }, SubscriptionTaskExecutor, }; use codec::Encode; -use futures::{ - channel::oneshot, - future::FutureExt, - stream::{self, Stream, StreamExt}, -}; -use futures_util::future::Either; +use futures::future::FutureExt; use jsonrpsee::{ core::{async_trait, RpcResult}, types::{SubscriptionEmptyError, SubscriptionId, SubscriptionResult}, SubscriptionSink, }; -use log::{debug, error}; +use log::debug; use sc_client_api::{ - Backend, BlockBackend, BlockImportNotification, BlockchainEvents, CallExecutor, ChildInfo, - ExecutorProvider, FinalityNotification, StorageKey, StorageProvider, + Backend, BlockBackend, BlockchainEvents, CallExecutor, ChildInfo, ExecutorProvider, StorageKey, + StorageProvider, }; -use serde::Serialize; use sp_api::CallApiAt; -use sp_blockchain::{ - Backend as BlockChainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata, -}; -use sp_core::{hexdisplay::HexDisplay, storage::well_known_keys, Bytes}; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, Header}, -}; -use std::{marker::PhantomData, sync::Arc}; +use sp_blockchain::{Error as BlockChainError, HeaderBackend, HeaderMetadata}; +use sp_core::{hexdisplay::HexDisplay, storage::well_known_keys, traits::CallContext, Bytes}; +use sp_runtime::traits::Block as BlockT; +use std::{marker::PhantomData, sync::Arc, time::Duration}; + +pub(crate) const LOG_TARGET: &str = "rpc-spec-v2"; /// An API for chain head RPC calls. -pub struct ChainHead { +pub struct ChainHead, Block: BlockT, Client> { /// Substrate client. client: Arc, /// Backend of the chain. @@ -68,16 +57,14 @@ pub struct ChainHead { /// Executor to spawn subscriptions. executor: SubscriptionTaskExecutor, /// Keep track of the pinned blocks for each subscription. - subscriptions: Arc>, + subscriptions: Arc>, /// The hexadecimal encoded hash of the genesis block. genesis_hash: String, - /// The maximum number of pinned blocks allowed per connection. - max_pinned_blocks: usize, /// Phantom member to pin the block type. _phantom: PhantomData, } -impl ChainHead { +impl, Block: BlockT, Client> ChainHead { /// Create a new [`ChainHead`]. pub fn new>( client: Arc, @@ -85,16 +72,20 @@ impl ChainHead { executor: SubscriptionTaskExecutor, genesis_hash: GenesisHash, max_pinned_blocks: usize, + max_pinned_duration: Duration, ) -> Self { let genesis_hash = format!("0x{:?}", HexDisplay::from(&genesis_hash.as_ref())); Self { client, - backend, + backend: backend.clone(), executor, - subscriptions: Arc::new(SubscriptionManagement::new()), + subscriptions: Arc::new(SubscriptionManagement::new( + max_pinned_blocks, + max_pinned_duration, + backend, + )), genesis_hash, - max_pinned_blocks, _phantom: PhantomData, } } @@ -122,73 +113,6 @@ impl ChainHead { } } -/// Generate the initial events reported by the RPC `follow` method. -/// -/// This includes the "Initialized" event followed by the in-memory -/// blocks via "NewBlock" and the "BestBlockChanged". -fn generate_initial_events( - client: &Arc, - backend: &Arc, - handle: &SubscriptionHandle, - runtime_updates: bool, -) -> Result>, SubscriptionManagementError> -where - Block: BlockT + 'static, - Block::Header: Unpin, - BE: Backend + 'static, - Client: HeaderBackend + CallApiAt + 'static, -{ - // The initialized event is the first one sent. - let finalized_block_hash = client.info().finalized_hash; - handle.pin_block(finalized_block_hash)?; - - let finalized_block_runtime = generate_runtime_event( - &client, - runtime_updates, - &BlockId::Hash(finalized_block_hash), - None, - ); - - let initialized_event = FollowEvent::Initialized(Initialized { - finalized_block_hash, - finalized_block_runtime, - runtime_updates, - }); - - let initial_blocks = get_initial_blocks(&backend, finalized_block_hash); - let mut in_memory_blocks = Vec::with_capacity(initial_blocks.len() + 1); - - in_memory_blocks.push(initialized_event); - for (child, parent) in initial_blocks.into_iter() { - handle.pin_block(child)?; - - let new_runtime = generate_runtime_event( - &client, - runtime_updates, - &BlockId::Hash(child), - Some(&BlockId::Hash(parent)), - ); - - let event = FollowEvent::NewBlock(NewBlock { - block_hash: child, - parent_block_hash: parent, - new_runtime, - runtime_updates, - }); - - in_memory_blocks.push(event); - } - - // Generate a new best block event. - let best_block_hash = client.info().best_hash; - if best_block_hash != finalized_block_hash { - let best_block = FollowEvent::BestBlockChanged(BestBlockChanged { best_block_hash }); - in_memory_blocks.push(best_block); - }; - - Ok(in_memory_blocks) -} - /// Parse hex-encoded string parameter as raw bytes. /// /// If the parsing fails, the subscription is rejected. @@ -210,243 +134,6 @@ fn parse_hex_param( } } -/// Conditionally generate the runtime event of the given block. -fn generate_runtime_event( - client: &Arc, - runtime_updates: bool, - block: &BlockId, - parent: Option<&BlockId>, -) -> Option -where - Block: BlockT + 'static, - Client: CallApiAt + 'static, -{ - // No runtime versions should be reported. - if !runtime_updates { - return None - } - - let block_rt = match client.runtime_version_at(block) { - Ok(rt) => rt, - Err(err) => return Some(err.into()), - }; - - let parent = match parent { - Some(parent) => parent, - // Nothing to compare against, always report. - None => return Some(RuntimeEvent::Valid(RuntimeVersionEvent { spec: block_rt })), - }; - - let parent_rt = match client.runtime_version_at(parent) { - Ok(rt) => rt, - Err(err) => return Some(err.into()), - }; - - // Report the runtime version change. - if block_rt != parent_rt { - Some(RuntimeEvent::Valid(RuntimeVersionEvent { spec: block_rt })) - } else { - None - } -} - -/// Get the in-memory blocks of the client, starting from the provided finalized hash. -/// -/// Returns a tuple of block hash with parent hash. -fn get_initial_blocks( - backend: &Arc, - parent_hash: Block::Hash, -) -> Vec<(Block::Hash, Block::Hash)> -where - Block: BlockT + 'static, - BE: Backend + 'static, -{ - let mut result = Vec::new(); - let mut next_hash = Vec::new(); - next_hash.push(parent_hash); - - while let Some(parent_hash) = next_hash.pop() { - let Ok(blocks) = backend.blockchain().children(parent_hash) else { - continue - }; - - for child_hash in blocks { - result.push((child_hash, parent_hash)); - next_hash.push(child_hash); - } - } - - result -} - -/// Submit the events from the provided stream to the RPC client -/// for as long as the `rx_stop` event was not called. -async fn submit_events( - sink: &mut SubscriptionSink, - mut stream: EventStream, - rx_stop: oneshot::Receiver<()>, -) where - EventStream: Stream + Unpin, - T: Serialize, -{ - let mut stream_item = stream.next(); - let mut stop_event = rx_stop; - - while let Either::Left((Some(event), next_stop_event)) = - futures_util::future::select(stream_item, stop_event).await - { - match sink.send(&event) { - Ok(true) => { - stream_item = stream.next(); - stop_event = next_stop_event; - }, - // Client disconnected. - Ok(false) => return, - Err(_) => { - // Failed to submit event. - break - }, - } - } - - let _ = sink.send(&FollowEvent::::Stop); -} - -/// Generate the "NewBlock" event and potentially the "BestBlockChanged" event for -/// every notification. -fn handle_import_blocks( - client: &Arc, - handle: &SubscriptionHandle, - runtime_updates: bool, - notification: BlockImportNotification, -) -> Result<(FollowEvent, Option>), SubscriptionManagementError> -where - Block: BlockT + 'static, - Client: CallApiAt + 'static, -{ - handle.pin_block(notification.hash)?; - - let new_runtime = generate_runtime_event( - &client, - runtime_updates, - &BlockId::Hash(notification.hash), - Some(&BlockId::Hash(*notification.header.parent_hash())), - ); - - // Note: `Block::Hash` will serialize to hexadecimal encoded string. - let new_block = FollowEvent::NewBlock(NewBlock { - block_hash: notification.hash, - parent_block_hash: *notification.header.parent_hash(), - new_runtime, - runtime_updates, - }); - - if !notification.is_new_best { - return Ok((new_block, None)) - } - - // If this is the new best block, then we need to generate two events. - let best_block_event = - FollowEvent::BestBlockChanged(BestBlockChanged { best_block_hash: notification.hash }); - - let mut best_block_cache = handle.best_block_write(); - match *best_block_cache { - Some(block_cache) => { - // The RPC layer has not reported this block as best before. - // Note: This handles the race with the finalized branch. - if block_cache != notification.hash { - *best_block_cache = Some(notification.hash); - Ok((new_block, Some(best_block_event))) - } else { - Ok((new_block, None)) - } - }, - None => { - *best_block_cache = Some(notification.hash); - Ok((new_block, Some(best_block_event))) - }, - } -} - -/// Generate the "Finalized" event and potentially the "BestBlockChanged" for -/// every notification. -fn handle_finalized_blocks( - client: &Arc, - handle: &SubscriptionHandle, - notification: FinalityNotification, -) -> Result<(FollowEvent, Option>), SubscriptionManagementError> -where - Block: BlockT + 'static, - Client: HeaderBackend + HeaderMetadata + 'static, -{ - let last_finalized = notification.hash; - // We might not receive all new blocks reports, also pin the block here. - handle.pin_block(last_finalized)?; - - // The tree route contains the exclusive path from the last finalized block - // to the block reported by the notification. Ensure the finalized block is - // properly reported to that path. - let mut finalized_block_hashes = notification.tree_route.iter().cloned().collect::>(); - finalized_block_hashes.push(last_finalized); - - let pruned_block_hashes: Vec<_> = notification.stale_heads.iter().cloned().collect(); - - let finalized_event = FollowEvent::Finalized(Finalized { - finalized_block_hashes, - pruned_block_hashes: pruned_block_hashes.clone(), - }); - - let mut best_block_cache = handle.best_block_write(); - match *best_block_cache { - Some(block_cache) => { - // Check if the current best block is also reported as pruned. - let reported_pruned = pruned_block_hashes.iter().find(|&&hash| hash == block_cache); - if reported_pruned.is_none() { - return Ok((finalized_event, None)) - } - - // The best block is reported as pruned. Therefore, we need to signal a new - // best block event before submitting the finalized event. - let best_block_hash = client.info().best_hash; - if best_block_hash == block_cache { - // The client doest not have any new information about the best block. - // The information from `.info()` is updated from the DB as the last - // step of the finalization and it should be up to date. Also, the - // displaced nodes (list of nodes reported) should be reported with - // an offset of 32 blocks for substrate. - // If the info is outdated, there is nothing the RPC can do for now. - error!(target: "rpc-spec-v2", "Client does not contain different best block"); - Ok((finalized_event, None)) - } else { - let ancestor = sp_blockchain::lowest_common_ancestor( - &**client, - last_finalized, - best_block_hash, - ) - .map_err(|_| { - SubscriptionManagementError::Custom("Could not find common ancestor".into()) - })?; - - // The client's best block must be a descendent of the last finalized block. - // In other words, the lowest common ancestor must be the last finalized block. - if ancestor.hash != last_finalized { - return Err(SubscriptionManagementError::Custom( - "The finalized block is not an ancestor of the best block".into(), - )) - } - - // The RPC needs to also submit a new best block changed before the - // finalized event. - *best_block_cache = Some(best_block_hash); - let best_block_event = - FollowEvent::BestBlockChanged(BestBlockChanged { best_block_hash }); - Ok((finalized_event, Some(best_block_event))) - } - }, - None => Ok((finalized_event, None)), - } -} - #[async_trait] impl ChainHeadApiServer for ChainHead where @@ -465,7 +152,7 @@ where fn chain_head_unstable_follow( &self, mut sink: SubscriptionSink, - runtime_updates: bool, + with_runtime: bool, ) -> SubscriptionResult { let sub_id = match self.accept_subscription(&mut sink) { Ok(sub_id) => sub_id, @@ -474,76 +161,33 @@ where return Err(err) }, }; - // Keep track of the subscription. - let Some((rx_stop, sub_handle)) = self.subscriptions.insert_subscription(sub_id.clone(), runtime_updates, self.max_pinned_blocks) else { + let Some(rx_stop) = self.subscriptions.insert_subscription(sub_id.clone(), with_runtime) + else { // Inserting the subscription can only fail if the JsonRPSee // generated a duplicate subscription ID. - debug!(target: "rpc-spec-v2", "[follow][id={:?}] Subscription already accepted", sub_id); + debug!(target: LOG_TARGET, "[follow][id={:?}] Subscription already accepted", sub_id); let _ = sink.send(&FollowEvent::::Stop); return Ok(()) }; - debug!(target: "rpc-spec-v2", "[follow][id={:?}] Subscription accepted", sub_id); - - let client = self.client.clone(); - let handle = sub_handle.clone(); - let subscription_id = sub_id.clone(); - - let stream_import = self - .client - .import_notification_stream() - .map(move |notification| { - match handle_import_blocks(&client, &handle, runtime_updates, notification) { - Ok((new_block, None)) => stream::iter(vec![new_block]), - Ok((new_block, Some(best_block))) => stream::iter(vec![new_block, best_block]), - Err(_) => { - debug!(target: "rpc-spec-v2", "[follow][id={:?}] Failed to handle block import notification.", subscription_id); - handle.stop(); - stream::iter(vec![]) - }, - } - }) - .flatten(); - - let client = self.client.clone(); - let handle = sub_handle.clone(); - let subscription_id = sub_id.clone(); - - let stream_finalized = self - .client - .finality_notification_stream() - .map(move |notification| { - match handle_finalized_blocks(&client, &handle, notification) { - Ok((finalized_event, None)) => stream::iter(vec![finalized_event]), - Ok((finalized_event, Some(best_block))) => - stream::iter(vec![best_block, finalized_event]), - Err(_) => { - debug!(target: "rpc-spec-v2", "[follow][id={:?}] Failed to import finalized blocks", subscription_id); - handle.stop(); - stream::iter(vec![]) - }, - } - }) - .flatten(); + debug!(target: LOG_TARGET, "[follow][id={:?}] Subscription accepted", sub_id); - let merged = tokio_stream::StreamExt::merge(stream_import, stream_finalized); let subscriptions = self.subscriptions.clone(); - let client = self.client.clone(); let backend = self.backend.clone(); + let client = self.client.clone(); let fut = async move { - let Ok(initial_events) = generate_initial_events(&client, &backend, &sub_handle, runtime_updates) else { - // Stop the subscription if we exceeded the maximum number of blocks pinned. - debug!(target: "rpc-spec-v2", "[follow][id={:?}] Exceeded max pinned blocks from initial events", sub_id); - let _ = sink.send(&FollowEvent::::Stop); - return - }; + let mut chain_head_follow = ChainHeadFollower::new( + client, + backend, + subscriptions.clone(), + with_runtime, + sub_id.clone(), + ); - let stream = stream::iter(initial_events).chain(merged); + chain_head_follow.generate_events(sink, rx_stop).await; - submit_events(&mut sink, stream.boxed(), rx_stop).await; - // The client disconnected or called the unsubscribe method. subscriptions.remove_subscription(&sub_id); - debug!(target: "rpc-spec-v2", "[follow][id={:?}] Subscription removed", sub_id); + debug!(target: LOG_TARGET, "[follow][id={:?}] Subscription removed", sub_id); }; self.executor.spawn("substrate-rpc-subscription", Some("rpc"), fut.boxed()); @@ -560,19 +204,28 @@ where let client = self.client.clone(); let subscriptions = self.subscriptions.clone(); - let fut = async move { - let Some(handle) = subscriptions.get_subscription(&follow_subscription) else { + let block_guard = match subscriptions.lock_block(&follow_subscription, hash) { + Ok(block) => block, + Err(SubscriptionManagementError::SubscriptionAbsent) => { // Invalid invalid subscription ID. let _ = sink.send(&ChainHeadEvent::::Disjoint); - return - }; - - // Block is not part of the subscription. - if !handle.contains_block(&hash) { + return Ok(()) + }, + Err(SubscriptionManagementError::BlockHashAbsent) => { + // Block is not part of the subscription. let _ = sink.reject(ChainHeadRpcError::InvalidBlock); - return - } + return Ok(()) + }, + Err(error) => { + let _ = sink.send(&ChainHeadEvent::::Error(ErrorEvent { + error: error.to_string(), + })); + return Ok(()) + }, + }; + let fut = async move { + let _block_guard = block_guard; let event = match client.block(hash) { Ok(Some(signed_block)) => { let extrinsics = signed_block.block.extrinsics(); @@ -581,8 +234,13 @@ where }, Ok(None) => { // The block's body was pruned. This subscription ID has become invalid. - debug!(target: "rpc-spec-v2", "[body][id={:?}] Stopping subscription because hash={:?} was pruned", follow_subscription, hash); - handle.stop(); + debug!( + target: LOG_TARGET, + "[body][id={:?}] Stopping subscription because hash={:?} was pruned", + &follow_subscription, + hash + ); + subscriptions.remove_subscription(&follow_subscription); ChainHeadEvent::::Disjoint }, Err(error) => ChainHeadEvent::Error(ErrorEvent { error: error.to_string() }), @@ -599,16 +257,19 @@ where follow_subscription: String, hash: Block::Hash, ) -> RpcResult> { - let Some(handle) = self.subscriptions.get_subscription(&follow_subscription) else { - // Invalid invalid subscription ID. - return Ok(None) + let _block_guard = match self.subscriptions.lock_block(&follow_subscription, hash) { + Ok(block) => block, + Err(SubscriptionManagementError::SubscriptionAbsent) => { + // Invalid invalid subscription ID. + return Ok(None) + }, + Err(SubscriptionManagementError::BlockHashAbsent) => { + // Block is not part of the subscription. + return Err(ChainHeadRpcError::InvalidBlock.into()) + }, + Err(_) => return Err(ChainHeadRpcError::InvalidBlock.into()), }; - // Block is not part of the subscription. - if !handle.contains_block(&hash) { - return Err(ChainHeadRpcError::InvalidBlock.into()) - } - self.client .header(hash) .map(|opt_header| opt_header.map(|h| format!("0x{:?}", HexDisplay::from(&h.encode())))) @@ -639,19 +300,28 @@ where let client = self.client.clone(); let subscriptions = self.subscriptions.clone(); - let fut = async move { - let Some(handle) = subscriptions.get_subscription(&follow_subscription) else { + let block_guard = match subscriptions.lock_block(&follow_subscription, hash) { + Ok(block) => block, + Err(SubscriptionManagementError::SubscriptionAbsent) => { // Invalid invalid subscription ID. let _ = sink.send(&ChainHeadEvent::::Disjoint); - return - }; - - // Block is not part of the subscription. - if !handle.contains_block(&hash) { + return Ok(()) + }, + Err(SubscriptionManagementError::BlockHashAbsent) => { + // Block is not part of the subscription. let _ = sink.reject(ChainHeadRpcError::InvalidBlock); - return - } + return Ok(()) + }, + Err(error) => { + let _ = sink.send(&ChainHeadEvent::::Error(ErrorEvent { + error: error.to_string(), + })); + return Ok(()) + }, + }; + let fut = async move { + let _block_guard = block_guard; // The child key is provided, use the key to query the child trie. if let Some(child_key) = child_key { // The child key must not be prefixed with ":child_storage:" nor @@ -720,21 +390,29 @@ where let client = self.client.clone(); let subscriptions = self.subscriptions.clone(); - let fut = async move { - let Some(handle) = subscriptions.get_subscription(&follow_subscription) else { + let block_guard = match subscriptions.lock_block(&follow_subscription, hash) { + Ok(block) => block, + Err(SubscriptionManagementError::SubscriptionAbsent) => { // Invalid invalid subscription ID. let _ = sink.send(&ChainHeadEvent::::Disjoint); - return - }; - - // Block is not part of the subscription. - if !handle.contains_block(&hash) { + return Ok(()) + }, + Err(SubscriptionManagementError::BlockHashAbsent) => { + // Block is not part of the subscription. let _ = sink.reject(ChainHeadRpcError::InvalidBlock); - return - } + return Ok(()) + }, + Err(error) => { + let _ = sink.send(&ChainHeadEvent::::Error(ErrorEvent { + error: error.to_string(), + })); + return Ok(()) + }, + }; - // Reject subscription if runtime_updates is false. - if !handle.has_runtime_updates() { + let fut = async move { + // Reject subscription if with_runtime is false. + if !block_guard.has_runtime() { let _ = sink.reject(ChainHeadRpcError::InvalidParam( "The runtime updates flag must be set".into(), )); @@ -743,12 +421,7 @@ where let res = client .executor() - .call( - hash, - &function, - &call_parameters, - client.execution_extensions().strategies().other, - ) + .call(hash, &function, &call_parameters, CallContext::Offchain) .map(|result| { let result = format!("0x{:?}", HexDisplay::from(&result)); ChainHeadEvent::Done(ChainHeadResult { result }) @@ -769,15 +442,17 @@ where follow_subscription: String, hash: Block::Hash, ) -> RpcResult<()> { - let Some(handle) = self.subscriptions.get_subscription(&follow_subscription) else { - // Invalid invalid subscription ID. - return Ok(()) - }; - - if !handle.unpin_block(&hash) { - return Err(ChainHeadRpcError::InvalidBlock.into()) + match self.subscriptions.unpin_block(&follow_subscription, hash) { + Ok(()) => Ok(()), + Err(SubscriptionManagementError::SubscriptionAbsent) => { + // Invalid invalid subscription ID. + Ok(()) + }, + Err(SubscriptionManagementError::BlockHashAbsent) => { + // Block is not part of the subscription. + Err(ChainHeadRpcError::InvalidBlock.into()) + }, + Err(_) => Err(ChainHeadRpcError::InvalidBlock.into()), } - - Ok(()) } } diff --git a/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs b/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs new file mode 100644 index 000000000000..799978be532a --- /dev/null +++ b/client/rpc-spec-v2/src/chain_head/chain_head_follow.rs @@ -0,0 +1,610 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +//! Implementation of the `chainHead_follow` method. + +use crate::chain_head::{ + chain_head::LOG_TARGET, + event::{ + BestBlockChanged, Finalized, FollowEvent, Initialized, NewBlock, RuntimeEvent, + RuntimeVersionEvent, + }, + subscription::{SubscriptionManagement, SubscriptionManagementError}, +}; +use futures::{ + channel::oneshot, + stream::{self, Stream, StreamExt}, +}; +use futures_util::future::Either; +use jsonrpsee::SubscriptionSink; +use log::{debug, error}; +use sc_client_api::{ + Backend, BlockBackend, BlockImportNotification, BlockchainEvents, FinalityNotification, +}; +use sp_api::CallApiAt; +use sp_blockchain::{ + Backend as BlockChainBackend, Error as BlockChainError, HeaderBackend, HeaderMetadata, Info, +}; +use sp_runtime::traits::{Block as BlockT, Header as HeaderT, NumberFor}; +use std::{collections::HashSet, sync::Arc}; + +/// Generates the events of the `chainHead_follow` method. +pub struct ChainHeadFollower, Block: BlockT, Client> { + /// Substrate client. + client: Arc, + /// Backend of the chain. + backend: Arc, + /// Subscriptions handle. + sub_handle: Arc>, + /// Subscription was started with the runtime updates flag. + with_runtime: bool, + /// Subscription ID. + sub_id: String, + /// The best reported block by this subscription. + best_block_cache: Option, +} + +impl, Block: BlockT, Client> ChainHeadFollower { + /// Create a new [`ChainHeadFollower`]. + pub fn new( + client: Arc, + backend: Arc, + sub_handle: Arc>, + with_runtime: bool, + sub_id: String, + ) -> Self { + Self { client, backend, sub_handle, with_runtime, sub_id, best_block_cache: None } + } +} + +/// A block notification. +enum NotificationType { + /// The initial events generated from the node's memory. + InitialEvents(Vec>), + /// The new block notification obtained from `import_notification_stream`. + NewBlock(BlockImportNotification), + /// The finalized block notification obtained from `finality_notification_stream`. + Finalized(FinalityNotification), +} + +/// The initial blocks that should be reported or ignored by the chainHead. +#[derive(Clone, Debug)] +struct InitialBlocks { + /// Children of the latest finalized block, for which the `NewBlock` + /// event must be generated. + /// + /// It is a tuple of (block hash, parent hash). + finalized_block_descendants: Vec<(Block::Hash, Block::Hash)>, + /// Blocks that should not be reported as pruned by the `Finalized` event. + /// + /// Substrate database will perform the pruning of height N at + /// the finalization N + 1. We could have the following block tree + /// when the user subscribes to the `follow` method: + /// [A] - [A1] - [A2] - [A3] + /// ^^ finalized + /// - [A1] - [B1] + /// + /// When the A3 block is finalized, B1 is reported as pruned, however + /// B1 was never reported as `NewBlock` (and as such was never pinned). + /// This is because the `NewBlock` events are generated for children of + /// the finalized hash. + pruned_forks: HashSet, +} + +/// The startup point from which chainHead started to generate events. +struct StartupPoint { + /// Best block hash. + pub best_hash: Block::Hash, + /// The head of the finalized chain. + pub finalized_hash: Block::Hash, + /// Last finalized block number. + pub finalized_number: NumberFor, +} + +impl From> for StartupPoint { + fn from(info: Info) -> Self { + StartupPoint:: { + best_hash: info.best_hash, + finalized_hash: info.finalized_hash, + finalized_number: info.finalized_number, + } + } +} + +impl ChainHeadFollower +where + Block: BlockT + 'static, + BE: Backend + 'static, + Client: BlockBackend + + HeaderBackend + + HeaderMetadata + + BlockchainEvents + + CallApiAt + + 'static, +{ + /// Conditionally generate the runtime event of the given block. + fn generate_runtime_event( + &self, + block: Block::Hash, + parent: Option, + ) -> Option { + // No runtime versions should be reported. + if !self.with_runtime { + return None + } + + let block_rt = match self.client.runtime_version_at(block) { + Ok(rt) => rt, + Err(err) => return Some(err.into()), + }; + + let parent = match parent { + Some(parent) => parent, + // Nothing to compare against, always report. + None => return Some(RuntimeEvent::Valid(RuntimeVersionEvent { spec: block_rt })), + }; + + let parent_rt = match self.client.runtime_version_at(parent) { + Ok(rt) => rt, + Err(err) => return Some(err.into()), + }; + + // Report the runtime version change. + if block_rt != parent_rt { + Some(RuntimeEvent::Valid(RuntimeVersionEvent { spec: block_rt })) + } else { + None + } + } + + /// Get the in-memory blocks of the client, starting from the provided finalized hash. + fn get_init_blocks_with_forks( + &self, + startup_point: &StartupPoint, + ) -> Result, SubscriptionManagementError> { + let blockchain = self.backend.blockchain(); + let leaves = blockchain.leaves()?; + let finalized = startup_point.finalized_hash; + let mut pruned_forks = HashSet::new(); + let mut finalized_block_descendants = Vec::new(); + let mut unique_descendants = HashSet::new(); + for leaf in leaves { + let tree_route = sp_blockchain::tree_route(blockchain, finalized, leaf)?; + + let blocks = tree_route.enacted().iter().map(|block| block.hash); + if !tree_route.retracted().is_empty() { + pruned_forks.extend(blocks); + } else { + // Ensure a `NewBlock` event is generated for all children of the + // finalized block. Describe the tree route as (child_node, parent_node) + // Note: the order of elements matters here. + let parents = std::iter::once(finalized).chain(blocks.clone()); + + for pair in blocks.zip(parents) { + if unique_descendants.insert(pair) { + finalized_block_descendants.push(pair); + } + } + } + } + + Ok(InitialBlocks { finalized_block_descendants, pruned_forks }) + } + + /// Generate the initial events reported by the RPC `follow` method. + /// + /// Returns the initial events that should be reported directly, together with pruned + /// block hashes that should be ignored by the `Finalized` event. + fn generate_init_events( + &mut self, + startup_point: &StartupPoint, + ) -> Result<(Vec>, HashSet), SubscriptionManagementError> + { + let init = self.get_init_blocks_with_forks(startup_point)?; + + let initial_blocks = init.finalized_block_descendants; + + // The initialized event is the first one sent. + let finalized_block_hash = startup_point.finalized_hash; + self.sub_handle.pin_block(&self.sub_id, finalized_block_hash)?; + + let finalized_block_runtime = self.generate_runtime_event(finalized_block_hash, None); + + let initialized_event = FollowEvent::Initialized(Initialized { + finalized_block_hash, + finalized_block_runtime, + with_runtime: self.with_runtime, + }); + + let mut finalized_block_descendants = Vec::with_capacity(initial_blocks.len() + 1); + + finalized_block_descendants.push(initialized_event); + for (child, parent) in initial_blocks.into_iter() { + self.sub_handle.pin_block(&self.sub_id, child)?; + + let new_runtime = self.generate_runtime_event(child, Some(parent)); + + let event = FollowEvent::NewBlock(NewBlock { + block_hash: child, + parent_block_hash: parent, + new_runtime, + with_runtime: self.with_runtime, + }); + + finalized_block_descendants.push(event); + } + + // Generate a new best block event. + let best_block_hash = startup_point.best_hash; + if best_block_hash != finalized_block_hash { + let best_block = FollowEvent::BestBlockChanged(BestBlockChanged { best_block_hash }); + self.best_block_cache = Some(best_block_hash); + finalized_block_descendants.push(best_block); + }; + + Ok((finalized_block_descendants, init.pruned_forks)) + } + + /// Generate the "NewBlock" event and potentially the "BestBlockChanged" event for the + /// given block hash. + fn generate_import_events( + &mut self, + block_hash: Block::Hash, + parent_block_hash: Block::Hash, + is_best_block: bool, + ) -> Vec> { + let new_runtime = self.generate_runtime_event(block_hash, Some(parent_block_hash)); + + let new_block = FollowEvent::NewBlock(NewBlock { + block_hash, + parent_block_hash, + new_runtime, + with_runtime: self.with_runtime, + }); + + if !is_best_block { + return vec![new_block] + } + + // If this is the new best block, then we need to generate two events. + let best_block_event = + FollowEvent::BestBlockChanged(BestBlockChanged { best_block_hash: block_hash }); + + match self.best_block_cache { + Some(block_cache) => { + // The RPC layer has not reported this block as best before. + // Note: This handles the race with the finalized branch. + if block_cache != block_hash { + self.best_block_cache = Some(block_hash); + vec![new_block, best_block_event] + } else { + vec![new_block] + } + }, + None => { + self.best_block_cache = Some(block_hash); + vec![new_block, best_block_event] + }, + } + } + + /// Handle the import of new blocks by generating the appropriate events. + fn handle_import_blocks( + &mut self, + notification: BlockImportNotification, + startup_point: &StartupPoint, + ) -> Result>, SubscriptionManagementError> { + // The block was already pinned by the initial block events or by the finalized event. + if !self.sub_handle.pin_block(&self.sub_id, notification.hash)? { + return Ok(Default::default()) + } + + // Ensure we are only reporting blocks after the starting point. + if *notification.header.number() < startup_point.finalized_number { + return Ok(Default::default()) + } + + Ok(self.generate_import_events( + notification.hash, + *notification.header.parent_hash(), + notification.is_new_best, + )) + } + + /// Generates new block events from the given finalized hashes. + /// + /// It may be possible that the `Finalized` event fired before the `NewBlock` + /// event. In that case, for each finalized hash that was not reported yet + /// generate the `NewBlock` event. For the final finalized hash we must also + /// generate one `BestBlock` event. + fn generate_finalized_events( + &mut self, + finalized_block_hashes: &[Block::Hash], + ) -> Result>, SubscriptionManagementError> { + let mut events = Vec::new(); + + // Nothing to be done if no finalized hashes are provided. + let Some(first_hash) = finalized_block_hashes.get(0) else { return Ok(Default::default()) }; + + // Find the parent header. + let Some(first_header) = self.client.header(*first_hash)? else { + return Err(SubscriptionManagementError::BlockHeaderAbsent) + }; + + let parents = + std::iter::once(first_header.parent_hash()).chain(finalized_block_hashes.iter()); + for (i, (hash, parent)) in finalized_block_hashes.iter().zip(parents).enumerate() { + // Check if the block was already reported and thus, is already pinned. + if !self.sub_handle.pin_block(&self.sub_id, *hash)? { + continue + } + + // Generate `NewBlock` events for all blocks beside the last block in the list + if i + 1 != finalized_block_hashes.len() { + // Generate only the `NewBlock` event for this block. + events.extend(self.generate_import_events(*hash, *parent, false)); + } else { + // If we end up here and the `best_block` is a descendent of the finalized block + // (last block in the list), it means that there were skipped notifications. + // Otherwise `pin_block` would had returned `true`. + // + // When the node falls out of sync and then syncs up to the tip of the chain, it can + // happen that we skip notifications. Then it is better to terminate the connection + // instead of trying to send notifications for all missed blocks. + if let Some(best_block_hash) = self.best_block_cache { + let ancestor = sp_blockchain::lowest_common_ancestor( + &*self.client, + *hash, + best_block_hash, + )?; + + if ancestor.hash == *hash { + return Err(SubscriptionManagementError::Custom( + "A descendent of the finalized block was already reported".into(), + )) + } + } + + // Let's generate the `NewBlock` and `NewBestBlock` events for the block. + events.extend(self.generate_import_events(*hash, *parent, true)) + } + } + + Ok(events) + } + + /// Get all pruned block hashes from the provided stale heads. + /// + /// The result does not include hashes from `to_ignore`. + fn get_pruned_hashes( + &self, + stale_heads: &[Block::Hash], + last_finalized: Block::Hash, + to_ignore: &mut HashSet, + ) -> Result, SubscriptionManagementError> { + let blockchain = self.backend.blockchain(); + let mut pruned = Vec::new(); + + for stale_head in stale_heads { + let tree_route = sp_blockchain::tree_route(blockchain, last_finalized, *stale_head)?; + + // Collect only blocks that are not part of the canonical chain. + pruned.extend(tree_route.enacted().iter().filter_map(|block| { + if !to_ignore.remove(&block.hash) { + Some(block.hash) + } else { + None + } + })) + } + + Ok(pruned) + } + + /// Handle the finalization notification by generating the `Finalized` event. + /// + /// If the block of the notification was not reported yet, this method also + /// generates the events similar to `handle_import_blocks`. + fn handle_finalized_blocks( + &mut self, + notification: FinalityNotification, + to_ignore: &mut HashSet, + startup_point: &StartupPoint, + ) -> Result>, SubscriptionManagementError> { + let last_finalized = notification.hash; + + // Ensure we are only reporting blocks after the starting point. + if *notification.header.number() < startup_point.finalized_number { + return Ok(Default::default()) + } + + // The tree route contains the exclusive path from the last finalized block to the block + // reported by the notification. Ensure the finalized block is also reported. + let mut finalized_block_hashes = notification.tree_route.to_vec(); + finalized_block_hashes.push(last_finalized); + + // If the finalized hashes were not reported yet, generate the `NewBlock` events. + let mut events = self.generate_finalized_events(&finalized_block_hashes)?; + + // Report all pruned blocks from the notification that are not + // part of the fork we need to ignore. + let pruned_block_hashes = + self.get_pruned_hashes(¬ification.stale_heads, last_finalized, to_ignore)?; + + let finalized_event = FollowEvent::Finalized(Finalized { + finalized_block_hashes, + pruned_block_hashes: pruned_block_hashes.clone(), + }); + + match self.best_block_cache { + Some(block_cache) => { + // If the best block wasn't pruned, we are done here. + if !pruned_block_hashes.iter().any(|hash| *hash == block_cache) { + events.push(finalized_event); + return Ok(events) + } + + // The best block is reported as pruned. Therefore, we need to signal a new + // best block event before submitting the finalized event. + let best_block_hash = self.client.info().best_hash; + if best_block_hash == block_cache { + // The client doest not have any new information about the best block. + // The information from `.info()` is updated from the DB as the last + // step of the finalization and it should be up to date. + // If the info is outdated, there is nothing the RPC can do for now. + error!( + target: LOG_TARGET, + "[follow][id={:?}] Client does not contain different best block", + self.sub_id, + ); + events.push(finalized_event); + Ok(events) + } else { + // The RPC needs to also submit a new best block changed before the + // finalized event. + self.best_block_cache = Some(best_block_hash); + let best_block_event = + FollowEvent::BestBlockChanged(BestBlockChanged { best_block_hash }); + events.extend([best_block_event, finalized_event]); + Ok(events) + } + }, + None => { + events.push(finalized_event); + Ok(events) + }, + } + } + + /// Submit the events from the provided stream to the RPC client + /// for as long as the `rx_stop` event was not called. + async fn submit_events( + &mut self, + startup_point: &StartupPoint, + mut stream: EventStream, + mut to_ignore: HashSet, + mut sink: SubscriptionSink, + rx_stop: oneshot::Receiver<()>, + ) where + EventStream: Stream> + Unpin, + { + let mut stream_item = stream.next(); + let mut stop_event = rx_stop; + + while let Either::Left((Some(event), next_stop_event)) = + futures_util::future::select(stream_item, stop_event).await + { + let events = match event { + NotificationType::InitialEvents(events) => Ok(events), + NotificationType::NewBlock(notification) => + self.handle_import_blocks(notification, &startup_point), + NotificationType::Finalized(notification) => + self.handle_finalized_blocks(notification, &mut to_ignore, &startup_point), + }; + + let events = match events { + Ok(events) => events, + Err(err) => { + debug!( + target: LOG_TARGET, + "[follow][id={:?}] Failed to handle stream notification {:?}", + self.sub_id, + err + ); + let _ = sink.send(&FollowEvent::::Stop); + return + }, + }; + + for event in events { + let result = sink.send(&event); + + // Migration note: the new version of jsonrpsee returns Result<(), DisconnectError> + // The logic from `Err(err)` should be moved when building the new + // `SubscriptionMessage`. + + // For now, jsonrpsee returns: + // Ok(true): message sent + // Ok(false): client disconnected or subscription closed + // Err(err): serder serialization error of the event + if let Err(err) = result { + // Failed to submit event. + debug!( + target: LOG_TARGET, + "[follow][id={:?}] Failed to send event {:?}", self.sub_id, err + ); + + let _ = sink.send(&FollowEvent::::Stop); + return + } + + if let Ok(false) = result { + // Client disconnected or subscription was closed. + return + } + } + + stream_item = stream.next(); + stop_event = next_stop_event; + } + + // If we got here either the substrate streams have closed + // or the `Stop` receiver was triggered. + let _ = sink.send(&FollowEvent::::Stop); + } + + /// Generate the block events for the `chainHead_follow` method. + pub async fn generate_events( + &mut self, + mut sink: SubscriptionSink, + rx_stop: oneshot::Receiver<()>, + ) { + // Register for the new block and finalized notifications. + let stream_import = self + .client + .import_notification_stream() + .map(|notification| NotificationType::NewBlock(notification)); + + let stream_finalized = self + .client + .finality_notification_stream() + .map(|notification| NotificationType::Finalized(notification)); + + let startup_point = StartupPoint::from(self.client.info()); + let (initial_events, pruned_forks) = match self.generate_init_events(&startup_point) { + Ok(blocks) => blocks, + Err(err) => { + debug!( + target: LOG_TARGET, + "[follow][id={:?}] Failed to generate the initial events {:?}", + self.sub_id, + err + ); + let _ = sink.send(&FollowEvent::::Stop); + return + }, + }; + + let initial = NotificationType::InitialEvents(initial_events); + let merged = tokio_stream::StreamExt::merge(stream_import, stream_finalized); + let stream = stream::once(futures::future::ready(initial)).chain(merged); + + self.submit_events(&startup_point, stream.boxed(), pruned_forks, sink, rx_stop) + .await; + } +} diff --git a/client/rpc-spec-v2/src/chain_head/error.rs b/client/rpc-spec-v2/src/chain_head/error.rs index 92f336ed4f4e..3f31d985de0f 100644 --- a/client/rpc-spec-v2/src/chain_head/error.rs +++ b/client/rpc-spec-v2/src/chain_head/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-spec-v2/src/chain_head/event.rs b/client/rpc-spec-v2/src/chain_head/event.rs index 25930cb6f995..3c970729d6ab 100644 --- a/client/rpc-spec-v2/src/chain_head/event.rs +++ b/client/rpc-spec-v2/src/chain_head/event.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -64,7 +64,7 @@ pub struct RuntimeVersionEvent { } /// The runtime event generated if the `follow` subscription -/// has set the `runtime_updates` flag. +/// has set the `with_runtime` flag. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] #[serde(tag = "type")] @@ -88,7 +88,7 @@ impl From for RuntimeEvent { /// This is the first event generated by the `follow` subscription /// and is submitted only once. /// -/// If the `runtime_updates` flag is set, then this event contains +/// If the `with_runtime` flag is set, then this event contains /// the `RuntimeEvent`, otherwise the `RuntimeEvent` is not present. #[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] @@ -99,23 +99,23 @@ pub struct Initialized { /// /// # Note /// - /// This is present only if the `runtime_updates` flag is set for + /// This is present only if the `with_runtime` flag is set for /// the `follow` subscription. pub finalized_block_runtime: Option, /// Privately keep track if the `finalized_block_runtime` should be /// serialized. #[serde(default)] - pub(crate) runtime_updates: bool, + pub(crate) with_runtime: bool, } impl Serialize for Initialized { /// Custom serialize implementation to include the `RuntimeEvent` depending - /// on the internal `runtime_updates` flag. + /// on the internal `with_runtime` flag. fn serialize(&self, serializer: S) -> Result where S: Serializer, { - if self.runtime_updates { + if self.with_runtime { let mut state = serializer.serialize_struct("Initialized", 2)?; state.serialize_field("finalizedBlockHash", &self.finalized_block_hash)?; state.serialize_field("finalizedBlockRuntime", &self.finalized_block_runtime)?; @@ -140,23 +140,23 @@ pub struct NewBlock { /// /// # Note /// - /// This is present only if the `runtime_updates` flag is set for + /// This is present only if the `with_runtime` flag is set for /// the `follow` subscription. pub new_runtime: Option, /// Privately keep track if the `finalized_block_runtime` should be /// serialized. #[serde(default)] - pub(crate) runtime_updates: bool, + pub(crate) with_runtime: bool, } impl Serialize for NewBlock { /// Custom serialize implementation to include the `RuntimeEvent` depending - /// on the internal `runtime_updates` flag. + /// on the internal `with_runtime` flag. fn serialize(&self, serializer: S) -> Result where S: Serializer, { - if self.runtime_updates { + if self.with_runtime { let mut state = serializer.serialize_struct("NewBlock", 3)?; state.serialize_field("blockHash", &self.block_hash)?; state.serialize_field("parentBlockHash", &self.parent_block_hash)?; @@ -192,12 +192,10 @@ pub struct Finalized { /// The event generated by the `follow` method. /// /// The events are generated in the following order: -/// 1. Initialized - generated only once to signal the -/// latest finalized block +/// 1. Initialized - generated only once to signal the latest finalized block /// 2. NewBlock - a new block was added. -/// 3. BestBlockChanged - indicate that the best block -/// is now the one from this event. The block was -/// announced priorly with the `NewBlock` event. +/// 3. BestBlockChanged - indicate that the best block is now the one from this event. The block was +/// announced priorly with the `NewBlock` event. /// 4. Finalized - State the finalized and pruned blocks. #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -253,7 +251,7 @@ mod tests { let event: FollowEvent = FollowEvent::Initialized(Initialized { finalized_block_hash: "0x1".into(), finalized_block_runtime: None, - runtime_updates: false, + with_runtime: false, }); let ser = serde_json::to_string(&event).unwrap(); @@ -278,7 +276,7 @@ mod tests { let mut initialized = Initialized { finalized_block_hash: "0x1".into(), finalized_block_runtime: Some(runtime_event), - runtime_updates: true, + with_runtime: true, }; let event: FollowEvent = FollowEvent::Initialized(initialized.clone()); @@ -291,8 +289,8 @@ mod tests { assert_eq!(ser, exp); let event_dec: FollowEvent = serde_json::from_str(exp).unwrap(); - // The `runtime_updates` field is used for serialization purposes. - initialized.runtime_updates = false; + // The `with_runtime` field is used for serialization purposes. + initialized.with_runtime = false; assert!(matches!( event_dec, FollowEvent::Initialized(ref dec) if dec == &initialized )); @@ -305,7 +303,7 @@ mod tests { block_hash: "0x1".into(), parent_block_hash: "0x2".into(), new_runtime: None, - runtime_updates: false, + with_runtime: false, }); let ser = serde_json::to_string(&event).unwrap(); @@ -331,7 +329,7 @@ mod tests { block_hash: "0x1".into(), parent_block_hash: "0x2".into(), new_runtime: Some(runtime_event), - runtime_updates: true, + with_runtime: true, }; let event: FollowEvent = FollowEvent::NewBlock(new_block.clone()); @@ -345,8 +343,8 @@ mod tests { assert_eq!(ser, exp); let event_dec: FollowEvent = serde_json::from_str(exp).unwrap(); - // The `runtime_updates` field is used for serialization purposes. - new_block.runtime_updates = false; + // The `with_runtime` field is used for serialization purposes. + new_block.with_runtime = false; assert!(matches!( event_dec, FollowEvent::NewBlock(ref dec) if dec == &new_block )); @@ -356,7 +354,7 @@ mod tests { block_hash: "0x1".into(), parent_block_hash: "0x2".into(), new_runtime: None, - runtime_updates: true, + with_runtime: true, }; let event: FollowEvent = FollowEvent::NewBlock(new_block.clone()); @@ -364,7 +362,7 @@ mod tests { let exp = r#"{"event":"newBlock","blockHash":"0x1","parentBlockHash":"0x2","newRuntime":null}"#; assert_eq!(ser, exp); - new_block.runtime_updates = false; + new_block.with_runtime = false; let event_dec: FollowEvent = serde_json::from_str(exp).unwrap(); assert!(matches!( event_dec, FollowEvent::NewBlock(ref dec) if dec == &new_block diff --git a/client/rpc-spec-v2/src/chain_head/mod.rs b/client/rpc-spec-v2/src/chain_head/mod.rs index a25933b40f07..1c489d323f19 100644 --- a/client/rpc-spec-v2/src/chain_head/mod.rs +++ b/client/rpc-spec-v2/src/chain_head/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -22,6 +22,8 @@ //! //! Methods are prefixed by `chainHead`. +#[cfg(test)] +mod test_utils; #[cfg(test)] mod tests; @@ -30,6 +32,7 @@ pub mod chain_head; pub mod error; pub mod event; +mod chain_head_follow; mod subscription; pub use api::ChainHeadApiServer; diff --git a/client/rpc-spec-v2/src/chain_head/subscription.rs b/client/rpc-spec-v2/src/chain_head/subscription.rs deleted file mode 100644 index 033db45ca755..000000000000 --- a/client/rpc-spec-v2/src/chain_head/subscription.rs +++ /dev/null @@ -1,284 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2022 Parity Technologies (UK) Ltd. -// 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 . - -//! Subscription management for tracking subscription IDs to pinned blocks. - -use futures::channel::oneshot; -use parking_lot::{RwLock, RwLockWriteGuard}; -use sp_runtime::traits::Block as BlockT; -use std::{ - collections::{hash_map::Entry, HashMap, HashSet}, - sync::Arc, -}; - -/// Subscription management error. -#[derive(Debug)] -pub enum SubscriptionManagementError { - /// The block cannot be pinned into memory because - /// the subscription has exceeded the maximum number - /// of blocks pinned. - ExceededLimits, - /// Custom error. - Custom(String), -} - -/// Inner subscription data structure. -struct SubscriptionInner { - /// The `runtime_updates` parameter flag of the subscription. - runtime_updates: bool, - /// Signals the "Stop" event. - tx_stop: Option>, - /// The blocks pinned. - blocks: HashSet, - /// The maximum number of pinned blocks allowed per subscription. - max_pinned_blocks: usize, -} - -/// Manage the blocks of a specific subscription ID. -#[derive(Clone)] -pub struct SubscriptionHandle { - inner: Arc>>, - /// The best reported block by this subscription. - /// Have this as a separate variable to easily share - /// the write guard with the RPC layer. - best_block: Arc>>, -} - -impl SubscriptionHandle { - /// Construct a new [`SubscriptionHandle`]. - fn new(runtime_updates: bool, tx_stop: oneshot::Sender<()>, max_pinned_blocks: usize) -> Self { - SubscriptionHandle { - inner: Arc::new(RwLock::new(SubscriptionInner { - runtime_updates, - tx_stop: Some(tx_stop), - blocks: HashSet::new(), - max_pinned_blocks, - })), - best_block: Arc::new(RwLock::new(None)), - } - } - - /// Trigger the stop event for the current subscription. - /// - /// This can happen on internal failure (ie, the pruning deleted the block from memory) - /// or if the user exceeded the amount of available pinned blocks. - pub fn stop(&self) { - let mut inner = self.inner.write(); - - if let Some(tx_stop) = inner.tx_stop.take() { - let _ = tx_stop.send(()); - } - } - - /// Pin a new block for the current subscription ID. - /// - /// Returns whether the value was newly inserted if the block can be pinned. - /// Otherwise, returns an error if the maximum number of blocks has been exceeded. - pub fn pin_block(&self, hash: Block::Hash) -> Result { - let mut inner = self.inner.write(); - - if inner.blocks.len() == inner.max_pinned_blocks { - // We have reached the limit. However, the block can be already inserted. - if inner.blocks.contains(&hash) { - return Ok(false) - } else { - return Err(SubscriptionManagementError::ExceededLimits) - } - } - - Ok(inner.blocks.insert(hash)) - } - - /// Unpin a new block for the current subscription ID. - /// - /// Returns whether the value was present in the set. - pub fn unpin_block(&self, hash: &Block::Hash) -> bool { - let mut inner = self.inner.write(); - inner.blocks.remove(hash) - } - - /// Check if the block hash is present for the provided subscription ID. - /// - /// Returns `true` if the set contains the block. - pub fn contains_block(&self, hash: &Block::Hash) -> bool { - let inner = self.inner.read(); - inner.blocks.contains(hash) - } - - /// Get the `runtime_updates` flag of this subscription. - pub fn has_runtime_updates(&self) -> bool { - let inner = self.inner.read(); - inner.runtime_updates - } - - /// Get the write guard of the best reported block. - pub fn best_block_write(&self) -> RwLockWriteGuard<'_, Option> { - self.best_block.write() - } -} - -/// Manage block pinning / unpinning for subscription IDs. -pub struct SubscriptionManagement { - /// Manage subscription by mapping the subscription ID - /// to a set of block hashes. - inner: RwLock>>, -} - -impl SubscriptionManagement { - /// Construct a new [`SubscriptionManagement`]. - pub fn new() -> Self { - SubscriptionManagement { inner: RwLock::new(HashMap::new()) } - } - - /// Insert a new subscription ID. - /// - /// If the subscription was not previously inserted, the method returns a tuple of - /// the receiver that is triggered upon the "Stop" event and the subscription - /// handle. Otherwise, when the subscription ID was already inserted returns none. - pub fn insert_subscription( - &self, - subscription_id: String, - runtime_updates: bool, - max_pinned_blocks: usize, - ) -> Option<(oneshot::Receiver<()>, SubscriptionHandle)> { - let mut subs = self.inner.write(); - - if let Entry::Vacant(entry) = subs.entry(subscription_id) { - let (tx_stop, rx_stop) = oneshot::channel(); - let handle = - SubscriptionHandle::::new(runtime_updates, tx_stop, max_pinned_blocks); - entry.insert(handle.clone()); - Some((rx_stop, handle)) - } else { - None - } - } - - /// Remove the subscription ID with associated pinned blocks. - pub fn remove_subscription(&self, subscription_id: &String) { - let mut subs = self.inner.write(); - subs.remove(subscription_id); - } - - /// Obtain the specific subscription handle. - pub fn get_subscription(&self, subscription_id: &String) -> Option> { - let subs = self.inner.write(); - subs.get(subscription_id).and_then(|handle| Some(handle.clone())) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use sp_core::H256; - use substrate_test_runtime_client::runtime::Block; - - #[test] - fn subscription_check_id() { - let subs = SubscriptionManagement::::new(); - - let id = "abc".to_string(); - let hash = H256::random(); - - let handle = subs.get_subscription(&id); - assert!(handle.is_none()); - - let (_, handle) = subs.insert_subscription(id.clone(), false, 10).unwrap(); - assert!(!handle.contains_block(&hash)); - - subs.remove_subscription(&id); - - let handle = subs.get_subscription(&id); - assert!(handle.is_none()); - } - - #[test] - fn subscription_check_block() { - let subs = SubscriptionManagement::::new(); - - let id = "abc".to_string(); - let hash = H256::random(); - - // Check with subscription. - let (_, handle) = subs.insert_subscription(id.clone(), false, 10).unwrap(); - assert!(!handle.contains_block(&hash)); - assert!(!handle.unpin_block(&hash)); - - handle.pin_block(hash).unwrap(); - assert!(handle.contains_block(&hash)); - // Unpin an invalid block. - assert!(!handle.unpin_block(&H256::random())); - - // Unpin the valid block. - assert!(handle.unpin_block(&hash)); - assert!(!handle.contains_block(&hash)); - } - - #[test] - fn subscription_check_stop_event() { - let subs = SubscriptionManagement::::new(); - - let id = "abc".to_string(); - - // Check with subscription. - let (mut rx_stop, handle) = subs.insert_subscription(id.clone(), false, 10).unwrap(); - - // Check the stop signal was not received. - let res = rx_stop.try_recv().unwrap(); - assert!(res.is_none()); - - // Inserting a second time returns None. - let res = subs.insert_subscription(id.clone(), false, 10); - assert!(res.is_none()); - - handle.stop(); - - // Check the signal was received. - let res = rx_stop.try_recv().unwrap(); - assert!(res.is_some()); - } - - #[test] - fn subscription_check_data() { - let subs = SubscriptionManagement::::new(); - - let id = "abc".to_string(); - let (_, handle) = subs.insert_subscription(id.clone(), false, 10).unwrap(); - assert!(!handle.has_runtime_updates()); - - let id2 = "abcd".to_string(); - let (_, handle) = subs.insert_subscription(id2.clone(), true, 10).unwrap(); - assert!(handle.has_runtime_updates()); - } - - #[test] - fn subscription_check_max_pinned() { - let subs = SubscriptionManagement::::new(); - - let id = "abc".to_string(); - let hash = H256::random(); - let hash_2 = H256::random(); - let (_, handle) = subs.insert_subscription(id.clone(), false, 1).unwrap(); - - handle.pin_block(hash).unwrap(); - // The same block can be pinned multiple times. - handle.pin_block(hash).unwrap(); - // Exceeded number of pinned blocks. - handle.pin_block(hash_2).unwrap_err(); - } -} diff --git a/client/rpc-spec-v2/src/chain_head/subscription/error.rs b/client/rpc-spec-v2/src/chain_head/subscription/error.rs new file mode 100644 index 000000000000..443ee9fb87a2 --- /dev/null +++ b/client/rpc-spec-v2/src/chain_head/subscription/error.rs @@ -0,0 +1,66 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 sp_blockchain::Error; + +/// Subscription management error. +#[derive(Debug, thiserror::Error)] +pub enum SubscriptionManagementError { + /// The block cannot be pinned into memory because + /// the subscription has exceeded the maximum number + /// of blocks pinned. + #[error("Exceeded pinning limits")] + ExceededLimits, + /// Error originated from the blockchain (client or backend). + #[error("Blockchain error {0}")] + Blockchain(Error), + /// The database does not contain a block hash. + #[error("Block hash is absent")] + BlockHashAbsent, + /// The database does not contain a block header. + #[error("Block header is absent")] + BlockHeaderAbsent, + /// The specified subscription ID is not present. + #[error("Subscription is absent")] + SubscriptionAbsent, + /// Custom error. + #[error("Subscription error {0}")] + Custom(String), +} + +// Blockchain error does not implement `PartialEq` needed for testing. +impl PartialEq for SubscriptionManagementError { + fn eq(&self, other: &SubscriptionManagementError) -> bool { + match (self, other) { + (Self::ExceededLimits, Self::ExceededLimits) | + // Not needed for testing. + (Self::Blockchain(_), Self::Blockchain(_)) | + (Self::BlockHashAbsent, Self::BlockHashAbsent) | + (Self::BlockHeaderAbsent, Self::BlockHeaderAbsent) | + (Self::SubscriptionAbsent, Self::SubscriptionAbsent) => true, + (Self::Custom(lhs), Self::Custom(rhs)) => lhs == rhs, + _ => false, + } + } +} + +impl From for SubscriptionManagementError { + fn from(err: Error) -> Self { + SubscriptionManagementError::Blockchain(err) + } +} diff --git a/client/rpc-spec-v2/src/chain_head/subscription/inner.rs b/client/rpc-spec-v2/src/chain_head/subscription/inner.rs new file mode 100644 index 000000000000..be8b8f46a284 --- /dev/null +++ b/client/rpc-spec-v2/src/chain_head/subscription/inner.rs @@ -0,0 +1,937 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 futures::channel::oneshot; +use sc_client_api::Backend; +use sp_runtime::traits::Block as BlockT; +use std::{ + collections::{hash_map::Entry, HashMap}, + sync::Arc, + time::{Duration, Instant}, +}; + +use crate::chain_head::subscription::SubscriptionManagementError; + +/// The state machine of a block of a single subscription ID. +/// +/// # Motivation +/// +/// Each block is registered twice: once from the `BestBlock` event +/// and once from the `Finalized` event. +/// +/// The state of a block must be tracked until both events register the +/// block and the user calls `unpin`. +/// +/// Otherwise, the following race might happen: +/// T0. BestBlock event: hash is tracked and pinned in backend. +/// T1. User calls unpin: hash is untracked and unpinned in backend. +/// T2. Finalized event: hash is tracked (no previous history) and pinned again. +/// +/// # State Machine Transition +/// +/// ```ignore +/// (register) +/// [ REGISTERED ] ---------------> [ FULLY REGISTERED ] +/// | | +/// | (unpin) | (unpin) +/// | | +/// V (register) V +/// [ UNPINNED ] -----------------> [ FULLY UNPINNED ] +/// ``` +#[derive(Debug, Clone, PartialEq)] +enum BlockStateMachine { + /// The block was registered by one event (either `Finalized` or `BestBlock` event). + /// + /// Unpin was not called. + Registered, + /// The block was registered by both events (`Finalized` and `BestBlock` events). + /// + /// Unpin was not called. + FullyRegistered, + /// The block was registered by one event (either `Finalized` or `BestBlock` event), + /// + /// Unpin __was__ called. + Unpinned, + /// The block was registered by both events (`Finalized` and `BestBlock` events). + /// + /// Unpin __was__ called. + FullyUnpinned, +} + +impl BlockStateMachine { + fn new() -> Self { + BlockStateMachine::Registered + } + + fn advance_register(&mut self) { + match self { + BlockStateMachine::Registered => *self = BlockStateMachine::FullyRegistered, + BlockStateMachine::Unpinned => *self = BlockStateMachine::FullyUnpinned, + _ => (), + } + } + + fn advance_unpin(&mut self) { + match self { + BlockStateMachine::Registered => *self = BlockStateMachine::Unpinned, + BlockStateMachine::FullyRegistered => *self = BlockStateMachine::FullyUnpinned, + _ => (), + } + } + + fn was_unpinned(&self) -> bool { + match self { + BlockStateMachine::Unpinned => true, + BlockStateMachine::FullyUnpinned => true, + _ => false, + } + } +} + +struct BlockState { + /// The state machine of this block. + state_machine: BlockStateMachine, + /// The timestamp when the block was inserted. + timestamp: Instant, +} + +/// The state of a single subscription ID. +struct SubscriptionState { + /// The `with_runtime` parameter flag of the subscription. + with_runtime: bool, + /// Signals the "Stop" event. + tx_stop: Option>, + /// Track the block hashes available for this subscription. + /// + /// This implementation assumes: + /// - most of the time subscriptions keep a few blocks of the chain's head pinned + /// - iteration through the blocks happens only when the hard limit is exceeded. + /// + /// Considering the assumption, iterating (in the unlike case) the hashmap O(N) is + /// more time efficient and code friendly than paying for: + /// - extra space: an extra BTreeMap to older hashes by oldest insertion + /// - extra time: O(log(N)) for insert/remove/find each `pin` block time per subscriptions + blocks: HashMap, +} + +impl SubscriptionState { + /// Trigger the stop event for the current subscription. + /// + /// This can happen on internal failure (ie, the pruning deleted the block from memory) + /// or if the subscription exceeded the available pinned blocks. + fn stop(&mut self) { + if let Some(tx_stop) = self.tx_stop.take() { + let _ = tx_stop.send(()); + } + } + + /// Keep track of the given block hash for this subscription. + /// + /// This does not handle pinning in the backend. + /// + /// Returns: + /// - true if this is the first time that the block is registered + /// - false if the block was already registered + fn register_block(&mut self, hash: Block::Hash) -> bool { + match self.blocks.entry(hash) { + Entry::Occupied(mut occupied) => { + let block_state = occupied.get_mut(); + + block_state.state_machine.advance_register(); + // Block was registered twice and unpin was called. + if block_state.state_machine == BlockStateMachine::FullyUnpinned { + occupied.remove(); + } + + // Second time we register this block. + false + }, + Entry::Vacant(vacant) => { + vacant.insert(BlockState { + state_machine: BlockStateMachine::new(), + timestamp: Instant::now(), + }); + + // First time we register this block. + true + }, + } + } + + /// A block is unregistered when the user calls `unpin`. + /// + /// Returns: + /// - true if the block can be unpinned. + /// - false if the subscription does not contain the block or it was unpinned. + fn unregister_block(&mut self, hash: Block::Hash) -> bool { + match self.blocks.entry(hash) { + Entry::Occupied(mut occupied) => { + let block_state = occupied.get_mut(); + + // Cannot unpin a block twice. + if block_state.state_machine.was_unpinned() { + return false + } + + block_state.state_machine.advance_unpin(); + // Block was registered twice and unpin was called. + if block_state.state_machine == BlockStateMachine::FullyUnpinned { + occupied.remove(); + } + + true + }, + // Block was not tracked. + Entry::Vacant(_) => false, + } + } + + /// A subscription contains a block when the block was + /// registered (`pin` was called) and the block was not `unpinned` yet. + /// + /// Returns `true` if the subscription contains the block. + fn contains_block(&self, hash: Block::Hash) -> bool { + let Some(state) = self.blocks.get(&hash) else { + // Block was not tracked. + return false + }; + + // Subscription no longer contains the block if `unpin` was called. + !state.state_machine.was_unpinned() + } + + /// Get the timestamp of the oldest inserted block. + /// + /// # Note + /// + /// This iterates over all the blocks of the subscription. + fn find_oldest_block_timestamp(&self) -> Instant { + let mut timestamp = Instant::now(); + for (_, state) in self.blocks.iter() { + timestamp = std::cmp::min(timestamp, state.timestamp); + } + timestamp + } +} + +/// Keeps a specific block pinned while the handle is alive. +/// This object ensures that the block is not unpinned while +/// executing an RPC method call. +pub struct BlockGuard> { + hash: Block::Hash, + with_runtime: bool, + backend: Arc, +} + +// Custom implementation of Debug to avoid bounds on `backend: Debug` for `unwrap_err()` needed for +// testing. +impl> std::fmt::Debug for BlockGuard { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "BlockGuard hash {:?} with_runtime {:?}", self.hash, self.with_runtime) + } +} + +impl> BlockGuard { + /// Construct a new [`BlockGuard`] . + fn new( + hash: Block::Hash, + with_runtime: bool, + backend: Arc, + ) -> Result { + backend + .pin_block(hash) + .map_err(|err| SubscriptionManagementError::Custom(err.to_string()))?; + + Ok(Self { hash, with_runtime, backend }) + } + + /// The `with_runtime` flag of the subscription. + pub fn has_runtime(&self) -> bool { + self.with_runtime + } +} + +impl> Drop for BlockGuard { + fn drop(&mut self) { + self.backend.unpin_block(self.hash); + } +} + +pub struct SubscriptionsInner> { + /// Reference count the block hashes across all subscriptions. + /// + /// The pinned blocks cannot exceed the [`Self::global_limit`] limit. + /// When the limit is exceeded subscriptions are stopped via the `Stop` event. + global_blocks: HashMap, + /// The maximum number of pinned blocks across all subscriptions. + global_max_pinned_blocks: usize, + /// The maximum duration that a block is allowed to be pinned per subscription. + local_max_pin_duration: Duration, + /// Map the subscription ID to internal details of the subscription. + subs: HashMap>, + /// Backend pinning / unpinning blocks. + /// + /// The `Arc` is handled one level-above, but substrate exposes the backend as Arc. + backend: Arc, +} + +impl> SubscriptionsInner { + /// Construct a new [`SubscriptionsInner`] from the specified limits. + pub fn new( + global_max_pinned_blocks: usize, + local_max_pin_duration: Duration, + backend: Arc, + ) -> Self { + SubscriptionsInner { + global_blocks: Default::default(), + global_max_pinned_blocks, + local_max_pin_duration, + subs: Default::default(), + backend, + } + } + + /// Insert a new subscription ID. + pub fn insert_subscription( + &mut self, + sub_id: String, + with_runtime: bool, + ) -> Option> { + if let Entry::Vacant(entry) = self.subs.entry(sub_id) { + let (tx_stop, rx_stop) = oneshot::channel(); + let state = SubscriptionState:: { + with_runtime, + tx_stop: Some(tx_stop), + blocks: Default::default(), + }; + entry.insert(state); + Some(rx_stop) + } else { + None + } + } + + /// Remove the subscription ID with associated pinned blocks. + pub fn remove_subscription(&mut self, sub_id: &str) { + let Some(mut sub) = self.subs.remove(sub_id) else { return }; + + // The `Stop` event can be generated only once. + sub.stop(); + + for (hash, state) in sub.blocks.iter() { + if !state.state_machine.was_unpinned() { + self.global_unregister_block(*hash); + } + } + } + + /// Ensure that a new block could be pinned. + /// + /// If the global number of blocks has been reached this method + /// will remove all subscriptions that have blocks older than the + /// specified pin duration. + /// + /// If after removing all subscriptions that exceed the pin duration + /// there is no space for pinning a new block, then all subscriptions + /// are terminated. + /// + /// Returns true if the given subscription is also terminated. + fn ensure_block_space(&mut self, request_sub_id: &str) -> bool { + if self.global_blocks.len() < self.global_max_pinned_blocks { + return false + } + + // Terminate all subscriptions that have blocks older than + // the specified pin duration. + let now = Instant::now(); + + let to_remove: Vec<_> = self + .subs + .iter_mut() + .filter_map(|(sub_id, sub)| { + let sub_time = sub.find_oldest_block_timestamp(); + // Subscriptions older than the specified pin duration should be removed. + let should_remove = match now.checked_duration_since(sub_time) { + Some(duration) => duration > self.local_max_pin_duration, + None => true, + }; + should_remove.then(|| sub_id.clone()) + }) + .collect(); + + let mut is_terminated = false; + for sub_id in to_remove { + if sub_id == request_sub_id { + is_terminated = true; + } + self.remove_subscription(&sub_id); + } + + // Make sure we have enough space after first pass of terminating subscriptions. + if self.global_blocks.len() < self.global_max_pinned_blocks { + return is_terminated + } + + // Sanity check: cannot uphold `chainHead` guarantees anymore. We have not + // found any subscriptions that have older pinned blocks to terminate. + let to_remove: Vec<_> = self.subs.keys().map(|sub_id| sub_id.clone()).collect(); + for sub_id in to_remove { + if sub_id == request_sub_id { + is_terminated = true; + } + self.remove_subscription(&sub_id); + } + return is_terminated + } + + pub fn pin_block( + &mut self, + sub_id: &str, + hash: Block::Hash, + ) -> Result { + let Some(sub) = self.subs.get_mut(sub_id) else { + return Err(SubscriptionManagementError::SubscriptionAbsent) + }; + + // Block was already registered for this subscription and therefore + // globally tracked. + if !sub.register_block(hash) { + return Ok(false) + } + + // Ensure we have enough space only if the hash is not globally registered. + if !self.global_blocks.contains_key(&hash) { + // Subscription ID was terminated while ensuring enough space. + if self.ensure_block_space(sub_id) { + return Err(SubscriptionManagementError::ExceededLimits) + } + } + + self.global_register_block(hash)?; + Ok(true) + } + + /// Register the block internally. + /// + /// If the block is present the reference counter is increased. + /// If this is a new block, the block is pinned in the backend. + fn global_register_block( + &mut self, + hash: Block::Hash, + ) -> Result<(), SubscriptionManagementError> { + match self.global_blocks.entry(hash) { + Entry::Occupied(mut occupied) => { + *occupied.get_mut() += 1; + }, + Entry::Vacant(vacant) => { + self.backend + .pin_block(hash) + .map_err(|err| SubscriptionManagementError::Custom(err.to_string()))?; + + vacant.insert(1); + }, + }; + Ok(()) + } + + /// Unregister the block internally. + /// + /// If the block is present the reference counter is decreased. + /// If this is the last reference of the block, the block + /// is unpinned from the backend and removed from internal tracking. + fn global_unregister_block(&mut self, hash: Block::Hash) { + if let Entry::Occupied(mut occupied) = self.global_blocks.entry(hash) { + let counter = occupied.get_mut(); + if *counter == 1 { + // Unpin the block from the backend. + self.backend.unpin_block(hash); + occupied.remove(); + } else { + *counter -= 1; + } + } + } + + pub fn unpin_block( + &mut self, + sub_id: &str, + hash: Block::Hash, + ) -> Result<(), SubscriptionManagementError> { + let Some(sub) = self.subs.get_mut(sub_id) else { + return Err(SubscriptionManagementError::SubscriptionAbsent) + }; + + // Check that unpin was not called before and the block was pinned + // for this subscription. + if !sub.unregister_block(hash) { + return Err(SubscriptionManagementError::BlockHashAbsent) + } + + self.global_unregister_block(hash); + Ok(()) + } + + pub fn lock_block( + &mut self, + sub_id: &str, + hash: Block::Hash, + ) -> Result, SubscriptionManagementError> { + let Some(sub) = self.subs.get(sub_id) else { + return Err(SubscriptionManagementError::SubscriptionAbsent) + }; + + if !sub.contains_block(hash) { + return Err(SubscriptionManagementError::BlockHashAbsent) + } + + BlockGuard::new(hash, sub.with_runtime, self.backend.clone()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use sc_block_builder::BlockBuilderProvider; + use sc_service::client::new_in_mem; + use sp_consensus::BlockOrigin; + use sp_core::{testing::TaskExecutor, H256}; + use substrate_test_runtime_client::{ + prelude::*, + runtime::{Block, RuntimeApi}, + Client, ClientBlockImportExt, GenesisInit, + }; + + fn init_backend() -> ( + Arc>, + Arc>>, + ) { + let backend = Arc::new(sc_client_api::in_mem::Backend::new()); + let executor = substrate_test_runtime_client::new_native_or_wasm_executor(); + let client_config = sc_service::ClientConfig::default(); + let genesis_block_builder = sc_service::GenesisBlockBuilder::new( + &substrate_test_runtime_client::GenesisParameters::default().genesis_storage(), + !client_config.no_genesis, + backend.clone(), + executor.clone(), + ) + .unwrap(); + let client = Arc::new( + new_in_mem::<_, Block, _, RuntimeApi>( + backend.clone(), + executor, + genesis_block_builder, + None, + None, + Box::new(TaskExecutor::new()), + client_config, + ) + .unwrap(), + ); + (backend, client) + } + + #[test] + fn block_state_machine_register_unpin() { + let mut state = BlockStateMachine::new(); + // Starts in `Registered` state. + assert_eq!(state, BlockStateMachine::Registered); + + state.advance_register(); + assert_eq!(state, BlockStateMachine::FullyRegistered); + + // Can call register multiple times. + state.advance_register(); + assert_eq!(state, BlockStateMachine::FullyRegistered); + + assert!(!state.was_unpinned()); + state.advance_unpin(); + assert_eq!(state, BlockStateMachine::FullyUnpinned); + assert!(state.was_unpinned()); + + // Can call unpin multiple times. + state.advance_unpin(); + assert_eq!(state, BlockStateMachine::FullyUnpinned); + assert!(state.was_unpinned()); + + // Nothing to advance. + state.advance_register(); + assert_eq!(state, BlockStateMachine::FullyUnpinned); + } + + #[test] + fn block_state_machine_unpin_register() { + let mut state = BlockStateMachine::new(); + // Starts in `Registered` state. + assert_eq!(state, BlockStateMachine::Registered); + + assert!(!state.was_unpinned()); + state.advance_unpin(); + assert_eq!(state, BlockStateMachine::Unpinned); + assert!(state.was_unpinned()); + + // Can call unpin multiple times. + state.advance_unpin(); + assert_eq!(state, BlockStateMachine::Unpinned); + assert!(state.was_unpinned()); + + state.advance_register(); + assert_eq!(state, BlockStateMachine::FullyUnpinned); + assert!(state.was_unpinned()); + + // Nothing to advance. + state.advance_register(); + assert_eq!(state, BlockStateMachine::FullyUnpinned); + // Nothing to unpin. + state.advance_unpin(); + assert_eq!(state, BlockStateMachine::FullyUnpinned); + assert!(state.was_unpinned()); + } + + #[test] + fn sub_state_register_twice() { + let mut sub_state = SubscriptionState:: { + with_runtime: false, + tx_stop: None, + blocks: Default::default(), + }; + + let hash = H256::random(); + assert_eq!(sub_state.register_block(hash), true); + let block_state = sub_state.blocks.get(&hash).unwrap(); + // Did not call `register_block` twice. + assert_eq!(block_state.state_machine, BlockStateMachine::Registered); + + assert_eq!(sub_state.register_block(hash), false); + let block_state = sub_state.blocks.get(&hash).unwrap(); + assert_eq!(block_state.state_machine, BlockStateMachine::FullyRegistered); + + // Block is no longer tracked when: `register_block` is called twice and + // `unregister_block` is called once. + assert_eq!(sub_state.unregister_block(hash), true); + let block_state = sub_state.blocks.get(&hash); + assert!(block_state.is_none()); + } + + #[test] + fn sub_state_register_unregister() { + let mut sub_state = SubscriptionState:: { + with_runtime: false, + tx_stop: None, + blocks: Default::default(), + }; + + let hash = H256::random(); + // Block was not registered before. + assert_eq!(sub_state.unregister_block(hash), false); + + assert_eq!(sub_state.register_block(hash), true); + let block_state = sub_state.blocks.get(&hash).unwrap(); + // Did not call `register_block` twice. + assert_eq!(block_state.state_machine, BlockStateMachine::Registered); + + // Unregister block before the second `register_block`. + assert_eq!(sub_state.unregister_block(hash), true); + let block_state = sub_state.blocks.get(&hash).unwrap(); + assert_eq!(block_state.state_machine, BlockStateMachine::Unpinned); + + assert_eq!(sub_state.register_block(hash), false); + let block_state = sub_state.blocks.get(&hash); + assert!(block_state.is_none()); + + // Block is no longer tracked when: `register_block` is called twice and + // `unregister_block` is called once. + assert_eq!(sub_state.unregister_block(hash), false); + let block_state = sub_state.blocks.get(&hash); + assert!(block_state.is_none()); + } + + #[test] + fn subscription_lock_block() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let mut subs = SubscriptionsInner::new(10, Duration::from_secs(10), backend); + + let id = "abc".to_string(); + let hash = H256::random(); + + // Subscription not inserted. + let err = subs.lock_block(&id, hash).unwrap_err(); + assert_eq!(err, SubscriptionManagementError::SubscriptionAbsent); + + let _stop = subs.insert_subscription(id.clone(), true).unwrap(); + // Cannot insert the same subscription ID twice. + assert!(subs.insert_subscription(id.clone(), true).is_none()); + + // No block hash. + let err = subs.lock_block(&id, hash).unwrap_err(); + assert_eq!(err, SubscriptionManagementError::BlockHashAbsent); + + subs.remove_subscription(&id); + + // No subscription. + let err = subs.lock_block(&id, hash).unwrap_err(); + assert_eq!(err, SubscriptionManagementError::SubscriptionAbsent); + } + + #[test] + fn subscription_check_block() { + let (backend, mut client) = init_backend(); + + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let hash = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + + let mut subs = SubscriptionsInner::new(10, Duration::from_secs(10), backend); + let id = "abc".to_string(); + + let _stop = subs.insert_subscription(id.clone(), true).unwrap(); + + // First time we are pinning the block. + assert_eq!(subs.pin_block(&id, hash).unwrap(), true); + + let block = subs.lock_block(&id, hash).unwrap(); + // Subscription started with runtime updates + assert_eq!(block.has_runtime(), true); + + let invalid_id = "abc-invalid".to_string(); + let err = subs.unpin_block(&invalid_id, hash).unwrap_err(); + assert_eq!(err, SubscriptionManagementError::SubscriptionAbsent); + + // Unpin the block. + subs.unpin_block(&id, hash).unwrap(); + let err = subs.lock_block(&id, hash).unwrap_err(); + assert_eq!(err, SubscriptionManagementError::BlockHashAbsent); + } + + #[test] + fn subscription_ref_count() { + let (backend, mut client) = init_backend(); + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let hash = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + + let mut subs = SubscriptionsInner::new(10, Duration::from_secs(10), backend); + let id = "abc".to_string(); + + let _stop = subs.insert_subscription(id.clone(), true).unwrap(); + assert_eq!(subs.pin_block(&id, hash).unwrap(), true); + // Check the global ref count. + assert_eq!(*subs.global_blocks.get(&hash).unwrap(), 1); + // Ensure the block propagated to the subscription. + subs.subs.get(&id).unwrap().blocks.get(&hash).unwrap(); + + // Insert the block for the same subscription again (simulate NewBlock + Finalized pinning) + assert_eq!(subs.pin_block(&id, hash).unwrap(), false); + // Check the global ref count should not get incremented. + assert_eq!(*subs.global_blocks.get(&hash).unwrap(), 1); + + // Ensure the hash propagates for the second subscription. + let id_second = "abcd".to_string(); + let _stop = subs.insert_subscription(id_second.clone(), true).unwrap(); + assert_eq!(subs.pin_block(&id_second, hash).unwrap(), true); + // Check the global ref count. + assert_eq!(*subs.global_blocks.get(&hash).unwrap(), 2); + // Ensure the block propagated to the subscription. + subs.subs.get(&id_second).unwrap().blocks.get(&hash).unwrap(); + + subs.unpin_block(&id, hash).unwrap(); + assert_eq!(*subs.global_blocks.get(&hash).unwrap(), 1); + // Cannot unpin a block twice for the same subscription. + let err = subs.unpin_block(&id, hash).unwrap_err(); + assert_eq!(err, SubscriptionManagementError::BlockHashAbsent); + + subs.unpin_block(&id_second, hash).unwrap(); + // Block unregistered from the memory. + assert!(subs.global_blocks.get(&hash).is_none()); + } + + #[test] + fn subscription_remove_subscription() { + let (backend, mut client) = init_backend(); + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let hash_1 = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let hash_2 = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let hash_3 = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + + let mut subs = SubscriptionsInner::new(10, Duration::from_secs(10), backend); + let id_1 = "abc".to_string(); + let id_2 = "abcd".to_string(); + + // Pin all blocks for the first subscription. + let _stop = subs.insert_subscription(id_1.clone(), true).unwrap(); + assert_eq!(subs.pin_block(&id_1, hash_1).unwrap(), true); + assert_eq!(subs.pin_block(&id_1, hash_2).unwrap(), true); + assert_eq!(subs.pin_block(&id_1, hash_3).unwrap(), true); + + // Pin only block 2 for the second subscription. + let _stop = subs.insert_subscription(id_2.clone(), true).unwrap(); + assert_eq!(subs.pin_block(&id_2, hash_2).unwrap(), true); + + // Check reference count. + assert_eq!(*subs.global_blocks.get(&hash_1).unwrap(), 1); + assert_eq!(*subs.global_blocks.get(&hash_2).unwrap(), 2); + assert_eq!(*subs.global_blocks.get(&hash_3).unwrap(), 1); + + subs.remove_subscription(&id_1); + + assert!(subs.global_blocks.get(&hash_1).is_none()); + assert_eq!(*subs.global_blocks.get(&hash_2).unwrap(), 1); + assert!(subs.global_blocks.get(&hash_3).is_none()); + + subs.remove_subscription(&id_2); + + assert!(subs.global_blocks.get(&hash_2).is_none()); + assert_eq!(subs.global_blocks.len(), 0); + } + + #[test] + fn subscription_check_limits() { + let (backend, mut client) = init_backend(); + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let hash_1 = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let hash_2 = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let hash_3 = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + + // Maximum number of pinned blocks is 2. + let mut subs = SubscriptionsInner::new(2, Duration::from_secs(10), backend); + let id_1 = "abc".to_string(); + let id_2 = "abcd".to_string(); + + // Both subscriptions can pin the maximum limit. + let _stop = subs.insert_subscription(id_1.clone(), true).unwrap(); + assert_eq!(subs.pin_block(&id_1, hash_1).unwrap(), true); + assert_eq!(subs.pin_block(&id_1, hash_2).unwrap(), true); + + let _stop = subs.insert_subscription(id_2.clone(), true).unwrap(); + assert_eq!(subs.pin_block(&id_2, hash_1).unwrap(), true); + assert_eq!(subs.pin_block(&id_2, hash_2).unwrap(), true); + + // Check reference count. + assert_eq!(*subs.global_blocks.get(&hash_1).unwrap(), 2); + assert_eq!(*subs.global_blocks.get(&hash_2).unwrap(), 2); + + // Block 3 pinning will exceed the limit and both subscriptions + // are terminated because no subscription with older blocks than 10 + // seconds are present. + let err = subs.pin_block(&id_1, hash_3).unwrap_err(); + assert_eq!(err, SubscriptionManagementError::ExceededLimits); + + // Ensure both subscriptions are removed. + let err = subs.lock_block(&id_1, hash_1).unwrap_err(); + assert_eq!(err, SubscriptionManagementError::SubscriptionAbsent); + + let err = subs.lock_block(&id_2, hash_1).unwrap_err(); + assert_eq!(err, SubscriptionManagementError::SubscriptionAbsent); + + assert!(subs.global_blocks.get(&hash_1).is_none()); + assert!(subs.global_blocks.get(&hash_2).is_none()); + assert!(subs.global_blocks.get(&hash_3).is_none()); + assert_eq!(subs.global_blocks.len(), 0); + } + + #[test] + fn subscription_check_limits_with_duration() { + let (backend, mut client) = init_backend(); + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let hash_1 = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let hash_2 = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let hash_3 = block.header.hash(); + futures::executor::block_on(client.import(BlockOrigin::Own, block.clone())).unwrap(); + + // Maximum number of pinned blocks is 2 and maximum pin duration is 5 second. + let mut subs = SubscriptionsInner::new(2, Duration::from_secs(5), backend); + let id_1 = "abc".to_string(); + let id_2 = "abcd".to_string(); + + let _stop = subs.insert_subscription(id_1.clone(), true).unwrap(); + assert_eq!(subs.pin_block(&id_1, hash_1).unwrap(), true); + assert_eq!(subs.pin_block(&id_1, hash_2).unwrap(), true); + + // Maximum pin duration is 5 second, sleep 5 seconds to ensure we clean up + // the first subscription. + std::thread::sleep(std::time::Duration::from_secs(5)); + + let _stop = subs.insert_subscription(id_2.clone(), true).unwrap(); + assert_eq!(subs.pin_block(&id_2, hash_1).unwrap(), true); + + // Check reference count. + assert_eq!(*subs.global_blocks.get(&hash_1).unwrap(), 2); + assert_eq!(*subs.global_blocks.get(&hash_2).unwrap(), 1); + + // Second subscription has only 1 block pinned. Only the first subscription is terminated. + let err = subs.pin_block(&id_1, hash_3).unwrap_err(); + assert_eq!(err, SubscriptionManagementError::ExceededLimits); + + // Ensure both subscriptions are removed. + let err = subs.lock_block(&id_1, hash_1).unwrap_err(); + assert_eq!(err, SubscriptionManagementError::SubscriptionAbsent); + + let _block_guard = subs.lock_block(&id_2, hash_1).unwrap(); + + assert_eq!(*subs.global_blocks.get(&hash_1).unwrap(), 1); + assert!(subs.global_blocks.get(&hash_2).is_none()); + assert!(subs.global_blocks.get(&hash_3).is_none()); + assert_eq!(subs.global_blocks.len(), 1); + + // Force second subscription to get terminated. + assert_eq!(subs.pin_block(&id_2, hash_2).unwrap(), true); + let err = subs.pin_block(&id_2, hash_3).unwrap_err(); + assert_eq!(err, SubscriptionManagementError::ExceededLimits); + + assert!(subs.global_blocks.get(&hash_1).is_none()); + assert!(subs.global_blocks.get(&hash_2).is_none()); + assert!(subs.global_blocks.get(&hash_3).is_none()); + assert_eq!(subs.global_blocks.len(), 0); + } + + #[test] + fn subscription_check_stop_event() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let mut subs = SubscriptionsInner::new(10, Duration::from_secs(10), backend); + + let id = "abc".to_string(); + + let mut rx_stop = subs.insert_subscription(id.clone(), true).unwrap(); + + // Check the stop signal was not received. + let res = rx_stop.try_recv().unwrap(); + assert!(res.is_none()); + + let sub = subs.subs.get_mut(&id).unwrap(); + sub.stop(); + + // Check the signal was received. + let res = rx_stop.try_recv().unwrap(); + assert!(res.is_some()); + } +} diff --git a/client/rpc-spec-v2/src/chain_head/subscription/mod.rs b/client/rpc-spec-v2/src/chain_head/subscription/mod.rs new file mode 100644 index 000000000000..86e55acc4c17 --- /dev/null +++ b/client/rpc-spec-v2/src/chain_head/subscription/mod.rs @@ -0,0 +1,125 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 futures::channel::oneshot; +use parking_lot::RwLock; +use sc_client_api::Backend; +use sp_runtime::traits::Block as BlockT; +use std::{sync::Arc, time::Duration}; + +mod error; +mod inner; + +pub use error::SubscriptionManagementError; +pub use inner::BlockGuard; +use inner::SubscriptionsInner; + +/// Manage block pinning / unpinning for subscription IDs. +pub struct SubscriptionManagement> { + /// Manage subscription by mapping the subscription ID + /// to a set of block hashes. + inner: RwLock>, +} + +impl> SubscriptionManagement { + /// Construct a new [`SubscriptionManagement`]. + pub fn new( + global_max_pinned_blocks: usize, + local_max_pin_duration: Duration, + backend: Arc, + ) -> Self { + SubscriptionManagement { + inner: RwLock::new(SubscriptionsInner::new( + global_max_pinned_blocks, + local_max_pin_duration, + backend, + )), + } + } + + /// Insert a new subscription ID. + /// + /// If the subscription was not previously inserted, returns the receiver that is + /// triggered upon the "Stop" event. Otherwise, if the subscription ID was already + /// inserted returns none. + pub fn insert_subscription( + &self, + sub_id: String, + runtime_updates: bool, + ) -> Option> { + let mut inner = self.inner.write(); + inner.insert_subscription(sub_id, runtime_updates) + } + + /// Remove the subscription ID with associated pinned blocks. + pub fn remove_subscription(&self, sub_id: &str) { + let mut inner = self.inner.write(); + inner.remove_subscription(sub_id) + } + + /// The block is pinned in the backend only once when the block's hash is first encountered. + /// + /// Each subscription is expected to call this method twice: + /// - once from the `NewBlock` import + /// - once from the `Finalized` import + /// + /// Returns + /// - Ok(true) if the subscription did not previously contain this block + /// - Ok(false) if the subscription already contained this this + /// - Error if the backend failed to pin the block or the subscription ID is invalid + pub fn pin_block( + &self, + sub_id: &str, + hash: Block::Hash, + ) -> Result { + let mut inner = self.inner.write(); + inner.pin_block(sub_id, hash) + } + + /// Unpin the block from the subscription. + /// + /// The last subscription that unpins the block is also unpinning the block + /// from the backend. + /// + /// This method is called only once per subscription. + /// + /// Returns an error if the block is not pinned for the subscription or + /// the subscription ID is invalid. + pub fn unpin_block( + &self, + sub_id: &str, + hash: Block::Hash, + ) -> Result<(), SubscriptionManagementError> { + let mut inner = self.inner.write(); + inner.unpin_block(sub_id, hash) + } + + /// Ensure the block remains pinned until the return object is dropped. + /// + /// Returns a [`BlockGuard`] that pins and unpins the block hash in RAII manner. + /// Returns an error if the block hash is not pinned for the subscription or + /// the subscription ID is invalid. + pub fn lock_block( + &self, + sub_id: &str, + hash: Block::Hash, + ) -> Result, SubscriptionManagementError> { + let mut inner = self.inner.write(); + inner.lock_block(sub_id, hash) + } +} diff --git a/client/rpc-spec-v2/src/chain_head/test_utils.rs b/client/rpc-spec-v2/src/chain_head/test_utils.rs new file mode 100644 index 000000000000..54c585932a74 --- /dev/null +++ b/client/rpc-spec-v2/src/chain_head/test_utils.rs @@ -0,0 +1,328 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 parking_lot::Mutex; +use sc_client_api::{ + execution_extensions::ExecutionExtensions, BlockBackend, BlockImportNotification, + BlockchainEvents, CallExecutor, ChildInfo, ExecutorProvider, FinalityNotification, + FinalityNotifications, FinalizeSummary, ImportNotifications, KeysIter, PairsIter, StorageData, + StorageEventStream, StorageKey, StorageProvider, +}; +use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender}; +use sp_api::{CallApiAt, CallApiAtParams, NumberFor, RuntimeVersion}; +use sp_blockchain::{BlockStatus, CachedHeaderMetadata, HeaderBackend, HeaderMetadata, Info}; +use sp_consensus::BlockOrigin; +use sp_runtime::{ + generic::SignedBlock, + traits::{Block as BlockT, Header as HeaderT}, + Justifications, +}; +use std::sync::Arc; +use substrate_test_runtime::{Block, Hash, Header}; + +pub struct ChainHeadMockClient { + client: Arc, + import_sinks: Mutex>>>, + finality_sinks: Mutex>>>, +} + +impl ChainHeadMockClient { + pub fn new(client: Arc) -> Self { + ChainHeadMockClient { + client, + import_sinks: Default::default(), + finality_sinks: Default::default(), + } + } + + pub async fn trigger_import_stream(&self, header: Header) { + // Ensure the client called the `import_notification_stream`. + while self.import_sinks.lock().is_empty() { + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + } + + // Build the notification. + let (sink, _stream) = tracing_unbounded("test_sink", 100_000); + let notification = + BlockImportNotification::new(header.hash(), BlockOrigin::Own, header, true, None, sink); + + for sink in self.import_sinks.lock().iter_mut() { + sink.unbounded_send(notification.clone()).unwrap(); + } + } + + pub async fn trigger_finality_stream(&self, header: Header) { + // Ensure the client called the `finality_notification_stream`. + while self.finality_sinks.lock().is_empty() { + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + } + + // Build the notification. + let (sink, _stream) = tracing_unbounded("test_sink", 100_000); + let summary = FinalizeSummary { + header: header.clone(), + finalized: vec![header.hash()], + stale_heads: vec![], + }; + let notification = FinalityNotification::from_summary(summary, sink); + + for sink in self.finality_sinks.lock().iter_mut() { + sink.unbounded_send(notification.clone()).unwrap(); + } + } +} + +// ChainHead calls `import_notification_stream` and `finality_notification_stream` in order to +// subscribe to block events. +impl BlockchainEvents for ChainHeadMockClient { + fn import_notification_stream(&self) -> ImportNotifications { + let (sink, stream) = tracing_unbounded("import_notification_stream", 1024); + self.import_sinks.lock().push(sink); + stream + } + + fn every_import_notification_stream(&self) -> ImportNotifications { + unimplemented!() + } + + fn finality_notification_stream(&self) -> FinalityNotifications { + let (sink, stream) = tracing_unbounded("finality_notification_stream", 1024); + self.finality_sinks.lock().push(sink); + stream + } + + fn storage_changes_notification_stream( + &self, + _filter_keys: Option<&[StorageKey]>, + _child_filter_keys: Option<&[(StorageKey, Option>)]>, + ) -> sp_blockchain::Result> { + unimplemented!() + } +} + +// The following implementations are imposed by the `chainHead` trait bounds. + +impl, Client: ExecutorProvider> + ExecutorProvider for ChainHeadMockClient +{ + type Executor = >::Executor; + + fn executor(&self) -> &Self::Executor { + self.client.executor() + } + + fn execution_extensions(&self) -> &ExecutionExtensions { + self.client.execution_extensions() + } +} + +impl< + BE: sc_client_api::backend::Backend + Send + Sync + 'static, + Block: BlockT, + Client: StorageProvider, + > StorageProvider for ChainHeadMockClient +{ + fn storage( + &self, + hash: Block::Hash, + key: &StorageKey, + ) -> sp_blockchain::Result> { + self.client.storage(hash, key) + } + + fn storage_hash( + &self, + hash: Block::Hash, + key: &StorageKey, + ) -> sp_blockchain::Result> { + self.client.storage_hash(hash, key) + } + + fn storage_keys( + &self, + hash: Block::Hash, + prefix: Option<&StorageKey>, + start_key: Option<&StorageKey>, + ) -> sp_blockchain::Result> { + self.client.storage_keys(hash, prefix, start_key) + } + + fn storage_pairs( + &self, + hash: ::Hash, + prefix: Option<&StorageKey>, + start_key: Option<&StorageKey>, + ) -> sp_blockchain::Result> { + self.client.storage_pairs(hash, prefix, start_key) + } + + fn child_storage( + &self, + hash: Block::Hash, + child_info: &ChildInfo, + key: &StorageKey, + ) -> sp_blockchain::Result> { + self.client.child_storage(hash, child_info, key) + } + + fn child_storage_keys( + &self, + hash: Block::Hash, + child_info: ChildInfo, + prefix: Option<&StorageKey>, + start_key: Option<&StorageKey>, + ) -> sp_blockchain::Result> { + self.client.child_storage_keys(hash, child_info, prefix, start_key) + } + + fn child_storage_hash( + &self, + hash: Block::Hash, + child_info: &ChildInfo, + key: &StorageKey, + ) -> sp_blockchain::Result> { + self.client.child_storage_hash(hash, child_info, key) + } +} + +impl> CallApiAt for ChainHeadMockClient { + type StateBackend = >::StateBackend; + + fn call_api_at( + &self, + params: CallApiAtParams>::StateBackend>, + ) -> Result, sp_api::ApiError> { + self.client.call_api_at(params) + } + + fn runtime_version_at(&self, hash: Block::Hash) -> Result { + self.client.runtime_version_at(hash) + } + + fn state_at(&self, at: Block::Hash) -> Result { + self.client.state_at(at) + } + + fn initialize_extensions( + &self, + at: ::Hash, + extensions: &mut sp_api::Extensions, + ) -> Result<(), sp_api::ApiError> { + self.client.initialize_extensions(at, extensions) + } +} + +impl> BlockBackend + for ChainHeadMockClient +{ + fn block_body( + &self, + hash: Block::Hash, + ) -> sp_blockchain::Result::Extrinsic>>> { + self.client.block_body(hash) + } + + fn block(&self, hash: Block::Hash) -> sp_blockchain::Result>> { + self.client.block(hash) + } + + fn block_status(&self, hash: Block::Hash) -> sp_blockchain::Result { + self.client.block_status(hash) + } + + fn justifications(&self, hash: Block::Hash) -> sp_blockchain::Result> { + self.client.justifications(hash) + } + + fn block_hash(&self, number: NumberFor) -> sp_blockchain::Result> { + self.client.block_hash(number) + } + + fn indexed_transaction(&self, hash: Block::Hash) -> sp_blockchain::Result>> { + self.client.indexed_transaction(hash) + } + + fn has_indexed_transaction(&self, hash: Block::Hash) -> sp_blockchain::Result { + self.client.has_indexed_transaction(hash) + } + + fn block_indexed_body(&self, hash: Block::Hash) -> sp_blockchain::Result>>> { + self.client.block_indexed_body(hash) + } + fn requires_full_sync(&self) -> bool { + self.client.requires_full_sync() + } +} + +impl + Send + Sync> HeaderMetadata + for ChainHeadMockClient +{ + type Error = >::Error; + + fn header_metadata( + &self, + hash: Block::Hash, + ) -> Result, Self::Error> { + self.client.header_metadata(hash) + } + + fn insert_header_metadata( + &self, + hash: Block::Hash, + header_metadata: CachedHeaderMetadata, + ) { + self.client.insert_header_metadata(hash, header_metadata) + } + + fn remove_header_metadata(&self, hash: Block::Hash) { + self.client.remove_header_metadata(hash) + } +} + +impl + Send + Sync> HeaderBackend + for ChainHeadMockClient +{ + fn header( + &self, + hash: Block::Hash, + ) -> sp_blockchain::Result::Header>> { + self.client.header(hash) + } + + fn info(&self) -> Info { + self.client.info() + } + + fn status(&self, hash: Block::Hash) -> sc_client_api::blockchain::Result { + self.client.status(hash) + } + + fn number( + &self, + hash: Block::Hash, + ) -> sc_client_api::blockchain::Result::Header as HeaderT>::Number>> { + self.client.number(hash) + } + + fn hash( + &self, + number: <::Header as HeaderT>::Number, + ) -> sp_blockchain::Result> { + self.client.hash(number) + } +} diff --git a/client/rpc-spec-v2/src/chain_head/tests.rs b/client/rpc-spec-v2/src/chain_head/tests.rs index 4084075f0b32..5dd3cc3da6d3 100644 --- a/client/rpc-spec-v2/src/chain_head/tests.rs +++ b/client/rpc-spec-v2/src/chain_head/tests.rs @@ -1,6 +1,9 @@ +use crate::chain_head::test_utils::ChainHeadMockClient; + use super::*; use assert_matches::assert_matches; use codec::{Decode, Encode}; +use futures::Future; use jsonrpsee::{ core::{error::Error, server::rpc_module::Subscription as RpcSubscription}, types::{error::CallError, EmptyServerParams as EmptyParams}, @@ -8,7 +11,8 @@ use jsonrpsee::{ }; use sc_block_builder::BlockBuilderProvider; use sc_client_api::ChildInfo; -use sp_api::BlockId; +use sc_service::client::new_in_mem; +use sp_api::BlockT; use sp_blockchain::HeaderBackend; use sp_consensus::BlockOrigin; use sp_core::{ @@ -17,15 +21,17 @@ use sp_core::{ testing::TaskExecutor, }; use sp_version::RuntimeVersion; -use std::sync::Arc; +use std::{sync::Arc, time::Duration}; use substrate_test_runtime::Transfer; use substrate_test_runtime_client::{ - prelude::*, runtime, Backend, BlockBuilderExt, Client, ClientBlockImportExt, + prelude::*, runtime, runtime::RuntimeApi, Backend, BlockBuilderExt, Client, + ClientBlockImportExt, GenesisInit, }; type Header = substrate_test_runtime_client::runtime::Header; type Block = substrate_test_runtime_client::runtime::Block; const MAX_PINNED_BLOCKS: usize = 32; +const MAX_PINNED_SECS: u64 = 60; const CHAIN_GENESIS: [u8; 32] = [0; 32]; const INVALID_HASH: [u8; 32] = [1; 32]; const KEY: &[u8] = b":mock"; @@ -34,7 +40,7 @@ const CHILD_STORAGE_KEY: &[u8] = b"child"; const CHILD_VALUE: &[u8] = b"child value"; async fn get_next_event(sub: &mut RpcSubscription) -> T { - let (event, _sub_id) = tokio::time::timeout(std::time::Duration::from_secs(1), sub.next()) + let (event, _sub_id) = tokio::time::timeout(std::time::Duration::from_secs(60), sub.next()) .await .unwrap() .unwrap() @@ -42,6 +48,12 @@ async fn get_next_event(sub: &mut RpcSubscriptio event } +async fn run_with_timeout(future: F) -> ::Output { + tokio::time::timeout(std::time::Duration::from_secs(60 * 10), future) + .await + .unwrap() +} + async fn setup_api() -> ( Arc>, RpcModule>>, @@ -64,6 +76,7 @@ async fn setup_api() -> ( Arc::new(TaskExecutor::default()), CHAIN_GENESIS, MAX_PINNED_BLOCKS, + Duration::from_secs(MAX_PINNED_SECS), ) .into_rpc(); @@ -103,6 +116,7 @@ async fn follow_subscription_produces_blocks() { Arc::new(TaskExecutor::default()), CHAIN_GENESIS, MAX_PINNED_BLOCKS, + Duration::from_secs(MAX_PINNED_SECS), ) .into_rpc(); @@ -114,7 +128,7 @@ async fn follow_subscription_produces_blocks() { let expected = FollowEvent::Initialized(Initialized { finalized_block_hash: format!("{:?}", finalized_hash), finalized_block_runtime: None, - runtime_updates: false, + with_runtime: false, }); assert_eq!(event, expected); @@ -128,7 +142,7 @@ async fn follow_subscription_produces_blocks() { block_hash: format!("{:?}", best_hash), parent_block_hash: format!("{:?}", finalized_hash), new_runtime: None, - runtime_updates: false, + with_runtime: false, }); assert_eq!(event, expected); @@ -160,6 +174,7 @@ async fn follow_with_runtime() { Arc::new(TaskExecutor::default()), CHAIN_GENESIS, MAX_PINNED_BLOCKS, + Duration::from_secs(MAX_PINNED_SECS), ) .into_rpc(); @@ -169,12 +184,14 @@ async fn follow_with_runtime() { // Initialized must always be reported first. let event: FollowEvent = get_next_event(&mut sub).await; + // it is basically json-encoded substrate_test_runtime_client::runtime::VERSION let runtime_str = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\ \"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",4],\ - [\"0x37e397fc7c91f5e4\",1],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",6],\ - [\"0xc6e9a76309f39b09\",1],[\"0xdd718d5cc53262d4\",1],[\"0xcbca25e39f142387\",2],\ - [\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],[\"0xbc9d89904f5b923f\",1]],\ - \"transactionVersion\":1,\"stateVersion\":1}"; + [\"0x37e397fc7c91f5e4\",2],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",6],\ + [\"0xbc9d89904f5b923f\",1],[\"0xc6e9a76309f39b09\",2],[\"0xdd718d5cc53262d4\",1],\ + [\"0xcbca25e39f142387\",2],[\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],\ + [\"0xed99c5acb25eedf5\",3]],\"transactionVersion\":1,\"stateVersion\":1}"; + let runtime: RuntimeVersion = serde_json::from_str(runtime_str).unwrap(); let finalized_block_runtime = @@ -183,9 +200,9 @@ async fn follow_with_runtime() { let expected = FollowEvent::Initialized(Initialized { finalized_block_hash: format!("{:?}", finalized_hash), finalized_block_runtime, - runtime_updates: false, + with_runtime: false, }); - assert_eq!(event, expected); + pretty_assertions::assert_eq!(event, expected); // Import a new block without runtime changes. // The runtime field must be None in this case. @@ -198,7 +215,7 @@ async fn follow_with_runtime() { block_hash: format!("{:?}", best_hash), parent_block_hash: format!("{:?}", finalized_hash), new_runtime: None, - runtime_updates: false, + with_runtime: false, }); assert_eq!(event, expected); @@ -248,7 +265,7 @@ async fn follow_with_runtime() { block_hash: format!("{:?}", best_hash), parent_block_hash: format!("{:?}", finalized_hash), new_runtime, - runtime_updates: false, + with_runtime: false, }); assert_eq!(event, expected); } @@ -265,6 +282,7 @@ async fn get_genesis() { Arc::new(TaskExecutor::default()), CHAIN_GENESIS, MAX_PINNED_BLOCKS, + Duration::from_secs(MAX_PINNED_SECS), ) .into_rpc(); @@ -449,6 +467,7 @@ async fn call_runtime_without_flag() { Arc::new(TaskExecutor::default()), CHAIN_GENESIS, MAX_PINNED_BLOCKS, + Duration::from_secs(MAX_PINNED_SECS), ) .into_rpc(); @@ -474,7 +493,7 @@ async fn call_runtime_without_flag() { FollowEvent::BestBlockChanged(_) ); - // Valid runtime call on a subscription started with `runtime_updates` false. + // Valid runtime call on a subscription started with `with_runtime` false. let alice_id = AccountKeyring::Alice.to_account_id(); let call_parameters = format!("0x{:?}", HexDisplay::from(&alice_id.encode())); let err = api @@ -623,6 +642,7 @@ async fn follow_generates_initial_blocks() { Arc::new(TaskExecutor::default()), CHAIN_GENESIS, MAX_PINNED_BLOCKS, + Duration::from_secs(MAX_PINNED_SECS), ) .into_rpc(); @@ -639,9 +659,8 @@ async fn follow_generates_initial_blocks() { let block_2_hash = block_2.header.hash(); client.import(BlockOrigin::Own, block_2.clone()).await.unwrap(); - let mut block_builder = client - .new_block_at(&BlockId::Hash(block_1.header.hash()), Default::default(), false) - .unwrap(); + let mut block_builder = + client.new_block_at(block_1.header.hash(), Default::default(), false).unwrap(); // This push is required as otherwise block 3 has the same hash as block 2 and won't get // imported block_builder @@ -663,7 +682,7 @@ async fn follow_generates_initial_blocks() { let expected = FollowEvent::Initialized(Initialized { finalized_block_hash: format!("{:?}", finalized_hash), finalized_block_runtime: None, - runtime_updates: false, + with_runtime: false, }); assert_eq!(event, expected); @@ -673,7 +692,7 @@ async fn follow_generates_initial_blocks() { block_hash: format!("{:?}", block_1_hash), parent_block_hash: format!("{:?}", finalized_hash), new_runtime: None, - runtime_updates: false, + with_runtime: false, }); assert_eq!(event, expected); @@ -683,7 +702,7 @@ async fn follow_generates_initial_blocks() { block_hash: format!("{:?}", block_2_hash), parent_block_hash: format!("{:?}", block_1_hash), new_runtime: None, - runtime_updates: false, + with_runtime: false, }); assert_eq!(event, expected); // Check block 3. @@ -692,7 +711,7 @@ async fn follow_generates_initial_blocks() { block_hash: format!("{:?}", block_3_hash), parent_block_hash: format!("{:?}", block_1_hash), new_runtime: None, - runtime_updates: false, + with_runtime: false, }); assert_eq!(event, expected); @@ -712,7 +731,7 @@ async fn follow_generates_initial_blocks() { block_hash: format!("{:?}", block_4_hash), parent_block_hash: format!("{:?}", block_2_hash), new_runtime: None, - runtime_updates: false, + with_runtime: false, }); assert_eq!(event, expected); @@ -751,6 +770,7 @@ async fn follow_exceeding_pinned_blocks() { Arc::new(TaskExecutor::default()), CHAIN_GENESIS, 2, + Duration::from_secs(MAX_PINNED_SECS), ) .into_rpc(); @@ -801,6 +821,7 @@ async fn follow_with_unpin() { Arc::new(TaskExecutor::default()), CHAIN_GENESIS, 2, + Duration::from_secs(MAX_PINNED_SECS), ) .into_rpc(); @@ -881,6 +902,7 @@ async fn follow_prune_best_block() { Arc::new(TaskExecutor::default()), CHAIN_GENESIS, MAX_PINNED_BLOCKS, + Duration::from_secs(MAX_PINNED_SECS), ) .into_rpc(); @@ -892,7 +914,7 @@ async fn follow_prune_best_block() { let expected = FollowEvent::Initialized(Initialized { finalized_block_hash: format!("{:?}", finalized_hash), finalized_block_runtime: None, - runtime_updates: false, + with_runtime: false, }); assert_eq!(event, expected); @@ -921,9 +943,7 @@ async fn follow_prune_best_block() { client.import(BlockOrigin::Own, block_4.clone()).await.unwrap(); // Import block 2 as best on the fork. - let mut block_builder = client - .new_block_at(&BlockId::Hash(block_1.header.hash()), Default::default(), false) - .unwrap(); + let mut block_builder = client.new_block_at(block_1_hash, Default::default(), false).unwrap(); // This push is required as otherwise block 3 has the same hash as block 2 and won't get // imported block_builder @@ -944,7 +964,7 @@ async fn follow_prune_best_block() { block_hash: format!("{:?}", block_1_hash), parent_block_hash: format!("{:?}", finalized_hash), new_runtime: None, - runtime_updates: false, + with_runtime: false, }); assert_eq!(event, expected); let event: FollowEvent = get_next_event(&mut sub).await; @@ -959,7 +979,7 @@ async fn follow_prune_best_block() { block_hash: format!("{:?}", block_3_hash), parent_block_hash: format!("{:?}", block_1_hash), new_runtime: None, - runtime_updates: false, + with_runtime: false, }); assert_eq!(event, expected); let event: FollowEvent = get_next_event(&mut sub).await; @@ -974,7 +994,7 @@ async fn follow_prune_best_block() { block_hash: format!("{:?}", block_4_hash), parent_block_hash: format!("{:?}", block_3_hash), new_runtime: None, - runtime_updates: false, + with_runtime: false, }); assert_eq!(event, expected); let event: FollowEvent = get_next_event(&mut sub).await; @@ -989,7 +1009,7 @@ async fn follow_prune_best_block() { block_hash: format!("{:?}", block_2_hash), parent_block_hash: format!("{:?}", block_1_hash), new_runtime: None, - runtime_updates: false, + with_runtime: false, }); assert_eq!(event, expected); let event: FollowEvent = get_next_event(&mut sub).await; @@ -1019,4 +1039,530 @@ async fn follow_prune_best_block() { pruned_block_hashes: vec![format!("{:?}", block_2_hash)], }); assert_eq!(event, expected); + + // Pruned hash can be unpinned. + let sub_id = sub.subscription_id(); + let sub_id = serde_json::to_string(&sub_id).unwrap(); + let hash = format!("{:?}", block_2_hash); + let _res: () = api.call("chainHead_unstable_unpin", [&sub_id, &hash]).await.unwrap(); +} + +#[tokio::test] +async fn follow_forks_pruned_block() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let mut client = Arc::new(builder.build()); + + let api = ChainHead::new( + client.clone(), + backend, + Arc::new(TaskExecutor::default()), + CHAIN_GENESIS, + MAX_PINNED_BLOCKS, + Duration::from_secs(MAX_PINNED_SECS), + ) + .into_rpc(); + + // Block tree before the subscription: + // + // finalized -> block 1 -> block 2 -> block 3 + // ^^^ finalized + // -> block 1 -> block 4 -> block 5 + // + + let block_1 = client.new_block(Default::default()).unwrap().build().unwrap().block; + client.import(BlockOrigin::Own, block_1.clone()).await.unwrap(); + + let block_2 = client.new_block(Default::default()).unwrap().build().unwrap().block; + client.import(BlockOrigin::Own, block_2.clone()).await.unwrap(); + + let block_3 = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_3_hash = block_3.header.hash(); + client.import(BlockOrigin::Own, block_3.clone()).await.unwrap(); + + // Block 4 with parent Block 1 is not the best imported. + let mut block_builder = + client.new_block_at(block_1.header.hash(), Default::default(), false).unwrap(); + // This push is required as otherwise block 4 has the same hash as block 2 and won't get + // imported + block_builder + .push_transfer(Transfer { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Ferdie.into(), + amount: 41, + nonce: 0, + }) + .unwrap(); + let block_4 = block_builder.build().unwrap().block; + client.import(BlockOrigin::Own, block_4.clone()).await.unwrap(); + + let mut block_builder = + client.new_block_at(block_4.header.hash(), Default::default(), false).unwrap(); + block_builder + .push_transfer(Transfer { + from: AccountKeyring::Bob.into(), + to: AccountKeyring::Ferdie.into(), + amount: 41, + nonce: 0, + }) + .unwrap(); + let block_5 = block_builder.build().unwrap().block; + client.import(BlockOrigin::Own, block_5.clone()).await.unwrap(); + + // Block 4 and 5 are not pruned, pruning happens at height (N - 1). + client.finalize_block(block_3_hash, None).unwrap(); + + let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap(); + + // Initialized must always be reported first. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Initialized(Initialized { + finalized_block_hash: format!("{:?}", block_3_hash), + finalized_block_runtime: None, + with_runtime: false, + }); + assert_eq!(event, expected); + + // Block tree: + // + // finalized -> block 1 -> block 2 -> block 3 -> block 6 + // ^^^ finalized + // -> block 1 -> block 4 -> block 5 + // + // Mark block 6 as finalized to force block 4 and 5 to get pruned. + + let block_6 = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_6_hash = block_6.header.hash(); + client.import(BlockOrigin::Own, block_6.clone()).await.unwrap(); + + client.finalize_block(block_6_hash, None).unwrap(); + + // Check block 6. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_6_hash), + parent_block_hash: format!("{:?}", block_3_hash), + new_runtime: None, + with_runtime: false, + }); + assert_eq!(event, expected); + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::BestBlockChanged(BestBlockChanged { + best_block_hash: format!("{:?}", block_6_hash), + }); + assert_eq!(event, expected); + + // Block 4 and 5 must not be reported as pruned. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Finalized(Finalized { + finalized_block_hashes: vec![format!("{:?}", block_6_hash)], + pruned_block_hashes: vec![], + }); + assert_eq!(event, expected); +} + +#[tokio::test] +async fn follow_report_multiple_pruned_block() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let mut client = Arc::new(builder.build()); + + let api = ChainHead::new( + client.clone(), + backend, + Arc::new(TaskExecutor::default()), + CHAIN_GENESIS, + MAX_PINNED_BLOCKS, + Duration::from_secs(MAX_PINNED_SECS), + ) + .into_rpc(); + + // Block tree: + // + // finalized -> block 1 -> block 2 -> block 3 + // ^^^ finalized after subscription + // -> block 1 -> block 4 -> block 5 + + let finalized_hash = client.info().finalized_hash; + + let block_1 = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_1_hash = block_1.header.hash(); + client.import(BlockOrigin::Own, block_1.clone()).await.unwrap(); + + let block_2 = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_2_hash = block_2.header.hash(); + client.import(BlockOrigin::Own, block_2.clone()).await.unwrap(); + + let block_3 = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_3_hash = block_3.header.hash(); + client.import(BlockOrigin::Own, block_3.clone()).await.unwrap(); + + // Block 4 with parent Block 1 is not the best imported. + let mut block_builder = + client.new_block_at(block_1.header.hash(), Default::default(), false).unwrap(); + // This push is required as otherwise block 4 has the same hash as block 2 and won't get + // imported + block_builder + .push_transfer(Transfer { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Ferdie.into(), + amount: 41, + nonce: 0, + }) + .unwrap(); + let block_4 = block_builder.build().unwrap().block; + let block_4_hash = block_4.header.hash(); + client.import(BlockOrigin::Own, block_4.clone()).await.unwrap(); + + let mut block_builder = + client.new_block_at(block_4.header.hash(), Default::default(), false).unwrap(); + block_builder + .push_transfer(Transfer { + from: AccountKeyring::Bob.into(), + to: AccountKeyring::Ferdie.into(), + amount: 41, + nonce: 0, + }) + .unwrap(); + let block_5 = block_builder.build().unwrap().block; + let block_5_hash = block_5.header.hash(); + client.import(BlockOrigin::Own, block_5.clone()).await.unwrap(); + let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap(); + + // Initialized must always be reported first. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Initialized(Initialized { + finalized_block_hash: format!("{:?}", finalized_hash), + finalized_block_runtime: None, + with_runtime: false, + }); + assert_eq!(event, expected); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_1_hash), + parent_block_hash: format!("{:?}", finalized_hash), + new_runtime: None, + with_runtime: false, + }); + assert_eq!(event, expected); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_2_hash), + parent_block_hash: format!("{:?}", block_1_hash), + new_runtime: None, + with_runtime: false, + }); + assert_eq!(event, expected); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_3_hash), + parent_block_hash: format!("{:?}", block_2_hash), + new_runtime: None, + with_runtime: false, + }); + assert_eq!(event, expected); + + // The fork must also be reported. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_4_hash), + parent_block_hash: format!("{:?}", block_1_hash), + new_runtime: None, + with_runtime: false, + }); + assert_eq!(event, expected); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_5_hash), + parent_block_hash: format!("{:?}", block_4_hash), + new_runtime: None, + with_runtime: false, + }); + assert_eq!(event, expected); + + // The best block of the chain must also be reported. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::BestBlockChanged(BestBlockChanged { + best_block_hash: format!("{:?}", block_3_hash), + }); + assert_eq!(event, expected); + + // Block 4 and 5 are not pruned, pruning happens at height (N - 1). + client.finalize_block(block_3_hash, None).unwrap(); + + // Finalizing block 3 directly will also result in block 1 and 2 being finalized. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Finalized(Finalized { + finalized_block_hashes: vec![ + format!("{:?}", block_1_hash), + format!("{:?}", block_2_hash), + format!("{:?}", block_3_hash), + ], + pruned_block_hashes: vec![], + }); + assert_eq!(event, expected); + + // Block tree: + // + // finalized -> block 1 -> block 2 -> block 3 -> block 6 + // ^^^ finalized + // -> block 1 -> block 4 -> block 5 + // + // Mark block 6 as finalized to force block 4 and 5 to get pruned. + + let block_6 = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_6_hash = block_6.header.hash(); + client.import(BlockOrigin::Own, block_6.clone()).await.unwrap(); + + client.finalize_block(block_6_hash, None).unwrap(); + + // Check block 6. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_6_hash), + parent_block_hash: format!("{:?}", block_3_hash), + new_runtime: None, + with_runtime: false, + }); + assert_eq!(event, expected); + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::BestBlockChanged(BestBlockChanged { + best_block_hash: format!("{:?}", block_6_hash), + }); + assert_eq!(event, expected); + + // Block 4 and 5 be reported as pruned, not just the stale head (block 5). + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Finalized(Finalized { + finalized_block_hashes: vec![format!("{:?}", block_6_hash)], + pruned_block_hashes: vec![format!("{:?}", block_4_hash), format!("{:?}", block_5_hash)], + }); + assert_eq!(event, expected); +} + +#[tokio::test] +async fn pin_block_references() { + // Manually construct an in-memory backend and client. + let backend = Arc::new(sc_client_api::in_mem::Backend::new()); + let executor = substrate_test_runtime_client::new_native_or_wasm_executor(); + let client_config = sc_service::ClientConfig::default(); + + let genesis_block_builder = sc_service::GenesisBlockBuilder::new( + &substrate_test_runtime_client::GenesisParameters::default().genesis_storage(), + !client_config.no_genesis, + backend.clone(), + executor.clone(), + ) + .unwrap(); + + let mut client = Arc::new( + new_in_mem::<_, Block, _, RuntimeApi>( + backend.clone(), + executor, + genesis_block_builder, + None, + None, + Box::new(TaskExecutor::new()), + client_config, + ) + .unwrap(), + ); + + let api = ChainHead::new( + client.clone(), + backend.clone(), + Arc::new(TaskExecutor::default()), + CHAIN_GENESIS, + 3, + Duration::from_secs(MAX_PINNED_SECS), + ) + .into_rpc(); + + async fn wait_pinned_references( + backend: &Arc>, + hash: &Block::Hash, + target: i64, + ) { + // Retry for at most 2 minutes. + let mut retries = 120; + while backend.pin_refs(hash).unwrap() != target { + if retries == 0 { + panic!("Expected target={} pinned references for hash={:?}", target, hash); + } + retries -= 1; + + tokio::time::sleep(tokio::time::Duration::from_secs(1)).await; + } + } + + let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap(); + let sub_id = sub.subscription_id(); + let sub_id = serde_json::to_string(&sub_id).unwrap(); + + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let hash = block.header.hash(); + let block_hash = format!("{:?}", hash); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + // Ensure the imported block is propagated for this subscription. + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::Initialized(_) + ); + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::NewBlock(_) + ); + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::BestBlockChanged(_) + ); + + // We need to wait a bit for: + // 1. `NewBlock` and `BestBlockChanged` notifications to propagate to the chainHead + // subscription. (pin_refs == 2) + // 2. The chainHead to call `pin_blocks` only once for the `NewBlock` + // notification (pin_refs == 3) + // 3. Both notifications to go out of scope (pin_refs == 1 (total 3 - dropped 2)). + wait_pinned_references(&backend, &hash, 1).await; + + // To not exceed the number of pinned blocks, we need to unpin before the next import. + let _res: () = api.call("chainHead_unstable_unpin", [&sub_id, &block_hash]).await.unwrap(); + + // Make sure unpin clears out the reference. + let refs = backend.pin_refs(&hash).unwrap(); + assert_eq!(refs, 0); + + // Add another 2 blocks and make sure we drop the subscription with the blocks pinned. + let mut hashes = Vec::new(); + for _ in 0..2 { + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + let hash = block.header.hash(); + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + // Ensure the imported block is propagated for this subscription. + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::NewBlock(_) + ); + assert_matches!( + get_next_event::>(&mut sub).await, + FollowEvent::BestBlockChanged(_) + ); + + hashes.push(hash); + } + + // Make sure the pin was propagated. + for hash in &hashes { + wait_pinned_references(&backend, hash, 1).await; + } + + // Drop the subscription and expect the pinned blocks to be released. + drop(sub); + // The `chainHead` detects the subscription was terminated when it tries + // to send another block. + let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + client.import(BlockOrigin::Own, block.clone()).await.unwrap(); + + for hash in &hashes { + wait_pinned_references(&backend, &hash, 0).await; + } +} + +#[tokio::test] +async fn follow_finalized_before_new_block() { + let builder = TestClientBuilder::new(); + let backend = builder.backend(); + let mut client = Arc::new(builder.build()); + + let client_mock = Arc::new(ChainHeadMockClient::new(client.clone())); + + let api = ChainHead::new( + client_mock.clone(), + backend, + Arc::new(TaskExecutor::default()), + CHAIN_GENESIS, + MAX_PINNED_BLOCKS, + Duration::from_secs(MAX_PINNED_SECS), + ) + .into_rpc(); + + // Make sure the block is imported for it to be pinned. + let block_1 = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_1_hash = block_1.header.hash(); + client.import(BlockOrigin::Own, block_1.clone()).await.unwrap(); + + let mut sub = api.subscribe("chainHead_unstable_follow", [false]).await.unwrap(); + + // Trigger the `FinalizedNotification` for block 1 before the `BlockImportNotification`, and + // expect for the `chainHead` to generate `NewBlock`, `BestBlock` and `Finalized` events. + + // Trigger the Finalized notification before the NewBlock one. + run_with_timeout(client_mock.trigger_finality_stream(block_1.header.clone())).await; + + // Initialized must always be reported first. + let finalized_hash = client.info().finalized_hash; + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Initialized(Initialized { + finalized_block_hash: format!("{:?}", finalized_hash), + finalized_block_runtime: None, + with_runtime: false, + }); + assert_eq!(event, expected); + + // Block 1 must be reported because we triggered the finalized notification. + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_1_hash), + parent_block_hash: format!("{:?}", finalized_hash), + new_runtime: None, + with_runtime: false, + }); + assert_eq!(event, expected); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::BestBlockChanged(BestBlockChanged { + best_block_hash: format!("{:?}", block_1_hash), + }); + assert_eq!(event, expected); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::Finalized(Finalized { + finalized_block_hashes: vec![format!("{:?}", block_1_hash)], + pruned_block_hashes: vec![], + }); + assert_eq!(event, expected); + + let block_2 = client.new_block(Default::default()).unwrap().build().unwrap().block; + let block_2_hash = block_2.header.hash(); + client.import(BlockOrigin::Own, block_2.clone()).await.unwrap(); + + // Triggering the `BlockImportNotification` notification for block 1 should have no effect + // on the notification because the events were handled by the `FinalizedNotification`. + // Also trigger the `BlockImportNotification` notification for block 2 to ensure + // `NewBlock and `BestBlock` events are generated. + + // Trigger NewBlock notification for block 1 and block 2. + run_with_timeout(client_mock.trigger_import_stream(block_1.header)).await; + run_with_timeout(client_mock.trigger_import_stream(block_2.header)).await; + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::NewBlock(NewBlock { + block_hash: format!("{:?}", block_2_hash), + parent_block_hash: format!("{:?}", block_1_hash), + new_runtime: None, + with_runtime: false, + }); + assert_eq!(event, expected); + + let event: FollowEvent = get_next_event(&mut sub).await; + let expected = FollowEvent::BestBlockChanged(BestBlockChanged { + best_block_hash: format!("{:?}", block_2_hash), + }); + assert_eq!(event, expected); } diff --git a/client/rpc-spec-v2/src/chain_spec/api.rs b/client/rpc-spec-v2/src/chain_spec/api.rs index dfe2d76de650..66c9f868047c 100644 --- a/client/rpc-spec-v2/src/chain_spec/api.rs +++ b/client/rpc-spec-v2/src/chain_spec/api.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-spec-v2/src/chain_spec/chain_spec.rs b/client/rpc-spec-v2/src/chain_spec/chain_spec.rs index 90d05f1d9d41..99ea34521f58 100644 --- a/client/rpc-spec-v2/src/chain_spec/chain_spec.rs +++ b/client/rpc-spec-v2/src/chain_spec/chain_spec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-spec-v2/src/chain_spec/mod.rs b/client/rpc-spec-v2/src/chain_spec/mod.rs index cd4fcf246f60..0dfbdf1b10cc 100644 --- a/client/rpc-spec-v2/src/chain_spec/mod.rs +++ b/client/rpc-spec-v2/src/chain_spec/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-spec-v2/src/chain_spec/tests.rs b/client/rpc-spec-v2/src/chain_spec/tests.rs index 6f662ba422bc..74aec01a2113 100644 --- a/client/rpc-spec-v2/src/chain_spec/tests.rs +++ b/client/rpc-spec-v2/src/chain_spec/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-spec-v2/src/lib.rs b/client/rpc-spec-v2/src/lib.rs index 5af7e3be264d..7c22ef5d5231 100644 --- a/client/rpc-spec-v2/src/lib.rs +++ b/client/rpc-spec-v2/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-spec-v2/src/transaction/api.rs b/client/rpc-spec-v2/src/transaction/api.rs index 2f0c799f1cc1..c226ab86787e 100644 --- a/client/rpc-spec-v2/src/transaction/api.rs +++ b/client/rpc-spec-v2/src/transaction/api.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-spec-v2/src/transaction/error.rs b/client/rpc-spec-v2/src/transaction/error.rs index 72a5959992f9..d2de07afd595 100644 --- a/client/rpc-spec-v2/src/transaction/error.rs +++ b/client/rpc-spec-v2/src/transaction/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -68,7 +68,7 @@ impl From for TransactionEvent { Error::Pool(PoolError::TooLowPriority { old, new }) => TransactionEvent::Invalid(TransactionError { error: format!( - "The priority of the transactin is too low (pool {} > current {})", + "The priority of the transaction is too low (pool {} > current {})", old, new ), }), diff --git a/client/rpc-spec-v2/src/transaction/event.rs b/client/rpc-spec-v2/src/transaction/event.rs index 3c75eaff10fd..bdc126366fb9 100644 --- a/client/rpc-spec-v2/src/transaction/event.rs +++ b/client/rpc-spec-v2/src/transaction/event.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-spec-v2/src/transaction/mod.rs b/client/rpc-spec-v2/src/transaction/mod.rs index bb983894a428..212912ba1c72 100644 --- a/client/rpc-spec-v2/src/transaction/mod.rs +++ b/client/rpc-spec-v2/src/transaction/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc-spec-v2/src/transaction/transaction.rs b/client/rpc-spec-v2/src/transaction/transaction.rs index e2cf736dff17..44f4bd36c8b8 100644 --- a/client/rpc-spec-v2/src/transaction/transaction.rs +++ b/client/rpc-spec-v2/src/transaction/transaction.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc/Cargo.toml b/client/rpc/Cargo.toml index a22f65787881..a3574ed84d01 100644 --- a/client/rpc/Cargo.toml +++ b/client/rpc/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" jsonrpsee = { version = "0.16.2", features = ["server"] } log = "0.4.17" @@ -28,13 +28,14 @@ sc-transaction-pool-api = { version = "4.0.0-dev", path = "../transaction-pool/a sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-keystore = { version = "0.27.0", path = "../../primitives/keystore" } sp-offchain = { version = "4.0.0-dev", path = "../../primitives/offchain" } sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } sp-session = { version = "4.0.0-dev", path = "../../primitives/session" } -sp-version = { version = "5.0.0", path = "../../primitives/version" } +sp-version = { version = "22.0.0", path = "../../primitives/version" } +sp-statement-store = { version = "4.0.0-dev", path = "../../primitives/statement-store" } tokio = "1.22.0" @@ -47,8 +48,9 @@ sc-network-common = { version = "0.10.0-dev", path = "../network/common" } sc-transaction-pool = { version = "4.0.0-dev", path = "../transaction-pool" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } tokio = "1.22.0" -sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } +pretty_assertions = "1.2.1" [features] test-helpers = [] diff --git a/client/rpc/src/author/mod.rs b/client/rpc/src/author/mod.rs index 7d0ffdc62e08..feee22641ef3 100644 --- a/client/rpc/src/author/mod.rs +++ b/client/rpc/src/author/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -37,10 +37,10 @@ use sc_transaction_pool_api::{ error::IntoPoolError, BlockHash, InPoolTransaction, TransactionFor, TransactionPool, TransactionSource, TxHash, }; -use sp_api::ProvideRuntimeApi; +use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_blockchain::HeaderBackend; use sp_core::Bytes; -use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; +use sp_keystore::{KeystoreExt, KeystorePtr}; use sp_runtime::{generic, traits::Block as BlockT}; use sp_session::SessionKeys; @@ -55,7 +55,7 @@ pub struct Author { /// Transactions pool pool: Arc

, /// The key store. - keystore: SyncCryptoStorePtr, + keystore: KeystorePtr, /// Whether to deny unsafe calls deny_unsafe: DenyUnsafe, /// Executor to spawn subscriptions. @@ -67,7 +67,7 @@ impl Author { pub fn new( client: Arc, pool: Arc

, - keystore: SyncCryptoStorePtr, + keystore: KeystorePtr, deny_unsafe: DenyUnsafe, executor: SubscriptionTaskExecutor, ) -> Self { @@ -112,8 +112,9 @@ where self.deny_unsafe.check_if_safe()?; let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?; - SyncCryptoStore::insert_unknown(&*self.keystore, key_type, &suri, &public[..]) - .map_err(|_| Error::KeyStoreUnavailable)?; + self.keystore + .insert(key_type, &suri, &public[..]) + .map_err(|_| Error::KeystoreUnavailable)?; Ok(()) } @@ -121,9 +122,12 @@ where self.deny_unsafe.check_if_safe()?; let best_block_hash = self.client.info().best_hash; - self.client - .runtime_api() - .generate_session_keys(&generic::BlockId::Hash(best_block_hash), None) + let mut runtime_api = self.client.runtime_api(); + + runtime_api.register_extension(KeystoreExt::from(self.keystore.clone())); + + runtime_api + .generate_session_keys(best_block_hash, None) .map(Into::into) .map_err(|api_err| Error::Client(Box::new(api_err)).into()) } @@ -135,18 +139,18 @@ where let keys = self .client .runtime_api() - .decode_session_keys(&generic::BlockId::Hash(best_block_hash), session_keys.to_vec()) + .decode_session_keys(best_block_hash, session_keys.to_vec()) .map_err(|e| Error::Client(Box::new(e)))? .ok_or(Error::InvalidSessionKeys)?; - Ok(SyncCryptoStore::has_keys(&*self.keystore, &keys)) + Ok(self.keystore.has_keys(&keys)) } fn has_key(&self, public_key: Bytes, key_type: String) -> RpcResult { self.deny_unsafe.check_if_safe()?; let key_type = key_type.as_str().try_into().map_err(|_| Error::BadKeyType)?; - Ok(SyncCryptoStore::has_keys(&*self.keystore, &[(public_key.to_vec(), key_type)])) + Ok(self.keystore.has_keys(&[(public_key.to_vec(), key_type)])) } fn pending_extrinsics(&self) -> RpcResult> { diff --git a/client/rpc/src/author/tests.rs b/client/rpc/src/author/tests.rs index 573d01630de3..f48b2f957142 100644 --- a/client/rpc/src/author/tests.rs +++ b/client/rpc/src/author/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -31,16 +31,17 @@ use sc_transaction_pool_api::TransactionStatus; use sp_core::{ blake2_256, bytes::to_hex, - crypto::{ByteArray, CryptoTypePublicPair, Pair}, - ed25519, sr25519, + crypto::{ByteArray, Pair}, + ed25519, testing::{ED25519, SR25519}, H256, }; -use sp_keystore::testing::KeyStore; +use sp_keystore::{testing::MemoryKeystore, Keystore}; +use sp_runtime::Perbill; use std::sync::Arc; use substrate_test_runtime_client::{ self, - runtime::{Block, Extrinsic, SessionKeys, Transfer}, + runtime::{Block, Extrinsic, ExtrinsicBuilder, SessionKeys, Transfer}, AccountKeyring, Backend, Client, DefaultTestClientBuilderExt, TestClientBuilderExt, }; @@ -51,22 +52,21 @@ fn uxt(sender: AccountKeyring, nonce: u64) -> Extrinsic { from: sender.into(), to: AccountKeyring::Bob.into(), }; - tx.into_signed_tx() + ExtrinsicBuilder::new_transfer(tx).build() } type FullTransactionPool = BasicPool, Block>, Block>; struct TestSetup { pub client: Arc>, - pub keystore: Arc, + pub keystore: Arc, pub pool: Arc, } impl Default for TestSetup { fn default() -> Self { - let keystore = Arc::new(KeyStore::new()); - let client_builder = substrate_test_runtime_client::TestClientBuilder::new(); - let client = Arc::new(client_builder.set_keystore(keystore.clone()).build()); + let keystore = Arc::new(MemoryKeystore::new()); + let client = Arc::new(substrate_test_runtime_client::TestClientBuilder::new().build()); let spawner = sp_core::testing::TaskExecutor::new(); let pool = @@ -111,7 +111,13 @@ async fn author_submit_transaction_should_not_cause_error() { #[tokio::test] async fn author_should_watch_extrinsic() { let api = TestSetup::into_rpc(); - let xt = to_hex(&uxt(AccountKeyring::Alice, 0).encode(), true); + let xt = to_hex( + &ExtrinsicBuilder::new_call_with_priority(0) + .signer(AccountKeyring::Alice.into()) + .build() + .encode(), + true, + ); let mut sub = api.subscribe("author_submitAndWatchExtrinsic", [xt]).await.unwrap(); let (tx, sub_id) = timeout_secs(10, sub.next::>()) @@ -125,15 +131,11 @@ async fn author_should_watch_extrinsic() { // Replace the extrinsic and observe the subscription is notified. let (xt_replacement, xt_hash) = { - let tx = Transfer { - amount: 5, - nonce: 0, - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), - }; - let tx = tx.into_signed_tx().encode(); + let tx = ExtrinsicBuilder::new_call_with_priority(1) + .signer(AccountKeyring::Alice.into()) + .build() + .encode(); let hash = blake2_256(&tx); - (to_hex(&tx, true), hash) }; @@ -152,10 +154,10 @@ async fn author_should_watch_extrinsic() { async fn author_should_return_watch_validation_error() { const METHOD: &'static str = "author_submitAndWatchExtrinsic"; + let invalid_xt = ExtrinsicBuilder::new_fill_block(Perbill::from_percent(100)).build(); + let api = TestSetup::into_rpc(); - let failed_sub = api - .subscribe(METHOD, [to_hex(&uxt(AccountKeyring::Alice, 179).encode(), true)]) - .await; + let failed_sub = api.subscribe(METHOD, [to_hex(&invalid_xt.encode(), true)]).await; assert_matches!( failed_sub, @@ -225,11 +227,9 @@ async fn author_should_insert_key() { keypair.public().0.to_vec().into(), ); api.call::<_, ()>("author_insertKey", params).await.unwrap(); - let pubkeys = SyncCryptoStore::keys(&*setup.keystore, ED25519).unwrap(); + let pubkeys = setup.keystore.keys(ED25519).unwrap(); - assert!( - pubkeys.contains(&CryptoTypePublicPair(ed25519::CRYPTO_ID, keypair.public().to_raw_vec())) - ); + assert!(pubkeys.contains(&keypair.public().to_raw_vec())); } #[tokio::test] @@ -240,12 +240,10 @@ async fn author_should_rotate_keys() { let new_pubkeys: Bytes = api.call("author_rotateKeys", EmptyParams::new()).await.unwrap(); let session_keys = SessionKeys::decode(&mut &new_pubkeys[..]).expect("SessionKeys decode successfully"); - let ed25519_pubkeys = SyncCryptoStore::keys(&*setup.keystore, ED25519).unwrap(); - let sr25519_pubkeys = SyncCryptoStore::keys(&*setup.keystore, SR25519).unwrap(); - assert!(ed25519_pubkeys - .contains(&CryptoTypePublicPair(ed25519::CRYPTO_ID, session_keys.ed25519.to_raw_vec()))); - assert!(sr25519_pubkeys - .contains(&CryptoTypePublicPair(sr25519::CRYPTO_ID, session_keys.sr25519.to_raw_vec()))); + let ed25519_pubkeys = setup.keystore.keys(ED25519).unwrap(); + let sr25519_pubkeys = setup.keystore.keys(SR25519).unwrap(); + assert!(ed25519_pubkeys.contains(&session_keys.ed25519.to_raw_vec())); + assert!(sr25519_pubkeys.contains(&session_keys.sr25519.to_raw_vec())); } #[tokio::test] diff --git a/client/rpc/src/chain/chain_full.rs b/client/rpc/src/chain/chain_full.rs index 6ff544b0deac..a88291eb7bd3 100644 --- a/client/rpc/src/chain/chain_full.rs +++ b/client/rpc/src/chain/chain_full.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc/src/chain/mod.rs b/client/rpc/src/chain/mod.rs index 16e69c0f6324..2b5994f217df 100644 --- a/client/rpc/src/chain/mod.rs +++ b/client/rpc/src/chain/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -59,10 +59,10 @@ where } } - /// Get header of a relay chain block. + /// Get header of a block. fn header(&self, hash: Option) -> Result, Error>; - /// Get header and body of a relay chain block. + /// Get header and body of a block. fn block(&self, hash: Option) -> Result>, Error>; /// Get hash of the n-th block in the canon chain. diff --git a/client/rpc/src/chain/tests.rs b/client/rpc/src/chain/tests.rs index 224d021f9409..75211a43bd9f 100644 --- a/client/rpc/src/chain/tests.rs +++ b/client/rpc/src/chain/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc/src/dev/mod.rs b/client/rpc/src/dev/mod.rs index e48a8ee4e7d5..4d2e8618d553 100644 --- a/client/rpc/src/dev/mod.rs +++ b/client/rpc/src/dev/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -28,7 +28,7 @@ use sc_rpc_api::{dev::error::Error, DenyUnsafe}; use sp_api::{ApiExt, Core, ProvideRuntimeApi}; use sp_core::Encode; use sp_runtime::{ - generic::{BlockId, DigestItem}, + generic::DigestItem, traits::{Block as BlockT, Header}, }; use std::{ @@ -98,7 +98,7 @@ where let mut runtime_api = self.client.runtime_api(); runtime_api.record_proof(); runtime_api - .execute_block(&BlockId::Hash(parent_header.hash()), block) + .execute_block(parent_header.hash(), block) .map_err(|_| Error::BlockExecutionFailed)?; let witness = runtime_api .extract_proof() diff --git a/client/rpc/src/dev/tests.rs b/client/rpc/src/dev/tests.rs index 816f3cdfe602..db6a9a119da0 100644 --- a/client/rpc/src/dev/tests.rs +++ b/client/rpc/src/dev/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -28,6 +28,22 @@ async fn block_stats_work() { let api = >::new(client.clone(), DenyUnsafe::No).into_rpc(); let block = client.new_block(Default::default()).unwrap().build().unwrap().block; + + let (expected_witness_len, expected_witness_compact_len, expected_block_len) = { + let genesis_hash = client.chain_info().genesis_hash; + let mut runtime_api = client.runtime_api(); + runtime_api.record_proof(); + runtime_api.execute_block(genesis_hash, block.clone()).unwrap(); + let witness = runtime_api.extract_proof().unwrap(); + let pre_root = *client.header(genesis_hash).unwrap().unwrap().state_root(); + + ( + witness.clone().encoded_size() as u64, + witness.into_compact_proof::>(pre_root).unwrap().encoded_size() as u64, + block.encoded_size() as u64, + ) + }; + client.import(BlockOrigin::Own, block).await.unwrap(); // Can't gather stats for a block without a parent. @@ -43,9 +59,9 @@ async fn block_stats_work() { .await .unwrap(), Some(BlockStats { - witness_len: 630, - witness_compact_len: 534, - block_len: 99, + witness_len: expected_witness_len, + witness_compact_len: expected_witness_compact_len, + block_len: expected_block_len, num_extrinsics: 0, }), ); diff --git a/client/rpc/src/lib.rs b/client/rpc/src/lib.rs index a0e810eafbb6..475fc77a9b5b 100644 --- a/client/rpc/src/lib.rs +++ b/client/rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -36,6 +36,7 @@ pub mod chain; pub mod dev; pub mod offchain; pub mod state; +pub mod statement; pub mod system; #[cfg(any(test, feature = "test-helpers"))] diff --git a/client/rpc/src/offchain/mod.rs b/client/rpc/src/offchain/mod.rs index 6896b8261916..de711accdcc1 100644 --- a/client/rpc/src/offchain/mod.rs +++ b/client/rpc/src/offchain/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc/src/offchain/tests.rs b/client/rpc/src/offchain/tests.rs index 28a7b6115b65..62a846d0887c 100644 --- a/client/rpc/src/offchain/tests.rs +++ b/client/rpc/src/offchain/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc/src/state/mod.rs b/client/rpc/src/state/mod.rs index 9ba4c8218dd7..057661d6ec7f 100644 --- a/client/rpc/src/state/mod.rs +++ b/client/rpc/src/state/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -33,7 +33,7 @@ use jsonrpsee::{ types::SubscriptionResult, }; -use sc_rpc_api::{state::ReadProof, DenyUnsafe}; +use sc_rpc_api::DenyUnsafe; use sp_core::{ storage::{PrefixedStorageKey, StorageChangeSet, StorageData, StorageKey}, Bytes, @@ -169,7 +169,6 @@ pub fn new_full( client: Arc, executor: SubscriptionTaskExecutor, deny_unsafe: DenyUnsafe, - rpc_max_payload: Option, ) -> (State, ChildState) where Block: BlockT + 'static, @@ -189,12 +188,9 @@ where + 'static, Client::Api: Metadata, { - let child_backend = Box::new(self::state_full::FullState::new( - client.clone(), - executor.clone(), - rpc_max_payload, - )); - let backend = Box::new(self::state_full::FullState::new(client, executor, rpc_max_payload)); + let child_backend = + Box::new(self::state_full::FullState::new(client.clone(), executor.clone())); + let backend = Box::new(self::state_full::FullState::new(client, executor)); (State { backend, deny_unsafe }, ChildState { backend: child_backend }) } diff --git a/client/rpc/src/state/state_full.rs b/client/rpc/src/state/state_full.rs index 58dfd9ea876a..9604d9165f98 100644 --- a/client/rpc/src/state/state_full.rs +++ b/client/rpc/src/state/state_full.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -46,9 +46,10 @@ use sp_core::{ storage::{ ChildInfo, ChildType, PrefixedStorageKey, StorageChangeSet, StorageData, StorageKey, }, + traits::CallContext, Bytes, }; -use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use sp_runtime::traits::Block as BlockT; use sp_version::RuntimeVersion; /// The maximum time allowed for an RPC call when running without unsafe RPC enabled. @@ -65,7 +66,6 @@ pub struct FullState { client: Arc, executor: SubscriptionTaskExecutor, _phantom: PhantomData<(BE, Block)>, - rpc_max_payload: Option, } impl FullState @@ -78,12 +78,8 @@ where Block: BlockT + 'static, { /// Create new state API backend for full nodes. - pub fn new( - client: Arc, - executor: SubscriptionTaskExecutor, - rpc_max_payload: Option, - ) -> Self { - Self { client, executor, _phantom: PhantomData, rpc_max_payload } + pub fn new(client: Arc, executor: SubscriptionTaskExecutor) -> Self { + Self { client, executor, _phantom: PhantomData } } /// Returns given block hash or best block hash if None is passed. @@ -202,34 +198,35 @@ where .and_then(|block| { self.client .executor() - .call( - block, - &method, - &call_data, - self.client.execution_extensions().strategies().other, - ) + .call(block, &method, &call_data, CallContext::Offchain) .map(Into::into) }) .map_err(client_err) } + // TODO: This is horribly broken; either remove it, or make it streaming. fn storage_keys( &self, block: Option, prefix: StorageKey, ) -> std::result::Result, Error> { + // TODO: Remove the `.collect`. self.block_or_best(block) - .and_then(|block| self.client.storage_keys(block, &prefix)) + .and_then(|block| self.client.storage_keys(block, Some(&prefix), None)) + .map(|iter| iter.collect()) .map_err(client_err) } + // TODO: This is horribly broken; either remove it, or make it streaming. fn storage_pairs( &self, block: Option, prefix: StorageKey, ) -> std::result::Result, Error> { + // TODO: Remove the `.collect`. self.block_or_best(block) - .and_then(|block| self.client.storage_pairs(block, &prefix)) + .and_then(|block| self.client.storage_pairs(block, Some(&prefix), None)) + .map(|iter| iter.collect()) .map_err(client_err) } @@ -241,9 +238,7 @@ where start_key: Option, ) -> std::result::Result, Error> { self.block_or_best(block) - .and_then(|block| { - self.client.storage_keys_iter(block, prefix.as_ref(), start_key.as_ref()) - }) + .and_then(|block| self.client.storage_keys(block, prefix.as_ref(), start_key.as_ref())) .map(|iter| iter.take(count as usize).collect()) .map_err(client_err) } @@ -284,7 +279,7 @@ where } // The key doesn't point to anything, so it's probably a prefix. - let iter = match client.storage_keys_iter(block, Some(&key), None).map_err(client_err) { + let iter = match client.storage_keys(block, Some(&key), None).map_err(client_err) { Ok(iter) => iter, Err(e) => return Ok(Err(e)), }; @@ -321,7 +316,7 @@ where self.block_or_best(block).map_err(client_err).and_then(|block| { self.client .runtime_api() - .metadata(&BlockId::Hash(block)) + .metadata(block) .map(Into::into) .map_err(|e| Error::Client(Box::new(e))) }) @@ -332,9 +327,7 @@ where block: Option, ) -> std::result::Result { self.block_or_best(block).map_err(client_err).and_then(|block| { - self.client - .runtime_version_at(&BlockId::Hash(block)) - .map_err(|e| Error::Client(Box::new(e))) + self.client.runtime_version_at(block).map_err(|e| Error::Client(Box::new(e))) }) } @@ -383,9 +376,7 @@ where let initial = match self .block_or_best(None) - .and_then(|block| { - self.client.runtime_version_at(&BlockId::Hash(block)).map_err(Into::into) - }) + .and_then(|block| self.client.runtime_version_at(block).map_err(Into::into)) .map_err(|e| Error::Client(Box::new(e))) { Ok(initial) => initial, @@ -402,9 +393,8 @@ where .import_notification_stream() .filter(|n| future::ready(n.is_new_best)) .filter_map(move |n| { - let version = client - .runtime_version_at(&BlockId::hash(n.hash)) - .map_err(|e| Error::Client(Box::new(e))); + let version = + client.runtime_version_at(n.hash).map_err(|e| Error::Client(Box::new(e))); match version { Ok(version) if version != previous_version => { @@ -480,7 +470,6 @@ where targets, storage_keys, methods, - self.rpc_max_payload, ) .trace_block() .map_err(|e| invalid_block::(block, None, e.to_string())) @@ -536,6 +525,7 @@ where storage_key: PrefixedStorageKey, prefix: StorageKey, ) -> std::result::Result, Error> { + // TODO: Remove the `.collect`. self.block_or_best(block) .and_then(|block| { let child_info = match ChildType::from_prefixed_key(&storage_key) { @@ -543,8 +533,9 @@ where ChildInfo::new_default(storage_key), None => return Err(sp_blockchain::Error::InvalidChildStorageKey), }; - self.client.child_storage_keys(block, &child_info, &prefix) + self.client.child_storage_keys(block, child_info, Some(&prefix), None) }) + .map(|iter| iter.collect()) .map_err(client_err) } @@ -563,7 +554,7 @@ where ChildInfo::new_default(storage_key), None => return Err(sp_blockchain::Error::InvalidChildStorageKey), }; - self.client.child_storage_keys_iter( + self.client.child_storage_keys( block, child_info, prefix.as_ref(), diff --git a/client/rpc/src/state/tests.rs b/client/rpc/src/state/tests.rs index fe8bdf0ac2da..7c73c1e1cd8d 100644 --- a/client/rpc/src/state/tests.rs +++ b/client/rpc/src/state/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -29,9 +29,11 @@ use sc_block_builder::BlockBuilderProvider; use sc_rpc_api::DenyUnsafe; use sp_consensus::BlockOrigin; use sp_core::{hash::H256, storage::ChildInfo}; -use sp_io::hashing::blake2_256; use std::sync::Arc; -use substrate_test_runtime_client::{prelude::*, runtime}; +use substrate_test_runtime_client::{ + prelude::*, + runtime::{ExtrinsicBuilder, Transfer}, +}; const STORAGE_KEY: &[u8] = b"child"; @@ -55,7 +57,7 @@ async fn should_return_storage() { .add_extra_storage(b":map:acc2".to_vec(), vec![1, 2, 3]) .build(); let genesis_hash = client.genesis_hash(); - let (client, child) = new_full(Arc::new(client), test_executor(), DenyUnsafe::No, None); + let (client, child) = new_full(Arc::new(client), test_executor(), DenyUnsafe::No); let key = StorageKey(KEY.to_vec()); assert_eq!( @@ -103,7 +105,7 @@ async fn should_return_storage_entries() { .add_extra_child_storage(&child_info, KEY2.to_vec(), CHILD_VALUE2.to_vec()) .build(); let genesis_hash = client.genesis_hash(); - let (_client, child) = new_full(Arc::new(client), test_executor(), DenyUnsafe::No, None); + let (_client, child) = new_full(Arc::new(client), test_executor(), DenyUnsafe::No); let keys = &[StorageKey(KEY1.to_vec()), StorageKey(KEY2.to_vec())]; assert_eq!( @@ -134,7 +136,7 @@ async fn should_return_child_storage() { .build(), ); let genesis_hash = client.genesis_hash(); - let (_client, child) = new_full(client, test_executor(), DenyUnsafe::No, None); + let (_client, child) = new_full(client, test_executor(), DenyUnsafe::No); let child_key = prefixed_storage_key(); let key = StorageKey(b"key".to_vec()); @@ -165,7 +167,7 @@ async fn should_return_child_storage_entries() { .build(), ); let genesis_hash = client.genesis_hash(); - let (_client, child) = new_full(client, test_executor(), DenyUnsafe::No, None); + let (_client, child) = new_full(client, test_executor(), DenyUnsafe::No); let child_key = prefixed_storage_key(); let keys = vec![StorageKey(b"key1".to_vec()), StorageKey(b"key2".to_vec())]; @@ -196,7 +198,7 @@ async fn should_return_child_storage_entries() { async fn should_call_contract() { let client = Arc::new(substrate_test_runtime_client::new()); let genesis_hash = client.genesis_hash(); - let (client, _child) = new_full(client, test_executor(), DenyUnsafe::No, None); + let (client, _child) = new_full(client, test_executor(), DenyUnsafe::No); use jsonrpsee::{core::Error, types::error::CallError}; @@ -210,7 +212,7 @@ async fn should_call_contract() { async fn should_notify_about_storage_changes() { let mut sub = { let mut client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No, None); + let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No); let api_rpc = api.into_rpc(); let sub = api_rpc.subscribe("state_subscribeStorage", EmptyParams::new()).await.unwrap(); @@ -218,7 +220,7 @@ async fn should_notify_about_storage_changes() { // Cause a change: let mut builder = client.new_block(Default::default()).unwrap(); builder - .push_transfer(runtime::Transfer { + .push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), amount: 42, @@ -242,20 +244,28 @@ async fn should_notify_about_storage_changes() { async fn should_send_initial_storage_changes_and_notifications() { let mut sub = { let mut client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No, None); - - let alice_balance_key = - blake2_256(&runtime::system::balance_of_key(AccountKeyring::Alice.into())); + let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No); + + let alice_balance_key = [ + sp_core::hashing::twox_128(b"System"), + sp_core::hashing::twox_128(b"Account"), + sp_core::hashing::blake2_128(&AccountKeyring::Alice.public()), + ] + .concat() + .iter() + .chain(AccountKeyring::Alice.public().0.iter()) + .cloned() + .collect::>(); let api_rpc = api.into_rpc(); let sub = api_rpc - .subscribe("state_subscribeStorage", [[StorageKey(alice_balance_key.to_vec())]]) + .subscribe("state_subscribeStorage", [[StorageKey(alice_balance_key)]]) .await .unwrap(); let mut builder = client.new_block(Default::default()).unwrap(); builder - .push_transfer(runtime::Transfer { + .push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), amount: 42, @@ -278,24 +288,44 @@ async fn should_send_initial_storage_changes_and_notifications() { #[tokio::test] async fn should_query_storage() { async fn run_tests(mut client: Arc) { - let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No, None); + let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No); - let mut add_block = |nonce| { + let mut add_block = |index| { let mut builder = client.new_block(Default::default()).unwrap(); // fake change: None -> None -> None - builder.push_storage_change(vec![1], None).unwrap(); + builder + .push(ExtrinsicBuilder::new_storage_change(vec![1], None).build()) + .unwrap(); // fake change: None -> Some(value) -> Some(value) - builder.push_storage_change(vec![2], Some(vec![2])).unwrap(); + builder + .push(ExtrinsicBuilder::new_storage_change(vec![2], Some(vec![2])).build()) + .unwrap(); // actual change: None -> Some(value) -> None builder - .push_storage_change(vec![3], if nonce == 0 { Some(vec![3]) } else { None }) + .push( + ExtrinsicBuilder::new_storage_change( + vec![3], + if index == 0 { Some(vec![3]) } else { None }, + ) + .build(), + ) .unwrap(); // actual change: None -> Some(value) builder - .push_storage_change(vec![4], if nonce == 0 { None } else { Some(vec![4]) }) + .push( + ExtrinsicBuilder::new_storage_change( + vec![4], + if index == 0 { None } else { Some(vec![4]) }, + ) + .build(), + ) .unwrap(); // actual change: Some(value1) -> Some(value2) - builder.push_storage_change(vec![5], Some(vec![nonce as u8])).unwrap(); + builder + .push( + ExtrinsicBuilder::new_storage_change(vec![5], Some(vec![index as u8])).build(), + ) + .unwrap(); let block = builder.build().unwrap().block; let hash = block.header.hash(); executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); @@ -480,18 +510,19 @@ async fn should_query_storage() { #[tokio::test] async fn should_return_runtime_version() { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No, None); + let (api, _child) = new_full(client.clone(), test_executor(), DenyUnsafe::No); + // it is basically json-encoded substrate_test_runtime_client::runtime::VERSION let result = "{\"specName\":\"test\",\"implName\":\"parity-test\",\"authoringVersion\":1,\ \"specVersion\":2,\"implVersion\":2,\"apis\":[[\"0xdf6acb689907609b\",4],\ - [\"0x37e397fc7c91f5e4\",1],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",6],\ - [\"0xc6e9a76309f39b09\",1],[\"0xdd718d5cc53262d4\",1],[\"0xcbca25e39f142387\",2],\ - [\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],[\"0xbc9d89904f5b923f\",1]],\ - \"transactionVersion\":1,\"stateVersion\":1}"; + [\"0x37e397fc7c91f5e4\",2],[\"0xd2bc9897eed08f15\",3],[\"0x40fe3ad401f8959a\",6],\ + [\"0xbc9d89904f5b923f\",1],[\"0xc6e9a76309f39b09\",2],[\"0xdd718d5cc53262d4\",1],\ + [\"0xcbca25e39f142387\",2],[\"0xf78b278be53f454c\",2],[\"0xab3c0572291feb8b\",1],\ + [\"0xed99c5acb25eedf5\",3]],\"transactionVersion\":1,\"stateVersion\":1}"; let runtime_version = api.runtime_version(None.into()).unwrap(); let serialized = serde_json::to_string(&runtime_version).unwrap(); - assert_eq!(serialized, result); + pretty_assertions::assert_eq!(serialized, result); let deserialized: RuntimeVersion = serde_json::from_str(result).unwrap(); assert_eq!(deserialized, runtime_version); @@ -501,7 +532,7 @@ async fn should_return_runtime_version() { async fn should_notify_on_runtime_version_initially() { let mut sub = { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client, test_executor(), DenyUnsafe::No, None); + let (api, _child) = new_full(client, test_executor(), DenyUnsafe::No); let api_rpc = api.into_rpc(); let sub = api_rpc @@ -530,7 +561,7 @@ fn should_deserialize_storage_key() { #[tokio::test] async fn wildcard_storage_subscriptions_are_rpc_unsafe() { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client, test_executor(), DenyUnsafe::Yes, None); + let (api, _child) = new_full(client, test_executor(), DenyUnsafe::Yes); let api_rpc = api.into_rpc(); let err = api_rpc.subscribe("state_subscribeStorage", EmptyParams::new()).await; @@ -540,7 +571,7 @@ async fn wildcard_storage_subscriptions_are_rpc_unsafe() { #[tokio::test] async fn concrete_storage_subscriptions_are_rpc_safe() { let client = Arc::new(substrate_test_runtime_client::new()); - let (api, _child) = new_full(client, test_executor(), DenyUnsafe::Yes, None); + let (api, _child) = new_full(client, test_executor(), DenyUnsafe::Yes); let api_rpc = api.into_rpc(); let key = StorageKey(STORAGE_KEY.to_vec()); diff --git a/client/rpc/src/state/utils.rs b/client/rpc/src/state/utils.rs index 81476cdd3426..9fc21b72b81e 100644 --- a/client/rpc/src/state/utils.rs +++ b/client/rpc/src/state/utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2023 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc/src/statement/mod.rs b/client/rpc/src/statement/mod.rs new file mode 100644 index 000000000000..b4f432bbbb0e --- /dev/null +++ b/client/rpc/src/statement/mod.rs @@ -0,0 +1,108 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +//! Substrate statement store API. + +use codec::{Decode, Encode}; +use jsonrpsee::core::{async_trait, RpcResult}; +/// Re-export the API for backward compatibility. +pub use sc_rpc_api::statement::{error::Error, StatementApiServer}; +use sc_rpc_api::DenyUnsafe; +use sp_core::Bytes; +use sp_statement_store::{StatementSource, SubmitResult}; +use std::sync::Arc; + +/// Statement store API +pub struct StatementStore { + store: Arc, + deny_unsafe: DenyUnsafe, +} + +impl StatementStore { + /// Create new instance of Offchain API. + pub fn new( + store: Arc, + deny_unsafe: DenyUnsafe, + ) -> Self { + StatementStore { store, deny_unsafe } + } +} + +#[async_trait] +impl StatementApiServer for StatementStore { + fn dump(&self) -> RpcResult> { + self.deny_unsafe.check_if_safe()?; + + let statements = + self.store.statements().map_err(|e| Error::StatementStore(e.to_string()))?; + Ok(statements.into_iter().map(|(_, s)| s.encode().into()).collect()) + } + + fn broadcasts(&self, match_all_topics: Vec<[u8; 32]>) -> RpcResult> { + Ok(self + .store + .broadcasts(&match_all_topics) + .map_err(|e| Error::StatementStore(e.to_string()))? + .into_iter() + .map(Into::into) + .collect()) + } + + fn posted(&self, match_all_topics: Vec<[u8; 32]>, dest: [u8; 32]) -> RpcResult> { + Ok(self + .store + .posted(&match_all_topics, dest) + .map_err(|e| Error::StatementStore(e.to_string()))? + .into_iter() + .map(Into::into) + .collect()) + } + + fn posted_clear( + &self, + match_all_topics: Vec<[u8; 32]>, + dest: [u8; 32], + ) -> RpcResult> { + Ok(self + .store + .posted_clear(&match_all_topics, dest) + .map_err(|e| Error::StatementStore(e.to_string()))? + .into_iter() + .map(Into::into) + .collect()) + } + + fn submit(&self, encoded: Bytes) -> RpcResult<()> { + let statement = Decode::decode(&mut &*encoded) + .map_err(|e| Error::StatementStore(format!("Eror decoding statement: {:?}", e)))?; + match self.store.submit(statement, StatementSource::Local) { + SubmitResult::New(_) | SubmitResult::Known => Ok(()), + // `KnownExpired` should not happen. Expired statements submitted with + // `StatementSource::Rpc` should be renewed. + SubmitResult::KnownExpired => + Err(Error::StatementStore("Submitted an expired statement.".into()).into()), + SubmitResult::Bad(e) => Err(Error::StatementStore(e.into()).into()), + SubmitResult::Ignored => Err(Error::StatementStore("Store is full.".into()).into()), + SubmitResult::InternalError(e) => Err(Error::StatementStore(e.to_string()).into()), + } + } + + fn remove(&self, hash: [u8; 32]) -> RpcResult<()> { + Ok(self.store.remove(&hash).map_err(|e| Error::StatementStore(e.to_string()))?) + } +} diff --git a/client/rpc/src/system/mod.rs b/client/rpc/src/system/mod.rs index ea24524cd2ea..0da4f8d0e211 100644 --- a/client/rpc/src/system/mod.rs +++ b/client/rpc/src/system/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/rpc/src/system/tests.rs b/client/rpc/src/system/tests.rs index 4da49cdd1a0c..b6bfec7736b0 100644 --- a/client/rpc/src/system/tests.rs +++ b/client/rpc/src/system/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -99,7 +99,7 @@ fn api>>(sync: T) -> RpcModule> { ); }, Request::NetworkAddReservedPeer(peer, sender) => { - let _ = match sc_network_common::config::parse_str_addr(&peer) { + let _ = match sc_network::config::parse_str_addr(&peer) { Ok(_) => sender.send(Ok(())), Err(s) => sender.send(Err(error::Error::MalformattedPeerArg(s.to_string()))), diff --git a/client/rpc/src/testing.rs b/client/rpc/src/testing.rs index 584e4a9901ea..6b6e1906d44d 100644 --- a/client/rpc/src/testing.rs +++ b/client/rpc/src/testing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/service/Cargo.toml b/client/service/Cargo.toml index b4ce3bbbb7f1..4f280503fbbc 100644 --- a/client/service/Cargo.toml +++ b/client/service/Cargo.toml @@ -31,22 +31,22 @@ log = "0.4.17" futures-timer = "3.0.1" exit-future = "0.2.0" pin-project = "1.0.12" -serde = "1.0.136" +serde = "1.0.163" serde_json = "1.0.85" sc-keystore = { version = "4.0.0-dev", path = "../keystore" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } -sp-trie = { version = "7.0.0", path = "../../primitives/trie" } -sp-externalities = { version = "0.13.0", path = "../../primitives/externalities" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } +sp-trie = { version = "22.0.0", path = "../../primitives/trie" } +sp-externalities = { version = "0.19.0", path = "../../primitives/externalities" } sc-utils = { version = "4.0.0-dev", path = "../utils" } -sp-version = { version = "5.0.0", path = "../../primitives/version" } +sp-version = { version = "22.0.0", path = "../../primitives/version" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-keystore = { version = "0.27.0", path = "../../primitives/keystore" } sp-session = { version = "4.0.0-dev", path = "../../primitives/session" } -sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } +sp-state-machine = { version = "0.28.0", path = "../../primitives/state-machine" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } sc-consensus = { version = "0.10.0-dev", path = "../../client/consensus/common" } -sp-storage = { version = "7.0.0", path = "../../primitives/storage" } +sp-storage = { version = "13.0.0", path = "../../primitives/storage" } sc-network = { version = "0.10.0-dev", path = "../network" } sc-network-bitswap = { version = "0.10.0-dev", path = "../network/bitswap" } sc-network-common = { version = "0.10.0-dev", path = "../network/common" } @@ -57,7 +57,7 @@ sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } sc-client-api = { version = "4.0.0-dev", path = "../api" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../db" } -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } sc-executor = { version = "0.10.0-dev", path = "../executor" } sc-transaction-pool = { version = "4.0.0-dev", path = "../transaction-pool" } sp-transaction-pool = { version = "4.0.0-dev", path = "../../primitives/transaction-pool" } @@ -69,11 +69,9 @@ sc-rpc-spec-v2 = { version = "0.10.0-dev", path = "../rpc-spec-v2" } sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sc-informant = { version = "0.10.0-dev", path = "../informant" } sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } -sc-offchain = { version = "4.0.0-dev", path = "../offchain" } prometheus-endpoint = { package = "substrate-prometheus-endpoint", path = "../../utils/prometheus", version = "0.10.0-dev" } sc-tracing = { version = "4.0.0-dev", path = "../tracing" } sc-sysinfo = { version = "6.0.0-dev", path = "../sysinfo" } -sc-storage-monitor = { version = "0.1.0", path = "../storage-monitor" } tracing = "0.1.29" tracing-futures = { version = "0.2.4" } async-trait = "0.1.57" diff --git a/client/service/src/builder.rs b/client/service/src/builder.rs index 0b09f550ce33..6b537e7ee48d 100644 --- a/client/service/src/builder.rs +++ b/client/service/src/builder.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -17,7 +17,7 @@ // along with this program. If not, see . use crate::{ - build_network_future, + build_network_future, build_system_rpc_future, client::{Client, ClientConfig}, config::{Configuration, KeystoreConfig, PrometheusConfig}, error::Error, @@ -36,20 +36,22 @@ use sc_client_api::{ }; use sc_client_db::{Backend, DatabaseSettings}; use sc_consensus::import_queue::ImportQueue; -use sc_executor::RuntimeVersionOf; +use sc_executor::{ + sp_wasm_interface::HostFunctions, HeapAllocStrategy, NativeElseWasmExecutor, + NativeExecutionDispatch, RuntimeVersionOf, WasmExecutor, DEFAULT_HEAP_ALLOC_STRATEGY, +}; use sc_keystore::LocalKeystore; -use sc_network::{config::SyncMode, NetworkService}; -use sc_network_bitswap::BitswapRequestHandler; -use sc_network_common::{ - protocol::role::Roles, - service::{NetworkStateInfo, NetworkStatusProvider}, - sync::warp::WarpSyncProvider, +use sc_network::{ + config::{FullNetworkConfiguration, SyncMode}, + NetworkService, NetworkStateInfo, NetworkStatusProvider, }; +use sc_network_bitswap::BitswapRequestHandler; +use sc_network_common::{role::Roles, sync::warp::WarpSyncParams}; use sc_network_light::light_client_requests::handler::LightClientRequestHandler; use sc_network_sync::{ - block_request_handler::BlockRequestHandler, service::network::NetworkServiceProvider, - state_request_handler::StateRequestHandler, - warp_request_handler::RequestHandler as WarpSyncRequestHandler, ChainSync, + block_request_handler::BlockRequestHandler, engine::SyncingEngine, + service::network::NetworkServiceProvider, state_request_handler::StateRequestHandler, + warp_request_handler::RequestHandler as WarpSyncRequestHandler, SyncingService, }; use sc_rpc::{ author::AuthorApiServer, @@ -61,7 +63,7 @@ use sc_rpc::{ }; use sc_rpc_spec_v2::{chain_head::ChainHeadApiServer, transaction::TransactionApiServer}; use sc_telemetry::{telemetry, ConnectionMessage, Telemetry, TelemetryHandle, SUBSTRATE_INFO}; -use sc_transaction_pool_api::MaintainedTransactionPool; +use sc_transaction_pool_api::{MaintainedTransactionPool, TransactionPool}; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender}; use sp_api::{CallApiAt, ProvideRuntimeApi}; use sp_blockchain::{HeaderBackend, HeaderMetadata}; @@ -69,46 +71,29 @@ use sp_consensus::block_validation::{ BlockAnnounceValidator, Chain, DefaultBlockAnnounceValidator, }; use sp_core::traits::{CodeExecutor, SpawnNamed}; -use sp_keystore::{CryptoStore, SyncCryptoStore, SyncCryptoStorePtr}; +use sp_keystore::KeystorePtr; use sp_runtime::traits::{Block as BlockT, BlockIdTo, NumberFor, Zero}; -use std::{str::FromStr, sync::Arc, time::SystemTime}; +use std::{ + str::FromStr, + sync::Arc, + time::{Duration, SystemTime}, +}; /// Full client type. pub type TFullClient = Client, TFullCallExecutor, TBl, TRtApi>; /// Full client backend type. -pub type TFullBackend = sc_client_db::Backend; +pub type TFullBackend = Backend; /// Full client call executor type. -pub type TFullCallExecutor = - crate::client::LocalCallExecutor, TExec>; +pub type TFullCallExecutor = crate::client::LocalCallExecutor, TExec>; type TFullParts = (TFullClient, Arc>, KeystoreContainer, TaskManager); -trait AsCryptoStoreRef { - fn keystore_ref(&self) -> Arc; - fn sync_keystore_ref(&self) -> Arc; -} - -impl AsCryptoStoreRef for Arc -where - T: CryptoStore + SyncCryptoStore + 'static, -{ - fn keystore_ref(&self) -> Arc { - self.clone() - } - fn sync_keystore_ref(&self) -> Arc { - self.clone() - } -} - -/// Construct and hold different layers of Keystore wrappers -pub struct KeystoreContainer { - remote: Option>, - local: Arc, -} +/// Construct a local keystore shareable container +pub struct KeystoreContainer(Arc); impl KeystoreContainer { /// Construct KeystoreContainer @@ -119,50 +104,17 @@ impl KeystoreContainer { KeystoreConfig::InMemory => LocalKeystore::in_memory(), }); - Ok(Self { remote: Default::default(), local: keystore }) + Ok(Self(keystore)) } - /// Set the remote keystore. - /// Should be called right away at startup and not at runtime: - /// even though this overrides any previously set remote store, it - /// does not reset any references previously handed out - they will - /// stick around. - pub fn set_remote_keystore(&mut self, remote: Arc) - where - T: CryptoStore + SyncCryptoStore + 'static, - { - self.remote = Some(Box::new(remote)) + /// Returns a shared reference to a dynamic `Keystore` trait implementation. + pub fn keystore(&self) -> KeystorePtr { + self.0.clone() } - /// Returns an adapter to the asynchronous keystore that implements `CryptoStore` - pub fn keystore(&self) -> Arc { - if let Some(c) = self.remote.as_ref() { - c.keystore_ref() - } else { - self.local.clone() - } - } - - /// Returns the synchronous keystore wrapper - pub fn sync_keystore(&self) -> SyncCryptoStorePtr { - if let Some(c) = self.remote.as_ref() { - c.sync_keystore_ref() - } else { - self.local.clone() as SyncCryptoStorePtr - } - } - - /// Returns the local keystore if available - /// - /// The function will return None if the available keystore is not a local keystore. - /// - /// # Note - /// - /// Using the [`LocalKeystore`] will result in loosing the ability to use any other keystore - /// implementation, like a remote keystore for example. Only use this if you a certain that you - /// require it! - pub fn local_keystore(&self) -> Option> { - Some(self.local.clone()) + /// Returns a shared reference to the local keystore . + pub fn local_keystore(&self) -> Arc { + self.0.clone() } } @@ -235,9 +187,8 @@ where let client = { let extensions = sc_client_api::execution_extensions::ExecutionExtensions::new( - config.execution_strategies.clone(), - Some(keystore_container.sync_keystore()), - sc_offchain::OffchainDb::factory_from_backend(&*backend), + None, + Arc::new(executor.clone()), ); let wasm_runtime_substitutes = config @@ -273,7 +224,7 @@ where wasm_runtime_overrides: config.wasm_runtime_overrides.clone(), no_genesis: matches!( config.network.sync_mode, - SyncMode::Fast { .. } | SyncMode::Warp { .. } + SyncMode::LightState { .. } | SyncMode::Warp { .. } ), wasm_runtime_substitutes, }, @@ -285,6 +236,27 @@ where Ok((client, backend, keystore_container, task_manager)) } +/// Creates a [`NativeElseWasmExecutor`] according to [`Configuration`]. +pub fn new_native_or_wasm_executor( + config: &Configuration, +) -> NativeElseWasmExecutor { + NativeElseWasmExecutor::new_with_wasm_executor(new_wasm_executor(config)) +} + +/// Creates a [`WasmExecutor`] according to [`Configuration`]. +pub fn new_wasm_executor(config: &Configuration) -> WasmExecutor { + let strategy = config + .default_heap_pages + .map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |p| HeapAllocStrategy::Static { extra_pages: p as _ }); + WasmExecutor::::builder() + .with_execution_method(config.wasm_method) + .with_onchain_heap_alloc_strategy(strategy) + .with_offchain_heap_alloc_strategy(strategy) + .with_max_runtime_instances(config.max_runtime_instances) + .with_runtime_cache_size(config.runtime_cache_size) + .build() +} + /// Create an instance of default DB-backend backend. pub fn new_db_backend( settings: DatabaseSettings, @@ -310,7 +282,7 @@ pub fn new_client( telemetry: Option, config: ClientConfig, ) -> Result< - crate::client::Client< + Client< Backend, crate::client::LocalCallExecutor, E>, Block, @@ -329,12 +301,11 @@ where let executor = crate::client::LocalCallExecutor::new( backend.clone(), executor, - spawn_handle.clone(), config.clone(), execution_extensions, )?; - crate::client::Client::new( + Client::new( backend, executor, spawn_handle, @@ -349,24 +320,14 @@ where /// Shared network instance implementing a set of mandatory traits. pub trait SpawnTaskNetwork: - sc_offchain::NetworkProvider - + NetworkStateInfo - + NetworkStatusProvider - + Send - + Sync - + 'static + NetworkStateInfo + NetworkStatusProvider + Send + Sync + 'static { } impl SpawnTaskNetwork for T where Block: BlockT, - T: sc_offchain::NetworkProvider - + NetworkStateInfo - + NetworkStatusProvider - + Send - + Sync - + 'static, + T: NetworkStateInfo + NetworkStatusProvider + Send + Sync + 'static, { } @@ -381,7 +342,7 @@ pub struct SpawnTasksParams<'a, TBl: BlockT, TCl, TExPool, TRpc, Backend> { /// A task manager returned by `new_full_parts`. pub task_manager: &'a mut TaskManager, /// A shared keystore returned by `new_full_parts`. - pub keystore: SyncCryptoStorePtr, + pub keystore: KeystorePtr, /// A shared transaction pool. pub transaction_pool: Arc, /// Builds additional [`RpcModule`]s that should be added to the server @@ -394,42 +355,12 @@ pub struct SpawnTasksParams<'a, TBl: BlockT, TCl, TExPool, TRpc, Backend> { /// Controller for transactions handlers pub tx_handler_controller: sc_network_transactions::TransactionsHandlerController<::Hash>, + /// Syncing service. + pub sync_service: Arc>, /// Telemetry instance for this node. pub telemetry: Option<&'a mut Telemetry>, } -/// Build a shared offchain workers instance. -pub fn build_offchain_workers( - config: &Configuration, - spawn_handle: SpawnTaskHandle, - client: Arc, - network: Arc, -) -> Option>> -where - TBl: BlockT, - TCl: Send + Sync + ProvideRuntimeApi + BlockchainEvents + 'static, - >::Api: sc_offchain::OffchainWorkerApi, -{ - let offchain_workers = Some(Arc::new(sc_offchain::OffchainWorkers::new(client.clone()))); - - // Inform the offchain worker about new imported blocks - if let Some(offchain) = offchain_workers.clone() { - spawn_handle.spawn( - "offchain-notifications", - Some("offchain-worker"), - sc_offchain::notification_future( - config.role.is_authority(), - client, - offchain, - Clone::clone(&spawn_handle), - network, - ), - ); - } - - offchain_workers -} - /// Spawn the tasks that are required to run a node. pub fn spawn_tasks( params: SpawnTasksParams, @@ -450,7 +381,6 @@ where + Send + 'static, >::Api: sp_api::Metadata - + sc_offchain::OffchainWorkerApi + sp_transaction_pool::runtime_api::TaggedTransactionQueue + sp_session::SessionKeys + sp_api::ApiExt, @@ -471,6 +401,7 @@ where network, system_rpc_tx, tx_handler_controller, + sync_service, telemetry, } = params; @@ -480,6 +411,7 @@ where client.clone(), chain_info.best_hash, config.dev_key_seed.clone().map(|s| vec![s]).unwrap_or_default(), + keystore.clone(), ) .map_err(|e| Error::Application(Box::new(e)))?; @@ -533,7 +465,12 @@ where spawn_handle.spawn( "telemetry-periodic-send", None, - metrics_service.run(client.clone(), transaction_pool.clone(), network.clone()), + metrics_service.run( + client.clone(), + transaction_pool.clone(), + network.clone(), + sync_service.clone(), + ), ); let rpc_id_provider = config.rpc_id_provider.take(); @@ -560,7 +497,12 @@ where spawn_handle.spawn( "informant", None, - sc_informant::build(client.clone(), network, config.informant_output_format), + sc_informant::build( + client.clone(), + network, + sync_service.clone(), + config.informant_output_format, + ), ); task_manager.keep_alive((config.base_path, rpc)); @@ -639,7 +581,7 @@ fn gen_rpc_module( spawn_handle: SpawnTaskHandle, client: Arc, transaction_pool: Arc, - keystore: SyncCryptoStorePtr, + keystore: KeystorePtr, system_rpc_tx: TracingUnboundedSender>, config: &Configuration, backend: Arc, @@ -678,12 +620,8 @@ where let (chain, state, child_state) = { let chain = sc_rpc::chain::new_full(client.clone(), task_executor.clone()).into_rpc(); - let (state, child_state) = sc_rpc::state::new_full( - client.clone(), - task_executor.clone(), - deny_unsafe, - config.rpc_max_payload, - ); + let (state, child_state) = + sc_rpc::state::new_full(client.clone(), task_executor.clone(), deny_unsafe); let state = state.into_rpc(); let child_state = child_state.into_rpc(); @@ -697,16 +635,24 @@ where ) .into_rpc(); - // Maximum pinned blocks per connection. - // This number is large enough to consider immediate blocks, - // but it will change to facilitate adequate limits for the pinning API. - const MAX_PINNED_BLOCKS: usize = 4096; + // Maximum pinned blocks across all connections. + // This number is large enough to consider immediate blocks. + // Note: This should never exceed the `PINNING_CACHE_SIZE` from client/db. + const MAX_PINNED_BLOCKS: usize = 512; + + // Any block of any subscription should not be pinned more than + // this constant. When a subscription contains a block older than this, + // the subscription becomes subject to termination. + // Note: This should be enough for immediate blocks. + const MAX_PINNED_SECONDS: u64 = 60; + let chain_head_v2 = sc_rpc_spec_v2::chain_head::ChainHead::new( client.clone(), backend.clone(), task_executor.clone(), client.info().genesis_hash, MAX_PINNED_BLOCKS, + Duration::from_secs(MAX_PINNED_SECONDS), ) .into_rpc(); @@ -748,6 +694,8 @@ where pub struct BuildNetworkParams<'a, TBl: BlockT, TExPool, TImpQu, TCl> { /// The service configuration. pub config: &'a Configuration, + /// Full network configuration. + pub net_config: FullNetworkConfiguration, /// A shared client returned by `new_full_parts`. pub client: Arc, /// A shared transaction pool. @@ -759,9 +707,10 @@ pub struct BuildNetworkParams<'a, TBl: BlockT, TExPool, TImpQu, TCl> { /// A block announce validator builder. pub block_announce_validator_builder: Option) -> Box + Send> + Send>>, - /// An optional warp sync provider. - pub warp_sync: Option>>, + /// Optional warp sync params. + pub warp_sync_params: Option>, } + /// Build the network service, the network status sinks and an RPC sender. pub fn build_network( params: BuildNetworkParams, @@ -771,6 +720,7 @@ pub fn build_network( TracingUnboundedSender>, sc_network_transactions::TransactionsHandlerController<::Hash>, NetworkStarter, + Arc>, ), Error, > @@ -785,28 +735,28 @@ where + HeaderBackend + BlockchainEvents + 'static, - TExPool: MaintainedTransactionPool::Hash> + 'static, + TExPool: TransactionPool::Hash> + 'static, TImpQu: ImportQueue + 'static, { let BuildNetworkParams { config, + mut net_config, client, transaction_pool, spawn_handle, import_queue, block_announce_validator_builder, - warp_sync, + warp_sync_params, } = params; - let mut request_response_protocol_configs = Vec::new(); - - if warp_sync.is_none() && config.network.sync_mode.is_warp() { + if warp_sync_params.is_none() && config.network.sync_mode.is_warp() { return Err("Warp sync enabled, but no warp sync provider configured.".into()) } if client.requires_full_sync() { match config.network.sync_mode { - SyncMode::Fast { .. } => return Err("Fast sync doesn't work for archive nodes".into()), + SyncMode::LightState { .. } => + return Err("Fast sync doesn't work for archive nodes".into()), SyncMode::Warp => return Err("Warp sync doesn't work for archive nodes".into()), SyncMode::Full => {}, } @@ -820,33 +770,36 @@ where Box::new(DefaultBlockAnnounceValidator) }; - let block_request_protocol_config = { + let (block_request_protocol_config, block_request_protocol_name) = { // Allow both outgoing and incoming requests. let (handler, protocol_config) = BlockRequestHandler::new( &protocol_id, config.chain_spec.fork_id(), client.clone(), - config.network.default_peers_set.in_peers as usize + - config.network.default_peers_set.out_peers as usize, + net_config.network_config.default_peers_set.in_peers as usize + + net_config.network_config.default_peers_set.out_peers as usize, ); + let config_name = protocol_config.name.clone(); spawn_handle.spawn("block-request-handler", Some("networking"), handler.run()); - protocol_config + (protocol_config, config_name) }; - let state_request_protocol_config = { + let (state_request_protocol_config, state_request_protocol_name) = { // Allow both outgoing and incoming requests. let (handler, protocol_config) = StateRequestHandler::new( &protocol_id, config.chain_spec.fork_id(), client.clone(), - config.network.default_peers_set_num_full as usize, + net_config.network_config.default_peers_set_num_full as usize, ); + let config_name = protocol_config.name.clone(); + spawn_handle.spawn("state-request-handler", Some("networking"), handler.run()); - protocol_config + (protocol_config, config_name) }; - let (warp_sync_provider, warp_sync_protocol_config) = warp_sync - .map(|provider| { + let (warp_sync_protocol_config, warp_request_protocol_name) = match warp_sync_params.as_ref() { + Some(WarpSyncParams::WithProvider(warp_with_provider)) => { // Allow both outgoing and incoming requests. let (handler, protocol_config) = WarpSyncRequestHandler::new( protocol_id.clone(), @@ -856,12 +809,15 @@ where .flatten() .expect("Genesis block exists; qed"), config.chain_spec.fork_id(), - provider.clone(), + warp_with_provider.clone(), ); + let config_name = protocol_config.name.clone(); + spawn_handle.spawn("warp-sync-request-handler", Some("networking"), handler.run()); - (Some(provider), Some(protocol_config)) - }) - .unwrap_or_default(); + (Some(protocol_config), Some(config_name)) + }, + _ => (None, None), + }; let light_client_request_protocol_config = { // Allow both outgoing and incoming requests. @@ -874,36 +830,57 @@ where protocol_config }; + // install request handlers to `FullNetworkConfiguration` + net_config.add_request_response_protocol(block_request_protocol_config); + net_config.add_request_response_protocol(state_request_protocol_config); + net_config.add_request_response_protocol(light_client_request_protocol_config); + + if let Some(config) = warp_sync_protocol_config { + net_config.add_request_response_protocol(config); + } + + if config.network.ipfs_server { + let (handler, protocol_config) = BitswapRequestHandler::new(client.clone()); + spawn_handle.spawn("bitswap-request-handler", Some("networking"), handler.run()); + net_config.add_request_response_protocol(protocol_config); + } + + // create transactions protocol and add it to the list of supported protocols of + // `network_params` + let transactions_handler_proto = sc_network_transactions::TransactionsHandlerPrototype::new( + protocol_id.clone(), + client + .block_hash(0u32.into()) + .ok() + .flatten() + .expect("Genesis block exists; qed"), + config.chain_spec.fork_id(), + ); + net_config.add_notification_protocol(transactions_handler_proto.set_config()); + + let (tx, rx) = sc_utils::mpsc::tracing_unbounded("mpsc_syncing_engine_protocol", 100_000); let (chain_sync_network_provider, chain_sync_network_handle) = NetworkServiceProvider::new(); - let (chain_sync, chain_sync_service, block_announce_config) = ChainSync::new( - match config.network.sync_mode { - SyncMode::Full => sc_network_common::sync::SyncMode::Full, - SyncMode::Fast { skip_proofs, storage_chain_mode } => - sc_network_common::sync::SyncMode::LightState { skip_proofs, storage_chain_mode }, - SyncMode::Warp => sc_network_common::sync::SyncMode::Warp, - }, + let (engine, sync_service, block_announce_config) = SyncingEngine::new( + Roles::from(&config.role), client.clone(), + config.prometheus_config.as_ref().map(|config| config.registry.clone()).as_ref(), + &net_config, protocol_id.clone(), &config.chain_spec.fork_id().map(ToOwned::to_owned), - Roles::from(&config.role), block_announce_validator, - config.network.max_parallel_downloads, - warp_sync_provider, - config.prometheus_config.as_ref().map(|config| config.registry.clone()).as_ref(), + warp_sync_params, chain_sync_network_handle, import_queue.service(), - block_request_protocol_config.name.clone(), - state_request_protocol_config.name.clone(), - warp_sync_protocol_config.as_ref().map(|config| config.name.clone()), + block_request_protocol_name, + state_request_protocol_name, + warp_request_protocol_name, + rx, )?; + let sync_service_import_queue = sync_service.clone(); + let sync_service = Arc::new(sync_service); - request_response_protocol_configs.push(config.network.ipfs_server.then(|| { - let (handler, protocol_config) = BitswapRequestHandler::new(client.clone()); - spawn_handle.spawn("bitswap-request-handler", Some("networking"), handler.run()); - protocol_config - })); - - let mut network_params = sc_network::config::Params { + let genesis_hash = client.hash(Zero::zero()).ok().flatten().expect("Genesis block exists; qed"); + let network_params = sc_network::config::Params:: { role: config.role.clone(), executor: { let spawn_handle = Clone::clone(&spawn_handle); @@ -911,70 +888,52 @@ where spawn_handle.spawn("libp2p-node", Some("networking"), fut); }) }, - network_config: config.network.clone(), - chain: client.clone(), + network_config: net_config, + genesis_hash, protocol_id: protocol_id.clone(), fork_id: config.chain_spec.fork_id().map(ToOwned::to_owned), - chain_sync: Box::new(chain_sync), - chain_sync_service: Box::new(chain_sync_service.clone()), metrics_registry: config.prometheus_config.as_ref().map(|config| config.registry.clone()), block_announce_config, - request_response_protocol_configs: request_response_protocol_configs - .into_iter() - .chain([ - Some(block_request_protocol_config), - Some(state_request_protocol_config), - Some(light_client_request_protocol_config), - warp_sync_protocol_config, - ]) - .flatten() - .collect::>(), + tx, }; - // crate transactions protocol and add it to the list of supported protocols of `network_params` - let transactions_handler_proto = sc_network_transactions::TransactionsHandlerPrototype::new( - protocol_id.clone(), - client - .block_hash(0u32.into()) - .ok() - .flatten() - .expect("Genesis block exists; qed"), - config.chain_spec.fork_id(), - ); - network_params - .network_config - .extra_sets - .insert(0, transactions_handler_proto.set_config()); - - let has_bootnodes = !network_params.network_config.boot_nodes.is_empty(); + let has_bootnodes = !network_params.network_config.network_config.boot_nodes.is_empty(); let network_mut = sc_network::NetworkWorker::new(network_params)?; let network = network_mut.service().clone(); let (tx_handler, tx_handler_controller) = transactions_handler_proto.build( network.clone(), + sync_service.clone(), Arc::new(TransactionPoolAdapter { pool: transaction_pool, client: client.clone() }), config.prometheus_config.as_ref().map(|config| &config.registry), )?; - spawn_handle.spawn("network-transactions-handler", Some("networking"), tx_handler.run()); - spawn_handle.spawn( + + spawn_handle.spawn_blocking( "chain-sync-network-service-provider", Some("networking"), chain_sync_network_provider.run(network.clone()), ); - spawn_handle.spawn("import-queue", None, import_queue.run(Box::new(chain_sync_service))); + spawn_handle.spawn("import-queue", None, import_queue.run(Box::new(sync_service_import_queue))); + spawn_handle.spawn_blocking("syncing", None, engine.run()); let (system_rpc_tx, system_rpc_rx) = tracing_unbounded("mpsc_system_rpc", 10_000); - - let future = build_network_future( - config.role.clone(), - network_mut, - client, - system_rpc_rx, - has_bootnodes, - config.announce_block, + spawn_handle.spawn( + "system-rpc-handler", + Some("networking"), + build_system_rpc_future( + config.role.clone(), + network_mut.service().clone(), + sync_service.clone(), + client.clone(), + system_rpc_rx, + has_bootnodes, + ), ); + let future = + build_network_future(network_mut, client, sync_service.clone(), config.announce_block); + // TODO: Normally, one is supposed to pass a list of notifications protocols supported by the // node through the `NetworkConfiguration` struct. But because this function doesn't know in // advance which components, such as GrandPa or Polkadot, will be plugged on top of the @@ -1011,7 +970,13 @@ where future.await }); - Ok((network, system_rpc_tx, tx_handler_controller, NetworkStarter(network_start_tx))) + Ok(( + network, + system_rpc_tx, + tx_handler_controller, + NetworkStarter(network_start_tx), + sync_service.clone(), + )) } /// Object used to start the network. diff --git a/client/service/src/chain_ops/check_block.rs b/client/service/src/chain_ops/check_block.rs index c3380e5476ab..a14f535b9b36 100644 --- a/client/service/src/chain_ops/check_block.rs +++ b/client/service/src/chain_ops/check_block.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/service/src/chain_ops/export_blocks.rs b/client/service/src/chain_ops/export_blocks.rs index 9638722ef6b2..8d66f1f96baf 100644 --- a/client/service/src/chain_ops/export_blocks.rs +++ b/client/service/src/chain_ops/export_blocks.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/service/src/chain_ops/export_raw_state.rs b/client/service/src/chain_ops/export_raw_state.rs index ca7a070086f4..fde2c5617cb4 100644 --- a/client/service/src/chain_ops/export_raw_state.rs +++ b/client/service/src/chain_ops/export_raw_state.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -21,7 +21,10 @@ use sc_client_api::{StorageProvider, UsageProvider}; use sp_core::storage::{well_known_keys, ChildInfo, Storage, StorageChild, StorageKey, StorageMap}; use sp_runtime::traits::Block as BlockT; -use std::{collections::HashMap, sync::Arc}; +use std::{ + collections::{BTreeMap, HashMap}, + sync::Arc, +}; /// Export the raw state at the given `block`. If `block` is `None`, the /// best block will be used. @@ -31,35 +34,30 @@ where B: BlockT, BA: sc_client_api::backend::Backend, { - let empty_key = StorageKey(Vec::new()); - let mut top_storage = client.storage_pairs(hash, &empty_key)?; + let mut top = BTreeMap::new(); let mut children_default = HashMap::new(); - // Remove all default child storage roots from the top storage and collect the child storage - // pairs. - while let Some(pos) = top_storage - .iter() - .position(|(k, _)| k.0.starts_with(well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX)) - { - let (key, _) = top_storage.swap_remove(pos); - - let key = - StorageKey(key.0[well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX.len()..].to_vec()); - let child_info = ChildInfo::new_default(&key.0); - - let keys = client.child_storage_keys(hash, &child_info, &empty_key)?; - let mut pairs = StorageMap::new(); - keys.into_iter().try_for_each(|k| { - if let Some(value) = client.child_storage(hash, &child_info, &k)? { - pairs.insert(k.0, value.0); + for (key, value) in client.storage_pairs(hash, None, None)? { + // Remove all default child storage roots from the top storage and collect the child storage + // pairs. + if key.0.starts_with(well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX) { + let child_root_key = StorageKey( + key.0[well_known_keys::DEFAULT_CHILD_STORAGE_KEY_PREFIX.len()..].to_vec(), + ); + let child_info = ChildInfo::new_default(&child_root_key.0); + let mut pairs = StorageMap::new(); + for child_key in client.child_storage_keys(hash, child_info.clone(), None, None)? { + if let Some(child_value) = client.child_storage(hash, &child_info, &child_key)? { + pairs.insert(child_key.0, child_value.0); + } } - Ok::<_, Error>(()) - })?; + children_default.insert(child_root_key.0, StorageChild { child_info, data: pairs }); + continue + } - children_default.insert(key.0, StorageChild { child_info, data: pairs }); + top.insert(key.0, value.0); } - let top = top_storage.into_iter().map(|(k, v)| (k.0, v.0)).collect(); Ok(Storage { top, children_default }) } diff --git a/client/service/src/chain_ops/import_blocks.rs b/client/service/src/chain_ops/import_blocks.rs index ca09c1658d72..34f7669d0106 100644 --- a/client/service/src/chain_ops/import_blocks.rs +++ b/client/service/src/chain_ops/import_blocks.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/service/src/chain_ops/mod.rs b/client/service/src/chain_ops/mod.rs index 98245eeb98d0..9ee6848ad4b1 100644 --- a/client/service/src/chain_ops/mod.rs +++ b/client/service/src/chain_ops/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/service/src/chain_ops/revert_chain.rs b/client/service/src/chain_ops/revert_chain.rs index 3ee4399d063b..f3c344a75c5b 100644 --- a/client/service/src/chain_ops/revert_chain.rs +++ b/client/service/src/chain_ops/revert_chain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/service/src/client/block_rules.rs b/client/service/src/client/block_rules.rs index 2ed27b8fe1b6..532cde1ae78f 100644 --- a/client/service/src/client/block_rules.rs +++ b/client/service/src/client/block_rules.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/service/src/client/call_executor.rs b/client/service/src/client/call_executor.rs index 7906d62240e0..facde72321db 100644 --- a/client/service/src/client/call_executor.rs +++ b/client/service/src/client/call_executor.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -21,12 +21,11 @@ use sc_client_api::{ backend, call_executor::CallExecutor, execution_extensions::ExecutionExtensions, HeaderBackend, }; use sc_executor::{RuntimeVersion, RuntimeVersionOf}; -use sp_api::{ExecutionContext, ProofRecorder, StorageTransactionCache}; -use sp_core::traits::{CodeExecutor, RuntimeCode, SpawnNamed}; +use sp_api::{ProofRecorder, StorageTransactionCache}; +use sp_core::traits::{CallContext, CodeExecutor, RuntimeCode}; +use sp_externalities::Extensions; use sp_runtime::{generic::BlockId, traits::Block as BlockT}; -use sp_state_machine::{ - backend::AsTrieBackend, ExecutionStrategy, Ext, OverlayedChanges, StateMachine, StorageProof, -}; +use sp_state_machine::{backend::AsTrieBackend, Ext, OverlayedChanges, StateMachine, StorageProof}; use std::{cell::RefCell, sync::Arc}; /// Call executor that executes methods locally, querying all required @@ -36,7 +35,6 @@ pub struct LocalCallExecutor { executor: E, wasm_override: Arc>, wasm_substitutes: WasmSubstitutes, - spawn_handle: Box, execution_extensions: Arc>, } @@ -49,7 +47,6 @@ where pub fn new( backend: Arc, executor: E, - spawn_handle: Box, client_config: ClientConfig, execution_extensions: ExecutionExtensions, ) -> sp_blockchain::Result { @@ -69,7 +66,6 @@ where backend, executor, wasm_override: Arc::new(wasm_override), - spawn_handle, wasm_substitutes, execution_extensions: Arc::new(execution_extensions), }) @@ -81,13 +77,14 @@ where fn check_override<'a>( &'a self, onchain_code: RuntimeCode<'a>, - hash: ::Hash, + state: &B::State, + hash: Block::Hash, ) -> sp_blockchain::Result<(RuntimeCode<'a>, RuntimeVersion)> where Block: BlockT, B: backend::Backend, { - let on_chain_version = self.on_chain_runtime_version(hash)?; + let on_chain_version = self.on_chain_runtime_version(&onchain_code, state)?; let code_and_version = if let Some(d) = self.wasm_override.as_ref().as_ref().and_then(|o| { o.get( &on_chain_version.spec_version, @@ -115,17 +112,18 @@ where } /// Returns the on chain runtime version. - fn on_chain_runtime_version(&self, hash: Block::Hash) -> sp_blockchain::Result { + fn on_chain_runtime_version( + &self, + code: &RuntimeCode, + state: &B::State, + ) -> sp_blockchain::Result { let mut overlay = OverlayedChanges::default(); - let state = self.backend.state_at(hash)?; let mut cache = StorageTransactionCache::::default(); - let mut ext = Ext::new(&mut overlay, &mut cache, &state, None); - let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(&state); - let runtime_code = - state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?; + let mut ext = Ext::new(&mut overlay, &mut cache, state, None); + self.executor - .runtime_version(&mut ext, &runtime_code) + .runtime_version(&mut ext, code) .map_err(|e| sp_blockchain::Error::VersionInvalid(e.to_string())) } } @@ -139,7 +137,6 @@ where backend: self.backend.clone(), executor: self.executor.clone(), wasm_override: self.wasm_override.clone(), - spawn_handle: self.spawn_handle.clone(), wasm_substitutes: self.wasm_substitutes.clone(), execution_extensions: self.execution_extensions.clone(), } @@ -165,7 +162,7 @@ where at_hash: Block::Hash, method: &str, call_data: &[u8], - strategy: ExecutionStrategy, + context: CallContext, ) -> sp_blockchain::Result> { let mut changes = OverlayedChanges::default(); let at_number = @@ -176,13 +173,9 @@ where let runtime_code = state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?; - let runtime_code = self.check_override(runtime_code, at_hash)?.0; + let runtime_code = self.check_override(runtime_code, &state, at_hash)?.0; - let extensions = self.execution_extensions.extensions( - at_hash, - at_number, - ExecutionContext::OffchainCall(None), - ); + let mut extensions = self.execution_extensions.extensions(at_hash, at_number); let mut sm = StateMachine::new( &state, @@ -190,14 +183,13 @@ where &self.executor, method, call_data, - extensions, + &mut extensions, &runtime_code, - self.spawn_handle.clone(), + context, ) .set_parent_hash(at_hash); - sm.execute_using_consensus_failure_handler(strategy.get_manager()) - .map_err(Into::into) + sm.execute().map_err(Into::into) } fn contextual_call( @@ -208,17 +200,13 @@ where changes: &RefCell, storage_transaction_cache: Option<&RefCell>>, recorder: &Option>, - context: ExecutionContext, + call_context: CallContext, + extensions: &RefCell, ) -> Result, sp_blockchain::Error> { let mut storage_transaction_cache = storage_transaction_cache.map(|c| c.borrow_mut()); - let at_number = - self.backend.blockchain().expect_block_number_from_id(&BlockId::Hash(at_hash))?; let state = self.backend.state_at(at_hash)?; - let (execution_manager, extensions) = - self.execution_extensions.manager_and_extensions(at_hash, at_number, context); - let changes = &mut *changes.borrow_mut(); // It is important to extract the runtime code here before we create the proof @@ -228,7 +216,8 @@ where let runtime_code = state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?; - let runtime_code = self.check_override(runtime_code, at_hash)?.0; + let runtime_code = self.check_override(runtime_code, &state, at_hash)?.0; + let mut extensions = extensions.borrow_mut(); match recorder { Some(recorder) => { @@ -244,13 +233,13 @@ where &self.executor, method, call_data, - extensions, + &mut extensions, &runtime_code, - self.spawn_handle.clone(), + call_context, ) .with_storage_transaction_cache(storage_transaction_cache.as_deref_mut()) .set_parent_hash(at_hash); - state_machine.execute_using_consensus_failure_handler(execution_manager) + state_machine.execute() }, None => { let mut state_machine = StateMachine::new( @@ -259,13 +248,13 @@ where &self.executor, method, call_data, - extensions, + &mut extensions, &runtime_code, - self.spawn_handle.clone(), + call_context, ) .with_storage_transaction_cache(storage_transaction_cache.as_deref_mut()) .set_parent_hash(at_hash); - state_machine.execute_using_consensus_failure_handler(execution_manager) + state_machine.execute() }, } .map_err(Into::into) @@ -277,7 +266,7 @@ where let runtime_code = state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?; - self.check_override(runtime_code, at_hash).map(|(_, v)| v) + self.check_override(runtime_code, &state, at_hash).map(|(_, v)| v) } fn prove_execution( @@ -295,21 +284,16 @@ where let state_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(trie_backend); let runtime_code = state_runtime_code.runtime_code().map_err(sp_blockchain::Error::RuntimeCode)?; - let runtime_code = self.check_override(runtime_code, at_hash)?.0; + let runtime_code = self.check_override(runtime_code, &state, at_hash)?.0; sp_state_machine::prove_execution_on_trie_backend( trie_backend, &mut Default::default(), &self.executor, - self.spawn_handle.clone(), method, call_data, &runtime_code, - self.execution_extensions.extensions( - at_hash, - at_number, - ExecutionContext::OffchainCall(None), - ), + &mut self.execution_extensions.extensions(at_hash, at_number), ) .map_err(Into::into) } @@ -356,7 +340,7 @@ mod tests { use super::*; use backend::Backend; use sc_client_api::in_mem; - use sc_executor::{NativeElseWasmExecutor, WasmExecutionMethod}; + use sc_executor::{NativeElseWasmExecutor, WasmExecutor}; use sp_core::{ testing::TaskExecutor, traits::{FetchRuntimeCode, WrappedRuntimeCode}, @@ -364,14 +348,18 @@ mod tests { use std::collections::HashMap; use substrate_test_runtime_client::{runtime, GenesisInit, LocalExecutorDispatch}; + fn executor() -> NativeElseWasmExecutor { + NativeElseWasmExecutor::new_with_wasm_executor( + WasmExecutor::builder() + .with_max_runtime_instances(1) + .with_runtime_cache_size(2) + .build(), + ) + } + #[test] fn should_get_override_if_exists() { - let executor = NativeElseWasmExecutor::::new( - WasmExecutionMethod::Interpreted, - Some(128), - 1, - 2, - ); + let executor = executor(); let overrides = crate::client::wasm_override::dummy_overrides(); let onchain_code = WrappedRuntimeCode(substrate_test_runtime::wasm_binary_unwrap().into()); @@ -401,7 +389,6 @@ mod tests { backend.clone(), executor.clone(), genesis_block_builder, - None, Box::new(TaskExecutor::new()), None, None, @@ -413,7 +400,6 @@ mod tests { backend: backend.clone(), executor: executor.clone(), wasm_override: Arc::new(Some(overrides)), - spawn_handle: Box::new(TaskExecutor::new()), wasm_substitutes: WasmSubstitutes::new( Default::default(), executor.clone(), @@ -421,14 +407,17 @@ mod tests { ) .unwrap(), execution_extensions: Arc::new(ExecutionExtensions::new( - Default::default(), - None, None, + Arc::new(executor.clone()), )), }; let check = call_executor - .check_override(onchain_code, backend.blockchain().info().genesis_hash) + .check_override( + onchain_code, + &backend.state_at(backend.blockchain().info().genesis_hash).unwrap(), + backend.blockchain().info().genesis_hash, + ) .expect("RuntimeCode override") .0; @@ -439,12 +428,7 @@ mod tests { fn returns_runtime_version_from_substitute() { const SUBSTITUTE_SPEC_NAME: &str = "substitute-spec-name-cool"; - let executor = NativeElseWasmExecutor::::new( - WasmExecutionMethod::Interpreted, - Some(128), - 1, - 2, - ); + let executor = executor(); let backend = Arc::new(in_mem::Backend::::new()); @@ -477,7 +461,6 @@ mod tests { backend.clone(), executor.clone(), genesis_block_builder, - None, Box::new(TaskExecutor::new()), None, None, @@ -485,7 +468,7 @@ mod tests { ) .expect("Creates a client"); - let version = client.runtime_version_at(&BlockId::Number(0)).unwrap(); + let version = client.runtime_version_at(client.chain_info().genesis_hash).unwrap(); assert_eq!(SUBSTITUTE_SPEC_NAME, &*version.spec_name); } diff --git a/client/service/src/client/client.rs b/client/service/src/client/client.rs index 6a75fad62868..8c4b14fbddc2 100644 --- a/client/service/src/client/client.rs +++ b/client/service/src/client/client.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,20 +18,18 @@ //! Substrate Client -use super::{ - block_rules::{BlockRules, LookupResult as BlockLookupResult}, - genesis::BuildGenesisBlock, -}; +use super::block_rules::{BlockRules, LookupResult as BlockLookupResult}; use futures::{FutureExt, StreamExt}; use log::{error, info, trace, warn}; use parking_lot::{Mutex, RwLock}; use prometheus_endpoint::Registry; use rand::Rng; use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider, RecordProof}; +use sc_chain_spec::{resolve_state_version_from_wasm, BuildGenesisBlock}; use sc_client_api::{ backend::{ self, apply_aux, BlockImportOperation, ClientImportOperation, FinalizeSummary, Finalizer, - ImportSummary, LockImportRun, NewBlockState, StorageProvider, + ImportNotificationAction, ImportSummary, LockImportRun, NewBlockState, StorageProvider, }, client::{ BadBlocks, BlockBackend, BlockImportNotification, BlockOf, BlockchainEvents, ClientInfo, @@ -40,34 +38,32 @@ use sc_client_api::{ }, execution_extensions::ExecutionExtensions, notifications::{StorageEventStream, StorageNotifications}, - CallExecutor, ExecutorProvider, KeyIterator, OnFinalityAction, OnImportAction, ProofProvider, - UsageProvider, + CallExecutor, ExecutorProvider, KeysIter, OnFinalityAction, OnImportAction, PairsIter, + ProofProvider, UsageProvider, }; use sc_consensus::{ BlockCheckParams, BlockImportParams, ForkChoiceStrategy, ImportResult, StateAction, }; -use sc_executor::{RuntimeVersion, RuntimeVersionOf}; +use sc_executor::RuntimeVersion; use sc_telemetry::{telemetry, TelemetryHandle, SUBSTRATE_INFO}; use sp_api::{ ApiExt, ApiRef, CallApiAt, CallApiAtParams, ConstructRuntimeApi, Core as CoreApi, ProvideRuntimeApi, }; use sp_blockchain::{ - self as blockchain, well_known_cache_keys::Id as CacheKeyId, Backend as ChainBackend, - CachedHeaderMetadata, Error, HeaderBackend as ChainHeaderBackend, HeaderMetadata, + self as blockchain, Backend as ChainBackend, CachedHeaderMetadata, Error, + HeaderBackend as ChainHeaderBackend, HeaderMetadata, Info as BlockchainInfo, }; use sp_consensus::{BlockOrigin, BlockStatus, Error as ConsensusError}; use sc_utils::mpsc::{tracing_unbounded, TracingUnboundedSender}; use sp_core::{ storage::{ - well_known_keys, ChildInfo, ChildType, PrefixedStorageKey, Storage, StorageChild, - StorageData, StorageKey, + well_known_keys, ChildInfo, ChildType, PrefixedStorageKey, StorageChild, StorageData, + StorageKey, }, - traits::SpawnNamed, + traits::{CallContext, SpawnNamed}, }; -#[cfg(feature = "test-helpers")] -use sp_keystore::SyncCryptoStorePtr; use sp_runtime::{ generic::{BlockId, SignedBlock}, traits::{ @@ -84,7 +80,7 @@ use sp_state_machine::{ }; use sp_trie::{CompactProof, StorageProof}; use std::{ - collections::{hash_map::DefaultHasher, HashMap, HashSet}, + collections::{HashMap, HashSet}, marker::PhantomData, path::PathBuf, sync::Arc, @@ -106,6 +102,7 @@ where executor: E, storage_notifications: StorageNotifications, import_notification_sinks: NotificationSinks>, + every_import_notification_sinks: NotificationSinks>, finality_notification_sinks: NotificationSinks>, // Collects auxiliary operations to be performed atomically together with // block import operations. @@ -162,7 +159,6 @@ pub fn new_in_mem( backend: Arc>, executor: E, genesis_block_builder: G, - keystore: Option, prometheus_registry: Option, telemetry: Option, spawn_handle: Box, @@ -171,7 +167,7 @@ pub fn new_in_mem( Client, LocalCallExecutor, E>, Block, RA>, > where - E: CodeExecutor + RuntimeVersionOf, + E: CodeExecutor + sc_executor::RuntimeVersionOf, Block: BlockT, G: BuildGenesisBlock< Block, @@ -182,7 +178,6 @@ where backend, executor, genesis_block_builder, - keystore, spawn_handle, prometheus_registry, telemetry, @@ -225,14 +220,13 @@ pub fn new_with_backend( backend: Arc, executor: E, genesis_block_builder: G, - keystore: Option, spawn_handle: Box, prometheus_registry: Option, telemetry: Option, config: ClientConfig, ) -> sp_blockchain::Result, Block, RA>> where - E: CodeExecutor + RuntimeVersionOf, + E: CodeExecutor + sc_executor::RuntimeVersionOf, G: BuildGenesisBlock< Block, BlockImportOperation = >::BlockImportOperation, @@ -240,19 +234,10 @@ where Block: BlockT, B: backend::LocalBackend + 'static, { - let extensions = ExecutionExtensions::new( - Default::default(), - keystore, - sc_offchain::OffchainDb::factory_from_backend(&*backend), - ); + let extensions = ExecutionExtensions::new(None, Arc::new(executor.clone())); - let call_executor = LocalCallExecutor::new( - backend.clone(), - executor, - spawn_handle.clone(), - config.clone(), - extensions, - )?; + let call_executor = + LocalCallExecutor::new(backend.clone(), executor, config.clone(), extensions)?; Client::new( backend, @@ -304,19 +289,22 @@ where FinalityNotification::from_summary(summary, self.unpin_worker_sender.clone()) }); - let (import_notification, storage_changes) = match notify_imported { - Some(mut summary) => { - let storage_changes = summary.storage_changes.take(); - ( - Some(BlockImportNotification::from_summary( - summary, - self.unpin_worker_sender.clone(), - )), - storage_changes, - ) - }, - None => (None, None), - }; + let (import_notification, storage_changes, import_notification_action) = + match notify_imported { + Some(mut summary) => { + let import_notification_action = summary.import_notification_action; + let storage_changes = summary.storage_changes.take(); + ( + Some(BlockImportNotification::from_summary( + summary, + self.unpin_worker_sender.clone(), + )), + storage_changes, + import_notification_action, + ) + }, + None => (None, None, ImportNotificationAction::None), + }; if let Some(ref notification) = finality_notification { for action in self.finality_actions.lock().iter_mut() { @@ -353,7 +341,7 @@ where } self.notify_finalized(finality_notification)?; - self.notify_imported(import_notification, storage_changes)?; + self.notify_imported(import_notification, import_notification_action, storage_changes)?; Ok(r) }; @@ -451,6 +439,7 @@ where executor, storage_notifications: StorageNotifications::new(prometheus_registry), import_notification_sinks: Default::default(), + every_import_notification_sinks: Default::default(), finality_notification_sinks: Default::default(), import_actions: Default::default(), finality_actions: Default::default(), @@ -481,8 +470,7 @@ where } /// Get the code at a given block. - pub fn code_at(&self, id: &BlockId) -> sp_blockchain::Result> { - let hash = self.backend.blockchain().expect_block_hash_from_id(id)?; + pub fn code_at(&self, hash: Block::Hash) -> sp_blockchain::Result> { Ok(StorageProvider::storage(self, hash, &StorageKey(well_known_keys::CODE.to_vec()))? .expect( "None is returned if there's no value stored for the given key;\ @@ -492,8 +480,7 @@ where } /// Get the RuntimeVersion at a given block. - pub fn runtime_version_at(&self, id: &BlockId) -> sp_blockchain::Result { - let hash = self.backend.blockchain().expect_block_hash_from_id(id)?; + pub fn runtime_version_at(&self, hash: Block::Hash) -> sp_blockchain::Result { CallExecutor::runtime_version(&self.executor, hash) } @@ -503,7 +490,6 @@ where &self, operation: &mut ClientImportOperation, import_block: BlockImportParams>, - new_cache: HashMap>, storage_changes: Option< sc_consensus::StorageChanges>, >, @@ -558,7 +544,6 @@ where body, indexed_body, storage_changes, - new_cache, finalized, auxiliary, fork_choice, @@ -598,7 +583,6 @@ where storage_changes: Option< sc_consensus::StorageChanges>, >, - new_cache: HashMap>, finalized: bool, aux: Vec<(Vec, Option>)>, fork_choice: ForkChoiceStrategy, @@ -711,7 +695,6 @@ where }, }; - operation.op.update_cache(new_cache); storage_changes }, None => None, @@ -724,7 +707,7 @@ where operation, parent_hash, None, - info.best_hash, + &info, make_notifications, )?; } @@ -771,11 +754,15 @@ where operation.op.insert_aux(aux)?; - // We only notify when we are already synced to the tip of the chain + let should_notify_every_block = !self.every_import_notification_sinks.lock().is_empty(); + + // Notify when we are already synced to the tip of the chain // or if this import triggers a re-org - if make_notifications || tree_route.is_some() { + let should_notify_recent_block = make_notifications || tree_route.is_some(); + + if should_notify_every_block || should_notify_recent_block { let header = import_headers.into_post(); - if finalized { + if finalized && should_notify_recent_block { let mut summary = match operation.notify_finalized.take() { Some(mut summary) => { summary.header = header.clone(); @@ -812,6 +799,16 @@ where operation.notify_finalized = Some(summary); } + let import_notification_action = if should_notify_every_block { + if should_notify_recent_block { + ImportNotificationAction::Both + } else { + ImportNotificationAction::EveryBlock + } + } else { + ImportNotificationAction::RecentBlock + }; + operation.notify_imported = Some(ImportSummary { hash, origin, @@ -819,6 +816,7 @@ where is_new_best, storage_changes, tree_route, + import_notification_action, }) } @@ -840,7 +838,6 @@ where CoreApi + ApiExt, { let parent_hash = import_block.header.parent_hash(); - let at = BlockId::Hash(*parent_hash); let state_action = std::mem::replace(&mut import_block.state_action, StateAction::Skip); let (enact_state, storage_changes) = match (self.block_status(*parent_hash)?, state_action) { @@ -868,12 +865,12 @@ where // We should enact state, but don't have any storage changes, so we need to execute the // block. (true, None, Some(ref body)) => { - let runtime_api = self.runtime_api(); - let execution_context = import_block.origin.into(); + let mut runtime_api = self.runtime_api(); - runtime_api.execute_block_with_context( - &at, - execution_context, + runtime_api.set_call_context(CallContext::Onchain); + + runtime_api.execute_block( + *parent_hash, Block::new(import_block.header.clone(), body.clone()), )?; @@ -900,48 +897,57 @@ where fn apply_finality_with_block_hash( &self, operation: &mut ClientImportOperation, - block: Block::Hash, + hash: Block::Hash, justification: Option, - best_block: Block::Hash, + info: &BlockchainInfo, notify: bool, ) -> sp_blockchain::Result<()> { - // find tree route from last finalized to given block. - let last_finalized = self.backend.blockchain().last_finalized()?; - - if block == last_finalized { + if hash == info.finalized_hash { warn!( "Possible safety violation: attempted to re-finalize last finalized block {:?} ", - last_finalized + hash, ); return Ok(()) } + // Find tree route from last finalized to given block. let route_from_finalized = - sp_blockchain::tree_route(self.backend.blockchain(), last_finalized, block)?; + sp_blockchain::tree_route(self.backend.blockchain(), info.finalized_hash, hash)?; if let Some(retracted) = route_from_finalized.retracted().get(0) { warn!( "Safety violation: attempted to revert finalized block {:?} which is not in the \ same chain as last finalized {:?}", - retracted, last_finalized + retracted, info.finalized_hash ); return Err(sp_blockchain::Error::NotInFinalizedChain) } - let route_from_best = - sp_blockchain::tree_route(self.backend.blockchain(), best_block, block)?; - - // if the block is not a direct ancestor of the current best chain, - // then some other block is the common ancestor. - if route_from_best.common_block().hash != block { - // NOTE: we're setting the finalized block as best block, this might - // be slightly inaccurate since we might have a "better" block - // further along this chain, but since best chain selection logic is - // plugable we cannot make a better choice here. usages that need - // an accurate "best" block need to go through `SelectChain` - // instead. - operation.op.mark_head(block)?; + // We may need to coercively update the best block if there is more than one + // leaf or if the finalized block number is greater than last best number recorded + // by the backend. This last condition may apply in case of consensus implementations + // not always checking this condition. + let block_number = self + .backend + .blockchain() + .number(hash)? + .ok_or(Error::MissingHeader(format!("{hash:?}")))?; + if self.backend.blockchain().leaves()?.len() > 1 || info.best_number < block_number { + let route_from_best = + sp_blockchain::tree_route(self.backend.blockchain(), info.best_hash, hash)?; + + // If the block is not a direct ancestor of the current best chain, + // then some other block is the common ancestor. + if route_from_best.common_block().hash != hash { + // NOTE: we're setting the finalized block as best block, this might + // be slightly inaccurate since we might have a "better" block + // further along this chain, but since best chain selection logic is + // plugable we cannot make a better choice here. usages that need + // an accurate "best" block need to go through `SelectChain` + // instead. + operation.op.mark_head(hash)?; + } } let enacted = route_from_finalized.enacted(); @@ -950,8 +956,8 @@ where operation.op.mark_finalized(finalize_new.hash, None)?; } - assert_eq!(enacted.last().map(|e| e.hash), Some(block)); - operation.op.mark_finalized(block, justification)?; + assert_eq!(enacted.last().map(|e| e.hash), Some(hash)); + operation.op.mark_finalized(hash, justification)?; if notify { let finalized = @@ -973,7 +979,7 @@ where let header = self .backend .blockchain() - .header(block)? + .header(hash)? .expect("Block to finalize expected to be onchain; qed"); operation.notify_finalized = Some(FinalizeSummary { header, finalized, stale_heads }); @@ -1015,6 +1021,7 @@ where fn notify_imported( &self, notification: Option>, + import_notification_action: ImportNotificationAction, storage_changes: Option<(StorageCollection, ChildStorageCollection)>, ) -> sp_blockchain::Result<()> { let notification = match notification { @@ -1027,22 +1034,59 @@ where // temporary leak of closed/discarded notification sinks (e.g. // from consensus code). self.import_notification_sinks.lock().retain(|sink| !sink.is_closed()); + + self.every_import_notification_sinks.lock().retain(|sink| !sink.is_closed()); + return Ok(()) }, }; - if let Some(storage_changes) = storage_changes { - // TODO [ToDr] How to handle re-orgs? Should we re-emit all storage changes? - self.storage_notifications.trigger( - ¬ification.hash, - storage_changes.0.into_iter(), - storage_changes.1.into_iter().map(|(sk, v)| (sk, v.into_iter())), - ); - } + let trigger_storage_changes_notification = || { + if let Some(storage_changes) = storage_changes { + // TODO [ToDr] How to handle re-orgs? Should we re-emit all storage changes? + self.storage_notifications.trigger( + ¬ification.hash, + storage_changes.0.into_iter(), + storage_changes.1.into_iter().map(|(sk, v)| (sk, v.into_iter())), + ); + } + }; - self.import_notification_sinks - .lock() - .retain(|sink| sink.unbounded_send(notification.clone()).is_ok()); + match import_notification_action { + ImportNotificationAction::Both => { + trigger_storage_changes_notification(); + self.import_notification_sinks + .lock() + .retain(|sink| sink.unbounded_send(notification.clone()).is_ok()); + + self.every_import_notification_sinks + .lock() + .retain(|sink| sink.unbounded_send(notification.clone()).is_ok()); + }, + ImportNotificationAction::RecentBlock => { + trigger_storage_changes_notification(); + self.import_notification_sinks + .lock() + .retain(|sink| sink.unbounded_send(notification.clone()).is_ok()); + + self.every_import_notification_sinks.lock().retain(|sink| !sink.is_closed()); + }, + ImportNotificationAction::EveryBlock => { + self.every_import_notification_sinks + .lock() + .retain(|sink| sink.unbounded_send(notification.clone()).is_ok()); + + self.import_notification_sinks.lock().retain(|sink| !sink.is_closed()); + }, + ImportNotificationAction::None => { + // This branch is unreachable in fact because the block import notification must be + // Some(_) instead of None (it's already handled at the beginning of this function) + // at this point. + self.import_notification_sinks.lock().retain(|sink| !sink.is_closed()); + + self.every_import_notification_sinks.lock().retain(|sink| !sink.is_closed()); + }, + } Ok(()) } @@ -1079,7 +1123,7 @@ where } /// Get blockchain info. - pub fn chain_info(&self) -> blockchain::Info { + pub fn chain_info(&self) -> BlockchainInfo { self.backend.blockchain().info() } @@ -1167,38 +1211,6 @@ where } } -/// Return the genesis state version given the genesis storage and executor. -pub fn resolve_state_version_from_wasm( - storage: &Storage, - executor: &E, -) -> sp_blockchain::Result -where - E: RuntimeVersionOf, -{ - if let Some(wasm) = storage.top.get(well_known_keys::CODE) { - let mut ext = sp_state_machine::BasicExternalities::new_empty(); // just to read runtime version. - - let code_fetcher = sp_core::traits::WrappedRuntimeCode(wasm.as_slice().into()); - let runtime_code = sp_core::traits::RuntimeCode { - code_fetcher: &code_fetcher, - heap_pages: None, - hash: { - use std::hash::{Hash, Hasher}; - let mut state = DefaultHasher::new(); - wasm.hash(&mut state); - state.finish().to_le_bytes().to_vec() - }, - }; - let runtime_version = RuntimeVersionOf::runtime_version(executor, &mut ext, &runtime_code) - .map_err(|e| sp_blockchain::Error::VersionInvalid(e.to_string()))?; - Ok(runtime_version.state_version()) - } else { - Err(sp_blockchain::Error::VersionInvalid( - "Runtime missing from initial storage, could not read state version.".to_string(), - )) - } -} - impl UsageProvider for Client where B: backend::Backend, @@ -1411,14 +1423,14 @@ where { fn new_block_at>( &self, - parent: &BlockId, + parent: Block::Hash, inherent_digests: Digest, record_proof: R, ) -> sp_blockchain::Result> { sc_block_builder::BlockBuilder::new( self, - self.expect_block_hash_from_id(parent)?, - self.expect_block_number_from_id(parent)?, + parent, + self.expect_block_number_from_id(&BlockId::Hash(parent))?, record_proof.into(), inherent_digests, &self.backend, @@ -1465,52 +1477,37 @@ where Block: BlockT, { fn storage_keys( - &self, - hash: Block::Hash, - key_prefix: &StorageKey, - ) -> sp_blockchain::Result> { - let keys = self.state_at(hash)?.keys(&key_prefix.0).into_iter().map(StorageKey).collect(); - Ok(keys) - } - - fn storage_pairs( &self, hash: ::Hash, - key_prefix: &StorageKey, - ) -> sp_blockchain::Result> { + prefix: Option<&StorageKey>, + start_key: Option<&StorageKey>, + ) -> sp_blockchain::Result> { let state = self.state_at(hash)?; - let keys = state - .keys(&key_prefix.0) - .into_iter() - .map(|k| { - let d = state.storage(&k).ok().flatten().unwrap_or_default(); - (StorageKey(k), StorageData(d)) - }) - .collect(); - Ok(keys) + KeysIter::new(state, prefix, start_key) + .map_err(|e| sp_blockchain::Error::from_state(Box::new(e))) } - fn storage_keys_iter( + fn child_storage_keys( &self, hash: ::Hash, + child_info: ChildInfo, prefix: Option<&StorageKey>, start_key: Option<&StorageKey>, - ) -> sp_blockchain::Result> { + ) -> sp_blockchain::Result> { let state = self.state_at(hash)?; - let start_key = start_key.or(prefix).map(|key| key.0.clone()).unwrap_or_else(Vec::new); - Ok(KeyIterator::new(state, prefix.cloned(), start_key)) + KeysIter::new_child(state, child_info, prefix, start_key) + .map_err(|e| sp_blockchain::Error::from_state(Box::new(e))) } - fn child_storage_keys_iter( + fn storage_pairs( &self, hash: ::Hash, - child_info: ChildInfo, prefix: Option<&StorageKey>, start_key: Option<&StorageKey>, - ) -> sp_blockchain::Result> { + ) -> sp_blockchain::Result> { let state = self.state_at(hash)?; - let start_key = start_key.or(prefix).map(|key| key.0.clone()).unwrap_or_else(Vec::new); - Ok(KeyIterator::new_child(state, child_info, prefix.cloned(), start_key)) + PairsIter::new(state, prefix, start_key) + .map_err(|e| sp_blockchain::Error::from_state(Box::new(e))) } fn storage( @@ -1535,21 +1532,6 @@ where .map_err(|e| sp_blockchain::Error::from_state(Box::new(e))) } - fn child_storage_keys( - &self, - hash: ::Hash, - child_info: &ChildInfo, - key_prefix: &StorageKey, - ) -> sp_blockchain::Result> { - let keys = self - .state_at(hash)? - .child_keys(child_info, &key_prefix.0) - .into_iter() - .map(StorageKey) - .collect(); - Ok(keys) - } - fn child_storage( &self, hash: ::Hash, @@ -1727,28 +1709,38 @@ where &self, params: CallApiAtParams, ) -> Result, sp_api::ApiError> { - let at_hash = self.expect_block_hash_from_id(params.at)?; self.executor .contextual_call( - at_hash, + params.at, params.function, ¶ms.arguments, params.overlayed_changes, Some(params.storage_transaction_cache), params.recorder, - params.context, + params.call_context, + params.extensions, ) .map_err(Into::into) } - fn runtime_version_at(&self, at: &BlockId) -> Result { - let hash = self.backend.blockchain().expect_block_hash_from_id(at)?; + fn runtime_version_at(&self, hash: Block::Hash) -> Result { CallExecutor::runtime_version(&self.executor, hash).map_err(Into::into) } - fn state_at(&self, at: &BlockId) -> Result { - let hash = self.backend.blockchain().expect_block_hash_from_id(at)?; - self.state_at(hash).map_err(Into::into) + fn state_at(&self, at: Block::Hash) -> Result { + self.state_at(at).map_err(Into::into) + } + + fn initialize_extensions( + &self, + at: Block::Hash, + extensions: &mut sp_externalities::Extensions, + ) -> Result<(), sp_api::ApiError> { + let block_number = self.expect_block_number_from_id(&BlockId::Hash(at))?; + + extensions.merge(self.executor.execution_extensions().extensions(at, block_number)); + + Ok(()) } } @@ -1782,7 +1774,6 @@ where async fn import_block( &mut self, mut import_block: BlockImportParams>, - new_cache: HashMap>, ) -> Result { let span = tracing::span!(tracing::Level::DEBUG, "import_block"); let _enter = span.enter(); @@ -1797,7 +1788,7 @@ where }; self.lock_import_and_run(|operation| { - self.apply_block(operation, import_block, new_cache, storage_changes) + self.apply_block(operation, import_block, storage_changes) }) .map_err(|e| { warn!("Block import error: {}", e); @@ -1887,9 +1878,8 @@ where async fn import_block( &mut self, import_block: BlockImportParams, - new_cache: HashMap>, ) -> Result { - (&*self).import_block(import_block, new_cache).await + (&*self).import_block(import_block).await } async fn check_block( @@ -1913,8 +1903,8 @@ where justification: Option, notify: bool, ) -> sp_blockchain::Result<()> { - let last_best = self.backend.blockchain().info().best_hash; - self.apply_finality_with_block_hash(operation, hash, justification, last_best, notify) + let info = self.backend.blockchain().info(); + self.apply_finality_with_block_hash(operation, hash, justification, &info, notify) } fn finalize_block( @@ -1980,6 +1970,12 @@ where stream } + fn every_import_notification_stream(&self) -> ImportNotifications { + let (sink, stream) = tracing_unbounded("mpsc_every_import_notification_stream", 100_000); + self.every_import_notification_sinks.lock().push(sink); + stream + } + fn finality_notification_stream(&self) -> FinalityNotifications { let (sink, stream) = tracing_unbounded("mpsc_finality_notification_stream", 100_000); self.finality_notification_sinks.lock().push(sink); diff --git a/client/service/src/client/mod.rs b/client/service/src/client/mod.rs index dafeb6883188..a13fd4317e15 100644 --- a/client/service/src/client/mod.rs +++ b/client/service/src/client/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -47,13 +47,12 @@ mod block_rules; mod call_executor; mod client; -pub mod genesis; mod wasm_override; mod wasm_substitutes; pub use self::{ call_executor::LocalCallExecutor, - client::{resolve_state_version_from_wasm, Client, ClientConfig}, + client::{Client, ClientConfig}, }; #[cfg(feature = "test-helpers")] diff --git a/client/service/src/client/wasm_override.rs b/client/service/src/client/wasm_override.rs index 03a5704adf8b..725c8ab9429a 100644 --- a/client/service/src/client/wasm_override.rs +++ b/client/service/src/client/wasm_override.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -264,21 +264,26 @@ pub fn dummy_overrides() -> WasmOverride { #[cfg(test)] mod tests { use super::*; - use sc_executor::{NativeElseWasmExecutor, WasmExecutionMethod}; + use sc_executor::{HeapAllocStrategy, NativeElseWasmExecutor, WasmExecutor}; use std::fs::{self, File}; use substrate_test_runtime_client::LocalExecutorDispatch; + fn executor() -> NativeElseWasmExecutor { + NativeElseWasmExecutor::::new_with_wasm_executor( + WasmExecutor::builder() + .with_onchain_heap_alloc_strategy(HeapAllocStrategy::Static {extra_pages: 128}) + .with_offchain_heap_alloc_strategy(HeapAllocStrategy::Static {extra_pages: 128}) + .with_max_runtime_instances(1) + .with_runtime_cache_size(2) + .build() + ) + } + fn wasm_test(fun: F) where F: Fn(&Path, &[u8], &NativeElseWasmExecutor), { - let exec = - NativeElseWasmExecutor::::new( - WasmExecutionMethod::Interpreted, - Some(128), - 1, - 2, - ); + let exec = executor(); let bytes = substrate_test_runtime::wasm_binary_unwrap(); let dir = tempfile::tempdir().expect("Create a temporary directory"); fun(dir.path(), bytes, &exec); @@ -287,12 +292,7 @@ mod tests { #[test] fn should_get_runtime_version() { - let executor = NativeElseWasmExecutor::::new( - WasmExecutionMethod::Interpreted, - Some(128), - 1, - 2, - ); + let executor = executor(); let version = WasmOverride::runtime_version( &executor, diff --git a/client/service/src/client/wasm_substitutes.rs b/client/service/src/client/wasm_substitutes.rs index ed931d3dae21..a792ab87e771 100644 --- a/client/service/src/client/wasm_substitutes.rs +++ b/client/service/src/client/wasm_substitutes.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/service/src/config.rs b/client/service/src/config.rs index efadb8433f63..39b7ee050790 100644 --- a/client/service/src/config.rs +++ b/client/service/src/config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,23 +18,21 @@ //! Service configuration. -pub use sc_client_api::execution_extensions::{ExecutionStrategies, ExecutionStrategy}; pub use sc_client_db::{BlocksPruning, Database, DatabaseSource, PruningMode}; pub use sc_executor::{WasmExecutionMethod, WasmtimeInstantiationStrategy}; pub use sc_network::{ - config::{NetworkConfiguration, NodeKeyConfig, Role}, - Multiaddr, -}; -pub use sc_network_common::{ - config::{MultiaddrWithPeerId, NonDefaultSetConfig, ProtocolId, SetConfig, TransportConfig}, + config::{ + MultiaddrWithPeerId, NetworkConfiguration, NodeKeyConfig, NonDefaultSetConfig, ProtocolId, + Role, SetConfig, SyncMode, TransportConfig, + }, request_responses::{ IncomingRequest, OutgoingResponse, ProtocolConfig as RequestResponseConfig, }, + Multiaddr, }; use prometheus_endpoint::Registry; use sc_chain_spec::ChainSpec; -use sc_network::config::SyncMode; pub use sc_telemetry::TelemetryEndpoints; pub use sc_transaction_pool::Options as TransactionPoolOptions; use sp_core::crypto::SecretString; @@ -62,8 +60,6 @@ pub struct Configuration { pub network: NetworkConfiguration, /// Configuration for the keystore. pub keystore: KeystoreConfig, - /// Remote URI to connect to for async keystore support - pub keystore_remote: Option, /// Configuration for the database. pub database: DatabaseSource, /// Maximum size of internal trie cache in bytes. @@ -84,36 +80,26 @@ pub struct Configuration { /// over on-chain runtimes when the spec version matches. Set to `None` to /// disable overrides (default). pub wasm_runtime_overrides: Option, - /// Execution strategies. - pub execution_strategies: ExecutionStrategies, - /// RPC over HTTP binding address. `None` if disabled. - pub rpc_http: Option, - /// RPC over Websockets binding address. `None` if disabled. - pub rpc_ws: Option, - /// RPC over IPC binding path. `None` if disabled. - pub rpc_ipc: Option, - /// Maximum number of connections for WebSockets RPC server. `None` if default. - pub rpc_ws_max_connections: Option, + /// JSON-RPC server binding address. + pub rpc_addr: Option, + /// Maximum number of connections for JSON-RPC server. + pub rpc_max_connections: u32, /// CORS settings for HTTP & WS servers. `None` if all origins are allowed. pub rpc_cors: Option>, /// RPC methods to expose (by default only a safe subset or all of them). pub rpc_methods: RpcMethods, - /// Maximum payload of rpc request/responses. - pub rpc_max_payload: Option, - /// Maximum payload of a rpc request - pub rpc_max_request_size: Option, /// Maximum payload of a rpc request - pub rpc_max_response_size: Option, + pub rpc_max_request_size: u32, + /// Maximum payload of a rpc response. + pub rpc_max_response_size: u32, /// Custom JSON-RPC subscription ID provider. /// /// Default: [`crate::RandomStringSubscriptionId`]. pub rpc_id_provider: Option>, /// Maximum allowed subscriptions per rpc connection - /// - /// Default: 1024. - pub rpc_max_subs_per_conn: Option, - /// Maximum size of the output buffer capacity for websocket connections. - pub ws_max_out_buffer_capacity: Option, + pub rpc_max_subs_per_conn: u32, + /// JSON-RPC server default port. + pub rpc_port: u16, /// Prometheus endpoint configuration. `None` if disabled. pub prometheus_config: Option, /// Telemetry service URL. `None` if disabled. @@ -143,8 +129,10 @@ pub struct Configuration { pub max_runtime_instances: usize, /// Announce block automatically after they have been imported pub announce_block: bool, - /// Base path of the configuration - pub base_path: Option, + /// Data path root for the configured chain. + pub data_path: PathBuf, + /// Base path of the configuration. This is shared between chains. + pub base_path: BasePath, /// Configuration of the output format that the informant uses. pub informant_output_format: sc_informant::OutputFormat, /// Maximum number of different runtime versions that can be cached. @@ -243,7 +231,7 @@ impl Configuration { /// Returns true if the genesis state writting will be skipped while initializing the genesis /// block. pub fn no_genesis(&self) -> bool { - matches!(self.network.sync_mode, SyncMode::Fast { .. } | SyncMode::Warp { .. }) + matches!(self.network.sync_mode, SyncMode::LightState { .. } | SyncMode::Warp { .. }) } /// Returns the database config for creating the backend. diff --git a/client/service/src/error.rs b/client/service/src/error.rs index ec2951193964..c871342c771e 100644 --- a/client/service/src/error.rs +++ b/client/service/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -40,7 +40,7 @@ pub enum Error { Consensus(#[from] sp_consensus::Error), #[error(transparent)] - Network(#[from] sc_network_common::error::Error), + Network(#[from] sc_network::error::Error), #[error(transparent)] Keystore(#[from] sc_keystore::Error), @@ -48,9 +48,6 @@ pub enum Error { #[error(transparent)] Telemetry(#[from] sc_telemetry::Error), - #[error(transparent)] - Storage(#[from] sc_storage_monitor::Error), - #[error("Best chain selection strategy (SelectChain) is not provided.")] SelectChainRequired, diff --git a/client/service/src/lib.rs b/client/service/src/lib.rs index 1529b822ade3..0961967f9ca2 100644 --- a/client/service/src/lib.rs +++ b/client/service/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -37,12 +37,14 @@ mod task_manager; use std::{collections::HashMap, net::SocketAddr}; use codec::{Decode, Encode}; -use futures::{channel::mpsc, FutureExt, StreamExt}; +use futures::{channel::mpsc, pin_mut, FutureExt, StreamExt}; use jsonrpsee::{core::Error as JsonRpseeError, RpcModule}; use log::{debug, error, warn}; use sc_client_api::{blockchain::HeaderBackend, BlockBackend, BlockchainEvents, ProofProvider}; -use sc_network::PeerId; -use sc_network_common::{config::MultiaddrWithPeerId, service::NetworkBlock}; +use sc_network::{ + config::MultiaddrWithPeerId, NetworkBlock, NetworkPeers, NetworkStateInfo, PeerId, +}; +use sc_network_sync::SyncingService; use sc_utils::mpsc::TracingUnboundedReceiver; use sp_blockchain::HeaderMetadata; use sp_consensus::SyncOracle; @@ -53,16 +55,20 @@ use sp_runtime::{ pub use self::{ builder::{ - build_network, build_offchain_workers, new_client, new_db_backend, new_full_client, - new_full_parts, spawn_tasks, BuildNetworkParams, KeystoreContainer, NetworkStarter, - SpawnTasksParams, TFullBackend, TFullCallExecutor, TFullClient, - }, - client::{ - genesis::{BuildGenesisBlock, GenesisBlockBuilder}, - resolve_state_version_from_wasm, ClientConfig, LocalCallExecutor, + build_network, new_client, new_db_backend, new_full_client, new_full_parts, + new_full_parts_with_genesis_builder, new_native_or_wasm_executor, new_wasm_executor, + spawn_tasks, BuildNetworkParams, KeystoreContainer, NetworkStarter, SpawnTasksParams, + TFullBackend, TFullCallExecutor, TFullClient, }, + client::{ClientConfig, LocalCallExecutor}, error::Error, }; + +pub use sc_chain_spec::{ + construct_genesis_block, resolve_state_version_from_wasm, BuildGenesisBlock, + GenesisBlockBuilder, +}; + pub use config::{ BasePath, BlocksPruning, Configuration, DatabaseSource, PruningMode, Role, RpcMethods, TaskType, }; @@ -73,6 +79,7 @@ pub use sc_chain_spec::{ pub use sc_consensus::ImportQueue; pub use sc_executor::NativeExecutionDispatch; +pub use sc_network_common::sync::warp::WarpSyncParams; #[doc(hidden)] pub use sc_network_transactions::config::{TransactionImport, TransactionImportFuture}; pub use sc_rpc::{ @@ -137,9 +144,7 @@ pub struct PartialComponents @@ -152,21 +157,19 @@ async fn build_network_future< + 'static, H: sc_network_common::ExHashT, >( - role: Role, - mut network: sc_network::NetworkWorker, + network: sc_network::NetworkWorker, client: Arc, - mut rpc_rx: TracingUnboundedReceiver>, - should_have_peers: bool, + sync_service: Arc>, announce_imported_blocks: bool, ) { let mut imported_blocks_stream = client.import_notification_stream().fuse(); - // Current best block at initialization, to report to the RPC layer. - let starting_block = client.info().best_number; - // Stream of finalized blocks reported by the client. let mut finality_notification_stream = client.finality_notification_stream().fuse(); + let network_run = network.run().fuse(); + pin_mut!(network_run); + loop { futures::select! { // List of blocks that the client has imported. @@ -175,15 +178,18 @@ async fn build_network_future< Some(n) => n, // If this stream is shut down, that means the client has shut down, and the // most appropriate thing to do for the network future is to shut down too. - None => return, + None => { + debug!("Block import stream has terminated, shutting down the network future."); + return + }, }; if announce_imported_blocks { - network.service().announce_block(notification.hash, None); + sync_service.announce_block(notification.hash, None); } if notification.is_new_best { - network.service().new_best_block_imported( + sync_service.new_best_block_imported( notification.hash, *notification.header.number(), ); @@ -192,106 +198,155 @@ async fn build_network_future< // List of blocks that the client has finalized. notification = finality_notification_stream.select_next_some() => { - network.on_block_finalized(notification.hash, notification.header); + sync_service.on_block_finalized(notification.hash, notification.header); } - // Answer incoming RPC requests. - request = rpc_rx.select_next_some() => { - match request { - sc_rpc::system::Request::Health(sender) => { - let _ = sender.send(sc_rpc::system::Health { - peers: network.peers_debug_info().len(), - is_syncing: network.service().is_major_syncing(), - should_have_peers, - }); - }, - sc_rpc::system::Request::LocalPeerId(sender) => { - let _ = sender.send(network.local_peer_id().to_base58()); - }, - sc_rpc::system::Request::LocalListenAddresses(sender) => { - let peer_id = (*network.local_peer_id()).into(); - let p2p_proto_suffix = sc_network::multiaddr::Protocol::P2p(peer_id); - let addresses = network.listen_addresses() - .map(|addr| addr.clone().with(p2p_proto_suffix.clone()).to_string()) - .collect(); - let _ = sender.send(addresses); - }, - sc_rpc::system::Request::Peers(sender) => { - let _ = sender.send(network.peers_debug_info().into_iter().map(|(peer_id, p)| - sc_rpc::system::PeerInfo { + // Drive the network. Shut down the network future if `NetworkWorker` has terminated. + _ = network_run => { + debug!("`NetworkWorker` has terminated, shutting down the network future."); + return + } + } + } +} + +/// Builds a future that processes system RPC requests. +pub async fn build_system_rpc_future< + B: BlockT, + C: BlockchainEvents + + HeaderBackend + + BlockBackend + + HeaderMetadata + + ProofProvider + + Send + + Sync + + 'static, + H: sc_network_common::ExHashT, +>( + role: Role, + network_service: Arc>, + sync_service: Arc>, + client: Arc, + mut rpc_rx: TracingUnboundedReceiver>, + should_have_peers: bool, +) { + // Current best block at initialization, to report to the RPC layer. + let starting_block = client.info().best_number; + + loop { + // Answer incoming RPC requests. + let Some(req) = rpc_rx.next().await else { + debug!("RPC requests stream has terminated, shutting down the system RPC future."); + return + }; + + match req { + sc_rpc::system::Request::Health(sender) => match sync_service.peers_info().await { + Ok(info) => { + let _ = sender.send(sc_rpc::system::Health { + peers: info.len(), + is_syncing: sync_service.is_major_syncing(), + should_have_peers, + }); + }, + Err(_) => log::error!("`SyncingEngine` shut down"), + }, + sc_rpc::system::Request::LocalPeerId(sender) => { + let _ = sender.send(network_service.local_peer_id().to_base58()); + }, + sc_rpc::system::Request::LocalListenAddresses(sender) => { + let peer_id = (network_service.local_peer_id()).into(); + let p2p_proto_suffix = sc_network::multiaddr::Protocol::P2p(peer_id); + let addresses = network_service + .listen_addresses() + .iter() + .map(|addr| addr.clone().with(p2p_proto_suffix.clone()).to_string()) + .collect(); + let _ = sender.send(addresses); + }, + sc_rpc::system::Request::Peers(sender) => match sync_service.peers_info().await { + Ok(info) => { + let _ = sender.send( + info.into_iter() + .map(|(peer_id, p)| sc_rpc::system::PeerInfo { peer_id: peer_id.to_base58(), roles: format!("{:?}", p.roles), best_hash: p.best_hash, best_number: p.best_number, - } - ).collect()); - } - sc_rpc::system::Request::NetworkState(sender) => { - if let Ok(network_state) = serde_json::to_value(&network.network_state()) { - let _ = sender.send(network_state); - } - } - sc_rpc::system::Request::NetworkAddReservedPeer(peer_addr, sender) => { - let result = match MultiaddrWithPeerId::try_from(peer_addr) { - Ok(peer) => { - network.add_reserved_peer(peer) - }, - Err(err) => { - Err(err.to_string()) - }, - }; - let x = result.map_err(sc_rpc::system::error::Error::MalformattedPeerArg); - let _ = sender.send(x); - } - sc_rpc::system::Request::NetworkRemoveReservedPeer(peer_id, sender) => { - let _ = match peer_id.parse::() { - Ok(peer_id) => { - network.remove_reserved_peer(peer_id); - sender.send(Ok(())) - } - Err(e) => sender.send(Err(sc_rpc::system::error::Error::MalformattedPeerArg( - e.to_string(), - ))), - }; - } - sc_rpc::system::Request::NetworkReservedPeers(sender) => { - let reserved_peers = network.reserved_peers(); - let reserved_peers = reserved_peers - .map(|peer_id| peer_id.to_base58()) - .collect(); - - let _ = sender.send(reserved_peers); + }) + .collect(), + ); + }, + Err(_) => log::error!("`SyncingEngine` shut down"), + }, + sc_rpc::system::Request::NetworkState(sender) => { + let network_state = network_service.network_state().await; + if let Ok(network_state) = network_state { + if let Ok(network_state) = serde_json::to_value(network_state) { + let _ = sender.send(network_state); } - sc_rpc::system::Request::NodeRoles(sender) => { - use sc_rpc::system::NodeRole; + } else { + break + } + }, + sc_rpc::system::Request::NetworkAddReservedPeer(peer_addr, sender) => { + let result = match MultiaddrWithPeerId::try_from(peer_addr) { + Ok(peer) => network_service.add_reserved_peer(peer), + Err(err) => Err(err.to_string()), + }; + let x = result.map_err(sc_rpc::system::error::Error::MalformattedPeerArg); + let _ = sender.send(x); + }, + sc_rpc::system::Request::NetworkRemoveReservedPeer(peer_id, sender) => { + let _ = match peer_id.parse::() { + Ok(peer_id) => { + network_service.remove_reserved_peer(peer_id); + sender.send(Ok(())) + }, + Err(e) => sender.send(Err(sc_rpc::system::error::Error::MalformattedPeerArg( + e.to_string(), + ))), + }; + }, + sc_rpc::system::Request::NetworkReservedPeers(sender) => { + let reserved_peers = network_service.reserved_peers().await; + if let Ok(reserved_peers) = reserved_peers { + let reserved_peers = + reserved_peers.iter().map(|peer_id| peer_id.to_base58()).collect(); + let _ = sender.send(reserved_peers); + } else { + break + } + }, + sc_rpc::system::Request::NodeRoles(sender) => { + use sc_rpc::system::NodeRole; - let node_role = match role { - Role::Authority { .. } => NodeRole::Authority, - Role::Full => NodeRole::Full, - }; + let node_role = match role { + Role::Authority { .. } => NodeRole::Authority, + Role::Full => NodeRole::Full, + }; - let _ = sender.send(vec![node_role]); - } - sc_rpc::system::Request::SyncState(sender) => { - use sc_rpc::system::SyncState; + let _ = sender.send(vec![node_role]); + }, + sc_rpc::system::Request::SyncState(sender) => { + use sc_rpc::system::SyncState; + match sync_service.best_seen_block().await { + Ok(best_seen_block) => { let best_number = client.info().best_number; - let _ = sender.send(SyncState { starting_block, current_block: best_number, - highest_block: network.best_seen_block().unwrap_or(best_number), + highest_block: best_seen_block.unwrap_or(best_number), }); - } + }, + Err(_) => log::error!("`SyncingEngine` shut down"), } - } - - // The network worker has done something. Nothing special to do, but could be - // used in the future to perform actions in response of things that happened on - // the network. - _ = (&mut network).fuse() => {} + }, } } + + debug!("`NetworkWorker` has terminated, shutting down the system RPC future."); } // Wrapper for HTTP and WS servers that makes sure they are properly shut down. @@ -325,57 +380,37 @@ where } } - let (max_request_size, ws_max_response_size, http_max_response_size) = - legacy_cli_parsing(config); - - let random_port = |mut addr: SocketAddr| { + // if binding the specified port failed then a random port is assigned by the OS. + let backup_port = |mut addr: SocketAddr| { addr.set_port(0); addr }; - let ws_addr = config - .rpc_ws - .unwrap_or_else(|| "127.0.0.1:9944".parse().expect("valid sockaddr; qed")); - let ws_addr2 = random_port(ws_addr); - - let http_addr = config - .rpc_http - .unwrap_or_else(|| "127.0.0.1:9933".parse().expect("valid sockaddr; qed")); - let http_addr2 = random_port(http_addr); - + let addr = config.rpc_addr.unwrap_or_else(|| ([127, 0, 0, 1], config.rpc_port).into()); + let backup_addr = backup_port(addr); let metrics = sc_rpc_server::RpcMetrics::new(config.prometheus_registry())?; - let server_config = sc_rpc_server::WsConfig { - max_connections: config.rpc_ws_max_connections, - max_payload_in_mb: max_request_size, - max_payload_out_mb: ws_max_response_size, + let server_config = sc_rpc_server::Config { + addrs: [addr, backup_addr], + max_connections: config.rpc_max_connections, + max_payload_in_mb: config.rpc_max_request_size, + max_payload_out_mb: config.rpc_max_response_size, max_subs_per_conn: config.rpc_max_subs_per_conn, + rpc_api: gen_rpc_module(deny_unsafe(addr, &config.rpc_methods))?, + metrics, + id_provider: rpc_id_provider, + cors: config.rpc_cors.as_ref(), + tokio_handle: config.tokio_handle.clone(), }; - let http_fut = sc_rpc_server::start_http( - [http_addr, http_addr2], - config.rpc_cors.as_ref(), - max_request_size, - http_max_response_size, - metrics.clone(), - gen_rpc_module(deny_unsafe(http_addr, &config.rpc_methods))?, - config.tokio_handle.clone(), - ); - - let ws_fut = sc_rpc_server::start( - [ws_addr, ws_addr2], - config.rpc_cors.as_ref(), - server_config.clone(), - metrics.clone(), - gen_rpc_module(deny_unsafe(ws_addr, &config.rpc_methods))?, - config.tokio_handle.clone(), - rpc_id_provider, - ); - + // TODO: https://github.com/paritytech/substrate/issues/13773 + // + // `block_in_place` is a hack to allow callers to call `block_on` prior to + // calling `start_rpc_servers`. match tokio::task::block_in_place(|| { - config.tokio_handle.block_on(futures::future::try_join(http_fut, ws_fut)) + config.tokio_handle.block_on(sc_rpc_server::start_server(server_config)) }) { - Ok((http, ws)) => Ok(Box::new((waiting::Server(Some(http)), waiting::Server(Some(ws))))), + Ok(server) => Ok(Box::new(waiting::Server(Some(server)))), Err(e) => Err(Error::Application(e)), } } @@ -386,6 +421,13 @@ pub struct TransactionPoolAdapter { client: Arc, } +impl TransactionPoolAdapter { + /// Constructs a new instance of [`TransactionPoolAdapter`]. + pub fn new(pool: Arc

, client: Arc) -> Self { + Self { pool, client } + } +} + /// Get transactions for propagation. /// /// Function extracted to simplify the test and prevent creating `ServiceFactory`. @@ -479,61 +521,15 @@ where } } -fn legacy_cli_parsing(config: &Configuration) -> (Option, Option, Option) { - let ws_max_response_size = match ( - config.ws_max_out_buffer_capacity, - config.rpc_max_response_size, - ) { - (Some(legacy_max), max) => { - eprintln!("DEPRECATED: `--ws_max_out_buffer_capacity` has been removed; use `rpc-max-response-size or rpc-max-request-size` instead"); - eprintln!("Setting WS `rpc-max-response-size` to `max(ws_max_out_buffer_capacity, rpc_max_response_size)`"); - Some(std::cmp::max(legacy_max, max.unwrap_or(0))) - }, - (None, Some(m)) => Some(m), - (None, None) => None, - }; - - let max_request_size = match (config.rpc_max_payload, config.rpc_max_request_size) { - (Some(legacy_max), max) => { - eprintln!("DEPRECATED: `--rpc_max_payload` has been removed use `rpc-max-response-size or rpc-max-request-size` instead"); - eprintln!( - "Setting `rpc-max-response-size` to `max(rpc_max_payload, rpc_max_request_size)`" - ); - Some(std::cmp::max(legacy_max, max.unwrap_or(0))) - }, - (None, Some(max)) => Some(max), - (None, None) => None, - }; - - let http_max_response_size = match (config.rpc_max_payload, config.rpc_max_response_size) { - (Some(legacy_max), max) => { - eprintln!("DEPRECATED: `--rpc_max_payload` has been removed use `rpc-max-response-size or rpc-max-request-size` instead"); - eprintln!( - "Setting HTTP `rpc-max-response-size` to `max(rpc_max_payload, rpc_max_response_size)`" - ); - Some(std::cmp::max(legacy_max, max.unwrap_or(0))) - }, - (None, Some(max)) => Some(max), - (None, None) => None, - }; - - if config.rpc_ipc.is_some() { - eprintln!("DEPRECATED: `--ipc-path` has no effect anymore IPC support has been removed"); - } - - (max_request_size, ws_max_response_size, http_max_response_size) -} - #[cfg(test)] mod tests { use super::*; use futures::executor::block_on; use sc_transaction_pool::BasicPool; use sp_consensus::SelectChain; - use sp_runtime::traits::BlindCheckable; use substrate_test_runtime_client::{ prelude::*, - runtime::{Extrinsic, Transfer}, + runtime::{ExtrinsicBuilder, Transfer, TransferData}, }; #[test] @@ -552,13 +548,13 @@ mod tests { from: AccountKeyring::Alice.into(), to: AccountKeyring::Bob.into(), } - .into_signed_tx(); + .into_unchecked_extrinsic(); block_on(pool.submit_one(&BlockId::hash(best.hash()), source, transaction.clone())) .unwrap(); block_on(pool.submit_one( &BlockId::hash(best.hash()), source, - Extrinsic::IncludeData(vec![1]), + ExtrinsicBuilder::new_call_do_not_propagate().nonce(1).build(), )) .unwrap(); assert_eq!(pool.status().ready, 2); @@ -568,8 +564,6 @@ mod tests { // then assert_eq!(transactions.len(), 1); - assert!(transactions[0].1.clone().check().is_ok()); - // this should not panic - let _ = transactions[0].1.transfer(); + assert!(TransferData::try_from(&transactions[0].1).is_ok()); } } diff --git a/client/service/src/metrics.rs b/client/service/src/metrics.rs index c83b3988f9fa..ece5758be771 100644 --- a/client/service/src/metrics.rs +++ b/client/service/src/metrics.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -22,8 +22,8 @@ use crate::config::Configuration; use futures_timer::Delay; use prometheus_endpoint::{register, Gauge, GaugeVec, Opts, PrometheusError, Registry, U64}; use sc_client_api::{ClientInfo, UsageProvider}; -use sc_network::config::Role; -use sc_network_common::service::{NetworkStatus, NetworkStatusProvider}; +use sc_network::{config::Role, NetworkStatus, NetworkStatusProvider}; +use sc_network_common::sync::{SyncStatus, SyncStatusProvider}; use sc_telemetry::{telemetry, TelemetryHandle, SUBSTRATE_INFO}; use sc_transaction_pool_api::{MaintainedTransactionPool, PoolStatus}; use sc_utils::metrics::register_globals; @@ -175,16 +175,18 @@ impl MetricsService { /// Returns a never-ending `Future` that performs the /// metric and telemetry updates with information from /// the given sources. - pub async fn run( + pub async fn run( mut self, client: Arc, transactions: Arc, network: TNet, + syncing: TSync, ) where TBl: Block, TCl: ProvideRuntimeApi + UsageProvider, TExPool: MaintainedTransactionPool::Hash>, - TNet: NetworkStatusProvider, + TNet: NetworkStatusProvider, + TSync: SyncStatusProvider, { let mut timer = Delay::new(Duration::from_secs(0)); let timer_interval = Duration::from_secs(5); @@ -196,8 +198,11 @@ impl MetricsService { // Try to get the latest network information. let net_status = network.status().await.ok(); + // Try to get the latest syncing information. + let sync_status = syncing.status().await.ok(); + // Update / Send the metrics. - self.update(&client.usage_info(), &transactions.status(), net_status); + self.update(&client.usage_info(), &transactions.status(), net_status, sync_status); // Schedule next tick. timer.reset(timer_interval); @@ -208,7 +213,8 @@ impl MetricsService { &mut self, info: &ClientInfo, txpool_status: &PoolStatus, - net_status: Option>, + net_status: Option, + sync_status: Option>, ) { let now = Instant::now(); let elapsed = (now - self.last_update).as_secs(); @@ -273,10 +279,12 @@ impl MetricsService { "bandwidth_download" => avg_bytes_per_sec_inbound, "bandwidth_upload" => avg_bytes_per_sec_outbound, ); + } + if let Some(sync_status) = sync_status { if let Some(metrics) = self.metrics.as_ref() { let best_seen_block: Option = - net_status.best_seen_block.map(|num: NumberFor| { + sync_status.best_seen_block.map(|num: NumberFor| { UniqueSaturatedInto::::unique_saturated_into(num) }); diff --git a/client/service/src/task_manager/mod.rs b/client/service/src/task_manager/mod.rs index f2987e684d9d..afccee9033e3 100644 --- a/client/service/src/task_manager/mod.rs +++ b/client/service/src/task_manager/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -281,7 +281,7 @@ impl SpawnEssentialTaskHandle { let essential_failed = self.essential_failed_tx.clone(); let essential_task = std::panic::AssertUnwindSafe(task).catch_unwind().map(move |_| { log::error!("Essential task `{}` failed. Shutting down service.", name); - let _ = essential_failed.close_channel(); + let _ = essential_failed.close(); }); let _ = self.inner.spawn_inner(name, group, essential_task, task_type); diff --git a/client/service/src/task_manager/prometheus_future.rs b/client/service/src/task_manager/prometheus_future.rs index 88296a67336f..51bf7fea496a 100644 --- a/client/service/src/task_manager/prometheus_future.rs +++ b/client/service/src/task_manager/prometheus_future.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/service/src/task_manager/tests.rs b/client/service/src/task_manager/tests.rs index 323110a4e1b0..27be4f9a8a26 100644 --- a/client/service/src/task_manager/tests.rs +++ b/client/service/src/task_manager/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/service/test/Cargo.toml b/client/service/test/Cargo.toml index 488fe76c333b..dea7bfaa5b7d 100644 --- a/client/service/test/Cargo.toml +++ b/client/service/test/Cargo.toml @@ -12,11 +12,12 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -array-bytes = "4.1" +async-channel = "1.8.0" +array-bytes = "6.1" fdlimit = "0.2.1" futures = "0.3.21" log = "0.4.17" -parity-scale-codec = "3.2.2" +parity-scale-codec = "3.6.1" parking_lot = "0.12.1" tempfile = "3.1.0" tokio = { version = "1.22.0", features = ["time"] } @@ -26,18 +27,18 @@ sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../.. sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/common" } sc-executor = { version = "0.10.0-dev", path = "../../executor" } sc-network = { version = "0.10.0-dev", path = "../../network" } -sc-network-common = { version = "0.10.0-dev", path = "../../network/common" } +sc-network-sync = { version = "0.10.0-dev", path = "../../network/sync" } sc-service = { version = "0.10.0-dev", features = ["test-helpers"], path = "../../service" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } -sp-state-machine = { version = "0.13.0", path = "../../../primitives/state-machine" } -sp-storage = { version = "7.0.0", path = "../../../primitives/storage" } -sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } -sp-trie = { version = "7.0.0", path = "../../../primitives/trie" } -sp-io = { version = "7.0.0", path = "../../../primitives/io" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } +sp-state-machine = { version = "0.28.0", path = "../../../primitives/state-machine" } +sp-storage = { version = "13.0.0", path = "../../../primitives/storage" } +sp-tracing = { version = "10.0.0", path = "../../../primitives/tracing" } +sp-trie = { version = "22.0.0", path = "../../../primitives/trie" } +sp-io = { version = "23.0.0", path = "../../../primitives/io" } substrate-test-runtime = { version = "2.0.0", path = "../../../test-utils/runtime" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/client/service/test/src/client/db.rs b/client/service/test/src/client/db.rs index 5c1315fc870c..def1a41e62bb 100644 --- a/client/service/test/src/client/db.rs +++ b/client/service/test/src/client/db.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/service/test/src/client/mod.rs b/client/service/test/src/client/mod.rs index 97c22a1cb509..c40ac33da4bb 100644 --- a/client/service/test/src/client/mod.rs +++ b/client/service/test/src/client/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,6 +16,7 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +use async_channel::TryRecvError; use futures::executor::block_on; use parity_scale_codec::{Decode, Encode, Joiner}; use sc_block_builder::BlockBuilderProvider; @@ -29,24 +30,24 @@ use sc_consensus::{ }; use sc_service::client::{new_in_mem, Client, LocalCallExecutor}; use sp_api::ProvideRuntimeApi; -use sp_consensus::{BlockOrigin, BlockStatus, Error as ConsensusError, SelectChain}; -use sp_core::{testing::TaskExecutor, H256}; +use sp_consensus::{BlockOrigin, Error as ConsensusError, SelectChain}; +use sp_core::{testing::TaskExecutor, traits::CallContext, H256}; use sp_runtime::{ generic::BlockId, traits::{BlakeTwo256, Block as BlockT, Header as HeaderT}, ConsensusEngineId, Justifications, StateVersion, }; -use sp_state_machine::{ - backend::Backend as _, ExecutionStrategy, InMemoryBackend, OverlayedChanges, StateMachine, -}; +use sp_state_machine::{backend::Backend as _, InMemoryBackend, OverlayedChanges, StateMachine}; use sp_storage::{ChildInfo, StorageKey}; use sp_trie::{LayoutV0, TrieConfiguration}; use std::{collections::HashSet, sync::Arc}; use substrate_test_runtime::TestAPI; use substrate_test_runtime_client::{ + new_native_or_wasm_executor, prelude::*, runtime::{ - genesismap::{insert_genesis_block, GenesisConfig}, + currency::DOLLARS, + genesismap::{insert_genesis_block, GenesisStorageBuilder}, Block, BlockNumber, Digest, Hash, Header, RuntimeApi, Transfer, }, AccountKeyring, BlockBuilderExt, ClientBlockImportExt, ClientExt, DefaultTestClientBuilderExt, @@ -57,29 +58,6 @@ mod db; const TEST_ENGINE_ID: ConsensusEngineId = *b"TEST"; -pub struct ExecutorDispatch; - -impl sc_executor::NativeExecutionDispatch for ExecutorDispatch { - type ExtendHostFunctions = (); - - fn dispatch(method: &str, data: &[u8]) -> Option> { - substrate_test_runtime_client::runtime::api::dispatch(method, data) - } - - fn native_version() -> sc_executor::NativeVersion { - substrate_test_runtime_client::runtime::native_version() - } -} - -fn executor() -> sc_executor::NativeElseWasmExecutor { - sc_executor::NativeElseWasmExecutor::new( - sc_executor::WasmExecutionMethod::Interpreted, - None, - 8, - 2, - ) -} - fn construct_block( backend: &InMemoryBackend, number: BlockNumber, @@ -87,7 +65,7 @@ fn construct_block( state_root: Hash, txs: Vec, ) -> (Vec, Hash) { - let transactions = txs.into_iter().map(|tx| tx.into_signed_tx()).collect::>(); + let transactions = txs.into_iter().map(|tx| tx.into_unchecked_extrinsic()).collect::>(); let iter = transactions.iter().map(Encode::encode); let extrinsics_root = LayoutV0::::ordered_trie_root(iter).into(); @@ -103,47 +81,46 @@ fn construct_block( let mut overlay = OverlayedChanges::default(); let backend_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(backend); let runtime_code = backend_runtime_code.runtime_code().expect("Code is part of the backend"); - let task_executor = Box::new(TaskExecutor::new()); StateMachine::new( backend, &mut overlay, - &executor(), + &new_native_or_wasm_executor(), "Core_initialize_block", &header.encode(), - Default::default(), + &mut Default::default(), &runtime_code, - task_executor.clone() as Box<_>, + CallContext::Onchain, ) - .execute(ExecutionStrategy::NativeElseWasm) + .execute() .unwrap(); for tx in transactions.iter() { StateMachine::new( backend, &mut overlay, - &executor(), + &new_native_or_wasm_executor(), "BlockBuilder_apply_extrinsic", &tx.encode(), - Default::default(), + &mut Default::default(), &runtime_code, - task_executor.clone() as Box<_>, + CallContext::Onchain, ) - .execute(ExecutionStrategy::NativeElseWasm) + .execute() .unwrap(); } let ret_data = StateMachine::new( backend, &mut overlay, - &executor(), + &new_native_or_wasm_executor(), "BlockBuilder_finalize_block", &[], - Default::default(), + &mut Default::default(), &runtime_code, - task_executor.clone() as Box<_>, + CallContext::Onchain, ) - .execute(ExecutionStrategy::NativeElseWasm) + .execute() .unwrap(); header = Header::decode(&mut &ret_data[..]).unwrap(); @@ -159,9 +136,9 @@ fn block1(genesis_hash: Hash, backend: &InMemoryBackend) -> (Vec { + match notifications.try_recv() { + Ok(notif) => { let stale_heads_expected: HashSet<_> = stale_heads.iter().collect(); let stale_heads: HashSet<_> = notif.stale_heads.iter().collect(); assert_eq!(notif.tree_route.as_ref(), &finalized[..finalized.len() - 1]); assert_eq!(notif.hash, *finalized.last().unwrap()); assert_eq!(stale_heads, stale_heads_expected); }, - Ok(None) => panic!("unexpected notification result, client send channel was closed"), - Err(_) => assert!(finalized.is_empty()), + Err(TryRecvError::Closed) => { + panic!("unexpected notification result, client send channel was closed") + }, + Err(TryRecvError::Empty) => assert!(finalized.is_empty()), } } #[test] fn construct_genesis_should_work_with_native() { - let mut storage = GenesisConfig::new( + let mut storage = GenesisStorageBuilder::new( vec![Sr25519Keyring::One.public().into(), Sr25519Keyring::Two.public().into()], vec![AccountKeyring::One.into(), AccountKeyring::Two.into()], - 1000, - None, - Default::default(), + 1000 * DOLLARS, ) - .genesis_map(); + .build(); let genesis_hash = insert_genesis_block(&mut storage); let backend = InMemoryBackend::from((storage, StateVersion::default())); @@ -207,27 +184,25 @@ fn construct_genesis_should_work_with_native() { let _ = StateMachine::new( &backend, &mut overlay, - &executor(), + &new_native_or_wasm_executor(), "Core_execute_block", &b1data, - Default::default(), + &mut Default::default(), &runtime_code, - TaskExecutor::new(), + CallContext::Onchain, ) - .execute(ExecutionStrategy::NativeElseWasm) + .execute() .unwrap(); } #[test] fn construct_genesis_should_work_with_wasm() { - let mut storage = GenesisConfig::new( + let mut storage = GenesisStorageBuilder::new( vec![Sr25519Keyring::One.public().into(), Sr25519Keyring::Two.public().into()], vec![AccountKeyring::One.into(), AccountKeyring::Two.into()], - 1000, - None, - Default::default(), + 1000 * DOLLARS, ) - .genesis_map(); + .build(); let genesis_hash = insert_genesis_block(&mut storage); let backend = InMemoryBackend::from((storage, StateVersion::default())); @@ -240,50 +215,17 @@ fn construct_genesis_should_work_with_wasm() { let _ = StateMachine::new( &backend, &mut overlay, - &executor(), + &new_native_or_wasm_executor(), "Core_execute_block", &b1data, - Default::default(), + &mut Default::default(), &runtime_code, - TaskExecutor::new(), + CallContext::Onchain, ) - .execute(ExecutionStrategy::AlwaysWasm) + .execute() .unwrap(); } -#[test] -fn construct_genesis_with_bad_transaction_should_panic() { - let mut storage = GenesisConfig::new( - vec![Sr25519Keyring::One.public().into(), Sr25519Keyring::Two.public().into()], - vec![AccountKeyring::One.into(), AccountKeyring::Two.into()], - 68, - None, - Default::default(), - ) - .genesis_map(); - let genesis_hash = insert_genesis_block(&mut storage); - - let backend = InMemoryBackend::from((storage, StateVersion::default())); - let (b1data, _b1hash) = block1(genesis_hash, &backend); - let backend_runtime_code = sp_state_machine::backend::BackendRuntimeCode::new(&backend); - let runtime_code = backend_runtime_code.runtime_code().expect("Code is part of the backend"); - - let mut overlay = OverlayedChanges::default(); - - let r = StateMachine::new( - &backend, - &mut overlay, - &executor(), - "Core_execute_block", - &b1data, - Default::default(), - &runtime_code, - TaskExecutor::new(), - ) - .execute(ExecutionStrategy::NativeElseWasm); - assert!(r.is_err()); -} - #[test] fn client_initializes_from_genesis_ok() { let client = substrate_test_runtime_client::new(); @@ -291,22 +233,16 @@ fn client_initializes_from_genesis_ok() { assert_eq!( client .runtime_api() - .balance_of( - &BlockId::Number(client.chain_info().best_number), - AccountKeyring::Alice.into(), - ) + .balance_of(client.chain_info().best_hash, AccountKeyring::Alice.into()) .unwrap(), - 1000 + 1000 * DOLLARS ); assert_eq!( client .runtime_api() - .balance_of( - &BlockId::Number(client.chain_info().best_number), - AccountKeyring::Ferdie.into(), - ) + .balance_of(client.chain_info().best_hash, AccountKeyring::Ferdie.into()) .unwrap(), - 0 + 0 * DOLLARS ); } @@ -331,7 +267,7 @@ fn block_builder_works_with_transactions() { .push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 42, + amount: 42 * DOLLARS, nonce: 0, }) .unwrap(); @@ -347,54 +283,62 @@ fn block_builder_works_with_transactions() { .expect("block 1 was just imported. qed"); assert_eq!(client.chain_info().best_number, 1); - assert_ne!(client.state_at(hash1).unwrap().pairs(), client.state_at(hash0).unwrap().pairs()); + assert_ne!( + client + .state_at(hash1) + .unwrap() + .pairs(Default::default()) + .unwrap() + .collect::>(), + client + .state_at(hash0) + .unwrap() + .pairs(Default::default()) + .unwrap() + .collect::>() + ); assert_eq!( client .runtime_api() - .balance_of( - &BlockId::Number(client.chain_info().best_number), - AccountKeyring::Alice.into(), - ) + .balance_of(client.chain_info().best_hash, AccountKeyring::Alice.into()) .unwrap(), - 958 + 958 * DOLLARS ); assert_eq!( client .runtime_api() - .balance_of( - &BlockId::Number(client.chain_info().best_number), - AccountKeyring::Ferdie.into(), - ) + .balance_of(client.chain_info().best_hash, AccountKeyring::Ferdie.into()) .unwrap(), - 42 + 42 * DOLLARS ); } #[test] fn block_builder_does_not_include_invalid() { let mut client = substrate_test_runtime_client::new(); - let mut builder = client.new_block(Default::default()).unwrap(); builder .push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 42, + amount: 42 * DOLLARS, nonce: 0, }) .unwrap(); assert!(builder .push_transfer(Transfer { - from: AccountKeyring::Eve.into(), - to: AccountKeyring::Alice.into(), - amount: 42, + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Ferdie.into(), + amount: 30 * DOLLARS, nonce: 0, }) .is_err()); let block = builder.build().unwrap().block; + //transfer from Eve should not be included + assert_eq!(block.extrinsics.len(), 1); block_on(client.import(BlockOrigin::Own, block)).unwrap(); let hashof0 = client @@ -406,8 +350,18 @@ fn block_builder_does_not_include_invalid() { assert_eq!(client.chain_info().best_number, 1); assert_ne!( - client.state_at(hashof1).unwrap().pairs(), - client.state_at(hashof0).unwrap().pairs() + client + .state_at(hashof1) + .unwrap() + .pairs(Default::default()) + .unwrap() + .collect::>(), + client + .state_at(hashof0) + .unwrap() + .pairs(Default::default()) + .unwrap() + .collect::>() ); assert_eq!(client.body(hashof1).unwrap().unwrap().len(), 1) } @@ -459,7 +413,7 @@ fn uncles_with_multiple_forks() { // A1 -> A2 let a2 = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) + .new_block_at(a1.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -468,7 +422,7 @@ fn uncles_with_multiple_forks() { // A2 -> A3 let a3 = client - .new_block_at(&BlockId::Hash(a2.hash()), Default::default(), false) + .new_block_at(a2.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -477,7 +431,7 @@ fn uncles_with_multiple_forks() { // A3 -> A4 let a4 = client - .new_block_at(&BlockId::Hash(a3.hash()), Default::default(), false) + .new_block_at(a3.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -486,7 +440,7 @@ fn uncles_with_multiple_forks() { // A4 -> A5 let a5 = client - .new_block_at(&BlockId::Hash(a4.hash()), Default::default(), false) + .new_block_at(a4.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -494,15 +448,13 @@ fn uncles_with_multiple_forks() { block_on(client.import(BlockOrigin::Own, a5.clone())).unwrap(); // A1 -> B2 - let mut builder = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) - .unwrap(); + let mut builder = client.new_block_at(a1.hash(), Default::default(), false).unwrap(); // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder .push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 41, + amount: 41 * DOLLARS, nonce: 0, }) .unwrap(); @@ -511,7 +463,7 @@ fn uncles_with_multiple_forks() { // B2 -> B3 let b3 = client - .new_block_at(&BlockId::Hash(b2.hash()), Default::default(), false) + .new_block_at(b2.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -520,7 +472,7 @@ fn uncles_with_multiple_forks() { // B3 -> B4 let b4 = client - .new_block_at(&BlockId::Hash(b3.hash()), Default::default(), false) + .new_block_at(b3.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -528,15 +480,13 @@ fn uncles_with_multiple_forks() { block_on(client.import(BlockOrigin::Own, b4.clone())).unwrap(); // // B2 -> C3 - let mut builder = client - .new_block_at(&BlockId::Hash(b2.hash()), Default::default(), false) - .unwrap(); + let mut builder = client.new_block_at(b2.hash(), Default::default(), false).unwrap(); // this push is required as otherwise C3 has the same hash as B3 and won't get imported builder .push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 1, + amount: 1 * DOLLARS, nonce: 1, }) .unwrap(); @@ -544,15 +494,13 @@ fn uncles_with_multiple_forks() { block_on(client.import(BlockOrigin::Own, c3.clone())).unwrap(); // A1 -> D2 - let mut builder = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) - .unwrap(); + let mut builder = client.new_block_at(a1.hash(), Default::default(), false).unwrap(); // this push is required as otherwise D2 has the same hash as B2 and won't get imported builder .push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 1, + amount: 1 * DOLLARS, nonce: 0, }) .unwrap(); @@ -581,7 +529,7 @@ fn uncles_with_multiple_forks() { } #[test] -fn best_containing_on_longest_chain_with_single_chain_3_blocks() { +fn finality_target_on_longest_chain_with_single_chain_3_blocks() { // block tree: // G -> A1 -> A2 @@ -606,7 +554,7 @@ fn best_containing_on_longest_chain_with_single_chain_3_blocks() { } #[test] -fn best_containing_on_longest_chain_with_multiple_forks() { +fn finality_target_on_longest_chain_with_multiple_forks() { // block tree: // G -> A1 -> A2 -> A3 -> A4 -> A5 // A1 -> B2 -> B3 -> B4 @@ -620,7 +568,7 @@ fn best_containing_on_longest_chain_with_multiple_forks() { // A1 -> A2 let a2 = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) + .new_block_at(a1.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -629,7 +577,7 @@ fn best_containing_on_longest_chain_with_multiple_forks() { // A2 -> A3 let a3 = client - .new_block_at(&BlockId::Hash(a2.hash()), Default::default(), false) + .new_block_at(a2.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -638,7 +586,7 @@ fn best_containing_on_longest_chain_with_multiple_forks() { // A3 -> A4 let a4 = client - .new_block_at(&BlockId::Hash(a3.hash()), Default::default(), false) + .new_block_at(a3.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -647,7 +595,7 @@ fn best_containing_on_longest_chain_with_multiple_forks() { // A4 -> A5 let a5 = client - .new_block_at(&BlockId::Hash(a4.hash()), Default::default(), false) + .new_block_at(a4.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -655,15 +603,13 @@ fn best_containing_on_longest_chain_with_multiple_forks() { block_on(client.import(BlockOrigin::Own, a5.clone())).unwrap(); // A1 -> B2 - let mut builder = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) - .unwrap(); + let mut builder = client.new_block_at(a1.hash(), Default::default(), false).unwrap(); // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder .push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 41, + amount: 41 * DOLLARS, nonce: 0, }) .unwrap(); @@ -672,7 +618,7 @@ fn best_containing_on_longest_chain_with_multiple_forks() { // B2 -> B3 let b3 = client - .new_block_at(&BlockId::Hash(b2.hash()), Default::default(), false) + .new_block_at(b2.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -681,7 +627,7 @@ fn best_containing_on_longest_chain_with_multiple_forks() { // B3 -> B4 let b4 = client - .new_block_at(&BlockId::Hash(b3.hash()), Default::default(), false) + .new_block_at(b3.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -689,15 +635,13 @@ fn best_containing_on_longest_chain_with_multiple_forks() { block_on(client.import(BlockOrigin::Own, b4.clone())).unwrap(); // B2 -> C3 - let mut builder = client - .new_block_at(&BlockId::Hash(b2.hash()), Default::default(), false) - .unwrap(); + let mut builder = client.new_block_at(b2.hash(), Default::default(), false).unwrap(); // this push is required as otherwise C3 has the same hash as B3 and won't get imported builder .push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 1, + amount: 1 * DOLLARS, nonce: 1, }) .unwrap(); @@ -705,15 +649,13 @@ fn best_containing_on_longest_chain_with_multiple_forks() { block_on(client.import(BlockOrigin::Own, c3.clone())).unwrap(); // A1 -> D2 - let mut builder = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) - .unwrap(); + let mut builder = client.new_block_at(a1.hash(), Default::default(), false).unwrap(); // this push is required as otherwise D2 has the same hash as B2 and won't get imported builder .push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 1, + amount: 1 * DOLLARS, nonce: 0, }) .unwrap(); @@ -731,8 +673,13 @@ fn best_containing_on_longest_chain_with_multiple_forks() { assert!(leaves.contains(&d2.hash())); assert_eq!(leaves.len(), 4); - let finality_target = |target_hash, number| { - block_on(longest_chain_select.finality_target(target_hash, number)).unwrap() + // On error we return a quite improbable hash + let error_hash = Hash::from([0xff; 32]); + let finality_target = |target_hash, number| match block_on( + longest_chain_select.finality_target(target_hash, number), + ) { + Ok(hash) => hash, + Err(_) => error_hash, }; // search without restriction @@ -742,11 +689,11 @@ fn best_containing_on_longest_chain_with_multiple_forks() { assert_eq!(a5.hash(), finality_target(a3.hash(), None)); assert_eq!(a5.hash(), finality_target(a4.hash(), None)); assert_eq!(a5.hash(), finality_target(a5.hash(), None)); - assert_eq!(b4.hash(), finality_target(b2.hash(), None)); - assert_eq!(b4.hash(), finality_target(b3.hash(), None)); - assert_eq!(b4.hash(), finality_target(b4.hash(), None)); - assert_eq!(c3.hash(), finality_target(c3.hash(), None)); - assert_eq!(d2.hash(), finality_target(d2.hash(), None)); + assert_eq!(error_hash, finality_target(b2.hash(), None)); + assert_eq!(error_hash, finality_target(b3.hash(), None)); + assert_eq!(error_hash, finality_target(b4.hash(), None)); + assert_eq!(error_hash, finality_target(c3.hash(), None)); + assert_eq!(error_hash, finality_target(d2.hash(), None)); // search only blocks with number <= 5. equivalent to without restriction for this scenario assert_eq!(a5.hash(), finality_target(genesis_hash, Some(5))); @@ -755,11 +702,11 @@ fn best_containing_on_longest_chain_with_multiple_forks() { assert_eq!(a5.hash(), finality_target(a3.hash(), Some(5))); assert_eq!(a5.hash(), finality_target(a4.hash(), Some(5))); assert_eq!(a5.hash(), finality_target(a5.hash(), Some(5))); - assert_eq!(b4.hash(), finality_target(b2.hash(), Some(5))); - assert_eq!(b4.hash(), finality_target(b3.hash(), Some(5))); - assert_eq!(b4.hash(), finality_target(b4.hash(), Some(5))); - assert_eq!(c3.hash(), finality_target(c3.hash(), Some(5))); - assert_eq!(d2.hash(), finality_target(d2.hash(), Some(5))); + assert_eq!(error_hash, finality_target(b2.hash(), Some(5))); + assert_eq!(error_hash, finality_target(b3.hash(), Some(5))); + assert_eq!(error_hash, finality_target(b4.hash(), Some(5))); + assert_eq!(error_hash, finality_target(c3.hash(), Some(5))); + assert_eq!(error_hash, finality_target(d2.hash(), Some(5))); // search only blocks with number <= 4 assert_eq!(a4.hash(), finality_target(genesis_hash, Some(4))); @@ -767,73 +714,72 @@ fn best_containing_on_longest_chain_with_multiple_forks() { assert_eq!(a4.hash(), finality_target(a2.hash(), Some(4))); assert_eq!(a4.hash(), finality_target(a3.hash(), Some(4))); assert_eq!(a4.hash(), finality_target(a4.hash(), Some(4))); - assert_eq!(a5.hash(), finality_target(a5.hash(), Some(4))); - assert_eq!(b4.hash(), finality_target(b2.hash(), Some(4))); - assert_eq!(b4.hash(), finality_target(b3.hash(), Some(4))); - assert_eq!(b4.hash(), finality_target(b4.hash(), Some(4))); - assert_eq!(c3.hash(), finality_target(c3.hash(), Some(4))); - assert_eq!(d2.hash(), finality_target(d2.hash(), Some(4))); + assert_eq!(error_hash, finality_target(a5.hash(), Some(4))); + assert_eq!(error_hash, finality_target(b2.hash(), Some(4))); + assert_eq!(error_hash, finality_target(b3.hash(), Some(4))); + assert_eq!(error_hash, finality_target(b4.hash(), Some(4))); + assert_eq!(error_hash, finality_target(c3.hash(), Some(4))); + assert_eq!(error_hash, finality_target(d2.hash(), Some(4))); // search only blocks with number <= 3 assert_eq!(a3.hash(), finality_target(genesis_hash, Some(3))); assert_eq!(a3.hash(), finality_target(a1.hash(), Some(3))); assert_eq!(a3.hash(), finality_target(a2.hash(), Some(3))); assert_eq!(a3.hash(), finality_target(a3.hash(), Some(3))); - assert_eq!(a4.hash(), finality_target(a4.hash(), Some(3))); - assert_eq!(a5.hash(), finality_target(a5.hash(), Some(3))); - assert_eq!(b3.hash(), finality_target(b2.hash(), Some(3))); - assert_eq!(b3.hash(), finality_target(b3.hash(), Some(3))); - assert_eq!(b4.hash(), finality_target(b4.hash(), Some(3))); - assert_eq!(c3.hash(), finality_target(c3.hash(), Some(3))); - assert_eq!(d2.hash(), finality_target(d2.hash(), Some(3))); + assert_eq!(error_hash, finality_target(a4.hash(), Some(3))); + assert_eq!(error_hash, finality_target(a5.hash(), Some(3))); + assert_eq!(error_hash, finality_target(b2.hash(), Some(3))); + assert_eq!(error_hash, finality_target(b3.hash(), Some(3))); + assert_eq!(error_hash, finality_target(b4.hash(), Some(3))); + assert_eq!(error_hash, finality_target(c3.hash(), Some(3))); + assert_eq!(error_hash, finality_target(d2.hash(), Some(3))); // search only blocks with number <= 2 assert_eq!(a2.hash(), finality_target(genesis_hash, Some(2))); assert_eq!(a2.hash(), finality_target(a1.hash(), Some(2))); assert_eq!(a2.hash(), finality_target(a2.hash(), Some(2))); - assert_eq!(a3.hash(), finality_target(a3.hash(), Some(2))); - assert_eq!(a4.hash(), finality_target(a4.hash(), Some(2))); - assert_eq!(a5.hash(), finality_target(a5.hash(), Some(2))); - assert_eq!(b2.hash(), finality_target(b2.hash(), Some(2))); - assert_eq!(b3.hash(), finality_target(b3.hash(), Some(2))); - assert_eq!(b4.hash(), finality_target(b4.hash(), Some(2))); - assert_eq!(c3.hash(), finality_target(c3.hash(), Some(2))); - assert_eq!(d2.hash(), finality_target(d2.hash(), Some(2))); + assert_eq!(error_hash, finality_target(a3.hash(), Some(2))); + assert_eq!(error_hash, finality_target(a4.hash(), Some(2))); + assert_eq!(error_hash, finality_target(a5.hash(), Some(2))); + assert_eq!(error_hash, finality_target(b2.hash(), Some(2))); + assert_eq!(error_hash, finality_target(b3.hash(), Some(2))); + assert_eq!(error_hash, finality_target(b4.hash(), Some(2))); + assert_eq!(error_hash, finality_target(c3.hash(), Some(2))); + assert_eq!(error_hash, finality_target(d2.hash(), Some(2))); // search only blocks with number <= 1 assert_eq!(a1.hash(), finality_target(genesis_hash, Some(1))); assert_eq!(a1.hash(), finality_target(a1.hash(), Some(1))); - assert_eq!(a2.hash(), finality_target(a2.hash(), Some(1))); - assert_eq!(a3.hash(), finality_target(a3.hash(), Some(1))); - assert_eq!(a4.hash(), finality_target(a4.hash(), Some(1))); - assert_eq!(a5.hash(), finality_target(a5.hash(), Some(1))); - - assert_eq!(b2.hash(), finality_target(b2.hash(), Some(1))); - assert_eq!(b3.hash(), finality_target(b3.hash(), Some(1))); - assert_eq!(b4.hash(), finality_target(b4.hash(), Some(1))); - assert_eq!(c3.hash(), finality_target(c3.hash(), Some(1))); - assert_eq!(d2.hash(), finality_target(d2.hash(), Some(1))); + assert_eq!(error_hash, finality_target(a2.hash(), Some(1))); + assert_eq!(error_hash, finality_target(a3.hash(), Some(1))); + assert_eq!(error_hash, finality_target(a4.hash(), Some(1))); + assert_eq!(error_hash, finality_target(a5.hash(), Some(1))); + assert_eq!(error_hash, finality_target(b2.hash(), Some(1))); + assert_eq!(error_hash, finality_target(b3.hash(), Some(1))); + assert_eq!(error_hash, finality_target(b4.hash(), Some(1))); + assert_eq!(error_hash, finality_target(c3.hash(), Some(1))); + assert_eq!(error_hash, finality_target(d2.hash(), Some(1))); // search only blocks with number <= 0 assert_eq!(genesis_hash, finality_target(genesis_hash, Some(0))); - assert_eq!(a1.hash(), finality_target(a1.hash(), Some(0))); - assert_eq!(a2.hash(), finality_target(a2.hash(), Some(0))); - assert_eq!(a3.hash(), finality_target(a3.hash(), Some(0))); - assert_eq!(a4.hash(), finality_target(a4.hash(), Some(0))); - assert_eq!(a5.hash(), finality_target(a5.hash(), Some(0))); - assert_eq!(b2.hash(), finality_target(b2.hash(), Some(0))); - assert_eq!(b3.hash(), finality_target(b3.hash(), Some(0))); - assert_eq!(b4.hash(), finality_target(b4.hash(), Some(0))); - assert_eq!(c3.hash(), finality_target(c3.hash(), Some(0))); - assert_eq!(d2.hash(), finality_target(d2.hash(), Some(0))); + assert_eq!(error_hash, finality_target(a1.hash(), Some(0))); + assert_eq!(error_hash, finality_target(a2.hash(), Some(0))); + assert_eq!(error_hash, finality_target(a3.hash(), Some(0))); + assert_eq!(error_hash, finality_target(a4.hash(), Some(0))); + assert_eq!(error_hash, finality_target(a5.hash(), Some(0))); + assert_eq!(error_hash, finality_target(b2.hash(), Some(0))); + assert_eq!(error_hash, finality_target(b3.hash(), Some(0))); + assert_eq!(error_hash, finality_target(b4.hash(), Some(0))); + assert_eq!(error_hash, finality_target(c3.hash(), Some(0))); + assert_eq!(error_hash, finality_target(d2.hash(), Some(0))); } #[test] -fn best_containing_on_longest_chain_with_max_depth_higher_than_best() { +fn finality_target_on_longest_chain_with_max_depth_higher_than_best() { // block tree: // G -> A1 -> A2 - let (mut client, longest_chain_select) = TestClientBuilder::new().build_with_longest_chain(); + let (mut client, chain_select) = TestClientBuilder::new().build_with_longest_chain(); // G -> A1 let a1 = client.new_block(Default::default()).unwrap().build().unwrap().block; @@ -845,10 +791,91 @@ fn best_containing_on_longest_chain_with_max_depth_higher_than_best() { let genesis_hash = client.chain_info().genesis_hash; - assert_eq!( - a2.hash(), - block_on(longest_chain_select.finality_target(genesis_hash, Some(10))).unwrap(), - ); + assert_eq!(a2.hash(), block_on(chain_select.finality_target(genesis_hash, Some(10))).unwrap(),); +} + +#[test] +fn finality_target_with_best_not_on_longest_chain() { + // block tree: + // G -> A1 -> A2 -> A3 -> A4 -> A5 + // -> B2 -> (B3) -> B4 + // ^best + + let (mut client, chain_select) = TestClientBuilder::new().build_with_longest_chain(); + let genesis_hash = client.chain_info().genesis_hash; + + // G -> A1 + let a1 = client.new_block(Default::default()).unwrap().build().unwrap().block; + block_on(client.import(BlockOrigin::Own, a1.clone())).unwrap(); + + // A1 -> A2 + let a2 = client.new_block(Default::default()).unwrap().build().unwrap().block; + block_on(client.import(BlockOrigin::Own, a2.clone())).unwrap(); + + // A2 -> A3 + let a3 = client.new_block(Default::default()).unwrap().build().unwrap().block; + block_on(client.import(BlockOrigin::Own, a3.clone())).unwrap(); + + // A3 -> A4 + let a4 = client.new_block(Default::default()).unwrap().build().unwrap().block; + block_on(client.import(BlockOrigin::Own, a4.clone())).unwrap(); + + // A3 -> A5 + let a5 = client.new_block(Default::default()).unwrap().build().unwrap().block; + block_on(client.import(BlockOrigin::Own, a5.clone())).unwrap(); + + // A1 -> B2 + let mut builder = client.new_block_at(a1.hash(), Default::default(), false).unwrap(); + // this push is required as otherwise B2 has the same hash as A2 and won't get imported + builder + .push_transfer(Transfer { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Ferdie.into(), + amount: 41 * DOLLARS, + nonce: 0, + }) + .unwrap(); + let b2 = builder.build().unwrap().block; + block_on(client.import(BlockOrigin::Own, b2.clone())).unwrap(); + + assert_eq!(a5.hash(), block_on(chain_select.finality_target(genesis_hash, None)).unwrap()); + assert_eq!(a5.hash(), block_on(chain_select.finality_target(a1.hash(), None)).unwrap()); + assert_eq!(a5.hash(), block_on(chain_select.finality_target(a2.hash(), None)).unwrap()); + assert_eq!(a5.hash(), block_on(chain_select.finality_target(a3.hash(), None)).unwrap()); + assert_eq!(a5.hash(), block_on(chain_select.finality_target(a4.hash(), None)).unwrap()); + assert_eq!(a5.hash(), block_on(chain_select.finality_target(a5.hash(), None)).unwrap()); + + // B2 -> B3 + let b3 = client + .new_block_at(b2.hash(), Default::default(), false) + .unwrap() + .build() + .unwrap() + .block; + block_on(client.import_as_best(BlockOrigin::Own, b3.clone())).unwrap(); + + // B3 -> B4 + let b4 = client + .new_block_at(b3.hash(), Default::default(), false) + .unwrap() + .build() + .unwrap() + .block; + let (header, extrinsics) = b4.clone().deconstruct(); + let mut import_params = BlockImportParams::new(BlockOrigin::Own, header); + import_params.body = Some(extrinsics); + import_params.fork_choice = Some(ForkChoiceStrategy::Custom(false)); + block_on(client.import_block(import_params)).unwrap(); + + // double check that B3 is still the best... + assert_eq!(client.info().best_hash, b3.hash()); + + assert_eq!(b4.hash(), block_on(chain_select.finality_target(genesis_hash, None)).unwrap()); + assert_eq!(b4.hash(), block_on(chain_select.finality_target(a1.hash(), None)).unwrap()); + assert!(block_on(chain_select.finality_target(a2.hash(), None)).is_err()); + assert_eq!(b4.hash(), block_on(chain_select.finality_target(b2.hash(), None)).unwrap()); + assert_eq!(b4.hash(), block_on(chain_select.finality_target(b3.hash(), None)).unwrap()); + assert_eq!(b4.hash(), block_on(chain_select.finality_target(b4.hash(), None)).unwrap()); } #[test] @@ -865,7 +892,7 @@ fn import_with_justification() { // A1 -> A2 let a2 = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) + .new_block_at(a1.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -876,7 +903,7 @@ fn import_with_justification() { // A2 -> A3 let justification = Justifications::from((TEST_ENGINE_ID, vec![1, 2, 3])); let a3 = client - .new_block_at(&BlockId::Hash(a2.hash()), Default::default(), false) + .new_block_at(a2.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -893,7 +920,7 @@ fn import_with_justification() { finality_notification_check(&mut finality_notifications, &[a1.hash(), a2.hash()], &[]); finality_notification_check(&mut finality_notifications, &[a3.hash()], &[]); - assert!(finality_notifications.try_next().is_err()); + assert!(matches!(finality_notifications.try_recv().unwrap_err(), TryRecvError::Empty)); } #[test] @@ -907,7 +934,7 @@ fn importing_diverged_finalized_block_should_trigger_reorg() { let mut finality_notifications = client.finality_notification_stream(); let a1 = client - .new_block_at(&BlockId::Number(0), Default::default(), false) + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) .unwrap() .build() .unwrap() @@ -915,19 +942,21 @@ fn importing_diverged_finalized_block_should_trigger_reorg() { block_on(client.import(BlockOrigin::Own, a1.clone())).unwrap(); let a2 = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) + .new_block_at(a1.hash(), Default::default(), false) .unwrap() .build() .unwrap() .block; block_on(client.import(BlockOrigin::Own, a2.clone())).unwrap(); - let mut b1 = client.new_block_at(&BlockId::Number(0), Default::default(), false).unwrap(); + let mut b1 = client + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) + .unwrap(); // needed to make sure B1 gets a different hash from A1 b1.push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 1, + amount: 1 * DOLLARS, nonce: 0, }) .unwrap(); @@ -946,7 +975,7 @@ fn importing_diverged_finalized_block_should_trigger_reorg() { assert_eq!(client.chain_info().finalized_hash, b1.hash()); finality_notification_check(&mut finality_notifications, &[b1.hash()], &[a2.hash()]); - assert!(finality_notifications.try_next().is_err()); + assert!(matches!(finality_notifications.try_recv().unwrap_err(), TryRecvError::Empty)); } #[test] @@ -960,7 +989,7 @@ fn finalizing_diverged_block_should_trigger_reorg() { let mut finality_notifications = client.finality_notification_stream(); let a1 = client - .new_block_at(&BlockId::Number(0), Default::default(), false) + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) .unwrap() .build() .unwrap() @@ -968,19 +997,21 @@ fn finalizing_diverged_block_should_trigger_reorg() { block_on(client.import(BlockOrigin::Own, a1.clone())).unwrap(); let a2 = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) + .new_block_at(a1.hash(), Default::default(), false) .unwrap() .build() .unwrap() .block; block_on(client.import(BlockOrigin::Own, a2.clone())).unwrap(); - let mut b1 = client.new_block_at(&BlockId::Number(0), Default::default(), false).unwrap(); + let mut b1 = client + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) + .unwrap(); // needed to make sure B1 gets a different hash from A1 b1.push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 1, + amount: 1 * DOLLARS, nonce: 0, }) .unwrap(); @@ -988,14 +1019,14 @@ fn finalizing_diverged_block_should_trigger_reorg() { block_on(client.import(BlockOrigin::Own, b1.clone())).unwrap(); let b2 = client - .new_block_at(&BlockId::Hash(b1.hash()), Default::default(), false) + .new_block_at(b1.hash(), Default::default(), false) .unwrap() .build() .unwrap() .block; block_on(client.import(BlockOrigin::Own, b2.clone())).unwrap(); - // A2 is the current best since it's the longest chain + // A2 is the current best since it's the (first) longest chain assert_eq!(client.chain_info().best_hash, a2.hash()); // we finalize block B1 which is on a different branch from current best @@ -1012,23 +1043,25 @@ fn finalizing_diverged_block_should_trigger_reorg() { // `SelectChain` should report B2 as best block though assert_eq!(block_on(select_chain.best_chain()).unwrap().hash(), b2.hash()); - // after we build B3 on top of B2 and import it - // it should be the new best block, + // after we build B3 on top of B2 and import it, it should be the new best block let b3 = client - .new_block_at(&BlockId::Hash(b2.hash()), Default::default(), false) + .new_block_at(b2.hash(), Default::default(), false) .unwrap() .build() .unwrap() .block; block_on(client.import(BlockOrigin::Own, b3.clone())).unwrap(); + // `SelectChain` should report B3 as best block though + assert_eq!(block_on(select_chain.best_chain()).unwrap().hash(), b3.hash()); + assert_eq!(client.chain_info().best_hash, b3.hash()); ClientExt::finalize_block(&client, b3.hash(), None).unwrap(); finality_notification_check(&mut finality_notifications, &[b1.hash()], &[]); finality_notification_check(&mut finality_notifications, &[b2.hash(), b3.hash()], &[a2.hash()]); - assert!(finality_notifications.try_next().is_err()); + assert!(matches!(finality_notifications.try_recv().unwrap_err(), TryRecvError::Empty)); } #[test] @@ -1044,7 +1077,7 @@ fn finality_notifications_content() { let mut finality_notifications = client.finality_notification_stream(); let a1 = client - .new_block_at(&BlockId::Number(0), Default::default(), false) + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) .unwrap() .build() .unwrap() @@ -1052,7 +1085,7 @@ fn finality_notifications_content() { block_on(client.import(BlockOrigin::Own, a1.clone())).unwrap(); let a2 = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) + .new_block_at(a1.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -1060,14 +1093,16 @@ fn finality_notifications_content() { block_on(client.import(BlockOrigin::Own, a2.clone())).unwrap(); let a3 = client - .new_block_at(&BlockId::Hash(a2.hash()), Default::default(), false) + .new_block_at(a2.hash(), Default::default(), false) .unwrap() .build() .unwrap() .block; block_on(client.import(BlockOrigin::Own, a3.clone())).unwrap(); - let mut b1 = client.new_block_at(&BlockId::Number(0), Default::default(), false).unwrap(); + let mut b1 = client + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) + .unwrap(); // needed to make sure B1 gets a different hash from A1 b1.push_transfer(Transfer { from: AccountKeyring::Alice.into(), @@ -1080,33 +1115,33 @@ fn finality_notifications_content() { block_on(client.import(BlockOrigin::Own, b1.clone())).unwrap(); let b2 = client - .new_block_at(&BlockId::Hash(b1.hash()), Default::default(), false) + .new_block_at(b1.hash(), Default::default(), false) .unwrap() .build() .unwrap() .block; block_on(client.import(BlockOrigin::Own, b2.clone())).unwrap(); - let mut c1 = client.new_block_at(&BlockId::Number(0), Default::default(), false).unwrap(); + let mut c1 = client + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) + .unwrap(); // needed to make sure B1 gets a different hash from A1 c1.push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 2, + amount: 2 * DOLLARS, nonce: 0, }) .unwrap(); let c1 = c1.build().unwrap().block; block_on(client.import(BlockOrigin::Own, c1.clone())).unwrap(); - let mut d3 = client - .new_block_at(&BlockId::Hash(a2.hash()), Default::default(), false) - .unwrap(); + let mut d3 = client.new_block_at(a2.hash(), Default::default(), false).unwrap(); // needed to make sure D3 gets a different hash from A3 d3.push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 2, + amount: 2 * DOLLARS, nonce: 0, }) .unwrap(); @@ -1114,7 +1149,7 @@ fn finality_notifications_content() { block_on(client.import(BlockOrigin::Own, d3.clone())).unwrap(); let d4 = client - .new_block_at(&BlockId::Hash(d3.hash()), Default::default(), false) + .new_block_at(d3.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -1129,7 +1164,7 @@ fn finality_notifications_content() { finality_notification_check(&mut finality_notifications, &[a1.hash(), a2.hash()], &[c1.hash()]); finality_notification_check(&mut finality_notifications, &[d3.hash(), d4.hash()], &[b2.hash()]); - assert!(finality_notifications.try_next().is_err()); + assert!(matches!(finality_notifications.try_recv().unwrap_err(), TryRecvError::Empty)); } #[test] @@ -1167,32 +1202,33 @@ fn state_reverted_on_reorg() { let current_balance = |client: &substrate_test_runtime_client::TestClient| { client .runtime_api() - .balance_of( - &BlockId::number(client.chain_info().best_number), - AccountKeyring::Alice.into(), - ) + .balance_of(client.chain_info().best_hash, AccountKeyring::Alice.into()) .unwrap() }; // G -> A1 -> A2 // \ // -> B1 - let mut a1 = client.new_block_at(&BlockId::Number(0), Default::default(), false).unwrap(); + let mut a1 = client + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) + .unwrap(); a1.push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Bob.into(), - amount: 10, + amount: 10 * DOLLARS, nonce: 0, }) .unwrap(); let a1 = a1.build().unwrap().block; block_on(client.import(BlockOrigin::Own, a1.clone())).unwrap(); - let mut b1 = client.new_block_at(&BlockId::Number(0), Default::default(), false).unwrap(); + let mut b1 = client + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) + .unwrap(); b1.push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 50, + amount: 50 * DOLLARS, nonce: 0, }) .unwrap(); @@ -1200,21 +1236,19 @@ fn state_reverted_on_reorg() { // Reorg to B1 block_on(client.import_as_best(BlockOrigin::Own, b1.clone())).unwrap(); - assert_eq!(950, current_balance(&client)); - let mut a2 = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) - .unwrap(); + assert_eq!(950 * DOLLARS, current_balance(&client)); + let mut a2 = client.new_block_at(a1.hash(), Default::default(), false).unwrap(); a2.push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Charlie.into(), - amount: 10, + amount: 10 * DOLLARS, nonce: 1, }) .unwrap(); let a2 = a2.build().unwrap().block; // Re-org to A2 block_on(client.import_as_best(BlockOrigin::Own, a2)).unwrap(); - assert_eq!(980, current_balance(&client)); + assert_eq!(980 * DOLLARS, current_balance(&client)); } #[test] @@ -1248,7 +1282,7 @@ fn doesnt_import_blocks_that_revert_finality() { // -> B1 -> B2 -> B3 let a1 = client - .new_block_at(&BlockId::Number(0), Default::default(), false) + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) .unwrap() .build() .unwrap() @@ -1256,20 +1290,22 @@ fn doesnt_import_blocks_that_revert_finality() { block_on(client.import(BlockOrigin::Own, a1.clone())).unwrap(); let a2 = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) + .new_block_at(a1.hash(), Default::default(), false) .unwrap() .build() .unwrap() .block; block_on(client.import(BlockOrigin::Own, a2.clone())).unwrap(); - let mut b1 = client.new_block_at(&BlockId::Number(0), Default::default(), false).unwrap(); + let mut b1 = client + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) + .unwrap(); // needed to make sure B1 gets a different hash from A1 b1.push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 1, + amount: 1 * DOLLARS, nonce: 0, }) .unwrap(); @@ -1277,7 +1313,7 @@ fn doesnt_import_blocks_that_revert_finality() { block_on(client.import(BlockOrigin::Own, b1.clone())).unwrap(); let b2 = client - .new_block_at(&BlockId::Hash(b1.hash()), Default::default(), false) + .new_block_at(b1.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -1287,7 +1323,7 @@ fn doesnt_import_blocks_that_revert_finality() { // prepare B3 before we finalize A2, because otherwise we won't be able to // read changes trie configuration after A2 is finalized let b3 = client - .new_block_at(&BlockId::Hash(b2.hash()), Default::default(), false) + .new_block_at(b2.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -1305,13 +1341,15 @@ fn doesnt_import_blocks_that_revert_finality() { // adding a C1 block which is lower than the last finalized should also // fail (with a cheaper check that doesn't require checking ancestry). - let mut c1 = client.new_block_at(&BlockId::Number(0), Default::default(), false).unwrap(); + let mut c1 = client + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) + .unwrap(); // needed to make sure C1 gets a different hash from A1 and B1 c1.push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 2, + amount: 2 * DOLLARS, nonce: 0, }) .unwrap(); @@ -1324,7 +1362,7 @@ fn doesnt_import_blocks_that_revert_finality() { assert_eq!(import_err.to_string(), expected_err.to_string()); let a3 = client - .new_block_at(&BlockId::Hash(a2.hash()), Default::default(), false) + .new_block_at(a2.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -1336,7 +1374,7 @@ fn doesnt_import_blocks_that_revert_finality() { finality_notification_check(&mut finality_notifications, &[a3.hash()], &[b2.hash()]); - assert!(finality_notifications.try_next().is_err()); + assert!(matches!(finality_notifications.try_recv().unwrap_err(), TryRecvError::Empty)); } #[test] @@ -1354,16 +1392,34 @@ fn respects_block_rules() { .build() }; + // test modus operandi: + // + // B[2] + // / + // G[0]--B[1] + // \ \ + // \ B'[2] + // \ + // B'[1] + // + // B[1] - block ok + // B'[1] - block not ok, added to block_rules::bad + // + // B[2] - block ok, correct fork for block height==2, added to block_rules::forks + // B'[2] - block not ok, (incorrect fork) + + // build B[1] let block_ok = client - .new_block_at(&BlockId::Number(0), Default::default(), false) + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) .unwrap() .build() .unwrap() .block; + let block_ok_1_hash = block_ok.hash(); let params = BlockCheckParams { hash: block_ok.hash(), - number: 0, + number: 1, parent_hash: *block_ok.header().parent_hash(), allow_missing_state: false, allow_missing_parent: false, @@ -1371,15 +1427,16 @@ fn respects_block_rules() { }; assert_eq!(block_on(client.check_block(params)).unwrap(), ImportResult::imported(false)); - // this is 0x0d6d6612a10485370d9e085aeea7ec427fb3f34d961c6a816cdbe5cde2278864 - let mut block_not_ok = - client.new_block_at(&BlockId::Number(0), Default::default(), false).unwrap(); + // build B'[1] + let mut block_not_ok = client + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) + .unwrap(); block_not_ok.push_storage_change(vec![0], Some(vec![1])).unwrap(); let block_not_ok = block_not_ok.build().unwrap().block; let params = BlockCheckParams { hash: block_not_ok.hash(), - number: 0, + number: 1, parent_hash: *block_not_ok.header().parent_hash(), allow_missing_state: false, allow_missing_parent: false, @@ -1394,34 +1451,35 @@ fn respects_block_rules() { // Now going to the fork block_on(client.import_as_final(BlockOrigin::Own, block_ok)).unwrap(); - // And check good fork - let mut block_ok = - client.new_block_at(&BlockId::Number(1), Default::default(), false).unwrap(); + // And check good fork (build B[2]) + let mut block_ok = client.new_block_at(block_ok_1_hash, Default::default(), false).unwrap(); block_ok.push_storage_change(vec![0], Some(vec![2])).unwrap(); let block_ok = block_ok.build().unwrap().block; + assert_eq!(*block_ok.header().number(), 2); let params = BlockCheckParams { hash: block_ok.hash(), - number: 1, + number: 2, parent_hash: *block_ok.header().parent_hash(), allow_missing_state: false, allow_missing_parent: false, import_existing: false, }; if record_only { - fork_rules.push((1, block_ok.hash())); + fork_rules.push((2, block_ok.hash())); } assert_eq!(block_on(client.check_block(params)).unwrap(), ImportResult::imported(false)); - // And now try bad fork + // And now try bad fork (build B'[2]) let mut block_not_ok = - client.new_block_at(&BlockId::Number(1), Default::default(), false).unwrap(); + client.new_block_at(block_ok_1_hash, Default::default(), false).unwrap(); block_not_ok.push_storage_change(vec![0], Some(vec![3])).unwrap(); let block_not_ok = block_not_ok.build().unwrap().block; + assert_eq!(*block_not_ok.header().number(), 2); let params = BlockCheckParams { hash: block_not_ok.hash(), - number: 1, + number: 2, parent_hash: *block_not_ok.header().parent_hash(), allow_missing_state: false, allow_missing_parent: false, @@ -1444,7 +1502,11 @@ fn respects_block_rules() { } #[test] +#[cfg(disable_flaky)] +#[allow(dead_code)] +// FIXME: https://github.com/paritytech/substrate/issues/11321 fn returns_status_for_pruned_blocks() { + use sc_consensus::BlockStatus; sp_tracing::try_init_simple(); let tmp = tempfile::tempdir().unwrap(); @@ -1466,19 +1528,21 @@ fn returns_status_for_pruned_blocks() { let mut client = TestClientBuilder::with_backend(backend).build(); let a1 = client - .new_block_at(&BlockId::Number(0), Default::default(), false) + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) .unwrap() .build() .unwrap() .block; - let mut b1 = client.new_block_at(&BlockId::Number(0), Default::default(), false).unwrap(); + let mut b1 = client + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) + .unwrap(); // b1 is created, but not imported b1.push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 1, + amount: 1 * DOLLARS, nonce: 0, }) .unwrap(); @@ -1508,7 +1572,7 @@ fn returns_status_for_pruned_blocks() { assert_eq!(client.block_status(check_block_a1.hash).unwrap(), BlockStatus::InChainWithState); let a2 = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) + .new_block_at(a1.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -1536,7 +1600,7 @@ fn returns_status_for_pruned_blocks() { assert_eq!(client.block_status(check_block_a2.hash).unwrap(), BlockStatus::InChainWithState); let a3 = client - .new_block_at(&BlockId::Hash(a2.hash()), Default::default(), false) + .new_block_at(a2.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -1594,7 +1658,7 @@ fn returns_status_for_pruned_blocks() { } #[test] -fn storage_keys_iter_prefix_and_start_key_works() { +fn storage_keys_prefix_and_start_key_works() { let child_info = ChildInfo::new_default(b"child"); let client = TestClientBuilder::new() .add_extra_child_storage(&child_info, b"first".to_vec(), vec![0u8; 32]) @@ -1604,37 +1668,37 @@ fn storage_keys_iter_prefix_and_start_key_works() { let block_hash = client.info().best_hash; - let child_root = b":child_storage:default:child".to_vec(); + let child_root = array_bytes::bytes2hex("", b":child_storage:default:child"); let prefix = StorageKey(array_bytes::hex2bytes_unchecked("3a")); let child_prefix = StorageKey(b"sec".to_vec()); let res: Vec<_> = client - .storage_keys_iter(block_hash, Some(&prefix), None) + .storage_keys(block_hash, Some(&prefix), None) .unwrap() - .map(|x| x.0) + .map(|x| array_bytes::bytes2hex("", &x.0)) .collect(); assert_eq!( res, [ - child_root.clone(), - array_bytes::hex2bytes_unchecked("3a636f6465"), - array_bytes::hex2bytes_unchecked("3a686561707061676573"), + &child_root, + "3a636f6465", //":code" + "3a65787472696e7369635f696e646578", //":extrinsic_index" ] ); let res: Vec<_> = client - .storage_keys_iter( + .storage_keys( block_hash, Some(&prefix), Some(&StorageKey(array_bytes::hex2bytes_unchecked("3a636f6465"))), ) .unwrap() - .map(|x| x.0) + .map(|x| array_bytes::bytes2hex("", &x.0)) .collect(); - assert_eq!(res, [array_bytes::hex2bytes_unchecked("3a686561707061676573")]); + assert_eq!(res, ["3a65787472696e7369635f696e646578",]); let res: Vec<_> = client - .storage_keys_iter( + .storage_keys( block_hash, Some(&prefix), Some(&StorageKey(array_bytes::hex2bytes_unchecked("3a686561707061676573"))), @@ -1645,19 +1709,14 @@ fn storage_keys_iter_prefix_and_start_key_works() { assert_eq!(res, Vec::>::new()); let res: Vec<_> = client - .child_storage_keys_iter(block_hash, child_info.clone(), Some(&child_prefix), None) + .child_storage_keys(block_hash, child_info.clone(), Some(&child_prefix), None) .unwrap() .map(|x| x.0) .collect(); assert_eq!(res, [b"second".to_vec()]); let res: Vec<_> = client - .child_storage_keys_iter( - block_hash, - child_info, - None, - Some(&StorageKey(b"second".to_vec())), - ) + .child_storage_keys(block_hash, child_info, None, Some(&StorageKey(b"second".to_vec()))) .unwrap() .map(|x| x.0) .collect(); @@ -1665,36 +1724,58 @@ fn storage_keys_iter_prefix_and_start_key_works() { } #[test] -fn storage_keys_iter_works() { - let client = substrate_test_runtime_client::new(); +fn storage_keys_works() { + sp_tracing::try_init_simple(); - let block_hash = client.info().best_hash; + let expected_keys = + substrate_test_runtime::storage_key_generator::get_expected_storage_hashed_keys(false); + let client = substrate_test_runtime_client::new(); + let block_hash = client.info().best_hash; let prefix = StorageKey(array_bytes::hex2bytes_unchecked("")); let res: Vec<_> = client - .storage_keys_iter(block_hash, Some(&prefix), None) + .storage_keys(block_hash, Some(&prefix), None) + .unwrap() + .take(19) + .map(|x| array_bytes::bytes2hex("", &x.0)) + .collect(); + + assert_eq!(res, expected_keys[0..19],); + + // Starting at an empty key nothing gets skipped. + let res: Vec<_> = client + .storage_keys(block_hash, Some(&prefix), Some(&StorageKey("".into()))) + .unwrap() + .take(19) + .map(|x| array_bytes::bytes2hex("", &x.0)) + .collect(); + assert_eq!(res, expected_keys[0..19],); + + // Starting at an incomplete key nothing gets skipped. + let res: Vec<_> = client + .storage_keys( + block_hash, + Some(&prefix), + Some(&StorageKey(array_bytes::hex2bytes_unchecked("3a636f64"))), + ) .unwrap() - .take(9) + .take(8) .map(|x| array_bytes::bytes2hex("", &x.0)) .collect(); assert_eq!( res, - [ - "00c232cf4e70a5e343317016dc805bf80a6a8cd8ad39958d56f99891b07851e0", - "085b2407916e53a86efeb8b72dbe338c4b341dab135252f96b6ed8022209b6cb", - "0befda6e1ca4ef40219d588a727f1271", - "1a560ecfd2a62c2b8521ef149d0804eb621050e3988ed97dca55f0d7c3e6aa34", - "1d66850d32002979d67dd29dc583af5b2ae2a1f71c1f35ad90fff122be7a3824", - "237498b98d8803334286e9f0483ef513098dd3c1c22ca21c4dc155b4ef6cc204", - "26aa394eea5630e07c48ae0c9558cef75e0621c4869aa60c02be9adcc98a0d1d", - "29b9db10ec5bf7907d8f74b5e60aa8140c4fbdd8127a1ee5600cb98e5ec01729", - "3a636f6465", - ] + expected_keys + .iter() + .filter(|&i| *i > "3a636f64") + .take(8) + .cloned() + .collect::>() ); + // Starting at a complete key the first key is skipped. let res: Vec<_> = client - .storage_keys_iter( + .storage_keys( block_hash, Some(&prefix), Some(&StorageKey(array_bytes::hex2bytes_unchecked("3a636f6465"))), @@ -1705,38 +1786,33 @@ fn storage_keys_iter_works() { .collect(); assert_eq!( res, - [ - "3a686561707061676573", - "52008686cc27f6e5ed83a216929942f8bcd32a396f09664a5698f81371934b56", - "5348d72ac6cc66e5d8cbecc27b0e0677503b845fe2382d819f83001781788fd5", - "5c2d5fda66373dabf970e4fb13d277ce91c5233473321129d32b5a8085fa8133", - "6644b9b8bc315888ac8e41a7968dc2b4141a5403c58acdf70b7e8f7e07bf5081", - "66484000ed3f75c95fc7b03f39c20ca1e1011e5999278247d3b2f5e3c3273808", - "7d5007603a7f5dd729d51d93cf695d6465789443bb967c0d1fe270e388c96eaa", - ] + expected_keys + .iter() + .filter(|&i| *i > "3a636f6465") + .take(8) + .cloned() + .collect::>() ); + const SOME_BALANCE_KEY : &str = "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9e2c1dc507e2035edbbd8776c440d870460c57f0008067cc01c5ff9eb2e2f9b3a94299a915a91198bd1021a6c55596f57"; let res: Vec<_> = client - .storage_keys_iter( + .storage_keys( block_hash, Some(&prefix), - Some(&StorageKey(array_bytes::hex2bytes_unchecked( - "7d5007603a7f5dd729d51d93cf695d6465789443bb967c0d1fe270e388c96eaa", - ))), + Some(&StorageKey(array_bytes::hex2bytes_unchecked(SOME_BALANCE_KEY))), ) .unwrap() - .take(5) + .take(8) .map(|x| array_bytes::bytes2hex("", &x.0)) .collect(); assert_eq!( res, - [ - "811ecfaadcf5f2ee1d67393247e2f71a1662d433e8ce7ff89fb0d4aa9561820b", - "a93d74caa7ec34ea1b04ce1e5c090245f867d333f0f88278a451e45299654dc5", - "a9ee1403384afbfc13f13be91ff70bfac057436212e53b9733914382ac942892", - "cf722c0832b5231d35e29f319ff27389f5032bfc7bfc3ba5ed7839f2042fb99f", - "e3b47b6c84c0493481f97c5197d2554f", - ] + expected_keys + .iter() + .filter(|&i| *i > SOME_BALANCE_KEY) + .take(8) + .cloned() + .collect::>() ); } @@ -1745,7 +1821,7 @@ fn cleans_up_closed_notification_sinks_on_block_import() { use substrate_test_runtime_client::GenesisInit; let backend = Arc::new(sc_client_api::in_mem::Backend::new()); - let executor = substrate_test_runtime_client::new_native_executor(); + let executor = new_native_or_wasm_executor(); let client_config = sc_service::ClientConfig::default(); let genesis_block_builder = sc_service::GenesisBlockBuilder::new( @@ -1765,7 +1841,6 @@ fn cleans_up_closed_notification_sinks_on_block_import() { genesis_block_builder, None, None, - None, Box::new(TaskExecutor::new()), client_config, ) @@ -1795,7 +1870,7 @@ fn cleans_up_closed_notification_sinks_on_block_import() { let mut import = BlockImportParams::new(origin, header); import.body = Some(extrinsics); import.fork_choice = Some(ForkChoiceStrategy::LongestChain); - block_on(client.import_block(import, Default::default())).unwrap(); + block_on(client.import_block(import)).unwrap(); }; // after importing a block we should still have 4 notification sinks @@ -1833,7 +1908,7 @@ fn reorg_triggers_a_notification_even_for_sources_that_should_not_trigger_notifi futures::executor::block_on_stream(client.import_notification_stream()); let a1 = client - .new_block_at(&BlockId::Number(0), Default::default(), false) + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) .unwrap() .build() .unwrap() @@ -1841,19 +1916,21 @@ fn reorg_triggers_a_notification_even_for_sources_that_should_not_trigger_notifi block_on(client.import(BlockOrigin::NetworkInitialSync, a1.clone())).unwrap(); let a2 = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) + .new_block_at(a1.hash(), Default::default(), false) .unwrap() .build() .unwrap() .block; block_on(client.import(BlockOrigin::NetworkInitialSync, a2.clone())).unwrap(); - let mut b1 = client.new_block_at(&BlockId::Number(0), Default::default(), false).unwrap(); + let mut b1 = client + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) + .unwrap(); // needed to make sure B1 gets a different hash from A1 b1.push_transfer(Transfer { from: AccountKeyring::Alice.into(), to: AccountKeyring::Ferdie.into(), - amount: 1, + amount: 1 * DOLLARS, nonce: 0, }) .unwrap(); @@ -1861,7 +1938,7 @@ fn reorg_triggers_a_notification_even_for_sources_that_should_not_trigger_notifi block_on(client.import(BlockOrigin::NetworkInitialSync, b1.clone())).unwrap(); let b2 = client - .new_block_at(&BlockId::Hash(b1.hash()), Default::default(), false) + .new_block_at(b1.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -1897,7 +1974,7 @@ fn use_dalek_ext_works() { ); let a1 = client - .new_block_at(&BlockId::Number(0), Default::default(), false) + .new_block_at(client.chain_info().genesis_hash, Default::default(), false) .unwrap() .build() .unwrap() @@ -1907,10 +1984,48 @@ fn use_dalek_ext_works() { // On block zero it will use dalek and then on block 1 it will use zebra assert!(!client .runtime_api() - .verify_ed25519(&BlockId::Number(0), zero_ed_sig(), zero_ed_pub(), vec![]) + .verify_ed25519(client.chain_info().genesis_hash, zero_ed_sig(), zero_ed_pub(), vec![]) .unwrap()); assert!(client .runtime_api() - .verify_ed25519(&BlockId::Number(1), zero_ed_sig(), zero_ed_pub(), vec![]) + .verify_ed25519(a1.hash(), zero_ed_sig(), zero_ed_pub(), vec![]) .unwrap()); } + +#[test] +fn finalize_after_best_block_updates_best() { + let mut client = substrate_test_runtime_client::new(); + + // G -> A1 + let a1 = client.new_block(Default::default()).unwrap().build().unwrap().block; + block_on(client.import(BlockOrigin::Own, a1.clone())).unwrap(); + + // A1 -> A2 + let a2 = client + .new_block_at(a1.hash(), Default::default(), false) + .unwrap() + .build() + .unwrap() + .block; + block_on(client.import(BlockOrigin::Own, a2.clone())).unwrap(); + + // A2 -> A3 + let a3 = client + .new_block_at(a2.hash(), Default::default(), false) + .unwrap() + .build() + .unwrap() + .block; + let (header, extrinsics) = a3.clone().deconstruct(); + let mut import_params = BlockImportParams::new(BlockOrigin::Own, header); + import_params.body = Some(extrinsics); + import_params.fork_choice = Some(ForkChoiceStrategy::Custom(false)); + block_on(client.import_block(import_params)).unwrap(); + + assert_eq!(client.chain_info().best_hash, a2.hash()); + + client.finalize_block(a3.hash(), None).unwrap(); + + assert_eq!(client.chain_info().finalized_hash, a3.hash()); + assert_eq!(client.chain_info().best_hash, a3.hash()); +} diff --git a/client/service/test/src/lib.rs b/client/service/test/src/lib.rs index 6533fe13961b..38a811acc740 100644 --- a/client/service/test/src/lib.rs +++ b/client/service/test/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -22,11 +22,11 @@ use futures::{task::Poll, Future, TryFutureExt as _}; use log::{debug, info}; use parking_lot::Mutex; use sc_client_api::{Backend, CallExecutor}; -use sc_network::{config::NetworkConfiguration, multiaddr}; -use sc_network_common::{ - config::{MultiaddrWithPeerId, TransportConfig}, - service::{NetworkBlock, NetworkPeers, NetworkStateInfo}, +use sc_network::{ + config::{MultiaddrWithPeerId, NetworkConfiguration, TransportConfig}, + multiaddr, NetworkBlock, NetworkPeers, NetworkStateInfo, }; +use sc_network_sync::SyncingService; use sc_service::{ client::Client, config::{BasePath, DatabaseSource, KeystoreConfig}, @@ -65,9 +65,7 @@ impl Drop for TestNet { } } -pub trait TestNetNode: - Clone + Future> + Send + 'static -{ +pub trait TestNetNode: Clone + Future> + Send + 'static { type Block: BlockT; type Backend: Backend; type Executor: CallExecutor + Send + Sync; @@ -79,6 +77,7 @@ pub trait TestNetNode: fn network( &self, ) -> Arc::Hash>>; + fn sync(&self) -> &Arc>; fn spawn_handle(&self) -> SpawnTaskHandle; } @@ -87,6 +86,7 @@ pub struct TestNetComponents { client: Arc>, transaction_pool: Arc, network: Arc::Hash>>, + sync: Arc>, } impl @@ -96,9 +96,16 @@ impl task_manager: TaskManager, client: Arc>, network: Arc::Hash>>, + sync: Arc>, transaction_pool: Arc, ) -> Self { - Self { client, transaction_pool, network, task_manager: Arc::new(Mutex::new(task_manager)) } + Self { + client, + sync, + transaction_pool, + network, + task_manager: Arc::new(Mutex::new(task_manager)), + } } } @@ -111,6 +118,7 @@ impl Clone client: self.client.clone(), transaction_pool: self.transaction_pool.clone(), network: self.network.clone(), + sync: self.sync.clone(), } } } @@ -118,7 +126,7 @@ impl Clone impl Future for TestNetComponents { - type Output = Result<(), sc_service::Error>; + type Output = Result<(), Error>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll { Pin::new(&mut self.task_manager.lock().future()).poll(cx) @@ -151,6 +159,9 @@ where ) -> Arc::Hash>> { self.network.clone() } + fn sync(&self) -> &Arc> { + &self.sync + } fn spawn_handle(&self) -> SpawnTaskHandle { self.task_manager.lock().spawn_handle() } @@ -171,10 +182,7 @@ where loop { interval.tick().await; - if full_nodes - .iter() - .all(|&(ref id, ref service, _, _)| full_predicate(*id, service)) - { + if full_nodes.iter().all(|(id, service, _, _)| full_predicate(*id, service)) { break } } @@ -229,28 +237,23 @@ fn node_config< tokio_handle, transaction_pool: Default::default(), network: network_config, - keystore_remote: Default::default(), keystore: KeystoreConfig::Path { path: root.join("key"), password: None }, database: DatabaseSource::RocksDb { path: root.join("db"), cache_size: 128 }, trie_cache_maximum_size: Some(16 * 1024 * 1024), state_pruning: Default::default(), blocks_pruning: BlocksPruning::KeepFinalized, chain_spec: Box::new((*spec).clone()), - wasm_method: sc_service::config::WasmExecutionMethod::Interpreted, + wasm_method: Default::default(), wasm_runtime_overrides: Default::default(), - execution_strategies: Default::default(), - rpc_http: None, - rpc_ipc: None, - rpc_ws: None, - rpc_ws_max_connections: None, + rpc_addr: Default::default(), + rpc_max_connections: Default::default(), rpc_cors: None, rpc_methods: Default::default(), - rpc_max_payload: None, - rpc_max_request_size: None, - rpc_max_response_size: None, - rpc_id_provider: None, - rpc_max_subs_per_conn: None, - ws_max_out_buffer_capacity: None, + rpc_max_request_size: Default::default(), + rpc_max_response_size: Default::default(), + rpc_id_provider: Default::default(), + rpc_max_subs_per_conn: Default::default(), + rpc_port: 9944, prometheus_config: None, telemetry_endpoints: None, default_heap_pages: None, @@ -262,7 +265,8 @@ fn node_config< tracing_receiver: Default::default(), max_runtime_instances: 8, announce_block: true, - base_path: Some(BasePath::new(root)), + base_path: BasePath::new(root.clone()), + data_path: root, informant_output_format: Default::default(), runtime_cache_size: 2, } @@ -477,7 +481,7 @@ pub fn sync( let info = network.full_nodes[0].1.client().info(); network.full_nodes[0] .1 - .network() + .sync() .new_best_block_imported(info.best_hash, info.best_number); network.full_nodes[0].3.clone() }; diff --git a/client/state-db/Cargo.toml b/client/state-db/Cargo.toml index 13439de11015..f7df4e9c8ce3 100644 --- a/client/state-db/Cargo.toml +++ b/client/state-db/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } log = "0.4.17" parking_lot = "0.12.1" -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } diff --git a/client/state-db/src/lib.rs b/client/state-db/src/lib.rs index 1befd6dff3bc..c656f126ae6e 100644 --- a/client/state-db/src/lib.rs +++ b/client/state-db/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -56,6 +56,8 @@ use std::{ fmt, }; +const LOG_TARGET: &str = "state-db"; +const LOG_TARGET_PIN: &str = "state-db::pin"; const PRUNING_MODE: &[u8] = b"mode"; const PRUNING_MODE_ARCHIVE: &[u8] = b"archive"; const PRUNING_MODE_ARCHIVE_CANON: &[u8] = b"archive_canonical"; @@ -152,6 +154,7 @@ impl From for Error { } /// Pinning error type. +#[derive(Debug)] pub enum PinError { /// Trying to pin invalid block. InvalidBlock, @@ -184,12 +187,14 @@ impl fmt::Debug for StateDbError { "Incompatible pruning modes [stored: {:?}; requested: {:?}]", stored, requested ), - Self::TooManySiblingBlocks { number } => - write!(f, "Too many sibling blocks at #{number} inserted"), + Self::TooManySiblingBlocks { number } => { + write!(f, "Too many sibling blocks at #{number} inserted") + }, Self::BlockAlreadyExists => write!(f, "Block already exists"), Self::Metadata(message) => write!(f, "Invalid metadata: {}", message), - Self::BlockUnavailable => - write!(f, "Trying to get a block record from db while it is not commit to db yet"), + Self::BlockUnavailable => { + write!(f, "Trying to get a block record from db while it is not commit to db yet") + }, Self::BlockMissing => write!(f, "Block record is missing from the pruning window"), } } @@ -308,7 +313,7 @@ impl StateDbSync { ref_counting: bool, db: D, ) -> Result, Error> { - trace!(target: "state-db", "StateDb settings: {:?}. Ref-counting: {}", mode, ref_counting); + trace!(target: LOG_TARGET, "StateDb settings: {:?}. Ref-counting: {}", mode, ref_counting); let non_canonical: NonCanonicalOverlay = NonCanonicalOverlay::new(&db)?; let pruning: Option> = match mode { @@ -389,7 +394,8 @@ impl StateDbSync { } } else { match self.pruning.as_ref() { - None => IsPruned::NotPruned, + // We don't know for sure. + None => IsPruned::MaybePruned, Some(pruning) => match pruning.have_block(hash, number) { HaveBlock::No => IsPruned::Pruned, HaveBlock::Yes => IsPruned::NotPruned, @@ -402,7 +408,7 @@ impl StateDbSync { } fn prune(&mut self, commit: &mut CommitSet) -> Result<(), Error> { - if let (&mut Some(ref mut pruning), &PruningMode::Constrained(ref constraints)) = + if let (&mut Some(ref mut pruning), PruningMode::Constrained(constraints)) = (&mut self.pruning, &self.mode) { loop { @@ -457,13 +463,14 @@ impl StateDbSync { PruningMode::ArchiveAll => Ok(()), PruningMode::ArchiveCanonical | PruningMode::Constrained(_) => { let have_block = self.non_canonical.have_block(hash) || - self.pruning.as_ref().map_or(false, |pruning| { - match pruning.have_block(hash, number) { + self.pruning.as_ref().map_or_else( + || hint(), + |pruning| match pruning.have_block(hash, number) { HaveBlock::No => false, HaveBlock::Yes => true, HaveBlock::Maybe => hint(), - } - }); + }, + ); if have_block { let refs = self.pinned.entry(hash.clone()).or_default(); if *refs == 0 { @@ -589,7 +596,7 @@ impl StateDb { } /// Prevents pruning of specified block and its descendants. - /// `hint` used for futher checking if the given block exists + /// `hint` used for further checking if the given block exists pub fn pin(&self, hash: &BlockHash, number: u64, hint: F) -> Result<(), PinError> where F: Fn() -> bool, @@ -642,7 +649,7 @@ impl StateDb { /// Check if block is pruned away. pub fn is_pruned(&self, hash: &BlockHash, number: u64) -> IsPruned { - return self.db.read().is_pruned(hash, number) + self.db.read().is_pruned(hash, number) } /// Reset in-memory changes to the last disk-backed state. @@ -660,7 +667,7 @@ pub enum IsPruned { Pruned, /// Definitely not pruned NotPruned, - /// May or may not pruned, need futher checking + /// May or may not pruned, need further checking MaybePruned, } diff --git a/client/state-db/src/noncanonical.rs b/client/state-db/src/noncanonical.rs index e950d245dcec..bdbe8318371c 100644 --- a/client/state-db/src/noncanonical.rs +++ b/client/state-db/src/noncanonical.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -20,6 +20,8 @@ //! Maintains trees of block overlays and allows discarding trees/roots //! The overlays are added in `insert` and removed in `canonicalize`. +use crate::{LOG_TARGET, LOG_TARGET_PIN}; + use super::{to_meta_key, ChangeSet, CommitSet, DBValue, Error, Hash, MetaDb, StateDbError}; use codec::{Decode, Encode}; use log::trace; @@ -178,7 +180,12 @@ impl NonCanonicalOverlay { let mut values = HashMap::new(); if let Some((ref hash, mut block)) = last_canonicalized { // read the journal - trace!(target: "state-db", "Reading uncanonicalized journal. Last canonicalized #{} ({:?})", block, hash); + trace!( + target: LOG_TARGET, + "Reading uncanonicalized journal. Last canonicalized #{} ({:?})", + block, + hash + ); let mut total: u64 = 0; block += 1; loop { @@ -198,7 +205,7 @@ impl NonCanonicalOverlay { }; insert_values(&mut values, record.inserted); trace!( - target: "state-db", + target: LOG_TARGET, "Uncanonicalized journal entry {}.{} ({:?}) ({} inserted, {} deleted)", block, index, @@ -217,7 +224,11 @@ impl NonCanonicalOverlay { levels.push_back(level); block += 1; } - trace!(target: "state-db", "Finished reading uncanonicalized journal, {} entries", total); + trace!( + target: LOG_TARGET, + "Finished reading uncanonicalized journal, {} entries", + total + ); } Ok(NonCanonicalOverlay { last_canonicalized, @@ -252,7 +263,9 @@ impl NonCanonicalOverlay { } else if self.last_canonicalized.is_some() { if number < front_block_number || number > front_block_number + self.levels.len() as u64 { - trace!(target: "state-db", "Failed to insert block {}, current is {} .. {})", + trace!( + target: LOG_TARGET, + "Failed to insert block {}, current is {} .. {})", number, front_block_number, front_block_number + self.levels.len() as u64, @@ -284,7 +297,7 @@ impl NonCanonicalOverlay { if level.blocks.len() >= MAX_BLOCKS_PER_LEVEL as usize { trace!( - target: "state-db", + target: LOG_TARGET, "Too many sibling blocks at #{number}: {:?}", level.blocks.iter().map(|b| &b.hash).collect::>() ); @@ -314,7 +327,15 @@ impl NonCanonicalOverlay { deleted: changeset.deleted, }; commit.meta.inserted.push((journal_key, journal_record.encode())); - trace!(target: "state-db", "Inserted uncanonicalized changeset {}.{} {:?} ({} inserted, {} deleted)", number, index, hash, journal_record.inserted.len(), journal_record.deleted.len()); + trace!( + target: LOG_TARGET, + "Inserted uncanonicalized changeset {}.{} {:?} ({} inserted, {} deleted)", + number, + index, + hash, + journal_record.inserted.len(), + journal_record.deleted.len() + ); insert_values(&mut self.values, journal_record.inserted); Ok(commit) } @@ -368,7 +389,7 @@ impl NonCanonicalOverlay { hash: &BlockHash, commit: &mut CommitSet, ) -> Result { - trace!(target: "state-db", "Canonicalizing {:?}", hash); + trace!(target: LOG_TARGET, "Canonicalizing {:?}", hash); let level = match self.levels.pop_front() { Some(level) => level, None => return Err(StateDbError::InvalidBlock), @@ -432,7 +453,7 @@ impl NonCanonicalOverlay { .meta .inserted .push((to_meta_key(LAST_CANONICAL, &()), canonicalized.encode())); - trace!(target: "state-db", "Discarding {} records", commit.meta.deleted.len()); + trace!(target: LOG_TARGET, "Discarding {} records", commit.meta.deleted.len()); let num = canonicalized.1; self.last_canonicalized = Some(canonicalized); @@ -479,7 +500,7 @@ impl NonCanonicalOverlay { }; // Check that it does not have any children if (level_index != level_count - 1) && self.parents.values().any(|h| h == hash) { - log::debug!(target: "state-db", "Trying to remove block {:?} with children", hash); + log::debug!(target: LOG_TARGET, "Trying to remove block {:?} with children", hash); return None } let overlay = level.remove(index); @@ -502,7 +523,7 @@ impl NonCanonicalOverlay { pub fn pin(&mut self, hash: &BlockHash) { let refs = self.pinned.entry(hash.clone()).or_default(); if *refs == 0 { - trace!(target: "state-db-pin", "Pinned non-canon block: {:?}", hash); + trace!(target: LOG_TARGET_PIN, "Pinned non-canon block: {:?}", hash); } *refs += 1; } @@ -531,7 +552,11 @@ impl NonCanonicalOverlay { entry.get_mut().1 -= 1; if entry.get().1 == 0 { let (inserted, _) = entry.remove(); - trace!(target: "state-db-pin", "Discarding unpinned non-canon block: {:?}", hash); + trace!( + target: LOG_TARGET_PIN, + "Discarding unpinned non-canon block: {:?}", + hash + ); discard_values(&mut self.values, inserted); self.parents.remove(&hash); } diff --git a/client/state-db/src/pruning.rs b/client/state-db/src/pruning.rs index d942fb2428b6..7bee2b1d99a3 100644 --- a/client/state-db/src/pruning.rs +++ b/client/state-db/src/pruning.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -26,7 +26,7 @@ use crate::{ noncanonical::LAST_CANONICAL, to_meta_key, CommitSet, Error, Hash, MetaDb, StateDbError, - DEFAULT_MAX_BLOCK_CONSTRAINT, + DEFAULT_MAX_BLOCK_CONSTRAINT, LOG_TARGET, }; use codec::{Decode, Encode}; use log::trace; @@ -79,14 +79,24 @@ impl DeathRowQueue { death_index: HashMap::new(), }; // read the journal - trace!(target: "state-db", "Reading pruning journal for the memory queue. Pending #{}", base); + trace!( + target: LOG_TARGET, + "Reading pruning journal for the memory queue. Pending #{}", + base, + ); loop { let journal_key = to_journal_key(block); match db.get_meta(&journal_key).map_err(Error::Db)? { Some(record) => { let record: JournalRecord = Decode::decode(&mut record.as_slice())?; - trace!(target: "state-db", "Pruning journal entry {} ({} inserted, {} deleted)", block, record.inserted.len(), record.deleted.len()); + trace!( + target: LOG_TARGET, + "Pruning journal entry {} ({} inserted, {} deleted)", + block, + record.inserted.len(), + record.deleted.len(), + ); queue.import(base, block, record); }, None => break, @@ -107,7 +117,11 @@ impl DeathRowQueue { // limit the cache capacity from 1 to `DEFAULT_MAX_BLOCK_CONSTRAINT` let cache_capacity = window_size.clamp(1, DEFAULT_MAX_BLOCK_CONSTRAINT) as usize; let mut cache = VecDeque::with_capacity(cache_capacity); - trace!(target: "state-db", "Reading pruning journal for the database-backed queue. Pending #{}", base); + trace!( + target: LOG_TARGET, + "Reading pruning journal for the database-backed queue. Pending #{}", + base + ); DeathRowQueue::load_batch_from_db(&db, &mut cache, base, cache_capacity)?; Ok(DeathRowQueue::DbBacked { db, cache, cache_capacity, last }) } @@ -115,13 +129,13 @@ impl DeathRowQueue { /// import a new block to the back of the queue fn import(&mut self, base: u64, num: u64, journal_record: JournalRecord) { let JournalRecord { hash, inserted, deleted } = journal_record; - trace!(target: "state-db", "Importing {}, base={}", num, base); + trace!(target: LOG_TARGET, "Importing {}, base={}", num, base); match self { DeathRowQueue::DbBacked { cache, cache_capacity, last, .. } => { // If the new block continues cached range and there is space, load it directly into // cache. if num == base + cache.len() as u64 && cache.len() < *cache_capacity { - trace!(target: "state-db", "Adding to DB backed cache {:?} (#{})", hash, num); + trace!(target: LOG_TARGET, "Adding to DB backed cache {:?} (#{})", hash, num); cache.push_back(DeathRow { hash, deleted: deleted.into_iter().collect() }); } *last = Some(num); @@ -306,6 +320,18 @@ impl RefWindow { }; let queue = if count_insertions { + // Highly scientific crafted number for deciding when to print the warning! + // + // Rocksdb doesn't support refcounting and requires that we load the entire pruning + // window into the memory. + if window_size > 1000 { + log::warn!( + target: LOG_TARGET, + "Large pruning window of {window_size} detected! THIS CAN LEAD TO HIGH MEMORY USAGE AND CRASHES. \ + Reduce the pruning window or switch your database to paritydb." + ); + } + DeathRowQueue::new_mem(&db, base)? } else { let last = match last_canonicalized_number { diff --git a/client/state-db/src/test.rs b/client/state-db/src/test.rs index 314ec2902452..74571145b52a 100644 --- a/client/state-db/src/test.rs +++ b/client/state-db/src/test.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/statement-store/Cargo.toml b/client/statement-store/Cargo.toml new file mode 100644 index 000000000000..8668dbfa8ba0 --- /dev/null +++ b/client/statement-store/Cargo.toml @@ -0,0 +1,32 @@ +[package] +name = "sc-statement-store" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "GPL-3.0-or-later WITH Classpath-exception-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Substrate statement store." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +log = "0.4.17" +parking_lot = "0.12.1" +parity-db = "0.4.8" +tokio = { version = "1.22.0", features = ["time"] } +sp-statement-store = { version = "4.0.0-dev", path = "../../primitives/statement-store" } +prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } +sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } +sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } +sc-client-api = { version = "4.0.0-dev", path = "../api" } +sc-keystore = { version = "4.0.0-dev", path = "../../client/keystore" } + +[dev-dependencies] +tempfile = "3.1.0" +env_logger = "0.9" + diff --git a/client/statement-store/README.md b/client/statement-store/README.md new file mode 100644 index 000000000000..41e268f4ece0 --- /dev/null +++ b/client/statement-store/README.md @@ -0,0 +1,4 @@ +Substrate statement store implementation. + +License: GPL-3.0-or-later WITH Classpath-exception-2.0 + diff --git a/client/statement-store/src/lib.rs b/client/statement-store/src/lib.rs new file mode 100644 index 000000000000..da0af08b4540 --- /dev/null +++ b/client/statement-store/src/lib.rs @@ -0,0 +1,1296 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +//! Disk-backed statement store. +//! +//! This module contains an implementation of `sp_statement_store::StatementStore` which is backed +//! by a database. +//! +//! Constraint management. +//! +//! Each time a new statement is inserted into the store, it is first validated with the runtime +//! Validation function computes `global_priority`, 'max_count' and `max_size` for a statement. +//! The following constraints are then checked: +//! * For a given account id, there may be at most `max_count` statements with `max_size` total data +//! size. To satisfy this, statements for this account ID are removed from the store starting with +//! the lowest priority until a constraint is satisfied. +//! * There may not be more than `MAX_TOTAL_STATEMENTS` total statements with `MAX_TOTAL_SIZE` size. +//! To satisfy this, statements are removed from the store starting with the lowest +//! `global_priority` until a constraint is satisfied. +//! +//! When a new statement is inserted that would not satisfy constraints in the first place, no +//! statements are deleted and `Ignored` result is returned. +//! The order in which statements with the same priority are deleted is unspecified. +//! +//! Statement expiration. +//! +//! Each time a statement is removed from the store (Either evicted by higher priority statement or +//! explicitly with the `remove` function) the statement is marked as expired. Expired statements +//! can't be added to the store for `Options::purge_after_sec` seconds. This is to prevent old +//! statements from being propagated on the network. + +#![warn(missing_docs)] +#![warn(unused_extern_crates)] + +mod metrics; + +pub use sp_statement_store::{Error, StatementStore, MAX_TOPICS}; + +use metrics::MetricsLink as PrometheusMetrics; +use parking_lot::RwLock; +use prometheus_endpoint::Registry as PrometheusRegistry; +use sc_keystore::LocalKeystore; +use sp_api::ProvideRuntimeApi; +use sp_blockchain::HeaderBackend; +use sp_core::{crypto::UncheckedFrom, hexdisplay::HexDisplay, traits::SpawnNamed, Decode, Encode}; +use sp_runtime::traits::Block as BlockT; +use sp_statement_store::{ + runtime_api::{ + InvalidStatement, StatementSource, StatementStoreExt, ValidStatement, ValidateStatement, + }, + AccountId, BlockHash, Channel, DecryptionKey, Hash, NetworkPriority, Proof, Result, Statement, + SubmitResult, Topic, +}; +use std::{ + collections::{BTreeMap, HashMap, HashSet}, + sync::Arc, +}; + +const KEY_VERSION: &[u8] = b"version".as_slice(); +const CURRENT_VERSION: u32 = 1; + +const LOG_TARGET: &str = "statement-store"; + +const DEFAULT_PURGE_AFTER_SEC: u64 = 2 * 24 * 60 * 60; //48h +const DEFAULT_MAX_TOTAL_STATEMENTS: usize = 8192; +const DEFAULT_MAX_TOTAL_SIZE: usize = 64 * 1024 * 1024; + +const MAINTENANCE_PERIOD: std::time::Duration = std::time::Duration::from_secs(30); + +mod col { + pub const META: u8 = 0; + pub const STATEMENTS: u8 = 1; + pub const EXPIRED: u8 = 2; + + pub const COUNT: u8 = 3; +} + +#[derive(Eq, PartialEq, Debug, Ord, PartialOrd, Clone, Copy)] +struct Priority(u32); + +#[derive(PartialEq, Eq)] +struct PriorityKey { + hash: Hash, + priority: Priority, +} + +impl PartialOrd for PriorityKey { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for PriorityKey { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.priority.cmp(&other.priority).then_with(|| self.hash.cmp(&other.hash)) + } +} + +#[derive(PartialEq, Eq)] +struct ChannelEntry { + hash: Hash, + priority: Priority, +} + +#[derive(Default)] +struct StatementsForAccount { + // Statements ordered by priority. + by_priority: BTreeMap, usize)>, + // Channel to statement map. Only one statement per channel is allowed. + channels: HashMap, + // Sum of all `Data` field sizes. + data_size: usize, +} + +/// Store configuration +pub struct Options { + /// Maximum statement allowed in the store. Once this limit is reached lower-priority + /// statements may be evicted. + max_total_statements: usize, + /// Maximum total data size allowed in the store. Once this limit is reached lower-priority + /// statements may be evicted. + max_total_size: usize, + /// Number of seconds for which removed statements won't be allowed to be added back in. + purge_after_sec: u64, +} + +impl Default for Options { + fn default() -> Self { + Options { + max_total_statements: DEFAULT_MAX_TOTAL_STATEMENTS, + max_total_size: DEFAULT_MAX_TOTAL_SIZE, + purge_after_sec: DEFAULT_PURGE_AFTER_SEC, + } + } +} + +#[derive(Default)] +struct Index { + by_topic: HashMap>, + by_dec_key: HashMap, HashSet>, + topics_and_keys: HashMap; MAX_TOPICS], Option)>, + entries: HashMap, + expired: HashMap, // Value is expiration timestamp. + accounts: HashMap, + options: Options, + total_size: usize, +} + +struct ClientWrapper { + client: Arc, + _block: std::marker::PhantomData, +} + +impl ClientWrapper +where + Block: BlockT, + Block::Hash: From, + Client: ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static, + Client::Api: ValidateStatement, +{ + fn validate_statement( + &self, + block: Option, + source: StatementSource, + statement: Statement, + ) -> std::result::Result { + let api = self.client.runtime_api(); + let block = block.map(Into::into).unwrap_or_else(|| { + // Validate against the finalized state. + self.client.info().finalized_hash + }); + api.validate_statement(block, source, statement) + .map_err(|_| InvalidStatement::InternalError)? + } +} + +/// Statement store. +pub struct Store { + db: parity_db::Db, + index: RwLock, + validate_fn: Box< + dyn Fn( + Option, + StatementSource, + Statement, + ) -> std::result::Result + + Send + + Sync, + >, + keystore: Arc, + // Used for testing + time_override: Option, + metrics: PrometheusMetrics, +} + +enum IndexQuery { + Unknown, + Exists, + Expired, +} + +enum MaybeInserted { + Inserted(HashSet), + Ignored, +} + +impl Index { + fn new(options: Options) -> Index { + Index { options, ..Default::default() } + } + + fn insert_new(&mut self, hash: Hash, account: AccountId, statement: &Statement) { + let mut all_topics = [None; MAX_TOPICS]; + let mut nt = 0; + while let Some(t) = statement.topic(nt) { + self.by_topic.entry(t).or_default().insert(hash); + all_topics[nt] = Some(t); + nt += 1; + } + let key = statement.decryption_key(); + self.by_dec_key.entry(key).or_default().insert(hash); + if nt > 0 || key.is_some() { + self.topics_and_keys.insert(hash, (all_topics, key)); + } + let priority = Priority(statement.priority().unwrap_or(0)); + self.entries.insert(hash, (account, priority, statement.data_len())); + self.total_size += statement.data_len(); + let account_info = self.accounts.entry(account).or_default(); + account_info.data_size += statement.data_len(); + if let Some(channel) = statement.channel() { + account_info.channels.insert(channel, ChannelEntry { hash, priority }); + } + account_info + .by_priority + .insert(PriorityKey { hash, priority }, (statement.channel(), statement.data_len())); + } + + fn query(&self, hash: &Hash) -> IndexQuery { + if self.entries.contains_key(hash) { + return IndexQuery::Exists + } + if self.expired.contains_key(hash) { + return IndexQuery::Expired + } + IndexQuery::Unknown + } + + fn insert_expired(&mut self, hash: Hash, timestamp: u64) { + self.expired.insert(hash, timestamp); + } + + fn iterate_with( + &self, + key: Option, + match_all_topics: &[Topic], + mut f: impl FnMut(&Hash) -> Result<()>, + ) -> Result<()> { + let empty = HashSet::new(); + let mut sets: [&HashSet; MAX_TOPICS + 1] = [∅ MAX_TOPICS + 1]; + if match_all_topics.len() > MAX_TOPICS { + return Ok(()) + } + let key_set = self.by_dec_key.get(&key); + if key_set.map_or(0, |s| s.len()) == 0 { + // Key does not exist in the index. + return Ok(()) + } + sets[0] = key_set.expect("Function returns if key_set is None"); + for (i, t) in match_all_topics.iter().enumerate() { + let set = self.by_topic.get(t); + if set.map_or(0, |s| s.len()) == 0 { + // At least one of the match_all_topics does not exist in the index. + return Ok(()) + } + sets[i + 1] = set.expect("Function returns if set is None"); + } + let sets = &mut sets[0..match_all_topics.len() + 1]; + // Start with the smallest topic set or the key set. + sets.sort_by_key(|s| s.len()); + for item in sets[0] { + if sets[1..].iter().all(|set| set.contains(item)) { + log::trace!( + target: LOG_TARGET, + "Iterating by topic/key: statement {:?}", + HexDisplay::from(item) + ); + f(item)? + } + } + Ok(()) + } + + fn maintain(&mut self, current_time: u64) -> Vec { + // Purge previously expired messages. + let mut purged = Vec::new(); + self.expired.retain(|hash, timestamp| { + if *timestamp + self.options.purge_after_sec <= current_time { + purged.push(*hash); + log::trace!(target: LOG_TARGET, "Purged statement {:?}", HexDisplay::from(hash)); + false + } else { + true + } + }); + purged + } + + fn make_expired(&mut self, hash: &Hash, current_time: u64) -> bool { + if let Some((account, priority, len)) = self.entries.remove(hash) { + self.total_size -= len; + if let Some((topics, key)) = self.topics_and_keys.remove(hash) { + for t in topics.into_iter().flatten() { + if let std::collections::hash_map::Entry::Occupied(mut set) = + self.by_topic.entry(t) + { + set.get_mut().remove(hash); + if set.get().is_empty() { + set.remove_entry(); + } + } + } + if let std::collections::hash_map::Entry::Occupied(mut set) = + self.by_dec_key.entry(key) + { + set.get_mut().remove(hash); + if set.get().is_empty() { + set.remove_entry(); + } + } + } + self.expired.insert(*hash, current_time); + if let std::collections::hash_map::Entry::Occupied(mut account_rec) = + self.accounts.entry(account) + { + let key = PriorityKey { hash: *hash, priority }; + if let Some((channel, len)) = account_rec.get_mut().by_priority.remove(&key) { + account_rec.get_mut().data_size -= len; + if let Some(channel) = channel { + account_rec.get_mut().channels.remove(&channel); + } + } + if account_rec.get().by_priority.is_empty() { + account_rec.remove_entry(); + } + } + log::trace!(target: LOG_TARGET, "Expired statement {:?}", HexDisplay::from(hash)); + true + } else { + false + } + } + + fn insert( + &mut self, + hash: Hash, + statement: &Statement, + account: &AccountId, + validation: &ValidStatement, + current_time: u64, + ) -> MaybeInserted { + let statement_len = statement.data_len(); + if statement_len > validation.max_size as usize { + log::debug!( + target: LOG_TARGET, + "Ignored oversize message: {:?} ({} bytes)", + HexDisplay::from(&hash), + statement_len, + ); + return MaybeInserted::Ignored + } + + let mut evicted = HashSet::new(); + let mut would_free_size = 0; + let priority = Priority(statement.priority().unwrap_or(0)); + let (max_size, max_count) = (validation.max_size as usize, validation.max_count as usize); + // It may happen that we can't delete enough lower priority messages + // to satisfy size constraints. We check for that before deleting anything, + // taking into account channel message replacement. + if let Some(account_rec) = self.accounts.get(account) { + if let Some(channel) = statement.channel() { + if let Some(channel_record) = account_rec.channels.get(&channel) { + if priority <= channel_record.priority { + // Trying to replace channel message with lower priority + log::debug!( + target: LOG_TARGET, + "Ignored lower priority channel message: {:?} {:?} <= {:?}", + HexDisplay::from(&hash), + priority, + channel_record.priority, + ); + return MaybeInserted::Ignored + } else { + // Would replace channel message. Still need to check for size constraints + // below. + log::debug!( + target: LOG_TARGET, + "Replacing higher priority channel message: {:?} ({:?}) > {:?} ({:?})", + HexDisplay::from(&hash), + priority, + HexDisplay::from(&channel_record.hash), + channel_record.priority, + ); + let key = PriorityKey { + hash: channel_record.hash, + priority: channel_record.priority, + }; + if let Some((_channel, len)) = account_rec.by_priority.get(&key) { + would_free_size += *len; + evicted.insert(channel_record.hash); + } + } + } + } + // Check if we can evict enough lower priority statements to satisfy constraints + for (entry, (_, len)) in account_rec.by_priority.iter() { + if (account_rec.data_size - would_free_size + statement_len <= max_size) && + account_rec.by_priority.len() + 1 - evicted.len() <= max_count + { + // Satisfied + break + } + if evicted.contains(&entry.hash) { + // Already accounted for above + continue + } + if entry.priority >= priority { + log::debug!( + target: LOG_TARGET, + "Ignored message due to constraints {:?} {:?} < {:?}", + HexDisplay::from(&hash), + priority, + entry.priority, + ); + return MaybeInserted::Ignored + } + evicted.insert(entry.hash); + would_free_size += len; + } + } + // Now check global constraints as well. + if !((self.total_size - would_free_size + statement_len <= self.options.max_total_size) && + self.entries.len() + 1 - evicted.len() <= self.options.max_total_statements) + { + log::debug!( + target: LOG_TARGET, + "Ignored statement {} because the store is full (size={}, count={})", + HexDisplay::from(&hash), + self.total_size, + self.entries.len(), + ); + return MaybeInserted::Ignored + } + + for h in &evicted { + self.make_expired(h, current_time); + } + self.insert_new(hash, *account, statement); + MaybeInserted::Inserted(evicted) + } +} + +impl Store { + /// Create a new shared store instance. There should only be one per process. + /// `path` will be used to open a statement database or create a new one if it does not exist. + pub fn new_shared( + path: &std::path::Path, + options: Options, + client: Arc, + keystore: Arc, + prometheus: Option<&PrometheusRegistry>, + task_spawner: &dyn SpawnNamed, + ) -> Result> + where + Block: BlockT, + Block::Hash: From, + Client: ProvideRuntimeApi + + HeaderBackend + + sc_client_api::ExecutorProvider + + Send + + Sync + + 'static, + Client::Api: ValidateStatement, + { + let store = Arc::new(Self::new(path, options, client, keystore, prometheus)?); + + // Perform periodic statement store maintenance + let worker_store = store.clone(); + task_spawner.spawn( + "statement-store-maintenance", + Some("statement-store"), + Box::pin(async move { + let mut interval = tokio::time::interval(MAINTENANCE_PERIOD); + loop { + interval.tick().await; + worker_store.maintain(); + } + }), + ); + + Ok(store) + } + + /// Create a new instance. + /// `path` will be used to open a statement database or create a new one if it does not exist. + fn new( + path: &std::path::Path, + options: Options, + client: Arc, + keystore: Arc, + prometheus: Option<&PrometheusRegistry>, + ) -> Result + where + Block: BlockT, + Block::Hash: From, + Client: ProvideRuntimeApi + HeaderBackend + Send + Sync + 'static, + Client::Api: ValidateStatement, + { + let mut path: std::path::PathBuf = path.into(); + path.push("statements"); + + let mut config = parity_db::Options::with_columns(&path, col::COUNT); + + let statement_col = &mut config.columns[col::STATEMENTS as usize]; + statement_col.ref_counted = false; + statement_col.preimage = true; + statement_col.uniform = true; + let db = parity_db::Db::open_or_create(&config).map_err(|e| Error::Db(e.to_string()))?; + match db.get(col::META, &KEY_VERSION).map_err(|e| Error::Db(e.to_string()))? { + Some(version) => { + let version = u32::from_le_bytes( + version + .try_into() + .map_err(|_| Error::Db("Error reading database version".into()))?, + ); + if version != CURRENT_VERSION { + return Err(Error::Db(format!("Unsupported database version: {version}"))) + } + }, + None => { + db.commit([( + col::META, + KEY_VERSION.to_vec(), + Some(CURRENT_VERSION.to_le_bytes().to_vec()), + )]) + .map_err(|e| Error::Db(e.to_string()))?; + }, + } + + let validator = ClientWrapper { client, _block: Default::default() }; + let validate_fn = Box::new(move |block, source, statement| { + validator.validate_statement(block, source, statement) + }); + + let store = Store { + db, + index: RwLock::new(Index::new(options)), + validate_fn, + keystore, + time_override: None, + metrics: PrometheusMetrics::new(prometheus), + }; + store.populate()?; + Ok(store) + } + + /// Create memory index from the data. + // This may be moved to a background thread if it slows startup too much. + // This function should only be used on startup. There should be no other DB operations when + // iterating the index. + fn populate(&self) -> Result<()> { + { + let mut index = self.index.write(); + self.db + .iter_column_while(col::STATEMENTS, |item| { + let statement = item.value; + if let Ok(statement) = Statement::decode(&mut statement.as_slice()) { + let hash = statement.hash(); + log::trace!( + target: LOG_TARGET, + "Statement loaded {:?}", + HexDisplay::from(&hash) + ); + if let Some(account_id) = statement.account_id() { + index.insert_new(hash, account_id, &statement); + } else { + log::debug!( + target: LOG_TARGET, + "Error decoding statement loaded from the DB: {:?}", + HexDisplay::from(&hash) + ); + } + } + true + }) + .map_err(|e| Error::Db(e.to_string()))?; + self.db + .iter_column_while(col::EXPIRED, |item| { + let expired_info = item.value; + if let Ok((hash, timestamp)) = + <(Hash, u64)>::decode(&mut expired_info.as_slice()) + { + log::trace!( + target: LOG_TARGET, + "Statement loaded (expired): {:?}", + HexDisplay::from(&hash) + ); + index.insert_expired(hash, timestamp); + } + true + }) + .map_err(|e| Error::Db(e.to_string()))?; + } + + self.maintain(); + Ok(()) + } + + fn collect_statements( + &self, + key: Option, + match_all_topics: &[Topic], + mut f: impl FnMut(Statement) -> Option, + ) -> Result> { + let mut result = Vec::new(); + let index = self.index.read(); + index.iterate_with(key, match_all_topics, |hash| { + match self.db.get(col::STATEMENTS, hash).map_err(|e| Error::Db(e.to_string()))? { + Some(entry) => { + if let Ok(statement) = Statement::decode(&mut entry.as_slice()) { + if let Some(data) = f(statement) { + result.push(data); + } + } else { + // DB inconsistency + log::warn!( + target: LOG_TARGET, + "Corrupt statement {:?}", + HexDisplay::from(hash) + ); + } + }, + None => { + // DB inconsistency + log::warn!( + target: LOG_TARGET, + "Missing statement {:?}", + HexDisplay::from(hash) + ); + }, + } + Ok(()) + })?; + Ok(result) + } + + /// Perform periodic store maintenance + pub fn maintain(&self) { + log::trace!(target: LOG_TARGET, "Started store maintenance"); + let deleted = self.index.write().maintain(self.timestamp()); + let deleted: Vec<_> = + deleted.into_iter().map(|hash| (col::EXPIRED, hash.to_vec(), None)).collect(); + let count = deleted.len() as u64; + if let Err(e) = self.db.commit(deleted) { + log::warn!(target: LOG_TARGET, "Error writing to the statement database: {:?}", e); + } else { + self.metrics.report(|metrics| metrics.statements_pruned.inc_by(count)); + } + log::trace!( + target: LOG_TARGET, + "Completed store maintenance. Purged: {}, Active: {}, Expired: {}", + count, + self.index.read().entries.len(), + self.index.read().expired.len() + ); + } + + fn timestamp(&self) -> u64 { + self.time_override.unwrap_or_else(|| { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap_or_default() + .as_secs() + }) + } + + #[cfg(test)] + fn set_time(&mut self, time: u64) { + self.time_override = Some(time); + } + + /// Returns `self` as [`StatementStoreExt`]. + pub fn as_statement_store_ext(self: Arc) -> StatementStoreExt { + StatementStoreExt::new(self) + } +} + +impl StatementStore for Store { + /// Return all statements. + fn statements(&self) -> Result> { + let index = self.index.read(); + let mut result = Vec::with_capacity(index.entries.len()); + for h in self.index.read().entries.keys() { + let encoded = self.db.get(col::STATEMENTS, h).map_err(|e| Error::Db(e.to_string()))?; + if let Some(encoded) = encoded { + if let Ok(statement) = Statement::decode(&mut encoded.as_slice()) { + let hash = statement.hash(); + result.push((hash, statement)); + } + } + } + Ok(result) + } + + /// Returns a statement by hash. + fn statement(&self, hash: &Hash) -> Result> { + Ok( + match self + .db + .get(col::STATEMENTS, hash.as_slice()) + .map_err(|e| Error::Db(e.to_string()))? + { + Some(entry) => { + log::trace!( + target: LOG_TARGET, + "Queried statement {:?}", + HexDisplay::from(hash) + ); + Some( + Statement::decode(&mut entry.as_slice()) + .map_err(|e| Error::Decode(e.to_string()))?, + ) + }, + None => { + log::trace!( + target: LOG_TARGET, + "Queried missing statement {:?}", + HexDisplay::from(hash) + ); + None + }, + }, + ) + } + + /// Return the data of all known statements which include all topics and have no `DecryptionKey` + /// field. + fn broadcasts(&self, match_all_topics: &[Topic]) -> Result>> { + self.collect_statements(None, match_all_topics, |statement| statement.into_data()) + } + + /// Return the data of all known statements whose decryption key is identified as `dest` (this + /// will generally be the public key or a hash thereof for symmetric ciphers, or a hash of the + /// private key for symmetric ciphers). + fn posted(&self, match_all_topics: &[Topic], dest: [u8; 32]) -> Result>> { + self.collect_statements(Some(dest), match_all_topics, |statement| statement.into_data()) + } + + /// Return the decrypted data of all known statements whose decryption key is identified as + /// `dest`. The key must be available to the client. + fn posted_clear(&self, match_all_topics: &[Topic], dest: [u8; 32]) -> Result>> { + self.collect_statements(Some(dest), match_all_topics, |statement| { + if let (Some(key), Some(_)) = (statement.decryption_key(), statement.data()) { + let public: sp_core::ed25519::Public = UncheckedFrom::unchecked_from(key); + let public: sp_statement_store::ed25519::Public = public.into(); + match self.keystore.key_pair::(&public) { + Err(e) => { + log::debug!( + target: LOG_TARGET, + "Keystore error: {:?}, for statement {:?}", + e, + HexDisplay::from(&statement.hash()) + ); + None + }, + Ok(None) => { + log::debug!( + target: LOG_TARGET, + "Keystore is missing key for statement {:?}", + HexDisplay::from(&statement.hash()) + ); + None + }, + Ok(Some(pair)) => match statement.decrypt_private(&pair.into_inner()) { + Ok(r) => r, + Err(e) => { + log::debug!( + target: LOG_TARGET, + "Decryption error: {:?}, for statement {:?}", + e, + HexDisplay::from(&statement.hash()) + ); + None + }, + }, + } + } else { + None + } + }) + } + + /// Submit a statement to the store. Validates the statement and returns validation result. + fn submit(&self, statement: Statement, source: StatementSource) -> SubmitResult { + let hash = statement.hash(); + match self.index.read().query(&hash) { + IndexQuery::Expired => + if !source.can_be_resubmitted() { + return SubmitResult::KnownExpired + }, + IndexQuery::Exists => + if !source.can_be_resubmitted() { + return SubmitResult::Known + }, + IndexQuery::Unknown => {}, + } + + let Some(account_id) = statement.account_id() else { + log::debug!( + target: LOG_TARGET, + "Statement validation failed: Missing proof ({:?})", + HexDisplay::from(&hash), + ); + self.metrics.report(|metrics| metrics.validations_invalid.inc()); + return SubmitResult::Bad("No statement proof") + }; + + // Validate. + let at_block = if let Some(Proof::OnChain { block_hash, .. }) = statement.proof() { + Some(*block_hash) + } else { + None + }; + let validation_result = (self.validate_fn)(at_block, source, statement.clone()); + let validation = match validation_result { + Ok(validation) => validation, + Err(InvalidStatement::BadProof) => { + log::debug!( + target: LOG_TARGET, + "Statement validation failed: BadProof, {:?}", + HexDisplay::from(&hash), + ); + self.metrics.report(|metrics| metrics.validations_invalid.inc()); + return SubmitResult::Bad("Bad statement proof") + }, + Err(InvalidStatement::NoProof) => { + log::debug!( + target: LOG_TARGET, + "Statement validation failed: NoProof, {:?}", + HexDisplay::from(&hash), + ); + self.metrics.report(|metrics| metrics.validations_invalid.inc()); + return SubmitResult::Bad("Missing statement proof") + }, + Err(InvalidStatement::InternalError) => + return SubmitResult::InternalError(Error::Runtime), + }; + + let current_time = self.timestamp(); + let mut commit = Vec::new(); + { + let mut index = self.index.write(); + + let evicted = + match index.insert(hash, &statement, &account_id, &validation, current_time) { + MaybeInserted::Ignored => return SubmitResult::Ignored, + MaybeInserted::Inserted(evicted) => evicted, + }; + + commit.push((col::STATEMENTS, hash.to_vec(), Some(statement.encode()))); + for hash in evicted { + commit.push((col::STATEMENTS, hash.to_vec(), None)); + commit.push((col::EXPIRED, hash.to_vec(), Some((hash, current_time).encode()))); + } + if let Err(e) = self.db.commit(commit) { + log::debug!( + target: LOG_TARGET, + "Statement validation failed: database error {}, {:?}", + e, + statement + ); + return SubmitResult::InternalError(Error::Db(e.to_string())) + } + } // Release index lock + self.metrics.report(|metrics| metrics.submitted_statements.inc()); + let network_priority = NetworkPriority::High; + log::trace!(target: LOG_TARGET, "Statement submitted: {:?}", HexDisplay::from(&hash)); + SubmitResult::New(network_priority) + } + + /// Remove a statement by hash. + fn remove(&self, hash: &Hash) -> Result<()> { + let current_time = self.timestamp(); + { + let mut index = self.index.write(); + if index.make_expired(hash, current_time) { + let commit = [ + (col::STATEMENTS, hash.to_vec(), None), + (col::EXPIRED, hash.to_vec(), Some((hash, current_time).encode())), + ]; + if let Err(e) = self.db.commit(commit) { + log::debug!( + target: LOG_TARGET, + "Error removing statement: database error {}, {:?}", + e, + HexDisplay::from(hash), + ); + return Err(Error::Db(e.to_string())) + } + } + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::Store; + use sc_keystore::Keystore; + use sp_core::Pair; + use sp_statement_store::{ + runtime_api::{InvalidStatement, ValidStatement, ValidateStatement}, + AccountId, Channel, DecryptionKey, NetworkPriority, Proof, SignatureVerificationResult, + Statement, StatementSource, StatementStore, SubmitResult, Topic, + }; + + type Extrinsic = sp_runtime::OpaqueExtrinsic; + type Hash = sp_core::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type BlockNumber = u64; + type Header = sp_runtime::generic::Header; + type Block = sp_runtime::generic::Block; + + const CORRECT_BLOCK_HASH: [u8; 32] = [1u8; 32]; + + #[derive(Clone)] + pub(crate) struct TestClient; + + pub(crate) struct RuntimeApi { + _inner: TestClient, + } + + impl sp_api::ProvideRuntimeApi for TestClient { + type Api = RuntimeApi; + fn runtime_api(&self) -> sp_api::ApiRef { + RuntimeApi { _inner: self.clone() }.into() + } + } + + sp_api::mock_impl_runtime_apis! { + impl ValidateStatement for RuntimeApi { + fn validate_statement( + _source: StatementSource, + statement: Statement, + ) -> std::result::Result { + use crate::tests::account; + match statement.verify_signature() { + SignatureVerificationResult::Valid(_) => Ok(ValidStatement{max_count: 100, max_size: 1000}), + SignatureVerificationResult::Invalid => Err(InvalidStatement::BadProof), + SignatureVerificationResult::NoSignature => { + if let Some(Proof::OnChain { block_hash, .. }) = statement.proof() { + if block_hash == &CORRECT_BLOCK_HASH { + let (max_count, max_size) = match statement.account_id() { + Some(a) if a == account(1) => (1, 1000), + Some(a) if a == account(2) => (2, 1000), + Some(a) if a == account(3) => (3, 1000), + Some(a) if a == account(4) => (4, 1000), + _ => (2, 2000), + }; + Ok(ValidStatement{ max_count, max_size }) + } else { + Err(InvalidStatement::BadProof) + } + } else { + Err(InvalidStatement::BadProof) + } + } + } + } + } + } + + impl sp_blockchain::HeaderBackend for TestClient { + fn header(&self, _hash: Hash) -> sp_blockchain::Result> { + unimplemented!() + } + fn info(&self) -> sp_blockchain::Info { + sp_blockchain::Info { + best_hash: CORRECT_BLOCK_HASH.into(), + best_number: 0, + genesis_hash: Default::default(), + finalized_hash: CORRECT_BLOCK_HASH.into(), + finalized_number: 1, + finalized_state: None, + number_leaves: 0, + block_gap: None, + } + } + fn status(&self, _hash: Hash) -> sp_blockchain::Result { + unimplemented!() + } + fn number(&self, _hash: Hash) -> sp_blockchain::Result> { + unimplemented!() + } + fn hash(&self, _number: BlockNumber) -> sp_blockchain::Result> { + unimplemented!() + } + } + + fn test_store() -> (Store, tempfile::TempDir) { + let _ = env_logger::try_init(); + let temp_dir = tempfile::Builder::new().tempdir().expect("Error creating test dir"); + + let client = std::sync::Arc::new(TestClient); + let mut path: std::path::PathBuf = temp_dir.path().into(); + path.push("db"); + let keystore = std::sync::Arc::new(sc_keystore::LocalKeystore::in_memory()); + let store = Store::new(&path, Default::default(), client, keystore, None).unwrap(); + (store, temp_dir) // return order is important. Store must be dropped before TempDir + } + + fn signed_statement(data: u8) -> Statement { + signed_statement_with_topics(data, &[], None) + } + + fn signed_statement_with_topics( + data: u8, + topics: &[Topic], + dec_key: Option, + ) -> Statement { + let mut statement = Statement::new(); + statement.set_plain_data(vec![data]); + for i in 0..topics.len() { + statement.set_topic(i, topics[i]); + } + if let Some(key) = dec_key { + statement.set_decryption_key(key); + } + let kp = sp_core::ed25519::Pair::from_string("//Alice", None).unwrap(); + statement.sign_ed25519_private(&kp); + statement + } + + fn topic(data: u64) -> Topic { + let mut topic: Topic = Default::default(); + topic[0..8].copy_from_slice(&data.to_le_bytes()); + topic + } + + fn dec_key(data: u64) -> DecryptionKey { + let mut dec_key: DecryptionKey = Default::default(); + dec_key[0..8].copy_from_slice(&data.to_le_bytes()); + dec_key + } + + fn account(id: u64) -> AccountId { + let mut account: AccountId = Default::default(); + account[0..8].copy_from_slice(&id.to_le_bytes()); + account + } + + fn channel(id: u64) -> Channel { + let mut channel: Channel = Default::default(); + channel[0..8].copy_from_slice(&id.to_le_bytes()); + channel + } + + fn statement(account_id: u64, priority: u32, c: Option, data_len: usize) -> Statement { + let mut statement = Statement::new(); + let mut data = Vec::new(); + data.resize(data_len, 0); + statement.set_plain_data(data); + statement.set_priority(priority); + if let Some(c) = c { + statement.set_channel(channel(c)); + } + statement.set_proof(Proof::OnChain { + block_hash: CORRECT_BLOCK_HASH, + who: account(account_id), + event_index: 0, + }); + statement + } + + #[test] + fn submit_one() { + let (store, _temp) = test_store(); + let statement0 = signed_statement(0); + assert_eq!( + store.submit(statement0, StatementSource::Network), + SubmitResult::New(NetworkPriority::High) + ); + let unsigned = statement(0, 1, None, 0); + assert_eq!( + store.submit(unsigned, StatementSource::Network), + SubmitResult::New(NetworkPriority::High) + ); + } + + #[test] + fn save_and_load_statements() { + let (store, temp) = test_store(); + let statement0 = signed_statement(0); + let statement1 = signed_statement(1); + let statement2 = signed_statement(2); + assert_eq!( + store.submit(statement0.clone(), StatementSource::Network), + SubmitResult::New(NetworkPriority::High) + ); + assert_eq!( + store.submit(statement1.clone(), StatementSource::Network), + SubmitResult::New(NetworkPriority::High) + ); + assert_eq!( + store.submit(statement2.clone(), StatementSource::Network), + SubmitResult::New(NetworkPriority::High) + ); + assert_eq!(store.statements().unwrap().len(), 3); + assert_eq!(store.broadcasts(&[]).unwrap().len(), 3); + assert_eq!(store.statement(&statement1.hash()).unwrap(), Some(statement1.clone())); + let keystore = store.keystore.clone(); + drop(store); + + let client = std::sync::Arc::new(TestClient); + let mut path: std::path::PathBuf = temp.path().into(); + path.push("db"); + let store = Store::new(&path, Default::default(), client, keystore, None).unwrap(); + assert_eq!(store.statements().unwrap().len(), 3); + assert_eq!(store.broadcasts(&[]).unwrap().len(), 3); + assert_eq!(store.statement(&statement1.hash()).unwrap(), Some(statement1)); + } + + #[test] + fn search_by_topic_and_key() { + let (store, _temp) = test_store(); + let statement0 = signed_statement(0); + let statement1 = signed_statement_with_topics(1, &[topic(0)], None); + let statement2 = signed_statement_with_topics(2, &[topic(0), topic(1)], Some(dec_key(2))); + let statement3 = signed_statement_with_topics(3, &[topic(0), topic(1), topic(2)], None); + let statement4 = + signed_statement_with_topics(4, &[topic(0), topic(42), topic(2), topic(3)], None); + let statements = vec![statement0, statement1, statement2, statement3, statement4]; + for s in &statements { + store.submit(s.clone(), StatementSource::Network); + } + + let assert_topics = |topics: &[u64], key: Option, expected: &[u8]| { + let key = key.map(dec_key); + let topics: Vec<_> = topics.iter().map(|t| topic(*t)).collect(); + let mut got_vals: Vec<_> = if let Some(key) = key { + store.posted(&topics, key).unwrap().into_iter().map(|d| d[0]).collect() + } else { + store.broadcasts(&topics).unwrap().into_iter().map(|d| d[0]).collect() + }; + got_vals.sort(); + assert_eq!(expected.to_vec(), got_vals); + }; + + assert_topics(&[], None, &[0, 1, 3, 4]); + assert_topics(&[], Some(2), &[2]); + assert_topics(&[0], None, &[1, 3, 4]); + assert_topics(&[1], None, &[3]); + assert_topics(&[2], None, &[3, 4]); + assert_topics(&[3], None, &[4]); + assert_topics(&[42], None, &[4]); + + assert_topics(&[0, 1], None, &[3]); + assert_topics(&[0, 1], Some(2), &[2]); + assert_topics(&[0, 1, 99], Some(2), &[]); + assert_topics(&[1, 2], None, &[3]); + assert_topics(&[99], None, &[]); + assert_topics(&[0, 99], None, &[]); + assert_topics(&[0, 1, 2, 3, 42], None, &[]); + } + + #[test] + fn constraints() { + let (store, _temp) = test_store(); + + store.index.write().options.max_total_size = 3000; + let source = StatementSource::Network; + let ok = SubmitResult::New(NetworkPriority::High); + let ignored = SubmitResult::Ignored; + + // Account 1 (limit = 1 msg, 1000 bytes) + + // Oversized statement is not allowed. Limit for account 1 is 1 msg, 1000 bytes + assert_eq!(store.submit(statement(1, 1, Some(1), 2000), source), ignored); + assert_eq!(store.submit(statement(1, 1, Some(1), 500), source), ok); + // Would not replace channel message with same priority + assert_eq!(store.submit(statement(1, 1, Some(1), 200), source), ignored); + assert_eq!(store.submit(statement(1, 2, Some(1), 600), source), ok); + // Submit another message to another channel with lower priority. Should not be allowed + // because msg count limit is 1 + assert_eq!(store.submit(statement(1, 1, Some(2), 100), source), ignored); + assert_eq!(store.index.read().expired.len(), 1); + + // Account 2 (limit = 2 msg, 1000 bytes) + + assert_eq!(store.submit(statement(2, 1, None, 500), source), ok); + assert_eq!(store.submit(statement(2, 2, None, 100), source), ok); + // Should evict priority 1 + assert_eq!(store.submit(statement(2, 3, None, 500), source), ok); + assert_eq!(store.index.read().expired.len(), 2); + // Should evict all + assert_eq!(store.submit(statement(2, 4, None, 1000), source), ok); + assert_eq!(store.index.read().expired.len(), 4); + + // Account 3 (limit = 3 msg, 1000 bytes) + + assert_eq!(store.submit(statement(3, 2, Some(1), 300), source), ok); + assert_eq!(store.submit(statement(3, 3, Some(2), 300), source), ok); + assert_eq!(store.submit(statement(3, 4, Some(3), 300), source), ok); + // Should evict 2 and 3 + assert_eq!(store.submit(statement(3, 5, None, 500), source), ok); + assert_eq!(store.index.read().expired.len(), 6); + + assert_eq!(store.index.read().total_size, 2400); + assert_eq!(store.index.read().entries.len(), 4); + + // Should be over the global size limit + assert_eq!(store.submit(statement(1, 1, None, 700), source), ignored); + // Should be over the global count limit + store.index.write().options.max_total_statements = 4; + assert_eq!(store.submit(statement(1, 1, None, 100), source), ignored); + + let mut expected_statements = vec![ + statement(1, 2, Some(1), 600).hash(), + statement(2, 4, None, 1000).hash(), + statement(3, 4, Some(3), 300).hash(), + statement(3, 5, None, 500).hash(), + ]; + expected_statements.sort(); + let mut statements: Vec<_> = + store.statements().unwrap().into_iter().map(|(hash, _)| hash).collect(); + statements.sort(); + assert_eq!(expected_statements, statements); + } + + #[test] + fn expired_statements_are_purged() { + use super::DEFAULT_PURGE_AFTER_SEC; + let (mut store, temp) = test_store(); + let mut statement = statement(1, 1, Some(3), 100); + store.set_time(0); + statement.set_topic(0, topic(4)); + store.submit(statement.clone(), StatementSource::Network); + assert_eq!(store.index.read().entries.len(), 1); + store.remove(&statement.hash()).unwrap(); + assert_eq!(store.index.read().entries.len(), 0); + assert_eq!(store.index.read().accounts.len(), 0); + store.set_time(DEFAULT_PURGE_AFTER_SEC + 1); + store.maintain(); + assert_eq!(store.index.read().expired.len(), 0); + let keystore = store.keystore.clone(); + drop(store); + + let client = std::sync::Arc::new(TestClient); + let mut path: std::path::PathBuf = temp.path().into(); + path.push("db"); + let store = Store::new(&path, Default::default(), client, keystore, None).unwrap(); + assert_eq!(store.statements().unwrap().len(), 0); + assert_eq!(store.index.read().expired.len(), 0); + } + + #[test] + fn posted_clear_decrypts() { + let (store, _temp) = test_store(); + let public = store + .keystore + .ed25519_generate_new(sp_core::crypto::key_types::STATEMENT, None) + .unwrap(); + let statement1 = statement(1, 1, None, 100); + let mut statement2 = statement(1, 2, None, 0); + let plain = b"The most valuable secret".to_vec(); + statement2.encrypt(&plain, &public).unwrap(); + store.submit(statement1, StatementSource::Network); + store.submit(statement2, StatementSource::Network); + let posted_clear = store.posted_clear(&[], public.into()).unwrap(); + assert_eq!(posted_clear, vec![plain]); + } +} diff --git a/client/statement-store/src/metrics.rs b/client/statement-store/src/metrics.rs new file mode 100644 index 000000000000..cf191b79757e --- /dev/null +++ b/client/statement-store/src/metrics.rs @@ -0,0 +1,79 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +//! Statement store Prometheus metrics. + +use std::sync::Arc; + +use prometheus_endpoint::{register, Counter, PrometheusError, Registry, U64}; + +#[derive(Clone, Default)] +pub struct MetricsLink(Arc>); + +impl MetricsLink { + pub fn new(registry: Option<&Registry>) -> Self { + Self(Arc::new(registry.and_then(|registry| { + Metrics::register(registry) + .map_err(|err| { + log::warn!("Failed to register prometheus metrics: {}", err); + }) + .ok() + }))) + } + + pub fn report(&self, do_this: impl FnOnce(&Metrics)) { + if let Some(metrics) = self.0.as_ref() { + do_this(metrics); + } + } +} + +/// Statement store Prometheus metrics. +pub struct Metrics { + pub submitted_statements: Counter, + pub validations_invalid: Counter, + pub statements_pruned: Counter, +} + +impl Metrics { + pub fn register(registry: &Registry) -> Result { + Ok(Self { + submitted_statements: register( + Counter::new( + "substrate_sub_statement_store_submitted_statements", + "Total number of statements submitted", + )?, + registry, + )?, + validations_invalid: register( + Counter::new( + "substrate_sub_statement_store_validations_invalid", + "Total number of statements that were removed from the pool as invalid", + )?, + registry, + )?, + statements_pruned: register( + Counter::new( + "substrate_sub_statement_store_block_statements", + "Total number of statements that was requested to be pruned by block events", + )?, + registry, + )?, + }) + } +} diff --git a/client/storage-monitor/Cargo.toml b/client/storage-monitor/Cargo.toml index 2ba24f9e2e71..02c67dedc59c 100644 --- a/client/storage-monitor/Cargo.toml +++ b/client/storage-monitor/Cargo.toml @@ -9,12 +9,10 @@ description = "Storage monitor service for substrate" homepage = "https://substrate.io" [dependencies] -clap = { version = "4.0.9", features = ["derive", "string"] } -futures = "0.3.21" +clap = { version = "4.2.5", features = ["derive", "string"] } log = "0.4.17" -nix = { version = "0.26.1", features = ["fs"] } +fs4 = "0.6.3" sc-client-db = { version = "0.10.0-dev", default-features = false, path = "../db" } -sc-utils = { version = "4.0.0-dev", path = "../utils" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } tokio = "1.22.0" thiserror = "1.0.30" diff --git a/client/storage-monitor/src/lib.rs b/client/storage-monitor/src/lib.rs index 162fcfe34a52..655b940e8bed 100644 --- a/client/storage-monitor/src/lib.rs +++ b/client/storage-monitor/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -17,22 +17,25 @@ // along with this program. If not, see . use clap::Args; -use nix::{errno::Errno, sys::statvfs::statvfs}; use sc_client_db::DatabaseSource; use sp_core::traits::SpawnEssentialNamed; use std::{ + io, path::{Path, PathBuf}, time::Duration, }; const LOG_TARGET: &str = "storage-monitor"; +/// Result type used in this crate. +pub type Result = std::result::Result; + /// Error type used in this crate. #[derive(Debug, thiserror::Error)] pub enum Error { #[error("IO Error")] - IOError(#[from] Errno), - #[error("Out of storage space: available {0}MB, required {1}MB")] + IOError(#[from] io::Error), + #[error("Out of storage space: available {0}MiB, required {1}MiB")] StorageOutOfSpace(u64, u64), } @@ -42,7 +45,7 @@ pub struct StorageMonitorParams { /// Required available space on database storage. If available space for DB storage drops below /// the given threshold, node will be gracefully terminated. If `0` is given monitoring will be /// disabled. - #[arg(long = "db-storage-threshold", value_name = "MB", default_value_t = 1000)] + #[arg(long = "db-storage-threshold", value_name = "MiB", default_value_t = 1024)] pub threshold: u64, /// How often available space is polled. @@ -50,14 +53,14 @@ pub struct StorageMonitorParams { pub polling_period: u32, } -/// Storage monitor service: checks the available space for the filesystem for fiven path. +/// Storage monitor service: checks the available space for the filesystem for given path. pub struct StorageMonitorService { /// watched path path: PathBuf, /// number of megabytes that shall be free on the filesystem for watched path threshold: u64, - /// storage space polling period (seconds) - polling_period: u32, + /// storage space polling period + polling_period: Duration, } impl StorageMonitorService { @@ -66,7 +69,7 @@ impl StorageMonitorService { parameters: StorageMonitorParams, database: DatabaseSource, spawner: &impl SpawnEssentialNamed, - ) -> Result<(), Error> { + ) -> Result<()> { Ok(match (parameters.threshold, database.path()) { (0, _) => { log::info!( @@ -83,8 +86,7 @@ impl StorageMonitorService { (threshold, Some(path)) => { log::debug!( target: LOG_TARGET, - "Initializing StorageMonitorService for db path: {:?}", - path, + "Initializing StorageMonitorService for db path: {path:?}", ); Self::check_free_space(&path, threshold)?; @@ -92,7 +94,7 @@ impl StorageMonitorService { let storage_monitor_service = StorageMonitorService { path: path.to_path_buf(), threshold, - polling_period: parameters.polling_period, + polling_period: Duration::from_secs(parameters.polling_period.into()), }; spawner.spawn_essential( @@ -108,27 +110,22 @@ impl StorageMonitorService { /// below threshold. async fn run(self) { loop { - tokio::time::sleep(Duration::from_secs(self.polling_period.into())).await; + tokio::time::sleep(self.polling_period).await; if Self::check_free_space(&self.path, self.threshold).is_err() { break }; } } - /// Returns free space in MB, or error if statvfs failed. - fn free_space(path: &Path) -> Result { - statvfs(path) - .map(|stats| { - u64::from(stats.blocks_available()).saturating_mul(u64::from(stats.block_size())) / - 1_000_000 - }) - .map_err(Error::from) + /// Returns free space in MiB, or error if statvfs failed. + fn free_space(path: &Path) -> Result { + Ok(fs4::available_space(path).map(|s| s / 1024 / 1024)?) } - /// Checks if the amount of free space for given `path` is above given `threshold`. + /// Checks if the amount of free space for given `path` is above given `threshold` in MiB. /// If it dropped below, error is returned. /// System errors are silently ignored. - fn check_free_space(path: &Path, threshold: u64) -> Result<(), Error> { + fn check_free_space(path: &Path, threshold: u64) -> Result<()> { match StorageMonitorService::free_space(path) { Ok(available_space) => { log::trace!( @@ -137,14 +134,14 @@ impl StorageMonitorService { ); if available_space < threshold { - log::error!(target: LOG_TARGET, "Available space {available_space}MB for path `{}` dropped below threshold: {threshold}MB , terminating...", path.display()); + log::error!(target: LOG_TARGET, "Available space {available_space}MiB for path `{}` dropped below threshold: {threshold}MiB , terminating...", path.display()); Err(Error::StorageOutOfSpace(available_space, threshold)) } else { Ok(()) } }, Err(e) => { - log::error!(target: LOG_TARGET, "Could not read available space: {:?}.", e); + log::error!(target: LOG_TARGET, "Could not read available space: {e:?}."); Err(e) }, } diff --git a/client/sync-state-rpc/Cargo.toml b/client/sync-state-rpc/Cargo.toml index 3a964521c505..79013cbd5b33 100644 --- a/client/sync-state-rpc/Cargo.toml +++ b/client/sync-state-rpc/Cargo.toml @@ -12,15 +12,15 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } -serde = { version = "1.0.136", features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.85" thiserror = "1.0.30" sc-chain-spec = { version = "4.0.0-dev", path = "../chain-spec" } sc-client-api = { version = "4.0.0-dev", path = "../api" } sc-consensus-babe = { version = "0.10.0-dev", path = "../consensus/babe" } sc-consensus-epochs = { version = "0.10.0-dev", path = "../consensus/epochs" } -sc-finality-grandpa = { version = "0.10.0-dev", path = "../finality-grandpa" } +sc-consensus-grandpa = { version = "0.10.0-dev", path = "../consensus/grandpa" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } diff --git a/client/sync-state-rpc/src/lib.rs b/client/sync-state-rpc/src/lib.rs index 9edc2ceecb78..dda8a7edfa9b 100644 --- a/client/sync-state-rpc/src/lib.rs +++ b/client/sync-state-rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -41,20 +41,21 @@ #![deny(unused_crate_dependencies)] +use std::sync::Arc; + use jsonrpsee::{ - core::{Error as JsonRpseeError, RpcResult}, + core::{async_trait, Error as JsonRpseeError, RpcResult}, proc_macros::rpc, types::{error::CallError, ErrorObject}, }; + use sc_client_api::StorageData; +use sc_consensus_babe::{BabeWorkerHandle, Error as BabeError}; use sp_blockchain::HeaderBackend; use sp_runtime::traits::{Block as BlockT, NumberFor}; -use std::sync::Arc; type SharedAuthoritySet = - sc_finality_grandpa::SharedAuthoritySet<::Hash, NumberFor>; -type SharedEpochChanges = - sc_consensus_epochs::SharedEpochChanges; + sc_consensus_grandpa::SharedAuthoritySet<::Hash, NumberFor>; /// Error type used by this crate. #[derive(Debug, thiserror::Error)] @@ -66,6 +67,9 @@ pub enum Error { #[error("Failed to load the block weight for block {0:?}")] LoadingBlockWeightFailed(Block::Hash), + #[error("Failed to load the BABE epoch data: {0}")] + LoadingEpochDataFailed(BabeError), + #[error("JsonRpc error: {0}")] JsonRpc(String), @@ -117,7 +121,7 @@ pub struct LightSyncState { /// The authority set for grandpa. #[serde(serialize_with = "serialize_encoded")] pub grandpa_authority_set: - sc_finality_grandpa::AuthoritySet<::Hash, NumberFor>, + sc_consensus_grandpa::AuthoritySet<::Hash, NumberFor>, } /// An api for sync state RPC calls. @@ -125,7 +129,7 @@ pub struct LightSyncState { pub trait SyncStateApi { /// Returns the JSON serialized chainspec running the node, with a sync state. #[method(name = "sync_state_genSyncSpec")] - fn system_gen_sync_spec(&self, raw: bool) -> RpcResult; + async fn system_gen_sync_spec(&self, raw: bool) -> RpcResult; } /// An api for sync state RPC calls. @@ -133,7 +137,7 @@ pub struct SyncState { chain_spec: Box, client: Arc, shared_authority_set: SharedAuthoritySet, - shared_epoch_changes: SharedEpochChanges, + babe_worker_handle: BabeWorkerHandle, } impl SyncState @@ -146,18 +150,24 @@ where chain_spec: Box, client: Arc, shared_authority_set: SharedAuthoritySet, - shared_epoch_changes: SharedEpochChanges, + babe_worker_handle: BabeWorkerHandle, ) -> Result> { if sc_chain_spec::get_extension::(chain_spec.extensions()) .is_some() { - Ok(Self { chain_spec, client, shared_authority_set, shared_epoch_changes }) + Ok(Self { chain_spec, client, shared_authority_set, babe_worker_handle }) } else { Err(Error::::LightSyncStateExtensionNotFound) } } - fn build_sync_state(&self) -> Result, Error> { + async fn build_sync_state(&self) -> Result, Error> { + let epoch_changes = self + .babe_worker_handle + .epoch_data() + .await + .map_err(Error::LoadingEpochDataFailed)?; + let finalized_hash = self.client.info().finalized_hash; let finalized_header = self .client @@ -170,20 +180,21 @@ where Ok(LightSyncState { finalized_block_header: finalized_header, - babe_epoch_changes: self.shared_epoch_changes.shared_data().clone(), + babe_epoch_changes: epoch_changes, babe_finalized_block_weight: finalized_block_weight, grandpa_authority_set: self.shared_authority_set.clone_inner(), }) } } +#[async_trait] impl SyncStateApiServer for SyncState where Block: BlockT, Backend: HeaderBackend + sc_client_api::AuxStore + 'static, { - fn system_gen_sync_spec(&self, raw: bool) -> RpcResult { - let current_sync_state = self.build_sync_state()?; + async fn system_gen_sync_spec(&self, raw: bool) -> RpcResult { + let current_sync_state = self.build_sync_state().await?; let mut chain_spec = self.chain_spec.cloned_box(); let extension = sc_chain_spec::get_extension_mut::( diff --git a/client/sysinfo/Cargo.toml b/client/sysinfo/Cargo.toml index 73aa7b8d1042..143551191573 100644 --- a/client/sysinfo/Cargo.toml +++ b/client/sysinfo/Cargo.toml @@ -20,12 +20,12 @@ log = "0.4.17" rand = "0.8.5" rand_pcg = "0.3.1" regex = "1" -serde = { version = "1.0.136", features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.85" sc-telemetry = { version = "4.0.0-dev", path = "../telemetry" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } -sp-std = { version = "5.0.0", path = "../../primitives/std" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } +sp-std = { version = "8.0.0", path = "../../primitives/std" } [dev-dependencies] -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } diff --git a/client/sysinfo/build.rs b/client/sysinfo/build.rs index 6d288107445a..d267f3670608 100644 --- a/client/sysinfo/build.rs +++ b/client/sysinfo/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/sysinfo/src/lib.rs b/client/sysinfo/src/lib.rs index f623bdae53e6..7065c9b997e7 100644 --- a/client/sysinfo/src/lib.rs +++ b/client/sysinfo/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/sysinfo/src/sysinfo.rs b/client/sysinfo/src/sysinfo.rs index 1fbca7d37efb..41161fc685d3 100644 --- a/client/sysinfo/src/sysinfo.rs +++ b/client/sysinfo/src/sysinfo.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/sysinfo/src/sysinfo_linux.rs b/client/sysinfo/src/sysinfo_linux.rs index 41ab6014cbef..ae678e07661e 100644 --- a/client/sysinfo/src/sysinfo_linux.rs +++ b/client/sysinfo/src/sysinfo_linux.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/telemetry/Cargo.toml b/client/telemetry/Cargo.toml index 4d09a28370e6..f138557c8c22 100644 --- a/client/telemetry/Cargo.toml +++ b/client/telemetry/Cargo.toml @@ -16,13 +16,13 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] chrono = "0.4.19" futures = "0.3.21" -libp2p = { version = "0.50.0", features = ["dns", "tcp", "tokio", "wasm-ext", "websocket"] } +libp2p = { version = "0.51.3", features = ["dns", "tcp", "tokio", "wasm-ext", "websocket"] } log = "0.4.17" parking_lot = "0.12.1" pin-project = "1.0.12" sc-utils = { version = "4.0.0-dev", path = "../utils" } rand = "0.8.5" -serde = { version = "1.0.136", features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.85" thiserror = "1.0.30" wasm-timer = "0.2.5" diff --git a/client/telemetry/src/endpoints.rs b/client/telemetry/src/endpoints.rs index fba3822a9067..a4f0d0f83d61 100644 --- a/client/telemetry/src/endpoints.rs +++ b/client/telemetry/src/endpoints.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/telemetry/src/error.rs b/client/telemetry/src/error.rs index 4d9cdc05c51b..b8e3d28751d9 100644 --- a/client/telemetry/src/error.rs +++ b/client/telemetry/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/telemetry/src/lib.rs b/client/telemetry/src/lib.rs index aa6b841b7916..113d8303a20f 100644 --- a/client/telemetry/src/lib.rs +++ b/client/telemetry/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/telemetry/src/node.rs b/client/telemetry/src/node.rs index 71eed04128c7..0bbdbfb622ef 100644 --- a/client/telemetry/src/node.rs +++ b/client/telemetry/src/node.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/telemetry/src/transport.rs b/client/telemetry/src/transport.rs index 5e940bbe54eb..a82626caac2d 100644 --- a/client/telemetry/src/transport.rs +++ b/client/telemetry/src/transport.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/tracing/Cargo.toml b/client/tracing/Cargo.toml index be6237a344f5..fa229782a781 100644 --- a/client/tracing/Cargo.toml +++ b/client/tracing/Cargo.toml @@ -19,27 +19,25 @@ chrono = "0.4.19" lazy_static = "1.4.0" libc = "0.2.121" log = { version = "0.4.17" } -once_cell = "1.8.0" parking_lot = "0.12.1" regex = "1.6.0" rustc-hash = "1.1.0" -serde = "1.0.136" +serde = "1.0.163" thiserror = "1.0.30" tracing = "0.1.29" tracing-log = "0.1.3" tracing-subscriber = { version = "0.2.25", features = ["parking_lot"] } sc-client-api = { version = "4.0.0-dev", path = "../api" } -sc-rpc-server = { version = "4.0.0-dev", path = "../rpc-servers" } sc-tracing-proc-macro = { version = "4.0.0-dev", path = "./proc-macro" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } [dev-dependencies] -criterion = "0.3" +criterion = "0.4.0" [[bench]] name = "bench" diff --git a/client/tracing/benches/bench.rs b/client/tracing/benches/bench.rs index a939a2679739..1379023ddfa6 100644 --- a/client/tracing/benches/bench.rs +++ b/client/tracing/benches/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/client/tracing/proc-macro/Cargo.toml b/client/tracing/proc-macro/Cargo.toml index 031f1fe49d97..4ae836e60836 100644 --- a/client/tracing/proc-macro/Cargo.toml +++ b/client/tracing/proc-macro/Cargo.toml @@ -16,6 +16,6 @@ proc-macro = true [dependencies] proc-macro-crate = "1.1.3" -proc-macro2 = "1.0.37" -quote = { version = "1.0.10", features = ["proc-macro"] } -syn = { version = "1.0.98", features = ["proc-macro", "full", "extra-traits", "parsing"] } +proc-macro2 = "1.0.56" +quote = { version = "1.0.28", features = ["proc-macro"] } +syn = { version = "2.0.16", features = ["proc-macro", "full", "extra-traits", "parsing"] } diff --git a/client/tracing/proc-macro/src/lib.rs b/client/tracing/proc-macro/src/lib.rs index ba757619fb5a..8fb01de40368 100644 --- a/client/tracing/proc-macro/src/lib.rs +++ b/client/tracing/proc-macro/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/tracing/src/block/mod.rs b/client/tracing/src/block/mod.rs index 89c0945e0eb0..c0442abc125b 100644 --- a/client/tracing/src/block/mod.rs +++ b/client/tracing/src/block/mod.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify @@ -34,38 +34,21 @@ use tracing::{ use crate::{SpanDatum, TraceEvent, Values}; use sc_client_api::BlockBackend; -use sc_rpc_server::RPC_MAX_PAYLOAD_DEFAULT; use sp_api::{Core, Encode, Metadata, ProvideRuntimeApi}; use sp_blockchain::HeaderBackend; use sp_core::hexdisplay::HexDisplay; -use sp_rpc::tracing::{BlockTrace, Span, TraceBlockResponse, TraceError}; +use sp_rpc::tracing::{BlockTrace, Span, TraceBlockResponse}; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Header}, }; use sp_tracing::{WASM_NAME_KEY, WASM_TARGET_KEY, WASM_TRACE_IDENTIFIER}; -// Heuristic for average event size in bytes. -const AVG_EVENT: usize = 600 * 8; -// Heuristic for average span size in bytes. -const AVG_SPAN: usize = 100 * 8; -// Estimate of the max base RPC payload size when the Id is bound as a u64. If strings -// are used for the RPC Id this may need to be adjusted. Note: The base payload -// does not include the RPC result. -// -// The estimate is based on the JSON-RPC response message which has the following format: -// `{"jsonrpc":"2.0","result":[],"id":18446744073709551615}`. -// -// We care about the total size of the payload because jsonrpc-server will simply ignore -// messages larger than `sc_rpc_server::MAX_PAYLOAD` and the caller will not get any -// response. -const BASE_PAYLOAD: usize = 100; // Default to only pallet, frame support and state related traces const DEFAULT_TARGETS: &str = "pallet,frame,state"; const TRACE_TARGET: &str = "block_trace"; // The name of a field required for all events. const REQUIRED_EVENT_FIELD: &str = "method"; -const MEGABYTE: usize = 1024 * 1024; /// Tracing Block Result type alias pub type TraceBlockResult = Result; @@ -182,7 +165,6 @@ pub struct BlockExecutor { targets: Option, storage_keys: Option, methods: Option, - rpc_max_payload: usize, } impl BlockExecutor @@ -203,12 +185,8 @@ where targets: Option, storage_keys: Option, methods: Option, - rpc_max_payload: Option, ) -> Self { - let rpc_max_payload = rpc_max_payload - .map(|mb| mb.saturating_mul(MEGABYTE)) - .unwrap_or(RPC_MAX_PAYLOAD_DEFAULT); - Self { client, block, targets, storage_keys, methods, rpc_max_payload } + Self { client, block, targets, storage_keys, methods } } /// Execute block, record all spans and events belonging to `Self::targets` @@ -229,7 +207,6 @@ where .ok_or_else(|| Error::MissingBlockComponent("Extrinsics not found".to_string()))?; tracing::debug!(target: "state_tracing", "Found {} extrinsics", extrinsics.len()); let parent_hash = *header.parent_hash(); - let parent_id = BlockId::Hash(parent_hash); // Remove all `Seal`s as they are added by the consensus engines after building the block. // On import they are normally removed by the consensus engine. header.digest_mut().logs.retain(|d| d.as_seal().is_none()); @@ -249,7 +226,7 @@ where if let Err(e) = dispatcher::with_default(&dispatch, || { let span = tracing::info_span!(target: TRACE_TARGET, "trace_block"); let _enter = span.enter(); - self.client.runtime_api().execute_block(&parent_id, block) + self.client.runtime_api().execute_block(parent_hash, block) }) { return Err(Error::Dispatch(format!( "Failed to collect traces and execute block: {}", @@ -290,24 +267,15 @@ where .collect(); tracing::debug!(target: "state_tracing", "Captured {} spans and {} events", spans.len(), events.len()); - let approx_payload_size = BASE_PAYLOAD + events.len() * AVG_EVENT + spans.len() * AVG_SPAN; - let response = if approx_payload_size > self.rpc_max_payload { - TraceBlockResponse::TraceError(TraceError { - error: "Payload likely exceeds max payload size of RPC server.".to_string(), - }) - } else { - TraceBlockResponse::BlockTrace(BlockTrace { - block_hash: block_id_as_string(BlockId::::Hash(self.block)), - parent_hash: block_id_as_string(parent_id), - tracing_targets: targets.to_string(), - storage_keys: self.storage_keys.clone().unwrap_or_default(), - methods: self.methods.clone().unwrap_or_default(), - spans, - events, - }) - }; - - Ok(response) + Ok(TraceBlockResponse::BlockTrace(BlockTrace { + block_hash: block_id_as_string(BlockId::::Hash(self.block)), + parent_hash: block_id_as_string(BlockId::::Hash(parent_hash)), + tracing_targets: targets.to_string(), + storage_keys: self.storage_keys.clone().unwrap_or_default(), + methods: self.methods.clone().unwrap_or_default(), + spans, + events, + })) } } diff --git a/client/tracing/src/lib.rs b/client/tracing/src/lib.rs index acbde8b75da2..2107943cf6a5 100644 --- a/client/tracing/src/lib.rs +++ b/client/tracing/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -328,6 +328,10 @@ where } fn on_event(&self, event: &Event<'_>, ctx: Context) { + if !self.check_target(event.metadata().target(), &event.metadata().level()) { + return + } + let parent_id = event.parent().cloned().or_else(|| { if event.is_contextual() { ctx.lookup_current().map(|span| span.id()) diff --git a/client/tracing/src/logging/directives.rs b/client/tracing/src/logging/directives.rs index b5fb373674ac..f1caf1a13a2d 100644 --- a/client/tracing/src/logging/directives.rs +++ b/client/tracing/src/logging/directives.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Substrate. // Substrate is free software: you can redistribute it and/or modify @@ -14,18 +14,18 @@ // You should have received a copy of the GNU General Public License // along with Substrate. If not, see . -use once_cell::sync::OnceCell; use parking_lot::Mutex; +use std::sync::OnceLock; use tracing_subscriber::{ filter::Directive, fmt as tracing_fmt, layer, reload::Handle, EnvFilter, Registry, }; // Handle to reload the tracing log filter -static FILTER_RELOAD_HANDLE: OnceCell> = OnceCell::new(); +static FILTER_RELOAD_HANDLE: OnceLock> = OnceLock::new(); // Directives that are defaulted to when resetting the log filter -static DEFAULT_DIRECTIVES: OnceCell>> = OnceCell::new(); +static DEFAULT_DIRECTIVES: OnceLock>> = OnceLock::new(); // Current state of log filter -static CURRENT_DIRECTIVES: OnceCell>> = OnceCell::new(); +static CURRENT_DIRECTIVES: OnceLock>> = OnceLock::new(); /// Add log filter directive(s) to the defaults /// diff --git a/client/tracing/src/logging/event_format.rs b/client/tracing/src/logging/event_format.rs index aec6b76843da..f4579f006c25 100644 --- a/client/tracing/src/logging/event_format.rs +++ b/client/tracing/src/logging/event_format.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/tracing/src/logging/fast_local_time.rs b/client/tracing/src/logging/fast_local_time.rs index 47fc23340482..7be7bec8364a 100644 --- a/client/tracing/src/logging/fast_local_time.rs +++ b/client/tracing/src/logging/fast_local_time.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/tracing/src/logging/layers/mod.rs b/client/tracing/src/logging/layers/mod.rs index 382b79bc85d7..b39320975f86 100644 --- a/client/tracing/src/logging/layers/mod.rs +++ b/client/tracing/src/logging/layers/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/tracing/src/logging/layers/prefix_layer.rs b/client/tracing/src/logging/layers/prefix_layer.rs index 836ffd2adda8..fc444257bde0 100644 --- a/client/tracing/src/logging/layers/prefix_layer.rs +++ b/client/tracing/src/logging/layers/prefix_layer.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/tracing/src/logging/mod.rs b/client/tracing/src/logging/mod.rs index 77307ad999da..a3cf277fbd50 100644 --- a/client/tracing/src/logging/mod.rs +++ b/client/tracing/src/logging/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/tracing/src/logging/stderr_writer.rs b/client/tracing/src/logging/stderr_writer.rs index de78a61af41a..80df2f1fe7cd 100644 --- a/client/tracing/src/logging/stderr_writer.rs +++ b/client/tracing/src/logging/stderr_writer.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/transaction-pool/Cargo.toml b/client/transaction-pool/Cargo.toml index bbb5ebae440a..1718751e6d83 100644 --- a/client/transaction-pool/Cargo.toml +++ b/client/transaction-pool/Cargo.toml @@ -14,14 +14,13 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = "0.1.57" -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" futures-timer = "3.0.2" linked-hash-map = "0.5.4" log = "0.4.17" -num-traits = "0.2.8" parking_lot = "0.12.1" -serde = { version = "1.0.136", features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"] } thiserror = "1.0.30" prometheus-endpoint = { package = "substrate-prometheus-endpoint", version = "0.10.0-dev", path = "../../utils/prometheus" } sc-client-api = { version = "4.0.0-dev", path = "../api" } @@ -29,15 +28,15 @@ sc-transaction-pool-api = { version = "4.0.0-dev", path = "./api" } sc-utils = { version = "4.0.0-dev", path = "../utils" } sp-api = { version = "4.0.0-dev", path = "../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } sp-transaction-pool = { version = "4.0.0-dev", path = "../../primitives/transaction-pool" } [dev-dependencies] -array-bytes = "4.1" +array-bytes = "6.1" assert_matches = "1.3.0" -criterion = "0.3" +criterion = "0.4.0" sc-block-builder = { version = "0.10.0-dev", path = "../block-builder" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } substrate-test-runtime = { version = "2.0.0", path = "../../test-utils/runtime" } diff --git a/client/transaction-pool/api/Cargo.toml b/client/transaction-pool/api/Cargo.toml index e14a3ff4f383..93efa05415d1 100644 --- a/client/transaction-pool/api/Cargo.toml +++ b/client/transaction-pool/api/Cargo.toml @@ -10,12 +10,14 @@ description = "Transaction pool client facing API." [dependencies] async-trait = "0.1.57" +codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" log = "0.4.17" -serde = { version = "1.0.136", features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"] } thiserror = "1.0.30" sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } [dev-dependencies] serde_json = "1.0" diff --git a/client/transaction-pool/api/src/error.rs b/client/transaction-pool/api/src/error.rs index aada44734d05..e521502f66fb 100644 --- a/client/transaction-pool/api/src/error.rs +++ b/client/transaction-pool/api/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/transaction-pool/api/src/lib.rs b/client/transaction-pool/api/src/lib.rs index 870760962a52..32fe30f4584f 100644 --- a/client/transaction-pool/api/src/lib.rs +++ b/client/transaction-pool/api/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -24,11 +24,12 @@ pub mod error; use async_trait::async_trait; use futures::{Future, Stream}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use sp_core::offchain::TransactionPoolExt; use sp_runtime::{ generic::BlockId, traits::{Block as BlockT, Member, NumberFor}, }; -use std::{collections::HashMap, hash::Hash, pin::Pin, sync::Arc}; +use std::{collections::HashMap, hash::Hash, marker::PhantomData, pin::Pin, sync::Arc}; const LOG_TARGET: &str = "txpool::api"; @@ -305,6 +306,20 @@ pub enum ChainEvent { }, } +impl ChainEvent { + /// Returns the block hash associated to the event. + pub fn hash(&self) -> B::Hash { + match self { + Self::NewBestBlock { hash, .. } | Self::Finalized { hash, .. } => *hash, + } + } + + /// Is `self == Self::Finalized`? + pub fn is_finalized(&self) -> bool { + matches!(self, Self::Finalized { .. }) + } +} + /// Trait for transaction pool maintenance. #[async_trait] pub trait MaintainedTransactionPool: TransactionPool { @@ -329,29 +344,43 @@ pub trait LocalTransactionPool: Send + Sync { /// `TransactionSource::Local`. fn submit_local( &self, - at: &BlockId, + at: ::Hash, xt: LocalTransactionFor, ) -> Result; } -/// An abstraction for transaction pool. +impl LocalTransactionPool for Arc { + type Block = T::Block; + + type Hash = T::Hash; + + type Error = T::Error; + + fn submit_local( + &self, + at: ::Hash, + xt: LocalTransactionFor, + ) -> Result { + (**self).submit_local(at, xt) + } +} + +/// An abstraction for [`LocalTransactionPool`] /// -/// This trait is used by offchain calls to be able to submit transactions. -/// The main use case is for offchain workers, to feed back the results of computations, -/// but since the transaction pool access is a separate `ExternalitiesExtension` it can -/// be also used in context of other offchain calls. For one may generate and submit -/// a transaction for some misbehavior reports (say equivocation). -pub trait OffchainSubmitTransaction: Send + Sync { +/// We want to use a transaction pool in [`OffchainTransactionPoolFactory`] in a `Arc` without +/// bleeding the associated types besides the `Block`. Thus, this abstraction here exists to achieve +/// the wrapping in a `Arc`. +trait OffchainSubmitTransaction: Send + Sync { /// Submit transaction. /// /// The transaction will end up in the pool and be propagated to others. - fn submit_at(&self, at: &BlockId, extrinsic: Block::Extrinsic) -> Result<(), ()>; + fn submit_at(&self, at: Block::Hash, extrinsic: Block::Extrinsic) -> Result<(), ()>; } impl OffchainSubmitTransaction for TPool { fn submit_at( &self, - at: &BlockId, + at: ::Hash, extrinsic: ::Extrinsic, ) -> Result<(), ()> { log::debug!( @@ -372,6 +401,54 @@ impl OffchainSubmitTransaction for TP } } +/// Factory for creating [`TransactionPoolExt`]s. +/// +/// This provides an easy way for creating [`TransactionPoolExt`] extensions for registering them in +/// the wasm execution environment to send transactions from an offchain call to the runtime. +#[derive(Clone)] +pub struct OffchainTransactionPoolFactory { + pool: Arc>, +} + +impl OffchainTransactionPoolFactory { + /// Creates a new instance using the given `tx_pool`. + pub fn new + 'static>(tx_pool: T) -> Self { + Self { pool: Arc::new(tx_pool) as Arc<_> } + } + + /// Returns an instance of [`TransactionPoolExt`] bound to the given `block_hash`. + /// + /// Transactions that are being submitted by this instance will be submitted with `block_hash` + /// as context for validation. + pub fn offchain_transaction_pool(&self, block_hash: Block::Hash) -> TransactionPoolExt { + TransactionPoolExt::new(OffchainTransactionPool { pool: self.pool.clone(), block_hash }) + } +} + +/// Wraps a `pool` and `block_hash` to implement [`sp_core::offchain::TransactionPool`]. +struct OffchainTransactionPool { + block_hash: Block::Hash, + pool: Arc>, +} + +impl sp_core::offchain::TransactionPool for OffchainTransactionPool { + fn submit_transaction(&mut self, extrinsic: Vec) -> Result<(), ()> { + let extrinsic = match codec::Decode::decode(&mut &extrinsic[..]) { + Ok(t) => t, + Err(e) => { + log::error!( + target: LOG_TARGET, + "Failed to decode extrinsic in `OffchainTransactionPool::submit_transaction`: {e:?}" + ); + + return Err(()) + }, + }; + + self.pool.submit_at(self.block_hash, extrinsic) + } +} + /// Wrapper functions to keep the API backwards compatible over the wire for the old RPC spec. mod v1_compatible { use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -395,6 +472,29 @@ mod v1_compatible { } } +/// Transaction pool that rejects all submitted transactions. +/// +/// Could be used for example in tests. +pub struct RejectAllTxPool(PhantomData); + +impl Default for RejectAllTxPool { + fn default() -> Self { + Self(PhantomData) + } +} + +impl LocalTransactionPool for RejectAllTxPool { + type Block = Block; + + type Hash = Block::Hash; + + type Error = error::Error; + + fn submit_local(&self, _: Block::Hash, _: Block::Extrinsic) -> Result { + Err(error::Error::ImmediatelyDropped) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/client/transaction-pool/benches/basics.rs b/client/transaction-pool/benches/basics.rs index 97d8edddffdb..d114acc343d5 100644 --- a/client/transaction-pool/benches/basics.rs +++ b/client/transaction-pool/benches/basics.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,7 +18,7 @@ use criterion::{criterion_group, criterion_main, Criterion}; -use codec::{Decode, Encode}; +use codec::Encode; use futures::{ executor::block_on, future::{ready, Ready}, @@ -33,7 +33,7 @@ use sp_runtime::{ ValidTransaction, }, }; -use substrate_test_runtime::{AccountId, Block, Extrinsic, Transfer, H256}; +use substrate_test_runtime::{AccountId, Block, Extrinsic, ExtrinsicBuilder, TransferData, H256}; #[derive(Clone, Debug, Default)] struct TestApi { @@ -65,8 +65,10 @@ impl ChainApi for TestApi { _source: TransactionSource, uxt: ::Extrinsic, ) -> Self::ValidationFuture { - let nonce = uxt.transfer().nonce; - let from = uxt.transfer().from; + let transfer = TransferData::try_from(&uxt) + .expect("uxt is expected to be bench_call (carrying TransferData)"); + let nonce = transfer.nonce; + let from = transfer.from; match self.block_id_to_number(at) { Ok(Some(num)) if num > 5 => return ready(Ok(Err(InvalidTransaction::Stale.into()))), @@ -131,13 +133,8 @@ impl ChainApi for TestApi { } } -fn uxt(transfer: Transfer) -> Extrinsic { - Extrinsic::Transfer { - transfer, - signature: Decode::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) - .expect("infinite input; no dead input space; qed"), - exhaust_resources_when_not_first: false, - } +fn uxt(transfer: TransferData) -> Extrinsic { + ExtrinsicBuilder::new_bench_call(transfer).build() } fn bench_configured(pool: Pool, number: u64) { @@ -146,7 +143,7 @@ fn bench_configured(pool: Pool, number: u64) { let mut tags = Vec::new(); for nonce in 1..=number { - let xt = uxt(Transfer { + let xt = uxt(TransferData { from: AccountId::from_h256(H256::from_low_u64_be(1)), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, diff --git a/client/transaction-pool/src/api.rs b/client/transaction-pool/src/api.rs index 0166dfaced20..f9d79ee429e6 100644 --- a/client/transaction-pool/src/api.rs +++ b/client/transaction-pool/src/api.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -225,27 +225,27 @@ where { sp_tracing::within_span!(sp_tracing::Level::TRACE, "validate_transaction"; { + let block_hash = client.to_hash(at) + .map_err(|e| Error::RuntimeApi(e.to_string()))? + .ok_or_else(|| Error::RuntimeApi(format!("Could not get hash for block `{:?}`.", at)))?; + let runtime_api = client.runtime_api(); let api_version = sp_tracing::within_span! { sp_tracing::Level::TRACE, "check_version"; runtime_api - .api_version::>(at) + .api_version::>(block_hash) .map_err(|e| Error::RuntimeApi(e.to_string()))? .ok_or_else(|| Error::RuntimeApi( format!("Could not find `TaggedTransactionQueue` api for block `{:?}`.", at) )) }?; - let block_hash = client.to_hash(at) - .map_err(|e| Error::RuntimeApi(e.to_string()))? - .ok_or_else(|| Error::RuntimeApi(format!("Could not get hash for block `{:?}`.", at)))?; - use sp_api::Core; sp_tracing::within_span!( sp_tracing::Level::TRACE, "runtime::validate_transaction"; { if api_version >= 3 { - runtime_api.validate_transaction(at, source, uxt, block_hash) + runtime_api.validate_transaction(block_hash, source, uxt, block_hash) .map_err(|e| Error::RuntimeApi(e.to_string())) } else { let block_number = client.to_number(at) @@ -255,7 +255,7 @@ where )?; // The old versions require us to call `initialize_block` before. - runtime_api.initialize_block(at, &sp_runtime::traits::Header::new( + runtime_api.initialize_block(block_hash, &sp_runtime::traits::Header::new( block_number + sp_runtime::traits::One::one(), Default::default(), Default::default(), @@ -265,11 +265,11 @@ where if api_version == 2 { #[allow(deprecated)] // old validate_transaction - runtime_api.validate_transaction_before_version_3(at, source, uxt) + runtime_api.validate_transaction_before_version_3(block_hash, source, uxt) .map_err(|e| Error::RuntimeApi(e.to_string())) } else { #[allow(deprecated)] // old validate_transaction - runtime_api.validate_transaction_before_version_2(at, uxt) + runtime_api.validate_transaction_before_version_2(block_hash, uxt) .map_err(|e| Error::RuntimeApi(e.to_string())) } } diff --git a/client/transaction-pool/src/enactment_state.rs b/client/transaction-pool/src/enactment_state.rs index 7252c0dec7f2..85c572c127e8 100644 --- a/client/transaction-pool/src/enactment_state.rs +++ b/client/transaction-pool/src/enactment_state.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -19,10 +19,9 @@ //! Substrate transaction pool implementation. use crate::LOG_TARGET; -use num_traits::CheckedSub; use sc_transaction_pool_api::ChainEvent; use sp_blockchain::TreeRoute; -use sp_runtime::traits::{Block as BlockT, NumberFor}; +use sp_runtime::traits::{Block as BlockT, NumberFor, Saturating}; /// The threshold since the last update where we will skip any maintenance for blocks. /// @@ -101,17 +100,16 @@ where TreeRouteF: Fn(Block::Hash, Block::Hash) -> Result, String>, BlockNumberF: Fn(Block::Hash) -> Result>, String>, { - let (new_hash, current_hash, finalized) = match event { - ChainEvent::NewBestBlock { hash, .. } => (*hash, self.recent_best_block, false), - ChainEvent::Finalized { hash, .. } => (*hash, self.recent_finalized_block, true), - }; + let new_hash = event.hash(); + let finalized = event.is_finalized(); // do not proceed with txpool maintain if block distance is to high - let skip_maintenance = match (hash_to_number(new_hash), hash_to_number(current_hash)) { - (Ok(Some(new)), Ok(Some(current))) => - new.checked_sub(¤t) > Some(SKIP_MAINTENANCE_THRESHOLD.into()), - _ => true, - }; + let skip_maintenance = + match (hash_to_number(new_hash), hash_to_number(self.recent_best_block)) { + (Ok(Some(new)), Ok(Some(current))) => + new.saturating_sub(current) > SKIP_MAINTENANCE_THRESHOLD.into(), + _ => true, + }; if skip_maintenance { log::debug!(target: LOG_TARGET, "skip maintain: tree_route would be too long"); @@ -131,10 +129,10 @@ where log::debug!( target: LOG_TARGET, - "resolve hash:{:?} finalized:{:?} tree_route:{:?} best_block:{:?} finalized_block:{:?}", - new_hash, - finalized, - tree_route, + "resolve hash: {new_hash:?} finalized: {finalized:?} \ + tree_route: (common {:?}, last {:?}) best_block: {:?} finalized_block:{:?}", + tree_route.common_block(), + tree_route.last(), self.recent_best_block, self.recent_finalized_block ); diff --git a/client/transaction-pool/src/error.rs b/client/transaction-pool/src/error.rs index a4cf756481dc..d93872d6c676 100644 --- a/client/transaction-pool/src/error.rs +++ b/client/transaction-pool/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/transaction-pool/src/graph/base_pool.rs b/client/transaction-pool/src/graph/base_pool.rs index 44371be62c62..a9d2d6c825f6 100644 --- a/client/transaction-pool/src/graph/base_pool.rs +++ b/client/transaction-pool/src/graph/base_pool.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/transaction-pool/src/graph/future.rs b/client/transaction-pool/src/graph/future.rs index d23b5f2b6e1f..bad466318485 100644 --- a/client/transaction-pool/src/graph/future.rs +++ b/client/transaction-pool/src/graph/future.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/transaction-pool/src/graph/listener.rs b/client/transaction-pool/src/graph/listener.rs index c603a542c276..46b7957e0b31 100644 --- a/client/transaction-pool/src/graph/listener.rs +++ b/client/transaction-pool/src/graph/listener.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/transaction-pool/src/graph/mod.rs b/client/transaction-pool/src/graph/mod.rs index 42812897a954..5afdddb7402d 100644 --- a/client/transaction-pool/src/graph/mod.rs +++ b/client/transaction-pool/src/graph/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/transaction-pool/src/graph/pool.rs b/client/transaction-pool/src/graph/pool.rs index 14217d56aaa5..4d34737a7ba7 100644 --- a/client/transaction-pool/src/graph/pool.rs +++ b/client/transaction-pool/src/graph/pool.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -463,7 +463,8 @@ mod tests { use sc_transaction_pool_api::TransactionStatus; use sp_runtime::transaction_validity::TransactionSource; use std::{collections::HashMap, time::Instant}; - use substrate_test_runtime::{AccountId, Extrinsic, Transfer, H256}; + use substrate_test_runtime::{AccountId, ExtrinsicBuilder, Transfer, H256}; + use substrate_test_runtime_client::AccountKeyring::{Alice, Bob}; const SOURCE: TransactionSource = TransactionSource::External; @@ -477,7 +478,7 @@ mod tests { &BlockId::Number(0), SOURCE, uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 0, @@ -494,7 +495,7 @@ mod tests { // given let pool = pool(); let uxt = uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 0, @@ -520,8 +521,8 @@ mod tests { TestApi::default().into(), ); - // after validation `IncludeData` will be set to non-propagable - let uxt = Extrinsic::IncludeData(vec![42]); + // after validation `IncludeData` will be set to non-propagable (validate_transaction mock) + let uxt = ExtrinsicBuilder::new_include_data(vec![42]).build(); // when let res = block_on(pool.submit_one(&BlockId::Number(0), SOURCE, uxt)); @@ -542,7 +543,7 @@ mod tests { &BlockId::Number(0), SOURCE, uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 0, @@ -553,7 +554,7 @@ mod tests { &BlockId::Number(0), SOURCE, uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 1, @@ -565,7 +566,7 @@ mod tests { &BlockId::Number(0), SOURCE, uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 3, @@ -594,7 +595,7 @@ mod tests { &BlockId::Number(0), SOURCE, uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 0, @@ -605,7 +606,7 @@ mod tests { &BlockId::Number(0), SOURCE, uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 1, @@ -616,7 +617,7 @@ mod tests { &BlockId::Number(0), SOURCE, uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 3, @@ -645,7 +646,7 @@ mod tests { &BlockId::Number(0), SOURCE, uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 0, @@ -659,27 +660,27 @@ mod tests { // then assert!(pool.validated_pool.is_banned(&hash1)); } + use codec::Encode; #[test] fn should_limit_futures() { + sp_tracing::try_init_simple(); + + let xt = uxt(Transfer { + from: Alice.into(), + to: AccountId::from_h256(H256::from_low_u64_be(2)), + amount: 5, + nonce: 1, + }); + // given - let limit = Limit { count: 100, total_bytes: 200 }; + let limit = Limit { count: 100, total_bytes: xt.encoded_size() }; let options = Options { ready: limit.clone(), future: limit.clone(), ..Default::default() }; let pool = Pool::new(options, true.into(), TestApi::default().into()); - let hash1 = block_on(pool.submit_one( - &BlockId::Number(0), - SOURCE, - uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), - to: AccountId::from_h256(H256::from_low_u64_be(2)), - amount: 5, - nonce: 1, - }), - )) - .unwrap(); + let hash1 = block_on(pool.submit_one(&BlockId::Number(0), SOURCE, xt)).unwrap(); assert_eq!(pool.validated_pool().status().future, 1); // when @@ -687,7 +688,7 @@ mod tests { &BlockId::Number(0), SOURCE, uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(2)), + from: Bob.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 10, @@ -715,7 +716,7 @@ mod tests { &BlockId::Number(0), SOURCE, uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 1, @@ -738,7 +739,7 @@ mod tests { &BlockId::Number(0), SOURCE, uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: INVALID_NONCE, @@ -763,7 +764,7 @@ mod tests { &BlockId::Number(0), SOURCE, uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 0, @@ -795,7 +796,7 @@ mod tests { &BlockId::Number(0), SOURCE, uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 0, @@ -828,7 +829,7 @@ mod tests { &BlockId::Number(0), SOURCE, uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 1, @@ -843,7 +844,7 @@ mod tests { &BlockId::Number(0), SOURCE, uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 0, @@ -863,7 +864,7 @@ mod tests { // given let pool = pool(); let uxt = uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 0, @@ -887,7 +888,7 @@ mod tests { // given let pool = pool(); let uxt = uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 0, @@ -918,7 +919,7 @@ mod tests { let pool = Pool::new(options, true.into(), TestApi::default().into()); let xt = uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 0, @@ -928,7 +929,7 @@ mod tests { // when let xt = uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(2)), + from: Bob.into(), to: AccountId::from_h256(H256::from_low_u64_be(1)), amount: 4, nonce: 1, @@ -952,13 +953,17 @@ mod tests { let pool = Pool::new(options, true.into(), TestApi::default().into()); - let xt = Extrinsic::IncludeData(Vec::new()); + // after validation `IncludeData` will have priority set to 9001 + // (validate_transaction mock) + let xt = ExtrinsicBuilder::new_include_data(Vec::new()).build(); block_on(pool.submit_one(&BlockId::Number(0), SOURCE, xt)).unwrap(); assert_eq!(pool.validated_pool().status().ready, 1); // then + // after validation `Transfer` will have priority set to 4 (validate_transaction + // mock) let xt = uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(2)), + from: Bob.into(), to: AccountId::from_h256(H256::from_low_u64_be(1)), amount: 4, nonce: 1, @@ -977,12 +982,16 @@ mod tests { let pool = Pool::new(options, true.into(), TestApi::default().into()); - let xt = Extrinsic::IncludeData(Vec::new()); + // after validation `IncludeData` will have priority set to 9001 + // (validate_transaction mock) + let xt = ExtrinsicBuilder::new_include_data(Vec::new()).build(); block_on(pool.submit_and_watch(&BlockId::Number(0), SOURCE, xt)).unwrap(); assert_eq!(pool.validated_pool().status().ready, 1); + // after validation `Transfer` will have priority set to 4 (validate_transaction + // mock) let xt = uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 0, @@ -992,7 +1001,9 @@ mod tests { assert_eq!(pool.validated_pool().status().ready, 2); // when - let xt = Extrinsic::Store(Vec::new()); + // after validation `Store` will have priority set to 9001 (validate_transaction + // mock) + let xt = ExtrinsicBuilder::new_indexed_call(Vec::new()).build(); block_on(pool.submit_one(&BlockId::Number(1), SOURCE, xt)).unwrap(); assert_eq!(pool.validated_pool().status().ready, 2); @@ -1014,7 +1025,7 @@ mod tests { // when let xt = uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 1, @@ -1030,7 +1041,7 @@ mod tests { // But now before the previous one is imported we import // the one that it depends on. let xt = uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 4, nonce: 0, diff --git a/client/transaction-pool/src/graph/ready.rs b/client/transaction-pool/src/graph/ready.rs index 9040c81b3c43..b4a5d9e3ba71 100644 --- a/client/transaction-pool/src/graph/ready.rs +++ b/client/transaction-pool/src/graph/ready.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/transaction-pool/src/graph/rotator.rs b/client/transaction-pool/src/graph/rotator.rs index 47e00a129215..61a26fb4138c 100644 --- a/client/transaction-pool/src/graph/rotator.rs +++ b/client/transaction-pool/src/graph/rotator.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/transaction-pool/src/graph/tracked_map.rs b/client/transaction-pool/src/graph/tracked_map.rs index 7292293258f5..47ad22603e46 100644 --- a/client/transaction-pool/src/graph/tracked_map.rs +++ b/client/transaction-pool/src/graph/tracked_map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/transaction-pool/src/graph/validated_pool.rs b/client/transaction-pool/src/graph/validated_pool.rs index bf8a059af2ff..ed76d439ae71 100644 --- a/client/transaction-pool/src/graph/validated_pool.rs +++ b/client/transaction-pool/src/graph/validated_pool.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/transaction-pool/src/graph/watcher.rs b/client/transaction-pool/src/graph/watcher.rs index df5bb94edfe6..fc440771d7bb 100644 --- a/client/transaction-pool/src/graph/watcher.rs +++ b/client/transaction-pool/src/graph/watcher.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/transaction-pool/src/lib.rs b/client/transaction-pool/src/lib.rs index 44bda82da38a..80e5925194c6 100644 --- a/client/transaction-pool/src/lib.rs +++ b/client/transaction-pool/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -396,9 +396,6 @@ where client.usage_info().chain.finalized_hash, )); - // make transaction pool available for off-chain runtime calls. - client.execution_extensions().register_transaction_pool(&pool); - pool } } @@ -421,7 +418,7 @@ where fn submit_local( &self, - at: &BlockId, + at: Block::Hash, xt: sc_transaction_pool_api::LocalTransactionFor, ) -> Result { use sp_runtime::{ @@ -430,7 +427,11 @@ where let validity = self .api - .validate_transaction_blocking(at, TransactionSource::Local, xt.clone())? + .validate_transaction_blocking( + &BlockId::hash(at), + TransactionSource::Local, + xt.clone(), + )? .map_err(|e| { Self::Error::Pool(match e { TransactionValidityError::Invalid(i) => TxPoolError::InvalidTransaction(i), @@ -441,7 +442,7 @@ where let (hash, bytes) = self.pool.validated_pool().api().hash_and_length(&xt); let block_number = self .api - .block_id_to_number(at)? + .block_id_to_number(&BlockId::hash(at))? .ok_or_else(|| error::Error::BlockIdConversion(format!("{:?}", at)))?; let validated = ValidatedTransaction::valid_at( diff --git a/client/transaction-pool/src/metrics.rs b/client/transaction-pool/src/metrics.rs index 8bcefe246bff..170bface9647 100644 --- a/client/transaction-pool/src/metrics.rs +++ b/client/transaction-pool/src/metrics.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/transaction-pool/src/revalidation.rs b/client/transaction-pool/src/revalidation.rs index 99602b50a226..b2c41be92eea 100644 --- a/client/transaction-pool/src/revalidation.rs +++ b/client/transaction-pool/src/revalidation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -362,6 +362,7 @@ mod tests { use sc_transaction_pool_api::TransactionSource; use sp_runtime::generic::BlockId; use substrate_test_runtime::{AccountId, Transfer, H256}; + use substrate_test_runtime_client::AccountKeyring::Alice; #[test] fn revalidation_queue_works() { @@ -370,7 +371,7 @@ mod tests { let queue = Arc::new(RevalidationQueue::new(api.clone(), pool.clone())); let uxt = uxt(Transfer { - from: AccountId::from_h256(H256::from_low_u64_be(1)), + from: Alice.into(), to: AccountId::from_h256(H256::from_low_u64_be(2)), amount: 5, nonce: 0, diff --git a/client/transaction-pool/src/tests.rs b/client/transaction-pool/src/tests.rs index d399367eae81..62911d5cbb47 100644 --- a/client/transaction-pool/src/tests.rs +++ b/client/transaction-pool/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2021 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -31,7 +31,10 @@ use sp_runtime::{ }, }; use std::{collections::HashSet, sync::Arc}; -use substrate_test_runtime::{Block, Extrinsic, Hashing, Transfer, H256}; +use substrate_test_runtime::{ + substrate_test_pallet::pallet::Call as PalletCall, BalancesCall, Block, Extrinsic, + ExtrinsicBuilder, Hashing, RuntimeCall, Transfer, TransferData, H256, +}; pub(crate) const INVALID_NONCE: u64 = 254; @@ -70,9 +73,11 @@ impl ChainApi for TestApi { let block_number = self.block_id_to_number(at).unwrap().unwrap(); let res = match uxt { - Extrinsic::Transfer { transfer, .. } => { - let nonce = transfer.nonce; - + Extrinsic { + function: RuntimeCall::Balances(BalancesCall::transfer_allow_death { .. }), + .. + } => { + let TransferData { nonce, .. } = (&uxt).try_into().unwrap(); // This is used to control the test flow. if nonce > 0 { let opt = self.delay.lock().take(); @@ -115,14 +120,20 @@ impl ChainApi for TestApi { Ok(transaction) } }, - Extrinsic::IncludeData(_) => Ok(ValidTransaction { + Extrinsic { + function: RuntimeCall::SubstrateTest(PalletCall::include_data { .. }), + .. + } => Ok(ValidTransaction { priority: 9001, requires: vec![], provides: vec![vec![42]], longevity: 9001, propagate: false, }), - Extrinsic::Store(_) => Ok(ValidTransaction { + Extrinsic { + function: RuntimeCall::SubstrateTest(PalletCall::indexed_call { .. }), + .. + } => Ok(ValidTransaction { priority: 9001, requires: vec![], provides: vec![vec![43]], @@ -185,8 +196,7 @@ impl ChainApi for TestApi { } pub(crate) fn uxt(transfer: Transfer) -> Extrinsic { - let signature = TryFrom::try_from(&[0; 64][..]).unwrap(); - Extrinsic::Transfer { transfer, signature, exhaust_resources_when_not_first: false } + ExtrinsicBuilder::new_transfer(transfer).build() } pub(crate) fn pool() -> Pool { diff --git a/client/transaction-pool/tests/pool.rs b/client/transaction-pool/tests/pool.rs index 511f6d9e24b5..4adf811b4252 100644 --- a/client/transaction-pool/tests/pool.rs +++ b/client/transaction-pool/tests/pool.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -35,11 +35,11 @@ use sp_consensus::BlockOrigin; use sp_runtime::{ generic::BlockId, traits::Block as _, - transaction_validity::{InvalidTransaction, TransactionSource, ValidTransaction}, + transaction_validity::{TransactionSource, ValidTransaction}, }; use std::{collections::BTreeSet, pin::Pin, sync::Arc}; use substrate_test_runtime_client::{ - runtime::{Block, Extrinsic, Hash, Header, Index, Transfer}, + runtime::{Block, Extrinsic, ExtrinsicBuilder, Hash, Header, Nonce, Transfer, TransferData}, AccountKeyring::*, ClientBlockImportExt, }; @@ -86,7 +86,11 @@ fn submission_should_work() { let pool = pool(); block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 209))).unwrap(); - let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); + let pending: Vec<_> = pool + .validated_pool() + .ready() + .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .collect(); assert_eq!(pending, vec![209]); } @@ -96,17 +100,26 @@ fn multiple_submission_should_work() { block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 209))).unwrap(); block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 210))).unwrap(); - let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); + let pending: Vec<_> = pool + .validated_pool() + .ready() + .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .collect(); assert_eq!(pending, vec![209, 210]); } #[test] fn early_nonce_should_be_culled() { + sp_tracing::try_init_simple(); let pool = pool(); block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 208))).unwrap(); - let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); - assert_eq!(pending, Vec::::new()); + let pending: Vec<_> = pool + .validated_pool() + .ready() + .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .collect(); + assert_eq!(pending, Vec::::new()); } #[test] @@ -114,11 +127,19 @@ fn late_nonce_should_be_queued() { let pool = pool(); block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 210))).unwrap(); - let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); - assert_eq!(pending, Vec::::new()); + let pending: Vec<_> = pool + .validated_pool() + .ready() + .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .collect(); + assert_eq!(pending, Vec::::new()); block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 209))).unwrap(); - let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); + let pending: Vec<_> = pool + .validated_pool() + .ready() + .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .collect(); assert_eq!(pending, vec![209, 210]); } @@ -128,14 +149,22 @@ fn prune_tags_should_work() { let hash209 = block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 209))).unwrap(); block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt(Alice, 210))).unwrap(); - let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); + let pending: Vec<_> = pool + .validated_pool() + .ready() + .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .collect(); assert_eq!(pending, vec![209, 210]); pool.validated_pool().api().push_block(1, Vec::new(), true); block_on(pool.prune_tags(&BlockId::number(1), vec![vec![209]], vec![hash209])) .expect("Prune tags"); - let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); + let pending: Vec<_> = pool + .validated_pool() + .ready() + .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .collect(); assert_eq!(pending, vec![210]); } @@ -148,8 +177,12 @@ fn should_ban_invalid_transactions() { block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt.clone())).unwrap_err(); // when - let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); - assert_eq!(pending, Vec::::new()); + let pending: Vec<_> = pool + .validated_pool() + .ready() + .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .collect(); + assert_eq!(pending, Vec::::new()); // then block_on(pool.submit_one(&BlockId::number(0), SOURCE, uxt.clone())).unwrap_err(); @@ -197,7 +230,11 @@ fn should_correctly_prune_transactions_providing_more_than_one_tag() { block_on(pool.submit_one(&BlockId::number(2), SOURCE, xt.clone())).expect("2. Imported"); assert_eq!(pool.validated_pool().status().ready, 1); assert_eq!(pool.validated_pool().status().future, 1); - let pending: Vec<_> = pool.validated_pool().ready().map(|a| a.data.transfer().nonce).collect(); + let pending: Vec<_> = pool + .validated_pool() + .ready() + .map(|a| TransferData::try_from(&a.data).unwrap().nonce) + .collect(); assert_eq!(pending, vec![211]); // prune it and make sure the pool is empty @@ -472,6 +509,7 @@ fn finalization() { #[test] fn fork_aware_finalization() { + sp_tracing::try_init_simple(); let api = TestApi::empty(); // starting block A1 (last finalized.) let a_header = api.push_block(1, vec![], true); @@ -888,48 +926,6 @@ fn ready_set_should_eventually_resolve_when_block_update_arrives() { } } -#[test] -fn should_not_accept_old_signatures() { - let client = Arc::new(substrate_test_runtime_client::new()); - let best_hash = client.info().best_hash; - let finalized_hash = client.info().finalized_hash; - let pool = Arc::new( - BasicPool::new_test( - Arc::new(FullChainApi::new(client, None, &sp_core::testing::TaskExecutor::new())), - best_hash, - finalized_hash, - ) - .0, - ); - - let transfer = Transfer { from: Alice.into(), to: Bob.into(), nonce: 0, amount: 1 }; - let _bytes: sp_core::sr25519::Signature = transfer.using_encoded(|e| Alice.sign(e)).into(); - - // generated with schnorrkel 0.1.1 from `_bytes` - let old_singature = sp_core::sr25519::Signature::try_from( - &array_bytes::hex2bytes( - "c427eb672e8c441c86d31f1a81b22b43102058e9ce237cabe9897ea5099ffd426\ - cd1c6a1f4f2869c3df57901d36bedcb295657adb3a4355add86ed234eb83108", - ) - .expect("hex invalid")[..], - ) - .expect("signature construction failed"); - - let xt = Extrinsic::Transfer { - transfer, - signature: old_singature, - exhaust_resources_when_not_first: false, - }; - - assert_matches::assert_matches!( - block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt.clone())), - Err(error::Error::Pool(sc_transaction_pool_api::error::Error::InvalidTransaction( - InvalidTransaction::BadProof - ))), - "Should be invalid transaction with bad proof", - ); -} - #[test] fn import_notification_to_pool_maintain_works() { let mut client = Arc::new(substrate_test_runtime_client::new()); @@ -975,7 +971,7 @@ fn import_notification_to_pool_maintain_works() { fn pruning_a_transaction_should_remove_it_from_best_transaction() { let (pool, api, _guard) = maintained_pool(); - let xt1 = Extrinsic::IncludeData(Vec::new()); + let xt1 = ExtrinsicBuilder::new_include_data(Vec::new()).build(); block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt1.clone())).expect("1. Imported"); assert_eq!(pool.status().ready, 1); @@ -1001,7 +997,7 @@ fn stale_transactions_are_pruned() { let (pool, api, _guard) = maintained_pool(); xts.into_iter().for_each(|xt| { - block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt.into_signed_tx())) + block_on(pool.submit_one(&BlockId::number(0), SOURCE, xt.into_unchecked_extrinsic())) .expect("1. Imported"); }); assert_eq!(pool.status().ready, 0); @@ -1010,9 +1006,12 @@ fn stale_transactions_are_pruned() { // Almost the same as our initial transactions, but with some different `amount`s to make them // generate a different hash let xts = vec![ - Transfer { from: Alice.into(), to: Bob.into(), nonce: 1, amount: 2 }.into_signed_tx(), - Transfer { from: Alice.into(), to: Bob.into(), nonce: 2, amount: 2 }.into_signed_tx(), - Transfer { from: Alice.into(), to: Bob.into(), nonce: 3, amount: 2 }.into_signed_tx(), + Transfer { from: Alice.into(), to: Bob.into(), nonce: 1, amount: 2 } + .into_unchecked_extrinsic(), + Transfer { from: Alice.into(), to: Bob.into(), nonce: 2, amount: 2 } + .into_unchecked_extrinsic(), + Transfer { from: Alice.into(), to: Bob.into(), nonce: 3, amount: 2 } + .into_unchecked_extrinsic(), ]; // Import block diff --git a/client/utils/Cargo.toml b/client/utils/Cargo.toml index e80588453597..40b742f37388 100644 --- a/client/utils/Cargo.toml +++ b/client/utils/Cargo.toml @@ -10,13 +10,14 @@ description = "I/O for Substrate runtimes" readme = "README.md" [dependencies] -backtrace = "0.3.67" +async-channel = "1.8.0" futures = "0.3.21" futures-timer = "3.0.2" lazy_static = "1.4.0" log = "0.4" parking_lot = "0.12.1" prometheus = { version = "0.13.0", default-features = false } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../primitives/arithmetic" } [features] default = ["metered"] diff --git a/client/utils/README.md b/client/utils/README.md index 2da70f09ccbc..d20fe69efc5a 100644 --- a/client/utils/README.md +++ b/client/utils/README.md @@ -1,16 +1,11 @@ -Utilities Primitives for Substrate +# Utilities Primitives for Substrate -## Features +This crate provides `mpsc::tracing_unbounded` function that returns wrapper types to +`async_channel::Sender` and `async_channel::Receiver`, which register every +`send`/`received`/`dropped` action happened on the channel. -### metered - -This feature changes the behaviour of the function `mpsc::tracing_unbounded`. -With the disabled feature this function is an alias to `futures::channel::mpsc::unbounded`. -However, when the feature is enabled it creates wrapper types to `UnboundedSender` -and `UnboundedReceiver` to register every `send`/`received`/`dropped` action happened on -the channel. - -Also this feature creates and registers a prometheus vector with name `unbounded_channel_len` and labels: +Also this wrapper creates and registers a prometheus vector with name `unbounded_channel_len` +and labels: | Label | Description | | ------------ | --------------------------------------------- | diff --git a/client/utils/src/id_sequence.rs b/client/utils/src/id_sequence.rs index 2ccb4fc3f32e..abb1271c72a0 100644 --- a/client/utils/src/id_sequence.rs +++ b/client/utils/src/id_sequence.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/utils/src/lib.rs b/client/utils/src/lib.rs index 0e47330335c2..017fc76207d2 100644 --- a/client/utils/src/lib.rs +++ b/client/utils/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -18,17 +18,11 @@ //! Utilities Primitives for Substrate //! -//! # Features +//! This crate provides `mpsc::tracing_unbounded` function that returns wrapper types to +//! `async_channel::Sender` and `async_channel::Receiver`, which register every +//! `send`/`received`/`dropped` action happened on the channel. //! -//! ## metered -//! -//! This feature changes the behaviour of the function `mpsc::tracing_unbounded`. -//! With the disabled feature this function is an alias to `futures::channel::mpsc::unbounded`. -//! However, when the feature is enabled it creates wrapper types to `UnboundedSender` -//! and `UnboundedReceiver` to register every `send`/`received`/`dropped` action happened on -//! the channel. -//! -//! Also this feature creates and registers a prometheus vector with name `unbounded_channel_len` +//! Also this wrapper creates and registers a prometheus vector with name `unbounded_channel_len` //! and labels: //! //! | Label | Description | diff --git a/client/utils/src/metrics.rs b/client/utils/src/metrics.rs index 457ef23e41ab..6bbdbe2e2e59 100644 --- a/client/utils/src/metrics.rs +++ b/client/utils/src/metrics.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -24,7 +24,6 @@ use prometheus::{ Error as PrometheusError, Registry, }; -#[cfg(feature = "metered")] use prometheus::{core::GenericCounterVec, Opts}; lazy_static! { @@ -36,7 +35,6 @@ lazy_static! { .expect("Creating of statics doesn't fail. qed"); } -#[cfg(feature = "metered")] lazy_static! { pub static ref UNBOUNDED_CHANNELS_COUNTER : GenericCounterVec = GenericCounterVec::new( Opts::new("substrate_unbounded_channel_len", "Items in each mpsc::unbounded instance"), @@ -49,8 +47,6 @@ lazy_static! { pub fn register_globals(registry: &Registry) -> Result<(), PrometheusError> { registry.register(Box::new(TOKIO_THREADS_ALIVE.clone()))?; registry.register(Box::new(TOKIO_THREADS_TOTAL.clone()))?; - - #[cfg(feature = "metered")] registry.register(Box::new(UNBOUNDED_CHANNELS_COUNTER.clone()))?; Ok(()) diff --git a/client/utils/src/mpsc.rs b/client/utils/src/mpsc.rs index d74703c4abd0..36e44be5e295 100644 --- a/client/utils/src/mpsc.rs +++ b/client/utils/src/mpsc.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,287 +16,190 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! Features to meter unbounded channels - -#[cfg(not(feature = "metered"))] -mod inner { - // just aliased, non performance implications - use futures::channel::mpsc::{self, UnboundedReceiver, UnboundedSender}; - pub type TracingUnboundedSender = UnboundedSender; - pub type TracingUnboundedReceiver = UnboundedReceiver; - - /// Alias `mpsc::unbounded` - pub fn tracing_unbounded( - _key: &'static str, - ) -> (TracingUnboundedSender, TracingUnboundedReceiver) { - mpsc::unbounded() - } +//! Code to meter unbounded channels. + +pub use async_channel::{TryRecvError, TrySendError}; + +use crate::metrics::UNBOUNDED_CHANNELS_COUNTER; +use async_channel::{Receiver, Sender}; +use futures::{ + stream::{FusedStream, Stream}, + task::{Context, Poll}, +}; +use log::error; +use sp_arithmetic::traits::SaturatedConversion; +use std::{ + backtrace::Backtrace, + pin::Pin, + sync::{ + atomic::{AtomicBool, Ordering}, + Arc, + }, +}; + +/// Wrapper Type around [`async_channel::Sender`] that increases the global +/// measure when a message is added. +#[derive(Debug)] +pub struct TracingUnboundedSender { + inner: Sender, + name: &'static str, + queue_size_warning: usize, + warning_fired: Arc, + creation_backtrace: Arc, } -#[cfg(feature = "metered")] -mod inner { - // tracing implementation - use crate::metrics::UNBOUNDED_CHANNELS_COUNTER; - use backtrace::Backtrace; - use futures::{ - channel::mpsc::{ - self, SendError, TryRecvError, TrySendError, UnboundedReceiver, UnboundedSender, - }, - sink::Sink, - stream::{FusedStream, Stream}, - task::{Context, Poll}, - }; - use log::error; - use std::{ - pin::Pin, - sync::{ - atomic::{AtomicBool, AtomicI64, Ordering}, - Arc, - }, - }; - - /// Wrapper Type around `UnboundedSender` that increases the global - /// measure when a message is added - #[derive(Debug)] - pub struct TracingUnboundedSender { - inner: UnboundedSender, - name: &'static str, - // To not bother with ordering and possible underflow errors of the unsigned counter - // we just use `i64` and `Ordering::Relaxed`, and perceive `queue_size` as approximate. - // It can turn < 0 though. - queue_size: Arc, - queue_size_warning: i64, - warning_fired: Arc, - creation_backtrace: Arc, - } - - // Strangely, deriving `Clone` requires that `T` is also `Clone`. - impl Clone for TracingUnboundedSender { - fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - name: self.name, - queue_size: self.queue_size.clone(), - queue_size_warning: self.queue_size_warning, - warning_fired: self.warning_fired.clone(), - creation_backtrace: self.creation_backtrace.clone(), - } +// Strangely, deriving `Clone` requires that `T` is also `Clone`. +impl Clone for TracingUnboundedSender { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + name: self.name, + queue_size_warning: self.queue_size_warning, + warning_fired: self.warning_fired.clone(), + creation_backtrace: self.creation_backtrace.clone(), } } +} - /// Wrapper Type around `UnboundedReceiver` that decreases the global - /// measure when a message is polled - #[derive(Debug)] - pub struct TracingUnboundedReceiver { - inner: UnboundedReceiver, - name: &'static str, - queue_size: Arc, - } - - /// Wrapper around `mpsc::unbounded` that tracks the in- and outflow via - /// `UNBOUNDED_CHANNELS_COUNTER` and warns if the message queue grows - /// above the warning threshold. - pub fn tracing_unbounded( - name: &'static str, - queue_size_warning: i64, - ) -> (TracingUnboundedSender, TracingUnboundedReceiver) { - let (s, r) = mpsc::unbounded(); - let queue_size = Arc::new(AtomicI64::new(0)); - let sender = TracingUnboundedSender { - inner: s, - name, - queue_size: queue_size.clone(), - queue_size_warning, - warning_fired: Arc::new(AtomicBool::new(false)), - creation_backtrace: Arc::new(Backtrace::new_unresolved()), - }; - let receiver = TracingUnboundedReceiver { inner: r, name, queue_size }; - (sender, receiver) - } - - impl TracingUnboundedSender { - /// Proxy function to mpsc::UnboundedSender - pub fn poll_ready(&self, ctx: &mut Context) -> Poll> { - self.inner.poll_ready(ctx) - } - - /// Proxy function to mpsc::UnboundedSender - pub fn is_closed(&self) -> bool { - self.inner.is_closed() - } - - /// Proxy function to mpsc::UnboundedSender - pub fn close_channel(&self) { - self.inner.close_channel() - } - - /// Proxy function to mpsc::UnboundedSender - pub fn disconnect(&mut self) { - self.inner.disconnect() - } - - pub fn start_send(&mut self, msg: T) -> Result<(), SendError> { - // The underlying implementation of [`UnboundedSender::start_send`] is the same as - // [`UnboundedSender::unbounded_send`], so we just reuse the message counting and - // error reporting code from `unbounded_send`. - self.unbounded_send(msg).map_err(TrySendError::into_send_error) - } - - /// Proxy function to mpsc::UnboundedSender - pub fn unbounded_send(&self, msg: T) -> Result<(), TrySendError> { - self.inner.unbounded_send(msg).map(|s| { - UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[self.name, "send"]).inc(); - - let queue_size = self.queue_size.fetch_add(1, Ordering::Relaxed); - if queue_size == self.queue_size_warning && - self.warning_fired - .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) - .is_ok() - { - // `warning_fired` and `queue_size` are not synchronized, so it's possible - // that the warning is fired few times before the `warning_fired` is seen - // by all threads. This seems better than introducing a mutex guarding them. - let mut backtrace = (*self.creation_backtrace).clone(); - backtrace.resolve(); - error!( - "The number of unprocessed messages in channel `{}` reached {}.\n\ - The channel was created at:\n{:?}", - self.name, self.queue_size_warning, backtrace, - ); - } +/// Wrapper Type around [`async_channel::Receiver`] that decreases the global +/// measure when a message is polled. +#[derive(Debug)] +pub struct TracingUnboundedReceiver { + inner: Receiver, + name: &'static str, +} - s - }) - } +/// Wrapper around [`async_channel::unbounded`] that tracks the in- and outflow via +/// `UNBOUNDED_CHANNELS_COUNTER` and warns if the message queue grows +/// above the warning threshold. +pub fn tracing_unbounded( + name: &'static str, + queue_size_warning: usize, +) -> (TracingUnboundedSender, TracingUnboundedReceiver) { + let (s, r) = async_channel::unbounded(); + let sender = TracingUnboundedSender { + inner: s, + name, + queue_size_warning, + warning_fired: Arc::new(AtomicBool::new(false)), + creation_backtrace: Arc::new(Backtrace::force_capture()), + }; + let receiver = TracingUnboundedReceiver { inner: r, name }; + (sender, receiver) +} - /// Proxy function to mpsc::UnboundedSender - pub fn same_receiver(&self, other: &UnboundedSender) -> bool { - self.inner.same_receiver(other) - } +impl TracingUnboundedSender { + /// Proxy function to [`async_channel::Sender`]. + pub fn is_closed(&self) -> bool { + self.inner.is_closed() } - impl TracingUnboundedReceiver { - fn consume(&mut self) { - // consume all items, make sure to reflect the updated count - let mut count = 0; - loop { - if self.inner.is_terminated() { - break - } + /// Proxy function to [`async_channel::Sender`]. + pub fn close(&self) -> bool { + self.inner.close() + } - match self.try_next() { - Ok(Some(..)) => count += 1, - _ => break, - } + /// Proxy function to `async_channel::Sender::try_send`. + pub fn unbounded_send(&self, msg: T) -> Result<(), TrySendError> { + self.inner.try_send(msg).map(|s| { + UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[self.name, "send"]).inc(); + + if self.inner.len() >= self.queue_size_warning && + self.warning_fired + .compare_exchange(false, true, Ordering::Relaxed, Ordering::Relaxed) + .is_ok() + { + error!( + "The number of unprocessed messages in channel `{}` exceeded {}.\n\ + The channel was created at:\n{}\n + Last message was sent from:\n{}", + self.name, + self.queue_size_warning, + self.creation_backtrace, + Backtrace::force_capture(), + ); } - // and discount the messages - if count > 0 { - UNBOUNDED_CHANNELS_COUNTER - .with_label_values(&[self.name, "dropped"]) - .inc_by(count); - } - } - /// Proxy function to mpsc::UnboundedReceiver - /// that consumes all messages first and updates the counter - pub fn close(&mut self) { - self.consume(); - self.inner.close() - } - - /// Proxy function to mpsc::UnboundedReceiver - /// that discounts the messages taken out - pub fn try_next(&mut self) -> Result, TryRecvError> { - self.inner.try_next().map(|s| { - if s.is_some() { - let _ = self.queue_size.fetch_sub(1, Ordering::Relaxed); - UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[self.name, "received"]).inc(); - } - s - }) - } + s + }) } +} - impl Drop for TracingUnboundedReceiver { - fn drop(&mut self) { - self.consume(); - } +impl TracingUnboundedReceiver { + /// Proxy function to [`async_channel::Receiver`]. + pub fn close(&mut self) -> bool { + self.inner.close() } - impl Unpin for TracingUnboundedReceiver {} - - impl Stream for TracingUnboundedReceiver { - type Item = T; - - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let s = self.get_mut(); - match Pin::new(&mut s.inner).poll_next(cx) { - Poll::Ready(msg) => { - if msg.is_some() { - let _ = s.queue_size.fetch_sub(1, Ordering::Relaxed); - UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[s.name, "received"]).inc(); - } - Poll::Ready(msg) - }, - Poll::Pending => Poll::Pending, - } - } + /// Proxy function to [`async_channel::Receiver`] + /// that discounts the messages taken out. + pub fn try_recv(&mut self) -> Result { + self.inner.try_recv().map(|s| { + UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[self.name, "received"]).inc(); + s + }) } +} - impl FusedStream for TracingUnboundedReceiver { - fn is_terminated(&self) -> bool { - self.inner.is_terminated() - } +impl Drop for TracingUnboundedReceiver { + fn drop(&mut self) { + // Close the channel to prevent any further messages to be sent into the channel + self.close(); + // the number of messages about to be dropped + let count = self.inner.len(); + // discount the messages + if count > 0 { + UNBOUNDED_CHANNELS_COUNTER + .with_label_values(&[self.name, "dropped"]) + .inc_by(count.saturated_into()); + } + // Drain all the pending messages in the channel since they can never be accessed, + // this can be removed once https://github.com/smol-rs/async-channel/issues/23 is + // resolved + while let Ok(_) = self.inner.try_recv() {} } +} - impl Sink for TracingUnboundedSender { - type Error = SendError; - - fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - TracingUnboundedSender::poll_ready(&*self, cx) - } - - fn start_send(mut self: Pin<&mut Self>, msg: T) -> Result<(), Self::Error> { - TracingUnboundedSender::start_send(&mut *self, msg) - } +impl Unpin for TracingUnboundedReceiver {} - fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } +impl Stream for TracingUnboundedReceiver { + type Item = T; - fn poll_close( - mut self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll> { - self.disconnect(); - Poll::Ready(Ok(())) + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let s = self.get_mut(); + match Pin::new(&mut s.inner).poll_next(cx) { + Poll::Ready(msg) => { + if msg.is_some() { + UNBOUNDED_CHANNELS_COUNTER.with_label_values(&[s.name, "received"]).inc(); + } + Poll::Ready(msg) + }, + Poll::Pending => Poll::Pending, } } +} - impl Sink for &TracingUnboundedSender { - type Error = SendError; +impl FusedStream for TracingUnboundedReceiver { + fn is_terminated(&self) -> bool { + self.inner.is_terminated() + } +} - fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - TracingUnboundedSender::poll_ready(*self, cx) - } +#[cfg(test)] +mod tests { + use super::tracing_unbounded; + use async_channel::{self, RecvError, TryRecvError}; - fn start_send(self: Pin<&mut Self>, msg: T) -> Result<(), Self::Error> { - self.unbounded_send(msg).map_err(TrySendError::into_send_error) - } + #[test] + fn test_tracing_unbounded_receiver_drop() { + let (tracing_unbounded_sender, tracing_unbounded_receiver) = + tracing_unbounded("test-receiver-drop", 10); + let (tx, rx) = async_channel::unbounded::(); - fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + tracing_unbounded_sender.unbounded_send(tx).unwrap(); + drop(tracing_unbounded_receiver); - fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { - // The difference with `TracingUnboundedSender` is intentional. The underlying - // implementation differs for `UnboundedSender` and `&UnboundedSender`: - // the latter closes the channel completely with `close_channel()`, while the former - // only closes this specific sender with `disconnect()`. - self.close_channel(); - Poll::Ready(Ok(())) - } + assert_eq!(rx.try_recv(), Err(TryRecvError::Closed)); + assert_eq!(rx.recv_blocking(), Err(RecvError)); } } - -pub use inner::{tracing_unbounded, TracingUnboundedReceiver, TracingUnboundedSender}; diff --git a/client/utils/src/notification.rs b/client/utils/src/notification.rs index 4917a43265df..dabb85d613cc 100644 --- a/client/utils/src/notification.rs +++ b/client/utils/src/notification.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -79,7 +79,7 @@ impl NotificationStream { } /// Subscribe to a channel through which the generic payload can be received. - pub fn subscribe(&self, queue_size_warning: i64) -> NotificationReceiver { + pub fn subscribe(&self, queue_size_warning: usize) -> NotificationReceiver { let receiver = self.hub.subscribe((), queue_size_warning); NotificationReceiver { receiver } } diff --git a/client/utils/src/notification/registry.rs b/client/utils/src/notification/registry.rs index ebe41452a567..6f0960ea918b 100644 --- a/client/utils/src/notification/registry.rs +++ b/client/utils/src/notification/registry.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/utils/src/notification/tests.rs b/client/utils/src/notification/tests.rs index f813f37d29dd..1afc2307e7b3 100644 --- a/client/utils/src/notification/tests.rs +++ b/client/utils/src/notification/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/utils/src/pubsub.rs b/client/utils/src/pubsub.rs index f85f44b49884..5293fa42ed94 100644 --- a/client/utils/src/pubsub.rs +++ b/client/utils/src/pubsub.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -164,7 +164,7 @@ impl Hub { /// Subscribe to this Hub using the `subs_key: K`. /// /// A subscription with a key `K` is possible if the Registry implements `Subscribe`. - pub fn subscribe(&self, subs_key: K, queue_size_warning: i64) -> Receiver + pub fn subscribe(&self, subs_key: K, queue_size_warning: usize) -> Receiver where R: Subscribe + Unsubscribe, { diff --git a/client/utils/src/pubsub/tests.rs b/client/utils/src/pubsub/tests.rs index 12945c0d886b..0d832c7dbd87 100644 --- a/client/utils/src/pubsub/tests.rs +++ b/client/utils/src/pubsub/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/utils/src/pubsub/tests/normal_operation.rs b/client/utils/src/pubsub/tests/normal_operation.rs index 830388de32e4..a3ea4f7ddee6 100644 --- a/client/utils/src/pubsub/tests/normal_operation.rs +++ b/client/utils/src/pubsub/tests/normal_operation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -37,9 +37,8 @@ fn positive_rx_receives_relevant_messages_and_terminates_upon_hub_drop() { // Hub is disposed. The rx_01 should be over after that. std::mem::drop(hub); - assert!(!rx_01.is_terminated()); - assert_eq!(None, rx_01.next().await); assert!(rx_01.is_terminated()); + assert_eq!(None, rx_01.next().await); }); } diff --git a/client/utils/src/pubsub/tests/panicking_registry.rs b/client/utils/src/pubsub/tests/panicking_registry.rs index cfe8168d8022..9f7579e79f47 100644 --- a/client/utils/src/pubsub/tests/panicking_registry.rs +++ b/client/utils/src/pubsub/tests/panicking_registry.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/client/utils/src/status_sinks.rs b/client/utils/src/status_sinks.rs index c536e2c18c6a..51d78aa49775 100644 --- a/client/utils/src/status_sinks.rs +++ b/client/utils/src/status_sinks.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/docker/README.md b/docker/README.md index ca3c1bde4e32..71ddb2dffd1b 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,22 +1,53 @@ # Substrate Builder Docker Image -The Docker image in this folder is a `builder` image. It is self contained and allow users to build the binaries themselves. +The Docker image in this folder is a `builder` image. It is self contained and allows users to build the binaries themselves. There is no requirement on having Rust or any other toolchain installed but a working Docker environment. Unlike the `parity/polkadot` image which contains a single binary (`polkadot`!) used by default, the image in this folder builds and contains several binaries and you need to provide the name of the binary to be called. -You should refer to the .Dockerfile for the actual list. At the time of editing, the list of included binaries is: +You should refer to the [.Dockerfile](./substrate_builder.Dockerfile) for the actual list. At the time of editing, the list of included binaries is: - substrate - subkey - node-template - chain-spec-builder +First, install [Docker](https://docs.docker.com/get-docker/). + +Then to generate the latest parity/substrate image. Please run: +```sh +./build.sh +``` + +> If you wish to create a debug build rather than a production build, then you may modify the [.Dockerfile](./substrate_builder.Dockerfile) replacing `cargo build --locked --release` with just `cargo build --locked` and replacing `target/release` with `target/debug`. + +> If you get an error that a tcp port address is already in use then find an available port to use for the host port in the [.Dockerfile](./substrate_builder.Dockerfile). + The image can be used by passing the selected binary followed by the appropriate tags for this binary. Your best guess to get started is to pass the `--help flag`. Here are a few examples: -- `docker run --rm -it parity/substrate substrate --version` -- `docker run --rm -it parity/substrate subkey --help` -- `docker run --rm -it parity/substrate node-template --version` -- `docker run --rm -it parity/substrate chain-spec-builder --help` +- `./run.sh substrate --version` +- `./run.sh subkey --help` +- `./run.sh node-template --version` +- `./run.sh chain-spec-builder --help` + +Then try running the following command to start a single node development chain using the Substrate Node Template binary `node-template`: + +```sh +./run.sh node-template --dev --ws-external +``` + +Note: It is recommended to provide a custom `--base-path` to store the chain database. For example: + +```sh +# Run Substrate Node Template without re-compiling +./run.sh node-template --dev --ws-external --base-path=/data +``` + +> To print logs follow the [Substrate debugging instructions](https://docs.substrate.io/test/debug/). + +```sh +# Purge the local dev chain +./run.sh node-template purge-chain --dev --base-path=/data -y +``` diff --git a/docker/build.sh b/docker/build.sh index f0a4560ff8fe..d4befd864e4e 100755 --- a/docker/build.sh +++ b/docker/build.sh @@ -3,9 +3,8 @@ set -e pushd . -# The following line ensure we run from the project root -PROJECT_ROOT=`git rev-parse --show-toplevel` -cd $PROJECT_ROOT +# Change to the project root and supports calls from symlinks +cd $(dirname "$(dirname "$(realpath "${BASH_SOURCE[0]}")")") # Find the current version from Cargo.toml VERSION=`grep "^version" ./bin/node/cli/Cargo.toml | egrep -o "([0-9\.]+)"` @@ -14,7 +13,7 @@ GITREPO=substrate # Build the image echo "Building ${GITUSER}/${GITREPO}:latest docker image, hang on!" -time docker build -f ./docker/substrate_builder.Dockerfile -t ${GITUSER}/${GITREPO}:latest . +time DOCKER_BUILDKIT=1 docker build -f ./docker/substrate_builder.Dockerfile -t ${GITUSER}/${GITREPO}:latest . docker tag ${GITUSER}/${GITREPO}:latest ${GITUSER}/${GITREPO}:v${VERSION} # Show the list of available images for this repo diff --git a/docker/run.sh b/docker/run.sh new file mode 100755 index 000000000000..43510bee07f5 --- /dev/null +++ b/docker/run.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +args=$@ + +# handle when arguments not provided. run arguments provided to script. +if [ "$args" = "" ] ; then + printf "Note: Please try providing an argument to the script.\n\n" + exit 1 +else + printf "*** Running Substrate Docker container with provided arguments: $args\n\n" + docker run --rm -it parity/substrate $args +fi + diff --git a/docker/substrate_builder.Dockerfile b/docker/substrate_builder.Dockerfile index d0812c1a80c4..03b6b46caf41 100644 --- a/docker/substrate_builder.Dockerfile +++ b/docker/substrate_builder.Dockerfile @@ -24,10 +24,10 @@ RUN useradd -m -u 1000 -U -s /bin/sh -d /substrate substrate && \ mkdir -p /data /substrate/.local/share/substrate && \ chown -R substrate:substrate /data && \ ln -s /data /substrate/.local/share/substrate && \ -# unclutter and minimize the attack surface - rm -rf /usr/bin /usr/sbin && \ # Sanity checks ldd /usr/local/bin/substrate && \ +# unclutter and minimize the attack surface + rm -rf /usr/bin /usr/sbin && \ /usr/local/bin/substrate --version USER substrate diff --git a/docker/substrate_builder.Dockerfile.dockerignore b/docker/substrate_builder.Dockerfile.dockerignore new file mode 100644 index 000000000000..dccfeb651ad8 --- /dev/null +++ b/docker/substrate_builder.Dockerfile.dockerignore @@ -0,0 +1,11 @@ +doc +**target* +.idea/ +.git/ +.github/ +Dockerfile +.dockerignore +.local +.env* +HEADER-GPL3 +LICENSE-GPL3 diff --git a/docs/CODEOWNERS b/docs/CODEOWNERS index 133ba7b094d4..63294d90e9d0 100644 --- a/docs/CODEOWNERS +++ b/docs/CODEOWNERS @@ -19,53 +19,56 @@ # - The latest matching rule, if multiple, takes precedence. # CI -/scripts/ci/ @paritytech/ci /.github/ @paritytech/ci /.gitlab-ci.yml @paritytech/ci - -# Sandboxing capability of Substrate Runtime -/primitives/sandbox/ @pepyakin @koute +/scripts/ci/ @paritytech/ci # WASM executor, low-level client <-> WASM interface and other WASM-related code -/client/executor/ @koute /client/allocator/ @koute -/primitives/wasm-interface/ @koute -/primitives/runtime-interface/ @koute +/client/executor/ @koute /primitives/panic-handler/ @koute +/primitives/runtime-interface/ @koute +/primitives/wasm-interface/ @koute /utils/wasm-builder/ @koute # Systems-related bits and bobs on the client side /client/sysinfo/ @koute /client/tracing/ @koute +# Documentation audit +/primitives/runtime @paritytech/docs-audit +/primitives/arithmetic @paritytech/docs-audit +# /primitives/core (to be added later) +# /primitives/io (to be added later) + +# FRAME +/frame/ @paritytech/frame-coders @paritytech/docs-audit +/frame/nfts/ @jsidorenko @paritytech/docs-audit +/frame/state-trie-migration/ @paritytech/frame-coders @cheme +/frame/uniques/ @jsidorenko @paritytech/docs-audit + # GRANDPA, BABE, consensus stuff -/frame/babe/ @andresilva -/frame/grandpa/ @andresilva -/client/finality-grandpa/ @andresilva /client/consensus/babe/ @andresilva -/client/consensus/slots/ @andresilva +/client/consensus/grandpa/ @andresilva /client/consensus/pow/ @sorpaas +/client/consensus/slots/ @andresilva +/frame/babe/ @andresilva +/frame/grandpa/ @andresilva /primitives/consensus/pow/ @sorpaas # BEEFY, MMR -/client/beefy/ @acatangiu /frame/beefy/ @acatangiu /frame/beefy-mmr/ @acatangiu /frame/merkle-mountain-range/ @acatangiu -/primitives/beefy/ @acatangiu /primitives/merkle-mountain-range/ @acatangiu # Contracts -/frame/contracts/ @athei +/frame/contracts/ @athei @paritytech/docs-audit # NPoS and election -/frame/staking/ @kianenigma -/frame/elections/ @kianenigma -/frame/elections-phragmen/ @kianenigma -/primitives/npos-elections/ @kianenigma - -# Fixed point arithmetic -/primitives/sp-arithmetic/ @kianenigma - -# Transaction weight stuff -/frame/support/src/weights.rs @shawntabrizi +/frame/election-provider-multi-phase/ @paritytech/staking-core @paritytech/docs-audit +/frame/election-provider-support/ @paritytech/staking-core @paritytech/docs-audit +/frame/elections-phragmen/ @paritytech/staking-core @paritytech/docs-audit +/frame/nomination-pools/ @paritytech/staking-core @paritytech/docs-audit +/frame/staking/ @paritytech/staking-core @paritytech/docs-audit +/primitives/npos-elections/ @paritytech/staking-core @paritytech/docs-audit diff --git a/docs/CONTRIBUTING.adoc b/docs/CONTRIBUTING.adoc index d39b8a6d729f..3e2fe117d324 100644 --- a/docs/CONTRIBUTING.adoc +++ b/docs/CONTRIBUTING.adoc @@ -15,6 +15,7 @@ There are a few basic ground-rules for contributors (including the maintainer(s) . **All modifications** must be made in a **pull-request** to solicit feedback from other contributors. . A pull-request *must not be merged until CI* has finished successfully. . Contributors should adhere to the link:STYLE_GUIDE.md[house coding style]. +. Contributors should adhere to the link:DOCUMENTATION_GUIDELINES.md[house documenting style], when applicable. == Merge Process @@ -30,30 +31,34 @@ A Pull Request (PR) needs to be reviewed and approved by project maintainers unl - `A-*` Pull request status. ONE REQUIRED. - `B-*` Changelog and/or Runtime-upgrade post composition markers. ONE REQUIRED. (used by automation) -- `C-*` Release notes release-priority markers. EXACTLY ONE REQUIRED. (used by automation) -- `D-*` More general tags on the PR denoting various implications and requirements. +- `C-*` Release notes release-criticality markers. EXACTLY ONE REQUIRED. (used by automation) +- `D-*` Audit tags denoting auditing requirements on the PR. *Process:* . Please tag each PR with exactly one `A`, `B`, `C` and `D` label at the minimum. -. Once a PR is ready for review please add the https://github.com/paritytech/substrate/pulls?q=is%3Apr+is%3Aopen+label%3AA0-pleasereview[`A0-pleasereview`] label. Generally PRs should sit with this label for 48 hours in order to garner feedback. It may be merged before if all relevant parties had a look at it. -. If the first review is not an approval, swap `A0-pleasereview` to any label `[A3, A7]` to indicate that the PR has received some feedback, but needs further work. For example. https://github.com/paritytech/substrate/labels/A3-inprogress[`A3-inprogress`] is a general indicator that the PR is work in progress and https://github.com/paritytech/substrate/labels/A4-gotissues[`A4-gotissues`] means that it has significant problems that need fixing. Once the work is done, change the label back to `A0-pleasereview`. You might end up swapping a few times back and forth to climb up the A label group. Once a PR is https://github.com/paritytech/substrate/labels/A8-mergeoncegreen[`A8-mergeoncegreen`], it is ready to merge. -. PRs must be tagged with their release notes requirements via the `B1-B9` labels. -. PRs must be tagged with their release importance via the `C1-C9` labels. +. When tagging a PR, it should be done while keeping all downstream users in mind. Downstream users are not just Polkadot or system parachains, but also all the other parachains and solo chains that are using Substrate. The labels are used by downstream users to track changes and to include these changes properly into their own releases. +. Once a PR is ready for review please add the https://github.com/paritytech/substrate/pulls?q=is%3Apr+is%3Aopen+label%3AA0-please_review+[`A0-please_review`] label. Generally PRs should sit with this label for 48 hours in order to garner feedback. It may be merged before if all relevant parties had a look at it. +. If the first review is not an approval, swap `A0-please_review` to any label `[A3, A5]` to indicate that the PR has received some feedback, but needs further work. For example. https://github.com/paritytech/substrate/labels/A3-in_progress[`A3-in_progress`] is a general indicator that the PR is work in progress. +. PRs must be tagged with `B*` labels to signal if a change is note worthy for downstream users. The respective `T*` labels should be added to signal the component that was changed. `B0-silent` must only be used for changes that don't require any attention by downstream users. +. PRs must be tagged with their release importance via the `C1-C7` labels. The release importance is only informing about how important it is to apply a release that contains the change. . PRs must be tagged with their audit requirements via the `D1-D9` labels. -. PRs that must be backported to a stable branch must be tagged with https://github.com/paritytech/substrate/labels/E1-runtimemigration[`E0-patchthis`]. -. PRs that introduce runtime migrations must be tagged with https://github.com/paritytech/substrate/labels/E1-runtimemigration[`E1-runtimemigration`]. See the https://github.com/paritytech/substrate/blob/master/utils/frame/try-runtime/cli/src/lib.rs#L18[Migration Best Practices here] for more info about how to test runtime migrations. -. PRs that introduce irreversible database migrations must be tagged with https://github.com/paritytech/substrate/labels/E2-databasemigration[`E2-databasemigration`]. -. PRs that add host functions must be tagged with with https://github.com/paritytech/substrate/labels/E4-newhostfunctions[`E4-newhostfunctions`]. -. PRs that break the external API must be tagged with https://github.com/paritytech/substrate/labels/E5-breaksapi[`E5-breaksapi`]. -. PRs that materially change the FRAME/runtime semantics must be tagged with https://github.com/paritytech/substrate/labels/E6-transactionversion[`E6-transactionversion`]. -. PRs that change the mechanism for block authoring in a backwards-incompatible way must be tagged with https://github.com/paritytech/substrate/labels/E7-breaksauthoring[`E7-breaksauthoring`]. -. PRs that "break everything" must be tagged with https://github.com/paritytech/substrate/labels/E8-breakseverything[`E8-breakseverything`]. -. PRs that block a new release must be tagged with https://github.com/paritytech/substrate/labels/E9-blocker%20%E2%9B%94%EF%B8%8F[`E9-blocker`]. +. PRs that introduce runtime migrations must be tagged with https://github.com/paritytech/substrate/labels/E0-runtime_migration[`E0-runtime_migration`]. See the https://github.com/paritytech/substrate/blob/master/utils/frame/try-runtime/cli/src/lib.rs#L18[Migration Best Practices here] for more info about how to test runtime migrations. +. PRs that introduce irreversible database migrations must be tagged with https://github.com/paritytech/substrate/labels/E1-database_migration[`E1-database_migration`]. +. PRs that add host functions must be tagged with with https://github.com/paritytech/substrate/labels/E3-host_functions[`E3-host_functions`]. +. PRs that break the external API must be tagged with https://github.com/paritytech/substrate/labels/F3-breaks_API[`F3-breaks_API`]. +. PRs that change the mechanism for block authoring in a backwards-incompatible way must be tagged with https://github.com/paritytech/substrate/labels/F1-breaks_authoring[`F1-breaks_authoring`]. +. PRs that "break everything" must be tagged with https://github.com/paritytech/substrate/labels/F0-breaks_everything[`F0-breaks_everything`]. . PRs should be categorized into projects. . No PR should be merged until all reviews' comments are addressed and CI is successful. -*Reviewing pull requests*: +*Noting relevant changes:* + +When breaking APIs, it should be mentioned on what was changed in the PR description alongside some examples on how to change the code to make it work/compile. + +The PR description should also mention potential storage migrations and if they require some special setup aside adding it to the list of migrations in the runtime. + +*Reviewing pull requests:* When reviewing a pull request, the end-goal is to suggest useful changes to the author. Reviews should finish with approval unless there are issues that would result in: @@ -76,36 +81,35 @@ When reviewing a pull request, the end-goal is to suggest useful changes to the To create a Polkadot companion PR: . Pull latest Polkadot master (or clone it, if you haven't yet). -. Override substrate deps to point to your local path or branch using https://github.com/bkchr/diener. (E.g. from the polkadot clone dir run `diener patch --crates-to-patch ../substrate --substrate` assuming substrate clone is in a sibling dir. If you do use diener, ensure that you _do not_ commit the changes diener makes to the Cargo.tomls.) -. Make the changes required and build polkadot locally. +. Override substrate deps to point to your local path or branch using https://github.com/bkchr/diener. (E.g. from the Polkadot clone dir run `diener patch --crates-to-patch ../substrate --substrate` assuming substrate clone is in a sibling dir. If you do use diener, ensure that you _do not_ commit the changes diener makes to the Cargo.tomls.) +. Make the changes required and build Polkadot locally. . Submit all this as a PR against the Polkadot Repo. -. In the _description_ of your _Substrate_ PR add "polkadot companion: [Polkadot_PR_URL]" -. Now you should see that the `check_polkadot` CI job will build your Substrate PR agains the mentioned Polkadot branch in your PR description. -. Someone will need to approve the Polkadot PR before the Substrate CI will go green. (The Polkadot CI failing can be ignored as long as the polkadot job in the _substrate_ PR is green). +. In the _description_ of your _Substrate_ PR add "Polkadot companion: [Polkadot_PR_URL]" +. Now you should see that the `check_polkadot` CI job will build your Substrate PR against the mentioned Polkadot branch in your PR description. +. Someone will need to approve the Polkadot PR before the Substrate CI will go green. (The Polkadot CI failing can be ignored as long as the Polkadot job in the _substrate_ PR is green). . Wait for reviews on both the Substrate and the Polkadot PRs. -. Once the Substrate PR runs green, a member of the `parity` github group can comment on the Substrate PR with `bot merge` which will: +. Once the Substrate PR runs green, a member of the `parity` Github group can comment on the Substrate PR with `bot merge` which will: - Merge the Substrate PR. - - The bot will push a commit to the Polkadot PR updating its Substrate reference. (effecively doing `cargo update -p sp-io`) - - If the polkadot PR origins from a fork then a project member may need to press `approve run` on the polkadot PR. + - The bot will push a commit to the Polkadot PR updating its Substrate reference. (effectively doing `cargo update -p sp-io`) + - If the Polkadot PR origins from a fork then a project member may need to press `approve run` on the Polkadot PR. - The bot will merge the Polkadot PR once all its CI `{"build_allow_failure":false}` checks are green. Note: The merge-bot currently doesn't work with forks on org accounts, only individual accounts. - (Hint: it's recommended to use `bot merge` to merge all substrate PRs, not just ones with a polkadot companion.) + (Hint: it's recommended to use `bot merge` to merge all substrate PRs, not just ones with a Polkadot companion.) -If your PR is reviewed well, but a Polkadot PR is missing, signal it with https://github.com/paritytech/substrate/labels/A7-needspolkadotpr[`A7-needspolkadotpr`] to prevent it from getting automatically merged. +If your PR is reviewed well, but a Polkadot PR is missing, signal it with https://github.com/paritytech/substrate/labels/E6-needs_polkadot_pr[`E6-needs_polkadot_pr`] to prevent it from getting automatically merged. In most cases the CI will add this label automatically. As there might be multiple pending PRs that might conflict with one another, a) you should not merge the substrate PR until the Polkadot PR has also been reviewed and b) both should be merged pretty quickly after another to not block others. == Helping out -We use https://github.com/paritytech/substrate/labels[labels] to manage PRs and issues and communicate state of a PR. Please familiarize yourself with them. Furthermore we are organizing issues in https://github.com/paritytech/substrate/milestones[milestones]. Best way to get started is to a pick a ticket from the current milestone tagged https://github.com/paritytech/substrate/issues?q=is%3Aissue+is%3Aopen+label%3AQ2-easy[`easy`] or https://github.com/paritytech/substrate/issues?q=is%3Aissue+is%3Aopen+label%3AQ3-medium[`medium`] and get going or https://github.com/paritytech/substrate/issues?q=is%3Aissue+is%3Aopen+label%3AX1-mentor[`mentor`] and get in contact with the mentor offering their support on that larger task. +We use https://paritytech.github.io/labels/doc_substrate.html[labels] to manage PRs and issues and communicate state of a PR. Please familiarize yourself with them. The best way to get started is to a pick a ticket tagged https://github.com/paritytech/substrate/issues?q=is%3Aissue+is%3Aopen+label%3AZ1-easy[`easy`] or https://github.com/paritytech/substrate/issues?q=is%3Aissue+is%3Aopen+label%3AZ2-medium[`medium`] and get going or https://github.com/paritytech/substrate/issues?q=is%3Aissue+is%3Aopen+label%3AZ6-mentor[`mentor`] and get in contact with the mentor offering their support on that larger task. == Issues Please label issues with the following labels: -. `I-*` Issue severity and type. EXACTLY ONE REQUIRED. -. `P-*` Issue priority. AT MOST ONE ALLOWED. -. `Q-*` Issue difficulty. AT MOST ONE ALLOWED. -. `Z-*` More general tags on the issue, denoting context and resolution. +. `I-*` or `J-*` Issue severity and type. EXACTLY ONE REQUIRED. +. `U-*` Issue urgency, suggesting in what time manner does this issue need to be resolved. AT MOST ONE ALLOWED. +. `Z-*` Issue difficulty. AT MOST ONE ALLOWED. == Releases @@ -116,7 +120,7 @@ Declaring formal releases remains the prerogative of the project maintainer(s). UI tests are used for macros to ensure that the output of a macro doesn't change and is in the expected format. These UI tests are sensible to any changes in the macro generated code or to switching the rust stable version. The tests are only run when the `RUN_UI_TESTS` environment variable is set. So, when the CI is for example complaining about failing UI tests and it is expected that they fail these tests need to be executed locally. To simplify the updating -of the UI test ouput there is the `.maintain/update-rust-stable.sh` script. This can be run with `.maintain/update-rust-stable.sh CURRENT_STABLE_VERSION` +of the UI test output there is the `.maintain/update-rust-stable.sh` script. This can be run with `.maintain/update-rust-stable.sh CURRENT_STABLE_VERSION` and then it will run all UI tests to update the expected output. == Changes to this arrangement diff --git a/docs/DOCUMENTATION_GUIDELINES.md b/docs/DOCUMENTATION_GUIDELINES.md new file mode 100644 index 000000000000..93b8672b8a4b --- /dev/null +++ b/docs/DOCUMENTATION_GUIDELINES.md @@ -0,0 +1,264 @@ +# Substrate Documentation Guidelines + +This document is only focused on documenting parts of substrate that relates to its external API. The list of such crates can be found in [CODEOWNERS](./CODEOWNERS). Search for the crates that are auto-assigned to a team called `docs-audit`. + +These are crates that are often used by external developers and need more thorough documentation. These are the crates most concerned with FRAME development. + +- [Substrate Documentation Guidelines](#substrate-documentation-guidelines) + - [General/Non-Pallet Crates](#generalnon-pallet-crates) + - [What to Document?](#what-to-document) + - [Rust Docs vs. Code Comments](#rust-docs-vs-code-comments) + - [How to Document?](#how-to-document) + - [TLDR](#tldr) + - [Proc-Macros](#proc-macros) + - [Other Guidelines](#other-guidelines) + - [Document Through Code](#document-through-code) + - [Formatting Matters](#formatting-matters) + - [Pallet Crates](#pallet-crates) + - [Top Level Pallet Docs (`lib.rs`)](#top-level-pallet-docs-librs) + - [Polkadot and Substrate](#polkadot-and-substrate) + - [Dispatchables](#dispatchables) + - [Storage Items](#storage-items) + - [Errors and Events](#errors-and-events) + + +## General/Non-Pallet Crates + +First, consider the case for all such crates, except for those that are pallets. + +### What to Document? + +The first question is, what should you document? Use the following filter: + +1. In the crates assigned to `docs-audit` in [CODEOWNERS](./CODEOWNERS), +2. All `pub` item need to be documented. If it is not `pub`, it does not appear in the rust-docs, and is not public facing. + * Within `pub` items, sometimes they are only `pub` in order to be used by another internal crate, and you can foresee that this will not be used by anyone else other than you. These need **not** be documented thoroughly, and are left to your discretion to identify. + * Reminder: `trait` items are public by definition, if the trait is public. +3. All public modules (`mod`) should have reasonable module-level documentation (`//!`). + + +#### Rust Docs vs. Code Comments + +Note that anything starting with `///` is an external rust-doc, and everything starting with `//` does not appear in the rust-docs. It's important to not confuse the two in your documentation. + +```rust +/// Computes the square root of the input, returning `Ok(_)` if successful. +/// +/// # Errors +/// ... +/// +// Details about the complexity, how you implemented this, and some quirks that +// are NOT relevant to the external interface, so it starts with '//'. +// This can also be moved inside the function. +pub fn sqrt(x: u32) -> Result { + todo!(); +} +``` + +### How to Document? + +There are a few very good sources that you can look into: + +- https://doc.rust-lang.org/rustdoc/how-to-write-documentation.html +- https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/first-edition/documentation.html +- https://blog.guillaume-gomez.fr/articles/2020-03-12+Guide+on+how+to+write+documentation+for+a+Rust+crate + +As mentioned [here](https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/first-edition/documentation.html#writing-documentation-comments) and [here](https://blog.guillaume-gomez.fr/articles/2020-03-12+Guide+on+how+to+write+documentation+for+a+Rust+crate), always start with a **single sentence** demonstrating what is being documented. All additional documentation should be added *after a newline*. Strive to make the first sentence succinct and short. The reason for this is the first paragraph of docs about an item (everything before the first newline) is used as the excerpt that rust doc displays about this item when it appears in tables, such as the table listing all functions in a module. If this excerpt is too long, the module docs will be very difficult to read. + +About [special sections](https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/first-edition/documentation.html#special-sections), we will most likely not need to think about panic and safety in any runtime related code. Our code is never `unsafe`, and will (almost) never panic. + +Use `# Examples as much as possible. These are great ways to further demonstrate what your APIs are doing, and add free test coverage. As an additional benefit, any code in rust-docs is treated as an "integration tests", not unit tests, which tests your crate in a different way than unit tests. So, it is both a win for "more documentation" and a win for "more test coverage". + +You can also consider having an `# Error` section optionally. Of course, this only applies if there is a `Result` being returned, and if the `Error` variants are overly complicated. + +Strive to include correct links to other items in your written docs as much as possible. In other words, avoid \`some_func\` and instead use \[\`some_fund\`\]. Read more about how to correctly use links in your rust-docs [here](https://doc.rust-lang.org/rustdoc/write-documentation/linking-to-items-by-name.html#valid-links) and [here](https://rust-lang.github.io/rfcs/1946-intra-rustdoc-links.html#additions-to-the-documentation-syntax). +Strive to include correct links to other items in your written docs as much as possible. In other words, avoid `` `some_func` `` and instead use ``[`some_func`]``. + + +> While you are linking, you might become conscious of the fact that you are in need of linking to (too many) foreign items in order to explain your API. This is leaning more towards API-Design rather than documentation, but it is a warning that the subject API might be slightly wrong. For example, most "glue" traits[^1] in `frame/support` should be designed and documented without making hard assumptions about particular pallets that implement them. + +#### TLDR + +0. Have the goal of enforcing `#![deny(missing_docs)]` mentally, even if it is not enforced by the compiler 🙈. +1. Start with a single, clear and concise sentence. Follow up with more context, after a newline, if needed. +2. Use examples as much as reasonably possible. +3. Use links as much as possible. +4. Think about context. If you are explaining a lot of foreign topics while documenting a trait that should not explicitly depend on them, you have likely not designed it properly. + +#### Proc-Macros + +Note that there are special considerations when documenting proc macros. Doc links will appear to function _within_ your proc macro crate, but often will no longer function when these proc macros are re-exported elsewhere in your project. The exception is doc links to _other proc macros_ which will function just fine if they are also being re-exported. It is also often necessary to disambiguate between a proc macro and a function of the same name, which can be done using the `macro@my_macro_name` syntax in your link. Read more about how to correctly use links in your rust-docs [here](https://doc.rust-lang.org/rustdoc/write-documentation/linking-to-items-by-name.html#valid-links) and [here](https://rust-lang.github.io/rfcs/1946-intra-rustdoc-links.html#additions-to-the-documentation-syntax). + + +### Other Guidelines + +The above five guidelines must always be reasonably respected in the documentation. + +The following are a set of notes that may not necessarily hold in all circumstances: + + +#### Document Through Code + +You should make sure that your code is properly-named and well-organized so that your code functions as a form of documentation. However, within the complexity of our projects in Polkadot/Substrate that is not enough. Particularly, things like examples, errors and panics cannot be documented only through properly-named and well-organized code. + +> Our north star is self-documenting code that also happens to be well-documented and littered with examples. + + +* Your written documents should *complement* the code, not *repeat* it. As an example, a documentation on top of a code example should never look like the following: + + ```rust + /// Sends request and handles the response. + trait SendRequestAndHandleResponse { + + } + ``` + +In the above example, the documentation has added no useful information not already contained within the properly-named trait and is redundant. + + +#### Formatting Matters + +The way you format your documents (newlines, heading and so on) makes a difference. Consider the below examples: + +```rust +/// This function works with input u32 x and multiplies it by two. If +/// we optimize the other variant of it, we would be able to achieve more +/// efficiency but I have to think about it. Probably can panic if the input +/// overflows u32. +fn multiply_by_2(x: u32) -> u32 { .. } +``` + +```rust +/// Multiplies an input of type [`u32`] by two. +/// +/// # Panics +/// +/// Panics if the input overflows. +/// +/// # Complexity +/// +/// Is implemented using some algorithm that yields complexity of O(1). +// More efficiency can be achieved if we improve this via such and such. +fn multiply_by_2(x: u32) -> u32 { .. } +``` + +They are both roughly conveying the same set of facts, but one is easier to follow because it was formatted cleanly. Especially for traits and types that you can foresee will be seen and used a lot, try and write a well formatted version. + +Similarly, make sure your comments are wrapped at 100 characters line-width (as defined by our [`rustfmt.toml`](../rustfmt.toml)), no **more and no less**! The more is fixed by `rustfmt` and our CI, but if you (for some unknown reason) wrap your lines at 59 characters, it will pass the CI, and it will not look good 🫣. Consider using a plugin like [rewrap](https://marketplace.visualstudio.com/items?itemName=stkb.rewrap) (for Visual Studio Code) to properly do this. + +[^1]: Those that help two pallets talk to each other. + +## Pallet Crates + +The guidelines so far have been general in nature, and are applicable to crates that are pallets and crates that're not pallets. + +The following is relevant to how to document parts of a crate that is a pallet. See [`pallet-fast-unstake`](../frame/fast-unstake/src/lib.rs) as one examples of adhering these guidelines. + +### Top Level Pallet Docs (`lib.rs`) + +For the top-level pallet docs, consider the following template: + +``` +//! # +//! +//! . +//! +//! ## Overview +//! +//! +//! +//! +//! +//! +//! +//! ### Example +//! +//! . +//! +//! ## Pallet API +//! +//! +//! +//! See the [`pallet`] module for more information about the interfaces this pallet exposes, including its configuration trait, dispatchables, storage items, events and errors. +//! +//! +//! +//! This section can most often be left as-is. +//! +//! ## Low Level / Implementation Details +//! +//! +//! +//! +//! +//! ### Design Goals (optional) +//! +//! +//! +//! ### Design (optional) +//! +//! +//! +//! ### Terminology (optional) +//! +//! +``` + +This template's details (heading 3s and beyond) are left flexible, and at the discretion of the developer to make the best final choice about. For example, you might want to include `### Terminology` or not. Moreover, you might find it more useful to include it in `## Overview`. + +Nonetheless, the high level flow of going from the most high level explanation to the most low level explanation is important to follow. + +As a rule of thumb, the Heading 2s (`##`) in this template can be considered a strict rule, while the Heading 3s (`###`) and beyond are flexible. + +#### Polkadot and Substrate + +Optionally, in order to demonstrate the relation between the two, you can start the pallet documentation with: + +``` +//! > Made with *Substrate*, for *Polkadot*. +//! +//! [![github]](https://github.com/paritytech/substrate/frame/fast-unstake) - +//! [![polkadot]](https://polkadot.network) +//! +//! [polkadot]: https://img.shields.io/badge/polkadot-E6007A?style=for-the-badge&logo=polkadot&logoColor=white +//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github +``` + +### Dispatchables + +For each dispatchable (`fn` item inside `#[pallet::call]`), consider the following template: + +``` +/// +/// +/// ## Dispatch Origin +/// +/// The dispatch origin of this call must be

+/// +/// ## Details +/// +/// +/// +/// ## Errors (optional) +/// +/// +/// +/// ## Events (optional) +/// +/// +pub fn name_of_dispatchable(origin: OriginFor, ...) -> DispatchResult {} +``` + +Consider the fact that these docs will be part of the metadata of the associated dispatchable, and might be used by wallets and explorers. + +### Storage Items + +1. If a map-like type is being used, always note the choice of your hashers as private code docs (`// Hasher X chosen because ...`). Recall that this is not relevant information to external people, so it must be documented as `//`. +2. Consider explaining the crypto-economics of how a deposit is being taken in return of the storage being used. +3. Consider explaining why it is safe for the storage item to be unbounded, if `#[pallet::unbounded]` or `#[pallet::without_storage_info]` is being used. + +### Errors and Events + +Consider the fact that, similar to dispatchables, these docs will be part of the metadata of the associated event/error, and might be used by wallets and explorers. + +Specifically for `error`, explain why the error has happened, and what can be done in order to avoid it. diff --git a/docs/PULL_REQUEST_TEMPLATE.md b/docs/PULL_REQUEST_TEMPLATE.md index 12f39371892e..c1730820c326 100644 --- a/docs/PULL_REQUEST_TEMPLATE.md +++ b/docs/PULL_REQUEST_TEMPLATE.md @@ -2,35 +2,34 @@ ✄ ----------------------------------------------------------------------------- -Thank you for your Pull Request! 🙏 - -Before you submit, please check that: - -- [ ] **Description:** You added a brief description of the PR, e.g.: - - What does it do? - - What important points should reviewers know? - - Is there something left for follow-up PRs? -- [ ] **Labels:** You labeled the PR appropriately if you have permissions to do so: - - [ ] `A*` for PR status (**one required**) - - [ ] `B*` for changelog (**one required**) - - [ ] `C*` for release notes (**exactly one required**) - - [ ] `D*` for various implications/requirements - - [ ] Github project assignment -- [ ] **Related Issues:** You mentioned a related issue if this PR is related to it, e.g. `Fixes #228` or `Related #1337`. -- [ ] **2 Reviewers:** You asked at least two reviewers to review. If you aren't sure, start with GH suggestions. -- [ ] **Style Guide:** Your PR adheres to [the style guide](https://github.com/paritytech/substrate/blob/master/docs/STYLE_GUIDE.md) - - In particular, mind the maximal line length of 100 (120 in exceptional circumstances). - - There is no commented code checked in unless necessary. - - Any panickers in the runtime have a proof or were removed. -- [ ] **Runtime Version:** You bumped the runtime version if there are breaking changes in the **runtime**. -- [ ] **Docs:** You updated any rustdocs which may need to change. -- [ ] **Polkadot Companion:** Has the PR altered the external API or interfaces used by Polkadot? - - [ ] If so, do you have the corresponding Polkadot PR ready? - - [ ] Optionally: Do you have a corresponding Cumulus PR? - -Refer to [the contributing guide](https://github.com/paritytech/substrate/blob/master/docs/CONTRIBUTING.adoc) for details. - -After you've read this notice feel free to remove it. -Thank you! +Thank you for your Pull Request! 🙏 Please make sure it follows the contribution guidelines outlined in [this document](https://github.com/paritytech/substrate/blob/master/docs/CONTRIBUTING.adoc) and fill out the sections below. Once you're ready to submit your PR for review, please delete this section and leave only the text under the "Description" heading. + +# Description + +*Please include a summary of the changes and the related issue. Please also include relevant motivation and context, including:* + +- What does this PR do? +- Why are these changes needed? +- How were these changes implemented and what do they affect? + +*Use [Github semantic linking](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword) to address any open issues this PR relates to or closes.* + +Fixes # (issue number, *if applicable*) + +Closes # (issue number, *if applicable*) + +Polkadot companion: (*if applicable*) + +Cumulus companion: (*if applicable*) + +# Checklist + +- [ ] My PR includes a detailed description as outlined in the "Description" section above +- [ ] My PR follows the [labeling requirements](https://github.com/paritytech/substrate/blob/master/docs/CONTRIBUTING.adoc#merge-process) of this project (at minimum one label for each `A`, `B`, `C` and `D` required) +- [ ] I have made corresponding changes to the documentation (if applicable) +- [ ] I have added tests that prove my fix is effective or that my feature works (if applicable) +- [ ] If this PR alters any external APIs or interfaces used by Polkadot, the corresponding Polkadot PR is ready as well as the corresponding Cumulus PR (optional) + +You can remove the "Checklist" section once all have been checked. Thank you for your contribution! ✄ ----------------------------------------------------------------------------- diff --git a/docs/README.adoc b/docs/README.adoc index 5d7b0b52c525..8e43757d10fb 100644 --- a/docs/README.adoc +++ b/docs/README.adoc @@ -297,7 +297,7 @@ Detailed logs may be shown by running the node with the following environment va If you want to see the multi-node consensus algorithm in action locally, then you can create a local testnet with two validator nodes for Alice and Bob, who are the initial authorities of the genesis chain specification that have been endowed with a testnet DOTs. We'll give each node a name and expose them so they are listed on link:https://telemetry.polkadot.io/#/Local%20Testnet[Telemetry]. You'll need two terminal windows open. -We'll start Alice's Substrate node first on default TCP port 30333 with her chain database stored locally at `/tmp/alice`. The Bootnode ID of her node is `QmRpheLN4JWdAnY7HGJfWFNbfkQCb6tFf4vvA6hgjMZKrR`, which is generated from the `--node-key` value that we specify below: +We'll start Alice's Substrate node first on default TCP port 30333 with their chain database stored locally at `/tmp/alice`. The Bootnode ID of Alice's node is `QmRpheLN4JWdAnY7HGJfWFNbfkQCb6tFf4vvA6hgjMZKrR`, which is generated from the `--node-key` value that we specify below: [source, shell] cargo run --release \-- \ @@ -308,7 +308,7 @@ cargo run --release \-- \ --telemetry-url 'ws://telemetry.polkadot.io:1024 0' \ --validator -In the second terminal, we'll run the following to start Bob's Substrate node on a different TCP port of 30334, and with his chain database stored locally at `/tmp/bob`. We'll specify a value for the `--bootnodes` option that will connect his node to Alice's Bootnode ID on TCP port 30333: +In the second terminal, we'll run the following to start Bob's Substrate node on a different TCP port of 30334, and with their chain database stored locally at `/tmp/bob`. We'll specify a value for the `--bootnodes` option that will connect Bob's node to Alice's Bootnode ID on TCP port 30333: [source, shell] cargo run --release \-- \ diff --git a/docs/node-template-release.md b/docs/node-template-release.md index 4f4977a9df03..911e6a2bbe71 100644 --- a/docs/node-template-release.md +++ b/docs/node-template-release.md @@ -27,33 +27,26 @@ by running the following command. delete files/directories that are removed from the source. So you need to manually check and remove them in the destination. -3. There are actually three packages in the Node Template, `node-template` (the node), -`node-template-runtime` (the runtime), and `pallet-template`, and each has its own `Cargo.toml`. -Inside these three files, dependencies are listed in expanded form and linked to a certain git -commit in Substrate remote repository, such as: +3. There is a `Cargo.toml` file in the root directory. Inside, dependencies are listed form and +linked to a certain git commit in Substrate remote repository, such as: ```toml - [dev-dependencies.sp-core] - default-features = false - git = 'https://github.com/paritytech/substrate.git' - rev = 'c1fe59d060600a10eebb4ace277af1fee20bad17' - version = '3.0.0' + sp-core = { version = "7.0.0", git = "https://github.com/paritytech/substrate.git", rev = "de80d0107336a9c7a2efdc0199015e4d67fcbdb5", default-features = false } ``` - We will update each of them to the shortened form and link them to the Rust - [crate registry](https://crates.io/). After confirming the versioned package is published in - the crate, the above will become: + We will update each of them to link to the Rust [crate registry](https://crates.io/). +After confirming the versioned package is published in the crate, the above will become: ```toml - [dev-dependencies] - sp-core = { version = '3.0.0', default-features = false } + [workspace.dependencies] + sp-core = { version = "7.0.0", default-features = false } ``` P.S: This step can be automated if we update `node-template-release` package in `scripts/ci/node-template-release`. -4. Once the three `Cargo.toml`s are updated, compile and confirm that the Node Template builds. Then -commit the changes to a new branch in [Substrate Node Template](https://github.com/substrate-developer-hub/substrate-node-template), and make a PR. +4. Once the `Cargo.toml` is updated, compile and confirm that the Node Template builds. Then commit +the changes to a new branch in [Substrate Node Template](https://github.com/substrate-developer-hub/substrate-node-template), and make a PR. > Note that there is a chance the code in Substrate Node Template works with the linked Substrate git commit but not with published packages due to the latest (as yet) unpublished features. In this case, diff --git a/frame/README.md b/frame/README.md new file mode 100644 index 000000000000..47a7892c2c8d --- /dev/null +++ b/frame/README.md @@ -0,0 +1,11 @@ +# FRAME + +The FRAME development environment provides modules (called "pallets") and support libraries that you can use, modify, and extend to build the runtime logic to suit the needs of your blockchain. + +### Documentation + +https://docs.substrate.io/reference/frame-pallets/ + +### Issues + +https://github.com/orgs/paritytech/projects/40 diff --git a/frame/alliance/Cargo.toml b/frame/alliance/Cargo.toml index d6a16e2c32e3..b411df760f61 100644 --- a/frame/alliance/Cargo.toml +++ b/frame/alliance/Cargo.toml @@ -13,17 +13,17 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -array-bytes = { version = "4.1", optional = true } -sha2 = { version = "0.10.1", default-features = false, optional = true } +array-bytes = { version = "6.1", optional = true } log = { version = "0.4.14", default-features = false } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-core-hashing = { version = "9.0.0", default-features = false, path = "../../primitives/core/hashing", optional = true } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } @@ -33,14 +33,15 @@ pallet-identity = { version = "4.0.0-dev", path = "../identity", default-feature pallet-collective = { version = "4.0.0-dev", path = "../collective", default-features = false, optional = true } [dev-dependencies] -array-bytes = "4.1" -sha2 = "0.10.1" +array-bytes = "6.1" +sp-core-hashing = { version = "9.0.0", default-features = false, path = "../../primitives/core/hashing" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-collective = { version = "4.0.0-dev", path = "../collective" } [features] default = ["std"] std = [ + "sp-core-hashing?/std", "pallet-collective?/std", "frame-benchmarking?/std", "log/std", @@ -56,7 +57,7 @@ std = [ ] runtime-benchmarks = [ "array-bytes", - "sha2", + "sp-core-hashing", "frame-benchmarking/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "frame-support/runtime-benchmarks", diff --git a/frame/alliance/src/benchmarking.rs b/frame/alliance/src/benchmarking.rs index a34b76bd96ec..eb32c6c466c9 100644 --- a/frame/alliance/src/benchmarking.rs +++ b/frame/alliance/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,9 +25,9 @@ use sp_std::{ prelude::*, }; -use frame_benchmarking::{account, benchmarks_instance_pallet}; +use frame_benchmarking::v1::{account, benchmarks_instance_pallet, BenchmarkError}; use frame_support::traits::{EnsureOrigin, Get, UnfilteredDispatchable}; -use frame_system::{Pallet as System, RawOrigin as SystemOrigin}; +use frame_system::{pallet_prelude::BlockNumberFor, Pallet as System, RawOrigin as SystemOrigin}; use super::{Call as AllianceCall, Pallet as Alliance, *}; @@ -40,11 +40,8 @@ fn assert_last_event, I: 'static>(generic_event: >:: } fn cid(input: impl AsRef<[u8]>) -> Cid { - use sha2::{Digest, Sha256}; - let mut hasher = Sha256::new(); - hasher.update(input); - let result = hasher.finalize(); - Cid::new_v0(&*result) + let result = sp_core_hashing::sha2_256(input.as_ref()); + Cid::new_v0(result) } fn rule(input: impl AsRef<[u8]>) -> Cid { @@ -435,7 +432,7 @@ benchmarks_instance_pallet! { false, )?; - System::::set_block_number(T::BlockNumber::max_value()); + System::::set_block_number(BlockNumberFor::::max_value()); }: close(SystemOrigin::Signed(voter), last_hash.clone(), index, Weight::MAX, bytes_in_storage) verify { @@ -507,7 +504,7 @@ benchmarks_instance_pallet! { } // caller is prime, prime already votes aye by creating the proposal - System::::set_block_number(T::BlockNumber::max_value()); + System::::set_block_number(BlockNumberFor::::max_value()); }: close(SystemOrigin::Signed(voter), last_hash.clone(), index, Weight::MAX, bytes_in_storage) verify { @@ -581,7 +578,8 @@ benchmarks_instance_pallet! { let rule = rule(b"hello world"); let call = Call::::set_rule { rule: rule.clone() }; - let origin = T::AdminOrigin::successful_origin(); + let origin = + T::AdminOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: { call.dispatch_bypass_filter(origin)? } verify { assert_eq!(Alliance::::rule(), Some(rule.clone())); @@ -594,7 +592,8 @@ benchmarks_instance_pallet! { let announcement = announcement(b"hello world"); let call = Call::::announce { announcement: announcement.clone() }; - let origin = T::AnnouncementOrigin::successful_origin(); + let origin = + T::AnnouncementOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: { call.dispatch_bypass_filter(origin)? } verify { assert!(Alliance::::announcements().contains(&announcement)); @@ -609,7 +608,8 @@ benchmarks_instance_pallet! { Announcements::::put(announcements); let call = Call::::remove_announcement { announcement: announcement.clone() }; - let origin = T::AnnouncementOrigin::successful_origin(); + let origin = + T::AnnouncementOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: { call.dispatch_bypass_filter(origin)? } verify { assert!(Alliance::::announcements().is_empty()); @@ -665,7 +665,8 @@ benchmarks_instance_pallet! { let ally1_lookup = T::Lookup::unlookup(ally1.clone()); let call = Call::::elevate_ally { ally: ally1_lookup }; - let origin = T::MembershipManager::successful_origin(); + let origin = + T::MembershipManager::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: { call.dispatch_bypass_filter(origin)? } verify { assert!(!Alliance::::is_ally(&ally1)); @@ -725,7 +726,8 @@ benchmarks_instance_pallet! { let fellow2_lookup = T::Lookup::unlookup(fellow2.clone()); let call = Call::::kick_member { who: fellow2_lookup }; - let origin = T::MembershipManager::successful_origin(); + let origin = + T::MembershipManager::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: { call.dispatch_bypass_filter(origin)? } verify { assert!(!Alliance::::is_member(&fellow2)); @@ -754,7 +756,8 @@ benchmarks_instance_pallet! { unscrupulous_list.extend(websites.into_iter().map(UnscrupulousItem::Website)); let call = Call::::add_unscrupulous_items { items: unscrupulous_list.clone() }; - let origin = T::AnnouncementOrigin::successful_origin(); + let origin = + T::AnnouncementOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: { call.dispatch_bypass_filter(origin)? } verify { assert_last_event::(Event::UnscrupulousItemAdded { items: unscrupulous_list }.into()); @@ -784,7 +787,8 @@ benchmarks_instance_pallet! { unscrupulous_list.extend(websites.into_iter().map(UnscrupulousItem::Website)); let call = Call::::remove_unscrupulous_items { items: unscrupulous_list.clone() }; - let origin = T::AnnouncementOrigin::successful_origin(); + let origin = + T::AnnouncementOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: { call.dispatch_bypass_filter(origin)? } verify { assert_last_event::(Event::UnscrupulousItemRemoved { items: unscrupulous_list }.into()); diff --git a/frame/alliance/src/lib.rs b/frame/alliance/src/lib.rs index 790c3c384e70..c103f975f23b 100644 --- a/frame/alliance/src/lib.rs +++ b/frame/alliance/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -114,7 +114,7 @@ use frame_support::{ ChangeMembers, Currency, Get, InitializeMembers, IsSubType, OnUnbalanced, ReservableCurrency, }, - weights::{OldWeight, Weight}, + weights::Weight, }; use pallet_identity::IdentityField; @@ -222,7 +222,6 @@ pub mod pallet { use super::*; #[pallet::pallet] - #[pallet::generate_store(pub (super) trait Store)] #[pallet::storage_version(migration::STORAGE_VERSION)] pub struct Pallet(PhantomData<(T, I)>); @@ -310,7 +309,7 @@ pub mod pallet { /// The number of blocks a member must wait between giving a retirement notice and retiring. /// Supposed to be greater than time required to `kick_member`. - type RetirementPeriod: Get; + type RetirementPeriod: Get>; } #[pallet::error] @@ -402,21 +401,16 @@ pub mod pallet { } #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig, I: 'static = ()> { pub fellows: Vec, pub allies: Vec, + #[serde(skip)] pub phantom: PhantomData<(T, I)>, } - #[cfg(feature = "std")] - impl, I: 'static> Default for GenesisConfig { - fn default() -> Self { - Self { fellows: Vec::new(), allies: Vec::new(), phantom: Default::default() } - } - } - #[pallet::genesis_build] - impl, I: 'static> GenesisBuild for GenesisConfig { + impl, I: 'static> BuildGenesisConfig for GenesisConfig { fn build(&self) { for m in self.fellows.iter().chain(self.allies.iter()) { assert!(Pallet::::has_identity(m).is_ok(), "Member does not set identity!"); @@ -483,7 +477,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn retiring_members)] pub type RetiringMembers, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, T::AccountId, T::BlockNumber, OptionQuery>; + StorageMap<_, Blake2_128Concat, T::AccountId, BlockNumberFor, OptionQuery>; /// The current list of accounts deemed unscrupulous. These accounts non grata cannot submit /// candidacy. @@ -498,7 +492,7 @@ pub mod pallet { pub type UnscrupulousWebsites, I: 'static = ()> = StorageValue<_, BoundedVec, T::MaxUnscrupulousItems>, ValueQuery>; - #[pallet::call] + #[pallet::call(weight(>::WeightInfo))] impl, I: 'static> Pallet { /// Add a new proposal to be voted on. /// @@ -540,36 +534,7 @@ pub mod pallet { Ok(()) } - /// Close a vote that is either approved, disapproved, or whose voting period has ended. - /// - /// Must be called by a Fellow. - #[pallet::call_index(2)] - #[pallet::weight({ - let b = *length_bound; - let m = T::MaxFellows::get(); - let p1 = *proposal_weight_bound; - let p2 = T::MaxProposals::get(); - T::WeightInfo::close_early_approved(b, m, p2) - .max(T::WeightInfo::close_early_disapproved(m, p2)) - .max(T::WeightInfo::close_approved(b, m, p2)) - .max(T::WeightInfo::close_disapproved(m, p2)) - .saturating_add(p1.into()) - })] - #[allow(deprecated)] - #[deprecated(note = "1D weight is used in this extrinsic, please migrate to use `close`")] - pub fn close_old_weight( - origin: OriginFor, - proposal_hash: T::Hash, - #[pallet::compact] index: ProposalIndex, - #[pallet::compact] proposal_weight_bound: OldWeight, - #[pallet::compact] length_bound: u32, - ) -> DispatchResultWithPostInfo { - let proposal_weight_bound: Weight = proposal_weight_bound.into(); - let who = ensure_signed(origin)?; - ensure!(Self::has_voting_rights(&who), Error::::NoVotingRights); - - Self::do_close(proposal_hash, index, proposal_weight_bound, length_bound) - } + // Index 2 was `close_old_weight`; it was removed due to weights v1 deprecation. /// Initialize the Alliance, onboard fellows and allies. /// @@ -679,7 +644,6 @@ pub mod pallet { /// Set a new IPFS CID to the alliance rule. #[pallet::call_index(5)] - #[pallet::weight(T::WeightInfo::set_rule())] pub fn set_rule(origin: OriginFor, rule: Cid) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; @@ -691,7 +655,6 @@ pub mod pallet { /// Make an announcement of a new IPFS CID about alliance issues. #[pallet::call_index(6)] - #[pallet::weight(T::WeightInfo::announce())] pub fn announce(origin: OriginFor, announcement: Cid) -> DispatchResult { T::AnnouncementOrigin::ensure_origin(origin)?; @@ -707,7 +670,6 @@ pub mod pallet { /// Remove an announcement. #[pallet::call_index(7)] - #[pallet::weight(T::WeightInfo::remove_announcement())] pub fn remove_announcement(origin: OriginFor, announcement: Cid) -> DispatchResult { T::AnnouncementOrigin::ensure_origin(origin)?; @@ -725,7 +687,6 @@ pub mod pallet { /// Submit oneself for candidacy. A fixed deposit is reserved. #[pallet::call_index(8)] - #[pallet::weight(T::WeightInfo::join_alliance())] pub fn join_alliance(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; @@ -762,7 +723,6 @@ pub mod pallet { /// A Fellow can nominate someone to join the alliance as an Ally. There is no deposit /// required from the nominator or nominee. #[pallet::call_index(9)] - #[pallet::weight(T::WeightInfo::nominate_ally())] pub fn nominate_ally(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { let nominator = ensure_signed(origin)?; ensure!(Self::has_voting_rights(&nominator), Error::::NoVotingRights); @@ -787,7 +747,6 @@ pub mod pallet { /// Elevate an Ally to Fellow. #[pallet::call_index(10)] - #[pallet::weight(T::WeightInfo::elevate_ally())] pub fn elevate_ally(origin: OriginFor, ally: AccountIdLookupOf) -> DispatchResult { T::MembershipManager::ensure_origin(origin)?; let ally = T::Lookup::lookup(ally)?; @@ -804,7 +763,6 @@ pub mod pallet { /// As a member, give a retirement notice and start a retirement period required to pass in /// order to retire. #[pallet::call_index(11)] - #[pallet::weight(T::WeightInfo::give_retirement_notice())] pub fn give_retirement_notice(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let role = Self::member_role_of(&who).ok_or(Error::::NotMember)?; @@ -827,7 +785,6 @@ pub mod pallet { /// This can only be done once you have called `give_retirement_notice` and the /// `RetirementPeriod` has passed. #[pallet::call_index(12)] - #[pallet::weight(T::WeightInfo::retire())] pub fn retire(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let retirement_period_end = RetiringMembers::::get(&who) @@ -850,7 +807,6 @@ pub mod pallet { /// Kick a member from the Alliance and slash its deposit. #[pallet::call_index(13)] - #[pallet::weight(T::WeightInfo::kick_member())] pub fn kick_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { T::MembershipManager::ensure_origin(origin)?; let member = T::Lookup::lookup(who)?; @@ -952,7 +908,6 @@ pub mod pallet { /// who do not want to leave the Alliance but do not have the capacity to participate /// operationally for some time. #[pallet::call_index(17)] - #[pallet::weight(T::WeightInfo::abdicate_fellow_status())] pub fn abdicate_fellow_status(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; let role = Self::member_role_of(&who).ok_or(Error::::NotMember)?; diff --git a/frame/alliance/src/migration.rs b/frame/alliance/src/migration.rs index ea07f8c1279b..e3a44a7887e9 100644 --- a/frame/alliance/src/migration.rs +++ b/frame/alliance/src/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/alliance/src/mock.rs b/frame/alliance/src/mock.rs index e708d29d529f..f04e7e414ed9 100644 --- a/frame/alliance/src/mock.rs +++ b/frame/alliance/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,6 @@ pub use sp_core::H256; use sp_runtime::traits::Hash; pub use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; @@ -28,7 +27,7 @@ use sp_std::convert::{TryFrom, TryInto}; pub use frame_support::{ assert_noop, assert_ok, ord_parameter_types, parameter_types, - traits::{EitherOfDiverse, GenesisBuild, SortedMembers}, + traits::{EitherOfDiverse, SortedMembers}, BoundedVec, }; use frame_system::{EnsureRoot, EnsureSignedBy}; @@ -43,20 +42,21 @@ type AccountId = u64; parameter_types! { pub const BlockHashCount: BlockNumber = 250; + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::MAX); } impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); + type BlockWeights = BlockWeights; type BlockLength = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = BlockNumber; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = BlockHashCount; type DbWeight = (); @@ -85,6 +85,10 @@ impl pallet_balances::Config for Test { type MaxLocks = MaxLocks; type MaxReserves = (); type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } const MOTION_DURATION_IN_BLOCKS: BlockNumber = 3; @@ -93,6 +97,7 @@ parameter_types! { pub const MotionDuration: BlockNumber = MOTION_DURATION_IN_BLOCKS; pub const MaxProposals: u32 = 100; pub const MaxMembers: u32 = 100; + pub MaxProposalWeight: Weight = sp_runtime::Perbill::from_percent(50) * BlockWeights::get().max_block; } type AllianceCollective = pallet_collective::Instance1; impl pallet_collective::Config for Test { @@ -104,6 +109,8 @@ impl pallet_collective::Config for Test { type MaxMembers = MaxMembers; type DefaultVote = pallet_collective::PrimeDefaultVote; type WeightInfo = (); + type SetMembersOrigin = EnsureRoot; + type MaxProposalWeight = MaxProposalWeight; } parameter_types! { @@ -229,14 +236,10 @@ impl Config for Test { type RetirementPeriod = RetirementPeriod; } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { System: frame_system, Balances: pallet_balances, @@ -247,7 +250,7 @@ frame_support::construct_runtime!( ); pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![ @@ -265,14 +268,12 @@ pub fn new_test_ext() -> sp_io::TestExternalities { .assimilate_storage(&mut t) .unwrap(); - GenesisBuild::::assimilate_storage( - &pallet_alliance::GenesisConfig { - fellows: vec![], - allies: vec![], - phantom: Default::default(), - }, - &mut t, - ) + pallet_alliance::GenesisConfig:: { + fellows: vec![], + allies: vec![], + phantom: Default::default(), + } + .assimilate_storage(&mut t) .unwrap(); let mut ext = sp_io::TestExternalities::new(t); @@ -363,15 +364,12 @@ pub fn new_test_ext() -> sp_io::TestExternalities { #[cfg(feature = "runtime-benchmarks")] pub fn new_bench_ext() -> sp_io::TestExternalities { - GenesisConfig::default().build_storage().unwrap().into() + RuntimeGenesisConfig::default().build_storage().unwrap().into() } pub fn test_cid() -> Cid { - use sha2::{Digest, Sha256}; - let mut hasher = Sha256::new(); - hasher.update(b"hello world"); - let result = hasher.finalize(); - Cid::new_v0(&*result) + let result = sp_core_hashing::sha2_256(b"hello world"); + Cid::new_v0(result) } pub fn make_remark_proposal(value: u64) -> (RuntimeCall, u32, H256) { diff --git a/frame/alliance/src/tests.rs b/frame/alliance/src/tests.rs index 363b19429b80..098fd86bbae1 100644 --- a/frame/alliance/src/tests.rs +++ b/frame/alliance/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -299,7 +299,7 @@ fn close_works() { })), record(mock::RuntimeEvent::AllianceMotion(AllianceMotionEvent::Executed { proposal_hash: hash, - result: Err(DispatchError::BadOrigin), + result: Ok(()), })) ] ); @@ -629,3 +629,12 @@ fn remove_unscrupulous_items_works() { assert_eq!(Alliance::unscrupulous_accounts(), Vec::::new()); }); } + +#[test] +fn weights_sane() { + let info = crate::Call::::join_alliance {}.get_dispatch_info(); + assert_eq!(<() as crate::WeightInfo>::join_alliance(), info.weight); + + let info = crate::Call::::nominate_ally { who: 10 }.get_dispatch_info(); + assert_eq!(<() as crate::WeightInfo>::nominate_ally(), info.weight); +} diff --git a/frame/alliance/src/types.rs b/frame/alliance/src/types.rs index c155b9fa90f9..784993b2bc13 100644 --- a/frame/alliance/src/types.rs +++ b/frame/alliance/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/alliance/src/weights.rs b/frame/alliance/src/weights.rs index 1cfc83dfc4bb..b5bb50957207 100644 --- a/frame/alliance/src/weights.rs +++ b/frame/alliance/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,33 +18,37 @@ //! Autogenerated weights for pallet_alliance //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-12-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// /home/benchbot/cargo_target_dir/production/substrate +// ./target/production/substrate // benchmark // pallet +// --chain=dev // --steps=50 // --repeat=20 +// --pallet=pallet_alliance +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json -// --pallet=pallet_alliance -// --chain=dev -// --header=./HEADER-APACHE2 // --output=./frame/alliance/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_alliance. pub trait WeightInfo { @@ -73,530 +77,832 @@ pub trait WeightInfo { /// Weights for pallet_alliance using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Alliance Members (r:1 w:0) - // Storage: AllianceMotion ProposalOf (r:1 w:1) - // Storage: AllianceMotion Proposals (r:1 w:1) - // Storage: AllianceMotion ProposalCount (r:1 w:1) - // Storage: AllianceMotion Voting (r:0 w:1) + /// Storage: Alliance Members (r:1 w:0) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion ProposalOf (r:1 w:1) + /// Proof Skipped: AllianceMotion ProposalOf (max_values: None, max_size: None, mode: Measured) + /// Storage: AllianceMotion Proposals (r:1 w:1) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion ProposalCount (r:1 w:1) + /// Proof Skipped: AllianceMotion ProposalCount (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Voting (r:0 w:1) + /// Proof Skipped: AllianceMotion Voting (max_values: None, max_size: None, mode: Measured) /// The range of component `b` is `[1, 1024]`. /// The range of component `m` is `[2, 100]`. /// The range of component `p` is `[1, 100]`. - fn propose_proposed(_b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 45_945 nanoseconds. - Weight::from_ref_time(44_195_340) - // Standard Error: 2_212 - .saturating_add(Weight::from_ref_time(73_715).saturating_mul(m.into())) - // Standard Error: 2_184 - .saturating_add(Weight::from_ref_time(172_373).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Alliance Members (r:1 w:0) - // Storage: AllianceMotion Voting (r:1 w:1) + fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `653 + m * (32 ±0) + p * (35 ±0)` + // Estimated: `6676 + m * (32 ±0) + p * (36 ±0)` + // Minimum execution time: 36_908_000 picoseconds. + Weight::from_parts(39_040_304, 6676) + // Standard Error: 131 + .saturating_add(Weight::from_parts(781, 0).saturating_mul(b.into())) + // Standard Error: 1_375 + .saturating_add(Weight::from_parts(48_745, 0).saturating_mul(m.into())) + // Standard Error: 1_358 + .saturating_add(Weight::from_parts(148_047, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into())) + } + /// Storage: Alliance Members (r:1 w:0) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Voting (r:1 w:1) + /// Proof Skipped: AllianceMotion Voting (max_values: None, max_size: None, mode: Measured) /// The range of component `m` is `[5, 100]`. fn vote(m: u32, ) -> Weight { - // Minimum execution time: 41_578 nanoseconds. - Weight::from_ref_time(44_299_385) - // Standard Error: 3_727 - .saturating_add(Weight::from_ref_time(138_681).saturating_mul(m.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Alliance Members (r:1 w:0) - // Storage: AllianceMotion Voting (r:1 w:1) - // Storage: AllianceMotion Members (r:1 w:0) - // Storage: AllianceMotion Proposals (r:1 w:1) - // Storage: AllianceMotion ProposalOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `1042 + m * (64 ±0)` + // Estimated: `6676 + m * (64 ±0)` + // Minimum execution time: 30_166_000 picoseconds. + Weight::from_parts(32_798_454, 6676) + // Standard Error: 1_432 + .saturating_add(Weight::from_parts(83_001, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) + } + /// Storage: Alliance Members (r:1 w:0) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Voting (r:1 w:1) + /// Proof Skipped: AllianceMotion Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: AllianceMotion Members (r:1 w:0) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Proposals (r:1 w:1) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion ProposalOf (r:0 w:1) + /// Proof Skipped: AllianceMotion ProposalOf (max_values: None, max_size: None, mode: Measured) /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. fn close_early_disapproved(m: u32, p: u32, ) -> Weight { - // Minimum execution time: 53_033 nanoseconds. - Weight::from_ref_time(44_608_804) - // Standard Error: 2_306 - .saturating_add(Weight::from_ref_time(102_842).saturating_mul(m.into())) - // Standard Error: 2_248 - .saturating_add(Weight::from_ref_time(172_018).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Alliance Members (r:1 w:0) - // Storage: AllianceMotion Voting (r:1 w:1) - // Storage: AllianceMotion Members (r:1 w:0) - // Storage: AllianceMotion ProposalOf (r:1 w:1) - // Storage: AllianceMotion Proposals (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `576 + m * (96 ±0) + p * (36 ±0)` + // Estimated: `6676 + m * (97 ±0) + p * (36 ±0)` + // Minimum execution time: 45_173_000 picoseconds. + Weight::from_parts(42_192_020, 6676) + // Standard Error: 1_456 + .saturating_add(Weight::from_parts(66_751, 0).saturating_mul(m.into())) + // Standard Error: 1_420 + .saturating_add(Weight::from_parts(158_161, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 97).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into())) + } + /// Storage: Alliance Members (r:1 w:0) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Voting (r:1 w:1) + /// Proof Skipped: AllianceMotion Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: AllianceMotion Members (r:1 w:0) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion ProposalOf (r:1 w:1) + /// Proof Skipped: AllianceMotion ProposalOf (max_values: None, max_size: None, mode: Measured) + /// Storage: AllianceMotion Proposals (r:1 w:1) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `b` is `[1, 1024]`. /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 61_157 nanoseconds. - Weight::from_ref_time(53_647_510) - // Standard Error: 235 - .saturating_add(Weight::from_ref_time(88).saturating_mul(b.into())) - // Standard Error: 2_486 - .saturating_add(Weight::from_ref_time(103_912).saturating_mul(m.into())) - // Standard Error: 2_424 - .saturating_add(Weight::from_ref_time(197_471).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Alliance Members (r:1 w:0) - // Storage: AllianceMotion Voting (r:1 w:1) - // Storage: AllianceMotion Members (r:1 w:0) - // Storage: AllianceMotion Prime (r:1 w:0) - // Storage: AllianceMotion Proposals (r:1 w:1) - // Storage: AllianceMotion ProposalOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `1087 + m * (96 ±0) + p * (39 ±0)` + // Estimated: `6676 + m * (97 ±0) + p * (40 ±0)` + // Minimum execution time: 58_290_000 picoseconds. + Weight::from_parts(54_924_919, 6676) + // Standard Error: 157 + .saturating_add(Weight::from_parts(464, 0).saturating_mul(b.into())) + // Standard Error: 1_665 + .saturating_add(Weight::from_parts(73_183, 0).saturating_mul(m.into())) + // Standard Error: 1_623 + .saturating_add(Weight::from_parts(168_318, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 97).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 40).saturating_mul(p.into())) + } + /// Storage: Alliance Members (r:1 w:0) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Voting (r:1 w:1) + /// Proof Skipped: AllianceMotion Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: AllianceMotion Members (r:1 w:0) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Prime (r:1 w:0) + /// Proof Skipped: AllianceMotion Prime (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Proposals (r:1 w:1) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion ProposalOf (r:0 w:1) + /// Proof Skipped: AllianceMotion ProposalOf (max_values: None, max_size: None, mode: Measured) /// The range of component `m` is `[2, 100]`. /// The range of component `p` is `[1, 100]`. fn close_disapproved(m: u32, p: u32, ) -> Weight { - // Minimum execution time: 53_830 nanoseconds. - Weight::from_ref_time(43_926_698) - // Standard Error: 2_495 - .saturating_add(Weight::from_ref_time(112_814).saturating_mul(m.into())) - // Standard Error: 2_465 - .saturating_add(Weight::from_ref_time(186_422).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Alliance Members (r:1 w:0) - // Storage: AllianceMotion Voting (r:1 w:1) - // Storage: AllianceMotion Members (r:1 w:0) - // Storage: AllianceMotion Prime (r:1 w:0) - // Storage: AllianceMotion Proposals (r:1 w:1) - // Storage: AllianceMotion ProposalOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `577 + m * (96 ±0) + p * (36 ±0)` + // Estimated: `6676 + m * (97 ±0) + p * (36 ±0)` + // Minimum execution time: 46_794_000 picoseconds. + Weight::from_parts(43_092_958, 6676) + // Standard Error: 1_273 + .saturating_add(Weight::from_parts(71_054, 0).saturating_mul(m.into())) + // Standard Error: 1_257 + .saturating_add(Weight::from_parts(152_820, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 97).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into())) + } + /// Storage: Alliance Members (r:1 w:0) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Voting (r:1 w:1) + /// Proof Skipped: AllianceMotion Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: AllianceMotion Members (r:1 w:0) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Prime (r:1 w:0) + /// Proof Skipped: AllianceMotion Prime (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Proposals (r:1 w:1) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion ProposalOf (r:0 w:1) + /// Proof Skipped: AllianceMotion ProposalOf (max_values: None, max_size: None, mode: Measured) /// The range of component `b` is `[1, 1024]`. /// The range of component `m` is `[5, 100]`. /// The range of component `p` is `[1, 100]`. - fn close_approved(_b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 53_665 nanoseconds. - Weight::from_ref_time(44_914_913) - // Standard Error: 2_244 - .saturating_add(Weight::from_ref_time(105_848).saturating_mul(m.into())) - // Standard Error: 2_164 - .saturating_add(Weight::from_ref_time(190_830).saturating_mul(p.into())) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Alliance Members (r:2 w:2) - // Storage: AllianceMotion Members (r:1 w:1) + fn close_approved(b: u32, m: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `684 + m * (96 ±0) + p * (35 ±0)` + // Estimated: `6676 + m * (97 ±0) + p * (36 ±0)` + // Minimum execution time: 47_338_000 picoseconds. + Weight::from_parts(41_257_479, 6676) + // Standard Error: 119 + .saturating_add(Weight::from_parts(1_019, 0).saturating_mul(b.into())) + // Standard Error: 1_277 + .saturating_add(Weight::from_parts(78_453, 0).saturating_mul(m.into())) + // Standard Error: 1_231 + .saturating_add(Weight::from_parts(150_991, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 97).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into())) + } + /// Storage: Alliance Members (r:2 w:2) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Members (r:1 w:1) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. /// The range of component `z` is `[0, 100]`. fn init_members(m: u32, z: u32, ) -> Weight { - // Minimum execution time: 46_486 nanoseconds. - Weight::from_ref_time(34_596_922) - // Standard Error: 1_807 - .saturating_add(Weight::from_ref_time(143_225).saturating_mul(m.into())) - // Standard Error: 1_786 - .saturating_add(Weight::from_ref_time(125_729).saturating_mul(z.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Alliance Members (r:2 w:2) - // Storage: AllianceMotion Proposals (r:1 w:0) - // Storage: Alliance DepositOf (r:101 w:50) - // Storage: System Account (r:50 w:50) - // Storage: AllianceMotion Members (r:0 w:1) - // Storage: AllianceMotion Prime (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `217` + // Estimated: `12362` + // Minimum execution time: 35_012_000 picoseconds. + Weight::from_parts(24_288_079, 12362) + // Standard Error: 878 + .saturating_add(Weight::from_parts(153_615, 0).saturating_mul(m.into())) + // Standard Error: 867 + .saturating_add(Weight::from_parts(129_307, 0).saturating_mul(z.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Alliance Members (r:2 w:2) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Proposals (r:1 w:0) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Alliance DepositOf (r:200 w:50) + /// Proof: Alliance DepositOf (max_values: None, max_size: Some(64), added: 2539, mode: MaxEncodedLen) + /// Storage: System Account (r:50 w:50) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: AllianceMotion Members (r:0 w:1) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Prime (r:0 w:1) + /// Proof Skipped: AllianceMotion Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `x` is `[1, 100]`. /// The range of component `y` is `[0, 100]`. /// The range of component `z` is `[0, 50]`. fn disband(x: u32, y: u32, z: u32, ) -> Weight { - // Minimum execution time: 251_640 nanoseconds. - Weight::from_ref_time(252_446_000) - // Standard Error: 20_419 - .saturating_add(Weight::from_ref_time(486_667).saturating_mul(x.into())) - // Standard Error: 20_320 - .saturating_add(Weight::from_ref_time(498_890).saturating_mul(y.into())) - // Standard Error: 40_604 - .saturating_add(Weight::from_ref_time(9_411_242).saturating_mul(z.into())) - .saturating_add(T::DbWeight::get().reads(3)) + // Proof Size summary in bytes: + // Measured: `0 + x * (50 ±0) + y * (51 ±0) + z * (251 ±0)` + // Estimated: `12362 + x * (2539 ±0) + y * (2539 ±0) + z * (2603 ±1)` + // Minimum execution time: 309_235_000 picoseconds. + Weight::from_parts(311_279_000, 12362) + // Standard Error: 26_510 + .saturating_add(Weight::from_parts(543_475, 0).saturating_mul(x.into())) + // Standard Error: 26_382 + .saturating_add(Weight::from_parts(603_169, 0).saturating_mul(y.into())) + // Standard Error: 52_716 + .saturating_add(Weight::from_parts(16_264_836, 0).saturating_mul(z.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(x.into()))) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(y.into()))) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(z.into()))) - .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes(4_u64)) .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(z.into()))) + .saturating_add(Weight::from_parts(0, 2539).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(0, 2539).saturating_mul(y.into())) + .saturating_add(Weight::from_parts(0, 2603).saturating_mul(z.into())) } - // Storage: Alliance Rule (r:0 w:1) + /// Storage: Alliance Rule (r:0 w:1) + /// Proof: Alliance Rule (max_values: Some(1), max_size: Some(87), added: 582, mode: MaxEncodedLen) fn set_rule() -> Weight { - // Minimum execution time: 18_867 nanoseconds. - Weight::from_ref_time(19_155_000) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Alliance Announcements (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_833_000 picoseconds. + Weight::from_parts(9_313_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Alliance Announcements (r:1 w:1) + /// Proof: Alliance Announcements (max_values: Some(1), max_size: Some(8702), added: 9197, mode: MaxEncodedLen) fn announce() -> Weight { - // Minimum execution time: 21_942 nanoseconds. - Weight::from_ref_time(22_319_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Alliance Announcements (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `246` + // Estimated: `10187` + // Minimum execution time: 12_231_000 picoseconds. + Weight::from_parts(12_761_000, 10187) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Alliance Announcements (r:1 w:1) + /// Proof: Alliance Announcements (max_values: Some(1), max_size: Some(8702), added: 9197, mode: MaxEncodedLen) fn remove_announcement() -> Weight { - // Minimum execution time: 23_419 nanoseconds. - Weight::from_ref_time(23_854_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Alliance Members (r:3 w:1) - // Storage: Alliance UnscrupulousAccounts (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Alliance DepositOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `319` + // Estimated: `10187` + // Minimum execution time: 13_079_000 picoseconds. + Weight::from_parts(13_612_000, 10187) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Alliance Members (r:3 w:1) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: Alliance UnscrupulousAccounts (r:1 w:0) + /// Proof: Alliance UnscrupulousAccounts (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Alliance DepositOf (r:0 w:1) + /// Proof: Alliance DepositOf (max_values: None, max_size: Some(64), added: 2539, mode: MaxEncodedLen) fn join_alliance() -> Weight { - // Minimum execution time: 53_016 nanoseconds. - Weight::from_ref_time(53_768_000) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Alliance Members (r:3 w:1) - // Storage: Alliance UnscrupulousAccounts (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `468` + // Estimated: `18048` + // Minimum execution time: 44_574_000 picoseconds. + Weight::from_parts(46_157_000, 18048) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Alliance Members (r:3 w:1) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: Alliance UnscrupulousAccounts (r:1 w:0) + /// Proof: Alliance UnscrupulousAccounts (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) fn nominate_ally() -> Weight { - // Minimum execution time: 40_019 nanoseconds. - Weight::from_ref_time(41_087_000) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Alliance Members (r:2 w:2) - // Storage: AllianceMotion Proposals (r:1 w:0) - // Storage: AllianceMotion Members (r:0 w:1) - // Storage: AllianceMotion Prime (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `367` + // Estimated: `18048` + // Minimum execution time: 26_114_000 picoseconds. + Weight::from_parts(27_069_000, 18048) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Alliance Members (r:2 w:2) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Proposals (r:1 w:0) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Members (r:0 w:1) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Prime (r:0 w:1) + /// Proof Skipped: AllianceMotion Prime (max_values: Some(1), max_size: None, mode: Measured) fn elevate_ally() -> Weight { - // Minimum execution time: 35_973 nanoseconds. - Weight::from_ref_time(36_365_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Alliance Members (r:4 w:2) - // Storage: AllianceMotion Proposals (r:1 w:0) - // Storage: AllianceMotion Members (r:0 w:1) - // Storage: AllianceMotion Prime (r:0 w:1) - // Storage: Alliance RetiringMembers (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `443` + // Estimated: `12362` + // Minimum execution time: 25_882_000 picoseconds. + Weight::from_parts(26_923_000, 12362) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Alliance Members (r:4 w:2) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Proposals (r:1 w:0) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Members (r:0 w:1) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Prime (r:0 w:1) + /// Proof Skipped: AllianceMotion Prime (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Alliance RetiringMembers (r:0 w:1) + /// Proof: Alliance RetiringMembers (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) fn give_retirement_notice() -> Weight { - // Minimum execution time: 44_949 nanoseconds. - Weight::from_ref_time(46_284_000) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(5)) - } - // Storage: Alliance RetiringMembers (r:1 w:1) - // Storage: Alliance Members (r:1 w:1) - // Storage: Alliance DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `443` + // Estimated: `23734` + // Minimum execution time: 34_112_000 picoseconds. + Weight::from_parts(35_499_000, 23734) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: Alliance RetiringMembers (r:1 w:1) + /// Proof: Alliance RetiringMembers (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: Alliance Members (r:1 w:1) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: Alliance DepositOf (r:1 w:1) + /// Proof: Alliance DepositOf (max_values: None, max_size: Some(64), added: 2539, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn retire() -> Weight { - // Minimum execution time: 45_989 nanoseconds. - Weight::from_ref_time(46_586_000) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Alliance Members (r:3 w:1) - // Storage: AllianceMotion Proposals (r:1 w:0) - // Storage: Alliance DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: AllianceMotion Members (r:0 w:1) - // Storage: AllianceMotion Prime (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `687` + // Estimated: `6676` + // Minimum execution time: 41_239_000 picoseconds. + Weight::from_parts(42_764_000, 6676) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Alliance Members (r:3 w:1) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Proposals (r:1 w:0) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Alliance DepositOf (r:1 w:1) + /// Proof: Alliance DepositOf (max_values: None, max_size: Some(64), added: 2539, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: AllianceMotion Members (r:0 w:1) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Prime (r:0 w:1) + /// Proof Skipped: AllianceMotion Prime (max_values: Some(1), max_size: None, mode: Measured) fn kick_member() -> Weight { - // Minimum execution time: 67_333 nanoseconds. - Weight::from_ref_time(68_302_000) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(5)) - } - // Storage: Alliance UnscrupulousAccounts (r:1 w:1) - // Storage: Alliance UnscrupulousWebsites (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `707` + // Estimated: `18048` + // Minimum execution time: 68_071_000 picoseconds. + Weight::from_parts(71_808_000, 18048) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: Alliance UnscrupulousAccounts (r:1 w:1) + /// Proof: Alliance UnscrupulousAccounts (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: Alliance UnscrupulousWebsites (r:1 w:1) + /// Proof: Alliance UnscrupulousWebsites (max_values: Some(1), max_size: Some(25702), added: 26197, mode: MaxEncodedLen) /// The range of component `n` is `[0, 100]`. /// The range of component `l` is `[0, 255]`. fn add_unscrupulous_items(n: u32, l: u32, ) -> Weight { - // Minimum execution time: 16_911 nanoseconds. - Weight::from_ref_time(17_204_000) - // Standard Error: 2_688 - .saturating_add(Weight::from_ref_time(1_324_527).saturating_mul(n.into())) - // Standard Error: 1_052 - .saturating_add(Weight::from_ref_time(73_828).saturating_mul(l.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Alliance UnscrupulousAccounts (r:1 w:1) - // Storage: Alliance UnscrupulousWebsites (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `246` + // Estimated: `27187` + // Minimum execution time: 7_006_000 picoseconds. + Weight::from_parts(7_253_000, 27187) + // Standard Error: 3_403 + .saturating_add(Weight::from_parts(1_680_082, 0).saturating_mul(n.into())) + // Standard Error: 1_333 + .saturating_add(Weight::from_parts(72_943, 0).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Alliance UnscrupulousAccounts (r:1 w:1) + /// Proof: Alliance UnscrupulousAccounts (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: Alliance UnscrupulousWebsites (r:1 w:1) + /// Proof: Alliance UnscrupulousWebsites (max_values: Some(1), max_size: Some(25702), added: 26197, mode: MaxEncodedLen) /// The range of component `n` is `[0, 100]`. /// The range of component `l` is `[0, 255]`. fn remove_unscrupulous_items(n: u32, l: u32, ) -> Weight { - // Minimum execution time: 17_117 nanoseconds. - Weight::from_ref_time(17_291_000) - // Standard Error: 166_132 - .saturating_add(Weight::from_ref_time(13_173_211).saturating_mul(n.into())) - // Standard Error: 65_064 - .saturating_add(Weight::from_ref_time(478_626).saturating_mul(l.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Alliance Members (r:3 w:2) - // Storage: AllianceMotion Proposals (r:1 w:0) - // Storage: AllianceMotion Members (r:0 w:1) - // Storage: AllianceMotion Prime (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `0 + l * (100 ±0) + n * (289 ±0)` + // Estimated: `27187` + // Minimum execution time: 7_292_000 picoseconds. + Weight::from_parts(7_629_000, 27187) + // Standard Error: 176_225 + .saturating_add(Weight::from_parts(16_646_429, 0).saturating_mul(n.into())) + // Standard Error: 69_017 + .saturating_add(Weight::from_parts(310_978, 0).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Alliance Members (r:3 w:2) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Proposals (r:1 w:0) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Members (r:0 w:1) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Prime (r:0 w:1) + /// Proof Skipped: AllianceMotion Prime (max_values: Some(1), max_size: None, mode: Measured) fn abdicate_fellow_status() -> Weight { - // Minimum execution time: 43_347 nanoseconds. - Weight::from_ref_time(43_986_000) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) + // Proof Size summary in bytes: + // Measured: `443` + // Estimated: `18048` + // Minimum execution time: 31_798_000 picoseconds. + Weight::from_parts(33_463_000, 18048) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Alliance Members (r:1 w:0) - // Storage: AllianceMotion ProposalOf (r:1 w:1) - // Storage: AllianceMotion Proposals (r:1 w:1) - // Storage: AllianceMotion ProposalCount (r:1 w:1) - // Storage: AllianceMotion Voting (r:0 w:1) + /// Storage: Alliance Members (r:1 w:0) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion ProposalOf (r:1 w:1) + /// Proof Skipped: AllianceMotion ProposalOf (max_values: None, max_size: None, mode: Measured) + /// Storage: AllianceMotion Proposals (r:1 w:1) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion ProposalCount (r:1 w:1) + /// Proof Skipped: AllianceMotion ProposalCount (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Voting (r:0 w:1) + /// Proof Skipped: AllianceMotion Voting (max_values: None, max_size: None, mode: Measured) /// The range of component `b` is `[1, 1024]`. /// The range of component `m` is `[2, 100]`. /// The range of component `p` is `[1, 100]`. - fn propose_proposed(_b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 45_945 nanoseconds. - Weight::from_ref_time(44_195_340) - // Standard Error: 2_212 - .saturating_add(Weight::from_ref_time(73_715).saturating_mul(m.into())) - // Standard Error: 2_184 - .saturating_add(Weight::from_ref_time(172_373).saturating_mul(p.into())) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Alliance Members (r:1 w:0) - // Storage: AllianceMotion Voting (r:1 w:1) + fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `653 + m * (32 ±0) + p * (35 ±0)` + // Estimated: `6676 + m * (32 ±0) + p * (36 ±0)` + // Minimum execution time: 36_908_000 picoseconds. + Weight::from_parts(39_040_304, 6676) + // Standard Error: 131 + .saturating_add(Weight::from_parts(781, 0).saturating_mul(b.into())) + // Standard Error: 1_375 + .saturating_add(Weight::from_parts(48_745, 0).saturating_mul(m.into())) + // Standard Error: 1_358 + .saturating_add(Weight::from_parts(148_047, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into())) + } + /// Storage: Alliance Members (r:1 w:0) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Voting (r:1 w:1) + /// Proof Skipped: AllianceMotion Voting (max_values: None, max_size: None, mode: Measured) /// The range of component `m` is `[5, 100]`. fn vote(m: u32, ) -> Weight { - // Minimum execution time: 41_578 nanoseconds. - Weight::from_ref_time(44_299_385) - // Standard Error: 3_727 - .saturating_add(Weight::from_ref_time(138_681).saturating_mul(m.into())) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Alliance Members (r:1 w:0) - // Storage: AllianceMotion Voting (r:1 w:1) - // Storage: AllianceMotion Members (r:1 w:0) - // Storage: AllianceMotion Proposals (r:1 w:1) - // Storage: AllianceMotion ProposalOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `1042 + m * (64 ±0)` + // Estimated: `6676 + m * (64 ±0)` + // Minimum execution time: 30_166_000 picoseconds. + Weight::from_parts(32_798_454, 6676) + // Standard Error: 1_432 + .saturating_add(Weight::from_parts(83_001, 0).saturating_mul(m.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) + } + /// Storage: Alliance Members (r:1 w:0) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Voting (r:1 w:1) + /// Proof Skipped: AllianceMotion Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: AllianceMotion Members (r:1 w:0) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Proposals (r:1 w:1) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion ProposalOf (r:0 w:1) + /// Proof Skipped: AllianceMotion ProposalOf (max_values: None, max_size: None, mode: Measured) /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. fn close_early_disapproved(m: u32, p: u32, ) -> Weight { - // Minimum execution time: 53_033 nanoseconds. - Weight::from_ref_time(44_608_804) - // Standard Error: 2_306 - .saturating_add(Weight::from_ref_time(102_842).saturating_mul(m.into())) - // Standard Error: 2_248 - .saturating_add(Weight::from_ref_time(172_018).saturating_mul(p.into())) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Alliance Members (r:1 w:0) - // Storage: AllianceMotion Voting (r:1 w:1) - // Storage: AllianceMotion Members (r:1 w:0) - // Storage: AllianceMotion ProposalOf (r:1 w:1) - // Storage: AllianceMotion Proposals (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `576 + m * (96 ±0) + p * (36 ±0)` + // Estimated: `6676 + m * (97 ±0) + p * (36 ±0)` + // Minimum execution time: 45_173_000 picoseconds. + Weight::from_parts(42_192_020, 6676) + // Standard Error: 1_456 + .saturating_add(Weight::from_parts(66_751, 0).saturating_mul(m.into())) + // Standard Error: 1_420 + .saturating_add(Weight::from_parts(158_161, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 97).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into())) + } + /// Storage: Alliance Members (r:1 w:0) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Voting (r:1 w:1) + /// Proof Skipped: AllianceMotion Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: AllianceMotion Members (r:1 w:0) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion ProposalOf (r:1 w:1) + /// Proof Skipped: AllianceMotion ProposalOf (max_values: None, max_size: None, mode: Measured) + /// Storage: AllianceMotion Proposals (r:1 w:1) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `b` is `[1, 1024]`. /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 61_157 nanoseconds. - Weight::from_ref_time(53_647_510) - // Standard Error: 235 - .saturating_add(Weight::from_ref_time(88).saturating_mul(b.into())) - // Standard Error: 2_486 - .saturating_add(Weight::from_ref_time(103_912).saturating_mul(m.into())) - // Standard Error: 2_424 - .saturating_add(Weight::from_ref_time(197_471).saturating_mul(p.into())) - .saturating_add(RocksDbWeight::get().reads(5)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Alliance Members (r:1 w:0) - // Storage: AllianceMotion Voting (r:1 w:1) - // Storage: AllianceMotion Members (r:1 w:0) - // Storage: AllianceMotion Prime (r:1 w:0) - // Storage: AllianceMotion Proposals (r:1 w:1) - // Storage: AllianceMotion ProposalOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `1087 + m * (96 ±0) + p * (39 ±0)` + // Estimated: `6676 + m * (97 ±0) + p * (40 ±0)` + // Minimum execution time: 58_290_000 picoseconds. + Weight::from_parts(54_924_919, 6676) + // Standard Error: 157 + .saturating_add(Weight::from_parts(464, 0).saturating_mul(b.into())) + // Standard Error: 1_665 + .saturating_add(Weight::from_parts(73_183, 0).saturating_mul(m.into())) + // Standard Error: 1_623 + .saturating_add(Weight::from_parts(168_318, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 97).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 40).saturating_mul(p.into())) + } + /// Storage: Alliance Members (r:1 w:0) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Voting (r:1 w:1) + /// Proof Skipped: AllianceMotion Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: AllianceMotion Members (r:1 w:0) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Prime (r:1 w:0) + /// Proof Skipped: AllianceMotion Prime (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Proposals (r:1 w:1) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion ProposalOf (r:0 w:1) + /// Proof Skipped: AllianceMotion ProposalOf (max_values: None, max_size: None, mode: Measured) /// The range of component `m` is `[2, 100]`. /// The range of component `p` is `[1, 100]`. fn close_disapproved(m: u32, p: u32, ) -> Weight { - // Minimum execution time: 53_830 nanoseconds. - Weight::from_ref_time(43_926_698) - // Standard Error: 2_495 - .saturating_add(Weight::from_ref_time(112_814).saturating_mul(m.into())) - // Standard Error: 2_465 - .saturating_add(Weight::from_ref_time(186_422).saturating_mul(p.into())) - .saturating_add(RocksDbWeight::get().reads(5)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Alliance Members (r:1 w:0) - // Storage: AllianceMotion Voting (r:1 w:1) - // Storage: AllianceMotion Members (r:1 w:0) - // Storage: AllianceMotion Prime (r:1 w:0) - // Storage: AllianceMotion Proposals (r:1 w:1) - // Storage: AllianceMotion ProposalOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `577 + m * (96 ±0) + p * (36 ±0)` + // Estimated: `6676 + m * (97 ±0) + p * (36 ±0)` + // Minimum execution time: 46_794_000 picoseconds. + Weight::from_parts(43_092_958, 6676) + // Standard Error: 1_273 + .saturating_add(Weight::from_parts(71_054, 0).saturating_mul(m.into())) + // Standard Error: 1_257 + .saturating_add(Weight::from_parts(152_820, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 97).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into())) + } + /// Storage: Alliance Members (r:1 w:0) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Voting (r:1 w:1) + /// Proof Skipped: AllianceMotion Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: AllianceMotion Members (r:1 w:0) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Prime (r:1 w:0) + /// Proof Skipped: AllianceMotion Prime (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Proposals (r:1 w:1) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion ProposalOf (r:0 w:1) + /// Proof Skipped: AllianceMotion ProposalOf (max_values: None, max_size: None, mode: Measured) /// The range of component `b` is `[1, 1024]`. /// The range of component `m` is `[5, 100]`. /// The range of component `p` is `[1, 100]`. - fn close_approved(_b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 53_665 nanoseconds. - Weight::from_ref_time(44_914_913) - // Standard Error: 2_244 - .saturating_add(Weight::from_ref_time(105_848).saturating_mul(m.into())) - // Standard Error: 2_164 - .saturating_add(Weight::from_ref_time(190_830).saturating_mul(p.into())) - .saturating_add(RocksDbWeight::get().reads(5)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Alliance Members (r:2 w:2) - // Storage: AllianceMotion Members (r:1 w:1) + fn close_approved(b: u32, m: u32, p: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `684 + m * (96 ±0) + p * (35 ±0)` + // Estimated: `6676 + m * (97 ±0) + p * (36 ±0)` + // Minimum execution time: 47_338_000 picoseconds. + Weight::from_parts(41_257_479, 6676) + // Standard Error: 119 + .saturating_add(Weight::from_parts(1_019, 0).saturating_mul(b.into())) + // Standard Error: 1_277 + .saturating_add(Weight::from_parts(78_453, 0).saturating_mul(m.into())) + // Standard Error: 1_231 + .saturating_add(Weight::from_parts(150_991, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 97).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into())) + } + /// Storage: Alliance Members (r:2 w:2) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Members (r:1 w:1) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. /// The range of component `z` is `[0, 100]`. fn init_members(m: u32, z: u32, ) -> Weight { - // Minimum execution time: 46_486 nanoseconds. - Weight::from_ref_time(34_596_922) - // Standard Error: 1_807 - .saturating_add(Weight::from_ref_time(143_225).saturating_mul(m.into())) - // Standard Error: 1_786 - .saturating_add(Weight::from_ref_time(125_729).saturating_mul(z.into())) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Alliance Members (r:2 w:2) - // Storage: AllianceMotion Proposals (r:1 w:0) - // Storage: Alliance DepositOf (r:101 w:50) - // Storage: System Account (r:50 w:50) - // Storage: AllianceMotion Members (r:0 w:1) - // Storage: AllianceMotion Prime (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `217` + // Estimated: `12362` + // Minimum execution time: 35_012_000 picoseconds. + Weight::from_parts(24_288_079, 12362) + // Standard Error: 878 + .saturating_add(Weight::from_parts(153_615, 0).saturating_mul(m.into())) + // Standard Error: 867 + .saturating_add(Weight::from_parts(129_307, 0).saturating_mul(z.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Alliance Members (r:2 w:2) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Proposals (r:1 w:0) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Alliance DepositOf (r:200 w:50) + /// Proof: Alliance DepositOf (max_values: None, max_size: Some(64), added: 2539, mode: MaxEncodedLen) + /// Storage: System Account (r:50 w:50) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: AllianceMotion Members (r:0 w:1) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Prime (r:0 w:1) + /// Proof Skipped: AllianceMotion Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `x` is `[1, 100]`. /// The range of component `y` is `[0, 100]`. /// The range of component `z` is `[0, 50]`. fn disband(x: u32, y: u32, z: u32, ) -> Weight { - // Minimum execution time: 251_640 nanoseconds. - Weight::from_ref_time(252_446_000) - // Standard Error: 20_419 - .saturating_add(Weight::from_ref_time(486_667).saturating_mul(x.into())) - // Standard Error: 20_320 - .saturating_add(Weight::from_ref_time(498_890).saturating_mul(y.into())) - // Standard Error: 40_604 - .saturating_add(Weight::from_ref_time(9_411_242).saturating_mul(z.into())) - .saturating_add(RocksDbWeight::get().reads(3)) + // Proof Size summary in bytes: + // Measured: `0 + x * (50 ±0) + y * (51 ±0) + z * (251 ±0)` + // Estimated: `12362 + x * (2539 ±0) + y * (2539 ±0) + z * (2603 ±1)` + // Minimum execution time: 309_235_000 picoseconds. + Weight::from_parts(311_279_000, 12362) + // Standard Error: 26_510 + .saturating_add(Weight::from_parts(543_475, 0).saturating_mul(x.into())) + // Standard Error: 26_382 + .saturating_add(Weight::from_parts(603_169, 0).saturating_mul(y.into())) + // Standard Error: 52_716 + .saturating_add(Weight::from_parts(16_264_836, 0).saturating_mul(z.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(x.into()))) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(y.into()))) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(z.into()))) - .saturating_add(RocksDbWeight::get().writes(4)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(z.into()))) + .saturating_add(Weight::from_parts(0, 2539).saturating_mul(x.into())) + .saturating_add(Weight::from_parts(0, 2539).saturating_mul(y.into())) + .saturating_add(Weight::from_parts(0, 2603).saturating_mul(z.into())) } - // Storage: Alliance Rule (r:0 w:1) + /// Storage: Alliance Rule (r:0 w:1) + /// Proof: Alliance Rule (max_values: Some(1), max_size: Some(87), added: 582, mode: MaxEncodedLen) fn set_rule() -> Weight { - // Minimum execution time: 18_867 nanoseconds. - Weight::from_ref_time(19_155_000) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Alliance Announcements (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_833_000 picoseconds. + Weight::from_parts(9_313_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Alliance Announcements (r:1 w:1) + /// Proof: Alliance Announcements (max_values: Some(1), max_size: Some(8702), added: 9197, mode: MaxEncodedLen) fn announce() -> Weight { - // Minimum execution time: 21_942 nanoseconds. - Weight::from_ref_time(22_319_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Alliance Announcements (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `246` + // Estimated: `10187` + // Minimum execution time: 12_231_000 picoseconds. + Weight::from_parts(12_761_000, 10187) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Alliance Announcements (r:1 w:1) + /// Proof: Alliance Announcements (max_values: Some(1), max_size: Some(8702), added: 9197, mode: MaxEncodedLen) fn remove_announcement() -> Weight { - // Minimum execution time: 23_419 nanoseconds. - Weight::from_ref_time(23_854_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Alliance Members (r:3 w:1) - // Storage: Alliance UnscrupulousAccounts (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Alliance DepositOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `319` + // Estimated: `10187` + // Minimum execution time: 13_079_000 picoseconds. + Weight::from_parts(13_612_000, 10187) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Alliance Members (r:3 w:1) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: Alliance UnscrupulousAccounts (r:1 w:0) + /// Proof: Alliance UnscrupulousAccounts (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Alliance DepositOf (r:0 w:1) + /// Proof: Alliance DepositOf (max_values: None, max_size: Some(64), added: 2539, mode: MaxEncodedLen) fn join_alliance() -> Weight { - // Minimum execution time: 53_016 nanoseconds. - Weight::from_ref_time(53_768_000) - .saturating_add(RocksDbWeight::get().reads(5)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Alliance Members (r:3 w:1) - // Storage: Alliance UnscrupulousAccounts (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `468` + // Estimated: `18048` + // Minimum execution time: 44_574_000 picoseconds. + Weight::from_parts(46_157_000, 18048) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Alliance Members (r:3 w:1) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: Alliance UnscrupulousAccounts (r:1 w:0) + /// Proof: Alliance UnscrupulousAccounts (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) fn nominate_ally() -> Weight { - // Minimum execution time: 40_019 nanoseconds. - Weight::from_ref_time(41_087_000) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Alliance Members (r:2 w:2) - // Storage: AllianceMotion Proposals (r:1 w:0) - // Storage: AllianceMotion Members (r:0 w:1) - // Storage: AllianceMotion Prime (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `367` + // Estimated: `18048` + // Minimum execution time: 26_114_000 picoseconds. + Weight::from_parts(27_069_000, 18048) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Alliance Members (r:2 w:2) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Proposals (r:1 w:0) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Members (r:0 w:1) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Prime (r:0 w:1) + /// Proof Skipped: AllianceMotion Prime (max_values: Some(1), max_size: None, mode: Measured) fn elevate_ally() -> Weight { - // Minimum execution time: 35_973 nanoseconds. - Weight::from_ref_time(36_365_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Alliance Members (r:4 w:2) - // Storage: AllianceMotion Proposals (r:1 w:0) - // Storage: AllianceMotion Members (r:0 w:1) - // Storage: AllianceMotion Prime (r:0 w:1) - // Storage: Alliance RetiringMembers (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `443` + // Estimated: `12362` + // Minimum execution time: 25_882_000 picoseconds. + Weight::from_parts(26_923_000, 12362) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Alliance Members (r:4 w:2) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Proposals (r:1 w:0) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Members (r:0 w:1) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Prime (r:0 w:1) + /// Proof Skipped: AllianceMotion Prime (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Alliance RetiringMembers (r:0 w:1) + /// Proof: Alliance RetiringMembers (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) fn give_retirement_notice() -> Weight { - // Minimum execution time: 44_949 nanoseconds. - Weight::from_ref_time(46_284_000) - .saturating_add(RocksDbWeight::get().reads(5)) - .saturating_add(RocksDbWeight::get().writes(5)) - } - // Storage: Alliance RetiringMembers (r:1 w:1) - // Storage: Alliance Members (r:1 w:1) - // Storage: Alliance DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `443` + // Estimated: `23734` + // Minimum execution time: 34_112_000 picoseconds. + Weight::from_parts(35_499_000, 23734) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: Alliance RetiringMembers (r:1 w:1) + /// Proof: Alliance RetiringMembers (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: Alliance Members (r:1 w:1) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: Alliance DepositOf (r:1 w:1) + /// Proof: Alliance DepositOf (max_values: None, max_size: Some(64), added: 2539, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn retire() -> Weight { - // Minimum execution time: 45_989 nanoseconds. - Weight::from_ref_time(46_586_000) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Alliance Members (r:3 w:1) - // Storage: AllianceMotion Proposals (r:1 w:0) - // Storage: Alliance DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: AllianceMotion Members (r:0 w:1) - // Storage: AllianceMotion Prime (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `687` + // Estimated: `6676` + // Minimum execution time: 41_239_000 picoseconds. + Weight::from_parts(42_764_000, 6676) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Alliance Members (r:3 w:1) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Proposals (r:1 w:0) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Alliance DepositOf (r:1 w:1) + /// Proof: Alliance DepositOf (max_values: None, max_size: Some(64), added: 2539, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: AllianceMotion Members (r:0 w:1) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Prime (r:0 w:1) + /// Proof Skipped: AllianceMotion Prime (max_values: Some(1), max_size: None, mode: Measured) fn kick_member() -> Weight { - // Minimum execution time: 67_333 nanoseconds. - Weight::from_ref_time(68_302_000) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(5)) - } - // Storage: Alliance UnscrupulousAccounts (r:1 w:1) - // Storage: Alliance UnscrupulousWebsites (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `707` + // Estimated: `18048` + // Minimum execution time: 68_071_000 picoseconds. + Weight::from_parts(71_808_000, 18048) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: Alliance UnscrupulousAccounts (r:1 w:1) + /// Proof: Alliance UnscrupulousAccounts (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: Alliance UnscrupulousWebsites (r:1 w:1) + /// Proof: Alliance UnscrupulousWebsites (max_values: Some(1), max_size: Some(25702), added: 26197, mode: MaxEncodedLen) /// The range of component `n` is `[0, 100]`. /// The range of component `l` is `[0, 255]`. fn add_unscrupulous_items(n: u32, l: u32, ) -> Weight { - // Minimum execution time: 16_911 nanoseconds. - Weight::from_ref_time(17_204_000) - // Standard Error: 2_688 - .saturating_add(Weight::from_ref_time(1_324_527).saturating_mul(n.into())) - // Standard Error: 1_052 - .saturating_add(Weight::from_ref_time(73_828).saturating_mul(l.into())) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Alliance UnscrupulousAccounts (r:1 w:1) - // Storage: Alliance UnscrupulousWebsites (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `246` + // Estimated: `27187` + // Minimum execution time: 7_006_000 picoseconds. + Weight::from_parts(7_253_000, 27187) + // Standard Error: 3_403 + .saturating_add(Weight::from_parts(1_680_082, 0).saturating_mul(n.into())) + // Standard Error: 1_333 + .saturating_add(Weight::from_parts(72_943, 0).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Alliance UnscrupulousAccounts (r:1 w:1) + /// Proof: Alliance UnscrupulousAccounts (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: Alliance UnscrupulousWebsites (r:1 w:1) + /// Proof: Alliance UnscrupulousWebsites (max_values: Some(1), max_size: Some(25702), added: 26197, mode: MaxEncodedLen) /// The range of component `n` is `[0, 100]`. /// The range of component `l` is `[0, 255]`. fn remove_unscrupulous_items(n: u32, l: u32, ) -> Weight { - // Minimum execution time: 17_117 nanoseconds. - Weight::from_ref_time(17_291_000) - // Standard Error: 166_132 - .saturating_add(Weight::from_ref_time(13_173_211).saturating_mul(n.into())) - // Standard Error: 65_064 - .saturating_add(Weight::from_ref_time(478_626).saturating_mul(l.into())) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Alliance Members (r:3 w:2) - // Storage: AllianceMotion Proposals (r:1 w:0) - // Storage: AllianceMotion Members (r:0 w:1) - // Storage: AllianceMotion Prime (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `0 + l * (100 ±0) + n * (289 ±0)` + // Estimated: `27187` + // Minimum execution time: 7_292_000 picoseconds. + Weight::from_parts(7_629_000, 27187) + // Standard Error: 176_225 + .saturating_add(Weight::from_parts(16_646_429, 0).saturating_mul(n.into())) + // Standard Error: 69_017 + .saturating_add(Weight::from_parts(310_978, 0).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Alliance Members (r:3 w:2) + /// Proof: Alliance Members (max_values: None, max_size: Some(3211), added: 5686, mode: MaxEncodedLen) + /// Storage: AllianceMotion Proposals (r:1 w:0) + /// Proof Skipped: AllianceMotion Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Members (r:0 w:1) + /// Proof Skipped: AllianceMotion Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: AllianceMotion Prime (r:0 w:1) + /// Proof Skipped: AllianceMotion Prime (max_values: Some(1), max_size: None, mode: Measured) fn abdicate_fellow_status() -> Weight { - // Minimum execution time: 43_347 nanoseconds. - Weight::from_ref_time(43_986_000) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(4)) + // Proof Size summary in bytes: + // Measured: `443` + // Estimated: `18048` + // Minimum execution time: 31_798_000 picoseconds. + Weight::from_parts(33_463_000, 18048) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } } diff --git a/frame/asset-conversion/Cargo.toml b/frame/asset-conversion/Cargo.toml new file mode 100644 index 000000000000..0a7f48b0047f --- /dev/null +++ b/frame/asset-conversion/Cargo.toml @@ -0,0 +1,50 @@ +[package] +name = "pallet-asset-conversion" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME asset conversion pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../primitives/arithmetic" } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +pallet-assets = { version = "4.0.0-dev", path = "../assets" } +primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "scale-info", "num-traits"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-std/std", + "sp-runtime/std", + "sp-arithmetic/std" +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/asset-conversion/README.md b/frame/asset-conversion/README.md new file mode 100644 index 000000000000..e895db5e83ad --- /dev/null +++ b/frame/asset-conversion/README.md @@ -0,0 +1,25 @@ +# asset-conversion + +## A swap pallet + +This pallet allows assets to be converted from one type to another by means of a constant product formula. +The pallet based is based on [Uniswap V2](https://github.com/Uniswap/v2-core) logic. + +### Overview + +This pallet allows you to: + + - create a liquidity pool for 2 assets + - provide the liquidity and receive back an LP token + - exchange the LP token back to assets + - swap 2 assets if there is a pool created + - query for an exchange price via a new runtime call endpoint + - query the size of a liquidity pool. + +Please see the rust module documentation for full details: + +`cargo doc -p pallet-asset-conversion --open` + +### License + +License: Apache-2.0 diff --git a/frame/asset-conversion/src/benchmarking.rs b/frame/asset-conversion/src/benchmarking.rs new file mode 100644 index 000000000000..54c634c0f19b --- /dev/null +++ b/frame/asset-conversion/src/benchmarking.rs @@ -0,0 +1,329 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Asset Conversion pallet benchmarking. + +use super::*; +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_support::{ + assert_ok, + storage::bounded_vec::BoundedVec, + traits::{ + fungible::{Inspect as InspectFungible, Mutate as MutateFungible, Unbalanced}, + fungibles::{Create, Inspect, Mutate}, + }, +}; +use frame_system::RawOrigin as SystemOrigin; +use sp_core::Get; +use sp_runtime::traits::{Bounded, StaticLookup}; +use sp_std::{ops::Div, prelude::*}; + +use crate::Pallet as AssetConversion; + +const INITIAL_ASSET_BALANCE: u128 = 1_000_000_000_000; +type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; +type BalanceOf = + <::Currency as InspectFungible<::AccountId>>::Balance; + +fn get_lp_token_id() -> T::PoolAssetId +where + T::PoolAssetId: Into, +{ + let next_id: u32 = AssetConversion::::get_next_pool_asset_id().into(); + (next_id - 1).into() +} + +fn create_asset(asset: &T::MultiAssetId) -> (T::AccountId, AccountIdLookupOf) +where + T::AssetBalance: From, + T::Currency: Unbalanced, + T::Assets: Create + Mutate, +{ + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + if let Ok(asset_id) = T::MultiAssetIdConverter::try_convert(asset) { + T::Currency::set_balance(&caller, BalanceOf::::max_value().div(1000u32.into())); + assert_ok!(T::Assets::create(asset_id.clone(), caller.clone(), true, 1.into())); + assert_ok!(T::Assets::mint_into(asset_id, &caller, INITIAL_ASSET_BALANCE.into())); + } + (caller, caller_lookup) +} + +fn create_asset_and_pool( + asset1: &T::MultiAssetId, + asset2: &T::MultiAssetId, +) -> (T::PoolAssetId, T::AccountId, AccountIdLookupOf) +where + T::AssetBalance: From, + T::Currency: Unbalanced, + T::Assets: Create + Mutate, + T::PoolAssetId: Into, +{ + let (_, _) = create_asset::(asset1); + let (caller, caller_lookup) = create_asset::(asset2); + + assert_ok!(AssetConversion::::create_pool( + SystemOrigin::Signed(caller.clone()).into(), + asset1.clone(), + asset2.clone() + )); + let lp_token = get_lp_token_id::(); + + (lp_token, caller, caller_lookup) +} + +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); +} + +benchmarks! { + where_clause { + where + T::AssetBalance: From + Into, + T::Currency: Unbalanced, + T::Balance: From + Into, + T::Assets: Create + Mutate, + T::PoolAssetId: Into, + } + + create_pool { + let asset1 = T::MultiAssetIdConverter::get_native(); + let asset2 = T::MultiAssetIdConverter::into_multiasset_id(&T::BenchmarkHelper::asset_id(0_u32)); + let (caller, _) = create_asset::(&asset2); + }: _(SystemOrigin::Signed(caller.clone()), asset1.clone(), asset2.clone()) + verify { + let lp_token = get_lp_token_id::(); + let pool_id = (asset1.clone(), asset2.clone()); + assert_last_event::(Event::PoolCreated { creator: caller.clone(), pool_id, lp_token }.into()); + } + + add_liquidity { + let asset1 = T::MultiAssetIdConverter::get_native(); + let asset2 = T::MultiAssetIdConverter::into_multiasset_id(&T::BenchmarkHelper::asset_id(0)); + let (lp_token, caller, _) = create_asset_and_pool::(&asset1, &asset2); + let ed: u128 = T::Currency::minimum_balance().into(); + let add_amount = 1000 + ed; + }: _(SystemOrigin::Signed(caller.clone()), asset1.clone(), asset2.clone(), add_amount.into(), 1000.into(), 0.into(), 0.into(), caller.clone()) + verify { + let pool_id = (asset1.clone(), asset2.clone()); + let lp_minted = AssetConversion::::calc_lp_amount_for_zero_supply(&add_amount.into(), &1000.into()).unwrap().into(); + assert_eq!( + T::PoolAssets::balance(lp_token, &caller), + lp_minted.into() + ); + assert_eq!( + T::Currency::balance(&AssetConversion::::get_pool_account(&pool_id)), + add_amount.into() + ); + assert_eq!( + T::Assets::balance(T::BenchmarkHelper::asset_id(0), &AssetConversion::::get_pool_account(&pool_id)), + 1000.into() + ); + } + + remove_liquidity { + let asset1 = T::MultiAssetIdConverter::get_native(); + let asset2 = T::MultiAssetIdConverter::into_multiasset_id(&T::BenchmarkHelper::asset_id(0)); + let (lp_token, caller, _) = create_asset_and_pool::(&asset1, &asset2); + let ed: u128 = T::Currency::minimum_balance().into(); + let add_amount = 100 * ed; + let lp_minted = AssetConversion::::calc_lp_amount_for_zero_supply(&add_amount.into(), &1000.into()).unwrap().into(); + let remove_lp_amount = lp_minted.checked_div(10).unwrap(); + + AssetConversion::::add_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + asset1.clone(), + asset2.clone(), + add_amount.into(), + 1000.into(), + 0.into(), + 0.into(), + caller.clone(), + )?; + let total_supply = >::total_issuance(lp_token.clone()); + }: _(SystemOrigin::Signed(caller.clone()), asset1, asset2, remove_lp_amount.into(), 0.into(), 0.into(), caller.clone()) + verify { + let new_total_supply = >::total_issuance(lp_token.clone()); + assert_eq!( + new_total_supply, + total_supply - remove_lp_amount.into() + ); + } + + swap_exact_tokens_for_tokens { + let native = T::MultiAssetIdConverter::get_native(); + let asset1 = T::MultiAssetIdConverter::into_multiasset_id(&T::BenchmarkHelper::asset_id(1)); + let asset2 = T::MultiAssetIdConverter::into_multiasset_id(&T::BenchmarkHelper::asset_id(2)); + let (_, caller, _) = create_asset_and_pool::(&native, &asset1); + let (_, _) = create_asset::(&asset2); + let ed: u128 = T::Currency::minimum_balance().into(); + + AssetConversion::::add_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + native.clone(), + asset1.clone(), + (100 * ed).into(), + 200.into(), + 0.into(), + 0.into(), + caller.clone(), + )?; + + let path; + // if we only allow the native-asset pools, then the worst case scenario would be to swap + // asset1-native-asset2 + if !T::AllowMultiAssetPools::get() { + AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), native.clone(), asset2.clone())?; + AssetConversion::::add_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + native.clone(), + asset2.clone(), + (500 * ed).into(), + 1000.into(), + 0.into(), + 0.into(), + caller.clone(), + )?; + path = vec![asset1.clone(), native.clone(), asset2.clone()]; + } else { + let asset3 = T::MultiAssetIdConverter::into_multiasset_id(&T::BenchmarkHelper::asset_id(3)); + AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset1.clone(), asset2.clone())?; + let (_, _) = create_asset::(&asset3); + AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset2.clone(), asset3.clone())?; + + AssetConversion::::add_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + asset1.clone(), + asset2.clone(), + 200.into(), + 2000.into(), + 0.into(), + 0.into(), + caller.clone(), + )?; + AssetConversion::::add_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + asset2.clone(), + asset3.clone(), + 2000.into(), + 2000.into(), + 0.into(), + 0.into(), + caller.clone(), + )?; + path = vec![native.clone(), asset1.clone(), asset2.clone(), asset3.clone()]; + } + + let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap(); + let native_balance = T::Currency::balance(&caller); + let asset1_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(1), &caller); + }: _(SystemOrigin::Signed(caller.clone()), path, ed.into(), 1.into(), caller.clone(), false) + verify { + if !T::AllowMultiAssetPools::get() { + let new_asset1_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(1), &caller); + assert_eq!(new_asset1_balance, asset1_balance - 100.into()); + } else { + let new_native_balance = T::Currency::balance(&caller); + assert_eq!(new_native_balance, native_balance - ed.into()); + } + } + + swap_tokens_for_exact_tokens { + let native = T::MultiAssetIdConverter::get_native(); + let asset1 = T::MultiAssetIdConverter::into_multiasset_id(&T::BenchmarkHelper::asset_id(1)); + let asset2 = T::MultiAssetIdConverter::into_multiasset_id(&T::BenchmarkHelper::asset_id(2)); + let (_, caller, _) = create_asset_and_pool::(&native, &asset1); + let (_, _) = create_asset::(&asset2); + let ed: u128 = T::Currency::minimum_balance().into(); + let add_native = 1000 + ed; + + AssetConversion::::add_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + native.clone(), + asset1.clone(), + add_native.into(), + 200.into(), + 0.into(), + 0.into(), + caller.clone(), + )?; + + let path; + // if we only allow the native-asset pools, then the worst case scenario would be to swap + // asset1-native-asset2 + if !T::AllowMultiAssetPools::get() { + AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), native.clone(), asset2.clone())?; + AssetConversion::::add_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + native.clone(), + asset2.clone(), + (500 + ed).into(), + 1000.into(), + 0.into(), + 0.into(), + caller.clone(), + )?; + path = vec![asset1.clone(), native.clone(), asset2.clone()]; + } else { + AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset1.clone(), asset2.clone())?; + let asset3 = T::MultiAssetIdConverter::into_multiasset_id(&T::BenchmarkHelper::asset_id(3)); + let (_, _) = create_asset::(&asset3); + AssetConversion::::create_pool(SystemOrigin::Signed(caller.clone()).into(), asset2.clone(), asset3.clone())?; + + AssetConversion::::add_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + asset1.clone(), + asset2.clone(), + 200.into(), + 2000.into(), + 0.into(), + 0.into(), + caller.clone(), + )?; + AssetConversion::::add_liquidity( + SystemOrigin::Signed(caller.clone()).into(), + asset2.clone(), + asset3.clone(), + 2000.into(), + 2000.into(), + 0.into(), + 0.into(), + caller.clone(), + )?; + path = vec![native.clone(), asset1.clone(), asset2.clone(), asset3.clone()]; + } + + let path: BoundedVec<_, T::MaxSwapPathLength> = BoundedVec::try_from(path).unwrap(); + let asset2_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(2), &caller); + let asset3_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(3), &caller); + }: _(SystemOrigin::Signed(caller.clone()), path.clone(), 100.into(), add_native.into(), caller.clone(), false) + verify { + if !T::AllowMultiAssetPools::get() { + let new_asset2_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(2), &caller); + assert_eq!(new_asset2_balance, asset2_balance + 100.into()); + } else { + let new_asset3_balance = T::Assets::balance(T::BenchmarkHelper::asset_id(3), &caller); + assert_eq!(new_asset3_balance, asset3_balance + 100.into()); + } + } + + impl_benchmark_test_suite!(AssetConversion, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/asset-conversion/src/lib.rs b/frame/asset-conversion/src/lib.rs new file mode 100644 index 000000000000..73f193c7ff55 --- /dev/null +++ b/frame/asset-conversion/src/lib.rs @@ -0,0 +1,1268 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +//! # Substrate Asset Conversion pallet +//! +//! Substrate Asset Conversion pallet based on the [Uniswap V2](https://github.com/Uniswap/v2-core) logic. +//! +//! ## Overview +//! +//! This pallet allows you to: +//! +//! - [create a liquidity pool](`Pallet::create_pool()`) for 2 assets +//! - [provide the liquidity](`Pallet::add_liquidity()`) and receive back an LP token +//! - [exchange the LP token back to assets](`Pallet::remove_liquidity()`) +//! - [swap a specific amount of assets for another](`Pallet::swap_exact_tokens_for_tokens()`) if +//! there is a pool created, or +//! - [swap some assets for a specific amount of +//! another](`Pallet::swap_tokens_for_exact_tokens()`). +//! - [query for an exchange price](`AssetConversionApi::quote_price_exact_tokens_for_tokens`) via +//! a runtime call endpoint +//! - [query the size of a liquidity pool](`AssetConversionApi::get_reserves`) via a runtime api +//! endpoint. +//! +//! The `quote_price_exact_tokens_for_tokens` and `quote_price_tokens_for_exact_tokens` functions +//! both take a path parameter of the route to take. If you want to swap from native asset to +//! non-native asset 1, you would pass in a path of `[DOT, 1]` or `[1, DOT]`. If you want to swap +//! from non-native asset 1 to non-native asset 2, you would pass in a path of `[1, DOT, 2]`. +//! +//! (For an example of configuring this pallet to use `MultiLocation` as an asset id, see the +//! cumulus repo). +//! +//! Here is an example `state_call` that asks for a quote of a pool of native versus asset 1: +//! +//! ```text +//! curl -sS -H "Content-Type: application/json" -d \ +//! '{"id":1, "jsonrpc":"2.0", "method": "state_call", "params": ["AssetConversionApi_quote_price_tokens_for_exact_tokens", "0x0101000000000000000000000011000000000000000000"]}' \ +//! http://localhost:9933/ +//! ``` +//! (This can be run against the kitchen sync node in the `node` folder of this repo.) +#![deny(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] +use frame_support::traits::{DefensiveOption, Incrementable}; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +mod types; +pub mod weights; + +#[cfg(test)] +mod tests; + +#[cfg(test)] +mod mock; + +use codec::Codec; +use frame_support::{ + ensure, + traits::tokens::{AssetId, Balance}, +}; +use frame_system::{ + ensure_signed, + pallet_prelude::{BlockNumberFor, OriginFor}, +}; +pub use pallet::*; +use sp_arithmetic::traits::Unsigned; +use sp_runtime::{ + traits::{ + CheckedAdd, CheckedDiv, CheckedMul, CheckedSub, Ensure, MaybeDisplay, TrailingZeroInput, + }, + DispatchError, +}; +use sp_std::prelude::*; +pub use types::*; +pub use weights::WeightInfo; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{ + pallet_prelude::*, + traits::{ + fungible::{Inspect as InspectFungible, Mutate as MutateFungible}, + fungibles::{Create, Inspect, Mutate}, + tokens::{ + Fortitude::Polite, + Precision::Exact, + Preservation::{Expendable, Preserve}, + }, + AccountTouch, ContainsPair, + }, + BoundedBTreeSet, PalletId, + }; + use sp_arithmetic::Permill; + use sp_runtime::{ + traits::{IntegerSquareRoot, One, Zero}, + Saturating, + }; + + #[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: InspectFungible + + MutateFungible; + + /// The `Currency::Balance` type of the native currency. + type Balance: Balance; + + /// The type used to describe the amount of fractions converted into assets. + type AssetBalance: Balance; + + /// A type used for conversions between `Balance` and `AssetBalance`. + type HigherPrecisionBalance: IntegerSquareRoot + + One + + Ensure + + Unsigned + + From + + From + + From + + TryInto + + TryInto; + + /// Identifier for the class of non-native asset. + /// Note: A `From` bound here would prevent `MultiLocation` from being used as an + /// `AssetId`. + type AssetId: AssetId + PartialOrd; + + /// Type that identifies either the native currency or a token class from `Assets`. + type MultiAssetId: AssetId + Ord + From; + + /// Type to convert an `AssetId` into `MultiAssetId`. + type MultiAssetIdConverter: MultiAssetIdConverter; + + /// `AssetId` to address the lp tokens by. + type PoolAssetId: AssetId + PartialOrd + Incrementable + From; + + /// Registry for the assets. + type Assets: Inspect + + Mutate + + AccountTouch + + ContainsPair; + + /// Registry for the lp tokens. Ideally only this pallet should have create permissions on + /// the assets. + type PoolAssets: Inspect + + Create + + Mutate + + AccountTouch; + + /// A % the liquidity providers will take of every swap. Represents 10ths of a percent. + #[pallet::constant] + type LPFee: Get; + + /// A one-time fee to setup the pool. + #[pallet::constant] + type PoolSetupFee: Get; + + /// An account that receives the pool setup fee. + type PoolSetupFeeReceiver: Get; + + /// A fee to withdraw the liquidity. + #[pallet::constant] + type LiquidityWithdrawalFee: Get; + + /// The minimum LP token amount that could be minted. Ameliorates rounding errors. + #[pallet::constant] + type MintMinLiquidity: Get; + + /// The max number of hops in a swap. + #[pallet::constant] + type MaxSwapPathLength: Get; + + /// The pallet's id, used for deriving its sovereign account ID. + #[pallet::constant] + type PalletId: Get; + + /// A setting to allow creating pools with both non-native assets. + #[pallet::constant] + type AllowMultiAssetPools: Get; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// The benchmarks need a way to create asset ids from u32s. + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper: BenchmarkHelper; + } + + /// Map from `PoolAssetId` to `PoolInfo`. This establishes whether a pool has been officially + /// created rather than people sending tokens directly to a pool's public account. + #[pallet::storage] + pub type Pools = + StorageMap<_, Blake2_128Concat, PoolIdOf, PoolInfo, OptionQuery>; + + /// Stores the `PoolAssetId` that is going to be used for the next lp token. + /// This gets incremented whenever a new lp pool is created. + #[pallet::storage] + pub type NextPoolAssetId = StorageValue<_, T::PoolAssetId, OptionQuery>; + + // 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 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. + pool_id: PoolIdOf, + /// The id of the liquidity tokens that will be minted when assets are added to this + /// pool. + lp_token: T::PoolAssetId, + }, + + /// A successful call of the `AddLiquidity` extrinsic will create this event. + LiquidityAdded { + /// The account that the liquidity was taken from. + who: T::AccountId, + /// The account that the liquidity tokens were minted to. + mint_to: T::AccountId, + /// The pool id of the pool that the liquidity was added to. + pool_id: PoolIdOf, + /// The amount of the first asset that was added to the pool. + amount1_provided: T::AssetBalance, + /// The amount of the second asset that was added to the pool. + amount2_provided: T::AssetBalance, + /// The id of the lp token that was minted. + lp_token: T::PoolAssetId, + /// The amount of lp tokens that were minted of that id. + lp_token_minted: T::AssetBalance, + }, + + /// A successful call of the `RemoveLiquidity` extrinsic will create this event. + LiquidityRemoved { + /// The account that the liquidity tokens were burned from. + who: T::AccountId, + /// The account that the assets were transferred to. + withdraw_to: T::AccountId, + /// The pool id that the liquidity was removed from. + pool_id: PoolIdOf, + /// The amount of the first asset that was removed from the pool. + amount1: T::AssetBalance, + /// The amount of the second asset that was removed from the pool. + amount2: T::AssetBalance, + /// The id of the lp token that was burned. + lp_token: T::PoolAssetId, + /// The amount of lp tokens that were burned of that id. + lp_token_burned: T::AssetBalance, + /// Liquidity withdrawal fee (%). + withdrawal_fee: Permill, + }, + /// Assets have been converted from one to another. Both `SwapExactTokenForToken` + /// and `SwapTokenForExactToken` will generate this event. + SwapExecuted { + /// Which account was the instigator of the swap. + who: T::AccountId, + /// The account that the assets were transferred to. + send_to: T::AccountId, + /// The route of asset ids that the swap went through. + /// E.g. A -> Dot -> B + path: BoundedVec, + /// The amount of the first asset that was swapped. + amount_in: T::AssetBalance, + /// The amount of the second asset that was received. + amount_out: T::AssetBalance, + }, + /// An amount has been transferred from one account to another. + Transfer { + /// The account that the assets were transferred from. + from: T::AccountId, + /// The account that the assets were transferred to. + to: T::AccountId, + /// The asset that was transferred. + asset: T::MultiAssetId, + /// The amount of the asset that was transferred. + amount: T::AssetBalance, + }, + } + + #[pallet::error] + pub enum Error { + /// Provided assets are equal. + EqualAssets, + /// Pool already exists. + PoolExists, + /// Desired amount can't be zero. + WrongDesiredAmount, + /// Provided amount should be greater than or equal to the existential deposit/asset's + /// minimal amount. + AmountOneLessThanMinimal, + /// Provided amount should be greater than or equal to the existential deposit/asset's + /// minimal amount. + AmountTwoLessThanMinimal, + /// Reserve needs to always be greater than or equal to the existential deposit/asset's + /// minimal amount. + ReserveLeftLessThanMinimal, + /// Desired amount can't be equal to the pool reserve. + AmountOutTooHigh, + /// The pool doesn't exist. + PoolNotFound, + /// An overflow happened. + Overflow, + /// The minimal amount requirement for the first token in the pair wasn't met. + AssetOneDepositDidNotMeetMinimum, + /// The minimal amount requirement for the second token in the pair wasn't met. + AssetTwoDepositDidNotMeetMinimum, + /// The minimal amount requirement for the first token in the pair wasn't met. + AssetOneWithdrawalDidNotMeetMinimum, + /// The minimal amount requirement for the second token in the pair wasn't met. + AssetTwoWithdrawalDidNotMeetMinimum, + /// Optimal calculated amount is less than desired. + OptimalAmountLessThanDesired, + /// Insufficient liquidity minted. + InsufficientLiquidityMinted, + /// Requested liquidity can't be zero. + ZeroLiquidity, + /// Amount can't be zero. + ZeroAmount, + /// Insufficient liquidity in the pool. + InsufficientLiquidity, + /// Calculated amount out is less than provided minimum amount. + ProvidedMinimumNotSufficientForSwap, + /// Provided maximum amount is not sufficient for swap. + ProvidedMaximumNotSufficientForSwap, + /// Only pools with native on one side are valid. + PoolMustContainNativeCurrency, + /// The provided path must consists of 2 assets at least. + InvalidPath, + /// It was not possible to calculate path data. + PathError, + /// The provided path must consists of unique assets. + NonUniquePath, + /// Unable to find an element in an array/vec that should have one-to-one correspondence + /// with another. For example, an array of assets constituting a `path` should have a + /// corresponding array of `amounts` along the path. + CorrespondenceError, + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + assert!( + T::MaxSwapPathLength::get() > 1, + "the `MaxSwapPathLength` should be greater than 1", + ); + } + } + + /// Pallet's callable functions. + #[pallet::call] + impl Pallet { + /// Creates an empty liquidity pool and an associated new `lp_token` asset + /// (the id of which is returned in the `Event::PoolCreated` event). + /// + /// Once a pool is created, someone may [`Pallet::add_liquidity`] to it. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::create_pool())] + pub fn create_pool( + origin: OriginFor, + asset1: T::MultiAssetId, + asset2: T::MultiAssetId, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + ensure!(asset1 != asset2, Error::::EqualAssets); + + let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); + let (asset1, asset2) = pool_id.clone(); + + if !T::AllowMultiAssetPools::get() && !T::MultiAssetIdConverter::is_native(&asset1) { + Err(Error::::PoolMustContainNativeCurrency)?; + } + + ensure!(!Pools::::contains_key(&pool_id), Error::::PoolExists); + + let pool_account = Self::get_pool_account(&pool_id); + frame_system::Pallet::::inc_providers(&pool_account); + + // pay the setup fee + T::Currency::transfer( + &sender, + &T::PoolSetupFeeReceiver::get(), + T::PoolSetupFee::get(), + Preserve, + )?; + + if let Ok(asset) = T::MultiAssetIdConverter::try_convert(&asset1) { + if !T::Assets::contains(&asset, &pool_account) { + T::Assets::touch(asset, pool_account.clone(), sender.clone())?; + } + } + if let Ok(asset) = T::MultiAssetIdConverter::try_convert(&asset2) { + if !T::Assets::contains(&asset, &pool_account) { + T::Assets::touch(asset, pool_account.clone(), sender.clone())?; + } + } + + let lp_token = NextPoolAssetId::::get().unwrap_or(T::PoolAssetId::initial_value()); + let next_lp_token_id = lp_token.increment(); + NextPoolAssetId::::set(Some(next_lp_token_id)); + + T::PoolAssets::create(lp_token.clone(), pool_account.clone(), false, 1u32.into())?; + T::PoolAssets::touch(lp_token.clone(), pool_account.clone(), sender.clone())?; + + let pool_info = PoolInfo { lp_token: lp_token.clone() }; + Pools::::insert(pool_id.clone(), pool_info); + + Self::deposit_event(Event::PoolCreated { creator: sender, pool_id, lp_token }); + + Ok(()) + } + + /// Provide liquidity into the pool of `asset1` and `asset2`. + /// NOTE: an optimal amount of asset1 and asset2 will be calculated and + /// might be different than the provided `amount1_desired`/`amount2_desired` + /// thus you should provide the min amount you're happy to provide. + /// Params `amount1_min`/`amount2_min` represent that. + /// `mint_to` will be sent the liquidity tokens that represent this share of the pool. + /// + /// Once liquidity is added, someone may successfully call + /// [`Pallet::swap_exact_tokens_for_tokens`] successfully. + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::add_liquidity())] + pub fn add_liquidity( + origin: OriginFor, + asset1: T::MultiAssetId, + asset2: T::MultiAssetId, + amount1_desired: T::AssetBalance, + amount2_desired: T::AssetBalance, + amount1_min: T::AssetBalance, + amount2_min: T::AssetBalance, + mint_to: T::AccountId, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); + // swap params if needed + let (amount1_desired, amount2_desired, amount1_min, amount2_min) = + if pool_id.0 == asset1 { + (amount1_desired, amount2_desired, amount1_min, amount2_min) + } else { + (amount2_desired, amount1_desired, amount2_min, amount1_min) + }; + let (asset1, asset2) = pool_id.clone(); + + ensure!( + amount1_desired > Zero::zero() && amount2_desired > Zero::zero(), + Error::::WrongDesiredAmount + ); + + let maybe_pool = Pools::::get(pool_id.clone()); + let pool = maybe_pool.as_ref().ok_or(Error::::PoolNotFound)?; + + let amount1: T::AssetBalance; + let amount2: T::AssetBalance; + let pool_account = Self::get_pool_account(&pool_id); + let reserve1 = Self::get_balance(&pool_account, &asset1)?; + let reserve2 = Self::get_balance(&pool_account, &asset2)?; + + if reserve1.is_zero() || reserve2.is_zero() { + amount1 = amount1_desired; + amount2 = amount2_desired; + } else { + let amount2_optimal = Self::quote(&amount1_desired, &reserve1, &reserve2)?; + + if amount2_optimal <= amount2_desired { + ensure!( + amount2_optimal >= amount2_min, + Error::::AssetTwoDepositDidNotMeetMinimum + ); + amount1 = amount1_desired; + amount2 = amount2_optimal; + } else { + let amount1_optimal = Self::quote(&amount2_desired, &reserve2, &reserve1)?; + ensure!( + amount1_optimal <= amount1_desired, + Error::::OptimalAmountLessThanDesired + ); + ensure!( + amount1_optimal >= amount1_min, + Error::::AssetOneDepositDidNotMeetMinimum + ); + amount1 = amount1_optimal; + amount2 = amount2_desired; + } + } + + Self::validate_minimal_amount(amount1.saturating_add(reserve1), &asset1) + .map_err(|_| Error::::AmountOneLessThanMinimal)?; + Self::validate_minimal_amount(amount2.saturating_add(reserve2), &asset2) + .map_err(|_| Error::::AmountTwoLessThanMinimal)?; + + Self::transfer(&asset1, &sender, &pool_account, amount1, true)?; + Self::transfer(&asset2, &sender, &pool_account, amount2, true)?; + + let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone()); + + let lp_token_amount: T::AssetBalance; + if total_supply.is_zero() { + lp_token_amount = Self::calc_lp_amount_for_zero_supply(&amount1, &amount2)?; + T::PoolAssets::mint_into( + pool.lp_token.clone(), + &pool_account, + T::MintMinLiquidity::get(), + )?; + } else { + let side1 = Self::mul_div(&amount1, &total_supply, &reserve1)?; + let side2 = Self::mul_div(&amount2, &total_supply, &reserve2)?; + lp_token_amount = side1.min(side2); + } + + ensure!( + lp_token_amount > T::MintMinLiquidity::get(), + Error::::InsufficientLiquidityMinted + ); + + T::PoolAssets::mint_into(pool.lp_token.clone(), &mint_to, lp_token_amount)?; + + Self::deposit_event(Event::LiquidityAdded { + who: sender, + mint_to, + pool_id, + amount1_provided: amount1, + amount2_provided: amount2, + lp_token: pool.lp_token.clone(), + lp_token_minted: lp_token_amount, + }); + + Ok(()) + } + + /// Allows you to remove liquidity by providing the `lp_token_burn` tokens that will be + /// burned in the process. With the usage of `amount1_min_receive`/`amount2_min_receive` + /// it's possible to control the min amount of returned tokens you're happy with. + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::remove_liquidity())] + pub fn remove_liquidity( + origin: OriginFor, + asset1: T::MultiAssetId, + asset2: T::MultiAssetId, + lp_token_burn: T::AssetBalance, + amount1_min_receive: T::AssetBalance, + amount2_min_receive: T::AssetBalance, + withdraw_to: T::AccountId, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); + // swap params if needed + let (amount1_min_receive, amount2_min_receive) = if pool_id.0 == asset1 { + (amount1_min_receive, amount2_min_receive) + } else { + (amount2_min_receive, amount1_min_receive) + }; + let (asset1, asset2) = pool_id.clone(); + + ensure!(lp_token_burn > Zero::zero(), Error::::ZeroLiquidity); + + let maybe_pool = Pools::::get(pool_id.clone()); + let pool = maybe_pool.as_ref().ok_or(Error::::PoolNotFound)?; + + let pool_account = Self::get_pool_account(&pool_id); + let reserve1 = Self::get_balance(&pool_account, &asset1)?; + let reserve2 = Self::get_balance(&pool_account, &asset2)?; + + let total_supply = T::PoolAssets::total_issuance(pool.lp_token.clone()); + let withdrawal_fee_amount = T::LiquidityWithdrawalFee::get() * lp_token_burn; + let lp_redeem_amount = lp_token_burn.saturating_sub(withdrawal_fee_amount); + + let amount1 = Self::mul_div(&lp_redeem_amount, &reserve1, &total_supply)?; + let amount2 = Self::mul_div(&lp_redeem_amount, &reserve2, &total_supply)?; + + ensure!( + !amount1.is_zero() && amount1 >= amount1_min_receive, + Error::::AssetOneWithdrawalDidNotMeetMinimum + ); + ensure!( + !amount2.is_zero() && amount2 >= amount2_min_receive, + Error::::AssetTwoWithdrawalDidNotMeetMinimum + ); + let reserve1_left = reserve1.saturating_sub(amount1); + let reserve2_left = reserve2.saturating_sub(amount2); + Self::validate_minimal_amount(reserve1_left, &asset1) + .map_err(|_| Error::::ReserveLeftLessThanMinimal)?; + Self::validate_minimal_amount(reserve2_left, &asset2) + .map_err(|_| Error::::ReserveLeftLessThanMinimal)?; + + // burn the provided lp token amount that includes the fee + T::PoolAssets::burn_from(pool.lp_token.clone(), &sender, lp_token_burn, Exact, Polite)?; + + Self::transfer(&asset1, &pool_account, &withdraw_to, amount1, false)?; + Self::transfer(&asset2, &pool_account, &withdraw_to, amount2, false)?; + + Self::deposit_event(Event::LiquidityRemoved { + who: sender, + withdraw_to, + pool_id, + amount1, + amount2, + lp_token: pool.lp_token.clone(), + lp_token_burned: lp_token_burn, + withdrawal_fee: T::LiquidityWithdrawalFee::get(), + }); + + Ok(()) + } + + /// Swap the exact amount of `asset1` into `asset2`. + /// `amount_out_min` param allows you to specify the min amount of the `asset2` + /// you're happy to receive. + /// + /// [`AssetConversionApi::quote_price_exact_tokens_for_tokens`] runtime call can be called + /// for a quote. + #[pallet::call_index(3)] + #[pallet::weight(T::WeightInfo::swap_exact_tokens_for_tokens())] + pub fn swap_exact_tokens_for_tokens( + origin: OriginFor, + path: BoundedVec, + amount_in: T::AssetBalance, + amount_out_min: T::AssetBalance, + send_to: T::AccountId, + keep_alive: bool, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + Self::do_swap_exact_tokens_for_tokens( + sender, + path, + amount_in, + Some(amount_out_min), + send_to, + keep_alive, + )?; + Ok(()) + } + + /// Swap any amount of `asset1` to get the exact amount of `asset2`. + /// `amount_in_max` param allows to specify the max amount of the `asset1` + /// you're happy to provide. + /// + /// [`AssetConversionApi::quote_price_tokens_for_exact_tokens`] runtime call can be called + /// for a quote. + #[pallet::call_index(4)] + #[pallet::weight(T::WeightInfo::swap_tokens_for_exact_tokens())] + pub fn swap_tokens_for_exact_tokens( + origin: OriginFor, + path: BoundedVec, + amount_out: T::AssetBalance, + amount_in_max: T::AssetBalance, + send_to: T::AccountId, + keep_alive: bool, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + Self::do_swap_tokens_for_exact_tokens( + sender, + path, + amount_out, + Some(amount_in_max), + send_to, + keep_alive, + )?; + Ok(()) + } + } + + impl Pallet { + /// Swap exactly `amount_in` of asset `path[0]` for asset `path[1]`. + /// If an `amount_out_min` is specified, it will return an error if it is unable to acquire + /// the amount desired. + /// + /// Withdraws the `path[0]` asset from `sender`, deposits the `path[1]` asset to `send_to`, + /// respecting `keep_alive`. + /// + /// If successful, returns the amount of `path[1]` acquired for the `amount_in`. + pub fn do_swap_exact_tokens_for_tokens( + sender: T::AccountId, + path: BoundedVec, + amount_in: T::AssetBalance, + amount_out_min: Option, + send_to: T::AccountId, + keep_alive: bool, + ) -> Result { + ensure!(amount_in > Zero::zero(), Error::::ZeroAmount); + if let Some(amount_out_min) = amount_out_min { + ensure!(amount_out_min > Zero::zero(), Error::::ZeroAmount); + } + + Self::validate_swap_path(&path)?; + + let amounts = Self::get_amounts_out(&amount_in, &path)?; + let amount_out = + *amounts.last().defensive_ok_or("get_amounts_out() returned an empty result")?; + + if let Some(amount_out_min) = amount_out_min { + ensure!( + amount_out >= amount_out_min, + Error::::ProvidedMinimumNotSufficientForSwap + ); + } + + Self::do_swap(sender, &amounts, path, send_to, keep_alive)?; + Ok(amount_out) + } + + /// Take the `path[0]` asset and swap some amount for `amount_out` of the `path[1]`. If an + /// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be + /// too costly. + /// + /// Withdraws `path[0]` asset from `sender`, deposits the `path[1]` asset to `send_to`, + /// respecting `keep_alive`. + /// + /// If successful returns the amount of the `path[0]` taken to provide `path[1]`. + pub fn do_swap_tokens_for_exact_tokens( + sender: T::AccountId, + path: BoundedVec, + amount_out: T::AssetBalance, + amount_in_max: Option, + send_to: T::AccountId, + keep_alive: bool, + ) -> Result { + ensure!(amount_out > Zero::zero(), Error::::ZeroAmount); + if let Some(amount_in_max) = amount_in_max { + ensure!(amount_in_max > Zero::zero(), Error::::ZeroAmount); + } + + Self::validate_swap_path(&path)?; + + let amounts = Self::get_amounts_in(&amount_out, &path)?; + let amount_in = + *amounts.first().defensive_ok_or("get_amounts_in() returned an empty result")?; + + if let Some(amount_in_max) = amount_in_max { + ensure!( + amount_in <= amount_in_max, + Error::::ProvidedMaximumNotSufficientForSwap + ); + } + + Self::do_swap(sender, &amounts, path, send_to, keep_alive)?; + Ok(amount_in) + } + + /// Transfer an `amount` of `asset_id`, respecting the `keep_alive` requirements. + fn transfer( + asset_id: &T::MultiAssetId, + from: &T::AccountId, + to: &T::AccountId, + amount: T::AssetBalance, + keep_alive: bool, + ) -> Result { + Self::deposit_event(Event::Transfer { + from: from.clone(), + to: to.clone(), + asset: (*asset_id).clone(), + amount, + }); + if T::MultiAssetIdConverter::is_native(asset_id) { + let preservation = match keep_alive { + true => Preserve, + false => Expendable, + }; + let amount = Self::convert_asset_balance_to_native_balance(amount)?; + Ok(Self::convert_native_balance_to_asset_balance(T::Currency::transfer( + from, + to, + amount, + preservation, + )?)?) + } else { + T::Assets::transfer( + T::MultiAssetIdConverter::try_convert(&asset_id) + .map_err(|_| Error::::Overflow)?, + from, + to, + amount, + Expendable, + ) + } + } + + /// Convert a `Balance` type to an `AssetBalance`. + pub(crate) fn convert_native_balance_to_asset_balance( + amount: T::Balance, + ) -> Result> { + T::HigherPrecisionBalance::from(amount) + .try_into() + .map_err(|_| Error::::Overflow) + } + + /// Convert an `AssetBalance` type to a `Balance`. + pub(crate) fn convert_asset_balance_to_native_balance( + amount: T::AssetBalance, + ) -> Result> { + T::HigherPrecisionBalance::from(amount) + .try_into() + .map_err(|_| Error::::Overflow) + } + + /// Convert a `HigherPrecisionBalance` type to an `AssetBalance`. + pub(crate) fn convert_hpb_to_asset_balance( + amount: T::HigherPrecisionBalance, + ) -> Result> { + amount.try_into().map_err(|_| Error::::Overflow) + } + + /// Swap assets along a `path`, depositing in `send_to`. + pub(crate) fn do_swap( + sender: T::AccountId, + amounts: &Vec, + path: BoundedVec, + send_to: T::AccountId, + keep_alive: bool, + ) -> Result<(), DispatchError> { + ensure!(amounts.len() > 1, Error::::CorrespondenceError); + if let Some([asset1, asset2]) = &path.get(0..2) { + let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); + let pool_account = Self::get_pool_account(&pool_id); + // amounts should always contain a corresponding element to path. + let first_amount = amounts.first().ok_or(Error::::CorrespondenceError)?; + + Self::transfer(asset1, &sender, &pool_account, *first_amount, keep_alive)?; + + let mut i = 0; + let path_len = path.len() as u32; + for assets_pair in path.windows(2) { + if let [asset1, asset2] = assets_pair { + let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); + let pool_account = Self::get_pool_account(&pool_id); + + let amount_out = + amounts.get((i + 1) as usize).ok_or(Error::::CorrespondenceError)?; + + let to = if i < path_len - 2 { + let asset3 = path.get((i + 2) as usize).ok_or(Error::::PathError)?; + Self::get_pool_account(&Self::get_pool_id( + asset2.clone(), + asset3.clone(), + )) + } else { + send_to.clone() + }; + + let reserve = Self::get_balance(&pool_account, asset2)?; + let reserve_left = reserve.saturating_sub(*amount_out); + Self::validate_minimal_amount(reserve_left, asset2) + .map_err(|_| Error::::ReserveLeftLessThanMinimal)?; + + Self::transfer(asset2, &pool_account, &to, *amount_out, true)?; + } + i.saturating_inc(); + } + Self::deposit_event(Event::SwapExecuted { + who: sender, + send_to, + path, + amount_in: *first_amount, + amount_out: *amounts.last().expect("Always has more than 1 element"), + }); + } else { + return Err(Error::::InvalidPath.into()) + } + Ok(()) + } + + /// The account ID of the pool. + /// + /// 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") + } + + /// Get the `owner`'s balance of `asset`, which could be the chain's native asset or another + /// fungible. Returns a value in the form of an `AssetBalance`. + fn get_balance( + owner: &T::AccountId, + asset: &T::MultiAssetId, + ) -> Result> { + if T::MultiAssetIdConverter::is_native(asset) { + Self::convert_native_balance_to_asset_balance( + <::Currency>::reducible_balance(owner, Expendable, Polite), + ) + } else { + Ok(<::Assets>::reducible_balance( + T::MultiAssetIdConverter::try_convert(asset) + .map_err(|_| Error::::Overflow)?, + owner, + Expendable, + Polite, + )) + } + } + + /// Returns a pool id constructed from 2 sorted assets. + /// Native asset should be lower than the other asset ids. + pub fn get_pool_id(asset1: T::MultiAssetId, asset2: T::MultiAssetId) -> PoolIdOf { + if asset1 <= asset2 { + (asset1, asset2) + } else { + (asset2, asset1) + } + } + + /// Returns the balance of each asset in the pool. + /// The tuple result is in the order requested (not necessarily the same as pool order). + pub fn get_reserves( + asset1: &T::MultiAssetId, + asset2: &T::MultiAssetId, + ) -> Result<(T::AssetBalance, T::AssetBalance), Error> { + let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); + let pool_account = Self::get_pool_account(&pool_id); + + let balance1 = Self::get_balance(&pool_account, asset1)?; + let balance2 = Self::get_balance(&pool_account, asset2)?; + + if balance1.is_zero() || balance2.is_zero() { + Err(Error::::PoolNotFound)?; + } + + Ok((balance1, balance2)) + } + + /// Leading to an amount at the end of a `path`, get the required amounts in. + pub(crate) fn get_amounts_in( + amount_out: &T::AssetBalance, + path: &BoundedVec, + ) -> Result, DispatchError> { + let mut amounts: Vec = vec![*amount_out]; + + for assets_pair in path.windows(2).rev() { + if let [asset1, asset2] = assets_pair { + let (reserve_in, reserve_out) = Self::get_reserves(asset1, asset2)?; + let prev_amount = amounts.last().expect("Always has at least one element"); + let amount_in = Self::get_amount_in(prev_amount, &reserve_in, &reserve_out)?; + amounts.push(amount_in); + } + } + + amounts.reverse(); + Ok(amounts) + } + + /// Following an amount into a `path`, get the corresponding amounts out. + pub(crate) fn get_amounts_out( + amount_in: &T::AssetBalance, + path: &BoundedVec, + ) -> Result, DispatchError> { + let mut amounts: Vec = vec![*amount_in]; + + for assets_pair in path.windows(2) { + if let [asset1, asset2] = assets_pair { + let (reserve_in, reserve_out) = Self::get_reserves(asset1, asset2)?; + let prev_amount = amounts.last().expect("Always has at least one element"); + let amount_out = Self::get_amount_out(prev_amount, &reserve_in, &reserve_out)?; + amounts.push(amount_out); + } + } + + Ok(amounts) + } + + /// Used by the RPC service to provide current prices. + pub fn quote_price_exact_tokens_for_tokens( + asset1: T::MultiAssetId, + asset2: T::MultiAssetId, + amount: T::AssetBalance, + include_fee: bool, + ) -> Option { + let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); + let pool_account = Self::get_pool_account(&pool_id); + + let balance1 = Self::get_balance(&pool_account, &asset1).ok()?; + let balance2 = Self::get_balance(&pool_account, &asset2).ok()?; + if !balance1.is_zero() { + if include_fee { + Self::get_amount_out(&amount, &balance1, &balance2).ok() + } else { + Self::quote(&amount, &balance1, &balance2).ok() + } + } else { + None + } + } + + /// Used by the RPC service to provide current prices. + pub fn quote_price_tokens_for_exact_tokens( + asset1: T::MultiAssetId, + asset2: T::MultiAssetId, + amount: T::AssetBalance, + include_fee: bool, + ) -> Option { + let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); + let pool_account = Self::get_pool_account(&pool_id); + + let balance1 = Self::get_balance(&pool_account, &asset1).ok()?; + let balance2 = Self::get_balance(&pool_account, &asset2).ok()?; + if !balance1.is_zero() { + if include_fee { + Self::get_amount_in(&amount, &balance1, &balance2).ok() + } else { + Self::quote(&amount, &balance2, &balance1).ok() + } + } else { + None + } + } + + /// Calculates the optimal amount from the reserves. + pub fn quote( + amount: &T::AssetBalance, + reserve1: &T::AssetBalance, + reserve2: &T::AssetBalance, + ) -> Result> { + // amount * reserve2 / reserve1 + Self::mul_div(amount, reserve2, reserve1) + } + + pub(super) fn calc_lp_amount_for_zero_supply( + amount1: &T::AssetBalance, + amount2: &T::AssetBalance, + ) -> Result> { + let amount1 = T::HigherPrecisionBalance::from(*amount1); + let amount2 = T::HigherPrecisionBalance::from(*amount2); + + let result = amount1 + .checked_mul(&amount2) + .ok_or(Error::::Overflow)? + .integer_sqrt() + .checked_sub(&T::MintMinLiquidity::get().into()) + .ok_or(Error::::InsufficientLiquidityMinted)?; + + result.try_into().map_err(|_| Error::::Overflow) + } + + fn mul_div( + a: &T::AssetBalance, + b: &T::AssetBalance, + c: &T::AssetBalance, + ) -> Result> { + let a = T::HigherPrecisionBalance::from(*a); + let b = T::HigherPrecisionBalance::from(*b); + let c = T::HigherPrecisionBalance::from(*c); + + let result = a + .checked_mul(&b) + .ok_or(Error::::Overflow)? + .checked_div(&c) + .ok_or(Error::::Overflow)?; + + result.try_into().map_err(|_| Error::::Overflow) + } + + /// Calculates amount out. + /// + /// Given an input amount of an asset and pair reserves, returns the maximum output amount + /// of the other asset. + pub fn get_amount_out( + amount_in: &T::AssetBalance, + reserve_in: &T::AssetBalance, + reserve_out: &T::AssetBalance, + ) -> Result> { + let amount_in = T::HigherPrecisionBalance::from(*amount_in); + let reserve_in = T::HigherPrecisionBalance::from(*reserve_in); + let reserve_out = T::HigherPrecisionBalance::from(*reserve_out); + + if reserve_in.is_zero() || reserve_out.is_zero() { + return Err(Error::::ZeroLiquidity.into()) + } + + let amount_in_with_fee = amount_in + .checked_mul(&(T::HigherPrecisionBalance::from(1000u32) - (T::LPFee::get().into()))) + .ok_or(Error::::Overflow)?; + + let numerator = + amount_in_with_fee.checked_mul(&reserve_out).ok_or(Error::::Overflow)?; + + let denominator = reserve_in + .checked_mul(&1000u32.into()) + .ok_or(Error::::Overflow)? + .checked_add(&amount_in_with_fee) + .ok_or(Error::::Overflow)?; + + let result = numerator.checked_div(&denominator).ok_or(Error::::Overflow)?; + + result.try_into().map_err(|_| Error::::Overflow) + } + + /// Calculates amount in. + /// + /// Given an output amount of an asset and pair reserves, returns a required input amount + /// of the other asset. + pub fn get_amount_in( + amount_out: &T::AssetBalance, + reserve_in: &T::AssetBalance, + reserve_out: &T::AssetBalance, + ) -> Result> { + let amount_out = T::HigherPrecisionBalance::from(*amount_out); + let reserve_in = T::HigherPrecisionBalance::from(*reserve_in); + let reserve_out = T::HigherPrecisionBalance::from(*reserve_out); + + if reserve_in.is_zero() || reserve_out.is_zero() { + Err(Error::::ZeroLiquidity.into())? + } + + if amount_out >= reserve_out { + Err(Error::::AmountOutTooHigh.into())? + } + + let numerator = reserve_in + .checked_mul(&amount_out) + .ok_or(Error::::Overflow)? + .checked_mul(&1000u32.into()) + .ok_or(Error::::Overflow)?; + + let denominator = reserve_out + .checked_sub(&amount_out) + .ok_or(Error::::Overflow)? + .checked_mul(&(T::HigherPrecisionBalance::from(1000u32) - T::LPFee::get().into())) + .ok_or(Error::::Overflow)?; + + let result = numerator + .checked_div(&denominator) + .ok_or(Error::::Overflow)? + .checked_add(&One::one()) + .ok_or(Error::::Overflow)?; + + result.try_into().map_err(|_| Error::::Overflow) + } + + /// Ensure that a `value` meets the minimum balance requirements of an `asset` class. + fn validate_minimal_amount( + value: T::AssetBalance, + asset: &T::MultiAssetId, + ) -> Result<(), ()> { + if T::MultiAssetIdConverter::is_native(asset) { + let ed = T::Currency::minimum_balance(); + ensure!( + T::HigherPrecisionBalance::from(value) >= T::HigherPrecisionBalance::from(ed), + () + ); + } else { + let asset_id = T::MultiAssetIdConverter::try_convert(asset).map_err(|_| ())?; + let minimal = T::Assets::minimum_balance(asset_id); + ensure!(value >= minimal, ()); + } + Ok(()) + } + + /// Ensure that a path is valid. + fn validate_swap_path( + path: &BoundedVec, + ) -> Result<(), DispatchError> { + ensure!(path.len() >= 2, Error::::InvalidPath); + + // validate all the pools in the path are unique + let mut pools = BoundedBTreeSet::, T::MaxSwapPathLength>::new(); + for assets_pair in path.windows(2) { + if let [asset1, asset2] = assets_pair { + let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone()); + let new_element = pools.try_insert(pool_id).expect("can't get here"); + if !new_element { + return Err(Error::::NonUniquePath.into()) + } + } + } + Ok(()) + } + + /// Returns the next pool asset id for benchmark purposes only. + #[cfg(any(test, feature = "runtime-benchmarks"))] + pub fn get_next_pool_asset_id() -> T::PoolAssetId { + NextPoolAssetId::::get().unwrap_or(T::PoolAssetId::initial_value()) + } + } +} + +impl Swap for Pallet { + fn swap_exact_tokens_for_tokens( + sender: T::AccountId, + path: Vec, + amount_in: T::HigherPrecisionBalance, + amount_out_min: Option, + send_to: T::AccountId, + keep_alive: bool, + ) -> Result { + let path = path.try_into().map_err(|_| Error::::PathError)?; + let amount_out_min = amount_out_min.map(Self::convert_hpb_to_asset_balance).transpose()?; + let amount_out = Self::do_swap_exact_tokens_for_tokens( + sender, + path, + Self::convert_hpb_to_asset_balance(amount_in)?, + amount_out_min, + send_to, + keep_alive, + )?; + Ok(amount_out.into()) + } + + fn swap_tokens_for_exact_tokens( + sender: T::AccountId, + path: Vec, + amount_out: T::HigherPrecisionBalance, + amount_in_max: Option, + send_to: T::AccountId, + keep_alive: bool, + ) -> Result { + let path = path.try_into().map_err(|_| Error::::PathError)?; + let amount_in_max = amount_in_max.map(Self::convert_hpb_to_asset_balance).transpose()?; + let amount_in = Self::do_swap_tokens_for_exact_tokens( + sender, + path, + Self::convert_hpb_to_asset_balance(amount_out)?, + amount_in_max, + send_to, + keep_alive, + )?; + Ok(amount_in.into()) + } +} + +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 AssetConversionApi where + Balance: Codec + MaybeDisplay, + AssetBalance: frame_support::traits::tokens::Balance, + AssetId: Codec + { + /// Provides a quote for [`Pallet::swap_tokens_for_exact_tokens`]. + /// + /// Note that the price may have changed by the time the transaction is executed. + /// (Use `amount_in_max` to control slippage.) + fn quote_price_tokens_for_exact_tokens(asset1: AssetId, asset2: AssetId, amount: AssetBalance, include_fee: bool) -> Option; + + /// Provides a quote for [`Pallet::swap_exact_tokens_for_tokens`]. + /// + /// Note that the price may have changed by the time the transaction is executed. + /// (Use `amount_out_min` to control slippage.) + fn quote_price_exact_tokens_for_tokens(asset1: AssetId, asset2: AssetId, amount: AssetBalance, include_fee: bool) -> Option; + + /// Returns the size of the liquidity pool for the given asset pair. + fn get_reserves(asset1: AssetId, asset2: AssetId) -> Option<(Balance, Balance)>; + } +} + +sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); diff --git a/frame/asset-conversion/src/mock.rs b/frame/asset-conversion/src/mock.rs new file mode 100644 index 000000000000..2b587161f154 --- /dev/null +++ b/frame/asset-conversion/src/mock.rs @@ -0,0 +1,191 @@ +// 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. + +//! Test environment for Asset Conversion pallet. + +use super::*; +use crate as pallet_asset_conversion; + +use frame_support::{ + construct_runtime, + instances::{Instance1, Instance2}, + ord_parameter_types, parameter_types, + traits::{AsEnsureOriginWithArg, ConstU128, ConstU32, ConstU64}, + PalletId, +}; +use frame_system::{EnsureSigned, EnsureSignedBy}; +use sp_arithmetic::Permill; +use sp_core::H256; +use sp_runtime::{ + traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, + BuildStorage, +}; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Assets: pallet_assets::, + PoolAssets: pallet_assets::, + AssetConversion: pallet_asset_conversion, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u128; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<100>; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); +} + +impl pallet_assets::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = u128; + type RemoveItemsLimit = ConstU32<1000>; + type AssetId = u32; + type AssetIdParameter = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = ConstU128<1>; + type AssetAccountDeposit = ConstU128<10>; + type MetadataDepositBase = ConstU128<1>; + type MetadataDepositPerByte = ConstU128<1>; + type ApprovalDeposit = ConstU128<1>; + type StringLimit = ConstU32<50>; + type Freezer = (); + type Extra = (); + type WeightInfo = (); + type CallbackHandle = (); + pallet_assets::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } +} + +impl pallet_assets::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = u128; + type RemoveItemsLimit = ConstU32<1000>; + type AssetId = u32; + type AssetIdParameter = u32; + type Currency = Balances; + type CreateOrigin = + AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = ConstU128<0>; + type AssetAccountDeposit = ConstU128<0>; + type MetadataDepositBase = ConstU128<0>; + type MetadataDepositPerByte = ConstU128<0>; + type ApprovalDeposit = ConstU128<0>; + type StringLimit = ConstU32<50>; + type Freezer = (); + type Extra = (); + type WeightInfo = (); + type CallbackHandle = (); + pallet_assets::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } +} + +parameter_types! { + pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon"); + pub storage AllowMultiAssetPools: bool = true; + pub storage LiquidityWithdrawalFee: Permill = Permill::from_percent(0); // should be non-zero if AllowMultiAssetPools is true, otherwise can be zero +} + +ord_parameter_types! { + pub const AssetConversionOrigin: u128 = AccountIdConversion::::into_account_truncating(&AssetConversionPalletId::get()); +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type AssetBalance = ::Balance; + type AssetId = u32; + type PoolAssetId = u32; + type Assets = Assets; + type PoolAssets = PoolAssets; + type PalletId = AssetConversionPalletId; + type WeightInfo = (); + type LPFee = ConstU32<3>; // means 0.3% + type PoolSetupFee = ConstU128<100>; // should be more or equal to the existential deposit + type PoolSetupFeeReceiver = AssetConversionOrigin; + type LiquidityWithdrawalFee = LiquidityWithdrawalFee; + type AllowMultiAssetPools = AllowMultiAssetPools; + type MaxSwapPathLength = ConstU32<4>; + type MintMinLiquidity = ConstU128<100>; // 100 is good enough when the main currency has 12 decimals. + + type Balance = u128; + type HigherPrecisionBalance = sp_core::U256; + + type MultiAssetId = NativeOrAssetId; + type MultiAssetIdConverter = NativeOrAssetIdConverter; + + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); +} + +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![(1, 10000), (2, 20000), (3, 30000), (4, 40000)], + } + .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/frame/asset-conversion/src/tests.rs b/frame/asset-conversion/src/tests.rs new file mode 100644 index 000000000000..a4c02f365bc0 --- /dev/null +++ b/frame/asset-conversion/src/tests.rs @@ -0,0 +1,1413 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +use crate::{mock::*, *}; +use frame_support::{ + assert_noop, assert_ok, + instances::Instance1, + traits::{fungible::Inspect, fungibles::InspectEnumerable, Get}, +}; +use sp_arithmetic::Permill; +use sp_runtime::{DispatchError, TokenError}; + +fn events() -> Vec> { + let result = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let mock::RuntimeEvent::AssetConversion(inner) = e { + Some(inner) + } else { + None + } + }) + .collect(); + + System::reset_events(); + + result +} + +fn pools() -> Vec> { + let mut s: Vec<_> = Pools::::iter().map(|x| x.0).collect(); + s.sort(); + s +} + +fn assets() -> Vec> { + // if the storage would be public: + // let mut s: Vec<_> = pallet_assets::pallet::Asset::::iter().map(|x| x.0).collect(); + let mut s: Vec<_> = <::Assets>::asset_ids() + .map(|id| NativeOrAssetId::Asset(id)) + .collect(); + s.sort(); + s +} + +fn pool_assets() -> Vec { + let mut s: Vec<_> = <::PoolAssets>::asset_ids().collect(); + s.sort(); + s +} + +fn create_tokens(owner: u128, tokens: Vec>) { + for token_id in tokens { + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + NativeOrAssetIdConverter::try_convert(&token_id).unwrap(), + owner, + false, + 1 + )); + } +} + +fn balance(owner: u128, token_id: NativeOrAssetId) -> u128 { + match token_id { + NativeOrAssetId::Native => <::Currency>::free_balance(owner), + NativeOrAssetId::Asset(token_id) => <::Assets>::balance(token_id, owner), + } +} + +fn pool_balance(owner: u128, token_id: u32) -> u128 { + <::PoolAssets>::balance(token_id, owner) +} + +fn get_ed() -> u128 { + <::Currency>::minimum_balance() +} + +macro_rules! bvec { + ($( $x:tt )*) => { + vec![$( $x )*].try_into().unwrap() + } +} + +#[test] +fn check_pool_accounts_dont_collide() { + use std::collections::HashSet; + let mut map = HashSet::new(); + + for i in 0..1_000_000u32 { + let account = AssetConversion::get_pool_account(&( + NativeOrAssetId::Native, + NativeOrAssetId::Asset(i), + )); + if map.contains(&account) { + panic!("Collision at {}", i); + } + map.insert(account); + } +} + +#[test] +fn check_max_numbers() { + new_test_ext().execute_with(|| { + assert_eq!(AssetConversion::quote(&3u128, &u128::MAX, &u128::MAX).ok().unwrap(), 3); + assert!(AssetConversion::quote(&u128::MAX, &3u128, &u128::MAX).is_err()); + assert_eq!(AssetConversion::quote(&u128::MAX, &u128::MAX, &1u128).ok().unwrap(), 1); + + assert_eq!( + AssetConversion::get_amount_out(&100u128, &u128::MAX, &u128::MAX).ok().unwrap(), + 99 + ); + assert_eq!( + AssetConversion::get_amount_in(&100u128, &u128::MAX, &u128::MAX).ok().unwrap(), + 101 + ); + }); +} + +#[test] +fn can_create_pool() { + new_test_ext().execute_with(|| { + let asset_account_deposit: u128 = + >::AssetAccountDeposit::get(); + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + let pool_id = (token_1, token_2); + + create_tokens(user, vec![token_2]); + + let lp_token = AssetConversion::get_next_pool_asset_id(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 1000)); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_1)); + + let setup_fee = <::PoolSetupFee as Get<::Balance>>::get(); + let pool_account = <::PoolSetupFeeReceiver as Get>::get(); + assert_eq!( + balance(user, NativeOrAssetId::Native), + 1000 - (setup_fee + asset_account_deposit) + ); + assert_eq!(balance(pool_account, NativeOrAssetId::Native), setup_fee); + assert_eq!(lp_token + 1, AssetConversion::get_next_pool_asset_id()); + + assert_eq!(events(), [Event::::PoolCreated { creator: user, pool_id, lp_token }]); + assert_eq!(pools(), vec![pool_id]); + assert_eq!(assets(), vec![token_2]); + assert_eq!(pool_assets(), vec![lp_token]); + + assert_noop!( + AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_1), + Error::::EqualAssets + ); + assert_noop!( + AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_2), + Error::::EqualAssets + ); + + // validate we can create Asset(1)/Asset(2) pool + let token_1 = NativeOrAssetId::Asset(1); + create_tokens(user, vec![token_1]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + + // validate we can force the first asset to be the Native currency only + AllowMultiAssetPools::set(&false); + let token_1 = NativeOrAssetId::Asset(3); + assert_noop!( + AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2), + Error::::PoolMustContainNativeCurrency + ); + }); +} + +#[test] +fn create_same_pool_twice_should_fail() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + + create_tokens(user, vec![token_2]); + + let lp_token = AssetConversion::get_next_pool_asset_id(); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_1)); + let expected_free = lp_token + 1; + assert_eq!(expected_free, AssetConversion::get_next_pool_asset_id()); + + assert_noop!( + AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_1), + Error::::PoolExists + ); + assert_eq!(expected_free, AssetConversion::get_next_pool_asset_id()); + + // Try switching the same tokens around: + assert_noop!( + AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2), + Error::::PoolExists + ); + assert_eq!(expected_free, AssetConversion::get_next_pool_asset_id()); + }); +} + +#[test] +fn different_pools_should_have_different_lp_tokens() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + let token_3 = NativeOrAssetId::Asset(3); + let pool_id_1_2 = (token_1, token_2); + let pool_id_1_3 = (token_1, token_3); + + create_tokens(user, vec![token_2, token_3]); + + let lp_token2_1 = AssetConversion::get_next_pool_asset_id(); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_1)); + let lp_token3_1 = AssetConversion::get_next_pool_asset_id(); + + assert_eq!( + events(), + [Event::::PoolCreated { + creator: user, + pool_id: pool_id_1_2, + lp_token: lp_token2_1 + }] + ); + + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_3, token_1)); + assert_eq!( + events(), + [Event::::PoolCreated { + creator: user, + pool_id: pool_id_1_3, + lp_token: lp_token3_1, + }] + ); + + assert_ne!(lp_token2_1, lp_token3_1); + }); +} + +#[test] +fn can_add_liquidity() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + let token_3 = NativeOrAssetId::Asset(3); + + create_tokens(user, vec![token_2, token_3]); + let lp_token1 = AssetConversion::get_next_pool_asset_id(); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + let lp_token2 = AssetConversion::get_next_pool_asset_id(); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_3)); + + let ed = get_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 * 2 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 3, user, 1000)); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + 10000, + 10, + 10000, + 10, + user, + )); + + let pool_id = (token_1, token_2); + assert!(events().contains(&Event::::LiquidityAdded { + who: user, + mint_to: user, + pool_id, + amount1_provided: 10000, + amount2_provided: 10, + lp_token: lp_token1, + lp_token_minted: 216, + })); + let pallet_account = AssetConversion::get_pool_account(&pool_id); + assert_eq!(balance(pallet_account, token_1), 10000); + assert_eq!(balance(pallet_account, token_2), 10); + assert_eq!(balance(user, token_1), 10000 + ed); + assert_eq!(balance(user, token_2), 1000 - 10); + assert_eq!(pool_balance(user, lp_token1), 216); + + // try to pass the non-native - native assets, the result should be the same + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_3, + token_1, + 10, + 10000, + 10, + 10000, + user, + )); + + let pool_id = (token_1, token_3); + assert!(events().contains(&Event::::LiquidityAdded { + who: user, + mint_to: user, + pool_id, + amount1_provided: 10000, + amount2_provided: 10, + lp_token: lp_token2, + lp_token_minted: 216, + })); + let pallet_account = AssetConversion::get_pool_account(&pool_id); + assert_eq!(balance(pallet_account, token_1), 10000); + assert_eq!(balance(pallet_account, token_3), 10); + assert_eq!(balance(user, token_1), ed); + assert_eq!(balance(user, token_3), 1000 - 10); + assert_eq!(pool_balance(user, lp_token2), 216); + }); +} + +#[test] +fn add_tiny_liquidity_leads_to_insufficient_liquidity_minted_error() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + + create_tokens(user, vec![token_2]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 1000)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + assert_noop!( + AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + 1, + 1, + 1, + 1, + user + ), + Error::::AmountOneLessThanMinimal + ); + + assert_noop!( + AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + get_ed(), + 1, + 1, + 1, + user + ), + Error::::InsufficientLiquidityMinted + ); + }); +} + +#[test] +fn add_tiny_liquidity_directly_to_pool_address() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + let token_3 = NativeOrAssetId::Asset(3); + + create_tokens(user, vec![token_2, token_3]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_3)); + + let ed = get_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 * 2 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 3, user, 1000)); + + // check we're still able to add the liquidity even when the pool already has some token_1 + let pallet_account = AssetConversion::get_pool_account(&(token_1, token_2)); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), pallet_account, 1000)); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + 10000, + 10, + 10000, + 10, + user, + )); + + // check the same but for token_3 (non-native token) + let pallet_account = AssetConversion::get_pool_account(&(token_1, token_3)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, pallet_account, 1)); + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_3, + 10000, + 10, + 10000, + 10, + user, + )); + }); +} + +#[test] +fn can_remove_liquidity() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + let pool_id = (token_1, token_2); + + create_tokens(user, vec![token_2]); + let lp_token = AssetConversion::get_next_pool_asset_id(); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000000000)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 100000)); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + 1000000000, + 100000, + 1000000000, + 100000, + user, + )); + + let total_lp_received = pool_balance(user, lp_token); + LiquidityWithdrawalFee::set(&Permill::from_percent(10)); + + assert_ok!(AssetConversion::remove_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + total_lp_received, + 0, + 0, + user, + )); + + assert!(events().contains(&Event::::LiquidityRemoved { + who: user, + withdraw_to: user, + pool_id, + amount1: 899991000, + amount2: 89999, + lp_token, + lp_token_burned: total_lp_received, + withdrawal_fee: ::LiquidityWithdrawalFee::get() + })); + + let pool_account = AssetConversion::get_pool_account(&pool_id); + assert_eq!(balance(pool_account, token_1), 100009000); + assert_eq!(balance(pool_account, token_2), 10001); + assert_eq!(pool_balance(pool_account, lp_token), 100); + + assert_eq!(balance(user, token_1), 10000000000 - 1000000000 + 899991000); + assert_eq!(balance(user, token_2), 89999); + assert_eq!(pool_balance(user, lp_token), 0); + }); +} + +#[test] +fn can_not_redeem_more_lp_tokens_than_were_minted() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + let lp_token = AssetConversion::get_next_pool_asset_id(); + + create_tokens(user, vec![token_2]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 + get_ed())); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + 10000, + 10, + 10000, + 10, + user, + )); + + // Only 216 lp_tokens_minted + assert_eq!(pool_balance(user, lp_token), 216); + + assert_noop!( + AssetConversion::remove_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + 216 + 1, // Try and redeem 10 lp tokens while only 9 minted. + 0, + 0, + user, + ), + DispatchError::Token(TokenError::FundsUnavailable) + ); + }); +} + +#[test] +fn can_quote_price() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + + create_tokens(user, vec![token_2]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 100000)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + 10000, + 200, + 1, + 1, + user, + )); + + assert_eq!( + AssetConversion::quote_price_exact_tokens_for_tokens( + NativeOrAssetId::Native, + NativeOrAssetId::Asset(2), + 3000, + false, + ), + Some(60) + ); + // Check it still gives same price: + // (if the above accidentally exchanged then it would not give same quote as before) + assert_eq!( + AssetConversion::quote_price_exact_tokens_for_tokens( + NativeOrAssetId::Native, + NativeOrAssetId::Asset(2), + 3000, + false, + ), + Some(60) + ); + + // Check inverse: + assert_eq!( + AssetConversion::quote_price_exact_tokens_for_tokens( + NativeOrAssetId::Asset(2), + NativeOrAssetId::Native, + 60, + false, + ), + Some(3000) + ); + }); +} + +#[test] +fn can_swap_with_native() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + let pool_id = (token_1, token_2); + + create_tokens(user, vec![token_2]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + + let ed = get_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + let liquidity1 = 10000; + let liquidity2 = 200; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + let input_amount = 100; + let expect_receive = + AssetConversion::get_amount_out(&input_amount, &liquidity2, &liquidity1) + .ok() + .unwrap(); + + assert_ok!(AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_2, token_1], + input_amount, + 1, + user, + false, + )); + + let pallet_account = AssetConversion::get_pool_account(&pool_id); + assert_eq!(balance(user, token_1), expect_receive + ed); + assert_eq!(balance(user, token_2), 1000 - liquidity2 - input_amount); + assert_eq!(balance(pallet_account, token_1), liquidity1 - expect_receive); + assert_eq!(balance(pallet_account, token_2), liquidity2 + input_amount); + }); +} + +#[test] +fn can_swap_with_realistic_values() { + new_test_ext().execute_with(|| { + let user = 1; + let dot = NativeOrAssetId::Native; + let usd = NativeOrAssetId::Asset(2); + create_tokens(user, vec![usd]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), dot, usd)); + + const UNIT: u128 = 1_000_000_000; + + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 300_000 * UNIT)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1_100_000 * UNIT)); + + let liquidity_dot = 200_000 * UNIT; // ratio for a 5$ price + let liquidity_usd = 1_000_000 * UNIT; + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + dot, + usd, + liquidity_dot, + liquidity_usd, + 1, + 1, + user, + )); + + let input_amount = 10 * UNIT; // usd + + assert_ok!(AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![usd, dot], + input_amount, + 1, + user, + false, + )); + + assert!(events().contains(&Event::::SwapExecuted { + who: user, + send_to: user, + path: bvec![usd, dot], + amount_in: 10 * UNIT, // usd + amount_out: 1_993_980_120, // About 2 dot after div by UNIT. + })); + }); +} + +#[test] +fn can_not_swap_in_pool_with_no_liquidity_added_yet() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + + create_tokens(user, vec![token_2]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + + // Check can't swap an empty pool + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_2, token_1], + 10, + 1, + user, + false, + ), + Error::::PoolNotFound + ); + }); +} + +#[test] +fn check_no_panic_when_try_swap_close_to_empty_pool() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + let pool_id = (token_1, token_2); + let lp_token = AssetConversion::get_next_pool_asset_id(); + + create_tokens(user, vec![token_2]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + + let ed = get_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + let liquidity1 = 10000; + let liquidity2 = 200; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + let lp_token_minted = pool_balance(user, lp_token); + assert!(events().contains(&Event::::LiquidityAdded { + who: user, + mint_to: user, + pool_id, + amount1_provided: liquidity1, + amount2_provided: liquidity2, + lp_token, + lp_token_minted, + })); + + let pallet_account = AssetConversion::get_pool_account(&pool_id); + assert_eq!(balance(pallet_account, token_1), liquidity1); + assert_eq!(balance(pallet_account, token_2), liquidity2); + + assert_ok!(AssetConversion::remove_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + lp_token_minted, + 1, + 1, + user, + )); + + // Now, the pool should exist but be almost empty. + // Let's try and drain it. + assert_eq!(balance(pallet_account, token_1), 708); + assert_eq!(balance(pallet_account, token_2), 15); + + // validate the reserve should always stay above the ED + assert_noop!( + AssetConversion::swap_tokens_for_exact_tokens( + RuntimeOrigin::signed(user), + bvec![token_2, token_1], + 708 - ed + 1, // amount_out + 500, // amount_in_max + user, + false, + ), + Error::::ReserveLeftLessThanMinimal + ); + + assert_ok!(AssetConversion::swap_tokens_for_exact_tokens( + RuntimeOrigin::signed(user), + bvec![token_2, token_1], + 608, // amount_out + 500, // amount_in_max + user, + false, + )); + + let token_1_left = balance(pallet_account, token_1); + let token_2_left = balance(pallet_account, token_2); + assert_eq!(token_1_left, 708 - 608); + + // The price for the last tokens should be very high + assert_eq!( + AssetConversion::get_amount_in(&(token_1_left - 1), &token_2_left, &token_1_left) + .ok() + .unwrap(), + 10625 + ); + + assert_noop!( + AssetConversion::swap_tokens_for_exact_tokens( + RuntimeOrigin::signed(user), + bvec![token_2, token_1], + token_1_left - 1, // amount_out + 1000, // amount_in_max + user, + false, + ), + Error::::ProvidedMaximumNotSufficientForSwap + ); + + // Try to swap what's left in the pool + assert_noop!( + AssetConversion::swap_tokens_for_exact_tokens( + RuntimeOrigin::signed(user), + bvec![token_2, token_1], + token_1_left, // amount_out + 1000, // amount_in_max + user, + false, + ), + Error::::AmountOutTooHigh + ); + }); +} + +#[test] +fn swap_should_not_work_if_too_much_slippage() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + + create_tokens(user, vec![token_2]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 + get_ed())); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + let liquidity1 = 10000; + let liquidity2 = 200; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + let exchange_amount = 100; + + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_2, token_1], + exchange_amount, // amount_in + 4000, // amount_out_min + user, + false, + ), + Error::::ProvidedMinimumNotSufficientForSwap + ); + }); +} + +#[test] +fn can_swap_tokens_for_exact_tokens() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + let pool_id = (token_1, token_2); + + create_tokens(user, vec![token_2]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + + let ed = get_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + let pallet_account = AssetConversion::get_pool_account(&pool_id); + let before1 = balance(pallet_account, token_1) + balance(user, token_1); + let before2 = balance(pallet_account, token_2) + balance(user, token_2); + + let liquidity1 = 10000; + let liquidity2 = 200; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + let exchange_out = 50; + let expect_in = AssetConversion::get_amount_in(&exchange_out, &liquidity1, &liquidity2) + .ok() + .unwrap(); + + assert_ok!(AssetConversion::swap_tokens_for_exact_tokens( + RuntimeOrigin::signed(user), + bvec![token_1, token_2], + exchange_out, // amount_out + 3500, // amount_in_max + user, + true, + )); + + assert_eq!(balance(user, token_1), 10000 + ed - expect_in); + assert_eq!(balance(user, token_2), 1000 - liquidity2 + exchange_out); + assert_eq!(balance(pallet_account, token_1), liquidity1 + expect_in); + assert_eq!(balance(pallet_account, token_2), liquidity2 - exchange_out); + + // check invariants: + + // native and asset totals should be preserved. + assert_eq!(before1, balance(pallet_account, token_1) + balance(user, token_1)); + assert_eq!(before2, balance(pallet_account, token_2) + balance(user, token_2)); + }); +} + +#[test] +fn can_swap_tokens_for_exact_tokens_when_not_liquidity_provider() { + new_test_ext().execute_with(|| { + let user = 1; + let user2 = 2; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + let pool_id = (token_1, token_2); + let lp_token = AssetConversion::get_next_pool_asset_id(); + + create_tokens(user2, vec![token_2]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user2), token_1, token_2)); + + let ed = get_ed(); + let base1 = 10000; + let base2 = 1000; + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, base1 + ed)); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user2, base1 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user2), 2, user2, base2)); + + let pallet_account = AssetConversion::get_pool_account(&pool_id); + let before1 = + balance(pallet_account, token_1) + balance(user, token_1) + balance(user2, token_1); + let before2 = + balance(pallet_account, token_2) + balance(user, token_2) + balance(user2, token_2); + + let liquidity1 = 10000; + let liquidity2 = 200; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user2), + token_1, + token_2, + liquidity1, + liquidity2, + 1, + 1, + user2, + )); + + assert_eq!(balance(user, token_1), base1 + ed); + assert_eq!(balance(user, token_2), 0); + + let exchange_out = 50; + let expect_in = AssetConversion::get_amount_in(&exchange_out, &liquidity1, &liquidity2) + .ok() + .unwrap(); + + assert_ok!(AssetConversion::swap_tokens_for_exact_tokens( + RuntimeOrigin::signed(user), + bvec![token_1, token_2], + exchange_out, // amount_out + 3500, // amount_in_max + user, + true, + )); + + assert_eq!(balance(user, token_1), base1 + ed - expect_in); + assert_eq!(balance(pallet_account, token_1), liquidity1 + expect_in); + assert_eq!(balance(user, token_2), exchange_out); + assert_eq!(balance(pallet_account, token_2), liquidity2 - exchange_out); + + // check invariants: + + // native and asset totals should be preserved. + assert_eq!( + before1, + balance(pallet_account, token_1) + balance(user, token_1) + balance(user2, token_1) + ); + assert_eq!( + before2, + balance(pallet_account, token_2) + balance(user, token_2) + balance(user2, token_2) + ); + + let lp_token_minted = pool_balance(user2, lp_token); + assert_eq!(lp_token_minted, 1314); + + assert_ok!(AssetConversion::remove_liquidity( + RuntimeOrigin::signed(user2), + token_1, + token_2, + lp_token_minted, + 0, + 0, + user2, + )); + }); +} + +#[test] +fn swap_when_existential_deposit_would_cause_reaping_but_keep_alive_set() { + new_test_ext().execute_with(|| { + let user = 1; + let user2 = 2; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + + create_tokens(user2, vec![token_2]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user2), token_1, token_2)); + + let ed = get_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 101)); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user2, 10000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user2), 2, user2, 1000)); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user2), + token_1, + token_2, + 10000, + 200, + 1, + 1, + user2, + )); + + assert_noop!( + AssetConversion::swap_tokens_for_exact_tokens( + RuntimeOrigin::signed(user), + bvec![token_1, token_2], + 1, // amount_out + 101, // amount_in_max + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); + + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_1, token_2], + 51, // amount_in + 1, // amount_out_min + user, + true, + ), + DispatchError::Token(TokenError::NotExpendable) + ); + }); +} + +#[test] +fn swap_tokens_for_exact_tokens_should_not_work_if_too_much_slippage() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + + create_tokens(user, vec![token_2]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 20000 + get_ed())); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 1000)); + + let liquidity1 = 10000; + let liquidity2 = 200; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + liquidity1, + liquidity2, + 1, + 1, + user, + )); + + let exchange_out = 1; + + assert_noop!( + AssetConversion::swap_tokens_for_exact_tokens( + RuntimeOrigin::signed(user), + bvec![token_1, token_2], + exchange_out, // amount_out + 50, // amount_in_max just greater than slippage. + user, + true + ), + Error::::ProvidedMaximumNotSufficientForSwap + ); + }); +} + +#[test] +fn swap_exact_tokens_for_tokens_in_multi_hops() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + let token_3 = NativeOrAssetId::Asset(3); + + create_tokens(user, vec![token_2, token_3]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_3)); + + let ed = get_ed(); + let base1 = 10000; + let base2 = 10000; + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, base1 * 2 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, base2)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 3, user, base2)); + + let liquidity1 = 10000; + let liquidity2 = 200; + let liquidity3 = 2000; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + liquidity1, + liquidity2, + 1, + 1, + user, + )); + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_2, + token_3, + liquidity2, + liquidity3, + 1, + 1, + user, + )); + + let input_amount = 500; + let expect_out2 = AssetConversion::get_amount_out(&input_amount, &liquidity1, &liquidity2) + .ok() + .unwrap(); + let expect_out3 = AssetConversion::get_amount_out(&expect_out2, &liquidity2, &liquidity3) + .ok() + .unwrap(); + + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_1], + input_amount, + 80, + user, + true, + ), + Error::::InvalidPath + ); + + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_1, token_2, token_3, token_2], + input_amount, + 80, + user, + true, + ), + Error::::NonUniquePath + ); + + assert_ok!(AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_1, token_2, token_3], + input_amount, // amount_in + 80, // amount_out_min + user, + true, + )); + + let pool_id1 = (token_1, token_2); + let pool_id2 = (token_2, token_3); + let pallet_account1 = AssetConversion::get_pool_account(&pool_id1); + let pallet_account2 = AssetConversion::get_pool_account(&pool_id2); + + assert_eq!(balance(user, token_1), base1 + ed - input_amount); + assert_eq!(balance(pallet_account1, token_1), liquidity1 + input_amount); + assert_eq!(balance(pallet_account1, token_2), liquidity2 - expect_out2); + assert_eq!(balance(pallet_account2, token_2), liquidity2 + expect_out2); + assert_eq!(balance(pallet_account2, token_3), liquidity3 - expect_out3); + assert_eq!(balance(user, token_3), 10000 - liquidity3 + expect_out3); + }); +} + +#[test] +fn swap_tokens_for_exact_tokens_in_multi_hops() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + let token_3 = NativeOrAssetId::Asset(3); + + create_tokens(user, vec![token_2, token_3]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_2, token_3)); + + let ed = get_ed(); + let base1 = 10000; + let base2 = 10000; + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, base1 * 2 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, base2)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 3, user, base2)); + + let liquidity1 = 10000; + let liquidity2 = 200; + let liquidity3 = 2000; + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + liquidity1, + liquidity2, + 1, + 1, + user, + )); + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_2, + token_3, + liquidity2, + liquidity3, + 1, + 1, + user, + )); + + let exchange_out3 = 100; + let expect_in2 = AssetConversion::get_amount_in(&exchange_out3, &liquidity2, &liquidity3) + .ok() + .unwrap(); + let expect_in1 = AssetConversion::get_amount_in(&expect_in2, &liquidity1, &liquidity2) + .ok() + .unwrap(); + + assert_ok!(AssetConversion::swap_tokens_for_exact_tokens( + RuntimeOrigin::signed(user), + bvec![token_1, token_2, token_3], + exchange_out3, // amount_out + 1000, // amount_in_max + user, + true, + )); + + let pool_id1 = (token_1, token_2); + let pool_id2 = (token_2, token_3); + let pallet_account1 = AssetConversion::get_pool_account(&pool_id1); + let pallet_account2 = AssetConversion::get_pool_account(&pool_id2); + + assert_eq!(balance(user, token_1), base1 + ed - expect_in1); + assert_eq!(balance(pallet_account1, token_1), liquidity1 + expect_in1); + assert_eq!(balance(pallet_account1, token_2), liquidity2 - expect_in2); + assert_eq!(balance(pallet_account2, token_2), liquidity2 + expect_in2); + assert_eq!(balance(pallet_account2, token_3), liquidity3 - exchange_out3); + assert_eq!(balance(user, token_3), 10000 - liquidity3 + exchange_out3); + }); +} + +#[test] +fn can_not_swap_same_asset() { + new_test_ext().execute_with(|| { + let user = 1; + let token_1 = NativeOrAssetId::Asset(1); + + create_tokens(user, vec![token_1]); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 1, user, 1000)); + + let liquidity1 = 1000; + let liquidity2 = 20; + assert_noop!( + AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_1, + liquidity1, + liquidity2, + 1, + 1, + user, + ), + Error::::PoolNotFound + ); + + let exchange_amount = 10; + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![token_1, token_1], + exchange_amount, + 1, + user, + true, + ), + Error::::PoolNotFound + ); + + assert_noop!( + AssetConversion::swap_exact_tokens_for_tokens( + RuntimeOrigin::signed(user), + bvec![NativeOrAssetId::Native, NativeOrAssetId::Native], + exchange_amount, + 1, + user, + true, + ), + Error::::PoolNotFound + ); + }); +} + +#[test] +fn validate_pool_id_sorting() { + new_test_ext().execute_with(|| { + use crate::NativeOrAssetId::{Asset, Native}; + assert_eq!(AssetConversion::get_pool_id(Native, Asset(2)), (Native, Asset(2))); + assert_eq!(AssetConversion::get_pool_id(Asset(2), Native), (Native, Asset(2))); + assert_eq!(AssetConversion::get_pool_id(Native, Native), (Native, Native)); + assert_eq!(AssetConversion::get_pool_id(Asset(2), Asset(1)), (Asset(1), Asset(2))); + assert!(Asset(2) > Asset(1)); + assert!(Asset(1) <= Asset(1)); + assert_eq!(Asset(1), Asset(1)); + assert_eq!(Native::, Native::); + assert!(Native < Asset(1)); + }); +} + +#[test] +fn cannot_block_pool_creation() { + new_test_ext().execute_with(|| { + // User 1 is the pool creator + let user = 1; + // User 2 is the attacker + let attacker = 2; + + let ed = get_ed(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), attacker, 10000 + ed)); + + // The target pool the user wants to create is Native <=> Asset(2) + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(2); + + // Attacker computes the still non-existing pool account for the target pair + let pool_account = + AssetConversion::get_pool_account(&AssetConversion::get_pool_id(token_2, token_1)); + // And transfers the ED to that pool account + assert_ok!(Balances::transfer(RuntimeOrigin::signed(attacker), pool_account, ed)); + // Then, the attacker creates 14 tokens and sends one of each to the pool account + for i in 10..25 { + create_tokens(attacker, vec![NativeOrAssetId::Asset(i)]); + assert_ok!(Assets::mint(RuntimeOrigin::signed(attacker), i, attacker, 1000)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(attacker), i, pool_account, 1)); + } + + // User can still create the pool + create_tokens(user, vec![token_2]); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(user), token_1, token_2)); + + // User has to transfer one Asset(2) token to the pool account (otherwise add_liquidity will + // fail with `AssetTwoDepositDidNotMeetMinimum`) + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), user, 10000 + ed)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(user), 2, user, 10000)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(user), 2, pool_account, 1)); + + // add_liquidity shouldn't fail because of the number of consumers + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(user), + token_1, + token_2, + 10000, + 100, + 10000, + 10, + user, + )); + }); +} diff --git a/frame/asset-conversion/src/types.rs b/frame/asset-conversion/src/types.rs new file mode 100644 index 000000000000..0612e363f056 --- /dev/null +++ b/frame/asset-conversion/src/types.rs @@ -0,0 +1,171 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +use super::*; +use core::marker::PhantomData; +use sp_std::cmp::Ordering; + +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; + +pub(super) type PoolIdOf = (::MultiAssetId, ::MultiAssetId); + +/// Stores the lp_token asset id a particular pool has been assigned. +#[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)] +pub struct PoolInfo { + /// Liquidity pool asset + pub lp_token: PoolAssetId, +} + +/// A trait that converts between a MultiAssetId and either the native currency or an AssetId. +pub trait MultiAssetIdConverter { + /// Returns the MultiAssetId representing the native currency of the chain. + fn get_native() -> MultiAssetId; + + /// Returns true if the given MultiAssetId is the native currency. + fn is_native(asset: &MultiAssetId) -> bool; + + /// If it's not native, returns the AssetId for the given MultiAssetId. + fn try_convert(asset: &MultiAssetId) -> Result; + + /// Wraps an AssetId as a MultiAssetId. + fn into_multiasset_id(asset: &AssetId) -> MultiAssetId; +} + +/// Benchmark Helper +#[cfg(feature = "runtime-benchmarks")] +pub trait BenchmarkHelper { + /// Returns an asset id from a given integer. + fn asset_id(asset_id: u32) -> AssetId; +} + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkHelper for () +where + AssetId: From, +{ + fn asset_id(asset_id: u32) -> AssetId { + asset_id.into() + } +} + +/// An implementation of MultiAssetId that can be either Native or an asset. +#[derive(Decode, Encode, Default, MaxEncodedLen, TypeInfo, Clone, Copy, Debug)] +pub enum NativeOrAssetId +where + AssetId: Ord, +{ + /// Native asset. For example, on statemint this would be dot. + #[default] + Native, + /// A non-native asset id. + Asset(AssetId), +} + +impl From for NativeOrAssetId { + fn from(asset: AssetId) -> Self { + Self::Asset(asset) + } +} + +impl Ord for NativeOrAssetId { + fn cmp(&self, other: &Self) -> Ordering { + match (self, other) { + (Self::Native, Self::Native) => Ordering::Equal, + (Self::Native, Self::Asset(_)) => Ordering::Less, + (Self::Asset(_), Self::Native) => Ordering::Greater, + (Self::Asset(id1), Self::Asset(id2)) => ::cmp(id1, id2), + } + } +} +impl PartialOrd for NativeOrAssetId { + fn partial_cmp(&self, other: &Self) -> Option { + Some(::cmp(self, other)) + } +} +impl PartialEq for NativeOrAssetId { + fn eq(&self, other: &Self) -> bool { + self.cmp(other) == Ordering::Equal + } +} +impl Eq for NativeOrAssetId {} + +/// Converts between a MultiAssetId and an AssetId +/// (or the native currency). +pub struct NativeOrAssetIdConverter { + _phantom: PhantomData, +} + +impl MultiAssetIdConverter, AssetId> + for NativeOrAssetIdConverter +{ + fn get_native() -> NativeOrAssetId { + NativeOrAssetId::Native + } + + fn is_native(asset: &NativeOrAssetId) -> bool { + *asset == Self::get_native() + } + + fn try_convert(asset: &NativeOrAssetId) -> Result { + match asset { + NativeOrAssetId::Asset(asset) => Ok(asset.clone()), + NativeOrAssetId::Native => Err(()), + } + } + + fn into_multiasset_id(asset: &AssetId) -> NativeOrAssetId { + NativeOrAssetId::Asset((*asset).clone()) + } +} + +/// Trait for providing methods to swap between the various asset classes. +pub trait Swap { + /// Swap exactly `amount_in` of asset `path[0]` for asset `path[1]`. + /// If an `amount_out_min` is specified, it will return an error if it is unable to acquire + /// the amount desired. + /// + /// Withdraws the `path[0]` asset from `sender`, deposits the `path[1]` asset to `send_to`, + /// respecting `keep_alive`. + /// + /// If successful, returns the amount of `path[1]` acquired for the `amount_in`. + fn swap_exact_tokens_for_tokens( + sender: AccountId, + path: Vec, + amount_in: Balance, + amount_out_min: Option, + send_to: AccountId, + keep_alive: bool, + ) -> Result; + + /// Take the `path[0]` asset and swap some amount for `amount_out` of the `path[1]`. If an + /// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be + /// too costly. + /// + /// Withdraws `path[0]` asset from `sender`, deposits `path[1]` asset to `send_to`, + /// respecting `keep_alive`. + /// + /// If successful returns the amount of the `path[0]` taken to provide `path[1]`. + fn swap_tokens_for_exact_tokens( + sender: AccountId, + path: Vec, + amount_out: Balance, + amount_in_max: Option, + send_to: AccountId, + keep_alive: bool, + ) -> Result; +} diff --git a/frame/asset-conversion/src/weights.rs b/frame/asset-conversion/src/weights.rs new file mode 100644 index 000000000000..682005076209 --- /dev/null +++ b/frame/asset-conversion/src/weights.rs @@ -0,0 +1,256 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_asset_conversion +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-07-17, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-gghbxkbs-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// target/production/substrate +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=pallet_asset_conversion +// --chain=dev +// --header=./HEADER-APACHE2 +// --output=./frame/asset-conversion/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for pallet_asset_conversion. +pub trait WeightInfo { + fn create_pool() -> Weight; + fn add_liquidity() -> Weight; + fn remove_liquidity() -> Weight; + fn swap_exact_tokens_for_tokens() -> Weight; + fn swap_tokens_for_exact_tokens() -> Weight; +} + +/// Weights for pallet_asset_conversion using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `AssetConversion::Pools` (r:1 w:1) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:1) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetConversion::NextPoolAssetId` (r:1 w:1) + /// Proof: `AssetConversion::NextPoolAssetId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `729` + // Estimated: `6196` + // Minimum execution time: 131_549_000 picoseconds. + Weight::from_parts(134_287_000, 6196) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) + } + /// Storage: `AssetConversion::Pools` (r:1 w:0) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:2 w:2) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn add_liquidity() -> Weight { + // Proof Size summary in bytes: + // Measured: `1382` + // Estimated: `6208` + // Minimum execution time: 156_395_000 picoseconds. + Weight::from_parts(157_952_000, 6208) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) + } + /// Storage: `AssetConversion::Pools` (r:1 w:0) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn remove_liquidity() -> Weight { + // Proof Size summary in bytes: + // Measured: `1371` + // Estimated: `6208` + // Minimum execution time: 140_295_000 picoseconds. + Weight::from_parts(143_452_000, 6208) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) + } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:3 w:3) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:6 w:6) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn swap_exact_tokens_for_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `1738` + // Estimated: `16644` + // Minimum execution time: 214_657_000 picoseconds. + Weight::from_parts(217_657_000, 16644) + .saturating_add(T::DbWeight::get().reads(10_u64)) + .saturating_add(T::DbWeight::get().writes(10_u64)) + } + /// Storage: `Assets::Asset` (r:3 w:3) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:6 w:6) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn swap_tokens_for_exact_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `1738` + // Estimated: `16644` + // Minimum execution time: 215_319_000 picoseconds. + Weight::from_parts(218_299_000, 16644) + .saturating_add(T::DbWeight::get().reads(10_u64)) + .saturating_add(T::DbWeight::get().writes(10_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + /// Storage: `AssetConversion::Pools` (r:1 w:1) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:1 w:1) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `AssetConversion::NextPoolAssetId` (r:1 w:1) + /// Proof: `AssetConversion::NextPoolAssetId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn create_pool() -> Weight { + // Proof Size summary in bytes: + // Measured: `729` + // Estimated: `6196` + // Minimum execution time: 131_549_000 picoseconds. + Weight::from_parts(134_287_000, 6196) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(8_u64)) + } + /// Storage: `AssetConversion::Pools` (r:1 w:0) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:2 w:2) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn add_liquidity() -> Weight { + // Proof Size summary in bytes: + // Measured: `1382` + // Estimated: `6208` + // Minimum execution time: 156_395_000 picoseconds. + Weight::from_parts(157_952_000, 6208) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) + } + /// Storage: `AssetConversion::Pools` (r:1 w:0) + /// Proof: `AssetConversion::Pools` (`max_values`: None, `max_size`: Some(30), added: 2505, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:1 w:1) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:2 w:2) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Asset` (r:1 w:1) + /// Proof: `PoolAssets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `PoolAssets::Account` (r:1 w:1) + /// Proof: `PoolAssets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn remove_liquidity() -> Weight { + // Proof Size summary in bytes: + // Measured: `1371` + // Estimated: `6208` + // Minimum execution time: 140_295_000 picoseconds. + Weight::from_parts(143_452_000, 6208) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// Storage: `Assets::Asset` (r:3 w:3) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:6 w:6) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + fn swap_exact_tokens_for_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `1738` + // Estimated: `16644` + // Minimum execution time: 214_657_000 picoseconds. + Weight::from_parts(217_657_000, 16644) + .saturating_add(RocksDbWeight::get().reads(10_u64)) + .saturating_add(RocksDbWeight::get().writes(10_u64)) + } + /// Storage: `Assets::Asset` (r:3 w:3) + /// Proof: `Assets::Asset` (`max_values`: None, `max_size`: Some(210), added: 2685, mode: `MaxEncodedLen`) + /// Storage: `Assets::Account` (r:6 w:6) + /// Proof: `Assets::Account` (`max_values`: None, `max_size`: Some(134), added: 2609, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn swap_tokens_for_exact_tokens() -> Weight { + // Proof Size summary in bytes: + // Measured: `1738` + // Estimated: `16644` + // Minimum execution time: 215_319_000 picoseconds. + Weight::from_parts(218_299_000, 16644) + .saturating_add(RocksDbWeight::get().reads(10_u64)) + .saturating_add(RocksDbWeight::get().writes(10_u64)) + } +} diff --git a/frame/asset-rate/Cargo.toml b/frame/asset-rate/Cargo.toml new file mode 100644 index 000000000000..565f658024e6 --- /dev/null +++ b/frame/asset-rate/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "pallet-asset-rate" +version = "4.0.0-dev" +description = "Whitelist non-native assets for treasury spending and provide conversion to native balance" +authors = ["William Freudenberger "] +homepage = "https://substrate.io" +edition = "2021" +license = "Apache-2.0" +repository = "https://github.com/paritytech/substrate/" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/frame/asset-rate/src/benchmarking.rs b/frame/asset-rate/src/benchmarking.rs new file mode 100644 index 000000000000..1209f8db6693 --- /dev/null +++ b/frame/asset-rate/src/benchmarking.rs @@ -0,0 +1,86 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The crate's benchmarks. + +use super::*; +use crate::{pallet as pallet_asset_rate, Pallet as AssetRate}; + +use frame_benchmarking::v2::*; +use frame_support::assert_ok; +use frame_system::RawOrigin; + +const ASSET_ID: u32 = 1; + +fn default_conversion_rate() -> FixedU128 { + FixedU128::from_u32(1u32) +} + +#[benchmarks(where ::AssetId: From)] +mod benchmarks { + use super::*; + + #[benchmark] + fn create() -> Result<(), BenchmarkError> { + let asset_id: T::AssetId = ASSET_ID.into(); + #[extrinsic_call] + _(RawOrigin::Root, asset_id.clone(), default_conversion_rate()); + + assert_eq!( + pallet_asset_rate::ConversionRateToNative::::get(asset_id), + Some(default_conversion_rate()) + ); + Ok(()) + } + + #[benchmark] + fn update() -> Result<(), BenchmarkError> { + let asset_id: T::AssetId = ASSET_ID.into(); + assert_ok!(AssetRate::::create( + RawOrigin::Root.into(), + asset_id.clone(), + default_conversion_rate() + )); + + #[extrinsic_call] + _(RawOrigin::Root, asset_id.clone(), FixedU128::from_u32(2)); + + assert_eq!( + pallet_asset_rate::ConversionRateToNative::::get(asset_id), + Some(FixedU128::from_u32(2)) + ); + Ok(()) + } + + #[benchmark] + fn remove() -> Result<(), BenchmarkError> { + let asset_id: T::AssetId = ASSET_ID.into(); + assert_ok!(AssetRate::::create( + RawOrigin::Root.into(), + ASSET_ID.into(), + default_conversion_rate() + )); + + #[extrinsic_call] + _(RawOrigin::Root, asset_id.clone()); + + assert!(pallet_asset_rate::ConversionRateToNative::::get(asset_id).is_none()); + Ok(()) + } + + impl_benchmark_test_suite! { AssetRate, crate::mock::new_test_ext(), crate::mock::Test } +} diff --git a/frame/asset-rate/src/lib.rs b/frame/asset-rate/src/lib.rs new file mode 100644 index 000000000000..ecc793184094 --- /dev/null +++ b/frame/asset-rate/src/lib.rs @@ -0,0 +1,239 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Asset Rate Pallet +//! +//! - [`Config`] +//! - [`Call`] +//! +//! ## Overview +//! +//! The AssetRate pallet provides means of setting conversion rates for some asset to native +//! balance. +//! +//! The supported dispatchable functions are documented in the [`Call`] enum. +//! +//! ### Terminology +//! +//! * **Asset balance**: The balance type of an arbitrary asset. The network might only know about +//! the identifier of the asset and nothing more. +//! * **Native balance**: The balance type of the network's native currency. +//! +//! ### Goals +//! +//! The asset-rate system in Substrate is designed to make the following possible: +//! +//! * Providing a soft conversion for the balance of supported assets to a default asset class. +//! * Updating existing conversion rates. +//! +//! ## Interface +//! +//! ### Permissioned Functions +//! +//! * `create`: Creates a new asset conversion rate. +//! * `remove`: Removes an existing asset conversion rate. +//! * `update`: Overwrites an existing assert conversion rate. +//! +//! Please refer to the [`Call`] enum and its associated variants for documentation on each +//! function. +//! +//! ### Assumptions +//! +//! * Conversion rates are only used as estimates, and are not designed to be precise or closely +//! tracking real world values. +//! * All conversion rates reflect the ration of some asset to native, e.g. native = asset * rate. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::traits::{ + fungible::Inspect, + tokens::{Balance, ConversionFromAssetBalance}, +}; +use sp_runtime::{traits::Zero, FixedPointNumber, FixedPointOperand, FixedU128}; + +pub use pallet::*; +pub use weights::WeightInfo; + +#[cfg(feature = "runtime-benchmarks")] +pub mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; +pub mod weights; + +// Type alias for `frame_system`'s account id. +type AccountIdOf = ::AccountId; +// This pallet's asset id and balance type. +type AssetIdOf = ::AssetId; +// Generic fungible balance type. +type BalanceOf = <::Currency as Inspect>>::Balance; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// The runtime event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The origin permissioned to create a conversion rate for an asset. + type CreateOrigin: EnsureOrigin; + + /// The origin permissioned to remove an existing conversion rate for an asset. + type RemoveOrigin: EnsureOrigin; + + /// The origin permissioned to update an existiing conversion rate for an asset. + type UpdateOrigin: EnsureOrigin; + + /// The units in which we record balances. + type Balance: Balance + FixedPointOperand; + + /// The currency mechanism for this pallet. + type Currency: Inspect; + + /// The identifier for the class of asset. + type AssetId: frame_support::traits::tokens::AssetId; + } + + /// Maps an asset to its fixed point representation in the native balance. + /// + /// E.g. `native_amount = asset_amount * ConversionRateToNative::::get(asset_id)` + #[pallet::storage] + pub type ConversionRateToNative = + StorageMap<_, Blake2_128Concat, T::AssetId, FixedU128, OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + // Some `asset_id` conversion rate was created. + AssetRateCreated { asset_id: T::AssetId, rate: FixedU128 }, + // Some `asset_id` conversion rate was removed. + AssetRateRemoved { asset_id: T::AssetId }, + // Some existing `asset_id` conversion rate was updated from `old` to `new`. + AssetRateUpdated { asset_id: T::AssetId, old: FixedU128, new: FixedU128 }, + } + + #[pallet::error] + pub enum Error { + /// The given asset ID is unknown. + UnknownAssetId, + /// The given asset ID already has an assigned conversion rate and cannot be re-created. + AlreadyExists, + } + + #[pallet::call] + impl Pallet { + /// Initialize a conversion rate to native balance for the given asset. + /// + /// ## Complexity + /// - O(1) + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::create())] + pub fn create( + origin: OriginFor, + asset_id: T::AssetId, + rate: FixedU128, + ) -> DispatchResult { + T::CreateOrigin::ensure_origin(origin)?; + + ensure!( + !ConversionRateToNative::::contains_key(asset_id.clone()), + Error::::AlreadyExists + ); + ConversionRateToNative::::set(asset_id.clone(), Some(rate)); + + Self::deposit_event(Event::AssetRateCreated { asset_id, rate }); + Ok(()) + } + + /// Update the conversion rate to native balance for the given asset. + /// + /// ## Complexity + /// - O(1) + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::update())] + pub fn update( + origin: OriginFor, + asset_id: T::AssetId, + rate: FixedU128, + ) -> DispatchResult { + T::UpdateOrigin::ensure_origin(origin)?; + + let mut old = FixedU128::zero(); + ConversionRateToNative::::mutate(asset_id.clone(), |maybe_rate| { + if let Some(r) = maybe_rate { + old = *r; + *r = rate; + + Ok(()) + } else { + Err(Error::::UnknownAssetId) + } + })?; + + Self::deposit_event(Event::AssetRateUpdated { asset_id, old, new: rate }); + Ok(()) + } + + /// Remove an existing conversion rate to native balance for the given asset. + /// + /// ## Complexity + /// - O(1) + #[pallet::call_index(2)] + #[pallet::weight(T::WeightInfo::remove())] + pub fn remove(origin: OriginFor, asset_id: T::AssetId) -> DispatchResult { + T::RemoveOrigin::ensure_origin(origin)?; + + ensure!( + ConversionRateToNative::::contains_key(asset_id.clone()), + Error::::UnknownAssetId + ); + ConversionRateToNative::::remove(asset_id.clone()); + + Self::deposit_event(Event::AssetRateRemoved { asset_id }); + Ok(()) + } + } +} + +/// Exposes conversion of an arbitrary balance of an asset to native balance. +impl ConversionFromAssetBalance, AssetIdOf, BalanceOf> for Pallet +where + T: Config, + BalanceOf: FixedPointOperand + Zero, +{ + type Error = pallet::Error; + + fn from_asset_balance( + balance: BalanceOf, + asset_id: AssetIdOf, + ) -> Result, pallet::Error> { + let rate = pallet::ConversionRateToNative::::get(asset_id) + .ok_or(pallet::Error::::UnknownAssetId.into())?; + Ok(rate.saturating_mul_int(balance)) + } +} diff --git a/frame/asset-rate/src/mock.rs b/frame/asset-rate/src/mock.rs new file mode 100644 index 000000000000..33a134628c86 --- /dev/null +++ b/frame/asset-rate/src/mock.rs @@ -0,0 +1,95 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The crate's mock. + +use crate as pallet_asset_rate; +use frame_support::traits::{ConstU16, ConstU64}; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + AssetRate: pallet_asset_rate, + Balances: pallet_balances, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = ConstU16<42>; + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type Balance = u64; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type RuntimeHoldReason = RuntimeHoldReason; + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); +} + +impl pallet_asset_rate::Config for Test { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type CreateOrigin = frame_system::EnsureRoot; + type RemoveOrigin = frame_system::EnsureRoot; + type UpdateOrigin = frame_system::EnsureRoot; + type Balance = u64; + type Currency = Balances; + type AssetId = u32; +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + frame_system::GenesisConfig::::default().build_storage().unwrap().into() +} diff --git a/frame/asset-rate/src/tests.rs b/frame/asset-rate/src/tests.rs new file mode 100644 index 000000000000..4e5a3167bef2 --- /dev/null +++ b/frame/asset-rate/src/tests.rs @@ -0,0 +1,121 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The crate's tests. + +use super::*; +use crate::pallet as pallet_asset_rate; +use frame_support::{assert_noop, assert_ok}; +use mock::{new_test_ext, AssetRate, RuntimeOrigin, Test}; +use sp_runtime::FixedU128; + +const ASSET_ID: u32 = 42; + +#[test] +fn create_works() { + new_test_ext().execute_with(|| { + assert!(pallet_asset_rate::ConversionRateToNative::::get(ASSET_ID).is_none()); + assert_ok!(AssetRate::create(RuntimeOrigin::root(), ASSET_ID, FixedU128::from_float(0.1))); + + assert_eq!( + pallet_asset_rate::ConversionRateToNative::::get(ASSET_ID), + Some(FixedU128::from_float(0.1)) + ); + }); +} + +#[test] +fn create_existing_throws() { + new_test_ext().execute_with(|| { + assert!(pallet_asset_rate::ConversionRateToNative::::get(ASSET_ID).is_none()); + assert_ok!(AssetRate::create(RuntimeOrigin::root(), ASSET_ID, FixedU128::from_float(0.1))); + + assert_noop!( + AssetRate::create(RuntimeOrigin::root(), ASSET_ID, FixedU128::from_float(0.1)), + Error::::AlreadyExists + ); + }); +} + +#[test] +fn remove_works() { + new_test_ext().execute_with(|| { + assert_ok!(AssetRate::create(RuntimeOrigin::root(), ASSET_ID, FixedU128::from_float(0.1))); + + assert_ok!(AssetRate::remove(RuntimeOrigin::root(), ASSET_ID,)); + assert!(pallet_asset_rate::ConversionRateToNative::::get(ASSET_ID).is_none()); + }); +} + +#[test] +fn remove_unknown_throws() { + new_test_ext().execute_with(|| { + assert_noop!( + AssetRate::remove(RuntimeOrigin::root(), ASSET_ID,), + Error::::UnknownAssetId + ); + }); +} + +#[test] +fn update_works() { + new_test_ext().execute_with(|| { + assert_ok!(AssetRate::create(RuntimeOrigin::root(), ASSET_ID, FixedU128::from_float(0.1))); + assert_ok!(AssetRate::update(RuntimeOrigin::root(), ASSET_ID, FixedU128::from_float(0.5))); + + assert_eq!( + pallet_asset_rate::ConversionRateToNative::::get(ASSET_ID), + Some(FixedU128::from_float(0.5)) + ); + }); +} + +#[test] +fn update_unknown_throws() { + new_test_ext().execute_with(|| { + assert_noop!( + AssetRate::update(RuntimeOrigin::root(), ASSET_ID, FixedU128::from_float(0.5)), + Error::::UnknownAssetId + ); + }); +} + +#[test] +fn convert_works() { + new_test_ext().execute_with(|| { + assert_ok!(AssetRate::create(RuntimeOrigin::root(), ASSET_ID, FixedU128::from_float(2.51))); + + let conversion = , + ::AssetId, + BalanceOf, + >>::from_asset_balance(10, ASSET_ID); + assert_eq!(conversion.expect("Conversion rate exists for asset"), 25); + }); +} + +#[test] +fn convert_unknown_throws() { + new_test_ext().execute_with(|| { + let conversion = , + ::AssetId, + BalanceOf, + >>::from_asset_balance(10, ASSET_ID); + assert!(conversion.is_err()); + }); +} diff --git a/frame/asset-rate/src/weights.rs b/frame/asset-rate/src/weights.rs new file mode 100644 index 000000000000..582e20e56d7d --- /dev/null +++ b/frame/asset-rate/src/weights.rs @@ -0,0 +1,133 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_asset_rate +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_asset_rate +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/asset-rate/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for pallet_asset_rate. +pub trait WeightInfo { + fn create() -> Weight; + fn update() -> Weight; + fn remove() -> Weight; +} + +/// Weights for pallet_asset_rate using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: AssetRate ConversionRateToNative (r:1 w:1) + /// Proof: AssetRate ConversionRateToNative (max_values: None, max_size: Some(36), added: 2511, mode: MaxEncodedLen) + fn create() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `3501` + // Minimum execution time: 11_700_000 picoseconds. + Weight::from_parts(12_158_000, 3501) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: AssetRate ConversionRateToNative (r:1 w:1) + /// Proof: AssetRate ConversionRateToNative (max_values: None, max_size: Some(36), added: 2511, mode: MaxEncodedLen) + fn update() -> Weight { + // Proof Size summary in bytes: + // Measured: `137` + // Estimated: `3501` + // Minimum execution time: 12_119_000 picoseconds. + Weight::from_parts(12_548_000, 3501) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: AssetRate ConversionRateToNative (r:1 w:1) + /// Proof: AssetRate ConversionRateToNative (max_values: None, max_size: Some(36), added: 2511, mode: MaxEncodedLen) + fn remove() -> Weight { + // Proof Size summary in bytes: + // Measured: `137` + // Estimated: `3501` + // Minimum execution time: 12_541_000 picoseconds. + Weight::from_parts(12_956_000, 3501) + .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: AssetRate ConversionRateToNative (r:1 w:1) + /// Proof: AssetRate ConversionRateToNative (max_values: None, max_size: Some(36), added: 2511, mode: MaxEncodedLen) + fn create() -> Weight { + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `3501` + // Minimum execution time: 11_700_000 picoseconds. + Weight::from_parts(12_158_000, 3501) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: AssetRate ConversionRateToNative (r:1 w:1) + /// Proof: AssetRate ConversionRateToNative (max_values: None, max_size: Some(36), added: 2511, mode: MaxEncodedLen) + fn update() -> Weight { + // Proof Size summary in bytes: + // Measured: `137` + // Estimated: `3501` + // Minimum execution time: 12_119_000 picoseconds. + Weight::from_parts(12_548_000, 3501) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: AssetRate ConversionRateToNative (r:1 w:1) + /// Proof: AssetRate ConversionRateToNative (max_values: None, max_size: Some(36), added: 2511, mode: MaxEncodedLen) + fn remove() -> Weight { + // Proof Size summary in bytes: + // Measured: `137` + // Estimated: `3501` + // Minimum execution time: 12_541_000 picoseconds. + Weight::from_parts(12_956_000, 3501) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } +} diff --git a/frame/assets/Cargo.toml b/frame/assets/Cargo.toml index 6fe0e3e6c304..404bea161ed9 100644 --- a/frame/assets/Cargo.toml +++ b/frame/assets/Cargo.toml @@ -13,21 +13,21 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } # Needed for various traits. In our case, `OnFinalize`. -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } # Needed for type-safe access to storage DB. frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } # `system` module provides us with all sorts of useful stuff and macros depend on it being around. frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } [dev-dependencies] -sp-std = { version = "5.0.0", path = "../../primitives/std" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-std = { version = "8.0.0", path = "../../primitives/std" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } [features] diff --git a/frame/assets/src/benchmarking.rs b/frame/assets/src/benchmarking.rs index 235d1d937450..376f19139ab9 100644 --- a/frame/assets/src/benchmarking.rs +++ b/frame/assets/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,8 +20,8 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::{ - account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, +use frame_benchmarking::v1::{ + account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, BenchmarkError, }; use frame_support::{ dispatch::UnfilteredDispatchable, @@ -105,14 +105,18 @@ fn add_sufficients, I: 'static>(minter: T::AccountId, n: u32) { fn add_approvals, I: 'static>(minter: T::AccountId, n: u32) { let asset_id = default_asset_id::(); - T::Currency::deposit_creating(&minter, T::ApprovalDeposit::get() * n.into()); + T::Currency::deposit_creating( + &minter, + T::ApprovalDeposit::get() * n.into() + T::Currency::minimum_balance(), + ); let minter_lookup = T::Lookup::unlookup(minter.clone()); let origin = SystemOrigin::Signed(minter); Assets::::mint(origin.clone().into(), asset_id, minter_lookup, (100 * (n + 1)).into()) .unwrap(); + let enough = T::Currency::minimum_balance(); for i in 0..n { let target = account("approval", i, SEED); - T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); + T::Currency::make_free_balance_be(&target, enough); let target_lookup = T::Lookup::unlookup(target); Assets::::approve_transfer( origin.clone().into(), @@ -135,11 +139,12 @@ fn assert_event, I: 'static>(generic_event: >::Runti benchmarks_instance_pallet! { create { let asset_id = default_asset_id::(); - let origin = T::CreateOrigin::successful_origin(&asset_id.into()); - let caller = T::CreateOrigin::ensure_origin(origin, &asset_id.into()).unwrap(); + let origin = T::CreateOrigin::try_successful_origin(&asset_id.into()) + .map_err(|_| BenchmarkError::Weightless)?; + let caller = T::CreateOrigin::ensure_origin(origin.clone(), &asset_id.into()).unwrap(); let caller_lookup = T::Lookup::unlookup(caller.clone()); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); - }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup, 1u32.into()) + }: _(origin, asset_id, caller_lookup, 1u32.into()) verify { assert_last_event::(Event::Created { asset_id: asset_id.into(), creator: caller.clone(), owner: caller }.into()); } @@ -362,7 +367,8 @@ benchmarks_instance_pallet! { let (asset_id, _, _) = create_default_asset::(true); - let origin = T::ForceOrigin::successful_origin(); + let origin = + T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let call = Call::::force_set_metadata { id: asset_id, name: name.clone(), @@ -382,7 +388,8 @@ benchmarks_instance_pallet! { let origin = SystemOrigin::Signed(caller).into(); Assets::::set_metadata(origin, asset_id, dummy.clone(), dummy, 12)?; - let origin = T::ForceOrigin::successful_origin(); + let origin = + T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let call = Call::::force_clear_metadata { id: asset_id }; }: { call.dispatch_bypass_filter(origin)? } verify { @@ -392,7 +399,8 @@ benchmarks_instance_pallet! { force_asset_status { let (asset_id, caller, caller_lookup) = create_default_asset::(true); - let origin = T::ForceOrigin::successful_origin(); + let origin = + T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let call = Call::::force_asset_status { id: asset_id, owner: caller_lookup.clone(), @@ -467,5 +475,81 @@ benchmarks_instance_pallet! { assert_last_event::(Event::ApprovalCancelled { asset_id: asset_id.into(), owner: caller, delegate }.into()); } + set_min_balance { + let (asset_id, caller, caller_lookup) = create_default_asset::(false); + }: _(SystemOrigin::Signed(caller.clone()), asset_id, 50u32.into()) + verify { + assert_last_event::(Event::AssetMinBalanceChanged { asset_id: asset_id.into(), new_min_balance: 50u32.into() }.into()); + } + + touch { + let (asset_id, asset_owner, asset_owner_lookup) = create_default_asset::(false); + let new_account: T::AccountId = account("newaccount", 1, SEED); + T::Currency::make_free_balance_be(&new_account, DepositBalanceOf::::max_value()); + assert_ne!(asset_owner, new_account); + assert!(!Account::::contains_key(asset_id.into(), &new_account)); + }: _(SystemOrigin::Signed(new_account.clone()), asset_id) + verify { + assert!(Account::::contains_key(asset_id.into(), &new_account)); + } + + touch_other { + let (asset_id, asset_owner, asset_owner_lookup) = create_default_asset::(false); + let new_account: T::AccountId = account("newaccount", 1, SEED); + let new_account_lookup = T::Lookup::unlookup(new_account.clone()); + T::Currency::make_free_balance_be(&asset_owner, DepositBalanceOf::::max_value()); + assert_ne!(asset_owner, new_account); + assert!(!Account::::contains_key(asset_id.into(), &new_account)); + }: _(SystemOrigin::Signed(asset_owner.clone()), asset_id, new_account_lookup) + verify { + assert!(Account::::contains_key(asset_id.into(), &new_account)); + } + + refund { + let (asset_id, asset_owner, asset_owner_lookup) = create_default_asset::(false); + let new_account: T::AccountId = account("newaccount", 1, SEED); + T::Currency::make_free_balance_be(&new_account, DepositBalanceOf::::max_value()); + assert_ne!(asset_owner, new_account); + assert!(Assets::::touch( + SystemOrigin::Signed(new_account.clone()).into(), + asset_id + ).is_ok()); + // `touch` should reserve balance of the caller according to the `AssetAccountDeposit` amount... + assert_eq!(T::Currency::reserved_balance(&new_account), T::AssetAccountDeposit::get()); + // ...and also create an `Account` entry. + assert!(Account::::contains_key(asset_id.into(), &new_account)); + }: _(SystemOrigin::Signed(new_account.clone()), asset_id, true) + verify { + // `refund`ing should of course repatriate the reserve + assert!(T::Currency::reserved_balance(&new_account).is_zero()); + } + + refund_other { + let (asset_id, asset_owner, asset_owner_lookup) = create_default_asset::(false); + let new_account: T::AccountId = account("newaccount", 1, SEED); + let new_account_lookup = T::Lookup::unlookup(new_account.clone()); + T::Currency::make_free_balance_be(&asset_owner, DepositBalanceOf::::max_value()); + assert_ne!(asset_owner, new_account); + assert!(Assets::::touch_other( + SystemOrigin::Signed(asset_owner.clone()).into(), + asset_id, + new_account_lookup.clone() + ).is_ok()); + // `touch` should reserve balance of the caller according to the `AssetAccountDeposit` amount... + assert_eq!(T::Currency::reserved_balance(&asset_owner), T::AssetAccountDeposit::get()); + assert!(Account::::contains_key(asset_id.into(), &new_account)); + }: _(SystemOrigin::Signed(asset_owner.clone()), asset_id, new_account_lookup.clone()) + verify { + // this should repatriate the reserved balance of the freezer + assert!(T::Currency::reserved_balance(&asset_owner).is_zero()); + } + + block { + let (asset_id, caller, caller_lookup) = create_default_minted_asset::(true, 100u32.into()); + }: _(SystemOrigin::Signed(caller.clone()), asset_id, caller_lookup) + verify { + assert_last_event::(Event::Blocked { asset_id: asset_id.into(), who: caller }.into()); + } + impl_benchmark_test_suite!(Assets, crate::mock::new_test_ext(), crate::mock::Test) } diff --git a/frame/assets/src/extra_mutator.rs b/frame/assets/src/extra_mutator.rs index b72bfa86df5b..2a44df5f0c66 100644 --- a/frame/assets/src/extra_mutator.rs +++ b/frame/assets/src/extra_mutator.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,7 +62,7 @@ impl, I: 'static> ExtraMutator { id: T::AssetId, who: impl sp_std::borrow::Borrow, ) -> Option> { - if let Some(a) = Account::::get(id, who.borrow()) { + if let Some(a) = Account::::get(&id, who.borrow()) { Some(ExtraMutator:: { id, who: who.borrow().clone(), @@ -77,7 +77,7 @@ impl, I: 'static> ExtraMutator { /// Commit any changes to storage. pub fn commit(&mut self) -> Result<(), ()> { if let Some(extra) = self.pending.take() { - Account::::try_mutate(self.id, self.who.borrow(), |maybe_account| { + Account::::try_mutate(&self.id, &self.who, |maybe_account| { maybe_account.as_mut().ok_or(()).map(|account| account.extra = extra) }) } else { @@ -88,7 +88,7 @@ impl, I: 'static> ExtraMutator { /// Revert any changes, even those already committed by `self` and drop self. pub fn revert(mut self) -> Result<(), ()> { self.pending = None; - Account::::try_mutate(self.id, self.who.borrow(), |maybe_account| { + Account::::try_mutate(&self.id, &self.who, |maybe_account| { maybe_account .as_mut() .ok_or(()) diff --git a/frame/assets/src/functions.rs b/frame/assets/src/functions.rs index b540f02b87c8..c2c1b6839060 100644 --- a/frame/assets/src/functions.rs +++ b/frame/assets/src/functions.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ //! Functions for the Assets pallet. use super::*; -use frame_support::{traits::Get, BoundedVec}; +use frame_support::{defensive, traits::Get, BoundedVec}; #[must_use] pub(super) enum DeadConsequence { @@ -66,17 +66,28 @@ impl, I: 'static> Pallet { pub(super) fn new_account( who: &T::AccountId, d: &mut AssetDetails>, - maybe_deposit: Option>, - ) -> Result>, DispatchError> { + maybe_deposit: Option<(&T::AccountId, DepositBalanceOf)>, + ) -> Result, DispatchError> { let accounts = d.accounts.checked_add(1).ok_or(ArithmeticError::Overflow)?; - let reason = if let Some(deposit) = maybe_deposit { - ExistenceReason::DepositHeld(deposit) + let reason = if let Some((depositor, deposit)) = maybe_deposit { + if depositor == who { + ExistenceReason::DepositHeld(deposit) + } else { + ExistenceReason::DepositFrom(depositor.clone(), deposit) + } } else if d.is_sufficient { frame_system::Pallet::::inc_sufficients(who); d.sufficients += 1; ExistenceReason::Sufficient } else { - frame_system::Pallet::::inc_consumers(who).map_err(|_| Error::::NoProvider)?; + frame_system::Pallet::::inc_consumers(who) + .map_err(|_| Error::::UnavailableConsumer)?; + // We ensure that we can still increment consumers once more because we could otherwise + // allow accidental usage of all consumer references which could cause grief. + if !frame_system::Pallet::::can_inc_consumer(who) { + frame_system::Pallet::::dec_consumers(who); + return Err(Error::::UnavailableConsumer.into()) + } ExistenceReason::Consumer }; d.accounts = accounts; @@ -86,18 +97,19 @@ impl, I: 'static> Pallet { pub(super) fn dead_account( who: &T::AccountId, d: &mut AssetDetails>, - reason: &ExistenceReason>, + reason: &ExistenceReasonOf, force: bool, ) -> DeadConsequence { + use ExistenceReason::*; match *reason { - ExistenceReason::Consumer => frame_system::Pallet::::dec_consumers(who), - ExistenceReason::Sufficient => { + Consumer => frame_system::Pallet::::dec_consumers(who), + Sufficient => { d.sufficients = d.sufficients.saturating_sub(1); frame_system::Pallet::::dec_sufficients(who); }, - ExistenceReason::DepositRefunded => {}, - ExistenceReason::DepositHeld(_) if !force => return Keep, - ExistenceReason::DepositHeld(_) => {}, + DepositRefunded => {}, + DepositHeld(_) | DepositFrom(..) if !force => return Keep, + DepositHeld(_) | DepositFrom(..) => {}, } d.accounts = d.accounts.saturating_sub(1); Remove @@ -116,22 +128,25 @@ impl, I: 'static> Pallet { amount: T::Balance, increase_supply: bool, ) -> DepositConsequence { - let details = match Asset::::get(id) { + let details = match Asset::::get(&id) { Some(details) => details, None => return DepositConsequence::UnknownAsset, }; if increase_supply && details.supply.checked_add(&amount).is_none() { return DepositConsequence::Overflow } - if let Some(balance) = Self::maybe_balance(id, who) { - if balance.checked_add(&amount).is_none() { + if let Some(account) = Account::::get(id, who) { + if account.status.is_blocked() { + return DepositConsequence::Blocked + } + if account.balance.checked_add(&amount).is_none() { return DepositConsequence::Overflow } } else { if amount < details.min_balance { return DepositConsequence::BelowMinimum } - if !details.is_sufficient && !frame_system::Pallet::::can_inc_consumer(who) { + if !details.is_sufficient && !frame_system::Pallet::::can_accrue_consumers(who, 2) { return DepositConsequence::CannotCreate } if details.is_sufficient && details.sufficients.checked_add(1).is_none() { @@ -150,7 +165,7 @@ impl, I: 'static> Pallet { keep_alive: bool, ) -> WithdrawConsequence { use WithdrawConsequence::*; - let details = match Asset::::get(id) { + let details = match Asset::::get(&id) { Some(details) => details, None => return UnknownAsset, }; @@ -163,15 +178,15 @@ impl, I: 'static> Pallet { if amount.is_zero() { return Success } - let account = match Account::::get(id, who) { + let account = match Account::::get(&id, who) { Some(a) => a, - None => return NoFunds, + None => return BalanceLow, }; - if account.is_frozen { + if account.status.is_frozen() { return Frozen } if let Some(rest) = account.balance.checked_sub(&amount) { - if let Some(frozen) = T::Freezer::frozen_balance(id, who) { + if let Some(frozen) = T::Freezer::frozen_balance(id.clone(), who) { match frozen.checked_add(&details.min_balance) { Some(required) if rest < required => return Frozen, None => return Overflow, @@ -179,12 +194,8 @@ impl, I: 'static> Pallet { } } - let is_provider = false; - let is_required = is_provider && !frame_system::Pallet::::can_dec_provider(who); - let must_keep_alive = keep_alive || is_required; - if rest < details.min_balance { - if must_keep_alive { + if keep_alive { WouldDie } else { ReducedToZero(rest) @@ -193,7 +204,7 @@ impl, I: 'static> Pallet { Success } } else { - NoFunds + BalanceLow } } @@ -204,11 +215,11 @@ impl, I: 'static> Pallet { who: &T::AccountId, keep_alive: bool, ) -> Result { - let details = Asset::::get(id).ok_or(Error::::Unknown)?; + let details = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); - let account = Account::::get(id, who).ok_or(Error::::NoAccount)?; - ensure!(!account.is_frozen, Error::::Frozen); + let account = Account::::get(&id, who).ok_or(Error::::NoAccount)?; + ensure!(!account.status.is_frozen(), Error::::Frozen); let amount = if let Some(frozen) = T::Freezer::frozen_balance(id, who) { // Frozen balance: account CANNOT be deleted @@ -216,9 +227,7 @@ impl, I: 'static> Pallet { frozen.checked_add(&details.min_balance).ok_or(ArithmeticError::Overflow)?; account.balance.saturating_sub(required) } else { - let is_provider = false; - let is_required = is_provider && !frame_system::Pallet::::can_dec_provider(who); - if keep_alive || is_required { + if keep_alive { // We want to keep the account around. account.balance.saturating_sub(details.min_balance) } else { @@ -250,11 +259,11 @@ impl, I: 'static> Pallet { amount: T::Balance, f: DebitFlags, ) -> Result { - let actual = Self::reducible_balance(id, target, f.keep_alive)?.min(amount); + let actual = Self::reducible_balance(id.clone(), target, f.keep_alive)?.min(amount); ensure!(f.best_effort || actual >= amount, Error::::BalanceLow); let conseq = Self::can_decrease(id, target, actual, f.keep_alive); - let actual = match conseq.into_result() { + let actual = match conseq.into_result(f.keep_alive) { Ok(dust) => actual.saturating_add(dust), //< guaranteed by reducible_balance Err(e) => { debug_assert!(false, "passed from reducible_balance; qed"); @@ -295,43 +304,63 @@ impl, I: 'static> Pallet { Ok((credit, maybe_burn)) } - /// Creates a account for `who` to hold asset `id` with a zero balance and takes a deposit. - pub(super) fn do_touch(id: T::AssetId, who: T::AccountId) -> DispatchResult { - ensure!(!Account::::contains_key(id, &who), Error::::AlreadyExists); + /// Creates an account for `who` to hold asset `id` with a zero balance and takes a deposit. + /// + /// When `check_depositor` is set to true, the depositor must be either the asset's Admin or + /// Freezer, otherwise the depositor can be any account. + pub(super) fn do_touch( + id: T::AssetId, + who: T::AccountId, + depositor: T::AccountId, + check_depositor: bool, + ) -> DispatchResult { + ensure!(!Account::::contains_key(&id, &who), Error::::AlreadyExists); let deposit = T::AssetAccountDeposit::get(); let mut details = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); - let reason = Self::new_account(&who, &mut details, Some(deposit))?; - T::Currency::reserve(&who, deposit)?; + ensure!( + !check_depositor || &depositor == &details.admin || &depositor == &details.freezer, + Error::::NoPermission + ); + let reason = Self::new_account(&who, &mut details, Some((&depositor, deposit)))?; + T::Currency::reserve(&depositor, deposit)?; Asset::::insert(&id, details); Account::::insert( - id, + &id, &who, AssetAccountOf:: { balance: Zero::zero(), - is_frozen: false, + status: AccountStatus::Liquid, reason, extra: T::Extra::default(), }, ); + Self::deposit_event(Event::Touched { asset_id: id, who, depositor }); Ok(()) } - /// Returns a deposit, destroying an asset-account. + /// Returns a deposit or a consumer reference, destroying an asset-account. + /// Non-zero balance accounts refunded and destroyed only if `allow_burn` is true. pub(super) fn do_refund(id: T::AssetId, who: T::AccountId, allow_burn: bool) -> DispatchResult { - let mut account = Account::::get(id, &who).ok_or(Error::::NoDeposit)?; - let deposit = account.reason.take_deposit().ok_or(Error::::NoDeposit)?; + use AssetStatus::*; + use ExistenceReason::*; + let mut account = Account::::get(&id, &who).ok_or(Error::::NoDeposit)?; + ensure!(matches!(account.reason, Consumer | DepositHeld(..)), Error::::NoDeposit); let mut details = Asset::::get(&id).ok_or(Error::::Unknown)?; - ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); + ensure!(matches!(details.status, Live | Frozen), Error::::IncorrectStatus); ensure!(account.balance.is_zero() || allow_burn, Error::::WouldBurn); - ensure!(!account.is_frozen, Error::::Frozen); - T::Currency::unreserve(&who, deposit); + if let Some(deposit) = account.reason.take_deposit() { + T::Currency::unreserve(&who, deposit); + } if let Remove = Self::dead_account(&who, &mut details, &account.reason, false) { - Account::::remove(id, &who); + Account::::remove(&id, &who); } else { debug_assert!(false, "refund did not result in dead account?!"); + // deposit may have been refunded, need to update `Account` + Account::::insert(id, &who, account); + return Ok(()) } Asset::::insert(&id, details); // Executing a hook here is safe, since it is not in a `mutate`. @@ -339,6 +368,37 @@ impl, I: 'static> Pallet { Ok(()) } + /// Returns a `DepositFrom` of an account only if balance is zero. + pub(super) fn do_refund_other( + id: T::AssetId, + who: &T::AccountId, + caller: &T::AccountId, + ) -> DispatchResult { + let mut account = Account::::get(&id, &who).ok_or(Error::::NoDeposit)?; + let (depositor, deposit) = + account.reason.take_deposit_from().ok_or(Error::::NoDeposit)?; + let mut details = Asset::::get(&id).ok_or(Error::::Unknown)?; + ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); + ensure!(!account.status.is_frozen(), Error::::Frozen); + ensure!(caller == &depositor || caller == &details.admin, Error::::NoPermission); + ensure!(account.balance.is_zero(), Error::::WouldBurn); + + T::Currency::unreserve(&depositor, deposit); + + if let Remove = Self::dead_account(&who, &mut details, &account.reason, false) { + Account::::remove(&id, &who); + } else { + debug_assert!(false, "refund did not result in dead account?!"); + // deposit may have been refunded, need to update `Account` + Account::::insert(&id, &who, account); + return Ok(()) + } + Asset::::insert(&id, details); + // Executing a hook here is safe, since it is not in a `mutate`. + T::Freezer::died(id, &who); + return Ok(()) + } + /// Increases the asset `id` balance of `beneficiary` by `amount`. /// /// This alters the registered supply of the asset and emits an event. @@ -350,7 +410,7 @@ impl, I: 'static> Pallet { amount: T::Balance, maybe_check_issuer: Option, ) -> DispatchResult { - Self::increase_balance(id, beneficiary, amount, |details| -> DispatchResult { + Self::increase_balance(id.clone(), beneficiary, amount, |details| -> DispatchResult { if let Some(check_issuer) = maybe_check_issuer { ensure!(check_issuer == details.issuer, Error::::NoPermission); } @@ -384,24 +444,25 @@ impl, I: 'static> Pallet { return Ok(()) } - Self::can_increase(id, beneficiary, amount, true).into_result()?; - Asset::::try_mutate(id, |maybe_details| -> DispatchResult { + Self::can_increase(id.clone(), beneficiary, amount, true).into_result()?; + Asset::::try_mutate(&id, |maybe_details| -> DispatchResult { let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); check(details)?; - Account::::try_mutate(id, beneficiary, |maybe_account| -> DispatchResult { + Account::::try_mutate(&id, beneficiary, |maybe_account| -> DispatchResult { match maybe_account { Some(ref mut account) => { account.balance.saturating_accrue(amount); }, maybe_account @ None => { - // Note this should never fail as it's already checked by `can_increase`. + // Note this should never fail as it's already checked by + // `can_increase`. ensure!(amount >= details.min_balance, TokenError::BelowMinimum); *maybe_account = Some(AssetAccountOf:: { balance: amount, reason: Self::new_account(beneficiary, details, None)?, - is_frozen: false, + status: AccountStatus::Liquid, extra: T::Extra::default(), }); }, @@ -427,13 +488,13 @@ impl, I: 'static> Pallet { maybe_check_admin: Option, f: DebitFlags, ) -> Result { - let d = Asset::::get(id).ok_or(Error::::Unknown)?; + let d = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!( d.status == AssetStatus::Live || d.status == AssetStatus::Frozen, Error::::AssetNotLive ); - let actual = Self::decrease_balance(id, target, amount, f, |actual, details| { + let actual = Self::decrease_balance(id.clone(), target, amount, f, |actual, details| { // Check admin rights. if let Some(check_admin) = maybe_check_admin { ensure!(check_admin == details.admin, Error::::NoPermission); @@ -470,17 +531,17 @@ impl, I: 'static> Pallet { return Ok(amount) } - let details = Asset::::get(id).ok_or(Error::::Unknown)?; + let details = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); - let actual = Self::prep_debit(id, target, amount, f)?; + let actual = Self::prep_debit(id.clone(), target, amount, f)?; let mut target_died: Option = None; - Asset::::try_mutate(id, |maybe_details| -> DispatchResult { + Asset::::try_mutate(&id, |maybe_details| -> DispatchResult { let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; check(actual, details)?; - Account::::try_mutate(id, target, |maybe_account| -> DispatchResult { + Account::::try_mutate(&id, target, |maybe_account| -> DispatchResult { let mut account = maybe_account.take().ok_or(Error::::NoAccount)?; debug_assert!(account.balance >= actual, "checked in prep; qed"); @@ -524,7 +585,7 @@ impl, I: 'static> Pallet { f: TransferFlags, ) -> Result { let (balance, died) = - Self::transfer_and_die(id, source, dest, amount, maybe_need_admin, f)?; + Self::transfer_and_die(id.clone(), source, dest, amount, maybe_need_admin, f)?; if let Some(Remove) = died { T::Freezer::died(id, source); } @@ -545,18 +606,18 @@ impl, I: 'static> Pallet { if amount.is_zero() { return Ok((amount, None)) } - let details = Asset::::get(id).ok_or(Error::::Unknown)?; + let details = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); // Figure out the debit and credit, together with side-effects. - let debit = Self::prep_debit(id, source, amount, f.into())?; - let (credit, maybe_burn) = Self::prep_credit(id, dest, amount, debit, f.burn_dust)?; + let debit = Self::prep_debit(id.clone(), source, amount, f.into())?; + let (credit, maybe_burn) = Self::prep_credit(id.clone(), dest, amount, debit, f.burn_dust)?; let mut source_account = - Account::::get(id, &source).ok_or(Error::::NoAccount)?; + Account::::get(&id, &source).ok_or(Error::::NoAccount)?; let mut source_died: Option = None; - Asset::::try_mutate(id, |maybe_details| -> DispatchResult { + Asset::::try_mutate(&id, |maybe_details| -> DispatchResult { let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; // Check admin rights. @@ -581,7 +642,7 @@ impl, I: 'static> Pallet { debug_assert!(source_account.balance >= debit, "checked in prep; qed"); source_account.balance = source_account.balance.saturating_sub(debit); - Account::::try_mutate(id, &dest, |maybe_account| -> DispatchResult { + Account::::try_mutate(&id, &dest, |maybe_account| -> DispatchResult { match maybe_account { Some(ref mut account) => { // Calculate new balance; this will not saturate since it's already checked @@ -595,7 +656,7 @@ impl, I: 'static> Pallet { maybe_account @ None => { *maybe_account = Some(AssetAccountOf:: { balance: credit, - is_frozen: false, + status: AccountStatus::Liquid, reason: Self::new_account(dest, details, None)?, extra: T::Extra::default(), }); @@ -610,11 +671,11 @@ impl, I: 'static> Pallet { source_died = Some(Self::dead_account(source, details, &source_account.reason, false)); if let Some(Remove) = source_died { - Account::::remove(id, &source); + Account::::remove(&id, &source); return Ok(()) } } - Account::::insert(id, &source, &source_account); + Account::::insert(&id, &source, &source_account); Ok(()) })?; @@ -641,11 +702,11 @@ impl, I: 'static> Pallet { is_sufficient: bool, min_balance: T::Balance, ) -> DispatchResult { - ensure!(!Asset::::contains_key(id), Error::::InUse); + ensure!(!Asset::::contains_key(&id), Error::::InUse); ensure!(!min_balance.is_zero(), Error::::MinBalanceZero); Asset::::insert( - id, + &id, AssetDetails { owner: owner.clone(), issuer: owner.clone(), @@ -661,8 +722,8 @@ impl, I: 'static> Pallet { status: AssetStatus::Live, }, ); + ensure!(T::CallbackHandle::created(&id, &owner).is_ok(), Error::::CallbackFailed); Self::deposit_event(Event::ForceCreated { asset_id: id, owner: owner.clone() }); - T::CallbackHandle::created(&id, &owner); Ok(()) } @@ -672,8 +733,8 @@ impl, I: 'static> Pallet { id: T::AssetId, maybe_check_owner: Option, ) -> DispatchResult { - Asset::::try_mutate_exists(id, |maybe_details| -> Result<(), DispatchError> { - let mut details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + Asset::::try_mutate_exists(id.clone(), |maybe_details| -> Result<(), DispatchError> { + let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; if let Some(check_owner) = maybe_check_owner { ensure!(details.owner == check_owner, Error::::NoPermission); } @@ -695,15 +756,26 @@ impl, I: 'static> Pallet { let mut dead_accounts: Vec = vec![]; let mut remaining_accounts = 0; let _ = - Asset::::try_mutate_exists(id, |maybe_details| -> Result<(), DispatchError> { + Asset::::try_mutate_exists(&id, |maybe_details| -> Result<(), DispatchError> { let mut details = maybe_details.as_mut().ok_or(Error::::Unknown)?; // Should only destroy accounts while the asset is in a destroying state ensure!(details.status == AssetStatus::Destroying, Error::::IncorrectStatus); - - for (who, v) in Account::::drain_prefix(id) { - let _ = Self::dead_account(&who, &mut details, &v.reason, true); - dead_accounts.push(who); - if dead_accounts.len() >= (max_items as usize) { + for (i, (who, mut v)) in Account::::iter_prefix(&id).enumerate() { + // unreserve the existence deposit if any + if let Some((depositor, deposit)) = v.reason.take_deposit_from() { + T::Currency::unreserve(&depositor, deposit); + } else if let Some(deposit) = v.reason.take_deposit() { + T::Currency::unreserve(&who, deposit); + } + if let Remove = Self::dead_account(&who, &mut details, &v.reason, false) { + Account::::remove(&id, &who); + dead_accounts.push(who); + } else { + // deposit may have been released, need to update `Account` + Account::::insert(&id, &who, v); + defensive!("destroy did not result in dead account?!"); + } + if i + 1 >= (max_items as usize) { break } } @@ -712,7 +784,7 @@ impl, I: 'static> Pallet { })?; for who in &dead_accounts { - T::Freezer::died(id, &who); + T::Freezer::died(id.clone(), &who); } Self::deposit_event(Event::AccountsDestroyed { @@ -732,14 +804,15 @@ impl, I: 'static> Pallet { max_items: u32, ) -> Result { let mut removed_approvals = 0; - let _ = - Asset::::try_mutate_exists(id, |maybe_details| -> Result<(), DispatchError> { - let mut details = maybe_details.as_mut().ok_or(Error::::Unknown)?; + let _ = Asset::::try_mutate_exists( + id.clone(), + |maybe_details| -> Result<(), DispatchError> { + let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; // Should only destroy accounts while the asset is in a destroying state. ensure!(details.status == AssetStatus::Destroying, Error::::IncorrectStatus); - for ((owner, _), approval) in Approvals::::drain_prefix((id,)) { + for ((owner, _), approval) in Approvals::::drain_prefix((id.clone(),)) { T::Currency::unreserve(&owner, approval.deposit); removed_approvals = removed_approvals.saturating_add(1); details.approvals = details.approvals.saturating_sub(1); @@ -752,9 +825,9 @@ impl, I: 'static> Pallet { approvals_destroyed: removed_approvals as u32, approvals_remaining: details.approvals as u32, }); - T::CallbackHandle::destroyed(&id); Ok(()) - })?; + }, + )?; Ok(removed_approvals) } @@ -762,11 +835,12 @@ impl, I: 'static> Pallet { /// /// On success, the `Event::Destroyed` event is emitted. pub(super) fn do_finish_destroy(id: T::AssetId) -> DispatchResult { - Asset::::try_mutate_exists(id, |maybe_details| -> Result<(), DispatchError> { + Asset::::try_mutate_exists(id.clone(), |maybe_details| -> Result<(), DispatchError> { let details = maybe_details.take().ok_or(Error::::Unknown)?; ensure!(details.status == AssetStatus::Destroying, Error::::IncorrectStatus); ensure!(details.accounts == 0, Error::::InUse); ensure!(details.approvals == 0, Error::::InUse); + ensure!(T::CallbackHandle::destroyed(&id).is_ok(), Error::::CallbackFailed); let metadata = Metadata::::take(&id); T::Currency::unreserve( @@ -789,10 +863,10 @@ impl, I: 'static> Pallet { delegate: &T::AccountId, amount: T::Balance, ) -> DispatchResult { - let mut d = Asset::::get(id).ok_or(Error::::Unknown)?; + let mut d = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); Approvals::::try_mutate( - (id, &owner, &delegate), + (id.clone(), &owner, &delegate), |maybe_approved| -> DispatchResult { let mut approved = match maybe_approved.take() { // an approval already exists and is being updated @@ -813,7 +887,7 @@ impl, I: 'static> Pallet { Ok(()) }, )?; - Asset::::insert(id, d); + Asset::::insert(&id, d); Self::deposit_event(Event::ApprovedTransfer { asset_id: id, source: owner.clone(), @@ -840,22 +914,23 @@ impl, I: 'static> Pallet { ) -> DispatchResult { let mut owner_died: Option = None; - let d = Asset::::get(id).ok_or(Error::::Unknown)?; + let d = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); Approvals::::try_mutate_exists( - (id, &owner, delegate), + (id.clone(), &owner, delegate), |maybe_approved| -> DispatchResult { let mut approved = maybe_approved.take().ok_or(Error::::Unapproved)?; let remaining = approved.amount.checked_sub(&amount).ok_or(Error::::Unapproved)?; let f = TransferFlags { keep_alive: false, best_effort: false, burn_dust: false }; - owner_died = Self::transfer_and_die(id, owner, destination, amount, None, f)?.1; + owner_died = + Self::transfer_and_die(id.clone(), owner, destination, amount, None, f)?.1; if remaining.is_zero() { T::Currency::unreserve(owner, approved.deposit); - Asset::::mutate(id, |maybe_details| { + Asset::::mutate(id.clone(), |maybe_details| { if let Some(details) = maybe_details { details.approvals.saturating_dec(); } @@ -888,17 +963,15 @@ impl, I: 'static> Pallet { let bounded_symbol: BoundedVec = symbol.clone().try_into().map_err(|_| Error::::BadMetadata)?; - let d = Asset::::get(id).ok_or(Error::::Unknown)?; + let d = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); ensure!(from == &d.owner, Error::::NoPermission); - Metadata::::try_mutate_exists(id, |metadata| { + Metadata::::try_mutate_exists(id.clone(), |metadata| { ensure!(metadata.as_ref().map_or(true, |m| !m.is_frozen), Error::::NoPermission); let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); - let new_deposit = T::MetadataDepositPerByte::get() - .saturating_mul(((name.len() + symbol.len()) as u32).into()) - .saturating_add(T::MetadataDepositBase::get()); + let new_deposit = Self::calc_metadata_deposit(&name, &symbol); if new_deposit > old_deposit { T::Currency::reserve(from, new_deposit - old_deposit)?; @@ -924,4 +997,20 @@ impl, I: 'static> Pallet { Ok(()) }) } + + /// Calculate the metadata deposit for the provided data. + pub(super) fn calc_metadata_deposit(name: &[u8], symbol: &[u8]) -> DepositBalanceOf { + T::MetadataDepositPerByte::get() + .saturating_mul(((name.len() + symbol.len()) as u32).into()) + .saturating_add(T::MetadataDepositBase::get()) + } + + /// Returns all the non-zero balances for all assets of the given `account`. + pub fn account_balances(account: T::AccountId) -> Vec<(T::AssetId, T::Balance)> { + Asset::::iter_keys() + .filter_map(|id| { + Self::maybe_balance(id.clone(), account.clone()).map(|balance| (id, balance)) + }) + .collect::>() + } } diff --git a/frame/assets/src/impl_fungibles.rs b/frame/assets/src/impl_fungibles.rs index b10b8c6b1075..a7df3d154ca8 100644 --- a/frame/assets/src/impl_fungibles.rs +++ b/frame/assets/src/impl_fungibles.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,6 +17,16 @@ //! Implementations for fungibles trait. +use frame_support::{ + defensive, + traits::tokens::{ + Fortitude, + Precision::{self, BestEffort}, + Preservation::{self, Expendable}, + Provenance::{self, Minted}, + }, +}; + use super::*; impl, I: 'static> fungibles::Inspect<::AccountId> for Pallet { @@ -35,21 +45,27 @@ impl, I: 'static> fungibles::Inspect<::AccountId Pallet::::balance(asset, who) } + fn total_balance(asset: Self::AssetId, who: &::AccountId) -> Self::Balance { + Pallet::::balance(asset, who) + } + fn reducible_balance( asset: Self::AssetId, who: &::AccountId, - keep_alive: bool, + preservation: Preservation, + _: Fortitude, ) -> Self::Balance { - Pallet::::reducible_balance(asset, who, keep_alive).unwrap_or(Zero::zero()) + Pallet::::reducible_balance(asset, who, !matches!(preservation, Expendable)) + .unwrap_or(Zero::zero()) } fn can_deposit( asset: Self::AssetId, who: &::AccountId, amount: Self::Balance, - mint: bool, + provenance: Provenance, ) -> DepositConsequence { - Pallet::::can_increase(asset, who, amount, mint) + Pallet::::can_increase(asset, who, amount, provenance == Minted) } fn can_withdraw( @@ -65,69 +81,57 @@ impl, I: 'static> fungibles::Inspect<::AccountId } } -impl, I: 'static> fungibles::InspectMetadata<::AccountId> - for Pallet -{ - /// Return the name of an asset. - fn name(asset: &Self::AssetId) -> Vec { - Metadata::::get(asset).name.to_vec() - } - - /// Return the symbol of an asset. - fn symbol(asset: &Self::AssetId) -> Vec { - Metadata::::get(asset).symbol.to_vec() - } - - /// Return the decimals of an asset. - fn decimals(asset: &Self::AssetId) -> u8 { - Metadata::::get(asset).decimals - } -} - impl, I: 'static> fungibles::Mutate<::AccountId> for Pallet { - fn mint_into( - asset: Self::AssetId, - who: &::AccountId, + fn done_mint_into( + asset_id: Self::AssetId, + beneficiary: &::AccountId, amount: Self::Balance, - ) -> DispatchResult { - Self::do_mint(asset, who, amount, None) + ) { + Self::deposit_event(Event::Issued { asset_id, owner: beneficiary.clone(), amount }) } - fn burn_from( - asset: Self::AssetId, - who: &::AccountId, - amount: Self::Balance, - ) -> Result { - let f = DebitFlags { keep_alive: false, best_effort: false }; - Self::do_burn(asset, who, amount, None, f) + fn done_burn_from( + asset_id: Self::AssetId, + target: &::AccountId, + balance: Self::Balance, + ) { + Self::deposit_event(Event::Burned { asset_id, owner: target.clone(), balance }); } - fn slash( - asset: Self::AssetId, - who: &::AccountId, + fn done_transfer( + asset_id: Self::AssetId, + source: &::AccountId, + dest: &::AccountId, amount: Self::Balance, - ) -> Result { - let f = DebitFlags { keep_alive: false, best_effort: true }; - Self::do_burn(asset, who, amount, None, f) + ) { + Self::deposit_event(Event::Transferred { + asset_id, + from: source.clone(), + to: dest.clone(), + amount, + }); } } -impl, I: 'static> fungibles::Transfer for Pallet { - fn transfer( - asset: Self::AssetId, - source: &T::AccountId, - dest: &T::AccountId, - amount: T::Balance, - keep_alive: bool, - ) -> Result { - let f = TransferFlags { keep_alive, best_effort: false, burn_dust: false }; - Self::do_transfer(asset, source, dest, amount, None, f) - } +impl, I: 'static> fungibles::Balanced<::AccountId> + for Pallet +{ + type OnDropCredit = fungibles::DecreaseIssuance; + type OnDropDebt = fungibles::IncreaseIssuance; } impl, I: 'static> fungibles::Unbalanced for Pallet { - fn set_balance(_: Self::AssetId, _: &T::AccountId, _: Self::Balance) -> DispatchResult { - unreachable!("set_balance is not used if other functions are impl'd"); + fn handle_raw_dust(_: Self::AssetId, _: Self::Balance) {} + fn handle_dust(_: fungibles::Dust) { + defensive!("`decrease_balance` and `increase_balance` have non-default impls; nothing else calls this; qed"); + } + fn write_balance( + _: Self::AssetId, + _: &T::AccountId, + _: Self::Balance, + ) -> Result, DispatchError> { + defensive!("write_balance is not used if other functions are impl'd"); + Err(DispatchError::Unavailable) } fn set_total_issuance(id: T::AssetId, amount: Self::Balance) { Asset::::mutate_exists(id, |maybe_asset| { @@ -140,36 +144,27 @@ impl, I: 'static> fungibles::Unbalanced for Pallet Result { - let f = DebitFlags { keep_alive: false, best_effort: false }; + let f = DebitFlags { + keep_alive: preservation != Expendable, + best_effort: precision == BestEffort, + }; Self::decrease_balance(asset, who, amount, f, |_, _| Ok(())) } - fn decrease_balance_at_most( - asset: T::AssetId, - who: &T::AccountId, - amount: Self::Balance, - ) -> Self::Balance { - let f = DebitFlags { keep_alive: false, best_effort: true }; - Self::decrease_balance(asset, who, amount, f, |_, _| Ok(())).unwrap_or(Zero::zero()) - } fn increase_balance( asset: T::AssetId, who: &T::AccountId, amount: Self::Balance, + _: Precision, ) -> Result { Self::increase_balance(asset, who, amount, |_| Ok(()))?; Ok(amount) } - fn increase_balance_at_most( - asset: T::AssetId, - who: &T::AccountId, - amount: Self::Balance, - ) -> Self::Balance { - match Self::increase_balance(asset, who, amount, |_| Ok(())) { - Ok(()) => amount, - Err(_) => Zero::zero(), - } - } + + // TODO: #13196 implement deactivate/reactivate once we have inactive balance tracking. } impl, I: 'static> fungibles::Create for Pallet { @@ -231,6 +226,19 @@ impl, I: 'static> fungibles::metadata::Mutate<:: } } +impl, I: 'static> + fungibles::metadata::MetadataDeposit< + ::AccountId>>::Balance, + > for Pallet +{ + fn calc_metadata_deposit( + name: &[u8], + symbol: &[u8], + ) -> ::AccountId>>::Balance { + Self::calc_metadata_deposit(&name, &symbol) + } +} + impl, I: 'static> fungibles::approvals::Inspect<::AccountId> for Pallet { diff --git a/frame/assets/src/impl_stored_map.rs b/frame/assets/src/impl_stored_map.rs index a4669c776ed4..a7a5a0859f70 100644 --- a/frame/assets/src/impl_stored_map.rs +++ b/frame/assets/src/impl_stored_map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,7 @@ use super::*; impl, I: 'static> StoredMap<(T::AssetId, T::AccountId), T::Extra> for Pallet { fn get(id_who: &(T::AssetId, T::AccountId)) -> T::Extra { - let &(id, ref who) = id_who; + let (id, who) = id_who; Account::::get(id, who).map(|a| a.extra).unwrap_or_default() } @@ -29,7 +29,7 @@ impl, I: 'static> StoredMap<(T::AssetId, T::AccountId), T::Extra> f id_who: &(T::AssetId, T::AccountId), f: impl FnOnce(&mut Option) -> Result, ) -> Result { - let &(id, ref who) = id_who; + let (id, who) = id_who; let mut maybe_extra = Account::::get(id, who).map(|a| a.extra); let r = f(&mut maybe_extra)?; // They want to write some value or delete it. diff --git a/frame/assets/src/lib.rs b/frame/assets/src/lib.rs index c2480a20636b..363a99701b56 100644 --- a/frame/assets/src/lib.rs +++ b/frame/assets/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -82,6 +82,10 @@ //! * `approve_transfer`: Create or increase an delegated transfer. //! * `cancel_approval`: Rescind a previous approval. //! * `transfer_approved`: Transfer third-party's assets to another account. +//! * `touch`: Create an asset account for non-provider assets. Caller must place a deposit. +//! * `refund`: Return the deposit (if any) of the caller's asset account or a consumer reference +//! (if any) of the caller's account. +//! * `refund_other`: Return the deposit (if any) of a specified asset account. //! //! ### Permissioned Functions //! @@ -98,12 +102,16 @@ //! * `burn`: Decreases the asset balance of an account; called by the asset class's Admin. //! * `force_transfer`: Transfers between arbitrary accounts; called by the asset class's Admin. //! * `freeze`: Disallows further `transfer`s from an account; called by the asset class's Freezer. -//! * `thaw`: Allows further `transfer`s from an account; called by the asset class's Admin. +//! * `thaw`: Allows further `transfer`s to and from an account; called by the asset class's Admin. //! * `transfer_ownership`: Changes an asset class's Owner; called by the asset class's Owner. //! * `set_team`: Changes an asset class's Admin, Freezer and Issuer; called by the asset class's //! Owner. //! * `set_metadata`: Set the metadata of an asset class; called by the asset class's Owner. //! * `clear_metadata`: Remove the metadata of an asset class; called by the asset class's Owner. +//! * `touch_other`: Create an asset account for specified account. Caller must place a deposit; +//! called by the asset class's Freezer or Admin. +//! * `block`: Disallows further `transfer`s to and from an account; called by the asset class's +//! Freezer. //! //! Please refer to the [`Call`] enum and its associated variants for documentation on each //! function. @@ -154,7 +162,7 @@ use sp_runtime::{ traits::{AtLeast32BitUnsigned, CheckedAdd, CheckedSub, Saturating, StaticLookup, Zero}, ArithmeticError, TokenError, }; -use sp_std::{borrow::Borrow, prelude::*}; +use sp_std::prelude::*; use frame_support::{ dispatch::{DispatchError, DispatchResult}, @@ -178,10 +186,14 @@ const LOG_TARGET: &str = "runtime::assets"; /// Trait with callbacks that are executed after successfull asset creation or destruction. pub trait AssetsCallback { /// Indicates that asset with `id` was successfully created by the `owner` - fn created(_id: &AssetId, _owner: &AccountId) {} + fn created(_id: &AssetId, _owner: &AccountId) -> Result<(), ()> { + Ok(()) + } /// Indicates that asset with `id` has just been destroyed - fn destroyed(_id: &AssetId) {} + fn destroyed(_id: &AssetId) -> Result<(), ()> { + Ok(()) + } } /// Empty implementation in case no callbacks are required. @@ -190,14 +202,16 @@ impl AssetsCallback for () {} #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::{ + pallet_prelude::*, + traits::{AccountTouch, ContainsPair}, + }; use frame_system::pallet_prelude::*; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); @@ -236,7 +250,7 @@ pub mod pallet { type RemoveItemsLimit: Get; /// Identifier for the class of asset. - type AssetId: Member + Parameter + Copy + MaybeSerializeDeserialize + MaxEncodedLen; + type AssetId: Member + Parameter + Clone + MaybeSerializeDeserialize + MaxEncodedLen; /// Wrapper around `Self::AssetId` to use in dispatchable call signatures. Allows the use /// of compact encoding in instances of the pallet, which will prevent breaking changes @@ -355,6 +369,7 @@ pub mod pallet { >; #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig, I: 'static = ()> { /// Genesis assets: id, owner, is_sufficient, min_balance pub assets: Vec<(T::AssetId, T::AccountId, bool, T::Balance)>, @@ -364,19 +379,8 @@ pub mod pallet { pub accounts: Vec<(T::AssetId, T::AccountId, T::Balance)>, } - #[cfg(feature = "std")] - impl, I: 'static> Default for GenesisConfig { - fn default() -> Self { - Self { - assets: Default::default(), - metadata: Default::default(), - accounts: Default::default(), - } - } - } - #[pallet::genesis_build] - impl, I: 'static> GenesisBuild for GenesisConfig { + impl, I: 'static> BuildGenesisConfig for GenesisConfig { fn build(&self) { for (id, owner, is_sufficient, min_balance) in &self.assets { assert!(!Asset::::contains_key(id), "Asset id already in use"); @@ -420,7 +424,7 @@ pub mod pallet { for (id, account_id, amount) in &self.accounts { let result = >::increase_balance( - *id, + id.clone(), account_id, *amount, |details| -> DispatchResult { @@ -514,6 +518,12 @@ pub mod pallet { }, /// An asset has had its attributes changed by the `Force` origin. AssetStatusChanged { asset_id: T::AssetId }, + /// The min_balance of an asset has been updated by the asset owner. + AssetMinBalanceChanged { asset_id: T::AssetId, new_min_balance: T::Balance }, + /// Some account `who` was created with a deposit from `depositor`. + Touched { asset_id: T::AssetId, who: T::AccountId, depositor: T::AccountId }, + /// Some account `who` was blocked. + Blocked { asset_id: T::AssetId, who: T::AccountId }, } #[pallet::error] @@ -535,9 +545,9 @@ pub mod pallet { /// Minimum balance should be non-zero. MinBalanceZero, /// Unable to increment the consumer reference counters on the account. Either no provider - /// reference exists to allow a non-zero balance of a non-self-sufficient asset, or the - /// maximum number of consumers has been reached. - NoProvider, + /// reference exists to allow a non-zero balance of a non-self-sufficient asset, or one + /// fewer then the maximum number of consumers has been reached. + UnavailableConsumer, /// Invalid metadata given. BadMetadata, /// No approval exists that would allow the transfer. @@ -559,9 +569,11 @@ pub mod pallet { IncorrectStatus, /// The asset should be frozen before the given operation. NotFrozen, + /// Callback action resulted in error + CallbackFailed, } - #[pallet::call] + #[pallet::call(weight(>::WeightInfo))] impl, I: 'static> Pallet { /// Issue a new class of fungible assets from a public origin. /// @@ -583,7 +595,6 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::create())] pub fn create( origin: OriginFor, id: T::AssetIdParameter, @@ -594,14 +605,14 @@ pub mod pallet { let owner = T::CreateOrigin::ensure_origin(origin, &id)?; let admin = T::Lookup::lookup(admin)?; - ensure!(!Asset::::contains_key(id), Error::::InUse); + ensure!(!Asset::::contains_key(&id), Error::::InUse); ensure!(!min_balance.is_zero(), Error::::MinBalanceZero); let deposit = T::AssetDeposit::get(); T::Currency::reserve(&owner, deposit)?; Asset::::insert( - id, + id.clone(), AssetDetails { owner: owner.clone(), issuer: admin.clone(), @@ -617,13 +628,12 @@ pub mod pallet { status: AssetStatus::Live, }, ); - + ensure!(T::CallbackHandle::created(&id, &owner).is_ok(), Error::::CallbackFailed); Self::deposit_event(Event::Created { asset_id: id, creator: owner.clone(), owner: admin, }); - T::CallbackHandle::created(&id, &owner); Ok(()) } @@ -648,7 +658,6 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::force_create())] pub fn force_create( origin: OriginFor, id: T::AssetIdParameter, @@ -674,7 +683,6 @@ pub mod pallet { /// /// The asset class must be frozen before calling `start_destroy`. #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::start_destroy())] pub fn start_destroy(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let maybe_check_owner = match T::ForceOrigin::try_origin(origin) { Ok(_) => None, @@ -743,7 +751,6 @@ pub mod pallet { /// /// Each successful call emits the `Event::Destroyed` event. #[pallet::call_index(5)] - #[pallet::weight(T::WeightInfo::finish_destroy())] pub fn finish_destroy(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let _ = ensure_signed(origin)?; let id: T::AssetId = id.into(); @@ -763,7 +770,6 @@ pub mod pallet { /// Weight: `O(1)` /// Modes: Pre-existing balance of `beneficiary`; Account pre-existence of `beneficiary`. #[pallet::call_index(6)] - #[pallet::weight(T::WeightInfo::mint())] pub fn mint( origin: OriginFor, id: T::AssetIdParameter, @@ -793,7 +799,6 @@ pub mod pallet { /// Weight: `O(1)` /// Modes: Post-existence of `who`; Pre & post Zombie-status of `who`. #[pallet::call_index(7)] - #[pallet::weight(T::WeightInfo::burn())] pub fn burn( origin: OriginFor, id: T::AssetIdParameter, @@ -828,7 +833,6 @@ pub mod pallet { /// Modes: Pre-existence of `target`; Post-existence of sender; Account pre-existence of /// `target`. #[pallet::call_index(8)] - #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( origin: OriginFor, id: T::AssetIdParameter, @@ -862,7 +866,6 @@ pub mod pallet { /// Modes: Pre-existence of `target`; Post-existence of sender; Account pre-existence of /// `target`. #[pallet::call_index(9)] - #[pallet::weight(T::WeightInfo::transfer_keep_alive())] pub fn transfer_keep_alive( origin: OriginFor, id: T::AssetIdParameter, @@ -897,7 +900,6 @@ pub mod pallet { /// Modes: Pre-existence of `dest`; Post-existence of `source`; Account pre-existence of /// `dest`. #[pallet::call_index(10)] - #[pallet::weight(T::WeightInfo::force_transfer())] pub fn force_transfer( origin: OriginFor, id: T::AssetIdParameter, @@ -914,7 +916,9 @@ pub mod pallet { Self::do_transfer(id, &source, &dest, amount, Some(origin), f).map(|_| ()) } - /// Disallow further unprivileged transfers from an account. + /// Disallow further unprivileged transfers of an asset `id` from an account `who`. `who` + /// must already exist as an entry in `Account`s of the asset. If you want to freeze an + /// account that does not have an entry, use `touch_other` first. /// /// Origin must be Signed and the sender should be the Freezer of the asset `id`. /// @@ -925,7 +929,6 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::call_index(11)] - #[pallet::weight(T::WeightInfo::freeze())] pub fn freeze( origin: OriginFor, id: T::AssetIdParameter, @@ -934,7 +937,7 @@ pub mod pallet { let origin = ensure_signed(origin)?; let id: T::AssetId = id.into(); - let d = Asset::::get(id).ok_or(Error::::Unknown)?; + let d = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!( d.status == AssetStatus::Live || d.status == AssetStatus::Frozen, Error::::AssetNotLive @@ -942,8 +945,9 @@ pub mod pallet { ensure!(origin == d.freezer, Error::::NoPermission); let who = T::Lookup::lookup(who)?; - Account::::try_mutate(id, &who, |maybe_account| -> DispatchResult { - maybe_account.as_mut().ok_or(Error::::NoAccount)?.is_frozen = true; + Account::::try_mutate(&id, &who, |maybe_account| -> DispatchResult { + maybe_account.as_mut().ok_or(Error::::NoAccount)?.status = + AccountStatus::Frozen; Ok(()) })?; @@ -951,7 +955,7 @@ pub mod pallet { Ok(()) } - /// Allow unprivileged transfers from an account again. + /// Allow unprivileged transfers to and from an account again. /// /// Origin must be Signed and the sender should be the Admin of the asset `id`. /// @@ -962,7 +966,6 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::call_index(12)] - #[pallet::weight(T::WeightInfo::thaw())] pub fn thaw( origin: OriginFor, id: T::AssetIdParameter, @@ -971,7 +974,7 @@ pub mod pallet { let origin = ensure_signed(origin)?; let id: T::AssetId = id.into(); - let details = Asset::::get(id).ok_or(Error::::Unknown)?; + let details = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!( details.status == AssetStatus::Live || details.status == AssetStatus::Frozen, Error::::AssetNotLive @@ -979,8 +982,9 @@ pub mod pallet { ensure!(origin == details.admin, Error::::NoPermission); let who = T::Lookup::lookup(who)?; - Account::::try_mutate(id, &who, |maybe_account| -> DispatchResult { - maybe_account.as_mut().ok_or(Error::::NoAccount)?.is_frozen = false; + Account::::try_mutate(&id, &who, |maybe_account| -> DispatchResult { + maybe_account.as_mut().ok_or(Error::::NoAccount)?.status = + AccountStatus::Liquid; Ok(()) })?; @@ -998,12 +1002,11 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::call_index(13)] - #[pallet::weight(T::WeightInfo::freeze_asset())] pub fn freeze_asset(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let origin = ensure_signed(origin)?; let id: T::AssetId = id.into(); - Asset::::try_mutate(id, |maybe_details| { + Asset::::try_mutate(id.clone(), |maybe_details| { let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); ensure!(origin == d.freezer, Error::::NoPermission); @@ -1025,12 +1028,11 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::call_index(14)] - #[pallet::weight(T::WeightInfo::thaw_asset())] pub fn thaw_asset(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let origin = ensure_signed(origin)?; let id: T::AssetId = id.into(); - Asset::::try_mutate(id, |maybe_details| { + Asset::::try_mutate(id.clone(), |maybe_details| { let d = maybe_details.as_mut().ok_or(Error::::Unknown)?; ensure!(origin == d.admin, Error::::NoPermission); ensure!(d.status == AssetStatus::Frozen, Error::::NotFrozen); @@ -1053,7 +1055,6 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::call_index(15)] - #[pallet::weight(T::WeightInfo::transfer_ownership())] pub fn transfer_ownership( origin: OriginFor, id: T::AssetIdParameter, @@ -1063,7 +1064,7 @@ pub mod pallet { let owner = T::Lookup::lookup(owner)?; let id: T::AssetId = id.into(); - Asset::::try_mutate(id, |maybe_details| { + Asset::::try_mutate(id.clone(), |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; ensure!(details.status == AssetStatus::Live, Error::::LiveAsset); ensure!(origin == details.owner, Error::::NoPermission); @@ -1071,7 +1072,7 @@ pub mod pallet { return Ok(()) } - let metadata_deposit = Metadata::::get(id).deposit; + let metadata_deposit = Metadata::::get(&id).deposit; let deposit = details.deposit + metadata_deposit; // Move the deposit to the new owner. @@ -1097,7 +1098,6 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::call_index(16)] - #[pallet::weight(T::WeightInfo::set_team())] pub fn set_team( origin: OriginFor, id: T::AssetIdParameter, @@ -1111,7 +1111,7 @@ pub mod pallet { let freezer = T::Lookup::lookup(freezer)?; let id: T::AssetId = id.into(); - Asset::::try_mutate(id, |maybe_details| { + Asset::::try_mutate(id.clone(), |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::Unknown)?; ensure!(details.status == AssetStatus::Live, Error::::AssetNotLive); ensure!(origin == details.owner, Error::::NoPermission); @@ -1167,16 +1167,15 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::call_index(18)] - #[pallet::weight(T::WeightInfo::clear_metadata())] pub fn clear_metadata(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { let origin = ensure_signed(origin)?; let id: T::AssetId = id.into(); - let d = Asset::::get(id).ok_or(Error::::Unknown)?; + let d = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); ensure!(origin == d.owner, Error::::NoPermission); - Metadata::::try_mutate_exists(id, |metadata| { + Metadata::::try_mutate_exists(id.clone(), |metadata| { let deposit = metadata.take().ok_or(Error::::Unknown)?.deposit; T::Currency::unreserve(&d.owner, deposit); Self::deposit_event(Event::MetadataCleared { asset_id: id }); @@ -1217,8 +1216,8 @@ pub mod pallet { let bounded_symbol: BoundedVec = symbol.clone().try_into().map_err(|_| Error::::BadMetadata)?; - ensure!(Asset::::contains_key(id), Error::::Unknown); - Metadata::::try_mutate_exists(id, |metadata| { + ensure!(Asset::::contains_key(&id), Error::::Unknown); + Metadata::::try_mutate_exists(id.clone(), |metadata| { let deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); *metadata = Some(AssetMetadata { deposit, @@ -1251,7 +1250,6 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::call_index(20)] - #[pallet::weight(T::WeightInfo::force_clear_metadata())] pub fn force_clear_metadata( origin: OriginFor, id: T::AssetIdParameter, @@ -1259,8 +1257,8 @@ pub mod pallet { T::ForceOrigin::ensure_origin(origin)?; let id: T::AssetId = id.into(); - let d = Asset::::get(id).ok_or(Error::::Unknown)?; - Metadata::::try_mutate_exists(id, |metadata| { + let d = Asset::::get(&id).ok_or(Error::::Unknown)?; + Metadata::::try_mutate_exists(id.clone(), |metadata| { let deposit = metadata.take().ok_or(Error::::Unknown)?.deposit; T::Currency::unreserve(&d.owner, deposit); Self::deposit_event(Event::MetadataCleared { asset_id: id }); @@ -1291,7 +1289,6 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::call_index(21)] - #[pallet::weight(T::WeightInfo::force_asset_status())] pub fn force_asset_status( origin: OriginFor, id: T::AssetIdParameter, @@ -1306,7 +1303,7 @@ pub mod pallet { T::ForceOrigin::ensure_origin(origin)?; let id: T::AssetId = id.into(); - Asset::::try_mutate(id, |maybe_asset| { + Asset::::try_mutate(id.clone(), |maybe_asset| { let mut asset = maybe_asset.take().ok_or(Error::::Unknown)?; ensure!(asset.status != AssetStatus::Destroying, Error::::AssetNotLive); asset.owner = T::Lookup::lookup(owner)?; @@ -1348,7 +1345,6 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::call_index(22)] - #[pallet::weight(T::WeightInfo::approve_transfer())] pub fn approve_transfer( origin: OriginFor, id: T::AssetIdParameter, @@ -1375,7 +1371,6 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::call_index(23)] - #[pallet::weight(T::WeightInfo::cancel_approval())] pub fn cancel_approval( origin: OriginFor, id: T::AssetIdParameter, @@ -1384,15 +1379,15 @@ pub mod pallet { let owner = ensure_signed(origin)?; let delegate = T::Lookup::lookup(delegate)?; let id: T::AssetId = id.into(); - let mut d = Asset::::get(id).ok_or(Error::::Unknown)?; + let mut d = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); - let approval = - Approvals::::take((id, &owner, &delegate)).ok_or(Error::::Unknown)?; + let approval = Approvals::::take((id.clone(), &owner, &delegate)) + .ok_or(Error::::Unknown)?; T::Currency::unreserve(&owner, approval.deposit); d.approvals.saturating_dec(); - Asset::::insert(id, d); + Asset::::insert(id.clone(), d); Self::deposit_event(Event::ApprovalCancelled { asset_id: id, owner, delegate }); Ok(()) @@ -1412,7 +1407,6 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::call_index(24)] - #[pallet::weight(T::WeightInfo::force_cancel_approval())] pub fn force_cancel_approval( origin: OriginFor, id: T::AssetIdParameter, @@ -1420,7 +1414,7 @@ pub mod pallet { delegate: AccountIdLookupOf, ) -> DispatchResult { let id: T::AssetId = id.into(); - let mut d = Asset::::get(id).ok_or(Error::::Unknown)?; + let mut d = Asset::::get(&id).ok_or(Error::::Unknown)?; ensure!(d.status == AssetStatus::Live, Error::::AssetNotLive); T::ForceOrigin::try_origin(origin) .map(|_| ()) @@ -1433,11 +1427,11 @@ pub mod pallet { let owner = T::Lookup::lookup(owner)?; let delegate = T::Lookup::lookup(delegate)?; - let approval = - Approvals::::take((id, &owner, &delegate)).ok_or(Error::::Unknown)?; + let approval = Approvals::::take((id.clone(), &owner, &delegate)) + .ok_or(Error::::Unknown)?; T::Currency::unreserve(&owner, approval.deposit); d.approvals.saturating_dec(); - Asset::::insert(id, d); + Asset::::insert(id.clone(), d); Self::deposit_event(Event::ApprovalCancelled { asset_id: id, owner, delegate }); Ok(()) @@ -1462,7 +1456,6 @@ pub mod pallet { /// /// Weight: `O(1)` #[pallet::call_index(25)] - #[pallet::weight(T::WeightInfo::transfer_approved())] pub fn transfer_approved( origin: OriginFor, id: T::AssetIdParameter, @@ -1487,22 +1480,25 @@ pub mod pallet { /// /// Emits `Touched` event when successful. #[pallet::call_index(26)] - #[pallet::weight(T::WeightInfo::mint())] + #[pallet::weight(T::WeightInfo::touch())] pub fn touch(origin: OriginFor, id: T::AssetIdParameter) -> DispatchResult { + let who = ensure_signed(origin)?; let id: T::AssetId = id.into(); - Self::do_touch(id, ensure_signed(origin)?) + Self::do_touch(id, who.clone(), who, false) } - /// Return the deposit (if any) of an asset account. + /// Return the deposit (if any) of an asset account or a consumer reference (if any) of an + /// account. /// /// The origin must be Signed. /// - /// - `id`: The identifier of the asset for the account to be created. + /// - `id`: The identifier of the asset for which the caller would like the deposit + /// refunded. /// - `allow_burn`: If `true` then assets may be destroyed in order to complete the refund. /// /// Emits `Refunded` event when successful. #[pallet::call_index(27)] - #[pallet::weight(T::WeightInfo::mint())] + #[pallet::weight(T::WeightInfo::refund())] pub fn refund( origin: OriginFor, id: T::AssetIdParameter, @@ -1511,6 +1507,158 @@ pub mod pallet { let id: T::AssetId = id.into(); Self::do_refund(id, ensure_signed(origin)?, allow_burn) } + + /// Sets the minimum balance of an asset. + /// + /// Only works if there aren't any accounts that are holding the asset or if + /// the new value of `min_balance` is less than the old one. + /// + /// Origin must be Signed and the sender has to be the Owner of the + /// asset `id`. + /// + /// - `id`: The identifier of the asset. + /// - `min_balance`: The new value of `min_balance`. + /// + /// Emits `AssetMinBalanceChanged` event when successful. + #[pallet::call_index(28)] + pub fn set_min_balance( + origin: OriginFor, + id: T::AssetIdParameter, + min_balance: T::Balance, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let id: T::AssetId = id.into(); + + let mut details = Asset::::get(&id).ok_or(Error::::Unknown)?; + ensure!(origin == details.owner, Error::::NoPermission); + + let old_min_balance = details.min_balance; + // If the asset is marked as sufficient it won't be allowed to + // change the min_balance. + ensure!(!details.is_sufficient, Error::::NoPermission); + + // Ensure that either the new min_balance is less than old + // min_balance or there aren't any accounts holding the asset. + ensure!( + min_balance < old_min_balance || details.accounts == 0, + Error::::NoPermission + ); + + details.min_balance = min_balance; + Asset::::insert(&id, details); + + Self::deposit_event(Event::AssetMinBalanceChanged { + asset_id: id, + new_min_balance: min_balance, + }); + Ok(()) + } + + /// Create an asset account for `who`. + /// + /// A deposit will be taken from the signer account. + /// + /// - `origin`: Must be Signed by `Freezer` or `Admin` of the asset `id`; the signer account + /// must have sufficient funds for a deposit to be taken. + /// - `id`: The identifier of the asset for the account to be created. + /// - `who`: The account to be created. + /// + /// Emits `Touched` event when successful. + #[pallet::call_index(29)] + #[pallet::weight(T::WeightInfo::touch_other())] + pub fn touch_other( + origin: OriginFor, + id: T::AssetIdParameter, + who: AccountIdLookupOf, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let who = T::Lookup::lookup(who)?; + let id: T::AssetId = id.into(); + Self::do_touch(id, who, origin, true) + } + + /// Return the deposit (if any) of a target asset account. Useful if you are the depositor. + /// + /// The origin must be Signed and either the account owner, depositor, or asset `Admin`. In + /// order to burn a non-zero balance of the asset, the caller must be the account and should + /// use `refund`. + /// + /// - `id`: The identifier of the asset for the account holding a deposit. + /// - `who`: The account to refund. + /// + /// Emits `Refunded` event when successful. + #[pallet::call_index(30)] + #[pallet::weight(T::WeightInfo::refund_other())] + pub fn refund_other( + origin: OriginFor, + id: T::AssetIdParameter, + who: AccountIdLookupOf, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let who = T::Lookup::lookup(who)?; + let id: T::AssetId = id.into(); + Self::do_refund_other(id, &who, &origin) + } + + /// Disallow further unprivileged transfers of an asset `id` to and from an account `who`. + /// + /// Origin must be Signed and the sender should be the Freezer of the asset `id`. + /// + /// - `id`: The identifier of the account's asset. + /// - `who`: The account to be unblocked. + /// + /// Emits `Blocked`. + /// + /// Weight: `O(1)` + #[pallet::call_index(31)] + pub fn block( + origin: OriginFor, + id: T::AssetIdParameter, + who: AccountIdLookupOf, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + let id: T::AssetId = id.into(); + + let d = Asset::::get(&id).ok_or(Error::::Unknown)?; + ensure!( + d.status == AssetStatus::Live || d.status == AssetStatus::Frozen, + Error::::AssetNotLive + ); + ensure!(origin == d.freezer, Error::::NoPermission); + let who = T::Lookup::lookup(who)?; + + Account::::try_mutate(&id, &who, |maybe_account| -> DispatchResult { + maybe_account.as_mut().ok_or(Error::::NoAccount)?.status = + AccountStatus::Blocked; + Ok(()) + })?; + + Self::deposit_event(Event::::Blocked { asset_id: id, who }); + Ok(()) + } + } + + /// Implements [`AccountTouch`] trait. + /// Note that a depositor can be any account, without any specific privilege. + /// This implementation is supposed to be used only for creation of system accounts. + impl, I: 'static> AccountTouch for Pallet { + type Balance = DepositBalanceOf; + + fn deposit_required(_: T::AssetId) -> Self::Balance { + T::AssetAccountDeposit::get() + } + + fn touch(asset: T::AssetId, who: T::AccountId, depositor: T::AccountId) -> DispatchResult { + Self::do_touch(asset, who, depositor, false) + } + } + + /// Implements [`ContainsPair`] trait for a pair of asset and account IDs. + impl, I: 'static> ContainsPair for Pallet { + /// Check if an account with the given asset ID and account address exists. + fn contains(asset: &T::AssetId, who: &T::AccountId) -> bool { + Account::::contains_key(asset, who) + } } } diff --git a/frame/assets/src/migration.rs b/frame/assets/src/migration.rs index 9f8905ceff6c..d854a64afb57 100644 --- a/frame/assets/src/migration.rs +++ b/frame/assets/src/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,9 @@ use super::*; use frame_support::{log, traits::OnRuntimeUpgrade}; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + pub mod v1 { use frame_support::{pallet_prelude::*, weights::Weight}; @@ -92,7 +95,7 @@ pub mod v1 { } #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { + fn pre_upgrade() -> Result, TryRuntimeError> { frame_support::ensure!( Pallet::::on_chain_storage_version() == 0, "must upgrade linearly" @@ -102,13 +105,13 @@ pub mod v1 { } #[cfg(feature = "try-runtime")] - fn post_upgrade(prev_count: Vec) -> Result<(), &'static str> { + fn post_upgrade(prev_count: Vec) -> Result<(), TryRuntimeError> { let prev_count: u32 = Decode::decode(&mut prev_count.as_slice()).expect( "the state parameter should be something that was generated by pre_upgrade", ); let post_count = Asset::::iter().count() as u32; - assert_eq!( - prev_count, post_count, + ensure!( + prev_count == post_count, "the asset count before and after the migration should be the same" ); @@ -116,14 +119,18 @@ pub mod v1 { let onchain_version = Pallet::::on_chain_storage_version(); frame_support::ensure!(current_version == 1, "must_upgrade"); - assert_eq!( - current_version, onchain_version, + ensure!( + current_version == onchain_version, "after migration, the current_version and onchain_version should be the same" ); - Asset::::iter().for_each(|(_id, asset)| { - assert!(asset.status == AssetStatus::Live || asset.status == AssetStatus::Frozen, "assets should only be live or frozen. None should be in destroying status, or undefined state") - }); + Asset::::iter().try_for_each(|(_id, asset)| -> Result<(), TryRuntimeError> { + ensure!( + asset.status == AssetStatus::Live || asset.status == AssetStatus::Frozen, + "assets should only be live or frozen. None should be in destroying status, or undefined state" + ); + Ok(()) + })?; Ok(()) } } diff --git a/frame/assets/src/mock.rs b/frame/assets/src/mock.rs index 93a87ee6714c..32ad02da9041 100644 --- a/frame/assets/src/mock.rs +++ b/frame/assets/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,25 +23,21 @@ use crate as pallet_assets; use codec::Encode; use frame_support::{ construct_runtime, parameter_types, - traits::{AsEnsureOriginWithArg, ConstU32, ConstU64, GenesisBuild}, + traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, }; use sp_core::H256; use sp_io::storage; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Assets: pallet_assets::{Pallet, Call, Storage, Event}, } @@ -56,13 +52,12 @@ impl frame_system::Config for Test { type BlockLength = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type DbWeight = (); @@ -74,7 +69,7 @@ impl frame_system::Config for Test { type SystemWeightInfo = (); type SS58Prefix = (); type OnSetCode = (); - type MaxConsumers = ConstU32<2>; + type MaxConsumers = ConstU32<3>; } impl pallet_balances::Config for Test { @@ -87,16 +82,52 @@ impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; + type RuntimeHoldReason = (); + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); } pub struct AssetsCallbackHandle; impl AssetsCallback for AssetsCallbackHandle { - fn created(_id: &AssetId, _owner: &AccountId) { - storage::set(b"asset_created", &().encode()); + fn created(_id: &AssetId, _owner: &AccountId) -> Result<(), ()> { + if Self::should_err() { + Err(()) + } else { + storage::set(Self::CREATED.as_bytes(), &().encode()); + Ok(()) + } } - fn destroyed(_id: &AssetId) { - storage::set(b"asset_destroyed", &().encode()); + fn destroyed(_id: &AssetId) -> Result<(), ()> { + if Self::should_err() { + Err(()) + } else { + storage::set(Self::DESTROYED.as_bytes(), &().encode()); + Ok(()) + } + } +} + +impl AssetsCallbackHandle { + pub const CREATED: &'static str = "asset_created"; + pub const DESTROYED: &'static str = "asset_destroyed"; + + const RETURN_ERROR: &'static str = "return_error"; + + // Configures `Self` to return `Ok` when callbacks are invoked + pub fn set_return_ok() { + storage::clear(Self::RETURN_ERROR.as_bytes()); + } + + // Configures `Self` to return `Err` when callbacks are invoked + pub fn set_return_error() { + storage::set(Self::RETURN_ERROR.as_bytes(), &().encode()); + } + + // If `true`, callback should return `Err`, `Ok` otherwise. + fn should_err() -> bool { + storage::exists(Self::RETURN_ERROR.as_bytes()) } } @@ -169,7 +200,7 @@ pub(crate) fn take_hooks() -> Vec { } pub(crate) fn new_test_ext() -> sp_io::TestExternalities { - let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); let config: pallet_assets::GenesisConfig = pallet_assets::GenesisConfig { assets: vec![ diff --git a/frame/assets/src/tests.rs b/frame/assets/src/tests.rs index f7007ff3ccf1..06d4ec121173 100644 --- a/frame/assets/src/tests.rs +++ b/frame/assets/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,8 @@ use super::*; use crate::{mock::*, Error}; use frame_support::{ assert_noop, assert_ok, - traits::{fungibles::InspectEnumerable, Currency}, + dispatch::GetDispatchInfo, + traits::{fungibles::InspectEnumerable, tokens::Preservation::Protect, Currency}, }; use pallet_balances::Error as BalancesError; use sp_io::storage; @@ -33,15 +34,67 @@ fn asset_ids() -> Vec { s } +/// returns tuple of asset's account and sufficient counts +fn asset_account_counts(asset_id: u32) -> (u32, u32) { + let asset = Asset::::get(asset_id).unwrap(); + (asset.accounts, asset.sufficients) +} + +#[test] +fn transfer_should_never_burn() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + + while System::inc_consumers(&2).is_ok() {} + let _ = System::dec_consumers(&2); + let _ = System::dec_consumers(&2); + // Exactly one consumer ref remaining. + assert_eq!(System::consumers(&2), 1); + + let _ = >::transfer(0, &1, &2, 50, Protect); + System::assert_has_event(RuntimeEvent::Assets(crate::Event::Transferred { + asset_id: 0, + from: 1, + to: 2, + amount: 50, + })); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 1) + Assets::balance(0, 2), 100); + }); +} + #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 1, 1, true, 1)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + System::assert_last_event(RuntimeEvent::Assets(crate::Event::Issued { + asset_id: 0, + owner: 1, + amount: 100, + })); assert_eq!(Assets::balance(0, 1), 100); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); + System::assert_last_event(RuntimeEvent::Assets(crate::Event::Issued { + asset_id: 0, + owner: 2, + amount: 100, + })); assert_eq!(Assets::balance(0, 2), 100); - assert_eq!(asset_ids(), vec![0, 999]); + assert_eq!(asset_ids(), vec![0, 1, 999]); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 1, 1, 100)); + System::assert_last_event(RuntimeEvent::Assets(crate::Event::Issued { + asset_id: 1, + owner: 1, + amount: 100, + })); + assert_eq!(Assets::account_balances(1), vec![(0, 100), (999, 100), (1, 100)]); }); } @@ -90,7 +143,7 @@ fn minting_insufficient_assets_with_deposit_without_consumer_should_work() { assert_ok!(Assets::touch(RuntimeOrigin::signed(1), 0)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Balances::reserved_balance(&1), 10); - assert_eq!(System::consumers(&1), 0); + assert_eq!(System::consumers(&1), 1); }); } @@ -131,9 +184,11 @@ fn refunding_asset_deposit_without_burn_should_work() { assert_eq!(Assets::balance(0, 2), 100); assert_eq!(Assets::balance(0, 1), 0); assert_eq!(Balances::reserved_balance(&1), 10); + assert_eq!(asset_account_counts(0), (2, 0)); assert_ok!(Assets::refund(RuntimeOrigin::signed(1), 0, false)); assert_eq!(Balances::reserved_balance(&1), 0); assert_eq!(Assets::balance(1, 0), 0); + assert_eq!(asset_account_counts(0), (1, 0)); }); } @@ -153,6 +208,99 @@ fn refunding_calls_died_hook() { }); } +#[test] +fn refunding_with_sufficient_existence_reason_should_fail() { + new_test_ext().execute_with(|| { + // create sufficient asset + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + // create an asset account with sufficient existence reason + // by transferring some sufficient assets + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 50); + assert_eq!(asset_account_counts(0), (2, 2)); + // fails to refund + assert_noop!(Assets::refund(RuntimeOrigin::signed(2), 0, true), Error::::NoDeposit); + }); +} + +#[test] +fn refunding_with_deposit_from_should_fail() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + // create asset account `2` with deposit from `1` + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 2)); + assert_eq!(Balances::reserved_balance(&1), 10); + // fails to refund + assert_noop!(Assets::refund(RuntimeOrigin::signed(2), 0, true), Error::::NoDeposit); + assert!(Account::::contains_key(0, &2)); + }); +} + +#[test] +fn refunding_frozen_with_consumer_ref_works() { + new_test_ext().execute_with(|| { + // 1 will be an admin + // 2 will be a frozen account + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + // create non-sufficient asset + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(System::consumers(&2), 0); + // create asset account `2` with a consumer reference by transferring + // non-sufficient funds into + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_eq!(System::consumers(&2), 1); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 50); + assert_eq!(asset_account_counts(0), (2, 0)); + // freeze asset account `2` and asset `0` + assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2)); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + // refund works + assert_ok!(Assets::refund(RuntimeOrigin::signed(2), 0, true)); + assert!(!Account::::contains_key(0, &2)); + assert_eq!(System::consumers(&2), 0); + assert_eq!(asset_account_counts(0), (1, 0)); + }); +} + +#[test] +fn refunding_frozen_with_deposit_works() { + new_test_ext().execute_with(|| { + // 1 will be an asset admin + // 2 will be a frozen account + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(System::consumers(&2), 0); + assert_ok!(Assets::touch(RuntimeOrigin::signed(2), 0)); + // reserve deposit holds one consumer ref + assert_eq!(System::consumers(&2), 1); + assert_eq!(Balances::reserved_balance(&2), 10); + assert!(Account::::contains_key(0, &2)); + // transfer some assets to `2` + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_eq!(System::consumers(&2), 1); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 50); + assert_eq!(asset_account_counts(0), (2, 0)); + // ensure refundable even if asset account and asset is frozen + assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2)); + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(1), 0)); + // success + assert_ok!(Assets::refund(RuntimeOrigin::signed(2), 0, true)); + assert!(!Account::::contains_key(0, &2)); + assert_eq!(Balances::reserved_balance(&2), 0); + assert_eq!(System::consumers(&2), 0); + assert_eq!(asset_account_counts(0), (1, 0)); + }); +} + #[test] fn approval_lifecycle_works() { new_test_ext().execute_with(|| { @@ -164,7 +312,7 @@ fn approval_lifecycle_works() { // so we create it :) assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 1); + Balances::make_free_balance_be(&1, 2); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); assert_eq!(Asset::::get(0).unwrap().approvals, 1); assert_eq!(Balances::reserved_balance(&1), 1); @@ -190,7 +338,7 @@ fn transfer_approved_all_funds() { // so we create it :) assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 1); + Balances::make_free_balance_be(&1, 2); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); assert_eq!(Asset::::get(0).unwrap().approvals, 1); assert_eq!(Balances::reserved_balance(&1), 1); @@ -212,7 +360,7 @@ fn approval_deposits_work() { let e = BalancesError::::InsufficientBalance; assert_noop!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50), e); - Balances::make_free_balance_be(&1, 1); + Balances::make_free_balance_be(&1, 2); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); assert_eq!(Balances::reserved_balance(&1), 1); @@ -230,7 +378,7 @@ fn cannot_transfer_more_than_approved() { new_test_ext().execute_with(|| { assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 1); + Balances::make_free_balance_be(&1, 2); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); let e = Error::::Unapproved; assert_noop!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 51), e); @@ -242,7 +390,7 @@ fn cannot_transfer_more_than_exists() { new_test_ext().execute_with(|| { assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 1); + Balances::make_free_balance_be(&1, 2); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 101)); let e = Error::::BalanceLow; assert_noop!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 101), e); @@ -254,7 +402,7 @@ fn cancel_approval_works() { new_test_ext().execute_with(|| { assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 1); + Balances::make_free_balance_be(&1, 2); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); assert_eq!(Asset::::get(0).unwrap().approvals, 1); assert_noop!( @@ -284,7 +432,7 @@ fn force_cancel_approval_works() { new_test_ext().execute_with(|| { assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 1); + Balances::make_free_balance_be(&1, 2); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 50)); assert_eq!(Asset::::get(0).unwrap().approvals, 1); let e = Error::::NoPermission; @@ -513,7 +661,7 @@ fn min_balance_should_work() { // Death by `transfer_approved`. assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 1); + Balances::make_free_balance_be(&1, 2); assert_ok!(Assets::approve_transfer(RuntimeOrigin::signed(1), 0, 2, 100)); assert_ok!(Assets::transfer_approved(RuntimeOrigin::signed(2), 0, 1, 3, 91)); assert_eq!(take_hooks(), vec![Hook::Died(0, 1)]); @@ -614,6 +762,37 @@ fn approve_transfer_frozen_asset_should_not_work() { }); } +#[test] +fn transferring_from_blocked_account_should_not_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_ok!(Assets::block(RuntimeOrigin::signed(1), 0, 1)); + // behaves as frozen when transferring from blocked + assert_noop!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50), Error::::Frozen); + assert_ok!(Assets::thaw(RuntimeOrigin::signed(1), 0, 1)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50)); + }); +} + +#[test] +fn transferring_to_blocked_account_should_not_work() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 2, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_eq!(Assets::balance(0, 2), 100); + assert_ok!(Assets::block(RuntimeOrigin::signed(1), 0, 1)); + assert_noop!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50), TokenError::Blocked); + assert_ok!(Assets::thaw(RuntimeOrigin::signed(1), 0, 1)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + }); +} + #[test] fn origin_guards_should_work() { new_test_ext().execute_with(|| { @@ -696,7 +875,7 @@ fn set_team_should_work() { } #[test] -fn transferring_to_frozen_account_should_work() { +fn transferring_from_frozen_account_should_not_work() { new_test_ext().execute_with(|| { assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); @@ -704,11 +883,226 @@ fn transferring_to_frozen_account_should_work() { assert_eq!(Assets::balance(0, 1), 100); assert_eq!(Assets::balance(0, 2), 100); assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2)); + // can transfer to `2` assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + // cannot transfer from `2` + assert_noop!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 25), Error::::Frozen); + assert_eq!(Assets::balance(0, 1), 50); assert_eq!(Assets::balance(0, 2), 150); }); } +#[test] +fn touching_and_freezing_account_with_zero_asset_balance_should_work() { + new_test_ext().execute_with(|| { + // need some deposit for the touch + Balances::make_free_balance_be(&2, 100); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_eq!(Assets::balance(0, 2), 0); + // cannot freeze an account that doesn't have an `Assets` entry + assert_noop!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2), Error::::NoAccount); + assert_ok!(Assets::touch(RuntimeOrigin::signed(2), 0)); + // now it can be frozen + assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2)); + // can transfer to `2` even though its frozen + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + // cannot transfer from `2` + assert_noop!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 25), Error::::Frozen); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 50); + }); +} + +#[test] +fn touch_other_works() { + new_test_ext().execute_with(|| { + // 1 will be admin + // 2 will be freezer + // 4 will be an account attempting to execute `touch_other` + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + Balances::make_free_balance_be(&4, 100); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::set_team(RuntimeOrigin::signed(1), 0, 1, 1, 2)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + // account `3` does not exist + assert!(!Account::::contains_key(0, &3)); + // creation of asset account `3` by account `4` fails + assert_noop!( + Assets::touch_other(RuntimeOrigin::signed(4), 0, 3), + Error::::NoPermission + ); + // creation of asset account `3` by admin `1` works + assert!(!Account::::contains_key(0, &3)); + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 3)); + assert!(Account::::contains_key(0, &3)); + // creation of asset account `4` by freezer `2` works + assert!(!Account::::contains_key(0, &4)); + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(2), 0, 4)); + assert!(Account::::contains_key(0, &4)); + }); +} + +#[test] +fn touch_other_and_freeze_works() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 100); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + // account `2` does not exist + assert!(!Account::::contains_key(0, &2)); + // create account `2` with touch_other + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 2)); + assert!(Account::::contains_key(0, &2)); + // now it can be frozen + assert_ok!(Assets::freeze(RuntimeOrigin::signed(1), 0, 2)); + // can transfer to `2` even though its frozen + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + // cannot transfer from `2` + assert_noop!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 25), Error::::Frozen); + assert_eq!(Assets::balance(0, 1), 50); + assert_eq!(Assets::balance(0, 2), 50); + }); +} + +#[test] +fn account_with_deposit_not_destroyed() { + new_test_ext().execute_with(|| { + // 1 will be the asset admin + // 2 will exist without balance but with deposit + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + assert_eq!(Assets::balance(0, 1), 100); + assert_eq!(Assets::balance(0, 2), 0); + // case 1; account `2` not destroyed with a holder's deposit + assert_ok!(Assets::touch(RuntimeOrigin::signed(2), 0)); + assert_eq!(Balances::reserved_balance(&2), 10); + assert!(Account::::contains_key(0, &2)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50)); + assert_eq!(Assets::balance(0, 2), 0); + assert!(Account::::contains_key(0, &2)); + + // destroy account `2` + assert_ok!(Assets::refund(RuntimeOrigin::signed(2), 0, false)); + assert!(!Account::::contains_key(0, &2)); + + // case 2; account `2` not destroyed with a deposit from `1` + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 2)); + assert_eq!(Balances::reserved_balance(&1), 10); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(1), 0, 2, 50)); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(2), 0, 1, 50)); + assert!(Account::::contains_key(0, &2)); + }); +} + +#[test] +fn refund_other_should_fails() { + new_test_ext().execute_with(|| { + // 1 will be the asset admin + // 2 will be the asset freezer + // 3 will be created with deposit of 2 + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + Balances::make_free_balance_be(&3, 0); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::set_team(RuntimeOrigin::signed(1), 0, 1, 1, 2)); + assert!(!Account::::contains_key(0, &3)); + + // create asset account `3` with a deposit from freezer `2` + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(2), 0, 3)); + assert_eq!(Balances::reserved_balance(&2), 10); + + // fail case; non-existing asset account `10` + assert_noop!( + Assets::refund_other(RuntimeOrigin::signed(2), 0, 10), + Error::::NoDeposit + ); + // fail case; non-existing asset `3` + assert_noop!( + Assets::refund_other(RuntimeOrigin::signed(2), 1, 3), + Error::::NoDeposit + ); + // fail case; no `DepositFrom` for asset account `1` + assert_noop!( + Assets::refund_other(RuntimeOrigin::signed(2), 0, 1), + Error::::NoDeposit + ); + // fail case; asset `0` is frozen + assert_ok!(Assets::freeze_asset(RuntimeOrigin::signed(2), 0)); + assert_noop!( + Assets::refund_other(RuntimeOrigin::signed(2), 0, 3), + Error::::AssetNotLive + ); + assert_ok!(Assets::thaw_asset(RuntimeOrigin::signed(1), 0)); + // fail case; asset `1` is being destroyed + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 10, 1, true, 1)); + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 10, 3)); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 10)); + assert_noop!( + Assets::refund_other(RuntimeOrigin::signed(2), 10, 3), + Error::::AssetNotLive + ); + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 10)); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 10)); + // fail case; account is frozen + assert_ok!(Assets::freeze(RuntimeOrigin::signed(2), 0, 3)); + assert_noop!(Assets::refund_other(RuntimeOrigin::signed(2), 0, 3), Error::::Frozen); + assert_ok!(Assets::thaw(RuntimeOrigin::signed(1), 0, 3)); + // fail case; not a freezer or an admin + assert_noop!( + Assets::refund_other(RuntimeOrigin::signed(4), 0, 3), + Error::::NoPermission + ); + // fail case; would burn + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 3, 100)); + assert_noop!( + Assets::refund_other(RuntimeOrigin::signed(1), 0, 3), + Error::::WouldBurn + ); + assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 3, 100)); + }) +} + +#[test] +fn refund_other_works() { + new_test_ext().execute_with(|| { + // 1 will be the asset admin + // 2 will be the asset freezer + // 3 will be created with deposit of 2 + Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&2, 100); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert_ok!(Assets::set_team(RuntimeOrigin::signed(1), 0, 1, 1, 2)); + assert!(!Account::::contains_key(0, &3)); + assert_eq!(asset_account_counts(0), (0, 0)); + + // success case; freezer is depositor + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(2), 0, 3)); + assert_eq!(Balances::reserved_balance(&2), 10); + assert_eq!(asset_account_counts(0), (1, 0)); + assert_ok!(Assets::refund_other(RuntimeOrigin::signed(2), 0, 3)); + assert_eq!(Balances::reserved_balance(&2), 0); + assert!(!Account::::contains_key(0, &3)); + assert_eq!(asset_account_counts(0), (0, 0)); + + // success case; admin is depositor + assert_ok!(Assets::touch_other(RuntimeOrigin::signed(1), 0, 3)); + assert_eq!(Balances::reserved_balance(&1), 10); + assert_eq!(asset_account_counts(0), (1, 0)); + assert_ok!(Assets::refund_other(RuntimeOrigin::signed(1), 0, 3)); + assert_eq!(Balances::reserved_balance(&1), 0); + assert!(!Account::::contains_key(0, &3)); + assert_eq!(asset_account_counts(0), (0, 0)); + }) +} + #[test] fn transferring_amount_more_than_available_balance_should_not_work() { new_test_ext().execute_with(|| { @@ -763,6 +1157,11 @@ fn burning_asset_balance_with_positive_balance_should_work() { assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); assert_eq!(Assets::balance(0, 1), 100); assert_ok!(Assets::burn(RuntimeOrigin::signed(1), 0, 1, u64::MAX)); + System::assert_last_event(RuntimeEvent::Assets(crate::Event::Burned { + asset_id: 0, + owner: 1, + balance: 100, + })); assert_eq!(Assets::balance(0, 1), 0); }); } @@ -1091,10 +1490,69 @@ fn force_asset_status_should_work() { }); } +#[test] +fn set_min_balance_should_work() { + new_test_ext().execute_with(|| { + let id = 42; + Balances::make_free_balance_be(&1, 10); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), id, 1, 30)); + + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), id, 1, 100)); + // Won't execute because there is an asset holder. + assert_noop!( + Assets::set_min_balance(RuntimeOrigin::signed(1), id, 50), + Error::::NoPermission + ); + + // Force asset status to make this a sufficient asset. + assert_ok!(Assets::force_asset_status( + RuntimeOrigin::root(), + id, + 1, + 1, + 1, + 1, + 30, + true, + false + )); + + // Won't execute because there is an account holding the asset and the asset is marked as + // sufficient. + assert_noop!( + Assets::set_min_balance(RuntimeOrigin::signed(1), id, 10), + Error::::NoPermission + ); + + // Make the asset not sufficient. + assert_ok!(Assets::force_asset_status( + RuntimeOrigin::root(), + id, + 1, + 1, + 1, + 1, + 60, + false, + false + )); + + // Will execute because the new value of min_balance is less than the + // old value. 10 < 30 + assert_ok!(Assets::set_min_balance(RuntimeOrigin::signed(1), id, 10)); + assert_eq!(Asset::::get(id).unwrap().min_balance, 10); + + assert_ok!(Assets::burn(RuntimeOrigin::signed(1), id, 1, 100)); + + assert_ok!(Assets::set_min_balance(RuntimeOrigin::signed(1), id, 50)); + assert_eq!(Asset::::get(id).unwrap().min_balance, 50); + }); +} + #[test] fn balance_conversion_should_work() { new_test_ext().execute_with(|| { - use frame_support::traits::tokens::BalanceConversion; + use frame_support::traits::tokens::ConversionToAssetBalance; let id = 42; assert_ok!(Assets::force_create(RuntimeOrigin::root(), id, 1, true, 10)); @@ -1155,7 +1613,7 @@ fn querying_allowance_should_work() { use frame_support::traits::tokens::fungibles::approvals::{Inspect, Mutate}; assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); - Balances::make_free_balance_be(&1, 1); + Balances::make_free_balance_be(&1, 2); assert_ok!(Assets::approve(0, &1, &2, 50)); assert_eq!(Assets::allowance(0, &1, &2), 50); // Transfer asset 0, from owner 1 and delegate 2 to destination 3 @@ -1199,28 +1657,121 @@ fn querying_roles_should_work() { #[test] fn normal_asset_create_and_destroy_callbacks_should_work() { new_test_ext().execute_with(|| { - assert!(storage::get(b"asset_created").is_none()); - assert!(storage::get(b"asset_destroyed").is_none()); + assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_none()); + assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none()); Balances::make_free_balance_be(&1, 100); assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); - assert!(storage::get(b"asset_created").is_some()); - assert!(storage::get(b"asset_destroyed").is_none()); + assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_some()); + assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none()); assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + // Callback still hasn't been invoked + assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none()); + assert_ok!(Assets::finish_destroy(RuntimeOrigin::signed(1), 0)); - assert!(storage::get(b"asset_destroyed").is_some()); + assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_some()); }); } #[test] fn root_asset_create_should_work() { new_test_ext().execute_with(|| { - assert!(storage::get(b"asset_created").is_none()); + assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_none()); + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); + assert!(storage::get(AssetsCallbackHandle::CREATED.as_bytes()).is_some()); + assert!(storage::get(AssetsCallbackHandle::DESTROYED.as_bytes()).is_none()); + }); +} + +#[test] +fn asset_create_and_destroy_is_reverted_if_callback_fails() { + new_test_ext().execute_with(|| { + // Asset creation fails due to callback failure + AssetsCallbackHandle::set_return_error(); + Balances::make_free_balance_be(&1, 100); + assert_noop!( + Assets::create(RuntimeOrigin::signed(1), 0, 1, 1), + Error::::CallbackFailed + ); + + // Callback succeeds, so asset creation succeeds + AssetsCallbackHandle::set_return_ok(); + assert_ok!(Assets::create(RuntimeOrigin::signed(1), 0, 1, 1)); + + // Asset destroy should fail due to callback failure + AssetsCallbackHandle::set_return_error(); + assert_ok!(Assets::start_destroy(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_accounts(RuntimeOrigin::signed(1), 0)); + assert_ok!(Assets::destroy_approvals(RuntimeOrigin::signed(1), 0)); + assert_noop!( + Assets::finish_destroy(RuntimeOrigin::signed(1), 0), + Error::::CallbackFailed + ); + }); +} + +#[test] +fn multiple_transfer_alls_work_ok() { + new_test_ext().execute_with(|| { + // Only run PoC when the system pallet is enabled, since the underlying bug is in the + // system pallet it won't work with BalancesAccountStore + // Start with a balance of 100 + Balances::force_set_balance(RuntimeOrigin::root(), 1, 100).unwrap(); + // Emulate a sufficient, in reality this could be reached by transferring a sufficient + // asset to the account assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, true, 1)); - assert!(storage::get(b"asset_created").is_some()); - assert!(storage::get(b"asset_destroyed").is_none()); + assert_ok!(Assets::mint(RuntimeOrigin::signed(1), 0, 1, 100)); + // Spend the same balance multiple times + assert_ok!(Balances::transfer_all(RuntimeOrigin::signed(1), 1337, false)); + assert_ok!(Balances::transfer_all(RuntimeOrigin::signed(1), 1337, false)); + + assert_eq!(Balances::free_balance(&1), 0); + assert_eq!(Balances::free_balance(&1337), 100); + }); +} + +#[test] +fn weights_sane() { + let info = crate::Call::::create { id: 10, admin: 4, min_balance: 3 }.get_dispatch_info(); + assert_eq!(<() as crate::WeightInfo>::create(), info.weight); + + let info = crate::Call::::finish_destroy { id: 10 }.get_dispatch_info(); + assert_eq!(<() as crate::WeightInfo>::finish_destroy(), info.weight); +} + +#[test] +fn asset_destroy_refund_existence_deposit() { + new_test_ext().execute_with(|| { + assert_ok!(Assets::force_create(RuntimeOrigin::root(), 0, 1, false, 1)); + Balances::make_free_balance_be(&1, 100); + let admin = 1; + let admin_origin = RuntimeOrigin::signed(admin); + + let account2 = 2; // account with own deposit + let account3 = 3; // account with admin's deposit + Balances::make_free_balance_be(&account2, 100); + + assert_eq!(Balances::reserved_balance(&account2), 0); + assert_eq!(Balances::reserved_balance(&account3), 0); + assert_eq!(Balances::reserved_balance(&admin), 0); + + assert_ok!(Assets::touch(RuntimeOrigin::signed(account2), 0)); + assert_ok!(Assets::touch_other(admin_origin.clone(), 0, account3)); + + assert_eq!(Balances::reserved_balance(&account2), 10); + assert_eq!(Balances::reserved_balance(&account3), 0); + assert_eq!(Balances::reserved_balance(&admin), 10); + + assert_ok!(Assets::start_destroy(admin_origin.clone(), 0)); + assert_ok!(Assets::destroy_accounts(admin_origin.clone(), 0)); + assert_ok!(Assets::destroy_approvals(admin_origin.clone(), 0)); + assert_ok!(Assets::finish_destroy(admin_origin.clone(), 0)); + + assert_eq!(Balances::reserved_balance(&account2), 0); + assert_eq!(Balances::reserved_balance(&account3), 0); + assert_eq!(Balances::reserved_balance(&admin), 0); }); } diff --git a/frame/assets/src/types.rs b/frame/assets/src/types.rs index 557af6bd3f48..559afccb946c 100644 --- a/frame/assets/src/types.rs +++ b/frame/assets/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,14 +20,20 @@ use super::*; use frame_support::{ pallet_prelude::*, - traits::{fungible, tokens::BalanceConversion}, + traits::{fungible, tokens::ConversionToAssetBalance}, }; use sp_runtime::{traits::Convert, FixedPointNumber, FixedPointOperand, FixedU128}; pub(super) type DepositBalanceOf = <>::Currency as Currency<::AccountId>>::Balance; -pub(super) type AssetAccountOf = - AssetAccount<>::Balance, DepositBalanceOf, >::Extra>; +pub(super) type AssetAccountOf = AssetAccount< + >::Balance, + DepositBalanceOf, + >::Extra, + ::AccountId, +>; +pub(super) type ExistenceReasonOf = + ExistenceReason, ::AccountId>; /// AssetStatus holds the current state of the asset. It could either be Live and available for use, /// or in a Destroying state. @@ -83,23 +89,35 @@ pub struct Approval { #[test] fn ensure_bool_decodes_to_consumer_or_sufficient() { - assert_eq!(false.encode(), ExistenceReason::<()>::Consumer.encode()); - assert_eq!(true.encode(), ExistenceReason::<()>::Sufficient.encode()); + assert_eq!(false.encode(), ExistenceReason::<(), ()>::Consumer.encode()); + assert_eq!(true.encode(), ExistenceReason::<(), ()>::Sufficient.encode()); } +/// The reason for an account's existence within an asset class. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub enum ExistenceReason { +pub enum ExistenceReason { + /// A consumer reference was used to create this account. #[codec(index = 0)] Consumer, + /// The asset class is `sufficient` for account existence. #[codec(index = 1)] Sufficient, + /// The account holder has placed a deposit to exist within an asset class. #[codec(index = 2)] DepositHeld(Balance), + /// A deposit was placed for this account to exist, but it has been refunded. #[codec(index = 3)] DepositRefunded, + /// Some other `AccountId` has placed a deposit to make this account exist. + /// An account with such a reason might not be referenced in `system`. + #[codec(index = 4)] + DepositFrom(AccountId, Balance), } -impl ExistenceReason { +impl ExistenceReason +where + AccountId: Clone, +{ pub(crate) fn take_deposit(&mut self) -> Option { if !matches!(self, ExistenceReason::DepositHeld(_)) { return None @@ -112,16 +130,56 @@ impl ExistenceReason { None } } + + pub(crate) fn take_deposit_from(&mut self) -> Option<(AccountId, Balance)> { + if !matches!(self, ExistenceReason::DepositFrom(..)) { + return None + } + if let ExistenceReason::DepositFrom(depositor, deposit) = + sp_std::mem::replace(self, ExistenceReason::DepositRefunded) + { + Some((depositor, deposit)) + } else { + None + } + } } +#[test] +fn ensure_bool_decodes_to_liquid_or_frozen() { + assert_eq!(false.encode(), AccountStatus::Liquid.encode()); + assert_eq!(true.encode(), AccountStatus::Frozen.encode()); +} + +/// The status of an asset account. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub struct AssetAccount { +pub enum AccountStatus { + /// Asset account can receive and transfer the assets. + Liquid, + /// Asset account cannot transfer the assets. + Frozen, + /// Asset account cannot receive and transfer the assets. + Blocked, +} +impl AccountStatus { + /// Returns `true` if frozen or blocked. + pub(crate) fn is_frozen(&self) -> bool { + matches!(self, AccountStatus::Frozen | AccountStatus::Blocked) + } + /// Returns `true` if blocked. + pub(crate) fn is_blocked(&self) -> bool { + matches!(self, AccountStatus::Blocked) + } +} + +#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct AssetAccount { /// The balance. pub(super) balance: Balance, - /// Whether the account is frozen. - pub(super) is_frozen: bool, + /// The status of the account. + pub(super) status: AccountStatus, /// The reason for the existence of the account. - pub(super) reason: ExistenceReason, + pub(super) reason: ExistenceReason, /// Additional "sidecar" data, in case some other pallet wants to use this storage item. pub(super) extra: Extra, } @@ -228,7 +286,7 @@ type BalanceOf = >>::Balance; /// Converts a balance value into an asset balance based on the ratio between the fungible's /// minimum balance and the minimum asset balance. pub struct BalanceToAssetBalance(PhantomData<(F, T, CON, I)>); -impl BalanceConversion, AssetIdOf, AssetBalanceOf> +impl ConversionToAssetBalance, AssetIdOf, AssetBalanceOf> for BalanceToAssetBalance where F: fungible::Inspect>, diff --git a/frame/assets/src/weights.rs b/frame/assets/src/weights.rs index 747198ae3e5a..f20f7e317cff 100644 --- a/frame/assets/src/weights.rs +++ b/frame/assets/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_assets //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_assets +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,17 +45,18 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_assets. pub trait WeightInfo { fn create() -> Weight; fn force_create() -> Weight; fn start_destroy() -> Weight; - fn destroy_accounts(c: u32) -> Weight; - fn destroy_approvals(m: u32) -> Weight; + fn destroy_accounts(c: u32, ) -> Weight; + fn destroy_approvals(a: u32, ) -> Weight; fn finish_destroy() -> Weight; fn mint() -> Weight; fn burn() -> Weight; @@ -73,469 +78,891 @@ pub trait WeightInfo { fn transfer_approved() -> Weight; fn cancel_approval() -> Weight; fn force_cancel_approval() -> Weight; + fn set_min_balance() -> Weight; + fn touch() -> Weight; + fn touch_other() -> Weight; + fn refund() -> Weight; + fn refund_other() -> Weight; + fn block() -> Weight; } /// Weights for pallet_assets using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Assets Asset (r:1 w:1) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn create() -> Weight { - // Minimum execution time: 33_241 nanoseconds. - Weight::from_ref_time(33_873_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `293` + // Estimated: `3675` + // Minimum execution time: 31_340_000 picoseconds. + Weight::from_parts(31_977_000, 3675) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) fn force_create() -> Weight { - // Minimum execution time: 19_883 nanoseconds. - Weight::from_ref_time(20_651_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - - // Storage: Assets Asset (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `153` + // Estimated: `3675` + // Minimum execution time: 13_342_000 picoseconds. + Weight::from_parts(13_782_000, 3675) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) fn start_destroy() -> Weight { - Weight::from_ref_time(31_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:1 w:0) - // Storage: System Account (r:20 w:20) + // Proof Size summary in bytes: + // Measured: `385` + // Estimated: `3675` + // Minimum execution time: 14_437_000 picoseconds. + Weight::from_parts(14_833_000, 3675) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1001 w:1000) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: System Account (r:1000 w:1000) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `c` is `[0, 1000]`. fn destroy_accounts(c: u32, ) -> Weight { - Weight::from_ref_time(37_000_000 as u64) - // Standard Error: 19_301 - .saturating_add(Weight::from_ref_time(25_467_908 as u64).saturating_mul(c as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().reads((2 as u64).saturating_mul(c as u64))) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(c as u64))) - } - - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Approvals (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `0 + c * (208 ±0)` + // Estimated: `3675 + c * (2609 ±0)` + // Minimum execution time: 18_728_000 picoseconds. + Weight::from_parts(18_982_000, 3675) + // Standard Error: 11_708 + .saturating_add(Weight::from_parts(14_363_570, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(c.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(c.into()))) + .saturating_add(Weight::from_parts(0, 2609).saturating_mul(c.into())) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1001 w:1000) + /// Proof: Assets Approvals (max_values: None, max_size: Some(148), added: 2623, mode: MaxEncodedLen) /// The range of component `a` is `[0, 1000]`. fn destroy_approvals(a: u32, ) -> Weight { - Weight::from_ref_time(39_000_000 as u64) - // Standard Error: 14_298 - .saturating_add(Weight::from_ref_time(27_632_144 as u64).saturating_mul(a as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(a as u64))) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(a as u64))) - } - - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Metadata (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `522 + a * (86 ±0)` + // Estimated: `3675 + a * (2623 ±0)` + // Minimum execution time: 18_611_000 picoseconds. + Weight::from_parts(18_970_000, 3675) + // Standard Error: 13_224 + .saturating_add(Weight::from_parts(16_397_299, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 2623).saturating_mul(a.into())) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:0) + /// Proof: Assets Metadata (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) fn finish_destroy() -> Weight { - Weight::from_ref_time(33_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3675` + // Minimum execution time: 14_504_000 picoseconds. + Weight::from_parts(14_906_000, 3675) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) fn mint() -> Weight { - // Minimum execution time: 36_782 nanoseconds. - Weight::from_ref_time(37_340_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3675` + // Minimum execution time: 26_653_000 picoseconds. + Weight::from_parts(27_260_000, 3675) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) fn burn() -> Weight { - // Minimum execution time: 44_425 nanoseconds. - Weight::from_ref_time(45_485_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:2 w:2) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `459` + // Estimated: `3675` + // Minimum execution time: 33_625_000 picoseconds. + Weight::from_parts(34_474_000, 3675) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn transfer() -> Weight { - // Minimum execution time: 58_294 nanoseconds. - Weight::from_ref_time(59_447_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - } - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:2 w:2) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `498` + // Estimated: `6208` + // Minimum execution time: 47_609_000 picoseconds. + Weight::from_parts(48_476_000, 6208) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn transfer_keep_alive() -> Weight { - // Minimum execution time: 46_704 nanoseconds. - Weight::from_ref_time(47_521_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - } - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:2 w:2) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `498` + // Estimated: `6208` + // Minimum execution time: 41_625_000 picoseconds. + Weight::from_parts(43_030_000, 6208) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn force_transfer() -> Weight { - // Minimum execution time: 57_647 nanoseconds. - Weight::from_ref_time(58_417_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - } - // Storage: Assets Asset (r:1 w:0) - // Storage: Assets Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `498` + // Estimated: `6208` + // Minimum execution time: 47_661_000 picoseconds. + Weight::from_parts(48_469_000, 6208) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) fn freeze() -> Weight { - // Minimum execution time: 26_827 nanoseconds. - Weight::from_ref_time(27_373_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:0) - // Storage: Assets Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `459` + // Estimated: `3675` + // Minimum execution time: 17_727_000 picoseconds. + Weight::from_parts(18_384_000, 3675) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) fn thaw() -> Weight { - // Minimum execution time: 26_291 nanoseconds. - Weight::from_ref_time(26_854_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `459` + // Estimated: `3675` + // Minimum execution time: 17_657_000 picoseconds. + Weight::from_parts(18_282_000, 3675) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) fn freeze_asset() -> Weight { - // Minimum execution time: 22_694 nanoseconds. - Weight::from_ref_time(23_613_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `385` + // Estimated: `3675` + // Minimum execution time: 13_743_000 picoseconds. + Weight::from_parts(14_193_000, 3675) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) fn thaw_asset() -> Weight { - // Minimum execution time: 22_572 nanoseconds. - Weight::from_ref_time(24_121_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Metadata (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `385` + // Estimated: `3675` + // Minimum execution time: 13_653_000 picoseconds. + Weight::from_parts(14_263_000, 3675) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:0) + /// Proof: Assets Metadata (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) fn transfer_ownership() -> Weight { - // Minimum execution time: 23_949 nanoseconds. - Weight::from_ref_time(24_347_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3675` + // Minimum execution time: 15_328_000 picoseconds. + Weight::from_parts(16_042_000, 3675) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) fn set_team() -> Weight { - // Minimum execution time: 23_102 nanoseconds. - Weight::from_ref_time(23_518_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:0) - // Storage: Assets Metadata (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3675` + // Minimum execution time: 14_097_000 picoseconds. + Weight::from_parts(14_641_000, 3675) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) /// The range of component `n` is `[0, 50]`. /// The range of component `s` is `[0, 50]`. - fn set_metadata(_n: u32, s: u32, ) -> Weight { - // Minimum execution time: 41_032 nanoseconds. - Weight::from_ref_time(42_845_624 as u64) - // Standard Error: 1_274 - .saturating_add(Weight::from_ref_time(1_875 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:0) - // Storage: Assets Metadata (r:1 w:1) + fn set_metadata(_n: u32, _s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3675` + // Minimum execution time: 29_535_000 picoseconds. + Weight::from_parts(31_456_892, 3675) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) fn clear_metadata() -> Weight { - // Minimum execution time: 42_570 nanoseconds. - Weight::from_ref_time(42_957_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:0) - // Storage: Assets Metadata (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `515` + // Estimated: `3675` + // Minimum execution time: 30_680_000 picoseconds. + Weight::from_parts(31_930_000, 3675) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) /// The range of component `n` is `[0, 50]`. /// The range of component `s` is `[0, 50]`. - fn force_set_metadata(n: u32, s: u32, ) -> Weight { - // Minimum execution time: 22_768 nanoseconds. - Weight::from_ref_time(23_868_816 as u64) - // Standard Error: 612 - .saturating_add(Weight::from_ref_time(1_602 as u64).saturating_mul(n as u64)) - // Standard Error: 612 - .saturating_add(Weight::from_ref_time(2_097 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:0) - // Storage: Assets Metadata (r:1 w:1) + fn force_set_metadata(_n: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `190` + // Estimated: `3675` + // Minimum execution time: 14_660_000 picoseconds. + Weight::from_parts(15_718_387, 3675) + // Standard Error: 622 + .saturating_add(Weight::from_parts(2_640, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) fn force_clear_metadata() -> Weight { - // Minimum execution time: 41_863 nanoseconds. - Weight::from_ref_time(42_643_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `515` + // Estimated: `3675` + // Minimum execution time: 30_853_000 picoseconds. + Weight::from_parts(31_483_000, 3675) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) fn force_asset_status() -> Weight { - // Minimum execution time: 21_747 nanoseconds. - Weight::from_ref_time(22_595_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Approvals (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3675` + // Minimum execution time: 13_632_000 picoseconds. + Weight::from_parts(14_077_000, 3675) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(148), added: 2623, mode: MaxEncodedLen) fn approve_transfer() -> Weight { - // Minimum execution time: 45_602 nanoseconds. - Weight::from_ref_time(46_004_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Assets Approvals (r:1 w:1) - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:2 w:2) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `385` + // Estimated: `3675` + // Minimum execution time: 33_780_000 picoseconds. + Weight::from_parts(34_533_000, 3675) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(148), added: 2623, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn transfer_approved() -> Weight { - // Minimum execution time: 70_944 nanoseconds. - Weight::from_ref_time(71_722_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) - } - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Approvals (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `668` + // Estimated: `6208` + // Minimum execution time: 67_712_000 picoseconds. + Weight::from_parts(69_946_000, 6208) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(148), added: 2623, mode: MaxEncodedLen) fn cancel_approval() -> Weight { - // Minimum execution time: 46_316 nanoseconds. - Weight::from_ref_time(46_910_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Approvals (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `555` + // Estimated: `3675` + // Minimum execution time: 36_668_000 picoseconds. + Weight::from_parts(37_637_000, 3675) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(148), added: 2623, mode: MaxEncodedLen) fn force_cancel_approval() -> Weight { - // Minimum execution time: 47_145 nanoseconds. - Weight::from_ref_time(47_611_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `555` + // Estimated: `3675` + // Minimum execution time: 36_685_000 picoseconds. + Weight::from_parts(37_950_000, 3675) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + fn set_min_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3675` + // Minimum execution time: 14_466_000 picoseconds. + Weight::from_parts(14_924_000, 3675) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn touch() -> Weight { + // Proof Size summary in bytes: + // Measured: `453` + // Estimated: `3675` + // Minimum execution time: 34_874_000 picoseconds. + Weight::from_parts(36_330_000, 3675) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + fn touch_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3675` + // Minimum execution time: 33_278_000 picoseconds. + Weight::from_parts(34_104_000, 3675) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn refund() -> Weight { + // Proof Size summary in bytes: + // Measured: `579` + // Estimated: `3675` + // Minimum execution time: 32_898_000 picoseconds. + Weight::from_parts(33_489_000, 3675) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + fn refund_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `510` + // Estimated: `3675` + // Minimum execution time: 31_243_000 picoseconds. + Weight::from_parts(31_909_000, 3675) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + fn block() -> Weight { + // Proof Size summary in bytes: + // Measured: `459` + // Estimated: `3675` + // Minimum execution time: 17_692_000 picoseconds. + Weight::from_parts(18_253_000, 3675) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Assets Asset (r:1 w:1) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn create() -> Weight { - // Minimum execution time: 33_241 nanoseconds. - Weight::from_ref_time(33_873_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `293` + // Estimated: `3675` + // Minimum execution time: 31_340_000 picoseconds. + Weight::from_parts(31_977_000, 3675) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) fn force_create() -> Weight { - // Minimum execution time: 19_883 nanoseconds. - Weight::from_ref_time(20_651_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - - // Storage: Assets Asset (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `153` + // Estimated: `3675` + // Minimum execution time: 13_342_000 picoseconds. + Weight::from_parts(13_782_000, 3675) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) fn start_destroy() -> Weight { - Weight::from_ref_time(31_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:1 w:0) - // Storage: System Account (r:20 w:20) + // Proof Size summary in bytes: + // Measured: `385` + // Estimated: `3675` + // Minimum execution time: 14_437_000 picoseconds. + Weight::from_parts(14_833_000, 3675) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1001 w:1000) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: System Account (r:1000 w:1000) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `c` is `[0, 1000]`. fn destroy_accounts(c: u32, ) -> Weight { - Weight::from_ref_time(37_000_000 as u64) - // Standard Error: 19_301 - .saturating_add(Weight::from_ref_time(25_467_908 as u64).saturating_mul(c as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().reads((2 as u64).saturating_mul(c as u64))) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(c as u64))) - } - - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Approvals (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `0 + c * (208 ±0)` + // Estimated: `3675 + c * (2609 ±0)` + // Minimum execution time: 18_728_000 picoseconds. + Weight::from_parts(18_982_000, 3675) + // Standard Error: 11_708 + .saturating_add(Weight::from_parts(14_363_570, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(c.into()))) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(c.into()))) + .saturating_add(Weight::from_parts(0, 2609).saturating_mul(c.into())) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1001 w:1000) + /// Proof: Assets Approvals (max_values: None, max_size: Some(148), added: 2623, mode: MaxEncodedLen) /// The range of component `a` is `[0, 1000]`. fn destroy_approvals(a: u32, ) -> Weight { - Weight::from_ref_time(39_000_000 as u64) - // Standard Error: 14_298 - .saturating_add(Weight::from_ref_time(27_632_144 as u64).saturating_mul(a as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(a as u64))) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(a as u64))) - } - - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Metadata (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `522 + a * (86 ±0)` + // Estimated: `3675 + a * (2623 ±0)` + // Minimum execution time: 18_611_000 picoseconds. + Weight::from_parts(18_970_000, 3675) + // Standard Error: 13_224 + .saturating_add(Weight::from_parts(16_397_299, 0).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 2623).saturating_mul(a.into())) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:0) + /// Proof: Assets Metadata (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) fn finish_destroy() -> Weight { - Weight::from_ref_time(33_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3675` + // Minimum execution time: 14_504_000 picoseconds. + Weight::from_parts(14_906_000, 3675) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) fn mint() -> Weight { - // Minimum execution time: 36_782 nanoseconds. - Weight::from_ref_time(37_340_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3675` + // Minimum execution time: 26_653_000 picoseconds. + Weight::from_parts(27_260_000, 3675) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) fn burn() -> Weight { - // Minimum execution time: 44_425 nanoseconds. - Weight::from_ref_time(45_485_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:2 w:2) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `459` + // Estimated: `3675` + // Minimum execution time: 33_625_000 picoseconds. + Weight::from_parts(34_474_000, 3675) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn transfer() -> Weight { - // Minimum execution time: 58_294 nanoseconds. - Weight::from_ref_time(59_447_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - } - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:2 w:2) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `498` + // Estimated: `6208` + // Minimum execution time: 47_609_000 picoseconds. + Weight::from_parts(48_476_000, 6208) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn transfer_keep_alive() -> Weight { - // Minimum execution time: 46_704 nanoseconds. - Weight::from_ref_time(47_521_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - } - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:2 w:2) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `498` + // Estimated: `6208` + // Minimum execution time: 41_625_000 picoseconds. + Weight::from_parts(43_030_000, 6208) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn force_transfer() -> Weight { - // Minimum execution time: 57_647 nanoseconds. - Weight::from_ref_time(58_417_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - } - // Storage: Assets Asset (r:1 w:0) - // Storage: Assets Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `498` + // Estimated: `6208` + // Minimum execution time: 47_661_000 picoseconds. + Weight::from_parts(48_469_000, 6208) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) fn freeze() -> Weight { - // Minimum execution time: 26_827 nanoseconds. - Weight::from_ref_time(27_373_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:0) - // Storage: Assets Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `459` + // Estimated: `3675` + // Minimum execution time: 17_727_000 picoseconds. + Weight::from_parts(18_384_000, 3675) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) fn thaw() -> Weight { - // Minimum execution time: 26_291 nanoseconds. - Weight::from_ref_time(26_854_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `459` + // Estimated: `3675` + // Minimum execution time: 17_657_000 picoseconds. + Weight::from_parts(18_282_000, 3675) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) fn freeze_asset() -> Weight { - // Minimum execution time: 22_694 nanoseconds. - Weight::from_ref_time(23_613_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `385` + // Estimated: `3675` + // Minimum execution time: 13_743_000 picoseconds. + Weight::from_parts(14_193_000, 3675) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) fn thaw_asset() -> Weight { - // Minimum execution time: 22_572 nanoseconds. - Weight::from_ref_time(24_121_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Metadata (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `385` + // Estimated: `3675` + // Minimum execution time: 13_653_000 picoseconds. + Weight::from_parts(14_263_000, 3675) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:0) + /// Proof: Assets Metadata (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) fn transfer_ownership() -> Weight { - // Minimum execution time: 23_949 nanoseconds. - Weight::from_ref_time(24_347_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3675` + // Minimum execution time: 15_328_000 picoseconds. + Weight::from_parts(16_042_000, 3675) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) fn set_team() -> Weight { - // Minimum execution time: 23_102 nanoseconds. - Weight::from_ref_time(23_518_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:0) - // Storage: Assets Metadata (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3675` + // Minimum execution time: 14_097_000 picoseconds. + Weight::from_parts(14_641_000, 3675) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) /// The range of component `n` is `[0, 50]`. /// The range of component `s` is `[0, 50]`. - fn set_metadata(_n: u32, s: u32, ) -> Weight { - // Minimum execution time: 41_032 nanoseconds. - Weight::from_ref_time(42_845_624 as u64) - // Standard Error: 1_274 - .saturating_add(Weight::from_ref_time(1_875 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:0) - // Storage: Assets Metadata (r:1 w:1) + fn set_metadata(_n: u32, _s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3675` + // Minimum execution time: 29_535_000 picoseconds. + Weight::from_parts(31_456_892, 3675) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) fn clear_metadata() -> Weight { - // Minimum execution time: 42_570 nanoseconds. - Weight::from_ref_time(42_957_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:0) - // Storage: Assets Metadata (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `515` + // Estimated: `3675` + // Minimum execution time: 30_680_000 picoseconds. + Weight::from_parts(31_930_000, 3675) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) /// The range of component `n` is `[0, 50]`. /// The range of component `s` is `[0, 50]`. - fn force_set_metadata(n: u32, s: u32, ) -> Weight { - // Minimum execution time: 22_768 nanoseconds. - Weight::from_ref_time(23_868_816 as u64) - // Standard Error: 612 - .saturating_add(Weight::from_ref_time(1_602 as u64).saturating_mul(n as u64)) - // Standard Error: 612 - .saturating_add(Weight::from_ref_time(2_097 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:0) - // Storage: Assets Metadata (r:1 w:1) + fn force_set_metadata(_n: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `190` + // Estimated: `3675` + // Minimum execution time: 14_660_000 picoseconds. + Weight::from_parts(15_718_387, 3675) + // Standard Error: 622 + .saturating_add(Weight::from_parts(2_640, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) fn force_clear_metadata() -> Weight { - // Minimum execution time: 41_863 nanoseconds. - Weight::from_ref_time(42_643_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `515` + // Estimated: `3675` + // Minimum execution time: 30_853_000 picoseconds. + Weight::from_parts(31_483_000, 3675) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) fn force_asset_status() -> Weight { - // Minimum execution time: 21_747 nanoseconds. - Weight::from_ref_time(22_595_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Approvals (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3675` + // Minimum execution time: 13_632_000 picoseconds. + Weight::from_parts(14_077_000, 3675) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(148), added: 2623, mode: MaxEncodedLen) fn approve_transfer() -> Weight { - // Minimum execution time: 45_602 nanoseconds. - Weight::from_ref_time(46_004_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Assets Approvals (r:1 w:1) - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:2 w:2) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `385` + // Estimated: `3675` + // Minimum execution time: 33_780_000 picoseconds. + Weight::from_parts(34_533_000, 3675) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(148), added: 2623, mode: MaxEncodedLen) + /// Storage: Assets Account (r:2 w:2) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn transfer_approved() -> Weight { - // Minimum execution time: 70_944 nanoseconds. - Weight::from_ref_time(71_722_000 as u64) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) - } - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Approvals (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `668` + // Estimated: `6208` + // Minimum execution time: 67_712_000 picoseconds. + Weight::from_parts(69_946_000, 6208) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(148), added: 2623, mode: MaxEncodedLen) fn cancel_approval() -> Weight { - // Minimum execution time: 46_316 nanoseconds. - Weight::from_ref_time(46_910_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Approvals (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `555` + // Estimated: `3675` + // Minimum execution time: 36_668_000 picoseconds. + Weight::from_parts(37_637_000, 3675) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Approvals (r:1 w:1) + /// Proof: Assets Approvals (max_values: None, max_size: Some(148), added: 2623, mode: MaxEncodedLen) fn force_cancel_approval() -> Weight { - // Minimum execution time: 47_145 nanoseconds. - Weight::from_ref_time(47_611_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `555` + // Estimated: `3675` + // Minimum execution time: 36_685_000 picoseconds. + Weight::from_parts(37_950_000, 3675) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + fn set_min_balance() -> Weight { + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3675` + // Minimum execution time: 14_466_000 picoseconds. + Weight::from_parts(14_924_000, 3675) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn touch() -> Weight { + // Proof Size summary in bytes: + // Measured: `453` + // Estimated: `3675` + // Minimum execution time: 34_874_000 picoseconds. + Weight::from_parts(36_330_000, 3675) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + fn touch_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3675` + // Minimum execution time: 33_278_000 picoseconds. + Weight::from_parts(34_104_000, 3675) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn refund() -> Weight { + // Proof Size summary in bytes: + // Measured: `579` + // Estimated: `3675` + // Minimum execution time: 32_898_000 picoseconds. + Weight::from_parts(33_489_000, 3675) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + fn refund_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `510` + // Estimated: `3675` + // Minimum execution time: 31_243_000 picoseconds. + Weight::from_parts(31_909_000, 3675) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Assets Asset (r:1 w:0) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + fn block() -> Weight { + // Proof Size summary in bytes: + // Measured: `459` + // Estimated: `3675` + // Minimum execution time: 17_692_000 picoseconds. + Weight::from_parts(18_253_000, 3675) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } diff --git a/frame/atomic-swap/Cargo.toml b/frame/atomic-swap/Cargo.toml index 9c3bcf73af98..a225a6fc0d62 100644 --- a/frame/atomic-swap/Cargo.toml +++ b/frame/atomic-swap/Cargo.toml @@ -13,14 +13,14 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } diff --git a/frame/atomic-swap/src/lib.rs b/frame/atomic-swap/src/lib.rs index 66628e8e6f24..8094c0603012 100644 --- a/frame/atomic-swap/src/lib.rs +++ b/frame/atomic-swap/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,6 +50,7 @@ use frame_support::{ weights::Weight, RuntimeDebugNoBound, }; +use frame_system::pallet_prelude::BlockNumberFor; use scale_info::TypeInfo; use sp_io::hashing::blake2_256; use sp_runtime::RuntimeDebug; @@ -69,7 +70,7 @@ pub struct PendingSwap { /// Action of this swap. pub action: T::SwapAction, /// End block of the lock. - pub end_block: T::BlockNumber, + pub end_block: BlockNumberFor, } /// Hashed proof type. @@ -184,8 +185,7 @@ pub mod pallet { } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(PhantomData); + pub struct Pallet(_); #[pallet::storage] pub type PendingSwaps = StorageDoubleMap< @@ -250,7 +250,7 @@ pub mod pallet { target: T::AccountId, hashed_proof: HashedProof, action: T::SwapAction, - duration: T::BlockNumber, + duration: BlockNumberFor, ) -> DispatchResult { let source = ensure_signed(origin)?; ensure!( diff --git a/frame/atomic-swap/src/tests.rs b/frame/atomic-swap/src/tests.rs index 081a449ec9a2..858417e8007f 100644 --- a/frame/atomic-swap/src/tests.rs +++ b/frame/atomic-swap/src/tests.rs @@ -6,20 +6,16 @@ use crate as pallet_atomic_swap; use frame_support::traits::{ConstU32, ConstU64}; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, AtomicSwap: pallet_atomic_swap::{Pallet, Call, Event}, } @@ -31,14 +27,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -62,6 +57,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } impl Config for Test { @@ -74,7 +73,7 @@ const A: u64 = 1; const B: u64 = 2; pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let genesis = pallet_balances::GenesisConfig:: { balances: vec![(A, 100), (B, 200)] }; genesis.assimilate_storage(&mut t).unwrap(); t.into() diff --git a/frame/aura/Cargo.toml b/frame/aura/Cargo.toml index d2fd5a5b9c37..c7e29c83ae2d 100644 --- a/frame/aura/Cargo.toml +++ b/frame/aura/Cargo.toml @@ -13,19 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive", "max-encoded-len"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../timestamp" } -sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../../primitives/application-crypto" } sp-consensus-aura = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/aura" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/aura/src/lib.rs b/frame/aura/src/lib.rs index b19fbd57c503..7a1969d905fc 100644 --- a/frame/aura/src/lib.rs +++ b/frame/aura/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -81,6 +81,20 @@ pub mod pallet { /// Blocks authored by a disabled validator will lead to a panic as part of this module's /// initialization. type DisabledValidators: DisabledValidators; + + /// Whether to allow block authors to create multiple blocks per slot. + /// + /// If this is `true`, the pallet will allow slots to stay the same across sequential + /// blocks. If this is `false`, the pallet will require that subsequent blocks always have + /// higher slots than previous ones. + /// + /// Regardless of the setting of this storage value, the pallet will always enforce the + /// invariant that slots don't move backwards as the chain progresses. + /// + /// The typical value for this should be 'false' unless this pallet is being augmented by + /// another pallet which enforces some limitation on the number of blocks authors can create + /// using the same slot. + type AllowMultipleBlocksPerSlot: Get; } #[pallet::pallet] @@ -88,11 +102,16 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { - fn on_initialize(_: T::BlockNumber) -> Weight { + fn on_initialize(_: BlockNumberFor) -> Weight { if let Some(new_slot) = Self::current_slot_from_digests() { let current_slot = CurrentSlot::::get(); - assert!(current_slot < new_slot, "Slot must increase"); + if T::AllowMultipleBlocksPerSlot::get() { + assert!(current_slot <= new_slot, "Slot must not decrease"); + } else { + assert!(current_slot < new_slot, "Slot must increase"); + } + CurrentSlot::::put(new_slot); if let Some(n_authorities) = >::decode_len() { @@ -113,6 +132,11 @@ pub mod pallet { T::DbWeight::get().reads(1) } } + + #[cfg(feature = "try-runtime")] + fn try_state(_: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { + Self::do_try_state() + } } /// The current authority set. @@ -129,19 +153,13 @@ pub mod pallet { pub(super) type CurrentSlot = StorageValue<_, Slot, ValueQuery>; #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { pub authorities: Vec, } - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - Self { authorities: Vec::new() } - } - } - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { Pallet::::initialize_authorities(&self.authorities); } @@ -204,6 +222,55 @@ impl Pallet { // the majority of its slot. ::MinimumPeriod::get().saturating_mul(2u32.into()) } + + /// Ensure the correctness of the state of this pallet. + /// + /// This should be valid before or after each state transition of this pallet. + /// + /// # Invariants + /// + /// ## `CurrentSlot` + /// + /// If we don't allow for multiple blocks per slot, then the current slot must be less than the + /// maximal slot number. Otherwise, it can be arbitrary. + /// + /// ## `Authorities` + /// + /// * The authorities must be non-empty. + /// * The current authority cannot be disabled. + /// * The number of authorities must be less than or equal to `T::MaxAuthorities`. This however, + /// is guarded by the type system. + #[cfg(any(test, feature = "try-runtime"))] + pub fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> { + // We don't have any guarantee that we are already after `on_initialize` and thus we have to + // check the current slot from the digest or take the last known slot. + let current_slot = + Self::current_slot_from_digests().unwrap_or_else(|| CurrentSlot::::get()); + + // Check that the current slot is less than the maximal slot number, unless we allow for + // multiple blocks per slot. + if !T::AllowMultipleBlocksPerSlot::get() { + frame_support::ensure!( + current_slot < u64::MAX, + "Current slot has reached maximum value and cannot be incremented further.", + ); + } + + let authorities_len = + >::decode_len().ok_or("Failed to decode authorities length")?; + + // Check that the authorities are non-empty. + frame_support::ensure!(!authorities_len.is_zero(), "Authorities must be non-empty."); + + // Check that the current authority is not disabled. + let authority_index = *current_slot % authorities_len as u64; + frame_support::ensure!( + !T::DisabledValidators::is_disabled(authority_index as u32), + "Current validator is disabled and should not be attempting to author blocks.", + ); + + Ok(()) + } } impl sp_runtime::BoundToRuntimeAppPublic for Pallet { diff --git a/frame/aura/src/migrations.rs b/frame/aura/src/migrations.rs index d9b7cb8872a8..b45e4eb7cb5c 100644 --- a/frame/aura/src/migrations.rs +++ b/frame/aura/src/migrations.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/aura/src/mock.rs b/frame/aura/src/mock.rs index 7a07fe99ae25..53b87ff36ec6 100644 --- a/frame/aura/src/mock.rs +++ b/frame/aura/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,25 +22,18 @@ use crate as pallet_aura; use frame_support::{ parameter_types, - traits::{ConstU32, ConstU64, DisabledValidators, GenesisBuild}, + traits::{ConstU32, ConstU64, DisabledValidators}, }; use sp_consensus_aura::{ed25519::AuthorityId, AuthorityIndex}; use sp_core::H256; -use sp_runtime::{ - testing::{Header, UintAuthorityId}, - traits::IdentityLookup, -}; +use sp_runtime::{testing::UintAuthorityId, traits::IdentityLookup, BuildStorage}; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, Aura: pallet_aura::{Pallet, Storage, Config}, } @@ -52,14 +45,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -82,6 +74,7 @@ impl pallet_timestamp::Config for Test { parameter_types! { static DisabledValidatorTestValue: Vec = Default::default(); + pub static AllowMultipleBlocksPerSlot: bool = false; } pub struct MockDisabledValidators; @@ -106,14 +99,23 @@ impl pallet_aura::Config for Test { type AuthorityId = AuthorityId; type DisabledValidators = MockDisabledValidators; type MaxAuthorities = ConstU32<10>; + type AllowMultipleBlocksPerSlot = AllowMultipleBlocksPerSlot; } -pub fn new_test_ext(authorities: Vec) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); +fn build_ext(authorities: Vec) -> sp_io::TestExternalities { + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_aura::GenesisConfig:: { authorities: authorities.into_iter().map(|a| UintAuthorityId(a).to_public_key()).collect(), } - .assimilate_storage(&mut t) + .assimilate_storage(&mut storage) .unwrap(); - t.into() + storage.into() +} + +pub fn build_ext_and_execute_test(authorities: Vec, test: impl FnOnce() -> ()) { + let mut ext = build_ext(authorities); + ext.execute_with(|| { + test(); + Aura::do_try_state().expect("Storage invariants should hold") + }); } diff --git a/frame/aura/src/tests.rs b/frame/aura/src/tests.rs index ce09f85678c0..d3ce877d3e60 100644 --- a/frame/aura/src/tests.rs +++ b/frame/aura/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ #![cfg(test)] -use crate::mock::{new_test_ext, Aura, MockDisabledValidators, System}; +use crate::mock::{build_ext_and_execute_test, Aura, MockDisabledValidators, System}; use codec::Encode; use frame_support::traits::OnInitialize; use sp_consensus_aura::{Slot, AURA_ENGINE_ID}; @@ -27,7 +27,7 @@ use sp_runtime::{Digest, DigestItem}; #[test] fn initial_values() { - new_test_ext(vec![0, 1, 2, 3]).execute_with(|| { + build_ext_and_execute_test(vec![0, 1, 2, 3], || { assert_eq!(Aura::current_slot(), 0u64); assert_eq!(Aura::authorities().len(), 4); }); @@ -38,7 +38,7 @@ fn initial_values() { expected = "Validator with index 1 is disabled and should not be attempting to author blocks." )] fn disabled_validators_cannot_author_blocks() { - new_test_ext(vec![0, 1, 2, 3]).execute_with(|| { + build_ext_and_execute_test(vec![0, 1, 2, 3], || { // slot 1 should be authored by validator at index 1 let slot = Slot::from(1); let pre_digest = @@ -54,3 +54,64 @@ fn disabled_validators_cannot_author_blocks() { Aura::on_initialize(42); }); } + +#[test] +#[should_panic(expected = "Slot must increase")] +fn pallet_requires_slot_to_increase_unless_allowed() { + build_ext_and_execute_test(vec![0, 1, 2, 3], || { + crate::mock::AllowMultipleBlocksPerSlot::set(false); + + let slot = Slot::from(1); + let pre_digest = + Digest { logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())] }; + + System::reset_events(); + System::initialize(&42, &System::parent_hash(), &pre_digest); + + // and we should not be able to initialize the block with the same slot a second time. + Aura::on_initialize(42); + Aura::on_initialize(42); + }); +} + +#[test] +fn pallet_can_allow_unchanged_slot() { + build_ext_and_execute_test(vec![0, 1, 2, 3], || { + let slot = Slot::from(1); + let pre_digest = + Digest { logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())] }; + + System::reset_events(); + System::initialize(&42, &System::parent_hash(), &pre_digest); + + crate::mock::AllowMultipleBlocksPerSlot::set(true); + + // and we should be able to initialize the block with the same slot a second time. + Aura::on_initialize(42); + Aura::on_initialize(42); + }); +} + +#[test] +#[should_panic(expected = "Slot must not decrease")] +fn pallet_always_rejects_decreasing_slot() { + build_ext_and_execute_test(vec![0, 1, 2, 3], || { + let slot = Slot::from(2); + let pre_digest = + Digest { logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())] }; + + System::reset_events(); + System::initialize(&42, &System::parent_hash(), &pre_digest); + + crate::mock::AllowMultipleBlocksPerSlot::set(true); + + Aura::on_initialize(42); + System::finalize(); + + let earlier_slot = Slot::from(1); + let pre_digest = + Digest { logs: vec![DigestItem::PreRuntime(AURA_ENGINE_ID, earlier_slot.encode())] }; + System::initialize(&43, &System::parent_hash(), &pre_digest); + Aura::on_initialize(43); + }); +} diff --git a/frame/authority-discovery/Cargo.toml b/frame/authority-discovery/Cargo.toml index 50c6c411c5ae..9a5c7f850aec 100644 --- a/frame/authority-discovery/Cargo.toml +++ b/frame/authority-discovery/Cargo.toml @@ -13,23 +13,23 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-session = { version = "4.0.0-dev", default-features = false, features = [ "historical", ], path = "../session" } -sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../../primitives/application-crypto" } sp-authority-discovery = { version = "4.0.0-dev", default-features = false, path = "../../primitives/authority-discovery" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/authority-discovery/src/lib.rs b/frame/authority-discovery/src/lib.rs index 39e01dba43b2..87b743ae1967 100644 --- a/frame/authority-discovery/src/lib.rs +++ b/frame/authority-discovery/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,7 +38,6 @@ pub mod pallet { use frame_support::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::config] @@ -60,14 +59,16 @@ pub mod pallet { pub(super) type NextKeys = StorageValue<_, WeakBoundedVec, ValueQuery>; - #[cfg_attr(feature = "std", derive(Default))] + #[derive(frame_support::DefaultNoBound)] #[pallet::genesis_config] - pub struct GenesisConfig { + pub struct GenesisConfig { pub keys: Vec, + #[serde(skip)] + pub _config: sp_std::marker::PhantomData, } #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { Pallet::::initialize_keys(&self.keys) } @@ -169,30 +170,26 @@ mod tests { use crate as pallet_authority_discovery; use frame_support::{ parameter_types, - traits::{ConstU32, ConstU64, GenesisBuild}, + traits::{ConstU32, ConstU64}, }; use sp_application_crypto::Pair; use sp_authority_discovery::AuthorityPair; use sp_core::{crypto::key_types, H256}; use sp_io::TestExternalities; use sp_runtime::{ - testing::{Header, UintAuthorityId}, + testing::UintAuthorityId, traits::{ConvertInto, IdentityLookup, OpaqueKeys}, - KeyTypeId, Perbill, + BuildStorage, KeyTypeId, Perbill, }; - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Session: pallet_session::{Pallet, Call, Storage, Event, Config}, - AuthorityDiscovery: pallet_authority_discovery::{Pallet, Config}, + AuthorityDiscovery: pallet_authority_discovery::{Pallet, Config}, } ); @@ -234,14 +231,13 @@ mod tests { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = BlockNumber; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AuthorityId; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -309,13 +305,11 @@ mod tests { .collect::>(); // Build genesis. - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); - GenesisBuild::::assimilate_storage( - &pallet_authority_discovery::GenesisConfig { keys: vec![] }, - &mut t, - ) - .unwrap(); + pallet_authority_discovery::GenesisConfig:: { keys: vec![], ..Default::default() } + .assimilate_storage(&mut t) + .unwrap(); // Create externalities. let mut externalities = TestExternalities::new(t); diff --git a/frame/authorship/Cargo.toml b/frame/authorship/Cargo.toml index 84d00a6fbc54..3c05854ea156 100644 --- a/frame/authorship/Cargo.toml +++ b/frame/authorship/Cargo.toml @@ -13,20 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } impl-trait-for-tuples = "0.2.2" -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-authorship = { version = "4.0.0-dev", default-features = false, path = "../../primitives/authorship" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } [features] default = ["std"] @@ -35,7 +34,6 @@ std = [ "frame-support/std", "frame-system/std", "scale-info/std", - "sp-authorship/std", "sp-runtime/std", "sp-std/std", ] diff --git a/frame/authorship/src/lib.rs b/frame/authorship/src/lib.rs index 4065472d9236..a9bd0c38cb67 100644 --- a/frame/authorship/src/lib.rs +++ b/frame/authorship/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,36 +17,12 @@ //! Authorship tracking for FRAME runtimes. //! -//! This tracks the current author of the block and recent uncles. +//! This tracks the current author of the block. #![cfg_attr(not(feature = "std"), no_std)] -use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::{ - dispatch, - traits::{Defensive, FindAuthor, Get, VerifySeal}, - BoundedSlice, BoundedVec, -}; -use sp_authorship::{InherentError, UnclesInherentData, INHERENT_IDENTIFIER}; -use sp_runtime::traits::{Header as HeaderT, One, Saturating, UniqueSaturatedInto}; -use sp_std::{collections::btree_set::BTreeSet, prelude::*, result}; - -const MAX_UNCLES: usize = 10; - -struct MaxUncleEntryItems(core::marker::PhantomData); -impl Get for MaxUncleEntryItems { - fn get() -> u32 { - // There can be at most `MAX_UNCLES` of `UncleEntryItem::Uncle` and - // one `UncleEntryItem::InclusionHeight` per one `UncleGenerations`, - // so this gives us `MAX_UNCLES + 1` entries per one generation. - // - // There can be one extra generation worth of uncles (e.g. even - // if `UncleGenerations` is zero the pallet will still hold - // one generation worth of uncles). - let max_generations: u32 = T::UncleGenerations::get().unique_saturated_into(); - (MAX_UNCLES as u32 + 1) * (max_generations + 1) - } -} +use frame_support::traits::FindAuthor; +use sp_std::prelude::*; pub use pallet::*; @@ -56,87 +32,8 @@ pub use pallet::*; pub trait EventHandler { /// Note that the given account ID is the author of the current block. fn note_author(author: Author); - - /// Note that the given account ID authored the given uncle, and how many - /// blocks older than the current block it is (age >= 0, so siblings are allowed) - fn note_uncle(author: Author, age: BlockNumber); -} - -/// Additional filtering on uncles that pass preliminary ancestry checks. -/// -/// This should do work such as checking seals -pub trait FilterUncle { - /// An accumulator of data about uncles included. - /// - /// In practice, this is used to validate uncles against others in the same block. - type Accumulator: Default; - - /// Do additional filtering on a seal-checked uncle block, with the accumulated - /// filter. - fn filter_uncle( - header: &Header, - acc: &mut Self::Accumulator, - ) -> Result, &'static str>; } -impl FilterUncle for () { - type Accumulator = (); - fn filter_uncle(_: &H, _acc: &mut Self::Accumulator) -> Result, &'static str> { - Ok(None) - } -} - -/// A filter on uncles which verifies seals and does no additional checks. -/// This is well-suited to consensus modes such as PoW where the cost of -/// equivocating is high. -pub struct SealVerify(sp_std::marker::PhantomData); - -impl> FilterUncle for SealVerify { - type Accumulator = (); - - fn filter_uncle(header: &Header, _acc: &mut ()) -> Result, &'static str> { - T::verify_seal(header) - } -} - -/// A filter on uncles which verifies seals and ensures that there is only -/// one uncle included per author per height. -/// -/// This does O(n log n) work in the number of uncles included. -pub struct OnePerAuthorPerHeight(sp_std::marker::PhantomData<(T, N)>); - -impl FilterUncle for OnePerAuthorPerHeight -where - Header: HeaderT + PartialEq, - Header::Number: Ord, - Author: Clone + PartialEq + Ord, - T: VerifySeal, -{ - type Accumulator = BTreeSet<(Header::Number, Author)>; - - fn filter_uncle( - header: &Header, - acc: &mut Self::Accumulator, - ) -> Result, &'static str> { - let author = T::verify_seal(header)?; - let number = header.number(); - - if let Some(ref author) = author { - if !acc.insert((*number, author.clone())) { - return Err("more than one uncle per number per author included") - } - } - - Ok(author) - } -} - -#[derive(Encode, Decode, sp_runtime::RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen)] -#[cfg_attr(any(feature = "std", test), derive(PartialEq))] -enum UncleEntryItem { - InclusionHeight(BlockNumber), - Uncle(Hash, Option), -} #[frame_support::pallet] pub mod pallet { use super::*; @@ -147,45 +44,16 @@ pub mod pallet { pub trait Config: frame_system::Config { /// Find the author of a block. type FindAuthor: FindAuthor; - /// The number of blocks back we should accept uncles. - /// This means that we will deal with uncle-parents that are - /// `UncleGenerations + 1` before `now`. - #[pallet::constant] - type UncleGenerations: Get; - /// A filter for uncles within a block. This is for implementing - /// further constraints on what uncles can be included, other than their ancestry. - /// - /// For PoW, as long as the seals are checked, there is no need to use anything - /// but the `VerifySeal` implementation as the filter. This is because the cost of making - /// many equivocating uncles is high. - /// - /// For PoS, there is no such limitation, so a further constraint must be imposed - /// beyond a seal check in order to prevent an arbitrary number of - /// equivocating uncles from being included. - /// - /// The `OnePerAuthorPerHeight` filter is good for many slot-based PoS - /// engines. - type FilterUncle: FilterUncle; /// An event handler for authored blocks. - type EventHandler: EventHandler; + type EventHandler: EventHandler>; } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::hooks] impl Hooks> for Pallet { - fn on_initialize(now: T::BlockNumber) -> Weight { - let uncle_generations = T::UncleGenerations::get(); - // prune uncles that are older than the allowed number of generations. - if uncle_generations <= now { - let minimum_height = now - uncle_generations; - Self::prune_old_uncles(minimum_height) - } - - >::put(false); - + fn on_initialize(_: BlockNumberFor) -> Weight { if let Some(author) = Self::author() { T::EventHandler::note_author(author); } @@ -193,128 +61,15 @@ pub mod pallet { Weight::zero() } - fn on_finalize(_: T::BlockNumber) { + fn on_finalize(_: BlockNumberFor) { // ensure we never go to trie with these values. >::kill(); - >::kill(); } } - #[pallet::storage] - /// Uncles - pub(super) type Uncles = StorageValue< - _, - BoundedVec, MaxUncleEntryItems>, - ValueQuery, - >; - #[pallet::storage] /// Author of current block. pub(super) type Author = StorageValue<_, T::AccountId, OptionQuery>; - - #[pallet::storage] - /// Whether uncles were already set in this block. - pub(super) type DidSetUncles = StorageValue<_, bool, ValueQuery>; - - #[pallet::error] - pub enum Error { - /// The uncle parent not in the chain. - InvalidUncleParent, - /// Uncles already set in the block. - UnclesAlreadySet, - /// Too many uncles. - TooManyUncles, - /// The uncle is genesis. - GenesisUncle, - /// The uncle is too high in chain. - TooHighUncle, - /// The uncle is already included. - UncleAlreadyIncluded, - /// The uncle isn't recent enough to be included. - OldUncle, - } - - #[pallet::call] - impl Pallet { - /// Provide a set of uncles. - #[pallet::call_index(0)] - #[pallet::weight((0, DispatchClass::Mandatory))] - pub fn set_uncles(origin: OriginFor, new_uncles: Vec) -> DispatchResult { - ensure_none(origin)?; - ensure!(new_uncles.len() <= MAX_UNCLES, Error::::TooManyUncles); - - if >::get() { - return Err(Error::::UnclesAlreadySet.into()) - } - >::put(true); - - Self::verify_and_import_uncles(new_uncles) - } - } - - #[pallet::inherent] - impl ProvideInherent for Pallet { - type Call = Call; - type Error = InherentError; - const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER; - - fn create_inherent(data: &InherentData) -> Option { - let uncles = data.uncles().unwrap_or_default(); - let mut new_uncles = Vec::new(); - - if !uncles.is_empty() { - let prev_uncles = >::get(); - let mut existing_hashes: Vec<_> = prev_uncles - .into_iter() - .filter_map(|entry| match entry { - UncleEntryItem::InclusionHeight(_) => None, - UncleEntryItem::Uncle(h, _) => Some(h), - }) - .collect(); - - let mut acc: >::Accumulator = - Default::default(); - - for uncle in uncles { - match Self::verify_uncle(&uncle, &existing_hashes, &mut acc) { - Ok(_) => { - let hash = uncle.hash(); - new_uncles.push(uncle); - existing_hashes.push(hash); - - if new_uncles.len() == MAX_UNCLES { - break - } - }, - Err(_) => { - // skip this uncle - }, - } - } - } - - if new_uncles.is_empty() { - None - } else { - Some(Call::set_uncles { new_uncles }) - } - } - - fn check_inherent( - call: &Self::Call, - _data: &InherentData, - ) -> result::Result<(), Self::Error> { - match call { - Call::set_uncles { ref new_uncles } if new_uncles.len() > MAX_UNCLES => - Err(InherentError::Uncles(Error::::TooManyUncles.as_str().into())), - _ => Ok(()), - } - } - - fn is_inherent(call: &Self::Call) -> bool { - matches!(call, Call::set_uncles { .. }) - } - } } impl Pallet { @@ -323,7 +78,7 @@ impl Pallet { /// This is safe to invoke in `on_initialize` implementations, as well /// as afterwards. pub fn author() -> Option { - // Check the memoized storage value. + // Check the memorized storage value. if let Some(author) = >::get() { return Some(author) } @@ -335,126 +90,32 @@ impl Pallet { a }) } - - fn verify_and_import_uncles(new_uncles: Vec) -> dispatch::DispatchResult { - let now = >::block_number(); - - let mut uncles = >::get(); - uncles - .try_push(UncleEntryItem::InclusionHeight(now)) - .defensive_proof("the list of uncles accepted per generation is bounded, and the number of generations is bounded, so pushing a new element will always succeed") - .map_err(|_| Error::::TooManyUncles)?; - - let mut acc: >::Accumulator = Default::default(); - - for uncle in new_uncles { - let prev_uncles = uncles.iter().filter_map(|entry| match entry { - UncleEntryItem::InclusionHeight(_) => None, - UncleEntryItem::Uncle(h, _) => Some(h), - }); - let maybe_author = Self::verify_uncle(&uncle, prev_uncles, &mut acc)?; - let hash = uncle.hash(); - - if let Some(author) = maybe_author.clone() { - T::EventHandler::note_uncle(author, now - *uncle.number()); - } - uncles.try_push(UncleEntryItem::Uncle(hash, maybe_author)) - .defensive_proof("the list of uncles accepted per generation is bounded, and the number of generations is bounded, so pushing a new element will always succeed") - .map_err(|_| Error::::TooManyUncles)?; - } - - >::put(&uncles); - Ok(()) - } - - fn verify_uncle<'a, I: IntoIterator>( - uncle: &T::Header, - existing_uncles: I, - accumulator: &mut >::Accumulator, - ) -> Result, dispatch::DispatchError> { - let now = >::block_number(); - - let (minimum_height, maximum_height) = { - let uncle_generations = T::UncleGenerations::get(); - let min = now.saturating_sub(uncle_generations); - - (min, now) - }; - - let hash = uncle.hash(); - - if uncle.number() < &One::one() { - return Err(Error::::GenesisUncle.into()) - } - - if uncle.number() > &maximum_height { - return Err(Error::::TooHighUncle.into()) - } - - { - let parent_number = *uncle.number() - One::one(); - let parent_hash = >::block_hash(&parent_number); - if &parent_hash != uncle.parent_hash() { - return Err(Error::::InvalidUncleParent.into()) - } - } - - if uncle.number() < &minimum_height { - return Err(Error::::OldUncle.into()) - } - - let duplicate = existing_uncles.into_iter().any(|h| *h == hash); - let in_chain = >::block_hash(uncle.number()) == hash; - - if duplicate || in_chain { - return Err(Error::::UncleAlreadyIncluded.into()) - } - - // check uncle validity. - T::FilterUncle::filter_uncle(uncle, accumulator).map_err(Into::into) - } - - fn prune_old_uncles(minimum_height: T::BlockNumber) { - let uncles = >::get(); - let prune_entries = uncles.iter().take_while(|item| match item { - UncleEntryItem::Uncle(_, _) => true, - UncleEntryItem::InclusionHeight(height) => height < &minimum_height, - }); - let prune_index = prune_entries.count(); - let pruned_uncles = - >>::try_from(&uncles[prune_index..]) - .expect("after pruning we can't end up with more uncles than we started with"); - - >::put(pruned_uncles); - } } #[cfg(test)] mod tests { use super::*; use crate as pallet_authorship; + use codec::{Decode, Encode}; use frame_support::{ - traits::{ConstU32, ConstU64, OnFinalize, OnInitialize}, + traits::{ConstU32, ConstU64}, ConsensusEngineId, }; use sp_core::H256; use sp_runtime::{ generic::DigestItem, testing::Header, - traits::{BlakeTwo256, IdentityLookup}, + traits::{BlakeTwo256, Header as HeaderT, IdentityLookup}, + BuildStorage, }; - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Authorship: pallet_authorship::{Pallet, Call, Storage, Inherent}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Authorship: pallet_authorship::{Pallet, Storage}, } ); @@ -464,14 +125,13 @@ mod tests { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -487,8 +147,6 @@ mod tests { impl pallet::Config for Test { type FindAuthor = AuthorGiven; - type UncleGenerations = ConstU64<5>; - type FilterUncle = SealVerify; type EventHandler = (); } @@ -511,34 +169,6 @@ mod tests { } } - pub struct VerifyBlock; - - impl VerifySeal for VerifyBlock { - fn verify_seal(header: &Header) -> Result, &'static str> { - let pre_runtime_digests = header.digest.logs.iter().filter_map(|d| d.as_pre_runtime()); - let seals = header.digest.logs.iter().filter_map(|d| d.as_seal()); - - let author = - AuthorGiven::find_author(pre_runtime_digests).ok_or_else(|| "no author")?; - - for (id, mut seal) in seals { - if id == TEST_ID { - match u64::decode(&mut seal) { - Err(_) => return Err("wrong seal"), - Ok(a) => { - if a != author { - return Err("wrong author in seal") - } - break - }, - } - } - } - - Ok(Some(author)) - } - } - fn seal_header(mut header: Header, author: u64) -> Header { { let digest = header.digest_mut(); @@ -554,200 +184,10 @@ mod tests { } fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); t.into() } - #[test] - fn prune_old_uncles_works() { - use UncleEntryItem::*; - new_test_ext().execute_with(|| { - let hash = Default::default(); - let author = Default::default(); - let uncles = vec![ - InclusionHeight(1u64), - Uncle(hash, Some(author)), - Uncle(hash, None), - Uncle(hash, None), - InclusionHeight(2u64), - Uncle(hash, None), - InclusionHeight(3u64), - Uncle(hash, None), - ]; - let uncles = BoundedVec::try_from(uncles).unwrap(); - - ::Uncles::put(uncles); - Authorship::prune_old_uncles(3); - - let uncles = ::Uncles::get(); - assert_eq!(uncles, vec![InclusionHeight(3u64), Uncle(hash, None)]); - }) - } - - #[test] - fn rejects_bad_uncles() { - new_test_ext().execute_with(|| { - let author_a = 69; - - struct CanonChain { - inner: Vec
, - } - - impl CanonChain { - fn best_hash(&self) -> H256 { - self.inner.last().unwrap().hash() - } - - fn canon_hash(&self, index: usize) -> H256 { - self.inner[index].hash() - } - - fn header(&self, index: usize) -> &Header { - &self.inner[index] - } - - fn push(&mut self, header: Header) { - self.inner.push(header) - } - } - - let mut canon_chain = CanonChain { - inner: vec![seal_header( - create_header(0, Default::default(), Default::default()), - 999, - )], - }; - - let initialize_block = |number, hash: H256| { - System::reset_events(); - System::initialize(&number, &hash, &Default::default()) - }; - - for number in 1..8 { - initialize_block(number, canon_chain.best_hash()); - let header = seal_header(System::finalize(), author_a); - canon_chain.push(header); - } - - // initialize so system context is set up correctly. - initialize_block(8, canon_chain.best_hash()); - - // 2 of the same uncle at once - { - let uncle_a = seal_header( - create_header(3, canon_chain.canon_hash(2), [1; 32].into()), - author_a, - ); - assert_eq!( - Authorship::verify_and_import_uncles(vec![uncle_a.clone(), uncle_a.clone()]), - Err(Error::::UncleAlreadyIncluded.into()), - ); - } - - // 2 of the same uncle at different times. - { - let uncle_a = seal_header( - create_header(3, canon_chain.canon_hash(2), [1; 32].into()), - author_a, - ); - - assert!(Authorship::verify_and_import_uncles(vec![uncle_a.clone()]).is_ok()); - - assert_eq!( - Authorship::verify_and_import_uncles(vec![uncle_a.clone()]), - Err(Error::::UncleAlreadyIncluded.into()), - ); - } - - // same uncle as ancestor. - { - let uncle_clone = canon_chain.header(5).clone(); - - assert_eq!( - Authorship::verify_and_import_uncles(vec![uncle_clone]), - Err(Error::::UncleAlreadyIncluded.into()), - ); - } - - // uncle without valid seal. - { - let unsealed = create_header(3, canon_chain.canon_hash(2), [2; 32].into()); - assert_eq!( - Authorship::verify_and_import_uncles(vec![unsealed]), - Err("no author".into()), - ); - } - - // old uncles can't get in. - { - assert_eq!(System::block_number(), 8); - - let gen_2 = seal_header( - create_header(2, canon_chain.canon_hash(1), [3; 32].into()), - author_a, - ); - - assert_eq!( - Authorship::verify_and_import_uncles(vec![gen_2]), - Err(Error::::OldUncle.into()), - ); - } - - // siblings are also allowed - { - let other_8 = seal_header( - create_header(8, canon_chain.canon_hash(7), [1; 32].into()), - author_a, - ); - - assert!(Authorship::verify_and_import_uncles(vec![other_8]).is_ok()); - } - }); - } - - #[test] - fn maximum_bound() { - new_test_ext().execute_with(|| { - let mut max_item_count = 0; - - let mut author_counter = 0; - let mut current_depth = 1; - let mut parent_hash: H256 = [1; 32].into(); - let mut uncles = vec![]; - - // We deliberately run this for more generations than the limit - // so that we can get the `Uncles` to hit its cap. - for _ in 0..<::UncleGenerations as Get>::get() + 3 { - let new_uncles: Vec<_> = (0..MAX_UNCLES) - .map(|_| { - System::reset_events(); - System::initialize(¤t_depth, &parent_hash, &Default::default()); - // Increment the author on every block to make sure the hash's always - // different. - author_counter += 1; - seal_header(System::finalize(), author_counter) - }) - .collect(); - - author_counter += 1; - System::reset_events(); - System::initialize(¤t_depth, &parent_hash, &Default::default()); - Authorship::on_initialize(current_depth); - Authorship::set_uncles(RuntimeOrigin::none(), uncles).unwrap(); - Authorship::on_finalize(current_depth); - max_item_count = - std::cmp::max(max_item_count, ::Uncles::get().len()); - - let new_parent = seal_header(System::finalize(), author_counter); - parent_hash = new_parent.hash(); - uncles = new_uncles; - current_depth += 1; - } - - assert_eq!(max_item_count, MaxUncleEntryItems::::get() as usize); - }); - } - #[test] fn sets_author_lazily() { new_test_ext().execute_with(|| { @@ -762,33 +202,4 @@ mod tests { assert_eq!(Authorship::author(), Some(author)); }); } - - #[test] - fn one_uncle_per_author_per_number() { - type Filter = OnePerAuthorPerHeight; - - let author_a = 42; - let author_b = 43; - - let mut acc: >::Accumulator = Default::default(); - let header_a1 = seal_header(create_header(1, Default::default(), [1; 32].into()), author_a); - let header_b1 = seal_header(create_header(1, Default::default(), [1; 32].into()), author_b); - - let header_a2_1 = - seal_header(create_header(2, Default::default(), [1; 32].into()), author_a); - let header_a2_2 = - seal_header(create_header(2, Default::default(), [2; 32].into()), author_a); - - let mut check_filter = move |uncle| Filter::filter_uncle(uncle, &mut acc); - - // same height, different author is OK. - assert_eq!(check_filter(&header_a1), Ok(Some(author_a))); - assert_eq!(check_filter(&header_b1), Ok(Some(author_b))); - - // same author, different height. - assert_eq!(check_filter(&header_a2_1), Ok(Some(author_a))); - - // same author, same height (author a, height 2) - assert!(check_filter(&header_a2_2).is_err()); - } } diff --git a/frame/babe/Cargo.toml b/frame/babe/Cargo.toml index aa581392721a..ebfe4d4746b3 100644 --- a/frame/babe/Cargo.toml +++ b/frame/babe/Cargo.toml @@ -13,23 +13,23 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive", "serde"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } pallet-session = { version = "4.0.0-dev", default-features = false, path = "../session" } pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../timestamp" } -sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } -sp-consensus-babe = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/babe" } -sp-consensus-vrf = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/vrf" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../../primitives/application-crypto", features = ["serde"] } +sp-consensus-babe = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/babe", features = ["serde"] } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core", features = ["serde"] } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime", features = ["serde"] } sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } -sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking", features = ["serde"] } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } @@ -37,7 +37,7 @@ pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-offences = { version = "4.0.0-dev", path = "../offences" } pallet-staking = { version = "4.0.0-dev", path = "../staking" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } [features] default = ["std"] @@ -53,7 +53,7 @@ std = [ "scale-info/std", "sp-application-crypto/std", "sp-consensus-babe/std", - "sp-consensus-vrf/std", + "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-session/std", diff --git a/frame/babe/src/benchmarking.rs b/frame/babe/src/benchmarking.rs index 20002c97f8e5..92f55665913e 100644 --- a/frame/babe/src/benchmarking.rs +++ b/frame/babe/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::benchmarks; +use frame_benchmarking::v1::benchmarks; type Header = sp_runtime::generic::Header; diff --git a/frame/babe/src/default_weights.rs b/frame/babe/src/default_weights.rs index f864fd18d86a..1f7de2b28c25 100644 --- a/frame/babe/src/default_weights.rs +++ b/frame/babe/src/default_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,30 +28,27 @@ impl crate::WeightInfo for () { DbWeight::get().writes(1) } - fn report_equivocation(validator_count: u32) -> Weight { + fn report_equivocation(validator_count: u32, max_nominators_per_validator: u32) -> Weight { // we take the validator set count from the membership proof to // calculate the weight but we set a floor of 100 validators. let validator_count = validator_count.max(100) as u64; - // worst case we are considering is that the given offender - // is backed by 200 nominators - const MAX_NOMINATORS: u64 = 200; - // checking membership proof - Weight::from_ref_time(35u64 * WEIGHT_REF_TIME_PER_MICROS) + Weight::from_parts(35u64 * WEIGHT_REF_TIME_PER_MICROS, 0) .saturating_add( - Weight::from_ref_time(175u64 * WEIGHT_REF_TIME_PER_NANOS) + Weight::from_parts(175u64 * WEIGHT_REF_TIME_PER_NANOS, 0) .saturating_mul(validator_count), ) .saturating_add(DbWeight::get().reads(5)) // check equivocation proof - .saturating_add(Weight::from_ref_time(110u64 * WEIGHT_REF_TIME_PER_MICROS)) + .saturating_add(Weight::from_parts(110u64 * WEIGHT_REF_TIME_PER_MICROS, 0)) // report offence - .saturating_add(Weight::from_ref_time(110u64 * WEIGHT_REF_TIME_PER_MICROS)) - .saturating_add(Weight::from_ref_time( - 25u64 * WEIGHT_REF_TIME_PER_MICROS * MAX_NOMINATORS, + .saturating_add(Weight::from_parts(110u64 * WEIGHT_REF_TIME_PER_MICROS, 0)) + .saturating_add(Weight::from_parts( + 25u64 * WEIGHT_REF_TIME_PER_MICROS * max_nominators_per_validator as u64, + 0, )) - .saturating_add(DbWeight::get().reads(14 + 3 * MAX_NOMINATORS)) - .saturating_add(DbWeight::get().writes(10 + 3 * MAX_NOMINATORS)) + .saturating_add(DbWeight::get().reads(14 + 3 * max_nominators_per_validator as u64)) + .saturating_add(DbWeight::get().writes(10 + 3 * max_nominators_per_validator as u64)) } } diff --git a/frame/babe/src/equivocation.rs b/frame/babe/src/equivocation.rs index 70f087fd461f..ed1df640583b 100644 --- a/frame/babe/src/equivocation.rs +++ b/frame/babe/src/equivocation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,143 +34,166 @@ //! definition. use frame_support::traits::{Get, KeyOwnerProofSystem}; -use sp_consensus_babe::{EquivocationProof, Slot}; +use frame_system::pallet_prelude::HeaderFor; +use log::{error, info}; + +use sp_consensus_babe::{AuthorityId, EquivocationProof, Slot, KEY_TYPE}; use sp_runtime::{ transaction_validity::{ InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, TransactionValidityError, ValidTransaction, }, - DispatchResult, Perbill, + DispatchError, KeyTypeId, Perbill, }; +use sp_session::{GetSessionNumber, GetValidatorCount}; use sp_staking::{ - offence::{Kind, Offence, OffenceError, ReportOffence}, + offence::{Kind, Offence, OffenceReportSystem, ReportOffence}, SessionIndex, }; use sp_std::prelude::*; -use crate::{Call, Config, Pallet, LOG_TARGET}; - -/// A trait with utility methods for handling equivocation reports in BABE. -/// The trait provides methods for reporting an offence triggered by a valid -/// equivocation report, checking the current block author (to declare as the -/// reporter), and also for creating and submitting equivocation report -/// extrinsics (useful only in offchain context). -pub trait HandleEquivocation { - /// The longevity, in blocks, that the equivocation report is valid for. When using the staking - /// pallet this should be equal to the bonding duration (in blocks, not eras). - type ReportLongevity: Get; - - /// Report an offence proved by the given reporters. - fn report_offence( - reporters: Vec, - offence: BabeEquivocationOffence, - ) -> Result<(), OffenceError>; - - /// Returns true if all of the offenders at the given time slot have already been reported. - fn is_known_offence(offenders: &[T::KeyOwnerIdentification], time_slot: &Slot) -> bool; - - /// Create and dispatch an equivocation report extrinsic. - fn submit_unsigned_equivocation_report( - equivocation_proof: EquivocationProof, - key_owner_proof: T::KeyOwnerProof, - ) -> DispatchResult; - - /// Fetch the current block author id, if defined. - fn block_author() -> Option; +use crate::{Call, Config, Error, Pallet, LOG_TARGET}; + +/// BABE equivocation offence report. +/// +/// When a validator released two or more blocks at the same slot. +pub struct EquivocationOffence { + /// A babe slot in which this incident happened. + pub slot: Slot, + /// The session index in which the incident happened. + pub session_index: SessionIndex, + /// The size of the validator set at the time of the offence. + pub validator_set_count: u32, + /// The authority that produced the equivocation. + pub offender: Offender, } -impl HandleEquivocation for () { - type ReportLongevity = (); +impl Offence for EquivocationOffence { + const ID: Kind = *b"babe:equivocatio"; + type TimeSlot = Slot; - fn report_offence( - _reporters: Vec, - _offence: BabeEquivocationOffence, - ) -> Result<(), OffenceError> { - Ok(()) + fn offenders(&self) -> Vec { + vec![self.offender.clone()] } - fn is_known_offence(_offenders: &[T::KeyOwnerIdentification], _time_slot: &Slot) -> bool { - true + fn session_index(&self) -> SessionIndex { + self.session_index } - fn submit_unsigned_equivocation_report( - _equivocation_proof: EquivocationProof, - _key_owner_proof: T::KeyOwnerProof, - ) -> DispatchResult { - Ok(()) + fn validator_set_count(&self) -> u32 { + self.validator_set_count } - fn block_author() -> Option { - None + fn time_slot(&self) -> Self::TimeSlot { + self.slot } -} - -/// Generic equivocation handler. This type implements `HandleEquivocation` -/// using existing subsystems that are part of frame (type bounds described -/// below) and will dispatch to them directly, it's only purpose is to wire all -/// subsystems together. -pub struct EquivocationHandler { - _phantom: sp_std::marker::PhantomData<(I, R, L)>, -} -impl Default for EquivocationHandler { - fn default() -> Self { - Self { _phantom: Default::default() } + // The formula is min((3k / n)^2, 1) + // where k = offenders_number and n = validators_number + fn slash_fraction(&self, offenders_count: u32) -> Perbill { + // Perbill type domain is [0, 1] by definition + Perbill::from_rational(3 * offenders_count, self.validator_set_count).square() } } -impl HandleEquivocation for EquivocationHandler +/// BABE equivocation offence report system. +/// +/// This type implements `OffenceReportSystem` such that: +/// - Equivocation reports are published on-chain as unsigned extrinsic via +/// `offchain::SendTransactionTypes`. +/// - On-chain validity checks and processing are mostly delegated to the user provided generic +/// types implementing `KeyOwnerProofSystem` and `ReportOffence` traits. +/// - Offence reporter for unsigned transactions is fetched via the the authorship pallet. +pub struct EquivocationReportSystem(sp_std::marker::PhantomData<(T, R, P, L)>); + +impl + OffenceReportSystem, (EquivocationProof>, T::KeyOwnerProof)> + for EquivocationReportSystem where - // We use the authorship pallet to fetch the current block author and use - // `offchain::SendTransactionTypes` for unsigned extrinsic creation and - // submission. T: Config + pallet_authorship::Config + frame_system::offchain::SendTransactionTypes>, - // A system for reporting offences after valid equivocation reports are - // processed. R: ReportOffence< T::AccountId, - T::KeyOwnerIdentification, - BabeEquivocationOffence, + P::IdentificationTuple, + EquivocationOffence, >, - // The longevity (in blocks) that the equivocation report is valid for. When using the staking - // pallet this should be the bonding duration. + P: KeyOwnerProofSystem<(KeyTypeId, AuthorityId), Proof = T::KeyOwnerProof>, + P::IdentificationTuple: Clone, L: Get, { - type ReportLongevity = L; + type Longevity = L; - fn report_offence( - reporters: Vec, - offence: BabeEquivocationOffence, - ) -> Result<(), OffenceError> { - R::report_offence(reporters, offence) - } - - fn is_known_offence(offenders: &[T::KeyOwnerIdentification], time_slot: &Slot) -> bool { - R::is_known_offence(offenders, time_slot) - } - - fn submit_unsigned_equivocation_report( - equivocation_proof: EquivocationProof, - key_owner_proof: T::KeyOwnerProof, - ) -> DispatchResult { + fn publish_evidence( + evidence: (EquivocationProof>, T::KeyOwnerProof), + ) -> Result<(), ()> { use frame_system::offchain::SubmitTransaction; + let (equivocation_proof, key_owner_proof) = evidence; let call = Call::report_equivocation_unsigned { equivocation_proof: Box::new(equivocation_proof), key_owner_proof, }; - - match SubmitTransaction::>::submit_unsigned_transaction(call.into()) { - Ok(()) => log::info!(target: LOG_TARGET, "Submitted BABE equivocation report.",), - Err(e) => - log::error!(target: LOG_TARGET, "Error submitting equivocation report: {:?}", e,), + let res = SubmitTransaction::>::submit_unsigned_transaction(call.into()); + match res { + Ok(_) => info!(target: LOG_TARGET, "Submitted equivocation report"), + Err(e) => error!(target: LOG_TARGET, "Error submitting equivocation report: {:?}", e), } + res + } - Ok(()) + fn check_evidence( + evidence: (EquivocationProof>, T::KeyOwnerProof), + ) -> Result<(), TransactionValidityError> { + let (equivocation_proof, key_owner_proof) = evidence; + + // Check the membership proof to extract the offender's id + let key = (sp_consensus_babe::KEY_TYPE, equivocation_proof.offender.clone()); + let offender = + P::check_proof(key, key_owner_proof.clone()).ok_or(InvalidTransaction::BadProof)?; + + // Check if the offence has already been reported, and if so then we can discard the report. + if R::is_known_offence(&[offender], &equivocation_proof.slot) { + Err(InvalidTransaction::Stale.into()) + } else { + Ok(()) + } } - fn block_author() -> Option { - >::author() + fn process_evidence( + reporter: Option, + evidence: (EquivocationProof>, T::KeyOwnerProof), + ) -> Result<(), DispatchError> { + let (equivocation_proof, key_owner_proof) = evidence; + let reporter = reporter.or_else(|| >::author()); + let offender = equivocation_proof.offender.clone(); + let slot = equivocation_proof.slot; + + // Validate the equivocation proof (check votes are different and signatures are valid) + if !sp_consensus_babe::check_equivocation_proof(equivocation_proof) { + return Err(Error::::InvalidEquivocationProof.into()) + } + + let validator_set_count = key_owner_proof.validator_count(); + let session_index = key_owner_proof.session(); + + let epoch_index = + *slot.saturating_sub(crate::GenesisSlot::::get()) / T::EpochDuration::get(); + + // Check that the slot number is consistent with the session index + // in the key ownership proof (i.e. slot is for that epoch) + if Pallet::::session_index_for_epoch(epoch_index) != session_index { + return Err(Error::::InvalidKeyOwnershipProof.into()) + } + + // Check the membership proof and extract the offender's id + let offender = P::check_proof((KEY_TYPE, offender), key_owner_proof) + .ok_or(Error::::InvalidKeyOwnershipProof)?; + + let offence = EquivocationOffence { slot, validator_set_count, offender, session_index }; + + R::report_offence(reporter.into_iter().collect(), offence) + .map_err(|_| Error::::DuplicateOffenceReport)?; + + Ok(()) } } @@ -194,11 +217,12 @@ impl Pallet { }, } - // check report staleness - is_known_offence::(equivocation_proof, key_owner_proof)?; + // Check report validity + let evidence = (*equivocation_proof.clone(), key_owner_proof.clone()); + T::EquivocationReportSystem::check_evidence(evidence)?; let longevity = - >::ReportLongevity::get(); + >::Longevity::get(); ValidTransaction::with_tag_prefix("BabeEquivocation") // We assign the maximum priority for any equivocation report. @@ -216,72 +240,10 @@ impl Pallet { pub fn pre_dispatch(call: &Call) -> Result<(), TransactionValidityError> { if let Call::report_equivocation_unsigned { equivocation_proof, key_owner_proof } = call { - is_known_offence::(equivocation_proof, key_owner_proof) + let evidence = (*equivocation_proof.clone(), key_owner_proof.clone()); + T::EquivocationReportSystem::check_evidence(evidence) } else { Err(InvalidTransaction::Call.into()) } } } - -fn is_known_offence( - equivocation_proof: &EquivocationProof, - key_owner_proof: &T::KeyOwnerProof, -) -> Result<(), TransactionValidityError> { - // check the membership proof to extract the offender's id - let key = (sp_consensus_babe::KEY_TYPE, equivocation_proof.offender.clone()); - - let offender = T::KeyOwnerProofSystem::check_proof(key, key_owner_proof.clone()) - .ok_or(InvalidTransaction::BadProof)?; - - // check if the offence has already been reported, - // and if so then we can discard the report. - if T::HandleEquivocation::is_known_offence(&[offender], &equivocation_proof.slot) { - Err(InvalidTransaction::Stale.into()) - } else { - Ok(()) - } -} - -/// A BABE equivocation offence report. -/// -/// When a validator released two or more blocks at the same slot. -pub struct BabeEquivocationOffence { - /// A babe slot in which this incident happened. - pub slot: Slot, - /// The session index in which the incident happened. - pub session_index: SessionIndex, - /// The size of the validator set at the time of the offence. - pub validator_set_count: u32, - /// The authority that produced the equivocation. - pub offender: FullIdentification, -} - -impl Offence - for BabeEquivocationOffence -{ - const ID: Kind = *b"babe:equivocatio"; - type TimeSlot = Slot; - - fn offenders(&self) -> Vec { - vec![self.offender.clone()] - } - - fn session_index(&self) -> SessionIndex { - self.session_index - } - - fn validator_set_count(&self) -> u32 { - self.validator_set_count - } - - fn time_slot(&self) -> Self::TimeSlot { - self.slot - } - - fn slash_fraction(&self, offenders_count: u32) -> Perbill { - // the formula is min((3k / n)^2, 1) - let x = Perbill::from_rational(3 * offenders_count, self.validator_set_count); - // _ ^ 2 - x.square() - } -} diff --git a/frame/babe/src/lib.rs b/frame/babe/src/lib.rs index eb87d65b0549..9549fac9fe2b 100644 --- a/frame/babe/src/lib.rs +++ b/frame/babe/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,30 +25,28 @@ use codec::{Decode, Encode}; use frame_support::{ dispatch::{DispatchResultWithPostInfo, Pays}, ensure, - traits::{ - ConstU32, DisabledValidators, FindAuthor, Get, KeyOwnerProofSystem, OnTimestampSet, - OneSessionHandler, - }, + traits::{ConstU32, DisabledValidators, FindAuthor, Get, OnTimestampSet, OneSessionHandler}, weights::Weight, BoundedVec, WeakBoundedVec, }; -use sp_application_crypto::ByteArray; +use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor}; +use sp_consensus_babe::{ + digests::{NextConfigDescriptor, NextEpochDescriptor, PreDigest}, + AllowedSlots, BabeAuthorityWeight, BabeEpochConfiguration, ConsensusLog, Epoch, + EquivocationProof, Randomness as BabeRandomness, Slot, BABE_ENGINE_ID, RANDOMNESS_LENGTH, + RANDOMNESS_VRF_CONTEXT, +}; +use sp_core::crypto::Wraps; use sp_runtime::{ generic::DigestItem, traits::{IsMember, One, SaturatedConversion, Saturating, Zero}, - ConsensusEngineId, KeyTypeId, Permill, + ConsensusEngineId, Permill, }; use sp_session::{GetSessionNumber, GetValidatorCount}; +use sp_staking::{offence::OffenceReportSystem, SessionIndex}; use sp_std::prelude::*; -use sp_consensus_babe::{ - digests::{NextConfigDescriptor, NextEpochDescriptor, PreDigest}, - AllowedSlots, BabeAuthorityWeight, BabeEpochConfiguration, ConsensusLog, Epoch, - EquivocationProof, Slot, BABE_ENGINE_ID, -}; -use sp_consensus_vrf::schnorrkel; - -pub use sp_consensus_babe::{AuthorityId, PUBLIC_KEY_LENGTH, RANDOMNESS_LENGTH, VRF_OUTPUT_LENGTH}; +pub use sp_consensus_babe::AuthorityId; const LOG_TARGET: &str = "runtime::babe"; @@ -63,7 +61,7 @@ mod mock; #[cfg(all(feature = "std", test))] mod tests; -pub use equivocation::{BabeEquivocationOffence, EquivocationHandler, HandleEquivocation}; +pub use equivocation::{EquivocationOffence, EquivocationReportSystem}; #[allow(deprecated)] pub use randomness::CurrentBlockRandomness; pub use randomness::{ @@ -74,14 +72,14 @@ pub use pallet::*; pub trait WeightInfo { fn plan_config_change() -> Weight; - fn report_equivocation(validator_count: u32) -> Weight; + fn report_equivocation(validator_count: u32, max_nominators_per_validator: u32) -> Weight; } /// Trigger an epoch change, if any should take place. pub trait EpochChangeTrigger { /// Trigger an epoch change, if any should take place. This should be called /// during every block, after initialization is done. - fn trigger(now: T::BlockNumber); + fn trigger(now: BlockNumberFor); } /// A type signifying to BABE that an external trigger @@ -89,7 +87,7 @@ pub trait EpochChangeTrigger { pub struct ExternalTrigger; impl EpochChangeTrigger for ExternalTrigger { - fn trigger(_: T::BlockNumber) {} // nothing - trigger is external. + fn trigger(_: BlockNumberFor) {} // nothing - trigger is external. } /// A type signifying to BABE that it should perform epoch changes @@ -97,12 +95,12 @@ impl EpochChangeTrigger for ExternalTrigger { pub struct SameAuthoritiesForever; impl EpochChangeTrigger for SameAuthoritiesForever { - fn trigger(now: T::BlockNumber) { + fn trigger(now: BlockNumberFor) { if >::should_epoch_change(now) { let authorities = >::authorities(); let next_authorities = authorities.clone(); - >::enact_epoch_change(authorities, next_authorities); + >::enact_epoch_change(authorities, next_authorities, None); } } } @@ -117,7 +115,6 @@ pub mod pallet { /// The BABE Pallet #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::config] @@ -149,35 +146,29 @@ pub mod pallet { /// initialization. type DisabledValidators: DisabledValidators; + /// Helper for weights computations + type WeightInfo: WeightInfo; + + /// Max number of authorities allowed + #[pallet::constant] + type MaxAuthorities: Get; + + /// The maximum number of nominators for each validator. + #[pallet::constant] + type MaxNominators: Get; + /// The proof of key ownership, used for validating equivocation reports. /// The proof must include the session index and validator count of the /// session at which the equivocation occurred. type KeyOwnerProof: Parameter + GetSessionNumber + GetValidatorCount; - /// The identification of a key owner, used when reporting equivocations. - type KeyOwnerIdentification: Parameter; - - /// A system for proving ownership of keys, i.e. that a given key was part - /// of a validator set, needed for validating equivocation reports. - type KeyOwnerProofSystem: KeyOwnerProofSystem< - (KeyTypeId, AuthorityId), - Proof = Self::KeyOwnerProof, - IdentificationTuple = Self::KeyOwnerIdentification, + /// The equivocation handling subsystem, defines methods to check/report an + /// offence and for submitting a transaction to report an equivocation + /// (from an offchain context). + type EquivocationReportSystem: OffenceReportSystem< + Option, + (EquivocationProof>, Self::KeyOwnerProof), >; - - /// The equivocation handling subsystem, defines methods to report an - /// offence (after the equivocation has been validated) and for submitting a - /// transaction to report an equivocation (from an offchain context). - /// NOTE: when enabling equivocation handling (i.e. this type isn't set to - /// `()`) you must use this pallet's `ValidateUnsigned` in the runtime - /// definition. - type HandleEquivocation: HandleEquivocation; - - type WeightInfo: WeightInfo; - - /// Max number of authorities allowed - #[pallet::constant] - type MaxAuthorities: Get; } #[pallet::error] @@ -232,7 +223,7 @@ pub mod pallet { // variable to its underlying value. #[pallet::storage] #[pallet::getter(fn randomness)] - pub type Randomness = StorageValue<_, schnorrkel::Randomness, ValueQuery>; + pub type Randomness = StorageValue<_, BabeRandomness, ValueQuery>; /// Pending epoch configuration change that will be applied when the next epoch is enacted. #[pallet::storage] @@ -240,7 +231,7 @@ pub mod pallet { /// Next epoch randomness. #[pallet::storage] - pub(super) type NextRandomness = StorageValue<_, schnorrkel::Randomness, ValueQuery>; + pub(super) type NextRandomness = StorageValue<_, BabeRandomness, ValueQuery>; /// Next epoch authorities. #[pallet::storage] @@ -268,7 +259,7 @@ pub mod pallet { _, Twox64Concat, u32, - BoundedVec>, + BoundedVec>, ValueQuery, >; @@ -284,8 +275,7 @@ pub mod pallet { /// It is set in `on_finalize`, before it will contain the value from the last block. #[pallet::storage] #[pallet::getter(fn author_vrf_randomness)] - pub(super) type AuthorVrfRandomness = - StorageValue<_, Option, ValueQuery>; + pub(super) type AuthorVrfRandomness = StorageValue<_, Option, ValueQuery>; /// The block numbers when the last and current epoch have started, respectively `N-1` and /// `N`. @@ -294,7 +284,7 @@ pub mod pallet { /// slots, which may be skipped, the block numbers may not line up with the slot numbers. #[pallet::storage] pub(super) type EpochStart = - StorageValue<_, (T::BlockNumber, T::BlockNumber), ValueQuery>; + StorageValue<_, (BlockNumberFor, BlockNumberFor), ValueQuery>; /// How late the current block is compared to its parent. /// @@ -303,7 +293,7 @@ pub mod pallet { /// execution context should always yield zero. #[pallet::storage] #[pallet::getter(fn lateness)] - pub(super) type Lateness = StorageValue<_, T::BlockNumber, ValueQuery>; + pub(super) type Lateness = StorageValue<_, BlockNumberFor, ValueQuery>; /// The configuration for the current epoch. Should never be `None` as it is initialized in /// genesis. @@ -316,15 +306,30 @@ pub mod pallet { #[pallet::storage] pub(super) type NextEpochConfig = StorageValue<_, BabeEpochConfiguration>; - #[cfg_attr(feature = "std", derive(Default))] + /// A list of the last 100 skipped epochs and the corresponding session index + /// when the epoch was skipped. + /// + /// This is only used for validating equivocation proofs. An equivocation proof + /// must contains a key-ownership proof for a given session, therefore we need a + /// way to tie together sessions and epoch indices, i.e. we need to validate that + /// a validator was the owner of a given key on a given session, and what the + /// active epoch index was during that session. + #[pallet::storage] + #[pallet::getter(fn skipped_epochs)] + pub(super) type SkippedEpochs = + StorageValue<_, BoundedVec<(u64, SessionIndex), ConstU32<100>>, ValueQuery>; + + #[derive(frame_support::DefaultNoBound)] #[pallet::genesis_config] - pub struct GenesisConfig { + pub struct GenesisConfig { pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, pub epoch_config: Option, + #[serde(skip)] + pub _config: sp_std::marker::PhantomData, } #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { SegmentIndex::::put(0); Pallet::::initialize_genesis_authorities(&self.authorities); @@ -359,31 +364,29 @@ pub mod pallet { ); } - if let Some((vrf_output, vrf_proof)) = pre_digest.vrf() { - let randomness: Option = Authorities::::get() + if let Some(signature) = pre_digest.vrf_signature() { + let randomness: Option = Authorities::::get() .get(authority_index as usize) .and_then(|(authority, _)| { - schnorrkel::PublicKey::from_bytes(authority.as_slice()).ok() - }) - .and_then(|pubkey| { - let current_slot = CurrentSlot::::get(); - - let transcript = sp_consensus_babe::make_transcript( + let public = authority.as_inner_ref(); + let transcript = sp_consensus_babe::make_vrf_transcript( &Self::randomness(), - current_slot, + CurrentSlot::::get(), EpochIndex::::get(), ); // NOTE: this is verified by the client when importing the block, before - // execution. we don't run the verification again here to avoid slowing + // execution. We don't run the verification again here to avoid slowing // down the runtime. - debug_assert!(pubkey - .vrf_verify(transcript.clone(), vrf_output, vrf_proof) - .is_ok()); + debug_assert!({ + use sp_core::crypto::VrfPublic; + public.vrf_verify(&transcript.clone().into_sign_data(), &signature) + }); - vrf_output.0.attach_input_hash(&pubkey, transcript).ok() - }) - .map(|inout| inout.make_bytes(sp_consensus_babe::BABE_VRF_INOUT_CONTEXT)); + public + .make_bytes(RANDOMNESS_VRF_CONTEXT, &transcript, &signature.output) + .ok() + }); if let Some(randomness) = pre_digest.is_primary().then(|| randomness).flatten() { @@ -408,15 +411,20 @@ pub mod pallet { #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::report_equivocation( key_owner_proof.validator_count(), + T::MaxNominators::get(), ))] pub fn report_equivocation( origin: OriginFor, - equivocation_proof: Box>, + equivocation_proof: Box>>, key_owner_proof: T::KeyOwnerProof, ) -> DispatchResultWithPostInfo { let reporter = ensure_signed(origin)?; - - Self::do_report_equivocation(Some(reporter), *equivocation_proof, key_owner_proof) + T::EquivocationReportSystem::process_evidence( + Some(reporter), + (*equivocation_proof, key_owner_proof), + )?; + // Waive the fee since the report is valid and beneficial + Ok(Pays::No.into()) } /// Report authority equivocation/misbehavior. This method will verify @@ -430,19 +438,19 @@ pub mod pallet { #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::report_equivocation( key_owner_proof.validator_count(), + T::MaxNominators::get(), ))] pub fn report_equivocation_unsigned( origin: OriginFor, - equivocation_proof: Box>, + equivocation_proof: Box>>, key_owner_proof: T::KeyOwnerProof, ) -> DispatchResultWithPostInfo { ensure_none(origin)?; - - Self::do_report_equivocation( - T::HandleEquivocation::block_author(), - *equivocation_proof, - key_owner_proof, - ) + T::EquivocationReportSystem::process_evidence( + None, + (*equivocation_proof, key_owner_proof), + )?; + Ok(Pays::No.into()) } /// Plan an epoch config change. The epoch config change is recorded and will be enacted on @@ -482,9 +490,6 @@ pub mod pallet { } } -/// A BABE public key -pub type BabeKey = [u8; PUBLIC_KEY_LENGTH]; - impl FindAuthor for Pallet { fn find_author<'a, I>(digests: I) -> Option where @@ -507,8 +512,8 @@ impl IsMember for Pallet { } } -impl pallet_session::ShouldEndSession for Pallet { - fn should_end_session(now: T::BlockNumber) -> bool { +impl pallet_session::ShouldEndSession> for Pallet { + fn should_end_session(now: BlockNumberFor) -> bool { // it might be (and it is in current implementation) that session module is calling // `should_end_session` from it's own `on_initialize` handler, in which case it's // possible that babe's own `on_initialize` has not run yet, so let's ensure that we @@ -528,7 +533,7 @@ impl Pallet { /// Determine whether an epoch change should take place at this block. /// Assumes that initialization has already taken place. - pub fn should_epoch_change(now: T::BlockNumber) -> bool { + pub fn should_epoch_change(now: BlockNumberFor) -> bool { // The epoch has technically ended during the passage of time // between this block and the last, but we have to "end" the epoch now, // since there is no earlier possible block we could have done it. @@ -558,11 +563,11 @@ impl Pallet { // // WEIGHT NOTE: This function is tied to the weight of `EstimateNextSessionRotation`. If you // update this function, you must also update the corresponding weight. - pub fn next_expected_epoch_change(now: T::BlockNumber) -> Option { + pub fn next_expected_epoch_change(now: BlockNumberFor) -> Option> { let next_slot = Self::current_epoch_start().saturating_add(T::EpochDuration::get()); next_slot.checked_sub(*CurrentSlot::::get()).map(|slots_remaining| { // This is a best effort guess. Drifts in the slot/block ratio will cause errors here. - let blocks_remaining: T::BlockNumber = slots_remaining.saturated_into(); + let blocks_remaining: BlockNumberFor = slots_remaining.saturated_into(); now.saturating_add(blocks_remaining) }) } @@ -577,6 +582,7 @@ impl Pallet { pub fn enact_epoch_change( authorities: WeakBoundedVec<(AuthorityId, BabeAuthorityWeight), T::MaxAuthorities>, next_authorities: WeakBoundedVec<(AuthorityId, BabeAuthorityWeight), T::MaxAuthorities>, + session_index: Option, ) { // PRECONDITION: caller has done initialization and is guaranteed // by the session module to be called before this. @@ -602,6 +608,35 @@ impl Pallet { T::EpochDuration::get(), ); + let current_epoch_index = EpochIndex::::get(); + if current_epoch_index.saturating_add(1) != epoch_index { + // we are skipping epochs therefore we need to update the mapping + // of epochs to session + if let Some(session_index) = session_index { + SkippedEpochs::::mutate(|skipped_epochs| { + if epoch_index < session_index as u64 { + log::warn!( + target: LOG_TARGET, + "Current epoch index {} is lower than session index {}.", + epoch_index, + session_index, + ); + + return + } + + if skipped_epochs.is_full() { + // NOTE: this is O(n) but we currently don't have a bounded `VecDeque`. + // this vector is bounded to a small number of elements so performance + // shouldn't be an issue. + skipped_epochs.remove(0); + } + + skipped_epochs.force_push((epoch_index, session_index)); + }) + } + } + EpochIndex::::put(epoch_index); Authorities::::put(authorities); @@ -705,7 +740,7 @@ impl Pallet { >::deposit_log(log) } - fn deposit_randomness(randomness: &schnorrkel::Randomness) { + fn deposit_randomness(randomness: &BabeRandomness) { let segment_idx = SegmentIndex::::get(); let mut segment = UnderConstruction::::get(&segment_idx); if segment.try_push(*randomness).is_ok() { @@ -750,7 +785,7 @@ impl Pallet { Self::deposit_consensus(ConsensusLog::NextEpochData(next)); } - fn initialize(now: T::BlockNumber) { + fn initialize(now: BlockNumberFor) { // since `initialize` can be called twice (e.g. if session module is present) // let's ensure that we only do the initialization once per block let initialized = Self::initialized().is_some(); @@ -785,7 +820,7 @@ impl Pallet { // how many slots were skipped between current and last block let lateness = current_slot.saturating_sub(CurrentSlot::::get() + 1); - let lateness = T::BlockNumber::from(*lateness as u32); + let lateness = BlockNumberFor::::from(*lateness as u32); Lateness::::put(lateness); CurrentSlot::::put(current_slot); @@ -799,7 +834,7 @@ impl Pallet { /// Call this function exactly once when an epoch changes, to update the /// randomness. Returns the new randomness. - fn randomness_change_epoch(next_epoch_index: u64) -> schnorrkel::Randomness { + fn randomness_change_epoch(next_epoch_index: u64) -> BabeRandomness { let this_randomness = NextRandomness::::get(); let segment_idx: u32 = SegmentIndex::::mutate(|s| sp_std::mem::replace(s, 0)); @@ -816,49 +851,34 @@ impl Pallet { this_randomness } - fn do_report_equivocation( - reporter: Option, - equivocation_proof: EquivocationProof, - key_owner_proof: T::KeyOwnerProof, - ) -> DispatchResultWithPostInfo { - let offender = equivocation_proof.offender.clone(); - let slot = equivocation_proof.slot; - - // validate the equivocation proof - if !sp_consensus_babe::check_equivocation_proof(equivocation_proof) { - return Err(Error::::InvalidEquivocationProof.into()) - } - - let validator_set_count = key_owner_proof.validator_count(); - let session_index = key_owner_proof.session(); - - let epoch_index = (*slot.saturating_sub(GenesisSlot::::get()) / T::EpochDuration::get()) - .saturated_into::(); - - // check that the slot number is consistent with the session index - // in the key ownership proof (i.e. slot is for that epoch) - if epoch_index != session_index { - return Err(Error::::InvalidKeyOwnershipProof.into()) + /// Returns the session index that was live when the given epoch happened, + /// taking into account any skipped epochs. + /// + /// This function is only well defined for epochs that actually existed, + /// e.g. if we skipped from epoch 10 to 20 then a call for epoch 15 (which + /// didn't exist) will return an incorrect session index. + pub(crate) fn session_index_for_epoch(epoch_index: u64) -> SessionIndex { + let skipped_epochs = SkippedEpochs::::get(); + match skipped_epochs.binary_search_by_key(&epoch_index, |(epoch_index, _)| *epoch_index) { + // we have an exact match so we just return the given session index + Ok(index) => skipped_epochs[index].1, + // we haven't found any skipped epoch before the given epoch, + // so the epoch index and session index should match + Err(0) => epoch_index.saturated_into::(), + // we have found a skipped epoch before the given epoch + Err(index) => { + // the element before the given index should give us the skipped epoch + // that's closest to the one we're trying to find the session index for + let closest_skipped_epoch = skipped_epochs[index - 1]; + + // calculate the number of skipped epochs at this point by checking the difference + // between the epoch and session indices. epoch index should always be greater or + // equal to session index, this is because epochs can be skipped whereas sessions + // can't (this is enforced when pushing into `SkippedEpochs`) + let skipped_epochs = closest_skipped_epoch.0 - closest_skipped_epoch.1 as u64; + epoch_index.saturating_sub(skipped_epochs).saturated_into::() + }, } - - // check the membership proof and extract the offender's id - let key = (sp_consensus_babe::KEY_TYPE, offender); - let offender = T::KeyOwnerProofSystem::check_proof(key, key_owner_proof) - .ok_or(Error::::InvalidKeyOwnershipProof)?; - - let offence = - BabeEquivocationOffence { slot, validator_set_count, offender, session_index }; - - let reporters = match reporter { - Some(id) => vec![id], - None => vec![], - }; - - T::HandleEquivocation::report_offence(reporters, offence) - .map_err(|_| Error::::DuplicateOffenceReport)?; - - // waive the fee since the report is valid and beneficial - Ok(Pays::No.into()) } /// Submits an extrinsic to report an equivocation. This method will create @@ -866,14 +886,10 @@ impl Pallet { /// will push the transaction to the pool. Only useful in an offchain /// context. pub fn submit_unsigned_equivocation_report( - equivocation_proof: EquivocationProof, + equivocation_proof: EquivocationProof>, key_owner_proof: T::KeyOwnerProof, ) -> Option<()> { - T::HandleEquivocation::submit_unsigned_equivocation_report( - equivocation_proof, - key_owner_proof, - ) - .ok() + T::EquivocationReportSystem::publish_evidence((equivocation_proof, key_owner_proof)).ok() } } @@ -892,12 +908,14 @@ impl OnTimestampSet for Pallet { } } -impl frame_support::traits::EstimateNextSessionRotation for Pallet { - fn average_session_length() -> T::BlockNumber { +impl frame_support::traits::EstimateNextSessionRotation> + for Pallet +{ + fn average_session_length() -> BlockNumberFor { T::EpochDuration::get().saturated_into() } - fn estimate_current_session_progress(_now: T::BlockNumber) -> (Option, Weight) { + fn estimate_current_session_progress(_now: BlockNumberFor) -> (Option, Weight) { let elapsed = CurrentSlot::::get().saturating_sub(Self::current_epoch_start()) + 1; ( @@ -907,7 +925,9 @@ impl frame_support::traits::EstimateNextSessionRotation (Option, Weight) { + fn estimate_next_session_rotation( + now: BlockNumberFor, + ) -> (Option>, Weight) { ( Self::next_expected_epoch_change(now), // Read: Current Slot, Epoch Index, Genesis Slot @@ -916,8 +936,8 @@ impl frame_support::traits::EstimateNextSessionRotation frame_support::traits::Lateness for Pallet { - fn lateness(&self) -> T::BlockNumber { +impl frame_support::traits::Lateness> for Pallet { + fn lateness(&self) -> BlockNumberFor { Self::lateness() } } @@ -926,7 +946,10 @@ impl sp_runtime::BoundToRuntimeAppPublic for Pallet { type Public = AuthorityId; } -impl OneSessionHandler for Pallet { +impl OneSessionHandler for Pallet +where + T: pallet_session::Config, +{ type Key = AuthorityId; fn on_genesis_session<'a, I: 'a>(validators: I) @@ -959,7 +982,9 @@ impl OneSessionHandler for Pallet { ), ); - Self::enact_epoch_change(bounded_authorities, next_bounded_authorities) + let session_index = >::current_index(); + + Self::enact_epoch_change(bounded_authorities, next_bounded_authorities, Some(session_index)) } fn on_disabled(i: u32) { @@ -972,12 +997,12 @@ impl OneSessionHandler for Pallet { // // an optional size hint as to how many VRF outputs there were may be provided. fn compute_randomness( - last_epoch_randomness: schnorrkel::Randomness, + last_epoch_randomness: BabeRandomness, epoch_index: u64, - rho: impl Iterator, + rho: impl Iterator, rho_size_hint: Option, -) -> schnorrkel::Randomness { - let mut s = Vec::with_capacity(40 + rho_size_hint.unwrap_or(0) * VRF_OUTPUT_LENGTH); +) -> BabeRandomness { + let mut s = Vec::with_capacity(40 + rho_size_hint.unwrap_or(0) * RANDOMNESS_LENGTH); s.extend_from_slice(&last_epoch_randomness); s.extend_from_slice(&epoch_index.to_le_bytes()); diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index abae1b7e3315..dc8e8e3499fa 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,13 +22,12 @@ use codec::Encode; use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, - traits::{ConstU128, ConstU32, ConstU64, GenesisBuild, KeyOwnerProofSystem, OnInitialize}, + traits::{ConstU128, ConstU32, ConstU64, KeyOwnerProofSystem, OnInitialize}, }; use pallet_session::historical as pallet_session_historical; -use sp_consensus_babe::{AuthorityId, AuthorityPair, Slot}; -use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; +use sp_consensus_babe::{AuthorityId, AuthorityPair, Randomness, Slot, VrfSignature}; use sp_core::{ - crypto::{IsWrappedBy, KeyTypeId, Pair}, + crypto::{KeyTypeId, Pair, VrfSecret}, H256, U256, }; use sp_io; @@ -37,30 +36,26 @@ use sp_runtime::{ impl_opaque_keys, testing::{Digest, DigestItem, Header, TestXt}, traits::{Header as _, IdentityLookup, OpaqueKeys}, - Perbill, + BuildStorage, Perbill, }; use sp_staking::{EraIndex, SessionIndex}; type DummyValidatorId = u64; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Authorship: pallet_authorship::{Pallet, Call, Storage, Inherent}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Historical: pallet_session_historical::{Pallet}, - Offences: pallet_offences::{Pallet, Storage, Event}, - Babe: pallet_babe::{Pallet, Call, Storage, Config, ValidateUnsigned}, - Staking: pallet_staking::{Pallet, Call, Storage, Config, Event}, - Session: pallet_session::{Pallet, Call, Storage, Event, Config}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, + System: frame_system, + Authorship: pallet_authorship, + Balances: pallet_balances, + Historical: pallet_session_historical, + Offences: pallet_offences, + Babe: pallet_babe, + Staking: pallet_staking, + Session: pallet_session, + Timestamp: pallet_timestamp, } ); @@ -70,15 +65,14 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Version = (); type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = DummyValidatorId; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type PalletInfo = PalletInfo; @@ -124,8 +118,6 @@ impl pallet_session::historical::Config for Test { impl pallet_authorship::Config for Test { type FindAuthor = pallet_session::FindAccountFromAuthorIndex; - type UncleGenerations = ConstU64<0>; - type FilterUncle = (); type EventHandler = (); } @@ -146,6 +138,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU128<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } pallet_staking_reward_curve::build! { @@ -181,7 +177,7 @@ impl onchain::Config for OnChainSeqPhragmen { impl pallet_staking::Config for Test { type MaxNominations = ConstU32<16>; type RewardRemainder = (); - type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type CurrencyToVote = (); type RuntimeEvent = RuntimeEvent; type Currency = Balances; type CurrencyBalance = ::Balance; @@ -203,7 +199,7 @@ impl pallet_staking::Config for Test { type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type HistoryDepth = ConstU32<84>; - type OnStakerSlash = (); + type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } @@ -225,22 +221,12 @@ impl Config for Test { type ExpectedBlockTime = ConstU64<1>; type EpochChangeTrigger = crate::ExternalTrigger; type DisabledValidators = Session; - - type KeyOwnerProofSystem = Historical; - - type KeyOwnerProof = - >::Proof; - - type KeyOwnerIdentification = >::IdentificationTuple; - - type HandleEquivocation = - super::EquivocationHandler; - type WeightInfo = (); type MaxAuthorities = ConstU32<10>; + type MaxNominators = ConstU32<100>; + type KeyOwnerProof = >::Proof; + type EquivocationReportSystem = + super::EquivocationReportSystem; } pub fn go_to_block(n: u64, s: u64) { @@ -292,16 +278,10 @@ pub fn start_era(era_index: EraIndex) { pub fn make_primary_pre_digest( authority_index: sp_consensus_babe::AuthorityIndex, slot: sp_consensus_babe::Slot, - vrf_output: VRFOutput, - vrf_proof: VRFProof, + vrf_signature: VrfSignature, ) -> Digest { let digest_data = sp_consensus_babe::digests::PreDigest::Primary( - sp_consensus_babe::digests::PrimaryPreDigest { - authority_index, - slot, - vrf_output, - vrf_proof, - }, + sp_consensus_babe::digests::PrimaryPreDigest { authority_index, slot, vrf_signature }, ); let log = DigestItem::PreRuntime(sp_consensus_babe::BABE_ENGINE_ID, digest_data.encode()); Digest { logs: vec![log] } @@ -321,34 +301,27 @@ pub fn make_secondary_plain_pre_digest( pub fn make_secondary_vrf_pre_digest( authority_index: sp_consensus_babe::AuthorityIndex, slot: sp_consensus_babe::Slot, - vrf_output: VRFOutput, - vrf_proof: VRFProof, + vrf_signature: VrfSignature, ) -> Digest { let digest_data = sp_consensus_babe::digests::PreDigest::SecondaryVRF( - sp_consensus_babe::digests::SecondaryVRFPreDigest { - authority_index, - slot, - vrf_output, - vrf_proof, - }, + sp_consensus_babe::digests::SecondaryVRFPreDigest { authority_index, slot, vrf_signature }, ); let log = DigestItem::PreRuntime(sp_consensus_babe::BABE_ENGINE_ID, digest_data.encode()); Digest { logs: vec![log] } } -pub fn make_vrf_output( +pub fn make_vrf_signature_and_randomness( slot: Slot, pair: &sp_consensus_babe::AuthorityPair, -) -> (VRFOutput, VRFProof, [u8; 32]) { - let pair = sp_core::sr25519::Pair::from_ref(pair).as_ref(); - let transcript = sp_consensus_babe::make_transcript(&Babe::randomness(), slot, 0); - let vrf_inout = pair.vrf_sign(transcript); - let vrf_randomness: sp_consensus_vrf::schnorrkel::Randomness = - vrf_inout.0.make_bytes::<[u8; 32]>(&sp_consensus_babe::BABE_VRF_INOUT_CONTEXT); - let vrf_output = VRFOutput(vrf_inout.0.to_output()); - let vrf_proof = VRFProof(vrf_inout.1); - - (vrf_output, vrf_proof, vrf_randomness) +) -> (VrfSignature, Randomness) { + let transcript = sp_consensus_babe::make_vrf_transcript(&Babe::randomness(), slot, 0); + + let randomness = + pair.as_ref().make_bytes(sp_consensus_babe::RANDOMNESS_VRF_CONTEXT, &transcript); + + let signature = pair.as_ref().vrf_sign(&transcript.into()); + + (signature, randomness) } pub fn new_test_ext(authorities_len: usize) -> sp_io::TestExternalities { @@ -368,7 +341,7 @@ pub fn new_test_ext_with_pairs( } pub fn new_test_ext_raw_authorities(authorities: Vec) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let balances: Vec<_> = (0..authorities.len()).map(|i| (i as u64, 10_000_000)).collect(); @@ -391,11 +364,9 @@ pub fn new_test_ext_raw_authorities(authorities: Vec) -> sp_io::Tes .assimilate_storage(&mut t) .unwrap(); - // controllers are the index + 1000 + // controllers are same as stash let stakers: Vec<_> = (0..authorities.len()) - .map(|i| { - (i as u64, i as u64 + 1000, 10_000, pallet_staking::StakerStatus::::Validator) - }) + .map(|i| (i as u64, i as u64, 10_000, pallet_staking::StakerStatus::::Validator)) .collect(); let staking_config = pallet_staking::GenesisConfig:: { diff --git a/frame/babe/src/randomness.rs b/frame/babe/src/randomness.rs index 28b2ea7cd665..d3d1bea2292d 100644 --- a/frame/babe/src/randomness.rs +++ b/frame/babe/src/randomness.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,9 +19,10 @@ //! randomness collected from VRF outputs. use super::{ - AuthorVrfRandomness, Config, EpochStart, NextRandomness, Randomness, VRF_OUTPUT_LENGTH, + AuthorVrfRandomness, Config, EpochStart, NextRandomness, Randomness, RANDOMNESS_LENGTH, }; use frame_support::traits::Randomness as RandomnessT; +use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::traits::{Hash, One, Saturating}; /// Randomness usable by consensus protocols that **depend** upon finality and take action @@ -129,31 +130,31 @@ pub struct ParentBlockRandomness(sp_std::marker::PhantomData); Please use `ParentBlockRandomness` instead.")] pub struct CurrentBlockRandomness(sp_std::marker::PhantomData); -impl RandomnessT for RandomnessFromTwoEpochsAgo { - fn random(subject: &[u8]) -> (T::Hash, T::BlockNumber) { +impl RandomnessT> for RandomnessFromTwoEpochsAgo { + fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor) { let mut subject = subject.to_vec(); - subject.reserve(VRF_OUTPUT_LENGTH); + subject.reserve(RANDOMNESS_LENGTH); subject.extend_from_slice(&Randomness::::get()[..]); (T::Hashing::hash(&subject[..]), EpochStart::::get().0) } } -impl RandomnessT for RandomnessFromOneEpochAgo { - fn random(subject: &[u8]) -> (T::Hash, T::BlockNumber) { +impl RandomnessT> for RandomnessFromOneEpochAgo { + fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor) { let mut subject = subject.to_vec(); - subject.reserve(VRF_OUTPUT_LENGTH); + subject.reserve(RANDOMNESS_LENGTH); subject.extend_from_slice(&NextRandomness::::get()[..]); (T::Hashing::hash(&subject[..]), EpochStart::::get().1) } } -impl RandomnessT, T::BlockNumber> for ParentBlockRandomness { - fn random(subject: &[u8]) -> (Option, T::BlockNumber) { +impl RandomnessT, BlockNumberFor> for ParentBlockRandomness { + fn random(subject: &[u8]) -> (Option, BlockNumberFor) { let random = AuthorVrfRandomness::::get().map(|random| { let mut subject = subject.to_vec(); - subject.reserve(VRF_OUTPUT_LENGTH); + subject.reserve(RANDOMNESS_LENGTH); subject.extend_from_slice(&random); T::Hashing::hash(&subject[..]) @@ -164,8 +165,8 @@ impl RandomnessT, T::BlockNumber> for ParentBlockRand } #[allow(deprecated)] -impl RandomnessT, T::BlockNumber> for CurrentBlockRandomness { - fn random(subject: &[u8]) -> (Option, T::BlockNumber) { +impl RandomnessT, BlockNumberFor> for CurrentBlockRandomness { + fn random(subject: &[u8]) -> (Option, BlockNumberFor) { let (random, _) = ParentBlockRandomness::::random(subject); (random, >::block_number()) } diff --git a/frame/babe/src/tests.rs b/frame/babe/src/tests.rs index 0b8a02547144..ae0c3e3873c5 100644 --- a/frame/babe/src/tests.rs +++ b/frame/babe/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,15 +21,16 @@ use super::{Call, *}; use frame_support::{ assert_err, assert_noop, assert_ok, dispatch::{GetDispatchInfo, Pays}, - traits::{Currency, EstimateNextSessionRotation, OnFinalize}, + traits::{Currency, EstimateNextSessionRotation, KeyOwnerProofSystem, OnFinalize}, }; use mock::*; use pallet_session::ShouldEndSession; -use sp_consensus_babe::{AllowedSlots, BabeEpochConfiguration, Slot}; -use sp_consensus_vrf::schnorrkel::{VRFOutput, VRFProof}; +use sp_consensus_babe::{ + AllowedSlots, BabeEpochConfiguration, Slot, VrfSignature, RANDOMNESS_LENGTH, +}; use sp_core::crypto::Pair; -const EMPTY_RANDOMNESS: [u8; 32] = [ +const EMPTY_RANDOMNESS: [u8; RANDOMNESS_LENGTH] = [ 74, 25, 49, 128, 53, 97, 244, 49, 222, 202, 176, 2, 231, 66, 95, 10, 133, 49, 213, 228, 86, 161, 164, 127, 217, 153, 138, 37, 48, 192, 248, 0, ]; @@ -62,10 +63,10 @@ fn first_block_epoch_zero_start() { ext.execute_with(|| { let genesis_slot = Slot::from(100); - let (vrf_output, vrf_proof, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]); + let (vrf_signature, vrf_randomness) = + make_vrf_signature_and_randomness(genesis_slot, &pairs[0]); - let first_vrf = vrf_output; - let pre_digest = make_primary_pre_digest(0, genesis_slot, first_vrf.clone(), vrf_proof); + let pre_digest = make_primary_pre_digest(0, genesis_slot, vrf_signature); assert_eq!(Babe::genesis_slot(), Slot::from(0)); System::reset_events(); @@ -111,8 +112,9 @@ fn current_slot_is_processed_on_initialization() { ext.execute_with(|| { let genesis_slot = Slot::from(10); - let (vrf_output, vrf_proof, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]); - let pre_digest = make_primary_pre_digest(0, genesis_slot, vrf_output, vrf_proof); + let (vrf_signature, vrf_randomness) = + make_vrf_signature_and_randomness(genesis_slot, &pairs[0]); + let pre_digest = make_primary_pre_digest(0, genesis_slot, vrf_signature); System::reset_events(); System::initialize(&1, &Default::default(), &pre_digest); @@ -134,14 +136,15 @@ fn current_slot_is_processed_on_initialization() { fn test_author_vrf_output(make_pre_digest: F) where - F: Fn(sp_consensus_babe::AuthorityIndex, Slot, VRFOutput, VRFProof) -> sp_runtime::Digest, + F: Fn(sp_consensus_babe::AuthorityIndex, Slot, VrfSignature) -> sp_runtime::Digest, { let (pairs, mut ext) = new_test_ext_with_pairs(1); ext.execute_with(|| { let genesis_slot = Slot::from(10); - let (vrf_output, vrf_proof, vrf_randomness) = make_vrf_output(genesis_slot, &pairs[0]); - let pre_digest = make_pre_digest(0, genesis_slot, vrf_output, vrf_proof); + let (vrf_signature, vrf_randomness) = + make_vrf_signature_and_randomness(genesis_slot, &pairs[0]); + let pre_digest = make_pre_digest(0, genesis_slot, vrf_signature); System::reset_events(); System::initialize(&1, &Default::default(), &pre_digest); @@ -812,7 +815,7 @@ fn report_equivocation_has_valid_weight() { // the weight depends on the size of the validator set, // but there's a lower bound of 100 validators. assert!((1..=100) - .map(::WeightInfo::report_equivocation) + .map(|validators| ::WeightInfo::report_equivocation(validators, 1000)) .collect::>() .windows(2) .all(|w| w[0] == w[1])); @@ -820,12 +823,62 @@ fn report_equivocation_has_valid_weight() { // after 100 validators the weight should keep increasing // with every extra validator. assert!((100..=1000) - .map(::WeightInfo::report_equivocation) + .map(|validators| ::WeightInfo::report_equivocation(validators, 1000)) .collect::>() .windows(2) .all(|w| w[0].ref_time() < w[1].ref_time())); } +#[test] +fn report_equivocation_after_skipped_epochs_works() { + let (pairs, mut ext) = new_test_ext_with_pairs(3); + + ext.execute_with(|| { + let epoch_duration: u64 = ::EpochDuration::get(); + + // this sets the genesis slot to 100; + let genesis_slot = 100; + go_to_block(1, genesis_slot); + assert_eq!(EpochIndex::::get(), 0); + + // skip from epoch #0 to epoch #10 + go_to_block(System::block_number() + 1, genesis_slot + epoch_duration * 10); + + assert_eq!(EpochIndex::::get(), 10); + assert_eq!(SkippedEpochs::::get(), vec![(10, 1)]); + + // generate an equivocation proof for validator at index 1 + let authorities = Babe::authorities(); + let offending_validator_index = 1; + let offending_authority_pair = pairs + .into_iter() + .find(|p| p.public() == authorities[offending_validator_index].0) + .unwrap(); + + let equivocation_proof = generate_equivocation_proof( + offending_validator_index as u32, + &offending_authority_pair, + CurrentSlot::::get(), + ); + + // create the key ownership proof + let key = (sp_consensus_babe::KEY_TYPE, &offending_authority_pair.public()); + let key_owner_proof = Historical::prove(key).unwrap(); + + // which is for session index 1 (while current epoch index is 10) + assert_eq!(key_owner_proof.session, 1); + + // report the equivocation, in order for the validation to pass the mapping + // between epoch index and session index must be checked. + assert!(Babe::report_equivocation_unsigned( + RuntimeOrigin::none(), + Box::new(equivocation_proof), + key_owner_proof + ) + .is_ok()); + }) +} + #[test] fn valid_equivocation_reports_dont_pay_fees() { let (pairs, mut ext) = new_test_ext_with_pairs(3); @@ -977,5 +1030,17 @@ fn skipping_over_epochs_works() { assert_eq!(EpochIndex::::get(), 4); assert_eq!(Randomness::::get(), randomness_for_epoch_2); + + // after skipping epochs the information is registered on-chain so that + // we can map epochs to sessions + assert_eq!(SkippedEpochs::::get(), vec![(4, 2)]); + + // before epochs are skipped the mapping should be one to one + assert_eq!(Babe::session_index_for_epoch(0), 0); + assert_eq!(Babe::session_index_for_epoch(1), 1); + + // otherwise the session index is offset by the number of skipped epochs + assert_eq!(Babe::session_index_for_epoch(4), 2); + assert_eq!(Babe::session_index_for_epoch(5), 3); }); } diff --git a/frame/bags-list/Cargo.toml b/frame/bags-list/Cargo.toml index e3a4965f6c08..15fbebfb6f2d 100644 --- a/frame/bags-list/Cargo.toml +++ b/frame/bags-list/Cargo.toml @@ -13,12 +13,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # parity -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } # primitives -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } # FRAME frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } @@ -31,14 +31,14 @@ log = { version = "0.4.17", default-features = false } # Optional imports for benchmarking frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking", optional = true, default-features = false } pallet-balances = { version = "4.0.0-dev", path = "../balances", optional = true, default-features = false } -sp-core = { version = "7.0.0", path = "../../primitives/core", optional = true, default-features = false } -sp-io = { version = "7.0.0", path = "../../primitives/io", optional = true, default-features = false } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing", optional = true, default-features = false } +sp-core = { version = "21.0.0", path = "../../primitives/core", optional = true, default-features = false } +sp-io = { version = "23.0.0", path = "../../primitives/io", optional = true, default-features = false } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing", optional = true, default-features = false } [dev-dependencies] -sp-core = { version = "7.0.0", path = "../../primitives/core"} -sp-io = { version = "7.0.0", path = "../../primitives/io"} -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-core = { version = "21.0.0", path = "../../primitives/core"} +sp-io = { version = "23.0.0", path = "../../primitives/io"} +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" } @@ -75,4 +75,4 @@ fuzz = [ "sp-tracing", "frame-election-provider-support/fuzz", ] -try-runtime = [ "frame-support/try-runtime" ] +try-runtime = [ "frame-support/try-runtime", "frame-election-provider-support/try-runtime" ] diff --git a/frame/bags-list/fuzzer/src/main.rs b/frame/bags-list/fuzzer/src/main.rs index 9f7ca464cc2b..18391bbb3c9a 100644 --- a/frame/bags-list/fuzzer/src/main.rs +++ b/frame/bags-list/fuzzer/src/main.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -88,7 +88,7 @@ fn main() { }, } - assert!(BagsList::try_state().is_ok()); + assert!(BagsList::do_try_state().is_ok()); }) }); } diff --git a/frame/bags-list/remote-tests/Cargo.toml b/frame/bags-list/remote-tests/Cargo.toml index 9fb6d0154b30..832a11d29d89 100644 --- a/frame/bags-list/remote-tests/Cargo.toml +++ b/frame/bags-list/remote-tests/Cargo.toml @@ -15,17 +15,17 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # frame pallet-staking = { path = "../../staking", version = "4.0.0-dev" } -pallet-bags-list = { path = "../../bags-list", version = "4.0.0-dev" } +pallet-bags-list = { path = "../../bags-list", version = "4.0.0-dev", features = ["fuzz"] } frame-election-provider-support = { path = "../../election-provider-support", version = "4.0.0-dev" } frame-system = { path = "../../system", version = "4.0.0-dev" } frame-support = { path = "../../support", version = "4.0.0-dev" } # core -sp-storage = { path = "../../../primitives/storage", version = "7.0.0" } -sp-core = { path = "../../../primitives/core", version = "7.0.0" } -sp-tracing = { path = "../../../primitives/tracing", version = "6.0.0" } -sp-runtime = { path = "../../../primitives/runtime", version = "7.0.0" } -sp-std = { path = "../../../primitives/std", version = "5.0.0" } +sp-storage = { path = "../../../primitives/storage", version = "13.0.0" } +sp-core = { path = "../../../primitives/core", version = "21.0.0" } +sp-tracing = { path = "../../../primitives/tracing", version = "10.0.0" } +sp-runtime = { path = "../../../primitives/runtime", version = "24.0.0" } +sp-std = { path = "../../../primitives/std", version = "8.0.0" } # utils remote-externalities = { path = "../../../utils/frame/remote-externalities", version = "0.10.0-dev", package = "frame-remote-externalities" } diff --git a/frame/bags-list/remote-tests/src/lib.rs b/frame/bags-list/remote-tests/src/lib.rs index fc25e3b65ddb..9f7c22d99dad 100644 --- a/frame/bags-list/remote-tests/src/lib.rs +++ b/frame/bags-list/remote-tests/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/bags-list/remote-tests/src/migration.rs b/frame/bags-list/remote-tests/src/migration.rs index 759906a4ef47..7847fdc7591c 100644 --- a/frame/bags-list/remote-tests/src/migration.rs +++ b/frame/bags-list/remote-tests/src/migration.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify diff --git a/frame/bags-list/remote-tests/src/snapshot.rs b/frame/bags-list/remote-tests/src/snapshot.rs index 0163ca200a15..0c6e194d3247 100644 --- a/frame/bags-list/remote-tests/src/snapshot.rs +++ b/frame/bags-list/remote-tests/src/snapshot.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify diff --git a/frame/bags-list/remote-tests/src/try_state.rs b/frame/bags-list/remote-tests/src/try_state.rs index 514c80d72ab6..5bbac00bc75a 100644 --- a/frame/bags-list/remote-tests/src/try_state.rs +++ b/frame/bags-list/remote-tests/src/try_state.rs @@ -1,4 +1,4 @@ -// Copyright 2021 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify @@ -16,7 +16,6 @@ //! Test to execute the sanity-check of the voter bag. -use frame_election_provider_support::SortedListProvider; use frame_support::{ storage::generator::StorageMap, traits::{Get, PalletInfoAccess}, @@ -51,7 +50,9 @@ pub async fn execute( ext.execute_with(|| { sp_core::crypto::set_default_ss58_version(Runtime::SS58Prefix::get().try_into().unwrap()); - pallet_bags_list::Pallet::::try_state().unwrap(); + + pallet_bags_list::Pallet::::do_try_state().unwrap(); + log::info!(target: crate::LOG_TARGET, "executed bags-list sanity check with no errors."); crate::display_and_check_bags::(currency_unit, currency_name); diff --git a/frame/bags-list/src/benchmarks.rs b/frame/bags-list/src/benchmarks.rs index 1f66697cb676..0c3955c0d7b7 100644 --- a/frame/bags-list/src/benchmarks.rs +++ b/frame/bags-list/src/benchmarks.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,13 +19,15 @@ use super::*; use crate::list::List; -use frame_benchmarking::{account, whitelist_account, whitelisted_caller}; +use frame_benchmarking::v1::{ + account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, +}; use frame_election_provider_support::ScoreProvider; use frame_support::{assert_ok, traits::Get}; use frame_system::RawOrigin as SystemOrigin; use sp_runtime::traits::One; -frame_benchmarking::benchmarks_instance_pallet! { +benchmarks_instance_pallet! { rebag_non_terminal { // An expensive case for rebag-ing (rebag a non-terminal node): // diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 14f8a613eb79..156c52cc87c4 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -59,6 +59,9 @@ use frame_system::ensure_signed; use sp_runtime::traits::{AtLeast32BitUnsigned, Bounded, StaticLookup}; use sp_std::prelude::*; +#[cfg(any(test, feature = "try-runtime", feature = "fuzz"))] +use sp_runtime::TryRuntimeError; + #[cfg(any(feature = "runtime-benchmarks", test))] mod benchmarks; @@ -99,7 +102,6 @@ pub mod pallet { use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(crate) trait Store)] pub struct Pallet(_); #[pallet::config] @@ -268,12 +270,19 @@ pub mod pallet { } #[cfg(feature = "try-runtime")] - fn try_state(_: BlockNumberFor) -> Result<(), &'static str> { + fn try_state(_: BlockNumberFor) -> Result<(), TryRuntimeError> { >::try_state() } } } +#[cfg(any(test, feature = "try-runtime", feature = "fuzz"))] +impl, I: 'static> Pallet { + pub fn do_try_state() -> Result<(), TryRuntimeError> { + List::::do_try_state() + } +} + impl, I: 'static> Pallet { /// Move an account from one bag to another, depositing an event on success. /// @@ -348,8 +357,9 @@ impl, I: 'static> SortedListProvider for Pallet List::::unsafe_regenerate(all, score_of) } - fn try_state() -> Result<(), &'static str> { - List::::try_state() + #[cfg(feature = "try-runtime")] + fn try_state() -> Result<(), TryRuntimeError> { + Self::do_try_state() } fn unsafe_clear() { @@ -390,7 +400,7 @@ impl, I: 'static> ScoreProvider for Pallet { Node::::get(id).map(|node| node.score()).unwrap_or_default() } - frame_election_provider_support::runtime_benchmarks_or_fuzz_enabled! { + frame_election_provider_support::runtime_benchmarks_fuzz_or_std_enabled! { fn set_score_of(id: &T::AccountId, new_score: T::Score) { ListNodes::::mutate(id, |maybe_node| { if let Some(node) = maybe_node.as_mut() { diff --git a/frame/bags-list/src/list/mod.rs b/frame/bags-list/src/list/mod.rs index 272526ad1a63..d8626080e252 100644 --- a/frame/bags-list/src/list/mod.rs +++ b/frame/bags-list/src/list/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,6 +42,9 @@ use sp_std::{ prelude::*, }; +#[cfg(any(test, feature = "try-runtime", feature = "fuzz"))] +use sp_runtime::TryRuntimeError; + #[derive(Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo, PalletError)] pub enum ListError { /// A duplicate id has been detected. @@ -220,9 +223,6 @@ impl, I: 'static> List { crate::ListBags::::remove(removed_bag); } - #[cfg(feature = "std")] - debug_assert_eq!(Self::try_state(), Ok(())); - num_affected } @@ -514,11 +514,12 @@ impl, I: 'static> List { /// * length of this list is in sync with `ListNodes::count()`, /// * and sanity-checks all bags and nodes. This will cascade down all the checks and makes sure /// all bags and nodes are checked per *any* update to `List`. - pub(crate) fn try_state() -> Result<(), &'static str> { + #[cfg(any(test, feature = "try-runtime", feature = "fuzz"))] + pub(crate) fn do_try_state() -> Result<(), TryRuntimeError> { let mut seen_in_list = BTreeSet::new(); ensure!( Self::iter().map(|node| node.id).all(|id| seen_in_list.insert(id)), - "duplicate identified", + "duplicate identified" ); let iter_count = Self::iter().count() as u32; @@ -542,7 +543,7 @@ impl, I: 'static> List { thresholds.into_iter().filter_map(|t| Bag::::get(t)) }; - let _ = active_bags.clone().try_for_each(|b| b.try_state())?; + let _ = active_bags.clone().try_for_each(|b| b.do_try_state())?; let nodes_in_bags_count = active_bags.clone().fold(0u32, |acc, cur| acc + cur.iter().count() as u32); @@ -553,7 +554,7 @@ impl, I: 'static> List { // check that all nodes are sane. We check the `ListNodes` storage item directly in case we // have some "stale" nodes that are not in a bag. for (_id, node) in crate::ListNodes::::iter() { - node.try_state()? + node.do_try_state()? } Ok(()) @@ -751,7 +752,8 @@ impl, I: 'static> Bag { /// * Ensures head has no prev. /// * Ensures tail has no next. /// * Ensures there are no loops, traversal from head to tail is correct. - fn try_state(&self) -> Result<(), &'static str> { + #[cfg(any(test, feature = "try-runtime", feature = "fuzz"))] + fn do_try_state(&self) -> Result<(), TryRuntimeError> { frame_support::ensure!( self.head() .map(|head| head.prev().is_none()) @@ -790,6 +792,7 @@ impl, I: 'static> Bag { } /// Check if the bag contains a node with `id`. + #[cfg(any(test, feature = "try-runtime", feature = "fuzz"))] fn contains(&self, id: &T::AccountId) -> bool { self.iter().any(|n| n.id() == id) } @@ -894,15 +897,13 @@ impl, I: 'static> Node { self.bag_upper } - fn try_state(&self) -> Result<(), &'static str> { + #[cfg(any(test, feature = "try-runtime", feature = "fuzz"))] + fn do_try_state(&self) -> Result<(), TryRuntimeError> { let expected_bag = Bag::::get(self.bag_upper).ok_or("bag not found for node")?; let id = self.id(); - frame_support::ensure!( - expected_bag.contains(id), - "node does not exist in the expected bag" - ); + frame_support::ensure!(expected_bag.contains(id), "node does not exist in the bag"); let non_terminal_check = !self.is_terminal() && expected_bag.head.as_ref() != Some(id) && diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index 966ea1a74c71..fd4ad8f893af 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ use crate::{ }; use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::{assert_ok, assert_storage_noop}; +use sp_runtime::TryRuntimeError; fn node( id: AccountId, @@ -137,6 +138,7 @@ fn migrate_works() { BagThresholds::set(NEW_THRESHOLDS); // and we call List::::migrate(old_thresholds); + assert_eq!(List::::do_try_state(), Ok(())); // then assert_eq!( @@ -352,13 +354,16 @@ mod list { #[test] fn try_state_works() { ExtBuilder::default().build_and_execute_no_post_check(|| { - assert_ok!(List::::try_state()); + assert_ok!(List::::do_try_state()); }); // make sure there are no duplicates. ExtBuilder::default().build_and_execute_no_post_check(|| { Bag::::get(10).unwrap().insert_unchecked(2, 10); - assert_eq!(List::::try_state(), Err("duplicate identified")); + assert_eq!( + List::::do_try_state(), + TryRuntimeError::Other("duplicate identified").into() + ); }); // ensure count is in sync with `ListNodes::count()`. @@ -372,7 +377,10 @@ mod list { CounterForListNodes::::mutate(|counter| *counter += 1); assert_eq!(crate::ListNodes::::count(), 5); - assert_eq!(List::::try_state(), Err("iter_count != stored_count")); + assert_eq!( + List::::do_try_state(), + TryRuntimeError::Other("iter_count != stored_count").into() + ); }); } @@ -804,7 +812,7 @@ mod bags { // then assert_eq!(bag_as_ids(&bag_1000), vec![2, 3, 13, 14]); - assert_ok!(bag_1000.try_state()); + assert_ok!(bag_1000.do_try_state()); // and the node isn't mutated when its removed assert_eq!(node_4, node_4_pre_remove); @@ -814,7 +822,7 @@ mod bags { // then assert_eq!(bag_as_ids(&bag_1000), vec![3, 13, 14]); - assert_ok!(bag_1000.try_state()); + assert_ok!(bag_1000.do_try_state()); // when removing a tail that is not pointing at the head let node_14 = Node::::get(&14).unwrap(); @@ -822,7 +830,7 @@ mod bags { // then assert_eq!(bag_as_ids(&bag_1000), vec![3, 13]); - assert_ok!(bag_1000.try_state()); + assert_ok!(bag_1000.do_try_state()); // when removing a tail that is pointing at the head let node_13 = Node::::get(&13).unwrap(); @@ -830,7 +838,7 @@ mod bags { // then assert_eq!(bag_as_ids(&bag_1000), vec![3]); - assert_ok!(bag_1000.try_state()); + assert_ok!(bag_1000.do_try_state()); // when removing a node that is both the head & tail let node_3 = Node::::get(&3).unwrap(); @@ -846,7 +854,7 @@ mod bags { // then assert_eq!(bag_as_ids(&bag_10), vec![1, 12]); - assert_ok!(bag_10.try_state()); + assert_ok!(bag_10.do_try_state()); // when removing a head that is pointing at the tail let node_1 = Node::::get(&1).unwrap(); @@ -854,7 +862,7 @@ mod bags { // then assert_eq!(bag_as_ids(&bag_10), vec![12]); - assert_ok!(bag_10.try_state()); + assert_ok!(bag_10.do_try_state()); // and since we updated the bag's head/tail, we need to write this storage so we // can correctly `get` it again in later checks bag_10.put(); @@ -865,7 +873,7 @@ mod bags { // then assert_eq!(bag_as_ids(&bag_2000), vec![15, 17, 18, 19]); - assert_ok!(bag_2000.try_state()); + assert_ok!(bag_2000.do_try_state()); // when removing a node that is pointing at tail, but not head let node_18 = Node::::get(&18).unwrap(); @@ -873,7 +881,7 @@ mod bags { // then assert_eq!(bag_as_ids(&bag_2000), vec![15, 17, 19]); - assert_ok!(bag_2000.try_state()); + assert_ok!(bag_2000.do_try_state()); // finally, when reading from storage, the state of all bags is as expected assert_eq!( @@ -905,7 +913,7 @@ mod bags { // .. and the bag it was removed from let bag_1000 = Bag::::get(1_000).unwrap(); // is sane - assert_ok!(bag_1000.try_state()); + assert_ok!(bag_1000.do_try_state()); // and has the correct head and tail. assert_eq!(bag_1000.head, Some(3)); assert_eq!(bag_1000.tail, Some(4)); diff --git a/frame/bags-list/src/migrations.rs b/frame/bags-list/src/migrations.rs index e1dc9f777e53..7df63a6a44c5 100644 --- a/frame/bags-list/src/migrations.rs +++ b/frame/bags-list/src/migrations.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,6 +24,9 @@ use frame_support::traits::OnRuntimeUpgrade; #[cfg(feature = "try-runtime")] use frame_support::ensure; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + #[cfg(feature = "try-runtime")] use sp_std::vec::Vec; @@ -35,7 +38,7 @@ impl, I: 'static> OnRuntimeUpgrade for CheckCounterPrefix Result, &'static str> { + fn pre_upgrade() -> Result, TryRuntimeError> { // The old explicit storage item. #[frame_support::storage_alias] type CounterForListNodes, I: 'static> = @@ -88,7 +91,7 @@ mod old { pub struct AddScore, I: 'static = ()>(sp_std::marker::PhantomData<(T, I)>); impl, I: 'static> OnRuntimeUpgrade for AddScore { #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { + fn pre_upgrade() -> Result, TryRuntimeError> { // The list node data should be corrupt at this point, so this is zero. ensure!(crate::ListNodes::::iter().count() == 0, "list node data is not corrupt"); // We can use the helper `old::ListNode` to get the existing data. @@ -119,7 +122,7 @@ impl, I: 'static> OnRuntimeUpgrade for AddScore { } #[cfg(feature = "try-runtime")] - fn post_upgrade(node_count_before: Vec) -> Result<(), &'static str> { + fn post_upgrade(node_count_before: Vec) -> Result<(), TryRuntimeError> { let node_count_before: u32 = Decode::decode(&mut node_count_before.as_slice()) .expect("the state parameter should be something that was generated by pre_upgrade"); // Now the list node data is not corrupt anymore. diff --git a/frame/bags-list/src/mock.rs b/frame/bags-list/src/mock.rs index 8cc96a988e72..ae50adabd508 100644 --- a/frame/bags-list/src/mock.rs +++ b/frame/bags-list/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +21,7 @@ use super::*; use crate::{self as bags_list}; use frame_election_provider_support::VoteWeight; use frame_support::parameter_types; +use sp_runtime::BuildStorage; use std::collections::HashMap; pub type AccountId = u32; @@ -40,9 +41,10 @@ impl frame_election_provider_support::ScoreProvider for StakingMock { *NextVoteWeightMap::get().get(id).unwrap_or(&NextVoteWeight::get()) } - #[cfg(any(feature = "runtime-benchmarks", feature = "fuzz", test))] - fn set_score_of(id: &AccountId, weight: Self::Score) { - NEXT_VOTE_WEIGHT_MAP.with(|m| m.borrow_mut().insert(*id, weight)); + frame_election_provider_support::runtime_benchmarks_fuzz_or_std_enabled! { + fn set_score_of(id: &AccountId, weight: Self::Score) { + NEXT_VOTE_WEIGHT_MAP.with(|m| m.borrow_mut().insert(*id, weight)); + } } } @@ -50,14 +52,13 @@ impl frame_system::Config for Runtime { type SS58Prefix = (); type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = sp_core::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = sp_runtime::traits::IdentityLookup; - type Header = sp_runtime::testing::Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type DbWeight = (); @@ -85,15 +86,11 @@ impl bags_list::Config for Runtime { type Score = VoteWeight; } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub struct Runtime { - System: frame_system::{Pallet, Call, Storage, Event, Config}, + System: frame_system::{Pallet, Call, Storage, Event, Config}, BagsList: bags_list::{Pallet, Call, Storage, Event}, } ); @@ -126,7 +123,7 @@ impl ExtBuilder { pub(crate) fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); - let storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); let ids_with_weight: Vec<_> = if self.skip_genesis_ids { self.ids.iter().collect() @@ -148,7 +145,7 @@ impl ExtBuilder { pub fn build_and_execute(self, test: impl FnOnce() -> ()) { self.build().execute_with(|| { test(); - List::::try_state().expect("Try-state post condition failed") + List::::do_try_state().expect("do_try_state post condition failed") }) } diff --git a/frame/bags-list/src/tests.rs b/frame/bags-list/src/tests.rs index 63a395ed0d65..74f1491835a3 100644 --- a/frame/bags-list/src/tests.rs +++ b/frame/bags-list/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/bags-list/src/weights.rs b/frame/bags-list/src/weights.rs index a298dc3172f7..d929c6bb9596 100644 --- a/frame/bags-list/src/weights.rs +++ b/frame/bags-list/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_bags_list //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_bags_list +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_bags_list. pub trait WeightInfo { @@ -55,70 +60,114 @@ pub trait WeightInfo { /// Weights for pallet_bags_list using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:0) - // Storage: VoterList ListNodes (r:4 w:4) - // Storage: VoterList ListBags (r:1 w:1) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:4 w:4) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) fn rebag_non_terminal() -> Weight { - // Minimum execution time: 73_553 nanoseconds. - Weight::from_ref_time(74_366_000 as u64) - .saturating_add(T::DbWeight::get().reads(7 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + // Proof Size summary in bytes: + // Measured: `1724` + // Estimated: `11506` + // Minimum execution time: 62_137_000 picoseconds. + Weight::from_parts(64_050_000, 11506) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:0) - // Storage: VoterList ListNodes (r:3 w:3) - // Storage: VoterList ListBags (r:2 w:2) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:3 w:3) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:2 w:2) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) fn rebag_terminal() -> Weight { - // Minimum execution time: 72_700 nanoseconds. - Weight::from_ref_time(73_322_000 as u64) - .saturating_add(T::DbWeight::get().reads(7 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + // Proof Size summary in bytes: + // Measured: `1618` + // Estimated: `8877` + // Minimum execution time: 60_880_000 picoseconds. + Weight::from_parts(62_078_000, 8877) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } - // Storage: VoterList ListNodes (r:4 w:4) - // Storage: Staking Bonded (r:2 w:0) - // Storage: Staking Ledger (r:2 w:0) - // Storage: VoterList CounterForListNodes (r:1 w:1) - // Storage: VoterList ListBags (r:1 w:1) + /// Storage: VoterList ListNodes (r:4 w:4) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:2 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:2 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) fn put_in_front_of() -> Weight { - // Minimum execution time: 71_691 nanoseconds. - Weight::from_ref_time(72_798_000 as u64) - .saturating_add(T::DbWeight::get().reads(10 as u64)) - .saturating_add(T::DbWeight::get().writes(6 as u64)) + // Proof Size summary in bytes: + // Measured: `1930` + // Estimated: `11506` + // Minimum execution time: 68_911_000 picoseconds. + Weight::from_parts(70_592_000, 11506) + .saturating_add(T::DbWeight::get().reads(10_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:0) - // Storage: VoterList ListNodes (r:4 w:4) - // Storage: VoterList ListBags (r:1 w:1) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:4 w:4) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) fn rebag_non_terminal() -> Weight { - // Minimum execution time: 73_553 nanoseconds. - Weight::from_ref_time(74_366_000 as u64) - .saturating_add(RocksDbWeight::get().reads(7 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + // Proof Size summary in bytes: + // Measured: `1724` + // Estimated: `11506` + // Minimum execution time: 62_137_000 picoseconds. + Weight::from_parts(64_050_000, 11506) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:0) - // Storage: VoterList ListNodes (r:3 w:3) - // Storage: VoterList ListBags (r:2 w:2) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:3 w:3) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:2 w:2) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) fn rebag_terminal() -> Weight { - // Minimum execution time: 72_700 nanoseconds. - Weight::from_ref_time(73_322_000 as u64) - .saturating_add(RocksDbWeight::get().reads(7 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + // Proof Size summary in bytes: + // Measured: `1618` + // Estimated: `8877` + // Minimum execution time: 60_880_000 picoseconds. + Weight::from_parts(62_078_000, 8877) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } - // Storage: VoterList ListNodes (r:4 w:4) - // Storage: Staking Bonded (r:2 w:0) - // Storage: Staking Ledger (r:2 w:0) - // Storage: VoterList CounterForListNodes (r:1 w:1) - // Storage: VoterList ListBags (r:1 w:1) + /// Storage: VoterList ListNodes (r:4 w:4) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:2 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:2 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) fn put_in_front_of() -> Weight { - // Minimum execution time: 71_691 nanoseconds. - Weight::from_ref_time(72_798_000 as u64) - .saturating_add(RocksDbWeight::get().reads(10 as u64)) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) + // Proof Size summary in bytes: + // Measured: `1930` + // Estimated: `11506` + // Minimum execution time: 68_911_000 picoseconds. + Weight::from_parts(70_592_000, 11506) + .saturating_add(RocksDbWeight::get().reads(10_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } } diff --git a/frame/balances/Cargo.toml b/frame/balances/Cargo.toml index 15ef136aa380..e240e3b6ce89 100644 --- a/frame/balances/Cargo.toml +++ b/frame/balances/Cargo.toml @@ -13,19 +13,20 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive", "max-encoded-len"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-transaction-payment = { version = "4.0.0-dev", path = "../transaction-payment" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } +paste = "1.0.12" [features] default = ["std"] @@ -39,5 +40,7 @@ std = [ "sp-runtime/std", "sp-std/std", ] +# Enable support for setting the existential deposit to zero. +insecure_zero_ed = [] runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/balances/README.md b/frame/balances/README.md index 93e424a89c72..fa1ee622d48c 100644 --- a/frame/balances/README.md +++ b/frame/balances/README.md @@ -70,7 +70,7 @@ given account is unused. ### Dispatchable Functions - `transfer` - Transfer some liquid free balance to another account. -- `set_balance` - Set the balances of a given account. The origin of this call must be root. +- `force_set_balance` - Set the balances of a given account. The origin of this call must be root. ## Usage @@ -94,7 +94,7 @@ The Staking module uses the `LockableCurrency` trait to lock a stash account's f use frame_support::traits::{WithdrawReasons, LockableCurrency}; use sp_runtime::traits::Bounded; pub trait Config: frame_system::Config { - type Currency: LockableCurrency; + type Currency: LockableCurrency>; } fn update_ledger( diff --git a/frame/balances/src/benchmarking.rs b/frame/balances/src/benchmarking.rs index f85ff43b715f..5641c68516c2 100644 --- a/frame/balances/src/benchmarking.rs +++ b/frame/balances/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2023 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,9 +22,10 @@ use super::*; use crate::Pallet as Balances; -use frame_benchmarking::{account, impl_benchmark_test_suite, whitelisted_caller}; -use frame_support::benchmarking::*; +use frame_benchmarking::v2::*; use frame_system::RawOrigin; +use sp_runtime::traits::Bounded; +use types::ExtraFlags; const SEED: u32 = 0; // existential deposit multiplier @@ -38,7 +39,7 @@ mod benchmarks { // * Transfer will kill the sender account. // * Transfer will create the recipient account. #[benchmark] - fn transfer() { + fn transfer_allow_death() { let existential_deposit = T::ExistentialDeposit::get(); let caller = whitelisted_caller(); @@ -80,7 +81,7 @@ mod benchmarks { let transfer_amount = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); #[extrinsic_call] - transfer(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount); + transfer_allow_death(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount); assert!(!Balances::::free_balance(&caller).is_zero()); assert!(!Balances::::free_balance(&recipient).is_zero()); @@ -107,9 +108,9 @@ mod benchmarks { assert_eq!(Balances::::free_balance(&recipient), transfer_amount); } - // Benchmark `set_balance` coming from ROOT account. This always creates an account. + // Benchmark `force_set_balance` coming from ROOT account. This always creates an account. #[benchmark] - fn set_balance_creating() { + fn force_set_balance_creating() { let user: T::AccountId = account("user", 0, SEED); let user_lookup = T::Lookup::unlookup(user.clone()); @@ -119,15 +120,14 @@ mod benchmarks { let _ = as Currency<_>>::make_free_balance_be(&user, balance_amount); #[extrinsic_call] - set_balance(RawOrigin::Root, user_lookup, balance_amount, balance_amount); + force_set_balance(RawOrigin::Root, user_lookup, balance_amount); assert_eq!(Balances::::free_balance(&user), balance_amount); - assert_eq!(Balances::::reserved_balance(&user), balance_amount); } - // Benchmark `set_balance` coming from ROOT account. This always kills an account. + // Benchmark `force_set_balance` coming from ROOT account. This always kills an account. #[benchmark] - fn set_balance_killing() { + fn force_set_balance_killing() { let user: T::AccountId = account("user", 0, SEED); let user_lookup = T::Lookup::unlookup(user.clone()); @@ -137,7 +137,7 @@ mod benchmarks { let _ = as Currency<_>>::make_free_balance_be(&user, balance_amount); #[extrinsic_call] - set_balance(RawOrigin::Root, user_lookup, Zero::zero(), Zero::zero()); + force_set_balance(RawOrigin::Root, user_lookup, Zero::zero()); assert!(Balances::::free_balance(&user).is_zero()); } @@ -198,7 +198,7 @@ mod benchmarks { } #[extrinsic_call] - transfer(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount); + transfer_allow_death(RawOrigin::Signed(caller.clone()), recipient_lookup, transfer_amount); assert_eq!(Balances::::free_balance(&caller), Zero::zero()); assert_eq!(Balances::::free_balance(&recipient), transfer_amount); @@ -226,30 +226,69 @@ mod benchmarks { } #[benchmark] - fn force_unreserve() { + fn force_unreserve() -> Result<(), BenchmarkError> { let user: T::AccountId = account("user", 0, SEED); let user_lookup = T::Lookup::unlookup(user.clone()); // Give some multiple of the existential deposit - let existential_deposit = T::ExistentialDeposit::get(); - let balance = existential_deposit.saturating_mul(ED_MULTIPLIER.into()); + let ed = T::ExistentialDeposit::get(); + let balance = ed + ed; let _ = as Currency<_>>::make_free_balance_be(&user, balance); // Reserve the balance - as ReservableCurrency<_>>::reserve(&user, balance)?; - assert_eq!(Balances::::reserved_balance(&user), balance); - assert!(Balances::::free_balance(&user).is_zero()); + as ReservableCurrency<_>>::reserve(&user, ed)?; + assert_eq!(Balances::::reserved_balance(&user), ed); + assert_eq!(Balances::::free_balance(&user), ed); #[extrinsic_call] _(RawOrigin::Root, user_lookup, balance); assert!(Balances::::reserved_balance(&user).is_zero()); - assert_eq!(Balances::::free_balance(&user), balance); + assert_eq!(Balances::::free_balance(&user), ed + ed); + + Ok(()) + } + + #[benchmark] + fn upgrade_accounts(u: Linear<1, 1_000>) { + let caller: T::AccountId = whitelisted_caller(); + let who = (0..u) + .into_iter() + .map(|i| -> T::AccountId { + let user = account("old_user", i, SEED); + let account = AccountData { + free: T::ExistentialDeposit::get(), + reserved: T::ExistentialDeposit::get(), + frozen: Zero::zero(), + flags: ExtraFlags::old_logic(), + }; + frame_system::Pallet::::inc_providers(&user); + assert!(T::AccountStore::try_mutate_exists(&user, |a| -> DispatchResult { + *a = Some(account); + Ok(()) + }) + .is_ok()); + assert!(!Balances::::account(&user).flags.is_new_logic()); + assert_eq!(frame_system::Pallet::::providers(&user), 1); + assert_eq!(frame_system::Pallet::::consumers(&user), 0); + user + }) + .collect(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), who); + + for i in 0..u { + let user: T::AccountId = account("old_user", i, SEED); + assert!(Balances::::account(&user).flags.is_new_logic()); + assert_eq!(frame_system::Pallet::::providers(&user), 1); + assert_eq!(frame_system::Pallet::::consumers(&user), 1); + } } impl_benchmark_test_suite! { Balances, - crate::tests_composite::ExtBuilder::default().build(), - crate::tests_composite::Test, + crate::tests::ExtBuilder::default().build(), + crate::tests::Test, } } diff --git a/frame/balances/src/impl_currency.rs b/frame/balances/src/impl_currency.rs new file mode 100644 index 000000000000..2cbe776c5129 --- /dev/null +++ b/frame/balances/src/impl_currency.rs @@ -0,0 +1,910 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Implementations for the `Currency` family of traits. +//! +//! Note that `WithdrawReasons` are intentionally not used for anything in this implementation and +//! are expected to be removed in the near future, once migration to `fungible::*` traits is done. + +use super::*; +use frame_support::{ + ensure, + pallet_prelude::DispatchResult, + traits::{ + tokens::{fungible, BalanceStatus as Status, Fortitude::Polite, Precision::BestEffort}, + Currency, DefensiveSaturating, ExistenceRequirement, + ExistenceRequirement::AllowDeath, + Get, Imbalance, LockIdentifier, LockableCurrency, NamedReservableCurrency, + ReservableCurrency, SignedImbalance, TryDrop, WithdrawReasons, + }, +}; +use frame_system::pallet_prelude::BlockNumberFor; +pub use imbalances::{NegativeImbalance, PositiveImbalance}; + +// wrapping these imbalances in a private module is necessary to ensure absolute privacy +// of the inner member. +mod imbalances { + use super::{result, Config, Imbalance, RuntimeDebug, Saturating, TryDrop, Zero}; + use frame_support::traits::SameOrOther; + use sp_std::mem; + + /// Opaque, move-only struct with private fields that serves as a token denoting that + /// funds have been created without any equal and opposite accounting. + #[must_use] + #[derive(RuntimeDebug, PartialEq, Eq)] + pub struct PositiveImbalance, I: 'static = ()>(T::Balance); + + impl, I: 'static> PositiveImbalance { + /// Create a new positive imbalance from a balance. + pub fn new(amount: T::Balance) -> Self { + PositiveImbalance(amount) + } + } + + /// Opaque, move-only struct with private fields that serves as a token denoting that + /// funds have been destroyed without any equal and opposite accounting. + #[must_use] + #[derive(RuntimeDebug, PartialEq, Eq)] + pub struct NegativeImbalance, I: 'static = ()>(T::Balance); + + impl, I: 'static> NegativeImbalance { + /// Create a new negative imbalance from a balance. + pub fn new(amount: T::Balance) -> Self { + NegativeImbalance(amount) + } + } + + impl, I: 'static> TryDrop for PositiveImbalance { + fn try_drop(self) -> result::Result<(), Self> { + self.drop_zero() + } + } + + impl, I: 'static> Default for PositiveImbalance { + fn default() -> Self { + Self::zero() + } + } + + impl, I: 'static> Imbalance for PositiveImbalance { + type Opposite = NegativeImbalance; + + fn zero() -> Self { + Self(Zero::zero()) + } + fn drop_zero(self) -> result::Result<(), Self> { + if self.0.is_zero() { + Ok(()) + } else { + Err(self) + } + } + fn split(self, amount: T::Balance) -> (Self, Self) { + let first = self.0.min(amount); + let second = self.0 - first; + + mem::forget(self); + (Self(first), Self(second)) + } + fn merge(mut self, other: Self) -> Self { + self.0 = self.0.saturating_add(other.0); + mem::forget(other); + + self + } + fn subsume(&mut self, other: Self) { + self.0 = self.0.saturating_add(other.0); + mem::forget(other); + } + fn offset(self, other: Self::Opposite) -> SameOrOther { + let (a, b) = (self.0, other.0); + mem::forget((self, other)); + + if a > b { + SameOrOther::Same(Self(a - b)) + } else if b > a { + SameOrOther::Other(NegativeImbalance::new(b - a)) + } else { + SameOrOther::None + } + } + fn peek(&self) -> T::Balance { + self.0 + } + } + + impl, I: 'static> TryDrop for NegativeImbalance { + fn try_drop(self) -> result::Result<(), Self> { + self.drop_zero() + } + } + + impl, I: 'static> Default for NegativeImbalance { + fn default() -> Self { + Self::zero() + } + } + + impl, I: 'static> Imbalance for NegativeImbalance { + type Opposite = PositiveImbalance; + + fn zero() -> Self { + Self(Zero::zero()) + } + fn drop_zero(self) -> result::Result<(), Self> { + if self.0.is_zero() { + Ok(()) + } else { + Err(self) + } + } + fn split(self, amount: T::Balance) -> (Self, Self) { + let first = self.0.min(amount); + let second = self.0 - first; + + mem::forget(self); + (Self(first), Self(second)) + } + fn merge(mut self, other: Self) -> Self { + self.0 = self.0.saturating_add(other.0); + mem::forget(other); + + self + } + fn subsume(&mut self, other: Self) { + self.0 = self.0.saturating_add(other.0); + mem::forget(other); + } + fn offset(self, other: Self::Opposite) -> SameOrOther { + let (a, b) = (self.0, other.0); + mem::forget((self, other)); + + if a > b { + SameOrOther::Same(Self(a - b)) + } else if b > a { + SameOrOther::Other(PositiveImbalance::new(b - a)) + } else { + SameOrOther::None + } + } + fn peek(&self) -> T::Balance { + self.0 + } + } + + impl, I: 'static> Drop for PositiveImbalance { + /// Basic drop handler will just square up the total issuance. + fn drop(&mut self) { + >::mutate(|v| *v = v.saturating_add(self.0)); + } + } + + impl, I: 'static> Drop for NegativeImbalance { + /// Basic drop handler will just square up the total issuance. + fn drop(&mut self) { + >::mutate(|v| *v = v.saturating_sub(self.0)); + } + } +} + +impl, I: 'static> Currency for Pallet +where + T::Balance: MaybeSerializeDeserialize + Debug, +{ + type Balance = T::Balance; + type PositiveImbalance = PositiveImbalance; + type NegativeImbalance = NegativeImbalance; + + fn total_balance(who: &T::AccountId) -> Self::Balance { + Self::account(who).total() + } + + // Check if `value` amount of free balance can be slashed from `who`. + fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool { + if value.is_zero() { + return true + } + Self::free_balance(who) >= value + } + + fn total_issuance() -> Self::Balance { + TotalIssuance::::get() + } + + fn active_issuance() -> Self::Balance { + >::active_issuance() + } + + fn deactivate(amount: Self::Balance) { + >::deactivate(amount); + } + + fn reactivate(amount: Self::Balance) { + >::reactivate(amount); + } + + fn minimum_balance() -> Self::Balance { + T::ExistentialDeposit::get() + } + + // Burn funds from the total issuance, returning a positive imbalance for the amount burned. + // Is a no-op if amount to be burned is zero. + fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance { + if amount.is_zero() { + return PositiveImbalance::zero() + } + >::mutate(|issued| { + *issued = issued.checked_sub(&amount).unwrap_or_else(|| { + amount = *issued; + Zero::zero() + }); + }); + PositiveImbalance::new(amount) + } + + // Create new funds into the total issuance, returning a negative imbalance + // for the amount issued. + // Is a no-op if amount to be issued it zero. + fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance { + if amount.is_zero() { + return NegativeImbalance::zero() + } + >::mutate(|issued| { + *issued = issued.checked_add(&amount).unwrap_or_else(|| { + amount = Self::Balance::max_value() - *issued; + Self::Balance::max_value() + }) + }); + NegativeImbalance::new(amount) + } + + fn free_balance(who: &T::AccountId) -> Self::Balance { + Self::account(who).free + } + + // Ensure that an account can withdraw from their free balance given any existing withdrawal + // restrictions like locks and vesting balance. + // Is a no-op if amount to be withdrawn is zero. + fn ensure_can_withdraw( + who: &T::AccountId, + amount: T::Balance, + _reasons: WithdrawReasons, + new_balance: T::Balance, + ) -> DispatchResult { + if amount.is_zero() { + return Ok(()) + } + ensure!(new_balance >= Self::account(who).frozen, Error::::LiquidityRestrictions); + Ok(()) + } + + // Transfer some free balance from `transactor` to `dest`, respecting existence requirements. + // Is a no-op if value to be transferred is zero or the `transactor` is the same as `dest`. + fn transfer( + transactor: &T::AccountId, + dest: &T::AccountId, + value: Self::Balance, + existence_requirement: ExistenceRequirement, + ) -> DispatchResult { + if value.is_zero() || transactor == dest { + return Ok(()) + } + let keep_alive = match existence_requirement { + ExistenceRequirement::KeepAlive => Preserve, + ExistenceRequirement::AllowDeath => Expendable, + }; + >::transfer(transactor, dest, value, keep_alive)?; + Ok(()) + } + + /// Slash a target account `who`, returning the negative imbalance created and any left over + /// amount that could not be slashed. + /// + /// Is a no-op if `value` to be slashed is zero or the account does not exist. + /// + /// NOTE: `slash()` prefers free balance, but assumes that reserve balance can be drawn + /// from in extreme circumstances. `can_slash()` should be used prior to `slash()` to avoid + /// having to draw from reserved funds, however we err on the side of punishment if things are + /// inconsistent or `can_slash` wasn't used appropriately. + fn slash(who: &T::AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) { + if value.is_zero() { + return (NegativeImbalance::zero(), Zero::zero()) + } + if Self::total_balance(who).is_zero() { + return (NegativeImbalance::zero(), value) + } + + let result = match Self::try_mutate_account_handling_dust( + who, + |account, _is_new| -> Result<(Self::NegativeImbalance, Self::Balance), DispatchError> { + // Best value is the most amount we can slash following liveness rules. + let ed = T::ExistentialDeposit::get(); + let actual = match system::Pallet::::can_dec_provider(who) { + true => value.min(account.free), + false => value.min(account.free.saturating_sub(ed)), + }; + account.free.saturating_reduce(actual); + let remaining = value.saturating_sub(actual); + Ok((NegativeImbalance::new(actual), remaining)) + }, + ) { + Ok((imbalance, remaining)) => { + Self::deposit_event(Event::Slashed { + who: who.clone(), + amount: value.saturating_sub(remaining), + }); + (imbalance, remaining) + }, + Err(_) => (Self::NegativeImbalance::zero(), value), + }; + result + } + + /// Deposit some `value` into the free balance of an existing target account `who`. + /// + /// Is a no-op if the `value` to be deposited is zero. + fn deposit_into_existing( + who: &T::AccountId, + value: Self::Balance, + ) -> Result { + if value.is_zero() { + return Ok(PositiveImbalance::zero()) + } + + Self::try_mutate_account_handling_dust( + who, + |account, is_new| -> Result { + ensure!(!is_new, Error::::DeadAccount); + account.free = account.free.checked_add(&value).ok_or(ArithmeticError::Overflow)?; + Self::deposit_event(Event::Deposit { who: who.clone(), amount: value }); + Ok(PositiveImbalance::new(value)) + }, + ) + } + + /// Deposit some `value` into the free balance of `who`, possibly creating a new account. + /// + /// This function is a no-op if: + /// - the `value` to be deposited is zero; or + /// - the `value` to be deposited is less than the required ED and the account does not yet + /// exist; or + /// - the deposit would necessitate the account to exist and there are no provider references; + /// or + /// - `value` is so large it would cause the balance of `who` to overflow. + fn deposit_creating(who: &T::AccountId, value: Self::Balance) -> Self::PositiveImbalance { + if value.is_zero() { + return Self::PositiveImbalance::zero() + } + + Self::try_mutate_account_handling_dust( + who, + |account, is_new| -> Result { + let ed = T::ExistentialDeposit::get(); + ensure!(value >= ed || !is_new, Error::::ExistentialDeposit); + + // defensive only: overflow should never happen, however in case it does, then this + // operation is a no-op. + account.free = match account.free.checked_add(&value) { + Some(x) => x, + None => return Ok(Self::PositiveImbalance::zero()), + }; + + Self::deposit_event(Event::Deposit { who: who.clone(), amount: value }); + Ok(PositiveImbalance::new(value)) + }, + ) + .unwrap_or_else(|_| Self::PositiveImbalance::zero()) + } + + /// Withdraw some free balance from an account, respecting existence requirements. + /// + /// Is a no-op if value to be withdrawn is zero. + fn withdraw( + who: &T::AccountId, + value: Self::Balance, + reasons: WithdrawReasons, + liveness: ExistenceRequirement, + ) -> result::Result { + if value.is_zero() { + return Ok(NegativeImbalance::zero()) + } + + Self::try_mutate_account_handling_dust( + who, + |account, _| -> Result { + let new_free_account = + account.free.checked_sub(&value).ok_or(Error::::InsufficientBalance)?; + + // bail if we need to keep the account alive and this would kill it. + let ed = T::ExistentialDeposit::get(); + let would_be_dead = new_free_account < ed; + let would_kill = would_be_dead && account.free >= ed; + ensure!(liveness == AllowDeath || !would_kill, Error::::Expendability); + + Self::ensure_can_withdraw(who, value, reasons, new_free_account)?; + + account.free = new_free_account; + + Self::deposit_event(Event::Withdraw { who: who.clone(), amount: value }); + Ok(NegativeImbalance::new(value)) + }, + ) + } + + /// Force the new free balance of a target account `who` to some new value `balance`. + fn make_free_balance_be( + who: &T::AccountId, + value: Self::Balance, + ) -> SignedImbalance { + Self::try_mutate_account_handling_dust( + who, + |account, + is_new| + -> Result, DispatchError> { + let ed = T::ExistentialDeposit::get(); + // If we're attempting to set an existing account to less than ED, then + // bypass the entire operation. It's a no-op if you follow it through, but + // since this is an instance where we might account for a negative imbalance + // (in the dust cleaner of set_account) before we account for its actual + // equal and opposite cause (returned as an Imbalance), then in the + // instance that there's no other accounts on the system at all, we might + // underflow the issuance and our arithmetic will be off. + ensure!(value >= ed || !is_new, Error::::ExistentialDeposit); + + let imbalance = if account.free <= value { + SignedImbalance::Positive(PositiveImbalance::new(value - account.free)) + } else { + SignedImbalance::Negative(NegativeImbalance::new(account.free - value)) + }; + account.free = value; + Self::deposit_event(Event::BalanceSet { who: who.clone(), free: account.free }); + Ok(imbalance) + }, + ) + .unwrap_or_else(|_| SignedImbalance::Positive(Self::PositiveImbalance::zero())) + } +} + +impl, I: 'static> ReservableCurrency for Pallet +where + T::Balance: MaybeSerializeDeserialize + Debug, +{ + /// Check if `who` can reserve `value` from their free balance. + /// + /// Always `true` if value to be reserved is zero. + fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool { + if value.is_zero() { + return true + } + Self::account(who).free.checked_sub(&value).map_or(false, |new_balance| { + new_balance >= T::ExistentialDeposit::get() && + Self::ensure_can_withdraw(who, value, WithdrawReasons::RESERVE, new_balance) + .is_ok() + }) + } + + fn reserved_balance(who: &T::AccountId) -> Self::Balance { + Self::account(who).reserved + } + + /// Move `value` from the free balance from `who` to their reserved balance. + /// + /// Is a no-op if value to be reserved is zero. + fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult { + if value.is_zero() { + return Ok(()) + } + + Self::try_mutate_account_handling_dust(who, |account, _| -> DispatchResult { + account.free = + account.free.checked_sub(&value).ok_or(Error::::InsufficientBalance)?; + account.reserved = + account.reserved.checked_add(&value).ok_or(ArithmeticError::Overflow)?; + Self::ensure_can_withdraw(&who, value, WithdrawReasons::RESERVE, account.free) + })?; + + Self::deposit_event(Event::Reserved { who: who.clone(), amount: value }); + Ok(()) + } + + /// Unreserve some funds, returning any amount that was unable to be unreserved. + /// + /// Is a no-op if the value to be unreserved is zero or the account does not exist. + /// + /// NOTE: returns amount value which wasn't successfully unreserved. + fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance { + if value.is_zero() { + return Zero::zero() + } + if Self::total_balance(who).is_zero() { + return value + } + + let actual = match Self::mutate_account_handling_dust(who, |account| { + let actual = cmp::min(account.reserved, value); + account.reserved -= actual; + // defensive only: this can never fail since total issuance which is at least + // free+reserved fits into the same data type. + account.free = account.free.defensive_saturating_add(actual); + actual + }) { + Ok(x) => x, + Err(_) => { + // This should never happen since we don't alter the total amount in the account. + // If it ever does, then we should fail gracefully though, indicating that nothing + // could be done. + return value + }, + }; + + Self::deposit_event(Event::Unreserved { who: who.clone(), amount: actual }); + value - actual + } + + /// Slash from reserved balance, returning the negative imbalance created, + /// and any amount that was unable to be slashed. + /// + /// Is a no-op if the value to be slashed is zero or the account does not exist. + fn slash_reserved( + who: &T::AccountId, + value: Self::Balance, + ) -> (Self::NegativeImbalance, Self::Balance) { + if value.is_zero() { + return (NegativeImbalance::zero(), Zero::zero()) + } + if Self::total_balance(who).is_zero() { + return (NegativeImbalance::zero(), value) + } + + // NOTE: `mutate_account` may fail if it attempts to reduce the balance to the point that an + // account is attempted to be illegally destroyed. + + match Self::mutate_account_handling_dust(who, |account| { + let actual = value.min(account.reserved); + account.reserved.saturating_reduce(actual); + + // underflow should never happen, but it if does, there's nothing to be done here. + (NegativeImbalance::new(actual), value.saturating_sub(actual)) + }) { + Ok((imbalance, not_slashed)) => { + Self::deposit_event(Event::Slashed { + who: who.clone(), + amount: value.saturating_sub(not_slashed), + }); + (imbalance, not_slashed) + }, + Err(_) => (Self::NegativeImbalance::zero(), value), + } + } + + /// Move the reserved balance of one account into the balance of another, according to `status`. + /// + /// Is a no-op if: + /// - the value to be moved is zero; or + /// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`. + /// + /// This is `Polite` and thus will not repatriate any funds which would lead the total balance + /// to be less than the frozen amount. Returns `Ok` with the actual amount of funds moved, + /// which may be less than `value` since the operation is done an a `BestEffort` basis. + fn repatriate_reserved( + slashed: &T::AccountId, + beneficiary: &T::AccountId, + value: Self::Balance, + status: Status, + ) -> Result { + let actual = + Self::do_transfer_reserved(slashed, beneficiary, value, BestEffort, Polite, status)?; + Ok(value.saturating_sub(actual)) + } +} + +impl, I: 'static> NamedReservableCurrency for Pallet +where + T::Balance: MaybeSerializeDeserialize + Debug, +{ + type ReserveIdentifier = T::ReserveIdentifier; + + fn reserved_balance_named(id: &Self::ReserveIdentifier, who: &T::AccountId) -> Self::Balance { + let reserves = Self::reserves(who); + reserves + .binary_search_by_key(id, |data| data.id) + .map(|index| reserves[index].amount) + .unwrap_or_default() + } + + /// Move `value` from the free balance from `who` to a named reserve balance. + /// + /// Is a no-op if value to be reserved is zero. + fn reserve_named( + id: &Self::ReserveIdentifier, + who: &T::AccountId, + value: Self::Balance, + ) -> DispatchResult { + if value.is_zero() { + return Ok(()) + } + + Reserves::::try_mutate(who, |reserves| -> DispatchResult { + match reserves.binary_search_by_key(id, |data| data.id) { + Ok(index) => { + // this add can't overflow but just to be defensive. + reserves[index].amount = reserves[index].amount.defensive_saturating_add(value); + }, + Err(index) => { + reserves + .try_insert(index, ReserveData { id: *id, amount: value }) + .map_err(|_| Error::::TooManyReserves)?; + }, + }; + >::reserve(who, value)?; + Ok(()) + }) + } + + /// Unreserve some funds, returning any amount that was unable to be unreserved. + /// + /// Is a no-op if the value to be unreserved is zero. + fn unreserve_named( + id: &Self::ReserveIdentifier, + who: &T::AccountId, + value: Self::Balance, + ) -> Self::Balance { + if value.is_zero() { + return Zero::zero() + } + + Reserves::::mutate_exists(who, |maybe_reserves| -> Self::Balance { + if let Some(reserves) = maybe_reserves.as_mut() { + match reserves.binary_search_by_key(id, |data| data.id) { + Ok(index) => { + let to_change = cmp::min(reserves[index].amount, value); + + let remain = >::unreserve(who, to_change); + + // remain should always be zero but just to be defensive here. + let actual = to_change.defensive_saturating_sub(remain); + + // `actual <= to_change` and `to_change <= amount`; qed; + reserves[index].amount -= actual; + + if reserves[index].amount.is_zero() { + if reserves.len() == 1 { + // no more named reserves + *maybe_reserves = None; + } else { + // remove this named reserve + reserves.remove(index); + } + } + + value - actual + }, + Err(_) => value, + } + } else { + value + } + }) + } + + /// Slash from reserved balance, returning the negative imbalance created, + /// and any amount that was unable to be slashed. + /// + /// Is a no-op if the value to be slashed is zero. + fn slash_reserved_named( + id: &Self::ReserveIdentifier, + who: &T::AccountId, + value: Self::Balance, + ) -> (Self::NegativeImbalance, Self::Balance) { + if value.is_zero() { + return (NegativeImbalance::zero(), Zero::zero()) + } + + Reserves::::mutate(who, |reserves| -> (Self::NegativeImbalance, Self::Balance) { + match reserves.binary_search_by_key(id, |data| data.id) { + Ok(index) => { + let to_change = cmp::min(reserves[index].amount, value); + + let (imb, remain) = + >::slash_reserved(who, to_change); + + // remain should always be zero but just to be defensive here. + let actual = to_change.defensive_saturating_sub(remain); + + // `actual <= to_change` and `to_change <= amount`; qed; + reserves[index].amount -= actual; + + Self::deposit_event(Event::Slashed { who: who.clone(), amount: actual }); + (imb, value - actual) + }, + Err(_) => (NegativeImbalance::zero(), value), + } + }) + } + + /// Move the reserved balance of one account into the balance of another, according to `status`. + /// If `status` is `Reserved`, the balance will be reserved with given `id`. + /// + /// Is a no-op if: + /// - the value to be moved is zero; or + /// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`. + fn repatriate_reserved_named( + id: &Self::ReserveIdentifier, + slashed: &T::AccountId, + beneficiary: &T::AccountId, + value: Self::Balance, + status: Status, + ) -> Result { + if value.is_zero() { + return Ok(Zero::zero()) + } + + if slashed == beneficiary { + return match status { + Status::Free => Ok(Self::unreserve_named(id, slashed, value)), + Status::Reserved => + Ok(value.saturating_sub(Self::reserved_balance_named(id, slashed))), + } + } + + Reserves::::try_mutate(slashed, |reserves| -> Result { + match reserves.binary_search_by_key(id, |data| data.id) { + Ok(index) => { + let to_change = cmp::min(reserves[index].amount, value); + + let actual = if status == Status::Reserved { + // make it the reserved under same identifier + Reserves::::try_mutate( + beneficiary, + |reserves| -> Result { + match reserves.binary_search_by_key(id, |data| data.id) { + Ok(index) => { + let remain = + >::repatriate_reserved( + slashed, + beneficiary, + to_change, + status, + )?; + + // remain should always be zero but just to be defensive + // here. + let actual = to_change.defensive_saturating_sub(remain); + + // this add can't overflow but just to be defensive. + reserves[index].amount = + reserves[index].amount.defensive_saturating_add(actual); + + Ok(actual) + }, + Err(index) => { + let remain = + >::repatriate_reserved( + slashed, + beneficiary, + to_change, + status, + )?; + + // remain should always be zero but just to be defensive + // here + let actual = to_change.defensive_saturating_sub(remain); + + reserves + .try_insert( + index, + ReserveData { id: *id, amount: actual }, + ) + .map_err(|_| Error::::TooManyReserves)?; + + Ok(actual) + }, + } + }, + )? + } else { + let remain = >::repatriate_reserved( + slashed, + beneficiary, + to_change, + status, + )?; + + // remain should always be zero but just to be defensive here + to_change.defensive_saturating_sub(remain) + }; + + // `actual <= to_change` and `to_change <= amount`; qed; + reserves[index].amount -= actual; + + Ok(value - actual) + }, + Err(_) => Ok(value), + } + }) + } +} + +impl, I: 'static> LockableCurrency for Pallet +where + T::Balance: MaybeSerializeDeserialize + Debug, +{ + type Moment = BlockNumberFor; + + type MaxLocks = T::MaxLocks; + + // Set or alter a lock on the balance of `who`. + fn set_lock( + id: LockIdentifier, + who: &T::AccountId, + amount: T::Balance, + reasons: WithdrawReasons, + ) { + if reasons.is_empty() || amount.is_zero() { + Self::remove_lock(id, who); + return + } + + let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() }); + let mut locks = Self::locks(who) + .into_iter() + .filter_map(|l| if l.id == id { new_lock.take() } else { Some(l) }) + .collect::>(); + if let Some(lock) = new_lock { + locks.push(lock) + } + Self::update_locks(who, &locks[..]); + } + + // Extend a lock on the balance of `who`. + // Is a no-op if lock amount is zero or `reasons` `is_none()`. + fn extend_lock( + id: LockIdentifier, + who: &T::AccountId, + amount: T::Balance, + reasons: WithdrawReasons, + ) { + if amount.is_zero() || reasons.is_empty() { + return + } + let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() }); + let mut locks = Self::locks(who) + .into_iter() + .filter_map(|l| { + if l.id == id { + new_lock.take().map(|nl| BalanceLock { + id: l.id, + amount: l.amount.max(nl.amount), + reasons: l.reasons | nl.reasons, + }) + } else { + Some(l) + } + }) + .collect::>(); + if let Some(lock) = new_lock { + locks.push(lock) + } + Self::update_locks(who, &locks[..]); + } + + fn remove_lock(id: LockIdentifier, who: &T::AccountId) { + let mut locks = Self::locks(who); + locks.retain(|l| l.id != id); + Self::update_locks(who, &locks[..]); + } +} diff --git a/frame/balances/src/impl_fungible.rs b/frame/balances/src/impl_fungible.rs new file mode 100644 index 000000000000..03c40bb3a840 --- /dev/null +++ b/frame/balances/src/impl_fungible.rs @@ -0,0 +1,358 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Implementation of `fungible` traits for Balances pallet. +use super::*; +use frame_support::traits::tokens::{ + Fortitude, + Preservation::{self, Preserve, Protect}, + Provenance::{self, Minted}, +}; + +impl, I: 'static> fungible::Inspect for Pallet { + type Balance = T::Balance; + + fn total_issuance() -> Self::Balance { + TotalIssuance::::get() + } + fn active_issuance() -> Self::Balance { + TotalIssuance::::get().saturating_sub(InactiveIssuance::::get()) + } + fn minimum_balance() -> Self::Balance { + T::ExistentialDeposit::get() + } + fn total_balance(who: &T::AccountId) -> Self::Balance { + Self::account(who).total() + } + fn balance(who: &T::AccountId) -> Self::Balance { + Self::account(who).free + } + fn reducible_balance( + who: &T::AccountId, + preservation: Preservation, + force: Fortitude, + ) -> Self::Balance { + let a = Self::account(who); + let mut untouchable = Zero::zero(); + if force == Polite { + // Frozen balance applies to total. Anything on hold therefore gets discounted from the + // limit given by the freezes. + untouchable = a.frozen.saturating_sub(a.reserved); + } + // If we want to keep our provider ref.. + if preservation == Preserve + // ..or we don't want the account to die and our provider ref is needed for it to live.. + || preservation == Protect && !a.free.is_zero() && + frame_system::Pallet::::providers(who) == 1 + // ..or we don't care about the account dying but our provider ref is required.. + || preservation == Expendable && !a.free.is_zero() && + !frame_system::Pallet::::can_dec_provider(who) + { + // ..then the ED needed.. + untouchable = untouchable.max(T::ExistentialDeposit::get()); + } + // Liquid balance is what is neither on hold nor frozen/required for provider. + a.free.saturating_sub(untouchable) + } + fn can_deposit( + who: &T::AccountId, + amount: Self::Balance, + provenance: Provenance, + ) -> DepositConsequence { + if amount.is_zero() { + return DepositConsequence::Success + } + + if provenance == Minted && TotalIssuance::::get().checked_add(&amount).is_none() { + return DepositConsequence::Overflow + } + + let account = Self::account(who); + let new_free = match account.free.checked_add(&amount) { + None => return DepositConsequence::Overflow, + Some(x) if x < T::ExistentialDeposit::get() => return DepositConsequence::BelowMinimum, + Some(x) => x, + }; + + match account.reserved.checked_add(&new_free) { + Some(_) => {}, + None => return DepositConsequence::Overflow, + }; + + // NOTE: We assume that we are a provider, so don't need to do any checks in the + // case of account creation. + + DepositConsequence::Success + } + fn can_withdraw( + who: &T::AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence { + if amount.is_zero() { + return WithdrawConsequence::Success + } + + if TotalIssuance::::get().checked_sub(&amount).is_none() { + return WithdrawConsequence::Underflow + } + + let account = Self::account(who); + let new_free_balance = match account.free.checked_sub(&amount) { + Some(x) => x, + None => return WithdrawConsequence::BalanceLow, + }; + + let liquid = Self::reducible_balance(who, Expendable, Polite); + if amount > liquid { + return WithdrawConsequence::Frozen + } + + // Provider restriction - total account balance cannot be reduced to zero if it cannot + // sustain the loss of a provider reference. + // NOTE: This assumes that the pallet is a provider (which is true). Is this ever changes, + // then this will need to adapt accordingly. + let ed = T::ExistentialDeposit::get(); + let success = if new_free_balance < ed { + if frame_system::Pallet::::can_dec_provider(who) { + WithdrawConsequence::ReducedToZero(new_free_balance) + } else { + return WithdrawConsequence::WouldDie + } + } else { + WithdrawConsequence::Success + }; + + let new_total_balance = new_free_balance.saturating_add(account.reserved); + + // Eventual free funds must be no less than the frozen balance. + if new_total_balance < account.frozen { + return WithdrawConsequence::Frozen + } + + success + } +} + +impl, I: 'static> fungible::Unbalanced for Pallet { + fn handle_dust(dust: fungible::Dust) { + T::DustRemoval::on_unbalanced(dust.into_credit()); + } + fn write_balance( + who: &T::AccountId, + amount: Self::Balance, + ) -> Result, DispatchError> { + let max_reduction = + >::reducible_balance(who, Expendable, Force); + let (result, maybe_dust) = Self::mutate_account(who, |account| -> DispatchResult { + // Make sure the reduction (if there is one) is no more than the maximum allowed. + let reduction = account.free.saturating_sub(amount); + ensure!(reduction <= max_reduction, Error::::InsufficientBalance); + + account.free = amount; + Ok(()) + })?; + result?; + Ok(maybe_dust) + } + + fn set_total_issuance(amount: Self::Balance) { + TotalIssuance::::mutate(|t| *t = amount); + } + + fn deactivate(amount: Self::Balance) { + InactiveIssuance::::mutate(|b| b.saturating_accrue(amount)); + } + + fn reactivate(amount: Self::Balance) { + InactiveIssuance::::mutate(|b| b.saturating_reduce(amount)); + } +} + +impl, I: 'static> fungible::Mutate for Pallet { + fn done_mint_into(who: &T::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::::Minted { who: who.clone(), amount }); + } + fn done_burn_from(who: &T::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::::Burned { who: who.clone(), amount }); + } + fn done_shelve(who: &T::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::::Suspended { who: who.clone(), amount }); + } + fn done_restore(who: &T::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::::Restored { who: who.clone(), amount }); + } + fn done_transfer(source: &T::AccountId, dest: &T::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::::Transfer { + from: source.clone(), + to: dest.clone(), + amount, + }); + } +} + +impl, I: 'static> fungible::MutateHold for Pallet {} + +impl, I: 'static> fungible::InspectHold for Pallet { + type Reason = T::RuntimeHoldReason; + + fn total_balance_on_hold(who: &T::AccountId) -> T::Balance { + Self::account(who).reserved + } + fn reducible_total_balance_on_hold(who: &T::AccountId, force: Fortitude) -> Self::Balance { + // The total balance must never drop below the freeze requirements if we're not forcing: + let a = Self::account(who); + let unavailable = if force == Force { + Self::Balance::zero() + } else { + // The freeze lock applies to the total balance, so we can discount the free balance + // from the amount which the total reserved balance must provide to satisfy it. + a.frozen.saturating_sub(a.free) + }; + a.reserved.saturating_sub(unavailable) + } + fn balance_on_hold(reason: &Self::Reason, who: &T::AccountId) -> T::Balance { + Holds::::get(who) + .iter() + .find(|x| &x.id == reason) + .map_or_else(Zero::zero, |x| x.amount) + } + fn hold_available(reason: &Self::Reason, who: &T::AccountId) -> bool { + if frame_system::Pallet::::providers(who) == 0 { + return false + } + let holds = Holds::::get(who); + if holds.is_full() && !holds.iter().any(|x| &x.id == reason) { + return false + } + true + } +} + +impl, I: 'static> fungible::UnbalancedHold for Pallet { + fn set_balance_on_hold( + reason: &Self::Reason, + who: &T::AccountId, + amount: Self::Balance, + ) -> DispatchResult { + let mut new_account = Self::account(who); + let mut holds = Holds::::get(who); + let mut increase = true; + let mut delta = amount; + + if let Some(item) = holds.iter_mut().find(|x| &x.id == reason) { + delta = item.amount.max(amount) - item.amount.min(amount); + increase = amount > item.amount; + item.amount = amount; + holds.retain(|x| !x.amount.is_zero()); + } else { + if !amount.is_zero() { + holds + .try_push(IdAmount { id: *reason, amount }) + .map_err(|_| Error::::TooManyHolds)?; + } + } + + new_account.reserved = if increase { + new_account.reserved.checked_add(&delta).ok_or(ArithmeticError::Overflow)? + } else { + new_account.reserved.checked_sub(&delta).ok_or(ArithmeticError::Underflow)? + }; + + let (result, maybe_dust) = Self::try_mutate_account(who, |a, _| -> DispatchResult { + *a = new_account; + Ok(()) + })?; + debug_assert!( + maybe_dust.is_none(), + "Does not alter main balance; dust only happens when it is altered; qed" + ); + Holds::::insert(who, holds); + Ok(result) + } +} + +impl, I: 'static> fungible::InspectFreeze for Pallet { + type Id = T::FreezeIdentifier; + + fn balance_frozen(id: &Self::Id, who: &T::AccountId) -> Self::Balance { + let locks = Freezes::::get(who); + locks.into_iter().find(|l| &l.id == id).map_or(Zero::zero(), |l| l.amount) + } + + fn can_freeze(id: &Self::Id, who: &T::AccountId) -> bool { + let l = Freezes::::get(who); + !l.is_full() || l.iter().any(|x| &x.id == id) + } +} + +impl, I: 'static> fungible::MutateFreeze for Pallet { + fn set_freeze(id: &Self::Id, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { + if amount.is_zero() { + return Self::thaw(id, who) + } + let mut locks = Freezes::::get(who); + if let Some(i) = locks.iter_mut().find(|x| &x.id == id) { + i.amount = amount; + } else { + locks + .try_push(IdAmount { id: *id, amount }) + .map_err(|_| Error::::TooManyFreezes)?; + } + Self::update_freezes(who, locks.as_bounded_slice()) + } + + fn extend_freeze(id: &Self::Id, who: &T::AccountId, amount: Self::Balance) -> DispatchResult { + if amount.is_zero() { + return Ok(()) + } + let mut locks = Freezes::::get(who); + if let Some(i) = locks.iter_mut().find(|x| &x.id == id) { + i.amount = i.amount.max(amount); + } else { + locks + .try_push(IdAmount { id: *id, amount }) + .map_err(|_| Error::::TooManyFreezes)?; + } + Self::update_freezes(who, locks.as_bounded_slice()) + } + + fn thaw(id: &Self::Id, who: &T::AccountId) -> DispatchResult { + let mut locks = Freezes::::get(who); + locks.retain(|l| &l.id != id); + Self::update_freezes(who, locks.as_bounded_slice()) + } +} + +impl, I: 'static> fungible::Balanced for Pallet { + type OnDropCredit = fungible::DecreaseIssuance; + type OnDropDebt = fungible::IncreaseIssuance; + + fn done_deposit(who: &T::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::::Deposit { who: who.clone(), amount }); + } + fn done_withdraw(who: &T::AccountId, amount: Self::Balance) { + Self::deposit_event(Event::::Withdraw { who: who.clone(), amount }); + } + fn done_issue(amount: Self::Balance) { + Self::deposit_event(Event::::Issued { amount }); + } + fn done_rescind(amount: Self::Balance) { + Self::deposit_event(Event::::Rescinded { amount }); + } +} + +impl, I: 'static> fungible::BalancedHold for Pallet {} diff --git a/frame/balances/src/lib.rs b/frame/balances/src/lib.rs index 99d77a3e7336..e77b0f5e5d95 100644 --- a/frame/balances/src/lib.rs +++ b/frame/balances/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -89,8 +89,9 @@ //! //! ### Dispatchable Functions //! -//! - `transfer` - Transfer some liquid free balance to another account. -//! - `set_balance` - Set the balances of a given account. The origin of this call must be root. +//! - `transfer_allow_death` - Transfer some liquid free balance to another account. +//! - `force_set_balance` - Set the balances of a given account. The origin of this call must be +//! root. //! //! ## Usage //! @@ -119,7 +120,7 @@ //! use frame_support::traits::{WithdrawReasons, LockableCurrency}; //! use sp_runtime::traits::Bounded; //! pub trait Config: frame_system::Config { -//! type Currency: LockableCurrency; +//! type Currency: LockableCurrency>; //! } //! # struct StakingLedger { //! # stash: ::AccountId, @@ -150,45 +151,49 @@ //! ## Assumptions //! //! * Total issued balanced of all accounts should be less than `Config::Balance::max_value()`. +//! * Existential Deposit is set to a value greater than zero. +//! +//! Note, you may find the Balances pallet still functions with an ED of zero in some circumstances, +//! however this is not a configuration which is generally supported, nor will it be. #![cfg_attr(not(feature = "std"), no_std)] - -#[macro_use] -mod tests; mod benchmarking; +mod impl_currency; +mod impl_fungible; pub mod migration; -mod tests_composite; -mod tests_local; -#[cfg(test)] -mod tests_reentrancy; +mod tests; +mod types; pub mod weights; -pub use self::imbalances::{NegativeImbalance, PositiveImbalance}; -use codec::{Codec, Decode, Encode, MaxEncodedLen}; -#[cfg(feature = "std")] -use frame_support::traits::GenesisBuild; +use codec::{Codec, MaxEncodedLen}; use frame_support::{ ensure, pallet_prelude::DispatchResult, traits::{ - tokens::{fungible, BalanceStatus as Status, DepositConsequence, WithdrawConsequence}, - Currency, DefensiveSaturating, ExistenceRequirement, - ExistenceRequirement::{AllowDeath, KeepAlive}, - Get, Imbalance, LockIdentifier, LockableCurrency, NamedReservableCurrency, OnUnbalanced, - ReservableCurrency, SignedImbalance, StoredMap, TryDrop, WithdrawReasons, + tokens::{ + fungible, BalanceStatus as Status, DepositConsequence, + Fortitude::{self, Force, Polite}, + Preservation::{Expendable, Preserve, Protect}, + WithdrawConsequence, + }, + Currency, Defensive, Get, OnUnbalanced, ReservableCurrency, StoredMap, }, - WeakBoundedVec, + BoundedSlice, WeakBoundedVec, }; use frame_system as system; +pub use impl_currency::{NegativeImbalance, PositiveImbalance}; use scale_info::TypeInfo; use sp_runtime::{ traits::{ AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedSub, MaybeSerializeDeserialize, Saturating, StaticLookup, Zero, }, - ArithmeticError, DispatchError, FixedPointOperand, RuntimeDebug, + ArithmeticError, DispatchError, FixedPointOperand, Perbill, RuntimeDebug, TokenError, +}; +use sp_std::{cmp, fmt::Debug, mem, prelude::*, result}; +pub use types::{ + AccountData, BalanceLock, DustCleaner, ExtraFlags, IdAmount, Reasons, ReserveData, }; -use sp_std::{cmp, fmt::Debug, mem, ops::BitOr, prelude::*, result}; pub use weights::WeightInfo; pub use pallet::*; @@ -200,11 +205,23 @@ type AccountIdLookupOf = <::Lookup as StaticLookup #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::{ + pallet_prelude::*, + traits::{fungible::Credit, tokens::Precision}, + }; use frame_system::pallet_prelude::*; + pub type CreditOf = Credit<::AccountId, Pallet>; + #[pallet::config] pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + /// The balance of an account. type Balance: Parameter + Member @@ -219,21 +236,32 @@ pub mod pallet { + FixedPointOperand; /// Handler for the unbalanced reduction when removing a dust account. - type DustRemoval: OnUnbalanced>; - - /// The overarching event type. - type RuntimeEvent: From> - + IsType<::RuntimeEvent>; + type DustRemoval: OnUnbalanced>; - /// The minimum amount required to keep an account open. + /// The minimum amount required to keep an account open. MUST BE GREATER THAN ZERO! + /// + /// If you *really* need it to be zero, you can enable the feature `insecure_zero_ed` for + /// this pallet. However, you do so at your own risk: this will open up a major DoS vector. + /// In case you have multiple sources of provider references, you may also get unexpected + /// behaviour if you set this to zero. + /// + /// Bottom line: Do yourself a favour and make it at least one! #[pallet::constant] type ExistentialDeposit: Get; /// The means of storing the balances of an account. type AccountStore: StoredMap>; - /// Weight information for extrinsics in this pallet. - type WeightInfo: WeightInfo; + /// The ID type for reserves. + /// + /// Use of reserves is deprecated in favour of holds. See `https://github.com/paritytech/substrate/pull/12951/` + type ReserveIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy; + + /// The overarching hold reason. + type RuntimeHoldReason: Parameter + Member + MaxEncodedLen + Ord + Copy; + + /// The ID type for freezes. + type FreezeIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy; /// The maximum number of locks that should exist on an account. /// Not strictly enforced, but used for weight estimation. @@ -244,8 +272,13 @@ pub mod pallet { #[pallet::constant] type MaxReserves: Get; - /// The id type for named reserves. - type ReserveIdentifier: Parameter + Member + MaxEncodedLen + Ord + Copy; + /// The maximum number of holds that can exist on an account at any time. + #[pallet::constant] + type MaxHolds: Get; + + /// The maximum number of individual freeze locks that can exist on an account at any time. + #[pallet::constant] + type MaxFreezes: Get; } /// The current storage version. @@ -253,206 +286,9 @@ pub mod pallet { frame_support::traits::StorageVersion::new(1); #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData<(T, I)>); - #[pallet::call] - impl, I: 'static> Pallet { - /// Transfer some liquid free balance to another account. - /// - /// `transfer` will set the `FreeBalance` of the sender and receiver. - /// If the sender's account is below the existential deposit as a result - /// of the transfer, the account will be reaped. - /// - /// The dispatch origin for this call must be `Signed` by the transactor. - /// - /// # - /// - Dependent on arguments but not critical, given proper implementations for input config - /// types. See related functions below. - /// - It contains a limited number of reads and writes internally and no complex - /// computation. - /// - /// Related functions: - /// - /// - `ensure_can_withdraw` is always called internally but has a bounded complexity. - /// - Transferring balances to accounts that did not exist before will cause - /// `T::OnNewAccount::on_new_account` to be called. - /// - Removing enough funds from an account will trigger `T::DustRemoval::on_unbalanced`. - /// - `transfer_keep_alive` works the same way as `transfer`, but has an additional check - /// that the transfer will not kill the origin account. - /// --------------------------------- - /// - Origin account is already in memory, so no DB operations for them. - /// # - #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::transfer())] - pub fn transfer( - origin: OriginFor, - dest: AccountIdLookupOf, - #[pallet::compact] value: T::Balance, - ) -> DispatchResultWithPostInfo { - let transactor = ensure_signed(origin)?; - let dest = T::Lookup::lookup(dest)?; - >::transfer( - &transactor, - &dest, - value, - ExistenceRequirement::AllowDeath, - )?; - Ok(().into()) - } - - /// Set the balances of a given account. - /// - /// This will alter `FreeBalance` and `ReservedBalance` in storage. it will - /// also alter the total issuance of the system (`TotalIssuance`) appropriately. - /// If the new free or reserved balance is below the existential deposit, - /// it will reset the account nonce (`frame_system::AccountNonce`). - /// - /// The dispatch origin for this call is `root`. - #[pallet::call_index(1)] - #[pallet::weight( - T::WeightInfo::set_balance_creating() // Creates a new account. - .max(T::WeightInfo::set_balance_killing()) // Kills an existing account. - )] - pub fn set_balance( - origin: OriginFor, - who: AccountIdLookupOf, - #[pallet::compact] new_free: T::Balance, - #[pallet::compact] new_reserved: T::Balance, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - let who = T::Lookup::lookup(who)?; - let existential_deposit = T::ExistentialDeposit::get(); - - let wipeout = new_free + new_reserved < existential_deposit; - let new_free = if wipeout { Zero::zero() } else { new_free }; - let new_reserved = if wipeout { Zero::zero() } else { new_reserved }; - - // First we try to modify the account's balance to the forced balance. - let (old_free, old_reserved) = Self::mutate_account(&who, |account| { - let old_free = account.free; - let old_reserved = account.reserved; - - account.free = new_free; - account.reserved = new_reserved; - - (old_free, old_reserved) - })?; - - // This will adjust the total issuance, which was not done by the `mutate_account` - // above. - if new_free > old_free { - mem::drop(PositiveImbalance::::new(new_free - old_free)); - } else if new_free < old_free { - mem::drop(NegativeImbalance::::new(old_free - new_free)); - } - - if new_reserved > old_reserved { - mem::drop(PositiveImbalance::::new(new_reserved - old_reserved)); - } else if new_reserved < old_reserved { - mem::drop(NegativeImbalance::::new(old_reserved - new_reserved)); - } - - Self::deposit_event(Event::BalanceSet { who, free: new_free, reserved: new_reserved }); - Ok(().into()) - } - - /// Exactly as `transfer`, except the origin must be root and the source account may be - /// specified. - /// # - /// - Same as transfer, but additional read and write because the source account is not - /// assumed to be in the overlay. - /// # - #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::force_transfer())] - pub fn force_transfer( - origin: OriginFor, - source: AccountIdLookupOf, - dest: AccountIdLookupOf, - #[pallet::compact] value: T::Balance, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - let source = T::Lookup::lookup(source)?; - let dest = T::Lookup::lookup(dest)?; - >::transfer( - &source, - &dest, - value, - ExistenceRequirement::AllowDeath, - )?; - Ok(().into()) - } - - /// Same as the [`transfer`] call, but with a check that the transfer will not kill the - /// origin account. - /// - /// 99% of the time you want [`transfer`] instead. - /// - /// [`transfer`]: struct.Pallet.html#method.transfer - #[pallet::call_index(3)] - #[pallet::weight(T::WeightInfo::transfer_keep_alive())] - pub fn transfer_keep_alive( - origin: OriginFor, - dest: AccountIdLookupOf, - #[pallet::compact] value: T::Balance, - ) -> DispatchResultWithPostInfo { - let transactor = ensure_signed(origin)?; - let dest = T::Lookup::lookup(dest)?; - >::transfer(&transactor, &dest, value, KeepAlive)?; - Ok(().into()) - } - - /// Transfer the entire transferable balance from the caller account. - /// - /// NOTE: This function only attempts to transfer _transferable_ balances. This means that - /// any locked, reserved, or existential deposits (when `keep_alive` is `true`), will not be - /// transferred by this function. To ensure that this function results in a killed account, - /// you might need to prepare the account by removing any reference counters, storage - /// deposits, etc... - /// - /// The dispatch origin of this call must be Signed. - /// - /// - `dest`: The recipient of the transfer. - /// - `keep_alive`: A boolean to determine if the `transfer_all` operation should send all - /// of the funds the account has, causing the sender account to be killed (false), or - /// transfer everything except at least the existential deposit, which will guarantee to - /// keep the sender account alive (true). # - /// - O(1). Just like transfer, but reading the user's transferable balance first. - /// # - #[pallet::call_index(4)] - #[pallet::weight(T::WeightInfo::transfer_all())] - pub fn transfer_all( - origin: OriginFor, - dest: AccountIdLookupOf, - keep_alive: bool, - ) -> DispatchResult { - use fungible::Inspect; - let transactor = ensure_signed(origin)?; - let reducible_balance = Self::reducible_balance(&transactor, keep_alive); - let dest = T::Lookup::lookup(dest)?; - let keep_alive = if keep_alive { KeepAlive } else { AllowDeath }; - >::transfer(&transactor, &dest, reducible_balance, keep_alive)?; - Ok(()) - } - - /// Unreserve some balance from a user by force. - /// - /// Can only be called by ROOT. - #[pallet::call_index(5)] - #[pallet::weight(T::WeightInfo::force_unreserve())] - pub fn force_unreserve( - origin: OriginFor, - who: AccountIdLookupOf, - amount: T::Balance, - ) -> DispatchResult { - ensure_root(origin)?; - let who = T::Lookup::lookup(who)?; - let _leftover = >::unreserve(&who, amount); - Ok(()) - } - } - #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { @@ -464,7 +300,7 @@ pub mod pallet { /// Transfer succeeded. Transfer { from: T::AccountId, to: T::AccountId, amount: T::Balance }, /// A balance was set by root. - BalanceSet { who: T::AccountId, free: T::Balance, reserved: T::Balance }, + BalanceSet { who: T::AccountId, free: T::Balance }, /// Some balance was reserved (moved from free to reserved). Reserved { who: T::AccountId, amount: T::Balance }, /// Some balance was unreserved (moved from reserved to free). @@ -483,26 +319,52 @@ pub mod pallet { Withdraw { who: T::AccountId, amount: T::Balance }, /// Some amount was removed from the account (e.g. for misbehavior). Slashed { who: T::AccountId, amount: T::Balance }, + /// Some amount was minted into an account. + Minted { who: T::AccountId, amount: T::Balance }, + /// Some amount was burned from an account. + Burned { who: T::AccountId, amount: T::Balance }, + /// Some amount was suspended from an account (it can be restored later). + Suspended { who: T::AccountId, amount: T::Balance }, + /// Some amount was restored into an account. + Restored { who: T::AccountId, amount: T::Balance }, + /// An account was upgraded. + Upgraded { who: T::AccountId }, + /// Total issuance was increased by `amount`, creating a credit to be balanced. + Issued { amount: T::Balance }, + /// Total issuance was decreased by `amount`, creating a debt to be balanced. + Rescinded { amount: T::Balance }, + /// Some balance was locked. + Locked { who: T::AccountId, amount: T::Balance }, + /// Some balance was unlocked. + Unlocked { who: T::AccountId, amount: T::Balance }, + /// Some balance was frozen. + Frozen { who: T::AccountId, amount: T::Balance }, + /// Some balance was thawed. + Thawed { who: T::AccountId, amount: T::Balance }, } #[pallet::error] pub enum Error { - /// Vesting balance too high to send value + /// Vesting balance too high to send value. VestingBalance, - /// Account liquidity restrictions prevent withdrawal + /// Account liquidity restrictions prevent withdrawal. LiquidityRestrictions, /// Balance too low to send value. InsufficientBalance, - /// Value too low to create account due to existential deposit + /// Value too low to create account due to existential deposit. ExistentialDeposit, - /// Transfer/payment would kill account - KeepAlive, - /// A vesting schedule already exists for this account + /// Transfer/payment would kill account. + Expendability, + /// A vesting schedule already exists for this account. ExistingVestingSchedule, - /// Beneficiary account must pre-exist + /// Beneficiary account must pre-exist. DeadAccount, - /// Number of named reserves exceed MaxReserves + /// Number of named reserves exceed `MaxReserves`. TooManyReserves, + /// Number of holds exceed `MaxHolds`. + TooManyHolds, + /// Number of freezes exceed `MaxFreezes`. + TooManyFreezes, } /// The total units issued in the system. @@ -569,12 +431,31 @@ pub mod pallet { ValueQuery, >; + /// Holds on account balances. + #[pallet::storage] + pub type Holds, I: 'static = ()> = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + BoundedVec, T::MaxHolds>, + ValueQuery, + >; + + /// Freeze locks on account balances. + #[pallet::storage] + pub type Freezes, I: 'static = ()> = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + BoundedVec, T::MaxFreezes>, + ValueQuery, + >; + #[pallet::genesis_config] pub struct GenesisConfig, I: 'static = ()> { pub balances: Vec<(T::AccountId, T::Balance)>, } - #[cfg(feature = "std")] impl, I: 'static> Default for GenesisConfig { fn default() -> Self { Self { balances: Default::default() } @@ -582,9 +463,10 @@ pub mod pallet { } #[pallet::genesis_build] - impl, I: 'static> GenesisBuild for GenesisConfig { + impl, I: 'static> BuildGenesisConfig for GenesisConfig { fn build(&self) { let total = self.balances.iter().fold(Zero::zero(), |acc: T::Balance, &(_, n)| acc + n); + >::put(total); for (_, balance) in &self.balances { @@ -600,7 +482,7 @@ pub mod pallet { .iter() .map(|(x, _)| x) .cloned() - .collect::>(); + .collect::>(); assert!( endowed_accounts.len() == self.balances.len(), @@ -608,1602 +490,680 @@ pub mod pallet { ); for &(ref who, free) in self.balances.iter() { + frame_system::Pallet::::inc_providers(who); assert!(T::AccountStore::insert(who, AccountData { free, ..Default::default() }) .is_ok()); } } } -} - -#[cfg(feature = "std")] -impl, I: 'static> GenesisConfig { - /// Direct implementation of `GenesisBuild::build_storage`. - /// - /// Kept in order not to break dependency. - pub fn build_storage(&self) -> Result { - >::build_storage(self) - } - - /// Direct implementation of `GenesisBuild::assimilate_storage`. - /// - /// Kept in order not to break dependency. - pub fn assimilate_storage(&self, storage: &mut sp_runtime::Storage) -> Result<(), String> { - >::assimilate_storage(self, storage) - } -} - -/// Simplified reasons for withdrawing balance. -#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub enum Reasons { - /// Paying system transaction fees. - Fee = 0, - /// Any reason other than paying system transaction fees. - Misc = 1, - /// Any reason at all. - All = 2, -} - -impl From for Reasons { - fn from(r: WithdrawReasons) -> Reasons { - if r == WithdrawReasons::TRANSACTION_PAYMENT { - Reasons::Fee - } else if r.contains(WithdrawReasons::TRANSACTION_PAYMENT) { - Reasons::All - } else { - Reasons::Misc - } - } -} - -impl BitOr for Reasons { - type Output = Reasons; - fn bitor(self, other: Reasons) -> Reasons { - if self == other { - return self - } - Reasons::All - } -} - -/// A single lock on a balance. There can be many of these on an account and they "overlap", so the -/// same balance is frozen by multiple locks. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub struct BalanceLock { - /// An identifier for this lock. Only one lock may be in existence for each identifier. - pub id: LockIdentifier, - /// The amount which the free balance may not drop below when this lock is in effect. - pub amount: Balance, - /// If true, then the lock remains in effect even for payment of transaction fees. - pub reasons: Reasons, -} - -/// Store named reserved balance. -#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub struct ReserveData { - /// The identifier for the named reserve. - pub id: ReserveIdentifier, - /// The amount of the named reserve. - pub amount: Balance, -} - -/// All balance information for an account. -#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, MaxEncodedLen, TypeInfo)] -pub struct AccountData { - /// Non-reserved part of the balance. There may still be restrictions on this, but it is the - /// total pool what may in principle be transferred, reserved and used for tipping. - /// - /// This is the only balance that matters in terms of most operations on tokens. It - /// alone is used to determine the balance when in the contract execution environment. - pub free: Balance, - /// Balance which is reserved and may not be used at all. - /// - /// This can still get slashed, but gets slashed last of all. - /// - /// This balance is a 'reserve' balance that other subsystems use in order to set aside tokens - /// that are still 'owned' by the account holder, but which are suspendable. - /// This includes named reserve and unnamed reserve. - pub reserved: Balance, - /// The amount that `free` may not drop below when withdrawing for *anything except transaction - /// fee payment*. - pub misc_frozen: Balance, - /// The amount that `free` may not drop below when withdrawing specifically for transaction - /// fee payment. - pub fee_frozen: Balance, -} -impl AccountData { - /// How much this account's balance can be reduced for the given `reasons`. - fn usable(&self, reasons: Reasons) -> Balance { - self.free.saturating_sub(self.frozen(reasons)) - } - /// The amount that this account's free balance may not be reduced beyond for the given - /// `reasons`. - fn frozen(&self, reasons: Reasons) -> Balance { - match reasons { - Reasons::All => self.misc_frozen.max(self.fee_frozen), - Reasons::Misc => self.misc_frozen, - Reasons::Fee => self.fee_frozen, + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + #[cfg(not(feature = "insecure_zero_ed"))] + fn integrity_test() { + assert!( + !>::ExistentialDeposit::get().is_zero(), + "The existential deposit must be greater than zero!" + ); } } - /// The total balance in this account including any that is reserved and ignoring any frozen. - fn total(&self) -> Balance { - self.free.saturating_add(self.reserved) - } -} - -pub struct DustCleaner, I: 'static = ()>( - Option<(T::AccountId, NegativeImbalance)>, -); -impl, I: 'static> Drop for DustCleaner { - fn drop(&mut self) { - if let Some((who, dust)) = self.0.take() { - Pallet::::deposit_event(Event::DustLost { account: who, amount: dust.peek() }); - T::DustRemoval::on_unbalanced(dust); + #[pallet::call(weight(>::WeightInfo))] + impl, I: 'static> Pallet { + /// Transfer some liquid free balance to another account. + /// + /// `transfer_allow_death` will set the `FreeBalance` of the sender and receiver. + /// If the sender's account is below the existential deposit as a result + /// of the transfer, the account will be reaped. + /// + /// The dispatch origin for this call must be `Signed` by the transactor. + #[pallet::call_index(0)] + pub fn transfer_allow_death( + origin: OriginFor, + dest: AccountIdLookupOf, + #[pallet::compact] value: T::Balance, + ) -> DispatchResult { + let source = ensure_signed(origin)?; + let dest = T::Lookup::lookup(dest)?; + >::transfer(&source, &dest, value, Expendable)?; + Ok(()) } - } -} - -impl, I: 'static> Pallet { - /// Get the free balance of an account. - pub fn free_balance(who: impl sp_std::borrow::Borrow) -> T::Balance { - Self::account(who.borrow()).free - } - /// Get the balance of an account that can be used for transfers, reservations, or any other - /// non-locking, non-transaction-fee activity. Will be at most `free_balance`. - pub fn usable_balance(who: impl sp_std::borrow::Borrow) -> T::Balance { - Self::account(who.borrow()).usable(Reasons::Misc) - } - - /// Get the balance of an account that can be used for paying transaction fees (not tipping, - /// or any other kind of fees, though). Will be at most `free_balance`. - pub fn usable_balance_for_fees(who: impl sp_std::borrow::Borrow) -> T::Balance { - Self::account(who.borrow()).usable(Reasons::Fee) - } + /// Set the regular balance of a given account; it also takes a reserved balance but this + /// must be the same as the account's current reserved balance. + /// + /// The dispatch origin for this call is `root`. + /// + /// WARNING: This call is DEPRECATED! Use `force_set_balance` instead. + #[pallet::call_index(1)] + #[pallet::weight( + T::WeightInfo::force_set_balance_creating() // Creates a new account. + .max(T::WeightInfo::force_set_balance_killing()) // Kills an existing account. + )] + pub fn set_balance_deprecated( + origin: OriginFor, + who: AccountIdLookupOf, + #[pallet::compact] new_free: T::Balance, + #[pallet::compact] old_reserved: T::Balance, + ) -> DispatchResult { + ensure_root(origin)?; + let who = T::Lookup::lookup(who)?; + let existential_deposit = Self::ed(); - /// Get the reserved balance of an account. - pub fn reserved_balance(who: impl sp_std::borrow::Borrow) -> T::Balance { - Self::account(who.borrow()).reserved - } + let wipeout = new_free < existential_deposit; + let new_free = if wipeout { Zero::zero() } else { new_free }; - /// Get both the free and reserved balances of an account. - fn account(who: &T::AccountId) -> AccountData { - T::AccountStore::get(who) - } + // First we try to modify the account's balance to the forced balance. + let old_free = Self::try_mutate_account_handling_dust( + &who, + |account, _is_new| -> Result { + let old_free = account.free; + ensure!(account.reserved == old_reserved, TokenError::Unsupported); + account.free = new_free; + Ok(old_free) + }, + )?; - /// Handles any steps needed after mutating an account. - /// - /// This includes DustRemoval unbalancing, in the case than the `new` account's total balance - /// is non-zero but below ED. - /// - /// Returns two values: - /// - `Some` containing the the `new` account, iff the account has sufficient balance. - /// - `Some` containing the dust to be dropped, iff some dust should be dropped. - fn post_mutation( - _who: &T::AccountId, - new: AccountData, - ) -> (Option>, Option>) { - let total = new.total(); - if total < T::ExistentialDeposit::get() { - if total.is_zero() { - (None, None) - } else { - (None, Some(NegativeImbalance::new(total))) + // This will adjust the total issuance, which was not done by the `mutate_account` + // above. + if new_free > old_free { + mem::drop(PositiveImbalance::::new(new_free - old_free)); + } else if new_free < old_free { + mem::drop(NegativeImbalance::::new(old_free - new_free)); } - } else { - (Some(new), None) - } - } - fn deposit_consequence( - _who: &T::AccountId, - amount: T::Balance, - account: &AccountData, - mint: bool, - ) -> DepositConsequence { - if amount.is_zero() { - return DepositConsequence::Success - } - - if mint && TotalIssuance::::get().checked_add(&amount).is_none() { - return DepositConsequence::Overflow + Self::deposit_event(Event::BalanceSet { who, free: new_free }); + Ok(()) } - let new_total_balance = match account.total().checked_add(&amount) { - Some(x) => x, - None => return DepositConsequence::Overflow, - }; - - if new_total_balance < T::ExistentialDeposit::get() { - return DepositConsequence::BelowMinimum + /// Exactly as `transfer_allow_death`, except the origin must be root and the source account + /// may be specified. + #[pallet::call_index(2)] + pub fn force_transfer( + origin: OriginFor, + source: AccountIdLookupOf, + dest: AccountIdLookupOf, + #[pallet::compact] value: T::Balance, + ) -> DispatchResult { + ensure_root(origin)?; + let source = T::Lookup::lookup(source)?; + let dest = T::Lookup::lookup(dest)?; + >::transfer(&source, &dest, value, Expendable)?; + Ok(()) } - // NOTE: We assume that we are a provider, so don't need to do any checks in the - // case of account creation. - - DepositConsequence::Success - } - - fn withdraw_consequence( - who: &T::AccountId, - amount: T::Balance, - account: &AccountData, - ) -> WithdrawConsequence { - if amount.is_zero() { - return WithdrawConsequence::Success + /// Same as the [`transfer_allow_death`] call, but with a check that the transfer will not + /// kill the origin account. + /// + /// 99% of the time you want [`transfer_allow_death`] instead. + /// + /// [`transfer_allow_death`]: struct.Pallet.html#method.transfer + #[pallet::call_index(3)] + pub fn transfer_keep_alive( + origin: OriginFor, + dest: AccountIdLookupOf, + #[pallet::compact] value: T::Balance, + ) -> DispatchResult { + let source = ensure_signed(origin)?; + let dest = T::Lookup::lookup(dest)?; + >::transfer(&source, &dest, value, Preserve)?; + Ok(()) } - if TotalIssuance::::get().checked_sub(&amount).is_none() { - return WithdrawConsequence::Underflow + /// Transfer the entire transferable balance from the caller account. + /// + /// NOTE: This function only attempts to transfer _transferable_ balances. This means that + /// any locked, reserved, or existential deposits (when `keep_alive` is `true`), will not be + /// transferred by this function. To ensure that this function results in a killed account, + /// you might need to prepare the account by removing any reference counters, storage + /// deposits, etc... + /// + /// The dispatch origin of this call must be Signed. + /// + /// - `dest`: The recipient of the transfer. + /// - `keep_alive`: A boolean to determine if the `transfer_all` operation should send all + /// of the funds the account has, causing the sender account to be killed (false), or + /// transfer everything except at least the existential deposit, which will guarantee to + /// keep the sender account alive (true). + #[pallet::call_index(4)] + pub fn transfer_all( + origin: OriginFor, + dest: AccountIdLookupOf, + keep_alive: bool, + ) -> DispatchResult { + let transactor = ensure_signed(origin)?; + let keep_alive = if keep_alive { Preserve } else { Expendable }; + let reducible_balance = >::reducible_balance( + &transactor, + keep_alive, + Fortitude::Polite, + ); + let dest = T::Lookup::lookup(dest)?; + >::transfer( + &transactor, + &dest, + reducible_balance, + keep_alive, + )?; + Ok(()) } - let new_total_balance = match account.total().checked_sub(&amount) { - Some(x) => x, - None => return WithdrawConsequence::NoFunds, - }; - - // Provider restriction - total account balance cannot be reduced to zero if it cannot - // sustain the loss of a provider reference. - // NOTE: This assumes that the pallet is a provider (which is true). Is this ever changes, - // then this will need to adapt accordingly. - let ed = T::ExistentialDeposit::get(); - let success = if new_total_balance < ed { - if frame_system::Pallet::::can_dec_provider(who) { - WithdrawConsequence::ReducedToZero(new_total_balance) - } else { - return WithdrawConsequence::WouldDie - } - } else { - WithdrawConsequence::Success - }; - - // Enough free funds to have them be reduced. - let new_free_balance = match account.free.checked_sub(&amount) { - Some(b) => b, - None => return WithdrawConsequence::NoFunds, - }; - - // Eventual free funds must be no less than the frozen balance. - let min_balance = account.frozen(Reasons::All); - if new_free_balance < min_balance { - return WithdrawConsequence::Frozen + /// Unreserve some balance from a user by force. + /// + /// Can only be called by ROOT. + #[pallet::call_index(5)] + pub fn force_unreserve( + origin: OriginFor, + who: AccountIdLookupOf, + amount: T::Balance, + ) -> DispatchResult { + ensure_root(origin)?; + let who = T::Lookup::lookup(who)?; + let _leftover = >::unreserve(&who, amount); + Ok(()) } - success - } - - /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce - /// `ExistentialDeposit` law, annulling the account as needed. - /// - /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used - /// when it is known that the account already exists. - /// - /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that - /// the caller will do this. - pub fn mutate_account( - who: &T::AccountId, - f: impl FnOnce(&mut AccountData) -> R, - ) -> Result { - Self::try_mutate_account(who, |a, _| -> Result { Ok(f(a)) }) - } - - /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce - /// `ExistentialDeposit` law, annulling the account as needed. This will do nothing if the - /// result of `f` is an `Err`. - /// - /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used - /// when it is known that the account already exists. - /// - /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that - /// the caller will do this. - fn try_mutate_account>( - who: &T::AccountId, - f: impl FnOnce(&mut AccountData, bool) -> Result, - ) -> Result { - Self::try_mutate_account_with_dust(who, f).map(|(result, dust_cleaner)| { - drop(dust_cleaner); - result - }) - } - - /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce - /// `ExistentialDeposit` law, annulling the account as needed. This will do nothing if the - /// result of `f` is an `Err`. - /// - /// It returns both the result from the closure, and an optional `DustCleaner` instance which - /// should be dropped once it is known that all nested mutates that could affect storage items - /// what the dust handler touches have completed. - /// - /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used - /// when it is known that the account already exists. - /// - /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that - /// the caller will do this. - fn try_mutate_account_with_dust>( - who: &T::AccountId, - f: impl FnOnce(&mut AccountData, bool) -> Result, - ) -> Result<(R, DustCleaner), E> { - let result = T::AccountStore::try_mutate_exists(who, |maybe_account| { - let is_new = maybe_account.is_none(); - let mut account = maybe_account.take().unwrap_or_default(); - f(&mut account, is_new).map(move |result| { - let maybe_endowed = if is_new { Some(account.free) } else { None }; - let maybe_account_maybe_dust = Self::post_mutation(who, account); - *maybe_account = maybe_account_maybe_dust.0; - (maybe_endowed, maybe_account_maybe_dust.1, result) - }) - }); - result.map(|(maybe_endowed, maybe_dust, result)| { - if let Some(endowed) = maybe_endowed { - Self::deposit_event(Event::Endowed { account: who.clone(), free_balance: endowed }); + /// Upgrade a specified account. + /// + /// - `origin`: Must be `Signed`. + /// - `who`: The account to be upgraded. + /// + /// This will waive the transaction fee if at least all but 10% of the accounts needed to + /// be upgraded. (We let some not have to be upgraded just in order to allow for the + /// possibililty of churn). + #[pallet::call_index(6)] + #[pallet::weight(T::WeightInfo::upgrade_accounts(who.len() as u32))] + pub fn upgrade_accounts( + origin: OriginFor, + who: Vec, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + if who.is_empty() { + return Ok(Pays::Yes.into()) } - let dust_cleaner = DustCleaner(maybe_dust.map(|dust| (who.clone(), dust))); - (result, dust_cleaner) - }) - } - - /// Update the account entry for `who`, given the locks. - fn update_locks(who: &T::AccountId, locks: &[BalanceLock]) { - let bounded_locks = WeakBoundedVec::<_, T::MaxLocks>::force_from( - locks.to_vec(), - Some("Balances Update Locks"), - ); - - if locks.len() as u32 > T::MaxLocks::get() { - log::warn!( - target: LOG_TARGET, - "Warning: A user has more currency locks than expected. \ - A runtime configuration adjustment may be needed." - ); - } - // No way this can fail since we do not alter the existential balances. - let res = Self::mutate_account(who, |b| { - b.misc_frozen = Zero::zero(); - b.fee_frozen = Zero::zero(); - for l in locks.iter() { - if l.reasons == Reasons::All || l.reasons == Reasons::Misc { - b.misc_frozen = b.misc_frozen.max(l.amount); - } - if l.reasons == Reasons::All || l.reasons == Reasons::Fee { - b.fee_frozen = b.fee_frozen.max(l.amount); + let mut upgrade_count = 0; + for i in &who { + let upgraded = Self::ensure_upgraded(i); + if upgraded { + upgrade_count.saturating_inc(); } } - }); - debug_assert!(res.is_ok()); - - let existed = Locks::::contains_key(who); - if locks.is_empty() { - Locks::::remove(who); - if existed { - // TODO: use Locks::::hashed_key - // https://github.com/paritytech/substrate/issues/4969 - system::Pallet::::dec_consumers(who); - } - } else { - Locks::::insert(who, bounded_locks); - if !existed && system::Pallet::::inc_consumers_without_limit(who).is_err() { - // No providers for the locks. This is impossible under normal circumstances - // since the funds that are under the lock will themselves be stored in the - // account and therefore will need a reference. - log::warn!( - target: LOG_TARGET, - "Warning: Attempt to introduce lock consumer reference, yet no providers. \ - This is unexpected but should be safe." - ); - } - } - } - - /// Move the reserved balance of one account into the balance of another, according to `status`. - /// - /// Is a no-op if: - /// - the value to be moved is zero; or - /// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`. - /// - /// NOTE: returns actual amount of transferred value in `Ok` case. - fn do_transfer_reserved( - slashed: &T::AccountId, - beneficiary: &T::AccountId, - value: T::Balance, - best_effort: bool, - status: Status, - ) -> Result { - if value.is_zero() { - return Ok(Zero::zero()) - } - - if slashed == beneficiary { - return match status { - Status::Free => Ok(value.saturating_sub(Self::unreserve(slashed, value))), - Status::Reserved => Ok(value.saturating_sub(Self::reserved_balance(slashed))), + let proportion_upgraded = Perbill::from_rational(upgrade_count, who.len() as u32); + if proportion_upgraded >= Perbill::from_percent(90) { + Ok(Pays::No.into()) + } else { + Ok(Pays::Yes.into()) } } - let ((actual, _maybe_one_dust), _maybe_other_dust) = Self::try_mutate_account_with_dust( - beneficiary, - |to_account, is_new| -> Result<(T::Balance, DustCleaner), DispatchError> { - ensure!(!is_new, Error::::DeadAccount); - Self::try_mutate_account_with_dust( - slashed, - |from_account, _| -> Result { - let actual = cmp::min(from_account.reserved, value); - ensure!(best_effort || actual == value, Error::::InsufficientBalance); - match status { - Status::Free => - to_account.free = to_account - .free - .checked_add(&actual) - .ok_or(ArithmeticError::Overflow)?, - Status::Reserved => - to_account.reserved = to_account - .reserved - .checked_add(&actual) - .ok_or(ArithmeticError::Overflow)?, - } - from_account.reserved -= actual; - Ok(actual) - }, - ) - }, - )?; - - Self::deposit_event(Event::ReserveRepatriated { - from: slashed.clone(), - to: beneficiary.clone(), - amount: actual, - destination_status: status, - }); - Ok(actual) - } -} - -impl, I: 'static> fungible::Inspect for Pallet { - type Balance = T::Balance; - - fn total_issuance() -> Self::Balance { - TotalIssuance::::get() - } - fn active_issuance() -> Self::Balance { - TotalIssuance::::get().saturating_sub(InactiveIssuance::::get()) - } - fn minimum_balance() -> Self::Balance { - T::ExistentialDeposit::get() - } - fn balance(who: &T::AccountId) -> Self::Balance { - Self::account(who).total() - } - fn reducible_balance(who: &T::AccountId, keep_alive: bool) -> Self::Balance { - let a = Self::account(who); - // Liquid balance is what is neither reserved nor locked/frozen. - let liquid = a.free.saturating_sub(a.fee_frozen.max(a.misc_frozen)); - if frame_system::Pallet::::can_dec_provider(who) && !keep_alive { - liquid - } else { - // `must_remain_to_exist` is the part of liquid balance which must remain to keep total - // over ED. - let must_remain_to_exist = - T::ExistentialDeposit::get().saturating_sub(a.total() - liquid); - liquid.saturating_sub(must_remain_to_exist) - } - } - fn can_deposit(who: &T::AccountId, amount: Self::Balance, mint: bool) -> DepositConsequence { - Self::deposit_consequence(who, amount, &Self::account(who), mint) - } - fn can_withdraw( - who: &T::AccountId, - amount: Self::Balance, - ) -> WithdrawConsequence { - Self::withdraw_consequence(who, amount, &Self::account(who)) - } -} - -impl, I: 'static> fungible::Mutate for Pallet { - fn mint_into(who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - if amount.is_zero() { - return Ok(()) - } - Self::try_mutate_account(who, |account, _is_new| -> DispatchResult { - Self::deposit_consequence(who, amount, account, true).into_result()?; - account.free += amount; + /// Alias for `transfer_allow_death`, provided only for name-wise compatibility. + /// + /// WARNING: DEPRECATED! Will be released in approximately 3 months. + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::transfer_allow_death())] + pub fn transfer( + origin: OriginFor, + dest: AccountIdLookupOf, + #[pallet::compact] value: T::Balance, + ) -> DispatchResult { + let source = ensure_signed(origin)?; + let dest = T::Lookup::lookup(dest)?; + >::transfer(&source, &dest, value, Expendable)?; Ok(()) - })?; - TotalIssuance::::mutate(|t| *t += amount); - Self::deposit_event(Event::Deposit { who: who.clone(), amount }); - Ok(()) - } - - fn burn_from( - who: &T::AccountId, - amount: Self::Balance, - ) -> Result { - if amount.is_zero() { - return Ok(Self::Balance::zero()) } - let actual = Self::try_mutate_account( - who, - |account, _is_new| -> Result { - let extra = Self::withdraw_consequence(who, amount, account).into_result()?; - let actual = amount + extra; - account.free -= actual; - Ok(actual) - }, - )?; - TotalIssuance::::mutate(|t| *t -= actual); - Self::deposit_event(Event::Withdraw { who: who.clone(), amount }); - Ok(actual) - } -} -impl, I: 'static> fungible::Transfer for Pallet { - fn transfer( - source: &T::AccountId, - dest: &T::AccountId, - amount: T::Balance, - keep_alive: bool, - ) -> Result { - let er = if keep_alive { KeepAlive } else { AllowDeath }; - >::transfer(source, dest, amount, er).map(|_| amount) - } + /// Set the regular balance of a given account. + /// + /// The dispatch origin for this call is `root`. + #[pallet::call_index(8)] + #[pallet::weight( + T::WeightInfo::force_set_balance_creating() // Creates a new account. + .max(T::WeightInfo::force_set_balance_killing()) // Kills an existing account. + )] + pub fn force_set_balance( + origin: OriginFor, + who: AccountIdLookupOf, + #[pallet::compact] new_free: T::Balance, + ) -> DispatchResult { + ensure_root(origin)?; + let who = T::Lookup::lookup(who)?; + let existential_deposit = Self::ed(); - fn deactivate(amount: Self::Balance) { - InactiveIssuance::::mutate(|b| b.saturating_accrue(amount)); - } + let wipeout = new_free < existential_deposit; + let new_free = if wipeout { Zero::zero() } else { new_free }; - fn reactivate(amount: Self::Balance) { - InactiveIssuance::::mutate(|b| b.saturating_reduce(amount)); - } -} + // First we try to modify the account's balance to the forced balance. + let old_free = Self::mutate_account_handling_dust(&who, |account| { + let old_free = account.free; + account.free = new_free; + old_free + })?; -impl, I: 'static> fungible::Unbalanced for Pallet { - fn set_balance(who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - Self::mutate_account(who, |account| -> DispatchResult { - // fungibles::Unbalanced::decrease_balance didn't check account.reserved - // free = new_balance - reserved - account.free = - amount.checked_sub(&account.reserved).ok_or(ArithmeticError::Underflow)?; - Self::deposit_event(Event::BalanceSet { - who: who.clone(), - free: account.free, - reserved: account.reserved, - }); + // This will adjust the total issuance, which was not done by the `mutate_account` + // above. + if new_free > old_free { + mem::drop(PositiveImbalance::::new(new_free - old_free)); + } else if new_free < old_free { + mem::drop(NegativeImbalance::::new(old_free - new_free)); + } + Self::deposit_event(Event::BalanceSet { who, free: new_free }); Ok(()) - })? - } - - fn set_total_issuance(amount: Self::Balance) { - TotalIssuance::::mutate(|t| *t = amount); - } -} - -impl, I: 'static> fungible::InspectHold for Pallet { - fn balance_on_hold(who: &T::AccountId) -> T::Balance { - Self::account(who).reserved - } - fn can_hold(who: &T::AccountId, amount: T::Balance) -> bool { - let a = Self::account(who); - let min_balance = T::ExistentialDeposit::get().max(a.frozen(Reasons::All)); - if a.reserved.checked_add(&amount).is_none() { - return false - } - // We require it to be min_balance + amount to ensure that the full reserved funds may be - // slashed without compromising locked funds or destroying the account. - let required_free = match min_balance.checked_add(&amount) { - Some(x) => x, - None => return false, - }; - a.free >= required_free - } -} -impl, I: 'static> fungible::MutateHold for Pallet { - fn hold(who: &T::AccountId, amount: Self::Balance) -> DispatchResult { - if amount.is_zero() { - return Ok(()) - } - ensure!(Self::can_reserve(who, amount), Error::::InsufficientBalance); - Self::mutate_account(who, |a| { - a.free -= amount; - a.reserved += amount; - })?; - Ok(()) - } - fn release( - who: &T::AccountId, - amount: Self::Balance, - best_effort: bool, - ) -> Result { - if amount.is_zero() { - return Ok(amount) - } - // Done on a best-effort basis. - Self::try_mutate_account(who, |a, _| { - let new_free = a.free.saturating_add(amount.min(a.reserved)); - let actual = new_free - a.free; - ensure!(best_effort || actual == amount, Error::::InsufficientBalance); - // ^^^ Guaranteed to be <= amount and <= a.reserved - a.free = new_free; - a.reserved = a.reserved.saturating_sub(actual); - Ok(actual) - }) - } - fn transfer_held( - source: &T::AccountId, - dest: &T::AccountId, - amount: Self::Balance, - best_effort: bool, - on_hold: bool, - ) -> Result { - let status = if on_hold { Status::Reserved } else { Status::Free }; - Self::do_transfer_reserved(source, dest, amount, best_effort, status) - } -} - -// wrapping these imbalances in a private module is necessary to ensure absolute privacy -// of the inner member. -mod imbalances { - use super::{result, Config, Imbalance, RuntimeDebug, Saturating, TryDrop, Zero}; - use frame_support::traits::SameOrOther; - use sp_std::mem; - - /// Opaque, move-only struct with private fields that serves as a token denoting that - /// funds have been created without any equal and opposite accounting. - #[must_use] - #[derive(RuntimeDebug, PartialEq, Eq)] - pub struct PositiveImbalance, I: 'static = ()>(T::Balance); - - impl, I: 'static> PositiveImbalance { - /// Create a new positive imbalance from a balance. - pub fn new(amount: T::Balance) -> Self { - PositiveImbalance(amount) - } - } - - /// Opaque, move-only struct with private fields that serves as a token denoting that - /// funds have been destroyed without any equal and opposite accounting. - #[must_use] - #[derive(RuntimeDebug, PartialEq, Eq)] - pub struct NegativeImbalance, I: 'static = ()>(T::Balance); - - impl, I: 'static> NegativeImbalance { - /// Create a new negative imbalance from a balance. - pub fn new(amount: T::Balance) -> Self { - NegativeImbalance(amount) - } - } - - impl, I: 'static> TryDrop for PositiveImbalance { - fn try_drop(self) -> result::Result<(), Self> { - self.drop_zero() - } - } - - impl, I: 'static> Default for PositiveImbalance { - fn default() -> Self { - Self::zero() } } - impl, I: 'static> Imbalance for PositiveImbalance { - type Opposite = NegativeImbalance; - - fn zero() -> Self { - Self(Zero::zero()) + impl, I: 'static> Pallet { + fn ed() -> T::Balance { + T::ExistentialDeposit::get() } - fn drop_zero(self) -> result::Result<(), Self> { - if self.0.is_zero() { - Ok(()) - } else { - Err(self) + /// Ensure the account `who` is using the new logic. + /// + /// Returns `true` if the account did get upgraded, `false` if it didn't need upgrading. + pub fn ensure_upgraded(who: &T::AccountId) -> bool { + let mut a = T::AccountStore::get(who); + if a.flags.is_new_logic() { + return false } - } - fn split(self, amount: T::Balance) -> (Self, Self) { - let first = self.0.min(amount); - let second = self.0 - first; - - mem::forget(self); - (Self(first), Self(second)) - } - fn merge(mut self, other: Self) -> Self { - self.0 = self.0.saturating_add(other.0); - mem::forget(other); - - self - } - fn subsume(&mut self, other: Self) { - self.0 = self.0.saturating_add(other.0); - mem::forget(other); - } - fn offset(self, other: Self::Opposite) -> SameOrOther { - let (a, b) = (self.0, other.0); - mem::forget((self, other)); - - if a > b { - SameOrOther::Same(Self(a - b)) - } else if b > a { - SameOrOther::Other(NegativeImbalance::new(b - a)) - } else { - SameOrOther::None + a.flags.set_new_logic(); + if !a.reserved.is_zero() && a.frozen.is_zero() { + if system::Pallet::::providers(who) == 0 { + // Gah!! We have no provider refs :( + // This shouldn't practically happen, but we need a failsafe anyway: let's give + // them enough for an ED. + log::warn!( + target: LOG_TARGET, + "account with a non-zero reserve balance has no provider refs, account_id: '{:?}'.", + who + ); + a.free = a.free.max(Self::ed()); + system::Pallet::::inc_providers(who); + } + let _ = system::Pallet::::inc_consumers_without_limit(who).defensive(); } - } - fn peek(&self) -> T::Balance { - self.0 - } - } - - impl, I: 'static> TryDrop for NegativeImbalance { - fn try_drop(self) -> result::Result<(), Self> { - self.drop_zero() - } - } - - impl, I: 'static> Default for NegativeImbalance { - fn default() -> Self { - Self::zero() - } - } - - impl, I: 'static> Imbalance for NegativeImbalance { - type Opposite = PositiveImbalance; - - fn zero() -> Self { - Self(Zero::zero()) - } - fn drop_zero(self) -> result::Result<(), Self> { - if self.0.is_zero() { + // Should never fail - we're only setting a bit. + let _ = T::AccountStore::try_mutate_exists(who, |account| -> DispatchResult { + *account = Some(a); Ok(()) - } else { - Err(self) - } - } - fn split(self, amount: T::Balance) -> (Self, Self) { - let first = self.0.min(amount); - let second = self.0 - first; - - mem::forget(self); - (Self(first), Self(second)) - } - fn merge(mut self, other: Self) -> Self { - self.0 = self.0.saturating_add(other.0); - mem::forget(other); - - self - } - fn subsume(&mut self, other: Self) { - self.0 = self.0.saturating_add(other.0); - mem::forget(other); - } - fn offset(self, other: Self::Opposite) -> SameOrOther { - let (a, b) = (self.0, other.0); - mem::forget((self, other)); - - if a > b { - SameOrOther::Same(Self(a - b)) - } else if b > a { - SameOrOther::Other(PositiveImbalance::new(b - a)) - } else { - SameOrOther::None - } - } - fn peek(&self) -> T::Balance { - self.0 - } - } - - impl, I: 'static> Drop for PositiveImbalance { - /// Basic drop handler will just square up the total issuance. - fn drop(&mut self) { - >::mutate(|v| *v = v.saturating_add(self.0)); - } - } - - impl, I: 'static> Drop for NegativeImbalance { - /// Basic drop handler will just square up the total issuance. - fn drop(&mut self) { - >::mutate(|v| *v = v.saturating_sub(self.0)); - } - } -} - -impl, I: 'static> Currency for Pallet -where - T::Balance: MaybeSerializeDeserialize + Debug, -{ - type Balance = T::Balance; - type PositiveImbalance = PositiveImbalance; - type NegativeImbalance = NegativeImbalance; - - fn total_balance(who: &T::AccountId) -> Self::Balance { - Self::account(who).total() - } - - // Check if `value` amount of free balance can be slashed from `who`. - fn can_slash(who: &T::AccountId, value: Self::Balance) -> bool { - if value.is_zero() { + }); + Self::deposit_event(Event::Upgraded { who: who.clone() }); return true } - Self::free_balance(who) >= value - } - - fn total_issuance() -> Self::Balance { - TotalIssuance::::get() - } - fn active_issuance() -> Self::Balance { - >::active_issuance() - } - - fn deactivate(amount: Self::Balance) { - >::deactivate(amount); - } - - fn reactivate(amount: Self::Balance) { - >::reactivate(amount); - } - - fn minimum_balance() -> Self::Balance { - T::ExistentialDeposit::get() - } - - // Burn funds from the total issuance, returning a positive imbalance for the amount burned. - // Is a no-op if amount to be burned is zero. - fn burn(mut amount: Self::Balance) -> Self::PositiveImbalance { - if amount.is_zero() { - return PositiveImbalance::zero() + /// Get the free balance of an account. + pub fn free_balance(who: impl sp_std::borrow::Borrow) -> T::Balance { + Self::account(who.borrow()).free } - >::mutate(|issued| { - *issued = issued.checked_sub(&amount).unwrap_or_else(|| { - amount = *issued; - Zero::zero() - }); - }); - PositiveImbalance::new(amount) - } - // Create new funds into the total issuance, returning a negative imbalance - // for the amount issued. - // Is a no-op if amount to be issued it zero. - fn issue(mut amount: Self::Balance) -> Self::NegativeImbalance { - if amount.is_zero() { - return NegativeImbalance::zero() + /// Get the balance of an account that can be used for transfers, reservations, or any other + /// non-locking, non-transaction-fee activity. Will be at most `free_balance`. + pub fn usable_balance(who: impl sp_std::borrow::Borrow) -> T::Balance { + >::reducible_balance(who.borrow(), Expendable, Polite) } - >::mutate(|issued| { - *issued = issued.checked_add(&amount).unwrap_or_else(|| { - amount = Self::Balance::max_value() - *issued; - Self::Balance::max_value() - }) - }); - NegativeImbalance::new(amount) - } - - fn free_balance(who: &T::AccountId) -> Self::Balance { - Self::account(who).free - } - // Ensure that an account can withdraw from their free balance given any existing withdrawal - // restrictions like locks and vesting balance. - // Is a no-op if amount to be withdrawn is zero. - // - // # - // Despite iterating over a list of locks, they are limited by the number of - // lock IDs, which means the number of runtime pallets that intend to use and create locks. - // # - fn ensure_can_withdraw( - who: &T::AccountId, - amount: T::Balance, - reasons: WithdrawReasons, - new_balance: T::Balance, - ) -> DispatchResult { - if amount.is_zero() { - return Ok(()) + /// Get the balance of an account that can be used for paying transaction fees (not tipping, + /// or any other kind of fees, though). Will be at most `free_balance`. + /// + /// This requires that the account stays alive. + pub fn usable_balance_for_fees( + who: impl sp_std::borrow::Borrow, + ) -> T::Balance { + >::reducible_balance(who.borrow(), Protect, Polite) } - let min_balance = Self::account(who).frozen(reasons.into()); - ensure!(new_balance >= min_balance, Error::::LiquidityRestrictions); - Ok(()) - } - // Transfer some free balance from `transactor` to `dest`, respecting existence requirements. - // Is a no-op if value to be transferred is zero or the `transactor` is the same as `dest`. - fn transfer( - transactor: &T::AccountId, - dest: &T::AccountId, - value: Self::Balance, - existence_requirement: ExistenceRequirement, - ) -> DispatchResult { - if value.is_zero() || transactor == dest { - return Ok(()) + /// Get the reserved balance of an account. + pub fn reserved_balance(who: impl sp_std::borrow::Borrow) -> T::Balance { + Self::account(who.borrow()).reserved } - Self::try_mutate_account_with_dust( - dest, - |to_account, _| -> Result, DispatchError> { - Self::try_mutate_account_with_dust( - transactor, - |from_account, _| -> DispatchResult { - from_account.free = from_account - .free - .checked_sub(&value) - .ok_or(Error::::InsufficientBalance)?; - - // NOTE: total stake being stored in the same type means that this could - // never overflow but better to be safe than sorry. - to_account.free = - to_account.free.checked_add(&value).ok_or(ArithmeticError::Overflow)?; - - let ed = T::ExistentialDeposit::get(); - ensure!(to_account.total() >= ed, Error::::ExistentialDeposit); - - Self::ensure_can_withdraw( - transactor, - value, - WithdrawReasons::TRANSFER, - from_account.free, - ) - .map_err(|_| Error::::LiquidityRestrictions)?; - - // TODO: This is over-conservative. There may now be other providers, and - // this pallet may not even be a provider. - let allow_death = existence_requirement == ExistenceRequirement::AllowDeath; - let allow_death = - allow_death && system::Pallet::::can_dec_provider(transactor); - ensure!( - allow_death || from_account.total() >= ed, - Error::::KeepAlive - ); - - Ok(()) - }, - ) - .map(|(_, maybe_dust_cleaner)| maybe_dust_cleaner) - }, - )?; - - // Emit transfer event. - Self::deposit_event(Event::Transfer { - from: transactor.clone(), - to: dest.clone(), - amount: value, - }); - - Ok(()) - } - - /// Slash a target account `who`, returning the negative imbalance created and any left over - /// amount that could not be slashed. - /// - /// Is a no-op if `value` to be slashed is zero or the account does not exist. - /// - /// NOTE: `slash()` prefers free balance, but assumes that reserve balance can be drawn - /// from in extreme circumstances. `can_slash()` should be used prior to `slash()` to avoid - /// having to draw from reserved funds, however we err on the side of punishment if things are - /// inconsistent or `can_slash` wasn't used appropriately. - fn slash(who: &T::AccountId, value: Self::Balance) -> (Self::NegativeImbalance, Self::Balance) { - if value.is_zero() { - return (NegativeImbalance::zero(), Zero::zero()) - } - if Self::total_balance(who).is_zero() { - return (NegativeImbalance::zero(), value) + /// Get both the free and reserved balances of an account. + pub(crate) fn account(who: &T::AccountId) -> AccountData { + T::AccountStore::get(who) } - for attempt in 0..2 { - match Self::try_mutate_account( - who, - |account, - _is_new| - -> Result<(Self::NegativeImbalance, Self::Balance), DispatchError> { - // Best value is the most amount we can slash following liveness rules. - let best_value = match attempt { - // First attempt we try to slash the full amount, and see if liveness issues - // happen. - 0 => value, - // If acting as a critical provider (i.e. first attempt failed), then slash - // as much as possible while leaving at least at ED. - _ => value.min( - (account.free + account.reserved) - .saturating_sub(T::ExistentialDeposit::get()), - ), - }; - - let free_slash = cmp::min(account.free, best_value); - account.free -= free_slash; // Safe because of above check - let remaining_slash = best_value - free_slash; // Safe because of above check - - if !remaining_slash.is_zero() { - // If we have remaining slash, take it from reserved balance. - let reserved_slash = cmp::min(account.reserved, remaining_slash); - account.reserved -= reserved_slash; // Safe because of above check - Ok(( - NegativeImbalance::new(free_slash + reserved_slash), - value - free_slash - reserved_slash, /* Safe because value is gt or - * eq total slashed */ - )) - } else { - // Else we are done! - Ok(( - NegativeImbalance::new(free_slash), - value - free_slash, // Safe because value is gt or eq to total slashed - )) - } - }, - ) { - Ok((imbalance, not_slashed)) => { - Self::deposit_event(Event::Slashed { - who: who.clone(), - amount: value.saturating_sub(not_slashed), - }); - return (imbalance, not_slashed) - }, - Err(_) => (), + /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce + /// `ExistentialDeposit` law, annulling the account as needed. + /// + /// It returns the result from the closure. Any dust is handled through the low-level + /// `fungible::Unbalanced` trap-door for legacy dust management. + /// + /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used + /// when it is known that the account already exists. + /// + /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that + /// the caller will do this. + pub(crate) fn mutate_account_handling_dust( + who: &T::AccountId, + f: impl FnOnce(&mut AccountData) -> R, + ) -> Result { + let (r, maybe_dust) = Self::mutate_account(who, f)?; + if let Some(dust) = maybe_dust { + >::handle_raw_dust(dust); } + Ok(r) } - // Should never get here. But we'll be defensive anyway. - (Self::NegativeImbalance::zero(), value) - } - - /// Deposit some `value` into the free balance of an existing target account `who`. - /// - /// Is a no-op if the `value` to be deposited is zero. - fn deposit_into_existing( - who: &T::AccountId, - value: Self::Balance, - ) -> Result { - if value.is_zero() { - return Ok(PositiveImbalance::zero()) - } - - Self::try_mutate_account( - who, - |account, is_new| -> Result { - ensure!(!is_new, Error::::DeadAccount); - account.free = account.free.checked_add(&value).ok_or(ArithmeticError::Overflow)?; - Self::deposit_event(Event::Deposit { who: who.clone(), amount: value }); - Ok(PositiveImbalance::new(value)) - }, - ) - } - - /// Deposit some `value` into the free balance of `who`, possibly creating a new account. - /// - /// This function is a no-op if: - /// - the `value` to be deposited is zero; or - /// - the `value` to be deposited is less than the required ED and the account does not yet - /// exist; or - /// - the deposit would necessitate the account to exist and there are no provider references; - /// or - /// - `value` is so large it would cause the balance of `who` to overflow. - fn deposit_creating(who: &T::AccountId, value: Self::Balance) -> Self::PositiveImbalance { - if value.is_zero() { - return Self::PositiveImbalance::zero() - } - - Self::try_mutate_account( - who, - |account, is_new| -> Result { - let ed = T::ExistentialDeposit::get(); - ensure!(value >= ed || !is_new, Error::::ExistentialDeposit); - - // defensive only: overflow should never happen, however in case it does, then this - // operation is a no-op. - account.free = match account.free.checked_add(&value) { - Some(x) => x, - None => return Ok(Self::PositiveImbalance::zero()), - }; - - Self::deposit_event(Event::Deposit { who: who.clone(), amount: value }); - Ok(PositiveImbalance::new(value)) - }, - ) - .unwrap_or_else(|_| Self::PositiveImbalance::zero()) - } - - /// Withdraw some free balance from an account, respecting existence requirements. - /// - /// Is a no-op if value to be withdrawn is zero. - fn withdraw( - who: &T::AccountId, - value: Self::Balance, - reasons: WithdrawReasons, - liveness: ExistenceRequirement, - ) -> result::Result { - if value.is_zero() { - return Ok(NegativeImbalance::zero()) + /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce + /// `ExistentialDeposit` law, annulling the account as needed. + /// + /// It returns the result from the closure. Any dust is handled through the low-level + /// `fungible::Unbalanced` trap-door for legacy dust management. + /// + /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used + /// when it is known that the account already exists. + /// + /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that + /// the caller will do this. + pub(crate) fn try_mutate_account_handling_dust>( + who: &T::AccountId, + f: impl FnOnce(&mut AccountData, bool) -> Result, + ) -> Result { + let (r, maybe_dust) = Self::try_mutate_account(who, f)?; + if let Some(dust) = maybe_dust { + >::handle_raw_dust(dust); + } + Ok(r) } - Self::try_mutate_account( - who, - |account, _| -> Result { - let new_free_account = - account.free.checked_sub(&value).ok_or(Error::::InsufficientBalance)?; - - // bail if we need to keep the account alive and this would kill it. - let ed = T::ExistentialDeposit::get(); - let would_be_dead = new_free_account + account.reserved < ed; - let would_kill = would_be_dead && account.free + account.reserved >= ed; - ensure!(liveness == AllowDeath || !would_kill, Error::::KeepAlive); - - Self::ensure_can_withdraw(who, value, reasons, new_free_account)?; - - account.free = new_free_account; - - Self::deposit_event(Event::Withdraw { who: who.clone(), amount: value }); - Ok(NegativeImbalance::new(value)) - }, - ) - } - - /// Force the new free balance of a target account `who` to some new value `balance`. - fn make_free_balance_be( - who: &T::AccountId, - value: Self::Balance, - ) -> SignedImbalance { - Self::try_mutate_account( - who, - |account, - is_new| - -> Result, DispatchError> { - let ed = T::ExistentialDeposit::get(); - let total = value.saturating_add(account.reserved); - // If we're attempting to set an existing account to less than ED, then - // bypass the entire operation. It's a no-op if you follow it through, but - // since this is an instance where we might account for a negative imbalance - // (in the dust cleaner of set_account) before we account for its actual - // equal and opposite cause (returned as an Imbalance), then in the - // instance that there's no other accounts on the system at all, we might - // underflow the issuance and our arithmetic will be off. - ensure!(total >= ed || !is_new, Error::::ExistentialDeposit); - - let imbalance = if account.free <= value { - SignedImbalance::Positive(PositiveImbalance::new(value - account.free)) - } else { - SignedImbalance::Negative(NegativeImbalance::new(account.free - value)) - }; - account.free = value; - Self::deposit_event(Event::BalanceSet { - who: who.clone(), - free: account.free, - reserved: account.reserved, - }); - Ok(imbalance) - }, - ) - .unwrap_or_else(|_| SignedImbalance::Positive(Self::PositiveImbalance::zero())) - } -} - -impl, I: 'static> ReservableCurrency for Pallet -where - T::Balance: MaybeSerializeDeserialize + Debug, -{ - /// Check if `who` can reserve `value` from their free balance. - /// - /// Always `true` if value to be reserved is zero. - fn can_reserve(who: &T::AccountId, value: Self::Balance) -> bool { - if value.is_zero() { - return true + /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce + /// `ExistentialDeposit` law, annulling the account as needed. + /// + /// It returns both the result from the closure, and an optional amount of dust + /// which should be handled once it is known that all nested mutates that could affect + /// storage items what the dust handler touches have completed. + /// + /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used + /// when it is known that the account already exists. + /// + /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that + /// the caller will do this. + pub(crate) fn mutate_account( + who: &T::AccountId, + f: impl FnOnce(&mut AccountData) -> R, + ) -> Result<(R, Option), DispatchError> { + Self::try_mutate_account(who, |a, _| -> Result { Ok(f(a)) }) } - Self::account(who).free.checked_sub(&value).map_or(false, |new_balance| { - Self::ensure_can_withdraw(who, value, WithdrawReasons::RESERVE, new_balance).is_ok() - }) - } - - fn reserved_balance(who: &T::AccountId) -> Self::Balance { - Self::account(who).reserved - } - /// Move `value` from the free balance from `who` to their reserved balance. - /// - /// Is a no-op if value to be reserved is zero. - fn reserve(who: &T::AccountId, value: Self::Balance) -> DispatchResult { - if value.is_zero() { - return Ok(()) + /// Returns `true` when `who` has some providers or `insecure_zero_ed` feature is disnabled. + /// Returns `false` otherwise. + #[cfg(not(feature = "insecure_zero_ed"))] + fn have_providers_or_no_zero_ed(_: &T::AccountId) -> bool { + true } - Self::try_mutate_account(who, |account, _| -> DispatchResult { - account.free = - account.free.checked_sub(&value).ok_or(Error::::InsufficientBalance)?; - account.reserved = - account.reserved.checked_add(&value).ok_or(ArithmeticError::Overflow)?; - Self::ensure_can_withdraw(&who, value, WithdrawReasons::RESERVE, account.free) - })?; - - Self::deposit_event(Event::Reserved { who: who.clone(), amount: value }); - Ok(()) - } - - /// Unreserve some funds, returning any amount that was unable to be unreserved. - /// - /// Is a no-op if the value to be unreserved is zero or the account does not exist. - /// - /// NOTE: returns amount value which wasn't successfully unreserved. - fn unreserve(who: &T::AccountId, value: Self::Balance) -> Self::Balance { - if value.is_zero() { - return Zero::zero() - } - if Self::total_balance(who).is_zero() { - return value + /// Returns `true` when `who` has some providers or `insecure_zero_ed` feature is disnabled. + /// Returns `false` otherwise. + #[cfg(feature = "insecure_zero_ed")] + fn have_providers_or_no_zero_ed(who: &T::AccountId) -> bool { + frame_system::Pallet::::providers(who) > 0 } - let actual = match Self::mutate_account(who, |account| { - let actual = cmp::min(account.reserved, value); - account.reserved -= actual; - // defensive only: this can never fail since total issuance which is at least - // free+reserved fits into the same data type. - account.free = account.free.defensive_saturating_add(actual); - actual - }) { - Ok(x) => x, - Err(_) => { - // This should never happen since we don't alter the total amount in the account. - // If it ever does, then we should fail gracefully though, indicating that nothing - // could be done. - return value - }, - }; - - Self::deposit_event(Event::Unreserved { who: who.clone(), amount: actual }); - value - actual - } + /// Mutate an account to some new value, or delete it entirely with `None`. Will enforce + /// `ExistentialDeposit` law, annulling the account as needed. This will do nothing if the + /// result of `f` is an `Err`. + /// + /// It returns both the result from the closure, and an optional amount of dust + /// which should be handled once it is known that all nested mutates that could affect + /// storage items what the dust handler touches have completed. + /// + /// NOTE: Doesn't do any preparatory work for creating a new account, so should only be used + /// when it is known that the account already exists. + /// + /// NOTE: LOW-LEVEL: This will not attempt to maintain total issuance. It is expected that + /// the caller will do this. + pub(crate) fn try_mutate_account>( + who: &T::AccountId, + f: impl FnOnce(&mut AccountData, bool) -> Result, + ) -> Result<(R, Option), E> { + Self::ensure_upgraded(who); + let result = T::AccountStore::try_mutate_exists(who, |maybe_account| { + let is_new = maybe_account.is_none(); + let mut account = maybe_account.take().unwrap_or_default(); + let did_provide = + account.free >= Self::ed() && Self::have_providers_or_no_zero_ed(who); + let did_consume = + !is_new && (!account.reserved.is_zero() || !account.frozen.is_zero()); + + let result = f(&mut account, is_new)?; + + let does_provide = account.free >= Self::ed(); + let does_consume = !account.reserved.is_zero() || !account.frozen.is_zero(); + + if !did_provide && does_provide { + frame_system::Pallet::::inc_providers(who); + } + if did_consume && !does_consume { + frame_system::Pallet::::dec_consumers(who); + } + if !did_consume && does_consume { + frame_system::Pallet::::inc_consumers(who)?; + } + if did_provide && !does_provide { + // This could reap the account so must go last. + frame_system::Pallet::::dec_providers(who).map_err(|r| { + if did_consume && !does_consume { + // best-effort revert consumer change. + let _ = frame_system::Pallet::::inc_consumers(who).defensive(); + } + if !did_consume && does_consume { + let _ = frame_system::Pallet::::dec_consumers(who); + } + r + })?; + } - /// Slash from reserved balance, returning the negative imbalance created, - /// and any amount that was unable to be slashed. - /// - /// Is a no-op if the value to be slashed is zero or the account does not exist. - fn slash_reserved( - who: &T::AccountId, - value: Self::Balance, - ) -> (Self::NegativeImbalance, Self::Balance) { - if value.is_zero() { - return (NegativeImbalance::zero(), Zero::zero()) - } - if Self::total_balance(who).is_zero() { - return (NegativeImbalance::zero(), value) - } + let maybe_endowed = if is_new { Some(account.free) } else { None }; - // NOTE: `mutate_account` may fail if it attempts to reduce the balance to the point that an - // account is attempted to be illegally destroyed. - - for attempt in 0..2 { - match Self::mutate_account(who, |account| { - let best_value = match attempt { - 0 => value, - // If acting as a critical provider (i.e. first attempt failed), then ensure - // slash leaves at least the ED. - _ => value.min( - (account.free + account.reserved) - .saturating_sub(T::ExistentialDeposit::get()), - ), + // Handle any steps needed after mutating an account. + // + // This includes DustRemoval unbalancing, in the case than the `new` account's total + // balance is non-zero but below ED. + // + // Updates `maybe_account` to `Some` iff the account has sufficient balance. + // Evaluates `maybe_dust`, which is `Some` containing the dust to be dropped, iff + // some dust should be dropped. + // + // We should never be dropping if reserved is non-zero. Reserved being non-zero + // should imply that we have a consumer ref, so this is economically safe. + let ed = Self::ed(); + let maybe_dust = if account.free < ed && account.reserved.is_zero() { + if account.free.is_zero() { + None + } else { + Some(account.free) + } + } else { + assert!( + account.free.is_zero() || account.free >= ed || !account.reserved.is_zero() + ); + *maybe_account = Some(account); + None }; - - let actual = cmp::min(account.reserved, best_value); - account.reserved -= actual; - - // underflow should never happen, but it if does, there's nothing to be done here. - (NegativeImbalance::new(actual), value - actual) - }) { - Ok((imbalance, not_slashed)) => { - Self::deposit_event(Event::Slashed { - who: who.clone(), - amount: value.saturating_sub(not_slashed), + Ok((maybe_endowed, maybe_dust, result)) + }); + result.map(|(maybe_endowed, maybe_dust, result)| { + if let Some(endowed) = maybe_endowed { + Self::deposit_event(Event::Endowed { + account: who.clone(), + free_balance: endowed, }); - return (imbalance, not_slashed) - }, - Err(_) => (), - } - } - // Should never get here as we ensure that ED is left in the second attempt. - // In case we do, though, then we fail gracefully. - (Self::NegativeImbalance::zero(), value) - } - - /// Move the reserved balance of one account into the balance of another, according to `status`. - /// - /// Is a no-op if: - /// - the value to be moved is zero; or - /// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`. - fn repatriate_reserved( - slashed: &T::AccountId, - beneficiary: &T::AccountId, - value: Self::Balance, - status: Status, - ) -> Result { - let actual = Self::do_transfer_reserved(slashed, beneficiary, value, true, status)?; - Ok(value.saturating_sub(actual)) - } -} - -impl, I: 'static> NamedReservableCurrency for Pallet -where - T::Balance: MaybeSerializeDeserialize + Debug, -{ - type ReserveIdentifier = T::ReserveIdentifier; - - fn reserved_balance_named(id: &Self::ReserveIdentifier, who: &T::AccountId) -> Self::Balance { - let reserves = Self::reserves(who); - reserves - .binary_search_by_key(id, |data| data.id) - .map(|index| reserves[index].amount) - .unwrap_or_default() - } - - /// Move `value` from the free balance from `who` to a named reserve balance. - /// - /// Is a no-op if value to be reserved is zero. - fn reserve_named( - id: &Self::ReserveIdentifier, - who: &T::AccountId, - value: Self::Balance, - ) -> DispatchResult { - if value.is_zero() { - return Ok(()) - } - - Reserves::::try_mutate(who, |reserves| -> DispatchResult { - match reserves.binary_search_by_key(id, |data| data.id) { - Ok(index) => { - // this add can't overflow but just to be defensive. - reserves[index].amount = reserves[index].amount.defensive_saturating_add(value); - }, - Err(index) => { - reserves - .try_insert(index, ReserveData { id: *id, amount: value }) - .map_err(|_| Error::::TooManyReserves)?; - }, - }; - >::reserve(who, value)?; - Ok(()) - }) - } - - /// Unreserve some funds, returning any amount that was unable to be unreserved. - /// - /// Is a no-op if the value to be unreserved is zero. - fn unreserve_named( - id: &Self::ReserveIdentifier, - who: &T::AccountId, - value: Self::Balance, - ) -> Self::Balance { - if value.is_zero() { - return Zero::zero() + } + if let Some(amount) = maybe_dust { + Pallet::::deposit_event(Event::DustLost { account: who.clone(), amount }); + } + (result, maybe_dust) + }) } - Reserves::::mutate_exists(who, |maybe_reserves| -> Self::Balance { - if let Some(reserves) = maybe_reserves.as_mut() { - match reserves.binary_search_by_key(id, |data| data.id) { - Ok(index) => { - let to_change = cmp::min(reserves[index].amount, value); - - let remain = >::unreserve(who, to_change); - - // remain should always be zero but just to be defensive here. - let actual = to_change.defensive_saturating_sub(remain); - - // `actual <= to_change` and `to_change <= amount`; qed; - reserves[index].amount -= actual; + /// Update the account entry for `who`, given the locks. + pub(crate) fn update_locks(who: &T::AccountId, locks: &[BalanceLock]) { + let bounded_locks = WeakBoundedVec::<_, T::MaxLocks>::force_from( + locks.to_vec(), + Some("Balances Update Locks"), + ); - if reserves[index].amount.is_zero() { - if reserves.len() == 1 { - // no more named reserves - *maybe_reserves = None; - } else { - // remove this named reserve - reserves.remove(index); - } - } + if locks.len() as u32 > T::MaxLocks::get() { + log::warn!( + target: LOG_TARGET, + "Warning: A user has more currency locks than expected. \ + A runtime configuration adjustment may be needed." + ); + } + let freezes = Freezes::::get(who); + let mut prev_frozen = Zero::zero(); + let mut after_frozen = Zero::zero(); + // TODO: Revisit this assumption. We no manipulate consumer/provider refs. + // No way this can fail since we do not alter the existential balances. + let res = Self::mutate_account(who, |b| { + prev_frozen = b.frozen; + b.frozen = Zero::zero(); + for l in locks.iter() { + b.frozen = b.frozen.max(l.amount); + } + for l in freezes.iter() { + b.frozen = b.frozen.max(l.amount); + } + after_frozen = b.frozen; + }); + debug_assert!(res.is_ok()); + if let Ok((_, maybe_dust)) = res { + debug_assert!(maybe_dust.is_none(), "Not altering main balance; qed"); + } - value - actual - }, - Err(_) => value, + let existed = Locks::::contains_key(who); + if locks.is_empty() { + Locks::::remove(who); + if existed { + // TODO: use Locks::::hashed_key + // https://github.com/paritytech/substrate/issues/4969 + system::Pallet::::dec_consumers(who); } } else { - value + Locks::::insert(who, bounded_locks); + if !existed && system::Pallet::::inc_consumers_without_limit(who).is_err() { + // No providers for the locks. This is impossible under normal circumstances + // since the funds that are under the lock will themselves be stored in the + // account and therefore will need a reference. + log::warn!( + target: LOG_TARGET, + "Warning: Attempt to introduce lock consumer reference, yet no providers. \ + This is unexpected but should be safe." + ); + } } - }) - } - - /// Slash from reserved balance, returning the negative imbalance created, - /// and any amount that was unable to be slashed. - /// - /// Is a no-op if the value to be slashed is zero. - fn slash_reserved_named( - id: &Self::ReserveIdentifier, - who: &T::AccountId, - value: Self::Balance, - ) -> (Self::NegativeImbalance, Self::Balance) { - if value.is_zero() { - return (NegativeImbalance::zero(), Zero::zero()) - } - - Reserves::::mutate(who, |reserves| -> (Self::NegativeImbalance, Self::Balance) { - match reserves.binary_search_by_key(id, |data| data.id) { - Ok(index) => { - let to_change = cmp::min(reserves[index].amount, value); - let (imb, remain) = - >::slash_reserved(who, to_change); - - // remain should always be zero but just to be defensive here. - let actual = to_change.defensive_saturating_sub(remain); - - // `actual <= to_change` and `to_change <= amount`; qed; - reserves[index].amount -= actual; - - Self::deposit_event(Event::Slashed { who: who.clone(), amount: actual }); - (imb, value - actual) - }, - Err(_) => (NegativeImbalance::zero(), value), + if prev_frozen > after_frozen { + let amount = prev_frozen.saturating_sub(after_frozen); + Self::deposit_event(Event::Unlocked { who: who.clone(), amount }); + } else if after_frozen > prev_frozen { + let amount = after_frozen.saturating_sub(prev_frozen); + Self::deposit_event(Event::Locked { who: who.clone(), amount }); } - }) - } - - /// Move the reserved balance of one account into the balance of another, according to `status`. - /// If `status` is `Reserved`, the balance will be reserved with given `id`. - /// - /// Is a no-op if: - /// - the value to be moved is zero; or - /// - the `slashed` id equal to `beneficiary` and the `status` is `Reserved`. - fn repatriate_reserved_named( - id: &Self::ReserveIdentifier, - slashed: &T::AccountId, - beneficiary: &T::AccountId, - value: Self::Balance, - status: Status, - ) -> Result { - if value.is_zero() { - return Ok(Zero::zero()) } - if slashed == beneficiary { - return match status { - Status::Free => Ok(Self::unreserve_named(id, slashed, value)), - Status::Reserved => - Ok(value.saturating_sub(Self::reserved_balance_named(id, slashed))), + /// Update the account entry for `who`, given the locks. + pub(crate) fn update_freezes( + who: &T::AccountId, + freezes: BoundedSlice, T::MaxFreezes>, + ) -> DispatchResult { + let mut prev_frozen = Zero::zero(); + let mut after_frozen = Zero::zero(); + let (_, maybe_dust) = Self::mutate_account(who, |b| { + prev_frozen = b.frozen; + b.frozen = Zero::zero(); + for l in Locks::::get(who).iter() { + b.frozen = b.frozen.max(l.amount); + } + for l in freezes.iter() { + b.frozen = b.frozen.max(l.amount); + } + after_frozen = b.frozen; + })?; + debug_assert!(maybe_dust.is_none(), "Not altering main balance; qed"); + if freezes.is_empty() { + Freezes::::remove(who); + } else { + Freezes::::insert(who, freezes); + } + if prev_frozen > after_frozen { + let amount = prev_frozen.saturating_sub(after_frozen); + Self::deposit_event(Event::Thawed { who: who.clone(), amount }); + } else if after_frozen > prev_frozen { + let amount = after_frozen.saturating_sub(prev_frozen); + Self::deposit_event(Event::Frozen { who: who.clone(), amount }); } + Ok(()) } - Reserves::::try_mutate(slashed, |reserves| -> Result { - match reserves.binary_search_by_key(id, |data| data.id) { - Ok(index) => { - let to_change = cmp::min(reserves[index].amount, value); - - let actual = if status == Status::Reserved { - // make it the reserved under same identifier - Reserves::::try_mutate( - beneficiary, - |reserves| -> Result { - match reserves.binary_search_by_key(id, |data| data.id) { - Ok(index) => { - let remain = - >::repatriate_reserved( - slashed, - beneficiary, - to_change, - status, - )?; - - // remain should always be zero but just to be defensive - // here. - let actual = to_change.defensive_saturating_sub(remain); - - // this add can't overflow but just to be defensive. - reserves[index].amount = - reserves[index].amount.defensive_saturating_add(actual); - - Ok(actual) - }, - Err(index) => { - let remain = - >::repatriate_reserved( - slashed, - beneficiary, - to_change, - status, - )?; - - // remain should always be zero but just to be defensive - // here - let actual = to_change.defensive_saturating_sub(remain); - - reserves - .try_insert( - index, - ReserveData { id: *id, amount: actual }, - ) - .map_err(|_| Error::::TooManyReserves)?; - - Ok(actual) - }, - } - }, - )? - } else { - let remain = >::repatriate_reserved( - slashed, - beneficiary, - to_change, - status, - )?; - - // remain should always be zero but just to be defensive here - to_change.defensive_saturating_sub(remain) - }; + /// Move the reserved balance of one account into the balance of another, according to + /// `status`. This will respect freezes/locks only if `fortitude` is `Polite`. + /// + /// Is a no-op if the value to be moved is zero. + /// + /// NOTE: returns actual amount of transferred value in `Ok` case. + pub(crate) fn do_transfer_reserved( + slashed: &T::AccountId, + beneficiary: &T::AccountId, + value: T::Balance, + precision: Precision, + fortitude: Fortitude, + status: Status, + ) -> Result { + if value.is_zero() { + return Ok(Zero::zero()) + } - // `actual <= to_change` and `to_change <= amount`; qed; - reserves[index].amount -= actual; + let max = >::reducible_total_balance_on_hold( + slashed, fortitude, + ); + let actual = match precision { + Precision::BestEffort => value.min(max), + Precision::Exact => value, + }; + ensure!(actual <= max, TokenError::FundsUnavailable); + if slashed == beneficiary { + return match status { + Status::Free => Ok(actual.saturating_sub(Self::unreserve(slashed, actual))), + Status::Reserved => Ok(actual), + } + } - Ok(value - actual) + let ((_, maybe_dust_1), maybe_dust_2) = Self::try_mutate_account( + beneficiary, + |to_account, is_new| -> Result<((), Option), DispatchError> { + ensure!(!is_new, Error::::DeadAccount); + Self::try_mutate_account(slashed, |from_account, _| -> DispatchResult { + match status { + Status::Free => + to_account.free = to_account + .free + .checked_add(&actual) + .ok_or(ArithmeticError::Overflow)?, + Status::Reserved => + to_account.reserved = to_account + .reserved + .checked_add(&actual) + .ok_or(ArithmeticError::Overflow)?, + } + from_account.reserved.saturating_reduce(actual); + Ok(()) + }) }, - Err(_) => Ok(value), - } - }) - } -} + )?; -impl, I: 'static> LockableCurrency for Pallet -where - T::Balance: MaybeSerializeDeserialize + Debug, -{ - type Moment = T::BlockNumber; - - type MaxLocks = T::MaxLocks; - - // Set a lock on the balance of `who`. - // Is a no-op if lock amount is zero or `reasons` `is_none()`. - fn set_lock( - id: LockIdentifier, - who: &T::AccountId, - amount: T::Balance, - reasons: WithdrawReasons, - ) { - if amount.is_zero() || reasons.is_empty() { - return - } - let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() }); - let mut locks = Self::locks(who) - .into_iter() - .filter_map(|l| if l.id == id { new_lock.take() } else { Some(l) }) - .collect::>(); - if let Some(lock) = new_lock { - locks.push(lock) - } - Self::update_locks(who, &locks[..]); - } + if let Some(dust) = maybe_dust_1 { + >::handle_raw_dust(dust); + } + if let Some(dust) = maybe_dust_2 { + >::handle_raw_dust(dust); + } - // Extend a lock on the balance of `who`. - // Is a no-op if lock amount is zero or `reasons` `is_none()`. - fn extend_lock( - id: LockIdentifier, - who: &T::AccountId, - amount: T::Balance, - reasons: WithdrawReasons, - ) { - if amount.is_zero() || reasons.is_empty() { - return - } - let mut new_lock = Some(BalanceLock { id, amount, reasons: reasons.into() }); - let mut locks = Self::locks(who) - .into_iter() - .filter_map(|l| { - if l.id == id { - new_lock.take().map(|nl| BalanceLock { - id: l.id, - amount: l.amount.max(nl.amount), - reasons: l.reasons | nl.reasons, - }) - } else { - Some(l) - } - }) - .collect::>(); - if let Some(lock) = new_lock { - locks.push(lock) + Self::deposit_event(Event::ReserveRepatriated { + from: slashed.clone(), + to: beneficiary.clone(), + amount: actual, + destination_status: status, + }); + Ok(actual) } - Self::update_locks(who, &locks[..]); - } - - fn remove_lock(id: LockIdentifier, who: &T::AccountId) { - let mut locks = Self::locks(who); - locks.retain(|l| l.id != id); - Self::update_locks(who, &locks[..]); } } diff --git a/frame/balances/src/migration.rs b/frame/balances/src/migration.rs index 80d1bcd35ba9..6a272a611c3f 100644 --- a/frame/balances/src/migration.rs +++ b/frame/balances/src/migration.rs @@ -1,4 +1,4 @@ -// Copyright 2017-2020 Parity Technologies (UK) Ltd. +// Copyright Parity Technologies (UK) Ltd. // This file is part of Polkadot. // Polkadot is free software: you can redistribute it and/or modify diff --git a/frame/balances/src/tests.rs b/frame/balances/src/tests.rs deleted file mode 100644 index 83944caf9f7f..000000000000 --- a/frame/balances/src/tests.rs +++ /dev/null @@ -1,1460 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! Macro for creating the tests for the module. - -#![cfg(test)] - -#[macro_export] -macro_rules! decl_tests { - ($test:ty, $ext_builder:ty, $existential_deposit:expr) => { - - use crate::*; - use sp_runtime::{ArithmeticError, TokenError, FixedPointNumber, traits::{SignedExtension, BadOrigin}}; - use frame_support::{ - assert_noop, assert_storage_noop, assert_ok, assert_err, - traits::{ - LockableCurrency, LockIdentifier, WithdrawReasons, - Currency, ReservableCurrency, ExistenceRequirement::AllowDeath - } - }; - use pallet_transaction_payment::{ChargeTransactionPayment, Multiplier}; - use frame_system::RawOrigin; - - const ID_1: LockIdentifier = *b"1 "; - const ID_2: LockIdentifier = *b"2 "; - - pub const CALL: &<$test as frame_system::Config>::RuntimeCall = - &RuntimeCall::Balances(pallet_balances::Call::transfer { dest: 0, value: 0 }); - - /// create a transaction info struct from weight. Handy to avoid building the whole struct. - pub fn info_from_weight(w: Weight) -> DispatchInfo { - DispatchInfo { weight: w, ..Default::default() } - } - - fn events() -> Vec { - let evt = System::events().into_iter().map(|evt| evt.event).collect::>(); - - System::reset_events(); - - evt - } - - #[test] - fn basic_locking_should_work() { - <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { - assert_eq!(Balances::free_balance(1), 10); - Balances::set_lock(ID_1, &1, 9, WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 5, AllowDeath), - Error::<$test, _>::LiquidityRestrictions - ); - }); - } - - #[test] - fn account_should_be_reaped() { - <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { - assert_eq!(Balances::free_balance(1), 10); - assert_ok!(>::transfer(&1, &2, 10, AllowDeath)); - // Check that the account is dead. - assert!(!frame_system::Account::::contains_key(&1)); - }); - } - - #[test] - fn reap_failed_due_to_provider_and_consumer() { - <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { - // SCENARIO: only one provider and there are remaining consumers. - assert_ok!(System::inc_consumers(&1)); - assert!(!System::can_dec_provider(&1)); - assert_noop!( - >::transfer(&1, &2, 10, AllowDeath), - Error::<$test, _>::KeepAlive - ); - assert!(System::account_exists(&1)); - assert_eq!(Balances::free_balance(1), 10); - - // SCENARIO: more than one provider, but will not kill account due to other provider. - assert_eq!(System::inc_providers(&1), frame_system::IncRefStatus::Existed); - assert_eq!(System::providers(&1), 2); - assert!(System::can_dec_provider(&1)); - assert_ok!(>::transfer(&1, &2, 10, AllowDeath)); - assert_eq!(System::providers(&1), 1); - assert!(System::account_exists(&1)); - assert_eq!(Balances::free_balance(1), 0); - }); - } - - #[test] - fn partial_locking_should_work() { - <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); - } - - #[test] - fn lock_removal_should_work() { - <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, u64::MAX, WithdrawReasons::all()); - Balances::remove_lock(ID_1, &1); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); - } - - #[test] - fn lock_replacement_should_work() { - <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, u64::MAX, WithdrawReasons::all()); - Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); - } - - #[test] - fn double_locking_should_work() { - <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); - Balances::set_lock(ID_2, &1, 5, WithdrawReasons::all()); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); - } - - #[test] - fn combination_locking_should_work() { - <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, u64::MAX, WithdrawReasons::empty()); - Balances::set_lock(ID_2, &1, 0, WithdrawReasons::all()); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - }); - } - - #[test] - fn lock_value_extension_should_work() { - <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - Error::<$test, _>::LiquidityRestrictions - ); - Balances::extend_lock(ID_1, &1, 2, WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - Error::<$test, _>::LiquidityRestrictions - ); - Balances::extend_lock(ID_1, &1, 8, WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 3, AllowDeath), - Error::<$test, _>::LiquidityRestrictions - ); - }); - } - - #[test] - fn lock_reasons_should_work() { - <$ext_builder>::default() - .existential_deposit(1) - .monied(true) - .build() - .execute_with(|| { - pallet_transaction_payment::NextFeeMultiplier::<$test>::put( - Multiplier::saturating_from_integer(1) - ); - Balances::set_lock(ID_1, &1, 10, WithdrawReasons::RESERVE); - assert_noop!( - >::transfer(&1, &2, 1, AllowDeath), - Error::<$test, _>::LiquidityRestrictions - ); - assert_noop!( - >::reserve(&1, 1), - Error::<$test, _>::LiquidityRestrictions, - ); - assert!( as SignedExtension>::pre_dispatch( - ChargeTransactionPayment::from(1), - &1, - CALL, - &info_from_weight(Weight::from_ref_time(1)), - 1, - ).is_err()); - assert_ok!( as SignedExtension>::pre_dispatch( - ChargeTransactionPayment::from(0), - &1, - CALL, - &info_from_weight(Weight::from_ref_time(1)), - 1, - )); - - Balances::set_lock(ID_1, &1, 10, WithdrawReasons::TRANSACTION_PAYMENT); - assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); - assert_ok!(>::reserve(&1, 1)); - assert!( as SignedExtension>::pre_dispatch( - ChargeTransactionPayment::from(1), - &1, - CALL, - &info_from_weight(Weight::from_ref_time(1)), - 1, - ).is_err()); - assert!( as SignedExtension>::pre_dispatch( - ChargeTransactionPayment::from(0), - &1, - CALL, - &info_from_weight(Weight::from_ref_time(1)), - 1, - ).is_err()); - }); - } - - #[test] - fn lock_block_number_extension_should_work() { - <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, 10, WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - Error::<$test, _>::LiquidityRestrictions - ); - Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - Error::<$test, _>::LiquidityRestrictions - ); - System::set_block_number(2); - Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::all()); - assert_noop!( - >::transfer(&1, &2, 3, AllowDeath), - Error::<$test, _>::LiquidityRestrictions - ); - }); - } - - #[test] - fn lock_reasons_extension_should_work() { - <$ext_builder>::default().existential_deposit(1).monied(true).build().execute_with(|| { - Balances::set_lock(ID_1, &1, 10, WithdrawReasons::TRANSFER); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - Error::<$test, _>::LiquidityRestrictions - ); - Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::empty()); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - Error::<$test, _>::LiquidityRestrictions - ); - Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::RESERVE); - assert_noop!( - >::transfer(&1, &2, 6, AllowDeath), - Error::<$test, _>::LiquidityRestrictions - ); - }); - } - - #[test] - fn default_indexing_on_new_accounts_should_not_work2() { - <$ext_builder>::default() - .existential_deposit(10) - .monied(true) - .build() - .execute_with(|| { - // account 5 should not exist - // ext_deposit is 10, value is 9, not satisfies for ext_deposit - assert_noop!( - Balances::transfer(Some(1).into(), 5, 9), - Error::<$test, _>::ExistentialDeposit, - ); - assert_eq!(Balances::free_balance(1), 100); - }); - } - - #[test] - fn reserved_balance_should_prevent_reclaim_count() { - <$ext_builder>::default() - .existential_deposit(256 * 1) - .monied(true) - .build() - .execute_with(|| { - System::inc_account_nonce(&2); - assert_eq!(Balances::total_balance(&2), 256 * 20); - - assert_ok!(Balances::reserve(&2, 256 * 19 + 1)); // account 2 becomes mostly reserved - assert_eq!(Balances::free_balance(2), 255); // "free" account deleted." - assert_eq!(Balances::total_balance(&2), 256 * 20); // reserve still exists. - assert_eq!(System::account_nonce(&2), 1); - - // account 4 tries to take index 1 for account 5. - assert_ok!(Balances::transfer(Some(4).into(), 5, 256 * 1 + 0x69)); - assert_eq!(Balances::total_balance(&5), 256 * 1 + 0x69); - - assert!(Balances::slash(&2, 256 * 19 + 2).1.is_zero()); // account 2 gets slashed - // "reserve" account reduced to 255 (below ED) so account deleted - assert_eq!(Balances::total_balance(&2), 0); - assert_eq!(System::account_nonce(&2), 0); // nonce zero - - // account 4 tries to take index 1 again for account 6. - assert_ok!(Balances::transfer(Some(4).into(), 6, 256 * 1 + 0x69)); - assert_eq!(Balances::total_balance(&6), 256 * 1 + 0x69); - }); - } - - #[test] - fn reward_should_work() { - <$ext_builder>::default().monied(true).build().execute_with(|| { - assert_eq!(Balances::total_balance(&1), 10); - assert_ok!(Balances::deposit_into_existing(&1, 10).map(drop)); - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Deposit { who: 1, amount: 10 })); - assert_eq!(Balances::total_balance(&1), 20); - assert_eq!(>::get(), 120); - }); - } - - #[test] - fn dust_account_removal_should_work() { - <$ext_builder>::default() - .existential_deposit(100) - .monied(true) - .build() - .execute_with(|| { - System::inc_account_nonce(&2); - assert_eq!(System::account_nonce(&2), 1); - assert_eq!(Balances::total_balance(&2), 2000); - // index 1 (account 2) becomes zombie - assert_ok!(Balances::transfer(Some(2).into(), 5, 1901)); - assert_eq!(Balances::total_balance(&2), 0); - assert_eq!(Balances::total_balance(&5), 1901); - assert_eq!(System::account_nonce(&2), 0); - }); - } - - #[test] - fn balance_works() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 42); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { who: 1, amount: 42 })); - assert_eq!(Balances::free_balance(1), 42); - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(Balances::total_balance(&1), 42); - assert_eq!(Balances::free_balance(2), 0); - assert_eq!(Balances::reserved_balance(2), 0); - assert_eq!(Balances::total_balance(&2), 0); - }); - } - - #[test] - fn balance_transfer_works() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - assert_ok!(Balances::transfer(Some(1).into(), 2, 69)); - assert_eq!(Balances::total_balance(&1), 42); - assert_eq!(Balances::total_balance(&2), 69); - }); - } - - #[test] - fn force_transfer_works() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - assert_noop!( - Balances::force_transfer(Some(2).into(), 1, 2, 69), - BadOrigin, - ); - assert_ok!(Balances::force_transfer(RawOrigin::Root.into(), 1, 2, 69)); - assert_eq!(Balances::total_balance(&1), 42); - assert_eq!(Balances::total_balance(&2), 69); - }); - } - - #[test] - fn reserving_balance_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - - assert_eq!(Balances::total_balance(&1), 111); - assert_eq!(Balances::free_balance(1), 111); - assert_eq!(Balances::reserved_balance(1), 0); - - assert_ok!(Balances::reserve(&1, 69)); - - assert_eq!(Balances::total_balance(&1), 111); - assert_eq!(Balances::free_balance(1), 42); - assert_eq!(Balances::reserved_balance(1), 69); - }); - } - - #[test] - fn balance_transfer_when_reserved_should_not_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - assert_ok!(Balances::reserve(&1, 69)); - assert_noop!( - Balances::transfer(Some(1).into(), 2, 69), - Error::<$test, _>::InsufficientBalance, - ); - }); - } - - #[test] - fn deducting_balance_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - assert_ok!(Balances::reserve(&1, 69)); - assert_eq!(Balances::free_balance(1), 42); - }); - } - - #[test] - fn refunding_balance_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 42); - assert_ok!(Balances::mutate_account(&1, |a| a.reserved = 69)); - Balances::unreserve(&1, 69); - assert_eq!(Balances::free_balance(1), 111); - assert_eq!(Balances::reserved_balance(1), 0); - }); - } - - #[test] - fn slashing_balance_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - assert_ok!(Balances::reserve(&1, 69)); - assert!(Balances::slash(&1, 69).1.is_zero()); - assert_eq!(Balances::free_balance(1), 0); - assert_eq!(Balances::reserved_balance(1), 42); - assert_eq!(>::get(), 42); - }); - } - - #[test] - fn withdrawing_balance_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&2, 111); - let _ = Balances::withdraw( - &2, 11, WithdrawReasons::TRANSFER, ExistenceRequirement::KeepAlive - ); - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Withdraw { who: 2, amount: 11 })); - assert_eq!(Balances::free_balance(2), 100); - assert_eq!(>::get(), 100); - }); - } - - #[test] - fn slashing_incomplete_balance_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 42); - assert_ok!(Balances::reserve(&1, 21)); - assert_eq!(Balances::slash(&1, 69).1, 27); - assert_eq!(Balances::free_balance(1), 0); - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(>::get(), 0); - }); - } - - #[test] - fn unreserving_balance_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - assert_ok!(Balances::reserve(&1, 111)); - Balances::unreserve(&1, 42); - assert_eq!(Balances::reserved_balance(1), 69); - assert_eq!(Balances::free_balance(1), 42); - }); - } - - #[test] - fn slashing_reserved_balance_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - assert_ok!(Balances::reserve(&1, 111)); - assert_eq!(Balances::slash_reserved(&1, 42).1, 0); - assert_eq!(Balances::reserved_balance(1), 69); - assert_eq!(Balances::free_balance(1), 0); - assert_eq!(>::get(), 69); - }); - } - - #[test] - fn slashing_incomplete_reserved_balance_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - assert_ok!(Balances::reserve(&1, 42)); - assert_eq!(Balances::slash_reserved(&1, 69).1, 27); - assert_eq!(Balances::free_balance(1), 69); - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(>::get(), 69); - }); - } - - #[test] - fn repatriating_reserved_balance_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 110); - let _ = Balances::deposit_creating(&2, 1); - assert_ok!(Balances::reserve(&1, 110)); - assert_ok!(Balances::repatriate_reserved(&1, &2, 41, Status::Free), 0); - System::assert_last_event( - RuntimeEvent::Balances(crate::Event::ReserveRepatriated { from: 1, to: 2, amount: 41, destination_status: Status::Free }) - ); - assert_eq!(Balances::reserved_balance(1), 69); - assert_eq!(Balances::free_balance(1), 0); - assert_eq!(Balances::reserved_balance(2), 0); - assert_eq!(Balances::free_balance(2), 42); - }); - } - - #[test] - fn transferring_reserved_balance_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 110); - let _ = Balances::deposit_creating(&2, 1); - assert_ok!(Balances::reserve(&1, 110)); - assert_ok!(Balances::repatriate_reserved(&1, &2, 41, Status::Reserved), 0); - assert_eq!(Balances::reserved_balance(1), 69); - assert_eq!(Balances::free_balance(1), 0); - assert_eq!(Balances::reserved_balance(2), 41); - assert_eq!(Balances::free_balance(2), 1); - }); - } - - #[test] - fn transferring_reserved_balance_to_yourself_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 110); - assert_ok!(Balances::reserve(&1, 50)); - assert_ok!(Balances::repatriate_reserved(&1, &1, 50, Status::Free), 0); - assert_eq!(Balances::free_balance(1), 110); - assert_eq!(Balances::reserved_balance(1), 0); - - assert_ok!(Balances::reserve(&1, 50)); - assert_ok!(Balances::repatriate_reserved(&1, &1, 60, Status::Free), 10); - assert_eq!(Balances::free_balance(1), 110); - assert_eq!(Balances::reserved_balance(1), 0); - }); - } - - #[test] - fn transferring_reserved_balance_to_nonexistent_should_fail() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - assert_ok!(Balances::reserve(&1, 111)); - assert_noop!(Balances::repatriate_reserved(&1, &2, 42, Status::Free), Error::<$test, _>::DeadAccount); - }); - } - - #[test] - fn transferring_incomplete_reserved_balance_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 110); - let _ = Balances::deposit_creating(&2, 1); - assert_ok!(Balances::reserve(&1, 41)); - assert_ok!(Balances::repatriate_reserved(&1, &2, 69, Status::Free), 28); - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(Balances::free_balance(1), 69); - assert_eq!(Balances::reserved_balance(2), 0); - assert_eq!(Balances::free_balance(2), 42); - }); - } - - #[test] - fn transferring_too_high_value_should_not_panic() { - <$ext_builder>::default().build().execute_with(|| { - Balances::make_free_balance_be(&1, u64::MAX); - Balances::make_free_balance_be(&2, 1); - - assert_err!( - Balances::transfer(Some(1).into(), 2, u64::MAX), - ArithmeticError::Overflow, - ); - - assert_eq!(Balances::free_balance(1), u64::MAX); - assert_eq!(Balances::free_balance(2), 1); - }); - } - - #[test] - fn account_create_on_free_too_low_with_other() { - <$ext_builder>::default().existential_deposit(100).build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 100); - assert_eq!(>::get(), 100); - - // No-op. - let _ = Balances::deposit_creating(&2, 50); - assert_eq!(Balances::free_balance(2), 0); - assert_eq!(>::get(), 100); - }) - } - - #[test] - fn account_create_on_free_too_low() { - <$ext_builder>::default().existential_deposit(100).build().execute_with(|| { - // No-op. - let _ = Balances::deposit_creating(&2, 50); - assert_eq!(Balances::free_balance(2), 0); - assert_eq!(>::get(), 0); - }) - } - - #[test] - fn account_removal_on_free_too_low() { - <$ext_builder>::default().existential_deposit(100).build().execute_with(|| { - assert_eq!(>::get(), 0); - - // Setup two accounts with free balance above the existential threshold. - let _ = Balances::deposit_creating(&1, 110); - let _ = Balances::deposit_creating(&2, 110); - - assert_eq!(Balances::free_balance(1), 110); - assert_eq!(Balances::free_balance(2), 110); - assert_eq!(>::get(), 220); - - // Transfer funds from account 1 of such amount that after this transfer - // the balance of account 1 will be below the existential threshold. - // This should lead to the removal of all balance of this account. - assert_ok!(Balances::transfer(Some(1).into(), 2, 20)); - - // Verify free balance removal of account 1. - assert_eq!(Balances::free_balance(1), 0); - assert_eq!(Balances::free_balance(2), 130); - - // Verify that TotalIssuance tracks balance removal when free balance is too low. - assert_eq!(>::get(), 130); - }); - } - - #[test] - fn burn_must_work() { - <$ext_builder>::default().monied(true).build().execute_with(|| { - let init_total_issuance = Balances::total_issuance(); - let imbalance = Balances::burn(10); - assert_eq!(Balances::total_issuance(), init_total_issuance - 10); - drop(imbalance); - assert_eq!(Balances::total_issuance(), init_total_issuance); - }); - } - - #[test] - fn transfer_keep_alive_works() { - <$ext_builder>::default().existential_deposit(1).build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 100); - assert_noop!( - Balances::transfer_keep_alive(Some(1).into(), 2, 100), - Error::<$test, _>::KeepAlive - ); - assert_eq!(Balances::total_balance(&1), 100); - assert_eq!(Balances::total_balance(&2), 0); - }); - } - - #[test] - #[should_panic = "the balance of any account should always be at least the existential deposit."] - fn cannot_set_genesis_value_below_ed() { - ($existential_deposit).with(|v| *v.borrow_mut() = 11); - let mut t = frame_system::GenesisConfig::default().build_storage::<$test>().unwrap(); - let _ = pallet_balances::GenesisConfig::<$test> { - balances: vec![(1, 10)], - }.assimilate_storage(&mut t).unwrap(); - } - - #[test] - #[should_panic = "duplicate balances in genesis."] - fn cannot_set_genesis_value_twice() { - let mut t = frame_system::GenesisConfig::default().build_storage::<$test>().unwrap(); - let _ = pallet_balances::GenesisConfig::<$test> { - balances: vec![(1, 10), (2, 20), (1, 15)], - }.assimilate_storage(&mut t).unwrap(); - } - - #[test] - fn dust_moves_between_free_and_reserved() { - <$ext_builder>::default() - .existential_deposit(100) - .build() - .execute_with(|| { - // Set balance to free and reserved at the existential deposit - assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0)); - // Check balance - assert_eq!(Balances::free_balance(1), 100); - assert_eq!(Balances::reserved_balance(1), 0); - - // Reserve some free balance - assert_ok!(Balances::reserve(&1, 50)); - // Check balance, the account should be ok. - assert_eq!(Balances::free_balance(1), 50); - assert_eq!(Balances::reserved_balance(1), 50); - - // Reserve the rest of the free balance - assert_ok!(Balances::reserve(&1, 50)); - // Check balance, the account should be ok. - assert_eq!(Balances::free_balance(1), 0); - assert_eq!(Balances::reserved_balance(1), 100); - - // Unreserve everything - Balances::unreserve(&1, 100); - // Check balance, all 100 should move to free_balance - assert_eq!(Balances::free_balance(1), 100); - assert_eq!(Balances::reserved_balance(1), 0); - }); - } - - #[test] - fn account_deleted_when_just_dust() { - <$ext_builder>::default() - .existential_deposit(100) - .build() - .execute_with(|| { - // Set balance to free and reserved at the existential deposit - assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 50, 50)); - // Check balance - assert_eq!(Balances::free_balance(1), 50); - assert_eq!(Balances::reserved_balance(1), 50); - - // Reserve some free balance - let res = Balances::slash(&1, 1); - assert_eq!(res, (NegativeImbalance::new(1), 0)); - - // The account should be dead. - assert_eq!(Balances::free_balance(1), 0); - assert_eq!(Balances::reserved_balance(1), 0); - }); - } - - #[test] - fn emit_events_with_reserve_and_unreserve() { - <$ext_builder>::default() - .build() - .execute_with(|| { - let _ = Balances::deposit_creating(&1, 100); - - System::set_block_number(2); - assert_ok!(Balances::reserve(&1, 10)); - - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Reserved { who: 1, amount: 10 })); - - System::set_block_number(3); - assert!(Balances::unreserve(&1, 5).is_zero()); - - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Unreserved { who: 1, amount: 5 })); - - System::set_block_number(4); - assert_eq!(Balances::unreserve(&1, 6), 1); - - // should only unreserve 5 - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Unreserved { who: 1, amount: 5 })); - }); - } - - #[test] - fn emit_events_with_existential_deposit() { - <$ext_builder>::default() - .existential_deposit(100) - .build() - .execute_with(|| { - assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0)); - - assert_eq!( - events(), - [ - RuntimeEvent::System(system::Event::NewAccount { account: 1 }), - RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }), - RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100, reserved: 0 }), - ] - ); - - let res = Balances::slash(&1, 1); - assert_eq!(res, (NegativeImbalance::new(1), 0)); - - assert_eq!( - events(), - [ - RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), - RuntimeEvent::Balances(crate::Event::DustLost { account: 1, amount: 99 }), - RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 1 }), - ] - ); - }); - } - - #[test] - fn emit_events_with_no_existential_deposit_suicide() { - <$ext_builder>::default() - .existential_deposit(1) - .build() - .execute_with(|| { - assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0)); - - assert_eq!( - events(), - [ - RuntimeEvent::System(system::Event::NewAccount { account: 1 }), - RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }), - RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100, reserved: 0 }), - ] - ); - - let res = Balances::slash(&1, 100); - assert_eq!(res, (NegativeImbalance::new(100), 0)); - - assert_eq!( - events(), - [ - RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), - RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 100 }), - ] - ); - }); - } - - #[test] - fn slash_loop_works() { - <$ext_builder>::default() - .existential_deposit(100) - .build() - .execute_with(|| { - /* User has no reference counter, so they can die in these scenarios */ - - // SCENARIO: Slash would not kill account. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 0)); - // Slashed completed in full - assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(900), 0)); - // Account is still alive - assert!(System::account_exists(&1)); - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 900 })); - - // SCENARIO: Slash will kill account because not enough balance left. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 0)); - // Slashed completed in full - assert_eq!(Balances::slash(&1, 950), (NegativeImbalance::new(950), 0)); - // Account is killed - assert!(!System::account_exists(&1)); - - // SCENARIO: Over-slash will kill account, and report missing slash amount. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 0)); - // Slashed full free_balance, and reports 300 not slashed - assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1000), 300)); - // Account is dead - assert!(!System::account_exists(&1)); - - // SCENARIO: Over-slash can take from reserved, but keep alive. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 400)); - // Slashed full free_balance and 300 of reserved balance - assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1300), 0)); - // Account is still alive - assert!(System::account_exists(&1)); - - // SCENARIO: Over-slash can take from reserved, and kill. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 350)); - // Slashed full free_balance and 300 of reserved balance - assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1300), 0)); - // Account is dead because 50 reserved balance is not enough to keep alive - assert!(!System::account_exists(&1)); - - // SCENARIO: Over-slash can take as much as possible from reserved, kill, and report missing amount. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 250)); - // Slashed full free_balance and 300 of reserved balance - assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1250), 50)); - // Account is super dead - assert!(!System::account_exists(&1)); - - /* User will now have a reference counter on them, keeping them alive in these scenarios */ - - // SCENARIO: Slash would not kill account. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 0)); - assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests - // Slashed completed in full - assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(900), 0)); - // Account is still alive - assert!(System::account_exists(&1)); - - // SCENARIO: Slash will take as much as possible without killing account. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 0)); - // Slashed completed in full - assert_eq!(Balances::slash(&1, 950), (NegativeImbalance::new(900), 50)); - // Account is still alive - assert!(System::account_exists(&1)); - - // SCENARIO: Over-slash will not kill account, and report missing slash amount. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 0)); - // Slashed full free_balance minus ED, and reports 400 not slashed - assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(900), 400)); - // Account is still alive - assert!(System::account_exists(&1)); - - // SCENARIO: Over-slash can take from reserved, but keep alive. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 400)); - // Slashed full free_balance and 300 of reserved balance - assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1300), 0)); - // Account is still alive - assert!(System::account_exists(&1)); - - // SCENARIO: Over-slash can take from reserved, but keep alive. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 350)); - // Slashed full free_balance and 250 of reserved balance to leave ED - assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1250), 50)); - // Account is still alive - assert!(System::account_exists(&1)); - - // SCENARIO: Over-slash can take as much as possible from reserved and report missing amount. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 1_000, 250)); - // Slashed full free_balance and 300 of reserved balance - assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1150), 150)); - // Account is still alive - assert!(System::account_exists(&1)); - - // Slash on non-existent account is okay. - assert_eq!(Balances::slash(&12345, 1_300), (NegativeImbalance::new(0), 1300)); - }); - } - - #[test] - fn slash_reserved_loop_works() { - <$ext_builder>::default() - .existential_deposit(100) - .build() - .execute_with(|| { - /* User has no reference counter, so they can die in these scenarios */ - - // SCENARIO: Slash would not kill account. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 50, 1_000)); - // Slashed completed in full - assert_eq!(Balances::slash_reserved(&1, 900), (NegativeImbalance::new(900), 0)); - // Account is still alive - assert!(System::account_exists(&1)); - - // SCENARIO: Slash would kill account. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 50, 1_000)); - // Slashed completed in full - assert_eq!(Balances::slash_reserved(&1, 1_000), (NegativeImbalance::new(1_000), 0)); - // Account is dead - assert!(!System::account_exists(&1)); - - // SCENARIO: Over-slash would kill account, and reports left over slash. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 50, 1_000)); - // Slashed completed in full - assert_eq!(Balances::slash_reserved(&1, 1_300), (NegativeImbalance::new(1_000), 300)); - // Account is dead - assert!(!System::account_exists(&1)); - - // SCENARIO: Over-slash does not take from free balance. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 300, 1_000)); - // Slashed completed in full - assert_eq!(Balances::slash_reserved(&1, 1_300), (NegativeImbalance::new(1_000), 300)); - // Account is alive because of free balance - assert!(System::account_exists(&1)); - - /* User has a reference counter, so they cannot die */ - - // SCENARIO: Slash would not kill account. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 50, 1_000)); - assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests - // Slashed completed in full - assert_eq!(Balances::slash_reserved(&1, 900), (NegativeImbalance::new(900), 0)); - // Account is still alive - assert!(System::account_exists(&1)); - - // SCENARIO: Slash as much as possible without killing. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 50, 1_000)); - // Slashed as much as possible - assert_eq!(Balances::slash_reserved(&1, 1_000), (NegativeImbalance::new(950), 50)); - // Account is still alive - assert!(System::account_exists(&1)); - - // SCENARIO: Over-slash reports correctly, where reserved is needed to keep alive. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 50, 1_000)); - // Slashed as much as possible - assert_eq!(Balances::slash_reserved(&1, 1_300), (NegativeImbalance::new(950), 350)); - // Account is still alive - assert!(System::account_exists(&1)); - - // SCENARIO: Over-slash reports correctly, where full reserved is removed. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 200, 1_000)); - // Slashed as much as possible - assert_eq!(Balances::slash_reserved(&1, 1_300), (NegativeImbalance::new(1_000), 300)); - // Account is still alive - assert!(System::account_exists(&1)); - - // Slash on non-existent account is okay. - assert_eq!(Balances::slash_reserved(&12345, 1_300), (NegativeImbalance::new(0), 1300)); - }); - } - - #[test] - fn operations_on_dead_account_should_not_change_state() { - // These functions all use `mutate_account` which may introduce a storage change when - // the account never existed to begin with, and shouldn't exist in the end. - <$ext_builder>::default() - .existential_deposit(0) - .build() - .execute_with(|| { - assert!(!frame_system::Account::::contains_key(&1337)); - - // Unreserve - assert_storage_noop!(assert_eq!(Balances::unreserve(&1337, 42), 42)); - // Reserve - assert_noop!(Balances::reserve(&1337, 42), Error::::InsufficientBalance); - // Slash Reserve - assert_storage_noop!(assert_eq!(Balances::slash_reserved(&1337, 42).1, 42)); - // Repatriate Reserve - assert_noop!(Balances::repatriate_reserved(&1337, &1338, 42, Status::Free), Error::::DeadAccount); - // Slash - assert_storage_noop!(assert_eq!(Balances::slash(&1337, 42).1, 42)); - }); - } - - #[test] - fn transfer_keep_alive_all_free_succeed() { - <$ext_builder>::default() - .existential_deposit(100) - .build() - .execute_with(|| { - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 100, 100)); - assert_ok!(Balances::transfer_keep_alive(Some(1).into(), 2, 100)); - assert_eq!(Balances::total_balance(&1), 100); - assert_eq!(Balances::total_balance(&2), 100); - }); - } - - #[test] - fn transfer_all_works() { - <$ext_builder>::default() - .existential_deposit(100) - .build() - .execute_with(|| { - // setup - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 200, 0)); - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 2, 0, 0)); - // transfer all and allow death - assert_ok!(Balances::transfer_all(Some(1).into(), 2, false)); - assert_eq!(Balances::total_balance(&1), 0); - assert_eq!(Balances::total_balance(&2), 200); - - // setup - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 200, 0)); - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 2, 0, 0)); - // transfer all and keep alive - assert_ok!(Balances::transfer_all(Some(1).into(), 2, true)); - assert_eq!(Balances::total_balance(&1), 100); - assert_eq!(Balances::total_balance(&2), 100); - - // setup - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 200, 10)); - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 2, 0, 0)); - // transfer all and allow death w/ reserved - assert_ok!(Balances::transfer_all(Some(1).into(), 2, false)); - assert_eq!(Balances::total_balance(&1), 0); - assert_eq!(Balances::total_balance(&2), 200); - - // setup - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1, 200, 10)); - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 2, 0, 0)); - // transfer all and keep alive w/ reserved - assert_ok!(Balances::transfer_all(Some(1).into(), 2, true)); - assert_eq!(Balances::total_balance(&1), 100); - assert_eq!(Balances::total_balance(&2), 110); - }); - } - - #[test] - fn named_reserve_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - - let id_1 = [1u8; 8]; - let id_2 = [2u8; 8]; - let id_3 = [3u8; 8]; - - // reserve - - assert_noop!(Balances::reserve_named(&id_1, &1, 112), Error::::InsufficientBalance); - - assert_ok!(Balances::reserve_named(&id_1, &1, 12)); - - assert_eq!(Balances::reserved_balance(1), 12); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 12); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 0); - - assert_ok!(Balances::reserve_named(&id_1, &1, 2)); - - assert_eq!(Balances::reserved_balance(1), 14); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 14); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 0); - - assert_ok!(Balances::reserve_named(&id_2, &1, 23)); - - assert_eq!(Balances::reserved_balance(1), 37); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 14); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23); - - assert_ok!(Balances::reserve(&1, 34)); - - assert_eq!(Balances::reserved_balance(1), 71); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 14); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23); - - assert_eq!(Balances::total_balance(&1), 111); - assert_eq!(Balances::free_balance(1), 40); - - assert_noop!(Balances::reserve_named(&id_3, &1, 2), Error::::TooManyReserves); - - // unreserve - - assert_eq!(Balances::unreserve_named(&id_1, &1, 10), 0); - - assert_eq!(Balances::reserved_balance(1), 61); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 4); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23); - - assert_eq!(Balances::unreserve_named(&id_1, &1, 5), 1); - - assert_eq!(Balances::reserved_balance(1), 57); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23); - - assert_eq!(Balances::unreserve_named(&id_2, &1, 3), 0); - - assert_eq!(Balances::reserved_balance(1), 54); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 20); - - assert_eq!(Balances::total_balance(&1), 111); - assert_eq!(Balances::free_balance(1), 57); - - // slash_reserved_named - - assert_ok!(Balances::reserve_named(&id_1, &1, 10)); - - assert_eq!(Balances::slash_reserved_named(&id_1, &1, 25).1, 15); - - assert_eq!(Balances::reserved_balance(1), 54); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 20); - assert_eq!(Balances::total_balance(&1), 101); - - assert_eq!(Balances::slash_reserved_named(&id_2, &1, 5).1, 0); - - assert_eq!(Balances::reserved_balance(1), 49); - assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 15); - assert_eq!(Balances::total_balance(&1), 96); - - // repatriate_reserved_named - - let _ = Balances::deposit_creating(&2, 100); - - assert_eq!(Balances::repatriate_reserved_named(&id_2, &1, &2, 10, Status::Reserved).unwrap(), 0); - - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 5); - assert_eq!(Balances::reserved_balance_named(&id_2, &2), 10); - assert_eq!(Balances::reserved_balance(&2), 10); - - assert_eq!(Balances::repatriate_reserved_named(&id_2, &2, &1, 11, Status::Reserved).unwrap(), 1); - - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 15); - assert_eq!(Balances::reserved_balance_named(&id_2, &2), 0); - assert_eq!(Balances::reserved_balance(&2), 0); - - assert_eq!(Balances::repatriate_reserved_named(&id_2, &1, &2, 10, Status::Free).unwrap(), 0); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 5); - assert_eq!(Balances::reserved_balance_named(&id_2, &2), 0); - assert_eq!(Balances::free_balance(&2), 110); - - // repatriate_reserved_named to self - - assert_eq!(Balances::repatriate_reserved_named(&id_2, &1, &1, 10, Status::Reserved).unwrap(), 5); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 5); - - assert_eq!(Balances::free_balance(&1), 47); - - assert_eq!(Balances::repatriate_reserved_named(&id_2, &1, &1, 15, Status::Free).unwrap(), 10); - assert_eq!(Balances::reserved_balance_named(&id_2, &1), 0); - - assert_eq!(Balances::free_balance(&1), 52); - }); - } - - #[test] - fn reserved_named_to_yourself_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 110); - - let id = [1u8; 8]; - - assert_ok!(Balances::reserve_named(&id, &1, 50)); - assert_ok!(Balances::repatriate_reserved_named(&id, &1, &1, 50, Status::Free), 0); - assert_eq!(Balances::free_balance(1), 110); - assert_eq!(Balances::reserved_balance_named(&id, &1), 0); - - assert_ok!(Balances::reserve_named(&id, &1, 50)); - assert_ok!(Balances::repatriate_reserved_named(&id, &1, &1, 60, Status::Free), 10); - assert_eq!(Balances::free_balance(1), 110); - assert_eq!(Balances::reserved_balance_named(&id, &1), 0); - }); - } - - #[test] - fn ensure_reserved_named_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - - let id = [1u8; 8]; - - assert_ok!(Balances::ensure_reserved_named(&id, &1, 15)); - assert_eq!(Balances::reserved_balance_named(&id, &1), 15); - - assert_ok!(Balances::ensure_reserved_named(&id, &1, 10)); - assert_eq!(Balances::reserved_balance_named(&id, &1), 10); - - assert_ok!(Balances::ensure_reserved_named(&id, &1, 20)); - assert_eq!(Balances::reserved_balance_named(&id, &1), 20); - }); - } - - #[test] - fn unreserve_all_named_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - - let id = [1u8; 8]; - - assert_ok!(Balances::reserve_named(&id, &1, 15)); - - assert_eq!(Balances::unreserve_all_named(&id, &1), 15); - assert_eq!(Balances::reserved_balance_named(&id, &1), 0); - assert_eq!(Balances::free_balance(&1), 111); - - assert_eq!(Balances::unreserve_all_named(&id, &1), 0); - }); - } - - #[test] - fn slash_all_reserved_named_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - - let id = [1u8; 8]; - - assert_ok!(Balances::reserve_named(&id, &1, 15)); - - assert_eq!(Balances::slash_all_reserved_named(&id, &1).peek(), 15); - assert_eq!(Balances::reserved_balance_named(&id, &1), 0); - assert_eq!(Balances::free_balance(&1), 96); - - assert_eq!(Balances::slash_all_reserved_named(&id, &1).peek(), 0); - }); - } - - #[test] - fn repatriate_all_reserved_named_should_work() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - let _ = Balances::deposit_creating(&2, 10); - let _ = Balances::deposit_creating(&3, 10); - - let id = [1u8; 8]; - - assert_ok!(Balances::reserve_named(&id, &1, 15)); - - assert_ok!(Balances::repatriate_all_reserved_named(&id, &1, &2, Status::Reserved)); - assert_eq!(Balances::reserved_balance_named(&id, &1), 0); - assert_eq!(Balances::reserved_balance_named(&id, &2), 15); - - assert_ok!(Balances::repatriate_all_reserved_named(&id, &2, &3, Status::Free)); - assert_eq!(Balances::reserved_balance_named(&id, &2), 0); - assert_eq!(Balances::free_balance(&3), 25); - }); - } - - #[test] - fn set_balance_handles_killing_account() { - <$ext_builder>::default().build().execute_with(|| { - let _ = Balances::deposit_creating(&1, 111); - assert_ok!(frame_system::Pallet::::inc_consumers(&1)); - assert_noop!( - Balances::set_balance(RuntimeOrigin::root(), 1, 0, 0), - DispatchError::ConsumerRemaining, - ); - }); - } - - #[test] - fn set_balance_handles_total_issuance() { - <$ext_builder>::default().build().execute_with(|| { - let old_total_issuance = Balances::total_issuance(); - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 1337, 69, 42)); - assert_eq!(Balances::total_issuance(), old_total_issuance + 69 + 42); - assert_eq!(Balances::total_balance(&1337), 69 + 42); - assert_eq!(Balances::free_balance(&1337), 69); - assert_eq!(Balances::reserved_balance(&1337), 42); - }); - } - - #[test] - fn fungible_unbalanced_trait_set_balance_works() { - <$ext_builder>::default().build().execute_with(|| { - assert_eq!(>::balance(&1337), 0); - assert_ok!(>::set_balance(&1337, 100)); - assert_eq!(>::balance(&1337), 100); - - assert_ok!(Balances::reserve(&1337, 60)); - assert_eq!(Balances::free_balance(1337) , 40); - assert_eq!(Balances::reserved_balance(1337), 60); - - assert_noop!(>::set_balance(&1337, 0), ArithmeticError::Underflow); - - assert_ok!(>::set_balance(&1337, 60)); - assert_eq!(Balances::free_balance(1337) , 0); - assert_eq!(Balances::reserved_balance(1337), 60); - }); - } - - #[test] - fn fungible_unbalanced_trait_set_total_issuance_works() { - <$ext_builder>::default().build().execute_with(|| { - assert_eq!(>::total_issuance(), 0); - >::set_total_issuance(100); - assert_eq!(>::total_issuance(), 100); - }); - } - - #[test] - fn fungible_unbalanced_trait_decrease_balance_simple_works() { - <$ext_builder>::default().build().execute_with(|| { - // An Account that starts at 100 - assert_ok!(>::set_balance(&1337, 100)); - // and reserves 50 - assert_ok!(Balances::reserve(&1337, 50)); - // and is decreased by 20 - assert_ok!(>::decrease_balance(&1337, 20)); - // should end up at 80. - assert_eq!(>::balance(&1337), 80); - }); - } - - #[test] - fn fungible_unbalanced_trait_decrease_balance_works() { - <$ext_builder>::default().build().execute_with(|| { - assert_ok!(>::set_balance(&1337, 100)); - assert_eq!(>::balance(&1337), 100); - - assert_noop!( - >::decrease_balance(&1337, 101), - TokenError::NoFunds - ); - assert_eq!( - >::decrease_balance(&1337, 100), - Ok(100) - ); - assert_eq!(>::balance(&1337), 0); - - // free: 40, reserved: 60 - assert_ok!(>::set_balance(&1337, 100)); - assert_ok!(Balances::reserve(&1337, 60)); - assert_eq!(Balances::free_balance(1337) , 40); - assert_eq!(Balances::reserved_balance(1337), 60); - assert_noop!( - >::decrease_balance(&1337, 41), - TokenError::NoFunds - ); - assert_eq!( - >::decrease_balance(&1337, 40), - Ok(40) - ); - assert_eq!(>::balance(&1337), 60); - assert_eq!(Balances::free_balance(1337), 0); - assert_eq!(Balances::reserved_balance(1337), 60); - }); - } - - #[test] - fn fungible_unbalanced_trait_decrease_balance_at_most_works() { - <$ext_builder>::default().build().execute_with(|| { - assert_ok!(>::set_balance(&1337, 100)); - assert_eq!(>::balance(&1337), 100); - - assert_eq!( - >::decrease_balance_at_most(&1337, 101), - 100 - ); - assert_eq!(>::balance(&1337), 0); - - assert_ok!(>::set_balance(&1337, 100)); - assert_eq!( - >::decrease_balance_at_most(&1337, 100), - 100 - ); - assert_eq!(>::balance(&1337), 0); - - // free: 40, reserved: 60 - assert_ok!(>::set_balance(&1337, 100)); - assert_ok!(Balances::reserve(&1337, 60)); - assert_eq!(Balances::free_balance(1337) , 40); - assert_eq!(Balances::reserved_balance(1337), 60); - assert_eq!( - >::decrease_balance_at_most(&1337, 0), - 0 - ); - assert_eq!(Balances::free_balance(1337) , 40); - assert_eq!(Balances::reserved_balance(1337), 60); - assert_eq!( - >::decrease_balance_at_most(&1337, 10), - 10 - ); - assert_eq!(Balances::free_balance(1337), 30); - assert_eq!( - >::decrease_balance_at_most(&1337, 200), - 30 - ); - assert_eq!(>::balance(&1337), 60); - assert_eq!(Balances::free_balance(1337), 0); - assert_eq!(Balances::reserved_balance(1337), 60); - }); - } - - #[test] - fn fungible_unbalanced_trait_increase_balance_works() { - <$ext_builder>::default().build().execute_with(|| { - assert_noop!( - >::increase_balance(&1337, 0), - TokenError::BelowMinimum - ); - assert_eq!( - >::increase_balance(&1337, 1), - Ok(1) - ); - assert_noop!( - >::increase_balance(&1337, u64::MAX), - ArithmeticError::Overflow - ); - }); - } - - #[test] - fn fungible_unbalanced_trait_increase_balance_at_most_works() { - <$ext_builder>::default().build().execute_with(|| { - assert_eq!( - >::increase_balance_at_most(&1337, 0), - 0 - ); - assert_eq!( - >::increase_balance_at_most(&1337, 1), - 1 - ); - assert_eq!( - >::increase_balance_at_most(&1337, u64::MAX), - u64::MAX - 1 - ); - }); - } - } -} diff --git a/frame/balances/src/tests/currency_tests.rs b/frame/balances/src/tests/currency_tests.rs new file mode 100644 index 000000000000..c9ad19f79e36 --- /dev/null +++ b/frame/balances/src/tests/currency_tests.rs @@ -0,0 +1,1331 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Tests regarding the functionality of the `Currency` trait set implementations. + +use super::*; +use crate::NegativeImbalance; +use frame_support::traits::{ + BalanceStatus::{Free, Reserved}, + Currency, + ExistenceRequirement::{self, AllowDeath}, + Hooks, LockIdentifier, LockableCurrency, NamedReservableCurrency, ReservableCurrency, + WithdrawReasons, +}; + +const ID_1: LockIdentifier = *b"1 "; +const ID_2: LockIdentifier = *b"2 "; + +pub const CALL: &::RuntimeCall = + &RuntimeCall::Balances(crate::Call::transfer_allow_death { dest: 0, value: 0 }); + +#[test] +fn set_lock_with_amount_zero_removes_lock() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, u64::MAX, WithdrawReasons::all()); + Balances::set_lock(ID_1, &1, 0, WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); +} + +#[test] +fn set_lock_with_withdraw_reasons_empty_removes_lock() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, u64::MAX, WithdrawReasons::all()); + Balances::set_lock(ID_1, &1, u64::MAX, WithdrawReasons::empty()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); +} + +#[test] +fn basic_locking_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + Balances::set_lock(ID_1, &1, 9, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 5, AllowDeath), + TokenError::Frozen + ); + }); +} + +#[test] +fn account_should_be_reaped() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_eq!(Balances::free_balance(1), 10); + assert_ok!(>::transfer(&1, &2, 10, AllowDeath)); + assert_eq!(System::providers(&1), 0); + assert_eq!(System::consumers(&1), 0); + // Check that the account is dead. + assert!(!frame_system::Account::::contains_key(&1)); + }); +} + +#[test] +fn reap_failed_due_to_provider_and_consumer() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + // SCENARIO: only one provider and there are remaining consumers. + assert_ok!(System::inc_consumers(&1)); + assert!(!System::can_dec_provider(&1)); + assert_noop!( + >::transfer(&1, &2, 10, AllowDeath), + TokenError::Frozen + ); + assert!(System::account_exists(&1)); + assert_eq!(Balances::free_balance(1), 10); + + // SCENARIO: more than one provider, but will not kill account due to other provider. + assert_eq!(System::inc_providers(&1), frame_system::IncRefStatus::Existed); + assert_eq!(System::providers(&1), 2); + assert!(System::can_dec_provider(&1)); + assert_ok!(>::transfer(&1, &2, 10, AllowDeath)); + assert_eq!(System::providers(&1), 1); + assert!(System::account_exists(&1)); + assert_eq!(Balances::free_balance(1), 0); + }); +} + +#[test] +fn partial_locking_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); +} + +#[test] +fn lock_removal_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, u64::MAX, WithdrawReasons::all()); + Balances::remove_lock(ID_1, &1); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); +} + +#[test] +fn lock_replacement_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, u64::MAX, WithdrawReasons::all()); + Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); +} + +#[test] +fn double_locking_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); + Balances::set_lock(ID_2, &1, 5, WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); +} + +#[test] +fn combination_locking_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, u64::MAX, WithdrawReasons::empty()); + Balances::set_lock(ID_2, &1, 0, WithdrawReasons::all()); + assert_ok!(>::transfer(&1, &2, 1, AllowDeath)); + }); +} + +#[test] +fn lock_value_extension_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + TokenError::Frozen + ); + Balances::extend_lock(ID_1, &1, 2, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + TokenError::Frozen + ); + Balances::extend_lock(ID_1, &1, 8, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 3, AllowDeath), + TokenError::Frozen + ); + }); +} + +#[test] +fn lock_should_work_reserve() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + pallet_transaction_payment::NextFeeMultiplier::::put( + Multiplier::saturating_from_integer(1), + ); + Balances::set_lock(ID_1, &1, 10, WithdrawReasons::RESERVE); + assert_noop!( + >::transfer(&1, &2, 1, AllowDeath), + TokenError::Frozen + ); + assert_noop!(Balances::reserve(&1, 1), Error::::LiquidityRestrictions,); + assert!( as SignedExtension>::pre_dispatch( + ChargeTransactionPayment::from(1), + &1, + CALL, + &info_from_weight(Weight::from_parts(1, 0)), + 1, + ) + .is_err()); + assert!( as SignedExtension>::pre_dispatch( + ChargeTransactionPayment::from(0), + &1, + CALL, + &info_from_weight(Weight::from_parts(1, 0)), + 1, + ) + .is_err()); + }); +} + +#[test] +fn lock_should_work_tx_fee() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, 10, WithdrawReasons::TRANSACTION_PAYMENT); + assert_noop!( + >::transfer(&1, &2, 1, AllowDeath), + TokenError::Frozen + ); + assert_noop!(Balances::reserve(&1, 1), Error::::LiquidityRestrictions,); + assert!( as SignedExtension>::pre_dispatch( + ChargeTransactionPayment::from(1), + &1, + CALL, + &info_from_weight(Weight::from_parts(1, 0)), + 1, + ) + .is_err()); + assert!( as SignedExtension>::pre_dispatch( + ChargeTransactionPayment::from(0), + &1, + CALL, + &info_from_weight(Weight::from_parts(1, 0)), + 1, + ) + .is_err()); + }); +} + +#[test] +fn lock_block_number_extension_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, 10, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + TokenError::Frozen + ); + Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + TokenError::Frozen + ); + System::set_block_number(2); + Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::all()); + assert_noop!( + >::transfer(&1, &2, 3, AllowDeath), + TokenError::Frozen + ); + }); +} + +#[test] +fn lock_reasons_extension_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + Balances::set_lock(ID_1, &1, 10, WithdrawReasons::TRANSFER); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + TokenError::Frozen + ); + Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::empty()); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + TokenError::Frozen + ); + Balances::extend_lock(ID_1, &1, 10, WithdrawReasons::RESERVE); + assert_noop!( + >::transfer(&1, &2, 6, AllowDeath), + TokenError::Frozen + ); + }); +} + +#[test] +fn reserved_balance_should_prevent_reclaim_count() { + ExtBuilder::default() + .existential_deposit(256 * 1) + .monied(true) + .build_and_execute_with(|| { + System::inc_account_nonce(&2); + assert_eq!(Balances::total_balance(&2), 256 * 20); + assert_eq!(System::providers(&2), 1); + System::inc_providers(&2); + assert_eq!(System::providers(&2), 2); + + assert_ok!(Balances::reserve(&2, 256 * 19 + 1)); // account 2 becomes mostly reserved + assert_eq!(System::providers(&2), 1); + assert_eq!(Balances::free_balance(2), 255); // "free" account would be deleted. + assert_eq!(Balances::total_balance(&2), 256 * 20); // reserve still exists. + assert_eq!(System::account_nonce(&2), 1); + + // account 4 tries to take index 1 for account 5. + assert_ok!(Balances::transfer_allow_death(Some(4).into(), 5, 256 * 1 + 0x69)); + assert_eq!(Balances::total_balance(&5), 256 * 1 + 0x69); + + assert!(Balances::slash_reserved(&2, 256 * 19 + 1).1.is_zero()); // account 2 gets slashed + + // "reserve" account reduced to 255 (below ED) so account no longer consuming + assert_ok!(System::dec_providers(&2)); + assert_eq!(System::providers(&2), 0); + // account deleted + assert_eq!(System::account_nonce(&2), 0); // nonce zero + assert_eq!(Balances::total_balance(&2), 0); + + // account 4 tries to take index 1 again for account 6. + assert_ok!(Balances::transfer_allow_death(Some(4).into(), 6, 256 * 1 + 0x69)); + assert_eq!(Balances::total_balance(&6), 256 * 1 + 0x69); + }); +} + +#[test] +fn reward_should_work() { + ExtBuilder::default().monied(true).build_and_execute_with(|| { + assert_eq!(Balances::total_balance(&1), 10); + assert_ok!(Balances::deposit_into_existing(&1, 10).map(drop)); + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Deposit { + who: 1, + amount: 10, + })); + assert_eq!(Balances::total_balance(&1), 20); + assert_eq!(Balances::total_issuance(), 120); + }); +} + +#[test] +fn balance_works() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 42); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { + who: 1, + amount: 42, + })); + assert_eq!(Balances::free_balance(1), 42); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::total_balance(&1), 42); + assert_eq!(Balances::free_balance(2), 0); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::total_balance(&2), 0); + }); +} + +#[test] +fn reserving_balance_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + + assert_eq!(Balances::total_balance(&1), 111); + assert_eq!(Balances::free_balance(1), 111); + assert_eq!(Balances::reserved_balance(1), 0); + + assert_ok!(Balances::reserve(&1, 69)); + + assert_eq!(Balances::total_balance(&1), 111); + assert_eq!(Balances::free_balance(1), 42); + assert_eq!(Balances::reserved_balance(1), 69); + }); +} + +#[test] +fn deducting_balance_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::reserve(&1, 69)); + assert_eq!(Balances::free_balance(1), 42); + }); +} + +#[test] +fn refunding_balance_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 42); + assert_ok!(Balances::mutate_account(&1, |a| a.reserved = 69)); + Balances::unreserve(&1, 69); + assert_eq!(Balances::free_balance(1), 111); + assert_eq!(Balances::reserved_balance(1), 0); + }); +} + +#[test] +fn slashing_balance_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 112); + assert_ok!(Balances::reserve(&1, 69)); + assert!(Balances::slash(&1, 42).1.is_zero()); + assert_eq!(Balances::free_balance(1), 1); + assert_eq!(Balances::reserved_balance(1), 69); + assert_eq!(Balances::total_issuance(), 70); + }); +} + +#[test] +fn withdrawing_balance_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&2, 111); + let _ = + Balances::withdraw(&2, 11, WithdrawReasons::TRANSFER, ExistenceRequirement::KeepAlive); + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Withdraw { + who: 2, + amount: 11, + })); + assert_eq!(Balances::free_balance(2), 100); + assert_eq!(Balances::total_issuance(), 100); + }); +} + +#[test] +fn withdrawing_balance_should_fail_when_not_expendable() { + ExtBuilder::default().build_and_execute_with(|| { + ExistentialDeposit::set(10); + let _ = Balances::deposit_creating(&2, 20); + assert_ok!(Balances::reserve(&2, 5)); + assert_noop!( + Balances::withdraw(&2, 6, WithdrawReasons::TRANSFER, ExistenceRequirement::KeepAlive), + Error::::Expendability, + ); + assert_ok!(Balances::withdraw( + &2, + 5, + WithdrawReasons::TRANSFER, + ExistenceRequirement::KeepAlive + ),); + }); +} + +#[test] +fn slashing_incomplete_balance_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 42); + assert_ok!(Balances::reserve(&1, 21)); + assert_eq!(Balances::slash(&1, 69).1, 49); + assert_eq!(Balances::free_balance(1), 1); + assert_eq!(Balances::reserved_balance(1), 21); + assert_eq!(Balances::total_issuance(), 22); + }); +} + +#[test] +fn unreserving_balance_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::reserve(&1, 110)); + Balances::unreserve(&1, 41); + assert_eq!(Balances::reserved_balance(1), 69); + assert_eq!(Balances::free_balance(1), 42); + }); +} + +#[test] +fn slashing_reserved_balance_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 112); + assert_ok!(Balances::reserve(&1, 111)); + assert_eq!(Balances::slash_reserved(&1, 42).1, 0); + assert_eq!(Balances::reserved_balance(1), 69); + assert_eq!(Balances::free_balance(1), 1); + assert_eq!(Balances::total_issuance(), 70); + }); +} + +#[test] +fn slashing_incomplete_reserved_balance_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::reserve(&1, 42)); + assert_eq!(Balances::slash_reserved(&1, 69).1, 27); + assert_eq!(Balances::free_balance(1), 69); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::total_issuance(), 69); + }); +} + +#[test] +fn repatriating_reserved_balance_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + let _ = Balances::deposit_creating(&2, 1); + assert_ok!(Balances::reserve(&1, 110)); + assert_ok!(Balances::repatriate_reserved(&1, &2, 41, Free), 0); + System::assert_last_event(RuntimeEvent::Balances(crate::Event::ReserveRepatriated { + from: 1, + to: 2, + amount: 41, + destination_status: Free, + })); + assert_eq!(Balances::reserved_balance(1), 69); + assert_eq!(Balances::free_balance(1), 1); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::free_balance(2), 42); + }); +} + +#[test] +fn transferring_reserved_balance_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + let _ = Balances::deposit_creating(&2, 1); + assert_ok!(Balances::reserve(&1, 110)); + assert_ok!(Balances::repatriate_reserved(&1, &2, 41, Reserved), 0); + assert_eq!(Balances::reserved_balance(1), 69); + assert_eq!(Balances::free_balance(1), 1); + assert_eq!(Balances::reserved_balance(2), 41); + assert_eq!(Balances::free_balance(2), 1); + }); +} + +#[test] +fn transferring_reserved_balance_to_yourself_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 110); + assert_ok!(Balances::reserve(&1, 50)); + assert_ok!(Balances::repatriate_reserved(&1, &1, 50, Free), 0); + assert_eq!(Balances::free_balance(1), 110); + assert_eq!(Balances::reserved_balance(1), 0); + + assert_ok!(Balances::reserve(&1, 50)); + assert_ok!(Balances::repatriate_reserved(&1, &1, 60, Free), 10); + assert_eq!(Balances::free_balance(1), 110); + assert_eq!(Balances::reserved_balance(1), 0); + }); +} + +#[test] +fn transferring_reserved_balance_to_nonexistent_should_fail() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + assert_ok!(Balances::reserve(&1, 110)); + assert_noop!( + Balances::repatriate_reserved(&1, &2, 42, Free), + Error::::DeadAccount + ); + }); +} + +#[test] +fn transferring_incomplete_reserved_balance_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 110); + let _ = Balances::deposit_creating(&2, 1); + assert_ok!(Balances::reserve(&1, 41)); + assert_ok!(Balances::repatriate_reserved(&1, &2, 69, Free), 28); + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::free_balance(1), 69); + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::free_balance(2), 42); + }); +} + +#[test] +fn transferring_too_high_value_should_not_panic() { + ExtBuilder::default().build_and_execute_with(|| { + Balances::make_free_balance_be(&1, u64::MAX); + Balances::make_free_balance_be(&2, 1); + + assert_err!( + >::transfer(&1, &2, u64::MAX, AllowDeath), + ArithmeticError::Overflow, + ); + + assert_eq!(Balances::free_balance(1), u64::MAX); + assert_eq!(Balances::free_balance(2), 1); + }); +} + +#[test] +fn account_create_on_free_too_low_with_other() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 100); + assert_eq!(Balances::total_issuance(), 100); + + // No-op. + let _ = Balances::deposit_creating(&2, 50); + assert_eq!(Balances::free_balance(2), 0); + assert_eq!(Balances::total_issuance(), 100); + }) +} + +#[test] +fn account_create_on_free_too_low() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + // No-op. + let _ = Balances::deposit_creating(&2, 50); + assert_eq!(Balances::free_balance(2), 0); + assert_eq!(Balances::total_issuance(), 0); + }) +} + +#[test] +fn account_removal_on_free_too_low() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + assert_eq!(Balances::total_issuance(), 0); + + // Setup two accounts with free balance above the existential threshold. + let _ = Balances::deposit_creating(&1, 110); + let _ = Balances::deposit_creating(&2, 110); + + assert_eq!(Balances::free_balance(1), 110); + assert_eq!(Balances::free_balance(2), 110); + assert_eq!(Balances::total_issuance(), 220); + + // Transfer funds from account 1 of such amount that after this transfer + // the balance of account 1 will be below the existential threshold. + // This should lead to the removal of all balance of this account. + assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 20)); + + // Verify free balance removal of account 1. + assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::free_balance(2), 130); + + // Verify that TotalIssuance tracks balance removal when free balance is too low. + assert_eq!(Balances::total_issuance(), 130); + }); +} + +#[test] +fn burn_must_work() { + ExtBuilder::default().monied(true).build_and_execute_with(|| { + let init_total_issuance = Balances::total_issuance(); + let imbalance = Balances::burn(10); + assert_eq!(Balances::total_issuance(), init_total_issuance - 10); + drop(imbalance); + assert_eq!(Balances::total_issuance(), init_total_issuance); + }); +} + +#[test] +#[should_panic = "the balance of any account should always be at least the existential deposit."] +fn cannot_set_genesis_value_below_ed() { + EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = 11); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let _ = crate::GenesisConfig:: { balances: vec![(1, 10)] } + .assimilate_storage(&mut t) + .unwrap(); +} + +#[test] +#[should_panic = "duplicate balances in genesis."] +fn cannot_set_genesis_value_twice() { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let _ = crate::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (1, 15)] } + .assimilate_storage(&mut t) + .unwrap(); +} + +#[test] +fn existential_deposit_respected_when_reserving() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + // Set balance to free and reserved at the existential deposit + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 101)); + // Check balance + assert_eq!(Balances::free_balance(1), 101); + assert_eq!(Balances::reserved_balance(1), 0); + + // Reserve some free balance + assert_ok!(Balances::reserve(&1, 1)); + // Check balance, the account should be ok. + assert_eq!(Balances::free_balance(1), 100); + assert_eq!(Balances::reserved_balance(1), 1); + + // Cannot reserve any more of the free balance. + assert_noop!(Balances::reserve(&1, 1), DispatchError::ConsumerRemaining); + }); +} + +#[test] +fn slash_fails_when_account_needed() { + ExtBuilder::default().existential_deposit(50).build_and_execute_with(|| { + // Set balance to free and reserved at the existential deposit + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 52)); + assert_ok!(Balances::reserve(&1, 1)); + // Check balance + assert_eq!(Balances::free_balance(1), 51); + assert_eq!(Balances::reserved_balance(1), 1); + + // Slash a small amount + let res = Balances::slash(&1, 1); + assert_eq!(res, (NegativeImbalance::new(1), 0)); + + // The account should be dead. + assert_eq!(Balances::free_balance(1), 50); + assert_eq!(Balances::reserved_balance(1), 1); + + // Slashing again doesn't work since we require the ED + let res = Balances::slash(&1, 1); + assert_eq!(res, (NegativeImbalance::new(0), 1)); + + // The account should be dead. + assert_eq!(Balances::free_balance(1), 50); + assert_eq!(Balances::reserved_balance(1), 1); + }); +} + +#[test] +fn account_deleted_when_just_dust() { + ExtBuilder::default().existential_deposit(50).build_and_execute_with(|| { + // Set balance to free and reserved at the existential deposit + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 50)); + // Check balance + assert_eq!(Balances::free_balance(1), 50); + + // Slash a small amount + let res = Balances::slash(&1, 1); + assert_eq!(res, (NegativeImbalance::new(1), 0)); + + // The account should be dead. + assert_eq!(Balances::free_balance(1), 0); + }); +} + +#[test] +fn emit_events_with_reserve_and_unreserve() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 100); + + System::set_block_number(2); + assert_ok!(Balances::reserve(&1, 10)); + + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Reserved { + who: 1, + amount: 10, + })); + + System::set_block_number(3); + assert!(Balances::unreserve(&1, 5).is_zero()); + + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Unreserved { + who: 1, + amount: 5, + })); + + System::set_block_number(4); + assert_eq!(Balances::unreserve(&1, 6), 1); + + // should only unreserve 5 + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Unreserved { + who: 1, + amount: 5, + })); + }); +} + +#[test] +fn emit_events_with_changing_locks() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 100); + System::reset_events(); + + // Locks = [] --> [10] + Balances::set_lock(*b"LOCK_000", &1, 10, WithdrawReasons::TRANSFER); + assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Locked { who: 1, amount: 10 })]); + + // Locks = [10] --> [15] + Balances::set_lock(*b"LOCK_000", &1, 15, WithdrawReasons::TRANSFER); + assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Locked { who: 1, amount: 5 })]); + + // Locks = [15] --> [15, 20] + Balances::set_lock(*b"LOCK_001", &1, 20, WithdrawReasons::TRANSACTION_PAYMENT); + assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Locked { who: 1, amount: 5 })]); + + // Locks = [15, 20] --> [17, 20] + Balances::set_lock(*b"LOCK_000", &1, 17, WithdrawReasons::TRANSACTION_PAYMENT); + for event in events() { + match event { + RuntimeEvent::Balances(crate::Event::Locked { .. }) => { + assert!(false, "unexpected lock event") + }, + RuntimeEvent::Balances(crate::Event::Unlocked { .. }) => { + assert!(false, "unexpected unlock event") + }, + _ => continue, + } + } + + // Locks = [17, 20] --> [17, 15] + Balances::set_lock(*b"LOCK_001", &1, 15, WithdrawReasons::TRANSFER); + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Unlocked { who: 1, amount: 3 })] + ); + + // Locks = [17, 15] --> [15] + Balances::remove_lock(*b"LOCK_000", &1); + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Unlocked { who: 1, amount: 2 })] + ); + + // Locks = [15] --> [] + Balances::remove_lock(*b"LOCK_001", &1); + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Unlocked { who: 1, amount: 15 })] + ); + }); +} + +#[test] +fn emit_events_with_existential_deposit() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 100)); + + assert_eq!( + events(), + [ + RuntimeEvent::System(system::Event::NewAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }), + RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100 }), + ] + ); + + let res = Balances::slash(&1, 1); + assert_eq!(res, (NegativeImbalance::new(1), 0)); + + assert_eq!( + events(), + [ + RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::DustLost { account: 1, amount: 99 }), + RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 1 }), + ] + ); + }); +} + +#[test] +fn emit_events_with_no_existential_deposit_suicide() { + ExtBuilder::default().existential_deposit(1).build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 100); + + assert_eq!( + events(), + [ + RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100 }), + RuntimeEvent::System(system::Event::NewAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }), + ] + ); + + let res = Balances::slash(&1, 100); + assert_eq!(res, (NegativeImbalance::new(100), 0)); + + assert_eq!( + events(), + [ + RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 100 }), + ] + ); + }); +} + +#[test] +fn slash_over_works() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + // SCENARIO: Over-slash will kill account, and report missing slash amount. + Balances::make_free_balance_be(&1, 1_000); + // Slashed full free_balance, and reports 300 not slashed + assert_eq!(Balances::slash(&1, 1_300), (NegativeImbalance::new(1000), 300)); + // Account is dead + assert!(!System::account_exists(&1)); + }); +} + +#[test] +fn slash_full_works() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + // Slashed completed in full + assert_eq!(Balances::slash(&1, 1_000), (NegativeImbalance::new(1000), 0)); + // Account is still alive + assert!(!System::account_exists(&1)); + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Slashed { + who: 1, + amount: 1000, + })); + }); +} + +#[test] +fn slash_partial_works() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + // Slashed completed in full + assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(900), 0)); + // Account is still alive + assert!(System::account_exists(&1)); + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Slashed { + who: 1, + amount: 900, + })); + }); +} + +#[test] +fn slash_dusting_works() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + // Slashed completed in full + assert_eq!(Balances::slash(&1, 950), (NegativeImbalance::new(950), 0)); + assert!(!System::account_exists(&1)); + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Slashed { + who: 1, + amount: 950, + })); + }); +} + +#[test] +fn slash_does_not_take_from_reserve() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + assert_ok!(Balances::reserve(&1, 100)); + // Slashed completed in full + assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(800), 100)); + assert_eq!(Balances::reserved_balance(&1), 100); + System::assert_last_event(RuntimeEvent::Balances(crate::Event::Slashed { + who: 1, + amount: 800, + })); + }); +} + +#[test] +fn slash_consumed_slash_full_works() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests + // Slashed completed in full + assert_eq!(Balances::slash(&1, 900), (NegativeImbalance::new(900), 0)); + // Account is still alive + assert!(System::account_exists(&1)); + }); +} + +#[test] +fn slash_consumed_slash_over_works() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests + // Slashed completed in full + assert_eq!(Balances::slash(&1, 1_000), (NegativeImbalance::new(900), 100)); + // Account is still alive + assert!(System::account_exists(&1)); + }); +} + +#[test] +fn slash_consumed_slash_partial_works() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + assert_ok!(System::inc_consumers(&1)); // <-- Reference counter added here is enough for all tests + // Slashed completed in full + assert_eq!(Balances::slash(&1, 800), (NegativeImbalance::new(800), 0)); + // Account is still alive + assert!(System::account_exists(&1)); + }); +} + +#[test] +fn slash_on_non_existant_works() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + // Slash on non-existent account is okay. + assert_eq!(Balances::slash(&12345, 1_300), (NegativeImbalance::new(0), 1300)); + }); +} + +#[test] +fn slash_reserved_slash_partial_works() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + assert_ok!(Balances::reserve(&1, 900)); + // Slashed completed in full + assert_eq!(Balances::slash_reserved(&1, 800), (NegativeImbalance::new(800), 0)); + assert_eq!(System::consumers(&1), 1); + assert_eq!(Balances::reserved_balance(&1), 100); + assert_eq!(Balances::free_balance(&1), 100); + }); +} + +#[test] +fn slash_reserved_slash_everything_works() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + Balances::make_free_balance_be(&1, 1_000); + assert_ok!(Balances::reserve(&1, 900)); + assert_eq!(System::consumers(&1), 1); + // Slashed completed in full + assert_eq!(Balances::slash_reserved(&1, 900), (NegativeImbalance::new(900), 0)); + assert_eq!(System::consumers(&1), 0); + // Account is still alive + assert!(System::account_exists(&1)); + }); +} + +#[test] +fn slash_reserved_overslash_does_not_touch_free_balance() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + // SCENARIO: Over-slash doesn't touch free balance. + Balances::make_free_balance_be(&1, 1_000); + assert_ok!(Balances::reserve(&1, 800)); + // Slashed done + assert_eq!(Balances::slash_reserved(&1, 900), (NegativeImbalance::new(800), 100)); + assert_eq!(Balances::free_balance(&1), 200); + }); +} + +#[test] +fn slash_reserved_on_non_existant_works() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + // Slash on non-existent account is okay. + assert_eq!(Balances::slash_reserved(&12345, 1_300), (NegativeImbalance::new(0), 1300)); + }); +} + +#[test] +fn operations_on_dead_account_should_not_change_state() { + // These functions all use `mutate_account` which may introduce a storage change when + // the account never existed to begin with, and shouldn't exist in the end. + ExtBuilder::default().existential_deposit(1).build_and_execute_with(|| { + assert!(!frame_system::Account::::contains_key(&1337)); + + // Unreserve + assert_storage_noop!(assert_eq!(Balances::unreserve(&1337, 42), 42)); + // Reserve + assert_noop!(Balances::reserve(&1337, 42), Error::::InsufficientBalance); + // Slash Reserve + assert_storage_noop!(assert_eq!(Balances::slash_reserved(&1337, 42).1, 42)); + // Repatriate Reserve + assert_noop!( + Balances::repatriate_reserved(&1337, &1338, 42, Free), + Error::::DeadAccount + ); + // Slash + assert_storage_noop!(assert_eq!(Balances::slash(&1337, 42).1, 42)); + }); +} + +#[test] +#[should_panic = "The existential deposit must be greater than zero!"] +fn zero_ed_is_prohibited() { + // These functions all use `mutate_account` which may introduce a storage change when + // the account never existed to begin with, and shouldn't exist in the end. + ExtBuilder::default().existential_deposit(0).build_and_execute_with(|| { + Balances::integrity_test(); + }); +} + +#[test] +fn named_reserve_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + + let id_1 = TestId::Foo; + let id_2 = TestId::Bar; + let id_3 = TestId::Baz; + + // reserve + + assert_noop!( + Balances::reserve_named(&id_1, &1, 112), + Error::::InsufficientBalance + ); + + assert_ok!(Balances::reserve_named(&id_1, &1, 12)); + + assert_eq!(Balances::reserved_balance(1), 12); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 12); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 0); + + assert_ok!(Balances::reserve_named(&id_1, &1, 2)); + + assert_eq!(Balances::reserved_balance(1), 14); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 14); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 0); + + assert_ok!(Balances::reserve_named(&id_2, &1, 23)); + + assert_eq!(Balances::reserved_balance(1), 37); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 14); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23); + + assert_ok!(Balances::reserve(&1, 34)); + + assert_eq!(Balances::reserved_balance(1), 71); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 14); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23); + + assert_eq!(Balances::total_balance(&1), 111); + assert_eq!(Balances::free_balance(1), 40); + + assert_noop!(Balances::reserve_named(&id_3, &1, 2), Error::::TooManyReserves); + + // unreserve + + assert_eq!(Balances::unreserve_named(&id_1, &1, 10), 0); + + assert_eq!(Balances::reserved_balance(1), 61); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 4); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23); + + assert_eq!(Balances::unreserve_named(&id_1, &1, 5), 1); + + assert_eq!(Balances::reserved_balance(1), 57); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 23); + + assert_eq!(Balances::unreserve_named(&id_2, &1, 3), 0); + + assert_eq!(Balances::reserved_balance(1), 54); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 20); + + assert_eq!(Balances::total_balance(&1), 111); + assert_eq!(Balances::free_balance(1), 57); + + // slash_reserved_named + + assert_ok!(Balances::reserve_named(&id_1, &1, 10)); + + assert_eq!(Balances::slash_reserved_named(&id_1, &1, 25).1, 15); + + assert_eq!(Balances::reserved_balance(1), 54); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 20); + assert_eq!(Balances::total_balance(&1), 101); + + assert_eq!(Balances::slash_reserved_named(&id_2, &1, 5).1, 0); + + assert_eq!(Balances::reserved_balance(1), 49); + assert_eq!(Balances::reserved_balance_named(&id_1, &1), 0); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 15); + assert_eq!(Balances::total_balance(&1), 96); + + // repatriate_reserved_named + + let _ = Balances::deposit_creating(&2, 100); + + assert_eq!(Balances::repatriate_reserved_named(&id_2, &1, &2, 10, Reserved).unwrap(), 0); + + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 5); + assert_eq!(Balances::reserved_balance_named(&id_2, &2), 10); + assert_eq!(Balances::reserved_balance(&2), 10); + + assert_eq!(Balances::repatriate_reserved_named(&id_2, &2, &1, 11, Reserved).unwrap(), 1); + + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 15); + assert_eq!(Balances::reserved_balance_named(&id_2, &2), 0); + assert_eq!(Balances::reserved_balance(&2), 0); + + assert_eq!(Balances::repatriate_reserved_named(&id_2, &1, &2, 10, Free).unwrap(), 0); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 5); + assert_eq!(Balances::reserved_balance_named(&id_2, &2), 0); + assert_eq!(Balances::free_balance(&2), 110); + + // repatriate_reserved_named to self + + assert_eq!(Balances::repatriate_reserved_named(&id_2, &1, &1, 10, Reserved).unwrap(), 5); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 5); + + assert_eq!(Balances::free_balance(&1), 47); + + assert_eq!(Balances::repatriate_reserved_named(&id_2, &1, &1, 15, Free).unwrap(), 10); + assert_eq!(Balances::reserved_balance_named(&id_2, &1), 0); + + assert_eq!(Balances::free_balance(&1), 52); + }); +} + +#[test] +fn reserve_must_succeed_if_can_reserve_does() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 1); + let _ = Balances::deposit_creating(&2, 2); + assert!(Balances::can_reserve(&1, 1) == Balances::reserve(&1, 1).is_ok()); + assert!(Balances::can_reserve(&2, 1) == Balances::reserve(&2, 1).is_ok()); + }); +} + +#[test] +fn reserved_named_to_yourself_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 110); + + let id = TestId::Foo; + + assert_ok!(Balances::reserve_named(&id, &1, 50)); + assert_ok!(Balances::repatriate_reserved_named(&id, &1, &1, 50, Free), 0); + assert_eq!(Balances::free_balance(1), 110); + assert_eq!(Balances::reserved_balance_named(&id, &1), 0); + + assert_ok!(Balances::reserve_named(&id, &1, 50)); + assert_ok!(Balances::repatriate_reserved_named(&id, &1, &1, 60, Free), 10); + assert_eq!(Balances::free_balance(1), 110); + assert_eq!(Balances::reserved_balance_named(&id, &1), 0); + }); +} + +#[test] +fn ensure_reserved_named_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + + let id = TestId::Foo; + + assert_ok!(Balances::ensure_reserved_named(&id, &1, 15)); + assert_eq!(Balances::reserved_balance_named(&id, &1), 15); + + assert_ok!(Balances::ensure_reserved_named(&id, &1, 10)); + assert_eq!(Balances::reserved_balance_named(&id, &1), 10); + + assert_ok!(Balances::ensure_reserved_named(&id, &1, 20)); + assert_eq!(Balances::reserved_balance_named(&id, &1), 20); + }); +} + +#[test] +fn unreserve_all_named_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + + let id = TestId::Foo; + + assert_ok!(Balances::reserve_named(&id, &1, 15)); + + assert_eq!(Balances::unreserve_all_named(&id, &1), 15); + assert_eq!(Balances::reserved_balance_named(&id, &1), 0); + assert_eq!(Balances::free_balance(&1), 111); + + assert_eq!(Balances::unreserve_all_named(&id, &1), 0); + }); +} + +#[test] +fn slash_all_reserved_named_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + + let id = TestId::Foo; + + assert_ok!(Balances::reserve_named(&id, &1, 15)); + + assert_eq!(Balances::slash_all_reserved_named(&id, &1).peek(), 15); + assert_eq!(Balances::reserved_balance_named(&id, &1), 0); + assert_eq!(Balances::free_balance(&1), 96); + + assert_eq!(Balances::slash_all_reserved_named(&id, &1).peek(), 0); + }); +} + +#[test] +fn repatriate_all_reserved_named_should_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::deposit_creating(&1, 111); + let _ = Balances::deposit_creating(&2, 10); + let _ = Balances::deposit_creating(&3, 10); + + let id = TestId::Foo; + + assert_ok!(Balances::reserve_named(&id, &1, 15)); + + assert_ok!(Balances::repatriate_all_reserved_named(&id, &1, &2, Reserved)); + assert_eq!(Balances::reserved_balance_named(&id, &1), 0); + assert_eq!(Balances::reserved_balance_named(&id, &2), 15); + + assert_ok!(Balances::repatriate_all_reserved_named(&id, &2, &3, Free)); + assert_eq!(Balances::reserved_balance_named(&id, &2), 0); + assert_eq!(Balances::free_balance(&3), 25); + }); +} + +#[test] +fn freezing_and_locking_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(>::set_freeze(&TestId::Foo, &1, 4)); + Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); + assert_eq!(System::consumers(&1), 2); + assert_eq!(Balances::account(&1).frozen, 5); + assert_ok!(>::set_freeze(&TestId::Foo, &1, 6)); + assert_eq!(Balances::account(&1).frozen, 6); + assert_ok!(>::set_freeze(&TestId::Foo, &1, 4)); + assert_eq!(Balances::account(&1).frozen, 5); + Balances::set_lock(ID_1, &1, 3, WithdrawReasons::all()); + assert_eq!(Balances::account(&1).frozen, 4); + Balances::set_lock(ID_1, &1, 5, WithdrawReasons::all()); + assert_eq!(Balances::account(&1).frozen, 5); + Balances::remove_lock(ID_1, &1); + assert_eq!(Balances::account(&1).frozen, 4); + assert_eq!(System::consumers(&1), 1); + }); +} diff --git a/frame/balances/src/tests/dispatchable_tests.rs b/frame/balances/src/tests/dispatchable_tests.rs new file mode 100644 index 000000000000..76d0961e577d --- /dev/null +++ b/frame/balances/src/tests/dispatchable_tests.rs @@ -0,0 +1,224 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Tests regarding the functionality of the dispatchables/extrinsics. + +use super::*; +use frame_support::traits::tokens::Preservation::Expendable; +use fungible::{hold::Mutate as HoldMutate, Inspect, Mutate}; + +#[test] +fn default_indexing_on_new_accounts_should_not_work2() { + ExtBuilder::default() + .existential_deposit(10) + .monied(true) + .build_and_execute_with(|| { + // account 5 should not exist + // ext_deposit is 10, value is 9, not satisfies for ext_deposit + assert_noop!( + Balances::transfer_allow_death(Some(1).into(), 5, 9), + TokenError::BelowMinimum, + ); + assert_eq!(Balances::free_balance(1), 100); + }); +} + +#[test] +fn dust_account_removal_should_work() { + ExtBuilder::default() + .existential_deposit(100) + .monied(true) + .build_and_execute_with(|| { + System::inc_account_nonce(&2); + assert_eq!(System::account_nonce(&2), 1); + assert_eq!(Balances::total_balance(&2), 2000); + // index 1 (account 2) becomes zombie + assert_ok!(Balances::transfer_allow_death(Some(2).into(), 5, 1901)); + assert_eq!(Balances::total_balance(&2), 0); + assert_eq!(Balances::total_balance(&5), 1901); + assert_eq!(System::account_nonce(&2), 0); + }); +} + +#[test] +fn balance_transfer_works() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::mint_into(&1, 111); + assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 69)); + assert_eq!(Balances::total_balance(&1), 42); + assert_eq!(Balances::total_balance(&2), 69); + }); +} + +#[test] +fn force_transfer_works() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::mint_into(&1, 111); + assert_noop!(Balances::force_transfer(Some(2).into(), 1, 2, 69), BadOrigin,); + assert_ok!(Balances::force_transfer(RawOrigin::Root.into(), 1, 2, 69)); + assert_eq!(Balances::total_balance(&1), 42); + assert_eq!(Balances::total_balance(&2), 69); + }); +} + +#[test] +fn balance_transfer_when_on_hold_should_not_work() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::mint_into(&1, 111); + assert_ok!(Balances::hold(&TestId::Foo, &1, 69)); + assert_noop!( + Balances::transfer_allow_death(Some(1).into(), 2, 69), + TokenError::FundsUnavailable, + ); + }); +} + +#[test] +fn transfer_keep_alive_works() { + ExtBuilder::default().existential_deposit(1).build_and_execute_with(|| { + let _ = Balances::mint_into(&1, 100); + assert_noop!( + Balances::transfer_keep_alive(Some(1).into(), 2, 100), + TokenError::NotExpendable + ); + assert_eq!(Balances::total_balance(&1), 100); + assert_eq!(Balances::total_balance(&2), 0); + }); +} + +#[test] +fn transfer_keep_alive_all_free_succeed() { + ExtBuilder::default().existential_deposit(100).build_and_execute_with(|| { + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 300)); + assert_ok!(Balances::hold(&TestId::Foo, &1, 100)); + assert_ok!(Balances::transfer_keep_alive(Some(1).into(), 2, 100)); + assert_eq!(Balances::total_balance(&1), 200); + assert_eq!(Balances::total_balance(&2), 100); + }); +} + +#[test] +fn transfer_all_works_1() { + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + // setup + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 200)); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0)); + // transfer all and allow death + assert_ok!(Balances::transfer_all(Some(1).into(), 2, false)); + assert_eq!(Balances::total_balance(&1), 0); + assert_eq!(Balances::total_balance(&2), 200); + }); +} + +#[test] +fn transfer_all_works_2() { + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + // setup + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 200)); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0)); + // transfer all and keep alive + assert_ok!(Balances::transfer_all(Some(1).into(), 2, true)); + assert_eq!(Balances::total_balance(&1), 100); + assert_eq!(Balances::total_balance(&2), 100); + }); +} + +#[test] +fn transfer_all_works_3() { + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + // setup + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 210)); + assert_ok!(Balances::hold(&TestId::Foo, &1, 10)); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0)); + // transfer all and allow death w/ reserved + assert_ok!(Balances::transfer_all(Some(1).into(), 2, false)); + assert_eq!(Balances::total_balance(&1), 110); + assert_eq!(Balances::total_balance(&2), 100); + }); +} + +#[test] +fn transfer_all_works_4() { + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + // setup + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1, 210)); + assert_ok!(Balances::hold(&TestId::Foo, &1, 10)); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 0)); + // transfer all and keep alive w/ reserved + assert_ok!(Balances::transfer_all(Some(1).into(), 2, true)); + assert_eq!(Balances::total_balance(&1), 110); + assert_eq!(Balances::total_balance(&2), 100); + }); +} + +#[test] +fn set_balance_handles_killing_account() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::mint_into(&1, 111); + assert_ok!(frame_system::Pallet::::inc_consumers(&1)); + assert_noop!( + Balances::force_set_balance(RuntimeOrigin::root(), 1, 0), + DispatchError::ConsumerRemaining, + ); + }); +} + +#[test] +fn set_balance_handles_total_issuance() { + ExtBuilder::default().build_and_execute_with(|| { + let old_total_issuance = Balances::total_issuance(); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 1337, 69)); + assert_eq!(Balances::total_issuance(), old_total_issuance + 69); + assert_eq!(Balances::total_balance(&1337), 69); + assert_eq!(Balances::free_balance(&1337), 69); + }); +} + +#[test] +fn upgrade_accounts_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + System::inc_providers(&7); + assert_ok!(::AccountStore::try_mutate_exists( + &7, + |a| -> DispatchResult { + *a = Some(AccountData { + free: 5, + reserved: 5, + frozen: Zero::zero(), + flags: crate::types::ExtraFlags::old_logic(), + }); + Ok(()) + } + )); + assert!(!Balances::account(&7).flags.is_new_logic()); + assert_eq!(System::providers(&7), 1); + assert_eq!(System::consumers(&7), 0); + assert_ok!(Balances::upgrade_accounts(Some(1).into(), vec![7])); + assert!(Balances::account(&7).flags.is_new_logic()); + assert_eq!(System::providers(&7), 1); + assert_eq!(System::consumers(&7), 1); + + >::unreserve(&7, 5); + assert_ok!(>::transfer(&7, &1, 10, Expendable)); + assert_eq!(Balances::total_balance(&7), 0); + assert_eq!(System::providers(&7), 0); + assert_eq!(System::consumers(&7), 0); + }); +} diff --git a/frame/balances/src/tests/fungible_conformance_tests.rs b/frame/balances/src/tests/fungible_conformance_tests.rs new file mode 100644 index 000000000000..6262aa04dc08 --- /dev/null +++ b/frame/balances/src/tests/fungible_conformance_tests.rs @@ -0,0 +1,88 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use frame_support::traits::fungible::{conformance_tests, Inspect, Mutate}; +use paste::paste; + +macro_rules! run_tests { + ($path:path, $ext_deposit:expr, $($name:ident),*) => { + $( + paste! { + #[test] + fn [< $name _existential_deposit_ $ext_deposit _dust_trap_on >]() { + let trap_account = ::AccountId::from(65174286u64); + let builder = ExtBuilder::default().existential_deposit($ext_deposit).dust_trap(trap_account); + builder.build_and_execute_with(|| { + Balances::set_balance(&trap_account, Balances::minimum_balance()); + $path::$name::< + Balances, + ::AccountId, + >(Some(trap_account)); + }); + } + + #[test] + fn [< $name _existential_deposit_ $ext_deposit _dust_trap_off >]() { + let builder = ExtBuilder::default().existential_deposit($ext_deposit); + builder.build_and_execute_with(|| { + $path::$name::< + Balances, + ::AccountId, + >(None); + }); + } + } + )* + }; + ($path:path, $ext_deposit:expr) => { + run_tests!( + $path, + $ext_deposit, + mint_into_success, + mint_into_overflow, + mint_into_below_minimum, + burn_from_exact_success, + burn_from_best_effort_success, + burn_from_exact_insufficient_funds, + restore_success, + restore_overflow, + restore_below_minimum, + shelve_success, + shelve_insufficient_funds, + transfer_success, + transfer_expendable_all, + transfer_expendable_dust, + transfer_protect_preserve, + set_balance_mint_success, + set_balance_burn_success, + can_deposit_success, + can_deposit_below_minimum, + can_deposit_overflow, + can_withdraw_success, + can_withdraw_reduced_to_zero, + can_withdraw_balance_low, + reducible_balance_expendable, + reducible_balance_protect_preserve + ); + }; +} + +run_tests!(conformance_tests::inspect_mutate, 1); +run_tests!(conformance_tests::inspect_mutate, 2); +run_tests!(conformance_tests::inspect_mutate, 5); +run_tests!(conformance_tests::inspect_mutate, 1000); diff --git a/frame/balances/src/tests/fungible_tests.rs b/frame/balances/src/tests/fungible_tests.rs new file mode 100644 index 000000000000..ab2606c53ff7 --- /dev/null +++ b/frame/balances/src/tests/fungible_tests.rs @@ -0,0 +1,470 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Tests regarding the functionality of the `fungible` trait set implementations. + +use super::*; +use frame_support::traits::tokens::{ + Fortitude::{Force, Polite}, + Precision::{BestEffort, Exact}, + Preservation::{Expendable, Preserve, Protect}, + Restriction::Free, +}; +use fungible::{Inspect, InspectFreeze, InspectHold, Mutate, MutateFreeze, MutateHold, Unbalanced}; + +#[test] +fn inspect_trait_reducible_balance_basic_works() { + ExtBuilder::default().existential_deposit(10).build_and_execute_with(|| { + Balances::set_balance(&1, 100); + assert_eq!(Balances::reducible_balance(&1, Expendable, Polite), 100); + assert_eq!(Balances::reducible_balance(&1, Protect, Polite), 90); + assert_eq!(Balances::reducible_balance(&1, Preserve, Polite), 90); + assert_eq!(Balances::reducible_balance(&1, Expendable, Force), 100); + assert_eq!(Balances::reducible_balance(&1, Protect, Force), 90); + assert_eq!(Balances::reducible_balance(&1, Preserve, Force), 90); + }); +} + +#[test] +fn inspect_trait_reducible_balance_other_provide_works() { + ExtBuilder::default().existential_deposit(10).build_and_execute_with(|| { + Balances::set_balance(&1, 100); + System::inc_providers(&1); + assert_eq!(Balances::reducible_balance(&1, Expendable, Polite), 100); + assert_eq!(Balances::reducible_balance(&1, Protect, Polite), 100); + assert_eq!(Balances::reducible_balance(&1, Preserve, Polite), 90); + assert_eq!(Balances::reducible_balance(&1, Expendable, Force), 100); + assert_eq!(Balances::reducible_balance(&1, Protect, Force), 100); + assert_eq!(Balances::reducible_balance(&1, Preserve, Force), 90); + }); +} + +#[test] +fn inspect_trait_reducible_balance_frozen_works() { + ExtBuilder::default().existential_deposit(10).build_and_execute_with(|| { + Balances::set_balance(&1, 100); + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 50)); + assert_eq!(Balances::reducible_balance(&1, Expendable, Polite), 50); + assert_eq!(Balances::reducible_balance(&1, Protect, Polite), 50); + assert_eq!(Balances::reducible_balance(&1, Preserve, Polite), 50); + assert_eq!(Balances::reducible_balance(&1, Expendable, Force), 90); + assert_eq!(Balances::reducible_balance(&1, Protect, Force), 90); + assert_eq!(Balances::reducible_balance(&1, Preserve, Force), 90); + }); +} + +#[test] +fn unbalanced_trait_set_balance_works() { + ExtBuilder::default().build_and_execute_with(|| { + assert_eq!(>::balance(&1337), 0); + assert_ok!(Balances::write_balance(&1337, 100)); + assert_eq!(>::balance(&1337), 100); + + assert_ok!(>::hold(&TestId::Foo, &1337, 60)); + assert_eq!(>::balance(&1337), 40); + assert_eq!(>::total_balance_on_hold(&1337), 60); + assert_eq!( + >::balance_on_hold(&TestId::Foo, &1337), + 60 + ); + + assert_noop!(Balances::write_balance(&1337, 0), Error::::InsufficientBalance); + + assert_ok!(Balances::write_balance(&1337, 1)); + assert_eq!(>::balance(&1337), 1); + assert_eq!( + >::balance_on_hold(&TestId::Foo, &1337), + 60 + ); + + assert_ok!(>::release(&TestId::Foo, &1337, 60, Exact)); + assert_eq!(>::balance_on_hold(&TestId::Foo, &1337), 0); + assert_eq!(>::total_balance_on_hold(&1337), 0); + }); +} + +#[test] +fn unbalanced_trait_set_total_issuance_works() { + ExtBuilder::default().build_and_execute_with(|| { + assert_eq!(>::total_issuance(), 0); + Balances::set_total_issuance(100); + assert_eq!(>::total_issuance(), 100); + }); +} + +#[test] +fn unbalanced_trait_decrease_balance_simple_works() { + ExtBuilder::default().build_and_execute_with(|| { + // An Account that starts at 100 + assert_ok!(Balances::write_balance(&1337, 100)); + assert_eq!(>::balance(&1337), 100); + // and reserves 50 + assert_ok!(>::hold(&TestId::Foo, &1337, 50)); + assert_eq!(>::balance(&1337), 50); + // and is decreased by 20 + assert_ok!(Balances::decrease_balance(&1337, 20, Exact, Expendable, Polite)); + assert_eq!(>::balance(&1337), 30); + }); +} + +#[test] +fn unbalanced_trait_decrease_balance_works_1() { + ExtBuilder::default().build_and_execute_with(|| { + assert_ok!(Balances::write_balance(&1337, 100)); + assert_eq!(>::balance(&1337), 100); + + assert_noop!( + Balances::decrease_balance(&1337, 101, Exact, Expendable, Polite), + TokenError::FundsUnavailable + ); + assert_eq!(Balances::decrease_balance(&1337, 100, Exact, Expendable, Polite), Ok(100)); + assert_eq!(>::balance(&1337), 0); + }); +} + +#[test] +fn unbalanced_trait_decrease_balance_works_2() { + ExtBuilder::default().build_and_execute_with(|| { + // free: 40, reserved: 60 + assert_ok!(Balances::write_balance(&1337, 100)); + assert_ok!(Balances::hold(&TestId::Foo, &1337, 60)); + assert_eq!(>::balance(&1337), 40); + assert_eq!(Balances::total_balance_on_hold(&1337), 60); + assert_noop!( + Balances::decrease_balance(&1337, 40, Exact, Expendable, Polite), + Error::::InsufficientBalance + ); + assert_eq!(Balances::decrease_balance(&1337, 39, Exact, Expendable, Polite), Ok(39)); + assert_eq!(>::balance(&1337), 1); + assert_eq!(Balances::total_balance_on_hold(&1337), 60); + }); +} + +#[test] +fn unbalanced_trait_decrease_balance_at_most_works_1() { + ExtBuilder::default().build_and_execute_with(|| { + assert_ok!(Balances::write_balance(&1337, 100)); + assert_eq!(>::balance(&1337), 100); + + assert_eq!(Balances::decrease_balance(&1337, 101, BestEffort, Expendable, Polite), Ok(100)); + assert_eq!(>::balance(&1337), 0); + }); +} + +#[test] +fn unbalanced_trait_decrease_balance_at_most_works_2() { + ExtBuilder::default().build_and_execute_with(|| { + assert_ok!(Balances::write_balance(&1337, 99)); + assert_eq!(Balances::decrease_balance(&1337, 99, BestEffort, Expendable, Polite), Ok(99)); + assert_eq!(>::balance(&1337), 0); + }); +} + +#[test] +fn unbalanced_trait_decrease_balance_at_most_works_3() { + ExtBuilder::default().build_and_execute_with(|| { + // free: 40, reserved: 60 + assert_ok!(Balances::write_balance(&1337, 100)); + assert_ok!(Balances::hold(&TestId::Foo, &1337, 60)); + assert_eq!(Balances::free_balance(1337), 40); + assert_eq!(Balances::total_balance_on_hold(&1337), 60); + assert_eq!(Balances::decrease_balance(&1337, 0, BestEffort, Expendable, Polite), Ok(0)); + assert_eq!(Balances::free_balance(1337), 40); + assert_eq!(Balances::total_balance_on_hold(&1337), 60); + assert_eq!(Balances::decrease_balance(&1337, 10, BestEffort, Expendable, Polite), Ok(10)); + assert_eq!(Balances::free_balance(1337), 30); + assert_eq!(Balances::decrease_balance(&1337, 200, BestEffort, Expendable, Polite), Ok(29)); + assert_eq!(>::balance(&1337), 1); + assert_eq!(Balances::free_balance(1337), 1); + assert_eq!(Balances::total_balance_on_hold(&1337), 60); + }); +} + +#[test] +fn unbalanced_trait_increase_balance_works() { + ExtBuilder::default().build_and_execute_with(|| { + assert_noop!(Balances::increase_balance(&1337, 0, Exact), TokenError::BelowMinimum); + assert_eq!(Balances::increase_balance(&1337, 1, Exact), Ok(1)); + assert_noop!(Balances::increase_balance(&1337, u64::MAX, Exact), ArithmeticError::Overflow); + }); +} + +#[test] +fn unbalanced_trait_increase_balance_at_most_works() { + ExtBuilder::default().build_and_execute_with(|| { + assert_eq!(Balances::increase_balance(&1337, 0, BestEffort), Ok(0)); + assert_eq!(Balances::increase_balance(&1337, 1, BestEffort), Ok(1)); + assert_eq!(Balances::increase_balance(&1337, u64::MAX, BestEffort), Ok(u64::MAX - 1)); + }); +} + +#[test] +fn freezing_and_holds_should_overlap() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10)); + assert_ok!(Balances::hold(&TestId::Foo, &1, 9)); + assert_eq!(Balances::account(&1).free, 1); + assert_eq!(System::consumers(&1), 1); + assert_eq!(Balances::account(&1).free, 1); + assert_eq!(Balances::account(&1).frozen, 10); + assert_eq!(Balances::account(&1).reserved, 9); + assert_eq!(Balances::total_balance_on_hold(&1), 9); + }); +} + +#[test] +fn frozen_hold_balance_cannot_be_moved_without_force() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10)); + assert_ok!(Balances::hold(&TestId::Foo, &1, 9)); + assert_eq!(Balances::reducible_total_balance_on_hold(&1, Force), 9); + assert_eq!(Balances::reducible_total_balance_on_hold(&1, Polite), 0); + let e = TokenError::Frozen; + assert_noop!( + Balances::transfer_on_hold(&TestId::Foo, &1, &2, 1, Exact, Free, Polite), + e + ); + assert_ok!(Balances::transfer_on_hold(&TestId::Foo, &1, &2, 1, Exact, Free, Force)); + }); +} + +#[test] +fn frozen_hold_balance_best_effort_transfer_works() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5)); + assert_ok!(Balances::hold(&TestId::Foo, &1, 9)); + assert_eq!(Balances::reducible_total_balance_on_hold(&1, Force), 9); + assert_eq!(Balances::reducible_total_balance_on_hold(&1, Polite), 5); + assert_ok!(Balances::transfer_on_hold( + &TestId::Foo, + &1, + &2, + 10, + BestEffort, + Free, + Polite + )); + assert_eq!(Balances::total_balance(&1), 5); + assert_eq!(Balances::total_balance(&2), 25); + }); +} + +#[test] +fn partial_freezing_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5)); + assert_eq!(System::consumers(&1), 1); + assert_ok!(>::transfer(&1, &2, 5, Expendable)); + assert_noop!( + >::transfer(&1, &2, 1, Expendable), + TokenError::Frozen + ); + }); +} + +#[test] +fn thaw_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, u64::MAX)); + assert_ok!(Balances::thaw(&TestId::Foo, &1)); + assert_eq!(System::consumers(&1), 0); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &1), 0); + assert_eq!(Balances::account(&1).frozen, 0); + assert_ok!(>::transfer(&1, &2, 10, Expendable)); + }); +} + +#[test] +fn set_freeze_zero_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, u64::MAX)); + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 0)); + assert_eq!(System::consumers(&1), 0); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &1), 0); + assert_eq!(Balances::account(&1).frozen, 0); + assert_ok!(>::transfer(&1, &2, 10, Expendable)); + }); +} + +#[test] +fn set_freeze_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, u64::MAX)); + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5)); + assert_ok!(>::transfer(&1, &2, 5, Expendable)); + assert_noop!( + >::transfer(&1, &2, 1, Expendable), + TokenError::Frozen + ); + }); +} + +#[test] +fn extend_freeze_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5)); + assert_ok!(Balances::extend_freeze(&TestId::Foo, &1, 10)); + assert_eq!(Balances::account(&1).frozen, 10); + assert_eq!(Balances::balance_frozen(&TestId::Foo, &1), 10); + assert_noop!( + >::transfer(&1, &2, 1, Expendable), + TokenError::Frozen + ); + }); +} + +#[test] +fn double_freezing_should_work() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 5)); + assert_ok!(Balances::set_freeze(&TestId::Bar, &1, 5)); + assert_eq!(System::consumers(&1), 1); + assert_ok!(>::transfer(&1, &2, 5, Expendable)); + assert_noop!( + >::transfer(&1, &2, 1, Expendable), + TokenError::Frozen + ); + }); +} + +#[test] +fn can_hold_entire_balance_when_second_provider() { + ExtBuilder::default() + .existential_deposit(1) + .monied(false) + .build_and_execute_with(|| { + >::set_balance(&1, 100); + assert_noop!(Balances::hold(&TestId::Foo, &1, 100), TokenError::FundsUnavailable); + System::inc_providers(&1); + assert_eq!(System::providers(&1), 2); + assert_ok!(Balances::hold(&TestId::Foo, &1, 100)); + assert_eq!(System::providers(&1), 1); + assert_noop!(System::dec_providers(&1), DispatchError::ConsumerRemaining); + }); +} + +#[test] +fn unholding_frees_hold_slot() { + ExtBuilder::default() + .existential_deposit(1) + .monied(false) + .build_and_execute_with(|| { + >::set_balance(&1, 100); + assert_ok!(Balances::hold(&TestId::Foo, &1, 10)); + assert_ok!(Balances::hold(&TestId::Bar, &1, 10)); + assert_ok!(Balances::release(&TestId::Foo, &1, 10, Exact)); + assert_ok!(Balances::hold(&TestId::Baz, &1, 10)); + }); +} + +#[test] +fn sufficients_work_properly_with_reference_counting() { + ExtBuilder::default() + .existential_deposit(1) + .monied(true) + .build_and_execute_with(|| { + // Only run PoC when the system pallet is enabled, since the underlying bug is in the + // system pallet it won't work with BalancesAccountStore + if UseSystem::get() { + // Start with a balance of 100 + >::set_balance(&1, 100); + // Emulate a sufficient, in reality this could be reached by transferring a + // sufficient asset to the account + System::inc_sufficients(&1); + // Spend the same balance multiple times + assert_ok!(>::transfer(&1, &1337, 100, Expendable)); + assert_eq!(Balances::free_balance(&1), 0); + assert_noop!( + >::transfer(&1, &1337, 100, Expendable), + TokenError::FundsUnavailable + ); + } + }); +} + +#[test] +fn emit_events_with_changing_freezes() { + ExtBuilder::default().build_and_execute_with(|| { + let _ = Balances::set_balance(&1, 100); + System::reset_events(); + + // Freeze = [] --> [10] + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 10)); + assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Frozen { who: 1, amount: 10 })]); + + // Freeze = [10] --> [15] + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 15)); + assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Frozen { who: 1, amount: 5 })]); + + // Freeze = [15] --> [15, 20] + assert_ok!(Balances::set_freeze(&TestId::Bar, &1, 20)); + assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Frozen { who: 1, amount: 5 })]); + + // Freeze = [15, 20] --> [17, 20] + assert_ok!(Balances::set_freeze(&TestId::Foo, &1, 17)); + for event in events() { + match event { + RuntimeEvent::Balances(crate::Event::Frozen { .. }) => { + assert!(false, "unexpected freeze event") + }, + RuntimeEvent::Balances(crate::Event::Thawed { .. }) => { + assert!(false, "unexpected thaw event") + }, + _ => continue, + } + } + + // Freeze = [17, 20] --> [17, 15] + assert_ok!(Balances::set_freeze(&TestId::Bar, &1, 15)); + assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Thawed { who: 1, amount: 3 })]); + + // Freeze = [17, 15] --> [15] + assert_ok!(Balances::thaw(&TestId::Foo, &1)); + assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Thawed { who: 1, amount: 2 })]); + + // Freeze = [15] --> [] + assert_ok!(Balances::thaw(&TestId::Bar, &1)); + assert_eq!(events(), [RuntimeEvent::Balances(crate::Event::Thawed { who: 1, amount: 15 })]); + }); +} diff --git a/frame/balances/src/tests/mod.rs b/frame/balances/src/tests/mod.rs new file mode 100644 index 000000000000..45f34110a6e9 --- /dev/null +++ b/frame/balances/src/tests/mod.rs @@ -0,0 +1,313 @@ +// This file is part of Substrate. + +// Copyright (C) 2018-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. + +//! Tests. + +#![cfg(test)] + +use crate::{self as pallet_balances, AccountData, Config, CreditOf, Error, Pallet}; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{ + assert_err, assert_noop, assert_ok, assert_storage_noop, + dispatch::{DispatchInfo, GetDispatchInfo}, + parameter_types, + traits::{ + tokens::fungible, ConstU32, ConstU64, ConstU8, Imbalance as ImbalanceT, OnUnbalanced, + StorageMapShim, StoredMap, WhitelistedStorageKeys, + }, + weights::{IdentityFee, Weight}, + RuntimeDebug, +}; +use frame_system::{self as system, RawOrigin}; +use pallet_transaction_payment::{ChargeTransactionPayment, CurrencyAdapter, Multiplier}; +use scale_info::TypeInfo; +use sp_core::{hexdisplay::HexDisplay, H256}; +use sp_io; +use sp_runtime::{ + traits::{BadOrigin, IdentityLookup, SignedExtension, Zero}, + ArithmeticError, BuildStorage, DispatchError, DispatchResult, FixedPointNumber, TokenError, +}; +use std::collections::BTreeSet; + +mod currency_tests; +mod dispatchable_tests; +mod fungible_conformance_tests; +mod fungible_tests; +mod reentrancy_tests; + +type Block = frame_system::mocking::MockBlock; + +#[derive( + Encode, + Decode, + Copy, + Clone, + Eq, + PartialEq, + Ord, + PartialOrd, + MaxEncodedLen, + TypeInfo, + RuntimeDebug, +)] +pub enum TestId { + Foo, + Bar, + Baz, +} + +frame_support::construct_runtime!( + pub struct Test + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max( + frame_support::weights::Weight::from_parts(1024, u64::MAX), + ); + pub static ExistentialDeposit: u64 = 1; +} +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = BlockWeights; + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = super::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_transaction_payment::Config for Test { + type RuntimeEvent = RuntimeEvent; + type OnChargeTransaction = CurrencyAdapter, ()>; + type OperationalFeeMultiplier = ConstU8<5>; + type WeightToFee = IdentityFee; + type LengthToFee = IdentityFee; + type FeeMultiplierUpdate = (); +} + +impl Config for Test { + type Balance = u64; + type DustRemoval = DustTrap; + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = TestAccountStore; + type MaxLocks = ConstU32<50>; + type MaxReserves = ConstU32<2>; + type ReserveIdentifier = TestId; + type WeightInfo = (); + type RuntimeHoldReason = TestId; + type FreezeIdentifier = TestId; + type MaxFreezes = ConstU32<2>; + type MaxHolds = ConstU32<2>; +} + +#[derive(Clone)] +pub struct ExtBuilder { + existential_deposit: u64, + monied: bool, + dust_trap: Option, +} +impl Default for ExtBuilder { + fn default() -> Self { + Self { existential_deposit: 1, monied: false, dust_trap: None } + } +} +impl ExtBuilder { + pub fn existential_deposit(mut self, existential_deposit: u64) -> Self { + self.existential_deposit = existential_deposit; + self + } + pub fn monied(mut self, monied: bool) -> Self { + self.monied = monied; + if self.existential_deposit == 0 { + self.existential_deposit = 1; + } + self + } + pub fn dust_trap(mut self, account: u64) -> Self { + self.dust_trap = Some(account); + self + } + pub fn set_associated_consts(&self) { + DUST_TRAP_TARGET.with(|v| v.replace(self.dust_trap)); + EXISTENTIAL_DEPOSIT.with(|v| v.replace(self.existential_deposit)); + } + pub fn build(self) -> sp_io::TestExternalities { + self.set_associated_consts(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + pallet_balances::GenesisConfig:: { + balances: if self.monied { + vec![ + (1, 10 * self.existential_deposit), + (2, 20 * self.existential_deposit), + (3, 30 * self.existential_deposit), + (4, 40 * self.existential_deposit), + (12, 10 * self.existential_deposit), + ] + } else { + vec![] + }, + } + .assimilate_storage(&mut t) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext + } + pub fn build_and_execute_with(self, f: impl Fn()) { + let other = self.clone(); + UseSystem::set(false); + other.build().execute_with(|| f()); + UseSystem::set(true); + self.build().execute_with(|| f()); + } +} + +parameter_types! { + static DustTrapTarget: Option = None; +} + +pub struct DustTrap; + +impl OnUnbalanced> for DustTrap { + fn on_nonzero_unbalanced(amount: CreditOf) { + match DustTrapTarget::get() { + None => drop(amount), + Some(a) => { + let result = >::resolve(&a, amount); + debug_assert!(result.is_ok()); + }, + } + } +} + +parameter_types! { + pub static UseSystem: bool = false; +} + +type BalancesAccountStore = StorageMapShim, u64, super::AccountData>; +type SystemAccountStore = frame_system::Pallet; + +pub struct TestAccountStore; +impl StoredMap> for TestAccountStore { + fn get(k: &u64) -> super::AccountData { + if UseSystem::get() { + >::get(k) + } else { + >::get(k) + } + } + fn try_mutate_exists>( + k: &u64, + f: impl FnOnce(&mut Option>) -> Result, + ) -> Result { + if UseSystem::get() { + >::try_mutate_exists(k, f) + } else { + >::try_mutate_exists(k, f) + } + } + fn mutate( + k: &u64, + f: impl FnOnce(&mut super::AccountData) -> R, + ) -> Result { + if UseSystem::get() { + >::mutate(k, f) + } else { + >::mutate(k, f) + } + } + fn mutate_exists( + k: &u64, + f: impl FnOnce(&mut Option>) -> R, + ) -> Result { + if UseSystem::get() { + >::mutate_exists(k, f) + } else { + >::mutate_exists(k, f) + } + } + fn insert(k: &u64, t: super::AccountData) -> Result<(), DispatchError> { + if UseSystem::get() { + >::insert(k, t) + } else { + >::insert(k, t) + } + } + fn remove(k: &u64) -> Result<(), DispatchError> { + if UseSystem::get() { + >::remove(k) + } else { + >::remove(k) + } + } +} + +pub fn events() -> Vec { + let evt = System::events().into_iter().map(|evt| evt.event).collect::>(); + System::reset_events(); + evt +} + +/// create a transaction info struct from weight. Handy to avoid building the whole struct. +pub fn info_from_weight(w: Weight) -> DispatchInfo { + DispatchInfo { weight: w, ..Default::default() } +} + +#[test] +fn weights_sane() { + let info = crate::Call::::transfer_allow_death { dest: 10, value: 4 }.get_dispatch_info(); + assert_eq!(<() as crate::WeightInfo>::transfer_allow_death(), info.weight); + + let info = crate::Call::::force_unreserve { who: 10, amount: 4 }.get_dispatch_info(); + assert_eq!(<() as crate::WeightInfo>::force_unreserve(), info.weight); +} + +#[test] +fn check_whitelist() { + let whitelist: BTreeSet = AllPalletsWithSystem::whitelisted_storage_keys() + .iter() + .map(|s| HexDisplay::from(&s.key).to_string()) + .collect(); + // Inactive Issuance + assert!(whitelist.contains("c2261276cc9d1f8598ea4b6a74b15c2f1ccde6872881f893a21de93dfe970cd5")); + // Total Issuance + assert!(whitelist.contains("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80")); +} diff --git a/frame/balances/src/tests/reentrancy_tests.rs b/frame/balances/src/tests/reentrancy_tests.rs new file mode 100644 index 000000000000..e97bf2ed2b70 --- /dev/null +++ b/frame/balances/src/tests/reentrancy_tests.rs @@ -0,0 +1,195 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Tests regarding the reentrancy functionality. + +use super::*; +use frame_support::traits::tokens::{ + Fortitude::Force, + Precision::BestEffort, + Preservation::{Expendable, Protect}, +}; +use fungible::Balanced; + +#[test] +fn transfer_dust_removal_tst1_should_work() { + ExtBuilder::default() + .existential_deposit(100) + .dust_trap(1) + .build_and_execute_with(|| { + // Verification of reentrancy in dust removal + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 1000)); + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 2, 500)); + + // In this transaction, account 2 free balance + // drops below existential balance + // and dust balance is removed from account 2 + assert_ok!(Balances::transfer_allow_death(RawOrigin::Signed(2).into(), 3, 450)); + + // As expected dust balance is removed. + assert_eq!(Balances::free_balance(&2), 0); + + // As expected beneficiary account 3 + // received the transfered fund. + assert_eq!(Balances::free_balance(&3), 450); + + // Dust balance is deposited to account 1 + // during the process of dust removal. + assert_eq!(Balances::free_balance(&1), 1050); + + // Verify the events + assert_eq!(System::events().len(), 12); + + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { + from: 2, + to: 3, + amount: 450, + })); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { + account: 2, + amount: 50, + })); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { + who: 1, + amount: 50, + })); + }); +} + +#[test] +fn transfer_dust_removal_tst2_should_work() { + ExtBuilder::default() + .existential_deposit(100) + .dust_trap(1) + .build_and_execute_with(|| { + // Verification of reentrancy in dust removal + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 1000)); + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 2, 500)); + + // In this transaction, account 2 free balance + // drops below existential balance + // and dust balance is removed from account 2 + assert_ok!(Balances::transfer_allow_death(RawOrigin::Signed(2).into(), 1, 450)); + + // As expected dust balance is removed. + assert_eq!(Balances::free_balance(&2), 0); + + // Dust balance is deposited to account 1 + // during the process of dust removal. + assert_eq!(Balances::free_balance(&1), 1500); + + // Verify the events + assert_eq!(System::events().len(), 10); + + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { + from: 2, + to: 1, + amount: 450, + })); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { + account: 2, + amount: 50, + })); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { + who: 1, + amount: 50, + })); + }); +} + +#[test] +fn repatriating_reserved_balance_dust_removal_should_work() { + ExtBuilder::default() + .existential_deposit(100) + .dust_trap(1) + .build_and_execute_with(|| { + // Verification of reentrancy in dust removal + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 1000)); + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 2, 500)); + + // Reserve a value on account 2, + // Such that free balance is lower than + // Exestintial deposit. + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), 1, 450)); + + // Since free balance of account 2 is lower than + // existential deposit, dust amount is + // removed from the account 2 + assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::free_balance(2), 0); + + // account 1 is credited with reserved amount + // together with dust balance during dust + // removal. + assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::free_balance(1), 1500); + + // Verify the events + assert_eq!(System::events().len(), 10); + + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { + from: 2, + to: 1, + amount: 450, + })); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { + account: 2, + amount: 50, + })); + System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { + who: 1, + amount: 50, + })); + }); +} + +#[test] +fn emit_events_with_no_existential_deposit_suicide_with_dust() { + ExtBuilder::default().existential_deposit(2).build_and_execute_with(|| { + assert_ok!(Balances::force_set_balance(RawOrigin::Root.into(), 1, 100)); + + assert_eq!( + events(), + [ + RuntimeEvent::System(system::Event::NewAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }), + RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100 }), + ] + ); + + let res = Balances::withdraw(&1, 98, BestEffort, Protect, Force); + assert_eq!(res.unwrap().peek(), 98); + + // no events + assert_eq!( + events(), + [RuntimeEvent::Balances(crate::Event::Withdraw { who: 1, amount: 98 })] + ); + + let res = Balances::withdraw(&1, 1, BestEffort, Expendable, Force); + assert_eq!(res.unwrap().peek(), 1); + + assert_eq!( + events(), + [ + RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), + RuntimeEvent::Balances(crate::Event::DustLost { account: 1, amount: 1 }), + RuntimeEvent::Balances(crate::Event::Withdraw { who: 1, amount: 1 }) + ] + ); + }); +} diff --git a/frame/balances/src/tests_composite.rs b/frame/balances/src/tests_composite.rs deleted file mode 100644 index f8a8fdd1851d..000000000000 --- a/frame/balances/src/tests_composite.rs +++ /dev/null @@ -1,149 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-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. - -//! Test utilities - -#![cfg(test)] - -use crate::{self as pallet_balances, decl_tests, Config, Pallet}; -use frame_support::{ - dispatch::DispatchInfo, - parameter_types, - traits::{ConstU32, ConstU64, ConstU8}, - weights::{IdentityFee, Weight}, -}; -use pallet_transaction_payment::CurrencyAdapter; -use sp_core::H256; -use sp_io; -use sp_runtime::{testing::Header, traits::IdentityLookup}; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, - } -); - -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max( - frame_support::weights::Weight::from_ref_time(1024).set_proof_size(u64::MAX), - ); - pub static ExistentialDeposit: u64 = 0; -} -impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = BlockWeights; - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = super::AccountData; - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; -} - -impl pallet_transaction_payment::Config for Test { - type RuntimeEvent = RuntimeEvent; - type OnChargeTransaction = CurrencyAdapter, ()>; - type OperationalFeeMultiplier = ConstU8<5>; - type WeightToFee = IdentityFee; - type LengthToFee = IdentityFee; - type FeeMultiplierUpdate = (); -} - -impl Config for Test { - type Balance = u64; - type DustRemoval = (); - type RuntimeEvent = RuntimeEvent; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = frame_system::Pallet; - type MaxLocks = (); - type MaxReserves = ConstU32<2>; - type ReserveIdentifier = [u8; 8]; - type WeightInfo = (); -} - -pub struct ExtBuilder { - existential_deposit: u64, - monied: bool, -} -impl Default for ExtBuilder { - fn default() -> Self { - Self { existential_deposit: 1, monied: false } - } -} -impl ExtBuilder { - pub fn existential_deposit(mut self, existential_deposit: u64) -> Self { - self.existential_deposit = existential_deposit; - self - } - pub fn monied(mut self, monied: bool) -> Self { - self.monied = monied; - self - } - pub fn set_associated_consts(&self) { - EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); - } - pub fn build(self) -> sp_io::TestExternalities { - self.set_associated_consts(); - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig:: { - balances: if self.monied { - vec![ - (1, 10 * self.existential_deposit), - (2, 20 * self.existential_deposit), - (3, 30 * self.existential_deposit), - (4, 40 * self.existential_deposit), - (12, 10 * self.existential_deposit), - ] - } else { - vec![] - }, - } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext - } -} - -decl_tests! { Test, ExtBuilder, EXISTENTIAL_DEPOSIT } diff --git a/frame/balances/src/tests_local.rs b/frame/balances/src/tests_local.rs deleted file mode 100644 index 152a5da37410..000000000000 --- a/frame/balances/src/tests_local.rs +++ /dev/null @@ -1,191 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-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. - -//! Test utilities - -#![cfg(test)] - -use crate::{self as pallet_balances, decl_tests, Config, Pallet}; -use frame_support::{ - dispatch::DispatchInfo, - parameter_types, - traits::{ConstU32, ConstU64, ConstU8, StorageMapShim}, - weights::{IdentityFee, Weight}, -}; -use pallet_transaction_payment::CurrencyAdapter; -use sp_core::H256; -use sp_io; -use sp_runtime::{testing::Header, traits::IdentityLookup}; - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -frame_support::construct_runtime!( - pub struct Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, - } -); - -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max( - frame_support::weights::Weight::from_ref_time(1024).set_proof_size(u64::MAX), - ); - pub static ExistentialDeposit: u64 = 0; -} -impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = BlockWeights; - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; -} - -impl pallet_transaction_payment::Config for Test { - type RuntimeEvent = RuntimeEvent; - type OnChargeTransaction = CurrencyAdapter, ()>; - type OperationalFeeMultiplier = ConstU8<5>; - type WeightToFee = IdentityFee; - type LengthToFee = IdentityFee; - type FeeMultiplierUpdate = (); -} - -impl Config for Test { - type Balance = u64; - type DustRemoval = (); - type RuntimeEvent = RuntimeEvent; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = - StorageMapShim, system::Provider, u64, super::AccountData>; - type MaxLocks = ConstU32<50>; - type MaxReserves = ConstU32<2>; - type ReserveIdentifier = [u8; 8]; - type WeightInfo = (); -} - -pub struct ExtBuilder { - existential_deposit: u64, - monied: bool, -} -impl Default for ExtBuilder { - fn default() -> Self { - Self { existential_deposit: 1, monied: false } - } -} -impl ExtBuilder { - pub fn existential_deposit(mut self, existential_deposit: u64) -> Self { - self.existential_deposit = existential_deposit; - self - } - pub fn monied(mut self, monied: bool) -> Self { - self.monied = monied; - if self.existential_deposit == 0 { - self.existential_deposit = 1; - } - self - } - pub fn set_associated_consts(&self) { - EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); - } - pub fn build(self) -> sp_io::TestExternalities { - self.set_associated_consts(); - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig:: { - balances: if self.monied { - vec![ - (1, 10 * self.existential_deposit), - (2, 20 * self.existential_deposit), - (3, 30 * self.existential_deposit), - (4, 40 * self.existential_deposit), - (12, 10 * self.existential_deposit), - ] - } else { - vec![] - }, - } - .assimilate_storage(&mut t) - .unwrap(); - - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext - } -} - -decl_tests! { Test, ExtBuilder, EXISTENTIAL_DEPOSIT } - -#[test] -fn emit_events_with_no_existential_deposit_suicide_with_dust() { - ::default().existential_deposit(2).build().execute_with(|| { - assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 100, 0)); - - assert_eq!( - events(), - [ - RuntimeEvent::System(system::Event::NewAccount { account: 1 }), - RuntimeEvent::Balances(crate::Event::Endowed { account: 1, free_balance: 100 }), - RuntimeEvent::Balances(crate::Event::BalanceSet { who: 1, free: 100, reserved: 0 }), - ] - ); - - let res = Balances::slash(&1, 98); - assert_eq!(res, (NegativeImbalance::new(98), 0)); - - // no events - assert_eq!( - events(), - [RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 98 })] - ); - - let res = Balances::slash(&1, 1); - assert_eq!(res, (NegativeImbalance::new(1), 0)); - - assert_eq!( - events(), - [ - RuntimeEvent::System(system::Event::KilledAccount { account: 1 }), - RuntimeEvent::Balances(crate::Event::DustLost { account: 1, amount: 1 }), - RuntimeEvent::Balances(crate::Event::Slashed { who: 1, amount: 1 }) - ] - ); - }); -} diff --git a/frame/balances/src/tests_reentrancy.rs b/frame/balances/src/tests_reentrancy.rs deleted file mode 100644 index 90363140000e..000000000000 --- a/frame/balances/src/tests_reentrancy.rs +++ /dev/null @@ -1,264 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-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. - -//! Test setup for potential reentracy and lost updates of nested mutations. - -#![cfg(test)] - -use crate::{self as pallet_balances, Config}; -use frame_support::{ - parameter_types, - traits::{ConstU32, ConstU64, StorageMapShim}, -}; -use sp_core::H256; -use sp_io; -use sp_runtime::{testing::Header, traits::IdentityLookup}; - -use crate::*; -use frame_support::{ - assert_ok, - traits::{Currency, ReservableCurrency}, -}; -use frame_system::RawOrigin; - -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; -type Block = frame_system::mocking::MockBlock; - -frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - } -); - -parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max( - frame_support::weights::Weight::from_ref_time(1024).set_proof_size(u64::MAX), - ); - pub static ExistentialDeposit: u64 = 0; -} -impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = BlockWeights; - type BlockLength = (); - type DbWeight = (); - type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; - type RuntimeCall = RuntimeCall; - type Hash = H256; - type Hashing = ::sp_runtime::traits::BlakeTwo256; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU64<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; -} - -pub struct OnDustRemoval; -impl OnUnbalanced> for OnDustRemoval { - fn on_nonzero_unbalanced(amount: NegativeImbalance) { - assert_ok!(Balances::resolve_into_existing(&1, amount)); - } -} - -impl Config for Test { - type Balance = u64; - type DustRemoval = OnDustRemoval; - type RuntimeEvent = RuntimeEvent; - type ExistentialDeposit = ExistentialDeposit; - type AccountStore = - StorageMapShim, system::Provider, u64, super::AccountData>; - type MaxLocks = ConstU32<50>; - type MaxReserves = ConstU32<2>; - type ReserveIdentifier = [u8; 8]; - type WeightInfo = (); -} - -pub struct ExtBuilder { - existential_deposit: u64, -} -impl Default for ExtBuilder { - fn default() -> Self { - Self { existential_deposit: 1 } - } -} -impl ExtBuilder { - pub fn existential_deposit(mut self, existential_deposit: u64) -> Self { - self.existential_deposit = existential_deposit; - self - } - - pub fn set_associated_consts(&self) { - EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); - } - - pub fn build(self) -> sp_io::TestExternalities { - self.set_associated_consts(); - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - pallet_balances::GenesisConfig:: { balances: vec![] } - .assimilate_storage(&mut t) - .unwrap(); - let mut ext = sp_io::TestExternalities::new(t); - ext.execute_with(|| System::set_block_number(1)); - ext - } -} - -#[test] -fn transfer_dust_removal_tst1_should_work() { - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - // Verification of reentrancy in dust removal - assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 1000, 0)); - assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 2, 500, 0)); - - // In this transaction, account 2 free balance - // drops below existential balance - // and dust balance is removed from account 2 - assert_ok!(Balances::transfer(RawOrigin::Signed(2).into(), 3, 450)); - - // As expected dust balance is removed. - assert_eq!(Balances::free_balance(&2), 0); - - // As expected beneficiary account 3 - // received the transfered fund. - assert_eq!(Balances::free_balance(&3), 450); - - // Dust balance is deposited to account 1 - // during the process of dust removal. - assert_eq!(Balances::free_balance(&1), 1050); - - // Verify the events - assert_eq!(System::events().len(), 12); - - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { - from: 2, - to: 3, - amount: 450, - })); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { - account: 2, - amount: 50, - })); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { - who: 1, - amount: 50, - })); - }); -} - -#[test] -fn transfer_dust_removal_tst2_should_work() { - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - // Verification of reentrancy in dust removal - assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 1000, 0)); - assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 2, 500, 0)); - - // In this transaction, account 2 free balance - // drops below existential balance - // and dust balance is removed from account 2 - assert_ok!(Balances::transfer(RawOrigin::Signed(2).into(), 1, 450)); - - // As expected dust balance is removed. - assert_eq!(Balances::free_balance(&2), 0); - - // Dust balance is deposited to account 1 - // during the process of dust removal. - assert_eq!(Balances::free_balance(&1), 1500); - - // Verify the events - assert_eq!(System::events().len(), 10); - - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Transfer { - from: 2, - to: 1, - amount: 450, - })); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { - account: 2, - amount: 50, - })); - System::assert_has_event(RuntimeEvent::Balances(crate::Event::Deposit { - who: 1, - amount: 50, - })); - }); -} - -#[test] -fn repatriating_reserved_balance_dust_removal_should_work() { - ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - // Verification of reentrancy in dust removal - assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 1, 1000, 0)); - assert_ok!(Balances::set_balance(RawOrigin::Root.into(), 2, 500, 0)); - - // Reserve a value on account 2, - // Such that free balance is lower than - // Exestintial deposit. - assert_ok!(Balances::reserve(&2, 450)); - - // Transfer of reserved fund from slashed account 2 to - // beneficiary account 1 - assert_ok!(Balances::repatriate_reserved(&2, &1, 450, Status::Free), 0); - - // Since free balance of account 2 is lower than - // existential deposit, dust amount is - // removed from the account 2 - assert_eq!(Balances::reserved_balance(2), 0); - assert_eq!(Balances::free_balance(2), 0); - - // account 1 is credited with reserved amount - // together with dust balance during dust - // removal. - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(Balances::free_balance(1), 1500); - - // Verify the events - assert_eq!(System::events().len(), 11); - - System::assert_has_event(RuntimeEvent::Balances(crate::Event::ReserveRepatriated { - from: 2, - to: 1, - amount: 450, - destination_status: Status::Free, - })); - - System::assert_has_event(RuntimeEvent::Balances(crate::Event::DustLost { - account: 2, - amount: 50, - })); - - System::assert_last_event(RuntimeEvent::Balances(crate::Event::Deposit { - who: 1, - amount: 50, - })); - }); -} diff --git a/frame/balances/src/types.rs b/frame/balances/src/types.rs new file mode 100644 index 000000000000..389124402a8f --- /dev/null +++ b/frame/balances/src/types.rs @@ -0,0 +1,157 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Types used in the pallet. + +use crate::{Config, CreditOf, Event, Pallet}; +use codec::{Decode, Encode, MaxEncodedLen}; +use core::ops::BitOr; +use frame_support::{ + traits::{Imbalance, LockIdentifier, OnUnbalanced, WithdrawReasons}, + RuntimeDebug, +}; +use scale_info::TypeInfo; +use sp_runtime::Saturating; + +/// Simplified reasons for withdrawing balance. +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub enum Reasons { + /// Paying system transaction fees. + Fee = 0, + /// Any reason other than paying system transaction fees. + Misc = 1, + /// Any reason at all. + All = 2, +} + +impl From for Reasons { + fn from(r: WithdrawReasons) -> Reasons { + if r == WithdrawReasons::TRANSACTION_PAYMENT { + Reasons::Fee + } else if r.contains(WithdrawReasons::TRANSACTION_PAYMENT) { + Reasons::All + } else { + Reasons::Misc + } + } +} + +impl BitOr for Reasons { + type Output = Reasons; + fn bitor(self, other: Reasons) -> Reasons { + if self == other { + return self + } + Reasons::All + } +} + +/// A single lock on a balance. There can be many of these on an account and they "overlap", so the +/// same balance is frozen by multiple locks. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct BalanceLock { + /// An identifier for this lock. Only one lock may be in existence for each identifier. + pub id: LockIdentifier, + /// The amount which the free balance may not drop below when this lock is in effect. + pub amount: Balance, + /// If true, then the lock remains in effect even for payment of transaction fees. + pub reasons: Reasons, +} + +/// Store named reserved balance. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct ReserveData { + /// The identifier for the named reserve. + pub id: ReserveIdentifier, + /// The amount of the named reserve. + pub amount: Balance, +} + +/// An identifier and balance. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct IdAmount { + /// An identifier for this item. + pub id: Id, + /// Some amount for this item. + pub amount: Balance, +} + +/// All balance information for an account. +#[derive(Encode, Decode, Clone, PartialEq, Eq, Default, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct AccountData { + /// Non-reserved part of the balance which the account holder may be able to control. + /// + /// This is the only balance that matters in terms of most operations on tokens. + pub free: Balance, + /// Balance which is has active holds on it and may not be used at all. + /// + /// This is the sum of all individual holds together with any sums still under the (deprecated) + /// reserves API. + pub reserved: Balance, + /// The amount that `free + reserved` may not drop below when reducing the balance, except for + /// actions where the account owner cannot reasonably benefit from the balance reduction, such + /// as slashing. + pub frozen: Balance, + /// Extra information about this account. The MSB is a flag indicating whether the new ref- + /// counting logic is in place for this account. + pub flags: ExtraFlags, +} + +const IS_NEW_LOGIC: u128 = 0x80000000_00000000_00000000_00000000u128; + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, MaxEncodedLen, TypeInfo)] +pub struct ExtraFlags(u128); +impl Default for ExtraFlags { + fn default() -> Self { + Self(IS_NEW_LOGIC) + } +} +impl ExtraFlags { + pub fn old_logic() -> Self { + Self(0) + } + pub fn set_new_logic(&mut self) { + self.0 = self.0 | IS_NEW_LOGIC + } + pub fn is_new_logic(&self) -> bool { + (self.0 & IS_NEW_LOGIC) == IS_NEW_LOGIC + } +} + +impl AccountData { + pub fn usable(&self) -> Balance { + self.free.saturating_sub(self.frozen) + } + + /// The total balance in this account including any that is reserved and ignoring any frozen. + pub fn total(&self) -> Balance { + self.free.saturating_add(self.reserved) + } +} + +pub struct DustCleaner, I: 'static = ()>( + pub(crate) Option<(T::AccountId, CreditOf)>, +); + +impl, I: 'static> Drop for DustCleaner { + fn drop(&mut self) { + if let Some((who, dust)) = self.0.take() { + Pallet::::deposit_event(Event::DustLost { account: who, amount: dust.peek() }); + T::DustRemoval::on_unbalanced(dust); + } + } +} diff --git a/frame/balances/src/weights.rs b/frame/balances/src/weights.rs index 6324745fd431..5671374948cd 100644 --- a/frame/balances/src/weights.rs +++ b/frame/balances/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,147 +18,236 @@ //! Autogenerated weights for pallet_balances //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` -//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 +//! DATE: 2023-07-14, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-o7yfgx5n-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` // Executed Command: -// ./target/production/substrate +// target/production/substrate // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=pallet_balances // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --output=./frame/balances/src/weights.rs +// --json-file=/builds/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=pallet_balances +// --chain=dev // --header=./HEADER-APACHE2 +// --output=./frame/balances/src/weights.rs // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_balances. pub trait WeightInfo { - fn transfer() -> Weight; + fn transfer_allow_death() -> Weight; fn transfer_keep_alive() -> Weight; - fn set_balance_creating() -> Weight; - fn set_balance_killing() -> Weight; + fn force_set_balance_creating() -> Weight; + fn force_set_balance_killing() -> Weight; fn force_transfer() -> Weight; fn transfer_all() -> Weight; fn force_unreserve() -> Weight; + fn upgrade_accounts(u: u32, ) -> Weight; } /// Weights for pallet_balances using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: System Account (r:1 w:1) - fn transfer() -> Weight { - // Minimum execution time: 48_134 nanoseconds. - Weight::from_ref_time(48_811_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: System Account (r:1 w:1) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn transfer_allow_death() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 58_474_000 picoseconds. + Weight::from_parts(59_117_000, 3593) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn transfer_keep_alive() -> Weight { - // Minimum execution time: 36_586 nanoseconds. - Weight::from_ref_time(36_966_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: System Account (r:1 w:1) - fn set_balance_creating() -> Weight { - // Minimum execution time: 28_486 nanoseconds. - Weight::from_ref_time(28_940_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: System Account (r:1 w:1) - fn set_balance_killing() -> Weight { - // Minimum execution time: 31_225 nanoseconds. - Weight::from_ref_time(31_946_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: System Account (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 44_629_000 picoseconds. + Weight::from_parts(45_798_000, 3593) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn force_set_balance_creating() -> Weight { + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 16_483_000 picoseconds. + Weight::from_parts(16_939_000, 3593) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn force_set_balance_killing() -> Weight { + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 24_638_000 picoseconds. + Weight::from_parts(25_487_000, 3593) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn force_transfer() -> Weight { - // Minimum execution time: 47_347 nanoseconds. - Weight::from_ref_time(48_005_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `103` + // Estimated: `6196` + // Minimum execution time: 60_041_000 picoseconds. + Weight::from_parts(63_365_000, 6196) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: System Account (r:1 w:1) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn transfer_all() -> Weight { - // Minimum execution time: 41_668 nanoseconds. - Weight::from_ref_time(42_232_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 54_445_000 picoseconds. + Weight::from_parts(55_623_000, 3593) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: System Account (r:1 w:1) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn force_unreserve() -> Weight { - // Minimum execution time: 23_741 nanoseconds. - Weight::from_ref_time(24_073_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 19_309_000 picoseconds. + Weight::from_parts(19_953_000, 3593) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `System::Account` (r:999 w:999) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `u` is `[1, 1000]`. + fn upgrade_accounts(u: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0 + u * (135 ±0)` + // Estimated: `990 + u * (2603 ±0)` + // Minimum execution time: 19_362_000 picoseconds. + Weight::from_parts(19_612_000, 990) + // Standard Error: 13_108 + .saturating_add(Weight::from_parts(16_444_591, 0).saturating_mul(u.into())) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(u.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(u.into()))) + .saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into())) } } -// For backwards compatibility and tests +// For backwards compatibility and tests. impl WeightInfo for () { - // Storage: System Account (r:1 w:1) - fn transfer() -> Weight { - // Minimum execution time: 48_134 nanoseconds. - Weight::from_ref_time(48_811_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: System Account (r:1 w:1) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn transfer_allow_death() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 58_474_000 picoseconds. + Weight::from_parts(59_117_000, 3593) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn transfer_keep_alive() -> Weight { - // Minimum execution time: 36_586 nanoseconds. - Weight::from_ref_time(36_966_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: System Account (r:1 w:1) - fn set_balance_creating() -> Weight { - // Minimum execution time: 28_486 nanoseconds. - Weight::from_ref_time(28_940_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: System Account (r:1 w:1) - fn set_balance_killing() -> Weight { - // Minimum execution time: 31_225 nanoseconds. - Weight::from_ref_time(31_946_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: System Account (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 44_629_000 picoseconds. + Weight::from_parts(45_798_000, 3593) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn force_set_balance_creating() -> Weight { + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 16_483_000 picoseconds. + Weight::from_parts(16_939_000, 3593) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn force_set_balance_killing() -> Weight { + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 24_638_000 picoseconds. + Weight::from_parts(25_487_000, 3593) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn force_transfer() -> Weight { - // Minimum execution time: 47_347 nanoseconds. - Weight::from_ref_time(48_005_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `103` + // Estimated: `6196` + // Minimum execution time: 60_041_000 picoseconds. + Weight::from_parts(63_365_000, 6196) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: System Account (r:1 w:1) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn transfer_all() -> Weight { - // Minimum execution time: 41_668 nanoseconds. - Weight::from_ref_time(42_232_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `3593` + // Minimum execution time: 54_445_000 picoseconds. + Weight::from_parts(55_623_000, 3593) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: System Account (r:1 w:1) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn force_unreserve() -> Weight { - // Minimum execution time: 23_741 nanoseconds. - Weight::from_ref_time(24_073_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 19_309_000 picoseconds. + Weight::from_parts(19_953_000, 3593) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: `System::Account` (r:999 w:999) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + /// The range of component `u` is `[1, 1000]`. + fn upgrade_accounts(u: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0 + u * (135 ±0)` + // Estimated: `990 + u * (2603 ±0)` + // Minimum execution time: 19_362_000 picoseconds. + Weight::from_parts(19_612_000, 990) + // Standard Error: 13_108 + .saturating_add(Weight::from_parts(16_444_591, 0).saturating_mul(u.into())) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(u.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(u.into()))) + .saturating_add(Weight::from_parts(0, 2603).saturating_mul(u.into())) } } diff --git a/frame/beefy-mmr/Cargo.toml b/frame/beefy-mmr/Cargo.toml index 54da26c394f0..fb6846efc21e 100644 --- a/frame/beefy-mmr/Cargo.toml +++ b/frame/beefy-mmr/Cargo.toml @@ -9,33 +9,33 @@ repository = "https://github.com/paritytech/substrate" homepage = "https://substrate.io" [dependencies] -array-bytes = { version = "4.1", optional = true } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +array-bytes = { version = "6.1", optional = true } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", optional = true } -beefy-merkle-tree = { version = "4.0.0-dev", default-features = false, path = "./primitives" } -beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy", package = "sp-beefy" } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", optional = true } +binary-merkle-tree = { version = "4.0.0-dev", default-features = false, path = "../../utils/binary-merkle-tree" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-beefy = { version = "4.0.0-dev", default-features = false, path = "../beefy" } pallet-mmr = { version = "4.0.0-dev", default-features = false, path = "../merkle-mountain-range" } pallet-session = { version = "4.0.0-dev", default-features = false, path = "../session" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-consensus-beefy = { version = "4.0.0-dev", default-features = false, path = "../../primitives/consensus/beefy" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } [dev-dependencies] -array-bytes = "4.1" +array-bytes = "6.1" sp-staking = { version = "4.0.0-dev", path = "../../primitives/staking" } [features] default = ["std"] std = [ "array-bytes", - "beefy-merkle-tree/std", - "beefy-primitives/std", + "binary-merkle-tree/std", "codec/std", "frame-support/std", "frame-system/std", @@ -45,9 +45,11 @@ std = [ "pallet-session/std", "scale-info/std", "serde", + "sp-consensus-beefy/std", "sp-core/std", "sp-io/std", "sp-runtime/std", "sp-std/std", + "sp-api/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/beefy-mmr/src/lib.rs b/frame/beefy-mmr/src/lib.rs index 0b7fc22cd279..ec1519ac41bd 100644 --- a/frame/beefy-mmr/src/lib.rs +++ b/frame/beefy-mmr/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,8 +23,8 @@ //! While both BEEFY and Merkle Mountain Range (MMR) can be used separately, //! these tools were designed to work together in unison. //! -//! The pallet provides a standardized MMR Leaf format that is can be used -//! to bridge BEEFY+MMR-based networks (both standalone and polkadot-like). +//! The pallet provides a standardized MMR Leaf format that can be used +//! to bridge BEEFY+MMR-based networks (both standalone and Polkadot-like). //! //! The MMR leaf contains: //! 1. Block number and parent block hash. @@ -36,13 +36,14 @@ use sp_runtime::traits::{Convert, Member}; use sp_std::prelude::*; -use beefy_primitives::{ +use pallet_mmr::{LeafDataProvider, ParentNumberAndHash}; +use sp_consensus_beefy::{ mmr::{BeefyAuthoritySet, BeefyDataProvider, BeefyNextAuthoritySet, MmrLeaf, MmrLeafVersion}, ValidatorSet as BeefyValidatorSet, }; -use pallet_mmr::{LeafDataProvider, ParentNumberAndHash}; use frame_support::{crypto::ecdsa::ECDSAExt, traits::Get}; +use frame_system::pallet_prelude::BlockNumberFor; pub use pallet::*; @@ -54,15 +55,15 @@ mod tests; /// A BEEFY consensus digest item with MMR root hash. pub struct DepositBeefyDigest(sp_std::marker::PhantomData); -impl pallet_mmr::primitives::OnNewRoot for DepositBeefyDigest +impl pallet_mmr::primitives::OnNewRoot for DepositBeefyDigest where - T: pallet_mmr::Config, + T: pallet_mmr::Config, T: pallet_beefy::Config, { - fn on_new_root(root: &::Hash) { + fn on_new_root(root: &sp_consensus_beefy::MmrRootHash) { let digest = sp_runtime::generic::DigestItem::Consensus( - beefy_primitives::BEEFY_ENGINE_ID, - codec::Encode::encode(&beefy_primitives::ConsensusLog::< + sp_consensus_beefy::BEEFY_ENGINE_ID, + codec::Encode::encode(&sp_consensus_beefy::ConsensusLog::< ::BeefyId, >::MmrRoot(*root)), ); @@ -72,8 +73,8 @@ where /// Convert BEEFY secp256k1 public keys into Ethereum addresses pub struct BeefyEcdsaToEthereum; -impl Convert> for BeefyEcdsaToEthereum { - fn convert(beefy_id: beefy_primitives::crypto::AuthorityId) -> Vec { +impl Convert> for BeefyEcdsaToEthereum { + fn convert(beefy_id: sp_consensus_beefy::crypto::AuthorityId) -> Vec { sp_core::ecdsa::Public::from(beefy_id) .to_eth_address() .map(|v| v.to_vec()) @@ -84,7 +85,7 @@ impl Convert> for BeefyEcdsaToEth } } -type MerkleRootOf = ::Hash; +type MerkleRootOf = <::Hashing as sp_runtime::traits::Hash>::Output; #[frame_support::pallet] pub mod pallet { @@ -95,7 +96,6 @@ pub mod pallet { /// BEEFY-MMR pallet. #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); /// The module's configuration trait. @@ -140,7 +140,7 @@ pub mod pallet { impl LeafDataProvider for Pallet { type LeafData = MmrLeaf< - ::BlockNumber, + BlockNumberFor, ::Hash, MerkleRootOf, T::LeafExtra, @@ -156,7 +156,7 @@ impl LeafDataProvider for Pallet { } } -impl beefy_primitives::OnNewValidatorSet<::BeefyId> for Pallet +impl sp_consensus_beefy::OnNewValidatorSet<::BeefyId> for Pallet where T: pallet::Config, { @@ -200,10 +200,24 @@ impl Pallet { .map(T::BeefyAuthorityToMerkleLeaf::convert) .collect::>(); let len = beefy_addresses.len() as u32; - let root = beefy_merkle_tree::merkle_root::<::Hashing, _>( + let root = binary_merkle_tree::merkle_root::<::Hashing, _>( beefy_addresses, ) .into(); BeefyAuthoritySet { id, len, root } } } + +sp_api::decl_runtime_apis! { + /// API useful for BEEFY light clients. + pub trait BeefyMmrApi + where + BeefyAuthoritySet: sp_api::Decode, + { + /// Return the currently active BEEFY authority set proof. + fn authority_set_proof() -> BeefyAuthoritySet; + + /// Return the next/queued BEEFY authority set proof. + fn next_authority_set_proof() -> BeefyNextAuthoritySet; + } +} diff --git a/frame/beefy-mmr/src/mock.rs b/frame/beefy-mmr/src/mock.rs index 0a64ad3fc997..b17550653d5e 100644 --- a/frame/beefy-mmr/src/mock.rs +++ b/frame/beefy-mmr/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,25 +17,25 @@ use std::vec; -use beefy_primitives::mmr::MmrLeafVersion; use codec::Encode; use frame_support::{ construct_runtime, parameter_types, sp_io::TestExternalities, - traits::{ConstU16, ConstU32, ConstU64, GenesisBuild}, + traits::{ConstU16, ConstU32, ConstU64}, BasicExternalities, }; -use sp_core::{Hasher, H256}; +use sp_consensus_beefy::mmr::MmrLeafVersion; +use sp_core::H256; use sp_runtime::{ app_crypto::ecdsa::Public, impl_opaque_keys, - testing::Header, traits::{BlakeTwo256, ConvertInto, IdentityLookup, Keccak256, OpaqueKeys}, + BuildStorage, }; use crate as pallet_beefy_mmr; -pub use beefy_primitives::{ +pub use sp_consensus_beefy::{ crypto::AuthorityId as BeefyId, mmr::BeefyDataProvider, ConsensusLog, BEEFY_ENGINE_ID, }; @@ -45,16 +45,12 @@ impl_opaque_keys! { } } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Session: pallet_session::{Pallet, Call, Storage, Event, Config}, Mmr: pallet_mmr::{Pallet, Storage}, Beefy: pallet_beefy::{Pallet, Config, Storage}, @@ -68,14 +64,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -101,10 +96,10 @@ impl pallet_session::Config for Test { type WeightInfo = (); } -pub type MmrLeaf = beefy_primitives::mmr::MmrLeaf< - ::BlockNumber, +pub type MmrLeaf = sp_consensus_beefy::mmr::MmrLeaf< + frame_system::pallet_prelude::BlockNumberFor, ::Hash, - ::Hash, + crate::MerkleRootOf, Vec, >; @@ -113,8 +108,6 @@ impl pallet_mmr::Config for Test { type Hashing = Keccak256; - type Hash = ::Out; - type LeafData = BeefyMmr; type OnNewRoot = pallet_beefy_mmr::DepositBeefyDigest; @@ -125,7 +118,12 @@ impl pallet_mmr::Config for Test { impl pallet_beefy::Config for Test { type BeefyId = BeefyId; type MaxAuthorities = ConstU32<100>; + type MaxNominators = ConstU32<1000>; + type MaxSetIdSessionEntries = ConstU64<100>; type OnNewValidatorSet = BeefyMmr; + type WeightInfo = (); + type KeyOwnerProof = sp_core::Void; + type EquivocationReportSystem = (); } parameter_types! { @@ -147,7 +145,7 @@ impl BeefyDataProvider> for DummyDataProvider { fn extra_data() -> Vec { let mut col = vec![(15, vec![1, 2, 3]), (5, vec![4, 5, 6])]; col.sort(); - beefy_merkle_tree::merkle_root::<::Hashing, _>( + binary_merkle_tree::merkle_root::<::Hashing, _>( col.into_iter().map(|pair| pair.encode()), ) .as_ref() @@ -191,7 +189,7 @@ pub fn new_test_ext(ids: Vec) -> TestExternalities { } pub fn new_test_ext_raw_authorities(authorities: Vec<(u64, BeefyId)>) -> TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let session_keys: Vec<_> = authorities .iter() diff --git a/frame/beefy-mmr/src/tests.rs b/frame/beefy-mmr/src/tests.rs index 0f7804e544aa..067c3851ba83 100644 --- a/frame/beefy-mmr/src/tests.rs +++ b/frame/beefy-mmr/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,11 +17,11 @@ use std::vec; -use beefy_primitives::{ +use codec::{Decode, Encode}; +use sp_consensus_beefy::{ mmr::{BeefyNextAuthoritySet, MmrLeafVersion}, ValidatorSet, }; -use codec::{Decode, Encode}; use sp_core::H256; use sp_io::TestExternalities; @@ -70,7 +70,7 @@ fn should_contain_mmr_digest() { ValidatorSet::new(vec![mock_beefy_id(1), mock_beefy_id(2)], 1).unwrap() )), beefy_log(ConsensusLog::MmrRoot(array_bytes::hex_n_into_unchecked( - "200e73880940ac0b66735ffb560fa0a3989292463d262deac6ad61e78a3e46a4" + "95803defe6ea9f41e7ec6afa497064f21bfded027d8812efacbdf984e630cbdc" ))) ] ); @@ -85,13 +85,13 @@ fn should_contain_mmr_digest() { ValidatorSet::new(vec![mock_beefy_id(1), mock_beefy_id(2)], 1).unwrap() )), beefy_log(ConsensusLog::MmrRoot(array_bytes::hex_n_into_unchecked( - "200e73880940ac0b66735ffb560fa0a3989292463d262deac6ad61e78a3e46a4" + "95803defe6ea9f41e7ec6afa497064f21bfded027d8812efacbdf984e630cbdc" ))), beefy_log(ConsensusLog::AuthoritiesChange( ValidatorSet::new(vec![mock_beefy_id(3), mock_beefy_id(4)], 2).unwrap() )), beefy_log(ConsensusLog::MmrRoot(array_bytes::hex_n_into_unchecked( - "ba37d8d5d195ac8caec391da35472f9ecf1116ff1642409148b62e08896d3884" + "a73271a0974f1e67d6e9b8dd58e506177a2e556519a330796721e98279a753e2" ))), ] ); @@ -124,7 +124,7 @@ fn should_contain_valid_leaf_data() { ) }, leaf_extra: array_bytes::hex2bytes_unchecked( - "5572d58c82bddf323f4fc7aecab8a8f0ad6ed2f06ab2bfb8ade36a77a45fcc68" + "55b8e9e1cc9f0db7776fac0ca66318ef8acfb8ec26db11e373120583e07ee648" ) } ); @@ -149,7 +149,7 @@ fn should_contain_valid_leaf_data() { ) }, leaf_extra: array_bytes::hex2bytes_unchecked( - "5572d58c82bddf323f4fc7aecab8a8f0ad6ed2f06ab2bfb8ade36a77a45fcc68" + "55b8e9e1cc9f0db7776fac0ca66318ef8acfb8ec26db11e373120583e07ee648" ) } ); @@ -164,7 +164,7 @@ fn should_update_authorities() { // check current authority set assert_eq!(0, auth_set.id); assert_eq!(2, auth_set.len); - let want = array_bytes::hex_n_into_unchecked::( + let want = array_bytes::hex_n_into_unchecked::<_, H256, 32>( "176e73f1bf656478b728e28dd1a7733c98621b8acf830bff585949763dca7a96", ); assert_eq!(want, auth_set.root); @@ -184,7 +184,7 @@ fn should_update_authorities() { assert_eq!(1, auth_set.id); // check next auth set assert_eq!(2, next_auth_set.id); - let want = array_bytes::hex_n_into_unchecked::( + let want = array_bytes::hex_n_into_unchecked::<_, H256, 32>( "9c6b2c1b0d0b25a008e6c882cc7b415f309965c72ad2b944ac0931048ca31cd5", ); assert_eq!(2, next_auth_set.len); @@ -200,7 +200,7 @@ fn should_update_authorities() { assert_eq!(2, auth_set.id); // check next auth set assert_eq!(3, next_auth_set.id); - let want = array_bytes::hex_n_into_unchecked::( + let want = array_bytes::hex_n_into_unchecked::<_, H256, 32>( "9c6b2c1b0d0b25a008e6c882cc7b415f309965c72ad2b944ac0931048ca31cd5", ); assert_eq!(2, next_auth_set.len); diff --git a/frame/beefy/Cargo.toml b/frame/beefy/Cargo.toml index 4db8c0568284..471b16e159dd 100644 --- a/frame/beefy/Cargo.toml +++ b/frame/beefy/Cargo.toml @@ -9,32 +9,44 @@ description = "BEEFY FRAME pallet" homepage = "https://substrate.io" [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", optional = true } -beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy", package = "sp-beefy" } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive", "serde"] } +serde = { version = "1.0.163", optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } pallet-session = { version = "4.0.0-dev", default-features = false, path = "../session" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-consensus-beefy = { version = "4.0.0-dev", default-features = false, path = "../../primitives/consensus/beefy", features = ["serde"] } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime", features = ["serde"] } +sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking", features = ["serde"] } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } +frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +pallet-offences = { version = "4.0.0-dev", path = "../offences" } +pallet-staking = { version = "4.0.0-dev", path = "../staking" } +pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } +pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } sp-staking = { version = "4.0.0-dev", path = "../../primitives/staking" } [features] default = ["std"] std = [ - "beefy-primitives/std", "codec/std", "frame-support/std", "frame-system/std", + "pallet-authorship/std", "pallet-session/std", "scale-info/std", - "serde", + "serde/std", + "sp-consensus-beefy/std", "sp-runtime/std", + "sp-session/std", + "sp-staking/std", "sp-std/std", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/beefy/src/default_weights.rs b/frame/beefy/src/default_weights.rs new file mode 100644 index 000000000000..091d58f47f97 --- /dev/null +++ b/frame/beefy/src/default_weights.rs @@ -0,0 +1,52 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Default weights for the BEEFY Pallet +//! This file was not auto-generated. + +use frame_support::weights::{ + constants::{RocksDbWeight as DbWeight, WEIGHT_REF_TIME_PER_MICROS, WEIGHT_REF_TIME_PER_NANOS}, + Weight, +}; + +impl crate::WeightInfo for () { + fn report_equivocation(validator_count: u32, max_nominators_per_validator: u32) -> Weight { + // we take the validator set count from the membership proof to + // calculate the weight but we set a floor of 100 validators. + let validator_count = validator_count.max(100) as u64; + + // checking membership proof + Weight::from_parts(35u64 * WEIGHT_REF_TIME_PER_MICROS, 0) + .saturating_add( + Weight::from_parts(175u64 * WEIGHT_REF_TIME_PER_NANOS, 0) + .saturating_mul(validator_count), + ) + .saturating_add(DbWeight::get().reads(5)) + // check equivocation proof + .saturating_add(Weight::from_parts(95u64 * WEIGHT_REF_TIME_PER_MICROS, 0)) + // report offence + .saturating_add(Weight::from_parts(110u64 * WEIGHT_REF_TIME_PER_MICROS, 0)) + .saturating_add(Weight::from_parts( + 25u64 * WEIGHT_REF_TIME_PER_MICROS * max_nominators_per_validator as u64, + 0, + )) + .saturating_add(DbWeight::get().reads(14 + 3 * max_nominators_per_validator as u64)) + .saturating_add(DbWeight::get().writes(10 + 3 * max_nominators_per_validator as u64)) + // fetching set id -> session index mappings + .saturating_add(DbWeight::get().reads(2)) + } +} diff --git a/frame/beefy/src/equivocation.rs b/frame/beefy/src/equivocation.rs new file mode 100644 index 000000000000..acbd3a2532d7 --- /dev/null +++ b/frame/beefy/src/equivocation.rs @@ -0,0 +1,290 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! An opt-in utility module for reporting equivocations. +//! +//! This module defines an offence type for BEEFY equivocations +//! and some utility traits to wire together: +//! - a key ownership proof system (e.g. to prove that a given authority was part of a session); +//! - a system for reporting offences; +//! - a system for signing and submitting transactions; +//! - a way to get the current block author; +//! +//! These can be used in an offchain context in order to submit equivocation +//! reporting extrinsics (from the client that's running the BEEFY protocol). +//! And in a runtime context, so that the BEEFY pallet can validate the +//! equivocation proofs in the extrinsic and report the offences. +//! +//! IMPORTANT: +//! When using this module for enabling equivocation reporting it is required +//! that the `ValidateUnsigned` for the BEEFY pallet is used in the runtime +//! definition. + +use codec::{self as codec, Decode, Encode}; +use frame_support::{ + log, + traits::{Get, KeyOwnerProofSystem}, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use log::{error, info}; +use sp_consensus_beefy::{EquivocationProof, ValidatorSetId, KEY_TYPE}; +use sp_runtime::{ + transaction_validity::{ + InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, + TransactionValidityError, ValidTransaction, + }, + DispatchError, KeyTypeId, Perbill, RuntimeAppPublic, +}; +use sp_session::{GetSessionNumber, GetValidatorCount}; +use sp_staking::{ + offence::{Kind, Offence, OffenceReportSystem, ReportOffence}, + SessionIndex, +}; +use sp_std::prelude::*; + +use super::{Call, Config, Error, Pallet, LOG_TARGET}; + +/// A round number and set id which point on the time of an offence. +#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode)] +pub struct TimeSlot { + // The order of these matters for `derive(Ord)`. + /// BEEFY Set ID. + pub set_id: ValidatorSetId, + /// Round number. + pub round: N, +} + +/// BEEFY equivocation offence report. +pub struct EquivocationOffence +where + N: Copy + Clone + PartialOrd + Ord + Eq + PartialEq + Encode + Decode, +{ + /// Time slot at which this incident happened. + pub time_slot: TimeSlot, + /// The session index in which the incident happened. + pub session_index: SessionIndex, + /// The size of the validator set at the time of the offence. + pub validator_set_count: u32, + /// The authority which produced this equivocation. + pub offender: Offender, +} + +impl Offence for EquivocationOffence +where + N: Copy + Clone + PartialOrd + Ord + Eq + PartialEq + Encode + Decode, +{ + const ID: Kind = *b"beefy:equivocati"; + type TimeSlot = TimeSlot; + + fn offenders(&self) -> Vec { + vec![self.offender.clone()] + } + + fn session_index(&self) -> SessionIndex { + self.session_index + } + + fn validator_set_count(&self) -> u32 { + self.validator_set_count + } + + fn time_slot(&self) -> Self::TimeSlot { + self.time_slot + } + + // The formula is min((3k / n)^2, 1) + // where k = offenders_number and n = validators_number + fn slash_fraction(&self, offenders_count: u32) -> Perbill { + // Perbill type domain is [0, 1] by definition + Perbill::from_rational(3 * offenders_count, self.validator_set_count).square() + } +} + +/// BEEFY equivocation offence report system. +/// +/// This type implements `OffenceReportSystem` such that: +/// - Equivocation reports are published on-chain as unsigned extrinsic via +/// `offchain::SendTransactionTypes`. +/// - On-chain validity checks and processing are mostly delegated to the user provided generic +/// types implementing `KeyOwnerProofSystem` and `ReportOffence` traits. +/// - Offence reporter for unsigned transactions is fetched via the the authorship pallet. +pub struct EquivocationReportSystem(sp_std::marker::PhantomData<(T, R, P, L)>); + +/// Equivocation evidence convenience alias. +pub type EquivocationEvidenceFor = ( + EquivocationProof< + BlockNumberFor, + ::BeefyId, + <::BeefyId as RuntimeAppPublic>::Signature, + >, + ::KeyOwnerProof, +); + +impl OffenceReportSystem, EquivocationEvidenceFor> + for EquivocationReportSystem +where + T: Config + pallet_authorship::Config + frame_system::offchain::SendTransactionTypes>, + R: ReportOffence< + T::AccountId, + P::IdentificationTuple, + EquivocationOffence>, + >, + P: KeyOwnerProofSystem<(KeyTypeId, T::BeefyId), Proof = T::KeyOwnerProof>, + P::IdentificationTuple: Clone, + L: Get, +{ + type Longevity = L; + + fn publish_evidence(evidence: EquivocationEvidenceFor) -> Result<(), ()> { + use frame_system::offchain::SubmitTransaction; + let (equivocation_proof, key_owner_proof) = evidence; + + let call = Call::report_equivocation_unsigned { + equivocation_proof: Box::new(equivocation_proof), + key_owner_proof, + }; + + let res = SubmitTransaction::>::submit_unsigned_transaction(call.into()); + match res { + Ok(_) => info!(target: LOG_TARGET, "Submitted equivocation report."), + Err(e) => error!(target: LOG_TARGET, "Error submitting equivocation report: {:?}", e), + } + res + } + + fn check_evidence( + evidence: EquivocationEvidenceFor, + ) -> Result<(), TransactionValidityError> { + let (equivocation_proof, key_owner_proof) = evidence; + + // Check the membership proof to extract the offender's id + let key = (KEY_TYPE, equivocation_proof.offender_id().clone()); + let offender = P::check_proof(key, key_owner_proof).ok_or(InvalidTransaction::BadProof)?; + + // Check if the offence has already been reported, and if so then we can discard the report. + let time_slot = TimeSlot { + set_id: equivocation_proof.set_id(), + round: *equivocation_proof.round_number(), + }; + + if R::is_known_offence(&[offender], &time_slot) { + Err(InvalidTransaction::Stale.into()) + } else { + Ok(()) + } + } + + fn process_evidence( + reporter: Option, + evidence: EquivocationEvidenceFor, + ) -> Result<(), DispatchError> { + let (equivocation_proof, key_owner_proof) = evidence; + let reporter = reporter.or_else(|| >::author()); + let offender = equivocation_proof.offender_id().clone(); + + // We check the equivocation within the context of its set id (and + // associated session) and round. We also need to know the validator + // set count at the time of the offence since it is required to calculate + // the slash amount. + let set_id = equivocation_proof.set_id(); + let round = *equivocation_proof.round_number(); + let session_index = key_owner_proof.session(); + let validator_set_count = key_owner_proof.validator_count(); + + // Validate the key ownership proof extracting the id of the offender. + let offender = P::check_proof((KEY_TYPE, offender), key_owner_proof) + .ok_or(Error::::InvalidKeyOwnershipProof)?; + + // Validate equivocation proof (check votes are different and signatures are valid). + if !sp_consensus_beefy::check_equivocation_proof(&equivocation_proof) { + return Err(Error::::InvalidEquivocationProof.into()) + } + + // Check that the session id for the membership proof is within the + // bounds of the set id reported in the equivocation. + let set_id_session_index = + crate::SetIdSession::::get(set_id).ok_or(Error::::InvalidEquivocationProof)?; + if session_index != set_id_session_index { + return Err(Error::::InvalidEquivocationProof.into()) + } + + let offence = EquivocationOffence { + time_slot: TimeSlot { set_id, round }, + session_index, + validator_set_count, + offender, + }; + + R::report_offence(reporter.into_iter().collect(), offence) + .map_err(|_| Error::::DuplicateOffenceReport)?; + + Ok(()) + } +} + +/// Methods for the `ValidateUnsigned` implementation: +/// It restricts calls to `report_equivocation_unsigned` to local calls (i.e. extrinsics generated +/// on this node) or that already in a block. This guarantees that only block authors can include +/// unsigned equivocation reports. +impl Pallet { + pub fn validate_unsigned(source: TransactionSource, call: &Call) -> TransactionValidity { + if let Call::report_equivocation_unsigned { equivocation_proof, key_owner_proof } = call { + // discard equivocation report not coming from the local node + match source { + TransactionSource::Local | TransactionSource::InBlock => { /* allowed */ }, + _ => { + log::warn!( + target: LOG_TARGET, + "rejecting unsigned report equivocation transaction because it is not local/in-block." + ); + return InvalidTransaction::Call.into() + }, + } + + let evidence = (*equivocation_proof.clone(), key_owner_proof.clone()); + T::EquivocationReportSystem::check_evidence(evidence)?; + + let longevity = + >::Longevity::get(); + + ValidTransaction::with_tag_prefix("BeefyEquivocation") + // We assign the maximum priority for any equivocation report. + .priority(TransactionPriority::MAX) + // Only one equivocation report for the same offender at the same slot. + .and_provides(( + equivocation_proof.offender_id().clone(), + equivocation_proof.set_id(), + *equivocation_proof.round_number(), + )) + .longevity(longevity) + // We don't propagate this. This can never be included on a remote node. + .propagate(false) + .build() + } else { + InvalidTransaction::Call.into() + } + } + + pub fn pre_dispatch(call: &Call) -> Result<(), TransactionValidityError> { + if let Call::report_equivocation_unsigned { equivocation_proof, key_owner_proof } = call { + let evidence = (*equivocation_proof.clone(), key_owner_proof.clone()); + T::EquivocationReportSystem::check_evidence(evidence) + } else { + Err(InvalidTransaction::Call.into()) + } + } +} diff --git a/frame/beefy/src/lib.rs b/frame/beefy/src/lib.rs index 4cb23107e784..35d3273e1ef7 100644 --- a/frame/beefy/src/lib.rs +++ b/frame/beefy/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,58 +20,103 @@ use codec::{Encode, MaxEncodedLen}; use frame_support::{ + dispatch::{DispatchResultWithPostInfo, Pays}, log, + pallet_prelude::*, traits::{Get, OneSessionHandler}, + weights::Weight, BoundedSlice, BoundedVec, Parameter, }; - +use frame_system::{ + ensure_none, ensure_signed, + pallet_prelude::{BlockNumberFor, OriginFor}, +}; use sp_runtime::{ generic::DigestItem, traits::{IsMember, Member}, RuntimeAppPublic, }; +use sp_session::{GetSessionNumber, GetValidatorCount}; +use sp_staking::{offence::OffenceReportSystem, SessionIndex}; use sp_std::prelude::*; -use beefy_primitives::{ - AuthorityIndex, ConsensusLog, OnNewValidatorSet, ValidatorSet, BEEFY_ENGINE_ID, - GENESIS_AUTHORITY_SET_ID, +use sp_consensus_beefy::{ + AuthorityIndex, BeefyAuthorityId, ConsensusLog, EquivocationProof, OnNewValidatorSet, + ValidatorSet, BEEFY_ENGINE_ID, GENESIS_AUTHORITY_SET_ID, }; +mod default_weights; +mod equivocation; #[cfg(test)] mod mock; - #[cfg(test)] mod tests; +pub use crate::equivocation::{EquivocationOffence, EquivocationReportSystem, TimeSlot}; pub use pallet::*; +use crate::equivocation::EquivocationEvidenceFor; + +const LOG_TARGET: &str = "runtime::beefy"; + #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::BlockNumberFor; #[pallet::config] pub trait Config: frame_system::Config { /// Authority identifier type type BeefyId: Member + Parameter - + RuntimeAppPublic + // todo: use custom signature hashing type instead of hardcoded `Keccak256` + + BeefyAuthorityId + MaybeSerializeDeserialize + MaxEncodedLen; /// The maximum number of authorities that can be added. + #[pallet::constant] type MaxAuthorities: Get; + /// The maximum number of nominators for each validator. + #[pallet::constant] + type MaxNominators: Get; + + /// The maximum number of entries to keep in the set id to session index mapping. + /// + /// Since the `SetIdSession` map is only used for validating equivocations this + /// value should relate to the bonding duration of whatever staking system is + /// being used (if any). If equivocation handling is not enabled then this value + /// can be zero. + #[pallet::constant] + type MaxSetIdSessionEntries: Get; + /// A hook to act on the new BEEFY validator set. /// /// For some applications it might be beneficial to make the BEEFY validator set available /// externally apart from having it in the storage. For instance you might cache a light /// weight MMR root over validators and make it available for Light Clients. type OnNewValidatorSet: OnNewValidatorSet<::BeefyId>; + + /// Weights for this pallet. + type WeightInfo: WeightInfo; + + /// The proof of key ownership, used for validating equivocation reports + /// The proof must include the session index and validator count of the + /// session at which the equivocation occurred. + type KeyOwnerProof: Parameter + GetSessionNumber + GetValidatorCount; + + /// The equivocation handling subsystem. + /// + /// Defines methods to publish, check and process an equivocation offence. + type EquivocationReportSystem: OffenceReportSystem< + Option, + EquivocationEvidenceFor, + >; } #[pallet::pallet] - pub struct Pallet(PhantomData); + pub struct Pallet(_); /// The current authorities set #[pallet::storage] @@ -83,7 +128,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn validator_set_id)] pub(super) type ValidatorSetId = - StorageValue<_, beefy_primitives::ValidatorSetId, ValueQuery>; + StorageValue<_, sp_consensus_beefy::ValidatorSetId, ValueQuery>; /// Authorities set scheduled to be used with the next session #[pallet::storage] @@ -91,25 +136,147 @@ pub mod pallet { pub(super) type NextAuthorities = StorageValue<_, BoundedVec, ValueQuery>; + /// A mapping from BEEFY set ID to the index of the *most recent* session for which its + /// members were responsible. + /// + /// This is only used for validating equivocation proofs. An equivocation proof must + /// contains a key-ownership proof for a given session, therefore we need a way to tie + /// together sessions and BEEFY set ids, i.e. we need to validate that a validator + /// was the owner of a given key on a given session, and what the active set ID was + /// during that session. + /// + /// TWOX-NOTE: `ValidatorSetId` is not under user control. + #[pallet::storage] + #[pallet::getter(fn session_for_set)] + pub(super) type SetIdSession = + StorageMap<_, Twox64Concat, sp_consensus_beefy::ValidatorSetId, SessionIndex>; + + /// Block number where BEEFY consensus is enabled/started. + /// By changing this (through governance or sudo), BEEFY consensus is effectively + /// restarted from the new block number. + #[pallet::storage] + #[pallet::getter(fn genesis_block)] + pub(super) type GenesisBlock = + StorageValue<_, Option>, ValueQuery>; + #[pallet::genesis_config] pub struct GenesisConfig { + /// Initial set of BEEFY authorities. pub authorities: Vec, + /// Block number where BEEFY consensus should start. + /// Should match the session where initial authorities are active. + /// *Note:* Ideally use block number where GRANDPA authorities are changed, + /// to guarantee the client gets a finality notification for exactly this block. + pub genesis_block: Option>, } - #[cfg(feature = "std")] impl Default for GenesisConfig { fn default() -> Self { - Self { authorities: Vec::new() } + // BEEFY genesis will be first BEEFY-MANDATORY block, + // use block number one instead of chain-genesis. + let genesis_block = Some(sp_runtime::traits::One::one()); + Self { authorities: Vec::new(), genesis_block } } } #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { - Pallet::::initialize_authorities(&self.authorities) + Pallet::::initialize(&self.authorities) // we panic here as runtime maintainers can simply reconfigure genesis and restart // the chain easily .expect("Authorities vec too big"); + >::put(&self.genesis_block); + } + } + + #[pallet::error] + pub enum Error { + /// A key ownership proof provided as part of an equivocation report is invalid. + InvalidKeyOwnershipProof, + /// An equivocation proof provided as part of an equivocation report is invalid. + InvalidEquivocationProof, + /// A given equivocation report is valid but already previously reported. + DuplicateOffenceReport, + } + + #[pallet::call] + impl Pallet { + /// Report voter equivocation/misbehavior. This method will verify the + /// equivocation proof and validate the given key ownership proof + /// against the extracted offender. If both are valid, the offence + /// will be reported. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::report_equivocation( + key_owner_proof.validator_count(), + T::MaxNominators::get(), + ))] + pub fn report_equivocation( + origin: OriginFor, + equivocation_proof: Box< + EquivocationProof< + BlockNumberFor, + T::BeefyId, + ::Signature, + >, + >, + key_owner_proof: T::KeyOwnerProof, + ) -> DispatchResultWithPostInfo { + let reporter = ensure_signed(origin)?; + + T::EquivocationReportSystem::process_evidence( + Some(reporter), + (*equivocation_proof, key_owner_proof), + )?; + // Waive the fee since the report is valid and beneficial + Ok(Pays::No.into()) + } + + /// Report voter equivocation/misbehavior. This method will verify the + /// equivocation proof and validate the given key ownership proof + /// against the extracted offender. If both are valid, the offence + /// will be reported. + /// + /// This extrinsic must be called unsigned and it is expected that only + /// block authors will call it (validated in `ValidateUnsigned`), as such + /// if the block author is defined it will be defined as the equivocation + /// reporter. + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::report_equivocation( + key_owner_proof.validator_count(), + T::MaxNominators::get(), + ))] + pub fn report_equivocation_unsigned( + origin: OriginFor, + equivocation_proof: Box< + EquivocationProof< + BlockNumberFor, + T::BeefyId, + ::Signature, + >, + >, + key_owner_proof: T::KeyOwnerProof, + ) -> DispatchResultWithPostInfo { + ensure_none(origin)?; + + T::EquivocationReportSystem::process_evidence( + None, + (*equivocation_proof, key_owner_proof), + )?; + Ok(Pays::No.into()) + } + } + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + + fn pre_dispatch(call: &Self::Call) -> Result<(), TransactionValidityError> { + Self::pre_dispatch(call) + } + + fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity { + Self::validate_unsigned(source, call) } } } @@ -118,10 +285,24 @@ impl Pallet { /// Return the current active BEEFY validator set. pub fn validator_set() -> Option> { let validators: BoundedVec = Self::authorities(); - let id: beefy_primitives::ValidatorSetId = Self::validator_set_id(); + let id: sp_consensus_beefy::ValidatorSetId = Self::validator_set_id(); ValidatorSet::::new(validators, id) } + /// Submits an extrinsic to report an equivocation. This method will create + /// an unsigned extrinsic with a call to `report_equivocation_unsigned` and + /// will push the transaction to the pool. Only useful in an offchain context. + pub fn submit_unsigned_equivocation_report( + equivocation_proof: EquivocationProof< + BlockNumberFor, + T::BeefyId, + ::Signature, + >, + key_owner_proof: T::KeyOwnerProof, + ) -> Option<()> { + T::EquivocationReportSystem::publish_evidence((equivocation_proof, key_owner_proof)).ok() + } + fn change_authorities( new: BoundedVec, queued: BoundedVec, @@ -150,7 +331,7 @@ impl Pallet { } } - fn initialize_authorities(authorities: &Vec) -> Result<(), ()> { + fn initialize(authorities: &Vec) -> Result<(), ()> { if authorities.is_empty() { return Ok(()) } @@ -180,6 +361,12 @@ impl Pallet { ); } } + + // NOTE: initialize first session of first set. this is necessary for + // the genesis set and session since we only update the set -> session + // mapping whenever a new session starts, i.e. through `on_new_session`. + SetIdSession::::insert(0, 0); + Ok(()) } } @@ -188,7 +375,10 @@ impl sp_runtime::BoundToRuntimeAppPublic for Pallet { type Public = T::BeefyId; } -impl OneSessionHandler for Pallet { +impl OneSessionHandler for Pallet +where + T: pallet_session::Config, +{ type Key = T::BeefyId; fn on_genesis_session<'a, I: 'a>(validators: I) @@ -198,7 +388,7 @@ impl OneSessionHandler for Pallet { let authorities = validators.map(|(_, k)| k).collect::>(); // we panic here as runtime maintainers can simply reconfigure genesis and restart the // chain easily - Self::initialize_authorities(&authorities).expect("Authorities vec too big"); + Self::initialize(&authorities).expect("Authorities vec too big"); } fn on_new_session<'a, I: 'a>(_changed: bool, validators: I, queued_validators: I) @@ -208,9 +398,10 @@ impl OneSessionHandler for Pallet { let next_authorities = validators.map(|(_, k)| k).collect::>(); if next_authorities.len() as u32 > T::MaxAuthorities::get() { log::error!( - target: "runtime::beefy", + target: LOG_TARGET, "authorities list {:?} truncated to length {}", - next_authorities, T::MaxAuthorities::get(), + next_authorities, + T::MaxAuthorities::get(), ); } let bounded_next_authorities = @@ -219,9 +410,10 @@ impl OneSessionHandler for Pallet { let next_queued_authorities = queued_validators.map(|(_, k)| k).collect::>(); if next_queued_authorities.len() as u32 > T::MaxAuthorities::get() { log::error!( - target: "runtime::beefy", + target: LOG_TARGET, "queued authorities list {:?} truncated to length {}", - next_queued_authorities, T::MaxAuthorities::get(), + next_queued_authorities, + T::MaxAuthorities::get(), ); } let bounded_next_queued_authorities = @@ -230,6 +422,16 @@ impl OneSessionHandler for Pallet { // Always issue a change on each `session`, even if validator set hasn't changed. // We want to have at least one BEEFY mandatory block per session. Self::change_authorities(bounded_next_authorities, bounded_next_queued_authorities); + + let validator_set_id = Self::validator_set_id(); + // Update the mapping for the new set id that corresponds to the latest session (i.e. now). + let session_index = >::current_index(); + SetIdSession::::insert(validator_set_id, &session_index); + // Prune old entry if limit reached. + let max_set_id_session_entries = T::MaxSetIdSessionEntries::get().max(1); + if validator_set_id >= max_set_id_session_entries { + SetIdSession::::remove(validator_set_id - max_set_id_session_entries); + } } fn on_disabled(i: u32) { @@ -247,3 +449,7 @@ impl IsMember for Pallet { Self::authorities().iter().any(|id| id == authority_id) } } + +pub trait WeightInfo { + fn report_equivocation(validator_count: u32, max_nominators_per_validator: u32) -> Weight; +} diff --git a/frame/beefy/src/mock.rs b/frame/beefy/src/mock.rs index ad3a672333dd..26a13fe16ca4 100644 --- a/frame/beefy/src/mock.rs +++ b/frame/beefy/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,24 +17,31 @@ use std::vec; +use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ construct_runtime, parameter_types, sp_io::TestExternalities, - traits::{ConstU16, ConstU32, ConstU64, GenesisBuild}, + traits::{ConstU16, ConstU32, ConstU64, KeyOwnerProofSystem, OnFinalize, OnInitialize}, BasicExternalities, }; -use sp_core::H256; +use pallet_session::historical as pallet_session_historical; +use sp_core::{crypto::KeyTypeId, ConstU128, H256}; use sp_runtime::{ app_crypto::ecdsa::Public, + curve::PiecewiseLinear, impl_opaque_keys, - testing::Header, - traits::{BlakeTwo256, ConvertInto, IdentityLookup, OpaqueKeys}, - Perbill, + testing::TestXt, + traits::{BlakeTwo256, IdentityLookup, OpaqueKeys}, + BuildStorage, Perbill, }; +use sp_staking::{EraIndex, SessionIndex}; use crate as pallet_beefy; -pub use beefy_primitives::{crypto::AuthorityId as BeefyId, ConsensusLog, BEEFY_ENGINE_ID}; +pub use sp_consensus_beefy::{ + crypto::{AuthorityId as BeefyId, AuthoritySignature as BeefySignature}, + ConsensusLog, EquivocationProof, BEEFY_ENGINE_ID, +}; impl_opaque_keys! { pub struct MockSessionKeys { @@ -42,18 +49,20 @@ impl_opaque_keys! { } } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Beefy: pallet_beefy::{Pallet, Config, Storage}, - Session: pallet_session::{Pallet, Call, Storage, Event, Config}, + System: frame_system, + Authorship: pallet_authorship, + Timestamp: pallet_timestamp, + Balances: pallet_balances, + Beefy: pallet_beefy, + Staking: pallet_staking, + Session: pallet_session, + Offences: pallet_offences, + Historical: pallet_session_historical, } ); @@ -63,19 +72,18 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); type PalletInfo = PalletInfo; - type AccountData = (); + type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); @@ -84,10 +92,31 @@ impl frame_system::Config for Test { type MaxConsumers = ConstU32<16>; } +impl frame_system::offchain::SendTransactionTypes for Test +where + RuntimeCall: From, +{ + type OverarchingCall = RuntimeCall; + type Extrinsic = TestXt; +} + +parameter_types! { + pub const Period: u64 = 1; + pub const ReportLongevity: u64 = + BondingDuration::get() as u64 * SessionsPerEra::get() as u64 * Period::get(); + pub const MaxSetIdSessionEntries: u32 = BondingDuration::get() * SessionsPerEra::get(); +} + impl pallet_beefy::Config for Test { type BeefyId = BeefyId; type MaxAuthorities = ConstU32<100>; + type MaxNominators = ConstU32<1000>; + type MaxSetIdSessionEntries = MaxSetIdSessionEntries; type OnNewValidatorSet = (); + type WeightInfo = (); + type KeyOwnerProof = >::Proof; + type EquivocationReportSystem = + super::EquivocationReportSystem; } parameter_types! { @@ -97,29 +126,111 @@ parameter_types! { impl pallet_session::Config for Test { type RuntimeEvent = RuntimeEvent; type ValidatorId = u64; - type ValidatorIdOf = ConvertInto; + type ValidatorIdOf = pallet_staking::StashOf; type ShouldEndSession = pallet_session::PeriodicSessions, ConstU64<0>>; type NextSessionRotation = pallet_session::PeriodicSessions, ConstU64<0>>; - type SessionManager = MockSessionManager; + type SessionManager = pallet_session::historical::NoteHistoricalRoot; type SessionHandler = ::KeyTypeIdProviders; type Keys = MockSessionKeys; type WeightInfo = (); } -pub struct MockSessionManager; +impl pallet_session::historical::Config for Test { + type FullIdentification = pallet_staking::Exposure; + type FullIdentificationOf = pallet_staking::ExposureOf; +} -impl pallet_session::SessionManager for MockSessionManager { - fn end_session(_: sp_staking::SessionIndex) {} - fn start_session(_: sp_staking::SessionIndex) {} - fn new_session(idx: sp_staking::SessionIndex) -> Option> { - if idx == 0 || idx == 1 { - Some(vec![1, 2]) - } else if idx == 2 { - Some(vec![3, 4]) - } else { - None - } - } +impl pallet_authorship::Config for Test { + type FindAuthor = (); + type EventHandler = (); +} + +impl pallet_balances::Config for Test { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = u128; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU128<1>; + type AccountStore = System; + type WeightInfo = (); + type RuntimeHoldReason = (); + type MaxHolds = (); + type FreezeIdentifier = (); + type MaxFreezes = (); +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<3>; + type WeightInfo = (); +} + +pallet_staking_reward_curve::build! { + const REWARD_CURVE: PiecewiseLinear<'static> = curve!( + min_inflation: 0_025_000u64, + max_inflation: 0_100_000, + ideal_stake: 0_500_000, + falloff: 0_050_000, + max_piece_count: 40, + test_precision: 0_005_000, + ); +} + +parameter_types! { + pub const SessionsPerEra: SessionIndex = 3; + pub const BondingDuration: EraIndex = 3; + pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; + pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); +} + +pub struct OnChainSeqPhragmen; +impl onchain::Config for OnChainSeqPhragmen { + type System = Test; + type Solver = SequentialPhragmen; + type DataProvider = Staking; + type WeightInfo = (); + type MaxWinners = ConstU32<100>; + type VotersBound = ConstU32<{ u32::MAX }>; + type TargetsBound = ConstU32<{ u32::MAX }>; +} + +impl pallet_staking::Config for Test { + type MaxNominations = ConstU32<16>; + type RewardRemainder = (); + type CurrencyToVote = (); + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type CurrencyBalance = ::Balance; + type Slash = (); + type Reward = (); + type SessionsPerEra = SessionsPerEra; + type BondingDuration = BondingDuration; + type SlashDeferDuration = (); + type AdminOrigin = frame_system::EnsureRoot; + type SessionInterface = Self; + type UnixTime = pallet_timestamp::Pallet; + type EraPayout = pallet_staking::ConvertCurve; + type MaxNominatorRewardedPerValidator = ConstU32<64>; + type OffendingValidatorsThreshold = OffendingValidatorsThreshold; + type NextNewSession = Session; + type ElectionProvider = onchain::OnChainExecution; + type GenesisElectionProvider = Self::ElectionProvider; + type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; + type TargetList = pallet_staking::UseValidatorsMap; + type MaxUnlockingChunks = ConstU32<32>; + type HistoryDepth = ConstU32<84>; + type EventListeners = (); + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; + type WeightInfo = (); +} + +impl pallet_offences::Config for Test { + type RuntimeEvent = RuntimeEvent; + type IdentificationTuple = pallet_session::historical::IdentificationTuple; + type OnOffenceHandler = Staking; } // Note, that we can't use `UintAuthorityId` here. Reason is that the implementation @@ -134,20 +245,27 @@ pub fn mock_beefy_id(id: u8) -> BeefyId { BeefyId::from(pk) } -pub fn mock_authorities(vec: Vec) -> Vec<(u64, BeefyId)> { - vec.into_iter().map(|id| ((id as u64), mock_beefy_id(id))).collect() +pub fn mock_authorities(vec: Vec) -> Vec { + vec.into_iter().map(|id| mock_beefy_id(id)).collect() } pub fn new_test_ext(ids: Vec) -> TestExternalities { new_test_ext_raw_authorities(mock_authorities(ids)) } -pub fn new_test_ext_raw_authorities(authorities: Vec<(u64, BeefyId)>) -> TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); +pub fn new_test_ext_raw_authorities(authorities: Vec) -> TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + let balances: Vec<_> = (0..authorities.len()).map(|i| (i as u64, 10_000_000)).collect(); + + pallet_balances::GenesisConfig:: { balances } + .assimilate_storage(&mut t) + .unwrap(); let session_keys: Vec<_> = authorities .iter() - .map(|id| (id.0 as u64, id.0 as u64, MockSessionKeys { dummy: id.1.clone() })) + .enumerate() + .map(|(i, k)| (i as u64, i as u64, MockSessionKeys { dummy: k.clone() })) .collect(); BasicExternalities::execute_with_storage(&mut t, || { @@ -160,5 +278,54 @@ pub fn new_test_ext_raw_authorities(authorities: Vec<(u64, BeefyId)>) -> TestExt .assimilate_storage(&mut t) .unwrap(); + // controllers are same as stash + let stakers: Vec<_> = (0..authorities.len()) + .map(|i| (i as u64, i as u64, 10_000, pallet_staking::StakerStatus::::Validator)) + .collect(); + + let staking_config = pallet_staking::GenesisConfig:: { + stakers, + validator_count: 2, + force_era: pallet_staking::Forcing::ForceNew, + minimum_validator_count: 0, + invulnerables: vec![], + ..Default::default() + }; + + staking_config.assimilate_storage(&mut t).unwrap(); + t.into() } + +pub fn start_session(session_index: SessionIndex) { + for i in Session::current_index()..session_index { + System::on_finalize(System::block_number()); + Session::on_finalize(System::block_number()); + Staking::on_finalize(System::block_number()); + Beefy::on_finalize(System::block_number()); + + let parent_hash = if System::block_number() > 1 { + let hdr = System::finalize(); + hdr.hash() + } else { + System::parent_hash() + }; + + System::reset_events(); + System::initialize(&(i as u64 + 1), &parent_hash, &Default::default()); + System::set_block_number((i + 1).into()); + Timestamp::set_timestamp(System::block_number() * 6000); + + System::on_initialize(System::block_number()); + Session::on_initialize(System::block_number()); + Staking::on_initialize(System::block_number()); + Beefy::on_initialize(System::block_number()); + } + + assert_eq!(Session::current_index(), session_index); +} + +pub fn start_era(era_index: EraIndex) { + start_session((era_index * 3).into()); + assert_eq!(Staking::current_era(), Some(era_index)); +} diff --git a/frame/beefy/src/tests.rs b/frame/beefy/src/tests.rs index 4136b0c1f1ec..8a8c6ae1092b 100644 --- a/frame/beefy/src/tests.rs +++ b/frame/beefy/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,14 +17,21 @@ use std::vec; -use beefy_primitives::ValidatorSet; use codec::Encode; +use sp_consensus_beefy::{ + check_equivocation_proof, generate_equivocation_proof, known_payloads::MMR_ROOT_ID, + Keyring as BeefyKeyring, Payload, ValidatorSet, +}; use sp_runtime::DigestItem; -use frame_support::traits::OnInitialize; +use frame_support::{ + assert_err, assert_ok, + dispatch::{GetDispatchInfo, Pays}, + traits::{Currency, KeyOwnerProofSystem, OnInitialize}, +}; -use crate::mock::*; +use crate::{mock::*, Call, Config, Error, Weight, WeightInfo}; fn init_block(block: u64) { System::set_block_number(block); @@ -37,12 +44,13 @@ pub fn beefy_log(log: ConsensusLog) -> DigestItem { #[test] fn genesis_session_initializes_authorities() { - let want = vec![mock_beefy_id(1), mock_beefy_id(2), mock_beefy_id(3), mock_beefy_id(4)]; + let authorities = mock_authorities(vec![1, 2, 3, 4]); + let want = authorities.clone(); - new_test_ext(vec![1, 2, 3, 4]).execute_with(|| { + new_test_ext_raw_authorities(authorities).execute_with(|| { let authorities = Beefy::authorities(); - assert!(authorities.len() == 2); + assert_eq!(authorities.len(), 4); assert_eq!(want[0], authorities[0]); assert_eq!(want[1], authorities[1]); @@ -50,7 +58,7 @@ fn genesis_session_initializes_authorities() { let next_authorities = Beefy::next_authorities(); - assert!(next_authorities.len() == 2); + assert_eq!(next_authorities.len(), 4); assert_eq!(want[0], next_authorities[0]); assert_eq!(want[1], next_authorities[1]); }); @@ -58,6 +66,9 @@ fn genesis_session_initializes_authorities() { #[test] fn session_change_updates_authorities() { + let authorities = mock_authorities(vec![1, 2, 3, 4]); + let want_validators = authorities.clone(); + new_test_ext(vec![1, 2, 3, 4]).execute_with(|| { assert!(0 == Beefy::validator_set_id()); @@ -66,7 +77,7 @@ fn session_change_updates_authorities() { assert!(1 == Beefy::validator_set_id()); let want = beefy_log(ConsensusLog::AuthoritiesChange( - ValidatorSet::new(vec![mock_beefy_id(1), mock_beefy_id(2)], 1).unwrap(), + ValidatorSet::new(want_validators, 1).unwrap(), )); let log = System::digest().logs[0].clone(); @@ -77,7 +88,7 @@ fn session_change_updates_authorities() { assert!(2 == Beefy::validator_set_id()); let want = beefy_log(ConsensusLog::AuthoritiesChange( - ValidatorSet::new(vec![mock_beefy_id(3), mock_beefy_id(4)], 2).unwrap(), + ValidatorSet::new(vec![mock_beefy_id(2), mock_beefy_id(4)], 2).unwrap(), )); let log = System::digest().logs[1].clone(); @@ -92,16 +103,18 @@ fn session_change_updates_next_authorities() { new_test_ext(vec![1, 2, 3, 4]).execute_with(|| { let next_authorities = Beefy::next_authorities(); - assert!(next_authorities.len() == 2); + assert_eq!(next_authorities.len(), 4); assert_eq!(want[0], next_authorities[0]); assert_eq!(want[1], next_authorities[1]); + assert_eq!(want[2], next_authorities[2]); + assert_eq!(want[3], next_authorities[3]); init_block(1); let next_authorities = Beefy::next_authorities(); - assert!(next_authorities.len() == 2); - assert_eq!(want[2], next_authorities[0]); + assert_eq!(next_authorities.len(), 2); + assert_eq!(want[1], next_authorities[0]); assert_eq!(want[3], next_authorities[1]); }); } @@ -126,6 +139,10 @@ fn validator_set_updates_work() { new_test_ext(vec![1, 2, 3, 4]).execute_with(|| { let vs = Beefy::validator_set().unwrap(); assert_eq!(vs.id(), 0u64); + assert_eq!(want[0], vs.validators()[0]); + assert_eq!(want[1], vs.validators()[1]); + assert_eq!(want[2], vs.validators()[2]); + assert_eq!(want[3], vs.validators()[3]); init_block(1); @@ -140,7 +157,644 @@ fn validator_set_updates_work() { let vs = Beefy::validator_set().unwrap(); assert_eq!(vs.id(), 2u64); - assert_eq!(want[2], vs.validators()[0]); + assert_eq!(want[1], vs.validators()[0]); assert_eq!(want[3], vs.validators()[1]); }); } + +#[test] +fn cleans_up_old_set_id_session_mappings() { + new_test_ext(vec![1, 2, 3, 4]).execute_with(|| { + let max_set_id_session_entries = MaxSetIdSessionEntries::get(); + + // we have 3 sessions per era + let era_limit = max_set_id_session_entries / 3; + // sanity check against division precision loss + assert_eq!(0, max_set_id_session_entries % 3); + // go through `max_set_id_session_entries` sessions + start_era(era_limit); + + // we should have a session id mapping for all the set ids from + // `max_set_id_session_entries` eras we have observed + for i in 1..=max_set_id_session_entries { + assert!(Beefy::session_for_set(i as u64).is_some()); + } + + // go through another `max_set_id_session_entries` sessions + start_era(era_limit * 2); + + // we should keep tracking the new mappings for new sessions + for i in max_set_id_session_entries + 1..=max_set_id_session_entries * 2 { + assert!(Beefy::session_for_set(i as u64).is_some()); + } + + // but the old ones should have been pruned by now + for i in 1..=max_set_id_session_entries { + assert!(Beefy::session_for_set(i as u64).is_none()); + } + }); +} + +/// Returns a list with 3 authorities with known keys: +/// Alice, Bob and Charlie. +pub fn test_authorities() -> Vec { + let authorities = vec![BeefyKeyring::Alice, BeefyKeyring::Bob, BeefyKeyring::Charlie]; + authorities.into_iter().map(|id| id.public()).collect() +} + +#[test] +fn should_sign_and_verify() { + use sp_runtime::traits::Keccak256; + + let set_id = 3; + let payload1 = Payload::from_single_entry(MMR_ROOT_ID, vec![42]); + let payload2 = Payload::from_single_entry(MMR_ROOT_ID, vec![128]); + + // generate an equivocation proof, with two votes in the same round for + // same payload signed by the same key + let equivocation_proof = generate_equivocation_proof( + (1, payload1.clone(), set_id, &BeefyKeyring::Bob), + (1, payload1.clone(), set_id, &BeefyKeyring::Bob), + ); + // expect invalid equivocation proof + assert!(!check_equivocation_proof::<_, _, Keccak256>(&equivocation_proof)); + + // generate an equivocation proof, with two votes in different rounds for + // different payloads signed by the same key + let equivocation_proof = generate_equivocation_proof( + (1, payload1.clone(), set_id, &BeefyKeyring::Bob), + (2, payload2.clone(), set_id, &BeefyKeyring::Bob), + ); + // expect invalid equivocation proof + assert!(!check_equivocation_proof::<_, _, Keccak256>(&equivocation_proof)); + + // generate an equivocation proof, with two votes by different authorities + let equivocation_proof = generate_equivocation_proof( + (1, payload1.clone(), set_id, &BeefyKeyring::Alice), + (1, payload2.clone(), set_id, &BeefyKeyring::Bob), + ); + // expect invalid equivocation proof + assert!(!check_equivocation_proof::<_, _, Keccak256>(&equivocation_proof)); + + // generate an equivocation proof, with two votes in different set ids + let equivocation_proof = generate_equivocation_proof( + (1, payload1.clone(), set_id, &BeefyKeyring::Bob), + (1, payload2.clone(), set_id + 1, &BeefyKeyring::Bob), + ); + // expect invalid equivocation proof + assert!(!check_equivocation_proof::<_, _, Keccak256>(&equivocation_proof)); + + // generate an equivocation proof, with two votes in the same round for + // different payloads signed by the same key + let payload2 = Payload::from_single_entry(MMR_ROOT_ID, vec![128]); + let equivocation_proof = generate_equivocation_proof( + (1, payload1, set_id, &BeefyKeyring::Bob), + (1, payload2, set_id, &BeefyKeyring::Bob), + ); + // expect valid equivocation proof + assert!(check_equivocation_proof::<_, _, Keccak256>(&equivocation_proof)); +} + +#[test] +fn report_equivocation_current_set_works() { + let authorities = test_authorities(); + + new_test_ext_raw_authorities(authorities).execute_with(|| { + assert_eq!(Staking::current_era(), Some(0)); + assert_eq!(Session::current_index(), 0); + + start_era(1); + + let block_num = System::block_number(); + let validator_set = Beefy::validator_set().unwrap(); + let authorities = validator_set.validators(); + let set_id = validator_set.id(); + let validators = Session::validators(); + + // make sure that all validators have the same balance + for validator in &validators { + assert_eq!(Balances::total_balance(validator), 10_000_000); + assert_eq!(Staking::slashable_balance_of(validator), 10_000); + + assert_eq!( + Staking::eras_stakers(1, validator), + pallet_staking::Exposure { total: 10_000, own: 10_000, others: vec![] }, + ); + } + + assert_eq!(authorities.len(), 2); + let equivocation_authority_index = 1; + let equivocation_key = &authorities[equivocation_authority_index]; + let equivocation_keyring = BeefyKeyring::from_public(equivocation_key).unwrap(); + + let payload1 = Payload::from_single_entry(MMR_ROOT_ID, vec![42]); + let payload2 = Payload::from_single_entry(MMR_ROOT_ID, vec![128]); + // generate an equivocation proof, with two votes in the same round for + // different payloads signed by the same key + let equivocation_proof = generate_equivocation_proof( + (block_num, payload1, set_id, &equivocation_keyring), + (block_num, payload2, set_id, &equivocation_keyring), + ); + + // create the key ownership proof + let key_owner_proof = + Historical::prove((sp_consensus_beefy::KEY_TYPE, &equivocation_key)).unwrap(); + + // report the equivocation and the tx should be dispatched successfully + assert_ok!(Beefy::report_equivocation_unsigned( + RuntimeOrigin::none(), + Box::new(equivocation_proof), + key_owner_proof, + ),); + + start_era(2); + + // check that the balance of 0-th validator is slashed 100%. + let equivocation_validator_id = validators[equivocation_authority_index]; + + assert_eq!(Balances::total_balance(&equivocation_validator_id), 10_000_000 - 10_000); + assert_eq!(Staking::slashable_balance_of(&equivocation_validator_id), 0); + assert_eq!( + Staking::eras_stakers(2, equivocation_validator_id), + pallet_staking::Exposure { total: 0, own: 0, others: vec![] }, + ); + + // check that the balances of all other validators are left intact. + for validator in &validators { + if *validator == equivocation_validator_id { + continue + } + + assert_eq!(Balances::total_balance(validator), 10_000_000); + assert_eq!(Staking::slashable_balance_of(validator), 10_000); + + assert_eq!( + Staking::eras_stakers(2, validator), + pallet_staking::Exposure { total: 10_000, own: 10_000, others: vec![] }, + ); + } + }); +} + +#[test] +fn report_equivocation_old_set_works() { + let authorities = test_authorities(); + + new_test_ext_raw_authorities(authorities).execute_with(|| { + start_era(1); + + let block_num = System::block_number(); + let validator_set = Beefy::validator_set().unwrap(); + let authorities = validator_set.validators(); + let validators = Session::validators(); + let old_set_id = validator_set.id(); + + assert_eq!(authorities.len(), 2); + let equivocation_authority_index = 0; + let equivocation_key = &authorities[equivocation_authority_index]; + + // create the key ownership proof in the "old" set + let key_owner_proof = + Historical::prove((sp_consensus_beefy::KEY_TYPE, &equivocation_key)).unwrap(); + + start_era(2); + + // make sure that all authorities have the same balance + for validator in &validators { + assert_eq!(Balances::total_balance(validator), 10_000_000); + assert_eq!(Staking::slashable_balance_of(validator), 10_000); + + assert_eq!( + Staking::eras_stakers(2, validator), + pallet_staking::Exposure { total: 10_000, own: 10_000, others: vec![] }, + ); + } + + let validator_set = Beefy::validator_set().unwrap(); + let new_set_id = validator_set.id(); + assert_eq!(old_set_id + 3, new_set_id); + + let equivocation_keyring = BeefyKeyring::from_public(equivocation_key).unwrap(); + + let payload1 = Payload::from_single_entry(MMR_ROOT_ID, vec![42]); + let payload2 = Payload::from_single_entry(MMR_ROOT_ID, vec![128]); + // generate an equivocation proof for the old set, + let equivocation_proof = generate_equivocation_proof( + (block_num, payload1, old_set_id, &equivocation_keyring), + (block_num, payload2, old_set_id, &equivocation_keyring), + ); + + // report the equivocation and the tx should be dispatched successfully + assert_ok!(Beefy::report_equivocation_unsigned( + RuntimeOrigin::none(), + Box::new(equivocation_proof), + key_owner_proof, + ),); + + start_era(3); + + // check that the balance of 0-th validator is slashed 100%. + let equivocation_validator_id = validators[equivocation_authority_index]; + + assert_eq!(Balances::total_balance(&equivocation_validator_id), 10_000_000 - 10_000); + assert_eq!(Staking::slashable_balance_of(&equivocation_validator_id), 0); + assert_eq!( + Staking::eras_stakers(3, equivocation_validator_id), + pallet_staking::Exposure { total: 0, own: 0, others: vec![] }, + ); + + // check that the balances of all other validators are left intact. + for validator in &validators { + if *validator == equivocation_validator_id { + continue + } + + assert_eq!(Balances::total_balance(validator), 10_000_000); + assert_eq!(Staking::slashable_balance_of(validator), 10_000); + + assert_eq!( + Staking::eras_stakers(3, validator), + pallet_staking::Exposure { total: 10_000, own: 10_000, others: vec![] }, + ); + } + }); +} + +#[test] +fn report_equivocation_invalid_set_id() { + let authorities = test_authorities(); + + new_test_ext_raw_authorities(authorities).execute_with(|| { + start_era(1); + + let block_num = System::block_number(); + let validator_set = Beefy::validator_set().unwrap(); + let authorities = validator_set.validators(); + let set_id = validator_set.id(); + + let equivocation_authority_index = 0; + let equivocation_key = &authorities[equivocation_authority_index]; + let equivocation_keyring = BeefyKeyring::from_public(equivocation_key).unwrap(); + + let key_owner_proof = + Historical::prove((sp_consensus_beefy::KEY_TYPE, &equivocation_key)).unwrap(); + + let payload1 = Payload::from_single_entry(MMR_ROOT_ID, vec![42]); + let payload2 = Payload::from_single_entry(MMR_ROOT_ID, vec![128]); + // generate an equivocation for a future set + let equivocation_proof = generate_equivocation_proof( + (block_num, payload1, set_id + 1, &equivocation_keyring), + (block_num, payload2, set_id + 1, &equivocation_keyring), + ); + + // the call for reporting the equivocation should error + assert_err!( + Beefy::report_equivocation_unsigned( + RuntimeOrigin::none(), + Box::new(equivocation_proof), + key_owner_proof, + ), + Error::::InvalidEquivocationProof, + ); + }); +} + +#[test] +fn report_equivocation_invalid_session() { + let authorities = test_authorities(); + + new_test_ext_raw_authorities(authorities).execute_with(|| { + start_era(1); + + let block_num = System::block_number(); + let validator_set = Beefy::validator_set().unwrap(); + let authorities = validator_set.validators(); + + let equivocation_authority_index = 0; + let equivocation_key = &authorities[equivocation_authority_index]; + let equivocation_keyring = BeefyKeyring::from_public(equivocation_key).unwrap(); + + // generate a key ownership proof at current era set id + let key_owner_proof = + Historical::prove((sp_consensus_beefy::KEY_TYPE, &equivocation_key)).unwrap(); + + start_era(2); + + let set_id = Beefy::validator_set().unwrap().id(); + + let payload1 = Payload::from_single_entry(MMR_ROOT_ID, vec![42]); + let payload2 = Payload::from_single_entry(MMR_ROOT_ID, vec![128]); + // generate an equivocation proof at following era set id = 2 + let equivocation_proof = generate_equivocation_proof( + (block_num, payload1, set_id, &equivocation_keyring), + (block_num, payload2, set_id, &equivocation_keyring), + ); + + // report an equivocation for the current set using an key ownership + // proof from the previous set, the session should be invalid. + assert_err!( + Beefy::report_equivocation_unsigned( + RuntimeOrigin::none(), + Box::new(equivocation_proof), + key_owner_proof, + ), + Error::::InvalidEquivocationProof, + ); + }); +} + +#[test] +fn report_equivocation_invalid_key_owner_proof() { + let authorities = test_authorities(); + + new_test_ext_raw_authorities(authorities).execute_with(|| { + start_era(1); + + let block_num = System::block_number(); + let validator_set = Beefy::validator_set().unwrap(); + let authorities = validator_set.validators(); + let set_id = validator_set.id(); + + let invalid_owner_authority_index = 1; + let invalid_owner_key = &authorities[invalid_owner_authority_index]; + + // generate a key ownership proof for the authority at index 1 + let invalid_key_owner_proof = + Historical::prove((sp_consensus_beefy::KEY_TYPE, &invalid_owner_key)).unwrap(); + + let equivocation_authority_index = 0; + let equivocation_key = &authorities[equivocation_authority_index]; + let equivocation_keyring = BeefyKeyring::from_public(equivocation_key).unwrap(); + + let payload1 = Payload::from_single_entry(MMR_ROOT_ID, vec![42]); + let payload2 = Payload::from_single_entry(MMR_ROOT_ID, vec![128]); + // generate an equivocation proof for the authority at index 0 + let equivocation_proof = generate_equivocation_proof( + (block_num, payload1, set_id + 1, &equivocation_keyring), + (block_num, payload2, set_id + 1, &equivocation_keyring), + ); + + // we need to start a new era otherwise the key ownership proof won't be + // checked since the authorities are part of the current session + start_era(2); + + // report an equivocation for the current set using a key ownership + // proof for a different key than the one in the equivocation proof. + assert_err!( + Beefy::report_equivocation_unsigned( + RuntimeOrigin::none(), + Box::new(equivocation_proof), + invalid_key_owner_proof, + ), + Error::::InvalidKeyOwnershipProof, + ); + }); +} + +#[test] +fn report_equivocation_invalid_equivocation_proof() { + let authorities = test_authorities(); + + new_test_ext_raw_authorities(authorities).execute_with(|| { + start_era(1); + + let block_num = System::block_number(); + let validator_set = Beefy::validator_set().unwrap(); + let authorities = validator_set.validators(); + let set_id = validator_set.id(); + + let equivocation_authority_index = 0; + let equivocation_key = &authorities[equivocation_authority_index]; + let equivocation_keyring = BeefyKeyring::from_public(equivocation_key).unwrap(); + + // generate a key ownership proof at set id in era 1 + let key_owner_proof = + Historical::prove((sp_consensus_beefy::KEY_TYPE, &equivocation_key)).unwrap(); + + let assert_invalid_equivocation_proof = |equivocation_proof| { + assert_err!( + Beefy::report_equivocation_unsigned( + RuntimeOrigin::none(), + Box::new(equivocation_proof), + key_owner_proof.clone(), + ), + Error::::InvalidEquivocationProof, + ); + }; + + start_era(2); + + let payload1 = Payload::from_single_entry(MMR_ROOT_ID, vec![42]); + let payload2 = Payload::from_single_entry(MMR_ROOT_ID, vec![128]); + + // both votes target the same block number and payload, + // there is no equivocation. + assert_invalid_equivocation_proof(generate_equivocation_proof( + (block_num, payload1.clone(), set_id, &equivocation_keyring), + (block_num, payload1.clone(), set_id, &equivocation_keyring), + )); + + // votes targeting different rounds, there is no equivocation. + assert_invalid_equivocation_proof(generate_equivocation_proof( + (block_num, payload1.clone(), set_id, &equivocation_keyring), + (block_num + 1, payload2.clone(), set_id, &equivocation_keyring), + )); + + // votes signed with different authority keys + assert_invalid_equivocation_proof(generate_equivocation_proof( + (block_num, payload1.clone(), set_id, &equivocation_keyring), + (block_num, payload1.clone(), set_id, &BeefyKeyring::Charlie), + )); + + // votes signed with a key that isn't part of the authority set + assert_invalid_equivocation_proof(generate_equivocation_proof( + (block_num, payload1.clone(), set_id, &equivocation_keyring), + (block_num, payload1.clone(), set_id, &BeefyKeyring::Dave), + )); + + // votes targeting different set ids + assert_invalid_equivocation_proof(generate_equivocation_proof( + (block_num, payload1, set_id, &equivocation_keyring), + (block_num, payload2, set_id + 1, &equivocation_keyring), + )); + }); +} + +#[test] +fn report_equivocation_validate_unsigned_prevents_duplicates() { + use sp_runtime::transaction_validity::{ + InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, + ValidTransaction, + }; + + let authorities = test_authorities(); + + new_test_ext_raw_authorities(authorities).execute_with(|| { + start_era(1); + + let block_num = System::block_number(); + let validator_set = Beefy::validator_set().unwrap(); + let authorities = validator_set.validators(); + let set_id = validator_set.id(); + + // generate and report an equivocation for the validator at index 0 + let equivocation_authority_index = 0; + let equivocation_key = &authorities[equivocation_authority_index]; + let equivocation_keyring = BeefyKeyring::from_public(equivocation_key).unwrap(); + + let payload1 = Payload::from_single_entry(MMR_ROOT_ID, vec![42]); + let payload2 = Payload::from_single_entry(MMR_ROOT_ID, vec![128]); + let equivocation_proof = generate_equivocation_proof( + (block_num, payload1, set_id, &equivocation_keyring), + (block_num, payload2, set_id, &equivocation_keyring), + ); + + let key_owner_proof = + Historical::prove((sp_consensus_beefy::KEY_TYPE, &equivocation_key)).unwrap(); + + let call = Call::report_equivocation_unsigned { + equivocation_proof: Box::new(equivocation_proof.clone()), + key_owner_proof: key_owner_proof.clone(), + }; + + // only local/inblock reports are allowed + assert_eq!( + ::validate_unsigned( + TransactionSource::External, + &call, + ), + InvalidTransaction::Call.into(), + ); + + // the transaction is valid when passed as local + let tx_tag = (equivocation_key, set_id, 3u64); + + assert_eq!( + ::validate_unsigned( + TransactionSource::Local, + &call, + ), + TransactionValidity::Ok(ValidTransaction { + priority: TransactionPriority::max_value(), + requires: vec![], + provides: vec![("BeefyEquivocation", tx_tag).encode()], + longevity: ReportLongevity::get(), + propagate: false, + }) + ); + + // the pre dispatch checks should also pass + assert_ok!(::pre_dispatch(&call)); + + // we submit the report + Beefy::report_equivocation_unsigned( + RuntimeOrigin::none(), + Box::new(equivocation_proof), + key_owner_proof, + ) + .unwrap(); + + // the report should now be considered stale and the transaction is invalid + // the check for staleness should be done on both `validate_unsigned` and on `pre_dispatch` + assert_err!( + ::validate_unsigned( + TransactionSource::Local, + &call, + ), + InvalidTransaction::Stale, + ); + + assert_err!( + ::pre_dispatch(&call), + InvalidTransaction::Stale, + ); + }); +} + +#[test] +fn report_equivocation_has_valid_weight() { + // the weight depends on the size of the validator set, + // but there's a lower bound of 100 validators. + assert!((1..=100) + .map(|validators| ::WeightInfo::report_equivocation(validators, 1000)) + .collect::>() + .windows(2) + .all(|w| w[0] == w[1])); + + // after 100 validators the weight should keep increasing + // with every extra validator. + assert!((100..=1000) + .map(|validators| ::WeightInfo::report_equivocation(validators, 1000)) + .collect::>() + .windows(2) + .all(|w| w[0].ref_time() < w[1].ref_time())); +} + +#[test] +fn valid_equivocation_reports_dont_pay_fees() { + let authorities = test_authorities(); + + new_test_ext_raw_authorities(authorities).execute_with(|| { + start_era(1); + + let block_num = System::block_number(); + let validator_set = Beefy::validator_set().unwrap(); + let authorities = validator_set.validators(); + let set_id = validator_set.id(); + + let equivocation_authority_index = 0; + let equivocation_key = &authorities[equivocation_authority_index]; + let equivocation_keyring = BeefyKeyring::from_public(equivocation_key).unwrap(); + + // generate equivocation proof + let payload1 = Payload::from_single_entry(MMR_ROOT_ID, vec![42]); + let payload2 = Payload::from_single_entry(MMR_ROOT_ID, vec![128]); + let equivocation_proof = generate_equivocation_proof( + (block_num, payload1, set_id, &equivocation_keyring), + (block_num, payload2, set_id, &equivocation_keyring), + ); + + // create the key ownership proof. + let key_owner_proof = + Historical::prove((sp_consensus_beefy::KEY_TYPE, &equivocation_key)).unwrap(); + + // check the dispatch info for the call. + let info = Call::::report_equivocation_unsigned { + equivocation_proof: Box::new(equivocation_proof.clone()), + key_owner_proof: key_owner_proof.clone(), + } + .get_dispatch_info(); + + // it should have non-zero weight and the fee has to be paid. + assert!(info.weight.any_gt(Weight::zero())); + assert_eq!(info.pays_fee, Pays::Yes); + + // report the equivocation. + let post_info = Beefy::report_equivocation_unsigned( + RuntimeOrigin::none(), + Box::new(equivocation_proof.clone()), + key_owner_proof.clone(), + ) + .unwrap(); + + // the original weight should be kept, but given that the report + // is valid the fee is waived. + assert!(post_info.actual_weight.is_none()); + assert_eq!(post_info.pays_fee, Pays::No); + + // report the equivocation again which is invalid now since it is + // duplicate. + let post_info = Beefy::report_equivocation_unsigned( + RuntimeOrigin::none(), + Box::new(equivocation_proof), + key_owner_proof, + ) + .err() + .unwrap() + .post_info; + + // the fee is not waived and the original weight is kept. + assert!(post_info.actual_weight.is_none()); + assert_eq!(post_info.pays_fee, Pays::Yes); + }) +} diff --git a/frame/benchmarking/Cargo.toml b/frame/benchmarking/Cargo.toml index 168b2605738a..9bf7613e6d27 100644 --- a/frame/benchmarking/Cargo.toml +++ b/frame/benchmarking/Cargo.toml @@ -13,27 +13,29 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -linregress = { version = "0.4.4", optional = true } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +linregress = { version = "0.5.1", optional = true } log = { version = "0.4.17", default-features = false } paste = "1.0" -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", optional = true } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-support-procedural = { version = "4.0.0-dev", default-features = false, path = "../support/procedural" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } -sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../../primitives/runtime-interface" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } -sp-storage = { version = "7.0.0", default-features = false, path = "../../primitives/storage" } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-runtime-interface = { version = "17.0.0", default-features = false, path = "../../primitives/runtime-interface" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-storage = { version = "13.0.0", default-features = false, path = "../../primitives/storage" } +static_assertions = "1.1.0" [dev-dependencies] -array-bytes = "4.1" +array-bytes = "6.1" rusty-fork = { version = "0.3.0", default-features = false } -sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } +sp-keystore = { version = "0.27.0", path = "../../primitives/keystore" } [features] default = ["std"] @@ -56,4 +58,5 @@ std = [ ] runtime-benchmarks = [ "frame-system/runtime-benchmarks", + "frame-support/runtime-benchmarks", ] diff --git a/frame/benchmarking/README.md b/frame/benchmarking/README.md index 76673c5f69b3..dc6a184435df 100644 --- a/frame/benchmarking/README.md +++ b/frame/benchmarking/README.md @@ -175,7 +175,6 @@ Then you can run a benchmark like so: ```bash ./target/production/substrate benchmark pallet \ --chain dev \ # Configurable Chain Spec - --execution=wasm \ # Always test with Wasm --wasm-execution=compiled \ # Always used `wasm-time` --pallet pallet_balances \ # Select the pallet --extrinsic transfer \ # Select the extrinsic diff --git a/frame/benchmarking/pov/Cargo.toml b/frame/benchmarking/pov/Cargo.toml new file mode 100644 index 000000000000..d54985eb6f35 --- /dev/null +++ b/frame/benchmarking/pov/Cargo.toml @@ -0,0 +1,43 @@ +[package] +name = "frame-benchmarking-pallet-pov" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Pallet for testing FRAME PoV benchmarking" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } +sp-io = { version = "23.0.0", default-features = false, path = "../../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-system/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/frame/benchmarking/pov/src/benchmarking.rs b/frame/benchmarking/pov/src/benchmarking.rs new file mode 100644 index 000000000000..473947b171ac --- /dev/null +++ b/frame/benchmarking/pov/src/benchmarking.rs @@ -0,0 +1,390 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! All benchmarks in this file are just for debugging the PoV calculation logic, they are unused. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use frame_support::traits::UnfilteredDispatchable; +use frame_system::{Pallet as System, RawOrigin}; +use sp_runtime::traits::Hash; + +frame_benchmarking::benchmarks! { + storage_single_value_read { + Value::::put(123); + }: { + assert_eq!(Value::::get(), Some(123)); + } + + #[pov_mode = Ignored] + storage_single_value_ignored_read { + Value::::put(123); + }: { + assert_eq!(Value::::get(), Some(123)); + } + + #[pov_mode = MaxEncodedLen { + Pov::Value2: Ignored + }] + storage_single_value_ignored_some_read { + Value::::put(123); + Value2::::put(123); + }: { + assert_eq!(Value::::get(), Some(123)); + assert_eq!(Value2::::get(), Some(123)); + } + + storage_single_value_read_twice { + Value::::put(123); + }: { + assert_eq!(Value::::get(), Some(123)); + assert_eq!(Value::::get(), Some(123)); + } + + storage_single_value_write { + }: { + Value::::put(123); + } verify { + assert_eq!(Value::::get(), Some(123)); + } + + storage_single_value_kill { + Value::::put(123); + }: { + Value::::kill(); + } verify { + assert!(!Value::::exists()); + } + + // This benchmark and the following are testing a storage map with adjacent storage items. + // + // First a storage map is filled and a specific number of other storage items is + // created. Then the one value is read from the map. This demonstrates that the number of other + // nodes in the Trie influences the proof size. The number of inserted nodes can be interpreted + // as the number of `StorageMap`/`StorageValue` in the whole runtime. + #[pov_mode = Measured] + storage_1m_map_read_one_value_two_additional_layers { + (0..(1<<10)).for_each(|i| Map1M::::insert(i, i)); + // Assume there are 16-256 other storage items. + (0..(1u32<<4)).for_each(|i| { + let k = T::Hashing::hash(&i.to_be_bytes()); + frame_support::storage::unhashed::put(k.as_ref(), &i); + }); + }: { + assert_eq!(Map1M::::get(1<<9), Some(1<<9)); + } + + #[pov_mode = Measured] + storage_1m_map_read_one_value_three_additional_layers { + (0..(1<<10)).for_each(|i| Map1M::::insert(i, i)); + // Assume there are 256-4096 other storage items. + (0..(1u32<<8)).for_each(|i| { + let k = T::Hashing::hash(&i.to_be_bytes()); + frame_support::storage::unhashed::put(k.as_ref(), &i); + }); + }: { + assert_eq!(Map1M::::get(1<<9), Some(1<<9)); + } + + #[pov_mode = Measured] + storage_1m_map_read_one_value_four_additional_layers { + (0..(1<<10)).for_each(|i| Map1M::::insert(i, i)); + // Assume there are 4096-65536 other storage items. + (0..(1u32<<12)).for_each(|i| { + let k = T::Hashing::hash(&i.to_be_bytes()); + frame_support::storage::unhashed::put(k.as_ref(), &i); + }); + }: { + assert_eq!(Map1M::::get(1<<9), Some(1<<9)); + } + + // Reads from both storage maps each `n` and `m` times. Should result in two linear components. + storage_map_read_per_component { + let n in 0 .. 100; + let m in 0 .. 100; + + (0..m*10).for_each(|i| Map1M::::insert(i, i)); + (0..n*10).for_each(|i| Map16M::::insert(i, i)); + }: { + (0..m).for_each(|i| + assert_eq!(Map1M::::get(i*10), Some(i*10))); + (0..n).for_each(|i| + assert_eq!(Map16M::::get(i*10), Some(i*10))); + } + + #[pov_mode = MaxEncodedLen { + Pov::Map1M: Ignored + }] + storage_map_read_per_component_one_ignored { + let n in 0 .. 100; + let m in 0 .. 100; + + (0..m*10).for_each(|i| Map1M::::insert(i, i)); + (0..n*10).for_each(|i| Map16M::::insert(i, i)); + }: { + (0..m).for_each(|i| + assert_eq!(Map1M::::get(i*10), Some(i*10))); + (0..n).for_each(|i| + assert_eq!(Map16M::::get(i*10), Some(i*10))); + } + + // Reads the same value from a storage map. Should not result in a component. + storage_1m_map_one_entry_repeated_read { + let n in 0 .. 100; + Map1M::::insert(0, 0); + }: { + (0..n).for_each(|i| + assert_eq!(Map1M::::get(0), Some(0))); + } + + // Reads the same values from a storage map. Should result in a `1x` linear component. + storage_1m_map_multiple_entry_repeated_read { + let n in 0 .. 100; + (0..n).for_each(|i| Map1M::::insert(i, i)); + }: { + (0..n).for_each(|i| { + // Reading the same value 10 times does nothing. + (0..10).for_each(|j| + assert_eq!(Map1M::::get(i), Some(i))); + }); + } + + storage_1m_double_map_read_per_component { + let n in 0 .. 1024; + (0..(1<<10)).for_each(|i| DoubleMap1M::::insert(i, i, i)); + }: { + (0..n).for_each(|i| + assert_eq!(DoubleMap1M::::get(i, i), Some(i))); + } + + storage_value_bounded_read { + }: { + assert!(BoundedValue::::get().is_none()); + } + + // Reading unbounded values will produce no mathematical worst case PoV size for this component. + storage_value_unbounded_read { + }: { + assert!(UnboundedValue::::get().is_none()); + } + + #[pov_mode = Ignored] + storage_value_unbounded_ignored_read { + }: { + assert!(UnboundedValue::::get().is_none()); + } + + // Same as above, but we still expect a mathematical worst case PoV size for the bounded one. + storage_value_bounded_and_unbounded_read { + (0..1024).for_each(|i| Map1M::::insert(i, i)); + }: { + assert!(UnboundedValue::::get().is_none()); + assert!(BoundedValue::::get().is_none()); + } + + #[pov_mode = Measured] + measured_storage_value_read_linear_size { + let l in 0 .. 1<<22; + let v: sp_runtime::BoundedVec = sp_std::vec![0u8; l as usize].try_into().unwrap(); + LargeValue::::put(&v); + }: { + assert!(LargeValue::::get().is_some()); + } + + #[pov_mode = MaxEncodedLen] + mel_storage_value_read_linear_size { + let l in 0 .. 1<<22; + let v: sp_runtime::BoundedVec = sp_std::vec![0u8; l as usize].try_into().unwrap(); + LargeValue::::put(&v); + }: { + assert!(LargeValue::::get().is_some()); + } + + #[pov_mode = Measured] + measured_storage_double_value_read_linear_size { + let l in 0 .. 1<<22; + let v: sp_runtime::BoundedVec = sp_std::vec![0u8; l as usize].try_into().unwrap(); + LargeValue::::put(&v); + LargeValue2::::put(&v); + }: { + assert!(LargeValue::::get().is_some()); + assert!(LargeValue2::::get().is_some()); + } + + #[pov_mode = MaxEncodedLen] + mel_storage_double_value_read_linear_size { + let l in 0 .. 1<<22; + let v: sp_runtime::BoundedVec = sp_std::vec![0u8; l as usize].try_into().unwrap(); + LargeValue::::put(&v); + LargeValue2::::put(&v); + }: { + assert!(LargeValue::::get().is_some()); + assert!(LargeValue2::::get().is_some()); + } + + #[pov_mode = MaxEncodedLen { + Pov::LargeValue2: Measured + }] + mel_mixed_storage_double_value_read_linear_size { + let l in 0 .. 1<<22; + let v: sp_runtime::BoundedVec = sp_std::vec![0u8; l as usize].try_into().unwrap(); + LargeValue::::put(&v); + LargeValue2::::put(&v); + }: { + assert!(LargeValue::::get().is_some()); + assert!(LargeValue2::::get().is_some()); + } + + #[pov_mode = Measured { + Pov::LargeValue2: MaxEncodedLen + }] + measured_mixed_storage_double_value_read_linear_size { + let l in 0 .. 1<<22; + let v: sp_runtime::BoundedVec = sp_std::vec![0u8; l as usize].try_into().unwrap(); + LargeValue::::put(&v); + LargeValue2::::put(&v); + }: { + assert!(LargeValue::::get().is_some()); + assert!(LargeValue2::::get().is_some()); + } + + #[pov_mode = Measured] + storage_map_unbounded_both_measured_read { + let i in 0 .. 1000; + + UnboundedMap::::insert(i, sp_std::vec![0; i as usize]); + UnboundedMap2::::insert(i, sp_std::vec![0; i as usize]); + }: { + assert!(UnboundedMap::::get(i).is_some()); + assert!(UnboundedMap2::::get(i).is_some()); + } + + #[pov_mode = MaxEncodedLen { + Pov::UnboundedMap: Measured + }] + storage_map_partial_unbounded_read { + let i in 0 .. 1000; + + Map1M::::insert(i, 0); + UnboundedMap::::insert(i, sp_std::vec![0; i as usize]); + }: { + assert!(Map1M::::get(i).is_some()); + assert!(UnboundedMap::::get(i).is_some()); + } + + #[pov_mode = MaxEncodedLen { + Pov::UnboundedMap: Ignored + }] + storage_map_partial_unbounded_ignored_read { + let i in 0 .. 1000; + + Map1M::::insert(i, 0); + UnboundedMap::::insert(i, sp_std::vec![0; i as usize]); + }: { + assert!(Map1M::::get(i).is_some()); + assert!(UnboundedMap::::get(i).is_some()); + } + + // Emitting an event will not incur any PoV. + emit_event { + // Emit a single event. + let call = Call::::emit_event { }; + }: { call.dispatch_bypass_filter(RawOrigin::Root.into()).unwrap(); } + verify { + assert_eq!(System::::events().len(), 1); + } + + // A No-OP will not incur any PoV. + noop { + let call = Call::::noop { }; + }: { + call.dispatch_bypass_filter(RawOrigin::Root.into()).unwrap(); + } + + storage_iteration { + for i in 0..65000 { + UnboundedMapTwox::::insert(i, sp_std::vec![0; 64]); + } + }: { + for (key, value) in UnboundedMapTwox::::iter() { + unsafe { + core::ptr::read_volatile(&key); + core::ptr::read_volatile(value.as_ptr()); + } + } + } + + impl_benchmark_test_suite!( + Pallet, + mock::new_test_ext(), + mock::Test, + ); +} + +#[cfg(test)] +mod mock { + use sp_runtime::{testing::H256, BuildStorage}; + + type AccountId = u64; + type Nonce = u32; + + type Block = frame_system::mocking::MockBlock; + + frame_support::construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Baseline: crate::{Pallet, Call, Storage, Event}, + } + ); + + impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Nonce = Nonce; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = sp_runtime::traits::IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + } + + impl crate::Config for Test { + type RuntimeEvent = RuntimeEvent; + } + + pub fn new_test_ext() -> sp_io::TestExternalities { + frame_system::GenesisConfig::::default().build_storage().unwrap().into() + } +} diff --git a/frame/benchmarking/pov/src/lib.rs b/frame/benchmarking/pov/src/lib.rs new file mode 100644 index 000000000000..eb02ccc983c0 --- /dev/null +++ b/frame/benchmarking/pov/src/lib.rs @@ -0,0 +1,135 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! End-to-end testing pallet for PoV benchmarking. Should only be deployed in a testing runtime. + +#![cfg_attr(not(feature = "std"), no_std)] + +mod benchmarking; +mod tests; +mod weights; + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + use sp_std::prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::storage] + pub(crate) type Value = StorageValue; + + #[pallet::storage] + pub(crate) type Value2 = StorageValue; + + /// A value without a MEL bound. + #[pallet::storage] + #[pallet::unbounded] + pub(crate) type UnboundedValue = + StorageValue, QueryKind = OptionQuery>; + + /// A value with a MEL bound of 32 byte. + #[pallet::storage] + pub(crate) type BoundedValue = + StorageValue>, QueryKind = OptionQuery>; + + /// 4MiB value. + #[pallet::storage] + pub(crate) type LargeValue = + StorageValue>, QueryKind = OptionQuery>; + + #[pallet::storage] + pub(crate) type LargeValue2 = + StorageValue>, QueryKind = OptionQuery>; + + /// A map with a maximum of 1M entries. + #[pallet::storage] + pub(crate) type Map1M = StorageMap< + Hasher = Blake2_256, + Key = u32, + Value = u32, + QueryKind = OptionQuery, + MaxValues = ConstU32<1_000_000>, + >; + + /// A map with a maximum of 16M entries. + #[pallet::storage] + pub(crate) type Map16M = StorageMap< + Hasher = Blake2_256, + Key = u32, + Value = u32, + QueryKind = OptionQuery, + MaxValues = ConstU32<16_000_000>, + >; + + #[pallet::storage] + pub(crate) type DoubleMap1M = StorageDoubleMap< + Hasher1 = Blake2_256, + Hasher2 = Blake2_256, + Key1 = u32, + Key2 = u32, + Value = u32, + QueryKind = OptionQuery, + MaxValues = ConstU32<1_000_000>, + >; + + #[pallet::storage] + #[pallet::unbounded] + pub(crate) type UnboundedMap = + StorageMap, QueryKind = OptionQuery>; + + #[pallet::storage] + #[pallet::unbounded] + pub(crate) type UnboundedMap2 = + StorageMap, QueryKind = OptionQuery>; + + #[pallet::storage] + #[pallet::unbounded] + pub(crate) type UnboundedMapTwox = + StorageMap, QueryKind = OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + TestEvent, + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight({0})] + pub fn emit_event(_origin: OriginFor) -> DispatchResult { + Self::deposit_event(Event::TestEvent); + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight({0})] + pub fn noop(_origin: OriginFor) -> DispatchResult { + Ok(()) + } + } +} diff --git a/frame/benchmarking/pov/src/tests.rs b/frame/benchmarking/pov/src/tests.rs new file mode 100644 index 000000000000..f09e37a5288a --- /dev/null +++ b/frame/benchmarking/pov/src/tests.rs @@ -0,0 +1,206 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Test the produces weight functions. + +#![cfg(test)] + +use super::weights::WeightInfo; +use mock::Test as T; +type W = crate::weights::SubstrateWeight; + +#[test] +fn writing_is_free() { + let w = W::storage_single_value_write().proof_size(); + assert_eq!(w, 0, "Writing is free"); +} + +#[test] +fn killing_is_free() { + // NOTE: This only applies to state version 1. + let w = W::storage_single_value_kill().proof_size(); + assert_eq!(w, 0, "Killing is free"); +} + +#[test] +fn reading_twice_is_the_same_as_once() { + let w = W::storage_single_value_read().proof_size(); + let w2 = W::storage_single_value_read_twice().proof_size(); + assert_eq!(w, w2, "Reading twice is the same as once"); +} + +#[test] +fn storage_single_value_ignored_read_no_pov() { + let w = W::storage_single_value_ignored_read(); + assert_eq!(w.proof_size(), 0, "Ignored PoV does not result in PoV"); +} + +#[test] +fn storage_single_value_ignored_some_read_has_pov() { + let w = W::storage_single_value_ignored_some_read(); + assert!(w.proof_size() != 0, "Ignored some does result in PoV"); +} + +/// Reading the same value from a map does not increase the PoV. +#[test] +fn storage_1m_map_one_entry_repeated_read_const() { + let weight = W::storage_1m_map_one_entry_repeated_read; + let w0 = weight(0).proof_size(); + assert!(w0 > 0, "There is a base weight"); + + let w1 = weight(1).proof_size(); + assert_eq!(w0, w1, "Component does not matter"); +} + +/// Reading multiple values multiple times from a map increases the PoV by the number of reads. +#[test] +fn storage_1m_map_multiple_entry_repeated_read_single_linear() { + let weight = W::storage_1m_map_multiple_entry_repeated_read; + let w0 = weight(0).proof_size(); + + let w1 = weight(1).proof_size() - w0; + assert!(w1 > 0, "Component matters"); + + let wm = weight(1000).proof_size(); + assert_eq!(w1 * 1000 + w0, wm, "x scales linearly"); +} + +/// Check that reading two maps at once increases the PoV linearly per map. +#[test] +fn storage_map_read_per_component_double_linear() { + let weight = W::storage_map_read_per_component; + let w00 = weight(0, 0).proof_size(); + + let w10 = weight(1, 0).proof_size() - w00; + let w01 = weight(0, 1).proof_size() - w00; + assert!(w10 > 0 && w01 > 0, "Components matter"); + assert!(w10 != w01, "Each map has its own component"); + + let wm0 = weight(1000, 0).proof_size(); + let w0m = weight(0, 1000).proof_size(); + assert_eq!(w00 + w10 * 1000, wm0, "x scales linearly"); + assert_eq!(w00 + w01 * 1000, w0m, "y scales linearly"); + + let wmm = weight(1000, 1000).proof_size(); + assert_eq!(wmm + w00, wm0 + w0m, "x + y scales linearly"); +} + +/// The proof size estimation takes the measured sizes into account and therefore increases with the +/// number of layers. +#[test] +fn additional_layers_do_not_matter() { + let w2 = W::storage_1m_map_read_one_value_two_additional_layers().proof_size(); + let w3 = W::storage_1m_map_read_one_value_three_additional_layers().proof_size(); + let w4 = W::storage_1m_map_read_one_value_four_additional_layers().proof_size(); + assert!(w3 > w2 && w4 > w3, "Additional layers do matter"); +} + +/// Check that the measured value size instead of the MEL is used. +#[test] +fn linear_measured_size_works() { + let weight = W::measured_storage_value_read_linear_size; + + let w0 = weight(0).proof_size(); + let w1 = weight(1).proof_size() - w0; + + assert_eq!(w1, 1, "x scales with a factor of 1"); + let wm = weight(1000).proof_size(); + assert_eq!(w1 * 1000 + w0, wm, "x scales linearly"); +} + +// vice-versa of above `linear_measured_size_works`. +#[test] +fn linear_mel_size_works() { + let weight = W::mel_storage_value_read_linear_size; + + let w1 = weight(1).proof_size(); + let wm = weight(1000).proof_size(); + assert_eq!(w1, wm, "PoV size is const"); +} + +/// Although there is no estimation possible, it uses the recorded proof size as best effort. +#[test] +fn unbounded_read_best_effort() { + let w = W::storage_value_unbounded_read().proof_size(); + assert!(w > 0, "There is a weight"); +} + +/// For mixed unbounded and bounded reads, the bounded part still increases the PoV. +#[test] +fn partial_unbounded_read_best_effort() { + let w_unbounded = W::storage_value_unbounded_read().proof_size(); + let w_bounded = W::storage_value_bounded_read().proof_size(); + let w_both = W::storage_value_bounded_and_unbounded_read().proof_size(); + + assert!(w_both > w_bounded && w_both > w_unbounded, "The bounded part increases the PoV"); +} + +#[test] +fn emit_event_is_free() { + let w = W::emit_event().proof_size(); + assert_eq!(w, 0, "Emitting an event is free"); +} + +#[test] +fn noop_is_free() { + let w = W::noop().proof_size(); + assert_eq!(w, 0, "Noop is free"); +} + +mod mock { + use sp_runtime::testing::H256; + + type Block = frame_system::mocking::MockBlock; + + frame_support::construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Baseline: crate::{Pallet, Call, Storage, Event}, + } + ); + + impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u32; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = u32; + type Lookup = sp_runtime::traits::IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; + } + + impl crate::Config for Test { + type RuntimeEvent = RuntimeEvent; + } +} diff --git a/frame/benchmarking/pov/src/weights.rs b/frame/benchmarking/pov/src/weights.rs new file mode 100644 index 000000000000..f16ac7fbc273 --- /dev/null +++ b/frame/benchmarking/pov/src/weights.rs @@ -0,0 +1,832 @@ + +//! Autogenerated weights for frame_benchmarking_pallet_pov +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-04-12, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `i9`, CPU: `13th Gen Intel(R) Core(TM) i9-13900K` +//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: None, DB CACHE: 1024 + +// Executed Command: +// ./target/release/substrate +// benchmark +// pallet +// --dev +// --pallet +// frame-benchmarking-pallet-pov +// --extrinsic +// +// --steps +// 50 +// --repeat +// 20 +// --template=.maintain/frame-weight-template.hbs +// --output=frame/benchmarking/pov/src/weights.rs + +#![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 frame_benchmarking_pallet_pov. +pub trait WeightInfo { + fn storage_single_value_read() -> Weight; + fn storage_single_value_ignored_read() -> Weight; + fn storage_single_value_ignored_some_read() -> Weight; + fn storage_single_value_read_twice() -> Weight; + fn storage_single_value_write() -> Weight; + fn storage_single_value_kill() -> Weight; + fn storage_1m_map_read_one_value_two_additional_layers() -> Weight; + fn storage_1m_map_read_one_value_three_additional_layers() -> Weight; + fn storage_1m_map_read_one_value_four_additional_layers() -> Weight; + fn storage_map_read_per_component(n: u32, m: u32, ) -> Weight; + fn storage_map_read_per_component_one_ignored(n: u32, m: u32, ) -> Weight; + fn storage_1m_map_one_entry_repeated_read(n: u32, ) -> Weight; + fn storage_1m_map_multiple_entry_repeated_read(n: u32, ) -> Weight; + fn storage_1m_double_map_read_per_component(n: u32, ) -> Weight; + fn storage_value_bounded_read() -> Weight; + fn storage_value_unbounded_read() -> Weight; + fn storage_value_unbounded_ignored_read() -> Weight; + fn storage_value_bounded_and_unbounded_read() -> Weight; + fn measured_storage_value_read_linear_size(l: u32, ) -> Weight; + fn mel_storage_value_read_linear_size(l: u32, ) -> Weight; + fn measured_storage_double_value_read_linear_size(l: u32, ) -> Weight; + fn mel_storage_double_value_read_linear_size(l: u32, ) -> Weight; + fn mel_mixed_storage_double_value_read_linear_size(l: u32, ) -> Weight; + fn measured_mixed_storage_double_value_read_linear_size(l: u32, ) -> Weight; + fn storage_map_unbounded_both_measured_read(i: u32, ) -> Weight; + fn storage_map_partial_unbounded_read(i: u32, ) -> Weight; + fn storage_map_partial_unbounded_ignored_read(i: u32, ) -> Weight; + fn emit_event() -> Weight; + fn noop() -> Weight; + fn storage_iteration() -> Weight; +} + +/// Weights for frame_benchmarking_pallet_pov using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: Pov Value (r:1 w:0) + /// Proof: Pov Value (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn storage_single_value_read() -> Weight { + // Proof Size summary in bytes: + // Measured: `136` + // Estimated: `1489` + // Minimum execution time: 1_706_000 picoseconds. + Weight::from_parts(1_788_000, 1489) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: Pov Value (r:1 w:0) + /// Proof: Pov Value (max_values: Some(1), max_size: Some(4), added: 499, mode: Ignored) + fn storage_single_value_ignored_read() -> Weight { + // Proof Size summary in bytes: + // Measured: `136` + // Estimated: `0` + // Minimum execution time: 1_661_000 picoseconds. + Weight::from_parts(1_718_000, 0) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: Pov Value (r:1 w:0) + /// Proof: Pov Value (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Pov Value2 (r:1 w:0) + /// Proof: Pov Value2 (max_values: Some(1), max_size: Some(4), added: 499, mode: Ignored) + fn storage_single_value_ignored_some_read() -> Weight { + // Proof Size summary in bytes: + // Measured: `160` + // Estimated: `1489` + // Minimum execution time: 2_226_000 picoseconds. + Weight::from_parts(2_365_000, 1489) + .saturating_add(T::DbWeight::get().reads(2_u64)) + } + /// Storage: Pov Value (r:1 w:0) + /// Proof: Pov Value (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn storage_single_value_read_twice() -> Weight { + // Proof Size summary in bytes: + // Measured: `136` + // Estimated: `1489` + // Minimum execution time: 1_785_000 picoseconds. + Weight::from_parts(1_980_000, 1489) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: Pov Value (r:0 w:1) + /// Proof: Pov Value (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn storage_single_value_write() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 254_000 picoseconds. + Weight::from_parts(326_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Pov Value (r:0 w:1) + /// Proof: Pov Value (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn storage_single_value_kill() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 239_000 picoseconds. + Weight::from_parts(277_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Pov Map1M (r:1 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: Measured) + fn storage_1m_map_read_one_value_two_additional_layers() -> Weight { + // Proof Size summary in bytes: + // Measured: `1275` + // Estimated: `4740` + // Minimum execution time: 4_760_000 picoseconds. + Weight::from_parts(5_051_000, 4740) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: Pov Map1M (r:1 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: Measured) + fn storage_1m_map_read_one_value_three_additional_layers() -> Weight { + // Proof Size summary in bytes: + // Measured: `1544` + // Estimated: `5009` + // Minimum execution time: 5_490_000 picoseconds. + Weight::from_parts(5_703_000, 5009) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: Pov Map1M (r:1 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: Measured) + fn storage_1m_map_read_one_value_four_additional_layers() -> Weight { + // Proof Size summary in bytes: + // Measured: `2044` + // Estimated: `5509` + // Minimum execution time: 6_397_000 picoseconds. + Weight::from_parts(7_084_000, 5509) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: Pov Map1M (r:100 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: MaxEncodedLen) + /// Storage: Pov Map16M (r:100 w:0) + /// Proof: Pov Map16M (max_values: Some(16000000), max_size: Some(36), added: 3006, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 100]`. + /// The range of component `m` is `[0, 100]`. + fn storage_map_read_per_component(n: u32, m: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `515 + m * (188 ±0) + n * (188 ±0)` + // Estimated: `990 + m * (2511 ±0) + n * (3006 ±0)` + // Minimum execution time: 181_481_000 picoseconds. + Weight::from_parts(129_275_141, 990) + // Standard Error: 13_049 + .saturating_add(Weight::from_parts(787_667, 0).saturating_mul(n.into())) + // Standard Error: 13_049 + .saturating_add(Weight::from_parts(830_378, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(m.into()))) + .saturating_add(Weight::from_parts(0, 2511).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 3006).saturating_mul(n.into())) + } + /// Storage: Pov Map1M (r:100 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: Ignored) + /// Storage: Pov Map16M (r:100 w:0) + /// Proof: Pov Map16M (max_values: Some(16000000), max_size: Some(36), added: 3006, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 100]`. + /// The range of component `m` is `[0, 100]`. + fn storage_map_read_per_component_one_ignored(n: u32, m: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `515 + m * (188 ±0) + n * (188 ±0)` + // Estimated: `1685 + m * (189 ±0) + n * (3006 ±0)` + // Minimum execution time: 181_925_000 picoseconds. + Weight::from_parts(134_416_814, 1685) + // Standard Error: 15_678 + .saturating_add(Weight::from_parts(827_168, 0).saturating_mul(n.into())) + // Standard Error: 15_678 + .saturating_add(Weight::from_parts(813_655, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(m.into()))) + .saturating_add(Weight::from_parts(0, 189).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 3006).saturating_mul(n.into())) + } + /// Storage: Pov Map1M (r:1 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 100]`. + fn storage_1m_map_one_entry_repeated_read(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `170` + // Estimated: `3501` + // Minimum execution time: 20_000 picoseconds. + Weight::from_parts(2_006_399, 3501) + // Standard Error: 808 + .saturating_add(Weight::from_parts(263_609, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: Pov Map1M (r:100 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 100]`. + fn storage_1m_map_multiple_entry_repeated_read(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `147 + n * (40 ±0)` + // Estimated: `990 + n * (2511 ±0)` + // Minimum execution time: 21_000 picoseconds. + Weight::from_parts(3_940_044, 990) + // Standard Error: 4_906 + .saturating_add(Weight::from_parts(3_454_882, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2511).saturating_mul(n.into())) + } + /// Storage: Pov DoubleMap1M (r:1024 w:0) + /// Proof: Pov DoubleMap1M (max_values: Some(1000000), max_size: Some(68), added: 2543, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 1024]`. + fn storage_1m_double_map_read_per_component(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `21938 + n * (57 ±0)` + // Estimated: `990 + n * (2543 ±0)` + // Minimum execution time: 28_000 picoseconds. + Weight::from_parts(20_674_869, 990) + // Standard Error: 3_035 + .saturating_add(Weight::from_parts(1_995_730, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2543).saturating_mul(n.into())) + } + /// Storage: Pov BoundedValue (r:1 w:0) + /// Proof: Pov BoundedValue (max_values: Some(1), max_size: Some(33), added: 528, mode: MaxEncodedLen) + fn storage_value_bounded_read() -> Weight { + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `1518` + // Minimum execution time: 1_091_000 picoseconds. + Weight::from_parts(1_181_000, 1518) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: Pov UnboundedValue (r:1 w:0) + /// Proof Skipped: Pov UnboundedValue (max_values: Some(1), max_size: None, mode: Measured) + fn storage_value_unbounded_read() -> Weight { + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `1594` + // Minimum execution time: 1_079_000 picoseconds. + Weight::from_parts(1_176_000, 1594) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: Pov UnboundedValue (r:1 w:0) + /// Proof Skipped: Pov UnboundedValue (max_values: Some(1), max_size: None, mode: Ignored) + fn storage_value_unbounded_ignored_read() -> Weight { + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `0` + // Minimum execution time: 1_101_000 picoseconds. + Weight::from_parts(1_160_000, 0) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: Pov UnboundedValue (r:1 w:0) + /// Proof Skipped: Pov UnboundedValue (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Pov BoundedValue (r:1 w:0) + /// Proof: Pov BoundedValue (max_values: Some(1), max_size: Some(33), added: 528, mode: MaxEncodedLen) + fn storage_value_bounded_and_unbounded_read() -> Weight { + // Proof Size summary in bytes: + // Measured: `147` + // Estimated: `1632` + // Minimum execution time: 2_143_000 picoseconds. + Weight::from_parts(2_280_000, 1632) + .saturating_add(T::DbWeight::get().reads(2_u64)) + } + /// Storage: Pov LargeValue (r:1 w:0) + /// Proof: Pov LargeValue (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: Measured) + /// The range of component `l` is `[0, 4194304]`. + fn measured_storage_value_read_linear_size(l: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `142 + l * (1 ±0)` + // Estimated: `1626 + l * (1 ±0)` + // Minimum execution time: 1_665_000 picoseconds. + Weight::from_parts(1_725_000, 1626) + // Standard Error: 3 + .saturating_add(Weight::from_parts(376, 0).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(l.into())) + } + /// Storage: Pov LargeValue (r:1 w:0) + /// Proof: Pov LargeValue (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: MaxEncodedLen) + /// The range of component `l` is `[0, 4194304]`. + fn mel_storage_value_read_linear_size(l: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `142 + l * (1 ±0)` + // Estimated: `4195793` + // Minimum execution time: 1_640_000 picoseconds. + Weight::from_parts(1_724_000, 4195793) + // Standard Error: 4 + .saturating_add(Weight::from_parts(395, 0).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: Pov LargeValue (r:1 w:0) + /// Proof: Pov LargeValue (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: Measured) + /// Storage: Pov LargeValue2 (r:1 w:0) + /// Proof: Pov LargeValue2 (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: Measured) + /// The range of component `l` is `[0, 4194304]`. + fn measured_storage_double_value_read_linear_size(l: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `171 + l * (2 ±0)` + // Estimated: `1655 + l * (2 ±0)` + // Minimum execution time: 2_263_000 picoseconds. + Weight::from_parts(2_358_000, 1655) + // Standard Error: 8 + .saturating_add(Weight::from_parts(737, 0).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(Weight::from_parts(0, 2).saturating_mul(l.into())) + } + /// Storage: Pov LargeValue (r:1 w:0) + /// Proof: Pov LargeValue (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: MaxEncodedLen) + /// Storage: Pov LargeValue2 (r:1 w:0) + /// Proof: Pov LargeValue2 (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: MaxEncodedLen) + /// The range of component `l` is `[0, 4194304]`. + fn mel_storage_double_value_read_linear_size(l: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `171 + l * (2 ±0)` + // Estimated: `4195793` + // Minimum execution time: 2_161_000 picoseconds. + Weight::from_parts(2_233_000, 4195793) + // Standard Error: 5 + .saturating_add(Weight::from_parts(639, 0).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + } + /// Storage: Pov LargeValue (r:1 w:0) + /// Proof: Pov LargeValue (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: MaxEncodedLen) + /// Storage: Pov LargeValue2 (r:1 w:0) + /// Proof: Pov LargeValue2 (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: Measured) + /// The range of component `l` is `[0, 4194304]`. + fn mel_mixed_storage_double_value_read_linear_size(l: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `171 + l * (2 ±0)` + // Estimated: `4195793 + l * (2 ±0)` + // Minimum execution time: 2_149_000 picoseconds. + Weight::from_parts(2_256_000, 4195793) + // Standard Error: 6 + .saturating_add(Weight::from_parts(677, 0).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(Weight::from_parts(0, 2).saturating_mul(l.into())) + } + /// Storage: Pov LargeValue (r:1 w:0) + /// Proof: Pov LargeValue (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: Measured) + /// Storage: Pov LargeValue2 (r:1 w:0) + /// Proof: Pov LargeValue2 (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: MaxEncodedLen) + /// The range of component `l` is `[0, 4194304]`. + fn measured_mixed_storage_double_value_read_linear_size(l: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `171 + l * (2 ±0)` + // Estimated: `4195793 + l * (2 ±0)` + // Minimum execution time: 2_254_000 picoseconds. + Weight::from_parts(2_319_000, 4195793) + // Standard Error: 5 + .saturating_add(Weight::from_parts(664, 0).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(Weight::from_parts(0, 2).saturating_mul(l.into())) + } + /// Storage: Pov UnboundedMap (r:1 w:0) + /// Proof Skipped: Pov UnboundedMap (max_values: None, max_size: None, mode: Measured) + /// Storage: Pov UnboundedMap2 (r:1 w:0) + /// Proof Skipped: Pov UnboundedMap2 (max_values: None, max_size: None, mode: Measured) + /// The range of component `i` is `[0, 1000]`. + fn storage_map_unbounded_both_measured_read(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `229 + i * (8 ±0)` + // Estimated: `3693 + i * (8 ±0)` + // Minimum execution time: 3_071_000 picoseconds. + Weight::from_parts(3_487_712, 3693) + // Standard Error: 26 + .saturating_add(Weight::from_parts(748, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(i.into())) + } + /// Storage: Pov Map1M (r:1 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: MaxEncodedLen) + /// Storage: Pov UnboundedMap (r:1 w:0) + /// Proof Skipped: Pov UnboundedMap (max_values: None, max_size: None, mode: Measured) + /// The range of component `i` is `[0, 1000]`. + fn storage_map_partial_unbounded_read(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `228 + i * (4 ±0)` + // Estimated: `3692 + i * (4 ±0)` + // Minimum execution time: 3_150_000 picoseconds. + Weight::from_parts(3_582_963, 3692) + // Standard Error: 18 + .saturating_add(Weight::from_parts(380, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(Weight::from_parts(0, 4).saturating_mul(i.into())) + } + /// Storage: Pov Map1M (r:1 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: MaxEncodedLen) + /// Storage: Pov UnboundedMap (r:1 w:0) + /// Proof Skipped: Pov UnboundedMap (max_values: None, max_size: None, mode: Ignored) + /// The range of component `i` is `[0, 1000]`. + fn storage_map_partial_unbounded_ignored_read(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `228 + i * (4 ±0)` + // Estimated: `3501 + i * (4 ±0)` + // Minimum execution time: 3_092_000 picoseconds. + Weight::from_parts(3_595_328, 3501) + // Standard Error: 20 + .saturating_add(Weight::from_parts(243, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(Weight::from_parts(0, 4).saturating_mul(i.into())) + } + fn emit_event() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_705_000 picoseconds. + Weight::from_parts(1_818_000, 0) + } + fn noop() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 533_000 picoseconds. + Weight::from_parts(587_000, 0) + } + /// Storage: Pov UnboundedMapTwox (r:65001 w:0) + /// Proof Skipped: Pov UnboundedMapTwox (max_values: None, max_size: None, mode: Measured) + fn storage_iteration() -> Weight { + // Proof Size summary in bytes: + // Measured: `17985289` + // Estimated: `178863754` + // Minimum execution time: 118_753_057_000 picoseconds. + Weight::from_parts(121_396_503_000, 178863754) + .saturating_add(T::DbWeight::get().reads(65001_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: Pov Value (r:1 w:0) + /// Proof: Pov Value (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn storage_single_value_read() -> Weight { + // Proof Size summary in bytes: + // Measured: `136` + // Estimated: `1489` + // Minimum execution time: 1_706_000 picoseconds. + Weight::from_parts(1_788_000, 1489) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: Pov Value (r:1 w:0) + /// Proof: Pov Value (max_values: Some(1), max_size: Some(4), added: 499, mode: Ignored) + fn storage_single_value_ignored_read() -> Weight { + // Proof Size summary in bytes: + // Measured: `136` + // Estimated: `0` + // Minimum execution time: 1_661_000 picoseconds. + Weight::from_parts(1_718_000, 0) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: Pov Value (r:1 w:0) + /// Proof: Pov Value (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Pov Value2 (r:1 w:0) + /// Proof: Pov Value2 (max_values: Some(1), max_size: Some(4), added: 499, mode: Ignored) + fn storage_single_value_ignored_some_read() -> Weight { + // Proof Size summary in bytes: + // Measured: `160` + // Estimated: `1489` + // Minimum execution time: 2_226_000 picoseconds. + Weight::from_parts(2_365_000, 1489) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } + /// Storage: Pov Value (r:1 w:0) + /// Proof: Pov Value (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn storage_single_value_read_twice() -> Weight { + // Proof Size summary in bytes: + // Measured: `136` + // Estimated: `1489` + // Minimum execution time: 1_785_000 picoseconds. + Weight::from_parts(1_980_000, 1489) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: Pov Value (r:0 w:1) + /// Proof: Pov Value (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn storage_single_value_write() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 254_000 picoseconds. + Weight::from_parts(326_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Pov Value (r:0 w:1) + /// Proof: Pov Value (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + fn storage_single_value_kill() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 239_000 picoseconds. + Weight::from_parts(277_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Pov Map1M (r:1 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: Measured) + fn storage_1m_map_read_one_value_two_additional_layers() -> Weight { + // Proof Size summary in bytes: + // Measured: `1275` + // Estimated: `4740` + // Minimum execution time: 4_760_000 picoseconds. + Weight::from_parts(5_051_000, 4740) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: Pov Map1M (r:1 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: Measured) + fn storage_1m_map_read_one_value_three_additional_layers() -> Weight { + // Proof Size summary in bytes: + // Measured: `1544` + // Estimated: `5009` + // Minimum execution time: 5_490_000 picoseconds. + Weight::from_parts(5_703_000, 5009) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: Pov Map1M (r:1 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: Measured) + fn storage_1m_map_read_one_value_four_additional_layers() -> Weight { + // Proof Size summary in bytes: + // Measured: `2044` + // Estimated: `5509` + // Minimum execution time: 6_397_000 picoseconds. + Weight::from_parts(7_084_000, 5509) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: Pov Map1M (r:100 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: MaxEncodedLen) + /// Storage: Pov Map16M (r:100 w:0) + /// Proof: Pov Map16M (max_values: Some(16000000), max_size: Some(36), added: 3006, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 100]`. + /// The range of component `m` is `[0, 100]`. + fn storage_map_read_per_component(n: u32, m: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `515 + m * (188 ±0) + n * (188 ±0)` + // Estimated: `990 + m * (2511 ±0) + n * (3006 ±0)` + // Minimum execution time: 181_481_000 picoseconds. + Weight::from_parts(129_275_141, 990) + // Standard Error: 13_049 + .saturating_add(Weight::from_parts(787_667, 0).saturating_mul(n.into())) + // Standard Error: 13_049 + .saturating_add(Weight::from_parts(830_378, 0).saturating_mul(m.into())) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(m.into()))) + .saturating_add(Weight::from_parts(0, 2511).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 3006).saturating_mul(n.into())) + } + /// Storage: Pov Map1M (r:100 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: Ignored) + /// Storage: Pov Map16M (r:100 w:0) + /// Proof: Pov Map16M (max_values: Some(16000000), max_size: Some(36), added: 3006, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 100]`. + /// The range of component `m` is `[0, 100]`. + fn storage_map_read_per_component_one_ignored(n: u32, m: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `515 + m * (188 ±0) + n * (188 ±0)` + // Estimated: `1685 + m * (189 ±0) + n * (3006 ±0)` + // Minimum execution time: 181_925_000 picoseconds. + Weight::from_parts(134_416_814, 1685) + // Standard Error: 15_678 + .saturating_add(Weight::from_parts(827_168, 0).saturating_mul(n.into())) + // Standard Error: 15_678 + .saturating_add(Weight::from_parts(813_655, 0).saturating_mul(m.into())) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(m.into()))) + .saturating_add(Weight::from_parts(0, 189).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 3006).saturating_mul(n.into())) + } + /// Storage: Pov Map1M (r:1 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 100]`. + fn storage_1m_map_one_entry_repeated_read(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `170` + // Estimated: `3501` + // Minimum execution time: 20_000 picoseconds. + Weight::from_parts(2_006_399, 3501) + // Standard Error: 808 + .saturating_add(Weight::from_parts(263_609, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: Pov Map1M (r:100 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 100]`. + fn storage_1m_map_multiple_entry_repeated_read(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `147 + n * (40 ±0)` + // Estimated: `990 + n * (2511 ±0)` + // Minimum execution time: 21_000 picoseconds. + Weight::from_parts(3_940_044, 990) + // Standard Error: 4_906 + .saturating_add(Weight::from_parts(3_454_882, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2511).saturating_mul(n.into())) + } + /// Storage: Pov DoubleMap1M (r:1024 w:0) + /// Proof: Pov DoubleMap1M (max_values: Some(1000000), max_size: Some(68), added: 2543, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 1024]`. + fn storage_1m_double_map_read_per_component(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `21938 + n * (57 ±0)` + // Estimated: `990 + n * (2543 ±0)` + // Minimum execution time: 28_000 picoseconds. + Weight::from_parts(20_674_869, 990) + // Standard Error: 3_035 + .saturating_add(Weight::from_parts(1_995_730, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2543).saturating_mul(n.into())) + } + /// Storage: Pov BoundedValue (r:1 w:0) + /// Proof: Pov BoundedValue (max_values: Some(1), max_size: Some(33), added: 528, mode: MaxEncodedLen) + fn storage_value_bounded_read() -> Weight { + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `1518` + // Minimum execution time: 1_091_000 picoseconds. + Weight::from_parts(1_181_000, 1518) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: Pov UnboundedValue (r:1 w:0) + /// Proof Skipped: Pov UnboundedValue (max_values: Some(1), max_size: None, mode: Measured) + fn storage_value_unbounded_read() -> Weight { + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `1594` + // Minimum execution time: 1_079_000 picoseconds. + Weight::from_parts(1_176_000, 1594) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: Pov UnboundedValue (r:1 w:0) + /// Proof Skipped: Pov UnboundedValue (max_values: Some(1), max_size: None, mode: Ignored) + fn storage_value_unbounded_ignored_read() -> Weight { + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `0` + // Minimum execution time: 1_101_000 picoseconds. + Weight::from_parts(1_160_000, 0) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: Pov UnboundedValue (r:1 w:0) + /// Proof Skipped: Pov UnboundedValue (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Pov BoundedValue (r:1 w:0) + /// Proof: Pov BoundedValue (max_values: Some(1), max_size: Some(33), added: 528, mode: MaxEncodedLen) + fn storage_value_bounded_and_unbounded_read() -> Weight { + // Proof Size summary in bytes: + // Measured: `147` + // Estimated: `1632` + // Minimum execution time: 2_143_000 picoseconds. + Weight::from_parts(2_280_000, 1632) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } + /// Storage: Pov LargeValue (r:1 w:0) + /// Proof: Pov LargeValue (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: Measured) + /// The range of component `l` is `[0, 4194304]`. + fn measured_storage_value_read_linear_size(l: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `142 + l * (1 ±0)` + // Estimated: `1626 + l * (1 ±0)` + // Minimum execution time: 1_665_000 picoseconds. + Weight::from_parts(1_725_000, 1626) + // Standard Error: 3 + .saturating_add(Weight::from_parts(376, 0).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(l.into())) + } + /// Storage: Pov LargeValue (r:1 w:0) + /// Proof: Pov LargeValue (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: MaxEncodedLen) + /// The range of component `l` is `[0, 4194304]`. + fn mel_storage_value_read_linear_size(l: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `142 + l * (1 ±0)` + // Estimated: `4195793` + // Minimum execution time: 1_640_000 picoseconds. + Weight::from_parts(1_724_000, 4195793) + // Standard Error: 4 + .saturating_add(Weight::from_parts(395, 0).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: Pov LargeValue (r:1 w:0) + /// Proof: Pov LargeValue (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: Measured) + /// Storage: Pov LargeValue2 (r:1 w:0) + /// Proof: Pov LargeValue2 (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: Measured) + /// The range of component `l` is `[0, 4194304]`. + fn measured_storage_double_value_read_linear_size(l: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `171 + l * (2 ±0)` + // Estimated: `1655 + l * (2 ±0)` + // Minimum execution time: 2_263_000 picoseconds. + Weight::from_parts(2_358_000, 1655) + // Standard Error: 8 + .saturating_add(Weight::from_parts(737, 0).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(Weight::from_parts(0, 2).saturating_mul(l.into())) + } + /// Storage: Pov LargeValue (r:1 w:0) + /// Proof: Pov LargeValue (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: MaxEncodedLen) + /// Storage: Pov LargeValue2 (r:1 w:0) + /// Proof: Pov LargeValue2 (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: MaxEncodedLen) + /// The range of component `l` is `[0, 4194304]`. + fn mel_storage_double_value_read_linear_size(l: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `171 + l * (2 ±0)` + // Estimated: `4195793` + // Minimum execution time: 2_161_000 picoseconds. + Weight::from_parts(2_233_000, 4195793) + // Standard Error: 5 + .saturating_add(Weight::from_parts(639, 0).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } + /// Storage: Pov LargeValue (r:1 w:0) + /// Proof: Pov LargeValue (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: MaxEncodedLen) + /// Storage: Pov LargeValue2 (r:1 w:0) + /// Proof: Pov LargeValue2 (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: Measured) + /// The range of component `l` is `[0, 4194304]`. + fn mel_mixed_storage_double_value_read_linear_size(l: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `171 + l * (2 ±0)` + // Estimated: `4195793 + l * (2 ±0)` + // Minimum execution time: 2_149_000 picoseconds. + Weight::from_parts(2_256_000, 4195793) + // Standard Error: 6 + .saturating_add(Weight::from_parts(677, 0).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(Weight::from_parts(0, 2).saturating_mul(l.into())) + } + /// Storage: Pov LargeValue (r:1 w:0) + /// Proof: Pov LargeValue (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: Measured) + /// Storage: Pov LargeValue2 (r:1 w:0) + /// Proof: Pov LargeValue2 (max_values: Some(1), max_size: Some(4194308), added: 4194803, mode: MaxEncodedLen) + /// The range of component `l` is `[0, 4194304]`. + fn measured_mixed_storage_double_value_read_linear_size(l: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `171 + l * (2 ±0)` + // Estimated: `4195793 + l * (2 ±0)` + // Minimum execution time: 2_254_000 picoseconds. + Weight::from_parts(2_319_000, 4195793) + // Standard Error: 5 + .saturating_add(Weight::from_parts(664, 0).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(Weight::from_parts(0, 2).saturating_mul(l.into())) + } + /// Storage: Pov UnboundedMap (r:1 w:0) + /// Proof Skipped: Pov UnboundedMap (max_values: None, max_size: None, mode: Measured) + /// Storage: Pov UnboundedMap2 (r:1 w:0) + /// Proof Skipped: Pov UnboundedMap2 (max_values: None, max_size: None, mode: Measured) + /// The range of component `i` is `[0, 1000]`. + fn storage_map_unbounded_both_measured_read(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `229 + i * (8 ±0)` + // Estimated: `3693 + i * (8 ±0)` + // Minimum execution time: 3_071_000 picoseconds. + Weight::from_parts(3_487_712, 3693) + // Standard Error: 26 + .saturating_add(Weight::from_parts(748, 0).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(i.into())) + } + /// Storage: Pov Map1M (r:1 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: MaxEncodedLen) + /// Storage: Pov UnboundedMap (r:1 w:0) + /// Proof Skipped: Pov UnboundedMap (max_values: None, max_size: None, mode: Measured) + /// The range of component `i` is `[0, 1000]`. + fn storage_map_partial_unbounded_read(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `228 + i * (4 ±0)` + // Estimated: `3692 + i * (4 ±0)` + // Minimum execution time: 3_150_000 picoseconds. + Weight::from_parts(3_582_963, 3692) + // Standard Error: 18 + .saturating_add(Weight::from_parts(380, 0).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(Weight::from_parts(0, 4).saturating_mul(i.into())) + } + /// Storage: Pov Map1M (r:1 w:0) + /// Proof: Pov Map1M (max_values: Some(1000000), max_size: Some(36), added: 2511, mode: MaxEncodedLen) + /// Storage: Pov UnboundedMap (r:1 w:0) + /// Proof Skipped: Pov UnboundedMap (max_values: None, max_size: None, mode: Ignored) + /// The range of component `i` is `[0, 1000]`. + fn storage_map_partial_unbounded_ignored_read(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `228 + i * (4 ±0)` + // Estimated: `3501 + i * (4 ±0)` + // Minimum execution time: 3_092_000 picoseconds. + Weight::from_parts(3_595_328, 3501) + // Standard Error: 20 + .saturating_add(Weight::from_parts(243, 0).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(Weight::from_parts(0, 4).saturating_mul(i.into())) + } + fn emit_event() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_705_000 picoseconds. + Weight::from_parts(1_818_000, 0) + } + fn noop() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 533_000 picoseconds. + Weight::from_parts(587_000, 0) + } + /// Storage: Pov UnboundedMapTwox (r:65001 w:0) + /// Proof Skipped: Pov UnboundedMapTwox (max_values: None, max_size: None, mode: Measured) + fn storage_iteration() -> Weight { + // Proof Size summary in bytes: + // Measured: `17985289` + // Estimated: `178863754` + // Minimum execution time: 118_753_057_000 picoseconds. + Weight::from_parts(121_396_503_000, 178863754) + .saturating_add(RocksDbWeight::get().reads(65001_u64)) + } +} diff --git a/frame/benchmarking/src/analysis.rs b/frame/benchmarking/src/analysis.rs index 0b77a92347d0..5fc3abb5a27f 100644 --- a/frame/benchmarking/src/analysis.rs +++ b/frame/benchmarking/src/analysis.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -161,7 +161,7 @@ fn raw_linear_regression( data.extend(xs); } let model = linregress::fit_low_level_regression_model(&data, ys.len(), x_vars + 2).ok()?; - Some((model.parameters[0], model.parameters[1..].to_vec(), model.se)) + Some((model.parameters()[0], model.parameters()[1..].to_vec(), model.se().to_vec())) } fn linear_regression( diff --git a/frame/benchmarking/src/baseline.rs b/frame/benchmarking/src/baseline.rs index 5fd845551dac..94c066269d91 100644 --- a/frame/benchmarking/src/baseline.rs +++ b/frame/benchmarking/src/baseline.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,29 +16,25 @@ // limitations under the License. //! A set of benchmarks which can establish a global baseline for all other -//! benchmarking. +//! benchmarking. These benchmarks do not require a pallet to be deployed. #![cfg(feature = "runtime-benchmarks")] +use super::*; use crate::benchmarks; -use codec::Encode; use frame_system::Pallet as System; -use sp_application_crypto::KeyTypeId; use sp_runtime::{ traits::{AppVerify, Hash}, RuntimeAppPublic, }; -use sp_std::prelude::*; -pub const TEST_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"test"); +mod crypto { + use sp_application_crypto::{app_crypto, sr25519, KeyTypeId}; -mod app_sr25519 { - use super::TEST_KEY_TYPE_ID; - use sp_application_crypto::{app_crypto, sr25519}; + pub const TEST_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"test"); app_crypto!(sr25519, TEST_KEY_TYPE_ID); } - -type SignerId = app_sr25519::Public; +pub type SignerId = crypto::Public; pub struct Pallet(System); pub trait Config: frame_system::Config {} @@ -81,7 +77,6 @@ benchmarks! { } hashing { - let i in 0 .. 100; let mut hash = T::Hash::default(); }: { (0..=100_000u32).for_each(|j| hash = T::Hashing::hash(&j.to_be_bytes())); @@ -106,68 +101,27 @@ benchmarks! { }); } - #[skip_meta] - storage_read { - let i in 0 .. 1_000; - let mut people = Vec::new(); - (0..i).for_each(|j| { - let hash = T::Hashing::hash(&j.to_be_bytes()).encode(); - frame_support::storage::unhashed::put(&hash, &hash); - people.push(hash); - }); - }: { - people.iter().for_each(|hash| { - // This does a storage read - let value = frame_support::storage::unhashed::get(hash); - assert_eq!(value, Some(hash.to_vec())); - }); - } - - #[skip_meta] - storage_write { - let i in 0 .. 1_000; - let mut hashes = Vec::new(); - (0..i).for_each(|j| { - let hash = T::Hashing::hash(&j.to_be_bytes()); - hashes.push(hash.encode()); - }); - }: { - hashes.iter().for_each(|hash| { - // This does a storage write - frame_support::storage::unhashed::put(hash, hash); - }); - } verify { - hashes.iter().for_each(|hash| { - let value = frame_support::storage::unhashed::get(hash); - assert_eq!(value, Some(hash.to_vec())); - }); - } - impl_benchmark_test_suite!( Pallet, - crate::baseline::mock::new_test_ext(), - crate::baseline::mock::Test, + mock::new_test_ext(), + mock::Test, ); } #[cfg(test)] pub mod mock { - use sp_runtime::{testing::H256, traits::IdentityLookup}; + use super::*; + use sp_runtime::{testing::H256, BuildStorage}; type AccountId = u64; - type AccountIndex = u32; - type BlockNumber = u64; + type Nonce = u32; - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, } ); @@ -177,14 +131,13 @@ pub mod mock { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = AccountIndex; - type BlockNumber = BlockNumber; + type Nonce = Nonce; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; - type Lookup = IdentityLookup; - type Header = sp_runtime::testing::Header; + type Lookup = sp_runtime::traits::IdentityLookup; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type Version = (); @@ -201,12 +154,11 @@ pub mod mock { impl super::Config for Test {} pub fn new_test_ext() -> sp_io::TestExternalities { - use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStorePtr}; - use sp_std::sync::Arc; + use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let mut ext = sp_io::TestExternalities::new(t); - ext.register_extension(KeystoreExt(Arc::new(KeyStore::new()) as SyncCryptoStorePtr)); + ext.register_extension(KeystoreExt::new(MemoryKeystore::new())); ext } diff --git a/frame/benchmarking/src/lib.rs b/frame/benchmarking/src/lib.rs index e4ebd2f228af..8864cd1d88dd 100644 --- a/frame/benchmarking/src/lib.rs +++ b/frame/benchmarking/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,1886 +47,309 @@ pub use sp_runtime::traits::Zero; pub use sp_runtime::StateVersion; #[doc(hidden)] pub use sp_std::{self, boxed::Box, prelude::Vec, str, vec}; -pub use sp_storage::TrackedStorageKey; +pub use sp_storage::{well_known_keys, TrackedStorageKey}; pub use utils::*; -/// Whitelist the given account. -#[macro_export] -macro_rules! whitelist { - ($acc:ident) => { - frame_benchmarking::benchmarking::add_to_whitelist( - frame_system::Account::::hashed_key_for(&$acc).into(), - ); - }; -} +pub mod v1; +pub use v1::*; -/// Construct pallet benchmarks for weighing dispatchables. -/// -/// Works around the idea of complexity parameters, named by a single letter (which is usually -/// upper cased in complexity notation but is lower-cased for use in this macro). -/// -/// Complexity parameters ("parameters") have a range which is a `u32` pair. Every time a benchmark -/// is prepared and run, this parameter takes a concrete value within the range. There is an -/// associated instancing block, which is a single expression that is evaluated during -/// preparation. It may use `?` (`i.e. `return Err(...)`) to bail with a string error. Here's a -/// few examples: -/// -/// ```ignore -/// // These two are equivalent: -/// let x in 0 .. 10; -/// let x in 0 .. 10 => (); -/// // This one calls a setup function and might return an error (which would be terminal). -/// let y in 0 .. 10 => setup(y)?; -/// // This one uses a code block to do lots of stuff: -/// let z in 0 .. 10 => { -/// let a = z * z / 5; -/// let b = do_something(a)?; -/// combine_into(z, b); -/// } -/// ``` +/// Contains macros, structs, and traits associated with v2 of the pallet benchmarking syntax. /// -/// Note that due to parsing restrictions, if the `from` expression is not a single token (i.e. a -/// literal or constant), then it must be parenthesized. +/// The [`v2::benchmarks`] and [`v2::instance_benchmarks`] macros can be used to designate a +/// module as a benchmarking module that can contain benchmarks and benchmark tests. The +/// `#[benchmarks]` variant will set up a regular, non-instance benchmarking module, and the +/// `#[instance_benchmarks]` variant will set up the module in instance benchmarking mode. /// -/// The macro allows for a number of "arms", each representing an individual benchmark. Using the -/// simple syntax, the associated dispatchable function maps 1:1 with the benchmark and the name of -/// the benchmark is the same as that of the associated function. However, extended syntax allows -/// for arbitrary expressions to be evaluated in a benchmark (including for example, -/// `on_initialize`). +/// Benchmarking modules should be gated behind a `#[cfg(feature = "runtime-benchmarks")]` +/// feature gate to ensure benchmarking code that is only compiled when the +/// `runtime-benchmarks` feature is enabled is not referenced. /// -/// Note that the ranges are *inclusive* on both sides. This is in contrast to ranges in Rust which -/// are left-inclusive right-exclusive. +/// The following is the general syntax for a benchmarks (or instance benchmarks) module: /// -/// Each arm may also have a block of code which is run prior to any instancing and a block of code -/// which is run afterwards. All code blocks may draw upon the specific value of each parameter -/// at any time. Local variables are shared between the two pre- and post- code blocks, but do not -/// leak from the interior of any instancing expressions. +/// ## General Syntax /// -/// Example: /// ```ignore -/// benchmarks! { -/// where_clause { where T::A: From } // Optional line to give additional bound on `T`. +/// #![cfg(feature = "runtime-benchmarks")] /// -/// // first dispatchable: foo; this is a user dispatchable and operates on a `u8` vector of -/// // size `l` -/// foo { -/// let caller = account::(b"caller", 0, benchmarks_seed); -/// let l in 1 .. MAX_LENGTH => initialize_l(l); -/// }: _(RuntimeOrigin::Signed(caller), vec![0u8; l]) +/// use super::{mock_helpers::*, Pallet as MyPallet}; +/// use frame_benchmarking::v2::*; /// -/// // second dispatchable: bar; this is a root dispatchable and accepts a `u8` vector of size -/// // `l`. -/// // In this case, we explicitly name the call using `bar` instead of `_`. -/// bar { -/// let l in 1 .. MAX_LENGTH => initialize_l(l); -/// }: bar(RuntimeOrigin::Root, vec![0u8; l]) -/// -/// // third dispatchable: baz; this is a user dispatchable. It isn't dependent on length like the -/// // other two but has its own complexity `c` that needs setting up. It uses `caller` (in the -/// // pre-instancing block) within the code block. This is only allowed in the param instancers -/// // of arms. -/// baz1 { -/// let caller = account::(b"caller", 0, benchmarks_seed); -/// let c = 0 .. 10 => setup_c(&caller, c); -/// }: baz(RuntimeOrigin::Signed(caller)) +/// #[benchmarks] +/// mod benchmarks { +/// use super::*; /// -/// // this is a second benchmark of the baz dispatchable with a different setup. -/// baz2 { -/// let caller = account::(b"caller", 0, benchmarks_seed); -/// let c = 0 .. 10 => setup_c_in_some_other_way(&caller, c); -/// }: baz(RuntimeOrigin::Signed(caller)) +/// #[benchmark] +/// fn bench_name_1(x: Linear<7, 1_000>, y: Linear<1_000, 100_0000>) { +/// // setup code +/// let z = x + y; +/// let caller = whitelisted_caller(); /// -/// // You may optionally specify the origin type if it can't be determined automatically like -/// // this. -/// baz3 { -/// let caller = account::(b"caller", 0, benchmarks_seed); -/// let l in 1 .. MAX_LENGTH => initialize_l(l); -/// }: baz(RuntimeOrigin::Signed(caller), vec![0u8; l]) +/// #[extrinsic_call] +/// extrinsic_name(SystemOrigin::Signed(caller), other, arguments); /// -/// // this is benchmarking some code that is not a dispatchable. -/// populate_a_set { -/// let x in 0 .. 10_000; -/// let mut m = Vec::::new(); -/// for i in 0..x { -/// m.insert(i); -/// } -/// }: { m.into_iter().collect::() } -/// } -/// ``` -/// -/// Test functions are automatically generated for each benchmark and are accessible to you when you -/// run `cargo test`. All tests are named `test_benchmark_`, implemented on the -/// Pallet struct, and run them in a test externalities environment. The test function runs your -/// benchmark just like a regular benchmark, but only testing at the lowest and highest values for -/// each component. The function will return `Ok(())` if the benchmarks return no errors. +/// // verification code +/// assert_eq!(MyPallet::::my_var(), z); +/// } /// -/// It is also possible to generate one #[test] function per benchmark by calling the -/// `impl_benchmark_test_suite` macro inside the `benchmarks` block. The functions will be named -/// `bench_` and can be run via `cargo test`. -/// You will see one line of output per benchmark. This approach will give you more understandable -/// error messages and allows for parallel benchmark execution. +/// #[benchmark] +/// fn bench_name_2() { +/// // setup code +/// let caller = whitelisted_caller(); /// -/// You can optionally add a `verify` code block at the end of a benchmark to test any final state -/// of your benchmark in a unit test. For example: +/// #[block] +/// { +/// something(some, thing); +/// my_extrinsic(RawOrigin::Signed(caller), some, argument); +/// something_else(foo, bar); +/// } /// -/// ```ignore -/// sort_vector { -/// let x in 1 .. 10000; -/// let mut m = Vec::::new(); -/// for i in (0..x).rev() { -/// m.push(i); +/// // verification code +/// assert_eq!(MyPallet::::something(), 37); /// } -/// }: { -/// m.sort(); -/// } verify { -/// ensure!(m[0] == 0, "You forgot to sort!") /// } /// ``` /// -/// These `verify` blocks will not affect your benchmark results! +/// ## Benchmark Definitions /// -/// You can construct benchmark by using the `impl_benchmark_test_suite` macro or -/// by manually implementing them like so: +/// Within a `#[benchmarks]` or `#[instance_benchmarks]` module, you can define individual +/// benchmarks using the `#[benchmark]` attribute, as shown in the example above. /// -/// ```ignore -/// #[test] -/// fn test_benchmarks() { -/// new_test_ext().execute_with(|| { -/// assert_ok!(Pallet::::test_benchmark_dummy()); -/// assert_err!(Pallet::::test_benchmark_other_name(), "Bad origin"); -/// assert_ok!(Pallet::::test_benchmark_sort_vector()); -/// assert_err!(Pallet::::test_benchmark_broken_benchmark(), "You forgot to sort!"); -/// }); -/// } -/// ``` -#[macro_export] -macro_rules! benchmarks { - ( - $( $rest:tt )* - ) => { - $crate::benchmarks_iter!( - { } - { } - { } - ( ) - ( ) - ( ) - $( $rest )* - ); - } -} - -/// Same as [`benchmarks`] but for instantiable module. +/// The `#[benchmark]` attribute expects a function definition with a blank return type (or a +/// return type compatible with `Result<(), BenchmarkError>`, as discussed below) and zero or +/// more arguments whose names are valid [BenchmarkParameter](`crate::BenchmarkParameter`) +/// parameters, such as `x`, `y`, `a`, `b`, etc., and whose param types must implement +/// [ParamRange](`v2::ParamRange`). At the moment the only valid type that implements +/// [ParamRange](`v2::ParamRange`) is [Linear](`v2::Linear`). /// -/// NOTE: For pallet declared with [`frame_support::pallet`], use [`benchmarks_instance_pallet`]. -#[macro_export] -macro_rules! benchmarks_instance { - ( - $( $rest:tt )* - ) => { - $crate::benchmarks_iter!( - { } - { I: Instance } - { } - ( ) - ( ) - ( ) - $( $rest )* - ); - } -} - -/// Same as [`benchmarks`] but for instantiable pallet declared [`frame_support::pallet`]. +/// The valid syntax for defining a [Linear](`v2::Linear`) is `Linear` where `A`, and `B` +/// are valid integer literals (that fit in a `u32`), such that `B` >= `A`. /// -/// NOTE: For pallet declared with `decl_module!`, use [`benchmarks_instance`]. -#[macro_export] -macro_rules! benchmarks_instance_pallet { - ( - $( $rest:tt )* - ) => { - $crate::benchmarks_iter!( - { } - { I: 'static } - { } - ( ) - ( ) - ( ) - $( $rest )* - ); - } -} - -#[macro_export] -#[doc(hidden)] -macro_rules! benchmarks_iter { - // detect and extract `impl_benchmark_test_suite` call: - // - with a semi-colon - ( - { } - { $( $instance:ident: $instance_bound:tt )? } - { $( $where_clause:tt )* } - ( $( $names:tt )* ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - impl_benchmark_test_suite!( - $bench_module:ident, - $new_test_ext:expr, - $test:path - $(, $( $args:tt )* )?); - $( $rest:tt )* - ) => { - $crate::benchmarks_iter! { - { $bench_module, $new_test_ext, $test $(, $( $args )* )? } - { $( $instance: $instance_bound )? } - { $( $where_clause )* } - ( $( $names )* ) - ( $( $names_extra )* ) - ( $( $names_skip_meta )* ) - $( $rest )* - } - }; - // - without a semicolon - ( - { } - { $( $instance:ident: $instance_bound:tt )? } - { $( $where_clause:tt )* } - ( $( $names:tt )* ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - impl_benchmark_test_suite!( - $bench_module:ident, - $new_test_ext:expr, - $test:path - $(, $( $args:tt )* )?) - $( $rest:tt )* - ) => { - $crate::benchmarks_iter! { - { $bench_module, $new_test_ext, $test $(, $( $args )* )? } - { $( $instance: $instance_bound )? } - { $( $where_clause )* } - ( $( $names )* ) - ( $( $names_extra )* ) - ( $( $names_skip_meta )* ) - $( $rest )* - } - }; - // detect and extract where clause: - ( - { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } - { $( $instance:ident: $instance_bound:tt )? } - { $( $where_clause:tt )* } - ( $( $names:tt )* ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - where_clause { where $( $where_bound:tt )* } - $( $rest:tt )* - ) => { - $crate::benchmarks_iter! { - { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } - { $( $instance: $instance_bound)? } - { $( $where_bound )* } - ( $( $names )* ) - ( $( $names_extra )* ) - ( $( $names_skip_meta )* ) - $( $rest )* - } - }; - // detect and extract `#[skip_meta]` tag: - ( - { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } - { $( $instance:ident: $instance_bound:tt )? } - { $( $where_clause:tt )* } - ( $( $names:tt )* ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - #[skip_meta] - $name:ident - $( $rest:tt )* - ) => { - $crate::benchmarks_iter! { - { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } - { $( $instance: $instance_bound )? } - { $( $where_clause )* } - ( $( $names )* ) - ( $( $names_extra )* ) - ( $( $names_skip_meta )* $name ) - $name - $( $rest )* - } - }; - // detect and extract `#[extra]` tag: - ( - { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } - { $( $instance:ident: $instance_bound:tt )? } - { $( $where_clause:tt )* } - ( $( $names:tt )* ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - #[extra] - $name:ident - $( $rest:tt )* - ) => { - $crate::benchmarks_iter! { - { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } - { $( $instance: $instance_bound )? } - { $( $where_clause )* } - ( $( $names )* ) - ( $( $names_extra )* $name ) - ( $( $names_skip_meta )* ) - $name - $( $rest )* - } - }; - // mutation arm: - ( - { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } - { $( $instance:ident: $instance_bound:tt )? } - { $( $where_clause:tt )* } - ( $( $names:tt )* ) // This contains $( $( { $instance } )? $name:ident )* - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - $name:ident { $( $code:tt )* }: _ $(< $origin_type:ty>)? ( $origin:expr $( , $arg:expr )* ) - verify $postcode:block - $( $rest:tt )* - ) => { - $crate::benchmarks_iter! { - { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } - { $( $instance: $instance_bound )? } - { $( $where_clause )* } - ( $( $names )* ) - ( $( $names_extra )* ) - ( $( $names_skip_meta )* ) - $name { $( $code )* }: $name $(< $origin_type >)? ( $origin $( , $arg )* ) - verify $postcode - $( $rest )* - } - }; - // mutation arm: - ( - { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } - { $( $instance:ident: $instance_bound:tt )? } - { $( $where_clause:tt )* } - ( $( $names:tt )* ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - $name:ident { $( $code:tt )* }: $dispatch:ident $(<$origin_type:ty>)? ( $origin:expr $( , $arg:expr )* ) - verify $postcode:block - $( $rest:tt )* - ) => { - $crate::paste::paste! { - $crate::benchmarks_iter! { - { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } - { $( $instance: $instance_bound )? } - { $( $where_clause )* } - ( $( $names )* ) - ( $( $names_extra )* ) - ( $( $names_skip_meta )* ) - $name { - $( $code )* - let __call = Call::< - T - $( , $instance )? - >:: [< new_call_variant_ $dispatch >] ( - $($arg),* - ); - let __benchmarked_call_encoded = $crate::frame_support::codec::Encode::encode( - &__call - ); - }: { - let __call_decoded = < - Call - as $crate::frame_support::codec::Decode - >::decode(&mut &__benchmarked_call_encoded[..]) - .expect("call is encoded above, encoding must be correct"); - let __origin = $crate::to_origin!($origin $(, $origin_type)?); - as $crate::frame_support::traits::UnfilteredDispatchable - >::dispatch_bypass_filter(__call_decoded, __origin)?; - } - verify $postcode - $( $rest )* - } - } - }; - // iteration arm: - ( - { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } - { $( $instance:ident: $instance_bound:tt )? } - { $( $where_clause:tt )* } - ( $( $names:tt )* ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - $name:ident { $( $code:tt )* }: $eval:block - verify $postcode:block - $( $rest:tt )* - ) => { - $crate::benchmark_backend! { - { $( $instance: $instance_bound )? } - $name - { $( $where_clause )* } - { } - { $eval } - { $( $code )* } - $postcode - } - - #[cfg(test)] - $crate::impl_benchmark_test!( - { $( $where_clause )* } - { $( $instance: $instance_bound )? } - $name - ); - - $crate::benchmarks_iter!( - { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } - { $( $instance: $instance_bound )? } - { $( $where_clause )* } - ( $( $names )* { $( $instance )? } $name ) - ( $( $names_extra )* ) - ( $( $names_skip_meta )* ) - $( $rest )* - ); - }; - // iteration-exit arm which generates a #[test] function for each case. - ( - { $bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )? } - { $( $instance:ident: $instance_bound:tt )? } - { $( $where_clause:tt )* } - ( $( $names:tt )* ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - ) => { - $crate::selected_benchmark!( - { $( $where_clause)* } - { $( $instance: $instance_bound )? } - $( $names )* - ); - $crate::impl_benchmark!( - { $( $where_clause )* } - { $( $instance: $instance_bound )? } - ( $( $names )* ) - ( $( $names_extra ),* ) - ( $( $names_skip_meta ),* ) - ); - $crate::impl_test_function!( - ( $( $names )* ) - ( $( $names_extra )* ) - ( $( $names_skip_meta )* ) - $bench_module, - $new_test_ext, - $test - $(, $( $args )* )? - ); - }; - // iteration-exit arm which doesn't generate a #[test] function for all cases. - ( - { } - { $( $instance:ident: $instance_bound:tt )? } - { $( $where_clause:tt )* } - ( $( $names:tt )* ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - ) => { - $crate::selected_benchmark!( - { $( $where_clause)* } - { $( $instance: $instance_bound )? } - $( $names )* - ); - $crate::impl_benchmark!( - { $( $where_clause )* } - { $( $instance: $instance_bound )? } - ( $( $names )* ) - ( $( $names_extra ),* ) - ( $( $names_skip_meta ),* ) - ); - }; - // add verify block to _() format - ( - { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } - { $( $instance:ident: $instance_bound:tt )? } - { $( $where_clause:tt )* } - ( $( $names:tt )* ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - $name:ident { $( $code:tt )* }: _ $(<$origin_type:ty>)? ( $origin:expr $( , $arg:expr )* ) - $( $rest:tt )* - ) => { - $crate::benchmarks_iter! { - { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } - { $( $instance: $instance_bound )? } - { $( $where_clause )* } - ( $( $names )* ) - ( $( $names_extra )* ) - ( $( $names_skip_meta )* ) - $name { $( $code )* }: _ $(<$origin_type>)? ( $origin $( , $arg )* ) - verify { } - $( $rest )* - } - }; - // add verify block to name() format - ( - { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } - { $( $instance:ident: $instance_bound:tt )? } - { $( $where_clause:tt )* } - ( $( $names:tt )* ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - $name:ident { $( $code:tt )* }: $dispatch:ident $(<$origin_type:ty>)? ( $origin:expr $( , $arg:expr )* ) - $( $rest:tt )* - ) => { - $crate::benchmarks_iter! { - { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } - { $( $instance: $instance_bound )? } - { $( $where_clause )* } - ( $( $names )* ) - ( $( $names_extra )* ) - ( $( $names_skip_meta )* ) - $name { $( $code )* }: $dispatch $(<$origin_type>)? ( $origin $( , $arg )* ) - verify { } - $( $rest )* - } - }; - // add verify block to {} format - ( - { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } - { $( $instance:ident: $instance_bound:tt )? } - { $( $where_clause:tt )* } - ( $( $names:tt )* ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - $name:ident { $( $code:tt )* }: $(<$origin_type:ty>)? $eval:block - $( $rest:tt )* - ) => { - $crate::benchmarks_iter!( - { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } - { $( $instance: $instance_bound )? } - { $( $where_clause )* } - ( $( $names )* ) - ( $( $names_extra )* ) - ( $( $names_skip_meta )* ) - $name { $( $code )* }: $(<$origin_type>)? $eval - verify { } - $( $rest )* - ); - }; -} - -#[macro_export] -#[doc(hidden)] -macro_rules! to_origin { - ($origin:expr) => { - $origin.into() - }; - ($origin:expr, $origin_type:ty) => { - <::RuntimeOrigin as From<$origin_type>>::from($origin) - }; -} - -#[macro_export] -#[doc(hidden)] -macro_rules! benchmark_backend { - // parsing arms - ( - { $( $instance:ident: $instance_bound:tt )? } - $name:ident - { $( $where_clause:tt )* } - { $( PRE { $( $pre_parsed:tt )* } )* } - { $eval:block } - { - let $pre_id:tt : $pre_ty:ty = $pre_ex:expr; - $( $rest:tt )* - } - $postcode:block - ) => { - $crate::benchmark_backend! { - { $( $instance: $instance_bound )? } - $name - { $( $where_clause )* } - { - $( PRE { $( $pre_parsed )* } )* - PRE { $pre_id , $pre_ty , $pre_ex } - } - { $eval } - { $( $rest )* } - $postcode - } - }; - ( - { $( $instance:ident: $instance_bound:tt )? } - $name:ident - { $( $where_clause:tt )* } - { $( $parsed:tt )* } - { $eval:block } - { - let $param:ident in ( $param_from:expr ) .. $param_to:expr => $param_instancer:expr; - $( $rest:tt )* - } - $postcode:block - ) => { - $crate::benchmark_backend! { - { $( $instance: $instance_bound )? } - $name - { $( $where_clause )* } - { - $( $parsed )* - PARAM { $param , $param_from , $param_to , $param_instancer } - } - { $eval } - { $( $rest )* } - $postcode - } - }; - // mutation arm to look after a single tt for param_from. - ( - { $( $instance:ident: $instance_bound:tt )? } - $name:ident - { $( $where_clause:tt )* } - { $( $parsed:tt )* } - { $eval:block } - { - let $param:ident in $param_from:tt .. $param_to:expr => $param_instancer:expr ; - $( $rest:tt )* - } - $postcode:block - ) => { - $crate::benchmark_backend! { - { $( $instance: $instance_bound )? } - $name - { $( $where_clause )* } - { $( $parsed )* } - { $eval } - { - let $param in ( $param_from ) .. $param_to => $param_instancer; - $( $rest )* - } - $postcode - } - }; - // mutation arm to look after the default tail of `=> ()` - ( - { $( $instance:ident: $instance_bound:tt )? } - $name:ident - { $( $where_clause:tt )* } - { $( $parsed:tt )* } - { $eval:block } - { - let $param:ident in $param_from:tt .. $param_to:expr; - $( $rest:tt )* - } - $postcode:block - ) => { - $crate::benchmark_backend! { - { $( $instance: $instance_bound )? } - $name - { $( $where_clause )* } - { $( $parsed )* } - { $eval } - { - let $param in $param_from .. $param_to => (); - $( $rest )* - } - $postcode - } - }; - // mutation arm to look after `let _ =` - ( - { $( $instance:ident: $instance_bound:tt )? } - $name:ident - { $( $where_clause:tt )* } - { $( $parsed:tt )* } - { $eval:block } - { - let $pre_id:tt = $pre_ex:expr; - $( $rest:tt )* - } - $postcode:block - ) => { - $crate::benchmark_backend! { - { $( $instance: $instance_bound )? } - $name - { $( $where_clause )* } - { $( $parsed )* } - { $eval } - { - let $pre_id : _ = $pre_ex; - $( $rest )* - } - $postcode - } - }; - // actioning arm - ( - { $( $instance:ident: $instance_bound:tt )? } - $name:ident - { $( $where_clause:tt )* } - { - $( PRE { $pre_id:tt , $pre_ty:ty , $pre_ex:expr } )* - $( PARAM { $param:ident , $param_from:expr , $param_to:expr , $param_instancer:expr } )* - } - { $eval:block } - { $( $post:tt )* } - $postcode:block - ) => { - #[allow(non_camel_case_types)] - struct $name; - #[allow(unused_variables)] - impl, $instance: $instance_bound )? > - $crate::BenchmarkingSetup for $name - where $( $where_clause )* - { - fn components(&self) -> $crate::Vec<($crate::BenchmarkParameter, u32, u32)> { - $crate::vec! [ - $( - ($crate::BenchmarkParameter::$param, $param_from, $param_to) - ),* - ] - } - - fn instance( - &self, - components: &[($crate::BenchmarkParameter, u32)], - verify: bool - ) -> Result<$crate::Box Result<(), $crate::BenchmarkError>>, $crate::BenchmarkError> { - $( - // Prepare instance - let $param = components.iter() - .find(|&c| c.0 == $crate::BenchmarkParameter::$param) - .ok_or("Could not find component in benchmark preparation.")? - .1; - )* - $( - let $pre_id : $pre_ty = $pre_ex; - )* - $( $param_instancer ; )* - $( $post )* - - Ok($crate::Box::new(move || -> Result<(), $crate::BenchmarkError> { - $eval; - if verify { - $postcode; - } - Ok(()) - })) - } - } - }; -} - -// Creates #[test] functions for the given bench cases. -#[macro_export] -#[doc(hidden)] -macro_rules! impl_bench_case_tests { - ( - { $module:ident, $new_test_exec:expr, $exec_name:ident, $test:path, $extra:expr } - { $( $names_extra:tt )* } - $( { $( $bench_inst:ident )? } $bench:ident )* - ) - => { - $crate::impl_bench_name_tests!( - $module, $new_test_exec, $exec_name, $test, $extra, - { $( $names_extra )* }, - $( { $bench } )+ - ); - } -} - -// Creates a #[test] function for the given bench name. -#[macro_export] -#[doc(hidden)] -macro_rules! impl_bench_name_tests { - // recursion anchor - ( - $module:ident, $new_test_exec:expr, $exec_name:ident, $test:path, $extra:expr, - { $( $names_extra:tt )* }, - { $name:ident } - ) => { - $crate::paste::paste! { - #[test] - fn [] () { - $new_test_exec.$exec_name(|| { - // Skip all #[extra] benchmarks if $extra is false. - if !($extra) { - let disabled = $crate::vec![ $( stringify!($names_extra).as_ref() ),* ]; - if disabled.contains(&stringify!($name)) { - $crate::log::error!( - "INFO: extra benchmark skipped - {}", - stringify!($name), - ); - return (); - } - } - - // Same per-case logic as when all cases are run in the - // same function. - match std::panic::catch_unwind(|| { - $module::<$test>::[< test_benchmark_ $name >] () - }) { - Err(err) => { - panic!("{}: {:?}", stringify!($name), err); - }, - Ok(Err(err)) => { - match err { - $crate::BenchmarkError::Stop(err) => { - panic!("{}: {:?}", stringify!($name), err); - }, - $crate::BenchmarkError::Override(_) => { - // This is still considered a success condition. - $crate::log::error!( - "WARNING: benchmark error overrided - {}", - stringify!($name), - ); - }, - $crate::BenchmarkError::Skip => { - // This is considered a success condition. - $crate::log::error!( - "WARNING: benchmark error skipped - {}", - stringify!($name), - ); - }, - $crate::BenchmarkError::Weightless => { - // This is considered a success condition. - $crate::log::error!( - "WARNING: benchmark weightless skipped - {}", - stringify!($name), - ); - } - } - }, - Ok(Ok(())) => (), - } - }); - } - } - }; - // recursion tail - ( - $module:ident, $new_test_exec:expr, $exec_name:ident, $test:path, $extra:expr, - { $( $names_extra:tt )* }, - { $name:ident } $( { $rest:ident } )+ - ) => { - // car - $crate::impl_bench_name_tests!($module, $new_test_exec, $exec_name, $test, $extra, - { $( $names_extra )* }, { $name }); - // cdr - $crate::impl_bench_name_tests!($module, $new_test_exec, $exec_name, $test, $extra, - { $( $names_extra )* }, $( { $rest } )+); - }; -} - -// Creates a `SelectedBenchmark` enum implementing `BenchmarkingSetup`. -// -// Every variant must implement [`BenchmarkingSetup`]. -// -// ```nocompile -// -// struct Transfer; -// impl BenchmarkingSetup for Transfer { ... } -// -// struct SetBalance; -// impl BenchmarkingSetup for SetBalance { ... } -// -// selected_benchmark!({} Transfer {} SetBalance); -// ``` -#[macro_export] -#[doc(hidden)] -macro_rules! selected_benchmark { - ( - { $( $where_clause:tt )* } - { $( $instance:ident: $instance_bound:tt )? } - $( { $( $bench_inst:ident )? } $bench:ident )* - ) => { - // The list of available benchmarks for this pallet. - #[allow(non_camel_case_types)] - enum SelectedBenchmark { - $( $bench, )* - } - - // Allow us to select a benchmark from the list of available benchmarks. - impl, $instance: $instance_bound )? > - $crate::BenchmarkingSetup for SelectedBenchmark - where $( $where_clause )* - { - fn components(&self) -> $crate::Vec<($crate::BenchmarkParameter, u32, u32)> { - match self { - $( - Self::$bench => < - $bench as $crate::BenchmarkingSetup - >::components(&$bench), - )* - } - } - - fn instance( - &self, - components: &[($crate::BenchmarkParameter, u32)], - verify: bool - ) -> Result<$crate::Box Result<(), $crate::BenchmarkError>>, $crate::BenchmarkError> { - match self { - $( - Self::$bench => < - $bench as $crate::BenchmarkingSetup - >::instance(&$bench, components, verify), - )* - } - } - } - }; -} - -#[macro_export] -#[doc(hidden)] -macro_rules! impl_benchmark { - ( - { $( $where_clause:tt )* } - { $( $instance:ident: $instance_bound:tt )? } - ( $( { $( $name_inst:ident )? } $name:ident )* ) - ( $( $name_extra:ident ),* ) - ( $( $name_skip_meta:ident ),* ) - ) => { - // We only need to implement benchmarks for the runtime-benchmarks feature or testing. - #[cfg(any(feature = "runtime-benchmarks", test))] - impl, $instance: $instance_bound )? > - $crate::Benchmarking for Pallet - where T: frame_system::Config, $( $where_clause )* - { - fn benchmarks(extra: bool) -> $crate::Vec<$crate::BenchmarkMetadata> { - let mut all_names = $crate::vec![ $( stringify!($name).as_ref() ),* ]; - if !extra { - let extra = [ $( stringify!($name_extra).as_ref() ),* ]; - all_names.retain(|x| !extra.contains(x)); - } - all_names.into_iter().map(|benchmark| { - let selected_benchmark = match benchmark { - $( stringify!($name) => SelectedBenchmark::$name, )* - _ => panic!("all benchmarks should be selectable"), - }; - let components = < - SelectedBenchmark as $crate::BenchmarkingSetup - >::components(&selected_benchmark); - - $crate::BenchmarkMetadata { - name: benchmark.as_bytes().to_vec(), - components, - } - }).collect::<$crate::Vec<_>>() - } - - fn run_benchmark( - extrinsic: &[u8], - c: &[($crate::BenchmarkParameter, u32)], - whitelist: &[$crate::TrackedStorageKey], - verify: bool, - internal_repeats: u32, - ) -> Result<$crate::Vec<$crate::BenchmarkResult>, $crate::BenchmarkError> { - // Map the input to the selected benchmark. - let extrinsic = $crate::str::from_utf8(extrinsic) - .map_err(|_| "`extrinsic` is not a valid utf8 string!")?; - let selected_benchmark = match extrinsic { - $( stringify!($name) => SelectedBenchmark::$name, )* - _ => return Err("Could not find extrinsic.".into()), - }; - - // Add whitelist to DB including whitelisted caller - let mut whitelist = whitelist.to_vec(); - let whitelisted_caller_key = - as $crate::frame_support::storage::StorageMap<_,_>>::hashed_key_for( - $crate::whitelisted_caller::() - ); - whitelist.push(whitelisted_caller_key.into()); - // Whitelist the transactional layer. - let transactional_layer_key = $crate::TrackedStorageKey::new( - $crate::frame_support::storage::transactional::TRANSACTION_LEVEL_KEY.into() - ); - whitelist.push(transactional_layer_key); - - $crate::benchmarking::set_whitelist(whitelist); - - let mut results: $crate::Vec<$crate::BenchmarkResult> = $crate::Vec::new(); - - // Always do at least one internal repeat... - for _ in 0 .. internal_repeats.max(1) { - // Always reset the state after the benchmark. - $crate::defer!($crate::benchmarking::wipe_db()); - - // Set up the externalities environment for the setup we want to - // benchmark. - let closure_to_benchmark = < - SelectedBenchmark as $crate::BenchmarkingSetup - >::instance(&selected_benchmark, c, verify)?; - - // Set the block number to at least 1 so events are deposited. - if $crate::Zero::is_zero(&frame_system::Pallet::::block_number()) { - frame_system::Pallet::::set_block_number(1u32.into()); - } - - // Commit the externalities to the database, flushing the DB cache. - // This will enable worst case scenario for reading from the database. - $crate::benchmarking::commit_db(); - - // Reset the read/write counter so we don't count operations in the setup process. - $crate::benchmarking::reset_read_write_count(); - - // Time the extrinsic logic. - $crate::log::trace!( - target: "benchmark", - "Start Benchmark: {} ({:?})", - extrinsic, - c - ); - - let start_pov = $crate::benchmarking::proof_size(); - let start_extrinsic = $crate::benchmarking::current_time(); - - closure_to_benchmark()?; - - let finish_extrinsic = $crate::benchmarking::current_time(); - let end_pov = $crate::benchmarking::proof_size(); - - // Calculate the diff caused by the benchmark. - let elapsed_extrinsic = finish_extrinsic.saturating_sub(start_extrinsic); - let diff_pov = match (start_pov, end_pov) { - (Some(start), Some(end)) => end.saturating_sub(start), - _ => Default::default(), - }; - - // Commit the changes to get proper write count - $crate::benchmarking::commit_db(); - $crate::log::trace!( - target: "benchmark", - "End Benchmark: {} ns", elapsed_extrinsic - ); - let read_write_count = $crate::benchmarking::read_write_count(); - $crate::log::trace!( - target: "benchmark", - "Read/Write Count {:?}", read_write_count - ); - - // Time the storage root recalculation. - let start_storage_root = $crate::benchmarking::current_time(); - $crate::storage_root($crate::StateVersion::V1); - let finish_storage_root = $crate::benchmarking::current_time(); - let elapsed_storage_root = finish_storage_root - start_storage_root; - - let skip_meta = [ $( stringify!($name_skip_meta).as_ref() ),* ]; - let read_and_written_keys = if skip_meta.contains(&extrinsic) { - $crate::vec![(b"Skipped Metadata".to_vec(), 0, 0, false)] - } else { - $crate::benchmarking::get_read_and_written_keys() - }; - - results.push($crate::BenchmarkResult { - components: c.to_vec(), - extrinsic_time: elapsed_extrinsic, - storage_root_time: elapsed_storage_root, - reads: read_write_count.0, - repeat_reads: read_write_count.1, - writes: read_write_count.2, - repeat_writes: read_write_count.3, - proof_size: diff_pov, - keys: read_and_written_keys, - }); - } - - return Ok(results); - } - } - - #[cfg(test)] - impl, $instance: $instance_bound )? > - Pallet - where T: frame_system::Config, $( $where_clause )* - { - /// Test a particular benchmark by name. - /// - /// This isn't called `test_benchmark_by_name` just in case some end-user eventually - /// writes a benchmark, itself called `by_name`; the function would be shadowed in - /// that case. - /// - /// This is generally intended to be used by child test modules such as those created - /// by the `impl_benchmark_test_suite` macro. However, it is not an error if a pallet - /// author chooses not to implement benchmarks. - #[allow(unused)] - fn test_bench_by_name(name: &[u8]) -> Result<(), $crate::BenchmarkError> { - let name = $crate::str::from_utf8(name) - .map_err(|_| -> $crate::BenchmarkError { "`name` is not a valid utf8 string!".into() })?; - match name { - $( stringify!($name) => { - $crate::paste::paste! { Self::[< test_benchmark_ $name >]() } - } )* - _ => Err("Could not find test for requested benchmark.".into()), - } - } - } - }; -} - -// This creates a unit test for one benchmark of the main benchmark macro. -// It runs the benchmark using the `high` and `low` value for each component -// and ensure that everything completes successfully. -// Instances each component with six values which can be controlled with the -// env variable `VALUES_PER_COMPONENT`. -#[macro_export] -#[doc(hidden)] -macro_rules! impl_benchmark_test { - ( - { $( $where_clause:tt )* } - { $( $instance:ident: $instance_bound:tt )? } - $name:ident - ) => { - $crate::paste::item! { - #[cfg(test)] - impl, $instance: $instance_bound )? > - Pallet - where T: frame_system::Config, $( $where_clause )* - { - #[allow(unused)] - fn [] () -> Result<(), $crate::BenchmarkError> { - let selected_benchmark = SelectedBenchmark::$name; - let components = < - SelectedBenchmark as $crate::BenchmarkingSetup - >::components(&selected_benchmark); - - let execute_benchmark = | - c: $crate::Vec<($crate::BenchmarkParameter, u32)> - | -> Result<(), $crate::BenchmarkError> { - // Always reset the state after the benchmark. - $crate::defer!($crate::benchmarking::wipe_db()); - - // Set up the benchmark, return execution + verification function. - let closure_to_verify = < - SelectedBenchmark as $crate::BenchmarkingSetup - >::instance(&selected_benchmark, &c, true)?; - - // Set the block number to at least 1 so events are deposited. - if $crate::Zero::is_zero(&frame_system::Pallet::::block_number()) { - frame_system::Pallet::::set_block_number(1u32.into()); - } - - // Run execution + verification - closure_to_verify() - }; - - if components.is_empty() { - execute_benchmark(Default::default())?; - } else { - let num_values: u32 = if let Ok(ev) = std::env::var("VALUES_PER_COMPONENT") { - ev.parse().map_err(|_| { - $crate::BenchmarkError::Stop( - "Could not parse env var `VALUES_PER_COMPONENT` as u32." - ) - })? - } else { - 6 - }; - - if num_values < 2 { - return Err("`VALUES_PER_COMPONENT` must be at least 2".into()); - } - - for (name, low, high) in components.clone().into_iter() { - // Test the lowest, highest (if its different from the lowest) - // and up to num_values-2 more equidistant values in between. - // For 0..10 and num_values=6 this would mean: [0, 2, 4, 6, 8, 10] - - let mut values = $crate::vec![low]; - let diff = (high - low).min(num_values - 1); - let slope = (high - low) as f32 / diff as f32; - - for i in 1..=diff { - let value = ((low as f32 + slope * i as f32) as u32) - .clamp(low, high); - values.push(value); - } - - for component_value in values { - // Select the max value for all the other components. - let c: $crate::Vec<($crate::BenchmarkParameter, u32)> = components - .iter() - .map(|(n, _, h)| - if *n == name { - (*n, component_value) - } else { - (*n, *h) - } - ) - .collect(); - - execute_benchmark(c)?; - } - } - } - Ok(()) - } - } - } - }; -} - -/// This creates a test suite which runs the module's benchmarks. +/// Anywhere within a benchmark function you may use the generic `T: Config` parameter as well +/// as `I` in the case of an `#[instance_benchmarks]` module. You should not add these to the +/// function signature as this will be handled automatically for you based on whether this is a +/// `#[benchmarks]` or `#[instance_benchmarks]` module and whatever [where clause](#where-clause) +/// you have defined for the module. You should not manually add any generics to the +/// signature of your benchmark function. /// -/// When called in `pallet_example_basic` as +/// Also note that the `// setup code` and `// verification code` comments shown above are not +/// required and are included simply for demonstration purposes. /// -/// ```rust,ignore -/// impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); -/// ``` +/// ### `#[extrinsic_call]` and `#[block]` /// -/// It expands to the equivalent of: +/// Within the benchmark function body, either an `#[extrinsic_call]` or a `#[block]` +/// annotation is required. These attributes should be attached to a block (shown in +/// `bench_name_2` above) or a one-line function call (shown in `bench_name_1` above, in `syn` +/// parlance this should be an `ExprCall`), respectively. /// -/// ```rust,ignore -/// #[cfg(test)] -/// mod tests { -/// use super::*; -/// use crate::tests::{new_test_ext, Test}; -/// use frame_support::assert_ok; +/// The `#[block]` syntax is broad and will benchmark any code contained within the block the +/// attribute is attached to. If `#[block]` is attached to something other than a block, a +/// compiler error will be emitted. /// -/// #[test] -/// fn test_benchmarks() { -/// new_test_ext().execute_with(|| { -/// assert_ok!(test_benchmark_accumulate_dummy::()); -/// assert_ok!(test_benchmark_set_dummy::()); -/// assert_ok!(test_benchmark_sort_vector::()); -/// }); -/// } -/// } -/// ``` -/// -/// When called inside the `benchmarks` macro of the `pallet_example_basic` as +/// The one-line `#[extrinsic_call]` syntax must consist of a function call to an extrinsic, +/// where the first argument is the origin. If `#[extrinsic_call]` is attached to an item that +/// doesn't meet these requirements, a compiler error will be emitted. /// -/// ```rust,ignore -/// benchmarks! { -/// // Benchmarks omitted for brevity +/// As a short-hand, you may substitute the name of the extrinsic call with `_`, such as the +/// following: /// -/// impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); -/// } +/// ```ignore +/// #[extrinsic_call] +/// _(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0); /// ``` /// -/// It expands to the equivalent of: +/// The underscore will be substituted with the name of the benchmark (i.e. the name of the +/// function in the benchmark function definition). /// -/// ```rust,ignore -/// #[cfg(test)] -/// mod benchmarking { -/// use super::*; -/// use crate::tests::{new_test_ext, Test}; -/// use frame_support::assert_ok; -/// -/// #[test] -/// fn bench_accumulate_dummy() { -/// new_test_ext().execute_with(|| { -/// assert_ok!(test_benchmark_accumulate_dummy::()); -/// }) -/// } -/// -/// #[test] -/// fn bench_set_dummy() { -/// new_test_ext().execute_with(|| { -/// assert_ok!(test_benchmark_set_dummy::()); -/// }) -/// } -/// -/// #[test] -/// fn bench_sort_vector() { -/// new_test_ext().execute_with(|| { -/// assert_ok!(test_benchmark_sort_vector::()); -/// }) -/// } -/// } +/// In case of a `force_origin` where you want to elevate the privileges of the provided origin, +/// this is the general syntax: +/// ```ignore +/// #[extrinsic_call] +/// _(force_origin as T::RuntimeOrigin, 0u32.into(), 0); /// ``` /// -/// ## Arguments +/// Regardless of whether `#[extrinsic_call]` or `#[block]` is used, this attribute also serves +/// the purpose of designating the boundary between the setup code portion of the benchmark +/// (everything before the `#[extrinsic_call]` or `#[block]` attribute) and the verification +/// stage (everything after the item that the `#[extrinsic_call]` or `#[block]` attribute is +/// attached to). The setup code section should contain any code that needs to execute before +/// the measured portion of the benchmark executes. The verification section is where you can +/// perform assertions to verify that the extrinsic call (or whatever is happening in your +/// block, if you used the `#[block]` syntax) executed successfully. /// -/// The first argument, `module`, must be the path to this crate's module. +/// Note that neither `#[extrinsic_call]` nor `#[block]` are real attribute macros and are +/// instead consumed by the outer macro pattern as part of the enclosing benchmark function +/// definition. This is why we are able to use `#[extrinsic_call]` and `#[block]` within a +/// function definition even though this behavior has not been stabilized +/// yet—`#[extrinsic_call]` and `#[block]` are parsed and consumed as part of the benchmark +/// definition parsing code, so they never expand as their own attribute macros. /// -/// The second argument, `new_test_ext`, must be a function call which returns either a -/// `sp_io::TestExternalities`, or some other type with a similar interface. +/// ### Optional Attributes /// -/// Note that this function call is _not_ evaluated at compile time, but is instead copied textually -/// into each appropriate invocation site. +/// The keywords `extra` and `skip_meta` can be provided as optional arguments to the +/// `#[benchmark]` attribute, i.e. `#[benchmark(extra, skip_meta)]`. Including either of these +/// will enable the `extra` or `skip_meta` option, respectively. These options enable the same +/// behavior they did in the old benchmarking syntax in `frame_benchmarking`, namely: /// -/// The third argument, `test`, must be the path to the runtime. The item to which this must refer -/// will generally take the form: +/// #### `extra` /// -/// ```rust,ignore -/// frame_support::construct_runtime!( -/// pub enum Test where ... -/// { ... } -/// ); -/// ``` +/// Specifies that this benchmark should not normally run. To run benchmarks marked with +/// `extra`, you will need to invoke the `frame-benchmarking-cli` with `--extra`. /// -/// There is an optional fourth argument, with keyword syntax: `benchmarks_path = -/// path_to_benchmarks_invocation`. In the typical case in which this macro is in the same module as -/// the `benchmarks!` invocation, you don't need to supply this. However, if the -/// `impl_benchmark_test_suite!` invocation is in a different module than the `benchmarks!` -/// invocation, then you should provide the path to the module containing the `benchmarks!` -/// invocation: +/// #### `skip_meta` /// -/// ```rust,ignore -/// mod benches { -/// benchmarks!{ -/// ... -/// } -/// } +/// Specifies that the benchmarking framework should not analyze the storage keys that the +/// benchmarked code read or wrote. This useful to suppress the prints in the form of unknown +/// 0x… in case a storage key that does not have metadata. Note that this skips the analysis of +/// all accesses, not just ones without metadata. /// -/// mod tests { -/// // because of macro syntax limitations, neither Pallet nor benches can be paths, but both have -/// // to be idents in the scope of `impl_benchmark_test_suite`. -/// use crate::{benches, Pallet}; +/// ## Where Clause /// -/// impl_benchmark_test_suite!(Pallet, new_test_ext(), Test, benchmarks_path = benches); +/// Some pallets require a where clause specifying constraints on their generics to make +/// writing benchmarks feasible. To accomodate this situation, you can provide such a where +/// clause as the (only) argument to the `#[benchmarks]` or `#[instance_benchmarks]` attribute +/// macros. Below is an example of this taken from the `message-queue` pallet. /// -/// // new_test_ext and the Test item are defined later in this module +/// ```ignore +/// #[benchmarks( +/// where +/// <::MessageProcessor as ProcessMessage>::Origin: From + PartialEq, +/// ::Size: From, +/// )] +/// mod benchmarks { +/// use super::*; +/// // ... /// } /// ``` /// -/// There is an optional fifth argument, with keyword syntax: `extra = true` or `extra = false`. -/// By default, this generates a test suite which iterates over all benchmarks, including those -/// marked with the `#[extra]` annotation. Setting `extra = false` excludes those. +/// ## Benchmark Tests /// -/// There is an optional sixth argument, with keyword syntax: `exec_name = custom_exec_name`. -/// By default, this macro uses `execute_with` for this parameter. This argument, if set, is subject -/// to these restrictions: -/// -/// - It must be the name of a method applied to the output of the `new_test_ext` argument. -/// - That method must have a signature capable of receiving a single argument of the form `impl -/// FnOnce()`. -// ## Notes (not for rustdoc) -// -// The biggest challenge for this macro is communicating the actual test functions to be run. We -// can't just build an array of function pointers to each test function and iterate over it, because -// the test functions are parameterized by the `Test` type. That's incompatible with -// monomorphization: if it were legal, then even if the compiler detected and monomorphized the -// functions into only the types of the callers, which implementation would the function pointer -// point to? There would need to be some kind of syntax for selecting the destination of the pointer -// according to a generic argument, and in general it would be a huge mess and not worth it. -// -// Instead, we're going to steal a trick from `fn run_benchmark`: generate a function which is -// itself parametrized by `Test`, which accepts a `&[u8]` parameter containing the name of the -// benchmark, and dispatches based on that to the appropriate real test implementation. Then, we can -// just iterate over the `Benchmarking::benchmarks` list to run the actual implementations. -#[macro_export] -macro_rules! impl_benchmark_test_suite { - ( - $bench_module:ident, - $new_test_ext:expr, - $test:path - $(, $( $rest:tt )* )? - ) => { - $crate::impl_test_function!( - () - () - () - $bench_module, - $new_test_ext, - $test - $(, $( $rest )* )? - ); - } -} - -// Takes all arguments from `impl_benchmark_test_suite` and three additional arguments. -// -// Can be configured to generate one #[test] fn per bench case or -// one #[test] fn for all bench cases. -// This depends on whether or not the first argument contains a non-empty list of bench names. -#[macro_export] -#[doc(hidden)] -macro_rules! impl_test_function { - // user might or might not have set some keyword arguments; set the defaults - // - // The weird syntax indicates that `rest` comes only after a comma, which is otherwise optional - ( - ( $( $names:tt )* ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - - $bench_module:ident, - $new_test_ext:expr, - $test:path - $(, $( $rest:tt )* )? - ) => { - $crate::impl_test_function!( - @cases: - ( $( $names )* ) - ( $( $names_extra )* ) - ( $( $names_skip_meta )* ) - @selected: - $bench_module, - $new_test_ext, - $test, - benchmarks_path = super, - extra = true, - exec_name = execute_with, - @user: - $( $( $rest )* )? - ); - }; - // pick off the benchmarks_path keyword argument - ( - @cases: - ( $( $names:tt )* ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - @selected: - $bench_module:ident, - $new_test_ext:expr, - $test:path, - benchmarks_path = $old:ident, - extra = $extra:expr, - exec_name = $exec_name:ident, - @user: - benchmarks_path = $benchmarks_path:ident - $(, $( $rest:tt )* )? - ) => { - $crate::impl_test_function!( - @cases: - ( $( $names )* ) - ( $( $names_extra )* ) - ( $( $names_skip_meta )* ) - @selected: - $bench_module, - $new_test_ext, - $test, - benchmarks_path = $benchmarks_path, - extra = $extra, - exec_name = $exec_name, - @user: - $( $( $rest )* )? - ); - }; - // pick off the extra keyword argument - ( - @cases: - ( $( $names:tt )* ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - @selected: - $bench_module:ident, - $new_test_ext:expr, - $test:path, - benchmarks_path = $benchmarks_path:ident, - extra = $old:expr, - exec_name = $exec_name:ident, - @user: - extra = $extra:expr - $(, $( $rest:tt )* )? - ) => { - $crate::impl_test_function!( - @cases: - ( $( $names )* ) - ( $( $names_extra )* ) - ( $( $names_skip_meta )* ) - @selected: - $bench_module, - $new_test_ext, - $test, - benchmarks_path = $benchmarks_path, - extra = $extra, - exec_name = $exec_name, - @user: - $( $( $rest )* )? - ); - }; - // pick off the exec_name keyword argument - ( - @cases: - ( $( $names:tt )* ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - @selected: - $bench_module:ident, - $new_test_ext:expr, - $test:path, - benchmarks_path = $benchmarks_path:ident, - extra = $extra:expr, - exec_name = $old:ident, - @user: - exec_name = $exec_name:ident - $(, $( $rest:tt )* )? - ) => { - $crate::impl_test_function!( - @cases: - ( $( $names )* ) - ( $( $names_extra )* ) - ( $( $names_skip_meta )* ) - @selected: - $bench_module, - $new_test_ext, - $test, - benchmarks_path = $benchmarks_path, - extra = $extra, - exec_name = $exec_name, - @user: - $( $( $rest )* )? - ); - }; - // iteration-exit arm which generates a #[test] function for each case. - ( - @cases: - ( $( $names:tt )+ ) - ( $( $names_extra:tt )* ) - ( $( $names_skip_meta:tt )* ) - @selected: - $bench_module:ident, - $new_test_ext:expr, - $test:path, - benchmarks_path = $path_to_benchmarks_invocation:ident, - extra = $extra:expr, - exec_name = $exec_name:ident, - @user: - $(,)? - ) => { - $crate::impl_bench_case_tests!( - { $bench_module, $new_test_ext, $exec_name, $test, $extra } - { $( $names_extra:tt )* } - $($names)+ - ); - }; - // iteration-exit arm which generates one #[test] function for all cases. - ( - @cases: - () - () - () - @selected: - $bench_module:ident, - $new_test_ext:expr, - $test:path, - benchmarks_path = $path_to_benchmarks_invocation:ident, - extra = $extra:expr, - exec_name = $exec_name:ident, - @user: - $(,)? - ) => { - #[cfg(test)] - mod benchmark_tests { - use super::$bench_module; - - #[test] - fn test_benchmarks() { - $new_test_ext.$exec_name(|| { - use $crate::Benchmarking; - - let mut anything_failed = false; - println!("failing benchmark tests:"); - for benchmark_metadata in $bench_module::<$test>::benchmarks($extra) { - let benchmark_name = &benchmark_metadata.name; - match std::panic::catch_unwind(|| { - $bench_module::<$test>::test_bench_by_name(benchmark_name) - }) { - Err(err) => { - println!( - "{}: {:?}", - $crate::str::from_utf8(benchmark_name) - .expect("benchmark name is always a valid string!"), - err, - ); - anything_failed = true; - }, - Ok(Err(err)) => { - match err { - $crate::BenchmarkError::Stop(err) => { - println!( - "{}: {:?}", - $crate::str::from_utf8(benchmark_name) - .expect("benchmark name is always a valid string!"), - err, - ); - anything_failed = true; - }, - $crate::BenchmarkError::Override(_) => { - // This is still considered a success condition. - $crate::log::error!( - "WARNING: benchmark error overrided - {}", - $crate::str::from_utf8(benchmark_name) - .expect("benchmark name is always a valid string!"), - ); - }, - $crate::BenchmarkError::Skip => { - // This is considered a success condition. - $crate::log::error!( - "WARNING: benchmark error skipped - {}", - $crate::str::from_utf8(benchmark_name) - .expect("benchmark name is always a valid string!"), - ); - } - $crate::BenchmarkError::Weightless => { - // This is considered a success condition. - $crate::log::error!( - "WARNING: benchmark weightless skipped - {}", - $crate::str::from_utf8(benchmark_name) - .expect("benchmark name is always a valid string!"), - ); - } - } - }, - Ok(Ok(())) => (), - } - } - assert!(!anything_failed); - }); - } - } - }; -} - -/// show error message and debugging info for the case of an error happening -/// during a benchmark -pub fn show_benchmark_debug_info( - instance_string: &[u8], - benchmark: &[u8], - components: &[(BenchmarkParameter, u32)], - verify: &bool, - error_message: &str, -) -> sp_runtime::RuntimeString { - sp_runtime::format_runtime_string!( - "\n* Pallet: {}\n\ - * Benchmark: {}\n\ - * Components: {:?}\n\ - * Verify: {:?}\n\ - * Error message: {}", - sp_std::str::from_utf8(instance_string) - .expect("it's all just strings ran through the wasm interface. qed"), - sp_std::str::from_utf8(benchmark) - .expect("it's all just strings ran through the wasm interface. qed"), - components, - verify, - error_message, - ) -} - -/// This macro adds pallet benchmarks to a `Vec` object. -/// -/// First create an object that holds in the input parameters for the benchmark: +/// Benchmark tests can be generated using the old syntax in `frame_benchmarking`, +/// including the `frame_benchmarking::impl_benchmark_test_suite` macro. /// +/// An example is shown below (taken from the `message-queue` pallet's `benchmarking` module): /// ```ignore -/// let params = (&config, &whitelist); -/// ``` -/// -/// The `whitelist` is a parameter you pass to control the DB read/write tracking. -/// We use a vector of [TrackedStorageKey](./struct.TrackedStorageKey.html), which is a simple -/// struct used to set if a key has been read or written to. -/// -/// For values that should be skipped entirely, we can just pass `key.into()`. For example: -/// -/// ``` -/// use frame_benchmarking::TrackedStorageKey; -/// let whitelist: Vec = vec![ -/// // Block Number -/// array_bytes::hex_into_unchecked("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac"), -/// // Total Issuance -/// array_bytes::hex_into_unchecked("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80"), -/// // Execution Phase -/// array_bytes::hex_into_unchecked("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a"), -/// // Event Count -/// array_bytes::hex_into_unchecked("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850"), -/// ]; +/// #[benchmarks] +/// mod benchmarks { +/// use super::*; +/// // ... +/// impl_benchmark_test_suite!( +/// MessageQueue, +/// crate::mock::new_test_ext::(), +/// crate::integration_test::Test +/// ); +/// } /// ``` /// -/// Then define a mutable local variable to hold your `BenchmarkBatch` object: +/// ## Benchmark Function Generation +/// +/// The benchmark function definition that you provide is used to automatically create a number +/// of impls and structs required by the benchmarking engine. Additionally, a benchmark +/// function is also generated that resembles the function definition you provide, with a few +/// modifications: +/// 1. The function name is transformed from i.e. `original_name` to `_original_name` so as not to +/// collide with the struct `original_name` that is created for some of the benchmarking engine +/// impls. +/// 2. Appropriate `T: Config` and `I` (if this is an instance benchmark) generics are added to the +/// function automatically during expansion, so you should not add these manually on your +/// function definition (but you may make use of `T` and `I` anywhere within your benchmark +/// function, in any of the three sections (setup, call, verification). +/// 3. Arguments such as `u: Linear<10, 100>` are converted to `u: u32` to make the function +/// directly callable. +/// 4. A `verify: bool` param is added as the last argument. Specifying `true` will result in the +/// verification section of your function executing, while a value of `false` will skip +/// verification. +/// 5. If you specify a return type on the function definition, it must conform to the [rules +/// below](#support-for-result-benchmarkerror-and-the--operator), and the last statement of the +/// function definition must resolve to something compatible with `Result<(), BenchmarkError>`. +/// +/// The reason we generate an actual function as part of the expansion is to allow the compiler +/// to enforce several constraints that would otherwise be difficult to enforce and to reduce +/// developer confusion (especially regarding the use of the `?` operator, as covered below). +/// +/// Note that any attributes, comments, and doc comments attached to your benchmark function +/// definition are also carried over onto the resulting benchmark function and the struct for +/// that benchmark. As a result you should be careful about what attributes you attach here as +/// they will be replicated in multiple places. +/// +/// ### Support for `Result<(), BenchmarkError>` and the `?` operator +/// +/// You may optionally specify `Result<(), BenchmarkError>` as the return type of your +/// benchmark function definition. If you do so, you must return a compatible `Result<(), +/// BenchmarkError>` as the *last statement* of your benchmark function definition. You may +/// also use the `?` operator throughout your benchmark function definition if you choose to +/// follow this route. See the example below: /// /// ```ignore -/// let mut batches = Vec::::new(); -/// ```` +/// #![cfg(feature = "runtime-benchmarks")] /// -/// Then add the pallets you want to benchmark to this object, using their crate name and generated -/// module struct: +/// use super::{mock_helpers::*, Pallet as MyPallet}; +/// use frame_benchmarking::v2::*; /// -/// ```ignore -/// add_benchmark!(params, batches, pallet_balances, Balances); -/// add_benchmark!(params, batches, pallet_session, SessionBench::); -/// add_benchmark!(params, batches, frame_system, SystemBench::); -/// ... -/// ``` -/// -/// At the end of `dispatch_benchmark`, you should return this batches object. -/// -/// In the case where you have multiple instances of a pallet that you need to separately benchmark, -/// the name of your module struct will be used as a suffix to your outputted weight file. For -/// example: -/// -/// ```ignore -/// add_benchmark!(params, batches, pallet_balances, Balances); // pallet_balances.rs -/// add_benchmark!(params, batches, pallet_collective, Council); // pallet_collective_council.rs -/// add_benchmark!(params, batches, pallet_collective, TechnicalCommittee); // pallet_collective_technical_committee.rs -/// ``` -/// -/// You can manipulate this suffixed string by using a type alias if needed. For example: +/// #[benchmarks] +/// mod benchmarks { +/// use super::*; /// -/// ```ignore -/// type Council2 = TechnicalCommittee; -/// add_benchmark!(params, batches, pallet_collective, Council2); // pallet_collective_council_2.rs -/// ``` -#[macro_export] -macro_rules! add_benchmark { - ( $params:ident, $batches:ident, $name:path, $( $location:tt )* ) => ( - let name_string = stringify!($name).as_bytes(); - let instance_string = stringify!( $( $location )* ).as_bytes(); - let (config, whitelist) = $params; - let $crate::BenchmarkConfig { - pallet, - benchmark, - selected_components, - verify, - internal_repeats, - } = config; - if &pallet[..] == &name_string[..] { - let benchmark_result = $( $location )*::run_benchmark( - &benchmark[..], - &selected_components[..], - whitelist, - *verify, - *internal_repeats, - ); - - let final_results = match benchmark_result { - Ok(results) => Some(results), - Err($crate::BenchmarkError::Override(mut result)) => { - // Insert override warning as the first storage key. - $crate::log::error!( - "WARNING: benchmark error overrided - {}", - $crate::str::from_utf8(benchmark) - .expect("benchmark name is always a valid string!") - ); - result.keys.insert(0, - (b"Benchmark Override".to_vec(), 0, 0, false) - ); - Some($crate::vec![result]) - }, - Err($crate::BenchmarkError::Stop(e)) => { - $crate::show_benchmark_debug_info( - instance_string, - benchmark, - selected_components, - verify, - e, - ); - return Err(e.into()); - }, - Err($crate::BenchmarkError::Skip) => { - $crate::log::error!( - "WARNING: benchmark error skipped - {}", - $crate::str::from_utf8(benchmark) - .expect("benchmark name is always a valid string!") - ); - None - }, - Err($crate::BenchmarkError::Weightless) => { - $crate::log::error!( - "WARNING: benchmark weightless skipped - {}", - $crate::str::from_utf8(benchmark) - .expect("benchmark name is always a valid string!") - ); - Some(vec![$crate::BenchmarkResult { - components: selected_components.clone(), - .. Default::default() - }]) - } - }; - - if let Some(final_results) = final_results { - $batches.push($crate::BenchmarkBatch { - pallet: name_string.to_vec(), - instance: instance_string.to_vec(), - benchmark: benchmark.clone(), - results: final_results, - }); - } - } - ) -} - -/// Callback for `define_benchmarks` to call `add_benchmark`. -#[macro_export] -macro_rules! cb_add_benchmarks { - // anchor - ( $params:ident, $batches:ident, [ $name:path, $( $location:tt )* ] ) => { - $crate::add_benchmark!( $params, $batches, $name, $( $location )* ); - }; - // recursion tail - ( $params:ident, $batches:ident, [ $name:path, $( $location:tt )* ] $([ $names:path, $( $locations:tt )* ])+ ) => { - $crate::cb_add_benchmarks!( $params, $batches, [ $name, $( $location )* ] ); - $crate::cb_add_benchmarks!( $params, $batches, $([ $names, $( $locations )* ])+ ); - } -} - -/// This macro allows users to easily generate a list of benchmarks for the pallets configured -/// in the runtime. +/// #[benchmark] +/// fn bench_name(x: Linear<5, 25>) -> Result<(), BenchmarkError> { +/// // setup code +/// let z = x + 4; +/// let caller = whitelisted_caller(); /// -/// To use this macro, first create a an object to store the list: +/// // note we can make use of the ? operator here because of the return type +/// something(z)?; /// -/// ```ignore -/// let mut list = Vec::::new(); -/// ``` +/// #[extrinsic_call] +/// extrinsic_name(SystemOrigin::Signed(caller), other, arguments); /// -/// Then pass this `list` to the macro, along with the `extra` boolean, the pallet crate, and -/// pallet struct: +/// // verification code +/// assert_eq!(MyPallet::::my_var(), z); /// -/// ```ignore -/// list_benchmark!(list, extra, pallet_balances, Balances); -/// list_benchmark!(list, extra, pallet_session, SessionBench::); -/// list_benchmark!(list, extra, frame_system, SystemBench::); +/// // we must return a valid `Result<(), BenchmarkError>` as the last line of our benchmark +/// // function definition. This line is not included as part of the verification code that +/// // appears above it. +/// Ok(()) +/// } +/// } /// ``` -/// -/// This should match what exists with the `add_benchmark!` macro. -#[macro_export] -macro_rules! list_benchmark { - ( $list:ident, $extra:ident, $name:path, $( $location:tt )* ) => ( - let pallet_string = stringify!($name).as_bytes(); - let instance_string = stringify!( $( $location )* ).as_bytes(); - let benchmarks = $( $location )*::benchmarks($extra); - let pallet_benchmarks = BenchmarkList { - pallet: pallet_string.to_vec(), - instance: instance_string.to_vec(), - benchmarks: benchmarks.to_vec(), - }; - $list.push(pallet_benchmarks) - ) -} - -/// Callback for `define_benchmarks` to call `list_benchmark`. -#[macro_export] -macro_rules! cb_list_benchmarks { - // anchor - ( $list:ident, $extra:ident, [ $name:path, $( $location:tt )* ] ) => { - $crate::list_benchmark!( $list, $extra, $name, $( $location )* ); - }; - // recursion tail - ( $list:ident, $extra:ident, [ $name:path, $( $location:tt )* ] $([ $names:path, $( $locations:tt )* ])+ ) => { - $crate::cb_list_benchmarks!( $list, $extra, [ $name, $( $location )* ] ); - $crate::cb_list_benchmarks!( $list, $extra, $([ $names, $( $locations )* ])+ ); +pub mod v2 { + pub use super::*; + pub use frame_support_procedural::{ + benchmark, benchmarks, block, extrinsic_call, instance_benchmarks, + }; + + // Used in #[benchmark] implementation to ensure that benchmark function arguments + // implement [`ParamRange`]. + #[doc(hidden)] + pub use static_assertions::{assert_impl_all, assert_type_eq_all}; + + /// Used by the new benchmarking code to specify that a benchmarking variable is linear + /// over some specified range, i.e. `Linear<0, 1_000>` means that the corresponding variable + /// is allowed to range from `0` to `1000`, inclusive. + /// + /// See [`v2`] for more info. + pub struct Linear; + + /// Trait that must be implemented by all structs that can be used as parameter range types + /// in the new benchmarking code (i.e. `Linear<0, 1_000>`). Right now there is just + /// [`Linear`] but this could later be extended to support additional non-linear parameter + /// ranges. + /// + /// See [`v2`] for more info. + pub trait ParamRange { + /// Represents the (inclusive) starting number of this `ParamRange`. + fn start(&self) -> u32; + + /// Represents the (inclusive) ending number of this `ParamRange`. + fn end(&self) -> u32; } -} -/// Defines pallet configs that `add_benchmarks` and `list_benchmarks` use. -/// Should be preferred instead of having a repetitive list of configs -/// in `add_benchmark` and `list_benchmark`. -#[macro_export] -macro_rules! define_benchmarks { - ( $([ $names:path, $( $locations:tt )* ])* ) => { - /// Calls `list_benchmark` with all configs from `define_benchmarks` - /// and passes the first two parameters on. - /// - /// Use as: - /// ```ignore - /// list_benchmarks!(list, extra); - /// ``` - #[macro_export] - macro_rules! list_benchmarks { - ( $list:ident, $extra:ident ) => { - $crate::cb_list_benchmarks!( $list, $extra, $([ $names, $( $locations )* ])+ ); - } + impl ParamRange for Linear { + fn start(&self) -> u32 { + A } - /// Calls `add_benchmark` with all configs from `define_benchmarks` - /// and passes the first two parameters on. - /// - /// Use as: - /// ```ignore - /// add_benchmarks!(params, batches); - /// ``` - #[macro_export] - macro_rules! add_benchmarks { - ( $params:ident, $batches:ident ) => { - $crate::cb_add_benchmarks!( $params, $batches, $([ $names, $( $locations )* ])+ ); - } + fn end(&self) -> u32 { + B } } } diff --git a/frame/benchmarking/src/tests.rs b/frame/benchmarking/src/tests.rs index 1499f9c182fc..4b8339f65913 100644 --- a/frame/benchmarking/src/tests.rs +++ b/frame/benchmarking/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,21 +22,20 @@ use super::*; use frame_support::{parameter_types, traits::ConstU32}; use sp_runtime::{ - testing::{Header, H256}, + testing::H256, traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; use sp_std::prelude::*; use std::cell::RefCell; -#[frame_support::pallet] +#[frame_support::pallet(dev_mode)] mod pallet_test { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(PhantomData); + pub struct Pallet(_); #[pallet::config] pub trait Config: frame_system::Config { @@ -46,44 +45,33 @@ mod pallet_test { } #[pallet::storage] - #[pallet::getter(fn value)] pub(crate) type Value = StorageValue<_, u32, OptionQuery>; #[pallet::call] impl Pallet { - #[pallet::call_index(0)] - #[pallet::weight(0)] pub fn set_value(origin: OriginFor, n: u32) -> DispatchResult { let _sender = ensure_signed(origin)?; Value::::put(n); Ok(()) } - #[pallet::call_index(1)] - #[pallet::weight(0)] pub fn dummy(origin: OriginFor, _n: u32) -> DispatchResult { let _sender = ensure_none(origin)?; Ok(()) } - #[pallet::call_index(2)] - #[pallet::weight(0)] pub fn always_error(_origin: OriginFor) -> DispatchResult { return Err("I always fail".into()) } } } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, TestPallet: pallet_test::{Pallet, Call, Storage}, } ); @@ -94,14 +82,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type Version = (); @@ -126,7 +113,7 @@ impl pallet_test::Config for Test { } fn new_test_ext() -> sp_io::TestExternalities { - GenesisConfig::default().build_storage().unwrap().into() + RuntimeGenesisConfig::default().build_storage().unwrap().into() } thread_local! { diff --git a/frame/benchmarking/src/tests_instance.rs b/frame/benchmarking/src/tests_instance.rs index ecc0a78a199b..822efa70a259 100644 --- a/frame/benchmarking/src/tests_instance.rs +++ b/frame/benchmarking/src/tests_instance.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ use super::*; use frame_support::traits::ConstU32; use sp_runtime::{ - testing::{Header, H256}, + testing::H256, traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; @@ -34,7 +34,6 @@ mod pallet_test { use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(PhantomData<(T, I)>); pub trait OtherConfig { @@ -50,7 +49,6 @@ mod pallet_test { } #[pallet::storage] - #[pallet::getter(fn value)] pub(crate) type Value, I: 'static = ()> = StorageValue<_, u32, OptionQuery>; #[pallet::event] @@ -62,7 +60,7 @@ mod pallet_test { ::OtherEvent: Into<>::RuntimeEvent>, { #[pallet::call_index(0)] - #[pallet::weight(0)] + #[pallet::weight({0})] pub fn set_value(origin: OriginFor, n: u32) -> DispatchResult { let _sender = ensure_signed(origin)?; Value::::put(n); @@ -70,7 +68,7 @@ mod pallet_test { } #[pallet::call_index(1)] - #[pallet::weight(0)] + #[pallet::weight({0})] pub fn dummy(origin: OriginFor, _n: u32) -> DispatchResult { let _sender = ensure_none(origin)?; Ok(()) @@ -78,16 +76,12 @@ mod pallet_test { } } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, TestPallet: pallet_test::{Pallet, Call, Storage, Event}, } ); @@ -98,13 +92,12 @@ impl frame_system::Config for Test { type BlockLength = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type DbWeight = (); @@ -130,7 +123,7 @@ impl pallet_test::OtherConfig for Test { } fn new_test_ext() -> sp_io::TestExternalities { - GenesisConfig::default().build_storage().unwrap().into() + RuntimeGenesisConfig::default().build_storage().unwrap().into() } mod benchmarks { diff --git a/frame/benchmarking/src/utils.rs b/frame/benchmarking/src/utils.rs index 654b1c34c065..59e5192b427b 100644 --- a/frame/benchmarking/src/utils.rs +++ b/frame/benchmarking/src/utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ use frame_support::{ pallet_prelude::*, traits::StorageInfo, }; +use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_io::hashing::blake2_256; @@ -31,7 +32,7 @@ use sp_storage::TrackedStorageKey; /// An alphabet of possible parameters to use for benchmarking. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Encode, Decode, Clone, Copy, PartialEq, Debug)] +#[derive(Encode, Decode, Clone, Copy, PartialEq, Debug, TypeInfo)] #[allow(missing_docs)] #[allow(non_camel_case_types)] pub enum BenchmarkParameter { @@ -72,7 +73,7 @@ impl std::fmt::Display for BenchmarkParameter { /// The results of a single of benchmark. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Encode, Decode, Clone, PartialEq, Debug)] +#[derive(Encode, Decode, Clone, PartialEq, Debug, TypeInfo)] pub struct BenchmarkBatch { /// The pallet containing this benchmark. #[cfg_attr(feature = "std", serde(with = "serde_as_str"))] @@ -111,7 +112,7 @@ pub struct BenchmarkBatchSplitResults { /// Contains duration of the function call in nanoseconds along with the benchmark parameters /// used for that benchmark result. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Encode, Decode, Default, Clone, PartialEq, Debug)] +#[derive(Encode, Decode, Default, Clone, PartialEq, Debug, TypeInfo)] pub struct BenchmarkResult { pub components: Vec<(BenchmarkParameter, u32)>, pub extrinsic_time: u128, @@ -199,7 +200,7 @@ impl From for BenchmarkError { } /// Configuration used to setup and run runtime benchmarks. -#[derive(Encode, Decode, Default, Clone, PartialEq, Debug)] +#[derive(Encode, Decode, Default, Clone, PartialEq, Debug, TypeInfo)] pub struct BenchmarkConfig { /// The encoded name of the pallet to benchmark. pub pallet: Vec, @@ -216,17 +217,18 @@ pub struct BenchmarkConfig { /// A list of benchmarks available for a particular pallet and instance. /// /// All `Vec` must be valid utf8 strings. -#[derive(Encode, Decode, Default, Clone, PartialEq, Debug)] +#[derive(Encode, Decode, Default, Clone, PartialEq, Debug, TypeInfo)] pub struct BenchmarkList { pub pallet: Vec, pub instance: Vec, pub benchmarks: Vec, } -#[derive(Encode, Decode, Default, Clone, PartialEq, Debug)] +#[derive(Encode, Decode, Default, Clone, PartialEq, Debug, TypeInfo)] pub struct BenchmarkMetadata { pub name: Vec, pub components: Vec<(BenchmarkParameter, u32, u32)>, + pub pov_modes: Vec<(Vec, Vec)>, } sp_api::decl_runtime_apis! { diff --git a/frame/benchmarking/src/v1.rs b/frame/benchmarking/src/v1.rs new file mode 100644 index 000000000000..d9f1c7dde005 --- /dev/null +++ b/frame/benchmarking/src/v1.rs @@ -0,0 +1,1990 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Macros for benchmarking a FRAME runtime. + +pub use super::*; + +/// Whitelist the given account. +#[macro_export] +macro_rules! whitelist { + ($acc:ident) => { + frame_benchmarking::benchmarking::add_to_whitelist( + frame_system::Account::::hashed_key_for(&$acc).into(), + ); + }; +} + +/// Construct pallet benchmarks for weighing dispatchables. +/// +/// Works around the idea of complexity parameters, named by a single letter (which is usually +/// upper cased in complexity notation but is lower-cased for use in this macro). +/// +/// Complexity parameters ("parameters") have a range which is a `u32` pair. Every time a benchmark +/// is prepared and run, this parameter takes a concrete value within the range. There is an +/// associated instancing block, which is a single expression that is evaluated during +/// preparation. It may use `?` (`i.e. `return Err(...)`) to bail with a string error. Here's a +/// few examples: +/// +/// ```ignore +/// // These two are equivalent: +/// let x in 0 .. 10; +/// let x in 0 .. 10 => (); +/// // This one calls a setup function and might return an error (which would be terminal). +/// let y in 0 .. 10 => setup(y)?; +/// // This one uses a code block to do lots of stuff: +/// let z in 0 .. 10 => { +/// let a = z * z / 5; +/// let b = do_something(a)?; +/// combine_into(z, b); +/// } +/// ``` +/// +/// Note that due to parsing restrictions, if the `from` expression is not a single token (i.e. a +/// literal or constant), then it must be parenthesized. +/// +/// The macro allows for a number of "arms", each representing an individual benchmark. Using the +/// simple syntax, the associated dispatchable function maps 1:1 with the benchmark and the name of +/// the benchmark is the same as that of the associated function. However, extended syntax allows +/// for arbitrary expressions to be evaluated in a benchmark (including for example, +/// `on_initialize`). +/// +/// Note that the ranges are *inclusive* on both sides. This is in contrast to ranges in Rust which +/// are left-inclusive right-exclusive. +/// +/// Each arm may also have a block of code which is run prior to any instancing and a block of code +/// which is run afterwards. All code blocks may draw upon the specific value of each parameter +/// at any time. Local variables are shared between the two pre- and post- code blocks, but do not +/// leak from the interior of any instancing expressions. +/// +/// Example: +/// ```ignore +/// benchmarks! { +/// where_clause { where T::A: From } // Optional line to give additional bound on `T`. +/// +/// // first dispatchable: foo; this is a user dispatchable and operates on a `u8` vector of +/// // size `l` +/// foo { +/// let caller = account::(b"caller", 0, benchmarks_seed); +/// let l in 1 .. MAX_LENGTH => initialize_l(l); +/// }: _(RuntimeOrigin::Signed(caller), vec![0u8; l]) +/// +/// // second dispatchable: bar; this is a root dispatchable and accepts a `u8` vector of size +/// // `l`. +/// // In this case, we explicitly name the call using `bar` instead of `_`. +/// bar { +/// let l in 1 .. MAX_LENGTH => initialize_l(l); +/// }: bar(RuntimeOrigin::Root, vec![0u8; l]) +/// +/// // third dispatchable: baz; this is a user dispatchable. It isn't dependent on length like the +/// // other two but has its own complexity `c` that needs setting up. It uses `caller` (in the +/// // pre-instancing block) within the code block. This is only allowed in the param instancers +/// // of arms. +/// baz1 { +/// let caller = account::(b"caller", 0, benchmarks_seed); +/// let c = 0 .. 10 => setup_c(&caller, c); +/// }: baz(RuntimeOrigin::Signed(caller)) +/// +/// // this is a second benchmark of the baz dispatchable with a different setup. +/// baz2 { +/// let caller = account::(b"caller", 0, benchmarks_seed); +/// let c = 0 .. 10 => setup_c_in_some_other_way(&caller, c); +/// }: baz(RuntimeOrigin::Signed(caller)) +/// +/// // You may optionally specify the origin type if it can't be determined automatically like +/// // this. +/// baz3 { +/// let caller = account::(b"caller", 0, benchmarks_seed); +/// let l in 1 .. MAX_LENGTH => initialize_l(l); +/// }: baz(RuntimeOrigin::Signed(caller), vec![0u8; l]) +/// +/// // this is benchmarking some code that is not a dispatchable. +/// populate_a_set { +/// let x in 0 .. 10_000; +/// let mut m = Vec::::new(); +/// for i in 0..x { +/// m.insert(i); +/// } +/// }: { m.into_iter().collect::() } +/// } +/// ``` +/// +/// Test functions are automatically generated for each benchmark and are accessible to you when you +/// run `cargo test`. All tests are named `test_benchmark_`, implemented on the +/// Pallet struct, and run them in a test externalities environment. The test function runs your +/// benchmark just like a regular benchmark, but only testing at the lowest and highest values for +/// each component. The function will return `Ok(())` if the benchmarks return no errors. +/// +/// It is also possible to generate one #[test] function per benchmark by calling the +/// `impl_benchmark_test_suite` macro inside the `benchmarks` block. The functions will be named +/// `bench_` and can be run via `cargo test`. +/// You will see one line of output per benchmark. This approach will give you more understandable +/// error messages and allows for parallel benchmark execution. +/// +/// You can optionally add a `verify` code block at the end of a benchmark to test any final state +/// of your benchmark in a unit test. For example: +/// +/// ```ignore +/// sort_vector { +/// let x in 1 .. 10000; +/// let mut m = Vec::::new(); +/// for i in (0..x).rev() { +/// m.push(i); +/// } +/// }: { +/// m.sort(); +/// } verify { +/// ensure!(m[0] == 0, "You forgot to sort!") +/// } +/// ``` +/// +/// These `verify` blocks will not affect your benchmark results! +/// +/// You can construct benchmark by using the `impl_benchmark_test_suite` macro or +/// by manually implementing them like so: +/// +/// ```ignore +/// #[test] +/// fn test_benchmarks() { +/// new_test_ext().execute_with(|| { +/// assert_ok!(Pallet::::test_benchmark_dummy()); +/// assert_err!(Pallet::::test_benchmark_other_name(), "Bad origin"); +/// assert_ok!(Pallet::::test_benchmark_sort_vector()); +/// assert_err!(Pallet::::test_benchmark_broken_benchmark(), "You forgot to sort!"); +/// }); +/// } +/// ``` +#[macro_export] +macro_rules! benchmarks { + ( + $( $rest:tt )* + ) => { + $crate::benchmarks_iter!( + { } + { } + { } + ( ) + ( ) + ( ) + ( ) + $( $rest )* + ); + } +} + +/// Same as [`benchmarks`] but for instantiable module. +/// +/// NOTE: For pallet declared with [`frame_support::pallet`], use [`benchmarks_instance_pallet`]. +#[macro_export] +macro_rules! benchmarks_instance { + ( + $( $rest:tt )* + ) => { + $crate::benchmarks_iter!( + { } + { I: Instance } + { } + ( ) + ( ) + ( ) + ( ) + $( $rest )* + ); + } +} + +/// Same as [`benchmarks`] but for instantiable pallet declared [`frame_support::pallet`]. +/// +/// NOTE: For pallet declared with `decl_module!`, use [`benchmarks_instance`]. +#[macro_export] +macro_rules! benchmarks_instance_pallet { + ( + $( $rest:tt )* + ) => { + $crate::benchmarks_iter!( + { } + { I: 'static } + { } + ( ) + ( ) + ( ) + ( ) + $( $rest )* + ); + } +} + +#[macro_export] +#[doc(hidden)] +macro_rules! benchmarks_iter { + // detect and extract `impl_benchmark_test_suite` call: + // - with a semi-colon + ( + { } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + ( $( $pov_name:ident: $( $storage:path = $pov_mode:ident )*; )* ) + impl_benchmark_test_suite!( + $bench_module:ident, + $new_test_ext:expr, + $test:path + $(, $( $args:tt )* )?); + $( $rest:tt )* + ) => { + $crate::benchmarks_iter! { + { $bench_module, $new_test_ext, $test $(, $( $args )* )? } + { $( $instance: $instance_bound )? } + { $( $where_clause )* } + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + ( $( $pov_name: $( $storage = $pov_mode )*; )* ) + $( $rest )* + } + }; + // - without a semicolon + ( + { } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + ( $( $pov_name:ident: $( $storage:path = $pov_mode:ident )*; )* ) + impl_benchmark_test_suite!( + $bench_module:ident, + $new_test_ext:expr, + $test:path + $(, $( $args:tt )* )?) + $( $rest:tt )* + ) => { + $crate::benchmarks_iter! { + { $bench_module, $new_test_ext, $test $(, $( $args )* )? } + { $( $instance: $instance_bound )? } + { $( $where_clause )* } + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + ( $( $pov_name: $( $storage = $pov_mode )*; )* ) + $( $rest )* + } + }; + // detect and extract where clause: + ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + ( $( $pov_name:ident: $( $storage:path = $pov_mode:ident )*; )* ) + where_clause { where $( $where_bound:tt )* } + $( $rest:tt )* + ) => { + $crate::benchmarks_iter! { + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } + { $( $instance: $instance_bound)? } + { $( $where_bound )* } + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + ( $( $pov_name: $( $storage = $pov_mode )*; )* ) + $( $rest )* + } + }; + // detect and extract `#[skip_meta]` tag: + ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + ( $( $pov_name:ident: $( $storage:path = $pov_mode:ident )*; )* ) + #[skip_meta] + $( #[ $($attributes:tt)+ ] )* + $name:ident + $( $rest:tt )* + ) => { + $crate::benchmarks_iter! { + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } + { $( $instance: $instance_bound )? } + { $( $where_clause )* } + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* $name ) + ( $( $pov_name: $( $storage = $pov_mode )*; )* ) + $( #[ $( $attributes )+ ] )* + $name + $( $rest )* + } + }; + // detect and extract `#[extra]` tag: + ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + ( $( $pov_name:ident: $( $storage:path = $pov_mode:ident )*; )* ) + #[extra] + $( #[ $($attributes:tt)+ ] )* + $name:ident + $( $rest:tt )* + ) => { + $crate::benchmarks_iter! { + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } + { $( $instance: $instance_bound )? } + { $( $where_clause )* } + ( $( $names )* ) + ( $( $names_extra )* $name ) + ( $( $names_skip_meta )* ) + ( $( $pov_name: $( $storage = $pov_mode )*; )* ) + $( #[ $( $attributes )+ ] )* + $name + $( $rest )* + } + }; + // detect and extract `#[pov_mode = Mode { Pallet::Storage: Mode ... }]` tag: + ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + ( $( $old_pov_name:ident: $( $old_storage:path = $old_pov_mode:ident )*; )* ) + #[pov_mode = $mode:ident $( { $( $storage:path: $pov_mode:ident )* } )?] + $( #[ $($attributes:tt)+ ] )* + $name:ident + $( $rest:tt )* + ) => { + $crate::benchmarks_iter! { + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } + { $( $instance: $instance_bound )? } + { $( $where_clause )* } + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + ( $name: ALL = $mode $($( $storage = $pov_mode )*)?; $( $old_pov_name: $( $old_storage = $old_pov_mode )*; )* ) + $( #[ $( $attributes )+ ] )* + $name + $( $rest )* + } + }; + // mutation arm: + ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) // This contains $( $( { $instance } )? $name:ident )* + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + ( $( $pov_name:ident: $( $storage:path = $pov_mode:ident )*; )* ) + $name:ident { $( $code:tt )* }: _ $(< $origin_type:ty>)? ( $origin:expr $( , $arg:expr )* ) + verify $postcode:block + $( $rest:tt )* + ) => { + $crate::benchmarks_iter! { + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } + { $( $instance: $instance_bound )? } + { $( $where_clause )* } + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + ( $( $pov_name: $( $storage = $pov_mode )*; )* ) + $name { $( $code )* }: $name $(< $origin_type >)? ( $origin $( , $arg )* ) + verify $postcode + $( $rest )* + } + }; + // mutation arm: + ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + ( $( $pov_name:ident: $( $storage:path = $pov_mode:ident )*; )* ) + $name:ident { $( $code:tt )* }: $dispatch:ident $(<$origin_type:ty>)? ( $origin:expr $( , $arg:expr )* ) + verify $postcode:block + $( $rest:tt )* + ) => { + $crate::paste::paste! { + $crate::benchmarks_iter! { + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } + { $( $instance: $instance_bound )? } + { $( $where_clause )* } + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + ( $( $pov_name: $( $storage = $pov_mode )*; )* ) + $name { + $( $code )* + let __call = Call::< + T + $( , $instance )? + >:: [< new_call_variant_ $dispatch >] ( + $($arg),* + ); + let __benchmarked_call_encoded = $crate::frame_support::codec::Encode::encode( + &__call + ); + }: { + let __call_decoded = < + Call + as $crate::frame_support::codec::Decode + >::decode(&mut &__benchmarked_call_encoded[..]) + .expect("call is encoded above, encoding must be correct"); + let __origin = $crate::to_origin!($origin $(, $origin_type)?); + as $crate::frame_support::traits::UnfilteredDispatchable + >::dispatch_bypass_filter(__call_decoded, __origin)?; + } + verify $postcode + $( $rest )* + } + } + }; + // iteration arm: + ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + ( $( $pov_name:ident: $( $storage:path = $pov_mode:ident )*; )* ) + $name:ident { $( $code:tt )* }: $eval:block + verify $postcode:block + $( $rest:tt )* + ) => { + $crate::benchmark_backend! { + { $( $instance: $instance_bound )? } + $name + { $( $where_clause )* } + { } + { $eval } + { $( $code )* } + $postcode + } + + #[cfg(test)] + $crate::impl_benchmark_test!( + { $( $where_clause )* } + { $( $instance: $instance_bound )? } + $name + ); + + $crate::benchmarks_iter!( + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } + { $( $instance: $instance_bound )? } + { $( $where_clause )* } + ( $( $names )* { $( $instance )? } $name ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + ( $( $pov_name: $( $storage = $pov_mode )*; )* ) + $( $rest )* + ); + }; + // iteration-exit arm which generates a #[test] function for each case. + ( + { $bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )? } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + ( $( $pov_name:ident: $( $storage:path = $pov_mode:ident )*; )* ) + ) => { + $crate::selected_benchmark!( + { $( $where_clause)* } + { $( $instance: $instance_bound )? } + $( $names )* + ); + $crate::impl_benchmark!( + { $( $where_clause )* } + { $( $instance: $instance_bound )? } + ( $( $names )* ) + ( $( $names_extra ),* ) + ( $( $names_skip_meta ),* ) + ( $( $pov_name: $( $storage = $pov_mode )*; )* ) + ); + $crate::impl_test_function!( + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + $bench_module, + $new_test_ext, + $test + $(, $( $args )* )? + ); + }; + // iteration-exit arm which doesn't generate a #[test] function for all cases. + ( + { } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + ( $( $pov_name:ident: $( $storage:path = $pov_mode:ident )*; )* ) + ) => { + $crate::selected_benchmark!( + { $( $where_clause)* } + { $( $instance: $instance_bound )? } + $( $names )* + ); + $crate::impl_benchmark!( + { $( $where_clause )* } + { $( $instance: $instance_bound )? } + ( $( $names )* ) + ( $( $names_extra ),* ) + ( $( $names_skip_meta ),* ) + ( $( $pov_name: $( $storage = $pov_mode )*; )* ) + ); + }; + // add verify block to _() format + ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + ( $( $pov_name:ident: $( $storage:path = $pov_mode:ident )*; )* ) + $name:ident { $( $code:tt )* }: _ $(<$origin_type:ty>)? ( $origin:expr $( , $arg:expr )* ) + $( $rest:tt )* + ) => { + $crate::benchmarks_iter! { + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } + { $( $instance: $instance_bound )? } + { $( $where_clause )* } + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + ( $( $pov_name: $( $storage = $pov_mode )*; )* ) + $name { $( $code )* }: _ $(<$origin_type>)? ( $origin $( , $arg )* ) + verify { } + $( $rest )* + } + }; + // add verify block to name() format + ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + ( $( $pov_name:ident: $( $storage:path = $pov_mode:ident )*; )* ) + $name:ident { $( $code:tt )* }: $dispatch:ident $(<$origin_type:ty>)? ( $origin:expr $( , $arg:expr )* ) + $( $rest:tt )* + ) => { + $crate::benchmarks_iter! { + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } + { $( $instance: $instance_bound )? } + { $( $where_clause )* } + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + ( $( $pov_name: $( $storage = $pov_mode )*; )* ) + $name { $( $code )* }: $dispatch $(<$origin_type>)? ( $origin $( , $arg )* ) + verify { } + $( $rest )* + } + }; + // add verify block to {} format + ( + { $($bench_module:ident, $new_test_ext:expr, $test:path $(, $( $args:tt )* )?)? } + { $( $instance:ident: $instance_bound:tt )? } + { $( $where_clause:tt )* } + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + ( $( $pov_name:ident: $( $storage:path = $pov_mode:ident )*; )* ) + $name:ident { $( $code:tt )* }: $(<$origin_type:ty>)? $eval:block + $( $rest:tt )* + ) => { + $crate::benchmarks_iter!( + { $($bench_module, $new_test_ext, $test $(, $( $args )* )?)? } + { $( $instance: $instance_bound )? } + { $( $where_clause )* } + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + ( $( $pov_name: $( $storage = $pov_mode )*; )* ) + $name { $( $code )* }: $(<$origin_type>)? $eval + verify { } + $( $rest )* + ); + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! to_origin { + ($origin:expr) => { + $origin.into() + }; + ($origin:expr, $origin_type:ty) => { + <::RuntimeOrigin as From<$origin_type>>::from($origin) + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! benchmark_backend { + // parsing arms + ( + { $( $instance:ident: $instance_bound:tt )? } + $name:ident + { $( $where_clause:tt )* } + { $( PRE { $( $pre_parsed:tt )* } )* } + { $eval:block } + { + let $pre_id:tt $( : $pre_ty:ty )? = $pre_ex:expr; + $( $rest:tt )* + } + $postcode:block + ) => { + $crate::benchmark_backend! { + { $( $instance: $instance_bound )? } + $name + { $( $where_clause )* } + { + $( PRE { $( $pre_parsed )* } )* + PRE { $pre_id , $( $pre_ty , )? $pre_ex } + } + { $eval } + { $( $rest )* } + $postcode + } + }; + ( + { $( $instance:ident: $instance_bound:tt )? } + $name:ident + { $( $where_clause:tt )* } + { $( $parsed:tt )* } + { $eval:block } + { + let $param:ident in ( $param_from:expr ) .. $param_to:expr => $param_instancer:expr; + $( $rest:tt )* + } + $postcode:block + ) => { + $crate::benchmark_backend! { + { $( $instance: $instance_bound )? } + $name + { $( $where_clause )* } + { + $( $parsed )* + PARAM { $param , $param_from , $param_to , $param_instancer } + } + { $eval } + { $( $rest )* } + $postcode + } + }; + // mutation arm to look after a single tt for param_from. + ( + { $( $instance:ident: $instance_bound:tt )? } + $name:ident + { $( $where_clause:tt )* } + { $( $parsed:tt )* } + { $eval:block } + { + let $param:ident in $param_from:tt .. $param_to:expr => $param_instancer:expr ; + $( $rest:tt )* + } + $postcode:block + ) => { + $crate::benchmark_backend! { + { $( $instance: $instance_bound )? } + $name + { $( $where_clause )* } + { $( $parsed )* } + { $eval } + { + let $param in ( $param_from ) .. $param_to => $param_instancer; + $( $rest )* + } + $postcode + } + }; + // mutation arm to look after the default tail of `=> ()` + ( + { $( $instance:ident: $instance_bound:tt )? } + $name:ident + { $( $where_clause:tt )* } + { $( $parsed:tt )* } + { $eval:block } + { + let $param:ident in $param_from:tt .. $param_to:expr; + $( $rest:tt )* + } + $postcode:block + ) => { + $crate::benchmark_backend! { + { $( $instance: $instance_bound )? } + $name + { $( $where_clause )* } + { $( $parsed )* } + { $eval } + { + let $param in $param_from .. $param_to => (); + $( $rest )* + } + $postcode + } + }; + // actioning arm + ( + { $( $instance:ident: $instance_bound:tt )? } + $name:ident + { $( $where_clause:tt )* } + { + $( PRE { $pre_id:tt , $( $pre_ty:ty , )? $pre_ex:expr } )* + $( PARAM { $param:ident , $param_from:expr , $param_to:expr , $param_instancer:expr } )* + } + { $eval:block } + { $( $post:tt )* } + $postcode:block + ) => { + #[allow(non_camel_case_types)] + struct $name; + #[allow(unused_variables)] + impl, $instance: $instance_bound )? > + $crate::BenchmarkingSetup for $name + where $( $where_clause )* + { + fn components(&self) -> $crate::Vec<($crate::BenchmarkParameter, u32, u32)> { + $crate::vec! [ + $( + ($crate::BenchmarkParameter::$param, $param_from, $param_to) + ),* + ] + } + + fn instance( + &self, + components: &[($crate::BenchmarkParameter, u32)], + verify: bool + ) -> Result<$crate::Box Result<(), $crate::BenchmarkError>>, $crate::BenchmarkError> { + $( + // Prepare instance + let $param = components.iter() + .find(|&c| c.0 == $crate::BenchmarkParameter::$param) + .ok_or("Could not find component in benchmark preparation.")? + .1; + )* + $( + let $pre_id $( : $pre_ty )? = $pre_ex; + )* + $( $param_instancer ; )* + $( $post )* + + Ok($crate::Box::new(move || -> Result<(), $crate::BenchmarkError> { + $eval; + if verify { + $postcode; + } + Ok(()) + })) + } + } + }; +} + +// Creates #[test] functions for the given bench cases. +#[macro_export] +#[doc(hidden)] +macro_rules! impl_bench_case_tests { + ( + { $module:ident, $new_test_exec:expr, $exec_name:ident, $test:path, $extra:expr } + { $( $names_extra:tt )* } + $( { $( $bench_inst:ident )? } $bench:ident )* + ) + => { + $crate::impl_bench_name_tests!( + $module, $new_test_exec, $exec_name, $test, $extra, + { $( $names_extra )* }, + $( { $bench } )+ + ); + } +} + +// Creates a #[test] function for the given bench name. +#[macro_export] +#[doc(hidden)] +macro_rules! impl_bench_name_tests { + // recursion anchor + ( + $module:ident, $new_test_exec:expr, $exec_name:ident, $test:path, $extra:expr, + { $( $names_extra:tt )* }, + { $name:ident } + ) => { + $crate::paste::paste! { + #[test] + fn [] () { + $new_test_exec.$exec_name(|| { + // Skip all #[extra] benchmarks if $extra is false. + if !($extra) { + let disabled = $crate::vec![ $( stringify!($names_extra).as_ref() ),* ]; + if disabled.contains(&stringify!($name)) { + $crate::log::error!( + "INFO: extra benchmark skipped - {}", + stringify!($name), + ); + return (); + } + } + + // Same per-case logic as when all cases are run in the + // same function. + match std::panic::catch_unwind(|| { + $module::<$test>::[< test_benchmark_ $name >] () + }) { + Err(err) => { + panic!("{}: {:?}", stringify!($name), err); + }, + Ok(Err(err)) => { + match err { + $crate::BenchmarkError::Stop(err) => { + panic!("{}: {:?}", stringify!($name), err); + }, + $crate::BenchmarkError::Override(_) => { + // This is still considered a success condition. + $crate::log::error!( + "WARNING: benchmark error overrided - {}", + stringify!($name), + ); + }, + $crate::BenchmarkError::Skip => { + // This is considered a success condition. + $crate::log::error!( + "WARNING: benchmark error skipped - {}", + stringify!($name), + ); + }, + $crate::BenchmarkError::Weightless => { + // This is considered a success condition. + $crate::log::error!( + "WARNING: benchmark weightless skipped - {}", + stringify!($name), + ); + } + } + }, + Ok(Ok(())) => (), + } + }); + } + } + }; + // recursion tail + ( + $module:ident, $new_test_exec:expr, $exec_name:ident, $test:path, $extra:expr, + { $( $names_extra:tt )* }, + { $name:ident } $( { $rest:ident } )+ + ) => { + // car + $crate::impl_bench_name_tests!($module, $new_test_exec, $exec_name, $test, $extra, + { $( $names_extra )* }, { $name }); + // cdr + $crate::impl_bench_name_tests!($module, $new_test_exec, $exec_name, $test, $extra, + { $( $names_extra )* }, $( { $rest } )+); + }; +} + +// Creates a `SelectedBenchmark` enum implementing `BenchmarkingSetup`. +// +// Every variant must implement [`BenchmarkingSetup`]. +// +// ```nocompile +// +// struct Transfer; +// impl BenchmarkingSetup for Transfer { ... } +// +// struct SetBalance; +// impl BenchmarkingSetup for SetBalance { ... } +// +// selected_benchmark!({} Transfer {} SetBalance); +// ``` +#[macro_export] +#[doc(hidden)] +macro_rules! selected_benchmark { + ( + { $( $where_clause:tt )* } + { $( $instance:ident: $instance_bound:tt )? } + $( { $( $bench_inst:ident )? } $bench:ident )* + ) => { + // The list of available benchmarks for this pallet. + #[allow(non_camel_case_types)] + enum SelectedBenchmark { + $( $bench, )* + } + + // Allow us to select a benchmark from the list of available benchmarks. + impl, $instance: $instance_bound )? > + $crate::BenchmarkingSetup for SelectedBenchmark + where $( $where_clause )* + { + fn components(&self) -> $crate::Vec<($crate::BenchmarkParameter, u32, u32)> { + match self { + $( + Self::$bench => < + $bench as $crate::BenchmarkingSetup + >::components(&$bench), + )* + } + } + + fn instance( + &self, + components: &[($crate::BenchmarkParameter, u32)], + verify: bool + ) -> Result<$crate::Box Result<(), $crate::BenchmarkError>>, $crate::BenchmarkError> { + match self { + $( + Self::$bench => < + $bench as $crate::BenchmarkingSetup + >::instance(&$bench, components, verify), + )* + } + } + } + }; +} + +#[macro_export] +#[doc(hidden)] +macro_rules! impl_benchmark { + ( + { $( $where_clause:tt )* } + { $( $instance:ident: $instance_bound:tt )? } + ( $( { $( $name_inst:ident )? } $name:ident )* ) + ( $( $name_extra:ident ),* ) + ( $( $name_skip_meta:ident ),* ) + ( $( $pov_name:ident: $( $storage:path = $pov_mode:ident )*; )* ) + ) => { + // We only need to implement benchmarks for the runtime-benchmarks feature or testing. + #[cfg(any(feature = "runtime-benchmarks", test))] + impl, $instance: $instance_bound )? > + $crate::Benchmarking for Pallet + where T: frame_system::Config, $( $where_clause )* + { + fn benchmarks(extra: bool) -> $crate::Vec<$crate::BenchmarkMetadata> { + $($crate::validate_pov_mode!( + $pov_name: $( $storage = $pov_mode )*; + );)* + let mut all_names = $crate::vec![ $( stringify!($name).as_ref() ),* ]; + if !extra { + let extra = [ $( stringify!($name_extra).as_ref() ),* ]; + all_names.retain(|x| !extra.contains(x)); + } + let pov_modes: $crate::Vec<($crate::Vec, $crate::Vec<($crate::Vec, $crate::Vec)>)> = $crate::vec![ + $( + (stringify!($pov_name).as_bytes().to_vec(), + $crate::vec![ + $( ( stringify!($storage).as_bytes().to_vec(), + stringify!($pov_mode).as_bytes().to_vec() ), )* + ]), + )* + ]; + all_names.into_iter().map(|benchmark| { + let selected_benchmark = match benchmark { + $( stringify!($name) => SelectedBenchmark::$name, )* + _ => panic!("all benchmarks should be selectable"), + }; + let name = benchmark.as_bytes().to_vec(); + let components = < + SelectedBenchmark as $crate::BenchmarkingSetup + >::components(&selected_benchmark); + + $crate::BenchmarkMetadata { + name: name.clone(), + components, + pov_modes: pov_modes.iter().find(|p| p.0 == name).map(|p| p.1.clone()).unwrap_or_default(), + } + }).collect::<$crate::Vec<_>>() + } + + fn run_benchmark( + extrinsic: &[u8], + c: &[($crate::BenchmarkParameter, u32)], + whitelist: &[$crate::TrackedStorageKey], + verify: bool, + internal_repeats: u32, + ) -> Result<$crate::Vec<$crate::BenchmarkResult>, $crate::BenchmarkError> { + // Map the input to the selected benchmark. + let extrinsic = $crate::str::from_utf8(extrinsic) + .map_err(|_| "`extrinsic` is not a valid utf8 string!")?; + let selected_benchmark = match extrinsic { + $( stringify!($name) => SelectedBenchmark::$name, )* + _ => return Err("Could not find extrinsic.".into()), + }; + + // Add whitelist to DB including whitelisted caller + let mut whitelist = whitelist.to_vec(); + let whitelisted_caller_key = + as $crate::frame_support::storage::StorageMap<_,_>>::hashed_key_for( + $crate::whitelisted_caller::() + ); + whitelist.push(whitelisted_caller_key.into()); + // Whitelist the transactional layer. + let transactional_layer_key = $crate::TrackedStorageKey::new( + $crate::frame_support::storage::transactional::TRANSACTION_LEVEL_KEY.into() + ); + whitelist.push(transactional_layer_key); + // Whitelist the `:extrinsic_index`. + let extrinsic_index = $crate::TrackedStorageKey::new( + $crate::well_known_keys::EXTRINSIC_INDEX.into() + ); + whitelist.push(extrinsic_index); + // Whitelist the `:intrablock_entropy`. + let intrablock_entropy = $crate::TrackedStorageKey::new( + $crate::well_known_keys::INTRABLOCK_ENTROPY.into() + ); + whitelist.push(intrablock_entropy); + + $crate::benchmarking::set_whitelist(whitelist.clone()); + + let mut results: $crate::Vec<$crate::BenchmarkResult> = $crate::Vec::new(); + + // Always do at least one internal repeat... + for _ in 0 .. internal_repeats.max(1) { + // Always reset the state after the benchmark. + $crate::defer!($crate::benchmarking::wipe_db()); + + // Set up the externalities environment for the setup we want to + // benchmark. + let closure_to_benchmark = < + SelectedBenchmark as $crate::BenchmarkingSetup + >::instance(&selected_benchmark, c, verify)?; + + // Set the block number to at least 1 so events are deposited. + if $crate::Zero::is_zero(&frame_system::Pallet::::block_number()) { + frame_system::Pallet::::set_block_number(1u32.into()); + } + + // Commit the externalities to the database, flushing the DB cache. + // This will enable worst case scenario for reading from the database. + $crate::benchmarking::commit_db(); + + // Access all whitelisted keys to get them into the proof recorder since the + // recorder does now have a whitelist. + for key in &whitelist { + $crate::frame_support::storage::unhashed::get_raw(&key.key); + } + + // Reset the read/write counter so we don't count operations in the setup process. + $crate::benchmarking::reset_read_write_count(); + + // Time the extrinsic logic. + $crate::log::trace!( + target: "benchmark", + "Start Benchmark: {} ({:?}) verify {}", + extrinsic, + c, + verify + ); + + let start_pov = $crate::benchmarking::proof_size(); + let start_extrinsic = $crate::benchmarking::current_time(); + + closure_to_benchmark()?; + + let finish_extrinsic = $crate::benchmarking::current_time(); + let end_pov = $crate::benchmarking::proof_size(); + + // Calculate the diff caused by the benchmark. + let elapsed_extrinsic = finish_extrinsic.saturating_sub(start_extrinsic); + let diff_pov = match (start_pov, end_pov) { + (Some(start), Some(end)) => end.saturating_sub(start), + _ => Default::default(), + }; + + // Commit the changes to get proper write count + $crate::benchmarking::commit_db(); + $crate::log::trace!( + target: "benchmark", + "End Benchmark: {} ns", elapsed_extrinsic + ); + let read_write_count = $crate::benchmarking::read_write_count(); + $crate::log::trace!( + target: "benchmark", + "Read/Write Count {:?}", read_write_count + ); + $crate::log::trace!( + target: "benchmark", + "Proof sizes: before {:?} after {:?} diff {}", &start_pov, &end_pov, &diff_pov + ); + + // Time the storage root recalculation. + let start_storage_root = $crate::benchmarking::current_time(); + $crate::storage_root($crate::StateVersion::V1); + let finish_storage_root = $crate::benchmarking::current_time(); + let elapsed_storage_root = finish_storage_root - start_storage_root; + + let skip_meta = [ $( stringify!($name_skip_meta).as_ref() ),* ]; + let read_and_written_keys = if skip_meta.contains(&extrinsic) { + $crate::vec![(b"Skipped Metadata".to_vec(), 0, 0, false)] + } else { + $crate::benchmarking::get_read_and_written_keys() + }; + + results.push($crate::BenchmarkResult { + components: c.to_vec(), + extrinsic_time: elapsed_extrinsic, + storage_root_time: elapsed_storage_root, + reads: read_write_count.0, + repeat_reads: read_write_count.1, + writes: read_write_count.2, + repeat_writes: read_write_count.3, + proof_size: diff_pov, + keys: read_and_written_keys, + }); + } + + return Ok(results); + } + } + + #[cfg(test)] + impl, $instance: $instance_bound )? > + Pallet + where T: frame_system::Config, $( $where_clause )* + { + /// Test a particular benchmark by name. + /// + /// This isn't called `test_benchmark_by_name` just in case some end-user eventually + /// writes a benchmark, itself called `by_name`; the function would be shadowed in + /// that case. + /// + /// This is generally intended to be used by child test modules such as those created + /// by the `impl_benchmark_test_suite` macro. However, it is not an error if a pallet + /// author chooses not to implement benchmarks. + #[allow(unused)] + fn test_bench_by_name(name: &[u8]) -> Result<(), $crate::BenchmarkError> { + let name = $crate::str::from_utf8(name) + .map_err(|_| -> $crate::BenchmarkError { "`name` is not a valid utf8 string!".into() })?; + match name { + $( stringify!($name) => { + $crate::paste::paste! { Self::[< test_benchmark_ $name >]() } + } )* + _ => Err("Could not find test for requested benchmark.".into()), + } + } + } + }; +} + +// This creates a unit test for one benchmark of the main benchmark macro. +// It runs the benchmark using the `high` and `low` value for each component +// and ensure that everything completes successfully. +// Instances each component with six values which can be controlled with the +// env variable `VALUES_PER_COMPONENT`. +#[macro_export] +#[doc(hidden)] +macro_rules! impl_benchmark_test { + ( + { $( $where_clause:tt )* } + { $( $instance:ident: $instance_bound:tt )? } + $name:ident + ) => { + $crate::paste::item! { + #[cfg(test)] + impl, $instance: $instance_bound )? > + Pallet + where T: frame_system::Config, $( $where_clause )* + { + #[allow(unused)] + fn [] () -> Result<(), $crate::BenchmarkError> { + let selected_benchmark = SelectedBenchmark::$name; + let components = < + SelectedBenchmark as $crate::BenchmarkingSetup + >::components(&selected_benchmark); + + let execute_benchmark = | + c: $crate::Vec<($crate::BenchmarkParameter, u32)> + | -> Result<(), $crate::BenchmarkError> { + // Always reset the state after the benchmark. + $crate::defer!($crate::benchmarking::wipe_db()); + + // Set up the benchmark, return execution + verification function. + let closure_to_verify = < + SelectedBenchmark as $crate::BenchmarkingSetup + >::instance(&selected_benchmark, &c, true)?; + + // Set the block number to at least 1 so events are deposited. + if $crate::Zero::is_zero(&frame_system::Pallet::::block_number()) { + frame_system::Pallet::::set_block_number(1u32.into()); + } + + // Run execution + verification + closure_to_verify() + }; + + if components.is_empty() { + execute_benchmark(Default::default())?; + } else { + let num_values: u32 = if let Ok(ev) = std::env::var("VALUES_PER_COMPONENT") { + ev.parse().map_err(|_| { + $crate::BenchmarkError::Stop( + "Could not parse env var `VALUES_PER_COMPONENT` as u32." + ) + })? + } else { + 6 + }; + + if num_values < 2 { + return Err("`VALUES_PER_COMPONENT` must be at least 2".into()); + } + + for (name, low, high) in components.clone().into_iter() { + // Test the lowest, highest (if its different from the lowest) + // and up to num_values-2 more equidistant values in between. + // For 0..10 and num_values=6 this would mean: [0, 2, 4, 6, 8, 10] + + let mut values = $crate::vec![low]; + let diff = (high - low).min(num_values - 1); + let slope = (high - low) as f32 / diff as f32; + + for i in 1..=diff { + let value = ((low as f32 + slope * i as f32) as u32) + .clamp(low, high); + values.push(value); + } + + for component_value in values { + // Select the max value for all the other components. + let c: $crate::Vec<($crate::BenchmarkParameter, u32)> = components + .iter() + .map(|(n, _, h)| + if *n == name { + (*n, component_value) + } else { + (*n, *h) + } + ) + .collect(); + + execute_benchmark(c)?; + } + } + } + Ok(()) + } + } + } + }; +} + +/// This creates a test suite which runs the module's benchmarks. +/// +/// When called in `pallet_example_basic` as +/// +/// ```rust,ignore +/// impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); +/// ``` +/// +/// It expands to the equivalent of: +/// +/// ```rust,ignore +/// #[cfg(test)] +/// mod tests { +/// use super::*; +/// use crate::tests::{new_test_ext, Test}; +/// use frame_support::assert_ok; +/// +/// #[test] +/// fn test_benchmarks() { +/// new_test_ext().execute_with(|| { +/// assert_ok!(test_benchmark_accumulate_dummy::()); +/// assert_ok!(test_benchmark_set_dummy::()); +/// assert_ok!(test_benchmark_sort_vector::()); +/// }); +/// } +/// } +/// ``` +/// +/// When called inside the `benchmarks` macro of the `pallet_example_basic` as +/// +/// ```rust,ignore +/// benchmarks! { +/// // Benchmarks omitted for brevity +/// +/// impl_benchmark_test_suite!(Pallet, crate::tests::new_test_ext(), crate::tests::Test); +/// } +/// ``` +/// +/// It expands to the equivalent of: +/// +/// ```rust,ignore +/// #[cfg(test)] +/// mod benchmarking { +/// use super::*; +/// use crate::tests::{new_test_ext, Test}; +/// use frame_support::assert_ok; +/// +/// #[test] +/// fn bench_accumulate_dummy() { +/// new_test_ext().execute_with(|| { +/// assert_ok!(test_benchmark_accumulate_dummy::()); +/// }) +/// } +/// +/// #[test] +/// fn bench_set_dummy() { +/// new_test_ext().execute_with(|| { +/// assert_ok!(test_benchmark_set_dummy::()); +/// }) +/// } +/// +/// #[test] +/// fn bench_sort_vector() { +/// new_test_ext().execute_with(|| { +/// assert_ok!(test_benchmark_sort_vector::()); +/// }) +/// } +/// } +/// ``` +/// +/// ## Arguments +/// +/// The first argument, `module`, must be the path to this crate's module. +/// +/// The second argument, `new_test_ext`, must be a function call which returns either a +/// `sp_io::TestExternalities`, or some other type with a similar interface. +/// +/// Note that this function call is _not_ evaluated at compile time, but is instead copied textually +/// into each appropriate invocation site. +/// +/// The third argument, `test`, must be the path to the runtime. The item to which this must refer +/// will generally take the form: +/// +/// ```rust,ignore +/// frame_support::construct_runtime!( +/// pub enum Test where ... +/// { ... } +/// ); +/// ``` +/// +/// There is an optional fourth argument, with keyword syntax: `benchmarks_path = +/// path_to_benchmarks_invocation`. In the typical case in which this macro is in the same module as +/// the `benchmarks!` invocation, you don't need to supply this. However, if the +/// `impl_benchmark_test_suite!` invocation is in a different module than the `benchmarks!` +/// invocation, then you should provide the path to the module containing the `benchmarks!` +/// invocation: +/// +/// ```rust,ignore +/// mod benches { +/// benchmarks!{ +/// ... +/// } +/// } +/// +/// mod tests { +/// // because of macro syntax limitations, neither Pallet nor benches can be paths, but both have +/// // to be idents in the scope of `impl_benchmark_test_suite`. +/// use crate::{benches, Pallet}; +/// +/// impl_benchmark_test_suite!(Pallet, new_test_ext(), Test, benchmarks_path = benches); +/// +/// // new_test_ext and the Test item are defined later in this module +/// } +/// ``` +/// +/// There is an optional fifth argument, with keyword syntax: `extra = true` or `extra = false`. +/// By default, this generates a test suite which iterates over all benchmarks, including those +/// marked with the `#[extra]` annotation. Setting `extra = false` excludes those. +/// +/// There is an optional sixth argument, with keyword syntax: `exec_name = custom_exec_name`. +/// By default, this macro uses `execute_with` for this parameter. This argument, if set, is subject +/// to these restrictions: +/// +/// - It must be the name of a method applied to the output of the `new_test_ext` argument. +/// - That method must have a signature capable of receiving a single argument of the form `impl +/// FnOnce()`. +// ## Notes (not for rustdoc) +// +// The biggest challenge for this macro is communicating the actual test functions to be run. We +// can't just build an array of function pointers to each test function and iterate over it, because +// the test functions are parameterized by the `Test` type. That's incompatible with +// monomorphization: if it were legal, then even if the compiler detected and monomorphized the +// functions into only the types of the callers, which implementation would the function pointer +// point to? There would need to be some kind of syntax for selecting the destination of the pointer +// according to a generic argument, and in general it would be a huge mess and not worth it. +// +// Instead, we're going to steal a trick from `fn run_benchmark`: generate a function which is +// itself parametrized by `Test`, which accepts a `&[u8]` parameter containing the name of the +// benchmark, and dispatches based on that to the appropriate real test implementation. Then, we can +// just iterate over the `Benchmarking::benchmarks` list to run the actual implementations. +#[macro_export] +macro_rules! impl_benchmark_test_suite { + ( + $bench_module:ident, + $new_test_ext:expr, + $test:path + $(, $( $rest:tt )* )? + ) => { + $crate::impl_test_function!( + () + () + () + $bench_module, + $new_test_ext, + $test + $(, $( $rest )* )? + ); + } +} + +/// Validates the passed `pov_mode`s. +/// +/// Checks that: +/// - a top-level `ignored` is exclusive +/// - all modes are valid +#[macro_export] +macro_rules! validate_pov_mode { + () => {}; + ( $_bench:ident: ; ) => { }; + ( $_bench:ident: $_car:path = Ignored ; ) => { }; + ( $bench:ident: $_car:path = Ignored $( $storage:path = $_pov_mode:ident )+; ) => { + compile_error!( + concat!(concat!("`pov_mode = Ignored` is exclusive. Please remove the attribute from keys: ", $( stringify!($storage) )+), " on benchmark '", stringify!($bench), "'")); + }; + ( $bench:ident: $car:path = Measured $( $storage:path = $pov_mode:ident )*; ) => { + $crate::validate_pov_mode!( + $bench: $( $storage = $pov_mode )*; + ); + }; + ( $bench:ident: $car:path = MaxEncodedLen $( $storage:path = $pov_mode:ident )*; ) => { + $crate::validate_pov_mode!( + $bench: $( $storage = $pov_mode )*; + ); + }; + ( $bench:ident: $key:path = $unknown:ident $( $_storage:path = $_pov_mode:ident )*; ) => { + compile_error!( + concat!("Unknown pov_mode '", stringify!($unknown) ,"' for benchmark '", stringify!($bench), "' on key '", stringify!($key), "'. Must be one of: Ignored, Measured, MaxEncodedLen") + ); + }; +} + +// Takes all arguments from `impl_benchmark_test_suite` and three additional arguments. +// +// Can be configured to generate one #[test] fn per bench case or +// one #[test] fn for all bench cases. +// This depends on whether or not the first argument contains a non-empty list of bench names. +#[macro_export] +#[doc(hidden)] +macro_rules! impl_test_function { + // user might or might not have set some keyword arguments; set the defaults + // + // The weird syntax indicates that `rest` comes only after a comma, which is otherwise optional + ( + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + + $bench_module:ident, + $new_test_ext:expr, + $test:path + $(, $( $rest:tt )* )? + ) => { + $crate::impl_test_function!( + @cases: + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + @selected: + $bench_module, + $new_test_ext, + $test, + benchmarks_path = super, + extra = true, + exec_name = execute_with, + @user: + $( $( $rest )* )? + ); + }; + // pick off the benchmarks_path keyword argument + ( + @cases: + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + @selected: + $bench_module:ident, + $new_test_ext:expr, + $test:path, + benchmarks_path = $old:ident, + extra = $extra:expr, + exec_name = $exec_name:ident, + @user: + benchmarks_path = $benchmarks_path:ident + $(, $( $rest:tt )* )? + ) => { + $crate::impl_test_function!( + @cases: + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + @selected: + $bench_module, + $new_test_ext, + $test, + benchmarks_path = $benchmarks_path, + extra = $extra, + exec_name = $exec_name, + @user: + $( $( $rest )* )? + ); + }; + // pick off the extra keyword argument + ( + @cases: + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + @selected: + $bench_module:ident, + $new_test_ext:expr, + $test:path, + benchmarks_path = $benchmarks_path:ident, + extra = $old:expr, + exec_name = $exec_name:ident, + @user: + extra = $extra:expr + $(, $( $rest:tt )* )? + ) => { + $crate::impl_test_function!( + @cases: + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + @selected: + $bench_module, + $new_test_ext, + $test, + benchmarks_path = $benchmarks_path, + extra = $extra, + exec_name = $exec_name, + @user: + $( $( $rest )* )? + ); + }; + // pick off the exec_name keyword argument + ( + @cases: + ( $( $names:tt )* ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + @selected: + $bench_module:ident, + $new_test_ext:expr, + $test:path, + benchmarks_path = $benchmarks_path:ident, + extra = $extra:expr, + exec_name = $old:ident, + @user: + exec_name = $exec_name:ident + $(, $( $rest:tt )* )? + ) => { + $crate::impl_test_function!( + @cases: + ( $( $names )* ) + ( $( $names_extra )* ) + ( $( $names_skip_meta )* ) + @selected: + $bench_module, + $new_test_ext, + $test, + benchmarks_path = $benchmarks_path, + extra = $extra, + exec_name = $exec_name, + @user: + $( $( $rest )* )? + ); + }; + // iteration-exit arm which generates a #[test] function for each case. + ( + @cases: + ( $( $names:tt )+ ) + ( $( $names_extra:tt )* ) + ( $( $names_skip_meta:tt )* ) + @selected: + $bench_module:ident, + $new_test_ext:expr, + $test:path, + benchmarks_path = $path_to_benchmarks_invocation:ident, + extra = $extra:expr, + exec_name = $exec_name:ident, + @user: + $(,)? + ) => { + $crate::impl_bench_case_tests!( + { $bench_module, $new_test_ext, $exec_name, $test, $extra } + { $( $names_extra:tt )* } + $($names)+ + ); + }; + // iteration-exit arm which generates one #[test] function for all cases. + ( + @cases: + () + () + () + @selected: + $bench_module:ident, + $new_test_ext:expr, + $test:path, + benchmarks_path = $path_to_benchmarks_invocation:ident, + extra = $extra:expr, + exec_name = $exec_name:ident, + @user: + $(,)? + ) => { + #[cfg(test)] + mod benchmark_tests { + use super::$bench_module; + + #[test] + fn test_benchmarks() { + $new_test_ext.$exec_name(|| { + use $crate::Benchmarking; + + let mut anything_failed = false; + println!("failing benchmark tests:"); + for benchmark_metadata in $bench_module::<$test>::benchmarks($extra) { + let benchmark_name = &benchmark_metadata.name; + match std::panic::catch_unwind(|| { + $bench_module::<$test>::test_bench_by_name(benchmark_name) + }) { + Err(err) => { + println!( + "{}: {:?}", + $crate::str::from_utf8(benchmark_name) + .expect("benchmark name is always a valid string!"), + err, + ); + anything_failed = true; + }, + Ok(Err(err)) => { + match err { + $crate::BenchmarkError::Stop(err) => { + println!( + "{}: {:?}", + $crate::str::from_utf8(benchmark_name) + .expect("benchmark name is always a valid string!"), + err, + ); + anything_failed = true; + }, + $crate::BenchmarkError::Override(_) => { + // This is still considered a success condition. + $crate::log::error!( + "WARNING: benchmark error overrided - {}", + $crate::str::from_utf8(benchmark_name) + .expect("benchmark name is always a valid string!"), + ); + }, + $crate::BenchmarkError::Skip => { + // This is considered a success condition. + $crate::log::error!( + "WARNING: benchmark error skipped - {}", + $crate::str::from_utf8(benchmark_name) + .expect("benchmark name is always a valid string!"), + ); + } + $crate::BenchmarkError::Weightless => { + // This is considered a success condition. + $crate::log::error!( + "WARNING: benchmark weightless skipped - {}", + $crate::str::from_utf8(benchmark_name) + .expect("benchmark name is always a valid string!"), + ); + } + } + }, + Ok(Ok(())) => (), + } + } + assert!(!anything_failed); + }); + } + } + }; +} + +/// show error message and debugging info for the case of an error happening +/// during a benchmark +pub fn show_benchmark_debug_info( + instance_string: &[u8], + benchmark: &[u8], + components: &[(BenchmarkParameter, u32)], + verify: &bool, + error_message: &str, +) -> sp_runtime::RuntimeString { + sp_runtime::format_runtime_string!( + "\n* Pallet: {}\n\ + * Benchmark: {}\n\ + * Components: {:?}\n\ + * Verify: {:?}\n\ + * Error message: {}", + sp_std::str::from_utf8(instance_string) + .expect("it's all just strings ran through the wasm interface. qed"), + sp_std::str::from_utf8(benchmark) + .expect("it's all just strings ran through the wasm interface. qed"), + components, + verify, + error_message, + ) +} + +/// This macro adds pallet benchmarks to a `Vec` object. +/// +/// First create an object that holds in the input parameters for the benchmark: +/// +/// ```ignore +/// let params = (&config, &whitelist); +/// ``` +/// +/// The `whitelist` is a parameter you pass to control the DB read/write tracking. +/// We use a vector of [TrackedStorageKey](./struct.TrackedStorageKey.html), which is a simple +/// struct used to set if a key has been read or written to. +/// +/// For values that should be skipped entirely, we can just pass `key.into()`. For example: +/// +/// ``` +/// use frame_benchmarking::TrackedStorageKey; +/// let whitelist: Vec = vec![ +/// // Block Number +/// array_bytes::hex_into_unchecked("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac"), +/// // Total Issuance +/// array_bytes::hex_into_unchecked("c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80"), +/// // Execution Phase +/// array_bytes::hex_into_unchecked("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a"), +/// // Event Count +/// array_bytes::hex_into_unchecked("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850"), +/// ]; +/// ``` +/// +/// Then define a mutable local variable to hold your `BenchmarkBatch` object: +/// +/// ```ignore +/// let mut batches = Vec::::new(); +/// ```` +/// +/// Then add the pallets you want to benchmark to this object, using their crate name and generated +/// module struct: +/// +/// ```ignore +/// add_benchmark!(params, batches, pallet_balances, Balances); +/// add_benchmark!(params, batches, pallet_session, SessionBench::); +/// add_benchmark!(params, batches, frame_system, SystemBench::); +/// ... +/// ``` +/// +/// At the end of `dispatch_benchmark`, you should return this batches object. +/// +/// In the case where you have multiple instances of a pallet that you need to separately benchmark, +/// the name of your module struct will be used as a suffix to your outputted weight file. For +/// example: +/// +/// ```ignore +/// add_benchmark!(params, batches, pallet_balances, Balances); // pallet_balances.rs +/// add_benchmark!(params, batches, pallet_collective, Council); // pallet_collective_council.rs +/// add_benchmark!(params, batches, pallet_collective, TechnicalCommittee); // pallet_collective_technical_committee.rs +/// ``` +/// +/// You can manipulate this suffixed string by using a type alias if needed. For example: +/// +/// ```ignore +/// type Council2 = TechnicalCommittee; +/// add_benchmark!(params, batches, pallet_collective, Council2); // pallet_collective_council_2.rs +/// ``` +#[macro_export] +macro_rules! add_benchmark { + ( $params:ident, $batches:ident, $name:path, $location:ty ) => { + let name_string = stringify!($name).as_bytes(); + let instance_string = stringify!($location).as_bytes(); + let (config, whitelist) = $params; + let $crate::BenchmarkConfig { + pallet, + benchmark, + selected_components, + verify, + internal_repeats, + } = config; + if &pallet[..] == &name_string[..] { + let benchmark_result = <$location>::run_benchmark( + &benchmark[..], + &selected_components[..], + whitelist, + *verify, + *internal_repeats, + ); + + let final_results = match benchmark_result { + Ok(results) => Some(results), + Err($crate::BenchmarkError::Override(mut result)) => { + // Insert override warning as the first storage key. + $crate::log::error!( + "WARNING: benchmark error overrided - {}", + $crate::str::from_utf8(benchmark) + .expect("benchmark name is always a valid string!") + ); + result.keys.insert(0, (b"Benchmark Override".to_vec(), 0, 0, false)); + Some($crate::vec![result]) + }, + Err($crate::BenchmarkError::Stop(e)) => { + $crate::show_benchmark_debug_info( + instance_string, + benchmark, + selected_components, + verify, + e, + ); + return Err(e.into()) + }, + Err($crate::BenchmarkError::Skip) => { + $crate::log::error!( + "WARNING: benchmark error skipped - {}", + $crate::str::from_utf8(benchmark) + .expect("benchmark name is always a valid string!") + ); + None + }, + Err($crate::BenchmarkError::Weightless) => { + $crate::log::error!( + "WARNING: benchmark weightless skipped - {}", + $crate::str::from_utf8(benchmark) + .expect("benchmark name is always a valid string!") + ); + Some(vec![$crate::BenchmarkResult { + components: selected_components.clone(), + ..Default::default() + }]) + }, + }; + + if let Some(final_results) = final_results { + $batches.push($crate::BenchmarkBatch { + pallet: name_string.to_vec(), + instance: instance_string.to_vec(), + benchmark: benchmark.clone(), + results: final_results, + }); + } + } + }; +} + +/// This macro allows users to easily generate a list of benchmarks for the pallets configured +/// in the runtime. +/// +/// To use this macro, first create a an object to store the list: +/// +/// ```ignore +/// let mut list = Vec::::new(); +/// ``` +/// +/// Then pass this `list` to the macro, along with the `extra` boolean, the pallet crate, and +/// pallet struct: +/// +/// ```ignore +/// list_benchmark!(list, extra, pallet_balances, Balances); +/// list_benchmark!(list, extra, pallet_session, SessionBench::); +/// list_benchmark!(list, extra, frame_system, SystemBench::); +/// ``` +/// +/// This should match what exists with the `add_benchmark!` macro. +#[macro_export] +macro_rules! list_benchmark { + ( $list:ident, $extra:ident, $name:path, $location:ty ) => { + let pallet_string = stringify!($name).as_bytes(); + let instance_string = stringify!($location).as_bytes(); + let benchmarks = <$location>::benchmarks($extra); + let pallet_benchmarks = BenchmarkList { + pallet: pallet_string.to_vec(), + instance: instance_string.to_vec(), + benchmarks: benchmarks.to_vec(), + }; + $list.push(pallet_benchmarks) + }; +} + +/// Defines pallet configs that `add_benchmarks` and `list_benchmarks` use. +/// Should be preferred instead of having a repetitive list of configs +/// in `add_benchmark` and `list_benchmark`. +#[macro_export] +macro_rules! define_benchmarks { + ( $([ $names:path, $locations:ty ])* ) => { + /// Calls `list_benchmark` with all configs from `define_benchmarks` + /// and passes the first two parameters on. + /// + /// Use as: + /// ```ignore + /// list_benchmarks!(list, extra); + /// ``` + #[macro_export] + macro_rules! list_benchmarks { + ( $list:ident, $extra:ident ) => { + $( $crate::list_benchmark!( $list, $extra, $names, $locations); )* + } + } + + /// Calls `add_benchmark` with all configs from `define_benchmarks` + /// and passes the first two parameters on. + /// + /// Use as: + /// ```ignore + /// add_benchmarks!(params, batches); + /// ``` + #[macro_export] + macro_rules! add_benchmarks { + ( $params:ident, $batches:ident ) => { + $( $crate::add_benchmark!( $params, $batches, $names, $locations ); )* + } + } + } +} + +pub use add_benchmark; +pub use benchmark_backend; +pub use benchmarks; +pub use benchmarks_instance; +pub use benchmarks_instance_pallet; +pub use benchmarks_iter; +pub use define_benchmarks; +pub use impl_bench_case_tests; +pub use impl_bench_name_tests; +pub use impl_benchmark; +pub use impl_benchmark_test; +pub use impl_benchmark_test_suite; +pub use impl_test_function; +pub use list_benchmark; +pub use selected_benchmark; +pub use to_origin; +pub use whitelist; diff --git a/frame/benchmarking/src/weights.rs b/frame/benchmarking/src/weights.rs index 5e5a2e7ee343..13d73e420cce 100644 --- a/frame/benchmarking/src/weights.rs +++ b/frame/benchmarking/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for frame_benchmarking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=frame_benchmarking +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for frame_benchmarking. pub trait WeightInfo { @@ -51,10 +56,8 @@ pub trait WeightInfo { fn subtraction(i: u32, ) -> Weight; fn multiplication(i: u32, ) -> Weight; fn division(i: u32, ) -> Weight; - fn hashing(i: u32, ) -> Weight; + fn hashing() -> Weight; fn sr25519_verification(i: u32, ) -> Weight; - fn storage_read(i: u32, ) -> Weight; - fn storage_write(i: u32, ) -> Weight; } /// Weights for frame_benchmarking using the Substrate node and recommended hardware. @@ -62,53 +65,52 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { /// The range of component `i` is `[0, 1000000]`. fn addition(_i: u32, ) -> Weight { - // Minimum execution time: 108 nanoseconds. - Weight::from_ref_time(137_610 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 147_000 picoseconds. + Weight::from_parts(185_656, 0) } /// The range of component `i` is `[0, 1000000]`. fn subtraction(_i: u32, ) -> Weight { - // Minimum execution time: 104 nanoseconds. - Weight::from_ref_time(133_508 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 146_000 picoseconds. + Weight::from_parts(189_816, 0) } /// The range of component `i` is `[0, 1000000]`. fn multiplication(_i: u32, ) -> Weight { - // Minimum execution time: 110 nanoseconds. - Weight::from_ref_time(140_230 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 148_000 picoseconds. + Weight::from_parts(202_367, 0) } /// The range of component `i` is `[0, 1000000]`. fn division(_i: u32, ) -> Weight { - // Minimum execution time: 96 nanoseconds. - Weight::from_ref_time(136_059 as u64) - } - /// The range of component `i` is `[0, 100]`. - fn hashing(_i: u32, ) -> Weight { - // Minimum execution time: 21_804_747 nanoseconds. - Weight::from_ref_time(22_013_681_386 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 143_000 picoseconds. + Weight::from_parts(189_693, 0) + } + fn hashing() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 24_167_071_000 picoseconds. + Weight::from_parts(24_391_749_000, 0) } /// The range of component `i` is `[0, 100]`. fn sr25519_verification(i: u32, ) -> Weight { - // Minimum execution time: 136 nanoseconds. - Weight::from_ref_time(156_000 as u64) - // Standard Error: 4_531 - .saturating_add(Weight::from_ref_time(46_817_640 as u64).saturating_mul(i as u64)) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `i` is `[0, 1000]`. - fn storage_read(i: u32, ) -> Weight { - // Minimum execution time: 125 nanoseconds. - Weight::from_ref_time(135_000 as u64) - // Standard Error: 3_651 - .saturating_add(Weight::from_ref_time(2_021_172 as u64).saturating_mul(i as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(i as u64))) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `i` is `[0, 1000]`. - fn storage_write(i: u32, ) -> Weight { - // Minimum execution time: 120 nanoseconds. - Weight::from_ref_time(131_000 as u64) - // Standard Error: 348 - .saturating_add(Weight::from_ref_time(377_243 as u64).saturating_mul(i as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(i as u64))) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 231_000 picoseconds. + Weight::from_parts(2_998_013, 0) + // Standard Error: 6_256 + .saturating_add(Weight::from_parts(55_456_705, 0).saturating_mul(i.into())) } } @@ -116,52 +118,51 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { /// The range of component `i` is `[0, 1000000]`. fn addition(_i: u32, ) -> Weight { - // Minimum execution time: 108 nanoseconds. - Weight::from_ref_time(137_610 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 147_000 picoseconds. + Weight::from_parts(185_656, 0) } /// The range of component `i` is `[0, 1000000]`. fn subtraction(_i: u32, ) -> Weight { - // Minimum execution time: 104 nanoseconds. - Weight::from_ref_time(133_508 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 146_000 picoseconds. + Weight::from_parts(189_816, 0) } /// The range of component `i` is `[0, 1000000]`. fn multiplication(_i: u32, ) -> Weight { - // Minimum execution time: 110 nanoseconds. - Weight::from_ref_time(140_230 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 148_000 picoseconds. + Weight::from_parts(202_367, 0) } /// The range of component `i` is `[0, 1000000]`. fn division(_i: u32, ) -> Weight { - // Minimum execution time: 96 nanoseconds. - Weight::from_ref_time(136_059 as u64) - } - /// The range of component `i` is `[0, 100]`. - fn hashing(_i: u32, ) -> Weight { - // Minimum execution time: 21_804_747 nanoseconds. - Weight::from_ref_time(22_013_681_386 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 143_000 picoseconds. + Weight::from_parts(189_693, 0) + } + fn hashing() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 24_167_071_000 picoseconds. + Weight::from_parts(24_391_749_000, 0) } /// The range of component `i` is `[0, 100]`. fn sr25519_verification(i: u32, ) -> Weight { - // Minimum execution time: 136 nanoseconds. - Weight::from_ref_time(156_000 as u64) - // Standard Error: 4_531 - .saturating_add(Weight::from_ref_time(46_817_640 as u64).saturating_mul(i as u64)) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `i` is `[0, 1000]`. - fn storage_read(i: u32, ) -> Weight { - // Minimum execution time: 125 nanoseconds. - Weight::from_ref_time(135_000 as u64) - // Standard Error: 3_651 - .saturating_add(Weight::from_ref_time(2_021_172 as u64).saturating_mul(i as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(i as u64))) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `i` is `[0, 1000]`. - fn storage_write(i: u32, ) -> Weight { - // Minimum execution time: 120 nanoseconds. - Weight::from_ref_time(131_000 as u64) - // Standard Error: 348 - .saturating_add(Weight::from_ref_time(377_243 as u64).saturating_mul(i as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64))) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 231_000 picoseconds. + Weight::from_parts(2_998_013, 0) + // Standard Error: 6_256 + .saturating_add(Weight::from_parts(55_456_705, 0).saturating_mul(i.into())) } } diff --git a/frame/bounties/Cargo.toml b/frame/bounties/Cargo.toml index dcb1adae0577..a5af0d72bdf1 100644 --- a/frame/bounties/Cargo.toml +++ b/frame/bounties/Cargo.toml @@ -13,19 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../treasury" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } diff --git a/frame/bounties/src/benchmarking.rs b/frame/bounties/src/benchmarking.rs index adadb3411ac6..6fff337cba45 100644 --- a/frame/bounties/src/benchmarking.rs +++ b/frame/bounties/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +21,10 @@ use super::*; -use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller, BenchmarkError}; -use frame_system::RawOrigin; +use frame_benchmarking::v1::{ + account, benchmarks_instance_pallet, whitelisted_caller, BenchmarkError, +}; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use sp_runtime::traits::Bounded; use crate::Pallet as Bounties; @@ -55,9 +57,12 @@ fn setup_bounty, I: 'static>( let fee = value / 2u32.into(); let deposit = T::BountyDepositBase::get() + T::DataDepositPerByte::get() * T::MaximumReasonLength::get().into(); - let _ = T::Currency::make_free_balance_be(&caller, deposit); + let _ = T::Currency::make_free_balance_be(&caller, deposit + T::Currency::minimum_balance()); let curator = account("curator", u, SEED); - let _ = T::Currency::make_free_balance_be(&curator, fee / 2u32.into()); + let _ = T::Currency::make_free_balance_be( + &curator, + fee / 2u32.into() + T::Currency::minimum_balance(), + ); let reason = vec![0; d as usize]; (caller, curator, fee, value, reason) } @@ -72,7 +77,7 @@ fn create_bounty, I: 'static>( let approve_origin = T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; Bounties::::approve_bounty(approve_origin.clone(), bounty_id)?; - Treasury::::on_initialize(T::BlockNumber::zero()); + Treasury::::on_initialize(BlockNumberFor::::zero()); Bounties::::propose_curator(approve_origin, bounty_id, curator_lookup.clone(), fee)?; Bounties::::accept_curator(RawOrigin::Signed(curator).into(), bounty_id)?; Ok((curator_lookup, bounty_id)) @@ -110,14 +115,14 @@ benchmarks_instance_pallet! { let bounty_id = BountyCount::::get() - 1; let approve_origin = T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; Bounties::::approve_bounty(approve_origin.clone(), bounty_id)?; - Treasury::::on_initialize(T::BlockNumber::zero()); + Treasury::::on_initialize(BlockNumberFor::::zero()); }: _(approve_origin, bounty_id, curator_lookup, fee) // Worst case when curator is inactive and any sender unassigns the curator. unassign_curator { setup_pot_account::(); let (curator_lookup, bounty_id) = create_bounty::()?; - Treasury::::on_initialize(T::BlockNumber::zero()); + Treasury::::on_initialize(BlockNumberFor::::zero()); let bounty_id = BountyCount::::get() - 1; frame_system::Pallet::::set_block_number(T::BountyUpdatePeriod::get() + 2u32.into()); let caller = whitelisted_caller(); @@ -131,14 +136,14 @@ benchmarks_instance_pallet! { let bounty_id = BountyCount::::get() - 1; let approve_origin = T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; Bounties::::approve_bounty(approve_origin.clone(), bounty_id)?; - Treasury::::on_initialize(T::BlockNumber::zero()); + Treasury::::on_initialize(BlockNumberFor::::zero()); Bounties::::propose_curator(approve_origin, bounty_id, curator_lookup, fee)?; }: _(RawOrigin::Signed(curator), bounty_id) award_bounty { setup_pot_account::(); let (curator_lookup, bounty_id) = create_bounty::()?; - Treasury::::on_initialize(T::BlockNumber::zero()); + Treasury::::on_initialize(BlockNumberFor::::zero()); let bounty_id = BountyCount::::get() - 1; let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?; @@ -149,7 +154,7 @@ benchmarks_instance_pallet! { claim_bounty { setup_pot_account::(); let (curator_lookup, bounty_id) = create_bounty::()?; - Treasury::::on_initialize(T::BlockNumber::zero()); + Treasury::::on_initialize(BlockNumberFor::::zero()); let bounty_id = BountyCount::::get() - 1; let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?; @@ -171,15 +176,17 @@ benchmarks_instance_pallet! { let (caller, curator, fee, value, reason) = setup_bounty::(0, 0); Bounties::::propose_bounty(RawOrigin::Signed(caller).into(), value, reason)?; let bounty_id = BountyCount::::get() - 1; - let approve_origin = T::ApproveOrigin::successful_origin(); + let approve_origin = + T::ApproveOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: close_bounty(approve_origin, bounty_id) close_bounty_active { setup_pot_account::(); let (curator_lookup, bounty_id) = create_bounty::()?; - Treasury::::on_initialize(T::BlockNumber::zero()); + Treasury::::on_initialize(BlockNumberFor::::zero()); let bounty_id = BountyCount::::get() - 1; - let approve_origin = T::ApproveOrigin::successful_origin(); + let approve_origin = + T::ApproveOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: close_bounty(approve_origin, bounty_id) verify { assert_last_event::(Event::BountyCanceled { index: bounty_id }.into()) @@ -188,7 +195,7 @@ benchmarks_instance_pallet! { extend_bounty_expiry { setup_pot_account::(); let (curator_lookup, bounty_id) = create_bounty::()?; - Treasury::::on_initialize(T::BlockNumber::zero()); + Treasury::::on_initialize(BlockNumberFor::::zero()); let bounty_id = BountyCount::::get() - 1; let curator = T::Lookup::lookup(curator_lookup).map_err(<&str>::from)?; diff --git a/frame/bounties/src/lib.rs b/frame/bounties/src/lib.rs index c3c2c08d24b2..c64a35672c7f 100644 --- a/frame/bounties/src/lib.rs +++ b/frame/bounties/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -187,8 +187,10 @@ pub trait ChildBountyManager { pub mod pallet { use super::*; + const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); + #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); #[pallet::config] @@ -199,11 +201,11 @@ pub mod pallet { /// The delay period for which a bounty beneficiary need to wait before claim the payout. #[pallet::constant] - type BountyDepositPayoutDelay: Get; + type BountyDepositPayoutDelay: Get>; /// Bounty duration in blocks. #[pallet::constant] - type BountyUpdatePeriod: Get; + type BountyUpdatePeriod: Get>; /// The curator deposit is calculated as a percentage of the curator fee. /// @@ -303,7 +305,7 @@ pub mod pallet { _, Twox64Concat, BountyIndex, - Bounty, T::BlockNumber>, + Bounty, BlockNumberFor>, >; /// The description of each bounty. @@ -349,9 +351,8 @@ pub mod pallet { /// /// May only be called from `T::SpendOrigin`. /// - /// # + /// ## Complexity /// - O(1). - /// # #[pallet::call_index(1)] #[pallet::weight(>::WeightInfo::approve_bounty())] pub fn approve_bounty( @@ -360,7 +361,7 @@ pub mod pallet { ) -> DispatchResult { let max_amount = T::SpendOrigin::ensure_origin(origin)?; Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + let bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; ensure!( bounty.value <= max_amount, pallet_treasury::Error::::InsufficientPermission @@ -381,9 +382,8 @@ pub mod pallet { /// /// May only be called from `T::SpendOrigin`. /// - /// # + /// ## Complexity /// - O(1). - /// # #[pallet::call_index(2)] #[pallet::weight(>::WeightInfo::propose_curator())] pub fn propose_curator( @@ -396,7 +396,7 @@ pub mod pallet { let curator = T::Lookup::lookup(curator)?; Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + let bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; ensure!( bounty.value <= max_amount, pallet_treasury::Error::::InsufficientPermission @@ -431,9 +431,8 @@ pub mod pallet { /// anyone in the community to call out that a curator is not doing their due diligence, and /// we should pick a new curator. In this case the curator should also be slashed. /// - /// # + /// ## Complexity /// - O(1). - /// # #[pallet::call_index(3)] #[pallet::weight(>::WeightInfo::unassign_curator())] pub fn unassign_curator( @@ -445,7 +444,7 @@ pub mod pallet { .or_else(|_| T::RejectOrigin::ensure_origin(origin).map(|_| None))?; Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + let bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; let slash_curator = |curator: &T::AccountId, curator_deposit: &mut BalanceOf| { @@ -517,9 +516,8 @@ pub mod pallet { /// /// May only be called from the curator. /// - /// # + /// ## Complexity /// - O(1). - /// # #[pallet::call_index(4)] #[pallet::weight(>::WeightInfo::accept_curator())] pub fn accept_curator( @@ -529,7 +527,7 @@ pub mod pallet { let signer = ensure_signed(origin)?; Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + let bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; match bounty.status { BountyStatus::CuratorProposed { ref curator } => { @@ -560,9 +558,8 @@ pub mod pallet { /// - `bounty_id`: Bounty ID to award. /// - `beneficiary`: The beneficiary account whom will receive the payout. /// - /// # + /// ## Complexity /// - O(1). - /// # #[pallet::call_index(5)] #[pallet::weight(>::WeightInfo::award_bounty())] pub fn award_bounty( @@ -574,7 +571,7 @@ pub mod pallet { let beneficiary = T::Lookup::lookup(beneficiary)?; Bounties::::try_mutate_exists(bounty_id, |maybe_bounty| -> DispatchResult { - let mut bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; + let bounty = maybe_bounty.as_mut().ok_or(Error::::InvalidIndex)?; // Ensure no active child bounties before processing the call. ensure!( @@ -608,9 +605,8 @@ pub mod pallet { /// /// - `bounty_id`: Bounty ID to claim. /// - /// # + /// ## Complexity /// - O(1). - /// # #[pallet::call_index(6)] #[pallet::weight(>::WeightInfo::claim_bounty())] pub fn claim_bounty( @@ -672,9 +668,8 @@ pub mod pallet { /// /// - `bounty_id`: Bounty ID to cancel. /// - /// # + /// ## Complexity /// - O(1). - /// # #[pallet::call_index(7)] #[pallet::weight(>::WeightInfo::close_bounty_proposed() .max(>::WeightInfo::close_bounty_active()))] @@ -764,9 +759,8 @@ pub mod pallet { /// - `bounty_id`: Bounty ID to extend. /// - `remark`: additional information. /// - /// # + /// ## Complexity /// - O(1). - /// # #[pallet::call_index(8)] #[pallet::weight(>::WeightInfo::extend_bounty_expiry())] pub fn extend_bounty_expiry( diff --git a/frame/bounties/src/migrations/mod.rs b/frame/bounties/src/migrations/mod.rs index 235d0f1c7cf1..2487ed1d5da5 100644 --- a/frame/bounties/src/migrations/mod.rs +++ b/frame/bounties/src/migrations/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/bounties/src/migrations/v4.rs b/frame/bounties/src/migrations/v4.rs index 2f81c97127bc..936bac117008 100644 --- a/frame/bounties/src/migrations/v4.rs +++ b/frame/bounties/src/migrations/v4.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/bounties/src/tests.rs b/frame/bounties/src/tests.rs index bc59e3a70fd8..355483ddac94 100644 --- a/frame/bounties/src/tests.rs +++ b/frame/bounties/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,37 +23,30 @@ use super::*; use crate as pallet_bounties; use frame_support::{ - assert_noop, assert_ok, - pallet_prelude::GenesisBuild, - parameter_types, + assert_noop, assert_ok, parameter_types, traits::{ConstU32, ConstU64, OnInitialize}, PalletId, }; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BadOrigin, BlakeTwo256, IdentityLookup}, BuildStorage, Perbill, Storage, }; use super::Event as BountiesEvent; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Bounties: pallet_bounties::{Pallet, Call, Storage, Event}, Bounties1: pallet_bounties::::{Pallet, Call, Storage, Event}, - Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, - Treasury1: pallet_treasury::::{Pallet, Call, Storage, Config, Event}, + Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, + Treasury1: pallet_treasury::::{Pallet, Call, Storage, Config, Event}, } ); @@ -69,14 +62,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u128; // u64 is not enough to hold bytes used to generate bounty account type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -100,6 +92,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } parameter_types! { pub const ProposalBond: Permill = Permill::from_percent(5); @@ -190,7 +186,7 @@ type TreasuryError = pallet_treasury::Error; type TreasuryError1 = pallet_treasury::Error; pub fn new_test_ext() -> sp_io::TestExternalities { - let mut ext: sp_io::TestExternalities = GenesisConfig { + let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { system: frame_system::GenesisConfig::default(), balances: pallet_balances::GenesisConfig { balances: vec![(0, 100), (1, 98), (2, 1)] }, treasury: Default::default(), @@ -408,7 +404,7 @@ fn treasury_account_doesnt_get_deleted() { // This is useful for chain that will just update runtime. #[test] fn inexistent_account_works() { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(0, 100), (1, 99), (2, 1)] } .assimilate_storage(&mut t) .unwrap(); @@ -754,7 +750,7 @@ fn award_and_claim_bounty_works() { System::set_block_number(5); >::on_initialize(5); - assert_ok!(Balances::transfer( + assert_ok!(Balances::transfer_allow_death( RuntimeOrigin::signed(0), Bounties::bounty_account_id(0), 10 @@ -832,7 +828,7 @@ fn cancel_and_refund() { System::set_block_number(2); >::on_initialize(2); - assert_ok!(Balances::transfer( + assert_ok!(Balances::transfer_allow_death( RuntimeOrigin::signed(0), Bounties::bounty_account_id(0), 10 @@ -1069,7 +1065,7 @@ fn test_migration_v4() { #[test] fn genesis_funding_works() { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let initial_funding = 100; pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized with 100. @@ -1077,7 +1073,9 @@ fn genesis_funding_works() { } .assimilate_storage(&mut t) .unwrap(); - GenesisBuild::::assimilate_storage(&pallet_treasury::GenesisConfig, &mut t).unwrap(); + pallet_treasury::GenesisConfig::::default() + .assimilate_storage(&mut t) + .unwrap(); let mut t: sp_io::TestExternalities = t.into(); t.execute_with(|| { diff --git a/frame/bounties/src/weights.rs b/frame/bounties/src/weights.rs index 34e78bfa556e..a172d15b56cc 100644 --- a/frame/bounties/src/weights.rs +++ b/frame/bounties/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,33 +18,37 @@ //! Autogenerated weights for pallet_bounties //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// /home/benchbot/cargo_target_dir/production/substrate +// ./target/production/substrate // benchmark // pallet +// --chain=dev // --steps=50 // --repeat=20 +// --pallet=pallet_bounties +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json -// --pallet=pallet_bounties -// --chain=dev -// --header=./HEADER-APACHE2 // --output=./frame/bounties/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_bounties. pub trait WeightInfo { @@ -64,214 +68,340 @@ pub trait WeightInfo { /// Weights for pallet_bounties using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Bounties BountyCount (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Bounties BountyDescriptions (r:0 w:1) - // Storage: Bounties Bounties (r:0 w:1) + /// Storage: Bounties BountyCount (r:1 w:1) + /// Proof: Bounties BountyCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Bounties BountyDescriptions (r:0 w:1) + /// Proof: Bounties BountyDescriptions (max_values: None, max_size: Some(314), added: 2789, mode: MaxEncodedLen) + /// Storage: Bounties Bounties (r:0 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) /// The range of component `d` is `[0, 300]`. fn propose_bounty(d: u32, ) -> Weight { - // Minimum execution time: 33_366 nanoseconds. - Weight::from_ref_time(34_444_773) - // Standard Error: 1_161 - .saturating_add(Weight::from_ref_time(4_723).saturating_mul(d.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(4)) + // Proof Size summary in bytes: + // Measured: `276` + // Estimated: `3593` + // Minimum execution time: 29_384_000 picoseconds. + Weight::from_parts(30_820_018, 3593) + // Standard Error: 298 + .saturating_add(Weight::from_parts(2_920, 0).saturating_mul(d.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } - // Storage: Bounties Bounties (r:1 w:1) - // Storage: Bounties BountyApprovals (r:1 w:1) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: Bounties BountyApprovals (r:1 w:1) + /// Proof: Bounties BountyApprovals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen) fn approve_bounty() -> Weight { - // Minimum execution time: 14_478 nanoseconds. - Weight::from_ref_time(14_763_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `368` + // Estimated: `3642` + // Minimum execution time: 10_873_000 picoseconds. + Weight::from_parts(11_421_000, 3642) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Bounties Bounties (r:1 w:1) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) fn propose_curator() -> Weight { - // Minimum execution time: 13_376 nanoseconds. - Weight::from_ref_time(13_705_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `388` + // Estimated: `3642` + // Minimum execution time: 9_181_000 picoseconds. + Weight::from_parts(9_726_000, 3642) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Bounties Bounties (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn unassign_curator() -> Weight { - // Minimum execution time: 38_072 nanoseconds. - Weight::from_ref_time(38_676_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `564` + // Estimated: `3642` + // Minimum execution time: 30_257_000 picoseconds. + Weight::from_parts(30_751_000, 3642) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Bounties Bounties (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn accept_curator() -> Weight { - // Minimum execution time: 33_207 nanoseconds. - Weight::from_ref_time(34_415_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `560` + // Estimated: `3642` + // Minimum execution time: 27_850_000 picoseconds. + Weight::from_parts(28_821_000, 3642) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Bounties Bounties (r:1 w:1) - // Storage: ChildBounties ParentChildBounties (r:1 w:0) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: ChildBounties ParentChildBounties (r:1 w:0) + /// Proof: ChildBounties ParentChildBounties (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) fn award_bounty() -> Weight { - // Minimum execution time: 28_033 nanoseconds. - Weight::from_ref_time(28_343_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `572` + // Estimated: `3642` + // Minimum execution time: 19_164_000 picoseconds. + Weight::from_parts(20_136_000, 3642) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Bounties Bounties (r:1 w:1) - // Storage: System Account (r:3 w:3) - // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) - // Storage: Bounties BountyDescriptions (r:0 w:1) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: System Account (r:3 w:3) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + /// Proof: ChildBounties ChildrenCuratorFees (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) + /// Storage: Bounties BountyDescriptions (r:0 w:1) + /// Proof: Bounties BountyDescriptions (max_values: None, max_size: Some(314), added: 2789, mode: MaxEncodedLen) fn claim_bounty() -> Weight { - // Minimum execution time: 75_855 nanoseconds. - Weight::from_ref_time(76_318_000) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(6)) + // Proof Size summary in bytes: + // Measured: `936` + // Estimated: `8799` + // Minimum execution time: 120_235_000 picoseconds. + Weight::from_parts(121_673_000, 8799) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) } - // Storage: Bounties Bounties (r:1 w:1) - // Storage: ChildBounties ParentChildBounties (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Bounties BountyDescriptions (r:0 w:1) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: ChildBounties ParentChildBounties (r:1 w:0) + /// Proof: ChildBounties ParentChildBounties (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Bounties BountyDescriptions (r:0 w:1) + /// Proof: Bounties BountyDescriptions (max_values: None, max_size: Some(314), added: 2789, mode: MaxEncodedLen) fn close_bounty_proposed() -> Weight { - // Minimum execution time: 41_955 nanoseconds. - Weight::from_ref_time(42_733_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `616` + // Estimated: `3642` + // Minimum execution time: 35_713_000 picoseconds. + Weight::from_parts(37_174_000, 3642) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - // Storage: Bounties Bounties (r:1 w:1) - // Storage: ChildBounties ParentChildBounties (r:1 w:0) - // Storage: System Account (r:2 w:2) - // Storage: Bounties BountyDescriptions (r:0 w:1) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: ChildBounties ParentChildBounties (r:1 w:0) + /// Proof: ChildBounties ParentChildBounties (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Bounties BountyDescriptions (r:0 w:1) + /// Proof: Bounties BountyDescriptions (max_values: None, max_size: Some(314), added: 2789, mode: MaxEncodedLen) fn close_bounty_active() -> Weight { - // Minimum execution time: 58_267 nanoseconds. - Weight::from_ref_time(59_604_000) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) + // Proof Size summary in bytes: + // Measured: `852` + // Estimated: `6196` + // Minimum execution time: 81_037_000 picoseconds. + Weight::from_parts(83_294_000, 6196) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } - // Storage: Bounties Bounties (r:1 w:1) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) fn extend_bounty_expiry() -> Weight { - // Minimum execution time: 24_893 nanoseconds. - Weight::from_ref_time(25_299_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `424` + // Estimated: `3642` + // Minimum execution time: 15_348_000 picoseconds. + Weight::from_parts(15_776_000, 3642) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Bounties BountyApprovals (r:1 w:1) - // Storage: Bounties Bounties (r:2 w:2) - // Storage: System Account (r:4 w:4) + /// Storage: Bounties BountyApprovals (r:1 w:1) + /// Proof: Bounties BountyApprovals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen) + /// Storage: Bounties Bounties (r:100 w:100) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: System Account (r:200 w:200) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `b` is `[0, 100]`. fn spend_funds(b: u32, ) -> Weight { - // Minimum execution time: 8_846 nanoseconds. - Weight::from_ref_time(20_166_004) - // Standard Error: 28_485 - .saturating_add(Weight::from_ref_time(26_712_253).saturating_mul(b.into())) - .saturating_add(T::DbWeight::get().reads(1)) + // Proof Size summary in bytes: + // Measured: `4 + b * (297 ±0)` + // Estimated: `1887 + b * (5206 ±0)` + // Minimum execution time: 5_082_000 picoseconds. + Weight::from_parts(5_126_000, 1887) + // Standard Error: 21_949 + .saturating_add(Weight::from_parts(42_635_308, 0).saturating_mul(b.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(b.into()))) - .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(b.into()))) + .saturating_add(Weight::from_parts(0, 5206).saturating_mul(b.into())) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Bounties BountyCount (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Bounties BountyDescriptions (r:0 w:1) - // Storage: Bounties Bounties (r:0 w:1) + /// Storage: Bounties BountyCount (r:1 w:1) + /// Proof: Bounties BountyCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Bounties BountyDescriptions (r:0 w:1) + /// Proof: Bounties BountyDescriptions (max_values: None, max_size: Some(314), added: 2789, mode: MaxEncodedLen) + /// Storage: Bounties Bounties (r:0 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) /// The range of component `d` is `[0, 300]`. fn propose_bounty(d: u32, ) -> Weight { - // Minimum execution time: 33_366 nanoseconds. - Weight::from_ref_time(34_444_773) - // Standard Error: 1_161 - .saturating_add(Weight::from_ref_time(4_723).saturating_mul(d.into())) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(4)) + // Proof Size summary in bytes: + // Measured: `276` + // Estimated: `3593` + // Minimum execution time: 29_384_000 picoseconds. + Weight::from_parts(30_820_018, 3593) + // Standard Error: 298 + .saturating_add(Weight::from_parts(2_920, 0).saturating_mul(d.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } - // Storage: Bounties Bounties (r:1 w:1) - // Storage: Bounties BountyApprovals (r:1 w:1) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: Bounties BountyApprovals (r:1 w:1) + /// Proof: Bounties BountyApprovals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen) fn approve_bounty() -> Weight { - // Minimum execution time: 14_478 nanoseconds. - Weight::from_ref_time(14_763_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `368` + // Estimated: `3642` + // Minimum execution time: 10_873_000 picoseconds. + Weight::from_parts(11_421_000, 3642) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Bounties Bounties (r:1 w:1) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) fn propose_curator() -> Weight { - // Minimum execution time: 13_376 nanoseconds. - Weight::from_ref_time(13_705_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `388` + // Estimated: `3642` + // Minimum execution time: 9_181_000 picoseconds. + Weight::from_parts(9_726_000, 3642) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Bounties Bounties (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn unassign_curator() -> Weight { - // Minimum execution time: 38_072 nanoseconds. - Weight::from_ref_time(38_676_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `564` + // Estimated: `3642` + // Minimum execution time: 30_257_000 picoseconds. + Weight::from_parts(30_751_000, 3642) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Bounties Bounties (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn accept_curator() -> Weight { - // Minimum execution time: 33_207 nanoseconds. - Weight::from_ref_time(34_415_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `560` + // Estimated: `3642` + // Minimum execution time: 27_850_000 picoseconds. + Weight::from_parts(28_821_000, 3642) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Bounties Bounties (r:1 w:1) - // Storage: ChildBounties ParentChildBounties (r:1 w:0) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: ChildBounties ParentChildBounties (r:1 w:0) + /// Proof: ChildBounties ParentChildBounties (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) fn award_bounty() -> Weight { - // Minimum execution time: 28_033 nanoseconds. - Weight::from_ref_time(28_343_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `572` + // Estimated: `3642` + // Minimum execution time: 19_164_000 picoseconds. + Weight::from_parts(20_136_000, 3642) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Bounties Bounties (r:1 w:1) - // Storage: System Account (r:3 w:3) - // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) - // Storage: Bounties BountyDescriptions (r:0 w:1) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: System Account (r:3 w:3) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + /// Proof: ChildBounties ChildrenCuratorFees (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) + /// Storage: Bounties BountyDescriptions (r:0 w:1) + /// Proof: Bounties BountyDescriptions (max_values: None, max_size: Some(314), added: 2789, mode: MaxEncodedLen) fn claim_bounty() -> Weight { - // Minimum execution time: 75_855 nanoseconds. - Weight::from_ref_time(76_318_000) - .saturating_add(RocksDbWeight::get().reads(5)) - .saturating_add(RocksDbWeight::get().writes(6)) + // Proof Size summary in bytes: + // Measured: `936` + // Estimated: `8799` + // Minimum execution time: 120_235_000 picoseconds. + Weight::from_parts(121_673_000, 8799) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } - // Storage: Bounties Bounties (r:1 w:1) - // Storage: ChildBounties ParentChildBounties (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Bounties BountyDescriptions (r:0 w:1) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: ChildBounties ParentChildBounties (r:1 w:0) + /// Proof: ChildBounties ParentChildBounties (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Bounties BountyDescriptions (r:0 w:1) + /// Proof: Bounties BountyDescriptions (max_values: None, max_size: Some(314), added: 2789, mode: MaxEncodedLen) fn close_bounty_proposed() -> Weight { - // Minimum execution time: 41_955 nanoseconds. - Weight::from_ref_time(42_733_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `616` + // Estimated: `3642` + // Minimum execution time: 35_713_000 picoseconds. + Weight::from_parts(37_174_000, 3642) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } - // Storage: Bounties Bounties (r:1 w:1) - // Storage: ChildBounties ParentChildBounties (r:1 w:0) - // Storage: System Account (r:2 w:2) - // Storage: Bounties BountyDescriptions (r:0 w:1) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: ChildBounties ParentChildBounties (r:1 w:0) + /// Proof: ChildBounties ParentChildBounties (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Bounties BountyDescriptions (r:0 w:1) + /// Proof: Bounties BountyDescriptions (max_values: None, max_size: Some(314), added: 2789, mode: MaxEncodedLen) fn close_bounty_active() -> Weight { - // Minimum execution time: 58_267 nanoseconds. - Weight::from_ref_time(59_604_000) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(4)) + // Proof Size summary in bytes: + // Measured: `852` + // Estimated: `6196` + // Minimum execution time: 81_037_000 picoseconds. + Weight::from_parts(83_294_000, 6196) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } - // Storage: Bounties Bounties (r:1 w:1) + /// Storage: Bounties Bounties (r:1 w:1) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) fn extend_bounty_expiry() -> Weight { - // Minimum execution time: 24_893 nanoseconds. - Weight::from_ref_time(25_299_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `424` + // Estimated: `3642` + // Minimum execution time: 15_348_000 picoseconds. + Weight::from_parts(15_776_000, 3642) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Bounties BountyApprovals (r:1 w:1) - // Storage: Bounties Bounties (r:2 w:2) - // Storage: System Account (r:4 w:4) + /// Storage: Bounties BountyApprovals (r:1 w:1) + /// Proof: Bounties BountyApprovals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen) + /// Storage: Bounties Bounties (r:100 w:100) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: System Account (r:200 w:200) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `b` is `[0, 100]`. fn spend_funds(b: u32, ) -> Weight { - // Minimum execution time: 8_846 nanoseconds. - Weight::from_ref_time(20_166_004) - // Standard Error: 28_485 - .saturating_add(Weight::from_ref_time(26_712_253).saturating_mul(b.into())) - .saturating_add(RocksDbWeight::get().reads(1)) + // Proof Size summary in bytes: + // Measured: `4 + b * (297 ±0)` + // Estimated: `1887 + b * (5206 ±0)` + // Minimum execution time: 5_082_000 picoseconds. + Weight::from_parts(5_126_000, 1887) + // Standard Error: 21_949 + .saturating_add(Weight::from_parts(42_635_308, 0).saturating_mul(b.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(b.into()))) - .saturating_add(RocksDbWeight::get().writes(1)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(b.into()))) + .saturating_add(Weight::from_parts(0, 5206).saturating_mul(b.into())) } } diff --git a/frame/child-bounties/Cargo.toml b/frame/child-bounties/Cargo.toml index 8d29b7619759..aa5b873dee59 100644 --- a/frame/child-bounties/Cargo.toml +++ b/frame/child-bounties/Cargo.toml @@ -13,20 +13,20 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-bounties = { version = "4.0.0-dev", default-features = false, path = "../bounties" } pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../treasury" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } diff --git a/frame/child-bounties/src/benchmarking.rs b/frame/child-bounties/src/benchmarking.rs index 04a8583f286f..1973564d0dc1 100644 --- a/frame/child-bounties/src/benchmarking.rs +++ b/frame/child-bounties/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +21,8 @@ use super::*; -use frame_benchmarking::{account, benchmarks, whitelisted_caller, BenchmarkError}; -use frame_system::RawOrigin; +use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller, BenchmarkError}; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use crate::Pallet as ChildBounties; use pallet_bounties::Pallet as Bounties; @@ -63,9 +63,12 @@ fn setup_bounty( let fee = value / 2u32.into(); let deposit = T::BountyDepositBase::get() + T::DataDepositPerByte::get() * T::MaximumReasonLength::get().into(); - let _ = T::Currency::make_free_balance_be(&caller, deposit); + let _ = T::Currency::make_free_balance_be(&caller, deposit + T::Currency::minimum_balance()); let curator = account("curator", user, SEED); - let _ = T::Currency::make_free_balance_be(&curator, fee / 2u32.into()); + let _ = T::Currency::make_free_balance_be( + &curator, + fee / 2u32.into() + T::Currency::minimum_balance(), + ); let reason = vec![0; description as usize]; (caller, curator, fee, value, reason) } @@ -73,7 +76,10 @@ fn setup_bounty( fn setup_child_bounty(user: u32, description: u32) -> BenchmarkChildBounty { let (caller, curator, fee, value, reason) = setup_bounty::(user, description); let child_curator = account("child-curator", user, SEED); - let _ = T::Currency::make_free_balance_be(&child_curator, fee / 2u32.into()); + let _ = T::Currency::make_free_balance_be( + &child_curator, + fee / 2u32.into() + T::Currency::minimum_balance(), + ); let child_bounty_value = (value - fee) / 4u32.into(); let child_bounty_fee = child_bounty_value / 2u32.into(); @@ -108,7 +114,7 @@ fn activate_bounty( let approve_origin = T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; Bounties::::approve_bounty(approve_origin, child_bounty_setup.bounty_id)?; - Treasury::::on_initialize(T::BlockNumber::zero()); + Treasury::::on_initialize(BlockNumberFor::::zero()); Bounties::::propose_curator( RawOrigin::Root.into(), child_bounty_setup.bounty_id, @@ -223,7 +229,7 @@ benchmarks! { unassign_curator { setup_pot_account::(); let bounty_setup = activate_child_bounty::(0, T::MaximumReasonLength::get())?; - Treasury::::on_initialize(T::BlockNumber::zero()); + Treasury::::on_initialize(BlockNumberFor::::zero()); frame_system::Pallet::::set_block_number(T::BountyUpdatePeriod::get() + 1u32.into()); let caller = whitelisted_caller(); }: _(RawOrigin::Signed(caller), bounty_setup.bounty_id, @@ -297,7 +303,7 @@ benchmarks! { close_child_bounty_active { setup_pot_account::(); let bounty_setup = activate_child_bounty::(0, T::MaximumReasonLength::get())?; - Treasury::::on_initialize(T::BlockNumber::zero()); + Treasury::::on_initialize(BlockNumberFor::::zero()); }: close_child_bounty(RawOrigin::Root, bounty_setup.bounty_id, bounty_setup.child_bounty_id) verify { assert_last_event::(Event::Canceled { diff --git a/frame/child-bounties/src/lib.rs b/frame/child-bounties/src/lib.rs index 9eb784eaccd2..1eedeaa5a1ae 100644 --- a/frame/child-bounties/src/lib.rs +++ b/frame/child-bounties/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -130,7 +130,6 @@ pub mod pallet { use super::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::config] @@ -201,7 +200,7 @@ pub mod pallet { BountyIndex, Twox64Concat, BountyIndex, - ChildBounty, T::BlockNumber>, + ChildBounty, BlockNumberFor>, >; /// The description of each child-bounty. @@ -332,7 +331,7 @@ pub mod pallet { parent_bounty_id, child_bounty_id, |maybe_child_bounty| -> DispatchResult { - let mut child_bounty = + let child_bounty = maybe_child_bounty.as_mut().ok_or(BountiesError::::InvalidIndex)?; // Ensure child-bounty is in expected state. @@ -397,7 +396,7 @@ pub mod pallet { parent_bounty_id, child_bounty_id, |maybe_child_bounty| -> DispatchResult { - let mut child_bounty = + let child_bounty = maybe_child_bounty.as_mut().ok_or(BountiesError::::InvalidIndex)?; // Ensure child-bounty is in expected state. @@ -474,7 +473,7 @@ pub mod pallet { parent_bounty_id, child_bounty_id, |maybe_child_bounty| -> DispatchResult { - let mut child_bounty = + let child_bounty = maybe_child_bounty.as_mut().ok_or(BountiesError::::InvalidIndex)?; let slash_curator = |curator: &T::AccountId, @@ -592,7 +591,7 @@ pub mod pallet { parent_bounty_id, child_bounty_id, |maybe_child_bounty| -> DispatchResult { - let mut child_bounty = + let child_bounty = maybe_child_bounty.as_mut().ok_or(BountiesError::::InvalidIndex)?; // Ensure child-bounty is in active state. @@ -817,7 +816,7 @@ impl Pallet { fn ensure_bounty_active( bounty_id: BountyIndex, - ) -> Result<(T::AccountId, T::BlockNumber), DispatchError> { + ) -> Result<(T::AccountId, BlockNumberFor), DispatchError> { let parent_bounty = pallet_bounties::Pallet::::bounties(bounty_id) .ok_or(BountiesError::::InvalidIndex)?; if let BountyStatus::Active { curator, update_due } = parent_bounty.get_status() { diff --git a/frame/child-bounties/src/tests.rs b/frame/child-bounties/src/tests.rs index f3415c69df61..24a6410f29f7 100644 --- a/frame/child-bounties/src/tests.rs +++ b/frame/child-bounties/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,9 +23,7 @@ use super::*; use crate as pallet_child_bounties; use frame_support::{ - assert_noop, assert_ok, - pallet_prelude::GenesisBuild, - parameter_types, + assert_noop, assert_ok, parameter_types, traits::{ConstU32, ConstU64, OnInitialize}, weights::Weight, PalletId, @@ -33,33 +31,28 @@ use frame_support::{ use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BadOrigin, BlakeTwo256, IdentityLookup}, - Perbill, Permill, + BuildStorage, Perbill, Permill, TokenError, }; use super::Event as ChildBountiesEvent; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; type BountiesError = pallet_bounties::Error; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Bounties: pallet_bounties::{Pallet, Call, Storage, Event}, - Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, + Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, ChildBounties: pallet_child_bounties::{Pallet, Call, Storage, Event}, } ); parameter_types! { - pub const MaximumBlockWeight: Weight = Weight::from_ref_time(1024); + pub const MaximumBlockWeight: Weight = Weight::from_parts(1024, 0); pub const MaximumBlockLength: u32 = 2 * 1024; pub const AvailableBlockRatio: Perbill = Perbill::one(); } @@ -72,14 +65,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u128; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -103,6 +95,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } parameter_types! { pub const ProposalBond: Permill = Permill::from_percent(5); @@ -158,14 +154,16 @@ impl pallet_child_bounties::Config for Test { } pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized at ED. balances: vec![(0, 100), (1, 98), (2, 1)], } .assimilate_storage(&mut t) .unwrap(); - GenesisBuild::::assimilate_storage(&pallet_treasury::GenesisConfig, &mut t).unwrap(); + pallet_treasury::GenesisConfig::::default() + .assimilate_storage(&mut t) + .unwrap(); t.into() } @@ -248,7 +246,7 @@ fn add_child_bounty() { assert_noop!( ChildBounties::add_child_bounty(RuntimeOrigin::signed(4), 0, 50, b"12345-p1".to_vec()), - pallet_balances::Error::::KeepAlive, + TokenError::NotExpendable, ); assert_noop!( diff --git a/frame/child-bounties/src/weights.rs b/frame/child-bounties/src/weights.rs index 235c84320eff..e4c1f238e88b 100644 --- a/frame/child-bounties/src/weights.rs +++ b/frame/child-bounties/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_child_bounties //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_child_bounties +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_child_bounties. pub trait WeightInfo { @@ -60,176 +65,286 @@ pub trait WeightInfo { /// Weights for pallet_child_bounties using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: ChildBounties ParentChildBounties (r:1 w:1) - // Storage: Bounties Bounties (r:1 w:0) - // Storage: System Account (r:2 w:2) - // Storage: ChildBounties ChildBountyCount (r:1 w:1) - // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) - // Storage: ChildBounties ChildBounties (r:0 w:1) + /// Storage: ChildBounties ParentChildBounties (r:1 w:1) + /// Proof: ChildBounties ParentChildBounties (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) + /// Storage: Bounties Bounties (r:1 w:0) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBountyCount (r:1 w:1) + /// Proof: ChildBounties ChildBountyCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + /// Proof: ChildBounties ChildBountyDescriptions (max_values: None, max_size: Some(314), added: 2789, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBounties (r:0 w:1) + /// Proof: ChildBounties ChildBounties (max_values: None, max_size: Some(145), added: 2620, mode: MaxEncodedLen) /// The range of component `d` is `[0, 300]`. - fn add_child_bounty(d: u32, ) -> Weight { - // Minimum execution time: 59_121 nanoseconds. - Weight::from_ref_time(60_212_235 as u64) - // Standard Error: 149 - .saturating_add(Weight::from_ref_time(412 as u64).saturating_mul(d as u64)) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(6 as u64)) - } - // Storage: Bounties Bounties (r:1 w:0) - // Storage: ChildBounties ChildBounties (r:1 w:1) - // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + fn add_child_bounty(_d: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `712` + // Estimated: `6196` + // Minimum execution time: 69_805_000 picoseconds. + Weight::from_parts(73_216_717, 6196) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) + } + /// Storage: Bounties Bounties (r:1 w:0) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBounties (r:1 w:1) + /// Proof: ChildBounties ChildBounties (max_values: None, max_size: Some(145), added: 2620, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + /// Proof: ChildBounties ChildrenCuratorFees (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) fn propose_curator() -> Weight { - // Minimum execution time: 20_785 nanoseconds. - Weight::from_ref_time(21_000_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Bounties Bounties (r:1 w:0) - // Storage: ChildBounties ChildBounties (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `766` + // Estimated: `3642` + // Minimum execution time: 18_190_000 picoseconds. + Weight::from_parts(18_932_000, 3642) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Bounties Bounties (r:1 w:0) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBounties (r:1 w:1) + /// Proof: ChildBounties ChildBounties (max_values: None, max_size: Some(145), added: 2620, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn accept_curator() -> Weight { - // Minimum execution time: 37_874 nanoseconds. - Weight::from_ref_time(38_322_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: ChildBounties ChildBounties (r:1 w:1) - // Storage: Bounties Bounties (r:1 w:0) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `912` + // Estimated: `3642` + // Minimum execution time: 35_035_000 picoseconds. + Weight::from_parts(35_975_000, 3642) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: ChildBounties ChildBounties (r:1 w:1) + /// Proof: ChildBounties ChildBounties (max_values: None, max_size: Some(145), added: 2620, mode: MaxEncodedLen) + /// Storage: Bounties Bounties (r:1 w:0) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn unassign_curator() -> Weight { - // Minimum execution time: 43_385 nanoseconds. - Weight::from_ref_time(43_774_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `912` + // Estimated: `3642` + // Minimum execution time: 37_636_000 picoseconds. + Weight::from_parts(38_610_000, 3642) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Bounties Bounties (r:1 w:0) - // Storage: ChildBounties ChildBounties (r:1 w:1) + /// Storage: Bounties Bounties (r:1 w:0) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBounties (r:1 w:1) + /// Proof: ChildBounties ChildBounties (max_values: None, max_size: Some(145), added: 2620, mode: MaxEncodedLen) fn award_child_bounty() -> Weight { - // Minimum execution time: 31_390 nanoseconds. - Weight::from_ref_time(31_802_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: ChildBounties ChildBounties (r:1 w:1) - // Storage: System Account (r:3 w:3) - // Storage: ChildBounties ParentChildBounties (r:1 w:1) - // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `809` + // Estimated: `3642` + // Minimum execution time: 22_457_000 picoseconds. + Weight::from_parts(23_691_000, 3642) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: ChildBounties ChildBounties (r:1 w:1) + /// Proof: ChildBounties ChildBounties (max_values: None, max_size: Some(145), added: 2620, mode: MaxEncodedLen) + /// Storage: System Account (r:3 w:3) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: ChildBounties ParentChildBounties (r:1 w:1) + /// Proof: ChildBounties ParentChildBounties (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + /// Proof: ChildBounties ChildBountyDescriptions (max_values: None, max_size: Some(314), added: 2789, mode: MaxEncodedLen) fn claim_child_bounty() -> Weight { - // Minimum execution time: 74_956 nanoseconds. - Weight::from_ref_time(75_850_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(6 as u64)) - } - // Storage: Bounties Bounties (r:1 w:0) - // Storage: ChildBounties ChildBounties (r:1 w:1) - // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) - // Storage: ChildBounties ParentChildBounties (r:1 w:1) - // Storage: System Account (r:2 w:2) - // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `682` + // Estimated: `8799` + // Minimum execution time: 118_272_000 picoseconds. + Weight::from_parts(121_646_000, 8799) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) + } + /// Storage: Bounties Bounties (r:1 w:0) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBounties (r:1 w:1) + /// Proof: ChildBounties ChildBounties (max_values: None, max_size: Some(145), added: 2620, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + /// Proof: ChildBounties ChildrenCuratorFees (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) + /// Storage: ChildBounties ParentChildBounties (r:1 w:1) + /// Proof: ChildBounties ParentChildBounties (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + /// Proof: ChildBounties ChildBountyDescriptions (max_values: None, max_size: Some(314), added: 2789, mode: MaxEncodedLen) fn close_child_bounty_added() -> Weight { - // Minimum execution time: 57_215 nanoseconds. - Weight::from_ref_time(58_285_000 as u64) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(6 as u64)) - } - // Storage: Bounties Bounties (r:1 w:0) - // Storage: ChildBounties ChildBounties (r:1 w:1) - // Storage: System Account (r:3 w:3) - // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) - // Storage: ChildBounties ParentChildBounties (r:1 w:1) - // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `1012` + // Estimated: `6196` + // Minimum execution time: 75_717_000 picoseconds. + Weight::from_parts(77_837_000, 6196) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) + } + /// Storage: Bounties Bounties (r:1 w:0) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBounties (r:1 w:1) + /// Proof: ChildBounties ChildBounties (max_values: None, max_size: Some(145), added: 2620, mode: MaxEncodedLen) + /// Storage: System Account (r:3 w:3) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + /// Proof: ChildBounties ChildrenCuratorFees (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) + /// Storage: ChildBounties ParentChildBounties (r:1 w:1) + /// Proof: ChildBounties ParentChildBounties (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + /// Proof: ChildBounties ChildBountyDescriptions (max_values: None, max_size: Some(314), added: 2789, mode: MaxEncodedLen) fn close_child_bounty_active() -> Weight { - // Minimum execution time: 67_641 nanoseconds. - Weight::from_ref_time(69_184_000 as u64) - .saturating_add(T::DbWeight::get().reads(7 as u64)) - .saturating_add(T::DbWeight::get().writes(7 as u64)) + // Proof Size summary in bytes: + // Measured: `1199` + // Estimated: `8799` + // Minimum execution time: 94_215_000 picoseconds. + Weight::from_parts(97_017_000, 8799) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: ChildBounties ParentChildBounties (r:1 w:1) - // Storage: Bounties Bounties (r:1 w:0) - // Storage: System Account (r:2 w:2) - // Storage: ChildBounties ChildBountyCount (r:1 w:1) - // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) - // Storage: ChildBounties ChildBounties (r:0 w:1) + /// Storage: ChildBounties ParentChildBounties (r:1 w:1) + /// Proof: ChildBounties ParentChildBounties (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) + /// Storage: Bounties Bounties (r:1 w:0) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBountyCount (r:1 w:1) + /// Proof: ChildBounties ChildBountyCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + /// Proof: ChildBounties ChildBountyDescriptions (max_values: None, max_size: Some(314), added: 2789, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBounties (r:0 w:1) + /// Proof: ChildBounties ChildBounties (max_values: None, max_size: Some(145), added: 2620, mode: MaxEncodedLen) /// The range of component `d` is `[0, 300]`. - fn add_child_bounty(d: u32, ) -> Weight { - // Minimum execution time: 59_121 nanoseconds. - Weight::from_ref_time(60_212_235 as u64) - // Standard Error: 149 - .saturating_add(Weight::from_ref_time(412 as u64).saturating_mul(d as u64)) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) - } - // Storage: Bounties Bounties (r:1 w:0) - // Storage: ChildBounties ChildBounties (r:1 w:1) - // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + fn add_child_bounty(_d: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `712` + // Estimated: `6196` + // Minimum execution time: 69_805_000 picoseconds. + Weight::from_parts(73_216_717, 6196) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + } + /// Storage: Bounties Bounties (r:1 w:0) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBounties (r:1 w:1) + /// Proof: ChildBounties ChildBounties (max_values: None, max_size: Some(145), added: 2620, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + /// Proof: ChildBounties ChildrenCuratorFees (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) fn propose_curator() -> Weight { - // Minimum execution time: 20_785 nanoseconds. - Weight::from_ref_time(21_000_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Bounties Bounties (r:1 w:0) - // Storage: ChildBounties ChildBounties (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `766` + // Estimated: `3642` + // Minimum execution time: 18_190_000 picoseconds. + Weight::from_parts(18_932_000, 3642) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Bounties Bounties (r:1 w:0) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBounties (r:1 w:1) + /// Proof: ChildBounties ChildBounties (max_values: None, max_size: Some(145), added: 2620, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn accept_curator() -> Weight { - // Minimum execution time: 37_874 nanoseconds. - Weight::from_ref_time(38_322_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: ChildBounties ChildBounties (r:1 w:1) - // Storage: Bounties Bounties (r:1 w:0) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `912` + // Estimated: `3642` + // Minimum execution time: 35_035_000 picoseconds. + Weight::from_parts(35_975_000, 3642) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: ChildBounties ChildBounties (r:1 w:1) + /// Proof: ChildBounties ChildBounties (max_values: None, max_size: Some(145), added: 2620, mode: MaxEncodedLen) + /// Storage: Bounties Bounties (r:1 w:0) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn unassign_curator() -> Weight { - // Minimum execution time: 43_385 nanoseconds. - Weight::from_ref_time(43_774_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `912` + // Estimated: `3642` + // Minimum execution time: 37_636_000 picoseconds. + Weight::from_parts(38_610_000, 3642) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Bounties Bounties (r:1 w:0) - // Storage: ChildBounties ChildBounties (r:1 w:1) + /// Storage: Bounties Bounties (r:1 w:0) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBounties (r:1 w:1) + /// Proof: ChildBounties ChildBounties (max_values: None, max_size: Some(145), added: 2620, mode: MaxEncodedLen) fn award_child_bounty() -> Weight { - // Minimum execution time: 31_390 nanoseconds. - Weight::from_ref_time(31_802_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: ChildBounties ChildBounties (r:1 w:1) - // Storage: System Account (r:3 w:3) - // Storage: ChildBounties ParentChildBounties (r:1 w:1) - // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `809` + // Estimated: `3642` + // Minimum execution time: 22_457_000 picoseconds. + Weight::from_parts(23_691_000, 3642) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: ChildBounties ChildBounties (r:1 w:1) + /// Proof: ChildBounties ChildBounties (max_values: None, max_size: Some(145), added: 2620, mode: MaxEncodedLen) + /// Storage: System Account (r:3 w:3) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: ChildBounties ParentChildBounties (r:1 w:1) + /// Proof: ChildBounties ParentChildBounties (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + /// Proof: ChildBounties ChildBountyDescriptions (max_values: None, max_size: Some(314), added: 2789, mode: MaxEncodedLen) fn claim_child_bounty() -> Weight { - // Minimum execution time: 74_956 nanoseconds. - Weight::from_ref_time(75_850_000 as u64) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) - } - // Storage: Bounties Bounties (r:1 w:0) - // Storage: ChildBounties ChildBounties (r:1 w:1) - // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) - // Storage: ChildBounties ParentChildBounties (r:1 w:1) - // Storage: System Account (r:2 w:2) - // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `682` + // Estimated: `8799` + // Minimum execution time: 118_272_000 picoseconds. + Weight::from_parts(121_646_000, 8799) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + } + /// Storage: Bounties Bounties (r:1 w:0) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBounties (r:1 w:1) + /// Proof: ChildBounties ChildBounties (max_values: None, max_size: Some(145), added: 2620, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + /// Proof: ChildBounties ChildrenCuratorFees (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) + /// Storage: ChildBounties ParentChildBounties (r:1 w:1) + /// Proof: ChildBounties ParentChildBounties (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + /// Proof: ChildBounties ChildBountyDescriptions (max_values: None, max_size: Some(314), added: 2789, mode: MaxEncodedLen) fn close_child_bounty_added() -> Weight { - // Minimum execution time: 57_215 nanoseconds. - Weight::from_ref_time(58_285_000 as u64) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) - } - // Storage: Bounties Bounties (r:1 w:0) - // Storage: ChildBounties ChildBounties (r:1 w:1) - // Storage: System Account (r:3 w:3) - // Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) - // Storage: ChildBounties ParentChildBounties (r:1 w:1) - // Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `1012` + // Estimated: `6196` + // Minimum execution time: 75_717_000 picoseconds. + Weight::from_parts(77_837_000, 6196) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + } + /// Storage: Bounties Bounties (r:1 w:0) + /// Proof: Bounties Bounties (max_values: None, max_size: Some(177), added: 2652, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBounties (r:1 w:1) + /// Proof: ChildBounties ChildBounties (max_values: None, max_size: Some(145), added: 2620, mode: MaxEncodedLen) + /// Storage: System Account (r:3 w:3) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildrenCuratorFees (r:1 w:1) + /// Proof: ChildBounties ChildrenCuratorFees (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) + /// Storage: ChildBounties ParentChildBounties (r:1 w:1) + /// Proof: ChildBounties ParentChildBounties (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) + /// Storage: ChildBounties ChildBountyDescriptions (r:0 w:1) + /// Proof: ChildBounties ChildBountyDescriptions (max_values: None, max_size: Some(314), added: 2789, mode: MaxEncodedLen) fn close_child_bounty_active() -> Weight { - // Minimum execution time: 67_641 nanoseconds. - Weight::from_ref_time(69_184_000 as u64) - .saturating_add(RocksDbWeight::get().reads(7 as u64)) - .saturating_add(RocksDbWeight::get().writes(7 as u64)) + // Proof Size summary in bytes: + // Measured: `1199` + // Estimated: `8799` + // Minimum execution time: 94_215_000 picoseconds. + Weight::from_parts(97_017_000, 8799) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) } } diff --git a/frame/collective/Cargo.toml b/frame/collective/Cargo.toml index 26cd5a8ab44c..7e9f38dc24a0 100644 --- a/frame/collective/Cargo.toml +++ b/frame/collective/Cargo.toml @@ -13,16 +13,16 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [features] default = ["std"] diff --git a/frame/collective/src/benchmarking.rs b/frame/collective/src/benchmarking.rs index 75a724623002..503d72510530 100644 --- a/frame/collective/src/benchmarking.rs +++ b/frame/collective/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,8 +23,10 @@ use crate::Pallet as Collective; use sp_runtime::traits::Bounded; use sp_std::mem::size_of; -use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller}; -use frame_system::{Call as SystemCall, Pallet as System, RawOrigin as SystemOrigin}; +use frame_benchmarking::v1::{account, benchmarks_instance_pallet, whitelisted_caller}; +use frame_system::{ + pallet_prelude::BlockNumberFor, Call as SystemCall, Pallet as System, RawOrigin as SystemOrigin, +}; const SEED: u32 = 0; @@ -131,7 +133,7 @@ benchmarks_instance_pallet! { let proposal_hash = T::Hashing::hash_of(&proposal); // Note that execution fails due to mis-matched origin assert_last_event::( - Event::MemberExecuted { proposal_hash, result: Err(DispatchError::BadOrigin) }.into() + Event::MemberExecuted { proposal_hash, result: Ok(()) }.into() ); } @@ -162,7 +164,7 @@ benchmarks_instance_pallet! { let proposal_hash = T::Hashing::hash_of(&proposal); // Note that execution fails due to mis-matched origin assert_last_event::( - Event::Executed { proposal_hash, result: Err(DispatchError::BadOrigin) }.into() + Event::Executed { proposal_hash, result: Ok(()) }.into() ); } @@ -441,7 +443,7 @@ benchmarks_instance_pallet! { verify { // The last proposal is removed. assert_eq!(Collective::::proposals().len(), (p - 1) as usize); - assert_last_event::(Event::Executed { proposal_hash: last_hash, result: Err(DispatchError::BadOrigin) }.into()); + assert_last_event::(Event::Executed { proposal_hash: last_hash, result: Ok(()) }.into()); } close_disapproved { @@ -516,7 +518,7 @@ benchmarks_instance_pallet! { false, )?; - System::::set_block_number(T::BlockNumber::max_value()); + System::::set_block_number(BlockNumberFor::::max_value()); assert_eq!(Collective::::proposals().len(), p as usize); // Prime nay will close it as disapproved @@ -588,14 +590,14 @@ benchmarks_instance_pallet! { } // caller is prime, prime already votes aye by creating the proposal - System::::set_block_number(T::BlockNumber::max_value()); + System::::set_block_number(BlockNumberFor::::max_value()); assert_eq!(Collective::::proposals().len(), p as usize); // Prime aye will close it as approved }: close(SystemOrigin::Signed(caller), last_hash, p - 1, Weight::MAX, bytes_in_storage) verify { assert_eq!(Collective::::proposals().len(), (p - 1) as usize); - assert_last_event::(Event::Executed { proposal_hash: last_hash, result: Err(DispatchError::BadOrigin) }.into()); + assert_last_event::(Event::Executed { proposal_hash: last_hash, result: Ok(()) }.into()); } disapprove_proposal { @@ -637,7 +639,7 @@ benchmarks_instance_pallet! { last_hash = T::Hashing::hash_of(&proposal); } - System::::set_block_number(T::BlockNumber::max_value()); + System::::set_block_number(BlockNumberFor::::max_value()); assert_eq!(Collective::::proposals().len(), p as usize); }: _(SystemOrigin::Root, last_hash) @@ -646,5 +648,5 @@ benchmarks_instance_pallet! { assert_last_event::(Event::Disapproved { proposal_hash: last_hash }.into()); } - impl_benchmark_test_suite!(Collective, crate::tests::new_test_ext(), crate::tests::Test); + impl_benchmark_test_suite!(Collective, crate::tests::ExtBuilder::default().build(), crate::tests::Test); } diff --git a/frame/collective/src/lib.rs b/frame/collective/src/lib.rs index 0fe05dbb01ac..108409117393 100644 --- a/frame/collective/src/lib.rs +++ b/frame/collective/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,16 +50,20 @@ use sp_std::{marker::PhantomData, prelude::*, result}; use frame_support::{ codec::{Decode, Encode, MaxEncodedLen}, dispatch::{ - DispatchError, DispatchResultWithPostInfo, Dispatchable, GetDispatchInfo, Pays, - PostDispatchInfo, + DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable, GetDispatchInfo, + Pays, PostDispatchInfo, }, - ensure, + ensure, impl_ensure_origin_with_arg_ignoring_arg, traits::{ - Backing, ChangeMembers, EnsureOrigin, Get, GetBacking, InitializeMembers, StorageVersion, + Backing, ChangeMembers, EnsureOrigin, EnsureOriginWithArg, Get, GetBacking, + InitializeMembers, StorageVersion, }, - weights::{OldWeight, Weight}, + weights::Weight, }; +#[cfg(any(feature = "try-runtime", test))] +use sp_runtime::TryRuntimeError; + #[cfg(test)] mod tests; @@ -175,7 +179,6 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] #[pallet::without_storage_info] pub struct Pallet(PhantomData<(T, I)>); @@ -198,7 +201,7 @@ pub mod pallet { + IsType<::RuntimeEvent>; /// The time-out for council motions. - type MotionDuration: Get; + type MotionDuration: Get>; /// Maximum number of proposals allowed to be active in parallel. type MaxProposals: Get; @@ -215,23 +218,25 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + + /// Origin allowed to set collective members + type SetMembersOrigin: EnsureOrigin<::RuntimeOrigin>; + + /// The maximum weight of a dispatch call that can be proposed and executed. + #[pallet::constant] + type MaxProposalWeight: Get; } #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig, I: 'static = ()> { + #[serde(skip)] pub phantom: PhantomData, pub members: Vec, } - #[cfg(feature = "std")] - impl, I: 'static> Default for GenesisConfig { - fn default() -> Self { - Self { phantom: Default::default(), members: Default::default() } - } - } - #[pallet::genesis_build] - impl, I: 'static> GenesisBuild for GenesisConfig { + impl, I: 'static> BuildGenesisConfig for GenesisConfig { fn build(&self) { use sp_std::collections::btree_set::BTreeSet; let members_set: BTreeSet<_> = self.members.iter().collect(); @@ -240,6 +245,10 @@ pub mod pallet { self.members.len(), "Members cannot contain duplicate accounts." ); + assert!( + self.members.len() <= T::MaxMembers::get() as usize, + "Members length cannot exceed MaxMembers.", + ); Pallet::::initialize_members(&self.members) } @@ -265,7 +274,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn voting)] pub type Voting, I: 'static = ()> = - StorageMap<_, Identity, T::Hash, Votes, OptionQuery>; + StorageMap<_, Identity, T::Hash, Votes>, OptionQuery>; /// Proposals so far. #[pallet::storage] @@ -337,6 +346,16 @@ pub mod pallet { WrongProposalWeight, /// The given length bound for the proposal was too low. WrongProposalLength, + /// Prime account is not a member + PrimeAccountNotMember, + } + + #[pallet::hooks] + impl, I: 'static> Hooks> for Pallet { + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumberFor) -> Result<(), TryRuntimeError> { + Self::do_try_state() + } } // Note that councillor operations are assigned to the operational class. @@ -349,7 +368,7 @@ pub mod pallet { /// - `old_count`: The upper bound for the previous number of members in storage. Used for /// weight estimation. /// - /// Requires root origin. + /// The dispatch of this call must be `SetMembersOrigin`. /// /// NOTE: Does not enforce the expected `MaxMembers` limit on the amount of members, but /// the weight estimations rely on it to estimate dispatchable weight. @@ -361,19 +380,11 @@ pub mod pallet { /// Any call to `set_members` must be careful that the member set doesn't get out of sync /// with other logic managing the member set. /// - /// # - /// ## Weight + /// ## Complexity: /// - `O(MP + N)` where: /// - `M` old-members-count (code- and governance-bounded) /// - `N` new-members-count (code- and governance-bounded) /// - `P` proposals-count (code-bounded) - /// - DB: - /// - 1 storage mutation (codec `O(M)` read, `O(N)` write) for reading and writing the - /// members - /// - 1 storage read (codec `O(P)`) for reading the proposals - /// - `P` storage mutations (codec `O(M)`) for updating the votes for each proposal - /// - 1 storage write (codec `O(1)`) for deleting the old `prime` and setting the new one - /// # #[pallet::call_index(0)] #[pallet::weight(( T::WeightInfo::set_members( @@ -389,7 +400,7 @@ pub mod pallet { prime: Option, old_count: MemberCount, ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; + T::SetMembersOrigin::ensure_origin(origin)?; if new_members.len() > T::MaxMembers::get() as usize { log::error!( target: LOG_TARGET, @@ -408,6 +419,9 @@ pub mod pallet { old.len(), ); } + if let Some(p) = &prime { + ensure!(new_members.contains(p), Error::::PrimeAccountNotMember); + } let mut new_members = new_members; new_members.sort(); >::set_members_sorted(&new_members, &old); @@ -425,13 +439,11 @@ pub mod pallet { /// /// Origin must be a member of the collective. /// - /// # - /// ## Weight - /// - `O(M + P)` where `M` members-count (code-bounded) and `P` complexity of dispatching - /// `proposal` - /// - DB: 1 read (codec `O(M)`) + DB access of `proposal` - /// - 1 event - /// # + /// ## Complexity: + /// - `O(B + M + P)` where: + /// - `B` is `proposal` size in bytes (length-fee-bounded) + /// - `M` members-count (code-bounded) + /// - `P` complexity of dispatching `proposal` #[pallet::call_index(1)] #[pallet::weight(( T::WeightInfo::execute( @@ -476,26 +488,13 @@ pub mod pallet { /// `threshold` determines whether `proposal` is executed directly (`threshold < 2`) /// or put up for voting. /// - /// # - /// ## Weight + /// ## Complexity /// - `O(B + M + P1)` or `O(B + M + P2)` where: /// - `B` is `proposal` size in bytes (length-fee-bounded) /// - `M` is members-count (code- and governance-bounded) /// - branching is influenced by `threshold` where: /// - `P1` is proposal execution complexity (`threshold < 2`) /// - `P2` is proposals-count (code-bounded) (`threshold >= 2`) - /// - DB: - /// - 1 storage read `is_member` (codec `O(M)`) - /// - 1 storage read `ProposalOf::contains_key` (codec `O(1)`) - /// - DB accesses influenced by `threshold`: - /// - EITHER storage accesses done by `proposal` (`threshold < 2`) - /// - OR proposal insertion (`threshold <= 2`) - /// - 1 storage mutation `Proposals` (codec `O(P2)`) - /// - 1 storage mutation `ProposalCount` (codec `O(1)`) - /// - 1 storage write `ProposalOf` (codec `O(B)`) - /// - 1 storage write `Voting` (codec `O(M)`) - /// - 1 event - /// # #[pallet::call_index(2)] #[pallet::weight(( if *threshold < 2 { @@ -554,14 +553,8 @@ pub mod pallet { /// Transaction fees will be waived if the member is voting on any particular proposal /// for the first time and the call is successful. Subsequent vote changes will charge a /// fee. - /// # - /// ## Weight + /// ## Complexity /// - `O(M)` where `M` is members-count (code- and governance-bounded) - /// - DB: - /// - 1 storage read `Members` (codec `O(M)`) - /// - 1 storage mutation `Voting` (codec `O(M)`) - /// - 1 event - /// # #[pallet::call_index(3)] #[pallet::weight((T::WeightInfo::vote(T::MaxMembers::get()), DispatchClass::Operational))] pub fn vote( @@ -584,67 +577,7 @@ pub mod pallet { } } - /// Close a vote that is either approved, disapproved or whose voting period has ended. - /// - /// May be called by any signed account in order to finish voting and close the proposal. - /// - /// If called before the end of the voting period it will only close the vote if it is - /// has enough votes to be approved or disapproved. - /// - /// If called after the end of the voting period abstentions are counted as rejections - /// unless there is a prime member set and the prime member cast an approval. - /// - /// If the close operation completes successfully with disapproval, the transaction fee will - /// be waived. Otherwise execution of the approved operation will be charged to the caller. - /// - /// + `proposal_weight_bound`: The maximum amount of weight consumed by executing the closed - /// proposal. - /// + `length_bound`: The upper bound for the length of the proposal in storage. Checked via - /// `storage::read` so it is `size_of::() == 4` larger than the pure length. - /// - /// # - /// ## Weight - /// - `O(B + M + P1 + P2)` where: - /// - `B` is `proposal` size in bytes (length-fee-bounded) - /// - `M` is members-count (code- and governance-bounded) - /// - `P1` is the complexity of `proposal` preimage. - /// - `P2` is proposal-count (code-bounded) - /// - DB: - /// - 2 storage reads (`Members`: codec `O(M)`, `Prime`: codec `O(1)`) - /// - 3 mutations (`Voting`: codec `O(M)`, `ProposalOf`: codec `O(B)`, `Proposals`: codec - /// `O(P2)`) - /// - any mutations done while executing `proposal` (`P1`) - /// - up to 3 events - /// # - #[pallet::call_index(4)] - #[pallet::weight(( - { - let b = *length_bound; - let m = T::MaxMembers::get(); - let p1 = *proposal_weight_bound; - let p2 = T::MaxProposals::get(); - T::WeightInfo::close_early_approved(b, m, p2) - .max(T::WeightInfo::close_early_disapproved(m, p2)) - .max(T::WeightInfo::close_approved(b, m, p2)) - .max(T::WeightInfo::close_disapproved(m, p2)) - .saturating_add(p1.into()) - }, - DispatchClass::Operational - ))] - #[allow(deprecated)] - #[deprecated(note = "1D weight is used in this extrinsic, please migrate to `close`")] - pub fn close_old_weight( - origin: OriginFor, - proposal_hash: T::Hash, - #[pallet::compact] index: ProposalIndex, - #[pallet::compact] proposal_weight_bound: OldWeight, - #[pallet::compact] length_bound: u32, - ) -> DispatchResultWithPostInfo { - let proposal_weight_bound: Weight = proposal_weight_bound.into(); - let _ = ensure_signed(origin)?; - - Self::do_close(proposal_hash, index, proposal_weight_bound, length_bound) - } + // Index 4 was `close_old_weight`; it was removed due to weights v1 deprecation. /// Disapprove a proposal, close, and remove it from the system, regardless of its current /// state. @@ -654,12 +587,8 @@ pub mod pallet { /// Parameters: /// * `proposal_hash`: The hash of the proposal that should be disapproved. /// - /// # - /// Complexity: O(P) where P is the number of max proposals - /// DB Weight: - /// * Reads: Proposals - /// * Writes: Voting, Proposals, ProposalOf - /// # + /// ## Complexity + /// O(P) where P is the number of max proposals #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::disapprove_proposal(T::MaxProposals::get()))] pub fn disapprove_proposal( @@ -689,20 +618,12 @@ pub mod pallet { /// + `length_bound`: The upper bound for the length of the proposal in storage. Checked via /// `storage::read` so it is `size_of::() == 4` larger than the pure length. /// - /// # - /// ## Weight + /// ## Complexity /// - `O(B + M + P1 + P2)` where: /// - `B` is `proposal` size in bytes (length-fee-bounded) /// - `M` is members-count (code- and governance-bounded) /// - `P1` is the complexity of `proposal` preimage. /// - `P2` is proposal-count (code-bounded) - /// - DB: - /// - 2 storage reads (`Members`: codec `O(M)`, `Prime`: codec `O(1)`) - /// - 3 mutations (`Voting`: codec `O(M)`, `ProposalOf`: codec `O(B)`, `Proposals`: codec - /// `O(P2)`) - /// - any mutations done while executing `proposal` (`P1`) - /// - up to 3 events - /// # #[pallet::call_index(6)] #[pallet::weight(( { @@ -757,6 +678,11 @@ impl, I: 'static> Pallet { ) -> Result<(u32, DispatchResultWithPostInfo), DispatchError> { let proposal_len = proposal.encoded_size(); ensure!(proposal_len <= length_bound as usize, Error::::WrongProposalLength); + let proposal_weight = proposal.get_dispatch_info().weight; + ensure!( + proposal_weight.all_lte(T::MaxProposalWeight::get()), + Error::::WrongProposalWeight + ); let proposal_hash = T::Hashing::hash_of(&proposal); ensure!(!>::contains_key(proposal_hash), Error::::DuplicateProposal); @@ -779,6 +705,11 @@ impl, I: 'static> Pallet { ) -> Result<(u32, u32), DispatchError> { let proposal_len = proposal.encoded_size(); ensure!(proposal_len <= length_bound as usize, Error::::WrongProposalLength); + let proposal_weight = proposal.get_dispatch_info().weight; + ensure!( + proposal_weight.all_lte(T::MaxProposalWeight::get()), + Error::::WrongProposalWeight + ); let proposal_hash = T::Hashing::hash_of(&proposal); ensure!(!>::contains_key(proposal_hash), Error::::DuplicateProposal); @@ -1015,6 +946,112 @@ impl, I: 'static> Pallet { }); num_proposals as u32 } + + /// Ensure the correctness of the state of this pallet. + /// + /// The following expectation must always apply. + /// + /// ## Expectations: + /// + /// Looking at proposals: + /// + /// * Each hash of a proposal that is stored inside `Proposals` must have a + /// call mapped to it inside the `ProposalOf` storage map. + /// * `ProposalCount` must always be more or equal to the number of + /// proposals inside the `Proposals` storage value. The reason why + /// `ProposalCount` can be more is because when a proposal is removed the + /// count is not deducted. + /// * Count of `ProposalOf` should match the count of `Proposals` + /// + /// Looking at votes: + /// * The sum of aye and nay votes for a proposal can never exceed + /// `MaxMembers`. + /// * The proposal index inside the `Voting` storage map must be unique. + /// * All proposal hashes inside `Voting` must exist in `Proposals`. + /// + /// Looking at members: + /// * The members count must never exceed `MaxMembers`. + /// * All the members must be sorted by value. + /// + /// Looking at prime account: + /// * The prime account must be a member of the collective. + #[cfg(any(feature = "try-runtime", test))] + fn do_try_state() -> Result<(), TryRuntimeError> { + Self::proposals() + .into_iter() + .try_for_each(|proposal| -> Result<(), TryRuntimeError> { + ensure!( + Self::proposal_of(proposal).is_some(), + "Proposal hash from `Proposals` is not found inside the `ProposalOf` mapping." + ); + Ok(()) + })?; + + ensure!( + Self::proposals().into_iter().count() <= Self::proposal_count() as usize, + "The actual number of proposals is greater than `ProposalCount`" + ); + ensure!( + Self::proposals().into_iter().count() == >::iter_keys().count(), + "Proposal count inside `Proposals` is not equal to the proposal count in `ProposalOf`" + ); + + Self::proposals() + .into_iter() + .try_for_each(|proposal| -> Result<(), TryRuntimeError> { + if let Some(votes) = Self::voting(proposal) { + let ayes = votes.ayes.len(); + let nays = votes.nays.len(); + + ensure!( + ayes.saturating_add(nays) <= T::MaxMembers::get() as usize, + "The sum of ayes and nays is greater than `MaxMembers`" + ); + } + Ok(()) + })?; + + let mut proposal_indices = vec![]; + Self::proposals() + .into_iter() + .try_for_each(|proposal| -> Result<(), TryRuntimeError> { + if let Some(votes) = Self::voting(proposal) { + let proposal_index = votes.index; + ensure!( + !proposal_indices.contains(&proposal_index), + "The proposal index is not unique." + ); + proposal_indices.push(proposal_index); + } + Ok(()) + })?; + + >::iter_keys().try_for_each( + |proposal_hash| -> Result<(), TryRuntimeError> { + ensure!( + Self::proposals().contains(&proposal_hash), + "`Proposals` doesn't contain the proposal hash from the `Voting` storage map." + ); + Ok(()) + }, + )?; + + ensure!( + Self::members().len() <= T::MaxMembers::get() as usize, + "The member count is greater than `MaxMembers`." + ); + + ensure!( + Self::members().windows(2).all(|members| members[0] <= members[1]), + "The members are not sorted by value." + ); + + if let Some(prime) = Self::prime() { + ensure!(Self::members().contains(&prime), "Prime account is not a member."); + } + + Ok(()) + } } impl, I: 'static> ChangeMembers for Pallet { @@ -1023,18 +1060,11 @@ impl, I: 'static> ChangeMembers for Pallet { /// NOTE: Does not enforce the expected `MaxMembers` limit on the amount of members, but /// the weight estimations rely on it to estimate dispatchable weight. /// - /// # - /// ## Weight + /// ## Complexity /// - `O(MP + N)` /// - where `M` old-members-count (governance-bounded) /// - where `N` new-members-count (governance-bounded) /// - where `P` proposals-count - /// - DB: - /// - 1 storage read (codec `O(P)`) for reading the proposals - /// - `P` storage mutations for updating the votes (codec `O(M)`) - /// - 1 storage write (codec `O(N)`) for storing the new members - /// - 1 storage write (codec `O(1)`) for deleting the old prime - /// # fn change_members_sorted( _incoming: &[T::AccountId], outgoing: &[T::AccountId], @@ -1085,6 +1115,8 @@ impl, I: 'static> InitializeMembers for Pallet fn initialize_members(members: &[T::AccountId]) { if !members.is_empty() { assert!(>::get().is_empty(), "Members are already initialized!"); + let mut members = members.to_vec(); + members.sort(); >::put(members); } } @@ -1129,6 +1161,12 @@ impl< } } +impl_ensure_origin_with_arg_ignoring_arg! { + impl< { O: .., I: 'static, AccountId: Decode, T } > + EnsureOriginWithArg for EnsureMember + {} +} + pub struct EnsureMembers(PhantomData<(AccountId, I)>); impl< O: Into, O>> + From>, @@ -1151,6 +1189,12 @@ impl< } } +impl_ensure_origin_with_arg_ignoring_arg! { + impl< { O: .., I: 'static, const N: u32, AccountId, T } > + EnsureOriginWithArg for EnsureMembers + {} +} + pub struct EnsureProportionMoreThan( PhantomData<(AccountId, I)>, ); @@ -1176,6 +1220,12 @@ impl< } } +impl_ensure_origin_with_arg_ignoring_arg! { + impl< { O: .., I: 'static, const N: u32, const D: u32, AccountId, T } > + EnsureOriginWithArg for EnsureProportionMoreThan + {} +} + pub struct EnsureProportionAtLeast( PhantomData<(AccountId, I)>, ); @@ -1200,3 +1250,9 @@ impl< Ok(O::from(RawOrigin::Members(0u32, 0u32))) } } + +impl_ensure_origin_with_arg_ignoring_arg! { + impl< { O: .., I: 'static, const N: u32, const D: u32, AccountId, T } > + EnsureOriginWithArg for EnsureProportionAtLeast + {} +} diff --git a/frame/collective/src/migrations/mod.rs b/frame/collective/src/migrations/mod.rs index 235d0f1c7cf1..2487ed1d5da5 100644 --- a/frame/collective/src/migrations/mod.rs +++ b/frame/collective/src/migrations/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/collective/src/migrations/v4.rs b/frame/collective/src/migrations/v4.rs index 2756b3fd15a6..b3326b4251c9 100644 --- a/frame/collective/src/migrations/v4.rs +++ b/frame/collective/src/migrations/v4.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/collective/src/tests.rs b/frame/collective/src/tests.rs index 5c90de9f91bf..86b85e07a8bd 100644 --- a/frame/collective/src/tests.rs +++ b/frame/collective/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,10 +21,10 @@ use frame_support::{ assert_noop, assert_ok, dispatch::Pays, parameter_types, - traits::{ConstU32, ConstU64, GenesisBuild, StorageVersion}, + traits::{ConstU32, ConstU64, StorageVersion}, Hashable, }; -use frame_system::{EventRecord, Phase}; +use frame_system::{EnsureRoot, EventRecord, Phase}; use sp_core::H256; use sp_runtime::{ testing::Header, @@ -36,10 +36,7 @@ pub type Block = sp_runtime::generic::Block; pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub enum Test { System: frame_system::{Pallet, Call, Event}, Collective: pallet_collective::::{Pallet, Call, Event, Origin, Config}, @@ -51,13 +48,12 @@ frame_support::construct_runtime!( mod mock_democracy { pub use pallet::*; - #[frame_support::pallet] + #[frame_support::pallet(dev_mode)] pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::config] @@ -69,8 +65,6 @@ mod mock_democracy { #[pallet::call] impl Pallet { - #[pallet::call_index(0)] - #[pallet::weight(0)] pub fn external_propose_majority(origin: OriginFor) -> DispatchResult { T::ExternalMajorityOrigin::ensure_origin(origin)?; Self::deposit_event(Event::::ExternalProposed); @@ -87,25 +81,28 @@ mod mock_democracy { } pub type MaxMembers = ConstU32<100>; +type AccountId = u64; parameter_types! { pub const MotionDuration: u64 = 3; pub const MaxProposals: u32 = 257; + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::MAX); + pub static MaxProposalWeight: Weight = default_max_proposal_weight(); } impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = (); + type BlockWeights = BlockWeights; type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; - type AccountId = u64; + type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -127,6 +124,8 @@ impl Config for Test { type MaxMembers = MaxMembers; type DefaultVote = PrimeDefaultVote; type WeightInfo = (); + type SetMembersOrigin = EnsureRoot; + type MaxProposalWeight = MaxProposalWeight; } impl Config for Test { type RuntimeOrigin = RuntimeOrigin; @@ -137,6 +136,8 @@ impl Config for Test { type MaxMembers = MaxMembers; type DefaultVote = MoreThanMajorityThenPrimeDefaultVote; type WeightInfo = (); + type SetMembersOrigin = EnsureRoot; + type MaxProposalWeight = MaxProposalWeight; } impl mock_democracy::Config for Test { type RuntimeEvent = RuntimeEvent; @@ -151,25 +152,51 @@ impl Config for Test { type MaxMembers = MaxMembers; type DefaultVote = PrimeDefaultVote; type WeightInfo = (); + type SetMembersOrigin = EnsureRoot; + type MaxProposalWeight = MaxProposalWeight; } -pub fn new_test_ext() -> sp_io::TestExternalities { - let mut ext: sp_io::TestExternalities = GenesisConfig { - collective: pallet_collective::GenesisConfig { - members: vec![1, 2, 3], - phantom: Default::default(), - }, - collective_majority: pallet_collective::GenesisConfig { - members: vec![1, 2, 3, 4, 5], - phantom: Default::default(), - }, - default_collective: Default::default(), +pub struct ExtBuilder { + collective_members: Vec, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { collective_members: vec![1, 2, 3] } + } +} + +impl ExtBuilder { + fn set_collective_members(mut self, collective_members: Vec) -> Self { + self.collective_members = collective_members; + self + } + + pub fn build(self) -> sp_io::TestExternalities { + let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { + collective: pallet_collective::GenesisConfig { + members: self.collective_members, + phantom: Default::default(), + }, + collective_majority: pallet_collective::GenesisConfig { + members: vec![1, 2, 3, 4, 5], + phantom: Default::default(), + }, + default_collective: Default::default(), + } + .build_storage() + .unwrap() + .into(); + ext.execute_with(|| System::set_block_number(1)); + ext + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + self.build().execute_with(|| { + test(); + Collective::do_try_state().unwrap(); + }) } - .build_storage() - .unwrap() - .into(); - ext.execute_with(|| System::set_block_number(1)); - ext } fn make_proposal(value: u64) -> RuntimeCall { @@ -182,17 +209,81 @@ fn record(event: RuntimeEvent) -> EventRecord { EventRecord { phase: Phase::Initialization, event, topics: vec![] } } +fn default_max_proposal_weight() -> Weight { + sp_runtime::Perbill::from_percent(80) * BlockWeights::get().max_block +} + #[test] fn motions_basic_environment_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { assert_eq!(Collective::members(), vec![1, 2, 3]); assert_eq!(*Collective::proposals(), Vec::::new()); }); } +#[test] +fn initialize_members_sorts_members() { + let unsorted_members = vec![3, 2, 4, 1]; + let expected_members = vec![1, 2, 3, 4]; + ExtBuilder::default() + .set_collective_members(unsorted_members) + .build_and_execute(|| { + assert_eq!(Collective::members(), expected_members); + }); +} + +#[test] +fn set_members_with_prime_works() { + ExtBuilder::default().build_and_execute(|| { + let members = vec![1, 2, 3]; + assert_ok!(Collective::set_members( + RuntimeOrigin::root(), + members.clone(), + Some(3), + MaxMembers::get() + )); + assert_eq!(Collective::members(), members.clone()); + assert_eq!(Collective::prime(), Some(3)); + assert_noop!( + Collective::set_members(RuntimeOrigin::root(), members, Some(4), MaxMembers::get()), + Error::::PrimeAccountNotMember + ); + }); +} + +#[test] +fn proposal_weight_limit_works() { + ExtBuilder::default().build_and_execute(|| { + let proposal = make_proposal(42); + let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); + + assert_ok!(Collective::propose( + RuntimeOrigin::signed(1), + 2, + Box::new(proposal.clone()), + proposal_len + )); + + // set a small limit for max proposal weight. + MaxProposalWeight::set(Weight::from_parts(1, 1)); + assert_noop!( + Collective::propose( + RuntimeOrigin::signed(1), + 2, + Box::new(proposal.clone()), + proposal_len + ), + Error::::WrongProposalWeight + ); + + // reset the max weight to default. + MaxProposalWeight::set(default_max_proposal_weight()); + }); +} + #[test] fn close_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let proposal_weight = proposal.get_dispatch_info().weight; @@ -260,7 +351,7 @@ fn close_works() { #[test] fn proposal_weight_limit_works_on_approve() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = RuntimeCall::Collective(crate::Call::set_members { new_members: vec![1, 2, 3], prime: None, @@ -285,7 +376,7 @@ fn proposal_weight_limit_works_on_approve() { RuntimeOrigin::signed(4), hash, 0, - proposal_weight - Weight::from_ref_time(100), + proposal_weight - Weight::from_parts(100, 0), proposal_len ), Error::::WrongProposalWeight @@ -302,7 +393,7 @@ fn proposal_weight_limit_works_on_approve() { #[test] fn proposal_weight_limit_ignored_on_disapprove() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = RuntimeCall::Collective(crate::Call::set_members { new_members: vec![1, 2, 3], prime: None, @@ -324,7 +415,7 @@ fn proposal_weight_limit_ignored_on_disapprove() { RuntimeOrigin::signed(4), hash, 0, - proposal_weight - Weight::from_ref_time(100), + proposal_weight - Weight::from_parts(100, 0), proposal_len )); }) @@ -332,7 +423,7 @@ fn proposal_weight_limit_ignored_on_disapprove() { #[test] fn close_with_prime_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let proposal_weight = proposal.get_dispatch_info().weight; @@ -400,7 +491,7 @@ fn close_with_prime_works() { #[test] fn close_with_voting_prime_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let proposal_weight = proposal.get_dispatch_info().weight; @@ -470,7 +561,7 @@ fn close_with_voting_prime_works() { #[test] fn close_with_no_prime_but_majority_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let proposal_weight = proposal.get_dispatch_info().weight; @@ -550,7 +641,7 @@ fn close_with_no_prime_but_majority_works() { #[test] fn removal_of_old_voters_votes_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let hash = BlakeTwo256::hash_of(&proposal); @@ -598,7 +689,7 @@ fn removal_of_old_voters_votes_works() { #[test] fn removal_of_old_voters_votes_works_with_set_members() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let hash = BlakeTwo256::hash_of(&proposal); @@ -656,7 +747,7 @@ fn removal_of_old_voters_votes_works_with_set_members() { #[test] fn propose_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let hash = proposal.blake2_256().into(); @@ -688,7 +779,7 @@ fn propose_works() { #[test] fn limit_active_proposals() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { for i in 0..MaxProposals::get() { let proposal = make_proposal(i as u64); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); @@ -715,7 +806,7 @@ fn limit_active_proposals() { #[test] fn correct_validate_and_get_proposal() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = RuntimeCall::Collective(crate::Call::set_members { new_members: vec![1, 2, 3], prime: None, @@ -747,7 +838,7 @@ fn correct_validate_and_get_proposal() { Collective::validate_and_get_proposal( &hash, length, - weight - Weight::from_ref_time(10) + weight - Weight::from_parts(10, 0) ), Error::::WrongProposalWeight ); @@ -761,7 +852,7 @@ fn correct_validate_and_get_proposal() { #[test] fn motions_ignoring_non_collective_proposals_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); assert_noop!( @@ -778,7 +869,7 @@ fn motions_ignoring_non_collective_proposals_works() { #[test] fn motions_ignoring_non_collective_votes_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let hash: H256 = proposal.blake2_256().into(); @@ -797,7 +888,7 @@ fn motions_ignoring_non_collective_votes_works() { #[test] fn motions_ignoring_bad_index_collective_vote_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { System::set_block_number(3); let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); @@ -817,7 +908,7 @@ fn motions_ignoring_bad_index_collective_vote_works() { #[test] fn motions_vote_after_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let hash: H256 = proposal.blake2_256().into(); @@ -886,7 +977,7 @@ fn motions_vote_after_works() { #[test] fn motions_all_first_vote_free_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let hash: H256 = proposal.blake2_256().into(); @@ -944,7 +1035,7 @@ fn motions_all_first_vote_free_works() { #[test] fn motions_reproposing_disapproved_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let proposal_weight = proposal.get_dispatch_info().weight; @@ -976,7 +1067,7 @@ fn motions_reproposing_disapproved_works() { #[test] fn motions_approval_with_enough_votes_and_lower_voting_threshold_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = RuntimeCall::Democracy(mock_democracy::Call::external_propose_majority {}); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let proposal_weight = proposal.get_dispatch_info().weight; @@ -1106,7 +1197,7 @@ fn motions_approval_with_enough_votes_and_lower_voting_threshold_works() { #[test] fn motions_disapproval_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let proposal_weight = proposal.get_dispatch_info().weight; @@ -1165,7 +1256,7 @@ fn motions_disapproval_works() { #[test] fn motions_approval_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let proposal_weight = proposal.get_dispatch_info().weight; @@ -1226,7 +1317,7 @@ fn motions_approval_works() { #[test] fn motion_with_no_votes_closes_with_disapproval() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let proposal_weight = proposal.get_dispatch_info().weight; @@ -1287,7 +1378,7 @@ fn close_disapprove_does_not_care_about_weight_or_len() { // This test confirms that if you close a proposal that would be disapproved, // we do not care about the proposal length or proposal weight since it will // not be read from storage or executed. - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let hash: H256 = proposal.blake2_256().into(); @@ -1319,7 +1410,7 @@ fn close_disapprove_does_not_care_about_weight_or_len() { #[test] fn disapprove_proposal_works() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { let proposal = make_proposal(42); let proposal_len: u32 = proposal.using_encoded(|p| p.len() as u32); let hash: H256 = proposal.blake2_256().into(); @@ -1365,6 +1456,19 @@ fn disapprove_proposal_works() { }) } +#[should_panic(expected = "Members length cannot exceed MaxMembers.")] +#[test] +fn genesis_build_panics_with_too_many_members() { + let max_members: u32 = MaxMembers::get(); + let too_many_members = (1..=max_members as u64 + 1).collect::>(); + pallet_collective::GenesisConfig:: { + members: too_many_members, + phantom: Default::default(), + } + .build_storage() + .unwrap(); +} + #[test] #[should_panic(expected = "Members cannot contain duplicate accounts.")] fn genesis_build_panics_with_duplicate_members() { @@ -1378,7 +1482,7 @@ fn genesis_build_panics_with_duplicate_members() { #[test] fn migration_v4() { - new_test_ext().execute_with(|| { + ExtBuilder::default().build_and_execute(|| { use frame_support::traits::PalletInfoAccess; let old_pallet = "OldCollective"; diff --git a/frame/collective/src/weights.rs b/frame/collective/src/weights.rs index 052550de7bd7..eece6a006b8f 100644 --- a/frame/collective/src/weights.rs +++ b/frame/collective/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_collective //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_collective +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_collective. pub trait WeightInfo { @@ -62,326 +67,492 @@ pub trait WeightInfo { /// Weights for pallet_collective using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Council Members (r:1 w:1) - // Storage: Council Proposals (r:1 w:0) - // Storage: Council Prime (r:0 w:1) - // Storage: Council Voting (r:100 w:100) + /// Storage: Council Members (r:1 w:1) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:0) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Voting (r:100 w:100) + /// Proof Skipped: Council Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Prime (r:0 w:1) + /// Proof Skipped: Council Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[0, 100]`. /// The range of component `n` is `[0, 100]`. /// The range of component `p` is `[0, 100]`. fn set_members(m: u32, _n: u32, p: u32, ) -> Weight { - // Minimum execution time: 18_895 nanoseconds. - Weight::from_ref_time(19_254_000 as u64) - // Standard Error: 63_540 - .saturating_add(Weight::from_ref_time(5_061_801 as u64).saturating_mul(m as u64)) - // Standard Error: 63_540 - .saturating_add(Weight::from_ref_time(7_588_981 as u64).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))) + // Proof Size summary in bytes: + // Measured: `0 + m * (3232 ±0) + p * (3190 ±0)` + // Estimated: `15861 + m * (1967 ±24) + p * (4332 ±24)` + // Minimum execution time: 17_506_000 picoseconds. + Weight::from_parts(17_767_000, 15861) + // Standard Error: 60_220 + .saturating_add(Weight::from_parts(4_374_805, 0).saturating_mul(m.into())) + // Standard Error: 60_220 + .saturating_add(Weight::from_parts(8_398_316, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(p.into()))) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) + .saturating_add(Weight::from_parts(0, 1967).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 4332).saturating_mul(p.into())) } - // Storage: Council Members (r:1 w:0) - /// The range of component `b` is `[1, 1024]`. + /// Storage: Council Members (r:1 w:0) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// The range of component `b` is `[2, 1024]`. /// The range of component `m` is `[1, 100]`. fn execute(b: u32, m: u32, ) -> Weight { - // Minimum execution time: 24_469 nanoseconds. - Weight::from_ref_time(23_961_134 as u64) - // Standard Error: 43 - .saturating_add(Weight::from_ref_time(1_677 as u64).saturating_mul(b as u64)) - // Standard Error: 450 - .saturating_add(Weight::from_ref_time(18_645 as u64).saturating_mul(m as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) + // Proof Size summary in bytes: + // Measured: `202 + m * (32 ±0)` + // Estimated: `1688 + m * (32 ±0)` + // Minimum execution time: 16_203_000 picoseconds. + Weight::from_parts(15_348_267, 1688) + // Standard Error: 37 + .saturating_add(Weight::from_parts(1_766, 0).saturating_mul(b.into())) + // Standard Error: 382 + .saturating_add(Weight::from_parts(15_765, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(m.into())) } - // Storage: Council Members (r:1 w:0) - // Storage: Council ProposalOf (r:1 w:0) - /// The range of component `b` is `[1, 1024]`. + /// Storage: Council Members (r:1 w:0) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council ProposalOf (r:1 w:0) + /// Proof Skipped: Council ProposalOf (max_values: None, max_size: None, mode: Measured) + /// The range of component `b` is `[2, 1024]`. /// The range of component `m` is `[1, 100]`. fn propose_execute(b: u32, m: u32, ) -> Weight { - // Minimum execution time: 26_476 nanoseconds. - Weight::from_ref_time(25_829_298 as u64) - // Standard Error: 49 - .saturating_add(Weight::from_ref_time(1_741 as u64).saturating_mul(b as u64)) - // Standard Error: 515 - .saturating_add(Weight::from_ref_time(29_436 as u64).saturating_mul(m as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) + // Proof Size summary in bytes: + // Measured: `202 + m * (32 ±0)` + // Estimated: `3668 + m * (32 ±0)` + // Minimum execution time: 18_642_000 picoseconds. + Weight::from_parts(17_708_609, 3668) + // Standard Error: 58 + .saturating_add(Weight::from_parts(2_285, 0).saturating_mul(b.into())) + // Standard Error: 598 + .saturating_add(Weight::from_parts(30_454, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(m.into())) } - // Storage: Council Members (r:1 w:0) - // Storage: Council ProposalOf (r:1 w:1) - // Storage: Council Proposals (r:1 w:1) - // Storage: Council ProposalCount (r:1 w:1) - // Storage: Council Voting (r:0 w:1) - /// The range of component `b` is `[1, 1024]`. + /// Storage: Council Members (r:1 w:0) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council ProposalOf (r:1 w:1) + /// Proof Skipped: Council ProposalOf (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:1) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council ProposalCount (r:1 w:1) + /// Proof Skipped: Council ProposalCount (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Voting (r:0 w:1) + /// Proof Skipped: Council Voting (max_values: None, max_size: None, mode: Measured) + /// The range of component `b` is `[2, 1024]`. /// The range of component `m` is `[2, 100]`. /// The range of component `p` is `[1, 100]`. fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 33_585 nanoseconds. - Weight::from_ref_time(33_092_289 as u64) - // Standard Error: 173 - .saturating_add(Weight::from_ref_time(4_266 as u64).saturating_mul(b as u64)) - // Standard Error: 1_812 - .saturating_add(Weight::from_ref_time(29_262 as u64).saturating_mul(m as u64)) - // Standard Error: 1_789 - .saturating_add(Weight::from_ref_time(181_285 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `492 + m * (32 ±0) + p * (36 ±0)` + // Estimated: `3884 + m * (33 ±0) + p * (36 ±0)` + // Minimum execution time: 27_067_000 picoseconds. + Weight::from_parts(25_456_964, 3884) + // Standard Error: 112 + .saturating_add(Weight::from_parts(3_773, 0).saturating_mul(b.into())) + // Standard Error: 1_177 + .saturating_add(Weight::from_parts(32_783, 0).saturating_mul(m.into())) + // Standard Error: 1_162 + .saturating_add(Weight::from_parts(194_388, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(Weight::from_parts(0, 33).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into())) } - // Storage: Council Members (r:1 w:0) - // Storage: Council Voting (r:1 w:1) + /// Storage: Council Members (r:1 w:0) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Voting (r:1 w:1) + /// Proof Skipped: Council Voting (max_values: None, max_size: None, mode: Measured) /// The range of component `m` is `[5, 100]`. fn vote(m: u32, ) -> Weight { - // Minimum execution time: 36_374 nanoseconds. - Weight::from_ref_time(38_950_243 as u64) - // Standard Error: 2_583 - .saturating_add(Weight::from_ref_time(65_345 as u64).saturating_mul(m as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `941 + m * (64 ±0)` + // Estimated: `4405 + m * (64 ±0)` + // Minimum execution time: 26_055_000 picoseconds. + Weight::from_parts(27_251_907, 4405) + // Standard Error: 1_008 + .saturating_add(Weight::from_parts(65_947, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) } - // Storage: Council Voting (r:1 w:1) - // Storage: Council Members (r:1 w:0) - // Storage: Council Proposals (r:1 w:1) - // Storage: Council ProposalOf (r:0 w:1) + /// Storage: Council Voting (r:1 w:1) + /// Proof Skipped: Council Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Members (r:1 w:0) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:1) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council ProposalOf (r:0 w:1) + /// Proof Skipped: Council ProposalOf (max_values: None, max_size: None, mode: Measured) /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. fn close_early_disapproved(m: u32, p: u32, ) -> Weight { - // Minimum execution time: 36_066 nanoseconds. - Weight::from_ref_time(38_439_655 as u64) - // Standard Error: 1_281 - .saturating_add(Weight::from_ref_time(17_045 as u64).saturating_mul(m as u64)) - // Standard Error: 1_249 - .saturating_add(Weight::from_ref_time(164_998 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `530 + m * (64 ±0) + p * (36 ±0)` + // Estimated: `3975 + m * (65 ±0) + p * (36 ±0)` + // Minimum execution time: 28_363_000 picoseconds. + Weight::from_parts(28_733_464, 3975) + // Standard Error: 1_275 + .saturating_add(Weight::from_parts(43_236, 0).saturating_mul(m.into())) + // Standard Error: 1_244 + .saturating_add(Weight::from_parts(180_187, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 65).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into())) } - // Storage: Council Voting (r:1 w:1) - // Storage: Council Members (r:1 w:0) - // Storage: Council ProposalOf (r:1 w:1) - // Storage: Council Proposals (r:1 w:1) - /// The range of component `b` is `[1, 1024]`. + /// Storage: Council Voting (r:1 w:1) + /// Proof Skipped: Council Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Members (r:1 w:0) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council ProposalOf (r:1 w:1) + /// Proof Skipped: Council ProposalOf (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:1) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// The range of component `b` is `[2, 1024]`. /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 47_753 nanoseconds. - Weight::from_ref_time(46_507_829 as u64) - // Standard Error: 149 - .saturating_add(Weight::from_ref_time(2_159 as u64).saturating_mul(b as u64)) - // Standard Error: 1_581 - .saturating_add(Weight::from_ref_time(37_842 as u64).saturating_mul(m as u64)) - // Standard Error: 1_541 - .saturating_add(Weight::from_ref_time(173_395 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `832 + b * (1 ±0) + m * (64 ±0) + p * (40 ±0)` + // Estimated: `4149 + b * (1 ±0) + m * (66 ±0) + p * (40 ±0)` + // Minimum execution time: 40_391_000 picoseconds. + Weight::from_parts(42_695_215, 4149) + // Standard Error: 167 + .saturating_add(Weight::from_parts(3_622, 0).saturating_mul(b.into())) + // Standard Error: 1_772 + .saturating_add(Weight::from_parts(33_830, 0).saturating_mul(m.into())) + // Standard Error: 1_727 + .saturating_add(Weight::from_parts(205_374, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(0, 66).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 40).saturating_mul(p.into())) } - // Storage: Council Voting (r:1 w:1) - // Storage: Council Members (r:1 w:0) - // Storage: Council Prime (r:1 w:0) - // Storage: Council Proposals (r:1 w:1) - // Storage: Council ProposalOf (r:0 w:1) + /// Storage: Council Voting (r:1 w:1) + /// Proof Skipped: Council Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Members (r:1 w:0) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Prime (r:1 w:0) + /// Proof Skipped: Council Prime (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:1) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council ProposalOf (r:0 w:1) + /// Proof Skipped: Council ProposalOf (max_values: None, max_size: None, mode: Measured) /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. fn close_disapproved(m: u32, p: u32, ) -> Weight { - // Minimum execution time: 39_416 nanoseconds. - Weight::from_ref_time(39_610_161 as u64) - // Standard Error: 1_231 - .saturating_add(Weight::from_ref_time(32_991 as u64).saturating_mul(m as u64)) - // Standard Error: 1_200 - .saturating_add(Weight::from_ref_time(170_773 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `550 + m * (64 ±0) + p * (36 ±0)` + // Estimated: `3995 + m * (65 ±0) + p * (36 ±0)` + // Minimum execution time: 31_368_000 picoseconds. + Weight::from_parts(32_141_835, 3995) + // Standard Error: 1_451 + .saturating_add(Weight::from_parts(36_372, 0).saturating_mul(m.into())) + // Standard Error: 1_415 + .saturating_add(Weight::from_parts(210_635, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 65).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into())) } - // Storage: Council Voting (r:1 w:1) - // Storage: Council Members (r:1 w:0) - // Storage: Council Prime (r:1 w:0) - // Storage: Council ProposalOf (r:1 w:1) - // Storage: Council Proposals (r:1 w:1) - /// The range of component `b` is `[1, 1024]`. + /// Storage: Council Voting (r:1 w:1) + /// Proof Skipped: Council Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Members (r:1 w:0) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Prime (r:1 w:0) + /// Proof Skipped: Council Prime (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council ProposalOf (r:1 w:1) + /// Proof Skipped: Council ProposalOf (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:1) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// The range of component `b` is `[2, 1024]`. /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. fn close_approved(b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 49_840 nanoseconds. - Weight::from_ref_time(48_542_914 as u64) - // Standard Error: 136 - .saturating_add(Weight::from_ref_time(2_650 as u64).saturating_mul(b as u64)) - // Standard Error: 1_442 - .saturating_add(Weight::from_ref_time(37_898 as u64).saturating_mul(m as u64)) - // Standard Error: 1_406 - .saturating_add(Weight::from_ref_time(182_176 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `852 + b * (1 ±0) + m * (64 ±0) + p * (40 ±0)` + // Estimated: `4169 + b * (1 ±0) + m * (66 ±0) + p * (40 ±0)` + // Minimum execution time: 43_271_000 picoseconds. + Weight::from_parts(45_495_648, 4169) + // Standard Error: 174 + .saturating_add(Weight::from_parts(3_034, 0).saturating_mul(b.into())) + // Standard Error: 1_840 + .saturating_add(Weight::from_parts(42_209, 0).saturating_mul(m.into())) + // Standard Error: 1_793 + .saturating_add(Weight::from_parts(207_525, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(0, 66).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 40).saturating_mul(p.into())) } - // Storage: Council Proposals (r:1 w:1) - // Storage: Council Voting (r:0 w:1) - // Storage: Council ProposalOf (r:0 w:1) + /// Storage: Council Proposals (r:1 w:1) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Voting (r:0 w:1) + /// Proof Skipped: Council Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Council ProposalOf (r:0 w:1) + /// Proof Skipped: Council ProposalOf (max_values: None, max_size: None, mode: Measured) /// The range of component `p` is `[1, 100]`. fn disapprove_proposal(p: u32, ) -> Weight { - // Minimum execution time: 24_199 nanoseconds. - Weight::from_ref_time(26_869_176 as u64) - // Standard Error: 1_609 - .saturating_add(Weight::from_ref_time(163_341 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `359 + p * (32 ±0)` + // Estimated: `1844 + p * (32 ±0)` + // Minimum execution time: 15_170_000 picoseconds. + Weight::from_parts(17_567_243, 1844) + // Standard Error: 1_430 + .saturating_add(Weight::from_parts(169_040, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(p.into())) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Council Members (r:1 w:1) - // Storage: Council Proposals (r:1 w:0) - // Storage: Council Prime (r:0 w:1) - // Storage: Council Voting (r:100 w:100) + /// Storage: Council Members (r:1 w:1) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:0) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Voting (r:100 w:100) + /// Proof Skipped: Council Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Prime (r:0 w:1) + /// Proof Skipped: Council Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[0, 100]`. /// The range of component `n` is `[0, 100]`. /// The range of component `p` is `[0, 100]`. fn set_members(m: u32, _n: u32, p: u32, ) -> Weight { - // Minimum execution time: 18_895 nanoseconds. - Weight::from_ref_time(19_254_000 as u64) - // Standard Error: 63_540 - .saturating_add(Weight::from_ref_time(5_061_801 as u64).saturating_mul(m as u64)) - // Standard Error: 63_540 - .saturating_add(Weight::from_ref_time(7_588_981 as u64).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))) + // Proof Size summary in bytes: + // Measured: `0 + m * (3232 ±0) + p * (3190 ±0)` + // Estimated: `15861 + m * (1967 ±24) + p * (4332 ±24)` + // Minimum execution time: 17_506_000 picoseconds. + Weight::from_parts(17_767_000, 15861) + // Standard Error: 60_220 + .saturating_add(Weight::from_parts(4_374_805, 0).saturating_mul(m.into())) + // Standard Error: 60_220 + .saturating_add(Weight::from_parts(8_398_316, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(p.into()))) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(p.into()))) + .saturating_add(Weight::from_parts(0, 1967).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 4332).saturating_mul(p.into())) } - // Storage: Council Members (r:1 w:0) - /// The range of component `b` is `[1, 1024]`. + /// Storage: Council Members (r:1 w:0) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// The range of component `b` is `[2, 1024]`. /// The range of component `m` is `[1, 100]`. fn execute(b: u32, m: u32, ) -> Weight { - // Minimum execution time: 24_469 nanoseconds. - Weight::from_ref_time(23_961_134 as u64) - // Standard Error: 43 - .saturating_add(Weight::from_ref_time(1_677 as u64).saturating_mul(b as u64)) - // Standard Error: 450 - .saturating_add(Weight::from_ref_time(18_645 as u64).saturating_mul(m as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) + // Proof Size summary in bytes: + // Measured: `202 + m * (32 ±0)` + // Estimated: `1688 + m * (32 ±0)` + // Minimum execution time: 16_203_000 picoseconds. + Weight::from_parts(15_348_267, 1688) + // Standard Error: 37 + .saturating_add(Weight::from_parts(1_766, 0).saturating_mul(b.into())) + // Standard Error: 382 + .saturating_add(Weight::from_parts(15_765, 0).saturating_mul(m.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(m.into())) } - // Storage: Council Members (r:1 w:0) - // Storage: Council ProposalOf (r:1 w:0) - /// The range of component `b` is `[1, 1024]`. + /// Storage: Council Members (r:1 w:0) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council ProposalOf (r:1 w:0) + /// Proof Skipped: Council ProposalOf (max_values: None, max_size: None, mode: Measured) + /// The range of component `b` is `[2, 1024]`. /// The range of component `m` is `[1, 100]`. fn propose_execute(b: u32, m: u32, ) -> Weight { - // Minimum execution time: 26_476 nanoseconds. - Weight::from_ref_time(25_829_298 as u64) - // Standard Error: 49 - .saturating_add(Weight::from_ref_time(1_741 as u64).saturating_mul(b as u64)) - // Standard Error: 515 - .saturating_add(Weight::from_ref_time(29_436 as u64).saturating_mul(m as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) + // Proof Size summary in bytes: + // Measured: `202 + m * (32 ±0)` + // Estimated: `3668 + m * (32 ±0)` + // Minimum execution time: 18_642_000 picoseconds. + Weight::from_parts(17_708_609, 3668) + // Standard Error: 58 + .saturating_add(Weight::from_parts(2_285, 0).saturating_mul(b.into())) + // Standard Error: 598 + .saturating_add(Weight::from_parts(30_454, 0).saturating_mul(m.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(m.into())) } - // Storage: Council Members (r:1 w:0) - // Storage: Council ProposalOf (r:1 w:1) - // Storage: Council Proposals (r:1 w:1) - // Storage: Council ProposalCount (r:1 w:1) - // Storage: Council Voting (r:0 w:1) - /// The range of component `b` is `[1, 1024]`. + /// Storage: Council Members (r:1 w:0) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council ProposalOf (r:1 w:1) + /// Proof Skipped: Council ProposalOf (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:1) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council ProposalCount (r:1 w:1) + /// Proof Skipped: Council ProposalCount (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Voting (r:0 w:1) + /// Proof Skipped: Council Voting (max_values: None, max_size: None, mode: Measured) + /// The range of component `b` is `[2, 1024]`. /// The range of component `m` is `[2, 100]`. /// The range of component `p` is `[1, 100]`. fn propose_proposed(b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 33_585 nanoseconds. - Weight::from_ref_time(33_092_289 as u64) - // Standard Error: 173 - .saturating_add(Weight::from_ref_time(4_266 as u64).saturating_mul(b as u64)) - // Standard Error: 1_812 - .saturating_add(Weight::from_ref_time(29_262 as u64).saturating_mul(m as u64)) - // Standard Error: 1_789 - .saturating_add(Weight::from_ref_time(181_285 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `492 + m * (32 ±0) + p * (36 ±0)` + // Estimated: `3884 + m * (33 ±0) + p * (36 ±0)` + // Minimum execution time: 27_067_000 picoseconds. + Weight::from_parts(25_456_964, 3884) + // Standard Error: 112 + .saturating_add(Weight::from_parts(3_773, 0).saturating_mul(b.into())) + // Standard Error: 1_177 + .saturating_add(Weight::from_parts(32_783, 0).saturating_mul(m.into())) + // Standard Error: 1_162 + .saturating_add(Weight::from_parts(194_388, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(Weight::from_parts(0, 33).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into())) } - // Storage: Council Members (r:1 w:0) - // Storage: Council Voting (r:1 w:1) + /// Storage: Council Members (r:1 w:0) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Voting (r:1 w:1) + /// Proof Skipped: Council Voting (max_values: None, max_size: None, mode: Measured) /// The range of component `m` is `[5, 100]`. fn vote(m: u32, ) -> Weight { - // Minimum execution time: 36_374 nanoseconds. - Weight::from_ref_time(38_950_243 as u64) - // Standard Error: 2_583 - .saturating_add(Weight::from_ref_time(65_345 as u64).saturating_mul(m as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `941 + m * (64 ±0)` + // Estimated: `4405 + m * (64 ±0)` + // Minimum execution time: 26_055_000 picoseconds. + Weight::from_parts(27_251_907, 4405) + // Standard Error: 1_008 + .saturating_add(Weight::from_parts(65_947, 0).saturating_mul(m.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) } - // Storage: Council Voting (r:1 w:1) - // Storage: Council Members (r:1 w:0) - // Storage: Council Proposals (r:1 w:1) - // Storage: Council ProposalOf (r:0 w:1) + /// Storage: Council Voting (r:1 w:1) + /// Proof Skipped: Council Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Members (r:1 w:0) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:1) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council ProposalOf (r:0 w:1) + /// Proof Skipped: Council ProposalOf (max_values: None, max_size: None, mode: Measured) /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. fn close_early_disapproved(m: u32, p: u32, ) -> Weight { - // Minimum execution time: 36_066 nanoseconds. - Weight::from_ref_time(38_439_655 as u64) - // Standard Error: 1_281 - .saturating_add(Weight::from_ref_time(17_045 as u64).saturating_mul(m as u64)) - // Standard Error: 1_249 - .saturating_add(Weight::from_ref_time(164_998 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `530 + m * (64 ±0) + p * (36 ±0)` + // Estimated: `3975 + m * (65 ±0) + p * (36 ±0)` + // Minimum execution time: 28_363_000 picoseconds. + Weight::from_parts(28_733_464, 3975) + // Standard Error: 1_275 + .saturating_add(Weight::from_parts(43_236, 0).saturating_mul(m.into())) + // Standard Error: 1_244 + .saturating_add(Weight::from_parts(180_187, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 65).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into())) } - // Storage: Council Voting (r:1 w:1) - // Storage: Council Members (r:1 w:0) - // Storage: Council ProposalOf (r:1 w:1) - // Storage: Council Proposals (r:1 w:1) - /// The range of component `b` is `[1, 1024]`. + /// Storage: Council Voting (r:1 w:1) + /// Proof Skipped: Council Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Members (r:1 w:0) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council ProposalOf (r:1 w:1) + /// Proof Skipped: Council ProposalOf (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:1) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// The range of component `b` is `[2, 1024]`. /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. fn close_early_approved(b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 47_753 nanoseconds. - Weight::from_ref_time(46_507_829 as u64) - // Standard Error: 149 - .saturating_add(Weight::from_ref_time(2_159 as u64).saturating_mul(b as u64)) - // Standard Error: 1_581 - .saturating_add(Weight::from_ref_time(37_842 as u64).saturating_mul(m as u64)) - // Standard Error: 1_541 - .saturating_add(Weight::from_ref_time(173_395 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `832 + b * (1 ±0) + m * (64 ±0) + p * (40 ±0)` + // Estimated: `4149 + b * (1 ±0) + m * (66 ±0) + p * (40 ±0)` + // Minimum execution time: 40_391_000 picoseconds. + Weight::from_parts(42_695_215, 4149) + // Standard Error: 167 + .saturating_add(Weight::from_parts(3_622, 0).saturating_mul(b.into())) + // Standard Error: 1_772 + .saturating_add(Weight::from_parts(33_830, 0).saturating_mul(m.into())) + // Standard Error: 1_727 + .saturating_add(Weight::from_parts(205_374, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(0, 66).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 40).saturating_mul(p.into())) } - // Storage: Council Voting (r:1 w:1) - // Storage: Council Members (r:1 w:0) - // Storage: Council Prime (r:1 w:0) - // Storage: Council Proposals (r:1 w:1) - // Storage: Council ProposalOf (r:0 w:1) + /// Storage: Council Voting (r:1 w:1) + /// Proof Skipped: Council Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Members (r:1 w:0) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Prime (r:1 w:0) + /// Proof Skipped: Council Prime (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:1) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council ProposalOf (r:0 w:1) + /// Proof Skipped: Council ProposalOf (max_values: None, max_size: None, mode: Measured) /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. fn close_disapproved(m: u32, p: u32, ) -> Weight { - // Minimum execution time: 39_416 nanoseconds. - Weight::from_ref_time(39_610_161 as u64) - // Standard Error: 1_231 - .saturating_add(Weight::from_ref_time(32_991 as u64).saturating_mul(m as u64)) - // Standard Error: 1_200 - .saturating_add(Weight::from_ref_time(170_773 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `550 + m * (64 ±0) + p * (36 ±0)` + // Estimated: `3995 + m * (65 ±0) + p * (36 ±0)` + // Minimum execution time: 31_368_000 picoseconds. + Weight::from_parts(32_141_835, 3995) + // Standard Error: 1_451 + .saturating_add(Weight::from_parts(36_372, 0).saturating_mul(m.into())) + // Standard Error: 1_415 + .saturating_add(Weight::from_parts(210_635, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 65).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 36).saturating_mul(p.into())) } - // Storage: Council Voting (r:1 w:1) - // Storage: Council Members (r:1 w:0) - // Storage: Council Prime (r:1 w:0) - // Storage: Council ProposalOf (r:1 w:1) - // Storage: Council Proposals (r:1 w:1) - /// The range of component `b` is `[1, 1024]`. + /// Storage: Council Voting (r:1 w:1) + /// Proof Skipped: Council Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Members (r:1 w:0) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Prime (r:1 w:0) + /// Proof Skipped: Council Prime (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council ProposalOf (r:1 w:1) + /// Proof Skipped: Council ProposalOf (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:1) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// The range of component `b` is `[2, 1024]`. /// The range of component `m` is `[4, 100]`. /// The range of component `p` is `[1, 100]`. fn close_approved(b: u32, m: u32, p: u32, ) -> Weight { - // Minimum execution time: 49_840 nanoseconds. - Weight::from_ref_time(48_542_914 as u64) - // Standard Error: 136 - .saturating_add(Weight::from_ref_time(2_650 as u64).saturating_mul(b as u64)) - // Standard Error: 1_442 - .saturating_add(Weight::from_ref_time(37_898 as u64).saturating_mul(m as u64)) - // Standard Error: 1_406 - .saturating_add(Weight::from_ref_time(182_176 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `852 + b * (1 ±0) + m * (64 ±0) + p * (40 ±0)` + // Estimated: `4169 + b * (1 ±0) + m * (66 ±0) + p * (40 ±0)` + // Minimum execution time: 43_271_000 picoseconds. + Weight::from_parts(45_495_648, 4169) + // Standard Error: 174 + .saturating_add(Weight::from_parts(3_034, 0).saturating_mul(b.into())) + // Standard Error: 1_840 + .saturating_add(Weight::from_parts(42_209, 0).saturating_mul(m.into())) + // Standard Error: 1_793 + .saturating_add(Weight::from_parts(207_525, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(0, 66).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 40).saturating_mul(p.into())) } - // Storage: Council Proposals (r:1 w:1) - // Storage: Council Voting (r:0 w:1) - // Storage: Council ProposalOf (r:0 w:1) + /// Storage: Council Proposals (r:1 w:1) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Voting (r:0 w:1) + /// Proof Skipped: Council Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Council ProposalOf (r:0 w:1) + /// Proof Skipped: Council ProposalOf (max_values: None, max_size: None, mode: Measured) /// The range of component `p` is `[1, 100]`. fn disapprove_proposal(p: u32, ) -> Weight { - // Minimum execution time: 24_199 nanoseconds. - Weight::from_ref_time(26_869_176 as u64) - // Standard Error: 1_609 - .saturating_add(Weight::from_ref_time(163_341 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `359 + p * (32 ±0)` + // Estimated: `1844 + p * (32 ±0)` + // Minimum execution time: 15_170_000 picoseconds. + Weight::from_parts(17_567_243, 1844) + // Standard Error: 1_430 + .saturating_add(Weight::from_parts(169_040, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(p.into())) } } diff --git a/frame/contracts/CHANGELOG.md b/frame/contracts/CHANGELOG.md index ab3998e6dc4f..dcb9d6d4d2b2 100644 --- a/frame/contracts/CHANGELOG.md +++ b/frame/contracts/CHANGELOG.md @@ -20,6 +20,9 @@ In other words: Upgrading this pallet will not break pre-existing contracts. ### Added +- Forbid calling back to contracts after switching to runtime +[#13443](https://github.com/paritytech/substrate/pull/13443) + - Allow contracts to dispatch calls into the runtime (**unstable**) [#9276](https://github.com/paritytech/substrate/pull/9276) diff --git a/frame/contracts/Cargo.toml b/frame/contracts/Cargo.toml index 0ee9e36f1dc7..f23e44741a6c 100644 --- a/frame/contracts/Cargo.toml +++ b/frame/contracts/Cargo.toml @@ -15,39 +15,39 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] bitflags = "1.3" -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", "max-encoded-len", ] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } log = { version = "0.4", default-features = false } -wasm-instrument = { version = "0.4", default-features = false } serde = { version = "1", optional = true, features = ["derive"] } smallvec = { version = "1", default-features = false, features = [ "const_generics", ] } -wasmi = { version = "0.20", default-features = false } -wasmparser = { package = "wasmparser-nostd", version = "0.91", default-features = false } +wasmi = { version = "0.30", default-features = false } impl-trait-for-tuples = "0.2" -# Only used in benchmarking to generate random contract code +# Only used in benchmarking to generate contract code +wasm-instrument = { version = "0.4", optional = true, default-features = false } rand = { version = "0.8", optional = true, default-features = false } rand_pcg = { version = "0.3", optional = true } # Substrate Dependencies +environmental = { version = "1.1.4", default-features = false } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -pallet-contracts-primitives = { version = "7.0.0", default-features = false, path = "primitives" } +pallet-contracts-primitives = { version = "24.0.0", default-features = false, path = "primitives" } pallet-contracts-proc-macro = { version = "4.0.0-dev", path = "proc-macro" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -array-bytes = "4.1" +array-bytes = "6.1" assert_matches = "1" env_logger = "0.9" pretty_assertions = "1" @@ -56,9 +56,10 @@ wat = "1" # Substrate Dependencies pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } -pallet-randomness-collective-flip = { version = "4.0.0-dev", path = "../randomness-collective-flip" } +pallet-insecure-randomness-collective-flip = { version = "4.0.0-dev", path = "../insecure-randomness-collective-flip" } pallet-utility = { version = "4.0.0-dev", path = "../utility" } -sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } +pallet-proxy = { version = "4.0.0-dev", path = "../proxy" } +sp-keystore = { version = "0.27.0", path = "../../primitives/keystore" } [features] default = ["std"] @@ -79,11 +80,12 @@ std = [ "pallet-contracts-proc-macro/full", "log/std", "rand/std", - "wasmparser/std", + "environmental/std", ] runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "rand", "rand_pcg", + "wasm-instrument", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/contracts/README.md b/frame/contracts/README.md index 4df7d9449682..2b5fec6ca53d 100644 --- a/frame/contracts/README.md +++ b/frame/contracts/README.md @@ -1,11 +1,11 @@ -# Contract Module +# Contracts Module -The Contract module provides functionality for the runtime to deploy and execute WebAssembly smart-contracts. +The Contracts module provides functionality for the runtime to deploy and execute WebAssembly smart-contracts. - [`Call`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/enum.Call.html) - [`Config`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/trait.Config.html) - [`Error`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/enum.Error.html) -- [`Event`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/enum.Error.html) +- [`Event`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/enum.Event.html) ## Overview @@ -13,8 +13,8 @@ This module extends accounts based on the `Currency` trait to have smart-contrac be used with other modules that implement accounts based on `Currency`. These "smart-contract accounts" have the ability to instantiate smart-contracts and make calls to other contract and non-contract accounts. -The smart-contract code is stored once in a `code_cache`, and later retrievable via its `code_hash`. -This means that multiple smart-contracts can be instantiated from the same `code_cache`, without replicating +The smart-contract code is stored once, and later retrievable via its `code_hash`. +This means that multiple smart-contracts can be instantiated from the same `code`, without replicating the code each time. When a smart-contract is called, its associated code is retrieved via the code hash and gets executed. @@ -24,18 +24,17 @@ or call other smart-contracts. Finally, when an account is reaped, its associated code and storage of the smart-contract account will also be deleted. -### Gas +### Weight -Senders must specify a gas limit with every call, as all instructions invoked by the smart-contract require gas. -Unused gas is refunded after the call, regardless of the execution outcome. +Senders must specify a [`Weight`](https://paritytech.github.io/substrate/master/sp_weights/struct.Weight.html) limit with every call, as all instructions invoked by the smart-contract require weight. +Unused weight is refunded after the call, regardless of the execution outcome. -If the gas limit is reached, then all calls and state changes (including balance transfers) are only -reverted at the current call's contract level. For example, if contract A calls B and B runs out of gas mid-call, +If the weight limit is reached, then all calls and state changes (including balance transfers) are only +reverted at the current call's contract level. For example, if contract A calls B and B runs out of weight mid-call, then all of B's calls are reverted. Assuming correct error handling by contract A, A's other calls and state changes still persist. -One gas is equivalent to one [weight](https://docs.substrate.io/v3/runtime/weights-and-fees) -which is defined as one picosecond of execution time on the runtime's reference machine. +One `ref_time` `Weight` is defined as one picosecond of execution time on the runtime's reference machine. ### Revert Behaviour @@ -43,29 +42,26 @@ Contract call failures are not cascading. When failures occur in a sub-call, the and the call will only revert at the specific contract level. For example, if contract A calls contract B, and B fails, A can decide how to handle that failure, either proceeding or reverting A's changes. -### Offchain Execution +### Off-chain Execution In general, a contract execution needs to be deterministic so that all nodes come to the same conclusion when executing it. To that end we disallow any instructions that could cause indeterminism. Most notable are any floating point arithmetic. That said, sometimes contracts are executed off-chain and hence are not subject to consensus. If code is only executed by a single node and implicitly trusted by other actors is such a case. Trusted execution environments -come to mind. To that end we allow the execution of indeterminstic code for offchain usages +come to mind. To that end we allow the execution of indeterminstic code for off-chain usages with the following constraints: 1. No contract can ever be instantiated from an indeterministic code. The only way to execute the code is to use a delegate call from a deterministic contract. -2. The code that wants to use this feature needs to depend on `pallet-contracts` and use `bare_call` +2. The code that wants to use this feature needs to depend on `pallet-contracts` and use [`bare_call()`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/struct.Pallet.html#method.bare_call) directly. This makes sure that by default `pallet-contracts` does not expose any indeterminism. -## How to use +#### How to use -When setting up the `Schedule` for your runtime make sure to set `InstructionWeights::fallback` -to a non zero value. The default is `0` and prevents the upload of any non deterministic code. - -An indeterministic code can be deployed on-chain by passing `Determinism::AllowIndeterministic` -to `upload_code`. A determinstic contract can then delegate call into it if and only if it -is ran by using `bare_call` and passing `Determinism::AllowIndeterministic` to it. **Never use +An indeterministic code can be deployed on-chain by passing `Determinism::Relaxed` +to [`upload_code()`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/struct.Pallet.html#method.upload_code). A deterministic contract can then delegate call into it if and only if it +is ran by using [`bare_call()`](https://paritytech.github.io/substrate/master/pallet_contracts/pallet/struct.Pallet.html#method.bare_call) and passing [`Determinism::Relaxed`](https://paritytech.github.io/substrate/master/pallet_contracts/enum.Determinism.html#variant.Relaxed) to it. **Never use this argument when the contract is called from an on-chain transaction.** ## Interface @@ -99,24 +95,22 @@ Each contract is one WebAssembly module that looks like this: ``` The documentation of all importable functions can be found -[here](https://github.com/paritytech/substrate/blob/master/frame/contracts/src/wasm/runtime.rs). -Look for the `define_env!` macro invocation. +[here](https://paritytech.github.io/substrate/master/pallet_contracts/api_doc/trait.Current.html). ## Usage This module executes WebAssembly smart contracts. These can potentially be written in any language -that compiles to web assembly. However, using a language that specifically targets this module -will make things a lot easier. One such language is [`ink`](https://github.com/paritytech/ink) -which is an [`eDSL`](https://wiki.haskell.org/Embedded_domain_specific_language) that enables -writing WebAssembly based smart contracts in the Rust programming language. +that compiles to Wasm. However, using a language that specifically targets this module +will make things a lot easier. One such language is [`ink!`](https://use.ink). It enables +writing WebAssembly-based smart-contracts in the Rust programming language. ## Debugging -Contracts can emit messages to the client when called as RPC through the `seal_debug_message` -API. This is exposed in ink! via +Contracts can emit messages to the client when called as RPC through the [`debug_message`](https://paritytech.github.io/substrate/master/pallet_contracts/api_doc/trait.Current.html#tymethod.debug_message) +API. This is exposed in [ink!](https://use.ink) via [`ink_env::debug_message()`](https://paritytech.github.io/ink/ink_env/fn.debug_message.html). -Those messages are gathered into an internal buffer and send to the RPC client. +Those messages are gathered into an internal buffer and sent to the RPC client. It is up the the individual client if and how those messages are presented to the user. This buffer is also printed as a debug message. In order to see these messages on the node @@ -135,6 +129,18 @@ to `error` in order to prevent them from spamming the console. `--dev`: Use a dev chain spec `--tmp`: Use temporary storage for chain data (the chain state is deleted on exit) +## Host function tracing + +For contract authors, it can be a helpful debugging tool to see which host functions are called, with which arguments, and what the result was. + +In order to see these messages on the node console, the log level for the `runtime::contracts::strace` target needs to be raised to the `trace` level. + +Example: + +```bash +cargo run --release -- --dev -lerror,runtime::contracts::strace=trace,runtime::contracts=debug +``` + ## Unstable Interfaces Driven by the desire to have an iterative approach in developing new contract interfaces @@ -142,11 +148,11 @@ this pallet contains the concept of an unstable interface. Akin to the rust nigh it allows us to add new interfaces but mark them as unstable so that contract languages can experiment with them and give feedback before we stabilize those. -In order to access interfaces marked as `#[unstable]` in `runtime.rs` one need to set -`pallet_contracts::Config::UnsafeUnstableInterface` to `ConstU32`. It should be obvious +In order to access interfaces marked as `#[unstable]` in [`runtime.rs`](src/wasm/runtime.rs) one need to set +`pallet_contracts::Config::UnsafeUnstableInterface` to `ConstU32`. **It should be obvious that any production runtime should never be compiled with this feature: In addition to be subject to change or removal those interfaces might not have proper weights associated with -them and are therefore considered unsafe. +them and are therefore considered unsafe**. New interfaces are generally added as unstable and might go through several iterations before they are promoted to a stable interface. diff --git a/frame/contracts/benchmarks/README.md b/frame/contracts/benchmarks/README.md index a4b15bd840db..a621dd65d593 100644 --- a/frame/contracts/benchmarks/README.md +++ b/frame/contracts/benchmarks/README.md @@ -1,6 +1,6 @@ # Benchmarks -This directory contains real world (ink!, solang) contracts which are used in macro benchmarks. +This directory contains real world ([ink!](https://use.ink), [solang](https://github.com/hyperledger/solang)) contracts which are used in macro benchmarks. Those benchmarks are not used to determine weights but rather to compare different contract languages and execution engines with larger wasm modules. diff --git a/frame/contracts/fixtures/call.wat b/frame/contracts/fixtures/call.wat new file mode 100644 index 000000000000..4558b2c6409b --- /dev/null +++ b/frame/contracts/fixtures/call.wat @@ -0,0 +1,39 @@ +;; This calls another contract as passed as its account id. +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal1" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "deploy")) + + (func (export "call") + ;; Store length of input buffer. + (i32.store (i32.const 0) (i32.const 512)) + + ;; Copy input at address 4. + (call $seal_input (i32.const 4) (i32.const 0)) + + ;; Call passed contract. + (call $assert (i32.eqz + (call $seal_call + (i32.const 0) ;; No flags + (i32.const 8) ;; Pointer to "callee" address. + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 512) ;; Pointer to the buffer with value to transfer + (i32.const 4) ;; Pointer to input data buffer address + (i32.const 4) ;; Length of input data buffer + (i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + ) + )) + ) +) diff --git a/frame/contracts/fixtures/call_runtime_and_call.wat b/frame/contracts/fixtures/call_runtime_and_call.wat new file mode 100644 index 000000000000..3320922d9e2c --- /dev/null +++ b/frame/contracts/fixtures/call_runtime_and_call.wat @@ -0,0 +1,56 @@ +(module + (import "seal0" "call_runtime" (func $call_runtime (param i32 i32) (result i32))) + (import "seal1" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32))) + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + + (func $assert (param i32) + (block $ok + (br_if $ok (get_local 0)) + (unreachable) + ) + ) + + (func (export "call") + ;; Store available input size at offset 0. + (i32.store (i32.const 0) (i32.const 512)) + + ;; read input data + (call $seal_input (i32.const 4) (i32.const 0)) + + ;; Input data layout. + ;; [0..4) - size of the call + ;; [4..8) - how many bytes to add to storage + ;; [8..40) - address of the callee + ;; [40..n) - encoded runtime call + + ;; Invoke call_runtime with the encoded call passed to this contract. + (call $assert (i32.eqz + (call $call_runtime + (i32.const 40) ;; Pointer where the call is stored + (i32.sub + (i32.load (i32.const 0)) ;; Size of the call + (i32.const 36) ;; Subtract size of the subcall-related part: 4 bytes for storage length to add + 32 bytes of the callee address + ) + ) + )) + + ;; call passed contract + (call $assert (i32.eqz + (call $seal_call + (i32.const 0) ;; No flags + (i32.const 8) ;; Pointer to "callee" address. + (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 512) ;; Pointer to the buffer with value to transfer + (i32.const 4) ;; Pointer to input data buffer address + (i32.const 4) ;; Length of input data buffer + (i32.const 4294967295) ;; u32 max value is the sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + ) + )) + ) + + (func (export "deploy")) +) + diff --git a/frame/contracts/fixtures/call_with_limit.wat b/frame/contracts/fixtures/call_with_limit.wat index abb870826727..04da59551a8c 100644 --- a/frame/contracts/fixtures/call_with_limit.wat +++ b/frame/contracts/fixtures/call_with_limit.wat @@ -1,8 +1,8 @@ -;; This expects [account_id, gas_limit] as input and calls the account_id with the supplied gas_limit. +;; This expects [account_id, ref_time, proof_size] as input and calls the account_id with the supplied 2D Weight limit. ;; It returns the result of the call as output data. (module (import "seal0" "seal_input" (func $seal_input (param i32 i32))) - (import "seal0" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32))) + (import "seal2" "call" (func $seal_call (param i32 i32 i64 i64 i32 i32 i32 i32 i32 i32) (result i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) @@ -13,24 +13,25 @@ (func (export "deploy")) (func (export "call") - ;; Receive the encoded call + gas_limit + ;; Receive the encoded account_id, ref_time, proof_size (call $seal_input (i32.const 4) ;; Pointer to the input buffer - (i32.const 0) ;; Size of the length buffer + (i32.const 0) ;; Pointer to the length of the input buffer ) (i32.store (i32.const 0) (call $seal_call + (i32.const 0) ;; Set no flag. (i32.const 4) ;; Pointer to "callee" address. - (i32.const 32) ;; Length of "callee" address. - (i64.load (i32.const 36)) ;; How much gas to devote for the execution. + (i64.load (i32.const 36)) ;; How much ref_time to devote for the execution. + (i64.load (i32.const 44)) ;; How much proof_size to devote for the execution. + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. (i32.const 0) ;; Pointer to the buffer with value to transfer - (i32.const 0) ;; Length of the buffer with value to transfer. (i32.const 0) ;; Pointer to input data buffer address - (i32.const 0) ;; Length of input data buffer + (i32.const 0) ;; Length of input data buffer (i32.const 0xffffffff) ;; u32 max sentinel value: do not copy output - (i32.const 0) ;; Ptr to output buffer len - ) + (i32.const 0) ;; Length is ignored in this case + ) ) (call $seal_return (i32.const 0) (i32.const 0) (i32.const 4)) ) diff --git a/frame/contracts/fixtures/caller_contract.wat b/frame/contracts/fixtures/caller_contract.wat index 9c7cdf62abfc..929171b9a26f 100644 --- a/frame/contracts/fixtures/caller_contract.wat +++ b/frame/contracts/fixtures/caller_contract.wat @@ -1,9 +1,9 @@ (module (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_balance" (func $seal_balance (param i32 i32))) - (import "seal0" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32 i32) (result i32))) - (import "seal0" "seal_instantiate" (func $seal_instantiate - (param i32 i32 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32) + (import "seal2" "call" (func $seal_call (param i32 i32 i64 i64 i32 i32 i32 i32 i32 i32) (result i32))) + (import "seal2" "instantiate" (func $seal_instantiate + (param i32 i64 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32) )) (import "env" "memory" (memory 1 1)) @@ -16,27 +16,11 @@ ) ) - (func $current_balance (param $sp i32) (result i64) - (i32.store - (i32.sub (get_local $sp) (i32.const 16)) - (i32.const 8) - ) - (call $seal_balance - (i32.sub (get_local $sp) (i32.const 8)) - (i32.sub (get_local $sp) (i32.const 16)) - ) - (call $assert - (i32.eq (i32.load (i32.sub (get_local $sp) (i32.const 16))) (i32.const 8)) - ) - (i64.load (i32.sub (get_local $sp) (i32.const 8))) - ) - (func (export "deploy")) (func (export "call") (local $sp i32) (local $exit_code i32) - (local $balance i64) ;; Length of the buffer (i32.store (i32.const 20) (i32.const 32)) @@ -54,26 +38,23 @@ ;; Read current balance into local variable. (set_local $sp (i32.const 1024)) - (set_local $balance - (call $current_balance (get_local $sp)) - ) ;; Fail to deploy the contract since it returns a non-zero exit status. (set_local $exit_code (call $seal_instantiate (i32.const 24) ;; Pointer to the code hash. - (i32.const 32) ;; Length of the code hash. - (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. + (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. (i32.const 0) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer. (i32.const 9) ;; Pointer to input data buffer address (i32.const 7) ;; Length of input data buffer - (i32.const 4294967295) ;; u32 max sentinel value: do not copy address - (i32.const 0) ;; Length is ignored in this case + (i32.const 4294967295) ;; u32 max sentinel value: do not copy address + (i32.const 0) ;; Length is ignored in this case (i32.const 4294967295) ;; u32 max sentinel value: do not copy output - (i32.const 0) ;; Length is ignored in this case - (i32.const 0) ;; salt_ptr - (i32.const 0) ;; salt_le + (i32.const 0) ;; Length is ignored in this case + (i32.const 0) ;; salt_ptr + (i32.const 0) ;; salt_le ) ) @@ -82,27 +63,22 @@ (i32.eq (get_local $exit_code) (i32.const 2)) ;; ReturnCode::CalleeReverted ) - ;; Check that balance has not changed. - (call $assert - (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) - ) - - ;; Fail to deploy the contract due to insufficient gas. + ;; Fail to deploy the contract due to insufficient ref_time weight. (set_local $exit_code (call $seal_instantiate (i32.const 24) ;; Pointer to the code hash. - (i32.const 32) ;; Length of the code hash. - (i64.const 1) ;; Supply too little gas + (i64.const 1) ;; Supply too little ref_time weight + (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. (i32.const 0) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer. (i32.const 8) ;; Pointer to input data buffer address (i32.const 8) ;; Length of input data buffer (i32.const 4294967295) ;; u32 max sentinel value: do not copy address - (i32.const 0) ;; Length is ignored in this case + (i32.const 0) ;; Length is ignored in this case (i32.const 4294967295) ;; u32 max sentinel value: do not copy output - (i32.const 0) ;; Length is ignored in this case - (i32.const 0) ;; salt_ptr - (i32.const 0) ;; salt_le + (i32.const 0) ;; Length is ignored in this case + (i32.const 0) ;; salt_ptr + (i32.const 0) ;; salt_le ) ) @@ -112,9 +88,29 @@ (i32.eq (get_local $exit_code) (i32.const 1)) ;; ReturnCode::CalleeTrapped ) - ;; Check that balance has not changed. + ;; Fail to deploy the contract due to insufficient ref_time weight. + (set_local $exit_code + (call $seal_instantiate + (i32.const 24) ;; Pointer to the code hash. + (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. + (i64.const 1) ;; Supply too little proof_size weight + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Pointer to input data buffer address + (i32.const 8) ;; Length of input data buffer + (i32.const 4294967295) ;; u32 max sentinel value: do not copy address + (i32.const 0) ;; Length is ignored in this case + (i32.const 4294967295) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + (i32.const 0) ;; salt_ptr + (i32.const 0) ;; salt_le + + ) + ) + + ;; Check for special trap exit status. (call $assert - (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) + (i32.eq (get_local $exit_code) (i32.const 1)) ;; ReturnCode::CalleeTrapped ) ;; Length of the output buffer @@ -127,18 +123,18 @@ (set_local $exit_code (call $seal_instantiate (i32.const 24) ;; Pointer to the code hash. - (i32.const 32) ;; Length of the code hash. - (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. + (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. (i32.const 0) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer. (i32.const 8) ;; Pointer to input data buffer address (i32.const 8) ;; Length of input data buffer - (i32.const 16) ;; Pointer to the address output buffer - (i32.sub (get_local $sp) (i32.const 4)) ;; Pointer to the address buffer length - (i32.const 4294967295) ;; u32 max sentinel value: do not copy output - (i32.const 0) ;; Length is ignored in this case - (i32.const 0) ;; salt_ptr - (i32.const 0) ;; salt_le + (i32.const 16) ;; Pointer to the address output buffer + (i32.sub (get_local $sp) (i32.const 4)) ;; Pointer to the address buffer length + (i32.const 4294967295) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + (i32.const 0) ;; salt_ptr + (i32.const 0) ;; salt_le ) ) @@ -153,14 +149,6 @@ (i32.eq (i32.load (i32.sub (get_local $sp) (i32.const 4))) (i32.const 32)) ) - ;; Check that balance has been deducted. - (set_local $balance - (i64.sub (get_local $balance) (i64.load (i32.const 0))) - ) - (call $assert - (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) - ) - ;; Zero out destination buffer of output (i32.store (i32.sub (get_local $sp) (i32.const 4)) @@ -176,15 +164,16 @@ ;; Call the new contract and expect it to return failing exit code. (set_local $exit_code (call $seal_call + (i32.const 0) ;; Set no flag (i32.const 16) ;; Pointer to "callee" address. - (i32.const 32) ;; Length of "callee" address. - (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. + (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. (i32.const 0) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer. (i32.const 9) ;; Pointer to input data buffer address (i32.const 7) ;; Length of input data buffer - (i32.sub (get_local $sp) (i32.const 4)) ;; Ptr to output buffer - (i32.sub (get_local $sp) (i32.const 8)) ;; Ptr to output buffer len + (i32.sub (get_local $sp) (i32.const 4)) ;; Ptr to output buffer + (i32.sub (get_local $sp) (i32.const 8)) ;; Ptr to output buffer len ) ) @@ -204,23 +193,19 @@ ) ) - ;; Check that balance has not changed. - (call $assert - (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) - ) - - ;; Fail to call the contract due to insufficient gas. + ;; Fail to call the contract due to insufficient ref_time weight. (set_local $exit_code (call $seal_call + (i32.const 0) ;; Set no flag (i32.const 16) ;; Pointer to "callee" address. - (i32.const 32) ;; Length of "callee" address. - (i64.const 1) ;; Supply too little gas + (i64.const 1) ;; Supply too little ref_time weight + (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. (i32.const 0) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer. (i32.const 8) ;; Pointer to input data buffer address (i32.const 8) ;; Length of input data buffer - (i32.const 4294967295) ;; u32 max sentinel value: do not copy output - (i32.const 0) ;; Length is ignored in this cas + (i32.const 4294967295) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this cas ) ) @@ -229,9 +214,25 @@ (i32.eq (get_local $exit_code) (i32.const 1)) ;; ReturnCode::CalleeTrapped ) - ;; Check that balance has not changed. + ;; Fail to call the contract due to insufficient proof_size weight. + (set_local $exit_code + (call $seal_call + (i32.const 0) ;; Set no flag + (i32.const 16) ;; Pointer to "callee" address. + (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. + (i64.const 1) ;; Supply too little proof_size weight + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. + (i32.const 0) ;; Pointer to the buffer with value to transfer + (i32.const 8) ;; Pointer to input data buffer address + (i32.const 8) ;; Length of input data buffer + (i32.const 4294967295) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this cas + ) + ) + + ;; Check for special trap exit status. (call $assert - (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) + (i32.eq (get_local $exit_code) (i32.const 1)) ;; ReturnCode::CalleeTrapped ) ;; Zero out destination buffer of output @@ -249,15 +250,16 @@ ;; Call the contract successfully. (set_local $exit_code (call $seal_call + (i32.const 0) ;; Set no flag (i32.const 16) ;; Pointer to "callee" address. - (i32.const 32) ;; Length of "callee" address. - (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. + (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. + (i32.const 0xffffffff) ;; u32 max sentinel value: pass no deposit limit. (i32.const 0) ;; Pointer to the buffer with value to transfer - (i32.const 8) ;; Length of the buffer with value to transfer. (i32.const 8) ;; Pointer to input data buffer address (i32.const 8) ;; Length of input data buffer - (i32.sub (get_local $sp) (i32.const 4)) ;; Ptr to output buffer - (i32.sub (get_local $sp) (i32.const 8)) ;; Ptr to output buffer len + (i32.sub (get_local $sp) (i32.const 4)) ;; Ptr to output buffer + (i32.sub (get_local $sp) (i32.const 8)) ;; Ptr to output buffer len ) ) @@ -276,14 +278,6 @@ (i32.const 0x77665544) ) ) - - ;; Check that balance has been deducted. - (set_local $balance - (i64.sub (get_local $balance) (i64.load (i32.const 0))) - ) - (call $assert - (i64.eq (get_local $balance) (call $current_balance (get_local $sp))) - ) ) (data (i32.const 0) "\00\80") ;; The value to transfer on instantiation and calls. diff --git a/frame/contracts/fixtures/create_storage_and_call.wat b/frame/contracts/fixtures/create_storage_and_call.wat index 2a1e53f7ce08..5592e7e96a98 100644 --- a/frame/contracts/fixtures/create_storage_and_call.wat +++ b/frame/contracts/fixtures/create_storage_and_call.wat @@ -2,7 +2,7 @@ (module (import "seal0" "seal_input" (func $seal_input (param i32 i32))) (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32))) - (import "seal1" "seal_call" (func $seal_call (param i32 i32 i64 i32 i32 i32 i32 i32) (result i32))) + (import "seal2" "call" (func $seal_call (param i32 i32 i64 i64 i32 i32 i32 i32 i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) (func $assert (param i32) @@ -20,7 +20,10 @@ ;; store length of input buffer (i32.store (i32.const 0) (i32.const 512)) - ;; copy input at address 4 + ;; copy input at address 4: + ;; first 4 bytes for the size of the storage to be created in callee + ;; next 32 bytes are for the callee address + ;; next bytes for the encoded deposit limit (call $seal_input (i32.const 4) (i32.const 0)) ;; create 4 byte of storage before calling @@ -34,8 +37,10 @@ (call $assert (i32.eqz (call $seal_call (i32.const 0) ;; No flags - (i32.const 8) ;; Pointer to "callee" address. - (i64.const 0) ;; How much gas to devote for the execution. 0 = all. + (i32.const 8) ;; Pointer to "callee" address + (i64.const 0) ;; How much ref_time to devote for the execution. 0 = all + (i64.const 0) ;; How much proof_limit to devote for the execution. 0 = all + (i32.const 40) ;; Pointer to the storage deposit limit (i32.const 512) ;; Pointer to the buffer with value to transfer (i32.const 4) ;; Pointer to input data buffer address (i32.const 4) ;; Length of input data buffer diff --git a/frame/contracts/fixtures/create_storage_and_instantiate.wat b/frame/contracts/fixtures/create_storage_and_instantiate.wat new file mode 100644 index 000000000000..cd7202478437 --- /dev/null +++ b/frame/contracts/fixtures/create_storage_and_instantiate.wat @@ -0,0 +1,66 @@ +;; This instantiates another contract and passes some input to its constructor. +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32))) + (import "seal2" "instantiate" (func $seal_instantiate + (param i32 i64 i64 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32) (result i32) + )) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 8) send 10_000 balance + (data (i32.const 48) "\10\27\00\00\00\00\00\00") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "deploy")) + + (func (export "call") + ;; store length of input buffer + (i32.store (i32.const 0) (i32.const 512)) + ;; store length of contract address + (i32.store (i32.const 84) (i32.const 32)) + + ;; copy input at address 4 + (call $seal_input (i32.const 4) (i32.const 0)) + + ;; memory layout is: + ;; [0,4): size of input buffer + ;; [4,8): size of the storage to be created in callee + ;; [8,40): the code hash of the contract to instantiate + ;; [40,48): for the encoded deposit limit + ;; [48,52): value to transfer + ;; [52,84): address of the deployed contract + ;; [84,88): len of the address + + ;; instantiate a contract + (call $assert (i32.eqz +;; (i32.store +;; (i32.const 64) + (call $seal_instantiate + (i32.const 8) ;; Pointer to the code hash. + (i64.const 0) ;; How much ref_time weight to devote for the execution. 0 = all. + (i64.const 0) ;; How much proof_size weight to devote for the execution. 0 = all. + (i32.const 40) ;; Pointer to the storage deposit limit + (i32.const 48) ;; Pointer to the buffer with value to transfer + (i32.const 4) ;; Pointer to input data buffer address + (i32.const 4) ;; Length of input data buffer + (i32.const 52) ;; Pointer to where to copy address + (i32.const 84) ;; Pointer to address len ptr + (i32.const 0xffffffff) ;; u32 max sentinel value: do not copy output + (i32.const 0) ;; Length is ignored in this case + (i32.const 0) ;; salt_ptr + (i32.const 0) ;; salt_len + ) + )) + ;; return the deployed contract address + (call $seal_return (i32.const 0) (i32.const 52) (i32.const 32)) + ) +) diff --git a/frame/contracts/fixtures/debug_message_invalid_utf8.wat b/frame/contracts/fixtures/debug_message_invalid_utf8.wat index c60371076440..e8c447b42fca 100644 --- a/frame/contracts/fixtures/debug_message_invalid_utf8.wat +++ b/frame/contracts/fixtures/debug_message_invalid_utf8.wat @@ -1,18 +1,28 @@ -;; Emit a "Hello World!" debug message +;; Emit a debug message with an invalid utf-8 code (module (import "seal0" "seal_debug_message" (func $seal_debug_message (param i32 i32) (result i32))) (import "env" "memory" (memory 1 1)) (data (i32.const 0) "\fc") - (func (export "call") - (call $seal_debug_message - (i32.const 0) ;; Pointer to the text buffer - (i32.const 12) ;; The size of the buffer + (func $assert_eq (param i32 i32) + (block $ok + (br_if $ok + (i32.eq (get_local 0) (get_local 1)) + ) + (unreachable) ) - ;; the above call traps because we supplied invalid utf8 - unreachable ) + (func (export "call") + (call $assert_eq + (call $seal_debug_message + (i32.const 0) ;; Pointer to the text buffer + (i32.const 12) ;; The size of the buffer + ) + (i32.const 0) ;; Success return code + ) + ) + (func (export "deploy")) ) diff --git a/frame/contracts/fixtures/debug_message_logging_disabled.wat b/frame/contracts/fixtures/debug_message_logging_disabled.wat index cfe238943ad0..fc6ee72df8b0 100644 --- a/frame/contracts/fixtures/debug_message_logging_disabled.wat +++ b/frame/contracts/fixtures/debug_message_logging_disabled.wat @@ -20,7 +20,7 @@ (i32.const 0) ;; Pointer to the text buffer (i32.const 12) ;; The size of the buffer ) - (i32.const 9) ;; LoggingDisabled return code + (i32.const 0) ;; Success return code ) ) diff --git a/frame/contracts/fixtures/drain.wat b/frame/contracts/fixtures/drain.wat index 94c651842266..9f126898fac8 100644 --- a/frame/contracts/fixtures/drain.wat +++ b/frame/contracts/fixtures/drain.wat @@ -34,8 +34,7 @@ ) ;; Try to self-destruct by sending full balance to the 0 address. - ;; All the *free* balance will be send away, which is a valid thing to do - ;; because the storage deposits will keep the account alive. + ;; The call will fail because a contract transfer has a keep alive requirement (call $assert (i32.eq (call $seal_transfer @@ -44,7 +43,7 @@ (i32.const 0) ;; Pointer to the buffer with value to transfer (i32.const 8) ;; Length of the buffer with value to transfer ) - (i32.const 0) ;; ReturnCode::Success + (i32.const 5) ;; ReturnCode::TransferFailed ) ) ) diff --git a/frame/contracts/fixtures/dummy.wat b/frame/contracts/fixtures/dummy.wat index 0aeefbcb7ebf..a6435e49df22 100644 --- a/frame/contracts/fixtures/dummy.wat +++ b/frame/contracts/fixtures/dummy.wat @@ -1,5 +1,6 @@ ;; A valid contract which does nothing at all (module + (import "env" "memory" (memory 1 1)) (func (export "deploy")) (func (export "call")) ) diff --git a/frame/contracts/fixtures/float_instruction.wat b/frame/contracts/fixtures/float_instruction.wat index c19b5c12cdce..efa6b9de52de 100644 --- a/frame/contracts/fixtures/float_instruction.wat +++ b/frame/contracts/fixtures/float_instruction.wat @@ -1,5 +1,6 @@ ;; Module that contains a float instruction which is illegal in deterministic mode (module + (import "env" "memory" (memory 1 1)) (func (export "call") f32.const 1 drop diff --git a/frame/contracts/fixtures/invalid_contract.wat b/frame/contracts/fixtures/invalid_contract_no_call.wat similarity index 68% rename from frame/contracts/fixtures/invalid_contract.wat rename to frame/contracts/fixtures/invalid_contract_no_call.wat index 085569000c55..34f7c99ba85e 100644 --- a/frame/contracts/fixtures/invalid_contract.wat +++ b/frame/contracts/fixtures/invalid_contract_no_call.wat @@ -1,4 +1,5 @@ ;; Valid module but missing the call function (module + (import "env" "memory" (memory 1 1)) (func (export "deploy")) ) diff --git a/frame/contracts/fixtures/invalid_contract_no_memory.wat b/frame/contracts/fixtures/invalid_contract_no_memory.wat new file mode 100644 index 000000000000..0aeefbcb7ebf --- /dev/null +++ b/frame/contracts/fixtures/invalid_contract_no_memory.wat @@ -0,0 +1,5 @@ +;; A valid contract which does nothing at all +(module + (func (export "deploy")) + (func (export "call")) +) diff --git a/frame/contracts/fixtures/run_out_of_gas.wat b/frame/contracts/fixtures/run_out_of_gas.wat index 52ee92539fd5..fe53e92c4fa8 100644 --- a/frame/contracts/fixtures/run_out_of_gas.wat +++ b/frame/contracts/fixtures/run_out_of_gas.wat @@ -1,4 +1,5 @@ (module + (import "env" "memory" (memory 1 1)) (func (export "call") (loop $inf (br $inf)) ;; just run out of gas (unreachable) diff --git a/frame/contracts/fixtures/seal_input_noop.wat b/frame/contracts/fixtures/seal_input_noop.wat new file mode 100644 index 000000000000..7b5a1e32af4d --- /dev/null +++ b/frame/contracts/fixtures/seal_input_noop.wat @@ -0,0 +1,14 @@ +;; Everything prepared for the host function call, but no call is performed. +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 8) buffer to write input + + ;; [8, 12) size of the input buffer + (data (i32.const 8) "\04") + + (func (export "call")) + + (func (export "deploy")) +) diff --git a/frame/contracts/fixtures/seal_input_once.wat b/frame/contracts/fixtures/seal_input_once.wat new file mode 100644 index 000000000000..919a03a9b690 --- /dev/null +++ b/frame/contracts/fixtures/seal_input_once.wat @@ -0,0 +1,22 @@ +;; Stores a value of the passed size. The host function is called once. +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 8) buffer to write input + + ;; [8, 12) size of the input buffer + (data (i32.const 8) "\04") + + (func (export "call") + ;; instructions to consume engine fuel + (drop + (i32.const 42) + ) + + (call $seal_input (i32.const 0) (i32.const 8)) + + ) + + (func (export "deploy")) +) diff --git a/frame/contracts/fixtures/seal_input_twice.wat b/frame/contracts/fixtures/seal_input_twice.wat new file mode 100644 index 000000000000..3a8be814efb0 --- /dev/null +++ b/frame/contracts/fixtures/seal_input_twice.wat @@ -0,0 +1,28 @@ +;; Stores a value of the passed size. The host function is called twice. +(module + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 8) buffer to write input + + ;; [8, 12) size of the input buffer + (data (i32.const 8) "\04") + + (func (export "call") + ;; instructions to consume engine fuel + (drop + (i32.const 42) + ) + + (call $seal_input (i32.const 0) (i32.const 8)) + + ;; instructions to consume engine fuel + (drop + (i32.const 42) + ) + + (call $seal_input (i32.const 0) (i32.const 8)) + ) + + (func (export "deploy")) +) diff --git a/frame/contracts/fixtures/sr25519_verify.wat b/frame/contracts/fixtures/sr25519_verify.wat new file mode 100644 index 000000000000..2da1ceb87eab --- /dev/null +++ b/frame/contracts/fixtures/sr25519_verify.wat @@ -0,0 +1,55 @@ +;; This contract: +;; 1) Reads signature, message and public key from the input +;; 2) Calls and return the result of sr25519_verify + +(module + ;; import the host functions from the seal0 module + (import "seal0" "sr25519_verify" (func $sr25519_verify (param i32 i32 i32 i32) (result i32))) + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + + ;; give the program 1 page of memory + (import "env" "memory" (memory 1 1)) + + ;; [0, 4) length of signature + message + public key - 64 + 11 + 32 = 107 bytes + ;; write the length of the input (6b = 107) bytes at offset 0 + (data (i32.const 0) "\6b") + + (func (export "deploy")) + + (func (export "call") + ;; define local variables + (local $signature_ptr i32) + (local $pub_key_ptr i32) + (local $message_len i32) + (local $message_ptr i32) + + ;; set the pointers to the memory locations + ;; Memory layout during `call` + ;; [10, 74) signature + ;; [74, 106) public key + ;; [106, 117) message (11 bytes) + (local.set $signature_ptr (i32.const 10)) + (local.set $pub_key_ptr (i32.const 74)) + (local.set $message_ptr (i32.const 106)) + + ;; store the input into the memory, starting at the signature and + ;; up to 107 bytes stored at offset 0 + (call $seal_input (local.get $signature_ptr) (i32.const 0)) + + ;; call sr25519_verify and store the return code + (i32.store + (i32.const 0) + (call $sr25519_verify + (local.get $signature_ptr) + (local.get $pub_key_ptr) + (i32.const 11) + (local.get $message_ptr) + ) + ) + + ;; exit with success and take transfer return code to the output buffer + (call $seal_return (i32.const 0) (i32.const 0) (i32.const 4)) + ) +) + diff --git a/frame/contracts/fixtures/store.wat b/frame/contracts/fixtures/store_call.wat similarity index 100% rename from frame/contracts/fixtures/store.wat rename to frame/contracts/fixtures/store_call.wat diff --git a/frame/contracts/fixtures/store_deploy.wat b/frame/contracts/fixtures/store_deploy.wat new file mode 100644 index 000000000000..cc428e9623bf --- /dev/null +++ b/frame/contracts/fixtures/store_deploy.wat @@ -0,0 +1,45 @@ +;; Stores a value of the passed size in constructor. +(module + (import "seal0" "seal_set_storage" (func $seal_set_storage (param i32 i32 i32))) + (import "seal0" "seal_input" (func $seal_input (param i32 i32))) + (import "env" "memory" (memory 16 16)) + + ;; [0, 32) storage key + (data (i32.const 0) "\01") + + ;; [32, 36) buffer where input is copied (expected size of storage item) + + ;; [36, 40) size of the input buffer + (data (i32.const 36) "\04") + + (func $assert (param i32) + (block $ok + (br_if $ok + (get_local 0) + ) + (unreachable) + ) + ) + + (func (export "deploy") + (call $seal_input (i32.const 32) (i32.const 36)) + + ;; assert input size == 4 + (call $assert + (i32.eq + (i32.load (i32.const 36)) + (i32.const 4) + ) + ) + + ;; place a value in storage, the size of which is specified by the call input. + ;; we don't care about the contents of the storage item + (call $seal_set_storage + (i32.const 0) ;; Pointer to storage key + (i32.const 0) ;; Pointer to value + (i32.load (i32.const 32)) ;; Size of value + ) + ) + + (func (export "call")) +) diff --git a/frame/contracts/primitives/Cargo.toml b/frame/contracts/primitives/Cargo.toml index aafcd912af65..2a22810e8e9f 100644 --- a/frame/contracts/primitives/Cargo.toml +++ b/frame/contracts/primitives/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-contracts-primitives" -version = "7.0.0" +version = "24.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -14,12 +14,13 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] bitflags = "1.0" -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } # Substrate Dependencies (This crate should not rely on frame) -sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-weights = { version = "4.0.0", default-features = false, path = "../../../primitives/weights" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-weights = { version = "20.0.0", default-features = false, path = "../../../primitives/weights" } [features] default = ["std"] @@ -27,4 +28,5 @@ std = [ "codec/std", "sp-runtime/std", "sp-std/std", + "scale-info/std", ] diff --git a/frame/contracts/primitives/src/lib.rs b/frame/contracts/primitives/src/lib.rs index 4faea9eb3ee7..ddd97d99b2f5 100644 --- a/frame/contracts/primitives/src/lib.rs +++ b/frame/contracts/primitives/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +21,7 @@ use bitflags::bitflags; use codec::{Decode, Encode}; +use scale_info::TypeInfo; use sp_runtime::{ traits::{Saturating, Zero}, DispatchError, RuntimeDebug, @@ -28,11 +29,18 @@ use sp_runtime::{ use sp_std::prelude::*; use sp_weights::Weight; -/// Result type of a `bare_call` or `bare_instantiate` call. +/// Result type of a `bare_call` or `bare_instantiate` call as well as `ContractsApi::call` and +/// `ContractsApi::instantiate`. /// /// It contains the execution result together with some auxiliary information. -#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)] -pub struct ContractResult { +/// +/// #Note +/// +/// It has been extended to include `events` at the end of the struct while not bumping the +/// `ContractsApi` version. Therefore when SCALE decoding a `ContractResult` its trailing data +/// should be ignored to avoid any potential compatibility issues. +#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ContractResult { /// How much weight was consumed during execution. pub gas_consumed: Weight, /// How much weight is required as gas limit in order to execute this call. @@ -46,10 +54,12 @@ pub struct ContractResult { /// Additionally, any `seal_call` or `seal_instantiate` makes use of pre-charging /// when a non-zero `gas_limit` argument is supplied. pub gas_required: Weight, - /// How much balance was deposited and reserved during execution in order to pay for storage. + /// How much balance was paid by the origin into the contract's deposit account in order to + /// pay for storage. /// - /// The storage deposit is never actually charged from the caller in case of [`Self::result`] - /// is `Err`. This is because on error all storage changes are rolled back. + /// The storage deposit is never actually charged from the origin in case of [`Self::result`] + /// is `Err`. This is because on error all storage changes are rolled back including the + /// payment of the deposit. pub storage_deposit: StorageDeposit, /// An optional debug message. This message is only filled when explicitly requested /// by the code that calls into the contract. Otherwise it is empty. @@ -68,15 +78,18 @@ pub struct ContractResult { pub debug_message: Vec, /// The execution result of the wasm code. pub result: R, + /// The events that were emitted during execution. It is an option as event collection is + /// optional. + pub events: Option>, } -/// Result type of a `bare_call` call. -pub type ContractExecResult = - ContractResult, Balance>; +/// Result type of a `bare_call` call as well as `ContractsApi::call`. +pub type ContractExecResult = + ContractResult, Balance, EventRecord>; -/// Result type of a `bare_instantiate` call. -pub type ContractInstantiateResult = - ContractResult, DispatchError>, Balance>; +/// Result type of a `bare_instantiate` call as well as `ContractsApi::instantiate`. +pub type ContractInstantiateResult = + ContractResult, DispatchError>, Balance, EventRecord>; /// Result type of a `bare_code_upload` call. pub type CodeUploadResult = @@ -86,17 +99,19 @@ pub type CodeUploadResult = pub type GetStorageResult = Result>, ContractAccessError>; /// The possible errors that can happen querying the storage of a contract. -#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)] +#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum ContractAccessError { /// The given address doesn't point to a contract. DoesntExist, /// Storage key cannot be decoded from the provided input data. KeyDecodingFailed, + /// Storage is migrating. Try again later. + MigrationInProgress, } bitflags! { /// Flags used by a contract to customize exit behaviour. - #[derive(Encode, Decode)] + #[derive(Encode, Decode, TypeInfo)] pub struct ReturnFlags: u32 { /// If this bit is set all changes made by the contract execution are rolled back. const REVERT = 0x0000_0001; @@ -104,7 +119,7 @@ bitflags! { } /// Output of a contract call or instantiation which ran to completion. -#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug)] +#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct ExecReturnValue { /// Flags passed along by `seal_return`. Empty when `seal_return` was never called. pub flags: ReturnFlags, @@ -120,7 +135,7 @@ impl ExecReturnValue { } /// The result of a successful contract instantiation. -#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug)] +#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct InstantiateReturnValue { /// The output of the called constructor. pub result: ExecReturnValue, @@ -128,8 +143,8 @@ pub struct InstantiateReturnValue { pub account_id: AccountId, } -/// The result of succesfully uploading a contract. -#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug)] +/// The result of successfully uploading a contract. +#[derive(PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct CodeUploadReturnValue { /// The key under which the new code is stored. pub code_hash: CodeHash, @@ -138,7 +153,7 @@ pub struct CodeUploadReturnValue { } /// Reference to an existing code hash or a new wasm module. -#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug)] +#[derive(Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum Code { /// A wasm module as raw bytes. Upload(Vec), @@ -146,24 +161,18 @@ pub enum Code { Existing(Hash), } -impl>, Hash> From for Code { - fn from(from: T) -> Self { - Code::Upload(from.into()) - } -} - /// The amount of balance that was either charged or refunded in order to pay for storage. -#[derive(Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, Clone)] +#[derive(Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, Clone, TypeInfo)] pub enum StorageDeposit { /// The transaction reduced storage consumption. /// /// This means that the specified amount of balance was transferred from the involved - /// contracts to the call origin. + /// deposit accounts to the origin. Refund(Balance), - /// The transaction increased overall storage usage. + /// The transaction increased storage consumption. /// - /// This means that the specified amount of balance was transferred from the call origin - /// to the contracts involved. + /// This means that the specified amount of balance was transferred from the origin + /// to the involved deposit accounts. Charge(Balance), } @@ -236,7 +245,7 @@ where } } - /// If the amount of deposit (this type) is constrained by a `limit` this calcuates how + /// If the amount of deposit (this type) is constrained by a `limit` this calculates how /// much balance (if any) is still available from this limit. /// /// # Note diff --git a/frame/contracts/proc-macro/Cargo.toml b/frame/contracts/proc-macro/Cargo.toml index 08dafcc6a8fc..8a63875f2a98 100644 --- a/frame/contracts/proc-macro/Cargo.toml +++ b/frame/contracts/proc-macro/Cargo.toml @@ -15,9 +15,9 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -proc-macro2 = "1" -quote = "1" -syn = { version = "1.0.98", features = ["full"] } +proc-macro2 = "1.0.56" +quote = "1.0.28" +syn = { version = "2.0.16", features = ["full"] } [dev-dependencies] diff --git a/frame/contracts/proc-macro/src/lib.rs b/frame/contracts/proc-macro/src/lib.rs index de7b9b881d30..b31403c29adf 100644 --- a/frame/contracts/proc-macro/src/lib.rs +++ b/frame/contracts/proc-macro/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Procedural macroses used in the contracts module. +//! Procedural macros used in the contracts module. //! //! Most likely you should use the [`#[define_env]`][`macro@define_env`] attribute macro which hides //! boilerplate of defining external environment for a wasm module. @@ -25,10 +25,12 @@ extern crate alloc; use alloc::{ + collections::BTreeMap, format, string::{String, ToString}, vec::Vec, }; +use core::cmp::Reverse; use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{quote, quote_spanned, ToTokens}; @@ -160,7 +162,7 @@ struct EnvDef { /// Parsed host function definition. struct HostFn { item: syn::ItemFn, - module: String, + version: u8, name: String, returns: HostFnReturn, is_stable: bool, @@ -207,21 +209,20 @@ impl HostFn { "only #[version()], #[unstable], #[prefixed_alias] and #[deprecated] attributes are allowed."; let span = item.span(); let mut attrs = item.attrs.clone(); - attrs.retain(|a| !a.path.is_ident("doc")); - let mut maybe_module = None; + attrs.retain(|a| !a.path().is_ident("doc")); + let mut maybe_version = None; let mut is_stable = true; let mut alias_to = None; let mut not_deprecated = true; while let Some(attr) = attrs.pop() { - let ident = attr.path.get_ident().ok_or(err(span, msg))?.to_string(); + let ident = attr.path().get_ident().ok_or(err(span, msg))?.to_string(); match ident.as_str() { "version" => { - if maybe_module.is_some() { + if maybe_version.is_some() { return Err(err(span, "#[version] can only be specified once")) } - let ver: u8 = - attr.parse_args::().and_then(|lit| lit.base10_parse())?; - maybe_module = Some(format!("seal{}", ver)); + maybe_version = + Some(attr.parse_args::().and_then(|lit| lit.base10_parse())?); }, "unstable" => { if !is_stable { @@ -341,7 +342,7 @@ impl HostFn { Ok(Self { item, - module: maybe_module.unwrap_or_else(|| "seal0".to_string()), + version: maybe_version.unwrap_or_default(), name, returns, is_stable, @@ -355,6 +356,10 @@ impl HostFn { _ => Err(err(span, &msg)), } } + + fn module(&self) -> String { + format!("seal{}", self.version) + } } impl EnvDef { @@ -372,7 +377,7 @@ impl EnvDef { _ => None, }; - let selector = |a: &syn::Attribute| a.path.is_ident("prefixed_alias"); + let selector = |a: &syn::Attribute| a.path().is_ident("prefixed_alias"); let aliases = items .iter() @@ -396,7 +401,7 @@ impl EnvDef { } fn is_valid_special_arg(idx: usize, arg: &FnArg) -> bool { - let pat = if let FnArg::Typed(pat) = arg { pat } else { return false }; + let FnArg::Typed(pat) = arg else { return false }; let ident = if let syn::Pat::Ident(ref ident) = *pat.pat { &ident.ident } else { return false }; let name_ok = match idx { 0 => ident == "ctx" || ident == "_ctx", @@ -409,109 +414,143 @@ fn is_valid_special_arg(idx: usize, arg: &FnArg) -> bool { matches!(*pat.ty, syn::Type::Infer(_)) } -/// Expands documentation for host functions. -fn expand_docs(def: &mut EnvDef) -> TokenStream2 { - let mut modules = def.host_funcs.iter().map(|f| f.module.clone()).collect::>(); - modules.sort(); - modules.dedup(); - - let doc_selector = |a: &syn::Attribute| a.path.is_ident("doc"); - let docs = modules.iter().map(|m| { - let funcs = def.host_funcs.iter_mut().map(|f| { - if *m == f.module { - // Remove auxiliary args: `ctx: _` and `memory: _` - f.item.sig.inputs = f - .item - .sig - .inputs - .iter() - .skip(2) - .map(|p| p.clone()) - .collect::>(); - let func_decl = f.item.sig.to_token_stream(); - let func_doc = if let Some(origin_fn) = &f.alias_to { - let alias_doc = format!( - "This is just an alias function to [`{0}()`][`Self::{0}`] with backwards-compatible prefixed identifier.", - origin_fn, - ); - quote! { #[doc = #alias_doc] } - - } else { - let func_docs = f.item.attrs.iter().filter(|a| doc_selector(a)).map(|d| { - let docs = d.to_token_stream(); - quote! { #docs } - }); - let unstable_notice = if !f.is_stable { - let warning = "\n # Unstable\n\n \ - This function is unstable and it is a subject to change (or removal) in the future.\n \ - Do not deploy a contract using it to a production chain."; - quote! { #[doc = #warning] } - } else { - quote! {} - }; - quote! { - #( #func_docs )* - #unstable_notice - } - }; - quote! { - #func_doc - #func_decl; - } - } else { - quote! {} - } - }); +fn expand_func_doc(func: &HostFn) -> TokenStream2 { + // Remove auxiliary args: `ctx: _` and `memory: _` + let func_decl = { + let mut sig = func.item.sig.clone(); + sig.inputs = sig + .inputs + .iter() + .skip(2) + .map(|p| p.clone()) + .collect::>(); + sig.to_token_stream() + }; + let func_doc = { + let func_docs = if let Some(origin_fn) = &func.alias_to { + let alias_doc = format!( + "This is just an alias function to [`{0}()`][`Self::{0}`] with backwards-compatible prefixed identifier.", + origin_fn, + ); + quote! { #[doc = #alias_doc] } + } else { + let docs = func.item.attrs.iter().filter(|a| a.path().is_ident("doc")).map(|d| { + let docs = d.to_token_stream(); + quote! { #docs } + }); + quote! { #( #docs )* } + }; + let deprecation_notice = if !func.not_deprecated { + let warning = "\n # Deprecated\n\n \ + This function is deprecated and will be removed in future versions.\n \ + No new code or contracts with this API can be deployed."; + quote! { #[doc = #warning] } + } else { + quote! {} + }; + let import_notice = { + let info = format!( + "\n# Wasm Import Statement\n```wat\n(import \"seal{}\" \"{}\" (func ...))\n```", + func.version, func.name, + ); + quote! { #[doc = #info] } + }; + let unstable_notice = if !func.is_stable { + let warning = "\n # Unstable\n\n \ + This function is unstable and it is a subject to change (or removal) in the future.\n \ + Do not deploy a contract using it to a production chain."; + quote! { #[doc = #warning] } + } else { + quote! {} + }; + quote! { + #deprecation_notice + #func_docs + #import_notice + #unstable_notice + } + }; + quote! { + #func_doc + #func_decl; + } +} - let module = Ident::new(m, Span::call_site()); - let module_doc = format!( - "Documentation of the API available to contracts by importing `{}` WASM module.", - module - ); +/// Expands documentation for host functions. +fn expand_docs(def: &EnvDef) -> TokenStream2 { + // Create the `Current` trait with only the newest versions + // we sort so that only the newest versions make it into `docs` + let mut current_docs = BTreeMap::new(); + let mut funcs: Vec<_> = def.host_funcs.iter().filter(|f| f.alias_to.is_none()).collect(); + funcs.sort_unstable_by_key(|func| Reverse(func.version)); + for func in funcs { + if current_docs.contains_key(&func.name) { + continue + } + current_docs.insert(func.name.clone(), expand_func_doc(&func)); + } + let current_docs = current_docs.values(); + // Create the `legacy` module with all functions + // Maps from version to list of functions that have this version + let mut legacy_doc = BTreeMap::>::new(); + for func in def.host_funcs.iter() { + legacy_doc.entry(func.version).or_default().push(expand_func_doc(&func)); + } + let legacy_doc = legacy_doc.into_iter().map(|(version, funcs)| { + let doc = format!("All functions available in the **seal{}** module", version); + let version = Ident::new(&format!("Version{version}"), Span::call_site()); quote! { - #[doc = #module_doc] - pub mod #module { - use crate::wasm::runtime::{TrapReason, ReturnCode}; - /// Every function in this trait represents (at least) one function that can be imported by a contract. - /// - /// The function's identifier is to be set as the name in the import definition. - /// Where it is specifically indicated, an _alias_ function having `seal_`-prefixed identifier and - /// just the same signature and body, is also available (for backwards-compatibility purposes). - pub trait Api { - #( #funcs )* - } + #[doc = #doc] + pub trait #version { + #( #funcs )* } } }); + quote! { - #( #docs )* + /// Contains only the latest version of each function. + /// + /// In reality there are more functions available but they are all obsolete: When a function + /// is updated a new **version** is added and the old versions stays available as-is. + /// We only list the newest version here. Some functions are available under additional + /// names (aliases) for historic reasons which are omitted here. + /// + /// If you want an overview of all the functions available to a contact all you need + /// to look at is this trait. It contains only the latest version of each + /// function and no aliases. If you are writing a contract(language) from scratch + /// this is where you should look at. + pub trait Current { + #( #current_docs )* + } + #( #legacy_doc )* } } -/// Expands environment definiton. +/// Expands environment definition. /// Should generate source code for: /// - implementations of the host functions to be added to the wasm runtime environment (see /// `expand_impls()`). -fn expand_env(def: &mut EnvDef, docs: bool) -> TokenStream2 { +fn expand_env(def: &EnvDef, docs: bool) -> TokenStream2 { let impls = expand_impls(def); let docs = docs.then_some(expand_docs(def)).unwrap_or(TokenStream2::new()); quote! { pub struct Env; #impls - /// Contains the documentation of the API available to contracts. + /// Documentation of the API (host functions) available to contracts. /// - /// In order to generate this documentation, pass `doc` attribute to the [`#[define_env]`][`macro@define_env`] macro: - /// `#[define_env(doc)]`, and then run `cargo doc`. + /// The `Current` trait might be the most useful doc to look at. The versioned + /// traits only exist for reference: If trying to find out if a specific version of + /// `pallet-contracts` contains a certain function. /// - /// This module is not meant to be used by any code. Rather, it is meant to be consumed by humans through rustdoc. + /// # Note /// - /// Every function described in this module's sub module's traits uses this sub module's identifier - /// as its imported module name. The identifier of the function is the function's imported name. - /// According to the [WASM spec of imports](https://webassembly.github.io/spec/core/text/modules.html#text-import). + /// This module is not meant to be used by any code. Rather, it is meant to be + /// consumed by humans through rustdoc. #[cfg(doc)] pub mod api_doc { + use super::{TrapReason, ReturnCode}; #docs } } @@ -520,7 +559,7 @@ fn expand_env(def: &mut EnvDef, docs: bool) -> TokenStream2 { /// Generates for every host function: /// - real implementation, to register it in the contract execution environment; /// - dummy implementation, to be used as mocks for contract validation step. -fn expand_impls(def: &mut EnvDef) -> TokenStream2 { +fn expand_impls(def: &EnvDef) -> TokenStream2 { let impls = expand_functions(def, true, quote! { crate::wasm::Runtime }); let dummy_impls = expand_functions(def, false, quote! { () }); @@ -553,16 +592,13 @@ fn expand_impls(def: &mut EnvDef) -> TokenStream2 { } } -fn expand_functions( - def: &mut EnvDef, - expand_blocks: bool, - host_state: TokenStream2, -) -> TokenStream2 { +fn expand_functions(def: &EnvDef, expand_blocks: bool, host_state: TokenStream2) -> TokenStream2 { let impls = def.host_funcs.iter().map(|f| { // skip the context and memory argument let params = f.item.sig.inputs.iter().skip(2); + let (module, name, body, wasm_output, output) = ( - &f.module, + f.module(), &f.name, &f.item.block, f.returns.to_wasm_sig(), @@ -571,6 +607,35 @@ fn expand_functions( let is_stable = f.is_stable; let not_deprecated = f.not_deprecated; + // wrapped host function body call with host function traces + // see https://github.com/paritytech/substrate/tree/master/frame/contracts#host-function-tracing + let wrapped_body_with_trace = { + let trace_fmt_args = params.clone().filter_map(|arg| match arg { + syn::FnArg::Receiver(_) => None, + syn::FnArg::Typed(p) => { + match *p.pat.clone() { + syn::Pat::Ident(ref pat_ident) => Some(pat_ident.ident.clone()), + _ => None, + } + }, + }); + + let params_fmt_str = trace_fmt_args.clone().map(|s| format!("{s}: {{:?}}")).collect::>().join(", "); + let trace_fmt_str = format!("{}::{}({}) = {{:?}}\n", module, name, params_fmt_str); + + quote! { + let result = #body; + if ::log::log_enabled!(target: "runtime::contracts::strace", ::log::Level::Trace) { + use sp_std::fmt::Write; + let mut w = sp_std::Writer::default(); + let _ = core::write!(&mut w, #trace_fmt_str, #( #trace_fmt_args, )* result); + let msg = core::str::from_utf8(&w.inner()).unwrap_or_default(); + ctx.ext().append_debug_buffer(msg); + } + result + } + }; + // If we don't expand blocks (implementing for `()`) we change a few things: // - We replace any code by unreachable! // - Allow unused variables as the code that uses is not expanded @@ -578,11 +643,11 @@ fn expand_functions( let inner = if expand_blocks { quote! { || #output { let (memory, ctx) = __caller__ - .host_data() + .data() .memory() .expect("Memory must be set when setting up host data; qed") .data_and_store_mut(&mut __caller__); - #body + #wrapped_body_with_trace } } } else { quote! { || -> #wasm_output { @@ -592,10 +657,10 @@ fn expand_functions( ::core::unreachable!() } } }; - let map_err = if expand_blocks { + let into_host = if expand_blocks { quote! { |reason| { - ::wasmi::core::Trap::host(reason) + ::wasmi::core::Trap::from(reason) } } } else { @@ -608,6 +673,43 @@ fn expand_functions( } else { quote! { #[allow(unused_variables)] } }; + let sync_gas_before = if expand_blocks { + quote! { + // Gas left in the gas meter right before switching to engine execution. + let __gas_before__ = { + let engine_consumed_total = + __caller__.fuel_consumed().expect("Fuel metering is enabled; qed"); + let gas_meter = __caller__.data_mut().ext().gas_meter_mut(); + gas_meter + .charge_fuel(engine_consumed_total) + .map_err(TrapReason::from) + .map_err(#into_host)? + .ref_time() + }; + } + } else { + quote! { } + }; + // Gas left in the gas meter right after returning from engine execution. + let sync_gas_after = if expand_blocks { + quote! { + let mut gas_after = __caller__.data_mut().ext().gas_meter().gas_left().ref_time(); + let mut host_consumed = __gas_before__.saturating_sub(gas_after); + // Possible undercharge of at max 1 fuel here, if host consumed less than `instruction_weights.base` + // Not a problem though, as soon as host accounts its spent gas properly. + let fuel_consumed = host_consumed + .checked_div(__caller__.data_mut().ext().schedule().instruction_weights.base as u64) + .ok_or(Error::::InvalidSchedule) + .map_err(TrapReason::from) + .map_err(#into_host)?; + __caller__ + .consume_fuel(fuel_consumed) + .map_err(|_| TrapReason::from(Error::::OutOfGas)) + .map_err(#into_host)?; + } + } else { + quote! { } + }; quote! { // We need to allow all interfaces when runtime benchmarks are performed because @@ -619,10 +721,11 @@ fn expand_functions( { #allow_unused linker.define(#module, #name, ::wasmi::Func::wrap(&mut*store, |mut __caller__: ::wasmi::Caller<#host_state>, #( #params, )*| -> #wasm_output { + #sync_gas_before let mut func = #inner; - func() - .map_err(#map_err) - .map(::core::convert::Into::into) + let result = func().map_err(#into_host).map(::core::convert::Into::into); + #sync_gas_after + result }))?; } } @@ -743,7 +846,7 @@ fn expand_functions( #[proc_macro_attribute] pub fn define_env(attr: TokenStream, item: TokenStream) -> TokenStream { if !attr.is_empty() && !(attr.to_string() == "doc".to_string()) { - let msg = r#"Invalid `define_env` attribute macro: expected either no attributes or a single `doc` attibute: + let msg = r#"Invalid `define_env` attribute macro: expected either no attributes or a single `doc` attribute: - `#[define_env]` - `#[define_env(doc)]`"#; let span = TokenStream2::from(attr).span(); diff --git a/frame/contracts/src/address.rs b/frame/contracts/src/address.rs new file mode 100644 index 000000000000..e36fc6fffd6f --- /dev/null +++ b/frame/contracts/src/address.rs @@ -0,0 +1,81 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Functions that deal with address derivation. + +use crate::{CodeHash, Config}; +use codec::{Decode, Encode}; +use sp_runtime::traits::{Hash, TrailingZeroInput}; + +/// Provides the contract address generation method. +/// +/// See [`DefaultAddressGenerator`] for the default implementation. +/// +/// # Note for implementors +/// +/// 1. Make sure that there are no collisions, different inputs never lead to the same output. +/// 2. Make sure that the same inputs lead to the same output. +pub trait AddressGenerator { + /// The address of a contract based on the given instantiate parameters. + /// + /// Changing the formular for an already deployed chain is fine as long as no collisions + /// with the old formular. Changes only affect existing contracts. + fn contract_address( + deploying_address: &T::AccountId, + code_hash: &CodeHash, + input_data: &[u8], + salt: &[u8], + ) -> T::AccountId; + + /// The address of the deposit account of `contract_addr`. + /// + /// The address is generated once on instantiation and then stored in the contracts + /// metadata. Hence changes do only affect newly created contracts. + fn deposit_address(contract_addr: &T::AccountId) -> T::AccountId; +} + +/// Default address generator. +/// +/// This is the default address generator used by contract instantiation. Its result +/// is only dependent on its inputs. It can therefore be used to reliably predict the +/// address of a contract. This is akin to the formula of eth's CREATE2 opcode. There +/// is no CREATE equivalent because CREATE2 is strictly more powerful. +/// Formula: +/// `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)` +pub struct DefaultAddressGenerator; + +impl AddressGenerator for DefaultAddressGenerator { + /// Formula: `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)` + fn contract_address( + deploying_address: &T::AccountId, + code_hash: &CodeHash, + input_data: &[u8], + salt: &[u8], + ) -> T::AccountId { + let entropy = (b"contract_addr_v1", deploying_address, code_hash, input_data, salt) + .using_encoded(T::Hashing::hash); + Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") + } + + /// Formula: `hash("contract_depo_v1" ++ contract_addr)` + fn deposit_address(contract_addr: &T::AccountId) -> T::AccountId { + let entropy = (b"contract_depo_v1", contract_addr).using_encoded(T::Hashing::hash); + Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) + .expect("infinite length input; no invalid inputs for type; qed") + } +} diff --git a/frame/contracts/src/benchmarking/code.rs b/frame/contracts/src/benchmarking/code.rs index 156b9e2f9994..2f50611b41c2 100644 --- a/frame/contracts/src/benchmarking/code.rs +++ b/frame/contracts/src/benchmarking/code.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,22 +24,19 @@ //! we define this simple definition of a contract that can be passed to `create_code` that //! compiles it down into a `WasmModule` that can be used as a contract's code. -use crate::{Config, Determinism}; +use crate::Config; use frame_support::traits::Get; use sp_runtime::traits::Hash; use sp_std::{borrow::ToOwned, prelude::*}; -use wasm_instrument::{ - gas_metering, - parity_wasm::{ - builder, - elements::{ - self, BlockType, CustomSection, External, FuncBody, Instruction, Instructions, Module, - Section, ValueType, - }, +use wasm_instrument::parity_wasm::{ + builder, + elements::{ + self, BlockType, CustomSection, External, FuncBody, Instruction, Instructions, Module, + Section, ValueType, }, }; -/// The location where to put the genrated code. +/// The location where to put the generated code. pub enum Location { /// Generate all code into the `call` exported function. Call, @@ -125,7 +122,7 @@ impl From for WasmModule { // internal functions start at that offset. let func_offset = u32::try_from(def.imported_functions.len()).unwrap(); - // Every contract must export "deploy" and "call" functions + // Every contract must export "deploy" and "call" functions. let mut contract = builder::module() // deploy function (first internal function) .function() @@ -166,15 +163,16 @@ impl From for WasmModule { } // Grant access to linear memory. - if let Some(memory) = &def.memory { - contract = contract - .import() - .module("env") - .field("memory") - .external() - .memory(memory.min_pages, Some(memory.max_pages)) - .build(); - } + // Every contract module is required to have an imported memory. + // If no memory is specified in the passed ModuleDefenition, then + // default to (1, 1). + let (init, max) = if let Some(memory) = &def.memory { + (memory.min_pages, Some(memory.max_pages)) + } else { + (1, Some(1)) + }; + + contract = contract.import().path("env", "memory").external().memory(init, max).build(); // Import supervisor functions. They start with idx 0. for func in def.imported_functions { @@ -240,15 +238,9 @@ impl From for WasmModule { } impl WasmModule { - /// Uses the supplied wasm module and instruments it when requested. - pub fn instrumented(code: &[u8], inject_gas: bool) -> Self { - let module = { - let mut module = Module::from_bytes(code).unwrap(); - if inject_gas { - module = inject_gas_metering::(module); - } - module - }; + /// Uses the supplied wasm module. + pub fn from_code(code: &[u8]) -> Self { + let module = Module::from_bytes(code).unwrap(); let limits = *module .import_section() .unwrap() @@ -366,37 +358,13 @@ impl WasmModule { } .into() } - - pub fn unary_instr(instr: Instruction, repeat: u32) -> Self { - use body::DynInstr::{RandomI64Repeated, Regular}; - ModuleDefinition { - call_body: Some(body::repeated_dyn( - repeat, - vec![RandomI64Repeated(1), Regular(instr), Regular(Instruction::Drop)], - )), - ..Default::default() - } - .into() - } - - pub fn binary_instr(instr: Instruction, repeat: u32) -> Self { - use body::DynInstr::{RandomI64Repeated, Regular}; - ModuleDefinition { - call_body: Some(body::repeated_dyn( - repeat, - vec![RandomI64Repeated(2), Regular(instr), Regular(Instruction::Drop)], - )), - ..Default::default() - } - .into() - } } /// Mechanisms to generate a function body that can be used inside a `ModuleDefinition`. pub mod body { use super::*; - /// When generating contract code by repeating a wasm sequence, it's sometimes necessary + /// When generating contract code by repeating a Wasm sequence, it's sometimes necessary /// to change those instructions on each repetition. The variants of this enum describe /// various ways in which this can happen. pub enum DynInstr { @@ -405,31 +373,8 @@ pub mod body { /// Insert a I32Const with incrementing value for each insertion. /// (start_at, increment_by) Counter(u32, u32), - /// Insert a I32Const with a random value in [low, high) not divisible by two. - /// (low, high) - RandomUnaligned(u32, u32), - /// Insert a I32Const with a random value in [low, high). - /// (low, high) - RandomI32(i32, i32), - /// Insert the specified amount of I32Const with a random value. - RandomI32Repeated(usize), /// Insert the specified amount of I64Const with a random value. RandomI64Repeated(usize), - /// Insert a GetLocal with a random offset in [low, high). - /// (low, high) - RandomGetLocal(u32, u32), - /// Insert a SetLocal with a random offset in [low, high). - /// (low, high) - RandomSetLocal(u32, u32), - /// Insert a TeeLocal with a random offset in [low, high). - /// (low, high) - RandomTeeLocal(u32, u32), - /// Insert a GetGlobal with a random offset in [low, high). - /// (low, high) - RandomGetGlobal(u32, u32), - /// Insert a SetGlobal with a random offset in [low, high). - /// (low, high) - RandomSetGlobal(u32, u32), } pub fn plain(instructions: Vec) -> FuncBody { @@ -466,53 +411,16 @@ pub mod body { *offset += *increment_by; vec![Instruction::I32Const(current as i32)] }, - DynInstr::RandomUnaligned(low, high) => { - let unaligned = rng.gen_range(*low..*high) | 1; - vec![Instruction::I32Const(unaligned as i32)] - }, - DynInstr::RandomI32(low, high) => { - vec![Instruction::I32Const(rng.gen_range(*low..*high))] - }, - DynInstr::RandomI32Repeated(num) => - (&mut rng).sample_iter(Standard).take(*num).map(Instruction::I32Const).collect(), DynInstr::RandomI64Repeated(num) => (&mut rng).sample_iter(Standard).take(*num).map(Instruction::I64Const).collect(), - DynInstr::RandomGetLocal(low, high) => { - vec![Instruction::GetLocal(rng.gen_range(*low..*high))] - }, - DynInstr::RandomSetLocal(low, high) => { - vec![Instruction::SetLocal(rng.gen_range(*low..*high))] - }, - DynInstr::RandomTeeLocal(low, high) => { - vec![Instruction::TeeLocal(rng.gen_range(*low..*high))] - }, - DynInstr::RandomGetGlobal(low, high) => { - vec![Instruction::GetGlobal(rng.gen_range(*low..*high))] - }, - DynInstr::RandomSetGlobal(low, high) => { - vec![Instruction::SetGlobal(rng.gen_range(*low..*high))] - }, }) .chain(sp_std::iter::once(Instruction::End)) .collect(); FuncBody::new(Vec::new(), Instructions::new(body)) } - - /// Replace the locals of the supplied `body` with `num` i64 locals. - pub fn inject_locals(body: &mut FuncBody, num: u32) { - use self::elements::Local; - *body.locals_mut() = vec![Local::new(num, ValueType::I64)]; - } } /// The maximum amount of pages any contract is allowed to have according to the current `Schedule`. pub fn max_pages() -> u32 { T::Schedule::get().limits.memory_pages } - -fn inject_gas_metering(module: Module) -> Module { - let schedule = T::Schedule::get(); - let gas_rules = schedule.rules(&module, Determinism::Deterministic); - let backend = gas_metering::host_function::Injector::new("seal0", "gas"); - gas_metering::inject(module, backend, &gas_rules).unwrap() -} diff --git a/frame/contracts/src/benchmarking/mod.rs b/frame/contracts/src/benchmarking/mod.rs index a750a6b1728d..0e853b6cde35 100644 --- a/frame/contracts/src/benchmarking/mod.rs +++ b/frame/contracts/src/benchmarking/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,6 @@ mod code; mod sandbox; - use self::{ code::{ body::{self, DynInstr::*}, @@ -30,28 +29,31 @@ use self::{ sandbox::Sandbox, }; use crate::{ - exec::{AccountIdOf, FixSizedKey, VarSizedKey}, - schedule::{API_BENCHMARK_BATCH_SIZE, INSTR_BENCHMARK_BATCH_SIZE}, - storage::Storage, + exec::{AccountIdOf, Key}, + migration::{v09, v10, v11, v12, MigrationStep}, wasm::CallFlags, Pallet as Contracts, *, }; use codec::{Encode, MaxEncodedLen}; -use frame_benchmarking::{account, benchmarks, whitelisted_caller}; -use frame_support::weights::Weight; +use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller}; +use frame_support::{pallet_prelude::StorageVersion, weights::Weight}; use frame_system::RawOrigin; -use sp_runtime::{ - traits::{Bounded, Hash}, - Perbill, -}; +use sp_runtime::traits::{Bounded, Hash}; use sp_std::prelude::*; -use wasm_instrument::parity_wasm::elements::{BlockType, BrTableData, Instruction, ValueType}; +use wasm_instrument::parity_wasm::elements::{BlockType, Instruction, ValueType}; -/// How many batches we do per API benchmark. -const API_BENCHMARK_BATCHES: u32 = 20; +/// How many runs we do per API benchmark. +/// +/// This is picked more or less arbitrary. We experimented with different numbers until +/// the results appeared to be stable. Reducing the number would speed up the benchmarks +/// but might make the results less precise. +const API_BENCHMARK_RUNS: u32 = 1600; -/// How many batches we do per Instruction benchmark. -const INSTR_BENCHMARK_BATCHES: u32 = 50; +/// How many runs we do per instruction benchmark. +/// +/// Same rationale as for [`API_BENCHMARK_RUNS`]. The number is bigger because instruction +/// benchmarks are faster. +const INSTR_BENCHMARK_RUNS: u32 = 5000; /// An instantiated and deployed contract. struct Contract { @@ -130,17 +132,11 @@ where } /// Store the supplied storage items into this contracts storage. - fn store(&self, items: &Vec<(FixSizedKey, Vec)>) -> Result<(), &'static str> { + fn store(&self, items: &Vec<([u8; 32], Vec)>) -> Result<(), &'static str> { let info = self.info()?; for item in items { - Storage::::write( - &info.trie_id, - &item.0 as &FixSizedKey, - Some(item.1.clone()), - None, - false, - ) - .map_err(|_| "Failed to write storage to restoration dest")?; + info.write(&Key::Fix(item.0), Some(item.1.clone()), None, false) + .map_err(|_| "Failed to write storage to restoration dest")?; } >::insert(&self.account_id, info); Ok(()) @@ -163,16 +159,12 @@ where /// Returns `true` iff all storage entries related to code storage exist. fn code_exists(hash: &CodeHash) -> bool { - >::contains_key(hash) && - >::contains_key(&hash) && - >::contains_key(&hash) + >::contains_key(hash) && >::contains_key(&hash) } /// Returns `true` iff no storage entry related to code storage exist. fn code_removed(hash: &CodeHash) -> bool { - !>::contains_key(hash) && - !>::contains_key(&hash) && - !>::contains_key(&hash) + !>::contains_key(hash) && !>::contains_key(&hash) } } @@ -205,49 +197,127 @@ benchmarks! { } // The base weight consumed on processing contracts deletion queue. + #[pov_mode = Measured] on_process_deletion_queue_batch {}: { - Storage::::process_deletion_queue_batch(Weight::MAX) + ContractInfo::::process_deletion_queue_batch(Weight::MAX) } #[skip_meta] + #[pov_mode = Measured] on_initialize_per_trie_key { let k in 0..1024; let instance = Contract::::with_storage(WasmModule::dummy(), k, T::Schedule::get().limits.payload_len)?; - Storage::::queue_trie_for_deletion(&instance.info()?)?; + instance.info()?.queue_trie_for_deletion(); }: { - Storage::::process_deletion_queue_batch(Weight::MAX) + ContractInfo::::process_deletion_queue_batch(Weight::MAX) } - on_initialize_per_queue_item { - let q in 0..1024.min(T::DeletionQueueDepth::get()); - for i in 0 .. q { - let instance = Contract::::with_index(i, WasmModule::dummy(), vec![])?; - Storage::::queue_trie_for_deletion(&instance.info()?)?; - ContractInfoOf::::remove(instance.account_id); - } + // This benchmarks the v9 migration step (update codeStorage). + #[pov_mode = Measured] + v9_migration_step { + let c in 0 .. T::MaxCodeLen::get(); + v09::store_old_dummy_code::(c as usize); + let mut m = v09::Migration::::default(); }: { - Storage::::process_deletion_queue_batch(Weight::MAX) + m.step(); } - // This benchmarks the additional weight that is charged when a contract is executed the - // first time after a new schedule was deployed: For every new schedule a contract needs - // to re-run the instrumentation once. - reinstrument { - let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get()); - let WasmModule { code, hash, .. } = WasmModule::::sized(c, Location::Call); - Contracts::::store_code_raw(code, whitelisted_caller())?; - let schedule = T::Schedule::get(); - let mut gas_meter = GasMeter::new(Weight::MAX); - let mut module = PrefabWasmModule::from_storage(hash, &schedule, &mut gas_meter)?; + // This benchmarks the v10 migration step (use dedicated deposit_account). + #[pov_mode = Measured] + v10_migration_step { + let contract = >::with_caller( + whitelisted_caller(), WasmModule::dummy(), vec![], + )?; + + v10::store_old_contract_info::(contract.account_id.clone(), contract.info()?); + let mut m = v10::Migration::::default(); + }: { + m.step(); + } + + // This benchmarks the v11 migration step (Don't rely on reserved balances keeping an account alive). + #[pov_mode = Measured] + v11_migration_step { + let k in 0 .. 1024; + v11::fill_old_queue::(k as usize); + let mut m = v11::Migration::::default(); }: { - Contracts::::reinstrument_module(&mut module, &schedule)?; + m.step(); + } + + // This benchmarks the v12 migration step (Move `OwnerInfo` to `CodeInfo`, + // add `determinism` field to the latter, clear `CodeStorage` + // and repay deposits). + #[pov_mode = Measured] + v12_migration_step { + let c in 0 .. T::MaxCodeLen::get(); + v12::store_old_dummy_code::(c as usize, account::("account", 0, 0)); + let mut m = v12::Migration::::default(); + }: { + m.step(); + } + + // This benchmarks the weight of executing Migration::migrate to execute a noop migration. + #[pov_mode = Measured] + migration_noop { + assert_eq!(StorageVersion::get::>(), 2); + }: { + Migration::::migrate(Weight::MAX) + } verify { + assert_eq!(StorageVersion::get::>(), 2); + } + + // This benchmarks the weight of dispatching migrate to execute 1 `NoopMigraton` + #[pov_mode = Measured] + migrate { + StorageVersion::new(0).put::>(); + as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade(); + let caller: T::AccountId = whitelisted_caller(); + let origin = RawOrigin::Signed(caller.clone()); + }: _(origin, Weight::MAX) + verify { + assert_eq!(StorageVersion::get::>(), 1); + } + + // This benchmarks the weight of running on_runtime_upgrade when there are no migration in progress. + #[pov_mode = Measured] + on_runtime_upgrade_noop { + assert_eq!(StorageVersion::get::>(), 2); + }: { + as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade() + } verify { + assert!(MigrationInProgress::::get().is_none()); + } + + // This benchmarks the weight of running on_runtime_upgrade when there is a migration in progress. + #[pov_mode = Measured] + on_runtime_upgrade_in_progress { + StorageVersion::new(0).put::>(); + let v = vec![42u8].try_into().ok(); + MigrationInProgress::::set(v.clone()); + }: { + as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade() + } verify { + assert!(MigrationInProgress::::get().is_some()); + assert_eq!(MigrationInProgress::::get(), v); + } + + // This benchmarks the weight of running on_runtime_upgrade when there is a migration to process. + #[pov_mode = Measured] + on_runtime_upgrade { + StorageVersion::new(0).put::>(); + }: { + as frame_support::traits::OnRuntimeUpgrade>::on_runtime_upgrade() + } verify { + assert!(MigrationInProgress::::get().is_some()); } // This benchmarks the overhead of loading a code of size `c` byte from storage and into // the sandbox. This does **not** include the actual execution for which the gas meter // is responsible. This is achieved by generating all code to the `deploy` function // which is in the wasm module but not executed on `call`. - // The results are supposed to be used as `call_with_code_kb(c) - call_with_code_kb(0)`. + // The results are supposed to be used as `call_with_code_per_byte(c) - call_with_code_per_byte(0)`. + #[pov_mode = Measured] call_with_code_per_byte { let c in 0 .. T::MaxCodeLen::get(); let instance = Contract::::with_caller( @@ -265,16 +335,12 @@ benchmarks! { // we don't benchmark the actual execution of this code but merely what it takes to load // a code of that size into the sandbox. // - // `c`: Size of the code in kilobytes. - // `i`: Size of the input in kilobytes. - // `s`: Size of the salt in kilobytes. - // - // # Note - // - // We cannot let `c` grow to the maximum code size because the code is not allowed - // to be larger than the maximum size **after instrumentation**. + // `c`: Size of the code in bytes. + // `i`: Size of the input in bytes. + // `s`: Size of the salt in bytes. + #[pov_mode = Measured] instantiate_with_code { - let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get()); + let c in 0 .. T::MaxCodeLen::get(); let i in 0 .. code::max_pages::() * 64 * 1024; let s in 0 .. code::max_pages::() * 64 * 1024; let input = vec![42u8; i as usize]; @@ -287,23 +353,22 @@ benchmarks! { let addr = Contracts::::contract_address(&caller, &hash, &input, &salt); }: _(origin, value, Weight::MAX, None, code, input, salt) verify { - // the contract itself does not trigger any reserves - let deposit = T::Currency::reserved_balance(&addr); + let deposit_account = Contract::::address_info(&addr)?.deposit_account().clone(); + let deposit = T::Currency::free_balance(&deposit_account); // uploading the code reserves some balance in the callers account let code_deposit = T::Currency::reserved_balance(&caller); assert_eq!( T::Currency::free_balance(&caller), - caller_funding::() - value - deposit - code_deposit, + caller_funding::() - value - deposit - code_deposit - Pallet::::min_balance(), ); // contract has the full value - assert_eq!(T::Currency::free_balance(&addr), value); - // instantiate should leave a contract - Contract::::address_info(&addr)?; + assert_eq!(T::Currency::free_balance(&addr), value + Pallet::::min_balance()); } // Instantiate uses a dummy contract constructor to measure the overhead of the instantiate. - // `i`: Size of the input in kilobytes. - // `s`: Size of the salt in kilobytes. + // `i`: Size of the input in bytes. + // `s`: Size of the salt in bytes. + #[pov_mode = Measured] instantiate { let i in 0 .. code::max_pages::() * 64 * 1024; let s in 0 .. code::max_pages::() * 64 * 1024; @@ -318,14 +383,15 @@ benchmarks! { Contracts::::store_code_raw(code, caller.clone())?; }: _(origin, value, Weight::MAX, None, hash, input, salt) verify { - // the contract itself does not trigger any reserves - let deposit = T::Currency::reserved_balance(&addr); + let deposit_account = Contract::::address_info(&addr)?.deposit_account().clone(); + let deposit = T::Currency::free_balance(&deposit_account); // value was removed from the caller - assert_eq!(T::Currency::free_balance(&caller), caller_funding::() - value - deposit); + assert_eq!( + T::Currency::free_balance(&caller), + caller_funding::() - value - deposit - Pallet::::min_balance(), + ); // contract has the full value - assert_eq!(T::Currency::free_balance(&addr), value); - // instantiate should leave a contract - Contract::::address_info(&addr)?; + assert_eq!(T::Currency::free_balance(&addr), value + Pallet::::min_balance()); } // We just call a dummy contract to measure the overhead of the call extrinsic. @@ -334,24 +400,26 @@ benchmarks! { // The dummy contract used here does not do this. The costs for the data copy is billed as // part of `seal_input`. The costs for invoking a contract of a specific size are not part // of this benchmark because we cannot know the size of the contract when issuing a call - // transaction. See `invoke_per_code_kb` for this. + // transaction. See `call_with_code_per_byte` for this. + #[pov_mode = Measured] call { let data = vec![42u8; 1024]; let instance = Contract::::with_caller( whitelisted_caller(), WasmModule::dummy(), vec![], )?; + let deposit_account = instance.info()?.deposit_account().clone(); let value = Pallet::::min_balance(); let origin = RawOrigin::Signed(instance.caller.clone()); let callee = instance.addr.clone(); let before = T::Currency::free_balance(&instance.account_id); + let before_deposit = T::Currency::free_balance(&deposit_account); }: _(origin, callee, value, Weight::MAX, None, data) verify { - // the contract itself does not trigger any reserves - let deposit = T::Currency::reserved_balance(&instance.account_id); - // value and value transfered via call should be removed from the caller + let deposit = T::Currency::free_balance(&deposit_account); + // value and value transferred via call should be removed from the caller assert_eq!( T::Currency::free_balance(&instance.caller), - caller_funding::() - instance.value - value - deposit, + caller_funding::() - instance.value - value - deposit - Pallet::::min_balance(), ); // contract should have received the value assert_eq!(T::Currency::free_balance(&instance.account_id), before + value); @@ -361,19 +429,15 @@ benchmarks! { // This constructs a contract that is maximal expensive to instrument. // It creates a maximum number of metering blocks per byte. - // `c`: Size of the code in kilobytes. - // - // # Note - // - // We cannot let `c` grow to the maximum code size because the code is not allowed - // to be larger than the maximum size **after instrumentation**. + // `c`: Size of the code in bytes. + #[pov_mode = Measured] upload_code { - let c in 0 .. Perbill::from_percent(49).mul_ceil(T::MaxCodeLen::get()); + let c in 0 .. T::MaxCodeLen::get(); let caller = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, caller_funding::()); let WasmModule { code, hash, .. } = WasmModule::::sized(c, Location::Call); let origin = RawOrigin::Signed(caller.clone()); - }: _(origin, code, None, Determinism::Deterministic) + }: _(origin, code, None, Determinism::Enforced) verify { // uploading the code reserves some balance in the callers account assert!(T::Currency::reserved_balance(&caller) > 0u32.into()); @@ -382,13 +446,14 @@ benchmarks! { // Removing code does not depend on the size of the contract because all the information // needed to verify the removal claim (refcount, owner) is stored in a separate storage - // item (`OwnerInfoOf`). + // item (`CodeInfoOf`). + #[pov_mode = Measured] remove_code { let caller = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, caller_funding::()); let WasmModule { code, hash, .. } = WasmModule::::dummy(); let origin = RawOrigin::Signed(caller.clone()); - let uploaded = >::bare_upload_code(caller.clone(), code, None, Determinism::Deterministic)?; + let uploaded = >::bare_upload_code(caller.clone(), code, None, Determinism::Enforced)?; assert_eq!(uploaded.code_hash, hash); assert_eq!(uploaded.deposit, T::Currency::reserved_balance(&caller)); assert!(>::code_exists(&hash)); @@ -399,6 +464,7 @@ benchmarks! { assert!(>::code_removed(&hash)); } + #[pov_mode = Measured] set_code { let instance = >::with_caller( whitelisted_caller(), WasmModule::dummy(), vec![], @@ -413,17 +479,19 @@ benchmarks! { assert_eq!(instance.info()?.code_hash, hash); } + #[pov_mode = Measured] seal_caller { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_caller", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_caller", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + #[pov_mode = Measured] seal_is_contract { - let r in 0 .. API_BENCHMARK_BATCHES; - let accounts = (0 .. r * API_BENCHMARK_BATCH_SIZE) + let r in 0 .. API_BENCHMARK_RUNS; + let accounts = (0 .. r) .map(|n| account::("account", n, 0)) .collect::>(); let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0); @@ -442,7 +510,7 @@ benchmarks! { value: accounts_bytes }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(0, account_len as u32), // address_ptr Regular(Instruction::Call(0)), Regular(Instruction::Drop), @@ -458,9 +526,10 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + #[pov_mode = Measured] seal_code_hash { - let r in 0 .. API_BENCHMARK_BATCHES; - let accounts = (0 .. r * API_BENCHMARK_BATCH_SIZE) + let r in 0 .. API_BENCHMARK_RUNS; + let accounts = (0 .. r) .map(|n| account::("account", n, 0)) .collect::>(); let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0); @@ -485,7 +554,7 @@ benchmarks! { value: accounts_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(36, account_len as u32), // address_ptr Regular(Instruction::I32Const(4)), // ptr to output data Regular(Instruction::I32Const(0)), // ptr to output length @@ -503,16 +572,18 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + #[pov_mode = Measured] seal_own_code_hash { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_own_code_hash", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_own_code_hash", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + #[pov_mode = Measured] seal_caller_is_origin { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -521,7 +592,7 @@ benchmarks! { params: vec![], return_type: Some(ValueType::I32), }], - call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r, &[ Instruction::Call(0), Instruction::Drop, ])), @@ -531,79 +602,110 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + #[pov_mode = Measured] + seal_caller_is_root { + let r in 0 .. API_BENCHMARK_RUNS; + + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal0", + name: "caller_is_root", + params: vec![], + return_type: Some(ValueType::I32), + }], + call_body: Some(body::repeated(r, &[ + Instruction::Call(0), + Instruction::Drop, + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let origin = RawOrigin::Root; + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + #[pov_mode = Measured] seal_address { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_address", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_address", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + #[pov_mode = Measured] seal_gas_left { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_gas_left", r * API_BENCHMARK_BATCH_SIZE + "seal1", "gas_left", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + #[pov_mode = Measured] seal_balance { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_balance", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_balance", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + #[pov_mode = Measured] seal_value_transferred { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_value_transferred", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_value_transferred", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + #[pov_mode = Measured] seal_minimum_balance { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_minimum_balance", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_minimum_balance", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + #[pov_mode = Measured] seal_block_number { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_block_number", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_block_number", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + #[pov_mode = Measured] seal_now { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let instance = Contract::::new(WasmModule::getter( - "seal0", "seal_now", r * API_BENCHMARK_BATCH_SIZE + "seal0", "seal_now", r ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + #[pov_mode = Measured] seal_weight_to_fee { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let pages = code::max_pages::(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", - name: "seal_weight_to_fee", - params: vec![ValueType::I64, ValueType::I32, ValueType::I32], + module: "seal1", + name: "weight_to_fee", + params: vec![ValueType::I64, ValueType::I64, ValueType::I32, ValueType::I32], return_type: None, }], data_segments: vec![DataSegment { offset: 0, value: (pages * 64 * 1024 - 4).to_le_bytes().to_vec(), }], - call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r, &[ Instruction::I64Const(500_000), + Instruction::I64Const(300_000), Instruction::I32Const(4), Instruction::I32Const(0), Instruction::Call(0), @@ -614,28 +716,9 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - seal_gas { - let r in 0 .. API_BENCHMARK_BATCHES; - let code = WasmModule::::from(ModuleDefinition { - imported_functions: vec![ImportedFunction { - module: "seal0", - name: "gas", - params: vec![ValueType::I64], - return_type: None, - }], - call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ - Instruction::I64Const(42), - Instruction::Call(0), - ])), - .. Default::default() - }); - let instance = Contract::::new(code, vec![])?; - let origin = RawOrigin::Signed(instance.caller.clone()); - - }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - + #[pov_mode = Measured] seal_input { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -650,7 +733,7 @@ benchmarks! { value: 0u32.to_le_bytes().to_vec(), }, ], - call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r, &[ Instruction::I32Const(4), // ptr where to store output Instruction::I32Const(0), // ptr to length Instruction::Call(0), @@ -661,10 +744,10 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - seal_input_per_kb { - let n in 0 .. code::max_pages::() * 64; - let pages = code::max_pages::(); - let buffer_size = pages * 64 * 1024 - 4; + #[pov_mode = Measured] + seal_input_per_byte { + let n in 0 .. code::max_pages::() * 64 * 1024; + let buffer_size = code::max_pages::() * 64 * 1024 - 4; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -679,21 +762,23 @@ benchmarks! { value: buffer_size.to_le_bytes().to_vec(), }, ], - call_body: Some(body::repeated(API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::plain(vec![ Instruction::I32Const(4), // ptr where to store output Instruction::I32Const(0), // ptr to length Instruction::Call(0), + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; - let data = vec![42u8; (n * 1024).min(buffer_size) as usize]; + let data = vec![42u8; n.min(buffer_size) as usize]; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, data) // We cannot call `seal_return` multiple times. Therefore our weight determination is not // as precise as with other APIs. Because this function can only be called once per // contract it cannot be used as an attack vector. + #[pov_mode = Measured] seal_return { let r in 0 .. 1; let code = WasmModule::::from(ModuleDefinition { @@ -716,8 +801,9 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - seal_return_per_kb { - let n in 0 .. code::max_pages::() * 64; + #[pov_mode = Measured] + seal_return_per_byte { + let n in 0 .. code::max_pages::() * 64 * 1024; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -729,7 +815,7 @@ benchmarks! { call_body: Some(body::plain(vec![ Instruction::I32Const(0), // flags Instruction::I32Const(0), // data_ptr - Instruction::I32Const((n * 1024) as i32), // data_len + Instruction::I32Const(n as i32), // data_len Instruction::Call(0), Instruction::End, ])), @@ -740,6 +826,7 @@ benchmarks! { }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // The same argument as for `seal_return` is true here. + #[pov_mode = Measured] seal_terminate { let r in 0 .. 1; let beneficiary = account::("beneficiary", 0, 0); @@ -768,22 +855,25 @@ benchmarks! { }); let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - assert_eq!(T::Currency::total_balance(&beneficiary), 0u32.into()); - assert_eq!(T::Currency::free_balance(&instance.account_id), Pallet::::min_balance()); - assert_ne!(T::Currency::reserved_balance(&instance.account_id), 0u32.into()); + let deposit_account = instance.info()?.deposit_account().clone(); + assert_eq!(>::total_balance(&beneficiary), 0u32.into()); + assert_eq!(T::Currency::free_balance(&instance.account_id), Pallet::::min_balance() * 2u32.into()); + assert_ne!(T::Currency::free_balance(&deposit_account), 0u32.into()); }: call(origin, instance.addr.clone(), 0u32.into(), Weight::MAX, None, vec![]) verify { if r > 0 { - assert_eq!(T::Currency::total_balance(&instance.account_id), 0u32.into()); - assert_eq!(T::Currency::total_balance(&beneficiary), Pallet::::min_balance()); + assert_eq!(>::total_balance(&instance.account_id), 0u32.into()); + assert_eq!(>::total_balance(&deposit_account), 0u32.into()); + assert_eq!(>::total_balance(&beneficiary), Pallet::::min_balance() * 2u32.into()); } } // We benchmark only for the maximum subject length. We assume that this is some lowish // number (< 1 KB). Therefore we are not overcharging too much in case a smaller subject is // used. + #[pov_mode = Measured] seal_random { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let pages = code::max_pages::(); let subject_len = T::Schedule::get().limits.subject_len; assert!(subject_len < 1024); @@ -801,7 +891,7 @@ benchmarks! { value: (pages * 64 * 1024 - subject_len - 4).to_le_bytes().to_vec(), }, ], - call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r, &[ Instruction::I32Const(4), // subject_ptr Instruction::I32Const(subject_len as i32), // subject_len Instruction::I32Const((subject_len + 4) as i32), // out_ptr @@ -816,8 +906,9 @@ benchmarks! { // Overhead of calling the function without any topic. // We benchmark for the worst case (largest event). + #[pov_mode = Measured] seal_deposit_event { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -826,7 +917,7 @@ benchmarks! { params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], return_type: None, }], - call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r, &[ Instruction::I32Const(0), // topics_ptr Instruction::I32Const(0), // topics_len Instruction::I32Const(0), // data_ptr @@ -841,15 +932,13 @@ benchmarks! { // Benchmark the overhead that topics generate. // `t`: Number of topics - // `n`: Size of event payload in kb - seal_deposit_event_per_topic_and_kb { + // `n`: Size of event payload in bytes + #[pov_mode = Measured] + seal_deposit_event_per_topic_and_byte { let t in 0 .. T::Schedule::get().limits.event_topics; - let n in 0 .. T::Schedule::get().limits.payload_len / 1024; - let mut topics = (0..API_BENCHMARK_BATCH_SIZE) - .map(|n| (n * t..n * t + t).map(|i| T::Hashing::hash_of(&i)).collect::>().encode()) - .peekable(); - let topics_len = topics.peek().map(|i| i.len()).unwrap_or(0); - let topics = topics.flatten().collect(); + let n in 0 .. T::Schedule::get().limits.payload_len; + let topics = (0..t).map(|i| T::Hashing::hash_of(&i)).collect::>().encode(); + let topics_len = topics.len(); let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -864,12 +953,13 @@ benchmarks! { value: topics, }, ], - call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, topics_len as u32), // topics_ptr - Regular(Instruction::I32Const(topics_len as i32)), // topics_len - Regular(Instruction::I32Const(0)), // data_ptr - Regular(Instruction::I32Const((n * 1024) as i32)), // data_len - Regular(Instruction::Call(0)), + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // topics_ptr + Instruction::I32Const(topics_len as i32), // topics_len + Instruction::I32Const(0), // data_ptr + Instruction::I32Const(n as i32), // data_len + Instruction::Call(0), + Instruction::End, ])), .. Default::default() }); @@ -877,12 +967,12 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - // The size of the supplied message does not influence the weight because as it is never - // processed during on-chain execution: It is only ever read during debugging which happens - // when the contract is called as RPC where weights do not matter. + // Benchmark debug_message call with zero input data. + // Whereas this function is used in RPC mode only, it still should be secured + // against an excessive use. + #[pov_mode = Measured] seal_debug_message { - let r in 0 .. API_BENCHMARK_BATCHES; - let max_bytes = code::max_pages::() * 64 * 1024; + let r in 0 .. API_BENCHMARK_RUNS; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory { min_pages: 1, max_pages: 1 }), imported_functions: vec![ImportedFunction { @@ -891,29 +981,97 @@ benchmarks! { params: vec![ValueType::I32, ValueType::I32], return_type: Some(ValueType::I32), }], - call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r, &[ Instruction::I32Const(0), // value_ptr - Instruction::I32Const(max_bytes as i32), // value_len + Instruction::I32Const(0), // value_len Instruction::Call(0), Instruction::Drop, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; - let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + }: { + >::bare_call( + instance.caller, + instance.account_id, + 0u32.into(), + Weight::MAX, + None, + vec![], + DebugInfo::UnsafeDebug, + CollectEvents::Skip, + Determinism::Enforced, + ) + .result?; + } + + seal_debug_message_per_byte { + // Vary size of input in bytes up to maximum allowed contract memory + // or maximum allowed debug buffer size, whichever is less. + let i in 0 .. (T::Schedule::get().limits.memory_pages * 64 * 1024).min(T::MaxDebugBufferLen::get()); + // We benchmark versus messages containing printable ASCII codes. + // About 1Kb goes to the contract code instructions, + // whereas all the space left we use for the initialization of the debug messages data. + let message = (0 .. T::MaxCodeLen::get() - 1024).zip((32..127).cycle()).map(|i| i.1).collect::>(); + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory { + min_pages: T::Schedule::get().limits.memory_pages, + max_pages: T::Schedule::get().limits.memory_pages, + }), + imported_functions: vec![ImportedFunction { + module: "seal0", + name: "seal_debug_message", + params: vec![ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: message, + }, + ], + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // value_ptr + Instruction::I32Const(i as i32), // value_len + Instruction::Call(0), + Instruction::Drop, + Instruction::End, + ])), + ..Default::default() + }); + let instance = Contract::::new(code, vec![])?; + }: { + >::bare_call( + instance.caller, + instance.account_id, + 0u32.into(), + Weight::MAX, + None, + vec![], + DebugInfo::UnsafeDebug, + CollectEvents::Skip, + Determinism::Enforced, + ) + .result?; + } // Only the overhead of calling the function itself with minimal arguments. // The contract is a bit more complex because it needs to use different keys in order // to generate unique storage accesses. However, it is still dominated by the storage - // accesses. We store all the keys that we are about to write at beforehand + // accesses. We store something at all the keys that we are about to write to // because re-writing at an existing key is always more expensive than writing - // it at a virgin key. + // to an key with no data behind it. + // + // # Note + // + // We need to use a smaller `r` because the keys are big and writing them all into the wasm + // might exceed the code size. #[skip_meta] + #[pov_mode = Measured] seal_set_storage { - let r in 0 .. API_BENCHMARK_BATCHES/2; + let r in 0 .. API_BENCHMARK_RUNS/2; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + let keys = (0 .. r) .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) .collect::>(); @@ -932,7 +1090,7 @@ benchmarks! { value: keys_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(0, max_key_len as u32), // key_ptr Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::I32Const(0)), // value_ptr @@ -945,9 +1103,8 @@ benchmarks! { let instance = Contract::::new(code, vec![])?; let info = instance.info()?; for key in keys { - Storage::::write( - &info.trie_id, - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + info.write( + &Key::::try_from_var(key).map_err(|e| "Key has wrong length")?, Some(vec![]), None, false, @@ -958,14 +1115,11 @@ benchmarks! { }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) #[skip_meta] - seal_set_storage_per_new_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + #[pov_mode = Measured] + seal_set_storage_per_new_byte { + let n in 0 .. T::Schedule::get().limits.payload_len; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) - .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); - h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) - .collect::>(); - let key_bytes = keys.iter().flatten().cloned().collect::>(); + let key = vec![0u8; max_key_len as usize]; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -977,43 +1131,38 @@ benchmarks! { data_segments: vec![ DataSegment { offset: 0, - value: key_bytes, + value: key.clone(), }, ], - call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, max_key_len as u32), // key_ptr - Regular(Instruction::I32Const(max_key_len as i32)), // key_len - Regular(Instruction::I32Const(0)), // value_ptr - Regular(Instruction::I32Const((n * 2048) as i32)), // value_len increments by 2kb up to max payload_len - Regular(Instruction::Call(0)), - Regular(Instruction::Drop), + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // key_ptr + Instruction::I32Const(max_key_len as i32), // key_len + Instruction::I32Const(0), // value_ptr + Instruction::I32Const(n as i32), // value_len + Instruction::Call(0), + Instruction::Drop, + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; let info = instance.info()?; - for key in keys { - Storage::::write( - &info.trie_id, - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, - Some(vec![]), - None, - false, - ) - .map_err(|_| "Failed to write to storage during setup.")?; - } + info.write( + &Key::::try_from_var(key).map_err(|e| "Key has wrong length")?, + Some(vec![]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) #[skip_meta] - seal_set_storage_per_old_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + #[pov_mode = Measured] + seal_set_storage_per_old_byte { + let n in 0 .. T::Schedule::get().limits.payload_len; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) - .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); - h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) - .collect::>(); - let key_bytes = keys.iter().flatten().cloned().collect::>(); + let key = vec![0u8; max_key_len as usize]; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -1025,43 +1174,42 @@ benchmarks! { data_segments: vec![ DataSegment { offset: 0, - value: key_bytes, + value: key.clone(), }, ], - call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, max_key_len as u32), // key_ptr - Regular(Instruction::I32Const(max_key_len as i32)), // key_len - Regular(Instruction::I32Const(0)), // value_ptr - Regular(Instruction::I32Const(0)), // value_len is 0 as testing vs pre-existing value len - Regular(Instruction::Call(0)), - Regular(Instruction::Drop), + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // key_ptr + Instruction::I32Const(max_key_len as i32), // key_len + Instruction::I32Const(0), // value_ptr + Instruction::I32Const(0), // value_len is 0 as testing vs pre-existing value len + Instruction::Call(0), + Instruction::Drop, + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; let info = instance.info()?; - for key in keys { - Storage::::write( - &info.trie_id, - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, - Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len - None, - false, - ) - .map_err(|_| "Failed to write to storage during setup.")?; - } + info.write( + &Key::::try_from_var(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; n as usize]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // Similar to seal_set_storage. We store all the keys that we are about to // delete beforehand in order to prevent any optimizations that could occur when // deleting a non existing key. We generate keys of a maximum length, and have to - // reduce batch size in order to make resulting contract code size less than MaxCodeLen. + // the amount of runs in order to make resulting contract code size less than MaxCodeLen. #[skip_meta] + #[pov_mode = Measured] seal_clear_storage { - let r in 0 .. API_BENCHMARK_BATCHES/2; + let r in 0 .. API_BENCHMARK_RUNS/2; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + let keys = (0 .. r) .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) .collect::>(); @@ -1080,7 +1228,7 @@ benchmarks! { value: key_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(0, max_key_len as u32), // key_ptr Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::Call(0)), @@ -1091,9 +1239,8 @@ benchmarks! { let instance = Contract::::new(code, vec![])?; let info = instance.info()?; for key in keys { - Storage::::write( - &info.trie_id, - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + info.write( + &Key::::try_from_var(key).map_err(|e| "Key has wrong length")?, Some(vec![]), None, false, @@ -1105,14 +1252,11 @@ benchmarks! { }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) #[skip_meta] - seal_clear_storage_per_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + #[pov_mode = Measured] + seal_clear_storage_per_byte { + let n in 0 .. T::Schedule::get().limits.payload_len; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) - .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); - h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) - .collect::>(); - let key_bytes = keys.iter().flatten().cloned().collect::>(); + let key = vec![0u8; max_key_len as usize]; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -1124,38 +1268,37 @@ benchmarks! { data_segments: vec![ DataSegment { offset: 0, - value: key_bytes, + value: key.clone(), }, ], - call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, max_key_len as u32), // key_ptr - Regular(Instruction::I32Const(max_key_len as i32)), // key_len - Regular(Instruction::Call(0)), - Regular(Instruction::Drop), + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // key_ptr + Instruction::I32Const(max_key_len as i32), // key_len + Instruction::Call(0), + Instruction::Drop, + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; let info = instance.info()?; - for key in keys { - Storage::::write( - &info.trie_id, - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, - Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len - None, - false, - ) - .map_err(|_| "Failed to write to storage during setup.")?; - } + info.write( + &Key::::try_from_var(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; n as usize]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // We make sure that all storage accesses are to unique keys. #[skip_meta] + #[pov_mode = Measured] seal_get_storage { - let r in 0 .. API_BENCHMARK_BATCHES/2; + let r in 0 .. API_BENCHMARK_RUNS/2; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + let keys = (0 .. r) .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) .collect::>(); @@ -1179,8 +1322,8 @@ benchmarks! { value: T::Schedule::get().limits.payload_len.to_le_bytes().into(), }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, max_key_len as u32), // key_ptr + call_body: Some(body::repeated_dyn(r, vec![ + Counter(0, max_key_len), // key_ptr Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr @@ -1192,9 +1335,8 @@ benchmarks! { let instance = Contract::::new(code, vec![])?; let info = instance.info()?; for key in keys { - Storage::::write( - &info.trie_id, - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + info.write( + &Key::::try_from_var(key).map_err(|e| "Key has wrong length")?, Some(vec![]), None, false, @@ -1206,15 +1348,11 @@ benchmarks! { }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) #[skip_meta] - seal_get_storage_per_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + #[pov_mode = Measured] + seal_get_storage_per_byte { + let n in 0 .. T::Schedule::get().limits.payload_len; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) - .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); - h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) - .collect::>(); - let key_bytes = keys.iter().flatten().cloned().collect::>(); - let key_bytes_len = key_bytes.len(); + let key = vec![0u8; max_key_len as usize]; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -1226,45 +1364,44 @@ benchmarks! { data_segments: vec![ DataSegment { offset: 0, - value: key_bytes, + value: key.clone(), }, DataSegment { - offset: key_bytes_len as u32, + offset: max_key_len, value: T::Schedule::get().limits.payload_len.to_le_bytes().into(), }, ], - call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, max_key_len as u32), // key_ptr - Regular(Instruction::I32Const(max_key_len as i32)), // key_len - Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr - Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr - Regular(Instruction::Call(0)), - Regular(Instruction::Drop), + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // key_ptr + Instruction::I32Const(max_key_len as i32), // key_len + Instruction::I32Const((max_key_len + 4) as i32), // out_ptr + Instruction::I32Const(max_key_len as i32), // out_len_ptr + Instruction::Call(0), + Instruction::Drop, + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; let info = instance.info()?; - for key in keys { - Storage::::write( - &info.trie_id, - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, - Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len - None, - false, - ) - .map_err(|_| "Failed to write to storage during setup.")?; - } + info.write( + &Key::::try_from_var(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; n as usize]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // We make sure that all storage accesses are to unique keys. #[skip_meta] + #[pov_mode = Measured] seal_contains_storage { - let r in 0 .. API_BENCHMARK_BATCHES/2; + let r in 0 .. API_BENCHMARK_RUNS/2; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + let keys = (0 .. r) .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) .collect::>(); @@ -1284,7 +1421,7 @@ benchmarks! { value: key_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(0, max_key_len as u32), // key_ptr Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::Call(0)), @@ -1295,9 +1432,8 @@ benchmarks! { let instance = Contract::::new(code, vec![])?; let info = instance.info()?; for key in keys { - Storage::::write( - &info.trie_id, - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + info.write( + &Key::::try_from_var(key).map_err(|e| "Key has wrong length")?, Some(vec![]), None, false, @@ -1309,14 +1445,11 @@ benchmarks! { }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) #[skip_meta] - seal_contains_storage_per_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + #[pov_mode = Measured] + seal_contains_storage_per_byte { + let n in 0 .. T::Schedule::get().limits.payload_len; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) - .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); - h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) - .collect::>(); - let key_bytes = keys.iter().flatten().cloned().collect::>(); + let key = vec![0u8; max_key_len as usize]; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -1328,38 +1461,37 @@ benchmarks! { data_segments: vec![ DataSegment { offset: 0, - value: key_bytes, + value: key.clone(), }, ], - call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, max_key_len as u32), // key_ptr - Regular(Instruction::I32Const(max_key_len as i32)), // key_len - Regular(Instruction::Call(0)), - Regular(Instruction::Drop), + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // key_ptr + Instruction::I32Const(max_key_len as i32), // key_len + Instruction::Call(0), + Instruction::Drop, + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; let info = instance.info()?; - for key in keys { - Storage::::write( - &info.trie_id, - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, - Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len - None, - false, - ) - .map_err(|_| "Failed to write to storage during setup.")?; - } + info.write( + &Key::::try_from_var(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; n as usize]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) #[skip_meta] + #[pov_mode = Measured] seal_take_storage { - let r in 0 .. API_BENCHMARK_BATCHES/2; + let r in 0 .. API_BENCHMARK_RUNS/2; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. r * API_BENCHMARK_BATCH_SIZE) + let keys = (0 .. r) .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) .collect::>(); @@ -1383,7 +1515,7 @@ benchmarks! { value: T::Schedule::get().limits.payload_len.to_le_bytes().into(), }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(0, max_key_len as u32), // key_ptr Regular(Instruction::I32Const(max_key_len as i32)), // key_len Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr @@ -1396,9 +1528,8 @@ benchmarks! { let instance = Contract::::new(code, vec![])?; let info = instance.info()?; for key in keys { - Storage::::write( - &info.trie_id, - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, + info.write( + &Key::::try_from_var(key).map_err(|e| "Key has wrong length")?, Some(vec![]), None, false, @@ -1410,15 +1541,11 @@ benchmarks! { }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) #[skip_meta] - seal_take_storage_per_kb { - let n in 0 .. T::Schedule::get().limits.payload_len / 2048; // half of the max payload_len in kb + #[pov_mode = Measured] + seal_take_storage_per_byte { + let n in 0 .. T::Schedule::get().limits.payload_len; let max_key_len = T::MaxStorageKeyLen::get(); - let keys = (0 .. n * API_BENCHMARK_BATCH_SIZE) - .map(|n| { let mut h = T::Hashing::hash_of(&n).as_ref().to_vec(); - h.resize(max_key_len.try_into().unwrap(), n.to_le_bytes()[0]); h }) - .collect::>(); - let key_bytes = keys.iter().flatten().cloned().collect::>(); - let key_bytes_len = key_bytes.len(); + let key = vec![0u8; max_key_len as usize]; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -1430,43 +1557,42 @@ benchmarks! { data_segments: vec![ DataSegment { offset: 0, - value: key_bytes, + value: key.clone(), }, DataSegment { - offset: key_bytes_len as u32, + offset: max_key_len, value: T::Schedule::get().limits.payload_len.to_le_bytes().into(), }, ], - call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Counter(0, max_key_len as u32), // key_ptr - Regular(Instruction::I32Const(max_key_len as i32)), // key_len - Regular(Instruction::I32Const((key_bytes_len + 4) as i32)), // out_ptr - Regular(Instruction::I32Const(key_bytes_len as i32)), // out_len_ptr - Regular(Instruction::Call(0)), - Regular(Instruction::Drop), + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // key_ptr + Instruction::I32Const(max_key_len as i32), // key_len + Instruction::I32Const((max_key_len + 4) as i32), // out_ptr + Instruction::I32Const(max_key_len as i32), // out_len_ptr + Instruction::Call(0), + Instruction::Drop, + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; let info = instance.info()?; - for key in keys { - Storage::::write( - &info.trie_id, - &VarSizedKey::::try_from(key).map_err(|e| "Key has wrong length")?, - Some(vec![42u8; (n * 2048) as usize]), // value_len increments by 2kb up to max payload_len - None, - false, - ) - .map_err(|_| "Failed to write to storage during setup.")?; - } + info.write( + &Key::::try_from_var(key).map_err(|e| "Key has wrong length")?, + Some(vec![42u8; n as usize]), + None, + false, + ) + .map_err(|_| "Failed to write to storage during setup.")?; >::insert(&instance.account_id, info); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // We transfer to unique accounts. + #[pov_mode = Measured] seal_transfer { - let r in 0 .. API_BENCHMARK_BATCHES; - let accounts = (0..r * API_BENCHMARK_BATCH_SIZE) + let r in 0 .. API_BENCHMARK_RUNS; + let accounts = (0..r) .map(|i| account::("receiver", i, 0)) .collect::>(); let account_len = accounts.get(0).map(|i| i.encode().len()).unwrap_or(0); @@ -1493,7 +1619,7 @@ benchmarks! { value: account_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(value_len as u32, account_len as u32), // account_ptr Regular(Instruction::I32Const(account_len as i32)), // account_len Regular(Instruction::I32Const(0)), // value_ptr @@ -1504,39 +1630,52 @@ benchmarks! { .. Default::default() }); let instance = Contract::::new(code, vec![])?; - instance.set_balance(value * (r * API_BENCHMARK_BATCH_SIZE + 1).into()); + instance.set_balance(value * (r + 1).into()); let origin = RawOrigin::Signed(instance.caller.clone()); for account in &accounts { - assert_eq!(T::Currency::total_balance(account), 0u32.into()); + assert_eq!(>::total_balance(account), 0u32.into()); } }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) verify { for account in &accounts { - assert_eq!(T::Currency::total_balance(account), value); + assert_eq!(>::total_balance(account), value); } } // We call unique accounts. + // + // This is a slow call: We redeuce the number of runs. + #[pov_mode = Measured] seal_call { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS / 2; let dummy_code = WasmModule::::dummy_with_bytes(0); - let callees = (0..r * API_BENCHMARK_BATCH_SIZE) + let callees = (0..r) .map(|i| Contract::with_index(i + 1, dummy_code.clone(), vec![])) .collect::, _>>()?; let callee_len = callees.get(0).map(|i| i.account_id.encode().len()).unwrap_or(0); let callee_bytes = callees.iter().flat_map(|x| x.account_id.encode()).collect(); let value: BalanceOf = 0u32.into(); let value_bytes = value.encode(); - let value_len = value_bytes.len(); + let value_len = BalanceOf::::max_encoded_len() as u32; + // Set an own limit every 2nd call + let own_limit = (u32::MAX - 100).into(); + let deposits = (0..r) + .map(|i| if i % 2 == 0 { 0u32.into() } else { own_limit } ) + .collect::>>(); + let deposits_bytes: Vec = deposits.iter().flat_map(|i| i.encode()).collect(); + let deposits_len = deposits_bytes.len() as u32; + let deposit_len = value_len.clone(); + let callee_offset = value_len + deposits_len; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", - name: "seal_call", + module: "seal2", + name: "call", params: vec![ ValueType::I32, ValueType::I32, ValueType::I64, + ValueType::I64, ValueType::I32, ValueType::I32, ValueType::I32, @@ -1552,16 +1691,21 @@ benchmarks! { value: value_bytes, }, DataSegment { - offset: value_len as u32, + offset: value_len, + value: deposits_bytes, + }, + DataSegment { + offset: callee_offset, value: callee_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ - Counter(value_len as u32, callee_len as u32), // callee_ptr - Regular(Instruction::I32Const(callee_len as i32)), // callee_len - Regular(Instruction::I64Const(0)), // gas + call_body: Some(body::repeated_dyn(r, vec![ + Regular(Instruction::I32Const(0)), // flags + Counter(callee_offset, callee_len as u32), // callee_ptr + Regular(Instruction::I64Const(0)), // ref_time weight + Regular(Instruction::I64Const(0)), // proof_size weight + Counter(value_len, deposit_len as u32), // deposit_limit_ptr Regular(Instruction::I32Const(0)), // value_ptr - Regular(Instruction::I32Const(value_len as i32)), // value_len Regular(Instruction::I32Const(0)), // input_data_ptr Regular(Instruction::I32Const(0)), // input_data_len Regular(Instruction::I32Const(SENTINEL as i32)), // output_ptr @@ -1573,11 +1717,13 @@ benchmarks! { }); let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, Some(BalanceOf::::from(u32::MAX).into()), vec![]) + // This is a slow call: We redeuce the number of runs. + #[pov_mode = Measured] seal_delegate_call { - let r in 0 .. API_BENCHMARK_BATCHES; - let hashes = (0..r * API_BENCHMARK_BATCH_SIZE) + let r in 0 .. API_BENCHMARK_RUNS / 2; + let hashes = (0..r) .map(|i| { let code = WasmModule::::dummy_with_bytes(i); Contracts::::store_code_raw(code.code, whitelisted_caller())?; @@ -1610,7 +1756,7 @@ benchmarks! { value: hashes_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Regular(Instruction::I32Const(0)), // flags Counter(hashes_offset as u32, hash_len as u32), // code_hash_ptr Regular(Instruction::I32Const(0)), // input_data_ptr @@ -1627,14 +1773,11 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller); }: call(origin, callee, 0u32.into(), Weight::MAX, None, vec![]) - seal_call_per_transfer_clone_kb { + #[pov_mode = Measured] + seal_call_per_transfer_clone_byte { let t in 0 .. 1; - let c in 0 .. code::max_pages::() * 64; - let callees = (0..API_BENCHMARK_BATCH_SIZE) - .map(|i| Contract::with_index(i + 1, >::dummy(), vec![])) - .collect::, _>>()?; - let callee_len = callees.get(0).map(|i| i.account_id.encode().len()).unwrap_or(0); - let callee_bytes = callees.iter().flat_map(|x| x.account_id.encode()).collect::>(); + let c in 0 .. code::max_pages::() * 64 * 1024; + let callee = Contract::with_index(5, >::dummy(), vec![])?; let value: BalanceOf = t.into(); let value_bytes = value.encode(); let value_len = value_bytes.len(); @@ -1662,38 +1805,41 @@ benchmarks! { }, DataSegment { offset: value_len as u32, - value: callee_bytes, + value: callee.account_id.encode(), }, ], - call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Regular(Instruction::I32Const(CallFlags::CLONE_INPUT.bits() as i32)), // flags - Counter(value_len as u32, callee_len as u32), // callee_ptr - Regular(Instruction::I64Const(0)), // gas - Regular(Instruction::I32Const(0)), // value_ptr - Regular(Instruction::I32Const(0)), // input_data_ptr - Regular(Instruction::I32Const(0)), // input_data_len - Regular(Instruction::I32Const(SENTINEL as i32)), // output_ptr - Regular(Instruction::I32Const(0)), // output_len_ptr - Regular(Instruction::Call(0)), - Regular(Instruction::Drop), + call_body: Some(body::plain(vec![ + Instruction::I32Const(CallFlags::CLONE_INPUT.bits() as i32), // flags + Instruction::I32Const(value_len as i32), // callee_ptr + Instruction::I64Const(0), // gas + Instruction::I32Const(0), // value_ptr + Instruction::I32Const(0), // input_data_ptr + Instruction::I32Const(0), // input_data_len + Instruction::I32Const(SENTINEL as i32), // output_ptr + Instruction::I32Const(0), // output_len_ptr + Instruction::Call(0), + Instruction::Drop, + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); - let bytes = vec![42; (c * 1024) as usize]; + let bytes = vec![42; c as usize]; }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, bytes) // We assume that every instantiate sends at least the minimum balance. + // This is a slow call: we reduce the number of runs. + #[pov_mode = Measured] seal_instantiate { - let r in 0 .. API_BENCHMARK_BATCHES; - let hashes = (0..r * API_BENCHMARK_BATCH_SIZE) + let r in 1 .. API_BENCHMARK_RUNS / 2; + let hashes = (0..r) .map(|i| { let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), call_body: Some(body::plain(vec![ - // we need to add this in order to make contracts unique - // so that they can be deployed from the same sender + // We need to add this in order to make contracts unique, + // so that they can be deployed from the same sender. Instruction::I32Const(i as i32), Instruction::Drop, Instruction::End, @@ -1706,27 +1852,24 @@ benchmarks! { .collect::, &'static str>>()?; let hash_len = hashes.get(0).map(|x| x.encode().len()).unwrap_or(0); let hashes_bytes = hashes.iter().flat_map(|x| x.encode()).collect::>(); - let hashes_len = hashes_bytes.len(); + let hashes_len = &hashes_bytes.len(); let value = Pallet::::min_balance(); assert!(value > 0u32.into()); let value_bytes = value.encode(); - let value_len = value_bytes.len(); + let value_len = BalanceOf::::max_encoded_len(); let addr_len = T::AccountId::max_encoded_len(); - - // offsets where to place static data in contract memory - let value_offset = 0; - let hashes_offset = value_offset + value_len; + // Offsets where to place static data in contract memory. + let hashes_offset = value_len; let addr_len_offset = hashes_offset + hashes_len; let addr_offset = addr_len_offset + addr_len; - let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", - name: "seal_instantiate", + module: "seal2", + name: "instantiate", params: vec![ ValueType::I32, - ValueType::I32, + ValueType::I64, ValueType::I64, ValueType::I32, ValueType::I32, @@ -1743,7 +1886,7 @@ benchmarks! { }], data_segments: vec![ DataSegment { - offset: value_offset as u32, + offset: 0, value: value_bytes, }, DataSegment { @@ -1755,12 +1898,12 @@ benchmarks! { value: addr_len.to_le_bytes().into(), }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(hashes_offset as u32, hash_len as u32), // code_hash_ptr - Regular(Instruction::I32Const(hash_len as i32)), // code_hash_len - Regular(Instruction::I64Const(0)), // gas - Regular(Instruction::I32Const(value_offset as i32)), // value_ptr - Regular(Instruction::I32Const(value_len as i32)), // value_len + Regular(Instruction::I64Const(0)), // ref_time weight + Regular(Instruction::I64Const(0)), // proof_size weight + Regular(Instruction::I32Const(SENTINEL as i32)), // deposit limit ptr: use parent's limit + Regular(Instruction::I32Const(0)), // value_ptr Regular(Instruction::I32Const(0)), // input_data_ptr Regular(Instruction::I32Const(0)), // input_data_len Regular(Instruction::I32Const(addr_offset as i32)), // address_ptr @@ -1768,14 +1911,14 @@ benchmarks! { Regular(Instruction::I32Const(SENTINEL as i32)), // output_ptr Regular(Instruction::I32Const(0)), // output_len_ptr Regular(Instruction::I32Const(0)), // salt_ptr - Regular(Instruction::I32Const(0)), // salt_ptr_len + Regular(Instruction::I32Const(0)), // salt_len_ptr Regular(Instruction::Call(0)), Regular(Instruction::Drop), ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; - instance.set_balance(value * (r * API_BENCHMARK_BATCH_SIZE + 1).into()); + instance.set_balance((value + Pallet::::min_balance()) * (r + 1).into()); let origin = RawOrigin::Signed(instance.caller.clone()); let callee = instance.addr.clone(); let addresses = hashes @@ -1798,37 +1941,25 @@ benchmarks! { } } - seal_instantiate_per_transfer_input_salt_kb { + #[pov_mode = Measured] + seal_instantiate_per_transfer_input_salt_byte { let t in 0 .. 1; - let i in 0 .. (code::max_pages::() - 1) * 64; - let s in 0 .. (code::max_pages::() - 1) * 64; + let i in 0 .. (code::max_pages::() - 1) * 64 * 1024; + let s in 0 .. (code::max_pages::() - 1) * 64 * 1024; let callee_code = WasmModule::::dummy(); let hash = callee_code.hash; let hash_bytes = callee_code.hash.encode(); let hash_len = hash_bytes.len(); Contracts::::store_code_raw(callee_code.code, whitelisted_caller())?; - let salts = (0..API_BENCHMARK_BATCH_SIZE).map(|x| x.encode()).collect::>(); - let salt_len = salts.get(0).map(|x| x.len()).unwrap_or(0); - let salt_bytes = salts.iter().cloned().flatten().collect::>(); - let salts_len = salt_bytes.len(); - let value: BalanceOf = t.into(); + let value: BalanceOf = t.into(); let value_bytes = value.encode(); - let value_len = value_bytes.len(); - let addr_len = T::AccountId::max_encoded_len(); - - // offsets where to place static data in contract memory - let salt_offset = 0; - let value_offset = salts_len; - let hash_offset = value_offset + value_len; - let addr_len_offset = hash_offset + hash_len; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { - module: "seal0", + module: "seal1", name: "seal_instantiate", params: vec![ - ValueType::I32, ValueType::I32, ValueType::I64, ValueType::I32, @@ -1840,137 +1971,242 @@ benchmarks! { ValueType::I32, ValueType::I32, ValueType::I32, - ValueType::I32, ], return_type: Some(ValueType::I32), }], data_segments: vec![ DataSegment { - offset: salt_offset as u32, - value: salt_bytes, - }, - DataSegment { - offset: value_offset as u32, - value: value_bytes, - }, - DataSegment { - offset: hash_offset as u32, + offset: 0, value: hash_bytes, }, DataSegment { - offset: addr_len_offset as u32, - value: (addr_len as u32).to_le_bytes().into(), + offset: hash_len as u32, + value: value_bytes, }, ], - call_body: Some(body::repeated_dyn(API_BENCHMARK_BATCH_SIZE, vec![ - Regular(Instruction::I32Const(hash_offset as i32)), // code_hash_ptr - Regular(Instruction::I32Const(hash_len as i32)), // code_hash_len - Regular(Instruction::I64Const(0)), // gas - Regular(Instruction::I32Const(value_offset as i32)), // value_ptr - Regular(Instruction::I32Const(value_len as i32)), // value_len - Counter(salt_offset as u32, salt_len as u32), // input_data_ptr - Regular(Instruction::I32Const((i * 1024) as i32)), // input_data_len - Regular(Instruction::I32Const((addr_len_offset + addr_len) as i32)), // address_ptr - Regular(Instruction::I32Const(addr_len_offset as i32)), // address_len_ptr - Regular(Instruction::I32Const(SENTINEL as i32)), // output_ptr - Regular(Instruction::I32Const(0)), // output_len_ptr - Counter(salt_offset as u32, salt_len as u32), // salt_ptr - Regular(Instruction::I32Const((s * 1024) as i32)), // salt_len - Regular(Instruction::Call(0)), - Regular(Instruction::I32Eqz), - Regular(Instruction::If(BlockType::NoResult)), - Regular(Instruction::Nop), - Regular(Instruction::Else), - Regular(Instruction::Unreachable), - Regular(Instruction::End), + call_body: Some(body::plain(vec![ + Instruction::I32Const(0 as i32), // code_hash_ptr + Instruction::I64Const(0), // gas + Instruction::I32Const(hash_len as i32), // value_ptr + Instruction::I32Const(0 as i32), // input_data_ptr + Instruction::I32Const(i as i32), // input_data_len + Instruction::I32Const(SENTINEL as i32), // address_ptr + Instruction::I32Const(0), // address_len_ptr + Instruction::I32Const(SENTINEL as i32), // output_ptr + Instruction::I32Const(0), // output_len_ptr + Instruction::I32Const(0 as i32), // salt_ptr + Instruction::I32Const(s as i32), // salt_len + Instruction::Call(0), + Instruction::I32Eqz, + Instruction::If(BlockType::NoResult), + Instruction::Nop, + Instruction::Else, + Instruction::Unreachable, + Instruction::End, + Instruction::End, ])), .. Default::default() }); let instance = Contract::::new(code, vec![])?; - instance.set_balance(value * (API_BENCHMARK_BATCH_SIZE + 1).into()); + instance.set_balance(value + (Pallet::::min_balance() * 2u32.into())); let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // Only the overhead of calling the function itself with minimal arguments. + #[pov_mode = Measured] seal_hash_sha2_256 { - let r in 0 .. 1; + let r in 0 .. API_BENCHMARK_RUNS; let instance = Contract::::new(WasmModule::hasher( - "seal_hash_sha2_256", r * API_BENCHMARK_BATCH_SIZE, 0, + "seal_hash_sha2_256", r, 0, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - // `n`: Input to hash in kilobytes - seal_hash_sha2_256_per_kb { - let n in 0 .. code::max_pages::() * 64; + // `n`: Input to hash in bytes + #[pov_mode = Measured] + seal_hash_sha2_256_per_byte { + let n in 0 .. code::max_pages::() * 64 * 1024; let instance = Contract::::new(WasmModule::hasher( - "seal_hash_sha2_256", API_BENCHMARK_BATCH_SIZE, n * 1024, + "seal_hash_sha2_256", 1, n, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // Only the overhead of calling the function itself with minimal arguments. + #[pov_mode = Measured] seal_hash_keccak_256 { - let r in 0 .. 1; + let r in 0 .. API_BENCHMARK_RUNS; let instance = Contract::::new(WasmModule::hasher( - "seal_hash_keccak_256", r * API_BENCHMARK_BATCH_SIZE, 0, + "seal_hash_keccak_256", r, 0, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - // `n`: Input to hash in kilobytes - seal_hash_keccak_256_per_kb { - let n in 0 .. code::max_pages::() * 64; + // `n`: Input to hash in bytes + #[pov_mode = Measured] + seal_hash_keccak_256_per_byte { + let n in 0 .. code::max_pages::() * 64 * 1024; let instance = Contract::::new(WasmModule::hasher( - "seal_hash_keccak_256", API_BENCHMARK_BATCH_SIZE, n * 1024, + "seal_hash_keccak_256", 1, n, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // Only the overhead of calling the function itself with minimal arguments. + #[pov_mode = Measured] seal_hash_blake2_256 { - let r in 0 .. 1; + let r in 0 .. API_BENCHMARK_RUNS; let instance = Contract::::new(WasmModule::hasher( - "seal_hash_blake2_256", r * API_BENCHMARK_BATCH_SIZE, 0, + "seal_hash_blake2_256", r, 0, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - // `n`: Input to hash in kilobytes - seal_hash_blake2_256_per_kb { - let n in 0 .. code::max_pages::() * 64; + // `n`: Input to hash in bytes + #[pov_mode = Measured] + seal_hash_blake2_256_per_byte { + let n in 0 .. code::max_pages::() * 64 * 1024; let instance = Contract::::new(WasmModule::hasher( - "seal_hash_blake2_256", API_BENCHMARK_BATCH_SIZE, n * 1024, + "seal_hash_blake2_256", 1, n, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // Only the overhead of calling the function itself with minimal arguments. + #[pov_mode = Measured] seal_hash_blake2_128 { - let r in 0 .. 1; + let r in 0 .. API_BENCHMARK_RUNS; let instance = Contract::::new(WasmModule::hasher( - "seal_hash_blake2_128", r * API_BENCHMARK_BATCH_SIZE, 0, + "seal_hash_blake2_128", r, 0, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) - // `n`: Input to hash in kilobytes - seal_hash_blake2_128_per_kb { - let n in 0 .. code::max_pages::() * 64; + // `n`: Input to hash in bytes + #[pov_mode = Measured] + seal_hash_blake2_128_per_byte { + let n in 0 .. code::max_pages::() * 64 * 1024; let instance = Contract::::new(WasmModule::hasher( - "seal_hash_blake2_128", API_BENCHMARK_BATCH_SIZE, n * 1024, + "seal_hash_blake2_128", 1, n, ), vec![])?; let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + // `n`: Message input length to verify in bytes. + #[pov_mode = Measured] + seal_sr25519_verify_per_byte { + let n in 0 .. T::MaxCodeLen::get() - 255; // need some buffer so the code size does not + // exceed the max code size. + + let message = (0..n).zip((32u8..127u8).cycle()).map(|(_, c)| c).collect::>(); + let message_len = message.len() as i32; + + let key_type = sp_core::crypto::KeyTypeId(*b"code"); + let pub_key = sp_io::crypto::sr25519_generate(key_type, None); + let sig = sp_io::crypto::sr25519_sign(key_type, &pub_key, &message).expect("Generates signature"); + let sig = AsRef::<[u8; 64]>::as_ref(&sig).to_vec(); + + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal0", + name: "sr25519_verify", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: sig, + }, + DataSegment { + offset: 64, + value: pub_key.to_vec(), + }, + DataSegment { + offset: 96, + value: message, + }, + ], + call_body: Some(body::plain(vec![ + Instruction::I32Const(0), // signature_ptr + Instruction::I32Const(64), // pub_key_ptr + Instruction::I32Const(message_len), // message_len + Instruction::I32Const(96), // message_ptr + Instruction::Call(0), + Instruction::Drop, + Instruction::End, + ])), + .. Default::default() + }); + + let instance = Contract::::new(code, vec![])?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + // Only calling the function itself with valid arguments. // It generates different private keys and signatures for the message "Hello world". + // This is a slow call: We reduce the number of runs. + #[pov_mode = Measured] + seal_sr25519_verify { + let r in 0 .. API_BENCHMARK_RUNS / 10; + + let message = b"Hello world".to_vec(); + let message_len = message.len() as i32; + let key_type = sp_core::crypto::KeyTypeId(*b"code"); + let sig_params = (0..r) + .map(|i| { + let pub_key = sp_io::crypto::sr25519_generate(key_type, None); + let sig = sp_io::crypto::sr25519_sign(key_type, &pub_key, &message).expect("Generates signature"); + let data: [u8; 96] = [AsRef::<[u8]>::as_ref(&sig), AsRef::<[u8]>::as_ref(&pub_key)].concat().try_into().unwrap(); + data + }) + .flatten() + .collect::>(); + let sig_params_len = sig_params.len() as i32; + + let code = WasmModule::::from(ModuleDefinition { + memory: Some(ImportedMemory::max::()), + imported_functions: vec![ImportedFunction { + module: "seal0", + name: "sr25519_verify", + params: vec![ValueType::I32, ValueType::I32, ValueType::I32, ValueType::I32], + return_type: Some(ValueType::I32), + }], + data_segments: vec![ + DataSegment { + offset: 0, + value: sig_params + }, + DataSegment { + offset: sig_params_len as u32, + value: message, + }, + ], + call_body: Some(body::repeated_dyn(r, vec![ + Counter(0, 96), // signature_ptr + Counter(64, 96), // pub_key_ptr + Regular(Instruction::I32Const(message_len)), // message_len + Regular(Instruction::I32Const(sig_params_len)), // message_ptr + Regular(Instruction::Call(0)), + Regular(Instruction::Drop), + ])), + .. Default::default() + }); + let instance = Contract::::new(code, vec![])?; + let origin = RawOrigin::Signed(instance.caller.clone()); + }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + + // Only calling the function itself with valid arguments. + // It generates different private keys and signatures for the message "Hello world". + // This is a slow call: We reduce the number of runs. + #[pov_mode = Measured] seal_ecdsa_recover { - let r in 0 .. 1; + let r in 0 .. API_BENCHMARK_RUNS / 10; let message_hash = sp_io::hashing::blake2_256("Hello world".as_bytes()); let key_type = sp_core::crypto::KeyTypeId(*b"code"); - let signatures = (0..r * API_BENCHMARK_BATCH_SIZE) + let signatures = (0..r) .map(|i| { let pub_key = sp_io::crypto::ecdsa_generate(key_type, None); let sig = sp_io::crypto::ecdsa_sign_prehashed(key_type, &pub_key, &message_hash).expect("Generates signature"); @@ -1998,7 +2234,7 @@ benchmarks! { value: signatures, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(32, 65), // signature_ptr Regular(Instruction::I32Const(0)), // message_hash_ptr Regular(Instruction::I32Const(signatures_bytes_len + 32)), // output_len_ptr @@ -2013,10 +2249,12 @@ benchmarks! { // Only calling the function itself for the list of // generated different ECDSA keys. + // This is a slow call: We redeuce the number of runs. + #[pov_mode = Measured] seal_ecdsa_to_eth_address { - let r in 0 .. 1; + let r in 0 .. API_BENCHMARK_RUNS / 10; let key_type = sp_core::crypto::KeyTypeId(*b"code"); - let pub_keys_bytes = (0..r * API_BENCHMARK_BATCH_SIZE) + let pub_keys_bytes = (0..r) .flat_map(|_| { sp_io::crypto::ecdsa_generate(key_type, None).0 }) @@ -2036,7 +2274,7 @@ benchmarks! { value: pub_keys_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(0, 33), // pub_key_ptr Regular(Instruction::I32Const(pub_keys_bytes_len)), // out_ptr Regular(Instruction::Call(0)), @@ -2048,9 +2286,10 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + #[pov_mode = Measured] seal_set_code_hash { - let r in 0 .. API_BENCHMARK_BATCHES; - let code_hashes = (0..r * API_BENCHMARK_BATCH_SIZE) + let r in 0 .. API_BENCHMARK_RUNS; + let code_hashes = (0..r) .map(|i| { let new_code = WasmModule::::dummy_with_bytes(i); Contracts::::store_code_raw(new_code.code, whitelisted_caller())?; @@ -2077,7 +2316,7 @@ benchmarks! { value: code_hashes_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(0, code_hash_len as u32), // code_hash_ptr Regular(Instruction::Call(0)), Regular(Instruction::Drop), @@ -2088,8 +2327,9 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + #[pov_mode = Measured] seal_reentrance_count { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -2098,7 +2338,7 @@ benchmarks! { params: vec![], return_type: Some(ValueType::I32), }], - call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r, &[ Instruction::Call(0), Instruction::Drop, ])), @@ -2108,10 +2348,11 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + #[pov_mode = Measured] seal_account_reentrance_count { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let dummy_code = WasmModule::::dummy_with_bytes(0); - let accounts = (0..r * API_BENCHMARK_BATCH_SIZE) + let accounts = (0..r) .map(|i| Contract::with_index(i + 1, dummy_code.clone(), vec![])) .collect::, _>>()?; let account_id_len = accounts.get(0).map(|i| i.account_id.encode().len()).unwrap_or(0); @@ -2130,7 +2371,7 @@ benchmarks! { value: account_id_bytes, }, ], - call_body: Some(body::repeated_dyn(r * API_BENCHMARK_BATCH_SIZE, vec![ + call_body: Some(body::repeated_dyn(r, vec![ Counter(0, account_id_len as u32), // account_ptr Regular(Instruction::Call(0)), Regular(Instruction::Drop), @@ -2141,8 +2382,9 @@ benchmarks! { let origin = RawOrigin::Signed(instance.caller.clone()); }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) + #[pov_mode = Measured] seal_instantiation_nonce { - let r in 0 .. API_BENCHMARK_BATCHES; + let r in 0 .. API_BENCHMARK_RUNS; let code = WasmModule::::from(ModuleDefinition { memory: Some(ImportedMemory::max::()), imported_functions: vec![ImportedFunction { @@ -2151,7 +2393,7 @@ benchmarks! { params: vec![], return_type: Some(ValueType::I64), }], - call_body: Some(body::repeated(r * API_BENCHMARK_BATCH_SIZE, &[ + call_body: Some(body::repeated(r, &[ Instruction::Call(0), Instruction::Drop, ])), @@ -2162,115 +2404,17 @@ benchmarks! { }: call(origin, instance.addr, 0u32.into(), Weight::MAX, None, vec![]) // We make the assumption that pushing a constant and dropping a value takes roughly - // the same amount of time. We follow that `t.load` and `drop` both have the weight - // of this benchmark / 2. We need to make this assumption because there is no way - // to measure them on their own using a valid wasm module. We need their individual - // values to derive the weight of individual instructions (by substraction) from - // benchmarks that include those for parameter pushing and return type dropping. - // We call the weight of `t.load` and `drop`: `w_param`. + // the same amount of time. We call this weight `w_base`. // The weight that would result from the respective benchmark we call: `w_bench`. // - // w_i{32,64}const = w_drop = w_bench / 2 + // w_base = w_i{32,64}const = w_drop = w_bench / 2 + #[pov_mode = Ignored] instr_i64const { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ - RandomI64Repeated(1), - Regular(Instruction::Drop), - ])), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_i{32,64}load = w_bench - 2 * w_param - instr_i64load { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - memory: Some(ImportedMemory::max::()), - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ - RandomUnaligned(0, code::max_pages::() * 64 * 1024 - 8), - Regular(Instruction::I64Load(3, 0)), - Regular(Instruction::Drop), - ])), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_i{32,64}store{...} = w_bench - 2 * w_param - instr_i64store { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - memory: Some(ImportedMemory::max::()), - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ - RandomUnaligned(0, code::max_pages::() * 64 * 1024 - 8), - RandomI64Repeated(1), - Regular(Instruction::I64Store(3, 0)), - ])), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_select = w_bench - 4 * w_param - instr_select { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ - RandomI64Repeated(1), - RandomI64Repeated(1), - RandomI32(0, 2), - Regular(Instruction::Select), - Regular(Instruction::Drop), - ])), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_if = w_bench - 3 * w_param - instr_if { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ - RandomI32(0, 2), - Regular(Instruction::If(BlockType::Value(ValueType::I64))), - RandomI64Repeated(1), - Regular(Instruction::Else), - RandomI64Repeated(1), - Regular(Instruction::End), - Regular(Instruction::Drop), - ])), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_br = w_bench - 2 * w_param - // Block instructions are not counted. - instr_br { - let r in 0 .. INSTR_BENCHMARK_BATCHES; + let r in 0 .. INSTR_BENCHMARK_RUNS; let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ - Regular(Instruction::Block(BlockType::NoResult)), - Regular(Instruction::Block(BlockType::NoResult)), - Regular(Instruction::Block(BlockType::NoResult)), - Regular(Instruction::Br(1)), - RandomI64Repeated(1), - Regular(Instruction::Drop), - Regular(Instruction::End), - RandomI64Repeated(1), - Regular(Instruction::Drop), - Regular(Instruction::End), + call_body: Some(body::repeated_dyn(r, vec![ RandomI64Repeated(1), Regular(Instruction::Drop), - Regular(Instruction::End), ])), .. Default::default() })); @@ -2278,694 +2422,39 @@ benchmarks! { sbox.invoke(); } - // w_br_if = w_bench - 3 * w_param - // Block instructions are not counted. - instr_br_if { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ - Regular(Instruction::Block(BlockType::NoResult)), - Regular(Instruction::Block(BlockType::NoResult)), - Regular(Instruction::Block(BlockType::NoResult)), - Regular(Instruction::I32Const(1)), - Regular(Instruction::BrIf(1)), - RandomI64Repeated(1), - Regular(Instruction::Drop), - Regular(Instruction::End), - RandomI64Repeated(1), - Regular(Instruction::Drop), - Regular(Instruction::End), - RandomI64Repeated(1), - Regular(Instruction::Drop), - Regular(Instruction::End), - ])), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_br_table = w_bench - 3 * w_param - // Block instructions are not counted. - instr_br_table { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let table = Box::new(BrTableData { - table: Box::new([1, 1, 1]), - default: 1, - }); - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ - Regular(Instruction::Block(BlockType::NoResult)), - Regular(Instruction::Block(BlockType::NoResult)), - Regular(Instruction::Block(BlockType::NoResult)), - RandomI32(0, 4), - Regular(Instruction::BrTable(table)), - RandomI64Repeated(1), - Regular(Instruction::Drop), - Regular(Instruction::End), - RandomI64Repeated(1), - Regular(Instruction::Drop), - Regular(Instruction::End), - RandomI64Repeated(1), - Regular(Instruction::Drop), - Regular(Instruction::End), - ])), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_br_table_per_entry = w_bench - instr_br_table_per_entry { - let e in 1 .. T::Schedule::get().limits.br_table_size; - let entry: Vec = [0, 1].iter() - .cloned() - .cycle() - .take((e / 2) as usize).collect(); - let table = Box::new(BrTableData { - table: entry.into_boxed_slice(), - default: 0, - }); - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(INSTR_BENCHMARK_BATCH_SIZE, vec![ - Regular(Instruction::Block(BlockType::NoResult)), - Regular(Instruction::Block(BlockType::NoResult)), - Regular(Instruction::Block(BlockType::NoResult)), - RandomI32(0, (e + 1) as i32), // Make sure the default entry is also used - Regular(Instruction::BrTable(table)), - RandomI64Repeated(1), - Regular(Instruction::Drop), - Regular(Instruction::End), - RandomI64Repeated(1), - Regular(Instruction::Drop), - Regular(Instruction::End), - RandomI64Repeated(1), - Regular(Instruction::Drop), - Regular(Instruction::End), - ])), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_call = w_bench - 2 * w_param - instr_call { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - // We need to make use of the stack here in order to trigger stack height - // instrumentation. - aux_body: Some(body::plain(vec![ - Instruction::I64Const(42), - Instruction::Drop, - Instruction::End, - ])), - call_body: Some(body::repeated(r * INSTR_BENCHMARK_BATCH_SIZE, &[ - Instruction::Call(2), // call aux - ])), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_call_indrect = w_bench - 3 * w_param - instr_call_indirect { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let num_elements = T::Schedule::get().limits.table_size; - use self::code::TableSegment; - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - // We need to make use of the stack here in order to trigger stack height - // instrumentation. - aux_body: Some(body::plain(vec![ - Instruction::I64Const(42), - Instruction::Drop, - Instruction::End, - ])), - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ - RandomI32(0, num_elements as i32), - Regular(Instruction::CallIndirect(0, 0)), // we only have one sig: 0 - ])), - table: Some(TableSegment { - num_elements, - function_index: 2, // aux - }), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_instr_call_indirect_per_param = w_bench - 1 * w_param - // Calling a function indirectly causes it to go through a thunk function whose runtime - // linearly depend on the amount of parameters to this function. - // Please note that this is not necessary with a direct call. - instr_call_indirect_per_param { - let p in 0 .. T::Schedule::get().limits.parameters; - let num_elements = T::Schedule::get().limits.table_size; - use self::code::TableSegment; - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - // We need to make use of the stack here in order to trigger stack height - // instrumentation. - aux_body: Some(body::plain(vec![ - Instruction::I64Const(42), - Instruction::Drop, - Instruction::End, - ])), - aux_arg_num: p, - call_body: Some(body::repeated_dyn(INSTR_BENCHMARK_BATCH_SIZE, vec![ - RandomI64Repeated(p as usize), - RandomI32(0, num_elements as i32), - Regular(Instruction::CallIndirect(p.min(1), 0)), // aux signature: 1 or 0 - ])), - table: Some(TableSegment { - num_elements, - function_index: 2, // aux - }), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_per_local = w_bench - instr_call_per_local { - let l in 0 .. T::Schedule::get().limits.locals; - let mut aux_body = body::plain(vec![ - Instruction::End, - ]); - body::inject_locals(&mut aux_body, l); - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - aux_body: Some(aux_body), - call_body: Some(body::repeated(INSTR_BENCHMARK_BATCH_SIZE, &[ - Instruction::Call(2), // call aux - ])), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_local_get = w_bench - 1 * w_param - instr_local_get { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let max_locals = T::Schedule::get().limits.locals; - let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ - RandomGetLocal(0, max_locals), - Regular(Instruction::Drop), - ]); - body::inject_locals(&mut call_body, max_locals); - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(call_body), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_local_set = w_bench - 1 * w_param - instr_local_set { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let max_locals = T::Schedule::get().limits.locals; - let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ - RandomI64Repeated(1), - RandomSetLocal(0, max_locals), - ]); - body::inject_locals(&mut call_body, max_locals); - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(call_body), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_local_tee = w_bench - 2 * w_param - instr_local_tee { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let max_locals = T::Schedule::get().limits.locals; - let mut call_body = body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ - RandomI64Repeated(1), - RandomTeeLocal(0, max_locals), - Regular(Instruction::Drop), - ]); - body::inject_locals(&mut call_body, max_locals); - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(call_body), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_global_get = w_bench - 1 * w_param - instr_global_get { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let max_globals = T::Schedule::get().limits.globals; - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ - RandomGetGlobal(0, max_globals), - Regular(Instruction::Drop), - ])), - num_globals: max_globals, - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_global_set = w_bench - 1 * w_param - instr_global_set { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let max_globals = T::Schedule::get().limits.globals; - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ - RandomI64Repeated(1), - RandomSetGlobal(0, max_globals), - ])), - num_globals: max_globals, - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_memory_get = w_bench - 1 * w_param - instr_memory_current { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - memory: Some(ImportedMemory::max::()), - call_body: Some(body::repeated(r * INSTR_BENCHMARK_BATCH_SIZE, &[ - Instruction::CurrentMemory(0), - Instruction::Drop - ])), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // w_memory_grow = w_bench - 2 * w_param - // We can only allow allocate as much memory as it is allowed in a contract. - // Therefore the repeat count is limited by the maximum memory any contract can have. - // Using a contract with more memory will skew the benchmark because the runtime of grow - // depends on how much memory is already allocated. - instr_memory_grow { - let r in 0 .. 1; - let max_pages = ImportedMemory::max::().max_pages; - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - memory: Some(ImportedMemory { - min_pages: 0, - max_pages, - }), - call_body: Some(body::repeated(r * max_pages, &[ - Instruction::I32Const(1), - Instruction::GrowMemory(0), - Instruction::Drop, - ])), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - // Unary numeric instructions. - // All use w = w_bench - 2 * w_param. - - instr_i64clz { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::unary_instr( - Instruction::I64Clz, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64ctz { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::unary_instr( - Instruction::I64Ctz, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64popcnt { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::unary_instr( - Instruction::I64Popcnt, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64eqz { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::unary_instr( - Instruction::I64Eqz, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64extendsi32 { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ - RandomI32Repeated(1), - Regular(Instruction::I64ExtendSI32), - Regular(Instruction::Drop), - ])), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - instr_i64extendui32 { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::from(ModuleDefinition { - call_body: Some(body::repeated_dyn(r * INSTR_BENCHMARK_BATCH_SIZE, vec![ - RandomI32Repeated(1), - Regular(Instruction::I64ExtendUI32), - Regular(Instruction::Drop), - ])), - .. Default::default() - })); - }: { - sbox.invoke(); - } - - instr_i32wrapi64 { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::unary_instr( - Instruction::I32WrapI64, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - // Binary numeric instructions. - // All use w = w_bench - 3 * w_param. - - instr_i64eq { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64Eq, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64ne { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64Ne, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64lts { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64LtS, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64ltu { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64LtU, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64gts { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64GtS, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64gtu { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64GtU, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64les { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64LeS, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64leu { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64LeU, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64ges { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64GeS, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64geu { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64GeU, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64add { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64Add, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64sub { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64Sub, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64mul { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64Mul, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64divs { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64DivS, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64divu { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64DivU, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64rems { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64RemS, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64remu { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64RemU, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64and { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64And, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64or { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64Or, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64xor { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64Xor, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64shl { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64Shl, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64shrs { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64ShrS, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64shru { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64ShrU, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64rotl { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64Rotl, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - instr_i64rotr { - let r in 0 .. INSTR_BENCHMARK_BATCHES; - let mut sbox = Sandbox::from(&WasmModule::::binary_instr( - Instruction::I64Rotr, - r * INSTR_BENCHMARK_BATCH_SIZE, - )); - }: { - sbox.invoke(); - } - - // This is no benchmark. It merely exist to have an easy way to pretty print the curently + // This is no benchmark. It merely exist to have an easy way to pretty print the currently // configured `Schedule` during benchmark development. - // It can be outputed using the following command: - // cargo run --manifest-path=bin/node/cli/Cargo.toml --release \ + // It can be outputted using the following command: + // cargo run --manifest-path=bin/node/cli/Cargo.toml \ // --features runtime-benchmarks -- benchmark pallet --extra --dev --execution=native \ // -p pallet_contracts -e print_schedule --no-median-slopes --no-min-squares #[extra] + #[pov_mode = Ignored] print_schedule { #[cfg(feature = "std")] { - let weight_per_key = T::WeightInfo::on_initialize_per_trie_key(1) - - T::WeightInfo::on_initialize_per_trie_key(0); - let weight_per_queue_item = T::WeightInfo::on_initialize_per_queue_item(1) - - T::WeightInfo::on_initialize_per_queue_item(0); - let weight_limit = T::DeletionWeightLimit::get(); - let queue_depth: u64 = T::DeletionQueueDepth::get().into(); + let max_weight = ::BlockWeights::get().max_block; + let (weight_per_key, key_budget) = ContractInfo::::deletion_budget(max_weight); println!("{:#?}", Schedule::::default()); println!("###############################################"); - println!("Lazy deletion throughput per block (empty queue, full queue): {}, {}", - weight_limit / weight_per_key.ref_time(), - (weight_limit - weight_per_queue_item * queue_depth) / weight_per_key.ref_time(), - ); + println!("Lazy deletion weight per key: {weight_per_key}"); + println!("Lazy deletion throughput per block: {key_budget}"); } #[cfg(not(feature = "std"))] Err("Run this bench with a native runtime in order to see the schedule.")?; }: {} // Execute one erc20 transfer using the ink! erc20 example contract. - // - // `g` is used to enable gas instrumentation to compare the performance impact of - // that instrumentation at runtime. #[extra] + #[pov_mode = Measured] ink_erc20_transfer { - let g in 0 .. 1; - let gas_metering = g != 0; let code = load_benchmark!("ink_erc20"); let data = { let new: ([u8; 4], BalanceOf) = ([0x9b, 0xae, 0x9d, 0x5e], 1000u32.into()); new.encode() }; let instance = Contract::::new( - WasmModule::instrumented(code, gas_metering), data, + WasmModule::from_code(code), data, )?; let data = { let transfer: ([u8; 4], AccountIdOf, BalanceOf) = ( @@ -2983,20 +2472,17 @@ benchmarks! { Weight::MAX, None, data, - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result?; } // Execute one erc20 transfer using the open zeppelin erc20 contract compiled with solang. - // - // `g` is used to enable gas instrumentation to compare the performance impact of - // that instrumentation at runtime. #[extra] + #[pov_mode = Measured] solang_erc20_transfer { - let g in 0 .. 1; - let gas_metering = g != 0; let code = include_bytes!("../../benchmarks/solang_erc20.wasm"); let caller = account::("instantiator", 0, 0); let mut balance = [0u8; 32]; @@ -3012,7 +2498,7 @@ benchmarks! { new.encode() }; let instance = Contract::::with_caller( - caller, WasmModule::instrumented(code, gas_metering), data, + caller, WasmModule::from_code(code), data, )?; balance[0] = 1; let data = { @@ -3031,8 +2517,9 @@ benchmarks! { Weight::MAX, None, data, - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result?; } diff --git a/frame/contracts/src/benchmarking/sandbox.rs b/frame/contracts/src/benchmarking/sandbox.rs index a6a97d11acbe..34974b02ea0c 100644 --- a/frame/contracts/src/benchmarking/sandbox.rs +++ b/frame/contracts/src/benchmarking/sandbox.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,13 +15,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -/// ! For instruction benchmarking we do no instantiate a full contract but merely the -/// ! sandbox to execute the wasm code. This is because we do not need the full +/// ! For instruction benchmarking we do not instantiate a full contract but merely the +/// ! sandbox to execute the Wasm code. This is because we do not need the full /// ! environment that provides the seal interface as imported functions. use super::{code::WasmModule, Config}; use crate::wasm::{ - AllowDeprecatedInterface, AllowUnstableInterface, Environment, PrefabWasmModule, + AllowDeprecatedInterface, AllowUnstableInterface, Determinism, Environment, WasmBlob, }; +use sp_core::Get; use wasmi::{errors::LinkerError, Func, Linker, StackLimits, Store}; /// Minimal execution environment without any imported functions. @@ -38,23 +39,25 @@ impl Sandbox { } impl From<&WasmModule> for Sandbox { - /// Creates an instance from the supplied module and supplies as much memory - /// to the instance as the module declares as imported. + /// Creates an instance from the supplied module. + /// Sets the execution engine fuel level to `u64::MAX`. fn from(module: &WasmModule) -> Self { - let memory = module - .memory - .as_ref() - .map(|mem| (mem.min_pages, mem.max_pages)) - .unwrap_or((0, 0)); - let (store, _memory, instance) = PrefabWasmModule::::instantiate::( + let (mut store, _memory, instance) = WasmBlob::::instantiate::( &module.code, (), - memory, + &::Schedule::get(), + Determinism::Relaxed, StackLimits::default(), // We are testing with an empty environment anyways AllowDeprecatedInterface::No, ) .expect("Failed to create benchmarking Sandbox instance"); + + // Set fuel for wasmi execution. + store + .add_fuel(u64::MAX) + .expect("We've set up engine to fuel consuming mode; qed"); + let entry_point = instance.get_export(&store, "call").unwrap().into_func().unwrap(); Self { entry_point, store } } diff --git a/frame/contracts/src/chain_extension.rs b/frame/contracts/src/chain_extension.rs index 3974bdba30d1..6d1f3df90f23 100644 --- a/frame/contracts/src/chain_extension.rs +++ b/frame/contracts/src/chain_extension.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -66,12 +66,11 @@ //! //! # Example //! -//! The ink! repository maintains an -//! [end-to-end example](https://github.com/paritytech/ink/tree/master/examples/rand-extension) +//! The ink-examples repository maintains an +//! [end-to-end example](https://github.com/paritytech/ink-examples/tree/main/rand-extension) //! on how to use a chain extension in order to provide new features to ink! contracts. use crate::{ - gas::ChargedAmount, wasm::{Runtime, RuntimeCosts}, Error, }; @@ -80,7 +79,7 @@ use frame_support::weights::Weight; use sp_runtime::DispatchError; use sp_std::{marker::PhantomData, vec::Vec}; -pub use crate::{exec::Ext, Config}; +pub use crate::{exec::Ext, gas::ChargedAmount, Config}; pub use frame_system::Config as SysConfig; pub use pallet_contracts_primitives::ReturnFlags; @@ -139,7 +138,7 @@ pub trait ChainExtension { /// /// # Note /// -/// Currently, we support tuples of up to ten registred chain extensions. If more chain extensions +/// Currently, we support tuples of up to ten registered chain extensions. If more chain extensions /// are needed consider opening an issue. pub trait RegisteredChainExtension: ChainExtension { /// The extensions globally unique identifier. diff --git a/frame/contracts/src/exec.rs b/frame/contracts/src/exec.rs index 7a07b01d27c2..cf37b132ddc2 100644 --- a/frame/contracts/src/exec.rs +++ b/frame/contracts/src/exec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,61 +17,75 @@ use crate::{ gas::GasMeter, - storage::{self, Storage, WriteOutcome}, + storage::{self, DepositAccount, WriteOutcome}, BalanceOf, CodeHash, Config, ContractInfo, ContractInfoOf, DebugBufferVec, Determinism, Error, - Event, Nonce, Pallet as Contracts, Schedule, + Event, Nonce, Origin, Pallet as Contracts, Schedule, System, LOG_TARGET, }; use frame_support::{ crypto::ecdsa::ECDSAExt, - dispatch::{DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable}, + dispatch::{ + fmt::Debug, DispatchError, DispatchResult, DispatchResultWithPostInfo, Dispatchable, + }, storage::{with_transaction, TransactionOutcome}, - traits::{Contains, Currency, ExistenceRequirement, OriginTrait, Randomness, Time}, + traits::{ + tokens::{Fortitude::Polite, Preservation::Expendable}, + Contains, Currency, ExistenceRequirement, OriginTrait, Randomness, Time, + }, weights::Weight, Blake2_128Concat, BoundedVec, StorageHasher, }; -use frame_system::RawOrigin; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use pallet_contracts_primitives::ExecReturnValue; use smallvec::{Array, SmallVec}; -use sp_core::ecdsa::Public as ECDSAPublic; +use sp_core::{ + ecdsa::Public as ECDSAPublic, + sr25519::{Public as SR25519Public, Signature as SR25519Signature}, +}; use sp_io::{crypto::secp256k1_ecdsa_recover_compressed, hashing::blake2_256}; -use sp_runtime::traits::{Convert, Hash}; -use sp_std::{marker::PhantomData, mem, prelude::*}; +use sp_runtime::traits::{Convert, Hash, Zero}; +use sp_std::{marker::PhantomData, mem, prelude::*, vec::Vec}; pub type AccountIdOf = ::AccountId; pub type MomentOf = <::Time as Time>::Moment; pub type SeedOf = ::Hash; -pub type BlockNumberOf = ::BlockNumber; pub type ExecResult = Result; /// A type that represents a topic of an event. At the moment a hash is used. pub type TopicOf = ::Hash; -/// Type for fix sized storage key. -pub type FixSizedKey = [u8; 32]; - /// Type for variable sized storage key. Used for transparent hashing. -pub type VarSizedKey = BoundedVec::MaxStorageKeyLen>; - -/// Trait for hashing storage keys. -pub trait StorageKey -where - T: Config, -{ - fn hash(&self) -> Vec; +type VarSizedKey = BoundedVec::MaxStorageKeyLen>; + +/// Combined key type for both fixed and variable sized storage keys. +pub enum Key { + /// Variant for fixed sized keys. + Fix([u8; 32]), + /// Variant for variable sized keys. + Var(VarSizedKey), } -impl StorageKey for FixSizedKey { - fn hash(&self) -> Vec { - blake2_256(self.as_slice()).to_vec() +impl Key { + /// Copies self into a new vec. + pub fn to_vec(&self) -> Vec { + match self { + Key::Fix(v) => v.to_vec(), + Key::Var(v) => v.to_vec(), + } } -} -impl StorageKey for VarSizedKey -where - T: Config, -{ - fn hash(&self) -> Vec { - Blake2_128Concat::hash(self.as_slice()) + pub fn hash(&self) -> Vec { + match self { + Key::Fix(v) => blake2_256(v.as_slice()).to_vec(), + Key::Var(v) => Blake2_128Concat::hash(v.as_slice()), + } + } + + pub fn try_from_fix(v: Vec) -> Result> { + <[u8; 32]>::try_from(v).map(Self::Fix) + } + + pub fn try_from_var(v: Vec) -> Result> { + VarSizedKey::::try_from(v).map(Self::Var) } } @@ -84,14 +98,14 @@ where pub enum ErrorOrigin { /// Caller error origin. /// - /// The error happened in the current exeuction context rather than in the one + /// The error happened in the current execution context rather than in the one /// of the contract that is called into. Caller, /// The error happened during execution of the called contract. Callee, } -/// Error returned by contract exection. +/// Error returned by contract execution. #[cfg_attr(test, derive(Debug, PartialEq))] pub struct ExecError { /// The reason why the execution failed. @@ -120,10 +134,11 @@ pub trait Ext: sealing::Sealed { /// Call (possibly transferring some amount of funds) into the specified account. /// - /// Returns the original code size of the called contract. + /// Returns the code size of the called contract. fn call( &mut self, gas_limit: Weight, + deposit_limit: BalanceOf, to: AccountIdOf, value: BalanceOf, input_data: Vec, @@ -132,7 +147,7 @@ pub trait Ext: sealing::Sealed { /// Execute code in the current frame. /// - /// Returns the original code size of the called contract. + /// Returns the code size of the called contract. fn delegate_call( &mut self, code: CodeHash, @@ -143,10 +158,11 @@ pub trait Ext: sealing::Sealed { /// /// Returns the original code size of the called contract. /// The newly created account will be associated with `code`. `value` specifies the amount of - /// value transferred from this to the newly created account. + /// value transferred from the caller to the newly created account. fn instantiate( &mut self, gas_limit: Weight, + deposit_limit: BalanceOf, code: CodeHash, value: BalanceOf, input_data: Vec, @@ -169,50 +185,25 @@ pub trait Ext: sealing::Sealed { /// /// Returns `None` if the `key` wasn't previously set by `set_storage` or /// was deleted. - fn get_storage(&mut self, key: &FixSizedKey) -> Option>; - - /// This is a variation of `get_storage()` to be used with transparent hashing. - /// These two will be merged into a single function after some refactoring is done. - /// Returns the storage entry of the executing account by the given `key`. - /// - /// Returns `None` if the `key` wasn't previously set by `set_storage` or - /// was deleted. - fn get_storage_transparent(&mut self, key: &VarSizedKey) -> Option>; - - /// Returns `Some(len)` (in bytes) if a storage item exists at `key`. - /// - /// Returns `None` if the `key` wasn't previously set by `set_storage` or - /// was deleted. - fn get_storage_size(&mut self, key: &FixSizedKey) -> Option; + fn get_storage(&mut self, key: &Key) -> Option>; - /// This is the variation of `get_storage_size()` to be used with transparent hashing. - /// These two will be merged into a single function after some refactoring is done. /// Returns `Some(len)` (in bytes) if a storage item exists at `key`. /// /// Returns `None` if the `key` wasn't previously set by `set_storage` or /// was deleted. - fn get_storage_size_transparent(&mut self, key: &VarSizedKey) -> Option; + fn get_storage_size(&mut self, key: &Key) -> Option; /// Sets the storage entry by the given key to the specified value. If `value` is `None` then /// the storage entry is deleted. fn set_storage( &mut self, - key: &FixSizedKey, + key: &Key, value: Option>, take_old: bool, ) -> Result; - /// This is the variation of `set_storage()` to be used with transparent hashing. - /// These two will be merged into a single function after some refactoring is done. - fn set_storage_transparent( - &mut self, - key: &VarSizedKey, - value: Option>, - take_old: bool, - ) -> Result; - - /// Returns a reference to the account id of the caller. - fn caller(&self) -> &AccountIdOf; + /// Returns the caller. + fn caller(&self) -> Origin; /// Check if a contract lives at the specified `address`. fn is_contract(&self, address: &AccountIdOf) -> bool; @@ -231,6 +222,9 @@ pub trait Ext: sealing::Sealed { /// However, this function does not require any storage lookup and therefore uses less weight. fn caller_is_origin(&self) -> bool; + /// Check if the caller is origin, and this origin is root. + fn caller_is_root(&self) -> bool; + /// Returns a reference to the account id of the current contract. fn address(&self) -> &AccountIdOf; @@ -249,7 +243,7 @@ pub trait Ext: sealing::Sealed { fn minimum_balance(&self) -> BalanceOf; /// Returns a random number for the current block with the given subject. - fn random(&self, subject: &[u8]) -> (SeedOf, BlockNumberOf); + fn random(&self, subject: &[u8]) -> (SeedOf, BlockNumberFor); /// Deposit an event with the given topics. /// @@ -257,7 +251,7 @@ pub trait Ext: sealing::Sealed { fn deposit_event(&mut self, topics: Vec>, data: Vec); /// Returns the current block number. - fn block_number(&self) -> BlockNumberOf; + fn block_number(&self) -> BlockNumberFor; /// Returns the maximum allowed size of a storage item. fn max_value_size(&self) -> u32; @@ -268,8 +262,11 @@ pub trait Ext: sealing::Sealed { /// Get a reference to the schedule used by the current call. fn schedule(&self) -> &Schedule; + /// Get an immutable reference to the nested gas meter. + fn gas_meter(&self) -> &GasMeter; + /// Get a mutable reference to the nested gas meter. - fn gas_meter(&mut self) -> &mut GasMeter; + fn gas_meter_mut(&mut self) -> &mut GasMeter; /// Append a string to the debug buffer. /// @@ -287,6 +284,9 @@ pub trait Ext: sealing::Sealed { /// Recovers ECDSA compressed public key based on signature and message hash. fn ecdsa_recover(&self, signature: &[u8; 65], message_hash: &[u8; 32]) -> Result<[u8; 33], ()>; + /// Verify a sr25519 signature. + fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool; + /// Returns Ethereum address from the ECDSA compressed public key. fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()>; @@ -327,24 +327,27 @@ pub trait Executable: Sized { /// Load the executable from storage. /// /// # Note - /// Charges size base load and instrumentation weight from the gas meter. + /// Charges size base load weight from the gas meter. fn from_storage( code_hash: CodeHash, - schedule: &Schedule, gas_meter: &mut GasMeter, ) -> Result; - /// Increment the refcount of a code in-storage by one. - /// - /// This is needed when the code is not set via instantiate but `seal_set_code_hash`. + /// Increment the reference count of a of a stored code by one. /// /// # Errors /// - /// [`Error::CodeNotFound`] is returned if the specified `code_hash` does not exist. - fn add_user(code_hash: CodeHash) -> Result<(), DispatchError>; + /// [`Error::CodeNotFound`] is returned if no stored code found having the specified + /// `code_hash`. + fn increment_refcount(code_hash: CodeHash) -> Result<(), DispatchError>; - /// Decrement the refcount by one if the code exists. - fn remove_user(code_hash: CodeHash); + /// Decrement the reference count of a stored code by one. + /// + /// # Note + /// + /// A contract whose reference count dropped to zero isn't automatically removed. A + /// `remove_code` transaction must be submitted by the original uploader to do so. + fn decrement_refcount(code_hash: CodeHash); /// Execute the specified exported function and return the result. /// @@ -365,7 +368,7 @@ pub trait Executable: Sized { /// The code hash of the executable. fn code_hash(&self) -> &CodeHash; - /// Size of the instrumented code in bytes. + /// Size of the contract code in bytes. fn code_len(&self) -> u32; /// The code does not contain any instructions which could lead to indeterminism. @@ -378,14 +381,15 @@ pub trait Executable: Sized { /// This type implements `Ext` and by that exposes the business logic of contract execution to /// the runtime module which interfaces with the contract (the wasm blob) itself. pub struct Stack<'a, T: Config, E> { - /// The account id of a plain account that initiated the call stack. + /// The origin that initiated the call stack. It could either be a Signed plain account that + /// holds an account id or Root. /// /// # Note /// - /// Please note that it is possible that the id belongs to a contract rather than a plain - /// account when being called through one of the contract RPCs where the client can freely - /// choose the origin. This usually makes no sense but is still possible. - origin: T::AccountId, + /// Please note that it is possible that the id of a Signed origin belongs to a contract rather + /// than a plain account when being called through one of the contract RPCs where the + /// client can freely choose the origin. This usually makes no sense but is still possible. + origin: Origin, /// The cost schedule used when charging from the gas meter. schedule: &'a Schedule, /// The gas meter where costs are charged to. @@ -395,7 +399,7 @@ pub struct Stack<'a, T: Config, E> { /// The timestamp at the point of call stack instantiation. timestamp: MomentOf, /// The block number at the time of call stack instantiation. - block_number: T::BlockNumber, + block_number: BlockNumberFor, /// The nonce is cached here when accessed. It is written back when the call stack /// finishes executing. Please refer to [`Nonce`] to a description of /// the nonce itself. @@ -441,15 +445,15 @@ pub struct Frame { /// If `false` the contract enabled its defense against reentrance attacks. allows_reentry: bool, /// The caller of the currently executing frame which was spawned by `delegate_call`. - delegate_caller: Option, + delegate_caller: Option>, } /// Used in a delegate call frame arguments in order to override the executable and caller. struct DelegatedCall { /// The executable which is run instead of the contracts own `executable`. executable: E, - /// The account id of the caller contract. - caller: T::AccountId, + /// The caller of the contract. + caller: Origin, } /// Parameter passed in when creating a new `Frame`. @@ -492,7 +496,7 @@ enum CachedContract { /// /// In this case a reload is neither allowed nor possible. Please note that recursive /// calls cannot remove a contract as this is checked and denied. - Terminated, + Terminated(DepositAccount), } impl CachedContract { @@ -513,6 +517,15 @@ impl CachedContract { None } } + + /// Returns `Some` iff the contract is not `Cached::Invalidated`. + fn deposit_account(&self) -> Option<&DepositAccount> { + match self { + CachedContract::Cached(contract) => Some(contract.deposit_account()), + CachedContract::Terminated(deposit_account) => Some(&deposit_account), + CachedContract::Invalidated => None, + } + } } impl Frame { @@ -591,7 +604,9 @@ impl CachedContract { /// Terminate and return the contract info. fn terminate(&mut self, account_id: &T::AccountId) -> ContractInfo { self.load(account_id); - get_cached_or_panic_after_load!(mem::replace(self, Self::Terminated)) + let contract = get_cached_or_panic_after_load!(self); + let deposit_account = contract.deposit_account().clone(); + get_cached_or_panic_after_load!(mem::replace(self, Self::Terminated(deposit_account))) } } @@ -611,7 +626,7 @@ where /// /// Result<(ExecReturnValue, CodeSize), (ExecError, CodeSize)> pub fn run_call( - origin: T::AccountId, + origin: Origin, dest: T::AccountId, gas_meter: &'a mut GasMeter, storage_meter: &'a mut storage::meter::Meter, @@ -663,13 +678,13 @@ where salt, input_data: input_data.as_ref(), }, - origin, + Origin::from_account_id(origin), gas_meter, storage_meter, schedule, value, debug_message, - Determinism::Deterministic, + Determinism::Enforced, )?; let account_id = stack.top_frame().account_id.clone(); stack.run(executable, input_data).map(|ret| (account_id, ret)) @@ -678,7 +693,7 @@ where /// Create a new call stack. fn new( args: FrameArgs, - origin: T::AccountId, + origin: Origin, gas_meter: &'a mut GasMeter, storage_meter: &'a mut storage::meter::Meter, schedule: &'a Schedule, @@ -690,11 +705,12 @@ where args, value, gas_meter, - storage_meter, Weight::zero(), - schedule, + storage_meter, + BalanceOf::::zero(), determinism, )?; + let stack = Self { origin, schedule, @@ -717,13 +733,13 @@ where /// /// This does not take `self` because when constructing the first frame `self` is /// not initialized, yet. - fn new_frame( + fn new_frame( frame_args: FrameArgs, value_transferred: BalanceOf, gas_meter: &mut GasMeter, - storage_meter: &mut storage::meter::GenericMeter, gas_limit: Weight, - schedule: &Schedule, + storage_meter: &mut storage::meter::GenericMeter, + deposit_limit: BalanceOf, determinism: Determinism, ) -> Result<(Frame, E, Option), ExecError> { let (account_id, contract_info, executable, delegate_caller, entry_point, nonce) = @@ -739,7 +755,7 @@ where if let Some(DelegatedCall { executable, caller }) = delegated_call { (executable, Some(caller)) } else { - (E::from_storage(contract.code_hash, schedule, gas_meter)?, None) + (E::from_storage(contract.code_hash, gas_meter)?, None) }; (dest, contract, executable, delegate_caller, ExportedFunction::Call, None) @@ -747,13 +763,11 @@ where FrameArgs::Instantiate { sender, nonce, executable, salt, input_data } => { let account_id = Contracts::::contract_address( &sender, - executable.code_hash(), + &executable.code_hash(), input_data, salt, ); - let trie_id = Storage::::generate_trie_id(&account_id, nonce); - let contract = - Storage::::new_contract(&account_id, trie_id, *executable.code_hash())?; + let contract = ContractInfo::new(&account_id, nonce, *executable.code_hash())?; ( account_id, contract, @@ -765,10 +779,10 @@ where }, }; - // `AllowIndeterminism` will only be ever set in case of off-chain execution. + // `Relaxed` will only be ever set in case of off-chain execution. // Instantiations are never allowed even when executing off-chain. if !(executable.is_deterministic() || - (matches!(determinism, Determinism::AllowIndeterminism) && + (matches!(determinism, Determinism::Relaxed) && matches!(entry_point, ExportedFunction::Call))) { return Err(Error::::Indeterministic.into()) @@ -781,7 +795,7 @@ where account_id, entry_point, nested_gas: gas_meter.nested(gas_limit)?, - nested_storage: storage_meter.nested(), + nested_storage: storage_meter.nested(deposit_limit), allows_reentry: true, }; @@ -794,6 +808,7 @@ where frame_args: FrameArgs, value_transferred: BalanceOf, gas_limit: Weight, + deposit_limit: BalanceOf, ) -> Result { if self.frames.len() == T::CallStack::size() { return Err(Error::::MaxCallDepthReached.into()) @@ -817,9 +832,9 @@ where frame_args, value_transferred, nested_gas, - nested_storage, gas_limit, - self.schedule, + nested_storage, + deposit_limit, self.determinism, )?; self.frames.push(frame); @@ -838,9 +853,12 @@ where // We need to charge the storage deposit before the initial transfer so that // it can create the account in case the initial transfer is < ed. if entry_point == ExportedFunction::Constructor { + // Root origin can't be used to instantiate a contract, so it is safe to assume that + // if we reached this point the origin has an associated account. + let origin = &self.origin.account_id()?; let frame = top_frame_mut!(self); frame.nested_storage.charge_instantiate( - &self.origin, + origin, &frame.account_id, frame.contract_info.get(&frame.account_id), )?; @@ -849,7 +867,7 @@ where // Every non delegate call or instantiate also optionally transfers the balance. self.initial_transfer()?; - // Call into the wasm blob. + // Call into the Wasm blob. let output = executable .execute(self, &entry_point, input_data) .map_err(|e| ExecError { error: e.error, origin: ErrorOrigin::Callee })?; @@ -859,8 +877,10 @@ where return Ok(output) } - // Storage limit is enforced as late as possible (when the last frame returns) so that - // the ordering of storage accesses does not matter. + // Storage limit is normally enforced as late as possible (when the last frame returns) + // so that the ordering of storage accesses does not matter. + // (However, if a special limit was set for a sub-call, it should be enforced right + // after the sub-call returned. See below for this case of enforcement). if self.frames.is_empty() { let frame = &mut self.first_frame; frame.contract_info.load(&frame.account_id); @@ -869,21 +889,27 @@ where } let frame = self.top_frame(); - let account_id = &frame.account_id; + let account_id = &frame.account_id.clone(); match (entry_point, delegated_code_hash) { (ExportedFunction::Constructor, _) => { // It is not allowed to terminate a contract inside its constructor. - if matches!(frame.contract_info, CachedContract::Terminated) { + if matches!(frame.contract_info, CachedContract::Terminated(_)) { return Err(Error::::TerminatedInConstructor.into()) } + // If a special limit was set for the sub-call, we enforce it here. + // This is needed because contract constructor might write to storage. + // The sub-call will be rolled back in case the limit is exhausted. + let frame = self.top_frame_mut(); + let contract = frame.contract_info.as_contract(); + frame.nested_storage.enforce_subcall_limit(contract)?; + + let caller = self.caller().account_id()?.clone(); + // Deposit an instantiation event. Contracts::::deposit_event( - vec![T::Hashing::hash_of(self.caller()), T::Hashing::hash_of(account_id)], - Event::Instantiated { - deployer: self.caller().clone(), - contract: account_id.clone(), - }, + vec![T::Hashing::hash_of(&caller), T::Hashing::hash_of(account_id)], + Event::Instantiated { deployer: caller, contract: account_id.clone() }, ); }, (ExportedFunction::Call, Some(code_hash)) => { @@ -893,9 +919,15 @@ where ); }, (ExportedFunction::Call, None) => { + // If a special limit was set for the sub-call, we enforce it here. + // The sub-call will be rolled back in case the limit is exhausted. + let frame = self.top_frame_mut(); + let contract = frame.contract_info.as_contract(); + frame.nested_storage.enforce_subcall_limit(contract)?; + let caller = self.caller(); Contracts::::deposit_event( - vec![T::Hashing::hash_of(caller), T::Hashing::hash_of(account_id)], + vec![T::Hashing::hash_of(&caller), T::Hashing::hash_of(&account_id)], Event::Called { caller: caller.clone(), contract: account_id.clone() }, ); }, @@ -962,11 +994,21 @@ where // Record the storage meter changes of the nested call into the parent meter. // If the dropped frame's contract wasn't terminated we update the deposit counter - // in its contract info. The load is necessary to to pull it from storage in case + // in its contract info. The load is necessary to pull it from storage in case // it was invalidated. frame.contract_info.load(account_id); + let deposit_account = frame + .contract_info + .deposit_account() + .expect( + "Is only `None` when the info is invalidated. + We just re-loaded from storage which either makes the state `Cached` or `Terminated`. + qed", + ) + .clone(); let mut contract = frame.contract_info.into_contract(); - prev.nested_storage.absorb(frame.nested_storage, account_id, contract.as_mut()); + prev.nested_storage + .absorb(frame.nested_storage, deposit_account, contract.as_mut()); // In case the contract wasn't terminated we need to persist changes made to it. if let Some(contract) = contract { @@ -992,7 +1034,7 @@ where } else { if let Some((msg, false)) = self.debug_message.as_ref().map(|m| (m, m.is_empty())) { log::debug!( - target: "runtime::contracts", + target: LOG_TARGET, "Execution finished with debug buffer: {}", core::str::from_utf8(msg).unwrap_or(""), ); @@ -1001,10 +1043,14 @@ where if !persist { return } + let deposit_account = self.first_frame.contract_info.deposit_account().expect( + "Is only `None` when the info is invalidated. The first frame can't be invalidated. + qed", + ).clone(); let mut contract = self.first_frame.contract_info.as_contract(); self.storage_meter.absorb( mem::take(&mut self.first_frame.nested_storage), - &self.first_frame.account_id, + deposit_account, contract.as_deref_mut(), ); if let Some(contract) = contract { @@ -1039,7 +1085,16 @@ where } let value = frame.value_transferred; - Self::transfer(ExistenceRequirement::KeepAlive, self.caller(), &frame.account_id, value) + + // Get the account id from the caller. + // If the caller is root there is no account to transfer from, and therefore we can't take + // any `value` other than 0. + let caller = match self.caller() { + Origin::Signed(caller) => caller, + Origin::Root if value.is_zero() => return Ok(()), + Origin::Root => return DispatchError::RootNotAllowed.into(), + }; + Self::transfer(ExistenceRequirement::KeepAlive, &caller, &frame.account_id, value) } /// Reference to the current (top) frame. @@ -1093,6 +1148,7 @@ where fn call( &mut self, gas_limit: Weight, + deposit_limit: BalanceOf, to: T::AccountId, value: BalanceOf, input_data: Vec, @@ -1121,6 +1177,7 @@ where FrameArgs::Call { dest: to, cached_info, delegated_call: None }, value, gas_limit, + deposit_limit, )?; self.run(executable, input_data) }; @@ -1139,7 +1196,7 @@ where code_hash: CodeHash, input_data: Vec, ) -> Result { - let executable = E::from_storage(code_hash, self.schedule, self.gas_meter())?; + let executable = E::from_storage(code_hash, self.gas_meter_mut())?; let top_frame = self.top_frame_mut(); let contract_info = top_frame.contract_info().clone(); let account_id = top_frame.account_id.clone(); @@ -1152,6 +1209,7 @@ where }, value, Weight::zero(), + BalanceOf::::zero(), )?; self.run(executable, input_data) } @@ -1159,12 +1217,13 @@ where fn instantiate( &mut self, gas_limit: Weight, + deposit_limit: BalanceOf, code_hash: CodeHash, value: BalanceOf, input_data: Vec, salt: &[u8], ) -> Result<(AccountIdOf, ExecReturnValue), ExecError> { - let executable = E::from_storage(code_hash, self.schedule, self.gas_meter())?; + let executable = E::from_storage(code_hash, self.gas_meter_mut())?; let nonce = self.next_nonce(); let executable = self.push_frame( FrameArgs::Instantiate { @@ -1176,27 +1235,30 @@ where }, value, gas_limit, + deposit_limit, )?; let account_id = self.top_frame().account_id.clone(); self.run(executable, input_data).map(|ret| (account_id, ret)) } fn terminate(&mut self, beneficiary: &AccountIdOf) -> Result<(), DispatchError> { + use frame_support::traits::fungible::Inspect; if self.is_recursive() { return Err(Error::::TerminatedWhileReentrant.into()) } let frame = self.top_frame_mut(); let info = frame.terminate(); frame.nested_storage.terminate(&info); - Storage::::queue_trie_for_deletion(&info)?; - >::transfer( - ExistenceRequirement::AllowDeath, + System::::dec_consumers(&frame.account_id); + T::Currency::transfer( &frame.account_id, beneficiary, - T::Currency::free_balance(&frame.account_id), + T::Currency::reducible_balance(&frame.account_id, Expendable, Polite), + ExistenceRequirement::AllowDeath, )?; + info.queue_trie_for_deletion(); ContractInfoOf::::remove(&frame.account_id); - E::remove_user(info.code_hash); + E::decrement_refcount(info.code_hash); Contracts::::deposit_event( vec![T::Hashing::hash_of(&frame.account_id), T::Hashing::hash_of(&beneficiary)], Event::Terminated { @@ -1211,48 +1273,23 @@ where Self::transfer(ExistenceRequirement::KeepAlive, &self.top_frame().account_id, to, value) } - fn get_storage(&mut self, key: &FixSizedKey) -> Option> { - Storage::::read(&self.top_frame_mut().contract_info().trie_id, key) + fn get_storage(&mut self, key: &Key) -> Option> { + self.top_frame_mut().contract_info().read(key) } - fn get_storage_transparent(&mut self, key: &VarSizedKey) -> Option> { - Storage::::read(&self.top_frame_mut().contract_info().trie_id, key) - } - - fn get_storage_size(&mut self, key: &FixSizedKey) -> Option { - Storage::::size(&self.top_frame_mut().contract_info().trie_id, key) - } - - fn get_storage_size_transparent(&mut self, key: &VarSizedKey) -> Option { - Storage::::size(&self.top_frame_mut().contract_info().trie_id, key) + fn get_storage_size(&mut self, key: &Key) -> Option { + self.top_frame_mut().contract_info().size(key.into()) } fn set_storage( &mut self, - key: &FixSizedKey, + key: &Key, value: Option>, take_old: bool, ) -> Result { let frame = self.top_frame_mut(); - Storage::::write( - &frame.contract_info.get(&frame.account_id).trie_id, - key, - value, - Some(&mut frame.nested_storage), - take_old, - ) - } - - fn set_storage_transparent( - &mut self, - key: &VarSizedKey, - value: Option>, - take_old: bool, - ) -> Result { - let frame = self.top_frame_mut(); - Storage::::write( - &frame.contract_info.get(&frame.account_id).trie_id, - key, + frame.contract_info.get(&frame.account_id).write( + key.into(), value, Some(&mut frame.nested_storage), take_old, @@ -1263,11 +1300,14 @@ where &self.top_frame().account_id } - fn caller(&self) -> &T::AccountId { + fn caller(&self) -> Origin { if let Some(caller) = &self.top_frame().delegate_caller { - caller + caller.clone() } else { - self.frames().nth(1).map(|f| &f.account_id).unwrap_or(&self.origin) + self.frames() + .nth(1) + .map(|f| Origin::from_account_id(f.account_id.clone())) + .unwrap_or(self.origin.clone()) } } @@ -1284,7 +1324,12 @@ where } fn caller_is_origin(&self) -> bool { - self.caller() == &self.origin + self.origin == self.caller() + } + + fn caller_is_root(&self) -> bool { + // if the caller isn't origin, then it can't be root. + self.caller_is_origin() && self.origin == Origin::Root } fn balance(&self) -> BalanceOf { @@ -1295,7 +1340,7 @@ where self.top_frame().value_transferred } - fn random(&self, subject: &[u8]) -> (SeedOf, BlockNumberOf) { + fn random(&self, subject: &[u8]) -> (SeedOf, BlockNumberFor) { T::Randomness::random(subject) } @@ -1314,7 +1359,7 @@ where ); } - fn block_number(&self) -> T::BlockNumber { + fn block_number(&self) -> BlockNumberFor { self.block_number } @@ -1330,37 +1375,26 @@ where self.schedule } - fn gas_meter(&mut self) -> &mut GasMeter { + fn gas_meter(&self) -> &GasMeter { + &self.top_frame().nested_gas + } + + fn gas_meter_mut(&mut self) -> &mut GasMeter { &mut self.top_frame_mut().nested_gas } fn append_debug_buffer(&mut self, msg: &str) -> bool { if let Some(buffer) = &mut self.debug_message { - let err_msg = scale_info::prelude::format!( - "Debug message too big (size={}) for debug buffer (bound={})", - msg.len(), - DebugBufferVec::::bound(), - ); - - let mut msg = if msg.len() > DebugBufferVec::::bound() { - err_msg.bytes() - } else { - msg.bytes() - }; - - let num_drain = { - let capacity = DebugBufferVec::::bound().checked_sub(buffer.len()).expect( - " - `buffer` is of type `DebugBufferVec`, - `DebugBufferVec` is a `BoundedVec`, - `BoundedVec::len()` <= `BoundedVec::bound()`; - qed - ", - ); - msg.len().saturating_sub(capacity).min(buffer.len()) - }; - buffer.drain(0..num_drain); - buffer.try_extend(&mut msg).ok(); + buffer + .try_extend(&mut msg.bytes()) + .map_err(|_| { + log::debug!( + target: LOG_TARGET, + "Debug buffer (of {} bytes) exhausted!", + DebugBufferVec::::bound(), + ) + }) + .ok(); true } else { false @@ -1377,6 +1411,14 @@ where secp256k1_ecdsa_recover_compressed(signature, message_hash).map_err(|_| ()) } + fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool { + sp_io::crypto::sr25519_verify( + &SR25519Signature(*signature), + message, + &SR25519Public(*pub_key), + ) + } + fn ecdsa_to_eth_address(&self, pk: &[u8; 33]) -> Result<[u8; 20], ()> { ECDSAPublic(*pk).to_eth_address() } @@ -1388,12 +1430,12 @@ where fn set_code_hash(&mut self, hash: CodeHash) -> Result<(), DispatchError> { let frame = top_frame_mut!(self); - if !E::from_storage(hash, self.schedule, &mut frame.nested_gas)?.is_deterministic() { + if !E::from_storage(hash, &mut frame.nested_gas)?.is_deterministic() { return Err(>::Indeterministic.into()) } - E::add_user(hash)?; + E::increment_refcount(hash)?; let prev_hash = frame.contract_info().code_hash; - E::remove_user(prev_hash); + E::decrement_refcount(prev_hash); frame.contract_info().code_hash = hash; Contracts::::deposit_event( vec![T::Hashing::hash_of(&frame.account_id), hash, prev_hash], @@ -1453,7 +1495,6 @@ mod tests { use crate::{ exec::ExportedFunction::*, gas::GasMeter, - storage::Storage, tests::{ test_utils::{get_balance, hash, place_contract, set_balance}, ExtBuilder, RuntimeCall, RuntimeEvent as MetaEvent, Test, TestFilter, ALICE, BOB, @@ -1557,7 +1598,6 @@ mod tests { impl Executable for MockExecutable { fn from_storage( code_hash: CodeHash, - _schedule: &Schedule, _gas_meter: &mut GasMeter, ) -> Result { Loader::mutate(|loader| { @@ -1565,11 +1605,11 @@ mod tests { }) } - fn add_user(code_hash: CodeHash) -> Result<(), DispatchError> { + fn increment_refcount(code_hash: CodeHash) -> Result<(), DispatchError> { MockLoader::increment_refcount(code_hash) } - fn remove_user(code_hash: CodeHash) { + fn decrement_refcount(code_hash: CodeHash) { MockLoader::decrement_refcount(code_hash); } @@ -1580,7 +1620,7 @@ mod tests { input_data: Vec, ) -> ExecResult { if let &Constructor = function { - Self::add_user(self.code_hash).unwrap(); + Self::increment_refcount(self.code_hash).unwrap(); } if function == &self.func_type { (self.func)(MockCtx { ext, input_data }, &self) @@ -1626,11 +1666,13 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, exec_ch); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), value).unwrap(); + let mut storage_meter = + storage::meter::Meter::new(&Origin::from_account_id(ALICE), Some(0), value) + .unwrap(); assert_matches!( MockStack::run_call( - ALICE, + Origin::from_account_id(ALICE), BOB, &mut gas_meter, &mut storage_meter, @@ -1638,7 +1680,7 @@ mod tests { value, vec![], None, - Determinism::Deterministic, + Determinism::Enforced, ), Ok(_) ); @@ -1681,10 +1723,12 @@ mod tests { place_contract(&dest, success_ch); set_balance(&origin, 100); let balance = get_balance(&dest); - let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 55).unwrap(); + let contract_origin = Origin::from_account_id(origin.clone()); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), value).unwrap(); let _ = MockStack::run_call( - origin.clone(), + contract_origin.clone(), dest.clone(), &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -1692,7 +1736,7 @@ mod tests { value, vec![], None, - Determinism::Deterministic, + Determinism::Enforced, ) .unwrap(); @@ -1723,10 +1767,12 @@ mod tests { place_contract(&dest, delegate_ch); set_balance(&origin, 100); let balance = get_balance(&dest); - let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 55).unwrap(); + let contract_origin = Origin::from_account_id(origin.clone()); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 55).unwrap(); let _ = MockStack::run_call( - origin.clone(), + contract_origin.clone(), dest.clone(), &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -1734,7 +1780,7 @@ mod tests { value, vec![], None, - Determinism::Deterministic, + Determinism::Enforced, ) .unwrap(); @@ -1759,10 +1805,12 @@ mod tests { place_contract(&dest, return_ch); set_balance(&origin, 100); let balance = get_balance(&dest); - let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 55).unwrap(); + let contract_origin = Origin::from_account_id(origin.clone()); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 55).unwrap(); let output = MockStack::run_call( - origin.clone(), + contract_origin.clone(), dest.clone(), &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -1770,7 +1818,7 @@ mod tests { 55, vec![], None, - Determinism::Deterministic, + Determinism::Enforced, ) .unwrap(); @@ -1810,11 +1858,13 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); - let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(origin); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); place_contract(&BOB, return_ch); let result = MockStack::run_call( - origin, + contract_origin, dest, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -1822,7 +1872,7 @@ mod tests { 0, vec![], None, - Determinism::Deterministic, + Determinism::Enforced, ); let output = result.unwrap(); @@ -1844,10 +1894,12 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, return_ch); - let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(origin); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( - origin, + contract_origin, dest, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -1855,7 +1907,7 @@ mod tests { 0, vec![], None, - Determinism::Deterministic, + Determinism::Enforced, ); let output = result.unwrap(); @@ -1875,10 +1927,12 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, input_data_ch); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( - ALICE, + contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -1886,7 +1940,7 @@ mod tests { 0, vec![1, 2, 3, 4], None, - Determinism::Deterministic, + Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); @@ -1904,11 +1958,11 @@ mod tests { let schedule = ::Schedule::get(); let min_balance = ::Currency::minimum_balance(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); - let executable = - MockExecutable::from_storage(input_data_ch, &schedule, &mut gas_meter).unwrap(); - set_balance(&ALICE, min_balance * 1000); + let executable = MockExecutable::from_storage(input_data_ch, &mut gas_meter).unwrap(); + set_balance(&ALICE, min_balance * 10_000); + let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = - storage::meter::Meter::new(&ALICE, Some(min_balance * 100), min_balance).unwrap(); + storage::meter::Meter::new(&contract_origin, None, min_balance).unwrap(); let result = MockStack::run_instantiate( ALICE, @@ -1935,7 +1989,7 @@ mod tests { let value = Default::default(); let recurse_ch = MockLoader::insert(Call, |ctx, _| { // Try to call into yourself. - let r = ctx.ext.call(Weight::zero(), BOB, 0, vec![], true); + let r = ctx.ext.call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![], true); ReachedBottom::mutate(|reached_bottom| { if !*reached_bottom { @@ -1956,10 +2010,12 @@ mod tests { let schedule = ::Schedule::get(); set_balance(&BOB, 1); place_contract(&BOB, recurse_ch); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), value).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), value).unwrap(); let result = MockStack::run_call( - ALICE, + contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -1967,7 +2023,7 @@ mod tests { value, vec![], None, - Determinism::Deterministic, + Determinism::Enforced, ); assert_matches!(result, Ok(_)); @@ -1986,15 +2042,23 @@ mod tests { let bob_ch = MockLoader::insert(Call, |ctx, _| { // Record the caller for bob. - WitnessedCallerBob::mutate(|caller| *caller = Some(ctx.ext.caller().clone())); + WitnessedCallerBob::mutate(|caller| { + *caller = Some(ctx.ext.caller().account_id().unwrap().clone()) + }); // Call into CHARLIE contract. - assert_matches!(ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true), Ok(_)); + assert_matches!( + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], true), + Ok(_) + ); exec_success() }); let charlie_ch = MockLoader::insert(Call, |ctx, _| { // Record the caller for charlie. - WitnessedCallerCharlie::mutate(|caller| *caller = Some(ctx.ext.caller().clone())); + WitnessedCallerCharlie::mutate(|caller| { + *caller = Some(ctx.ext.caller().account_id().unwrap().clone()) + }); exec_success() }); @@ -2002,10 +2066,12 @@ mod tests { let schedule = ::Schedule::get(); place_contract(&dest, bob_ch); place_contract(&CHARLIE, charlie_ch); - let mut storage_meter = storage::meter::Meter::new(&origin, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(origin.clone()); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( - origin.clone(), + contract_origin.clone(), dest.clone(), &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -2013,7 +2079,7 @@ mod tests { 0, vec![], None, - Determinism::Deterministic, + Determinism::Enforced, ); assert_matches!(result, Ok(_)); @@ -2037,9 +2103,11 @@ mod tests { let schedule = ::Schedule::get(); place_contract(&BOB, bob_ch); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( - ALICE, + contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -2047,7 +2115,7 @@ mod tests { 0, vec![], None, - Determinism::Deterministic, + Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); @@ -2056,9 +2124,9 @@ mod tests { #[test] fn code_hash_returns_proper_values() { let code_bob = MockLoader::insert(Call, |ctx, _| { - // ALICE is not a contract and hence she does not have a code_hash + // ALICE is not a contract and hence they do not have a code_hash assert!(ctx.ext.code_hash(&ALICE).is_none()); - // BOB is a contract and hence he has a code_hash + // BOB is a contract and hence it has a code_hash assert!(ctx.ext.code_hash(&BOB).is_some()); exec_success() }); @@ -2066,10 +2134,12 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, code_bob); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); // ALICE (not contract) -> BOB (contract) let result = MockStack::run_call( - ALICE, + contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -2077,7 +2147,7 @@ mod tests { 0, vec![0], None, - Determinism::Deterministic, + Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); @@ -2094,10 +2164,12 @@ mod tests { ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, bob_ch); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); // ALICE (not contract) -> BOB (contract) let result = MockStack::run_call( - ALICE, + contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -2105,7 +2177,7 @@ mod tests { 0, vec![0], None, - Determinism::Deterministic, + Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); @@ -2123,17 +2195,119 @@ mod tests { // ALICE is the origin of the call stack assert!(ctx.ext.caller_is_origin()); // BOB calls CHARLIE - ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true) + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], true) }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); // ALICE -> BOB (caller is origin) -> CHARLIE (caller is not origin) let result = MockStack::run_call( - ALICE, + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + &schedule, + 0, + vec![0], + None, + Determinism::Enforced, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn root_caller_succeeds() { + let code_bob = MockLoader::insert(Call, |ctx, _| { + // root is the origin of the call stack. + assert!(ctx.ext.caller_is_root()); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + place_contract(&BOB, code_bob); + let contract_origin = Origin::Root; + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); + // root -> BOB (caller is root) + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + &schedule, + 0, + vec![0], + None, + Determinism::Enforced, + ); + assert_matches!(result, Ok(_)); + }); + } + + #[test] + fn root_caller_does_not_succeed_when_value_not_zero() { + let code_bob = MockLoader::insert(Call, |ctx, _| { + // root is the origin of the call stack. + assert!(ctx.ext.caller_is_root()); + exec_success() + }); + + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + place_contract(&BOB, code_bob); + let contract_origin = Origin::Root; + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); + // root -> BOB (caller is root) + let result = MockStack::run_call( + contract_origin, + BOB, + &mut GasMeter::::new(GAS_LIMIT), + &mut storage_meter, + &schedule, + 1, + vec![0], + None, + Determinism::Enforced, + ); + assert_matches!(result, Err(_)); + }); + } + + #[test] + fn root_caller_succeeds_with_consecutive_calls() { + let code_charlie = MockLoader::insert(Call, |ctx, _| { + // BOB is not root, even though the origin is root. + assert!(!ctx.ext.caller_is_root()); + exec_success() + }); + + let code_bob = MockLoader::insert(Call, |ctx, _| { + // root is the origin of the call stack. + assert!(ctx.ext.caller_is_root()); + // BOB calls CHARLIE. + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], true) + }); + + ExtBuilder::default().build().execute_with(|| { + let schedule = ::Schedule::get(); + place_contract(&BOB, code_bob); + place_contract(&CHARLIE, code_charlie); + let contract_origin = Origin::Root; + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); + // root -> BOB (caller is root) -> CHARLIE (caller is not root) + let result = MockStack::run_call( + contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -2141,7 +2315,7 @@ mod tests { 0, vec![0], None, - Determinism::Deterministic, + Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); @@ -2154,7 +2328,11 @@ mod tests { assert_eq!(*ctx.ext.address(), BOB); // Call into charlie contract. - assert_matches!(ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true), Ok(_)); + assert_matches!( + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], true), + Ok(_) + ); exec_success() }); let charlie_ch = MockLoader::insert(Call, |ctx, _| { @@ -2166,10 +2344,12 @@ mod tests { let schedule = ::Schedule::get(); place_contract(&BOB, bob_ch); place_contract(&CHARLIE, charlie_ch); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( - ALICE, + contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -2177,7 +2357,7 @@ mod tests { 0, vec![], None, - Determinism::Deterministic, + Determinism::Enforced, ); assert_matches!(result, Ok(_)); @@ -2191,9 +2371,10 @@ mod tests { ExtBuilder::default().existential_deposit(15).build().execute_with(|| { let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); - let executable = - MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap(); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); assert_matches!( MockStack::run_instantiate( @@ -2222,11 +2403,12 @@ mod tests { let schedule = ::Schedule::get(); let min_balance = ::Currency::minimum_balance(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); - let executable = - MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap(); + let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap(); set_balance(&ALICE, min_balance * 1000); + let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = - storage::meter::Meter::new(&ALICE, Some(min_balance * 100), min_balance).unwrap(); + storage::meter::Meter::new(&contract_origin, Some(min_balance * 100), min_balance) + .unwrap(); let instantiated_contract_address = assert_matches!( MockStack::run_instantiate( @@ -2246,7 +2428,7 @@ mod tests { // Check that the newly created account has the expected code hash and // there are instantiation event. assert_eq!( - Storage::::code_hash(&instantiated_contract_address).unwrap(), + ContractInfo::::load_code_hash(&instantiated_contract_address).unwrap(), dummy_ch ); assert_eq!( @@ -2266,11 +2448,12 @@ mod tests { let schedule = ::Schedule::get(); let min_balance = ::Currency::minimum_balance(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); - let executable = - MockExecutable::from_storage(dummy_ch, &schedule, &mut gas_meter).unwrap(); + let executable = MockExecutable::from_storage(dummy_ch, &mut gas_meter).unwrap(); set_balance(&ALICE, min_balance * 1000); + let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = - storage::meter::Meter::new(&ALICE, Some(min_balance * 100), min_balance).unwrap(); + storage::meter::Meter::new(&contract_origin, Some(min_balance * 100), min_balance) + .unwrap(); let instantiated_contract_address = assert_matches!( MockStack::run_instantiate( @@ -2288,7 +2471,7 @@ mod tests { ); // Check that the account has not been created. - assert!(Storage::::code_hash(&instantiated_contract_address).is_none()); + assert!(ContractInfo::::load_code_hash(&instantiated_contract_address).is_none()); assert!(events().is_empty()); }); } @@ -2305,6 +2488,7 @@ mod tests { .ext .instantiate( Weight::zero(), + BalanceOf::::zero(), dummy_ch, ::Currency::minimum_balance(), vec![], @@ -2322,13 +2506,17 @@ mod tests { let min_balance = ::Currency::minimum_balance(); set_balance(&ALICE, min_balance * 100); place_contract(&BOB, instantiator_ch); - let mut storage_meter = - storage::meter::Meter::new(&ALICE, Some(min_balance * 10), min_balance * 10) - .unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new( + &contract_origin, + Some(min_balance * 10), + min_balance * 10, + ) + .unwrap(); assert_matches!( MockStack::run_call( - ALICE, + contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -2336,7 +2524,7 @@ mod tests { min_balance * 10, vec![], None, - Determinism::Deterministic, + Determinism::Enforced, ), Ok(_) ); @@ -2347,14 +2535,14 @@ mod tests { // Check that the newly created account has the expected code hash and // there are instantiation event. assert_eq!( - Storage::::code_hash(&instantiated_contract_address).unwrap(), + ContractInfo::::load_code_hash(&instantiated_contract_address).unwrap(), dummy_ch ); assert_eq!( &events(), &[ Event::Instantiated { deployer: BOB, contract: instantiated_contract_address }, - Event::Called { caller: ALICE, contract: BOB }, + Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB }, ] ); }); @@ -2369,6 +2557,7 @@ mod tests { assert_matches!( ctx.ext.instantiate( Weight::zero(), + BalanceOf::::zero(), dummy_ch, ::Currency::minimum_balance(), vec![], @@ -2389,11 +2578,13 @@ mod tests { set_balance(&ALICE, 1000); set_balance(&BOB, 100); place_contract(&BOB, instantiator_ch); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(100), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(200), 0).unwrap(); assert_matches!( MockStack::run_call( - ALICE, + contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -2401,14 +2592,17 @@ mod tests { 0, vec![], None, - Determinism::Deterministic, + Determinism::Enforced, ), Ok(_) ); // The contract wasn't instantiated so we don't expect to see an instantiation // event here. - assert_eq!(&events(), &[Event::Called { caller: ALICE, contract: BOB },]); + assert_eq!( + &events(), + &[Event::Called { caller: Origin::from_account_id(ALICE), contract: BOB },] + ); }); } @@ -2422,10 +2616,11 @@ mod tests { ExtBuilder::default().existential_deposit(15).build().execute_with(|| { let schedule = ::Schedule::get(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); - let executable = - MockExecutable::from_storage(terminate_ch, &schedule, &mut gas_meter).unwrap(); - set_balance(&ALICE, 1000); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(100), 100).unwrap(); + let executable = MockExecutable::from_storage(terminate_ch, &mut gas_meter).unwrap(); + set_balance(&ALICE, 10_000); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, None, 100).unwrap(); assert_eq!( MockStack::run_instantiate( @@ -2449,7 +2644,7 @@ mod tests { #[test] fn in_memory_changes_not_discarded() { // Call stack: BOB -> CHARLIE (trap) -> BOB' (success) - // This tests verfies some edge case of the contract info cache: + // This tests verifies some edge case of the contract info cache: // We change some value in our contract info before calling into a contract // that calls into ourself. This triggers a case where BOBs contract info // is written to storage and invalidated by the successful execution of BOB'. @@ -2461,13 +2656,26 @@ mod tests { let info = ctx.ext.contract_info(); assert_eq!(info.storage_byte_deposit, 0); info.storage_byte_deposit = 42; - assert_eq!(ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], true), exec_trapped()); + assert_eq!( + ctx.ext.call( + Weight::zero(), + BalanceOf::::zero(), + CHARLIE, + 0, + vec![], + true + ), + exec_trapped() + ); assert_eq!(ctx.ext.contract_info().storage_byte_deposit, 42); } exec_success() }); let code_charlie = MockLoader::insert(Call, |ctx, _| { - assert!(ctx.ext.call(Weight::zero(), BOB, 0, vec![99], true).is_ok()); + assert!(ctx + .ext + .call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![99], true) + .is_ok()); exec_trapped() }); @@ -2476,10 +2684,12 @@ mod tests { let schedule = ::Schedule::get(); place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( - ALICE, + contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -2487,7 +2697,7 @@ mod tests { 0, vec![0], None, - Determinism::Deterministic, + Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); @@ -2497,7 +2707,7 @@ mod tests { fn recursive_call_during_constructor_fails() { let code = MockLoader::insert(Constructor, |ctx, _| { assert_matches!( - ctx.ext.call(Weight::zero(), ctx.ext.address().clone(), 0, vec![], true), + ctx.ext.call(Weight::zero(), BalanceOf::::zero(), ctx.ext.address().clone(), 0, vec![], true), Err(ExecError{error, ..}) if error == >::ContractNotFound.into() ); exec_success() @@ -2508,10 +2718,11 @@ mod tests { let schedule = ::Schedule::get(); let min_balance = ::Currency::minimum_balance(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); - let executable = MockExecutable::from_storage(code, &schedule, &mut gas_meter).unwrap(); - set_balance(&ALICE, min_balance * 1000); + let executable = MockExecutable::from_storage(code, &mut gas_meter).unwrap(); + set_balance(&ALICE, min_balance * 10_000); + let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = - storage::meter::Meter::new(&ALICE, Some(min_balance * 100), min_balance).unwrap(); + storage::meter::Meter::new(&contract_origin, None, min_balance).unwrap(); let result = MockStack::run_instantiate( ALICE, @@ -2544,9 +2755,11 @@ mod tests { let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); MockStack::run_call( - ALICE, + contract_origin, BOB, &mut gas_meter, &mut storage_meter, @@ -2554,7 +2767,7 @@ mod tests { 0, vec![], Some(&mut debug_buffer), - Determinism::Deterministic, + Determinism::Enforced, ) .unwrap(); }); @@ -2578,9 +2791,11 @@ mod tests { let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( - ALICE, + contract_origin, BOB, &mut gas_meter, &mut storage_meter, @@ -2588,7 +2803,7 @@ mod tests { 0, vec![], Some(&mut debug_buffer), - Determinism::Deterministic, + Determinism::Enforced, ); assert!(result.is_err()); }); @@ -2603,9 +2818,11 @@ mod tests { exec_success() }); - // Pre-fill the buffer up to its limit - let mut debug_buffer = - DebugBufferVec::::try_from(vec![0u8; DebugBufferVec::::bound()]).unwrap(); + // Pre-fill the buffer almost up to its limit, leaving not enough space to the message + let debug_buf_before = + DebugBufferVec::::try_from(vec![0u8; DebugBufferVec::::bound() - 5]) + .unwrap(); + let mut debug_buf_after = debug_buf_before.clone(); ExtBuilder::default().build().execute_with(|| { let schedule: Schedule = ::Schedule::get(); @@ -2613,24 +2830,22 @@ mod tests { let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); MockStack::run_call( - ALICE, + contract_origin, BOB, &mut gas_meter, &mut storage_meter, &schedule, 0, vec![], - Some(&mut debug_buffer), - Determinism::Deterministic, + Some(&mut debug_buf_after), + Determinism::Enforced, ) .unwrap(); - assert_eq!( - &String::from_utf8(debug_buffer[DebugBufferVec::::bound() - 17..].to_vec()) - .unwrap(), - "overflowing bytes" - ); + assert_eq!(debug_buf_before, debug_buf_after); }); } @@ -2639,7 +2854,7 @@ mod tests { // call the contract passed as input with disabled reentry let code_bob = MockLoader::insert(Call, |ctx, _| { let dest = Decode::decode(&mut ctx.input_data.as_ref()).unwrap(); - ctx.ext.call(Weight::zero(), dest, 0, vec![], false) + ctx.ext.call(Weight::zero(), BalanceOf::::zero(), dest, 0, vec![], false) }); let code_charlie = MockLoader::insert(Call, |_, _| exec_success()); @@ -2648,11 +2863,13 @@ mod tests { let schedule = ::Schedule::get(); place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); // Calling another contract should succeed assert_ok!(MockStack::run_call( - ALICE, + contract_origin.clone(), BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -2660,13 +2877,13 @@ mod tests { 0, CHARLIE.encode(), None, - Determinism::Deterministic + Determinism::Enforced )); // Calling into oneself fails assert_err!( MockStack::run_call( - ALICE, + contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -2674,7 +2891,7 @@ mod tests { 0, BOB.encode(), None, - Determinism::Deterministic + Determinism::Enforced ) .map_err(|e| e.error), >::ReentranceDenied, @@ -2686,26 +2903,30 @@ mod tests { fn call_deny_reentry() { let code_bob = MockLoader::insert(Call, |ctx, _| { if ctx.input_data[0] == 0 { - ctx.ext.call(Weight::zero(), CHARLIE, 0, vec![], false) + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), CHARLIE, 0, vec![], false) } else { exec_success() } }); // call BOB with input set to '1' - let code_charlie = - MockLoader::insert(Call, |ctx, _| ctx.ext.call(Weight::zero(), BOB, 0, vec![1], true)); + let code_charlie = MockLoader::insert(Call, |ctx, _| { + ctx.ext.call(Weight::zero(), BalanceOf::::zero(), BOB, 0, vec![1], true) + }); ExtBuilder::default().build().execute_with(|| { let schedule = ::Schedule::get(); place_contract(&BOB, code_bob); place_contract(&CHARLIE, code_charlie); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); // BOB -> CHARLIE -> BOB fails as BOB denies reentry. assert_err!( MockStack::run_call( - ALICE, + contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -2713,7 +2934,7 @@ mod tests { 0, vec![0], None, - Determinism::Deterministic + Determinism::Enforced ) .map_err(|e| e.error), >::ReentranceDenied, @@ -2737,10 +2958,12 @@ mod tests { let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); System::reset_events(); MockStack::run_call( - ALICE, + contract_origin, BOB, &mut gas_meter, &mut storage_meter, @@ -2748,7 +2971,7 @@ mod tests { 0, vec![], None, - Determinism::Deterministic, + Determinism::Enforced, ) .unwrap(); @@ -2767,10 +2990,10 @@ mod tests { EventRecord { phase: Phase::Initialization, event: MetaEvent::Contracts(crate::Event::Called { - caller: ALICE, + caller: Origin::from_account_id(ALICE), contract: BOB, }), - topics: vec![hash(&ALICE), hash(&BOB)], + topics: vec![hash(&Origin::::from_account_id(ALICE)), hash(&BOB)], }, ] ); @@ -2789,8 +3012,10 @@ mod tests { RuntimeCall::System(SysCall::remark_with_event { remark: b"Hello".to_vec() }); // transfers are disallowed by the `TestFiler` (see below) - let forbidden_call = - RuntimeCall::Balances(BalanceCall::transfer { dest: CHARLIE, value: 22 }); + let forbidden_call = RuntimeCall::Balances(BalanceCall::transfer_allow_death { + dest: CHARLIE, + value: 22, + }); // simple cases: direct call assert_err!( @@ -2810,7 +3035,7 @@ mod tests { }); TestFilter::set_filter(|call| match call { - RuntimeCall::Balances(pallet_balances::Call::transfer { .. }) => false, + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. }) => false, _ => true, }); @@ -2820,10 +3045,12 @@ mod tests { let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 10); place_contract(&BOB, code_hash); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); System::reset_events(); MockStack::run_call( - ALICE, + contract_origin, BOB, &mut gas_meter, &mut storage_meter, @@ -2831,7 +3058,7 @@ mod tests { 0, vec![], None, - Determinism::Deterministic, + Determinism::Enforced, ) .unwrap(); @@ -2863,10 +3090,10 @@ mod tests { EventRecord { phase: Phase::Initialization, event: MetaEvent::Contracts(crate::Event::Called { - caller: ALICE, + caller: Origin::from_account_id(ALICE), contract: BOB, }), - topics: vec![hash(&ALICE), hash(&BOB)], + topics: vec![hash(&Origin::::from_account_id(ALICE)), hash(&BOB)], }, ] ); @@ -2881,6 +3108,7 @@ mod tests { ctx.ext .instantiate( Weight::zero(), + BalanceOf::::zero(), fail_code, ctx.ext.minimum_balance() * 100, vec![], @@ -2894,6 +3122,7 @@ mod tests { .ext .instantiate( Weight::zero(), + BalanceOf::::zero(), success_code, ctx.ext.minimum_balance() * 100, vec![], @@ -2902,7 +3131,9 @@ mod tests { .unwrap(); // a plain call should not influence the account counter - ctx.ext.call(Weight::zero(), account_id, 0, vec![], false).unwrap(); + ctx.ext + .call(Weight::zero(), BalanceOf::::zero(), account_id, 0, vec![], false) + .unwrap(); exec_success() }); @@ -2911,18 +3142,17 @@ mod tests { let schedule = ::Schedule::get(); let min_balance = ::Currency::minimum_balance(); let mut gas_meter = GasMeter::::new(GAS_LIMIT); - let fail_executable = - MockExecutable::from_storage(fail_code, &schedule, &mut gas_meter).unwrap(); + let fail_executable = MockExecutable::from_storage(fail_code, &mut gas_meter).unwrap(); let success_executable = - MockExecutable::from_storage(success_code, &schedule, &mut gas_meter).unwrap(); + MockExecutable::from_storage(success_code, &mut gas_meter).unwrap(); let succ_fail_executable = - MockExecutable::from_storage(succ_fail_code, &schedule, &mut gas_meter).unwrap(); + MockExecutable::from_storage(succ_fail_code, &mut gas_meter).unwrap(); let succ_succ_executable = - MockExecutable::from_storage(succ_succ_code, &schedule, &mut gas_meter).unwrap(); - set_balance(&ALICE, min_balance * 1000); + MockExecutable::from_storage(succ_succ_code, &mut gas_meter).unwrap(); + set_balance(&ALICE, min_balance * 10_000); + let contract_origin = Origin::from_account_id(ALICE); let mut storage_meter = - storage::meter::Meter::new(&ALICE, Some(min_balance * 500), min_balance * 100) - .unwrap(); + storage::meter::Meter::new(&contract_origin, None, min_balance * 100).unwrap(); MockStack::run_instantiate( ALICE, @@ -2984,35 +3214,41 @@ mod tests { let code_hash = MockLoader::insert(Call, |ctx, _| { // Write assert_eq!( - ctx.ext.set_storage(&[1; 32], Some(vec![1, 2, 3]), false), + ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false), Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.set_storage(&[2; 32], Some(vec![4, 5, 6]), true), + ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![4, 5, 6]), true), + Ok(WriteOutcome::New) + ); + assert_eq!(ctx.ext.set_storage(&Key::Fix([3; 32]), None, false), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&Key::Fix([4; 32]), None, true), Ok(WriteOutcome::New)); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([5; 32]), Some(vec![]), false), + Ok(WriteOutcome::New) + ); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([6; 32]), Some(vec![]), true), Ok(WriteOutcome::New) ); - assert_eq!(ctx.ext.set_storage(&[3; 32], None, false), Ok(WriteOutcome::New)); - assert_eq!(ctx.ext.set_storage(&[4; 32], None, true), Ok(WriteOutcome::New)); - assert_eq!(ctx.ext.set_storage(&[5; 32], Some(vec![]), false), Ok(WriteOutcome::New)); - assert_eq!(ctx.ext.set_storage(&[6; 32], Some(vec![]), true), Ok(WriteOutcome::New)); // Overwrite assert_eq!( - ctx.ext.set_storage(&[1; 32], Some(vec![42]), false), + ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![42]), false), Ok(WriteOutcome::Overwritten(3)) ); assert_eq!( - ctx.ext.set_storage(&[2; 32], Some(vec![48]), true), + ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![48]), true), Ok(WriteOutcome::Taken(vec![4, 5, 6])) ); - assert_eq!(ctx.ext.set_storage(&[3; 32], None, false), Ok(WriteOutcome::New)); - assert_eq!(ctx.ext.set_storage(&[4; 32], None, true), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&Key::Fix([3; 32]), None, false), Ok(WriteOutcome::New)); + assert_eq!(ctx.ext.set_storage(&Key::Fix([4; 32]), None, true), Ok(WriteOutcome::New)); assert_eq!( - ctx.ext.set_storage(&[5; 32], Some(vec![]), false), + ctx.ext.set_storage(&Key::Fix([5; 32]), Some(vec![]), false), Ok(WriteOutcome::Overwritten(0)) ); assert_eq!( - ctx.ext.set_storage(&[6; 32], Some(vec![]), true), + ctx.ext.set_storage(&Key::Fix([6; 32]), Some(vec![]), true), Ok(WriteOutcome::Taken(vec![])) ); @@ -3025,9 +3261,10 @@ mod tests { let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); - let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap(); assert_ok!(MockStack::run_call( - ALICE, + contract_origin, BOB, &mut gas_meter, &mut storage_meter, @@ -3035,58 +3272,58 @@ mod tests { 0, vec![], None, - Determinism::Deterministic + Determinism::Enforced )); }); } #[test] - fn set_storage_transparent_works() { + fn set_storage_varsized_key_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { // Write assert_eq!( - ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from([1; 64].to_vec()).unwrap(), + ctx.ext.set_storage( + &Key::::try_from_var([1; 64].to_vec()).unwrap(), Some(vec![1, 2, 3]), false ), Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from([2; 19].to_vec()).unwrap(), + ctx.ext.set_storage( + &Key::::try_from_var([2; 19].to_vec()).unwrap(), Some(vec![4, 5, 6]), true ), Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from([3; 19].to_vec()).unwrap(), + ctx.ext.set_storage( + &Key::::try_from_var([3; 19].to_vec()).unwrap(), None, false ), Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from([4; 64].to_vec()).unwrap(), + ctx.ext.set_storage( + &Key::::try_from_var([4; 64].to_vec()).unwrap(), None, true ), Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from([5; 30].to_vec()).unwrap(), + ctx.ext.set_storage( + &Key::::try_from_var([5; 30].to_vec()).unwrap(), Some(vec![]), false ), Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from([6; 128].to_vec()).unwrap(), + ctx.ext.set_storage( + &Key::::try_from_var([6; 128].to_vec()).unwrap(), Some(vec![]), true ), @@ -3095,48 +3332,48 @@ mod tests { // Overwrite assert_eq!( - ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from([1; 64].to_vec()).unwrap(), + ctx.ext.set_storage( + &Key::::try_from_var([1; 64].to_vec()).unwrap(), Some(vec![42, 43, 44]), false ), Ok(WriteOutcome::Overwritten(3)) ); assert_eq!( - ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from([2; 19].to_vec()).unwrap(), + ctx.ext.set_storage( + &Key::::try_from_var([2; 19].to_vec()).unwrap(), Some(vec![48]), true ), Ok(WriteOutcome::Taken(vec![4, 5, 6])) ); assert_eq!( - ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from([3; 19].to_vec()).unwrap(), + ctx.ext.set_storage( + &Key::::try_from_var([3; 19].to_vec()).unwrap(), None, false ), Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from([4; 64].to_vec()).unwrap(), + ctx.ext.set_storage( + &Key::::try_from_var([4; 64].to_vec()).unwrap(), None, true ), Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from([5; 30].to_vec()).unwrap(), + ctx.ext.set_storage( + &Key::::try_from_var([5; 30].to_vec()).unwrap(), Some(vec![]), false ), Ok(WriteOutcome::Overwritten(0)) ); assert_eq!( - ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from([6; 128].to_vec()).unwrap(), + ctx.ext.set_storage( + &Key::::try_from_var([6; 128].to_vec()).unwrap(), Some(vec![]), true ), @@ -3152,9 +3389,10 @@ mod tests { let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); - let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap(); assert_ok!(MockStack::run_call( - ALICE, + contract_origin, BOB, &mut gas_meter, &mut storage_meter, @@ -3162,7 +3400,7 @@ mod tests { 0, vec![], None, - Determinism::Deterministic + Determinism::Enforced )); }); } @@ -3171,13 +3409,16 @@ mod tests { fn get_storage_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { assert_eq!( - ctx.ext.set_storage(&[1; 32], Some(vec![1, 2, 3]), false), + ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false), Ok(WriteOutcome::New) ); - assert_eq!(ctx.ext.set_storage(&[2; 32], Some(vec![]), false), Ok(WriteOutcome::New)); - assert_eq!(ctx.ext.get_storage(&[1; 32]), Some(vec![1, 2, 3])); - assert_eq!(ctx.ext.get_storage(&[2; 32]), Some(vec![])); - assert_eq!(ctx.ext.get_storage(&[3; 32]), None); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![]), false), + Ok(WriteOutcome::New) + ); + assert_eq!(ctx.ext.get_storage(&Key::Fix([1; 32])), Some(vec![1, 2, 3])); + assert_eq!(ctx.ext.get_storage(&Key::Fix([2; 32])), Some(vec![])); + assert_eq!(ctx.ext.get_storage(&Key::Fix([3; 32])), None); exec_success() }); @@ -3188,9 +3429,10 @@ mod tests { let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); - let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap(); assert_ok!(MockStack::run_call( - ALICE, + contract_origin, BOB, &mut gas_meter, &mut storage_meter, @@ -3198,7 +3440,7 @@ mod tests { 0, vec![], None, - Determinism::Deterministic + Determinism::Enforced )); }); } @@ -3207,13 +3449,16 @@ mod tests { fn get_storage_size_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { assert_eq!( - ctx.ext.set_storage(&[1; 32], Some(vec![1, 2, 3]), false), + ctx.ext.set_storage(&Key::Fix([1; 32]), Some(vec![1, 2, 3]), false), Ok(WriteOutcome::New) ); - assert_eq!(ctx.ext.set_storage(&[2; 32], Some(vec![]), false), Ok(WriteOutcome::New)); - assert_eq!(ctx.ext.get_storage_size(&[1; 32]), Some(3)); - assert_eq!(ctx.ext.get_storage_size(&[2; 32]), Some(0)); - assert_eq!(ctx.ext.get_storage_size(&[3; 32]), None); + assert_eq!( + ctx.ext.set_storage(&Key::Fix([2; 32]), Some(vec![]), false), + Ok(WriteOutcome::New) + ); + assert_eq!(ctx.ext.get_storage_size(&Key::Fix([1; 32])), Some(3)); + assert_eq!(ctx.ext.get_storage_size(&Key::Fix([2; 32])), Some(0)); + assert_eq!(ctx.ext.get_storage_size(&Key::Fix([3; 32])), None); exec_success() }); @@ -3224,9 +3469,10 @@ mod tests { let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); - let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap(); assert_ok!(MockStack::run_call( - ALICE, + contract_origin, BOB, &mut gas_meter, &mut storage_meter, @@ -3234,46 +3480,40 @@ mod tests { 0, vec![], None, - Determinism::Deterministic + Determinism::Enforced )); }); } #[test] - fn get_storage_transparent_works() { + fn get_storage_varsized_key_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { assert_eq!( - ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from([1; 19].to_vec()).unwrap(), + ctx.ext.set_storage( + &Key::::try_from_var([1; 19].to_vec()).unwrap(), Some(vec![1, 2, 3]), false ), Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from([2; 16].to_vec()).unwrap(), + ctx.ext.set_storage( + &Key::::try_from_var([2; 16].to_vec()).unwrap(), Some(vec![]), false ), Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.get_storage_transparent( - &VarSizedKey::::try_from([1; 19].to_vec()).unwrap() - ), + ctx.ext.get_storage(&Key::::try_from_var([1; 19].to_vec()).unwrap()), Some(vec![1, 2, 3]) ); assert_eq!( - ctx.ext.get_storage_transparent( - &VarSizedKey::::try_from([2; 16].to_vec()).unwrap() - ), + ctx.ext.get_storage(&Key::::try_from_var([2; 16].to_vec()).unwrap()), Some(vec![]) ); assert_eq!( - ctx.ext.get_storage_transparent( - &VarSizedKey::::try_from([3; 8].to_vec()).unwrap() - ), + ctx.ext.get_storage(&Key::::try_from_var([3; 8].to_vec()).unwrap()), None ); @@ -3286,9 +3526,10 @@ mod tests { let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); - let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap(); assert_ok!(MockStack::run_call( - ALICE, + contract_origin, BOB, &mut gas_meter, &mut storage_meter, @@ -3296,46 +3537,40 @@ mod tests { 0, vec![], None, - Determinism::Deterministic + Determinism::Enforced )); }); } #[test] - fn get_storage_size_transparent_works() { + fn get_storage_size_varsized_key_works() { let code_hash = MockLoader::insert(Call, |ctx, _| { assert_eq!( - ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from([1; 19].to_vec()).unwrap(), + ctx.ext.set_storage( + &Key::::try_from_var([1; 19].to_vec()).unwrap(), Some(vec![1, 2, 3]), false ), Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from([2; 16].to_vec()).unwrap(), + ctx.ext.set_storage( + &Key::::try_from_var([2; 16].to_vec()).unwrap(), Some(vec![]), false ), Ok(WriteOutcome::New) ); assert_eq!( - ctx.ext.get_storage_size_transparent( - &VarSizedKey::::try_from([1; 19].to_vec()).unwrap() - ), + ctx.ext.get_storage_size(&Key::::try_from_var([1; 19].to_vec()).unwrap()), Some(3) ); assert_eq!( - ctx.ext.get_storage_size_transparent( - &VarSizedKey::::try_from([2; 16].to_vec()).unwrap() - ), + ctx.ext.get_storage_size(&Key::::try_from_var([2; 16].to_vec()).unwrap()), Some(0) ); assert_eq!( - ctx.ext.get_storage_size_transparent( - &VarSizedKey::::try_from([3; 8].to_vec()).unwrap() - ), + ctx.ext.get_storage_size(&Key::::try_from_var([3; 8].to_vec()).unwrap()), None ); @@ -3348,9 +3583,10 @@ mod tests { let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); - let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap(); assert_ok!(MockStack::run_call( - ALICE, + contract_origin, BOB, &mut gas_meter, &mut storage_meter, @@ -3358,7 +3594,7 @@ mod tests { 0, vec![], None, - Determinism::Deterministic + Determinism::Enforced )); }); } @@ -3371,7 +3607,9 @@ mod tests { ); assert_eq!( ctx.ext.ecdsa_to_eth_address(&pubkey_compressed).unwrap(), - array_bytes::hex2array_unchecked::<20>("09231da7b19A016f9e576d23B16277062F4d46A8") + array_bytes::hex2array_unchecked::<_, 20>( + "09231da7b19A016f9e576d23B16277062F4d46A8" + ) ); exec_success() }); @@ -3380,9 +3618,11 @@ mod tests { let schedule = ::Schedule::get(); place_contract(&BOB, bob_ch); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( - ALICE, + contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -3390,7 +3630,7 @@ mod tests { 0, vec![], None, - Determinism::Deterministic, + Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); @@ -3403,19 +3643,35 @@ mod tests { let code_hash = MockLoader::insert(Call, move |ctx, _| { // It is set to one when this contract was instantiated by `place_contract` assert_eq!(ctx.ext.nonce(), 1); - // Should not change without any instantation in-between + // Should not change without any instantiation in-between assert_eq!(ctx.ext.nonce(), 1); // Should not change with a failed instantiation assert_err!( - ctx.ext.instantiate(Weight::zero(), fail_code, 0, vec![], &[],), + ctx.ext.instantiate( + Weight::zero(), + BalanceOf::::zero(), + fail_code, + 0, + vec![], + &[], + ), ExecError { error: >::ContractTrapped.into(), origin: ErrorOrigin::Callee } ); assert_eq!(ctx.ext.nonce(), 1); - // Successful instantation increments - ctx.ext.instantiate(Weight::zero(), success_code, 0, vec![], &[]).unwrap(); + // Successful instantiation increments + ctx.ext + .instantiate( + Weight::zero(), + BalanceOf::::zero(), + success_code, + 0, + vec![], + &[], + ) + .unwrap(); assert_eq!(ctx.ext.nonce(), 2); exec_success() }); @@ -3426,9 +3682,10 @@ mod tests { let mut gas_meter = GasMeter::::new(GAS_LIMIT); set_balance(&ALICE, min_balance * 1000); place_contract(&BOB, code_hash); - let mut storage_meter = storage::meter::Meter::new(&ALICE, None, 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = storage::meter::Meter::new(&contract_origin, None, 0).unwrap(); assert_ok!(MockStack::run_call( - ALICE, + contract_origin, BOB, &mut gas_meter, &mut storage_meter, @@ -3436,7 +3693,7 @@ mod tests { 0, vec![], None, - Determinism::Deterministic + Determinism::Enforced )); }); } @@ -3456,9 +3713,11 @@ mod tests { let schedule = ::Schedule::get(); place_contract(&BOB, code_hash); - let mut storage_meter = storage::meter::Meter::new(&ALICE, Some(0), 0).unwrap(); + let contract_origin = Origin::from_account_id(ALICE); + let mut storage_meter = + storage::meter::Meter::new(&contract_origin, Some(0), 0).unwrap(); let result = MockStack::run_call( - ALICE, + contract_origin, BOB, &mut GasMeter::::new(GAS_LIMIT), &mut storage_meter, @@ -3466,7 +3725,7 @@ mod tests { 0, vec![], None, - Determinism::Deterministic, + Determinism::Enforced, ); assert_matches!(result, Ok(_)); }); diff --git a/frame/contracts/src/gas.rs b/frame/contracts/src/gas.rs index c8383f307c0e..7d17642d92e5 100644 --- a/frame/contracts/src/gas.rs +++ b/frame/contracts/src/gas.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,6 +23,7 @@ use frame_support::{ weights::Weight, DefaultNoBound, }; +use sp_core::Get; use sp_runtime::traits::Zero; use sp_std::marker::PhantomData; @@ -80,6 +81,8 @@ pub struct GasMeter { gas_left: Weight, /// Due to `adjust_gas` and `nested` the `gas_left` can temporarily dip below its final value. gas_left_lowest: Weight, + /// Amount of fuel consumed by the engine from the last host function call. + engine_consumed: u64, _phantom: PhantomData, #[cfg(test)] tokens: Vec, @@ -91,6 +94,7 @@ impl GasMeter { gas_limit, gas_left: gas_limit, gas_left_lowest: gas_limit, + engine_consumed: Default::default(), _phantom: PhantomData, #[cfg(test)] tokens: Vec::new(), @@ -151,7 +155,7 @@ impl GasMeter { /// Amount is calculated by the given `token`. /// /// Returns `OutOfGas` if there is not enough gas or addition of the specified - /// amount of gas has lead to overflow. On success returns `Proceed`. + /// amount of gas has lead to overflow. /// /// NOTE that amount isn't consumed if there is not enough gas. This is considered /// safe because we always charge gas before performing any resource-spending action. @@ -181,17 +185,45 @@ impl GasMeter { self.gas_left = self.gas_left.saturating_add(adjustment).min(self.gas_limit); } + /// This method is used for gas syncs with the engine. + /// + /// Updates internal `engine_comsumed` tracker of engine fuel consumption. + /// + /// Charges self with the `ref_time` Weight corresponding to wasmi fuel consumed on the engine + /// side since last sync. Passed value is scaled by multiplying it by the weight of a basic + /// operation, as such an operation in wasmi engine costs 1. + /// + /// Returns the updated `gas_left` `Weight` value from the meter. + /// Normally this would never fail, as engine should fail first when out of gas. + pub fn charge_fuel(&mut self, wasmi_fuel_total: u64) -> Result { + // Take the part consumed since the last update. + let wasmi_fuel = wasmi_fuel_total.saturating_sub(self.engine_consumed); + if !wasmi_fuel.is_zero() { + self.engine_consumed = wasmi_fuel_total; + let reftime_consumed = + wasmi_fuel.saturating_mul(T::Schedule::get().instruction_weights.base as u64); + let ref_time_left = self + .gas_left + .ref_time() + .checked_sub(reftime_consumed) + .ok_or_else(|| Error::::OutOfGas)?; + + *(self.gas_left.ref_time_mut()) = ref_time_left; + } + Ok(self.gas_left) + } + /// Returns the amount of gas that is required to run the same call. /// /// This can be different from `gas_spent` because due to `adjust_gas` the amount of /// spent gas can temporarily drop and be refunded later. pub fn gas_required(&self) -> Weight { - self.gas_limit - self.gas_left_lowest() + self.gas_limit.saturating_sub(self.gas_left_lowest()) } /// Returns how much gas was spent pub fn gas_consumed(&self) -> Weight { - self.gas_limit - self.gas_left + self.gas_limit.saturating_sub(self.gas_left) } /// Returns how much gas left from the initial budget. @@ -275,19 +307,19 @@ mod tests { struct SimpleToken(u64); impl Token for SimpleToken { fn weight(&self) -> Weight { - Weight::from_ref_time(self.0) + Weight::from_parts(self.0, 0) } } #[test] fn it_works() { - let gas_meter = GasMeter::::new(Weight::from_ref_time(50000)); - assert_eq!(gas_meter.gas_left(), Weight::from_ref_time(50000)); + let gas_meter = GasMeter::::new(Weight::from_parts(50000, 0)); + assert_eq!(gas_meter.gas_left(), Weight::from_parts(50000, 0)); } #[test] fn tracing() { - let mut gas_meter = GasMeter::::new(Weight::from_ref_time(50000)); + let mut gas_meter = GasMeter::::new(Weight::from_parts(50000, 0)); assert!(!gas_meter.charge(SimpleToken(1)).is_err()); let mut tokens = gas_meter.tokens().iter(); @@ -304,7 +336,7 @@ mod tests { // Make sure that the gas meter does not charge in case of overcharger #[test] fn overcharge_does_not_charge() { - let mut gas_meter = GasMeter::::new(Weight::from_ref_time(200)); + let mut gas_meter = GasMeter::::new(Weight::from_parts(200, 0)); // The first charge is should lead to OOG. assert!(gas_meter.charge(SimpleToken(300)).is_err()); @@ -317,7 +349,7 @@ mod tests { // possible. #[test] fn charge_exact_amount() { - let mut gas_meter = GasMeter::::new(Weight::from_ref_time(25)); + let mut gas_meter = GasMeter::::new(Weight::from_parts(25, 0)); assert!(!gas_meter.charge(SimpleToken(25)).is_err()); } } diff --git a/frame/contracts/src/lib.rs b/frame/contracts/src/lib.rs index f97b4b790cf6..da4bd3bad230 100644 --- a/frame/contracts/src/lib.rs +++ b/frame/contracts/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,9 +15,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! # Contract Pallet +//! # Contracts Pallet //! -//! The Contract module provides functionality for the runtime to deploy and execute WebAssembly +//! The Contracts module provides functionality for the runtime to deploy and execute WebAssembly //! smart-contracts. //! //! - [`Config`] @@ -30,7 +30,7 @@ //! These "smart-contract accounts" have the ability to instantiate smart-contracts and make calls //! to other contract and non-contract accounts. //! -//! The smart-contract code is stored once in a code cache, and later retrievable via its hash. +//! The smart-contract code is stored once, and later retrievable via its hash. //! This means that multiple smart-contracts can be instantiated from the same hash, without //! replicating the code each time. //! @@ -41,14 +41,14 @@ //! Finally, when an account is reaped, its associated code and storage of the smart-contract //! account will also be deleted. //! -//! ### Gas +//! ### Weight //! -//! Senders must specify a gas limit with every call, as all instructions invoked by the -//! smart-contract require gas. Unused gas is refunded after the call, regardless of the execution -//! outcome. +//! Senders must specify a [`Weight`] limit with every call, as all instructions invoked by the +//! smart-contract require weight. Unused weight is refunded after the call, regardless of the +//! execution outcome. //! -//! If the gas limit is reached, then all calls and state changes (including balance transfers) are -//! only reverted at the current call's contract level. For example, if contract A calls B and B +//! If the weight limit is reached, then all calls and state changes (including balance transfers) +//! are only reverted at the current call's contract level. For example, if contract A calls B and B //! runs out of gas mid-call, then all of B's calls are reverted. Assuming correct error handling by //! contract A, A's other calls and state changes still persist. //! @@ -63,73 +63,82 @@ //! //! ### Dispatchable functions //! -//! * [`Pallet::instantiate_with_code`] - Deploys a new contract from the supplied wasm binary, +//! * [`Pallet::instantiate_with_code`] - Deploys a new contract from the supplied Wasm binary, //! optionally transferring //! some balance. This instantiates a new smart contract account with the supplied code and //! calls its constructor to initialize the contract. //! * [`Pallet::instantiate`] - The same as `instantiate_with_code` but instead of uploading new //! code an existing `code_hash` is supplied. //! * [`Pallet::call`] - Makes a call to an account, optionally transferring some balance. +//! * [`Pallet::upload_code`] - Uploads new code without instantiating a contract from it. +//! * [`Pallet::remove_code`] - Removes the stored code and refunds the deposit to its owner. Only +//! allowed to code owner. +//! * [`Pallet::set_code`] - Changes the code of an existing contract. Only allowed to `Root` +//! origin. +//! * [`Pallet::migrate`] - Runs migration steps of current multi-block migration in priority, +//! before [`Hooks::on_idle`][frame_support::traits::Hooks::on_idle] activates. //! //! ## Usage //! -//! The Contract module is a work in progress. The following examples show how this Contract module -//! can be used to instantiate and call contracts. -//! -//! * [`ink`](https://github.com/paritytech/ink) is -//! an [`eDSL`](https://wiki.haskell.org/Embedded_domain_specific_language) that enables writing -//! WebAssembly based smart contracts in the Rust programming language. +//! * [`ink!`](https://use.ink) is language that enables writing Wasm-based smart contracts in plain +//! Rust. +#![allow(rustdoc::private_intra_doc_links)] #![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(feature = "runtime-benchmarks", recursion_limit = "512")] +#![cfg_attr(feature = "runtime-benchmarks", recursion_limit = "1024")] -#[macro_use] -mod gas; +mod address; mod benchmarking; mod exec; -mod migration; +mod gas; mod schedule; mod storage; mod wasm; pub mod chain_extension; +pub mod migration; pub mod weights; #[cfg(test)] mod tests; - use crate::{ - exec::{AccountIdOf, ExecError, Executable, Stack as ExecStack}, + exec::{AccountIdOf, ErrorOrigin, ExecError, Executable, Key, Stack as ExecStack}, gas::GasMeter, - storage::{meter::Meter as StorageMeter, ContractInfo, DeletedContract, Storage}, - wasm::{OwnerInfo, PrefabWasmModule, TryInstantiate}, - weights::WeightInfo, + storage::{meter::Meter as StorageMeter, ContractInfo, DeletionQueueManager}, + wasm::{CodeInfo, WasmBlob}, }; use codec::{Codec, Decode, Encode, HasCompact}; +use environmental::*; use frame_support::{ - dispatch::{Dispatchable, GetDispatchInfo, Pays, PostDispatchInfo}, + dispatch::{ + DispatchError, Dispatchable, GetDispatchInfo, Pays, PostDispatchInfo, RawOrigin, + WithPostDispatchInfo, + }, ensure, + error::BadOrigin, traits::{ tokens::fungible::Inspect, ConstU32, Contains, Currency, Get, Randomness, ReservableCurrency, Time, }, - weights::{OldWeight, Weight}, - BoundedVec, WeakBoundedVec, + weights::Weight, + BoundedVec, RuntimeDebugNoBound, }; -use frame_system::Pallet as System; +use frame_system::{ensure_signed, pallet_prelude::OriginFor, EventRecord, Pallet as System}; use pallet_contracts_primitives::{ Code, CodeUploadResult, CodeUploadReturnValue, ContractAccessError, ContractExecResult, - ContractInstantiateResult, ExecReturnValue, GetStorageResult, InstantiateReturnValue, - StorageDeposit, + ContractInstantiateResult, ContractResult, ExecReturnValue, GetStorageResult, + InstantiateReturnValue, StorageDeposit, }; use scale_info::TypeInfo; use smallvec::Array; -use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup, TrailingZeroInput}; -use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; +use sp_runtime::traits::{Convert, Hash, Saturating, StaticLookup, Zero}; +use sp_std::{fmt::Debug, prelude::*}; +pub use weights::WeightInfo; pub use crate::{ - exec::{Frame, VarSizedKey as StorageKey}, - migration::Migration, + address::{AddressGenerator, DefaultAddressGenerator}, + exec::Frame, + migration::{MigrateSequence, Migration, NoopMigration}, pallet::*, schedule::{HostFnWeights, InstructionWeights, Limits, Schedule}, wasm::Determinism, @@ -143,9 +152,16 @@ type TrieId = BoundedVec>; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type CodeVec = BoundedVec::MaxCodeLen>; -type RelaxedCodeVec = WeakBoundedVec::MaxCodeLen>; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; type DebugBufferVec = BoundedVec::MaxDebugBufferLen>; +type EventRecordOf = + EventRecord<::RuntimeEvent, ::Hash>; + +/// The old weight type. +/// +/// This is a copy of the [`frame_support::weights::OldWeight`] type since the contracts pallet +/// needs to support it indefinitely. +type OldWeight = u64; /// Used as a sentinel value when reading and writing contract memory. /// @@ -155,48 +171,12 @@ type DebugBufferVec = BoundedVec::MaxDebugBufferLen>; /// that this value makes sense for a memory location or length. const SENTINEL: u32 = u32::MAX; -/// Provides the contract address generation method. +/// The target that is used for the log output emitted by this crate. /// -/// See [`DefaultAddressGenerator`] for the default implementation. -pub trait AddressGenerator { - /// Generate the address of a contract based on the given instantiate parameters. - /// - /// # Note for implementors - /// 1. Make sure that there are no collisions, different inputs never lead to the same output. - /// 2. Make sure that the same inputs lead to the same output. - /// 3. Changing the implementation through a runtime upgrade without a proper storage migration - /// would lead to catastrophic misbehavior. - fn generate_address( - deploying_address: &T::AccountId, - code_hash: &CodeHash, - input_data: &[u8], - salt: &[u8], - ) -> T::AccountId; -} - -/// Default address generator. +/// Hence you can use this target to selectively increase the log level for this crate. /// -/// This is the default address generator used by contract instantiation. Its result -/// is only dependant on its inputs. It can therefore be used to reliably predict the -/// address of a contract. This is akin to the formula of eth's CREATE2 opcode. There -/// is no CREATE equivalent because CREATE2 is strictly more powerful. -/// Formula: -/// `hash("contract_addr_v1" ++ deploying_address ++ code_hash ++ input_data ++ salt)` -pub struct DefaultAddressGenerator; - -impl AddressGenerator for DefaultAddressGenerator { - fn generate_address( - deploying_address: &T::AccountId, - code_hash: &CodeHash, - input_data: &[u8], - salt: &[u8], - ) -> T::AccountId { - let entropy = (b"contract_addr_v1", deploying_address, code_hash, input_data, salt) - .using_encoded(T::Hashing::hash); - Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref())) - .expect("infinite length input; no invalid inputs for type; qed") - } -} +/// Example: `RUST_LOG=runtime::contracts=debug my_code --dev` +const LOG_TARGET: &str = "runtime::contracts"; #[frame_support::pallet] pub mod pallet { @@ -205,11 +185,16 @@ pub mod pallet { use frame_system::pallet_prelude::*; /// The current storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(9); + #[cfg(not(any(test, feature = "runtime-benchmarks")))] + const STORAGE_VERSION: StorageVersion = StorageVersion::new(12); + + /// Hard coded storage version for running tests that depend on the current storage version. + #[cfg(any(test, feature = "runtime-benchmarks"))] + const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] - pub struct Pallet(PhantomData); + pub struct Pallet(_); #[pallet::config] pub trait Config: frame_system::Config { @@ -224,10 +209,10 @@ pub mod pallet { /// be instantiated from existing codes that use this deprecated functionality. It will /// be removed eventually. Hence for new `pallet-contracts` deployments it is okay /// to supply a dummy implementation for this type (because it is never used). - type Randomness: Randomness; + type Randomness: Randomness>; /// The currency in which fees are paid and contract balances are held. - type Currency: ReservableCurrency + type Currency: ReservableCurrency // TODO: Move to fungible traits + Inspect>; /// The overarching event type. @@ -285,33 +270,6 @@ pub mod pallet { /// memory usage of your runtime. type CallStack: Array>; - /// The maximum number of contracts that can be pending for deletion. - /// - /// When a contract is deleted by calling `seal_terminate` it becomes inaccessible - /// immediately, but the deletion of the storage items it has accumulated is performed - /// later. The contract is put into the deletion queue. This defines how many - /// contracts can be queued up at the same time. If that limit is reached `seal_terminate` - /// will fail. The action must be retried in a later block in that case. - /// - /// The reasons for limiting the queue depth are: - /// - /// 1. The queue is in storage in order to be persistent between blocks. We want to limit - /// the amount of storage that can be consumed. - /// 2. The queue is stored in a vector and needs to be decoded as a whole when reading - /// it at the end of each block. Longer queues take more weight to decode and hence - /// limit the amount of items that can be deleted per block. - #[pallet::constant] - type DeletionQueueDepth: Get; - - /// The maximum amount of weight that can be consumed per block for lazy trie removal. - /// - /// The amount of weight that is dedicated per block to work on the deletion queue. Larger - /// values allow more trie keys to be deleted in each block but reduce the amount of - /// weight that is left for transactions. See [`Self::DeletionQueueDepth`] for more - /// information about the deletion queue. - #[pallet::constant] - type DeletionWeightLimit: Get; - /// The amount of balance a caller has to pay for each byte of storage. /// /// # Note @@ -320,6 +278,10 @@ pub mod pallet { #[pallet::constant] type DepositPerByte: Get>; + /// Fallback value to limit the storage deposit if it's not being set by the caller. + #[pallet::constant] + type DefaultDepositLimit: Get>; + /// The amount of balance a caller has to pay for each storage item. /// /// # Note @@ -331,9 +293,7 @@ pub mod pallet { /// The address generator used to generate the addresses of contracts. type AddressGenerator: AddressGenerator; - /// The maximum length of a contract code in bytes. This limit applies to the instrumented - /// version of the code. Therefore `instantiate_with_code` can fail even when supplying - /// a wasm binary below this maximum size. + /// The maximum length of a contract code in bytes. /// /// The value should be chosen carefully taking into the account the overall memory limit /// your runtime has, as well as the [maximum allowed callstack @@ -360,37 +320,55 @@ pub mod pallet { /// The maximum length of the debug buffer in bytes. #[pallet::constant] type MaxDebugBufferLen: Get; + + /// The sequence of migration steps that will be applied during a migration. + /// + /// # Examples + /// ``` + /// use pallet_contracts::migration::{v10, v11}; + /// # struct Runtime {}; + /// type Migrations = (v10::Migration, v11::Migration); + /// ``` + /// + /// If you have a single migration step, you can use a tuple with a single element: + /// ``` + /// use pallet_contracts::migration::v10; + /// # struct Runtime {}; + /// type Migrations = (v10::Migration,); + /// ``` + type Migrations: MigrateSequence; } #[pallet::hooks] impl Hooks> for Pallet { - fn on_idle(_block: T::BlockNumber, remaining_weight: Weight) -> Weight { - Storage::::process_deletion_queue_batch(remaining_weight) - .saturating_add(T::WeightInfo::on_process_deletion_queue_batch()) - } - - fn on_initialize(_block: T::BlockNumber) -> Weight { - // We want to process the deletion_queue in the on_idle hook. Only in the case - // that the queue length has reached its maximal depth, we process it here. - let max_len = T::DeletionQueueDepth::get() as usize; - let queue_len = >::decode_len().unwrap_or(0); - if queue_len >= max_len { - // We do not want to go above the block limit and rather avoid lazy deletion - // in that case. This should only happen on runtime upgrades. - let weight_limit = T::BlockWeights::get() - .max_block - .saturating_sub(System::::block_weight().total()) - .min(T::DeletionWeightLimit::get()); - Storage::::process_deletion_queue_batch(weight_limit) - .saturating_add(T::WeightInfo::on_process_deletion_queue_batch()) - } else { - T::WeightInfo::on_process_deletion_queue_batch() + fn on_idle(_block: BlockNumberFor, mut remaining_weight: Weight) -> Weight { + use migration::MigrateResult::*; + + loop { + let (result, weight) = Migration::::migrate(remaining_weight); + remaining_weight.saturating_reduce(weight); + + match result { + // There is not enough weight to perform a migration, or make any progress, we + // just return the remaining weight. + NoMigrationPerformed | InProgress { steps_done: 0 } => return remaining_weight, + // Migration is still in progress, we can start the next step. + InProgress { .. } => continue, + // Either no migration is in progress, or we are done with all migrations, we + // can do some more other work with the remaining weight. + Completed | NoMigrationInProgress => break, + } } + + ContractInfo::::process_deletion_queue_batch(remaining_weight) + .saturating_add(T::WeightInfo::on_process_deletion_queue_batch()) } fn integrity_test() { - // Total runtime memory is expected to have 128Mb upper limit - const MAX_RUNTIME_MEM: u32 = 1024 * 1024 * 128; + Migration::::integrity_test(); + + // Total runtime memory limit + let max_runtime_mem: u32 = T::Schedule::get().limits.runtime_memory; // Memory limits for a single contract: // Value stack size: 1Mb per contract, default defined in wasmi const MAX_STACK_SIZE: u32 = 1024 * 1024; @@ -402,19 +380,19 @@ pub mod pallet { // Check that given configured `MaxCodeLen`, runtime heap memory limit can't be broken. // - // In worst case, the decoded wasm contract code would be `x16` times larger than the + // In worst case, the decoded Wasm contract code would be `x16` times larger than the // encoded one. This is because even a single-byte wasm instruction has 16-byte size in // wasmi. This gives us `MaxCodeLen*16` safety margin. // - // Next, the pallet keeps both the original and instrumented wasm blobs for each - // contract, hence we add up `MaxCodeLen*2` more to the safety margin. + // Next, the pallet keeps the Wasm blob for each + // contract, hence we add up `MaxCodeLen` to the safety margin. // // Finally, the inefficiencies of the freeing-bump allocator // being used in the client for the runtime memory allocations, could lead to possible // memory allocations for contract code grow up to `x4` times in some extreme cases, - // which gives us total multiplier of `18*4` for `MaxCodeLen`. + // which gives us total multiplier of `17*4` for `MaxCodeLen`. // - // That being said, for every contract executed in runtime, at least `MaxCodeLen*18*4` + // That being said, for every contract executed in runtime, at least `MaxCodeLen*17*4` // memory should be available. Note that maximum allowed heap memory and stack size per // each contract (stack frame) should also be counted. // @@ -423,16 +401,16 @@ pub mod pallet { // // This gives us the following formula: // - // `(MaxCodeLen * 18 * 4 + MAX_STACK_SIZE + max_heap_size) * max_call_depth < - // MAX_RUNTIME_MEM/2` + // `(MaxCodeLen * 17 * 4 + MAX_STACK_SIZE + max_heap_size) * max_call_depth < + // max_runtime_mem/2` // // Hence the upper limit for the `MaxCodeLen` can be defined as follows: - let code_len_limit = MAX_RUNTIME_MEM + let code_len_limit = max_runtime_mem .saturating_div(2) .saturating_div(max_call_depth) .saturating_sub(max_heap_size) .saturating_sub(MAX_STACK_SIZE) - .saturating_div(18 * 4); + .saturating_div(17 * 4); assert!( T::MaxCodeLen::get() < code_len_limit, @@ -443,7 +421,7 @@ pub mod pallet { T::MaxCodeLen::get(), ); - // Debug buffer should at least be large enough to accomodate a simple error message + // Debug buffer should at least be large enough to accommodate a simple error message const MIN_DEBUG_BUF_SIZE: u32 = 256; assert!( T::MaxDebugBufferLen::get() > MIN_DEBUG_BUF_SIZE, @@ -543,15 +521,15 @@ pub mod pallet { /// /// If the code does not already exist a deposit is reserved from the caller /// and unreserved only when [`Self::remove_code`] is called. The size of the reserve - /// depends on the instrumented size of the the supplied `code`. + /// depends on the size of the supplied `code`. /// /// If the code already exists in storage it will still return `Ok` and upgrades /// the in storage version to the current /// [`InstructionWeights::version`](InstructionWeights). /// - /// - `determinism`: If this is set to any other value but [`Determinism::Deterministic`] - /// then the only way to use this code is to delegate call into it from an offchain - /// execution. Set to [`Determinism::Deterministic`] if in doubt. + /// - `determinism`: If this is set to any other value but [`Determinism::Enforced`] then + /// the only way to use this code is to delegate call into it from an offchain execution. + /// Set to [`Determinism::Enforced`] if in doubt. /// /// # Note /// @@ -567,6 +545,7 @@ pub mod pallet { storage_deposit_limit: Option< as codec::HasCompact>::Type>, determinism: Determinism, ) -> DispatchResult { + Migration::::ensure_migrated()?; let origin = ensure_signed(origin)?; Self::bare_upload_code(origin, code, storage_deposit_limit.map(Into::into), determinism) .map(|_| ()) @@ -582,8 +561,9 @@ pub mod pallet { origin: OriginFor, code_hash: CodeHash, ) -> DispatchResultWithPostInfo { + Migration::::ensure_migrated()?; let origin = ensure_signed(origin)?; - >::remove(&origin, code_hash)?; + >::remove(&origin, code_hash)?; // we waive the fee because removing unused code is beneficial Ok(Pays::No.into()) } @@ -605,6 +585,7 @@ pub mod pallet { dest: AccountIdLookupOf, code_hash: CodeHash, ) -> DispatchResult { + Migration::::ensure_migrated()?; ensure_root(origin)?; let dest = T::Lookup::lookup(dest)?; >::try_mutate(&dest, |contract| { @@ -613,8 +594,8 @@ pub mod pallet { } else { return Err(>::ContractNotFound.into()) }; - >::add_user(code_hash)?; - >::remove_user(contract.code_hash); + >::increment_refcount(code_hash)?; + >::decrement_refcount(contract.code_hash); Self::deposit_event( vec![T::Hashing::hash_of(&dest), code_hash, contract.code_hash], Event::ContractCodeUpdated { @@ -654,19 +635,18 @@ pub mod pallet { storage_deposit_limit: Option< as codec::HasCompact>::Type>, data: Vec, ) -> DispatchResultWithPostInfo { - let gas_limit: Weight = gas_limit.into(); - let origin = ensure_signed(origin)?; - let dest = T::Lookup::lookup(dest)?; - let mut output = Self::internal_call( - origin, - dest, + Migration::::ensure_migrated()?; + let common = CommonInput { + origin: Origin::from_runtime_origin(origin)?, value, - gas_limit, - storage_deposit_limit.map(Into::into), data, - None, - Determinism::Deterministic, - ); + gas_limit: gas_limit.into(), + storage_deposit_limit: storage_deposit_limit.map(Into::into), + debug_message: None, + }; + let dest = T::Lookup::lookup(dest)?; + let mut output = + CallInput:: { dest, determinism: Determinism::Enforced }.run_guarded(common); if let Ok(retval) = &output.result { if retval.did_revert() { output.result = Err(>::ContractReverted.into()); @@ -694,8 +674,7 @@ pub mod pallet { /// /// Instantiation is executed as follows: /// - /// - The supplied `code` is instrumented, deployed, and a `code_hash` is created for that - /// code. + /// - The supplied `code` is deployed, and a `code_hash` is created for that code. /// - If the `code_hash` already exists on the chain the underlying `code` will be shared. /// - The destination address is computed based on the sender, code_hash and the salt. /// - The smart-contract account is created at the computed address. @@ -715,25 +694,41 @@ pub mod pallet { data: Vec, salt: Vec, ) -> DispatchResultWithPostInfo { + Migration::::ensure_migrated()?; let origin = ensure_signed(origin)?; let code_len = code.len() as u32; + + let (module, upload_deposit) = Self::try_upload_code( + origin.clone(), + code, + storage_deposit_limit.clone().map(Into::into), + Determinism::Enforced, + None, + )?; + + // Reduces the storage deposit limit by the amount that was reserved for the upload. + let storage_deposit_limit = + storage_deposit_limit.map(|limit| limit.into().saturating_sub(upload_deposit)); + let data_len = data.len() as u32; let salt_len = salt.len() as u32; - let mut output = Self::internal_instantiate( - origin, + let common = CommonInput { + origin: Origin::from_account_id(origin), value, - gas_limit, - storage_deposit_limit.map(Into::into), - Code::Upload(code), data, - salt, - None, - ); + gas_limit, + storage_deposit_limit, + debug_message: None, + }; + + let mut output = + InstantiateInput:: { code: WasmCode::Wasm(module), salt }.run_guarded(common); if let Ok(retval) = &output.result { if retval.1.did_revert() { output.result = Err(>::ContractReverted.into()); } } + output.gas_meter.into_dispatch_result( output.result.map(|(_address, result)| result), T::WeightInfo::instantiate_with_code(code_len, data_len, salt_len), @@ -758,19 +753,19 @@ pub mod pallet { data: Vec, salt: Vec, ) -> DispatchResultWithPostInfo { - let origin = ensure_signed(origin)?; + Migration::::ensure_migrated()?; let data_len = data.len() as u32; let salt_len = salt.len() as u32; - let mut output = Self::internal_instantiate( - origin, + let common = CommonInput { + origin: Origin::from_runtime_origin(origin)?, value, - gas_limit, - storage_deposit_limit.map(Into::into), - Code::Existing(code_hash), data, - salt, - None, - ); + gas_limit, + storage_deposit_limit: storage_deposit_limit.map(Into::into), + debug_message: None, + }; + let mut output = InstantiateInput:: { code: WasmCode::CodeHash(code_hash), salt } + .run_guarded(common); if let Ok(retval) = &output.result { if retval.1.did_revert() { output.result = Err(>::ContractReverted.into()); @@ -781,6 +776,33 @@ pub mod pallet { T::WeightInfo::instantiate(data_len, salt_len), ) } + + /// When a migration is in progress, this dispatchable can be used to run migration steps. + /// Calls that contribute to advancing the migration have their fees waived, as it's helpful + /// for the chain. Note that while the migration is in progress, the pallet will also + /// leverage the `on_idle` hooks to run migration steps. + #[pallet::call_index(9)] + #[pallet::weight(T::WeightInfo::migrate().saturating_add(*weight_limit))] + pub fn migrate(origin: OriginFor, weight_limit: Weight) -> DispatchResultWithPostInfo { + use migration::MigrateResult::*; + ensure_signed(origin)?; + + let weight_limit = weight_limit.saturating_add(T::WeightInfo::migrate()); + let (result, weight) = Migration::::migrate(weight_limit); + + match result { + Completed => + Ok(PostDispatchInfo { actual_weight: Some(weight), pays_fee: Pays::No }), + InProgress { steps_done, .. } if steps_done > 0 => + Ok(PostDispatchInfo { actual_weight: Some(weight), pays_fee: Pays::No }), + InProgress { .. } => + Ok(PostDispatchInfo { actual_weight: Some(weight), pays_fee: Pays::Yes }), + NoMigrationInProgress | NoMigrationPerformed => { + let err: DispatchError = >::NoMigrationPerformed.into(); + Err(err.with_weight(T::WeightInfo::migrate())) + }, + } + } } #[pallet::event] @@ -834,8 +856,8 @@ pub mod pallet { /// calls. This is because on failure all storage changes including events are /// rolled back. Called { - /// The account that called the `contract`. - caller: T::AccountId, + /// The caller of the `contract`. + caller: Origin, /// The contract that was called. contract: T::AccountId, }, @@ -858,8 +880,8 @@ pub mod pallet { #[pallet::error] pub enum Error { - /// A new schedule must have a greater version than the current one. - InvalidScheduleVersion, + /// Invalid schedule supplied, e.g. with zero weight of a basic operation. + InvalidSchedule, /// Invalid combination of flags supplied to `seal_call` or `seal_delegate_call`. InvalidCallFlags, /// The executed contract exhausted its gas limit. @@ -879,6 +901,8 @@ pub mod pallet { CodeTooLarge, /// No code could be found at the supplied code hash. CodeNotFound, + /// No code info could be found at the supplied code hash. + CodeInfoNotFound, /// A buffer outside of sandbox memory was passed to a contract API function. OutOfBounds, /// Input passed to a contract API function failed to decode as expected type. @@ -900,21 +924,16 @@ pub mod pallet { /// in this error. Note that this usually shouldn't happen as deploying such contracts /// is rejected. NoChainExtension, - /// Removal of a contract failed because the deletion queue is full. - /// - /// This can happen when calling `seal_terminate`. - /// The queue is filled by deleting contracts and emptied by a fixed amount each block. - /// Trying again during another block is the only way to resolve this issue. - DeletionQueueFull, /// A contract with the same AccountId already exists. DuplicateContract, /// A contract self destructed in its constructor. /// /// This can be triggered by a call to `seal_terminate`. TerminatedInConstructor, - /// The debug message specified to `seal_debug_message` does contain invalid UTF-8. - DebugMessageInvalidUTF8, /// A call tried to invoke a contract that is flagged as non-reentrant. + /// The only other cause is that a call from a contract into the runtime tried to call back + /// into `pallet-contracts`. This would make the whole pallet reentrant with regard to + /// contract code execution which is not supported. ReentranceDenied, /// Origin doesn't have enough balance to pay the required storage deposits. StorageDepositNotEnoughFunds, @@ -927,10 +946,10 @@ pub mod pallet { /// or via RPC an `Ok` will be returned. In this case the caller needs to inspect the flags /// to determine whether a reversion has taken place. ContractReverted, - /// The contract's code was found to be invalid during validation or instrumentation. + /// The contract's code was found to be invalid during validation. /// /// The most likely cause of this is that an API was used which is not supported by the - /// node. This hapens if an older node is used with a new version of ink!. Try updating + /// node. This happens if an older node is used with a new version of ink!. Try updating /// your node to the newest available version. /// /// A more detailed error can be found on the node console if debug messages are enabled @@ -938,20 +957,19 @@ pub mod pallet { CodeRejected, /// An indetermistic code was used in a context where this is not permitted. Indeterministic, + /// A pending migration needs to complete before the extrinsic can be called. + MigrationInProgress, + /// Migrate dispatch call was attempted but no migration was performed. + NoMigrationPerformed, } - /// A mapping from an original code hash to the original code, untouched by instrumentation. + /// A mapping from a contract's code hash to its code. #[pallet::storage] pub(crate) type PristineCode = StorageMap<_, Identity, CodeHash, CodeVec>; - /// A mapping between an original code hash and instrumented wasm code, ready for execution. + /// A mapping from a contract's code hash to its code info. #[pallet::storage] - pub(crate) type CodeStorage = - StorageMap<_, Identity, CodeHash, PrefabWasmModule>; - - /// A mapping between an original code hash and its owner information. - #[pallet::storage] - pub(crate) type OwnerInfoOf = StorageMap<_, Identity, CodeHash, OwnerInfo>; + pub(crate) type CodeInfoOf = StorageMap<_, Identity, CodeHash, CodeInfo>; /// This is a **monotonic** counter incremented on contract instantiation. /// @@ -988,17 +1006,108 @@ pub mod pallet { /// Evicted contracts that await child trie deletion. /// /// Child trie deletion is a heavy operation depending on the amount of storage items - /// stored in said trie. Therefore this operation is performed lazily in `on_initialize`. + /// stored in said trie. Therefore this operation is performed lazily in `on_idle`. #[pallet::storage] - pub(crate) type DeletionQueue = - StorageValue<_, BoundedVec, ValueQuery>; + pub(crate) type DeletionQueue = StorageMap<_, Twox64Concat, u32, TrieId>; + + /// A pair of monotonic counters used to track the latest contract marked for deletion + /// and the latest deleted contract in queue. + #[pallet::storage] + pub(crate) type DeletionQueueCounter = + StorageValue<_, DeletionQueueManager, ValueQuery>; + + /// A migration can span across multiple blocks. This storage defines a cursor to track the + /// progress of the migration, enabling us to resume from the last completed position. + #[pallet::storage] + pub(crate) type MigrationInProgress = + StorageValue<_, migration::Cursor, OptionQuery>; +} + +/// The type of origins supported by the contracts pallet. +#[derive(Clone, Encode, Decode, PartialEq, TypeInfo, RuntimeDebugNoBound)] +pub enum Origin { + Root, + Signed(T::AccountId), +} + +impl Origin { + /// Creates a new Signed Caller from an AccountId. + pub fn from_account_id(account_id: T::AccountId) -> Self { + Origin::Signed(account_id) + } + /// Creates a new Origin from a `RuntimeOrigin`. + pub fn from_runtime_origin(o: OriginFor) -> Result { + match o.into() { + Ok(RawOrigin::Root) => Ok(Self::Root), + Ok(RawOrigin::Signed(t)) => Ok(Self::Signed(t)), + _ => Err(BadOrigin.into()), + } + } + /// Returns the AccountId of a Signed Origin or an error if the origin is Root. + pub fn account_id(&self) -> Result<&T::AccountId, DispatchError> { + match self { + Origin::Signed(id) => Ok(id), + Origin::Root => Err(DispatchError::RootNotAllowed), + } + } +} + +/// Context of a contract invocation. +struct CommonInput<'a, T: Config> { + origin: Origin, + value: BalanceOf, + data: Vec, + gas_limit: Weight, + storage_deposit_limit: Option>, + debug_message: Option<&'a mut DebugBufferVec>, } -/// Return type of the private [`Pallet::internal_call`] function. -type InternalCallOutput = InternalOutput; +/// Input specific to a call into contract. +struct CallInput { + dest: T::AccountId, + determinism: Determinism, +} -/// Return type of the private [`Pallet::internal_instantiate`] function. -type InternalInstantiateOutput = InternalOutput, ExecReturnValue)>; +/// Reference to an existing code hash or a new wasm module. +enum WasmCode { + Wasm(WasmBlob), + CodeHash(CodeHash), +} + +/// Input specific to a contract instantiation invocation. +struct InstantiateInput { + code: WasmCode, + salt: Vec, +} + +/// Determines whether events should be collected during execution. +#[derive(PartialEq)] +pub enum CollectEvents { + /// Collect events. + /// + /// # Note + /// + /// Events should only be collected when called off-chain, as this would otherwise + /// collect all the Events emitted in the block so far and put them into the PoV. + /// + /// **Never** use this mode for on-chain execution. + UnsafeCollect, + /// Skip event collection. + Skip, +} + +/// Determines whether debug messages will be collected. +#[derive(PartialEq)] +pub enum DebugInfo { + /// Collect debug messages. + /// # Note + /// + /// This should only ever be set to `UnsafeDebug` when executing as an RPC because + /// it adds allocations and could be abused to drive the runtime into an OOM panic. + UnsafeDebug, + /// Skip collection of debug messages. + Skip, +} /// Return type of private helper functions. struct InternalOutput { @@ -1010,6 +1119,190 @@ struct InternalOutput { result: Result, } +/// Helper trait to wrap contract execution entry points into a single function +/// [`Invokable::run_guarded`]. +trait Invokable: Sized { + /// What is returned as a result of a successful invocation. + type Output; + + /// Single entry point to contract execution. + /// Downstream execution flow is branched by implementations of [`Invokable`] trait: + /// + /// - [`InstantiateInput::run`] runs contract instantiation, + /// - [`CallInput::run`] runs contract call. + /// + /// We enforce a re-entrancy guard here by initializing and checking a boolean flag through a + /// global reference. + fn run_guarded(self, common: CommonInput) -> InternalOutput { + // Set up a global reference to the boolean flag used for the re-entrancy guard. + environmental!(executing_contract: bool); + + let gas_limit = common.gas_limit; + + // Check whether the origin is allowed here. The logic of the access rules + // is in the `ensure_origin`, this could vary for different implementations of this + // trait. For example, some actions might not allow Root origin as they could require an + // AccountId associated with the origin. + if let Err(e) = self.ensure_origin(common.origin.clone()) { + return InternalOutput { + gas_meter: GasMeter::new(gas_limit), + storage_deposit: Default::default(), + result: Err(ExecError { error: e.into(), origin: ErrorOrigin::Caller }), + } + } + + executing_contract::using_once(&mut false, || { + executing_contract::with(|f| { + // Fail if already entered contract execution + if *f { + return Err(()) + } + // We are entering contract execution + *f = true; + Ok(()) + }) + .expect("Returns `Ok` if called within `using_once`. It is syntactically obvious that this is the case; qed") + .map_or_else( + |_| InternalOutput { + gas_meter: GasMeter::new(gas_limit), + storage_deposit: Default::default(), + result: Err(ExecError { + error: >::ReentranceDenied.into(), + origin: ErrorOrigin::Caller, + }), + }, + // Enter contract call. + |_| self.run(common, GasMeter::new(gas_limit)), + ) + }) + } + + /// Method that does the actual call to a contract. It can be either a call to a deployed + /// contract or a instantiation of a new one. + /// + /// Called by dispatchables and public functions through the [`Invokable::run_guarded`]. + fn run(self, common: CommonInput, gas_meter: GasMeter) + -> InternalOutput; + + /// This method ensures that the given `origin` is allowed to invoke the current `Invokable`. + /// + /// Called by dispatchables and public functions through the [`Invokable::run_guarded`]. + fn ensure_origin(&self, origin: Origin) -> Result<(), DispatchError>; +} + +impl Invokable for CallInput { + type Output = ExecReturnValue; + + fn run( + self, + common: CommonInput, + mut gas_meter: GasMeter, + ) -> InternalOutput { + let CallInput { dest, determinism } = self; + let CommonInput { origin, value, data, debug_message, .. } = common; + let mut storage_meter = + match StorageMeter::new(&origin, common.storage_deposit_limit, common.value) { + Ok(meter) => meter, + Err(err) => + return InternalOutput { + result: Err(err.into()), + gas_meter, + storage_deposit: Default::default(), + }, + }; + let schedule = T::Schedule::get(); + let result = ExecStack::>::run_call( + origin.clone(), + dest.clone(), + &mut gas_meter, + &mut storage_meter, + &schedule, + value, + data.clone(), + debug_message, + determinism, + ); + + match storage_meter.try_into_deposit(&origin) { + Ok(storage_deposit) => InternalOutput { gas_meter, storage_deposit, result }, + Err(err) => InternalOutput { + gas_meter, + storage_deposit: Default::default(), + result: Err(err.into()), + }, + } + } + + fn ensure_origin(&self, _origin: Origin) -> Result<(), DispatchError> { + Ok(()) + } +} + +impl Invokable for InstantiateInput { + type Output = (AccountIdOf, ExecReturnValue); + + fn run( + self, + common: CommonInput, + mut gas_meter: GasMeter, + ) -> InternalOutput { + let mut storage_deposit = Default::default(); + let try_exec = || { + let schedule = T::Schedule::get(); + let InstantiateInput { salt, .. } = self; + let CommonInput { origin: contract_origin, .. } = common; + let origin = contract_origin.account_id()?; + + let executable = match self.code { + WasmCode::Wasm(module) => module, + WasmCode::CodeHash(code_hash) => WasmBlob::from_storage(code_hash, &mut gas_meter)?, + }; + + let contract_origin = Origin::from_account_id(origin.clone()); + let mut storage_meter = + StorageMeter::new(&contract_origin, common.storage_deposit_limit, common.value)?; + let CommonInput { value, data, debug_message, .. } = common; + let result = ExecStack::>::run_instantiate( + origin.clone(), + executable, + &mut gas_meter, + &mut storage_meter, + &schedule, + value, + data.clone(), + &salt, + debug_message, + ); + + storage_deposit = storage_meter.try_into_deposit(&contract_origin)?; + result + }; + InternalOutput { result: try_exec(), gas_meter, storage_deposit } + } + + fn ensure_origin(&self, origin: Origin) -> Result<(), DispatchError> { + match origin { + Origin::Signed(_) => Ok(()), + Origin::Root => Err(DispatchError::RootNotAllowed), + } + } +} + +macro_rules! ensure_no_migration_in_progress { + () => { + if Migration::::in_progress() { + return ContractResult { + gas_consumed: Zero::zero(), + gas_required: Zero::zero(), + storage_deposit: Default::default(), + debug_message: Vec::new(), + result: Err(Error::::MigrationInProgress.into()), + events: None, + } + } + }; +} + impl Pallet { /// Perform a call to a specified contract. /// @@ -1018,11 +1311,11 @@ impl Pallet { /// /// # Note /// - /// `debug` should only ever be set to `true` when executing as an RPC because - /// it adds allocations and could be abused to drive the runtime into an OOM panic. - /// If set to `true` it returns additional human readable debugging information. + /// If `debug` is set to `DebugInfo::UnsafeDebug` it returns additional human readable debugging + /// information. /// - /// It returns the execution result and the amount of used weight. + /// If `collect_events` is set to `CollectEvents::UnsafeCollect` it collects all the Events + /// emitted in the block so far and the ones emitted during the execution of this contract. pub fn bare_call( origin: T::AccountId, dest: T::AccountId, @@ -1030,26 +1323,40 @@ impl Pallet { gas_limit: Weight, storage_deposit_limit: Option>, data: Vec, - debug: bool, + debug: DebugInfo, + collect_events: CollectEvents, determinism: Determinism, - ) -> ContractExecResult> { - let mut debug_message = if debug { Some(DebugBufferVec::::default()) } else { None }; - let output = Self::internal_call( + ) -> ContractExecResult, EventRecordOf> { + ensure_no_migration_in_progress!(); + + let mut debug_message = if matches!(debug, DebugInfo::UnsafeDebug) { + Some(DebugBufferVec::::default()) + } else { + None + }; + let origin = Origin::from_account_id(origin); + let common = CommonInput { origin, - dest, value, + data, gas_limit, storage_deposit_limit, - data, - debug_message.as_mut(), - determinism, - ); + debug_message: debug_message.as_mut(), + }; + let output = CallInput:: { dest, determinism }.run_guarded(common); + let events = if matches!(collect_events, CollectEvents::UnsafeCollect) { + Some(System::::read_events_no_consensus().map(|e| *e).collect()) + } else { + None + }; + ContractExecResult { result: output.result.map_err(|r| r.error), gas_consumed: output.gas_meter.gas_consumed(), gas_required: output.gas_meter.gas_required(), storage_deposit: output.storage_deposit, debug_message: debug_message.unwrap_or_default().to_vec(), + events, } } @@ -1062,30 +1369,78 @@ impl Pallet { /// /// # Note /// - /// `debug` should only ever be set to `true` when executing as an RPC because - /// it adds allocations and could be abused to drive the runtime into an OOM panic. - /// If set to `true` it returns additional human readable debugging information. + /// If `debug` is set to `DebugInfo::UnsafeDebug` it returns additional human readable debugging + /// information. + /// + /// If `collect_events` is set to `CollectEvents::UnsafeCollect` it collects all the Events + /// emitted in the block so far. pub fn bare_instantiate( origin: T::AccountId, value: BalanceOf, gas_limit: Weight, - storage_deposit_limit: Option>, + mut storage_deposit_limit: Option>, code: Code>, data: Vec, salt: Vec, - debug: bool, - ) -> ContractInstantiateResult> { - let mut debug_message = if debug { Some(DebugBufferVec::::default()) } else { None }; - let output = Self::internal_instantiate( - origin, + debug: DebugInfo, + collect_events: CollectEvents, + ) -> ContractInstantiateResult, EventRecordOf> { + ensure_no_migration_in_progress!(); + + let mut debug_message = if debug == DebugInfo::UnsafeDebug { + Some(DebugBufferVec::::default()) + } else { + None + }; + // collect events if CollectEvents is UnsafeCollect + let events = || { + if collect_events == CollectEvents::UnsafeCollect { + Some(System::::read_events_no_consensus().map(|e| *e).collect()) + } else { + None + } + }; + + let (code, upload_deposit): (WasmCode, BalanceOf) = match code { + Code::Upload(code) => { + let result = Self::try_upload_code( + origin.clone(), + code, + storage_deposit_limit.map(Into::into), + Determinism::Enforced, + debug_message.as_mut(), + ); + + let (module, deposit) = match result { + Ok(result) => result, + Err(error) => + return ContractResult { + gas_consumed: Zero::zero(), + gas_required: Zero::zero(), + storage_deposit: Default::default(), + debug_message: debug_message.unwrap_or(Default::default()).into(), + result: Err(error), + events: events(), + }, + }; + + storage_deposit_limit = + storage_deposit_limit.map(|l| l.saturating_sub(deposit.into())); + (WasmCode::Wasm(module), deposit) + }, + Code::Existing(hash) => (WasmCode::CodeHash(hash), Default::default()), + }; + + let common = CommonInput { + origin: Origin::from_account_id(origin), value, + data, gas_limit, storage_deposit_limit, - code, - data, - salt, - debug_message.as_mut(), - ); + debug_message: debug_message.as_mut(), + }; + + let output = InstantiateInput:: { code, salt }.run_guarded(common); ContractInstantiateResult { result: output .result @@ -1093,8 +1448,11 @@ impl Pallet { .map_err(|e| e.error), gas_consumed: output.gas_meter.gas_consumed(), gas_required: output.gas_meter.gas_required(), - storage_deposit: output.storage_deposit, + storage_deposit: output + .storage_deposit + .saturating_add(&StorageDeposit::Charge(upload_deposit)), debug_message: debug_message.unwrap_or_default().to_vec(), + events: events(), } } @@ -1108,32 +1466,46 @@ impl Pallet { storage_deposit_limit: Option>, determinism: Determinism, ) -> CodeUploadResult, BalanceOf> { + Migration::::ensure_migrated()?; + let (module, deposit) = + Self::try_upload_code(origin, code, storage_deposit_limit, determinism, None)?; + Ok(CodeUploadReturnValue { code_hash: *module.code_hash(), deposit }) + } + + /// Uploads new code and returns the Wasm blob and deposit amount collected. + fn try_upload_code( + origin: T::AccountId, + code: Vec, + storage_deposit_limit: Option>, + determinism: Determinism, + mut debug_message: Option<&mut DebugBufferVec>, + ) -> Result<(WasmBlob, BalanceOf), DispatchError> { let schedule = T::Schedule::get(); - let module = PrefabWasmModule::from_code( - code, - &schedule, - origin, - determinism, - TryInstantiate::Instantiate, - ) - .map_err(|(err, _)| err)?; - let deposit = module.open_deposit(); + let mut module = + WasmBlob::from_code(code, &schedule, origin, determinism).map_err(|(err, msg)| { + debug_message.as_mut().map(|d| d.try_extend(msg.bytes())); + err + })?; + let deposit = module.store_code()?; if let Some(storage_deposit_limit) = storage_deposit_limit { ensure!(storage_deposit_limit >= deposit, >::StorageDepositLimitExhausted); } - let result = CodeUploadReturnValue { code_hash: *module.code_hash(), deposit }; - module.store()?; - Ok(result) + + Ok((module, deposit)) } /// Query storage of a specified contract under a specified key. pub fn get_storage(address: T::AccountId, key: Vec) -> GetStorageResult { + if Migration::::in_progress() { + return Err(ContractAccessError::MigrationInProgress) + } let contract_info = ContractInfoOf::::get(&address).ok_or(ContractAccessError::DoesntExist)?; - let maybe_value = Storage::::read( - &contract_info.trie_id, - &StorageKey::::try_from(key).map_err(|_| ContractAccessError::KeyDecodingFailed)?, + let maybe_value = contract_info.read( + &Key::::try_from_var(key) + .map_err(|_| ContractAccessError::KeyDecodingFailed)? + .into(), ); Ok(maybe_value) } @@ -1148,141 +1520,25 @@ impl Pallet { input_data: &[u8], salt: &[u8], ) -> T::AccountId { - T::AddressGenerator::generate_address(deploying_address, code_hash, input_data, salt) + T::AddressGenerator::contract_address(deploying_address, code_hash, input_data, salt) } /// Returns the code hash of the contract specified by `account` ID. pub fn code_hash(account: &AccountIdOf) -> Option> { - Storage::::code_hash(account) + ContractInfo::::load_code_hash(account) } - /// Store code for benchmarks which does not check nor instrument the code. + /// Store code for benchmarks which does not validate the code. #[cfg(feature = "runtime-benchmarks")] fn store_code_raw( code: Vec, owner: T::AccountId, ) -> frame_support::dispatch::DispatchResult { let schedule = T::Schedule::get(); - PrefabWasmModule::store_code_unchecked(code, &schedule, owner)?; + WasmBlob::::from_code_unchecked(code, &schedule, owner)?.store_code()?; Ok(()) } - /// This exists so that benchmarks can determine the weight of running an instrumentation. - #[cfg(feature = "runtime-benchmarks")] - fn reinstrument_module( - module: &mut PrefabWasmModule, - schedule: &Schedule, - ) -> frame_support::dispatch::DispatchResult { - self::wasm::reinstrument(module, schedule).map(|_| ()) - } - - /// Internal function that does the actual call. - /// - /// Called by dispatchables and public functions. - fn internal_call( - origin: T::AccountId, - dest: T::AccountId, - value: BalanceOf, - gas_limit: Weight, - storage_deposit_limit: Option>, - data: Vec, - debug_message: Option<&mut DebugBufferVec>, - determinism: Determinism, - ) -> InternalCallOutput { - let mut gas_meter = GasMeter::new(gas_limit); - let mut storage_meter = match StorageMeter::new(&origin, storage_deposit_limit, value) { - Ok(meter) => meter, - Err(err) => - return InternalCallOutput { - result: Err(err.into()), - gas_meter, - storage_deposit: Default::default(), - }, - }; - let schedule = T::Schedule::get(); - let result = ExecStack::>::run_call( - origin.clone(), - dest, - &mut gas_meter, - &mut storage_meter, - &schedule, - value, - data, - debug_message, - determinism, - ); - InternalCallOutput { - result, - gas_meter, - storage_deposit: storage_meter.into_deposit(&origin), - } - } - - /// Internal function that does the actual instantiation. - /// - /// Called by dispatchables and public functions. - fn internal_instantiate( - origin: T::AccountId, - value: BalanceOf, - gas_limit: Weight, - storage_deposit_limit: Option>, - code: Code>, - data: Vec, - salt: Vec, - mut debug_message: Option<&mut DebugBufferVec>, - ) -> InternalInstantiateOutput { - let mut storage_deposit = Default::default(); - let mut gas_meter = GasMeter::new(gas_limit); - let try_exec = || { - let schedule = T::Schedule::get(); - let (extra_deposit, executable) = match code { - Code::Upload(binary) => { - let executable = PrefabWasmModule::from_code( - binary, - &schedule, - origin.clone(), - Determinism::Deterministic, - TryInstantiate::Skip, - ) - .map_err(|(err, msg)| { - debug_message.as_mut().map(|buffer| buffer.try_extend(&mut msg.bytes())); - err - })?; - // The open deposit will be charged during execution when the - // uploaded module does not already exist. This deposit is not part of the - // storage meter because it is not transferred to the contract but - // reserved on the uploading account. - (executable.open_deposit(), executable) - }, - Code::Existing(hash) => ( - Default::default(), - PrefabWasmModule::from_storage(hash, &schedule, &mut gas_meter)?, - ), - }; - let mut storage_meter = StorageMeter::new( - &origin, - storage_deposit_limit, - value.saturating_add(extra_deposit), - )?; - let result = ExecStack::>::run_instantiate( - origin.clone(), - executable, - &mut gas_meter, - &mut storage_meter, - &schedule, - value, - data, - &salt, - debug_message, - ); - storage_deposit = storage_meter - .into_deposit(&origin) - .saturating_add(&StorageDeposit::Charge(extra_deposit)); - result - }; - InternalInstantiateOutput { result: try_exec(), gas_meter, storage_deposit } - } - /// Deposit a pallet contracts event. Handles the conversion to the overarching event type. fn deposit_event(topics: Vec, event: Event) { >::deposit_event_indexed( @@ -1301,18 +1557,19 @@ impl Pallet { /// Used by backwards compatible extrinsics. We cannot just set the proof_size weight limit to /// zero or an old `Call` will just fail with OutOfGas. fn compat_weight_limit(gas_limit: OldWeight) -> Weight { - Weight::from_parts(gas_limit.0, u64::from(T::MaxCodeLen::get()) * 2) + Weight::from_parts(gas_limit, u64::from(T::MaxCodeLen::get()) * 2) } } sp_api::decl_runtime_apis! { /// The API used to dry-run contract interactions. #[api_version(2)] - pub trait ContractsApi where + pub trait ContractsApi where AccountId: Codec, Balance: Codec, BlockNumber: Codec, Hash: Codec, + EventRecord: Codec, { /// Perform a call from a specified account to a given contract. /// @@ -1324,7 +1581,7 @@ sp_api::decl_runtime_apis! { gas_limit: Option, storage_deposit_limit: Option, input_data: Vec, - ) -> ContractExecResult; + ) -> ContractExecResult; /// Instantiate a new contract. /// @@ -1337,8 +1594,7 @@ sp_api::decl_runtime_apis! { code: Code, data: Vec, salt: Vec, - ) -> ContractInstantiateResult; - + ) -> ContractInstantiateResult; /// Upload new code without instantiating a contract from it. /// diff --git a/frame/contracts/src/migration.rs b/frame/contracts/src/migration.rs index 56d688abc730..36dc8e60cdcd 100644 --- a/frame/contracts/src/migration.rs +++ b/frame/contracts/src/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,459 +15,598 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{BalanceOf, CodeHash, Config, Pallet, TrieId, Weight}; -use codec::{Decode, Encode}; +//! Multi-block Migration framework for pallet-contracts. +//! +//! This module allows us to define a migration as a sequence of [`MigrationStep`]s that can be +//! executed across multiple blocks. +//! +//! # Usage +//! +//! A migration step is defined under `src/migration/vX.rs`, where `X` is the version number. +//! For example, `vX.rs` defines a migration from version `X - 1` to version `X`. +//! +//! ## Example: +//! +//! To configure a migration to `v11` for a runtime using `v10` of pallet-contracts on the chain, +//! you would set the `Migrations` type as follows: +//! +//! ``` +//! use pallet_contracts::migration::{v10, v11}; +//! # pub enum Runtime {}; +//! type Migrations = (v10::Migration, v11::Migration); +//! ``` +//! +//! ## Notes: +//! +//! - Migrations should always be tested with `try-runtime` before being deployed. +//! - By testing with `try-runtime` against a live network, you ensure that all migration steps work +//! and that you have included the required steps. +//! +//! ## Low Level / Implementation Details +//! +//! When a migration starts and [`OnRuntimeUpgrade::on_runtime_upgrade`] is called, instead of +//! performing the actual migration, we set a custom storage item [`MigrationInProgress`]. +//! This storage item defines a [`Cursor`] for the current migration. +//! +//! If the [`MigrationInProgress`] storage item exists, it means a migration is in progress, and its +//! value holds a cursor for the current migration step. These migration steps are executed during +//! [`Hooks::on_idle`] or when the [`Pallet::migrate`] dispatchable is +//! called. +//! +//! While the migration is in progress, all dispatchables except `migrate`, are blocked, and returns +//! a `MigrationInProgress` error. + +pub mod v09; +pub mod v10; +pub mod v11; +pub mod v12; + +use crate::{weights::WeightInfo, Config, Error, MigrationInProgress, Pallet, Weight, LOG_TARGET}; +use codec::{Codec, Decode}; use frame_support::{ codec, pallet_prelude::*, - storage::migration, - storage_alias, - traits::{Get, OnRuntimeUpgrade}, - Identity, Twox64Concat, + traits::{ConstU32, OnRuntimeUpgrade}, }; -use sp_runtime::traits::Saturating; -use sp_std::{marker::PhantomData, prelude::*}; +use sp_runtime::Saturating; +use sp_std::marker::PhantomData; -/// Performs all necessary migrations based on `StorageVersion`. -pub struct Migration(PhantomData); -impl OnRuntimeUpgrade for Migration { - fn on_runtime_upgrade() -> Weight { - let version = >::on_chain_storage_version(); - let mut weight = Weight::zero(); +#[cfg(feature = "try-runtime")] +use sp_std::prelude::*; - if version < 4 { - v4::migrate::(&mut weight); - } +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; - if version < 5 { - v5::migrate::(&mut weight); - } +const PROOF_ENCODE: &str = "Tuple::max_encoded_len() < Cursor::max_encoded_len()` is verified in `Self::integrity_test()`; qed"; +const PROOF_DECODE: &str = + "We encode to the same type in this trait only. No other code touches this item; qed"; - if version < 6 { - v6::migrate::(&mut weight); - } +fn invalid_version(version: StorageVersion) -> ! { + panic!("Required migration {version:?} not supported by this runtime. This is a bug."); +} - if version < 7 { - v7::migrate::(&mut weight); - } +/// The cursor used to encode the position (usually the last iterated key) of the current migration +/// step. +pub type Cursor = BoundedVec>; - if version < 8 { - v8::migrate::(&mut weight); - } +/// IsFinished describes whether a migration is finished or not. +pub enum IsFinished { + Yes, + No, +} - if version < 9 { - v9::migrate::(&mut weight); +/// A trait that allows to migrate storage from one version to another. +/// +/// The migration is done in steps. The migration is finished when +/// `step()` returns `IsFinished::Yes`. +pub trait MigrationStep: Codec + MaxEncodedLen + Default { + /// Returns the version of the migration. + const VERSION: u16; + + /// Returns the maximum weight that can be consumed in a single step. + fn max_step_weight() -> Weight; + + /// Process one step of the migration. + /// + /// Returns whether the migration is finished and the weight consumed. + fn step(&mut self) -> (IsFinished, Weight); + + /// Verify that the migration step fits into `Cursor`, and that `max_step_weight` is not greater + /// than `max_block_weight`. + fn integrity_test(max_block_weight: Weight) { + if Self::max_step_weight().any_gt(max_block_weight) { + panic!( + "Invalid max_step_weight for Migration {}. Value should be lower than {}", + Self::VERSION, + max_block_weight + ); } - StorageVersion::new(9).put::>(); - weight.saturating_accrue(T::DbWeight::get().writes(1)); - - weight + let len = ::max_encoded_len(); + let max = Cursor::bound(); + if len > max { + panic!( + "Migration {} has size {} which is bigger than the maximum of {}", + Self::VERSION, + len, + max, + ); + } } + /// Execute some pre-checks prior to running the first step of this migration. #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - let version = >::on_chain_storage_version(); - - if version == 7 { - v8::pre_upgrade::()?; - } - - Ok(version.encode()) + fn pre_upgrade_step() -> Result, TryRuntimeError> { + Ok(Vec::new()) } + /// Execute some post-checks after running the last step of this migration. #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), &'static str> { - let version = Decode::decode(&mut state.as_ref()).map_err(|_| "Cannot decode version")?; - post_checks::post_upgrade::(version) + fn post_upgrade_step(_state: Vec) -> Result<(), TryRuntimeError> { + Ok(()) } } -/// V4: `Schedule` is changed to be a config item rather than an in-storage value. -mod v4 { - use super::*; +/// A noop migration that can be used when there is no migration to be done for a given version. +#[doc(hidden)] +#[derive(frame_support::DefaultNoBound, Encode, Decode, MaxEncodedLen)] +pub struct NoopMigration; - pub fn migrate(weight: &mut Weight) { - #[allow(deprecated)] - migration::remove_storage_prefix(>::name().as_bytes(), b"CurrentSchedule", b""); - weight.saturating_accrue(T::DbWeight::get().writes(1)); +impl MigrationStep for NoopMigration { + const VERSION: u16 = N; + fn max_step_weight() -> Weight { + Weight::zero() + } + fn step(&mut self) -> (IsFinished, Weight) { + log::debug!(target: LOG_TARGET, "Noop migration for version {}", N); + (IsFinished::Yes, Weight::zero()) } } -/// V5: State rent is removed which obsoletes some fields in `ContractInfo`. -mod v5 { - use super::*; +mod private { + use crate::migration::MigrationStep; + pub trait Sealed {} + #[impl_trait_for_tuples::impl_for_tuples(10)] + #[tuple_types_custom_trait_bound(MigrationStep)] + impl Sealed for Tuple {} +} - type AliveContractInfo = - RawAliveContractInfo, BalanceOf, ::BlockNumber>; - type TombstoneContractInfo = RawTombstoneContractInfo< - ::Hash, - ::Hashing, - >; - - #[derive(Decode)] - enum OldContractInfo { - Alive(AliveContractInfo), - Tombstone(TombstoneContractInfo), - } +/// Defines a sequence of migrations. +/// +/// The sequence must be defined by a tuple of migrations, each of which must implement the +/// `MigrationStep` trait. Migrations must be ordered by their versions with no gaps. +pub trait MigrateSequence: private::Sealed { + /// Returns the range of versions that this migrations sequence can handle. + /// Migrations must be ordered by their versions with no gaps. + /// + /// The following code will fail to compile: + /// + /// ```compile_fail + /// # use pallet_contracts::{NoopMigration, MigrateSequence}; + /// let _ = <(NoopMigration<1>, NoopMigration<3>)>::VERSION_RANGE; + /// ``` + /// The following code will compile: + /// ``` + /// # use pallet_contracts::{NoopMigration, MigrateSequence}; + /// let _ = <(NoopMigration<1>, NoopMigration<2>)>::VERSION_RANGE; + /// ``` + const VERSION_RANGE: (u16, u16); + + /// Returns the default cursor for the given version. + fn new(version: StorageVersion) -> Cursor; - #[derive(Decode)] - struct RawAliveContractInfo { - trie_id: TrieId, - _storage_size: u32, - _pair_count: u32, - code_hash: CodeHash, - _rent_allowance: Balance, - _rent_paid: Balance, - _deduct_block: BlockNumber, - _last_write: Option, - _reserved: Option<()>, + #[cfg(feature = "try-runtime")] + fn pre_upgrade_step(_version: StorageVersion) -> Result, TryRuntimeError> { + Ok(Vec::new()) } - #[derive(Decode)] - struct RawTombstoneContractInfo(H, PhantomData); - - #[derive(Decode)] - struct OldDeletedContract { - _pair_count: u32, - trie_id: TrieId, + #[cfg(feature = "try-runtime")] + fn post_upgrade_step(_version: StorageVersion, _state: Vec) -> Result<(), TryRuntimeError> { + Ok(()) } - pub type ContractInfo = RawContractInfo>; + /// Execute the migration step until the weight limit is reached. + fn steps(version: StorageVersion, cursor: &[u8], weight_left: &mut Weight) -> StepResult; - #[derive(Encode, Decode)] - pub struct RawContractInfo { - pub trie_id: TrieId, - pub code_hash: CodeHash, - pub _reserved: Option<()>, - } + /// Verify that the migration step fits into `Cursor`, and that `max_step_weight` is not greater + /// than `max_block_weight`. + fn integrity_test(max_block_weight: Weight); - #[derive(Encode, Decode)] - struct DeletedContract { - trie_id: TrieId, + /// Returns whether migrating from `in_storage` to `target` is supported. + /// + /// A migration is supported if `VERSION_RANGE` is (in_storage + 1, target). + fn is_upgrade_supported(in_storage: StorageVersion, target: StorageVersion) -> bool { + let (low, high) = Self::VERSION_RANGE; + target == high && in_storage + 1 == low } +} - #[storage_alias] - type ContractInfoOf = StorageMap< - Pallet, - Twox64Concat, - ::AccountId, - ContractInfo, - >; - - #[storage_alias] - type DeletionQueue = StorageValue, Vec>; - - pub fn migrate(weight: &mut Weight) { - >::translate(|_key, old: OldContractInfo| { - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - match old { - OldContractInfo::Alive(old) => Some(ContractInfo:: { - trie_id: old.trie_id, - code_hash: old.code_hash, - _reserved: old._reserved, - }), - OldContractInfo::Tombstone(_) => None, +/// Performs all necessary migrations based on `StorageVersion`. +/// +/// If `TEST_ALL_STEPS == true` and `try-runtime` is enabled, this will run all the migrations +/// inside `on_runtime_upgrade`. This should be set to false in tests that want to ensure the step +/// by step migration works. +pub struct Migration(PhantomData); + +#[cfg(feature = "try-runtime")] +impl Migration { + fn run_all_steps() -> Result<(), TryRuntimeError> { + let mut weight = Weight::zero(); + let name = >::name(); + loop { + let in_progress_version = >::on_chain_storage_version() + 1; + let state = T::Migrations::pre_upgrade_step(in_progress_version)?; + let (status, w) = Self::migrate(Weight::MAX); + weight.saturating_accrue(w); + log::info!( + target: LOG_TARGET, + "{name}: Migration step {:?} weight = {}", + in_progress_version, + weight + ); + T::Migrations::post_upgrade_step(in_progress_version, state)?; + if matches!(status, MigrateResult::Completed) { + break } - }); + } - DeletionQueue::::translate(|old: Option>| { - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - old.map(|old| old.into_iter().map(|o| DeletedContract { trie_id: o.trie_id }).collect()) - }) - .ok(); + let name = >::name(); + log::info!(target: LOG_TARGET, "{name}: Migration steps weight = {}", weight); + Ok(()) } } -/// V6: Added storage deposits -mod v6 { - use super::*; +impl OnRuntimeUpgrade for Migration { + fn on_runtime_upgrade() -> Weight { + let name = >::name(); + let latest_version = >::current_storage_version(); + let storage_version = >::on_chain_storage_version(); + + if storage_version == latest_version { + log::warn!( + target: LOG_TARGET, + "{name}: No Migration performed storage_version = latest_version = {:?}", + &storage_version + ); + return T::WeightInfo::on_runtime_upgrade_noop() + } - #[derive(Encode, Decode)] - struct OldPrefabWasmModule { - #[codec(compact)] - instruction_weights_version: u32, - #[codec(compact)] - initial: u32, - #[codec(compact)] - maximum: u32, - #[codec(compact)] - refcount: u64, - _reserved: Option<()>, - code: Vec, - original_code_len: u32, - } + // In case a migration is already in progress we create the next migration + // (if any) right when the current one finishes. + if Self::in_progress() { + log::warn!( + target: LOG_TARGET, + "{name}: Migration already in progress {:?}", + &storage_version + ); - #[derive(Encode, Decode)] - pub struct PrefabWasmModule { - #[codec(compact)] - pub instruction_weights_version: u32, - #[codec(compact)] - pub initial: u32, - #[codec(compact)] - pub maximum: u32, - pub code: Vec, - } + return T::WeightInfo::on_runtime_upgrade_in_progress() + } - use v5::ContractInfo as OldContractInfo; + log::info!( + target: LOG_TARGET, + "{name}: Upgrading storage from {storage_version:?} to {latest_version:?}.", + ); - #[derive(Encode, Decode)] - pub struct RawContractInfo { - pub trie_id: TrieId, - pub code_hash: CodeHash, - pub storage_deposit: Balance, - } + let cursor = T::Migrations::new(storage_version + 1); + MigrationInProgress::::set(Some(cursor)); - #[derive(Encode, Decode)] - pub struct OwnerInfo { - owner: T::AccountId, - #[codec(compact)] - deposit: BalanceOf, - #[codec(compact)] - refcount: u64, - } + #[cfg(feature = "try-runtime")] + if TEST_ALL_STEPS { + Self::run_all_steps().unwrap(); + } - pub type ContractInfo = RawContractInfo, BalanceOf>; - - #[storage_alias] - type ContractInfoOf = StorageMap< - Pallet, - Twox64Concat, - ::AccountId, - ContractInfo, - >; - - #[storage_alias] - type CodeStorage = StorageMap, Identity, CodeHash, PrefabWasmModule>; - - #[storage_alias] - type OwnerInfoOf = StorageMap, Identity, CodeHash, OwnerInfo>; - - pub fn migrate(weight: &mut Weight) { - >::translate(|_key, old: OldContractInfo| { - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - Some(ContractInfo:: { - trie_id: old.trie_id, - code_hash: old.code_hash, - storage_deposit: Default::default(), - }) - }); + T::WeightInfo::on_runtime_upgrade() + } - let nobody = T::AccountId::decode(&mut sp_runtime::traits::TrailingZeroInput::zeroes()) - .expect("Infinite input; no dead input space; qed"); - - >::translate(|key, old: OldPrefabWasmModule| { - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)); - >::insert( - key, - OwnerInfo { - refcount: old.refcount, - owner: nobody.clone(), - deposit: Default::default(), - }, - ); - Some(PrefabWasmModule { - instruction_weights_version: old.instruction_weights_version, - initial: old.initial, - maximum: old.maximum, - code: old.code, - }) - }); + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + // We can't really do much here as our migrations do not happen during the runtime upgrade. + // Instead, we call the migrations `pre_upgrade` and `post_upgrade` hooks when we iterate + // over our migrations. + let storage_version = >::on_chain_storage_version(); + let target_version = >::current_storage_version(); + + ensure!( + storage_version != target_version, + "No upgrade: Please remove this migration from your runtime upgrade configuration." + ); + + log::debug!( + target: LOG_TARGET, + "Requested migration of {} from {:?}(on-chain storage version) to {:?}(current storage version)", + >::name(), storage_version, target_version + ); + + ensure!( + T::Migrations::is_upgrade_supported(storage_version, target_version), + "Unsupported upgrade: VERSION_RANGE should be (on-chain storage version + 1, current storage version)" + ); + Ok(Default::default()) } } -/// Rename `AccountCounter` to `Nonce`. -mod v7 { - use super::*; - - pub fn migrate(weight: &mut Weight) { - #[storage_alias] - type AccountCounter = StorageValue, u64, ValueQuery>; - #[storage_alias] - type Nonce = StorageValue, u64, ValueQuery>; +/// The result of running the migration. +#[derive(Debug, PartialEq)] +pub enum MigrateResult { + /// No migration was performed + NoMigrationPerformed, + /// No migration currently in progress + NoMigrationInProgress, + /// A migration is in progress + InProgress { steps_done: u32 }, + /// All migrations are completed + Completed, +} - Nonce::::set(AccountCounter::::take()); - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 2)) - } +/// The result of running a migration step. +#[derive(Debug, PartialEq)] +pub enum StepResult { + InProgress { cursor: Cursor, steps_done: u32 }, + Completed { steps_done: u32 }, } -/// Update `ContractInfo` with new fields that track storage deposits. -mod v8 { - use super::*; - use sp_io::default_child_storage as child; - use v6::ContractInfo as OldContractInfo; - - #[derive(Encode, Decode)] - pub struct ContractInfo { - pub trie_id: TrieId, - pub code_hash: CodeHash, - pub storage_bytes: u32, - pub storage_items: u32, - pub storage_byte_deposit: BalanceOf, - pub storage_item_deposit: BalanceOf, - pub storage_base_deposit: BalanceOf, +impl Migration { + /// Verify that each migration's step of the [`Config::Migrations`] sequence fits into + /// `Cursor`. + pub(crate) fn integrity_test() { + let max_weight = ::BlockWeights::get().max_block; + T::Migrations::integrity_test(max_weight) } - #[storage_alias] - type ContractInfoOf = - StorageMap, Twox64Concat, ::AccountId, V>; - - pub fn migrate(weight: &mut Weight) { - >>::translate_values(|old: OldContractInfo| { - // Count storage items of this contract - let mut storage_bytes = 0u32; - let mut storage_items = 0u32; - let mut key = Vec::new(); - while let Some(next) = child::next_key(&old.trie_id, &key) { - key = next; - let mut val_out = []; - let len = child::read(&old.trie_id, &key, &mut val_out, 0) - .expect("The loop conditions checks for existence of the key; qed"); - storage_bytes.saturating_accrue(len); - storage_items.saturating_accrue(1); - } + /// Migrate + /// Return the weight used and whether or not a migration is in progress + pub(crate) fn migrate(weight_limit: Weight) -> (MigrateResult, Weight) { + let name = >::name(); + let mut weight_left = weight_limit; + + if weight_left.checked_reduce(T::WeightInfo::migrate()).is_none() { + return (MigrateResult::NoMigrationPerformed, Weight::zero()) + } - let storage_byte_deposit = - T::DepositPerByte::get().saturating_mul(storage_bytes.into()); - let storage_item_deposit = - T::DepositPerItem::get().saturating_mul(storage_items.into()); - let storage_base_deposit = old - .storage_deposit - .saturating_sub(storage_byte_deposit) - .saturating_sub(storage_item_deposit); - - // Reads: One read for each storage item plus the contract info itself. - // Writes: Only the new contract info. - weight.saturating_accrue( - T::DbWeight::get().reads_writes(u64::from(storage_items) + 1, 1), + MigrationInProgress::::mutate_exists(|progress| { + let Some(cursor_before) = progress.as_mut() else { + return (MigrateResult::NoMigrationInProgress, T::WeightInfo::migration_noop()) + }; + + // if a migration is running it is always upgrading to the next version + let storage_version = >::on_chain_storage_version(); + let in_progress_version = storage_version + 1; + + log::info!( + target: LOG_TARGET, + "{name}: Migrating from {:?} to {:?},", + storage_version, + in_progress_version, ); - Some(ContractInfo { - trie_id: old.trie_id, - code_hash: old.code_hash, - storage_bytes, - storage_items, - storage_byte_deposit, - storage_item_deposit, - storage_base_deposit, - }) - }); + let result = match T::Migrations::steps( + in_progress_version, + cursor_before.as_ref(), + &mut weight_left, + ) { + StepResult::InProgress { cursor, steps_done } => { + *progress = Some(cursor); + MigrateResult::InProgress { steps_done } + }, + StepResult::Completed { steps_done } => { + in_progress_version.put::>(); + if >::current_storage_version() != in_progress_version { + log::info!( + target: LOG_TARGET, + "{name}: Next migration is {:?},", + in_progress_version + 1 + ); + *progress = Some(T::Migrations::new(in_progress_version + 1)); + MigrateResult::InProgress { steps_done } + } else { + log::info!( + target: LOG_TARGET, + "{name}: All migrations done. At version {:?},", + in_progress_version + ); + *progress = None; + MigrateResult::Completed + } + }, + }; + + (result, weight_limit.saturating_sub(weight_left)) + }) } - #[cfg(feature = "try-runtime")] - pub fn pre_upgrade() -> Result<(), &'static str> { - use frame_support::traits::ReservableCurrency; - for (key, value) in ContractInfoOf::>::iter() { - let reserved = T::Currency::reserved_balance(&key); - ensure!(reserved >= value.storage_deposit, "Reserved balance out of sync."); + pub(crate) fn ensure_migrated() -> DispatchResult { + if Self::in_progress() { + Err(Error::::MigrationInProgress.into()) + } else { + Ok(()) } - Ok(()) + } + + pub(crate) fn in_progress() -> bool { + MigrationInProgress::::exists() } } -/// Update `CodeStorage` with the new `determinism` field. -mod v9 { - use super::*; - use crate::Determinism; - use v6::PrefabWasmModule as OldPrefabWasmModule; - - #[derive(Encode, Decode)] - pub struct PrefabWasmModule { - #[codec(compact)] - pub instruction_weights_version: u32, - #[codec(compact)] - pub initial: u32, - #[codec(compact)] - pub maximum: u32, - pub code: Vec, - pub determinism: Determinism, +#[impl_trait_for_tuples::impl_for_tuples(10)] +#[tuple_types_custom_trait_bound(MigrationStep)] +impl MigrateSequence for Tuple { + const VERSION_RANGE: (u16, u16) = { + let mut versions: (u16, u16) = (0, 0); + for_tuples!( + #( + match versions { + (0, 0) => { + versions = (Tuple::VERSION, Tuple::VERSION); + }, + (min_version, last_version) if Tuple::VERSION == last_version + 1 => { + versions = (min_version, Tuple::VERSION); + }, + _ => panic!("Migrations must be ordered by their versions with no gaps.") + } + )* + ); + versions + }; + + fn new(version: StorageVersion) -> Cursor { + for_tuples!( + #( + if version == Tuple::VERSION { + return Tuple::default().encode().try_into().expect(PROOF_ENCODE) + } + )* + ); + invalid_version(version) } - #[storage_alias] - type CodeStorage = StorageMap, Identity, CodeHash, PrefabWasmModule>; - - pub fn migrate(weight: &mut Weight) { - >::translate_values(|old: OldPrefabWasmModule| { - weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); - Some(PrefabWasmModule { - instruction_weights_version: old.instruction_weights_version, - initial: old.initial, - maximum: old.maximum, - code: old.code, - determinism: Determinism::Deterministic, - }) - }); + #[cfg(feature = "try-runtime")] + /// Execute the pre-checks of the step associated with this version. + fn pre_upgrade_step(version: StorageVersion) -> Result, TryRuntimeError> { + for_tuples!( + #( + if version == Tuple::VERSION { + return Tuple::pre_upgrade_step() + } + )* + ); + invalid_version(version) + } + + #[cfg(feature = "try-runtime")] + /// Execute the post-checks of the step associated with this version. + fn post_upgrade_step(version: StorageVersion, state: Vec) -> Result<(), TryRuntimeError> { + for_tuples!( + #( + if version == Tuple::VERSION { + return Tuple::post_upgrade_step(state) + } + )* + ); + invalid_version(version) + } + + fn steps(version: StorageVersion, mut cursor: &[u8], weight_left: &mut Weight) -> StepResult { + for_tuples!( + #( + if version == Tuple::VERSION { + let mut migration = ::decode(&mut cursor) + .expect(PROOF_DECODE); + let max_weight = Tuple::max_step_weight(); + let mut steps_done = 0; + while weight_left.all_gt(max_weight) { + let (finished, weight) = migration.step(); + steps_done.saturating_accrue(1); + weight_left.saturating_reduce(weight); + if matches!(finished, IsFinished::Yes) { + return StepResult::Completed{ steps_done } + } + } + return StepResult::InProgress{cursor: migration.encode().try_into().expect(PROOF_ENCODE), steps_done } + } + )* + ); + invalid_version(version) + } + + fn integrity_test(max_block_weight: Weight) { + for_tuples!( + #( + Tuple::integrity_test(max_block_weight); + )* + ); } } -// Post checks always need to be run against the latest storage version. This is why we -// do not scope them in the per version modules. They always need to be ported to the latest -// version. -#[cfg(feature = "try-runtime")] -mod post_checks { +#[cfg(test)] +mod test { use super::*; - use crate::Determinism; - use sp_io::default_child_storage as child; - use v8::ContractInfo; - use v9::PrefabWasmModule; - - #[storage_alias] - type CodeStorage = StorageMap, Identity, CodeHash, PrefabWasmModule>; + use crate::tests::{ExtBuilder, Test}; - #[storage_alias] - type ContractInfoOf = - StorageMap, Twox64Concat, ::AccountId, V>; + #[derive(Default, Encode, Decode, MaxEncodedLen)] + struct MockMigration { + // MockMigration needs `N` steps to finish + count: u16, + } - pub fn post_upgrade(old_version: StorageVersion) -> Result<(), &'static str> { - if old_version < 7 { - return Ok(()) + impl MigrationStep for MockMigration { + const VERSION: u16 = N; + fn max_step_weight() -> Weight { + Weight::from_all(1) } - - if old_version < 8 { - v8::()?; + fn step(&mut self) -> (IsFinished, Weight) { + assert!(self.count != N); + self.count += 1; + if self.count == N { + (IsFinished::Yes, Weight::from_all(1)) + } else { + (IsFinished::No, Weight::from_all(1)) + } } + } - if old_version < 9 { - v9::()?; - } + #[test] + fn version_range_works() { + let range = <(MockMigration<1>, MockMigration<2>)>::VERSION_RANGE; + assert_eq!(range, (1, 2)); + } - Ok(()) + #[test] + fn is_upgrade_supported_works() { + type Migrations = (MockMigration<9>, MockMigration<10>, MockMigration<11>); + assert!(Migrations::is_upgrade_supported(StorageVersion::new(8), StorageVersion::new(11))); + assert!(!Migrations::is_upgrade_supported(StorageVersion::new(9), StorageVersion::new(11))); + assert!(!Migrations::is_upgrade_supported(StorageVersion::new(8), StorageVersion::new(12))); } - fn v8() -> Result<(), &'static str> { - use frame_support::traits::ReservableCurrency; - for (key, value) in ContractInfoOf::>::iter() { - let reserved = T::Currency::reserved_balance(&key); - let stored = value - .storage_base_deposit - .saturating_add(value.storage_byte_deposit) - .saturating_add(value.storage_item_deposit); - ensure!(reserved >= stored, "Reserved balance out of sync."); - - let mut storage_bytes = 0u32; - let mut storage_items = 0u32; - let mut key = Vec::new(); - while let Some(next) = child::next_key(&value.trie_id, &key) { - key = next; - let mut val_out = []; - let len = child::read(&value.trie_id, &key, &mut val_out, 0) - .expect("The loop conditions checks for existence of the key; qed"); - storage_bytes.saturating_accrue(len); - storage_items.saturating_accrue(1); - } - ensure!(storage_bytes == value.storage_bytes, "Storage bytes do not match.",); - ensure!(storage_items == value.storage_items, "Storage items do not match.",); - } - Ok(()) + #[test] + fn steps_works() { + type Migrations = (MockMigration<2>, MockMigration<3>); + let version = StorageVersion::new(2); + let mut cursor = Migrations::new(version); + + let mut weight = Weight::from_all(2); + let result = Migrations::steps(version, &cursor, &mut weight); + cursor = vec![1u8, 0].try_into().unwrap(); + assert_eq!(result, StepResult::InProgress { cursor: cursor.clone(), steps_done: 1 }); + assert_eq!(weight, Weight::from_all(1)); + + let mut weight = Weight::from_all(2); + assert_eq!( + Migrations::steps(version, &cursor, &mut weight), + StepResult::Completed { steps_done: 1 } + ); } - fn v9() -> Result<(), &'static str> { - for value in CodeStorage::::iter_values() { - ensure!( - value.determinism == Determinism::Deterministic, - "All pre-existing codes need to be deterministic." - ); - } - Ok(()) + #[test] + fn no_migration_in_progress_works() { + type TestMigration = Migration; + + ExtBuilder::default().build().execute_with(|| { + assert_eq!(StorageVersion::get::>(), 2); + assert_eq!(TestMigration::migrate(Weight::MAX).0, MigrateResult::NoMigrationInProgress) + }); + } + + #[test] + fn migration_works() { + type TestMigration = Migration; + + ExtBuilder::default().set_storage_version(0).build().execute_with(|| { + assert_eq!(StorageVersion::get::>(), 0); + TestMigration::on_runtime_upgrade(); + for (version, status) in + [(1, MigrateResult::InProgress { steps_done: 1 }), (2, MigrateResult::Completed)] + { + assert_eq!(TestMigration::migrate(Weight::MAX).0, status); + assert_eq!( + >::on_chain_storage_version(), + StorageVersion::new(version) + ); + } + + assert_eq!(TestMigration::migrate(Weight::MAX).0, MigrateResult::NoMigrationInProgress); + assert_eq!(StorageVersion::get::>(), 2); + }); } } diff --git a/frame/contracts/src/migration/v09.rs b/frame/contracts/src/migration/v09.rs new file mode 100644 index 000000000000..e6c664295564 --- /dev/null +++ b/frame/contracts/src/migration/v09.rs @@ -0,0 +1,144 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Update `CodeStorage` with the new `determinism` field. + +use crate::{ + migration::{IsFinished, MigrationStep}, + weights::WeightInfo, + CodeHash, Config, Determinism, Pallet, Weight, LOG_TARGET, +}; +use codec::{Decode, Encode}; +use frame_support::{codec, pallet_prelude::*, storage_alias, DefaultNoBound, Identity}; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; +use sp_std::prelude::*; + +mod old { + use super::*; + + #[derive(Encode, Decode)] + pub struct PrefabWasmModule { + #[codec(compact)] + pub instruction_weights_version: u32, + #[codec(compact)] + pub initial: u32, + #[codec(compact)] + pub maximum: u32, + pub code: Vec, + } + + #[storage_alias] + pub type CodeStorage = + StorageMap, Identity, CodeHash, PrefabWasmModule>; +} + +#[cfg(feature = "runtime-benchmarks")] +pub fn store_old_dummy_code(len: usize) { + use sp_runtime::traits::Hash; + let module = old::PrefabWasmModule { + instruction_weights_version: 0, + initial: 0, + maximum: 0, + code: vec![42u8; len], + }; + let hash = T::Hashing::hash(&module.code); + old::CodeStorage::::insert(hash, module); +} + +#[derive(Encode, Decode)] +struct PrefabWasmModule { + #[codec(compact)] + pub instruction_weights_version: u32, + #[codec(compact)] + pub initial: u32, + #[codec(compact)] + pub maximum: u32, + pub code: Vec, + pub determinism: Determinism, +} + +#[storage_alias] +type CodeStorage = StorageMap, Identity, CodeHash, PrefabWasmModule>; + +#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)] +pub struct Migration { + last_code_hash: Option>, +} + +impl MigrationStep for Migration { + const VERSION: u16 = 9; + + fn max_step_weight() -> Weight { + T::WeightInfo::v9_migration_step(T::MaxCodeLen::get()) + } + + fn step(&mut self) -> (IsFinished, Weight) { + let mut iter = if let Some(last_key) = self.last_code_hash.take() { + old::CodeStorage::::iter_from(old::CodeStorage::::hashed_key_for(last_key)) + } else { + old::CodeStorage::::iter() + }; + + if let Some((key, old)) = iter.next() { + log::debug!(target: LOG_TARGET, "Migrating contract code {:?}", key); + let len = old.code.len() as u32; + let module = PrefabWasmModule { + instruction_weights_version: old.instruction_weights_version, + initial: old.initial, + maximum: old.maximum, + code: old.code, + determinism: Determinism::Enforced, + }; + CodeStorage::::insert(key, module); + self.last_code_hash = Some(key); + (IsFinished::No, T::WeightInfo::v9_migration_step(len)) + } else { + log::debug!(target: LOG_TARGET, "No more contracts code to migrate"); + (IsFinished::Yes, T::WeightInfo::v9_migration_step(0)) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade_step() -> Result, TryRuntimeError> { + let sample: Vec<_> = old::CodeStorage::::iter().take(100).collect(); + + log::debug!(target: LOG_TARGET, "Taking sample of {} contract codes", sample.len()); + Ok(sample.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade_step(state: Vec) -> Result<(), TryRuntimeError> { + let sample = , old::PrefabWasmModule)> as Decode>::decode(&mut &state[..]) + .expect("pre_upgrade_step provides a valid state; qed"); + + log::debug!(target: LOG_TARGET, "Validating sample of {} contract codes", sample.len()); + for (code_hash, old) in sample { + let module = CodeStorage::::get(&code_hash).unwrap(); + ensure!( + module.instruction_weights_version == old.instruction_weights_version, + "invalid isntruction weights version" + ); + ensure!(module.determinism == Determinism::Enforced, "invalid determinism"); + ensure!(module.initial == old.initial, "invalid initial"); + ensure!(module.maximum == old.maximum, "invalid maximum"); + ensure!(module.code == old.code, "invalid code"); + } + + Ok(()) + } +} diff --git a/frame/contracts/src/migration/v10.rs b/frame/contracts/src/migration/v10.rs new file mode 100644 index 000000000000..40e007bf710b --- /dev/null +++ b/frame/contracts/src/migration/v10.rs @@ -0,0 +1,278 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Don't rely on reserved balances keeping an account alive +//! See . + +use crate::{ + address::AddressGenerator, + exec::AccountIdOf, + migration::{IsFinished, MigrationStep}, + weights::WeightInfo, + BalanceOf, CodeHash, Config, Pallet, TrieId, Weight, LOG_TARGET, +}; +use codec::{Decode, Encode}; +use core::cmp::{max, min}; +use frame_support::{ + codec, + pallet_prelude::*, + storage_alias, + traits::{ + fungible::Inspect, + tokens::{Fortitude::Polite, Preservation::Preserve}, + Currency, ExistenceRequirement, ReservableCurrency, + }, + DefaultNoBound, +}; +use sp_core::hexdisplay::HexDisplay; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; +use sp_runtime::{traits::Zero, Perbill, Saturating}; +use sp_std::{ops::Deref, prelude::*}; + +mod old { + use super::*; + + #[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] + #[scale_info(skip_type_params(T))] + pub struct ContractInfo { + pub trie_id: TrieId, + pub code_hash: CodeHash, + pub storage_bytes: u32, + pub storage_items: u32, + pub storage_byte_deposit: BalanceOf, + pub storage_item_deposit: BalanceOf, + pub storage_base_deposit: BalanceOf, + } + + #[storage_alias] + pub type ContractInfoOf = StorageMap< + Pallet, + Twox64Concat, + ::AccountId, + ContractInfo, + >; +} + +#[cfg(feature = "runtime-benchmarks")] +pub fn store_old_contract_info(account: T::AccountId, info: crate::ContractInfo) { + let info = old::ContractInfo { + trie_id: info.trie_id, + code_hash: info.code_hash, + storage_bytes: Default::default(), + storage_items: Default::default(), + storage_byte_deposit: Default::default(), + storage_item_deposit: Default::default(), + storage_base_deposit: Default::default(), + }; + old::ContractInfoOf::::insert(account, info); +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(T))] +pub struct DepositAccount(AccountIdOf); + +impl Deref for DepositAccount { + type Target = AccountIdOf; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(T))] +pub struct ContractInfo { + pub trie_id: TrieId, + deposit_account: DepositAccount, + pub code_hash: CodeHash, + storage_bytes: u32, + storage_items: u32, + pub storage_byte_deposit: BalanceOf, + storage_item_deposit: BalanceOf, + storage_base_deposit: BalanceOf, +} + +#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)] +pub struct Migration { + last_account: Option, +} + +#[storage_alias] +type ContractInfoOf = + StorageMap, Twox64Concat, ::AccountId, ContractInfo>; + +impl MigrationStep for Migration { + const VERSION: u16 = 10; + + fn max_step_weight() -> Weight { + T::WeightInfo::v10_migration_step() + } + + fn step(&mut self) -> (IsFinished, Weight) { + let mut iter = if let Some(last_account) = self.last_account.take() { + old::ContractInfoOf::::iter_from(old::ContractInfoOf::::hashed_key_for( + last_account, + )) + } else { + old::ContractInfoOf::::iter() + }; + + if let Some((account, contract)) = iter.next() { + let min_balance = Pallet::::min_balance(); + log::debug!(target: LOG_TARGET, "Account: 0x{} ", HexDisplay::from(&account.encode())); + + // Get the new deposit account address + let deposit_account: DepositAccount = + DepositAccount(T::AddressGenerator::deposit_address(&account)); + + // Calculate the existing deposit, that should be reserved on the contract account + let old_deposit = contract + .storage_base_deposit + .saturating_add(contract.storage_item_deposit) + .saturating_add(contract.storage_byte_deposit); + + // Unreserve the existing deposit + // Note we can't use repatriate_reserve, because it only works with existing accounts + let remaining = T::Currency::unreserve(&account, old_deposit); + if !remaining.is_zero() { + log::warn!( + target: LOG_TARGET, + "Partially unreserved. Remaining {:?} out of {:?} asked", + remaining, + old_deposit + ); + } + + // Attempt to transfer the old deposit to the deposit account. + let amount = old_deposit + .saturating_sub(min_balance) + .min(T::Currency::reducible_balance(&account, Preserve, Polite)); + + let new_deposit = T::Currency::transfer( + &account, + &deposit_account, + amount, + ExistenceRequirement::KeepAlive, + ) + .map(|_| { + log::debug!( + target: LOG_TARGET, + "Transferred deposit ({:?}) to deposit account", + amount + ); + amount + }) + // If it fails we fallback to minting the ED. + .unwrap_or_else(|err| { + log::error!( + target: LOG_TARGET, + "Failed to transfer the base deposit, reason: {:?}", + err + ); + T::Currency::deposit_creating(&deposit_account, min_balance); + min_balance + }); + + // Calculate the new base_deposit to store in the contract: + // Ideally, it should be the same as the old one + // Ideally, it should be at least 2xED (for the contract and deposit accounts). + // It can't be more than the `new_deposit`. + let new_base_deposit = min( + max(contract.storage_base_deposit, min_balance.saturating_add(min_balance)), + new_deposit, + ); + + // Calculate the ratio to adjust storage_byte and storage_item deposits. + let new_deposit_without_base = new_deposit.saturating_sub(new_base_deposit); + let old_deposit_without_base = + old_deposit.saturating_sub(contract.storage_base_deposit); + let ratio = Perbill::from_rational(new_deposit_without_base, old_deposit_without_base); + + // Calculate the new storage deposits based on the ratio + let storage_byte_deposit = ratio.mul_ceil(contract.storage_byte_deposit); + let storage_item_deposit = ratio.mul_ceil(contract.storage_item_deposit); + + // Recalculate the new base deposit, instead of using new_base_deposit to avoid rounding + // errors + let storage_base_deposit = new_deposit + .saturating_sub(storage_byte_deposit) + .saturating_sub(storage_item_deposit); + + let new_contract_info = ContractInfo { + trie_id: contract.trie_id, + deposit_account, + code_hash: contract.code_hash, + storage_bytes: contract.storage_bytes, + storage_items: contract.storage_items, + storage_byte_deposit, + storage_item_deposit, + storage_base_deposit, + }; + + ContractInfoOf::::insert(&account, new_contract_info); + + // Store last key for next migration step + self.last_account = Some(account); + + (IsFinished::No, T::WeightInfo::v10_migration_step()) + } else { + log::debug!(target: LOG_TARGET, "Done Migrating contract info"); + (IsFinished::Yes, T::WeightInfo::v10_migration_step()) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade_step() -> Result, TryRuntimeError> { + let sample: Vec<_> = old::ContractInfoOf::::iter().take(10).collect(); + + log::debug!(target: LOG_TARGET, "Taking sample of {} contracts", sample.len()); + Ok(sample.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade_step(state: Vec) -> Result<(), TryRuntimeError> { + let sample = )> as Decode>::decode(&mut &state[..]) + .expect("pre_upgrade_step provides a valid state; qed"); + + log::debug!(target: LOG_TARGET, "Validating sample of {} contracts", sample.len()); + for (account, old_contract) in sample { + log::debug!(target: LOG_TARGET, "==="); + log::debug!(target: LOG_TARGET, "Account: 0x{} ", HexDisplay::from(&account.encode())); + let contract = ContractInfoOf::::get(&account).unwrap(); + ensure!(old_contract.trie_id == contract.trie_id, "invalid trie_id"); + ensure!(old_contract.code_hash == contract.code_hash, "invalid code_hash"); + ensure!(old_contract.storage_bytes == contract.storage_bytes, "invalid storage_bytes"); + ensure!(old_contract.storage_items == contract.storage_items, "invalid storage_items"); + + let deposit = + <::Currency as frame_support::traits::Currency<_>>::total_balance( + &contract.deposit_account, + ); + ensure!( + deposit == + contract + .storage_base_deposit + .saturating_add(contract.storage_item_deposit) + .saturating_add(contract.storage_byte_deposit), + "deposit mismatch" + ); + } + + Ok(()) + } +} diff --git a/frame/contracts/src/migration/v11.rs b/frame/contracts/src/migration/v11.rs new file mode 100644 index 000000000000..8123d73aee56 --- /dev/null +++ b/frame/contracts/src/migration/v11.rs @@ -0,0 +1,133 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Overflowing bounded DeletionQueue. +//! See . + +use crate::{ + migration::{IsFinished, MigrationStep}, + weights::WeightInfo, + Config, Pallet, TrieId, Weight, LOG_TARGET, +}; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + +use codec::{Decode, Encode}; +use frame_support::{codec, pallet_prelude::*, storage_alias, DefaultNoBound}; +use sp_std::{marker::PhantomData, prelude::*}; +mod old { + use super::*; + + #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] + pub struct DeletedContract { + pub(crate) trie_id: TrieId, + } + + #[storage_alias] + pub type DeletionQueue = StorageValue, Vec>; +} + +#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, DefaultNoBound, Clone)] +#[scale_info(skip_type_params(T))] +pub struct DeletionQueueManager { + insert_counter: u32, + delete_counter: u32, + _phantom: PhantomData, +} + +#[cfg(any(feature = "runtime-benchmarks", feature = "try-runtime"))] +pub fn fill_old_queue(len: usize) { + let queue: Vec = + core::iter::repeat_with(|| old::DeletedContract { trie_id: Default::default() }) + .take(len) + .collect(); + old::DeletionQueue::::set(Some(queue)); +} + +#[storage_alias] +type DeletionQueue = StorageMap, Twox64Concat, u32, TrieId>; + +#[storage_alias] +type DeletionQueueCounter = StorageValue, DeletionQueueManager, ValueQuery>; + +#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)] +pub struct Migration { + _phantom: PhantomData, +} + +impl MigrationStep for Migration { + const VERSION: u16 = 11; + + // It would be more correct to make our use the now removed [DeletionQueueDepth](https://github.com/paritytech/substrate/pull/13702/files#diff-70e9723e9db62816e35f6f885b6770a8449c75a6c2733e9fa7a245fe52c4656c) + // but in practice the queue is always empty, so 128 is a good enough approximation for not + // underestimating the weight of our migration. + fn max_step_weight() -> Weight { + T::WeightInfo::v11_migration_step(128) + } + + fn step(&mut self) -> (IsFinished, Weight) { + let Some(old_queue) = old::DeletionQueue::::take() else { + return (IsFinished::Yes, Weight::zero()) + }; + let len = old_queue.len(); + + log::debug!( + target: LOG_TARGET, + "Migrating deletion queue with {} deleted contracts", + old_queue.len() + ); + + if !old_queue.is_empty() { + let mut queue = DeletionQueueManager::::default(); + for contract in old_queue { + >::insert(queue.insert_counter, contract.trie_id); + queue.insert_counter += 1; + } + + >::set(queue); + } + + (IsFinished::Yes, T::WeightInfo::v11_migration_step(len as u32)) + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade_step() -> Result, TryRuntimeError> { + let old_queue = old::DeletionQueue::::take().unwrap_or_default(); + + if old_queue.is_empty() { + let len = 10u32; + log::debug!( + target: LOG_TARGET, + "Injecting {len} entries to deletion queue to test migration" + ); + fill_old_queue::(len as usize); + return Ok(len.encode()) + } + + Ok((old_queue.len() as u32).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade_step(state: Vec) -> Result<(), TryRuntimeError> { + let len = ::decode(&mut &state[..]) + .expect("pre_upgrade_step provides a valid state; qed"); + let counter = >::get(); + ensure!(counter.insert_counter == len, "invalid insert counter"); + ensure!(counter.delete_counter == 0, "invalid delete counter"); + Ok(()) + } +} diff --git a/frame/contracts/src/migration/v12.rs b/frame/contracts/src/migration/v12.rs new file mode 100644 index 000000000000..4fb7ca76fea9 --- /dev/null +++ b/frame/contracts/src/migration/v12.rs @@ -0,0 +1,318 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Move `OwnerInfo` to `CodeInfo`, add `determinism` field to the latter, clear `CodeStorage` and +//! repay deposits. + +use crate::{ + migration::{IsFinished, MigrationStep}, + weights::WeightInfo, + AccountIdOf, BalanceOf, CodeHash, Config, Determinism, Pallet, Weight, LOG_TARGET, +}; +use codec::{Decode, Encode}; +use frame_support::{ + codec, pallet_prelude::*, storage_alias, traits::ReservableCurrency, DefaultNoBound, Identity, +}; +use scale_info::prelude::format; +use sp_core::hexdisplay::HexDisplay; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; +use sp_runtime::{traits::Zero, FixedPointNumber, FixedU128, Saturating}; +use sp_std::prelude::*; + +mod old { + use super::*; + + #[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] + #[codec(mel_bound())] + #[scale_info(skip_type_params(T))] + pub struct OwnerInfo { + pub owner: AccountIdOf, + #[codec(compact)] + pub deposit: BalanceOf, + #[codec(compact)] + pub refcount: u64, + } + + #[derive(Encode, Decode, scale_info::TypeInfo)] + #[codec(mel_bound())] + #[scale_info(skip_type_params(T))] + pub struct PrefabWasmModule { + #[codec(compact)] + pub instruction_weights_version: u32, + #[codec(compact)] + pub initial: u32, + #[codec(compact)] + pub maximum: u32, + pub code: Vec, + pub determinism: Determinism, + } + + #[storage_alias] + pub type OwnerInfoOf = StorageMap, Identity, CodeHash, OwnerInfo>; + + #[storage_alias] + pub type CodeStorage = + StorageMap, Identity, CodeHash, PrefabWasmModule>; +} + +#[derive(Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] +#[codec(mel_bound())] +#[scale_info(skip_type_params(T))] +pub struct CodeInfo { + owner: AccountIdOf, + #[codec(compact)] + deposit: BalanceOf, + #[codec(compact)] + refcount: u64, + determinism: Determinism, + code_len: u32, +} + +#[storage_alias] +pub type CodeInfoOf = StorageMap, Twox64Concat, CodeHash, CodeInfo>; + +#[storage_alias] +pub type PristineCode = StorageMap, Identity, CodeHash, Vec>; + +#[cfg(feature = "runtime-benchmarks")] +pub fn store_old_dummy_code(len: usize, account: T::AccountId) { + use sp_runtime::traits::Hash; + + let code = vec![42u8; len]; + let hash = T::Hashing::hash(&code); + PristineCode::::insert(hash, code.clone()); + + let module = old::PrefabWasmModule { + instruction_weights_version: Default::default(), + initial: Default::default(), + maximum: Default::default(), + code, + determinism: Determinism::Enforced, + }; + old::CodeStorage::::insert(hash, module); + + let info = old::OwnerInfo { owner: account, deposit: u32::MAX.into(), refcount: u64::MAX }; + old::OwnerInfoOf::::insert(hash, info); +} + +#[derive(Encode, Decode, MaxEncodedLen, DefaultNoBound)] +pub struct Migration { + last_code_hash: Option>, +} + +impl MigrationStep for Migration { + const VERSION: u16 = 12; + + fn max_step_weight() -> Weight { + T::WeightInfo::v12_migration_step(T::MaxCodeLen::get()) + } + + fn step(&mut self) -> (IsFinished, Weight) { + let mut iter = if let Some(last_key) = self.last_code_hash.take() { + old::OwnerInfoOf::::iter_from(old::OwnerInfoOf::::hashed_key_for(last_key)) + } else { + old::OwnerInfoOf::::iter() + }; + if let Some((hash, old_info)) = iter.next() { + log::debug!(target: LOG_TARGET, "Migrating OwnerInfo for code_hash {:?}", hash); + + let module = old::CodeStorage::::take(hash) + .expect(format!("No PrefabWasmModule found for code_hash: {:?}", hash).as_str()); + + let code_len = module.code.len(); + // We print this to measure the impact of the migration. + // Storage removed: deleted PrefabWasmModule's encoded len. + // Storage added: determinism field encoded len (as all other CodeInfo fields are the + // same as in the deleted OwnerInfo). + log::debug!(target: LOG_TARGET, "Storage removed: 1 item, {} bytes", &code_len,); + + // Storage usage prices could change over time, and accounts who uploaded their + // contracts code before the storage deposits where introduced, had not been ever + // charged with any deposit for that (see migration v6). + // + // This is why deposit to be refunded here is calculated as follows: + // + // 1. Calculate the deposit amount for storage before the migration, given current + // prices. + // 2. Given current reserved deposit amount, calculate the correction factor. + // 3. Calculate the deposit amount for storage after the migration, given current + // prices. + // 4. Calculate real deposit amount to be reserved after the migration. + let price_per_byte = T::DepositPerByte::get(); + let price_per_item = T::DepositPerItem::get(); + let bytes_before = module + .encoded_size() + .saturating_add(code_len) + .saturating_add(old::OwnerInfo::::max_encoded_len()) as u32; + let items_before = 3u32; + let deposit_expected_before = price_per_byte + .saturating_mul(bytes_before.into()) + .saturating_add(price_per_item.saturating_mul(items_before.into())); + let ratio = FixedU128::checked_from_rational(old_info.deposit, deposit_expected_before) + .unwrap_or_default() + .min(FixedU128::from_u32(1)); + let bytes_after = code_len.saturating_add(CodeInfo::::max_encoded_len()) as u32; + let items_after = 2u32; + let deposit_expected_after = price_per_byte + .saturating_mul(bytes_after.into()) + .saturating_add(price_per_item.saturating_mul(items_after.into())); + let deposit = ratio.saturating_mul_int(deposit_expected_after); + + let info = CodeInfo:: { + determinism: module.determinism, + owner: old_info.owner, + deposit, + refcount: old_info.refcount, + code_len: code_len as u32, + }; + + let amount = old_info.deposit.saturating_sub(info.deposit); + if !amount.is_zero() { + T::Currency::unreserve(&info.owner, amount); + log::debug!( + target: LOG_TARGET, + "Deposit refunded: {:?} Balance, to: {:?}", + &amount, + HexDisplay::from(&info.owner.encode()) + ); + } else { + log::warn!( + target: LOG_TARGET, + "new deposit: {:?} >= old deposit: {:?}", + &info.deposit, + &old_info.deposit + ); + } + CodeInfoOf::::insert(hash, info); + + self.last_code_hash = Some(hash); + + (IsFinished::No, T::WeightInfo::v12_migration_step(code_len as u32)) + } else { + log::debug!(target: LOG_TARGET, "No more OwnerInfo to migrate"); + (IsFinished::Yes, T::WeightInfo::v12_migration_step(0)) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade_step() -> Result, TryRuntimeError> { + let len = 100; + log::debug!(target: LOG_TARGET, "Taking sample of {} OwnerInfo(s)", len); + let sample: Vec<_> = old::OwnerInfoOf::::iter() + .take(len) + .map(|(k, v)| { + let module = old::CodeStorage::::get(k) + .expect("No PrefabWasmModule found for code_hash: {:?}"); + let info: CodeInfo = CodeInfo { + determinism: module.determinism, + deposit: v.deposit, + refcount: v.refcount, + owner: v.owner, + code_len: module.code.len() as u32, + }; + (k, info) + }) + .collect(); + + let storage: u32 = + old::CodeStorage::::iter().map(|(_k, v)| v.encoded_size() as u32).sum(); + let mut deposit: BalanceOf = Default::default(); + old::OwnerInfoOf::::iter().for_each(|(_k, v)| deposit += v.deposit); + + Ok((sample, deposit, storage).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade_step(state: Vec) -> Result<(), TryRuntimeError> { + let state = <(Vec<(CodeHash, CodeInfo)>, BalanceOf, u32) as Decode>::decode( + &mut &state[..], + ) + .unwrap(); + + log::debug!(target: LOG_TARGET, "Validating state of {} Codeinfo(s)", state.0.len()); + for (hash, old) in state.0 { + let info = CodeInfoOf::::get(&hash) + .expect(format!("CodeInfo for code_hash {:?} not found!", hash).as_str()); + ensure!(info.determinism == old.determinism, "invalid determinism"); + ensure!(info.owner == old.owner, "invalid owner"); + ensure!(info.refcount == old.refcount, "invalid refcount"); + } + + if let Some((k, _)) = old::CodeStorage::::iter().next() { + log::warn!( + target: LOG_TARGET, + "CodeStorage is still NOT empty, found code_hash: {:?}", + k + ); + } else { + log::debug!(target: LOG_TARGET, "CodeStorage is empty."); + } + if let Some((k, _)) = old::OwnerInfoOf::::iter().next() { + log::warn!( + target: LOG_TARGET, + "OwnerInfoOf is still NOT empty, found code_hash: {:?}", + k + ); + } else { + log::debug!(target: LOG_TARGET, "OwnerInfoOf is empty."); + } + + let mut deposit: BalanceOf = Default::default(); + let mut items = 0u32; + let mut storage_info = 0u32; + CodeInfoOf::::iter().for_each(|(_k, v)| { + deposit += v.deposit; + items += 1; + storage_info += v.encoded_size() as u32; + }); + let mut storage_code = 0u32; + PristineCode::::iter().for_each(|(_k, v)| { + storage_code += v.len() as u32; + }); + let (_, old_deposit, storage_module) = state; + // CodeInfoOf::max_encoded_len == OwnerInfoOf::max_encoded_len + 1 + // I.e. code info adds up 1 byte per record. + let info_bytes_added = items.clone(); + // We removed 1 PrefabWasmModule, and added 1 byte of determinism flag, per contract code. + let storage_removed = storage_module.saturating_sub(info_bytes_added); + // module+code+info - bytes + let storage_was = storage_module + .saturating_add(storage_code) + .saturating_add(storage_info) + .saturating_sub(info_bytes_added); + // We removed 1 storage item (PrefabWasmMod) for every stored contract code (was stored 3 + // items per code). + let items_removed = items; + log::info!( + target: LOG_TARGET, + "Storage freed, bytes: {} (of {}), items: {} (of {})", + storage_removed, + storage_was, + items_removed, + items_removed * 3, + ); + log::info!( + target: LOG_TARGET, + "Deposits returned, total: {:?} Balance (of {:?} Balance)", + old_deposit.saturating_sub(deposit), + old_deposit, + ); + + Ok(()) + } +} diff --git a/frame/contracts/src/schedule.rs b/frame/contracts/src/schedule.rs index 7e09d55b44c5..1e906a99061a 100644 --- a/frame/contracts/src/schedule.rs +++ b/frame/contracts/src/schedule.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ //! This module contains the cost schedule and supporting code that constructs a //! sane default schedule from a `WeightInfo` implementation. -use crate::{wasm::Determinism, weights::WeightInfo, Config}; +use crate::{weights::WeightInfo, Config}; use codec::{Decode, Encode}; use frame_support::{weights::Weight, DefaultNoBound}; @@ -27,17 +27,7 @@ use scale_info::TypeInfo; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; use sp_runtime::RuntimeDebug; -use sp_std::{marker::PhantomData, vec::Vec}; -use wasm_instrument::{gas_metering, parity_wasm::elements}; - -/// How many API calls are executed in a single batch. The reason for increasing the amount -/// of API calls in batches (per benchmark component increase) is so that the linear regression -/// has an easier time determining the contribution of that component. -pub const API_BENCHMARK_BATCH_SIZE: u32 = 80; - -/// How many instructions are executed in a single batch. The reasoning is the same -/// as for `API_BENCHMARK_BATCH_SIZE`. -pub const INSTR_BENCHMARK_BATCH_SIZE: u32 = 100; +use sp_std::marker::PhantomData; /// Definition of the cost schedule and other parameterizations for the wasm vm. /// @@ -59,18 +49,12 @@ pub const INSTR_BENCHMARK_BATCH_SIZE: u32 = 100; /// .. Default::default() /// }, /// instruction_weights: InstructionWeights { -/// version: 5, /// .. Default::default() /// }, /// .. Default::default() /// } /// } /// ``` -/// -/// # Note -/// -/// Please make sure to bump the [`InstructionWeights::version`] whenever substantial -/// changes are made to its values. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[cfg_attr(feature = "std", serde(bound(serialize = "", deserialize = "")))] #[derive(Clone, Encode, Decode, PartialEq, Eq, ScheduleDebug, DefaultNoBound, TypeInfo)] @@ -87,12 +71,6 @@ pub struct Schedule { } /// Describes the upper limits on various metrics. -/// -/// # Note -/// -/// The values in this struct should never be decreased. The reason is that decreasing those -/// values will break existing contracts which are above the new limits when a -/// re-instrumentation is triggered. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Clone, Encode, Decode, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct Limits { @@ -136,6 +114,10 @@ pub struct Limits { /// The maximum size of a storage value and event payload in bytes. pub payload_len: u32, + + /// The maximum node runtime memory. This is for integrity checks only and does not affect the + /// real setting. + pub runtime_memory: u32, } impl Limits { @@ -145,102 +127,15 @@ impl Limits { } } -/// Describes the weight for all categories of supported wasm instructions. -/// -/// There there is one field for each wasm instruction that describes the weight to -/// execute one instruction of that name. There are a few execptions: -/// -/// 1. If there is a i64 and a i32 variant of an instruction we use the weight -/// of the former for both. -/// 2. The following instructions are free of charge because they merely structure the -/// wasm module and cannot be spammed without making the module invalid (and rejected): -/// End, Unreachable, Return, Else -/// 3. The following instructions cannot be benchmarked because they are removed by any -/// real world execution engine as a preprocessing step and therefore don't yield a -/// meaningful benchmark result. However, in contrast to the instructions mentioned -/// in 2. they can be spammed. We price them with the same weight as the "default" -/// instruction (i64.const): Block, Loop, Nop -/// 4. We price both i64.const and drop as InstructionWeights.i64const / 2. The reason -/// for that is that we cannot benchmark either of them on its own but we need their -/// individual values to derive (by subtraction) the weight of all other instructions -/// that use them as supporting instructions. Supporting means mainly pushing arguments -/// and dropping return values in order to maintain a valid module. +/// Gas metering of Wasm executed instructions is being done on the engine side. +/// This struct holds a reference value used to gas units scaling between host and engine. #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Clone, Encode, Decode, PartialEq, Eq, ScheduleDebug, TypeInfo)] #[scale_info(skip_type_params(T))] pub struct InstructionWeights { - /// Version of the instruction weights. - /// - /// # Note - /// - /// Should be incremented whenever any instruction weight is changed. The - /// reason is that changes to instruction weights require a re-instrumentation - /// in order to apply the changes to an already deployed code. The re-instrumentation - /// is triggered by comparing the version of the current schedule with the version the code was - /// instrumented with. Changes usually happen when pallet_contracts is re-benchmarked. - /// - /// Changes to other parts of the schedule should not increment the version in - /// order to avoid unnecessary re-instrumentations. - pub version: u32, - /// Weight to be used for instructions which don't have benchmarks assigned. - /// - /// This weight is used whenever a code is uploaded with [`Determinism::AllowIndeterminism`] - /// and an instruction (usually a float instruction) is encountered. This weight is **not** - /// used if a contract is uploaded with [`Determinism::Deterministic`]. If this field is set to - /// `0` (the default) only deterministic codes are allowed to be uploaded. - pub fallback: u32, - pub i64const: u32, - pub i64load: u32, - pub i64store: u32, - pub select: u32, - pub r#if: u32, - pub br: u32, - pub br_if: u32, - pub br_table: u32, - pub br_table_per_entry: u32, - pub call: u32, - pub call_indirect: u32, - pub call_indirect_per_param: u32, - pub call_per_local: u32, - pub local_get: u32, - pub local_set: u32, - pub local_tee: u32, - pub global_get: u32, - pub global_set: u32, - pub memory_current: u32, - pub memory_grow: u32, - pub i64clz: u32, - pub i64ctz: u32, - pub i64popcnt: u32, - pub i64eqz: u32, - pub i64extendsi32: u32, - pub i64extendui32: u32, - pub i32wrapi64: u32, - pub i64eq: u32, - pub i64ne: u32, - pub i64lts: u32, - pub i64ltu: u32, - pub i64gts: u32, - pub i64gtu: u32, - pub i64les: u32, - pub i64leu: u32, - pub i64ges: u32, - pub i64geu: u32, - pub i64add: u32, - pub i64sub: u32, - pub i64mul: u32, - pub i64divs: u32, - pub i64divu: u32, - pub i64rems: u32, - pub i64remu: u32, - pub i64and: u32, - pub i64or: u32, - pub i64xor: u32, - pub i64shl: u32, - pub i64shrs: u32, - pub i64shru: u32, - pub i64rotl: u32, - pub i64rotr: u32, + /// Base instruction `ref_time` Weight. + /// Should match to wasmi's `1` fuel (see ). + pub base: u32, /// The type parameter is used in the default implementation. #[codec(skip)] pub _phantom: PhantomData, @@ -266,6 +161,9 @@ pub struct HostFnWeights { /// Weight of calling `seal_caller_is_origin`. pub caller_is_origin: Weight, + /// Weight of calling `seal_caller_is_root`. + pub caller_is_root: Weight, + /// Weight of calling `seal_address`. pub address: Weight, @@ -290,9 +188,6 @@ pub struct HostFnWeights { /// Weight of calling `seal_weight_to_fee`. pub weight_to_fee: Weight, - /// Weight of calling `gas`. - pub gas: Weight, - /// Weight of calling `seal_input`. pub input: Weight, @@ -323,6 +218,9 @@ pub struct HostFnWeights { /// Weight of calling `seal_debug_message`. pub debug_message: Weight, + /// Weight of calling `seal_debug_message` per byte of the message. + pub debug_message_per_byte: Weight, + /// Weight of calling `seal_set_storage`. pub set_storage: Weight, @@ -416,6 +314,12 @@ pub struct HostFnWeights { /// Weight of calling `seal_ecdsa_to_eth_address`. pub ecdsa_to_eth_address: Weight, + /// Weight of calling `sr25519_verify`. + pub sr25519_verify: Weight, + + /// Weight per byte of calling `sr25519_verify`. + pub sr25519_verify_per_byte: Weight, + /// Weight of calling `reentrance_count`. pub reentrance_count: Weight, @@ -444,76 +348,26 @@ macro_rules! call_zero { macro_rules! cost_args { ($name:ident, $( $arg: expr ),+) => { - (T::WeightInfo::$name($( $arg ),+).saturating_sub(call_zero!($name, $( $arg ),+))).ref_time() - } -} - -macro_rules! cost_batched_args { - ($name:ident, $( $arg: expr ),+) => { - cost_args!($name, $( $arg ),+) / u64::from(API_BENCHMARK_BATCH_SIZE) + (T::WeightInfo::$name($( $arg ),+).saturating_sub(call_zero!($name, $( $arg ),+))) } } -macro_rules! cost_instr_no_params_with_batch_size { - ($name:ident, $batch_size:expr) => { - (cost_args!($name, 1) / u64::from($batch_size)) as u32 - }; -} - -macro_rules! cost_instr_with_batch_size { - ($name:ident, $num_params:expr, $batch_size:expr) => { - cost_instr_no_params_with_batch_size!($name, $batch_size).saturating_sub( - (cost_instr_no_params_with_batch_size!(instr_i64const, $batch_size) / 2) - .saturating_mul($num_params), - ) - }; -} - -macro_rules! cost_instr { - ($name:ident, $num_params:expr) => { - cost_instr_with_batch_size!($name, $num_params, INSTR_BENCHMARK_BATCH_SIZE) +macro_rules! cost_instr_no_params { + ($name:ident) => { + cost_args!($name, 1).ref_time() as u32 }; } -macro_rules! cost_byte_args { - ($name:ident, $( $arg: expr ),+) => { - cost_args!($name, $( $arg ),+) / 1024 - } -} - -macro_rules! cost_byte_batched_args { - ($name:ident, $( $arg: expr ),+) => { - cost_batched_args!($name, $( $arg ),+) / 1024 - } -} - macro_rules! cost { ($name:ident) => { cost_args!($name, 1) }; } -macro_rules! cost_batched { - ($name:ident) => { - cost_batched_args!($name, 1) - }; -} - -macro_rules! cost_byte { - ($name:ident) => { - cost_byte_args!($name, 1) - }; -} - -macro_rules! cost_byte_batched { - ($name:ident) => { - cost_byte_batched_args!($name, 1) - }; -} - -macro_rules! to_weight { - ($ref_time:expr $(, $proof_size:expr )?) => { - Weight::from_ref_time($ref_time)$(.set_proof_size($proof_size))? +macro_rules! cost_instr { + ($name:ident, $num_params:expr) => { + cost_instr_no_params!($name) + .saturating_sub((cost_instr_no_params!(instr_i64const) / 2).saturating_mul($num_params)) }; } @@ -530,314 +384,103 @@ impl Default for Limits { br_table_size: 256, subject_len: 32, payload_len: 16 * 1024, + runtime_memory: 1024 * 1024 * 128, } } } impl Default for InstructionWeights { + /// We price both `i64.const` and `drop` as `instr_i64const / 2`. The reason + /// for that is that we cannot benchmark either of them on its own. fn default() -> Self { - let max_pages = Limits::default().memory_pages; - Self { - version: 4, - fallback: 0, - i64const: cost_instr!(instr_i64const, 1), - i64load: cost_instr!(instr_i64load, 2), - i64store: cost_instr!(instr_i64store, 2), - select: cost_instr!(instr_select, 4), - r#if: cost_instr!(instr_if, 3), - br: cost_instr!(instr_br, 2), - br_if: cost_instr!(instr_br_if, 3), - br_table: cost_instr!(instr_br_table, 3), - br_table_per_entry: cost_instr!(instr_br_table_per_entry, 0), - call: cost_instr!(instr_call, 2), - call_indirect: cost_instr!(instr_call_indirect, 3), - call_indirect_per_param: cost_instr!(instr_call_indirect_per_param, 1), - call_per_local: cost_instr!(instr_call_per_local, 1), - local_get: cost_instr!(instr_local_get, 1), - local_set: cost_instr!(instr_local_set, 1), - local_tee: cost_instr!(instr_local_tee, 2), - global_get: cost_instr!(instr_global_get, 1), - global_set: cost_instr!(instr_global_set, 1), - memory_current: cost_instr!(instr_memory_current, 1), - memory_grow: cost_instr_with_batch_size!(instr_memory_grow, 1, max_pages), - i64clz: cost_instr!(instr_i64clz, 2), - i64ctz: cost_instr!(instr_i64ctz, 2), - i64popcnt: cost_instr!(instr_i64popcnt, 2), - i64eqz: cost_instr!(instr_i64eqz, 2), - i64extendsi32: cost_instr!(instr_i64extendsi32, 2), - i64extendui32: cost_instr!(instr_i64extendui32, 2), - i32wrapi64: cost_instr!(instr_i32wrapi64, 2), - i64eq: cost_instr!(instr_i64eq, 3), - i64ne: cost_instr!(instr_i64ne, 3), - i64lts: cost_instr!(instr_i64lts, 3), - i64ltu: cost_instr!(instr_i64ltu, 3), - i64gts: cost_instr!(instr_i64gts, 3), - i64gtu: cost_instr!(instr_i64gtu, 3), - i64les: cost_instr!(instr_i64les, 3), - i64leu: cost_instr!(instr_i64leu, 3), - i64ges: cost_instr!(instr_i64ges, 3), - i64geu: cost_instr!(instr_i64geu, 3), - i64add: cost_instr!(instr_i64add, 3), - i64sub: cost_instr!(instr_i64sub, 3), - i64mul: cost_instr!(instr_i64mul, 3), - i64divs: cost_instr!(instr_i64divs, 3), - i64divu: cost_instr!(instr_i64divu, 3), - i64rems: cost_instr!(instr_i64rems, 3), - i64remu: cost_instr!(instr_i64remu, 3), - i64and: cost_instr!(instr_i64and, 3), - i64or: cost_instr!(instr_i64or, 3), - i64xor: cost_instr!(instr_i64xor, 3), - i64shl: cost_instr!(instr_i64shl, 3), - i64shrs: cost_instr!(instr_i64shrs, 3), - i64shru: cost_instr!(instr_i64shru, 3), - i64rotl: cost_instr!(instr_i64rotl, 3), - i64rotr: cost_instr!(instr_i64rotr, 3), - _phantom: PhantomData, - } + Self { base: cost_instr!(instr_i64const, 1), _phantom: PhantomData } } } impl Default for HostFnWeights { - /// PoV should contain all trie nodes that are read during state transition (i.e. block - /// production). Hence we need to charge the `proof_size` weight for every host function which - /// reads storage, namely: - /// - get_storage, - /// - take_storage, - /// - contains_storage, - /// - clear_storage, - /// - set_storage. - /// - /// The last two functions write to storage, but they also do read storage in order to return - /// the size of the pre-existed value. Till we have PoV benchmarks implemented, we approximate - /// `proof_size` as being equal to the size of storage read. fn default() -> Self { Self { - caller: to_weight!(cost_batched!(seal_caller)), - is_contract: to_weight!(cost_batched!(seal_is_contract)), - code_hash: to_weight!(cost_batched!(seal_code_hash)), - own_code_hash: to_weight!(cost_batched!(seal_own_code_hash)), - caller_is_origin: to_weight!(cost_batched!(seal_caller_is_origin)), - address: to_weight!(cost_batched!(seal_address)), - gas_left: to_weight!(cost_batched!(seal_gas_left)), - balance: to_weight!(cost_batched!(seal_balance)), - value_transferred: to_weight!(cost_batched!(seal_value_transferred)), - minimum_balance: to_weight!(cost_batched!(seal_minimum_balance)), - block_number: to_weight!(cost_batched!(seal_block_number)), - now: to_weight!(cost_batched!(seal_now)), - weight_to_fee: to_weight!(cost_batched!(seal_weight_to_fee)), - gas: to_weight!(cost_batched!(seal_gas)), - input: to_weight!(cost_batched!(seal_input)), - input_per_byte: to_weight!(cost_byte_batched!(seal_input_per_kb)), - r#return: to_weight!(cost!(seal_return)), - return_per_byte: to_weight!(cost_byte!(seal_return_per_kb)), - terminate: to_weight!(cost!(seal_terminate)), - random: to_weight!(cost_batched!(seal_random)), - deposit_event: to_weight!(cost_batched!(seal_deposit_event)), - deposit_event_per_topic: to_weight!(cost_batched_args!( - seal_deposit_event_per_topic_and_kb, + caller: cost!(seal_caller), + is_contract: cost!(seal_is_contract), + code_hash: cost!(seal_code_hash), + own_code_hash: cost!(seal_own_code_hash), + caller_is_origin: cost!(seal_caller_is_origin), + caller_is_root: cost!(seal_caller_is_root), + address: cost!(seal_address), + gas_left: cost!(seal_gas_left), + balance: cost!(seal_balance), + value_transferred: cost!(seal_value_transferred), + minimum_balance: cost!(seal_minimum_balance), + block_number: cost!(seal_block_number), + now: cost!(seal_now), + weight_to_fee: cost!(seal_weight_to_fee), + input: cost!(seal_input), + input_per_byte: cost!(seal_input_per_byte), + r#return: cost!(seal_return), + return_per_byte: cost!(seal_return_per_byte), + terminate: cost!(seal_terminate), + random: cost!(seal_random), + deposit_event: cost!(seal_deposit_event), + deposit_event_per_topic: cost_args!(seal_deposit_event_per_topic_and_byte, 1, 0), + deposit_event_per_byte: cost_args!(seal_deposit_event_per_topic_and_byte, 0, 1), + debug_message: cost!(seal_debug_message), + debug_message_per_byte: cost!(seal_debug_message_per_byte), + set_storage: cost!(seal_set_storage), + set_code_hash: cost!(seal_set_code_hash), + set_storage_per_new_byte: cost!(seal_set_storage_per_new_byte), + set_storage_per_old_byte: cost!(seal_set_storage_per_old_byte), + clear_storage: cost!(seal_clear_storage), + clear_storage_per_byte: cost!(seal_clear_storage_per_byte), + contains_storage: cost!(seal_contains_storage), + contains_storage_per_byte: cost!(seal_contains_storage_per_byte), + get_storage: cost!(seal_get_storage), + get_storage_per_byte: cost!(seal_get_storage_per_byte), + take_storage: cost!(seal_take_storage), + take_storage_per_byte: cost!(seal_take_storage_per_byte), + transfer: cost!(seal_transfer), + call: cost!(seal_call), + delegate_call: cost!(seal_delegate_call), + call_transfer_surcharge: cost_args!(seal_call_per_transfer_clone_byte, 1, 0), + call_per_cloned_byte: cost_args!(seal_call_per_transfer_clone_byte, 0, 1), + instantiate: cost!(seal_instantiate), + instantiate_transfer_surcharge: cost_args!( + seal_instantiate_per_transfer_input_salt_byte, 1, - 0 - )), - deposit_event_per_byte: to_weight!(cost_byte_batched_args!( - seal_deposit_event_per_topic_and_kb, 0, - 1 - )), - debug_message: to_weight!(cost_batched!(seal_debug_message)), - set_storage: to_weight!(cost_batched!(seal_set_storage), 1024u64), - set_code_hash: to_weight!(cost_batched!(seal_set_code_hash)), - set_storage_per_new_byte: to_weight!(cost_byte_batched!(seal_set_storage_per_new_kb)), - set_storage_per_old_byte: to_weight!( - cost_byte_batched!(seal_set_storage_per_old_kb), - 1u64 - ), - clear_storage: to_weight!(cost_batched!(seal_clear_storage), 1024u64), - clear_storage_per_byte: to_weight!(cost_byte_batched!(seal_clear_storage_per_kb), 1u64), - contains_storage: to_weight!(cost_batched!(seal_contains_storage), 1024u64), - contains_storage_per_byte: to_weight!( - cost_byte_batched!(seal_contains_storage_per_kb), - 1u64 - ), - get_storage: to_weight!(cost_batched!(seal_get_storage), 1024u64), - get_storage_per_byte: to_weight!(cost_byte_batched!(seal_get_storage_per_kb), 1u64), - take_storage: to_weight!(cost_batched!(seal_take_storage), 1024u64), - take_storage_per_byte: to_weight!(cost_byte_batched!(seal_take_storage_per_kb), 1u64), - transfer: to_weight!(cost_batched!(seal_transfer)), - call: to_weight!(cost_batched!(seal_call)), - delegate_call: to_weight!(cost_batched!(seal_delegate_call)), - call_transfer_surcharge: to_weight!(cost_batched_args!( - seal_call_per_transfer_clone_kb, - 1, 0 - )), - call_per_cloned_byte: to_weight!(cost_batched_args!( - seal_call_per_transfer_clone_kb, - 0, - 1 - )), - instantiate: to_weight!(cost_batched!(seal_instantiate)), - instantiate_transfer_surcharge: to_weight!(cost_byte_batched_args!( - seal_instantiate_per_transfer_input_salt_kb, - 1, - 0, - 0 - )), - instantiate_per_input_byte: to_weight!(cost_byte_batched_args!( - seal_instantiate_per_transfer_input_salt_kb, + ), + instantiate_per_input_byte: cost_args!( + seal_instantiate_per_transfer_input_salt_byte, 0, 1, 0 - )), - instantiate_per_salt_byte: to_weight!(cost_byte_batched_args!( - seal_instantiate_per_transfer_input_salt_kb, + ), + instantiate_per_salt_byte: cost_args!( + seal_instantiate_per_transfer_input_salt_byte, 0, 0, 1 - )), - hash_sha2_256: to_weight!(cost_batched!(seal_hash_sha2_256)), - hash_sha2_256_per_byte: to_weight!(cost_byte_batched!(seal_hash_sha2_256_per_kb)), - hash_keccak_256: to_weight!(cost_batched!(seal_hash_keccak_256)), - hash_keccak_256_per_byte: to_weight!(cost_byte_batched!(seal_hash_keccak_256_per_kb)), - hash_blake2_256: to_weight!(cost_batched!(seal_hash_blake2_256)), - hash_blake2_256_per_byte: to_weight!(cost_byte_batched!(seal_hash_blake2_256_per_kb)), - hash_blake2_128: to_weight!(cost_batched!(seal_hash_blake2_128)), - hash_blake2_128_per_byte: to_weight!(cost_byte_batched!(seal_hash_blake2_128_per_kb)), - ecdsa_recover: to_weight!(cost_batched!(seal_ecdsa_recover)), - ecdsa_to_eth_address: to_weight!(cost_batched!(seal_ecdsa_to_eth_address)), - reentrance_count: to_weight!(cost_batched!(seal_reentrance_count)), - account_reentrance_count: to_weight!(cost_batched!(seal_account_reentrance_count)), - instantiation_nonce: to_weight!(cost_batched!(seal_instantiation_nonce)), + ), + hash_sha2_256: cost!(seal_hash_sha2_256), + hash_sha2_256_per_byte: cost!(seal_hash_sha2_256_per_byte), + hash_keccak_256: cost!(seal_hash_keccak_256), + hash_keccak_256_per_byte: cost!(seal_hash_keccak_256_per_byte), + hash_blake2_256: cost!(seal_hash_blake2_256), + hash_blake2_256_per_byte: cost!(seal_hash_blake2_256_per_byte), + hash_blake2_128: cost!(seal_hash_blake2_128), + hash_blake2_128_per_byte: cost!(seal_hash_blake2_128_per_byte), + ecdsa_recover: cost!(seal_ecdsa_recover), + sr25519_verify: cost!(seal_sr25519_verify), + sr25519_verify_per_byte: cost!(seal_sr25519_verify_per_byte), + ecdsa_to_eth_address: cost!(seal_ecdsa_to_eth_address), + reentrance_count: cost!(seal_reentrance_count), + account_reentrance_count: cost!(seal_account_reentrance_count), + instantiation_nonce: cost!(seal_instantiation_nonce), _phantom: PhantomData, } } } -struct ScheduleRules<'a, T: Config> { - schedule: &'a Schedule, - params: Vec, - determinism: Determinism, -} - -impl Schedule { - pub(crate) fn rules( - &self, - module: &elements::Module, - determinism: Determinism, - ) -> impl gas_metering::Rules + '_ { - ScheduleRules { - schedule: self, - params: module - .type_section() - .iter() - .flat_map(|section| section.types()) - .map(|func| { - let elements::Type::Function(func) = func; - func.params().len() as u32 - }) - .collect(), - determinism, - } - } -} - -impl<'a, T: Config> gas_metering::Rules for ScheduleRules<'a, T> { - fn instruction_cost(&self, instruction: &elements::Instruction) -> Option { - use self::elements::Instruction::*; - let w = &self.schedule.instruction_weights; - let max_params = self.schedule.limits.parameters; - - let weight = match *instruction { - End | Unreachable | Return | Else => 0, - I32Const(_) | I64Const(_) | Block(_) | Loop(_) | Nop | Drop => w.i64const, - I32Load(_, _) | - I32Load8S(_, _) | - I32Load8U(_, _) | - I32Load16S(_, _) | - I32Load16U(_, _) | - I64Load(_, _) | - I64Load8S(_, _) | - I64Load8U(_, _) | - I64Load16S(_, _) | - I64Load16U(_, _) | - I64Load32S(_, _) | - I64Load32U(_, _) => w.i64load, - I32Store(_, _) | - I32Store8(_, _) | - I32Store16(_, _) | - I64Store(_, _) | - I64Store8(_, _) | - I64Store16(_, _) | - I64Store32(_, _) => w.i64store, - Select => w.select, - If(_) => w.r#if, - Br(_) => w.br, - BrIf(_) => w.br_if, - Call(_) => w.call, - GetLocal(_) => w.local_get, - SetLocal(_) => w.local_set, - TeeLocal(_) => w.local_tee, - GetGlobal(_) => w.global_get, - SetGlobal(_) => w.global_set, - CurrentMemory(_) => w.memory_current, - GrowMemory(_) => w.memory_grow, - CallIndirect(idx, _) => *self.params.get(idx as usize).unwrap_or(&max_params), - BrTable(ref data) => w - .br_table - .saturating_add(w.br_table_per_entry.saturating_mul(data.table.len() as u32)), - I32Clz | I64Clz => w.i64clz, - I32Ctz | I64Ctz => w.i64ctz, - I32Popcnt | I64Popcnt => w.i64popcnt, - I32Eqz | I64Eqz => w.i64eqz, - I64ExtendSI32 => w.i64extendsi32, - I64ExtendUI32 => w.i64extendui32, - I32WrapI64 => w.i32wrapi64, - I32Eq | I64Eq => w.i64eq, - I32Ne | I64Ne => w.i64ne, - I32LtS | I64LtS => w.i64lts, - I32LtU | I64LtU => w.i64ltu, - I32GtS | I64GtS => w.i64gts, - I32GtU | I64GtU => w.i64gtu, - I32LeS | I64LeS => w.i64les, - I32LeU | I64LeU => w.i64leu, - I32GeS | I64GeS => w.i64ges, - I32GeU | I64GeU => w.i64geu, - I32Add | I64Add => w.i64add, - I32Sub | I64Sub => w.i64sub, - I32Mul | I64Mul => w.i64mul, - I32DivS | I64DivS => w.i64divs, - I32DivU | I64DivU => w.i64divu, - I32RemS | I64RemS => w.i64rems, - I32RemU | I64RemU => w.i64remu, - I32And | I64And => w.i64and, - I32Or | I64Or => w.i64or, - I32Xor | I64Xor => w.i64xor, - I32Shl | I64Shl => w.i64shl, - I32ShrS | I64ShrS => w.i64shrs, - I32ShrU | I64ShrU => w.i64shru, - I32Rotl | I64Rotl => w.i64rotl, - I32Rotr | I64Rotr => w.i64rotr, - - // Returning None makes the gas instrumentation fail which we intend for - // unsupported or unknown instructions. Offchain we might allow indeterminism and hence - // use the fallback weight for those instructions. - _ if matches!(self.determinism, Determinism::AllowIndeterminism) && w.fallback > 0 => - w.fallback, - _ => return None, - }; - Some(weight) - } - - fn memory_grow_cost(&self) -> gas_metering::MemoryGrowCost { - // We benchmarked the memory.grow instruction with the maximum allowed pages. - // The cost for growing is therefore already included in the instruction cost. - gas_metering::MemoryGrowCost::Free - } - - fn call_per_local_cost(&self) -> u32 { - self.schedule.instruction_weights.call_per_local - } -} - #[cfg(test)] mod test { use super::*; diff --git a/frame/contracts/src/storage.rs b/frame/contracts/src/storage.rs index d40cc755c315..769caef0736f 100644 --- a/frame/contracts/src/storage.rs +++ b/frame/contracts/src/storage.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,15 +20,17 @@ pub mod meter; use crate::{ - exec::{AccountIdOf, StorageKey}, + exec::{AccountIdOf, Key}, weights::WeightInfo, - BalanceOf, CodeHash, Config, ContractInfoOf, DeletionQueue, Error, TrieId, SENTINEL, + AddressGenerator, BalanceOf, CodeHash, Config, ContractInfoOf, DeletionQueue, + DeletionQueueCounter, Error, Pallet, TrieId, SENTINEL, }; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ - dispatch::{DispatchError, DispatchResult}, + dispatch::DispatchError, storage::child::{self, ChildInfo}, weights::Weight, + DefaultNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; use sp_io::KillStorageResult; @@ -36,7 +38,7 @@ use sp_runtime::{ traits::{Hash, Saturating, Zero}, RuntimeDebug, }; -use sp_std::{marker::PhantomData, prelude::*}; +use sp_std::{marker::PhantomData, ops::Deref, prelude::*}; /// Information for managing an account and its sub trie abstraction. /// This is the required info to cache for an account. @@ -45,28 +47,68 @@ use sp_std::{marker::PhantomData, prelude::*}; pub struct ContractInfo { /// Unique ID for the subtree encoded as a bytes vector. pub trie_id: TrieId, + /// The account that holds this contracts storage deposit. + /// + /// This is held in a separate account to prevent the contract from spending it. + deposit_account: DepositAccount, /// The code associated with a given account. pub code_hash: CodeHash, /// How many bytes of storage are accumulated in this contract's child trie. - pub storage_bytes: u32, + storage_bytes: u32, /// How many items of storage are accumulated in this contract's child trie. - pub storage_items: u32, + storage_items: u32, /// This records to how much deposit the accumulated `storage_bytes` amount to. pub storage_byte_deposit: BalanceOf, /// This records to how much deposit the accumulated `storage_items` amount to. - pub storage_item_deposit: BalanceOf, + storage_item_deposit: BalanceOf, /// This records how much deposit is put down in order to pay for the contract itself. /// /// We need to store this information separately so it is not used when calculating any refunds /// since the base deposit can only ever be refunded on contract termination. - pub storage_base_deposit: BalanceOf, + storage_base_deposit: BalanceOf, } impl ContractInfo { + /// Constructs a new contract info **without** writing it to storage. + /// + /// This returns an `Err` if an contract with the supplied `account` already exists + /// in storage. + pub fn new( + account: &AccountIdOf, + nonce: u64, + code_hash: CodeHash, + ) -> Result { + if >::contains_key(account) { + return Err(Error::::DuplicateContract.into()) + } + + let trie_id = { + let buf = (account, nonce).using_encoded(T::Hashing::hash); + buf.as_ref() + .to_vec() + .try_into() + .expect("Runtime uses a reasonable hash size. Hence sizeof(T::Hash) <= 128; qed") + }; + + let deposit_account = DepositAccount(T::AddressGenerator::deposit_address(account)); + + let contract = Self { + trie_id, + deposit_account, + code_hash, + storage_bytes: 0, + storage_items: 0, + storage_byte_deposit: Zero::zero(), + storage_item_deposit: Zero::zero(), + storage_base_deposit: Zero::zero(), + }; + + Ok(contract) + } + /// Associated child trie unique id is built from the hash part of the trie id. - #[cfg(test)] pub fn child_trie_info(&self) -> ChildInfo { - child_trie_info(&self.trie_id[..]) + ChildInfo::new_default(self.trie_id.as_ref()) } /// The deposit paying for the accumulated storage generated within the contract's child trie. @@ -76,79 +118,30 @@ impl ContractInfo { /// Same as [`Self::extra_deposit`] but including the base deposit. pub fn total_deposit(&self) -> BalanceOf { - self.extra_deposit().saturating_add(self.storage_base_deposit) + self.extra_deposit() + .saturating_add(self.storage_base_deposit) + .saturating_sub(Pallet::::min_balance()) } -} - -/// Associated child trie unique id is built from the hash part of the trie id. -fn child_trie_info(trie_id: &[u8]) -> ChildInfo { - ChildInfo::new_default(trie_id) -} -#[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] -pub struct DeletedContract { - pub(crate) trie_id: TrieId, -} - -/// Information about what happended to the pre-existing value when calling [`Storage::write`]. -#[cfg_attr(test, derive(Debug, PartialEq))] -pub enum WriteOutcome { - /// No value existed at the specified key. - New, - /// A value of the returned length was overwritten. - Overwritten(u32), - /// The returned value was taken out of storage before being overwritten. - /// - /// This is only returned when specifically requested because it causes additional work - /// depending on the size of the pre-existing value. When not requested [`Self::Overwritten`] - /// is returned instead. - Taken(Vec), -} - -impl WriteOutcome { - /// Extracts the size of the overwritten value or `0` if there - /// was no value in storage. - pub fn old_len(&self) -> u32 { - match self { - Self::New => 0, - Self::Overwritten(len) => *len, - Self::Taken(value) => value.len() as u32, - } - } - - /// Extracts the size of the overwritten value or `SENTINEL` if there - /// was no value in storage. - /// - /// # Note - /// - /// We cannot use `0` as sentinel value because there could be a zero sized - /// storage entry which is different from a non existing one. - pub fn old_len_with_sentinel(&self) -> u32 { - match self { - Self::New => SENTINEL, - Self::Overwritten(len) => *len, - Self::Taken(value) => value.len() as u32, - } + /// Return the account that storage deposits should be deposited into. + pub fn deposit_account(&self) -> &DepositAccount { + &self.deposit_account } -} - -pub struct Storage(PhantomData); -impl Storage { /// Reads a storage kv pair of a contract. /// /// The read is performed from the `trie_id` only. The `address` is not necessary. If the /// contract doesn't store under the given `key` `None` is returned. - pub fn read>(trie_id: &TrieId, key: &K) -> Option> { - child::get_raw(&child_trie_info(trie_id), key.hash().as_slice()) + pub fn read(&self, key: &Key) -> Option> { + child::get_raw(&self.child_trie_info(), key.hash().as_slice()) } /// Returns `Some(len)` (in bytes) if a storage item exists at `key`. /// /// Returns `None` if the `key` wasn't previously set by `set_storage` or /// was deleted. - pub fn size>(trie_id: &TrieId, key: &K) -> Option { - child::len(&child_trie_info(trie_id), key.hash().as_slice()) + pub fn size(&self, key: &Key) -> Option { + child::len(&self.child_trie_info(), key.hash().as_slice()) } /// Update a storage entry into a contract's kv storage. @@ -158,14 +151,14 @@ impl Storage { /// /// This function also records how much storage was created or removed if a `storage_meter` /// is supplied. It should only be absent for testing or benchmarking code. - pub fn write>( - trie_id: &TrieId, - key: &K, + pub fn write( + &self, + key: &Key, new_value: Option>, storage_meter: Option<&mut meter::NestedMeter>, take: bool, ) -> Result { - let child_trie_info = &child_trie_info(trie_id); + let child_trie_info = &self.child_trie_info(); let hashed_key = key.hash(); let (old_len, old_value) = if take { let val = child::get_raw(child_trie_info, &hashed_key); @@ -208,60 +201,26 @@ impl Storage { }) } - /// Creates a new contract descriptor in the storage with the given code hash at the given - /// address. - /// - /// Returns `Err` if there is already a contract at the given address. - pub fn new_contract( - account: &AccountIdOf, - trie_id: TrieId, - code_hash: CodeHash, - ) -> Result, DispatchError> { - if >::contains_key(account) { - return Err(Error::::DuplicateContract.into()) - } - - let contract = ContractInfo:: { - code_hash, - trie_id, - storage_bytes: 0, - storage_items: 0, - storage_byte_deposit: Zero::zero(), - storage_item_deposit: Zero::zero(), - storage_base_deposit: Zero::zero(), - }; - - Ok(contract) - } - /// Push a contract's trie to the deletion queue for lazy removal. /// /// You must make sure that the contract is also removed when queuing the trie for deletion. - pub fn queue_trie_for_deletion(contract: &ContractInfo) -> DispatchResult { - >::try_append(DeletedContract { trie_id: contract.trie_id.clone() }) - .map_err(|_| >::DeletionQueueFull.into()) + pub fn queue_trie_for_deletion(&self) { + DeletionQueueManager::::load().insert(self.trie_id.clone()); } /// Calculates the weight that is necessary to remove one key from the trie and how many - /// of those keys can be deleted from the deletion queue given the supplied queue length - /// and weight limit. - pub fn deletion_budget(queue_len: usize, weight_limit: Weight) -> (u64, u32) { + /// of those keys can be deleted from the deletion queue given the supplied weight limit. + pub fn deletion_budget(weight_limit: Weight) -> (Weight, u32) { let base_weight = T::WeightInfo::on_process_deletion_queue_batch(); - let weight_per_queue_item = T::WeightInfo::on_initialize_per_queue_item(1) - - T::WeightInfo::on_initialize_per_queue_item(0); - let weight_per_key = (T::WeightInfo::on_initialize_per_trie_key(1) - - T::WeightInfo::on_initialize_per_trie_key(0)) - .ref_time(); - let decoding_weight = weight_per_queue_item.saturating_mul(queue_len as u64); + let weight_per_key = T::WeightInfo::on_initialize_per_trie_key(1) - + T::WeightInfo::on_initialize_per_trie_key(0); // `weight_per_key` being zero makes no sense and would constitute a failure to // benchmark properly. We opt for not removing any keys at all in this case. let key_budget = weight_limit .saturating_sub(base_weight) - .saturating_sub(decoding_weight) - .checked_div(weight_per_key) - .unwrap_or(Weight::zero()) - .ref_time() as u32; + .checked_div_per_component(&weight_per_key) + .unwrap_or(0) as u32; (weight_per_key, key_budget) } @@ -270,13 +229,13 @@ impl Storage { /// /// It returns the amount of weight used for that task. pub fn process_deletion_queue_batch(weight_limit: Weight) -> Weight { - let queue_len = >::decode_len().unwrap_or(0); - if queue_len == 0 { + let mut queue = >::load(); + + if queue.is_empty() { return Weight::zero() } - let (weight_per_key, mut remaining_key_budget) = - Self::deletion_budget(queue_len, weight_limit); + let (weight_per_key, mut remaining_key_budget) = Self::deletion_budget(weight_limit); // We want to check whether we have enough weight to decode the queue before // proceeding. Too little weight for decoding might happen during runtime upgrades @@ -285,55 +244,165 @@ impl Storage { return weight_limit } - let mut queue = >::get(); + while remaining_key_budget > 0 { + let Some(entry) = queue.next() else { break }; - while !queue.is_empty() && remaining_key_budget > 0 { - // Cannot panic due to loop condition - let trie = &mut queue[0]; #[allow(deprecated)] - let outcome = child::kill_storage(&child_trie_info(&trie.trie_id), Some(remaining_key_budget)); - let keys_removed = match outcome { + let outcome = child::kill_storage( + &ChildInfo::new_default(&entry.trie_id), + Some(remaining_key_budget), + ); + + match outcome { // This happens when our budget wasn't large enough to remove all keys. - KillStorageResult::SomeRemaining(c) => c, - KillStorageResult::AllRemoved(c) => { - // We do not care to preserve order. The contract is deleted already and - // no one waits for the trie to be deleted. - queue.swap_remove(0); - c + KillStorageResult::SomeRemaining(_) => return weight_limit, + KillStorageResult::AllRemoved(keys_removed) => { + entry.remove(); + remaining_key_budget = remaining_key_budget.saturating_sub(keys_removed); }, }; - remaining_key_budget = remaining_key_budget.saturating_sub(keys_removed); } - >::put(queue); - let ref_time_weight = weight_limit - .ref_time() - .saturating_sub(weight_per_key.saturating_mul(u64::from(remaining_key_budget))); - Weight::from_ref_time(ref_time_weight) - } - - /// Generates a unique trie id by returning `hash(account_id ++ nonce)`. - pub fn generate_trie_id(account_id: &AccountIdOf, nonce: u64) -> TrieId { - let buf = (account_id, nonce).using_encoded(T::Hashing::hash); - buf.as_ref() - .to_vec() - .try_into() - .expect("Runtime uses a reasonable hash size. Hence sizeof(T::Hash) <= 128; qed") + weight_limit.saturating_sub(weight_per_key.saturating_mul(u64::from(remaining_key_budget))) } /// Returns the code hash of the contract specified by `account` ID. - pub fn code_hash(account: &AccountIdOf) -> Option> { + pub fn load_code_hash(account: &AccountIdOf) -> Option> { >::get(account).map(|i| i.code_hash) } +} + +/// Information about what happened to the pre-existing value when calling [`ContractInfo::write`]. +#[cfg_attr(test, derive(Debug, PartialEq))] +pub enum WriteOutcome { + /// No value existed at the specified key. + New, + /// A value of the returned length was overwritten. + Overwritten(u32), + /// The returned value was taken out of storage before being overwritten. + /// + /// This is only returned when specifically requested because it causes additional work + /// depending on the size of the pre-existing value. When not requested [`Self::Overwritten`] + /// is returned instead. + Taken(Vec), +} + +impl WriteOutcome { + /// Extracts the size of the overwritten value or `0` if there + /// was no value in storage. + pub fn old_len(&self) -> u32 { + match self { + Self::New => 0, + Self::Overwritten(len) => *len, + Self::Taken(value) => value.len() as u32, + } + } + + /// Extracts the size of the overwritten value or `SENTINEL` if there + /// was no value in storage. + /// + /// # Note + /// + /// We cannot use `0` as sentinel value because there could be a zero sized + /// storage entry which is different from a non existing one. + pub fn old_len_with_sentinel(&self) -> u32 { + match self { + Self::New => SENTINEL, + Self::Overwritten(len) => *len, + Self::Taken(value) => value.len() as u32, + } + } +} + +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(T))] +pub struct DepositAccount(AccountIdOf); + +impl Deref for DepositAccount { + type Target = AccountIdOf; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +/// Manage the removal of contracts storage that are marked for deletion. +/// +/// When a contract is deleted by calling `seal_terminate` it becomes inaccessible +/// immediately, but the deletion of the storage items it has accumulated is performed +/// later by pulling the contract from the queue in the `on_idle` hook. +#[derive(Encode, Decode, TypeInfo, MaxEncodedLen, DefaultNoBound, Clone)] +#[scale_info(skip_type_params(T))] +pub struct DeletionQueueManager { + /// Counter used as a key for inserting a new deleted contract in the queue. + /// The counter is incremented after each insertion. + insert_counter: u32, + /// The index used to read the next element to be deleted in the queue. + /// The counter is incremented after each deletion. + delete_counter: u32, + + _phantom: PhantomData, +} + +/// View on a contract that is marked for deletion. +struct DeletionQueueEntry<'a, T: Config> { + /// the trie id of the contract to delete. + trie_id: TrieId, + + /// A mutable reference on the queue so that the contract can be removed, and none can be added + /// or read in the meantime. + queue: &'a mut DeletionQueueManager, +} + +impl<'a, T: Config> DeletionQueueEntry<'a, T> { + /// Remove the contract from the deletion queue. + fn remove(self) { + >::remove(self.queue.delete_counter); + self.queue.delete_counter = self.queue.delete_counter.wrapping_add(1); + >::set(self.queue.clone()); + } +} + +impl DeletionQueueManager { + /// Load the `DeletionQueueCounter`, so we can perform read or write operations on the + /// DeletionQueue storage. + fn load() -> Self { + >::get() + } + + /// Returns `true` if the queue contains no elements. + fn is_empty(&self) -> bool { + self.insert_counter.wrapping_sub(self.delete_counter) == 0 + } + + /// Insert a contract in the deletion queue. + fn insert(&mut self, trie_id: TrieId) { + >::insert(self.insert_counter, trie_id); + self.insert_counter = self.insert_counter.wrapping_add(1); + >::set(self.clone()); + } + + /// Fetch the next contract to be deleted. + /// + /// Note: + /// we use the delete counter to get the next value to read from the queue and thus don't pay + /// the cost of an extra call to `sp_io::storage::next_key` to lookup the next entry in the map + fn next(&mut self) -> Option> { + if self.is_empty() { + return None + } + + let entry = >::get(self.delete_counter); + entry.map(|trie_id| DeletionQueueEntry { trie_id, queue: self }) + } +} - /// Fill up the queue in order to exercise the limits during testing. - #[cfg(test)] - pub fn fill_queue_with_dummies() { - use frame_support::{traits::Get, BoundedVec}; - let queue: Vec = (0..T::DeletionQueueDepth::get()) - .map(|_| DeletedContract { trie_id: TrieId::default() }) - .collect(); - let bounded: BoundedVec<_, _> = queue.try_into().map_err(|_| ()).unwrap(); - >::put(bounded); +#[cfg(test)] +impl DeletionQueueManager { + pub fn from_test_values(insert_counter: u32, delete_counter: u32) -> Self { + Self { insert_counter, delete_counter, _phantom: Default::default() } + } + pub fn as_test_tuple(&self) -> (u32, u32) { + (self.insert_counter, self.delete_counter) } } diff --git a/frame/contracts/src/storage/meter.rs b/frame/contracts/src/storage/meter.rs index 94bca741af66..e5caa68a8848 100644 --- a/frame/contracts/src/storage/meter.rs +++ b/frame/contracts/src/storage/meter.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,14 +17,17 @@ //! This module contains functions to meter the storage deposit. -use crate::{storage::ContractInfo, BalanceOf, Config, Error, Inspect, Pallet}; +use crate::{ + storage::{ContractInfo, DepositAccount}, + BalanceOf, Config, Error, Inspect, Origin, Pallet, System, +}; use codec::Encode; use frame_support::{ - dispatch::DispatchError, + dispatch::{fmt::Debug, DispatchError}, ensure, traits::{ - tokens::{BalanceStatus, WithdrawConsequence}, - Currency, ExistenceRequirement, Get, ReservableCurrency, + tokens::{Fortitude::Polite, Preservation::Protect, WithdrawConsequence}, + Currency, ExistenceRequirement, Get, }, DefaultNoBound, RuntimeDebugNoBound, }; @@ -72,22 +75,22 @@ pub trait Ext { /// This is called to inform the implementer that some balance should be charged due to /// some interaction of the `origin` with a `contract`. /// - /// The balance transfer can either flow from `origin` to `contract` or the other way + /// The balance transfer can either flow from `origin` to `deposit_account` or the other way /// around depending on whether `amount` constitutes a `Charge` or a `Refund`. - /// It is guaranteed that that this succeeds because no more balance than returned by + /// It is guaranteed that this succeeds because no more balance than returned by /// `check_limit` is ever charged. This is why this function is infallible. /// `terminated` designates whether the `contract` was terminated. fn charge( origin: &T::AccountId, - contract: &T::AccountId, + deposit_account: &DepositAccount, amount: &DepositOf, terminated: bool, - ); + ) -> Result<(), DispatchError>; } /// This [`Ext`] is used for actual on-chain execution when balance needs to be charged. /// -/// It uses [`ReservableCurrency`] in order to do accomplish the reserves. +/// It uses [`frame_support::traits::ReservableCurrency`] in order to do accomplish the reserves. pub enum ReservingExt {} /// Used to implement a type state pattern for the meter. @@ -96,17 +99,24 @@ pub enum ReservingExt {} pub trait State: private::Sealed {} /// State parameter that constitutes a meter that is in its root state. -pub enum Root {} +#[derive(Default, Debug)] +pub struct Root; /// State parameter that constitutes a meter that is in its nested state. -pub enum Nested {} +/// Its value indicates whether the nested meter has its own limit. +#[derive(DefaultNoBound, RuntimeDebugNoBound)] +pub enum Nested { + #[default] + DerivedLimit, + OwnLimit, +} impl State for Root {} impl State for Nested {} /// A type that allows the metering of consumed or freed storage of a single contract call stack. #[derive(DefaultNoBound, RuntimeDebugNoBound)] -pub struct RawMeter { +pub struct RawMeter { /// The limit of how much balance this meter is allowed to consume. limit: BalanceOf, /// The amount of balance that was used in this meter and all of its already absorbed children. @@ -118,8 +128,10 @@ pub struct RawMeter { /// We only have one charge per contract hence the size of this vector is /// limited by the maximum call depth. charges: Vec>, - /// Type parameters are only used in impls. - _phantom: PhantomData<(E, S)>, + /// We store the nested state to determine if it has a special limit for sub-call. + nested: S, + /// Type parameter only used in impls. + _phantom: PhantomData, } /// This type is used to describe a storage change when charging from the meter. @@ -214,9 +226,12 @@ impl Diff { /// this we can do all the refunds before doing any charge. This way a plain account can use /// more deposit than it has balance as along as it is covered by a refund. This /// essentially makes the order of storage changes irrelevant with regard to the deposit system. +/// The only exception is when a special (tougher) deposit limit is specified for a cross-contract +/// call. In that case the limit is enforced once the call is returned, rolling it back if +/// exhausted. #[derive(RuntimeDebugNoBound, Clone)] struct Charge { - contract: T::AccountId, + deposit_account: DepositAccount, amount: DepositOf, terminated: bool, } @@ -255,23 +270,31 @@ impl RawMeter where T: Config, E: Ext, - S: State, + S: State + Default + Debug, { - /// Create a new child that has its `limit` set to whatever is remaining of it. + /// Create a new child that has its `limit`. + /// Passing `0` as the limit is interpreted as to take whatever is remaining from its parent. /// /// This is called whenever a new subcall is initiated in order to track the storage /// usage for this sub call separately. This is necessary because we want to exchange balance /// with the current contract we are interacting with. - pub fn nested(&self) -> RawMeter { + pub fn nested(&self, limit: BalanceOf) -> RawMeter { debug_assert!(self.is_alive()); - RawMeter { limit: self.available(), ..Default::default() } + // If a special limit is specified higher than it is available, + // we want to enforce the lesser limit to the nested meter, to fail in the sub-call. + let limit = self.available().min(limit); + if limit.is_zero() { + RawMeter { limit: self.available(), ..Default::default() } + } else { + RawMeter { limit, nested: Nested::OwnLimit, ..Default::default() } + } } /// Absorb a child that was spawned to handle a sub call. /// /// This should be called whenever a sub call comes to its end and it is **not** reverted. - /// This does the actual balance transfer from/to `origin` and `contract` based on the overall - /// storage consumption of the call. It also updates the supplied contract info. + /// This does the actual balance transfer from/to `origin` and `deposit_account` based on the + /// overall storage consumption of the call. It also updates the supplied contract info. /// /// In case a contract reverted the child meter should just be dropped in order to revert /// any changes it recorded. @@ -280,12 +303,12 @@ where /// /// - `absorbed`: The child storage meter that should be absorbed. /// - `origin`: The origin that spawned the original root meter. - /// - `contract`: The contract that this sub call belongs to. + /// - `deposit_account`: The contract's deposit account that this sub call belongs to. /// - `info`: The info of the contract in question. `None` if the contract was terminated. pub fn absorb( &mut self, absorbed: RawMeter, - contract: &T::AccountId, + deposit_account: DepositAccount, info: Option<&mut ContractInfo>, ) { let own_deposit = absorbed.own_contribution.update_contract(info); @@ -293,10 +316,10 @@ where .total_deposit .saturating_add(&absorbed.total_deposit) .saturating_add(&own_deposit); + self.charges.extend_from_slice(&absorbed.charges); if !own_deposit.is_zero() { - self.charges.extend_from_slice(&absorbed.charges); self.charges.push(Charge { - contract: contract.clone(), + deposit_account, amount: own_deposit, terminated: absorbed.is_terminated(), }); @@ -329,12 +352,21 @@ where /// /// This tries to [`Ext::check_limit`] on `origin` and fails if this is not possible. pub fn new( - origin: &T::AccountId, + origin: &Origin, limit: Option>, min_leftover: BalanceOf, ) -> Result { - let limit = E::check_limit(origin, limit, min_leftover)?; - Ok(Self { limit, ..Default::default() }) + // Check the limit only if the origin is not root. + return match origin { + Origin::Root => Ok(Self { + limit: limit.unwrap_or(T::DefaultDepositLimit::get()), + ..Default::default() + }), + Origin::Signed(o) => { + let limit = E::check_limit(o, limit, min_leftover)?; + Ok(Self { limit, ..Default::default() }) + }, + } } /// The total amount of deposit that should change hands as result of the execution @@ -343,14 +375,20 @@ where /// /// This drops the root meter in order to make sure it is only called when the whole /// execution did finish. - pub fn into_deposit(self, origin: &T::AccountId) -> DepositOf { + + pub fn try_into_deposit(self, origin: &Origin) -> Result, DispatchError> { + // Only refund or charge deposit if the origin is not root. + let origin = match origin { + Origin::Root => return Ok(Deposit::Charge(Zero::zero())), + Origin::Signed(o) => o, + }; for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Refund(_))) { - E::charge(origin, &charge.contract, &charge.amount, charge.terminated); + E::charge(origin, &charge.deposit_account, &charge.amount, charge.terminated)?; } for charge in self.charges.iter().filter(|c| matches!(c.amount, Deposit::Charge(_))) { - E::charge(origin, &charge.contract, &charge.amount, charge.terminated); + E::charge(origin, &charge.deposit_account, &charge.amount, charge.terminated)?; } - self.total_deposit + Ok(self.total_deposit) } } @@ -360,9 +398,8 @@ where T: Config, E: Ext, { - /// Try to charge the `diff` from the meter. Fails if this would exceed the original limit. + /// Charge `diff` from the meter. pub fn charge(&mut self, diff: &Diff) { - debug_assert!(self.is_alive()); match &mut self.own_contribution { Contribution::Alive(own) => *own = own.saturating_add(diff), _ => panic!("Charge is never called after termination; qed"), @@ -379,13 +416,16 @@ where info: &mut ContractInfo, ) -> Result, DispatchError> { debug_assert!(self.is_alive()); + + let ed = Pallet::::min_balance(); let mut deposit = Diff { bytes_added: info.encoded_size() as u32, items_added: 1, ..Default::default() } .update_contract::(None); - // Instantiate needs to transfer the minimum balance at least in order to pull the - // contract's account into existence. - deposit = deposit.max(Deposit::Charge(Pallet::::min_balance())); + // Instantiate needs to transfer at least the minimum balance in order to pull the + // deposit account into existence. + // We also add another `ed` here which goes to the contract's own account into existence. + deposit = deposit.max(Deposit::Charge(ed)).saturating_add(&Deposit::Charge(ed)); if deposit.charge_or_zero() > self.limit { return Err(>::StorageDepositLimitExhausted.into()) } @@ -394,11 +434,23 @@ where // contract execution does conclude and hence would lead to a double charge. self.total_deposit = deposit.clone(); info.storage_base_deposit = deposit.charge_or_zero(); - if !deposit.is_zero() { - // We need to charge immediately so that the account is created before the `value` - // is transferred from the caller to the contract. - E::charge(origin, contract, &deposit, false); - } + + // Normally, deposit charges are deferred to be able to coalesce them with refunds. + // However, we need to charge immediately so that the account is created before + // charges possibly below the ed are collected and fail. + E::charge( + origin, + info.deposit_account(), + &deposit.saturating_sub(&Deposit::Charge(ed)), + false, + )?; + + System::::inc_consumers(info.deposit_account())?; + + // We also need to make sure that the contract's account itself exists. + T::Currency::transfer(origin, contract, ed, ExistenceRequirement::KeepAlive)?; + System::::inc_consumers(contract)?; + Ok(deposit) } @@ -416,8 +468,10 @@ where /// /// # Note /// - /// We only need to call this **once** for every call stack and not for every cross contract - /// call. Hence this is only called when the last call frame returns. + /// We normally need to call this **once** for every call stack and not for every cross contract + /// call. However, if a dedicated limit is specified for a sub-call, this needs to be called + /// once the sub-call has returned. For this, the [`Self::enforce_subcall_limit`] wrapper is + /// used. pub fn enforce_limit( &mut self, info: Option<&mut ContractInfo>, @@ -435,6 +489,18 @@ where } Ok(()) } + + /// This is a wrapper around [`Self::enforce_limit`] to use on the exit from a sub-call to + /// enforce its special limit if needed. + pub fn enforce_subcall_limit( + &mut self, + info: Option<&mut ContractInfo>, + ) -> Result<(), DispatchError> { + match self.nested { + Nested::OwnLimit => self.enforce_limit(info), + Nested::DerivedLimit => Ok(()), + } + } } impl Ext for ReservingExt { @@ -443,8 +509,14 @@ impl Ext for ReservingExt { limit: Option>, min_leftover: BalanceOf, ) -> Result, DispatchError> { - let max = T::Currency::reducible_balance(origin, true).saturating_sub(min_leftover); - let limit = limit.unwrap_or(max); + // We are sending the `min_leftover` and the `min_balance` from the origin + // account as part of a contract call. Hence origin needs to have those left over + // as free balance after accounting for all deposits. + let max = T::Currency::reducible_balance(origin, Protect, Polite) + .saturating_sub(min_leftover) + .saturating_sub(Pallet::::min_balance()); + let default = max.min(T::DefaultDepositLimit::get()); + let limit = limit.unwrap_or(default); ensure!( limit <= max && matches!(T::Currency::can_withdraw(origin, limit), WithdrawConsequence::Success), @@ -455,74 +527,30 @@ impl Ext for ReservingExt { fn charge( origin: &T::AccountId, - contract: &T::AccountId, + deposit_account: &DepositAccount, amount: &DepositOf, terminated: bool, - ) { - // There is nothing we can do when this fails as this constitutes a bug in the runtime: - // Either the runtime does not hold up the invariant of never deleting a contract's account - // or it does not honor reserved balances. We need to settle for emitting an error log - // in this case. + ) -> Result<(), DispatchError> { match amount { - Deposit::Charge(amount) => { - // This will never fail because a contract's account is required to exist - // at all times. The pallet enforces this invariant by depositing at least the - // existential deposit when instantiating and never refunds it unless the contract - // is removed. This means the receiver always exists except when instantiating a - // contract. In this case we made sure that at least the existential deposit is - // sent. The sender always has enough balance because we checked that it had enough - // balance when instantiating the storage meter. - let result = T::Currency::transfer( + Deposit::Charge(amount) => T::Currency::transfer( + origin, + deposit_account, + *amount, + ExistenceRequirement::KeepAlive, + ), + Deposit::Refund(amount) => { + if terminated { + System::::dec_consumers(&deposit_account); + } + T::Currency::transfer( + deposit_account, origin, - contract, *amount, - ExistenceRequirement::KeepAlive, + // We can safely use `AllowDeath` because our own consumer prevents an removal. + ExistenceRequirement::AllowDeath, ) - .and_then(|_| T::Currency::reserve(contract, *amount)); - if let Err(err) = result { - log::error!( - target: "runtime::contracts", - "Failed to transfer storage deposit {:?} from origin {:?} to contract {:?}: {:?}", - amount, origin, contract, err, - ); - if cfg!(debug_assertions) { - panic!("Unable to collect storage deposit. This is a bug."); - } - } }, - // For `Refund(_)` no error happen because the initial value transfer from the - // origin to the contract has a keep alive existence requirement and when reserving we - // make sure to leave at least the ed in the free balance. Therefore the receiver always - // exists because there is no way for it to be removed in between. The sender always has - // enough reserved balance because we track it in the `ContractInfo` and never send more - // back than we have. - Deposit::Refund(amount) => { - let amount = if terminated { - *amount - } else { - // This is necessary when the `storage_deposit` tracked inside the account - // info is out of sync with the actual balance. That can only happen due to - // slashing. We make sure to never dust the contract's account through a - // refund because we consider this unexpected behaviour. - *amount.min( - &T::Currency::reserved_balance(contract) - .saturating_sub(Pallet::::min_balance()), - ) - }; - let result = - T::Currency::repatriate_reserved(contract, origin, amount, BalanceStatus::Free); - if matches!(result, Ok(val) if !val.is_zero()) || matches!(result, Err(_)) { - log::error!( - target: "runtime::contracts", - "Failed to repatriate storage deposit {:?} from contract {:?} to origin {:?}: {:?}", - amount, contract, origin, result, - ); - if cfg!(debug_assertions) { - panic!("Unable to refund storage deposit. This is a bug."); - } - } - }, - }; + } } } @@ -558,7 +586,7 @@ mod tests { #[derive(Debug, PartialEq, Eq, Clone)] struct Charge { origin: AccountIdOf, - contract: AccountIdOf, + contract: DepositAccount, amount: DepositOf, terminated: bool, } @@ -592,10 +620,10 @@ mod tests { fn charge( origin: &AccountIdOf, - contract: &AccountIdOf, + contract: &DepositAccount, amount: &DepositOf, terminated: bool, - ) { + ) -> Result<(), DispatchError> { TestExtTestValue::mutate(|ext| { ext.charges.push(Charge { origin: origin.clone(), @@ -604,6 +632,7 @@ mod tests { terminated, }) }); + Ok(()) } } @@ -611,6 +640,12 @@ mod tests { TestExtTestValue::mutate(|ext| ext.clear()) } + struct ChargingTestCase { + origin: Origin, + deposit: DepositOf, + expected: TestExt, + } + #[derive(Default)] struct StorageInfo { bytes: u32, @@ -620,12 +655,10 @@ mod tests { } fn new_info(info: StorageInfo) -> ContractInfo { - use crate::storage::Storage; - use sp_runtime::traits::Hash; - ContractInfo:: { - trie_id: >::generate_trie_id(&ALICE, 42), - code_hash: ::Hashing::hash(b"42"), + trie_id: Default::default(), + deposit_account: DepositAccount([0u8; 32].into()), + code_hash: Default::default(), storage_bytes: info.bytes, storage_items: info.items, storage_byte_deposit: info.bytes_deposit, @@ -638,7 +671,7 @@ mod tests { fn new_reserves_balance_works() { clear_ext(); - TestMeter::new(&ALICE, Some(1_000), 0).unwrap(); + TestMeter::new(&Origin::from_account_id(ALICE), Some(1_000), 0).unwrap(); assert_eq!( TestExtTestValue::get(), @@ -653,13 +686,13 @@ mod tests { fn empty_charge_works() { clear_ext(); - let mut meter = TestMeter::new(&ALICE, Some(1_000), 0).unwrap(); + let mut meter = TestMeter::new(&Origin::from_account_id(ALICE), Some(1_000), 0).unwrap(); assert_eq!(meter.available(), 1_000); // an empty charge does not create a `Charge` entry - let mut nested0 = meter.nested(); + let mut nested0 = meter.nested(BalanceOf::::zero()); nested0.charge(&Default::default()); - meter.absorb(nested0, &BOB, None); + meter.absorb(nested0, DepositAccount(BOB), None); assert_eq!( TestExtTestValue::get(), @@ -672,118 +705,158 @@ mod tests { #[test] fn charging_works() { - clear_ext(); + let test_cases = vec![ + ChargingTestCase { + origin: Origin::::from_account_id(ALICE), + deposit: Deposit::Refund(28), + expected: TestExt { + limit_checks: vec![LimitCheck { origin: ALICE, limit: 100, min_leftover: 0 }], + charges: vec![ + Charge { + origin: ALICE, + contract: DepositAccount(CHARLIE), + amount: Deposit::Refund(10), + terminated: false, + }, + Charge { + origin: ALICE, + contract: DepositAccount(CHARLIE), + amount: Deposit::Refund(20), + terminated: false, + }, + Charge { + origin: ALICE, + contract: DepositAccount(BOB), + amount: Deposit::Charge(2), + terminated: false, + }, + ], + }, + }, + ChargingTestCase { + origin: Origin::::Root, + deposit: Deposit::Charge(0), + expected: TestExt { limit_checks: vec![], charges: vec![] }, + }, + ]; - let mut meter = TestMeter::new(&ALICE, Some(100), 0).unwrap(); - assert_eq!(meter.available(), 100); - - let mut nested0_info = - new_info(StorageInfo { bytes: 100, items: 5, bytes_deposit: 100, items_deposit: 10 }); - let mut nested0 = meter.nested(); - nested0.charge(&Diff { - bytes_added: 108, - bytes_removed: 5, - items_added: 1, - items_removed: 2, - }); - nested0.charge(&Diff { bytes_removed: 99, ..Default::default() }); - - let mut nested1_info = - new_info(StorageInfo { bytes: 100, items: 10, bytes_deposit: 100, items_deposit: 20 }); - let mut nested1 = nested0.nested(); - nested1.charge(&Diff { items_removed: 5, ..Default::default() }); - nested0.absorb(nested1, &CHARLIE, Some(&mut nested1_info)); - - let mut nested2_info = - new_info(StorageInfo { bytes: 100, items: 7, bytes_deposit: 100, items_deposit: 20 }); - let mut nested2 = nested0.nested(); - nested2.charge(&Diff { items_removed: 7, ..Default::default() }); - nested0.absorb(nested2, &CHARLIE, Some(&mut nested2_info)); - - nested0.enforce_limit(Some(&mut nested0_info)).unwrap(); - meter.absorb(nested0, &BOB, Some(&mut nested0_info)); - - meter.into_deposit(&ALICE); - - assert_eq!(nested0_info.extra_deposit(), 112); - assert_eq!(nested1_info.extra_deposit(), 110); - assert_eq!(nested2_info.extra_deposit(), 100); + for test_case in test_cases { + clear_ext(); - assert_eq!( - TestExtTestValue::get(), - TestExt { - limit_checks: vec![LimitCheck { origin: ALICE, limit: 100, min_leftover: 0 }], - charges: vec![ - Charge { - origin: ALICE, - contract: CHARLIE, - amount: Deposit::Refund(10), - terminated: false - }, - Charge { - origin: ALICE, - contract: CHARLIE, - amount: Deposit::Refund(20), - terminated: false - }, - Charge { - origin: ALICE, - contract: BOB, - amount: Deposit::Charge(2), - terminated: false - } - ] - } - ) + let mut meter = TestMeter::new(&test_case.origin, Some(100), 0).unwrap(); + assert_eq!(meter.available(), 100); + + let mut nested0_info = new_info(StorageInfo { + bytes: 100, + items: 5, + bytes_deposit: 100, + items_deposit: 10, + }); + let mut nested0 = meter.nested(BalanceOf::::zero()); + nested0.charge(&Diff { + bytes_added: 108, + bytes_removed: 5, + items_added: 1, + items_removed: 2, + }); + nested0.charge(&Diff { bytes_removed: 99, ..Default::default() }); + + let mut nested1_info = new_info(StorageInfo { + bytes: 100, + items: 10, + bytes_deposit: 100, + items_deposit: 20, + }); + let mut nested1 = nested0.nested(BalanceOf::::zero()); + nested1.charge(&Diff { items_removed: 5, ..Default::default() }); + nested0.absorb(nested1, DepositAccount(CHARLIE), Some(&mut nested1_info)); + + let mut nested2_info = new_info(StorageInfo { + bytes: 100, + items: 7, + bytes_deposit: 100, + items_deposit: 20, + }); + let mut nested2 = nested0.nested(BalanceOf::::zero()); + nested2.charge(&Diff { items_removed: 7, ..Default::default() }); + nested0.absorb(nested2, DepositAccount(CHARLIE), Some(&mut nested2_info)); + + nested0.enforce_limit(Some(&mut nested0_info)).unwrap(); + meter.absorb(nested0, DepositAccount(BOB), Some(&mut nested0_info)); + + assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit); + + assert_eq!(nested0_info.extra_deposit(), 112); + assert_eq!(nested1_info.extra_deposit(), 110); + assert_eq!(nested2_info.extra_deposit(), 100); + + assert_eq!(TestExtTestValue::get(), test_case.expected) + } } #[test] fn termination_works() { - clear_ext(); + let test_cases = vec![ + ChargingTestCase { + origin: Origin::::from_account_id(ALICE), + deposit: Deposit::Refund(107), + expected: TestExt { + limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }], + charges: vec![ + Charge { + origin: ALICE, + contract: DepositAccount(CHARLIE), + amount: Deposit::Refund(119), + terminated: true, + }, + Charge { + origin: ALICE, + contract: DepositAccount(BOB), + amount: Deposit::Charge(12), + terminated: false, + }, + ], + }, + }, + ChargingTestCase { + origin: Origin::::Root, + deposit: Deposit::Charge(0), + expected: TestExt { limit_checks: vec![], charges: vec![] }, + }, + ]; - let mut meter = TestMeter::new(&ALICE, Some(1_000), 0).unwrap(); - assert_eq!(meter.available(), 1_000); + for test_case in test_cases { + clear_ext(); - let mut nested0 = meter.nested(); - nested0.charge(&Diff { - bytes_added: 5, - bytes_removed: 1, - items_added: 3, - items_removed: 1, - }); - nested0.charge(&Diff { items_added: 2, ..Default::default() }); - - let mut nested1_info = - new_info(StorageInfo { bytes: 100, items: 10, bytes_deposit: 100, items_deposit: 20 }); - let mut nested1 = nested0.nested(); - nested1.charge(&Diff { items_removed: 5, ..Default::default() }); - nested1.charge(&Diff { bytes_added: 20, ..Default::default() }); - nested1.terminate(&nested1_info); - nested0.enforce_limit(Some(&mut nested1_info)).unwrap(); - nested0.absorb(nested1, &CHARLIE, None); - - meter.absorb(nested0, &BOB, None); - meter.into_deposit(&ALICE); + let mut meter = TestMeter::new(&test_case.origin, Some(1_000), 0).unwrap(); + assert_eq!(meter.available(), 1_000); - assert_eq!( - TestExtTestValue::get(), - TestExt { - limit_checks: vec![LimitCheck { origin: ALICE, limit: 1_000, min_leftover: 0 }], - charges: vec![ - Charge { - origin: ALICE, - contract: CHARLIE, - amount: Deposit::Refund(120), - terminated: true - }, - Charge { - origin: ALICE, - contract: BOB, - amount: Deposit::Charge(12), - terminated: false - } - ] - } - ) + let mut nested0 = meter.nested(BalanceOf::::zero()); + nested0.charge(&Diff { + bytes_added: 5, + bytes_removed: 1, + items_added: 3, + items_removed: 1, + }); + nested0.charge(&Diff { items_added: 2, ..Default::default() }); + + let mut nested1_info = new_info(StorageInfo { + bytes: 100, + items: 10, + bytes_deposit: 100, + items_deposit: 20, + }); + let mut nested1 = nested0.nested(BalanceOf::::zero()); + nested1.charge(&Diff { items_removed: 5, ..Default::default() }); + nested1.charge(&Diff { bytes_added: 20, ..Default::default() }); + nested1.terminate(&nested1_info); + nested0.enforce_limit(Some(&mut nested1_info)).unwrap(); + nested0.absorb(nested1, DepositAccount(CHARLIE), None); + + meter.absorb(nested0, DepositAccount(BOB), None); + assert_eq!(meter.try_into_deposit(&test_case.origin).unwrap(), test_case.deposit); + + assert_eq!(TestExtTestValue::get(), test_case.expected) + } } } diff --git a/frame/contracts/src/tests.rs b/frame/contracts/src/tests.rs index ce84da743d11..b25487469ba0 100644 --- a/frame/contracts/src/tests.rs +++ b/frame/contracts/src/tests.rs @@ -1,6 +1,7 @@ // This file is part of Substrate. +mod pallet_dummy; -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,71 +16,83 @@ // See the License for the specific language governing permissions and // limitations under the License. -use self::test_utils::hash; +use self::test_utils::{ensure_stored, expected_deposit, hash}; +use crate as pallet_contracts; use crate::{ chain_extension::{ ChainExtension, Environment, Ext, InitState, RegisteredChainExtension, Result as ExtensionResult, RetVal, ReturnFlags, SysConfig, }, - exec::{FixSizedKey, Frame}, - storage::Storage, + exec::{Frame, Key}, + storage::DeletionQueueManager, tests::test_utils::{get_contract, get_contract_checked}, - wasm::{Determinism, PrefabWasmModule, ReturnCode as RuntimeReturnCode}, + wasm::{Determinism, ReturnCode as RuntimeReturnCode}, weights::WeightInfo, - BalanceOf, Code, CodeStorage, Config, ContractInfoOf, DefaultAddressGenerator, DeletionQueue, - Error, Pallet, Schedule, + BalanceOf, Code, CollectEvents, Config, ContractInfo, ContractInfoOf, DebugInfo, + DefaultAddressGenerator, DeletionQueueCounter, Error, MigrationInProgress, NoopMigration, + Origin, Pallet, PristineCode, Schedule, }; use assert_matches::assert_matches; use codec::Encode; use frame_support::{ - assert_err, assert_err_ignore_postinfo, assert_noop, assert_ok, - dispatch::{DispatchClass, DispatchErrorWithPostInfo, PostDispatchInfo}, + assert_err, assert_err_ignore_postinfo, assert_err_with_weight, assert_noop, assert_ok, + dispatch::{DispatchError, DispatchErrorWithPostInfo, PostDispatchInfo}, parameter_types, storage::child, traits::{ - BalanceStatus, ConstU32, ConstU64, Contains, Currency, Get, LockableCurrency, OnIdle, - OnInitialize, ReservableCurrency, WithdrawReasons, + ConstU32, ConstU64, Contains, Currency, ExistenceRequirement, LockableCurrency, OnIdle, + OnInitialize, StorageVersion, WithdrawReasons, }, weights::{constants::WEIGHT_REF_TIME_PER_SECOND, Weight}, }; -use frame_system::{self as system, EventRecord, Phase}; +use frame_system::{EventRecord, Phase}; use pretty_assertions::{assert_eq, assert_ne}; +use sp_core::ByteArray; use sp_io::hashing::blake2_256; -use sp_keystore::{testing::KeyStore, KeystoreExt}; +use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::{ - testing::{Header, H256}, + testing::H256, traits::{BlakeTwo256, Convert, Hash, IdentityLookup}, - AccountId32, + AccountId32, BuildStorage, TokenError, }; -use std::sync::Arc; - -use crate as pallet_contracts; +use std::ops::Deref; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, - Randomness: pallet_randomness_collective_flip::{Pallet, Storage}, + Randomness: pallet_insecure_randomness_collective_flip::{Pallet, Storage}, Utility: pallet_utility::{Pallet, Call, Storage, Event}, Contracts: pallet_contracts::{Pallet, Call, Storage, Event}, + Proxy: pallet_proxy::{Pallet, Call, Storage, Event}, + Dummy: pallet_dummy } ); -#[macro_use] +macro_rules! assert_return_code { + ( $x:expr , $y:expr $(,)? ) => {{ + assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32); + }}; +} + +macro_rules! assert_refcount { + ( $code_hash:expr , $should:expr $(,)? ) => {{ + let is = crate::CodeInfoOf::::get($code_hash).map(|m| m.refcount()).unwrap(); + assert_eq!(is, $should); + }}; +} + pub mod test_utils { - use super::{Balances, Hash, SysConfig, Test}; + use super::{Balances, DepositPerByte, DepositPerItem, Hash, SysConfig, Test}; use crate::{ - exec::AccountIdOf, storage::Storage, CodeHash, Config, ContractInfo, ContractInfoOf, Nonce, + exec::AccountIdOf, CodeHash, CodeInfo, CodeInfoOf, Config, ContractInfo, ContractInfoOf, + Nonce, PristineCode, }; - use codec::Encode; + use codec::{Encode, MaxEncodedLen}; use frame_support::traits::Currency; pub fn place_contract(address: &AccountIdOf, code_hash: CodeHash) { @@ -87,9 +100,8 @@ pub mod test_utils { *counter += 1; *counter }); - let trie_id = Storage::::generate_trie_id(address, nonce); set_balance(address, ::Currency::minimum_balance() * 10); - let contract = Storage::::new_contract(&address, trie_id, code_hash).unwrap(); + let contract = >::new(&address, nonce, code_hash).unwrap(); >::insert(address, contract); } pub fn set_balance(who: &AccountIdOf, amount: u64) { @@ -108,17 +120,19 @@ pub mod test_utils { pub fn hash(s: &S) -> <::Hashing as Hash>::Output { <::Hashing as Hash>::hash_of(s) } - macro_rules! assert_return_code { - ( $x:expr , $y:expr $(,)? ) => {{ - assert_eq!(u32::from_le_bytes($x.data[..].try_into().unwrap()), $y as u32); - }}; + pub fn expected_deposit(code_len: usize) -> u64 { + // For code_info, the deposit for max_encoded_len is taken. + let code_info_len = CodeInfo::::max_encoded_len() as u64; + // Calculate deposit to be reserved. + // We add 2 storage items: one for code, other for code_info + DepositPerByte::get().saturating_mul(code_len as u64 + code_info_len) + + DepositPerItem::get().saturating_mul(2) } - - macro_rules! assert_refcount { - ( $code_hash:expr , $should:expr $(,)? ) => {{ - let is = crate::OwnerInfoOf::::get($code_hash).map(|m| m.refcount()).unwrap(); - assert_eq!(is, $should); - }}; + pub fn ensure_stored(code_hash: CodeHash) -> usize { + // Assert that code_info is stored + assert!(CodeInfoOf::::contains_key(&code_hash)); + // Assert that contract code is stored, and get its size. + PristineCode::::try_get(&code_hash).unwrap().len() } } @@ -175,6 +189,8 @@ impl ChainExtension for TestExtension { where E: Ext, { + use codec::Decode; + let func_id = env.func_id(); let id = env.ext_id() as u32 | func_id as u32; match func_id { @@ -194,7 +210,11 @@ impl ChainExtension for TestExtension { }, 2 => { let mut env = env.buf_in_buf_out(); - let weight = Weight::from_ref_time(env.read(5)?[4].into()); + let mut enc = &env.read(9)?[4..8]; + let weight = Weight::from_parts( + u32::decode(&mut enc).map_err(|_| Error::::ContractTrapped)?.into(), + 0, + ); env.charge_weight(weight)?; Ok(RetVal::Converging(id)) }, @@ -291,14 +311,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = AccountId32; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -311,7 +330,7 @@ impl frame_system::Config for Test { type OnSetCode = (); type MaxConsumers = frame_support::traits::ConstU32<16>; } -impl pallet_randomness_collective_flip::Config for Test {} +impl pallet_insecure_randomness_collective_flip::Config for Test {} impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); @@ -322,6 +341,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } impl pallet_timestamp::Config for Test { @@ -336,14 +359,33 @@ impl pallet_utility::Config for Test { type PalletsOrigin = OriginCaller; type WeightInfo = (); } + +impl pallet_proxy::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = (); + type ProxyDepositBase = ConstU64<1>; + type ProxyDepositFactor = ConstU64<1>; + type MaxProxies = ConstU32<32>; + type WeightInfo = (); + type MaxPending = ConstU32<32>; + type CallHasher = BlakeTwo256; + type AnnouncementDepositBase = ConstU64<1>; + type AnnouncementDepositFactor = ConstU64<1>; +} + +impl pallet_dummy::Config for Test {} + parameter_types! { pub MySchedule: Schedule = { - let mut schedule = >::default(); - schedule.instruction_weights.fallback = 1; + let schedule = >::default(); schedule }; pub static DepositPerByte: BalanceOf = 1; pub const DepositPerItem: BalanceOf = 2; + // We need this one set high enough for running benchmarks. + pub static DefaultDepositLimit: BalanceOf = 10_000_000; } impl Convert> for Test { @@ -383,7 +425,6 @@ impl Contains for TestFilter { } parameter_types! { - pub const DeletionWeightLimit: Weight = Weight::from_ref_time(500_000_000_000); pub static UnstableInterface: bool = true; } @@ -399,16 +440,16 @@ impl Config for Test { type WeightInfo = (); type ChainExtension = (TestExtension, DisabledExtension, RevertingExtension, TempStorageExtension); - type DeletionQueueDepth = ConstU32<1024>; - type DeletionWeightLimit = DeletionWeightLimit; type Schedule = MySchedule; type DepositPerByte = DepositPerByte; type DepositPerItem = DepositPerItem; + type DefaultDepositLimit = DefaultDepositLimit; type AddressGenerator = DefaultAddressGenerator; type MaxCodeLen = ConstU32<{ 123 * 1024 }>; type MaxStorageKeyLen = ConstU32<128>; type UnsafeUnstableInterface = UnstableInterface; type MaxDebugBufferLen = ConstU32<{ 2 * 1024 * 1024 }>; + type Migrations = (NoopMigration<1>, NoopMigration<2>); } pub const ALICE: AccountId32 = AccountId32::new([1u8; 32]); @@ -416,14 +457,15 @@ pub const BOB: AccountId32 = AccountId32::new([2u8; 32]); pub const CHARLIE: AccountId32 = AccountId32::new([3u8; 32]); pub const DJANGO: AccountId32 = AccountId32::new([4u8; 32]); -pub const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 512 * 1024); +pub const GAS_LIMIT: Weight = Weight::from_parts(100_000_000_000, 3 * 1024 * 1024); pub struct ExtBuilder { existential_deposit: u64, + storage_version: Option, } impl Default for ExtBuilder { fn default() -> Self { - Self { existential_deposit: ExistentialDeposit::get() } + Self { existential_deposit: ExistentialDeposit::get(), storage_version: None } } } impl ExtBuilder { @@ -434,18 +476,30 @@ impl ExtBuilder { pub fn set_associated_consts(&self) { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); } + pub fn set_storage_version(mut self, version: u16) -> Self { + self.storage_version = Some(StorageVersion::new(version)); + self + } pub fn build(self) -> sp_io::TestExternalities { use env_logger::{Builder, Env}; let env = Env::new().default_filter_or("runtime=debug"); let _ = Builder::from_env(env).is_test(true).try_init(); self.set_associated_consts(); - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![] } .assimilate_storage(&mut t) .unwrap(); let mut ext = sp_io::TestExternalities::new(t); - ext.register_extension(KeystoreExt(Arc::new(KeyStore::new()))); - ext.execute_with(|| System::set_block_number(1)); + ext.register_extension(KeystoreExt::new(MemoryKeystore::new())); + ext.execute_with(|| { + use frame_support::traits::OnGenesis; + + Pallet::::on_genesis(); + if let Some(storage_version) = self.storage_version { + storage_version.put::>(); + } + System::set_block_number(1) + }); ext } } @@ -458,7 +512,14 @@ fn compile_module(fixture_name: &str) -> wat::Result<(Vec, From> for Vec { } } +impl Default for Origin { + fn default() -> Self { + Self::Signed(ALICE) + } +} // Perform a call to a plain account. // The actual transfer fails because we can only call contracts. // Then we check that at least the base costs where charged (no runtime gas costs.) @@ -514,11 +580,85 @@ fn calling_plain_account_fails() { }); } +#[test] +fn migration_on_idle_hooks_works() { + // Defines expectations of how many migration steps can be done given the weight limit. + let tests = [ + (Weight::zero(), 0), + (::WeightInfo::migrate() + 1.into(), 1), + (Weight::MAX, 2), + ]; + + for (weight, expected_version) in tests { + ExtBuilder::default().set_storage_version(0).build().execute_with(|| { + MigrationInProgress::::set(Some(Default::default())); + Contracts::on_idle(System::block_number(), weight); + assert_eq!(StorageVersion::get::>(), expected_version); + }); + } +} + +#[test] +fn migration_in_progress_works() { + let (wasm, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + MigrationInProgress::::set(Some(Default::default())); + + assert_err!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + vec![], + None, + Determinism::Enforced + ), + Error::::MigrationInProgress, + ); + assert_err!( + Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash), + Error::::MigrationInProgress, + ); + assert_err!( + Contracts::set_code(RuntimeOrigin::signed(ALICE), BOB.clone(), code_hash), + Error::::MigrationInProgress, + ); + assert_err_ignore_postinfo!( + Contracts::call(RuntimeOrigin::signed(ALICE), BOB, 0, GAS_LIMIT, None, vec![],), + Error::::MigrationInProgress, + ); + assert_err_ignore_postinfo!( + Contracts::instantiate_with_code( + RuntimeOrigin::signed(ALICE), + 100_000, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + ), + Error::::MigrationInProgress, + ); + assert_err_ignore_postinfo!( + Contracts::instantiate( + RuntimeOrigin::signed(ALICE), + 100_000, + GAS_LIMIT, + None, + code_hash, + vec![], + vec![], + ), + Error::::MigrationInProgress, + ); + }); +} + #[test] fn instantiate_and_call_and_deposit_event() { let (wasm, code_hash) = compile_module::("event_and_return_on_deploy").unwrap(); - ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let min_balance = ::Currency::minimum_balance(); let value = 100; @@ -529,7 +669,7 @@ fn instantiate_and_call_and_deposit_event() { RuntimeOrigin::signed(ALICE), wasm, None, - Determinism::Deterministic + Determinism::Enforced )); // Drop previous events @@ -544,28 +684,32 @@ fn instantiate_and_call_and_deposit_event() { Code::Existing(code_hash), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() .account_id; assert!(ContractInfoOf::::contains_key(&addr)); + let contract = get_contract(&addr); + let deposit_account = contract.deposit_account().deref(); + assert_eq!( System::events(), vec![ EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::NewAccount { - account: addr.clone() + account: deposit_account.clone(), }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { - account: addr.clone(), - free_balance: min_balance, + account: deposit_account.clone(), + free_balance: 131, }), topics: vec![], }, @@ -573,15 +717,31 @@ fn instantiate_and_call_and_deposit_event() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: ALICE, - to: addr.clone(), - amount: min_balance, + to: deposit_account.clone(), + amount: 131, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { - who: addr.clone(), + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: addr.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), amount: min_balance, }), topics: vec![], @@ -631,7 +791,8 @@ fn deposit_event_max_value_limit() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -662,8 +823,9 @@ fn deposit_event_max_value_limit() { }); } +// Fail out of fuel (ref_time weight) in the engine. #[test] -fn run_out_of_gas() { +fn run_out_of_fuel_engine() { let (wasm, _code_hash) = compile_module::("run_out_of_gas").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); @@ -677,7 +839,8 @@ fn run_out_of_gas() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -690,7 +853,7 @@ fn run_out_of_gas() { RuntimeOrigin::signed(ALICE), addr, // newly created account 0, - Weight::from_ref_time(1_000_000_000_000).set_proof_size(u64::MAX), + Weight::from_parts(1_000_000_000_000, u64::MAX), None, vec![], ), @@ -699,6 +862,155 @@ fn run_out_of_gas() { }); } +// Fail out of fuel (ref_time weight) in the host. +#[test] +fn run_out_of_fuel_host() { + let (code, _hash) = compile_module::("chain_extension").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); + + let addr = Contracts::bare_instantiate( + ALICE, + min_balance * 100, + GAS_LIMIT, + None, + Code::Upload(code), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + let gas_limit = Weight::from_parts(u32::MAX as u64, GAS_LIMIT.proof_size()); + + // Use chain extension to charge more ref_time than it is available. + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + gas_limit, + None, + ExtensionInput { extension_id: 0, func_id: 2, extra: &u32::MAX.encode() }.into(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ) + .result; + assert_err!(result, >::OutOfGas); + }); +} + +#[test] +fn gas_syncs_work() { + let (wasm0, _code_hash) = compile_module::("seal_input_noop").unwrap(); + let (wasm1, _code_hash) = compile_module::("seal_input_once").unwrap(); + let (wasm2, _code_hash) = compile_module::("seal_input_twice").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + // Instantiate noop contract. + let addr0 = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm0), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + // Instantiate 1st contract. + let addr1 = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm1), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + // Instantiate 2nd contract. + let addr2 = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm2), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + let result = Contracts::bare_call( + ALICE, + addr0, + 0, + GAS_LIMIT, + None, + 1u8.to_le_bytes().to_vec(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ); + assert_ok!(result.result); + let engine_consumed_noop = result.gas_consumed.ref_time(); + + let result = Contracts::bare_call( + ALICE, + addr1, + 0, + GAS_LIMIT, + None, + 1u8.to_le_bytes().to_vec(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ); + assert_ok!(result.result); + let gas_consumed_once = result.gas_consumed.ref_time(); + let host_consumed_once = ::Schedule::get().host_fn_weights.input.ref_time(); + let engine_consumed_once = gas_consumed_once - host_consumed_once - engine_consumed_noop; + + let result = Contracts::bare_call( + ALICE, + addr2, + 0, + GAS_LIMIT, + None, + 1u8.to_le_bytes().to_vec(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ); + assert_ok!(result.result); + let gas_consumed_twice = result.gas_consumed.ref_time(); + let host_consumed_twice = host_consumed_once * 2; + let engine_consumed_twice = gas_consumed_twice - host_consumed_twice - engine_consumed_noop; + + // Second contract just repeats first contract's instructions twice. + // If runtime syncs gas with the engine properly, this should pass. + assert_eq!(engine_consumed_twice, engine_consumed_once * 2); + }); +} + /// Check that contracts with the same account id have different trie ids. /// Check the `Nonce` storage item for more information. #[test] @@ -707,13 +1019,8 @@ fn instantiate_unique_trie_id() { ExtBuilder::default().existential_deposit(500).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); - Contracts::upload_code( - RuntimeOrigin::signed(ALICE), - wasm, - None, - Determinism::Deterministic, - ) - .unwrap(); + Contracts::upload_code(RuntimeOrigin::signed(ALICE), wasm, None, Determinism::Enforced) + .unwrap(); // Instantiate the contract and store its trie id for later comparison. let addr = Contracts::bare_instantiate( @@ -724,7 +1031,8 @@ fn instantiate_unique_trie_id() { Code::Existing(code_hash), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -786,7 +1094,8 @@ fn storage_max_value_limit() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -823,7 +1132,7 @@ fn deploy_and_call_other_contract() { let (caller_wasm, _caller_code_hash) = compile_module::("caller_contract").unwrap(); let (callee_wasm, callee_code_hash) = compile_module::("return_with_data").unwrap(); - ExtBuilder::default().existential_deposit(500).build().execute_with(|| { + ExtBuilder::default().existential_deposit(1).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); // Create @@ -836,23 +1145,13 @@ fn deploy_and_call_other_contract() { Code::Upload(caller_wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() .account_id; - Contracts::bare_instantiate( - ALICE, - 100_000, - GAS_LIMIT, - None, - Code::Upload(callee_wasm), - 0u32.to_le_bytes().encode(), - vec![42], - false, - ) - .result - .unwrap(); + Contracts::bare_upload_code(ALICE, callee_wasm, None, Determinism::Enforced).unwrap(); let callee_addr = Contracts::contract_address( &caller_addr, @@ -875,21 +1174,24 @@ fn deploy_and_call_other_contract() { callee_code_hash.as_ref().to_vec(), )); + let callee = get_contract(&callee_addr); + let deposit_account = callee.deposit_account().deref(); + assert_eq!( System::events(), vec![ EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::NewAccount { - account: callee_addr.clone() + account: deposit_account.clone(), }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { - account: callee_addr.clone(), - free_balance: min_balance, + account: deposit_account.clone(), + free_balance: 131, }), topics: vec![], }, @@ -897,15 +1199,31 @@ fn deploy_and_call_other_contract() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: ALICE, - to: callee_addr.clone(), - amount: min_balance, + to: deposit_account.clone(), + amount: 131, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { - who: callee_addr.clone(), + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: callee_addr.clone() + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: callee_addr.clone(), + free_balance: min_balance, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: callee_addr.clone(), amount: min_balance, }), topics: vec![], @@ -915,7 +1233,7 @@ fn deploy_and_call_other_contract() { event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: caller_addr.clone(), to: callee_addr.clone(), - amount: 32768, // hard coded in wasm + amount: 32768 // hardcoded in wasm }), topics: vec![], }, @@ -939,18 +1257,21 @@ fn deploy_and_call_other_contract() { EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Called { - caller: caller_addr.clone(), + caller: Origin::from_account_id(caller_addr.clone()), contract: callee_addr.clone(), }), - topics: vec![hash(&caller_addr), hash(&callee_addr)], + topics: vec![ + hash(&Origin::::from_account_id(caller_addr.clone())), + hash(&callee_addr) + ], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Called { - caller: ALICE, + caller: Origin::from_account_id(ALICE), contract: caller_addr.clone(), }), - topics: vec![hash(&ALICE), hash(&caller_addr)], + topics: vec![hash(&Origin::::from_account_id(ALICE)), hash(&caller_addr)], }, ] ); @@ -974,7 +1295,8 @@ fn delegate_call() { Code::Upload(caller_wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -984,7 +1306,7 @@ fn delegate_call() { RuntimeOrigin::signed(ALICE), callee_wasm, Some(codec::Compact(100_000)), - Determinism::Deterministic, + Determinism::Enforced, )); assert_ok!(Contracts::call( @@ -999,8 +1321,8 @@ fn delegate_call() { } #[test] -fn cannot_self_destruct_through_draning() { - let (wasm, _code_hash) = compile_module::("drain").unwrap(); +fn transfer_allow_death_cannot_kill_account() { + let (wasm, _code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); @@ -1013,7 +1335,8 @@ fn cannot_self_destruct_through_draning() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -1022,91 +1345,74 @@ fn cannot_self_destruct_through_draning() { // Check that the BOB contract has been instantiated. get_contract(&addr); - // Call BOB which makes it send all funds to the zero address - // The contract code asserts that the transfer was successful - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - addr.clone(), - 0, - GAS_LIMIT, - None, - vec![] - )); + let total_balance = ::Currency::total_balance(&addr); - // Make sure the account wasn't remove by sending all free balance away. - assert_eq!( - ::Currency::total_balance(&addr), - ::Currency::minimum_balance(), + assert_err!( + <::Currency as Currency>::transfer( + &addr, + &ALICE, + total_balance, + ExistenceRequirement::AllowDeath, + ), + TokenError::Frozen, ); + + assert_eq!(::Currency::total_balance(&addr), total_balance); }); } #[test] -fn cannot_self_destruct_through_storage_refund_after_price_change() { - let (wasm, _code_hash) = compile_module::("store").unwrap(); +fn cannot_self_destruct_through_draning() { + let (wasm, _code_hash) = compile_module::("drain").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); - let min_balance = ::Currency::minimum_balance(); // Instantiate the BOB contract. let addr = Contracts::bare_instantiate( ALICE, - 0, + 1_000, GAS_LIMIT, None, Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() .account_id; - // Check that the BOB contract has been instantiated and has the minimum balance - assert_eq!(get_contract(&addr).total_deposit(), min_balance); - assert_eq!(get_contract(&addr).extra_deposit(), 0); - assert_eq!(::Currency::total_balance(&addr), min_balance); - - // Create 100 bytes of storage with a price of per byte and a single storage item of price 2 - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - addr.clone(), - 0, - GAS_LIMIT, - None, - 100u32.to_le_bytes().to_vec() - )); - assert_eq!(get_contract(&addr).total_deposit(), min_balance + 102); + // Check that the BOB contract has been instantiated. + get_contract(&addr); - // Increase the byte price and trigger a refund. This should not have any influence because - // the removal is pro rata and exactly those 100 bytes should have been removed. - DEPOSIT_PER_BYTE.with(|c| *c.borrow_mut() = 500); + // Call BOB which makes it send all funds to the zero address + // The contract code asserts that the transfer fails with the correct error code assert_ok!(Contracts::call( RuntimeOrigin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, None, - 0u32.to_le_bytes().to_vec() + vec![] )); - // Make sure the account wasn't removed by the refund + // Make sure the account wasn't remove by sending all free balance away. assert_eq!( ::Currency::total_balance(&addr), - get_contract(&addr).total_deposit(), + 1_000 + ::Currency::minimum_balance(), ); - assert_eq!(get_contract(&addr).extra_deposit(), 2,); }); } #[test] -fn cannot_self_destruct_by_refund_after_slash() { - let (wasm, _code_hash) = compile_module::("store").unwrap(); - ExtBuilder::default().existential_deposit(500).build().execute_with(|| { +fn cannot_self_destruct_through_storage_refund_after_price_change() { + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let min_balance = ::Currency::minimum_balance(); + // Instantiate the BOB contract. let addr = Contracts::bare_instantiate( ALICE, 0, @@ -1115,74 +1421,47 @@ fn cannot_self_destruct_by_refund_after_slash() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() .account_id; - // create 100 more reserved balance + // Check that the BOB contract has been instantiated and has the minimum balance + assert_eq!(get_contract(&addr).total_deposit(), min_balance); + assert_eq!(get_contract(&addr).extra_deposit(), 0); + assert_eq!(::Currency::total_balance(&addr), min_balance); + + // Create 100 bytes of storage with a price of per byte and a single storage item of price 2 assert_ok!(Contracts::call( RuntimeOrigin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, None, - 98u32.encode(), + 100u32.to_le_bytes().to_vec() )); + assert_eq!(get_contract(&addr).total_deposit(), min_balance + 102); - // Drop previous events - initialize_block(2); - - // slash parts of the 100 so that the next refund ould remove the account - // because it the value it stored for `storage_deposit` becomes out of sync - let _ = ::Currency::slash(&addr, 90); - assert_eq!(::Currency::total_balance(&addr), min_balance + 10); - - // trigger a refund of 50 which would bring the contract below min when actually refunded + // Increase the byte price and trigger a refund. This should not have any influence because + // the removal is pro rata and exactly those 100 bytes should have been removed. + DEPOSIT_PER_BYTE.with(|c| *c.borrow_mut() = 500); assert_ok!(Contracts::call( RuntimeOrigin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, None, - 48u32.encode(), + 0u32.to_le_bytes().to_vec() )); - // Make sure the account kept the minimum balance and was not destroyed - assert_eq!(::Currency::total_balance(&addr), min_balance); - + // Make sure the account wasn't removed by the refund assert_eq!( - System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Slashed { - who: addr.clone(), - amount: 90, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: ALICE, - contract: addr.clone(), - }), - topics: vec![hash(&ALICE), hash(&addr)], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::ReserveRepatriated { - from: addr.clone(), - to: ALICE, - amount: 10, - destination_status: BalanceStatus::Free, - }), - topics: vec![], - }, - ] + ::Currency::total_balance(get_contract(&addr).deposit_account()), + get_contract(&addr).total_deposit(), ); + assert_eq!(get_contract(&addr).extra_deposit(), 2); }); } @@ -1201,7 +1480,8 @@ fn cannot_self_destruct_while_live() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -1245,14 +1525,15 @@ fn self_destruct_works() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() .account_id; // Check that the BOB contract has been instantiated. - get_contract(&addr); + let contract = get_contract(&addr); // Drop all previous events initialize_block(2); @@ -1271,17 +1552,25 @@ fn self_destruct_works() { assert_eq!(Balances::total_balance(&addr), 0); // check that the beneficiary (django) got remaining balance - assert_eq!(Balances::free_balance(DJANGO), 1_000_000 + 100_000); + let ed = ::Currency::minimum_balance(); + assert_eq!(Balances::free_balance(DJANGO), 1_000_000 + 100_000 + ed); pretty_assertions::assert_eq!( System::events(), vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::System(frame_system::Event::KilledAccount { + account: addr.clone() + }), + topics: vec![], + }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: addr.clone(), to: DJANGO, - amount: 100_000, + amount: 100_000 + ed, }), topics: vec![], }, @@ -1296,25 +1585,24 @@ fn self_destruct_works() { EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Called { - caller: ALICE, + caller: Origin::from_account_id(ALICE), contract: addr.clone(), }), - topics: vec![hash(&ALICE), hash(&addr)], + topics: vec![hash(&Origin::::from_account_id(ALICE)), hash(&addr)], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::KilledAccount { - account: addr.clone() + account: contract.deposit_account().deref().clone(), }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::ReserveRepatriated { - from: addr.clone(), + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: contract.deposit_account().deref().clone(), to: ALICE, amount: 1_000, - destination_status: BalanceStatus::Free, }), topics: vec![], }, @@ -1333,7 +1621,7 @@ fn destroy_contract_and_transfer_funds() { ExtBuilder::default().existential_deposit(50).build().execute_with(|| { // Create code hash for bob to instantiate let _ = Balances::deposit_creating(&ALICE, 1_000_000); - Contracts::bare_upload_code(ALICE, callee_wasm, None, Determinism::Deterministic).unwrap(); + Contracts::bare_upload_code(ALICE, callee_wasm, None, Determinism::Enforced).unwrap(); // This deploys the BOB contract, which in turn deploys the CHARLIE contract during // construction. @@ -1345,7 +1633,8 @@ fn destroy_contract_and_transfer_funds() { Code::Upload(caller_wasm), callee_code_hash.as_ref().to_vec(), vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -1409,7 +1698,8 @@ fn crypto_hashes() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -1442,8 +1732,9 @@ fn crypto_hashes() { GAS_LIMIT, None, params, - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -1469,7 +1760,8 @@ fn transfer_return_code() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -1484,27 +1776,9 @@ fn transfer_return_code() { GAS_LIMIT, None, vec![], - false, - Determinism::Deterministic, - ) - .result - .unwrap(); - assert_return_code!(result, RuntimeReturnCode::TransferFailed); - - // Contract has enough total balance in order to not go below the min balance - // threshold when transfering 100 balance but this balance is reserved so - // the transfer still fails. - Balances::make_free_balance_be(&addr, min_balance + 100); - Balances::reserve(&addr, min_balance + 100).unwrap(); - let result = Contracts::bare_call( - ALICE, - addr, - 0, - GAS_LIMIT, - None, - vec![], - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -1529,7 +1803,8 @@ fn call_return_code() { Code::Upload(caller_code), vec![0], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -1544,8 +1819,9 @@ fn call_return_code() { GAS_LIMIT, None, AsRef::<[u8]>::as_ref(&DJANGO).to_vec(), - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -1559,7 +1835,8 @@ fn call_return_code() { Code::Upload(callee_code), vec![0], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -1578,31 +1855,9 @@ fn call_return_code() { .chain(&0u32.to_le_bytes()) .cloned() .collect(), - false, - Determinism::Deterministic, - ) - .result - .unwrap(); - assert_return_code!(result, RuntimeReturnCode::TransferFailed); - - // Contract has enough total balance in order to not go below the min balance - // threshold when transfering 100 balance but this balance is reserved so - // the transfer still fails. - Balances::make_free_balance_be(&addr_bob, min_balance + 100); - Balances::reserve(&addr_bob, min_balance + 100).unwrap(); - let result = Contracts::bare_call( - ALICE, - addr_bob.clone(), - 0, - GAS_LIMIT, - None, - AsRef::<[u8]>::as_ref(&addr_django) - .iter() - .chain(&0u32.to_le_bytes()) - .cloned() - .collect(), - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -1621,8 +1876,9 @@ fn call_return_code() { .chain(&1u32.to_le_bytes()) .cloned() .collect(), - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -1640,8 +1896,9 @@ fn call_return_code() { .chain(&2u32.to_le_bytes()) .cloned() .collect(), - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -1677,7 +1934,8 @@ fn instantiate_return_code() { Code::Upload(caller_code), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -1692,27 +1950,9 @@ fn instantiate_return_code() { GAS_LIMIT, None, callee_hash.clone(), - false, - Determinism::Deterministic, - ) - .result - .unwrap(); - assert_return_code!(result, RuntimeReturnCode::TransferFailed); - - // Contract has enough total balance in order to not go below the min_balance - // threshold when transfering the balance but this balance is reserved so - // the transfer still fails. - Balances::make_free_balance_be(&addr, min_balance + 10_000); - Balances::reserve(&addr, min_balance + 10_000).unwrap(); - let result = Contracts::bare_call( - ALICE, - addr.clone(), - 0, - GAS_LIMIT, - None, - callee_hash.clone(), - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -1727,8 +1967,9 @@ fn instantiate_return_code() { GAS_LIMIT, None, vec![0; 33], - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -1742,8 +1983,9 @@ fn instantiate_return_code() { GAS_LIMIT, None, callee_hash.iter().chain(&1u32.to_le_bytes()).cloned().collect(), - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -1757,8 +1999,9 @@ fn instantiate_return_code() { GAS_LIMIT, None, callee_hash.iter().chain(&2u32.to_le_bytes()).cloned().collect(), - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -1802,7 +2045,8 @@ fn disabled_chain_extension_errors_on_call() { Code::Upload(code), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -1810,7 +2054,7 @@ fn disabled_chain_extension_errors_on_call() { TestExtension::disable(); assert_err_ignore_postinfo!( Contracts::call(RuntimeOrigin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, None, vec![],), - Error::::NoChainExtension, + Error::::CodeRejected, ); }); } @@ -1829,7 +2073,8 @@ fn chain_extension_works() { Code::Upload(code), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -1844,8 +2089,9 @@ fn chain_extension_works() { GAS_LIMIT, None, input.clone(), - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ); assert_eq!(TestExtension::last_seen_buffer(), input); assert_eq!(result.result.unwrap().data, input); @@ -1858,8 +2104,9 @@ fn chain_extension_works() { GAS_LIMIT, None, ExtensionInput { extension_id: 0, func_id: 1, extra: &[] }.into(), - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -1873,9 +2120,10 @@ fn chain_extension_works() { 0, GAS_LIMIT, None, - ExtensionInput { extension_id: 0, func_id: 2, extra: &[0] }.into(), - false, - Determinism::Deterministic, + ExtensionInput { extension_id: 0, func_id: 2, extra: &0u32.encode() }.into(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ); assert_ok!(result.result); let gas_consumed = result.gas_consumed; @@ -1885,9 +2133,10 @@ fn chain_extension_works() { 0, GAS_LIMIT, None, - ExtensionInput { extension_id: 0, func_id: 2, extra: &[42] }.into(), - false, - Determinism::Deterministic, + ExtensionInput { extension_id: 0, func_id: 2, extra: &42u32.encode() }.into(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ); assert_ok!(result.result); assert_eq!(result.gas_consumed.ref_time(), gas_consumed.ref_time() + 42); @@ -1897,9 +2146,10 @@ fn chain_extension_works() { 0, GAS_LIMIT, None, - ExtensionInput { extension_id: 0, func_id: 2, extra: &[95] }.into(), - false, - Determinism::Deterministic, + ExtensionInput { extension_id: 0, func_id: 2, extra: &95u32.encode() }.into(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ); assert_ok!(result.result); assert_eq!(result.gas_consumed.ref_time(), gas_consumed.ref_time() + 95); @@ -1912,8 +2162,9 @@ fn chain_extension_works() { GAS_LIMIT, None, ExtensionInput { extension_id: 0, func_id: 3, extra: &[] }.into(), - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -1930,8 +2181,9 @@ fn chain_extension_works() { GAS_LIMIT, None, ExtensionInput { extension_id: 1, func_id: 0, extra: &[] }.into(), - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -1968,7 +2220,8 @@ fn chain_extension_temp_storage_works() { Code::Upload(code), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -1991,8 +2244,9 @@ fn chain_extension_temp_storage_works() { GAS_LIMIT, None, input.clone(), - false, - Determinism::Deterministic + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result ); @@ -2014,7 +2268,8 @@ fn lazy_removal_works() { Code::Upload(code), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -2050,25 +2305,6 @@ fn lazy_removal_works() { }); } -#[test] -fn lazy_removal_on_full_queue_works_on_initialize() { - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Fill the deletion queue with dummy values, so that on_initialize attempts - // to clear the queue - Storage::::fill_queue_with_dummies(); - - let queue_len_initial = >::decode_len().unwrap_or(0); - - // Run the lazy removal - Contracts::on_initialize(System::block_number()); - - let queue_len_after_on_initialize = >::decode_len().unwrap_or(0); - - // Queue length should be decreased after call of on_initialize() - assert!(queue_len_initial - queue_len_after_on_initialize > 0); - }); -} - #[test] fn lazy_batch_removal_works() { let (code, _hash) = compile_module::("self_destruct").unwrap(); @@ -2086,7 +2322,8 @@ fn lazy_batch_removal_works() { Code::Upload(code.clone()), vec![], vec![i], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -2131,8 +2368,8 @@ fn lazy_removal_partial_remove_works() { // We create a contract with some extra keys above the weight limit let extra_keys = 7u32; - let weight_limit = Weight::from_ref_time(5_000_000_000); - let (_, max_keys) = Storage::::deletion_budget(1, weight_limit); + let weight_limit = Weight::from_parts(5_000_000_000, 0); + let (_, max_keys) = ContractInfo::::deletion_budget(weight_limit); let vals: Vec<_> = (0..max_keys + extra_keys) .map(|i| (blake2_256(&i.encode()), (i as u32), (i as u32).encode())) .collect(); @@ -2151,7 +2388,8 @@ fn lazy_removal_partial_remove_works() { Code::Upload(code), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -2161,14 +2399,7 @@ fn lazy_removal_partial_remove_works() { // Put value into the contracts child trie for val in &vals { - Storage::::write( - &info.trie_id, - &val.0 as &FixSizedKey, - Some(val.2.clone()), - None, - false, - ) - .unwrap(); + info.write(&Key::Fix(val.0), Some(val.2.clone()), None, false).unwrap(); } >::insert(&addr, info.clone()); @@ -2201,7 +2432,7 @@ fn lazy_removal_partial_remove_works() { ext.execute_with(|| { // Run the lazy removal - let weight_used = Storage::::process_deletion_queue_batch(weight_limit); + let weight_used = ContractInfo::::process_deletion_queue_batch(weight_limit); // Weight should be exhausted because we could not even delete all keys assert_eq!(weight_used, weight_limit); @@ -2224,33 +2455,6 @@ fn lazy_removal_partial_remove_works() { }); } -#[test] -fn lazy_removal_does_no_run_on_full_queue_and_full_block() { - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - // Fill up the block which should prevent the lazy storage removal from running. - System::register_extra_weight_unchecked( - ::BlockWeights::get().max_block, - DispatchClass::Mandatory, - ); - - // Fill the deletion queue with dummy values, so that on_initialize attempts - // to clear the queue - Storage::::fill_queue_with_dummies(); - - // Check that on_initialize() tries to perform lazy removal but removes nothing - // as no more weight is left for that. - let weight_used = Contracts::on_initialize(System::block_number()); - let base = <::WeightInfo as WeightInfo>::on_process_deletion_queue_batch(); - assert_eq!(weight_used, base); - - // Check that the deletion queue is still full after execution of the - // on_initialize() hook. - let max_len: u32 = ::DeletionQueueDepth::get(); - let queue_len: u32 = >::decode_len().unwrap_or(0).try_into().unwrap(); - assert_eq!(max_len, queue_len); - }); -} - #[test] fn lazy_removal_does_no_run_on_low_remaining_weight() { let (code, _hash) = compile_module::("self_destruct").unwrap(); @@ -2266,7 +2470,8 @@ fn lazy_removal_does_no_run_on_low_remaining_weight() { Code::Upload(code), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -2294,7 +2499,7 @@ fn lazy_removal_does_no_run_on_low_remaining_weight() { // But value should be still there as the lazy removal did not run, yet. assert_matches!(child::get(trie, &[99]), Some(42)); - // Assign a remaining weight which is too low for a successfull deletion of the contract + // Assign a remaining weight which is too low for a successful deletion of the contract let low_remaining_weight = <::WeightInfo as WeightInfo>::on_process_deletion_queue_batch(); @@ -2322,7 +2527,7 @@ fn lazy_removal_does_no_run_on_low_remaining_weight() { fn lazy_removal_does_not_use_all_weight() { let (code, _hash) = compile_module::("self_destruct").unwrap(); - let weight_limit = Weight::from_ref_time(5_000_000_000); + let weight_limit = Weight::from_parts(5_000_000_000, 100 * 1024); let mut ext = ExtBuilder::default().existential_deposit(50).build(); let (trie, vals, weight_per_key) = ext.execute_with(|| { @@ -2337,14 +2542,15 @@ fn lazy_removal_does_not_use_all_weight() { Code::Upload(code), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() .account_id; let info = get_contract(&addr); - let (weight_per_key, max_keys) = Storage::::deletion_budget(1, weight_limit); + let (weight_per_key, max_keys) = ContractInfo::::deletion_budget(weight_limit); // We create a contract with one less storage item than we can remove within the limit let vals: Vec<_> = (0..max_keys - 1) @@ -2353,14 +2559,7 @@ fn lazy_removal_does_not_use_all_weight() { // Put value into the contracts child trie for val in &vals { - Storage::::write( - &info.trie_id, - &val.0 as &FixSizedKey, - Some(val.2.clone()), - None, - false, - ) - .unwrap(); + info.write(&Key::Fix(val.0), Some(val.2.clone()), None, false).unwrap(); } >::insert(&addr, info.clone()); @@ -2393,10 +2592,10 @@ fn lazy_removal_does_not_use_all_weight() { ext.execute_with(|| { // Run the lazy removal - let weight_used = Storage::::process_deletion_queue_batch(weight_limit); + let weight_used = ContractInfo::::process_deletion_queue_batch(weight_limit); // We have one less key in our trie than our weight limit suffices for - assert_eq!(weight_used, weight_limit - Weight::from_ref_time(weight_per_key)); + assert_eq!(weight_used, weight_limit - weight_per_key); // All the keys are removed for val in vals { @@ -2406,40 +2605,76 @@ fn lazy_removal_does_not_use_all_weight() { } #[test] -fn deletion_queue_full() { +fn deletion_queue_ring_buffer_overflow() { let (code, _hash) = compile_module::("self_destruct").unwrap(); - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let mut ext = ExtBuilder::default().existential_deposit(50).build(); + + // setup the deletion queue with custom counters + ext.execute_with(|| { + let queue = DeletionQueueManager::from_test_values(u32::MAX - 1, u32::MAX - 1); + >::set(queue); + }); + + // commit the changes to the storage + ext.commit_all().unwrap(); + + ext.execute_with(|| { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); + let mut tries: Vec = vec![]; - let addr = Contracts::bare_instantiate( - ALICE, - min_balance * 100, - GAS_LIMIT, - None, - Code::Upload(code), - vec![], - vec![], - false, - ) - .result - .unwrap() - .account_id; + // add 3 contracts to the deletion queue + for i in 0..3u8 { + let addr = Contracts::bare_instantiate( + ALICE, + min_balance * 100, + GAS_LIMIT, + None, + Code::Upload(code.clone()), + vec![], + vec![i], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; - // fill the deletion queue up until its limit - Storage::::fill_queue_with_dummies(); + let info = get_contract(&addr); + let trie = &info.child_trie_info(); - // Terminate the contract should fail - assert_err_ignore_postinfo!( - Contracts::call(RuntimeOrigin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, None, vec![],), - Error::::DeletionQueueFull, - ); + // Put value into the contracts child trie + child::put(trie, &[99], &42); - // Contract should exist because removal failed - get_contract(&addr); - }); -} + // Terminate the contract. Contract info should be gone, but value should be still + // there as the lazy removal did not run, yet. + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![] + )); + + assert!(!>::contains_key(&addr)); + assert_matches!(child::get(trie, &[99]), Some(42)); + + tries.push(trie.clone()) + } + + // Run single lazy removal + Contracts::on_idle(System::block_number(), Weight::MAX); + + // The single lazy removal should have removed all queued tries + for trie in tries.iter() { + assert_matches!(child::get::(trie, &[99]), None); + } + // insert and delete counter values should go from u32::MAX - 1 to 1 + assert_eq!(>::get().as_test_tuple(), (1, 1)); + }) +} #[test] fn refcounter() { let (wasm, code_hash) = compile_module::("self_destruct").unwrap(); @@ -2456,7 +2691,8 @@ fn refcounter() { Code::Upload(wasm.clone()), vec![], vec![0], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -2469,7 +2705,8 @@ fn refcounter() { Code::Upload(wasm.clone()), vec![], vec![1], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -2485,7 +2722,8 @@ fn refcounter() { Code::Existing(code_hash), vec![], vec![2], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -2515,7 +2753,7 @@ fn refcounter() { assert_refcount!(code_hash, 1); // Pristine code should still be there - crate::PristineCode::::get(code_hash).unwrap(); + PristineCode::::get(code_hash).unwrap(); // remove the last contract assert_ok!(Contracts::call( @@ -2530,92 +2768,49 @@ fn refcounter() { // refcount is `0` but code should still exists because it needs to be removed manually assert!(crate::PristineCode::::contains_key(&code_hash)); - assert!(crate::CodeStorage::::contains_key(&code_hash)); }); } #[test] -fn reinstrument_does_charge() { - let (wasm, code_hash) = compile_module::("return_with_data").unwrap(); +fn debug_message_works() { + let (wasm, _code_hash) = compile_module::("debug_message_works").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); - let min_balance = ::Currency::minimum_balance(); - let zero = 0u32.to_le_bytes().encode(); - let code_len = wasm.len() as u32; - let addr = Contracts::bare_instantiate( ALICE, - min_balance * 100, + 30_000, GAS_LIMIT, None, Code::Upload(wasm), - zero.clone(), vec![], - false, + vec![], + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() .account_id; - - // Call the contract two times without reinstrument - - let result0 = Contracts::bare_call( + let result = Contracts::bare_call( ALICE, - addr.clone(), + addr, 0, GAS_LIMIT, None, - zero.clone(), - false, - Determinism::Deterministic, + vec![], + DebugInfo::UnsafeDebug, + CollectEvents::Skip, + Determinism::Enforced, ); - assert!(!result0.result.unwrap().did_revert()); - let result1 = Contracts::bare_call( - ALICE, - addr.clone(), - 0, - GAS_LIMIT, - None, - zero.clone(), - false, - Determinism::Deterministic, - ); - assert!(!result1.result.unwrap().did_revert()); + assert_matches!(result.result, Ok(_)); + assert_eq!(std::str::from_utf8(&result.debug_message).unwrap(), "Hello World!"); + }); +} - // They should match because both where called with the same schedule. - assert_eq!(result0.gas_consumed, result1.gas_consumed); - - // We cannot change the schedule. Instead, we decrease the version of the deployed - // contract below the current schedule's version. - crate::CodeStorage::mutate(&code_hash, |code: &mut Option>| { - code.as_mut().unwrap().decrement_version(); - }); - - // This call should trigger reinstrumentation - let result2 = Contracts::bare_call( - ALICE, - addr.clone(), - 0, - GAS_LIMIT, - None, - zero.clone(), - false, - Determinism::Deterministic, - ); - assert!(!result2.result.unwrap().did_revert()); - assert!(result2.gas_consumed.ref_time() > result1.gas_consumed.ref_time()); - assert_eq!( - result2.gas_consumed.ref_time(), - result1.gas_consumed.ref_time() + - ::WeightInfo::reinstrument(code_len).ref_time(), - ); - }); -} - -#[test] -fn debug_message_works() { - let (wasm, _code_hash) = compile_module::("debug_message_works").unwrap(); +#[test] +fn debug_message_logging_disabled() { + let (wasm, _code_hash) = compile_module::("debug_message_logging_disabled").unwrap(); ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); @@ -2627,42 +2822,8 @@ fn debug_message_works() { Code::Upload(wasm), vec![], vec![], - false, - ) - .result - .unwrap() - .account_id; - let result = Contracts::bare_call( - ALICE, - addr, - 0, - GAS_LIMIT, - None, - vec![], - true, - Determinism::Deterministic, - ); - - assert_matches!(result.result, Ok(_)); - assert_eq!(std::str::from_utf8(&result.debug_message).unwrap(), "Hello World!"); - }); -} - -#[test] -fn debug_message_logging_disabled() { - let (wasm, _code_hash) = compile_module::("debug_message_logging_disabled").unwrap(); - - ExtBuilder::default().existential_deposit(50).build().execute_with(|| { - let _ = Balances::deposit_creating(&ALICE, 1_000_000); - let addr = Contracts::bare_instantiate( - ALICE, - 30_000, - GAS_LIMIT, - None, - Code::Upload(wasm), - vec![], - vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -2675,8 +2836,9 @@ fn debug_message_logging_disabled() { GAS_LIMIT, None, vec![], - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ); assert_matches!(result.result, Ok(_)); // the dispatchables always run without debugging @@ -2699,7 +2861,8 @@ fn debug_message_invalid_utf8() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -2711,10 +2874,12 @@ fn debug_message_invalid_utf8() { GAS_LIMIT, None, vec![], - true, - Determinism::Deterministic, + DebugInfo::UnsafeDebug, + CollectEvents::Skip, + Determinism::Enforced, ); - assert_err!(result.result, >::DebugMessageInvalidUTF8); + assert_ok!(result.result); + assert!(result.debug_message.is_empty()); }); } @@ -2725,7 +2890,6 @@ fn gas_estimation_nested_call_fixed_limit() { ExtBuilder::default().existential_deposit(50).build().execute_with(|| { let min_balance = ::Currency::minimum_balance(); let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); - let _ = Balances::deposit_creating(&CHARLIE, 1000 * min_balance); let addr_caller = Contracts::bare_instantiate( ALICE, @@ -2735,7 +2899,8 @@ fn gas_estimation_nested_call_fixed_limit() { Code::Upload(caller_code), vec![], vec![0], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -2749,7 +2914,8 @@ fn gas_estimation_nested_call_fixed_limit() { Code::Upload(callee_code), vec![], vec![1], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -2759,6 +2925,7 @@ fn gas_estimation_nested_call_fixed_limit() { .iter() .cloned() .chain((GAS_LIMIT / 5).ref_time().to_le_bytes()) + .chain((GAS_LIMIT / 5).proof_size().to_le_bytes()) .collect(); // Call in order to determine the gas that is required for this call @@ -2769,28 +2936,45 @@ fn gas_estimation_nested_call_fixed_limit() { GAS_LIMIT, None, input.clone(), - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ); assert_ok!(&result.result); // We have a subcall with a fixed gas limit. This constitutes precharging. - assert!(result.gas_required.ref_time() > result.gas_consumed.ref_time()); + assert!(result.gas_required.all_gt(result.gas_consumed)); // Make the same call using the estimated gas. Should succeed. assert_ok!( Contracts::bare_call( ALICE, - addr_caller, + addr_caller.clone(), 0, result.gas_required, Some(result.storage_deposit.charge_or_zero()), - input, - false, - Determinism::Deterministic, + input.clone(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result ); + + // Make the same call using proof_size but less than estimated. Should fail with OutOfGas. + let result = Contracts::bare_call( + ALICE, + addr_caller, + 0, + result.gas_required.sub_proof_size(1), + Some(result.storage_deposit.charge_or_zero()), + input, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ) + .result; + assert_err!(result, >::OutOfGas); }); } @@ -2812,13 +2996,14 @@ fn gas_estimation_call_runtime() { Code::Upload(caller_code), vec![], vec![0], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() .account_id; - let addr_callee = Contracts::bare_instantiate( + Contracts::bare_instantiate( ALICE, min_balance * 100, GAS_LIMIT, @@ -2826,21 +3011,15 @@ fn gas_estimation_call_runtime() { Code::Upload(callee_code), vec![], vec![1], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result - .unwrap() - .account_id; + .unwrap(); // Call something trivial with a huge gas limit so that we can observe the effects // of pre-charging. This should create a difference between consumed and required. - let call = RuntimeCall::Contracts(crate::Call::call { - dest: addr_callee, - value: 0, - gas_limit: GAS_LIMIT / 3, - storage_deposit_limit: None, - data: vec![], - }); + let call = RuntimeCall::Dummy(pallet_dummy::Call::overestimate_pre_charge {}); let result = Contracts::bare_call( ALICE, addr_caller.clone(), @@ -2848,8 +3027,9 @@ fn gas_estimation_call_runtime() { GAS_LIMIT, None, call.encode(), - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ); // contract encodes the result of the dispatch runtime let outcome = u32::decode(&mut result.result.unwrap().data.as_ref()).unwrap(); @@ -2865,14 +3045,83 @@ fn gas_estimation_call_runtime() { result.gas_required, None, call.encode(), - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result ); }); } +#[test] +fn call_runtime_reentrancy_guarded() { + let (caller_code, _caller_hash) = compile_module::("call_runtime").unwrap(); + let (callee_code, _callee_hash) = compile_module::("dummy").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); + let _ = Balances::deposit_creating(&CHARLIE, 1000 * min_balance); + + let addr_caller = Contracts::bare_instantiate( + ALICE, + min_balance * 100, + GAS_LIMIT, + None, + Code::Upload(caller_code), + vec![], + vec![0], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + let addr_callee = Contracts::bare_instantiate( + ALICE, + min_balance * 100, + GAS_LIMIT, + None, + Code::Upload(callee_code), + vec![], + vec![1], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + // Call pallet_contracts call() dispatchable + let call = RuntimeCall::Contracts(crate::Call::call { + dest: addr_callee, + value: 0, + gas_limit: GAS_LIMIT / 3, + storage_deposit_limit: None, + data: vec![], + }); + + // Call runtime to re-enter back to contracts engine by + // calling dummy contract + let result = Contracts::bare_call( + ALICE, + addr_caller.clone(), + 0, + GAS_LIMIT, + None, + call.encode(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ) + .result + .unwrap(); + // Call to runtime should fail because of the re-entrancy guard + assert_return_code!(result, RuntimeReturnCode::CallRuntimeFailed); + }); +} + #[test] fn ecdsa_recover() { let (wasm, _code_hash) = compile_module::("ecdsa_recover").unwrap(); @@ -2889,7 +3138,8 @@ fn ecdsa_recover() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -2925,8 +3175,9 @@ fn ecdsa_recover() { GAS_LIMIT, None, params, - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -2935,6 +3186,288 @@ fn ecdsa_recover() { }) } +#[test] +fn bare_instantiate_returns_events() { + let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); + + let result = Contracts::bare_instantiate( + ALICE, + min_balance * 100, + GAS_LIMIT, + None, + Code::Upload(wasm), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::UnsafeCollect, + ); + + let events = result.events.unwrap(); + assert!(!events.is_empty()); + assert_eq!(events, System::events()); + }); +} + +#[test] +fn bare_instantiate_does_not_return_events() { + let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); + + let result = Contracts::bare_instantiate( + ALICE, + min_balance * 100, + GAS_LIMIT, + None, + Code::Upload(wasm), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ); + + let events = result.events; + assert!(!System::events().is_empty()); + assert!(events.is_none()); + }); +} + +#[test] +fn bare_call_returns_events() { + let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); + + let addr = Contracts::bare_instantiate( + ALICE, + min_balance * 100, + GAS_LIMIT, + None, + Code::Upload(wasm), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![], + DebugInfo::Skip, + CollectEvents::UnsafeCollect, + Determinism::Enforced, + ); + + let events = result.events.unwrap(); + assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); + assert!(!events.is_empty()); + assert_eq!(events, System::events()); + }); +} + +#[test] +fn bare_call_does_not_return_events() { + let (wasm, _code_hash) = compile_module::("transfer_return_code").unwrap(); + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let min_balance = ::Currency::minimum_balance(); + let _ = Balances::deposit_creating(&ALICE, 1000 * min_balance); + + let addr = Contracts::bare_instantiate( + ALICE, + min_balance * 100, + GAS_LIMIT, + None, + Code::Upload(wasm), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + let result = Contracts::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ); + + let events = result.events; + assert_return_code!(&result.result.unwrap(), RuntimeReturnCode::Success); + assert!(!System::events().is_empty()); + assert!(events.is_none()); + }); +} + +#[test] +fn sr25519_verify() { + let (wasm, _code_hash) = compile_module::("sr25519_verify").unwrap(); + + ExtBuilder::default().existential_deposit(50).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Instantiate the sr25519_verify contract. + let addr = Contracts::bare_instantiate( + ALICE, + 100_000, + GAS_LIMIT, + None, + Code::Upload(wasm), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + let call_with = |message: &[u8; 11]| { + // Alice's signature for "hello world" + #[rustfmt::skip] + let signature: [u8; 64] = [ + 184, 49, 74, 238, 78, 165, 102, 252, 22, 92, 156, 176, 124, 118, 168, 116, 247, + 99, 0, 94, 2, 45, 9, 170, 73, 222, 182, 74, 60, 32, 75, 64, 98, 174, 69, 55, 83, + 85, 180, 98, 208, 75, 231, 57, 205, 62, 4, 105, 26, 136, 172, 17, 123, 99, 90, 255, + 228, 54, 115, 63, 30, 207, 205, 131, + ]; + + // Alice's public key + #[rustfmt::skip] + let public_key: [u8; 32] = [ + 212, 53, 147, 199, 21, 253, 211, 28, 97, 20, 26, 189, 4, 169, 159, 214, 130, 44, + 133, 88, 133, 76, 205, 227, 154, 86, 132, 231, 165, 109, 162, 125, + ]; + + let mut params = vec![]; + params.extend_from_slice(&signature); + params.extend_from_slice(&public_key); + params.extend_from_slice(message); + + >::bare_call( + ALICE, + addr.clone(), + 0, + GAS_LIMIT, + None, + params, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ) + .result + .unwrap() + }; + + // verification should succeed for "hello world" + assert_return_code!(call_with(&b"hello world"), RuntimeReturnCode::Success); + + // verification should fail for other messages + assert_return_code!(call_with(&b"hello worlD"), RuntimeReturnCode::Sr25519VerifyFailed); + }); +} + +#[test] +fn failed_deposit_charge_should_roll_back_call() { + let (wasm_caller, _) = compile_module::("call_runtime_and_call").unwrap(); + let (wasm_callee, _) = compile_module::("store_call").unwrap(); + const ED: u64 = 200; + + let execute = || { + ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Instantiate both contracts. + let addr_caller = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm_caller.clone()), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + let addr_callee = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm_callee.clone()), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + // Give caller proxy access to Alice. + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(ALICE), addr_caller.clone(), (), 0)); + + // Create a Proxy call that will attempt to transfer away Alice's balance. + let transfer_call = + Box::new(RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { + dest: CHARLIE, + value: pallet_balances::Pallet::::free_balance(&ALICE) - 2 * ED, + })); + + // Wrap the transfer call in a proxy call. + let transfer_proxy_call = RuntimeCall::Proxy(pallet_proxy::Call::proxy { + real: ALICE, + force_proxy_type: Some(()), + call: transfer_call, + }); + + let data = ( + (ED - DepositPerItem::get()) as u32, // storage length + addr_callee, + transfer_proxy_call, + ); + + >::call( + RuntimeOrigin::signed(ALICE), + addr_caller.clone(), + 0, + GAS_LIMIT, + None, + data.encode(), + ) + }) + }; + + // With a low enough deposit per byte, the call should succeed. + let result = execute().unwrap(); + + // Bump the deposit per byte to a high value to trigger a FundsUnavailable error. + DEPOSIT_PER_BYTE.with(|c| *c.borrow_mut() = ED); + assert_err_with_weight!(execute(), TokenError::FundsUnavailable, result.actual_weight); +} + #[test] fn upload_code_works() { let (wasm, code_hash) = compile_module::("dummy").unwrap(); @@ -2945,14 +3478,16 @@ fn upload_code_works() { // Drop previous events initialize_block(2); - assert!(!>::contains_key(code_hash)); + assert!(!PristineCode::::contains_key(&code_hash)); + assert_ok!(Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm, Some(codec::Compact(1_000)), - Determinism::Deterministic, + Determinism::Enforced, )); - assert!(>::contains_key(code_hash)); + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); assert_eq!( System::events(), @@ -2961,7 +3496,7 @@ fn upload_code_works() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { who: ALICE, - amount: 173, + amount: deposit_expected, }), topics: vec![], }, @@ -2978,6 +3513,8 @@ fn upload_code_works() { #[test] fn upload_code_limit_too_low() { let (wasm, _code_hash) = compile_module::("dummy").unwrap(); + let deposit_expected = expected_deposit(wasm.len()); + let deposit_insufficient = deposit_expected.saturating_sub(1); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); @@ -2989,8 +3526,8 @@ fn upload_code_limit_too_low() { Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm, - Some(codec::Compact(100)), - Determinism::Deterministic + Some(codec::Compact(deposit_insufficient)), + Determinism::Enforced ), >::StorageDepositLimitExhausted, ); @@ -3002,9 +3539,11 @@ fn upload_code_limit_too_low() { #[test] fn upload_code_not_enough_balance() { let (wasm, _code_hash) = compile_module::("dummy").unwrap(); + let deposit_expected = expected_deposit(wasm.len()); + let deposit_insufficient = deposit_expected.saturating_sub(1); ExtBuilder::default().existential_deposit(100).build().execute_with(|| { - let _ = Balances::deposit_creating(&ALICE, 150); + let _ = Balances::deposit_creating(&ALICE, deposit_insufficient); // Drop previous events initialize_block(2); @@ -3014,7 +3553,7 @@ fn upload_code_not_enough_balance() { RuntimeOrigin::signed(ALICE), wasm, Some(codec::Compact(1_000)), - Determinism::Deterministic + Determinism::Enforced ), >::StorageDepositNotEnoughFunds, ); @@ -3037,13 +3576,12 @@ fn remove_code_works() { RuntimeOrigin::signed(ALICE), wasm, Some(codec::Compact(1_000)), - Determinism::Deterministic, + Determinism::Enforced, )); + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); - assert!(>::contains_key(code_hash)); assert_ok!(Contracts::remove_code(RuntimeOrigin::signed(ALICE), code_hash)); - assert!(!>::contains_key(code_hash)); - assert_eq!( System::events(), vec![ @@ -3051,7 +3589,7 @@ fn remove_code_works() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { who: ALICE, - amount: 173, + amount: deposit_expected, }), topics: vec![], }, @@ -3064,7 +3602,7 @@ fn remove_code_works() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Unreserved { who: ALICE, - amount: 173, + amount: deposit_expected, }), topics: vec![], }, @@ -3092,8 +3630,10 @@ fn remove_code_wrong_origin() { RuntimeOrigin::signed(ALICE), wasm, Some(codec::Compact(1_000)), - Determinism::Deterministic, + Determinism::Enforced, )); + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); assert_noop!( Contracts::remove_code(RuntimeOrigin::signed(BOB), code_hash), @@ -3107,7 +3647,7 @@ fn remove_code_wrong_origin() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { who: ALICE, - amount: 173, + amount: deposit_expected, }), topics: vec![], }, @@ -3188,36 +3728,50 @@ fn instantiate_with_zero_balance_works() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() .account_id; // Check that the BOB contract has been instantiated. - get_contract(&addr); + let contract = get_contract(&addr); + let deposit_account = contract.deposit_account().deref(); + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); // Make sure the account exists even though no free balance was send - assert_eq!(::Currency::free_balance(&addr), 0,); - assert_eq!( - ::Currency::total_balance(&addr), - ::Currency::minimum_balance(), - ); + assert_eq!(::Currency::free_balance(&addr), min_balance); + assert_eq!(::Currency::total_balance(&addr), min_balance,); assert_eq!( System::events(), vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { + who: ALICE, + amount: deposit_expected, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash }), + topics: vec![code_hash], + }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::NewAccount { - account: addr.clone() + account: deposit_account.clone(), }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { - account: addr.clone(), + account: deposit_account.clone(), free_balance: min_balance, }), topics: vec![], @@ -3226,31 +3780,34 @@ fn instantiate_with_zero_balance_works() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: ALICE, - to: addr.clone(), + to: deposit_account.clone(), amount: min_balance, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { - who: addr.clone(), - amount: min_balance, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: addr.clone(), }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { - who: ALICE, - amount: 173, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: addr.clone(), + free_balance: min_balance, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash }), - topics: vec![code_hash], + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: min_balance, + }), + topics: vec![], }, EventRecord { phase: Phase::Initialization, @@ -3284,36 +3841,49 @@ fn instantiate_with_below_existential_deposit_works() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() .account_id; // Check that the BOB contract has been instantiated. - get_contract(&addr); - - // Make sure the account exists even though no free balance was send - assert_eq!(::Currency::free_balance(&addr), 50,); - assert_eq!( - ::Currency::total_balance(&addr), - ::Currency::minimum_balance() + 50, - ); + let contract = get_contract(&addr); + let deposit_account = contract.deposit_account().deref(); + // Ensure the contract was stored and get expected deposit amount to be reserved. + let deposit_expected = expected_deposit(ensure_stored(code_hash)); + // Make sure the account exists even though not enough free balance was send + assert_eq!(::Currency::free_balance(&addr), min_balance + 50); + assert_eq!(::Currency::total_balance(&addr), min_balance + 50); assert_eq!( System::events(), vec![ + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { + who: ALICE, + amount: deposit_expected, + }), + topics: vec![], + }, + EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash }), + topics: vec![code_hash], + }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::System(frame_system::Event::NewAccount { - account: addr.clone() + account: deposit_account.clone(), }), topics: vec![], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { - account: addr.clone(), + account: deposit_account.clone(), free_balance: min_balance, }), topics: vec![], @@ -3322,40 +3892,43 @@ fn instantiate_with_below_existential_deposit_works() { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: ALICE, - to: addr.clone(), + to: deposit_account.clone(), amount: min_balance, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { - who: addr.clone(), - amount: min_balance, + event: RuntimeEvent::System(frame_system::Event::NewAccount { + account: addr.clone() }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: addr.clone(), - amount: 50, + event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { + account: addr.clone(), + free_balance: min_balance, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { - who: ALICE, - amount: 173, + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: min_balance, }), topics: vec![], }, EventRecord { phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::CodeStored { code_hash }), - topics: vec![code_hash], + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: ALICE, + to: addr.clone(), + amount: 50, + }), + topics: vec![], }, EventRecord { phase: Phase::Initialization, @@ -3385,7 +3958,8 @@ fn storage_deposit_works() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -3435,6 +4009,9 @@ fn storage_deposit_works() { deposit -= refunded0; assert_eq!(get_contract(&addr).total_deposit(), deposit); + let contract = get_contract(&addr); + let deposit_account = contract.deposit_account().deref(); + assert_eq!( System::events(), vec![ @@ -3450,24 +4027,16 @@ fn storage_deposit_works() { EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Called { - caller: ALICE, + caller: Origin::from_account_id(ALICE), contract: addr.clone(), }), - topics: vec![hash(&ALICE), hash(&addr)], + topics: vec![hash(&Origin::::from_account_id(ALICE)), hash(&addr)], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: ALICE, - to: addr.clone(), - amount: charged0, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { - who: addr.clone(), + to: deposit_account.clone(), amount: charged0, }), topics: vec![], @@ -3475,24 +4044,16 @@ fn storage_deposit_works() { EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Called { - caller: ALICE, + caller: Origin::from_account_id(ALICE), contract: addr.clone(), }), - topics: vec![hash(&ALICE), hash(&addr)], + topics: vec![hash(&Origin::::from_account_id(ALICE)), hash(&addr)], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { from: ALICE, - to: addr.clone(), - amount: charged1, - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Reserved { - who: addr.clone(), + to: deposit_account.clone(), amount: charged1, }), topics: vec![], @@ -3500,18 +4061,17 @@ fn storage_deposit_works() { EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Called { - caller: ALICE, + caller: Origin::from_account_id(ALICE), contract: addr.clone(), }), - topics: vec![hash(&ALICE), hash(&addr)], + topics: vec![hash(&Origin::::from_account_id(ALICE)), hash(&addr)], }, EventRecord { phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::ReserveRepatriated { - from: addr.clone(), + event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { + from: deposit_account.clone(), to: ALICE, amount: refunded0, - destination_status: BalanceStatus::Free, }), topics: vec![], }, @@ -3520,6 +4080,61 @@ fn storage_deposit_works() { }); } +#[test] +fn storage_deposit_callee_works() { + let (wasm_caller, _code_hash_caller) = compile_module::("call").unwrap(); + let (wasm_callee, _code_hash_callee) = compile_module::("store_call").unwrap(); + const ED: u64 = 200; + ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + // Create both contracts: Constructors do nothing. + let addr_caller = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm_caller), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + let addr_callee = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm_callee), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr_caller, + 0, + GAS_LIMIT, + None, + (100u32, &addr_callee).encode() + )); + + let callee = get_contract(&addr_callee); + let deposit = ED + DepositPerByte::get() * 100 + DepositPerItem::get() * 1; + + assert_eq!(test_utils::get_balance(callee.deposit_account()), deposit); + assert_eq!(callee.total_deposit(), deposit); + }); +} + #[test] fn set_code_extrinsic() { let (wasm, code_hash) = compile_module::("dummy").unwrap(); @@ -3538,7 +4153,8 @@ fn set_code_extrinsic() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -3548,7 +4164,7 @@ fn set_code_extrinsic() { RuntimeOrigin::signed(ALICE), new_wasm, None, - Determinism::Deterministic + Determinism::Enforced )); // Drop previous events @@ -3609,11 +4225,10 @@ fn set_code_extrinsic() { } #[test] -fn call_after_killed_account_needs_funding() { +fn slash_cannot_kill_account() { let (wasm, _code_hash) = compile_module::("dummy").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); - let min_balance = ::Currency::minimum_balance(); let addr = Contracts::bare_instantiate( ALICE, @@ -3623,116 +4238,34 @@ fn call_after_killed_account_needs_funding() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() .account_id; // Drop previous events - initialize_block(2); - - // Destroy the account of the contract by slashing. - // Slashing can actually happen if the contract takes part in staking. - // It is a corner case and we accept the destruction of the account. - let _ = ::Currency::slash( - &addr, - ::Currency::total_balance(&addr), - ); - - // Sending below the minimum balance will fail the call because it needs to create the - // account in order to send balance there. - assert_err_ignore_postinfo!( - Contracts::call( - RuntimeOrigin::signed(ALICE), - addr.clone(), - min_balance - 1, - GAS_LIMIT, - None, - vec![], - ), - >::TransferFailed - ); - - // Sending zero should work as it does not do a transfer - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - addr.clone(), - 0, - GAS_LIMIT, - None, - vec![], - )); - - // Sending minimum balance should work - assert_ok!(Contracts::call( - RuntimeOrigin::signed(ALICE), - addr.clone(), - min_balance, - GAS_LIMIT, - None, - vec![], - )); - - assert_eq!( - System::events(), - vec![ - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::System(frame_system::Event::KilledAccount { - account: addr.clone() - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Slashed { - who: addr.clone(), - amount: min_balance + 700 - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: ALICE, - contract: addr.clone(), - }), - topics: vec![hash(&ALICE), hash(&addr)], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::System(frame_system::Event::NewAccount { - account: addr.clone() - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Endowed { - account: addr.clone(), - free_balance: min_balance - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Balances(pallet_balances::Event::Transfer { - from: ALICE, - to: addr.clone(), - amount: min_balance - }), - topics: vec![], - }, - EventRecord { - phase: Phase::Initialization, - event: RuntimeEvent::Contracts(crate::Event::Called { - caller: ALICE, - contract: addr.clone(), - }), - topics: vec![hash(&ALICE), hash(&addr)], - }, - ] + initialize_block(2); + + // Try to destroy the account of the contract by slashing. + // The account does not get destroyed because of the consumer reference. + // Slashing can for example happen if the contract takes part in staking. + let _ = ::Currency::slash( + &addr, + ::Currency::total_balance(&addr), + ); + + assert_eq!( + System::events(), + vec![EventRecord { + phase: Phase::Initialization, + event: RuntimeEvent::Balances(pallet_balances::Event::Slashed { + who: addr.clone(), + amount: 700, // slash didn't remove the minimum balance + }), + topics: vec![], + },] ); }); } @@ -3752,7 +4285,7 @@ fn contract_reverted() { RuntimeOrigin::signed(ALICE), wasm.clone(), None, - Determinism::Deterministic + Determinism::Enforced )); // Calling extrinsic: revert leads to an error @@ -3795,7 +4328,8 @@ fn contract_reverted() { Code::Existing(code_hash), input.clone(), vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap(); @@ -3812,7 +4346,8 @@ fn contract_reverted() { Code::Existing(code_hash), ReturnFlags::empty().bits().encode(), vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -3839,8 +4374,9 @@ fn contract_reverted() { GAS_LIMIT, None, input, - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -3860,7 +4396,7 @@ fn code_rejected_error_works() { RuntimeOrigin::signed(ALICE), wasm.clone(), None, - Determinism::Deterministic + Determinism::Enforced ), >::CodeRejected, ); @@ -3872,21 +4408,22 @@ fn code_rejected_error_works() { Code::Upload(wasm), vec![], vec![], - true, + DebugInfo::UnsafeDebug, + CollectEvents::Skip, ); assert_err!(result.result, >::CodeRejected); assert_eq!( std::str::from_utf8(&result.debug_message).unwrap(), - "validation of new code failed" + "Can't load the module into wasmi!" ); - let (wasm, _) = compile_module::("invalid_contract").unwrap(); + let (wasm, _) = compile_module::("invalid_contract_no_call").unwrap(); assert_noop!( Contracts::upload_code( RuntimeOrigin::signed(ALICE), wasm.clone(), None, - Determinism::Deterministic + Determinism::Enforced ), >::CodeRejected, ); @@ -3899,13 +4436,42 @@ fn code_rejected_error_works() { Code::Upload(wasm), vec![], vec![], - true, + DebugInfo::UnsafeDebug, + CollectEvents::Skip, ); assert_err!(result.result, >::CodeRejected); assert_eq!( std::str::from_utf8(&result.debug_message).unwrap(), "call function isn't exported" ); + + let (wasm, _) = compile_module::("invalid_contract_no_memory").unwrap(); + assert_noop!( + Contracts::upload_code( + RuntimeOrigin::signed(ALICE), + wasm.clone(), + None, + Determinism::Enforced + ), + >::CodeRejected, + ); + + let result = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm), + vec![], + vec![], + DebugInfo::UnsafeDebug, + CollectEvents::Skip, + ); + assert_err!(result.result, >::CodeRejected); + assert_eq!( + std::str::from_utf8(&result.debug_message).unwrap(), + "No memory import found in the module" + ); }); } @@ -3926,7 +4492,8 @@ fn set_code_hash() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -3936,7 +4503,7 @@ fn set_code_hash() { RuntimeOrigin::signed(ALICE), new_wasm.clone(), None, - Determinism::Deterministic + Determinism::Enforced )); System::reset_events(); @@ -3949,8 +4516,9 @@ fn set_code_hash() { GAS_LIMIT, None, new_code_hash.as_ref().to_vec(), - true, - Determinism::Deterministic, + DebugInfo::UnsafeDebug, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -3964,8 +4532,9 @@ fn set_code_hash() { GAS_LIMIT, None, vec![], - true, - Determinism::Deterministic, + DebugInfo::UnsafeDebug, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -3987,18 +4556,24 @@ fn set_code_hash() { EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Called { - caller: ALICE, + caller: Origin::from_account_id(ALICE), contract: contract_addr.clone(), }), - topics: vec![hash(&ALICE), hash(&contract_addr)], + topics: vec![ + hash(&Origin::::from_account_id(ALICE)), + hash(&contract_addr) + ], }, EventRecord { phase: Phase::Initialization, event: RuntimeEvent::Contracts(crate::Event::Called { - caller: ALICE, + caller: Origin::from_account_id(ALICE), contract: contract_addr.clone(), }), - topics: vec![hash(&ALICE), hash(&contract_addr)], + topics: vec![ + hash(&Origin::::from_account_id(ALICE)), + hash(&contract_addr) + ], }, ], ); @@ -4007,11 +4582,29 @@ fn set_code_hash() { #[test] fn storage_deposit_limit_is_enforced() { - let (wasm, _code_hash) = compile_module::("store").unwrap(); - ExtBuilder::default().existential_deposit(200).build().execute_with(|| { + let ed = 200; + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); + ExtBuilder::default().existential_deposit(ed).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let min_balance = ::Currency::minimum_balance(); + // Setting insufficient storage_deposit should fail. + assert_err!( + Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + Some((2 * ed + 3 - 1).into()), // expected deposit is 2 * ed + 3 for the call + Code::Upload(wasm.clone()), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result, + >::StorageDepositLimitExhausted, + ); + // Instantiate the BOB contract. let addr = Contracts::bare_instantiate( ALICE, @@ -4021,7 +4614,8 @@ fn storage_deposit_limit_is_enforced() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -4031,15 +4625,47 @@ fn storage_deposit_limit_is_enforced() { assert_eq!(get_contract(&addr).total_deposit(), min_balance); assert_eq!(::Currency::total_balance(&addr), min_balance); - // Create 100 bytes of storage with a price of per byte + // Create 1 byte of storage with a price of per byte, + // setting insufficient deposit limit, as it requires 3 Balance: + // 2 for the item added + 1 for the new storage item. assert_err_ignore_postinfo!( Contracts::call( RuntimeOrigin::signed(ALICE), addr.clone(), 0, GAS_LIMIT, - Some(codec::Compact(1)), - 100u32.to_le_bytes().to_vec() + Some(codec::Compact(2)), + 1u32.to_le_bytes().to_vec() + ), + >::StorageDepositLimitExhausted, + ); + + // To check that deposit limit fallbacks to DefaultDepositLimit, + // we customize it here. + DEFAULT_DEPOSIT_LIMIT.with(|c| *c.borrow_mut() = 3); + + // Create 1 byte of storage, should cost 3 Balance: + // 2 for the item added + 1 for the new storage item. + // Should pass as it fallbacks to DefaultDepositLimit. + assert_ok!(Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + 1u32.to_le_bytes().to_vec() + )); + + // Use 4 more bytes of the storage for the same item, which requires 4 Balance. + // Should fail as DefaultDepositLimit is 3 and hence isn't enough. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + addr.clone(), + 0, + GAS_LIMIT, + None, + 5u32.to_le_bytes().to_vec() ), >::StorageDepositLimitExhausted, ); @@ -4047,10 +4673,10 @@ fn storage_deposit_limit_is_enforced() { } #[test] -fn storage_deposit_limit_is_enforced_late() { +fn deposit_limit_in_nested_calls() { let (wasm_caller, _code_hash_caller) = compile_module::("create_storage_and_call").unwrap(); - let (wasm_callee, _code_hash_callee) = compile_module::("store").unwrap(); + let (wasm_callee, _code_hash_callee) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); @@ -4063,7 +4689,8 @@ fn storage_deposit_limit_is_enforced_late() { Code::Upload(wasm_caller), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -4076,7 +4703,8 @@ fn storage_deposit_limit_is_enforced_late() { Code::Upload(wasm_callee), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -4093,27 +4721,28 @@ fn storage_deposit_limit_is_enforced_late() { 100u32.to_le_bytes().to_vec() )); - // We do not remove any storage but require 14 bytes of storage for the new - // storage created in the immediate contract. + // We do not remove any storage but add a storage item of 12 bytes in the caller + // contract. This would cost 12 + 2 = 14 Balance. + // The nested call doesn't get a special limit, which is set by passing 0 to it. + // This should fail as the specified parent's limit is less than the cost: 13 < + // 14. assert_err_ignore_postinfo!( Contracts::call( RuntimeOrigin::signed(ALICE), addr_caller.clone(), 0, GAS_LIMIT, - Some(codec::Compact(5)), - 100u32 - .to_le_bytes() - .as_ref() - .iter() - .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) - .cloned() - .collect(), + Some(codec::Compact(13)), + (100u32, &addr_callee, 0u64).encode(), ), >::StorageDepositLimitExhausted, ); - - // Allow for the additional 14 bytes but demand an additional byte in the callee contract. + // Now we specify the parent's limit high enough to cover the caller's storage additions. + // However, we use a single byte more in the callee, hence the storage deposit should be 15 + // Balance. + // The nested call doesn't get a special limit, which is set by passing 0 to it. + // This should fail as the specified parent's limit is less than the cost: 14 + // < 15. assert_err_ignore_postinfo!( Contracts::call( RuntimeOrigin::signed(ALICE), @@ -4121,19 +4750,31 @@ fn storage_deposit_limit_is_enforced_late() { 0, GAS_LIMIT, Some(codec::Compact(14)), - 101u32 - .to_le_bytes() - .as_ref() - .iter() - .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) - .cloned() - .collect(), + (101u32, &addr_callee, 0u64).encode(), + ), + >::StorageDepositLimitExhausted, + ); + + // Now we specify the parent's limit high enough to cover both the caller's and callee's + // storage additions. However, we set a special deposit limit of 1 Balance for the nested + // call. This should fail as callee adds up 2 bytes to the storage, meaning that the nested + // call should have a deposit limit of at least 2 Balance. The sub-call should be rolled + // back, which is covered by the next test case. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(ALICE), + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(16)), + (102u32, &addr_callee, 1u64).encode(), ), >::StorageDepositLimitExhausted, ); - // Refund in the callee contract but not enough to cover the 14 balance required by the - // caller. + // Refund in the callee contract but not enough to cover the 14 Balance required by the + // caller. Note that if previous sub-call wouldn't roll back, this call would pass making + // the test case fail. We don't set a special limit for the nested call here. assert_err_ignore_postinfo!( Contracts::call( RuntimeOrigin::signed(ALICE), @@ -4141,59 +4782,198 @@ fn storage_deposit_limit_is_enforced_late() { 0, GAS_LIMIT, Some(codec::Compact(0)), - 87u32 - .to_le_bytes() - .as_ref() - .iter() - .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) - .cloned() - .collect(), + (87u32, &addr_callee, 0u64).encode(), ), >::StorageDepositLimitExhausted, ); let _ = Balances::make_free_balance_be(&ALICE, 1_000); - // Send more than the sender has balance. + // Require more than the sender's balance. + // We don't set a special limit for the nested call. assert_err_ignore_postinfo!( Contracts::call( RuntimeOrigin::signed(ALICE), addr_caller.clone(), 0, GAS_LIMIT, - Some(codec::Compact(50)), - 1_200u32 - .to_le_bytes() - .as_ref() - .iter() - .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) - .cloned() - .collect(), + None, + (1200u32, &addr_callee, 1u64).encode(), ), >::StorageDepositLimitExhausted, ); - // Same as above but allow for the additional balance. + // Same as above but allow for the additional deposit of 1 Balance in parent. + // We set the special deposit limit of 1 Balance for the nested call, which isn't + // enforced as callee frees up storage. This should pass. assert_ok!(Contracts::call( RuntimeOrigin::signed(ALICE), addr_caller.clone(), 0, GAS_LIMIT, Some(codec::Compact(1)), - 87u32 - .to_le_bytes() - .as_ref() - .iter() - .chain(<_ as AsRef<[u8]>>::as_ref(&addr_callee)) - .cloned() - .collect(), + (87u32, &addr_callee, 1u64).encode(), )); }); } +#[test] +fn deposit_limit_in_nested_instantiate() { + let (wasm_caller, _code_hash_caller) = + compile_module::("create_storage_and_instantiate").unwrap(); + let (wasm_callee, code_hash_callee) = compile_module::("store_deploy").unwrap(); + const ED: u64 = 5; + ExtBuilder::default().existential_deposit(ED).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + let _ = Balances::deposit_creating(&BOB, 1_000_000); + // Create caller contract + let addr_caller = Contracts::bare_instantiate( + ALICE, + 10_000u64, // this balance is later passed to the deployed contract + GAS_LIMIT, + None, + Code::Upload(wasm_caller), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + // Deploy a contract to get its occupied storage size + let addr = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm_callee), + vec![0, 0, 0, 0], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + let callee_info_len = ContractInfoOf::::get(&addr).unwrap().encoded_size() as u64; + + // We don't set a special deposit limit for the nested instantiation. + // + // The deposit limit set for the parent is insufficient for the instantiation, which + // requires: + // - callee_info_len + 2 for storing the new contract info, + // - ED for deployed contract account, + // - 2 for the storage item of 0 bytes being created in the callee constructor + // or (callee_info_len + 2 + ED + 2) Balance in total. + // + // Provided the limit is set to be 1 Balance less, + // this call should fail on the return from the caller contract. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(BOB), + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(callee_info_len + 2 + ED + 1)), + (0u32, &code_hash_callee, 0u64).encode(), + ), + >::StorageDepositLimitExhausted, + ); + // The charges made on instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Now we give enough limit for the instantiation itself, but require for 1 more storage + // byte in the constructor. Hence +1 Balance to the limit is needed. This should fail on the + // return from constructor. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(BOB), + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(callee_info_len + 2 + ED + 2)), + (1u32, &code_hash_callee, 0u64).encode(), + ), + >::StorageDepositLimitExhausted, + ); + // The charges made on the instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Now we set enough limit in parent call, but an insufficient limit for child instantiate. + // This should fail during the charging for the instantiation in + // `RawMeter::charge_instantiate()` + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(BOB), + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(callee_info_len + 2 + ED + 2)), + (0u32, &code_hash_callee, callee_info_len + 2 + ED + 1).encode(), + ), + >::StorageDepositLimitExhausted, + ); + // The charges made on the instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Same as above but requires for single added storage + // item of 1 byte to be covered by the limit, which implies 3 more Balance. + // Now we set enough limit for the parent call, but insufficient limit for child + // instantiate. This should fail right after the constructor execution. + assert_err_ignore_postinfo!( + Contracts::call( + RuntimeOrigin::signed(BOB), + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(callee_info_len + 2 + ED + 3)), // enough parent limit + (1u32, &code_hash_callee, callee_info_len + 2 + ED + 2).encode(), + ), + >::StorageDepositLimitExhausted, + ); + // The charges made on the instantiation should be rolled back. + assert_eq!(::Currency::free_balance(&BOB), 1_000_000); + + // Set enough deposit limit for the child instantiate. This should succeed. + let result = Contracts::bare_call( + BOB, + addr_caller.clone(), + 0, + GAS_LIMIT, + Some(codec::Compact(callee_info_len + 2 + ED + 4).into()), + (1u32, &code_hash_callee, callee_info_len + 2 + ED + 3).encode(), + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, + ); + + let returned = result.result.unwrap(); + // All balance of the caller except ED has been transferred to the callee. + // No deposit has been taken from it. + assert_eq!(::Currency::free_balance(&addr_caller), ED); + // Get address of the deployed contract. + let addr_callee = AccountId32::from_slice(&returned.data[0..32]).unwrap(); + // 10_000 should be sent to callee from the caller contract, plus ED to be sent from the + // origin. + assert_eq!(::Currency::free_balance(&addr_callee), 10_000 + ED); + // The origin should be charged with: + // - callee instantiation deposit = (callee_info_len + 2) + // - callee account ED + // - for writing an item of 1 byte to storage = 3 Balance + assert_eq!( + ::Currency::free_balance(&BOB), + 1_000_000 - (callee_info_len + 2 + ED + 3) + ); + // Check that deposit due to be charged still includes these 3 Balance + assert_eq!(result.storage_deposit.charge_or_zero(), (callee_info_len + 2 + ED + 3),) + }); +} + #[test] fn deposit_limit_honors_liquidity_restrictions() { - let (wasm, _code_hash) = compile_module::("store").unwrap(); + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let _ = Balances::deposit_creating(&BOB, 1_000); @@ -4208,7 +4988,8 @@ fn deposit_limit_honors_liquidity_restrictions() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -4237,7 +5018,7 @@ fn deposit_limit_honors_liquidity_restrictions() { #[test] fn deposit_limit_honors_existential_deposit() { - let (wasm, _code_hash) = compile_module::("store").unwrap(); + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let _ = Balances::deposit_creating(&BOB, 1_000); @@ -4252,7 +5033,8 @@ fn deposit_limit_honors_existential_deposit() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -4280,7 +5062,7 @@ fn deposit_limit_honors_existential_deposit() { #[test] fn deposit_limit_honors_min_leftover() { - let (wasm, _code_hash) = compile_module::("store").unwrap(); + let (wasm, _code_hash) = compile_module::("store_call").unwrap(); ExtBuilder::default().existential_deposit(200).build().execute_with(|| { let _ = Balances::deposit_creating(&ALICE, 1_000_000); let _ = Balances::deposit_creating(&BOB, 1_000); @@ -4295,7 +5077,8 @@ fn deposit_limit_honors_min_leftover() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -4305,7 +5088,7 @@ fn deposit_limit_honors_min_leftover() { assert_eq!(get_contract(&addr).total_deposit(), min_balance); assert_eq!(::Currency::total_balance(&addr), min_balance); - // check that the minumum leftover (value send) is considered + // check that the minimum leftover (value send) is considered assert_err_ignore_postinfo!( Contracts::call( RuntimeOrigin::signed(BOB), @@ -4350,7 +5133,8 @@ fn cannot_instantiate_indeterministic_code() { Code::Upload(wasm.clone()), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result, >::CodeRejected, @@ -4362,7 +5146,7 @@ fn cannot_instantiate_indeterministic_code() { RuntimeOrigin::signed(ALICE), wasm.clone(), None, - Determinism::Deterministic + Determinism::Enforced ), >::CodeRejected, ); @@ -4372,8 +5156,9 @@ fn cannot_instantiate_indeterministic_code() { RuntimeOrigin::signed(ALICE), wasm, None, - Determinism::AllowIndeterminism, + Determinism::Relaxed, )); + assert_err_ignore_postinfo!( Contracts::instantiate( RuntimeOrigin::signed(ALICE), @@ -4395,7 +5180,8 @@ fn cannot_instantiate_indeterministic_code() { Code::Existing(code_hash), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result, >::Indeterministic, @@ -4410,7 +5196,8 @@ fn cannot_instantiate_indeterministic_code() { Code::Upload(caller_wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -4425,8 +5212,9 @@ fn cannot_instantiate_indeterministic_code() { GAS_LIMIT, None, code_hash.encode(), - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result, >::Indeterministic, @@ -4441,8 +5229,9 @@ fn cannot_instantiate_indeterministic_code() { GAS_LIMIT, None, code_hash.encode(), - false, - Determinism::AllowIndeterminism, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Relaxed, ) .result, >::Indeterministic, @@ -4462,7 +5251,7 @@ fn cannot_set_code_indeterministic_code() { RuntimeOrigin::signed(ALICE), wasm, None, - Determinism::AllowIndeterminism, + Determinism::Relaxed, )); // Create the contract that will call `seal_set_code_hash` @@ -4474,7 +5263,8 @@ fn cannot_set_code_indeterministic_code() { Code::Upload(caller_wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -4489,8 +5279,9 @@ fn cannot_set_code_indeterministic_code() { GAS_LIMIT, None, code_hash.encode(), - false, - Determinism::AllowIndeterminism, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Relaxed, ) .result, >::Indeterministic, @@ -4510,7 +5301,7 @@ fn delegate_call_indeterministic_code() { RuntimeOrigin::signed(ALICE), wasm, None, - Determinism::AllowIndeterminism, + Determinism::Relaxed, )); // Create the contract that will call `seal_delegate_call` @@ -4522,7 +5313,8 @@ fn delegate_call_indeterministic_code() { Code::Upload(caller_wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -4537,8 +5329,9 @@ fn delegate_call_indeterministic_code() { GAS_LIMIT, None, code_hash.encode(), - false, - Determinism::Deterministic, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Enforced, ) .result, >::Indeterministic, @@ -4553,8 +5346,9 @@ fn delegate_call_indeterministic_code() { GAS_LIMIT, None, code_hash.encode(), - false, - Determinism::AllowIndeterminism, + DebugInfo::Skip, + CollectEvents::Skip, + Determinism::Relaxed, ) .result ); @@ -4576,7 +5370,8 @@ fn reentrance_count_works_with_call() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -4592,8 +5387,9 @@ fn reentrance_count_works_with_call() { GAS_LIMIT, None, input, - true, - Determinism::Deterministic, + DebugInfo::UnsafeDebug, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -4615,7 +5411,8 @@ fn reentrance_count_works_with_delegated_call() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -4631,8 +5428,9 @@ fn reentrance_count_works_with_delegated_call() { GAS_LIMIT, None, input, - true, - Determinism::Deterministic, + DebugInfo::UnsafeDebug, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -4656,7 +5454,8 @@ fn account_reentrance_count_works() { Code::Upload(wasm), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -4670,7 +5469,8 @@ fn account_reentrance_count_works() { Code::Upload(wasm_reentrance_count), vec![], vec![], - false, + DebugInfo::Skip, + CollectEvents::Skip, ) .result .unwrap() @@ -4683,8 +5483,9 @@ fn account_reentrance_count_works() { GAS_LIMIT, None, contract_addr.encode(), - true, - Determinism::Deterministic, + DebugInfo::UnsafeDebug, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -4696,8 +5497,9 @@ fn account_reentrance_count_works() { GAS_LIMIT, None, another_contract_addr.encode(), - true, - Determinism::Deterministic, + DebugInfo::UnsafeDebug, + CollectEvents::Skip, + Determinism::Enforced, ) .result .unwrap(); @@ -4706,3 +5508,123 @@ fn account_reentrance_count_works() { assert_eq!(result2.data, 0.encode()); }); } + +#[test] +fn root_cannot_upload_code() { + let (wasm, _) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Contracts::upload_code(RuntimeOrigin::root(), wasm, None, Determinism::Enforced), + DispatchError::BadOrigin, + ); + }); +} + +#[test] +fn root_cannot_remove_code() { + let (_, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Contracts::remove_code(RuntimeOrigin::root(), code_hash), + DispatchError::BadOrigin, + ); + }); +} + +#[test] +fn signed_cannot_set_code() { + let (_, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Contracts::set_code(RuntimeOrigin::signed(ALICE), BOB, code_hash), + DispatchError::BadOrigin, + ); + }); +} + +#[test] +fn none_cannot_call_code() { + ExtBuilder::default().build().execute_with(|| { + assert_noop!( + Contracts::call(RuntimeOrigin::none(), BOB, 0, GAS_LIMIT, None, Vec::new()), + DispatchError::BadOrigin, + ); + }); +} + +#[test] +fn root_can_call() { + let (wasm, _) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().existential_deposit(100).build().execute_with(|| { + let _ = Balances::deposit_creating(&ALICE, 1_000_000); + + let addr = Contracts::bare_instantiate( + ALICE, + 0, + GAS_LIMIT, + None, + Code::Upload(wasm), + vec![], + vec![], + DebugInfo::Skip, + CollectEvents::Skip, + ) + .result + .unwrap() + .account_id; + + // Call the contract. + assert_ok!(Contracts::call( + RuntimeOrigin::root(), + addr.clone(), + 0, + GAS_LIMIT, + None, + vec![] + )); + }); +} + +#[test] +fn root_cannot_instantiate_with_code() { + let (wasm, _) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_err_ignore_postinfo!( + Contracts::instantiate_with_code( + RuntimeOrigin::root(), + 0, + GAS_LIMIT, + None, + wasm, + vec![], + vec![], + ), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn root_cannot_instantiate() { + let (_, code_hash) = compile_module::("dummy").unwrap(); + + ExtBuilder::default().build().execute_with(|| { + assert_err_ignore_postinfo!( + Contracts::instantiate( + RuntimeOrigin::root(), + 0, + GAS_LIMIT, + None, + code_hash, + vec![], + vec![], + ), + DispatchError::RootNotAllowed + ); + }); +} diff --git a/frame/contracts/src/tests/pallet_dummy.rs b/frame/contracts/src/tests/pallet_dummy.rs new file mode 100644 index 000000000000..d84e34de06c8 --- /dev/null +++ b/frame/contracts/src/tests/pallet_dummy.rs @@ -0,0 +1,33 @@ +pub use pallet::*; + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::{ + dispatch::{Pays, PostDispatchInfo}, + pallet_prelude::DispatchResultWithPostInfo, + weights::Weight, + }; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::call] + impl Pallet { + /// Dummy function that overcharges the predispatch weight, allowing us to test the correct + /// values of [`ContractResult::gas_consumed`] and [`ContractResult::gas_required`] in + /// tests. + #[pallet::call_index(1)] + #[pallet::weight(Weight::from_parts(10_000_000, 0))] + pub fn overestimate_pre_charge(origin: OriginFor) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + Ok(PostDispatchInfo { + actual_weight: Some(Weight::from_parts(100, 0)), + pays_fee: Pays::Yes, + }) + } + } +} diff --git a/frame/contracts/src/wasm/code_cache.rs b/frame/contracts/src/wasm/code_cache.rs deleted file mode 100644 index 410d26921543..000000000000 --- a/frame/contracts/src/wasm/code_cache.rs +++ /dev/null @@ -1,231 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2018-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. - -//! A module that implements instrumented code cache. -//! -//! - In order to run contract code we need to instrument it with gas metering. -//! To do that we need to provide the schedule which will supply exact gas costs values. -//! We cache this code in the storage saving the schedule version. -//! - Before running contract code we check if the cached code has the schedule version that -//! is equal to the current saved schedule. -//! If it is equal then run the code, if it isn't reinstrument with the current schedule. -//! - When we update the schedule we want it to have strictly greater version than the current saved -//! one: -//! this guarantees that every instrumented contract code in cache cannot have the version equal to -//! the current one. Thus, before executing a contract it should be reinstrument with new schedule. - -use crate::{ - gas::{GasMeter, Token}, - wasm::{prepare, PrefabWasmModule}, - weights::WeightInfo, - CodeHash, CodeStorage, Config, Error, Event, OwnerInfoOf, Pallet, PristineCode, Schedule, - Weight, -}; -use frame_support::{ - dispatch::{DispatchError, DispatchResult}, - ensure, - traits::{Get, ReservableCurrency}, - WeakBoundedVec, -}; -use sp_runtime::traits::BadOrigin; -use sp_std::vec; - -/// Put the instrumented module in storage. -/// -/// Increments the refcount of the in-storage `prefab_module` if it already exists in storage -/// under the specified `code_hash`. -pub fn store(mut module: PrefabWasmModule, instantiated: bool) -> DispatchResult { - let code_hash = sp_std::mem::take(&mut module.code_hash); - >::mutate(&code_hash, |existing| match existing { - Some(existing) => { - // We instrument any uploaded contract anyways. We might as well store it to save - // a potential re-instrumentation later. - existing.code = module.code; - existing.instruction_weights_version = module.instruction_weights_version; - // When the code was merely uploaded but not instantiated we can skip this. - if instantiated { - >::mutate(&code_hash, |owner_info| { - if let Some(owner_info) = owner_info { - owner_info.refcount = owner_info.refcount.checked_add(1).expect( - " - refcount is 64bit. Generating this overflow would require to store - _at least_ 18 exabyte of data assuming that a contract consumes only - one byte of data. Any node would run out of storage space before hitting - this overflow. - qed - ", - ); - } - }) - } - Ok(()) - }, - None => { - let orig_code = module.original_code.take().expect( - " - If an executable isn't in storage it was uploaded. - If it was uploaded the original code must exist. qed - ", - ); - let mut owner_info = module.owner_info.take().expect( - "If an executable isn't in storage it was uploaded. - If it was uploaded the owner info was generated and attached. qed - ", - ); - // This `None` case happens only in freshly uploaded modules. This means that - // the `owner` is always the origin of the current transaction. - T::Currency::reserve(&owner_info.owner, owner_info.deposit) - .map_err(|_| >::StorageDepositNotEnoughFunds)?; - owner_info.refcount = if instantiated { 1 } else { 0 }; - >::insert(&code_hash, orig_code); - >::insert(&code_hash, owner_info); - *existing = Some(module); - >::deposit_event(vec![code_hash], Event::CodeStored { code_hash }); - Ok(()) - }, - }) -} - -/// Decrement the refcount of a code in-storage by one. -/// -/// # Note -/// -/// A contract whose refcount dropped to zero isn't automatically removed. A `remove_code` -/// transaction must be submitted by the original uploader to do so. -pub fn decrement_refcount(code_hash: CodeHash) { - >::mutate(code_hash, |existing| { - if let Some(info) = existing { - info.refcount = info.refcount.saturating_sub(1); - } - }); -} - -/// Increment the refcount of a code in-storage by one. -/// -/// # Errors -/// -/// [`Error::CodeNotFound`] is returned if the specified `code_hash` does not exist. -pub fn increment_refcount(code_hash: CodeHash) -> Result<(), DispatchError> { - >::mutate(code_hash, |existing| -> Result<(), DispatchError> { - if let Some(info) = existing { - info.refcount = info.refcount.saturating_add(1); - Ok(()) - } else { - Err(Error::::CodeNotFound.into()) - } - }) -} - -/// Try to remove code together with all associated information. -pub fn try_remove(origin: &T::AccountId, code_hash: CodeHash) -> DispatchResult { - >::try_mutate_exists(&code_hash, |existing| { - if let Some(owner_info) = existing { - ensure!(owner_info.refcount == 0, >::CodeInUse); - ensure!(&owner_info.owner == origin, BadOrigin); - T::Currency::unreserve(&owner_info.owner, owner_info.deposit); - *existing = None; - >::remove(&code_hash); - >::remove(&code_hash); - >::deposit_event(vec![code_hash], Event::CodeRemoved { code_hash }); - Ok(()) - } else { - Err(>::CodeNotFound.into()) - } - }) -} - -/// Load code with the given code hash. -/// -/// If the module was instrumented with a lower version of schedule than -/// the current one given as an argument, then this function will perform -/// re-instrumentation and update the cache in the storage. -pub fn load( - code_hash: CodeHash, - schedule: &Schedule, - gas_meter: &mut GasMeter, -) -> Result, DispatchError> { - let max_code_len = T::MaxCodeLen::get(); - let charged = gas_meter.charge(CodeToken::Load(max_code_len))?; - - let mut prefab_module = >::get(code_hash).ok_or(Error::::CodeNotFound)?; - gas_meter.adjust_gas(charged, CodeToken::Load(prefab_module.code.len() as u32)); - prefab_module.code_hash = code_hash; - - if prefab_module.instruction_weights_version < schedule.instruction_weights.version { - // The instruction weights have changed. - // We need to re-instrument the code with the new instruction weights. - let charged = gas_meter.charge(CodeToken::Reinstrument(max_code_len))?; - let code_size = reinstrument(&mut prefab_module, schedule)?; - gas_meter.adjust_gas(charged, CodeToken::Reinstrument(code_size)); - } - - Ok(prefab_module) -} - -/// Instruments the passed prefab wasm module with the supplied schedule. -/// -/// Returns the size in bytes of the uninstrumented code. -pub fn reinstrument( - prefab_module: &mut PrefabWasmModule, - schedule: &Schedule, -) -> Result { - let original_code = - >::get(&prefab_module.code_hash).ok_or(Error::::CodeNotFound)?; - let original_code_len = original_code.len(); - // We need to allow contracts growing too big after re-instrumentation. Otherwise - // the contract can become inaccessible. The user has no influence over this size - // as the contract is already deployed and every change in size would be the result - // of changes in the instrumentation algorithm controlled by the chain authors. - prefab_module.code = WeakBoundedVec::force_from( - prepare::reinstrument::( - &original_code, - schedule, - prefab_module.determinism, - )?, - Some("Contract exceeds size limit after re-instrumentation."), - ); - prefab_module.instruction_weights_version = schedule.instruction_weights.version; - >::insert(&prefab_module.code_hash, &*prefab_module); - Ok(original_code_len as u32) -} - -/// Costs for operations that are related to code handling. -#[cfg_attr(test, derive(Debug, PartialEq, Eq))] -#[derive(Clone, Copy)] -enum CodeToken { - /// Weight for reinstrumenting a contract contract of the supplied size in bytes. - Reinstrument(u32), - /// Weight for loading a contract per byte. - Load(u32), -} - -impl Token for CodeToken { - fn weight(&self) -> Weight { - use self::CodeToken::*; - // In case of `Load` we already covered the general costs of - // calling the storage but still need to account for the actual size of the - // contract code. This is why we subtract `T::*::(0)`. We need to do this at this - // point because when charging the general weight for calling the contract we not know the - // size of the contract. - match *self { - Reinstrument(len) => T::WeightInfo::reinstrument(len), - Load(len) => T::WeightInfo::call_with_code_per_byte(len) - .saturating_sub(T::WeightInfo::call_with_code_per_byte(0)) - .set_proof_size(len.into()), - } - } -} diff --git a/frame/contracts/src/wasm/mod.rs b/frame/contracts/src/wasm/mod.rs index 6b9cefcdd2d9..b975a39a8105 100644 --- a/frame/contracts/src/wasm/mod.rs +++ b/frame/contracts/src/wasm/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,101 +18,82 @@ //! This module provides a means for executing contracts //! represented in wasm. -mod code_cache; mod prepare; mod runtime; -#[cfg(feature = "runtime-benchmarks")] -pub use crate::wasm::code_cache::reinstrument; #[cfg(doc)] pub use crate::wasm::runtime::api_doc; -pub use crate::wasm::{ - prepare::TryInstantiate, - runtime::{ - AllowDeprecatedInterface, AllowUnstableInterface, CallFlags, Environment, ReturnCode, - Runtime, RuntimeCosts, - }, + +#[cfg(test)] +pub use tests::MockExt; + +pub use crate::wasm::runtime::{ + AllowDeprecatedInterface, AllowUnstableInterface, CallFlags, Environment, ReturnCode, Runtime, + RuntimeCosts, }; use crate::{ exec::{ExecResult, Executable, ExportedFunction, Ext}, - gas::GasMeter, - AccountIdOf, BalanceOf, CodeHash, CodeStorage, CodeVec, Config, Error, RelaxedCodeVec, - Schedule, + gas::{GasMeter, Token}, + wasm::prepare::LoadedModule, + weights::WeightInfo, + AccountIdOf, BadOrigin, BalanceOf, CodeHash, CodeInfoOf, CodeVec, Config, Error, Event, Pallet, + PristineCode, Schedule, Weight, LOG_TARGET, }; use codec::{Decode, Encode, MaxEncodedLen}; -use frame_support::dispatch::{DispatchError, DispatchResult}; +use frame_support::{ + dispatch::{DispatchError, DispatchResult}, + ensure, + traits::ReservableCurrency, +}; use sp_core::Get; use sp_runtime::RuntimeDebug; use sp_std::prelude::*; -#[cfg(test)] -pub use tests::MockExt; -use wasmi::{ - Config as WasmiConfig, Engine, Instance, Linker, Memory, MemoryType, Module, StackLimits, Store, -}; +use wasmi::{Instance, Linker, Memory, MemoryType, StackLimits, Store}; -/// A prepared wasm module ready for execution. -/// -/// # Note -/// -/// This data structure is mostly immutable once created and stored. The exceptions that -/// can be changed by calling a contract are `instruction_weights_version` and `code`. -/// `instruction_weights_version` and `code` change when a contract with an outdated instrumentation -/// is called. Therefore one must be careful when holding any in-memory representation of this -/// type while calling into a contract as those fields can get out of date. -#[derive(Clone, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] +const BYTES_PER_PAGE: usize = 64 * 1024; + +/// Validated Wasm module ready for execution. +/// This data structure is immutable once created and stored. +#[derive(Encode, Decode, scale_info::TypeInfo)] #[codec(mel_bound())] #[scale_info(skip_type_params(T))] -pub struct PrefabWasmModule { - /// Version of the instruction weights with which the code was instrumented. - #[codec(compact)] - instruction_weights_version: u32, - /// Initial memory size of a contract's sandbox. - #[codec(compact)] - initial: u32, - /// The maximum memory size of a contract's sandbox. - #[codec(compact)] - maximum: u32, - /// Code instrumented with the latest schedule. - code: RelaxedCodeVec, - /// A code that might contain non deterministic features and is therefore never allowed - /// to be run on chain. Specifically this code can never be instantiated into a contract - /// and can just be used through a delegate call. - determinism: Determinism, - /// The uninstrumented, pristine version of the code. - /// - /// It is not stored because the pristine code has its own storage item. The value - /// is only `Some` when this module was created from an `original_code` and `None` if - /// it was loaded from storage. +pub struct WasmBlob { + code: CodeVec, + // This isn't needed for contract execution and is not stored alongside it. #[codec(skip)] - original_code: Option>, - /// The code hash of the stored code which is defined as the hash over the `original_code`. - /// - /// As the map key there is no need to store the hash in the value, too. It is set manually - /// when loading the module from storage. + code_info: CodeInfo, + // This is for not calculating the hash every time we need it. #[codec(skip)] code_hash: CodeHash, - // This isn't needed for contract execution and does not get loaded from storage by default. - // It is `Some` if and only if this struct was generated from code. - #[codec(skip)] - owner_info: Option>, } -/// Information that belongs to a [`PrefabWasmModule`] but is stored separately. +/// Contract code related data, such as: +/// +/// - owner of the contract, i.e. account uploaded its code, +/// - storage deposit amount, +/// - reference count, +/// - determinism marker. /// /// It is stored in a separate storage entry to avoid loading the code when not necessary. #[derive(Clone, Encode, Decode, scale_info::TypeInfo, MaxEncodedLen)] #[codec(mel_bound())] #[scale_info(skip_type_params(T))] -pub struct OwnerInfo { - /// The account that has deployed the contract and hence is allowed to remove it. +pub struct CodeInfo { + /// The account that has uploaded the contract code and hence is allowed to remove it. owner: AccountIdOf, - /// The amount of balance that was deposited by the owner in order to deploy it. + /// The amount of balance that was deposited by the owner in order to store it on-chain. #[codec(compact)] deposit: BalanceOf, - /// The number of contracts that use this as their code. + /// The number of instantiated contracts that use this as their code. #[codec(compact)] refcount: u64, + /// Marks if the code might contain non-deterministic features and is therefore never allowed + /// to be run on-chain. Specifically, such a code can never be instantiated into a contract + /// and can just be used through a delegate call. + determinism: Determinism, + /// length of the code in bytes. + code_len: u32, } /// Defines the required determinism level of a wasm blob when either running or uploading code. @@ -124,7 +105,7 @@ pub enum Determinism { /// allowed. /// /// Dispatchables always use this mode in order to make on-chain execution deterministic. - Deterministic, + Enforced, /// Allow calling or uploading an indeterministic code. /// /// This is only possible when calling into `pallet-contracts` directly via @@ -133,7 +114,7 @@ pub enum Determinism { /// # Note /// /// **Never** use this mode for on-chain execution. - AllowIndeterminism, + Relaxed, } impl ExportedFunction { @@ -146,83 +127,65 @@ impl ExportedFunction { } } -impl PrefabWasmModule { - /// Create the module by checking and instrumenting `original_code`. - /// - /// This does **not** store the module. For this one need to either call [`Self::store`] - /// or [`::execute`][`Executable::execute`]. +/// Cost of code loading from storage. +#[cfg_attr(test, derive(Debug, PartialEq, Eq))] +#[derive(Clone, Copy)] +struct CodeLoadToken(u32); + +impl Token for CodeLoadToken { + fn weight(&self) -> Weight { + // When loading the contract, we already covered the general costs of + // calling the storage but still need to account for the actual size of the + // contract code. This is why we subtract `T::*::(0)`. We need to do this at this + // point because when charging the general weight for calling the contract we don't know the + // size of the contract. + T::WeightInfo::call_with_code_per_byte(self.0) + .saturating_sub(T::WeightInfo::call_with_code_per_byte(0)) + } +} + +impl WasmBlob { + /// Create the module by checking the `code`. pub fn from_code( - original_code: Vec, + code: Vec, schedule: &Schedule, owner: AccountIdOf, determinism: Determinism, - try_instantiate: TryInstantiate, ) -> Result { - let module = prepare::prepare::( - original_code.try_into().map_err(|_| (>::CodeTooLarge.into(), ""))?, + prepare::prepare::( + code.try_into().map_err(|_| (>::CodeTooLarge.into(), ""))?, schedule, owner, determinism, - try_instantiate, - )?; - Ok(module) - } - - /// Store the code without instantiating it. - /// - /// Otherwise the code is stored when [`::execute`][`Executable::execute`] - /// is called. - pub fn store(self) -> DispatchResult { - code_cache::store(self, false) + ) } /// Remove the code from storage and refund the deposit to its owner. /// /// Applies all necessary checks before removing the code. pub fn remove(origin: &T::AccountId, code_hash: CodeHash) -> DispatchResult { - code_cache::try_remove::(origin, code_hash) - } - - /// Returns whether there is a deposit to be payed for this module. - /// - /// Returns `0` if the module is already in storage and hence no deposit will - /// be charged when storing it. - pub fn open_deposit(&self) -> BalanceOf { - if >::contains_key(&self.code_hash) { - 0u32.into() - } else { - // Only already in-storage contracts have their `owner_info` set to `None`. - // Therefore it is correct to return `0` in this case. - self.owner_info.as_ref().map(|i| i.deposit).unwrap_or_default() - } + Self::try_remove_code(origin, code_hash) } /// Creates and returns an instance of the supplied code. /// /// This is either used for later executing a contract or for validation of a contract. /// When validating we pass `()` as `host_state`. Please note that such a dummy instance must - /// **never** be called/executed since it will panic the executor. + /// **never** be called/executed, since it will panic the executor. pub fn instantiate( code: &[u8], host_state: H, - memory: (u32, u32), + schedule: &Schedule, + determinism: Determinism, stack_limits: StackLimits, allow_deprecated: AllowDeprecatedInterface, - ) -> Result<(Store, Memory, Instance), wasmi::Error> + ) -> Result<(Store, Memory, Instance), &'static str> where E: Environment, { - let mut config = WasmiConfig::default(); - config - .set_stack_limits(stack_limits) - .wasm_multi_value(false) - .wasm_mutable_global(false) - .wasm_sign_extension(false) - .wasm_saturating_float_to_int(false); - let engine = Engine::new(&config); - let module = Module::new(&engine, code)?; - let mut store = Store::new(&engine, host_state); - let mut linker = Linker::new(); + let contract = LoadedModule::new::(&code, determinism, Some(stack_limits))?; + let mut store = Store::new(&contract.engine, host_state); + let mut linker = Linker::new(&contract.engine); E::define( &mut store, &mut linker, @@ -232,55 +195,106 @@ impl PrefabWasmModule { AllowUnstableInterface::No }, allow_deprecated, - )?; - let memory = Memory::new(&mut store, MemoryType::new(memory.0, Some(memory.1))?).expect( - "The limits defined in our `Schedule` limit the amount of memory well below u32::MAX; qed", - ); + ) + .map_err(|_| "can't define host functions to Linker")?; + + // Query wasmi for memory limits specified in the module's import entry. + let memory_limits = contract.scan_imports::(schedule)?; + // Here we allocate this memory in the _store_. It allocates _inital_ value, but allows it + // to grow up to maximum number of memory pages, if necessary. + let qed = "We checked the limits versus our Schedule, + which specifies the max amount of memory pages + well below u16::MAX; qed"; + let memory = Memory::new( + &mut store, + MemoryType::new(memory_limits.0, Some(memory_limits.1)).expect(qed), + ) + .expect(qed); + linker .define("env", "memory", memory) - .expect("We just created the linker. It has no define with this name attached; qed"); + .expect("We just created the Linker. It has no definitions with this name; qed"); - let instance = linker.instantiate(&mut store, &module)?.ensure_no_start(&mut store)?; + let instance = linker + .instantiate(&mut store, &contract.module) + .map_err(|_| "can't instantiate module with provided definitions")? + .ensure_no_start(&mut store) + .map_err(|_| "start function is forbidden but found in the module")?; Ok((store, memory, instance)) } - /// See [`Self::from_code_unchecked`]. - #[cfg(feature = "runtime-benchmarks")] - pub fn store_code_unchecked( - original_code: Vec, - schedule: &Schedule, - owner: T::AccountId, - ) -> DispatchResult { - let executable = Self::from_code_unchecked(original_code, schedule, owner)?; - code_cache::store(executable, false) + /// Puts the module blob into storage, and returns the deposit collected for the storage. + pub fn store_code(&mut self) -> Result, Error> { + let code_hash = *self.code_hash(); + >::mutate(code_hash, |stored_code_info| { + match stored_code_info { + // Contract code is already stored in storage. Nothing to be done here. + Some(_) => Ok(Default::default()), + // Upload a new contract code. + // We need to store the code and its code_info, and collect the deposit. + // This `None` case happens only with freshly uploaded modules. This means that + // the `owner` is always the origin of the current transaction. + None => { + let deposit = self.code_info.deposit; + T::Currency::reserve(&self.code_info.owner, deposit) + .map_err(|_| >::StorageDepositNotEnoughFunds)?; + self.code_info.refcount = 0; + >::insert(code_hash, &self.code); + *stored_code_info = Some(self.code_info.clone()); + >::deposit_event(vec![code_hash], Event::CodeStored { code_hash }); + Ok(deposit) + }, + } + }) + } + + /// Try to remove code together with all associated information. + fn try_remove_code(origin: &T::AccountId, code_hash: CodeHash) -> DispatchResult { + >::try_mutate_exists(&code_hash, |existing| { + if let Some(code_info) = existing { + ensure!(code_info.refcount == 0, >::CodeInUse); + ensure!(&code_info.owner == origin, BadOrigin); + T::Currency::unreserve(&code_info.owner, code_info.deposit); + *existing = None; + >::remove(&code_hash); + >::deposit_event(vec![code_hash], Event::CodeRemoved { code_hash }); + Ok(()) + } else { + Err(>::CodeNotFound.into()) + } + }) } - /// Decrement instruction_weights_version by 1. Panics if it is already 0. - #[cfg(test)] - pub fn decrement_version(&mut self) { - self.instruction_weights_version = self.instruction_weights_version.checked_sub(1).unwrap(); + /// Load code with the given code hash. + fn load_code( + code_hash: CodeHash, + gas_meter: &mut GasMeter, + ) -> Result<(CodeVec, CodeInfo), DispatchError> { + let code_info = >::get(code_hash).ok_or(Error::::CodeNotFound)?; + gas_meter.charge(CodeLoadToken(code_info.code_len))?; + let code = >::get(code_hash).ok_or(Error::::CodeNotFound)?; + Ok((code, code_info)) } - /// Create the module without checking nor instrumenting the passed code. + /// Create the module without checking the passed code. /// /// # Note /// - /// This is useful for benchmarking where we don't want instrumentation to skew + /// This is useful for benchmarking where we don't want validation of the module to skew /// our results. This also does not collect any deposit from the `owner`. Also useful /// during testing when we want to deploy codes that do not pass the instantiation checks. #[cfg(any(test, feature = "runtime-benchmarks"))] - fn from_code_unchecked( - original_code: Vec, + pub fn from_code_unchecked( + code: Vec, schedule: &Schedule, owner: T::AccountId, ) -> Result { - prepare::benchmarking::prepare(original_code, schedule, owner) - .map_err::(Into::into) + prepare::benchmarking::prepare(code, schedule, owner) } } -impl OwnerInfo { +impl CodeInfo { /// Return the refcount of the module. #[cfg(test)] pub fn refcount(&self) -> u64 { @@ -288,21 +302,32 @@ impl OwnerInfo { } } -impl Executable for PrefabWasmModule { +impl Executable for WasmBlob { fn from_storage( code_hash: CodeHash, - schedule: &Schedule, gas_meter: &mut GasMeter, ) -> Result { - code_cache::load(code_hash, schedule, gas_meter) + let (code, code_info) = Self::load_code(code_hash, gas_meter)?; + Ok(Self { code, code_info, code_hash }) } - fn add_user(code_hash: CodeHash) -> Result<(), DispatchError> { - code_cache::increment_refcount::(code_hash) + fn increment_refcount(code_hash: CodeHash) -> Result<(), DispatchError> { + >::mutate(code_hash, |existing| -> Result<(), DispatchError> { + if let Some(info) = existing { + info.refcount = info.refcount.saturating_add(1); + Ok(()) + } else { + Err(Error::::CodeNotFound.into()) + } + }) } - fn remove_user(code_hash: CodeHash) { - code_cache::decrement_refcount::(code_hash) + fn decrement_refcount(code_hash: CodeHash) { + >::mutate(code_hash, |existing| { + if let Some(info) = existing { + info.refcount = info.refcount.saturating_sub(1); + } + }); } fn execute>( @@ -311,39 +336,60 @@ impl Executable for PrefabWasmModule { function: &ExportedFunction, input_data: Vec, ) -> ExecResult { + let code = self.code.as_slice(); + // Instantiate the Wasm module to the engine. let runtime = Runtime::new(ext, input_data); + let schedule = ::Schedule::get(); let (mut store, memory, instance) = Self::instantiate::( - self.code.as_slice(), + code, runtime, - (self.initial, self.maximum), + &schedule, + self.code_info.determinism, StackLimits::default(), match function { - ExportedFunction::Constructor => AllowDeprecatedInterface::No, ExportedFunction::Call => AllowDeprecatedInterface::Yes, + ExportedFunction::Constructor => AllowDeprecatedInterface::No, }, ) .map_err(|msg| { - log::debug!(target: "runtime::contracts", "failed to instantiate code: {}", msg); + log::debug!(target: LOG_TARGET, "failed to instantiate code to wasmi: {}", msg); Error::::CodeRejected })?; - store.state_mut().set_memory(memory); + store.data_mut().set_memory(memory); + + // Set fuel limit for the wasmi execution. + // We normalize it by the base instruction weight, as its cost in wasmi engine is `1`. + let fuel_limit = store + .data_mut() + .ext() + .gas_meter_mut() + .gas_left() + .ref_time() + .checked_div(T::Schedule::get().instruction_weights.base as u64) + .ok_or(Error::::InvalidSchedule)?; + store + .add_fuel(fuel_limit) + .expect("We've set up engine to fuel consuming mode; qed"); let exported_func = instance .get_export(&store, function.identifier()) .and_then(|export| export.into_func()) .ok_or_else(|| { - log::error!(target: "runtime::contracts", "failed to find entry point"); + log::error!(target: LOG_TARGET, "failed to find entry point"); Error::::CodeRejected })?; - // We store before executing so that the code hash is available in the constructor. if let &ExportedFunction::Constructor = function { - code_cache::store(self, true)?; + WasmBlob::::increment_refcount(self.code_hash)?; } let result = exported_func.call(&mut store, &[], &mut []); + let engine_consumed_total = store.fuel_consumed().expect("Fuel metering is enabled; qed"); + // Sync this frame's gas meter with the engine's one. + let gas_meter = store.data_mut().ext().gas_meter_mut(); + gas_meter.charge_fuel(engine_consumed_total)?; - store.into_state().to_execution_result(result) + store.into_data().to_execution_result(result) } fn code_hash(&self) -> &CodeHash { @@ -355,7 +401,7 @@ impl Executable for PrefabWasmModule { } fn is_deterministic(&self) -> bool { - matches!(self.determinism, Determinism::Deterministic) + matches!(self.code_info.determinism, Determinism::Enforced) } } @@ -363,21 +409,17 @@ impl Executable for PrefabWasmModule { mod tests { use super::*; use crate::{ - exec::{ - AccountIdOf, BlockNumberOf, ErrorOrigin, ExecError, Executable, Ext, FixSizedKey, - SeedOf, VarSizedKey, - }, + exec::{AccountIdOf, ErrorOrigin, ExecError, Executable, Ext, Key, SeedOf}, gas::GasMeter, storage::WriteOutcome, tests::{RuntimeCall, Test, ALICE, BOB}, - BalanceOf, CodeHash, Error, Pallet as Contracts, + BalanceOf, CodeHash, Error, Origin, Pallet as Contracts, }; use assert_matches::assert_matches; use frame_support::{ - assert_err, assert_ok, - dispatch::DispatchResultWithPostInfo, - weights::{OldWeight, Weight}, + assert_err, assert_ok, dispatch::DispatchResultWithPostInfo, weights::Weight, }; + use frame_system::pallet_prelude::BlockNumberFor; use pallet_contracts_primitives::{ExecReturnValue, ReturnFlags}; use pretty_assertions::assert_eq; use sp_core::H256; @@ -436,7 +478,9 @@ mod tests { gas_meter: GasMeter, debug_buffer: Vec, ecdsa_recover: RefCell>, + sr25519_verify: RefCell, [u8; 32])>>, code_hashes: Vec>, + caller: Origin, } /// The call is mocked and just returns this hardcoded value. @@ -460,6 +504,8 @@ mod tests { gas_meter: GasMeter::new(Weight::from_parts(10_000_000_000, 10 * 1024 * 1024)), debug_buffer: Default::default(), ecdsa_recover: Default::default(), + caller: Default::default(), + sr25519_verify: Default::default(), } } } @@ -470,6 +516,7 @@ mod tests { fn call( &mut self, _gas_limit: Weight, + _deposit_limit: BalanceOf, to: AccountIdOf, value: u64, data: Vec, @@ -489,6 +536,7 @@ mod tests { fn instantiate( &mut self, gas_limit: Weight, + _deposit_limit: BalanceOf, code_hash: CodeHash, value: u64, data: Vec, @@ -518,21 +566,15 @@ mod tests { self.terminations.push(TerminationEntry { beneficiary: beneficiary.clone() }); Ok(()) } - fn get_storage(&mut self, key: &FixSizedKey) -> Option> { - self.storage.get(&key.to_vec()).cloned() - } - fn get_storage_transparent(&mut self, key: &VarSizedKey) -> Option> { + fn get_storage(&mut self, key: &Key) -> Option> { self.storage.get(&key.to_vec()).cloned() } - fn get_storage_size(&mut self, key: &FixSizedKey) -> Option { - self.storage.get(&key.to_vec()).map(|val| val.len() as u32) - } - fn get_storage_size_transparent(&mut self, key: &VarSizedKey) -> Option { + fn get_storage_size(&mut self, key: &Key) -> Option { self.storage.get(&key.to_vec()).map(|val| val.len() as u32) } fn set_storage( &mut self, - key: &FixSizedKey, + key: &Key, value: Option>, take_old: bool, ) -> Result { @@ -549,27 +591,8 @@ mod tests { } Ok(result) } - fn set_storage_transparent( - &mut self, - key: &VarSizedKey, - value: Option>, - take_old: bool, - ) -> Result { - let key = key.to_vec(); - let entry = self.storage.entry(key.clone()); - let result = match (entry, take_old) { - (Entry::Vacant(_), _) => WriteOutcome::New, - (Entry::Occupied(entry), false) => - WriteOutcome::Overwritten(entry.remove().len() as u32), - (Entry::Occupied(entry), true) => WriteOutcome::Taken(entry.remove()), - }; - if let Some(value) = value { - self.storage.insert(key, value); - } - Ok(result) - } - fn caller(&self) -> &AccountIdOf { - &ALICE + fn caller(&self) -> Origin { + self.caller.clone() } fn is_contract(&self, _address: &AccountIdOf) -> bool { true @@ -584,6 +607,9 @@ mod tests { fn caller_is_origin(&self) -> bool { false } + fn caller_is_root(&self) -> bool { + &self.caller == &Origin::Root + } fn address(&self) -> &AccountIdOf { &BOB } @@ -599,7 +625,7 @@ mod tests { fn minimum_balance(&self) -> u64 { 666 } - fn random(&self, subject: &[u8]) -> (SeedOf, BlockNumberOf) { + fn random(&self, subject: &[u8]) -> (SeedOf, BlockNumberFor) { (H256::from_slice(subject), 42) } fn deposit_event(&mut self, topics: Vec, data: Vec) { @@ -612,12 +638,19 @@ mod tests { 16_384 } fn get_weight_price(&self, weight: Weight) -> BalanceOf { - BalanceOf::::from(1312_u32).saturating_mul(weight.ref_time().into()) + BalanceOf::::from(1312_u32) + .saturating_mul(weight.ref_time().into()) + .saturating_add( + BalanceOf::::from(103_u32).saturating_mul(weight.proof_size()), + ) } fn schedule(&self) -> &Schedule { &self.schedule } - fn gas_meter(&mut self) -> &mut GasMeter { + fn gas_meter(&self) -> &GasMeter { + &self.gas_meter + } + fn gas_meter_mut(&mut self) -> &mut GasMeter { &mut self.gas_meter } fn append_debug_buffer(&mut self, msg: &str) -> bool { @@ -639,6 +672,10 @@ mod tests { self.ecdsa_recover.borrow_mut().push((*signature, *message_hash)); Ok([3; 33]) } + fn sr25519_verify(&self, signature: &[u8; 64], message: &[u8], pub_key: &[u8; 32]) -> bool { + self.sr25519_verify.borrow_mut().push((*signature, message.to_vec(), *pub_key)); + true + } fn contract_info(&mut self) -> &mut crate::ContractInfo { unimplemented!() } @@ -670,23 +707,25 @@ mod tests { type RuntimeConfig = ::T; RuntimeConfig::set_unstable_interface(unstable_interface); let wasm = wat::parse_str(wat).unwrap(); - let schedule = crate::Schedule::default(); let executable = if skip_checks { - PrefabWasmModule::::from_code_unchecked(wasm, &schedule, ALICE)? + WasmBlob::::from_code_unchecked( + wasm, + ext.borrow_mut().schedule(), + ALICE, + )? } else { - PrefabWasmModule::::from_code( + WasmBlob::::from_code( wasm, - &schedule, + ext.borrow_mut().schedule(), ALICE, - Determinism::Deterministic, - TryInstantiate::Instantiate, + Determinism::Enforced, ) .map_err(|err| err.0)? }; executable.execute(ext.borrow_mut(), entry_point, input_data) } - /// Execute the suppplied code. + /// Execute the supplied code. fn execute>(wat: &str, input_data: Vec, ext: E) -> ExecResult { execute_internal(wat, input_data, ext, &ExportedFunction::Call, true, false) } @@ -1068,14 +1107,14 @@ mod tests { "#; let mut ext = MockExt::default(); - ext.set_storage_transparent( - &VarSizedKey::::try_from([1u8; 64].to_vec()).unwrap(), + ext.set_storage( + &Key::::try_from_var([1u8; 64].to_vec()).unwrap(), Some(vec![42u8]), false, ) .unwrap(); - ext.set_storage_transparent( - &VarSizedKey::::try_from([2u8; 19].to_vec()).unwrap(), + ext.set_storage( + &Key::::try_from_var([2u8; 19].to_vec()).unwrap(), Some(vec![]), false, ) @@ -1346,6 +1385,49 @@ mod tests { ); } + #[test] + fn contract_sr25519() { + const CODE_SR25519: &str = r#" +(module + (import "seal0" "sr25519_verify" (func $sr25519_verify (param i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call") + (drop + (call $sr25519_verify + (i32.const 0) ;; Pointer to signature. + (i32.const 64) ;; Pointer to public key. + (i32.const 16) ;; message length. + (i32.const 96) ;; Pointer to message. + ) + ) + ) + (func (export "deploy")) + + ;; Signature (64 bytes) + (data (i32.const 0) + "\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01" + "\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01" + "\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01" + "\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01" + ) + + ;; public key (32 bytes) + (data (i32.const 64) + "\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01" + "\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01" + ) + + ;; message. (16 bytes) + (data (i32.const 96) + "\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01\01" + ) +) +"#; + let mut mock_ext = MockExt::default(); + assert_ok!(execute(&CODE_SR25519, vec![], &mut mock_ext)); + assert_eq!(mock_ext.sr25519_verify.into_inner(), [([1; 64], [1; 16].to_vec(), [1; 32])]); + } + const CODE_GET_STORAGE: &str = r#" (module (import "seal0" "seal_get_storage" (func $seal_get_storage (param i32 i32 i32) (result i32))) @@ -1471,6 +1553,16 @@ mod tests { assert_ok!(execute(CODE_CALLER, vec![], MockExt::default())); } + #[test] + fn caller_traps_when_no_account_id() { + let mut ext = MockExt::default(); + ext.caller = Origin::Root; + assert_eq!( + execute(CODE_CALLER, vec![], ext), + Err(ExecError { error: DispatchError::RootNotAllowed, origin: ErrorOrigin::Caller }) + ); + } + /// calls `seal_address` and compares the result with the constant (BOB's address part). const CODE_ADDRESS: &str = r#" (module @@ -1567,7 +1659,7 @@ mod tests { const CODE_GAS_PRICE: &str = r#" (module - (import "seal0" "seal_weight_to_fee" (func $seal_weight_to_fee (param i64 i32 i32))) + (import "seal1" "weight_to_fee" (func $seal_weight_to_fee (param i64 i64 i32 i32))) (import "env" "memory" (memory 1 1)) ;; size of our buffer is 32 bytes @@ -1584,7 +1676,7 @@ mod tests { (func (export "call") ;; This stores the gas price in the buffer - (call $seal_weight_to_fee (i64.const 2) (i32.const 0) (i32.const 32)) + (call $seal_weight_to_fee (i64.const 2) (i64.const 1) (i32.const 0) (i32.const 32)) ;; assert len == 8 (call $assert @@ -1594,11 +1686,11 @@ mod tests { ) ) - ;; assert that contents of the buffer is equal to the i64 value of 2 * 1312. + ;; assert that contents of the buffer is equal to the i64 value of 2 * 1312 + 103 = 2727. (call $assert (i64.eq (i64.load (i32.const 0)) - (i64.const 2624) + (i64.const 2727) ) ) ) @@ -1613,12 +1705,12 @@ mod tests { const CODE_GAS_LEFT: &str = r#" (module - (import "seal0" "seal_gas_left" (func $seal_gas_left (param i32 i32))) + (import "seal1" "gas_left" (func $seal_gas_left (param i32 i32))) (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) (import "env" "memory" (memory 1 1)) - ;; size of our buffer is 32 bytes - (data (i32.const 32) "\20") + ;; Make output buffer size 20 bytes + (data (i32.const 20) "\14") (func $assert (param i32) (block $ok @@ -1630,19 +1722,19 @@ mod tests { ) (func (export "call") - ;; This stores the gas left in the buffer - (call $seal_gas_left (i32.const 0) (i32.const 32)) + ;; This stores the weight left to the buffer + (call $seal_gas_left (i32.const 0) (i32.const 20)) - ;; assert len == 8 + ;; Assert len <= 16 (max encoded Weight len) (call $assert - (i32.eq - (i32.load (i32.const 32)) - (i32.const 8) + (i32.le_u + (i32.load (i32.const 20)) + (i32.const 16) ) ) - ;; return gas left - (call $seal_return (i32.const 0) (i32.const 0) (i32.const 8)) + ;; Return weight left and its encoded value len + (call $seal_return (i32.const 0) (i32.const 0) (i32.load (i32.const 20))) (unreachable) ) @@ -1657,11 +1749,22 @@ mod tests { let output = execute(CODE_GAS_LEFT, vec![], &mut ext).unwrap(); - let OldWeight(gas_left) = OldWeight::decode(&mut &*output.data).unwrap(); + let weight_left = Weight::decode(&mut &*output.data).unwrap(); let actual_left = ext.gas_meter.gas_left(); - // TODO: account for proof size weight - assert!(gas_left < gas_limit.ref_time(), "gas_left must be less than initial"); - assert!(gas_left > actual_left.ref_time(), "gas_left must be greater than final"); + + assert!(weight_left.all_lt(gas_limit), "gas_left must be less than initial"); + assert!(weight_left.all_gt(actual_left), "gas_left must be greater than final"); + } + + /// Test that [`frame_support::weights::OldWeight`] en/decodes the same as our + /// [`crate::OldWeight`]. + #[test] + fn old_weight_decode() { + #![allow(deprecated)] + let sp = frame_support::weights::OldWeight(42).encode(); + let our = crate::OldWeight::decode(&mut &*sp).unwrap(); + + assert_eq!(our, 42); } const CODE_VALUE_TRANSFERRED: &str = r#" @@ -2005,7 +2108,7 @@ mod tests { ExecReturnValue { flags: ReturnFlags::empty(), data: ( - array_bytes::hex2array_unchecked::<32>( + array_bytes::hex2array_unchecked::<_, 32>( "000102030405060708090A0B0C0D0E0F000102030405060708090A0B0C0D0E0F" ), 42u64, @@ -2360,13 +2463,8 @@ mod tests { "#; let mut ext = MockExt::default(); let result = execute(CODE_DEBUG_MESSAGE_FAIL, vec![], &mut ext); - assert_eq!( - result, - Err(ExecError { - error: Error::::DebugMessageInvalidUTF8.into(), - origin: ErrorOrigin::Caller, - }) - ); + assert_ok!(result); + assert!(ext.debug_buffer.is_empty()); } const CODE_CALL_RUNTIME: &str = r#" @@ -2549,15 +2647,15 @@ mod tests { let mut ext = MockExt::default(); - ext.set_storage_transparent( - &VarSizedKey::::try_from([1u8; 64].to_vec()).unwrap(), + ext.set_storage( + &Key::::try_from_var([1u8; 64].to_vec()).unwrap(), Some(vec![42u8]), false, ) .unwrap(); - ext.set_storage_transparent( - &VarSizedKey::::try_from([2u8; 19].to_vec()).unwrap(), + ext.set_storage( + &Key::::try_from_var([2u8; 19].to_vec()).unwrap(), Some(vec![]), false, ) @@ -2633,14 +2731,14 @@ mod tests { let mut ext = MockExt::default(); - ext.set_storage_transparent( - &VarSizedKey::::try_from([1u8; 64].to_vec()).unwrap(), + ext.set_storage( + &Key::::try_from_var([1u8; 64].to_vec()).unwrap(), Some(vec![42u8]), false, ) .unwrap(); - ext.set_storage_transparent( - &VarSizedKey::::try_from([2u8; 19].to_vec()).unwrap(), + ext.set_storage( + &Key::::try_from_var([2u8; 19].to_vec()).unwrap(), Some(vec![]), false, ) @@ -2730,15 +2828,15 @@ mod tests { let mut ext = MockExt::default(); - ext.set_storage_transparent( - &VarSizedKey::::try_from([1u8; 64].to_vec()).unwrap(), + ext.set_storage( + &Key::::try_from_var([1u8; 64].to_vec()).unwrap(), Some(vec![42u8]), false, ) .unwrap(); - ext.set_storage_transparent( - &VarSizedKey::::try_from([2u8; 19].to_vec()).unwrap(), + ext.set_storage( + &Key::::try_from_var([2u8; 19].to_vec()).unwrap(), Some(vec![]), false, ) @@ -2943,6 +3041,45 @@ mod tests { assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: 0u32.encode() },); } + #[test] + fn caller_is_root_works() { + const CODE_CALLER_IS_ROOT: &str = r#" +;; This runs `caller_is_root` check on zero account address +(module + (import "seal0" "caller_is_root" (func $caller_is_root (result i32))) + (import "seal0" "seal_return" (func $seal_return (param i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + + ;; [0, 4) here the return code of the `caller_is_root` will be stored + ;; we initialize it with non-zero value to be sure that it's being overwritten below + (data (i32.const 0) "\10\10\10\10") + + (func (export "deploy")) + + (func (export "call") + (i32.store + (i32.const 0) + (call $caller_is_root) + ) + ;; exit with success and take `caller_is_root` return code to the output buffer + (call $seal_return (i32.const 0) (i32.const 0) (i32.const 4)) + ) +) +"#; + // The default `caller` is ALICE. Therefore not root. + let output = execute(CODE_CALLER_IS_ROOT, vec![], MockExt::default()).unwrap(); + assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: 0u32.encode() },); + + // The caller is forced to be root instead of using the default ALICE. + let output = execute( + CODE_CALLER_IS_ROOT, + vec![], + MockExt { caller: Origin::Root, ..MockExt::default() }, + ) + .unwrap(); + assert_eq!(output, ExecReturnValue { flags: ReturnFlags::empty(), data: 1u32.encode() },); + } + #[test] fn set_code_hash() { const CODE: &str = r#" @@ -3052,6 +3189,8 @@ mod tests { const CODE: &str = r#" (module (import "seal0" "instantiation_nonce" (func $nonce (result i64))) + (import "env" "memory" (memory 1 1)) + (func $assert (param i32) (block $ok (br_if $ok @@ -3073,29 +3212,6 @@ mod tests { execute(CODE, vec![], &mut mock_ext).unwrap(); } - /// Code with deprecated functions cannot be uploaded or instantiated. However, we - /// need to make sure that it still can be re-instrumented. - #[test] - fn can_reinstrument_deprecated() { - const CODE_RANDOM: &str = r#" -(module - (import "seal0" "random" (func $seal_random (param i32 i32 i32 i32))) - (func (export "call")) - (func (export "deploy")) -) - "#; - let wasm = wat::parse_str(CODE_RANDOM).unwrap(); - let schedule = crate::Schedule::::default(); - #[cfg(not(feature = "runtime-benchmarks"))] - assert_err!(execute(CODE_RANDOM, vec![], MockExt::default()), >::CodeRejected); - self::prepare::reinstrument::( - &wasm, - &schedule, - Determinism::Deterministic, - ) - .unwrap(); - } - /// This test check that an unstable interface cannot be deployed. In case of runtime /// benchmarks we always allow unstable interfaces. This is why this test does not /// work when this feature is enabled. @@ -3105,6 +3221,8 @@ mod tests { const CANNOT_DEPLOY_UNSTABLE: &str = r#" (module (import "seal0" "reentrance_count" (func $reentrance_count (result i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call")) (func (export "deploy")) ) @@ -3125,6 +3243,8 @@ mod tests { const CODE_RANDOM_0: &str = r#" (module (import "seal0" "seal_random" (func $seal_random (param i32 i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call")) (func (export "deploy")) ) @@ -3132,6 +3252,8 @@ mod tests { const CODE_RANDOM_1: &str = r#" (module (import "seal1" "seal_random" (func $seal_random (param i32 i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call")) (func (export "deploy")) ) @@ -3139,6 +3261,8 @@ mod tests { const CODE_RANDOM_2: &str = r#" (module (import "seal0" "random" (func $seal_random (param i32 i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call")) (func (export "deploy")) ) @@ -3146,6 +3270,8 @@ mod tests { const CODE_RANDOM_3: &str = r#" (module (import "seal1" "random" (func $seal_random (param i32 i32 i32 i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call")) (func (export "deploy")) ) diff --git a/frame/contracts/src/wasm/prepare.rs b/frame/contracts/src/wasm/prepare.rs index 787b97d31acc..7deccdde7a06 100644 --- a/frame/contracts/src/wasm/prepare.rs +++ b/frame/contracts/src/wasm/prepare.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,207 +23,68 @@ use crate::{ chain_extension::ChainExtension, storage::meter::Diff, wasm::{ - runtime::AllowDeprecatedInterface, Determinism, Environment, OwnerInfo, PrefabWasmModule, + runtime::AllowDeprecatedInterface, CodeInfo, Determinism, Environment, WasmBlob, + BYTES_PER_PAGE, }, - AccountIdOf, CodeVec, Config, Error, Schedule, + AccountIdOf, CodeVec, Config, Error, Schedule, LOG_TARGET, }; -use codec::{Encode, MaxEncodedLen}; +use codec::MaxEncodedLen; use sp_runtime::{traits::Hash, DispatchError}; -use sp_std::prelude::*; -use wasm_instrument::{ - gas_metering, - parity_wasm::elements::{self, External, Internal, MemoryType, Type, ValueType}, +#[cfg(any(test, feature = "runtime-benchmarks"))] +use sp_std::prelude::Vec; +use wasmi::{ + core::ValueType as WasmiValueType, Config as WasmiConfig, Engine, ExternType, + FuelConsumptionMode, Module, StackLimits, }; -use wasmi::StackLimits; -use wasmparser::{Validator, WasmFeatures}; /// Imported memory must be located inside this module. The reason for hardcoding is that current /// compiler toolchains might not support specifying other modules than "env" for memory imports. pub const IMPORT_MODULE_MEMORY: &str = "env"; -/// Determines whether a module should be instantiated during preparation. -pub enum TryInstantiate { - /// Do the instantiation to make sure that the module is valid. - /// - /// This should be used if a module is only uploaded but not executed. We need - /// to make sure that it can be actually instantiated. - Instantiate, - /// Skip the instantiation during preparation. - /// - /// This makes sense when the preparation takes place as part of an instantation. Then - /// this instantiation would fail the whole transaction and an extra check is not - /// necessary. - Skip, -} - -/// The reason why a contract is instrumented. -enum InstrumentReason { - /// A new code is uploaded. - New, - /// Existing code is re-instrumented. - Reinstrument, +/// The inner deserialized module is valid and contains only allowed WebAssembly features. +/// This is checked by loading it into wasmi interpreter `engine`. +pub struct LoadedModule { + pub module: Module, + pub engine: Engine, } -struct ContractModule<'a, T: Config> { - /// A deserialized module. The module is valid (this is Guaranteed by `new` method). - module: elements::Module, - schedule: &'a Schedule, -} - -impl<'a, T: Config> ContractModule<'a, T> { - /// Creates a new instance of `ContractModule`. - /// - /// Returns `Err` if the `original_code` couldn't be decoded or - /// if it contains an invalid module. - fn new(original_code: &[u8], schedule: &'a Schedule) -> Result { - let module = - elements::deserialize_buffer(original_code).map_err(|_| "Can't decode wasm code")?; - - // Return a `ContractModule` instance with - // __valid__ module. - Ok(ContractModule { module, schedule }) - } - - /// Ensures that module doesn't declare internal memories. +impl LoadedModule { + /// Creates a new instance of `LoadedModule`. /// - /// In this runtime we only allow wasm module to import memory from the environment. - /// Memory section contains declarations of internal linear memories, so if we find one - /// we reject such a module. - fn ensure_no_internal_memory(&self) -> Result<(), &'static str> { - if self.module.memory_section().map_or(false, |ms| ms.entries().len() > 0) { - return Err("module declares internal memory") - } - Ok(()) - } - - /// Ensures that tables declared in the module are not too big. - fn ensure_table_size_limit(&self, limit: u32) -> Result<(), &'static str> { - if let Some(table_section) = self.module.table_section() { - // In Wasm MVP spec, there may be at most one table declared. Double check this - // explicitly just in case the Wasm version changes. - if table_section.entries().len() > 1 { - return Err("multiple tables declared") - } - if let Some(table_type) = table_section.entries().first() { - // Check the table's initial size as there is no instruction or environment function - // capable of growing the table. - if table_type.limits().initial() > limit { - return Err("table exceeds maximum size allowed") - } - } - } - Ok(()) - } - - /// Ensure that any `br_table` instruction adheres to its immediate value limit. - fn ensure_br_table_size_limit(&self, limit: u32) -> Result<(), &'static str> { - let code_section = if let Some(type_section) = self.module.code_section() { - type_section - } else { - return Ok(()) - }; - for instr in code_section.bodies().iter().flat_map(|body| body.code().elements()) { - use self::elements::Instruction::BrTable; - if let BrTable(table) = instr { - if table.table.len() > limit as usize { - return Err("BrTable's immediate value is too big.") - } - } - } - Ok(()) - } - - fn ensure_global_variable_limit(&self, limit: u32) -> Result<(), &'static str> { - if let Some(global_section) = self.module.global_section() { - if global_section.entries().len() > limit as usize { - return Err("module declares too many globals") - } - } - Ok(()) - } - - fn ensure_local_variable_limit(&self, limit: u32) -> Result<(), &'static str> { - if let Some(code_section) = self.module.code_section() { - for func_body in code_section.bodies() { - let locals_count: u32 = - func_body.locals().iter().map(|val_type| val_type.count()).sum(); - if locals_count > limit { - return Err("single function declares too many locals") - } - } - } - Ok(()) - } - - /// Ensures that no floating point types are in use. - fn ensure_no_floating_types(&self) -> Result<(), &'static str> { - if let Some(global_section) = self.module.global_section() { - for global in global_section.entries() { - match global.global_type().content_type() { - ValueType::F32 | ValueType::F64 => - return Err("use of floating point type in globals is forbidden"), - _ => {}, - } - } - } - - if let Some(code_section) = self.module.code_section() { - for func_body in code_section.bodies() { - for local in func_body.locals() { - match local.value_type() { - ValueType::F32 | ValueType::F64 => - return Err("use of floating point type in locals is forbidden"), - _ => {}, - } - } - } - } - - if let Some(type_section) = self.module.type_section() { - for wasm_type in type_section.types() { - match wasm_type { - Type::Function(func_type) => { - let return_type = func_type.results().get(0); - for value_type in func_type.params().iter().chain(return_type) { - match value_type { - ValueType::F32 | ValueType::F64 => - return Err( - "use of floating point type in function types is forbidden", - ), - _ => {}, - } - } - }, - } - } - } - - Ok(()) - } - - /// Ensure that no function exists that has more parameters than allowed. - fn ensure_parameter_limit(&self, limit: u32) -> Result<(), &'static str> { - let type_section = if let Some(type_section) = self.module.type_section() { - type_section - } else { - return Ok(()) - }; - - for Type::Function(func) in type_section.types() { - if func.params().len() > limit as usize { - return Err("Use of a function type with too many parameters.") - } + /// The inner Wasm module is checked not to have restricted WebAssembly proposals. + /// Returns `Err` if the `code` cannot be deserialized or if it contains an invalid module. + pub fn new( + code: &[u8], + determinism: Determinism, + stack_limits: Option, + ) -> Result { + // NOTE: wasmi does not support unstable WebAssembly features. The module is implicitly + // checked for not having those ones when creating `wasmi::Module` below. + let mut config = WasmiConfig::default(); + config + .wasm_multi_value(false) + .wasm_mutable_global(false) + .wasm_sign_extension(false) + .wasm_bulk_memory(false) + .wasm_reference_types(false) + .wasm_tail_call(false) + .wasm_extended_const(false) + .wasm_saturating_float_to_int(false) + .floats(matches!(determinism, Determinism::Relaxed)) + .consume_fuel(true) + .fuel_consumption_mode(FuelConsumptionMode::Eager); + + if let Some(stack_limits) = stack_limits { + config.set_stack_limits(stack_limits); } - Ok(()) - } + let engine = Engine::new(&config); + let module = + Module::new(&engine, code.clone()).map_err(|_| "Can't load the module into wasmi!")?; - fn inject_gas_metering(self, determinism: Determinism) -> Result { - let gas_rules = self.schedule.rules(&self.module, determinism); - let backend = gas_metering::host_function::Injector::new("seal0", "gas"); - let contract_module = gas_metering::inject(self.module, backend, &gas_rules) - .map_err(|_| "gas instrumentation failed")?; - Ok(ContractModule { module: contract_module, schedule: self.schedule }) + // Return a `LoadedModule` instance with + // __valid__ module. + Ok(LoadedModule { module, engine }) } /// Check that the module has required exported functions. For now @@ -236,60 +97,32 @@ impl<'a, T: Config> ContractModule<'a, T> { fn scan_exports(&self) -> Result<(), &'static str> { let mut deploy_found = false; let mut call_found = false; - let module = &self.module; - - let types = module.type_section().map(|ts| ts.types()).unwrap_or(&[]); - let export_entries = module.export_section().map(|is| is.entries()).unwrap_or(&[]); - let func_entries = module.function_section().map(|fs| fs.entries()).unwrap_or(&[]); - - // Function index space consists of imported function following by - // declared functions. Calculate the total number of imported functions so - // we can use it to convert indexes from function space to declared function space. - let fn_space_offset = module - .import_section() - .map(|is| is.entries()) - .unwrap_or(&[]) - .iter() - .filter(|entry| matches!(*entry.external(), External::Function(_))) - .count(); - - for export in export_entries { - match export.field() { - "call" => call_found = true, - "deploy" => deploy_found = true, - _ => return Err("unknown export: expecting only deploy and call functions"), - } - - // Then check the export kind. "call" and "deploy" are - // functions. - let fn_idx = match export.internal() { - Internal::Function(ref fn_idx) => *fn_idx, - _ => return Err("expected a function"), - }; - - // convert index from function index space to declared index space. - let fn_idx = match fn_idx.checked_sub(fn_space_offset as u32) { - Some(fn_idx) => fn_idx, - None => { - // Underflow here means fn_idx points to imported function which we don't allow! - return Err("entry point points to an imported function") + let exports = module.exports(); + + for export in exports { + match export.ty() { + ExternType::Func(ft) => { + match export.name() { + "call" => call_found = true, + "deploy" => deploy_found = true, + _ => + return Err( + "unknown function export: expecting only deploy and call functions", + ), + } + // Check the signature. + // Both "call" and "deploy" have the () -> () function type. + // We still support () -> (i32) for backwards compatibility. + if !(ft.params().is_empty() && + (ft.results().is_empty() || ft.results() == [WasmiValueType::I32])) + { + return Err("entry point has wrong signature") + } }, - }; - - // Then check the signature. - // Both "call" and "deploy" has a () -> () function type. - // We still support () -> (i32) for backwards compatibility. - let func_ty_idx = func_entries - .get(fn_idx as usize) - .ok_or("export refers to non-existent function")? - .type_ref(); - let Type::Function(ref func_ty) = - types.get(func_ty_idx as usize).ok_or("function has a non-existent type")?; - if !(func_ty.params().is_empty() && - (func_ty.results().is_empty() || func_ty.results() == [ValueType::I32])) - { - return Err("entry point has wrong signature") + ExternType::Memory(_) => return Err("memory export is forbidden"), + ExternType::Global(_) => return Err("global export is forbidden"), + ExternType::Table(_) => return Err("table export is forbidden"), } } @@ -305,311 +138,195 @@ impl<'a, T: Config> ContractModule<'a, T> { /// Scan an import section if any. /// - /// This makes sure that the import section looks as we expect it from a contract - /// and enforces and returns the memory type declared by the contract if any. + /// This makes sure that: + /// - The import section looks as we expect it from a contract. + /// - The limits of the memory type declared by the contract comply with the Schedule. + /// + /// Returns the checked memory limits back to caller. /// - /// `import_fn_banlist`: list of function names that are disallowed to be imported - fn scan_imports( + /// This method fails if: + /// + /// - Memory import not found in the module. + /// - Tables or globals found among imports. + /// - `call_chain_extension` host function is imported, while chain extensions are disabled. + /// + /// NOTE that only single memory instance is allowed for contract modules, which is enforced by + /// this check combined with multi_memory proposal gets disabled in the engine. + pub fn scan_imports( &self, - import_fn_banlist: &[&[u8]], - ) -> Result, &'static str> { + schedule: &Schedule, + ) -> Result<(u32, u32), &'static str> { let module = &self.module; - let import_entries = module.import_section().map(|is| is.entries()).unwrap_or(&[]); - let mut imported_mem_type = None; - - for import in import_entries { - match *import.external() { - External::Table(_) => return Err("Cannot import tables"), - External::Global(_) => return Err("Cannot import globals"), - External::Function(_) => { - if !T::ChainExtension::enabled() && - import.field().as_bytes() == b"seal_call_chain_extension" + let imports = module.imports(); + let mut memory_limits = None; + + for import in imports { + match *import.ty() { + ExternType::Table(_) => return Err("Cannot import tables"), + ExternType::Global(_) => return Err("Cannot import globals"), + ExternType::Func(_) => { + let _ = import.ty().func().ok_or("expected a function")?; + + if !::ChainExtension::enabled() && + import.name().as_bytes() == b"seal_call_chain_extension" || + import.name().as_bytes() == b"call_chain_extension" { - return Err("module uses chain extensions but chain extensions are disabled") - } - - if import_fn_banlist.iter().any(|f| import.field().as_bytes() == *f) { - return Err("module imports a banned function") + return Err("Module uses chain extensions but chain extensions are disabled") } }, - External::Memory(ref memory_type) => { - if import.module() != IMPORT_MODULE_MEMORY { + ExternType::Memory(mt) => { + if import.module().as_bytes() != IMPORT_MODULE_MEMORY.as_bytes() { return Err("Invalid module for imported memory") } - if import.field() != "memory" { + if import.name().as_bytes() != b"memory" { return Err("Memory import must have the field name 'memory'") } - if imported_mem_type.is_some() { + if memory_limits.is_some() { return Err("Multiple memory imports defined") } - imported_mem_type = Some(memory_type); + // Parse memory limits defaulting it to (0,0). + // Any access to it will then lead to out of bounds trap. + let (initial, maximum) = ( + mt.initial_pages().to_bytes().unwrap_or(0).saturating_div(BYTES_PER_PAGE) + as u32, + mt.maximum_pages().map_or(schedule.limits.memory_pages, |p| { + p.to_bytes().unwrap_or(0).saturating_div(BYTES_PER_PAGE) as u32 + }), + ); + if initial > maximum { + return Err( + "Requested initial number of memory pages should not exceed the requested maximum", + ) + } + if maximum > schedule.limits.memory_pages { + return Err("Maximum number of memory pages should not exceed the maximum configured in the Schedule") + } + + memory_limits = Some((initial, maximum)); continue }, } } - Ok(imported_mem_type) - } - - fn into_wasm_code(self) -> Result, &'static str> { - elements::serialize(self.module).map_err(|_| "error serializing instrumented module") - } -} -fn get_memory_limits( - module: Option<&MemoryType>, - schedule: &Schedule, -) -> Result<(u32, u32), &'static str> { - if let Some(memory_type) = module { - // Inspect the module to extract the initial and maximum page count. - let limits = memory_type.limits(); - match (limits.initial(), limits.maximum()) { - (initial, Some(maximum)) if initial > maximum => - Err("Requested initial number of pages should not exceed the requested maximum"), - (_, Some(maximum)) if maximum > schedule.limits.memory_pages => - Err("Maximum number of pages should not exceed the configured maximum."), - (initial, Some(maximum)) => Ok((initial, maximum)), - (_, None) => { - // Maximum number of pages should be always declared. - // This isn't a hard requirement and can be treated as a maximum set - // to configured maximum. - Err("Maximum number of pages should be always declared.") - }, - } - } else { - // If none memory imported then just crate an empty placeholder. - // Any access to it will lead to out of bounds trap. - Ok((0, 0)) + memory_limits.ok_or("No memory import found in the module") } } -/// Check and instrument the given `original_code`. +/// Check that given `code` satisfies constraints required for the contract Wasm module. +/// This includes two groups of checks: /// -/// On success it returns the instrumented versions together with its `(initial, maximum)` -/// error requirement. The memory requirement was also validated against the `schedule`. -fn instrument( - original_code: &[u8], +/// 1. General engine-side validation makes sure the module is consistent and does not contain +/// forbidden WebAssembly features. +/// 2. Additional checks which are specific to smart contracts eligible for this pallet. +fn validate( + code: &[u8], schedule: &Schedule, determinism: Determinism, - try_instantiate: TryInstantiate, - reason: InstrumentReason, -) -> Result<(Vec, (u32, u32)), (DispatchError, &'static str)> +) -> Result<(), (DispatchError, &'static str)> where E: Environment<()>, T: Config, { - // Do not enable any features here. Any additional feature needs to be carefully - // checked for potential security issues. For example, enabling multi value could lead - // to a DoS vector: It breaks our assumption that branch instructions are of constant time. - // Depending on the implementation they can linearly depend on the amount of values returned - // from a block. - Validator::new_with_features(WasmFeatures { - relaxed_simd: false, - threads: false, - tail_call: false, - multi_memory: false, - exceptions: false, - memory64: false, - extended_const: false, - component_model: false, - // This is not our only defense: We check for float types later in the preparation - // process. Additionally, all instructions explictily need to have weights assigned - // or the deployment will fail. We have none assigned for float instructions. - deterministic_only: matches!(determinism, Determinism::Deterministic), - mutable_global: false, - saturating_float_to_int: false, - sign_extension: false, - bulk_memory: false, - multi_value: false, - reference_types: false, - simd: false, - }) - .validate_all(original_code) - .map_err(|err| { - log::debug!(target: "runtime::contracts", "{}", err); - (Error::::CodeRejected.into(), "validation of new code failed") - })?; - - let (code, (initial, maximum)) = (|| { - let contract_module = ContractModule::new(original_code, schedule)?; + (|| { + // We check that the module is generally valid, + // and does not have restricted WebAssembly features, here. + let contract_module = LoadedModule::new::(code, determinism, None)?; + // The we check that module satisfies constraints the pallet puts on contracts. contract_module.scan_exports()?; - contract_module.ensure_no_internal_memory()?; - contract_module.ensure_table_size_limit(schedule.limits.table_size)?; - contract_module.ensure_global_variable_limit(schedule.limits.globals)?; - contract_module.ensure_local_variable_limit(schedule.limits.locals)?; - contract_module.ensure_parameter_limit(schedule.limits.parameters)?; - contract_module.ensure_br_table_size_limit(schedule.limits.br_table_size)?; - - if matches!(determinism, Determinism::Deterministic) { - contract_module.ensure_no_floating_types()?; - } - - // We disallow importing `gas` function here since it is treated as implementation detail. - let disallowed_imports = [b"gas".as_ref()]; - let memory_limits = - get_memory_limits(contract_module.scan_imports(&disallowed_imports)?, schedule)?; - - let code = contract_module.inject_gas_metering(determinism)?.into_wasm_code()?; - - Ok((code, memory_limits)) + contract_module.scan_imports::(schedule)?; + Ok(()) })() .map_err(|msg: &str| { - log::debug!(target: "runtime::contracts", "new code rejected: {}", msg); + log::debug!(target: LOG_TARGET, "New code rejected on validation: {}", msg); (Error::::CodeRejected.into(), msg) })?; // This will make sure that the module can be actually run within wasmi: // - // - Doesn't use any unknown imports. - // - Doesn't explode the wasmi bytecode generation. - if matches!(try_instantiate, TryInstantiate::Instantiate) { - // We don't actually ever run any code so we can get away with a minimal stack which - // reduces the amount of memory that needs to be zeroed. - let stack_limits = StackLimits::new(1, 1, 0).expect("initial <= max; qed"); - PrefabWasmModule::::instantiate::( - &code, - (), - (initial, maximum), - stack_limits, - match reason { - InstrumentReason::New => AllowDeprecatedInterface::No, - InstrumentReason::Reinstrument => AllowDeprecatedInterface::Yes, - }, - ) - .map_err(|err| { - log::debug!(target: "runtime::contracts", "{}", err); - (Error::::CodeRejected.into(), "new code rejected after instrumentation") - })?; - } + // - It doesn't use any unknown imports. + // - It doesn't explode the wasmi bytecode generation. + // + // We don't actually ever execute this instance so we can get away with a minimal stack which + // reduces the amount of memory that needs to be zeroed. + let stack_limits = StackLimits::new(1, 1, 0).expect("initial <= max; qed"); + WasmBlob::::instantiate::( + &code, + (), + schedule, + determinism, + stack_limits, + AllowDeprecatedInterface::No, + ) + .map_err(|err| { + log::debug!(target: LOG_TARGET, "{}", err); + (Error::::CodeRejected.into(), "New code rejected on wasmi instantiation!") + })?; - Ok((code, (initial, maximum))) + Ok(()) } -/// Loads the given module given in `original_code`, performs some checks on it and -/// does some preprocessing. -/// -/// The checks are: +/// Validates the given binary `code` is a valid Wasm module satisfying following constraints: /// -/// - the provided code is a valid wasm module -/// - the module doesn't define an internal memory instance -/// - imported memory (if any) doesn't reserve more memory than permitted by the `schedule` -/// - all imported functions from the external environment matches defined by `env` module +/// - The module doesn't export any memory. +/// - The module does imports memory, which limits lay within the limits permitted by the +/// `schedule`. +/// - All imported functions from the external environment match defined by `env` module. /// -/// The preprocessing includes injecting code for gas metering and metering the height of stack. +/// Also constructs contract `code_info` by calculating the storage deposit. pub fn prepare( - original_code: CodeVec, + code: CodeVec, schedule: &Schedule, owner: AccountIdOf, determinism: Determinism, - try_instantiate: TryInstantiate, -) -> Result, (DispatchError, &'static str)> +) -> Result, (DispatchError, &'static str)> where E: Environment<()>, T: Config, { - let (code, (initial, maximum)) = instrument::( - original_code.as_ref(), - schedule, - determinism, - try_instantiate, - InstrumentReason::New, - )?; - - let original_code_len = original_code.len(); - - let mut module = PrefabWasmModule { - instruction_weights_version: schedule.instruction_weights.version, - initial, - maximum, - code: code.try_into().map_err(|_| (>::CodeTooLarge.into(), ""))?, - code_hash: T::Hashing::hash(&original_code), - original_code: Some(original_code), - owner_info: None, - determinism, - }; + validate::(code.as_ref(), schedule, determinism)?; - // We need to add the sizes of the `#[codec(skip)]` fields which are stored in different - // storage items. This is also why we have `3` items added and not only one. - let bytes_added = module - .encoded_size() - .saturating_add(original_code_len) - .saturating_add(>::max_encoded_len()) as u32; - let deposit = Diff { bytes_added, items_added: 3, ..Default::default() } + // Calculate deposit for storing contract code and `code_info` in two different storage items. + let code_len = code.len() as u32; + let bytes_added = code_len.saturating_add(>::max_encoded_len() as u32); + let deposit = Diff { bytes_added, items_added: 2, ..Default::default() } .update_contract::(None) .charge_or_zero(); + let code_info = CodeInfo { owner, deposit, determinism, refcount: 0, code_len }; + let code_hash = T::Hashing::hash(&code); - module.owner_info = Some(OwnerInfo { owner, deposit, refcount: 0 }); - - Ok(module) -} - -/// Same as [`prepare`] but without constructing a new module. -/// -/// Used to update the code of an existing module to the newest [`Schedule`] version. -/// Stictly speaking is not necessary to check the existing code before reinstrumenting because -/// it can't change in the meantime. However, since we recently switched the validation library -/// we want to re-validate to weed out any bugs that were lurking in the old version. -pub fn reinstrument( - original_code: &[u8], - schedule: &Schedule, - determinism: Determinism, -) -> Result, DispatchError> -where - E: Environment<()>, - T: Config, -{ - instrument::( - original_code, - schedule, - determinism, - // This function was triggered by an interaction with an existing contract code - // that will try to instantiate anyways. Failing here would not help - // as the contract is already on chain. - TryInstantiate::Skip, - InstrumentReason::Reinstrument, - ) - .map_err(|(err, msg)| { - log::error!(target: "runtime::contracts", "CodeRejected during reinstrument: {}", msg); - err - }) - .map(|(code, _)| code) + Ok(WasmBlob { code, code_info, code_hash }) } /// Alternate (possibly unsafe) preparation functions used only for benchmarking and testing. /// /// For benchmarking we need to construct special contracts that might not pass our -/// sanity checks or need to skip instrumentation for correct results. We hide functions -/// allowing this behind a feature that is only set during benchmarking or testing to -/// prevent usage in production code. +/// sanity checks. We hide functions allowing this behind a feature that is only set during +/// benchmarking or testing to prevent usage in production code. #[cfg(any(test, feature = "runtime-benchmarks"))] pub mod benchmarking { use super::*; - /// Prepare function that neither checks nor instruments the passed in code. + /// Prepare function that does not perform export section checks on the passed in code. pub fn prepare( - original_code: Vec, + code: Vec, schedule: &Schedule, owner: AccountIdOf, - ) -> Result, &'static str> { - let contract_module = ContractModule::new(&original_code, schedule)?; - let memory_limits = get_memory_limits(contract_module.scan_imports(&[])?, schedule)?; - Ok(PrefabWasmModule { - instruction_weights_version: schedule.instruction_weights.version, - initial: memory_limits.0, - maximum: memory_limits.1, - code_hash: T::Hashing::hash(&original_code), - original_code: Some(original_code.try_into().map_err(|_| "Original code too large")?), - code: contract_module - .into_wasm_code()? - .try_into() - .map_err(|_| "Instrumented code too large")?, - owner_info: Some(OwnerInfo { - owner, - // this is a helper function for benchmarking which skips deposit collection - deposit: Default::default(), - refcount: 0, - }), - determinism: Determinism::Deterministic, - }) + ) -> Result, DispatchError> { + let determinism = Determinism::Enforced; + let contract_module = LoadedModule::new::(&code, determinism, None)?; + let _ = contract_module.scan_imports::(schedule)?; + let code: CodeVec = code.try_into().map_err(|_| >::CodeTooLarge)?; + let code_info = CodeInfo { + owner, + // this is a helper function for benchmarking which skips deposit collection + deposit: Default::default(), + refcount: 0, + code_len: code.len() as u32, + determinism, + }; + let code_hash = T::Hashing::hash(&code); + + Ok(WasmBlob { code, code_info, code_hash }) } } @@ -624,9 +341,9 @@ mod tests { use pallet_contracts_proc_macro::define_env; use std::fmt; - impl fmt::Debug for PrefabWasmModule { + impl fmt::Debug for WasmBlob { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "PreparedContract {{ .. }}") + write!(f, "ContractCode {{ .. }}") } } @@ -682,8 +399,7 @@ mod tests { wasm, &schedule, ALICE, - Determinism::Deterministic, - TryInstantiate::Instantiate, + Determinism::Enforced, ); assert_matches::assert_matches!(r.map_err(|(_, msg)| msg), $($expected)*); } @@ -704,108 +420,9 @@ mod tests { ) (func (export "deploy")) )"#, - Err("gas instrumentation failed") + Err("Can't load the module into wasmi!") ); - mod functions { - use super::*; - - prepare_test!( - param_number_valid, - r#" - (module - (func (export "call")) - (func (export "deploy")) - (func (param i32 i32 i32)) - ) - "#, - Ok(_) - ); - - prepare_test!( - param_number_invalid, - r#" - (module - (func (export "call")) - (func (export "deploy")) - (func (param i32 i32 i32 i32)) - (func (param i32)) - ) - "#, - Err("Use of a function type with too many parameters.") - ); - } - - mod globals { - use super::*; - - prepare_test!( - global_number_valid, - r#" - (module - (global i64 (i64.const 0)) - (global i64 (i64.const 0)) - (global i64 (i64.const 0)) - (func (export "call")) - (func (export "deploy")) - ) - "#, - Ok(_) - ); - - prepare_test!( - global_number_too_high, - r#" - (module - (global i64 (i64.const 0)) - (global i64 (i64.const 0)) - (global i64 (i64.const 0)) - (global i64 (i64.const 0)) - (func (export "call")) - (func (export "deploy")) - ) - "#, - Err("module declares too many globals") - ); - } - - mod locals { - use super::*; - - prepare_test!( - local_number_valid, - r#" - (module - (func - (local i32) - (local i32) - (local i32) - ) - (func (export "call")) - (func (export "deploy")) - ) - "#, - Ok(_) - ); - - prepare_test!( - local_number_too_high, - r#" - (module - (func - (local i32) - (local i32) - (local i32) - (local i32) - ) - (func (export "call")) - (func (export "deploy")) - ) - "#, - Err("single function declares too many locals") - ); - } - mod memories { use super::*; @@ -832,7 +449,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("module declares internal memory") + Err("No memory import found in the module") ); prepare_test!( @@ -844,7 +461,7 @@ mod tests { (func (export "call")) (func (export "deploy")) )"#, - Ok(_) + Err("No memory import found in the module") ); prepare_test!( @@ -857,20 +474,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("validation of new code failed") - ); - - prepare_test!( - no_maximum, - r#" - (module - (import "env" "memory" (memory 1)) - - (func (export "call")) - (func (export "deploy")) - ) - "#, - Err("Maximum number of pages should be always declared.") + Err("Can't load the module into wasmi!") ); prepare_test!( @@ -896,7 +500,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("Maximum number of pages should not exceed the configured maximum.") + Err("Maximum number of memory pages should not exceed the maximum configured in the Schedule") ); prepare_test!( @@ -923,7 +527,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("validation of new code failed") + Err("Can't load the module into wasmi!") ); prepare_test!( @@ -952,75 +556,6 @@ mod tests { ); } - mod tables { - use super::*; - - prepare_test!( - no_tables, - r#" - (module - (func (export "call")) - (func (export "deploy")) - ) - "#, - Ok(_) - ); - - prepare_test!( - table_valid_size, - r#" - (module - (table 3 funcref) - - (func (export "call")) - (func (export "deploy")) - ) - "#, - Ok(_) - ); - - prepare_test!( - table_too_big, - r#" - (module - (table 4 funcref) - - (func (export "call")) - (func (export "deploy")) - )"#, - Err("table exceeds maximum size allowed") - ); - - prepare_test!( - br_table_valid_size, - r#" - (module - (func (export "call")) - (func (export "deploy")) - (func - i32.const 0 - br_table 0 0 0 0 - ) - ) - "#, - Ok(_) - ); - - prepare_test!( - br_table_too_big, - r#" - (module - (func (export "call")) - (func (export "deploy")) - (func - i32.const 0 - br_table 0 0 0 0 0 - ) - )"#, - Err("BrTable's immediate value is too big.") - ); - } - mod imports { use super::*; @@ -1029,6 +564,7 @@ mod tests { r#" (module (import "seal0" "nop" (func (param i64))) + (import "env" "memory" (memory 1 1)) (func (export "call")) (func (export "deploy")) @@ -1037,21 +573,6 @@ mod tests { Ok(_) ); - // even though gas is defined the contract can't import it since - // it is an implementation defined. - prepare_test!( - can_not_import_gas_function, - r#" - (module - (import "seal0" "gas" (func (param i32))) - - (func (export "call")) - (func (export "deploy")) - ) - "#, - Err("module imports a banned function") - ); - // memory is in "env" and not in "seal0" prepare_test!( memory_not_in_seal0, @@ -1066,7 +587,7 @@ mod tests { Err("Invalid module for imported memory") ); - // memory is in "env" and not in some arbitrary module + // Memory is in "env" and not in some arbitrary module prepare_test!( memory_not_in_arbitrary_module, r#" @@ -1085,6 +606,8 @@ mod tests { r#" (module (import "seal1" "nop" (func (param i32))) + (import "env" "memory" (memory 1 1)) + (func (export "call")) (func (export "deploy")) @@ -1093,18 +616,18 @@ mod tests { Ok(_) ); - // wrong signature prepare_test!( wrong_signature, r#" (module - (import "seal0" "gas" (func (param i64))) + (import "seal0" "input" (func (param i64))) + (import "env" "memory" (memory 1 1)) (func (export "call")) (func (export "deploy")) ) "#, - Err("module imports a banned function") + Err("New code rejected on wasmi instantiation!") ); prepare_test!( @@ -1117,7 +640,22 @@ mod tests { (func (export "deploy")) ) "#, - Err("new code rejected after instrumentation") + Err("No memory import found in the module") + ); + + // Try to import function from not a "seal*" module. + prepare_test!( + try_import_from_wrong_module, + r#" + (module + (import "env" "panic" (func)) + (import "env" "memory" (memory 1 1)) + + (func (export "call")) + (func (export "deploy")) + ) + "#, + Err("New code rejected on wasmi instantiation!") ); } @@ -1128,6 +666,7 @@ mod tests { it_works, r#" (module + (import "env" "memory" (memory 1 1)) (func (export "call")) (func (export "deploy")) ) @@ -1135,6 +674,17 @@ mod tests { Ok(_) ); + prepare_test!( + omit_memory, + r#" + (module + (func (export "call")) + (func (export "deploy")) + ) + "#, + Err("No memory import found in the module") + ); + prepare_test!( omit_deploy, r#" @@ -1156,21 +706,23 @@ mod tests { ); // Try to use imported function as an entry point. + // This is allowed. prepare_test!( try_sneak_export_as_entrypoint, r#" (module (import "seal0" "panic" (func)) + (import "env" "memory" (memory 1 1)) (func (export "deploy")) (export "call" (func 0)) ) "#, - Err("entry point points to an imported function") + Ok(_) ); - // Try to use imported function as an entry point. + // Try to use global as an entry point. prepare_test!( try_sneak_export_as_global, r#" @@ -1179,7 +731,7 @@ mod tests { (global (export "call") i32 (i32.const 0)) ) "#, - Err("expected a function") + Err("global export is forbidden") ); prepare_test!( @@ -1202,7 +754,7 @@ mod tests { (func (export "whatevs")) ) "#, - Err("unknown export: expecting only deploy and call functions") + Err("unknown function export: expecting only deploy and call functions") ); prepare_test!( @@ -1214,7 +766,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("use of floating point type in globals is forbidden") + Err("Can't load the module into wasmi!") ); prepare_test!( @@ -1226,7 +778,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("use of floating point type in locals is forbidden") + Err("Can't load the module into wasmi!") ); prepare_test!( @@ -1238,7 +790,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("use of floating point type in function types is forbidden") + Err("Can't load the module into wasmi!") ); prepare_test!( @@ -1250,7 +802,7 @@ mod tests { (func (export "deploy")) ) "#, - Err("use of floating point type in function types is forbidden") + Err("Can't load the module into wasmi!") ); } } diff --git a/frame/contracts/src/wasm/runtime.rs b/frame/contracts/src/wasm/runtime.rs index 9cdf0bf0928a..4c5b98d8786d 100644 --- a/frame/contracts/src/wasm/runtime.rs +++ b/frame/contracts/src/wasm/runtime.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,10 +18,10 @@ //! Environment definition of the wasm smart-contract runtime. use crate::{ - exec::{ExecError, ExecResult, Ext, FixSizedKey, TopicOf, VarSizedKey}, + exec::{ExecError, ExecResult, Ext, Key, TopicOf}, gas::{ChargedAmount, Token}, schedule::HostFnWeights, - BalanceOf, CodeHash, Config, Error, SENTINEL, + BalanceOf, CodeHash, Config, DebugBufferVec, Error, SENTINEL, }; use bitflags::bitflags; @@ -67,25 +67,12 @@ pub trait Environment { } /// Type of a storage key. -#[allow(dead_code)] enum KeyType { /// Legacy fix sized key `[u8;32]`. Fix, /// Variable sized key used in transparent hashing, /// cannot be larger than MaxStorageKeyLen. - Variable(u32), -} - -impl KeyType { - fn len(&self) -> Result { - match self { - KeyType::Fix => Ok(32u32), - KeyType::Variable(len) => { - ensure!(len <= &::MaxStorageKeyLen::get(), Error::::DecodingFailed); - Ok(*len) - }, - } - } + Var(u32), } /// Every error that can be returned to a contract when it calls any of the host functions. @@ -96,6 +83,7 @@ impl KeyType { /// will not be changed or removed. This means that any contract **must not** exhaustively /// match return codes. Instead, contracts should prepare for unknown variants and deal with /// those errors gracefully in order to be forward compatible. +#[derive(Debug)] #[repr(u32)] pub enum ReturnCode { /// API call successful. @@ -114,15 +102,14 @@ pub enum ReturnCode { CodeNotFound = 7, /// The contract that was called is no contract (a plain account). NotCallable = 8, - /// The call to `seal_debug_message` had no effect because debug message - /// recording was disabled. - LoggingDisabled = 9, /// The call dispatched by `seal_call_runtime` was executed but returned an error. - CallRuntimeReturnedError = 10, + CallRuntimeFailed = 10, /// ECDSA pubkey recovery failed (most probably wrong recovery id or signature), or /// ECDSA compressed pubkey conversion into Ethereum address failed (most probably /// wrong pubkey provided). EcdsaRecoverFailed = 11, + /// sr25519 signature verification failed. + Sr25519VerifyFailed = 12, } impl From for ReturnCode { @@ -186,9 +173,6 @@ impl HostError for TrapReason {} #[cfg_attr(test, derive(Debug, PartialEq, Eq))] #[derive(Copy, Clone)] pub enum RuntimeCosts { - /// Charge the gas meter with the cost of a metering block. The charged costs are - /// the supplied cost of the block plus the overhead of the metering itself. - MeteringBlock(u64), /// Weight charged for copying data from the sandbox. CopyFromContract(u32), /// Weight charged for copying data to the sandbox. @@ -203,6 +187,8 @@ pub enum RuntimeCosts { OwnCodeHash, /// Weight of calling `seal_caller_is_origin`. CallerIsOrigin, + /// Weight of calling `caller_is_root`. + CallerIsRoot, /// Weight of calling `seal_address`. Address, /// Weight of calling `seal_gas_left`. @@ -229,8 +215,8 @@ pub enum RuntimeCosts { Random, /// Weight of calling `seal_deposit_event` with the given number of topics and event size. DepositEvent { num_topic: u32, len: u32 }, - /// Weight of calling `seal_debug_message`. - DebugMessage, + /// Weight of calling `seal_debug_message` per byte of passed message. + DebugMessage(u32), /// Weight of calling `seal_set_storage` for the given storage item sizes. SetStorage { old_bytes: u32, new_bytes: u32 }, /// Weight of calling `seal_clear_storage` per cleared byte. @@ -265,6 +251,8 @@ pub enum RuntimeCosts { HashBlake128(u32), /// Weight of calling `seal_ecdsa_recover`. EcdsaRecovery, + /// Weight of calling `seal_sr25519_verify` for the given input size. + Sr25519Verify(u32), /// Weight charged by a chain extension through `seal_call_chain_extension`. ChainExtension(Weight), /// Weight charged for calling into the runtime. @@ -285,7 +273,6 @@ impl RuntimeCosts { fn token(&self, s: &HostFnWeights) -> RuntimeToken { use self::RuntimeCosts::*; let weight = match *self { - MeteringBlock(amount) => s.gas.saturating_add(Weight::from_ref_time(amount)), CopyFromContract(len) => s.return_per_byte.saturating_mul(len.into()), CopyToContract(len) => s.input_per_byte.saturating_mul(len.into()), Caller => s.caller, @@ -293,6 +280,7 @@ impl RuntimeCosts { CodeHash => s.code_hash, OwnCodeHash => s.own_code_hash, CallerIsOrigin => s.caller_is_origin, + CallerIsRoot => s.caller_is_root, Address => s.address, GasLeft => s.gas_left, Balance => s.balance, @@ -309,7 +297,9 @@ impl RuntimeCosts { .deposit_event .saturating_add(s.deposit_event_per_topic.saturating_mul(num_topic.into())) .saturating_add(s.deposit_event_per_byte.saturating_mul(len.into())), - DebugMessage => s.debug_message, + DebugMessage(len) => s + .debug_message + .saturating_add(s.deposit_event_per_byte.saturating_mul(len.into())), SetStorage { new_bytes, old_bytes } => s .set_storage .saturating_add(s.set_storage_per_new_byte.saturating_mul(new_bytes.into())) @@ -348,6 +338,9 @@ impl RuntimeCosts { .hash_blake2_128 .saturating_add(s.hash_blake2_128_per_byte.saturating_mul(len.into())), EcdsaRecovery => s.ecdsa_recover, + Sr25519Verify(len) => s + .sr25519_verify + .saturating_add(s.sr25519_verify_per_byte.saturating_mul(len.into())), ChainExtension(weight) => weight, CallRuntime(weight) => weight, SetCodeHash => s.set_code_hash, @@ -371,7 +364,7 @@ impl RuntimeCosts { macro_rules! charge_gas { ($runtime:expr, $costs:expr) => {{ let token = $costs.token(&$runtime.ext.schedule().host_fn_weights); - $runtime.ext.gas_meter().charge(token) + $runtime.ext.gas_meter_mut().charge(token) }}; } @@ -438,7 +431,7 @@ bitflags! { /// The kind of call that should be performed. enum CallType { /// Execute another instantiated contract - Call { callee_ptr: u32, value_ptr: u32, gas: u64 }, + Call { callee_ptr: u32, value_ptr: u32, deposit_ptr: u32, weight: Weight }, /// Execute deployed code in the context (storage, account ID, value) of the caller contract DelegateCall { code_hash_ptr: u32 }, } @@ -487,29 +480,40 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { /// Converts the sandbox result and the runtime state into the execution outcome. pub fn to_execution_result(self, sandbox_result: Result<(), wasmi::Error>) -> ExecResult { + use wasmi::core::TrapCode::OutOfFuel; use TrapReason::*; + match sandbox_result { // Contract returned from main function -> no data was returned. Ok(_) => Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }), + // `OutOfGas` when host asks engine to consume more than left in the _store_. + // We should never get this case, as gas meter is being charged (and hence raises error) + // first. + Err(wasmi::Error::Store(_)) => Err(Error::::OutOfGas.into()), // Contract either trapped or some host function aborted the execution. Err(wasmi::Error::Trap(trap)) => { + if let Some(OutOfFuel) = trap.trap_code() { + // `OutOfGas` during engine execution. + return Err(Error::::OutOfGas.into()) + } // If we encoded a reason then it is some abort generated by a host function. - // Otherwise the trap came from the contract. - let reason: TrapReason = *trap - .into_host() - .ok_or(Error::::ContractTrapped)? - .downcast() - .expect("`TrapReason` is the only type we use to encode host errors; qed"); - match reason { - Return(ReturnData { flags, data }) => { - let flags = - ReturnFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?; - Ok(ExecReturnValue { flags, data }) - }, - Termination => - Ok(ExecReturnValue { flags: ReturnFlags::empty(), data: Vec::new() }), - SupervisorError(error) => return Err(error.into()), + if let Some(reason) = &trap.downcast_ref::() { + match &reason { + Return(ReturnData { flags, data }) => { + let flags = ReturnFlags::from_bits(*flags) + .ok_or(Error::::InvalidCallFlags)?; + return Ok(ExecReturnValue { flags, data: data.to_vec() }) + }, + Termination => + return Ok(ExecReturnValue { + flags: ReturnFlags::empty(), + data: Vec::new(), + }), + SupervisorError(error) => return Err((*error).into()), + } } + // Otherwise the trap came from the contract itself. + Err(Error::::ContractTrapped.into()) }, // Any other error is returned only if instantiation or linking failed (i.e. // wasm binary tried to import a function that is not provided by the host). @@ -542,7 +546,7 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { /// refunded to match the actual amount. pub fn adjust_gas(&mut self, charged: ChargedAmount, actual_costs: RuntimeCosts) { let token = actual_costs.token(&self.ext.schedule().host_fn_weights); - self.ext.gas_meter().adjust_gas(charged, token); + self.ext.gas_meter_mut().adjust_gas(charged, token); } /// Read designated chunk from the sandbox memory. @@ -753,6 +757,29 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { (err, _) => Self::err_into_return_code(err), } } + fn decode_key( + &self, + memory: &[u8], + key_type: KeyType, + key_ptr: u32, + ) -> Result, TrapReason> { + let res = match key_type { + KeyType::Fix => { + let key = self.read_sandbox_memory(memory, key_ptr, 32u32)?; + Key::try_from_fix(key) + }, + KeyType::Var(len) => { + ensure!( + len <= <::T as Config>::MaxStorageKeyLen::get(), + Error::::DecodingFailed + ); + let key = self.read_sandbox_memory(memory, key_ptr, len)?; + Key::try_from_var(key) + }, + }; + + res.map_err(|_| Error::::DecodingFailed.into()) + } fn set_storage( &mut self, @@ -768,20 +795,9 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { if value_len > max_size { return Err(Error::::ValueTooLarge.into()) } - let key = self.read_sandbox_memory(memory, key_ptr, key_type.len::()?)?; + let key = self.decode_key(memory, key_type, key_ptr)?; let value = Some(self.read_sandbox_memory(memory, value_ptr, value_len)?); - let write_outcome = match key_type { - KeyType::Fix => self.ext.set_storage( - &FixSizedKey::try_from(key).map_err(|_| Error::::DecodingFailed)?, - value, - false, - )?, - KeyType::Variable(_) => self.ext.set_storage_transparent( - &VarSizedKey::::try_from(key).map_err(|_| Error::::DecodingFailed)?, - value, - false, - )?, - }; + let write_outcome = self.ext.set_storage(&key, value, false)?; self.adjust_gas( charged, @@ -797,19 +813,8 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { key_ptr: u32, ) -> Result { let charged = self.charge_gas(RuntimeCosts::ClearStorage(self.ext.max_value_size()))?; - let key = self.read_sandbox_memory(memory, key_ptr, key_type.len::()?)?; - let outcome = match key_type { - KeyType::Fix => self.ext.set_storage( - &FixSizedKey::try_from(key).map_err(|_| Error::::DecodingFailed)?, - None, - false, - )?, - KeyType::Variable(_) => self.ext.set_storage_transparent( - &VarSizedKey::::try_from(key).map_err(|_| Error::::DecodingFailed)?, - None, - false, - )?, - }; + let key = self.decode_key(memory, key_type, key_ptr)?; + let outcome = self.ext.set_storage(&key, None, false)?; self.adjust_gas(charged, RuntimeCosts::ClearStorage(outcome.old_len())); Ok(outcome.old_len_with_sentinel()) @@ -824,15 +829,8 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { out_len_ptr: u32, ) -> Result { let charged = self.charge_gas(RuntimeCosts::GetStorage(self.ext.max_value_size()))?; - let key = self.read_sandbox_memory(memory, key_ptr, key_type.len::()?)?; - let outcome = match key_type { - KeyType::Fix => self.ext.get_storage( - &FixSizedKey::try_from(key).map_err(|_| Error::::DecodingFailed)?, - ), - KeyType::Variable(_) => self.ext.get_storage_transparent( - &VarSizedKey::::try_from(key).map_err(|_| Error::::DecodingFailed)?, - ), - }; + let key = self.decode_key(memory, key_type, key_ptr)?; + let outcome = self.ext.get_storage(&key); if let Some(value) = outcome { self.adjust_gas(charged, RuntimeCosts::GetStorage(value.len() as u32)); @@ -858,15 +856,8 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { key_ptr: u32, ) -> Result { let charged = self.charge_gas(RuntimeCosts::ContainsStorage(self.ext.max_value_size()))?; - let key = self.read_sandbox_memory(memory, key_ptr, key_type.len::()?)?; - let outcome = match key_type { - KeyType::Fix => self.ext.get_storage_size( - &FixSizedKey::try_from(key).map_err(|_| Error::::DecodingFailed)?, - ), - KeyType::Variable(_) => self.ext.get_storage_size_transparent( - &VarSizedKey::::try_from(key).map_err(|_| Error::::DecodingFailed)?, - ), - }; + let key = self.decode_key(memory, key_type, key_ptr)?; + let outcome = self.ext.get_storage_size(&key); self.adjust_gas(charged, RuntimeCosts::ClearStorage(outcome.unwrap_or(0))); Ok(outcome.unwrap_or(SENTINEL)) @@ -895,16 +886,22 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { }; let call_outcome = match call_type { - CallType::Call { callee_ptr, value_ptr, gas } => { + CallType::Call { callee_ptr, value_ptr, deposit_ptr, weight } => { let callee: <::T as frame_system::Config>::AccountId = self.read_sandbox_memory_as(memory, callee_ptr)?; + let deposit_limit: BalanceOf<::T> = if deposit_ptr == SENTINEL { + BalanceOf::<::T>::zero() + } else { + self.read_sandbox_memory_as(memory, deposit_ptr)? + }; let value: BalanceOf<::T> = self.read_sandbox_memory_as(memory, value_ptr)?; if value > 0u32.into() { self.charge_gas(RuntimeCosts::CallSurchargeTransfer)?; } self.ext.call( - Weight::from_ref_time(gas), + weight, + deposit_limit, callee, value, input_data, @@ -948,7 +945,8 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { &mut self, memory: &mut [u8], code_hash_ptr: u32, - gas: u64, + weight: Weight, + deposit_ptr: u32, value_ptr: u32, input_data_ptr: u32, input_data_len: u32, @@ -959,8 +957,12 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { salt_ptr: u32, salt_len: u32, ) -> Result { - let gas = Weight::from_ref_time(gas); self.charge_gas(RuntimeCosts::InstantiateBase { input_data_len, salt_len })?; + let deposit_limit: BalanceOf<::T> = if deposit_ptr == SENTINEL { + BalanceOf::<::T>::zero() + } else { + self.read_sandbox_memory_as(memory, deposit_ptr)? + }; let value: BalanceOf<::T> = self.read_sandbox_memory_as(memory, value_ptr)?; if value > 0u32.into() { self.charge_gas(RuntimeCosts::InstantiateSurchargeTransfer)?; @@ -969,7 +971,8 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { self.read_sandbox_memory_as(memory, code_hash_ptr)?; let input_data = self.read_sandbox_memory(memory, input_data_ptr, input_data_len)?; let salt = self.read_sandbox_memory(memory, salt_ptr, salt_len)?; - let instantiate_outcome = self.ext.instantiate(gas, code_hash, value, input_data, &salt); + let instantiate_outcome = + self.ext.instantiate(weight, deposit_limit, code_hash, value, input_data, &salt); if let Ok((address, output)) = &instantiate_outcome { if !output.flags.contains(ReturnFlags::REVERT) { self.write_sandbox_output( @@ -1011,21 +1014,11 @@ impl<'a, E: Ext + 'a> Runtime<'a, E> { // for every function. #[define_env(doc)] pub mod env { - /// Account for used gas. Traps if gas used is greater than gas limit. - /// - /// NOTE: This is a implementation defined call and is NOT a part of the public API. - /// This call is supposed to be called only by instrumentation injected code. - /// - /// - `amount`: How much gas is used. - fn gas(ctx: _, _memory: _, amount: u64) -> Result<(), TrapReason> { - ctx.charge_gas(RuntimeCosts::MeteringBlock(amount))?; - Ok(()) - } - /// Set the value at the given key in the contract storage. /// - /// Equivalent to the newer version [`super::seal1::Api::set_storage`] with the exception of the - /// return type. Still a valid thing to call when not interested in the return value. + /// Equivalent to the newer [`seal1`][`super::api_doc::Version1::set_storage`] version with the + /// exception of the return type. Still a valid thing to call when not interested in the return + /// value. #[prefixed_alias] fn set_storage( ctx: _, @@ -1093,13 +1086,14 @@ pub mod env { value_ptr: u32, value_len: u32, ) -> Result { - ctx.set_storage(memory, KeyType::Variable(key_len), key_ptr, value_ptr, value_len) + ctx.set_storage(memory, KeyType::Var(key_len), key_ptr, value_ptr, value_len) } /// Clear the value at the given key in the contract storage. /// - /// Equivalent to the newer version [`super::seal1::Api::clear_storage`] with the exception of - /// the return type. Still a valid thing to call when not interested in the return value. + /// Equivalent to the newer [`seal1`][`super::api_doc::Version1::clear_storage`] version with + /// the exception of the return type. Still a valid thing to call when not interested in the + /// return value. #[prefixed_alias] fn clear_storage(ctx: _, memory: _, key_ptr: u32) -> Result<(), TrapReason> { ctx.clear_storage(memory, KeyType::Fix, key_ptr).map(|_| ()) @@ -1119,7 +1113,7 @@ pub mod env { #[version(1)] #[prefixed_alias] fn clear_storage(ctx: _, memory: _, key_ptr: u32, key_len: u32) -> Result { - ctx.clear_storage(memory, KeyType::Variable(key_len), key_ptr) + ctx.clear_storage(memory, KeyType::Var(key_len), key_ptr) } /// Retrieve the value under the given key from storage. @@ -1176,7 +1170,7 @@ pub mod env { out_ptr: u32, out_len_ptr: u32, ) -> Result { - ctx.get_storage(memory, KeyType::Variable(key_len), key_ptr, out_ptr, out_len_ptr) + ctx.get_storage(memory, KeyType::Var(key_len), key_ptr, out_ptr, out_len_ptr) } /// Checks whether there is a value stored under the given key. @@ -1213,7 +1207,7 @@ pub mod env { #[version(1)] #[prefixed_alias] fn contains_storage(ctx: _, memory: _, key_ptr: u32, key_len: u32) -> Result { - ctx.contains_storage(memory, KeyType::Variable(key_len), key_ptr) + ctx.contains_storage(memory, KeyType::Var(key_len), key_ptr) } /// Retrieve and remove the value under the given key from storage. @@ -1239,9 +1233,13 @@ pub mod env { out_len_ptr: u32, ) -> Result { let charged = ctx.charge_gas(RuntimeCosts::TakeStorage(ctx.ext.max_value_size()))?; + ensure!( + key_len <= <::T as Config>::MaxStorageKeyLen::get(), + Error::::DecodingFailed + ); let key = ctx.read_sandbox_memory(memory, key_ptr, key_len)?; - if let crate::storage::WriteOutcome::Taken(value) = ctx.ext.set_storage_transparent( - &VarSizedKey::::try_from(key).map_err(|_| Error::::DecodingFailed)?, + if let crate::storage::WriteOutcome::Taken(value) = ctx.ext.set_storage( + &Key::::try_from_var(key).map_err(|_| Error::::DecodingFailed)?, None, true, )? { @@ -1321,7 +1319,47 @@ pub mod env { ctx.call( memory, CallFlags::ALLOW_REENTRY, - CallType::Call { callee_ptr, value_ptr, gas }, + CallType::Call { + callee_ptr, + value_ptr, + deposit_ptr: SENTINEL, + weight: Weight::from_parts(gas, 0), + }, + input_data_ptr, + input_data_len, + output_ptr, + output_len_ptr, + ) + } + + /// Make a call to another contract. + /// + /// Equivalent to the newer [`seal2`][`super::api_doc::Version2::call`] version but works with + /// *ref_time* Weight only. It is recommended to switch to the latest version, once it's + /// stabilized. + #[version(1)] + #[prefixed_alias] + fn call( + ctx: _, + memory: _, + flags: u32, + callee_ptr: u32, + gas: u64, + value_ptr: u32, + input_data_ptr: u32, + input_data_len: u32, + output_ptr: u32, + output_len_ptr: u32, + ) -> Result { + ctx.call( + memory, + CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, + CallType::Call { + callee_ptr, + value_ptr, + deposit_ptr: SENTINEL, + weight: Weight::from_parts(gas, 0), + }, input_data_ptr, input_data_len, output_ptr, @@ -1337,10 +1375,15 @@ pub mod env { /// /// # Parameters /// - /// - `flags`: See `crate::wasm::runtime::CallFlags` for a documenation of the supported flags. + /// - `flags`: See `crate::wasm::runtime::CallFlags` for a documentation of the supported flags. /// - `callee_ptr`: a pointer to the address of the callee contract. Should be decodable as an /// `T::AccountId`. Traps otherwise. - /// - `gas`: how much gas to devote to the execution. + /// - `ref_time_limit`: how much *ref_time* Weight to devote to the execution. + /// - `proof_size_limit`: how much *proof_size* Weight to devote to the execution. + /// - `deposit_ptr`: a pointer to the buffer with value of the storage deposit limit for the + /// call. Should be decodable as a `T::Balance`. Traps otherwise. Passing `SENTINEL` means + /// setting no specific limit for the call, which implies storage usage up to the limit of the + /// parent call. /// - `value_ptr`: a pointer to the buffer with value, how much value to send. Should be /// decodable as a `T::Balance`. Traps otherwise. /// - `input_data_ptr`: a pointer to a buffer to be used as input data to the callee. @@ -1358,14 +1401,16 @@ pub mod env { /// - `ReturnCode::CalleeTrapped` /// - `ReturnCode::TransferFailed` /// - `ReturnCode::NotCallable` - #[version(1)] - #[prefixed_alias] + #[version(2)] + #[unstable] fn call( ctx: _, memory: _, flags: u32, callee_ptr: u32, - gas: u64, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_ptr: u32, value_ptr: u32, input_data_ptr: u32, input_data_len: u32, @@ -1375,7 +1420,12 @@ pub mod env { ctx.call( memory, CallFlags::from_bits(flags).ok_or(Error::::InvalidCallFlags)?, - CallType::Call { callee_ptr, value_ptr, gas }, + CallType::Call { + callee_ptr, + value_ptr, + deposit_ptr, + weight: Weight::from_parts(ref_time_limit, proof_size_limit), + }, input_data_ptr, input_data_len, output_ptr, @@ -1439,8 +1489,7 @@ pub mod env { /// # Note /// /// The values `_code_hash_len` and `_value_len` are ignored because the encoded sizes - /// of those types are fixed through - /// [`codec::MaxEncodedLen`]. The fields exist + /// of those types are fixed through [`codec::MaxEncodedLen`]. The fields exist /// for backwards compatibility. Consider switching to the newest version of this function. #[prefixed_alias] fn instantiate( @@ -1463,7 +1512,47 @@ pub mod env { ctx.instantiate( memory, code_hash_ptr, - gas, + Weight::from_parts(gas, 0), + SENTINEL, + value_ptr, + input_data_ptr, + input_data_len, + address_ptr, + address_len_ptr, + output_ptr, + output_len_ptr, + salt_ptr, + salt_len, + ) + } + + /// Instantiate a contract with the specified code hash. + /// + /// Equivalent to the newer [`seal2`][`super::api_doc::Version2::instantiate`] version but works + /// with *ref_time* Weight only. It is recommended to switch to the latest version, once it's + /// stabilized. + #[version(1)] + #[prefixed_alias] + fn instantiate( + ctx: _, + memory: _, + code_hash_ptr: u32, + gas: u64, + value_ptr: u32, + input_data_ptr: u32, + input_data_len: u32, + address_ptr: u32, + address_len_ptr: u32, + output_ptr: u32, + output_len_ptr: u32, + salt_ptr: u32, + salt_len: u32, + ) -> Result { + ctx.instantiate( + memory, + code_hash_ptr, + Weight::from_parts(gas, 0), + SENTINEL, value_ptr, input_data_ptr, input_data_len, @@ -1490,15 +1579,21 @@ pub mod env { /// # Parameters /// /// - `code_hash_ptr`: a pointer to the buffer that contains the initializer code. - /// - `gas`: how much gas to devote to the execution of the initializer code. + /// - `ref_time_limit`: how much *ref_time* Weight to devote to the execution. + /// - `proof_size_limit`: how much *proof_size* Weight to devote to the execution. + /// - `deposit_ptr`: a pointer to the buffer with value of the storage deposit limit for + /// instantiation. Should be decodable as a `T::Balance`. Traps otherwise. Passing `SENTINEL` + /// means setting no specific limit for the call, which implies storage usage up to the limit + /// of the parent call. /// - `value_ptr`: a pointer to the buffer with value, how much value to send. Should be /// decodable as a `T::Balance`. Traps otherwise. /// - `input_data_ptr`: a pointer to a buffer to be used as input data to the initializer code. /// - `input_data_len`: length of the input data buffer. - /// - `address_ptr`: a pointer where the new account's address is copied to. - /// - `address_len_ptr`: in-out pointer to where the length of the buffer is read from and the - /// actual length is written to. - /// - `output_ptr`: a pointer where the output buffer is copied to. + /// - `address_ptr`: a pointer where the new account's address is copied to. `SENTINEL` means + /// not to copy. + /// - `address_len_ptr`: pointer to where put the length of the address. + /// - `output_ptr`: a pointer where the output buffer is copied to. `SENTINEL` means not to + /// copy. /// - `output_len_ptr`: in-out pointer to where the length of the buffer is read from and the /// actual length is written to. /// - `salt_ptr`: Pointer to raw bytes used for address derivation. See `fn contract_address`. @@ -1516,13 +1611,15 @@ pub mod env { /// - `ReturnCode::CalleeTrapped` /// - `ReturnCode::TransferFailed` /// - `ReturnCode::CodeNotFound` - #[version(1)] - #[prefixed_alias] + #[version(2)] + #[unstable] fn instantiate( ctx: _, memory: _, code_hash_ptr: u32, - gas: u64, + ref_time_limit: u64, + proof_size_limit: u64, + deposit_ptr: u32, value_ptr: u32, input_data_ptr: u32, input_data_len: u32, @@ -1536,7 +1633,8 @@ pub mod env { ctx.instantiate( memory, code_hash_ptr, - gas, + Weight::from_parts(ref_time_limit, proof_size_limit), + deposit_ptr, value_ptr, input_data_ptr, input_data_len, @@ -1657,14 +1755,18 @@ pub mod env { /// If this is a top-level call (i.e. initiated by an extrinsic) the origin address of the /// extrinsic will be returned. Otherwise, if this call is initiated by another contract then /// the address of the contract will be returned. The value is encoded as T::AccountId. + /// + /// If there is no address associated with the caller (e.g. because the caller is root) then + /// it traps with `BadOrigin`. #[prefixed_alias] fn caller(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::Caller)?; + let caller = ctx.ext.caller().account_id()?.clone(); Ok(ctx.write_sandbox_output( memory, out_ptr, out_len_ptr, - &ctx.ext.caller().encode(), + &caller.encode(), false, already_charged, )?) @@ -1763,6 +1865,21 @@ pub mod env { Ok(ctx.ext.caller_is_origin() as u32) } + /// Checks whether the caller of the current contract is root. + /// + /// Note that only the origin of the call stack can be root. Hence this function returning + /// `true` implies that the contract is being called by the origin. + /// + /// A return value of `true` indicates that this contract is being called by a root origin, + /// and `false` indicates that the caller is a signed origin. + /// + /// Returned value is a `u32`-encoded boolean: (`0 = false`, `1 = true`). + #[unstable] + fn caller_is_root(ctx: _, _memory: _) -> Result { + ctx.charge_gas(RuntimeCosts::CallerIsRoot)?; + Ok(ctx.ext.caller_is_root() as u32) + } + /// Stores the address of the current contract into the supplied buffer. /// /// The value is stored to linear memory at the address pointed to by `out_ptr`. @@ -1784,49 +1901,99 @@ pub mod env { /// Stores the price for the specified amount of gas into the supplied buffer. /// - /// The value is stored to linear memory at the address pointed to by `out_ptr`. - /// `out_len_ptr` must point to a u32 value that describes the available space at - /// `out_ptr`. This call overwrites it with the size of the value. If the available - /// space at `out_ptr` is less than the size of the value a trap is triggered. + /// Equivalent to the newer [`seal1`][`super::api_doc::Version2::weight_to_fee`] version but + /// works with *ref_time* Weight only. It is recommended to switch to the latest version, once + /// it's stabilized. + #[prefixed_alias] + fn weight_to_fee( + ctx: _, + memory: _, + gas: u64, + out_ptr: u32, + out_len_ptr: u32, + ) -> Result<(), TrapReason> { + let gas = Weight::from_parts(gas, 0); + ctx.charge_gas(RuntimeCosts::WeightToFee)?; + Ok(ctx.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + &ctx.ext.get_weight_price(gas).encode(), + false, + already_charged, + )?) + } + + /// Stores the price for the specified amount of weight into the supplied buffer. + /// + /// # Parameters + /// + /// - `out_ptr`: pointer to the linear memory where the returning value is written to. If the + /// available space at `out_ptr` is less than the size of the value a trap is triggered. + /// - `out_len_ptr`: in-out pointer into linear memory where the buffer length is read from and + /// the value length is written to. /// /// The data is encoded as `T::Balance`. /// /// # Note /// - /// It is recommended to avoid specifying very small values for `gas` as the prices for a single - /// gas can be smaller than one. - #[prefixed_alias] + /// It is recommended to avoid specifying very small values for `ref_time_limit` and + /// `proof_size_limit` as the prices for a single gas can be smaller than the basic balance + /// unit. + #[version(1)] + #[unstable] fn weight_to_fee( ctx: _, memory: _, - gas: u64, + ref_time_limit: u64, + proof_size_limit: u64, out_ptr: u32, out_len_ptr: u32, ) -> Result<(), TrapReason> { - let gas = Weight::from_ref_time(gas); + let weight = Weight::from_parts(ref_time_limit, proof_size_limit); ctx.charge_gas(RuntimeCosts::WeightToFee)?; Ok(ctx.write_sandbox_output( memory, out_ptr, out_len_ptr, - &ctx.ext.get_weight_price(gas).encode(), + &ctx.ext.get_weight_price(weight).encode(), false, already_charged, )?) } - /// Stores the amount of gas left into the supplied buffer. + /// Stores the weight left into the supplied buffer. + /// + /// Equivalent to the newer [`seal1`][`super::api_doc::Version2::gas_left`] version but + /// works with *ref_time* Weight only. It is recommended to switch to the latest version, once + /// it's stabilized. + #[prefixed_alias] + fn gas_left(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { + ctx.charge_gas(RuntimeCosts::GasLeft)?; + let gas_left = &ctx.ext.gas_meter().gas_left().ref_time().encode(); + Ok(ctx.write_sandbox_output( + memory, + out_ptr, + out_len_ptr, + gas_left, + false, + already_charged, + )?) + } + + /// Stores the amount of weight left into the supplied buffer. /// /// The value is stored to linear memory at the address pointed to by `out_ptr`. /// `out_len_ptr` must point to a u32 value that describes the available space at /// `out_ptr`. This call overwrites it with the size of the value. If the available /// space at `out_ptr` is less than the size of the value a trap is triggered. /// - /// The data is encoded as Gas. - #[prefixed_alias] + /// The data is encoded as Weight. + #[version(1)] + #[unstable] fn gas_left(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { ctx.charge_gas(RuntimeCosts::GasLeft)?; - let gas_left = &ctx.ext.gas_meter().gas_left().ref_time().encode(); + let gas_left = &ctx.ext.gas_meter().gas_left().encode(); Ok(ctx.write_sandbox_output( memory, out_ptr, @@ -1924,7 +2091,7 @@ pub mod env { /// `out_ptr`. This call overwrites it with the size of the value. If the available /// space at `out_ptr` is less than the size of the value a trap is triggered. /// - /// The data is encoded as (T::Hash, T::BlockNumber). + /// The data is encoded as (T::Hash, frame_system::pallet_prelude::BlockNumberFor::). /// /// # Changes from v0 /// @@ -2039,7 +2206,7 @@ pub mod env { /// # Note /// /// The state rent functionality was removed. This is stub only exists for - /// backwards compatiblity + /// backwards compatibility #[prefixed_alias] #[deprecated] fn restore_to( @@ -2054,7 +2221,7 @@ pub mod env { _delta_ptr: u32, _delta_count: u32, ) -> Result<(), TrapReason> { - ctx.charge_gas(RuntimeCosts::DebugMessage)?; + ctx.charge_gas(RuntimeCosts::DebugMessage(0))?; Ok(()) } @@ -2063,7 +2230,7 @@ pub mod env { /// # Note /// /// The state rent functionality was removed. This is stub only exists for - /// backwards compatiblity + /// backwards compatibility #[version(1)] #[prefixed_alias] #[deprecated] @@ -2076,7 +2243,7 @@ pub mod env { _delta_ptr: u32, _delta_count: u32, ) -> Result<(), TrapReason> { - ctx.charge_gas(RuntimeCosts::DebugMessage)?; + ctx.charge_gas(RuntimeCosts::DebugMessage(0))?; Ok(()) } @@ -2085,7 +2252,7 @@ pub mod env { /// # Note /// /// The state rent functionality was removed. This is stub only exists for - /// backwards compatiblity. + /// backwards compatibility. #[prefixed_alias] #[deprecated] fn set_rent_allowance( @@ -2094,7 +2261,7 @@ pub mod env { _value_ptr: u32, _value_len: u32, ) -> Result<(), TrapReason> { - ctx.charge_gas(RuntimeCosts::DebugMessage)?; + ctx.charge_gas(RuntimeCosts::DebugMessage(0))?; Ok(()) } @@ -2103,12 +2270,12 @@ pub mod env { /// # Note /// /// The state rent functionality was removed. This is stub only exists for - /// backwards compatiblity. + /// backwards compatibility. #[version(1)] #[prefixed_alias] #[deprecated] fn set_rent_allowance(ctx: _, _memory: _, _value_ptr: u32) -> Result<(), TrapReason> { - ctx.charge_gas(RuntimeCosts::DebugMessage)?; + ctx.charge_gas(RuntimeCosts::DebugMessage(0))?; Ok(()) } @@ -2117,7 +2284,7 @@ pub mod env { /// # Note /// /// The state rent functionality was removed. This is stub only exists for - /// backwards compatiblity. + /// backwards compatibility. #[prefixed_alias] #[deprecated] fn rent_allowance(ctx: _, memory: _, out_ptr: u32, out_len_ptr: u32) -> Result<(), TrapReason> { @@ -2362,7 +2529,7 @@ pub mod env { /// Emit a custom debug message. /// /// No newlines are added to the supplied message. - /// Specifying invalid UTF-8 triggers a trap. + /// Specifying invalid UTF-8 just drops the message with no trap. /// /// This is a no-op if debug message recording is disabled which is always the case /// when the code is executing on-chain. The message is interpreted as UTF-8 and @@ -2383,15 +2550,15 @@ pub mod env { str_ptr: u32, str_len: u32, ) -> Result { - ctx.charge_gas(RuntimeCosts::DebugMessage)?; + let str_len = str_len.min(DebugBufferVec::::bound() as u32); + ctx.charge_gas(RuntimeCosts::DebugMessage(str_len))?; if ctx.ext.append_debug_buffer("") { let data = ctx.read_sandbox_memory(memory, str_ptr, str_len)?; - let msg = - core::str::from_utf8(&data).map_err(|_| >::DebugMessageInvalidUTF8)?; - ctx.ext.append_debug_buffer(msg); - return Ok(ReturnCode::Success) + if let Some(msg) = core::str::from_utf8(&data).ok() { + ctx.ext.append_debug_buffer(msg); + } } - Ok(ReturnCode::LoggingDisabled) + Ok(ReturnCode::Success) } /// Call some dispatchable of the runtime. @@ -2405,29 +2572,27 @@ pub mod env { /// /// # Parameters /// - /// - `input_ptr`: the pointer into the linear memory where the input data is placed. - /// - `input_len`: the length of the input data in bytes. + /// - `call_ptr`: the pointer into the linear memory where the input data is placed. + /// - `call_len`: the length of the input data in bytes. /// /// # Return Value /// - /// Returns `ReturnCode::Success` when the dispatchable was succesfully executed and + /// Returns `ReturnCode::Success` when the dispatchable was successfully executed and /// returned `Ok`. When the dispatchable was exeuted but returned an error - /// `ReturnCode::CallRuntimeReturnedError` is returned. The full error is not + /// `ReturnCode::CallRuntimeFailed` is returned. The full error is not /// provided because it is not guaranteed to be stable. /// /// # Comparison with `ChainExtension` /// /// Just as a chain extension this API allows the runtime to extend the functionality - /// of contracts. While making use of this function is generelly easier it cannot be - /// used in call cases. Consider writing a chain extension if you need to do perform + /// of contracts. While making use of this function is generally easier it cannot be + /// used in all cases. Consider writing a chain extension if you need to do perform /// one of the following tasks: /// /// - Return data. /// - Provide functionality **exclusively** to contracts. /// - Provide custom weights. /// - Avoid the need to keep the `Call` data structure stable. - #[unstable] - #[prefixed_alias] fn call_runtime( ctx: _, memory: _, @@ -2445,7 +2610,13 @@ pub mod env { ctx.adjust_gas(charged, RuntimeCosts::CallRuntime(actual_weight)); match result { Ok(_) => Ok(ReturnCode::Success), - Err(_) => Ok(ReturnCode::CallRuntimeReturnedError), + Err(e) => { + if ctx.ext.append_debug_buffer("") { + ctx.ext.append_debug_buffer("seal0::call_runtime failed with: "); + ctx.ext.append_debug_buffer(e.into()); + }; + Ok(ReturnCode::CallRuntimeFailed) + }, } } @@ -2495,6 +2666,46 @@ pub mod env { } } + /// Verify a sr25519 signature + /// + /// # Parameters + /// + /// - `signature_ptr`: the pointer into the linear memory where the signature is placed. Should + /// be a value of 64 bytes. + /// - `pub_key_ptr`: the pointer into the linear memory where the public key is placed. Should + /// be a value of 32 bytes. + /// - `message_len`: the length of the message payload. + /// - `message_ptr`: the pointer into the linear memory where the message is placed. + /// + /// # Errors + /// + /// - `ReturnCode::Sr25519VerifyFailed + #[unstable] + fn sr25519_verify( + ctx: _, + memory: _, + signature_ptr: u32, + pub_key_ptr: u32, + message_len: u32, + message_ptr: u32, + ) -> Result { + ctx.charge_gas(RuntimeCosts::Sr25519Verify(message_len))?; + + let mut signature: [u8; 64] = [0; 64]; + ctx.read_sandbox_memory_into_buf(memory, signature_ptr, &mut signature)?; + + let mut pub_key: [u8; 32] = [0; 32]; + ctx.read_sandbox_memory_into_buf(memory, pub_key_ptr, &mut pub_key)?; + + let message: Vec = ctx.read_sandbox_memory(memory, message_ptr, message_len)?; + + if ctx.ext.sr25519_verify(&signature, &message, &pub_key) { + Ok(ReturnCode::Success) + } else { + Ok(ReturnCode::Sr25519VerifyFailed) + } + } + /// Replace the contract code at the specified address with new code. /// /// # Note @@ -2604,9 +2815,8 @@ pub mod env { /// Returns a nonce that is unique per contract instantiation. /// - /// The nonce is incremented for each succesful contract instantiation. This is a + /// The nonce is incremented for each successful contract instantiation. This is a /// sensible default salt for contract instantiations. - #[unstable] fn instantiation_nonce(ctx: _, _memory: _) -> Result { ctx.charge_gas(RuntimeCosts::InstantationNonce)?; Ok(ctx.ext.nonce()) diff --git a/frame/contracts/src/weights.rs b/frame/contracts/src/weights.rs index 7ae1a9c59467..754a28fc2292 100644 --- a/frame/contracts/src/weights.rs +++ b/frame/contracts/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,12 +18,13 @@ //! Autogenerated weights for pallet_contracts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-01-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-07-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-xerhrdyb-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// /home/benchbot/cargo_target_dir/production/substrate +// target/production/substrate // benchmark // pallet // --steps=50 @@ -32,7 +33,7 @@ // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json +// --json-file=/builds/parity/mirrors/substrate/.git/.artifacts/bench.json // --pallet=pallet_contracts // --chain=dev // --header=./HEADER-APACHE2 @@ -42,16 +43,24 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_contracts. pub trait WeightInfo { fn on_process_deletion_queue_batch() -> Weight; fn on_initialize_per_trie_key(k: u32, ) -> Weight; - fn on_initialize_per_queue_item(q: u32, ) -> Weight; - fn reinstrument(c: u32, ) -> Weight; + fn v9_migration_step(c: u32, ) -> Weight; + fn v10_migration_step() -> Weight; + fn v11_migration_step(k: u32, ) -> Weight; + fn v12_migration_step(c: u32, ) -> Weight; + fn migration_noop() -> Weight; + fn migrate() -> Weight; + fn on_runtime_upgrade_noop() -> Weight; + fn on_runtime_upgrade_in_progress() -> Weight; + fn on_runtime_upgrade() -> Weight; fn call_with_code_per_byte(c: u32, ) -> Weight; fn instantiate_with_code(c: u32, i: u32, s: u32, ) -> Weight; fn instantiate(i: u32, s: u32, ) -> Weight; @@ -64,6 +73,7 @@ pub trait WeightInfo { fn seal_code_hash(r: u32, ) -> Weight; fn seal_own_code_hash(r: u32, ) -> Weight; fn seal_caller_is_origin(r: u32, ) -> Weight; + fn seal_caller_is_root(r: u32, ) -> Weight; fn seal_address(r: u32, ) -> Weight; fn seal_gas_left(r: u32, ) -> Weight; fn seal_balance(r: u32, ) -> Weight; @@ -72,41 +82,43 @@ pub trait WeightInfo { fn seal_block_number(r: u32, ) -> Weight; fn seal_now(r: u32, ) -> Weight; fn seal_weight_to_fee(r: u32, ) -> Weight; - fn seal_gas(r: u32, ) -> Weight; fn seal_input(r: u32, ) -> Weight; - fn seal_input_per_kb(n: u32, ) -> Weight; + fn seal_input_per_byte(n: u32, ) -> Weight; fn seal_return(r: u32, ) -> Weight; - fn seal_return_per_kb(n: u32, ) -> Weight; + fn seal_return_per_byte(n: u32, ) -> Weight; fn seal_terminate(r: u32, ) -> Weight; fn seal_random(r: u32, ) -> Weight; fn seal_deposit_event(r: u32, ) -> Weight; - fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight; + fn seal_deposit_event_per_topic_and_byte(t: u32, n: u32, ) -> Weight; fn seal_debug_message(r: u32, ) -> Weight; + fn seal_debug_message_per_byte(i: u32, ) -> Weight; fn seal_set_storage(r: u32, ) -> Weight; - fn seal_set_storage_per_new_kb(n: u32, ) -> Weight; - fn seal_set_storage_per_old_kb(n: u32, ) -> Weight; + fn seal_set_storage_per_new_byte(n: u32, ) -> Weight; + fn seal_set_storage_per_old_byte(n: u32, ) -> Weight; fn seal_clear_storage(r: u32, ) -> Weight; - fn seal_clear_storage_per_kb(n: u32, ) -> Weight; + fn seal_clear_storage_per_byte(n: u32, ) -> Weight; fn seal_get_storage(r: u32, ) -> Weight; - fn seal_get_storage_per_kb(n: u32, ) -> Weight; + fn seal_get_storage_per_byte(n: u32, ) -> Weight; fn seal_contains_storage(r: u32, ) -> Weight; - fn seal_contains_storage_per_kb(n: u32, ) -> Weight; + fn seal_contains_storage_per_byte(n: u32, ) -> Weight; fn seal_take_storage(r: u32, ) -> Weight; - fn seal_take_storage_per_kb(n: u32, ) -> Weight; + fn seal_take_storage_per_byte(n: u32, ) -> Weight; fn seal_transfer(r: u32, ) -> Weight; fn seal_call(r: u32, ) -> Weight; fn seal_delegate_call(r: u32, ) -> Weight; - fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight; + fn seal_call_per_transfer_clone_byte(t: u32, c: u32, ) -> Weight; fn seal_instantiate(r: u32, ) -> Weight; - fn seal_instantiate_per_transfer_input_salt_kb(t: u32, i: u32, s: u32, ) -> Weight; + fn seal_instantiate_per_transfer_input_salt_byte(t: u32, i: u32, s: u32, ) -> Weight; fn seal_hash_sha2_256(r: u32, ) -> Weight; - fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight; + fn seal_hash_sha2_256_per_byte(n: u32, ) -> Weight; fn seal_hash_keccak_256(r: u32, ) -> Weight; - fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight; + fn seal_hash_keccak_256_per_byte(n: u32, ) -> Weight; fn seal_hash_blake2_256(r: u32, ) -> Weight; - fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight; + fn seal_hash_blake2_256_per_byte(n: u32, ) -> Weight; fn seal_hash_blake2_128(r: u32, ) -> Weight; - fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight; + fn seal_hash_blake2_128_per_byte(n: u32, ) -> Weight; + fn seal_sr25519_verify_per_byte(n: u32, ) -> Weight; + fn seal_sr25519_verify(r: u32, ) -> Weight; fn seal_ecdsa_recover(r: u32, ) -> Weight; fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight; fn seal_set_code_hash(r: u32, ) -> Weight; @@ -114,2610 +126,3571 @@ pub trait WeightInfo { fn seal_account_reentrance_count(r: u32, ) -> Weight; fn seal_instantiation_nonce(r: u32, ) -> Weight; fn instr_i64const(r: u32, ) -> Weight; - fn instr_i64load(r: u32, ) -> Weight; - fn instr_i64store(r: u32, ) -> Weight; - fn instr_select(r: u32, ) -> Weight; - fn instr_if(r: u32, ) -> Weight; - fn instr_br(r: u32, ) -> Weight; - fn instr_br_if(r: u32, ) -> Weight; - fn instr_br_table(r: u32, ) -> Weight; - fn instr_br_table_per_entry(e: u32, ) -> Weight; - fn instr_call(r: u32, ) -> Weight; - fn instr_call_indirect(r: u32, ) -> Weight; - fn instr_call_indirect_per_param(p: u32, ) -> Weight; - fn instr_call_per_local(l: u32, ) -> Weight; - fn instr_local_get(r: u32, ) -> Weight; - fn instr_local_set(r: u32, ) -> Weight; - fn instr_local_tee(r: u32, ) -> Weight; - fn instr_global_get(r: u32, ) -> Weight; - fn instr_global_set(r: u32, ) -> Weight; - fn instr_memory_current(r: u32, ) -> Weight; - fn instr_memory_grow(r: u32, ) -> Weight; - fn instr_i64clz(r: u32, ) -> Weight; - fn instr_i64ctz(r: u32, ) -> Weight; - fn instr_i64popcnt(r: u32, ) -> Weight; - fn instr_i64eqz(r: u32, ) -> Weight; - fn instr_i64extendsi32(r: u32, ) -> Weight; - fn instr_i64extendui32(r: u32, ) -> Weight; - fn instr_i32wrapi64(r: u32, ) -> Weight; - fn instr_i64eq(r: u32, ) -> Weight; - fn instr_i64ne(r: u32, ) -> Weight; - fn instr_i64lts(r: u32, ) -> Weight; - fn instr_i64ltu(r: u32, ) -> Weight; - fn instr_i64gts(r: u32, ) -> Weight; - fn instr_i64gtu(r: u32, ) -> Weight; - fn instr_i64les(r: u32, ) -> Weight; - fn instr_i64leu(r: u32, ) -> Weight; - fn instr_i64ges(r: u32, ) -> Weight; - fn instr_i64geu(r: u32, ) -> Weight; - fn instr_i64add(r: u32, ) -> Weight; - fn instr_i64sub(r: u32, ) -> Weight; - fn instr_i64mul(r: u32, ) -> Weight; - fn instr_i64divs(r: u32, ) -> Weight; - fn instr_i64divu(r: u32, ) -> Weight; - fn instr_i64rems(r: u32, ) -> Weight; - fn instr_i64remu(r: u32, ) -> Weight; - fn instr_i64and(r: u32, ) -> Weight; - fn instr_i64or(r: u32, ) -> Weight; - fn instr_i64xor(r: u32, ) -> Weight; - fn instr_i64shl(r: u32, ) -> Weight; - fn instr_i64shrs(r: u32, ) -> Weight; - fn instr_i64shru(r: u32, ) -> Weight; - fn instr_i64rotl(r: u32, ) -> Weight; - fn instr_i64rotr(r: u32, ) -> Weight; } /// Weights for pallet_contracts using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Contracts DeletionQueue (r:1 w:0) + /// Storage: Contracts DeletionQueueCounter (r:1 w:0) + /// Proof: Contracts DeletionQueueCounter (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) fn on_process_deletion_queue_batch() -> Weight { - // Minimum execution time: 3_326 nanoseconds. - Weight::from_ref_time(3_433_000) - .saturating_add(T::DbWeight::get().reads(1)) - } - // Storage: Skipped Metadata (r:0 w:0) + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `1627` + // Minimum execution time: 2_519_000 picoseconds. + Weight::from_parts(2_660_000, 1627) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) /// The range of component `k` is `[0, 1024]`. fn on_initialize_per_trie_key(k: u32, ) -> Weight { - // Minimum execution time: 15_703 nanoseconds. - Weight::from_ref_time(15_288_927) - // Standard Error: 951 - .saturating_add(Weight::from_ref_time(940_816).saturating_mul(k.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `451 + k * (69 ±0)` + // Estimated: `441 + k * (70 ±0)` + // Minimum execution time: 13_096_000 picoseconds. + Weight::from_parts(13_395_000, 441) + // Standard Error: 1_046 + .saturating_add(Weight::from_parts(1_246_238, 0).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) + .saturating_add(T::DbWeight::get().writes(2_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) - } - // Storage: Contracts DeletionQueue (r:1 w:0) - /// The range of component `q` is `[0, 128]`. - fn on_initialize_per_queue_item(q: u32, ) -> Weight { - // Minimum execution time: 3_548 nanoseconds. - Weight::from_ref_time(15_764_121) - // Standard Error: 3_339 - .saturating_add(Weight::from_ref_time(1_214_890).saturating_mul(q.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Contracts PristineCode (r:1 w:0) - // Storage: Contracts CodeStorage (r:0 w:1) - /// The range of component `c` is `[0, 64226]`. - fn reinstrument(c: u32, ) -> Weight { - // Minimum execution time: 29_801 nanoseconds. - Weight::from_ref_time(27_932_706) - // Standard Error: 49 - .saturating_add(Weight::from_ref_time(50_532).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `c` is `[0, 131072]`. + .saturating_add(Weight::from_parts(0, 70).saturating_mul(k.into())) + } + /// Storage: unknown `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:2 w:1) + /// Proof Skipped: unknown `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:2 w:1) + /// The range of component `c` is `[0, 125952]`. + fn v9_migration_step(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `211 + c * (1 ±0)` + // Estimated: `6149 + c * (1 ±0)` + // Minimum execution time: 8_409_000 picoseconds. + Weight::from_parts(9_006_438, 6149) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_345, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: Contracts ContractInfoOf (r:2 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + fn v10_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `510` + // Estimated: `6450` + // Minimum execution time: 16_962_000 picoseconds. + Weight::from_parts(17_716_000, 6450) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Contracts DeletionQueue (r:1 w:1025) + /// Proof: Contracts DeletionQueue (max_values: None, max_size: Some(142), added: 2617, mode: Measured) + /// Storage: Contracts DeletionQueueCounter (r:0 w:1) + /// Proof: Contracts DeletionQueueCounter (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// The range of component `k` is `[0, 1024]`. + fn v11_migration_step(k: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `171 + k * (1 ±0)` + // Estimated: `3635 + k * (1 ±0)` + // Minimum execution time: 3_763_000 picoseconds. + Weight::from_parts(2_401_625, 3635) + // Standard Error: 2_827 + .saturating_add(Weight::from_parts(1_201_671, 0).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(k.into())) + } + /// Storage: unknown `0x4342193e496fab7ec59d615ed0dc553053f13fd319a03c211337c76e0fe776df` (r:2 w:0) + /// Proof Skipped: unknown `0x4342193e496fab7ec59d615ed0dc553053f13fd319a03c211337c76e0fe776df` (r:2 w:0) + /// Storage: unknown `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:1 w:1) + /// Proof Skipped: unknown `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:1 w:1) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:0 w:1) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// The range of component `c` is `[0, 125952]`. + fn v12_migration_step(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `325 + c * (1 ±0)` + // Estimated: `6263 + c * (1 ±0)` + // Minimum execution time: 17_490_000 picoseconds. + Weight::from_parts(17_712_278, 6263) + // Standard Error: 0 + .saturating_add(Weight::from_parts(427, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:1) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + fn migration_noop() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `1627` + // Minimum execution time: 3_282_000 picoseconds. + Weight::from_parts(3_536_000, 1627) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:1) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: unknown `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:1) + /// Proof Skipped: unknown `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:1) + fn migrate() -> Weight { + // Proof Size summary in bytes: + // Measured: `166` + // Estimated: `3631` + // Minimum execution time: 12_973_000 picoseconds. + Weight::from_parts(13_366_000, 3631) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: unknown `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof Skipped: unknown `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + fn on_runtime_upgrade_noop() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 4_764_000 picoseconds. + Weight::from_parts(5_000_000, 3607) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: unknown `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof Skipped: unknown `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + fn on_runtime_upgrade_in_progress() -> Weight { + // Proof Size summary in bytes: + // Measured: `167` + // Estimated: `3632` + // Minimum execution time: 6_616_000 picoseconds. + Weight::from_parts(6_935_000, 3632) + .saturating_add(T::DbWeight::get().reads(2_u64)) + } + /// Storage: unknown `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof Skipped: unknown `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Storage: Contracts MigrationInProgress (r:1 w:1) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + fn on_runtime_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 6_953_000 picoseconds. + Weight::from_parts(7_440_000, 3607) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `c` is `[0, 125952]`. fn call_with_code_per_byte(c: u32, ) -> Weight { - // Minimum execution time: 307_894 nanoseconds. - Weight::from_ref_time(322_346_319) - // Standard Error: 25 - .saturating_add(Weight::from_ref_time(30_678).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Contracts CodeStorage (r:1 w:1) - // Storage: Contracts Nonce (r:1 w:1) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: System EventTopics (r:3 w:3) - // Storage: Contracts PristineCode (r:0 w:1) - // Storage: Contracts OwnerInfoOf (r:0 w:1) - /// The range of component `c` is `[0, 64226]`. + // Proof Size summary in bytes: + // Measured: `786` + // Estimated: `6735 + c * (1 ±0)` + // Minimum execution time: 302_714_000 picoseconds. + Weight::from_parts(271_320_595, 6735) + // Standard Error: 72 + .saturating_add(Weight::from_parts(38_474, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:1) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: System EventTopics (r:3 w:3) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// Storage: Contracts Nonce (r:1 w:1) + /// Proof: Contracts Nonce (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts PristineCode (r:0 w:1) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// The range of component `c` is `[0, 125952]`. /// The range of component `i` is `[0, 1048576]`. /// The range of component `s` is `[0, 1048576]`. fn instantiate_with_code(c: u32, i: u32, s: u32, ) -> Weight { - // Minimum execution time: 3_610_424 nanoseconds. - Weight::from_ref_time(583_426_386) - // Standard Error: 277 - .saturating_add(Weight::from_ref_time(90_224).saturating_mul(c.into())) - // Standard Error: 16 - .saturating_add(Weight::from_ref_time(1_325).saturating_mul(i.into())) - // Standard Error: 16 - .saturating_add(Weight::from_ref_time(1_727).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(9)) - } - // Storage: Contracts CodeStorage (r:1 w:1) - // Storage: Contracts Nonce (r:1 w:1) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Contracts OwnerInfoOf (r:1 w:1) - // Storage: System EventTopics (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `303` + // Estimated: `8745` + // Minimum execution time: 4_506_957_000 picoseconds. + Weight::from_parts(643_316_921, 8745) + // Standard Error: 278 + .saturating_add(Weight::from_parts(112_835, 0).saturating_mul(c.into())) + // Standard Error: 33 + .saturating_add(Weight::from_parts(1_830, 0).saturating_mul(i.into())) + // Standard Error: 33 + .saturating_add(Weight::from_parts(2_022, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(10_u64)) + .saturating_add(T::DbWeight::get().writes(9_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:1) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Contracts Nonce (r:1 w:1) + /// Proof: Contracts Nonce (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) /// The range of component `i` is `[0, 1048576]`. /// The range of component `s` is `[0, 1048576]`. fn instantiate(i: u32, s: u32, ) -> Weight { - // Minimum execution time: 1_892_996 nanoseconds. - Weight::from_ref_time(202_958_196) - // Standard Error: 8 - .saturating_add(Weight::from_ref_time(1_627).saturating_mul(i.into())) - // Standard Error: 8 - .saturating_add(Weight::from_ref_time(1_755).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(7)) - } - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: System EventTopics (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `523` + // Estimated: `6513` + // Minimum execution time: 2_103_482_000 picoseconds. + Weight::from_parts(316_666_183, 6513) + // Standard Error: 7 + .saturating_add(Weight::from_parts(1_933, 0).saturating_mul(i.into())) + // Standard Error: 7 + .saturating_add(Weight::from_parts(1_803, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(10_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) fn call() -> Weight { - // Minimum execution time: 157_347 nanoseconds. - Weight::from_ref_time(159_084_000) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Contracts CodeStorage (r:1 w:1) - // Storage: System EventTopics (r:1 w:1) - // Storage: Contracts PristineCode (r:0 w:1) - // Storage: Contracts OwnerInfoOf (r:0 w:1) - /// The range of component `c` is `[0, 64226]`. + // Proof Size summary in bytes: + // Measured: `820` + // Estimated: `6760` + // Minimum execution time: 207_530_000 picoseconds. + Weight::from_parts(217_243_000, 6760) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:1) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: System EventTopics (r:1 w:1) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// Storage: Contracts PristineCode (r:0 w:1) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// The range of component `c` is `[0, 125952]`. fn upload_code(c: u32, ) -> Weight { - // Minimum execution time: 299_987 nanoseconds. - Weight::from_ref_time(305_274_879) - // Standard Error: 72 - .saturating_add(Weight::from_ref_time(91_916).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Contracts OwnerInfoOf (r:1 w:1) - // Storage: System EventTopics (r:1 w:1) - // Storage: Contracts CodeStorage (r:0 w:1) - // Storage: Contracts PristineCode (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 246_381_000 picoseconds. + Weight::from_parts(242_933_576, 3607) + // Standard Error: 100 + .saturating_add(Weight::from_parts(74_645, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:1) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: System EventTopics (r:1 w:1) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// Storage: Contracts PristineCode (r:0 w:1) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) fn remove_code() -> Weight { - // Minimum execution time: 40_795 nanoseconds. - Weight::from_ref_time(41_297_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts OwnerInfoOf (r:2 w:2) - // Storage: System EventTopics (r:3 w:3) + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3720` + // Minimum execution time: 35_519_000 picoseconds. + Weight::from_parts(36_813_000, 3720) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:2 w:2) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: System EventTopics (r:3 w:3) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) fn set_code() -> Weight { - // Minimum execution time: 41_957 nanoseconds. - Weight::from_ref_time(42_536_000) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(6)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `575` + // Estimated: `8990` + // Minimum execution time: 37_769_000 picoseconds. + Weight::from_parts(39_349_000, 8990) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_caller(r: u32, ) -> Weight { - // Minimum execution time: 293_544 nanoseconds. - Weight::from_ref_time(298_981_222) - // Standard Error: 39_139 - .saturating_add(Weight::from_ref_time(17_278_436).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `860 + r * (6 ±0)` + // Estimated: `6801 + r * (6 ±0)` + // Minimum execution time: 273_355_000 picoseconds. + Weight::from_parts(280_115_308, 6801) + // Standard Error: 662 + .saturating_add(Weight::from_parts(351_066, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1601 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_is_contract(r: u32, ) -> Weight { - // Minimum execution time: 293_500 nanoseconds. - Weight::from_ref_time(237_825_284) - // Standard Error: 450_234 - .saturating_add(Weight::from_ref_time(198_995_806).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `918 + r * (240 ±0)` + // Estimated: `6822 + r * (2715 ±0)` + // Minimum execution time: 264_066_000 picoseconds. + Weight::from_parts(103_474_597, 6822) + // Standard Error: 7_010 + .saturating_add(Weight::from_parts(3_917_988, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 2715).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1601 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_code_hash(r: u32, ) -> Weight { - // Minimum execution time: 295_851 nanoseconds. - Weight::from_ref_time(258_113_447) - // Standard Error: 408_747 - .saturating_add(Weight::from_ref_time(237_490_216).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `910 + r * (244 ±0)` + // Estimated: `6826 + r * (2719 ±0)` + // Minimum execution time: 275_726_000 picoseconds. + Weight::from_parts(111_512_451, 6826) + // Standard Error: 6_673 + .saturating_add(Weight::from_parts(4_626_511, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 2719).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_own_code_hash(r: u32, ) -> Weight { - // Minimum execution time: 294_614 nanoseconds. - Weight::from_ref_time(301_528_998) - // Standard Error: 40_613 - .saturating_add(Weight::from_ref_time(20_815_088).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `867 + r * (6 ±0)` + // Estimated: `6809 + r * (6 ±0)` + // Minimum execution time: 274_377_000 picoseconds. + Weight::from_parts(286_299_699, 6809) + // Standard Error: 521 + .saturating_add(Weight::from_parts(419_417, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_caller_is_origin(r: u32, ) -> Weight { - // Minimum execution time: 293_088 nanoseconds. - Weight::from_ref_time(299_120_595) - // Standard Error: 23_277 - .saturating_add(Weight::from_ref_time(11_130_334).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `857 + r * (3 ±0)` + // Estimated: `6802 + r * (3 ±0)` + // Minimum execution time: 265_297_000 picoseconds. + Weight::from_parts(283_474_927, 6802) + // Standard Error: 376 + .saturating_add(Weight::from_parts(186_214, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 3).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. + fn seal_caller_is_root(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `747 + r * (3 ±0)` + // Estimated: `6687 + r * (3 ±0)` + // Minimum execution time: 258_385_000 picoseconds. + Weight::from_parts(269_869_790, 6687) + // Standard Error: 334 + .saturating_add(Weight::from_parts(164_806, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 3).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_address(r: u32, ) -> Weight { - // Minimum execution time: 295_110 nanoseconds. - Weight::from_ref_time(305_295_787) - // Standard Error: 48_521 - .saturating_add(Weight::from_ref_time(16_610_806).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `861 + r * (6 ±0)` + // Estimated: `6803 + r * (6 ±0)` + // Minimum execution time: 271_351_000 picoseconds. + Weight::from_parts(286_390_305, 6803) + // Standard Error: 628 + .saturating_add(Weight::from_parts(339_374, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_gas_left(r: u32, ) -> Weight { - // Minimum execution time: 293_414 nanoseconds. - Weight::from_ref_time(299_960_283) - // Standard Error: 32_003 - .saturating_add(Weight::from_ref_time(16_652_433).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `857 + r * (6 ±0)` + // Estimated: `6798 + r * (6 ±0)` + // Minimum execution time: 273_060_000 picoseconds. + Weight::from_parts(285_959_049, 6798) + // Standard Error: 813 + .saturating_add(Weight::from_parts(544_941, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:2 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_balance(r: u32, ) -> Weight { - // Minimum execution time: 292_429 nanoseconds. - Weight::from_ref_time(301_025_128) - // Standard Error: 92_155 - .saturating_add(Weight::from_ref_time(92_731_719).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `1001 + r * (6 ±0)` + // Estimated: `6925 + r * (6 ±0)` + // Minimum execution time: 273_717_000 picoseconds. + Weight::from_parts(301_053_119, 6925) + // Standard Error: 3_314 + .saturating_add(Weight::from_parts(1_645_480, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_value_transferred(r: u32, ) -> Weight { - // Minimum execution time: 293_516 nanoseconds. - Weight::from_ref_time(300_071_428) - // Standard Error: 25_871 - .saturating_add(Weight::from_ref_time(16_599_369).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `871 + r * (6 ±0)` + // Estimated: `6820 + r * (6 ±0)` + // Minimum execution time: 273_480_000 picoseconds. + Weight::from_parts(284_751_212, 6820) + // Standard Error: 501 + .saturating_add(Weight::from_parts(334_063, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_minimum_balance(r: u32, ) -> Weight { - // Minimum execution time: 293_666 nanoseconds. - Weight::from_ref_time(299_803_599) - // Standard Error: 24_508 - .saturating_add(Weight::from_ref_time(16_500_973).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `869 + r * (6 ±0)` + // Estimated: `6818 + r * (6 ±0)` + // Minimum execution time: 278_938_000 picoseconds. + Weight::from_parts(284_829_302, 6818) + // Standard Error: 488 + .saturating_add(Weight::from_parts(338_782, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_block_number(r: u32, ) -> Weight { - // Minimum execution time: 289_193 nanoseconds. - Weight::from_ref_time(297_946_784) - // Standard Error: 43_719 - .saturating_add(Weight::from_ref_time(16_442_254).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `866 + r * (6 ±0)` + // Estimated: `6816 + r * (6 ±0)` + // Minimum execution time: 276_799_000 picoseconds. + Weight::from_parts(290_353_700, 6816) + // Standard Error: 675 + .saturating_add(Weight::from_parts(323_565, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_now(r: u32, ) -> Weight { - // Minimum execution time: 288_897 nanoseconds. - Weight::from_ref_time(298_856_658) - // Standard Error: 31_003 - .saturating_add(Weight::from_ref_time(16_490_508).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `857 + r * (6 ±0)` + // Estimated: `6802 + r * (6 ±0)` + // Minimum execution time: 267_740_000 picoseconds. + Weight::from_parts(287_560_339, 6802) + // Standard Error: 479 + .saturating_add(Weight::from_parts(329_276, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + /// Proof: TransactionPayment NextFeeMultiplier (max_values: Some(1), max_size: Some(16), added: 511, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_weight_to_fee(r: u32, ) -> Weight { - // Minimum execution time: 289_850 nanoseconds. - Weight::from_ref_time(302_898_449) - // Standard Error: 92_822 - .saturating_add(Weight::from_ref_time(86_376_874).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. - fn seal_gas(r: u32, ) -> Weight { - // Minimum execution time: 145_105 nanoseconds. - Weight::from_ref_time(152_388_934) - // Standard Error: 29_988 - .saturating_add(Weight::from_ref_time(7_761_947).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `931 + r * (14 ±0)` + // Estimated: `6864 + r * (14 ±0)` + // Minimum execution time: 275_471_000 picoseconds. + Weight::from_parts(297_332_107, 6864) + // Standard Error: 2_230 + .saturating_add(Weight::from_parts(1_484_476, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 14).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_input(r: u32, ) -> Weight { - // Minimum execution time: 289_319 nanoseconds. - Weight::from_ref_time(298_390_911) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(14_225_527).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `n` is `[0, 1024]`. - fn seal_input_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 305_538 nanoseconds. - Weight::from_ref_time(339_734_596) - // Standard Error: 4_636 - .saturating_add(Weight::from_ref_time(9_592_149).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `859 + r * (6 ±0)` + // Estimated: `6803 + r * (6 ±0)` + // Minimum execution time: 255_279_000 picoseconds. + Weight::from_parts(282_649_020, 6803) + // Standard Error: 429 + .saturating_add(Weight::from_parts(290_527, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 1048576]`. + fn seal_input_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `863` + // Estimated: `6803` + // Minimum execution time: 268_029_000 picoseconds. + Weight::from_parts(231_474_232, 6803) + // Standard Error: 23 + .saturating_add(Weight::from_parts(1_050, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) /// The range of component `r` is `[0, 1]`. fn seal_return(r: u32, ) -> Weight { - // Minimum execution time: 287_189 nanoseconds. - Weight::from_ref_time(295_472_383) - // Standard Error: 314_142 - .saturating_add(Weight::from_ref_time(214_016).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `n` is `[0, 1024]`. - fn seal_return_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 289_380 nanoseconds. - Weight::from_ref_time(296_302_404) - // Standard Error: 889 - .saturating_add(Weight::from_ref_time(190_017).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - // Storage: Contracts DeletionQueue (r:1 w:1) - // Storage: Contracts OwnerInfoOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `847 + r * (45 ±0)` + // Estimated: `6787 + r * (45 ±0)` + // Minimum execution time: 252_126_000 picoseconds. + Weight::from_parts(277_677_710, 6787) + // Standard Error: 770_704 + .saturating_add(Weight::from_parts(2_678_989, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 45).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 1048576]`. + fn seal_return_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `857` + // Estimated: `6810` + // Minimum execution time: 271_967_000 picoseconds. + Weight::from_parts(282_988_484, 6810) + // Standard Error: 0 + .saturating_add(Weight::from_parts(387, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:4 w:4) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:1) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: Contracts DeletionQueueCounter (r:1 w:1) + /// Proof: Contracts DeletionQueueCounter (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:3 w:3) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// Storage: Contracts DeletionQueue (r:0 w:1) + /// Proof: Contracts DeletionQueue (max_values: None, max_size: Some(142), added: 2617, mode: Measured) /// The range of component `r` is `[0, 1]`. fn seal_terminate(r: u32, ) -> Weight { - // Minimum execution time: 292_383 nanoseconds. - Weight::from_ref_time(297_557_691) - // Standard Error: 251_228 - .saturating_add(Weight::from_ref_time(56_688_008).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) + // Proof Size summary in bytes: + // Measured: `889 + r * (300 ±0)` + // Estimated: `6829 + r * (7725 ±0)` + // Minimum execution time: 257_246_000 picoseconds. + Weight::from_parts(280_196_561, 6829) + // Standard Error: 815_845 + .saturating_add(Weight::from_parts(127_831_338, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3)) - .saturating_add(T::DbWeight::get().writes((6_u64).saturating_mul(r.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) - /// The range of component `r` is `[0, 20]`. + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes((8_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 7725).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) + /// Proof: RandomnessCollectiveFlip RandomMaterial (max_values: Some(1), max_size: Some(2594), added: 3089, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_random(r: u32, ) -> Weight { - // Minimum execution time: 293_019 nanoseconds. - Weight::from_ref_time(301_747_439) - // Standard Error: 92_043 - .saturating_add(Weight::from_ref_time(109_861_144).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `938 + r * (10 ±0)` + // Estimated: `6879 + r * (10 ±0)` + // Minimum execution time: 270_074_000 picoseconds. + Weight::from_parts(292_298_331, 6879) + // Standard Error: 2_123 + .saturating_add(Weight::from_parts(2_089_487, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 10).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_deposit_event(r: u32, ) -> Weight { - // Minimum execution time: 287_586 nanoseconds. - Weight::from_ref_time(304_751_302) - // Standard Error: 127_698 - .saturating_add(Weight::from_ref_time(221_525_053).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `857 + r * (10 ±0)` + // Estimated: `6802 + r * (10 ±0)` + // Minimum execution time: 267_080_000 picoseconds. + Weight::from_parts(298_470_496, 6802) + // Standard Error: 3_004 + .saturating_add(Weight::from_parts(3_898_460, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 10).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:6 w:6) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) /// The range of component `t` is `[0, 4]`. - /// The range of component `n` is `[0, 16]`. - fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { - // Minimum execution time: 1_182_204 nanoseconds. - Weight::from_ref_time(508_607_957) - // Standard Error: 503_533 - .saturating_add(Weight::from_ref_time(173_998_655).saturating_mul(t.into())) - // Standard Error: 138_294 - .saturating_add(Weight::from_ref_time(67_991_373).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(t.into()))) - .saturating_add(T::DbWeight::get().writes(3)) - .saturating_add(T::DbWeight::get().writes((80_u64).saturating_mul(t.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + /// The range of component `n` is `[0, 16384]`. + fn seal_deposit_event_per_topic_and_byte(t: u32, n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `876 + t * (32 ±0)` + // Estimated: `6823 + t * (2508 ±0)` + // Minimum execution time: 277_152_000 picoseconds. + Weight::from_parts(290_745_178, 6823) + // Standard Error: 88_577 + .saturating_add(Weight::from_parts(2_476_405, 0).saturating_mul(t.into())) + // Standard Error: 24 + .saturating_add(Weight::from_parts(702, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(t.into()))) + .saturating_add(Weight::from_parts(0, 2508).saturating_mul(t.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_debug_message(r: u32, ) -> Weight { - // Minimum execution time: 157_706 nanoseconds. - Weight::from_ref_time(161_895_583) - // Standard Error: 14_952 - .saturating_add(Weight::from_ref_time(12_990_237).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `r` is `[0, 10]`. + // Proof Size summary in bytes: + // Measured: `856 + r * (7 ±0)` + // Estimated: `6800 + r * (7 ±0)` + // Minimum execution time: 168_782_000 picoseconds. + Weight::from_parts(179_694_331, 6800) + // Standard Error: 338 + .saturating_add(Weight::from_parts(246_541, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 7).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: MaxEncodedLen) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: MaxEncodedLen) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `i` is `[0, 1048576]`. + fn seal_debug_message_per_byte(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `125807` + // Estimated: `131749` + // Minimum execution time: 428_673_000 picoseconds. + Weight::from_parts(398_928_494, 131749) + // Standard Error: 12 + .saturating_add(Weight::from_parts(1_106, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 800]`. fn seal_set_storage(r: u32, ) -> Weight { - // Minimum execution time: 290_063 nanoseconds. - Weight::from_ref_time(256_404_853) - // Standard Error: 436_195 - .saturating_add(Weight::from_ref_time(406_569_743).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3)) - .saturating_add(T::DbWeight::get().writes((80_u64).saturating_mul(r.into()))) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `n` is `[0, 8]`. - fn seal_set_storage_per_new_kb(n: u32, ) -> Weight { - // Minimum execution time: 426_295 nanoseconds. - Weight::from_ref_time(582_989_911) - // Standard Error: 1_404_141 - .saturating_add(Weight::from_ref_time(89_545_963).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(52)) - .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(50)) - .saturating_add(T::DbWeight::get().writes((7_u64).saturating_mul(n.into()))) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `n` is `[0, 8]`. - fn seal_set_storage_per_old_kb(n: u32, ) -> Weight { - // Minimum execution time: 425_062 nanoseconds. - Weight::from_ref_time(552_380_900) - // Standard Error: 1_140_169 - .saturating_add(Weight::from_ref_time(64_085_108).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(51)) - .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(49)) - .saturating_add(T::DbWeight::get().writes((7_u64).saturating_mul(n.into()))) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `r` is `[0, 10]`. + // Proof Size summary in bytes: + // Measured: `924 + r * (292 ±0)` + // Estimated: `922 + r * (293 ±0)` + // Minimum execution time: 271_384_000 picoseconds. + Weight::from_parts(147_677_611, 922) + // Standard Error: 13_371 + .saturating_add(Weight::from_parts(7_085_478, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 293).saturating_mul(r.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 16384]`. + fn seal_set_storage_per_new_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1383` + // Estimated: `1359` + // Minimum execution time: 279_587_000 picoseconds. + Weight::from_parts(335_690_918, 1359) + // Standard Error: 57 + .saturating_add(Weight::from_parts(708, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(10_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 16384]`. + fn seal_set_storage_per_old_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1246 + n * (1 ±0)` + // Estimated: `1246 + n * (1 ±0)` + // Minimum execution time: 275_572_000 picoseconds. + Weight::from_parts(300_309_544, 1246) + // Standard Error: 35 + .saturating_add(Weight::from_parts(299, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 800]`. fn seal_clear_storage(r: u32, ) -> Weight { - // Minimum execution time: 294_421 nanoseconds. - Weight::from_ref_time(260_897_072) - // Standard Error: 474_135 - .saturating_add(Weight::from_ref_time(403_191_862).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3)) - .saturating_add(T::DbWeight::get().writes((80_u64).saturating_mul(r.into()))) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `n` is `[0, 8]`. - fn seal_clear_storage_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 391_743 nanoseconds. - Weight::from_ref_time(535_327_545) - // Standard Error: 1_297_157 - .saturating_add(Weight::from_ref_time(67_222_981).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(51)) - .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(48)) - .saturating_add(T::DbWeight::get().writes((7_u64).saturating_mul(n.into()))) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `r` is `[0, 10]`. + // Proof Size summary in bytes: + // Measured: `920 + r * (288 ±0)` + // Estimated: `924 + r * (289 ±0)` + // Minimum execution time: 271_875_000 picoseconds. + Weight::from_parts(153_680_437, 924) + // Standard Error: 13_050 + .saturating_add(Weight::from_parts(6_892_925, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 289).saturating_mul(r.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 16384]`. + fn seal_clear_storage_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1242 + n * (1 ±0)` + // Estimated: `1242 + n * (1 ±0)` + // Minimum execution time: 272_682_000 picoseconds. + Weight::from_parts(301_025_128, 1242) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 800]`. fn seal_get_storage(r: u32, ) -> Weight { - // Minimum execution time: 294_947 nanoseconds. - Weight::from_ref_time(266_280_887) - // Standard Error: 382_477 - .saturating_add(Weight::from_ref_time(323_306_898).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `n` is `[0, 8]`. - fn seal_get_storage_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 375_487 nanoseconds. - Weight::from_ref_time(497_019_365) - // Standard Error: 1_141_672 - .saturating_add(Weight::from_ref_time(152_354_482).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(51)) - .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `r` is `[0, 10]`. + // Proof Size summary in bytes: + // Measured: `914 + r * (296 ±0)` + // Estimated: `919 + r * (297 ±0)` + // Minimum execution time: 271_796_000 picoseconds. + Weight::from_parts(183_856_480, 919) + // Standard Error: 10_064 + .saturating_add(Weight::from_parts(5_660_636, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 297).saturating_mul(r.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 16384]`. + fn seal_get_storage_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1258 + n * (1 ±0)` + // Estimated: `1258 + n * (1 ±0)` + // Minimum execution time: 273_102_000 picoseconds. + Weight::from_parts(297_455_692, 1258) + // Standard Error: 35 + .saturating_add(Weight::from_parts(868, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 800]`. fn seal_contains_storage(r: u32, ) -> Weight { - // Minimum execution time: 294_716 nanoseconds. - Weight::from_ref_time(266_187_186) - // Standard Error: 395_323 - .saturating_add(Weight::from_ref_time(309_907_221).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `n` is `[0, 8]`. - fn seal_contains_storage_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 371_667 nanoseconds. - Weight::from_ref_time(478_075_482) - // Standard Error: 979_116 - .saturating_add(Weight::from_ref_time(62_075_707).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(51)) - .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `r` is `[0, 10]`. + // Proof Size summary in bytes: + // Measured: `935 + r * (288 ±0)` + // Estimated: `936 + r * (289 ±0)` + // Minimum execution time: 271_323_000 picoseconds. + Weight::from_parts(190_080_834, 936) + // Standard Error: 9_143 + .saturating_add(Weight::from_parts(5_488_362, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 289).saturating_mul(r.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 16384]`. + fn seal_contains_storage_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1245 + n * (1 ±0)` + // Estimated: `1245 + n * (1 ±0)` + // Minimum execution time: 270_399_000 picoseconds. + Weight::from_parts(296_679_410, 1245) + // Standard Error: 34 + .saturating_add(Weight::from_parts(161, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 800]`. fn seal_take_storage(r: u32, ) -> Weight { - // Minimum execution time: 292_147 nanoseconds. - Weight::from_ref_time(254_374_598) - // Standard Error: 442_367 - .saturating_add(Weight::from_ref_time(413_674_705).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3)) - .saturating_add(T::DbWeight::get().writes((80_u64).saturating_mul(r.into()))) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `n` is `[0, 8]`. - fn seal_take_storage_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 398_554 nanoseconds. - Weight::from_ref_time(553_715_911) - // Standard Error: 1_415_272 - .saturating_add(Weight::from_ref_time(158_254_544).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(51)) - .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(48)) - .saturating_add(T::DbWeight::get().writes((7_u64).saturating_mul(n.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `908 + r * (296 ±0)` + // Estimated: `915 + r * (297 ±0)` + // Minimum execution time: 271_645_000 picoseconds. + Weight::from_parts(147_320_521, 915) + // Standard Error: 13_502 + .saturating_add(Weight::from_parts(7_074_778, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 297).saturating_mul(r.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 16384]`. + fn seal_take_storage_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1259 + n * (1 ±0)` + // Estimated: `1259 + n * (1 ±0)` + // Minimum execution time: 280_680_000 picoseconds. + Weight::from_parts(304_043_474, 1259) + // Standard Error: 29 + .saturating_add(Weight::from_parts(644, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1602 w:1601) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_transfer(r: u32, ) -> Weight { - // Minimum execution time: 295_081 nanoseconds. - Weight::from_ref_time(253_461_581) - // Standard Error: 740_198 - .saturating_add(Weight::from_ref_time(1_369_153_623).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().reads((80_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(4)) - .saturating_add(T::DbWeight::get().writes((80_u64).saturating_mul(r.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `1452 + r * (45 ±0)` + // Estimated: `7349 + r * (2520 ±0)` + // Minimum execution time: 274_928_000 picoseconds. + Weight::from_parts(192_111_339, 7349) + // Standard Error: 42_436 + .saturating_add(Weight::from_parts(40_323_660, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 2520).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:801 w:801) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:2 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:2 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:803 w:803) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 800]`. fn seal_call(r: u32, ) -> Weight { - // Minimum execution time: 296_089 nanoseconds. - Weight::from_ref_time(297_193_000) - // Standard Error: 6_571_547 - .saturating_add(Weight::from_ref_time(21_080_280_397).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().reads((160_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3)) - .saturating_add(T::DbWeight::get().writes((160_u64).saturating_mul(r.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `1296 + r * (276 ±0)` + // Estimated: `9481 + r * (2752 ±0)` + // Minimum execution time: 275_293_000 picoseconds. + Weight::from_parts(278_243_000, 9481) + // Standard Error: 119_869 + .saturating_add(Weight::from_parts(245_114_905, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(11_u64)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 2752).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:736 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:736 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:737 w:737) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 800]`. fn seal_delegate_call(r: u32, ) -> Weight { - // Minimum execution time: 296_828 nanoseconds. - Weight::from_ref_time(297_685_000) - // Standard Error: 6_864_880 - .saturating_add(Weight::from_ref_time(20_770_321_946).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((150_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3)) - .saturating_add(T::DbWeight::get().writes((75_u64).saturating_mul(r.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:81 w:81) - // Storage: Contracts CodeStorage (r:2 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:82 w:82) + // Proof Size summary in bytes: + // Measured: `0 + r * (572 ±0)` + // Estimated: `6806 + r * (2633 ±3)` + // Minimum execution time: 271_857_000 picoseconds. + Weight::from_parts(278_276_000, 6806) + // Standard Error: 152_056 + .saturating_add(Weight::from_parts(243_744_830, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 2633).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:3 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:2 w:2) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:2 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:2 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:4 w:4) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) /// The range of component `t` is `[0, 1]`. - /// The range of component `c` is `[0, 1024]`. - fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight { - // Minimum execution time: 9_824_671 nanoseconds. - Weight::from_ref_time(8_713_861_450) - // Standard Error: 7_388_925 - .saturating_add(Weight::from_ref_time(1_327_819_806).saturating_mul(t.into())) - // Standard Error: 11_079 - .saturating_add(Weight::from_ref_time(9_789_514).saturating_mul(c.into())) - .saturating_add(T::DbWeight::get().reads(167)) - .saturating_add(T::DbWeight::get().reads((81_u64).saturating_mul(t.into()))) - .saturating_add(T::DbWeight::get().writes(163)) - .saturating_add(T::DbWeight::get().writes((81_u64).saturating_mul(t.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - // Storage: Contracts Nonce (r:1 w:1) - // Storage: Contracts OwnerInfoOf (r:80 w:80) - /// The range of component `r` is `[0, 20]`. + /// The range of component `c` is `[0, 1048576]`. + fn seal_call_per_transfer_clone_byte(t: u32, c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1328 + t * (310 ±0)` + // Estimated: `12218 + t * (5260 ±0)` + // Minimum execution time: 463_865_000 picoseconds. + Weight::from_parts(70_396_050, 12218) + // Standard Error: 11_489_598 + .saturating_add(Weight::from_parts(359_195_747, 0).saturating_mul(t.into())) + // Standard Error: 16 + .saturating_add(Weight::from_parts(1_090, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(13_u64)) + .saturating_add(T::DbWeight::get().reads((2_u64).saturating_mul(t.into()))) + .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(t.into()))) + .saturating_add(Weight::from_parts(0, 5260).saturating_mul(t.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1602 w:1602) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:801 w:801) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:801 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:801 w:800) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: Contracts Nonce (r:1 w:1) + /// Proof: Contracts Nonce (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:802 w:802) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[1, 800]`. fn seal_instantiate(r: u32, ) -> Weight { - // Minimum execution time: 293_374 nanoseconds. - Weight::from_ref_time(302_067_000) - // Standard Error: 20_137_097 - .saturating_add(Weight::from_ref_time(26_444_812_817).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().reads((400_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(5)) - .saturating_add(T::DbWeight::get().writes((400_u64).saturating_mul(r.into()))) - } - // Storage: System Account (r:81 w:81) - // Storage: Contracts ContractInfoOf (r:81 w:81) - // Storage: Contracts CodeStorage (r:2 w:1) - // Storage: Timestamp Now (r:1 w:0) - // Storage: Contracts Nonce (r:1 w:1) - // Storage: Contracts OwnerInfoOf (r:1 w:1) - // Storage: System EventTopics (r:82 w:82) + // Proof Size summary in bytes: + // Measured: `1383 + r * (251 ±0)` + // Estimated: `7207 + r * (5202 ±0)` + // Minimum execution time: 660_947_000 picoseconds. + Weight::from_parts(668_346_000, 7207) + // Standard Error: 357_950 + .saturating_add(Weight::from_parts(397_202_020, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(10_u64)) + .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(T::DbWeight::get().writes((5_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 5202).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:4 w:4) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:2 w:2) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:2 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:2 w:1) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: Contracts Nonce (r:1 w:1) + /// Proof: Contracts Nonce (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:3 w:3) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) /// The range of component `t` is `[0, 1]`. - /// The range of component `i` is `[0, 960]`. - /// The range of component `s` is `[0, 960]`. - fn seal_instantiate_per_transfer_input_salt_kb(t: u32, i: u32, s: u32, ) -> Weight { - // Minimum execution time: 126_456_770 nanoseconds. - Weight::from_ref_time(9_223_715_752) - // Standard Error: 96_040_318 - .saturating_add(Weight::from_ref_time(19_550_519).saturating_mul(t.into())) - // Standard Error: 156_614 - .saturating_add(Weight::from_ref_time(122_958_298).saturating_mul(i.into())) - // Standard Error: 156_614 - .saturating_add(Weight::from_ref_time(123_316_079).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(249)) + /// The range of component `i` is `[0, 983040]`. + /// The range of component `s` is `[0, 983040]`. + fn seal_instantiate_per_transfer_input_salt_byte(t: u32, i: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1232 + t * (156 ±0)` + // Estimated: `9662 + t * (2578 ±2)` + // Minimum execution time: 2_419_720_000 picoseconds. + Weight::from_parts(1_328_224_119, 9662) + // Standard Error: 17 + .saturating_add(Weight::from_parts(1_171, 0).saturating_mul(i.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(1_263, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(15_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(t.into()))) - .saturating_add(T::DbWeight::get().writes(247)) + .saturating_add(T::DbWeight::get().writes(10_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(t.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 1]`. + .saturating_add(Weight::from_parts(0, 2578).saturating_mul(t.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_hash_sha2_256(r: u32, ) -> Weight { - // Minimum execution time: 289_275 nanoseconds. - Weight::from_ref_time(297_803_961) - // Standard Error: 303_405 - .saturating_add(Weight::from_ref_time(40_800_138).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `n` is `[0, 1024]`. - fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 336_077 nanoseconds. - Weight::from_ref_time(336_846_000) - // Standard Error: 51_610 - .saturating_add(Weight::from_ref_time(322_208_787).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 1]`. + // Proof Size summary in bytes: + // Measured: `856 + r * (8 ±0)` + // Estimated: `6797 + r * (8 ±0)` + // Minimum execution time: 263_620_000 picoseconds. + Weight::from_parts(285_686_431, 6797) + // Standard Error: 605 + .saturating_add(Weight::from_parts(393_863, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_sha2_256_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `864` + // Estimated: `6804` + // Minimum execution time: 271_378_000 picoseconds. + Weight::from_parts(266_737_832, 6804) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_124, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_hash_keccak_256(r: u32, ) -> Weight { - // Minimum execution time: 290_189 nanoseconds. - Weight::from_ref_time(298_454_259) - // Standard Error: 252_946 - .saturating_add(Weight::from_ref_time(55_034_240).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `n` is `[0, 1024]`. - fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 350_030 nanoseconds. - Weight::from_ref_time(351_356_000) - // Standard Error: 57_422 - .saturating_add(Weight::from_ref_time(256_912_844).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 1]`. + // Proof Size summary in bytes: + // Measured: `858 + r * (8 ±0)` + // Estimated: `6800 + r * (8 ±0)` + // Minimum execution time: 269_277_000 picoseconds. + Weight::from_parts(282_723_951, 6800) + // Standard Error: 577 + .saturating_add(Weight::from_parts(808_522, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_keccak_256_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `866` + // Estimated: `6808` + // Minimum execution time: 254_252_000 picoseconds. + Weight::from_parts(277_589_498, 6808) + // Standard Error: 1 + .saturating_add(Weight::from_parts(3_394, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_hash_blake2_256(r: u32, ) -> Weight { - // Minimum execution time: 288_480 nanoseconds. - Weight::from_ref_time(297_613_263) - // Standard Error: 456_736 - .saturating_add(Weight::from_ref_time(38_100_936).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `n` is `[0, 1024]`. - fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 321_195 nanoseconds. - Weight::from_ref_time(325_561_000) - // Standard Error: 46_289 - .saturating_add(Weight::from_ref_time(99_536_902).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 1]`. + // Proof Size summary in bytes: + // Measured: `858 + r * (8 ±0)` + // Estimated: `6803 + r * (8 ±0)` + // Minimum execution time: 254_411_000 picoseconds. + Weight::from_parts(283_572_987, 6803) + // Standard Error: 549 + .saturating_add(Weight::from_parts(455_436, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_blake2_256_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `866` + // Estimated: `6812` + // Minimum execution time: 264_371_000 picoseconds. + Weight::from_parts(269_330_603, 6812) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1_249, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_hash_blake2_128(r: u32, ) -> Weight { - // Minimum execution time: 287_659 nanoseconds. - Weight::from_ref_time(295_963_340) - // Standard Error: 433_889 - .saturating_add(Weight::from_ref_time(30_251_859).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `n` is `[0, 1024]`. - fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 324_298 nanoseconds. - Weight::from_ref_time(325_738_000) - // Standard Error: 48_563 - .saturating_add(Weight::from_ref_time(99_479_838).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 1]`. + // Proof Size summary in bytes: + // Measured: `858 + r * (8 ±0)` + // Estimated: `6804 + r * (8 ±0)` + // Minimum execution time: 257_896_000 picoseconds. + Weight::from_parts(286_738_151, 6804) + // Standard Error: 680 + .saturating_add(Weight::from_parts(459_525, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_blake2_128_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `866` + // Estimated: `6806` + // Minimum execution time: 272_952_000 picoseconds. + Weight::from_parts(271_516_361, 6806) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1_242, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 125697]`. + fn seal_sr25519_verify_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `991 + n * (1 ±0)` + // Estimated: `6928 + n * (1 ±0)` + // Minimum execution time: 351_363_000 picoseconds. + Weight::from_parts(356_558_856, 6928) + // Standard Error: 10 + .saturating_add(Weight::from_parts(6_085, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 160]`. + fn seal_sr25519_verify(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `801 + r * (112 ±0)` + // Estimated: `6745 + r * (112 ±0)` + // Minimum execution time: 261_688_000 picoseconds. + Weight::from_parts(338_043_015, 6745) + // Standard Error: 13_532 + .saturating_add(Weight::from_parts(56_420_806, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 112).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 160]`. fn seal_ecdsa_recover(r: u32, ) -> Weight { - // Minimum execution time: 294_257 nanoseconds. - Weight::from_ref_time(299_467_620) - // Standard Error: 589_749 - .saturating_add(Weight::from_ref_time(3_015_389_579).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 1]`. + // Proof Size summary in bytes: + // Measured: `901 + r * (76 ±0)` + // Estimated: `6795 + r * (77 ±0)` + // Minimum execution time: 267_401_000 picoseconds. + Weight::from_parts(345_773_771, 6795) + // Standard Error: 14_486 + .saturating_add(Weight::from_parts(46_180_739, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 77).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 160]`. fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { - // Minimum execution time: 289_480 nanoseconds. - Weight::from_ref_time(298_762_412) - // Standard Error: 397_004 - .saturating_add(Weight::from_ref_time(743_937_087).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - // Storage: Contracts OwnerInfoOf (r:16 w:16) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `871 + r * (42 ±0)` + // Estimated: `6810 + r * (42 ±0)` + // Minimum execution time: 277_890_000 picoseconds. + Weight::from_parts(319_211_194, 6810) + // Standard Error: 9_132 + .saturating_add(Weight::from_parts(12_128_696, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 42).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1536 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1536 w:1536) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:1538 w:1538) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_set_code_hash(r: u32, ) -> Weight { - // Minimum execution time: 293_494 nanoseconds. - Weight::from_ref_time(297_756_000) - // Standard Error: 2_731_227 - .saturating_add(Weight::from_ref_time(1_387_380_436).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((225_u64).saturating_mul(r.into()))) - .saturating_add(T::DbWeight::get().writes(3)) - .saturating_add(T::DbWeight::get().writes((150_u64).saturating_mul(r.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `0 + r * (961 ±0)` + // Estimated: `6801 + r * (3087 ±10)` + // Minimum execution time: 259_692_000 picoseconds. + Weight::from_parts(278_327_000, 6801) + // Standard Error: 60_024 + .saturating_add(Weight::from_parts(25_758_805, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 3087).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_reentrance_count(r: u32, ) -> Weight { - // Minimum execution time: 295_339 nanoseconds. - Weight::from_ref_time(301_577_907) - // Standard Error: 23_836 - .saturating_add(Weight::from_ref_time(10_876_508).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `852 + r * (3 ±0)` + // Estimated: `6802 + r * (3 ±0)` + // Minimum execution time: 258_907_000 picoseconds. + Weight::from_parts(285_755_890, 6802) + // Standard Error: 378 + .saturating_add(Weight::from_parts(179_649, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 3).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_account_reentrance_count(r: u32, ) -> Weight { - // Minimum execution time: 297_096 nanoseconds. - Weight::from_ref_time(336_319_823) - // Standard Error: 128_941 - .saturating_add(Weight::from_ref_time(25_211_374).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - // Storage: Contracts Nonce (r:1 w:1) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `2092 + r * (39 ±0)` + // Estimated: `7919 + r * (40 ±0)` + // Minimum execution time: 260_415_000 picoseconds. + Weight::from_parts(363_871_048, 7919) + // Standard Error: 2_010 + .saturating_add(Weight::from_parts(317_607, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 40).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: Contracts Nonce (r:1 w:1) + /// Proof: Contracts Nonce (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_instantiation_nonce(r: u32, ) -> Weight { - // Minimum execution time: 293_819 nanoseconds. - Weight::from_ref_time(302_765_659) - // Standard Error: 28_518 - .saturating_add(Weight::from_ref_time(9_325_552).saturating_mul(r.into())) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(4)) - } - /// The range of component `r` is `[0, 50]`. + // Proof Size summary in bytes: + // Measured: `855 + r * (3 ±0)` + // Estimated: `6802 + r * (3 ±0)` + // Minimum execution time: 257_725_000 picoseconds. + Weight::from_parts(283_441_372, 6802) + // Standard Error: 371 + .saturating_add(Weight::from_parts(157_674, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(Weight::from_parts(0, 3).saturating_mul(r.into())) + } + /// The range of component `r` is `[0, 5000]`. fn instr_i64const(r: u32, ) -> Weight { - // Minimum execution time: 805 nanoseconds. - Weight::from_ref_time(1_002_066) - // Standard Error: 609 - .saturating_add(Weight::from_ref_time(346_608).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64load(r: u32, ) -> Weight { - // Minimum execution time: 917 nanoseconds. - Weight::from_ref_time(1_247_710) - // Standard Error: 1_069 - .saturating_add(Weight::from_ref_time(984_608).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64store(r: u32, ) -> Weight { - // Minimum execution time: 917 nanoseconds. - Weight::from_ref_time(1_244_315) - // Standard Error: 524 - .saturating_add(Weight::from_ref_time(882_167).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_select(r: u32, ) -> Weight { - // Minimum execution time: 791 nanoseconds. - Weight::from_ref_time(1_119_053) - // Standard Error: 379 - .saturating_add(Weight::from_ref_time(956_169).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_if(r: u32, ) -> Weight { - // Minimum execution time: 782 nanoseconds. - Weight::from_ref_time(773_373) - // Standard Error: 463 - .saturating_add(Weight::from_ref_time(1_299_306).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_br(r: u32, ) -> Weight { - // Minimum execution time: 743 nanoseconds. - Weight::from_ref_time(1_273_896) - // Standard Error: 925 - .saturating_add(Weight::from_ref_time(527_015).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_br_if(r: u32, ) -> Weight { - // Minimum execution time: 761 nanoseconds. - Weight::from_ref_time(929_565) - // Standard Error: 1_275 - .saturating_add(Weight::from_ref_time(805_592).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_br_table(r: u32, ) -> Weight { - // Minimum execution time: 813 nanoseconds. - Weight::from_ref_time(703_802) - // Standard Error: 1_665 - .saturating_add(Weight::from_ref_time(1_074_321).saturating_mul(r.into())) - } - /// The range of component `e` is `[1, 256]`. - fn instr_br_table_per_entry(e: u32, ) -> Weight { - // Minimum execution time: 2_347 nanoseconds. - Weight::from_ref_time(2_752_638) - // Standard Error: 69 - .saturating_add(Weight::from_ref_time(4_771).saturating_mul(e.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_call(r: u32, ) -> Weight { - // Minimum execution time: 826 nanoseconds. - Weight::from_ref_time(1_784_596) - // Standard Error: 1_646 - .saturating_add(Weight::from_ref_time(2_189_993).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_call_indirect(r: u32, ) -> Weight { - // Minimum execution time: 926 nanoseconds. - Weight::from_ref_time(1_898_358) - // Standard Error: 2_678 - .saturating_add(Weight::from_ref_time(2_811_379).saturating_mul(r.into())) - } - /// The range of component `p` is `[0, 128]`. - fn instr_call_indirect_per_param(p: u32, ) -> Weight { - // Minimum execution time: 4_428 nanoseconds. - Weight::from_ref_time(5_257_445) - // Standard Error: 306 - .saturating_add(Weight::from_ref_time(178_882).saturating_mul(p.into())) - } - /// The range of component `l` is `[0, 1024]`. - fn instr_call_per_local(l: u32, ) -> Weight { - // Minimum execution time: 2_945 nanoseconds. - Weight::from_ref_time(4_240_619) - // Standard Error: 39 - .saturating_add(Weight::from_ref_time(46_640).saturating_mul(l.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_local_get(r: u32, ) -> Weight { - // Minimum execution time: 2_204 nanoseconds. - Weight::from_ref_time(2_430_272) - // Standard Error: 270 - .saturating_add(Weight::from_ref_time(365_718).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_local_set(r: u32, ) -> Weight { - // Minimum execution time: 2_117 nanoseconds. - Weight::from_ref_time(2_429_354) - // Standard Error: 240 - .saturating_add(Weight::from_ref_time(381_830).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_local_tee(r: u32, ) -> Weight { - // Minimum execution time: 2_123 nanoseconds. - Weight::from_ref_time(2_460_016) - // Standard Error: 262 - .saturating_add(Weight::from_ref_time(526_554).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_global_get(r: u32, ) -> Weight { - // Minimum execution time: 892 nanoseconds. - Weight::from_ref_time(1_234_618) - // Standard Error: 485 - .saturating_add(Weight::from_ref_time(813_721).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_global_set(r: u32, ) -> Weight { - // Minimum execution time: 901 nanoseconds. - Weight::from_ref_time(1_182_838) - // Standard Error: 482 - .saturating_add(Weight::from_ref_time(831_308).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_memory_current(r: u32, ) -> Weight { - // Minimum execution time: 929 nanoseconds. - Weight::from_ref_time(1_221_388) - // Standard Error: 434 - .saturating_add(Weight::from_ref_time(693_301).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 1]`. - fn instr_memory_grow(r: u32, ) -> Weight { - // Minimum execution time: 808 nanoseconds. - Weight::from_ref_time(906_944) - // Standard Error: 6_164 - .saturating_add(Weight::from_ref_time(187_445_255).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64clz(r: u32, ) -> Weight { - // Minimum execution time: 807 nanoseconds. - Weight::from_ref_time(1_064_776) - // Standard Error: 287 - .saturating_add(Weight::from_ref_time(507_278).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64ctz(r: u32, ) -> Weight { - // Minimum execution time: 776 nanoseconds. - Weight::from_ref_time(1_033_205) - // Standard Error: 443 - .saturating_add(Weight::from_ref_time(512_445).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64popcnt(r: u32, ) -> Weight { - // Minimum execution time: 909 nanoseconds. - Weight::from_ref_time(1_089_083) - // Standard Error: 335 - .saturating_add(Weight::from_ref_time(510_392).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64eqz(r: u32, ) -> Weight { - // Minimum execution time: 791 nanoseconds. - Weight::from_ref_time(1_063_118) - // Standard Error: 210 - .saturating_add(Weight::from_ref_time(522_817).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64extendsi32(r: u32, ) -> Weight { - // Minimum execution time: 789 nanoseconds. - Weight::from_ref_time(1_056_006) - // Standard Error: 240 - .saturating_add(Weight::from_ref_time(505_198).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64extendui32(r: u32, ) -> Weight { - // Minimum execution time: 804 nanoseconds. - Weight::from_ref_time(1_087_882) - // Standard Error: 234 - .saturating_add(Weight::from_ref_time(503_830).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i32wrapi64(r: u32, ) -> Weight { - // Minimum execution time: 756 nanoseconds. - Weight::from_ref_time(1_050_994) - // Standard Error: 172 - .saturating_add(Weight::from_ref_time(503_343).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64eq(r: u32, ) -> Weight { - // Minimum execution time: 775 nanoseconds. - Weight::from_ref_time(1_061_228) - // Standard Error: 232 - .saturating_add(Weight::from_ref_time(730_751).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64ne(r: u32, ) -> Weight { - // Minimum execution time: 809 nanoseconds. - Weight::from_ref_time(1_080_957) - // Standard Error: 320 - .saturating_add(Weight::from_ref_time(732_246).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64lts(r: u32, ) -> Weight { - // Minimum execution time: 808 nanoseconds. - Weight::from_ref_time(1_055_752) - // Standard Error: 496 - .saturating_add(Weight::from_ref_time(740_084).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64ltu(r: u32, ) -> Weight { - // Minimum execution time: 792 nanoseconds. - Weight::from_ref_time(1_111_972) - // Standard Error: 270 - .saturating_add(Weight::from_ref_time(741_581).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64gts(r: u32, ) -> Weight { - // Minimum execution time: 862 nanoseconds. - Weight::from_ref_time(1_111_246) - // Standard Error: 263 - .saturating_add(Weight::from_ref_time(746_026).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64gtu(r: u32, ) -> Weight { - // Minimum execution time: 810 nanoseconds. - Weight::from_ref_time(1_094_933) - // Standard Error: 271 - .saturating_add(Weight::from_ref_time(743_645).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64les(r: u32, ) -> Weight { - // Minimum execution time: 769 nanoseconds. - Weight::from_ref_time(819_933) - // Standard Error: 4_528 - .saturating_add(Weight::from_ref_time(751_599).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64leu(r: u32, ) -> Weight { - // Minimum execution time: 797 nanoseconds. - Weight::from_ref_time(1_107_352) - // Standard Error: 344 - .saturating_add(Weight::from_ref_time(738_684).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64ges(r: u32, ) -> Weight { - // Minimum execution time: 784 nanoseconds. - Weight::from_ref_time(1_068_487) - // Standard Error: 3_796 - .saturating_add(Weight::from_ref_time(741_207).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64geu(r: u32, ) -> Weight { - // Minimum execution time: 813 nanoseconds. - Weight::from_ref_time(1_067_278) - // Standard Error: 459 - .saturating_add(Weight::from_ref_time(737_841).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64add(r: u32, ) -> Weight { - // Minimum execution time: 798 nanoseconds. - Weight::from_ref_time(1_100_254) - // Standard Error: 297 - .saturating_add(Weight::from_ref_time(717_622).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64sub(r: u32, ) -> Weight { - // Minimum execution time: 778 nanoseconds. - Weight::from_ref_time(1_063_748) - // Standard Error: 378 - .saturating_add(Weight::from_ref_time(713_577).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64mul(r: u32, ) -> Weight { - // Minimum execution time: 780 nanoseconds. - Weight::from_ref_time(1_088_755) - // Standard Error: 316 - .saturating_add(Weight::from_ref_time(714_127).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64divs(r: u32, ) -> Weight { - // Minimum execution time: 776 nanoseconds. - Weight::from_ref_time(1_062_591) - // Standard Error: 610 - .saturating_add(Weight::from_ref_time(1_355_430).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64divu(r: u32, ) -> Weight { - // Minimum execution time: 801 nanoseconds. - Weight::from_ref_time(1_092_395) - // Standard Error: 423 - .saturating_add(Weight::from_ref_time(1_282_356).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64rems(r: u32, ) -> Weight { - // Minimum execution time: 822 nanoseconds. - Weight::from_ref_time(1_083_079) - // Standard Error: 562 - .saturating_add(Weight::from_ref_time(1_398_522).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64remu(r: u32, ) -> Weight { - // Minimum execution time: 798 nanoseconds. - Weight::from_ref_time(1_088_997) - // Standard Error: 401 - .saturating_add(Weight::from_ref_time(1_283_442).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64and(r: u32, ) -> Weight { - // Minimum execution time: 815 nanoseconds. - Weight::from_ref_time(1_081_903) - // Standard Error: 264 - .saturating_add(Weight::from_ref_time(718_069).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64or(r: u32, ) -> Weight { - // Minimum execution time: 813 nanoseconds. - Weight::from_ref_time(1_091_426) - // Standard Error: 296 - .saturating_add(Weight::from_ref_time(717_894).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64xor(r: u32, ) -> Weight { - // Minimum execution time: 794 nanoseconds. - Weight::from_ref_time(1_040_144) - // Standard Error: 400 - .saturating_add(Weight::from_ref_time(719_886).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64shl(r: u32, ) -> Weight { - // Minimum execution time: 774 nanoseconds. - Weight::from_ref_time(1_092_466) - // Standard Error: 4_173 - .saturating_add(Weight::from_ref_time(739_873).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64shrs(r: u32, ) -> Weight { - // Minimum execution time: 793 nanoseconds. - Weight::from_ref_time(1_105_611) - // Standard Error: 394 - .saturating_add(Weight::from_ref_time(735_358).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64shru(r: u32, ) -> Weight { - // Minimum execution time: 805 nanoseconds. - Weight::from_ref_time(1_130_462) - // Standard Error: 327 - .saturating_add(Weight::from_ref_time(734_198).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64rotl(r: u32, ) -> Weight { - // Minimum execution time: 831 nanoseconds. - Weight::from_ref_time(1_118_894) - // Standard Error: 389 - .saturating_add(Weight::from_ref_time(733_834).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64rotr(r: u32, ) -> Weight { - // Minimum execution time: 772 nanoseconds. - Weight::from_ref_time(1_123_733) - // Standard Error: 337 - .saturating_add(Weight::from_ref_time(733_923).saturating_mul(r.into())) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_635_000 picoseconds. + Weight::from_parts(2_990_110, 0) + // Standard Error: 31 + .saturating_add(Weight::from_parts(10_213, 0).saturating_mul(r.into())) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Contracts DeletionQueue (r:1 w:0) + /// Storage: Contracts DeletionQueueCounter (r:1 w:0) + /// Proof: Contracts DeletionQueueCounter (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) fn on_process_deletion_queue_batch() -> Weight { - // Minimum execution time: 3_326 nanoseconds. - Weight::from_ref_time(3_433_000) - .saturating_add(RocksDbWeight::get().reads(1)) - } - // Storage: Skipped Metadata (r:0 w:0) + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `1627` + // Minimum execution time: 2_519_000 picoseconds. + Weight::from_parts(2_660_000, 1627) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) /// The range of component `k` is `[0, 1024]`. fn on_initialize_per_trie_key(k: u32, ) -> Weight { - // Minimum execution time: 15_703 nanoseconds. - Weight::from_ref_time(15_288_927) - // Standard Error: 951 - .saturating_add(Weight::from_ref_time(940_816).saturating_mul(k.into())) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `451 + k * (69 ±0)` + // Estimated: `441 + k * (70 ±0)` + // Minimum execution time: 13_096_000 picoseconds. + Weight::from_parts(13_395_000, 441) + // Standard Error: 1_046 + .saturating_add(Weight::from_parts(1_246_238, 0).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) + .saturating_add(RocksDbWeight::get().writes(2_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) - } - // Storage: Contracts DeletionQueue (r:1 w:0) - /// The range of component `q` is `[0, 128]`. - fn on_initialize_per_queue_item(q: u32, ) -> Weight { - // Minimum execution time: 3_548 nanoseconds. - Weight::from_ref_time(15_764_121) - // Standard Error: 3_339 - .saturating_add(Weight::from_ref_time(1_214_890).saturating_mul(q.into())) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Contracts PristineCode (r:1 w:0) - // Storage: Contracts CodeStorage (r:0 w:1) - /// The range of component `c` is `[0, 64226]`. - fn reinstrument(c: u32, ) -> Weight { - // Minimum execution time: 29_801 nanoseconds. - Weight::from_ref_time(27_932_706) - // Standard Error: 49 - .saturating_add(Weight::from_ref_time(50_532).saturating_mul(c.into())) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `c` is `[0, 131072]`. + .saturating_add(Weight::from_parts(0, 70).saturating_mul(k.into())) + } + /// Storage: unknown `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:2 w:1) + /// Proof Skipped: unknown `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:2 w:1) + /// The range of component `c` is `[0, 125952]`. + fn v9_migration_step(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `211 + c * (1 ±0)` + // Estimated: `6149 + c * (1 ±0)` + // Minimum execution time: 8_409_000 picoseconds. + Weight::from_parts(9_006_438, 6149) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_345, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: Contracts ContractInfoOf (r:2 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + fn v10_migration_step() -> Weight { + // Proof Size summary in bytes: + // Measured: `510` + // Estimated: `6450` + // Minimum execution time: 16_962_000 picoseconds. + Weight::from_parts(17_716_000, 6450) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Contracts DeletionQueue (r:1 w:1025) + /// Proof: Contracts DeletionQueue (max_values: None, max_size: Some(142), added: 2617, mode: Measured) + /// Storage: Contracts DeletionQueueCounter (r:0 w:1) + /// Proof: Contracts DeletionQueueCounter (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// The range of component `k` is `[0, 1024]`. + fn v11_migration_step(k: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `171 + k * (1 ±0)` + // Estimated: `3635 + k * (1 ±0)` + // Minimum execution time: 3_763_000 picoseconds. + Weight::from_parts(2_401_625, 3635) + // Standard Error: 2_827 + .saturating_add(Weight::from_parts(1_201_671, 0).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(k.into())) + } + /// Storage: unknown `0x4342193e496fab7ec59d615ed0dc553053f13fd319a03c211337c76e0fe776df` (r:2 w:0) + /// Proof Skipped: unknown `0x4342193e496fab7ec59d615ed0dc553053f13fd319a03c211337c76e0fe776df` (r:2 w:0) + /// Storage: unknown `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:1 w:1) + /// Proof Skipped: unknown `0x4342193e496fab7ec59d615ed0dc553022fca90611ba8b7942f8bdb3b97f6580` (r:1 w:1) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:0 w:1) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// The range of component `c` is `[0, 125952]`. + fn v12_migration_step(c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `325 + c * (1 ±0)` + // Estimated: `6263 + c * (1 ±0)` + // Minimum execution time: 17_490_000 picoseconds. + Weight::from_parts(17_712_278, 6263) + // Standard Error: 0 + .saturating_add(Weight::from_parts(427, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:1) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + fn migration_noop() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `1627` + // Minimum execution time: 3_282_000 picoseconds. + Weight::from_parts(3_536_000, 1627) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:1) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: unknown `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:1) + /// Proof Skipped: unknown `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:1) + fn migrate() -> Weight { + // Proof Size summary in bytes: + // Measured: `166` + // Estimated: `3631` + // Minimum execution time: 12_973_000 picoseconds. + Weight::from_parts(13_366_000, 3631) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: unknown `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof Skipped: unknown `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + fn on_runtime_upgrade_noop() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 4_764_000 picoseconds. + Weight::from_parts(5_000_000, 3607) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: unknown `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof Skipped: unknown `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + fn on_runtime_upgrade_in_progress() -> Weight { + // Proof Size summary in bytes: + // Measured: `167` + // Estimated: `3632` + // Minimum execution time: 6_616_000 picoseconds. + Weight::from_parts(6_935_000, 3632) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } + /// Storage: unknown `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Proof Skipped: unknown `0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429` (r:1 w:0) + /// Storage: Contracts MigrationInProgress (r:1 w:1) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + fn on_runtime_upgrade() -> Weight { + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 6_953_000 picoseconds. + Weight::from_parts(7_440_000, 3607) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `c` is `[0, 125952]`. fn call_with_code_per_byte(c: u32, ) -> Weight { - // Minimum execution time: 307_894 nanoseconds. - Weight::from_ref_time(322_346_319) - // Standard Error: 25 - .saturating_add(Weight::from_ref_time(30_678).saturating_mul(c.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Contracts CodeStorage (r:1 w:1) - // Storage: Contracts Nonce (r:1 w:1) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: System EventTopics (r:3 w:3) - // Storage: Contracts PristineCode (r:0 w:1) - // Storage: Contracts OwnerInfoOf (r:0 w:1) - /// The range of component `c` is `[0, 64226]`. + // Proof Size summary in bytes: + // Measured: `786` + // Estimated: `6735 + c * (1 ±0)` + // Minimum execution time: 302_714_000 picoseconds. + Weight::from_parts(271_320_595, 6735) + // Standard Error: 72 + .saturating_add(Weight::from_parts(38_474, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(c.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:1) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: System EventTopics (r:3 w:3) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// Storage: Contracts Nonce (r:1 w:1) + /// Proof: Contracts Nonce (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts PristineCode (r:0 w:1) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// The range of component `c` is `[0, 125952]`. /// The range of component `i` is `[0, 1048576]`. /// The range of component `s` is `[0, 1048576]`. fn instantiate_with_code(c: u32, i: u32, s: u32, ) -> Weight { - // Minimum execution time: 3_610_424 nanoseconds. - Weight::from_ref_time(583_426_386) - // Standard Error: 277 - .saturating_add(Weight::from_ref_time(90_224).saturating_mul(c.into())) - // Standard Error: 16 - .saturating_add(Weight::from_ref_time(1_325).saturating_mul(i.into())) - // Standard Error: 16 - .saturating_add(Weight::from_ref_time(1_727).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(8)) - .saturating_add(RocksDbWeight::get().writes(9)) - } - // Storage: Contracts CodeStorage (r:1 w:1) - // Storage: Contracts Nonce (r:1 w:1) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Contracts OwnerInfoOf (r:1 w:1) - // Storage: System EventTopics (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `303` + // Estimated: `8745` + // Minimum execution time: 4_506_957_000 picoseconds. + Weight::from_parts(643_316_921, 8745) + // Standard Error: 278 + .saturating_add(Weight::from_parts(112_835, 0).saturating_mul(c.into())) + // Standard Error: 33 + .saturating_add(Weight::from_parts(1_830, 0).saturating_mul(i.into())) + // Standard Error: 33 + .saturating_add(Weight::from_parts(2_022, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(10_u64)) + .saturating_add(RocksDbWeight::get().writes(9_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:1) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Contracts Nonce (r:1 w:1) + /// Proof: Contracts Nonce (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) /// The range of component `i` is `[0, 1048576]`. /// The range of component `s` is `[0, 1048576]`. fn instantiate(i: u32, s: u32, ) -> Weight { - // Minimum execution time: 1_892_996 nanoseconds. - Weight::from_ref_time(202_958_196) - // Standard Error: 8 - .saturating_add(Weight::from_ref_time(1_627).saturating_mul(i.into())) - // Standard Error: 8 - .saturating_add(Weight::from_ref_time(1_755).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(8)) - .saturating_add(RocksDbWeight::get().writes(7)) - } - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: System EventTopics (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `523` + // Estimated: `6513` + // Minimum execution time: 2_103_482_000 picoseconds. + Weight::from_parts(316_666_183, 6513) + // Standard Error: 7 + .saturating_add(Weight::from_parts(1_933, 0).saturating_mul(i.into())) + // Standard Error: 7 + .saturating_add(Weight::from_parts(1_803, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(10_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) fn call() -> Weight { - // Minimum execution time: 157_347 nanoseconds. - Weight::from_ref_time(159_084_000) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Contracts CodeStorage (r:1 w:1) - // Storage: System EventTopics (r:1 w:1) - // Storage: Contracts PristineCode (r:0 w:1) - // Storage: Contracts OwnerInfoOf (r:0 w:1) - /// The range of component `c` is `[0, 64226]`. + // Proof Size summary in bytes: + // Measured: `820` + // Estimated: `6760` + // Minimum execution time: 207_530_000 picoseconds. + Weight::from_parts(217_243_000, 6760) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:1) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: System EventTopics (r:1 w:1) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// Storage: Contracts PristineCode (r:0 w:1) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// The range of component `c` is `[0, 125952]`. fn upload_code(c: u32, ) -> Weight { - // Minimum execution time: 299_987 nanoseconds. - Weight::from_ref_time(305_274_879) - // Standard Error: 72 - .saturating_add(Weight::from_ref_time(91_916).saturating_mul(c.into())) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Contracts OwnerInfoOf (r:1 w:1) - // Storage: System EventTopics (r:1 w:1) - // Storage: Contracts CodeStorage (r:0 w:1) - // Storage: Contracts PristineCode (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3607` + // Minimum execution time: 246_381_000 picoseconds. + Weight::from_parts(242_933_576, 3607) + // Standard Error: 100 + .saturating_add(Weight::from_parts(74_645, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:1) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: System EventTopics (r:1 w:1) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// Storage: Contracts PristineCode (r:0 w:1) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) fn remove_code() -> Weight { - // Minimum execution time: 40_795 nanoseconds. - Weight::from_ref_time(41_297_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts OwnerInfoOf (r:2 w:2) - // Storage: System EventTopics (r:3 w:3) + // Proof Size summary in bytes: + // Measured: `255` + // Estimated: `3720` + // Minimum execution time: 35_519_000 picoseconds. + Weight::from_parts(36_813_000, 3720) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:2 w:2) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: System EventTopics (r:3 w:3) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) fn set_code() -> Weight { - // Minimum execution time: 41_957 nanoseconds. - Weight::from_ref_time(42_536_000) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(6)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `575` + // Estimated: `8990` + // Minimum execution time: 37_769_000 picoseconds. + Weight::from_parts(39_349_000, 8990) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_caller(r: u32, ) -> Weight { - // Minimum execution time: 293_544 nanoseconds. - Weight::from_ref_time(298_981_222) - // Standard Error: 39_139 - .saturating_add(Weight::from_ref_time(17_278_436).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `860 + r * (6 ±0)` + // Estimated: `6801 + r * (6 ±0)` + // Minimum execution time: 273_355_000 picoseconds. + Weight::from_parts(280_115_308, 6801) + // Standard Error: 662 + .saturating_add(Weight::from_parts(351_066, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1601 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_is_contract(r: u32, ) -> Weight { - // Minimum execution time: 293_500 nanoseconds. - Weight::from_ref_time(237_825_284) - // Standard Error: 450_234 - .saturating_add(Weight::from_ref_time(198_995_806).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `918 + r * (240 ±0)` + // Estimated: `6822 + r * (2715 ±0)` + // Minimum execution time: 264_066_000 picoseconds. + Weight::from_parts(103_474_597, 6822) + // Standard Error: 7_010 + .saturating_add(Weight::from_parts(3_917_988, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 2715).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1601 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_code_hash(r: u32, ) -> Weight { - // Minimum execution time: 295_851 nanoseconds. - Weight::from_ref_time(258_113_447) - // Standard Error: 408_747 - .saturating_add(Weight::from_ref_time(237_490_216).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `910 + r * (244 ±0)` + // Estimated: `6826 + r * (2719 ±0)` + // Minimum execution time: 275_726_000 picoseconds. + Weight::from_parts(111_512_451, 6826) + // Standard Error: 6_673 + .saturating_add(Weight::from_parts(4_626_511, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 2719).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_own_code_hash(r: u32, ) -> Weight { - // Minimum execution time: 294_614 nanoseconds. - Weight::from_ref_time(301_528_998) - // Standard Error: 40_613 - .saturating_add(Weight::from_ref_time(20_815_088).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `867 + r * (6 ±0)` + // Estimated: `6809 + r * (6 ±0)` + // Minimum execution time: 274_377_000 picoseconds. + Weight::from_parts(286_299_699, 6809) + // Standard Error: 521 + .saturating_add(Weight::from_parts(419_417, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_caller_is_origin(r: u32, ) -> Weight { - // Minimum execution time: 293_088 nanoseconds. - Weight::from_ref_time(299_120_595) - // Standard Error: 23_277 - .saturating_add(Weight::from_ref_time(11_130_334).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `857 + r * (3 ±0)` + // Estimated: `6802 + r * (3 ±0)` + // Minimum execution time: 265_297_000 picoseconds. + Weight::from_parts(283_474_927, 6802) + // Standard Error: 376 + .saturating_add(Weight::from_parts(186_214, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 3).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. + fn seal_caller_is_root(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `747 + r * (3 ±0)` + // Estimated: `6687 + r * (3 ±0)` + // Minimum execution time: 258_385_000 picoseconds. + Weight::from_parts(269_869_790, 6687) + // Standard Error: 334 + .saturating_add(Weight::from_parts(164_806, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 3).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_address(r: u32, ) -> Weight { - // Minimum execution time: 295_110 nanoseconds. - Weight::from_ref_time(305_295_787) - // Standard Error: 48_521 - .saturating_add(Weight::from_ref_time(16_610_806).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `861 + r * (6 ±0)` + // Estimated: `6803 + r * (6 ±0)` + // Minimum execution time: 271_351_000 picoseconds. + Weight::from_parts(286_390_305, 6803) + // Standard Error: 628 + .saturating_add(Weight::from_parts(339_374, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_gas_left(r: u32, ) -> Weight { - // Minimum execution time: 293_414 nanoseconds. - Weight::from_ref_time(299_960_283) - // Standard Error: 32_003 - .saturating_add(Weight::from_ref_time(16_652_433).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `857 + r * (6 ±0)` + // Estimated: `6798 + r * (6 ±0)` + // Minimum execution time: 273_060_000 picoseconds. + Weight::from_parts(285_959_049, 6798) + // Standard Error: 813 + .saturating_add(Weight::from_parts(544_941, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:2 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_balance(r: u32, ) -> Weight { - // Minimum execution time: 292_429 nanoseconds. - Weight::from_ref_time(301_025_128) - // Standard Error: 92_155 - .saturating_add(Weight::from_ref_time(92_731_719).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(7)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `1001 + r * (6 ±0)` + // Estimated: `6925 + r * (6 ±0)` + // Minimum execution time: 273_717_000 picoseconds. + Weight::from_parts(301_053_119, 6925) + // Standard Error: 3_314 + .saturating_add(Weight::from_parts(1_645_480, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_value_transferred(r: u32, ) -> Weight { - // Minimum execution time: 293_516 nanoseconds. - Weight::from_ref_time(300_071_428) - // Standard Error: 25_871 - .saturating_add(Weight::from_ref_time(16_599_369).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `871 + r * (6 ±0)` + // Estimated: `6820 + r * (6 ±0)` + // Minimum execution time: 273_480_000 picoseconds. + Weight::from_parts(284_751_212, 6820) + // Standard Error: 501 + .saturating_add(Weight::from_parts(334_063, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_minimum_balance(r: u32, ) -> Weight { - // Minimum execution time: 293_666 nanoseconds. - Weight::from_ref_time(299_803_599) - // Standard Error: 24_508 - .saturating_add(Weight::from_ref_time(16_500_973).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `869 + r * (6 ±0)` + // Estimated: `6818 + r * (6 ±0)` + // Minimum execution time: 278_938_000 picoseconds. + Weight::from_parts(284_829_302, 6818) + // Standard Error: 488 + .saturating_add(Weight::from_parts(338_782, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_block_number(r: u32, ) -> Weight { - // Minimum execution time: 289_193 nanoseconds. - Weight::from_ref_time(297_946_784) - // Standard Error: 43_719 - .saturating_add(Weight::from_ref_time(16_442_254).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `866 + r * (6 ±0)` + // Estimated: `6816 + r * (6 ±0)` + // Minimum execution time: 276_799_000 picoseconds. + Weight::from_parts(290_353_700, 6816) + // Standard Error: 675 + .saturating_add(Weight::from_parts(323_565, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_now(r: u32, ) -> Weight { - // Minimum execution time: 288_897 nanoseconds. - Weight::from_ref_time(298_856_658) - // Standard Error: 31_003 - .saturating_add(Weight::from_ref_time(16_490_508).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `857 + r * (6 ±0)` + // Estimated: `6802 + r * (6 ±0)` + // Minimum execution time: 267_740_000 picoseconds. + Weight::from_parts(287_560_339, 6802) + // Standard Error: 479 + .saturating_add(Weight::from_parts(329_276, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + /// Proof: TransactionPayment NextFeeMultiplier (max_values: Some(1), max_size: Some(16), added: 511, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_weight_to_fee(r: u32, ) -> Weight { - // Minimum execution time: 289_850 nanoseconds. - Weight::from_ref_time(302_898_449) - // Standard Error: 92_822 - .saturating_add(Weight::from_ref_time(86_376_874).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(7)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. - fn seal_gas(r: u32, ) -> Weight { - // Minimum execution time: 145_105 nanoseconds. - Weight::from_ref_time(152_388_934) - // Standard Error: 29_988 - .saturating_add(Weight::from_ref_time(7_761_947).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `931 + r * (14 ±0)` + // Estimated: `6864 + r * (14 ±0)` + // Minimum execution time: 275_471_000 picoseconds. + Weight::from_parts(297_332_107, 6864) + // Standard Error: 2_230 + .saturating_add(Weight::from_parts(1_484_476, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 14).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_input(r: u32, ) -> Weight { - // Minimum execution time: 289_319 nanoseconds. - Weight::from_ref_time(298_390_911) - // Standard Error: 27_329 - .saturating_add(Weight::from_ref_time(14_225_527).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `n` is `[0, 1024]`. - fn seal_input_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 305_538 nanoseconds. - Weight::from_ref_time(339_734_596) - // Standard Error: 4_636 - .saturating_add(Weight::from_ref_time(9_592_149).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `859 + r * (6 ±0)` + // Estimated: `6803 + r * (6 ±0)` + // Minimum execution time: 255_279_000 picoseconds. + Weight::from_parts(282_649_020, 6803) + // Standard Error: 429 + .saturating_add(Weight::from_parts(290_527, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 6).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 1048576]`. + fn seal_input_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `863` + // Estimated: `6803` + // Minimum execution time: 268_029_000 picoseconds. + Weight::from_parts(231_474_232, 6803) + // Standard Error: 23 + .saturating_add(Weight::from_parts(1_050, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) /// The range of component `r` is `[0, 1]`. fn seal_return(r: u32, ) -> Weight { - // Minimum execution time: 287_189 nanoseconds. - Weight::from_ref_time(295_472_383) - // Standard Error: 314_142 - .saturating_add(Weight::from_ref_time(214_016).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `n` is `[0, 1024]`. - fn seal_return_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 289_380 nanoseconds. - Weight::from_ref_time(296_302_404) - // Standard Error: 889 - .saturating_add(Weight::from_ref_time(190_017).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - // Storage: Contracts DeletionQueue (r:1 w:1) - // Storage: Contracts OwnerInfoOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `847 + r * (45 ±0)` + // Estimated: `6787 + r * (45 ±0)` + // Minimum execution time: 252_126_000 picoseconds. + Weight::from_parts(277_677_710, 6787) + // Standard Error: 770_704 + .saturating_add(Weight::from_parts(2_678_989, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 45).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 1048576]`. + fn seal_return_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `857` + // Estimated: `6810` + // Minimum execution time: 271_967_000 picoseconds. + Weight::from_parts(282_988_484, 6810) + // Standard Error: 0 + .saturating_add(Weight::from_parts(387, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:4 w:4) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:1) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: Contracts DeletionQueueCounter (r:1 w:1) + /// Proof: Contracts DeletionQueueCounter (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:3 w:3) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// Storage: Contracts DeletionQueue (r:0 w:1) + /// Proof: Contracts DeletionQueue (max_values: None, max_size: Some(142), added: 2617, mode: Measured) /// The range of component `r` is `[0, 1]`. fn seal_terminate(r: u32, ) -> Weight { - // Minimum execution time: 292_383 nanoseconds. - Weight::from_ref_time(297_557_691) - // Standard Error: 251_228 - .saturating_add(Weight::from_ref_time(56_688_008).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) + // Proof Size summary in bytes: + // Measured: `889 + r * (300 ±0)` + // Estimated: `6829 + r * (7725 ±0)` + // Minimum execution time: 257_246_000 picoseconds. + Weight::from_parts(280_196_561, 6829) + // Standard Error: 815_845 + .saturating_add(Weight::from_parts(127_831_338, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(r.into()))) - .saturating_add(RocksDbWeight::get().writes(3)) - .saturating_add(RocksDbWeight::get().writes((6_u64).saturating_mul(r.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) - /// The range of component `r` is `[0, 20]`. + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes((8_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 7725).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) + /// Proof: RandomnessCollectiveFlip RandomMaterial (max_values: Some(1), max_size: Some(2594), added: 3089, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_random(r: u32, ) -> Weight { - // Minimum execution time: 293_019 nanoseconds. - Weight::from_ref_time(301_747_439) - // Standard Error: 92_043 - .saturating_add(Weight::from_ref_time(109_861_144).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(7)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `938 + r * (10 ±0)` + // Estimated: `6879 + r * (10 ±0)` + // Minimum execution time: 270_074_000 picoseconds. + Weight::from_parts(292_298_331, 6879) + // Standard Error: 2_123 + .saturating_add(Weight::from_parts(2_089_487, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 10).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_deposit_event(r: u32, ) -> Weight { - // Minimum execution time: 287_586 nanoseconds. - Weight::from_ref_time(304_751_302) - // Standard Error: 127_698 - .saturating_add(Weight::from_ref_time(221_525_053).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `857 + r * (10 ±0)` + // Estimated: `6802 + r * (10 ±0)` + // Minimum execution time: 267_080_000 picoseconds. + Weight::from_parts(298_470_496, 6802) + // Standard Error: 3_004 + .saturating_add(Weight::from_parts(3_898_460, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 10).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:6 w:6) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) /// The range of component `t` is `[0, 4]`. - /// The range of component `n` is `[0, 16]`. - fn seal_deposit_event_per_topic_and_kb(t: u32, n: u32, ) -> Weight { - // Minimum execution time: 1_182_204 nanoseconds. - Weight::from_ref_time(508_607_957) - // Standard Error: 503_533 - .saturating_add(Weight::from_ref_time(173_998_655).saturating_mul(t.into())) - // Standard Error: 138_294 - .saturating_add(Weight::from_ref_time(67_991_373).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(t.into()))) - .saturating_add(RocksDbWeight::get().writes(3)) - .saturating_add(RocksDbWeight::get().writes((80_u64).saturating_mul(t.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + /// The range of component `n` is `[0, 16384]`. + fn seal_deposit_event_per_topic_and_byte(t: u32, n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `876 + t * (32 ±0)` + // Estimated: `6823 + t * (2508 ±0)` + // Minimum execution time: 277_152_000 picoseconds. + Weight::from_parts(290_745_178, 6823) + // Standard Error: 88_577 + .saturating_add(Weight::from_parts(2_476_405, 0).saturating_mul(t.into())) + // Standard Error: 24 + .saturating_add(Weight::from_parts(702, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(t.into()))) + .saturating_add(Weight::from_parts(0, 2508).saturating_mul(t.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_debug_message(r: u32, ) -> Weight { - // Minimum execution time: 157_706 nanoseconds. - Weight::from_ref_time(161_895_583) - // Standard Error: 14_952 - .saturating_add(Weight::from_ref_time(12_990_237).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `r` is `[0, 10]`. + // Proof Size summary in bytes: + // Measured: `856 + r * (7 ±0)` + // Estimated: `6800 + r * (7 ±0)` + // Minimum execution time: 168_782_000 picoseconds. + Weight::from_parts(179_694_331, 6800) + // Standard Error: 338 + .saturating_add(Weight::from_parts(246_541, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 7).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: MaxEncodedLen) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: MaxEncodedLen) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `i` is `[0, 1048576]`. + fn seal_debug_message_per_byte(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `125807` + // Estimated: `131749` + // Minimum execution time: 428_673_000 picoseconds. + Weight::from_parts(398_928_494, 131749) + // Standard Error: 12 + .saturating_add(Weight::from_parts(1_106, 0).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 800]`. fn seal_set_storage(r: u32, ) -> Weight { - // Minimum execution time: 290_063 nanoseconds. - Weight::from_ref_time(256_404_853) - // Standard Error: 436_195 - .saturating_add(Weight::from_ref_time(406_569_743).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) - .saturating_add(RocksDbWeight::get().writes(3)) - .saturating_add(RocksDbWeight::get().writes((80_u64).saturating_mul(r.into()))) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `n` is `[0, 8]`. - fn seal_set_storage_per_new_kb(n: u32, ) -> Weight { - // Minimum execution time: 426_295 nanoseconds. - Weight::from_ref_time(582_989_911) - // Standard Error: 1_404_141 - .saturating_add(Weight::from_ref_time(89_545_963).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(52)) - .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(50)) - .saturating_add(RocksDbWeight::get().writes((7_u64).saturating_mul(n.into()))) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `n` is `[0, 8]`. - fn seal_set_storage_per_old_kb(n: u32, ) -> Weight { - // Minimum execution time: 425_062 nanoseconds. - Weight::from_ref_time(552_380_900) - // Standard Error: 1_140_169 - .saturating_add(Weight::from_ref_time(64_085_108).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(51)) - .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(49)) - .saturating_add(RocksDbWeight::get().writes((7_u64).saturating_mul(n.into()))) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `r` is `[0, 10]`. + // Proof Size summary in bytes: + // Measured: `924 + r * (292 ±0)` + // Estimated: `922 + r * (293 ±0)` + // Minimum execution time: 271_384_000 picoseconds. + Weight::from_parts(147_677_611, 922) + // Standard Error: 13_371 + .saturating_add(Weight::from_parts(7_085_478, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 293).saturating_mul(r.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 16384]`. + fn seal_set_storage_per_new_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1383` + // Estimated: `1359` + // Minimum execution time: 279_587_000 picoseconds. + Weight::from_parts(335_690_918, 1359) + // Standard Error: 57 + .saturating_add(Weight::from_parts(708, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(10_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 16384]`. + fn seal_set_storage_per_old_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1246 + n * (1 ±0)` + // Estimated: `1246 + n * (1 ±0)` + // Minimum execution time: 275_572_000 picoseconds. + Weight::from_parts(300_309_544, 1246) + // Standard Error: 35 + .saturating_add(Weight::from_parts(299, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 800]`. fn seal_clear_storage(r: u32, ) -> Weight { - // Minimum execution time: 294_421 nanoseconds. - Weight::from_ref_time(260_897_072) - // Standard Error: 474_135 - .saturating_add(Weight::from_ref_time(403_191_862).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) - .saturating_add(RocksDbWeight::get().writes(3)) - .saturating_add(RocksDbWeight::get().writes((80_u64).saturating_mul(r.into()))) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `n` is `[0, 8]`. - fn seal_clear_storage_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 391_743 nanoseconds. - Weight::from_ref_time(535_327_545) - // Standard Error: 1_297_157 - .saturating_add(Weight::from_ref_time(67_222_981).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(51)) - .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(48)) - .saturating_add(RocksDbWeight::get().writes((7_u64).saturating_mul(n.into()))) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `r` is `[0, 10]`. + // Proof Size summary in bytes: + // Measured: `920 + r * (288 ±0)` + // Estimated: `924 + r * (289 ±0)` + // Minimum execution time: 271_875_000 picoseconds. + Weight::from_parts(153_680_437, 924) + // Standard Error: 13_050 + .saturating_add(Weight::from_parts(6_892_925, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 289).saturating_mul(r.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 16384]`. + fn seal_clear_storage_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1242 + n * (1 ±0)` + // Estimated: `1242 + n * (1 ±0)` + // Minimum execution time: 272_682_000 picoseconds. + Weight::from_parts(301_025_128, 1242) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 800]`. fn seal_get_storage(r: u32, ) -> Weight { - // Minimum execution time: 294_947 nanoseconds. - Weight::from_ref_time(266_280_887) - // Standard Error: 382_477 - .saturating_add(Weight::from_ref_time(323_306_898).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `n` is `[0, 8]`. - fn seal_get_storage_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 375_487 nanoseconds. - Weight::from_ref_time(497_019_365) - // Standard Error: 1_141_672 - .saturating_add(Weight::from_ref_time(152_354_482).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(51)) - .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `r` is `[0, 10]`. + // Proof Size summary in bytes: + // Measured: `914 + r * (296 ±0)` + // Estimated: `919 + r * (297 ±0)` + // Minimum execution time: 271_796_000 picoseconds. + Weight::from_parts(183_856_480, 919) + // Standard Error: 10_064 + .saturating_add(Weight::from_parts(5_660_636, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 297).saturating_mul(r.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 16384]`. + fn seal_get_storage_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1258 + n * (1 ±0)` + // Estimated: `1258 + n * (1 ±0)` + // Minimum execution time: 273_102_000 picoseconds. + Weight::from_parts(297_455_692, 1258) + // Standard Error: 35 + .saturating_add(Weight::from_parts(868, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 800]`. fn seal_contains_storage(r: u32, ) -> Weight { - // Minimum execution time: 294_716 nanoseconds. - Weight::from_ref_time(266_187_186) - // Standard Error: 395_323 - .saturating_add(Weight::from_ref_time(309_907_221).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `n` is `[0, 8]`. - fn seal_contains_storage_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 371_667 nanoseconds. - Weight::from_ref_time(478_075_482) - // Standard Error: 979_116 - .saturating_add(Weight::from_ref_time(62_075_707).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(51)) - .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `r` is `[0, 10]`. + // Proof Size summary in bytes: + // Measured: `935 + r * (288 ±0)` + // Estimated: `936 + r * (289 ±0)` + // Minimum execution time: 271_323_000 picoseconds. + Weight::from_parts(190_080_834, 936) + // Standard Error: 9_143 + .saturating_add(Weight::from_parts(5_488_362, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 289).saturating_mul(r.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 16384]`. + fn seal_contains_storage_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1245 + n * (1 ±0)` + // Estimated: `1245 + n * (1 ±0)` + // Minimum execution time: 270_399_000 picoseconds. + Weight::from_parts(296_679_410, 1245) + // Standard Error: 34 + .saturating_add(Weight::from_parts(161, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 800]`. fn seal_take_storage(r: u32, ) -> Weight { - // Minimum execution time: 292_147 nanoseconds. - Weight::from_ref_time(254_374_598) - // Standard Error: 442_367 - .saturating_add(Weight::from_ref_time(413_674_705).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) - .saturating_add(RocksDbWeight::get().writes(3)) - .saturating_add(RocksDbWeight::get().writes((80_u64).saturating_mul(r.into()))) - } - // Storage: Skipped Metadata (r:0 w:0) - /// The range of component `n` is `[0, 8]`. - fn seal_take_storage_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 398_554 nanoseconds. - Weight::from_ref_time(553_715_911) - // Standard Error: 1_415_272 - .saturating_add(Weight::from_ref_time(158_254_544).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(51)) - .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(48)) - .saturating_add(RocksDbWeight::get().writes((7_u64).saturating_mul(n.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `908 + r * (296 ±0)` + // Estimated: `915 + r * (297 ±0)` + // Minimum execution time: 271_645_000 picoseconds. + Weight::from_parts(147_320_521, 915) + // Standard Error: 13_502 + .saturating_add(Weight::from_parts(7_074_778, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 297).saturating_mul(r.into())) + } + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 16384]`. + fn seal_take_storage_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1259 + n * (1 ±0)` + // Estimated: `1259 + n * (1 ±0)` + // Minimum execution time: 280_680_000 picoseconds. + Weight::from_parts(304_043_474, 1259) + // Standard Error: 29 + .saturating_add(Weight::from_parts(644, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1602 w:1601) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_transfer(r: u32, ) -> Weight { - // Minimum execution time: 295_081 nanoseconds. - Weight::from_ref_time(253_461_581) - // Standard Error: 740_198 - .saturating_add(Weight::from_ref_time(1_369_153_623).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(7)) - .saturating_add(RocksDbWeight::get().reads((80_u64).saturating_mul(r.into()))) - .saturating_add(RocksDbWeight::get().writes(4)) - .saturating_add(RocksDbWeight::get().writes((80_u64).saturating_mul(r.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `1452 + r * (45 ±0)` + // Estimated: `7349 + r * (2520 ±0)` + // Minimum execution time: 274_928_000 picoseconds. + Weight::from_parts(192_111_339, 7349) + // Standard Error: 42_436 + .saturating_add(Weight::from_parts(40_323_660, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 2520).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:801 w:801) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:2 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:2 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:803 w:803) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 800]`. fn seal_call(r: u32, ) -> Weight { - // Minimum execution time: 296_089 nanoseconds. - Weight::from_ref_time(297_193_000) - // Standard Error: 6_571_547 - .saturating_add(Weight::from_ref_time(21_080_280_397).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(7)) - .saturating_add(RocksDbWeight::get().reads((160_u64).saturating_mul(r.into()))) - .saturating_add(RocksDbWeight::get().writes(3)) - .saturating_add(RocksDbWeight::get().writes((160_u64).saturating_mul(r.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `1296 + r * (276 ±0)` + // Estimated: `9481 + r * (2752 ±0)` + // Minimum execution time: 275_293_000 picoseconds. + Weight::from_parts(278_243_000, 9481) + // Standard Error: 119_869 + .saturating_add(Weight::from_parts(245_114_905, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(11_u64)) + .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 2752).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:736 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:736 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:737 w:737) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 800]`. fn seal_delegate_call(r: u32, ) -> Weight { - // Minimum execution time: 296_828 nanoseconds. - Weight::from_ref_time(297_685_000) - // Standard Error: 6_864_880 - .saturating_add(Weight::from_ref_time(20_770_321_946).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().reads((150_u64).saturating_mul(r.into()))) - .saturating_add(RocksDbWeight::get().writes(3)) - .saturating_add(RocksDbWeight::get().writes((75_u64).saturating_mul(r.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:81 w:81) - // Storage: Contracts CodeStorage (r:2 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:82 w:82) + // Proof Size summary in bytes: + // Measured: `0 + r * (572 ±0)` + // Estimated: `6806 + r * (2633 ±3)` + // Minimum execution time: 271_857_000 picoseconds. + Weight::from_parts(278_276_000, 6806) + // Standard Error: 152_056 + .saturating_add(Weight::from_parts(243_744_830, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 2633).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:3 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:2 w:2) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:2 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:2 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:4 w:4) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) /// The range of component `t` is `[0, 1]`. - /// The range of component `c` is `[0, 1024]`. - fn seal_call_per_transfer_clone_kb(t: u32, c: u32, ) -> Weight { - // Minimum execution time: 9_824_671 nanoseconds. - Weight::from_ref_time(8_713_861_450) - // Standard Error: 7_388_925 - .saturating_add(Weight::from_ref_time(1_327_819_806).saturating_mul(t.into())) - // Standard Error: 11_079 - .saturating_add(Weight::from_ref_time(9_789_514).saturating_mul(c.into())) - .saturating_add(RocksDbWeight::get().reads(167)) - .saturating_add(RocksDbWeight::get().reads((81_u64).saturating_mul(t.into()))) - .saturating_add(RocksDbWeight::get().writes(163)) - .saturating_add(RocksDbWeight::get().writes((81_u64).saturating_mul(t.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - // Storage: Contracts Nonce (r:1 w:1) - // Storage: Contracts OwnerInfoOf (r:80 w:80) - /// The range of component `r` is `[0, 20]`. + /// The range of component `c` is `[0, 1048576]`. + fn seal_call_per_transfer_clone_byte(t: u32, c: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1328 + t * (310 ±0)` + // Estimated: `12218 + t * (5260 ±0)` + // Minimum execution time: 463_865_000 picoseconds. + Weight::from_parts(70_396_050, 12218) + // Standard Error: 11_489_598 + .saturating_add(Weight::from_parts(359_195_747, 0).saturating_mul(t.into())) + // Standard Error: 16 + .saturating_add(Weight::from_parts(1_090, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(13_u64)) + .saturating_add(RocksDbWeight::get().reads((2_u64).saturating_mul(t.into()))) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(t.into()))) + .saturating_add(Weight::from_parts(0, 5260).saturating_mul(t.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1602 w:1602) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:801 w:801) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:801 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:801 w:800) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: Contracts Nonce (r:1 w:1) + /// Proof: Contracts Nonce (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:802 w:802) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[1, 800]`. fn seal_instantiate(r: u32, ) -> Weight { - // Minimum execution time: 293_374 nanoseconds. - Weight::from_ref_time(302_067_000) - // Standard Error: 20_137_097 - .saturating_add(Weight::from_ref_time(26_444_812_817).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(8)) - .saturating_add(RocksDbWeight::get().reads((400_u64).saturating_mul(r.into()))) - .saturating_add(RocksDbWeight::get().writes(5)) - .saturating_add(RocksDbWeight::get().writes((400_u64).saturating_mul(r.into()))) - } - // Storage: System Account (r:81 w:81) - // Storage: Contracts ContractInfoOf (r:81 w:81) - // Storage: Contracts CodeStorage (r:2 w:1) - // Storage: Timestamp Now (r:1 w:0) - // Storage: Contracts Nonce (r:1 w:1) - // Storage: Contracts OwnerInfoOf (r:1 w:1) - // Storage: System EventTopics (r:82 w:82) + // Proof Size summary in bytes: + // Measured: `1383 + r * (251 ±0)` + // Estimated: `7207 + r * (5202 ±0)` + // Minimum execution time: 660_947_000 picoseconds. + Weight::from_parts(668_346_000, 7207) + // Standard Error: 357_950 + .saturating_add(Weight::from_parts(397_202_020, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(10_u64)) + .saturating_add(RocksDbWeight::get().reads((6_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + .saturating_add(RocksDbWeight::get().writes((5_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 5202).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:4 w:4) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:2 w:2) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:2 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:2 w:1) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: Contracts Nonce (r:1 w:1) + /// Proof: Contracts Nonce (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:3 w:3) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) /// The range of component `t` is `[0, 1]`. - /// The range of component `i` is `[0, 960]`. - /// The range of component `s` is `[0, 960]`. - fn seal_instantiate_per_transfer_input_salt_kb(t: u32, i: u32, s: u32, ) -> Weight { - // Minimum execution time: 126_456_770 nanoseconds. - Weight::from_ref_time(9_223_715_752) - // Standard Error: 96_040_318 - .saturating_add(Weight::from_ref_time(19_550_519).saturating_mul(t.into())) - // Standard Error: 156_614 - .saturating_add(Weight::from_ref_time(122_958_298).saturating_mul(i.into())) - // Standard Error: 156_614 - .saturating_add(Weight::from_ref_time(123_316_079).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(249)) + /// The range of component `i` is `[0, 983040]`. + /// The range of component `s` is `[0, 983040]`. + fn seal_instantiate_per_transfer_input_salt_byte(t: u32, i: u32, s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `1232 + t * (156 ±0)` + // Estimated: `9662 + t * (2578 ±2)` + // Minimum execution time: 2_419_720_000 picoseconds. + Weight::from_parts(1_328_224_119, 9662) + // Standard Error: 17 + .saturating_add(Weight::from_parts(1_171, 0).saturating_mul(i.into())) + // Standard Error: 17 + .saturating_add(Weight::from_parts(1_263, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(15_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(t.into()))) - .saturating_add(RocksDbWeight::get().writes(247)) + .saturating_add(RocksDbWeight::get().writes(10_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(t.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 1]`. + .saturating_add(Weight::from_parts(0, 2578).saturating_mul(t.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_hash_sha2_256(r: u32, ) -> Weight { - // Minimum execution time: 289_275 nanoseconds. - Weight::from_ref_time(297_803_961) - // Standard Error: 303_405 - .saturating_add(Weight::from_ref_time(40_800_138).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `n` is `[0, 1024]`. - fn seal_hash_sha2_256_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 336_077 nanoseconds. - Weight::from_ref_time(336_846_000) - // Standard Error: 51_610 - .saturating_add(Weight::from_ref_time(322_208_787).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 1]`. + // Proof Size summary in bytes: + // Measured: `856 + r * (8 ±0)` + // Estimated: `6797 + r * (8 ±0)` + // Minimum execution time: 263_620_000 picoseconds. + Weight::from_parts(285_686_431, 6797) + // Standard Error: 605 + .saturating_add(Weight::from_parts(393_863, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_sha2_256_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `864` + // Estimated: `6804` + // Minimum execution time: 271_378_000 picoseconds. + Weight::from_parts(266_737_832, 6804) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_124, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_hash_keccak_256(r: u32, ) -> Weight { - // Minimum execution time: 290_189 nanoseconds. - Weight::from_ref_time(298_454_259) - // Standard Error: 252_946 - .saturating_add(Weight::from_ref_time(55_034_240).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `n` is `[0, 1024]`. - fn seal_hash_keccak_256_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 350_030 nanoseconds. - Weight::from_ref_time(351_356_000) - // Standard Error: 57_422 - .saturating_add(Weight::from_ref_time(256_912_844).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 1]`. + // Proof Size summary in bytes: + // Measured: `858 + r * (8 ±0)` + // Estimated: `6800 + r * (8 ±0)` + // Minimum execution time: 269_277_000 picoseconds. + Weight::from_parts(282_723_951, 6800) + // Standard Error: 577 + .saturating_add(Weight::from_parts(808_522, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_keccak_256_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `866` + // Estimated: `6808` + // Minimum execution time: 254_252_000 picoseconds. + Weight::from_parts(277_589_498, 6808) + // Standard Error: 1 + .saturating_add(Weight::from_parts(3_394, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_hash_blake2_256(r: u32, ) -> Weight { - // Minimum execution time: 288_480 nanoseconds. - Weight::from_ref_time(297_613_263) - // Standard Error: 456_736 - .saturating_add(Weight::from_ref_time(38_100_936).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `n` is `[0, 1024]`. - fn seal_hash_blake2_256_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 321_195 nanoseconds. - Weight::from_ref_time(325_561_000) - // Standard Error: 46_289 - .saturating_add(Weight::from_ref_time(99_536_902).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 1]`. + // Proof Size summary in bytes: + // Measured: `858 + r * (8 ±0)` + // Estimated: `6803 + r * (8 ±0)` + // Minimum execution time: 254_411_000 picoseconds. + Weight::from_parts(283_572_987, 6803) + // Standard Error: 549 + .saturating_add(Weight::from_parts(455_436, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_blake2_256_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `866` + // Estimated: `6812` + // Minimum execution time: 264_371_000 picoseconds. + Weight::from_parts(269_330_603, 6812) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1_249, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_hash_blake2_128(r: u32, ) -> Weight { - // Minimum execution time: 287_659 nanoseconds. - Weight::from_ref_time(295_963_340) - // Standard Error: 433_889 - .saturating_add(Weight::from_ref_time(30_251_859).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `n` is `[0, 1024]`. - fn seal_hash_blake2_128_per_kb(n: u32, ) -> Weight { - // Minimum execution time: 324_298 nanoseconds. - Weight::from_ref_time(325_738_000) - // Standard Error: 48_563 - .saturating_add(Weight::from_ref_time(99_479_838).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 1]`. + // Proof Size summary in bytes: + // Measured: `858 + r * (8 ±0)` + // Estimated: `6804 + r * (8 ±0)` + // Minimum execution time: 257_896_000 picoseconds. + Weight::from_parts(286_738_151, 6804) + // Standard Error: 680 + .saturating_add(Weight::from_parts(459_525, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 8).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 1048576]`. + fn seal_hash_blake2_128_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `866` + // Estimated: `6806` + // Minimum execution time: 272_952_000 picoseconds. + Weight::from_parts(271_516_361, 6806) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1_242, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `n` is `[0, 125697]`. + fn seal_sr25519_verify_per_byte(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `991 + n * (1 ±0)` + // Estimated: `6928 + n * (1 ±0)` + // Minimum execution time: 351_363_000 picoseconds. + Weight::from_parts(356_558_856, 6928) + // Standard Error: 10 + .saturating_add(Weight::from_parts(6_085, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 160]`. + fn seal_sr25519_verify(r: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `801 + r * (112 ±0)` + // Estimated: `6745 + r * (112 ±0)` + // Minimum execution time: 261_688_000 picoseconds. + Weight::from_parts(338_043_015, 6745) + // Standard Error: 13_532 + .saturating_add(Weight::from_parts(56_420_806, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 112).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 160]`. fn seal_ecdsa_recover(r: u32, ) -> Weight { - // Minimum execution time: 294_257 nanoseconds. - Weight::from_ref_time(299_467_620) - // Standard Error: 589_749 - .saturating_add(Weight::from_ref_time(3_015_389_579).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 1]`. + // Proof Size summary in bytes: + // Measured: `901 + r * (76 ±0)` + // Estimated: `6795 + r * (77 ±0)` + // Minimum execution time: 267_401_000 picoseconds. + Weight::from_parts(345_773_771, 6795) + // Standard Error: 14_486 + .saturating_add(Weight::from_parts(46_180_739, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 77).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 160]`. fn seal_ecdsa_to_eth_address(r: u32, ) -> Weight { - // Minimum execution time: 289_480 nanoseconds. - Weight::from_ref_time(298_762_412) - // Standard Error: 397_004 - .saturating_add(Weight::from_ref_time(743_937_087).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - // Storage: Contracts OwnerInfoOf (r:16 w:16) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `871 + r * (42 ±0)` + // Estimated: `6810 + r * (42 ±0)` + // Minimum execution time: 277_890_000 picoseconds. + Weight::from_parts(319_211_194, 6810) + // Standard Error: 9_132 + .saturating_add(Weight::from_parts(12_128_696, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 42).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1536 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1536 w:1536) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:1538 w:1538) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_set_code_hash(r: u32, ) -> Weight { - // Minimum execution time: 293_494 nanoseconds. - Weight::from_ref_time(297_756_000) - // Standard Error: 2_731_227 - .saturating_add(Weight::from_ref_time(1_387_380_436).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().reads((225_u64).saturating_mul(r.into()))) - .saturating_add(RocksDbWeight::get().writes(3)) - .saturating_add(RocksDbWeight::get().writes((150_u64).saturating_mul(r.into()))) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `0 + r * (961 ±0)` + // Estimated: `6801 + r * (3087 ±10)` + // Minimum execution time: 259_692_000 picoseconds. + Weight::from_parts(278_327_000, 6801) + // Standard Error: 60_024 + .saturating_add(Weight::from_parts(25_758_805, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 3087).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_reentrance_count(r: u32, ) -> Weight { - // Minimum execution time: 295_339 nanoseconds. - Weight::from_ref_time(301_577_907) - // Standard Error: 23_836 - .saturating_add(Weight::from_ref_time(10_876_508).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `852 + r * (3 ±0)` + // Estimated: `6802 + r * (3 ±0)` + // Minimum execution time: 258_907_000 picoseconds. + Weight::from_parts(285_755_890, 6802) + // Standard Error: 378 + .saturating_add(Weight::from_parts(179_649, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 3).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_account_reentrance_count(r: u32, ) -> Weight { - // Minimum execution time: 297_096 nanoseconds. - Weight::from_ref_time(336_319_823) - // Standard Error: 128_941 - .saturating_add(Weight::from_ref_time(25_211_374).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: System Account (r:1 w:0) - // Storage: Contracts ContractInfoOf (r:1 w:1) - // Storage: Contracts CodeStorage (r:1 w:0) - // Storage: Timestamp Now (r:1 w:0) - // Storage: System EventTopics (r:2 w:2) - // Storage: Contracts Nonce (r:1 w:1) - /// The range of component `r` is `[0, 20]`. + // Proof Size summary in bytes: + // Measured: `2092 + r * (39 ±0)` + // Estimated: `7919 + r * (40 ±0)` + // Minimum execution time: 260_415_000 picoseconds. + Weight::from_parts(363_871_048, 7919) + // Standard Error: 2_010 + .saturating_add(Weight::from_parts(317_607, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 40).saturating_mul(r.into())) + } + /// Storage: Contracts MigrationInProgress (r:1 w:0) + /// Proof: Contracts MigrationInProgress (max_values: Some(1), max_size: Some(1026), added: 1521, mode: Measured) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: Measured) + /// Storage: Contracts ContractInfoOf (r:1 w:1) + /// Proof: Contracts ContractInfoOf (max_values: None, max_size: Some(290), added: 2765, mode: Measured) + /// Storage: Contracts PristineCode (r:1 w:0) + /// Proof: Contracts PristineCode (max_values: None, max_size: Some(125988), added: 128463, mode: Measured) + /// Storage: Contracts CodeInfoOf (r:1 w:0) + /// Proof: Contracts CodeInfoOf (max_values: None, max_size: Some(89), added: 2564, mode: Measured) + /// Storage: Timestamp Now (r:1 w:0) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: Contracts Nonce (r:1 w:1) + /// Proof: Contracts Nonce (max_values: Some(1), max_size: Some(8), added: 503, mode: Measured) + /// Storage: System EventTopics (r:2 w:2) + /// Proof Skipped: System EventTopics (max_values: None, max_size: None, mode: Measured) + /// The range of component `r` is `[0, 1600]`. fn seal_instantiation_nonce(r: u32, ) -> Weight { - // Minimum execution time: 293_819 nanoseconds. - Weight::from_ref_time(302_765_659) - // Standard Error: 28_518 - .saturating_add(Weight::from_ref_time(9_325_552).saturating_mul(r.into())) - .saturating_add(RocksDbWeight::get().reads(7)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - /// The range of component `r` is `[0, 50]`. + // Proof Size summary in bytes: + // Measured: `855 + r * (3 ±0)` + // Estimated: `6802 + r * (3 ±0)` + // Minimum execution time: 257_725_000 picoseconds. + Weight::from_parts(283_441_372, 6802) + // Standard Error: 371 + .saturating_add(Weight::from_parts(157_674, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(Weight::from_parts(0, 3).saturating_mul(r.into())) + } + /// The range of component `r` is `[0, 5000]`. fn instr_i64const(r: u32, ) -> Weight { - // Minimum execution time: 805 nanoseconds. - Weight::from_ref_time(1_002_066) - // Standard Error: 609 - .saturating_add(Weight::from_ref_time(346_608).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64load(r: u32, ) -> Weight { - // Minimum execution time: 917 nanoseconds. - Weight::from_ref_time(1_247_710) - // Standard Error: 1_069 - .saturating_add(Weight::from_ref_time(984_608).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64store(r: u32, ) -> Weight { - // Minimum execution time: 917 nanoseconds. - Weight::from_ref_time(1_244_315) - // Standard Error: 524 - .saturating_add(Weight::from_ref_time(882_167).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_select(r: u32, ) -> Weight { - // Minimum execution time: 791 nanoseconds. - Weight::from_ref_time(1_119_053) - // Standard Error: 379 - .saturating_add(Weight::from_ref_time(956_169).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_if(r: u32, ) -> Weight { - // Minimum execution time: 782 nanoseconds. - Weight::from_ref_time(773_373) - // Standard Error: 463 - .saturating_add(Weight::from_ref_time(1_299_306).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_br(r: u32, ) -> Weight { - // Minimum execution time: 743 nanoseconds. - Weight::from_ref_time(1_273_896) - // Standard Error: 925 - .saturating_add(Weight::from_ref_time(527_015).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_br_if(r: u32, ) -> Weight { - // Minimum execution time: 761 nanoseconds. - Weight::from_ref_time(929_565) - // Standard Error: 1_275 - .saturating_add(Weight::from_ref_time(805_592).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_br_table(r: u32, ) -> Weight { - // Minimum execution time: 813 nanoseconds. - Weight::from_ref_time(703_802) - // Standard Error: 1_665 - .saturating_add(Weight::from_ref_time(1_074_321).saturating_mul(r.into())) - } - /// The range of component `e` is `[1, 256]`. - fn instr_br_table_per_entry(e: u32, ) -> Weight { - // Minimum execution time: 2_347 nanoseconds. - Weight::from_ref_time(2_752_638) - // Standard Error: 69 - .saturating_add(Weight::from_ref_time(4_771).saturating_mul(e.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_call(r: u32, ) -> Weight { - // Minimum execution time: 826 nanoseconds. - Weight::from_ref_time(1_784_596) - // Standard Error: 1_646 - .saturating_add(Weight::from_ref_time(2_189_993).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_call_indirect(r: u32, ) -> Weight { - // Minimum execution time: 926 nanoseconds. - Weight::from_ref_time(1_898_358) - // Standard Error: 2_678 - .saturating_add(Weight::from_ref_time(2_811_379).saturating_mul(r.into())) - } - /// The range of component `p` is `[0, 128]`. - fn instr_call_indirect_per_param(p: u32, ) -> Weight { - // Minimum execution time: 4_428 nanoseconds. - Weight::from_ref_time(5_257_445) - // Standard Error: 306 - .saturating_add(Weight::from_ref_time(178_882).saturating_mul(p.into())) - } - /// The range of component `l` is `[0, 1024]`. - fn instr_call_per_local(l: u32, ) -> Weight { - // Minimum execution time: 2_945 nanoseconds. - Weight::from_ref_time(4_240_619) - // Standard Error: 39 - .saturating_add(Weight::from_ref_time(46_640).saturating_mul(l.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_local_get(r: u32, ) -> Weight { - // Minimum execution time: 2_204 nanoseconds. - Weight::from_ref_time(2_430_272) - // Standard Error: 270 - .saturating_add(Weight::from_ref_time(365_718).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_local_set(r: u32, ) -> Weight { - // Minimum execution time: 2_117 nanoseconds. - Weight::from_ref_time(2_429_354) - // Standard Error: 240 - .saturating_add(Weight::from_ref_time(381_830).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_local_tee(r: u32, ) -> Weight { - // Minimum execution time: 2_123 nanoseconds. - Weight::from_ref_time(2_460_016) - // Standard Error: 262 - .saturating_add(Weight::from_ref_time(526_554).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_global_get(r: u32, ) -> Weight { - // Minimum execution time: 892 nanoseconds. - Weight::from_ref_time(1_234_618) - // Standard Error: 485 - .saturating_add(Weight::from_ref_time(813_721).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_global_set(r: u32, ) -> Weight { - // Minimum execution time: 901 nanoseconds. - Weight::from_ref_time(1_182_838) - // Standard Error: 482 - .saturating_add(Weight::from_ref_time(831_308).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_memory_current(r: u32, ) -> Weight { - // Minimum execution time: 929 nanoseconds. - Weight::from_ref_time(1_221_388) - // Standard Error: 434 - .saturating_add(Weight::from_ref_time(693_301).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 1]`. - fn instr_memory_grow(r: u32, ) -> Weight { - // Minimum execution time: 808 nanoseconds. - Weight::from_ref_time(906_944) - // Standard Error: 6_164 - .saturating_add(Weight::from_ref_time(187_445_255).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64clz(r: u32, ) -> Weight { - // Minimum execution time: 807 nanoseconds. - Weight::from_ref_time(1_064_776) - // Standard Error: 287 - .saturating_add(Weight::from_ref_time(507_278).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64ctz(r: u32, ) -> Weight { - // Minimum execution time: 776 nanoseconds. - Weight::from_ref_time(1_033_205) - // Standard Error: 443 - .saturating_add(Weight::from_ref_time(512_445).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64popcnt(r: u32, ) -> Weight { - // Minimum execution time: 909 nanoseconds. - Weight::from_ref_time(1_089_083) - // Standard Error: 335 - .saturating_add(Weight::from_ref_time(510_392).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64eqz(r: u32, ) -> Weight { - // Minimum execution time: 791 nanoseconds. - Weight::from_ref_time(1_063_118) - // Standard Error: 210 - .saturating_add(Weight::from_ref_time(522_817).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64extendsi32(r: u32, ) -> Weight { - // Minimum execution time: 789 nanoseconds. - Weight::from_ref_time(1_056_006) - // Standard Error: 240 - .saturating_add(Weight::from_ref_time(505_198).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64extendui32(r: u32, ) -> Weight { - // Minimum execution time: 804 nanoseconds. - Weight::from_ref_time(1_087_882) - // Standard Error: 234 - .saturating_add(Weight::from_ref_time(503_830).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i32wrapi64(r: u32, ) -> Weight { - // Minimum execution time: 756 nanoseconds. - Weight::from_ref_time(1_050_994) - // Standard Error: 172 - .saturating_add(Weight::from_ref_time(503_343).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64eq(r: u32, ) -> Weight { - // Minimum execution time: 775 nanoseconds. - Weight::from_ref_time(1_061_228) - // Standard Error: 232 - .saturating_add(Weight::from_ref_time(730_751).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64ne(r: u32, ) -> Weight { - // Minimum execution time: 809 nanoseconds. - Weight::from_ref_time(1_080_957) - // Standard Error: 320 - .saturating_add(Weight::from_ref_time(732_246).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64lts(r: u32, ) -> Weight { - // Minimum execution time: 808 nanoseconds. - Weight::from_ref_time(1_055_752) - // Standard Error: 496 - .saturating_add(Weight::from_ref_time(740_084).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64ltu(r: u32, ) -> Weight { - // Minimum execution time: 792 nanoseconds. - Weight::from_ref_time(1_111_972) - // Standard Error: 270 - .saturating_add(Weight::from_ref_time(741_581).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64gts(r: u32, ) -> Weight { - // Minimum execution time: 862 nanoseconds. - Weight::from_ref_time(1_111_246) - // Standard Error: 263 - .saturating_add(Weight::from_ref_time(746_026).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64gtu(r: u32, ) -> Weight { - // Minimum execution time: 810 nanoseconds. - Weight::from_ref_time(1_094_933) - // Standard Error: 271 - .saturating_add(Weight::from_ref_time(743_645).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64les(r: u32, ) -> Weight { - // Minimum execution time: 769 nanoseconds. - Weight::from_ref_time(819_933) - // Standard Error: 4_528 - .saturating_add(Weight::from_ref_time(751_599).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64leu(r: u32, ) -> Weight { - // Minimum execution time: 797 nanoseconds. - Weight::from_ref_time(1_107_352) - // Standard Error: 344 - .saturating_add(Weight::from_ref_time(738_684).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64ges(r: u32, ) -> Weight { - // Minimum execution time: 784 nanoseconds. - Weight::from_ref_time(1_068_487) - // Standard Error: 3_796 - .saturating_add(Weight::from_ref_time(741_207).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64geu(r: u32, ) -> Weight { - // Minimum execution time: 813 nanoseconds. - Weight::from_ref_time(1_067_278) - // Standard Error: 459 - .saturating_add(Weight::from_ref_time(737_841).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64add(r: u32, ) -> Weight { - // Minimum execution time: 798 nanoseconds. - Weight::from_ref_time(1_100_254) - // Standard Error: 297 - .saturating_add(Weight::from_ref_time(717_622).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64sub(r: u32, ) -> Weight { - // Minimum execution time: 778 nanoseconds. - Weight::from_ref_time(1_063_748) - // Standard Error: 378 - .saturating_add(Weight::from_ref_time(713_577).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64mul(r: u32, ) -> Weight { - // Minimum execution time: 780 nanoseconds. - Weight::from_ref_time(1_088_755) - // Standard Error: 316 - .saturating_add(Weight::from_ref_time(714_127).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64divs(r: u32, ) -> Weight { - // Minimum execution time: 776 nanoseconds. - Weight::from_ref_time(1_062_591) - // Standard Error: 610 - .saturating_add(Weight::from_ref_time(1_355_430).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64divu(r: u32, ) -> Weight { - // Minimum execution time: 801 nanoseconds. - Weight::from_ref_time(1_092_395) - // Standard Error: 423 - .saturating_add(Weight::from_ref_time(1_282_356).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64rems(r: u32, ) -> Weight { - // Minimum execution time: 822 nanoseconds. - Weight::from_ref_time(1_083_079) - // Standard Error: 562 - .saturating_add(Weight::from_ref_time(1_398_522).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64remu(r: u32, ) -> Weight { - // Minimum execution time: 798 nanoseconds. - Weight::from_ref_time(1_088_997) - // Standard Error: 401 - .saturating_add(Weight::from_ref_time(1_283_442).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64and(r: u32, ) -> Weight { - // Minimum execution time: 815 nanoseconds. - Weight::from_ref_time(1_081_903) - // Standard Error: 264 - .saturating_add(Weight::from_ref_time(718_069).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64or(r: u32, ) -> Weight { - // Minimum execution time: 813 nanoseconds. - Weight::from_ref_time(1_091_426) - // Standard Error: 296 - .saturating_add(Weight::from_ref_time(717_894).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64xor(r: u32, ) -> Weight { - // Minimum execution time: 794 nanoseconds. - Weight::from_ref_time(1_040_144) - // Standard Error: 400 - .saturating_add(Weight::from_ref_time(719_886).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64shl(r: u32, ) -> Weight { - // Minimum execution time: 774 nanoseconds. - Weight::from_ref_time(1_092_466) - // Standard Error: 4_173 - .saturating_add(Weight::from_ref_time(739_873).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64shrs(r: u32, ) -> Weight { - // Minimum execution time: 793 nanoseconds. - Weight::from_ref_time(1_105_611) - // Standard Error: 394 - .saturating_add(Weight::from_ref_time(735_358).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64shru(r: u32, ) -> Weight { - // Minimum execution time: 805 nanoseconds. - Weight::from_ref_time(1_130_462) - // Standard Error: 327 - .saturating_add(Weight::from_ref_time(734_198).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64rotl(r: u32, ) -> Weight { - // Minimum execution time: 831 nanoseconds. - Weight::from_ref_time(1_118_894) - // Standard Error: 389 - .saturating_add(Weight::from_ref_time(733_834).saturating_mul(r.into())) - } - /// The range of component `r` is `[0, 50]`. - fn instr_i64rotr(r: u32, ) -> Weight { - // Minimum execution time: 772 nanoseconds. - Weight::from_ref_time(1_123_733) - // Standard Error: 337 - .saturating_add(Weight::from_ref_time(733_923).saturating_mul(r.into())) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_635_000 picoseconds. + Weight::from_parts(2_990_110, 0) + // Standard Error: 31 + .saturating_add(Weight::from_parts(10_213, 0).saturating_mul(r.into())) } } diff --git a/frame/conviction-voting/Cargo.toml b/frame/conviction-voting/Cargo.toml index d112c54fb64a..adaf10d55f99 100644 --- a/frame/conviction-voting/Cargo.toml +++ b/frame/conviction-voting/Cargo.toml @@ -14,23 +14,23 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] assert_matches = "1.3.0" -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", "max-encoded-len", ] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", features = ["derive"], optional = true } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"], optional = true } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-scheduler = { version = "4.0.0-dev", path = "../scheduler" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/conviction-voting/src/benchmarking.rs b/frame/conviction-voting/src/benchmarking.rs index 117bb7fe2298..8701ed7ebb07 100644 --- a/frame/conviction-voting/src/benchmarking.rs +++ b/frame/conviction-voting/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,10 +20,14 @@ use super::*; use assert_matches::assert_matches; -use frame_benchmarking::{account, benchmarks_instance_pallet, whitelist_account}; +use frame_benchmarking::v1::{account, benchmarks_instance_pallet, whitelist_account}; use frame_support::{ dispatch::RawOrigin, - traits::{fungible, Currency, Get}, + traits::{ + fungible, + tokens::{Fortitude::Polite, Preservation::Expendable}, + Currency, Get, + }, }; use sp_runtime::traits::Bounded; use sp_std::collections::btree_map::BTreeMap; @@ -257,13 +261,13 @@ benchmarks_instance_pallet! { } } - let orig_usable = >::reducible_balance(&caller, false); + let orig_usable = >::reducible_balance(&caller, Expendable, Polite); let polls = &all_polls[&class]; // Vote big on the class with the most ongoing votes of them to bump the lock and make it // hard to recompute when removed. ConvictionVoting::::vote(RawOrigin::Signed(caller.clone()).into(), polls[0], big_account_vote)?; - let now_usable = >::reducible_balance(&caller, false); + let now_usable = >::reducible_balance(&caller, Expendable, Polite); assert_eq!(orig_usable - now_usable, 100u32.into()); // Remove the vote @@ -272,7 +276,7 @@ benchmarks_instance_pallet! { // We can now unlock on `class` from 200 to 100... }: _(RawOrigin::Signed(caller.clone()), class, caller_lookup) verify { - assert_eq!(orig_usable, >::reducible_balance(&caller, false)); + assert_eq!(orig_usable, >::reducible_balance(&caller, Expendable, Polite)); } impl_benchmark_test_suite!( diff --git a/frame/conviction-voting/src/conviction.rs b/frame/conviction-voting/src/conviction.rs index 1feff35b19fc..b5c9a3a705f6 100644 --- a/frame/conviction-voting/src/conviction.rs +++ b/frame/conviction-voting/src/conviction.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/conviction-voting/src/lib.rs b/frame/conviction-voting/src/lib.rs index 141e9690fa29..9c2993fc5cae 100644 --- a/frame/conviction-voting/src/lib.rs +++ b/frame/conviction-voting/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,6 +35,7 @@ use frame_support::{ ReservableCurrency, WithdrawReasons, }, }; +use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::{ traits::{AtLeast32BitUnsigned, Saturating, StaticLookup, Zero}, ArithmeticError, Perbill, @@ -68,16 +69,13 @@ type BalanceOf = type VotingOf = Voting< BalanceOf, ::AccountId, - ::BlockNumber, + BlockNumberFor, PollIndexOf, >::MaxVotes, >; #[allow(dead_code)] -type DelegatingOf = Delegating< - BalanceOf, - ::AccountId, - ::BlockNumber, ->; +type DelegatingOf = + Delegating, ::AccountId, BlockNumberFor>; pub type TallyOf = Tally, >::MaxTurnout>; pub type VotesOf = BalanceOf; type PollIndexOf = <>::Polls as Polling>>::Index; @@ -92,7 +90,6 @@ pub mod pallet { use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::config] @@ -104,14 +101,14 @@ pub mod pallet { type WeightInfo: WeightInfo; /// Currency type with which voting happens. type Currency: ReservableCurrency - + LockableCurrency + + LockableCurrency> + fungible::Inspect; /// The implementation of the logic which conducts polls. type Polls: Polling< TallyOf, Votes = BalanceOf, - Moment = Self::BlockNumber, + Moment = BlockNumberFor, >; /// The maximum amount of tokens which may be used for voting. May just be @@ -131,7 +128,7 @@ pub mod pallet { /// It should be no shorter than enactment period to ensure that in the case of an approval, /// those successful voters are locked into the consequences that their votes entail. #[pallet::constant] - type VoteLockingPeriod: Get; + type VoteLockingPeriod: Get>; } /// All voting for a particular voter in a particular voting class. We store the balance for the @@ -389,7 +386,10 @@ impl, I: 'static> Pallet { poll_index: PollIndexOf, vote: AccountVote>, ) -> DispatchResult { - ensure!(vote.balance() <= T::Currency::free_balance(who), Error::::InsufficientFunds); + ensure!( + vote.balance() <= T::Currency::total_balance(who), + Error::::InsufficientFunds + ); T::Polls::try_access_poll(poll_index, |poll_status| { let (tally, class) = poll_status.ensure_ongoing().ok_or(Error::::NotOngoing)?; VotingFor::::try_mutate(who, &class, |voting| { @@ -549,7 +549,7 @@ impl, I: 'static> Pallet { ) -> Result { ensure!(who != target, Error::::Nonsense); T::Polls::classes().binary_search(&class).map_err(|_| Error::::BadClass)?; - ensure!(balance <= T::Currency::free_balance(&who), Error::::InsufficientFunds); + ensure!(balance <= T::Currency::total_balance(&who), Error::::InsufficientFunds); let votes = VotingFor::::try_mutate(&who, &class, |voting| -> Result { let old = sp_std::mem::replace( @@ -637,7 +637,12 @@ impl, I: 'static> Pallet { }, } }); - T::Currency::extend_lock(CONVICTION_VOTING_ID, who, amount, WithdrawReasons::TRANSFER); + T::Currency::extend_lock( + CONVICTION_VOTING_ID, + who, + amount, + WithdrawReasons::except(WithdrawReasons::RESERVE), + ); } /// Rejig the lock on an account. It will never get more stringent (since that would indicate @@ -667,7 +672,7 @@ impl, I: 'static> Pallet { CONVICTION_VOTING_ID, who, lock_needed, - WithdrawReasons::TRANSFER, + WithdrawReasons::except(WithdrawReasons::RESERVE), ); } } diff --git a/frame/conviction-voting/src/tests.rs b/frame/conviction-voting/src/tests.rs index 9caa451e0dae..656112deebfb 100644 --- a/frame/conviction-voting/src/tests.rs +++ b/frame/conviction-voting/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,23 +25,19 @@ use frame_support::{ }; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; use super::*; use crate as pallet_conviction_voting; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Voting: pallet_conviction_voting::{Pallet, Call, Storage, Event}, } @@ -51,7 +47,7 @@ frame_support::construct_runtime!( pub struct BaseFilter; impl Contains for BaseFilter { fn contains(call: &RuntimeCall) -> bool { - !matches!(call, &RuntimeCall::Balances(pallet_balances::Call::set_balance { .. })) + !matches!(call, &RuntimeCall::Balances(pallet_balances::Call::force_set_balance { .. })) } } @@ -61,14 +57,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -92,6 +87,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } #[derive(Clone, PartialEq, Eq, Debug)] @@ -192,7 +191,7 @@ impl Config for Test { } pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], } diff --git a/frame/conviction-voting/src/types.rs b/frame/conviction-voting/src/types.rs index aa2406eef711..2c45b54485bd 100644 --- a/frame/conviction-voting/src/types.rs +++ b/frame/conviction-voting/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/conviction-voting/src/vote.rs b/frame/conviction-voting/src/vote.rs index 1fed70536d24..5ae08f0de65f 100644 --- a/frame/conviction-voting/src/vote.rs +++ b/frame/conviction-voting/src/vote.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/conviction-voting/src/weights.rs b/frame/conviction-voting/src/weights.rs index e50842449f88..225f5c2cadd6 100644 --- a/frame/conviction-voting/src/weights.rs +++ b/frame/conviction-voting/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_conviction_voting //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_conviction_voting +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_conviction_voting. pub trait WeightInfo { @@ -59,164 +64,278 @@ pub trait WeightInfo { /// Weights for pallet_conviction_voting using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: ConvictionVoting VotingFor (r:1 w:1) - // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: ConvictionVoting VotingFor (r:1 w:1) + /// Proof: ConvictionVoting VotingFor (max_values: None, max_size: Some(27241), added: 29716, mode: MaxEncodedLen) + /// Storage: ConvictionVoting ClassLocksFor (r:1 w:1) + /// Proof: ConvictionVoting ClassLocksFor (max_values: None, max_size: Some(59), added: 2534, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn vote_new() -> Weight { - // Minimum execution time: 131_633 nanoseconds. - Weight::from_ref_time(132_742_000 as u64) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(6 as u64)) + // Proof Size summary in bytes: + // Measured: `13074` + // Estimated: `219984` + // Minimum execution time: 112_936_000 picoseconds. + Weight::from_parts(116_972_000, 219984) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: ConvictionVoting VotingFor (r:1 w:1) - // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: ConvictionVoting VotingFor (r:1 w:1) + /// Proof: ConvictionVoting VotingFor (max_values: None, max_size: Some(27241), added: 29716, mode: MaxEncodedLen) + /// Storage: ConvictionVoting ClassLocksFor (r:1 w:1) + /// Proof: ConvictionVoting ClassLocksFor (max_values: None, max_size: Some(59), added: 2534, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn vote_existing() -> Weight { - // Minimum execution time: 176_240 nanoseconds. - Weight::from_ref_time(183_274_000 as u64) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(6 as u64)) + // Proof Size summary in bytes: + // Measured: `20216` + // Estimated: `219984` + // Minimum execution time: 291_971_000 picoseconds. + Weight::from_parts(301_738_000, 219984) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) } - // Storage: ConvictionVoting VotingFor (r:1 w:1) - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + /// Storage: ConvictionVoting VotingFor (r:1 w:1) + /// Proof: ConvictionVoting VotingFor (max_values: None, max_size: Some(27241), added: 29716, mode: MaxEncodedLen) + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn remove_vote() -> Weight { - // Minimum execution time: 158_880 nanoseconds. - Weight::from_ref_time(164_648_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `19968` + // Estimated: `219984` + // Minimum execution time: 262_582_000 picoseconds. + Weight::from_parts(270_955_000, 219984) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } - // Storage: ConvictionVoting VotingFor (r:1 w:1) - // Storage: Referenda ReferendumInfoFor (r:1 w:0) + /// Storage: ConvictionVoting VotingFor (r:1 w:1) + /// Proof: ConvictionVoting VotingFor (max_values: None, max_size: Some(27241), added: 29716, mode: MaxEncodedLen) + /// Storage: Referenda ReferendumInfoFor (r:1 w:0) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) fn remove_other_vote() -> Weight { - // Minimum execution time: 60_330 nanoseconds. - Weight::from_ref_time(61_588_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `12675` + // Estimated: `30706` + // Minimum execution time: 52_909_000 picoseconds. + Weight::from_parts(56_365_000, 30706) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: ConvictionVoting VotingFor (r:2 w:2) - // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + /// Storage: ConvictionVoting VotingFor (r:2 w:2) + /// Proof: ConvictionVoting VotingFor (max_values: None, max_size: Some(27241), added: 29716, mode: MaxEncodedLen) + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: ConvictionVoting ClassLocksFor (r:1 w:1) + /// Proof: ConvictionVoting ClassLocksFor (max_values: None, max_size: Some(59), added: 2534, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `r` is `[0, 1]`. fn delegate(r: u32, ) -> Weight { - // Minimum execution time: 63_088 nanoseconds. - Weight::from_ref_time(67_803_536 as u64) - // Standard Error: 197_102 - .saturating_add(Weight::from_ref_time(31_557_563 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().reads((3 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(r as u64))) + // Proof Size summary in bytes: + // Measured: `240 + r * (1627 ±0)` + // Estimated: `109992 + r * (109992 ±0)` + // Minimum execution time: 54_640_000 picoseconds. + Weight::from_parts(57_185_281, 109992) + // Standard Error: 193_362 + .saturating_add(Weight::from_parts(44_897_418, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 109992).saturating_mul(r.into())) } - // Storage: ConvictionVoting VotingFor (r:2 w:2) - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + /// Storage: ConvictionVoting VotingFor (r:2 w:2) + /// Proof: ConvictionVoting VotingFor (max_values: None, max_size: Some(27241), added: 29716, mode: MaxEncodedLen) + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) /// The range of component `r` is `[0, 1]`. fn undelegate(r: u32, ) -> Weight { - // Minimum execution time: 45_150 nanoseconds. - Weight::from_ref_time(51_547_530 as u64) - // Standard Error: 771_127 - .saturating_add(Weight::from_ref_time(26_927_969 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().reads((3 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(r as u64))) + // Proof Size summary in bytes: + // Measured: `406 + r * (1376 ±0)` + // Estimated: `109992 + r * (109992 ±0)` + // Minimum execution time: 26_514_000 picoseconds. + Weight::from_parts(28_083_732, 109992) + // Standard Error: 104_905 + .saturating_add(Weight::from_parts(40_722_467, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 109992).saturating_mul(r.into())) } - // Storage: ConvictionVoting VotingFor (r:1 w:1) - // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: ConvictionVoting VotingFor (r:1 w:1) + /// Proof: ConvictionVoting VotingFor (max_values: None, max_size: Some(27241), added: 29716, mode: MaxEncodedLen) + /// Storage: ConvictionVoting ClassLocksFor (r:1 w:1) + /// Proof: ConvictionVoting ClassLocksFor (max_values: None, max_size: Some(59), added: 2534, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn unlock() -> Weight { - // Minimum execution time: 75_067 nanoseconds. - Weight::from_ref_time(76_888_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `11734` + // Estimated: `30706` + // Minimum execution time: 71_140_000 picoseconds. + Weight::from_parts(77_388_000, 30706) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: ConvictionVoting VotingFor (r:1 w:1) - // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: ConvictionVoting VotingFor (r:1 w:1) + /// Proof: ConvictionVoting VotingFor (max_values: None, max_size: Some(27241), added: 29716, mode: MaxEncodedLen) + /// Storage: ConvictionVoting ClassLocksFor (r:1 w:1) + /// Proof: ConvictionVoting ClassLocksFor (max_values: None, max_size: Some(59), added: 2534, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn vote_new() -> Weight { - // Minimum execution time: 131_633 nanoseconds. - Weight::from_ref_time(132_742_000 as u64) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) + // Proof Size summary in bytes: + // Measured: `13074` + // Estimated: `219984` + // Minimum execution time: 112_936_000 picoseconds. + Weight::from_parts(116_972_000, 219984) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: ConvictionVoting VotingFor (r:1 w:1) - // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: ConvictionVoting VotingFor (r:1 w:1) + /// Proof: ConvictionVoting VotingFor (max_values: None, max_size: Some(27241), added: 29716, mode: MaxEncodedLen) + /// Storage: ConvictionVoting ClassLocksFor (r:1 w:1) + /// Proof: ConvictionVoting ClassLocksFor (max_values: None, max_size: Some(59), added: 2534, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn vote_existing() -> Weight { - // Minimum execution time: 176_240 nanoseconds. - Weight::from_ref_time(183_274_000 as u64) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) + // Proof Size summary in bytes: + // Measured: `20216` + // Estimated: `219984` + // Minimum execution time: 291_971_000 picoseconds. + Weight::from_parts(301_738_000, 219984) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } - // Storage: ConvictionVoting VotingFor (r:1 w:1) - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + /// Storage: ConvictionVoting VotingFor (r:1 w:1) + /// Proof: ConvictionVoting VotingFor (max_values: None, max_size: Some(27241), added: 29716, mode: MaxEncodedLen) + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn remove_vote() -> Weight { - // Minimum execution time: 158_880 nanoseconds. - Weight::from_ref_time(164_648_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `19968` + // Estimated: `219984` + // Minimum execution time: 262_582_000 picoseconds. + Weight::from_parts(270_955_000, 219984) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } - // Storage: ConvictionVoting VotingFor (r:1 w:1) - // Storage: Referenda ReferendumInfoFor (r:1 w:0) + /// Storage: ConvictionVoting VotingFor (r:1 w:1) + /// Proof: ConvictionVoting VotingFor (max_values: None, max_size: Some(27241), added: 29716, mode: MaxEncodedLen) + /// Storage: Referenda ReferendumInfoFor (r:1 w:0) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) fn remove_other_vote() -> Weight { - // Minimum execution time: 60_330 nanoseconds. - Weight::from_ref_time(61_588_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `12675` + // Estimated: `30706` + // Minimum execution time: 52_909_000 picoseconds. + Weight::from_parts(56_365_000, 30706) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: ConvictionVoting VotingFor (r:2 w:2) - // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + /// Storage: ConvictionVoting VotingFor (r:2 w:2) + /// Proof: ConvictionVoting VotingFor (max_values: None, max_size: Some(27241), added: 29716, mode: MaxEncodedLen) + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: ConvictionVoting ClassLocksFor (r:1 w:1) + /// Proof: ConvictionVoting ClassLocksFor (max_values: None, max_size: Some(59), added: 2534, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `r` is `[0, 1]`. fn delegate(r: u32, ) -> Weight { - // Minimum execution time: 63_088 nanoseconds. - Weight::from_ref_time(67_803_536 as u64) - // Standard Error: 197_102 - .saturating_add(Weight::from_ref_time(31_557_563 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().reads((3 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(r as u64))) + // Proof Size summary in bytes: + // Measured: `240 + r * (1627 ±0)` + // Estimated: `109992 + r * (109992 ±0)` + // Minimum execution time: 54_640_000 picoseconds. + Weight::from_parts(57_185_281, 109992) + // Standard Error: 193_362 + .saturating_add(Weight::from_parts(44_897_418, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 109992).saturating_mul(r.into())) } - // Storage: ConvictionVoting VotingFor (r:2 w:2) - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + /// Storage: ConvictionVoting VotingFor (r:2 w:2) + /// Proof: ConvictionVoting VotingFor (max_values: None, max_size: Some(27241), added: 29716, mode: MaxEncodedLen) + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) /// The range of component `r` is `[0, 1]`. fn undelegate(r: u32, ) -> Weight { - // Minimum execution time: 45_150 nanoseconds. - Weight::from_ref_time(51_547_530 as u64) - // Standard Error: 771_127 - .saturating_add(Weight::from_ref_time(26_927_969 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().reads((3 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(r as u64))) + // Proof Size summary in bytes: + // Measured: `406 + r * (1376 ±0)` + // Estimated: `109992 + r * (109992 ±0)` + // Minimum execution time: 26_514_000 picoseconds. + Weight::from_parts(28_083_732, 109992) + // Standard Error: 104_905 + .saturating_add(Weight::from_parts(40_722_467, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 109992).saturating_mul(r.into())) } - // Storage: ConvictionVoting VotingFor (r:1 w:1) - // Storage: ConvictionVoting ClassLocksFor (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: ConvictionVoting VotingFor (r:1 w:1) + /// Proof: ConvictionVoting VotingFor (max_values: None, max_size: Some(27241), added: 29716, mode: MaxEncodedLen) + /// Storage: ConvictionVoting ClassLocksFor (r:1 w:1) + /// Proof: ConvictionVoting ClassLocksFor (max_values: None, max_size: Some(59), added: 2534, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn unlock() -> Weight { - // Minimum execution time: 75_067 nanoseconds. - Weight::from_ref_time(76_888_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `11734` + // Estimated: `30706` + // Minimum execution time: 71_140_000 picoseconds. + Weight::from_parts(77_388_000, 30706) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } } diff --git a/frame/core-fellowship/Cargo.toml b/frame/core-fellowship/Cargo.toml new file mode 100644 index 000000000000..b971936b4620 --- /dev/null +++ b/frame/core-fellowship/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "pallet-core-fellowship" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Logic as per the description of The Fellowship for core Polkadot technology" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +log = { version = "0.4.16", default-features = false } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/core-fellowship/README.md b/frame/core-fellowship/README.md new file mode 100644 index 000000000000..3c9b1f63e089 --- /dev/null +++ b/frame/core-fellowship/README.md @@ -0,0 +1,3 @@ +# Core Fellowship + +Logic specific to the core Polkadot Fellowship. \ No newline at end of file diff --git a/frame/core-fellowship/src/benchmarking.rs b/frame/core-fellowship/src/benchmarking.rs new file mode 100644 index 000000000000..ea0b5c6d4495 --- /dev/null +++ b/frame/core-fellowship/src/benchmarking.rs @@ -0,0 +1,216 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Salary pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use crate::Pallet as CoreFellowship; + +use frame_benchmarking::v2::*; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; +use sp_arithmetic::traits::Bounded; + +const SEED: u32 = 0; + +type BenchResult = Result<(), BenchmarkError>; + +#[instance_benchmarks] +mod benchmarks { + use super::*; + + fn ensure_evidence, I: 'static>(who: &T::AccountId) -> BenchResult { + let evidence = BoundedVec::try_from(vec![0; Evidence::::bound()]).unwrap(); + let wish = Wish::Retention; + let origin = RawOrigin::Signed(who.clone()).into(); + CoreFellowship::::submit_evidence(origin, wish, evidence)?; + assert!(MemberEvidence::::contains_key(who)); + Ok(()) + } + + fn make_member, I: 'static>(rank: u16) -> Result { + let member = account("member", 0, SEED); + T::Members::induct(&member)?; + for _ in 0..rank { + T::Members::promote(&member)?; + } + CoreFellowship::::import(RawOrigin::Signed(member.clone()).into())?; + Ok(member) + } + + #[benchmark] + fn set_params() -> Result<(), BenchmarkError> { + let params = ParamsType { + active_salary: [100u32.into(); 9], + passive_salary: [10u32.into(); 9], + demotion_period: [100u32.into(); 9], + min_promotion_period: [100u32.into(); 9], + offboard_timeout: 1u32.into(), + }; + + #[extrinsic_call] + _(RawOrigin::Root, Box::new(params.clone())); + + assert_eq!(Params::::get(), params); + Ok(()) + } + + #[benchmark] + fn bump_offboard() -> Result<(), BenchmarkError> { + let member = make_member::(0)?; + + // Set it to the max value to ensure that any possible auto-demotion period has passed. + frame_system::Pallet::::set_block_number(BlockNumberFor::::max_value()); + ensure_evidence::(&member)?; + assert!(Member::::contains_key(&member)); + + #[extrinsic_call] + CoreFellowship::::bump(RawOrigin::Signed(member.clone()), member.clone()); + + assert!(!Member::::contains_key(&member)); + assert!(!MemberEvidence::::contains_key(&member)); + Ok(()) + } + + #[benchmark] + fn bump_demote() -> Result<(), BenchmarkError> { + let member = make_member::(2)?; + + // Set it to the max value to ensure that any possible auto-demotion period has passed. + frame_system::Pallet::::set_block_number(BlockNumberFor::::max_value()); + ensure_evidence::(&member)?; + assert!(Member::::contains_key(&member)); + assert_eq!(T::Members::rank_of(&member), Some(2)); + + #[extrinsic_call] + CoreFellowship::::bump(RawOrigin::Signed(member.clone()), member.clone()); + + assert!(Member::::contains_key(&member)); + assert_eq!(T::Members::rank_of(&member), Some(1)); + assert!(!MemberEvidence::::contains_key(&member)); + Ok(()) + } + + #[benchmark] + fn set_active() -> Result<(), BenchmarkError> { + let member = make_member::(1)?; + assert!(Member::::get(&member).unwrap().is_active); + + #[extrinsic_call] + _(RawOrigin::Signed(member.clone()), false); + + assert!(!Member::::get(&member).unwrap().is_active); + Ok(()) + } + + #[benchmark] + fn induct() -> Result<(), BenchmarkError> { + let candidate: T::AccountId = account("candidate", 0, SEED); + + #[extrinsic_call] + _(RawOrigin::Root, candidate.clone()); + + assert_eq!(T::Members::rank_of(&candidate), Some(0)); + assert!(Member::::contains_key(&candidate)); + Ok(()) + } + + #[benchmark] + fn promote() -> Result<(), BenchmarkError> { + let member = make_member::(1)?; + ensure_evidence::(&member)?; + + #[extrinsic_call] + _(RawOrigin::Root, member.clone(), 2u8.into()); + + assert_eq!(T::Members::rank_of(&member), Some(2)); + assert!(!MemberEvidence::::contains_key(&member)); + Ok(()) + } + + #[benchmark] + fn offboard() -> Result<(), BenchmarkError> { + let member = make_member::(0)?; + T::Members::demote(&member)?; + ensure_evidence::(&member)?; + + assert!(T::Members::rank_of(&member).is_none()); + assert!(Member::::contains_key(&member)); + assert!(MemberEvidence::::contains_key(&member)); + + #[extrinsic_call] + _(RawOrigin::Signed(member.clone()), member.clone()); + + assert!(!Member::::contains_key(&member)); + assert!(!MemberEvidence::::contains_key(&member)); + Ok(()) + } + + #[benchmark] + fn import() -> Result<(), BenchmarkError> { + let member = account("member", 0, SEED); + T::Members::induct(&member)?; + T::Members::promote(&member)?; + + assert!(!Member::::contains_key(&member)); + + #[extrinsic_call] + _(RawOrigin::Signed(member.clone())); + + assert!(Member::::contains_key(&member)); + Ok(()) + } + + #[benchmark] + fn approve() -> Result<(), BenchmarkError> { + let member = make_member::(1)?; + let then = frame_system::Pallet::::block_number(); + let now = then.saturating_plus_one(); + frame_system::Pallet::::set_block_number(now); + ensure_evidence::(&member)?; + + assert_eq!(Member::::get(&member).unwrap().last_proof, then); + + #[extrinsic_call] + _(RawOrigin::Root, member.clone(), 1u8.into()); + + assert_eq!(Member::::get(&member).unwrap().last_proof, now); + assert!(!MemberEvidence::::contains_key(&member)); + Ok(()) + } + + #[benchmark] + fn submit_evidence() -> Result<(), BenchmarkError> { + let member = make_member::(1)?; + let evidence = vec![0; Evidence::::bound()].try_into().unwrap(); + + assert!(!MemberEvidence::::contains_key(&member)); + + #[extrinsic_call] + _(RawOrigin::Signed(member.clone()), Wish::Retention, evidence); + + assert!(MemberEvidence::::contains_key(&member)); + Ok(()) + } + + impl_benchmark_test_suite! { + CoreFellowship, + crate::tests::new_test_ext(), + crate::tests::Test, + } +} diff --git a/frame/core-fellowship/src/lib.rs b/frame/core-fellowship/src/lib.rs new file mode 100644 index 000000000000..8f3d9633fcf2 --- /dev/null +++ b/frame/core-fellowship/src/lib.rs @@ -0,0 +1,600 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Additional logic for the Core Fellowship. This determines salary, registers activity/passivity +//! and handles promotion and demotion periods. +//! +//! This only handles members of non-zero rank. +//! +//! # Process Flow +//! - Begin with a call to `induct`, where some privileged origin (perhaps a pre-existing member of +//! `rank > 1`) is able to make a candidate from an account and introduce it to be tracked in this +//! pallet in order to allow evidence to be submitted and promotion voted on. +//! - The candidate then calls `submit_evidence` to apply for their promotion to rank 1. +//! - A `PromoteOrigin` of at least rank 1 calls `promote` on the candidate to elevate it to rank 1. +//! - Some time later but before rank 1's `demotion_period` elapses, candidate calls +//! `submit_evidence` with evidence of their efforts to apply for approval to stay at rank 1. +//! - An `ApproveOrigin` of at least rank 1 calls `approve` on the candidate to avoid imminent +//! demotion and keep it at rank 1. +//! - These last two steps continue until the candidate is ready to apply for a promotion, at which +//! point the previous two steps are repeated with a higher rank. +//! - If the member fails to get an approval within the `demotion_period` then anyone may call +//! `bump` to demote the candidate by one rank. +//! - If a candidate fails to be promoted to a member within the `offboard_timeout` period, then +//! anyone may call `bump` to remove the account's candidacy. +//! - Pre-existing members may call `import` to have their rank recognised and be inducted into this +//! pallet (to gain a salary and allow for eventual promotion). +//! - If, externally to this pallet, a member or candidate has their rank removed completely, then +//! `offboard` may be called to remove them entirely from this pallet. +//! +//! Note there is a difference between having a rank of 0 (whereby the account is a *candidate*) and +//! having no rank at all (whereby we consider it *unranked*). An account can be demoted from rank +//! 0 to become unranked. This process is called being offboarded and there is an extrinsic to do +//! this explicitly when external factors to this pallet have caused the tracked account to become +//! unranked. At rank 0, there is not a "demotion" period after which the account may be bumped to +//! become offboarded but rather an "offboard timeout". +//! +//! Candidates may be introduced (i.e. an account to go from unranked to rank of 0) by an origin +//! of a different privilege to that for promotion. This allows the possibility for even a single +//! existing member to introduce a new candidate without payment. +//! +//! Only tracked/ranked accounts may submit evidence for their proof and promotion. Candidates +//! cannot be approved - they must proceed only to promotion prior to the offboard timeout elapsing. + +#![cfg_attr(not(feature = "std"), no_std)] +#![recursion_limit = "128"] + +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_arithmetic::traits::{Saturating, Zero}; +use sp_std::{marker::PhantomData, prelude::*}; + +use frame_support::{ + dispatch::DispatchResultWithPostInfo, + ensure, impl_ensure_origin_with_arg_ignoring_arg, + traits::{ + tokens::Balance as BalanceTrait, EnsureOrigin, EnsureOriginWithArg, Get, RankedMembers, + }, + BoundedVec, RuntimeDebug, +}; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod weights; + +pub use pallet::*; +pub use weights::WeightInfo; + +/// The desired outcome for which evidence is presented. +#[derive(Encode, Decode, Eq, PartialEq, Copy, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub enum Wish { + /// Member wishes only to retain their current rank. + Retention, + /// Member wishes to be promoted. + Promotion, +} + +/// A piece of evidence to underpin a [Wish]. +/// +/// From the pallet's perspective, this is just a blob of data without meaning. The fellows can +/// decide how to concretely utilise it. This could be an IPFS hash, a URL or structured data. +pub type Evidence = BoundedVec>::EvidenceSize>; + +/// The status of the pallet instance. +#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub struct ParamsType { + /// The amounts to be paid when a member of a given rank (-1) is active. + active_salary: [Balance; RANKS], + /// The amounts to be paid when a member of a given rank (-1) is passive. + passive_salary: [Balance; RANKS], + /// The period between which unproven members become demoted. + demotion_period: [BlockNumber; RANKS], + /// The period between which members must wait before they may proceed to this rank. + min_promotion_period: [BlockNumber; RANKS], + /// Amount by which an account can remain at rank 0 (candidate before being offboard entirely). + offboard_timeout: BlockNumber, +} + +impl Default + for ParamsType +{ + fn default() -> Self { + Self { + active_salary: [Balance::default(); RANKS], + passive_salary: [Balance::default(); RANKS], + demotion_period: [BlockNumber::default(); RANKS], + min_promotion_period: [BlockNumber::default(); RANKS], + offboard_timeout: BlockNumber::default(), + } + } +} + +/// The status of a single member. +#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub struct MemberStatus { + /// Are they currently active? + is_active: bool, + /// The block number at which we last promoted them. + last_promotion: BlockNumber, + /// The last time a member was demoted, promoted or proved their rank. + last_proof: BlockNumber, +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{ + dispatch::Pays, + pallet_prelude::*, + traits::{tokens::GetSalary, EnsureOrigin}, + }; + use frame_system::{ensure_root, pallet_prelude::*}; + + const RANK_COUNT: usize = 9; + + #[pallet::pallet] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// The runtime event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + + /// The current membership of the fellowship. + type Members: RankedMembers< + AccountId = ::AccountId, + Rank = u16, + >; + + /// The type in which salaries/budgets are measured. + type Balance: BalanceTrait; + + /// The origin which has permission update the parameters. + type ParamsOrigin: EnsureOrigin; + + /// The origin which has permission to move a candidate into being tracked in this pallet. + /// Generally a very low-permission, such as a pre-existing member of rank 1 or above. + /// + /// This allows the candidate to deposit evidence for their request to be promoted to a + /// member. + type InductOrigin: EnsureOrigin; + + /// The origin which has permission to issue a proof that a member may retain their rank. + /// The `Success` value is the maximum rank of members it is able to prove. + type ApproveOrigin: EnsureOrigin>; + + /// The origin which has permission to promote a member. The `Success` value is the maximum + /// rank to which it can promote. + type PromoteOrigin: EnsureOrigin>; + + /// The maximum size in bytes submitted evidence is allowed to be. + #[pallet::constant] + type EvidenceSize: Get; + } + + pub type ParamsOf = ParamsType<>::Balance, BlockNumberFor, RANK_COUNT>; + pub type MemberStatusOf = MemberStatus>; + pub type RankOf = <>::Members as RankedMembers>::Rank; + + /// The overall status of the system. + #[pallet::storage] + pub(super) type Params, I: 'static = ()> = + StorageValue<_, ParamsOf, ValueQuery>; + + /// The status of a claimant. + #[pallet::storage] + pub(super) type Member, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, MemberStatusOf, OptionQuery>; + + /// Some evidence together with the desired outcome for which it was presented. + #[pallet::storage] + pub(super) type MemberEvidence, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, (Wish, Evidence), OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// Parameters for the pallet have changed. + ParamsChanged { params: ParamsOf }, + /// Member activity flag has been set. + ActiveChanged { who: T::AccountId, is_active: bool }, + /// Member has begun being tracked in this pallet. + Inducted { who: T::AccountId }, + /// Member has been removed from being tracked in this pallet (i.e. because rank is now + /// zero). + Offboarded { who: T::AccountId }, + /// Member has been promoted to the given rank. + Promoted { who: T::AccountId, to_rank: RankOf }, + /// Member has been demoted to the given (non-zero) rank. + Demoted { who: T::AccountId, to_rank: RankOf }, + /// Member has been proven at their current rank, postponing auto-demotion. + Proven { who: T::AccountId, at_rank: RankOf }, + /// Member has stated evidence of their efforts their request for rank. + Requested { who: T::AccountId, wish: Wish }, + /// Some submitted evidence was judged and removed. There may or may not have been a change + /// to the rank, but in any case, `last_proof` is reset. + EvidenceJudged { + /// The member/candidate. + who: T::AccountId, + /// The desired outcome for which the evidence was presented. + wish: Wish, + /// The evidence of efforts. + evidence: Evidence, + /// The old rank, prior to this change. + old_rank: u16, + /// New rank. If `None` then candidate record was removed entirely. + new_rank: Option, + }, + /// Pre-ranked account has been inducted at their current rank. + Imported { who: T::AccountId, rank: RankOf }, + } + + #[pallet::error] + pub enum Error { + /// Member's rank is too low. + Unranked, + /// Member's rank is not zero. + Ranked, + /// Member's rank is not as expected - generally means that the rank provided to the call + /// does not agree with the state of the system. + UnexpectedRank, + /// The given rank is invalid - this generally means it's not between 1 and `RANK_COUNT`. + InvalidRank, + /// The origin does not have enough permission to do this operation. + NoPermission, + /// No work needs to be done at present for this member. + NothingDoing, + /// The candidate has already been inducted. This should never happen since it would + /// require a candidate (rank 0) to already be tracked in the pallet. + AlreadyInducted, + /// The candidate has not been inducted, so cannot be offboarded from this pallet. + NotTracked, + /// Operation cannot be done yet since not enough time has passed. + TooSoon, + } + + #[pallet::call] + impl, I: 'static> Pallet { + /// Bump the state of a member. + /// + /// This will demote a member whose `last_proof` is now beyond their rank's + /// `demotion_period`. + /// + /// - `origin`: A `Signed` origin of an account. + /// - `who`: A member account whose state is to be updated. + #[pallet::weight(T::WeightInfo::bump_offboard().max(T::WeightInfo::bump_demote()))] + #[pallet::call_index(0)] + pub fn bump(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + let mut member = Member::::get(&who).ok_or(Error::::NotTracked)?; + let rank = T::Members::rank_of(&who).ok_or(Error::::Unranked)?; + + let params = Params::::get(); + let demotion_period = if rank == 0 { + params.offboard_timeout + } else { + let rank_index = Self::rank_to_index(rank).ok_or(Error::::InvalidRank)?; + params.demotion_period[rank_index] + }; + let demotion_block = member.last_proof.saturating_add(demotion_period); + + // Ensure enough time has passed. + let now = frame_system::Pallet::::block_number(); + if now >= demotion_block { + T::Members::demote(&who)?; + let maybe_to_rank = T::Members::rank_of(&who); + Self::dispose_evidence(who.clone(), rank, maybe_to_rank); + let event = if let Some(to_rank) = maybe_to_rank { + member.last_proof = now; + Member::::insert(&who, &member); + Event::::Demoted { who, to_rank } + } else { + Member::::remove(&who); + Event::::Offboarded { who } + }; + Self::deposit_event(event); + return Ok(Pays::No.into()) + } + + Err(Error::::NothingDoing.into()) + } + + /// Set the parameters. + /// + /// - `origin`: An origin complying with `ParamsOrigin` or root. + /// - `params`: The new parameters for the pallet. + #[pallet::weight(T::WeightInfo::set_params())] + #[pallet::call_index(1)] + pub fn set_params(origin: OriginFor, params: Box>) -> DispatchResult { + T::ParamsOrigin::try_origin(origin).map(|_| ()).or_else(|o| ensure_root(o))?; + Params::::put(params.as_ref()); + Self::deposit_event(Event::::ParamsChanged { params: *params }); + Ok(()) + } + + /// Set whether a member is active or not. + /// + /// - `origin`: A `Signed` origin of a member's account. + /// - `is_active`: `true` iff the member is active. + #[pallet::weight(T::WeightInfo::set_active())] + #[pallet::call_index(2)] + pub fn set_active(origin: OriginFor, is_active: bool) -> DispatchResult { + let who = ensure_signed(origin)?; + ensure!( + T::Members::rank_of(&who).map_or(false, |r| !r.is_zero()), + Error::::Unranked + ); + let mut member = Member::::get(&who).ok_or(Error::::NotTracked)?; + member.is_active = is_active; + Member::::insert(&who, &member); + Self::deposit_event(Event::::ActiveChanged { who, is_active }); + Ok(()) + } + + /// Approve a member to continue at their rank. + /// + /// This resets `last_proof` to the current block, thereby delaying any automatic demotion. + /// + /// If `who` is not already tracked by this pallet, then it will become tracked. + /// `last_promotion` will be set to zero. + /// + /// - `origin`: An origin which satisfies `ApproveOrigin` or root. + /// - `who`: A member (i.e. of non-zero rank). + /// - `at_rank`: The rank of member. + #[pallet::weight(T::WeightInfo::approve())] + #[pallet::call_index(3)] + pub fn approve( + origin: OriginFor, + who: T::AccountId, + at_rank: RankOf, + ) -> DispatchResult { + match T::ApproveOrigin::try_origin(origin) { + Ok(allow_rank) => ensure!(allow_rank >= at_rank, Error::::NoPermission), + Err(origin) => ensure_root(origin)?, + } + ensure!(at_rank > 0, Error::::InvalidRank); + let rank = T::Members::rank_of(&who).ok_or(Error::::Unranked)?; + ensure!(rank == at_rank, Error::::UnexpectedRank); + let mut member = Member::::get(&who).ok_or(Error::::NotTracked)?; + + member.last_proof = frame_system::Pallet::::block_number(); + Member::::insert(&who, &member); + + Self::dispose_evidence(who.clone(), at_rank, Some(at_rank)); + Self::deposit_event(Event::::Proven { who, at_rank }); + + Ok(()) + } + + /// Introduce a new and unranked candidate (rank zero). + /// + /// - `origin`: An origin which satisfies `InductOrigin` or root. + /// - `who`: The account ID of the candidate to be inducted and become a member. + #[pallet::weight(T::WeightInfo::induct())] + #[pallet::call_index(4)] + pub fn induct(origin: OriginFor, who: T::AccountId) -> DispatchResult { + match T::InductOrigin::try_origin(origin) { + Ok(_) => {}, + Err(origin) => ensure_root(origin)?, + } + ensure!(!Member::::contains_key(&who), Error::::AlreadyInducted); + ensure!(T::Members::rank_of(&who).is_none(), Error::::Ranked); + + T::Members::induct(&who)?; + let now = frame_system::Pallet::::block_number(); + Member::::insert( + &who, + MemberStatus { is_active: true, last_promotion: now, last_proof: now }, + ); + Self::deposit_event(Event::::Inducted { who }); + Ok(()) + } + + /// Increment the rank of a ranked and tracked account. + /// + /// - `origin`: An origin which satisfies `PromoteOrigin` with a `Success` result of + /// `to_rank` or more or root. + /// - `who`: The account ID of the member to be promoted to `to_rank`. + /// - `to_rank`: One more than the current rank of `who`. + #[pallet::weight(T::WeightInfo::promote())] + #[pallet::call_index(5)] + pub fn promote( + origin: OriginFor, + who: T::AccountId, + to_rank: RankOf, + ) -> DispatchResult { + match T::PromoteOrigin::try_origin(origin) { + Ok(allow_rank) => ensure!(allow_rank >= to_rank, Error::::NoPermission), + Err(origin) => ensure_root(origin)?, + } + let rank = T::Members::rank_of(&who).ok_or(Error::::Unranked)?; + ensure!( + rank.checked_add(1).map_or(false, |i| i == to_rank), + Error::::UnexpectedRank + ); + + let mut member = Member::::get(&who).ok_or(Error::::NotTracked)?; + let now = frame_system::Pallet::::block_number(); + + let params = Params::::get(); + let rank_index = Self::rank_to_index(to_rank).ok_or(Error::::InvalidRank)?; + let min_period = params.min_promotion_period[rank_index]; + // Ensure enough time has passed. + ensure!( + member.last_promotion.saturating_add(min_period) <= now, + Error::::TooSoon, + ); + + T::Members::promote(&who)?; + member.last_promotion = now; + member.last_proof = now; + Member::::insert(&who, &member); + Self::dispose_evidence(who.clone(), rank, Some(to_rank)); + + Self::deposit_event(Event::::Promoted { who, to_rank }); + + Ok(()) + } + + /// Stop tracking a prior member who is now not a ranked member of the collective. + /// + /// - `origin`: A `Signed` origin of an account. + /// - `who`: The ID of an account which was tracked in this pallet but which is now not a + /// ranked member of the collective. + #[pallet::weight(T::WeightInfo::offboard())] + #[pallet::call_index(6)] + pub fn offboard(origin: OriginFor, who: T::AccountId) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + ensure!(T::Members::rank_of(&who).is_none(), Error::::Ranked); + ensure!(Member::::contains_key(&who), Error::::NotTracked); + Member::::remove(&who); + MemberEvidence::::remove(&who); + Self::deposit_event(Event::::Offboarded { who }); + Ok(Pays::No.into()) + } + + /// Provide evidence that a rank is deserved. + /// + /// This is free as long as no evidence for the forthcoming judgement is already submitted. + /// Evidence is cleared after an outcome (either demotion, promotion of approval). + /// + /// - `origin`: A `Signed` origin of an inducted and ranked account. + /// - `wish`: The stated desire of the member. + /// - `evidence`: A dump of evidence to be considered. This should generally be either a + /// Markdown-encoded document or a series of 32-byte hashes which can be found on a + /// decentralised content-based-indexing system such as IPFS. + #[pallet::weight(T::WeightInfo::submit_evidence())] + #[pallet::call_index(7)] + pub fn submit_evidence( + origin: OriginFor, + wish: Wish, + evidence: Evidence, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + ensure!(Member::::contains_key(&who), Error::::NotTracked); + let replaced = MemberEvidence::::contains_key(&who); + MemberEvidence::::insert(&who, (wish, evidence)); + Self::deposit_event(Event::::Requested { who, wish }); + Ok(if replaced { Pays::Yes } else { Pays::No }.into()) + } + + /// Introduce an already-ranked individual of the collective into this pallet. The rank may + /// still be zero. + /// + /// This resets `last_proof` to the current block and `last_promotion` will be set to zero, + /// thereby delaying any automatic demotion but allowing immediate promotion. + /// + /// - `origin`: A signed origin of a ranked, but not tracked, account. + #[pallet::weight(T::WeightInfo::import())] + #[pallet::call_index(8)] + pub fn import(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + ensure!(!Member::::contains_key(&who), Error::::AlreadyInducted); + let rank = T::Members::rank_of(&who).ok_or(Error::::Unranked)?; + + let now = frame_system::Pallet::::block_number(); + Member::::insert( + &who, + MemberStatus { is_active: true, last_promotion: 0u32.into(), last_proof: now }, + ); + Self::deposit_event(Event::::Imported { who, rank }); + + Ok(Pays::No.into()) + } + } + + impl, I: 'static> Pallet { + /// Convert a rank into a `0..RANK_COUNT` index suitable for the arrays in Params. + /// + /// Rank 1 becomes index 0, rank `RANK_COUNT` becomes index `RANK_COUNT - 1`. Any rank not + /// in the range `1..=RANK_COUNT` is `None`. + pub(crate) fn rank_to_index(rank: RankOf) -> Option { + match TryInto::::try_into(rank) { + Ok(r) if r <= RANK_COUNT && r > 0 => Some(r - 1), + _ => return None, + } + } + + fn dispose_evidence(who: T::AccountId, old_rank: u16, new_rank: Option) { + if let Some((wish, evidence)) = MemberEvidence::::take(&who) { + let e = Event::::EvidenceJudged { who, wish, evidence, old_rank, new_rank }; + Self::deposit_event(e); + } + } + } + + impl, I: 'static> GetSalary, T::AccountId, T::Balance> for Pallet { + fn get_salary(rank: RankOf, who: &T::AccountId) -> T::Balance { + let index = match Self::rank_to_index(rank) { + Some(i) => i, + None => return Zero::zero(), + }; + let member = match Member::::get(who) { + Some(m) => m, + None => return Zero::zero(), + }; + let params = Params::::get(); + let salary = + if member.is_active { params.active_salary } else { params.passive_salary }; + salary[index] + } + } +} + +/// Guard to ensure that the given origin is inducted into this pallet with a given minimum rank. +/// The account ID of the member is the `Success` value. +pub struct EnsureInducted(PhantomData<(T, I)>); +impl, I: 'static, const MIN_RANK: u16> EnsureOrigin + for EnsureInducted +{ + type Success = T::AccountId; + + fn try_origin(o: T::RuntimeOrigin) -> Result { + let who = as EnsureOrigin<_>>::try_origin(o)?; + match T::Members::rank_of(&who) { + Some(rank) if rank >= MIN_RANK && Member::::contains_key(&who) => Ok(who), + _ => Err(frame_system::RawOrigin::Signed(who).into()), + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin() -> Result { + let who = frame_benchmarking::account::("successful_origin", 0, 0); + if T::Members::rank_of(&who).is_none() { + T::Members::induct(&who).map_err(|_| ())?; + } + for _ in 0..MIN_RANK { + if T::Members::rank_of(&who).ok_or(())? < MIN_RANK { + T::Members::promote(&who).map_err(|_| ())?; + } + } + Ok(frame_system::RawOrigin::Signed(who).into()) + } +} + +impl_ensure_origin_with_arg_ignoring_arg! { + impl< { T: Config, I: 'static, const MIN_RANK: u16, A } > + EnsureOriginWithArg for EnsureInducted + {} +} diff --git a/frame/core-fellowship/src/tests.rs b/frame/core-fellowship/src/tests.rs new file mode 100644 index 000000000000..c95699e66e41 --- /dev/null +++ b/frame/core-fellowship/src/tests.rs @@ -0,0 +1,356 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The crate's tests. + +use std::collections::BTreeMap; + +use frame_support::{ + assert_noop, assert_ok, ord_parameter_types, + pallet_prelude::Weight, + parameter_types, + traits::{tokens::GetSalary, ConstU32, ConstU64, Everything, IsInVec, TryMapSuccess}, +}; +use frame_system::EnsureSignedBy; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup, TryMorphInto}, + BuildStorage, DispatchError, DispatchResult, +}; +use sp_std::cell::RefCell; + +use super::*; +use crate as pallet_core_fellowship; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + CoreFellowship: pallet_core_fellowship::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1_000_000, u64::max_value())); +} +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +thread_local! { + pub static CLUB: RefCell> = RefCell::new(BTreeMap::new()); +} + +pub struct TestClub; +impl RankedMembers for TestClub { + type AccountId = u64; + type Rank = u16; + fn min_rank() -> Self::Rank { + 0 + } + fn rank_of(who: &Self::AccountId) -> Option { + CLUB.with(|club| club.borrow().get(who).cloned()) + } + fn induct(who: &Self::AccountId) -> DispatchResult { + CLUB.with(|club| club.borrow_mut().insert(*who, 0)); + Ok(()) + } + fn promote(who: &Self::AccountId) -> DispatchResult { + CLUB.with(|club| { + club.borrow_mut().entry(*who).and_modify(|r| *r += 1); + }); + Ok(()) + } + fn demote(who: &Self::AccountId) -> DispatchResult { + CLUB.with(|club| match Self::rank_of(who) { + None => Err(sp_runtime::DispatchError::Unavailable), + Some(0) => { + club.borrow_mut().remove(&who); + Ok(()) + }, + Some(_) => { + club.borrow_mut().entry(*who).and_modify(|x| *x -= 1); + Ok(()) + }, + }) + } +} + +fn set_rank(who: u64, rank: u16) { + CLUB.with(|club| club.borrow_mut().insert(who, rank)); +} + +fn unrank(who: u64) { + CLUB.with(|club| club.borrow_mut().remove(&who)); +} + +parameter_types! { + pub ZeroToNine: Vec = vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; +} +ord_parameter_types! { + pub const One: u64 = 1; +} + +impl Config for Test { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Members = TestClub; + type Balance = u64; + type ParamsOrigin = EnsureSignedBy; + type InductOrigin = EnsureInducted; + type ApproveOrigin = TryMapSuccess, u64>, TryMorphInto>; + type PromoteOrigin = TryMapSuccess, u64>, TryMorphInto>; + type EvidenceSize = ConstU32<1024>; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + let params = ParamsType { + active_salary: [10, 20, 30, 40, 50, 60, 70, 80, 90], + passive_salary: [1, 2, 3, 4, 5, 6, 7, 8, 9], + demotion_period: [2, 4, 6, 8, 10, 12, 14, 16, 18], + min_promotion_period: [3, 6, 9, 12, 15, 18, 21, 24, 27], + offboard_timeout: 1, + }; + assert_ok!(CoreFellowship::set_params(signed(1), Box::new(params))); + System::set_block_number(1); + }); + ext +} + +fn next_block() { + System::set_block_number(System::block_number() + 1); +} + +fn run_to(n: u64) { + while System::block_number() < n { + next_block(); + } +} + +fn signed(who: u64) -> RuntimeOrigin { + RuntimeOrigin::signed(who) +} + +fn next_demotion(who: u64) -> u64 { + let member = Member::::get(who).unwrap(); + let demotion_period = Params::::get().demotion_period; + member.last_proof + demotion_period[TestClub::rank_of(&who).unwrap() as usize - 1] +} + +#[test] +fn basic_stuff() { + new_test_ext().execute_with(|| { + assert_eq!(CoreFellowship::rank_to_index(0), None); + assert_eq!(CoreFellowship::rank_to_index(1), Some(0)); + assert_eq!(CoreFellowship::rank_to_index(9), Some(8)); + assert_eq!(CoreFellowship::rank_to_index(10), None); + assert_eq!(CoreFellowship::get_salary(0, &1), 0); + }); +} + +#[test] +fn set_params_works() { + new_test_ext().execute_with(|| { + let params = ParamsType { + active_salary: [10, 20, 30, 40, 50, 60, 70, 80, 90], + passive_salary: [1, 2, 3, 4, 5, 6, 7, 8, 9], + demotion_period: [1, 2, 3, 4, 5, 6, 7, 8, 9], + min_promotion_period: [1, 2, 3, 4, 5, 10, 15, 20, 30], + offboard_timeout: 1, + }; + assert_noop!( + CoreFellowship::set_params(signed(2), Box::new(params.clone())), + DispatchError::BadOrigin + ); + assert_ok!(CoreFellowship::set_params(signed(1), Box::new(params))); + }); +} + +#[test] +fn induct_works() { + new_test_ext().execute_with(|| { + set_rank(0, 0); + assert_ok!(CoreFellowship::import(signed(0))); + set_rank(1, 1); + assert_ok!(CoreFellowship::import(signed(1))); + + assert_noop!(CoreFellowship::induct(signed(10), 10), DispatchError::BadOrigin); + assert_noop!(CoreFellowship::induct(signed(0), 10), DispatchError::BadOrigin); + assert_ok!(CoreFellowship::induct(signed(1), 10)); + assert_noop!(CoreFellowship::induct(signed(1), 10), Error::::AlreadyInducted); + }); +} + +#[test] +fn promote_works() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_ok!(CoreFellowship::import(signed(1))); + assert_noop!(CoreFellowship::promote(signed(1), 10, 1), Error::::Unranked); + + assert_ok!(CoreFellowship::induct(signed(1), 10)); + assert_noop!(CoreFellowship::promote(signed(10), 10, 1), DispatchError::BadOrigin); + assert_noop!(CoreFellowship::promote(signed(0), 10, 1), Error::::NoPermission); + assert_noop!(CoreFellowship::promote(signed(3), 10, 2), Error::::UnexpectedRank); + run_to(3); + assert_noop!(CoreFellowship::promote(signed(1), 10, 1), Error::::TooSoon); + run_to(4); + assert_ok!(CoreFellowship::promote(signed(1), 10, 1)); + set_rank(11, 0); + assert_noop!(CoreFellowship::promote(signed(1), 11, 1), Error::::NotTracked); + }); +} + +#[test] +fn sync_works() { + new_test_ext().execute_with(|| { + set_rank(10, 5); + assert_noop!(CoreFellowship::approve(signed(4), 10, 5), Error::::NoPermission); + assert_noop!(CoreFellowship::approve(signed(6), 10, 6), Error::::UnexpectedRank); + assert_ok!(CoreFellowship::import(signed(10))); + assert!(Member::::contains_key(10)); + assert_eq!(next_demotion(10), 11); + }); +} + +#[test] +fn auto_demote_works() { + new_test_ext().execute_with(|| { + set_rank(10, 5); + assert_ok!(CoreFellowship::import(signed(10))); + + run_to(10); + assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NothingDoing); + run_to(11); + assert_ok!(CoreFellowship::bump(signed(0), 10)); + assert_eq!(TestClub::rank_of(&10), Some(4)); + assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NothingDoing); + assert_eq!(next_demotion(10), 19); + }); +} + +#[test] +fn auto_demote_offboard_works() { + new_test_ext().execute_with(|| { + set_rank(10, 1); + assert_ok!(CoreFellowship::import(signed(10))); + + run_to(3); + assert_ok!(CoreFellowship::bump(signed(0), 10)); + assert_eq!(TestClub::rank_of(&10), Some(0)); + assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NothingDoing); + run_to(4); + assert_ok!(CoreFellowship::bump(signed(0), 10)); + assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NotTracked); + }); +} + +#[test] +fn offboard_works() { + new_test_ext().execute_with(|| { + assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::NotTracked); + set_rank(10, 0); + assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::Ranked); + + assert_ok!(CoreFellowship::import(signed(10))); + assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::Ranked); + + unrank(10); + assert_ok!(CoreFellowship::offboard(signed(0), 10)); + assert_noop!(CoreFellowship::offboard(signed(0), 10), Error::::NotTracked); + assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NotTracked); + }); +} + +#[test] +fn proof_postpones_auto_demote() { + new_test_ext().execute_with(|| { + set_rank(10, 5); + assert_ok!(CoreFellowship::import(signed(10))); + + run_to(11); + assert_ok!(CoreFellowship::approve(signed(5), 10, 5)); + assert_eq!(next_demotion(10), 21); + assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NothingDoing); + }); +} + +#[test] +fn promote_postpones_auto_demote() { + new_test_ext().execute_with(|| { + set_rank(10, 5); + assert_ok!(CoreFellowship::import(signed(10))); + + run_to(19); + assert_ok!(CoreFellowship::promote(signed(6), 10, 6)); + assert_eq!(next_demotion(10), 31); + assert_noop!(CoreFellowship::bump(signed(0), 10), Error::::NothingDoing); + }); +} + +#[test] +fn get_salary_works() { + new_test_ext().execute_with(|| { + for i in 1..=9u64 { + set_rank(10 + i, i as u16); + assert_ok!(CoreFellowship::import(signed(10 + i))); + assert_eq!(CoreFellowship::get_salary(i as u16, &(10 + i)), i * 10); + } + }); +} + +#[test] +fn active_changing_get_salary_works() { + new_test_ext().execute_with(|| { + for i in 1..=9u64 { + set_rank(10 + i, i as u16); + assert_ok!(CoreFellowship::import(signed(10 + i))); + assert_ok!(CoreFellowship::set_active(signed(10 + i), false)); + assert_eq!(CoreFellowship::get_salary(i as u16, &(10 + i)), i); + assert_ok!(CoreFellowship::set_active(signed(10 + i), true)); + assert_eq!(CoreFellowship::get_salary(i as u16, &(10 + i)), i * 10); + } + }); +} diff --git a/frame/core-fellowship/src/weights.rs b/frame/core-fellowship/src/weights.rs new file mode 100644 index 000000000000..8bbfd1a4dd81 --- /dev/null +++ b/frame/core-fellowship/src/weights.rs @@ -0,0 +1,400 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_core_fellowship +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_core_fellowship +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/core-fellowship/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for pallet_core_fellowship. +pub trait WeightInfo { + fn set_params() -> Weight; + fn bump_offboard() -> Weight; + fn bump_demote() -> Weight; + fn set_active() -> Weight; + fn induct() -> Weight; + fn promote() -> Weight; + fn offboard() -> Weight; + fn import() -> Weight; + fn approve() -> Weight; + fn submit_evidence() -> Weight; +} + +/// Weights for pallet_core_fellowship using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: CoreFellowship Params (r:0 w:1) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(364), added: 859, mode: MaxEncodedLen) + fn set_params() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 9_454_000 picoseconds. + Weight::from_parts(9_804_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Params (r:1 w:0) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(364), added: 859, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:1 w:0) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) + fn bump_offboard() -> Weight { + // Proof Size summary in bytes: + // Measured: `16887` + // Estimated: `19894` + // Minimum execution time: 58_489_000 picoseconds. + Weight::from_parts(60_202_000, 19894) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Params (r:1 w:0) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(364), added: 859, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:1 w:0) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) + fn bump_demote() -> Weight { + // Proof Size summary in bytes: + // Measured: `16997` + // Estimated: `19894` + // Minimum execution time: 60_605_000 picoseconds. + Weight::from_parts(63_957_000, 19894) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + fn set_active() -> Weight { + // Proof Size summary in bytes: + // Measured: `388` + // Estimated: `3514` + // Minimum execution time: 17_816_000 picoseconds. + Weight::from_parts(18_524_000, 3514) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IndexToId (r:0 w:1) + /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:0 w:1) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + fn induct() -> Weight { + // Proof Size summary in bytes: + // Measured: `146` + // Estimated: `3514` + // Minimum execution time: 27_249_000 picoseconds. + Weight::from_parts(28_049_000, 3514) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: CoreFellowship Params (r:1 w:0) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(364), added: 859, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) + /// Storage: RankedCollective IndexToId (r:0 w:1) + /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:0 w:1) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + fn promote() -> Weight { + // Proof Size summary in bytes: + // Measured: `16865` + // Estimated: `19894` + // Minimum execution time: 56_642_000 picoseconds. + Weight::from_parts(59_353_000, 19894) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) + } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:0 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) + fn offboard() -> Weight { + // Proof Size summary in bytes: + // Measured: `359` + // Estimated: `3514` + // Minimum execution time: 17_459_000 picoseconds. + Weight::from_parts(18_033_000, 3514) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + fn import() -> Weight { + // Proof Size summary in bytes: + // Measured: `313` + // Estimated: `3514` + // Minimum execution time: 16_728_000 picoseconds. + Weight::from_parts(17_263_000, 3514) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) + fn approve() -> Weight { + // Proof Size summary in bytes: + // Measured: `16843` + // Estimated: `19894` + // Minimum execution time: 41_487_000 picoseconds. + Weight::from_parts(43_459_000, 19894) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: CoreFellowship Member (r:1 w:0) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) + fn submit_evidence() -> Weight { + // Proof Size summary in bytes: + // Measured: `79` + // Estimated: `19894` + // Minimum execution time: 26_033_000 picoseconds. + Weight::from_parts(26_612_000, 19894) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: CoreFellowship Params (r:0 w:1) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(364), added: 859, mode: MaxEncodedLen) + fn set_params() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 9_454_000 picoseconds. + Weight::from_parts(9_804_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Params (r:1 w:0) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(364), added: 859, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:1 w:0) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) + fn bump_offboard() -> Weight { + // Proof Size summary in bytes: + // Measured: `16887` + // Estimated: `19894` + // Minimum execution time: 58_489_000 picoseconds. + Weight::from_parts(60_202_000, 19894) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Params (r:1 w:0) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(364), added: 859, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:1 w:0) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) + fn bump_demote() -> Weight { + // Proof Size summary in bytes: + // Measured: `16997` + // Estimated: `19894` + // Minimum execution time: 60_605_000 picoseconds. + Weight::from_parts(63_957_000, 19894) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + fn set_active() -> Weight { + // Proof Size summary in bytes: + // Measured: `388` + // Estimated: `3514` + // Minimum execution time: 17_816_000 picoseconds. + Weight::from_parts(18_524_000, 3514) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IndexToId (r:0 w:1) + /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:0 w:1) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + fn induct() -> Weight { + // Proof Size summary in bytes: + // Measured: `146` + // Estimated: `3514` + // Minimum execution time: 27_249_000 picoseconds. + Weight::from_parts(28_049_000, 3514) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: CoreFellowship Params (r:1 w:0) + /// Proof: CoreFellowship Params (max_values: Some(1), max_size: Some(364), added: 859, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) + /// Storage: RankedCollective IndexToId (r:0 w:1) + /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:0 w:1) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + fn promote() -> Weight { + // Proof Size summary in bytes: + // Measured: `16865` + // Estimated: `19894` + // Minimum execution time: 56_642_000 picoseconds. + Weight::from_parts(59_353_000, 19894) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:0 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) + fn offboard() -> Weight { + // Proof Size summary in bytes: + // Measured: `359` + // Estimated: `3514` + // Minimum execution time: 17_459_000 picoseconds. + Weight::from_parts(18_033_000, 3514) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + fn import() -> Weight { + // Proof Size summary in bytes: + // Measured: `313` + // Estimated: `3514` + // Minimum execution time: 16_728_000 picoseconds. + Weight::from_parts(17_263_000, 3514) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: CoreFellowship Member (r:1 w:1) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) + fn approve() -> Weight { + // Proof Size summary in bytes: + // Measured: `16843` + // Estimated: `19894` + // Minimum execution time: 41_487_000 picoseconds. + Weight::from_parts(43_459_000, 19894) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: CoreFellowship Member (r:1 w:0) + /// Proof: CoreFellowship Member (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: CoreFellowship MemberEvidence (r:1 w:1) + /// Proof: CoreFellowship MemberEvidence (max_values: None, max_size: Some(16429), added: 18904, mode: MaxEncodedLen) + fn submit_evidence() -> Weight { + // Proof Size summary in bytes: + // Measured: `79` + // Estimated: `19894` + // Minimum execution time: 26_033_000 picoseconds. + Weight::from_parts(26_612_000, 19894) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } +} diff --git a/frame/democracy/Cargo.toml b/frame/democracy/Cargo.toml index fa107218cf69..1d805142d369 100644 --- a/frame/democracy/Cargo.toml +++ b/frame/democracy/Cargo.toml @@ -13,18 +13,18 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", features = ["derive"], optional = true } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"], optional = true } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } log = { version = "0.4.17", default-features = false } [dev-dependencies] diff --git a/frame/democracy/src/benchmarking.rs b/frame/democracy/src/benchmarking.rs index 424192e2521d..e4a21a4e1d9b 100644 --- a/frame/democracy/src/benchmarking.rs +++ b/frame/democracy/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,12 +19,12 @@ use super::*; -use frame_benchmarking::{account, benchmarks, whitelist_account}; +use frame_benchmarking::v1::{account, benchmarks, whitelist_account, BenchmarkError}; use frame_support::{ assert_noop, assert_ok, traits::{Currency, EnsureOrigin, Get, OnInitialize, UnfilteredDispatchable}, }; -use frame_system::RawOrigin; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use sp_core::H256; use sp_runtime::{traits::Bounded, BoundedVec}; @@ -54,19 +54,20 @@ fn add_proposal(n: u32) -> Result { Ok(proposal.hash()) } -fn add_referendum(n: u32) -> (ReferendumIndex, H256) { +// add a referendum with a metadata. +fn add_referendum(n: u32) -> (ReferendumIndex, H256, PreimageHash) { let vote_threshold = VoteThreshold::SimpleMajority; let proposal = make_proposal::(n); let hash = proposal.hash(); - ( - Democracy::::inject_referendum( - T::LaunchPeriod::get(), - proposal, - vote_threshold, - 0u32.into(), - ), - hash, - ) + let index = Democracy::::inject_referendum( + T::LaunchPeriod::get(), + proposal, + vote_threshold, + 0u32.into(), + ); + let preimage_hash = note_preimage::(); + MetadataOf::::insert(crate::MetadataOwner::Referendum(index), preimage_hash.clone()); + (index, hash, preimage_hash) } fn account_vote(b: BalanceOf) -> AccountVote> { @@ -75,6 +76,25 @@ fn account_vote(b: BalanceOf) -> AccountVote> { AccountVote::Standard { vote: v, balance: b } } +fn assert_last_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +fn assert_has_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_has_event(generic_event.into()); +} + +// note a new preimage. +fn note_preimage() -> PreimageHash { + use core::sync::atomic::{AtomicU8, Ordering}; + use sp_std::borrow::Cow; + // note a new preimage on every function invoke. + static COUNTER: AtomicU8 = AtomicU8::new(0); + let data = Cow::from(vec![COUNTER.fetch_add(1, Ordering::Relaxed)]); + let hash = ::Preimages::note(data).unwrap(); + hash +} + benchmarks! { propose { let p = T::MaxProposals::get(); @@ -177,8 +197,9 @@ benchmarks! { } emergency_cancel { - let origin = T::CancellationOrigin::successful_origin(); - let ref_index = add_referendum::(0).0; + let origin = + T::CancellationOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + let (ref_index, _, preimage_hash) = add_referendum::(0); assert_ok!(Democracy::::referendum_status(ref_index)); }: _(origin, ref_index) verify { @@ -187,6 +208,10 @@ benchmarks! { Democracy::::referendum_status(ref_index), Error::::ReferendumInvalid, ); + assert_last_event::(crate::Event::MetadataCleared { + owner: MetadataOwner::Referendum(ref_index), + hash: preimage_hash, + }.into()); } blacklist { @@ -197,13 +222,16 @@ benchmarks! { // We should really add a lot of seconds here, but we're not doing it elsewhere. // Add a referendum of our proposal. - let (ref_index, hash) = add_referendum::(0); + let (ref_index, hash, preimage_hash) = add_referendum::(0); assert_ok!(Democracy::::referendum_status(ref_index)); // Place our proposal in the external queue, too. - assert_ok!( - Democracy::::external_propose(T::ExternalOrigin::successful_origin(), make_proposal::(0)) - ); - let origin = T::BlacklistOrigin::successful_origin(); + assert_ok!(Democracy::::external_propose( + T::ExternalOrigin::try_successful_origin() + .expect("ExternalOrigin has no successful origin required for the benchmark"), + make_proposal::(0) + )); + let origin = + T::BlacklistOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: _(origin, hash, Some(ref_index)) verify { // Referendum has been canceled @@ -211,11 +239,16 @@ benchmarks! { Democracy::::referendum_status(ref_index), Error::::ReferendumInvalid ); + assert_has_event::(crate::Event::MetadataCleared { + owner: MetadataOwner::Referendum(ref_index), + hash: preimage_hash, + }.into()); } // Worst case scenario, we external propose a previously blacklisted proposal external_propose { - let origin = T::ExternalOrigin::successful_origin(); + let origin = + T::ExternalOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let proposal = make_proposal::(0); // Add proposal to blacklist with block number 0 @@ -225,7 +258,7 @@ benchmarks! { .collect::>() .try_into() .unwrap(); - Blacklist::::insert(proposal.hash(), (T::BlockNumber::zero(), addresses)); + Blacklist::::insert(proposal.hash(), (BlockNumberFor::::zero(), addresses)); }: _(origin, proposal) verify { // External proposal created @@ -233,7 +266,8 @@ benchmarks! { } external_propose_majority { - let origin = T::ExternalMajorityOrigin::successful_origin(); + let origin = T::ExternalMajorityOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; let proposal = make_proposal::(0); }: _(origin, proposal) verify { @@ -242,7 +276,8 @@ benchmarks! { } external_propose_default { - let origin = T::ExternalDefaultOrigin::successful_origin(); + let origin = T::ExternalDefaultOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; let proposal = make_proposal::(0); }: _(origin, proposal) verify { @@ -251,35 +286,55 @@ benchmarks! { } fast_track { - let origin_propose = T::ExternalDefaultOrigin::successful_origin(); + let origin_propose = T::ExternalDefaultOrigin::try_successful_origin() + .expect("ExternalDefaultOrigin has no successful origin required for the benchmark"); let proposal = make_proposal::(0); let proposal_hash = proposal.hash(); - Democracy::::external_propose_default(origin_propose, proposal)?; - + Democracy::::external_propose_default(origin_propose.clone(), proposal)?; + // Set metadata to the external proposal. + let preimage_hash = note_preimage::(); + assert_ok!(Democracy::::set_metadata( + origin_propose, + MetadataOwner::External, + Some(preimage_hash))); // NOTE: Instant origin may invoke a little bit more logic, but may not always succeed. - let origin_fast_track = T::FastTrackOrigin::successful_origin(); + let origin_fast_track = + T::FastTrackOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let voting_period = T::FastTrackVotingPeriod::get(); let delay = 0u32; }: _(origin_fast_track, proposal_hash, voting_period, delay.into()) verify { - assert_eq!(Democracy::::referendum_count(), 1, "referendum not created") + assert_eq!(Democracy::::referendum_count(), 1, "referendum not created"); + assert_last_event::(crate::Event::MetadataTransferred { + prev_owner: MetadataOwner::External, + owner: MetadataOwner::Referendum(0), + hash: preimage_hash, + }.into()); } veto_external { let proposal = make_proposal::(0); let proposal_hash = proposal.hash(); - let origin_propose = T::ExternalDefaultOrigin::successful_origin(); - Democracy::::external_propose_default(origin_propose, proposal)?; + let origin_propose = T::ExternalDefaultOrigin::try_successful_origin() + .expect("ExternalDefaultOrigin has no successful origin required for the benchmark"); + Democracy::::external_propose_default(origin_propose.clone(), proposal)?; + + let preimage_hash = note_preimage::(); + assert_ok!(Democracy::::set_metadata( + origin_propose, + MetadataOwner::External, + Some(preimage_hash)) + ); let mut vetoers: BoundedVec = Default::default(); for i in 0 .. (T::MaxBlacklisted::get() - 1) { vetoers.try_push(account::("vetoer", i, SEED)).unwrap(); } vetoers.sort(); - Blacklist::::insert(proposal_hash, (T::BlockNumber::zero(), vetoers)); + Blacklist::::insert(proposal_hash, (BlockNumberFor::::zero(), vetoers)); - let origin = T::VetoOrigin::successful_origin(); + let origin = T::VetoOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; ensure!(NextExternal::::get().is_some(), "no external proposal"); }: _(origin, proposal_hash) verify { @@ -293,12 +348,32 @@ benchmarks! { for i in 0 .. T::MaxProposals::get() { add_proposal::(i)?; } - let cancel_origin = T::CancelProposalOrigin::successful_origin(); + // Add metadata to the first proposal. + let proposer = funded_account::("proposer", 0); + let preimage_hash = note_preimage::(); + assert_ok!(Democracy::::set_metadata( + RawOrigin::Signed(proposer).into(), + MetadataOwner::Proposal(0), + Some(preimage_hash))); + let cancel_origin = T::CancelProposalOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; }: _(cancel_origin, 0) + verify { + assert_last_event::(crate::Event::MetadataCleared { + owner: MetadataOwner::Proposal(0), + hash: preimage_hash, + }.into()); + } cancel_referendum { - let ref_index = add_referendum::(0).0; + let (ref_index, _, preimage_hash) = add_referendum::(0); }: _(RawOrigin::Root, ref_index) + verify { + assert_last_event::(crate::Event::MetadataCleared { + owner: MetadataOwner::Referendum(0), + hash: preimage_hash, + }.into()); + } #[extra] on_initialize_external { @@ -313,7 +388,8 @@ benchmarks! { // Launch external LastTabledWasExternal::::put(false); - let origin = T::ExternalMajorityOrigin::successful_origin(); + let origin = T::ExternalMajorityOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; let proposal = make_proposal::(r); let call = Call::::external_propose_majority { proposal }; call.dispatch_bypass_filter(origin)?; @@ -666,6 +742,111 @@ benchmarks! { assert_eq!(votes.len(), (r - 1) as usize, "Vote was not removed"); } + set_external_metadata { + let origin = T::ExternalOrigin::try_successful_origin() + .expect("ExternalOrigin has no successful origin required for the benchmark"); + assert_ok!( + Democracy::::external_propose(origin.clone(), make_proposal::(0)) + ); + let owner = MetadataOwner::External; + let hash = note_preimage::(); + }: set_metadata(origin, owner.clone(), Some(hash)) + verify { + assert_last_event::(crate::Event::MetadataSet { + owner, + hash, + }.into()); + } + + clear_external_metadata { + let origin = T::ExternalOrigin::try_successful_origin() + .expect("ExternalOrigin has no successful origin required for the benchmark"); + assert_ok!( + Democracy::::external_propose(origin.clone(), make_proposal::(0)) + ); + let owner = MetadataOwner::External; + let proposer = funded_account::("proposer", 0); + let hash = note_preimage::(); + assert_ok!(Democracy::::set_metadata(origin.clone(), owner.clone(), Some(hash))); + }: set_metadata(origin, owner.clone(), None) + verify { + assert_last_event::(crate::Event::MetadataCleared { + owner, + hash, + }.into()); + } + + set_proposal_metadata { + // Place our proposal at the end to make sure it's worst case. + for i in 0 .. T::MaxProposals::get() { + add_proposal::(i)?; + } + let owner = MetadataOwner::Proposal(0); + let proposer = funded_account::("proposer", 0); + let hash = note_preimage::(); + }: set_metadata(RawOrigin::Signed(proposer).into(), owner.clone(), Some(hash)) + verify { + assert_last_event::(crate::Event::MetadataSet { + owner, + hash, + }.into()); + } + + clear_proposal_metadata { + // Place our proposal at the end to make sure it's worst case. + for i in 0 .. T::MaxProposals::get() { + add_proposal::(i)?; + } + let proposer = funded_account::("proposer", 0); + let owner = MetadataOwner::Proposal(0); + let hash = note_preimage::(); + assert_ok!(Democracy::::set_metadata( + RawOrigin::Signed(proposer.clone()).into(), + owner.clone(), + Some(hash))); + }: set_metadata(RawOrigin::Signed(proposer).into(), owner.clone(), None) + verify { + assert_last_event::(crate::Event::MetadataCleared { + owner, + hash, + }.into()); + } + + set_referendum_metadata { + // create not ongoing referendum. + ReferendumInfoOf::::insert( + 0, + ReferendumInfo::Finished { end: BlockNumberFor::::zero(), approved: true }, + ); + let owner = MetadataOwner::Referendum(0); + let caller = funded_account::("caller", 0); + let hash = note_preimage::(); + }: set_metadata(RawOrigin::Root.into(), owner.clone(), Some(hash)) + verify { + assert_last_event::(crate::Event::MetadataSet { + owner, + hash, + }.into()); + } + + clear_referendum_metadata { + // create not ongoing referendum. + ReferendumInfoOf::::insert( + 0, + ReferendumInfo::Finished { end: BlockNumberFor::::zero(), approved: true }, + ); + let owner = MetadataOwner::Referendum(0); + let hash = note_preimage::(); + MetadataOf::::insert(owner.clone(), hash); + let caller = funded_account::("caller", 0); + }: set_metadata(RawOrigin::Signed(caller).into(), owner.clone(), None) + verify { + assert_last_event::(crate::Event::MetadataCleared { + owner, + hash, + }.into()); + } + impl_benchmark_test_suite!( Democracy, crate::tests::new_test_ext(), diff --git a/frame/democracy/src/conviction.rs b/frame/democracy/src/conviction.rs index a938d8a4e685..d2f685f7d99e 100644 --- a/frame/democracy/src/conviction.rs +++ b/frame/democracy/src/conviction.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/democracy/src/lib.rs b/frame/democracy/src/lib.rs index 2c65e5d94bc4..e538d31c6ad0 100644 --- a/frame/democracy/src/lib.rs +++ b/frame/democracy/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -155,14 +155,17 @@ use codec::{Decode, Encode}; use frame_support::{ ensure, + error::BadOrigin, traits::{ defensive_prelude::*, schedule::{v3::Named as ScheduleNamed, DispatchTime}, - Bounded, Currency, Get, LockIdentifier, LockableCurrency, OnUnbalanced, QueryPreimage, - ReservableCurrency, StorePreimage, WithdrawReasons, + Bounded, Currency, EnsureOrigin, Get, Hash as PreimageHash, LockIdentifier, + LockableCurrency, OnUnbalanced, QueryPreimage, ReservableCurrency, StorePreimage, + WithdrawReasons, }, weights::Weight, }; +use frame_system::pallet_prelude::{BlockNumberFor, OriginFor}; use sp_runtime::{ traits::{Bounded as ArithBounded, One, Saturating, StaticLookup, Zero}, ArithmeticError, DispatchError, DispatchResult, @@ -176,7 +179,10 @@ mod vote_threshold; pub mod weights; pub use conviction::Conviction; pub use pallet::*; -pub use types::{Delegations, ReferendumInfo, ReferendumStatus, Tally, UnvoteScope}; +pub use types::{ + Delegations, MetadataOwner, PropIndex, ReferendumIndex, ReferendumInfo, ReferendumStatus, + Tally, UnvoteScope, +}; pub use vote::{AccountVote, Vote, Voting}; pub use vote_threshold::{Approved, VoteThreshold}; pub use weights::WeightInfo; @@ -189,13 +195,7 @@ pub mod benchmarking; pub mod migrations; -const DEMOCRACY_ID: LockIdentifier = *b"democrac"; - -/// A proposal index. -pub type PropIndex = u32; - -/// A referendum index. -pub type ReferendumIndex = u32; +pub(crate) const DEMOCRACY_ID: LockIdentifier = *b"democrac"; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -217,7 +217,6 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); @@ -227,14 +226,14 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; /// The Scheduler. - type Scheduler: ScheduleNamed, Self::PalletsOrigin>; + type Scheduler: ScheduleNamed, CallOf, Self::PalletsOrigin>; /// The Preimage provider. type Preimages: QueryPreimage + StorePreimage; /// Currency type for this pallet. type Currency: ReservableCurrency - + LockableCurrency; + + LockableCurrency>; /// The period between a proposal being approved and enacted. /// @@ -242,22 +241,22 @@ pub mod pallet { /// voting stakers have an opportunity to remove themselves from the system in the case /// where they are on the losing side of a vote. #[pallet::constant] - type EnactmentPeriod: Get; + type EnactmentPeriod: Get>; /// How often (in blocks) new public referenda are launched. #[pallet::constant] - type LaunchPeriod: Get; + type LaunchPeriod: Get>; /// How often (in blocks) to check for new votes. #[pallet::constant] - type VotingPeriod: Get; + type VotingPeriod: Get>; /// The minimum period of vote locking. /// /// It should be no shorter than enactment period to ensure that in the case of an approval, /// those successful voters are locked into the consequences that their votes entail. #[pallet::constant] - type VoteLockingPeriod: Get; + type VoteLockingPeriod: Get>; /// The minimum amount to be used as a deposit for a public referendum proposal. #[pallet::constant] @@ -271,11 +270,11 @@ pub mod pallet { /// Minimum voting period allowed for a fast-track referendum. #[pallet::constant] - type FastTrackVotingPeriod: Get; + type FastTrackVotingPeriod: Get>; /// Period in blocks where an external proposal may not be re-submitted after being vetoed. #[pallet::constant] - type CooloffPeriod: Get; + type CooloffPeriod: Get>; /// The maximum number of votes for an account. /// @@ -308,6 +307,11 @@ pub mod pallet { /// of a negative-turnout-bias (default-carries) referendum. type ExternalDefaultOrigin: EnsureOrigin; + /// Origin from which the new proposal can be made. + /// + /// The success variant is the account id of the depositor. + type SubmitOrigin: EnsureOrigin; + /// Origin from which the next majority-carries (or more permissive) referendum may be /// tabled to vote according to the `FastTrackVotingPeriod` asynchronously in a similar /// manner to the emergency origin. It retains its threshold method. @@ -383,7 +387,7 @@ pub mod pallet { _, Twox64Concat, ReferendumIndex, - ReferendumInfo, BalanceOf>, + ReferendumInfo, BoundedCallOf, BalanceOf>, >; /// All votes for a particular voter. We store the balance for the number of votes that we @@ -395,7 +399,7 @@ pub mod pallet { _, Twox64Concat, T::AccountId, - Voting, T::AccountId, T::BlockNumber, T::MaxVotes>, + Voting, T::AccountId, BlockNumberFor, T::MaxVotes>, ValueQuery, >; @@ -418,27 +422,31 @@ pub mod pallet { _, Identity, H256, - (T::BlockNumber, BoundedVec), + (BlockNumberFor, BoundedVec), >; /// Record of all proposals that have been subject to emergency cancellation. #[pallet::storage] pub type Cancellations = StorageMap<_, Identity, H256, bool, ValueQuery>; + /// General information concerning any proposal or referendum. + /// The `PreimageHash` refers to the preimage of the `Preimages` provider which can be a JSON + /// dump or IPFS hash of a JSON file. + /// + /// Consider a garbage collection for a metadata of finished referendums to `unrequest` (remove) + /// large preimages. + #[pallet::storage] + pub type MetadataOf = StorageMap<_, Blake2_128Concat, MetadataOwner, PreimageHash>; + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { - _phantom: sp_std::marker::PhantomData, - } - - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - GenesisConfig { _phantom: Default::default() } - } + #[serde(skip)] + _config: sp_std::marker::PhantomData, } #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { PublicPropCount::::put(0 as PropIndex); ReferendumCount::::put(0 as ReferendumIndex); @@ -468,7 +476,7 @@ pub mod pallet { /// An account has cancelled a previous delegation operation. Undelegated { account: T::AccountId }, /// An external proposal has been vetoed. - Vetoed { who: T::AccountId, proposal_hash: H256, until: T::BlockNumber }, + Vetoed { who: T::AccountId, proposal_hash: H256, until: BlockNumberFor }, /// A proposal_hash has been blacklisted permanently. Blacklisted { proposal_hash: H256 }, /// An account has voted in a referendum @@ -477,6 +485,29 @@ pub mod pallet { Seconded { seconder: T::AccountId, prop_index: PropIndex }, /// A proposal got canceled. ProposalCanceled { prop_index: PropIndex }, + /// Metadata for a proposal or a referendum has been set. + MetadataSet { + /// Metadata owner. + owner: MetadataOwner, + /// Preimage hash. + hash: PreimageHash, + }, + /// Metadata for a proposal or a referendum has been cleared. + MetadataCleared { + /// Metadata owner. + owner: MetadataOwner, + /// Preimage hash. + hash: PreimageHash, + }, + /// Metadata has been transferred to new owner. + MetadataTransferred { + /// Previous metadata owner. + prev_owner: MetadataOwner, + /// New metadata owner. + owner: MetadataOwner, + /// Preimage hash. + hash: PreimageHash, + }, } #[pallet::error] @@ -528,12 +559,14 @@ pub mod pallet { TooMany, /// Voting period too low VotingPeriodLow, + /// The preimage does not exist. + PreimageNotExist, } #[pallet::hooks] impl Hooks> for Pallet { /// Weight: see `begin_block` - fn on_initialize(n: T::BlockNumber) -> Weight { + fn on_initialize(n: BlockNumberFor) -> Weight { Self::begin_block(n) } } @@ -556,7 +589,7 @@ pub mod pallet { proposal: BoundedCallOf, #[pallet::compact] value: BalanceOf, ) -> DispatchResult { - let who = ensure_signed(origin)?; + let who = T::SubmitOrigin::ensure_origin(origin)?; ensure!(value >= T::MinimumDeposit::get(), Error::::ValueLow); let index = Self::public_prop_count(); @@ -743,8 +776,8 @@ pub mod pallet { pub fn fast_track( origin: OriginFor, proposal_hash: H256, - voting_period: T::BlockNumber, - delay: T::BlockNumber, + voting_period: BlockNumberFor, + delay: BlockNumberFor, ) -> DispatchResult { // Rather complicated bit of code to ensure that either: // - `voting_period` is at least `FastTrackVotingPeriod` and `origin` is @@ -762,7 +795,7 @@ pub mod pallet { ensure!(T::InstantAllowed::get(), Error::::InstantNotAllowed); } - ensure!(voting_period > T::BlockNumber::zero(), Error::::VotingPeriodLow); + ensure!(voting_period > Zero::zero(), Error::::VotingPeriodLow); let (ext_proposal, threshold) = >::get().ok_or(Error::::ProposalMissing)?; ensure!( @@ -773,12 +806,13 @@ pub mod pallet { >::kill(); let now = >::block_number(); - Self::inject_referendum( + let ref_index = Self::inject_referendum( now.saturating_add(voting_period), ext_proposal, threshold, delay, ); + Self::transfer_metadata(MetadataOwner::External, MetadataOwner::Referendum(ref_index)); Ok(()) } @@ -816,6 +850,7 @@ pub mod pallet { Self::deposit_event(Event::::Vetoed { who, proposal_hash, until }); >::kill(); + Self::clear_metadata(MetadataOwner::External); Ok(()) } @@ -1013,7 +1048,8 @@ pub mod pallet { T::BlacklistOrigin::ensure_origin(origin)?; // Insert the proposal into the blacklist. - let permanent = (T::BlockNumber::max_value(), BoundedVec::::default()); + let permanent = + (BlockNumberFor::::max_value(), BoundedVec::::default()); Blacklist::::insert(&proposal_hash, permanent); // Remove the queued proposal, if it's there. @@ -1025,12 +1061,14 @@ pub mod pallet { T::Slash::on_unbalanced(T::Currency::slash_reserved(&who, amount).0); } } + Self::clear_metadata(MetadataOwner::Proposal(prop_index)); } }); // Remove the external queued referendum, if it's there. if matches!(NextExternal::::get(), Some((p, ..)) if p.hash() == proposal_hash) { NextExternal::::kill(); + Self::clear_metadata(MetadataOwner::External); } // Remove the referendum, if it's there. @@ -1067,8 +1105,68 @@ pub mod pallet { T::Slash::on_unbalanced(T::Currency::slash_reserved(&who, amount).0); } } - Self::deposit_event(Event::::ProposalCanceled { prop_index }); + Self::clear_metadata(MetadataOwner::Proposal(prop_index)); + Ok(()) + } + + /// Set or clear a metadata of a proposal or a referendum. + /// + /// Parameters: + /// - `origin`: Must correspond to the `MetadataOwner`. + /// - `ExternalOrigin` for an external proposal with the `SuperMajorityApprove` + /// threshold. + /// - `ExternalDefaultOrigin` for an external proposal with the `SuperMajorityAgainst` + /// threshold. + /// - `ExternalMajorityOrigin` for an external proposal with the `SimpleMajority` + /// threshold. + /// - `Signed` by a creator for a public proposal. + /// - `Signed` to clear a metadata for a finished referendum. + /// - `Root` to set a metadata for an ongoing referendum. + /// - `owner`: an identifier of a metadata owner. + /// - `maybe_hash`: The hash of an on-chain stored preimage. `None` to clear a metadata. + #[pallet::call_index(18)] + #[pallet::weight( + match (owner, maybe_hash) { + (MetadataOwner::External, Some(_)) => T::WeightInfo::set_external_metadata(), + (MetadataOwner::External, None) => T::WeightInfo::clear_external_metadata(), + (MetadataOwner::Proposal(_), Some(_)) => T::WeightInfo::set_proposal_metadata(), + (MetadataOwner::Proposal(_), None) => T::WeightInfo::clear_proposal_metadata(), + (MetadataOwner::Referendum(_), Some(_)) => T::WeightInfo::set_referendum_metadata(), + (MetadataOwner::Referendum(_), None) => T::WeightInfo::clear_referendum_metadata(), + } + )] + pub fn set_metadata( + origin: OriginFor, + owner: MetadataOwner, + maybe_hash: Option, + ) -> DispatchResult { + match owner { + MetadataOwner::External => { + let (_, threshold) = >::get().ok_or(Error::::NoProposal)?; + Self::ensure_external_origin(threshold, origin)?; + }, + MetadataOwner::Proposal(index) => { + let who = ensure_signed(origin)?; + let (_, _, proposer) = Self::proposal(index)?; + ensure!(proposer == who, Error::::NoPermission); + }, + MetadataOwner::Referendum(index) => { + let is_root = ensure_signed_or_root(origin)?.is_none(); + ensure!(is_root || maybe_hash.is_none(), Error::::NoPermission); + ensure!( + is_root || Self::referendum_status(index).is_err(), + Error::::NoPermission + ); + }, + } + if let Some(hash) = maybe_hash { + ensure!(T::Preimages::len(&hash).is_some(), Error::::PreimageNotExist); + MetadataOf::::insert(owner.clone(), hash); + Self::deposit_event(Event::::MetadataSet { owner, hash }); + } else { + Self::clear_metadata(owner); + } Ok(()) } } @@ -1104,17 +1202,19 @@ impl Pallet { /// Get all referenda ready for tally at block `n`. pub fn maturing_referenda_at( - n: T::BlockNumber, - ) -> Vec<(ReferendumIndex, ReferendumStatus, BalanceOf>)> { + n: BlockNumberFor, + ) -> Vec<(ReferendumIndex, ReferendumStatus, BoundedCallOf, BalanceOf>)> + { let next = Self::lowest_unbaked(); let last = Self::referendum_count(); Self::maturing_referenda_at_inner(n, next..last) } fn maturing_referenda_at_inner( - n: T::BlockNumber, + n: BlockNumberFor, range: core::ops::Range, - ) -> Vec<(ReferendumIndex, ReferendumStatus, BalanceOf>)> { + ) -> Vec<(ReferendumIndex, ReferendumStatus, BoundedCallOf, BalanceOf>)> + { range .into_iter() .map(|i| (i, Self::referendum_info(i))) @@ -1132,7 +1232,7 @@ impl Pallet { pub fn internal_start_referendum( proposal: BoundedCallOf, threshold: VoteThreshold, - delay: T::BlockNumber, + delay: BlockNumberFor, ) -> ReferendumIndex { >::inject_referendum( >::block_number().saturating_add(T::VotingPeriod::get()), @@ -1146,14 +1246,16 @@ impl Pallet { pub fn internal_cancel_referendum(ref_index: ReferendumIndex) { Self::deposit_event(Event::::Cancelled { ref_index }); ReferendumInfoOf::::remove(ref_index); + Self::clear_metadata(MetadataOwner::Referendum(ref_index)); } // private. /// Ok if the given referendum is active, Err otherwise fn ensure_ongoing( - r: ReferendumInfo, BalanceOf>, - ) -> Result, BalanceOf>, DispatchError> { + r: ReferendumInfo, BoundedCallOf, BalanceOf>, + ) -> Result, BoundedCallOf, BalanceOf>, DispatchError> + { match r { ReferendumInfo::Ongoing(s) => Ok(s), _ => Err(Error::::ReferendumInvalid.into()), @@ -1162,7 +1264,8 @@ impl Pallet { fn referendum_status( ref_index: ReferendumIndex, - ) -> Result, BalanceOf>, DispatchError> { + ) -> Result, BoundedCallOf, BalanceOf>, DispatchError> + { let info = ReferendumInfoOf::::get(ref_index).ok_or(Error::::ReferendumInvalid)?; Self::ensure_ongoing(info) } @@ -1205,7 +1308,12 @@ impl Pallet { })?; // Extend the lock to `balance` (rather than setting it) since we don't know what other // votes are in place. - T::Currency::extend_lock(DEMOCRACY_ID, who, vote.balance(), WithdrawReasons::TRANSFER); + T::Currency::extend_lock( + DEMOCRACY_ID, + who, + vote.balance(), + WithdrawReasons::except(WithdrawReasons::RESERVE), + ); ReferendumInfoOf::::insert(ref_index, ReferendumInfo::Ongoing(status)); Ok(()) } @@ -1351,7 +1459,12 @@ impl Pallet { let votes = Self::increase_upstream_delegation(&target, conviction.votes(balance)); // Extend the lock to `balance` (rather than setting it) since we don't know what other // votes are in place. - T::Currency::extend_lock(DEMOCRACY_ID, &who, balance, WithdrawReasons::TRANSFER); + T::Currency::extend_lock( + DEMOCRACY_ID, + &who, + balance, + WithdrawReasons::except(WithdrawReasons::RESERVE), + ); Ok(votes) })?; Self::deposit_event(Event::::Delegated { who, target }); @@ -1396,16 +1509,21 @@ impl Pallet { if lock_needed.is_zero() { T::Currency::remove_lock(DEMOCRACY_ID, who); } else { - T::Currency::set_lock(DEMOCRACY_ID, who, lock_needed, WithdrawReasons::TRANSFER); + T::Currency::set_lock( + DEMOCRACY_ID, + who, + lock_needed, + WithdrawReasons::except(WithdrawReasons::RESERVE), + ); } } /// Start a referendum fn inject_referendum( - end: T::BlockNumber, + end: BlockNumberFor, proposal: BoundedCallOf, threshold: VoteThreshold, - delay: T::BlockNumber, + delay: BlockNumberFor, ) -> ReferendumIndex { let ref_index = Self::referendum_count(); ReferendumCount::::put(ref_index + 1); @@ -1418,7 +1536,7 @@ impl Pallet { } /// Table the next waiting proposal for a vote. - fn launch_next(now: T::BlockNumber) -> DispatchResult { + fn launch_next(now: BlockNumberFor) -> DispatchResult { if LastTabledWasExternal::::take() { Self::launch_public(now).or_else(|_| Self::launch_external(now)) } else { @@ -1428,16 +1546,17 @@ impl Pallet { } /// Table the waiting external proposal for a vote, if there is one. - fn launch_external(now: T::BlockNumber) -> DispatchResult { + fn launch_external(now: BlockNumberFor) -> DispatchResult { if let Some((proposal, threshold)) = >::take() { LastTabledWasExternal::::put(true); Self::deposit_event(Event::::ExternalTabled); - Self::inject_referendum( + let ref_index = Self::inject_referendum( now.saturating_add(T::VotingPeriod::get()), proposal, threshold, T::EnactmentPeriod::get(), ); + Self::transfer_metadata(MetadataOwner::External, MetadataOwner::Referendum(ref_index)); Ok(()) } else { return Err(Error::::NoneWaiting.into()) @@ -1445,7 +1564,7 @@ impl Pallet { } /// Table the waiting public proposal with the highest backing for a vote. - fn launch_public(now: T::BlockNumber) -> DispatchResult { + fn launch_public(now: BlockNumberFor) -> DispatchResult { let mut public_props = Self::public_props(); if let Some((winner_index, _)) = public_props.iter().enumerate().max_by_key( // defensive only: All current public proposals have an amount locked @@ -1460,12 +1579,16 @@ impl Pallet { T::Currency::unreserve(d, deposit); } Self::deposit_event(Event::::Tabled { proposal_index: prop_index, deposit }); - Self::inject_referendum( + let ref_index = Self::inject_referendum( now.saturating_add(T::VotingPeriod::get()), proposal, VoteThreshold::SuperMajorityApprove, T::EnactmentPeriod::get(), ); + Self::transfer_metadata( + MetadataOwner::Proposal(prop_index), + MetadataOwner::Referendum(ref_index), + ) } Ok(()) } else { @@ -1474,20 +1597,15 @@ impl Pallet { } fn bake_referendum( - now: T::BlockNumber, + now: BlockNumberFor, index: ReferendumIndex, - status: ReferendumStatus, BalanceOf>, + status: ReferendumStatus, BoundedCallOf, BalanceOf>, ) -> bool { let total_issuance = T::Currency::total_issuance(); let approved = status.threshold.approved(status.tally, total_issuance); if approved { Self::deposit_event(Event::::Passed { ref_index: index }); - // Actually `hold` the proposal now since we didn't hold it when it came in via the - // submit extrinsic and we now know that it will be needed. This will be reversed by - // Scheduler pallet once it is executed which assumes that we will already have placed - // a `hold` on it. - T::Preimages::hold(&status.proposal); // Earliest it can be scheduled for is next block. let when = now.saturating_add(status.delay.max(One::one())); @@ -1513,16 +1631,10 @@ impl Pallet { /// Current era is ending; we should finish up any proposals. /// /// - /// # + /// ## Complexity: /// If a referendum is launched or maturing, this will take full block weight if queue is not - /// empty. Otherwise: - /// - Complexity: `O(R)` where `R` is the number of unbaked referenda. - /// - Db reads: `LastTabledWasExternal`, `NextExternal`, `PublicProps`, `account`, - /// `ReferendumCount`, `LowestUnbaked` - /// - Db writes: `PublicProps`, `account`, `ReferendumCount`, `DepositOf`, `ReferendumInfoOf` - /// - Db reads per R: `DepositOf`, `ReferendumInfoOf` - /// # - fn begin_block(now: T::BlockNumber) -> Weight { + /// empty. Otherwise, `O(R)` where `R` is the number of unbaked referenda. + fn begin_block(now: BlockNumberFor) -> Weight { let max_block_weight = T::BlockWeights::get().max_block; let mut weight = Weight::zero(); @@ -1578,6 +1690,52 @@ impl Pallet { // `Compact`. decode_compact_u32_at(&>::hashed_key_for(proposal)) } + + /// Return a proposal of an index. + fn proposal(index: PropIndex) -> Result<(PropIndex, BoundedCallOf, T::AccountId), Error> { + PublicProps::::get() + .into_iter() + .find(|(prop_index, _, _)| prop_index == &index) + .ok_or(Error::::ProposalMissing) + } + + /// Clear metadata if exist for a given owner. + fn clear_metadata(owner: MetadataOwner) { + if let Some(hash) = MetadataOf::::take(&owner) { + Self::deposit_event(Event::::MetadataCleared { owner, hash }); + } + } + + /// Transfer the metadata of an `owner` to a `new_owner`. + fn transfer_metadata(owner: MetadataOwner, new_owner: MetadataOwner) { + if let Some(hash) = MetadataOf::::take(&owner) { + MetadataOf::::insert(&new_owner, hash); + Self::deposit_event(Event::::MetadataTransferred { + prev_owner: owner, + owner: new_owner, + hash, + }); + } + } + + /// Ensure external origin for corresponding vote threshold. + fn ensure_external_origin( + threshold: VoteThreshold, + origin: OriginFor, + ) -> Result<(), BadOrigin> { + match threshold { + VoteThreshold::SuperMajorityApprove => { + let _ = T::ExternalOrigin::ensure_origin(origin)?; + }, + VoteThreshold::SuperMajorityAgainst => { + let _ = T::ExternalDefaultOrigin::ensure_origin(origin)?; + }, + VoteThreshold::SimpleMajority => { + let _ = T::ExternalMajorityOrigin::ensure_origin(origin)?; + }, + }; + Ok(()) + } } /// Decode `Compact` from the trie at given key. diff --git a/frame/democracy/src/migrations/mod.rs b/frame/democracy/src/migrations/mod.rs new file mode 100644 index 000000000000..3f77e03884ed --- /dev/null +++ b/frame/democracy/src/migrations/mod.rs @@ -0,0 +1,22 @@ +// This file is part of Substrate. +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! All migrations of this pallet. + +/// Migration to unlock and unreserve all pallet funds. +pub mod unlock_and_unreserve_all_funds; + +/// V1 storage migrations for the preimage pallet. +pub mod v1; diff --git a/frame/democracy/src/migrations/unlock_and_unreserve_all_funds.rs b/frame/democracy/src/migrations/unlock_and_unreserve_all_funds.rs new file mode 100644 index 000000000000..1f46070d1d7f --- /dev/null +++ b/frame/democracy/src/migrations/unlock_and_unreserve_all_funds.rs @@ -0,0 +1,360 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A migration that unreserves all deposit and unlocks all stake held in the context of this +//! pallet. + +use crate::{BalanceOf, DEMOCRACY_ID}; +use core::iter::Sum; +use frame_support::traits::{LockableCurrency, OnRuntimeUpgrade, ReservableCurrency}; +use sp_core::Get; +use sp_runtime::{traits::Zero, Saturating}; +use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; + +const LOG_TARGET: &str = "runtime::democracy::migrations::unlock_and_unreserve_all_funds"; + +/// A migration that unreserves all deposit and unlocks all stake held in the context of this +/// pallet. +/// +/// Useful to prevent funds from being locked up when the pallet is being deprecated. +/// +/// The pallet should be made inoperable before this migration is run. +/// +/// (See also [`RemovePallet`][frame_support::migrations::RemovePallet]) +pub struct UnlockAndUnreserveAllFunds(sp_std::marker::PhantomData); + +impl UnlockAndUnreserveAllFunds { + /// Calculates and returns the total amounts reserved by each account by this pallet, and all + /// accounts with locks in the context of this pallet. + /// + /// There is no need to return the amount locked, because the entire lock is removed (always + /// should be zero post-migration). We need to return the amounts reserved to check that the + /// reserved amount is deducted correctly. + /// + /// # Returns + /// + /// This function returns a tuple of two `BTreeMap` collections and the weight of the reads: + /// + /// * `BTreeMap>`: Map of account IDs to their respective total + /// reserved balance by this pallet + /// * `BTreeMap>`: Map of account IDs to their respective total + /// locked balance by this pallet + /// * `frame_support::weights::Weight`: the weight consumed by this call. + fn get_account_deposits_and_locks() -> ( + BTreeMap>, + BTreeMap>, + frame_support::weights::Weight, + ) { + let mut deposit_of_len = 0; + + // Get all deposits (reserved). + let mut total_voting_vec_entries: u64 = 0; + let account_deposits: BTreeMap> = crate::DepositOf::::iter() + .flat_map(|(_prop_index, (accounts, balance))| { + // Count the number of deposits + deposit_of_len.saturating_inc(); + + // Track the total number of vec entries to calculate the weight of the reads. + total_voting_vec_entries.saturating_accrue(accounts.len() as u64); + + // Create a vec of tuples where each account is associated with the given balance + accounts.into_iter().map(|account| (account, balance)).collect::>() + }) + .fold(BTreeMap::new(), |mut acc, (account, balance)| { + // Add the balance to the account's existing balance in the accumulator + acc.entry(account.clone()).or_insert(Zero::zero()).saturating_accrue(balance); + acc + }); + + // Voter accounts have amounts locked. + let account_stakes: BTreeMap> = crate::VotingOf::::iter() + .map(|(account_id, voting)| (account_id, voting.locked_balance())) + .collect(); + let voting_of_len = account_stakes.len() as u64; + + ( + account_deposits, + account_stakes, + T::DbWeight::get().reads( + deposit_of_len.saturating_add(voting_of_len).saturating_add( + // Max items in a Voting enum is MaxVotes + 5 + total_voting_vec_entries + .saturating_mul(T::MaxVotes::get().saturating_add(5) as u64), + ), + ), + ) + } +} + +impl OnRuntimeUpgrade for UnlockAndUnreserveAllFunds +where + BalanceOf: Sum, +{ + /// Collects pre-migration data useful for validating the migration was successful, and also + /// checks the integrity of deposited and reserved balances. + /// + /// Steps: + /// 1. Gets the deposited balances for each account stored in this pallet. + /// 2. Collects actual pre-migration reserved balances for each account. + /// 3. Checks the integrity of the deposited balances. + /// 4. Prints summary statistics about the state to be migrated. + /// 5. Encodes and returns pre-migration data to be used in post_upgrade. + /// + /// Fails with a `TryRuntimeError` if somehow the amount reserved by this pallet is greater than + /// the actual total reserved amount for any accounts. + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + use codec::Encode; + use sp_std::collections::btree_set::BTreeSet; + + // Get staked and deposited balances as reported by this pallet. + let (account_deposits, account_locks, _) = Self::get_account_deposits_and_locks(); + + let all_accounts = account_deposits + .keys() + .chain(account_locks.keys()) + .cloned() + .collect::>(); + let account_reserved_before: BTreeMap> = account_deposits + .keys() + .map(|account| (account.clone(), T::Currency::reserved_balance(&account))) + .collect(); + + // Total deposited for each account *should* be less than or equal to the total reserved, + // however this does not hold for all cases due to bugs in the reserve logic of this pallet. + let bugged_deposits = all_accounts + .iter() + .filter(|account| { + account_deposits.get(&account).unwrap_or(&Zero::zero()) > + account_reserved_before.get(&account).unwrap_or(&Zero::zero()) + }) + .count(); + + let total_deposits_to_unreserve = + account_deposits.clone().into_values().sum::>(); + let total_stake_to_unlock = account_locks.clone().into_values().sum::>(); + + log::info!(target: LOG_TARGET, "Total accounts: {:?}", all_accounts.len()); + log::info!(target: LOG_TARGET, "Total stake to unlock: {:?}", total_stake_to_unlock); + log::info!( + target: LOG_TARGET, + "Total deposit to unreserve: {:?}", + total_deposits_to_unreserve + ); + log::info!( + target: LOG_TARGET, + "Bugged deposits: {}/{}", + bugged_deposits, + account_deposits.len() + ); + + Ok(account_reserved_before.encode()) + } + + /// Executes the migration. + /// + /// Steps: + /// 1. Retrieves the deposit and accounts with locks for the pallet. + /// 2. Unreserves the deposited funds for each account. + /// 3. Unlocks the staked funds for each account. + fn on_runtime_upgrade() -> frame_support::weights::Weight { + // Get staked and deposited balances as reported by this pallet. + let (account_deposits, account_stakes, initial_reads) = + Self::get_account_deposits_and_locks(); + + // Deposited funds need to be unreserved. + for (account, unreserve_amount) in account_deposits.iter() { + if unreserve_amount.is_zero() { + log::warn!(target: LOG_TARGET, "Unexpected zero amount to unreserve!"); + continue + } + T::Currency::unreserve(&account, *unreserve_amount); + } + + // Staked funds need to be unlocked. + for account in account_stakes.keys() { + T::Currency::remove_lock(DEMOCRACY_ID, account); + } + + T::DbWeight::get() + .reads_writes( + account_stakes.len().saturating_add(account_deposits.len()) as u64, + account_stakes.len().saturating_add(account_deposits.len()) as u64, + ) + .saturating_add(initial_reads) + } + + /// Performs post-upgrade sanity checks: + /// + /// 1. No locks remain for this pallet in Balances. + /// 2. The reserved balance for each account has been reduced by the expected amount. + #[cfg(feature = "try-runtime")] + fn post_upgrade( + account_reserved_before_bytes: Vec, + ) -> Result<(), sp_runtime::TryRuntimeError> { + use codec::Decode; + + let account_reserved_before = + BTreeMap::>::decode(&mut &account_reserved_before_bytes[..]) + .map_err(|_| "Failed to decode account_reserved_before_bytes")?; + + // Get staked and deposited balances as reported by this pallet. + let (account_deposits, _, _) = Self::get_account_deposits_and_locks(); + + // Check that the reserved balance is reduced by the expected deposited amount. + for (account, actual_reserved_before) in account_reserved_before { + let actual_reserved_after = T::Currency::reserved_balance(&account); + let expected_amount_deducted = *account_deposits + .get(&account) + .expect("account deposit must exist to be in pre_migration_data, qed"); + let expected_reserved_after = + actual_reserved_before.saturating_sub(expected_amount_deducted); + assert!( + actual_reserved_after == expected_reserved_after, + "Reserved balance for {:?} is incorrect. actual before: {:?}, actual after, {:?}, expected deducted: {:?}", + account, + actual_reserved_before, + actual_reserved_after, + expected_amount_deducted, + ); + } + + Ok(()) + } +} + +#[cfg(all(feature = "try-runtime", test))] +mod test { + use super::*; + use crate::{ + tests::{new_test_ext, Test}, + DepositOf, Voting, VotingOf, + }; + use frame_support::{ + assert_ok, + traits::{Currency, OnRuntimeUpgrade, ReservableCurrency, WithdrawReasons}, + BoundedVec, + }; + + #[test] + fn unreserve_works_for_depositer() { + let depositer_0 = 10; + let depositer_1 = 11; + let deposit = 25; + let depositer_0_initial_reserved = 0; + let depositer_1_initial_reserved = 15; + let initial_balance = 100_000; + new_test_ext().execute_with(|| { + // Set up initial state. + ::Currency::make_free_balance_be(&depositer_0, initial_balance); + ::Currency::make_free_balance_be(&depositer_1, initial_balance); + assert_ok!(::Currency::reserve( + &depositer_0, + depositer_0_initial_reserved + deposit + )); + assert_ok!(::Currency::reserve( + &depositer_1, + depositer_1_initial_reserved + deposit + )); + let depositors = + BoundedVec::<_, ::MaxDeposits>::truncate_from(vec![ + depositer_0, + depositer_1, + ]); + DepositOf::::insert(0, (depositors, deposit)); + + // Sanity check: ensure initial reserved balance was set correctly. + assert_eq!( + ::Currency::reserved_balance(&depositer_0), + depositer_0_initial_reserved + deposit + ); + assert_eq!( + ::Currency::reserved_balance(&depositer_1), + depositer_1_initial_reserved + deposit + ); + + // Run the migration. + let bytes = UnlockAndUnreserveAllFunds::::pre_upgrade() + .unwrap_or_else(|e| panic!("pre_upgrade failed: {:?}", e)); + UnlockAndUnreserveAllFunds::::on_runtime_upgrade(); + assert_ok!(UnlockAndUnreserveAllFunds::::post_upgrade(bytes)); + + // Assert the reserved balance was reduced by the expected amount. + assert_eq!( + ::Currency::reserved_balance(&depositer_0), + depositer_0_initial_reserved + ); + assert_eq!( + ::Currency::reserved_balance(&depositer_1), + depositer_1_initial_reserved + ); + }); + } + + #[test] + fn unlock_works_for_voter() { + let voter = 10; + let stake = 25; + let initial_locks = vec![(b"somethin", 10)]; + let initial_balance = 100_000; + new_test_ext().execute_with(|| { + // Set up initial state. + ::Currency::make_free_balance_be(&voter, initial_balance); + for lock in initial_locks.clone() { + ::Currency::set_lock( + *lock.0, + &voter, + lock.1, + WithdrawReasons::all(), + ); + } + VotingOf::::insert(voter, Voting::default()); + ::Currency::set_lock( + DEMOCRACY_ID, + &voter, + stake, + WithdrawReasons::all(), + ); + + // Sanity check: ensure initial Balance state was set up correctly. + let mut voter_all_locks = initial_locks.clone(); + voter_all_locks.push((&DEMOCRACY_ID, stake)); + assert_eq!( + ::Currency::locks(&voter) + .iter() + .map(|lock| (&lock.id, lock.amount)) + .collect::>(), + voter_all_locks + ); + + // Run the migration. + let bytes = UnlockAndUnreserveAllFunds::::pre_upgrade() + .unwrap_or_else(|e| panic!("pre_upgrade failed: {:?}", e)); + UnlockAndUnreserveAllFunds::::on_runtime_upgrade(); + assert_ok!(UnlockAndUnreserveAllFunds::::post_upgrade(bytes)); + + // Assert the voter lock was removed + assert_eq!( + ::Currency::locks(&voter) + .iter() + .map(|lock| (&lock.id, lock.amount)) + .collect::>(), + initial_locks + ); + }); + } +} diff --git a/frame/democracy/src/migrations.rs b/frame/democracy/src/migrations/v1.rs similarity index 89% rename from frame/democracy/src/migrations.rs rename to frame/democracy/src/migrations/v1.rs index 3ec249c1d981..27a500a615cf 100644 --- a/frame/democracy/src/migrations.rs +++ b/frame/democracy/src/migrations/v1.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,8 +17,9 @@ //! Storage migrations for the preimage pallet. -use super::*; +use crate::*; use frame_support::{pallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade, BoundedVec}; +use frame_system::pallet_prelude::BlockNumberFor; use sp_core::H256; /// The log target. @@ -45,11 +46,7 @@ mod v0 { Pallet, frame_support::Twox64Concat, ReferendumIndex, - ReferendumInfo< - ::BlockNumber, - ::Hash, - BalanceOf, - >, + ReferendumInfo, ::Hash, BalanceOf>, >; } @@ -61,12 +58,12 @@ pub mod v1 { impl> OnRuntimeUpgrade for Migration { #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - assert_eq!(StorageVersion::get::>(), 0, "can only upgrade from version 0"); + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + ensure!(StorageVersion::get::>() == 0, "can only upgrade from version 0"); let props_count = v0::PublicProps::::get().len(); log::info!(target: TARGET, "{} public proposals will be migrated.", props_count,); - ensure!(props_count <= T::MaxProposals::get() as usize, "too many proposals"); + ensure!(props_count <= T::MaxProposals::get() as usize, Error::::TooMany); let referenda_count = v0::ReferendumInfoOf::::iter().count(); log::info!(target: TARGET, "{} referenda will be migrated.", referenda_count); @@ -87,7 +84,7 @@ pub mod v1 { } ReferendumInfoOf::::translate( - |index, old: ReferendumInfo>| { + |index, old: ReferendumInfo, T::Hash, BalanceOf>| { weight.saturating_accrue(T::DbWeight::get().reads_writes(1, 1)); log::info!(target: TARGET, "migrating referendum #{:?}", &index); Some(match old { @@ -133,15 +130,15 @@ pub mod v1 { } #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), &'static str> { - assert_eq!(StorageVersion::get::>(), 1, "must upgrade"); + fn post_upgrade(state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + ensure!(StorageVersion::get::>() == 1, "must upgrade"); let (old_props_count, old_ref_count): (u32, u32) = Decode::decode(&mut &state[..]).expect("pre_upgrade provides a valid state; qed"); let new_props_count = crate::PublicProps::::get().len() as u32; - assert_eq!(new_props_count, old_props_count, "must migrate all public proposals"); + ensure!(new_props_count == old_props_count, "must migrate all public proposals"); let new_ref_count = crate::ReferendumInfoOf::::iter().count() as u32; - assert_eq!(new_ref_count, old_ref_count, "must migrate all referenda"); + ensure!(new_ref_count == old_ref_count, "must migrate all referenda"); log::info!( target: TARGET, diff --git a/frame/democracy/src/tests.rs b/frame/democracy/src/tests.rs index 41b279035028..e5cfcc5b4002 100644 --- a/frame/democracy/src/tests.rs +++ b/frame/democracy/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,18 +22,17 @@ use crate as pallet_democracy; use frame_support::{ assert_noop, assert_ok, ord_parameter_types, parameter_types, traits::{ - ConstU32, ConstU64, Contains, EqualPrivilegeOnly, GenesisBuild, OnInitialize, - SortedMembers, StorePreimage, + ConstU32, ConstU64, Contains, EqualPrivilegeOnly, OnInitialize, SortedMembers, + StorePreimage, }, weights::Weight, }; -use frame_system::{EnsureRoot, EnsureSignedBy}; +use frame_system::{EnsureRoot, EnsureSigned, EnsureSignedBy}; use pallet_balances::{BalanceLock, Error as BalancesError}; use sp_core::H256; use sp_runtime::{ - testing::Header, - traits::{BadOrigin, BlakeTwo256, IdentityLookup}, - Perbill, + traits::{BadOrigin, BlakeTwo256, Hash, IdentityLookup}, + BuildStorage, Perbill, }; mod cancellation; mod decoders; @@ -41,6 +40,7 @@ mod delegation; mod external_proposing; mod fast_tracking; mod lock_voting; +mod metadata; mod public_proposals; mod scheduling; mod voting; @@ -50,16 +50,12 @@ const NAY: Vote = Vote { aye: false, conviction: Conviction::None }; const BIG_AYE: Vote = Vote { aye: true, conviction: Conviction::Locked1x }; const BIG_NAY: Vote = Vote { aye: false, conviction: Conviction::Locked1x }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Preimage: pallet_preimage, Scheduler: pallet_scheduler::{Pallet, Call, Storage, Event}, @@ -71,7 +67,7 @@ frame_support::construct_runtime!( pub struct BaseFilter; impl Contains for BaseFilter { fn contains(call: &RuntimeCall) -> bool { - !matches!(call, &RuntimeCall::Balances(pallet_balances::Call::set_balance { .. })) + !matches!(call, &RuntimeCall::Balances(pallet_balances::Call::force_set_balance { .. })) } } @@ -87,14 +83,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -143,6 +138,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } parameter_types! { pub static PreimageByteDeposit: u64 = 0; @@ -176,6 +175,7 @@ impl Config for Test { type MinimumDeposit = ConstU64<1>; type MaxDeposits = ConstU32<1000>; type MaxBlacklisted = ConstU32<5>; + type SubmitOrigin = EnsureSigned; type ExternalOrigin = EnsureSignedBy; type ExternalMajorityOrigin = EnsureSignedBy; type ExternalDefaultOrigin = EnsureSignedBy; @@ -197,7 +197,7 @@ impl Config for Test { } pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], } @@ -221,7 +221,7 @@ fn params_should_work() { } fn set_balance_proposal(value: u64) -> BoundedCallOf { - let inner = pallet_balances::Call::set_balance { who: 42, new_free: value, new_reserved: 0 }; + let inner = pallet_balances::Call::force_set_balance { who: 42, new_free: value }; let outer = RuntimeCall::Balances(inner); Preimage::bound(outer).unwrap() } @@ -276,3 +276,15 @@ fn big_nay(who: u64) -> AccountVote { fn tally(r: ReferendumIndex) -> Tally { Democracy::referendum_status(r).unwrap().tally } + +/// note a new preimage without registering. +fn note_preimage(who: u64) -> PreimageHash { + use std::sync::atomic::{AtomicU8, Ordering}; + // note a new preimage on every function invoke. + static COUNTER: AtomicU8 = AtomicU8::new(0); + let data = vec![COUNTER.fetch_add(1, Ordering::Relaxed)]; + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(who), data.clone())); + let hash = BlakeTwo256::hash(&data); + assert!(!Preimage::is_requested(&hash)); + hash +} diff --git a/frame/democracy/src/tests/cancellation.rs b/frame/democracy/src/tests/cancellation.rs index ff046d612c02..4384fe6a1641 100644 --- a/frame/democracy/src/tests/cancellation.rs +++ b/frame/democracy/src/tests/cancellation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/democracy/src/tests/decoders.rs b/frame/democracy/src/tests/decoders.rs index 1c8b9c3d980f..9cd725d97f64 100644 --- a/frame/democracy/src/tests/decoders.rs +++ b/frame/democracy/src/tests/decoders.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/democracy/src/tests/delegation.rs b/frame/democracy/src/tests/delegation.rs index bca7cb952411..710faf919238 100644 --- a/frame/democracy/src/tests/delegation.rs +++ b/frame/democracy/src/tests/delegation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/democracy/src/tests/external_proposing.rs b/frame/democracy/src/tests/external_proposing.rs index 4cfdd2aa74a3..08b497ab4b90 100644 --- a/frame/democracy/src/tests/external_proposing.rs +++ b/frame/democracy/src/tests/external_proposing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/democracy/src/tests/fast_tracking.rs b/frame/democracy/src/tests/fast_tracking.rs index 97bb7a63908a..85e7792a4c2e 100644 --- a/frame/democracy/src/tests/fast_tracking.rs +++ b/frame/democracy/src/tests/fast_tracking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,6 +32,14 @@ fn fast_track_referendum_works() { RuntimeOrigin::signed(3), set_balance_proposal(2) )); + let hash = note_preimage(1); + assert!(>::get(MetadataOwner::External).is_none()); + assert_ok!(Democracy::set_metadata( + RuntimeOrigin::signed(3), + MetadataOwner::External, + Some(hash), + ),); + assert!(>::get(MetadataOwner::External).is_some()); assert_noop!(Democracy::fast_track(RuntimeOrigin::signed(1), h, 3, 2), BadOrigin); assert_ok!(Democracy::fast_track(RuntimeOrigin::signed(5), h, 2, 0)); assert_eq!( @@ -44,6 +52,9 @@ fn fast_track_referendum_works() { tally: Tally { ayes: 0, nays: 0, turnout: 0 }, }) ); + // metadata reset from the external proposal to the referendum. + assert!(>::get(MetadataOwner::External).is_none()); + assert!(>::get(MetadataOwner::Referendum(0)).is_some()); }); } diff --git a/frame/democracy/src/tests/lock_voting.rs b/frame/democracy/src/tests/lock_voting.rs index 540198ecf33a..31f2e3f3dcc2 100644 --- a/frame/democracy/src/tests/lock_voting.rs +++ b/frame/democracy/src/tests/lock_voting.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,7 +34,7 @@ fn nay(x: u8, balance: u64) -> AccountVote { } fn the_lock(amount: u64) -> BalanceLock { - BalanceLock { id: DEMOCRACY_ID, amount, reasons: pallet_balances::Reasons::Misc } + BalanceLock { id: DEMOCRACY_ID, amount, reasons: pallet_balances::Reasons::All } } #[test] diff --git a/frame/democracy/src/tests/metadata.rs b/frame/democracy/src/tests/metadata.rs new file mode 100644 index 000000000000..5a36d80b7263 --- /dev/null +++ b/frame/democracy/src/tests/metadata.rs @@ -0,0 +1,209 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The tests for functionality concerning the metadata. + +use super::*; + +#[test] +fn set_external_metadata_works() { + new_test_ext().execute_with(|| { + use frame_support::traits::Hash as PreimageHash; + // invalid preimage hash. + let invalid_hash: PreimageHash = [1u8; 32].into(); + // metadata owner is an external proposal. + let owner = MetadataOwner::External; + // fails to set metadata if an external proposal does not exist. + assert_noop!( + Democracy::set_metadata(RuntimeOrigin::signed(2), owner.clone(), Some(invalid_hash)), + Error::::NoProposal, + ); + // create an external proposal. + assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2))); + assert!(>::exists()); + // fails to set metadata with non external origin. + assert_noop!( + Democracy::set_metadata(RuntimeOrigin::signed(1), owner.clone(), Some(invalid_hash)), + BadOrigin, + ); + // fails to set non-existing preimage. + assert_noop!( + Democracy::set_metadata(RuntimeOrigin::signed(2), owner.clone(), Some(invalid_hash)), + Error::::PreimageNotExist, + ); + // set metadata successful. + let hash = note_preimage(1); + assert_ok!(Democracy::set_metadata(RuntimeOrigin::signed(2), owner.clone(), Some(hash))); + System::assert_last_event(RuntimeEvent::Democracy(crate::Event::MetadataSet { + owner, + hash, + })); + }); +} + +#[test] +fn clear_metadata_works() { + new_test_ext().execute_with(|| { + // metadata owner is an external proposal. + let owner = MetadataOwner::External; + // create an external proposal. + assert_ok!(Democracy::external_propose(RuntimeOrigin::signed(2), set_balance_proposal(2))); + assert!(>::exists()); + // set metadata. + let hash = note_preimage(1); + assert_ok!(Democracy::set_metadata(RuntimeOrigin::signed(2), owner.clone(), Some(hash))); + // fails to clear metadata with a wrong origin. + assert_noop!( + Democracy::set_metadata(RuntimeOrigin::signed(1), owner.clone(), None), + BadOrigin, + ); + // clear metadata successful. + assert_ok!(Democracy::set_metadata(RuntimeOrigin::signed(2), owner.clone(), None)); + System::assert_last_event(RuntimeEvent::Democracy(crate::Event::MetadataCleared { + owner, + hash, + })); + }); +} + +#[test] +fn set_proposal_metadata_works() { + new_test_ext().execute_with(|| { + use frame_support::traits::Hash as PreimageHash; + // invalid preimage hash. + let invalid_hash: PreimageHash = [1u8; 32].into(); + // create an external proposal. + assert_ok!(propose_set_balance(1, 2, 5)); + // metadata owner is a public proposal. + let owner = MetadataOwner::Proposal(Democracy::public_prop_count() - 1); + // fails to set non-existing preimage. + assert_noop!( + Democracy::set_metadata(RuntimeOrigin::signed(1), owner.clone(), Some(invalid_hash)), + Error::::PreimageNotExist, + ); + // note preimage. + let hash = note_preimage(1); + // fails to set a preimage if an origin is not a proposer. + assert_noop!( + Democracy::set_metadata(RuntimeOrigin::signed(3), owner.clone(), Some(hash)), + Error::::NoPermission, + ); + // set metadata successful. + assert_ok!(Democracy::set_metadata(RuntimeOrigin::signed(1), owner.clone(), Some(hash))); + System::assert_last_event(RuntimeEvent::Democracy(crate::Event::MetadataSet { + owner, + hash, + })); + }); +} + +#[test] +fn clear_proposal_metadata_works() { + new_test_ext().execute_with(|| { + // create an external proposal. + assert_ok!(propose_set_balance(1, 2, 5)); + // metadata owner is a public proposal. + let owner = MetadataOwner::Proposal(Democracy::public_prop_count() - 1); + // set metadata. + let hash = note_preimage(1); + assert_ok!(Democracy::set_metadata(RuntimeOrigin::signed(1), owner.clone(), Some(hash))); + // fails to clear metadata with a wrong origin. + assert_noop!( + Democracy::set_metadata(RuntimeOrigin::signed(3), owner.clone(), None), + Error::::NoPermission, + ); + // clear metadata successful. + assert_ok!(Democracy::set_metadata(RuntimeOrigin::signed(1), owner.clone(), None)); + System::assert_last_event(RuntimeEvent::Democracy(crate::Event::MetadataCleared { + owner, + hash, + })); + }); +} + +#[test] +fn set_referendum_metadata_by_root() { + new_test_ext().execute_with(|| { + let index = Democracy::inject_referendum( + 2, + set_balance_proposal(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + // metadata owner is a referendum. + let owner = MetadataOwner::Referendum(index); + // note preimage. + let hash = note_preimage(1); + // fails to set if not a root. + assert_noop!( + Democracy::set_metadata(RuntimeOrigin::signed(3), owner.clone(), Some(hash)), + Error::::NoPermission, + ); + // fails to clear if not a root. + assert_noop!( + Democracy::set_metadata(RuntimeOrigin::signed(3), owner.clone(), None), + Error::::NoPermission, + ); + // succeed to set metadata by a root for an ongoing referendum. + assert_ok!(Democracy::set_metadata(RuntimeOrigin::root(), owner.clone(), Some(hash))); + System::assert_last_event(RuntimeEvent::Democracy(crate::Event::MetadataSet { + owner: owner.clone(), + hash, + })); + // succeed to clear metadata by a root for an ongoing referendum. + assert_ok!(Democracy::set_metadata(RuntimeOrigin::root(), owner.clone(), None)); + System::assert_last_event(RuntimeEvent::Democracy(crate::Event::MetadataCleared { + owner, + hash, + })); + }); +} + +#[test] +fn clear_referendum_metadata_works() { + new_test_ext().execute_with(|| { + // create a referendum. + let index = Democracy::inject_referendum( + 2, + set_balance_proposal(2), + VoteThreshold::SuperMajorityApprove, + 0, + ); + // metadata owner is a referendum. + let owner = MetadataOwner::Referendum(index); + // set metadata. + let hash = note_preimage(1); + // referendum finished. + MetadataOf::::insert(owner.clone(), hash); + // no permission to clear metadata of an ongoing referendum. + assert_noop!( + Democracy::set_metadata(RuntimeOrigin::signed(1), owner.clone(), None), + Error::::NoPermission, + ); + // referendum finished. + ReferendumInfoOf::::insert( + index, + ReferendumInfo::Finished { end: 1, approved: true }, + ); + // clear metadata successful. + assert_ok!(Democracy::set_metadata(RuntimeOrigin::signed(1), owner.clone(), None)); + System::assert_last_event(RuntimeEvent::Democracy(crate::Event::MetadataCleared { + owner, + hash, + })); + }); +} diff --git a/frame/democracy/src/tests/public_proposals.rs b/frame/democracy/src/tests/public_proposals.rs index f48824dc95c5..69a2d3e25686 100644 --- a/frame/democracy/src/tests/public_proposals.rs +++ b/frame/democracy/src/tests/public_proposals.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -91,8 +91,20 @@ fn cancel_proposal_should_work() { assert_ok!(propose_set_balance(1, 2, 2)); assert_ok!(propose_set_balance(1, 4, 4)); assert_noop!(Democracy::cancel_proposal(RuntimeOrigin::signed(1), 0), BadOrigin); + let hash = note_preimage(1); + assert_ok!(Democracy::set_metadata( + RuntimeOrigin::signed(1), + MetadataOwner::Proposal(0), + Some(hash) + )); + assert!(>::get(MetadataOwner::Proposal(0)).is_some()); assert_ok!(Democracy::cancel_proposal(RuntimeOrigin::root(), 0)); - System::assert_last_event(crate::Event::ProposalCanceled { prop_index: 0 }.into()); + // metadata cleared, preimage unrequested. + assert!(>::get(MetadataOwner::Proposal(0)).is_none()); + System::assert_has_event(crate::Event::ProposalCanceled { prop_index: 0 }.into()); + System::assert_last_event( + crate::Event::MetadataCleared { owner: MetadataOwner::Proposal(0), hash }.into(), + ); assert_eq!(Democracy::backing_for(0), None); assert_eq!(Democracy::backing_for(1), Some(4)); }); diff --git a/frame/democracy/src/tests/scheduling.rs b/frame/democracy/src/tests/scheduling.rs index 5e133f38945d..fdbc8fdb3494 100644 --- a/frame/democracy/src/tests/scheduling.rs +++ b/frame/democracy/src/tests/scheduling.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/democracy/src/tests/voting.rs b/frame/democracy/src/tests/voting.rs index 482cd430e0e7..f096b633ee6d 100644 --- a/frame/democracy/src/tests/voting.rs +++ b/frame/democracy/src/tests/voting.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/democracy/src/types.rs b/frame/democracy/src/types.rs index 4b7f1a0fac45..ee6e2e0aa253 100644 --- a/frame/democracy/src/types.rs +++ b/frame/democracy/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +25,12 @@ use sp_runtime::{ RuntimeDebug, }; +/// A proposal index. +pub type PropIndex = u32; + +/// A referendum index. +pub type ReferendumIndex = u32; + /// Info regarding an ongoing referendum. #[derive(Encode, MaxEncodedLen, Decode, Default, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub struct Tally { @@ -206,3 +212,14 @@ pub enum UnvoteScope { /// Permitted to do only the changes that do not need the owner's permission. OnlyExpired, } + +/// Identifies an owner of a metadata. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] +pub enum MetadataOwner { + /// External proposal. + External, + /// Public proposal of the index. + Proposal(PropIndex), + /// Referendum of the index. + Referendum(ReferendumIndex), +} diff --git a/frame/democracy/src/vote.rs b/frame/democracy/src/vote.rs index 122f54febd8c..b3fe9aa28e1a 100644 --- a/frame/democracy/src/vote.rs +++ b/frame/democracy/src/vote.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -202,7 +202,7 @@ impl< .rejig(now); } - /// The amount of this account's balance that much currently be locked due to voting. + /// The amount of this account's balance that must currently be locked due to voting. pub fn locked_balance(&self) -> Balance { match self { Voting::Direct { votes, prior, .. } => diff --git a/frame/democracy/src/vote_threshold.rs b/frame/democracy/src/vote_threshold.rs index e8ef91def982..e8efa179ed8b 100644 --- a/frame/democracy/src/vote_threshold.rs +++ b/frame/democracy/src/vote_threshold.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/democracy/src/weights.rs b/frame/democracy/src/weights.rs index db3969d400b9..241f6c3cb38d 100644 --- a/frame/democracy/src/weights.rs +++ b/frame/democracy/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_democracy //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_democracy +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_democracy. pub trait WeightInfo { @@ -69,443 +74,913 @@ pub trait WeightInfo { fn unlock_set(r: u32, ) -> Weight; fn remove_vote(r: u32, ) -> Weight; fn remove_other_vote(r: u32, ) -> Weight; + fn set_external_metadata() -> Weight; + fn clear_external_metadata() -> Weight; + fn set_proposal_metadata() -> Weight; + fn clear_proposal_metadata() -> Weight; + fn set_referendum_metadata() -> Weight; + fn clear_referendum_metadata() -> Weight; } /// Weights for pallet_democracy using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Democracy PublicPropCount (r:1 w:1) - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:0) - // Storage: Democracy DepositOf (r:0 w:1) + /// Storage: Democracy PublicPropCount (r:1 w:1) + /// Proof: Democracy PublicPropCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Democracy PublicProps (r:1 w:1) + /// Proof: Democracy PublicProps (max_values: Some(1), max_size: Some(16702), added: 17197, mode: MaxEncodedLen) + /// Storage: Democracy Blacklist (r:1 w:0) + /// Proof: Democracy Blacklist (max_values: None, max_size: Some(3238), added: 5713, mode: MaxEncodedLen) + /// Storage: Democracy DepositOf (r:0 w:1) + /// Proof: Democracy DepositOf (max_values: None, max_size: Some(3230), added: 5705, mode: MaxEncodedLen) fn propose() -> Weight { - // Minimum execution time: 56_868 nanoseconds. - Weight::from_ref_time(57_788_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - } - // Storage: Democracy DepositOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `4801` + // Estimated: `18187` + // Minimum execution time: 49_339_000 picoseconds. + Weight::from_parts(50_942_000, 18187) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Democracy DepositOf (r:1 w:1) + /// Proof: Democracy DepositOf (max_values: None, max_size: Some(3230), added: 5705, mode: MaxEncodedLen) fn second() -> Weight { - // Minimum execution time: 49_328 nanoseconds. - Weight::from_ref_time(49_764_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3556` + // Estimated: `6695` + // Minimum execution time: 43_291_000 picoseconds. + Weight::from_parts(44_856_000, 6695) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Democracy ReferendumInfoOf (r:1 w:1) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) + /// Storage: Democracy VotingOf (r:1 w:1) + /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3795), added: 6270, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn vote_new() -> Weight { - // Minimum execution time: 60_323 nanoseconds. - Weight::from_ref_time(61_389_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3470` + // Estimated: `7260` + // Minimum execution time: 61_890_000 picoseconds. + Weight::from_parts(63_626_000, 7260) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Democracy ReferendumInfoOf (r:1 w:1) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) + /// Storage: Democracy VotingOf (r:1 w:1) + /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3795), added: 6270, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn vote_existing() -> Weight { - // Minimum execution time: 60_612 nanoseconds. - Weight::from_ref_time(61_282_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy Cancellations (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3492` + // Estimated: `7260` + // Minimum execution time: 67_802_000 picoseconds. + Weight::from_parts(69_132_000, 7260) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Democracy ReferendumInfoOf (r:1 w:1) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) + /// Storage: Democracy Cancellations (r:1 w:1) + /// Proof: Democracy Cancellations (max_values: None, max_size: Some(33), added: 2508, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:1 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) fn emergency_cancel() -> Weight { - // Minimum execution time: 24_780 nanoseconds. - Weight::from_ref_time(25_194_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy Blacklist (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `366` + // Estimated: `3666` + // Minimum execution time: 25_757_000 picoseconds. + Weight::from_parts(27_226_000, 3666) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Democracy PublicProps (r:1 w:1) + /// Proof: Democracy PublicProps (max_values: Some(1), max_size: Some(16702), added: 17197, mode: MaxEncodedLen) + /// Storage: Democracy DepositOf (r:1 w:1) + /// Proof: Democracy DepositOf (max_values: None, max_size: Some(3230), added: 5705, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:3 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + /// Storage: Democracy NextExternal (r:1 w:1) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumInfoOf (r:1 w:1) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) + /// Storage: Democracy Blacklist (r:0 w:1) + /// Proof: Democracy Blacklist (max_values: None, max_size: Some(3238), added: 5713, mode: MaxEncodedLen) fn blacklist() -> Weight { - // Minimum execution time: 85_177 nanoseconds. - Weight::from_ref_time(91_733_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(6 as u64)) - } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `5910` + // Estimated: `18187` + // Minimum execution time: 113_060_000 picoseconds. + Weight::from_parts(114_813_000, 18187) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) + } + /// Storage: Democracy NextExternal (r:1 w:1) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) + /// Storage: Democracy Blacklist (r:1 w:0) + /// Proof: Democracy Blacklist (max_values: None, max_size: Some(3238), added: 5713, mode: MaxEncodedLen) fn external_propose() -> Weight { - // Minimum execution time: 19_483 nanoseconds. - Weight::from_ref_time(19_914_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Democracy NextExternal (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `3416` + // Estimated: `6703` + // Minimum execution time: 13_413_000 picoseconds. + Weight::from_parts(13_794_000, 6703) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Democracy NextExternal (r:0 w:1) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) fn external_propose_majority() -> Weight { - // Minimum execution time: 4_963 nanoseconds. - Weight::from_ref_time(5_250_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Democracy NextExternal (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_213_000 picoseconds. + Weight::from_parts(3_429_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Democracy NextExternal (r:0 w:1) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) fn external_propose_default() -> Weight { - // Minimum execution time: 5_075 nanoseconds. - Weight::from_ref_time(5_187_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy ReferendumCount (r:1 w:1) - // Storage: Democracy ReferendumInfoOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_280_000 picoseconds. + Weight::from_parts(3_389_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Democracy NextExternal (r:1 w:1) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumCount (r:1 w:1) + /// Proof: Democracy ReferendumCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:1 w:2) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumInfoOf (r:0 w:1) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) fn fast_track() -> Weight { - // Minimum execution time: 23_956 nanoseconds. - Weight::from_ref_time(24_814_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `286` + // Estimated: `3518` + // Minimum execution time: 28_142_000 picoseconds. + Weight::from_parts(28_862_000, 3518) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: Democracy NextExternal (r:1 w:1) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) + /// Storage: Democracy Blacklist (r:1 w:1) + /// Proof: Democracy Blacklist (max_values: None, max_size: Some(3238), added: 5713, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:1 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) fn veto_external() -> Weight { - // Minimum execution time: 31_472 nanoseconds. - Weight::from_ref_time(31_770_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3519` + // Estimated: `6703` + // Minimum execution time: 32_395_000 picoseconds. + Weight::from_parts(33_617_000, 6703) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Democracy PublicProps (r:1 w:1) + /// Proof: Democracy PublicProps (max_values: Some(1), max_size: Some(16702), added: 17197, mode: MaxEncodedLen) + /// Storage: Democracy DepositOf (r:1 w:1) + /// Proof: Democracy DepositOf (max_values: None, max_size: Some(3230), added: 5705, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:1 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) fn cancel_proposal() -> Weight { - // Minimum execution time: 73_811 nanoseconds. - Weight::from_ref_time(78_943_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - } - // Storage: Democracy ReferendumInfoOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `5821` + // Estimated: `18187` + // Minimum execution time: 92_255_000 picoseconds. + Weight::from_parts(93_704_000, 18187) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Democracy MetadataOf (r:1 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumInfoOf (r:0 w:1) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) fn cancel_referendum() -> Weight { - // Minimum execution time: 16_074 nanoseconds. - Weight::from_ref_time(16_409_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Democracy LowestUnbaked (r:1 w:1) - // Storage: Democracy ReferendumCount (r:1 w:0) - // Storage: Democracy ReferendumInfoOf (r:2 w:0) + // Proof Size summary in bytes: + // Measured: `271` + // Estimated: `3518` + // Minimum execution time: 19_623_000 picoseconds. + Weight::from_parts(20_545_000, 3518) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Democracy LowestUnbaked (r:1 w:1) + /// Proof: Democracy LowestUnbaked (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumCount (r:1 w:0) + /// Proof: Democracy ReferendumCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumInfoOf (r:99 w:0) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. fn on_initialize_base(r: u32, ) -> Weight { - // Minimum execution time: 7_430 nanoseconds. - Weight::from_ref_time(12_086_064 as u64) - // Standard Error: 3_474 - .saturating_add(Weight::from_ref_time(2_283_457 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Democracy LowestUnbaked (r:1 w:1) - // Storage: Democracy ReferendumCount (r:1 w:0) - // Storage: Democracy LastTabledWasExternal (r:1 w:0) - // Storage: Democracy NextExternal (r:1 w:0) - // Storage: Democracy PublicProps (r:1 w:0) - // Storage: Democracy ReferendumInfoOf (r:2 w:0) + // Proof Size summary in bytes: + // Measured: `244 + r * (86 ±0)` + // Estimated: `1489 + r * (2676 ±0)` + // Minimum execution time: 7_032_000 picoseconds. + Weight::from_parts(7_931_421, 1489) + // Standard Error: 7_395 + .saturating_add(Weight::from_parts(3_236_964, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 2676).saturating_mul(r.into())) + } + /// Storage: Democracy LowestUnbaked (r:1 w:1) + /// Proof: Democracy LowestUnbaked (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumCount (r:1 w:0) + /// Proof: Democracy ReferendumCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Democracy LastTabledWasExternal (r:1 w:0) + /// Proof: Democracy LastTabledWasExternal (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: Democracy NextExternal (r:1 w:0) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) + /// Storage: Democracy PublicProps (r:1 w:0) + /// Proof: Democracy PublicProps (max_values: Some(1), max_size: Some(16702), added: 17197, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumInfoOf (r:99 w:0) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { - // Minimum execution time: 9_882 nanoseconds. - Weight::from_ref_time(14_566_711 as u64) - // Standard Error: 3_354 - .saturating_add(Weight::from_ref_time(2_282_038 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Democracy VotingOf (r:3 w:3) - // Storage: Balances Locks (r:1 w:1) - // Storage: Democracy ReferendumInfoOf (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `244 + r * (86 ±0)` + // Estimated: `18187 + r * (2676 ±0)` + // Minimum execution time: 10_524_000 picoseconds. + Weight::from_parts(10_369_064, 18187) + // Standard Error: 8_385 + .saturating_add(Weight::from_parts(3_242_334, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 2676).saturating_mul(r.into())) + } + /// Storage: Democracy VotingOf (r:3 w:3) + /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3795), added: 6270, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumInfoOf (r:99 w:99) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. fn delegate(r: u32, ) -> Weight { - // Minimum execution time: 48_840 nanoseconds. - Weight::from_ref_time(56_403_092 as u64) - // Standard Error: 6_093 - .saturating_add(Weight::from_ref_time(3_344_243 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(r as u64))) - } - // Storage: Democracy VotingOf (r:2 w:2) - // Storage: Democracy ReferendumInfoOf (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `830 + r * (108 ±0)` + // Estimated: `19800 + r * (2676 ±0)` + // Minimum execution time: 46_106_000 picoseconds. + Weight::from_parts(48_936_654, 19800) + // Standard Error: 8_879 + .saturating_add(Weight::from_parts(4_708_141, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 2676).saturating_mul(r.into())) + } + /// Storage: Democracy VotingOf (r:2 w:2) + /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3795), added: 6270, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumInfoOf (r:99 w:99) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. fn undelegate(r: u32, ) -> Weight { - // Minimum execution time: 30_483 nanoseconds. - Weight::from_ref_time(32_035_405 as u64) - // Standard Error: 4_383 - .saturating_add(Weight::from_ref_time(3_347_667 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(r as u64))) - } - // Storage: Democracy PublicProps (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `493 + r * (108 ±0)` + // Estimated: `13530 + r * (2676 ±0)` + // Minimum execution time: 21_078_000 picoseconds. + Weight::from_parts(22_732_737, 13530) + // Standard Error: 7_969 + .saturating_add(Weight::from_parts(4_626_458, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 2676).saturating_mul(r.into())) + } + /// Storage: Democracy PublicProps (r:0 w:1) + /// Proof: Democracy PublicProps (max_values: Some(1), max_size: Some(16702), added: 17197, mode: MaxEncodedLen) fn clear_public_proposals() -> Weight { - // Minimum execution time: 6_421 nanoseconds. - Weight::from_ref_time(6_638_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_229_000 picoseconds. + Weight::from_parts(3_415_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Democracy VotingOf (r:1 w:1) + /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3795), added: 6270, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. fn unlock_remove(r: u32, ) -> Weight { - // Minimum execution time: 30_291 nanoseconds. - Weight::from_ref_time(37_071_950 as u64) - // Standard Error: 1_619 - .saturating_add(Weight::from_ref_time(59_302 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - } - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `563` + // Estimated: `7260` + // Minimum execution time: 25_735_000 picoseconds. + Weight::from_parts(41_341_468, 7260) + // Standard Error: 3_727 + .saturating_add(Weight::from_parts(94_755, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Democracy VotingOf (r:1 w:1) + /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3795), added: 6270, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. fn unlock_set(r: u32, ) -> Weight { - // Minimum execution time: 34_888 nanoseconds. - Weight::from_ref_time(36_418_789 as u64) - // Standard Error: 906 - .saturating_add(Weight::from_ref_time(109_602 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `564 + r * (22 ±0)` + // Estimated: `7260` + // Minimum execution time: 36_233_000 picoseconds. + Weight::from_parts(39_836_017, 7260) + // Standard Error: 1_791 + .saturating_add(Weight::from_parts(132_158, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Democracy ReferendumInfoOf (r:1 w:1) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) + /// Storage: Democracy VotingOf (r:1 w:1) + /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3795), added: 6270, mode: MaxEncodedLen) /// The range of component `r` is `[1, 100]`. fn remove_vote(r: u32, ) -> Weight { - // Minimum execution time: 18_739 nanoseconds. - Weight::from_ref_time(21_004_077 as u64) - // Standard Error: 1_075 - .saturating_add(Weight::from_ref_time(116_457 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `728 + r * (26 ±0)` + // Estimated: `7260` + // Minimum execution time: 16_081_000 picoseconds. + Weight::from_parts(19_624_101, 7260) + // Standard Error: 1_639 + .saturating_add(Weight::from_parts(133_630, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Democracy ReferendumInfoOf (r:1 w:1) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) + /// Storage: Democracy VotingOf (r:1 w:1) + /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3795), added: 6270, mode: MaxEncodedLen) /// The range of component `r` is `[1, 100]`. fn remove_other_vote(r: u32, ) -> Weight { - // Minimum execution time: 18_514 nanoseconds. - Weight::from_ref_time(21_030_667 as u64) - // Standard Error: 1_102 - .saturating_add(Weight::from_ref_time(118_039 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `728 + r * (26 ±0)` + // Estimated: `7260` + // Minimum execution time: 15_634_000 picoseconds. + Weight::from_parts(19_573_407, 7260) + // Standard Error: 1_790 + .saturating_add(Weight::from_parts(139_707, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Democracy NextExternal (r:1 w:0) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) + /// Storage: Preimage StatusFor (r:1 w:0) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:0 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + fn set_external_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `356` + // Estimated: `3556` + // Minimum execution time: 18_344_000 picoseconds. + Weight::from_parts(18_727_000, 3556) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Democracy NextExternal (r:1 w:0) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:1 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + fn clear_external_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `286` + // Estimated: `3518` + // Minimum execution time: 16_497_000 picoseconds. + Weight::from_parts(16_892_000, 3518) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Democracy PublicProps (r:1 w:0) + /// Proof: Democracy PublicProps (max_values: Some(1), max_size: Some(16702), added: 17197, mode: MaxEncodedLen) + /// Storage: Preimage StatusFor (r:1 w:0) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:0 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + fn set_proposal_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `4888` + // Estimated: `18187` + // Minimum execution time: 39_517_000 picoseconds. + Weight::from_parts(40_632_000, 18187) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Democracy PublicProps (r:1 w:0) + /// Proof: Democracy PublicProps (max_values: Some(1), max_size: Some(16702), added: 17197, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:1 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + fn clear_proposal_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `4822` + // Estimated: `18187` + // Minimum execution time: 37_108_000 picoseconds. + Weight::from_parts(37_599_000, 18187) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Preimage StatusFor (r:1 w:0) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:0 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + fn set_referendum_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `144` + // Estimated: `3556` + // Minimum execution time: 13_997_000 picoseconds. + Weight::from_parts(14_298_000, 3556) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Democracy ReferendumInfoOf (r:1 w:0) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:1 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + fn clear_referendum_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `302` + // Estimated: `3666` + // Minimum execution time: 18_122_000 picoseconds. + Weight::from_parts(18_655_000, 3666) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Democracy PublicPropCount (r:1 w:1) - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:0) - // Storage: Democracy DepositOf (r:0 w:1) + /// Storage: Democracy PublicPropCount (r:1 w:1) + /// Proof: Democracy PublicPropCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Democracy PublicProps (r:1 w:1) + /// Proof: Democracy PublicProps (max_values: Some(1), max_size: Some(16702), added: 17197, mode: MaxEncodedLen) + /// Storage: Democracy Blacklist (r:1 w:0) + /// Proof: Democracy Blacklist (max_values: None, max_size: Some(3238), added: 5713, mode: MaxEncodedLen) + /// Storage: Democracy DepositOf (r:0 w:1) + /// Proof: Democracy DepositOf (max_values: None, max_size: Some(3230), added: 5705, mode: MaxEncodedLen) fn propose() -> Weight { - // Minimum execution time: 56_868 nanoseconds. - Weight::from_ref_time(57_788_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - } - // Storage: Democracy DepositOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `4801` + // Estimated: `18187` + // Minimum execution time: 49_339_000 picoseconds. + Weight::from_parts(50_942_000, 18187) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Democracy DepositOf (r:1 w:1) + /// Proof: Democracy DepositOf (max_values: None, max_size: Some(3230), added: 5705, mode: MaxEncodedLen) fn second() -> Weight { - // Minimum execution time: 49_328 nanoseconds. - Weight::from_ref_time(49_764_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3556` + // Estimated: `6695` + // Minimum execution time: 43_291_000 picoseconds. + Weight::from_parts(44_856_000, 6695) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Democracy ReferendumInfoOf (r:1 w:1) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) + /// Storage: Democracy VotingOf (r:1 w:1) + /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3795), added: 6270, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn vote_new() -> Weight { - // Minimum execution time: 60_323 nanoseconds. - Weight::from_ref_time(61_389_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3470` + // Estimated: `7260` + // Minimum execution time: 61_890_000 picoseconds. + Weight::from_parts(63_626_000, 7260) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Democracy ReferendumInfoOf (r:1 w:1) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) + /// Storage: Democracy VotingOf (r:1 w:1) + /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3795), added: 6270, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn vote_existing() -> Weight { - // Minimum execution time: 60_612 nanoseconds. - Weight::from_ref_time(61_282_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy Cancellations (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3492` + // Estimated: `7260` + // Minimum execution time: 67_802_000 picoseconds. + Weight::from_parts(69_132_000, 7260) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Democracy ReferendumInfoOf (r:1 w:1) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) + /// Storage: Democracy Cancellations (r:1 w:1) + /// Proof: Democracy Cancellations (max_values: None, max_size: Some(33), added: 2508, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:1 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) fn emergency_cancel() -> Weight { - // Minimum execution time: 24_780 nanoseconds. - Weight::from_ref_time(25_194_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy Blacklist (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `366` + // Estimated: `3666` + // Minimum execution time: 25_757_000 picoseconds. + Weight::from_parts(27_226_000, 3666) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Democracy PublicProps (r:1 w:1) + /// Proof: Democracy PublicProps (max_values: Some(1), max_size: Some(16702), added: 17197, mode: MaxEncodedLen) + /// Storage: Democracy DepositOf (r:1 w:1) + /// Proof: Democracy DepositOf (max_values: None, max_size: Some(3230), added: 5705, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:3 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + /// Storage: Democracy NextExternal (r:1 w:1) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumInfoOf (r:1 w:1) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) + /// Storage: Democracy Blacklist (r:0 w:1) + /// Proof: Democracy Blacklist (max_values: None, max_size: Some(3238), added: 5713, mode: MaxEncodedLen) fn blacklist() -> Weight { - // Minimum execution time: 85_177 nanoseconds. - Weight::from_ref_time(91_733_000 as u64) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) - } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `5910` + // Estimated: `18187` + // Minimum execution time: 113_060_000 picoseconds. + Weight::from_parts(114_813_000, 18187) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) + } + /// Storage: Democracy NextExternal (r:1 w:1) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) + /// Storage: Democracy Blacklist (r:1 w:0) + /// Proof: Democracy Blacklist (max_values: None, max_size: Some(3238), added: 5713, mode: MaxEncodedLen) fn external_propose() -> Weight { - // Minimum execution time: 19_483 nanoseconds. - Weight::from_ref_time(19_914_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Democracy NextExternal (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `3416` + // Estimated: `6703` + // Minimum execution time: 13_413_000 picoseconds. + Weight::from_parts(13_794_000, 6703) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Democracy NextExternal (r:0 w:1) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) fn external_propose_majority() -> Weight { - // Minimum execution time: 4_963 nanoseconds. - Weight::from_ref_time(5_250_000 as u64) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Democracy NextExternal (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_213_000 picoseconds. + Weight::from_parts(3_429_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Democracy NextExternal (r:0 w:1) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) fn external_propose_default() -> Weight { - // Minimum execution time: 5_075 nanoseconds. - Weight::from_ref_time(5_187_000 as u64) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy ReferendumCount (r:1 w:1) - // Storage: Democracy ReferendumInfoOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_280_000 picoseconds. + Weight::from_parts(3_389_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Democracy NextExternal (r:1 w:1) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumCount (r:1 w:1) + /// Proof: Democracy ReferendumCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:1 w:2) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumInfoOf (r:0 w:1) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) fn fast_track() -> Weight { - // Minimum execution time: 23_956 nanoseconds. - Weight::from_ref_time(24_814_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - } - // Storage: Democracy NextExternal (r:1 w:1) - // Storage: Democracy Blacklist (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `286` + // Estimated: `3518` + // Minimum execution time: 28_142_000 picoseconds. + Weight::from_parts(28_862_000, 3518) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: Democracy NextExternal (r:1 w:1) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) + /// Storage: Democracy Blacklist (r:1 w:1) + /// Proof: Democracy Blacklist (max_values: None, max_size: Some(3238), added: 5713, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:1 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) fn veto_external() -> Weight { - // Minimum execution time: 31_472 nanoseconds. - Weight::from_ref_time(31_770_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Democracy PublicProps (r:1 w:1) - // Storage: Democracy DepositOf (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3519` + // Estimated: `6703` + // Minimum execution time: 32_395_000 picoseconds. + Weight::from_parts(33_617_000, 6703) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Democracy PublicProps (r:1 w:1) + /// Proof: Democracy PublicProps (max_values: Some(1), max_size: Some(16702), added: 17197, mode: MaxEncodedLen) + /// Storage: Democracy DepositOf (r:1 w:1) + /// Proof: Democracy DepositOf (max_values: None, max_size: Some(3230), added: 5705, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:1 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) fn cancel_proposal() -> Weight { - // Minimum execution time: 73_811 nanoseconds. - Weight::from_ref_time(78_943_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - } - // Storage: Democracy ReferendumInfoOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `5821` + // Estimated: `18187` + // Minimum execution time: 92_255_000 picoseconds. + Weight::from_parts(93_704_000, 18187) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Democracy MetadataOf (r:1 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumInfoOf (r:0 w:1) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) fn cancel_referendum() -> Weight { - // Minimum execution time: 16_074 nanoseconds. - Weight::from_ref_time(16_409_000 as u64) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Democracy LowestUnbaked (r:1 w:1) - // Storage: Democracy ReferendumCount (r:1 w:0) - // Storage: Democracy ReferendumInfoOf (r:2 w:0) + // Proof Size summary in bytes: + // Measured: `271` + // Estimated: `3518` + // Minimum execution time: 19_623_000 picoseconds. + Weight::from_parts(20_545_000, 3518) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Democracy LowestUnbaked (r:1 w:1) + /// Proof: Democracy LowestUnbaked (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumCount (r:1 w:0) + /// Proof: Democracy ReferendumCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumInfoOf (r:99 w:0) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. fn on_initialize_base(r: u32, ) -> Weight { - // Minimum execution time: 7_430 nanoseconds. - Weight::from_ref_time(12_086_064 as u64) - // Standard Error: 3_474 - .saturating_add(Weight::from_ref_time(2_283_457 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Democracy LowestUnbaked (r:1 w:1) - // Storage: Democracy ReferendumCount (r:1 w:0) - // Storage: Democracy LastTabledWasExternal (r:1 w:0) - // Storage: Democracy NextExternal (r:1 w:0) - // Storage: Democracy PublicProps (r:1 w:0) - // Storage: Democracy ReferendumInfoOf (r:2 w:0) + // Proof Size summary in bytes: + // Measured: `244 + r * (86 ±0)` + // Estimated: `1489 + r * (2676 ±0)` + // Minimum execution time: 7_032_000 picoseconds. + Weight::from_parts(7_931_421, 1489) + // Standard Error: 7_395 + .saturating_add(Weight::from_parts(3_236_964, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 2676).saturating_mul(r.into())) + } + /// Storage: Democracy LowestUnbaked (r:1 w:1) + /// Proof: Democracy LowestUnbaked (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumCount (r:1 w:0) + /// Proof: Democracy ReferendumCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Democracy LastTabledWasExternal (r:1 w:0) + /// Proof: Democracy LastTabledWasExternal (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: Democracy NextExternal (r:1 w:0) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) + /// Storage: Democracy PublicProps (r:1 w:0) + /// Proof: Democracy PublicProps (max_values: Some(1), max_size: Some(16702), added: 17197, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumInfoOf (r:99 w:0) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. fn on_initialize_base_with_launch_period(r: u32, ) -> Weight { - // Minimum execution time: 9_882 nanoseconds. - Weight::from_ref_time(14_566_711 as u64) - // Standard Error: 3_354 - .saturating_add(Weight::from_ref_time(2_282_038 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Democracy VotingOf (r:3 w:3) - // Storage: Balances Locks (r:1 w:1) - // Storage: Democracy ReferendumInfoOf (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `244 + r * (86 ±0)` + // Estimated: `18187 + r * (2676 ±0)` + // Minimum execution time: 10_524_000 picoseconds. + Weight::from_parts(10_369_064, 18187) + // Standard Error: 8_385 + .saturating_add(Weight::from_parts(3_242_334, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 2676).saturating_mul(r.into())) + } + /// Storage: Democracy VotingOf (r:3 w:3) + /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3795), added: 6270, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumInfoOf (r:99 w:99) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. fn delegate(r: u32, ) -> Weight { - // Minimum execution time: 48_840 nanoseconds. - Weight::from_ref_time(56_403_092 as u64) - // Standard Error: 6_093 - .saturating_add(Weight::from_ref_time(3_344_243 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(r as u64))) - } - // Storage: Democracy VotingOf (r:2 w:2) - // Storage: Democracy ReferendumInfoOf (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `830 + r * (108 ±0)` + // Estimated: `19800 + r * (2676 ±0)` + // Minimum execution time: 46_106_000 picoseconds. + Weight::from_parts(48_936_654, 19800) + // Standard Error: 8_879 + .saturating_add(Weight::from_parts(4_708_141, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 2676).saturating_mul(r.into())) + } + /// Storage: Democracy VotingOf (r:2 w:2) + /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3795), added: 6270, mode: MaxEncodedLen) + /// Storage: Democracy ReferendumInfoOf (r:99 w:99) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. fn undelegate(r: u32, ) -> Weight { - // Minimum execution time: 30_483 nanoseconds. - Weight::from_ref_time(32_035_405 as u64) - // Standard Error: 4_383 - .saturating_add(Weight::from_ref_time(3_347_667 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(r as u64))) - } - // Storage: Democracy PublicProps (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `493 + r * (108 ±0)` + // Estimated: `13530 + r * (2676 ±0)` + // Minimum execution time: 21_078_000 picoseconds. + Weight::from_parts(22_732_737, 13530) + // Standard Error: 7_969 + .saturating_add(Weight::from_parts(4_626_458, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 2676).saturating_mul(r.into())) + } + /// Storage: Democracy PublicProps (r:0 w:1) + /// Proof: Democracy PublicProps (max_values: Some(1), max_size: Some(16702), added: 17197, mode: MaxEncodedLen) fn clear_public_proposals() -> Weight { - // Minimum execution time: 6_421 nanoseconds. - Weight::from_ref_time(6_638_000 as u64) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_229_000 picoseconds. + Weight::from_parts(3_415_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Democracy VotingOf (r:1 w:1) + /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3795), added: 6270, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. fn unlock_remove(r: u32, ) -> Weight { - // Minimum execution time: 30_291 nanoseconds. - Weight::from_ref_time(37_071_950 as u64) - // Standard Error: 1_619 - .saturating_add(Weight::from_ref_time(59_302 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - } - // Storage: Democracy VotingOf (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `563` + // Estimated: `7260` + // Minimum execution time: 25_735_000 picoseconds. + Weight::from_parts(41_341_468, 7260) + // Standard Error: 3_727 + .saturating_add(Weight::from_parts(94_755, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Democracy VotingOf (r:1 w:1) + /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3795), added: 6270, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `r` is `[0, 99]`. fn unlock_set(r: u32, ) -> Weight { - // Minimum execution time: 34_888 nanoseconds. - Weight::from_ref_time(36_418_789 as u64) - // Standard Error: 906 - .saturating_add(Weight::from_ref_time(109_602 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `564 + r * (22 ±0)` + // Estimated: `7260` + // Minimum execution time: 36_233_000 picoseconds. + Weight::from_parts(39_836_017, 7260) + // Standard Error: 1_791 + .saturating_add(Weight::from_parts(132_158, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Democracy ReferendumInfoOf (r:1 w:1) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) + /// Storage: Democracy VotingOf (r:1 w:1) + /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3795), added: 6270, mode: MaxEncodedLen) /// The range of component `r` is `[1, 100]`. fn remove_vote(r: u32, ) -> Weight { - // Minimum execution time: 18_739 nanoseconds. - Weight::from_ref_time(21_004_077 as u64) - // Standard Error: 1_075 - .saturating_add(Weight::from_ref_time(116_457 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Democracy ReferendumInfoOf (r:1 w:1) - // Storage: Democracy VotingOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `728 + r * (26 ±0)` + // Estimated: `7260` + // Minimum execution time: 16_081_000 picoseconds. + Weight::from_parts(19_624_101, 7260) + // Standard Error: 1_639 + .saturating_add(Weight::from_parts(133_630, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Democracy ReferendumInfoOf (r:1 w:1) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) + /// Storage: Democracy VotingOf (r:1 w:1) + /// Proof: Democracy VotingOf (max_values: None, max_size: Some(3795), added: 6270, mode: MaxEncodedLen) /// The range of component `r` is `[1, 100]`. fn remove_other_vote(r: u32, ) -> Weight { - // Minimum execution time: 18_514 nanoseconds. - Weight::from_ref_time(21_030_667 as u64) - // Standard Error: 1_102 - .saturating_add(Weight::from_ref_time(118_039 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `728 + r * (26 ±0)` + // Estimated: `7260` + // Minimum execution time: 15_634_000 picoseconds. + Weight::from_parts(19_573_407, 7260) + // Standard Error: 1_790 + .saturating_add(Weight::from_parts(139_707, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Democracy NextExternal (r:1 w:0) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) + /// Storage: Preimage StatusFor (r:1 w:0) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:0 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + fn set_external_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `356` + // Estimated: `3556` + // Minimum execution time: 18_344_000 picoseconds. + Weight::from_parts(18_727_000, 3556) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Democracy NextExternal (r:1 w:0) + /// Proof: Democracy NextExternal (max_values: Some(1), max_size: Some(132), added: 627, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:1 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + fn clear_external_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `286` + // Estimated: `3518` + // Minimum execution time: 16_497_000 picoseconds. + Weight::from_parts(16_892_000, 3518) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Democracy PublicProps (r:1 w:0) + /// Proof: Democracy PublicProps (max_values: Some(1), max_size: Some(16702), added: 17197, mode: MaxEncodedLen) + /// Storage: Preimage StatusFor (r:1 w:0) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:0 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + fn set_proposal_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `4888` + // Estimated: `18187` + // Minimum execution time: 39_517_000 picoseconds. + Weight::from_parts(40_632_000, 18187) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Democracy PublicProps (r:1 w:0) + /// Proof: Democracy PublicProps (max_values: Some(1), max_size: Some(16702), added: 17197, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:1 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + fn clear_proposal_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `4822` + // Estimated: `18187` + // Minimum execution time: 37_108_000 picoseconds. + Weight::from_parts(37_599_000, 18187) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Preimage StatusFor (r:1 w:0) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:0 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + fn set_referendum_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `144` + // Estimated: `3556` + // Minimum execution time: 13_997_000 picoseconds. + Weight::from_parts(14_298_000, 3556) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Democracy ReferendumInfoOf (r:1 w:0) + /// Proof: Democracy ReferendumInfoOf (max_values: None, max_size: Some(201), added: 2676, mode: MaxEncodedLen) + /// Storage: Democracy MetadataOf (r:1 w:1) + /// Proof: Democracy MetadataOf (max_values: None, max_size: Some(53), added: 2528, mode: MaxEncodedLen) + fn clear_referendum_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `302` + // Estimated: `3666` + // Minimum execution time: 18_122_000 picoseconds. + Weight::from_parts(18_655_000, 3666) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } diff --git a/frame/election-provider-multi-phase/Cargo.toml b/frame/election-provider-multi-phase/Cargo.toml index aa734850aae4..2bc74ab87775 100644 --- a/frame/election-provider-multi-phase/Cargo.toml +++ b/frame/election-provider-multi-phase/Cargo.toml @@ -12,10 +12,10 @@ description = "PALLET two phase election providers" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.1.1", default-features = false, features = [ +scale-info = { version = "2.5.0", default-features = false, features = [ "derive", ] } log = { version = "0.4.17", default-features = false } @@ -23,12 +23,12 @@ log = { version = "0.4.17", default-features = false } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } -sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../primitives/arithmetic" } frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../election-provider-support" } # Optional imports for benchmarking @@ -40,10 +40,10 @@ strum = { version = "0.24.1", default-features = false, features = ["derive"], [dev-dependencies] parking_lot = "0.12.1" rand = "0.8.5" -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" } diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index 16263d97da58..1284d0b873ea 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -219,7 +219,7 @@ frame_benchmarking::benchmarks! { finalize_signed_phase_accept_solution { let receiver = account("receiver", 0, SEED); - let initial_balance = T::Currency::minimum_balance() * 10u32.into(); + let initial_balance = T::Currency::minimum_balance() + 10u32.into(); T::Currency::make_free_balance_be(&receiver, initial_balance); let ready = Default::default(); let deposit: BalanceOf = 10u32.into(); @@ -228,7 +228,7 @@ frame_benchmarking::benchmarks! { let call_fee: BalanceOf = 30u32.into(); assert_ok!(T::Currency::reserve(&receiver, deposit)); - assert_eq!(T::Currency::free_balance(&receiver), initial_balance - 10u32.into()); + assert_eq!(T::Currency::free_balance(&receiver), T::Currency::minimum_balance()); }: { >::finalize_signed_phase_accept_solution( ready, @@ -246,17 +246,17 @@ frame_benchmarking::benchmarks! { finalize_signed_phase_reject_solution { let receiver = account("receiver", 0, SEED); - let initial_balance = T::Currency::minimum_balance().max(One::one()) * 10u32.into(); + let initial_balance = T::Currency::minimum_balance() + 10u32.into(); let deposit: BalanceOf = 10u32.into(); T::Currency::make_free_balance_be(&receiver, initial_balance); assert_ok!(T::Currency::reserve(&receiver, deposit)); - assert_eq!(T::Currency::free_balance(&receiver), initial_balance - 10u32.into()); + assert_eq!(T::Currency::free_balance(&receiver), T::Currency::minimum_balance()); assert_eq!(T::Currency::reserved_balance(&receiver), 10u32.into()); }: { >::finalize_signed_phase_reject_solution(&receiver, deposit) } verify { - assert_eq!(T::Currency::free_balance(&receiver), initial_balance - 10u32.into()); + assert_eq!(T::Currency::free_balance(&receiver), T::Currency::minimum_balance()); assert_eq!(T::Currency::reserved_balance(&receiver), 0u32.into()); } @@ -313,7 +313,7 @@ frame_benchmarking::benchmarks! { assert!(>::get().is_none()); assert!(>::get().is_none()); assert!(>::get().is_none()); - assert_eq!(>::get(), >::Off); + assert_eq!(>::get(), >>::Off); } submit { diff --git a/frame/election-provider-multi-phase/src/helpers.rs b/frame/election-provider-multi-phase/src/helpers.rs index 0a7240c5d27a..57d580e93016 100644 --- a/frame/election-provider-multi-phase/src/helpers.rs +++ b/frame/election-provider-multi-phase/src/helpers.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 6c1b1d163d0f..03ab5574a218 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -241,16 +241,13 @@ use frame_support::{ weights::Weight, DefaultNoBound, EqNoBound, PartialEqNoBound, }; -use frame_system::{ensure_none, offchain::SendTransactionTypes}; +use frame_system::{ensure_none, offchain::SendTransactionTypes, pallet_prelude::BlockNumberFor}; use scale_info::TypeInfo; use sp_arithmetic::{ traits::{CheckedAdd, Zero}, UpperOf, }; -use sp_npos_elections::{ - assignment_ratio_to_staked_normalized, BoundedSupports, ElectionScore, EvaluateSupport, - Supports, VoteWeight, -}; +use sp_npos_elections::{BoundedSupports, ElectionScore, IdentifierT, Supports, VoteWeight}; use sp_runtime::{ transaction_validity::{ InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, @@ -260,6 +257,9 @@ use sp_runtime::{ }; use sp_std::prelude::*; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + #[cfg(feature = "runtime-benchmarks")] mod benchmarking; #[cfg(test)] @@ -430,13 +430,17 @@ impl Default for RawSolution { DefaultNoBound, scale_info::TypeInfo, )] -#[scale_info(skip_type_params(T))] -pub struct ReadySolution { +#[scale_info(skip_type_params(AccountId, MaxWinners))] +pub struct ReadySolution +where + AccountId: IdentifierT, + MaxWinners: Get, +{ /// The final supports of the solution. /// /// This is target-major vector, storing each winners, total backing, and each individual /// backer. - pub supports: BoundedSupports, + pub supports: BoundedSupports, /// The score of the solution. /// /// This is needed to potentially challenge the solution. @@ -451,11 +455,11 @@ pub struct ReadySolution { /// These are stored together because they are often accessed together. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)] #[scale_info(skip_type_params(T))] -pub struct RoundSnapshot { +pub struct RoundSnapshot { /// All of the voters. - pub voters: Vec>, + pub voters: Vec, /// All of the targets. - pub targets: Vec, + pub targets: Vec, } /// Encodes the length of a solution or a snapshot. @@ -501,10 +505,10 @@ where fn eq(&self, other: &Self) -> bool { use ElectionError::*; match (self, other) { - (&Feasibility(ref x), &Feasibility(ref y)) if x == y => true, - (&Miner(ref x), &Miner(ref y)) if x == y => true, - (&DataProvider(ref x), &DataProvider(ref y)) if x == y => true, - (&Fallback(ref x), &Fallback(ref y)) if x == y => true, + (Feasibility(x), Feasibility(y)) if x == y => true, + (Miner(x), Miner(y)) if x == y => true, + (DataProvider(x), DataProvider(y)) if x == y => true, + (Fallback(x), Fallback(y)) if x == y => true, _ => false, } } @@ -581,10 +585,10 @@ pub mod pallet { /// Duration of the unsigned phase. #[pallet::constant] - type UnsignedPhase: Get; + type UnsignedPhase: Get>; /// Duration of the signed phase. #[pallet::constant] - type SignedPhase: Get; + type SignedPhase: Get>; /// The minimum amount of improvement to the solution score that defines a solution as /// "better" in the Signed phase. @@ -601,7 +605,7 @@ pub mod pallet { /// For example, if it is 5, that means that at least 5 blocks will elapse between attempts /// to submit the worker's solution. #[pallet::constant] - type OffchainRepeat: Get; + type OffchainRepeat: Get>; /// The priority of the unsigned transaction submitted in the unsigned-phase #[pallet::constant] @@ -614,6 +618,7 @@ pub mod pallet { type MinerConfig: crate::unsigned::MinerConfig< AccountId = Self::AccountId, MaxVotesPerVoter = ::MaxVotesPerVoter, + MaxWinners = Self::MaxWinners, >; /// Maximum number of signed submissions that can be queued. @@ -680,13 +685,13 @@ pub mod pallet { /// Something that will provide the election data. type DataProvider: ElectionDataProvider< AccountId = Self::AccountId, - BlockNumber = Self::BlockNumber, + BlockNumber = BlockNumberFor, >; /// Configuration for the fallback. type Fallback: InstantElectionProvider< AccountId = Self::AccountId, - BlockNumber = Self::BlockNumber, + BlockNumber = BlockNumberFor, DataProvider = Self::DataProvider, MaxWinners = Self::MaxWinners, >; @@ -697,7 +702,7 @@ pub mod pallet { /// BoundedExecution<_>` if the test-net is not expected to have thousands of nominators. type GovernanceFallback: InstantElectionProvider< AccountId = Self::AccountId, - BlockNumber = Self::BlockNumber, + BlockNumber = BlockNumberFor, DataProvider = Self::DataProvider, MaxWinners = Self::MaxWinners, >; @@ -733,11 +738,16 @@ pub mod pallet { fn max_votes_per_voter() -> u32 { ::MaxVotesPerVoter::get() } + + #[pallet::constant_name(MinerMaxWinners)] + fn max_winners() -> u32 { + ::MaxWinners::get() + } } #[pallet::hooks] impl Hooks> for Pallet { - fn on_initialize(now: T::BlockNumber) -> Weight { + fn on_initialize(now: BlockNumberFor) -> Weight { let next_election = T::DataProvider::next_election_prediction(now).max(now); let signed_deadline = T::SignedPhase::get() + T::UnsignedPhase::get(); @@ -814,7 +824,7 @@ pub mod pallet { } } - fn offchain_worker(now: T::BlockNumber) { + fn offchain_worker(now: BlockNumberFor) { use sp_runtime::offchain::storage_lock::{BlockAndTime, StorageLock}; // Create a lock with the maximum deadline of number of blocks in the unsigned phase. @@ -874,6 +884,11 @@ pub mod pallet { // configure this pallet. assert!(T::SignedMaxSubmissions::get() >= T::SignedMaxRefunds::get()); } + + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumberFor) -> Result<(), TryRuntimeError> { + Self::do_try_state() + } } #[pallet::call] @@ -1140,7 +1155,11 @@ pub mod pallet { /// An account has been slashed for submitting an invalid signed submission. Slashed { account: ::AccountId, value: BalanceOf }, /// There was a phase transition in a given round. - PhaseTransitioned { from: Phase, to: Phase, round: u32 }, + PhaseTransitioned { + from: Phase>, + to: Phase>, + round: u32, + }, } /// Error of the pallet that can be returned in response to dispatches. @@ -1242,19 +1261,22 @@ pub mod pallet { /// Current phase. #[pallet::storage] #[pallet::getter(fn current_phase)] - pub type CurrentPhase = StorageValue<_, Phase, ValueQuery>; + pub type CurrentPhase = StorageValue<_, Phase>, ValueQuery>; /// Current best solution, signed or unsigned, queued to be returned upon `elect`. + /// + /// Always sorted by score. #[pallet::storage] #[pallet::getter(fn queued_solution)] - pub type QueuedSolution = StorageValue<_, ReadySolution>; + pub type QueuedSolution = + StorageValue<_, ReadySolution>; /// Snapshot data of the round. /// /// This is created at the beginning of the signed phase and cleared upon calling `elect`. #[pallet::storage] #[pallet::getter(fn snapshot)] - pub type Snapshot = StorageValue<_, RoundSnapshot>; + pub type Snapshot = StorageValue<_, RoundSnapshot>>; /// Desired number of targets to elect for this round. /// @@ -1325,13 +1347,13 @@ pub mod pallet { #[pallet::pallet] #[pallet::without_storage_info] #[pallet::storage_version(STORAGE_VERSION)] - pub struct Pallet(PhantomData); + pub struct Pallet(_); } impl Pallet { /// Internal logic of the offchain worker, to be executed only when the offchain lock is /// acquired with success. - fn do_synchronized_offchain_worker(now: T::BlockNumber) { + fn do_synchronized_offchain_worker(now: BlockNumberFor) { let current_phase = Self::current_phase(); log!(trace, "lock for offchain worker acquired. Phase = {:?}", current_phase); match current_phase { @@ -1357,7 +1379,7 @@ impl Pallet { } /// Phase transition helper. - pub(crate) fn phase_transition(to: Phase) { + pub(crate) fn phase_transition(to: Phase>) { log!(info, "Starting phase {:?}, round {}.", to, Self::round()); Self::deposit_event(Event::PhaseTransitioned { from: >::get(), @@ -1385,7 +1407,7 @@ impl Pallet { // instead of using storage APIs, we do a manual encoding into a fixed-size buffer. // `encoded_size` encodes it without storing it anywhere, this should not cause any // allocation. - let snapshot = RoundSnapshot:: { voters, targets }; + let snapshot = RoundSnapshot::> { voters, targets }; let size = snapshot.encoded_size(); log!(debug, "snapshot pre-calculated size {:?}", size); let mut buffer = Vec::with_capacity(size); @@ -1479,89 +1501,22 @@ impl Pallet { pub fn feasibility_check( raw_solution: RawSolution>, compute: ElectionCompute, - ) -> Result, FeasibilityError> { - let RawSolution { solution, score, round } = raw_solution; - - // First, check round. - ensure!(Self::round() == round, FeasibilityError::InvalidRound); - - // Winners are not directly encoded in the solution. - let winners = solution.unique_targets(); - + ) -> Result, FeasibilityError> { let desired_targets = Self::desired_targets().ok_or(FeasibilityError::SnapshotUnavailable)?; - ensure!(winners.len() as u32 == desired_targets, FeasibilityError::WrongWinnerCount); - // Fail early if targets requested by data provider exceed maximum winners supported. - ensure!( - desired_targets <= ::MaxWinners::get(), - FeasibilityError::TooManyDesiredTargets - ); - - // Ensure that the solution's score can pass absolute min-score. - let submitted_score = raw_solution.score; - ensure!( - Self::minimum_untrusted_score().map_or(true, |min_score| { - submitted_score.strict_threshold_better(min_score, Perbill::zero()) - }), - FeasibilityError::UntrustedScoreTooLow - ); - - // Read the entire snapshot. - let RoundSnapshot { voters: snapshot_voters, targets: snapshot_targets } = - Self::snapshot().ok_or(FeasibilityError::SnapshotUnavailable)?; - - // ----- Start building. First, we need some closures. - let cache = helpers::generate_voter_cache::(&snapshot_voters); - let voter_at = helpers::voter_at_fn::(&snapshot_voters); - let target_at = helpers::target_at_fn::(&snapshot_targets); - let voter_index = helpers::voter_index_fn_usize::(&cache); - - // Then convert solution -> assignment. This will fail if any of the indices are gibberish, - // namely any of the voters or targets. - let assignments = solution - .into_assignment(voter_at, target_at) - .map_err::(Into::into)?; - - // Ensure that assignments is correct. - let _ = assignments.iter().try_for_each(|assignment| { - // Check that assignment.who is actually a voter (defensive-only). - // NOTE: while using the index map from `voter_index` is better than a blind linear - // search, this *still* has room for optimization. Note that we had the index when - // we did `solution -> assignment` and we lost it. Ideal is to keep the index - // around. - - // Defensive-only: must exist in the snapshot. - let snapshot_index = - voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?; - // Defensive-only: index comes from the snapshot, must exist. - let (_voter, _stake, targets) = - snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?; - - // Check that all of the targets are valid based on the snapshot. - if assignment.distribution.iter().any(|(d, _)| !targets.contains(d)) { - return Err(FeasibilityError::InvalidVote) - } - Ok(()) - })?; - - // ----- Start building support. First, we need one more closure. - let stake_of = helpers::stake_of_fn::(&snapshot_voters, &cache); - - // This might fail if the normalization fails. Very unlikely. See `integrity_test`. - let staked_assignments = assignment_ratio_to_staked_normalized(assignments, stake_of) - .map_err::(Into::into)?; - let supports = sp_npos_elections::to_supports(&staked_assignments); - - // Finally, check that the claimed score was indeed correct. - let known_score = supports.evaluate(); - ensure!(known_score == score, FeasibilityError::InvalidScore); - - // Size of winners in miner solution is equal to `desired_targets` <= `MaxWinners`. - let supports = supports - .try_into() - .defensive_map_err(|_| FeasibilityError::BoundedConversionFailed)?; - Ok(ReadySolution { supports, compute, score }) + let snapshot = Self::snapshot().ok_or(FeasibilityError::SnapshotUnavailable)?; + let round = Self::round(); + let minimum_untrusted_score = Self::minimum_untrusted_score(); + + Miner::::feasibility_check( + raw_solution, + compute, + desired_targets, + snapshot, + round, + minimum_untrusted_score, + ) } /// Perform the tasks to be done after a new `elect` has been triggered: @@ -1629,9 +1584,99 @@ impl Pallet { } } +#[cfg(feature = "try-runtime")] +impl Pallet { + fn do_try_state() -> Result<(), TryRuntimeError> { + Self::try_state_snapshot()?; + Self::try_state_signed_submissions_map()?; + Self::try_state_phase_off() + } + + // [`Snapshot`] state check. Invariants: + // - [`DesiredTargets`] exists if and only if [`Snapshot`] is present. + // - [`SnapshotMetadata`] exist if and only if [`Snapshot`] is present. + fn try_state_snapshot() -> Result<(), TryRuntimeError> { + if >::exists() && + >::exists() && + >::exists() + { + Ok(()) + } else if !>::exists() && + !>::exists() && + !>::exists() + { + Ok(()) + } else { + Err("If snapshot exists, metadata and desired targets should be set too. Otherwise, none should be set.".into()) + } + } + + // [`SignedSubmissionsMap`] state check. Invariants: + // - All [`SignedSubmissionIndices`] are present in [`SignedSubmissionsMap`], and no more; + // - [`SignedSubmissionNextIndex`] is not present in [`SignedSubmissionsMap`]; + // - [`SignedSubmissionIndices`] is sorted by election score. + fn try_state_signed_submissions_map() -> Result<(), TryRuntimeError> { + let mut last_score: ElectionScore = Default::default(); + let indices = >::get(); + + for (i, indice) in indices.iter().enumerate() { + let submission = >::get(indice.2); + if submission.is_none() { + return Err( + "All signed submissions indices must be part of the submissions map".into() + ) + } + + if i == 0 { + last_score = indice.0 + } else { + if last_score.strict_threshold_better(indice.0, Perbill::zero()) { + return Err( + "Signed submission indices vector must be ordered by election score".into() + ) + } + last_score = indice.0; + } + } + + if >::iter().nth(indices.len()).is_some() { + return Err( + "Signed submissions map length should be the same as the indices vec length".into() + ) + } + + match >::get() { + 0 => Ok(()), + next => + if >::get(next).is_some() { + return Err( + "The next submissions index should not be in the submissions maps already" + .into(), + ) + } else { + Ok(()) + }, + } + } + + // [`Phase::Off`] state check. Invariants: + // - If phase is `Phase::Off`, [`Snapshot`] must be none. + fn try_state_phase_off() -> Result<(), TryRuntimeError> { + match Self::current_phase().is_off() { + false => Ok(()), + true => + if >::get().is_some() { + Err("Snapshot must be none when in Phase::Off".into()) + } else { + Ok(()) + }, + } + } +} + impl ElectionProviderBase for Pallet { type AccountId = T::AccountId; - type BlockNumber = T::BlockNumber; + type BlockNumber = BlockNumberFor; type Error = ElectionError; type MaxWinners = T::MaxWinners; type DataProvider = T::DataProvider; @@ -1701,6 +1746,11 @@ mod feasibility_check { MultiPhase::feasibility_check(solution, COMPUTE), FeasibilityError::SnapshotUnavailable ); + + // kill also `SnapshotMetadata` and `DesiredTargets` for the storage state to be + // consistent for the try_state checks to pass. + >::kill(); + >::kill(); }) } diff --git a/frame/election-provider-multi-phase/src/migrations.rs b/frame/election-provider-multi-phase/src/migrations.rs index 77efe0d0c5e9..50b821e6db6a 100644 --- a/frame/election-provider-multi-phase/src/migrations.rs +++ b/frame/election-provider-multi-phase/src/migrations.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 347a4f19185f..911e93d35860 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,7 +45,7 @@ use sp_npos_elections::{ use sp_runtime::{ testing::Header, traits::{BlakeTwo256, IdentityLookup}, - PerU16, + BuildStorage, PerU16, }; use std::sync::Arc; @@ -54,12 +54,9 @@ pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { - System: frame_system::{Pallet, Call, Event, Config}, + System: frame_system::{Pallet, Call, Event, Config}, Balances: pallet_balances::{Pallet, Call, Event, Config}, MultiPhase: multi_phase::{Pallet, Call, Event}, } @@ -211,14 +208,13 @@ impl frame_system::Config for Runtime { type SS58Prefix = (); type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = BlockNumber; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type DbWeight = (); @@ -254,6 +250,10 @@ impl pallet_balances::Config for Runtime { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } #[derive(Default, Eq, PartialEq, Debug, Clone, Copy)] @@ -297,6 +297,8 @@ parameter_types! { pub static MockWeightInfo: MockedWeightInfo = MockedWeightInfo::Real; pub static MaxElectingVoters: VoterIndex = u32::max_value(); pub static MaxElectableTargets: TargetIndex = TargetIndex::max_value(); + + #[derive(Debug)] pub static MaxWinners: u32 = 200; pub static EpochLength: u64 = 30; @@ -316,8 +318,8 @@ impl onchain::Config for OnChainSeqPhragmen { pub struct MockFallback; impl ElectionProviderBase for MockFallback { + type BlockNumber = BlockNumber; type AccountId = AccountId; - type BlockNumber = u64; type Error = &'static str; type DataProvider = StakingMock; type MaxWinners = MaxWinners; @@ -359,15 +361,17 @@ impl MinerConfig for Runtime { type MaxLength = MinerMaxLength; type MaxWeight = MinerMaxWeight; type MaxVotesPerVoter = ::MaxVotesPerVoter; + type MaxWinners = MaxWinners; type Solution = TestNposSolution; fn solution_weight(v: u32, t: u32, a: u32, d: u32) -> Weight { match MockWeightInfo::get() { - MockedWeightInfo::Basic => Weight::from_ref_time( + MockedWeightInfo::Basic => Weight::from_parts( (10 as u64).saturating_add((5 as u64).saturating_mul(a as u64)), + 0, ), MockedWeightInfo::Complex => - Weight::from_ref_time((0 * v + 0 * t + 1000 * a + 0 * d) as u64), + Weight::from_parts((0 * v + 0 * t + 1000 * a + 0 * d) as u64, 0), MockedWeightInfo::Real => <() as multi_phase::weights::WeightInfo>::feasibility_check(v, t, a, d), } @@ -428,8 +432,8 @@ pub struct ExtBuilder {} pub struct StakingMock; impl ElectionDataProvider for StakingMock { + type BlockNumber = BlockNumber; type AccountId = AccountId; - type BlockNumber = u64; type MaxVotesPerVoter = MaxNominations; fn electable_targets(maybe_max_len: Option) -> data_provider::Result> { @@ -566,7 +570,7 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); let mut storage = - frame_system::GenesisConfig::default().build_storage::().unwrap(); + frame_system::GenesisConfig::::default().build_storage().unwrap(); let _ = pallet_balances::GenesisConfig:: { balances: vec![ @@ -607,7 +611,17 @@ impl ExtBuilder { } pub fn build_and_execute(self, test: impl FnOnce() -> ()) { - self.build().execute_with(test) + sp_tracing::try_init_simple(); + + let mut ext = self.build(); + ext.execute_with(test); + + #[cfg(feature = "try-runtime")] + ext.execute_with(|| { + assert_ok!(>::try_state( + System::block_number() + )); + }); } } diff --git a/frame/election-provider-multi-phase/src/signed.rs b/frame/election-provider-multi-phase/src/signed.rs index 895f3670a7f0..226404e4afc1 100644 --- a/frame/election-provider-multi-phase/src/signed.rs +++ b/frame/election-provider-multi-phase/src/signed.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,6 +27,7 @@ use frame_election_provider_support::NposSolution; use frame_support::traits::{ defensive_prelude::*, Currency, Get, OnUnbalanced, ReservableCurrency, }; +use frame_system::pallet_prelude::BlockNumberFor; use sp_arithmetic::traits::SaturatedConversion; use sp_core::bounded::BoundedVec; use sp_npos_elections::ElectionScore; @@ -100,10 +101,8 @@ pub type SignedSubmissionOf = SignedSubmission< /// Always sorted vector of a score, submitted at the given block number, which can be found at the /// given index (`u32`) of the `SignedSubmissionsMap`. -pub type SubmissionIndicesOf = BoundedVec< - (ElectionScore, ::BlockNumber, u32), - ::SignedMaxSubmissions, ->; +pub type SubmissionIndicesOf = + BoundedVec<(ElectionScore, BlockNumberFor, u32), ::SignedMaxSubmissions>; /// Outcome of [`SignedSubmissions::insert`]. pub enum InsertResult { @@ -216,7 +215,7 @@ impl SignedSubmissions { fn swap_out_submission( &mut self, remove_pos: usize, - insert: Option<(ElectionScore, T::BlockNumber, u32)>, + insert: Option<(ElectionScore, BlockNumberFor, u32)>, ) -> Option> { if remove_pos >= self.indices.len() { return None @@ -462,7 +461,7 @@ impl Pallet { /// /// Infallible pub fn finalize_signed_phase_accept_solution( - ready_solution: ReadySolution, + ready_solution: ReadySolution, who: &T::AccountId, deposit: BalanceOf, call_fee: BalanceOf, @@ -554,6 +553,11 @@ mod tests { MultiPhase::submit(RuntimeOrigin::signed(10), Box::new(solution)), Error::::PreDispatchEarlySubmission, ); + + // make sure invariants hold true and post-test try state checks to pass. + >::kill(); + >::kill(); + >::kill(); }) } @@ -1254,7 +1258,7 @@ mod tests { #[test] fn cannot_consume_too_much_future_weight() { ExtBuilder::default() - .signed_weight(Weight::from_ref_time(40).set_proof_size(u64::MAX)) + .signed_weight(Weight::from_parts(40, u64::MAX)) .mock_weight_info(MockedWeightInfo::Basic) .build_and_execute(|| { roll_to_signed(); @@ -1268,16 +1272,16 @@ mod tests { raw.solution.unique_targets().len() as u32, ); // default solution will have 5 edges (5 * 5 + 10) - assert_eq!(solution_weight, Weight::from_ref_time(35)); + assert_eq!(solution_weight, Weight::from_parts(35, 0)); assert_eq!(raw.solution.voter_count(), 5); assert_eq!( ::SignedMaxWeight::get(), - Weight::from_ref_time(40).set_proof_size(u64::MAX) + Weight::from_parts(40, u64::MAX) ); assert_ok!(MultiPhase::submit(RuntimeOrigin::signed(99), Box::new(raw.clone()))); - ::set(Weight::from_ref_time(30).set_proof_size(u64::MAX)); + ::set(Weight::from_parts(30, u64::MAX)); // note: resubmitting the same solution is technically okay as long as the queue has // space. diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index cdc71f7bf5bf..e21e6c5e6d22 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,12 +23,17 @@ use crate::{ }; use codec::Encode; use frame_election_provider_support::{NposSolution, NposSolver, PerThing128, VoteWeight}; -use frame_support::{dispatch::DispatchResult, ensure, traits::Get, BoundedVec}; -use frame_system::offchain::SubmitTransaction; +use frame_support::{ + dispatch::DispatchResult, + ensure, + traits::{DefensiveResult, Get}, + BoundedVec, +}; +use frame_system::{offchain::SubmitTransaction, pallet_prelude::BlockNumberFor}; use scale_info::TypeInfo; use sp_npos_elections::{ assignment_ratio_to_staked_normalized, assignment_staked_to_ratio_normalized, ElectionResult, - ElectionScore, + ElectionScore, EvaluateSupport, }; use sp_runtime::{ offchain::storage::{MutateStorageError, StorageValueRef}, @@ -293,12 +298,12 @@ impl Pallet { /// /// Returns `Ok(())` if offchain worker limit is respected, `Err(reason)` otherwise. If `Ok()` /// is returned, `now` is written in storage and will be used in further calls as the baseline. - pub fn ensure_offchain_repeat_frequency(now: T::BlockNumber) -> Result<(), MinerError> { + pub fn ensure_offchain_repeat_frequency(now: BlockNumberFor) -> Result<(), MinerError> { let threshold = T::OffchainRepeat::get(); let last_block = StorageValueRef::persistent(OFFCHAIN_LAST_BLOCK); let mutate_stat = last_block.mutate::<_, &'static str, _>( - |maybe_head: Result, _>| { + |maybe_head: Result>, _>| { match maybe_head { Ok(Some(head)) if now < head => Err("fork."), Ok(Some(head)) if now >= head && now <= head + threshold => @@ -351,7 +356,7 @@ impl Pallet { // ensure score is being improved. Panic henceforth. ensure!( - Self::queued_solution().map_or(true, |q: ReadySolution<_>| raw_solution + Self::queued_solution().map_or(true, |q: ReadySolution<_, _>| raw_solution .score .strict_threshold_better(q.score, T::BetterUnsignedThreshold::get())), Error::::PreDispatchWeakSubmission, @@ -387,6 +392,8 @@ pub trait MinerConfig { /// /// The weight is computed using `solution_weight`. type MaxWeight: Get; + /// The maximum number of winners that can be elected. + type MaxWinners: Get; /// Something that can compute the weight of a solution. /// /// This weight estimate is then used to trim the solution, based on [`MinerConfig::MaxWeight`]. @@ -689,6 +696,91 @@ impl Miner { ); final_decision } + + /// Checks the feasibility of a solution. + pub fn feasibility_check( + raw_solution: RawSolution>, + compute: ElectionCompute, + desired_targets: u32, + snapshot: RoundSnapshot>, + current_round: u32, + minimum_untrusted_score: Option, + ) -> Result, FeasibilityError> { + let RawSolution { solution, score, round } = raw_solution; + let RoundSnapshot { voters: snapshot_voters, targets: snapshot_targets } = snapshot; + + // First, check round. + ensure!(current_round == round, FeasibilityError::InvalidRound); + + // Winners are not directly encoded in the solution. + let winners = solution.unique_targets(); + + ensure!(winners.len() as u32 == desired_targets, FeasibilityError::WrongWinnerCount); + // Fail early if targets requested by data provider exceed maximum winners supported. + ensure!(desired_targets <= T::MaxWinners::get(), FeasibilityError::TooManyDesiredTargets); + + // Ensure that the solution's score can pass absolute min-score. + let submitted_score = raw_solution.score; + ensure!( + minimum_untrusted_score.map_or(true, |min_score| { + submitted_score.strict_threshold_better(min_score, sp_runtime::Perbill::zero()) + }), + FeasibilityError::UntrustedScoreTooLow + ); + + // ----- Start building. First, we need some closures. + let cache = helpers::generate_voter_cache::(&snapshot_voters); + let voter_at = helpers::voter_at_fn::(&snapshot_voters); + let target_at = helpers::target_at_fn::(&snapshot_targets); + let voter_index = helpers::voter_index_fn_usize::(&cache); + + // Then convert solution -> assignment. This will fail if any of the indices are gibberish, + // namely any of the voters or targets. + let assignments = solution + .into_assignment(voter_at, target_at) + .map_err::(Into::into)?; + + // Ensure that assignments is correct. + let _ = assignments.iter().try_for_each(|assignment| { + // Check that assignment.who is actually a voter (defensive-only). + // NOTE: while using the index map from `voter_index` is better than a blind linear + // search, this *still* has room for optimization. Note that we had the index when + // we did `solution -> assignment` and we lost it. Ideal is to keep the index + // around. + + // Defensive-only: must exist in the snapshot. + let snapshot_index = + voter_index(&assignment.who).ok_or(FeasibilityError::InvalidVoter)?; + // Defensive-only: index comes from the snapshot, must exist. + let (_voter, _stake, targets) = + snapshot_voters.get(snapshot_index).ok_or(FeasibilityError::InvalidVoter)?; + + // Check that all of the targets are valid based on the snapshot. + if assignment.distribution.iter().any(|(d, _)| !targets.contains(d)) { + return Err(FeasibilityError::InvalidVote) + } + Ok(()) + })?; + + // ----- Start building support. First, we need one more closure. + let stake_of = helpers::stake_of_fn::(&snapshot_voters, &cache); + + // This might fail if the normalization fails. Very unlikely. See `integrity_test`. + let staked_assignments = assignment_ratio_to_staked_normalized(assignments, stake_of) + .map_err::(Into::into)?; + let supports = sp_npos_elections::to_supports(&staked_assignments); + + // Finally, check that the claimed score was indeed correct. + let known_score = supports.evaluate(); + ensure!(known_score == score, FeasibilityError::InvalidScore); + + // Size of winners in miner solution is equal to `desired_targets` <= `MaxWinners`. + let supports = supports + .try_into() + .defensive_map_err(|_| FeasibilityError::BoundedConversionFailed)?; + + Ok(ReadySolution { supports, compute, score }) + } } #[cfg(test)] @@ -701,345 +793,177 @@ mod max_weight { let w = SolutionOrSnapshotSize { voters: 10, targets: 0 }; MockWeightInfo::set(crate::mock::MockedWeightInfo::Complex); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::zero().set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(0, u64::MAX)), 0 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(1).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(1, u64::MAX)), 0 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(999).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(999, u64::MAX)), 0 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(1000).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(1000, u64::MAX)), 1 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(1001).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(1001, u64::MAX)), 1 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(1990).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(1990, u64::MAX)), 1 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(1999).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(1999, u64::MAX)), 1 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(2000).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(2000, u64::MAX)), 2 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(2001).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(2001, u64::MAX)), 2 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(2010).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(2010, u64::MAX)), 2 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(2990).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(2990, u64::MAX)), 2 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(2999).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(2999, u64::MAX)), 2 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(3000).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(3000, u64::MAX)), 3 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(3333).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(3333, u64::MAX)), 3 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(5500).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(5500, u64::MAX)), 5 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(7777).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(7777, u64::MAX)), 7 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(9999).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(9999, u64::MAX)), 9 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(10_000).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(10_000, u64::MAX)), 10 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(10_999).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(10_999, u64::MAX)), 10 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(11_000).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(11_000, u64::MAX)), 10 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(22_000).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(22_000, u64::MAX)), 10 ); let w = SolutionOrSnapshotSize { voters: 1, targets: 0 }; assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(0).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(0, u64::MAX)), 0 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(1).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(1, u64::MAX)), 0 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(999).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(999, u64::MAX)), 0 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(1000).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(1000, u64::MAX)), 1 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(1001).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(1001, u64::MAX)), 1 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(1990).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(1990, u64::MAX)), 1 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(1999).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(1999, u64::MAX)), 1 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(2000).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(2000, u64::MAX)), 1 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(2001).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(2001, u64::MAX)), 1 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(2010).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(2010, u64::MAX)), 1 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(3333).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(3333, u64::MAX)), 1 ); let w = SolutionOrSnapshotSize { voters: 2, targets: 0 }; assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(0).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(0, u64::MAX)), 0 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(1).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(1, u64::MAX)), 0 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(999).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(999, u64::MAX)), 0 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(1000).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(1000, u64::MAX)), 1 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(1001).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(1001, u64::MAX)), 1 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(1999).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(1999, u64::MAX)), 1 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(2000).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(2000, u64::MAX)), 2 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(2001).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(2001, u64::MAX)), 2 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(2010).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(2010, u64::MAX)), 2 ); assert_eq!( - Miner::::maximum_voter_for_weight( - 0, - w, - Weight::from_ref_time(3333).set_proof_size(u64::MAX) - ), + Miner::::maximum_voter_for_weight(0, w, Weight::from_parts(3333, u64::MAX)), 2 ); } @@ -1340,7 +1264,7 @@ mod tests { #[test] fn miner_trims_weight() { ExtBuilder::default() - .miner_weight(Weight::from_ref_time(100).set_proof_size(u64::MAX)) + .miner_weight(Weight::from_parts(100, u64::MAX)) .mock_weight_info(crate::mock::MockedWeightInfo::Basic) .build_and_execute(|| { roll_to_unsigned(); @@ -1354,11 +1278,11 @@ mod tests { raw.solution.unique_targets().len() as u32, ); // default solution will have 5 edges (5 * 5 + 10) - assert_eq!(solution_weight, Weight::from_ref_time(35)); + assert_eq!(solution_weight, Weight::from_parts(35, 0)); assert_eq!(raw.solution.voter_count(), 5); // now reduce the max weight - ::set(Weight::from_ref_time(25).set_proof_size(u64::MAX)); + ::set(Weight::from_parts(25, u64::MAX)); let (raw, witness) = MultiPhase::mine_solution().unwrap(); let solution_weight = ::solution_weight( @@ -1368,7 +1292,7 @@ mod tests { raw.solution.unique_targets().len() as u32, ); // default solution will have 5 edges (5 * 5 + 10) - assert_eq!(solution_weight, Weight::from_ref_time(25)); + assert_eq!(solution_weight, Weight::from_parts(25, 0)); assert_eq!(raw.solution.voter_count(), 3); }) } diff --git a/frame/election-provider-multi-phase/src/weights.rs b/frame/election-provider-multi-phase/src/weights.rs index 221fd5837f7b..be578fac8c43 100644 --- a/frame/election-provider-multi-phase/src/weights.rs +++ b/frame/election-provider-multi-phase/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_election_provider_multi_phase //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_election_provider_multi_phase +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_election_provider_multi_phase. pub trait WeightInfo { @@ -62,262 +67,418 @@ pub trait WeightInfo { /// Weights for pallet_election_provider_multi_phase using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking CurrentPlannedSession (r:1 w:0) - // Storage: Staking ErasStartSessionIndex (r:1 w:0) - // Storage: Babe EpochIndex (r:1 w:0) - // Storage: Babe GenesisSlot (r:1 w:0) - // Storage: Babe CurrentSlot (r:1 w:0) - // Storage: Staking ForceEra (r:1 w:0) - // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking CurrentPlannedSession (r:1 w:0) + /// Proof: Staking CurrentPlannedSession (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ErasStartSessionIndex (r:1 w:0) + /// Proof: Staking ErasStartSessionIndex (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) + /// Storage: Babe EpochIndex (r:1 w:0) + /// Proof: Babe EpochIndex (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Babe GenesisSlot (r:1 w:0) + /// Proof: Babe GenesisSlot (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Babe CurrentSlot (r:1 w:0) + /// Proof: Babe CurrentSlot (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Staking ForceEra (r:1 w:0) + /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) fn on_initialize_nothing() -> Weight { - // Minimum execution time: 17_309 nanoseconds. - Weight::from_ref_time(17_646_000 as u64) - .saturating_add(T::DbWeight::get().reads(8 as u64)) + // Proof Size summary in bytes: + // Measured: `1028` + // Estimated: `3481` + // Minimum execution time: 22_089_000 picoseconds. + Weight::from_parts(22_677_000, 3481) + .saturating_add(T::DbWeight::get().reads(8_u64)) } - // Storage: ElectionProviderMultiPhase Round (r:1 w:0) - // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + /// Storage: ElectionProviderMultiPhase Round (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase Round (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) fn on_initialize_open_signed() -> Weight { - // Minimum execution time: 17_992 nanoseconds. - Weight::from_ref_time(18_426_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `148` + // Estimated: `1633` + // Minimum execution time: 11_986_000 picoseconds. + Weight::from_parts(12_445_000, 1633) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: ElectionProviderMultiPhase Round (r:1 w:0) - // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + /// Storage: ElectionProviderMultiPhase Round (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase Round (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) fn on_initialize_open_unsigned() -> Weight { - // Minimum execution time: 17_340 nanoseconds. - Weight::from_ref_time(17_881_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `148` + // Estimated: `1633` + // Minimum execution time: 12_988_000 picoseconds. + Weight::from_parts(13_281_000, 1633) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: System Account (r:1 w:1) - // Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) + /// Proof Skipped: ElectionProviderMultiPhase QueuedSolution (max_values: Some(1), max_size: None, mode: Measured) fn finalize_signed_phase_accept_solution() -> Weight { - // Minimum execution time: 35_571 nanoseconds. - Weight::from_ref_time(35_989_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 32_659_000 picoseconds. + Weight::from_parts(33_281_000, 3593) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: System Account (r:1 w:1) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn finalize_signed_phase_reject_solution() -> Weight { - // Minimum execution time: 27_403 nanoseconds. - Weight::from_ref_time(27_879_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 22_471_000 picoseconds. + Weight::from_parts(23_046_000, 3593) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:0 w:1) - // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) - // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) + /// Storage: ElectionProviderMultiPhase SnapshotMetadata (r:0 w:1) + /// Proof Skipped: ElectionProviderMultiPhase SnapshotMetadata (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) + /// Proof Skipped: ElectionProviderMultiPhase DesiredTargets (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) + /// Proof Skipped: ElectionProviderMultiPhase Snapshot (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `v` is `[1000, 2000]`. /// The range of component `t` is `[500, 1000]`. fn create_snapshot_internal(v: u32, _t: u32, ) -> Weight { - // Minimum execution time: 571_900 nanoseconds. - Weight::from_ref_time(589_170_000 as u64) - // Standard Error: 7_123 - .saturating_add(Weight::from_ref_time(384_767 as u64).saturating_mul(v as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 262_360_000 picoseconds. + Weight::from_parts(279_313_000, 0) + // Standard Error: 2_384 + .saturating_add(Weight::from_parts(176_415, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) - // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) - // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:1) - // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:1 w:0) - // Storage: ElectionProviderMultiPhase QueuedSolution (r:1 w:1) - // Storage: ElectionProviderMultiPhase Round (r:1 w:1) - // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) - // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) - // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + /// Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionIndices (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionNextIndex (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase SnapshotMetadata (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionsMap (max_values: None, max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase QueuedSolution (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase QueuedSolution (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase Round (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase Round (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) + /// Proof Skipped: ElectionProviderMultiPhase DesiredTargets (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) + /// Proof Skipped: ElectionProviderMultiPhase Snapshot (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `a` is `[500, 800]`. /// The range of component `d` is `[200, 400]`. fn elect_queued(a: u32, d: u32, ) -> Weight { - // Minimum execution time: 1_296_481 nanoseconds. - Weight::from_ref_time(1_076_121_575 as u64) - // Standard Error: 5_708 - .saturating_add(Weight::from_ref_time(474_995 as u64).saturating_mul(a as u64)) - // Standard Error: 8_556 - .saturating_add(Weight::from_ref_time(39_224 as u64).saturating_mul(d as u64)) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(8 as u64)) + // Proof Size summary in bytes: + // Measured: `371 + a * (768 ±0) + d * (48 ±0)` + // Estimated: `3923 + a * (768 ±0) + d * (49 ±0)` + // Minimum execution time: 301_283_000 picoseconds. + Weight::from_parts(324_586_000, 3923) + // Standard Error: 4_763 + .saturating_add(Weight::from_parts(279_812, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) + .saturating_add(Weight::from_parts(0, 768).saturating_mul(a.into())) + .saturating_add(Weight::from_parts(0, 49).saturating_mul(d.into())) } - // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) - // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) - // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) - // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) - // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) - // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) + /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase SnapshotMetadata (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + /// Proof: TransactionPayment NextFeeMultiplier (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionIndices (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionNextIndex (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) + /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionsMap (max_values: None, max_size: None, mode: Measured) fn submit() -> Weight { - // Minimum execution time: 58_716 nanoseconds. - Weight::from_ref_time(59_480_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `927` + // Estimated: `2412` + // Minimum execution time: 52_276_000 picoseconds. + Weight::from_parts(53_846_000, 2412) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) - // Storage: ElectionProviderMultiPhase Round (r:1 w:0) - // Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) - // Storage: ElectionProviderMultiPhase QueuedSolution (r:1 w:1) - // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) - // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) - // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) + /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase Round (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase Round (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase DesiredTargets (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase QueuedSolution (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase QueuedSolution (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase SnapshotMetadata (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase Snapshot (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase MinimumUntrustedScore (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `v` is `[1000, 2000]`. /// The range of component `t` is `[500, 1000]`. /// The range of component `a` is `[500, 800]`. /// The range of component `d` is `[200, 400]`. - fn submit_unsigned(v: u32, _t: u32, a: u32, _d: u32, ) -> Weight { - // Minimum execution time: 5_540_737 nanoseconds. - Weight::from_ref_time(5_567_381_000 as u64) - // Standard Error: 18_563 - .saturating_add(Weight::from_ref_time(603_280 as u64).saturating_mul(v as u64)) - // Standard Error: 55_009 - .saturating_add(Weight::from_ref_time(3_164_053 as u64).saturating_mul(a as u64)) - .saturating_add(T::DbWeight::get().reads(7 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + fn submit_unsigned(v: u32, t: u32, a: u32, _d: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `253 + t * (32 ±0) + v * (553 ±0)` + // Estimated: `1738 + t * (32 ±0) + v * (553 ±0)` + // Minimum execution time: 5_448_459_000 picoseconds. + Weight::from_parts(5_525_622_000, 1738) + // Standard Error: 21_478 + .saturating_add(Weight::from_parts(256_345, 0).saturating_mul(v.into())) + // Standard Error: 63_648 + .saturating_add(Weight::from_parts(5_103_224, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 553).saturating_mul(v.into())) } - // Storage: ElectionProviderMultiPhase Round (r:1 w:0) - // Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) - // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) - // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) + /// Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase DesiredTargets (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase Snapshot (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase Round (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase Round (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase MinimumUntrustedScore (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `v` is `[1000, 2000]`. /// The range of component `t` is `[500, 1000]`. /// The range of component `a` is `[500, 800]`. /// The range of component `d` is `[200, 400]`. - fn feasibility_check(v: u32, _t: u32, a: u32, _d: u32, ) -> Weight { - // Minimum execution time: 5_021_808 nanoseconds. - Weight::from_ref_time(5_051_856_000 as u64) - // Standard Error: 16_650 - .saturating_add(Weight::from_ref_time(683_344 as u64).saturating_mul(v as u64)) - // Standard Error: 49_342 - .saturating_add(Weight::from_ref_time(2_190_098 as u64).saturating_mul(a as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) + fn feasibility_check(v: u32, t: u32, a: u32, _d: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `228 + t * (32 ±0) + v * (553 ±0)` + // Estimated: `1713 + t * (32 ±0) + v * (553 ±0)` + // Minimum execution time: 4_724_399_000 picoseconds. + Weight::from_parts(4_886_472_000, 1713) + // Standard Error: 15_220 + .saturating_add(Weight::from_parts(365_569, 0).saturating_mul(v.into())) + // Standard Error: 45_104 + .saturating_add(Weight::from_parts(3_176_675, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 553).saturating_mul(v.into())) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking CurrentPlannedSession (r:1 w:0) - // Storage: Staking ErasStartSessionIndex (r:1 w:0) - // Storage: Babe EpochIndex (r:1 w:0) - // Storage: Babe GenesisSlot (r:1 w:0) - // Storage: Babe CurrentSlot (r:1 w:0) - // Storage: Staking ForceEra (r:1 w:0) - // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking CurrentPlannedSession (r:1 w:0) + /// Proof: Staking CurrentPlannedSession (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ErasStartSessionIndex (r:1 w:0) + /// Proof: Staking ErasStartSessionIndex (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) + /// Storage: Babe EpochIndex (r:1 w:0) + /// Proof: Babe EpochIndex (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Babe GenesisSlot (r:1 w:0) + /// Proof: Babe GenesisSlot (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Babe CurrentSlot (r:1 w:0) + /// Proof: Babe CurrentSlot (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Staking ForceEra (r:1 w:0) + /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) fn on_initialize_nothing() -> Weight { - // Minimum execution time: 17_309 nanoseconds. - Weight::from_ref_time(17_646_000 as u64) - .saturating_add(RocksDbWeight::get().reads(8 as u64)) + // Proof Size summary in bytes: + // Measured: `1028` + // Estimated: `3481` + // Minimum execution time: 22_089_000 picoseconds. + Weight::from_parts(22_677_000, 3481) + .saturating_add(RocksDbWeight::get().reads(8_u64)) } - // Storage: ElectionProviderMultiPhase Round (r:1 w:0) - // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + /// Storage: ElectionProviderMultiPhase Round (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase Round (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) fn on_initialize_open_signed() -> Weight { - // Minimum execution time: 17_992 nanoseconds. - Weight::from_ref_time(18_426_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `148` + // Estimated: `1633` + // Minimum execution time: 11_986_000 picoseconds. + Weight::from_parts(12_445_000, 1633) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: ElectionProviderMultiPhase Round (r:1 w:0) - // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + /// Storage: ElectionProviderMultiPhase Round (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase Round (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) fn on_initialize_open_unsigned() -> Weight { - // Minimum execution time: 17_340 nanoseconds. - Weight::from_ref_time(17_881_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `148` + // Estimated: `1633` + // Minimum execution time: 12_988_000 picoseconds. + Weight::from_parts(13_281_000, 1633) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: System Account (r:1 w:1) - // Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: ElectionProviderMultiPhase QueuedSolution (r:0 w:1) + /// Proof Skipped: ElectionProviderMultiPhase QueuedSolution (max_values: Some(1), max_size: None, mode: Measured) fn finalize_signed_phase_accept_solution() -> Weight { - // Minimum execution time: 35_571 nanoseconds. - Weight::from_ref_time(35_989_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 32_659_000 picoseconds. + Weight::from_parts(33_281_000, 3593) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: System Account (r:1 w:1) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn finalize_signed_phase_reject_solution() -> Weight { - // Minimum execution time: 27_403 nanoseconds. - Weight::from_ref_time(27_879_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 22_471_000 picoseconds. + Weight::from_parts(23_046_000, 3593) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:0 w:1) - // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) - // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) + /// Storage: ElectionProviderMultiPhase SnapshotMetadata (r:0 w:1) + /// Proof Skipped: ElectionProviderMultiPhase SnapshotMetadata (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) + /// Proof Skipped: ElectionProviderMultiPhase DesiredTargets (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) + /// Proof Skipped: ElectionProviderMultiPhase Snapshot (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `v` is `[1000, 2000]`. /// The range of component `t` is `[500, 1000]`. fn create_snapshot_internal(v: u32, _t: u32, ) -> Weight { - // Minimum execution time: 571_900 nanoseconds. - Weight::from_ref_time(589_170_000 as u64) - // Standard Error: 7_123 - .saturating_add(Weight::from_ref_time(384_767 as u64).saturating_mul(v as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 262_360_000 picoseconds. + Weight::from_parts(279_313_000, 0) + // Standard Error: 2_384 + .saturating_add(Weight::from_parts(176_415, 0).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } - // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) - // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) - // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:1) - // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:1 w:0) - // Storage: ElectionProviderMultiPhase QueuedSolution (r:1 w:1) - // Storage: ElectionProviderMultiPhase Round (r:1 w:1) - // Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) - // Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) - // Storage: ElectionProviderMultiPhase CurrentPhase (r:0 w:1) + /// Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionIndices (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionNextIndex (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase SnapshotMetadata (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionsMap (max_values: None, max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase QueuedSolution (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase QueuedSolution (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase Round (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase Round (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase DesiredTargets (r:0 w:1) + /// Proof Skipped: ElectionProviderMultiPhase DesiredTargets (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase Snapshot (r:0 w:1) + /// Proof Skipped: ElectionProviderMultiPhase Snapshot (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `a` is `[500, 800]`. /// The range of component `d` is `[200, 400]`. fn elect_queued(a: u32, d: u32, ) -> Weight { - // Minimum execution time: 1_296_481 nanoseconds. - Weight::from_ref_time(1_076_121_575 as u64) - // Standard Error: 5_708 - .saturating_add(Weight::from_ref_time(474_995 as u64).saturating_mul(a as u64)) - // Standard Error: 8_556 - .saturating_add(Weight::from_ref_time(39_224 as u64).saturating_mul(d as u64)) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(8 as u64)) + // Proof Size summary in bytes: + // Measured: `371 + a * (768 ±0) + d * (48 ±0)` + // Estimated: `3923 + a * (768 ±0) + d * (49 ±0)` + // Minimum execution time: 301_283_000 picoseconds. + Weight::from_parts(324_586_000, 3923) + // Standard Error: 4_763 + .saturating_add(Weight::from_parts(279_812, 0).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(8_u64)) + .saturating_add(Weight::from_parts(0, 768).saturating_mul(a.into())) + .saturating_add(Weight::from_parts(0, 49).saturating_mul(d.into())) } - // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) - // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) - // Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) - // Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) - // Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) - // Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) + /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase SnapshotMetadata (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TransactionPayment NextFeeMultiplier (r:1 w:0) + /// Proof: TransactionPayment NextFeeMultiplier (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: ElectionProviderMultiPhase SignedSubmissionIndices (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionIndices (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase SignedSubmissionNextIndex (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionNextIndex (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase SignedSubmissionsMap (r:0 w:1) + /// Proof Skipped: ElectionProviderMultiPhase SignedSubmissionsMap (max_values: None, max_size: None, mode: Measured) fn submit() -> Weight { - // Minimum execution time: 58_716 nanoseconds. - Weight::from_ref_time(59_480_000 as u64) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `927` + // Estimated: `2412` + // Minimum execution time: 52_276_000 picoseconds. + Weight::from_parts(53_846_000, 2412) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } - // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) - // Storage: ElectionProviderMultiPhase Round (r:1 w:0) - // Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) - // Storage: ElectionProviderMultiPhase QueuedSolution (r:1 w:1) - // Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) - // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) - // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) + /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase Round (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase Round (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase DesiredTargets (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase QueuedSolution (r:1 w:1) + /// Proof Skipped: ElectionProviderMultiPhase QueuedSolution (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase SnapshotMetadata (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase SnapshotMetadata (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase Snapshot (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase MinimumUntrustedScore (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `v` is `[1000, 2000]`. /// The range of component `t` is `[500, 1000]`. /// The range of component `a` is `[500, 800]`. /// The range of component `d` is `[200, 400]`. - fn submit_unsigned(v: u32, _t: u32, a: u32, _d: u32, ) -> Weight { - // Minimum execution time: 5_540_737 nanoseconds. - Weight::from_ref_time(5_567_381_000 as u64) - // Standard Error: 18_563 - .saturating_add(Weight::from_ref_time(603_280 as u64).saturating_mul(v as u64)) - // Standard Error: 55_009 - .saturating_add(Weight::from_ref_time(3_164_053 as u64).saturating_mul(a as u64)) - .saturating_add(RocksDbWeight::get().reads(7 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + fn submit_unsigned(v: u32, t: u32, a: u32, _d: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `253 + t * (32 ±0) + v * (553 ±0)` + // Estimated: `1738 + t * (32 ±0) + v * (553 ±0)` + // Minimum execution time: 5_448_459_000 picoseconds. + Weight::from_parts(5_525_622_000, 1738) + // Standard Error: 21_478 + .saturating_add(Weight::from_parts(256_345, 0).saturating_mul(v.into())) + // Standard Error: 63_648 + .saturating_add(Weight::from_parts(5_103_224, 0).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 553).saturating_mul(v.into())) } - // Storage: ElectionProviderMultiPhase Round (r:1 w:0) - // Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) - // Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) - // Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) + /// Storage: ElectionProviderMultiPhase DesiredTargets (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase DesiredTargets (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase Snapshot (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase Snapshot (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase Round (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase Round (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ElectionProviderMultiPhase MinimumUntrustedScore (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase MinimumUntrustedScore (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `v` is `[1000, 2000]`. /// The range of component `t` is `[500, 1000]`. /// The range of component `a` is `[500, 800]`. /// The range of component `d` is `[200, 400]`. - fn feasibility_check(v: u32, _t: u32, a: u32, _d: u32, ) -> Weight { - // Minimum execution time: 5_021_808 nanoseconds. - Weight::from_ref_time(5_051_856_000 as u64) - // Standard Error: 16_650 - .saturating_add(Weight::from_ref_time(683_344 as u64).saturating_mul(v as u64)) - // Standard Error: 49_342 - .saturating_add(Weight::from_ref_time(2_190_098 as u64).saturating_mul(a as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) + fn feasibility_check(v: u32, t: u32, a: u32, _d: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `228 + t * (32 ±0) + v * (553 ±0)` + // Estimated: `1713 + t * (32 ±0) + v * (553 ±0)` + // Minimum execution time: 4_724_399_000 picoseconds. + Weight::from_parts(4_886_472_000, 1713) + // Standard Error: 15_220 + .saturating_add(Weight::from_parts(365_569, 0).saturating_mul(v.into())) + // Standard Error: 45_104 + .saturating_add(Weight::from_parts(3_176_675, 0).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(t.into())) + .saturating_add(Weight::from_parts(0, 553).saturating_mul(v.into())) } } diff --git a/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml b/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml new file mode 100644 index 000000000000..01644887759c --- /dev/null +++ b/frame/election-provider-multi-phase/test-staking-e2e/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "pallet-election-provider-e2e-test" +version = "1.0.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME election provider multi phase pallet tests with staking pallet, bags-list and session pallets" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dev-dependencies] +parking_lot = "0.12.1" +codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } +scale-info = { version = "2.0.1", features = ["derive"] } +log = { version = "0.4.17", default-features = false } + +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } +sp-io = { version = "23.0.0", path = "../../../primitives/io" } +sp-std = { version = "8.0.0", path = "../../../primitives/std" } +sp-staking = { version = "4.0.0-dev", path = "../../../primitives/staking" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/npos-elections" } +sp-tracing = { version = "10.0.0", path = "../../../primitives/tracing" } + +frame-system = { version = "4.0.0-dev", path = "../../system" } +frame-support = { version = "4.0.0-dev", path = "../../support" } +frame-election-provider-support = { version = "4.0.0-dev", path = "../../election-provider-support" } + +pallet-election-provider-multi-phase = { version = "4.0.0-dev", path = "../../election-provider-multi-phase" } +pallet-staking = { version = "4.0.0-dev", path = "../../staking" } +pallet-bags-list = { version = "4.0.0-dev", path = "../../bags-list" } +pallet-balances = { version = "4.0.0-dev", path = "../../balances" } +pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } +pallet-session = { version = "4.0.0-dev", path = "../../session" } + diff --git a/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs b/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs new file mode 100644 index 000000000000..e40bac3e9fc4 --- /dev/null +++ b/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs @@ -0,0 +1,389 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(test)] +mod mock; + +pub(crate) const LOG_TARGET: &str = "tests::e2e-epm"; + +use frame_support::{assert_err, assert_noop, assert_ok}; +use mock::*; +use sp_core::Get; +use sp_npos_elections::{to_supports, StakedAssignment}; +use sp_runtime::Perbill; + +use crate::mock::RuntimeOrigin; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("🛠️ ", $patter) $(, $values)* + ) + }; +} + +fn log_current_time() { + log!( + trace, + "block: {:?}, session: {:?}, era: {:?}, EPM phase: {:?} ts: {:?}", + System::block_number(), + Session::current_index(), + Staking::current_era(), + ElectionProviderMultiPhase::current_phase(), + Timestamp::now() + ); +} + +#[test] +fn block_progression_works() { + let (mut ext, pool_state, _) = ExtBuilder::default().build_offchainify(); + + ext.execute_with(|| { + assert_eq!(active_era(), 0); + assert_eq!(Session::current_index(), 0); + assert!(ElectionProviderMultiPhase::current_phase().is_off()); + + assert!(start_next_active_era(pool_state.clone()).is_ok()); + assert_eq!(active_era(), 1); + assert_eq!(Session::current_index(), >::get()); + + assert!(ElectionProviderMultiPhase::current_phase().is_off()); + + roll_to_epm_signed(); + assert!(ElectionProviderMultiPhase::current_phase().is_signed()); + }); + + let (mut ext, pool_state, _) = ExtBuilder::default().build_offchainify(); + + ext.execute_with(|| { + assert_eq!(active_era(), 0); + assert_eq!(Session::current_index(), 0); + assert!(ElectionProviderMultiPhase::current_phase().is_off()); + + assert!(start_next_active_era_delayed_solution(pool_state).is_ok()); + // if the solution is delayed, EPM will end up in emergency mode.. + assert!(ElectionProviderMultiPhase::current_phase().is_emergency()); + // .. era won't progress.. + assert_eq!(active_era(), 0); + // .. but session does. + assert_eq!(Session::current_index(), 2); + }) +} + +#[test] +fn offchainify_works() { + use pallet_election_provider_multi_phase::QueuedSolution; + + let staking_builder = StakingExtBuilder::default(); + let epm_builder = EpmExtBuilder::default(); + let (mut ext, pool_state, _) = ExtBuilder::default() + .epm(epm_builder) + .staking(staking_builder) + .build_offchainify(); + + ext.execute_with(|| { + // test ocw progression and solution queue if submission when unsigned phase submission is + // not delayed. + for _ in 0..100 { + roll_one(pool_state.clone(), false); + let current_phase = ElectionProviderMultiPhase::current_phase(); + + assert!( + match QueuedSolution::::get() { + Some(_) => current_phase.is_unsigned(), + None => !current_phase.is_unsigned(), + }, + "solution must be queued *only* in unsigned phase" + ); + } + + // test ocw solution queue if submission in unsigned phase is delayed. + for _ in 0..100 { + roll_one(pool_state.clone(), true); + assert_eq!( + QueuedSolution::::get(), + None, + "solution must never be submitted and stored since it is delayed" + ); + } + }) +} + +#[test] +/// Replicates the Kusama incident of 8th Dec 2022 and its resolution through the governance +/// fallback. +/// +/// After enough slashes exceeded the `Staking::OffendingValidatorsThreshold`, the staking pallet +/// set `Forcing::ForceNew`. When a new session starts, staking will start to force a new era and +/// calls ::elect(). If at this point EPM and the staking miners did not +/// have enough time to queue a new solution (snapshot + solution submission), the election request +/// fails. If there is no election fallback mechanism in place, EPM enters in emergency mode. +/// Recovery: Once EPM is in emergency mode, subsequent calls to `elect()` will fail until a new +/// solution is added to EPM's `QueuedSolution` queue. This can be achieved through +/// `Call::set_emergency_election_result` or `Call::governance_fallback` dispatchables. Once a new +/// solution is added to the queue, EPM phase transitions to `Phase::Off` and the election flow +/// restarts. Note that in this test case, the emergency throttling is disabled. +fn enters_emergency_phase_after_forcing_before_elect() { + let epm_builder = EpmExtBuilder::default().disable_emergency_throttling(); + let (mut ext, pool_state, _) = ExtBuilder::default().epm(epm_builder).build_offchainify(); + + ext.execute_with(|| { + log!( + trace, + "current validators (staking): {:?}", + >::validators() + ); + let session_validators_before = Session::validators(); + + roll_to_epm_off(); + assert!(ElectionProviderMultiPhase::current_phase().is_off()); + + assert_eq!(pallet_staking::ForceEra::::get(), pallet_staking::Forcing::NotForcing); + // slashes so that staking goes into `Forcing::ForceNew`. + slash_through_offending_threshold(); + + assert_eq!(pallet_staking::ForceEra::::get(), pallet_staking::Forcing::ForceNew); + + advance_session_delayed_solution(pool_state.clone()); + assert!(ElectionProviderMultiPhase::current_phase().is_emergency()); + log_current_time(); + + let era_before_delayed_next = Staking::current_era(); + // try to advance 2 eras. + assert!(start_next_active_era_delayed_solution(pool_state.clone()).is_ok()); + assert_eq!(Staking::current_era(), era_before_delayed_next); + assert!(start_next_active_era(pool_state).is_err()); + assert_eq!(Staking::current_era(), era_before_delayed_next); + + // EPM is still in emergency phase. + assert!(ElectionProviderMultiPhase::current_phase().is_emergency()); + + // session validator set remains the same. + assert_eq!(Session::validators(), session_validators_before); + + // performs recovery through the set emergency result. + let supports = to_supports(&vec![ + StakedAssignment { who: 21, distribution: vec![(21, 10)] }, + StakedAssignment { who: 31, distribution: vec![(21, 10), (31, 10)] }, + StakedAssignment { who: 41, distribution: vec![(41, 10)] }, + ]); + assert!(ElectionProviderMultiPhase::set_emergency_election_result( + RuntimeOrigin::root(), + supports + ) + .is_ok()); + + // EPM can now roll to signed phase to proceed with elections. The validator set is the + // expected (ie. set through `set_emergency_election_result`). + roll_to_epm_signed(); + //assert!(ElectionProviderMultiPhase::current_phase().is_signed()); + assert_eq!(Session::validators(), vec![21, 31, 41]); + assert_eq!(Staking::current_era(), era_before_delayed_next.map(|e| e + 1)); + }); +} + +#[test] +/// Continuously slash 10% of the active validators per era. +/// +/// Since the `OffendingValidatorsThreshold` is only checked per era staking does not force a new +/// era even as the number of active validators is decreasing across eras. When processing a new +/// slash, staking calculates the offending threshold based on the length of the current list of +/// active validators. Thus, slashing a percentage of the current validators that is lower than +/// `OffendingValidatorsThreshold` will never force a new era. However, as the slashes progress, if +/// the subsequent elections do not meet the minimum election untrusted score, the election will +/// fail and enter in emenergency mode. +fn continous_slashes_below_offending_threshold() { + let staking_builder = StakingExtBuilder::default().validator_count(10); + let epm_builder = EpmExtBuilder::default().disable_emergency_throttling(); + + let (mut ext, pool_state, _) = ExtBuilder::default() + .epm(epm_builder) + .staking(staking_builder) + .build_offchainify(); + + ext.execute_with(|| { + assert_eq!(Session::validators().len(), 10); + let mut active_validator_set = Session::validators(); + + roll_to_epm_signed(); + + // set a minimum election score. + assert!(set_minimum_election_score(500, 1000, 500).is_ok()); + + // slash 10% of the active validators and progress era until the minimum trusted score + // is reached. + while active_validator_set.len() > 0 { + let slashed = slash_percentage(Perbill::from_percent(10)); + assert_eq!(slashed.len(), 1); + + // break loop when era does not progress; EPM is in emergency phase as election + // failed due to election minimum score. + if start_next_active_era(pool_state.clone()).is_err() { + assert!(ElectionProviderMultiPhase::current_phase().is_emergency()); + break + } + + active_validator_set = Session::validators(); + + log!( + trace, + "slashed 10% of active validators ({:?}). After slash: {:?}", + slashed, + active_validator_set + ); + } + }); +} + +#[test] +/// Slashed validator sets intentions in the same era of slashing. +/// +/// When validators are slashed, they are chilled and removed from the current `VoterList`. Thus, +/// the slashed validator should not be considered in the next validator set. However, if the +/// slashed validator sets its intention to validate again in the same era when it was slashed and +/// chilled, the validator may not be removed from the active validator set across eras, provided +/// it would selected in the subsequent era if there was no slash. Nominators of the slashed +/// validator will also be slashed and chilled, as expected, but the nomination intentions will +/// remain after the validator re-set the intention to be validating again. +/// +/// This behaviour is due to removing implicit chill upon slash +/// . +/// +/// Related to . +fn set_validation_intention_after_chilled() { + use frame_election_provider_support::SortedListProvider; + use pallet_staking::{Event, Forcing, Nominators}; + + let (mut ext, pool_state, _) = ExtBuilder::default() + .epm(EpmExtBuilder::default()) + .staking(StakingExtBuilder::default()) + .build_offchainify(); + + ext.execute_with(|| { + assert_eq!(active_era(), 0); + // validator is part of the validator set. + assert!(Session::validators().contains(&41)); + assert!(::VoterList::contains(&41)); + + // nominate validator 81. + assert_ok!(Staking::nominate(RuntimeOrigin::signed(21), vec![41])); + assert_eq!(Nominators::::get(21).unwrap().targets, vec![41]); + + // validator is slashed. it is removed from the `VoterList` through chilling but in the + // current era, the validator is still part of the active validator set. + add_slash(&41); + assert!(Session::validators().contains(&41)); + assert!(!::VoterList::contains(&41)); + assert_eq!( + staking_events(), + [ + Event::Chilled { stash: 41 }, + Event::ForceEra { mode: Forcing::ForceNew }, + Event::SlashReported { + validator: 41, + slash_era: 0, + fraction: Perbill::from_percent(10) + } + ], + ); + + // after the nominator is slashed and chilled, the nominations remain. + assert_eq!(Nominators::::get(21).unwrap().targets, vec![41]); + + // validator sets intention to stake again in the same era it was chilled. + assert_ok!(Staking::validate(RuntimeOrigin::signed(41), Default::default())); + + // progress era and check that the slashed validator is still part of the validator + // set. + assert!(start_next_active_era(pool_state).is_ok()); + assert_eq!(active_era(), 1); + assert!(Session::validators().contains(&41)); + assert!(::VoterList::contains(&41)); + + // nominations are still active as before the slash. + assert_eq!(Nominators::::get(21).unwrap().targets, vec![41]); + }) +} + +#[test] +/// Active ledger balance may fall below ED if account chills before unbounding. +/// +/// Unbonding call fails if the remaining ledger's stash balance falls below the existential +/// deposit. However, if the stash is chilled before unbonding, the ledger's active balance may +/// be below ED. In that case, only the stash (or root) can kill the ledger entry by calling +/// `withdraw_unbonded` after the bonding period has passed. +/// +/// Related to . +fn ledger_consistency_active_balance_below_ed() { + use pallet_staking::{Error, Event}; + + let (mut ext, pool_state, _) = + ExtBuilder::default().staking(StakingExtBuilder::default()).build_offchainify(); + + ext.execute_with(|| { + assert_eq!(Staking::ledger(&11).unwrap().active, 1000); + + // unbonding total of active stake fails because the active ledger balance would fall + // below the `MinNominatorBond`. + assert_noop!( + Staking::unbond(RuntimeOrigin::signed(11), 1000), + Error::::InsufficientBond + ); + + // however, chilling works as expected. + assert_ok!(Staking::chill(RuntimeOrigin::signed(11))); + + // now unbonding the full active balance works, since remainer of the active balance is + // not enforced to be below `MinNominatorBond` if the stash has been chilled. + assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1000)); + + // the active balance of the ledger entry is 0, while total balance is 1000 until + // `withdraw_unbonded` is called. + assert_eq!(Staking::ledger(&11).unwrap().active, 0); + assert_eq!(Staking::ledger(&11).unwrap().total, 1000); + + // trying to withdraw the unbonded balance won't work yet because not enough bonding + // eras have passed. + assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0)); + assert_eq!(Staking::ledger(&11).unwrap().total, 1000); + + // tries to reap stash after chilling, which fails since the stash total balance is + // above ED. + assert_err!( + Staking::reap_stash(RuntimeOrigin::signed(11), 21, 0), + Error::::FundedTarget, + ); + + // check the events so far: 1x Chilled and 1x Unbounded + assert_eq!( + staking_events(), + [Event::Chilled { stash: 11 }, Event::Unbonded { stash: 11, amount: 1000 }] + ); + + // after advancing `BondingDuration` eras, the `withdraw_unbonded` will unlock the + // chunks and the ledger entry will be cleared, since the ledger active balance is 0. + advance_eras( + ::BondingDuration::get() as usize, + pool_state, + ); + assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0)); + assert_eq!(Staking::ledger(&11), None); + }); +} diff --git a/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs b/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs new file mode 100644 index 000000000000..0d5f8872193a --- /dev/null +++ b/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs @@ -0,0 +1,885 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +#![allow(dead_code)] + +use _feps::ExtendedBalance; +use frame_support::{ + assert_ok, dispatch::UnfilteredDispatchable, parameter_types, traits, traits::Hooks, + weights::constants, +}; +use frame_system::EnsureRoot; +use sp_core::{ConstU32, Get}; +use sp_npos_elections::{ElectionScore, VoteWeight}; +use sp_runtime::{ + offchain::{ + testing::{OffchainState, PoolState, TestOffchainExt, TestTransactionPoolExt}, + OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, + }, + testing, + traits::Zero, + transaction_validity, BuildStorage, PerU16, Perbill, +}; +use sp_staking::{ + offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, + EraIndex, SessionIndex, +}; +use sp_std::prelude::*; +use std::collections::BTreeMap; + +use codec::Decode; +use frame_election_provider_support::{onchain, ElectionDataProvider, SequentialPhragmen, Weight}; +use pallet_election_provider_multi_phase::{ + unsigned::MinerConfig, Call, ElectionCompute, QueuedSolution, SolutionAccuracyOf, +}; +use pallet_staking::StakerStatus; +use parking_lot::RwLock; +use std::sync::Arc; + +use frame_support::derive_impl; + +use crate::{log, log_current_time}; + +pub const INIT_TIMESTAMP: BlockNumber = 30_000; +pub const BLOCK_TIME: BlockNumber = 1000; + +type Block = frame_system::mocking::MockBlockU32; +type Extrinsic = testing::TestXt; + +frame_support::construct_runtime!( + pub enum Runtime + { + System: frame_system, + ElectionProviderMultiPhase: pallet_election_provider_multi_phase, + Staking: pallet_staking, + Balances: pallet_balances, + BagsList: pallet_bags_list, + Session: pallet_session, + Historical: pallet_session::historical, + Timestamp: pallet_timestamp, + } +); + +pub(crate) type AccountId = u64; +pub(crate) type AccountIndex = u32; +pub(crate) type BlockNumber = u32; +pub(crate) type Balance = u64; +pub(crate) type VoterIndex = u16; +pub(crate) type TargetIndex = u16; +pub(crate) type Moment = u32; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type Block = Block; + type BlockHashCount = ConstU32<10>; + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type OnSetCode = (); + + type AccountData = pallet_balances::AccountData; +} + +const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); +parameter_types! { + pub static ExistentialDeposit: Balance = 1; + pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights + ::with_sensible_defaults( + Weight::from_parts(2u64 * constants::WEIGHT_REF_TIME_PER_SECOND, u64::MAX), + NORMAL_DISPATCH_RATIO, + ); +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = traits::ConstU32<1024>; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type MaxHolds = ConstU32<1>; + type MaxFreezes = traits::ConstU32<1>; + type RuntimeHoldReason = RuntimeHoldReason; + type FreezeIdentifier = (); + type WeightInfo = (); +} + +impl pallet_timestamp::Config for Runtime { + type Moment = Moment; + type OnTimestampSet = (); + type MinimumPeriod = traits::ConstU32<5>; + type WeightInfo = (); +} + +parameter_types! { + pub static Period: u32 = 30; + pub static Offset: u32 = 0; +} + +sp_runtime::impl_opaque_keys! { + pub struct SessionKeys { + pub other: OtherSessionHandler, + } +} + +impl pallet_session::Config for Runtime { + type SessionManager = pallet_session::historical::NoteHistoricalRoot; + type Keys = SessionKeys; + type ShouldEndSession = pallet_session::PeriodicSessions; + type NextSessionRotation = pallet_session::PeriodicSessions; + type SessionHandler = (OtherSessionHandler,); + type RuntimeEvent = RuntimeEvent; + type ValidatorId = AccountId; + type ValidatorIdOf = pallet_staking::StashOf; + type WeightInfo = (); +} +impl pallet_session::historical::Config for Runtime { + type FullIdentification = pallet_staking::Exposure; + type FullIdentificationOf = pallet_staking::ExposureOf; +} + +frame_election_provider_support::generate_solution_type!( + #[compact] + pub struct MockNposSolution::< + VoterIndex = VoterIndex, + TargetIndex = TargetIndex, + Accuracy = PerU16, + MaxVoters = ConstU32::<2_000> + >(16) +); + +parameter_types! { + pub static SignedPhase: BlockNumber = 10; + pub static UnsignedPhase: BlockNumber = 10; + // we expect a minimum of 3 blocks in signed phase and unsigned phases before trying + // enetering in emergency phase after the election failed. + pub static MinBlocksBeforeEmergency: BlockNumber = 3; + pub static MaxElectingVoters: VoterIndex = 1000; + pub static MaxElectableTargets: TargetIndex = 1000; + pub static MaxActiveValidators: u32 = 1000; + pub static OffchainRepeat: u32 = 5; + pub static MinerMaxLength: u32 = 256; + pub static MinerMaxWeight: Weight = BlockWeights::get().max_block; + pub static TransactionPriority: transaction_validity::TransactionPriority = 1; + #[derive(Debug)] + pub static MaxWinners: u32 = 100; + pub static MaxVotesPerVoter: u32 = 16; + pub static MaxNominations: u32 = 16; +} + +impl pallet_election_provider_multi_phase::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type EstimateCallFee = frame_support::traits::ConstU32<8>; + type SignedPhase = SignedPhase; + type UnsignedPhase = UnsignedPhase; + type BetterSignedThreshold = (); + type BetterUnsignedThreshold = (); + type OffchainRepeat = OffchainRepeat; + type MinerTxPriority = TransactionPriority; + type MinerConfig = Self; + type SignedMaxSubmissions = ConstU32<10>; + type SignedRewardBase = (); + type SignedDepositBase = (); + type SignedDepositByte = (); + type SignedMaxRefunds = ConstU32<3>; + type SignedDepositWeight = (); + type SignedMaxWeight = (); + type SlashHandler = (); + type RewardHandler = (); + type DataProvider = Staking; + type Fallback = + frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, MaxWinners)>; + type GovernanceFallback = onchain::OnChainExecution; + type Solver = SequentialPhragmen, ()>; + type ForceOrigin = EnsureRoot; + type MaxElectableTargets = MaxElectableTargets; + type MaxElectingVoters = MaxElectingVoters; + type MaxWinners = MaxWinners; + type BenchmarkingConfig = NoopElectionProviderBenchmarkConfig; + type WeightInfo = (); +} + +impl MinerConfig for Runtime { + type AccountId = AccountId; + type Solution = MockNposSolution; + type MaxVotesPerVoter = + <::DataProvider as ElectionDataProvider>::MaxVotesPerVoter; + type MaxLength = MinerMaxLength; + type MaxWeight = MinerMaxWeight; + type MaxWinners = MaxWinners; + + fn solution_weight(_v: u32, _t: u32, _a: u32, _d: u32) -> Weight { + Weight::zero() + } +} + +const THRESHOLDS: [VoteWeight; 9] = [10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; + +parameter_types! { + pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; + pub const SessionsPerEra: sp_staking::SessionIndex = 2; + pub const BondingDuration: sp_staking::EraIndex = 28; + pub const SlashDeferDuration: sp_staking::EraIndex = 7; // 1/4 the bonding duration. + pub const MaxNominatorRewardedPerValidator: u32 = 256; + pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(40); + pub HistoryDepth: u32 = 84; +} + +impl pallet_bags_list::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + type ScoreProvider = Staking; + type BagThresholds = BagThresholds; + type Score = VoteWeight; +} + +impl pallet_staking::Config for Runtime { + type MaxNominations = MaxNominations; + type Currency = Balances; + type CurrencyBalance = Balance; + type UnixTime = Timestamp; + type CurrencyToVote = (); + type RewardRemainder = (); + type RuntimeEvent = RuntimeEvent; + type Slash = (); // burn slashes + type Reward = (); // rewards are minted from the void + type SessionsPerEra = SessionsPerEra; + type BondingDuration = BondingDuration; + type SlashDeferDuration = SlashDeferDuration; + type AdminOrigin = EnsureRoot; // root can cancel slashes + type SessionInterface = Self; + type EraPayout = (); + type NextNewSession = Session; + type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type OffendingValidatorsThreshold = OffendingValidatorsThreshold; + type ElectionProvider = ElectionProviderMultiPhase; + type GenesisElectionProvider = onchain::OnChainExecution; + type VoterList = BagsList; + type TargetList = pallet_staking::UseValidatorsMap; + type MaxUnlockingChunks = ConstU32<32>; + type HistoryDepth = HistoryDepth; + type EventListeners = (); + type WeightInfo = pallet_staking::weights::SubstrateWeight; + type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; +} + +impl frame_system::offchain::SendTransactionTypes for Runtime +where + RuntimeCall: From, +{ + type OverarchingCall = RuntimeCall; + type Extrinsic = Extrinsic; +} + +pub struct OnChainSeqPhragmen; + +parameter_types! { + pub static VotersBound: u32 = 600; + pub static TargetsBound: u32 = 400; +} + +impl onchain::Config for OnChainSeqPhragmen { + type System = Runtime; + type Solver = SequentialPhragmen< + AccountId, + pallet_election_provider_multi_phase::SolutionAccuracyOf, + >; + type DataProvider = Staking; + type WeightInfo = (); + type MaxWinners = MaxWinners; + type VotersBound = VotersBound; + type TargetsBound = TargetsBound; +} + +pub struct NoopElectionProviderBenchmarkConfig; + +impl pallet_election_provider_multi_phase::BenchmarkingConfig + for NoopElectionProviderBenchmarkConfig +{ + const VOTERS: [u32; 2] = [0, 0]; + const TARGETS: [u32; 2] = [0, 0]; + const ACTIVE_VOTERS: [u32; 2] = [0, 0]; + const DESIRED_TARGETS: [u32; 2] = [0, 0]; + const SNAPSHOT_MAXIMUM_VOTERS: u32 = 0; + const MINER_MAXIMUM_VOTERS: u32 = 0; + const MAXIMUM_TARGETS: u32 = 0; +} + +pub struct OtherSessionHandler; +impl traits::OneSessionHandler for OtherSessionHandler { + type Key = testing::UintAuthorityId; + + fn on_genesis_session<'a, I: 'a>(_: I) + where + I: Iterator, + AccountId: 'a, + { + } + + fn on_new_session<'a, I: 'a>(_: bool, _: I, _: I) + where + I: Iterator, + AccountId: 'a, + { + } + + fn on_disabled(_validator_index: u32) {} +} + +impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler { + type Public = testing::UintAuthorityId; +} + +pub struct StakingExtBuilder { + validator_count: u32, + minimum_validator_count: u32, + min_nominator_bond: Balance, + min_validator_bond: Balance, + status: BTreeMap>, + stakes: BTreeMap, + stakers: Vec<(AccountId, AccountId, Balance, StakerStatus)>, +} + +impl Default for StakingExtBuilder { + fn default() -> Self { + let stakers = vec![ + // (stash, ctrl, stake, status) + // these two will be elected in the default test where we elect 2. + (11, 11, 1000, StakerStatus::::Validator), + (21, 21, 1000, StakerStatus::::Validator), + // loser validators if validator_count() is default. + (31, 31, 500, StakerStatus::::Validator), + (41, 41, 1500, StakerStatus::::Validator), + (51, 51, 1500, StakerStatus::::Validator), + (61, 61, 1500, StakerStatus::::Validator), + (71, 71, 1500, StakerStatus::::Validator), + (81, 81, 1500, StakerStatus::::Validator), + (91, 91, 1500, StakerStatus::::Validator), + (101, 101, 500, StakerStatus::::Validator), + // an idle validator + (201, 201, 1000, StakerStatus::::Idle), + ]; + + Self { + validator_count: 2, + minimum_validator_count: 0, + min_nominator_bond: ExistentialDeposit::get(), + min_validator_bond: ExistentialDeposit::get(), + status: Default::default(), + stakes: Default::default(), + stakers, + } + } +} + +impl StakingExtBuilder { + pub fn validator_count(mut self, n: u32) -> Self { + self.validator_count = n; + self + } +} + +pub struct EpmExtBuilder {} + +impl Default for EpmExtBuilder { + fn default() -> Self { + EpmExtBuilder {} + } +} + +impl EpmExtBuilder { + pub fn disable_emergency_throttling(self) -> Self { + ::set(0); + self + } + + pub fn phases(self, signed: BlockNumber, unsigned: BlockNumber) -> Self { + ::set(signed); + ::set(unsigned); + self + } +} + +pub struct BalancesExtBuilder { + balances: Vec<(AccountId, Balance)>, +} + +impl Default for BalancesExtBuilder { + fn default() -> Self { + let balances = vec![ + // (account_id, balance) + (1, 10), + (2, 20), + (3, 300), + (4, 400), + // controllers (still used in some tests. Soon to be deprecated). + (10, 100), + (20, 100), + (30, 100), + (40, 100), + (50, 100), + (60, 100), + (70, 100), + (80, 100), + (90, 100), + (100, 100), + (200, 100), + // stashes + (11, 1000), + (21, 2000), + (31, 3000), + (41, 4000), + (51, 5000), + (61, 6000), + (71, 7000), + (81, 8000), + (91, 9000), + (101, 10000), + (201, 20000), + // This allows us to have a total_payout different from 0. + (999, 1_000_000_000_000), + ]; + Self { balances } + } +} + +pub struct ExtBuilder { + staking_builder: StakingExtBuilder, + epm_builder: EpmExtBuilder, + balances_builder: BalancesExtBuilder, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + staking_builder: StakingExtBuilder::default(), + epm_builder: EpmExtBuilder::default(), + balances_builder: BalancesExtBuilder::default(), + } + } +} + +impl ExtBuilder { + pub fn build(&self) -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + let mut storage = + frame_system::GenesisConfig::::default().build_storage().unwrap(); + + let _ = pallet_balances::GenesisConfig:: { + balances: self.balances_builder.balances.clone(), + } + .assimilate_storage(&mut storage); + + let mut stakers = self.staking_builder.stakers.clone(); + self.staking_builder.status.clone().into_iter().for_each(|(stash, status)| { + let (_, _, _, ref mut prev_status) = stakers + .iter_mut() + .find(|s| s.0 == stash) + .expect("set_status staker should exist; qed"); + *prev_status = status; + }); + // replaced any of the stakes if needed. + self.staking_builder.stakes.clone().into_iter().for_each(|(stash, stake)| { + let (_, _, ref mut prev_stake, _) = stakers + .iter_mut() + .find(|s| s.0 == stash) + .expect("set_stake staker should exits; qed."); + *prev_stake = stake; + }); + + let _ = pallet_staking::GenesisConfig:: { + stakers: stakers.clone(), + validator_count: self.staking_builder.validator_count, + minimum_validator_count: self.staking_builder.minimum_validator_count, + slash_reward_fraction: Perbill::from_percent(10), + min_nominator_bond: self.staking_builder.min_nominator_bond, + min_validator_bond: self.staking_builder.min_validator_bond, + ..Default::default() + } + .assimilate_storage(&mut storage); + + let _ = pallet_session::GenesisConfig:: { + // set the keys for the first session. + keys: stakers + .into_iter() + .map(|(id, ..)| (id, id, SessionKeys { other: (id as u64).into() })) + .collect(), + } + .assimilate_storage(&mut storage); + + let mut ext = sp_io::TestExternalities::from(storage); + + // We consider all test to start after timestamp is initialized This must be ensured by + // having `timestamp::on_initialize` called before `staking::on_initialize`. + ext.execute_with(|| { + System::set_block_number(1); + Session::on_initialize(1); + >::on_initialize(1); + Timestamp::set_timestamp(INIT_TIMESTAMP); + }); + + ext + } + + pub fn staking(mut self, builder: StakingExtBuilder) -> Self { + self.staking_builder = builder; + self + } + + pub fn epm(mut self, builder: EpmExtBuilder) -> Self { + self.epm_builder = builder; + self + } + + pub fn balances(mut self, builder: BalancesExtBuilder) -> Self { + self.balances_builder = builder; + self + } + + pub fn build_offchainify( + self, + ) -> (sp_io::TestExternalities, Arc>, Arc>) { + // add offchain and pool externality extensions. + let mut ext = self.build(); + let (offchain, offchain_state) = TestOffchainExt::new(); + let (pool, pool_state) = TestTransactionPoolExt::new(); + + ext.register_extension(OffchainDbExt::new(offchain.clone())); + ext.register_extension(OffchainWorkerExt::new(offchain)); + ext.register_extension(TransactionPoolExt::new(pool)); + + (ext, pool_state, offchain_state) + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + let mut ext = self.build(); + ext.execute_with(test); + + #[cfg(feature = "try-runtime")] + ext.execute_with(|| { + let bn = System::block_number(); + + assert_ok!(>::try_state(bn)); + assert_ok!(>::try_state(bn)); + assert_ok!(>::try_state(bn)); + }); + } +} + +// Progress to given block, triggering session and era changes as we progress and ensuring that +// there is a solution queued when expected. +pub fn roll_to(n: BlockNumber, delay_solution: bool) { + for b in (System::block_number()) + 1..=n { + System::set_block_number(b); + Session::on_initialize(b); + Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP); + + // TODO(gpestana): implement a realistic OCW worker insted of simulating it + // https://github.com/paritytech/substrate/issues/13589 + // if there's no solution queued and the solution should not be delayed, try mining and + // queue a solution. + if ElectionProviderMultiPhase::current_phase().is_signed() && !delay_solution { + let _ = try_queue_solution(ElectionCompute::Signed).map_err(|e| { + log!(info, "failed to mine/queue solution: {:?}", e); + }); + } + ElectionProviderMultiPhase::on_initialize(b); + + Staking::on_initialize(b); + if b != n { + Staking::on_finalize(System::block_number()); + } + + log_current_time(); + } +} + +// Progress to given block, triggering session and era changes as we progress and ensuring that +// there is a solution queued when expected. +pub fn roll_to_with_ocw(n: BlockNumber, pool: Arc>, delay_solution: bool) { + for b in (System::block_number()) + 1..=n { + System::set_block_number(b); + Session::on_initialize(b); + Timestamp::set_timestamp(System::block_number() * BLOCK_TIME + INIT_TIMESTAMP); + + ElectionProviderMultiPhase::on_initialize(b); + ElectionProviderMultiPhase::offchain_worker(b); + + if !delay_solution && pool.read().transactions.len() > 0 { + // decode submit_unsigned callable that may be queued in the pool by ocw. skip all + // other extrinsics in the pool. + for encoded in &pool.read().transactions { + let extrinsic = Extrinsic::decode(&mut &encoded[..]).unwrap(); + + let _ = match extrinsic.call { + RuntimeCall::ElectionProviderMultiPhase( + call @ Call::submit_unsigned { .. }, + ) => { + // call submit_unsigned callable in OCW pool. + crate::assert_ok!(call.dispatch_bypass_filter(RuntimeOrigin::none())); + }, + _ => (), + }; + } + + pool.try_write().unwrap().transactions.clear(); + } + + Staking::on_initialize(b); + if b != n { + Staking::on_finalize(System::block_number()); + } + + log_current_time(); + } +} +// helper to progress one block ahead. +pub fn roll_one(pool: Arc>, delay_solution: bool) { + let bn = System::block_number().saturating_add(1); + roll_to_with_ocw(bn, pool, delay_solution); +} + +/// Progresses from the current block number (whatever that may be) to the block where the session +/// `session_index` starts. +pub(crate) fn start_session( + session_index: SessionIndex, + pool: Arc>, + delay_solution: bool, +) { + let end = if Offset::get().is_zero() { + Period::get() * session_index + } else { + Offset::get() * session_index + Period::get() * session_index + }; + + assert!(end >= System::block_number()); + + roll_to_with_ocw(end, pool, delay_solution); + + // session must have progressed properly. + assert_eq!( + Session::current_index(), + session_index, + "current session index = {}, expected = {}", + Session::current_index(), + session_index, + ); +} + +/// Go one session forward. +pub(crate) fn advance_session(pool: Arc>) { + let current_index = Session::current_index(); + start_session(current_index + 1, pool, false); +} + +pub(crate) fn advance_session_delayed_solution(pool: Arc>) { + let current_index = Session::current_index(); + start_session(current_index + 1, pool, true); +} + +pub(crate) fn start_next_active_era(pool: Arc>) -> Result<(), ()> { + start_active_era(active_era() + 1, pool, false) +} + +pub(crate) fn start_next_active_era_delayed_solution( + pool: Arc>, +) -> Result<(), ()> { + start_active_era(active_era() + 1, pool, true) +} + +pub(crate) fn advance_eras(n: usize, pool: Arc>) { + for _ in 0..n { + assert_ok!(start_next_active_era(pool.clone())); + } +} + +/// Progress until the given era. +pub(crate) fn start_active_era( + era_index: EraIndex, + pool: Arc>, + delay_solution: bool, +) -> Result<(), ()> { + let era_before = current_era(); + + start_session((era_index * >::get()).into(), pool, delay_solution); + + log!( + info, + "start_active_era - era_before: {}, current era: {} -> progress to: {} -> after era: {}", + era_before, + active_era(), + era_index, + current_era(), + ); + + // if the solution was not delayed, era should have progressed. + if !delay_solution && (active_era() != era_index || current_era() != active_era()) { + Err(()) + } else { + Ok(()) + } +} + +pub(crate) fn active_era() -> EraIndex { + Staking::active_era().unwrap().index +} + +pub(crate) fn current_era() -> EraIndex { + Staking::current_era().unwrap() +} + +// Fast forward until EPM signed phase. +pub fn roll_to_epm_signed() { + while !matches!( + ElectionProviderMultiPhase::current_phase(), + pallet_election_provider_multi_phase::Phase::Signed + ) { + roll_to(System::block_number() + 1, false); + } +} + +// Fast forward until EPM unsigned phase. +pub fn roll_to_epm_unsigned() { + while !matches!( + ElectionProviderMultiPhase::current_phase(), + pallet_election_provider_multi_phase::Phase::Unsigned(_) + ) { + roll_to(System::block_number() + 1, false); + } +} + +// Fast forward until EPM off. +pub fn roll_to_epm_off() { + while !matches!( + ElectionProviderMultiPhase::current_phase(), + pallet_election_provider_multi_phase::Phase::Off + ) { + roll_to(System::block_number() + 1, false); + } +} + +// Queue a solution based on the current snapshot. +pub(crate) fn try_queue_solution(when: ElectionCompute) -> Result<(), String> { + let raw_solution = ElectionProviderMultiPhase::mine_solution() + .map_err(|e| format!("error mining solution: {:?}", e))?; + + ElectionProviderMultiPhase::feasibility_check(raw_solution.0, when) + .map(|ready| { + QueuedSolution::::put(ready); + }) + .map_err(|e| format!("error in solution feasibility: {:?}", e)) +} + +pub(crate) fn on_offence_now( + offenders: &[OffenceDetails< + AccountId, + pallet_session::historical::IdentificationTuple, + >], + slash_fraction: &[Perbill], +) { + let now = Staking::active_era().unwrap().index; + let _ = Staking::on_offence( + offenders, + slash_fraction, + Staking::eras_start_session_index(now).unwrap(), + DisableStrategy::WhenSlashed, + ); +} + +// Add offence to validator, slash it. +pub(crate) fn add_slash(who: &AccountId) { + on_offence_now( + &[OffenceDetails { + offender: (*who, Staking::eras_stakers(active_era(), *who)), + reporters: vec![], + }], + &[Perbill::from_percent(10)], + ); +} + +// Slashes enough validators to cross the `Staking::OffendingValidatorsThreshold`. +pub(crate) fn slash_through_offending_threshold() { + let validators = Session::validators(); + let mut remaining_slashes = + ::OffendingValidatorsThreshold::get() * + validators.len() as u32; + + for v in validators.into_iter() { + if remaining_slashes != 0 { + add_slash(&v); + remaining_slashes -= 1; + } + } +} + +// Slashes a percentage of the active nominators that haven't been slashed yet, with +// a minimum of 1 validator slash. +pub(crate) fn slash_percentage(percentage: Perbill) -> Vec { + let validators = Session::validators(); + let mut remaining_slashes = (percentage * validators.len() as u32).max(1); + let mut slashed = vec![]; + + for v in validators.into_iter() { + if remaining_slashes != 0 { + add_slash(&v); + slashed.push(v); + remaining_slashes -= 1; + } + } + slashed +} + +pub(crate) fn set_minimum_election_score( + minimal_stake: ExtendedBalance, + sum_stake: ExtendedBalance, + sum_stake_squared: ExtendedBalance, +) -> Result<(), ()> { + let election_score = ElectionScore { minimal_stake, sum_stake, sum_stake_squared }; + ElectionProviderMultiPhase::set_minimum_untrusted_score( + RuntimeOrigin::root(), + Some(election_score), + ) + .map(|_| ()) + .map_err(|_| ()) +} + +pub(crate) fn staking_events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None }) + .collect::>() +} + +pub(crate) fn epm_events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let RuntimeEvent::ElectionProviderMultiPhase(inner) = e { + Some(inner) + } else { + None + } + }) + .collect::>() +} diff --git a/frame/election-provider-support/Cargo.toml b/frame/election-provider-support/Cargo.toml index c7f47e721d1a..8786683d0bfb 100644 --- a/frame/election-provider-support/Cargo.toml +++ b/frame/election-provider-support/Cargo.toml @@ -12,20 +12,20 @@ description = "election provider supporting traits" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-election-provider-solution-type = { version = "4.0.0-dev", path = "solution-type" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../primitives/arithmetic" } sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } [dev-dependencies] rand = { version = "0.8.5", features = ["small_rng"] } -sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" } [features] @@ -43,3 +43,4 @@ std = [ "sp-std/std", ] runtime-benchmarks = [] +try-runtime = [] diff --git a/frame/election-provider-support/benchmarking/Cargo.toml b/frame/election-provider-support/benchmarking/Cargo.toml index bef371ec5efb..53c378b001a7 100644 --- a/frame/election-provider-support/benchmarking/Cargo.toml +++ b/frame/election-provider-support/benchmarking/Cargo.toml @@ -12,14 +12,14 @@ description = "Benchmarking for election provider support onchain config trait" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../../benchmarking" } frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = ".." } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/npos-elections" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } [features] default = ["std"] diff --git a/frame/election-provider-support/benchmarking/src/lib.rs b/frame/election-provider-support/benchmarking/src/lib.rs index 547e35bed36e..774b7036fd41 100644 --- a/frame/election-provider-support/benchmarking/src/lib.rs +++ b/frame/election-provider-support/benchmarking/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::Decode; -use frame_benchmarking::{benchmarks, Vec}; +use frame_benchmarking::v1::{benchmarks, Vec}; use frame_election_provider_support::{NposSolver, PhragMMS, SequentialPhragmen}; pub struct Pallet(frame_system::Pallet); diff --git a/frame/election-provider-support/solution-type/Cargo.toml b/frame/election-provider-support/solution-type/Cargo.toml index eb9598dca20b..ef98cb8c1f38 100644 --- a/frame/election-provider-support/solution-type/Cargo.toml +++ b/frame/election-provider-support/solution-type/Cargo.toml @@ -15,15 +15,15 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -syn = { version = "1.0.98", features = ["full", "visit"] } -quote = "1.0" -proc-macro2 = "1.0.37" +syn = { version = "2.0.16", features = ["full", "visit"] } +quote = "1.0.28" +proc-macro2 = "1.0.56" proc-macro-crate = "1.1.3" [dev-dependencies] -parity-scale-codec = "3.2.2" +parity-scale-codec = "3.6.1" scale-info = "2.1.1" -sp-arithmetic = { version = "6.0.0", path = "../../../primitives/arithmetic" } +sp-arithmetic = { version = "16.0.0", path = "../../../primitives/arithmetic" } # used by generate_solution_type: frame-election-provider-support = { version = "4.0.0-dev", path = ".." } frame-support = { version = "4.0.0-dev", path = "../../support" } diff --git a/frame/election-provider-support/solution-type/fuzzer/Cargo.toml b/frame/election-provider-support/solution-type/fuzzer/Cargo.toml index 9275692788d4..91b544d16219 100644 --- a/frame/election-provider-support/solution-type/fuzzer/Cargo.toml +++ b/frame/election-provider-support/solution-type/fuzzer/Cargo.toml @@ -13,16 +13,16 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.0.9", features = ["derive"] } +clap = { version = "4.2.5", features = ["derive"] } honggfuzz = "0.5" rand = { version = "0.8", features = ["std", "small_rng"] } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-election-provider-solution-type = { version = "4.0.0-dev", path = ".." } frame-election-provider-support = { version = "4.0.0-dev", path = "../.." } -sp-arithmetic = { version = "6.0.0", path = "../../../../primitives/arithmetic" } -sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } +sp-arithmetic = { version = "16.0.0", path = "../../../../primitives/arithmetic" } +sp-runtime = { version = "24.0.0", path = "../../../../primitives/runtime" } # used by generate_solution_type: sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/npos-elections" } frame-support = { version = "4.0.0-dev", path = "../../../support" } diff --git a/frame/election-provider-support/solution-type/src/codec.rs b/frame/election-provider-support/solution-type/src/codec.rs index 05d753d3d845..17a256c228e2 100644 --- a/frame/election-provider-support/solution-type/src/codec.rs +++ b/frame/election-provider-support/solution-type/src/codec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,14 +51,14 @@ fn decode_impl( quote! { let #name = < - _feps::sp_std::prelude::Vec<(_feps::codec::Compact<#voter_type>, _feps::codec::Compact<#target_type>)> + _fepsp::sp_std::prelude::Vec<(_fepsp::codec::Compact<#voter_type>, _fepsp::codec::Compact<#target_type>)> as - _feps::codec::Decode + _fepsp::codec::Decode >::decode(value)?; let #name = #name .into_iter() .map(|(v, t)| (v.0, t.0)) - .collect::<_feps::sp_std::prelude::Vec<_>>(); + .collect::<_fepsp::sp_std::prelude::Vec<_>>(); } }; @@ -73,12 +73,12 @@ fn decode_impl( quote! { let #name = < - _feps::sp_std::prelude::Vec<( - _feps::codec::Compact<#voter_type>, - [(_feps::codec::Compact<#target_type>, _feps::codec::Compact<#weight_type>); #c-1], - _feps::codec::Compact<#target_type>, + _fepsp::sp_std::prelude::Vec<( + _fepsp::codec::Compact<#voter_type>, + [(_fepsp::codec::Compact<#target_type>, _fepsp::codec::Compact<#weight_type>); #c-1], + _fepsp::codec::Compact<#target_type>, )> - as _feps::codec::Decode + as _fepsp::codec::Decode >::decode(value)?; let #name = #name .into_iter() @@ -87,7 +87,7 @@ fn decode_impl( [ #inner_impl ], t_last.0, )) - .collect::<_feps::sp_std::prelude::Vec<_>>(); + .collect::<_fepsp::sp_std::prelude::Vec<_>>(); } }) .collect::(); @@ -100,8 +100,8 @@ fn decode_impl( .collect::(); quote!( - impl _feps::codec::Decode for #ident { - fn decode(value: &mut I) -> Result { + impl _fepsp::codec::Decode for #ident { + fn decode(value: &mut I) -> Result { #decode_impl_single #decode_impl_rest @@ -123,10 +123,10 @@ fn encode_impl(ident: &syn::Ident, count: usize) -> TokenStream2 { let #name = self.#name .iter() .map(|(v, t)| ( - _feps::codec::Compact(v.clone()), - _feps::codec::Compact(t.clone()), + _fepsp::codec::Compact(v.clone()), + _fepsp::codec::Compact(t.clone()), )) - .collect::<_feps::sp_std::prelude::Vec<_>>(); + .collect::<_fepsp::sp_std::prelude::Vec<_>>(); #name.encode_to(&mut r); } }; @@ -139,8 +139,8 @@ fn encode_impl(ident: &syn::Ident, count: usize) -> TokenStream2 { let inners_solution_array = (0..c - 1) .map(|i| { quote! {( - _feps::codec::Compact(inner[#i].0.clone()), - _feps::codec::Compact(inner[#i].1.clone()), + _fepsp::codec::Compact(inner[#i].0.clone()), + _fepsp::codec::Compact(inner[#i].1.clone()), ),} }) .collect::(); @@ -149,19 +149,19 @@ fn encode_impl(ident: &syn::Ident, count: usize) -> TokenStream2 { let #name = self.#name .iter() .map(|(v, inner, t_last)| ( - _feps::codec::Compact(v.clone()), + _fepsp::codec::Compact(v.clone()), [ #inners_solution_array ], - _feps::codec::Compact(t_last.clone()), + _fepsp::codec::Compact(t_last.clone()), )) - .collect::<_feps::sp_std::prelude::Vec<_>>(); + .collect::<_fepsp::sp_std::prelude::Vec<_>>(); #name.encode_to(&mut r); } }) .collect::(); quote!( - impl _feps::codec::Encode for #ident { - fn encode(&self) -> _feps::sp_std::prelude::Vec { + impl _fepsp::codec::Encode for #ident { + fn encode(&self) -> _fepsp::sp_std::prelude::Vec { let mut r = vec![]; #encode_impl_single #encode_impl_rest @@ -182,8 +182,8 @@ fn scale_info_impl( let name = format!("{}", vote_field(1)); quote! { .field(|f| - f.ty::<_feps::sp_std::prelude::Vec< - (_feps::codec::Compact<#voter_type>, _feps::codec::Compact<#target_type>) + f.ty::<_fepsp::sp_std::prelude::Vec< + (_fepsp::codec::Compact<#voter_type>, _fepsp::codec::Compact<#target_type>) >>() .name(#name) ) @@ -194,10 +194,10 @@ fn scale_info_impl( let name = format!("{}", vote_field(2)); quote! { .field(|f| - f.ty::<_feps::sp_std::prelude::Vec<( - _feps::codec::Compact<#voter_type>, - (_feps::codec::Compact<#target_type>, _feps::codec::Compact<#weight_type>), - _feps::codec::Compact<#target_type> + f.ty::<_fepsp::sp_std::prelude::Vec<( + _fepsp::codec::Compact<#voter_type>, + (_fepsp::codec::Compact<#target_type>, _fepsp::codec::Compact<#weight_type>), + _fepsp::codec::Compact<#target_type> )>>() .name(#name) ) @@ -209,13 +209,13 @@ fn scale_info_impl( let name = format!("{}", vote_field(c)); quote! { .field(|f| - f.ty::<_feps::sp_std::prelude::Vec<( - _feps::codec::Compact<#voter_type>, + f.ty::<_fepsp::sp_std::prelude::Vec<( + _fepsp::codec::Compact<#voter_type>, [ - (_feps::codec::Compact<#target_type>, _feps::codec::Compact<#weight_type>); + (_fepsp::codec::Compact<#target_type>, _fepsp::codec::Compact<#weight_type>); #c - 1 ], - _feps::codec::Compact<#target_type> + _fepsp::codec::Compact<#target_type> )>>() .name(#name) ) @@ -224,14 +224,14 @@ fn scale_info_impl( .collect::(); quote!( - impl _feps::scale_info::TypeInfo for #ident { + impl _fepsp::scale_info::TypeInfo for #ident { type Identity = Self; - fn type_info() -> _feps::scale_info::Type<_feps::scale_info::form::MetaForm> { - _feps::scale_info::Type::builder() - .path(_feps::scale_info::Path::new(stringify!(#ident), module_path!())) + fn type_info() -> _fepsp::scale_info::Type<_fepsp::scale_info::form::MetaForm> { + _fepsp::scale_info::Type::builder() + .path(_fepsp::scale_info::Path::new(stringify!(#ident), module_path!())) .composite( - _feps::scale_info::build::Fields::named() + _fepsp::scale_info::build::Fields::named() #scale_info_impl_single #scale_info_impl_double #scale_info_impl_rest diff --git a/frame/election-provider-support/solution-type/src/from_assignment_helpers.rs b/frame/election-provider-support/solution-type/src/from_assignment_helpers.rs index e0613afc0d39..e8429b7f91cb 100644 --- a/frame/election-provider-support/solution-type/src/from_assignment_helpers.rs +++ b/frame/election-provider-support/solution-type/src/from_assignment_helpers.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/election-provider-support/solution-type/src/index_assignment.rs b/frame/election-provider-support/solution-type/src/index_assignment.rs index ccdcb4ba25cd..251839303858 100644 --- a/frame/election-provider-support/solution-type/src/index_assignment.rs +++ b/frame/election-provider-support/solution-type/src/index_assignment.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/election-provider-support/solution-type/src/lib.rs b/frame/election-provider-support/solution-type/src/lib.rs index 0a5c11e76ded..80773f6fb476 100644 --- a/frame/election-provider-support/solution-type/src/lib.rs +++ b/frame/election-provider-support/solution-type/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -161,7 +161,7 @@ fn check_attributes(input: ParseStream) -> syn::Result { return Ok(false) } let attr = attrs.pop().expect("attributes vec with len 1 can be popped."); - if attr.path.is_ident("compact") { + if attr.path().is_ident("compact") { Ok(true) } else { Err(syn::Error::new_spanned(attr, "compact solution can accept only #[compact]")) @@ -200,7 +200,7 @@ impl Parse for SolutionDef { format!("Expected binding: `{} = ...`", expected), )) }, - syn::GenericArgument::Binding(syn::Binding { ident, ty, .. }) => { + syn::GenericArgument::AssocType(syn::AssocType { ident, ty, .. }) => { // check that we have the right keyword for this position in the argument list if ident == expected { Ok(ty.clone()) @@ -252,10 +252,16 @@ where fn imports() -> Result { match crate_name("frame-election-provider-support") { - Ok(FoundCrate::Itself) => Ok(quote! { use crate as _feps; }), + Ok(FoundCrate::Itself) => Ok(quote! { + use crate as _feps; + use _feps::private as _fepsp; + }), Ok(FoundCrate::Name(frame_election_provider_support)) => { let ident = syn::Ident::new(&frame_election_provider_support, Span::call_site()); - Ok(quote!( extern crate #ident as _feps; )) + Ok(quote!( + use #ident as _feps; + use _feps::private as _fepsp; + )) }, Err(e) => Err(syn::Error::new(Span::call_site(), e)), } diff --git a/frame/election-provider-support/solution-type/src/single_page.rs b/frame/election-provider-support/solution-type/src/single_page.rs index a7ccf5085d2b..161631ee83fa 100644 --- a/frame/election-provider-support/solution-type/src/single_page.rs +++ b/frame/election-provider-support/solution-type/src/single_page.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,7 +40,7 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { let name = vote_field(1); // NOTE: we use the visibility of the struct for the fields as well.. could be made better. quote!( - #vis #name: _feps::sp_std::prelude::Vec<(#voter_type, #target_type)>, + #vis #name: _fepsp::sp_std::prelude::Vec<(#voter_type, #target_type)>, ) }; @@ -49,7 +49,7 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { let field_name = vote_field(c); let array_len = c - 1; quote!( - #vis #field_name: _feps::sp_std::prelude::Vec<( + #vis #field_name: _fepsp::sp_std::prelude::Vec<( #voter_type, [(#target_type, #weight_type); #array_len], #target_type @@ -84,9 +84,9 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { Eq, Clone, Debug, - _feps::codec::Encode, - _feps::codec::Decode, - _feps::scale_info::TypeInfo, + _fepsp::codec::Encode, + _fepsp::codec::Decode, + _fepsp::scale_info::TypeInfo, )]) }; @@ -102,7 +102,7 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { #derives_and_maybe_compact_encoding #vis struct #ident { #single #rest } - use _feps::__OrInvalidIndex; + use _fepsp::__OrInvalidIndex; impl _feps::NposSolution for #ident { const LIMIT: usize = #count; type VoterIndex = #voter_type; @@ -147,8 +147,8 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { self, voter_at: impl Fn(Self::VoterIndex) -> Option, target_at: impl Fn(Self::TargetIndex) -> Option, - ) -> Result<_feps::sp_std::prelude::Vec<_feps::Assignment>, _feps::Error> { - let mut #assignment_name: _feps::sp_std::prelude::Vec<_feps::Assignment> = Default::default(); + ) -> Result<_fepsp::sp_std::prelude::Vec<_feps::Assignment>, _feps::Error> { + let mut #assignment_name: _fepsp::sp_std::prelude::Vec<_feps::Assignment> = Default::default(); #into_impl Ok(#assignment_name) } @@ -165,10 +165,10 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { all_edges } - fn unique_targets(&self) -> _feps::sp_std::prelude::Vec { + fn unique_targets(&self) -> _fepsp::sp_std::prelude::Vec { // NOTE: this implementation returns the targets sorted, but we don't use it yet per // se, nor is the API enforcing it. - use _feps::sp_std::collections::btree_set::BTreeSet; + use _fepsp::sp_std::collections::btree_set::BTreeSet; let mut all_targets: BTreeSet = BTreeSet::new(); let mut maybe_insert_target = |t: Self::TargetIndex| { all_targets.insert(t); @@ -185,10 +185,10 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { <#ident as _feps::NposSolution>::TargetIndex, <#ident as _feps::NposSolution>::Accuracy, >; - impl _feps::codec::MaxEncodedLen for #ident { + impl _fepsp::codec::MaxEncodedLen for #ident { fn max_encoded_len() -> usize { use frame_support::traits::Get; - use _feps::codec::Encode; + use _fepsp::codec::Encode; let s: u32 = #max_voters::get(); let max_element_size = // the first voter.. @@ -202,11 +202,11 @@ pub(crate) fn generate(def: crate::SolutionDef) -> Result { // The assumption is that it contains #count-1 empty elements // and then last element with full size #count - .saturating_mul(_feps::codec::Compact(0u32).encoded_size()) + .saturating_mul(_fepsp::codec::Compact(0u32).encoded_size()) .saturating_add((s as usize).saturating_mul(max_element_size)) } } - impl<'a> _feps::sp_std::convert::TryFrom<&'a [__IndexAssignment]> for #ident { + impl<'a> _fepsp::sp_std::convert::TryFrom<&'a [__IndexAssignment]> for #ident { type Error = _feps::Error; fn try_from(index_assignments: &'a [__IndexAssignment]) -> Result { let mut #struct_name = #ident::default(); @@ -357,18 +357,18 @@ pub(crate) fn into_impl( let mut inners_parsed = inners .iter() .map(|(ref t_idx, p)| { - sum = _feps::sp_arithmetic::traits::Saturating::saturating_add(sum, *p); + sum = _fepsp::sp_arithmetic::traits::Saturating::saturating_add(sum, *p); let target = target_at(*t_idx).or_invalid_index()?; Ok((target, *p)) }) - .collect::, _feps::Error>>()?; + .collect::, _feps::Error>>()?; if sum >= #per_thing::one() { return Err(_feps::Error::SolutionWeightOverflow); } // defensive only. Since Percent doesn't have `Sub`. - let p_last = _feps::sp_arithmetic::traits::Saturating::saturating_sub( + let p_last = _fepsp::sp_arithmetic::traits::Saturating::saturating_sub( #per_thing::one(), sum, ); diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 9d5d6c018e5e..978a5df42724 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -189,15 +189,32 @@ pub use sp_npos_elections::{ }; pub use traits::NposSolution; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + // re-export for the solution macro, with the dependencies of the macro. #[doc(hidden)] -pub use codec; -#[doc(hidden)] -pub use scale_info; -#[doc(hidden)] -pub use sp_arithmetic; -#[doc(hidden)] -pub use sp_std; +pub mod private { + pub use codec; + pub use scale_info; + pub use sp_arithmetic; + pub use sp_std; + + // Simple Extension trait to easily convert `None` from index closures to `Err`. + // + // This is only generated and re-exported for the solution code to use. + pub trait __OrInvalidIndex { + fn or_invalid_index(self) -> Result; + } + + impl __OrInvalidIndex for Option { + fn or_invalid_index(self) -> Result { + self.ok_or(crate::Error::SolutionInvalidIndex) + } + } +} + +use private::__OrInvalidIndex; pub mod weights; pub use weights::WeightInfo; @@ -206,19 +223,6 @@ pub use weights::WeightInfo; mod mock; #[cfg(test)] mod tests; -// Simple Extension trait to easily convert `None` from index closures to `Err`. -// -// This is only generated and re-exported for the solution code to use. -#[doc(hidden)] -pub trait __OrInvalidIndex { - fn or_invalid_index(self) -> Result; -} - -impl __OrInvalidIndex for Option { - fn or_invalid_index(self) -> Result { - self.ok_or(Error::SolutionInvalidIndex) - } -} /// The [`IndexAssignment`] type is an intermediate between the assignments list /// ([`&[Assignment]`][Assignment]) and `SolutionOf`. @@ -562,8 +566,9 @@ pub trait SortedListProvider { /// unbounded amount of storage accesses. fn unsafe_clear(); - /// Check internal state of list. Only meant for debugging. - fn try_state() -> Result<(), &'static str>; + /// Check internal state of the list. Only meant for debugging. + #[cfg(feature = "try-runtime")] + fn try_state() -> Result<(), TryRuntimeError>; /// If `who` changes by the returned amount they are guaranteed to have a worst case change /// in their list position. @@ -581,7 +586,7 @@ pub trait ScoreProvider { fn score(who: &AccountId) -> Self::Score; /// For tests, benchmarks and fuzzing, set the `score`. - #[cfg(any(feature = "runtime-benchmarks", feature = "fuzz", test))] + #[cfg(any(feature = "runtime-benchmarks", feature = "fuzz", feature = "std"))] fn set_score_of(_: &AccountId, _: Self::Score) {} } @@ -672,5 +677,14 @@ pub type BoundedSupportsOf = BoundedSupports< ::MaxWinners, >; -sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); -sp_core::generate_feature_enabled_macro!(runtime_benchmarks_or_fuzz_enabled, any(feature = "runtime-benchmarks", feature = "fuzzing"), $); +sp_core::generate_feature_enabled_macro!( + runtime_benchmarks_enabled, + feature = "runtime-benchmarks", + $ +); + +sp_core::generate_feature_enabled_macro!( + runtime_benchmarks_fuzz_or_std_enabled, + any(feature = "runtime-benchmarks", feature = "fuzzing", feature = "std"), + $ +); diff --git a/frame/election-provider-support/src/mock.rs b/frame/election-provider-support/src/mock.rs index 0789ee90e666..5cbe305eb522 100644 --- a/frame/election-provider-support/src/mock.rs +++ b/frame/election-provider-support/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/election-provider-support/src/onchain.rs b/frame/election-provider-support/src/onchain.rs index 483c402fe249..bfdc21a8fe2d 100644 --- a/frame/election-provider-support/src/onchain.rs +++ b/frame/election-provider-support/src/onchain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -73,7 +73,7 @@ pub trait Config { /// Something that provides the data for election. type DataProvider: ElectionDataProvider< AccountId = ::AccountId, - BlockNumber = ::BlockNumber, + BlockNumber = frame_system::pallet_prelude::BlockNumberFor, >; /// Weight information for extrinsics in this pallet. @@ -151,7 +151,7 @@ fn elect_with_input_bounds( impl ElectionProviderBase for OnChainExecution { type AccountId = ::AccountId; - type BlockNumber = ::BlockNumber; + type BlockNumber = frame_system::pallet_prelude::BlockNumberFor; type Error = Error; type MaxWinners = T::MaxWinners; type DataProvider = T::DataProvider; @@ -190,6 +190,7 @@ mod tests { use sp_npos_elections::Support; use sp_runtime::Perbill; type AccountId = u64; + type Nonce = u64; type BlockNumber = u64; pub type Header = sp_runtime::generic::Header; @@ -197,10 +198,7 @@ mod tests { pub type Block = sp_runtime::generic::Block; frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { System: frame_system::{Pallet, Call, Event}, } @@ -210,14 +208,13 @@ mod tests { type SS58Prefix = (); type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; - type Index = AccountId; - type BlockNumber = BlockNumber; + type Nonce = Nonce; type RuntimeCall = RuntimeCall; type Hash = sp_core::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = sp_runtime::traits::IdentityLookup; - type Header = sp_runtime::testing::Header; + type Block = Block; type RuntimeEvent = (); type BlockHashCount = (); type DbWeight = (); diff --git a/frame/election-provider-support/src/tests.rs b/frame/election-provider-support/src/tests.rs index c38444f011df..73ce1427cf2f 100644 --- a/frame/election-provider-support/src/tests.rs +++ b/frame/election-provider-support/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/election-provider-support/src/traits.rs b/frame/election-provider-support/src/traits.rs index ed812e2e0f2c..43d183b338e2 100644 --- a/frame/election-provider-support/src/traits.rs +++ b/frame/election-provider-support/src/traits.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/election-provider-support/src/weights.rs b/frame/election-provider-support/src/weights.rs index 44075ba87122..addb6ad8d030 100644 --- a/frame/election-provider-support/src/weights.rs +++ b/frame/election-provider-support/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -53,43 +53,43 @@ pub trait WeightInfo { pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { fn phragmen(v: u32, t: u32, d: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) + Weight::from_parts(0 as u64, 0) // Standard Error: 667_000 - .saturating_add(Weight::from_ref_time(32_973_000 as u64).saturating_mul(v as u64)) + .saturating_add(Weight::from_parts(32_973_000 as u64, 0).saturating_mul(v as u64)) // Standard Error: 1_334_000 - .saturating_add(Weight::from_ref_time(1_334_000 as u64).saturating_mul(t as u64)) + .saturating_add(Weight::from_parts(1_334_000 as u64, 0).saturating_mul(t as u64)) // Standard Error: 60_644_000 - .saturating_add(Weight::from_ref_time(2_636_364_000 as u64).saturating_mul(d as u64)) + .saturating_add(Weight::from_parts(2_636_364_000 as u64, 0).saturating_mul(d as u64)) } fn phragmms(v: u32, t: u32, d: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) + Weight::from_parts(0 as u64, 0) // Standard Error: 73_000 - .saturating_add(Weight::from_ref_time(21_073_000 as u64).saturating_mul(v as u64)) + .saturating_add(Weight::from_parts(21_073_000 as u64, 0).saturating_mul(v as u64)) // Standard Error: 146_000 - .saturating_add(Weight::from_ref_time(65_000 as u64).saturating_mul(t as u64)) + .saturating_add(Weight::from_parts(65_000 as u64, 0).saturating_mul(t as u64)) // Standard Error: 6_649_000 - .saturating_add(Weight::from_ref_time(1_711_424_000 as u64).saturating_mul(d as u64)) + .saturating_add(Weight::from_parts(1_711_424_000 as u64, 0).saturating_mul(d as u64)) } } // For backwards compatibility and tests impl WeightInfo for () { fn phragmen(v: u32, t: u32, d: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) + Weight::from_parts(0 as u64, 0) // Standard Error: 667_000 - .saturating_add(Weight::from_ref_time(32_973_000 as u64).saturating_mul(v as u64)) + .saturating_add(Weight::from_parts(32_973_000 as u64, 0).saturating_mul(v as u64)) // Standard Error: 1_334_000 - .saturating_add(Weight::from_ref_time(1_334_000 as u64).saturating_mul(t as u64)) + .saturating_add(Weight::from_parts(1_334_000 as u64, 0).saturating_mul(t as u64)) // Standard Error: 60_644_000 - .saturating_add(Weight::from_ref_time(2_636_364_000 as u64).saturating_mul(d as u64)) + .saturating_add(Weight::from_parts(2_636_364_000 as u64, 0).saturating_mul(d as u64)) } fn phragmms(v: u32, t: u32, d: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) + Weight::from_parts(0 as u64, 0) // Standard Error: 73_000 - .saturating_add(Weight::from_ref_time(21_073_000 as u64).saturating_mul(v as u64)) + .saturating_add(Weight::from_parts(21_073_000 as u64, 0).saturating_mul(v as u64)) // Standard Error: 146_000 - .saturating_add(Weight::from_ref_time(65_000 as u64).saturating_mul(t as u64)) + .saturating_add(Weight::from_parts(65_000 as u64, 0).saturating_mul(t as u64)) // Standard Error: 6_649_000 - .saturating_add(Weight::from_ref_time(1_711_424_000 as u64).saturating_mul(d as u64)) + .saturating_add(Weight::from_parts(1_711_424_000 as u64, 0).saturating_mul(d as u64)) } } diff --git a/frame/elections-phragmen/Cargo.toml b/frame/elections-phragmen/Cargo.toml index ce39e42b8eeb..9b26e1e367a9 100644 --- a/frame/elections-phragmen/Cargo.toml +++ b/frame/elections-phragmen/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } log = { version = "0.4.14", default-features = false } @@ -21,15 +21,16 @@ scale-info = { version = "2.0.0", default-features = false, features = ["derive" frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } sp-npos-elections = { version = "4.0.0-dev", default-features = false, path = "../../primitives/npos-elections" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-staking = { default-features = false, path = "../../primitives/staking" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } sp-tracing = { path = "../../primitives/tracing" } substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } diff --git a/frame/elections-phragmen/src/benchmarking.rs b/frame/elections-phragmen/src/benchmarking.rs index 06ac8d7c6016..56ea19578c8f 100644 --- a/frame/elections-phragmen/src/benchmarking.rs +++ b/frame/elections-phragmen/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,7 @@ use super::*; -use frame_benchmarking::{account, benchmarks, whitelist, BenchmarkError, BenchmarkResult}; +use frame_benchmarking::v1::{account, benchmarks, whitelist, BenchmarkError, BenchmarkResult}; use frame_support::{dispatch::DispatchResultWithPostInfo, traits::OnInitialize}; use frame_system::RawOrigin; @@ -32,7 +32,7 @@ const BALANCE_FACTOR: u32 = 250; /// grab new account with infinite balance. fn endowed_account(name: &'static str, index: u32) -> T::AccountId { let account: T::AccountId = account(name, index, 0); - // Fund each account with at-least his stake but still a sane amount as to not mess up + // Fund each account with at-least their stake but still a sane amount as to not mess up // the vote calculation. let amount = default_stake::(T::MaxVoters::get()) * BalanceOf::::from(BALANCE_FACTOR); let _ = T::Currency::make_free_balance_be(&account, amount); @@ -148,7 +148,7 @@ fn clean() { benchmarks! { // -- Signed ones vote_equal { - let v in 1 .. (MAXIMUM_VOTE as u32); + let v in 1 .. T::MaxVotesPerVoter::get(); clean::(); // create a bunch of candidates. @@ -168,7 +168,7 @@ benchmarks! { }: vote(RawOrigin::Signed(caller), votes, stake) vote_more { - let v in 2 .. (MAXIMUM_VOTE as u32); + let v in 2 .. T::MaxVotesPerVoter::get(); clean::(); // create a bunch of candidates. @@ -190,7 +190,7 @@ benchmarks! { }: vote(RawOrigin::Signed(caller), votes, stake / >::from(10u32)) vote_less { - let v in 2 .. (MAXIMUM_VOTE as u32); + let v in 2 .. T::MaxVotesPerVoter::get(); clean::(); // create a bunch of candidates. @@ -212,7 +212,7 @@ benchmarks! { remove_voter { // we fix the number of voted candidates to max - let v = MAXIMUM_VOTE as u32; + let v = T::MaxVotesPerVoter::get(); clean::(); // create a bunch of candidates. @@ -368,7 +368,7 @@ benchmarks! { clean::(); let all_candidates = submit_candidates::(T::MaxCandidates::get(), "candidates")?; - distribute_voters::(all_candidates, v, MAXIMUM_VOTE)?; + distribute_voters::(all_candidates, v, T::MaxVotesPerVoter::get() as usize)?; // all candidates leave. >::kill(); @@ -389,17 +389,17 @@ benchmarks! { // that we give all candidates a self vote to make sure they are all considered. let c in 1 .. T::MaxCandidates::get(); let v in 1 .. T::MaxVoters::get(); - let e in (T::MaxVoters::get()) .. T::MaxVoters::get() as u32 * MAXIMUM_VOTE as u32; + let e in (T::MaxVoters::get()) .. T::MaxVoters::get() * T::MaxVotesPerVoter::get(); clean::(); // so we have a situation with v and e. we want e to basically always be in the range of `e - // -> e * MAXIMUM_VOTE`, but we cannot express that now with the benchmarks. So what we do - // is: when c is being iterated, v, and e are max and fine. when v is being iterated, e is - // being set to max and this is a problem. In these cases, we cap e to a lower value, namely - // v * MAXIMUM_VOTE. when e is being iterated, v is at max, and again fine. all in all, - // votes_per_voter can never be more than MAXIMUM_VOTE. Note that this might cause `v` to be - // an overestimate. - let votes_per_voter = (e / v).min(MAXIMUM_VOTE as u32); + // -> e * T::MaxVotesPerVoter::get()`, but we cannot express that now with the benchmarks. + // So what we do is: when c is being iterated, v, and e are max and fine. when v is being + // iterated, e is being set to max and this is a problem. In these cases, we cap e to a + // lower value, namely v * T::MaxVotesPerVoter::get(). when e is being iterated, v is at + // max, and again fine. all in all, votes_per_voter can never be more than + // T::MaxVotesPerVoter::get(). Note that this might cause `v` to be an overestimate. + let votes_per_voter = (e / v).min(T::MaxVotesPerVoter::get()); let all_candidates = submit_candidates_with_self_vote::(c, "candidates")?; let _ = distribute_voters::(all_candidates, v.saturating_sub(c), votes_per_voter as usize)?; @@ -421,68 +421,6 @@ benchmarks! { } } - #[extra] - election_phragmen_c_e { - let c in 1 .. T::MaxCandidates::get(); - let e in (T::MaxVoters::get()) .. T::MaxVoters::get() * MAXIMUM_VOTE as u32; - let fixed_v = T::MaxVoters::get(); - clean::(); - - let votes_per_voter = e / fixed_v; - - let all_candidates = submit_candidates_with_self_vote::(c, "candidates")?; - let _ = distribute_voters::( - all_candidates, - fixed_v - c, - votes_per_voter as usize, - )?; - }: { - >::on_initialize(T::TermDuration::get()); - } - verify { - assert_eq!(>::members().len() as u32, T::DesiredMembers::get().min(c)); - assert_eq!( - >::runners_up().len() as u32, - T::DesiredRunnersUp::get().min(c.saturating_sub(T::DesiredMembers::get())), - ); - - #[cfg(test)] - { - // reset members in between benchmark tests. - use crate::tests::MEMBERS; - MEMBERS.with(|m| *m.borrow_mut() = vec![]); - } - } - - #[extra] - election_phragmen_v { - let v in 4 .. 16; - let fixed_c = T::MaxCandidates::get() / 10; - let fixed_e = 64; - clean::(); - - let votes_per_voter = fixed_e / v; - - let all_candidates = submit_candidates_with_self_vote::(fixed_c, "candidates")?; - let _ = distribute_voters::(all_candidates, v, votes_per_voter as usize)?; - }: { - >::on_initialize(T::TermDuration::get()); - } - verify { - assert_eq!(>::members().len() as u32, T::DesiredMembers::get().min(fixed_c)); - assert_eq!( - >::runners_up().len() as u32, - T::DesiredRunnersUp::get().min(fixed_c.saturating_sub(T::DesiredMembers::get())), - ); - - #[cfg(test)] - { - // reset members in between benchmark tests. - use crate::tests::MEMBERS; - MEMBERS.with(|m| *m.borrow_mut() = vec![]); - } - } - impl_benchmark_test_suite!( Elections, crate::tests::ExtBuilder::default().desired_members(13).desired_runners_up(7), diff --git a/frame/elections-phragmen/src/lib.rs b/frame/elections-phragmen/src/lib.rs index 1a020adb2863..b23ddda4e8d1 100644 --- a/frame/elections-phragmen/src/lib.rs +++ b/frame/elections-phragmen/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,14 +43,14 @@ //! ### Voting //! //! Voters can vote for a limited number of the candidates by providing a list of account ids, -//! bounded by [`MAXIMUM_VOTE`]. Invalid votes (voting for non-candidates) and duplicate votes are -//! ignored during election. Yet, a voter _might_ vote for a future candidate. Voters reserve a bond -//! as they vote. Each vote defines a `value`. This amount is locked from the account of the voter -//! and indicates the weight of the vote. Voters can update their votes at any time by calling -//! `vote()` again. This can update the vote targets (which might update the deposit) or update the -//! vote's stake ([`Voter::stake`]). After a round, votes are kept and might still be valid for -//! further rounds. A voter is responsible for calling `remove_voter` once they are done to have -//! their bond back and remove the lock. +//! bounded by [`Config::MaxVotesPerVoter`]. Invalid votes (voting for non-candidates) and duplicate +//! votes are ignored during election. Yet, a voter _might_ vote for a future candidate. Voters +//! reserve a bond as they vote. Each vote defines a `value`. This amount is locked from the account +//! of the voter and indicates the weight of the vote. Voters can update their votes at any time by +//! calling `vote()` again. This can update the vote targets (which might update the deposit) or +//! update the vote's stake ([`Voter::stake`]). After a round, votes are kept and might still be +//! valid for further rounds. A voter is responsible for calling `remove_voter` once they are done +//! to have their bond back and remove the lock. //! //! See [`Call::vote`], [`Call::remove_voter`]. //! @@ -101,9 +101,9 @@ use codec::{Decode, Encode}; use frame_support::{ traits::{ - defensive_prelude::*, ChangeMembers, Contains, ContainsLengthBound, Currency, - CurrencyToVote, Get, InitializeMembers, LockIdentifier, LockableCurrency, OnUnbalanced, - ReservableCurrency, SortedMembers, WithdrawReasons, + defensive_prelude::*, ChangeMembers, Contains, ContainsLengthBound, Currency, Get, + InitializeMembers, LockIdentifier, LockableCurrency, OnUnbalanced, ReservableCurrency, + SortedMembers, WithdrawReasons, }, weights::Weight, }; @@ -113,8 +113,12 @@ use sp_runtime::{ traits::{Saturating, StaticLookup, Zero}, DispatchError, Perbill, RuntimeDebug, }; +use sp_staking::currency_to_vote::CurrencyToVote; use sp_std::{cmp::Ordering, prelude::*}; +#[cfg(any(feature = "try-runtime", test))] +use sp_runtime::TryRuntimeError; + mod benchmarking; pub mod weights; pub use weights::WeightInfo; @@ -124,9 +128,6 @@ pub mod migrations; const LOG_TARGET: &str = "runtime::elections-phragmen"; -/// The maximum votes allowed per voter. -pub const MAXIMUM_VOTE: usize = 16; - type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type NegativeImbalanceOf = <::Currency as Currency< @@ -190,10 +191,9 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] #[pallet::without_storage_info] - pub struct Pallet(PhantomData); + pub struct Pallet(_); #[pallet::config] pub trait Config: frame_system::Config { @@ -204,7 +204,7 @@ pub mod pallet { type PalletId: Get; /// The currency that people are electing with. - type Currency: LockableCurrency + type Currency: LockableCurrency> + ReservableCurrency; /// What to do when the members change. @@ -250,23 +250,33 @@ pub mod pallet { /// round will happen. If set to zero, no elections are ever triggered and the module will /// be in passive mode. #[pallet::constant] - type TermDuration: Get; + type TermDuration: Get>; /// The maximum number of candidates in a phragmen election. /// - /// Warning: The election happens onchain, and this value will determine - /// the size of the election. When this limit is reached no more - /// candidates are accepted in the election. + /// Warning: This impacts the size of the election which is run onchain. Chose wisely, and + /// consider how it will impact `T::WeightInfo::election_phragmen`. + /// + /// When this limit is reached no more candidates are accepted in the election. #[pallet::constant] type MaxCandidates: Get; /// The maximum number of voters to allow in a phragmen election. /// - /// Warning: This impacts the size of the election which is run onchain. + /// Warning: This impacts the size of the election which is run onchain. Chose wisely, and + /// consider how it will impact `T::WeightInfo::election_phragmen`. + /// /// When the limit is reached the new voters are ignored. #[pallet::constant] type MaxVoters: Get; + /// Maximum numbers of votes per voter. + /// + /// Warning: This impacts the size of the election which is run onchain. Chose wisely, and + /// consider how it will impact `T::WeightInfo::election_phragmen`. + #[pallet::constant] + type MaxVotesPerVoter: Get; + /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; } @@ -276,7 +286,7 @@ pub mod pallet { /// What to do at the end of each block. /// /// Checks if an election needs to happen or not. - fn on_initialize(n: T::BlockNumber) -> Weight { + fn on_initialize(n: BlockNumberFor) -> Weight { let term_duration = T::TermDuration::get(); if !term_duration.is_zero() && (n % term_duration).is_zero() { Self::do_phragmen() @@ -284,6 +294,46 @@ pub mod pallet { Weight::zero() } } + + fn integrity_test() { + let block_weight = T::BlockWeights::get().max_block; + // mind the order. + let election_weight = T::WeightInfo::election_phragmen( + T::MaxCandidates::get(), + T::MaxVoters::get(), + T::MaxVotesPerVoter::get() * T::MaxVoters::get(), + ); + + let to_seconds = |w: &Weight| { + w.ref_time() as f32 / + frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND as f32 + }; + + frame_support::log::debug!( + target: LOG_TARGET, + "election weight {}s ({:?}) // chain's block weight {}s ({:?})", + to_seconds(&election_weight), + election_weight, + to_seconds(&block_weight), + block_weight, + ); + assert!( + election_weight.all_lt(block_weight), + "election weight {}s ({:?}) will exceed a {}s chain's block weight ({:?}) (MaxCandidates {}, MaxVoters {}, MaxVotesPerVoter {} -- tweak these parameters)", + election_weight, + to_seconds(&election_weight), + to_seconds(&block_weight), + block_weight, + T::MaxCandidates::get(), + T::MaxVoters::get(), + T::MaxVotesPerVoter::get(), + ); + } + + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumberFor) -> Result<(), TryRuntimeError> { + Self::do_try_state() + } } #[pallet::call] @@ -307,10 +357,6 @@ pub mod pallet { /// /// It is the responsibility of the caller to **NOT** place all of their balance into the /// lock and keep some for further operations. - /// - /// # - /// We assume the maximum weight among all 3 cases: vote_equal, vote_more and vote_less. - /// # #[pallet::call_index(0)] #[pallet::weight( T::WeightInfo::vote_more(votes.len() as u32) @@ -324,8 +370,10 @@ pub mod pallet { ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; - // votes should not be empty and more than `MAXIMUM_VOTE` in any case. - ensure!(votes.len() <= MAXIMUM_VOTE, Error::::MaximumVotesExceeded); + ensure!( + votes.len() <= T::MaxVotesPerVoter::get() as usize, + Error::::MaximumVotesExceeded + ); ensure!(!votes.is_empty(), Error::::NoVotes); let candidates_count = >::decode_len().unwrap_or(0); @@ -395,9 +443,9 @@ pub mod pallet { /// Even if a candidate ends up being a member, they must call [`Call::renounce_candidacy`] /// to get their deposit back. Losing the spot in an election will always lead to a slash. /// - /// # /// The number of current candidates must be provided as witness data. - /// # + /// ## Complexity + /// O(C + log(C)) where C is candidate_count. #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::submit_candidacy(*candidate_count))] pub fn submit_candidacy( @@ -439,10 +487,12 @@ pub mod pallet { /// next round. /// /// The dispatch origin of this call must be signed, and have one of the above roles. - /// - /// # /// The type of renouncing must be provided as witness data. - /// # + /// + /// ## Complexity + /// - Renouncing::Candidate(count): O(count + log(count)) + /// - Renouncing::Member: O(1) + /// - Renouncing::RunnerUp: O(1) #[pallet::call_index(3)] #[pallet::weight(match *renouncing { Renouncing::Candidate(count) => T::WeightInfo::renounce_candidacy_candidate(count), @@ -502,10 +552,8 @@ pub mod pallet { /// /// Note that this does not affect the designated block number of the next election. /// - /// # - /// If we have a replacement, we use a small weight. Else, since this is a root call and - /// will go into phragmen, we assume full block for now. - /// # + /// ## Complexity + /// - Check details of remove_and_replace_member() and do_phragmen(). #[pallet::call_index(4)] #[pallet::weight(if *rerun_election { T::WeightInfo::remove_member_without_replacement() @@ -539,9 +587,8 @@ pub mod pallet { /// /// The dispatch origin of this call must be root. /// - /// # - /// The total number of voters and those that are defunct must be provided as witness data. - /// # + /// ## Complexity + /// - Check is_defunct_voter() details. #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::clean_defunct_voters(*_num_voters, *_num_defunct))] pub fn clean_defunct_voters( @@ -668,19 +715,13 @@ pub mod pallet { StorageMap<_, Twox64Concat, T::AccountId, Voter>, ValueQuery>; #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { pub members: Vec<(T::AccountId, BalanceOf)>, } - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - Self { members: Default::default() } - } - } - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { assert!( self.members.len() as u32 <= T::DesiredMembers::get(), @@ -703,7 +744,10 @@ pub mod pallet { Members::::mutate(|members| { match members.binary_search_by(|m| m.who.cmp(member)) { Ok(_) => { - panic!("Duplicate member in elections-phragmen genesis: {}", member) + panic!( + "Duplicate member in elections-phragmen genesis: {:?}", + member + ) }, Err(pos) => members.insert( pos, @@ -855,7 +899,7 @@ impl Pallet { } /// Get the members' account ids. - fn members_ids() -> Vec { + pub(crate) fn members_ids() -> Vec { Self::members().into_iter().map(|m| m.who).collect::>() } @@ -1006,7 +1050,7 @@ impl Pallet { // count](https://en.wikipedia.org/wiki/Borda_count). We weigh everyone's vote for // that new member by a multiplier based on the order of the votes. i.e. the // first person a voter votes for gets a 16x multiplier, the next person gets a - // 15x multiplier, an so on... (assuming `MAXIMUM_VOTE` = 16) + // 15x multiplier, an so on... (assuming `T::MaxVotesPerVoter` = 16) let mut prime_votes = new_members_sorted_by_id .iter() .map(|c| (&c.0, BalanceOf::::zero())) @@ -1014,7 +1058,7 @@ impl Pallet { for (_, stake, votes) in voters_and_stakes.into_iter() { for (vote_multiplier, who) in votes.iter().enumerate().map(|(vote_position, who)| { - ((MAXIMUM_VOTE - vote_position) as u32, who) + ((T::MaxVotesPerVoter::get() as usize - vote_position) as u32, who) }) { if let Ok(i) = prime_votes.binary_search_by_key(&who, |k| k.0) { prime_votes[i].1 = prime_votes[i] @@ -1154,6 +1198,106 @@ impl ContainsLengthBound for Pallet { } } +#[cfg(any(feature = "try-runtime", test))] +impl Pallet { + fn do_try_state() -> Result<(), TryRuntimeError> { + Self::try_state_members()?; + Self::try_state_runners_up()?; + Self::try_state_candidates()?; + Self::try_state_candidates_runners_up_disjoint()?; + Self::try_state_members_disjoint()?; + Self::try_state_members_approval_stake() + } + + /// [`Members`] state checks. Invariants: + /// - Members are always sorted based on account ID. + fn try_state_members() -> Result<(), TryRuntimeError> { + let mut members = Members::::get().clone(); + members.sort_by_key(|m| m.who.clone()); + + if Members::::get() == members { + Ok(()) + } else { + Err("try_state checks: Members must be always sorted by account ID".into()) + } + } + + // [`RunnersUp`] state checks. Invariants: + // - Elements are sorted based on weight (worst to best). + fn try_state_runners_up() -> Result<(), TryRuntimeError> { + let mut sorted = RunnersUp::::get(); + // worst stake first + sorted.sort_by(|a, b| a.stake.cmp(&b.stake)); + + if RunnersUp::::get() == sorted { + Ok(()) + } else { + Err("try_state checks: Runners Up must always be sorted by stake (worst to best)" + .into()) + } + } + + // [`Candidates`] state checks. Invariants: + // - Always sorted based on account ID. + fn try_state_candidates() -> Result<(), TryRuntimeError> { + let mut candidates = Candidates::::get().clone(); + candidates.sort_by_key(|(c, _)| c.clone()); + + if Candidates::::get() == candidates { + Ok(()) + } else { + Err("try_state checks: Candidates must be always sorted by account ID".into()) + } + } + // [`Candidates`] and [`RunnersUp`] state checks. Invariants: + // - Candidates and runners-ups sets are disjoint. + fn try_state_candidates_runners_up_disjoint() -> Result<(), TryRuntimeError> { + match Self::intersects(&Self::candidates_ids(), &Self::runners_up_ids()) { + true => Err("Candidates and runners up sets should always be disjoint".into()), + false => Ok(()), + } + } + + // [`Members`], [`Candidates`] and [`RunnersUp`] state checks. Invariants: + // - Members and candidates sets are disjoint; + // - Members and runners-ups sets are disjoint. + fn try_state_members_disjoint() -> Result<(), TryRuntimeError> { + match Self::intersects(&Pallet::::members_ids(), &Self::candidates_ids()) && + Self::intersects(&Pallet::::members_ids(), &Self::runners_up_ids()) + { + true => + Err("Members set should be disjoint from candidates and runners-up sets".into()), + false => Ok(()), + } + } + + // [`Members`], [`RunnersUp`] and approval stake state checks. Invariants: + // - Selected members should have approval stake; + // - Selected RunnersUp should have approval stake. + fn try_state_members_approval_stake() -> Result<(), TryRuntimeError> { + match Members::::get() + .iter() + .chain(RunnersUp::::get().iter()) + .all(|s| s.stake != BalanceOf::::zero()) + { + true => Ok(()), + false => Err("Members and RunnersUp must have approval stake".into()), + } + } + + fn intersects(a: &[P], b: &[P]) -> bool { + a.iter().any(|e| b.contains(e)) + } + + fn candidates_ids() -> Vec { + Pallet::::candidates().iter().map(|(x, _)| x).cloned().collect::>() + } + + fn runners_up_ids() -> Vec { + Pallet::::runners_up().into_iter().map(|r| r.who).collect::>() + } +} + #[cfg(test)] mod tests { use super::*; @@ -1173,27 +1317,19 @@ mod tests { }; use substrate_test_utils::assert_eq_uvec; - parameter_types! { - pub BlockWeights: frame_system::limits::BlockWeights = - frame_system::limits::BlockWeights::simple_max( - frame_support::weights::Weight::from_ref_time(1024).set_proof_size(u64::MAX), - ); - } - impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; - type BlockWeights = BlockWeights; + type BlockWeights = (); type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -1217,6 +1353,10 @@ mod tests { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } frame_support::parameter_types! { @@ -1284,7 +1424,7 @@ mod tests { type PalletId = ElectionsPhragmenPalletId; type RuntimeEvent = RuntimeEvent; type Currency = Balances; - type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type CurrencyToVote = (); type ChangeMembers = TestChangeMembers; type InitializeMembers = (); type CandidacyBond = CandidacyBond; @@ -1297,6 +1437,7 @@ mod tests { type KickedMember = (); type WeightInfo = (); type MaxVoters = PhragmenMaxVoters; + type MaxVotesPerVoter = ConstU32<16>; type MaxCandidates = PhragmenMaxCandidates; } @@ -1305,10 +1446,7 @@ mod tests { sp_runtime::generic::UncheckedExtrinsic; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub enum Test { System: frame_system::{Pallet, Call, Event}, Balances: pallet_balances::{Pallet, Call, Event, Config}, @@ -1362,7 +1500,7 @@ mod tests { MEMBERS.with(|m| { *m.borrow_mut() = self.genesis_members.iter().map(|(m, _)| *m).collect::>() }); - let mut ext: sp_io::TestExternalities = GenesisConfig { + let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { balances: pallet_balances::GenesisConfig:: { balances: vec![ (1, 10 * self.balance_factor), @@ -1382,7 +1520,13 @@ mod tests { .into(); ext.execute_with(pre_conditions); ext.execute_with(test); - ext.execute_with(post_conditions) + + #[cfg(feature = "try-runtime")] + ext.execute_with(|| { + assert_ok!(>::try_state( + System::block_number() + )); + }); } } @@ -1439,54 +1583,13 @@ mod tests { .unwrap_or_default() } - fn intersects(a: &[T], b: &[T]) -> bool { - a.iter().any(|e| b.contains(e)) - } - - fn ensure_members_sorted() { - let mut members = Elections::members().clone(); - members.sort_by_key(|m| m.who); - assert_eq!(Elections::members(), members); - } - - fn ensure_candidates_sorted() { - let mut candidates = Elections::candidates().clone(); - candidates.sort_by_key(|(c, _)| *c); - assert_eq!(Elections::candidates(), candidates); - } - fn locked_stake_of(who: &u64) -> u64 { Voting::::get(who).stake } - fn ensure_members_has_approval_stake() { - // we filter members that have no approval state. This means that even we have more seats - // than candidates, we will never ever chose a member with no votes. - assert!(Elections::members() - .iter() - .chain(Elections::runners_up().iter()) - .all(|s| s.stake != u64::zero())); - } - - fn ensure_member_candidates_runners_up_disjoint() { - // members, candidates and runners-up must always be disjoint sets. - assert!(!intersects(&members_ids(), &candidate_ids())); - assert!(!intersects(&members_ids(), &runners_up_ids())); - assert!(!intersects(&candidate_ids(), &runners_up_ids())); - } - fn pre_conditions() { System::set_block_number(1); - ensure_members_sorted(); - ensure_candidates_sorted(); - ensure_member_candidates_runners_up_disjoint(); - } - - fn post_conditions() { - ensure_members_sorted(); - ensure_candidates_sorted(); - ensure_member_candidates_runners_up_disjoint(); - ensure_members_has_approval_stake(); + Elections::do_try_state().unwrap(); } fn submit_candidacy(origin: RuntimeOrigin) -> sp_runtime::DispatchResult { @@ -2089,7 +2192,8 @@ mod tests { assert_ok!(submit_candidacy(RuntimeOrigin::signed(4))); // User has 100 free and 50 reserved. - assert_ok!(Balances::set_balance(RuntimeOrigin::root(), 2, 100, 50)); + assert_ok!(Balances::force_set_balance(RuntimeOrigin::root(), 2, 150)); + assert_ok!(Balances::reserve(&2, 50)); // User tries to vote with 150 tokens. assert_ok!(vote(RuntimeOrigin::signed(2), vec![4, 5], 150)); // We truncate to only their free balance, after reserving additional for voting. diff --git a/frame/elections-phragmen/src/migrations/mod.rs b/frame/elections-phragmen/src/migrations/mod.rs index 7c62e8fa9306..f5403284126a 100644 --- a/frame/elections-phragmen/src/migrations/mod.rs +++ b/frame/elections-phragmen/src/migrations/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,6 +17,8 @@ //! All migrations of this pallet. +/// Migration to unreserve all pallet funds. +pub mod unlock_and_unreserve_all_funds; /// Version 3. pub mod v3; /// Version 4. diff --git a/frame/elections-phragmen/src/migrations/unlock_and_unreserve_all_funds.rs b/frame/elections-phragmen/src/migrations/unlock_and_unreserve_all_funds.rs new file mode 100644 index 000000000000..f566f84e51a2 --- /dev/null +++ b/frame/elections-phragmen/src/migrations/unlock_and_unreserve_all_funds.rs @@ -0,0 +1,445 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A migration that unreserves all deposit and unlocks all stake held in the context of this +//! pallet. + +use crate::BalanceOf; +use core::iter::Sum; +use frame_support::traits::{LockableCurrency, OnRuntimeUpgrade, ReservableCurrency}; +use sp_core::Get; +use sp_runtime::traits::Zero; +use sp_std::collections::btree_map::BTreeMap; + +#[cfg(feature = "try-runtime")] +use sp_std::vec::Vec; + +const LOG_TARGET: &str = "elections_phragmen::migrations::unlock_and_unreserve_all_funds"; + +/// A migration that unreserves all deposit and unlocks all stake held in the context of this +/// pallet. +/// +/// Useful to prevent funds from being locked up when the pallet is being deprecated. +/// +/// The pallet should be made inoperable before this migration is run. +/// +/// (See also [`RemovePallet`][frame_support::migrations::RemovePallet]) +pub struct UnlockAndUnreserveAllFunds(sp_std::marker::PhantomData); + +impl UnlockAndUnreserveAllFunds { + /// Calculates and returns the total amounts deposited and staked by each account in the context + /// of this pallet. + /// + /// The deposited and staked amounts are returned in two separate `BTreeMap` collections. + /// + /// The first `BTreeMap`, `account_deposited_sums`, contains each account's total amount + /// deposited. This includes deposits made by Members, RunnerUps, Candidates, and Voters. + /// + /// The second `BTreeMap`, `account_staked_sums`, contains each account's total amount staked. + /// This includes stakes made by Voters. + /// + /// # Returns + /// + /// This function returns a tuple of two `BTreeMap` collections and the weight of the reads: + /// + /// * `BTreeMap>`: Map of account IDs to their respective total + /// deposit sums. + /// * `BTreeMap>`: Map of account IDs to their respective total + /// staked sums. + /// * `frame_support::weights::Weight`: The weight of reading the storage. + fn get_account_deposited_and_staked_sums() -> ( + BTreeMap>, + BTreeMap>, + frame_support::weights::Weight, + ) { + use crate::Voting; + use sp_runtime::Saturating; + + let members = crate::Members::::get(); + let runner_ups = crate::RunnersUp::::get(); + let candidates = crate::Candidates::::get(); + + // Get the total amount deposited (Members, RunnerUps, Candidates and Voters all can have + // deposits). + let account_deposited_sums: BTreeMap> = members + // Massage all data structures into (account_id, deposit) tuples. + .iter() + .chain(runner_ups.iter()) + .map(|member| (member.who.clone(), member.deposit)) + .chain(candidates.iter().map(|(candidate, amount)| (candidate.clone(), *amount))) + .chain( + Voting::::iter().map(|(account_id, voter)| (account_id.clone(), voter.deposit)), + ) + // Finally, aggregate the tuples into a Map. + .fold(BTreeMap::new(), |mut acc, (id, deposit)| { + acc.entry(id.clone()).or_insert(Zero::zero()).saturating_accrue(deposit); + acc + }); + + // Get the total amount staked (only Voters stake) and count the number of voters. + let mut voters_len = 0; + let account_staked_sums: BTreeMap> = Voting::::iter() + .map(|(account_id, voter)| (account_id.clone(), voter.stake)) + .fold(BTreeMap::new(), |mut acc, (id, stake)| { + voters_len.saturating_accrue(1); + acc.entry(id.clone()).or_insert(Zero::zero()).saturating_accrue(stake); + acc + }); + + ( + account_deposited_sums, + account_staked_sums, + T::DbWeight::get().reads( + members + .len() + .saturating_add(runner_ups.len()) + .saturating_add(candidates.len()) + .saturating_add(voters_len.saturating_mul(T::MaxVotesPerVoter::get() as usize)) + as u64, + ), + ) + } +} + +impl OnRuntimeUpgrade for UnlockAndUnreserveAllFunds +where + BalanceOf: Sum, +{ + /// Collects pre-migration data useful for validating the migration was successful, and also + /// checks the integrity of deposited and reserved balances. + /// + /// Steps: + /// 1. Gets the deposited and staked balances for each account stored in this pallet. + /// 2. Collects actual pre-migration locked and reserved balances for each account. + /// 3. Checks the integrity of the deposited and reserved balances. + /// 4. Prints summary statistics about the state to be migrated. + /// 5. Encodes and returns pre-migration data to be used in post_upgrade. + /// + /// Fails with a `TryRuntimeError` if there's a discrepancy between the amount + /// reported as staked by the pallet and the amount actually locked in `Balances`. + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + use codec::Encode; + use sp_std::collections::btree_set::BTreeSet; + + // Get staked and deposited balances as reported by this pallet. + let (account_deposited_sums, account_staked_sums, _) = + Self::get_account_deposited_and_staked_sums(); + + let all_accounts: BTreeSet = account_staked_sums + .keys() + .chain(account_deposited_sums.keys()) + .cloned() + .collect(); + + let account_reserved_before: BTreeMap> = all_accounts + .iter() + .map(|account| (account.clone(), T::Currency::reserved_balance(&account))) + .collect(); + + // Total deposited for each account *should* be less than or equal to the total reserved, + // however this does not hold for all cases due to bugs in the reserve logic of this pallet. + let bugged_deposits = all_accounts + .iter() + .filter(|account| { + account_deposited_sums.get(&account).unwrap_or(&Zero::zero()) > + account_reserved_before.get(&account).unwrap_or(&Zero::zero()) + }) + .count(); + + // Print some summary stats. + let total_stake_to_unlock = account_staked_sums.clone().into_values().sum::>(); + let total_deposits_to_unreserve = + account_deposited_sums.clone().into_values().sum::>(); + log::info!(target: LOG_TARGET, "Total accounts: {:?}", all_accounts.len()); + log::info!(target: LOG_TARGET, "Total stake to unlock: {:?}", total_stake_to_unlock); + log::info!( + target: LOG_TARGET, + "Total deposit to unreserve: {:?}", + total_deposits_to_unreserve + ); + if bugged_deposits > 0 { + log::warn!( + target: LOG_TARGET, + "Bugged deposits: {}/{}", + bugged_deposits, + all_accounts.len() + ); + } + + Ok(account_reserved_before.encode()) + } + + /// Executes the migration. + /// + /// Steps: + /// 1. Retrieves the deposit and stake amounts from the pallet. + /// 2. Unreserves the deposited funds for each account. + /// 3. Unlocks the staked funds for each account. + fn on_runtime_upgrade() -> frame_support::weights::Weight { + // Get staked and deposited balances as reported by this pallet. + let (account_deposited_sums, account_staked_sums, initial_reads) = + Self::get_account_deposited_and_staked_sums(); + + // Deposited funds need to be unreserved. + for (account, unreserve_amount) in account_deposited_sums.iter() { + if unreserve_amount.is_zero() { + log::warn!(target: LOG_TARGET, "Unexpected zero amount to unreserve"); + continue + } + T::Currency::unreserve(&account, *unreserve_amount); + } + + // Staked funds need to be unlocked. + for (account, amount) in account_staked_sums.iter() { + if amount.is_zero() { + log::warn!(target: LOG_TARGET, "Unexpected zero amount to unlock"); + continue + } + T::Currency::remove_lock(T::PalletId::get(), account); + } + + T::DbWeight::get() + .reads_writes( + (account_deposited_sums.len().saturating_add(account_staked_sums.len())) as u64, + (account_deposited_sums.len().saturating_add(account_staked_sums.len())) as u64, + ) + .saturating_add(initial_reads) + } + + /// Performs post-upgrade sanity checks: + /// + /// 1. All expected locks were removed after the migration. + /// 2. The reserved balance for each account has been reduced by the expected amount. + #[cfg(feature = "try-runtime")] + fn post_upgrade( + account_reserved_before_bytes: Vec, + ) -> Result<(), sp_runtime::TryRuntimeError> { + use codec::Decode; + use sp_runtime::Saturating; + + let account_reserved_before = + BTreeMap::>::decode(&mut &account_reserved_before_bytes[..]) + .map_err(|_| "Failed to decode account_reserved_before_bytes")?; + + // Get deposited balances as reported by this pallet. + let (account_deposited_sums, _, _) = Self::get_account_deposited_and_staked_sums(); + + // Check that the reserved balance is reduced by the expected deposited amount. + for (account, actual_reserved_before) in account_reserved_before { + let actual_reserved_after = T::Currency::reserved_balance(&account); + let expected_amount_deducted = *account_deposited_sums + .get(&account) + .unwrap_or(&Zero::zero()) + // .min here to handle bugged deposits where actual_reserved_before is less than the + // amount the pallet reports is reserved + .min(&actual_reserved_before); + let expected_reserved_after = + actual_reserved_before.saturating_sub(expected_amount_deducted); + assert!( + actual_reserved_after == expected_reserved_after, + "Reserved balance for {:?} is incorrect. actual before: {:?}, actual after, {:?}, expected deducted: {:?}", + account, + actual_reserved_before, + actual_reserved_after, + expected_amount_deducted, + ); + } + + Ok(()) + } +} + +#[cfg(all(feature = "try-runtime", test))] +mod test { + use super::*; + use crate::{ + tests::{ExtBuilder, Test}, + Candidates, Members, RunnersUp, SeatHolder, Voter, Voting, + }; + use frame_support::{ + assert_ok, + traits::{Currency, OnRuntimeUpgrade, ReservableCurrency, WithdrawReasons}, + }; + + #[test] + fn unreserve_works_for_candidate() { + let candidate = 10; + let deposit = 100; + let initial_reserved = 15; + let initial_balance = 100_000; + ExtBuilder::default().build_and_execute(|| { + // Set up initial state. + ::Currency::make_free_balance_be(&candidate, initial_balance); + assert_ok!(::Currency::reserve(&candidate, initial_reserved)); + Candidates::::set(vec![(candidate, deposit)]); + assert_ok!(::Currency::reserve(&candidate, deposit)); + + // Sanity check: ensure initial Balance state was set up correctly. + assert_eq!( + ::Currency::reserved_balance(&candidate), + deposit + initial_reserved + ); + + // Run the migration. + let bytes = UnlockAndUnreserveAllFunds::::pre_upgrade() + .unwrap_or_else(|e| panic!("pre_upgrade failed: {:?}", e)); + UnlockAndUnreserveAllFunds::::on_runtime_upgrade(); + assert_ok!(UnlockAndUnreserveAllFunds::::post_upgrade(bytes)); + + // Assert the candidate reserved balance was reduced by the expected amount. + assert_eq!( + ::Currency::reserved_balance(&candidate), + initial_reserved + ); + }); + } + + #[test] + fn unreserve_works_for_runner_up() { + let runner_up = 10; + let deposit = 100; + let initial_reserved = 15; + let initial_balance = 100_000; + ExtBuilder::default().build_and_execute(|| { + // Set up initial state. + ::Currency::make_free_balance_be(&runner_up, initial_balance); + assert_ok!(::Currency::reserve(&runner_up, initial_reserved)); + RunnersUp::::set(vec![SeatHolder { who: runner_up, deposit, stake: 10 }]); + assert_ok!(::Currency::reserve(&runner_up, deposit)); + + // Sanity check: ensure initial Balance state was set up correctly. + assert_eq!( + ::Currency::reserved_balance(&runner_up), + deposit + initial_reserved + ); + + // Run the migration. + let bytes = UnlockAndUnreserveAllFunds::::pre_upgrade() + .unwrap_or_else(|e| panic!("pre_upgrade failed: {:?}", e)); + UnlockAndUnreserveAllFunds::::on_runtime_upgrade(); + assert_ok!(UnlockAndUnreserveAllFunds::::post_upgrade(bytes)); + + // Assert the reserved balance was reduced by the expected amount. + assert_eq!( + ::Currency::reserved_balance(&runner_up), + initial_reserved + ); + }); + } + + #[test] + fn unreserve_works_for_member() { + let member = 10; + let deposit = 100; + let initial_reserved = 15; + let initial_balance = 100_000; + ExtBuilder::default().build_and_execute(|| { + // Set up initial state. + ::Currency::make_free_balance_be(&member, initial_balance); + assert_ok!(::Currency::reserve(&member, initial_reserved)); + Members::::set(vec![SeatHolder { who: member, deposit, stake: 10 }]); + assert_ok!(::Currency::reserve(&member, deposit)); + + // Sanity check: ensure initial Balance state was set up correctly. + assert_eq!( + ::Currency::reserved_balance(&member), + deposit + initial_reserved + ); + + // Run the migration. + let bytes = UnlockAndUnreserveAllFunds::::pre_upgrade() + .unwrap_or_else(|e| panic!("pre_upgrade failed: {:?}", e)); + UnlockAndUnreserveAllFunds::::on_runtime_upgrade(); + assert_ok!(UnlockAndUnreserveAllFunds::::post_upgrade(bytes)); + + // Assert the reserved balance was reduced by the expected amount. + assert_eq!( + ::Currency::reserved_balance(&member), + initial_reserved + ); + }); + } + + #[test] + fn unlock_and_unreserve_works_for_voter() { + let voter = 10; + let deposit = 100; + let initial_reserved = 15; + let initial_locks = vec![(b"somethin", 10)]; + let stake = 25; + let initial_balance = 100_000; + ExtBuilder::default().build_and_execute(|| { + let pallet_id = ::PalletId::get(); + + // Set up initial state. + ::Currency::make_free_balance_be(&voter, initial_balance); + assert_ok!(::Currency::reserve(&voter, initial_reserved)); + for lock in initial_locks.clone() { + ::Currency::set_lock( + *lock.0, + &voter, + lock.1, + WithdrawReasons::all(), + ); + } + Voting::::insert(voter, Voter { votes: vec![], deposit, stake }); + assert_ok!(::Currency::reserve(&voter, deposit)); + ::Currency::set_lock( + ::PalletId::get(), + &voter, + stake, + WithdrawReasons::all(), + ); + + // Sanity check: ensure initial Balance state was set up correctly. + assert_eq!( + ::Currency::reserved_balance(&voter), + deposit + initial_reserved + ); + let mut voter_all_locks = initial_locks.clone(); + voter_all_locks.push((&pallet_id, stake)); + assert_eq!( + ::Currency::locks(&voter) + .iter() + .map(|lock| (&lock.id, lock.amount)) + .collect::>(), + voter_all_locks + ); + + // Run the migration. + let bytes = UnlockAndUnreserveAllFunds::::pre_upgrade() + .unwrap_or_else(|e| panic!("pre_upgrade failed: {:?}", e)); + UnlockAndUnreserveAllFunds::::on_runtime_upgrade(); + assert_ok!(UnlockAndUnreserveAllFunds::::post_upgrade(bytes)); + + // Assert the voter lock was removed and the reserved balance was reduced by the + // expected amount. + assert_eq!( + ::Currency::reserved_balance(&voter), + initial_reserved + ); + assert_eq!( + ::Currency::locks(&voter) + .iter() + .map(|lock| (&lock.id, lock.amount)) + .collect::>(), + initial_locks + ); + }); + } +} diff --git a/frame/elections-phragmen/src/migrations/v3.rs b/frame/elections-phragmen/src/migrations/v3.rs index e48cd6c1a805..204614a5b46c 100644 --- a/frame/elections-phragmen/src/migrations/v3.rs +++ b/frame/elections-phragmen/src/migrations/v3.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/elections-phragmen/src/migrations/v4.rs b/frame/elections-phragmen/src/migrations/v4.rs index 535a9fb8201d..7e946371f5ca 100644 --- a/frame/elections-phragmen/src/migrations/v4.rs +++ b/frame/elections-phragmen/src/migrations/v4.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/elections-phragmen/src/weights.rs b/frame/elections-phragmen/src/weights.rs index ddc55b08750d..b7ed13dae9f7 100644 --- a/frame/elections-phragmen/src/weights.rs +++ b/frame/elections-phragmen/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_elections_phragmen //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_elections_phragmen +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_elections_phragmen. pub trait WeightInfo { @@ -64,306 +69,514 @@ pub trait WeightInfo { /// Weights for pallet_elections_phragmen using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Elections Candidates (r:1 w:0) - // Storage: Elections Members (r:1 w:0) - // Storage: Elections RunnersUp (r:1 w:0) - // Storage: Elections Voting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Elections Candidates (r:1 w:0) + /// Proof Skipped: Elections Candidates (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Members (r:1 w:0) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections RunnersUp (r:1 w:0) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Voting (r:1 w:1) + /// Proof Skipped: Elections Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `v` is `[1, 16]`. fn vote_equal(v: u32, ) -> Weight { - // Minimum execution time: 38_496 nanoseconds. - Weight::from_ref_time(39_424_348 as u64) - // Standard Error: 3_547 - .saturating_add(Weight::from_ref_time(119_971 as u64).saturating_mul(v as u64)) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `403 + v * (80 ±0)` + // Estimated: `4764 + v * (80 ±0)` + // Minimum execution time: 33_028_000 picoseconds. + Weight::from_parts(34_073_914, 4764) + // Standard Error: 3_474 + .saturating_add(Weight::from_parts(205_252, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 80).saturating_mul(v.into())) } - // Storage: Elections Candidates (r:1 w:0) - // Storage: Elections Members (r:1 w:0) - // Storage: Elections RunnersUp (r:1 w:0) - // Storage: Elections Voting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Elections Candidates (r:1 w:0) + /// Proof Skipped: Elections Candidates (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Members (r:1 w:0) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections RunnersUp (r:1 w:0) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Voting (r:1 w:1) + /// Proof Skipped: Elections Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `v` is `[2, 16]`. fn vote_more(v: u32, ) -> Weight { - // Minimum execution time: 49_459 nanoseconds. - Weight::from_ref_time(50_225_486 as u64) - // Standard Error: 3_160 - .saturating_add(Weight::from_ref_time(170_360 as u64).saturating_mul(v as u64)) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `371 + v * (80 ±0)` + // Estimated: `4764 + v * (80 ±0)` + // Minimum execution time: 45_725_000 picoseconds. + Weight::from_parts(47_169_586, 4764) + // Standard Error: 5_148 + .saturating_add(Weight::from_parts(213_742, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 80).saturating_mul(v.into())) } - // Storage: Elections Candidates (r:1 w:0) - // Storage: Elections Members (r:1 w:0) - // Storage: Elections RunnersUp (r:1 w:0) - // Storage: Elections Voting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Elections Candidates (r:1 w:0) + /// Proof Skipped: Elections Candidates (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Members (r:1 w:0) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections RunnersUp (r:1 w:0) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Voting (r:1 w:1) + /// Proof Skipped: Elections Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `v` is `[2, 16]`. fn vote_less(v: u32, ) -> Weight { - // Minimum execution time: 48_712 nanoseconds. - Weight::from_ref_time(49_463_298 as u64) - // Standard Error: 2_678 - .saturating_add(Weight::from_ref_time(231_771 as u64).saturating_mul(v as u64)) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `403 + v * (80 ±0)` + // Estimated: `4764 + v * (80 ±0)` + // Minimum execution time: 45_519_000 picoseconds. + Weight::from_parts(47_339_108, 4764) + // Standard Error: 5_501 + .saturating_add(Weight::from_parts(195_247, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 80).saturating_mul(v.into())) } - // Storage: Elections Voting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Elections Voting (r:1 w:1) + /// Proof Skipped: Elections Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn remove_voter() -> Weight { - // Minimum execution time: 48_359 nanoseconds. - Weight::from_ref_time(48_767_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `925` + // Estimated: `4764` + // Minimum execution time: 50_386_000 picoseconds. + Weight::from_parts(51_378_000, 4764) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Elections Candidates (r:1 w:1) - // Storage: Elections Members (r:1 w:0) - // Storage: Elections RunnersUp (r:1 w:0) - /// The range of component `c` is `[1, 1000]`. + /// Storage: Elections Candidates (r:1 w:1) + /// Proof Skipped: Elections Candidates (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Members (r:1 w:0) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections RunnersUp (r:1 w:0) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) + /// The range of component `c` is `[1, 64]`. fn submit_candidacy(c: u32, ) -> Weight { - // Minimum execution time: 43_369 nanoseconds. - Weight::from_ref_time(49_587_113 as u64) - // Standard Error: 1_008 - .saturating_add(Weight::from_ref_time(77_752 as u64).saturating_mul(c as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `1570 + c * (48 ±0)` + // Estimated: `3055 + c * (48 ±0)` + // Minimum execution time: 38_987_000 picoseconds. + Weight::from_parts(41_302_276, 3055) + // Standard Error: 2_047 + .saturating_add(Weight::from_parts(125_200, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 48).saturating_mul(c.into())) } - // Storage: Elections Candidates (r:1 w:1) - /// The range of component `c` is `[1, 1000]`. + /// Storage: Elections Candidates (r:1 w:1) + /// Proof Skipped: Elections Candidates (max_values: Some(1), max_size: None, mode: Measured) + /// The range of component `c` is `[1, 64]`. fn renounce_candidacy_candidate(c: u32, ) -> Weight { - // Minimum execution time: 41_321 nanoseconds. - Weight::from_ref_time(50_803_289 as u64) - // Standard Error: 1_159 - .saturating_add(Weight::from_ref_time(57_239 as u64).saturating_mul(c as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `285 + c * (48 ±0)` + // Estimated: `1770 + c * (48 ±0)` + // Minimum execution time: 33_510_000 picoseconds. + Weight::from_parts(34_947_760, 1770) + // Standard Error: 1_781 + .saturating_add(Weight::from_parts(78_851, 0).saturating_mul(c.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 48).saturating_mul(c.into())) } - // Storage: Elections Members (r:1 w:1) - // Storage: Elections RunnersUp (r:1 w:1) - // Storage: Council Prime (r:1 w:1) - // Storage: Council Proposals (r:1 w:0) - // Storage: Council Members (r:0 w:1) + /// Storage: Elections Members (r:1 w:1) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections RunnersUp (r:1 w:1) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Prime (r:1 w:1) + /// Proof Skipped: Council Prime (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:0) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Members (r:0 w:1) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) fn renounce_candidacy_members() -> Weight { - // Minimum execution time: 53_542 nanoseconds. - Weight::from_ref_time(54_481_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `1900` + // Estimated: `3385` + // Minimum execution time: 50_603_000 picoseconds. + Weight::from_parts(51_715_000, 3385) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } - // Storage: Elections RunnersUp (r:1 w:1) + /// Storage: Elections RunnersUp (r:1 w:1) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) fn renounce_candidacy_runners_up() -> Weight { - // Minimum execution time: 41_825 nanoseconds. - Weight::from_ref_time(42_248_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `880` + // Estimated: `2365` + // Minimum execution time: 33_441_000 picoseconds. + Weight::from_parts(34_812_000, 2365) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Benchmark Override (r:0 w:0) + /// Storage: Benchmark Override (r:0 w:0) + /// Proof Skipped: Benchmark Override (max_values: None, max_size: None, mode: Measured) fn remove_member_without_replacement() -> Weight { - // Minimum execution time: 2_000_000_000 nanoseconds. - Weight::from_ref_time(2_000_000_000_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_000_000_000_000 picoseconds. + Weight::from_parts(2_000_000_000_000, 0) } - // Storage: Elections Members (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Elections RunnersUp (r:1 w:1) - // Storage: Council Prime (r:1 w:1) - // Storage: Council Proposals (r:1 w:0) - // Storage: Council Members (r:0 w:1) + /// Storage: Elections Members (r:1 w:1) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Elections RunnersUp (r:1 w:1) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Prime (r:1 w:1) + /// Proof Skipped: Council Prime (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:0) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Members (r:0 w:1) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) fn remove_member_with_replacement() -> Weight { - // Minimum execution time: 62_600 nanoseconds. - Weight::from_ref_time(63_152_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + // Proof Size summary in bytes: + // Measured: `1900` + // Estimated: `3593` + // Minimum execution time: 57_289_000 picoseconds. + Weight::from_parts(58_328_000, 3593) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } - // Storage: Elections Voting (r:5001 w:5000) - // Storage: Elections Members (r:1 w:0) - // Storage: Elections RunnersUp (r:1 w:0) - // Storage: Elections Candidates (r:1 w:0) - // Storage: Balances Locks (r:5000 w:5000) - // Storage: System Account (r:5000 w:5000) - /// The range of component `v` is `[5000, 10000]`. - /// The range of component `d` is `[0, 5000]`. + /// Storage: Elections Voting (r:513 w:512) + /// Proof Skipped: Elections Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Elections Members (r:1 w:0) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections RunnersUp (r:1 w:0) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Candidates (r:1 w:0) + /// Proof Skipped: Elections Candidates (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Balances Locks (r:512 w:512) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:512 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: System Account (r:512 w:512) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// The range of component `v` is `[256, 512]`. + /// The range of component `d` is `[0, 256]`. fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { - // Minimum execution time: 297_149_264 nanoseconds. - Weight::from_ref_time(297_898_499_000 as u64) - // Standard Error: 263_819 - .saturating_add(Weight::from_ref_time(37_914_985 as u64).saturating_mul(v as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().reads((3 as u64).saturating_mul(v as u64))) - .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(v as u64))) + // Proof Size summary in bytes: + // Measured: `1149 + v * (811 ±0)` + // Estimated: `4621 + v * (3774 ±0)` + // Minimum execution time: 18_774_231_000 picoseconds. + Weight::from_parts(18_933_040_000, 4621) + // Standard Error: 301_534 + .saturating_add(Weight::from_parts(44_306_903, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(v.into()))) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(v.into()))) + .saturating_add(Weight::from_parts(0, 3774).saturating_mul(v.into())) } - // Storage: Elections Candidates (r:1 w:1) - // Storage: Elections Members (r:1 w:1) - // Storage: Elections RunnersUp (r:1 w:1) - // Storage: Elections Voting (r:10001 w:0) - // Storage: Council Proposals (r:1 w:0) - // Storage: Elections ElectionRounds (r:1 w:1) - // Storage: Council Members (r:0 w:1) - // Storage: Council Prime (r:0 w:1) - // Storage: System Account (r:1 w:1) - /// The range of component `c` is `[1, 1000]`. - /// The range of component `v` is `[1, 10000]`. - /// The range of component `e` is `[10000, 160000]`. + /// Storage: Elections Candidates (r:1 w:1) + /// Proof Skipped: Elections Candidates (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Members (r:1 w:1) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections RunnersUp (r:1 w:1) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Voting (r:513 w:0) + /// Proof Skipped: Elections Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:0) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: System Account (r:44 w:44) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Elections ElectionRounds (r:1 w:1) + /// Proof Skipped: Elections ElectionRounds (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Members (r:0 w:1) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Prime (r:0 w:1) + /// Proof Skipped: Council Prime (max_values: Some(1), max_size: None, mode: Measured) + /// The range of component `c` is `[1, 64]`. + /// The range of component `v` is `[1, 512]`. + /// The range of component `e` is `[512, 8192]`. fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { - // Minimum execution time: 22_034_317 nanoseconds. - Weight::from_ref_time(22_110_020_000 as u64) - // Standard Error: 235_528 - .saturating_add(Weight::from_ref_time(25_553_585 as u64).saturating_mul(v as u64)) - // Standard Error: 15_114 - .saturating_add(Weight::from_ref_time(1_032_330 as u64).saturating_mul(e as u64)) - .saturating_add(T::DbWeight::get().reads(280 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(c as u64))) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(v as u64))) - .saturating_add(T::DbWeight::get().writes(6 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(c as u64))) + // Proof Size summary in bytes: + // Measured: `0 + e * (28 ±0) + v * (606 ±0)` + // Estimated: `178887 + c * (2135 ±7) + e * (12 ±0) + v * (2653 ±6)` + // Minimum execution time: 1_281_877_000 picoseconds. + Weight::from_parts(1_288_147_000, 178887) + // Standard Error: 528_851 + .saturating_add(Weight::from_parts(17_761_407, 0).saturating_mul(v.into())) + // Standard Error: 33_932 + .saturating_add(Weight::from_parts(698_277, 0).saturating_mul(e.into())) + .saturating_add(T::DbWeight::get().reads(21_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(c.into()))) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) + .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(c.into()))) + .saturating_add(Weight::from_parts(0, 2135).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(0, 12).saturating_mul(e.into())) + .saturating_add(Weight::from_parts(0, 2653).saturating_mul(v.into())) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Elections Candidates (r:1 w:0) - // Storage: Elections Members (r:1 w:0) - // Storage: Elections RunnersUp (r:1 w:0) - // Storage: Elections Voting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Elections Candidates (r:1 w:0) + /// Proof Skipped: Elections Candidates (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Members (r:1 w:0) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections RunnersUp (r:1 w:0) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Voting (r:1 w:1) + /// Proof Skipped: Elections Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `v` is `[1, 16]`. fn vote_equal(v: u32, ) -> Weight { - // Minimum execution time: 38_496 nanoseconds. - Weight::from_ref_time(39_424_348 as u64) - // Standard Error: 3_547 - .saturating_add(Weight::from_ref_time(119_971 as u64).saturating_mul(v as u64)) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `403 + v * (80 ±0)` + // Estimated: `4764 + v * (80 ±0)` + // Minimum execution time: 33_028_000 picoseconds. + Weight::from_parts(34_073_914, 4764) + // Standard Error: 3_474 + .saturating_add(Weight::from_parts(205_252, 0).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 80).saturating_mul(v.into())) } - // Storage: Elections Candidates (r:1 w:0) - // Storage: Elections Members (r:1 w:0) - // Storage: Elections RunnersUp (r:1 w:0) - // Storage: Elections Voting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Elections Candidates (r:1 w:0) + /// Proof Skipped: Elections Candidates (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Members (r:1 w:0) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections RunnersUp (r:1 w:0) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Voting (r:1 w:1) + /// Proof Skipped: Elections Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `v` is `[2, 16]`. fn vote_more(v: u32, ) -> Weight { - // Minimum execution time: 49_459 nanoseconds. - Weight::from_ref_time(50_225_486 as u64) - // Standard Error: 3_160 - .saturating_add(Weight::from_ref_time(170_360 as u64).saturating_mul(v as u64)) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `371 + v * (80 ±0)` + // Estimated: `4764 + v * (80 ±0)` + // Minimum execution time: 45_725_000 picoseconds. + Weight::from_parts(47_169_586, 4764) + // Standard Error: 5_148 + .saturating_add(Weight::from_parts(213_742, 0).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 80).saturating_mul(v.into())) } - // Storage: Elections Candidates (r:1 w:0) - // Storage: Elections Members (r:1 w:0) - // Storage: Elections RunnersUp (r:1 w:0) - // Storage: Elections Voting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Elections Candidates (r:1 w:0) + /// Proof Skipped: Elections Candidates (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Members (r:1 w:0) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections RunnersUp (r:1 w:0) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Voting (r:1 w:1) + /// Proof Skipped: Elections Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `v` is `[2, 16]`. fn vote_less(v: u32, ) -> Weight { - // Minimum execution time: 48_712 nanoseconds. - Weight::from_ref_time(49_463_298 as u64) - // Standard Error: 2_678 - .saturating_add(Weight::from_ref_time(231_771 as u64).saturating_mul(v as u64)) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `403 + v * (80 ±0)` + // Estimated: `4764 + v * (80 ±0)` + // Minimum execution time: 45_519_000 picoseconds. + Weight::from_parts(47_339_108, 4764) + // Standard Error: 5_501 + .saturating_add(Weight::from_parts(195_247, 0).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 80).saturating_mul(v.into())) } - // Storage: Elections Voting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Elections Voting (r:1 w:1) + /// Proof Skipped: Elections Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn remove_voter() -> Weight { - // Minimum execution time: 48_359 nanoseconds. - Weight::from_ref_time(48_767_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `925` + // Estimated: `4764` + // Minimum execution time: 50_386_000 picoseconds. + Weight::from_parts(51_378_000, 4764) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Elections Candidates (r:1 w:1) - // Storage: Elections Members (r:1 w:0) - // Storage: Elections RunnersUp (r:1 w:0) - /// The range of component `c` is `[1, 1000]`. + /// Storage: Elections Candidates (r:1 w:1) + /// Proof Skipped: Elections Candidates (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Members (r:1 w:0) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections RunnersUp (r:1 w:0) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) + /// The range of component `c` is `[1, 64]`. fn submit_candidacy(c: u32, ) -> Weight { - // Minimum execution time: 43_369 nanoseconds. - Weight::from_ref_time(49_587_113 as u64) - // Standard Error: 1_008 - .saturating_add(Weight::from_ref_time(77_752 as u64).saturating_mul(c as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `1570 + c * (48 ±0)` + // Estimated: `3055 + c * (48 ±0)` + // Minimum execution time: 38_987_000 picoseconds. + Weight::from_parts(41_302_276, 3055) + // Standard Error: 2_047 + .saturating_add(Weight::from_parts(125_200, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 48).saturating_mul(c.into())) } - // Storage: Elections Candidates (r:1 w:1) - /// The range of component `c` is `[1, 1000]`. + /// Storage: Elections Candidates (r:1 w:1) + /// Proof Skipped: Elections Candidates (max_values: Some(1), max_size: None, mode: Measured) + /// The range of component `c` is `[1, 64]`. fn renounce_candidacy_candidate(c: u32, ) -> Weight { - // Minimum execution time: 41_321 nanoseconds. - Weight::from_ref_time(50_803_289 as u64) - // Standard Error: 1_159 - .saturating_add(Weight::from_ref_time(57_239 as u64).saturating_mul(c as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `285 + c * (48 ±0)` + // Estimated: `1770 + c * (48 ±0)` + // Minimum execution time: 33_510_000 picoseconds. + Weight::from_parts(34_947_760, 1770) + // Standard Error: 1_781 + .saturating_add(Weight::from_parts(78_851, 0).saturating_mul(c.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 48).saturating_mul(c.into())) } - // Storage: Elections Members (r:1 w:1) - // Storage: Elections RunnersUp (r:1 w:1) - // Storage: Council Prime (r:1 w:1) - // Storage: Council Proposals (r:1 w:0) - // Storage: Council Members (r:0 w:1) + /// Storage: Elections Members (r:1 w:1) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections RunnersUp (r:1 w:1) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Prime (r:1 w:1) + /// Proof Skipped: Council Prime (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:0) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Members (r:0 w:1) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) fn renounce_candidacy_members() -> Weight { - // Minimum execution time: 53_542 nanoseconds. - Weight::from_ref_time(54_481_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `1900` + // Estimated: `3385` + // Minimum execution time: 50_603_000 picoseconds. + Weight::from_parts(51_715_000, 3385) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } - // Storage: Elections RunnersUp (r:1 w:1) + /// Storage: Elections RunnersUp (r:1 w:1) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) fn renounce_candidacy_runners_up() -> Weight { - // Minimum execution time: 41_825 nanoseconds. - Weight::from_ref_time(42_248_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `880` + // Estimated: `2365` + // Minimum execution time: 33_441_000 picoseconds. + Weight::from_parts(34_812_000, 2365) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Benchmark Override (r:0 w:0) + /// Storage: Benchmark Override (r:0 w:0) + /// Proof Skipped: Benchmark Override (max_values: None, max_size: None, mode: Measured) fn remove_member_without_replacement() -> Weight { - // Minimum execution time: 2_000_000_000 nanoseconds. - Weight::from_ref_time(2_000_000_000_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_000_000_000_000 picoseconds. + Weight::from_parts(2_000_000_000_000, 0) } - // Storage: Elections Members (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Elections RunnersUp (r:1 w:1) - // Storage: Council Prime (r:1 w:1) - // Storage: Council Proposals (r:1 w:0) - // Storage: Council Members (r:0 w:1) + /// Storage: Elections Members (r:1 w:1) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Elections RunnersUp (r:1 w:1) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Prime (r:1 w:1) + /// Proof Skipped: Council Prime (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:0) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Members (r:0 w:1) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) fn remove_member_with_replacement() -> Weight { - // Minimum execution time: 62_600 nanoseconds. - Weight::from_ref_time(63_152_000 as u64) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + // Proof Size summary in bytes: + // Measured: `1900` + // Estimated: `3593` + // Minimum execution time: 57_289_000 picoseconds. + Weight::from_parts(58_328_000, 3593) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } - // Storage: Elections Voting (r:5001 w:5000) - // Storage: Elections Members (r:1 w:0) - // Storage: Elections RunnersUp (r:1 w:0) - // Storage: Elections Candidates (r:1 w:0) - // Storage: Balances Locks (r:5000 w:5000) - // Storage: System Account (r:5000 w:5000) - /// The range of component `v` is `[5000, 10000]`. - /// The range of component `d` is `[0, 5000]`. + /// Storage: Elections Voting (r:513 w:512) + /// Proof Skipped: Elections Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Elections Members (r:1 w:0) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections RunnersUp (r:1 w:0) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Candidates (r:1 w:0) + /// Proof Skipped: Elections Candidates (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Balances Locks (r:512 w:512) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:512 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: System Account (r:512 w:512) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// The range of component `v` is `[256, 512]`. + /// The range of component `d` is `[0, 256]`. fn clean_defunct_voters(v: u32, _d: u32, ) -> Weight { - // Minimum execution time: 297_149_264 nanoseconds. - Weight::from_ref_time(297_898_499_000 as u64) - // Standard Error: 263_819 - .saturating_add(Weight::from_ref_time(37_914_985 as u64).saturating_mul(v as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().reads((3 as u64).saturating_mul(v as u64))) - .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(v as u64))) + // Proof Size summary in bytes: + // Measured: `1149 + v * (811 ±0)` + // Estimated: `4621 + v * (3774 ±0)` + // Minimum execution time: 18_774_231_000 picoseconds. + Weight::from_parts(18_933_040_000, 4621) + // Standard Error: 301_534 + .saturating_add(Weight::from_parts(44_306_903, 0).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(v.into()))) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(v.into()))) + .saturating_add(Weight::from_parts(0, 3774).saturating_mul(v.into())) } - // Storage: Elections Candidates (r:1 w:1) - // Storage: Elections Members (r:1 w:1) - // Storage: Elections RunnersUp (r:1 w:1) - // Storage: Elections Voting (r:10001 w:0) - // Storage: Council Proposals (r:1 w:0) - // Storage: Elections ElectionRounds (r:1 w:1) - // Storage: Council Members (r:0 w:1) - // Storage: Council Prime (r:0 w:1) - // Storage: System Account (r:1 w:1) - /// The range of component `c` is `[1, 1000]`. - /// The range of component `v` is `[1, 10000]`. - /// The range of component `e` is `[10000, 160000]`. + /// Storage: Elections Candidates (r:1 w:1) + /// Proof Skipped: Elections Candidates (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Members (r:1 w:1) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections RunnersUp (r:1 w:1) + /// Proof Skipped: Elections RunnersUp (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Elections Voting (r:513 w:0) + /// Proof Skipped: Elections Voting (max_values: None, max_size: None, mode: Measured) + /// Storage: Council Proposals (r:1 w:0) + /// Proof Skipped: Council Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: System Account (r:44 w:44) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Elections ElectionRounds (r:1 w:1) + /// Proof Skipped: Elections ElectionRounds (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Members (r:0 w:1) + /// Proof Skipped: Council Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Council Prime (r:0 w:1) + /// Proof Skipped: Council Prime (max_values: Some(1), max_size: None, mode: Measured) + /// The range of component `c` is `[1, 64]`. + /// The range of component `v` is `[1, 512]`. + /// The range of component `e` is `[512, 8192]`. fn election_phragmen(c: u32, v: u32, e: u32, ) -> Weight { - // Minimum execution time: 22_034_317 nanoseconds. - Weight::from_ref_time(22_110_020_000 as u64) - // Standard Error: 235_528 - .saturating_add(Weight::from_ref_time(25_553_585 as u64).saturating_mul(v as u64)) - // Standard Error: 15_114 - .saturating_add(Weight::from_ref_time(1_032_330 as u64).saturating_mul(e as u64)) - .saturating_add(RocksDbWeight::get().reads(280 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(c as u64))) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(v as u64))) - .saturating_add(RocksDbWeight::get().writes(6 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(c as u64))) + // Proof Size summary in bytes: + // Measured: `0 + e * (28 ±0) + v * (606 ±0)` + // Estimated: `178887 + c * (2135 ±7) + e * (12 ±0) + v * (2653 ±6)` + // Minimum execution time: 1_281_877_000 picoseconds. + Weight::from_parts(1_288_147_000, 178887) + // Standard Error: 528_851 + .saturating_add(Weight::from_parts(17_761_407, 0).saturating_mul(v.into())) + // Standard Error: 33_932 + .saturating_add(Weight::from_parts(698_277, 0).saturating_mul(e.into())) + .saturating_add(RocksDbWeight::get().reads(21_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(c.into()))) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(v.into()))) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(c.into()))) + .saturating_add(Weight::from_parts(0, 2135).saturating_mul(c.into())) + .saturating_add(Weight::from_parts(0, 12).saturating_mul(e.into())) + .saturating_add(Weight::from_parts(0, 2653).saturating_mul(v.into())) } } diff --git a/frame/examples/Cargo.toml b/frame/examples/Cargo.toml new file mode 100644 index 000000000000..af67bef792b6 --- /dev/null +++ b/frame/examples/Cargo.toml @@ -0,0 +1,39 @@ +[package] +name = "pallet-examples" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "The single package with various examples for frame pallets" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +pallet-example-basic = { default-features = false, path = "./basic" } +pallet-default-config-example = { default-features = false, path = "./default-config" } +pallet-example-offchain-worker = { default-features = false, path = "./offchain-worker" } +pallet-example-kitchensink = { default-features = false, path = "./kitchensink" } +pallet-dev-mode = { default-features = false, path = "./dev-mode" } +pallet-example-split = { default-features = false, path = "./split" } + +[features] +default = [ "std" ] +std = [ + "pallet-example-basic/std", + "pallet-default-config-example/std", + "pallet-example-offchain-worker/std", + "pallet-example-kitchensink/std", + "pallet-dev-mode/std", + "pallet-example-split/std", +] +try-runtime = [ + "pallet-example-basic/try-runtime", + "pallet-default-config-example/try-runtime", + "pallet-example-offchain-worker/try-runtime", + "pallet-example-kitchensink/try-runtime", + "pallet-dev-mode/try-runtime", + "pallet-example-split/try-runtime", +] diff --git a/frame/examples/basic/Cargo.toml b/frame/examples/basic/Cargo.toml index 47f623f45381..e45a0627f9de 100644 --- a/frame/examples/basic/Cargo.toml +++ b/frame/examples/basic/Cargo.toml @@ -3,7 +3,7 @@ name = "pallet-example-basic" version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" -license = "Unlicense" +license = "MIT-0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME example pallet" @@ -13,19 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } -sp-io = { version = "7.0.0", default-features = false, path = "../../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, path = "../../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } [dev-dependencies] -sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } +sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } [features] default = ["std"] diff --git a/frame/examples/basic/README.md b/frame/examples/basic/README.md index 358829192f11..2af50573f807 100644 --- a/frame/examples/basic/README.md +++ b/frame/examples/basic/README.md @@ -237,4 +237,4 @@ pub trait Config: ::Config { } // that the implementation is based on.

-License: Unlicense +License: MIT-0 diff --git a/frame/examples/basic/src/benchmarking.rs b/frame/examples/basic/src/benchmarking.rs index 699e56685fbe..4b2ebb41fbda 100644 --- a/frame/examples/basic/src/benchmarking.rs +++ b/frame/examples/basic/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +21,7 @@ #![cfg(feature = "runtime-benchmarks")] use crate::*; -use frame_benchmarking::{impl_benchmark_test_suite, whitelisted_caller}; -use frame_support::benchmarking::{benchmarks, Linear}; +use frame_benchmarking::v2::*; use frame_system::RawOrigin; // To actually run this benchmark on pallet-example-basic, we need to put this pallet into the @@ -52,19 +51,31 @@ mod benchmarks { assert_eq!(Pallet::::dummy(), Some(value)) } + // An example method that returns a Result that can be called within a benchmark + fn example_result_method() -> Result<(), BenchmarkError> { + Ok(()) + } + // This will measure the execution time of `accumulate_dummy`. // The benchmark execution phase is shorthanded. When the name of the benchmark case is the same // as the extrinsic call. `_(...)` is used to represent the extrinsic name. // The benchmark verification phase is omitted. #[benchmark] - fn accumulate_dummy() { + fn accumulate_dummy() -> Result<(), BenchmarkError> { let value = 1000u32.into(); // The caller account is whitelisted for DB reads/write by the benchmarking macro. let caller: T::AccountId = whitelisted_caller(); + // an example of calling something result-based within a benchmark using the ? operator + // this necessitates specifying the `Result<(), BenchmarkError>` return type + example_result_method()?; + // You can use `_` if the name of the Call matches the benchmark name. #[extrinsic_call] _(RawOrigin::Signed(caller), value); + + // need this to be compatible with the return type + Ok(()) } /// You can write helper functions in here since its a normal Rust module. diff --git a/frame/examples/basic/src/lib.rs b/frame/examples/basic/src/lib.rs index d5045157dade..426e9b7ec648 100644 --- a/frame/examples/basic/src/lib.rs +++ b/frame/examples/basic/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -329,7 +329,7 @@ impl WeighData<(&BalanceOf,)> for WeightForSetDum let multiplier = self.0; // *target.0 is the amount passed into the extrinsic let cents = *target.0 / >::from(MILLICENTS); - Weight::from_ref_time((cents * multiplier).saturated_into::()) + Weight::from_parts((cents * multiplier).saturated_into::(), 0) } } @@ -379,7 +379,6 @@ pub mod pallet { // Simple declaration of the `Pallet` type. It is placeholder we use to implement traits and // method. #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); // Pallet implements [`Hooks`] trait to define some logic to execute in some context. @@ -389,21 +388,21 @@ pub mod pallet { // dispatched. // // This function must return the weight consumed by `on_initialize` and `on_finalize`. - fn on_initialize(_n: T::BlockNumber) -> Weight { + fn on_initialize(_n: BlockNumberFor) -> Weight { // Anything that needs to be done at the start of the block. // We don't do anything here. Weight::zero() } // `on_finalize` is executed at the end of block after all extrinsic are dispatched. - fn on_finalize(_n: T::BlockNumber) { + fn on_finalize(_n: BlockNumberFor) { // Perform necessary data/state clean up here. } // A runtime code run after every block and have access to extended set of APIs. // // For instance you can generate extrinsics for the upcoming produced block. - fn offchain_worker(_n: T::BlockNumber) { + fn offchain_worker(_n: BlockNumberFor) { // We don't do anything here. // but we could dispatch extrinsic (transaction/unsigned/inherent) using // sp_io::submit_extrinsic. @@ -442,7 +441,7 @@ pub mod pallet { // against them as the first thing you do in your function. There are three convenience calls // in system that do the matching for you and return a convenient result: `ensure_signed`, // `ensure_root` and `ensure_none`. - #[pallet::call] + #[pallet::call(weight(::WeightInfo))] impl Pallet { /// This is your public interface. Be extremely careful. /// This is just a simple example of how to interact with the pallet from the external @@ -498,9 +497,6 @@ pub mod pallet { // The weight for this extrinsic we rely on the auto-generated `WeightInfo` from the // benchmark toolchain. #[pallet::call_index(0)] - #[pallet::weight( - ::WeightInfo::accumulate_dummy() - )] pub fn accumulate_dummy(origin: OriginFor, increase_by: T::Balance) -> DispatchResult { // This is a public call, so we ensure that the origin is some signed account. let _sender = ensure_signed(origin)?; @@ -622,23 +618,16 @@ pub mod pallet { // The genesis config type. #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { pub dummy: T::Balance, pub bar: Vec<(T::AccountId, T::Balance)>, pub foo: T::Balance, } - // The default value for the genesis config type. - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - Self { dummy: Default::default(), bar: Default::default(), foo: Default::default() } - } - } - // The build of genesis for the pallet. #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { >::put(&self.dummy); for (a, b) in &self.bar { diff --git a/frame/examples/basic/src/tests.rs b/frame/examples/basic/src/tests.rs index 891c7c78628a..addf219dc3c3 100644 --- a/frame/examples/basic/src/tests.rs +++ b/frame/examples/basic/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,24 +27,19 @@ use sp_core::H256; // The testing primitives are very useful for avoiding having to work with signatures // or public keys. `u64` is used as the `AccountId` and no `Signature`s are required. use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; // Reexport crate as its pallet name for construct_runtime. use crate as pallet_example_basic; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; // For testing the pallet, we construct a mock runtime. frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Example: pallet_example_basic::{Pallet, Call, Storage, Config, Event}, } @@ -56,14 +51,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -87,6 +81,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } impl Config for Test { @@ -98,7 +96,7 @@ impl Config for Test { // 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 = GenesisConfig { + let t = RuntimeGenesisConfig { // We use default for brevity, but you can configure as desired if needed. system: Default::default(), balances: Default::default(), @@ -188,6 +186,7 @@ fn weights_work() { // aka. `let info = as GetDispatchInfo>::get_dispatch_info(&default_call);` // TODO: account for proof size weight assert!(info1.weight.ref_time() > 0); + assert_eq!(info1.weight, ::WeightInfo::accumulate_dummy()); // `set_dummy` is simpler than `accumulate_dummy`, and the weight // should be less. diff --git a/frame/examples/basic/src/weights.rs b/frame/examples/basic/src/weights.rs index a69f0824eac1..def944054cce 100644 --- a/frame/examples/basic/src/weights.rs +++ b/frame/examples/basic/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -56,20 +56,20 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { // Storage: BasicExample Dummy (r:0 w:1) fn set_dummy_benchmark() -> Weight { - Weight::from_ref_time(19_000_000 as u64) + Weight::from_parts(19_000_000 as u64, 0) .saturating_add(T::DbWeight::get().writes(1 as u64)) } // Storage: BasicExample Dummy (r:1 w:1) fn accumulate_dummy() -> Weight { - Weight::from_ref_time(18_000_000 as u64) + Weight::from_parts(18_000_000 as u64, 0) .saturating_add(T::DbWeight::get().reads(1 as u64)) .saturating_add(T::DbWeight::get().writes(1 as u64)) } /// The range of component `x` is `[0, 10000]`. fn sort_vector(x: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) + Weight::from_parts(0 as u64, 0) // Standard Error: 2 - .saturating_add(Weight::from_ref_time(520 as u64).saturating_mul(x as u64)) + .saturating_add(Weight::from_parts(520 as u64, 0).saturating_mul(x as u64)) } } @@ -77,19 +77,19 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { // Storage: BasicExample Dummy (r:0 w:1) fn set_dummy_benchmark() -> Weight { - Weight::from_ref_time(19_000_000 as u64) + Weight::from_parts(19_000_000 as u64, 0) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } // Storage: BasicExample Dummy (r:1 w:1) fn accumulate_dummy() -> Weight { - Weight::from_ref_time(18_000_000 as u64) + Weight::from_parts(18_000_000 as u64, 0) .saturating_add(RocksDbWeight::get().reads(1 as u64)) .saturating_add(RocksDbWeight::get().writes(1 as u64)) } /// The range of component `x` is `[0, 10000]`. fn sort_vector(x: u32, ) -> Weight { - Weight::from_ref_time(0 as u64) + Weight::from_parts(0 as u64, 0) // Standard Error: 2 - .saturating_add(Weight::from_ref_time(520 as u64).saturating_mul(x as u64)) + .saturating_add(Weight::from_parts(520 as u64, 0).saturating_mul(x as u64)) } } diff --git a/frame/examples/default-config/Cargo.toml b/frame/examples/default-config/Cargo.toml new file mode 100644 index 000000000000..ceca331308be --- /dev/null +++ b/frame/examples/default-config/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "pallet-default-config-example" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "MIT-0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME example pallet demonstrating derive_impl / default_config in action" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +frame-support = { default-features = false, path = "../../support" } +frame-system = { default-features = false, path = "../../system" } + +sp-io = { default-features = false, path = "../../../primitives/io" } +sp-runtime = { default-features = false, path = "../../../primitives/runtime" } +sp-std = { default-features = false, path = "../../../primitives/std" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/examples/default-config/README.md b/frame/examples/default-config/README.md new file mode 100644 index 000000000000..b1a67a5c16e5 --- /dev/null +++ b/frame/examples/default-config/README.md @@ -0,0 +1,8 @@ +# Default Config Example Pallet + +An example pallet demonstrating the ability to derive default testing configs via +`#[derive_impl]` and `#[pallet::config(with_default)]`. + +Run `cargo doc --package pallet-default-config-example --open` to view this pallet's documentation. + +License: MIT-0 diff --git a/frame/examples/default-config/src/lib.rs b/frame/examples/default-config/src/lib.rs new file mode 100644 index 000000000000..63a6a941b829 --- /dev/null +++ b/frame/examples/default-config/src/lib.rs @@ -0,0 +1,205 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Default Config Pallet Example +//! +//! A simple example of a FRAME pallet that utilizes [`frame_support::derive_impl`] to demonstrate +//! the simpler way to implement `Config` trait of pallets. This example only showcases this in a +//! `mock.rs` environment, but the same applies to a real runtime as well. +//! +//! See the source code of [`tests`] for a real examples. +//! +//! Study the following types: +//! +//! - [`pallet::DefaultConfig`], and how it differs from [`pallet::Config`]. +//! - [`pallet::config_preludes::TestDefaultConfig`] and how it implements +//! [`pallet::DefaultConfig`]. +//! - Notice how [`pallet::DefaultConfig`] is independent of [`frame_system::Config`]. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + + /// This pallet is annotated to have a default config. This will auto-generate + /// [`DefaultConfig`]. + /// + /// It will be an identical, but won't have anything that is `#[pallet::no_default]`. + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + /// The overarching event type. This is coming from the runtime, and cannot have a default. + /// In general, `Runtime*`-oriented types cannot have a sensible default. + #[pallet::no_default] // optional. `RuntimeEvent` is automatically excluded as well. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// An input parameter to this pallet. This value can have a default, because it is not + /// reliant on `frame_system::Config` or the overarching runtime in any way. + type WithDefaultValue: Get; + + /// Same as [`Config::WithDefaultValue`], but we don't intend to define a default for this + /// in our tests below. + type OverwrittenDefaultValue: Get; + + /// An input parameter that relies on `::AccountId`. As of + /// now, such types cannot have defaults and need to be annotated as such, iff + /// `#[pallet::config(with_default)]` is enabled: + #[pallet::no_default] + type CannotHaveDefault: Get; + + /// Something that is a normal type, with default. + type WithDefaultType; + + /// Same as [`Config::WithDefaultType`], but we don't intend to define a default for this + /// in our tests below. + type OverwrittenDefaultType; + } + + /// Container for different types that implement [`DefaultConfig`]` of this pallet. + pub mod config_preludes { + // This will help use not need to disambiguate anything when using `derive_impl`. + use super::*; + + /// A type providing default configurations for this pallet in testing environment. + pub struct TestDefaultConfig; + #[frame_support::register_default_impl(TestDefaultConfig)] + impl DefaultConfig for TestDefaultConfig { + type WithDefaultValue = frame_support::traits::ConstU32<42>; + type OverwrittenDefaultValue = frame_support::traits::ConstU32<42>; + + type WithDefaultType = u32; + type OverwrittenDefaultType = u32; + } + + /// A type providing default configurations for this pallet in a parachain environment. + pub struct ParachainDefaultConfig; + #[frame_support::register_default_impl(ParachainDefaultConfig)] + impl DefaultConfig for ParachainDefaultConfig { + type WithDefaultValue = frame_support::traits::ConstU32<66>; + type OverwrittenDefaultValue = frame_support::traits::ConstU32<66>; + type WithDefaultType = u32; + type OverwrittenDefaultType = u32; + } + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::event] + pub enum Event {} +} + +#[cfg(any(test, doc))] +pub mod tests { + use super::*; + use frame_support::derive_impl; + use sp_runtime::traits::ConstU64; + + use super::pallet as pallet_default_config_example; + + type Block = frame_system::mocking::MockBlock; + + frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + DefaultPallet: pallet_default_config_example, + } + ); + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] + impl frame_system::Config for Test { + // these items are defined by frame-system as `no_default`, so we must specify them here. + // Note that these are types that actually rely on the outer runtime, and can't sensibly + // have an _independent_ default. + type Block = Block; + type BlockHashCount = ConstU64<10>; + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type OnSetCode = (); + + // all of this is coming from `frame_system::config_preludes::TestDefaultConfig`. + + // type Nonce = u32; + // type BlockNumber = u32; + // type Header = + // sp_runtime::generic::Header, + // Self::Hashing>; + // type Hash = sp_core::hash::H256; + // type Hashing = sp_runtime::traits::BlakeTwo256; + // type AccountId = u64; + // type Lookup = sp_runtime::traits::IdentityLookup; + // type BlockHashCount = frame_support::traits::ConstU32<10>; + // type MaxConsumers = frame_support::traits::ConstU32<16>; + // type AccountData = (); + // type OnNewAccount = (); + // type OnKilledAccount = (); + // type SystemWeightInfo = (); + // type SS58Prefix = (); + // type Version = (); + // type BlockWeights = (); + // type BlockLength = (); + // type DbWeight = (); + + // you could still overwrite any of them if desired. + type SS58Prefix = frame_support::traits::ConstU16<456>; + } + + // Similarly, we use the defaults provided by own crate as well. + use pallet::config_preludes::*; + #[derive_impl(TestDefaultConfig as pallet::DefaultConfig)] + impl crate::pallet::Config for Test { + // These two both cannot have defaults. + type RuntimeEvent = RuntimeEvent; + // Note that the default account-id type in + // `frame_system::config_preludes::TestDefaultConfig` is `u64`. + type CannotHaveDefault = frame_support::traits::ConstU64<1>; + + type OverwrittenDefaultValue = frame_support::traits::ConstU32<678>; + type OverwrittenDefaultType = u128; + } + + #[test] + fn it_works() { + use frame_support::traits::Get; + use pallet::{Config, DefaultConfig}; + + // assert one of the value types that is not overwritten. + assert_eq!( + <::WithDefaultValue as Get>::get(), + <::WithDefaultValue as Get>::get() + ); + + // assert one of the value types that is overwritten. + assert_eq!(<::OverwrittenDefaultValue as Get>::get(), 678u32); + + // assert one of the types that is not overwritten. + assert_eq!( + std::any::TypeId::of::<::WithDefaultType>(), + std::any::TypeId::of::<::WithDefaultType>() + ); + + // assert one of the types that is overwritten. + assert_eq!( + std::any::TypeId::of::<::OverwrittenDefaultType>(), + std::any::TypeId::of::() + ) + } +} diff --git a/frame/examples/dev-mode/Cargo.toml b/frame/examples/dev-mode/Cargo.toml new file mode 100644 index 000000000000..4b7b2e61f1ea --- /dev/null +++ b/frame/examples/dev-mode/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "pallet-dev-mode" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "MIT-0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME example pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } +pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } +sp-io = { version = "23.0.0", default-features = false, path = "../../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } + +[dev-dependencies] +sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-balances/std", + "scale-info/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/examples/dev-mode/README.md b/frame/examples/dev-mode/README.md new file mode 100644 index 000000000000..4c9ee88629c2 --- /dev/null +++ b/frame/examples/dev-mode/README.md @@ -0,0 +1,11 @@ + +# Dev Mode Example Pallet + +A simple example of a FRAME pallet demonstrating +the ease of requirements for a pallet in dev mode. + +Run `cargo doc --package pallet-dev-mode --open` to view this pallet's documentation. + +**Dev mode is not meant to be used in production.** + +License: MIT-0 diff --git a/frame/examples/dev-mode/src/lib.rs b/frame/examples/dev-mode/src/lib.rs new file mode 100644 index 000000000000..d57e7a5b76b8 --- /dev/null +++ b/frame/examples/dev-mode/src/lib.rs @@ -0,0 +1,118 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! +//! # Dev Mode Example Pallet +//! +//! A simple example of a FRAME pallet demonstrating +//! the ease of requirements for a pallet in dev mode. +//! +//! Run `cargo doc --package pallet-dev-mode --open` to view this pallet's documentation. +//! +//! **Dev mode is not meant to be used in production.** + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::dispatch::DispatchResult; +use frame_system::ensure_signed; + +// Re-export pallet items so that they can be accessed from the crate namespace. +pub use pallet::*; + +#[cfg(test)] +mod tests; + +/// A type alias for the balance type from this pallet's point of view. +type BalanceOf = ::Balance; + +/// Enable `dev_mode` for this pallet. +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: pallet_balances::Config + frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + // Simple declaration of the `Pallet` type. It is placeholder we use to implement traits and + // method. + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + // No need to define a `call_index` attribute here because of `dev_mode`. + // No need to define a `weight` attribute here because of `dev_mode`. + pub fn add_dummy(origin: OriginFor, id: T::AccountId) -> DispatchResult { + ensure_root(origin)?; + + if let Some(mut dummies) = Dummy::::get() { + dummies.push(id.clone()); + Dummy::::set(Some(dummies)); + } else { + Dummy::::set(Some(vec![id.clone()])); + } + + // Let's deposit an event to let the outside world know this happened. + Self::deposit_event(Event::AddDummy { account: id }); + + Ok(()) + } + + // No need to define a `call_index` attribute here because of `dev_mode`. + // No need to define a `weight` attribute here because of `dev_mode`. + pub fn set_bar( + origin: OriginFor, + #[pallet::compact] new_value: T::Balance, + ) -> DispatchResult { + let sender = ensure_signed(origin)?; + + // Put the new value into storage. + >::insert(&sender, new_value); + + Self::deposit_event(Event::SetBar { account: sender, balance: new_value }); + + Ok(()) + } + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + AddDummy { account: T::AccountId }, + SetBar { account: T::AccountId, balance: BalanceOf }, + } + + /// The MEL requirement for bounded pallets is skipped by `dev_mode`. + /// This means that all storages are marked as unbounded. + /// This is equivalent to specifying `#[pallet::unbounded]` on this type definitions. + /// When the dev_mode is removed, we would need to implement implement `MaxEncodedLen`. + #[pallet::storage] + pub type Dummy = StorageValue<_, Vec>; + + /// The Hasher requirement is skipped by `dev_mode`. So, second parameter can be `_` + /// and `Blake2_128Concat` is used as a default. + /// When the dev_mode is removed, we would need to specify the hasher like so: + /// `pub type Bar = StorageMap<_, Blake2_128Concat, T::AccountId, T::Balance>;`. + #[pallet::storage] + pub type Bar = StorageMap<_, _, T::AccountId, T::Balance>; +} diff --git a/frame/examples/dev-mode/src/tests.rs b/frame/examples/dev-mode/src/tests.rs new file mode 100644 index 000000000000..ba98f5174ce2 --- /dev/null +++ b/frame/examples/dev-mode/src/tests.rs @@ -0,0 +1,124 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for pallet-dev-mode. + +use crate::*; +use frame_support::{assert_ok, traits::ConstU64}; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; +// Reexport crate as its pallet name for construct_runtime. +use crate as pallet_dev_mode; + +type Block = frame_system::mocking::MockBlock; + +// For testing the pallet, we construct a mock runtime. +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Example: pallet_dev_mode::{Pallet, Call, Storage, Event}, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = u64; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = RuntimeHoldReason; + type MaxHolds = (); +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; +} + +// 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 = RuntimeGenesisConfig { + // We use default for brevity, but you can configure as desired if needed. + system: Default::default(), + balances: Default::default(), + } + .build_storage() + .unwrap(); + t.into() +} + +#[test] +fn it_works_for_optional_value() { + new_test_ext().execute_with(|| { + assert_eq!(Dummy::::get(), None); + + let val1 = 42; + assert_ok!(Example::add_dummy(RuntimeOrigin::root(), val1)); + assert_eq!(Dummy::::get(), Some(vec![val1])); + + // Check that accumulate works when we have Some value in Dummy already. + let val2 = 27; + assert_ok!(Example::add_dummy(RuntimeOrigin::root(), val2)); + assert_eq!(Dummy::::get(), Some(vec![val1, val2])); + }); +} + +#[test] +fn set_dummy_works() { + new_test_ext().execute_with(|| { + let test_val = 133; + assert_ok!(Example::set_bar(RuntimeOrigin::signed(1), test_val.into())); + assert_eq!(Bar::::get(1), Some(test_val)); + }); +} diff --git a/frame/examples/kitchensink/Cargo.toml b/frame/examples/kitchensink/Cargo.toml new file mode 100644 index 000000000000..31141a0fff60 --- /dev/null +++ b/frame/examples/kitchensink/Cargo.toml @@ -0,0 +1,53 @@ +[package] +name = "pallet-example-kitchensink" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "MIT-0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME example kitchensink pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } + +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } + +sp-io = { version = "23.0.0", default-features = false, path = "../../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../../benchmarking" } + +pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } + +[dev-dependencies] +sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } + +[features] +default = ["std"] +std = [ + "codec/std", + "log/std", + "scale-info/std", + + "frame-support/std", + "frame-system/std", + + "sp-io/std", + "sp-runtime/std", + "sp-std/std", + + "frame-benchmarking?/std", + + "pallet-balances/std", +] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/examples/kitchensink/src/benchmarking.rs b/frame/examples/kitchensink/src/benchmarking.rs new file mode 100644 index 000000000000..24da581fc967 --- /dev/null +++ b/frame/examples/kitchensink/src/benchmarking.rs @@ -0,0 +1,66 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Benchmarking for `pallet-example-kitchensink`. + +// Only enable this module for benchmarking. +#![cfg(feature = "runtime-benchmarks")] +use super::*; + +#[allow(unused)] +use crate::Pallet as Kitchensink; + +use frame_benchmarking::v2::*; +use frame_system::RawOrigin; + +// To actually run this benchmark on pallet-example-kitchensink, we need to put this pallet into the +// runtime and compile it with `runtime-benchmarks` feature. The detail procedures are +// documented at: +// https://docs.substrate.io/reference/how-to-guides/weights/add-benchmarks/ +// +// The auto-generated weight estimate of this pallet is copied over to the `weights.rs` file. +// The exact command of how the estimate generated is printed at the top of the file. + +// Details on using the benchmarks macro can be seen at: +// https://paritytech.github.io/substrate/master/frame_benchmarking/trait.Benchmarking.html#tymethod.benchmarks +#[benchmarks] +mod benchmarks { + use super::*; + + // This will measure the execution time of `set_foo`. + #[benchmark] + fn set_foo_benchmark() { + // This is the benchmark setup phase. + // `set_foo` is a constant time function, hence we hard-code some random value here. + let value = 1000u32.into(); + #[extrinsic_call] + set_foo(RawOrigin::Root, value, 10u128); // The execution phase is just running `set_foo` extrinsic call + + // This is the optional benchmark verification phase, asserting certain states. + assert_eq!(Pallet::::foo(), Some(value)) + } + + // This line generates test cases for benchmarking, and could be run by: + // `cargo test -p pallet-example-kitchensink --all-features`, you will see one line per case: + // `test benchmarking::bench_sort_vector ... ok` + // `test benchmarking::bench_accumulate_dummy ... ok` + // `test benchmarking::bench_set_dummy_benchmark ... ok` in the result. + // + // The line generates three steps per benchmark, with repeat=1 and the three steps are + // [low, mid, high] of the range. + impl_benchmark_test_suite!(Kitchensink, crate::tests::new_test_ext(), crate::tests::Test); +} diff --git a/frame/examples/kitchensink/src/lib.rs b/frame/examples/kitchensink/src/lib.rs new file mode 100644 index 000000000000..0fbffc971da6 --- /dev/null +++ b/frame/examples/kitchensink/src/lib.rs @@ -0,0 +1,330 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Kitchensink Example Pallet +//! +//! **This pallet serves as an example and is not meant to be used in production.** +//! +//! The kitchen-sink catalog of the the FRAME macros and their various syntax options. +//! +//! This example does not focus on pallet instancing, `dev_mode`, and does nto include any 'where' +//! clauses on `T`. These will both incur additional complexity to the syntax, but are not discussed +//! here. + +#![cfg_attr(not(feature = "std"), no_std)] + +// Re-export pallet items so that they can be accessed from the crate namespace. +pub use pallet::*; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + +pub mod weights; +pub use weights::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + /// The config trait of the pallet. You can basically do anything with the config trait that you + /// can do with a normal rust trait: import items consisting of types, constants and functions. + /// + /// A very common pattern is for a pallet to import implementations of traits such as + /// [`frame_support::traits::Currency`], [`frame_support::traits::fungibles::Inspect`] and + /// [`frame_support::traits::Get`]. These are all types that the pallet is delegating to the top + /// level runtime to provide to it. + /// + /// The `FRAME`-specific syntax are: + /// + /// * the use of `#[pallet::constant]`([`frame_support::procedural`]), which places a `Get` + /// implementation in the metadata. + /// * `type RuntimeEvent`, which is mandatory if your pallet has events. See TODO. + /// * Needless to say, because [`Config`] is bounded by [`frame_system::Config`], you can use + /// all the items from [`frame_system::Config`] as well, such as `AccountId`. + /// * `#[pallet::disable_frame_system_supertrait_check]` would remove the need for + /// `frame_system::Config` to exist, which you should almost never need. + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching runtime event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Type representing the weight of this pallet + type WeightInfo: WeightInfo; + + /// This is a normal Rust type, nothing specific to FRAME here. + type Currency: frame_support::traits::tokens::fungible::Inspect; + + /// Similarly, let the runtime decide this. + fn some_function() -> u32; + + /// And this + const FOO: u32; + + /// This is a FRAME-specific item. It will be placed in the metadata of the pallet, and + /// therefore can be queried by offchain applications. + #[pallet::constant] + type InMetadata: Get; + } + + /// Allows you to define some extra constants to be added into constant metadata. + #[pallet::extra_constants] + impl Pallet { + #[allow(non_snake_case)] + fn SomeValue() -> u32 { + unimplemented!() + } + + #[pallet::constant_name(OtherValue)] + fn arbitrary_name() -> u32 { + unimplemented!() + } + } + + const STORAGE_VERSION: frame_support::traits::StorageVersion = StorageVersion::new(1); + + /// The pallet struct. There's nothing special to FRAME about this; it can implement functions + /// in an impl blocks, traits and so on. + #[pallet::pallet] + #[pallet::without_storage_info] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + /// Allows you to define some origin for the pallet. + #[pallet::origin] + pub type Origin = frame_system::RawOrigin<::AccountId>; + + // first, we showcase all the possible storage types, with most of their details. + + /// A storage value. We mark this as unbounded, alter its prefix, and define a custom storage + /// getter for it. + /// + /// The value is stored a single trie node, and therefore can be retrieved with a single + /// database access. + #[pallet::storage] + #[pallet::unbounded] // optional + #[pallet::storage_prefix = "OtherFoo"] // optional + #[pallet::getter(fn foo)] // optional + pub type Foo = StorageValue; + + #[pallet::type_value] + pub fn DefaultForFoo() -> u32 { + 1 + } + + #[pallet::storage] + pub type FooWithDefault = + StorageValue; + + /// A storage map. This creates a mapping from keys of type `u32` to values of type `u32`. + /// + /// Keys and values can be iterated, albeit each value is stored under a unique trie key, + /// meaning that an iteration consists of many database accesses. + #[pallet::storage] + pub type Bar = StorageMap; + + /// Conceptually same as `StorageMap<>` where the key is a tuple of `(u32, u32)`. On top, it + /// provides some functions to iterate or remove items based on only the first key. + #[pallet::storage] + pub type Qux = StorageDoubleMap< + Hasher1 = Blake2_128Concat, + Key1 = u32, + Hasher2 = Blake2_128Concat, + Key2 = u32, + Value = u32, + >; + + /// Same as `StorageDoubleMap`, but with arbitrary number of keys. + #[pallet::storage] + pub type Quux = StorageNMap< + Key = ( + NMapKey, + NMapKey, + NMapKey, + ), + Value = u64, + >; + + /// In all of these examples, we chose a syntax where the storage item is defined using the + /// explicit generic syntax (`X = Y`). Alternatively: + #[pallet::storage] + pub type AlternativeSyntax = StorageMap<_, Blake2_128Concat, u32, u32>; + + /// Lastly, all storage items, as you saw, had to be generic over `T`. If they want to use an + /// item from `Config`, `` should be used. + #[pallet::storage] + pub type AlternativeSyntax2 = StorageMap<_, Blake2_128Concat, T::AccountId, u32>; + + /// The genesis config type. This allows the pallet to define how it should initialized upon + /// genesis. + /// + /// It can be generic over `T` or not, depending on whether it is or not. + #[pallet::genesis_config] + pub struct GenesisConfig { + pub foo: u32, + pub bar: BlockNumberFor, + } + + impl Default for GenesisConfig { + fn default() -> Self { + Self { foo: 0, bar: Default::default() } + } + } + + /// Allows you to define how `genesis_configuration is built. + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + Foo::::put(self.foo); + } + } + + /// The call declaration. This states the entry points that we handle. The + /// macro takes care of the marshalling of arguments and dispatch. + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::set_foo_benchmark())] + pub fn set_foo( + _: OriginFor, + new_foo: u32, + #[pallet::compact] _other_compact: u128, + ) -> DispatchResult { + Foo::::set(Some(new_foo)); + + Ok(()) + } + } + + /// The event type. This exactly like a normal Rust enum. + /// + /// It can or cannot be generic over ``. Note that unlike a normal enum, if none of + /// the variants actually use ``, the macro will generate a hidden `PhantomData` + /// variant. + /// + /// The `generate_deposit` macro generates a function on `Pallet` called `deposit_event` which + /// will properly convert the error type of your pallet into `RuntimeEvent` (recall `type + /// RuntimeEvent: From>`, so it can be converted) and deposit it via + /// `frame_system::Pallet::deposit_event`. + #[pallet::event] + #[pallet::generate_deposit(pub fn deposit_event)] + pub enum Event { + /// A simple tuple style variant. + SomethingHappened(u32), + /// A simple struct-style variant. Note that we use `AccountId` from `T` because `T: + /// Config`, which by extension implies `T: frame_system::Config`. + SomethingDetailedHappened { at: u32, to: T::AccountId }, + /// Another variant. + SomeoneJoined(T::AccountId), + } + + /// The error enum. Must always be generic over ``, which is expanded to ``. + #[pallet::error] + pub enum Error { + SomethingWentWrong, + SomethingBroke, + } + + /// All the possible hooks that a pallet can have. See [`frame_support::traits::Hooks`] for more + /// info. + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() {} + + fn offchain_worker(_n: BlockNumberFor) { + unimplemented!() + } + + fn on_initialize(_n: BlockNumberFor) -> Weight { + unimplemented!() + } + + fn on_finalize(_n: BlockNumberFor) { + unimplemented!() + } + + fn on_idle(_n: BlockNumberFor, _remaining_weight: Weight) -> Weight { + unimplemented!() + } + + fn on_runtime_upgrade() -> Weight { + unimplemented!() + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + unimplemented!() + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { + unimplemented!() + } + + #[cfg(feature = "try-runtime")] + fn try_state(_n: BlockNumberFor) -> Result<(), TryRuntimeError> { + unimplemented!() + } + } + + /// Allows you to define an enum on the pallet which will then instruct + /// `construct_runtime` to amalgamate all similarly-named enums from other + /// pallets into an aggregate enum. + #[pallet::composite_enum] + pub enum HoldReason { + Staking, + } + + /// Allows the pallet to validate some unsigned transaction. See + /// [`sp_runtime::traits::ValidateUnsigned`] for more info. + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + fn validate_unsigned(_: TransactionSource, _: &Self::Call) -> TransactionValidity { + unimplemented!() + } + + fn pre_dispatch(_: &Self::Call) -> Result<(), TransactionValidityError> { + unimplemented!() + } + } + + /// Allows the pallet to provide some inherent. See [`frame_support::inherent::ProvideInherent`] + /// for more info. + #[pallet::inherent] + impl ProvideInherent for Pallet { + type Call = Call; + type Error = MakeFatalError<()>; + + const INHERENT_IDENTIFIER: [u8; 8] = *b"test1234"; + + fn create_inherent(_data: &InherentData) -> Option { + unimplemented!(); + } + + fn is_inherent(_call: &Self::Call) -> bool { + unimplemented!() + } + } +} diff --git a/frame/examples/kitchensink/src/tests.rs b/frame/examples/kitchensink/src/tests.rs new file mode 100644 index 000000000000..b2af7c8983f5 --- /dev/null +++ b/frame/examples/kitchensink/src/tests.rs @@ -0,0 +1,111 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for pallet-example-kitchensink. + +use crate::*; +use frame_support::{assert_ok, derive_impl, parameter_types, traits::ConstU64}; +use sp_runtime::BuildStorage; +// Reexport crate as its pallet name for construct_runtime. +use crate as pallet_example_kitchensink; + +type Block = frame_system::mocking::MockBlock; + +// For testing the pallet, we construct a mock runtime. +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + Kitchensink: pallet_example_kitchensink::{Pallet, Call, Storage, Config, Event}, + } +); + +/// Using a default config for [`frame_system`] in tests. See `default-config` example for more +/// details. +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type Block = Block; + type BlockHashCount = ConstU64<10>; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type OnSetCode = (); + + type AccountData = pallet_balances::AccountData; +} + +impl pallet_balances::Config for Test { + type MaxLocks = (); + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = u64; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); +} + +parameter_types! { + pub const InMetadata: u32 = 30; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + + type Currency = Balances; + type InMetadata = InMetadata; + + const FOO: u32 = 100; + + fn some_function() -> u32 { + 5u32 + } +} + +// 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 = RuntimeGenesisConfig { + // We use default for brevity, but you can configure as desired if needed. + system: Default::default(), + balances: Default::default(), + kitchensink: pallet_example_kitchensink::GenesisConfig { bar: 32, foo: 24 }, + } + .build_storage() + .unwrap(); + t.into() +} + +#[test] +fn set_foo_works() { + new_test_ext().execute_with(|| { + assert_eq!(Foo::::get(), Some(24)); // From genesis config. + + let val1 = 42; + assert_ok!(Kitchensink::set_foo(RuntimeOrigin::root(), val1, 2)); + assert_eq!(Foo::::get(), Some(val1)); + }); +} diff --git a/frame/examples/kitchensink/src/weights.rs b/frame/examples/kitchensink/src/weights.rs new file mode 100644 index 000000000000..1d083a9b80ee --- /dev/null +++ b/frame/examples/kitchensink/src/weights.rs @@ -0,0 +1,68 @@ + +//! Autogenerated weights for `pallet_example_kitchensink` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-06-02, STEPS: `20`, REPEAT: `10`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `MacBook.local`, CPU: `` +//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/node-template +// benchmark +// pallet +// --chain +// dev +// --pallet +// pallet_example_kitchensink +// --extrinsic +// * +// --steps +// 20 +// --repeat +// 10 +// --output +// frame/examples/kitchensink/src/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for pallet_template. +pub trait WeightInfo { + fn set_foo_benchmark() -> Weight; +} + +/// Weight functions for `pallet_example_kitchensink`. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: Kitchensink OtherFoo (r:0 w:1) + /// Proof Skipped: Kitchensink OtherFoo (max_values: Some(1), max_size: None, mode: Measured) + fn set_foo_benchmark() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(1_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} + +impl WeightInfo for () { + /// Storage: Kitchensink OtherFoo (r:0 w:1) + /// Proof Skipped: Kitchensink OtherFoo (max_values: Some(1), max_size: None, mode: Measured) + fn set_foo_benchmark() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 1_000_000 picoseconds. + Weight::from_parts(1_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(RocksDbWeight::get().writes(1)) + } +} diff --git a/frame/examples/offchain-worker/Cargo.toml b/frame/examples/offchain-worker/Cargo.toml index f94cf36b2b99..c0cd16bf19a6 100644 --- a/frame/examples/offchain-worker/Cargo.toml +++ b/frame/examples/offchain-worker/Cargo.toml @@ -3,7 +3,7 @@ name = "pallet-example-offchain-worker" version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" -license = "Unlicense" +license = "MIT-0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME example pallet for offchain worker" @@ -13,17 +13,17 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } lite-json = { version = "0.2.0", default-features = false } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } -sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../../primitives/io" } -sp-keystore = { version = "0.13.0", optional = true, path = "../../../primitives/keystore" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../../primitives/io" } +sp-keystore = { version = "0.27.0", optional = true, path = "../../../primitives/keystore" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } [features] default = ["std"] diff --git a/frame/examples/offchain-worker/README.md b/frame/examples/offchain-worker/README.md index 587431c92c0e..7b8905cda307 100644 --- a/frame/examples/offchain-worker/README.md +++ b/frame/examples/offchain-worker/README.md @@ -26,4 +26,4 @@ Additional logic in OCW is put in place to prevent spamming the network with bot and unsigned transactions, and custom `UnsignedValidator` makes sure that there is only one unsigned transaction floating in the network. -License: Unlicense +License: MIT-0 diff --git a/frame/examples/offchain-worker/src/lib.rs b/frame/examples/offchain-worker/src/lib.rs index 46ff7725e3b1..6c1fa6ea8ec4 100644 --- a/frame/examples/offchain-worker/src/lib.rs +++ b/frame/examples/offchain-worker/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -53,6 +53,7 @@ use frame_system::{ AppCrypto, CreateSignedTransaction, SendSignedTransaction, SendUnsignedTransaction, SignedPayload, Signer, SigningTypes, SubmitTransaction, }, + pallet_prelude::BlockNumberFor, }; use lite_json::json::JsonValue; use sp_core::crypto::KeyTypeId; @@ -136,14 +137,14 @@ pub mod pallet { /// every `GRACE_PERIOD` blocks. We use Local Storage to coordinate /// sending between distinct runs of this offchain worker. #[pallet::constant] - type GracePeriod: Get; + type GracePeriod: Get>; /// Number of blocks of cooldown after unsigned transaction is included. /// /// This ensures that we only accept unsigned transactions once, every `UnsignedInterval` /// blocks. #[pallet::constant] - type UnsignedInterval: Get; + type UnsignedInterval: Get>; /// A configuration for base priority of unsigned transactions. /// @@ -158,7 +159,6 @@ pub mod pallet { } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::hooks] @@ -167,12 +167,12 @@ pub mod pallet { /// /// By implementing `fn offchain_worker` you declare a new offchain worker. /// This function will be called when the node is fully synced and a new best block is - /// succesfuly imported. + /// successfully imported. /// Note that it's not guaranteed for offchain workers to run on EVERY block, there might /// be cases where some blocks are skipped, or for some the worker runs twice (re-orgs), /// so the code should be able to handle that. /// You can use `Local Storage` API to coordinate runs of the worker. - fn offchain_worker(block_number: T::BlockNumber) { + fn offchain_worker(block_number: BlockNumberFor) { // Note that having logs compiled to WASM may cause the size of the blob to increase // significantly. You can use `RuntimeDebug` custom derive to hide details of the types // in WASM. The `sp-api` crate also provides a feature `disable-logging` to disable @@ -225,12 +225,12 @@ pub mod pallet { /// The transaction needs to be signed (see `ensure_signed`) check, so that the caller /// pays a fee to execute it. /// This makes sure that it's not easy (or rather cheap) to attack the chain by submitting - /// excesive transactions, but note that it doesn't ensure the price oracle is actually + /// excessive transactions, but note that it doesn't ensure the price oracle is actually /// working and receives (and provides) meaningful data. /// This example is not focused on correctness of the oracle itself, but rather its /// purpose is to showcase offchain worker capabilities. #[pallet::call_index(0)] - #[pallet::weight(0)] + #[pallet::weight({0})] pub fn submit_price(origin: OriginFor, price: u32) -> DispatchResultWithPostInfo { // Retrieve sender of the transaction. let who = ensure_signed(origin)?; @@ -256,10 +256,10 @@ pub mod pallet { /// This example is not focused on correctness of the oracle itself, but rather its /// purpose is to showcase offchain worker capabilities. #[pallet::call_index(1)] - #[pallet::weight(0)] + #[pallet::weight({0})] pub fn submit_price_unsigned( origin: OriginFor, - _block_number: T::BlockNumber, + _block_number: BlockNumberFor, price: u32, ) -> DispatchResultWithPostInfo { // This ensures that the function can only be called via unsigned transaction. @@ -273,10 +273,10 @@ pub mod pallet { } #[pallet::call_index(2)] - #[pallet::weight(0)] + #[pallet::weight({0})] pub fn submit_price_unsigned_with_signed_payload( origin: OriginFor, - price_payload: PricePayload, + price_payload: PricePayload>, _signature: T::Signature, ) -> DispatchResultWithPostInfo { // This ensures that the function can only be called via unsigned transaction. @@ -337,12 +337,12 @@ pub mod pallet { /// Defines the block when next unsigned transaction will be accepted. /// - /// To prevent spam of unsigned (and unpayed!) transactions on the network, + /// To prevent spam of unsigned (and unpaid!) transactions on the network, /// we only allow one transaction every `T::UnsignedInterval` blocks. /// This storage entry defines when new transaction is going to be accepted. #[pallet::storage] #[pallet::getter(fn next_unsigned_at)] - pub(super) type NextUnsignedAt = StorageValue<_, T::BlockNumber, ValueQuery>; + pub(super) type NextUnsignedAt = StorageValue<_, BlockNumberFor, ValueQuery>; } /// Payload used by this example crate to hold price @@ -354,7 +354,7 @@ pub struct PricePayload { public: Public, } -impl SignedPayload for PricePayload { +impl SignedPayload for PricePayload> { fn public(&self) -> T::Public { self.public.clone() } @@ -375,7 +375,7 @@ impl Pallet { /// and local storage usage. /// /// Returns a type of transaction that should be produced in current run. - fn choose_transaction_type(block_number: T::BlockNumber) -> TransactionType { + fn choose_transaction_type(block_number: BlockNumberFor) -> TransactionType { /// A friendlier name for the error that is going to be returned in case we are in the grace /// period. const RECENTLY_SENT: () = (); @@ -390,16 +390,17 @@ impl Pallet { // low-level method of local storage API, which means that only one worker // will be able to "acquire a lock" and send a transaction if multiple workers // happen to be executed concurrently. - let res = val.mutate(|last_send: Result, StorageRetrievalError>| { - match last_send { - // If we already have a value in storage and the block number is recent enough - // we avoid sending another transaction at this time. - Ok(Some(block)) if block_number < block + T::GracePeriod::get() => - Err(RECENTLY_SENT), - // In every other case we attempt to acquire the lock and send a transaction. - _ => Ok(block_number), - } - }); + let res = + val.mutate(|last_send: Result>, StorageRetrievalError>| { + match last_send { + // If we already have a value in storage and the block number is recent enough + // we avoid sending another transaction at this time. + Ok(Some(block)) if block_number < block + T::GracePeriod::get() => + Err(RECENTLY_SENT), + // In every other case we attempt to acquire the lock and send a transaction. + _ => Ok(block_number), + } + }); // The result of `mutate` call will give us a nested `Result` type. // The first one matches the return of the closure passed to `mutate`, i.e. @@ -410,20 +411,19 @@ impl Pallet { match res { // The value has been set correctly, which means we can safely send a transaction now. Ok(block_number) => { - // Depending if the block is even or odd we will send a `Signed` or `Unsigned` - // transaction. + // We will send different transactions based on a random number. // Note that this logic doesn't really guarantee that the transactions will be sent // in an alternating fashion (i.e. fairly distributed). Depending on the execution // order and lock acquisition, we may end up for instance sending two `Signed` // transactions in a row. If a strict order is desired, it's better to use // the storage entry for that. (for instance store both block number and a flag // indicating the type of next transaction to send). - let transaction_type = block_number % 3u32.into(); + let transaction_type = block_number % 4u32.into(); if transaction_type == Zero::zero() { TransactionType::Signed - } else if transaction_type == T::BlockNumber::from(1u32) { + } else if transaction_type == BlockNumberFor::::from(1u32) { TransactionType::UnsignedForAny - } else if transaction_type == T::BlockNumber::from(2u32) { + } else if transaction_type == BlockNumberFor::::from(2u32) { TransactionType::UnsignedForAll } else { TransactionType::Raw @@ -474,7 +474,9 @@ impl Pallet { } /// A helper function to fetch the price and send a raw unsigned transaction. - fn fetch_price_and_send_raw_unsigned(block_number: T::BlockNumber) -> Result<(), &'static str> { + fn fetch_price_and_send_raw_unsigned( + block_number: BlockNumberFor, + ) -> Result<(), &'static str> { // Make sure we don't fetch the price if unsigned transaction is going to be rejected // anyway. let next_unsigned_at = >::get(); @@ -507,7 +509,7 @@ impl Pallet { /// A helper function to fetch the price, sign payload and send an unsigned transaction fn fetch_price_and_send_unsigned_for_any_account( - block_number: T::BlockNumber, + block_number: BlockNumberFor, ) -> Result<(), &'static str> { // Make sure we don't fetch the price if unsigned transaction is going to be rejected // anyway. @@ -537,7 +539,7 @@ impl Pallet { /// A helper function to fetch the price, sign payload and send an unsigned transaction fn fetch_price_and_send_unsigned_for_all_accounts( - block_number: T::BlockNumber, + block_number: BlockNumberFor, ) -> Result<(), &'static str> { // Make sure we don't fetch the price if unsigned transaction is going to be rejected // anyway. @@ -572,12 +574,12 @@ impl Pallet { fn fetch_price() -> Result { // We want to keep the offchain worker execution time reasonable, so we set a hard-coded // deadline to 2s to complete the external call. - // You can also wait idefinitely for the response, however you may still get a timeout + // You can also wait indefinitely for the response, however you may still get a timeout // coming from the host machine. let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(2_000)); // Initiate an external HTTP GET request. // This is using high-level wrappers from `sp_runtime`, for the low-level calls that - // you can find in `sp_io`. The API is trying to be similar to `reqwest`, but + // you can find in `sp_io`. The API is trying to be similar to `request`, but // since we are running in a custom WASM execution environment we can't simply // import the library here. let request = @@ -671,7 +673,7 @@ impl Pallet { } fn validate_transaction_parameters( - block_number: &T::BlockNumber, + block_number: &BlockNumberFor, new_price: &u32, ) -> TransactionValidity { // Now let's check if the transaction has any chance to succeed. diff --git a/frame/examples/offchain-worker/src/tests.rs b/frame/examples/offchain-worker/src/tests.rs index 2050d76a241f..203a59a8af03 100644 --- a/frame/examples/offchain-worker/src/tests.rs +++ b/frame/examples/offchain-worker/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,26 +27,21 @@ use sp_core::{ sr25519::Signature, H256, }; -use std::sync::Arc; -use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStore}; +use sp_keystore::{testing::MemoryKeystore, Keystore, KeystoreExt}; use sp_runtime::{ - testing::{Header, TestXt}, + testing::TestXt, traits::{BlakeTwo256, Extrinsic as ExtrinsicT, IdentifyAccount, IdentityLookup, Verify}, RuntimeAppPublic, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; // For testing the module, we construct a mock runtime. frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Example: example_offchain_worker::{Pallet, Call, Storage, Event, ValidateUnsigned}, } ); @@ -58,13 +53,12 @@ impl frame_system::Config for Test { type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = sp_core::sr25519::Public; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -205,18 +199,15 @@ fn should_submit_signed_transaction_on_chain() { let (offchain, offchain_state) = testing::TestOffchainExt::new(); let (pool, pool_state) = testing::TestTransactionPoolExt::new(); - let keystore = KeyStore::new(); - SyncCryptoStore::sr25519_generate_new( - &keystore, - crate::crypto::Public::ID, - Some(&format!("{}/hunter1", PHRASE)), - ) - .unwrap(); + let keystore = MemoryKeystore::new(); + keystore + .sr25519_generate_new(crate::crypto::Public::ID, Some(&format!("{}/hunter1", PHRASE))) + .unwrap(); let mut t = sp_io::TestExternalities::default(); t.register_extension(OffchainWorkerExt::new(offchain)); t.register_extension(TransactionPoolExt::new(pool)); - t.register_extension(KeystoreExt(Arc::new(keystore))); + t.register_extension(KeystoreExt::new(keystore)); price_oracle_response(&mut offchain_state.write()); @@ -239,23 +230,18 @@ fn should_submit_unsigned_transaction_on_chain_for_any_account() { let (offchain, offchain_state) = testing::TestOffchainExt::new(); let (pool, pool_state) = testing::TestTransactionPoolExt::new(); - let keystore = KeyStore::new(); - - SyncCryptoStore::sr25519_generate_new( - &keystore, - crate::crypto::Public::ID, - Some(&format!("{}/hunter1", PHRASE)), - ) - .unwrap(); + let keystore = MemoryKeystore::new(); - let public_key = *SyncCryptoStore::sr25519_public_keys(&keystore, crate::crypto::Public::ID) - .get(0) + keystore + .sr25519_generate_new(crate::crypto::Public::ID, Some(&format!("{}/hunter1", PHRASE))) .unwrap(); + let public_key = *keystore.sr25519_public_keys(crate::crypto::Public::ID).get(0).unwrap(); + let mut t = sp_io::TestExternalities::default(); t.register_extension(OffchainWorkerExt::new(offchain)); t.register_extension(TransactionPoolExt::new(pool)); - t.register_extension(KeystoreExt(Arc::new(keystore))); + t.register_extension(KeystoreExt::new(keystore)); price_oracle_response(&mut offchain_state.write()); @@ -283,7 +269,7 @@ fn should_submit_unsigned_transaction_on_chain_for_any_account() { let signature_valid = ::Public, - ::BlockNumber, + frame_system::pallet_prelude::BlockNumberFor, > as SignedPayload>::verify::(&price_payload, signature); assert!(signature_valid); @@ -298,23 +284,18 @@ fn should_submit_unsigned_transaction_on_chain_for_all_accounts() { let (offchain, offchain_state) = testing::TestOffchainExt::new(); let (pool, pool_state) = testing::TestTransactionPoolExt::new(); - let keystore = KeyStore::new(); + let keystore = MemoryKeystore::new(); - SyncCryptoStore::sr25519_generate_new( - &keystore, - crate::crypto::Public::ID, - Some(&format!("{}/hunter1", PHRASE)), - ) - .unwrap(); - - let public_key = *SyncCryptoStore::sr25519_public_keys(&keystore, crate::crypto::Public::ID) - .get(0) + keystore + .sr25519_generate_new(crate::crypto::Public::ID, Some(&format!("{}/hunter1", PHRASE))) .unwrap(); + let public_key = *keystore.sr25519_public_keys(crate::crypto::Public::ID).get(0).unwrap(); + let mut t = sp_io::TestExternalities::default(); t.register_extension(OffchainWorkerExt::new(offchain)); t.register_extension(TransactionPoolExt::new(pool)); - t.register_extension(KeystoreExt(Arc::new(keystore))); + t.register_extension(KeystoreExt::new(keystore)); price_oracle_response(&mut offchain_state.write()); @@ -342,7 +323,7 @@ fn should_submit_unsigned_transaction_on_chain_for_all_accounts() { let signature_valid = ::Public, - ::BlockNumber, + frame_system::pallet_prelude::BlockNumberFor, > as SignedPayload>::verify::(&price_payload, signature); assert!(signature_valid); @@ -355,12 +336,12 @@ fn should_submit_raw_unsigned_transaction_on_chain() { let (offchain, offchain_state) = testing::TestOffchainExt::new(); let (pool, pool_state) = testing::TestTransactionPoolExt::new(); - let keystore = KeyStore::new(); + let keystore = MemoryKeystore::new(); let mut t = sp_io::TestExternalities::default(); t.register_extension(OffchainWorkerExt::new(offchain)); t.register_extension(TransactionPoolExt::new(pool)); - t.register_extension(KeystoreExt(Arc::new(keystore))); + t.register_extension(KeystoreExt::new(keystore)); price_oracle_response(&mut offchain_state.write()); diff --git a/frame/examples/split/Cargo.toml b/frame/examples/split/Cargo.toml new file mode 100644 index 000000000000..21ecf89330dd --- /dev/null +++ b/frame/examples/split/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "pallet-example-split" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "MIT-0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME example splitted pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } + +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } + +sp-io = { version = "23.0.0", default-features = false, path = "../../../primitives/io" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../../benchmarking" } + +[dev-dependencies] +sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } + +[features] +default = ["std"] +std = [ + "codec/std", + "log/std", + "scale-info/std", + + "frame-support/std", + "frame-system/std", + + "sp-io/std", + "sp-std/std", + + "frame-benchmarking?/std", +] +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/examples/split/README.md b/frame/examples/split/README.md new file mode 100644 index 000000000000..413ce9b913cb --- /dev/null +++ b/frame/examples/split/README.md @@ -0,0 +1,10 @@ + +# Basic Example For Splitting A Pallet +A simple example of a FRAME pallet demonstrating the ability to split sections across multiple +files. + +Note that this is purely experimental at this point. + +Run `cargo doc --package pallet-example-split --open` to view this pallet's documentation. + +License: MIT-0 diff --git a/frame/examples/split/src/benchmarking.rs b/frame/examples/split/src/benchmarking.rs new file mode 100644 index 000000000000..5a5030093720 --- /dev/null +++ b/frame/examples/split/src/benchmarking.rs @@ -0,0 +1,54 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Benchmarking setup for pallet-example-split + +// Only enable this module for benchmarking. +#![cfg(feature = "runtime-benchmarks")] +use super::*; + +#[allow(unused)] +use crate::Pallet as Template; +use frame_benchmarking::v2::*; +use frame_system::RawOrigin; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn do_something() { + let value = 100u32.into(); + let caller: T::AccountId = whitelisted_caller(); + #[extrinsic_call] + do_something(RawOrigin::Signed(caller), value); + + assert_eq!(Something::::get(), Some(value)); + } + + #[benchmark] + fn cause_error() { + Something::::put(100u32); + let caller: T::AccountId = whitelisted_caller(); + #[extrinsic_call] + cause_error(RawOrigin::Signed(caller)); + + assert_eq!(Something::::get(), Some(101u32)); + } + + impl_benchmark_test_suite!(Template, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/examples/split/src/events.rs b/frame/examples/split/src/events.rs new file mode 100644 index 000000000000..7560766bacb3 --- /dev/null +++ b/frame/examples/split/src/events.rs @@ -0,0 +1,31 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use frame_support::pallet_macros::*; + +/// A [`pallet_section`] that defines the events for a pallet. +/// This can later be imported into the pallet using [`import_section`]. +#[pallet_section] +mod events { + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// Event documentation should end with an array that provides descriptive names for event + /// parameters. [something, who] + SomethingStored { something: u32, who: T::AccountId }, + } +} diff --git a/frame/examples/split/src/lib.rs b/frame/examples/split/src/lib.rs new file mode 100644 index 000000000000..74d2e0cc24b7 --- /dev/null +++ b/frame/examples/split/src/lib.rs @@ -0,0 +1,123 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Split Example Pallet +//! +//! **This pallet serves as an example and is not meant to be used in production.** +//! +//! A FRAME pallet demonstrating the ability to split sections across multiple files. +//! +//! Note that this is purely experimental at this point. + +#![cfg_attr(not(feature = "std"), no_std)] + +// Re-export pallet items so that they can be accessed from the crate namespace. +pub use pallet::*; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +mod events; + +pub mod weights; +pub use weights::*; + +use frame_support::pallet_macros::*; + +/// Imports a [`pallet_section`] defined at [`events::events`]. +/// This brings the events defined in that section into the pallet's namespace. +#[import_section(events::events)] +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + /// Configure the pallet by specifying the parameters and types on which it depends. + #[pallet::config] + pub trait Config: frame_system::Config { + /// Because this pallet emits events, it depends on the runtime's definition of an event. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// Type representing the weight of this pallet + type WeightInfo: WeightInfo; + } + + // The pallet's runtime storage items. + #[pallet::storage] + pub type Something = StorageValue<_, u32>; + + // Errors inform users that something went wrong. + #[pallet::error] + pub enum Error { + /// Error names should be descriptive. + NoneValue, + /// Errors should have helpful documentation associated with them. + StorageOverflow, + } + + // Dispatchable functions allows users to interact with the pallet and invoke state changes. + // These functions materialize as "extrinsics", which are often compared to transactions. + // Dispatchable functions must be annotated with a weight and must return a DispatchResult. + #[pallet::call] + impl Pallet { + /// An example dispatchable that takes a singles value as a parameter, writes the value to + /// storage and emits an event. This function must be dispatched by a signed extrinsic. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::do_something())] + pub fn do_something(origin: OriginFor, something: u32) -> DispatchResult { + // Check that the extrinsic was signed and get the signer. + // This function will return an error if the extrinsic is not signed. + let who = ensure_signed(origin)?; + + // Update storage. + >::put(something); + + // Emit an event. + Self::deposit_event(Event::SomethingStored { something, who }); + // Return a successful DispatchResultWithPostInfo + Ok(()) + } + + /// An example dispatchable that may throw a custom error. + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::cause_error())] + pub fn cause_error(origin: OriginFor) -> DispatchResult { + let _who = ensure_signed(origin)?; + + // Read a value from storage. + match >::get() { + // Return an error if the value has not been set. + None => return Err(Error::::NoneValue.into()), + Some(old) => { + // Increment the value read from storage; will error in the event of overflow. + let new = old.checked_add(1).ok_or(Error::::StorageOverflow)?; + // Update the value in storage with the incremented result. + >::put(new); + Ok(()) + }, + } + } + } +} diff --git a/frame/examples/split/src/mock.rs b/frame/examples/split/src/mock.rs new file mode 100644 index 000000000000..bee3633ef68f --- /dev/null +++ b/frame/examples/split/src/mock.rs @@ -0,0 +1,55 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate as pallet_template; +use frame_support::{derive_impl, sp_runtime::BuildStorage}; +use sp_core::ConstU64; + +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + TemplatePallet: pallet_template, + } +); + +/// Using a default config for [`frame_system`] in tests. See `default-config` example for more +/// details. +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type Block = Block; + type BlockHashCount = ConstU64<10>; + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type OnSetCode = (); +} + +impl pallet_template::Config for Test { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +// Build genesis storage according to the mock runtime. +pub fn new_test_ext() -> sp_io::TestExternalities { + frame_system::GenesisConfig::::default().build_storage().unwrap().into() +} diff --git a/frame/examples/split/src/tests.rs b/frame/examples/split/src/tests.rs new file mode 100644 index 000000000000..1d4b6dfcff9d --- /dev/null +++ b/frame/examples/split/src/tests.rs @@ -0,0 +1,44 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{mock::*, Error, Event, Something}; +use frame_support::{assert_noop, assert_ok}; + +#[test] +fn it_works_for_default_value() { + new_test_ext().execute_with(|| { + // Go past genesis block so events get deposited + System::set_block_number(1); + // Dispatch a signed extrinsic. + assert_ok!(TemplatePallet::do_something(RuntimeOrigin::signed(1), 42)); + // Read pallet storage and assert an expected result. + assert_eq!(Something::::get(), Some(42)); + // Assert that the correct event was deposited + System::assert_last_event(Event::SomethingStored { something: 42, who: 1 }.into()); + }); +} + +#[test] +fn correct_error_for_none_value() { + new_test_ext().execute_with(|| { + // Ensure the expected error is thrown when no value is present. + assert_noop!( + TemplatePallet::cause_error(RuntimeOrigin::signed(1)), + Error::::NoneValue + ); + }); +} diff --git a/frame/examples/split/src/weights.rs b/frame/examples/split/src/weights.rs new file mode 100644 index 000000000000..4219ce1e2697 --- /dev/null +++ b/frame/examples/split/src/weights.rs @@ -0,0 +1,91 @@ + +//! 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 +// --execution=wasm +// --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: TemplatePallet Something (r:0 w:1) + /// Proof: TemplatePallet 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: TemplatePallet Something (r:1 w:1) + /// Proof: TemplatePallet 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: TemplatePallet Something (r:0 w:1) + /// Proof: TemplatePallet 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: TemplatePallet Something (r:1 w:1) + /// Proof: TemplatePallet 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/frame/examples/src/lib.rs b/frame/examples/src/lib.rs new file mode 100644 index 000000000000..d1cd32bb50f2 --- /dev/null +++ b/frame/examples/src/lib.rs @@ -0,0 +1,40 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # FRAME Pallet Examples +//! +//! This crate contains examples of FRAME pallets. It is not intended to be used in production. +//! +//! ## Pallets +//! +//! - [**`pallet_example_basic`**](./basic): A simple example of a FRAME pallet demonstrating +//! concepts, APIs and structures common to most FRAME runtimes. +//! +//! - [**`pallet_example_offchain_worker`**](./offchain-worker): A simple example of a FRAME pallet +//! demonstrating concepts, APIs and structures common to most offchain workers. +//! +//! - [**`pallet-default-config-example`**](./default-config): A simple example of a FRAME pallet +//! demonstrating the simpler way to implement `Config` trait of pallets. +//! +//! - [**`pallet-dev-mode`**](./dev-mode): A simple example of a FRAME pallet demonstrating the ease +//! of requirements for a pallet in dev mode. +//! +//! - [**`pallet-example-kitchensink`**](./kitchensink): A simple example of a FRAME pallet +//! demonstrating a catalog of the the FRAME macros and their various syntax options. +//! +//! - [**`pallet-example-split`**](./split): A simple example of a FRAME pallet demonstrating the +//! ability to split sections across multiple files. diff --git a/frame/executive/Cargo.toml b/frame/executive/Cargo.toml index 1636cb3929d9..6c7651358129 100644 --- a/frame/executive/Cargo.toml +++ b/frame/executive/Cargo.toml @@ -13,27 +13,27 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } frame-try-runtime = { version = "0.10.0-dev", default-features = false, path = "../try-runtime", optional = true } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } -sp-tracing = { version = "6.0.0", default-features = false, path = "../../primitives/tracing" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-tracing = { version = "10.0.0", default-features = false, path = "../../primitives/tracing" } [dev-dependencies] -array-bytes = "4.1" +array-bytes = "6.1" pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-transaction-payment = { version = "4.0.0-dev", path = "../transaction-payment" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } sp-inherents = { version = "4.0.0-dev", path = "../../primitives/inherents" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } -sp-version = { version = "5.0.0", path = "../../primitives/version" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } +sp-version = { version = "22.0.0", path = "../../primitives/version" } [features] default = ["std"] @@ -42,6 +42,7 @@ std = [ "codec/std", "frame-support/std", "frame-system/std", + "frame-try-runtime/std", "scale-info/std", "sp-core/std", "sp-io/std", diff --git a/frame/executive/src/lib.rs b/frame/executive/src/lib.rs index b454d1e276cc..4e24717a39e9 100644 --- a/frame/executive/src/lib.rs +++ b/frame/executive/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -126,6 +126,7 @@ use frame_support::{ }, weights::Weight, }; +use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::{ generic::Digest, traits::{ @@ -137,6 +138,12 @@ use sp_runtime::{ }; use sp_std::{marker::PhantomData, prelude::*}; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + +#[allow(dead_code)] +const LOG_TARGET: &str = "runtime::executive"; + pub type CheckedOf = >::Checked; pub type CallOf = as Applyable>::Call; pub type OriginOf = as Dispatchable>::RuntimeOrigin; @@ -172,14 +179,17 @@ pub struct Executive< impl< System: frame_system::Config + EnsureInherentsAreFirst, - Block: traits::Block
, + Block: traits::Block< + Header = frame_system::pallet_prelude::HeaderFor, + Hash = System::Hash, + >, Context: Default, UnsignedValidator, AllPalletsWithSystem: OnRuntimeUpgrade - + OnInitialize - + OnIdle - + OnFinalize - + OffchainWorker, + + OnInitialize> + + OnIdle> + + OnFinalize> + + OffchainWorker>, COnRuntimeUpgrade: OnRuntimeUpgrade, > ExecuteBlock for Executive @@ -206,15 +216,18 @@ where #[cfg(feature = "try-runtime")] impl< System: frame_system::Config + EnsureInherentsAreFirst, - Block: traits::Block
, + Block: traits::Block< + Header = frame_system::pallet_prelude::HeaderFor, + Hash = System::Hash, + >, Context: Default, UnsignedValidator, AllPalletsWithSystem: OnRuntimeUpgrade - + OnInitialize - + OnIdle - + OnFinalize - + OffchainWorker - + frame_support::traits::TryState, + + OnInitialize> + + OnIdle> + + OnFinalize> + + OffchainWorker> + + frame_support::traits::TryState>, COnRuntimeUpgrade: OnRuntimeUpgrade, > Executive where @@ -240,7 +253,7 @@ where select: frame_try_runtime::TryStateSelect, ) -> Result { frame_support::log::info!( - target: "frame::executive", + target: LOG_TARGET, "try-runtime: executing block #{:?} / state root check: {:?} / signature check: {:?} / try-state-select: {:?}", block.header().number(), state_root_check, @@ -277,7 +290,7 @@ where for e in extrinsics { if let Err(err) = try_apply_extrinsic(e.clone()) { frame_support::log::error!( - target: "runtime::executive", "executing transaction {:?} failed due to {:?}. Aborting the rest of the block execution.", + target: LOG_TARGET, "executing transaction {:?} failed due to {:?}. Aborting the rest of the block execution.", e, err, ); @@ -291,12 +304,11 @@ where // run the try-state checks of all pallets, ensuring they don't alter any state. let _guard = frame_support::StorageNoopGuard::default(); - >::try_state( - *header.number(), - select, - ) + , + >>::try_state(*header.number(), select) .map_err(|e| { - frame_support::log::error!(target: "runtime::executive", "failure: {:?}", e); + frame_support::log::error!(target: LOG_TARGET, "failure: {:?}", e); e })?; drop(_guard); @@ -326,6 +338,12 @@ where ); } + frame_support::log::info!( + target: LOG_TARGET, + "try-runtime: Block #{:?} successfully executed", + header.number(), + ); + Ok(frame_system::Pallet::::block_weight().total()) } @@ -335,10 +353,12 @@ where /// `true`. Also, if set to `true`, it runs the `pre_upgrade` and `post_upgrade` hooks. pub fn try_runtime_upgrade( checks: frame_try_runtime::UpgradeCheckSelect, - ) -> Result { + ) -> Result { if checks.try_state() { let _guard = frame_support::StorageNoopGuard::default(); - >::try_state( + , + >>::try_state( frame_system::Pallet::::block_number(), frame_try_runtime::TryStateSelect::All, )?; @@ -351,7 +371,9 @@ where if checks.try_state() { let _guard = frame_support::StorageNoopGuard::default(); - >::try_state( + , + >>::try_state( frame_system::Pallet::::block_number(), frame_try_runtime::TryStateSelect::All, )?; @@ -363,14 +385,17 @@ where impl< System: frame_system::Config + EnsureInherentsAreFirst, - Block: traits::Block
, + Block: traits::Block< + Header = frame_system::pallet_prelude::HeaderFor, + Hash = System::Hash, + >, Context: Default, UnsignedValidator, AllPalletsWithSystem: OnRuntimeUpgrade - + OnInitialize - + OnIdle - + OnFinalize - + OffchainWorker, + + OnInitialize> + + OnIdle> + + OnFinalize> + + OffchainWorker>, COnRuntimeUpgrade: OnRuntimeUpgrade, > Executive where @@ -387,14 +412,14 @@ where } /// Start the execution of a particular block. - pub fn initialize_block(header: &System::Header) { + pub fn initialize_block(header: &frame_system::pallet_prelude::HeaderFor) { sp_io::init_tracing(); sp_tracing::enter_span!(sp_tracing::Level::TRACE, "init_block"); let digests = Self::extract_pre_digest(header); Self::initialize_block_impl(header.number(), header.parent_hash(), &digests); } - fn extract_pre_digest(header: &System::Header) -> Digest { + fn extract_pre_digest(header: &frame_system::pallet_prelude::HeaderFor) -> Digest { let mut digest = ::default(); header.digest().logs().iter().for_each(|d| { if d.as_pre_runtime().is_some() { @@ -405,7 +430,7 @@ where } fn initialize_block_impl( - block_number: &System::BlockNumber, + block_number: &BlockNumberFor, parent_hash: &System::Hash, digest: &Digest, ) { @@ -420,7 +445,7 @@ where } >::initialize(block_number, parent_hash, digest); weight = weight.saturating_add(, >>::on_initialize(*block_number)); weight = weight.saturating_add( >::get().base_block, @@ -455,8 +480,8 @@ where // Check that `parent_hash` is correct. let n = *header.number(); assert!( - n > System::BlockNumber::zero() && - >::block_hash(n - System::BlockNumber::one()) == + n > BlockNumberFor::::zero() && + >::block_hash(n - BlockNumberFor::::one()) == *header.parent_hash(), "Parent hash should be valid.", ); @@ -477,16 +502,10 @@ where // any initial checks Self::initial_checks(&block); - let signature_batching = sp_runtime::SignatureBatching::start(); - // execute extrinsics let (header, extrinsics) = block.deconstruct(); Self::execute_extrinsics_with_book_keeping(extrinsics, *header.number()); - if !signature_batching.verify() { - panic!("Signature verification failed."); - } - // any final checks Self::final_checks(&header); } @@ -512,7 +531,7 @@ where /// Finalize the block - it is up the caller to ensure that all header fields are valid /// except state-root. - pub fn finalize_block() -> System::Header { + pub fn finalize_block() -> frame_system::pallet_prelude::HeaderFor { sp_io::init_tracing(); sp_tracing::enter_span!(sp_tracing::Level::TRACE, "finalize_block"); >::note_finished_extrinsics(); @@ -529,7 +548,7 @@ where let remaining_weight = max_weight.saturating_sub(weight.total()); if remaining_weight.all_gt(Weight::zero()) { - let used_weight = >::on_idle( + let used_weight = >>::on_idle( block_number, remaining_weight, ); @@ -539,7 +558,7 @@ where ); } - >::on_finalize(block_number); + >>::on_finalize(block_number); } /// Apply extrinsic outside of the block execution function. @@ -579,7 +598,7 @@ where Ok(r.map(|_| ()).map_err(|e| e.error)) } - fn final_checks(header: &System::Header) { + fn final_checks(header: &frame_system::pallet_prelude::HeaderFor) { sp_tracing::enter_span!(sp_tracing::Level::TRACE, "final_checks"); // remove temporaries let new_header = >::finalize(); @@ -651,7 +670,7 @@ where } /// Start an offchain worker and generate extrinsics. - pub fn offchain_worker(header: &System::Header) { + pub fn offchain_worker(header: &frame_system::pallet_prelude::HeaderFor) { sp_io::init_tracing(); // We need to keep events available for offchain workers, // hence we initialize the block manually. @@ -665,7 +684,7 @@ where // as well. frame_system::BlockHash::::insert(header.number(), header.hash()); - >::offchain_worker( + >>::offchain_worker( *header.number(), ) } @@ -683,30 +702,26 @@ mod tests { transaction_validity::{ InvalidTransaction, TransactionValidityError, UnknownTransaction, ValidTransaction, }, - DispatchError, + BuildStorage, DispatchError, }; use frame_support::{ assert_err, parameter_types, - traits::{ - ConstU32, ConstU64, ConstU8, Currency, LockIdentifier, LockableCurrency, - WithdrawReasons, - }, + traits::{fungible, ConstU32, ConstU64, ConstU8, Currency}, weights::{ConstantMultiplier, IdentityFee, RuntimeDbWeight, Weight, WeightToFee}, }; - use frame_system::{Call as SystemCall, ChainContext, LastRuntimeUpgradeInfo}; + use frame_system::{ChainContext, LastRuntimeUpgradeInfo}; use pallet_balances::Call as BalancesCall; use pallet_transaction_payment::CurrencyAdapter; const TEST_KEY: &[u8] = b":test:key:"; - #[frame_support::pallet] + #[frame_support::pallet(dev_mode)] mod custom { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::config] @@ -716,77 +731,65 @@ mod tests { impl Hooks> for Pallet { // module hooks. // one with block number arg and one without - fn on_initialize(n: T::BlockNumber) -> Weight { + fn on_initialize(n: BlockNumberFor) -> Weight { println!("on_initialize({})", n); - Weight::from_ref_time(175) + Weight::from_parts(175, 0) } - fn on_idle(n: T::BlockNumber, remaining_weight: Weight) -> Weight { + fn on_idle(n: BlockNumberFor, remaining_weight: Weight) -> Weight { println!("on_idle{}, {})", n, remaining_weight); - Weight::from_ref_time(175) + Weight::from_parts(175, 0) } - fn on_finalize(n: T::BlockNumber) { + fn on_finalize(n: BlockNumberFor) { println!("on_finalize({})", n); } fn on_runtime_upgrade() -> Weight { sp_io::storage::set(super::TEST_KEY, "module".as_bytes()); - Weight::from_ref_time(200) + Weight::from_parts(200, 0) } - fn offchain_worker(n: T::BlockNumber) { - assert_eq!(T::BlockNumber::from(1u32), n); + fn offchain_worker(n: BlockNumberFor) { + assert_eq!(BlockNumberFor::::from(1u32), n); } } #[pallet::call] impl Pallet { - #[pallet::call_index(0)] - #[pallet::weight(100)] pub fn some_function(origin: OriginFor) -> DispatchResult { // NOTE: does not make any different. frame_system::ensure_signed(origin)?; Ok(()) } - #[pallet::call_index(1)] #[pallet::weight((200, DispatchClass::Operational))] pub fn some_root_operation(origin: OriginFor) -> DispatchResult { frame_system::ensure_root(origin)?; Ok(()) } - #[pallet::call_index(2)] - #[pallet::weight(0)] pub fn some_unsigned_message(origin: OriginFor) -> DispatchResult { frame_system::ensure_none(origin)?; Ok(()) } - #[pallet::call_index(3)] - #[pallet::weight(0)] pub fn allowed_unsigned(origin: OriginFor) -> DispatchResult { frame_system::ensure_root(origin)?; Ok(()) } - #[pallet::call_index(4)] - #[pallet::weight(0)] pub fn unallowed_unsigned(origin: OriginFor) -> DispatchResult { frame_system::ensure_root(origin)?; Ok(()) } - #[pallet::call_index(5)] #[pallet::weight((0, DispatchClass::Mandatory))] pub fn inherent_call(origin: OriginFor) -> DispatchResult { frame_system::ensure_none(origin)?; Ok(()) } - #[pallet::call_index(6)] - #[pallet::weight(0)] pub fn calculate_storage_root(_origin: OriginFor) -> DispatchResult { let root = sp_io::storage::root(sp_runtime::StateVersion::V1); sp_io::storage::set("storage_root".as_bytes(), &root); @@ -838,12 +841,9 @@ mod tests { } frame_support::construct_runtime!( - pub enum Runtime where - Block = TestBlock, - NodeBlock = TestBlock, - UncheckedExtrinsic = TestUncheckedExtrinsic + pub struct Runtime { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, Custom: custom::{Pallet, Call, ValidateUnsigned, Inherent}, @@ -853,9 +853,9 @@ mod tests { parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::builder() - .base_block(Weight::from_ref_time(10)) - .for_class(DispatchClass::all(), |weights| weights.base_extrinsic = Weight::from_ref_time(5)) - .for_class(DispatchClass::non_mandatory(), |weights| weights.max_total = Weight::from_ref_time(1024).set_proof_size(u64::MAX).into()) + .base_block(Weight::from_parts(10, 0)) + .for_class(DispatchClass::all(), |weights| weights.base_extrinsic = Weight::from_parts(5, 0)) + .for_class(DispatchClass::non_mandatory(), |weights| weights.max_total = Weight::from_parts(1024, u64::MAX).into()) .build_or_panic(); pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 10, @@ -868,14 +868,13 @@ mod tests { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; - type BlockNumber = u64; type Hash = sp_core::H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = TestBlock; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = RuntimeVersion; @@ -900,6 +899,10 @@ mod tests { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = ConstU32<1>; + type RuntimeHoldReason = (); + type MaxHolds = ConstU32<1>; } parameter_types! { @@ -935,7 +938,6 @@ mod tests { ); type TestXt = sp_runtime::testing::TestXt; type TestBlock = Block; - type TestUncheckedExtrinsic = TestXt; // Will contain `true` when the custom runtime logic was called. const CUSTOM_ON_RUNTIME_KEY: &[u8] = b":custom:on_runtime"; @@ -946,7 +948,7 @@ mod tests { sp_io::storage::set(TEST_KEY, "custom_upgrade".as_bytes()); sp_io::storage::set(CUSTOM_ON_RUNTIME_KEY, &true.encode()); System::deposit_event(frame_system::Event::CodeUpdated); - Weight::from_ref_time(100) + Weight::from_parts(100, 0) } } @@ -973,12 +975,12 @@ mod tests { } fn call_transfer(dest: u64, value: u64) -> RuntimeCall { - RuntimeCall::Balances(BalancesCall::transfer { dest, value }) + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value }) } #[test] fn balance_transfer_dispatch_works() { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 211)] } .assimilate_storage(&mut t) .unwrap(); @@ -1006,7 +1008,7 @@ mod tests { } fn new_test_ext(balance_factor: Balance) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 111 * balance_factor)] } .assimilate_storage(&mut t) .unwrap(); @@ -1014,7 +1016,7 @@ mod tests { } fn new_test_ext_v0(balance_factor: Balance) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 111 * balance_factor)] } .assimilate_storage(&mut t) .unwrap(); @@ -1026,13 +1028,13 @@ mod tests { block_import_works_inner( new_test_ext_v0(1), array_bytes::hex_n_into_unchecked( - "216e61b2689d1243eb56d89c9084db48e50ebebc4871d758db131432c675d7c0", + "65e953676859e7a33245908af7ad3637d6861eb90416d433d485e95e2dd174a1", ), ); block_import_works_inner( new_test_ext(1), array_bytes::hex_n_into_unchecked( - "4738b4c0aab02d6ddfa62a2a6831ccc975a9f978f7db8d7ea8e68eba8639530a", + "5a19b3d6fdb7241836349fdcbe2d9df4d4f945b949d979e31ad50bff1cbcd1c2", ), ); } @@ -1117,14 +1119,14 @@ mod tests { let mut t = new_test_ext(10000); // given: TestXt uses the encoded len as fixed Len: let xt = TestXt::new( - RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }), sign_extra(1, 0, 0), ); let encoded = xt.encode(); let encoded_len = encoded.len() as u64; // on_initialize weight + base block execution weight let block_weights = ::BlockWeights::get(); - let base_block_weight = Weight::from_ref_time(175) + block_weights.base_block; + let base_block_weight = Weight::from_parts(175, 0) + block_weights.base_block; let limit = block_weights.get(DispatchClass::Normal).max_total.unwrap() - base_block_weight; let num_to_exhaust_block = limit.ref_time() / (encoded_len + 5); t.execute_with(|| { @@ -1140,7 +1142,10 @@ mod tests { for nonce in 0..=num_to_exhaust_block { let xt = TestXt::new( - RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer_allow_death { + dest: 33, + value: 0, + }), sign_extra(1, nonce.into(), 0), ); let res = Executive::apply_extrinsic(xt); @@ -1149,7 +1154,7 @@ mod tests { assert_eq!( >::block_weight().total(), //--------------------- on_initialize + block_execution + extrinsic_base weight - Weight::from_ref_time((encoded_len + 5) * (nonce + 1)) + base_block_weight, + Weight::from_parts((encoded_len + 5) * (nonce + 1), 0) + base_block_weight, ); assert_eq!( >::extrinsic_index(), @@ -1165,22 +1170,22 @@ mod tests { #[test] fn block_weight_and_size_is_stored_per_tx() { let xt = TestXt::new( - RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }), sign_extra(1, 0, 0), ); let x1 = TestXt::new( - RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }), sign_extra(1, 1, 0), ); let x2 = TestXt::new( - RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }), sign_extra(1, 2, 0), ); let len = xt.clone().encode().len() as u32; let mut t = new_test_ext(1); t.execute_with(|| { // Block execution weight + on_initialize weight from custom module - let base_block_weight = Weight::from_ref_time(175) + + let base_block_weight = Weight::from_parts(175, 0) + ::BlockWeights::get().base_block; Executive::initialize_block(&Header::new( @@ -1199,7 +1204,7 @@ mod tests { assert!(Executive::apply_extrinsic(x2.clone()).unwrap().is_ok()); // default weight for `TestXt` == encoded length. - let extrinsic_weight = Weight::from_ref_time(len as u64) + + let extrinsic_weight = Weight::from_parts(len as u64, 0) + ::BlockWeights::get() .get(DispatchClass::Normal) .base_extrinsic; @@ -1259,50 +1264,30 @@ mod tests { } #[test] - fn can_pay_for_tx_fee_on_full_lock() { - let id: LockIdentifier = *b"0 "; - let execute_with_lock = |lock: WithdrawReasons| { - let mut t = new_test_ext(1); - t.execute_with(|| { - as LockableCurrency>::set_lock( - id, &1, 110, lock, - ); - let xt = TestXt::new( - RuntimeCall::System(SystemCall::remark { remark: vec![1u8] }), - sign_extra(1, 0, 0), - ); - let weight = xt.get_dispatch_info().weight + - ::BlockWeights::get() - .get(DispatchClass::Normal) - .base_extrinsic; - let fee: Balance = - ::WeightToFee::weight_to_fee( - &weight, - ); - Executive::initialize_block(&Header::new( - 1, - H256::default(), - H256::default(), - [69u8; 32].into(), - Digest::default(), - )); - - if lock == WithdrawReasons::except(WithdrawReasons::TRANSACTION_PAYMENT) { - assert!(Executive::apply_extrinsic(xt).unwrap().is_ok()); - // tx fee has been deducted. - assert_eq!(>::total_balance(&1), 111 - fee); - } else { - assert_eq!( - Executive::apply_extrinsic(xt), - Err(InvalidTransaction::Payment.into()), - ); - assert_eq!(>::total_balance(&1), 111); - } - }); - }; + fn can_not_pay_for_tx_fee_on_full_lock() { + let mut t = new_test_ext(1); + t.execute_with(|| { + as fungible::MutateFreeze>::set_freeze( + &(), + &1, + 110, + ) + .unwrap(); + let xt = TestXt::new( + RuntimeCall::System(frame_system::Call::remark { remark: vec![1u8] }), + sign_extra(1, 0, 0), + ); + Executive::initialize_block(&Header::new( + 1, + H256::default(), + H256::default(), + [69u8; 32].into(), + Digest::default(), + )); - execute_with_lock(WithdrawReasons::all()); - execute_with_lock(WithdrawReasons::except(WithdrawReasons::TRANSACTION_PAYMENT)); + assert_eq!(Executive::apply_extrinsic(xt), Err(InvalidTransaction::Payment.into()),); + assert_eq!(>::total_balance(&1), 111); + }); } #[test] @@ -1315,7 +1300,7 @@ mod tests { // the `on_initialize` weight defined in the custom test module. assert_eq!( >::block_weight().total(), - Weight::from_ref_time(175 + 175 + 10) + Weight::from_parts(175 + 175 + 10, 0) ); }) } @@ -1444,7 +1429,7 @@ mod tests { #[test] fn custom_runtime_upgrade_is_called_when_using_execute_block_trait() { let xt = TestXt::new( - RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }), sign_extra(1, 0, 0), ); @@ -1570,7 +1555,7 @@ mod tests { #[should_panic(expected = "Invalid inherent position for extrinsic at index 1")] fn invalid_inherent_position_fail() { let xt1 = TestXt::new( - RuntimeCall::Balances(BalancesCall::transfer { dest: 33, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 33, value: 0 }), sign_extra(1, 0, 0), ); let xt2 = TestXt::new(RuntimeCall::Custom(custom::Call::inherent_call {}), None); diff --git a/frame/fast-unstake/Cargo.toml b/frame/fast-unstake/Cargo.toml index 36b4c3a5ec1c..c628da7123f2 100644 --- a/frame/fast-unstake/Cargo.toml +++ b/frame/fast-unstake/Cargo.toml @@ -3,7 +3,7 @@ name = "pallet-fast-unstake" version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" -license = "Unlicense" +license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "FRAME fast unstake pallet" @@ -12,26 +12,28 @@ description = "FRAME fast unstake pallet" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } sp-staking = { default-features = false, path = "../../primitives/staking" } frame-election-provider-support = { default-features = false, path = "../election-provider-support" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +docify = "0.2.0" + [dev-dependencies] pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } pallet-staking = { path = "../staking" } pallet-balances = { path = "../balances" } pallet-timestamp = { path = "../timestamp" } diff --git a/frame/fast-unstake/src/benchmarking.rs b/frame/fast-unstake/src/benchmarking.rs index de85502f7b41..5ec997e8eaa2 100644 --- a/frame/fast-unstake/src/benchmarking.rs +++ b/frame/fast-unstake/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ #![cfg(feature = "runtime-benchmarks")] use crate::{types::*, Pallet as FastUnstake, *}; -use frame_benchmarking::{benchmarks, whitelist_account}; +use frame_benchmarking::v1::{benchmarks, whitelist_account, BenchmarkError}; use frame_support::{ assert_ok, traits::{Currency, EnsureOrigin, Get, Hooks}, @@ -192,7 +192,8 @@ benchmarks! { } control { - let origin = ::ControlOrigin::successful_origin(); + let origin = ::ControlOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; } : _(origin, T::MaxErasToCheckPerBlock::get()) verify {} diff --git a/frame/fast-unstake/src/lib.rs b/frame/fast-unstake/src/lib.rs index 82454a4b8ac7..39783271e656 100644 --- a/frame/fast-unstake/src/lib.rs +++ b/frame/fast-unstake/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,37 +15,100 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! A pallet that's designed to JUST do the following: +//! > Made with *Substrate*, for *Polkadot*. //! -//! If a nominator is not exposed in any `ErasStakers` (i.e. "has not actively backed any -//! validators in the last `BondingDuration` days"), then they can register themselves in this -//! pallet, unstake faster than having to wait an entire bonding duration. +//! [![github]](https://github.com/paritytech/substrate/frame/fast-unstake) - +//! [![polkadot]](https://polkadot.network) //! -//! Appearing in the exposure of a validator means being exposed equal to that validator from the -//! point of view of the staking system. This usually means earning rewards with the validator, and -//! also being at the risk of slashing with the validator. This is equivalent to the "Active -//! Nominator" role explained in the -//! [February Staking Update](https://polkadot.network/blog/staking-update-february-2022/). +//! [polkadot]: https://img.shields.io/badge/polkadot-E6007A?style=for-the-badge&logo=polkadot&logoColor=white +//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github //! -//! This pallet works off the basis of `on_idle`, meaning that it provides no guarantee about when -//! it will succeed, if at all. Moreover, the queue implementation is unordered. In case of -//! congestion, no FIFO ordering is provided. +//! # Fast Unstake Pallet +//! +//! A pallet to allow participants of the staking system (represented by [`Config::Staking`], being +//! [`StakingInterface`]) to unstake quicker, if and only if they meet the condition of not being +//! exposed to any slashes. +//! +//! ## Overview +//! +//! If a nominator is not exposed anywhere in the staking system, checked via +//! [`StakingInterface::is_exposed_in_era`] (i.e. "has not actively backed any validators in the +//! last [`StakingInterface::bonding_duration`] days"), then they can register themselves in this +//! pallet and unstake faster than having to wait an entire bonding duration. +//! +//! *Being exposed with validator* from the point of view of the staking system means earning +//! rewards with the validator, and also being at the risk of slashing with the validator. This is +//! equivalent to the "Active Nominator" role explained in +//! [here](https://polkadot.network/blog/staking-update-february-2022/). //! //! Stakers who are certain about NOT being exposed can register themselves with -//! [`Call::register_fast_unstake`]. This will chill, and fully unbond the staker, and place them in -//! the queue to be checked. +//! [`Pallet::register_fast_unstake`]. This will chill, fully unbond the staker and place them +//! in the queue to be checked. +//! +//! A successful registration implies being fully unbonded and chilled in the staking system. These +//! effects persist even if the fast-unstake registration is retracted (see [`Pallet::deregister`] +//! and further). //! -//! Once queued, but not being actively processed, stakers can withdraw their request via -//! [`Call::deregister`]. +//! Once registered as a fast-unstaker, the staker will be queued and checked by the system. This +//! can take a variable number of blocks based on demand, but will almost certainly be "faster" (as +//! the name suggest) than waiting the standard bonding duration. //! -//! Once queued, a staker wishing to unbond can perform no further action in pallet-staking. This is -//! to prevent them from accidentally exposing themselves behind a validator etc. +//! A fast-unstaker is either in [`Queue`] or actively being checked, at which point it lives in +//! [`Head`]. Once in [`Head`], the request cannot be retracted anymore. But, once in [`Queue`], it +//! can, via [`Pallet::deregister`]. +//! +//! A deposit equal to [`Config::Deposit`] is collected for this process, and is returned in case a +//! successful unstake occurs (`Event::Unstaked` signals that). //! //! Once processed, if successful, no additional fee for the checking process is taken, and the //! staker is instantly unbonded. //! -//! If unsuccessful, meaning that the staker was exposed sometime in the last `BondingDuration` eras -//! they will end up being slashed for the amount of wasted work they have inflicted on the chian. +//! If unsuccessful, meaning that the staker was exposed, the aforementioned deposit will be slashed +//! for the amount of wasted work they have inflicted on the chain. +//! +//! All in all, this pallet is meant to provide an easy off-ramp for some stakers. +//! +//! ### Example +//! +//! 1. Fast-unstake with multiple participants in the queue. +#![doc = docify::embed!("src/tests.rs", successful_multi_queue)] +//! +//! 2. Fast unstake failing because a nominator is exposed. +#![doc = docify::embed!("src/tests.rs", exposed_nominator_cannot_unstake)] +//! +//! ## Pallet API +//! +//! See the [`pallet`] module for more information about the interfaces this pallet exposes, +//! including its configuration trait, dispatchables, storage items, events and errors. +//! +//! ## Low Level / Implementation Details +//! +//! This pallet works off the basis of `on_idle`, meaning that it provides no guarantee about when +//! it will succeed, if at all. Moreover, the queue implementation is unordered. In case of +//! congestion, no FIFO ordering is provided. +//! +//! A few important considerations can be concluded based on the `on_idle`-based implementation: +//! +//! * It is crucial for the weights of this pallet to be correct. The code inside +//! [`Pallet::on_idle`] MUST be able to measure itself and report the remaining weight correctly +//! after execution. +//! +//! * If the weight measurement is incorrect, it can lead to perpetual overweight (consequently +//! slow) blocks. +//! +//! * The amount of weight that `on_idle` consumes is a direct function of [`ErasToCheckPerBlock`]. +//! +//! * Thus, a correct value of [`ErasToCheckPerBlock`] (which can be set via [`Pallet::control`]) +//! should be chosen, such that a reasonable amount of weight is used `on_idle`. If +//! [`ErasToCheckPerBlock`] is too large, `on_idle` will always conclude that it has not enough +//! weight to proceed, and will early-return. Nonetheless, this should also be *safe* as long as +//! the benchmarking/weights are *accurate*. +//! +//! * See the inline code-comments on `do_on_idle` (private) for more details. +//! +//! * For further safety, in case of any unforeseen errors, the pallet will emit +//! [`Event::InternalError`] and set [`ErasToCheckPerBlock`] back to 0, which essentially means +//! the pallet will halt/disable itself. #![cfg_attr(not(feature = "std"), no_std)] @@ -64,9 +127,15 @@ pub mod migrations; pub mod types; pub mod weights; +// some extra imports for docs to link properly. +#[cfg(doc)] +pub use frame_support::traits::Hooks; +#[cfg(doc)] +pub use sp_staking::StakingInterface; + +/// The logging target of this pallet. pub const LOG_TARGET: &'static str = "runtime::fast-unstake"; -// syntactic sugar for logging. #[macro_export] macro_rules! log { ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { @@ -91,15 +160,8 @@ pub mod pallet { use sp_std::{prelude::*, vec::Vec}; pub use weights::WeightInfo; - #[derive(scale_info::TypeInfo, codec::Encode, codec::Decode, codec::MaxEncodedLen)] - #[codec(mel_bound(T: Config))] - #[scale_info(skip_type_params(T))] - pub struct MaxChecking(sp_std::marker::PhantomData); - impl frame_support::traits::Get for MaxChecking { - fn get() -> u32 { - T::Staking::bonding_duration() + 1 - } - } + #[cfg(feature = "try-runtime")] + use sp_runtime::TryRuntimeError; const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); @@ -122,7 +184,7 @@ pub mod pallet { #[pallet::constant] type Deposit: Get>; - /// The origin that can control this pallet. + /// The origin that can control this pallet, in other words invoke [`Pallet::control`]. type ControlOrigin: frame_support::traits::EnsureOrigin; /// Batch size. @@ -133,40 +195,45 @@ pub mod pallet { /// The access to staking functionality. type Staking: StakingInterface, AccountId = Self::AccountId>; + /// Maximum value for `ErasToCheckPerBlock`, checked in [`Pallet::control`]. + /// + /// This should be slightly bigger than the actual value in order to have accurate + /// benchmarks. + type MaxErasToCheckPerBlock: Get; + /// The weight information of this pallet. type WeightInfo: WeightInfo; - /// Maximum value for `ErasToCheckPerBlock`. This should be as close as possible, but more - /// than the actual value, in order to have accurate benchmarks. - type MaxErasToCheckPerBlock: Get; - /// Use only for benchmarking. #[cfg(feature = "runtime-benchmarks")] type MaxBackersPerValidator: Get; } /// The current "head of the queue" being unstaked. + /// + /// The head in itself can be a batch of up to [`Config::BatchSize`] stakers. #[pallet::storage] pub type Head = StorageValue<_, UnstakeRequest, OptionQuery>; /// The map of all accounts wishing to be unstaked. /// /// Keeps track of `AccountId` wishing to unstake and it's corresponding deposit. - /// - /// TWOX-NOTE: SAFE since `AccountId` is a secure hash. + // Hasher: Twox safe since `AccountId` is a secure hash. #[pallet::storage] pub type Queue = CountedStorageMap<_, Twox64Concat, T::AccountId, BalanceOf>; /// Number of eras to check per block. /// - /// If set to 0, this pallet does absolutely nothing. + /// If set to 0, this pallet does absolutely nothing. Cannot be set to more than + /// [`Config::MaxErasToCheckPerBlock`]. /// - /// Based on the amount of weight available at `on_idle`, up to this many eras of a single - /// nominator might be checked. + /// Based on the amount of weight available at [`Pallet::on_idle`], up to this many eras are + /// checked. The checking is represented by updating [`UnstakeRequest::checked`], which is + /// stored in [`Head`]. #[pallet::storage] + #[pallet::getter(fn eras_to_check_per_block)] pub type ErasToCheckPerBlock = StorageValue<_, u32, ValueQuery>; - /// The events of this pallet. #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -174,8 +241,6 @@ pub mod pallet { Unstaked { stash: T::AccountId, result: DispatchResult }, /// A staker was slashed for requesting fast-unstake whilst being exposed. Slashed { stash: T::AccountId, amount: BalanceOf }, - /// An internal error happened. Operations will be paused now. - InternalError, /// A batch was partially checked for the given eras, but the process did not finish. BatchChecked { eras: Vec }, /// A batch of a given size was terminated. @@ -183,6 +248,8 @@ pub mod pallet { /// This is always follows by a number of `Unstaked` or `Slashed` events, marking the end /// of the batch. A new batch will be created upon next block. BatchFinished { size: u32 }, + /// An internal error happened. Operations will be paused now. + InternalError, } #[pallet::error] @@ -205,33 +272,29 @@ pub mod pallet { } #[pallet::hooks] - impl Hooks for Pallet { - fn on_idle(_: T::BlockNumber, remaining_weight: Weight) -> Weight { + impl Hooks> for Pallet { + fn on_idle(_: BlockNumberFor, remaining_weight: Weight) -> Weight { if remaining_weight.any_lt(T::DbWeight::get().reads(2)) { - return Weight::from_ref_time(0) + return Weight::from_parts(0, 0) } Self::do_on_idle(remaining_weight) } fn integrity_test() { - sp_std::if_std! { - sp_io::TestExternalities::new_empty().execute_with(|| { - // ensure that the value of `ErasToCheckPerBlock` is less than - // `T::MaxErasToCheckPerBlock`. - assert!( - ErasToCheckPerBlock::::get() <= T::MaxErasToCheckPerBlock::get(), - "the value of `ErasToCheckPerBlock` is greater than `T::MaxErasToCheckPerBlock`", - ); - }); - } + // Ensure that the value of `ErasToCheckPerBlock` is less or equal to + // `T::MaxErasToCheckPerBlock`. + assert!( + ErasToCheckPerBlock::::get() <= T::MaxErasToCheckPerBlock::get(), + "the value of `ErasToCheckPerBlock` is greater than `T::MaxErasToCheckPerBlock`", + ); } #[cfg(feature = "try-runtime")] - fn try_state(_n: T::BlockNumber) -> Result<(), &'static str> { + fn try_state(_n: BlockNumberFor) -> Result<(), TryRuntimeError> { // ensure that the value of `ErasToCheckPerBlock` is less than // `T::MaxErasToCheckPerBlock`. - assert!( + ensure!( ErasToCheckPerBlock::::get() <= T::MaxErasToCheckPerBlock::get(), "the value of `ErasToCheckPerBlock` is greater than `T::MaxErasToCheckPerBlock`", ); @@ -244,8 +307,12 @@ pub mod pallet { impl Pallet { /// Register oneself for fast-unstake. /// - /// The dispatch origin of this call must be signed by the controller account, similar to - /// `staking::unbond`. + /// ## Dispatch Origin + /// + /// The dispatch origin of this call must be *signed* by whoever is permitted to call + /// unbond funds by the staking system. See [`Config::Staking`]. + /// + /// ## Details /// /// The stash associated with the origin must have no ongoing unlocking chunks. If /// successful, this will fully unbond and chill the stash. Then, it will enqueue the stash @@ -260,6 +327,10 @@ pub mod pallet { /// If the check fails, the stash remains chilled and waiting for being unbonded as in with /// the normal staking system, but they lose part of their unbonding chunks due to consuming /// the chain's resources. + /// + /// ## Events + /// + /// Some events from the staking and currency system might be emitted. #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::register_fast_unstake())] pub fn register_fast_unstake(origin: OriginFor) -> DispatchResult { @@ -285,11 +356,22 @@ pub mod pallet { /// Deregister oneself from the fast-unstake. /// + /// ## Dispatch Origin + /// + /// The dispatch origin of this call must be *signed* by whoever is permitted to call + /// unbond funds by the staking system. See [`Config::Staking`]. + /// + /// ## Details + /// /// This is useful if one is registered, they are still waiting, and they change their mind. /// /// Note that the associated stash is still fully unbonded and chilled as a consequence of - /// calling `register_fast_unstake`. This should probably be followed by a call to - /// `Staking::rebond`. + /// calling [`Pallet::register_fast_unstake`]. Therefore, this should probably be followed + /// by a call to `rebond` in the staking system. + /// + /// ## Events + /// + /// Some events from the staking and currency system might be emitted. #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::deregister())] pub fn deregister(origin: OriginFor) -> DispatchResult { @@ -315,7 +397,17 @@ pub mod pallet { /// Control the operation of this pallet. /// - /// Dispatch origin must be signed by the [`Config::ControlOrigin`]. + /// ## Dispatch Origin + /// + /// The dispatch origin of this call must be [`Config::ControlOrigin`]. + /// + /// ## Details + /// + /// Can set the number of eras to check per block, and potentially other admin work. + /// + /// ## Events + /// + /// No events are emitted from this dispatch. #[pallet::call_index(2)] #[pallet::weight(::WeightInfo::control())] pub fn control(origin: OriginFor, eras_to_check: EraIndex) -> DispatchResult { @@ -356,7 +448,7 @@ pub mod pallet { /// checked, we don't finish the process. pub(crate) fn do_on_idle(remaining_weight: Weight) -> Weight { // any weight that is unaccounted for - let mut unaccounted_weight = Weight::from_ref_time(0); + let mut unaccounted_weight = Weight::from_parts(0, 0); let eras_to_check_per_block = ErasToCheckPerBlock::::get(); if eras_to_check_per_block.is_zero() { diff --git a/frame/fast-unstake/src/migrations.rs b/frame/fast-unstake/src/migrations.rs index d2778d48b1b6..564388407045 100644 --- a/frame/fast-unstake/src/migrations.rs +++ b/frame/fast-unstake/src/migrations.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +25,11 @@ pub mod v1 { use sp_staking::EraIndex; use sp_std::prelude::*; + #[cfg(feature = "try-runtime")] + use frame_support::ensure; + #[cfg(feature = "try-runtime")] + use sp_runtime::TryRuntimeError; + pub struct MigrateToV1(sp_std::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV1 { fn on_runtime_upgrade() -> Weight { @@ -65,14 +70,20 @@ pub mod v1 { } #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - assert_eq!(Pallet::::on_chain_storage_version(), 0); + fn pre_upgrade() -> Result, TryRuntimeError> { + ensure!( + Pallet::::on_chain_storage_version() == 0, + "The onchain storage version must be zero for the migration to execute." + ); Ok(Default::default()) } #[cfg(feature = "try-runtime")] - fn post_upgrade(_: Vec) -> Result<(), &'static str> { - assert_eq!(Pallet::::on_chain_storage_version(), 1); + fn post_upgrade(_: Vec) -> Result<(), TryRuntimeError> { + ensure!( + Pallet::::on_chain_storage_version() == 1, + "The onchain version must be updated after the migration." + ); Ok(()) } } diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index 38cf41fdebe8..192d525e278d 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,13 +23,16 @@ use frame_support::{ traits::{ConstU64, Currency}, weights::constants::WEIGHT_REF_TIME_PER_SECOND, }; -use sp_runtime::traits::{Convert, IdentityLookup}; +use sp_runtime::{ + traits::{Convert, IdentityLookup}, + BuildStorage, +}; use pallet_staking::{Exposure, IndividualExposure, StakerStatus}; use sp_std::prelude::*; pub type AccountId = u128; -pub type AccountIndex = u32; +pub type Nonce = u32; pub type BlockNumber = u64; pub type Balance = u128; pub type T = Runtime; @@ -47,14 +50,13 @@ impl frame_system::Config for Runtime { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = AccountIndex; - type BlockNumber = BlockNumber; + type Nonce = Nonce; type RuntimeCall = RuntimeCall; type Hash = sp_core::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = sp_runtime::testing::Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type Version = (); @@ -89,6 +91,10 @@ impl pallet_balances::Config for Runtime { type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } pallet_staking_reward_curve::build! { @@ -133,7 +139,7 @@ impl pallet_staking::Config for Runtime { type Currency = Balances; type CurrencyBalance = Balance; type UnixTime = pallet_timestamp::Pallet; - type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type CurrencyToVote = (); type RewardRemainder = (); type RuntimeEvent = RuntimeEvent; type Slash = (); @@ -153,7 +159,7 @@ impl pallet_staking::Config for Runtime { type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; - type OnStakerSlash = (); + type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } @@ -191,13 +197,9 @@ impl fast_unstake::Config for Runtime { } type Block = frame_system::mocking::MockBlock; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { + pub struct Runtime { System: frame_system, Timestamp: pallet_timestamp, Balances: pallet_balances, @@ -229,11 +231,11 @@ impl Default for ExtBuilder { fn default() -> Self { Self { unexposed: vec![ - (1, 2, 7 + 100), - (3, 4, 7 + 100), - (5, 6, 7 + 100), - (7, 8, 7 + 100), - (9, 10, 7 + 100), + (1, 1, 7 + 100), + (3, 3, 7 + 100), + (5, 5, 7 + 100), + (7, 7, 7 + 100), + (9, 9, 7 + 100), ], } } @@ -273,7 +275,7 @@ impl ExtBuilder { pub(crate) fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); let mut storage = - frame_system::GenesisConfig::default().build_storage::().unwrap(); + frame_system::GenesisConfig::::default().build_storage().unwrap(); let validators_range = VALIDATOR_PREFIX..VALIDATOR_PREFIX + VALIDATORS_PER_ERA; let nominators_range = @@ -285,12 +287,6 @@ impl ExtBuilder { .clone() .into_iter() .map(|(stash, _, balance)| (stash, balance * 2)) - .chain( - self.unexposed - .clone() - .into_iter() - .map(|(_, ctrl, balance)| (ctrl, balance * 2)), - ) .chain(validators_range.clone().map(|x| (x, 7 + 100))) .chain(nominators_range.clone().map(|x| (x, 7 + 100))) .collect::>(), @@ -372,7 +368,6 @@ pub fn create_exposed_nominator(exposed: AccountId, era: u32) { Balances::make_free_balance_be(&exposed, 100); assert_ok!(Staking::bond( RuntimeOrigin::signed(exposed), - exposed, 10, pallet_staking::RewardDestination::Staked )); diff --git a/frame/fast-unstake/src/tests.rs b/frame/fast-unstake/src/tests.rs index 3e62146ae299..94ad6a84b85a 100644 --- a/frame/fast-unstake/src/tests.rs +++ b/frame/fast-unstake/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ use super::*; use crate::{mock::*, types::*, Event}; -use frame_support::{assert_noop, assert_ok, bounded_vec, pallet_prelude::*, traits::Currency}; +use frame_support::{pallet_prelude::*, testing_prelude::*, traits::Currency}; use pallet_staking::{CurrentEra, RewardDestination}; use sp_runtime::traits::BadOrigin; @@ -37,7 +37,7 @@ fn register_works() { ExtBuilder::default().build_and_execute(|| { ErasToCheckPerBlock::::put(1); // Controller account registers for fast unstake. - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); // Ensure stash is in the queue. assert_ne!(Queue::::get(1), None); }); @@ -52,7 +52,7 @@ fn register_insufficient_funds_fails() { // Controller account registers for fast unstake. assert_noop!( - FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)), + FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1)), BalancesError::::InsufficientBalance, ); @@ -65,7 +65,7 @@ fn register_insufficient_funds_fails() { fn register_disabled_fails() { ExtBuilder::default().build_and_execute(|| { assert_noop!( - FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)), + FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1)), Error::::CallNotAllowed ); }); @@ -81,7 +81,7 @@ fn cannot_register_if_not_bonded() { } // Attempt to fast unstake. assert_noop!( - FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1)), + FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)), Error::::NotController ); }); @@ -95,7 +95,7 @@ fn cannot_register_if_in_queue() { Queue::::insert(1, 10); // Cannot re-register, already in queue assert_noop!( - FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)), + FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1)), Error::::AlreadyQueued ); }); @@ -112,7 +112,7 @@ fn cannot_register_if_head() { }); // Controller attempts to regsiter assert_noop!( - FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)), + FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1)), Error::::AlreadyHead ); }); @@ -123,10 +123,10 @@ fn cannot_register_if_has_unlocking_chunks() { ExtBuilder::default().build_and_execute(|| { ErasToCheckPerBlock::::put(1); // Start unbonding half of staked tokens - assert_ok!(Staking::unbond(RuntimeOrigin::signed(2), 50_u128)); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(1), 50_u128)); // Cannot register for fast unstake with unlock chunks active assert_noop!( - FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)), + FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1)), Error::::NotFullyBonded ); }); @@ -140,11 +140,11 @@ fn deregister_works() { assert_eq!(::Currency::reserved_balance(&1), 0); // Controller account registers for fast unstake. - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); assert_eq!(::Currency::reserved_balance(&1), Deposit::get()); // Controller then changes mind and deregisters. - assert_ok!(FastUnstake::deregister(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::deregister(RuntimeOrigin::signed(1))); assert_eq!(::Currency::reserved_balance(&1), 0); // Ensure stash no longer exists in the queue. @@ -156,9 +156,9 @@ fn deregister_works() { fn deregister_disabled_fails() { ExtBuilder::default().build_and_execute(|| { ErasToCheckPerBlock::::put(1); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); ErasToCheckPerBlock::::put(0); - assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(2)), Error::::CallNotAllowed); + assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(1)), Error::::CallNotAllowed); }); } @@ -166,10 +166,10 @@ fn deregister_disabled_fails() { fn cannot_deregister_if_not_controller() { ExtBuilder::default().build_and_execute(|| { ErasToCheckPerBlock::::put(1); - // Controller account registers for fast unstake. - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); - // Stash tries to deregister. - assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(1)), Error::::NotController); + // Controller (same as stash) account registers for fast unstake. + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); + // Another account tries to deregister. + assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(2)), Error::::NotController); }); } @@ -178,7 +178,7 @@ fn cannot_deregister_if_not_queued() { ExtBuilder::default().build_and_execute(|| { ErasToCheckPerBlock::::put(1); // Controller tries to deregister without first registering - assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(2)), Error::::NotQueued); + assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(1)), Error::::NotQueued); }); } @@ -187,14 +187,14 @@ fn cannot_deregister_already_head() { ExtBuilder::default().build_and_execute(|| { ErasToCheckPerBlock::::put(1); // Controller attempts to register, should fail - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); // Insert some Head item for stash. Head::::put(UnstakeRequest { stashes: bounded_vec![(1, Deposit::get())], checked: bounded_vec![], }); // Controller attempts to deregister - assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(2)), Error::::AlreadyHead); + assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(1)), Error::::AlreadyHead); }); } @@ -210,7 +210,7 @@ fn control_works() { fn control_must_be_control_origin() { ExtBuilder::default().build_and_execute(|| { // account without control (root) origin wants to only check 1 era per block. - assert_noop!(FastUnstake::control(RuntimeOrigin::signed(1), 1_u32), BadOrigin); + assert_noop!(FastUnstake::control(RuntimeOrigin::signed(2), 1_u32), BadOrigin); }); } @@ -224,11 +224,11 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // set up Queue item - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); assert_eq!(Queue::::get(1), Some(Deposit::get())); // call on_idle with no remaining weight - FastUnstake::on_idle(System::block_number(), Weight::from_ref_time(0)); + FastUnstake::on_idle(System::block_number(), Weight::from_parts(0, 0)); // assert nothing changed in Queue and Head assert_eq!(Head::::get(), None); @@ -245,11 +245,11 @@ mod on_idle { // given assert_eq!(::Currency::reserved_balance(&1), 0); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4))); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(6))); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(8))); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(10))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(3))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(5))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(7))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(9))); assert_eq!(::Currency::reserved_balance(&1), Deposit::get()); @@ -303,6 +303,7 @@ mod on_idle { }); } + #[docify::export] #[test] fn successful_multi_queue() { ExtBuilder::default().build_and_execute(|| { @@ -310,9 +311,9 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // register multi accounts for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); assert_eq!(Queue::::get(1), Some(Deposit::get())); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(3))); assert_eq!(Queue::::get(3), Some(Deposit::get())); // assert 2 queue items are in Queue & None in Head to start with @@ -356,6 +357,7 @@ mod on_idle { }); } + #[docify::export] #[test] fn successful_unstake() { ExtBuilder::default().build_and_execute(|| { @@ -363,7 +365,7 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // register for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); assert_eq!(Queue::::get(1), Some(Deposit::get())); // process on idle @@ -405,7 +407,7 @@ mod on_idle { Balances::make_free_balance_be(&2, 100); // register for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); assert_eq!(Queue::::get(1), Some(Deposit::get())); // process on idle @@ -446,7 +448,7 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // register for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); assert_eq!(Queue::::get(1), Some(Deposit::get())); // process on idle @@ -524,7 +526,7 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // register for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); assert_eq!(Queue::::get(1), Some(Deposit::get())); next_block(true); @@ -605,7 +607,7 @@ mod on_idle { CurrentEra::::put(BondingDuration::get()); // register for fast unstake - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); // process 2 blocks next_block(true); @@ -693,6 +695,7 @@ mod on_idle { }); } + #[docify::export] #[test] fn exposed_nominator_cannot_unstake() { ExtBuilder::default().build_and_execute(|| { @@ -812,7 +815,7 @@ mod on_idle { // create a new validator that 100% not exposed. Balances::make_free_balance_be(&42, 100 + Deposit::get()); - assert_ok!(Staking::bond(RuntimeOrigin::signed(42), 42, 10, RewardDestination::Staked)); + assert_ok!(Staking::bond(RuntimeOrigin::signed(42), 10, RewardDestination::Staked)); assert_ok!(Staking::validate(RuntimeOrigin::signed(42), Default::default())); // let them register: @@ -851,10 +854,10 @@ mod batched { ErasToCheckPerBlock::::put(BondingDuration::get() + 1); CurrentEra::::put(BondingDuration::get()); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4))); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(6))); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(8))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(3))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(5))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(7))); assert_eq!(Queue::::count(), 4); assert_eq!(Head::::get(), None); @@ -902,10 +905,10 @@ mod batched { ErasToCheckPerBlock::::put(2); CurrentEra::::put(BondingDuration::get()); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4))); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(6))); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(8))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(3))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(5))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(7))); assert_eq!(Queue::::count(), 4); assert_eq!(Head::::get(), None); @@ -969,8 +972,8 @@ mod batched { CurrentEra::::put(BondingDuration::get()); // register two good ones. - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(3))); create_exposed_nominator(666, 1); create_exposed_nominator(667, 3); @@ -1045,8 +1048,8 @@ mod batched { next_block(true); // ..and register two good ones. - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2))); - assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(1))); + assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(3))); // then one of the bad ones is reaped. assert_eq!( @@ -1091,8 +1094,9 @@ mod batched { #[test] fn kusama_estimate() { use crate::WeightInfo; - let block_time = frame_support::weights::Weight::from_ref_time( + let block_time = frame_support::weights::Weight::from_parts( frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND * 2, + 0, ) .ref_time() as f32; let on_idle = crate::weights::SubstrateWeight::::on_idle_check(1000, 64).ref_time() as f32; @@ -1102,8 +1106,9 @@ fn kusama_estimate() { #[test] fn polkadot_estimate() { use crate::WeightInfo; - let block_time = frame_support::weights::Weight::from_ref_time( + let block_time = frame_support::weights::Weight::from_parts( frame_support::weights::constants::WEIGHT_REF_TIME_PER_SECOND * 2, + 0, ) .ref_time() as f32; let on_idle = crate::weights::SubstrateWeight::::on_idle_check(300, 64).ref_time() as f32; diff --git a/frame/fast-unstake/src/types.rs b/frame/fast-unstake/src/types.rs index 34ca6517f316..15d0a327e917 100644 --- a/frame/fast-unstake/src/types.rs +++ b/frame/fast-unstake/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,25 +17,41 @@ //! Types used in the Fast Unstake pallet. -use crate::{Config, MaxChecking}; +use crate::Config; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ traits::Currency, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; -use sp_staking::EraIndex; +use sp_staking::{EraIndex, StakingInterface}; use sp_std::prelude::*; -pub type BalanceOf = +/// Maximum number of eras that we might check for a single staker. +/// +/// In effect, it is the bonding duration, coming from [`Config::Staking`], plus one. +#[derive(scale_info::TypeInfo, codec::Encode, codec::Decode, codec::MaxEncodedLen)] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct MaxChecking(sp_std::marker::PhantomData); +impl frame_support::traits::Get for MaxChecking { + fn get() -> u32 { + T::Staking::bonding_duration() + 1 + } +} + +pub(crate) type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; /// An unstake request. +/// +/// This is stored in [`crate::Head`] storage item and points to the current unstake request that is +/// being processed. #[derive( Encode, Decode, EqNoBound, PartialEqNoBound, Clone, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen, )] #[scale_info(skip_type_params(T))] pub struct UnstakeRequest { - /// This list of stashes being processed in this request, and their corresponding deposit. - pub(crate) stashes: BoundedVec<(T::AccountId, BalanceOf), T::BatchSize>, + /// This list of stashes are being processed in this request, and their corresponding deposit. + pub stashes: BoundedVec<(T::AccountId, BalanceOf), T::BatchSize>, /// The list of eras for which they have been checked. - pub(crate) checked: BoundedVec>, + pub checked: BoundedVec>, } diff --git a/frame/fast-unstake/src/weights.rs b/frame/fast-unstake/src/weights.rs index f7d0cacba0bc..9c25a409f740 100644 --- a/frame/fast-unstake/src/weights.rs +++ b/frame/fast-unstake/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,33 +18,37 @@ //! Autogenerated weights for pallet_fast_unstake //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2023-01-25, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `runner-b3zmxxc-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// target/production/substrate +// ./target/production/substrate // benchmark // pallet +// --chain=dev // --steps=50 // --repeat=20 +// --pallet=pallet_fast_unstake +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --json-file=/builds/parity/mirrors/substrate/.git/.artifacts/bench.json -// --pallet=pallet_fast_unstake -// --chain=dev -// --header=./HEADER-APACHE2 // --output=./frame/fast-unstake/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_fast_unstake. pub trait WeightInfo { @@ -58,172 +62,298 @@ pub trait WeightInfo { /// Weights for pallet_fast_unstake using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) - // Storage: Staking ValidatorCount (r:1 w:0) - // Storage: FastUnstake Head (r:1 w:1) - // Storage: FastUnstake CounterForQueue (r:1 w:0) - // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking SlashingSpans (r:1 w:0) - // Storage: Staking Bonded (r:1 w:1) - // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: Staking Ledger (r:0 w:1) - // Storage: Staking Payee (r:0 w:1) + /// Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + /// Proof: FastUnstake ErasToCheckPerBlock (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ValidatorCount (r:1 w:0) + /// Proof: Staking ValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: FastUnstake Head (r:1 w:1) + /// Proof: FastUnstake Head (max_values: Some(1), max_size: Some(5768), added: 6263, mode: MaxEncodedLen) + /// Storage: FastUnstake CounterForQueue (r:1 w:0) + /// Proof: FastUnstake CounterForQueue (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking SlashingSpans (r:64 w:0) + /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking Bonded (r:64 w:64) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:64 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:64 w:0) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: System Account (r:64 w:64) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:64 w:64) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:64 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:0 w:64) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:0 w:64) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) /// The range of component `b` is `[1, 64]`. fn on_idle_unstake(b: u32, ) -> Weight { - // Minimum execution time: 92_833 nanoseconds. - Weight::from_ref_time(62_136_346) - // Standard Error: 25_541 - .saturating_add(Weight::from_ref_time(42_904_859).saturating_mul(b.into())) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(b.into()))) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `1378 + b * (343 ±0)` + // Estimated: `7253 + b * (3774 ±0)` + // Minimum execution time: 92_847_000 picoseconds. + Weight::from_parts(42_300_813, 7253) + // Standard Error: 40_514 + .saturating_add(Weight::from_parts(58_412_402, 0).saturating_mul(b.into())) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(b.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) .saturating_add(T::DbWeight::get().writes((5_u64).saturating_mul(b.into()))) + .saturating_add(Weight::from_parts(0, 3774).saturating_mul(b.into())) } - // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) - // Storage: Staking ValidatorCount (r:1 w:0) - // Storage: FastUnstake Head (r:1 w:1) - // Storage: FastUnstake CounterForQueue (r:1 w:0) - // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking ErasStakers (r:2 w:0) + /// Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + /// Proof: FastUnstake ErasToCheckPerBlock (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ValidatorCount (r:1 w:0) + /// Proof: Staking ValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: FastUnstake Head (r:1 w:1) + /// Proof: FastUnstake Head (max_values: Some(1), max_size: Some(5768), added: 6263, mode: MaxEncodedLen) + /// Storage: FastUnstake CounterForQueue (r:1 w:0) + /// Proof: FastUnstake CounterForQueue (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ErasStakers (r:257 w:0) + /// Proof Skipped: Staking ErasStakers (max_values: None, max_size: None, mode: Measured) /// The range of component `v` is `[1, 256]`. /// The range of component `b` is `[1, 64]`. fn on_idle_check(v: u32, b: u32, ) -> Weight { - // Minimum execution time: 1_775_293 nanoseconds. - Weight::from_ref_time(1_787_133_000) - // Standard Error: 17_109_142 - .saturating_add(Weight::from_ref_time(546_766_552).saturating_mul(v.into())) - // Standard Error: 68_455_625 - .saturating_add(Weight::from_ref_time(2_135_980_830).saturating_mul(b.into())) - .saturating_add(T::DbWeight::get().reads(7)) + // Proof Size summary in bytes: + // Measured: `1546 + b * (48 ±0) + v * (10037 ±0)` + // Estimated: `7253 + b * (49 ±0) + v * (12513 ±0)` + // Minimum execution time: 1_685_784_000 picoseconds. + Weight::from_parts(1_693_370_000, 7253) + // Standard Error: 13_295_842 + .saturating_add(Weight::from_parts(425_349_148, 0).saturating_mul(v.into())) + // Standard Error: 53_198_180 + .saturating_add(Weight::from_parts(1_673_328_444, 0).saturating_mul(b.into())) + .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) - .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 49).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(0, 12513).saturating_mul(v.into())) } - // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: FastUnstake Queue (r:1 w:1) - // Storage: FastUnstake Head (r:1 w:0) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: VoterList ListNodes (r:1 w:1) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Balances Locks (r:1 w:1) - // Storage: FastUnstake CounterForQueue (r:1 w:1) + /// Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + /// Proof: FastUnstake ErasToCheckPerBlock (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: FastUnstake Queue (r:1 w:1) + /// Proof: FastUnstake Queue (max_values: None, max_size: Some(56), added: 2531, mode: MaxEncodedLen) + /// Storage: FastUnstake Head (r:1 w:0) + /// Proof: FastUnstake Head (max_values: Some(1), max_size: Some(5768), added: 6263, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:1 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:1 w:1) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: FastUnstake CounterForQueue (r:1 w:1) + /// Proof: FastUnstake CounterForQueue (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn register_fast_unstake() -> Weight { - // Minimum execution time: 124_849 nanoseconds. - Weight::from_ref_time(128_176_000) - .saturating_add(T::DbWeight::get().reads(14)) - .saturating_add(T::DbWeight::get().writes(9)) + // Proof Size summary in bytes: + // Measured: `1964` + // Estimated: `7253` + // Minimum execution time: 125_512_000 picoseconds. + Weight::from_parts(129_562_000, 7253) + .saturating_add(T::DbWeight::get().reads(15_u64)) + .saturating_add(T::DbWeight::get().writes(9_u64)) } - // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) - // Storage: Staking Ledger (r:1 w:0) - // Storage: FastUnstake Queue (r:1 w:1) - // Storage: FastUnstake Head (r:1 w:0) - // Storage: FastUnstake CounterForQueue (r:1 w:1) + /// Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + /// Proof: FastUnstake ErasToCheckPerBlock (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: FastUnstake Queue (r:1 w:1) + /// Proof: FastUnstake Queue (max_values: None, max_size: Some(56), added: 2531, mode: MaxEncodedLen) + /// Storage: FastUnstake Head (r:1 w:0) + /// Proof: FastUnstake Head (max_values: Some(1), max_size: Some(5768), added: 6263, mode: MaxEncodedLen) + /// Storage: FastUnstake CounterForQueue (r:1 w:1) + /// Proof: FastUnstake CounterForQueue (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn deregister() -> Weight { - // Minimum execution time: 48_246 nanoseconds. - Weight::from_ref_time(49_720_000) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `1223` + // Estimated: `7253` + // Minimum execution time: 43_943_000 picoseconds. + Weight::from_parts(45_842_000, 7253) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: FastUnstake ErasToCheckPerBlock (r:0 w:1) + /// Storage: FastUnstake ErasToCheckPerBlock (r:0 w:1) + /// Proof: FastUnstake ErasToCheckPerBlock (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn control() -> Weight { - // Minimum execution time: 4_611 nanoseconds. - Weight::from_ref_time(4_844_000) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_677_000 picoseconds. + Weight::from_parts(2_849_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) - // Storage: Staking ValidatorCount (r:1 w:0) - // Storage: FastUnstake Head (r:1 w:1) - // Storage: FastUnstake CounterForQueue (r:1 w:0) - // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking SlashingSpans (r:1 w:0) - // Storage: Staking Bonded (r:1 w:1) - // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: Staking Ledger (r:0 w:1) - // Storage: Staking Payee (r:0 w:1) + /// Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + /// Proof: FastUnstake ErasToCheckPerBlock (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ValidatorCount (r:1 w:0) + /// Proof: Staking ValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: FastUnstake Head (r:1 w:1) + /// Proof: FastUnstake Head (max_values: Some(1), max_size: Some(5768), added: 6263, mode: MaxEncodedLen) + /// Storage: FastUnstake CounterForQueue (r:1 w:0) + /// Proof: FastUnstake CounterForQueue (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking SlashingSpans (r:64 w:0) + /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking Bonded (r:64 w:64) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:64 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:64 w:0) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: System Account (r:64 w:64) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:64 w:64) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:64 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:0 w:64) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:0 w:64) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) /// The range of component `b` is `[1, 64]`. fn on_idle_unstake(b: u32, ) -> Weight { - // Minimum execution time: 92_833 nanoseconds. - Weight::from_ref_time(62_136_346) - // Standard Error: 25_541 - .saturating_add(Weight::from_ref_time(42_904_859).saturating_mul(b.into())) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().reads((6_u64).saturating_mul(b.into()))) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `1378 + b * (343 ±0)` + // Estimated: `7253 + b * (3774 ±0)` + // Minimum execution time: 92_847_000 picoseconds. + Weight::from_parts(42_300_813, 7253) + // Standard Error: 40_514 + .saturating_add(Weight::from_parts(58_412_402, 0).saturating_mul(b.into())) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(b.into()))) + .saturating_add(RocksDbWeight::get().writes(1_u64)) .saturating_add(RocksDbWeight::get().writes((5_u64).saturating_mul(b.into()))) + .saturating_add(Weight::from_parts(0, 3774).saturating_mul(b.into())) } - // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) - // Storage: Staking ValidatorCount (r:1 w:0) - // Storage: FastUnstake Head (r:1 w:1) - // Storage: FastUnstake CounterForQueue (r:1 w:0) - // Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking ErasStakers (r:2 w:0) + /// Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + /// Proof: FastUnstake ErasToCheckPerBlock (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ValidatorCount (r:1 w:0) + /// Proof: Staking ValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: FastUnstake Head (r:1 w:1) + /// Proof: FastUnstake Head (max_values: Some(1), max_size: Some(5768), added: 6263, mode: MaxEncodedLen) + /// Storage: FastUnstake CounterForQueue (r:1 w:0) + /// Proof: FastUnstake CounterForQueue (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: ElectionProviderMultiPhase CurrentPhase (r:1 w:0) + /// Proof Skipped: ElectionProviderMultiPhase CurrentPhase (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ErasStakers (r:257 w:0) + /// Proof Skipped: Staking ErasStakers (max_values: None, max_size: None, mode: Measured) /// The range of component `v` is `[1, 256]`. /// The range of component `b` is `[1, 64]`. fn on_idle_check(v: u32, b: u32, ) -> Weight { - // Minimum execution time: 1_775_293 nanoseconds. - Weight::from_ref_time(1_787_133_000) - // Standard Error: 17_109_142 - .saturating_add(Weight::from_ref_time(546_766_552).saturating_mul(v.into())) - // Standard Error: 68_455_625 - .saturating_add(Weight::from_ref_time(2_135_980_830).saturating_mul(b.into())) - .saturating_add(RocksDbWeight::get().reads(7)) + // Proof Size summary in bytes: + // Measured: `1546 + b * (48 ±0) + v * (10037 ±0)` + // Estimated: `7253 + b * (49 ±0) + v * (12513 ±0)` + // Minimum execution time: 1_685_784_000 picoseconds. + Weight::from_parts(1_693_370_000, 7253) + // Standard Error: 13_295_842 + .saturating_add(Weight::from_parts(425_349_148, 0).saturating_mul(v.into())) + // Standard Error: 53_198_180 + .saturating_add(Weight::from_parts(1_673_328_444, 0).saturating_mul(b.into())) + .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(v.into()))) - .saturating_add(RocksDbWeight::get().writes(1)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 49).saturating_mul(b.into())) + .saturating_add(Weight::from_parts(0, 12513).saturating_mul(v.into())) } - // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: FastUnstake Queue (r:1 w:1) - // Storage: FastUnstake Head (r:1 w:0) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: VoterList ListNodes (r:1 w:1) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Balances Locks (r:1 w:1) - // Storage: FastUnstake CounterForQueue (r:1 w:1) + /// Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + /// Proof: FastUnstake ErasToCheckPerBlock (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: FastUnstake Queue (r:1 w:1) + /// Proof: FastUnstake Queue (max_values: None, max_size: Some(56), added: 2531, mode: MaxEncodedLen) + /// Storage: FastUnstake Head (r:1 w:0) + /// Proof: FastUnstake Head (max_values: Some(1), max_size: Some(5768), added: 6263, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:1 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:1 w:1) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: FastUnstake CounterForQueue (r:1 w:1) + /// Proof: FastUnstake CounterForQueue (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn register_fast_unstake() -> Weight { - // Minimum execution time: 124_849 nanoseconds. - Weight::from_ref_time(128_176_000) - .saturating_add(RocksDbWeight::get().reads(14)) - .saturating_add(RocksDbWeight::get().writes(9)) + // Proof Size summary in bytes: + // Measured: `1964` + // Estimated: `7253` + // Minimum execution time: 125_512_000 picoseconds. + Weight::from_parts(129_562_000, 7253) + .saturating_add(RocksDbWeight::get().reads(15_u64)) + .saturating_add(RocksDbWeight::get().writes(9_u64)) } - // Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) - // Storage: Staking Ledger (r:1 w:0) - // Storage: FastUnstake Queue (r:1 w:1) - // Storage: FastUnstake Head (r:1 w:0) - // Storage: FastUnstake CounterForQueue (r:1 w:1) + /// Storage: FastUnstake ErasToCheckPerBlock (r:1 w:0) + /// Proof: FastUnstake ErasToCheckPerBlock (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: FastUnstake Queue (r:1 w:1) + /// Proof: FastUnstake Queue (max_values: None, max_size: Some(56), added: 2531, mode: MaxEncodedLen) + /// Storage: FastUnstake Head (r:1 w:0) + /// Proof: FastUnstake Head (max_values: Some(1), max_size: Some(5768), added: 6263, mode: MaxEncodedLen) + /// Storage: FastUnstake CounterForQueue (r:1 w:1) + /// Proof: FastUnstake CounterForQueue (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn deregister() -> Weight { - // Minimum execution time: 48_246 nanoseconds. - Weight::from_ref_time(49_720_000) - .saturating_add(RocksDbWeight::get().reads(5)) - .saturating_add(RocksDbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `1223` + // Estimated: `7253` + // Minimum execution time: 43_943_000 picoseconds. + Weight::from_parts(45_842_000, 7253) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: FastUnstake ErasToCheckPerBlock (r:0 w:1) + /// Storage: FastUnstake ErasToCheckPerBlock (r:0 w:1) + /// Proof: FastUnstake ErasToCheckPerBlock (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn control() -> Weight { - // Minimum execution time: 4_611 nanoseconds. - Weight::from_ref_time(4_844_000) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_677_000 picoseconds. + Weight::from_parts(2_849_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } diff --git a/frame/glutton/Cargo.toml b/frame/glutton/Cargo.toml new file mode 100644 index 000000000000..4675ada115e1 --- /dev/null +++ b/frame/glutton/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "pallet-glutton" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet for pushing a chain to its weight limits" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +blake2 = { version = "0.10.4", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +log = { version = "0.4.14", default-features = false } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", path = "../balances" } + +[features] +default = ["std"] +std = [ + "blake2/std", + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +try-runtime = ["frame-support/try-runtime"] + +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] diff --git a/frame/glutton/README.md b/frame/glutton/README.md new file mode 100644 index 000000000000..8ad4f7917182 --- /dev/null +++ b/frame/glutton/README.md @@ -0,0 +1,9 @@ +# WARNING + +**DO NOT USE ON VALUE-BEARING CHAINS. THIS PALLET IS ONLY INTENDED FOR TESTING USAGE.** + +# Glutton Pallet + +The `Glutton` pallet gets the name from its property to consume vast amounts of resources. It can be used to push para-chains and their relay-chains to the limits. This is good for testing out theoretical limits in a practical way. + +The `Glutton` can be set to consume a fraction of the available unused weight of a chain. It accomplishes this by utilizing the `on_idle` hook and consuming a specific ration of the remaining weight. The rations can be set via `set_compute` and `set_storage`. Initially the `Glutton` needs to be initialized once with `initialize_pallet`. diff --git a/frame/glutton/src/benchmarking.rs b/frame/glutton/src/benchmarking.rs new file mode 100644 index 000000000000..587207587456 --- /dev/null +++ b/frame/glutton/src/benchmarking.rs @@ -0,0 +1,99 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +//! Glutton pallet benchmarking. +//! +//! Has to be compiled and run twice to calibrate on new hardware. + +#[cfg(feature = "runtime-benchmarks")] +use super::*; + +use frame_benchmarking::benchmarks; +use frame_support::{pallet_prelude::*, weights::constants::*}; +use frame_system::RawOrigin as SystemOrigin; +use sp_runtime::{traits::One, Perbill}; + +use crate::Pallet as Glutton; +use frame_system::Pallet as System; + +benchmarks! { + initialize_pallet_grow { + let n in 0 .. 1_000; + }: { + Glutton::::initialize_pallet(SystemOrigin::Root.into(), n, None).unwrap() + } verify { + assert_eq!(TrashDataCount::::get(), n); + } + + initialize_pallet_shrink { + let n in 0 .. 1_000; + + Glutton::::initialize_pallet(SystemOrigin::Root.into(), n, None).unwrap(); + }: { + Glutton::::initialize_pallet(SystemOrigin::Root.into(), 0, Some(n)).unwrap() + } verify { + assert_eq!(TrashDataCount::::get(), 0); + } + + waste_ref_time_iter { + let i in 0..100_000; + }: { + Glutton::::waste_ref_time_iter(vec![0u8; 64], i); + } + + waste_proof_size_some { + let i in 0..5_000; + + (0..5000).for_each(|i| TrashData::::insert(i, [i as u8; 1024])); + }: { + (0..i).for_each(|i| { + TrashData::::get(i); + }) + } + + // For manual verification only. + on_idle_high_proof_waste { + (0..5000).for_each(|i| TrashData::::insert(i, [i as u8; 1024])); + let _ = Glutton::::set_compute(SystemOrigin::Root.into(), One::one()); + let _ = Glutton::::set_storage(SystemOrigin::Root.into(), One::one()); + }: { + let weight = Glutton::::on_idle(System::::block_number(), Weight::from_parts(WEIGHT_REF_TIME_PER_MILLIS * 100, WEIGHT_PROOF_SIZE_PER_MB * 5)); + } + + // For manual verification only. + on_idle_low_proof_waste { + (0..5000).for_each(|i| TrashData::::insert(i, [i as u8; 1024])); + let _ = Glutton::::set_compute(SystemOrigin::Root.into(), One::one()); + let _ = Glutton::::set_storage(SystemOrigin::Root.into(), One::one()); + }: { + let weight = Glutton::::on_idle(System::::block_number(), Weight::from_parts(WEIGHT_REF_TIME_PER_MILLIS * 100, WEIGHT_PROOF_SIZE_PER_KB * 20)); + } + + empty_on_idle { + }: { + // Enough weight do do nothing. + Glutton::::on_idle(System::::block_number(), T::WeightInfo::empty_on_idle()); + } + + set_compute { + }: _(SystemOrigin::Root, FixedU64::from_perbill(Perbill::from_percent(50))) + + set_storage { + }: _(SystemOrigin::Root, FixedU64::from_perbill(Perbill::from_percent(50))) + + impl_benchmark_test_suite!(Glutton, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/glutton/src/lib.rs b/frame/glutton/src/lib.rs new file mode 100644 index 000000000000..1db68003abe4 --- /dev/null +++ b/frame/glutton/src/lib.rs @@ -0,0 +1,364 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +//! # WARNING +//! +//! **DO NOT USE ON VALUE-BEARING CHAINS. THIS PALLET IS ONLY INTENDED FOR TESTING USAGE.** +//! +//! # Glutton Pallet +//! +//! Pallet that consumes `ref_time` and `proof_size` of a block. Based on the +//! `Compute` and `Storage` parameters the pallet consumes the adequate amount +//! of weight. + +#![cfg_attr(not(feature = "std"), no_std)] + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; +pub mod weights; + +use blake2::{Blake2b512, Digest}; +use frame_support::{pallet_prelude::*, weights::WeightMeter, DefaultNoBound}; +use frame_system::pallet_prelude::*; +use sp_io::hashing::twox_256; +use sp_runtime::{traits::Zero, FixedPointNumber, FixedU64}; +use sp_std::{vec, vec::Vec}; + +pub use pallet::*; +pub use weights::WeightInfo; + +/// The size of each value in the `TrashData` storage in bytes. +pub const VALUE_SIZE: usize = 1024; +/// Max number of entries for the `TrashData` map. +pub const MAX_TRASH_DATA_ENTRIES: u32 = 65_000; +/// Hard limit for any other resource limit (in units). +pub const RESOURCE_HARD_LIMIT: FixedU64 = FixedU64::from_u32(10); + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From + IsType<::RuntimeEvent>; + + /// The admin origin that can set computational limits and initialize the pallet. + type AdminOrigin: EnsureOrigin; + + /// Weight information for this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// The pallet has been (re)initialized. + PalletInitialized { reinit: bool }, + /// The computation limit has been updated. + ComputationLimitSet { compute: FixedU64 }, + /// The storage limit has been updated. + StorageLimitSet { storage: FixedU64 }, + } + + #[pallet::error] + pub enum Error { + /// The pallet was already initialized. + /// + /// Set `witness_count` to `Some` to bypass this error. + AlreadyInitialized, + + /// The limit was over [`crate::RESOURCE_HARD_LIMIT`]. + InsaneLimit, + } + + /// The proportion of the remaining `ref_time` to consume during `on_idle`. + /// + /// `1.0` is mapped to `100%`. Must be at most [`crate::RESOURCE_HARD_LIMIT`]. Setting this to + /// over `1.0` could stall the chain. + #[pallet::storage] + pub(crate) type Compute = StorageValue<_, FixedU64, ValueQuery>; + + /// The proportion of the remaining `proof_size` to consume during `on_idle`. + /// + /// `1.0` is mapped to `100%`. Must be at most [`crate::RESOURCE_HARD_LIMIT`]. Setting this to + /// over `1.0` could stall the chain. + #[pallet::storage] + pub(crate) type Storage = StorageValue<_, FixedU64, ValueQuery>; + + /// Storage map used for wasting proof size. + /// + /// It contains no meaningful data - hence the name "Trash". The maximal number of entries is + /// set to 65k, which is just below the next jump at 16^4. This is important to reduce the proof + /// size benchmarking overestimate. The assumption here is that we won't have more than 65k * + /// 1KiB = 65MiB of proof size wasting in practice. However, this limit is not enforced, so the + /// pallet would also work out of the box with more entries, but its benchmarked proof weight + /// would possibly be underestimated in that case. + #[pallet::storage] + pub(super) type TrashData = StorageMap< + Hasher = Twox64Concat, + Key = u32, + Value = [u8; VALUE_SIZE], + QueryKind = OptionQuery, + MaxValues = ConstU32, + >; + + /// The current number of entries in `TrashData`. + #[pallet::storage] + pub(crate) type TrashDataCount = StorageValue<_, u32, ValueQuery>; + + #[pallet::genesis_config] + #[derive(DefaultNoBound)] + pub struct GenesisConfig { + pub compute: FixedU64, + pub storage: FixedU64, + pub trash_data_count: u32, + #[serde(skip)] + pub _config: sp_std::marker::PhantomData, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + assert!( + self.trash_data_count <= MAX_TRASH_DATA_ENTRIES, + "number of TrashData entries cannot be bigger than {:?}", + MAX_TRASH_DATA_ENTRIES + ); + + (0..self.trash_data_count) + .for_each(|i| TrashData::::insert(i, Pallet::::gen_value(i))); + + TrashDataCount::::set(self.trash_data_count); + + assert!(self.compute <= RESOURCE_HARD_LIMIT, "Compute limit is insane"); + >::put(self.compute); + + assert!(self.storage <= RESOURCE_HARD_LIMIT, "Storage limit is insane"); + >::put(self.storage); + } + } + + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + assert!( + !T::WeightInfo::waste_ref_time_iter(1).ref_time().is_zero(), + "Weight zero; would get stuck in an infinite loop" + ); + assert!( + !T::WeightInfo::waste_proof_size_some(1).proof_size().is_zero(), + "Weight zero; would get stuck in an infinite loop" + ); + } + + fn on_idle(_: BlockNumberFor, remaining_weight: Weight) -> Weight { + let mut meter = WeightMeter::from_limit(remaining_weight); + if meter.try_consume(T::WeightInfo::empty_on_idle()).is_err() { + return T::WeightInfo::empty_on_idle() + } + + let proof_size_limit = + Storage::::get().saturating_mul_int(meter.remaining().proof_size()); + let computation_weight_limit = + Compute::::get().saturating_mul_int(meter.remaining().ref_time()); + let mut meter = WeightMeter::from_limit(Weight::from_parts( + computation_weight_limit, + proof_size_limit, + )); + + Self::waste_at_most_proof_size(&mut meter); + Self::waste_at_most_ref_time(&mut meter); + + meter.consumed() + } + } + + #[pallet::call(weight = T::WeightInfo)] + impl Pallet { + /// Initialize the pallet. Should be called once, if no genesis state was provided. + /// + /// `current_count` is the current number of elements in `TrashData`. This can be set to + /// `None` when the pallet is first initialized. + /// + /// Only callable by Root or `AdminOrigin`. A good default for `new_count` is `5_000`. + #[pallet::call_index(0)] + #[pallet::weight( + T::WeightInfo::initialize_pallet_grow(witness_count.unwrap_or_default()) + .max(T::WeightInfo::initialize_pallet_shrink(witness_count.unwrap_or_default())) + )] + pub fn initialize_pallet( + origin: OriginFor, + new_count: u32, + witness_count: Option, + ) -> DispatchResult { + T::AdminOrigin::try_origin(origin).map(|_| ()).or_else(|o| ensure_root(o))?; + + let current_count = TrashDataCount::::get(); + ensure!( + current_count == witness_count.unwrap_or_default(), + Error::::AlreadyInitialized + ); + + if new_count > current_count { + (current_count..new_count) + .for_each(|i| TrashData::::insert(i, Self::gen_value(i))); + } else { + (new_count..current_count).for_each(TrashData::::remove); + } + + Self::deposit_event(Event::PalletInitialized { reinit: witness_count.is_some() }); + TrashDataCount::::set(new_count); + Ok(()) + } + + /// Set how much of the remaining `ref_time` weight should be consumed by `on_idle`. + /// + /// Only callable by Root or `AdminOrigin`. + #[pallet::call_index(1)] + pub fn set_compute(origin: OriginFor, compute: FixedU64) -> DispatchResult { + T::AdminOrigin::try_origin(origin).map(|_| ()).or_else(|o| ensure_root(o))?; + + ensure!(compute <= RESOURCE_HARD_LIMIT, Error::::InsaneLimit); + Compute::::set(compute); + + Self::deposit_event(Event::ComputationLimitSet { compute }); + Ok(()) + } + + /// Set how much of the remaining `proof_size` weight should be consumed by `on_idle`. + // + /// `1.0` means that all remaining `proof_size` will be consumed. The PoV benchmarking + /// results that are used here are likely an over-estimation. 100% intended consumption will + /// therefore translate to less than 100% actual consumption. + /// + /// Only callable by Root or `AdminOrigin`. + #[pallet::call_index(2)] + pub fn set_storage(origin: OriginFor, storage: FixedU64) -> DispatchResult { + T::AdminOrigin::try_origin(origin).map(|_| ()).or_else(|o| ensure_root(o))?; + + ensure!(storage <= RESOURCE_HARD_LIMIT, Error::::InsaneLimit); + Storage::::set(storage); + + Self::deposit_event(Event::StorageLimitSet { storage }); + Ok(()) + } + } + + impl Pallet { + /// Waste at most the remaining proof size of `meter`. + /// + /// Tries to come as close to the limit as possible. + pub(crate) fn waste_at_most_proof_size(meter: &mut WeightMeter) { + let Ok(n) = Self::calculate_proof_size_iters(&meter) else { return }; + + meter.consume(T::WeightInfo::waste_proof_size_some(n)); + + (0..n).for_each(|i| { + TrashData::::get(i); + }); + } + + /// Calculate how many times `waste_proof_size_some` should be called to fill up `meter`. + fn calculate_proof_size_iters(meter: &WeightMeter) -> Result { + let base = T::WeightInfo::waste_proof_size_some(0); + let slope = T::WeightInfo::waste_proof_size_some(1).saturating_sub(base); + + let remaining = meter.remaining().saturating_sub(base); + let iter_by_proof_size = + remaining.proof_size().checked_div(slope.proof_size()).ok_or(())?; + let iter_by_ref_time = remaining.ref_time().checked_div(slope.ref_time()).ok_or(())?; + + if iter_by_proof_size > 0 && iter_by_proof_size <= iter_by_ref_time { + Ok(iter_by_proof_size as u32) + } else { + Err(()) + } + } + + /// Waste at most the remaining ref time weight of `meter`. + /// + /// Tries to come as close to the limit as possible. + pub(crate) fn waste_at_most_ref_time(meter: &mut WeightMeter) { + let Ok(n) = Self::calculate_ref_time_iters(&meter) else { return }; + meter.consume(T::WeightInfo::waste_ref_time_iter(n)); + + let clobber = Self::waste_ref_time_iter(vec![0u8; 64], n); + + // By casting it into a vec we can hopefully prevent the compiler from optimizing it + // out. Note that `Blake2b512` produces 64 bytes, this is therefore impossible - but the + // compiler does not know that (hopefully). + debug_assert!(clobber.len() == 64); + if clobber.len() == 65 { + TrashData::::insert(0, [clobber[0] as u8; VALUE_SIZE]); + } + } + + /// Wastes some `ref_time`. Receives the previous result as an argument. + /// + /// The ref_time of one iteration should be in the order of 1-10 ms. + pub(crate) fn waste_ref_time_iter(clobber: Vec, i: u32) -> Vec { + let mut hasher = Blake2b512::new(); + + // Blake2 has a very high speed of hashing so we make multiple hashes with it to + // waste more `ref_time` at once. + (0..i).for_each(|_| { + hasher.update(clobber.as_slice()); + }); + + hasher.finalize().to_vec() + } + + /// Calculate how many times `waste_ref_time_iter` should be called to fill up `meter`. + fn calculate_ref_time_iters(meter: &WeightMeter) -> Result { + let base = T::WeightInfo::waste_ref_time_iter(0); + let slope = T::WeightInfo::waste_ref_time_iter(1).saturating_sub(base); + if !slope.proof_size().is_zero() || !base.proof_size().is_zero() { + return Err(()) + } + + match meter + .remaining() + .ref_time() + .saturating_sub(base.ref_time()) + .checked_div(slope.ref_time()) + { + Some(0) | None => Err(()), + Some(i) => Ok(i as u32), + } + } + + /// Generate a pseudo-random deterministic value from a `seed`. + pub(crate) fn gen_value(seed: u32) -> [u8; VALUE_SIZE] { + let mut ret = [0u8; VALUE_SIZE]; + + for i in 0u32..(VALUE_SIZE as u32 / 32) { + let hash = (seed, i).using_encoded(twox_256); + ret[i as usize * 32..(i + 1) as usize * 32].copy_from_slice(&hash); + } + + ret + } + } +} diff --git a/frame/glutton/src/mock.rs b/frame/glutton/src/mock.rs new file mode 100644 index 000000000000..c79ddd53718e --- /dev/null +++ b/frame/glutton/src/mock.rs @@ -0,0 +1,87 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +use super::*; +use crate as pallet_glutton; + +use frame_support::{ + assert_ok, + traits::{ConstU32, ConstU64}, +}; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Glutton: pallet_glutton::{Pallet, Event}, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type Hash = H256; + type RuntimeCall = RuntimeCall; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type AdminOrigin = frame_system::EnsureRoot; + type WeightInfo = (); +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +/// Set the `compute` and `storage` limits. +/// +/// `1.0` corresponds to `100%`. +pub fn set_limits(compute: f64, storage: f64) { + assert_ok!(Glutton::set_compute(RuntimeOrigin::root(), FixedU64::from_float(compute))); + assert_ok!(Glutton::set_storage(RuntimeOrigin::root(), FixedU64::from_float(storage))); +} diff --git a/frame/glutton/src/tests.rs b/frame/glutton/src/tests.rs new file mode 100644 index 000000000000..1897ff63a70f --- /dev/null +++ b/frame/glutton/src/tests.rs @@ -0,0 +1,294 @@ +// This file is part of Substrate. + +// Copyright (C) 2023 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 glutton pallet. + +use super::{mock::*, *}; + +use frame_support::{assert_err, assert_noop, assert_ok, weights::constants::*}; +use sp_runtime::{traits::One, Perbill}; + +const CALIBRATION_ERROR: &'static str = + "Weight calibration failed. Please re-run the benchmarks on the same hardware."; + +#[test] +fn initialize_pallet_works() { + new_test_ext().execute_with(|| { + assert_eq!(TrashData::::get(0), None); + + assert_noop!( + Glutton::initialize_pallet(RuntimeOrigin::signed(1), 3, None), + DispatchError::BadOrigin + ); + assert_noop!( + Glutton::initialize_pallet(RuntimeOrigin::none(), 3, None), + DispatchError::BadOrigin + ); + + assert_ok!(Glutton::initialize_pallet(RuntimeOrigin::root(), 2, None)); + System::assert_last_event(Event::PalletInitialized { reinit: false }.into()); + assert_err!( + Glutton::initialize_pallet(RuntimeOrigin::root(), 2, None), + Error::::AlreadyInitialized + ); + + assert_eq!(TrashData::::get(0), Some(Pallet::::gen_value(0))); + assert_eq!(TrashData::::get(1), Some(Pallet::::gen_value(1))); + assert_eq!(TrashData::::get(2), None); + + assert_eq!(TrashDataCount::::get(), 2); + + assert_ok!(Glutton::initialize_pallet(RuntimeOrigin::root(), 20, Some(2))); + + assert_eq!(TrashDataCount::::get(), 20); + assert_eq!(TrashData::::iter_keys().count(), 20); + }); +} + +#[test] +fn expand_and_shrink_trash_data_works() { + new_test_ext().execute_with(|| { + assert_eq!(TrashDataCount::::get(), 0); + + assert_ok!(Glutton::initialize_pallet(RuntimeOrigin::root(), 5000, None)); + assert_eq!(TrashDataCount::::get(), 5000); + assert_eq!(TrashData::::iter_keys().count(), 5000); + + assert_ok!(Glutton::initialize_pallet(RuntimeOrigin::root(), 8000, Some(5000))); + assert_eq!(TrashDataCount::::get(), 8000); + assert_eq!(TrashData::::iter_keys().count(), 8000); + + assert_ok!(Glutton::initialize_pallet(RuntimeOrigin::root(), 6000, Some(8000))); + assert_eq!(TrashDataCount::::get(), 6000); + assert_eq!(TrashData::::iter_keys().count(), 6000); + + assert_noop!( + Glutton::initialize_pallet(RuntimeOrigin::root(), 0, None), + Error::::AlreadyInitialized + ); + assert_ok!(Glutton::initialize_pallet(RuntimeOrigin::root(), 0, Some(6000))); + assert_eq!(TrashDataCount::::get(), 0); + assert_eq!(TrashData::::iter_keys().count(), 0); + }); +} + +#[test] +fn setting_compute_works() { + new_test_ext().execute_with(|| { + assert_eq!(Compute::::get(), Zero::zero()); + + assert_ok!(Glutton::set_compute(RuntimeOrigin::root(), FixedU64::from_float(0.3))); + assert_eq!(Compute::::get(), FixedU64::from_float(0.3)); + System::assert_last_event( + Event::ComputationLimitSet { compute: FixedU64::from_float(0.3) }.into(), + ); + + assert_noop!( + Glutton::set_compute(RuntimeOrigin::signed(1), FixedU64::from_float(0.5)), + DispatchError::BadOrigin + ); + assert_noop!( + Glutton::set_compute(RuntimeOrigin::none(), FixedU64::from_float(0.5)), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn setting_compute_respects_limit() { + new_test_ext().execute_with(|| { + // < 1000% is fine + assert_ok!(Glutton::set_compute(RuntimeOrigin::root(), FixedU64::from_float(9.99)),); + // == 1000% is fine + assert_ok!(Glutton::set_compute(RuntimeOrigin::root(), FixedU64::from_u32(10)),); + // > 1000% is not + assert_noop!( + Glutton::set_compute(RuntimeOrigin::root(), FixedU64::from_float(10.01)), + Error::::InsaneLimit + ); + }); +} + +#[test] +fn setting_storage_works() { + new_test_ext().execute_with(|| { + assert!(Storage::::get().is_zero()); + + assert_ok!(Glutton::set_storage(RuntimeOrigin::root(), FixedU64::from_float(0.3))); + assert_eq!(Storage::::get(), FixedU64::from_float(0.3)); + System::assert_last_event( + Event::StorageLimitSet { storage: FixedU64::from_float(0.3) }.into(), + ); + + assert_noop!( + Glutton::set_storage(RuntimeOrigin::signed(1), FixedU64::from_float(0.5)), + DispatchError::BadOrigin + ); + assert_noop!( + Glutton::set_storage(RuntimeOrigin::none(), FixedU64::from_float(0.5)), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn setting_storage_respects_limit() { + new_test_ext().execute_with(|| { + // < 1000% is fine + assert_ok!(Glutton::set_storage(RuntimeOrigin::root(), FixedU64::from_float(9.99)),); + // == 1000% is fine + assert_ok!(Glutton::set_storage(RuntimeOrigin::root(), FixedU64::from_u32(10)),); + // > 1000% is not + assert_noop!( + Glutton::set_storage(RuntimeOrigin::root(), FixedU64::from_float(10.01)), + Error::::InsaneLimit + ); + }); +} + +#[test] +fn on_idle_works() { + new_test_ext().execute_with(|| { + set_limits(One::one(), One::one()); + + Glutton::on_idle(1, Weight::from_parts(20_000_000, 0)); + }); +} + +/// Check that the expected is close enough to the consumed weight. +#[test] +fn on_idle_weight_high_proof_is_close_enough_works() { + new_test_ext().execute_with(|| { + set_limits(One::one(), One::one()); + + let should = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, WEIGHT_PROOF_SIZE_PER_MB * 5); + let got = Glutton::on_idle(1, should); + assert!(got.all_lte(should), "Consumed too much weight"); + + let ratio = Perbill::from_rational(got.proof_size(), should.proof_size()); + assert!( + ratio >= Perbill::from_percent(99), + "Too few proof size consumed, was only {ratio:?} of expected", + ); + + let ratio = Perbill::from_rational(got.ref_time(), should.ref_time()); + assert!( + ratio >= Perbill::from_percent(99), + "Too few ref time consumed, was only {ratio:?} of expected", + ); + }); +} + +#[test] +fn on_idle_weight_low_proof_is_close_enough_works() { + new_test_ext().execute_with(|| { + set_limits(One::one(), One::one()); + + let should = Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, WEIGHT_PROOF_SIZE_PER_KB * 20); + let got = Glutton::on_idle(1, should); + assert!(got.all_lte(should), "Consumed too much weight"); + + let ratio = Perbill::from_rational(got.proof_size(), should.proof_size()); + // Just a sanity check here for > 0 + assert!( + ratio >= Perbill::from_percent(50), + "Too few proof size consumed, was only {ratio:?} of expected", + ); + + let ratio = Perbill::from_rational(got.ref_time(), should.ref_time()); + assert!( + ratio >= Perbill::from_percent(99), + "Too few ref time consumed, was only {ratio:?} of expected", + ); + }); +} + +#[test] +fn on_idle_weight_over_unity_is_close_enough_works() { + new_test_ext().execute_with(|| { + // Para blocks get ~500ms compute and ~5MB proof size. + let max_block = + Weight::from_parts(500 * WEIGHT_REF_TIME_PER_MILLIS, 5 * WEIGHT_PROOF_SIZE_PER_MB); + // But now we tell it to consume more than that. + set_limits(1.75, 1.5); + let want = Weight::from_parts( + (1.75 * max_block.ref_time() as f64) as u64, + (1.5 * max_block.proof_size() as f64) as u64, + ); + + let consumed = Glutton::on_idle(1, max_block); + assert!(consumed.all_gt(max_block), "Must consume more than the block limit"); + assert!(consumed.all_lte(want), "Consumed more than the requested weight"); + + let ratio = Perbill::from_rational(consumed.proof_size(), want.proof_size()); + assert!( + ratio >= Perbill::from_percent(99), + "Too few proof size consumed, was only {ratio:?} of expected", + ); + + let ratio = Perbill::from_rational(consumed.ref_time(), want.ref_time()); + assert!( + ratio >= Perbill::from_percent(99), + "Too few ref time consumed, was only {ratio:?} of expected", + ); + }); +} + +#[test] +fn waste_at_most_ref_time_weight_close_enough() { + new_test_ext().execute_with(|| { + let mut meter = + WeightMeter::from_limit(Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND, u64::MAX)); + // Over-spending fails defensively. + Glutton::waste_at_most_ref_time(&mut meter); + + // We require it to be under-spend by at most 1%. + assert!( + meter.consumed_ratio() >= Perbill::from_percent(99), + "{CALIBRATION_ERROR}\nConsumed too few: {:?}", + meter.consumed_ratio() + ); + }); +} + +#[test] +fn waste_at_most_proof_size_weight_close_enough() { + new_test_ext().execute_with(|| { + let mut meter = + WeightMeter::from_limit(Weight::from_parts(u64::MAX, WEIGHT_PROOF_SIZE_PER_MB * 5)); + // Over-spending fails defensively. + Glutton::waste_at_most_proof_size(&mut meter); + + // We require it to be under-spend by at most 1%. + assert!( + meter.consumed_ratio() >= Perbill::from_percent(99), + "{CALIBRATION_ERROR}\nConsumed too few: {:?}", + meter.consumed_ratio() + ); + }); +} + +#[test] +fn gen_value_works() { + let g0 = Pallet::::gen_value(0); + let g1 = Pallet::::gen_value(1); + + assert_eq!(g0.len(), VALUE_SIZE); + assert_ne!(g0, g1, "Is distinct"); + assert_ne!(g0, [0; VALUE_SIZE], "Is not zero"); + assert_eq!(g0, Pallet::::gen_value(0), "Is deterministic"); +} diff --git a/frame/glutton/src/weights.rs b/frame/glutton/src/weights.rs new file mode 100644 index 000000000000..cbc0fb022f51 --- /dev/null +++ b/frame/glutton/src/weights.rs @@ -0,0 +1,309 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_glutton +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_glutton +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/glutton/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for pallet_glutton. +pub trait WeightInfo { + fn initialize_pallet_grow(n: u32, ) -> Weight; + fn initialize_pallet_shrink(n: u32, ) -> Weight; + fn waste_ref_time_iter(i: u32, ) -> Weight; + fn waste_proof_size_some(i: u32, ) -> Weight; + fn on_idle_high_proof_waste() -> Weight; + fn on_idle_low_proof_waste() -> Weight; + fn empty_on_idle() -> Weight; + fn set_compute() -> Weight; + fn set_storage() -> Weight; +} + +/// Weights for pallet_glutton using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: Glutton TrashDataCount (r:1 w:1) + /// Proof: Glutton TrashDataCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Glutton TrashData (r:0 w:1000) + /// Proof: Glutton TrashData (max_values: Some(65000), max_size: Some(1036), added: 3016, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 1000]`. + fn initialize_pallet_grow(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `86` + // Estimated: `1489` + // Minimum execution time: 11_488_000 picoseconds. + Weight::from_parts(93_073_710, 1489) + // Standard Error: 22_390 + .saturating_add(Weight::from_parts(9_572_012, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + } + /// Storage: Glutton TrashDataCount (r:1 w:1) + /// Proof: Glutton TrashDataCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Glutton TrashData (r:0 w:1000) + /// Proof: Glutton TrashData (max_values: Some(65000), max_size: Some(1036), added: 3016, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 1000]`. + fn initialize_pallet_shrink(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `119` + // Estimated: `1489` + // Minimum execution time: 11_378_000 picoseconds. + Weight::from_parts(5_591_508, 1489) + // Standard Error: 1_592 + .saturating_add(Weight::from_parts(1_163_758, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + } + /// The range of component `i` is `[0, 100000]`. + fn waste_ref_time_iter(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 669_000 picoseconds. + Weight::from_parts(990_745, 0) + // Standard Error: 10 + .saturating_add(Weight::from_parts(105_224, 0).saturating_mul(i.into())) + } + /// Storage: Glutton TrashData (r:5000 w:0) + /// Proof: Glutton TrashData (max_values: Some(65000), max_size: Some(1036), added: 3016, mode: MaxEncodedLen) + /// The range of component `i` is `[0, 5000]`. + fn waste_proof_size_some(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `119114 + i * (1022 ±0)` + // Estimated: `990 + i * (3016 ±0)` + // Minimum execution time: 435_000 picoseconds. + Weight::from_parts(66_547_542, 990) + // Standard Error: 4_557 + .saturating_add(Weight::from_parts(5_851_324, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into()))) + .saturating_add(Weight::from_parts(0, 3016).saturating_mul(i.into())) + } + /// Storage: Glutton Storage (r:1 w:0) + /// Proof: Glutton Storage (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Glutton Compute (r:1 w:0) + /// Proof: Glutton Compute (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Glutton TrashData (r:1737 w:0) + /// Proof: Glutton TrashData (max_values: Some(65000), max_size: Some(1036), added: 3016, mode: MaxEncodedLen) + fn on_idle_high_proof_waste() -> Weight { + // Proof Size summary in bytes: + // Measured: `1900497` + // Estimated: `5239782` + // Minimum execution time: 67_699_845_000 picoseconds. + Weight::from_parts(67_893_204_000, 5239782) + .saturating_add(T::DbWeight::get().reads(1739_u64)) + } + /// Storage: Glutton Storage (r:1 w:0) + /// Proof: Glutton Storage (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Glutton Compute (r:1 w:0) + /// Proof: Glutton Compute (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Glutton TrashData (r:5 w:0) + /// Proof: Glutton TrashData (max_values: Some(65000), max_size: Some(1036), added: 3016, mode: MaxEncodedLen) + fn on_idle_low_proof_waste() -> Weight { + // Proof Size summary in bytes: + // Measured: `9547` + // Estimated: `16070` + // Minimum execution time: 122_297_527_000 picoseconds. + Weight::from_parts(122_394_818_000, 16070) + .saturating_add(T::DbWeight::get().reads(7_u64)) + } + /// Storage: Glutton Storage (r:1 w:0) + /// Proof: Glutton Storage (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Glutton Compute (r:1 w:0) + /// Proof: Glutton Compute (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + fn empty_on_idle() -> Weight { + // Proof Size summary in bytes: + // Measured: `86` + // Estimated: `1493` + // Minimum execution time: 5_882_000 picoseconds. + Weight::from_parts(6_138_000, 1493) + .saturating_add(T::DbWeight::get().reads(2_u64)) + } + /// Storage: Glutton Compute (r:0 w:1) + /// Proof: Glutton Compute (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + fn set_compute() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_830_000 picoseconds. + Weight::from_parts(8_366_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Glutton Storage (r:0 w:1) + /// Proof: Glutton Storage (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + fn set_storage() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_933_000 picoseconds. + Weight::from_parts(8_213_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: Glutton TrashDataCount (r:1 w:1) + /// Proof: Glutton TrashDataCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Glutton TrashData (r:0 w:1000) + /// Proof: Glutton TrashData (max_values: Some(65000), max_size: Some(1036), added: 3016, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 1000]`. + fn initialize_pallet_grow(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `86` + // Estimated: `1489` + // Minimum execution time: 11_488_000 picoseconds. + Weight::from_parts(93_073_710, 1489) + // Standard Error: 22_390 + .saturating_add(Weight::from_parts(9_572_012, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + } + /// Storage: Glutton TrashDataCount (r:1 w:1) + /// Proof: Glutton TrashDataCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Glutton TrashData (r:0 w:1000) + /// Proof: Glutton TrashData (max_values: Some(65000), max_size: Some(1036), added: 3016, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 1000]`. + fn initialize_pallet_shrink(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `119` + // Estimated: `1489` + // Minimum execution time: 11_378_000 picoseconds. + Weight::from_parts(5_591_508, 1489) + // Standard Error: 1_592 + .saturating_add(Weight::from_parts(1_163_758, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + } + /// The range of component `i` is `[0, 100000]`. + fn waste_ref_time_iter(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 669_000 picoseconds. + Weight::from_parts(990_745, 0) + // Standard Error: 10 + .saturating_add(Weight::from_parts(105_224, 0).saturating_mul(i.into())) + } + /// Storage: Glutton TrashData (r:5000 w:0) + /// Proof: Glutton TrashData (max_values: Some(65000), max_size: Some(1036), added: 3016, mode: MaxEncodedLen) + /// The range of component `i` is `[0, 5000]`. + fn waste_proof_size_some(i: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `119114 + i * (1022 ±0)` + // Estimated: `990 + i * (3016 ±0)` + // Minimum execution time: 435_000 picoseconds. + Weight::from_parts(66_547_542, 990) + // Standard Error: 4_557 + .saturating_add(Weight::from_parts(5_851_324, 0).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(i.into()))) + .saturating_add(Weight::from_parts(0, 3016).saturating_mul(i.into())) + } + /// Storage: Glutton Storage (r:1 w:0) + /// Proof: Glutton Storage (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Glutton Compute (r:1 w:0) + /// Proof: Glutton Compute (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Glutton TrashData (r:1737 w:0) + /// Proof: Glutton TrashData (max_values: Some(65000), max_size: Some(1036), added: 3016, mode: MaxEncodedLen) + fn on_idle_high_proof_waste() -> Weight { + // Proof Size summary in bytes: + // Measured: `1900497` + // Estimated: `5239782` + // Minimum execution time: 67_699_845_000 picoseconds. + Weight::from_parts(67_893_204_000, 5239782) + .saturating_add(RocksDbWeight::get().reads(1739_u64)) + } + /// Storage: Glutton Storage (r:1 w:0) + /// Proof: Glutton Storage (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Glutton Compute (r:1 w:0) + /// Proof: Glutton Compute (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Glutton TrashData (r:5 w:0) + /// Proof: Glutton TrashData (max_values: Some(65000), max_size: Some(1036), added: 3016, mode: MaxEncodedLen) + fn on_idle_low_proof_waste() -> Weight { + // Proof Size summary in bytes: + // Measured: `9547` + // Estimated: `16070` + // Minimum execution time: 122_297_527_000 picoseconds. + Weight::from_parts(122_394_818_000, 16070) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + } + /// Storage: Glutton Storage (r:1 w:0) + /// Proof: Glutton Storage (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Glutton Compute (r:1 w:0) + /// Proof: Glutton Compute (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + fn empty_on_idle() -> Weight { + // Proof Size summary in bytes: + // Measured: `86` + // Estimated: `1493` + // Minimum execution time: 5_882_000 picoseconds. + Weight::from_parts(6_138_000, 1493) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + } + /// Storage: Glutton Compute (r:0 w:1) + /// Proof: Glutton Compute (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + fn set_compute() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_830_000 picoseconds. + Weight::from_parts(8_366_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Glutton Storage (r:0 w:1) + /// Proof: Glutton Storage (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + fn set_storage() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_933_000 picoseconds. + Weight::from_parts(8_213_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } +} diff --git a/frame/grandpa/Cargo.toml b/frame/grandpa/Cargo.toml index fa63b8427523..793a694b438b 100644 --- a/frame/grandpa/Cargo.toml +++ b/frame/grandpa/Cargo.toml @@ -13,25 +13,25 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive", "serde"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } pallet-session = { version = "4.0.0-dev", default-features = false, path = "../session" } -sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-finality-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../primitives/finality-grandpa" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../../primitives/application-crypto", features = ["serde"] } +sp-consensus-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../primitives/consensus/grandpa", features = ["serde"] } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core", features = ["serde"] } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime", features = ["serde"] } sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } -sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking", features = ["serde"] } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -grandpa = { package = "finality-grandpa", version = "0.16.1", features = ["derive-codec"] } +grandpa = { package = "finality-grandpa", version = "0.16.2", features = ["derive-codec"] } frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" } frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } @@ -39,7 +39,7 @@ pallet-offences = { version = "4.0.0-dev", path = "../offences" } pallet-staking = { version = "4.0.0-dev", path = "../staking" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } -sp-keyring = { version = "7.0.0", path = "../../primitives/keyring" } +sp-keyring = { version = "24.0.0", path = "../../primitives/keyring" } [features] default = ["std"] @@ -54,7 +54,7 @@ std = [ "scale-info/std", "sp-application-crypto/std", "sp-core/std", - "sp-finality-grandpa/std", + "sp-consensus-grandpa/std", "sp-io/std", "sp-runtime/std", "sp-session/std", diff --git a/frame/grandpa/src/benchmarking.rs b/frame/grandpa/src/benchmarking.rs index 124085914992..7a87f0c4b078 100644 --- a/frame/grandpa/src/benchmarking.rs +++ b/frame/grandpa/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ //! Benchmarks for the GRANDPA pallet. use super::{Pallet as Grandpa, *}; -use frame_benchmarking::benchmarks; +use frame_benchmarking::v1::benchmarks; use frame_system::RawOrigin; use sp_core::H256; @@ -50,14 +50,14 @@ benchmarks! { 251, 129, 29, 45, 32, 29, 6 ]; - let equivocation_proof1: sp_finality_grandpa::EquivocationProof = + let equivocation_proof1: sp_consensus_grandpa::EquivocationProof = Decode::decode(&mut &EQUIVOCATION_PROOF_BLOB[..]).unwrap(); let equivocation_proof2 = equivocation_proof1.clone(); }: { - sp_finality_grandpa::check_equivocation_proof(equivocation_proof1); + sp_consensus_grandpa::check_equivocation_proof(equivocation_proof1); } verify { - assert!(sp_finality_grandpa::check_equivocation_proof(equivocation_proof2)); + assert!(sp_consensus_grandpa::check_equivocation_proof(equivocation_proof2)); } note_stalled { diff --git a/frame/grandpa/src/default_weights.rs b/frame/grandpa/src/default_weights.rs index ba343cabac62..5ccf3794880e 100644 --- a/frame/grandpa/src/default_weights.rs +++ b/frame/grandpa/src/default_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,37 +24,34 @@ use frame_support::weights::{ }; impl crate::WeightInfo for () { - fn report_equivocation(validator_count: u32) -> Weight { + fn report_equivocation(validator_count: u32, max_nominators_per_validator: u32) -> Weight { // we take the validator set count from the membership proof to // calculate the weight but we set a floor of 100 validators. let validator_count = validator_count.max(100) as u64; - // worst case we are considering is that the given offender - // is backed by 200 nominators - const MAX_NOMINATORS: u64 = 200; - // checking membership proof - Weight::from_ref_time(35u64 * WEIGHT_REF_TIME_PER_MICROS) + Weight::from_parts(35u64 * WEIGHT_REF_TIME_PER_MICROS, 0) .saturating_add( - Weight::from_ref_time(175u64 * WEIGHT_REF_TIME_PER_NANOS) + Weight::from_parts(175u64 * WEIGHT_REF_TIME_PER_NANOS, 0) .saturating_mul(validator_count), ) .saturating_add(DbWeight::get().reads(5)) // check equivocation proof - .saturating_add(Weight::from_ref_time(95u64 * WEIGHT_REF_TIME_PER_MICROS)) + .saturating_add(Weight::from_parts(95u64 * WEIGHT_REF_TIME_PER_MICROS, 0)) // report offence - .saturating_add(Weight::from_ref_time(110u64 * WEIGHT_REF_TIME_PER_MICROS)) - .saturating_add(Weight::from_ref_time( - 25u64 * WEIGHT_REF_TIME_PER_MICROS * MAX_NOMINATORS, + .saturating_add(Weight::from_parts(110u64 * WEIGHT_REF_TIME_PER_MICROS, 0)) + .saturating_add(Weight::from_parts( + 25u64 * WEIGHT_REF_TIME_PER_MICROS * max_nominators_per_validator as u64, + 0, )) - .saturating_add(DbWeight::get().reads(14 + 3 * MAX_NOMINATORS)) - .saturating_add(DbWeight::get().writes(10 + 3 * MAX_NOMINATORS)) + .saturating_add(DbWeight::get().reads(14 + 3 * max_nominators_per_validator as u64)) + .saturating_add(DbWeight::get().writes(10 + 3 * max_nominators_per_validator as u64)) // fetching set id -> session index mappings .saturating_add(DbWeight::get().reads(2)) } fn note_stalled() -> Weight { - Weight::from_ref_time(3u64 * WEIGHT_REF_TIME_PER_MICROS) + Weight::from_parts(3u64 * WEIGHT_REF_TIME_PER_MICROS, 0) .saturating_add(DbWeight::get().writes(1)) } } diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index 23801a4982a8..16727f79a58d 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,162 +35,203 @@ //! that the `ValidateUnsigned` for the GRANDPA pallet is used in the runtime //! definition. -use sp_std::prelude::*; - use codec::{self as codec, Decode, Encode}; use frame_support::traits::{Get, KeyOwnerProofSystem}; -use sp_finality_grandpa::{EquivocationProof, RoundNumber, SetId}; +use frame_system::pallet_prelude::BlockNumberFor; +use log::{error, info}; +use sp_consensus_grandpa::{AuthorityId, EquivocationProof, RoundNumber, SetId, KEY_TYPE}; use sp_runtime::{ transaction_validity::{ InvalidTransaction, TransactionPriority, TransactionSource, TransactionValidity, TransactionValidityError, ValidTransaction, }, - DispatchResult, Perbill, + DispatchError, KeyTypeId, Perbill, }; +use sp_session::{GetSessionNumber, GetValidatorCount}; use sp_staking::{ - offence::{Kind, Offence, OffenceError, ReportOffence}, + offence::{Kind, Offence, OffenceReportSystem, ReportOffence}, SessionIndex, }; +use sp_std::prelude::*; + +use super::{Call, Config, Error, Pallet, LOG_TARGET}; -use super::{Call, Config, Pallet, LOG_TARGET}; - -/// A trait with utility methods for handling equivocation reports in GRANDPA. -/// The offence type is generic, and the trait provides , reporting an offence -/// triggered by a valid equivocation report, and also for creating and -/// submitting equivocation report extrinsics (useful only in offchain context). -pub trait HandleEquivocation { - /// The offence type used for reporting offences on valid equivocation reports. - type Offence: GrandpaOffence; - - /// The longevity, in blocks, that the equivocation report is valid for. When using the staking - /// pallet this should be equal to the bonding duration (in blocks, not eras). - type ReportLongevity: Get; - - /// Report an offence proved by the given reporters. - fn report_offence( - reporters: Vec, - offence: Self::Offence, - ) -> Result<(), OffenceError>; - - /// Returns true if all of the offenders at the given time slot have already been reported. - fn is_known_offence( - offenders: &[T::KeyOwnerIdentification], - time_slot: &>::TimeSlot, - ) -> bool; - - /// Create and dispatch an equivocation report extrinsic. - fn submit_unsigned_equivocation_report( - equivocation_proof: EquivocationProof, - key_owner_proof: T::KeyOwnerProof, - ) -> DispatchResult; - - /// Fetch the current block author id, if defined. - fn block_author() -> Option; +/// A round number and set id which point on the time of an offence. +#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode)] +pub struct TimeSlot { + // The order of these matters for `derive(Ord)`. + /// Grandpa Set ID. + pub set_id: SetId, + /// Round number. + pub round: RoundNumber, } -impl HandleEquivocation for () { - type Offence = GrandpaEquivocationOffence; - type ReportLongevity = (); +/// GRANDPA equivocation offence report. +pub struct EquivocationOffence { + /// Time slot at which this incident happened. + pub time_slot: TimeSlot, + /// The session index in which the incident happened. + pub session_index: SessionIndex, + /// The size of the validator set at the time of the offence. + pub validator_set_count: u32, + /// The authority which produced this equivocation. + pub offender: Offender, +} - fn report_offence( - _reporters: Vec, - _offence: GrandpaEquivocationOffence, - ) -> Result<(), OffenceError> { - Ok(()) - } +impl Offence for EquivocationOffence { + const ID: Kind = *b"grandpa:equivoca"; + type TimeSlot = TimeSlot; - fn is_known_offence( - _offenders: &[T::KeyOwnerIdentification], - _time_slot: &GrandpaTimeSlot, - ) -> bool { - true + fn offenders(&self) -> Vec { + vec![self.offender.clone()] } - fn submit_unsigned_equivocation_report( - _equivocation_proof: EquivocationProof, - _key_owner_proof: T::KeyOwnerProof, - ) -> DispatchResult { - Ok(()) + fn session_index(&self) -> SessionIndex { + self.session_index } - fn block_author() -> Option { - None + fn validator_set_count(&self) -> u32 { + self.validator_set_count } -} -/// Generic equivocation handler. This type implements `HandleEquivocation` -/// using existing subsystems that are part of frame (type bounds described -/// below) and will dispatch to them directly, it's only purpose is to wire all -/// subsystems together. -pub struct EquivocationHandler> { - _phantom: sp_std::marker::PhantomData<(I, R, L, O)>, -} + fn time_slot(&self) -> Self::TimeSlot { + self.time_slot + } -impl Default for EquivocationHandler { - fn default() -> Self { - Self { _phantom: Default::default() } + // The formula is min((3k / n)^2, 1) + // where k = offenders_number and n = validators_number + fn slash_fraction(&self, offenders_count: u32) -> Perbill { + // Perbill type domain is [0, 1] by definition + Perbill::from_rational(3 * offenders_count, self.validator_set_count).square() } } -impl HandleEquivocation for EquivocationHandler +/// GRANDPA equivocation offence report system. +/// +/// This type implements `OffenceReportSystem` such that: +/// - Equivocation reports are published on-chain as unsigned extrinsic via +/// `offchain::SendTransactionTypes`. +/// - On-chain validity checks and processing are mostly delegated to the user provided generic +/// types implementing `KeyOwnerProofSystem` and `ReportOffence` traits. +/// - Offence reporter for unsigned transactions is fetched via the the authorship pallet. +pub struct EquivocationReportSystem(sp_std::marker::PhantomData<(T, R, P, L)>); + +impl + OffenceReportSystem< + Option, + (EquivocationProof>, T::KeyOwnerProof), + > for EquivocationReportSystem where - // We use the authorship pallet to fetch the current block author and use - // `offchain::SendTransactionTypes` for unsigned extrinsic creation and - // submission. T: Config + pallet_authorship::Config + frame_system::offchain::SendTransactionTypes>, - // A system for reporting offences after valid equivocation reports are - // processed. - R: ReportOffence, - // The longevity (in blocks) that the equivocation report is valid for. When using the staking - // pallet this should be the bonding duration. + R: ReportOffence< + T::AccountId, + P::IdentificationTuple, + EquivocationOffence, + >, + P: KeyOwnerProofSystem<(KeyTypeId, AuthorityId), Proof = T::KeyOwnerProof>, + P::IdentificationTuple: Clone, L: Get, - // The offence type that should be used when reporting. - O: GrandpaOffence, { - type Offence = O; - type ReportLongevity = L; - - fn report_offence(reporters: Vec, offence: O) -> Result<(), OffenceError> { - R::report_offence(reporters, offence) - } - - fn is_known_offence(offenders: &[T::KeyOwnerIdentification], time_slot: &O::TimeSlot) -> bool { - R::is_known_offence(offenders, time_slot) - } + type Longevity = L; - fn submit_unsigned_equivocation_report( - equivocation_proof: EquivocationProof, - key_owner_proof: T::KeyOwnerProof, - ) -> DispatchResult { + fn publish_evidence( + evidence: (EquivocationProof>, T::KeyOwnerProof), + ) -> Result<(), ()> { use frame_system::offchain::SubmitTransaction; + let (equivocation_proof, key_owner_proof) = evidence; let call = Call::report_equivocation_unsigned { equivocation_proof: Box::new(equivocation_proof), key_owner_proof, }; - - match SubmitTransaction::>::submit_unsigned_transaction(call.into()) { - Ok(()) => log::info!(target: LOG_TARGET, "Submitted GRANDPA equivocation report.",), - Err(e) => - log::error!(target: LOG_TARGET, "Error submitting equivocation report: {:?}", e,), + let res = SubmitTransaction::>::submit_unsigned_transaction(call.into()); + match res { + Ok(_) => info!(target: LOG_TARGET, "Submitted equivocation report"), + Err(e) => error!(target: LOG_TARGET, "Error submitting equivocation report: {:?}", e), } - - Ok(()) + res } - fn block_author() -> Option { - >::author() + fn check_evidence( + evidence: (EquivocationProof>, T::KeyOwnerProof), + ) -> Result<(), TransactionValidityError> { + let (equivocation_proof, key_owner_proof) = evidence; + + // Check the membership proof to extract the offender's id + let key = (KEY_TYPE, equivocation_proof.offender().clone()); + let offender = P::check_proof(key, key_owner_proof).ok_or(InvalidTransaction::BadProof)?; + + // Check if the offence has already been reported, and if so then we can discard the report. + let time_slot = + TimeSlot { set_id: equivocation_proof.set_id(), round: equivocation_proof.round() }; + if R::is_known_offence(&[offender], &time_slot) { + Err(InvalidTransaction::Stale.into()) + } else { + Ok(()) + } } -} -/// A round number and set id which point on the time of an offence. -#[derive(Copy, Clone, PartialOrd, Ord, Eq, PartialEq, Encode, Decode)] -pub struct GrandpaTimeSlot { - // The order of these matters for `derive(Ord)`. - /// Grandpa Set ID. - pub set_id: SetId, - /// Round number. - pub round: RoundNumber, + fn process_evidence( + reporter: Option, + evidence: (EquivocationProof>, T::KeyOwnerProof), + ) -> Result<(), DispatchError> { + let (equivocation_proof, key_owner_proof) = evidence; + let reporter = reporter.or_else(|| >::author()); + let offender = equivocation_proof.offender().clone(); + + // We check the equivocation within the context of its set id (and + // associated session) and round. We also need to know the validator + // set count when the offence since it is required to calculate the + // slash amount. + let set_id = equivocation_proof.set_id(); + let round = equivocation_proof.round(); + let session_index = key_owner_proof.session(); + let validator_set_count = key_owner_proof.validator_count(); + + // Validate equivocation proof (check votes are different and signatures are valid). + if !sp_consensus_grandpa::check_equivocation_proof(equivocation_proof) { + return Err(Error::::InvalidEquivocationProof.into()) + } + + // Validate the key ownership proof extracting the id of the offender. + let offender = P::check_proof((KEY_TYPE, offender), key_owner_proof) + .ok_or(Error::::InvalidKeyOwnershipProof)?; + + // Fetch the current and previous sets last session index. + // For genesis set there's no previous set. + let previous_set_id_session_index = if set_id != 0 { + let idx = crate::SetIdSession::::get(set_id - 1) + .ok_or(Error::::InvalidEquivocationProof)?; + Some(idx) + } else { + None + }; + + let set_id_session_index = + crate::SetIdSession::::get(set_id).ok_or(Error::::InvalidEquivocationProof)?; + + // Check that the session id for the membership proof is within the + // bounds of the set id reported in the equivocation. + if session_index > set_id_session_index || + previous_set_id_session_index + .map(|previous_index| session_index <= previous_index) + .unwrap_or(false) + { + return Err(Error::::InvalidEquivocationProof.into()) + } + + let offence = EquivocationOffence { + time_slot: TimeSlot { set_id, round }, + session_index, + offender, + validator_set_count, + }; + + R::report_offence(reporter.into_iter().collect(), offence) + .map_err(|_| Error::::DuplicateOffenceReport)?; + + Ok(()) + } } /// Methods for the `ValidateUnsigned` implementation: @@ -213,11 +254,12 @@ impl Pallet { }, } - // check report staleness - is_known_offence::(equivocation_proof, key_owner_proof)?; + // Check report validity + let evidence = (*equivocation_proof.clone(), key_owner_proof.clone()); + T::EquivocationReportSystem::check_evidence(evidence)?; let longevity = - >::ReportLongevity::get(); + >::Longevity::get(); ValidTransaction::with_tag_prefix("GrandpaEquivocation") // We assign the maximum priority for any equivocation report. @@ -239,118 +281,10 @@ impl Pallet { pub fn pre_dispatch(call: &Call) -> Result<(), TransactionValidityError> { if let Call::report_equivocation_unsigned { equivocation_proof, key_owner_proof } = call { - is_known_offence::(equivocation_proof, key_owner_proof) + let evidence = (*equivocation_proof.clone(), key_owner_proof.clone()); + T::EquivocationReportSystem::check_evidence(evidence) } else { Err(InvalidTransaction::Call.into()) } } } - -fn is_known_offence( - equivocation_proof: &EquivocationProof, - key_owner_proof: &T::KeyOwnerProof, -) -> Result<(), TransactionValidityError> { - // check the membership proof to extract the offender's id - let key = (sp_finality_grandpa::KEY_TYPE, equivocation_proof.offender().clone()); - - let offender = T::KeyOwnerProofSystem::check_proof(key, key_owner_proof.clone()) - .ok_or(InvalidTransaction::BadProof)?; - - // check if the offence has already been reported, - // and if so then we can discard the report. - let time_slot = >::Offence::new_time_slot( - equivocation_proof.set_id(), - equivocation_proof.round(), - ); - - let is_known_offence = T::HandleEquivocation::is_known_offence(&[offender], &time_slot); - - if is_known_offence { - Err(InvalidTransaction::Stale.into()) - } else { - Ok(()) - } -} - -/// A grandpa equivocation offence report. -#[allow(dead_code)] -pub struct GrandpaEquivocationOffence { - /// Time slot at which this incident happened. - pub time_slot: GrandpaTimeSlot, - /// The session index in which the incident happened. - pub session_index: SessionIndex, - /// The size of the validator set at the time of the offence. - pub validator_set_count: u32, - /// The authority which produced this equivocation. - pub offender: FullIdentification, -} - -/// An interface for types that will be used as GRANDPA offences and must also -/// implement the `Offence` trait. This trait provides a constructor that is -/// provided all available data during processing of GRANDPA equivocations. -pub trait GrandpaOffence: Offence { - /// Create a new GRANDPA offence using the given equivocation details. - fn new( - session_index: SessionIndex, - validator_set_count: u32, - offender: FullIdentification, - set_id: SetId, - round: RoundNumber, - ) -> Self; - - /// Create a new GRANDPA offence time slot. - fn new_time_slot(set_id: SetId, round: RoundNumber) -> Self::TimeSlot; -} - -impl GrandpaOffence - for GrandpaEquivocationOffence -{ - fn new( - session_index: SessionIndex, - validator_set_count: u32, - offender: FullIdentification, - set_id: SetId, - round: RoundNumber, - ) -> Self { - GrandpaEquivocationOffence { - session_index, - validator_set_count, - offender, - time_slot: GrandpaTimeSlot { set_id, round }, - } - } - - fn new_time_slot(set_id: SetId, round: RoundNumber) -> Self::TimeSlot { - GrandpaTimeSlot { set_id, round } - } -} - -impl Offence - for GrandpaEquivocationOffence -{ - const ID: Kind = *b"grandpa:equivoca"; - type TimeSlot = GrandpaTimeSlot; - - fn offenders(&self) -> Vec { - vec![self.offender.clone()] - } - - fn session_index(&self) -> SessionIndex { - self.session_index - } - - fn validator_set_count(&self) -> u32 { - self.validator_set_count - } - - fn time_slot(&self) -> Self::TimeSlot { - self.time_slot - } - - fn slash_fraction(&self, offenders_count: u32) -> Perbill { - // the formula is min((3k / n)^2, 1) - let x = Perbill::from_rational(3 * offenders_count, self.validator_set_count); - // _ ^ 2 - x.square() - } -} diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index aa09b445c6bd..2a0e707ac414 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,29 +28,30 @@ #![cfg_attr(not(feature = "std"), no_std)] -// re-export since this is necessary for `impl_apis` in runtime. -pub use sp_finality_grandpa as fg_primitives; - -use sp_std::prelude::*; +// Re-export since this is necessary for `impl_apis` in runtime. +pub use sp_consensus_grandpa::{ + self as fg_primitives, AuthorityId, AuthorityList, AuthorityWeight, VersionedAuthorityList, +}; use codec::{self as codec, Decode, Encode, MaxEncodedLen}; -pub use fg_primitives::{AuthorityId, AuthorityList, AuthorityWeight, VersionedAuthorityList}; -use fg_primitives::{ - ConsensusLog, EquivocationProof, ScheduledChange, SetId, GRANDPA_AUTHORITIES_KEY, - GRANDPA_ENGINE_ID, RUNTIME_LOG_TARGET as LOG_TARGET, -}; use frame_support::{ dispatch::{DispatchResultWithPostInfo, Pays}, pallet_prelude::Get, storage, - traits::{KeyOwnerProofSystem, OneSessionHandler}, + traits::OneSessionHandler, weights::Weight, WeakBoundedVec, }; +use frame_system::pallet_prelude::BlockNumberFor; use scale_info::TypeInfo; -use sp_runtime::{generic::DigestItem, traits::Zero, DispatchResult, KeyTypeId}; +use sp_consensus_grandpa::{ + ConsensusLog, EquivocationProof, ScheduledChange, SetId, GRANDPA_AUTHORITIES_KEY, + GRANDPA_ENGINE_ID, RUNTIME_LOG_TARGET as LOG_TARGET, +}; +use sp_runtime::{generic::DigestItem, traits::Zero, DispatchResult}; use sp_session::{GetSessionNumber, GetValidatorCount}; -use sp_staking::SessionIndex; +use sp_staking::{offence::OffenceReportSystem, SessionIndex}; +use sp_std::prelude::*; mod default_weights; mod equivocation; @@ -63,10 +64,7 @@ mod mock; #[cfg(all(feature = "std", test))] mod tests; -pub use equivocation::{ - EquivocationHandler, GrandpaEquivocationOffence, GrandpaOffence, GrandpaTimeSlot, - HandleEquivocation, -}; +pub use equivocation::{EquivocationOffence, EquivocationReportSystem, TimeSlot}; pub use pallet::*; @@ -80,7 +78,6 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); @@ -91,41 +88,43 @@ pub mod pallet { + Into<::RuntimeEvent> + IsType<::RuntimeEvent>; - /// The proof of key ownership, used for validating equivocation reports - /// The proof must include the session index and validator count of the - /// session at which the equivocation occurred. - type KeyOwnerProof: Parameter + GetSessionNumber + GetValidatorCount; - - /// The identification of a key owner, used when reporting equivocations. - type KeyOwnerIdentification: Parameter; - - /// A system for proving ownership of keys, i.e. that a given key was part - /// of a validator set, needed for validating equivocation reports. - type KeyOwnerProofSystem: KeyOwnerProofSystem< - (KeyTypeId, AuthorityId), - Proof = Self::KeyOwnerProof, - IdentificationTuple = Self::KeyOwnerIdentification, - >; - - /// The equivocation handling subsystem, defines methods to report an - /// offence (after the equivocation has been validated) and for submitting a - /// transaction to report an equivocation (from an offchain context). - /// NOTE: when enabling equivocation handling (i.e. this type isn't set to - /// `()`) you must use this pallet's `ValidateUnsigned` in the runtime - /// definition. - type HandleEquivocation: HandleEquivocation; - /// Weights for this pallet. type WeightInfo: WeightInfo; /// Max Authorities in use #[pallet::constant] type MaxAuthorities: Get; + + /// The maximum number of nominators for each validator. + #[pallet::constant] + type MaxNominators: Get; + + /// The maximum number of entries to keep in the set id to session index mapping. + /// + /// Since the `SetIdSession` map is only used for validating equivocations this + /// value should relate to the bonding duration of whatever staking system is + /// being used (if any). If equivocation handling is not enabled then this value + /// can be zero. + #[pallet::constant] + type MaxSetIdSessionEntries: Get; + + /// The proof of key ownership, used for validating equivocation reports + /// The proof include the session index and validator count of the + /// session at which the equivocation occurred. + type KeyOwnerProof: Parameter + GetSessionNumber + GetValidatorCount; + + /// The equivocation handling subsystem, defines methods to check/report an + /// offence and for submitting a transaction to report an equivocation + /// (from an offchain context). + type EquivocationReportSystem: OffenceReportSystem< + Option, + (EquivocationProof>, Self::KeyOwnerProof), + >; } #[pallet::hooks] impl Hooks> for Pallet { - fn on_finalize(block_number: T::BlockNumber) { + fn on_finalize(block_number: BlockNumberFor) { // check for scheduled pending authority set changes if let Some(pending_change) = >::get() { // emit signal if we're at the block that scheduled the change @@ -194,15 +193,23 @@ pub mod pallet { /// against the extracted offender. If both are valid, the offence /// will be reported. #[pallet::call_index(0)] - #[pallet::weight(T::WeightInfo::report_equivocation(key_owner_proof.validator_count()))] + #[pallet::weight(T::WeightInfo::report_equivocation( + key_owner_proof.validator_count(), + T::MaxNominators::get(), + ))] pub fn report_equivocation( origin: OriginFor, - equivocation_proof: Box>, + equivocation_proof: Box>>, key_owner_proof: T::KeyOwnerProof, ) -> DispatchResultWithPostInfo { let reporter = ensure_signed(origin)?; - Self::do_report_equivocation(Some(reporter), *equivocation_proof, key_owner_proof) + T::EquivocationReportSystem::process_evidence( + Some(reporter), + (*equivocation_proof, key_owner_proof), + )?; + // Waive the fee since the report is valid and beneficial + Ok(Pays::No.into()) } /// Report voter equivocation/misbehavior. This method will verify the @@ -215,19 +222,22 @@ pub mod pallet { /// if the block author is defined it will be defined as the equivocation /// reporter. #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::report_equivocation(key_owner_proof.validator_count()))] + #[pallet::weight(T::WeightInfo::report_equivocation( + key_owner_proof.validator_count(), + T::MaxNominators::get(), + ))] pub fn report_equivocation_unsigned( origin: OriginFor, - equivocation_proof: Box>, + equivocation_proof: Box>>, key_owner_proof: T::KeyOwnerProof, ) -> DispatchResultWithPostInfo { ensure_none(origin)?; - Self::do_report_equivocation( - T::HandleEquivocation::block_author(), - *equivocation_proof, - key_owner_proof, - ) + T::EquivocationReportSystem::process_evidence( + None, + (*equivocation_proof, key_owner_proof), + )?; + Ok(Pays::No.into()) } /// Note that the current authority set of the GRANDPA finality gadget has stalled. @@ -246,8 +256,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::note_stalled())] pub fn note_stalled( origin: OriginFor, - delay: T::BlockNumber, - best_finalized_block_number: T::BlockNumber, + delay: BlockNumberFor, + best_finalized_block_number: BlockNumberFor, ) -> DispatchResult { ensure_root(origin)?; @@ -288,7 +298,7 @@ pub mod pallet { } #[pallet::type_value] - pub(super) fn DefaultForState() -> StoredState { + pub(super) fn DefaultForState() -> StoredState> { StoredState::Live } @@ -296,23 +306,23 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn state)] pub(super) type State = - StorageValue<_, StoredState, ValueQuery, DefaultForState>; + StorageValue<_, StoredState>, ValueQuery, DefaultForState>; /// Pending change: (signaled at, scheduled change). #[pallet::storage] #[pallet::getter(fn pending_change)] pub(super) type PendingChange = - StorageValue<_, StoredPendingChange>; + StorageValue<_, StoredPendingChange, T::MaxAuthorities>>; /// next block number where we can force a change. #[pallet::storage] #[pallet::getter(fn next_forced)] - pub(super) type NextForced = StorageValue<_, T::BlockNumber>; + pub(super) type NextForced = StorageValue<_, BlockNumberFor>; /// `true` if we are currently stalled. #[pallet::storage] #[pallet::getter(fn stalled)] - pub(super) type Stalled = StorageValue<_, (T::BlockNumber, T::BlockNumber)>; + pub(super) type Stalled = StorageValue<_, (BlockNumberFor, BlockNumberFor)>; /// The number of changes (both in terms of keys and underlying economic responsibilities) /// in the "set" of Grandpa validators from genesis. @@ -323,21 +333,29 @@ pub mod pallet { /// A mapping from grandpa set ID to the index of the *most recent* session for which its /// members were responsible. /// + /// This is only used for validating equivocation proofs. An equivocation proof must + /// contains a key-ownership proof for a given session, therefore we need a way to tie + /// together sessions and GRANDPA set ids, i.e. we need to validate that a validator + /// was the owner of a given key on a given session, and what the active set ID was + /// during that session. + /// /// TWOX-NOTE: `SetId` is not under user control. #[pallet::storage] #[pallet::getter(fn session_for_set)] pub(super) type SetIdSession = StorageMap<_, Twox64Concat, SetId, SessionIndex>; - #[cfg_attr(feature = "std", derive(Default))] + #[derive(frame_support::DefaultNoBound)] #[pallet::genesis_config] - pub struct GenesisConfig { + pub struct GenesisConfig { pub authorities: AuthorityList, + #[serde(skip)] + pub _config: sp_std::marker::PhantomData, } #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { - CurrentSetId::::put(fg_primitives::SetId::default()); + CurrentSetId::::put(SetId::default()); Pallet::::initialize(&self.authorities) } } @@ -345,6 +363,7 @@ pub mod pallet { #[pallet::validate_unsigned] impl ValidateUnsigned for Pallet { type Call = Call; + fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity { Self::validate_unsigned(source, call) } @@ -356,7 +375,7 @@ pub mod pallet { } pub trait WeightInfo { - fn report_equivocation(validator_count: u32) -> Weight; + fn report_equivocation(validator_count: u32, max_nominators_per_validator: u32) -> Weight; fn note_stalled() -> Weight; } @@ -421,7 +440,7 @@ impl Pallet { /// Schedule GRANDPA to pause starting in the given number of blocks. /// Cannot be done when already paused. - pub fn schedule_pause(in_blocks: T::BlockNumber) -> DispatchResult { + pub fn schedule_pause(in_blocks: BlockNumberFor) -> DispatchResult { if let StoredState::Live = >::get() { let scheduled_at = >::block_number(); >::put(StoredState::PendingPause { delay: in_blocks, scheduled_at }); @@ -433,7 +452,7 @@ impl Pallet { } /// Schedule a resume of GRANDPA after pausing. - pub fn schedule_resume(in_blocks: T::BlockNumber) -> DispatchResult { + pub fn schedule_resume(in_blocks: BlockNumberFor) -> DispatchResult { if let StoredState::Paused = >::get() { let scheduled_at = >::block_number(); >::put(StoredState::PendingResume { delay: in_blocks, scheduled_at }); @@ -460,8 +479,8 @@ impl Pallet { /// an error if a change is already pending. pub fn schedule_change( next_authorities: AuthorityList, - in_blocks: T::BlockNumber, - forced: Option, + in_blocks: BlockNumberFor, + forced: Option>, ) -> DispatchResult { if !>::exists() { let scheduled_at = >::block_number(); @@ -498,7 +517,7 @@ impl Pallet { } /// Deposit one of this module's logs. - fn deposit_log(log: ConsensusLog) { + fn deposit_log(log: ConsensusLog>) { let log = DigestItem::Consensus(GRANDPA_ENGINE_ID, log.encode()); >::deposit_log(log); } @@ -517,90 +536,18 @@ impl Pallet { SetIdSession::::insert(0, 0); } - fn do_report_equivocation( - reporter: Option, - equivocation_proof: EquivocationProof, - key_owner_proof: T::KeyOwnerProof, - ) -> DispatchResultWithPostInfo { - // we check the equivocation within the context of its set id (and - // associated session) and round. we also need to know the validator - // set count when the offence since it is required to calculate the - // slash amount. - let set_id = equivocation_proof.set_id(); - let round = equivocation_proof.round(); - let session_index = key_owner_proof.session(); - let validator_count = key_owner_proof.validator_count(); - - // validate the key ownership proof extracting the id of the offender. - let offender = T::KeyOwnerProofSystem::check_proof( - (fg_primitives::KEY_TYPE, equivocation_proof.offender().clone()), - key_owner_proof, - ) - .ok_or(Error::::InvalidKeyOwnershipProof)?; - - // validate equivocation proof (check votes are different and - // signatures are valid). - if !sp_finality_grandpa::check_equivocation_proof(equivocation_proof) { - return Err(Error::::InvalidEquivocationProof.into()) - } - - // fetch the current and previous sets last session index. on the - // genesis set there's no previous set. - let previous_set_id_session_index = if set_id == 0 { - None - } else { - let session_index = - Self::session_for_set(set_id - 1).ok_or(Error::::InvalidEquivocationProof)?; - - Some(session_index) - }; - - let set_id_session_index = - Self::session_for_set(set_id).ok_or(Error::::InvalidEquivocationProof)?; - - // check that the session id for the membership proof is within the - // bounds of the set id reported in the equivocation. - if session_index > set_id_session_index || - previous_set_id_session_index - .map(|previous_index| session_index <= previous_index) - .unwrap_or(false) - { - return Err(Error::::InvalidEquivocationProof.into()) - } - - // report to the offences module rewarding the sender. - T::HandleEquivocation::report_offence( - reporter.into_iter().collect(), - >::Offence::new( - session_index, - validator_count, - offender, - set_id, - round, - ), - ) - .map_err(|_| Error::::DuplicateOffenceReport)?; - - // waive the fee since the report is valid and beneficial - Ok(Pays::No.into()) - } - /// Submits an extrinsic to report an equivocation. This method will create /// an unsigned extrinsic with a call to `report_equivocation_unsigned` and /// will push the transaction to the pool. Only useful in an offchain /// context. pub fn submit_unsigned_equivocation_report( - equivocation_proof: EquivocationProof, + equivocation_proof: EquivocationProof>, key_owner_proof: T::KeyOwnerProof, ) -> Option<()> { - T::HandleEquivocation::submit_unsigned_equivocation_report( - equivocation_proof, - key_owner_proof, - ) - .ok() + T::EquivocationReportSystem::publish_evidence((equivocation_proof, key_owner_proof)).ok() } - fn on_stalled(further_wait: T::BlockNumber, median: T::BlockNumber) { + fn on_stalled(further_wait: BlockNumberFor, median: BlockNumberFor) { // when we record old authority sets we could try to figure out _who_ // failed. until then, we can't meaningfully guard against // `next == last` the way that normal session changes do. @@ -643,10 +590,17 @@ where }; if res.is_ok() { - CurrentSetId::::mutate(|s| { + let current_set_id = CurrentSetId::::mutate(|s| { *s += 1; *s - }) + }); + + let max_set_id_session_entries = T::MaxSetIdSessionEntries::get().max(1); + if current_set_id >= max_set_id_session_entries { + SetIdSession::::remove(current_set_id - max_set_id_session_entries); + } + + current_set_id } else { // either the session module signalled that the validators have changed // or the set was stalled. but since we didn't successfully schedule @@ -659,8 +613,8 @@ where Self::current_set_id() }; - // if we didn't issue a change, we update the mapping to note that the current - // set corresponds to the latest equivalent session (i.e. now). + // update the mapping to note that the current set corresponds to the + // latest equivalent session (i.e. now). let session_index = >::current_index(); SetIdSession::::insert(current_set_id, &session_index); } diff --git a/frame/grandpa/src/migrations.rs b/frame/grandpa/src/migrations.rs index 7795afcd8034..6307cbdd3b05 100644 --- a/frame/grandpa/src/migrations.rs +++ b/frame/grandpa/src/migrations.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,5 +15,51 @@ // See the License for the specific language governing permissions and // limitations under the License. +use frame_support::{ + traits::{Get, OnRuntimeUpgrade}, + weights::Weight, +}; + +use crate::{Config, CurrentSetId, SetIdSession, LOG_TARGET}; + /// Version 4. pub mod v4; + +/// This migration will clean up all stale set id -> session entries from the +/// `SetIdSession` storage map, only the latest `max_set_id_session_entries` +/// will be kept. +/// +/// This migration should be added with a runtime upgrade that introduces the +/// `MaxSetIdSessionEntries` constant to the pallet (although it could also be +/// done later on). +pub struct CleanupSetIdSessionMap(sp_std::marker::PhantomData); +impl OnRuntimeUpgrade for CleanupSetIdSessionMap { + fn on_runtime_upgrade() -> Weight { + // NOTE: since this migration will loop over all stale entries in the + // map we need to set some cutoff value, otherwise the migration might + // take too long to run. for scenarios where there are that many entries + // to cleanup a multiblock migration will be needed instead. + if CurrentSetId::::get() > 25_000 { + log::warn!( + target: LOG_TARGET, + "CleanupSetIdSessionMap migration was aborted since there are too many entries to cleanup." + ); + + return T::DbWeight::get().reads(1) + } + + cleanup_set_id_sesion_map::() + } +} + +fn cleanup_set_id_sesion_map() -> Weight { + let until_set_id = CurrentSetId::::get().saturating_sub(T::MaxSetIdSessionEntries::get()); + + for set_id in 0..=until_set_id { + SetIdSession::::remove(set_id); + } + + T::DbWeight::get() + .reads(1) + .saturating_add(T::DbWeight::get().writes(until_set_id + 1)) +} diff --git a/frame/grandpa/src/migrations/v4.rs b/frame/grandpa/src/migrations/v4.rs index 33e200b72833..8604296b6e57 100644 --- a/frame/grandpa/src/migrations/v4.rs +++ b/frame/grandpa/src/migrations/v4.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index c618705af065..86f91ba80323 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,41 +25,35 @@ use codec::Encode; use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, - traits::{ - ConstU128, ConstU32, ConstU64, GenesisBuild, KeyOwnerProofSystem, OnFinalize, OnInitialize, - }, + traits::{ConstU128, ConstU32, ConstU64, KeyOwnerProofSystem, OnFinalize, OnInitialize}, }; use pallet_session::historical as pallet_session_historical; +use sp_consensus_grandpa::{RoundNumber, SetId, GRANDPA_ENGINE_ID}; use sp_core::{crypto::KeyTypeId, H256}; -use sp_finality_grandpa::{RoundNumber, SetId, GRANDPA_ENGINE_ID}; use sp_keyring::Ed25519Keyring; use sp_runtime::{ curve::PiecewiseLinear, impl_opaque_keys, - testing::{Header, TestXt, UintAuthorityId}, + testing::{TestXt, UintAuthorityId}, traits::{IdentityLookup, OpaqueKeys}, - DigestItem, Perbill, + BuildStorage, DigestItem, Perbill, }; use sp_staking::{EraIndex, SessionIndex}; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Authorship: pallet_authorship::{Pallet, Call, Storage, Inherent}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, - Session: pallet_session::{Pallet, Call, Storage, Event, Config}, - Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event, ValidateUnsigned}, - Offences: pallet_offences::{Pallet, Storage, Event}, - Historical: pallet_session_historical::{Pallet}, + System: frame_system, + Authorship: pallet_authorship, + Timestamp: pallet_timestamp, + Balances: pallet_balances, + Staking: pallet_staking, + Session: pallet_session, + Grandpa: pallet_grandpa, + Offences: pallet_offences, + Historical: pallet_session_historical, } ); @@ -75,14 +69,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -129,8 +122,6 @@ impl pallet_session::historical::Config for Test { impl pallet_authorship::Config for Test { type FindAuthor = (); - type UncleGenerations = ConstU64<0>; - type FilterUncle = (); type EventHandler = (); } @@ -144,6 +135,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU128<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } impl pallet_timestamp::Config for Test { @@ -185,7 +180,7 @@ impl onchain::Config for OnChainSeqPhragmen { impl pallet_staking::Config for Test { type MaxNominations = ConstU32<16>; type RewardRemainder = (); - type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type CurrencyToVote = (); type RuntimeEvent = RuntimeEvent; type Currency = Balances; type CurrencyBalance = ::Balance; @@ -207,7 +202,7 @@ impl pallet_staking::Config for Test { type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type HistoryDepth = ConstU32<84>; - type OnStakerSlash = (); + type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } @@ -221,26 +216,18 @@ impl pallet_offences::Config for Test { parameter_types! { pub const ReportLongevity: u64 = BondingDuration::get() as u64 * SessionsPerEra::get() as u64 * Period::get(); + pub const MaxSetIdSessionEntries: u32 = BondingDuration::get() * SessionsPerEra::get(); } impl Config for Test { type RuntimeEvent = RuntimeEvent; - - type KeyOwnerProofSystem = Historical; - - type KeyOwnerProof = - >::Proof; - - type KeyOwnerIdentification = >::IdentificationTuple; - - type HandleEquivocation = - super::EquivocationHandler; - type WeightInfo = (); type MaxAuthorities = ConstU32<100>; + type MaxNominators = ConstU32<1000>; + type MaxSetIdSessionEntries = MaxSetIdSessionEntries; + type KeyOwnerProof = >::Proof; + type EquivocationReportSystem = + super::EquivocationReportSystem; } pub fn grandpa_log(log: ConsensusLog) -> DigestItem { @@ -264,7 +251,7 @@ pub fn new_test_ext(vec: Vec<(u64, u64)>) -> sp_io::TestExternalities { } pub fn new_test_ext_raw_authorities(authorities: AuthorityList) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let balances: Vec<_> = (0..authorities.len()).map(|i| (i as u64, 10_000_000)).collect(); @@ -291,11 +278,9 @@ pub fn new_test_ext_raw_authorities(authorities: AuthorityList) -> sp_io::TestEx .assimilate_storage(&mut t) .unwrap(); - // controllers are the index + 1000 + // controllers are the same as stash let stakers: Vec<_> = (0..authorities.len()) - .map(|i| { - (i as u64, i as u64 + 1000, 10_000, pallet_staking::StakerStatus::::Validator) - }) + .map(|i| (i as u64, i as u64, 10_000, pallet_staking::StakerStatus::::Validator)) .collect(); let staking_config = pallet_staking::GenesisConfig:: { @@ -354,12 +339,12 @@ pub fn generate_equivocation_proof( set_id: SetId, vote1: (RoundNumber, H256, u64, &Ed25519Keyring), vote2: (RoundNumber, H256, u64, &Ed25519Keyring), -) -> sp_finality_grandpa::EquivocationProof { +) -> sp_consensus_grandpa::EquivocationProof { let signed_prevote = |round, hash, number, keyring: &Ed25519Keyring| { let prevote = finality_grandpa::Prevote { target_hash: hash, target_number: number }; let prevote_msg = finality_grandpa::Message::Prevote(prevote.clone()); - let payload = sp_finality_grandpa::localized_payload(round, set_id, &prevote_msg); + let payload = sp_consensus_grandpa::localized_payload(round, set_id, &prevote_msg); let signed = keyring.sign(&payload).into(); (prevote, signed) }; @@ -367,9 +352,9 @@ pub fn generate_equivocation_proof( let (prevote1, signed1) = signed_prevote(vote1.0, vote1.1, vote1.2, vote1.3); let (prevote2, signed2) = signed_prevote(vote2.0, vote2.1, vote2.2, vote2.3); - sp_finality_grandpa::EquivocationProof::new( + sp_consensus_grandpa::EquivocationProof::new( set_id, - sp_finality_grandpa::Equivocation::Prevote(finality_grandpa::Equivocation { + sp_consensus_grandpa::Equivocation::Prevote(finality_grandpa::Equivocation { round_number: vote1.0, identity: vote1.3.public().into(), first: (prevote1, signed1), diff --git a/frame/grandpa/src/tests.rs b/frame/grandpa/src/tests.rs index 626decd12821..59d73ee729ee 100644 --- a/frame/grandpa/src/tests.rs +++ b/frame/grandpa/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,12 +21,11 @@ use super::{Call, Event, *}; use crate::mock::*; -use codec::Encode; use fg_primitives::ScheduledChange; use frame_support::{ assert_err, assert_noop, assert_ok, dispatch::{GetDispatchInfo, Pays}, - traits::{Currency, OnFinalize, OneSessionHandler}, + traits::{Currency, KeyOwnerProofSystem, OnFinalize, OneSessionHandler}, }; use frame_system::{EventRecord, Phase}; use sp_core::H256; @@ -297,12 +296,12 @@ fn schedule_resume_only_when_paused() { #[test] fn time_slot_have_sane_ord() { // Ensure that `Ord` implementation is sane. - const FIXTURE: &[GrandpaTimeSlot] = &[ - GrandpaTimeSlot { set_id: 0, round: 0 }, - GrandpaTimeSlot { set_id: 0, round: 1 }, - GrandpaTimeSlot { set_id: 1, round: 0 }, - GrandpaTimeSlot { set_id: 1, round: 1 }, - GrandpaTimeSlot { set_id: 1, round: 2 }, + const FIXTURE: &[TimeSlot] = &[ + TimeSlot { set_id: 0, round: 0 }, + TimeSlot { set_id: 0, round: 1 }, + TimeSlot { set_id: 1, round: 0 }, + TimeSlot { set_id: 1, round: 1 }, + TimeSlot { set_id: 1, round: 2 }, ]; assert!(FIXTURE.windows(2).all(|f| f[0] < f[1])); } @@ -355,7 +354,7 @@ fn report_equivocation_current_set_works() { // create the key ownership proof let key_owner_proof = - Historical::prove((sp_finality_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); + Historical::prove((sp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); // report the equivocation and the tx should be dispatched successfully assert_ok!(Grandpa::report_equivocation_unsigned( @@ -408,7 +407,7 @@ fn report_equivocation_old_set_works() { // create the key ownership proof in the "old" set let key_owner_proof = - Historical::prove((sp_finality_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); + Historical::prove((sp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); start_era(2); @@ -486,7 +485,7 @@ fn report_equivocation_invalid_set_id() { let equivocation_keyring = extract_keyring(equivocation_key); let key_owner_proof = - Historical::prove((sp_finality_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); + Historical::prove((sp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); let set_id = Grandpa::current_set_id(); @@ -524,7 +523,7 @@ fn report_equivocation_invalid_session() { // generate a key ownership proof at set id = 1 let key_owner_proof = - Historical::prove((sp_finality_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); + Historical::prove((sp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); start_era(2); @@ -563,7 +562,7 @@ fn report_equivocation_invalid_key_owner_proof() { // generate a key ownership proof for the authority at index 1 let invalid_key_owner_proof = - Historical::prove((sp_finality_grandpa::KEY_TYPE, &invalid_owner_key)).unwrap(); + Historical::prove((sp_consensus_grandpa::KEY_TYPE, &invalid_owner_key)).unwrap(); let equivocation_authority_index = 0; let equivocation_key = &authorities[equivocation_authority_index].0; @@ -610,7 +609,7 @@ fn report_equivocation_invalid_equivocation_proof() { // generate a key ownership proof at set id = 1 let key_owner_proof = - Historical::prove((sp_finality_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); + Historical::prove((sp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); let set_id = Grandpa::current_set_id(); @@ -685,7 +684,7 @@ fn report_equivocation_validate_unsigned_prevents_duplicates() { ); let key_owner_proof = - Historical::prove((sp_finality_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); + Historical::prove((sp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); let call = Call::report_equivocation_unsigned { equivocation_proof: Box::new(equivocation_proof.clone()), @@ -781,6 +780,33 @@ fn on_new_session_doesnt_start_new_set_if_schedule_change_failed() { }); } +#[test] +fn cleans_up_old_set_id_session_mappings() { + new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| { + let max_set_id_session_entries = MaxSetIdSessionEntries::get(); + + start_era(max_set_id_session_entries); + + // we should have a session id mapping for all the set ids from + // `max_set_id_session_entries` eras we have observed + for i in 1..=max_set_id_session_entries { + assert!(Grandpa::session_for_set(i as u64).is_some()); + } + + start_era(max_set_id_session_entries * 2); + + // we should keep tracking the new mappings for new eras + for i in max_set_id_session_entries + 1..=max_set_id_session_entries * 2 { + assert!(Grandpa::session_for_set(i as u64).is_some()); + } + + // but the old ones should have been pruned by now + for i in 1..=max_set_id_session_entries { + assert!(Grandpa::session_for_set(i as u64).is_none()); + } + }); +} + #[test] fn always_schedules_a_change_on_new_session_when_stalled() { new_test_ext(vec![(1, 1), (2, 1), (3, 1)]).execute_with(|| { @@ -812,7 +838,7 @@ fn report_equivocation_has_valid_weight() { // the weight depends on the size of the validator set, // but there's a lower bound of 100 validators. assert!((1..=100) - .map(::WeightInfo::report_equivocation) + .map(|validators| ::WeightInfo::report_equivocation(validators, 1000)) .collect::>() .windows(2) .all(|w| w[0] == w[1])); @@ -820,7 +846,7 @@ fn report_equivocation_has_valid_weight() { // after 100 validators the weight should keep increasing // with every extra validator. assert!((100..=1000) - .map(::WeightInfo::report_equivocation) + .map(|validators| ::WeightInfo::report_equivocation(validators, 1000)) .collect::>() .windows(2) .all(|w| w[0].ref_time() < w[1].ref_time())); @@ -846,7 +872,7 @@ fn valid_equivocation_reports_dont_pay_fees() { // create the key ownership proof. let key_owner_proof = - Historical::prove((sp_finality_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); + Historical::prove((sp_consensus_grandpa::KEY_TYPE, &equivocation_key)).unwrap(); // check the dispatch info for the call. let info = Call::::report_equivocation_unsigned { diff --git a/frame/identity/Cargo.toml b/frame/identity/Cargo.toml index c1a250368fad..85ad332398dd 100644 --- a/frame/identity/Cargo.toml +++ b/frame/identity/Cargo.toml @@ -13,19 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive", "max-encoded-len"] } -enumflags2 = { version = "0.7.4" } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } +enumflags2 = { version = "0.7.7" } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/identity/src/benchmarking.rs b/frame/identity/src/benchmarking.rs index 3584eb954b39..4b51d23f6b34 100644 --- a/frame/identity/src/benchmarking.rs +++ b/frame/identity/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ use super::*; use crate::Pallet as Identity; -use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller, BenchmarkError}; use frame_support::{ ensure, traits::{EnsureOrigin, Get}, @@ -42,7 +42,8 @@ fn add_registrars(r: u32) -> Result<(), &'static str> { let registrar: T::AccountId = account("registrar", i, SEED); let registrar_lookup = T::Lookup::unlookup(registrar.clone()); let _ = T::Currency::make_free_balance_be(®istrar, BalanceOf::::max_value()); - let registrar_origin = T::RegistrarOrigin::successful_origin(); + let registrar_origin = T::RegistrarOrigin::try_successful_origin() + .expect("RegistrarOrigin has no successful origin required for the benchmark"); Identity::::add_registrar(registrar_origin, registrar_lookup)?; Identity::::set_fee(RawOrigin::Signed(registrar.clone()).into(), i, 10u32.into())?; let fields = @@ -121,7 +122,8 @@ benchmarks! { add_registrar { let r in 1 .. T::MaxRegistrars::get() - 1 => add_registrars::(r)?; ensure!(Registrars::::get().len() as u32 == r, "Registrars not set up correctly."); - let origin = T::RegistrarOrigin::successful_origin(); + let origin = + T::RegistrarOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let account = T::Lookup::unlookup(account("registrar", r + 1, SEED)); }: _(origin, account) verify { @@ -280,7 +282,8 @@ benchmarks! { let r in 1 .. T::MaxRegistrars::get() - 1 => add_registrars::(r)?; - let registrar_origin = T::RegistrarOrigin::successful_origin(); + let registrar_origin = T::RegistrarOrigin::try_successful_origin() + .expect("RegistrarOrigin has no successful origin required for the benchmark"); Identity::::add_registrar(registrar_origin, caller_lookup)?; let registrars = Registrars::::get(); ensure!(registrars[r as usize].as_ref().unwrap().fee == 0u32.into(), "Fee already set."); @@ -297,7 +300,8 @@ benchmarks! { let r in 1 .. T::MaxRegistrars::get() - 1 => add_registrars::(r)?; - let registrar_origin = T::RegistrarOrigin::successful_origin(); + let registrar_origin = T::RegistrarOrigin::try_successful_origin() + .expect("RegistrarOrigin has no successful origin required for the benchmark"); Identity::::add_registrar(registrar_origin, caller_lookup)?; let registrars = Registrars::::get(); ensure!(registrars[r as usize].as_ref().unwrap().account == caller, "id not set."); @@ -315,7 +319,8 @@ benchmarks! { let r in 1 .. T::MaxRegistrars::get() - 1 => add_registrars::(r)?; - let registrar_origin = T::RegistrarOrigin::successful_origin(); + let registrar_origin = T::RegistrarOrigin::try_successful_origin() + .expect("RegistrarOrigin has no successful origin required for the benchmark"); Identity::::add_registrar(registrar_origin, caller_lookup)?; let fields = IdentityFields( IdentityField::Display | IdentityField::Legal | IdentityField::Web | IdentityField::Riot @@ -347,7 +352,8 @@ benchmarks! { let info_hash = T::Hashing::hash_of(&info); Identity::::set_identity(user_origin.clone(), Box::new(info))?; - let registrar_origin = T::RegistrarOrigin::successful_origin(); + let registrar_origin = T::RegistrarOrigin::try_successful_origin() + .expect("RegistrarOrigin has no successful origin required for the benchmark"); Identity::::add_registrar(registrar_origin, caller_lookup)?; Identity::::request_judgement(user_origin, r, 10u32.into())?; }: _(RawOrigin::Signed(caller), r, user_lookup, Judgement::Reasonable, info_hash) @@ -385,7 +391,8 @@ benchmarks! { )?; } ensure!(IdentityOf::::contains_key(&target), "Identity not set"); - let origin = T::ForceOrigin::successful_origin(); + let origin = + T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: _(origin, target_lookup) verify { ensure!(!IdentityOf::::contains_key(&target), "Identity not removed"); diff --git a/frame/identity/src/lib.rs b/frame/identity/src/lib.rs index 8eab2c67418a..f192ee2b461b 100644 --- a/frame/identity/src/lib.rs +++ b/frame/identity/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -152,7 +152,6 @@ pub mod pallet { } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); /// Information that is pertinent to identify the entity behind an account. @@ -279,11 +278,8 @@ pub mod pallet { /// /// Emits `RegistrarAdded` if successful. /// - /// # + /// ## Complexity /// - `O(R)` where `R` registrar-count (governance-bounded and code-bounded). - /// - One storage mutation (codec `O(R)`). - /// - One event. - /// # #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::add_registrar(T::MaxRegistrars::get()))] pub fn add_registrar( @@ -322,14 +318,10 @@ pub mod pallet { /// /// Emits `IdentitySet` if successful. /// - /// # + /// ## Complexity /// - `O(X + X' + R)` /// - where `X` additional-field-count (deposit-bounded and code-bounded) /// - where `R` judgements-count (registrar-count-bounded) - /// - One balance reserve operation. - /// - One storage mutation (codec-read `O(X' + R)`, codec-write `O(X + R)`). - /// - One event. - /// # #[pallet::call_index(1)] #[pallet::weight( T::WeightInfo::set_identity( T::MaxRegistrars::get(), // R @@ -389,17 +381,10 @@ pub mod pallet { /// /// - `subs`: The identity's (new) sub-accounts. /// - /// # + /// ## Complexity /// - `O(P + S)` /// - where `P` old-subs-count (hard- and deposit-bounded). /// - where `S` subs-count (hard- and deposit-bounded). - /// - At most one balance operations. - /// - DB: - /// - `P + S` storage mutations (codec complexity `O(1)`) - /// - One storage read (codec complexity `O(P)`). - /// - One storage write (codec complexity `O(S)`). - /// - One storage-exists (`IdentityOf::contains_key`). - /// # // TODO: This whole extrinsic screams "not optimized". For example we could // filter any overlap between new and old subs, and avoid reading/writing // to those values... We could also ideally avoid needing to write to @@ -469,15 +454,11 @@ pub mod pallet { /// /// Emits `IdentityCleared` if successful. /// - /// # + /// ## Complexity /// - `O(R + S + X)` /// - where `R` registrar-count (governance-bounded). /// - where `S` subs-count (hard- and deposit-bounded). /// - where `X` additional-field-count (deposit-bounded and code-bounded). - /// - One balance-unreserve operation. - /// - `2` storage reads and `S + 2` storage deletions. - /// - One event. - /// # #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::clear_identity( T::MaxRegistrars::get(), // R @@ -524,12 +505,10 @@ pub mod pallet { /// /// Emits `JudgementRequested` if successful. /// - /// # + /// ## Complexity /// - `O(R + X)`. - /// - One balance-reserve operation. - /// - Storage: 1 read `O(R)`, 1 mutate `O(X + R)`. - /// - One event. - /// # + /// - where `R` registrar-count (governance-bounded). + /// - where `X` additional-field-count (deposit-bounded and code-bounded). #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::request_judgement( T::MaxRegistrars::get(), // R @@ -587,12 +566,10 @@ pub mod pallet { /// /// Emits `JudgementUnrequested` if successful. /// - /// # + /// ## Complexity /// - `O(R + X)`. - /// - One balance-reserve operation. - /// - One storage mutation `O(R + X)`. - /// - One event - /// # + /// - where `R` registrar-count (governance-bounded). + /// - where `X` additional-field-count (deposit-bounded and code-bounded). #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::cancel_request( T::MaxRegistrars::get(), // R @@ -637,11 +614,9 @@ pub mod pallet { /// - `index`: the index of the registrar whose fee is to be set. /// - `fee`: the new fee. /// - /// # + /// ## Complexity /// - `O(R)`. - /// - One storage mutation `O(R)`. - /// - Benchmark: 7.315 + R * 0.329 µs (min squares analysis) - /// # + /// - where `R` registrar-count (governance-bounded). #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::set_fee(T::MaxRegistrars::get()))] // R pub fn set_fee( @@ -676,11 +651,9 @@ pub mod pallet { /// - `index`: the index of the registrar whose fee is to be set. /// - `new`: the new account ID. /// - /// # + /// ## Complexity /// - `O(R)`. - /// - One storage mutation `O(R)`. - /// - Benchmark: 8.823 + R * 0.32 µs (min squares analysis) - /// # + /// - where `R` registrar-count (governance-bounded). #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::set_account_id(T::MaxRegistrars::get()))] // R pub fn set_account_id( @@ -716,11 +689,9 @@ pub mod pallet { /// - `index`: the index of the registrar whose fee is to be set. /// - `fields`: the fields that the registrar concerns themselves with. /// - /// # + /// ## Complexity /// - `O(R)`. - /// - One storage mutation `O(R)`. - /// - Benchmark: 7.464 + R * 0.325 µs (min squares analysis) - /// # + /// - where `R` registrar-count (governance-bounded). #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::set_fields(T::MaxRegistrars::get()))] // R pub fn set_fields( @@ -763,13 +734,10 @@ pub mod pallet { /// /// Emits `JudgementGiven` if successful. /// - /// # + /// ## Complexity /// - `O(R + X)`. - /// - One balance-transfer operation. - /// - Up to one account-lookup operation. - /// - Storage: 1 read `O(R)`, 1 mutate `O(R + X)`. - /// - One event. - /// # + /// - where `R` registrar-count (governance-bounded). + /// - where `X` additional-field-count (deposit-bounded and code-bounded). #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::provide_judgement( T::MaxRegistrars::get(), // R @@ -838,12 +806,11 @@ pub mod pallet { /// /// Emits `IdentityKilled` if successful. /// - /// # - /// - `O(R + S + X)`. - /// - One balance-reserve operation. - /// - `S + 2` storage mutations. - /// - One event. - /// # + /// ## Complexity + /// - `O(R + S + X)` + /// - where `R` registrar-count (governance-bounded). + /// - where `S` subs-count (hard- and deposit-bounded). + /// - where `X` additional-field-count (deposit-bounded and code-bounded). #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::kill_identity( T::MaxRegistrars::get(), // R diff --git a/frame/identity/src/tests.rs b/frame/identity/src/tests.rs index baca70e2795f..1532980574c2 100644 --- a/frame/identity/src/tests.rs +++ b/frame/identity/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,20 +29,16 @@ use frame_support::{ use frame_system::{EnsureRoot, EnsureSignedBy}; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BadOrigin, BlakeTwo256, IdentityLookup}, + BuildStorage, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Identity: pallet_identity::{Pallet, Call, Storage, Event}, } @@ -53,14 +49,13 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type DbWeight = (); @@ -85,6 +80,10 @@ impl pallet_balances::Config for Test { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } parameter_types! { @@ -114,7 +113,7 @@ impl pallet_identity::Config for Test { } pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 10), (3, 10), (10, 100), (20, 100), (30, 100)], } diff --git a/frame/identity/src/types.rs b/frame/identity/src/types.rs index b1f15da3b111..1837b30b027f 100644 --- a/frame/identity/src/types.rs +++ b/frame/identity/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -442,7 +442,7 @@ mod tests { let mut registry = scale_info::Registry::new(); let type_id = registry.register_type(&scale_info::meta_type::()); let registry: scale_info::PortableRegistry = registry.into(); - let type_info = registry.resolve(type_id.id()).unwrap(); + let type_info = registry.resolve(type_id.id).unwrap(); let check_type_info = |data: &Data| { let variant_name = match data { @@ -453,20 +453,20 @@ mod tests { Data::ShaThree256(_) => "ShaThree256".to_string(), Data::Raw(bytes) => format!("Raw{}", bytes.len()), }; - if let scale_info::TypeDef::Variant(variant) = type_info.type_def() { + if let scale_info::TypeDef::Variant(variant) = &type_info.type_def { let variant = variant - .variants() + .variants .iter() - .find(|v| v.name() == &variant_name) + .find(|v| v.name == variant_name) .expect(&format!("Expected to find variant {}", variant_name)); let field_arr_len = variant - .fields() + .fields .first() - .and_then(|f| registry.resolve(f.ty().id())) + .and_then(|f| registry.resolve(f.ty.id)) .map(|ty| { - if let scale_info::TypeDef::Array(arr) = ty.type_def() { - arr.len() + if let scale_info::TypeDef::Array(arr) = &ty.type_def { + arr.len } else { panic!("Should be an array type") } @@ -474,7 +474,7 @@ mod tests { .unwrap_or(0); let encoded = data.encode(); - assert_eq!(encoded[0], variant.index()); + assert_eq!(encoded[0], variant.index); assert_eq!(encoded.len() as u32 - 1, field_arr_len); } else { panic!("Should be a variant type") diff --git a/frame/identity/src/weights.rs b/frame/identity/src/weights.rs index 1f2e8f98e988..02fcd7db3c95 100644 --- a/frame/identity/src/weights.rs +++ b/frame/identity/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_identity //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_identity +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_identity. pub trait WeightInfo { @@ -68,420 +73,588 @@ pub trait WeightInfo { /// Weights for pallet_identity using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Identity Registrars (r:1 w:1) + /// Storage: Identity Registrars (r:1 w:1) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) /// The range of component `r` is `[1, 19]`. fn add_registrar(r: u32, ) -> Weight { - // Minimum execution time: 20_269 nanoseconds. - Weight::from_ref_time(21_910_543 as u64) - // Standard Error: 4_604 - .saturating_add(Weight::from_ref_time(223_104 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Identity IdentityOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `32 + r * (57 ±0)` + // Estimated: `2626` + // Minimum execution time: 11_683_000 picoseconds. + Weight::from_parts(12_515_830, 2626) + // Standard Error: 2_154 + .saturating_add(Weight::from_parts(147_919, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. /// The range of component `x` is `[0, 100]`. fn set_identity(r: u32, x: u32, ) -> Weight { - // Minimum execution time: 41_872 nanoseconds. - Weight::from_ref_time(40_230_216 as u64) - // Standard Error: 2_342 - .saturating_add(Weight::from_ref_time(145_168 as u64).saturating_mul(r as u64)) - // Standard Error: 457 - .saturating_add(Weight::from_ref_time(291_732 as u64).saturating_mul(x as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Identity IdentityOf (r:1 w:0) - // Storage: Identity SubsOf (r:1 w:1) - // Storage: Identity SuperOf (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `442 + r * (5 ±0)` + // Estimated: `11003` + // Minimum execution time: 32_949_000 picoseconds. + Weight::from_parts(31_329_634, 11003) + // Standard Error: 4_496 + .saturating_add(Weight::from_parts(203_570, 0).saturating_mul(r.into())) + // Standard Error: 877 + .saturating_add(Weight::from_parts(429_346, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:100 w:100) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `s` is `[0, 100]`. fn set_subs_new(s: u32, ) -> Weight { - // Minimum execution time: 12_024 nanoseconds. - Weight::from_ref_time(32_550_819 as u64) - // Standard Error: 5_057 - .saturating_add(Weight::from_ref_time(2_521_245 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(s as u64))) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(s as u64))) - } - // Storage: Identity IdentityOf (r:1 w:0) - // Storage: Identity SubsOf (r:1 w:1) - // Storage: Identity SuperOf (r:0 w:2) + // Proof Size summary in bytes: + // Measured: `101` + // Estimated: `11003 + s * (2589 ±0)` + // Minimum execution time: 9_157_000 picoseconds. + Weight::from_parts(24_917_444, 11003) + // Standard Error: 4_554 + .saturating_add(Weight::from_parts(3_279_868, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(s.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) + .saturating_add(Weight::from_parts(0, 2589).saturating_mul(s.into())) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:0 w:100) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `p` is `[0, 100]`. fn set_subs_old(p: u32, ) -> Weight { - // Minimum execution time: 12_232 nanoseconds. - Weight::from_ref_time(34_009_761 as u64) - // Standard Error: 5_047 - .saturating_add(Weight::from_ref_time(1_113_100 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(p as u64))) - } - // Storage: Identity SubsOf (r:1 w:1) - // Storage: Identity IdentityOf (r:1 w:1) - // Storage: Identity SuperOf (r:0 w:100) + // Proof Size summary in bytes: + // Measured: `194 + p * (32 ±0)` + // Estimated: `11003` + // Minimum execution time: 9_240_000 picoseconds. + Weight::from_parts(23_326_035, 11003) + // Standard Error: 3_664 + .saturating_add(Weight::from_parts(1_439_873, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) + } + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:0 w:100) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. /// The range of component `s` is `[0, 100]`. /// The range of component `x` is `[0, 100]`. fn clear_identity(r: u32, s: u32, x: u32, ) -> Weight { - // Minimum execution time: 57_144 nanoseconds. - Weight::from_ref_time(41_559_247 as u64) - // Standard Error: 9_996 - .saturating_add(Weight::from_ref_time(146_770 as u64).saturating_mul(r as u64)) - // Standard Error: 1_952 - .saturating_add(Weight::from_ref_time(1_086_673 as u64).saturating_mul(s as u64)) - // Standard Error: 1_952 - .saturating_add(Weight::from_ref_time(162_481 as u64).saturating_mul(x as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(s as u64))) - } - // Storage: Identity Registrars (r:1 w:0) - // Storage: Identity IdentityOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `469 + r * (5 ±0) + s * (32 ±0) + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 55_687_000 picoseconds. + Weight::from_parts(30_695_182, 11003) + // Standard Error: 9_921 + .saturating_add(Weight::from_parts(162_357, 0).saturating_mul(r.into())) + // Standard Error: 1_937 + .saturating_add(Weight::from_parts(1_427_998, 0).saturating_mul(s.into())) + // Standard Error: 1_937 + .saturating_add(Weight::from_parts(247_578, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) + } + /// Storage: Identity Registrars (r:1 w:0) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. /// The range of component `x` is `[0, 100]`. fn request_judgement(r: u32, x: u32, ) -> Weight { - // Minimum execution time: 44_726 nanoseconds. - Weight::from_ref_time(41_637_308 as u64) - // Standard Error: 1_907 - .saturating_add(Weight::from_ref_time(219_078 as u64).saturating_mul(r as u64)) - // Standard Error: 372 - .saturating_add(Weight::from_ref_time(309_888 as u64).saturating_mul(x as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Identity IdentityOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `367 + r * (57 ±0) + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 34_876_000 picoseconds. + Weight::from_parts(32_207_018, 11003) + // Standard Error: 5_247 + .saturating_add(Weight::from_parts(249_156, 0).saturating_mul(r.into())) + // Standard Error: 1_023 + .saturating_add(Weight::from_parts(458_329, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. /// The range of component `x` is `[0, 100]`. fn cancel_request(r: u32, x: u32, ) -> Weight { - // Minimum execution time: 39_719 nanoseconds. - Weight::from_ref_time(38_008_751 as u64) - // Standard Error: 2_394 - .saturating_add(Weight::from_ref_time(181_870 as u64).saturating_mul(r as u64)) - // Standard Error: 467 - .saturating_add(Weight::from_ref_time(314_990 as u64).saturating_mul(x as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Identity Registrars (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `398 + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 30_689_000 picoseconds. + Weight::from_parts(31_967_170, 11003) + // Standard Error: 5_387 + .saturating_add(Weight::from_parts(42_676, 0).saturating_mul(r.into())) + // Standard Error: 1_051 + .saturating_add(Weight::from_parts(446_213, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Identity Registrars (r:1 w:1) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) /// The range of component `r` is `[1, 19]`. fn set_fee(r: u32, ) -> Weight { - // Minimum execution time: 10_634 nanoseconds. - Weight::from_ref_time(11_383_704 as u64) - // Standard Error: 2_250 - .saturating_add(Weight::from_ref_time(193_094 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Identity Registrars (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `89 + r * (57 ±0)` + // Estimated: `2626` + // Minimum execution time: 7_357_000 picoseconds. + Weight::from_parts(7_932_950, 2626) + // Standard Error: 1_804 + .saturating_add(Weight::from_parts(132_653, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Identity Registrars (r:1 w:1) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) /// The range of component `r` is `[1, 19]`. fn set_account_id(r: u32, ) -> Weight { - // Minimum execution time: 10_840 nanoseconds. - Weight::from_ref_time(11_638_740 as u64) - // Standard Error: 1_985 - .saturating_add(Weight::from_ref_time(193_016 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Identity Registrars (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `89 + r * (57 ±0)` + // Estimated: `2626` + // Minimum execution time: 7_437_000 picoseconds. + Weight::from_parts(8_051_889, 2626) + // Standard Error: 1_997 + .saturating_add(Weight::from_parts(129_592, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Identity Registrars (r:1 w:1) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) /// The range of component `r` is `[1, 19]`. fn set_fields(r: u32, ) -> Weight { - // Minimum execution time: 10_748 nanoseconds. - Weight::from_ref_time(11_346_901 as u64) - // Standard Error: 2_132 - .saturating_add(Weight::from_ref_time(196_630 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Identity Registrars (r:1 w:0) - // Storage: Identity IdentityOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `89 + r * (57 ±0)` + // Estimated: `2626` + // Minimum execution time: 7_385_000 picoseconds. + Weight::from_parts(7_911_589, 2626) + // Standard Error: 1_791 + .saturating_add(Weight::from_parts(125_788, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Identity Registrars (r:1 w:0) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) /// The range of component `r` is `[1, 19]`. /// The range of component `x` is `[0, 100]`. fn provide_judgement(r: u32, x: u32, ) -> Weight { - // Minimum execution time: 33_682 nanoseconds. - Weight::from_ref_time(31_336_603 as u64) - // Standard Error: 3_056 - .saturating_add(Weight::from_ref_time(200_403 as u64).saturating_mul(r as u64)) - // Standard Error: 565 - .saturating_add(Weight::from_ref_time(525_142 as u64).saturating_mul(x as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Identity SubsOf (r:1 w:1) - // Storage: Identity IdentityOf (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Identity SuperOf (r:0 w:100) + // Proof Size summary in bytes: + // Measured: `445 + r * (57 ±0) + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 24_073_000 picoseconds. + Weight::from_parts(17_817_684, 11003) + // Standard Error: 8_612 + .saturating_add(Weight::from_parts(406_251, 0).saturating_mul(r.into())) + // Standard Error: 1_593 + .saturating_add(Weight::from_parts(755_225, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:0 w:100) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. /// The range of component `s` is `[0, 100]`. /// The range of component `x` is `[0, 100]`. fn kill_identity(r: u32, s: u32, x: u32, ) -> Weight { - // Minimum execution time: 68_794 nanoseconds. - Weight::from_ref_time(52_114_486 as u64) - // Standard Error: 4_808 - .saturating_add(Weight::from_ref_time(153_462 as u64).saturating_mul(r as u64)) - // Standard Error: 939 - .saturating_add(Weight::from_ref_time(1_084_612 as u64).saturating_mul(s as u64)) - // Standard Error: 939 - .saturating_add(Weight::from_ref_time(170_112 as u64).saturating_mul(x as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(s as u64))) - } - // Storage: Identity IdentityOf (r:1 w:0) - // Storage: Identity SuperOf (r:1 w:1) - // Storage: Identity SubsOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `676 + r * (5 ±0) + s * (32 ±0) + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 73_981_000 picoseconds. + Weight::from_parts(51_684_057, 11003) + // Standard Error: 12_662 + .saturating_add(Weight::from_parts(145_285, 0).saturating_mul(r.into())) + // Standard Error: 2_472 + .saturating_add(Weight::from_parts(1_421_039, 0).saturating_mul(s.into())) + // Standard Error: 2_472 + .saturating_add(Weight::from_parts(240_907, 0).saturating_mul(x.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:1 w:1) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) /// The range of component `s` is `[0, 99]`. fn add_sub(s: u32, ) -> Weight { - // Minimum execution time: 37_914 nanoseconds. - Weight::from_ref_time(43_488_083 as u64) - // Standard Error: 1_631 - .saturating_add(Weight::from_ref_time(118_845 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Identity IdentityOf (r:1 w:0) - // Storage: Identity SuperOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `475 + s * (36 ±0)` + // Estimated: `11003` + // Minimum execution time: 29_367_000 picoseconds. + Weight::from_parts(34_214_998, 11003) + // Standard Error: 1_522 + .saturating_add(Weight::from_parts(114_551, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:1 w:1) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `s` is `[1, 100]`. fn rename_sub(s: u32, ) -> Weight { - // Minimum execution time: 16_124 nanoseconds. - Weight::from_ref_time(18_580_462 as u64) - // Standard Error: 688 - .saturating_add(Weight::from_ref_time(67_220 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Identity IdentityOf (r:1 w:0) - // Storage: Identity SuperOf (r:1 w:1) - // Storage: Identity SubsOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `591 + s * (3 ±0)` + // Estimated: `11003` + // Minimum execution time: 12_384_000 picoseconds. + Weight::from_parts(14_417_903, 11003) + // Standard Error: 539 + .saturating_add(Weight::from_parts(38_371, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:1 w:1) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) /// The range of component `s` is `[1, 100]`. fn remove_sub(s: u32, ) -> Weight { - // Minimum execution time: 41_517 nanoseconds. - Weight::from_ref_time(45_123_530 as u64) - // Standard Error: 1_530 - .saturating_add(Weight::from_ref_time(105_429 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Identity SuperOf (r:1 w:1) - // Storage: Identity SubsOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `638 + s * (35 ±0)` + // Estimated: `11003` + // Minimum execution time: 33_327_000 picoseconds. + Weight::from_parts(36_208_941, 11003) + // Standard Error: 1_240 + .saturating_add(Weight::from_parts(105_805, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Identity SuperOf (r:1 w:1) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `s` is `[0, 99]`. fn quit_sub(s: u32, ) -> Weight { - // Minimum execution time: 30_171 nanoseconds. - Weight::from_ref_time(33_355_514 as u64) - // Standard Error: 1_286 - .saturating_add(Weight::from_ref_time(114_716 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `704 + s * (37 ±0)` + // Estimated: `6723` + // Minimum execution time: 23_764_000 picoseconds. + Weight::from_parts(26_407_731, 6723) + // Standard Error: 1_025 + .saturating_add(Weight::from_parts(101_112, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Identity Registrars (r:1 w:1) + /// Storage: Identity Registrars (r:1 w:1) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) /// The range of component `r` is `[1, 19]`. fn add_registrar(r: u32, ) -> Weight { - // Minimum execution time: 20_269 nanoseconds. - Weight::from_ref_time(21_910_543 as u64) - // Standard Error: 4_604 - .saturating_add(Weight::from_ref_time(223_104 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Identity IdentityOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `32 + r * (57 ±0)` + // Estimated: `2626` + // Minimum execution time: 11_683_000 picoseconds. + Weight::from_parts(12_515_830, 2626) + // Standard Error: 2_154 + .saturating_add(Weight::from_parts(147_919, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. /// The range of component `x` is `[0, 100]`. fn set_identity(r: u32, x: u32, ) -> Weight { - // Minimum execution time: 41_872 nanoseconds. - Weight::from_ref_time(40_230_216 as u64) - // Standard Error: 2_342 - .saturating_add(Weight::from_ref_time(145_168 as u64).saturating_mul(r as u64)) - // Standard Error: 457 - .saturating_add(Weight::from_ref_time(291_732 as u64).saturating_mul(x as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Identity IdentityOf (r:1 w:0) - // Storage: Identity SubsOf (r:1 w:1) - // Storage: Identity SuperOf (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `442 + r * (5 ±0)` + // Estimated: `11003` + // Minimum execution time: 32_949_000 picoseconds. + Weight::from_parts(31_329_634, 11003) + // Standard Error: 4_496 + .saturating_add(Weight::from_parts(203_570, 0).saturating_mul(r.into())) + // Standard Error: 877 + .saturating_add(Weight::from_parts(429_346, 0).saturating_mul(x.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:100 w:100) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `s` is `[0, 100]`. fn set_subs_new(s: u32, ) -> Weight { - // Minimum execution time: 12_024 nanoseconds. - Weight::from_ref_time(32_550_819 as u64) - // Standard Error: 5_057 - .saturating_add(Weight::from_ref_time(2_521_245 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(s as u64))) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(s as u64))) - } - // Storage: Identity IdentityOf (r:1 w:0) - // Storage: Identity SubsOf (r:1 w:1) - // Storage: Identity SuperOf (r:0 w:2) + // Proof Size summary in bytes: + // Measured: `101` + // Estimated: `11003 + s * (2589 ±0)` + // Minimum execution time: 9_157_000 picoseconds. + Weight::from_parts(24_917_444, 11003) + // Standard Error: 4_554 + .saturating_add(Weight::from_parts(3_279_868, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(s.into()))) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) + .saturating_add(Weight::from_parts(0, 2589).saturating_mul(s.into())) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:0 w:100) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `p` is `[0, 100]`. fn set_subs_old(p: u32, ) -> Weight { - // Minimum execution time: 12_232 nanoseconds. - Weight::from_ref_time(34_009_761 as u64) - // Standard Error: 5_047 - .saturating_add(Weight::from_ref_time(1_113_100 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(p as u64))) - } - // Storage: Identity SubsOf (r:1 w:1) - // Storage: Identity IdentityOf (r:1 w:1) - // Storage: Identity SuperOf (r:0 w:100) + // Proof Size summary in bytes: + // Measured: `194 + p * (32 ±0)` + // Estimated: `11003` + // Minimum execution time: 9_240_000 picoseconds. + Weight::from_parts(23_326_035, 11003) + // Standard Error: 3_664 + .saturating_add(Weight::from_parts(1_439_873, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(p.into()))) + } + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:0 w:100) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. /// The range of component `s` is `[0, 100]`. /// The range of component `x` is `[0, 100]`. fn clear_identity(r: u32, s: u32, x: u32, ) -> Weight { - // Minimum execution time: 57_144 nanoseconds. - Weight::from_ref_time(41_559_247 as u64) - // Standard Error: 9_996 - .saturating_add(Weight::from_ref_time(146_770 as u64).saturating_mul(r as u64)) - // Standard Error: 1_952 - .saturating_add(Weight::from_ref_time(1_086_673 as u64).saturating_mul(s as u64)) - // Standard Error: 1_952 - .saturating_add(Weight::from_ref_time(162_481 as u64).saturating_mul(x as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(s as u64))) - } - // Storage: Identity Registrars (r:1 w:0) - // Storage: Identity IdentityOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `469 + r * (5 ±0) + s * (32 ±0) + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 55_687_000 picoseconds. + Weight::from_parts(30_695_182, 11003) + // Standard Error: 9_921 + .saturating_add(Weight::from_parts(162_357, 0).saturating_mul(r.into())) + // Standard Error: 1_937 + .saturating_add(Weight::from_parts(1_427_998, 0).saturating_mul(s.into())) + // Standard Error: 1_937 + .saturating_add(Weight::from_parts(247_578, 0).saturating_mul(x.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) + } + /// Storage: Identity Registrars (r:1 w:0) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. /// The range of component `x` is `[0, 100]`. fn request_judgement(r: u32, x: u32, ) -> Weight { - // Minimum execution time: 44_726 nanoseconds. - Weight::from_ref_time(41_637_308 as u64) - // Standard Error: 1_907 - .saturating_add(Weight::from_ref_time(219_078 as u64).saturating_mul(r as u64)) - // Standard Error: 372 - .saturating_add(Weight::from_ref_time(309_888 as u64).saturating_mul(x as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Identity IdentityOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `367 + r * (57 ±0) + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 34_876_000 picoseconds. + Weight::from_parts(32_207_018, 11003) + // Standard Error: 5_247 + .saturating_add(Weight::from_parts(249_156, 0).saturating_mul(r.into())) + // Standard Error: 1_023 + .saturating_add(Weight::from_parts(458_329, 0).saturating_mul(x.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. /// The range of component `x` is `[0, 100]`. fn cancel_request(r: u32, x: u32, ) -> Weight { - // Minimum execution time: 39_719 nanoseconds. - Weight::from_ref_time(38_008_751 as u64) - // Standard Error: 2_394 - .saturating_add(Weight::from_ref_time(181_870 as u64).saturating_mul(r as u64)) - // Standard Error: 467 - .saturating_add(Weight::from_ref_time(314_990 as u64).saturating_mul(x as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Identity Registrars (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `398 + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 30_689_000 picoseconds. + Weight::from_parts(31_967_170, 11003) + // Standard Error: 5_387 + .saturating_add(Weight::from_parts(42_676, 0).saturating_mul(r.into())) + // Standard Error: 1_051 + .saturating_add(Weight::from_parts(446_213, 0).saturating_mul(x.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Identity Registrars (r:1 w:1) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) /// The range of component `r` is `[1, 19]`. fn set_fee(r: u32, ) -> Weight { - // Minimum execution time: 10_634 nanoseconds. - Weight::from_ref_time(11_383_704 as u64) - // Standard Error: 2_250 - .saturating_add(Weight::from_ref_time(193_094 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Identity Registrars (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `89 + r * (57 ±0)` + // Estimated: `2626` + // Minimum execution time: 7_357_000 picoseconds. + Weight::from_parts(7_932_950, 2626) + // Standard Error: 1_804 + .saturating_add(Weight::from_parts(132_653, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Identity Registrars (r:1 w:1) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) /// The range of component `r` is `[1, 19]`. fn set_account_id(r: u32, ) -> Weight { - // Minimum execution time: 10_840 nanoseconds. - Weight::from_ref_time(11_638_740 as u64) - // Standard Error: 1_985 - .saturating_add(Weight::from_ref_time(193_016 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Identity Registrars (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `89 + r * (57 ±0)` + // Estimated: `2626` + // Minimum execution time: 7_437_000 picoseconds. + Weight::from_parts(8_051_889, 2626) + // Standard Error: 1_997 + .saturating_add(Weight::from_parts(129_592, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Identity Registrars (r:1 w:1) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) /// The range of component `r` is `[1, 19]`. fn set_fields(r: u32, ) -> Weight { - // Minimum execution time: 10_748 nanoseconds. - Weight::from_ref_time(11_346_901 as u64) - // Standard Error: 2_132 - .saturating_add(Weight::from_ref_time(196_630 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Identity Registrars (r:1 w:0) - // Storage: Identity IdentityOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `89 + r * (57 ±0)` + // Estimated: `2626` + // Minimum execution time: 7_385_000 picoseconds. + Weight::from_parts(7_911_589, 2626) + // Standard Error: 1_791 + .saturating_add(Weight::from_parts(125_788, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Identity Registrars (r:1 w:0) + /// Proof: Identity Registrars (max_values: Some(1), max_size: Some(1141), added: 1636, mode: MaxEncodedLen) + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) /// The range of component `r` is `[1, 19]`. /// The range of component `x` is `[0, 100]`. fn provide_judgement(r: u32, x: u32, ) -> Weight { - // Minimum execution time: 33_682 nanoseconds. - Weight::from_ref_time(31_336_603 as u64) - // Standard Error: 3_056 - .saturating_add(Weight::from_ref_time(200_403 as u64).saturating_mul(r as u64)) - // Standard Error: 565 - .saturating_add(Weight::from_ref_time(525_142 as u64).saturating_mul(x as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Identity SubsOf (r:1 w:1) - // Storage: Identity IdentityOf (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Identity SuperOf (r:0 w:100) + // Proof Size summary in bytes: + // Measured: `445 + r * (57 ±0) + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 24_073_000 picoseconds. + Weight::from_parts(17_817_684, 11003) + // Standard Error: 8_612 + .saturating_add(Weight::from_parts(406_251, 0).saturating_mul(r.into())) + // Standard Error: 1_593 + .saturating_add(Weight::from_parts(755_225, 0).saturating_mul(x.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: Identity IdentityOf (r:1 w:1) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:0 w:100) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `r` is `[1, 20]`. /// The range of component `s` is `[0, 100]`. /// The range of component `x` is `[0, 100]`. fn kill_identity(r: u32, s: u32, x: u32, ) -> Weight { - // Minimum execution time: 68_794 nanoseconds. - Weight::from_ref_time(52_114_486 as u64) - // Standard Error: 4_808 - .saturating_add(Weight::from_ref_time(153_462 as u64).saturating_mul(r as u64)) - // Standard Error: 939 - .saturating_add(Weight::from_ref_time(1_084_612 as u64).saturating_mul(s as u64)) - // Standard Error: 939 - .saturating_add(Weight::from_ref_time(170_112 as u64).saturating_mul(x as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(s as u64))) - } - // Storage: Identity IdentityOf (r:1 w:0) - // Storage: Identity SuperOf (r:1 w:1) - // Storage: Identity SubsOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `676 + r * (5 ±0) + s * (32 ±0) + x * (66 ±0)` + // Estimated: `11003` + // Minimum execution time: 73_981_000 picoseconds. + Weight::from_parts(51_684_057, 11003) + // Standard Error: 12_662 + .saturating_add(Weight::from_parts(145_285, 0).saturating_mul(r.into())) + // Standard Error: 2_472 + .saturating_add(Weight::from_parts(1_421_039, 0).saturating_mul(s.into())) + // Standard Error: 2_472 + .saturating_add(Weight::from_parts(240_907, 0).saturating_mul(x.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:1 w:1) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) /// The range of component `s` is `[0, 99]`. fn add_sub(s: u32, ) -> Weight { - // Minimum execution time: 37_914 nanoseconds. - Weight::from_ref_time(43_488_083 as u64) - // Standard Error: 1_631 - .saturating_add(Weight::from_ref_time(118_845 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Identity IdentityOf (r:1 w:0) - // Storage: Identity SuperOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `475 + s * (36 ±0)` + // Estimated: `11003` + // Minimum execution time: 29_367_000 picoseconds. + Weight::from_parts(34_214_998, 11003) + // Standard Error: 1_522 + .saturating_add(Weight::from_parts(114_551, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:1 w:1) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) /// The range of component `s` is `[1, 100]`. fn rename_sub(s: u32, ) -> Weight { - // Minimum execution time: 16_124 nanoseconds. - Weight::from_ref_time(18_580_462 as u64) - // Standard Error: 688 - .saturating_add(Weight::from_ref_time(67_220 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Identity IdentityOf (r:1 w:0) - // Storage: Identity SuperOf (r:1 w:1) - // Storage: Identity SubsOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `591 + s * (3 ±0)` + // Estimated: `11003` + // Minimum execution time: 12_384_000 picoseconds. + Weight::from_parts(14_417_903, 11003) + // Standard Error: 539 + .saturating_add(Weight::from_parts(38_371, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Identity IdentityOf (r:1 w:0) + /// Proof: Identity IdentityOf (max_values: None, max_size: Some(7538), added: 10013, mode: MaxEncodedLen) + /// Storage: Identity SuperOf (r:1 w:1) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) /// The range of component `s` is `[1, 100]`. fn remove_sub(s: u32, ) -> Weight { - // Minimum execution time: 41_517 nanoseconds. - Weight::from_ref_time(45_123_530 as u64) - // Standard Error: 1_530 - .saturating_add(Weight::from_ref_time(105_429 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Identity SuperOf (r:1 w:1) - // Storage: Identity SubsOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `638 + s * (35 ±0)` + // Estimated: `11003` + // Minimum execution time: 33_327_000 picoseconds. + Weight::from_parts(36_208_941, 11003) + // Standard Error: 1_240 + .saturating_add(Weight::from_parts(105_805, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Identity SuperOf (r:1 w:1) + /// Proof: Identity SuperOf (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: Identity SubsOf (r:1 w:1) + /// Proof: Identity SubsOf (max_values: None, max_size: Some(3258), added: 5733, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `s` is `[0, 99]`. fn quit_sub(s: u32, ) -> Weight { - // Minimum execution time: 30_171 nanoseconds. - Weight::from_ref_time(33_355_514 as u64) - // Standard Error: 1_286 - .saturating_add(Weight::from_ref_time(114_716 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `704 + s * (37 ±0)` + // Estimated: `6723` + // Minimum execution time: 23_764_000 picoseconds. + Weight::from_parts(26_407_731, 6723) + // Standard Error: 1_025 + .saturating_add(Weight::from_parts(101_112, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } } diff --git a/frame/im-online/Cargo.toml b/frame/im-online/Cargo.toml index d76fba4fb649..1adbb2ae9043 100644 --- a/frame/im-online/Cargo.toml +++ b/frame/im-online/Cargo.toml @@ -13,19 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive", "serde"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } -sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../../primitives/application-crypto", features = ["serde"] } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core", features = ["serde"] } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime", features = ["serde"] } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking", features = ["serde"] } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-session = { version = "4.0.0-dev", path = "../session" } diff --git a/frame/im-online/src/benchmarking.rs b/frame/im-online/src/benchmarking.rs index edc21043c34d..d8170d4817e3 100644 --- a/frame/im-online/src/benchmarking.rs +++ b/frame/im-online/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,10 +21,9 @@ use super::*; -use frame_benchmarking::benchmarks; +use frame_benchmarking::v1::benchmarks; use frame_support::{traits::UnfilteredDispatchable, WeakBoundedVec}; use frame_system::RawOrigin; -use sp_core::{offchain::OpaqueMultiaddr, OpaquePeerId}; use sp_runtime::{ traits::{ValidateUnsigned, Zero}, transaction_validity::TransactionSource, @@ -33,13 +32,14 @@ use sp_runtime::{ use crate::Pallet as ImOnline; const MAX_KEYS: u32 = 1000; -const MAX_EXTERNAL_ADDRESSES: u32 = 100; pub fn create_heartbeat( k: u32, - e: u32, ) -> Result< - (crate::Heartbeat, ::Signature), + ( + crate::Heartbeat>, + ::Signature, + ), &'static str, > { let mut keys = Vec::new(); @@ -50,13 +50,8 @@ pub fn create_heartbeat( .map_err(|()| "More than the maximum number of keys provided")?; Keys::::put(bounded_keys); - let network_state = OpaqueNetworkState { - peer_id: OpaquePeerId::default(), - external_addresses: vec![OpaqueMultiaddr::new(vec![0; 32]); e as usize], - }; let input_heartbeat = Heartbeat { - block_number: T::BlockNumber::zero(), - network_state, + block_number: frame_system::pallet_prelude::BlockNumberFor::::zero(), session_index: 0, authority_index: k - 1, validators_len: keys.len() as u32, @@ -73,15 +68,13 @@ benchmarks! { #[extra] heartbeat { let k in 1 .. MAX_KEYS; - let e in 1 .. MAX_EXTERNAL_ADDRESSES; - let (input_heartbeat, signature) = create_heartbeat::(k, e)?; + let (input_heartbeat, signature) = create_heartbeat::(k)?; }: _(RawOrigin::None, input_heartbeat, signature) #[extra] validate_unsigned { let k in 1 .. MAX_KEYS; - let e in 1 .. MAX_EXTERNAL_ADDRESSES; - let (input_heartbeat, signature) = create_heartbeat::(k, e)?; + let (input_heartbeat, signature) = create_heartbeat::(k)?; let call = Call::heartbeat { heartbeat: input_heartbeat, signature }; }: { ImOnline::::validate_unsigned(TransactionSource::InBlock, &call) @@ -90,8 +83,7 @@ benchmarks! { validate_unsigned_and_then_heartbeat { let k in 1 .. MAX_KEYS; - let e in 1 .. MAX_EXTERNAL_ADDRESSES; - let (input_heartbeat, signature) = create_heartbeat::(k, e)?; + let (input_heartbeat, signature) = create_heartbeat::(k)?; let call = Call::heartbeat { heartbeat: input_heartbeat, signature }; let call_enc = call.encode(); }: { diff --git a/frame/im-online/src/lib.rs b/frame/im-online/src/lib.rs index f23e610a4874..1de89dd00c81 100644 --- a/frame/im-online/src/lib.rs +++ b/frame/im-online/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,8 +26,7 @@ //! in the current era or session. //! //! The heartbeat is a signed transaction, which was signed using the session key -//! and includes the recent best block number of the local validators chain as well -//! as the [NetworkState](../../client/offchain/struct.NetworkState.html). +//! and includes the recent best block number of the local validators chain. //! It is submitted as an Unsigned Transaction via off-chain workers. //! //! - [`Config`] @@ -78,30 +77,34 @@ #![cfg_attr(not(feature = "std"), no_std)] mod benchmarking; +pub mod migration; mod mock; mod tests; pub mod weights; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ + pallet_prelude::*, traits::{ EstimateNextSessionRotation, Get, OneSessionHandler, ValidatorSet, - ValidatorSetWithIdentification, WrapperOpaque, + ValidatorSetWithIdentification, }, BoundedSlice, WeakBoundedVec, }; -use frame_system::offchain::{SendTransactionTypes, SubmitTransaction}; +use frame_system::{ + offchain::{SendTransactionTypes, SubmitTransaction}, + pallet_prelude::*, +}; pub use pallet::*; use scale_info::TypeInfo; use sp_application_crypto::RuntimeAppPublic; -use sp_core::offchain::OpaqueNetworkState; use sp_runtime::{ offchain::storage::{MutateStorageError, StorageRetrievalError, StorageValueRef}, traits::{AtLeast32BitUnsigned, Convert, Saturating, TrailingZeroInput}, PerThing, Perbill, Permill, RuntimeDebug, SaturatedConversion, }; use sp_staking::{ - offence::{Kind, Offence, ReportOffence}, + offence::{DisableStrategy, Kind, Offence, ReportOffence}, SessionIndex, }; use sp_std::prelude::*; @@ -190,7 +193,6 @@ enum OffchainErr { AlreadyOnline(u32), FailedSigning, FailedToAcquireLock, - NetworkState, SubmitTransaction, } @@ -206,7 +208,6 @@ impl sp_std::fmt::Debug for OffchainErr write!(fmt, "Failed to sign heartbeat"), OffchainErr::FailedToAcquireLock => write!(fmt, "Failed to acquire lock"), - OffchainErr::NetworkState => write!(fmt, "Failed to fetch network state"), OffchainErr::SubmitTransaction => write!(fmt, "Failed to submit transaction"), } } @@ -222,8 +223,6 @@ where { /// Block number at the time heartbeat is created.. pub block_number: BlockNumber, - /// A state of local network (peer id and external addresses) - pub network_state: OpaqueNetworkState, /// Index of the current session. pub session_index: SessionIndex, /// An index of the authority on the list of validators. @@ -232,64 +231,6 @@ where pub validators_len: u32, } -/// A type that is the same as [`OpaqueNetworkState`] but with [`Vec`] replaced with -/// [`WeakBoundedVec`] where Limit is the respective size limit -/// `PeerIdEncodingLimit` represents the size limit of the encoding of `PeerId` -/// `MultiAddrEncodingLimit` represents the size limit of the encoding of `MultiAddr` -/// `AddressesLimit` represents the size limit of the vector of peers connected -#[derive(Clone, Eq, PartialEq, Encode, Decode, MaxEncodedLen, TypeInfo)] -#[codec(mel_bound())] -#[scale_info(skip_type_params(PeerIdEncodingLimit, MultiAddrEncodingLimit, AddressesLimit))] -pub struct BoundedOpaqueNetworkState -where - PeerIdEncodingLimit: Get, - MultiAddrEncodingLimit: Get, - AddressesLimit: Get, -{ - /// PeerId of the local node in SCALE encoded. - pub peer_id: WeakBoundedVec, - /// List of addresses the node knows it can be reached as. - pub external_addresses: - WeakBoundedVec, AddressesLimit>, -} - -impl, MultiAddrEncodingLimit: Get, AddressesLimit: Get> - BoundedOpaqueNetworkState -{ - fn force_from(ons: &OpaqueNetworkState) -> Self { - let peer_id = WeakBoundedVec::<_, PeerIdEncodingLimit>::force_from( - ons.peer_id.0.clone(), - Some( - "Warning: The size of the encoding of PeerId \ - is bigger than expected. A runtime configuration \ - adjustment may be needed.", - ), - ); - - let external_addresses = WeakBoundedVec::<_, AddressesLimit>::force_from( - ons.external_addresses - .iter() - .map(|x| { - WeakBoundedVec::<_, MultiAddrEncodingLimit>::force_from( - x.0.clone(), - Some( - "Warning: The size of the encoding of MultiAddr \ - is bigger than expected. A runtime configuration \ - adjustment may be needed.", - ), - ) - }) - .collect(), - Some( - "Warning: The network has more peers than expected \ - A runtime configuration adjustment may be needed.", - ), - ); - - Self { peer_id, external_addresses } - } -} - /// A type for representing the validator id in a session. pub type ValidatorId = <::ValidatorSet as ValidatorSet< ::AccountId, @@ -304,16 +245,17 @@ pub type IdentificationTuple = ( >>::Identification, ); -type OffchainResult = Result::BlockNumber>>; +type OffchainResult = Result>>; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); #[pallet::config] @@ -332,10 +274,6 @@ pub mod pallet { /// The maximum number of peers to be stored in `ReceivedHeartbeats` type MaxPeerInHeartbeats: Get; - /// The maximum size of the encoding of `PeerId` and `MultiAddr` that are coming - /// from the hearbeat - type MaxPeerDataEncodingSize: Get; - /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -349,7 +287,7 @@ pub mod pallet { /// rough time when we should start considering sending heartbeats, since the workers /// avoids sending them at the very beginning of the session, assuming there is a /// chance the authority will produce a block and they won't be necessary. - type NextSessionRotation: EstimateNextSessionRotation; + type NextSessionRotation: EstimateNextSessionRotation>; /// A type that gives us the ability to submit unresponsiveness offence reports. type ReportUnresponsiveness: ReportOffence< @@ -401,38 +339,25 @@ pub mod pallet { /// more accurate then the value we calculate for `HeartbeatAfter`. #[pallet::storage] #[pallet::getter(fn heartbeat_after)] - pub(crate) type HeartbeatAfter = StorageValue<_, T::BlockNumber, ValueQuery>; + pub(super) type HeartbeatAfter = StorageValue<_, BlockNumberFor, ValueQuery>; /// The current set of keys that may issue a heartbeat. #[pallet::storage] #[pallet::getter(fn keys)] - pub(crate) type Keys = + pub(super) type Keys = StorageValue<_, WeakBoundedVec, ValueQuery>; - /// For each session index, we keep a mapping of `SessionIndex` and `AuthIndex` to - /// `WrapperOpaque`. + /// For each session index, we keep a mapping of `SessionIndex` and `AuthIndex`. #[pallet::storage] #[pallet::getter(fn received_heartbeats)] - pub(crate) type ReceivedHeartbeats = StorageDoubleMap< - _, - Twox64Concat, - SessionIndex, - Twox64Concat, - AuthIndex, - WrapperOpaque< - BoundedOpaqueNetworkState< - T::MaxPeerDataEncodingSize, - T::MaxPeerDataEncodingSize, - T::MaxPeerInHeartbeats, - >, - >, - >; + pub(super) type ReceivedHeartbeats = + StorageDoubleMap<_, Twox64Concat, SessionIndex, Twox64Concat, AuthIndex, bool>; /// For each session index, we keep a mapping of `ValidatorId` to the /// number of blocks authored by the given authority. #[pallet::storage] #[pallet::getter(fn authored_blocks)] - pub(crate) type AuthoredBlocks = StorageDoubleMap< + pub(super) type AuthoredBlocks = StorageDoubleMap< _, Twox64Concat, SessionIndex, @@ -443,19 +368,13 @@ pub mod pallet { >; #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { pub keys: Vec, } - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - GenesisConfig { keys: Default::default() } - } - } - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { Pallet::::initialize_keys(&self.keys); } @@ -463,25 +382,18 @@ pub mod pallet { #[pallet::call] impl Pallet { - /// # - /// - Complexity: `O(K + E)` where K is length of `Keys` (heartbeat.validators_len) and E is - /// length of `heartbeat.network_state.external_address` + /// ## Complexity: + /// - `O(K)` where K is length of `Keys` (heartbeat.validators_len) /// - `O(K)`: decoding of length `K` - /// - `O(E)`: decoding/encoding of length `E` - /// - DbReads: pallet_session `Validators`, pallet_session `CurrentIndex`, `Keys`, - /// `ReceivedHeartbeats` - /// - DbWrites: `ReceivedHeartbeats` - /// # // NOTE: the weight includes the cost of validate_unsigned as it is part of the cost to // import block with such an extrinsic. #[pallet::call_index(0)] #[pallet::weight(::WeightInfo::validate_unsigned_and_then_heartbeat( - heartbeat.validators_len as u32, - heartbeat.network_state.external_addresses.len() as u32, + heartbeat.validators_len, ))] pub fn heartbeat( origin: OriginFor, - heartbeat: Heartbeat, + heartbeat: Heartbeat>, // since signature verification is done in `validate_unsigned` // we can skip doing it here again. _signature: ::Signature, @@ -490,22 +402,13 @@ pub mod pallet { let current_session = T::ValidatorSet::session_index(); let exists = - ReceivedHeartbeats::::contains_key(¤t_session, &heartbeat.authority_index); + ReceivedHeartbeats::::contains_key(current_session, heartbeat.authority_index); let keys = Keys::::get(); let public = keys.get(heartbeat.authority_index as usize); if let (false, Some(public)) = (exists, public) { Self::deposit_event(Event::::HeartbeatReceived { authority_id: public.clone() }); - let network_state_bounded = BoundedOpaqueNetworkState::< - T::MaxPeerDataEncodingSize, - T::MaxPeerDataEncodingSize, - T::MaxPeerInHeartbeats, - >::force_from(&heartbeat.network_state); - ReceivedHeartbeats::::insert( - ¤t_session, - &heartbeat.authority_index, - WrapperOpaque::from(network_state_bounded), - ); + ReceivedHeartbeats::::insert(current_session, heartbeat.authority_index, true); Ok(()) } else if exists { @@ -602,15 +505,11 @@ pub mod pallet { /// Keep track of number of authored blocks per authority, uncles are counted as /// well since they're a valid proof of being online. impl - pallet_authorship::EventHandler, T::BlockNumber> for Pallet + pallet_authorship::EventHandler, BlockNumberFor> for Pallet { fn note_author(author: ValidatorId) { Self::note_authorship(author); } - - fn note_uncle(author: ValidatorId, _age: T::BlockNumber) { - Self::note_authorship(author); - } } impl Pallet { @@ -633,26 +532,26 @@ impl Pallet { fn is_online_aux(authority_index: AuthIndex, authority: &ValidatorId) -> bool { let current_session = T::ValidatorSet::session_index(); - ReceivedHeartbeats::::contains_key(¤t_session, &authority_index) || - AuthoredBlocks::::get(¤t_session, authority) != 0 + ReceivedHeartbeats::::contains_key(current_session, authority_index) || + AuthoredBlocks::::get(current_session, authority) != 0 } /// Returns `true` if a heartbeat has been received for the authority at `authority_index` in /// the authorities series, during the current session. Otherwise `false`. pub fn received_heartbeat_in_current_session(authority_index: AuthIndex) -> bool { let current_session = T::ValidatorSet::session_index(); - ReceivedHeartbeats::::contains_key(¤t_session, &authority_index) + ReceivedHeartbeats::::contains_key(current_session, authority_index) } /// Note that the given authority has authored a block in the current session. fn note_authorship(author: ValidatorId) { let current_session = T::ValidatorSet::session_index(); - AuthoredBlocks::::mutate(¤t_session, author, |authored| *authored += 1); + AuthoredBlocks::::mutate(current_session, author, |authored| *authored += 1); } pub(crate) fn send_heartbeats( - block_number: T::BlockNumber, + block_number: BlockNumberFor, ) -> OffchainResult>> { const START_HEARTBEAT_RANDOM_PERIOD: Permill = Permill::from_percent(10); const START_HEARTBEAT_FINAL_PERIOD: Permill = Permill::from_percent(80); @@ -715,20 +614,13 @@ impl Pallet { authority_index: u32, key: T::AuthorityId, session_index: SessionIndex, - block_number: T::BlockNumber, + block_number: BlockNumberFor, validators_len: u32, ) -> OffchainResult { // A helper function to prepare heartbeat call. let prepare_heartbeat = || -> OffchainResult> { - let network_state = - sp_io::offchain::network_state().map_err(|_| OffchainErr::NetworkState)?; - let heartbeat = Heartbeat { - block_number, - network_state, - session_index, - authority_index, - validators_len, - }; + let heartbeat = + Heartbeat { block_number, session_index, authority_index, validators_len }; let signature = key.sign(&heartbeat.encode()).ok_or(OffchainErr::FailedSigning)?; @@ -764,7 +656,7 @@ impl Pallet { // // At index `idx`: // 1. A (ImOnline) public key to be used by a validator at index `idx` to send im-online - // heartbeats. + // heartbeats. let authorities = Keys::::get(); // local keystore @@ -785,7 +677,7 @@ impl Pallet { fn with_heartbeat_lock( authority_index: u32, session_index: SessionIndex, - now: T::BlockNumber, + now: BlockNumberFor, f: impl FnOnce() -> OffchainResult, ) -> OffchainResult { let key = { @@ -795,7 +687,7 @@ impl Pallet { }; let storage = StorageValueRef::persistent(&key); let res = storage.mutate( - |status: Result>, StorageRetrievalError>| { + |status: Result>>, StorageRetrievalError>| { // Check if there is already a lock for that particular block. // This means that the heartbeat has already been sent, and we are just waiting // for it to be included. However if it doesn't get included for INCLUDE_THRESHOLD @@ -902,9 +794,9 @@ impl OneSessionHandler for Pallet { // current session, they have already been processed and won't be needed // anymore. #[allow(deprecated)] - ReceivedHeartbeats::::remove_prefix(&T::ValidatorSet::session_index(), None); + ReceivedHeartbeats::::remove_prefix(T::ValidatorSet::session_index(), None); #[allow(deprecated)] - AuthoredBlocks::::remove_prefix(&T::ValidatorSet::session_index(), None); + AuthoredBlocks::::remove_prefix(T::ValidatorSet::session_index(), None); if offenders.is_empty() { Self::deposit_event(Event::::AllGood); @@ -959,6 +851,10 @@ impl Offence for UnresponsivenessOffence { self.session_index } + fn disable_strategy(&self) -> DisableStrategy { + DisableStrategy::Never + } + fn slash_fraction(&self, offenders: u32) -> Perbill { // the formula is min((3 * (k - (n / 10 + 1))) / n, 1) * 0.07 // basically, 10% can be offline with no slash, but after that, it linearly climbs up to 7% diff --git a/frame/im-online/src/migration.rs b/frame/im-online/src/migration.rs new file mode 100644 index 000000000000..84652965972e --- /dev/null +++ b/frame/im-online/src/migration.rs @@ -0,0 +1,161 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Storage migrations for the im-online pallet. + +use super::*; +use frame_support::{storage_alias, traits::OnRuntimeUpgrade}; + +#[cfg(feature = "try-runtime")] +use frame_support::ensure; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + +/// The log target. +const TARGET: &str = "runtime::im-online::migration::v1"; + +/// The original data layout of the im-online pallet (`ReceivedHeartbeats` storage item). +mod v0 { + use super::*; + use frame_support::traits::WrapperOpaque; + + #[derive(Encode, Decode, Default)] + pub(super) struct BoundedOpaqueNetworkState { + /// PeerId of the local node in SCALE encoded. + pub peer_id: Vec, + /// List of addresses the node knows it can be reached as. + pub external_addresses: Vec>, + } + + #[storage_alias] + pub(super) type ReceivedHeartbeats = StorageDoubleMap< + Pallet, + Twox64Concat, + SessionIndex, + Twox64Concat, + AuthIndex, + WrapperOpaque, + >; +} + +pub mod v1 { + use super::*; + + /// Simple migration that replaces `ReceivedHeartbeats` values with `true`. + pub struct Migration(sp_std::marker::PhantomData); + + impl OnRuntimeUpgrade for Migration { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + let count = v0::ReceivedHeartbeats::::iter().count(); + log::info!(target: TARGET, "Migrating {} received heartbeats", count); + + Ok((count as u32).encode()) + } + + fn on_runtime_upgrade() -> Weight { + let mut weight = T::DbWeight::get().reads(1); + if StorageVersion::get::>() != 0 { + log::warn!( + target: TARGET, + "Skipping migration because current storage version is not 0" + ); + return weight + } + + let heartbeats = v0::ReceivedHeartbeats::::drain().collect::>(); + + weight.saturating_accrue(T::DbWeight::get().reads(heartbeats.len() as u64)); + weight.saturating_accrue(T::DbWeight::get().writes(heartbeats.len() as u64)); + + for (session_index, auth_index, _) in heartbeats { + log::trace!( + target: TARGET, + "Migrated received heartbeat for {:?}...", + (session_index, auth_index) + ); + crate::ReceivedHeartbeats::::insert(session_index, auth_index, true); + } + + StorageVersion::new(1).put::>(); + weight.saturating_add(T::DbWeight::get().writes(1)) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(state: Vec) -> DispatchResult { + let old_received_heartbeats: u32 = + Decode::decode(&mut &state[..]).expect("pre_upgrade provides a valid state; qed"); + let new_received_heartbeats = crate::ReceivedHeartbeats::::iter().count(); + + if new_received_heartbeats != old_received_heartbeats as usize { + log::error!( + target: TARGET, + "migrated {} received heartbeats, expected {}", + new_received_heartbeats, + old_received_heartbeats + ); + } + ensure!(StorageVersion::get::>() >= 1, "must upgrade"); + + Ok(()) + } + } +} + +#[cfg(all(feature = "try-runtime", test))] +mod test { + use super::*; + use crate::mock::{new_test_ext, Runtime as T}; + use frame_support::traits::WrapperOpaque; + + #[test] + fn migration_works() { + new_test_ext().execute_with(|| { + assert_eq!(StorageVersion::get::>(), 0); + + // Insert some received heartbeats into the v0 storage: + let current_session = ::ValidatorSet::session_index(); + v0::ReceivedHeartbeats::::insert( + ¤t_session, + 0, + WrapperOpaque(v0::BoundedOpaqueNetworkState::default()), + ); + v0::ReceivedHeartbeats::::insert( + ¤t_session, + 1, + WrapperOpaque(v0::BoundedOpaqueNetworkState::default()), + ); + + // Check that the v0 storage is populated + assert_eq!(v0::ReceivedHeartbeats::::iter().count(), 2); + assert_eq!(crate::ReceivedHeartbeats::::iter().count(), 0, "V1 storage corrupted"); + + // Perform the migration + let state = v1::Migration::::pre_upgrade().unwrap(); + let _w = v1::Migration::::on_runtime_upgrade(); + v1::Migration::::post_upgrade(state).unwrap(); + + // Check that the v1 storage is populated and v0 storage is empty + assert_eq!(v0::ReceivedHeartbeats::::iter().count(), 0); + assert_eq!(crate::ReceivedHeartbeats::::iter().count(), 2); + assert!(crate::ReceivedHeartbeats::::contains_key(¤t_session, 0)); + assert_eq!(Some(true), crate::ReceivedHeartbeats::::get(¤t_session, 1)); + + assert_eq!(StorageVersion::get::>(), 1); + }); + } +} diff --git a/frame/im-online/src/mock.rs b/frame/im-online/src/mock.rs index bcb13fa1be83..85da061fe904 100644 --- a/frame/im-online/src/mock.rs +++ b/frame/im-online/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,9 +27,9 @@ use frame_support::{ use pallet_session::historical as pallet_session_historical; use sp_core::H256; use sp_runtime::{ - testing::{Header, TestXt, UintAuthorityId}, + testing::{TestXt, UintAuthorityId}, traits::{BlakeTwo256, ConvertInto, IdentityLookup}, - Permill, + BuildStorage, Permill, }; use sp_staking::{ offence::{OffenceError, ReportOffence}, @@ -39,16 +39,12 @@ use sp_staking::{ use crate as imonline; use crate::Config; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub struct Runtime { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Session: pallet_session::{Pallet, Call, Storage, Event, Config}, ImOnline: imonline::{Pallet, Call, Storage, Config, Event}, Historical: pallet_session_historical::{Pallet}, @@ -105,7 +101,7 @@ impl ReportOffence for OffenceHandler { } pub fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let mut result: sp_io::TestExternalities = t.into(); // Set the default keys, otherwise session will discard the validator. result.execute_with(|| { @@ -123,14 +119,13 @@ impl frame_system::Config for Runtime { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -169,8 +164,6 @@ impl pallet_session::historical::Config for Runtime { impl pallet_authorship::Config for Runtime { type FindAuthor = (); - type UncleGenerations = ConstU64<5>; - type FilterUncle = (); type EventHandler = ImOnline; } @@ -219,7 +212,6 @@ impl Config for Runtime { type WeightInfo = (); type MaxKeys = ConstU32<10_000>; type MaxPeerInHeartbeats = ConstU32<10_000>; - type MaxPeerDataEncodingSize = ConstU32<1_000>; } impl frame_system::offchain::SendTransactionTypes for Runtime diff --git a/frame/im-online/src/tests.rs b/frame/im-online/src/tests.rs index 366119278d83..79036760c2d4 100644 --- a/frame/im-online/src/tests.rs +++ b/frame/im-online/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,12 +22,9 @@ use super::*; use crate::mock::*; use frame_support::{assert_noop, dispatch}; -use sp_core::{ - offchain::{ - testing::{TestOffchainExt, TestTransactionPoolExt}, - OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, - }, - OpaquePeerId, +use sp_core::offchain::{ + testing::{TestOffchainExt, TestTransactionPoolExt}, + OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, }; use sp_runtime::{ testing::UintAuthorityId, @@ -56,6 +53,9 @@ fn test_unresponsiveness_slash_fraction() { dummy_offence.slash_fraction(17), Perbill::from_parts(46200000), // 4.62% ); + + // Offline offences should never lead to being disabled. + assert_eq!(dummy_offence.disable_strategy(), DisableStrategy::Never); } #[test] @@ -118,14 +118,8 @@ fn heartbeat( id: UintAuthorityId, validators: Vec, ) -> dispatch::DispatchResult { - use frame_support::unsigned::ValidateUnsigned; - let heartbeat = Heartbeat { block_number, - network_state: OpaqueNetworkState { - peer_id: OpaquePeerId(vec![1]), - external_addresses: vec![], - }, session_index, authority_index, validators_len: validators.len() as u32, @@ -209,8 +203,6 @@ fn late_heartbeat_and_invalid_keys_len_should_fail() { #[test] fn should_generate_heartbeats() { - use frame_support::traits::OffchainWorker; - let mut ext = new_test_ext(); let (offchain, _state) = TestOffchainExt::new(); let (pool, state) = TestTransactionPoolExt::new(); @@ -249,7 +241,6 @@ fn should_generate_heartbeats() { heartbeat, Heartbeat { block_number: block, - network_state: sp_io::offchain::network_state().unwrap(), session_index: 2, authority_index: 2, validators_len: 3, @@ -308,11 +299,10 @@ fn should_mark_online_validator_when_block_is_authored() { // when ImOnline::note_author(1); - ImOnline::note_uncle(2, 0); // then assert!(ImOnline::is_online(0)); - assert!(ImOnline::is_online(1)); + assert!(!ImOnline::is_online(1)); assert!(!ImOnline::is_online(2)); }); } @@ -338,7 +328,7 @@ fn should_not_send_a_report_if_already_online() { assert_eq!(Session::current_index(), 2); assert_eq!(Session::validators(), vec![1, 2, 3]); ImOnline::note_author(2); - ImOnline::note_uncle(3, 0); + ImOnline::note_author(3); // when UintAuthorityId::set_all_keys(vec![1, 2, 3]); @@ -363,21 +353,13 @@ fn should_not_send_a_report_if_already_online() { assert_eq!( heartbeat, - Heartbeat { - block_number: 4, - network_state: sp_io::offchain::network_state().unwrap(), - session_index: 2, - authority_index: 0, - validators_len: 3, - } + Heartbeat { block_number: 4, session_index: 2, authority_index: 0, validators_len: 3 } ); }); } #[test] fn should_handle_missing_progress_estimates() { - use frame_support::traits::OffchainWorker; - let mut ext = new_test_ext(); let (offchain, _state) = TestOffchainExt::new(); let (pool, state) = TestTransactionPoolExt::new(); diff --git a/frame/im-online/src/weights.rs b/frame/im-online/src/weights.rs index f81db997c303..c3db02af2578 100644 --- a/frame/im-online/src/weights.rs +++ b/frame/im-online/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_im_online //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_im_online +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,54 +45,67 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_im_online. pub trait WeightInfo { - fn validate_unsigned_and_then_heartbeat(k: u32, e: u32, ) -> Weight; + fn validate_unsigned_and_then_heartbeat(k: u32, ) -> Weight; } /// Weights for pallet_im_online using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Session Validators (r:1 w:0) - // Storage: Session CurrentIndex (r:1 w:0) - // Storage: ImOnline ReceivedHeartbeats (r:1 w:1) - // Storage: ImOnline AuthoredBlocks (r:1 w:0) - // Storage: ImOnline Keys (r:1 w:0) + /// Storage: Session Validators (r:1 w:0) + /// Proof Skipped: Session Validators (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Session CurrentIndex (r:1 w:0) + /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ImOnline Keys (r:1 w:0) + /// Proof: ImOnline Keys (max_values: Some(1), max_size: Some(320002), added: 320497, mode: MaxEncodedLen) + /// Storage: ImOnline ReceivedHeartbeats (r:1 w:1) + /// Proof: ImOnline ReceivedHeartbeats (max_values: None, max_size: Some(25), added: 2500, mode: MaxEncodedLen) + /// Storage: ImOnline AuthoredBlocks (r:1 w:0) + /// Proof: ImOnline AuthoredBlocks (max_values: None, max_size: Some(56), added: 2531, mode: MaxEncodedLen) /// The range of component `k` is `[1, 1000]`. - /// The range of component `e` is `[1, 100]`. - fn validate_unsigned_and_then_heartbeat(k: u32, e: u32, ) -> Weight { - // Minimum execution time: 101_380 nanoseconds. - Weight::from_ref_time(82_735_670 as u64) - // Standard Error: 121 - .saturating_add(Weight::from_ref_time(21_279 as u64).saturating_mul(k as u64)) - // Standard Error: 1_222 - .saturating_add(Weight::from_ref_time(296_343 as u64).saturating_mul(e as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + fn validate_unsigned_and_then_heartbeat(k: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `295 + k * (32 ±0)` + // Estimated: `321487 + k * (1761 ±0)` + // Minimum execution time: 80_568_000 picoseconds. + Weight::from_parts(95_175_595, 321487) + // Standard Error: 627 + .saturating_add(Weight::from_parts(39_094, 0).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1761).saturating_mul(k.into())) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Session Validators (r:1 w:0) - // Storage: Session CurrentIndex (r:1 w:0) - // Storage: ImOnline ReceivedHeartbeats (r:1 w:1) - // Storage: ImOnline AuthoredBlocks (r:1 w:0) - // Storage: ImOnline Keys (r:1 w:0) + /// Storage: Session Validators (r:1 w:0) + /// Proof Skipped: Session Validators (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Session CurrentIndex (r:1 w:0) + /// Proof Skipped: Session CurrentIndex (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: ImOnline Keys (r:1 w:0) + /// Proof: ImOnline Keys (max_values: Some(1), max_size: Some(320002), added: 320497, mode: MaxEncodedLen) + /// Storage: ImOnline ReceivedHeartbeats (r:1 w:1) + /// Proof: ImOnline ReceivedHeartbeats (max_values: None, max_size: Some(25), added: 2500, mode: MaxEncodedLen) + /// Storage: ImOnline AuthoredBlocks (r:1 w:0) + /// Proof: ImOnline AuthoredBlocks (max_values: None, max_size: Some(56), added: 2531, mode: MaxEncodedLen) /// The range of component `k` is `[1, 1000]`. - /// The range of component `e` is `[1, 100]`. - fn validate_unsigned_and_then_heartbeat(k: u32, e: u32, ) -> Weight { - // Minimum execution time: 101_380 nanoseconds. - Weight::from_ref_time(82_735_670 as u64) - // Standard Error: 121 - .saturating_add(Weight::from_ref_time(21_279 as u64).saturating_mul(k as u64)) - // Standard Error: 1_222 - .saturating_add(Weight::from_ref_time(296_343 as u64).saturating_mul(e as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + fn validate_unsigned_and_then_heartbeat(k: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `295 + k * (32 ±0)` + // Estimated: `321487 + k * (1761 ±0)` + // Minimum execution time: 80_568_000 picoseconds. + Weight::from_parts(95_175_595, 321487) + // Standard Error: 627 + .saturating_add(Weight::from_parts(39_094, 0).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1761).saturating_mul(k.into())) } } diff --git a/frame/indices/Cargo.toml b/frame/indices/Cargo.toml index b667916ebcb4..e0714cdbccb3 100644 --- a/frame/indices/Cargo.toml +++ b/frame/indices/Cargo.toml @@ -13,16 +13,16 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-keyring = { version = "7.0.0", optional = true, path = "../../primitives/keyring" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-keyring = { version = "24.0.0", optional = true, path = "../../primitives/keyring" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } diff --git a/frame/indices/src/benchmarking.rs b/frame/indices/src/benchmarking.rs index f462f22284d4..bd173815cb34 100644 --- a/frame/indices/src/benchmarking.rs +++ b/frame/indices/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller}; use frame_system::RawOrigin; use sp_runtime::traits::Bounded; diff --git a/frame/indices/src/lib.rs b/frame/indices/src/lib.rs index 95d3cf4b2eed..3c0b49304131 100644 --- a/frame/indices/src/lib.rs +++ b/frame/indices/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -75,8 +75,7 @@ pub mod pallet { } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(PhantomData); + pub struct Pallet(_); #[pallet::call] impl Pallet { @@ -90,14 +89,8 @@ pub mod pallet { /// /// Emits `IndexAssigned` if successful. /// - /// # + /// ## Complexity /// - `O(1)`. - /// - One storage mutation (codec `O(1)`). - /// - One reserve operation. - /// - One event. - /// ------------------- - /// - DB Weight: 1 Read/Write (Accounts) - /// # #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::claim())] pub fn claim(origin: OriginFor, index: T::AccountIndex) -> DispatchResult { @@ -122,16 +115,8 @@ pub mod pallet { /// /// Emits `IndexAssigned` if successful. /// - /// # + /// ## Complexity /// - `O(1)`. - /// - One storage mutation (codec `O(1)`). - /// - One transfer operation. - /// - One event. - /// ------------------- - /// - DB Weight: - /// - Reads: Indices Accounts, System Account (recipient) - /// - Writes: Indices Accounts, System Account (recipient) - /// # #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::transfer())] pub fn transfer( @@ -165,14 +150,8 @@ pub mod pallet { /// /// Emits `IndexFreed` if successful. /// - /// # + /// ## Complexity /// - `O(1)`. - /// - One storage mutation (codec `O(1)`). - /// - One reserve operation. - /// - One event. - /// ------------------- - /// - DB Weight: 1 Read/Write (Accounts) - /// # #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::free())] pub fn free(origin: OriginFor, index: T::AccountIndex) -> DispatchResult { @@ -200,16 +179,8 @@ pub mod pallet { /// /// Emits `IndexAssigned` if successful. /// - /// # + /// ## Complexity /// - `O(1)`. - /// - One storage mutation (codec `O(1)`). - /// - Up to one reserve operation. - /// - One event. - /// ------------------- - /// - DB Weight: - /// - Reads: Indices Accounts, System Account (original owner) - /// - Writes: Indices Accounts, System Account (original owner) - /// # #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::force_transfer())] pub fn force_transfer( @@ -241,14 +212,8 @@ pub mod pallet { /// /// Emits `IndexFrozen` if successful. /// - /// # + /// ## Complexity /// - `O(1)`. - /// - One storage mutation (codec `O(1)`). - /// - Up to one slash operation. - /// - One event. - /// ------------------- - /// - DB Weight: 1 Read/Write (Accounts) - /// # #[pallet::call_index(4)] #[pallet::weight(T::WeightInfo::freeze())] pub fn freeze(origin: OriginFor, index: T::AccountIndex) -> DispatchResult { @@ -298,19 +263,13 @@ pub mod pallet { StorageMap<_, Blake2_128Concat, T::AccountIndex, (T::AccountId, BalanceOf, bool)>; #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { pub indices: Vec<(T::AccountIndex, T::AccountId)>, } - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - Self { indices: Default::default() } - } - } - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { for (a, b) in &self.indices { >::insert(a, (b, >::zero(), false)) diff --git a/frame/indices/src/mock.rs b/frame/indices/src/mock.rs index 6b5373bb43b5..d63081e0b73f 100644 --- a/frame/indices/src/mock.rs +++ b/frame/indices/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,18 +22,14 @@ use crate::{self as pallet_indices, Config}; use frame_support::traits::{ConstU32, ConstU64}; use sp_core::H256; -use sp_runtime::testing::Header; +use sp_runtime::BuildStorage; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Indices: pallet_indices::{Pallet, Call, Storage, Config, Event}, } @@ -46,13 +42,12 @@ impl frame_system::Config for Test { type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = Indices; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -76,6 +71,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } impl Config for Test { @@ -87,7 +86,7 @@ impl Config for Test { } pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 20), (3, 30), (4, 40), (5, 50), (6, 60)], } diff --git a/frame/indices/src/tests.rs b/frame/indices/src/tests.rs index bed6cfffaa82..56b101900038 100644 --- a/frame/indices/src/tests.rs +++ b/frame/indices/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/indices/src/weights.rs b/frame/indices/src/weights.rs index 7b974875cdf5..d081cc738b1d 100644 --- a/frame/indices/src/weights.rs +++ b/frame/indices/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_indices //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_indices +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_indices. pub trait WeightInfo { @@ -57,82 +62,126 @@ pub trait WeightInfo { /// Weights for pallet_indices using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Indices Accounts (r:1 w:1) + /// Storage: Indices Accounts (r:1 w:1) + /// Proof: Indices Accounts (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) fn claim() -> Weight { - // Minimum execution time: 31_756 nanoseconds. - Weight::from_ref_time(32_252_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `3534` + // Minimum execution time: 25_491_000 picoseconds. + Weight::from_parts(26_456_000, 3534) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Indices Accounts (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Indices Accounts (r:1 w:1) + /// Proof: Indices Accounts (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn transfer() -> Weight { - // Minimum execution time: 38_686 nanoseconds. - Weight::from_ref_time(39_402_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `275` + // Estimated: `3593` + // Minimum execution time: 38_027_000 picoseconds. + Weight::from_parts(38_749_000, 3593) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Indices Accounts (r:1 w:1) + /// Storage: Indices Accounts (r:1 w:1) + /// Proof: Indices Accounts (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) fn free() -> Weight { - // Minimum execution time: 33_752 nanoseconds. - Weight::from_ref_time(34_348_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `172` + // Estimated: `3534` + // Minimum execution time: 26_652_000 picoseconds. + Weight::from_parts(27_273_000, 3534) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Indices Accounts (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Indices Accounts (r:1 w:1) + /// Proof: Indices Accounts (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn force_transfer() -> Weight { - // Minimum execution time: 32_739 nanoseconds. - Weight::from_ref_time(33_151_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `275` + // Estimated: `3593` + // Minimum execution time: 29_464_000 picoseconds. + Weight::from_parts(30_959_000, 3593) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Indices Accounts (r:1 w:1) + /// Storage: Indices Accounts (r:1 w:1) + /// Proof: Indices Accounts (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) fn freeze() -> Weight { - // Minimum execution time: 40_369 nanoseconds. - Weight::from_ref_time(40_982_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `172` + // Estimated: `3534` + // Minimum execution time: 29_015_000 picoseconds. + Weight::from_parts(29_714_000, 3534) + .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: Indices Accounts (r:1 w:1) + /// Storage: Indices Accounts (r:1 w:1) + /// Proof: Indices Accounts (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) fn claim() -> Weight { - // Minimum execution time: 31_756 nanoseconds. - Weight::from_ref_time(32_252_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `3534` + // Minimum execution time: 25_491_000 picoseconds. + Weight::from_parts(26_456_000, 3534) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Indices Accounts (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Indices Accounts (r:1 w:1) + /// Proof: Indices Accounts (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn transfer() -> Weight { - // Minimum execution time: 38_686 nanoseconds. - Weight::from_ref_time(39_402_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `275` + // Estimated: `3593` + // Minimum execution time: 38_027_000 picoseconds. + Weight::from_parts(38_749_000, 3593) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Indices Accounts (r:1 w:1) + /// Storage: Indices Accounts (r:1 w:1) + /// Proof: Indices Accounts (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) fn free() -> Weight { - // Minimum execution time: 33_752 nanoseconds. - Weight::from_ref_time(34_348_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `172` + // Estimated: `3534` + // Minimum execution time: 26_652_000 picoseconds. + Weight::from_parts(27_273_000, 3534) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Indices Accounts (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Indices Accounts (r:1 w:1) + /// Proof: Indices Accounts (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn force_transfer() -> Weight { - // Minimum execution time: 32_739 nanoseconds. - Weight::from_ref_time(33_151_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `275` + // Estimated: `3593` + // Minimum execution time: 29_464_000 picoseconds. + Weight::from_parts(30_959_000, 3593) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Indices Accounts (r:1 w:1) + /// Storage: Indices Accounts (r:1 w:1) + /// Proof: Indices Accounts (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) fn freeze() -> Weight { - // Minimum execution time: 40_369 nanoseconds. - Weight::from_ref_time(40_982_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `172` + // Estimated: `3534` + // Minimum execution time: 29_015_000 picoseconds. + Weight::from_parts(29_714_000, 3534) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } diff --git a/frame/randomness-collective-flip/Cargo.toml b/frame/insecure-randomness-collective-flip/Cargo.toml similarity index 61% rename from frame/randomness-collective-flip/Cargo.toml rename to frame/insecure-randomness-collective-flip/Cargo.toml index 435c38d632b6..b0a4dc270b98 100644 --- a/frame/randomness-collective-flip/Cargo.toml +++ b/frame/insecure-randomness-collective-flip/Cargo.toml @@ -1,29 +1,29 @@ [package] -name = "pallet-randomness-collective-flip" +name = "pallet-insecure-randomness-collective-flip" version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" -description = "FRAME randomness collective flip pallet" +description = "Insecure do not use in production: FRAME randomness collective flip pallet" readme = "README.md" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } safe-mix = { version = "1.0", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/randomness-collective-flip/README.md b/frame/insecure-randomness-collective-flip/README.md similarity index 59% rename from frame/randomness-collective-flip/README.md rename to frame/insecure-randomness-collective-flip/README.md index 0730d4abf7cf..ef02e4b5c844 100644 --- a/frame/randomness-collective-flip/README.md +++ b/frame/insecure-randomness-collective-flip/README.md @@ -1,6 +1,10 @@ +# DO NOT USE IN PRODUCTION + +The produced values do not fulfill the cryptographic requirements for random numbers. Should not be used for high-stake production use-cases. + # Randomness Module -The Randomness Collective Flip module provides a [`random`](https://docs.rs/pallet-randomness-collective-flip/latest/pallet_randomness_collective_flip/struct.Module.html#method.random) +The Randomness Collective Flip module provides a [`random`](https://docs.rs/pallet-insecure-randomness-collective-flip/latest/pallet_insecure_randomness_collective_flip/struct.Module.html#method.random) function that generates low-influence random values based on the block hashes from the previous `81` blocks. Low-influence randomness can be useful when defending against relatively weak adversaries. Using this pallet as a randomness source is advisable primarily in low-security @@ -8,7 +12,7 @@ situations like testing. ## Public Functions -See the [`Module`](https://docs.rs/pallet-randomness-collective-flip/latest/pallet_randomness_collective_flip/struct.Module.html) struct for details of publicly available functions. +See the [`Module`](https://docs.rs/pallet-insecure-randomness-collective-flip/latest/pallet_insecure_randomness_collective_flip/struct.Module.html) struct for details of publicly available functions. ## Usage @@ -32,17 +36,17 @@ pub mod pallet { pub struct Pallet(_); #[pallet::config] - pub trait Config: frame_system::Config + pallet_randomness_collective_flip::Config {} + pub trait Config: frame_system::Config + pallet_insecure_randomness_collective_flip::Config {} #[pallet::call] impl Pallet { #[pallet::weight(0)] pub fn random_module_example(origin: OriginFor) -> DispatchResult { - let _random_value = >::random(&b"my context"[..]); + let _random_value = >::random(&b"my context"[..]); Ok(()) } } } ``` -License: Apache-2.0 \ No newline at end of file +License: Apache-2.0 diff --git a/frame/randomness-collective-flip/src/lib.rs b/frame/insecure-randomness-collective-flip/src/lib.rs similarity index 86% rename from frame/randomness-collective-flip/src/lib.rs rename to frame/insecure-randomness-collective-flip/src/lib.rs index d92b053655dc..474087777c46 100644 --- a/frame/randomness-collective-flip/src/lib.rs +++ b/frame/insecure-randomness-collective-flip/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,6 +15,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! # DO NOT USE IN PRODUCTION +//! +//! The produced values do not fulfill the cryptographic requirements for random numbers. +//! Should not be used for high-stake production use-cases. +//! //! # Randomness Pallet //! //! The Randomness Collective Flip pallet provides a [`random`](./struct.Module.html#method.random) @@ -46,17 +51,16 @@ //! use frame_system::pallet_prelude::*; //! //! #[pallet::pallet] -//! #[pallet::generate_store(pub(super) trait Store)] //! pub struct Pallet(_); //! //! #[pallet::config] -//! pub trait Config: frame_system::Config + pallet_randomness_collective_flip::Config {} +//! pub trait Config: frame_system::Config + pallet_insecure_randomness_collective_flip::Config {} //! //! #[pallet::call] //! impl Pallet { //! #[pallet::weight(0)] //! pub fn random_module_example(origin: OriginFor) -> DispatchResult { -//! let _random_value = >::random(&b"my context"[..]); +//! let _random_value = >::random(&b"my context"[..]); //! Ok(()) //! } //! } @@ -70,11 +74,12 @@ use safe_mix::TripletMix; use codec::Encode; use frame_support::{pallet_prelude::Weight, traits::Randomness}; +use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::traits::{Hash, Saturating}; const RANDOM_MATERIAL_LEN: u32 = 81; -fn block_number_to_index(block_number: T::BlockNumber) -> usize { +fn block_number_to_index(block_number: BlockNumberFor) -> usize { // on_initialize is called on the first block after genesis let index = (block_number - 1u32.into()) % RANDOM_MATERIAL_LEN.into(); index.try_into().ok().expect("Something % 81 is always smaller than usize; qed") @@ -86,10 +91,8 @@ pub use pallet::*; pub mod pallet { use super::*; use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::config] @@ -97,7 +100,7 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { - fn on_initialize(block_number: T::BlockNumber) -> Weight { + fn on_initialize(block_number: BlockNumberFor) -> Weight { let parent_hash = >::parent_hash(); >::mutate(|ref mut values| { @@ -120,7 +123,7 @@ pub mod pallet { StorageValue<_, BoundedVec>, ValueQuery>; } -impl Randomness for Pallet { +impl Randomness> for Pallet { /// This randomness uses a low-influence function, drawing upon the block hashes from the /// previous 81 blocks. Its result for any given subject will be known far in advance by anyone /// observing the chain. Any block producer has significant influence over their block hashes @@ -131,7 +134,7 @@ impl Randomness for Pallet { /// WARNING: Hashing the result of this function will remove any low-influence properties it has /// and mean that all bits of the resulting value are entirely manipulatable by the author of /// the parent block, who can determine the value of `parent_hash`. - fn random(subject: &[u8]) -> (T::Hash, T::BlockNumber) { + fn random(subject: &[u8]) -> (T::Hash, BlockNumberFor) { let block_number = >::block_number(); let index = block_number_to_index::(block_number); @@ -157,12 +160,12 @@ impl Randomness for Pallet { #[cfg(test)] mod tests { use super::*; - use crate as pallet_randomness_collective_flip; + use crate as pallet_insecure_randomness_collective_flip; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, Header as _, IdentityLookup}, + BuildStorage, }; use frame_support::{ @@ -171,17 +174,13 @@ mod tests { }; use frame_system::limits; - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - CollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, + CollectiveFlip: pallet_insecure_randomness_collective_flip::{Pallet, Storage}, } ); @@ -196,14 +195,13 @@ mod tests { type BlockLength = BlockLength; type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -217,10 +215,10 @@ mod tests { type MaxConsumers = ConstU32<16>; } - impl pallet_randomness_collective_flip::Config for Test {} + impl pallet_insecure_randomness_collective_flip::Config for Test {} fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); t.into() } diff --git a/frame/lottery/Cargo.toml b/frame/lottery/Cargo.toml index 8c1bf3010685..a38d1ac2a529 100644 --- a/frame/lottery/Cargo.toml +++ b/frame/lottery/Cargo.toml @@ -12,21 +12,21 @@ description = "FRAME Participation Lottery Pallet" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] frame-support-test = { version = "3.0.0", path = "../support/test" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/lottery/src/benchmarking.rs b/frame/lottery/src/benchmarking.rs index fba722a07fab..1510d250dbea 100644 --- a/frame/lottery/src/benchmarking.rs +++ b/frame/lottery/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,12 @@ use super::*; -use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use crate::Pallet as Lottery; +use frame_benchmarking::{ + impl_benchmark_test_suite, + v1::{account, whitelisted_caller, BenchmarkError}, + v2::*, +}; use frame_support::{ storage::bounded_vec::BoundedVec, traits::{EnsureOrigin, OnInitialize}, @@ -29,8 +34,6 @@ use frame_support::{ use frame_system::RawOrigin; use sp_runtime::traits::{Bounded, Zero}; -use crate::Pallet as Lottery; - // Set up and start a lottery fn setup_lottery(repeat: bool) -> Result<(), &'static str> { let price = T::Currency::minimum_balance(); @@ -43,75 +46,107 @@ fn setup_lottery(repeat: bool) -> Result<(), &'static str> { ]; // Last call will be the match for worst case scenario. calls.push(frame_system::Call::::remark { remark: vec![] }.into()); - let origin = T::ManagerOrigin::successful_origin(); + let origin = T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"); Lottery::::set_calls(origin.clone(), calls)?; Lottery::::start_lottery(origin, price, length, delay, repeat)?; Ok(()) } -benchmarks! { - buy_ticket { +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn buy_ticket() -> Result<(), BenchmarkError> { let caller = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); setup_lottery::(false)?; // force user to have a long vec of calls participating let set_code_index: CallIndex = Lottery::::call_to_index( - &frame_system::Call::::set_code{ code: vec![] }.into() + &frame_system::Call::::set_code { code: vec![] }.into(), )?; let already_called: (u32, BoundedVec) = ( LotteryIndex::::get(), BoundedVec::::try_from(vec![ set_code_index; - T::MaxCalls::get().saturating_sub(1) as usize - ]).unwrap(), + T::MaxCalls::get().saturating_sub(1) + as usize + ]) + .unwrap(), ); Participants::::insert(&caller, already_called); let call = frame_system::Call::::remark { remark: vec![] }; - }: _(RawOrigin::Signed(caller), Box::new(call.into())) - verify { + + #[extrinsic_call] + _(RawOrigin::Signed(caller), Box::new(call.into())); + assert_eq!(TicketsCount::::get(), 1); + + Ok(()) } - set_calls { - let n in 0 .. T::MaxCalls::get() as u32; + #[benchmark] + fn set_calls(n: Linear<0, { T::MaxCalls::get() }>) -> Result<(), BenchmarkError> { let calls = vec![frame_system::Call::::remark { remark: vec![] }.into(); n as usize]; - let origin = T::ManagerOrigin::successful_origin(); + let origin = + T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; assert!(CallIndices::::get().is_empty()); - }: _(origin, calls) - verify { + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, calls); + if !n.is_zero() { assert!(!CallIndices::::get().is_empty()); } + + Ok(()) } - start_lottery { + #[benchmark] + fn start_lottery() -> Result<(), BenchmarkError> { let price = BalanceOf::::max_value(); let end = 10u32.into(); let payout = 5u32.into(); - let origin = T::ManagerOrigin::successful_origin(); - }: _(origin, price, end, payout, true) - verify { + let origin = + T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, price, end, payout, true); + assert!(crate::Lottery::::get().is_some()); + + Ok(()) } - stop_repeat { + #[benchmark] + fn stop_repeat() -> Result<(), BenchmarkError> { setup_lottery::(true)?; assert_eq!(crate::Lottery::::get().unwrap().repeat, true); - let origin = T::ManagerOrigin::successful_origin(); - }: _(origin) - verify { + let origin = + T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin); + assert_eq!(crate::Lottery::::get().unwrap().repeat, false); + + Ok(()) } - on_initialize_end { + #[benchmark] + fn on_initialize_end() -> Result<(), BenchmarkError> { setup_lottery::(false)?; let winner = account("winner", 0, 0); // User needs more than min balance to get ticket T::Currency::make_free_balance_be(&winner, T::Currency::minimum_balance() * 10u32.into()); // Make sure lottery account has at least min balance too let lottery_account = Lottery::::account_id(); - T::Currency::make_free_balance_be(&lottery_account, T::Currency::minimum_balance() * 10u32.into()); + T::Currency::make_free_balance_be( + &lottery_account, + T::Currency::minimum_balance() * 10u32.into(), + ); // Buy a ticket let call = frame_system::Call::::remark { remark: vec![] }; Lottery::::buy_ticket(RawOrigin::Signed(winner.clone()).into(), Box::new(call.into()))?; @@ -120,29 +155,37 @@ benchmarks! { // Assert that lotto is set up for winner assert_eq!(TicketsCount::::get(), 1); assert!(!Lottery::::pot().1.is_zero()); - }: { - // Generate `MaxGenerateRandom` numbers for worst case scenario - for i in 0 .. T::MaxGenerateRandom::get() { - Lottery::::generate_random_number(i); + + #[block] + { + // Generate `MaxGenerateRandom` numbers for worst case scenario + for i in 0..T::MaxGenerateRandom::get() { + Lottery::::generate_random_number(i); + } + // Start lottery has block 15 configured for payout + Lottery::::on_initialize(15u32.into()); } - // Start lottery has block 15 configured for payout - Lottery::::on_initialize(15u32.into()); - } - verify { + assert!(crate::Lottery::::get().is_none()); assert_eq!(TicketsCount::::get(), 0); assert_eq!(Lottery::::pot().1, 0u32.into()); - assert!(!T::Currency::free_balance(&winner).is_zero()) + assert!(!T::Currency::free_balance(&winner).is_zero()); + + Ok(()) } - on_initialize_repeat { + #[benchmark] + fn on_initialize_repeat() -> Result<(), BenchmarkError> { setup_lottery::(true)?; let winner = account("winner", 0, 0); // User needs more than min balance to get ticket T::Currency::make_free_balance_be(&winner, T::Currency::minimum_balance() * 10u32.into()); // Make sure lottery account has at least min balance too let lottery_account = Lottery::::account_id(); - T::Currency::make_free_balance_be(&lottery_account, T::Currency::minimum_balance() * 10u32.into()); + T::Currency::make_free_balance_be( + &lottery_account, + T::Currency::minimum_balance() * 10u32.into(), + ); // Buy a ticket let call = frame_system::Call::::remark { remark: vec![] }; Lottery::::buy_ticket(RawOrigin::Signed(winner.clone()).into(), Box::new(call.into()))?; @@ -151,20 +194,24 @@ benchmarks! { // Assert that lotto is set up for winner assert_eq!(TicketsCount::::get(), 1); assert!(!Lottery::::pot().1.is_zero()); - }: { - // Generate `MaxGenerateRandom` numbers for worst case scenario - for i in 0 .. T::MaxGenerateRandom::get() { - Lottery::::generate_random_number(i); + + #[block] + { + // Generate `MaxGenerateRandom` numbers for worst case scenario + for i in 0..T::MaxGenerateRandom::get() { + Lottery::::generate_random_number(i); + } + // Start lottery has block 15 configured for payout + Lottery::::on_initialize(15u32.into()); } - // Start lottery has block 15 configured for payout - Lottery::::on_initialize(15u32.into()); - } - verify { + assert!(crate::Lottery::::get().is_some()); assert_eq!(LotteryIndex::::get(), 2); assert_eq!(TicketsCount::::get(), 0); assert_eq!(Lottery::::pot().1, 0u32.into()); - assert!(!T::Currency::free_balance(&winner).is_zero()) + assert!(!T::Currency::free_balance(&winner).is_zero()); + + Ok(()) } impl_benchmark_test_suite!(Lottery, crate::mock::new_test_ext(), crate::mock::Test); diff --git a/frame/lottery/src/lib.rs b/frame/lottery/src/lib.rs index 3255062e3fe7..a6a94b4ab19a 100644 --- a/frame/lottery/src/lib.rs +++ b/frame/lottery/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -123,7 +123,6 @@ pub mod pallet { use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); /// The pallet's config trait. @@ -143,7 +142,7 @@ pub mod pallet { type Currency: ReservableCurrency; /// Something that provides randomness in the runtime. - type Randomness: Randomness; + type Randomness: Randomness>; /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -209,7 +208,7 @@ pub mod pallet { /// The configuration for the current lottery. #[pallet::storage] pub(crate) type Lottery = - StorageValue<_, LotteryConfig>>; + StorageValue<_, LotteryConfig, BalanceOf>>; /// Users who have purchased a ticket. (Lottery Index, Tickets Purchased) #[pallet::storage] @@ -240,7 +239,7 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { - fn on_initialize(n: T::BlockNumber) -> Weight { + fn on_initialize(n: BlockNumberFor) -> Weight { Lottery::::mutate(|mut lottery| -> Weight { if let Some(config) = &mut lottery { let payout_block = @@ -351,8 +350,8 @@ pub mod pallet { pub fn start_lottery( origin: OriginFor, price: BalanceOf, - length: T::BlockNumber, - delay: T::BlockNumber, + length: BlockNumberFor, + delay: BlockNumberFor, repeat: bool, ) -> DispatchResult { T::ManagerOrigin::ensure_origin(origin)?; diff --git a/frame/lottery/src/mock.rs b/frame/lottery/src/mock.rs index 1977da5959d3..aefb6a1cce2b 100644 --- a/frame/lottery/src/mock.rs +++ b/frame/lottery/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,21 +28,16 @@ use frame_support_test::TestRandomness; use frame_system::EnsureRoot; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, - Perbill, + BuildStorage, Perbill, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Lottery: pallet_lottery::{Pallet, Call, Storage, Event}, } @@ -58,14 +53,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; - type BlockNumber = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -89,6 +83,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } parameter_types! { @@ -112,7 +110,7 @@ pub type SystemCall = frame_system::Call; pub type BalancesCall = pallet_balances::Call; pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)], } diff --git a/frame/lottery/src/tests.rs b/frame/lottery/src/tests.rs index 0eaf08056400..ae3a6c858f24 100644 --- a/frame/lottery/src/tests.rs +++ b/frame/lottery/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,8 +23,7 @@ use mock::{ new_test_ext, run_to_block, Balances, BalancesCall, Lottery, RuntimeCall, RuntimeOrigin, SystemCall, Test, }; -use pallet_balances::Error as BalancesError; -use sp_runtime::traits::BadOrigin; +use sp_runtime::{traits::BadOrigin, TokenError}; #[test] fn initial_state() { @@ -45,7 +44,7 @@ fn basic_end_to_end_works() { let delay = 5; let calls = vec![ RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }), - RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }), ]; // Set calls for the lottery @@ -56,7 +55,10 @@ fn basic_end_to_end_works() { assert!(crate::Lottery::::get().is_some()); assert_eq!(Balances::free_balance(&1), 100); - let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 20 })); + let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death { + dest: 2, + value: 20, + })); assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call.clone())); // 20 from the transfer, 10 from buying a ticket assert_eq!(Balances::free_balance(&1), 100 - 20 - 10); @@ -129,7 +131,7 @@ fn set_calls_works() { let calls = vec![ RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }), - RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }), ]; assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls)); @@ -137,7 +139,7 @@ fn set_calls_works() { let too_many_calls = vec![ RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }), - RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }), RuntimeCall::System(SystemCall::remark { remark: vec![] }), ]; @@ -157,7 +159,7 @@ fn call_to_indices_works() { new_test_ext().execute_with(|| { let calls = vec![ RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }), - RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }), ]; let indices = Lottery::calls_to_indices(&calls).unwrap().into_inner(); // Only comparing the length since it is otherwise dependant on the API @@ -166,7 +168,7 @@ fn call_to_indices_works() { let too_many_calls = vec![ RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }), - RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }), RuntimeCall::System(SystemCall::remark { remark: vec![] }), ]; assert_noop!(Lottery::calls_to_indices(&too_many_calls), Error::::TooManyCalls); @@ -203,7 +205,10 @@ fn buy_ticket_works_as_simple_passthrough() { // as a simple passthrough to the real call. new_test_ext().execute_with(|| { // No lottery set up - let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 20 })); + let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death { + dest: 2, + value: 20, + })); // This is just a basic transfer then assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call.clone())); assert_eq!(Balances::free_balance(&1), 100 - 20); @@ -212,7 +217,7 @@ fn buy_ticket_works_as_simple_passthrough() { // Lottery is set up, but too expensive to enter, so `do_buy_ticket` fails. let calls = vec![ RuntimeCall::Balances(BalancesCall::force_transfer { source: 0, dest: 0, value: 0 }), - RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }), ]; assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls)); @@ -223,11 +228,13 @@ fn buy_ticket_works_as_simple_passthrough() { assert_eq!(TicketsCount::::get(), 0); // If call would fail, the whole thing still fails the same - let fail_call = - Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 1000 })); + let fail_call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death { + dest: 2, + value: 1000, + })); assert_noop!( Lottery::buy_ticket(RuntimeOrigin::signed(1), fail_call), - BalancesError::::InsufficientBalance, + ArithmeticError::Underflow, ); let bad_origin_call = Box::new(RuntimeCall::Balances(BalancesCall::force_transfer { @@ -243,8 +250,10 @@ fn buy_ticket_works_as_simple_passthrough() { assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), remark_call)); assert_eq!(TicketsCount::::get(), 0); - let successful_call = - Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 1 })); + let successful_call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death { + dest: 2, + value: 1, + })); assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(2), successful_call)); assert_eq!(TicketsCount::::get(), 1); }); @@ -256,12 +265,15 @@ fn buy_ticket_works() { // Set calls for the lottery. let calls = vec![ RuntimeCall::System(SystemCall::remark { remark: vec![] }), - RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 }), + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 }), ]; assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls)); // Can't buy ticket before start - let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 1 })); + let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death { + dest: 2, + value: 1, + })); assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call.clone())); assert_eq!(TicketsCount::::get(), 0); @@ -274,7 +286,10 @@ fn buy_ticket_works() { assert_eq!(TicketsCount::::get(), 1); // Can't buy another of the same ticket (even if call is slightly changed) - let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 3, value: 30 })); + let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death { + dest: 3, + value: 30, + })); assert_ok!(Lottery::buy_ticket(RuntimeOrigin::signed(1), call)); assert_eq!(TicketsCount::::get(), 1); @@ -302,7 +317,8 @@ fn buy_ticket_works() { #[test] fn do_buy_ticket_already_participating() { new_test_ext().execute_with(|| { - let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })]; + let calls = + vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })]; assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone())); assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 1, 10, 10, false)); @@ -317,7 +333,8 @@ fn do_buy_ticket_already_participating() { #[test] fn buy_ticket_already_participating() { new_test_ext().execute_with(|| { - let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })]; + let calls = + vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })]; assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone())); assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 1, 10, 10, false)); @@ -337,7 +354,8 @@ fn buy_ticket_already_participating() { #[test] fn buy_ticket_insufficient_balance() { new_test_ext().execute_with(|| { - let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })]; + let calls = + vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })]; assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone())); // Price set to 100. assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 100, 10, 10, false)); @@ -352,16 +370,14 @@ fn buy_ticket_insufficient_balance() { #[test] fn do_buy_ticket_insufficient_balance() { new_test_ext().execute_with(|| { - let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })]; + let calls = + vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })]; assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone())); // Price set to 101. assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 101, 10, 10, false)); // Buying fails with InsufficientBalance. - assert_noop!( - Lottery::do_buy_ticket(&1, &calls[0]), - BalancesError::::InsufficientBalance - ); + assert_noop!(Lottery::do_buy_ticket(&1, &calls[0]), TokenError::FundsUnavailable,); assert!(TicketsCount::::get().is_zero()); }); } @@ -369,13 +385,13 @@ fn do_buy_ticket_insufficient_balance() { #[test] fn do_buy_ticket_keep_alive() { new_test_ext().execute_with(|| { - let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })]; + let calls = + vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })]; assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone())); // Price set to 100. assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 100, 10, 10, false)); - // Buying fails with KeepAlive. - assert_noop!(Lottery::do_buy_ticket(&1, &calls[0]), BalancesError::::KeepAlive); + assert_noop!(Lottery::do_buy_ticket(&1, &calls[0]), TokenError::NotExpendable); assert!(TicketsCount::::get().is_zero()); }); } @@ -421,7 +437,8 @@ fn choose_ticket_trivial_cases() { #[test] fn choose_account_one_participant() { new_test_ext().execute_with(|| { - let calls = vec![RuntimeCall::Balances(BalancesCall::transfer { dest: 0, value: 0 })]; + let calls = + vec![RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 0, value: 0 })]; assert_ok!(Lottery::set_calls(RuntimeOrigin::root(), calls.clone())); assert_ok!(Lottery::start_lottery(RuntimeOrigin::root(), 10, 10, 10, false)); let call = Box::new(calls[0].clone()); diff --git a/frame/lottery/src/weights.rs b/frame/lottery/src/weights.rs index e9ee528cc43b..3b4e56237534 100644 --- a/frame/lottery/src/weights.rs +++ b/frame/lottery/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,33 +18,37 @@ //! Autogenerated weights for pallet_lottery //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-12-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// /home/benchbot/cargo_target_dir/production/substrate +// ./target/production/substrate // benchmark // pallet +// --chain=dev // --steps=50 // --repeat=20 +// --pallet=pallet_lottery +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json -// --pallet=pallet_lottery -// --chain=dev -// --header=./HEADER-APACHE2 // --output=./frame/lottery/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_lottery. pub trait WeightInfo { @@ -59,130 +63,212 @@ pub trait WeightInfo { /// Weights for pallet_lottery using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Lottery Lottery (r:1 w:0) - // Storage: Lottery CallIndices (r:1 w:0) - // Storage: Lottery TicketsCount (r:1 w:1) - // Storage: Lottery Participants (r:1 w:1) - // Storage: Lottery LotteryIndex (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Lottery Tickets (r:0 w:1) + /// Storage: Lottery Lottery (r:1 w:0) + /// Proof: Lottery Lottery (max_values: Some(1), max_size: Some(29), added: 524, mode: MaxEncodedLen) + /// Storage: Lottery CallIndices (r:1 w:0) + /// Proof: Lottery CallIndices (max_values: Some(1), max_size: Some(21), added: 516, mode: MaxEncodedLen) + /// Storage: Lottery TicketsCount (r:1 w:1) + /// Proof: Lottery TicketsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Lottery Participants (r:1 w:1) + /// Proof: Lottery Participants (max_values: None, max_size: Some(65), added: 2540, mode: MaxEncodedLen) + /// Storage: Lottery LotteryIndex (r:1 w:0) + /// Proof: Lottery LotteryIndex (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Lottery Tickets (r:0 w:1) + /// Proof: Lottery Tickets (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) fn buy_ticket() -> Weight { - // Minimum execution time: 52_479 nanoseconds. - Weight::from_ref_time(53_225_000) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) + // Proof Size summary in bytes: + // Measured: `452` + // Estimated: `3593` + // Minimum execution time: 60_298_000 picoseconds. + Weight::from_parts(62_058_000, 3593) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } - // Storage: Lottery CallIndices (r:0 w:1) + /// Storage: Lottery CallIndices (r:0 w:1) + /// Proof: Lottery CallIndices (max_values: Some(1), max_size: Some(21), added: 516, mode: MaxEncodedLen) /// The range of component `n` is `[0, 10]`. fn set_calls(n: u32, ) -> Weight { - // Minimum execution time: 14_433 nanoseconds. - Weight::from_ref_time(15_660_780) - // Standard Error: 5_894 - .saturating_add(Weight::from_ref_time(290_482).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_291_000 picoseconds. + Weight::from_parts(8_178_186, 0) + // Standard Error: 3_048 + .saturating_add(Weight::from_parts(330_871, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Lottery Lottery (r:1 w:1) - // Storage: Lottery LotteryIndex (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Lottery Lottery (r:1 w:1) + /// Proof: Lottery Lottery (max_values: Some(1), max_size: Some(29), added: 524, mode: MaxEncodedLen) + /// Storage: Lottery LotteryIndex (r:1 w:1) + /// Proof: Lottery LotteryIndex (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn start_lottery() -> Weight { - // Minimum execution time: 43_683 nanoseconds. - Weight::from_ref_time(44_580_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `161` + // Estimated: `3593` + // Minimum execution time: 36_741_000 picoseconds. + Weight::from_parts(38_288_000, 3593) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - // Storage: Lottery Lottery (r:1 w:1) + /// Storage: Lottery Lottery (r:1 w:1) + /// Proof: Lottery Lottery (max_values: Some(1), max_size: Some(29), added: 524, mode: MaxEncodedLen) fn stop_repeat() -> Weight { - // Minimum execution time: 10_514 nanoseconds. - Weight::from_ref_time(10_821_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `219` + // Estimated: `1514` + // Minimum execution time: 7_270_000 picoseconds. + Weight::from_parts(7_578_000, 1514) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) - // Storage: Lottery Lottery (r:1 w:1) - // Storage: System Account (r:2 w:2) - // Storage: Lottery TicketsCount (r:1 w:1) - // Storage: Lottery Tickets (r:1 w:0) + /// Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) + /// Proof: RandomnessCollectiveFlip RandomMaterial (max_values: Some(1), max_size: Some(2594), added: 3089, mode: MaxEncodedLen) + /// Storage: Lottery Lottery (r:1 w:1) + /// Proof: Lottery Lottery (max_values: Some(1), max_size: Some(29), added: 524, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Lottery TicketsCount (r:1 w:1) + /// Proof: Lottery TicketsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Lottery Tickets (r:1 w:0) + /// Proof: Lottery Tickets (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) fn on_initialize_end() -> Weight { - // Minimum execution time: 60_254 nanoseconds. - Weight::from_ref_time(61_924_000) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(4)) + // Proof Size summary in bytes: + // Measured: `558` + // Estimated: `6196` + // Minimum execution time: 76_611_000 picoseconds. + Weight::from_parts(78_107_000, 6196) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } - // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) - // Storage: Lottery Lottery (r:1 w:1) - // Storage: System Account (r:2 w:2) - // Storage: Lottery TicketsCount (r:1 w:1) - // Storage: Lottery Tickets (r:1 w:0) - // Storage: Lottery LotteryIndex (r:1 w:1) + /// Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) + /// Proof: RandomnessCollectiveFlip RandomMaterial (max_values: Some(1), max_size: Some(2594), added: 3089, mode: MaxEncodedLen) + /// Storage: Lottery Lottery (r:1 w:1) + /// Proof: Lottery Lottery (max_values: Some(1), max_size: Some(29), added: 524, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Lottery TicketsCount (r:1 w:1) + /// Proof: Lottery TicketsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Lottery Tickets (r:1 w:0) + /// Proof: Lottery Tickets (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) + /// Storage: Lottery LotteryIndex (r:1 w:1) + /// Proof: Lottery LotteryIndex (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn on_initialize_repeat() -> Weight { - // Minimum execution time: 61_552 nanoseconds. - Weight::from_ref_time(62_152_000) - .saturating_add(T::DbWeight::get().reads(7)) - .saturating_add(T::DbWeight::get().writes(5)) + // Proof Size summary in bytes: + // Measured: `558` + // Estimated: `6196` + // Minimum execution time: 78_731_000 picoseconds. + Weight::from_parts(80_248_000, 6196) + .saturating_add(T::DbWeight::get().reads(7_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Lottery Lottery (r:1 w:0) - // Storage: Lottery CallIndices (r:1 w:0) - // Storage: Lottery TicketsCount (r:1 w:1) - // Storage: Lottery Participants (r:1 w:1) - // Storage: Lottery LotteryIndex (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Lottery Tickets (r:0 w:1) + /// Storage: Lottery Lottery (r:1 w:0) + /// Proof: Lottery Lottery (max_values: Some(1), max_size: Some(29), added: 524, mode: MaxEncodedLen) + /// Storage: Lottery CallIndices (r:1 w:0) + /// Proof: Lottery CallIndices (max_values: Some(1), max_size: Some(21), added: 516, mode: MaxEncodedLen) + /// Storage: Lottery TicketsCount (r:1 w:1) + /// Proof: Lottery TicketsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Lottery Participants (r:1 w:1) + /// Proof: Lottery Participants (max_values: None, max_size: Some(65), added: 2540, mode: MaxEncodedLen) + /// Storage: Lottery LotteryIndex (r:1 w:0) + /// Proof: Lottery LotteryIndex (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Lottery Tickets (r:0 w:1) + /// Proof: Lottery Tickets (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) fn buy_ticket() -> Weight { - // Minimum execution time: 52_479 nanoseconds. - Weight::from_ref_time(53_225_000) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(4)) + // Proof Size summary in bytes: + // Measured: `452` + // Estimated: `3593` + // Minimum execution time: 60_298_000 picoseconds. + Weight::from_parts(62_058_000, 3593) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } - // Storage: Lottery CallIndices (r:0 w:1) + /// Storage: Lottery CallIndices (r:0 w:1) + /// Proof: Lottery CallIndices (max_values: Some(1), max_size: Some(21), added: 516, mode: MaxEncodedLen) /// The range of component `n` is `[0, 10]`. fn set_calls(n: u32, ) -> Weight { - // Minimum execution time: 14_433 nanoseconds. - Weight::from_ref_time(15_660_780) - // Standard Error: 5_894 - .saturating_add(Weight::from_ref_time(290_482).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_291_000 picoseconds. + Weight::from_parts(8_178_186, 0) + // Standard Error: 3_048 + .saturating_add(Weight::from_parts(330_871, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Lottery Lottery (r:1 w:1) - // Storage: Lottery LotteryIndex (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Lottery Lottery (r:1 w:1) + /// Proof: Lottery Lottery (max_values: Some(1), max_size: Some(29), added: 524, mode: MaxEncodedLen) + /// Storage: Lottery LotteryIndex (r:1 w:1) + /// Proof: Lottery LotteryIndex (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn start_lottery() -> Weight { - // Minimum execution time: 43_683 nanoseconds. - Weight::from_ref_time(44_580_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `161` + // Estimated: `3593` + // Minimum execution time: 36_741_000 picoseconds. + Weight::from_parts(38_288_000, 3593) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } - // Storage: Lottery Lottery (r:1 w:1) + /// Storage: Lottery Lottery (r:1 w:1) + /// Proof: Lottery Lottery (max_values: Some(1), max_size: Some(29), added: 524, mode: MaxEncodedLen) fn stop_repeat() -> Weight { - // Minimum execution time: 10_514 nanoseconds. - Weight::from_ref_time(10_821_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `219` + // Estimated: `1514` + // Minimum execution time: 7_270_000 picoseconds. + Weight::from_parts(7_578_000, 1514) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) - // Storage: Lottery Lottery (r:1 w:1) - // Storage: System Account (r:2 w:2) - // Storage: Lottery TicketsCount (r:1 w:1) - // Storage: Lottery Tickets (r:1 w:0) + /// Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) + /// Proof: RandomnessCollectiveFlip RandomMaterial (max_values: Some(1), max_size: Some(2594), added: 3089, mode: MaxEncodedLen) + /// Storage: Lottery Lottery (r:1 w:1) + /// Proof: Lottery Lottery (max_values: Some(1), max_size: Some(29), added: 524, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Lottery TicketsCount (r:1 w:1) + /// Proof: Lottery TicketsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Lottery Tickets (r:1 w:0) + /// Proof: Lottery Tickets (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) fn on_initialize_end() -> Weight { - // Minimum execution time: 60_254 nanoseconds. - Weight::from_ref_time(61_924_000) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(4)) + // Proof Size summary in bytes: + // Measured: `558` + // Estimated: `6196` + // Minimum execution time: 76_611_000 picoseconds. + Weight::from_parts(78_107_000, 6196) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } - // Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) - // Storage: Lottery Lottery (r:1 w:1) - // Storage: System Account (r:2 w:2) - // Storage: Lottery TicketsCount (r:1 w:1) - // Storage: Lottery Tickets (r:1 w:0) - // Storage: Lottery LotteryIndex (r:1 w:1) + /// Storage: RandomnessCollectiveFlip RandomMaterial (r:1 w:0) + /// Proof: RandomnessCollectiveFlip RandomMaterial (max_values: Some(1), max_size: Some(2594), added: 3089, mode: MaxEncodedLen) + /// Storage: Lottery Lottery (r:1 w:1) + /// Proof: Lottery Lottery (max_values: Some(1), max_size: Some(29), added: 524, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Lottery TicketsCount (r:1 w:1) + /// Proof: Lottery TicketsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Lottery Tickets (r:1 w:0) + /// Proof: Lottery Tickets (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) + /// Storage: Lottery LotteryIndex (r:1 w:1) + /// Proof: Lottery LotteryIndex (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn on_initialize_repeat() -> Weight { - // Minimum execution time: 61_552 nanoseconds. - Weight::from_ref_time(62_152_000) - .saturating_add(RocksDbWeight::get().reads(7)) - .saturating_add(RocksDbWeight::get().writes(5)) + // Proof Size summary in bytes: + // Measured: `558` + // Estimated: `6196` + // Minimum execution time: 78_731_000 picoseconds. + Weight::from_parts(80_248_000, 6196) + .saturating_add(RocksDbWeight::get().reads(7_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } } diff --git a/frame/membership/Cargo.toml b/frame/membership/Cargo.toml index bba5700cdd84..afef5bc4b535 100644 --- a/frame/membership/Cargo.toml +++ b/frame/membership/Cargo.toml @@ -13,16 +13,16 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive", "serde"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core", features = ["serde"] } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime", features = ["serde"] } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [features] default = ["std"] diff --git a/frame/membership/src/lib.rs b/frame/membership/src/lib.rs index 934abb996df8..24b9fb5fe906 100644 --- a/frame/membership/src/lib.rs +++ b/frame/membership/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,7 +50,6 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData<(T, I)>); @@ -106,20 +105,15 @@ pub mod pallet { pub type Prime, I: 'static = ()> = StorageValue<_, T::AccountId, OptionQuery>; #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig, I: 'static = ()> { pub members: BoundedVec, + #[serde(skip)] pub phantom: PhantomData, } - #[cfg(feature = "std")] - impl, I: 'static> Default for GenesisConfig { - fn default() -> Self { - Self { members: Default::default(), phantom: Default::default() } - } - } - #[pallet::genesis_build] - impl, I: 'static> GenesisBuild for GenesisConfig { + impl, I: 'static> BuildGenesisConfig for GenesisConfig { fn build(&self) { use sp_std::collections::btree_set::BTreeSet; let members_set: BTreeSet<_> = self.members.iter().collect(); @@ -169,7 +163,7 @@ pub mod pallet { /// /// May only be called from `T::AddOrigin`. #[pallet::call_index(0)] - #[pallet::weight(50_000_000)] + #[pallet::weight({50_000_000})] pub fn add_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { T::AddOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(who)?; @@ -192,7 +186,7 @@ pub mod pallet { /// /// May only be called from `T::RemoveOrigin`. #[pallet::call_index(1)] - #[pallet::weight(50_000_000)] + #[pallet::weight({50_000_000})] pub fn remove_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { T::RemoveOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(who)?; @@ -216,7 +210,7 @@ pub mod pallet { /// /// Prime membership is *not* passed from `remove` to `add`, if extant. #[pallet::call_index(2)] - #[pallet::weight(50_000_000)] + #[pallet::weight({50_000_000})] pub fn swap_member( origin: OriginFor, remove: AccountIdLookupOf, @@ -250,7 +244,7 @@ pub mod pallet { /// /// May only be called from `T::ResetOrigin`. #[pallet::call_index(3)] - #[pallet::weight(50_000_000)] + #[pallet::weight({50_000_000})] pub fn reset_members(origin: OriginFor, members: Vec) -> DispatchResult { T::ResetOrigin::ensure_origin(origin)?; @@ -273,7 +267,7 @@ pub mod pallet { /// /// Prime membership is passed from the origin account to `new`, if extant. #[pallet::call_index(4)] - #[pallet::weight(50_000_000)] + #[pallet::weight({50_000_000})] pub fn change_key(origin: OriginFor, new: AccountIdLookupOf) -> DispatchResult { let remove = ensure_signed(origin)?; let new = T::Lookup::lookup(new)?; @@ -308,7 +302,7 @@ pub mod pallet { /// /// May only be called from `T::PrimeOrigin`. #[pallet::call_index(5)] - #[pallet::weight(50_000_000)] + #[pallet::weight({50_000_000})] pub fn set_prime(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { T::PrimeOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(who)?; @@ -322,7 +316,7 @@ pub mod pallet { /// /// May only be called from `T::PrimeOrigin`. #[pallet::call_index(6)] - #[pallet::weight(50_000_000)] + #[pallet::weight({50_000_000})] pub fn clear_prime(origin: OriginFor) -> DispatchResult { T::PrimeOrigin::ensure_origin(origin)?; Prime::::kill(); @@ -362,15 +356,17 @@ impl, I: 'static> SortedMembers for Pallet { #[cfg(feature = "runtime-benchmarks")] mod benchmark { use super::{Pallet as Membership, *}; - use frame_benchmarking::{account, benchmarks_instance_pallet, whitelist}; + use frame_benchmarking::v1::{account, benchmarks_instance_pallet, whitelist, BenchmarkError}; use frame_support::{assert_ok, traits::EnsureOrigin}; use frame_system::RawOrigin; const SEED: u32 = 0; fn set_members, I: 'static>(members: Vec, prime: Option) { - let reset_origin = T::ResetOrigin::successful_origin(); - let prime_origin = T::PrimeOrigin::successful_origin(); + let reset_origin = T::ResetOrigin::try_successful_origin() + .expect("ResetOrigin has no successful origin required for the benchmark"); + let prime_origin = T::PrimeOrigin::try_successful_origin() + .expect("PrimeOrigin has no successful origin required for the benchmark"); assert_ok!(>::reset_members(reset_origin, members.clone())); if let Some(prime) = prime.map(|i| members[i].clone()) { @@ -390,9 +386,11 @@ mod benchmark { let new_member = account::("add", m, SEED); let new_member_lookup = T::Lookup::unlookup(new_member.clone()); }: { - assert_ok!(>::add_member(T::AddOrigin::successful_origin(), new_member_lookup)); - } - verify { + assert_ok!(>::add_member( + T::AddOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, + new_member_lookup, + )); + } verify { assert!(>::get().contains(&new_member)); #[cfg(test)] crate::tests::clean(); } @@ -408,7 +406,10 @@ mod benchmark { let to_remove = members.first().cloned().unwrap(); let to_remove_lookup = T::Lookup::unlookup(to_remove.clone()); }: { - assert_ok!(>::remove_member(T::RemoveOrigin::successful_origin(), to_remove_lookup)); + assert_ok!(>::remove_member( + T::RemoveOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, + to_remove_lookup, + )); } verify { assert!(!>::get().contains(&to_remove)); // prime is rejigged @@ -428,7 +429,7 @@ mod benchmark { let remove_lookup = T::Lookup::unlookup(remove.clone()); }: { assert_ok!(>::swap_member( - T::SwapOrigin::successful_origin(), + T::SwapOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, remove_lookup, add_lookup, )); @@ -448,7 +449,10 @@ mod benchmark { set_members::(members.clone(), Some(members.len() - 1)); let mut new_members = (m..2*m).map(|i| account("member", i, SEED)).collect::>(); }: { - assert_ok!(>::reset_members(T::ResetOrigin::successful_origin(), new_members.clone())); + assert_ok!(>::reset_members( + T::ResetOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, + new_members.clone(), + )); } verify { new_members.sort(); assert_eq!(>::get(), new_members); @@ -485,7 +489,10 @@ mod benchmark { let prime_lookup = T::Lookup::unlookup(prime.clone()); set_members::(members, None); }: { - assert_ok!(>::set_prime(T::PrimeOrigin::successful_origin(), prime_lookup)); + assert_ok!(>::set_prime( + T::PrimeOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, + prime_lookup, + )); } verify { assert!(>::get().is_some()); assert!(::get_prime().is_some()); @@ -498,7 +505,9 @@ mod benchmark { let prime = members.last().cloned().unwrap(); set_members::(members, None); }: { - assert_ok!(>::clear_prime(T::PrimeOrigin::successful_origin())); + assert_ok!(>::clear_prime( + T::PrimeOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, + )); } verify { assert!(>::get().is_none()); assert!(::get_prime().is_none()); @@ -516,26 +525,22 @@ mod tests { use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BadOrigin, BlakeTwo256, IdentityLookup}, + BuildStorage, }; use frame_support::{ assert_noop, assert_ok, bounded_vec, ord_parameter_types, parameter_types, - traits::{ConstU32, ConstU64, GenesisBuild, StorageVersion}, + traits::{ConstU32, ConstU64, StorageVersion}, }; use frame_system::EnsureSignedBy; - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Membership: pallet_membership::{Pallet, Call, Storage, Config, Event}, } ); @@ -551,14 +556,13 @@ mod tests { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -621,7 +625,7 @@ mod tests { } pub(crate) fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); // We use default for brevity, but you can configure as desired if needed. pallet_membership::GenesisConfig:: { members: bounded_vec![10, 20, 30], @@ -634,7 +638,7 @@ mod tests { #[cfg(feature = "runtime-benchmarks")] pub(crate) fn new_bench_ext() -> sp_io::TestExternalities { - frame_system::GenesisConfig::default().build_storage::().unwrap().into() + frame_system::GenesisConfig::::default().build_storage().unwrap().into() } #[cfg(feature = "runtime-benchmarks")] diff --git a/frame/membership/src/migrations/mod.rs b/frame/membership/src/migrations/mod.rs index 235d0f1c7cf1..2487ed1d5da5 100644 --- a/frame/membership/src/migrations/mod.rs +++ b/frame/membership/src/migrations/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/membership/src/migrations/v4.rs b/frame/membership/src/migrations/v4.rs index cbc58cd6ae91..38e97af51a09 100644 --- a/frame/membership/src/migrations/v4.rs +++ b/frame/membership/src/migrations/v4.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/membership/src/weights.rs b/frame/membership/src/weights.rs index 11574bc8fa39..18ea7fcb315a 100644 --- a/frame/membership/src/weights.rs +++ b/frame/membership/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_membership //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_membership +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_membership. pub trait WeightInfo { @@ -59,190 +64,302 @@ pub trait WeightInfo { /// Weights for pallet_membership using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: TechnicalMembership Members (r:1 w:1) - // Storage: TechnicalCommittee Proposals (r:1 w:0) - // Storage: TechnicalCommittee Members (r:0 w:1) - // Storage: TechnicalCommittee Prime (r:0 w:1) + /// Storage: TechnicalMembership Members (r:1 w:1) + /// Proof: TechnicalMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Proposals (r:1 w:0) + /// Proof Skipped: TechnicalCommittee Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalCommittee Members (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalCommittee Prime (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 99]`. fn add_member(m: u32, ) -> Weight { - // Minimum execution time: 23_796 nanoseconds. - Weight::from_ref_time(24_829_996 as u64) - // Standard Error: 723 - .saturating_add(Weight::from_ref_time(48_467 as u64).saturating_mul(m as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `208 + m * (64 ±0)` + // Estimated: `4687 + m * (64 ±0)` + // Minimum execution time: 17_040_000 picoseconds. + Weight::from_parts(18_344_571, 4687) + // Standard Error: 847 + .saturating_add(Weight::from_parts(50_842, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) } - // Storage: TechnicalMembership Members (r:1 w:1) - // Storage: TechnicalCommittee Proposals (r:1 w:0) - // Storage: TechnicalMembership Prime (r:1 w:0) - // Storage: TechnicalCommittee Members (r:0 w:1) - // Storage: TechnicalCommittee Prime (r:0 w:1) + /// Storage: TechnicalMembership Members (r:1 w:1) + /// Proof: TechnicalMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Proposals (r:1 w:0) + /// Proof Skipped: TechnicalCommittee Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalMembership Prime (r:1 w:0) + /// Proof: TechnicalMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Members (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalCommittee Prime (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[2, 100]`. fn remove_member(m: u32, ) -> Weight { - // Minimum execution time: 27_255 nanoseconds. - Weight::from_ref_time(28_234_490 as u64) - // Standard Error: 833 - .saturating_add(Weight::from_ref_time(34_894 as u64).saturating_mul(m as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `312 + m * (64 ±0)` + // Estimated: `4687 + m * (64 ±0)` + // Minimum execution time: 20_088_000 picoseconds. + Weight::from_parts(21_271_384, 4687) + // Standard Error: 786 + .saturating_add(Weight::from_parts(44_806, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) } - // Storage: TechnicalMembership Members (r:1 w:1) - // Storage: TechnicalCommittee Proposals (r:1 w:0) - // Storage: TechnicalMembership Prime (r:1 w:0) - // Storage: TechnicalCommittee Members (r:0 w:1) - // Storage: TechnicalCommittee Prime (r:0 w:1) + /// Storage: TechnicalMembership Members (r:1 w:1) + /// Proof: TechnicalMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Proposals (r:1 w:0) + /// Proof Skipped: TechnicalCommittee Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalMembership Prime (r:1 w:0) + /// Proof: TechnicalMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Members (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalCommittee Prime (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[2, 100]`. fn swap_member(m: u32, ) -> Weight { - // Minimum execution time: 26_626 nanoseconds. - Weight::from_ref_time(27_989_042 as u64) - // Standard Error: 729 - .saturating_add(Weight::from_ref_time(51_567 as u64).saturating_mul(m as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `312 + m * (64 ±0)` + // Estimated: `4687 + m * (64 ±0)` + // Minimum execution time: 20_308_000 picoseconds. + Weight::from_parts(21_469_843, 4687) + // Standard Error: 782 + .saturating_add(Weight::from_parts(56_893, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) } - // Storage: TechnicalMembership Members (r:1 w:1) - // Storage: TechnicalCommittee Proposals (r:1 w:0) - // Storage: TechnicalMembership Prime (r:1 w:0) - // Storage: TechnicalCommittee Members (r:0 w:1) - // Storage: TechnicalCommittee Prime (r:0 w:1) + /// Storage: TechnicalMembership Members (r:1 w:1) + /// Proof: TechnicalMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Proposals (r:1 w:0) + /// Proof Skipped: TechnicalCommittee Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalMembership Prime (r:1 w:0) + /// Proof: TechnicalMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Members (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalCommittee Prime (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. fn reset_member(m: u32, ) -> Weight { - // Minimum execution time: 25_412 nanoseconds. - Weight::from_ref_time(27_713_414 as u64) - // Standard Error: 883 - .saturating_add(Weight::from_ref_time(157_085 as u64).saturating_mul(m as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `312 + m * (64 ±0)` + // Estimated: `4687 + m * (64 ±0)` + // Minimum execution time: 19_464_000 picoseconds. + Weight::from_parts(21_223_702, 4687) + // Standard Error: 1_068 + .saturating_add(Weight::from_parts(165_438, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) } - // Storage: TechnicalMembership Members (r:1 w:1) - // Storage: TechnicalCommittee Proposals (r:1 w:0) - // Storage: TechnicalMembership Prime (r:1 w:1) - // Storage: TechnicalCommittee Members (r:0 w:1) - // Storage: TechnicalCommittee Prime (r:0 w:1) + /// Storage: TechnicalMembership Members (r:1 w:1) + /// Proof: TechnicalMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Proposals (r:1 w:0) + /// Proof Skipped: TechnicalCommittee Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalMembership Prime (r:1 w:1) + /// Proof: TechnicalMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Members (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalCommittee Prime (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. fn change_key(m: u32, ) -> Weight { - // Minimum execution time: 27_122 nanoseconds. - Weight::from_ref_time(28_477_394 as u64) - // Standard Error: 801 - .saturating_add(Weight::from_ref_time(56_383 as u64).saturating_mul(m as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `312 + m * (64 ±0)` + // Estimated: `4687 + m * (64 ±0)` + // Minimum execution time: 20_965_000 picoseconds. + Weight::from_parts(22_551_007, 4687) + // Standard Error: 860 + .saturating_add(Weight::from_parts(52_397, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) } - // Storage: TechnicalMembership Members (r:1 w:0) - // Storage: TechnicalMembership Prime (r:0 w:1) - // Storage: TechnicalCommittee Prime (r:0 w:1) + /// Storage: TechnicalMembership Members (r:1 w:0) + /// Proof: TechnicalMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: TechnicalMembership Prime (r:0 w:1) + /// Proof: TechnicalMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Prime (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. fn set_prime(m: u32, ) -> Weight { - // Minimum execution time: 9_368 nanoseconds. - Weight::from_ref_time(10_133_132 as u64) - // Standard Error: 366 - .saturating_add(Weight::from_ref_time(17_708 as u64).saturating_mul(m as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `32 + m * (32 ±0)` + // Estimated: `4687 + m * (32 ±0)` + // Minimum execution time: 7_481_000 picoseconds. + Weight::from_parts(7_959_053, 4687) + // Standard Error: 364 + .saturating_add(Weight::from_parts(18_653, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(m.into())) } - // Storage: TechnicalMembership Prime (r:0 w:1) - // Storage: TechnicalCommittee Prime (r:0 w:1) + /// Storage: TechnicalMembership Prime (r:0 w:1) + /// Proof: TechnicalMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Prime (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. fn clear_prime(m: u32, ) -> Weight { - // Minimum execution time: 5_546 nanoseconds. - Weight::from_ref_time(5_962_740 as u64) - // Standard Error: 186 - .saturating_add(Weight::from_ref_time(2_096 as u64).saturating_mul(m as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_373_000 picoseconds. + Weight::from_parts(3_750_452, 0) + // Standard Error: 142 + .saturating_add(Weight::from_parts(505, 0).saturating_mul(m.into())) + .saturating_add(T::DbWeight::get().writes(2_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: TechnicalMembership Members (r:1 w:1) - // Storage: TechnicalCommittee Proposals (r:1 w:0) - // Storage: TechnicalCommittee Members (r:0 w:1) - // Storage: TechnicalCommittee Prime (r:0 w:1) + /// Storage: TechnicalMembership Members (r:1 w:1) + /// Proof: TechnicalMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Proposals (r:1 w:0) + /// Proof Skipped: TechnicalCommittee Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalCommittee Members (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalCommittee Prime (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 99]`. fn add_member(m: u32, ) -> Weight { - // Minimum execution time: 23_796 nanoseconds. - Weight::from_ref_time(24_829_996 as u64) - // Standard Error: 723 - .saturating_add(Weight::from_ref_time(48_467 as u64).saturating_mul(m as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `208 + m * (64 ±0)` + // Estimated: `4687 + m * (64 ±0)` + // Minimum execution time: 17_040_000 picoseconds. + Weight::from_parts(18_344_571, 4687) + // Standard Error: 847 + .saturating_add(Weight::from_parts(50_842, 0).saturating_mul(m.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) } - // Storage: TechnicalMembership Members (r:1 w:1) - // Storage: TechnicalCommittee Proposals (r:1 w:0) - // Storage: TechnicalMembership Prime (r:1 w:0) - // Storage: TechnicalCommittee Members (r:0 w:1) - // Storage: TechnicalCommittee Prime (r:0 w:1) + /// Storage: TechnicalMembership Members (r:1 w:1) + /// Proof: TechnicalMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Proposals (r:1 w:0) + /// Proof Skipped: TechnicalCommittee Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalMembership Prime (r:1 w:0) + /// Proof: TechnicalMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Members (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalCommittee Prime (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[2, 100]`. fn remove_member(m: u32, ) -> Weight { - // Minimum execution time: 27_255 nanoseconds. - Weight::from_ref_time(28_234_490 as u64) - // Standard Error: 833 - .saturating_add(Weight::from_ref_time(34_894 as u64).saturating_mul(m as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `312 + m * (64 ±0)` + // Estimated: `4687 + m * (64 ±0)` + // Minimum execution time: 20_088_000 picoseconds. + Weight::from_parts(21_271_384, 4687) + // Standard Error: 786 + .saturating_add(Weight::from_parts(44_806, 0).saturating_mul(m.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) } - // Storage: TechnicalMembership Members (r:1 w:1) - // Storage: TechnicalCommittee Proposals (r:1 w:0) - // Storage: TechnicalMembership Prime (r:1 w:0) - // Storage: TechnicalCommittee Members (r:0 w:1) - // Storage: TechnicalCommittee Prime (r:0 w:1) + /// Storage: TechnicalMembership Members (r:1 w:1) + /// Proof: TechnicalMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Proposals (r:1 w:0) + /// Proof Skipped: TechnicalCommittee Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalMembership Prime (r:1 w:0) + /// Proof: TechnicalMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Members (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalCommittee Prime (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[2, 100]`. fn swap_member(m: u32, ) -> Weight { - // Minimum execution time: 26_626 nanoseconds. - Weight::from_ref_time(27_989_042 as u64) - // Standard Error: 729 - .saturating_add(Weight::from_ref_time(51_567 as u64).saturating_mul(m as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `312 + m * (64 ±0)` + // Estimated: `4687 + m * (64 ±0)` + // Minimum execution time: 20_308_000 picoseconds. + Weight::from_parts(21_469_843, 4687) + // Standard Error: 782 + .saturating_add(Weight::from_parts(56_893, 0).saturating_mul(m.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) } - // Storage: TechnicalMembership Members (r:1 w:1) - // Storage: TechnicalCommittee Proposals (r:1 w:0) - // Storage: TechnicalMembership Prime (r:1 w:0) - // Storage: TechnicalCommittee Members (r:0 w:1) - // Storage: TechnicalCommittee Prime (r:0 w:1) + /// Storage: TechnicalMembership Members (r:1 w:1) + /// Proof: TechnicalMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Proposals (r:1 w:0) + /// Proof Skipped: TechnicalCommittee Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalMembership Prime (r:1 w:0) + /// Proof: TechnicalMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Members (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalCommittee Prime (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. fn reset_member(m: u32, ) -> Weight { - // Minimum execution time: 25_412 nanoseconds. - Weight::from_ref_time(27_713_414 as u64) - // Standard Error: 883 - .saturating_add(Weight::from_ref_time(157_085 as u64).saturating_mul(m as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `312 + m * (64 ±0)` + // Estimated: `4687 + m * (64 ±0)` + // Minimum execution time: 19_464_000 picoseconds. + Weight::from_parts(21_223_702, 4687) + // Standard Error: 1_068 + .saturating_add(Weight::from_parts(165_438, 0).saturating_mul(m.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) } - // Storage: TechnicalMembership Members (r:1 w:1) - // Storage: TechnicalCommittee Proposals (r:1 w:0) - // Storage: TechnicalMembership Prime (r:1 w:1) - // Storage: TechnicalCommittee Members (r:0 w:1) - // Storage: TechnicalCommittee Prime (r:0 w:1) + /// Storage: TechnicalMembership Members (r:1 w:1) + /// Proof: TechnicalMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Proposals (r:1 w:0) + /// Proof Skipped: TechnicalCommittee Proposals (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalMembership Prime (r:1 w:1) + /// Proof: TechnicalMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Members (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: TechnicalCommittee Prime (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. fn change_key(m: u32, ) -> Weight { - // Minimum execution time: 27_122 nanoseconds. - Weight::from_ref_time(28_477_394 as u64) - // Standard Error: 801 - .saturating_add(Weight::from_ref_time(56_383 as u64).saturating_mul(m as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `312 + m * (64 ±0)` + // Estimated: `4687 + m * (64 ±0)` + // Minimum execution time: 20_965_000 picoseconds. + Weight::from_parts(22_551_007, 4687) + // Standard Error: 860 + .saturating_add(Weight::from_parts(52_397, 0).saturating_mul(m.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(Weight::from_parts(0, 64).saturating_mul(m.into())) } - // Storage: TechnicalMembership Members (r:1 w:0) - // Storage: TechnicalMembership Prime (r:0 w:1) - // Storage: TechnicalCommittee Prime (r:0 w:1) + /// Storage: TechnicalMembership Members (r:1 w:0) + /// Proof: TechnicalMembership Members (max_values: Some(1), max_size: Some(3202), added: 3697, mode: MaxEncodedLen) + /// Storage: TechnicalMembership Prime (r:0 w:1) + /// Proof: TechnicalMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Prime (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. fn set_prime(m: u32, ) -> Weight { - // Minimum execution time: 9_368 nanoseconds. - Weight::from_ref_time(10_133_132 as u64) - // Standard Error: 366 - .saturating_add(Weight::from_ref_time(17_708 as u64).saturating_mul(m as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `32 + m * (32 ±0)` + // Estimated: `4687 + m * (32 ±0)` + // Minimum execution time: 7_481_000 picoseconds. + Weight::from_parts(7_959_053, 4687) + // Standard Error: 364 + .saturating_add(Weight::from_parts(18_653, 0).saturating_mul(m.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(m.into())) } - // Storage: TechnicalMembership Prime (r:0 w:1) - // Storage: TechnicalCommittee Prime (r:0 w:1) + /// Storage: TechnicalMembership Prime (r:0 w:1) + /// Proof: TechnicalMembership Prime (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: TechnicalCommittee Prime (r:0 w:1) + /// Proof Skipped: TechnicalCommittee Prime (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `m` is `[1, 100]`. fn clear_prime(m: u32, ) -> Weight { - // Minimum execution time: 5_546 nanoseconds. - Weight::from_ref_time(5_962_740 as u64) - // Standard Error: 186 - .saturating_add(Weight::from_ref_time(2_096 as u64).saturating_mul(m as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_373_000 picoseconds. + Weight::from_parts(3_750_452, 0) + // Standard Error: 142 + .saturating_add(Weight::from_parts(505, 0).saturating_mul(m.into())) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } } diff --git a/frame/merkle-mountain-range/Cargo.toml b/frame/merkle-mountain-range/Cargo.toml index 3f7180f79adb..83bb7405f3d9 100644 --- a/frame/merkle-mountain-range/Cargo.toml +++ b/frame/merkle-mountain-range/Cargo.toml @@ -12,19 +12,19 @@ description = "FRAME Merkle Mountain Range pallet." targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } sp-mmr-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/merkle-mountain-range" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -array-bytes = "4.1" +array-bytes = "6.1" env_logger = "0.9" itertools = "0.10.3" diff --git a/frame/merkle-mountain-range/src/benchmarking.rs b/frame/merkle-mountain-range/src/benchmarking.rs index d24364a55f9e..9eb676a4ee44 100644 --- a/frame/merkle-mountain-range/src/benchmarking.rs +++ b/frame/merkle-mountain-range/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ #![cfg(feature = "runtime-benchmarks")] use crate::*; -use frame_benchmarking::benchmarks_instance_pallet; +use frame_benchmarking::v1::benchmarks_instance_pallet; use frame_support::traits::OnInitialize; benchmarks_instance_pallet! { diff --git a/frame/merkle-mountain-range/src/default_weights.rs b/frame/merkle-mountain-range/src/default_weights.rs index e4f9750fbcba..52e3f130383f 100644 --- a/frame/merkle-mountain-range/src/default_weights.rs +++ b/frame/merkle-mountain-range/src/default_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,7 @@ impl crate::WeightInfo for () { // Reading the parent hash. let leaf_weight = DbWeight::get().reads(1); // Blake2 hash cost. - let hash_weight = Weight::from_ref_time(2u64 * WEIGHT_REF_TIME_PER_NANOS); + let hash_weight = Weight::from_parts(2u64 * WEIGHT_REF_TIME_PER_NANOS, 0); // No-op hook. let hook_weight = Weight::zero(); diff --git a/frame/merkle-mountain-range/src/lib.rs b/frame/merkle-mountain-range/src/lib.rs index 170c1d724f3a..2edef9a35d57 100644 --- a/frame/merkle-mountain-range/src/lib.rs +++ b/frame/merkle-mountain-range/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -57,6 +57,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::{log, weights::Weight}; +use frame_system::pallet_prelude::{BlockNumberFor, HeaderFor}; use sp_mmr_primitives::utils; use sp_runtime::{ traits::{self, One, Saturating}, @@ -91,7 +92,7 @@ pub struct ParentNumberAndHash { } impl LeafDataProvider for ParentNumberAndHash { - type LeafData = (::BlockNumber, ::Hash); + type LeafData = (BlockNumberFor, ::Hash); fn leaf_data() -> Self::LeafData { ( @@ -113,15 +114,15 @@ type LeafOf = <>::LeafData as primitives::LeafDataProvider> /// Hashing used for the pallet. pub(crate) type HashingOf = >::Hashing; +/// Hash type used for the pallet. +pub(crate) type HashOf = <>::Hashing as traits::Hash>::Output; #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; - use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(PhantomData<(T, I)>); /// This pallet's configuration trait @@ -147,25 +148,7 @@ pub mod pallet { /// /// Then we create a tuple of these two hashes, SCALE-encode it (concatenate) and /// hash, to obtain a new MMR inner node - the new peak. - type Hashing: traits::Hash>::Hash>; - - /// The hashing output type. - /// - /// This type is actually going to be stored in the MMR. - /// Required to be provided again, to satisfy trait bounds for storage items. - type Hash: traits::Member - + traits::MaybeSerializeDeserialize - + sp_std::fmt::Debug - + sp_std::hash::Hash - + AsRef<[u8]> - + AsMut<[u8]> - + Copy - + Default - + codec::Codec - + codec::EncodeLike - + scale_info::TypeInfo - + MaxEncodedLen - + PartialOrd; + type Hashing: traits::Hash; /// Data stored in the leaf nodes. /// @@ -191,7 +174,7 @@ pub mod pallet { /// apart from having it in the storage. For instance you might output it in the header /// digest (see [`frame_system::Pallet::deposit_log`]) to make it available for Light /// Clients. Hook complexity should be `O(1)`. - type OnNewRoot: primitives::OnNewRoot<>::Hash>; + type OnNewRoot: primitives::OnNewRoot>; /// Weights for this pallet. type WeightInfo: WeightInfo; @@ -200,8 +183,7 @@ pub mod pallet { /// Latest MMR Root hash. #[pallet::storage] #[pallet::getter(fn mmr_root_hash)] - pub type RootHash, I: 'static = ()> = - StorageValue<_, >::Hash, ValueQuery>; + pub type RootHash, I: 'static = ()> = StorageValue<_, HashOf, ValueQuery>; /// Current size of the MMR (number of leaves). #[pallet::storage] @@ -215,11 +197,11 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn mmr_peak)] pub type Nodes, I: 'static = ()> = - StorageMap<_, Identity, NodeIndex, >::Hash, OptionQuery>; + StorageMap<_, Identity, NodeIndex, HashOf, OptionQuery>; #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { - fn on_initialize(_n: T::BlockNumber) -> Weight { + fn on_initialize(_n: BlockNumberFor) -> Weight { use primitives::LeafDataProvider; let leaves = Self::mmr_leaves(); let peaks_before = sp_mmr_primitives::utils::NodesUtils::new(leaves).number_of_peaks(); @@ -284,11 +266,7 @@ impl, I: 'static> Pallet { pos: NodeIndex, parent_hash: ::Hash, ) -> sp_std::prelude::Vec { - NodesUtils::node_temp_offchain_key::<::Header>( - &T::INDEXING_PREFIX, - pos, - parent_hash, - ) + NodesUtils::node_temp_offchain_key::>(&T::INDEXING_PREFIX, pos, parent_hash) } /// Build canonical offchain key for node `pos` in MMR. @@ -304,7 +282,7 @@ impl, I: 'static> Pallet { fn leaf_index_to_parent_block_num( leaf_index: LeafIndex, leaves_count: LeafIndex, - ) -> ::BlockNumber { + ) -> BlockNumberFor { // leaves are zero-indexed and were added one per block since pallet activation, // while block numbers are one-indexed, so block number that added `leaf_idx` is: // `block_num = block_num_when_pallet_activated + leaf_idx + 1` @@ -316,16 +294,16 @@ impl, I: 'static> Pallet { } /// Convert a block number into a leaf index. - fn block_num_to_leaf_index(block_num: T::BlockNumber) -> Result + fn block_num_to_leaf_index(block_num: BlockNumberFor) -> Result where T: frame_system::Config, { - let first_mmr_block = utils::first_mmr_block_num::( + let first_mmr_block = utils::first_mmr_block_num::>( >::block_number(), Self::mmr_leaves(), )?; - utils::block_num_to_leaf_index::(block_num, first_mmr_block) + utils::block_num_to_leaf_index::>(block_num, first_mmr_block) } /// Generate an MMR proof for the given `block_numbers`. @@ -338,9 +316,9 @@ impl, I: 'static> Pallet { /// all the leaves to be present. /// It may return an error or panic if used incorrectly. pub fn generate_proof( - block_numbers: Vec, - best_known_block_number: Option, - ) -> Result<(Vec>, primitives::Proof<>::Hash>), primitives::Error> { + block_numbers: Vec>, + best_known_block_number: Option>, + ) -> Result<(Vec>, primitives::Proof>), primitives::Error> { // check whether best_known_block_number provided, else use current best block let best_known_block_number = best_known_block_number.unwrap_or_else(|| >::block_number()); @@ -361,7 +339,7 @@ impl, I: 'static> Pallet { } /// Return the on-chain MMR root hash. - pub fn mmr_root() -> >::Hash { + pub fn mmr_root() -> HashOf { Self::mmr_root_hash() } @@ -373,7 +351,7 @@ impl, I: 'static> Pallet { /// or the proof is invalid. pub fn verify_leaves( leaves: Vec>, - proof: primitives::Proof<>::Hash>, + proof: primitives::Proof>, ) -> Result<(), primitives::Error> { if proof.leaf_count > Self::mmr_leaves() || proof.leaf_count == 0 || diff --git a/frame/merkle-mountain-range/src/mmr/mmr.rs b/frame/merkle-mountain-range/src/mmr/mmr.rs index cdb189a14b66..aeb3e7ea6641 100644 --- a/frame/merkle-mountain-range/src/mmr/mmr.rs +++ b/frame/merkle-mountain-range/src/mmr/mmr.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,7 @@ use crate::{ Hasher, Node, NodeOf, }, primitives::{self, Error, NodeIndex}, - Config, HashingOf, + Config, HashOf, HashingOf, }; use sp_mmr_primitives::{mmr_lib, utils::NodesUtils}; use sp_std::prelude::*; @@ -95,7 +95,7 @@ where pub fn verify_leaves_proof( &self, leaves: Vec, - proof: primitives::Proof<>::Hash>, + proof: primitives::Proof>, ) -> Result { let p = mmr_lib::MerkleProof::, Hasher, L>>::new( self.mmr.mmr_size(), @@ -145,7 +145,7 @@ where /// Commit the changes to underlying storage, return current number of leaves and /// calculate the new MMR's root hash. - pub fn finalize(self) -> Result<(NodeIndex, >::Hash), Error> { + pub fn finalize(self) -> Result<(NodeIndex, HashOf), Error> { let root = self.mmr.get_root().map_err(|e| Error::GetRoot.log_error(e))?; self.mmr.commit().map_err(|e| Error::Commit.log_error(e))?; Ok((self.leaves, root.hash())) @@ -166,7 +166,7 @@ where pub fn generate_proof( &self, leaf_indices: Vec, - ) -> Result<(Vec, primitives::Proof<>::Hash>), Error> { + ) -> Result<(Vec, primitives::Proof>), Error> { let positions = leaf_indices .iter() .map(|index| mmr_lib::leaf_index_to_pos(*index)) diff --git a/frame/merkle-mountain-range/src/mmr/mod.rs b/frame/merkle-mountain-range/src/mmr/mod.rs index 51de29475315..536faa68e4e9 100644 --- a/frame/merkle-mountain-range/src/mmr/mod.rs +++ b/frame/merkle-mountain-range/src/mmr/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/merkle-mountain-range/src/mmr/storage.rs b/frame/merkle-mountain-range/src/mmr/storage.rs index 2e3b174f3943..d7466784e4ab 100644 --- a/frame/merkle-mountain-range/src/mmr/storage.rs +++ b/frame/merkle-mountain-range/src/mmr/storage.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/merkle-mountain-range/src/mock.rs b/frame/merkle-mountain-range/src/mock.rs index 3fd44275857c..ecc254278bf0 100644 --- a/frame/merkle-mountain-range/src/mock.rs +++ b/frame/merkle-mountain-range/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,21 +25,14 @@ use frame_support::{ }; use sp_core::H256; use sp_mmr_primitives::{Compact, LeafDataProvider}; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup, Keccak256}, -}; +use sp_runtime::traits::{BlakeTwo256, IdentityLookup, Keccak256}; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, MMR: pallet_mmr::{Pallet, Storage}, } ); @@ -48,13 +41,12 @@ impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = sp_core::sr25519::Public; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type DbWeight = (); @@ -75,7 +67,6 @@ impl Config for Test { const INDEXING_PREFIX: &'static [u8] = b"mmr-"; type Hashing = Keccak256; - type Hash = H256; type LeafData = Compact, LeafData)>; type OnNewRoot = (); type WeightInfo = (); diff --git a/frame/merkle-mountain-range/src/tests.rs b/frame/merkle-mountain-range/src/tests.rs index b5f9a78ede01..429df75182ee 100644 --- a/frame/merkle-mountain-range/src/tests.rs +++ b/frame/merkle-mountain-range/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,9 +23,10 @@ use sp_core::{ H256, }; use sp_mmr_primitives::{mmr_lib::helper, utils, Compact, Proof}; +use sp_runtime::BuildStorage; pub(crate) fn new_test_ext() -> sp_io::TestExternalities { - frame_system::GenesisConfig::default().build_storage::().unwrap().into() + frame_system::GenesisConfig::::default().build_storage().unwrap().into() } fn register_offchain_ext(ext: &mut sp_io::TestExternalities) { @@ -53,7 +54,7 @@ pub(crate) fn hex(s: &str) -> H256 { s.parse().unwrap() } -type BlockNumber = ::BlockNumber; +type BlockNumber = frame_system::pallet_prelude::BlockNumberFor; fn decode_node( v: Vec, diff --git a/frame/message-queue/Cargo.toml b/frame/message-queue/Cargo.toml index 115200b82676..23c30e7a8455 100644 --- a/frame/message-queue/Cargo.toml +++ b/frame/message-queue/Cargo.toml @@ -9,24 +9,24 @@ repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet to queue and process messages" [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } -serde = { version = "1.0.137", optional = true, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", optional = true, features = ["derive"] } log = { version = "0.4.17", default-features = false } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } -sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } -sp-weights = { version = "4.0.0", default-features = false, path = "../../primitives/weights" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-weights = { version = "20.0.0", default-features = false, path = "../../primitives/weights" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } [dev-dependencies] -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } rand = "0.8.5" rand_distr = "0.4.3" diff --git a/frame/message-queue/src/benchmarking.rs b/frame/message-queue/src/benchmarking.rs index 9651c81e5e66..bbd321ceadd1 100644 --- a/frame/message-queue/src/benchmarking.rs +++ b/frame/message-queue/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,8 +22,8 @@ use super::{mock_helpers::*, Pallet as MessageQueue, *}; -use frame_benchmarking::{impl_benchmark_test_suite, whitelisted_caller}; -use frame_support::{benchmarking::*, traits::Get}; +use frame_benchmarking::v2::*; +use frame_support::traits::Get; use frame_system::RawOrigin; use sp_std::prelude::*; @@ -142,7 +142,7 @@ mod benchmarks { // Check that it was processed. assert_last_event::( Event::Processed { - hash: T::Hashing::hash(&msg), + id: sp_io::hashing::blake2_256(&msg), origin: 0.into(), weight_used: 1.into_weight(), success: true, @@ -166,7 +166,7 @@ mod benchmarks { } assert_eq!(ServiceHead::::get().unwrap(), 10u32.into()); - assert_eq!(weight.consumed, T::WeightInfo::bump_service_head()); + assert_eq!(weight.consumed(), T::WeightInfo::bump_service_head()); } #[benchmark] @@ -227,7 +227,7 @@ mod benchmarks { assert_last_event::( Event::Processed { - hash: T::Hashing::hash(&((msgs - 1) as u32).encode()), + id: sp_io::hashing::blake2_256(&((msgs - 1) as u32).encode()), origin: 0.into(), weight_used: Weight::from_parts(1, 1), success: true, @@ -264,7 +264,7 @@ mod benchmarks { assert_last_event::( Event::Processed { - hash: T::Hashing::hash(&((msgs - 1) as u32).encode()), + id: sp_io::hashing::blake2_256(&((msgs - 1) as u32).encode()), origin: 0.into(), weight_used: Weight::from_parts(1, 1), success: true, diff --git a/frame/message-queue/src/integration_test.rs b/frame/message-queue/src/integration_test.rs index c9e3b2e870fd..4fc639101a01 100644 --- a/frame/message-queue/src/integration_test.rs +++ b/frame/message-queue/src/integration_test.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,9 @@ use crate::{ mock::{ new_test_ext, CountingMessageProcessor, IntoWeight, MockedWeightInfo, NumMessagesProcessed, + YieldingQueues, }, + mock_helpers::MessageOrigin, *, }; @@ -35,21 +37,15 @@ use frame_support::{ use rand::{rngs::StdRng, Rng, SeedableRng}; use rand_distr::Pareto; use sp_core::H256; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, -}; +use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; +use std::collections::{BTreeMap, BTreeSet}; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, } ); @@ -60,14 +56,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -93,6 +88,7 @@ impl Config for Test { type MessageProcessor = CountingMessageProcessor; type Size = u32; type QueueChangeHandler = (); + type QueuePausedQuery = (); type HeapSize = HeapSize; type MaxStale = MaxStale; type ServiceWeight = ServiceWeight; @@ -100,7 +96,8 @@ impl Config for Test { /// Simulates heavy usage by enqueueing and processing large amounts of messages. /// -/// Best to run with `-r`, `RUST_LOG=info` and `RUSTFLAGS='-Cdebug-assertions=y'`. +/// Best to run with `RUST_LOG=info RUSTFLAGS='-Cdebug-assertions=y' cargo test -r -p +/// pallet-message-queue -- --ignored`. /// /// # Example output /// @@ -130,29 +127,131 @@ fn stress_test_enqueue_and_service() { let mut msgs_remaining = 0; for _ in 0..blocks { // Start by enqueuing a large number of messages. - let (enqueued, _) = + let enqueued = enqueue_messages(max_queues, max_messages_per_queue, max_msg_len, &mut rng); msgs_remaining += enqueued; // Pick a fraction of all messages currently in queue and process them. let processed = rng.gen_range(1..=msgs_remaining); log::info!("Processing {} of all messages {}", processed, msgs_remaining); - process_messages(processed); // This also advances the block. + process_some_messages(processed); // This also advances the block. + msgs_remaining -= processed; + } + log::info!("Processing all remaining {} messages", msgs_remaining); + process_all_messages(msgs_remaining); + post_conditions(); + }); +} + +/// Simulates heavy usage of the suspension logic via `Yield`. +/// +/// Best to run with `RUST_LOG=info RUSTFLAGS='-Cdebug-assertions=y' cargo test -r -p +/// pallet-message-queue -- --ignored`. +/// +/// # Example output +/// +/// ```pre +/// Enqueued 11776 messages across 2526 queues. Payload 173.94 KiB +/// Suspended 63 and resumed 7 queues of 2526 in total +/// Processing 593 messages. Resumed msgs: 11599, All msgs: 11776 +/// Enqueued 30104 messages across 5533 queues. Payload 416.62 KiB +/// Suspended 24 and resumed 15 queues of 5533 in total +/// Processing 12841 messages. Resumed msgs: 40857, All msgs: 41287 +/// Processing all 28016 remaining resumed messages +/// Resumed all 64 suspended queues +/// Processing all remaining 430 messages +/// ``` +#[test] +#[ignore] // Only run in the CI. +fn stress_test_queue_suspension() { + let blocks = 20; + let max_queues = 10_000; + let max_messages_per_queue = 10_000; + let (max_suspend_per_block, max_resume_per_block) = (100, 50); + let max_msg_len = MaxMessageLenOf::::get(); + let mut rng = StdRng::seed_from_u64(41); + + new_test_ext::().execute_with(|| { + let mut suspended = BTreeSet::::new(); + let mut msgs_remaining = 0; + + for _ in 0..blocks { + // Start by enqueuing a large number of messages. + let enqueued = + enqueue_messages(max_queues, max_messages_per_queue, max_msg_len, &mut rng); + msgs_remaining += enqueued; + let per_queue = msgs_per_queue(); + + // Suspend a random subset of queues. + let to_suspend = rng.gen_range(0..max_suspend_per_block).min(per_queue.len()); + for _ in 0..to_suspend { + let q = rng.gen_range(0..per_queue.len()); + suspended.insert(*per_queue.iter().nth(q).map(|(q, _)| q).unwrap()); + } + // Resume a random subst of suspended queues. + let to_resume = rng.gen_range(0..max_resume_per_block).min(suspended.len()); + for _ in 0..to_resume { + let q = rng.gen_range(0..suspended.len()); + suspended.remove(&suspended.iter().nth(q).unwrap().clone()); + } + log::info!( + "Suspended {} and resumed {} queues of {} in total", + to_suspend, + to_resume, + per_queue.len() + ); + YieldingQueues::set(suspended.iter().map(|q| MessageOrigin::Everywhere(*q)).collect()); + + // Pick a fraction of all messages currently in queue and process them. + let resumed_messages = + per_queue.iter().filter(|(q, _)| !suspended.contains(q)).map(|(_, n)| n).sum(); + let processed = rng.gen_range(1..=resumed_messages); + log::info!( + "Processing {} messages. Resumed msgs: {}, All msgs: {}", + processed, + resumed_messages, + msgs_remaining + ); + process_some_messages(processed); // This also advances the block. msgs_remaining -= processed; } + let per_queue = msgs_per_queue(); + let resumed_messages = + per_queue.iter().filter(|(q, _)| !suspended.contains(q)).map(|(_, n)| n).sum(); + log::info!("Processing all {} remaining resumed messages", resumed_messages); + process_all_messages(resumed_messages); + msgs_remaining -= resumed_messages; + + let resumed = YieldingQueues::take(); + log::info!("Resumed all {} suspended queues", resumed.len()); log::info!("Processing all remaining {} messages", msgs_remaining); - process_messages(msgs_remaining); + process_all_messages(msgs_remaining); post_conditions(); }); } +/// How many messages are in each queue. +fn msgs_per_queue() -> BTreeMap { + let mut per_queue = BTreeMap::new(); + for (o, q) in BookStateFor::::iter() { + let MessageOrigin::Everywhere(o) = o else { + unreachable!(); + }; + per_queue.insert(o, q.message_count as u32); + } + per_queue +} + /// Enqueue a random number of random messages into a random number of queues. +/// +/// Returns the total number of enqueued messages, their combined length and the number of messages +/// per queue. fn enqueue_messages( max_queues: u32, max_per_queue: u32, max_msg_len: u32, rng: &mut StdRng, -) -> (u32, usize) { +) -> u32 { let num_queues = rng.gen_range(1..max_queues); let mut num_messages = 0; let mut total_msg_len = 0; @@ -179,11 +278,11 @@ fn enqueue_messages( num_queues, total_msg_len as f64 / 1024.0 ); - (num_messages, total_msg_len as usize) + num_messages } /// Process the number of messages. -fn process_messages(num_msgs: u32) { +fn process_some_messages(num_msgs: u32) { let weight = (num_msgs as u64).into_weight(); ServiceWeight::set(Some(weight)); let consumed = next_block(); @@ -192,6 +291,15 @@ fn process_messages(num_msgs: u32) { assert_eq!(NumMessagesProcessed::take(), num_msgs as usize); } +/// Process all remaining messages and assert their number. +fn process_all_messages(expected: u32) { + ServiceWeight::set(Some(Weight::MAX)); + let consumed = next_block(); + + assert_eq!(consumed, Weight::from_all(expected as u64)); + assert_eq!(NumMessagesProcessed::take(), expected as usize); +} + /// Returns the weight consumed by `MessageQueue::on_initialize()`. fn next_block() -> Weight { MessageQueue::on_finalize(System::block_number()); diff --git a/frame/message-queue/src/lib.rs b/frame/message-queue/src/lib.rs index 8d9faebe0517..08795914ebe7 100644 --- a/frame/message-queue/src/lib.rs +++ b/frame/message-queue/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,7 +39,7 @@ //! which queue it will be stored. Messages are stored by being appended to the last [`Page`] of a //! book. Each book keeps track of its pages by indexing `Pages`. The `ReadyRing` contains all //! queues which hold at least one unprocessed message and are thereby *ready* to be serviced. The -//! `ServiceHead` indicates which *ready* queue is the next to be serviced. +//! `ServiceHead` indicates which *ready* queue is the next to be serviced. //! The pallet implements [`frame_support::traits::EnqueueMessage`], //! [`frame_support::traits::ServiceQueues`] and has [`frame_support::traits::ProcessMessage`] and //! [`OnQueueChanged`] hooks to communicate with the outside world. @@ -56,7 +56,7 @@ //! **Pagination** //! //! Queues are stored in a *paged* manner by splitting their messages into [`Page`]s. This results -//! in a lot of complexity when implementing the pallet but is completely necessary to archive the +//! in a lot of complexity when implementing the pallet but is completely necessary to achieve the //! second #[Design Goal](design-goals). The problem comes from the fact a message can *possibly* be //! quite large, lets say 64KiB. This then results in a *MEL* of at least 64KiB which results in a //! PoV of at least 64KiB. Now we have the assumption that most messages are much shorter than their @@ -195,7 +195,7 @@ use frame_support::{ pallet_prelude::*, traits::{ DefensiveTruncateFrom, EnqueueMessage, ExecuteOverweightError, Footprint, ProcessMessage, - ProcessMessageError, ServiceQueues, + ProcessMessageError, QueuePausedQuery, ServiceQueues, }, BoundedSlice, CloneNoBound, DefaultNoBound, }; @@ -204,7 +204,7 @@ pub use pallet::*; use scale_info::TypeInfo; use sp_arithmetic::traits::{BaseArithmetic, Unsigned}; use sp_runtime::{ - traits::{Hash, One, Zero}, + traits::{One, Zero}, SaturatedConversion, Saturating, }; use sp_std::{fmt::Debug, ops::Deref, prelude::*, vec}; @@ -438,7 +438,6 @@ pub mod pallet { use super::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); /// The module configuration trait. @@ -474,6 +473,13 @@ pub mod pallet { /// removed. type QueueChangeHandler: OnQueueChanged<::Origin>; + /// Queried by the pallet to check whether a queue can be serviced. + /// + /// This also applies to manual servicing via `execute_overweight` and `service_queues`. The + /// value of this is only polled once before servicing the queue. This means that changes to + /// it that happen *within* the servicing will not be reflected. + type QueuePausedQuery: QueuePausedQuery<::Origin>; + /// The size of the page; this implies the maximum message size which can be sent. /// /// A good value depends on the expected message sizes, their weights, the weight that is @@ -500,16 +506,13 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { - /// Message discarded due to an inability to decode the item. Usually caused by state - /// corruption. - Discarded { hash: T::Hash }, /// Message discarded due to an error in the `MessageProcessor` (usually a format error). - ProcessingFailed { hash: T::Hash, origin: MessageOriginOf, error: ProcessMessageError }, + ProcessingFailed { id: [u8; 32], origin: MessageOriginOf, error: ProcessMessageError }, /// Message is processed. - Processed { hash: T::Hash, origin: MessageOriginOf, weight_used: Weight, success: bool }, + Processed { id: [u8; 32], origin: MessageOriginOf, weight_used: Weight, success: bool }, /// Message placed in overweight queue. OverweightEnqueued { - hash: T::Hash, + id: [u8; 32], origin: MessageOriginOf, page_index: PageIndex, message_index: T::Size, @@ -533,6 +536,15 @@ pub mod pallet { Queued, /// There is temporarily not enough weight to continue servicing messages. InsufficientWeight, + /// This message is temporarily unprocessable. + /// + /// Such errors are expected, but not guaranteed, to resolve themselves eventually through + /// retrying. + TemporarilyUnprocessable, + /// The queue is paused and no message can be executed from it. + /// + /// This can change at any time and may resolve in the future by re-trying. + QueuePaused, } /// The index of the first and last (non-empty) pages. @@ -588,6 +600,9 @@ pub mod pallet { /// Execute an overweight message. /// + /// Temporary processing errors will be propagated whereas permanent errors are treated + /// as success condition. + /// /// - `origin`: Must be `Signed`. /// - `message_origin`: The origin from which the message to be executed arrived. /// - `page`: The page in the queue in which the message to be executed is sitting. @@ -621,6 +636,10 @@ pub mod pallet { enum PageExecutionStatus { /// The execution bailed because there was not enough weight remaining. Bailed, + /// The page did not make any progress on its execution. + /// + /// This is a transient condition and can be handled by retrying - exactly like [Bailed]. + NoProgress, /// No more messages could be loaded. This does _not_ imply `page.is_complete()`. /// /// The reasons for this status are: @@ -634,6 +653,10 @@ enum PageExecutionStatus { enum ItemExecutionStatus { /// The execution bailed because there was not enough weight remaining. Bailed, + /// The item did not make any progress on its execution. + /// + /// This is a transient condition and can be handled by retrying - exactly like [Bailed]. + NoProgress, /// The item was not found. NoItem, /// Whether the execution of an item resulted in it being processed. @@ -651,8 +674,8 @@ enum MessageExecutionStatus { Overweight, /// The message was processed successfully. Processed, - /// The message was processed and resulted in a permanent error. - Unprocessable, + /// The message was processed and resulted in a, possibly permanent, error. + Unprocessable { permanent: bool }, } impl Pallet { @@ -714,7 +737,7 @@ impl Pallet { /// /// Returns the current head if it got be bumped and `None` otherwise. fn bump_service_head(weight: &mut WeightMeter) -> Option> { - if !weight.check_accrue(T::WeightInfo::bump_service_head()) { + if weight.try_consume(T::WeightInfo::bump_service_head()).is_err() { return None } @@ -791,6 +814,8 @@ impl Pallet { weight_limit: Weight, ) -> Result> { let mut book_state = BookStateFor::::get(&origin); + ensure!(!T::QueuePausedQuery::is_paused(&origin), Error::::QueuePaused); + let mut page = Pages::::get(&origin, page_index).ok_or(Error::::NoPage)?; let (pos, is_processed, payload) = page.peek_index(index.into() as usize).ok_or(Error::::NoMessage)?; @@ -814,7 +839,8 @@ impl Pallet { // additional overweight event being deposited. ) { Overweight | InsufficientWeight => Err(Error::::InsufficientWeight), - Unprocessable | Processed => { + Unprocessable { permanent: false } => Err(Error::::TemporarilyUnprocessable), + Unprocessable { permanent: true } | Processed => { page.note_processed_at_pos(pos); book_state.message_count.saturating_dec(); book_state.size.saturating_reduce(payload_len); @@ -839,7 +865,7 @@ impl Pallet { book_state.message_count, book_state.size, ); - Ok(weight_counter.consumed.saturating_add(page_weight)) + Ok(weight_counter.consumed().saturating_add(page_weight)) }, } } @@ -921,14 +947,23 @@ impl Pallet { weight: &mut WeightMeter, overweight_limit: Weight, ) -> (bool, Option>) { - if !weight.check_accrue( - T::WeightInfo::service_queue_base().saturating_add(T::WeightInfo::ready_ring_unknit()), - ) { + use PageExecutionStatus::*; + if weight + .try_consume( + T::WeightInfo::service_queue_base() + .saturating_add(T::WeightInfo::ready_ring_unknit()), + ) + .is_err() + { return (false, None) } let mut book_state = BookStateFor::::get(&origin); let mut total_processed = 0; + if T::QueuePausedQuery::is_paused(&origin) { + let next_ready = book_state.ready_neighbours.as_ref().map(|x| x.next.clone()); + return (false, next_ready) + } while book_state.end > book_state.begin { let (processed, status) = @@ -936,18 +971,18 @@ impl Pallet { total_processed.saturating_accrue(processed); match status { // Store the page progress and do not go to the next one. - PageExecutionStatus::Bailed => break, + Bailed | NoProgress => break, // Go to the next page if this one is at the end. - PageExecutionStatus::NoMore => (), + NoMore => (), }; book_state.begin.saturating_inc(); } let next_ready = book_state.ready_neighbours.as_ref().map(|x| x.next.clone()); - if book_state.begin >= book_state.end && total_processed > 0 { + if book_state.begin >= book_state.end { // No longer ready - unknit. if let Some(neighbours) = book_state.ready_neighbours.take() { Self::ready_ring_unknit(&origin, neighbours); - } else { + } else if total_processed > 0 { defensive!("Freshly processed queue must have been ready"); } } @@ -972,10 +1007,13 @@ impl Pallet { overweight_limit: Weight, ) -> (u32, PageExecutionStatus) { use PageExecutionStatus::*; - if !weight.check_accrue( - T::WeightInfo::service_page_base_completion() - .max(T::WeightInfo::service_page_base_no_completion()), - ) { + if weight + .try_consume( + T::WeightInfo::service_page_base_completion() + .max(T::WeightInfo::service_page_base_no_completion()), + ) + .is_err() + { return (0, Bailed) } @@ -1003,6 +1041,7 @@ impl Pallet { ) { Bailed => break PageExecutionStatus::Bailed, NoItem => break PageExecutionStatus::NoMore, + NoProgress => break PageExecutionStatus::NoProgress, // Keep going as long as we make progress... Executed(true) => total_processed.saturating_inc(), Executed(false) => (), @@ -1034,7 +1073,7 @@ impl Pallet { if page.is_complete() { return ItemExecutionStatus::NoItem } - if !weight.check_accrue(T::WeightInfo::service_page_item()) { + if weight.try_consume(T::WeightInfo::service_page_item()).is_err() { return ItemExecutionStatus::Bailed } @@ -1053,7 +1092,8 @@ impl Pallet { overweight_limit, ) { InsufficientWeight => return ItemExecutionStatus::Bailed, - Processed | Unprocessable => true, + Unprocessable { permanent: false } => return ItemExecutionStatus::NoProgress, + Processed | Unprocessable { permanent: true } => true, Overweight => false, }; @@ -1125,16 +1165,19 @@ impl Pallet { page_index: PageIndex, message_index: T::Size, message: &[u8], - weight: &mut WeightMeter, + meter: &mut WeightMeter, overweight_limit: Weight, ) -> MessageExecutionStatus { - let hash = T::Hashing::hash(message); - use ProcessMessageError::Overweight; - match T::MessageProcessor::process_message(message, origin.clone(), weight.remaining()) { + let hash = sp_io::hashing::blake2_256(message); + use ProcessMessageError::*; + let prev_consumed = meter.consumed(); + let mut id = hash; + + match T::MessageProcessor::process_message(message, origin.clone(), meter, &mut id) { Err(Overweight(w)) if w.any_gt(overweight_limit) => { // Permanently overweight. Self::deposit_event(Event::::OverweightEnqueued { - hash, + id, origin, page_index, message_index, @@ -1146,16 +1189,19 @@ impl Pallet { // queue. MessageExecutionStatus::InsufficientWeight }, - Err(error) => { + Err(Yield) => { + // Processing should be reattempted later. + MessageExecutionStatus::Unprocessable { permanent: false } + }, + Err(error @ BadFormat | error @ Corrupt | error @ Unsupported) => { // Permanent error - drop - Self::deposit_event(Event::::ProcessingFailed { hash, origin, error }); - MessageExecutionStatus::Unprocessable + Self::deposit_event(Event::::ProcessingFailed { id, origin, error }); + MessageExecutionStatus::Unprocessable { permanent: true } }, - Ok((success, weight_used)) => { + Ok(success) => { // Success - weight.defensive_saturating_accrue(weight_used); - let event = Event::::Processed { hash, origin, weight_used, success }; - Self::deposit_event(event); + let weight_used = meter.consumed().saturating_sub(prev_consumed); + Self::deposit_event(Event::::Processed { id, origin, weight_used, success }); MessageExecutionStatus::Processed }, } @@ -1215,7 +1261,7 @@ impl ServiceQueues for Pallet { let mut next = match Self::bump_service_head(&mut weight) { Some(h) => h, - None => return weight.consumed, + None => return weight.consumed(), }; // The last queue that did not make any progress. // The loop aborts as soon as it arrives at this queue again without making any progress @@ -1241,7 +1287,7 @@ impl ServiceQueues for Pallet { None => break, } } - weight.consumed + weight.consumed() } /// Execute a single overweight message. @@ -1252,17 +1298,24 @@ impl ServiceQueues for Pallet { (message_origin, page, index): Self::OverweightMessageAddress, ) -> Result { let mut weight = WeightMeter::from_limit(weight_limit); - if !weight.check_accrue( - T::WeightInfo::execute_overweight_page_removed() - .max(T::WeightInfo::execute_overweight_page_updated()), - ) { + if weight + .try_consume( + T::WeightInfo::execute_overweight_page_removed() + .max(T::WeightInfo::execute_overweight_page_updated()), + ) + .is_err() + { return Err(ExecuteOverweightError::InsufficientWeight) } Pallet::::do_execute_overweight(message_origin, page, index, weight.remaining()).map_err( |e| match e { Error::::InsufficientWeight => ExecuteOverweightError::InsufficientWeight, - _ => ExecuteOverweightError::NotFound, + Error::::AlreadyProcessed => ExecuteOverweightError::AlreadyProcessed, + Error::::QueuePaused => ExecuteOverweightError::QueuePaused, + Error::::NoPage | Error::::NoMessage | Error::::Queued => + ExecuteOverweightError::NotFound, + _ => ExecuteOverweightError::Other, }, ) } diff --git a/frame/message-queue/src/mock.rs b/frame/message-queue/src/mock.rs index 37e504ac5003..04b763d59cff 100644 --- a/frame/message-queue/src/mock.rs +++ b/frame/message-queue/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,21 +29,17 @@ use frame_support::{ }; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; use sp_std::collections::btree_map::BTreeMap; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event}, } ); @@ -53,14 +49,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -84,6 +79,7 @@ impl Config for Test { type MessageProcessor = RecordingMessageProcessor; type Size = u32; type QueueChangeHandler = RecordingQueueChangeHandler; + type QueuePausedQuery = MockedQueuePauser; type HeapSize = HeapSize; type MaxStale = MaxStale; type ServiceWeight = ServiceWeight; @@ -154,6 +150,8 @@ impl crate::weights::WeightInfo for MockedWeightInfo { parameter_types! { pub static MessagesProcessed: Vec<(Vec, MessageOrigin)> = vec![]; + /// Queues that should return `Yield` upon being processed. + pub static YieldingQueues: Vec = vec![]; } /// A message processor which records all processed messages into [`MessagesProcessed`]. @@ -170,9 +168,10 @@ impl ProcessMessage for RecordingMessageProcessor { fn process_message( message: &[u8], origin: Self::Origin, - weight_limit: Weight, - ) -> Result<(bool, Weight), ProcessMessageError> { - processing_message(message)?; + meter: &mut WeightMeter, + _id: &mut [u8; 32], + ) -> Result { + processing_message(message, &origin)?; let weight = if message.starts_with(&b"weight="[..]) { let mut w: u64 = 0; @@ -187,22 +186,26 @@ impl ProcessMessage for RecordingMessageProcessor { } else { 1 }; - let weight = Weight::from_parts(weight, weight); + let required = Weight::from_parts(weight, weight); - if weight.all_lte(weight_limit) { + if meter.try_consume(required).is_ok() { let mut m = MessagesProcessed::get(); m.push((message.to_vec(), origin)); MessagesProcessed::set(m); - Ok((true, weight)) + Ok(true) } else { - Err(ProcessMessageError::Overweight(weight)) + Err(ProcessMessageError::Overweight(required)) } } } -/// Processed a mocked message. Messages that end with `badformat`, `corrupt` or `unsupported` will -/// fail with the respective error. -fn processing_message(msg: &[u8]) -> Result<(), ProcessMessageError> { +/// Processed a mocked message. Messages that end with `badformat`, `corrupt`, `unsupported` or +/// `yield` will fail with an error respectively. +fn processing_message(msg: &[u8], origin: &MessageOrigin) -> Result<(), ProcessMessageError> { + if YieldingQueues::get().contains(&origin) { + return Err(ProcessMessageError::Yield) + } + let msg = String::from_utf8_lossy(msg); if msg.ends_with("badformat") { Err(ProcessMessageError::BadFormat) @@ -210,6 +213,8 @@ fn processing_message(msg: &[u8]) -> Result<(), ProcessMessageError> { Err(ProcessMessageError::Corrupt) } else if msg.ends_with("unsupported") { Err(ProcessMessageError::Unsupported) + } else if msg.ends_with("yield") { + Err(ProcessMessageError::Yield) } else { Ok(()) } @@ -230,20 +235,21 @@ impl ProcessMessage for CountingMessageProcessor { fn process_message( message: &[u8], - _origin: Self::Origin, - weight_limit: Weight, - ) -> Result<(bool, Weight), ProcessMessageError> { - if let Err(e) = processing_message(message) { + origin: Self::Origin, + meter: &mut WeightMeter, + _id: &mut [u8; 32], + ) -> Result { + if let Err(e) = processing_message(message, &origin) { NumMessagesErrored::set(NumMessagesErrored::get() + 1); return Err(e) } - let weight = Weight::from_parts(1, 1); + let required = Weight::from_parts(1, 1); - if weight.all_lte(weight_limit) { + if meter.try_consume(required).is_ok() { NumMessagesProcessed::set(NumMessagesProcessed::get() + 1); - Ok((true, weight)) + Ok(true) } else { - Err(ProcessMessageError::Overweight(weight)) + Err(ProcessMessageError::Overweight(required)) } } } @@ -261,23 +267,40 @@ impl OnQueueChanged for RecordingQueueChangeHandler { } } +parameter_types! { + pub static PausedQueues: Vec = vec![]; +} + +pub struct MockedQueuePauser; +impl QueuePausedQuery for MockedQueuePauser { + fn is_paused(id: &MessageOrigin) -> bool { + PausedQueues::get().contains(id) + } +} + /// Create new test externalities. /// /// Is generic since it is used by the unit test, integration tests and benchmarks. pub fn new_test_ext() -> sp_io::TestExternalities where - ::BlockNumber: From, + frame_system::pallet_prelude::BlockNumberFor: From, { sp_tracing::try_init_simple(); WeightForCall::take(); QueueChanges::take(); NumMessagesErrored::take(); - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| frame_system::Pallet::::set_block_number(1.into())); ext } +/// Run this closure in test externalities. +pub fn test_closure(f: impl FnOnce() -> R) -> R { + let mut ext = new_test_ext::(); + ext.execute_with(f) +} + /// Set the weight of a specific weight function. pub fn set_weight(name: &str, w: Weight) { MockedWeightInfo::set_weight::(name, w); @@ -285,7 +308,11 @@ pub fn set_weight(name: &str, w: Weight) { /// Assert that exactly these pages are present. Assumes `Here` origin. pub fn assert_pages(indices: &[u32]) { - assert_eq!(Pages::::iter().count(), indices.len()); + assert_eq!( + Pages::::iter_keys().count(), + indices.len(), + "Wrong number of pages in the queue" + ); for i in indices { assert!(Pages::::contains_key(MessageOrigin::Here, i)); } @@ -309,3 +336,12 @@ pub fn knit(queue: &MessageOrigin) { pub fn unknit(queue: &MessageOrigin) { super::mock_helpers::unknit::(queue); } + +pub fn num_overweight_enqueued_events() -> u32 { + frame_system::Pallet::::events() + .into_iter() + .filter(|e| { + matches!(e.event, RuntimeEvent::MessageQueue(crate::Event::OverweightEnqueued { .. })) + }) + .count() as u32 +} diff --git a/frame/message-queue/src/mock_helpers.rs b/frame/message-queue/src/mock_helpers.rs index f12cf4cc4107..4e3cb323be72 100644 --- a/frame/message-queue/src/mock_helpers.rs +++ b/frame/message-queue/src/mock_helpers.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,32 +47,39 @@ impl From for MessageOrigin { } } -/// Processes any message and consumes (1, 1) weight per message. -pub struct NoopMessageProcessor; -impl ProcessMessage for NoopMessageProcessor { - type Origin = MessageOrigin; +/// Processes any message and consumes `(REQUIRED_WEIGHT, REQUIRED_WEIGHT)` weight. +/// +/// Returns [ProcessMessageError::Overweight] error if the weight limit is not sufficient. +pub struct NoopMessageProcessor(PhantomData); +impl ProcessMessage + for NoopMessageProcessor +where + Origin: codec::FullCodec + MaxEncodedLen + Clone + Eq + PartialEq + TypeInfo + Debug, +{ + type Origin = Origin; fn process_message( _message: &[u8], _origin: Self::Origin, - weight_limit: Weight, - ) -> Result<(bool, Weight), ProcessMessageError> { - let weight = Weight::from_parts(1, 1); + meter: &mut WeightMeter, + _id: &mut [u8; 32], + ) -> Result { + let required = Weight::from_parts(REQUIRED_WEIGHT, REQUIRED_WEIGHT); - if weight.all_lte(weight_limit) { - Ok((true, weight)) + if meter.try_consume(required).is_ok() { + Ok(true) } else { - Err(ProcessMessageError::Overweight(weight)) + Err(ProcessMessageError::Overweight(required)) } } } /// Create a message from the given data. -pub fn msg>(x: &'static str) -> BoundedSlice { +pub fn msg>(x: &str) -> BoundedSlice { BoundedSlice::defensive_truncate_from(x.as_bytes()) } -pub fn vmsg(x: &'static str) -> Vec { +pub fn vmsg(x: &str) -> Vec { x.as_bytes().to_vec() } @@ -82,11 +89,11 @@ pub fn page(msg: &[u8]) -> PageOf { } pub fn single_page_book() -> BookStateOf { - BookState { begin: 0, end: 1, count: 1, ready_neighbours: None, message_count: 0, size: 0 } + BookState { begin: 0, end: 1, count: 1, ..Default::default() } } pub fn empty_book() -> BookStateOf { - BookState { begin: 0, end: 1, count: 1, ready_neighbours: None, message_count: 0, size: 0 } + BookState { begin: 0, end: 1, count: 1, ..Default::default() } } /// Returns a full page of messages with their index as payload and the number of messages. @@ -111,9 +118,9 @@ pub fn book_for(page: &PageOf) -> BookStateOf { count: 1, begin: 0, end: 1, - ready_neighbours: None, message_count: page.remaining.into() as u64, size: page.remaining_size.into() as u64, + ..Default::default() } } diff --git a/frame/message-queue/src/tests.rs b/frame/message-queue/src/tests.rs index 103fb690ddba..7b7b51efc66f 100644 --- a/frame/message-queue/src/tests.rs +++ b/frame/message-queue/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,25 +23,26 @@ use crate::{mock::*, *}; use frame_support::{assert_noop, assert_ok, assert_storage_noop, StorageNoopGuard}; use rand::{rngs::StdRng, Rng, SeedableRng}; +use sp_core::blake2_256; #[test] fn mocked_weight_works() { - new_test_ext::().execute_with(|| { + test_closure(|| { assert!(::WeightInfo::service_queue_base().is_zero()); }); - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("service_queue_base", Weight::MAX); assert_eq!(::WeightInfo::service_queue_base(), Weight::MAX); }); // The externalities reset it. - new_test_ext::().execute_with(|| { + test_closure(|| { assert!(::WeightInfo::service_queue_base().is_zero()); }); } #[test] fn enqueue_within_one_page_works() { - new_test_ext::().execute_with(|| { + test_closure(|| { use MessageOrigin::*; MessageQueue::enqueue_message(msg("a"), Here); MessageQueue::enqueue_message(msg("b"), Here); @@ -76,7 +77,7 @@ fn enqueue_within_one_page_works() { #[test] fn queue_priority_retains() { - new_test_ext::().execute_with(|| { + test_closure(|| { use MessageOrigin::*; assert_ring(&[]); MessageQueue::enqueue_message(msg("a"), Everywhere(1)); @@ -107,7 +108,7 @@ fn queue_priority_retains() { #[test] fn queue_priority_reset_once_serviced() { - new_test_ext::().execute_with(|| { + test_closure(|| { use MessageOrigin::*; MessageQueue::enqueue_message(msg("a"), Everywhere(1)); MessageQueue::enqueue_message(msg("b"), Everywhere(2)); @@ -134,7 +135,7 @@ fn queue_priority_reset_once_serviced() { #[test] fn service_queues_basic_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { MessageQueue::enqueue_messages(vec![msg("a"), msg("ab"), msg("abc")].into_iter(), Here); MessageQueue::enqueue_messages(vec![msg("x"), msg("xy"), msg("xyz")].into_iter(), There); assert_eq!(QueueChanges::take(), vec![(Here, 3, 6), (There, 3, 6)]); @@ -145,13 +146,11 @@ fn service_queues_basic_works() { assert_eq!(QueueChanges::take(), vec![(Here, 2, 5)]); // Service one message from `There`. - ServiceHead::::set(There.into()); assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_eq!(MessagesProcessed::take(), vec![(vmsg("x"), There)]); assert_eq!(QueueChanges::take(), vec![(There, 2, 5)]); // Service the remaining from `Here`. - ServiceHead::::set(Here.into()); assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); assert_eq!(MessagesProcessed::take(), vec![(vmsg("ab"), Here), (vmsg("abc"), Here)]); assert_eq!(QueueChanges::take(), vec![(Here, 0, 0)]); @@ -166,18 +165,19 @@ fn service_queues_basic_works() { #[test] fn service_queues_failing_messages_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("service_page_item", 1.into_weight()); MessageQueue::enqueue_message(msg("badformat"), Here); MessageQueue::enqueue_message(msg("corrupt"), Here); MessageQueue::enqueue_message(msg("unsupported"), Here); - // Starts with three pages. - assert_pages(&[0, 1, 2]); + MessageQueue::enqueue_message(msg("yield"), Here); + // Starts with four pages. + assert_pages(&[0, 1, 2, 3]); assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_last_event::( Event::ProcessingFailed { - hash: ::Hashing::hash(b"badformat"), + id: blake2_256(b"badformat"), origin: MessageOrigin::Here, error: ProcessMessageError::BadFormat, } @@ -186,7 +186,7 @@ fn service_queues_failing_messages_works() { assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_last_event::( Event::ProcessingFailed { - hash: ::Hashing::hash(b"corrupt"), + id: blake2_256(b"corrupt"), origin: MessageOrigin::Here, error: ProcessMessageError::Corrupt, } @@ -195,21 +195,78 @@ fn service_queues_failing_messages_works() { assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); assert_last_event::( Event::ProcessingFailed { - hash: ::Hashing::hash(b"unsupported"), + id: blake2_256(b"unsupported"), origin: MessageOrigin::Here, error: ProcessMessageError::Unsupported, } .into(), ); - // All pages removed. - assert_pages(&[]); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(System::events().len(), 3); + // Last page with the `yield` stays in. + assert_pages(&[3]); + }); +} + +#[test] +fn service_queues_suspension_works() { + use MessageOrigin::*; + test_closure(|| { + MessageQueue::enqueue_messages(vec![msg("a"), msg("b"), msg("c")].into_iter(), Here); + MessageQueue::enqueue_messages(vec![msg("x"), msg("y"), msg("z")].into_iter(), There); + MessageQueue::enqueue_messages( + vec![msg("m"), msg("n"), msg("o")].into_iter(), + Everywhere(0), + ); + assert_eq!(QueueChanges::take(), vec![(Here, 3, 3), (There, 3, 3), (Everywhere(0), 3, 3)]); + + // Service one message from `Here`. + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("a"), Here)]); + assert_eq!(QueueChanges::take(), vec![(Here, 2, 2)]); + + // Make queue `Here` and `Everywhere(0)` yield. + YieldingQueues::set(vec![Here, Everywhere(0)]); + + // Service one message from `There`. + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("x"), There)]); + assert_eq!(QueueChanges::take(), vec![(There, 2, 2)]); + + // Now it would normally swap to `Everywhere(0)` and `Here`, but they are paused so we + // expect `There` again. + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("y"), There), (vmsg("z"), There)]); + + // Processing with max-weight won't do anything. + assert_eq!(MessageQueue::service_queues(Weight::MAX), Weight::zero()); + assert_eq!(MessageQueue::service_queues(Weight::MAX), Weight::zero()); + + // ... until we resume `Here`: + YieldingQueues::set(vec![Everywhere(0)]); + assert_eq!(MessageQueue::service_queues(Weight::MAX), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("b"), Here), (vmsg("c"), Here)]); + + // Everywhere still won't move. + assert_eq!(MessageQueue::service_queues(Weight::MAX), Weight::zero()); + YieldingQueues::take(); + // Resume `Everywhere(0)` makes it work. + assert_eq!(MessageQueue::service_queues(Weight::MAX), 3.into_weight()); + assert_eq!( + MessagesProcessed::take(), + vec![ + (vmsg("m"), Everywhere(0)), + (vmsg("n"), Everywhere(0)), + (vmsg("o"), Everywhere(0)) + ] + ); }); } #[test] fn reap_page_permanent_overweight_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { // Create 10 pages more than the stale limit. let n = (MaxStale::get() + 10) as usize; for _ in 0..n { @@ -249,7 +306,7 @@ fn reaping_overweight_fails_properly() { use MessageOrigin::*; assert_eq!(MaxStale::get(), 2, "The stale limit is two"); - new_test_ext::().execute_with(|| { + test_closure(|| { // page 0 MessageQueue::enqueue_message(msg("weight=4"), Here); MessageQueue::enqueue_message(msg("a"), Here); @@ -319,29 +376,29 @@ fn reaping_overweight_fails_properly() { #[test] fn service_queue_bails() { // Not enough weight for `service_queue_base`. - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("service_queue_base", 2.into_weight()); let mut meter = WeightMeter::from_limit(1.into_weight()); assert_storage_noop!(MessageQueue::service_queue(0u32.into(), &mut meter, Weight::MAX)); - assert!(meter.consumed.is_zero()); + assert!(meter.consumed().is_zero()); }); // Not enough weight for `ready_ring_unknit`. - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("ready_ring_unknit", 2.into_weight()); let mut meter = WeightMeter::from_limit(1.into_weight()); assert_storage_noop!(MessageQueue::service_queue(0u32.into(), &mut meter, Weight::MAX)); - assert!(meter.consumed.is_zero()); + assert!(meter.consumed().is_zero()); }); // Not enough weight for `service_queue_base` and `ready_ring_unknit`. - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("service_queue_base", 2.into_weight()); set_weight("ready_ring_unknit", 2.into_weight()); let mut meter = WeightMeter::from_limit(3.into_weight()); assert_storage_noop!(MessageQueue::service_queue(0.into(), &mut meter, Weight::MAX)); - assert!(meter.consumed.is_zero()); + assert!(meter.consumed().is_zero()); }); } @@ -350,7 +407,7 @@ fn service_page_works() { use super::integration_test::Test; // Run with larger page size. use MessageOrigin::*; use PageExecutionStatus::*; - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("service_page_base_completion", 2.into_weight()); set_weight("service_page_item", 3.into_weight()); @@ -379,7 +436,7 @@ fn service_page_works() { assert_eq!(status, Bailed); } } - assert!(!Pages::::contains_key(Here, 0), "The page got removed"); + assert_pages(&[]); }); } @@ -387,7 +444,7 @@ fn service_page_works() { #[test] fn service_page_bails() { // Not enough weight for `service_page_base_completion`. - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("service_page_base_completion", 2.into_weight()); let mut meter = WeightMeter::from_limit(1.into_weight()); @@ -401,10 +458,10 @@ fn service_page_bails() { &mut meter, Weight::MAX )); - assert!(meter.consumed.is_zero()); + assert!(meter.consumed().is_zero()); }); // Not enough weight for `service_page_base_no_completion`. - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("service_page_base_no_completion", 2.into_weight()); let mut meter = WeightMeter::from_limit(1.into_weight()); @@ -418,13 +475,13 @@ fn service_page_bails() { &mut meter, Weight::MAX )); - assert!(meter.consumed.is_zero()); + assert!(meter.consumed().is_zero()); }); } #[test] fn service_page_item_bails() { - new_test_ext::().execute_with(|| { + test_closure(|| { let _guard = StorageNoopGuard::default(); let (mut page, _) = full_page::(); let mut weight = WeightMeter::from_limit(10.into_weight()); @@ -445,10 +502,61 @@ fn service_page_item_bails() { }); } +#[test] +fn service_page_suspension_works() { + use super::integration_test::Test; // Run with larger page size. + use MessageOrigin::*; + use PageExecutionStatus::*; + + test_closure(|| { + let (page, mut msgs) = full_page::(); + assert!(msgs >= 10, "pre-condition: need at least 10 msgs per page"); + let mut book = book_for::(&page); + Pages::::insert(Here, 0, page); + + // First we process 5 messages from this page. + let mut meter = WeightMeter::from_limit(5.into_weight()); + let (_, status) = + crate::Pallet::::service_page(&Here, &mut book, &mut meter, Weight::MAX); + + assert_eq!(NumMessagesProcessed::take(), 5); + assert!(meter.remaining().is_zero()); + assert_eq!(status, Bailed); // It bailed since weight is missing. + msgs -= 5; + + // Then we pause the queue. + YieldingQueues::set(vec![Here]); + // Noting happens... + for _ in 0..5 { + let (_, status) = crate::Pallet::::service_page( + &Here, + &mut book, + &mut WeightMeter::max_limit(), + Weight::MAX, + ); + assert_eq!(status, NoProgress); + assert!(NumMessagesProcessed::take().is_zero()); + } + + // Resume and process all remaining. + YieldingQueues::take(); + let (_, status) = crate::Pallet::::service_page( + &Here, + &mut book, + &mut WeightMeter::max_limit(), + Weight::MAX, + ); + assert_eq!(status, NoMore); + assert_eq!(NumMessagesProcessed::take(), msgs); + + assert!(Pages::::iter_keys().count().is_zero()); + }); +} + #[test] fn bump_service_head_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { // Create a ready ring with three queues. BookStateFor::::insert(Here, empty_book::()); knit(&Here); @@ -471,41 +579,41 @@ fn bump_service_head_works() { /// `bump_service_head` does nothing when called with an insufficient weight limit. #[test] fn bump_service_head_bails() { - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("bump_service_head", 2.into_weight()); setup_bump_service_head::(0.into(), 10.into()); let _guard = StorageNoopGuard::default(); let mut meter = WeightMeter::from_limit(1.into_weight()); assert!(MessageQueue::bump_service_head(&mut meter).is_none()); - assert_eq!(meter.consumed, 0.into_weight()); + assert_eq!(meter.consumed(), 0.into_weight()); }); } #[test] fn bump_service_head_trivial_works() { - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("bump_service_head", 2.into_weight()); let mut meter = WeightMeter::max_limit(); assert_eq!(MessageQueue::bump_service_head(&mut meter), None, "Cannot bump"); - assert_eq!(meter.consumed, 2.into_weight()); + assert_eq!(meter.consumed(), 2.into_weight()); setup_bump_service_head::(0.into(), 1.into()); assert_eq!(MessageQueue::bump_service_head(&mut meter), Some(0.into())); assert_eq!(ServiceHead::::get().unwrap(), 1.into(), "Bumped the head"); - assert_eq!(meter.consumed, 4.into_weight()); + assert_eq!(meter.consumed(), 4.into_weight()); assert_eq!(MessageQueue::bump_service_head(&mut meter), None, "Cannot bump"); - assert_eq!(meter.consumed, 6.into_weight()); + assert_eq!(meter.consumed(), 6.into_weight()); }); } #[test] fn bump_service_head_no_head_noops() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { // Create a ready ring with three queues. BookStateFor::::insert(Here, empty_book::()); knit(&Here); @@ -524,7 +632,7 @@ fn bump_service_head_no_head_noops() { #[test] fn service_page_item_consumes_correct_weight() { - new_test_ext::().execute_with(|| { + test_closure(|| { let mut page = page::(b"weight=3"); let mut weight = WeightMeter::from_limit(10.into_weight()); let overweight_limit = 0.into_weight(); @@ -541,14 +649,14 @@ fn service_page_item_consumes_correct_weight() { ), ItemExecutionStatus::Executed(true) ); - assert_eq!(weight.consumed, 5.into_weight()); + assert_eq!(weight.consumed(), 5.into_weight()); }); } /// `service_page_item` skips a permanently `Overweight` message and marks it as `unprocessed`. #[test] fn service_page_item_skips_perm_overweight_message() { - new_test_ext::().execute_with(|| { + test_closure(|| { let mut page = page::(b"TooMuch"); let mut weight = WeightMeter::from_limit(2.into_weight()); let overweight_limit = 0.into_weight(); @@ -565,10 +673,10 @@ fn service_page_item_skips_perm_overweight_message() { ), ItemExecutionStatus::Executed(false) ); - assert_eq!(weight.consumed, 2.into_weight()); + assert_eq!(weight.consumed(), 2.into_weight()); assert_last_event::( Event::OverweightEnqueued { - hash: ::Hashing::hash(b"TooMuch"), + id: blake2_256(b"TooMuch"), origin: MessageOrigin::Here, message_index: 0, page_index: 0, @@ -587,7 +695,7 @@ fn service_page_item_skips_perm_overweight_message() { #[test] fn peek_index_works() { use super::integration_test::Test; // Run with larger page size. - new_test_ext::().execute_with(|| { + test_closure(|| { // Fill a page with messages. let (mut page, msgs) = full_page::(); let msg_enc_len = ItemHeader::<::Size>::max_encoded_len() + 4; @@ -608,7 +716,7 @@ fn peek_index_works() { #[test] fn peek_first_and_skip_first_works() { use super::integration_test::Test; // Run with larger page size. - new_test_ext::().execute_with(|| { + test_closure(|| { // Fill a page with messages. let (mut page, msgs) = full_page::(); @@ -631,7 +739,7 @@ fn peek_first_and_skip_first_works() { #[test] fn note_processed_at_pos_works() { use super::integration_test::Test; // Run with larger page size. - new_test_ext::().execute_with(|| { + test_closure(|| { let (mut page, msgs) = full_page::(); for i in 0..msgs { @@ -667,7 +775,7 @@ fn note_processed_at_pos_idempotent() { #[test] fn is_complete_works() { use super::integration_test::Test; // Run with larger page size. - new_test_ext::().execute_with(|| { + test_closure(|| { let (mut page, msgs) = full_page::(); assert!(msgs > 3, "Boring"); let msg_enc_len = ItemHeader::<::Size>::max_encoded_len() + 4; @@ -823,7 +931,7 @@ fn page_from_message_max_len_works() { #[test] fn sweep_queue_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { build_triple_ring(); let book = BookStateFor::::get(Here); @@ -859,7 +967,7 @@ fn sweep_queue_works() { #[test] fn sweep_queue_wraps_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { BookStateFor::::insert(Here, empty_book::()); knit(&Here); @@ -872,14 +980,14 @@ fn sweep_queue_wraps_works() { #[test] fn sweep_queue_invalid_noops() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { assert_storage_noop!(MessageQueue::sweep_queue(Here)); }); } #[test] fn footprint_works() { - new_test_ext::().execute_with(|| { + test_closure(|| { let origin = MessageOrigin::Here; let (page, msgs) = full_page::(); let book = book_for::(&page); @@ -897,7 +1005,7 @@ fn footprint_works() { /// The footprint of an invalid queue is the default footprint. #[test] fn footprint_invalid_works() { - new_test_ext::().execute_with(|| { + test_closure(|| { let origin = MessageOrigin::Here; assert_eq!(MessageQueue::footprint(origin), Default::default()); }) @@ -907,7 +1015,7 @@ fn footprint_invalid_works() { #[test] fn footprint_on_swept_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { let mut book = empty_book::(); book.message_count = 3; book.size = 10; @@ -923,7 +1031,7 @@ fn footprint_on_swept_works() { #[test] fn execute_overweight_works() { - new_test_ext::().execute_with(|| { + test_closure(|| { set_weight("bump_service_head", 1.into_weight()); set_weight("service_queue_base", 1.into_weight()); set_weight("service_page_base_completion", 1.into_weight()); @@ -941,7 +1049,7 @@ fn execute_overweight_works() { assert_eq!(QueueChanges::take(), vec![(origin, 1, 8)]); assert_last_event::( Event::OverweightEnqueued { - hash: ::Hashing::hash(b"weight=6"), + id: blake2_256(b"weight=6"), origin: MessageOrigin::Here, message_index: 0, page_index: 0, @@ -974,6 +1082,121 @@ fn execute_overweight_works() { assert_eq!(consumed, Err(ExecuteOverweightError::NotFound)); assert!(QueueChanges::take().is_empty()); assert!(!Pages::::contains_key(origin, 0), "Page is gone"); + // The book should have been unknit from the ready ring. + assert!(!ServiceHead::::exists(), "No ready book"); + }); +} + +#[test] +fn permanently_overweight_book_unknits() { + use MessageOrigin::*; + + test_closure(|| { + set_weight("bump_service_head", 1.into_weight()); + set_weight("service_queue_base", 1.into_weight()); + set_weight("service_page_base_completion", 1.into_weight()); + + MessageQueue::enqueue_messages([msg("weight=9")].into_iter(), Here); + + // It is the only ready book. + assert_ring(&[Here]); + // Mark the message as overweight. + assert_eq!(MessageQueue::service_queues(8.into_weight()), 4.into_weight()); + assert_last_event::( + Event::OverweightEnqueued { + id: blake2_256(b"weight=9"), + origin: Here, + message_index: 0, + page_index: 0, + } + .into(), + ); + // The book is not ready anymore. + assert_ring(&[]); + assert_eq!(MessagesProcessed::take().len(), 0); + assert_eq!(BookStateFor::::get(Here).message_count, 1); + // Now if we enqueue another message, it will become ready again. + MessageQueue::enqueue_messages([msg("weight=1")].into_iter(), Here); + assert_ring(&[Here]); + assert_eq!(MessageQueue::service_queues(8.into_weight()), 5.into_weight()); + assert_eq!(MessagesProcessed::take().len(), 1); + assert_ring(&[]); + }); +} + +#[test] +fn permanently_overweight_book_unknits_multiple() { + use MessageOrigin::*; + + test_closure(|| { + set_weight("bump_service_head", 1.into_weight()); + set_weight("service_queue_base", 1.into_weight()); + set_weight("service_page_base_completion", 1.into_weight()); + + MessageQueue::enqueue_messages( + [msg("weight=1"), msg("weight=9"), msg("weight=9")].into_iter(), + Here, + ); + + assert_ring(&[Here]); + // Process the first message. + assert_eq!(MessageQueue::service_queues(4.into_weight()), 4.into_weight()); + assert_eq!(num_overweight_enqueued_events(), 0); + assert_eq!(MessagesProcessed::take().len(), 1); + + // Book is still ready since it was not marked as overweight yet. + assert_ring(&[Here]); + assert_eq!(MessageQueue::service_queues(8.into_weight()), 5.into_weight()); + assert_eq!(num_overweight_enqueued_events(), 2); + assert_eq!(MessagesProcessed::take().len(), 0); + // Now it is overweight. + assert_ring(&[]); + // Enqueue another message. + MessageQueue::enqueue_messages([msg("weight=1")].into_iter(), Here); + assert_ring(&[Here]); + assert_eq!(MessageQueue::service_queues(4.into_weight()), 4.into_weight()); + assert_eq!(MessagesProcessed::take().len(), 1); + assert_ring(&[]); + }); +} + +/// We don't want empty books in the ready ring, but if they somehow make their way in there, it +/// should not panic. +#[test] +#[cfg(not(debug_assertions))] // Would trigger a defensive failure otherwise. +fn ready_but_empty_does_not_panic() { + use MessageOrigin::*; + + test_closure(|| { + BookStateFor::::insert(Here, empty_book::()); + BookStateFor::::insert(There, empty_book::()); + + knit(&Here); + knit(&There); + assert_ring(&[Here, There]); + + assert_eq!(MessageQueue::service_queues(Weight::MAX), 0.into_weight()); + assert_ring(&[]); + }); +} + +/// We don't want permanently books in the ready ring, but if they somehow make their way in there, +/// it should not panic. +#[test] +#[cfg(not(debug_assertions))] // Would trigger a defensive failure otherwise. +fn ready_but_perm_overweight_does_not_panic() { + use MessageOrigin::*; + + test_closure(|| { + MessageQueue::enqueue_message(msg("weight=9"), Here); + assert_eq!(MessageQueue::service_queues(8.into_weight()), 0.into_weight()); + assert_ring(&[]); + // Force it back into the ready ring. + knit(&Here); + assert_ring(&[Here]); + assert_eq!(MessageQueue::service_queues(Weight::MAX), 0.into_weight()); + // Unready again. + assert_ring(&[]); }); } @@ -984,7 +1207,7 @@ fn execute_overweight_works() { fn ready_ring_knit_basic_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { BookStateFor::::insert(Here, empty_book::()); for i in 0..10 { @@ -1004,12 +1227,15 @@ fn ready_ring_knit_basic_works() { fn ready_ring_knit_and_unknit_works() { use MessageOrigin::*; - new_test_ext::().execute_with(|| { + test_closure(|| { // Place three queues into the storage. BookStateFor::::insert(Here, empty_book::()); BookStateFor::::insert(There, empty_book::()); BookStateFor::::insert(Everywhere(0), empty_book::()); + // Pausing should make no difference: + PausedQueues::set(vec![Here, There, Everywhere(0)]); + // Knit them into the ready ring. assert_ring(&[]); knit(&Here); @@ -1035,7 +1261,7 @@ fn enqueue_message_works() { let max_msg_per_page = ::HeapSize::get() as u64 / (ItemHeader::<::Size>::max_encoded_len() as u64 + 1); - new_test_ext::().execute_with(|| { + test_closure(|| { // Enqueue messages which should fill three pages. let n = max_msg_per_page * 3; for i in 1..=n { @@ -1065,7 +1291,7 @@ fn enqueue_messages_works() { let max_msg_per_page = ::HeapSize::get() as u64 / (ItemHeader::<::Size>::max_encoded_len() as u64 + 1); - new_test_ext::().execute_with(|| { + test_closure(|| { // Enqueue messages which should fill three pages. let n = max_msg_per_page * 3; let msgs = vec![msg("a"); n as usize]; @@ -1090,3 +1316,144 @@ fn enqueue_messages_works() { assert_eq!(book.count as usize, Pages::::iter().count()); }); } + +#[test] +fn service_queues_suspend_works() { + use MessageOrigin::*; + test_closure(|| { + MessageQueue::enqueue_messages(vec![msg("a"), msg("ab"), msg("abc")].into_iter(), Here); + MessageQueue::enqueue_messages(vec![msg("x"), msg("xy"), msg("xyz")].into_iter(), There); + assert_eq!(QueueChanges::take(), vec![(Here, 3, 6), (There, 3, 6)]); + + // Pause `Here` - execution starts `There`. + PausedQueues::set(vec![Here]); + assert_eq!( + (true, false), + ( + ::QueuePausedQuery::is_paused(&Here), + ::QueuePausedQuery::is_paused(&There) + ) + ); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("x"), There)]); + assert_eq!(QueueChanges::take(), vec![(There, 2, 5)]); + + // Unpause `Here` - execution continues `There`. + PausedQueues::take(); + assert_eq!( + (false, false), + ( + ::QueuePausedQuery::is_paused(&Here), + ::QueuePausedQuery::is_paused(&There) + ) + ); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("xy"), There)]); + assert_eq!(QueueChanges::take(), vec![(There, 1, 3)]); + + // Now it swaps to `Here`. + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("a"), Here)]); + assert_eq!(QueueChanges::take(), vec![(Here, 2, 5)]); + + // Pause `There` - execution continues `Here`. + PausedQueues::set(vec![There]); + assert_eq!( + (false, true), + ( + ::QueuePausedQuery::is_paused(&Here), + ::QueuePausedQuery::is_paused(&There) + ) + ); + assert_eq!(MessageQueue::service_queues(1.into_weight()), 1.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("ab"), Here)]); + assert_eq!(QueueChanges::take(), vec![(Here, 1, 3)]); + + // Unpause `There` and service all remaining messages. + PausedQueues::take(); + assert_eq!( + (false, false), + ( + ::QueuePausedQuery::is_paused(&Here), + ::QueuePausedQuery::is_paused(&There) + ) + ); + assert_eq!(MessageQueue::service_queues(2.into_weight()), 2.into_weight()); + assert_eq!(MessagesProcessed::take(), vec![(vmsg("abc"), Here), (vmsg("xyz"), There)]); + assert_eq!(QueueChanges::take(), vec![(Here, 0, 0), (There, 0, 0)]); + }); +} + +/// Tests that manual overweight execution on a suspended queue errors with `QueueSuspended`. +#[test] +fn execute_overweight_respects_suspension() { + test_closure(|| { + let origin = MessageOrigin::Here; + MessageQueue::enqueue_message(msg("weight=5"), origin); + // Mark the message as permanently overweight. + MessageQueue::service_queues(4.into_weight()); + assert_last_event::( + Event::OverweightEnqueued { + id: blake2_256(b"weight=5"), + origin, + message_index: 0, + page_index: 0, + } + .into(), + ); + PausedQueues::set(vec![origin]); + assert!(::QueuePausedQuery::is_paused(&origin)); + + // Execution should fail. + assert_eq!( + ::execute_overweight(Weight::MAX, (origin, 0, 0)), + Err(ExecuteOverweightError::QueuePaused) + ); + + PausedQueues::take(); + assert!(!::QueuePausedQuery::is_paused(&origin)); + + // Execution should work again with same args. + assert_ok!(::execute_overweight( + Weight::MAX, + (origin, 0, 0) + )); + + assert_last_event::( + Event::Processed { + id: blake2_256(b"weight=5"), + origin, + weight_used: 5.into_weight(), + success: true, + } + .into(), + ); + }); +} + +#[test] +fn service_queue_suspension_ready_ring_works() { + test_closure(|| { + let origin = MessageOrigin::Here; + PausedQueues::set(vec![origin]); + MessageQueue::enqueue_message(msg("weight=5"), origin); + + MessageQueue::service_queues(Weight::MAX); + // It did not execute but is in the ready ring. + assert!(System::events().is_empty(), "Paused"); + assert_ring(&[origin]); + + // Now when we un-pause, it will execute. + PausedQueues::take(); + MessageQueue::service_queues(Weight::MAX); + assert_last_event::( + Event::Processed { + id: blake2_256(b"weight=5"), + origin, + weight_used: 5.into_weight(), + success: true, + } + .into(), + ); + }); +} diff --git a/frame/message-queue/src/weights.rs b/frame/message-queue/src/weights.rs index cd9268ffde22..e86f23e274ff 100644 --- a/frame/message-queue/src/weights.rs +++ b/frame/message-queue/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,33 +18,37 @@ //! Autogenerated weights for pallet_message_queue //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-12-08, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// /home/benchbot/cargo_target_dir/production/substrate +// ./target/production/substrate // benchmark // pallet +// --chain=dev // --steps=50 // --repeat=20 +// --pallet=pallet_message_queue +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json -// --pallet=pallet_message_queue -// --chain=dev -// --header=./HEADER-APACHE2 // --output=./frame/message-queue/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_message_queue. pub trait WeightInfo { @@ -63,154 +67,244 @@ pub trait WeightInfo { /// Weights for pallet_message_queue using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: MessageQueue ServiceHead (r:1 w:0) - // Storage: MessageQueue BookStateFor (r:2 w:2) + /// Storage: MessageQueue ServiceHead (r:1 w:0) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: MessageQueue BookStateFor (r:2 w:2) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn ready_ring_knit() -> Weight { - // Minimum execution time: 12_330 nanoseconds. - Weight::from_ref_time(12_711_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `267` + // Estimated: `6038` + // Minimum execution time: 12_025_000 picoseconds. + Weight::from_parts(12_597_000, 6038) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: MessageQueue BookStateFor (r:2 w:2) - // Storage: MessageQueue ServiceHead (r:1 w:1) + /// Storage: MessageQueue BookStateFor (r:2 w:2) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: MessageQueue ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn ready_ring_unknit() -> Weight { - // Minimum execution time: 12_322 nanoseconds. - Weight::from_ref_time(12_560_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `267` + // Estimated: `6038` + // Minimum execution time: 11_563_000 picoseconds. + Weight::from_parts(11_785_000, 6038) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - // Storage: MessageQueue BookStateFor (r:1 w:1) + /// Storage: MessageQueue BookStateFor (r:1 w:1) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn service_queue_base() -> Weight { - // Minimum execution time: 4_652 nanoseconds. - Weight::from_ref_time(4_848_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `3514` + // Minimum execution time: 4_467_000 picoseconds. + Weight::from_parts(4_655_000, 3514) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: MessageQueue Pages (r:1 w:1) + /// Storage: MessageQueue Pages (r:1 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65584), added: 68059, mode: MaxEncodedLen) fn service_page_base_completion() -> Weight { - // Minimum execution time: 7_115 nanoseconds. - Weight::from_ref_time(7_407_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `147` + // Estimated: `69049` + // Minimum execution time: 6_103_000 picoseconds. + Weight::from_parts(6_254_000, 69049) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: MessageQueue Pages (r:1 w:1) + /// Storage: MessageQueue Pages (r:1 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65584), added: 68059, mode: MaxEncodedLen) fn service_page_base_no_completion() -> Weight { - // Minimum execution time: 6_974 nanoseconds. - Weight::from_ref_time(7_200_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `147` + // Estimated: `69049` + // Minimum execution time: 6_320_000 picoseconds. + Weight::from_parts(6_565_000, 69049) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } fn service_page_item() -> Weight { - // Minimum execution time: 79_657 nanoseconds. - Weight::from_ref_time(80_050_000) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 66_062_000 picoseconds. + Weight::from_parts(66_371_000, 0) } - // Storage: MessageQueue ServiceHead (r:1 w:1) - // Storage: MessageQueue BookStateFor (r:1 w:0) + /// Storage: MessageQueue ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: MessageQueue BookStateFor (r:1 w:0) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn bump_service_head() -> Weight { - // Minimum execution time: 7_598 nanoseconds. - Weight::from_ref_time(8_118_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3514` + // Minimum execution time: 6_788_000 picoseconds. + Weight::from_parts(7_176_000, 3514) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: MessageQueue BookStateFor (r:1 w:1) - // Storage: MessageQueue Pages (r:1 w:1) + /// Storage: MessageQueue BookStateFor (r:1 w:1) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:1 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65584), added: 68059, mode: MaxEncodedLen) fn reap_page() -> Weight { - // Minimum execution time: 60_562 nanoseconds. - Weight::from_ref_time(61_430_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `65744` + // Estimated: `69049` + // Minimum execution time: 52_865_000 picoseconds. + Weight::from_parts(54_398_000, 69049) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: MessageQueue BookStateFor (r:1 w:1) - // Storage: MessageQueue Pages (r:1 w:1) + /// Storage: MessageQueue BookStateFor (r:1 w:1) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:1 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65584), added: 68059, mode: MaxEncodedLen) fn execute_overweight_page_removed() -> Weight { - // Minimum execution time: 74_582 nanoseconds. - Weight::from_ref_time(75_445_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `65744` + // Estimated: `69049` + // Minimum execution time: 69_168_000 picoseconds. + Weight::from_parts(70_560_000, 69049) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: MessageQueue BookStateFor (r:1 w:1) - // Storage: MessageQueue Pages (r:1 w:1) + /// Storage: MessageQueue BookStateFor (r:1 w:1) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:1 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65584), added: 68059, mode: MaxEncodedLen) fn execute_overweight_page_updated() -> Weight { - // Minimum execution time: 87_526 nanoseconds. - Weight::from_ref_time(88_055_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `65744` + // Estimated: `69049` + // Minimum execution time: 80_947_000 picoseconds. + Weight::from_parts(82_715_000, 69049) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: MessageQueue ServiceHead (r:1 w:0) - // Storage: MessageQueue BookStateFor (r:2 w:2) + /// Storage: MessageQueue ServiceHead (r:1 w:0) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: MessageQueue BookStateFor (r:2 w:2) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn ready_ring_knit() -> Weight { - // Minimum execution time: 12_330 nanoseconds. - Weight::from_ref_time(12_711_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `267` + // Estimated: `6038` + // Minimum execution time: 12_025_000 picoseconds. + Weight::from_parts(12_597_000, 6038) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: MessageQueue BookStateFor (r:2 w:2) - // Storage: MessageQueue ServiceHead (r:1 w:1) + /// Storage: MessageQueue BookStateFor (r:2 w:2) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: MessageQueue ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn ready_ring_unknit() -> Weight { - // Minimum execution time: 12_322 nanoseconds. - Weight::from_ref_time(12_560_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `267` + // Estimated: `6038` + // Minimum execution time: 11_563_000 picoseconds. + Weight::from_parts(11_785_000, 6038) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } - // Storage: MessageQueue BookStateFor (r:1 w:1) + /// Storage: MessageQueue BookStateFor (r:1 w:1) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn service_queue_base() -> Weight { - // Minimum execution time: 4_652 nanoseconds. - Weight::from_ref_time(4_848_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `3514` + // Minimum execution time: 4_467_000 picoseconds. + Weight::from_parts(4_655_000, 3514) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: MessageQueue Pages (r:1 w:1) + /// Storage: MessageQueue Pages (r:1 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65584), added: 68059, mode: MaxEncodedLen) fn service_page_base_completion() -> Weight { - // Minimum execution time: 7_115 nanoseconds. - Weight::from_ref_time(7_407_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `147` + // Estimated: `69049` + // Minimum execution time: 6_103_000 picoseconds. + Weight::from_parts(6_254_000, 69049) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: MessageQueue Pages (r:1 w:1) + /// Storage: MessageQueue Pages (r:1 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65584), added: 68059, mode: MaxEncodedLen) fn service_page_base_no_completion() -> Weight { - // Minimum execution time: 6_974 nanoseconds. - Weight::from_ref_time(7_200_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `147` + // Estimated: `69049` + // Minimum execution time: 6_320_000 picoseconds. + Weight::from_parts(6_565_000, 69049) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn service_page_item() -> Weight { - // Minimum execution time: 79_657 nanoseconds. - Weight::from_ref_time(80_050_000) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 66_062_000 picoseconds. + Weight::from_parts(66_371_000, 0) } - // Storage: MessageQueue ServiceHead (r:1 w:1) - // Storage: MessageQueue BookStateFor (r:1 w:0) + /// Storage: MessageQueue ServiceHead (r:1 w:1) + /// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: MessageQueue BookStateFor (r:1 w:0) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) fn bump_service_head() -> Weight { - // Minimum execution time: 7_598 nanoseconds. - Weight::from_ref_time(8_118_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3514` + // Minimum execution time: 6_788_000 picoseconds. + Weight::from_parts(7_176_000, 3514) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: MessageQueue BookStateFor (r:1 w:1) - // Storage: MessageQueue Pages (r:1 w:1) + /// Storage: MessageQueue BookStateFor (r:1 w:1) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:1 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65584), added: 68059, mode: MaxEncodedLen) fn reap_page() -> Weight { - // Minimum execution time: 60_562 nanoseconds. - Weight::from_ref_time(61_430_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `65744` + // Estimated: `69049` + // Minimum execution time: 52_865_000 picoseconds. + Weight::from_parts(54_398_000, 69049) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: MessageQueue BookStateFor (r:1 w:1) - // Storage: MessageQueue Pages (r:1 w:1) + /// Storage: MessageQueue BookStateFor (r:1 w:1) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:1 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65584), added: 68059, mode: MaxEncodedLen) fn execute_overweight_page_removed() -> Weight { - // Minimum execution time: 74_582 nanoseconds. - Weight::from_ref_time(75_445_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `65744` + // Estimated: `69049` + // Minimum execution time: 69_168_000 picoseconds. + Weight::from_parts(70_560_000, 69049) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: MessageQueue BookStateFor (r:1 w:1) - // Storage: MessageQueue Pages (r:1 w:1) + /// Storage: MessageQueue BookStateFor (r:1 w:1) + /// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: MessageQueue Pages (r:1 w:1) + /// Proof: MessageQueue Pages (max_values: None, max_size: Some(65584), added: 68059, mode: MaxEncodedLen) fn execute_overweight_page_updated() -> Weight { - // Minimum execution time: 87_526 nanoseconds. - Weight::from_ref_time(88_055_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `65744` + // Estimated: `69049` + // Minimum execution time: 80_947_000 picoseconds. + Weight::from_parts(82_715_000, 69049) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } } diff --git a/frame/multisig/Cargo.toml b/frame/multisig/Cargo.toml index 5cd744124ef2..2afce62b0596 100644 --- a/frame/multisig/Cargo.toml +++ b/frame/multisig/Cargo.toml @@ -13,21 +13,21 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } # third party log = { version = "0.4.17", default-features = false } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/multisig/README.md b/frame/multisig/README.md index 4eab00d10820..5f320377d345 100644 --- a/frame/multisig/README.md +++ b/frame/multisig/README.md @@ -1,8 +1,8 @@ # Multisig Module A module for doing multisig dispatch. -- [`multisig::Config`](https://docs.rs/pallet-multisig/latest/pallet_multisig/trait.Config.html) -- [`Call`](https://docs.rs/pallet-multisig/latest/pallet_multisig/enum.Call.html) +- [`Config`](https://docs.rs/pallet-multisig/latest/pallet_multisig/pallet/trait.Config.html) +- [`Call`](https://docs.rs/pallet-multisig/latest/pallet_multisig/pallet/enum.Call.html) ## Overview diff --git a/frame/multisig/src/benchmarking.rs b/frame/multisig/src/benchmarking.rs index d5faf9ae8ac1..ebe19df5dc43 100644 --- a/frame/multisig/src/benchmarking.rs +++ b/frame/multisig/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::{account, benchmarks}; +use frame_benchmarking::v1::{account, benchmarks}; use frame_system::RawOrigin; use sp_runtime::traits::Bounded; diff --git a/frame/multisig/src/lib.rs b/frame/multisig/src/lib.rs index 076a289e0651..ab117315f898 100644 --- a/frame/multisig/src/lib.rs +++ b/frame/multisig/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,9 +39,6 @@ //! number of signed origins. //! * `approve_as_multi` - Approve a call from a composite origin. //! * `cancel_as_multi` - Cancel a call from a composite origin. -//! -//! [`Call`]: ./enum.Call.html -//! [`Config`]: ./trait.Config.html // Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] @@ -62,7 +59,7 @@ use frame_support::{ weights::Weight, BoundedVec, RuntimeDebug, }; -use frame_system::{self as system, RawOrigin}; +use frame_system::{self as system, pallet_prelude::BlockNumberFor, RawOrigin}; use scale_info::TypeInfo; use sp_io::hashing::blake2_256; use sp_runtime::{ @@ -175,7 +172,6 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); @@ -187,7 +183,7 @@ pub mod pallet { T::AccountId, Blake2_128Concat, [u8; 32], - Multisig, T::AccountId, T::MaxSignatories>, + Multisig, BalanceOf, T::AccountId, T::MaxSignatories>, >; #[pallet::error] @@ -230,14 +226,14 @@ pub mod pallet { /// A multisig operation has been approved by someone. MultisigApproval { approving: T::AccountId, - timepoint: Timepoint, + timepoint: Timepoint>, multisig: T::AccountId, call_hash: CallHash, }, /// A multisig operation has been executed. MultisigExecuted { approving: T::AccountId, - timepoint: Timepoint, + timepoint: Timepoint>, multisig: T::AccountId, call_hash: CallHash, result: DispatchResult, @@ -245,7 +241,7 @@ pub mod pallet { /// A multisig operation has been cancelled. MultisigCancelled { cancelling: T::AccountId, - timepoint: Timepoint, + timepoint: Timepoint>, multisig: T::AccountId, call_hash: CallHash, }, @@ -266,12 +262,8 @@ pub mod pallet { /// /// Result is equivalent to the dispatched result. /// - /// # + /// ## Complexity /// O(Z + C) where Z is the length of the call and C its execution weight. - /// ------------------------------- - /// - DB Weight: None - /// - Plus Call Weight - /// # #[pallet::call_index(0)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); @@ -347,7 +339,7 @@ pub mod pallet { /// on success, result is `Ok` and the result from the interior call, if it was executed, /// may be found in the deposited `MultisigExecuted` event. /// - /// # + /// ## Complexity /// - `O(S + Z + Call)`. /// - Up to one balance-reserve or unreserve operation. /// - One passthrough operation, one insert, both `O(S)` where `S` is the number of @@ -360,12 +352,6 @@ pub mod pallet { /// - The weight of the `call`. /// - Storage: inserts one item, value size bounded by `MaxSignatories`, with a deposit /// taken for its lifetime of `DepositBase + threshold * DepositFactor`. - /// ------------------------------- - /// - DB Weight: - /// - Reads: Multisig Storage, [Caller Account] - /// - Writes: Multisig Storage, [Caller Account] - /// - Plus Call Weight - /// # #[pallet::call_index(1)] #[pallet::weight({ let s = other_signatories.len() as u32; @@ -380,7 +366,7 @@ pub mod pallet { origin: OriginFor, threshold: u16, other_signatories: Vec, - maybe_timepoint: Option>, + maybe_timepoint: Option>>, call: Box<::RuntimeCall>, max_weight: Weight, ) -> DispatchResultWithPostInfo { @@ -414,7 +400,7 @@ pub mod pallet { /// /// NOTE: If this is the final approval, you will want to use `as_multi` instead. /// - /// # + /// ## Complexity /// - `O(S)`. /// - Up to one balance-reserve or unreserve operation. /// - One passthrough operation, one insert, both `O(S)` where `S` is the number of @@ -425,11 +411,6 @@ pub mod pallet { /// - One event. /// - Storage: inserts one item, value size bounded by `MaxSignatories`, with a deposit /// taken for its lifetime of `DepositBase + threshold * DepositFactor`. - /// ---------------------------------- - /// - DB Weight: - /// - Read: Multisig Storage, [Caller Account] - /// - Write: Multisig Storage, [Caller Account] - /// # #[pallet::call_index(2)] #[pallet::weight({ let s = other_signatories.len() as u32; @@ -442,7 +423,7 @@ pub mod pallet { origin: OriginFor, threshold: u16, other_signatories: Vec, - maybe_timepoint: Option>, + maybe_timepoint: Option>>, call_hash: [u8; 32], max_weight: Weight, ) -> DispatchResultWithPostInfo { @@ -469,7 +450,7 @@ pub mod pallet { /// transaction for this dispatch. /// - `call_hash`: The hash of the call to be executed. /// - /// # + /// ## Complexity /// - `O(S)`. /// - Up to one balance-reserve or unreserve operation. /// - One passthrough operation, one insert, both `O(S)` where `S` is the number of @@ -478,18 +459,13 @@ pub mod pallet { /// - One event. /// - I/O: 1 read `O(S)`, one remove. /// - Storage: removes one item. - /// ---------------------------------- - /// - DB Weight: - /// - Read: Multisig Storage, [Caller Account], Refund Account - /// - Write: Multisig Storage, [Caller Account], Refund Account - /// # #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::cancel_as_multi(other_signatories.len() as u32))] pub fn cancel_as_multi( origin: OriginFor, threshold: u16, other_signatories: Vec, - timepoint: Timepoint, + timepoint: Timepoint>, call_hash: [u8; 32], ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -535,7 +511,7 @@ impl Pallet { who: T::AccountId, threshold: u16, other_signatories: Vec, - maybe_timepoint: Option>, + maybe_timepoint: Option>>, call_or_hash: CallOrHash, max_weight: Weight, ) -> DispatchResultWithPostInfo { @@ -661,7 +637,7 @@ impl Pallet { } /// The current `Timepoint`. - pub fn timepoint() -> Timepoint { + pub fn timepoint() -> Timepoint> { Timepoint { height: >::block_number(), index: >::extrinsic_index().unwrap_or_default(), diff --git a/frame/multisig/src/migrations.rs b/frame/multisig/src/migrations.rs index 5085297cde43..298e73c5d757 100644 --- a/frame/multisig/src/migrations.rs +++ b/frame/multisig/src/migrations.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,7 +43,7 @@ pub mod v1 { pub struct MigrateToV1(sp_std::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV1 { #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { let onchain = Pallet::::on_chain_storage_version(); ensure!(onchain < 1, "this migration can be deleted"); @@ -72,7 +72,7 @@ pub mod v1 { } #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), &'static str> { + fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { let onchain = Pallet::::on_chain_storage_version(); ensure!(onchain < 2, "this migration needs to be removed"); ensure!(onchain == 1, "this migration needs to be run"); diff --git a/frame/multisig/src/tests.rs b/frame/multisig/src/tests.rs index 13493587caa2..8e704b923742 100644 --- a/frame/multisig/src/tests.rs +++ b/frame/multisig/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,20 +28,16 @@ use frame_support::{ }; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, TokenError, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Multisig: pallet_multisig::{Pallet, Call, Storage, Event}, } @@ -53,14 +49,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -84,6 +79,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } pub struct TestBaseCallFilter; @@ -107,10 +106,10 @@ impl Config for Test { type WeightInfo = (); } -use pallet_balances::{Call as BalancesCall, Error as BalancesError}; +use pallet_balances::Call as BalancesCall; pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 2)], } @@ -126,16 +125,16 @@ fn now() -> Timepoint { } fn call_transfer(dest: u64, value: u64) -> Box { - Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest, value })) + Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value })) } #[test] fn multisig_deposit_is_taken_and_returned() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5)); let call = call_transfer(6, 15); let call_weight = call.get_dispatch_info().weight; @@ -196,9 +195,9 @@ fn cancel_multisig_returns_deposit() { fn timepoint_checking_works() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5)); let call = call_transfer(6, 15); let hash = blake2_256(&call.encode()); @@ -254,9 +253,9 @@ fn timepoint_checking_works() { fn multisig_2_of_3_works() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5)); let call = call_transfer(6, 15); let call_weight = call.get_dispatch_info().weight; @@ -287,9 +286,9 @@ fn multisig_2_of_3_works() { fn multisig_3_of_3_works() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 3); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5)); let call = call_transfer(6, 15); let call_weight = call.get_dispatch_info().weight; @@ -357,9 +356,9 @@ fn cancel_multisig_works() { fn multisig_2_of_3_as_multi_works() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5)); let call = call_transfer(6, 15); let call_weight = call.get_dispatch_info().weight; @@ -389,9 +388,9 @@ fn multisig_2_of_3_as_multi_works() { fn multisig_2_of_3_as_multi_with_many_calls_works() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5)); let call1 = call_transfer(6, 10); let call1_weight = call1.get_dispatch_info().weight; @@ -440,9 +439,9 @@ fn multisig_2_of_3_as_multi_with_many_calls_works() { fn multisig_2_of_3_cannot_reissue_same_call() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5)); let call = call_transfer(6, 10); let call_weight = call.get_dispatch_info().weight; @@ -482,14 +481,13 @@ fn multisig_2_of_3_cannot_reissue_same_call() { call_weight )); - let err = DispatchError::from(BalancesError::::InsufficientBalance).stripped(); System::assert_last_event( pallet_multisig::Event::MultisigExecuted { approving: 3, timepoint: now(), multisig: multi, call_hash: hash, - result: Err(err), + result: Err(TokenError::FundsUnavailable.into()), } .into(), ); @@ -593,9 +591,9 @@ fn duplicate_approvals_are_ignored() { fn multisig_1_of_3_works() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 1); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5)); let call = call_transfer(6, 15); let hash = blake2_256(&call.encode()); @@ -646,9 +644,9 @@ fn multisig_filters() { fn weight_check_works() { new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 2); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5)); let call = call_transfer(6, 15); assert_ok!(Multisig::as_multi( @@ -682,9 +680,9 @@ fn multisig_handles_no_preimage_after_all_approve() { // the call will go through. new_test_ext().execute_with(|| { let multi = Multisig::multi_account_id(&[1, 2, 3][..], 3); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(2), multi, 5)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(2), multi, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), multi, 5)); let call = call_transfer(6, 15); let call_weight = call.get_dispatch_info().weight; diff --git a/frame/multisig/src/weights.rs b/frame/multisig/src/weights.rs index 1f435cb9f908..7b87d258d383 100644 --- a/frame/multisig/src/weights.rs +++ b/frame/multisig/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_multisig //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_multisig +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_multisig. pub trait WeightInfo { @@ -61,82 +66,108 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { /// The range of component `z` is `[0, 10000]`. fn as_multi_threshold_1(z: u32, ) -> Weight { - // Minimum execution time: 20_447 nanoseconds. - Weight::from_ref_time(20_896_236 as u64) - // Standard Error: 2 - .saturating_add(Weight::from_ref_time(568 as u64).saturating_mul(z as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 13_452_000 picoseconds. + Weight::from_parts(14_425_869, 0) + // Standard Error: 4 + .saturating_add(Weight::from_parts(493, 0).saturating_mul(z.into())) } - // Storage: Multisig Multisigs (r:1 w:1) - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) + /// Storage: Multisig Multisigs (r:1 w:1) + /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { - // Minimum execution time: 54_987 nanoseconds. - Weight::from_ref_time(42_525_077 as u64) - // Standard Error: 562 - .saturating_add(Weight::from_ref_time(136_064 as u64).saturating_mul(s as u64)) - // Standard Error: 5 - .saturating_add(Weight::from_ref_time(1_508 as u64).saturating_mul(z as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `301 + s * (2 ±0)` + // Estimated: `6811` + // Minimum execution time: 46_012_000 picoseconds. + Weight::from_parts(34_797_344, 6811) + // Standard Error: 833 + .saturating_add(Weight::from_parts(127_671, 0).saturating_mul(s.into())) + // Standard Error: 8 + .saturating_add(Weight::from_parts(1_498, 0).saturating_mul(z.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Multisig Multisigs (r:1 w:1) + /// Storage: Multisig Multisigs (r:1 w:1) + /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) /// The range of component `s` is `[3, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { - // Minimum execution time: 42_573 nanoseconds. - Weight::from_ref_time(30_585_734 as u64) + // Proof Size summary in bytes: + // Measured: `320` + // Estimated: `6811` + // Minimum execution time: 29_834_000 picoseconds. + Weight::from_parts(20_189_154, 6811) // Standard Error: 637 - .saturating_add(Weight::from_ref_time(128_012 as u64).saturating_mul(s as u64)) + .saturating_add(Weight::from_parts(110_080, 0).saturating_mul(s.into())) // Standard Error: 6 - .saturating_add(Weight::from_ref_time(1_507 as u64).saturating_mul(z as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + .saturating_add(Weight::from_parts(1_483, 0).saturating_mul(z.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Multisig Multisigs (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Multisig Multisigs (r:1 w:1) + /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { - // Minimum execution time: 57_143 nanoseconds. - Weight::from_ref_time(43_921_674 as u64) - // Standard Error: 704 - .saturating_add(Weight::from_ref_time(153_474 as u64).saturating_mul(s as u64)) - // Standard Error: 6 - .saturating_add(Weight::from_ref_time(1_536 as u64).saturating_mul(z as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `426 + s * (33 ±0)` + // Estimated: `6811` + // Minimum execution time: 51_464_000 picoseconds. + Weight::from_parts(39_246_644, 6811) + // Standard Error: 1_251 + .saturating_add(Weight::from_parts(143_313, 0).saturating_mul(s.into())) + // Standard Error: 12 + .saturating_add(Weight::from_parts(1_523, 0).saturating_mul(z.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Multisig Multisigs (r:1 w:1) - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) + /// Storage: Multisig Multisigs (r:1 w:1) + /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) /// The range of component `s` is `[2, 100]`. fn approve_as_multi_create(s: u32, ) -> Weight { - // Minimum execution time: 39_088 nanoseconds. - Weight::from_ref_time(41_258_697 as u64) - // Standard Error: 1_038 - .saturating_add(Weight::from_ref_time(126_040 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `301 + s * (2 ±0)` + // Estimated: `6811` + // Minimum execution time: 33_275_000 picoseconds. + Weight::from_parts(34_073_221, 6811) + // Standard Error: 1_163 + .saturating_add(Weight::from_parts(124_815, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Multisig Multisigs (r:1 w:1) + /// Storage: Multisig Multisigs (r:1 w:1) + /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { - // Minimum execution time: 26_872 nanoseconds. - Weight::from_ref_time(28_625_218 as u64) - // Standard Error: 793 - .saturating_add(Weight::from_ref_time(128_542 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `320` + // Estimated: `6811` + // Minimum execution time: 18_411_000 picoseconds. + Weight::from_parts(19_431_787, 6811) + // Standard Error: 694 + .saturating_add(Weight::from_parts(107_220, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Multisig Multisigs (r:1 w:1) + /// Storage: Multisig Multisigs (r:1 w:1) + /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { - // Minimum execution time: 37_636 nanoseconds. - Weight::from_ref_time(39_614_705 as u64) - // Standard Error: 850 - .saturating_add(Weight::from_ref_time(136_222 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `492 + s * (1 ±0)` + // Estimated: `6811` + // Minimum execution time: 33_985_000 picoseconds. + Weight::from_parts(35_547_970, 6811) + // Standard Error: 1_135 + .saturating_add(Weight::from_parts(116_537, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } } @@ -144,81 +175,107 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { /// The range of component `z` is `[0, 10000]`. fn as_multi_threshold_1(z: u32, ) -> Weight { - // Minimum execution time: 20_447 nanoseconds. - Weight::from_ref_time(20_896_236 as u64) - // Standard Error: 2 - .saturating_add(Weight::from_ref_time(568 as u64).saturating_mul(z as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 13_452_000 picoseconds. + Weight::from_parts(14_425_869, 0) + // Standard Error: 4 + .saturating_add(Weight::from_parts(493, 0).saturating_mul(z.into())) } - // Storage: Multisig Multisigs (r:1 w:1) - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) + /// Storage: Multisig Multisigs (r:1 w:1) + /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_create(s: u32, z: u32, ) -> Weight { - // Minimum execution time: 54_987 nanoseconds. - Weight::from_ref_time(42_525_077 as u64) - // Standard Error: 562 - .saturating_add(Weight::from_ref_time(136_064 as u64).saturating_mul(s as u64)) - // Standard Error: 5 - .saturating_add(Weight::from_ref_time(1_508 as u64).saturating_mul(z as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `301 + s * (2 ±0)` + // Estimated: `6811` + // Minimum execution time: 46_012_000 picoseconds. + Weight::from_parts(34_797_344, 6811) + // Standard Error: 833 + .saturating_add(Weight::from_parts(127_671, 0).saturating_mul(s.into())) + // Standard Error: 8 + .saturating_add(Weight::from_parts(1_498, 0).saturating_mul(z.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Multisig Multisigs (r:1 w:1) + /// Storage: Multisig Multisigs (r:1 w:1) + /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) /// The range of component `s` is `[3, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_approve(s: u32, z: u32, ) -> Weight { - // Minimum execution time: 42_573 nanoseconds. - Weight::from_ref_time(30_585_734 as u64) + // Proof Size summary in bytes: + // Measured: `320` + // Estimated: `6811` + // Minimum execution time: 29_834_000 picoseconds. + Weight::from_parts(20_189_154, 6811) // Standard Error: 637 - .saturating_add(Weight::from_ref_time(128_012 as u64).saturating_mul(s as u64)) + .saturating_add(Weight::from_parts(110_080, 0).saturating_mul(s.into())) // Standard Error: 6 - .saturating_add(Weight::from_ref_time(1_507 as u64).saturating_mul(z as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + .saturating_add(Weight::from_parts(1_483, 0).saturating_mul(z.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Multisig Multisigs (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Multisig Multisigs (r:1 w:1) + /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `s` is `[2, 100]`. /// The range of component `z` is `[0, 10000]`. fn as_multi_complete(s: u32, z: u32, ) -> Weight { - // Minimum execution time: 57_143 nanoseconds. - Weight::from_ref_time(43_921_674 as u64) - // Standard Error: 704 - .saturating_add(Weight::from_ref_time(153_474 as u64).saturating_mul(s as u64)) - // Standard Error: 6 - .saturating_add(Weight::from_ref_time(1_536 as u64).saturating_mul(z as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `426 + s * (33 ±0)` + // Estimated: `6811` + // Minimum execution time: 51_464_000 picoseconds. + Weight::from_parts(39_246_644, 6811) + // Standard Error: 1_251 + .saturating_add(Weight::from_parts(143_313, 0).saturating_mul(s.into())) + // Standard Error: 12 + .saturating_add(Weight::from_parts(1_523, 0).saturating_mul(z.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Multisig Multisigs (r:1 w:1) - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) + /// Storage: Multisig Multisigs (r:1 w:1) + /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) /// The range of component `s` is `[2, 100]`. fn approve_as_multi_create(s: u32, ) -> Weight { - // Minimum execution time: 39_088 nanoseconds. - Weight::from_ref_time(41_258_697 as u64) - // Standard Error: 1_038 - .saturating_add(Weight::from_ref_time(126_040 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `301 + s * (2 ±0)` + // Estimated: `6811` + // Minimum execution time: 33_275_000 picoseconds. + Weight::from_parts(34_073_221, 6811) + // Standard Error: 1_163 + .saturating_add(Weight::from_parts(124_815, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Multisig Multisigs (r:1 w:1) + /// Storage: Multisig Multisigs (r:1 w:1) + /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) /// The range of component `s` is `[2, 100]`. fn approve_as_multi_approve(s: u32, ) -> Weight { - // Minimum execution time: 26_872 nanoseconds. - Weight::from_ref_time(28_625_218 as u64) - // Standard Error: 793 - .saturating_add(Weight::from_ref_time(128_542 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `320` + // Estimated: `6811` + // Minimum execution time: 18_411_000 picoseconds. + Weight::from_parts(19_431_787, 6811) + // Standard Error: 694 + .saturating_add(Weight::from_parts(107_220, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Multisig Multisigs (r:1 w:1) + /// Storage: Multisig Multisigs (r:1 w:1) + /// Proof: Multisig Multisigs (max_values: None, max_size: Some(3346), added: 5821, mode: MaxEncodedLen) /// The range of component `s` is `[2, 100]`. fn cancel_as_multi(s: u32, ) -> Weight { - // Minimum execution time: 37_636 nanoseconds. - Weight::from_ref_time(39_614_705 as u64) - // Standard Error: 850 - .saturating_add(Weight::from_ref_time(136_222 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `492 + s * (1 ±0)` + // Estimated: `6811` + // Minimum execution time: 33_985_000 picoseconds. + Weight::from_parts(35_547_970, 6811) + // Standard Error: 1_135 + .saturating_add(Weight::from_parts(116_537, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } diff --git a/frame/nft-fractionalization/Cargo.toml b/frame/nft-fractionalization/Cargo.toml new file mode 100644 index 000000000000..adee4c45071f --- /dev/null +++ b/frame/nft-fractionalization/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "pallet-nft-fractionalization" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet to convert non-fungible to fungible tokens." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +pallet-assets = { version = "4.0.0-dev", default-features = false, path = "../assets" } +pallet-nfts = { version = "4.0.0-dev", default-features = false, path = "../nfts" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", path = "../balances" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } +sp-std = { version = "8.0.0", path = "../../primitives/std" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "log/std", + "pallet-assets/std", + "pallet-nfts/std", + "scale-info/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/nft-fractionalization/README.md b/frame/nft-fractionalization/README.md new file mode 100644 index 000000000000..180eef22cc46 --- /dev/null +++ b/frame/nft-fractionalization/README.md @@ -0,0 +1,6 @@ +### Lock NFT + +Lock an NFT from `pallet-nfts` and mint fungible assets from `pallet-assets`. + +The NFT gets locked by putting a system-level attribute named `Locked`. This prevents the NFT from being transferred further. +The NFT becomes unlocked when the `Locked` attribute is removed. In order to unify the fungible asset and unlock the NFT, an account must hold the full issuance of the asset the NFT was fractionalised into. Holding less of the fungible asset will not allow the unlocking of the NFT. diff --git a/frame/nft-fractionalization/src/benchmarking.rs b/frame/nft-fractionalization/src/benchmarking.rs new file mode 100644 index 000000000000..0b54acdab49e --- /dev/null +++ b/frame/nft-fractionalization/src/benchmarking.rs @@ -0,0 +1,132 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Nft fractionalization pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_support::{ + assert_ok, + traits::{ + fungible::{Inspect as InspectFungible, Mutate as MutateFungible}, + tokens::nonfungibles_v2::{Create, Mutate}, + Get, + }, +}; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin as SystemOrigin}; +use pallet_nfts::{CollectionConfig, CollectionSettings, ItemConfig, MintSettings}; +use sp_runtime::traits::StaticLookup; +use sp_std::prelude::*; + +use crate::Pallet as NftFractionalization; + +type BalanceOf = + <::Currency as InspectFungible<::AccountId>>::Balance; + +type CollectionConfigOf = + CollectionConfig, BlockNumberFor, ::NftCollectionId>; + +fn default_collection_config() -> CollectionConfigOf +where + T::Currency: InspectFungible, +{ + CollectionConfig { + settings: CollectionSettings::all_enabled(), + max_supply: None, + mint_settings: MintSettings::default(), + } +} + +fn mint_nft(nft_id: T::NftId) -> (T::AccountId, AccountIdLookupOf) +where + T::Nfts: Create, BlockNumberFor, T::NftCollectionId>> + + Mutate, +{ + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let ed = T::Currency::minimum_balance(); + let multiplier = BalanceOf::::from(100u8); + T::Currency::set_balance(&caller, ed * multiplier + T::Deposit::get() * multiplier); + + assert_ok!(T::Nfts::create_collection(&caller, &caller, &default_collection_config::())); + let collection = T::BenchmarkHelper::collection(0); + assert_ok!(T::Nfts::mint_into(&collection, &nft_id, &caller, &ItemConfig::default(), true)); + (caller, caller_lookup) +} + +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); +} + +benchmarks! { + where_clause { + where + T::Nfts: Create, frame_system::pallet_prelude::BlockNumberFor::, T::NftCollectionId>> + + Mutate, + } + + fractionalize { + let asset = T::BenchmarkHelper::asset(0); + let collection = T::BenchmarkHelper::collection(0); + let nft = T::BenchmarkHelper::nft(0); + let (caller, caller_lookup) = mint_nft::(nft); + }: _(SystemOrigin::Signed(caller.clone()), collection, nft, asset.clone(), caller_lookup, 1000u32.into()) + verify { + assert_last_event::( + Event::NftFractionalized { + nft_collection: collection, + nft, + fractions: 1000u32.into(), + asset, + beneficiary: caller, + }.into() + ); + } + + unify { + let asset = T::BenchmarkHelper::asset(0); + let collection = T::BenchmarkHelper::collection(0); + let nft = T::BenchmarkHelper::nft(0); + let (caller, caller_lookup) = mint_nft::(nft); + NftFractionalization::::fractionalize( + SystemOrigin::Signed(caller.clone()).into(), + collection, + nft, + asset.clone(), + caller_lookup.clone(), + 1000u32.into(), + )?; + }: _(SystemOrigin::Signed(caller.clone()), collection, nft, asset.clone(), caller_lookup) + verify { + assert_last_event::( + Event::NftUnified { + nft_collection: collection, + nft, + asset, + beneficiary: caller, + }.into() + ); + } + + impl_benchmark_test_suite!(NftFractionalization, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/nft-fractionalization/src/lib.rs b/frame/nft-fractionalization/src/lib.rs new file mode 100644 index 000000000000..b1663e95d855 --- /dev/null +++ b/frame/nft-fractionalization/src/lib.rs @@ -0,0 +1,407 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +//! # NFT Fractionalization Pallet +//! +//! This pallet provides the basic functionality that should allow users +//! to leverage partial ownership, transfers, and sales, of illiquid assets, +//! whether real-world assets represented by their digital twins, or NFTs, +//! or original NFTs. +//! +//! The functionality allows a user to lock an NFT they own, create a new +//! fungible asset, and mint a set amount of tokens (`fractions`). +//! +//! It also allows the user to burn 100% of the asset and to unlock the NFT +//! into their account. +//! +//! ### Functions +//! +//! * `fractionalize`: Lock the NFT and create and mint a new fungible asset. +//! * `unify`: Return 100% of the asset and unlock the NFT. + +// Ensure we're `no_std` when compiling for Wasm. +#![cfg_attr(not(feature = "std"), no_std)] + +mod types; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +#[cfg(test)] +pub mod mock; +#[cfg(test)] +mod tests; + +pub mod weights; + +use frame_system::Config as SystemConfig; +pub use pallet::*; +pub use scale_info::Type; +pub use types::*; +pub use weights::WeightInfo; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{ + dispatch::DispatchResult, + ensure, + pallet_prelude::*, + sp_runtime::traits::{AccountIdConversion, StaticLookup}, + traits::{ + fungible::{ + hold::Mutate as HoldMutateFungible, Inspect as InspectFungible, + Mutate as MutateFungible, + }, + fungibles::{ + metadata::{MetadataDeposit, Mutate as MutateMetadata}, + Create, Destroy, Inspect, Mutate, + }, + tokens::{ + nonfungibles_v2::{Inspect as NonFungiblesInspect, Transfer}, + AssetId, Balance as AssetBalance, + Fortitude::Polite, + Precision::{BestEffort, Exact}, + Preservation::Preserve, + }, + }, + BoundedVec, PalletId, + }; + use frame_system::pallet_prelude::*; + use scale_info::prelude::{format, string::String}; + use sp_runtime::traits::{One, Zero}; + use sp_std::{fmt::Display, prelude::*}; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The currency mechanism, used for paying for deposits. + type Currency: InspectFungible + + MutateFungible + + HoldMutateFungible; + + /// Overarching hold reason. + type RuntimeHoldReason: From; + + /// The deposit paid by the user locking an NFT. The deposit is returned to the original NFT + /// owner when the asset is unified and the NFT is unlocked. + #[pallet::constant] + type Deposit: Get>; + + /// Identifier for the collection of NFT. + type NftCollectionId: Member + Parameter + MaxEncodedLen + Copy + Display; + + /// The type used to identify an NFT within a collection. + type NftId: Member + Parameter + MaxEncodedLen + Copy + Display; + + /// The type used to describe the amount of fractions converted into assets. + type AssetBalance: AssetBalance; + + /// The type used to identify the assets created during fractionalization. + type AssetId: AssetId; + + /// Registry for the minted assets. + type Assets: Inspect + + Create + + Destroy + + Mutate + + MutateMetadata + + MetadataDeposit>; + + /// Registry for minted NFTs. + type Nfts: NonFungiblesInspect< + Self::AccountId, + ItemId = Self::NftId, + CollectionId = Self::NftCollectionId, + > + Transfer; + + /// The pallet's id, used for deriving its sovereign account ID. + #[pallet::constant] + type PalletId: Get; + + /// The newly created asset's symbol. + #[pallet::constant] + type NewAssetSymbol: Get>; + + /// The newly created asset's name. + #[pallet::constant] + type NewAssetName: Get>; + + /// The maximum length of a name or symbol stored on-chain. + #[pallet::constant] + type StringLimit: Get; + + /// A set of helper functions for benchmarking. + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper: BenchmarkHelper; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + /// Keeps track of the corresponding NFT ID, asset ID and amount minted. + #[pallet::storage] + #[pallet::getter(fn nft_to_asset)] + pub type NftToAsset = StorageMap< + _, + Blake2_128Concat, + (T::NftCollectionId, T::NftId), + Details, AssetBalanceOf, DepositOf, T::AccountId>, + OptionQuery, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// An NFT was successfully fractionalized. + NftFractionalized { + nft_collection: T::NftCollectionId, + nft: T::NftId, + fractions: AssetBalanceOf, + asset: AssetIdOf, + beneficiary: T::AccountId, + }, + /// An NFT was successfully returned back. + NftUnified { + nft_collection: T::NftCollectionId, + nft: T::NftId, + asset: AssetIdOf, + beneficiary: T::AccountId, + }, + } + + #[pallet::error] + pub enum Error { + /// Asset ID does not correspond to locked NFT. + IncorrectAssetId, + /// The signing account has no permission to do the operation. + NoPermission, + /// NFT doesn't exist. + NftNotFound, + /// NFT has not yet been fractionalised. + NftNotFractionalized, + } + + /// A reason for the pallet placing a hold on funds. + #[pallet::composite_enum] + pub enum HoldReason { + /// Reserved for a fractionalized NFT. + #[codec(index = 0)] + Fractionalized, + } + + #[pallet::call] + impl Pallet { + /// Lock the NFT and mint a new fungible asset. + /// + /// The dispatch origin for this call must be Signed. + /// The origin must be the owner of the NFT they are trying to lock. + /// + /// `Deposit` funds of sender are reserved. + /// + /// - `nft_collection_id`: The ID used to identify the collection of the NFT. + /// Is used within the context of `pallet_nfts`. + /// - `nft_id`: The ID used to identify the NFT within the given collection. + /// Is used within the context of `pallet_nfts`. + /// - `asset_id`: The ID of the new asset. It must not exist. + /// Is used within the context of `pallet_assets`. + /// - `beneficiary`: The account that will receive the newly created asset. + /// - `fractions`: The total issuance of the newly created asset class. + /// + /// Emits `NftFractionalized` event when successful. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::fractionalize())] + pub fn fractionalize( + origin: OriginFor, + nft_collection_id: T::NftCollectionId, + nft_id: T::NftId, + asset_id: AssetIdOf, + beneficiary: AccountIdLookupOf, + fractions: AssetBalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let beneficiary = T::Lookup::lookup(beneficiary)?; + + let nft_owner = + T::Nfts::owner(&nft_collection_id, &nft_id).ok_or(Error::::NftNotFound)?; + ensure!(nft_owner == who, Error::::NoPermission); + + let pallet_account = Self::get_pallet_account(); + let deposit = T::Deposit::get(); + T::Currency::hold(&HoldReason::Fractionalized.into(), &nft_owner, deposit)?; + Self::do_lock_nft(nft_collection_id, nft_id)?; + Self::do_create_asset(asset_id.clone(), pallet_account.clone())?; + Self::do_mint_asset(asset_id.clone(), &beneficiary, fractions)?; + Self::do_set_metadata( + asset_id.clone(), + &who, + &pallet_account, + &nft_collection_id, + &nft_id, + )?; + + NftToAsset::::insert( + (nft_collection_id, nft_id), + Details { asset: asset_id.clone(), fractions, asset_creator: nft_owner, deposit }, + ); + + Self::deposit_event(Event::NftFractionalized { + nft_collection: nft_collection_id, + nft: nft_id, + fractions, + asset: asset_id, + beneficiary, + }); + + Ok(()) + } + + /// Burn the total issuance of the fungible asset and return (unlock) the locked NFT. + /// + /// The dispatch origin for this call must be Signed. + /// + /// `Deposit` funds will be returned to `asset_creator`. + /// + /// - `nft_collection_id`: The ID used to identify the collection of the NFT. + /// Is used within the context of `pallet_nfts`. + /// - `nft_id`: The ID used to identify the NFT within the given collection. + /// Is used within the context of `pallet_nfts`. + /// - `asset_id`: The ID of the asset being returned and destroyed. Must match + /// the original ID of the created asset, corresponding to the NFT. + /// Is used within the context of `pallet_assets`. + /// - `beneficiary`: The account that will receive the unified NFT. + /// + /// Emits `NftUnified` event when successful. + #[pallet::call_index(1)] + #[pallet::weight(T::WeightInfo::unify())] + pub fn unify( + origin: OriginFor, + nft_collection_id: T::NftCollectionId, + nft_id: T::NftId, + asset_id: AssetIdOf, + beneficiary: AccountIdLookupOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let beneficiary = T::Lookup::lookup(beneficiary)?; + + NftToAsset::::try_mutate_exists((nft_collection_id, nft_id), |maybe_details| { + let details = maybe_details.take().ok_or(Error::::NftNotFractionalized)?; + ensure!(details.asset == asset_id, Error::::IncorrectAssetId); + + let deposit = details.deposit; + let asset_creator = details.asset_creator; + Self::do_burn_asset(asset_id.clone(), &who, details.fractions)?; + Self::do_unlock_nft(nft_collection_id, nft_id, &beneficiary)?; + T::Currency::release( + &HoldReason::Fractionalized.into(), + &asset_creator, + deposit, + BestEffort, + )?; + + Self::deposit_event(Event::NftUnified { + nft_collection: nft_collection_id, + nft: nft_id, + asset: asset_id, + beneficiary, + }); + + Ok(()) + }) + } + } + + impl Pallet { + /// The account ID of the pallet. + /// + /// This actually does computation. If you need to keep using it, then make sure you cache + /// the value and only call this once. + fn get_pallet_account() -> T::AccountId { + T::PalletId::get().into_account_truncating() + } + + /// Prevent further transferring of NFT. + fn do_lock_nft(nft_collection_id: T::NftCollectionId, nft_id: T::NftId) -> DispatchResult { + T::Nfts::disable_transfer(&nft_collection_id, &nft_id) + } + + /// Remove the transfer lock and transfer the NFT to the account returning the tokens. + fn do_unlock_nft( + nft_collection_id: T::NftCollectionId, + nft_id: T::NftId, + account: &T::AccountId, + ) -> DispatchResult { + T::Nfts::enable_transfer(&nft_collection_id, &nft_id)?; + T::Nfts::transfer(&nft_collection_id, &nft_id, account) + } + + /// Create the new asset. + fn do_create_asset(asset_id: AssetIdOf, admin: T::AccountId) -> DispatchResult { + T::Assets::create(asset_id, admin, false, One::one()) + } + + /// Mint the `amount` of tokens with `asset_id` into the beneficiary's account. + fn do_mint_asset( + asset_id: AssetIdOf, + beneficiary: &T::AccountId, + amount: AssetBalanceOf, + ) -> DispatchResult { + T::Assets::mint_into(asset_id, beneficiary, amount)?; + Ok(()) + } + + /// Burn tokens from the account. + fn do_burn_asset( + asset_id: AssetIdOf, + account: &T::AccountId, + amount: AssetBalanceOf, + ) -> DispatchResult { + T::Assets::burn_from(asset_id.clone(), account, amount, Exact, Polite)?; + T::Assets::start_destroy(asset_id, None) + } + + /// Set the metadata for the newly created asset. + fn do_set_metadata( + asset_id: AssetIdOf, + depositor: &T::AccountId, + pallet_account: &T::AccountId, + nft_collection_id: &T::NftCollectionId, + nft_id: &T::NftId, + ) -> DispatchResult { + let name = format!( + "{} {nft_collection_id}-{nft_id}", + String::from_utf8_lossy(&T::NewAssetName::get()) + ); + let symbol: &[u8] = &T::NewAssetSymbol::get(); + let existential_deposit = T::Currency::minimum_balance(); + let pallet_account_balance = T::Currency::balance(&pallet_account); + + if pallet_account_balance < existential_deposit { + T::Currency::transfer(&depositor, &pallet_account, existential_deposit, Preserve)?; + } + let metadata_deposit = T::Assets::calc_metadata_deposit(name.as_bytes(), symbol); + if !metadata_deposit.is_zero() { + T::Currency::transfer(&depositor, &pallet_account, metadata_deposit, Preserve)?; + } + T::Assets::set(asset_id, &pallet_account, name.into(), symbol.into(), 0) + } + } +} diff --git a/frame/nft-fractionalization/src/mock.rs b/frame/nft-fractionalization/src/mock.rs new file mode 100644 index 000000000000..6565adaf6fc7 --- /dev/null +++ b/frame/nft-fractionalization/src/mock.rs @@ -0,0 +1,186 @@ +// 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. + +//! Test environment for Nft fractionalization pallet. + +use super::*; +use crate as pallet_nft_fractionalization; + +use frame_support::{ + construct_runtime, parameter_types, + traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, + BoundedVec, PalletId, +}; +use frame_system::EnsureSigned; +use pallet_nfts::PalletFeatures; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + BuildStorage, MultiSignature, +}; + +type Block = frame_system::mocking::MockBlock; +type Signature = MultiSignature; +type AccountPublic = ::Signer; +type AccountId = ::AccountId; + +// Configure a mock runtime to test the pallet. +construct_runtime!( + pub enum Test + { + System: frame_system, + NftFractionalization: pallet_nft_fractionalization, + Assets: pallet_assets, + Balances: pallet_balances, + Nfts: pallet_nfts, + } +); +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type Balance = u64; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ConstU64<1>; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; + type RuntimeHoldReason = RuntimeHoldReason; + type MaxHolds = ConstU32<1>; + type FreezeIdentifier = (); + type MaxFreezes = (); +} + +impl pallet_assets::Config for Test { + type RuntimeEvent = RuntimeEvent; + type Balance = u64; + type RemoveItemsLimit = ConstU32<1000>; + type AssetId = u32; + type AssetIdParameter = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = ConstU64<1>; + type AssetAccountDeposit = ConstU64<10>; + type MetadataDepositBase = ConstU64<1>; + type MetadataDepositPerByte = ConstU64<1>; + type ApprovalDeposit = ConstU64<1>; + type StringLimit = ConstU32<50>; + type Freezer = (); + type Extra = (); + type CallbackHandle = (); + type WeightInfo = (); + pallet_assets::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } +} + +parameter_types! { + pub storage Features: PalletFeatures = PalletFeatures::all_enabled(); +} + +impl pallet_nfts::Config for Test { + type RuntimeEvent = RuntimeEvent; + type CollectionId = u32; + type ItemId = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type Locker = (); + type CollectionDeposit = ConstU64<2>; + type ItemDeposit = ConstU64<1>; + type MetadataDepositBase = ConstU64<1>; + type AttributeDepositBase = ConstU64<1>; + type DepositPerByte = ConstU64<1>; + type StringLimit = ConstU32<50>; + type KeyLimit = ConstU32<50>; + type ValueLimit = ConstU32<50>; + type ApprovalsLimit = ConstU32<10>; + type ItemAttributesApprovalsLimit = ConstU32<2>; + type MaxTips = ConstU32<10>; + type MaxDeadlineDuration = ConstU64<10000>; + type MaxAttributesPerCall = ConstU32<2>; + type Features = Features; + type OffchainSignature = Signature; + type OffchainPublic = AccountPublic; + type WeightInfo = (); + pallet_nfts::runtime_benchmarks_enabled! { + type Helper = (); + } +} + +parameter_types! { + pub const StringLimit: u32 = 50; + pub const NftFractionalizationPalletId: PalletId = PalletId(*b"fraction"); + pub NewAssetSymbol: BoundedVec = (*b"FRAC").to_vec().try_into().unwrap(); + pub NewAssetName: BoundedVec = (*b"Frac").to_vec().try_into().unwrap(); +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type Deposit = ConstU64<1>; + type Currency = Balances; + type NewAssetSymbol = NewAssetSymbol; + type NewAssetName = NewAssetName; + type NftCollectionId = ::CollectionId; + type NftId = ::ItemId; + type AssetBalance = ::Balance; + type AssetId = ::AssetId; + type Assets = Assets; + type Nfts = Nfts; + type PalletId = NftFractionalizationPalletId; + type WeightInfo = (); + type StringLimit = StringLimit; + #[cfg(feature = "runtime-benchmarks")] + type BenchmarkHelper = (); + type RuntimeHoldReason = RuntimeHoldReason; +} + +// Build genesis storage according to the mock runtime. +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} diff --git a/frame/nft-fractionalization/src/tests.rs b/frame/nft-fractionalization/src/tests.rs new file mode 100644 index 000000000000..b82402bda1e6 --- /dev/null +++ b/frame/nft-fractionalization/src/tests.rs @@ -0,0 +1,305 @@ +// 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. + +//! Tests for Nft fractionalization pallet. + +use crate::{mock::*, *}; +use frame_support::{ + assert_noop, assert_ok, + traits::{ + fungible::{hold::Inspect as InspectHold, Mutate as MutateFungible}, + fungibles::{metadata::Inspect, InspectEnumerable}, + }, +}; +use pallet_nfts::CollectionConfig; +use sp_runtime::{DispatchError, ModuleError, TokenError::FundsUnavailable}; + +fn assets() -> Vec { + let mut s: Vec<_> = <::Assets>::asset_ids().collect(); + s.sort(); + s +} + +fn events() -> Vec> { + let result = System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| { + if let mock::RuntimeEvent::NftFractionalization(inner) = e { + Some(inner) + } else { + None + } + }) + .collect(); + + System::reset_events(); + + result +} + +type AccountIdOf = ::AccountId; + +fn account(id: u8) -> AccountIdOf { + [id; 32].into() +} + +#[test] +fn fractionalize_should_work() { + new_test_ext().execute_with(|| { + let nft_collection_id = 0; + let nft_id = 0; + let asset_id = 0; + let fractions = 1000; + + Balances::set_balance(&account(1), 100); + Balances::set_balance(&account(2), 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + CollectionConfig::default(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(account(1)), + nft_collection_id, + nft_id, + account(1), + None, + )); + + assert_ok!(NftFractionalization::fractionalize( + RuntimeOrigin::signed(account(1)), + nft_collection_id, + nft_id, + asset_id, + account(2), + fractions, + )); + assert_eq!(assets(), vec![asset_id]); + assert_eq!(Assets::balance(asset_id, account(2)), fractions); + assert_eq!(Balances::total_balance_on_hold(&account(1)), 2); + assert_eq!(String::from_utf8(Assets::name(0)).unwrap(), "Frac 0-0"); + assert_eq!(String::from_utf8(Assets::symbol(0)).unwrap(), "FRAC"); + assert_eq!(Nfts::owner(nft_collection_id, nft_id), Some(account(1))); + assert_noop!( + Nfts::transfer( + RuntimeOrigin::signed(account(1)), + nft_collection_id, + nft_id, + account(2), + ), + DispatchError::Module(ModuleError { + index: 4, + error: [12, 0, 0, 0], + message: Some("ItemLocked") + }) + ); + + let details = NftToAsset::::get((&nft_collection_id, &nft_id)).unwrap(); + assert_eq!(details.asset, asset_id); + assert_eq!(details.fractions, fractions); + + assert!(events().contains(&Event::::NftFractionalized { + nft_collection: nft_collection_id, + nft: nft_id, + fractions, + asset: asset_id, + beneficiary: account(2), + })); + + // owner can't burn an already fractionalized NFT + assert_noop!( + Nfts::burn(RuntimeOrigin::signed(account(1)), nft_collection_id, nft_id), + DispatchError::Module(ModuleError { + index: 4, + error: [12, 0, 0, 0], + message: Some("ItemLocked") + }) + ); + + // can't fractionalize twice + assert_noop!( + NftFractionalization::fractionalize( + RuntimeOrigin::signed(account(1)), + nft_collection_id, + nft_id, + asset_id + 1, + account(2), + fractions, + ), + DispatchError::Module(ModuleError { + index: 4, + error: [12, 0, 0, 0], + message: Some("ItemLocked") + }) + ); + + let nft_id = nft_id + 1; + assert_noop!( + NftFractionalization::fractionalize( + RuntimeOrigin::signed(account(1)), + nft_collection_id, + nft_id, + asset_id, + account(2), + fractions, + ), + Error::::NftNotFound + ); + + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(account(1)), + nft_collection_id, + nft_id, + account(2), + None + )); + assert_noop!( + NftFractionalization::fractionalize( + RuntimeOrigin::signed(account(1)), + nft_collection_id, + nft_id, + asset_id, + account(2), + fractions, + ), + Error::::NoPermission + ); + }); +} + +#[test] +fn unify_should_work() { + new_test_ext().execute_with(|| { + let nft_collection_id = 0; + let nft_id = 0; + let asset_id = 0; + let fractions = 1000; + + Balances::set_balance(&account(1), 100); + Balances::set_balance(&account(2), 100); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + CollectionConfig::default(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(account(1)), + nft_collection_id, + nft_id, + account(1), + None, + )); + assert_ok!(NftFractionalization::fractionalize( + RuntimeOrigin::signed(account(1)), + nft_collection_id, + nft_id, + asset_id, + account(2), + fractions, + )); + + assert_noop!( + NftFractionalization::unify( + RuntimeOrigin::signed(account(2)), + nft_collection_id + 1, + nft_id, + asset_id, + account(1), + ), + Error::::NftNotFractionalized + ); + assert_noop!( + NftFractionalization::unify( + RuntimeOrigin::signed(account(2)), + nft_collection_id, + nft_id, + asset_id + 1, + account(1), + ), + Error::::IncorrectAssetId + ); + + // can't unify the asset a user doesn't hold + assert_noop!( + NftFractionalization::unify( + RuntimeOrigin::signed(account(1)), + nft_collection_id, + nft_id, + asset_id, + account(1), + ), + DispatchError::Token(FundsUnavailable) + ); + + assert_ok!(NftFractionalization::unify( + RuntimeOrigin::signed(account(2)), + nft_collection_id, + nft_id, + asset_id, + account(1), + )); + + assert_eq!(Assets::balance(asset_id, account(2)), 0); + assert_eq!(Balances::reserved_balance(&account(1)), 1); + assert_eq!(Nfts::owner(nft_collection_id, nft_id), Some(account(1))); + assert!(!NftToAsset::::contains_key((&nft_collection_id, &nft_id))); + + assert!(events().contains(&Event::::NftUnified { + nft_collection: nft_collection_id, + nft: nft_id, + asset: asset_id, + beneficiary: account(1), + })); + + // validate we need to hold the full balance to un-fractionalize the NFT + let asset_id = asset_id + 1; + assert_ok!(NftFractionalization::fractionalize( + RuntimeOrigin::signed(account(1)), + nft_collection_id, + nft_id, + asset_id, + account(1), + fractions, + )); + assert_ok!(Assets::transfer(RuntimeOrigin::signed(account(1)), asset_id, account(2), 1)); + assert_eq!(Assets::balance(asset_id, account(1)), fractions - 1); + assert_eq!(Assets::balance(asset_id, account(2)), 1); + assert_noop!( + NftFractionalization::unify( + RuntimeOrigin::signed(account(1)), + nft_collection_id, + nft_id, + asset_id, + account(1), + ), + DispatchError::Token(FundsUnavailable) + ); + + assert_ok!(Assets::transfer(RuntimeOrigin::signed(account(2)), asset_id, account(1), 1)); + assert_ok!(NftFractionalization::unify( + RuntimeOrigin::signed(account(1)), + nft_collection_id, + nft_id, + asset_id, + account(2), + )); + assert_eq!(Nfts::owner(nft_collection_id, nft_id), Some(account(2))); + }); +} diff --git a/frame/nft-fractionalization/src/types.rs b/frame/nft-fractionalization/src/types.rs new file mode 100644 index 000000000000..cbaaf5f5160d --- /dev/null +++ b/frame/nft-fractionalization/src/types.rs @@ -0,0 +1,76 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Various basic types for use in the Nft fractionalization pallet. + +use super::*; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::traits::{fungible::Inspect as FunInspect, fungibles::Inspect}; +use scale_info::TypeInfo; +use sp_runtime::traits::StaticLookup; + +pub type AssetIdOf = <::Assets as Inspect<::AccountId>>::AssetId; +pub type AssetBalanceOf = + <::Assets as Inspect<::AccountId>>::Balance; +pub type DepositOf = + <::Currency as FunInspect<::AccountId>>::Balance; +pub type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; + +/// Stores the details of a fractionalized item. +#[derive(Decode, Encode, Default, PartialEq, Eq, MaxEncodedLen, TypeInfo)] +pub struct Details { + /// Minted asset. + pub asset: AssetId, + + /// Number of fractions minted. + pub fractions: Fractions, + + /// Reserved deposit for creating a new asset. + pub deposit: Deposit, + + /// Account that fractionalized an item. + pub asset_creator: AccountId, +} + +/// Benchmark Helper +#[cfg(feature = "runtime-benchmarks")] +pub trait BenchmarkHelper { + /// Returns an asset id from a given integer. + fn asset(id: u32) -> AssetId; + /// Returns a collection id from a given integer. + fn collection(id: u32) -> CollectionId; + /// Returns an nft id from a given integer. + fn nft(id: u32) -> ItemId; +} + +#[cfg(feature = "runtime-benchmarks")] +impl BenchmarkHelper for () +where + AssetId: From, + CollectionId: From, + ItemId: From, +{ + fn asset(id: u32) -> AssetId { + id.into() + } + fn collection(id: u32) -> CollectionId { + id.into() + } + fn nft(id: u32) -> ItemId { + id.into() + } +} diff --git a/frame/nft-fractionalization/src/weights.rs b/frame/nft-fractionalization/src/weights.rs new file mode 100644 index 000000000000..ebb4aa0fbcfb --- /dev/null +++ b/frame/nft-fractionalization/src/weights.rs @@ -0,0 +1,186 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_nft_fractionalization +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_nft_fractionalization +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/nft-fractionalization/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for pallet_nft_fractionalization. +pub trait WeightInfo { + fn fractionalize() -> Weight; + fn unify() -> Weight; +} + +/// Weights for pallet_nft_fractionalization using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: Nfts Item (r:1 w:0) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(85), added: 2560, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:1 w:1) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) + /// Storage: NftFractionalization NftToAsset (r:0 w:1) + /// Proof: NftFractionalization NftToAsset (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + fn fractionalize() -> Weight { + // Proof Size summary in bytes: + // Measured: `609` + // Estimated: `4326` + // Minimum execution time: 187_416_000 picoseconds. + Weight::from_parts(191_131_000, 4326) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) + } + /// Storage: NftFractionalization NftToAsset (r:1 w:1) + /// Proof: NftFractionalization NftToAsset (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:1 w:1) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(85), added: 2560, mode: MaxEncodedLen) + /// Storage: Nfts Account (r:0 w:1) + /// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Nfts ItemPriceOf (r:0 w:1) + /// Proof: Nfts ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) + /// Storage: Nfts PendingSwapOf (r:0 w:1) + /// Proof: Nfts PendingSwapOf (max_values: None, max_size: Some(71), added: 2546, mode: MaxEncodedLen) + fn unify() -> Weight { + // Proof Size summary in bytes: + // Measured: `1422` + // Estimated: `4326` + // Minimum execution time: 134_159_000 picoseconds. + Weight::from_parts(136_621_000, 4326) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(10_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: Nfts Item (r:1 w:0) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(85), added: 2560, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:1 w:1) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Assets Metadata (r:1 w:1) + /// Proof: Assets Metadata (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) + /// Storage: NftFractionalization NftToAsset (r:0 w:1) + /// Proof: NftFractionalization NftToAsset (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + fn fractionalize() -> Weight { + // Proof Size summary in bytes: + // Measured: `609` + // Estimated: `4326` + // Minimum execution time: 187_416_000 picoseconds. + Weight::from_parts(191_131_000, 4326) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(8_u64)) + } + /// Storage: NftFractionalization NftToAsset (r:1 w:1) + /// Proof: NftFractionalization NftToAsset (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:1 w:1) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(85), added: 2560, mode: MaxEncodedLen) + /// Storage: Nfts Account (r:0 w:1) + /// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Nfts ItemPriceOf (r:0 w:1) + /// Proof: Nfts ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) + /// Storage: Nfts PendingSwapOf (r:0 w:1) + /// Proof: Nfts PendingSwapOf (max_values: None, max_size: Some(71), added: 2546, mode: MaxEncodedLen) + fn unify() -> Weight { + // Proof Size summary in bytes: + // Measured: `1422` + // Estimated: `4326` + // Minimum execution time: 134_159_000 picoseconds. + Weight::from_parts(136_621_000, 4326) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(10_u64)) + } +} diff --git a/frame/nfts/Cargo.toml b/frame/nfts/Cargo.toml index 9e010cb55795..dca35a7d1075 100644 --- a/frame/nfts/Cargo.toml +++ b/frame/nfts/Cargo.toml @@ -13,22 +13,21 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -enumflags2 = { version = "0.7.5" } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +enumflags2 = { version = "0.7.7" } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } -sp-std = { version = "5.0.0", path = "../../primitives/std" } +sp-keystore = { version = "0.27.0", path = "../../primitives/keystore" } [features] default = ["std"] @@ -40,6 +39,7 @@ std = [ "log/std", "scale-info/std", "sp-core/std", + "sp-io/std", "sp-runtime/std", "sp-std/std", ] diff --git a/frame/nfts/runtime-api/Cargo.toml b/frame/nfts/runtime-api/Cargo.toml new file mode 100644 index 000000000000..bf5f9f0ec001 --- /dev/null +++ b/frame/nfts/runtime-api/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "pallet-nfts-runtime-api" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Runtime API for the FRAME NFTs pallet." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } +pallet-nfts = { version = "4.0.0-dev", default-features = false, path = "../../nfts" } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "pallet-nfts/std", + "sp-api/std", +] diff --git a/frame/nfts/runtime-api/README.md b/frame/nfts/runtime-api/README.md new file mode 100644 index 000000000000..289036d2c0d2 --- /dev/null +++ b/frame/nfts/runtime-api/README.md @@ -0,0 +1,3 @@ +RPC runtime API for the FRAME NFTs pallet. + +License: Apache-2.0 diff --git a/frame/nfts/runtime-api/src/lib.rs b/frame/nfts/runtime-api/src/lib.rs new file mode 100644 index 000000000000..0c23d178107e --- /dev/null +++ b/frame/nfts/runtime-api/src/lib.rs @@ -0,0 +1,57 @@ +// This file is part of Substrate. + +// Copyright (C) 2023 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. + +//! Runtime API definition for the FRAME NFTs pallet. + +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::{Decode, Encode}; +use frame_support::dispatch::Vec; + +sp_api::decl_runtime_apis! { + pub trait NftsApi + where + AccountId: Encode + Decode, + CollectionId: Encode, + ItemId: Encode, + { + fn owner(collection: CollectionId, item: ItemId) -> Option; + + fn collection_owner(collection: CollectionId) -> Option; + + fn attribute( + collection: CollectionId, + item: ItemId, + key: Vec, + ) -> Option>; + + fn custom_attribute( + account: AccountId, + collection: CollectionId, + item: ItemId, + key: Vec, + ) -> Option>; + + fn system_attribute( + collection: CollectionId, + item: ItemId, + key: Vec, + ) -> Option>; + + fn collection_attribute(collection: CollectionId, key: Vec) -> Option>; + } +} diff --git a/frame/nfts/src/benchmarking.rs b/frame/nfts/src/benchmarking.rs index 6517445da672..67ba29266245 100644 --- a/frame/nfts/src/benchmarking.rs +++ b/frame/nfts/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +21,8 @@ use super::*; use enumflags2::{BitFlag, BitFlags}; -use frame_benchmarking::{ - account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, +use frame_benchmarking::v1::{ + account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, BenchmarkError, }; use frame_support::{ assert_ok, @@ -30,8 +30,12 @@ use frame_support::{ traits::{EnsureOrigin, Get}, BoundedVec, }; -use frame_system::RawOrigin as SystemOrigin; -use sp_runtime::traits::{Bounded, One}; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin as SystemOrigin}; +use sp_io::crypto::{sr25519_generate, sr25519_sign}; +use sp_runtime::{ + traits::{Bounded, IdentifyAccount, One}, + AccountId32, MultiSignature, MultiSigner, +}; use sp_std::prelude::*; use crate::Pallet as Nfts; @@ -68,6 +72,40 @@ fn add_collection_metadata, I: 'static>() -> (T::AccountId, Account fn mint_item, I: 'static>( index: u16, +) -> (T::ItemId, T::AccountId, AccountIdLookupOf) { + let item = T::Helper::item(index); + let collection = T::Helper::collection(0); + let caller = Collection::::get(collection).unwrap().owner; + if caller != whitelisted_caller() { + whitelist_account!(caller); + } + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let item_exists = Item::::contains_key(&collection, &item); + let item_config = ItemConfigOf::::get(&collection, &item); + if item_exists { + return (item, caller, caller_lookup) + } else if let Some(item_config) = item_config { + assert_ok!(Nfts::::force_mint( + SystemOrigin::Signed(caller.clone()).into(), + collection, + item, + caller_lookup.clone(), + item_config, + )); + } else { + assert_ok!(Nfts::::mint( + SystemOrigin::Signed(caller.clone()).into(), + collection, + item, + caller_lookup.clone(), + None, + )); + } + (item, caller, caller_lookup) +} + +fn lock_item, I: 'static>( + index: u16, ) -> (T::ItemId, T::AccountId, AccountIdLookupOf) { let caller = Collection::::get(T::Helper::collection(0)).unwrap().owner; if caller != whitelisted_caller() { @@ -75,12 +113,27 @@ fn mint_item, I: 'static>( } let caller_lookup = T::Lookup::unlookup(caller.clone()); let item = T::Helper::item(index); - assert_ok!(Nfts::::mint( + assert_ok!(Nfts::::lock_item_transfer( + SystemOrigin::Signed(caller.clone()).into(), + T::Helper::collection(0), + item, + )); + (item, caller, caller_lookup) +} + +fn burn_item, I: 'static>( + index: u16, +) -> (T::ItemId, T::AccountId, AccountIdLookupOf) { + let caller = Collection::::get(T::Helper::collection(0)).unwrap().owner; + if caller != whitelisted_caller() { + whitelist_account!(caller); + } + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let item = T::Helper::item(index); + assert_ok!(Nfts::::burn( SystemOrigin::Signed(caller.clone()).into(), T::Helper::collection(0), item, - caller_lookup.clone(), - None, )); (item, caller, caller_lookup) } @@ -122,6 +175,26 @@ fn add_item_attribute, I: 'static>( (key, caller, caller_lookup) } +fn add_collection_attribute, I: 'static>( + i: u16, +) -> (BoundedVec, T::AccountId, AccountIdLookupOf) { + let caller = Collection::::get(T::Helper::collection(0)).unwrap().owner; + if caller != whitelisted_caller() { + whitelist_account!(caller); + } + let caller_lookup = T::Lookup::unlookup(caller.clone()); + let key: BoundedVec<_, _> = make_filled_vec(i, T::KeyLimit::get() as usize).try_into().unwrap(); + assert_ok!(Nfts::::set_attribute( + SystemOrigin::Signed(caller.clone()).into(), + T::Helper::collection(0), + None, + AttributeNamespace::CollectionOwner, + key.clone(), + vec![0; T::ValueLimit::get() as usize].try_into().unwrap(), + )); + (key, caller, caller_lookup) +} + fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { let events = frame_system::Pallet::::events(); let system_event: ::RuntimeEvent = generic_event.into(); @@ -148,10 +221,25 @@ fn default_item_config() -> ItemConfig { ItemConfig { settings: ItemSettings::all_enabled() } } +fn make_filled_vec(value: u16, length: usize) -> Vec { + let mut vec = vec![0u8; length]; + let mut s = Vec::from(value.to_be_bytes()); + vec.truncate(length - s.len()); + vec.append(&mut s); + vec +} + benchmarks_instance_pallet! { + where_clause { + where + T::OffchainSignature: From, + T::AccountId: From, + } + create { let collection = T::Helper::collection(0); - let origin = T::CreateOrigin::successful_origin(&collection); + let origin = T::CreateOrigin::try_successful_origin(&collection) + .map_err(|_| BenchmarkError::Weightless)?; let caller = T::CreateOrigin::ensure_origin(origin.clone(), &collection).unwrap(); whitelist_account!(caller); let admin = T::Lookup::unlookup(caller.clone()); @@ -171,26 +259,25 @@ benchmarks_instance_pallet! { } destroy { - let n in 0 .. 1_000; let m in 0 .. 1_000; + let c in 0 .. 1_000; let a in 0 .. 1_000; let (collection, caller, _) = create_collection::(); add_collection_metadata::(); - for i in 0..n { - mint_item::(i as u16); - } for i in 0..m { - if !Item::::contains_key(collection, T::Helper::item(i as u16)) { - mint_item::(i as u16); - } + mint_item::(i as u16); add_item_metadata::(T::Helper::item(i as u16)); + lock_item::(i as u16); + burn_item::(i as u16); + } + for i in 0..c { + mint_item::(i as u16); + lock_item::(i as u16); + burn_item::(i as u16); } for i in 0..a { - if !Item::::contains_key(collection, T::Helper::item(i as u16)) { - mint_item::(i as u16); - } - add_item_attribute::(T::Helper::item(i as u16)); + add_collection_attribute::(i as u16); } let witness = Collection::::get(collection).unwrap().destroy_witness(); }: _(SystemOrigin::Signed(caller), collection, witness) @@ -215,9 +302,9 @@ benchmarks_instance_pallet! { } burn { - let (collection, caller, caller_lookup) = create_collection::(); + let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(caller_lookup)) + }: _(SystemOrigin::Signed(caller.clone()), collection, item) verify { assert_last_event::(Event::Burned { collection, item, owner: caller }.into()); } @@ -296,22 +383,23 @@ benchmarks_instance_pallet! { set_team { let (collection, caller, _) = create_collection::(); - let target0 = T::Lookup::unlookup(account("target", 0, SEED)); - let target1 = T::Lookup::unlookup(account("target", 1, SEED)); - let target2 = T::Lookup::unlookup(account("target", 2, SEED)); + let target0 = Some(T::Lookup::unlookup(account("target", 0, SEED))); + let target1 = Some(T::Lookup::unlookup(account("target", 1, SEED))); + let target2 = Some(T::Lookup::unlookup(account("target", 2, SEED))); }: _(SystemOrigin::Signed(caller), collection, target0, target1, target2) verify { assert_last_event::(Event::TeamChanged{ collection, - issuer: account("target", 0, SEED), - admin: account("target", 1, SEED), - freezer: account("target", 2, SEED), + issuer: Some(account("target", 0, SEED)), + admin: Some(account("target", 1, SEED)), + freezer: Some(account("target", 2, SEED)), }.into()); } force_collection_owner { let (collection, _, _) = create_collection::(); - let origin = T::ForceOrigin::successful_origin(); + let origin = + T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); @@ -326,7 +414,8 @@ benchmarks_instance_pallet! { force_collection_config { let (collection, caller, _) = create_collection::(); - let origin = T::ForceOrigin::successful_origin(); + let origin = + T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let call = Call::::force_collection_config { collection, config: make_collection_config::(CollectionSetting::DepositRequired.into()), @@ -436,11 +525,7 @@ benchmarks_instance_pallet! { T::Currency::make_free_balance_be(&target, DepositBalanceOf::::max_value()); let value: BoundedVec<_, _> = vec![0u8; T::ValueLimit::get() as usize].try_into().unwrap(); for i in 0..n { - let mut key = vec![0u8; T::KeyLimit::get() as usize]; - let mut s = Vec::from((i as u16).to_be_bytes()); - key.truncate(s.len()); - key.append(&mut s); - + let key = make_filled_vec(i as u16, T::KeyLimit::get() as usize); Nfts::::set_attribute( SystemOrigin::Signed(target.clone()).into(), T::Helper::collection(0), @@ -504,7 +589,7 @@ benchmarks_instance_pallet! { let (item, ..) = mint_item::(0); let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); - let deadline = T::BlockNumber::max_value(); + let deadline = BlockNumberFor::::max_value(); }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup, Some(deadline)) verify { assert_last_event::(Event::TransferApproved { collection, item, owner: caller, delegate, deadline: Some(deadline) }.into()); @@ -516,7 +601,7 @@ benchmarks_instance_pallet! { let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); - let deadline = T::BlockNumber::max_value(); + let deadline = BlockNumberFor::::max_value(); Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?; }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup) verify { @@ -529,7 +614,7 @@ benchmarks_instance_pallet! { let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); - let deadline = T::BlockNumber::max_value(); + let deadline = BlockNumberFor::::max_value(); Nfts::::approve_transfer(origin, collection, item, delegate_lookup.clone(), Some(deadline))?; }: _(SystemOrigin::Signed(caller.clone()), collection, item) verify { @@ -595,9 +680,9 @@ benchmarks_instance_pallet! { let buyer_lookup = T::Lookup::unlookup(buyer.clone()); let price = ItemPrice::::from(0u32); let origin = SystemOrigin::Signed(seller.clone()).into(); - Nfts::::set_price(origin, collection, item, Some(price.clone()), Some(buyer_lookup))?; + Nfts::::set_price(origin, collection, item, Some(price), Some(buyer_lookup))?; T::Currency::make_free_balance_be(&buyer, DepositBalanceOf::::max_value()); - }: _(SystemOrigin::Signed(buyer.clone()), collection, item, price.clone()) + }: _(SystemOrigin::Signed(buyer.clone()), collection, item, price) verify { assert_last_event::(Event::ItemBought { collection, @@ -714,5 +799,98 @@ benchmarks_instance_pallet! { }.into()); } + mint_pre_signed { + let n in 0 .. T::MaxAttributesPerCall::get() as u32; + let caller_public = sr25519_generate(0.into(), None); + let caller = MultiSigner::Sr25519(caller_public).into_account().into(); + T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); + let caller_lookup = T::Lookup::unlookup(caller.clone()); + + let collection = T::Helper::collection(0); + let item = T::Helper::item(0); + assert_ok!(Nfts::::force_create( + SystemOrigin::Root.into(), + caller_lookup.clone(), + default_collection_config::() + )); + + let metadata = vec![0u8; T::StringLimit::get() as usize]; + let mut attributes = vec![]; + let attribute_value = vec![0u8; T::ValueLimit::get() as usize]; + for i in 0..n { + let attribute_key = make_filled_vec(i as u16, T::KeyLimit::get() as usize); + attributes.push((attribute_key, attribute_value.clone())); + } + let mint_data = PreSignedMint { + collection, + item, + attributes, + metadata: metadata.clone(), + only_account: None, + deadline: One::one(), + mint_price: Some(DepositBalanceOf::::min_value()), + }; + let message = Encode::encode(&mint_data); + let signature = MultiSignature::Sr25519(sr25519_sign(0.into(), &caller_public, &message).unwrap()); + + let target: T::AccountId = account("target", 0, SEED); + T::Currency::make_free_balance_be(&target, DepositBalanceOf::::max_value()); + frame_system::Pallet::::set_block_number(One::one()); + }: _(SystemOrigin::Signed(target.clone()), Box::new(mint_data), signature.into(), caller) + verify { + let metadata: BoundedVec<_, _> = metadata.try_into().unwrap(); + assert_last_event::(Event::ItemMetadataSet { collection, item, data: metadata }.into()); + } + + set_attributes_pre_signed { + let n in 0 .. T::MaxAttributesPerCall::get() as u32; + let (collection, _, _) = create_collection::(); + + let item_owner: T::AccountId = account("item_owner", 0, SEED); + let item_owner_lookup = T::Lookup::unlookup(item_owner.clone()); + + let signer_public = sr25519_generate(0.into(), None); + let signer: T::AccountId = MultiSigner::Sr25519(signer_public).into_account().into(); + + T::Currency::make_free_balance_be(&item_owner, DepositBalanceOf::::max_value()); + + let item = T::Helper::item(0); + assert_ok!(Nfts::::force_mint( + SystemOrigin::Root.into(), + collection, + item, + item_owner_lookup.clone(), + default_item_config(), + )); + + let mut attributes = vec![]; + let attribute_value = vec![0u8; T::ValueLimit::get() as usize]; + for i in 0..n { + let attribute_key = make_filled_vec(i as u16, T::KeyLimit::get() as usize); + attributes.push((attribute_key, attribute_value.clone())); + } + let pre_signed_data = PreSignedAttributes { + collection, + item, + attributes, + namespace: AttributeNamespace::Account(signer.clone()), + deadline: One::one(), + }; + let message = Encode::encode(&pre_signed_data); + let signature = MultiSignature::Sr25519(sr25519_sign(0.into(), &signer_public, &message).unwrap()); + + frame_system::Pallet::::set_block_number(One::one()); + }: _(SystemOrigin::Signed(item_owner.clone()), pre_signed_data, signature.into(), signer.clone()) + verify { + assert_last_event::( + Event::PreSignedAttributesSet { + collection, + item, + namespace: AttributeNamespace::Account(signer.clone()), + } + .into(), + ); + } + impl_benchmark_test_suite!(Nfts, crate::mock::new_test_ext(), crate::mock::Test); } diff --git a/frame/nfts/src/common_functions.rs b/frame/nfts/src/common_functions.rs index 9c0faeb6b7c7..a3486edec23c 100644 --- a/frame/nfts/src/common_functions.rs +++ b/frame/nfts/src/common_functions.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,8 @@ //! Various pieces of common functionality. -use super::*; +use crate::*; +use frame_support::pallet_prelude::*; impl, I: 'static> Pallet { /// Get the owner of the item, if the item exists. @@ -30,6 +31,30 @@ impl, I: 'static> Pallet { Collection::::get(collection).map(|i| i.owner) } + /// Validate the `data` was signed by `signer` and the `signature` is correct. + pub fn validate_signature( + data: &Vec, + signature: &T::OffchainSignature, + signer: &T::AccountId, + ) -> DispatchResult { + if signature.verify(&**data, &signer) { + return Ok(()) + } + + // NOTE: for security reasons modern UIs implicitly wrap the data requested to sign into + // , that's why we support both wrapped and raw versions. + let prefix = b""; + let suffix = b""; + let mut wrapped: Vec = Vec::with_capacity(data.len() + prefix.len() + suffix.len()); + wrapped.extend(prefix); + wrapped.extend(data); + wrapped.extend(suffix); + + ensure!(signature.verify(&*wrapped, &signer), Error::::WrongSignature); + + Ok(()) + } + #[cfg(any(test, feature = "runtime-benchmarks"))] pub fn set_next_id(id: T::CollectionId) { NextCollectionId::::set(Some(id)); diff --git a/frame/nfts/src/features/approvals.rs b/frame/nfts/src/features/approvals.rs index cb5279fd949d..c78dd2d96b08 100644 --- a/frame/nfts/src/features/approvals.rs +++ b/frame/nfts/src/features/approvals.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ impl, I: 'static> Pallet { collection: T::CollectionId, item: T::ItemId, delegate: T::AccountId, - maybe_deadline: Option<::BlockNumber>, + maybe_deadline: Option>, ) -> DispatchResult { ensure!( Self::is_pallet_feature_enabled(PalletFeature::Approvals), @@ -40,9 +40,7 @@ impl, I: 'static> Pallet { ); if let Some(check_origin) = maybe_check_origin { - let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin); - let permitted = is_admin || check_origin == details.owner; - ensure!(permitted, Error::::NoPermission); + ensure!(check_origin == details.owner, Error::::NoPermission); } let now = frame_system::Pallet::::block_number(); @@ -85,9 +83,7 @@ impl, I: 'static> Pallet { if !is_past_deadline { if let Some(check_origin) = maybe_check_origin { - let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin); - let permitted = is_admin || check_origin == details.owner; - ensure!(permitted, Error::::NoPermission); + ensure!(check_origin == details.owner, Error::::NoPermission); } } @@ -113,9 +109,7 @@ impl, I: 'static> Pallet { Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; if let Some(check_origin) = maybe_check_origin { - let is_admin = Self::has_role(&collection, &check_origin, CollectionRole::Admin); - let permitted = is_admin || check_origin == details.owner; - ensure!(permitted, Error::::NoPermission); + ensure!(check_origin == details.owner, Error::::NoPermission); } details.approvals.clear(); diff --git a/frame/nfts/src/features/atomic_swap.rs b/frame/nfts/src/features/atomic_swap.rs index bacaccdaedcb..5b0096d72a3d 100644 --- a/frame/nfts/src/features/atomic_swap.rs +++ b/frame/nfts/src/features/atomic_swap.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +29,7 @@ impl, I: 'static> Pallet { desired_collection_id: T::CollectionId, maybe_desired_item_id: Option, maybe_price: Option>>, - duration: ::BlockNumber, + duration: frame_system::pallet_prelude::BlockNumberFor, ) -> DispatchResult { ensure!( Self::is_pallet_feature_enabled(PalletFeature::Swaps), diff --git a/frame/nfts/src/features/attributes.rs b/frame/nfts/src/features/attributes.rs index b25f2a60cd62..8a9bbe8a61de 100644 --- a/frame/nfts/src/features/attributes.rs +++ b/frame/nfts/src/features/attributes.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,23 +26,15 @@ impl, I: 'static> Pallet { namespace: AttributeNamespace, key: BoundedVec, value: BoundedVec, + depositor: T::AccountId, ) -> DispatchResult { ensure!( Self::is_pallet_feature_enabled(PalletFeature::Attributes), Error::::MethodDisabled ); - let mut collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - ensure!( - Self::is_valid_namespace( - &origin, - &namespace, - &collection, - &collection_details.owner, - &maybe_item, - )?, + Self::is_valid_namespace(&origin, &namespace, &collection, &maybe_item)?, Error::::NoPermission ); @@ -65,8 +57,12 @@ impl, I: 'static> Pallet { _ => (), } + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + let attribute = Attribute::::get((collection, maybe_item, &namespace, &key)); - if attribute.is_none() { + let attribute_exists = attribute.is_some(); + if !attribute_exists { collection_details.attributes.saturating_inc(); } @@ -74,6 +70,7 @@ impl, I: 'static> Pallet { attribute.map_or(AttributeDeposit { account: None, amount: Zero::zero() }, |m| m.1); let mut deposit = Zero::zero(); + // disabled DepositRequired setting only affects the CollectionOwner namespace if collection_config.is_setting_enabled(CollectionSetting::DepositRequired) || namespace != AttributeNamespace::CollectionOwner { @@ -82,33 +79,50 @@ impl, I: 'static> Pallet { .saturating_add(T::AttributeDepositBase::get()); } + let is_collection_owner_namespace = namespace == AttributeNamespace::CollectionOwner; + let is_depositor_collection_owner = + is_collection_owner_namespace && collection_details.owner == depositor; + + // NOTE: in the CollectionOwner namespace if the depositor is `None` that means the deposit + // was paid by the collection's owner. + let old_depositor = + if is_collection_owner_namespace && old_deposit.account.is_none() && attribute_exists { + Some(collection_details.owner.clone()) + } else { + old_deposit.account + }; + let depositor_has_changed = old_depositor != Some(depositor.clone()); + // NOTE: when we transfer an item, we don't move attributes in the ItemOwner namespace. // When the new owner updates the same attribute, we will update the depositor record // and return the deposit to the previous owner. - if old_deposit.account.is_some() && old_deposit.account != Some(origin.clone()) { - T::Currency::unreserve(&old_deposit.account.unwrap(), old_deposit.amount); - T::Currency::reserve(&origin, deposit)?; + if depositor_has_changed { + if let Some(old_depositor) = old_depositor { + T::Currency::unreserve(&old_depositor, old_deposit.amount); + } + T::Currency::reserve(&depositor, deposit)?; } else if deposit > old_deposit.amount { - T::Currency::reserve(&origin, deposit - old_deposit.amount)?; + T::Currency::reserve(&depositor, deposit - old_deposit.amount)?; } else if deposit < old_deposit.amount { - T::Currency::unreserve(&origin, old_deposit.amount - deposit); + T::Currency::unreserve(&depositor, old_deposit.amount - deposit); } - // NOTE: we don't track the depositor in the CollectionOwner namespace as it's always a - // collection's owner. This simplifies the collection's transfer to another owner. - let deposit_owner = match namespace { - AttributeNamespace::CollectionOwner => { - collection_details.owner_deposit.saturating_accrue(deposit); + if is_depositor_collection_owner { + if !depositor_has_changed { collection_details.owner_deposit.saturating_reduce(old_deposit.amount); - None - }, - _ => Some(origin), - }; + } + collection_details.owner_deposit.saturating_accrue(deposit); + } + let new_deposit_owner = match is_depositor_collection_owner { + true => None, + false => Some(depositor), + }; Attribute::::insert( (&collection, maybe_item, &namespace, &key), - (&value, AttributeDeposit { account: deposit_owner, amount: deposit }), + (&value, AttributeDeposit { account: new_deposit_owner, amount: deposit }), ); + Collection::::insert(collection, &collection_details); Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value, namespace }); Ok(()) @@ -145,8 +159,61 @@ impl, I: 'static> Pallet { Ok(()) } + pub(crate) fn do_set_attributes_pre_signed( + origin: T::AccountId, + data: PreSignedAttributesOf, + signer: T::AccountId, + ) -> DispatchResult { + let PreSignedAttributes { collection, item, attributes, namespace, deadline } = data; + + ensure!( + attributes.len() <= T::MaxAttributesPerCall::get() as usize, + Error::::MaxAttributesLimitReached + ); + + let now = frame_system::Pallet::::block_number(); + ensure!(deadline >= now, Error::::DeadlineExpired); + + let item_details = + Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; + ensure!(item_details.owner == origin, Error::::NoPermission); + + // Only the CollectionOwner and Account() namespaces could be updated in this way. + // For the Account() namespace we check and set the approval if it wasn't set before. + match &namespace { + AttributeNamespace::CollectionOwner => {}, + AttributeNamespace::Account(account) => { + ensure!(account == &signer, Error::::NoPermission); + let approvals = ItemAttributesApprovalsOf::::get(&collection, &item); + if !approvals.contains(account) { + Self::do_approve_item_attributes( + origin.clone(), + collection, + item, + account.clone(), + )?; + } + }, + _ => return Err(Error::::WrongNamespace.into()), + } + + for (key, value) in attributes { + Self::do_set_attribute( + signer.clone(), + collection, + Some(item), + namespace.clone(), + Self::construct_attribute_key(key)?, + Self::construct_attribute_value(value)?, + origin.clone(), + )?; + } + Self::deposit_event(Event::PreSignedAttributesSet { collection, item, namespace }); + Ok(()) + } + pub(crate) fn do_clear_attribute( - maybe_check_owner: Option, + maybe_check_origin: Option, collection: T::CollectionId, maybe_item: Option, namespace: AttributeNamespace, @@ -154,21 +221,13 @@ impl, I: 'static> Pallet { ) -> DispatchResult { let (_, deposit) = Attribute::::take((collection, maybe_item, &namespace, &key)) .ok_or(Error::::AttributeNotFound)?; - let mut collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { + if let Some(check_origin) = &maybe_check_origin { // validate the provided namespace when it's not a root call and the caller is not // the same as the `deposit.account` (e.g. the deposit was paid by different account) - if deposit.account != maybe_check_owner { + if deposit.account != maybe_check_origin { ensure!( - Self::is_valid_namespace( - &check_owner, - &namespace, - &collection, - &collection_details.owner, - &maybe_item, - )?, + Self::is_valid_namespace(&check_origin, &namespace, &collection, &maybe_item)?, Error::::NoPermission ); } @@ -188,27 +247,39 @@ impl, I: 'static> Pallet { // NOTE: if the item was previously burned, the ItemConfigOf record // might not exist. In that case, we allow to clear the attribute. let maybe_is_locked = Self::get_item_config(&collection, &item) - .map_or(false, |c| { - c.has_disabled_setting(ItemSetting::UnlockedAttributes) + .map_or(None, |c| { + Some(c.has_disabled_setting(ItemSetting::UnlockedAttributes)) }); - ensure!(!maybe_is_locked, Error::::LockedItemAttributes); + if let Some(is_locked) = maybe_is_locked { + ensure!(!is_locked, Error::::LockedItemAttributes); + // Only the collection's admin can clear attributes in that namespace. + // e.g. in off-chain mints, the attribute's depositor will be the item's + // owner, that's why we need to do this extra check. + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Admin), + Error::::NoPermission + ); + } }, }, _ => (), }; } + let mut collection_details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + collection_details.attributes.saturating_dec(); - match namespace { - AttributeNamespace::CollectionOwner => { + + match deposit.account { + Some(deposit_account) => { + T::Currency::unreserve(&deposit_account, deposit.amount); + }, + None if namespace == AttributeNamespace::CollectionOwner => { collection_details.owner_deposit.saturating_reduce(deposit.amount); T::Currency::unreserve(&collection_details.owner, deposit.amount); }, _ => (), - }; - - if let Some(deposit_account) = deposit.account { - T::Currency::unreserve(&deposit_account, deposit.amount); } Collection::::insert(collection, &collection_details); @@ -288,12 +359,12 @@ impl, I: 'static> Pallet { origin: &T::AccountId, namespace: &AttributeNamespace, collection: &T::CollectionId, - collection_owner: &T::AccountId, maybe_item: &Option, ) -> Result { let mut result = false; match namespace { - AttributeNamespace::CollectionOwner => result = origin == collection_owner, + AttributeNamespace::CollectionOwner => + result = Self::has_role(&collection, &origin, CollectionRole::Admin), AttributeNamespace::ItemOwner => if let Some(item) = maybe_item { let item_details = @@ -323,4 +394,19 @@ impl, I: 'static> Pallet { ) -> Result, DispatchError> { Ok(BoundedVec::try_from(value).map_err(|_| Error::::IncorrectData)?) } + + /// A helper method to check whether a system attribute is set for a given item. + pub fn has_system_attribute( + collection: &T::CollectionId, + item: &T::ItemId, + attribute_key: PalletAttributes, + ) -> Result { + let attribute = ( + &collection, + Some(item), + AttributeNamespace::Pallet, + &Self::construct_attribute_key(attribute_key.encode())?, + ); + Ok(Attribute::::contains_key(attribute)) + } } diff --git a/frame/nfts/src/features/buy_sell.rs b/frame/nfts/src/features/buy_sell.rs index 8ba5171f8d82..ad721e0748ad 100644 --- a/frame/nfts/src/features/buy_sell.rs +++ b/frame/nfts/src/features/buy_sell.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/nfts/src/features/create_delete_collection.rs b/frame/nfts/src/features/create_delete_collection.rs index a2caa04bc8dc..e9434760628e 100644 --- a/frame/nfts/src/features/create_delete_collection.rs +++ b/frame/nfts/src/features/create_delete_collection.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,6 +38,7 @@ impl, I: 'static> Pallet { owner_deposit: deposit, items: 0, item_metadatas: 0, + item_configs: 0, attributes: 0, }, ); @@ -71,24 +72,23 @@ impl, I: 'static> Pallet { if let Some(check_owner) = maybe_check_owner { ensure!(collection_details.owner == check_owner, Error::::NoPermission); } - ensure!(collection_details.items == witness.items, Error::::BadWitness); + ensure!(collection_details.items == 0, Error::::CollectionNotEmpty); + ensure!(collection_details.attributes == witness.attributes, Error::::BadWitness); ensure!( collection_details.item_metadatas == witness.item_metadatas, Error::::BadWitness ); - ensure!(collection_details.attributes == witness.attributes, Error::::BadWitness); + ensure!( + collection_details.item_configs == witness.item_configs, + Error::::BadWitness + ); - for (item, details) in Item::::drain_prefix(&collection) { - Account::::remove((&details.owner, &collection, &item)); - T::Currency::unreserve(&details.deposit.account, details.deposit.amount); - } for (_, metadata) in ItemMetadataOf::::drain_prefix(&collection) { if let Some(depositor) = metadata.deposit.account { T::Currency::unreserve(&depositor, metadata.deposit.amount); } } - let _ = ItemPriceOf::::clear_prefix(&collection, witness.items, None); - let _ = PendingSwapOf::::clear_prefix(&collection, witness.items, None); + CollectionMetadataOf::::remove(&collection); Self::clear_roles(&collection)?; @@ -103,15 +103,13 @@ impl, I: 'static> Pallet { CollectionAccount::::remove(&collection_details.owner, &collection); T::Currency::unreserve(&collection_details.owner, collection_details.owner_deposit); CollectionConfigOf::::remove(&collection); - let _ = ItemConfigOf::::clear_prefix(&collection, witness.items, None); - let _ = - ItemAttributesApprovalsOf::::clear_prefix(&collection, witness.items, None); + let _ = ItemConfigOf::::clear_prefix(&collection, witness.item_configs, None); Self::deposit_event(Event::Destroyed { collection }); Ok(DestroyWitness { - items: collection_details.items, item_metadatas: collection_details.item_metadatas, + item_configs: collection_details.item_configs, attributes: collection_details.attributes, }) }) diff --git a/frame/nfts/src/features/create_delete_item.rs b/frame/nfts/src/features/create_delete_item.rs index f724fe5c63b4..e3d1334d48ef 100644 --- a/frame/nfts/src/features/create_delete_item.rs +++ b/frame/nfts/src/features/create_delete_item.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +16,7 @@ // limitations under the License. use crate::*; -use frame_support::pallet_prelude::*; +use frame_support::{pallet_prelude::*, traits::ExistenceRequirement}; impl, I: 'static> Pallet { pub fn do_mint( @@ -66,6 +66,7 @@ impl, I: 'static> Pallet { ensure!(existing_config == item_config, Error::::InconsistentItemConfig); } else { ItemConfigOf::::insert(&collection, &item, item_config); + collection_details.item_configs.saturating_inc(); } T::Currency::reserve(&deposit_account, deposit_amount)?; @@ -85,13 +86,97 @@ impl, I: 'static> Pallet { Ok(()) } + pub(crate) fn do_mint_pre_signed( + mint_to: T::AccountId, + mint_data: PreSignedMintOf, + signer: T::AccountId, + ) -> DispatchResult { + let PreSignedMint { + collection, + item, + attributes, + metadata, + deadline, + only_account, + mint_price, + } = mint_data; + let metadata = Self::construct_metadata(metadata)?; + + ensure!( + attributes.len() <= T::MaxAttributesPerCall::get() as usize, + Error::::MaxAttributesLimitReached + ); + if let Some(account) = only_account { + ensure!(account == mint_to, Error::::WrongOrigin); + } + + let now = frame_system::Pallet::::block_number(); + ensure!(deadline >= now, Error::::DeadlineExpired); + + ensure!( + Self::has_role(&collection, &signer, CollectionRole::Issuer), + Error::::NoPermission + ); + + let item_config = ItemConfig { settings: Self::get_default_item_settings(&collection)? }; + Self::do_mint( + collection, + item, + Some(mint_to.clone()), + mint_to.clone(), + item_config, + |collection_details, _| { + if let Some(price) = mint_price { + T::Currency::transfer( + &mint_to, + &collection_details.owner, + price, + ExistenceRequirement::KeepAlive, + )?; + } + Ok(()) + }, + )?; + let admin_account = Self::find_account_by_role(&collection, CollectionRole::Admin); + if let Some(admin_account) = admin_account { + for (key, value) in attributes { + Self::do_set_attribute( + admin_account.clone(), + collection, + Some(item), + AttributeNamespace::CollectionOwner, + Self::construct_attribute_key(key)?, + Self::construct_attribute_value(value)?, + mint_to.clone(), + )?; + } + if !metadata.len().is_zero() { + Self::do_set_item_metadata( + Some(admin_account.clone()), + collection, + item, + metadata, + Some(mint_to.clone()), + )?; + } + } + Ok(()) + } + pub fn do_burn( collection: T::CollectionId, item: T::ItemId, with_details: impl FnOnce(&ItemDetailsFor) -> DispatchResult, ) -> DispatchResult { ensure!(!T::Locker::is_locked(collection, item), Error::::ItemLocked); + ensure!( + !Self::has_system_attribute(&collection, &item, PalletAttributes::TransferDisabled)?, + Error::::ItemLocked + ); let item_config = Self::get_item_config(&collection, &item)?; + // NOTE: if item's settings are not empty (e.g. item's metadata is locked) + // then we keep the config record and don't remove it + let remove_config = !item_config.has_disabled_settings(); let owner = Collection::::try_mutate( &collection, |maybe_collection_details| -> Result { @@ -105,6 +190,10 @@ impl, I: 'static> Pallet { T::Currency::unreserve(&details.deposit.account, details.deposit.amount); collection_details.items.saturating_dec(); + if remove_config { + collection_details.item_configs.saturating_dec(); + } + // Clear the metadata if it's not locked. if item_config.is_setting_enabled(ItemSetting::UnlockedMetadata) { if let Some(metadata) = ItemMetadataOf::::take(&collection, &item) { @@ -132,9 +221,7 @@ impl, I: 'static> Pallet { PendingSwapOf::::remove(&collection, &item); ItemAttributesApprovalsOf::::remove(&collection, &item); - // NOTE: if item's settings are not empty (e.g. item's metadata is locked) - // then we keep the record and don't remove it - if !item_config.has_disabled_settings() { + if remove_config { ItemConfigOf::::remove(&collection, &item); } diff --git a/frame/nfts/src/features/lock.rs b/frame/nfts/src/features/lock.rs index e96a30dfd2c7..8b4914baeb45 100644 --- a/frame/nfts/src/features/lock.rs +++ b/frame/nfts/src/features/lock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,10 +24,7 @@ impl, I: 'static> Pallet { collection: T::CollectionId, lock_settings: CollectionSettings, ) -> DispatchResult { - ensure!( - Self::has_role(&collection, &origin, CollectionRole::Freezer), - Error::::NoPermission - ); + ensure!(Self::collection_owner(collection) == Some(origin), Error::::NoPermission); ensure!( !lock_settings.is_disabled(CollectionSetting::DepositRequired), Error::::WrongSetting @@ -85,17 +82,17 @@ impl, I: 'static> Pallet { } pub(crate) fn do_lock_item_properties( - maybe_check_owner: Option, + maybe_check_origin: Option, collection: T::CollectionId, item: T::ItemId, lock_metadata: bool, lock_attributes: bool, ) -> DispatchResult { - let collection_details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); + if let Some(check_origin) = &maybe_check_origin { + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Admin), + Error::::NoPermission + ); } ItemConfigOf::::try_mutate(collection, item, |maybe_config| { diff --git a/frame/nfts/src/features/metadata.rs b/frame/nfts/src/features/metadata.rs index 272b2247426d..fde0296784d1 100644 --- a/frame/nfts/src/features/metadata.rs +++ b/frame/nfts/src/features/metadata.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,13 +21,20 @@ use frame_support::pallet_prelude::*; impl, I: 'static> Pallet { /// Note: if `maybe_depositor` is None, that means the depositor will be a collection's owner pub(crate) fn do_set_item_metadata( - maybe_check_owner: Option, + maybe_check_origin: Option, collection: T::CollectionId, item: T::ItemId, data: BoundedVec, maybe_depositor: Option, ) -> DispatchResult { - let is_root = maybe_check_owner.is_none(); + if let Some(check_origin) = &maybe_check_origin { + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Admin), + Error::::NoPermission + ); + } + + let is_root = maybe_check_origin.is_none(); let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; @@ -37,10 +44,6 @@ impl, I: 'static> Pallet { Error::::LockedItemMetadata ); - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } - let collection_config = Self::get_collection_config(&collection)?; ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { @@ -60,14 +63,16 @@ impl, I: 'static> Pallet { .saturating_add(T::MetadataDepositBase::get()); } - // the previous deposit was taken from the item's owner - if old_deposit.account.is_some() && maybe_depositor.is_none() { - T::Currency::unreserve(&old_deposit.account.unwrap(), old_deposit.amount); - T::Currency::reserve(&collection_details.owner, deposit)?; + let depositor = maybe_depositor.clone().unwrap_or(collection_details.owner.clone()); + let old_depositor = old_deposit.account.unwrap_or(collection_details.owner.clone()); + + if depositor != old_depositor { + T::Currency::unreserve(&old_depositor, old_deposit.amount); + T::Currency::reserve(&depositor, deposit)?; } else if deposit > old_deposit.amount { - T::Currency::reserve(&collection_details.owner, deposit - old_deposit.amount)?; + T::Currency::reserve(&depositor, deposit - old_deposit.amount)?; } else if deposit < old_deposit.amount { - T::Currency::unreserve(&collection_details.owner, old_deposit.amount - deposit); + T::Currency::unreserve(&depositor, old_deposit.amount - deposit); } if maybe_depositor.is_none() { @@ -87,22 +92,26 @@ impl, I: 'static> Pallet { } pub(crate) fn do_clear_item_metadata( - maybe_check_owner: Option, + maybe_check_origin: Option, collection: T::CollectionId, item: T::ItemId, ) -> DispatchResult { - let is_root = maybe_check_owner.is_none(); + if let Some(check_origin) = &maybe_check_origin { + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Admin), + Error::::NoPermission + ); + } + + let is_root = maybe_check_origin.is_none(); let metadata = ItemMetadataOf::::take(collection, item) .ok_or(Error::::MetadataNotFound)?; let mut collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + let depositor_account = metadata.deposit.account.unwrap_or(collection_details.owner.clone()); - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &collection_details.owner, Error::::NoPermission); - } - // NOTE: if the item was previously burned, the ItemConfigOf record might not exist let is_locked = Self::get_item_config(&collection, &item) .map_or(false, |c| c.has_disabled_setting(ItemSetting::UnlockedMetadata)); @@ -123,29 +132,32 @@ impl, I: 'static> Pallet { } pub(crate) fn do_set_collection_metadata( - maybe_check_owner: Option, + maybe_check_origin: Option, collection: T::CollectionId, data: BoundedVec, ) -> DispatchResult { + if let Some(check_origin) = &maybe_check_origin { + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Admin), + Error::::NoPermission + ); + } + + let is_root = maybe_check_origin.is_none(); let collection_config = Self::get_collection_config(&collection)?; ensure!( - maybe_check_owner.is_none() || - collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), + is_root || collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), Error::::LockedCollectionMetadata ); let mut details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &details.owner, Error::::NoPermission); - } CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { let old_deposit = metadata.take().map_or(Zero::zero(), |m| m.deposit); details.owner_deposit.saturating_reduce(old_deposit); let mut deposit = Zero::zero(); - if maybe_check_owner.is_some() && - collection_config.is_setting_enabled(CollectionSetting::DepositRequired) + if !is_root && collection_config.is_setting_enabled(CollectionSetting::DepositRequired) { deposit = T::DepositPerByte::get() .saturating_mul(((data.len()) as u32).into()) @@ -168,18 +180,22 @@ impl, I: 'static> Pallet { } pub(crate) fn do_clear_collection_metadata( - maybe_check_owner: Option, + maybe_check_origin: Option, collection: T::CollectionId, ) -> DispatchResult { - let details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &details.owner, Error::::NoPermission); + if let Some(check_origin) = &maybe_check_origin { + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Admin), + Error::::NoPermission + ); } + let details = + Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; let collection_config = Self::get_collection_config(&collection)?; + ensure!( - maybe_check_owner.is_none() || + maybe_check_origin.is_none() || collection_config.is_setting_enabled(CollectionSetting::UnlockedMetadata), Error::::LockedCollectionMetadata ); @@ -191,4 +207,11 @@ impl, I: 'static> Pallet { Ok(()) }) } + + /// A helper method to construct metadata. + pub fn construct_metadata( + metadata: Vec, + ) -> Result, DispatchError> { + Ok(BoundedVec::try_from(metadata).map_err(|_| Error::::IncorrectMetadata)?) + } } diff --git a/frame/nfts/src/features/mod.rs b/frame/nfts/src/features/mod.rs index b77ee9bf2491..752feaf5db85 100644 --- a/frame/nfts/src/features/mod.rs +++ b/frame/nfts/src/features/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/nfts/src/features/roles.rs b/frame/nfts/src/features/roles.rs index d6be9965a5e7..3bac002069cf 100644 --- a/frame/nfts/src/features/roles.rs +++ b/frame/nfts/src/features/roles.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,24 +23,46 @@ impl, I: 'static> Pallet { pub(crate) fn do_set_team( maybe_check_owner: Option, collection: T::CollectionId, - issuer: T::AccountId, - admin: T::AccountId, - freezer: T::AccountId, + issuer: Option, + admin: Option, + freezer: Option, ) -> DispatchResult { Collection::::try_mutate(collection, |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; + let is_root = maybe_check_owner.is_none(); if let Some(check_origin) = maybe_check_owner { ensure!(check_origin == details.owner, Error::::NoPermission); } - // delete previous values - Self::clear_roles(&collection)?; - - let account_to_role = Self::group_roles_by_account(vec![ + let roles_map = [ (issuer.clone(), CollectionRole::Issuer), (admin.clone(), CollectionRole::Admin), (freezer.clone(), CollectionRole::Freezer), - ]); + ]; + + // only root can change the role from `None` to `Some(account)` + if !is_root { + for (account, role) in roles_map.iter() { + if account.is_some() { + ensure!( + Self::find_account_by_role(&collection, *role).is_some(), + Error::::NoPermission + ); + } + } + } + + let roles = roles_map + .into_iter() + .filter_map(|(account, role)| account.map(|account| (account, role))) + .collect(); + + let account_to_role = Self::group_roles_by_account(roles); + + // delete the previous records + Self::clear_roles(&collection)?; + + // insert new records for (account, roles) in account_to_role { CollectionRoleOf::::insert(&collection, &account, roles); } @@ -82,6 +104,21 @@ impl, I: 'static> Pallet { .map_or(false, |roles| roles.has_role(role)) } + /// Finds the account by a provided role within a collection. + /// + /// - `collection_id`: A collection to check the role in. + /// - `role`: A role to find the account for. + /// + /// Returns `Some(T::AccountId)` if the record was found, `None` otherwise. + pub(crate) fn find_account_by_role( + collection_id: &T::CollectionId, + role: CollectionRole, + ) -> Option { + CollectionRoleOf::::iter_prefix(&collection_id).into_iter().find_map( + |(account, roles)| if roles.has_role(role) { Some(account.clone()) } else { None }, + ) + } + /// Groups provided roles by account, given one account could have multiple roles. /// /// - `input`: A vector of (Account, Role) tuples. diff --git a/frame/nfts/src/features/settings.rs b/frame/nfts/src/features/settings.rs index 5f408ed183c3..3d96a411ae70 100644 --- a/frame/nfts/src/features/settings.rs +++ b/frame/nfts/src/features/settings.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -57,18 +57,19 @@ impl, I: 'static> Pallet { } pub(crate) fn do_update_mint_settings( - maybe_check_owner: Option, + maybe_check_origin: Option, collection: T::CollectionId, mint_settings: MintSettings< BalanceOf, - ::BlockNumber, + frame_system::pallet_prelude::BlockNumberFor, T::CollectionId, >, ) -> DispatchResult { - let details = - Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; - if let Some(check_owner) = &maybe_check_owner { - ensure!(check_owner == &details.owner, Error::::NoPermission); + if let Some(check_origin) = &maybe_check_origin { + ensure!( + Self::has_role(&collection, &check_origin, CollectionRole::Issuer), + Error::::NoPermission + ); } CollectionConfigOf::::try_mutate(collection, |maybe_config| { @@ -96,6 +97,13 @@ impl, I: 'static> Pallet { Ok(config) } + pub(crate) fn get_default_item_settings( + collection_id: &T::CollectionId, + ) -> Result { + let collection_config = Self::get_collection_config(collection_id)?; + Ok(collection_config.mint_settings.default_item_settings) + } + pub(crate) fn is_pallet_feature_enabled(feature: PalletFeature) -> bool { let features = T::Features::get(); return features.is_enabled(feature) diff --git a/frame/nfts/src/features/transfer.rs b/frame/nfts/src/features/transfer.rs index 7d6ae3553a36..69209e1bb6c4 100644 --- a/frame/nfts/src/features/transfer.rs +++ b/frame/nfts/src/features/transfer.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,7 +30,12 @@ impl, I: 'static> Pallet { ) -> DispatchResult { let collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; + ensure!(!T::Locker::is_locked(collection, item), Error::::ItemLocked); + ensure!( + !Self::has_system_attribute(&collection, &item, PalletAttributes::TransferDisabled)?, + Error::::ItemLocked + ); let collection_config = Self::get_collection_config(&collection)?; ensure!( @@ -48,23 +53,13 @@ impl, I: 'static> Pallet { Item::::get(&collection, &item).ok_or(Error::::UnknownItem)?; with_details(&collection_details, &mut details)?; - if details.deposit.account == details.owner { - // Move the deposit to the new owner. - T::Currency::repatriate_reserved( - &details.owner, - &dest, - details.deposit.amount, - Reserved, - )?; - } - Account::::remove((&details.owner, &collection, &item)); Account::::insert((&dest, &collection, &item), ()); let origin = details.owner; details.owner = dest; // The approved accounts have to be reset to None, because otherwise pre-approve attack - // would be possible, where the owner can approve his second account before making the + // would be possible, where the owner can approve their second account before making the // transaction and then claiming the item back. details.approvals.clear(); diff --git a/frame/nfts/src/impl_nonfungibles.rs b/frame/nfts/src/impl_nonfungibles.rs index 86d82b402151..090a6a372c1e 100644 --- a/frame/nfts/src/impl_nonfungibles.rs +++ b/frame/nfts/src/impl_nonfungibles.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,18 +50,48 @@ impl, I: 'static> Inspect<::AccountId> for Palle fn attribute( collection: &Self::CollectionId, item: &Self::ItemId, - namespace: &AttributeNamespace<::AccountId>, key: &[u8], ) -> Option> { if key.is_empty() { // We make the empty key map to the item metadata value. ItemMetadataOf::::get(collection, item).map(|m| m.data.into()) } else { + let namespace = AttributeNamespace::CollectionOwner; let key = BoundedSlice::<_, _>::try_from(key).ok()?; Attribute::::get((collection, Some(item), namespace, key)).map(|a| a.0.into()) } } + /// Returns the custom attribute value of `item` of `collection` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn custom_attribute( + account: &T::AccountId, + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &[u8], + ) -> Option> { + let namespace = Account::::get((account, collection, item)) + .map(|_| AttributeNamespace::ItemOwner) + .unwrap_or_else(|| AttributeNamespace::Account(account.clone())); + + let key = BoundedSlice::<_, _>::try_from(key).ok()?; + Attribute::::get((collection, Some(item), namespace, key)).map(|a| a.0.into()) + } + + /// Returns the system attribute value of `item` of `collection` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn system_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &[u8], + ) -> Option> { + let namespace = AttributeNamespace::Pallet; + let key = BoundedSlice::<_, _>::try_from(key).ok()?; + Attribute::::get((collection, Some(item), namespace, key)).map(|a| a.0.into()) + } + /// Returns the attribute value of `item` of `collection` corresponding to `key`. /// /// When `key` is empty, we return the item metadata value. @@ -87,6 +117,11 @@ impl, I: 'static> Inspect<::AccountId> for Palle /// /// Default implementation is that all items are transferable. fn can_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> bool { + use PalletAttributes::TransferDisabled; + match Self::has_system_attribute(&collection, &item, TransferDisabled) { + Ok(transfer_disabled) if transfer_disabled => return false, + _ => (), + } match ( CollectionConfigOf::::get(collection), ItemConfigOf::::get(collection, item), @@ -100,6 +135,18 @@ impl, I: 'static> Inspect<::AccountId> for Palle } } +impl, I: 'static> InspectRole<::AccountId> for Pallet { + fn is_issuer(collection: &Self::CollectionId, who: &::AccountId) -> bool { + Self::has_role(collection, who, CollectionRole::Issuer) + } + fn is_admin(collection: &Self::CollectionId, who: &::AccountId) -> bool { + Self::has_role(collection, who, CollectionRole::Admin) + } + fn is_freezer(collection: &Self::CollectionId, who: &::AccountId) -> bool { + Self::has_role(collection, who, CollectionRole::Freezer) + } +} + impl, I: 'static> Create<::AccountId, CollectionConfigFor> for Pallet { @@ -292,6 +339,30 @@ impl, I: 'static> Transfer for Pallet { ) -> DispatchResult { Self::do_transfer(*collection, *item, destination.clone(), |_, _| Ok(())) } + + fn disable_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> DispatchResult { + let transfer_disabled = + Self::has_system_attribute(&collection, &item, PalletAttributes::TransferDisabled)?; + // Can't lock the item twice + if transfer_disabled { + return Err(Error::::ItemLocked.into()) + } + + >::set_attribute( + collection, + item, + &PalletAttributes::::TransferDisabled.encode(), + &[], + ) + } + + fn enable_transfer(collection: &Self::CollectionId, item: &Self::ItemId) -> DispatchResult { + >::clear_attribute( + collection, + item, + &PalletAttributes::::TransferDisabled.encode(), + ) + } } impl, I: 'static> InspectEnumerable for Pallet { diff --git a/frame/nfts/src/lib.rs b/frame/nfts/src/lib.rs index 8f24c8dcd6e9..8124c7168245 100644 --- a/frame/nfts/src/lib.rs +++ b/frame/nfts/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,6 +30,7 @@ #[cfg(feature = "runtime-benchmarks")] mod benchmarking; +pub mod migration; #[cfg(test)] pub mod mock; #[cfg(test)] @@ -45,13 +46,12 @@ pub mod weights; use codec::{Decode, Encode}; use frame_support::traits::{ - tokens::{AttributeNamespace, Locker}, - BalanceStatus::Reserved, - Currency, EnsureOriginWithArg, ReservableCurrency, + tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, Incrementable, + ReservableCurrency, }; use frame_system::Config as SystemConfig; use sp_runtime::{ - traits::{Saturating, StaticLookup, Zero}, + traits::{IdentifyAccount, Saturating, StaticLookup, Verify, Zero}, RuntimeDebug, }; use sp_std::prelude::*; @@ -60,6 +60,9 @@ pub use pallet::*; pub use types::*; pub use weights::WeightInfo; +/// The log target of this pallet. +pub const LOG_TARGET: &'static str = "runtime::nfts"; + type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; #[frame_support::pallet] @@ -68,9 +71,12 @@ pub mod pallet { use frame_support::{pallet_prelude::*, traits::ExistenceRequirement}; use frame_system::pallet_prelude::*; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(_); + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(PhantomData<(T, I)>); #[cfg(feature = "runtime-benchmarks")] pub trait BenchmarkHelper { @@ -165,12 +171,26 @@ pub mod pallet { /// The max duration in blocks for deadlines. #[pallet::constant] - type MaxDeadlineDuration: Get<::BlockNumber>; + type MaxDeadlineDuration: Get>; + + /// The max number of attributes a user could set per call. + #[pallet::constant] + type MaxAttributesPerCall: Get; /// Disables some of pallet's features. #[pallet::constant] type Features: Get; + /// Off-Chain signature type. + /// + /// Can verify whether an `Self::OffchainPublic` created a signature. + type OffchainSignature: Verify + Parameter; + + /// Off-Chain public key. + /// + /// Must identify as an on-chain `Self::AccountId`. + type OffchainPublic: IdentifyAccount; + #[cfg(feature = "runtime-benchmarks")] /// A set of helper functions for benchmarking. type Helper: BenchmarkHelper; @@ -181,7 +201,7 @@ pub mod pallet { /// Details of a collection. #[pallet::storage] - pub(super) type Collection, I: 'static = ()> = StorageMap< + pub type Collection, I: 'static = ()> = StorageMap< _, Blake2_128Concat, T::CollectionId, @@ -190,13 +210,13 @@ pub mod pallet { /// The collection, if any, of which an account is willing to take ownership. #[pallet::storage] - pub(super) type OwnershipAcceptance, I: 'static = ()> = + pub type OwnershipAcceptance, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::AccountId, T::CollectionId>; /// The items held by any given account; set out this way so that items owned by a single /// account can be enumerated. #[pallet::storage] - pub(super) type Account, I: 'static = ()> = StorageNMap< + pub type Account, I: 'static = ()> = StorageNMap< _, ( NMapKey, // owner @@ -210,7 +230,7 @@ pub mod pallet { /// The collections owned by any given account; set out this way so that collections owned by /// a single account can be enumerated. #[pallet::storage] - pub(super) type CollectionAccount, I: 'static = ()> = StorageDoubleMap< + pub type CollectionAccount, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::AccountId, @@ -223,7 +243,7 @@ pub mod pallet { /// The items in existence and their ownership details. #[pallet::storage] /// Stores collection roles as per account. - pub(super) type CollectionRoleOf, I: 'static = ()> = StorageDoubleMap< + pub type CollectionRoleOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::CollectionId, @@ -235,7 +255,7 @@ pub mod pallet { /// The items in existence and their ownership details. #[pallet::storage] - pub(super) type Item, I: 'static = ()> = StorageDoubleMap< + pub type Item, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::CollectionId, @@ -247,7 +267,7 @@ pub mod pallet { /// Metadata of a collection. #[pallet::storage] - pub(super) type CollectionMetadataOf, I: 'static = ()> = StorageMap< + pub type CollectionMetadataOf, I: 'static = ()> = StorageMap< _, Blake2_128Concat, T::CollectionId, @@ -257,7 +277,7 @@ pub mod pallet { /// Metadata of an item. #[pallet::storage] - pub(super) type ItemMetadataOf, I: 'static = ()> = StorageDoubleMap< + pub type ItemMetadataOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::CollectionId, @@ -269,7 +289,7 @@ pub mod pallet { /// Attributes of a collection. #[pallet::storage] - pub(super) type Attribute, I: 'static = ()> = StorageNMap< + pub type Attribute, I: 'static = ()> = StorageNMap< _, ( NMapKey, @@ -283,7 +303,7 @@ pub mod pallet { /// A price of an item. #[pallet::storage] - pub(super) type ItemPriceOf, I: 'static = ()> = StorageDoubleMap< + pub type ItemPriceOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::CollectionId, @@ -295,7 +315,7 @@ pub mod pallet { /// Item attribute approvals. #[pallet::storage] - pub(super) type ItemAttributesApprovalsOf, I: 'static = ()> = StorageDoubleMap< + pub type ItemAttributesApprovalsOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::CollectionId, @@ -308,12 +328,12 @@ pub mod pallet { /// Stores the `CollectionId` that is going to be used for the next collection. /// This gets incremented whenever a new collection is created. #[pallet::storage] - pub(super) type NextCollectionId, I: 'static = ()> = + pub type NextCollectionId, I: 'static = ()> = StorageValue<_, T::CollectionId, OptionQuery>; /// Handles all the pending swaps. #[pallet::storage] - pub(super) type PendingSwapOf, I: 'static = ()> = StorageDoubleMap< + pub type PendingSwapOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::CollectionId, @@ -323,19 +343,19 @@ pub mod pallet { T::CollectionId, T::ItemId, PriceWithDirection>, - ::BlockNumber, + BlockNumberFor, >, OptionQuery, >; /// Config of a collection. #[pallet::storage] - pub(super) type CollectionConfigOf, I: 'static = ()> = + pub type CollectionConfigOf, I: 'static = ()> = StorageMap<_, Blake2_128Concat, T::CollectionId, CollectionConfigFor, OptionQuery>; /// Config of an item. #[pallet::storage] - pub(super) type ItemConfigOf, I: 'static = ()> = StorageDoubleMap< + pub type ItemConfigOf, I: 'static = ()> = StorageDoubleMap< _, Blake2_128Concat, T::CollectionId, @@ -383,9 +403,9 @@ pub mod pallet { /// The management team changed. TeamChanged { collection: T::CollectionId, - issuer: T::AccountId, - admin: T::AccountId, - freezer: T::AccountId, + issuer: Option, + admin: Option, + freezer: Option, }, /// An `item` of a `collection` has been approved by the `owner` for transfer by /// a `delegate`. @@ -394,7 +414,7 @@ pub mod pallet { item: T::ItemId, owner: T::AccountId, delegate: T::AccountId, - deadline: Option<::BlockNumber>, + deadline: Option>, }, /// An approval for a `delegate` account to transfer the `item` of an item /// `collection` was cancelled by its `owner`. @@ -489,7 +509,7 @@ pub mod pallet { desired_collection: T::CollectionId, desired_item: Option, price: Option>>, - deadline: ::BlockNumber, + deadline: BlockNumberFor, }, /// The swap was cancelled. SwapCancelled { @@ -498,7 +518,7 @@ pub mod pallet { desired_collection: T::CollectionId, desired_item: Option, price: Option>>, - deadline: ::BlockNumber, + deadline: BlockNumberFor, }, /// The swap has been claimed. SwapClaimed { @@ -509,7 +529,21 @@ pub mod pallet { received_item: T::ItemId, received_item_owner: T::AccountId, price: Option>>, - deadline: ::BlockNumber, + deadline: BlockNumberFor, + }, + /// New attributes have been set for an `item` of the `collection`. + PreSignedAttributesSet { + collection: T::CollectionId, + item: T::ItemId, + namespace: AttributeNamespace, + }, + /// A new attribute in the `Pallet` namespace was set for the `collection` or an `item` + /// within that `collection`. + PalletAttributeSet { + collection: T::CollectionId, + item: Option, + attribute: PalletAttributes, + value: BoundedVec, }, } @@ -591,6 +625,20 @@ pub mod pallet { AlreadyClaimed, /// The provided data is incorrect. IncorrectData, + /// The extrinsic was sent by the wrong origin. + WrongOrigin, + /// The provided signature is incorrect. + WrongSignature, + /// The provided metadata might be too long. + IncorrectMetadata, + /// Can't set more attributes per one call. + MaxAttributesLimitReached, + /// The provided namespace isn't supported in this call. + WrongNamespace, + /// Can't delete non-empty collections. + CollectionNotEmpty, + /// The witness data should be provided. + WitnessRequired, } #[pallet::call] @@ -682,20 +730,22 @@ pub mod pallet { /// The origin must conform to `ForceOrigin` or must be `Signed` and the sender must be the /// owner of the `collection`. /// + /// NOTE: The collection must have 0 items to be destroyed. + /// /// - `collection`: The identifier of the collection to be destroyed. /// - `witness`: Information on the items minted in the collection. This must be /// correct. /// /// Emits `Destroyed` event when successful. /// - /// Weight: `O(n + m)` where: - /// - `n = witness.items` + /// Weight: `O(m + c + a)` where: /// - `m = witness.item_metadatas` + /// - `c = witness.item_configs` /// - `a = witness.attributes` #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::destroy( - witness.items, - witness.item_metadatas, + witness.item_metadatas, + witness.item_configs, witness.attributes, ))] pub fn destroy( @@ -709,8 +759,8 @@ pub mod pallet { let details = Self::do_destroy_collection(collection, witness, maybe_check_owner)?; Ok(Some(T::WeightInfo::destroy( - details.items, details.item_metadatas, + details.item_configs, details.attributes, )) .into()) @@ -718,13 +768,14 @@ pub mod pallet { /// Mint an item of a particular collection. /// - /// The origin must be Signed and the sender must be the Issuer of the `collection`. + /// The origin must be Signed and the sender must comply with the `mint_settings` rules. /// /// - `collection`: The collection of the item to be minted. /// - `item`: An identifier of the new item. /// - `mint_to`: Account into which the item will be minted. /// - `witness_data`: When the mint type is `HolderOf(collection_id)`, then the owned - /// item_id from that collection needs to be provided within the witness data object. + /// item_id from that collection needs to be provided within the witness data object. If + /// the mint price is set, then it should be additionally confirmed in the `witness_data`. /// /// Note: the deposit will be taken from the `origin` and not the `owner` of the `item`. /// @@ -738,14 +789,12 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, mint_to: AccountIdLookupOf, - witness_data: Option>, + witness_data: Option>>, ) -> DispatchResult { let caller = ensure_signed(origin)?; let mint_to = T::Lookup::lookup(mint_to)?; - - let collection_config = Self::get_collection_config(&collection)?; - let item_settings = collection_config.mint_settings.default_item_settings; - let item_config = ItemConfig { settings: item_settings }; + let item_config = + ItemConfig { settings: Self::get_default_item_settings(&collection)? }; Self::do_mint( collection, @@ -754,11 +803,6 @@ pub mod pallet { mint_to.clone(), item_config, |collection_details, collection_config| { - // Issuer can mint regardless of mint settings - if Self::has_role(&collection, &caller, CollectionRole::Issuer) { - return Ok(()) - } - let mint_settings = collection_config.mint_settings; let now = frame_system::Pallet::::block_number(); @@ -770,42 +814,58 @@ pub mod pallet { } match mint_settings.mint_type { - MintType::Issuer => return Err(Error::::NoPermission.into()), + MintType::Issuer => { + ensure!( + Self::has_role(&collection, &caller, CollectionRole::Issuer), + Error::::NoPermission + ); + }, MintType::HolderOf(collection_id) => { - let MintWitness { owner_of_item } = - witness_data.ok_or(Error::::BadWitness)?; + let MintWitness { owned_item, .. } = + witness_data.clone().ok_or(Error::::WitnessRequired)?; - let has_item = Account::::contains_key(( + let owns_item = Account::::contains_key(( &caller, &collection_id, - &owner_of_item, + &owned_item, )); - ensure!(has_item, Error::::BadWitness); + ensure!(owns_item, Error::::BadWitness); - let attribute_key = Self::construct_attribute_key( - PalletAttributes::::UsedToClaim(collection) - .encode(), - )?; + let pallet_attribute = + PalletAttributes::::UsedToClaim(collection); let key = ( &collection_id, - Some(owner_of_item), + Some(owned_item), AttributeNamespace::Pallet, - &attribute_key, + &Self::construct_attribute_key(pallet_attribute.encode())?, ); let already_claimed = Attribute::::contains_key(key.clone()); ensure!(!already_claimed, Error::::AlreadyClaimed); - let value = Self::construct_attribute_value(vec![0])?; + let attribute_value = Self::construct_attribute_value(vec![])?; Attribute::::insert( key, - (value, AttributeDeposit { account: None, amount: Zero::zero() }), + ( + attribute_value.clone(), + AttributeDeposit { account: None, amount: Zero::zero() }, + ), ); + Self::deposit_event(Event::PalletAttributeSet { + collection, + item: Some(item), + attribute: pallet_attribute, + value: attribute_value, + }); }, _ => {}, } if let Some(price) = mint_settings.price { + let MintWitness { mint_price, .. } = + witness_data.clone().ok_or(Error::::WitnessRequired)?; + let mint_price = mint_price.ok_or(Error::::BadWitness)?; + ensure!(mint_price >= price, Error::::BadWitness); T::Currency::transfer( &caller, &collection_details.owner, @@ -857,38 +917,30 @@ pub mod pallet { /// Destroy a single item. /// - /// Origin must be Signed and the signing account must be either: - /// - the Admin of the `collection`; - /// - the Owner of the `item`; + /// The origin must conform to `ForceOrigin` or must be Signed and the signing account must + /// be the owner of the `item`. /// /// - `collection`: The collection of the item to be burned. /// - `item`: The item to be burned. - /// - `check_owner`: If `Some` then the operation will fail with `WrongOwner` unless the - /// item is owned by this value. /// - /// Emits `Burned` with the actual amount burned. + /// Emits `Burned`. /// /// Weight: `O(1)` - /// Modes: `check_owner.is_some()`. #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::burn())] pub fn burn( origin: OriginFor, collection: T::CollectionId, item: T::ItemId, - check_owner: Option>, ) -> DispatchResult { - let origin = ensure_signed(origin)?; - let check_owner = check_owner.map(T::Lookup::lookup).transpose()?; + let maybe_check_origin = T::ForceOrigin::try_origin(origin) + .map(|_| None) + .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_burn(collection, item, |details| { - let is_admin = Self::has_role(&collection, &origin, CollectionRole::Admin); - let is_permitted = is_admin || details.owner == origin; - ensure!(is_permitted, Error::::NoPermission); - ensure!( - check_owner.map_or(true, |o| o == details.owner), - Error::::WrongOwner - ); + if let Some(check_origin) = maybe_check_origin { + ensure!(details.owner == check_origin, Error::::NoPermission); + } Ok(()) }) } @@ -896,7 +948,6 @@ pub mod pallet { /// Move an item from the sender account to another. /// /// Origin must be Signed and the signing account must be either: - /// - the Admin of the `collection`; /// - the Owner of the `item`; /// - the approved delegate for the `item` (in this case, the approval is reset). /// @@ -920,8 +971,7 @@ pub mod pallet { let dest = T::Lookup::lookup(dest)?; Self::do_transfer(collection, item, dest, |_, details| { - let is_admin = Self::has_role(&collection, &origin, CollectionRole::Admin); - if details.owner != origin && !is_admin { + if details.owner != origin { let deadline = details.approvals.get(&origin).ok_or(Error::::NoPermission)?; if let Some(d) = deadline { @@ -1044,12 +1094,13 @@ pub mod pallet { /// Disallows specified settings for the whole collection. /// - /// Origin must be Signed and the sender should be the Freezer of the `collection`. + /// Origin must be Signed and the sender should be the Owner of the `collection`. /// /// - `collection`: The collection to be locked. /// - `lock_settings`: The settings to be locked. /// /// Note: it's possible to only lock(set) the setting, but not to unset it. + /// /// Emits `CollectionLocked`. /// /// Weight: `O(1)` @@ -1092,6 +1143,9 @@ pub mod pallet { /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the /// `collection`. /// + /// Note: by setting the role to `None` only the `ForceOrigin` will be able to change it + /// after to `Some(account)`. + /// /// - `collection`: The collection whose team should be changed. /// - `issuer`: The new Issuer of this collection. /// - `admin`: The new Admin of this collection. @@ -1105,16 +1159,16 @@ pub mod pallet { pub fn set_team( origin: OriginFor, collection: T::CollectionId, - issuer: AccountIdLookupOf, - admin: AccountIdLookupOf, - freezer: AccountIdLookupOf, + issuer: Option>, + admin: Option>, + freezer: Option>, ) -> DispatchResult { let maybe_check_owner = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - let issuer = T::Lookup::lookup(issuer)?; - let admin = T::Lookup::lookup(admin)?; - let freezer = T::Lookup::lookup(freezer)?; + let issuer = issuer.map(T::Lookup::lookup).transpose()?; + let admin = admin.map(T::Lookup::lookup).transpose()?; + let freezer = freezer.map(T::Lookup::lookup).transpose()?; Self::do_set_team(maybe_check_owner, collection, issuer, admin, freezer) } @@ -1182,7 +1236,7 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, delegate: AccountIdLookupOf, - maybe_deadline: Option<::BlockNumber>, + maybe_deadline: Option>, ) -> DispatchResult { let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) @@ -1201,7 +1255,6 @@ pub mod pallet { /// /// Origin must be either: /// - the `Force` origin; - /// - `Signed` with the signer being the Admin of the `collection`; /// - `Signed` with the signer being the Owner of the `item`; /// /// Arguments: @@ -1231,7 +1284,6 @@ pub mod pallet { /// /// Origin must be either: /// - the `Force` origin; - /// - `Signed` with the signer being the Admin of the `collection`; /// - `Signed` with the signer being the Owner of the `item`; /// /// Arguments: @@ -1256,8 +1308,8 @@ pub mod pallet { /// Disallows changing the metadata or attributes of the item. /// - /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the - /// `collection`. + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Admin + /// of the `collection`. /// /// - `collection`: The collection if the `item`. /// - `item`: An item to be locked. @@ -1265,8 +1317,8 @@ pub mod pallet { /// - `lock_attributes`: Specifies whether the attributes in the `CollectionOwner` namespace /// should be locked. /// - /// Note: `lock_attributes` affects the attributes in the `CollectionOwner` namespace - /// only. When the metadata or attributes are locked, it won't be possible the unlock them. + /// Note: `lock_attributes` affects the attributes in the `CollectionOwner` namespace only. + /// When the metadata or attributes are locked, it won't be possible the unlock them. /// /// Emits `ItemPropertiesLocked`. /// @@ -1280,11 +1332,11 @@ pub mod pallet { lock_metadata: bool, lock_attributes: bool, ) -> DispatchResult { - let maybe_check_owner = T::ForceOrigin::try_origin(origin) + let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; Self::do_lock_item_properties( - maybe_check_owner, + maybe_check_origin, collection, item, lock_metadata, @@ -1295,7 +1347,7 @@ pub mod pallet { /// Set an attribute for a collection or item. /// /// Origin must be Signed and must conform to the namespace ruleset: - /// - `CollectionOwner` namespace could be modified by the `collection` owner only; + /// - `CollectionOwner` namespace could be modified by the `collection` Admin only; /// - `ItemOwner` namespace could be modified by the `maybe_item` owner only. `maybe_item` /// should be set in that case; /// - `Account(AccountId)` namespace could be modified only when the `origin` was given a @@ -1325,7 +1377,12 @@ pub mod pallet { value: BoundedVec, ) -> DispatchResult { let origin = ensure_signed(origin)?; - Self::do_set_attribute(origin, collection, maybe_item, namespace, key, value) + let depositor = match namespace { + AttributeNamespace::CollectionOwner => + Self::collection_owner(collection).ok_or(Error::::UnknownCollection)?, + _ => origin.clone(), + }; + Self::do_set_attribute(origin, collection, maybe_item, namespace, key, value, depositor) } /// Force-set an attribute for a collection or item. @@ -1440,7 +1497,7 @@ pub mod pallet { /// Set the metadata for an item. /// - /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Admin of the /// `collection`. /// /// If the origin is Signed, then funds of signer are reserved according to the formula: @@ -1462,15 +1519,15 @@ pub mod pallet { item: T::ItemId, data: BoundedVec, ) -> DispatchResult { - let maybe_check_owner = T::ForceOrigin::try_origin(origin) + let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - Self::do_set_item_metadata(maybe_check_owner, collection, item, data, None) + Self::do_set_item_metadata(maybe_check_origin, collection, item, data, None) } /// Clear the metadata for an item. /// - /// Origin must be either `ForceOrigin` or Signed and the sender should be the Owner of the + /// Origin must be either `ForceOrigin` or Signed and the sender should be the Admin of the /// `collection`. /// /// Any deposit is freed for the collection's owner. @@ -1488,15 +1545,15 @@ pub mod pallet { collection: T::CollectionId, item: T::ItemId, ) -> DispatchResult { - let maybe_check_owner = T::ForceOrigin::try_origin(origin) + let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - Self::do_clear_item_metadata(maybe_check_owner, collection, item) + Self::do_clear_item_metadata(maybe_check_origin, collection, item) } /// Set the metadata for a collection. /// - /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Admin of /// the `collection`. /// /// If the origin is `Signed`, then funds of signer are reserved according to the formula: @@ -1516,15 +1573,15 @@ pub mod pallet { collection: T::CollectionId, data: BoundedVec, ) -> DispatchResult { - let maybe_check_owner = T::ForceOrigin::try_origin(origin) + let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - Self::do_set_collection_metadata(maybe_check_owner, collection, data) + Self::do_set_collection_metadata(maybe_check_origin, collection, data) } /// Clear the metadata for a collection. /// - /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Admin of /// the `collection`. /// /// Any deposit is freed for the collection's owner. @@ -1540,10 +1597,10 @@ pub mod pallet { origin: OriginFor, collection: T::CollectionId, ) -> DispatchResult { - let maybe_check_owner = T::ForceOrigin::try_origin(origin) + let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - Self::do_clear_collection_metadata(maybe_check_owner, collection) + Self::do_clear_collection_metadata(maybe_check_origin, collection) } /// Set (or reset) the acceptance of ownership for a particular account. @@ -1590,8 +1647,8 @@ pub mod pallet { /// Update mint settings. /// - /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Owner of - /// the `collection`. + /// Origin must be either `ForceOrigin` or `Signed` and the sender should be the Issuer + /// of the `collection`. /// /// - `collection`: The identifier of the collection to change. /// - `mint_settings`: The new mint settings. @@ -1602,21 +1659,17 @@ pub mod pallet { pub fn update_mint_settings( origin: OriginFor, collection: T::CollectionId, - mint_settings: MintSettings< - BalanceOf, - ::BlockNumber, - T::CollectionId, - >, + mint_settings: MintSettings, BlockNumberFor, T::CollectionId>, ) -> DispatchResult { - let maybe_check_owner = T::ForceOrigin::try_origin(origin) + let maybe_check_origin = T::ForceOrigin::try_origin(origin) .map(|_| None) .or_else(|origin| ensure_signed(origin).map(Some).map_err(DispatchError::from))?; - Self::do_update_mint_settings(maybe_check_owner, collection, mint_settings) + Self::do_update_mint_settings(maybe_check_origin, collection, mint_settings) } /// Set (or reset) the price for an item. /// - /// Origin must be Signed and must be the owner of the asset `item`. + /// Origin must be Signed and must be the owner of the `item`. /// /// - `collection`: The collection of the item. /// - `item`: The item to set the price for. @@ -1702,7 +1755,7 @@ pub mod pallet { desired_collection: T::CollectionId, maybe_desired_item: Option, maybe_price: Option>>, - duration: ::BlockNumber, + duration: BlockNumberFor, ) -> DispatchResult { let origin = ensure_signed(origin)?; Self::do_create_swap( @@ -1768,6 +1821,58 @@ pub mod pallet { witness_price, ) } + + /// Mint an item by providing the pre-signed approval. + /// + /// Origin must be Signed. + /// + /// - `mint_data`: The pre-signed approval that consists of the information about the item, + /// its metadata, attributes, who can mint it (`None` for anyone) and until what block + /// number. + /// - `signature`: The signature of the `data` object. + /// - `signer`: The `data` object's signer. Should be an Issuer of the collection. + /// + /// Emits `Issued` on success. + /// Emits `AttributeSet` if the attributes were provided. + /// Emits `ItemMetadataSet` if the metadata was not empty. + #[pallet::call_index(37)] + #[pallet::weight(T::WeightInfo::mint_pre_signed(mint_data.attributes.len() as u32))] + pub fn mint_pre_signed( + origin: OriginFor, + mint_data: Box>, + signature: T::OffchainSignature, + signer: T::AccountId, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::validate_signature(&Encode::encode(&mint_data), &signature, &signer)?; + Self::do_mint_pre_signed(origin, *mint_data, signer) + } + + /// Set attributes for an item by providing the pre-signed approval. + /// + /// Origin must be Signed and must be an owner of the `data.item`. + /// + /// - `data`: The pre-signed approval that consists of the information about the item, + /// attributes to update and until what block number. + /// - `signature`: The signature of the `data` object. + /// - `signer`: The `data` object's signer. Should be an Admin of the collection for the + /// `CollectionOwner` namespace. + /// + /// Emits `AttributeSet` for each provided attribute. + /// Emits `ItemAttributesApprovalAdded` if the approval wasn't set before. + /// Emits `PreSignedAttributesSet` on success. + #[pallet::call_index(38)] + #[pallet::weight(T::WeightInfo::set_attributes_pre_signed(data.attributes.len() as u32))] + pub fn set_attributes_pre_signed( + origin: OriginFor, + data: PreSignedAttributesOf, + signature: T::OffchainSignature, + signer: T::AccountId, + ) -> DispatchResult { + let origin = ensure_signed(origin)?; + Self::validate_signature(&Encode::encode(&data), &signature, &signer)?; + Self::do_set_attributes_pre_signed(origin, data, signer) + } } } diff --git a/frame/nfts/src/macros.rs b/frame/nfts/src/macros.rs index 07a8f3b9f955..db560cfbc33f 100644 --- a/frame/nfts/src/macros.rs +++ b/frame/nfts/src/macros.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,25 +15,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -macro_rules! impl_incrementable { - ($($type:ty),+) => { - $( - impl Incrementable for $type { - fn increment(&self) -> Self { - let mut val = self.clone(); - val.saturating_inc(); - val - } - - fn initial_value() -> Self { - 0 - } - } - )+ - }; -} -pub(crate) use impl_incrementable; - macro_rules! impl_codec_bitflags { ($wrapper:ty, $size:ty, $bitflag_enum:ty) => { impl MaxEncodedLen for $wrapper { diff --git a/frame/nfts/src/migration.rs b/frame/nfts/src/migration.rs new file mode 100644 index 000000000000..ba14492be84c --- /dev/null +++ b/frame/nfts/src/migration.rs @@ -0,0 +1,120 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use frame_support::{log, traits::OnRuntimeUpgrade}; + +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + +pub mod v1 { + use frame_support::{pallet_prelude::*, weights::Weight}; + + use super::*; + + #[derive(Decode)] + pub struct OldCollectionDetails { + pub owner: AccountId, + pub owner_deposit: DepositBalance, + pub items: u32, + pub item_metadatas: u32, + pub attributes: u32, + } + + impl OldCollectionDetails { + fn migrate_to_v1(self, item_configs: u32) -> CollectionDetails { + CollectionDetails { + owner: self.owner, + owner_deposit: self.owner_deposit, + items: self.items, + item_metadatas: self.item_metadatas, + item_configs, + attributes: self.attributes, + } + } + } + + pub struct MigrateToV1(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV1 { + fn on_runtime_upgrade() -> Weight { + let current_version = Pallet::::current_storage_version(); + let onchain_version = Pallet::::on_chain_storage_version(); + + log::info!( + target: LOG_TARGET, + "Running migration with current storage version {:?} / onchain {:?}", + current_version, + onchain_version + ); + + if onchain_version == 0 && current_version == 1 { + let mut translated = 0u64; + let mut configs_iterated = 0u64; + Collection::::translate::< + OldCollectionDetails>, + _, + >(|key, old_value| { + let item_configs = ItemConfigOf::::iter_prefix(&key).count() as u32; + configs_iterated += item_configs as u64; + translated.saturating_inc(); + Some(old_value.migrate_to_v1(item_configs)) + }); + + current_version.put::>(); + + log::info!( + target: LOG_TARGET, + "Upgraded {} records, storage to version {:?}", + translated, + current_version + ); + T::DbWeight::get().reads_writes(translated + configs_iterated + 1, translated + 1) + } else { + log::info!( + target: LOG_TARGET, + "Migration did not execute. This probably should be removed" + ); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + let current_version = Pallet::::current_storage_version(); + let onchain_version = Pallet::::on_chain_storage_version(); + ensure!(onchain_version == 0 && current_version == 1, "migration from version 0 to 1."); + let prev_count = Collection::::iter().count(); + Ok((prev_count as u32).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(prev_count: Vec) -> Result<(), TryRuntimeError> { + let prev_count: u32 = Decode::decode(&mut prev_count.as_slice()).expect( + "the state parameter should be something that was generated by pre_upgrade", + ); + let post_count = Collection::::iter().count() as u32; + ensure!( + prev_count == post_count, + "the records count before and after the migration should be the same" + ); + + ensure!(Pallet::::on_chain_storage_version() == 1, "wrong storage version"); + + Ok(()) + } + } +} diff --git a/frame/nfts/src/mock.rs b/frame/nfts/src/mock.rs index f814b209d5f7..f091a53f8d7c 100644 --- a/frame/nfts/src/mock.rs +++ b/frame/nfts/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,39 +25,39 @@ use frame_support::{ traits::{AsEnsureOriginWithArg, ConstU32, ConstU64}, }; use sp_core::H256; +use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, + traits::{BlakeTwo256, IdentifyAccount, IdentityLookup, Verify}, + BuildStorage, MultiSignature, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Nfts: pallet_nfts::{Pallet, Call, Storage, Event}, } ); +pub type Signature = MultiSignature; +pub type AccountPublic = ::Signer; +pub type AccountId = ::AccountId; + impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; - type AccountId = u64; + type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type DbWeight = (); @@ -82,6 +82,10 @@ impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = ConstU32<50>; type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } parameter_types! { @@ -93,8 +97,8 @@ impl Config for Test { type CollectionId = u32; type ItemId = u32; type Currency = Balances; - type CreateOrigin = AsEnsureOriginWithArg>; - type ForceOrigin = frame_system::EnsureRoot; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; type Locker = (); type CollectionDeposit = ConstU64<2>; type ItemDeposit = ConstU64<1>; @@ -108,16 +112,23 @@ impl Config for Test { type ItemAttributesApprovalsLimit = ConstU32<2>; type MaxTips = ConstU32<10>; type MaxDeadlineDuration = ConstU64<10000>; + type MaxAttributesPerCall = ConstU32<2>; type Features = Features; + /// Off-chain = signature On-chain - therefore no conversion needed. + /// It needs to be From for benchmarking. + type OffchainSignature = Signature; + /// Using `AccountPublic` here makes it trivial to convert to `AccountId` via `into_account()`. + type OffchainPublic = AccountPublic; type WeightInfo = (); #[cfg(feature = "runtime-benchmarks")] type Helper = (); } pub(crate) fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let mut ext = sp_io::TestExternalities::new(t); + ext.register_extension(KeystoreExt::new(MemoryKeystore::new())); ext.execute_with(|| System::set_block_number(1)); ext } diff --git a/frame/nfts/src/tests.rs b/frame/nfts/src/tests.rs index ebbba33b04fa..4df57cb13218 100644 --- a/frame/nfts/src/tests.rs +++ b/frame/nfts/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,10 +28,17 @@ use frame_support::{ }, }; use pallet_balances::Error as BalancesError; -use sp_core::bounded::BoundedVec; +use sp_core::{bounded::BoundedVec, Pair}; +use sp_runtime::{traits::IdentifyAccount, MultiSignature, MultiSigner}; use sp_std::prelude::*; -fn items() -> Vec<(u64, u32, u32)> { +type AccountIdOf = ::AccountId; + +fn account(id: u8) -> AccountIdOf { + [id; 32].into() +} + +fn items() -> Vec<(AccountIdOf, u32, u32)> { let mut r: Vec<_> = Account::::iter().map(|x| x.0).collect(); r.sort(); let mut s: Vec<_> = Item::::iter().map(|x| (x.2.owner, x.0, x.1)).collect(); @@ -56,7 +63,7 @@ fn items() -> Vec<(u64, u32, u32)> { r } -fn collections() -> Vec<(u64, u32)> { +fn collections() -> Vec<(AccountIdOf, u32)> { let mut r: Vec<_> = CollectionAccount::::iter().map(|x| (x.0, x.1)).collect(); r.sort(); let mut s: Vec<_> = Collection::::iter().map(|x| (x.1.owner, x.0)).collect(); @@ -71,22 +78,26 @@ macro_rules! bvec { } } -fn attributes(collection: u32) -> Vec<(Option, AttributeNamespace, Vec, Vec)> { +fn attributes( + collection: u32, +) -> Vec<(Option, AttributeNamespace>, Vec, Vec)> { let mut s: Vec<_> = Attribute::::iter_prefix((collection,)) .map(|(k, v)| (k.0, k.1, k.2.into(), v.0.into())) .collect(); - s.sort_by_key(|k: &(Option, AttributeNamespace, Vec, Vec)| k.0); - s.sort_by_key(|k: &(Option, AttributeNamespace, Vec, Vec)| k.2.clone()); + s.sort_by_key(|k: &(Option, AttributeNamespace>, Vec, Vec)| k.0); + s.sort_by_key(|k: &(Option, AttributeNamespace>, Vec, Vec)| { + k.2.clone() + }); s } -fn approvals(collection_id: u32, item_id: u32) -> Vec<(u64, Option)> { +fn approvals(collection_id: u32, item_id: u32) -> Vec<(AccountIdOf, Option)> { let item = Item::::get(collection_id, item_id).unwrap(); let s: Vec<_> = item.approvals.into_iter().collect(); s } -fn item_attributes_approvals(collection_id: u32, item_id: u32) -> Vec { +fn item_attributes_approvals(collection_id: u32, item_id: u32) -> Vec> { let approvals = ItemAttributesApprovalsOf::::get(collection_id, item_id); let s: Vec<_> = approvals.into_iter().collect(); s @@ -144,60 +155,106 @@ fn basic_setup_works() { #[test] fn basic_minting_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_eq!(collections(), vec![(1, 0)]); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); - assert_eq!(items(), vec![(1, 0, 42)]); - - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 2, default_collection_config())); - assert_eq!(collections(), vec![(1, 0), (2, 1)]); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 1, 69, 1, None)); - // assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(2), 1, 69, 1, default_item_config())); - assert_eq!(items(), vec![(1, 0, 42), (1, 1, 69)]); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_eq!(collections(), vec![(account(1), 0)]); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_eq!(items(), vec![(account(1), 0, 42)]); + + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(2), + default_collection_config() + )); + assert_eq!(collections(), vec![(account(1), 0), (account(2), 1)]); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(2)), 1, 69, account(1), None)); + assert_eq!(items(), vec![(account(1), 0, 42), (account(1), 1, 69)]); }); } #[test] fn lifecycle_should_work() { new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); - Balances::make_free_balance_be(&2, 100); + Balances::make_free_balance_be(&account(1), 100); + Balances::make_free_balance_be(&account(2), 100); assert_ok!(Nfts::create( - RuntimeOrigin::signed(1), - 1, + RuntimeOrigin::signed(account(1)), + account(1), collection_config_with_all_settings_enabled() )); - assert_eq!(Balances::reserved_balance(&1), 2); - assert_eq!(collections(), vec![(1, 0)]); - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0, 0])); - assert_eq!(Balances::reserved_balance(&1), 5); + assert_eq!(Balances::reserved_balance(&account(1)), 2); + assert_eq!(collections(), vec![(account(1), 0)]); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(1)), + 0, + bvec![0, 0] + )); + assert_eq!(Balances::reserved_balance(&account(1)), 5); assert!(CollectionMetadataOf::::contains_key(0)); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 10, default_item_config())); - assert_eq!(Balances::reserved_balance(&1), 6); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 20, default_item_config())); - assert_eq!(Balances::reserved_balance(&1), 7); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 70, 1, None)); - assert_eq!(items(), vec![(1, 0, 70), (10, 0, 42), (20, 0, 69)]); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(10), + default_item_config() + )); + assert_eq!(Balances::reserved_balance(&account(1)), 6); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 69, + account(20), + default_item_config() + )); + assert_eq!(Balances::reserved_balance(&account(1)), 7); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 70, account(1), None)); + assert_eq!(items(), vec![(account(1), 0, 70), (account(10), 0, 42), (account(20), 0, 69)]); assert_eq!(Collection::::get(0).unwrap().items, 3); assert_eq!(Collection::::get(0).unwrap().item_metadatas, 0); + assert_eq!(Collection::::get(0).unwrap().item_configs, 3); - assert_eq!(Balances::reserved_balance(&2), 0); - assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 70, 2)); - assert_eq!(Balances::reserved_balance(&2), 1); + assert_eq!(Balances::reserved_balance(&account(1)), 8); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 70, account(2))); + assert_eq!(Balances::reserved_balance(&account(1)), 8); + assert_eq!(Balances::reserved_balance(&account(2)), 0); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![42, 42])); - assert_eq!(Balances::reserved_balance(&1), 10); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![42, 42])); + assert_eq!(Balances::reserved_balance(&account(1)), 11); assert!(ItemMetadataOf::::contains_key(0, 42)); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![69, 69])); - assert_eq!(Balances::reserved_balance(&1), 13); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 69, bvec![69, 69])); + assert_eq!(Balances::reserved_balance(&account(1)), 14); assert!(ItemMetadataOf::::contains_key(0, 69)); - + assert!(ItemConfigOf::::contains_key(0, 69)); let w = Nfts::get_destroy_witness(&0).unwrap(); - assert_eq!(w.items, 3); assert_eq!(w.item_metadatas, 2); - assert_ok!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w)); - assert_eq!(Balances::reserved_balance(&1), 0); + assert_eq!(w.item_configs, 3); + assert_noop!( + Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w), + Error::::CollectionNotEmpty + ); + + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(account(1)), + 0, + Some(69), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![0], + )); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(10)), 0, 42)); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(20)), 0, 69)); + assert_ok!(Nfts::burn(RuntimeOrigin::root(), 0, 70)); + + let w = Nfts::get_destroy_witness(&0).unwrap(); + assert_eq!(w.attributes, 1); + assert_eq!(w.item_metadatas, 0); + assert_eq!(w.item_configs, 0); + assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w)); + assert_eq!(Balances::reserved_balance(&account(1)), 0); assert!(!Collection::::contains_key(0)); assert!(!CollectionConfigOf::::contains_key(0)); @@ -206,6 +263,8 @@ fn lifecycle_should_work() { assert!(!CollectionMetadataOf::::contains_key(0)); assert!(!ItemMetadataOf::::contains_key(0, 42)); assert!(!ItemMetadataOf::::contains_key(0, 69)); + assert!(!ItemConfigOf::::contains_key(0, 69)); + assert_eq!(attributes(0), vec![]); assert_eq!(collections(), vec![]); assert_eq!(items(), vec![]); }); @@ -214,31 +273,75 @@ fn lifecycle_should_work() { #[test] fn destroy_with_bad_witness_should_not_work() { new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&account(1), 100); assert_ok!(Nfts::create( - RuntimeOrigin::signed(1), - 1, + RuntimeOrigin::signed(account(1)), + account(1), collection_config_with_all_settings_enabled() )); let w = Collection::::get(0).unwrap().destroy_witness(); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); - assert_noop!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w), Error::::BadWitness); + assert_noop!( + Nfts::destroy( + RuntimeOrigin::signed(account(1)), + 0, + DestroyWitness { item_configs: 1, ..w } + ), + Error::::BadWitness + ); + }); +} + +#[test] +fn destroy_should_work() { + new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&account(1), 100); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(account(1)), + account(1), + collection_config_with_all_settings_enabled() + )); + + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(2), None)); + assert_noop!( + Nfts::destroy( + RuntimeOrigin::signed(account(1)), + 0, + Nfts::get_destroy_witness(&0).unwrap() + ), + Error::::CollectionNotEmpty + ); + assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(account(1)), 0, 42)); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(2)), 0, 42)); + assert_eq!(Collection::::get(0).unwrap().item_configs, 1); + assert_eq!(ItemConfigOf::::iter_prefix(0).count() as u32, 1); + assert!(ItemConfigOf::::contains_key(0, 42)); + assert_ok!(Nfts::destroy( + RuntimeOrigin::signed(account(1)), + 0, + Nfts::get_destroy_witness(&0).unwrap() + )); + assert!(!ItemConfigOf::::contains_key(0, 42)); + assert_eq!(ItemConfigOf::::iter_prefix(0).count() as u32, 0); }); } #[test] fn mint_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); - assert_eq!(Nfts::owner(0, 42).unwrap(), 1); - assert_eq!(collections(), vec![(1, 0)]); - assert_eq!(items(), vec![(1, 0, 42)]); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_eq!(Nfts::owner(0, 42).unwrap(), account(1)); + assert_eq!(collections(), vec![(account(1), 0)]); + assert_eq!(items(), vec![(account(1), 0, 42)]); // validate minting start and end settings assert_ok!(Nfts::update_mint_settings( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, MintSettings { start_block: Some(2), @@ -250,55 +353,101 @@ fn mint_should_work() { System::set_block_number(1); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(2), 0, 43, 1, None), + Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 43, account(1), None), Error::::MintNotStarted ); System::set_block_number(4); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(2), 0, 43, 1, None), + Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 43, account(1), None), Error::::MintEnded ); // validate price assert_ok!(Nfts::update_mint_settings( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, MintSettings { mint_type: MintType::Public, price: Some(1), ..Default::default() } )); - Balances::make_free_balance_be(&2, 100); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 43, 2, None)); - assert_eq!(Balances::total_balance(&2), 99); + Balances::make_free_balance_be(&account(2), 100); + assert_noop!( + Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 43, account(2), None,), + Error::::WitnessRequired + ); + assert_noop!( + Nfts::mint( + RuntimeOrigin::signed(account(2)), + 0, + 43, + account(2), + Some(MintWitness { ..Default::default() }) + ), + Error::::BadWitness + ); + assert_noop!( + Nfts::mint( + RuntimeOrigin::signed(account(2)), + 0, + 43, + account(2), + Some(MintWitness { mint_price: Some(0), ..Default::default() }) + ), + Error::::BadWitness + ); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(account(2)), + 0, + 43, + account(2), + Some(MintWitness { mint_price: Some(1), ..Default::default() }) + )); + assert_eq!(Balances::total_balance(&account(2)), 99); // validate types - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); assert_ok!(Nfts::update_mint_settings( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 1, MintSettings { mint_type: MintType::HolderOf(0), ..Default::default() } )); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(3), 1, 42, 3, None), - Error::::BadWitness + Nfts::mint(RuntimeOrigin::signed(account(3)), 1, 42, account(3), None), + Error::::WitnessRequired ); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(2), 1, 42, 2, None), - Error::::BadWitness + Nfts::mint(RuntimeOrigin::signed(account(2)), 1, 42, account(2), None), + Error::::WitnessRequired ); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(2), 1, 42, 2, Some(MintWitness { owner_of_item: 42 })), + Nfts::mint( + RuntimeOrigin::signed(account(2)), + 1, + 42, + account(2), + Some(MintWitness { owned_item: 42, ..Default::default() }) + ), Error::::BadWitness ); assert_ok!(Nfts::mint( - RuntimeOrigin::signed(2), + RuntimeOrigin::signed(account(2)), 1, 42, - 2, - Some(MintWitness { owner_of_item: 43 }) + account(2), + Some(MintWitness { owned_item: 43, ..Default::default() }) )); // can't mint twice assert_noop!( - Nfts::mint(RuntimeOrigin::signed(2), 1, 46, 2, Some(MintWitness { owner_of_item: 43 })), + Nfts::mint( + RuntimeOrigin::signed(account(2)), + 1, + 46, + account(2), + Some(MintWitness { owned_item: 43, ..Default::default() }) + ), Error::::AlreadyClaimed ); }); @@ -307,33 +456,55 @@ fn mint_should_work() { #[test] fn transfer_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); - assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 3)); - assert_eq!(items(), vec![(3, 0, 42)]); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); + assert_eq!(items(), vec![(account(3), 0, 42)]); assert_noop!( - Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 4), + Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), Error::::NoPermission ); - assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(3), 0, 42, 2, None)); - assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 4)); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(3)), + 0, + 42, + account(2), + None + )); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(4))); // validate we can't transfer non-transferable items let collection_id = 1; assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - 1, + account(1), collection_config_from_disabled_settings( CollectionSetting::TransferableItems | CollectionSetting::DepositRequired ) )); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 1, 1, 42, default_item_config())); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 1, + 1, + account(42), + default_item_config() + )); assert_noop!( - Nfts::transfer(RuntimeOrigin::signed(1), collection_id, 42, 3,), + Nfts::transfer(RuntimeOrigin::signed(account(1)), collection_id, 42, account(3)), Error::::ItemsNonTransferable ); }); @@ -342,19 +513,26 @@ fn transfer_should_work() { #[test] fn locking_transfer_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); - assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(1), 0, 42)); - assert_noop!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), Error::::ItemLocked); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(account(1)), 0, 42)); + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 42, account(2)), + Error::::ItemLocked + ); - assert_ok!(Nfts::unlock_item_transfer(RuntimeOrigin::signed(1), 0, 42)); + assert_ok!(Nfts::unlock_item_transfer(RuntimeOrigin::signed(account(1)), 0, 42)); assert_ok!(Nfts::lock_collection( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into()) )); assert_noop!( - Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2), + Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 42, account(2)), Error::::ItemsNonTransferable ); @@ -363,99 +541,118 @@ fn locking_transfer_should_work() { 0, collection_config_with_all_settings_enabled(), )); - assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 42, account(2))); }); } #[test] fn origin_guards_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); - Balances::make_free_balance_be(&2, 100); - assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(2), Some(0))); + Balances::make_free_balance_be(&account(2), 100); + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(account(2)), Some(0))); assert_noop!( - Nfts::transfer_ownership(RuntimeOrigin::signed(2), 0, 2), + Nfts::transfer_ownership(RuntimeOrigin::signed(account(2)), 0, account(2)), Error::::NoPermission ); assert_noop!( - Nfts::set_team(RuntimeOrigin::signed(2), 0, 2, 2, 2), + Nfts::set_team( + RuntimeOrigin::signed(account(2)), + 0, + Some(account(2)), + Some(account(2)), + Some(account(2)), + ), Error::::NoPermission ); assert_noop!( - Nfts::lock_item_transfer(RuntimeOrigin::signed(2), 0, 42), + Nfts::lock_item_transfer(RuntimeOrigin::signed(account(2)), 0, 42), Error::::NoPermission ); assert_noop!( - Nfts::unlock_item_transfer(RuntimeOrigin::signed(2), 0, 42), + Nfts::unlock_item_transfer(RuntimeOrigin::signed(account(2)), 0, 42), Error::::NoPermission ); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(2), 0, 69, 2, None), + Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 69, account(2), None), Error::::NoPermission ); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 43, account(2), None)); assert_noop!( - Nfts::burn(RuntimeOrigin::signed(2), 0, 42, None), + Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 43), Error::::NoPermission ); let w = Nfts::get_destroy_witness(&0).unwrap(); - assert_noop!(Nfts::destroy(RuntimeOrigin::signed(2), 0, w), Error::::NoPermission); + assert_noop!( + Nfts::destroy(RuntimeOrigin::signed(account(2)), 0, w), + Error::::NoPermission + ); }); } #[test] fn transfer_owner_should_work() { new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); - Balances::make_free_balance_be(&2, 100); - Balances::make_free_balance_be(&3, 100); + Balances::make_free_balance_be(&account(1), 100); + Balances::make_free_balance_be(&account(2), 100); + Balances::make_free_balance_be(&account(3), 100); assert_ok!(Nfts::create( - RuntimeOrigin::signed(1), - 1, + RuntimeOrigin::signed(account(1)), + account(1), collection_config_with_all_settings_enabled() )); - assert_eq!(collections(), vec![(1, 0)]); + assert_eq!(collections(), vec![(account(1), 0)]); assert_noop!( - Nfts::transfer_ownership(RuntimeOrigin::signed(1), 0, 2), + Nfts::transfer_ownership(RuntimeOrigin::signed(account(1)), 0, account(2)), Error::::Unaccepted ); - assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(2), Some(0))); - assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(1), 0, 2)); + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(account(2)), Some(0))); + assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(account(1)), 0, account(2))); - assert_eq!(collections(), vec![(2, 0)]); - assert_eq!(Balances::total_balance(&1), 98); - assert_eq!(Balances::total_balance(&2), 102); - assert_eq!(Balances::reserved_balance(&1), 0); - assert_eq!(Balances::reserved_balance(&2), 2); + assert_eq!(collections(), vec![(account(2), 0)]); + assert_eq!(Balances::total_balance(&account(1)), 98); + assert_eq!(Balances::total_balance(&account(2)), 102); + assert_eq!(Balances::reserved_balance(&account(1)), 0); + assert_eq!(Balances::reserved_balance(&account(2)), 2); - assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(1), Some(0))); + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(account(1)), Some(0))); assert_noop!( - Nfts::transfer_ownership(RuntimeOrigin::signed(1), 0, 1), + Nfts::transfer_ownership(RuntimeOrigin::signed(account(1)), 0, account(1)), Error::::NoPermission ); // Mint and set metadata now and make sure that deposit gets transferred back. - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20])); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); - assert_eq!(Balances::reserved_balance(&1), 1); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20])); - assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(3), Some(0))); - assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(2), 0, 3)); - assert_eq!(collections(), vec![(3, 0)]); - assert_eq!(Balances::total_balance(&2), 58); - assert_eq!(Balances::total_balance(&3), 144); - assert_eq!(Balances::reserved_balance(&2), 0); - assert_eq!(Balances::reserved_balance(&3), 44); - - assert_ok!(Nfts::transfer(RuntimeOrigin::signed(1), 0, 42, 2)); - assert_eq!(Balances::reserved_balance(&1), 0); - assert_eq!(Balances::reserved_balance(&2), 1); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(1)), + 0, + bvec![0u8; 20], + )); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_eq!(Balances::reserved_balance(&account(1)), 1); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 20])); + assert_ok!(Nfts::set_accept_ownership(RuntimeOrigin::signed(account(3)), Some(0))); + assert_ok!(Nfts::transfer_ownership(RuntimeOrigin::signed(account(2)), 0, account(3))); + assert_eq!(collections(), vec![(account(3), 0)]); + assert_eq!(Balances::total_balance(&account(2)), 58); + assert_eq!(Balances::total_balance(&account(3)), 144); + assert_eq!(Balances::reserved_balance(&account(2)), 0); + assert_eq!(Balances::reserved_balance(&account(3)), 44); + + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(1)), 0, 42, account(2))); + // reserved_balance of accounts 1 & 2 should be unchanged: + assert_eq!(Balances::reserved_balance(&account(1)), 1); + assert_eq!(Balances::reserved_balance(&account(2)), 0); // 2's acceptance from before is reset when it became an owner, so it cannot be transferred // without a fresh acceptance. assert_noop!( - Nfts::transfer_ownership(RuntimeOrigin::signed(3), 0, 2), + Nfts::transfer_ownership(RuntimeOrigin::signed(account(3)), 0, account(2)), Error::::Unaccepted ); }); @@ -464,14 +661,70 @@ fn transfer_owner_should_work() { #[test] fn set_team_should_work() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); - - assert_ok!(Nfts::mint(RuntimeOrigin::signed(2), 0, 42, 2, None)); - assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(4), 0, 42)); - assert_ok!(Nfts::unlock_item_transfer(RuntimeOrigin::signed(4), 0, 42)); - assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 3)); - assert_ok!(Nfts::burn(RuntimeOrigin::signed(3), 0, 42, None)); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config(), + )); + assert_ok!(Nfts::set_team( + RuntimeOrigin::signed(account(1)), + 0, + Some(account(2)), + Some(account(3)), + Some(account(4)), + )); + + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(2)), 0, 42, account(2), None)); + + // admin can't transfer/burn items he doesn't own + assert_noop!( + Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(3)), + Error::::NoPermission + ); + assert_noop!( + Nfts::burn(RuntimeOrigin::signed(account(3)), 0, 42), + Error::::NoPermission + ); + + assert_ok!(Nfts::lock_item_transfer(RuntimeOrigin::signed(account(4)), 0, 42)); + assert_ok!(Nfts::unlock_item_transfer(RuntimeOrigin::signed(account(4)), 0, 42)); + + // validate we can set any role to None + assert_ok!(Nfts::set_team( + RuntimeOrigin::signed(account(1)), + 0, + Some(account(2)), + Some(account(3)), + None, + )); + assert_noop!( + Nfts::lock_item_transfer(RuntimeOrigin::signed(account(4)), 0, 42), + Error::::NoPermission + ); + + // set all the roles to None + assert_ok!(Nfts::set_team(RuntimeOrigin::signed(account(1)), 0, None, None, None,)); + + // validate we can't set the roles back + assert_noop!( + Nfts::set_team( + RuntimeOrigin::signed(account(1)), + 0, + Some(account(2)), + Some(account(3)), + None, + ), + Error::::NoPermission + ); + + // only the root account can change the roles from None to Some() + assert_ok!(Nfts::set_team( + RuntimeOrigin::root(), + 0, + Some(account(2)), + Some(account(3)), + None, + )); }); } @@ -480,69 +733,85 @@ fn set_collection_metadata_should_work() { new_test_ext().execute_with(|| { // Cannot add metadata to unknown item assert_noop!( - Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20]), - Error::::NoConfig, + Nfts::set_collection_metadata(RuntimeOrigin::signed(account(1)), 0, bvec![0u8; 20]), + Error::::NoPermission, ); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - 1, + account(1), collection_config_with_all_settings_enabled() )); // Cannot add metadata to unowned item assert_noop!( - Nfts::set_collection_metadata(RuntimeOrigin::signed(2), 0, bvec![0u8; 20]), + Nfts::set_collection_metadata(RuntimeOrigin::signed(account(2)), 0, bvec![0u8; 20]), Error::::NoPermission, ); // Successfully add metadata and take deposit - Balances::make_free_balance_be(&1, 30); - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 20])); - assert_eq!(Balances::free_balance(&1), 9); + Balances::make_free_balance_be(&account(1), 30); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(1)), + 0, + bvec![0u8; 20] + )); + assert_eq!(Balances::free_balance(&account(1)), 9); assert!(CollectionMetadataOf::::contains_key(0)); // Force origin works, too. assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 18])); // Update deposit - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15])); - assert_eq!(Balances::free_balance(&1), 14); - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 25])); - assert_eq!(Balances::free_balance(&1), 4); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(1)), + 0, + bvec![0u8; 15] + )); + assert_eq!(Balances::free_balance(&account(1)), 14); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(1)), + 0, + bvec![0u8; 25] + )); + assert_eq!(Balances::free_balance(&account(1)), 4); // Cannot over-reserve assert_noop!( - Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 40]), + Nfts::set_collection_metadata(RuntimeOrigin::signed(account(1)), 0, bvec![0u8; 40]), BalancesError::::InsufficientBalance, ); // Can't set or clear metadata once frozen - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15])); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(1)), + 0, + bvec![0u8; 15] + )); assert_ok!(Nfts::lock_collection( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, CollectionSettings::from_disabled(CollectionSetting::UnlockedMetadata.into()) )); assert_noop!( - Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0u8; 15]), + Nfts::set_collection_metadata(RuntimeOrigin::signed(account(1)), 0, bvec![0u8; 15]), Error::::LockedCollectionMetadata, ); assert_noop!( - Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 0), + Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(1)), 0), Error::::LockedCollectionMetadata ); // Clear Metadata assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::root(), 0, bvec![0u8; 15])); assert_noop!( - Nfts::clear_collection_metadata(RuntimeOrigin::signed(2), 0), + Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(2)), 0), Error::::NoPermission ); assert_noop!( - Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 1), - Error::::UnknownCollection + Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(1)), 1), + Error::::NoPermission ); assert_noop!( - Nfts::clear_collection_metadata(RuntimeOrigin::signed(1), 0), + Nfts::clear_collection_metadata(RuntimeOrigin::signed(account(1)), 0), Error::::LockedCollectionMetadata ); assert_ok!(Nfts::clear_collection_metadata(RuntimeOrigin::root(), 0)); @@ -553,62 +822,68 @@ fn set_collection_metadata_should_work() { #[test] fn set_item_metadata_should_work() { new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 30); + Balances::make_free_balance_be(&account(1), 30); // Cannot add metadata to unknown item assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - 1, + account(1), collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); // Cannot add metadata to unowned item assert_noop!( - Nfts::set_metadata(RuntimeOrigin::signed(2), 0, 42, bvec![0u8; 20]), + Nfts::set_metadata(RuntimeOrigin::signed(account(2)), 0, 42, bvec![0u8; 20]), Error::::NoPermission, ); // Successfully add metadata and take deposit - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 20])); - assert_eq!(Balances::free_balance(&1), 8); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 20])); + assert_eq!(Balances::free_balance(&account(1)), 8); assert!(ItemMetadataOf::::contains_key(0, 42)); // Force origin works, too. assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 18])); // Update deposit - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15])); - assert_eq!(Balances::free_balance(&1), 13); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 25])); - assert_eq!(Balances::free_balance(&1), 3); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 15])); + assert_eq!(Balances::free_balance(&account(1)), 13); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 25])); + assert_eq!(Balances::free_balance(&account(1)), 3); // Cannot over-reserve assert_noop!( - Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 40]), + Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 40]), BalancesError::::InsufficientBalance, ); // Can't set or clear metadata once frozen - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15])); - assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(1), 0, 42, true, false)); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 15])); + assert_ok!(Nfts::lock_item_properties( + RuntimeOrigin::signed(account(1)), + 0, + 42, + true, + false + )); assert_noop!( - Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0u8; 15]), + Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0u8; 15]), Error::::LockedItemMetadata, ); assert_noop!( - Nfts::clear_metadata(RuntimeOrigin::signed(1), 0, 42), + Nfts::clear_metadata(RuntimeOrigin::signed(account(1)), 0, 42), Error::::LockedItemMetadata, ); // Clear Metadata assert_ok!(Nfts::set_metadata(RuntimeOrigin::root(), 0, 42, bvec![0u8; 15])); assert_noop!( - Nfts::clear_metadata(RuntimeOrigin::signed(2), 0, 42), + Nfts::clear_metadata(RuntimeOrigin::signed(account(2)), 0, 42), Error::::NoPermission, ); assert_noop!( - Nfts::clear_metadata(RuntimeOrigin::signed(1), 1, 42), - Error::::MetadataNotFound, + Nfts::clear_metadata(RuntimeOrigin::signed(account(1)), 1, 42), + Error::::NoPermission, ); assert_ok!(Nfts::clear_metadata(RuntimeOrigin::root(), 0, 42)); assert!(!ItemMetadataOf::::contains_key(0, 42)); @@ -618,17 +893,17 @@ fn set_item_metadata_should_work() { #[test] fn set_collection_owner_attributes_should_work() { new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&account(1), 100); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - 1, + account(1), collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 0, account(1), None)); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, None, AttributeNamespace::CollectionOwner, @@ -636,7 +911,7 @@ fn set_collection_owner_attributes_should_work() { bvec![0], )); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, Some(0), AttributeNamespace::CollectionOwner, @@ -644,7 +919,7 @@ fn set_collection_owner_attributes_should_work() { bvec![0], )); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, Some(0), AttributeNamespace::CollectionOwner, @@ -659,11 +934,11 @@ fn set_collection_owner_attributes_should_work() { (Some(0), AttributeNamespace::CollectionOwner, bvec![1], bvec![0]), ] ); - assert_eq!(Balances::reserved_balance(1), 10); + assert_eq!(Balances::reserved_balance(account(1)), 10); assert_eq!(Collection::::get(0).unwrap().owner_deposit, 9); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, None, AttributeNamespace::CollectionOwner, @@ -678,11 +953,11 @@ fn set_collection_owner_attributes_should_work() { (Some(0), AttributeNamespace::CollectionOwner, bvec![1], bvec![0]), ] ); - assert_eq!(Balances::reserved_balance(1), 19); + assert_eq!(Balances::reserved_balance(account(1)), 19); assert_eq!(Collection::::get(0).unwrap().owner_deposit, 18); assert_ok!(Nfts::clear_attribute( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, Some(0), AttributeNamespace::CollectionOwner, @@ -695,33 +970,40 @@ fn set_collection_owner_attributes_should_work() { (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), ] ); - assert_eq!(Balances::reserved_balance(1), 16); + assert_eq!(Balances::reserved_balance(account(1)), 16); + assert_ok!(Nfts::burn(RuntimeOrigin::root(), 0, 0)); let w = Nfts::get_destroy_witness(&0).unwrap(); - assert_ok!(Nfts::destroy(RuntimeOrigin::signed(1), 0, w)); + assert_ok!(Nfts::destroy(RuntimeOrigin::signed(account(1)), 0, w)); assert_eq!(attributes(0), vec![]); - assert_eq!(Balances::reserved_balance(1), 0); + assert_eq!(Balances::reserved_balance(account(1)), 0); }); } #[test] fn set_item_owner_attributes_should_work() { new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); - Balances::make_free_balance_be(&2, 100); - Balances::make_free_balance_be(&3, 100); + Balances::make_free_balance_be(&account(1), 100); + Balances::make_free_balance_be(&account(2), 100); + Balances::make_free_balance_be(&account(3), 100); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - 1, + account(1), collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 0, 2, default_item_config())); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 0, + account(2), + default_item_config() + )); // can't set for the collection assert_noop!( Nfts::set_attribute( - RuntimeOrigin::signed(2), + RuntimeOrigin::signed(account(2)), 0, None, AttributeNamespace::ItemOwner, @@ -733,7 +1015,7 @@ fn set_item_owner_attributes_should_work() { // can't set for the non-owned item assert_noop!( Nfts::set_attribute( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, Some(0), AttributeNamespace::ItemOwner, @@ -743,7 +1025,7 @@ fn set_item_owner_attributes_should_work() { Error::::NoPermission, ); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(2), + RuntimeOrigin::signed(account(2)), 0, Some(0), AttributeNamespace::ItemOwner, @@ -751,7 +1033,7 @@ fn set_item_owner_attributes_should_work() { bvec![0], )); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(2), + RuntimeOrigin::signed(account(2)), 0, Some(0), AttributeNamespace::ItemOwner, @@ -759,7 +1041,7 @@ fn set_item_owner_attributes_should_work() { bvec![0], )); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(2), + RuntimeOrigin::signed(account(2)), 0, Some(0), AttributeNamespace::ItemOwner, @@ -774,11 +1056,11 @@ fn set_item_owner_attributes_should_work() { (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]), ] ); - assert_eq!(Balances::reserved_balance(2), 9); + assert_eq!(Balances::reserved_balance(account(2)), 9); // validate an attribute can be updated assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(2), + RuntimeOrigin::signed(account(2)), 0, Some(0), AttributeNamespace::ItemOwner, @@ -793,12 +1075,12 @@ fn set_item_owner_attributes_should_work() { (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]), ] ); - assert_eq!(Balances::reserved_balance(2), 18); + assert_eq!(Balances::reserved_balance(account(2)), 18); // validate only item's owner (or the root) can remove an attribute assert_noop!( Nfts::clear_attribute( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, Some(0), AttributeNamespace::ItemOwner, @@ -807,7 +1089,7 @@ fn set_item_owner_attributes_should_work() { Error::::NoPermission, ); assert_ok!(Nfts::clear_attribute( - RuntimeOrigin::signed(2), + RuntimeOrigin::signed(account(2)), 0, Some(0), AttributeNamespace::ItemOwner, @@ -820,10 +1102,10 @@ fn set_item_owner_attributes_should_work() { (Some(0), AttributeNamespace::ItemOwner, bvec![2], bvec![0]) ] ); - assert_eq!(Balances::reserved_balance(2), 15); + assert_eq!(Balances::reserved_balance(account(2)), 15); // transfer item - assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 0, 3)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 0, account(3))); // validate the attribute are still here & the deposit belongs to the previous owner assert_eq!( @@ -836,12 +1118,12 @@ fn set_item_owner_attributes_should_work() { let key: BoundedVec<_, _> = bvec![0]; let (_, deposit) = Attribute::::get((0, Some(0), AttributeNamespace::ItemOwner, &key)).unwrap(); - assert_eq!(deposit.account, Some(2)); + assert_eq!(deposit.account, Some(account(2))); assert_eq!(deposit.amount, 12); // on attribute update the deposit should be returned to the previous owner assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(3), + RuntimeOrigin::signed(account(3)), 0, Some(0), AttributeNamespace::ItemOwner, @@ -850,13 +1132,13 @@ fn set_item_owner_attributes_should_work() { )); let (_, deposit) = Attribute::::get((0, Some(0), AttributeNamespace::ItemOwner, &key)).unwrap(); - assert_eq!(deposit.account, Some(3)); + assert_eq!(deposit.account, Some(account(3))); assert_eq!(deposit.amount, 13); - assert_eq!(Balances::reserved_balance(2), 3); - assert_eq!(Balances::reserved_balance(3), 13); + assert_eq!(Balances::reserved_balance(account(2)), 3); + assert_eq!(Balances::reserved_balance(account(3)), 13); // validate attributes on item deletion - assert_ok!(Nfts::burn(RuntimeOrigin::signed(3), 0, 0, None)); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(3)), 0, 0)); assert_eq!( attributes(0), vec![ @@ -865,90 +1147,101 @@ fn set_item_owner_attributes_should_work() { ] ); assert_ok!(Nfts::clear_attribute( - RuntimeOrigin::signed(3), + RuntimeOrigin::signed(account(3)), 0, Some(0), AttributeNamespace::ItemOwner, bvec![0], )); assert_ok!(Nfts::clear_attribute( - RuntimeOrigin::signed(2), + RuntimeOrigin::signed(account(2)), 0, Some(0), AttributeNamespace::ItemOwner, bvec![2], )); - assert_eq!(Balances::reserved_balance(2), 0); - assert_eq!(Balances::reserved_balance(3), 0); + assert_eq!(Balances::reserved_balance(account(2)), 0); + assert_eq!(Balances::reserved_balance(account(3)), 0); }); } #[test] fn set_external_account_attributes_should_work() { new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); - Balances::make_free_balance_be(&2, 100); + Balances::make_free_balance_be(&account(1), 100); + Balances::make_free_balance_be(&account(2), 100); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - 1, + account(1), collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config())); - assert_ok!(Nfts::approve_item_attributes(RuntimeOrigin::signed(1), 0, 0, 2)); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 0, + account(1), + default_item_config() + )); + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(account(1)), + 0, + 0, + account(2) + )); assert_noop!( Nfts::set_attribute( - RuntimeOrigin::signed(2), + RuntimeOrigin::signed(account(2)), 0, Some(0), - AttributeNamespace::Account(1), + AttributeNamespace::Account(account(1)), bvec![0], bvec![0], ), Error::::NoPermission, ); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(2), + RuntimeOrigin::signed(account(2)), 0, Some(0), - AttributeNamespace::Account(2), + AttributeNamespace::Account(account(2)), bvec![0], bvec![0], )); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(2), + RuntimeOrigin::signed(account(2)), 0, Some(0), - AttributeNamespace::Account(2), + AttributeNamespace::Account(account(2)), bvec![1], bvec![0], )); assert_eq!( attributes(0), vec![ - (Some(0), AttributeNamespace::Account(2), bvec![0], bvec![0]), - (Some(0), AttributeNamespace::Account(2), bvec![1], bvec![0]), + (Some(0), AttributeNamespace::Account(account(2)), bvec![0], bvec![0]), + (Some(0), AttributeNamespace::Account(account(2)), bvec![1], bvec![0]), ] ); - assert_eq!(Balances::reserved_balance(2), 6); + assert_eq!(Balances::reserved_balance(account(2)), 6); // remove permission to set attributes assert_ok!(Nfts::cancel_item_attributes_approval( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, 0, - 2, + account(2), CancelAttributesApprovalWitness { account_attributes: 2 }, )); assert_eq!(attributes(0), vec![]); - assert_eq!(Balances::reserved_balance(2), 0); + assert_eq!(Balances::reserved_balance(account(2)), 0); assert_noop!( Nfts::set_attribute( - RuntimeOrigin::signed(2), + RuntimeOrigin::signed(account(2)), 0, Some(0), - AttributeNamespace::Account(2), + AttributeNamespace::Account(account(2)), bvec![0], bvec![0], ), @@ -960,18 +1253,33 @@ fn set_external_account_attributes_should_work() { #[test] fn validate_deposit_required_setting() { new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); - Balances::make_free_balance_be(&2, 100); - Balances::make_free_balance_be(&3, 100); + Balances::make_free_balance_be(&account(1), 100); + Balances::make_free_balance_be(&account(2), 100); + Balances::make_free_balance_be(&account(3), 100); // with the disabled DepositRequired setting, only the collection's owner can set the // attributes for free. - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 0, 2, default_item_config())); - assert_ok!(Nfts::approve_item_attributes(RuntimeOrigin::signed(2), 0, 0, 3)); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 0, + account(2), + default_item_config() + )); + assert_ok!(Nfts::approve_item_attributes( + RuntimeOrigin::signed(account(2)), + 0, + 0, + account(3) + )); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, Some(0), AttributeNamespace::CollectionOwner, @@ -979,7 +1287,7 @@ fn validate_deposit_required_setting() { bvec![0], )); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(2), + RuntimeOrigin::signed(account(2)), 0, Some(0), AttributeNamespace::ItemOwner, @@ -987,10 +1295,10 @@ fn validate_deposit_required_setting() { bvec![0], )); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(3), + RuntimeOrigin::signed(account(3)), 0, Some(0), - AttributeNamespace::Account(3), + AttributeNamespace::Account(account(3)), bvec![2], bvec![0], )); @@ -1005,13 +1313,13 @@ fn validate_deposit_required_setting() { vec![ (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), (Some(0), AttributeNamespace::ItemOwner, bvec![1], bvec![0]), - (Some(0), AttributeNamespace::Account(3), bvec![2], bvec![0]), + (Some(0), AttributeNamespace::Account(account(3)), bvec![2], bvec![0]), (Some(0), AttributeNamespace::Pallet, bvec![3], bvec![0]), ] ); - assert_eq!(Balances::reserved_balance(1), 0); - assert_eq!(Balances::reserved_balance(2), 3); - assert_eq!(Balances::reserved_balance(3), 3); + assert_eq!(Balances::reserved_balance(account(1)), 0); + assert_eq!(Balances::reserved_balance(account(2)), 3); + assert_eq!(Balances::reserved_balance(account(3)), 3); assert_ok!( ::AccountId, ItemConfig>>::clear_attribute( @@ -1025,7 +1333,7 @@ fn validate_deposit_required_setting() { vec![ (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), (Some(0), AttributeNamespace::ItemOwner, bvec![1], bvec![0]), - (Some(0), AttributeNamespace::Account(3), bvec![2], bvec![0]), + (Some(0), AttributeNamespace::Account(account(3)), bvec![2], bvec![0]), ] ); }); @@ -1034,18 +1342,18 @@ fn validate_deposit_required_setting() { #[test] fn set_attribute_should_respect_lock() { new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&account(1), 100); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - 1, + account(1), collection_config_with_all_settings_enabled(), )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, None)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 0, account(1), None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 1, account(1), None)); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, None, AttributeNamespace::CollectionOwner, @@ -1053,7 +1361,7 @@ fn set_attribute_should_respect_lock() { bvec![0], )); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, Some(0), AttributeNamespace::CollectionOwner, @@ -1061,7 +1369,7 @@ fn set_attribute_should_respect_lock() { bvec![0], )); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, Some(1), AttributeNamespace::CollectionOwner, @@ -1076,11 +1384,11 @@ fn set_attribute_should_respect_lock() { (Some(1), AttributeNamespace::CollectionOwner, bvec![0], bvec![0]), ] ); - assert_eq!(Balances::reserved_balance(1), 11); + assert_eq!(Balances::reserved_balance(account(1)), 11); - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![])); + assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(account(1)), 0, bvec![])); assert_ok!(Nfts::lock_collection( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, CollectionSettings::from_disabled(CollectionSetting::UnlockedAttributes.into()) )); @@ -1088,7 +1396,7 @@ fn set_attribute_should_respect_lock() { let e = Error::::LockedCollectionAttributes; assert_noop!( Nfts::set_attribute( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, None, AttributeNamespace::CollectionOwner, @@ -1098,7 +1406,7 @@ fn set_attribute_should_respect_lock() { e ); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, Some(0), AttributeNamespace::CollectionOwner, @@ -1106,11 +1414,17 @@ fn set_attribute_should_respect_lock() { bvec![1], )); - assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(1), 0, 0, false, true)); + assert_ok!(Nfts::lock_item_properties( + RuntimeOrigin::signed(account(1)), + 0, + 0, + false, + true + )); let e = Error::::LockedItemAttributes; assert_noop!( Nfts::set_attribute( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, Some(0), AttributeNamespace::CollectionOwner, @@ -1120,7 +1434,7 @@ fn set_attribute_should_respect_lock() { e ); assert_ok!(Nfts::set_attribute( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, Some(1), AttributeNamespace::CollectionOwner, @@ -1133,22 +1447,22 @@ fn set_attribute_should_respect_lock() { #[test] fn preserve_config_for_frozen_items() { new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&account(1), 100); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - 1, + account(1), collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, None)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 1, 1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 0, account(1), None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 1, account(1), None)); // if the item is not locked/frozen then the config gets deleted on item burn - assert_ok!(Nfts::burn(RuntimeOrigin::signed(1), 0, 1, Some(1))); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 1)); assert!(!ItemConfigOf::::contains_key(0, 1)); // lock the item and ensure the config stays unchanged - assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(1), 0, 0, true, true)); + assert_ok!(Nfts::lock_item_properties(RuntimeOrigin::signed(account(1)), 0, 0, true, true)); let expect_config = item_config_from_disabled_settings( ItemSetting::UnlockedAttributes | ItemSetting::UnlockedMetadata, @@ -1156,18 +1470,24 @@ fn preserve_config_for_frozen_items() { let config = ItemConfigOf::::get(0, 0).unwrap(); assert_eq!(config, expect_config); - assert_ok!(Nfts::burn(RuntimeOrigin::signed(1), 0, 0, Some(1))); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(1)), 0, 0)); let config = ItemConfigOf::::get(0, 0).unwrap(); assert_eq!(config, expect_config); // can't mint with the different config assert_noop!( - Nfts::force_mint(RuntimeOrigin::signed(1), 0, 0, 1, default_item_config()), + Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 0, + account(2), + default_item_config() + ), Error::::InconsistentItemConfig ); assert_ok!(Nfts::update_mint_settings( - RuntimeOrigin::signed(1), + RuntimeOrigin::signed(account(1)), 0, MintSettings { default_item_settings: ItemSettings::from_disabled( @@ -1176,26 +1496,36 @@ fn preserve_config_for_frozen_items() { ..Default::default() } )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 0, 1, None)); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 0, account(1), None)); }); } #[test] fn force_update_collection_should_work() { new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&account(1), 100); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - 1, + account(1), collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 42, 1, None)); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(1), 0, bvec![0; 20])); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 42, bvec![0; 20])); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 69, bvec![0; 20])); - assert_eq!(Balances::reserved_balance(1), 65); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 42, account(1), None)); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 69, + account(2), + default_item_config(), + )); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(1)), + 0, + bvec![0; 20] + )); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 42, bvec![0; 20])); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 69, bvec![0; 20])); + assert_eq!(Balances::reserved_balance(account(1)), 65); // force item status to be free holding assert_ok!(Nfts::force_collection_config( @@ -1203,52 +1533,85 @@ fn force_update_collection_should_work() { 0, collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()), )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 0, 142, 1, None)); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 169, 2, default_item_config())); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 142, bvec![0; 20])); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(1), 0, 169, bvec![0; 20])); + assert_ok!(Nfts::mint(RuntimeOrigin::signed(account(1)), 0, 142, account(1), None)); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 169, + account(2), + default_item_config(), + )); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 142, bvec![0; 20])); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(1)), 0, 169, bvec![0; 20])); - Balances::make_free_balance_be(&5, 100); - assert_ok!(Nfts::force_collection_owner(RuntimeOrigin::root(), 0, 5)); - assert_eq!(collections(), vec![(5, 0)]); - assert_eq!(Balances::reserved_balance(1), 2); - assert_eq!(Balances::reserved_balance(5), 63); + Balances::make_free_balance_be(&account(5), 100); + assert_ok!(Nfts::force_collection_owner(RuntimeOrigin::root(), 0, account(5))); + assert_ok!(Nfts::set_team( + RuntimeOrigin::root(), + 0, + Some(account(2)), + Some(account(5)), + Some(account(4)), + )); + assert_eq!(collections(), vec![(account(5), 0)]); + assert_eq!(Balances::reserved_balance(account(1)), 2); + assert_eq!(Balances::reserved_balance(account(5)), 63); - assert_ok!(Nfts::redeposit(RuntimeOrigin::signed(5), 0, bvec![0, 42, 50, 69, 100])); - assert_eq!(Balances::reserved_balance(1), 0); + assert_ok!(Nfts::redeposit( + RuntimeOrigin::signed(account(5)), + 0, + bvec![0, 42, 50, 69, 100] + )); + assert_eq!(Balances::reserved_balance(account(1)), 0); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(5), 0, 42, bvec![0; 20])); - assert_eq!(Balances::reserved_balance(5), 42); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(5)), 0, 42, bvec![0; 20])); + assert_eq!(Balances::reserved_balance(account(5)), 42); - assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(5), 0, 69, bvec![0; 20])); - assert_eq!(Balances::reserved_balance(5), 21); + assert_ok!(Nfts::set_metadata(RuntimeOrigin::signed(account(5)), 0, 69, bvec![0; 20])); + assert_eq!(Balances::reserved_balance(account(5)), 21); - assert_ok!(Nfts::set_collection_metadata(RuntimeOrigin::signed(5), 0, bvec![0; 20])); - assert_eq!(Balances::reserved_balance(5), 0); + assert_ok!(Nfts::set_collection_metadata( + RuntimeOrigin::signed(account(5)), + 0, + bvec![0; 20] + )); + assert_eq!(Balances::reserved_balance(account(5)), 0); // validate new roles - assert_ok!(Nfts::set_team(RuntimeOrigin::root(), 0, 2, 3, 4)); + assert_ok!(Nfts::set_team( + RuntimeOrigin::root(), + 0, + Some(account(2)), + Some(account(3)), + Some(account(4)), + )); assert_eq!( - CollectionRoleOf::::get(0, 2).unwrap(), + CollectionRoleOf::::get(0, account(2)).unwrap(), CollectionRoles(CollectionRole::Issuer.into()) ); assert_eq!( - CollectionRoleOf::::get(0, 3).unwrap(), + CollectionRoleOf::::get(0, account(3)).unwrap(), CollectionRoles(CollectionRole::Admin.into()) ); assert_eq!( - CollectionRoleOf::::get(0, 4).unwrap(), + CollectionRoleOf::::get(0, account(4)).unwrap(), CollectionRoles(CollectionRole::Freezer.into()) ); - assert_ok!(Nfts::set_team(RuntimeOrigin::root(), 0, 3, 2, 3)); + assert_ok!(Nfts::set_team( + RuntimeOrigin::root(), + 0, + Some(account(3)), + Some(account(2)), + Some(account(3)), + )); assert_eq!( - CollectionRoleOf::::get(0, 2).unwrap(), + CollectionRoleOf::::get(0, account(2)).unwrap(), CollectionRoles(CollectionRole::Admin.into()) ); assert_eq!( - CollectionRoleOf::::get(0, 3).unwrap(), + CollectionRoleOf::::get(0, account(3)).unwrap(), CollectionRoles(CollectionRole::Issuer | CollectionRole::Freezer) ); }); @@ -1257,68 +1620,116 @@ fn force_update_collection_should_work() { #[test] fn burn_works() { new_test_ext().execute_with(|| { - Balances::make_free_balance_be(&1, 100); + Balances::make_free_balance_be(&account(1), 100); assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - 1, + account(1), collection_config_with_all_settings_enabled() )); - assert_ok!(Nfts::set_team(RuntimeOrigin::signed(1), 0, 2, 3, 4)); + assert_ok!(Nfts::set_team( + RuntimeOrigin::signed(account(1)), + 0, + Some(account(2)), + Some(account(3)), + Some(account(4)), + )); assert_noop!( - Nfts::burn(RuntimeOrigin::signed(5), 0, 42, Some(5)), + Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42), Error::::UnknownItem ); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(2), 0, 42, 5, default_item_config())); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(2), 0, 69, 5, default_item_config())); - assert_eq!(Balances::reserved_balance(1), 2); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(5), + default_item_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(2)), + 0, + 69, + account(5), + default_item_config() + )); + assert_eq!(Balances::reserved_balance(account(1)), 2); assert_noop!( - Nfts::burn(RuntimeOrigin::signed(0), 0, 42, None), + Nfts::burn(RuntimeOrigin::signed(account(0)), 0, 42), Error::::NoPermission ); - assert_noop!( - Nfts::burn(RuntimeOrigin::signed(5), 0, 42, Some(6)), - Error::::WrongOwner - ); - assert_ok!(Nfts::burn(RuntimeOrigin::signed(5), 0, 42, Some(5))); - assert_ok!(Nfts::burn(RuntimeOrigin::signed(3), 0, 69, Some(5))); - assert_eq!(Balances::reserved_balance(1), 0); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 42)); + assert_ok!(Nfts::burn(RuntimeOrigin::signed(account(5)), 0, 69)); + assert_eq!(Balances::reserved_balance(account(1)), 0); }); } #[test] fn approval_lifecycle_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); - assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); - assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 4)); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(3), + None + )); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4))); assert_noop!( - Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 3), + Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(3)), Error::::NoPermission ); assert!(Item::::get(0, 42).unwrap().approvals.is_empty()); - assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(4), 0, 42, 2, None)); - assert_ok!(Nfts::transfer(RuntimeOrigin::signed(2), 0, 42, 2)); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(4)), + 0, + 42, + account(2), + None + )); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(2))); // ensure we can't buy an item when the collection has a NonTransferableItems flag let collection_id = 1; assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - 1, + account(1), collection_config_from_disabled_settings( CollectionSetting::TransferableItems | CollectionSetting::DepositRequired ) )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(1), 1, collection_id, 1, None)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(account(1)), + 1, + collection_id, + account(1), + None, + )); assert_noop!( - Nfts::approve_transfer(RuntimeOrigin::signed(1), collection_id, 1, 2, None), + Nfts::approve_transfer( + RuntimeOrigin::signed(account(1)), + collection_id, + 1, + account(2), + None + ), Error::::ItemsNonTransferable ); }); @@ -1327,46 +1738,74 @@ fn approval_lifecycle_works() { #[test] fn cancel_approval_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); - - assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); - assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(2), 1, 42, 3), + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); + + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(3), + None + )); + assert_noop!( + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, 42, account(3)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(2), 0, 43, 3), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 43, account(3)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(3), 0, 42, 3), + Nfts::cancel_approval(RuntimeOrigin::signed(account(3)), 0, 42, account(3)), Error::::NoPermission ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(2), 0, 42, 4), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(2), 0, 42, 3)); + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(2), 0, 42, 3), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3)), Error::::NotDelegate ); let current_block = 1; System::set_block_number(current_block); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 69, 2, default_item_config())); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 69, + account(2), + default_item_config() + )); // approval expires after 2 blocks. - assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, Some(2))); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(3), + Some(2) + )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(5), 0, 42, 3), + Nfts::cancel_approval(RuntimeOrigin::signed(account(5)), 0, 42, account(3)), Error::::NoPermission ); System::set_block_number(current_block + 3); // 5 can cancel the approval since the deadline has passed. - assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(5), 0, 42, 3)); + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(5)), 0, 42, account(3))); assert_eq!(approvals(0, 69), vec![]); }); } @@ -1374,23 +1813,54 @@ fn cancel_approval_works() { #[test] fn approving_multiple_accounts_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); let current_block = 1; System::set_block_number(current_block); - assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); - assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 4, None)); - assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 5, Some(2))); - assert_eq!(approvals(0, 42), vec![(3, None), (4, None), (5, Some(current_block + 2))]); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(3), + None + )); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(4), + None + )); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(5), + Some(2) + )); + assert_eq!( + approvals(0, 42), + vec![(account(3), None), (account(4), None), (account(5), Some(current_block + 2))] + ); - assert_ok!(Nfts::transfer(RuntimeOrigin::signed(4), 0, 42, 6)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(4)), 0, 42, account(6))); assert_noop!( - Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 7), + Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(7)), Error::::NoPermission ); assert_noop!( - Nfts::transfer(RuntimeOrigin::signed(5), 0, 42, 8), + Nfts::transfer(RuntimeOrigin::signed(account(5)), 0, 42, account(8)), Error::::NoPermission ); }); @@ -1399,15 +1869,31 @@ fn approving_multiple_accounts_works() { #[test] fn approvals_limit_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); for i in 3..13 { - assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, i, None)); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(i), + None + )); } // the limit is 10 assert_noop!( - Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 14, None), + Nfts::approve_transfer(RuntimeOrigin::signed(account(2)), 0, 42, account(14), None), Error::::ReachedApprovalLimit ); }); @@ -1421,55 +1907,89 @@ fn approval_deadline_works() { assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - 1, + account(1), collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()) )); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); // the approval expires after the 2nd block. - assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, Some(2))); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(3), + Some(2) + )); System::set_block_number(3); assert_noop!( - Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 4), + Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4)), Error::::ApprovalExpired ); System::set_block_number(1); - assert_ok!(Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 4)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(4))); assert_eq!(System::block_number(), 1); // make a new approval with a deadline after 4 blocks, so it will expire after the 5th // block. - assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(4), 0, 42, 6, Some(4))); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(4)), + 0, + 42, + account(6), + Some(4) + )); // this should still work. System::set_block_number(5); - assert_ok!(Nfts::transfer(RuntimeOrigin::signed(6), 0, 42, 5)); + assert_ok!(Nfts::transfer(RuntimeOrigin::signed(account(6)), 0, 42, account(5))); }); } #[test] fn cancel_approval_works_with_admin() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); - assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(3), + None + )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(1), 1, 42, 1), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 1, 42, account(1)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(1), 0, 43, 1), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 43, account(1)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(1), 0, 42, 4), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(4)), Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(1), 0, 42, 3)); + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(3))); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::signed(1), 0, 42, 1), + Nfts::cancel_approval(RuntimeOrigin::signed(account(2)), 0, 42, account(1)), Error::::NotDelegate ); }); @@ -1478,26 +1998,42 @@ fn cancel_approval_works_with_admin() { #[test] fn cancel_approval_works_with_force() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); - assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(3), + None + )); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::root(), 1, 42, 1), + Nfts::cancel_approval(RuntimeOrigin::root(), 1, 42, account(1)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::root(), 0, 43, 1), + Nfts::cancel_approval(RuntimeOrigin::root(), 0, 43, account(1)), Error::::UnknownItem ); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, 4), + Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(4)), Error::::NotDelegate ); - assert_ok!(Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, 3)); + assert_ok!(Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(3))); assert_noop!( - Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, 1), + Nfts::cancel_approval(RuntimeOrigin::root(), 0, 42, account(1)), Error::::NotDelegate ); }); @@ -1506,32 +2042,54 @@ fn cancel_approval_works_with_force() { #[test] fn clear_all_transfer_approvals_works() { new_test_ext().execute_with(|| { - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); - assert_ok!(Nfts::force_mint(RuntimeOrigin::signed(1), 0, 42, 2, default_item_config())); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); + assert_ok!(Nfts::force_mint( + RuntimeOrigin::signed(account(1)), + 0, + 42, + account(2), + default_item_config() + )); - assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 3, None)); - assert_ok!(Nfts::approve_transfer(RuntimeOrigin::signed(2), 0, 42, 4, None)); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(3), + None + )); + assert_ok!(Nfts::approve_transfer( + RuntimeOrigin::signed(account(2)), + 0, + 42, + account(4), + None + )); assert_noop!( - Nfts::clear_all_transfer_approvals(RuntimeOrigin::signed(3), 0, 42), + Nfts::clear_all_transfer_approvals(RuntimeOrigin::signed(account(3)), 0, 42), Error::::NoPermission ); - assert_ok!(Nfts::clear_all_transfer_approvals(RuntimeOrigin::signed(2), 0, 42)); + assert_ok!(Nfts::clear_all_transfer_approvals(RuntimeOrigin::signed(account(2)), 0, 42)); assert!(events().contains(&Event::::AllApprovalsCancelled { collection: 0, item: 42, - owner: 2, + owner: account(2), })); assert_eq!(approvals(0, 42), vec![]); assert_noop!( - Nfts::transfer(RuntimeOrigin::signed(3), 0, 42, 5), + Nfts::transfer(RuntimeOrigin::signed(account(3)), 0, 42, account(5)), Error::::NoPermission ); assert_noop!( - Nfts::transfer(RuntimeOrigin::signed(4), 0, 42, 5), + Nfts::transfer(RuntimeOrigin::signed(account(4)), 0, 42, account(5)), Error::::NoPermission ); }); @@ -1541,15 +2099,19 @@ fn clear_all_transfer_approvals_works() { fn max_supply_should_work() { new_test_ext().execute_with(|| { let collection_id = 0; - let user_id = 1; + let user_id = account(1); let max_supply = 1; // validate set_collection_max_supply - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + default_collection_config() + )); assert_eq!(CollectionConfigOf::::get(collection_id).unwrap().max_supply, None); assert_ok!(Nfts::set_collection_max_supply( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, max_supply )); @@ -1564,18 +2126,18 @@ fn max_supply_should_work() { })); assert_ok!(Nfts::set_collection_max_supply( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, max_supply + 1 )); assert_ok!(Nfts::lock_collection( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, CollectionSettings::from_disabled(CollectionSetting::UnlockedMaxSupply.into()) )); assert_noop!( Nfts::set_collection_max_supply( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, max_supply + 2 ), @@ -1583,10 +2145,22 @@ fn max_supply_should_work() { ); // validate we can't mint more to max supply - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 0, user_id, None)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 1, user_id, None)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + 0, + user_id.clone(), + None + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + 1, + user_id.clone(), + None + )); assert_noop!( - Nfts::mint(RuntimeOrigin::signed(user_id), collection_id, 2, user_id, None), + Nfts::mint(RuntimeOrigin::signed(user_id.clone()), collection_id, 2, user_id, None), Error::::MaxSupplyReached ); }); @@ -1596,15 +2170,19 @@ fn max_supply_should_work() { fn mint_settings_should_work() { new_test_ext().execute_with(|| { let collection_id = 0; - let user_id = 1; + let user_id = account(1); let item_id = 0; - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + default_collection_config() + )); assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, item_id, - user_id, + user_id.clone(), None, )); assert_eq!( @@ -1618,7 +2196,7 @@ fn mint_settings_should_work() { let collection_id = 1; assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - user_id, + user_id.clone(), CollectionConfig { mint_settings: MintSettings { default_item_settings: ItemSettings::from_disabled( @@ -1630,10 +2208,10 @@ fn mint_settings_should_work() { } )); assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, item_id, - user_id, + user_id.clone(), None, )); assert_eq!( @@ -1650,30 +2228,34 @@ fn mint_settings_should_work() { #[test] fn set_price_should_work() { new_test_ext().execute_with(|| { - let user_id = 1; + let user_id = account(1); let collection_id = 0; let item_1 = 1; let item_2 = 2; - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + default_collection_config() + )); assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, item_1, - user_id, + user_id.clone(), None, )); assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, item_2, - user_id, + user_id.clone(), None, )); assert_ok!(Nfts::set_price( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, item_1, Some(1), @@ -1681,11 +2263,11 @@ fn set_price_should_work() { )); assert_ok!(Nfts::set_price( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, item_2, Some(2), - Some(3) + Some(account(3)), )); let item = ItemPriceOf::::get(collection_id, item_1).unwrap(); @@ -1694,7 +2276,7 @@ fn set_price_should_work() { let item = ItemPriceOf::::get(collection_id, item_2).unwrap(); assert_eq!(item.0, 2); - assert_eq!(item.1, Some(3)); + assert_eq!(item.1, Some(account(3))); assert!(events().contains(&Event::::ItemPriceSet { collection: collection_id, @@ -1705,7 +2287,7 @@ fn set_price_should_work() { // validate we can unset the price assert_ok!(Nfts::set_price( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, item_2, None, @@ -1721,22 +2303,28 @@ fn set_price_should_work() { let collection_id = 1; assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - user_id, + user_id.clone(), collection_config_from_disabled_settings( CollectionSetting::TransferableItems | CollectionSetting::DepositRequired ) )); assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, item_1, - user_id, + user_id.clone(), None, )); assert_noop!( - Nfts::set_price(RuntimeOrigin::signed(user_id), collection_id, item_1, Some(2), None), + Nfts::set_price( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_1, + Some(2), + None + ), Error::::ItemsNonTransferable ); }); @@ -1745,9 +2333,9 @@ fn set_price_should_work() { #[test] fn buy_item_should_work() { new_test_ext().execute_with(|| { - let user_1 = 1; - let user_2 = 2; - let user_3 = 3; + let user_1 = account(1); + let user_2 = account(2); + let user_3 = account(3); let collection_id = 0; let item_1 = 1; let item_2 = 2; @@ -1760,14 +2348,36 @@ fn buy_item_should_work() { Balances::make_free_balance_be(&user_2, initial_balance); Balances::make_free_balance_be(&user_3, initial_balance); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_1.clone(), + default_collection_config() + )); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_1, user_1, None)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_2, user_1, None)); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_3, user_1, None)); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_1, + user_1.clone(), + None + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_2, + user_1.clone(), + None + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_3, + user_1.clone(), + None + )); assert_ok!(Nfts::set_price( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), collection_id, item_1, Some(price_1), @@ -1775,22 +2385,22 @@ fn buy_item_should_work() { )); assert_ok!(Nfts::set_price( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), collection_id, item_2, Some(price_2), - Some(user_3), + Some(user_3.clone()), )); // can't buy for less assert_noop!( - Nfts::buy_item(RuntimeOrigin::signed(user_2), collection_id, item_1, 1), + Nfts::buy_item(RuntimeOrigin::signed(user_2.clone()), collection_id, item_1, 1), Error::::BidTooLow ); // pass the higher price to validate it will still deduct correctly assert_ok!(Nfts::buy_item( - RuntimeOrigin::signed(user_2), + RuntimeOrigin::signed(user_2.clone()), collection_id, item_1, price_1 + 1, @@ -1798,31 +2408,36 @@ fn buy_item_should_work() { // validate the new owner & balances let item = Item::::get(collection_id, item_1).unwrap(); - assert_eq!(item.owner, user_2); - assert_eq!(Balances::total_balance(&user_1), initial_balance + price_1); - assert_eq!(Balances::total_balance(&user_2), initial_balance - price_1); + assert_eq!(item.owner, user_2.clone()); + assert_eq!(Balances::total_balance(&user_1.clone()), initial_balance + price_1); + assert_eq!(Balances::total_balance(&user_2.clone()), initial_balance - price_1); // can't buy from yourself assert_noop!( - Nfts::buy_item(RuntimeOrigin::signed(user_1), collection_id, item_2, price_2), + Nfts::buy_item(RuntimeOrigin::signed(user_1.clone()), collection_id, item_2, price_2), Error::::NoPermission ); // can't buy when the item is listed for a specific buyer assert_noop!( - Nfts::buy_item(RuntimeOrigin::signed(user_2), collection_id, item_2, price_2), + Nfts::buy_item(RuntimeOrigin::signed(user_2.clone()), collection_id, item_2, price_2), Error::::NoPermission ); // can buy when I'm a whitelisted buyer - assert_ok!(Nfts::buy_item(RuntimeOrigin::signed(user_3), collection_id, item_2, price_2)); + assert_ok!(Nfts::buy_item( + RuntimeOrigin::signed(user_3.clone()), + collection_id, + item_2, + price_2 + )); assert!(events().contains(&Event::::ItemBought { collection: collection_id, item: item_2, price: price_2, - seller: user_1, - buyer: user_3, + seller: user_1.clone(), + buyer: user_3.clone(), })); // ensure we reset the buyer field @@ -1830,14 +2445,14 @@ fn buy_item_should_work() { // can't buy when item is not for sale assert_noop!( - Nfts::buy_item(RuntimeOrigin::signed(user_2), collection_id, item_3, price_2), + Nfts::buy_item(RuntimeOrigin::signed(user_2.clone()), collection_id, item_3, price_2), Error::::NotForSale ); // ensure we can't buy an item when the collection or an item are frozen { assert_ok!(Nfts::set_price( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), collection_id, item_3, Some(price_1), @@ -1846,7 +2461,7 @@ fn buy_item_should_work() { // lock the collection assert_ok!(Nfts::lock_collection( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), collection_id, CollectionSettings::from_disabled(CollectionSetting::TransferableItems.into()) )); @@ -1857,7 +2472,7 @@ fn buy_item_should_work() { bid_price: price_1, }); assert_noop!( - buy_item_call.dispatch(RuntimeOrigin::signed(user_2)), + buy_item_call.dispatch(RuntimeOrigin::signed(user_2.clone())), Error::::ItemsNonTransferable ); @@ -1870,7 +2485,7 @@ fn buy_item_should_work() { // lock the transfer assert_ok!(Nfts::lock_item_transfer( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), collection_id, item_3, )); @@ -1891,9 +2506,9 @@ fn buy_item_should_work() { #[test] fn pay_tips_should_work() { new_test_ext().execute_with(|| { - let user_1 = 1; - let user_2 = 2; - let user_3 = 3; + let user_1 = account(1); + let user_2 = account(2); + let user_3 = account(3); let collection_id = 0; let item_id = 1; let tip = 2; @@ -1904,10 +2519,20 @@ fn pay_tips_should_work() { Balances::make_free_balance_be(&user_3, initial_balance); assert_ok!(Nfts::pay_tips( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), bvec![ - ItemTip { collection: collection_id, item: item_id, receiver: user_2, amount: tip }, - ItemTip { collection: collection_id, item: item_id, receiver: user_3, amount: tip }, + ItemTip { + collection: collection_id, + item: item_id, + receiver: user_2.clone(), + amount: tip + }, + ItemTip { + collection: collection_id, + item: item_id, + receiver: user_3.clone(), + amount: tip + }, ] )); @@ -1919,15 +2544,15 @@ fn pay_tips_should_work() { assert!(events.contains(&Event::::TipSent { collection: collection_id, item: item_id, - sender: user_1, - receiver: user_2, + sender: user_1.clone(), + receiver: user_2.clone(), amount: tip, })); assert!(events.contains(&Event::::TipSent { collection: collection_id, item: item_id, - sender: user_1, - receiver: user_3, + sender: user_1.clone(), + receiver: user_3.clone(), amount: tip, })); }); @@ -1937,7 +2562,7 @@ fn pay_tips_should_work() { fn create_cancel_swap_should_work() { new_test_ext().execute_with(|| { System::set_block_number(1); - let user_id = 1; + let user_id = account(1); let collection_id = 0; let item_1 = 1; let item_2 = 2; @@ -1947,27 +2572,31 @@ fn create_cancel_swap_should_work() { let duration = 2; let expect_deadline = 3; - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + default_collection_config() + )); assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, item_1, - user_id, + user_id.clone(), None, )); assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, item_2, - user_id, + user_id.clone(), None, )); // validate desired item and the collection exists assert_noop!( Nfts::create_swap( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, item_1, collection_id, @@ -1979,7 +2608,7 @@ fn create_cancel_swap_should_work() { ); assert_noop!( Nfts::create_swap( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, item_1, collection_id + 1, @@ -1993,7 +2622,7 @@ fn create_cancel_swap_should_work() { let max_duration: u64 = ::MaxDeadlineDuration::get(); assert_noop!( Nfts::create_swap( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, item_1, collection_id, @@ -2005,7 +2634,7 @@ fn create_cancel_swap_should_work() { ); assert_ok!(Nfts::create_swap( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, item_1, collection_id, @@ -2030,7 +2659,11 @@ fn create_cancel_swap_should_work() { })); // validate we can cancel the swap - assert_ok!(Nfts::cancel_swap(RuntimeOrigin::signed(user_id), collection_id, item_1)); + assert_ok!(Nfts::cancel_swap( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_1 + )); assert!(events().contains(&Event::::SwapCancelled { offered_collection: collection_id, offered_item: item_1, @@ -2043,7 +2676,7 @@ fn create_cancel_swap_should_work() { // validate anyone can cancel the expired swap assert_ok!(Nfts::create_swap( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, item_1, collection_id, @@ -2052,11 +2685,11 @@ fn create_cancel_swap_should_work() { duration, )); assert_noop!( - Nfts::cancel_swap(RuntimeOrigin::signed(user_id + 1), collection_id, item_1), + Nfts::cancel_swap(RuntimeOrigin::signed(account(2)), collection_id, item_1), Error::::NoPermission ); System::set_block_number(expect_deadline + 1); - assert_ok!(Nfts::cancel_swap(RuntimeOrigin::signed(user_id + 1), collection_id, item_1)); + assert_ok!(Nfts::cancel_swap(RuntimeOrigin::signed(account(2)), collection_id, item_1)); // validate optional desired_item param assert_ok!(Nfts::create_swap( @@ -2078,8 +2711,8 @@ fn create_cancel_swap_should_work() { fn claim_swap_should_work() { new_test_ext().execute_with(|| { System::set_block_number(1); - let user_1 = 1; - let user_2 = 2; + let user_1 = account(1); + let user_2 = account(2); let collection_id = 0; let item_1 = 1; let item_2 = 2; @@ -2097,45 +2730,50 @@ fn claim_swap_should_work() { Balances::make_free_balance_be(&user_1, initial_balance); Balances::make_free_balance_be(&user_2, initial_balance); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_1.clone(), + default_collection_config() + )); assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), collection_id, - item_1,user_1, + item_1, + user_1.clone(), None, )); assert_ok!(Nfts::force_mint( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), collection_id, item_2, - user_2, + user_2.clone(), default_item_config(), )); assert_ok!(Nfts::force_mint( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), collection_id, item_3, - user_2, + user_2.clone(), default_item_config(), )); assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), collection_id, item_4, - user_1, + user_1.clone(), None, )); assert_ok!(Nfts::force_mint( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), collection_id, item_5, - user_2, + user_2.clone(), default_item_config(), )); assert_ok!(Nfts::create_swap( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), collection_id, item_1, collection_id, @@ -2148,7 +2786,7 @@ fn claim_swap_should_work() { System::set_block_number(5); assert_noop!( Nfts::claim_swap( - RuntimeOrigin::signed(user_2), + RuntimeOrigin::signed(user_2.clone()), collection_id, item_2, collection_id, @@ -2162,7 +2800,7 @@ fn claim_swap_should_work() { // validate edge cases assert_noop!( Nfts::claim_swap( - RuntimeOrigin::signed(user_2), + RuntimeOrigin::signed(user_2.clone()), collection_id, item_2, collection_id, @@ -2173,7 +2811,7 @@ fn claim_swap_should_work() { ); assert_noop!( Nfts::claim_swap( - RuntimeOrigin::signed(user_2), + RuntimeOrigin::signed(user_2.clone()), collection_id, item_4, // not my item collection_id, @@ -2184,7 +2822,7 @@ fn claim_swap_should_work() { ); assert_noop!( Nfts::claim_swap( - RuntimeOrigin::signed(user_2), + RuntimeOrigin::signed(user_2.clone()), collection_id, item_5, // my item, but not the one another part wants collection_id, @@ -2195,7 +2833,7 @@ fn claim_swap_should_work() { ); assert_noop!( Nfts::claim_swap( - RuntimeOrigin::signed(user_2), + RuntimeOrigin::signed(user_2.clone()), collection_id, item_2, collection_id, @@ -2206,7 +2844,7 @@ fn claim_swap_should_work() { ); assert_noop!( Nfts::claim_swap( - RuntimeOrigin::signed(user_2), + RuntimeOrigin::signed(user_2.clone()), collection_id, item_2, collection_id, @@ -2217,7 +2855,7 @@ fn claim_swap_should_work() { ); assert_ok!(Nfts::claim_swap( - RuntimeOrigin::signed(user_2), + RuntimeOrigin::signed(user_2.clone()), collection_id, item_2, collection_id, @@ -2227,9 +2865,9 @@ fn claim_swap_should_work() { // validate the new owner let item = Item::::get(collection_id, item_1).unwrap(); - assert_eq!(item.owner, user_2); + assert_eq!(item.owner, user_2.clone()); let item = Item::::get(collection_id, item_2).unwrap(); - assert_eq!(item.owner, user_1); + assert_eq!(item.owner, user_1.clone()); // validate the balances assert_eq!(Balances::total_balance(&user_1), initial_balance + price); @@ -2242,10 +2880,10 @@ fn claim_swap_should_work() { assert!(events().contains(&Event::::SwapClaimed { sent_collection: collection_id, sent_item: item_2, - sent_item_owner: user_2, + sent_item_owner: user_2.clone(), received_collection: collection_id, received_item: item_1, - received_item_owner: user_1, + received_item_owner: user_1.clone(), price: Some(price_with_direction.clone()), deadline, })); @@ -2257,7 +2895,7 @@ fn claim_swap_should_work() { Balances::make_free_balance_be(&user_2, initial_balance); assert_ok!(Nfts::create_swap( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), collection_id, item_4, collection_id, @@ -2266,7 +2904,7 @@ fn claim_swap_should_work() { duration, )); assert_ok!(Nfts::claim_swap( - RuntimeOrigin::signed(user_2), + RuntimeOrigin::signed(user_2.clone()), collection_id, item_1, collection_id, @@ -2289,7 +2927,7 @@ fn various_collection_settings() { // when we set only one value it's required to call .into() on it let config = collection_config_from_disabled_settings(CollectionSetting::TransferableItems.into()); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), account(1), config)); let config = CollectionConfigOf::::get(0).unwrap(); assert!(!config.is_setting_enabled(CollectionSetting::TransferableItems)); @@ -2299,25 +2937,29 @@ fn various_collection_settings() { let config = collection_config_from_disabled_settings( CollectionSetting::UnlockedMetadata | CollectionSetting::TransferableItems, ); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, config)); + assert_ok!(Nfts::force_create(RuntimeOrigin::root(), account(1), config)); let config = CollectionConfigOf::::get(1).unwrap(); assert!(!config.is_setting_enabled(CollectionSetting::TransferableItems)); assert!(!config.is_setting_enabled(CollectionSetting::UnlockedMetadata)); - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), 1, default_collection_config())); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + account(1), + default_collection_config() + )); }); } #[test] fn collection_locking_should_work() { new_test_ext().execute_with(|| { - let user_id = 1; + let user_id = account(1); let collection_id = 0; assert_ok!(Nfts::force_create( RuntimeOrigin::root(), - user_id, + user_id.clone(), collection_config_with_all_settings_enabled() )); @@ -2325,7 +2967,7 @@ fn collection_locking_should_work() { collection_config_from_disabled_settings(CollectionSetting::DepositRequired.into()); assert_noop!( Nfts::lock_collection( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, lock_config.settings, ), @@ -2337,7 +2979,7 @@ fn collection_locking_should_work() { CollectionSetting::TransferableItems | CollectionSetting::UnlockedAttributes, ); assert_ok!(Nfts::lock_collection( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, lock_config.settings, )); @@ -2369,33 +3011,49 @@ fn pallet_level_feature_flags_should_work() { PalletFeature::Trading | PalletFeature::Approvals | PalletFeature::Attributes, )); - let user_id = 1; + let user_id = account(1); let collection_id = 0; let item_id = 1; - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_id, default_collection_config())); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_id.clone(), + default_collection_config() + )); assert_ok!(Nfts::mint( - RuntimeOrigin::signed(user_id), + RuntimeOrigin::signed(user_id.clone()), collection_id, item_id, - user_id, + user_id.clone(), None, )); // PalletFeature::Trading assert_noop!( - Nfts::set_price(RuntimeOrigin::signed(user_id), collection_id, item_id, Some(1), None), + Nfts::set_price( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_id, + Some(1), + None + ), Error::::MethodDisabled ); assert_noop!( - Nfts::buy_item(RuntimeOrigin::signed(user_id), collection_id, item_id, 1), + Nfts::buy_item(RuntimeOrigin::signed(user_id.clone()), collection_id, item_id, 1), Error::::MethodDisabled ); // PalletFeature::Approvals assert_noop!( - Nfts::approve_transfer(RuntimeOrigin::signed(user_id), collection_id, item_id, 2, None), + Nfts::approve_transfer( + RuntimeOrigin::signed(user_id.clone()), + collection_id, + item_id, + account(2), + None + ), Error::::MethodDisabled ); @@ -2420,25 +3078,25 @@ fn group_roles_by_account_should_work() { assert_eq!(Nfts::group_roles_by_account(vec![]), vec![]); let account_to_role = Nfts::group_roles_by_account(vec![ - (3, CollectionRole::Freezer), - (1, CollectionRole::Issuer), - (2, CollectionRole::Admin), + (account(3), CollectionRole::Freezer), + (account(1), CollectionRole::Issuer), + (account(2), CollectionRole::Admin), ]); let expect = vec![ - (1, CollectionRoles(CollectionRole::Issuer.into())), - (2, CollectionRoles(CollectionRole::Admin.into())), - (3, CollectionRoles(CollectionRole::Freezer.into())), + (account(1), CollectionRoles(CollectionRole::Issuer.into())), + (account(2), CollectionRoles(CollectionRole::Admin.into())), + (account(3), CollectionRoles(CollectionRole::Freezer.into())), ]; assert_eq!(account_to_role, expect); let account_to_role = Nfts::group_roles_by_account(vec![ - (3, CollectionRole::Freezer), - (2, CollectionRole::Issuer), - (2, CollectionRole::Admin), + (account(3), CollectionRole::Freezer), + (account(2), CollectionRole::Issuer), + (account(2), CollectionRole::Admin), ]); let expect = vec![ - (2, CollectionRoles(CollectionRole::Issuer | CollectionRole::Admin)), - (3, CollectionRoles(CollectionRole::Freezer.into())), + (account(2), CollectionRoles(CollectionRole::Issuer | CollectionRole::Admin)), + (account(3), CollectionRoles(CollectionRole::Freezer.into())), ]; assert_eq!(account_to_role, expect); }) @@ -2447,40 +3105,53 @@ fn group_roles_by_account_should_work() { #[test] fn add_remove_item_attributes_approval_should_work() { new_test_ext().execute_with(|| { - let user_1 = 1; - let user_2 = 2; - let user_3 = 3; - let user_4 = 4; + let user_1 = account(1); + let user_2 = account(2); + let user_3 = account(3); + let user_4 = account(4); let collection_id = 0; let item_id = 0; - assert_ok!(Nfts::force_create(RuntimeOrigin::root(), user_1, default_collection_config())); - assert_ok!(Nfts::mint(RuntimeOrigin::signed(user_1), collection_id, item_id, user_1, None)); + assert_ok!(Nfts::force_create( + RuntimeOrigin::root(), + user_1.clone(), + default_collection_config() + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_id, + user_1.clone(), + None + )); assert_ok!(Nfts::approve_item_attributes( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), collection_id, item_id, - user_2, + user_2.clone(), )); - assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_2]); + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_2.clone()]); assert_ok!(Nfts::approve_item_attributes( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), collection_id, item_id, - user_3, + user_3.clone(), )); assert_ok!(Nfts::approve_item_attributes( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), collection_id, item_id, - user_2, + user_2.clone(), )); - assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_2, user_3]); + assert_eq!( + item_attributes_approvals(collection_id, item_id), + vec![user_2.clone(), user_3.clone()] + ); assert_noop!( Nfts::approve_item_attributes( - RuntimeOrigin::signed(user_1), + RuntimeOrigin::signed(user_1.clone()), collection_id, item_id, user_4, @@ -2498,3 +3169,512 @@ fn add_remove_item_attributes_approval_should_work() { assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_3]); }) } + +#[test] +fn validate_signature() { + new_test_ext().execute_with(|| { + let user_1_pair = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap(); + let user_1_signer = MultiSigner::Sr25519(user_1_pair.public()); + let user_1 = user_1_signer.clone().into_account(); + let mint_data: PreSignedMint = PreSignedMint { + collection: 0, + item: 0, + attributes: vec![], + metadata: vec![], + only_account: None, + deadline: 100000, + mint_price: None, + }; + let encoded_data = Encode::encode(&mint_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&encoded_data)); + assert_ok!(Nfts::validate_signature(&encoded_data, &signature, &user_1)); + + let mut wrapped_data: Vec = Vec::new(); + wrapped_data.extend(b""); + wrapped_data.extend(&encoded_data); + wrapped_data.extend(b""); + + let signature = MultiSignature::Sr25519(user_1_pair.sign(&wrapped_data)); + assert_ok!(Nfts::validate_signature(&encoded_data, &signature, &user_1)); + }) +} + +#[test] +fn pre_signed_mints_should_work() { + new_test_ext().execute_with(|| { + let user_0 = account(0); + let user_1_pair = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap(); + let user_1_signer = MultiSigner::Sr25519(user_1_pair.public()); + let user_1 = user_1_signer.clone().into_account(); + let mint_data = PreSignedMint { + collection: 0, + item: 0, + attributes: vec![(vec![0], vec![1]), (vec![2], vec![3])], + metadata: vec![0, 1], + only_account: None, + deadline: 10000000, + mint_price: Some(10), + }; + let message = Encode::encode(&mint_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&message)); + let user_2 = account(2); + let user_3 = account(3); + + Balances::make_free_balance_be(&user_0, 100); + Balances::make_free_balance_be(&user_2, 100); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(user_0.clone()), + user_1.clone(), + collection_config_with_all_settings_enabled(), + )); + + assert_ok!(Nfts::mint_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + Box::new(mint_data.clone()), + signature.clone(), + user_1.clone(), + )); + assert_eq!(items(), vec![(user_2.clone(), 0, 0)]); + let metadata = ItemMetadataOf::::get(0, 0).unwrap(); + assert_eq!( + metadata.deposit, + ItemMetadataDeposit { account: Some(user_2.clone()), amount: 3 } + ); + assert_eq!(metadata.data, vec![0, 1]); + + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![1]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![2], bvec![3]), + ] + ); + let attribute_key: BoundedVec<_, _> = bvec![0]; + let (_, deposit) = Attribute::::get(( + 0, + Some(0), + AttributeNamespace::CollectionOwner, + &attribute_key, + )) + .unwrap(); + assert_eq!(deposit.account, Some(user_2.clone())); + assert_eq!(deposit.amount, 3); + + assert_eq!(Balances::free_balance(&user_0), 100 - 2 + 10); // 2 - collection deposit, 10 - mint price + assert_eq!(Balances::free_balance(&user_2), 100 - 1 - 3 - 6 - 10); // 1 - item deposit, 3 - metadata, 6 - attributes, 10 - mint price + + assert_noop!( + Nfts::mint_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + Box::new(mint_data), + signature.clone(), + user_1.clone(), + ), + Error::::AlreadyExists + ); + + assert_ok!(Nfts::burn(RuntimeOrigin::signed(user_2.clone()), 0, 0)); + assert_eq!(Balances::free_balance(&user_2), 100 - 6 - 10); + + // validate the `only_account` field + let mint_data = PreSignedMint { + collection: 0, + item: 0, + attributes: vec![], + metadata: vec![], + only_account: Some(account(2)), + deadline: 10000000, + mint_price: None, + }; + + // can't mint with the wrong signature + assert_noop!( + Nfts::mint_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + Box::new(mint_data.clone()), + signature.clone(), + user_1.clone(), + ), + Error::::WrongSignature + ); + + let message = Encode::encode(&mint_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&message)); + + assert_noop!( + Nfts::mint_pre_signed( + RuntimeOrigin::signed(user_3), + Box::new(mint_data.clone()), + signature.clone(), + user_1.clone(), + ), + Error::::WrongOrigin + ); + + // validate signature's expiration + System::set_block_number(10000001); + assert_noop!( + Nfts::mint_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + Box::new(mint_data), + signature, + user_1.clone(), + ), + Error::::DeadlineExpired + ); + System::set_block_number(1); + + // validate the collection + let mint_data = PreSignedMint { + collection: 1, + item: 0, + attributes: vec![], + metadata: vec![], + only_account: Some(account(2)), + deadline: 10000000, + mint_price: None, + }; + let message = Encode::encode(&mint_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&message)); + + assert_noop!( + Nfts::mint_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + Box::new(mint_data), + signature, + user_1.clone(), + ), + Error::::NoPermission + ); + + // validate max attributes limit + let mint_data = PreSignedMint { + collection: 0, + item: 0, + attributes: vec![(vec![0], vec![1]), (vec![2], vec![3]), (vec![2], vec![3])], + metadata: vec![0, 1], + only_account: None, + deadline: 10000000, + mint_price: None, + }; + let message = Encode::encode(&mint_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&message)); + assert_noop!( + Nfts::mint_pre_signed( + RuntimeOrigin::signed(user_2), + Box::new(mint_data), + signature, + user_1.clone(), + ), + Error::::MaxAttributesLimitReached + ); + }) +} + +#[test] +fn pre_signed_attributes_should_work() { + new_test_ext().execute_with(|| { + let user_1_pair = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap(); + let user_1_signer = MultiSigner::Sr25519(user_1_pair.public()); + let user_1 = user_1_signer.clone().into_account(); + let user_2 = account(2); + let user_3_pair = sp_core::sr25519::Pair::from_string("//Bob", None).unwrap(); + let user_3_signer = MultiSigner::Sr25519(user_3_pair.public()); + let user_3 = user_3_signer.clone().into_account(); + let collection_id = 0; + let item_id = 0; + + Balances::make_free_balance_be(&user_1, 100); + Balances::make_free_balance_be(&user_2, 100); + Balances::make_free_balance_be(&user_3, 100); + assert_ok!(Nfts::create( + RuntimeOrigin::signed(user_1.clone()), + user_1.clone(), + collection_config_with_all_settings_enabled(), + )); + assert_ok!(Nfts::mint( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + item_id, + user_2.clone(), + None, + )); + + // validate the CollectionOwner namespace + let pre_signed_data = PreSignedAttributes { + collection: 0, + item: 0, + attributes: vec![(vec![0], vec![1]), (vec![2], vec![3])], + namespace: AttributeNamespace::CollectionOwner, + deadline: 10000000, + }; + let message = Encode::encode(&pre_signed_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&message)); + + assert_ok!(Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_1.clone(), + )); + + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![1]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![2], bvec![3]), + ] + ); + let attribute_key: BoundedVec<_, _> = bvec![0]; + let (_, deposit) = Attribute::::get(( + 0, + Some(0), + AttributeNamespace::CollectionOwner, + &attribute_key, + )) + .unwrap(); + assert_eq!(deposit.account, Some(user_2.clone())); + assert_eq!(deposit.amount, 3); + + assert_eq!(Balances::free_balance(&user_1), 100 - 2 - 1); // 2 - collection deposit, 1 - item deposit + assert_eq!(Balances::free_balance(&user_2), 100 - 6); // 6 - attributes + + // validate the deposit gets returned on attribute update from collection's owner + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(user_1.clone()), + collection_id, + Some(item_id), + AttributeNamespace::CollectionOwner, + bvec![0], + bvec![1], + )); + let (_, deposit) = Attribute::::get(( + 0, + Some(0), + AttributeNamespace::CollectionOwner, + &attribute_key, + )) + .unwrap(); + assert_eq!(deposit.account, None); + assert_eq!(deposit.amount, 3); + + // validate we don't partially modify the state + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![]); + let pre_signed_data = PreSignedAttributes { + collection: 0, + item: 0, + attributes: vec![(vec![0], vec![1]), (vec![2; 51], vec![3])], + namespace: AttributeNamespace::Account(user_3.clone()), + deadline: 10000000, + }; + let message = Encode::encode(&pre_signed_data); + let signature = MultiSignature::Sr25519(user_3_pair.sign(&message)); + + assert_noop!( + Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_3.clone(), + ), + Error::::IncorrectData + ); + + // no new approval was set + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![]); + + // no new attributes were added + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![1]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![2], bvec![3]), + ] + ); + + // validate the Account namespace + let pre_signed_data = PreSignedAttributes { + collection: 0, + item: 0, + attributes: vec![(vec![0], vec![1]), (vec![2], vec![3])], + namespace: AttributeNamespace::Account(user_3.clone()), + deadline: 10000000, + }; + let message = Encode::encode(&pre_signed_data); + let signature = MultiSignature::Sr25519(user_3_pair.sign(&message)); + + assert_ok!(Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_3.clone(), + )); + + assert_eq!( + attributes(0), + vec![ + (Some(0), AttributeNamespace::CollectionOwner, bvec![0], bvec![1]), + (Some(0), AttributeNamespace::Account(user_3.clone()), bvec![0], bvec![1]), + (Some(0), AttributeNamespace::CollectionOwner, bvec![2], bvec![3]), + (Some(0), AttributeNamespace::Account(user_3.clone()), bvec![2], bvec![3]), + ] + ); + assert_eq!(item_attributes_approvals(collection_id, item_id), vec![user_3.clone()]); + + let attribute_key: BoundedVec<_, _> = bvec![0]; + let (_, deposit) = Attribute::::get(( + 0, + Some(0), + AttributeNamespace::Account(user_3.clone()), + &attribute_key, + )) + .unwrap(); + assert_eq!(deposit.account, Some(user_2.clone())); + assert_eq!(deposit.amount, 3); + + assert_eq!(Balances::free_balance(&user_2), 100 - 9); + assert_eq!(Balances::free_balance(&user_3), 100); + + // validate the deposit gets returned on attribute update from user_3 + assert_ok!(Nfts::set_attribute( + RuntimeOrigin::signed(user_3.clone()), + collection_id, + Some(item_id), + AttributeNamespace::Account(user_3.clone()), + bvec![0], + bvec![1], + )); + let (_, deposit) = Attribute::::get(( + 0, + Some(0), + AttributeNamespace::Account(user_3.clone()), + &attribute_key, + )) + .unwrap(); + assert_eq!(deposit.account, Some(user_3.clone())); + assert_eq!(deposit.amount, 3); + + assert_eq!(Balances::free_balance(&user_2), 100 - 6); + assert_eq!(Balances::free_balance(&user_3), 100 - 3); + + // can't update with the wrong signature + assert_noop!( + Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_1.clone(), + ), + Error::::WrongSignature + ); + + // can't update if I don't own that item + assert_noop!( + Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_3.clone()), + pre_signed_data.clone(), + signature.clone(), + user_3.clone(), + ), + Error::::NoPermission + ); + + // can't update the CollectionOwner namespace if the signer is not an owner of that + // collection + let pre_signed_data = PreSignedAttributes { + collection: 0, + item: 0, + attributes: vec![(vec![0], vec![1]), (vec![2], vec![3])], + namespace: AttributeNamespace::CollectionOwner, + deadline: 10000000, + }; + let message = Encode::encode(&pre_signed_data); + let signature = MultiSignature::Sr25519(user_3_pair.sign(&message)); + + assert_noop!( + Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_3.clone(), + ), + Error::::NoPermission + ); + + // validate signature's expiration + System::set_block_number(10000001); + assert_noop!( + Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_3.clone(), + ), + Error::::DeadlineExpired + ); + System::set_block_number(1); + + // validate item & collection + let pre_signed_data = PreSignedAttributes { + collection: 1, + item: 1, + attributes: vec![(vec![0], vec![1]), (vec![2], vec![3])], + namespace: AttributeNamespace::CollectionOwner, + deadline: 10000000, + }; + let message = Encode::encode(&pre_signed_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&message)); + + assert_noop!( + Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_1.clone(), + ), + Error::::UnknownItem + ); + + // validate max attributes limit + let pre_signed_data = PreSignedAttributes { + collection: 1, + item: 1, + attributes: vec![(vec![0], vec![1]), (vec![2], vec![3]), (vec![2], vec![3])], + namespace: AttributeNamespace::CollectionOwner, + deadline: 10000000, + }; + let message = Encode::encode(&pre_signed_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&message)); + + assert_noop!( + Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_1.clone(), + ), + Error::::MaxAttributesLimitReached + ); + + // validate the attribute's value length + let pre_signed_data = PreSignedAttributes { + collection: 0, + item: 0, + attributes: vec![(vec![0], vec![1]), (vec![2], vec![3; 51])], + namespace: AttributeNamespace::CollectionOwner, + deadline: 10000000, + }; + let message = Encode::encode(&pre_signed_data); + let signature = MultiSignature::Sr25519(user_1_pair.sign(&message)); + + assert_noop!( + Nfts::set_attributes_pre_signed( + RuntimeOrigin::signed(user_2.clone()), + pre_signed_data.clone(), + signature.clone(), + user_1.clone(), + ), + Error::::IncorrectData + ); + }) +} diff --git a/frame/nfts/src/types.rs b/frame/nfts/src/types.rs index d8938aab4377..e4e616577462 100644 --- a/frame/nfts/src/types.rs +++ b/frame/nfts/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,6 +26,7 @@ use frame_support::{ traits::Get, BoundedBTreeMap, BoundedBTreeSet, }; +use frame_system::pallet_prelude::BlockNumberFor; use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter}; pub(super) type DepositBalanceOf = @@ -34,7 +35,7 @@ pub(super) type CollectionDetailsFor = CollectionDetails<::AccountId, DepositBalanceOf>; pub(super) type ApprovalsOf = BoundedBTreeMap< ::AccountId, - Option<::BlockNumber>, + Option>, >::ApprovalsLimit, >; pub(super) type ItemAttributesApprovals = @@ -56,18 +57,22 @@ pub(super) type ItemTipOf = ItemTip< ::AccountId, BalanceOf, >; -pub(super) type CollectionConfigFor = CollectionConfig< +pub(super) type CollectionConfigFor = + CollectionConfig, BlockNumberFor, >::CollectionId>; +pub(super) type PreSignedMintOf = PreSignedMint< + >::CollectionId, + >::ItemId, + ::AccountId, + BlockNumberFor, BalanceOf, - ::BlockNumber, +>; +pub(super) type PreSignedAttributesOf = PreSignedAttributes< >::CollectionId, + >::ItemId, + ::AccountId, + BlockNumberFor, >; -pub trait Incrementable { - fn increment(&self) -> Self; - fn initial_value() -> Self; -} -impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); - /// Information about a collection. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct CollectionDetails { @@ -80,6 +85,8 @@ pub struct CollectionDetails { pub(super) items: u32, /// The total number of outstanding item metadata of this collection. pub(super) item_metadatas: u32, + /// The total number of outstanding item configs of this collection. + pub(super) item_configs: u32, /// The total number of attributes for this collection. pub(super) attributes: u32, } @@ -87,12 +94,12 @@ pub struct CollectionDetails { /// Witness data for the destroy transactions. #[derive(Copy, Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct DestroyWitness { - /// The total number of outstanding items of this collection. - #[codec(compact)] - pub items: u32, /// The total number of items in this collection that have outstanding item metadata. #[codec(compact)] pub item_metadatas: u32, + /// The total number of outstanding item configs of this collection. + #[codec(compact)] + pub item_configs: u32, /// The total number of attributes for this collection. #[codec(compact)] pub attributes: u32, @@ -101,18 +108,20 @@ pub struct DestroyWitness { impl CollectionDetails { pub fn destroy_witness(&self) -> DestroyWitness { DestroyWitness { - items: self.items, item_metadatas: self.item_metadatas, + item_configs: self.item_configs, attributes: self.attributes, } } } /// Witness data for items mint transactions. -#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct MintWitness { +#[derive(Clone, Encode, Decode, Default, Eq, PartialEq, RuntimeDebug, TypeInfo)] +pub struct MintWitness { /// Provide the id of the item in a required collection. - pub owner_of_item: ItemId, + pub owned_item: ItemId, + /// The price specified in mint settings. + pub mint_price: Option, } /// Information concerning the ownership of a single unique item. @@ -187,7 +196,7 @@ pub struct PendingSwap { pub(super) desired_item: Option, /// A price for the desired `item` with the direction. pub(super) price: Option, - /// An optional deadline for the swap. + /// A deadline for the swap. pub(super) deadline: Deadline, } @@ -305,6 +314,21 @@ impl Default for MintSettings { + /// An attribute was set by the pallet. + Pallet, + /// An attribute was set by collection's owner. + CollectionOwner, + /// An attribute was set by item's owner. + ItemOwner, + /// An attribute was set by pre-approved account. + Account(AccountId), +} + /// A witness data to cancel attributes approval operation. #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)] pub struct CancelAttributesApprovalWitness { @@ -317,6 +341,8 @@ pub struct CancelAttributesApprovalWitness { pub enum PalletAttributes { /// Marks an item as being used in order to claim another item. UsedToClaim(CollectionId), + /// Marks an item as being restricted from transferring. + TransferDisabled, } /// Collection's configuration. @@ -387,7 +413,7 @@ impl_codec_bitflags!(ItemSettings, u64, ItemSetting); )] pub struct ItemConfig { /// Item's settings. - pub(super) settings: ItemSettings, + pub settings: ItemSettings, } impl ItemConfig { @@ -473,3 +499,35 @@ impl CollectionRoles { } } impl_codec_bitflags!(CollectionRoles, u8, CollectionRole); + +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct PreSignedMint { + /// A collection of the item to be minted. + pub(super) collection: CollectionId, + /// Item's ID. + pub(super) item: ItemId, + /// Additional item's key-value attributes. + pub(super) attributes: Vec<(Vec, Vec)>, + /// Additional item's metadata. + pub(super) metadata: Vec, + /// Restrict the claim to a particular account. + pub(super) only_account: Option, + /// A deadline for the signature. + pub(super) deadline: Deadline, + /// An optional price the claimer would need to pay for the mint. + pub(super) mint_price: Option, +} + +#[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct PreSignedAttributes { + /// Collection's ID. + pub(super) collection: CollectionId, + /// Item's ID. + pub(super) item: ItemId, + /// Key-value attributes. + pub(super) attributes: Vec<(Vec, Vec)>, + /// Attributes' namespace. + pub(super) namespace: AttributeNamespace, + /// A deadline for the signature. + pub(super) deadline: Deadline, +} diff --git a/frame/nfts/src/weights.rs b/frame/nfts/src/weights.rs index f05f8ca514c3..6b8c577bb12e 100644 --- a/frame/nfts/src/weights.rs +++ b/frame/nfts/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,39 +18,42 @@ //! Autogenerated weights for pallet_nfts //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-12-22, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// /home/benchbot/cargo_target_dir/production/substrate +// ./target/production/substrate // benchmark // pallet +// --chain=dev // --steps=50 // --repeat=20 +// --pallet=pallet_nfts +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* -// --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json -// --pallet=pallet_nfts -// --chain=dev -// --header=./HEADER-APACHE2 // --output=./frame/nfts/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_nfts. pub trait WeightInfo { fn create() -> Weight; fn force_create() -> Weight; - fn destroy(n: u32, m: u32, a: u32, ) -> Weight; + fn destroy(m: u32, c: u32, a: u32, ) -> Weight; fn mint() -> Weight; fn force_mint() -> Weight; fn burn() -> Weight; @@ -85,767 +88,1373 @@ pub trait WeightInfo { fn create_swap() -> Weight; fn cancel_swap() -> Weight; fn claim_swap() -> Weight; + fn mint_pre_signed(n: u32, ) -> Weight; + fn set_attributes_pre_signed(n: u32, ) -> Weight; } /// Weights for pallet_nfts using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Nfts NextCollectionId (r:1 w:1) - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionRoleOf (r:0 w:1) - // Storage: Nfts CollectionConfigOf (r:0 w:1) - // Storage: Nfts CollectionAccount (r:0 w:1) + /// Storage: Nfts NextCollectionId (r:1 w:1) + /// Proof: Nfts NextCollectionId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionRoleOf (r:0 w:1) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:0 w:1) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts CollectionAccount (r:0 w:1) + /// Proof: Nfts CollectionAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) fn create() -> Weight { - // Minimum execution time: 44_312 nanoseconds. - Weight::from_ref_time(44_871_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(5)) - } - // Storage: Nfts NextCollectionId (r:1 w:1) - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionRoleOf (r:0 w:1) - // Storage: Nfts CollectionConfigOf (r:0 w:1) - // Storage: Nfts CollectionAccount (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `216` + // Estimated: `3549` + // Minimum execution time: 40_489_000 picoseconds. + Weight::from_parts(41_320_000, 3549) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: Nfts NextCollectionId (r:1 w:1) + /// Proof: Nfts NextCollectionId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionRoleOf (r:0 w:1) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:0 w:1) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts CollectionAccount (r:0 w:1) + /// Proof: Nfts CollectionAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) fn force_create() -> Weight { - // Minimum execution time: 31_654 nanoseconds. - Weight::from_ref_time(32_078_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(5)) - } - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts Item (r:1001 w:1000) - // Storage: Nfts Attribute (r:1001 w:1000) - // Storage: Nfts ItemMetadataOf (r:0 w:1000) - // Storage: Nfts CollectionRoleOf (r:0 w:1) - // Storage: Nfts CollectionMetadataOf (r:0 w:1) - // Storage: Nfts CollectionConfigOf (r:0 w:1) - // Storage: Nfts ItemConfigOf (r:0 w:1000) - // Storage: Nfts Account (r:0 w:1000) - // Storage: Nfts CollectionAccount (r:0 w:1) - /// The range of component `n` is `[0, 1000]`. + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `3549` + // Minimum execution time: 23_257_000 picoseconds. + Weight::from_parts(23_770_000, 3549) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts ItemMetadataOf (r:1 w:0) + /// Proof: Nfts ItemMetadataOf (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) + /// Storage: Nfts CollectionRoleOf (r:1 w:1) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:1001 w:1000) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1000 w:1000) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts CollectionMetadataOf (r:0 w:1) + /// Proof: Nfts CollectionMetadataOf (max_values: None, max_size: Some(87), added: 2562, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:0 w:1) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts CollectionAccount (r:0 w:1) + /// Proof: Nfts CollectionAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) /// The range of component `m` is `[0, 1000]`. + /// The range of component `c` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. - fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - // Minimum execution time: 19_183_393 nanoseconds. - Weight::from_ref_time(17_061_526_855) - // Standard Error: 16_689 - .saturating_add(Weight::from_ref_time(353_523).saturating_mul(n.into())) - // Standard Error: 16_689 - .saturating_add(Weight::from_ref_time(1_861_080).saturating_mul(m.into())) - // Standard Error: 16_689 - .saturating_add(Weight::from_ref_time(8_858_987).saturating_mul(a.into())) - .saturating_add(T::DbWeight::get().reads(1003)) + fn destroy(_m: u32, _c: u32, a: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `32220 + a * (332 ±0)` + // Estimated: `2523990 + a * (2921 ±0)` + // Minimum execution time: 1_310_198_000 picoseconds. + Weight::from_parts(1_479_261_043, 2523990) + // Standard Error: 4_415 + .saturating_add(Weight::from_parts(6_016_212, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(1004_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) - .saturating_add(T::DbWeight::get().writes(3005)) - .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(m.into()))) + .saturating_add(T::DbWeight::get().writes(1005_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) - } - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts Item (r:1 w:1) - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionRoleOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:1) - // Storage: Nfts Account (r:0 w:1) + .saturating_add(Weight::from_parts(0, 2921).saturating_mul(a.into())) + } + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:1) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Account (r:0 w:1) + /// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) fn mint() -> Weight { - // Minimum execution time: 57_753 nanoseconds. - Weight::from_ref_time(58_313_000) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Nfts CollectionRoleOf (r:1 w:0) - // Storage: Nfts Item (r:1 w:1) - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:1) - // Storage: Nfts Account (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `455` + // Estimated: `4326` + // Minimum execution time: 51_910_000 picoseconds. + Weight::from_parts(53_441_000, 4326) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:1) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Account (r:0 w:1) + /// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) fn force_mint() -> Weight { - // Minimum execution time: 56_429 nanoseconds. - Weight::from_ref_time(57_202_000) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts Item (r:1 w:1) - // Storage: Nfts CollectionRoleOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:1) - // Storage: Nfts Account (r:0 w:1) - // Storage: Nfts ItemPriceOf (r:0 w:1) - // Storage: Nfts ItemAttributesApprovalsOf (r:0 w:1) - // Storage: Nfts PendingSwapOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `455` + // Estimated: `4326` + // Minimum execution time: 50_168_000 picoseconds. + Weight::from_parts(51_380_000, 4326) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Nfts ItemConfigOf (r:1 w:1) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts ItemMetadataOf (r:1 w:0) + /// Proof: Nfts ItemMetadataOf (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) + /// Storage: Nfts Account (r:0 w:1) + /// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Nfts ItemPriceOf (r:0 w:1) + /// Proof: Nfts ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) + /// Storage: Nfts ItemAttributesApprovalsOf (r:0 w:1) + /// Proof: Nfts ItemAttributesApprovalsOf (max_values: None, max_size: Some(681), added: 3156, mode: MaxEncodedLen) + /// Storage: Nfts PendingSwapOf (r:0 w:1) + /// Proof: Nfts PendingSwapOf (max_values: None, max_size: Some(71), added: 2546, mode: MaxEncodedLen) fn burn() -> Weight { - // Minimum execution time: 59_681 nanoseconds. - Weight::from_ref_time(60_058_000) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(7)) - } - // Storage: Nfts Collection (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts Item (r:1 w:1) - // Storage: Nfts CollectionRoleOf (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Nfts Account (r:0 w:2) - // Storage: Nfts ItemPriceOf (r:0 w:1) - // Storage: Nfts PendingSwapOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `564` + // Estimated: `4326` + // Minimum execution time: 50_738_000 picoseconds. + Weight::from_parts(51_850_000, 4326) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) + } + /// Storage: Nfts Collection (r:1 w:0) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:1 w:0) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts Account (r:0 w:2) + /// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Nfts ItemPriceOf (r:0 w:1) + /// Proof: Nfts ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) + /// Storage: Nfts PendingSwapOf (r:0 w:1) + /// Proof: Nfts PendingSwapOf (max_values: None, max_size: Some(71), added: 2546, mode: MaxEncodedLen) fn transfer() -> Weight { - // Minimum execution time: 66_085 nanoseconds. - Weight::from_ref_time(67_065_000) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(6)) - } - // Storage: Nfts Collection (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts Item (r:102 w:102) + // Proof Size summary in bytes: + // Measured: `593` + // Estimated: `4326` + // Minimum execution time: 41_055_000 picoseconds. + Weight::from_parts(42_336_000, 4326) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: Nfts Collection (r:1 w:0) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts Item (r:5000 w:5000) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - // Minimum execution time: 25_949 nanoseconds. - Weight::from_ref_time(26_106_000) - // Standard Error: 10_326 - .saturating_add(Weight::from_ref_time(11_496_776).saturating_mul(i.into())) - .saturating_add(T::DbWeight::get().reads(2)) + // Proof Size summary in bytes: + // Measured: `763 + i * (108 ±0)` + // Estimated: `3549 + i * (3336 ±0)` + // Minimum execution time: 15_688_000 picoseconds. + Weight::from_parts(15_921_000, 3549) + // Standard Error: 14_827 + .saturating_add(Weight::from_parts(17_105_395, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) + .saturating_add(Weight::from_parts(0, 3336).saturating_mul(i.into())) } - // Storage: Nfts CollectionRoleOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:1) + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:1) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn lock_item_transfer() -> Weight { - // Minimum execution time: 30_080 nanoseconds. - Weight::from_ref_time(30_825_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Nfts CollectionRoleOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `435` + // Estimated: `3534` + // Minimum execution time: 19_981_000 picoseconds. + Weight::from_parts(20_676_000, 3534) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:1) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn unlock_item_transfer() -> Weight { - // Minimum execution time: 30_612 nanoseconds. - Weight::from_ref_time(31_422_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Nfts CollectionRoleOf (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `435` + // Estimated: `3534` + // Minimum execution time: 19_911_000 picoseconds. + Weight::from_parts(20_612_000, 3534) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Collection (r:1 w:0) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:1) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) fn lock_collection() -> Weight { - // Minimum execution time: 27_470 nanoseconds. - Weight::from_ref_time(28_015_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Nfts OwnershipAcceptance (r:1 w:1) - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionAccount (r:0 w:2) + // Proof Size summary in bytes: + // Measured: `340` + // Estimated: `3549` + // Minimum execution time: 16_441_000 picoseconds. + Weight::from_parts(16_890_000, 3549) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Nfts OwnershipAcceptance (r:1 w:1) + /// Proof: Nfts OwnershipAcceptance (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionAccount (r:0 w:2) + /// Proof: Nfts CollectionAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) fn transfer_ownership() -> Weight { - // Minimum execution time: 33_750 nanoseconds. - Weight::from_ref_time(34_139_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionRoleOf (r:0 w:4) + // Proof Size summary in bytes: + // Measured: `388` + // Estimated: `3549` + // Minimum execution time: 22_610_000 picoseconds. + Weight::from_parts(23_422_000, 3549) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionRoleOf (r:2 w:4) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) fn set_team() -> Weight { - // Minimum execution time: 36_565 nanoseconds. - Weight::from_ref_time(37_464_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(5)) - } - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionAccount (r:0 w:2) + // Proof Size summary in bytes: + // Measured: `369` + // Estimated: `6078` + // Minimum execution time: 39_739_000 picoseconds. + Weight::from_parts(41_306_000, 6078) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionAccount (r:0 w:2) + /// Proof: Nfts CollectionAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) fn force_collection_owner() -> Weight { - // Minimum execution time: 29_028 nanoseconds. - Weight::from_ref_time(29_479_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Nfts Collection (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `311` + // Estimated: `3549` + // Minimum execution time: 17_685_000 picoseconds. + Weight::from_parts(18_258_000, 3549) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Nfts Collection (r:1 w:0) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:0 w:1) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) fn force_collection_config() -> Weight { - // Minimum execution time: 24_695 nanoseconds. - Weight::from_ref_time(25_304_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Nfts Collection (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `276` + // Estimated: `3549` + // Minimum execution time: 13_734_000 picoseconds. + Weight::from_parts(14_337_000, 3549) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:1) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn lock_item_properties() -> Weight { - // Minimum execution time: 28_910 nanoseconds. - Weight::from_ref_time(29_186_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts Attribute (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `435` + // Estimated: `3534` + // Minimum execution time: 19_269_000 picoseconds. + Weight::from_parts(19_859_000, 3534) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:1 w:1) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) fn set_attribute() -> Weight { - // Minimum execution time: 56_407 nanoseconds. - Weight::from_ref_time(58_176_000) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts Attribute (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `539` + // Estimated: `3911` + // Minimum execution time: 51_540_000 picoseconds. + Weight::from_parts(52_663_000, 3911) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:1 w:1) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) fn force_set_attribute() -> Weight { - // Minimum execution time: 36_402 nanoseconds. - Weight::from_ref_time(37_034_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Nfts Attribute (r:1 w:1) - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts ItemConfigOf (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `344` + // Estimated: `3911` + // Minimum execution time: 26_529_000 picoseconds. + Weight::from_parts(27_305_000, 3911) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Nfts Attribute (r:1 w:1) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) fn clear_attribute() -> Weight { - // Minimum execution time: 52_022 nanoseconds. - Weight::from_ref_time(54_059_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Nfts Item (r:1 w:0) - // Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `950` + // Estimated: `3911` + // Minimum execution time: 46_951_000 picoseconds. + Weight::from_parts(48_481_000, 3911) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Nfts Item (r:1 w:0) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) + /// Proof: Nfts ItemAttributesApprovalsOf (max_values: None, max_size: Some(681), added: 3156, mode: MaxEncodedLen) fn approve_item_attributes() -> Weight { - // Minimum execution time: 28_475 nanoseconds. - Weight::from_ref_time(29_162_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Nfts Item (r:1 w:0) - // Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) - // Storage: Nfts Attribute (r:1 w:0) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `381` + // Estimated: `4326` + // Minimum execution time: 17_222_000 picoseconds. + Weight::from_parts(17_819_000, 4326) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Item (r:1 w:0) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) + /// Proof: Nfts ItemAttributesApprovalsOf (max_values: None, max_size: Some(681), added: 3156, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:1001 w:1000) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `n` is `[0, 1000]`. fn cancel_item_attributes_approval(n: u32, ) -> Weight { - // Minimum execution time: 37_529 nanoseconds. - Weight::from_ref_time(38_023_000) - // Standard Error: 8_136 - .saturating_add(Weight::from_ref_time(7_452_872).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(4)) + // Proof Size summary in bytes: + // Measured: `837 + n * (364 ±0)` + // Estimated: `4326 + n * (2921 ±0)` + // Minimum execution time: 26_185_000 picoseconds. + Weight::from_parts(27_038_000, 4326) + // Standard Error: 2_378 + .saturating_add(Weight::from_parts(6_067_888, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes(2_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) - } - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemMetadataOf (r:1 w:1) + .saturating_add(Weight::from_parts(0, 2921).saturating_mul(n.into())) + } + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts ItemMetadataOf (r:1 w:1) + /// Proof: Nfts ItemMetadataOf (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) fn set_metadata() -> Weight { - // Minimum execution time: 49_300 nanoseconds. - Weight::from_ref_time(49_790_000) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts ItemMetadataOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `539` + // Estimated: `3605` + // Minimum execution time: 42_120_000 picoseconds. + Weight::from_parts(43_627_000, 3605) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts ItemMetadataOf (r:1 w:1) + /// Proof: Nfts ItemMetadataOf (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn clear_metadata() -> Weight { - // Minimum execution time: 47_248 nanoseconds. - Weight::from_ref_time(48_094_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionMetadataOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `642` + // Estimated: `3605` + // Minimum execution time: 40_732_000 picoseconds. + Weight::from_parts(42_760_000, 3605) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionMetadataOf (r:1 w:1) + /// Proof: Nfts CollectionMetadataOf (max_values: None, max_size: Some(87), added: 2562, mode: MaxEncodedLen) fn set_collection_metadata() -> Weight { - // Minimum execution time: 44_137 nanoseconds. - Weight::from_ref_time(44_905_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Nfts Collection (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts CollectionMetadataOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `398` + // Estimated: `3552` + // Minimum execution time: 39_443_000 picoseconds. + Weight::from_parts(40_482_000, 3552) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:0) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts CollectionMetadataOf (r:1 w:1) + /// Proof: Nfts CollectionMetadataOf (max_values: None, max_size: Some(87), added: 2562, mode: MaxEncodedLen) fn clear_collection_metadata() -> Weight { - // Minimum execution time: 43_005 nanoseconds. - Weight::from_ref_time(43_898_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Nfts Item (r:1 w:1) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `509` + // Estimated: `3552` + // Minimum execution time: 37_676_000 picoseconds. + Weight::from_parts(39_527_000, 3552) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) fn approve_transfer() -> Weight { - // Minimum execution time: 36_344 nanoseconds. - Weight::from_ref_time(36_954_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Nfts Item (r:1 w:1) - // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `410` + // Estimated: `4326` + // Minimum execution time: 20_787_000 picoseconds. + Weight::from_parts(21_315_000, 4326) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) fn cancel_approval() -> Weight { - // Minimum execution time: 32_418 nanoseconds. - Weight::from_ref_time(33_029_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Nfts Item (r:1 w:1) - // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `418` + // Estimated: `4326` + // Minimum execution time: 18_200_000 picoseconds. + Weight::from_parts(19_064_000, 4326) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) fn clear_all_transfer_approvals() -> Weight { - // Minimum execution time: 31_448 nanoseconds. - Weight::from_ref_time(31_979_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Nfts OwnershipAcceptance (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `418` + // Estimated: `4326` + // Minimum execution time: 17_128_000 picoseconds. + Weight::from_parts(17_952_000, 4326) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Nfts OwnershipAcceptance (r:1 w:1) + /// Proof: Nfts OwnershipAcceptance (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) fn set_accept_ownership() -> Weight { - // Minimum execution time: 27_487 nanoseconds. - Weight::from_ref_time(28_080_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Nfts CollectionConfigOf (r:1 w:1) - // Storage: Nfts Collection (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `3517` + // Minimum execution time: 14_667_000 picoseconds. + Weight::from_parts(15_262_000, 3517) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Nfts CollectionConfigOf (r:1 w:1) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:0) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) fn set_collection_max_supply() -> Weight { - // Minimum execution time: 28_235 nanoseconds. - Weight::from_ref_time(28_967_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Nfts Collection (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `340` + // Estimated: `3549` + // Minimum execution time: 18_435_000 picoseconds. + Weight::from_parts(18_775_000, 3549) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:1) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) fn update_mint_settings() -> Weight { - // Minimum execution time: 28_172 nanoseconds. - Weight::from_ref_time(28_636_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Nfts Item (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts ItemPriceOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `323` + // Estimated: `3538` + // Minimum execution time: 18_125_000 picoseconds. + Weight::from_parts(18_415_000, 3538) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Item (r:1 w:0) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts ItemPriceOf (r:0 w:1) + /// Proof: Nfts ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) fn set_price() -> Weight { - // Minimum execution time: 35_336 nanoseconds. - Weight::from_ref_time(36_026_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Nfts Item (r:1 w:1) - // Storage: Nfts ItemPriceOf (r:1 w:1) - // Storage: Nfts Collection (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Nfts Account (r:0 w:2) - // Storage: Nfts PendingSwapOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `518` + // Estimated: `4326` + // Minimum execution time: 23_237_000 picoseconds. + Weight::from_parts(24_128_000, 4326) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts ItemPriceOf (r:1 w:1) + /// Proof: Nfts ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:0) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:1 w:0) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Account (r:0 w:2) + /// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Nfts PendingSwapOf (r:0 w:1) + /// Proof: Nfts PendingSwapOf (max_values: None, max_size: Some(71), added: 2546, mode: MaxEncodedLen) fn buy_item() -> Weight { - // Minimum execution time: 70_971 nanoseconds. - Weight::from_ref_time(72_036_000) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(6)) + // Proof Size summary in bytes: + // Measured: `705` + // Estimated: `4326` + // Minimum execution time: 53_291_000 picoseconds. + Weight::from_parts(54_614_000, 4326) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { - // Minimum execution time: 5_151 nanoseconds. - Weight::from_ref_time(11_822_888) - // Standard Error: 38_439 - .saturating_add(Weight::from_ref_time(3_511_844).saturating_mul(n.into())) - } - // Storage: Nfts Item (r:2 w:0) - // Storage: Nfts PendingSwapOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_192_000 picoseconds. + Weight::from_parts(4_039_901, 0) + // Standard Error: 10_309 + .saturating_add(Weight::from_parts(3_934_017, 0).saturating_mul(n.into())) + } + /// Storage: Nfts Item (r:2 w:0) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts PendingSwapOf (r:0 w:1) + /// Proof: Nfts PendingSwapOf (max_values: None, max_size: Some(71), added: 2546, mode: MaxEncodedLen) fn create_swap() -> Weight { - // Minimum execution time: 33_027 nanoseconds. - Weight::from_ref_time(33_628_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Nfts PendingSwapOf (r:1 w:1) - // Storage: Nfts Item (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `494` + // Estimated: `7662` + // Minimum execution time: 21_011_000 picoseconds. + Weight::from_parts(22_065_000, 7662) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Nfts PendingSwapOf (r:1 w:1) + /// Proof: Nfts PendingSwapOf (max_values: None, max_size: Some(71), added: 2546, mode: MaxEncodedLen) + /// Storage: Nfts Item (r:1 w:0) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) fn cancel_swap() -> Weight { - // Minimum execution time: 35_890 nanoseconds. - Weight::from_ref_time(36_508_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Nfts Item (r:2 w:2) - // Storage: Nfts PendingSwapOf (r:1 w:2) - // Storage: Nfts Collection (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:2 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Nfts Account (r:0 w:4) - // Storage: Nfts ItemPriceOf (r:0 w:2) + // Proof Size summary in bytes: + // Measured: `513` + // Estimated: `4326` + // Minimum execution time: 21_423_000 picoseconds. + Weight::from_parts(21_743_000, 4326) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Item (r:2 w:2) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts PendingSwapOf (r:1 w:2) + /// Proof: Nfts PendingSwapOf (max_values: None, max_size: Some(71), added: 2546, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:0) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:2 w:0) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:2 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Account (r:0 w:4) + /// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Nfts ItemPriceOf (r:0 w:2) + /// Proof: Nfts ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) fn claim_swap() -> Weight { - // Minimum execution time: 101_076 nanoseconds. - Weight::from_ref_time(101_863_000) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(11)) + // Proof Size summary in bytes: + // Measured: `834` + // Estimated: `7662` + // Minimum execution time: 86_059_000 picoseconds. + Weight::from_parts(88_401_000, 7662) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(10_u64)) + } + /// Storage: Nfts CollectionRoleOf (r:2 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:1) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:10 w:10) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: Nfts ItemMetadataOf (r:1 w:1) + /// Proof: Nfts ItemMetadataOf (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) + /// Storage: Nfts Account (r:0 w:1) + /// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 10]`. + fn mint_pre_signed(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `629` + // Estimated: `6078 + n * (2921 ±0)` + // Minimum execution time: 146_746_000 picoseconds. + Weight::from_parts(152_885_862, 6078) + // Standard Error: 40_442 + .saturating_add(Weight::from_parts(32_887_800, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2921).saturating_mul(n.into())) + } + /// Storage: Nfts Item (r:1 w:0) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) + /// Proof: Nfts ItemAttributesApprovalsOf (max_values: None, max_size: Some(681), added: 3156, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:10 w:10) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 10]`. + fn set_attributes_pre_signed(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `659` + // Estimated: `4326 + n * (2921 ±0)` + // Minimum execution time: 83_960_000 picoseconds. + Weight::from_parts(98_609_885, 4326) + // Standard Error: 85_991 + .saturating_add(Weight::from_parts(32_633_495, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2921).saturating_mul(n.into())) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Nfts NextCollectionId (r:1 w:1) - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionRoleOf (r:0 w:1) - // Storage: Nfts CollectionConfigOf (r:0 w:1) - // Storage: Nfts CollectionAccount (r:0 w:1) + /// Storage: Nfts NextCollectionId (r:1 w:1) + /// Proof: Nfts NextCollectionId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionRoleOf (r:0 w:1) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:0 w:1) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts CollectionAccount (r:0 w:1) + /// Proof: Nfts CollectionAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) fn create() -> Weight { - // Minimum execution time: 44_312 nanoseconds. - Weight::from_ref_time(44_871_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(5)) - } - // Storage: Nfts NextCollectionId (r:1 w:1) - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionRoleOf (r:0 w:1) - // Storage: Nfts CollectionConfigOf (r:0 w:1) - // Storage: Nfts CollectionAccount (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `216` + // Estimated: `3549` + // Minimum execution time: 40_489_000 picoseconds. + Weight::from_parts(41_320_000, 3549) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: Nfts NextCollectionId (r:1 w:1) + /// Proof: Nfts NextCollectionId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionRoleOf (r:0 w:1) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:0 w:1) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts CollectionAccount (r:0 w:1) + /// Proof: Nfts CollectionAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) fn force_create() -> Weight { - // Minimum execution time: 31_654 nanoseconds. - Weight::from_ref_time(32_078_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(5)) - } - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts Item (r:1001 w:1000) - // Storage: Nfts Attribute (r:1001 w:1000) - // Storage: Nfts ItemMetadataOf (r:0 w:1000) - // Storage: Nfts CollectionRoleOf (r:0 w:1) - // Storage: Nfts CollectionMetadataOf (r:0 w:1) - // Storage: Nfts CollectionConfigOf (r:0 w:1) - // Storage: Nfts ItemConfigOf (r:0 w:1000) - // Storage: Nfts Account (r:0 w:1000) - // Storage: Nfts CollectionAccount (r:0 w:1) - /// The range of component `n` is `[0, 1000]`. + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `3549` + // Minimum execution time: 23_257_000 picoseconds. + Weight::from_parts(23_770_000, 3549) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts ItemMetadataOf (r:1 w:0) + /// Proof: Nfts ItemMetadataOf (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) + /// Storage: Nfts CollectionRoleOf (r:1 w:1) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:1001 w:1000) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1000 w:1000) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts CollectionMetadataOf (r:0 w:1) + /// Proof: Nfts CollectionMetadataOf (max_values: None, max_size: Some(87), added: 2562, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:0 w:1) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts CollectionAccount (r:0 w:1) + /// Proof: Nfts CollectionAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) /// The range of component `m` is `[0, 1000]`. + /// The range of component `c` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. - fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - // Minimum execution time: 19_183_393 nanoseconds. - Weight::from_ref_time(17_061_526_855) - // Standard Error: 16_689 - .saturating_add(Weight::from_ref_time(353_523).saturating_mul(n.into())) - // Standard Error: 16_689 - .saturating_add(Weight::from_ref_time(1_861_080).saturating_mul(m.into())) - // Standard Error: 16_689 - .saturating_add(Weight::from_ref_time(8_858_987).saturating_mul(a.into())) - .saturating_add(RocksDbWeight::get().reads(1003)) + fn destroy(_m: u32, _c: u32, a: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `32220 + a * (332 ±0)` + // Estimated: `2523990 + a * (2921 ±0)` + // Minimum execution time: 1_310_198_000 picoseconds. + Weight::from_parts(1_479_261_043, 2523990) + // Standard Error: 4_415 + .saturating_add(Weight::from_parts(6_016_212, 0).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(1004_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(a.into()))) - .saturating_add(RocksDbWeight::get().writes(3005)) - .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(m.into()))) + .saturating_add(RocksDbWeight::get().writes(1005_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(a.into()))) - } - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts Item (r:1 w:1) - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionRoleOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:1) - // Storage: Nfts Account (r:0 w:1) + .saturating_add(Weight::from_parts(0, 2921).saturating_mul(a.into())) + } + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:1) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Account (r:0 w:1) + /// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) fn mint() -> Weight { - // Minimum execution time: 57_753 nanoseconds. - Weight::from_ref_time(58_313_000) - .saturating_add(RocksDbWeight::get().reads(5)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Nfts CollectionRoleOf (r:1 w:0) - // Storage: Nfts Item (r:1 w:1) - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:1) - // Storage: Nfts Account (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `455` + // Estimated: `4326` + // Minimum execution time: 51_910_000 picoseconds. + Weight::from_parts(53_441_000, 4326) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:1) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Account (r:0 w:1) + /// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) fn force_mint() -> Weight { - // Minimum execution time: 56_429 nanoseconds. - Weight::from_ref_time(57_202_000) - .saturating_add(RocksDbWeight::get().reads(5)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts Item (r:1 w:1) - // Storage: Nfts CollectionRoleOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:1) - // Storage: Nfts Account (r:0 w:1) - // Storage: Nfts ItemPriceOf (r:0 w:1) - // Storage: Nfts ItemAttributesApprovalsOf (r:0 w:1) - // Storage: Nfts PendingSwapOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `455` + // Estimated: `4326` + // Minimum execution time: 50_168_000 picoseconds. + Weight::from_parts(51_380_000, 4326) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Nfts ItemConfigOf (r:1 w:1) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts ItemMetadataOf (r:1 w:0) + /// Proof: Nfts ItemMetadataOf (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) + /// Storage: Nfts Account (r:0 w:1) + /// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Nfts ItemPriceOf (r:0 w:1) + /// Proof: Nfts ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) + /// Storage: Nfts ItemAttributesApprovalsOf (r:0 w:1) + /// Proof: Nfts ItemAttributesApprovalsOf (max_values: None, max_size: Some(681), added: 3156, mode: MaxEncodedLen) + /// Storage: Nfts PendingSwapOf (r:0 w:1) + /// Proof: Nfts PendingSwapOf (max_values: None, max_size: Some(71), added: 2546, mode: MaxEncodedLen) fn burn() -> Weight { - // Minimum execution time: 59_681 nanoseconds. - Weight::from_ref_time(60_058_000) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(7)) - } - // Storage: Nfts Collection (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts Item (r:1 w:1) - // Storage: Nfts CollectionRoleOf (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Nfts Account (r:0 w:2) - // Storage: Nfts ItemPriceOf (r:0 w:1) - // Storage: Nfts PendingSwapOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `564` + // Estimated: `4326` + // Minimum execution time: 50_738_000 picoseconds. + Weight::from_parts(51_850_000, 4326) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) + } + /// Storage: Nfts Collection (r:1 w:0) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:1 w:0) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts Account (r:0 w:2) + /// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Nfts ItemPriceOf (r:0 w:1) + /// Proof: Nfts ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) + /// Storage: Nfts PendingSwapOf (r:0 w:1) + /// Proof: Nfts PendingSwapOf (max_values: None, max_size: Some(71), added: 2546, mode: MaxEncodedLen) fn transfer() -> Weight { - // Minimum execution time: 66_085 nanoseconds. - Weight::from_ref_time(67_065_000) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(6)) - } - // Storage: Nfts Collection (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts Item (r:102 w:102) + // Proof Size summary in bytes: + // Measured: `593` + // Estimated: `4326` + // Minimum execution time: 41_055_000 picoseconds. + Weight::from_parts(42_336_000, 4326) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: Nfts Collection (r:1 w:0) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts Item (r:5000 w:5000) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - // Minimum execution time: 25_949 nanoseconds. - Weight::from_ref_time(26_106_000) - // Standard Error: 10_326 - .saturating_add(Weight::from_ref_time(11_496_776).saturating_mul(i.into())) - .saturating_add(RocksDbWeight::get().reads(2)) + // Proof Size summary in bytes: + // Measured: `763 + i * (108 ±0)` + // Estimated: `3549 + i * (3336 ±0)` + // Minimum execution time: 15_688_000 picoseconds. + Weight::from_parts(15_921_000, 3549) + // Standard Error: 14_827 + .saturating_add(Weight::from_parts(17_105_395, 0).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(i.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(i.into()))) + .saturating_add(Weight::from_parts(0, 3336).saturating_mul(i.into())) } - // Storage: Nfts CollectionRoleOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:1) + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:1) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn lock_item_transfer() -> Weight { - // Minimum execution time: 30_080 nanoseconds. - Weight::from_ref_time(30_825_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Nfts CollectionRoleOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `435` + // Estimated: `3534` + // Minimum execution time: 19_981_000 picoseconds. + Weight::from_parts(20_676_000, 3534) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:1) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn unlock_item_transfer() -> Weight { - // Minimum execution time: 30_612 nanoseconds. - Weight::from_ref_time(31_422_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Nfts CollectionRoleOf (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `435` + // Estimated: `3534` + // Minimum execution time: 19_911_000 picoseconds. + Weight::from_parts(20_612_000, 3534) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Collection (r:1 w:0) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:1) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) fn lock_collection() -> Weight { - // Minimum execution time: 27_470 nanoseconds. - Weight::from_ref_time(28_015_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Nfts OwnershipAcceptance (r:1 w:1) - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionAccount (r:0 w:2) + // Proof Size summary in bytes: + // Measured: `340` + // Estimated: `3549` + // Minimum execution time: 16_441_000 picoseconds. + Weight::from_parts(16_890_000, 3549) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Nfts OwnershipAcceptance (r:1 w:1) + /// Proof: Nfts OwnershipAcceptance (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionAccount (r:0 w:2) + /// Proof: Nfts CollectionAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) fn transfer_ownership() -> Weight { - // Minimum execution time: 33_750 nanoseconds. - Weight::from_ref_time(34_139_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionRoleOf (r:0 w:4) + // Proof Size summary in bytes: + // Measured: `388` + // Estimated: `3549` + // Minimum execution time: 22_610_000 picoseconds. + Weight::from_parts(23_422_000, 3549) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionRoleOf (r:2 w:4) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) fn set_team() -> Weight { - // Minimum execution time: 36_565 nanoseconds. - Weight::from_ref_time(37_464_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(5)) - } - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionAccount (r:0 w:2) + // Proof Size summary in bytes: + // Measured: `369` + // Estimated: `6078` + // Minimum execution time: 39_739_000 picoseconds. + Weight::from_parts(41_306_000, 6078) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionAccount (r:0 w:2) + /// Proof: Nfts CollectionAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) fn force_collection_owner() -> Weight { - // Minimum execution time: 29_028 nanoseconds. - Weight::from_ref_time(29_479_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Nfts Collection (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `311` + // Estimated: `3549` + // Minimum execution time: 17_685_000 picoseconds. + Weight::from_parts(18_258_000, 3549) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Nfts Collection (r:1 w:0) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:0 w:1) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) fn force_collection_config() -> Weight { - // Minimum execution time: 24_695 nanoseconds. - Weight::from_ref_time(25_304_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Nfts Collection (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `276` + // Estimated: `3549` + // Minimum execution time: 13_734_000 picoseconds. + Weight::from_parts(14_337_000, 3549) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:1) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn lock_item_properties() -> Weight { - // Minimum execution time: 28_910 nanoseconds. - Weight::from_ref_time(29_186_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts Attribute (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `435` + // Estimated: `3534` + // Minimum execution time: 19_269_000 picoseconds. + Weight::from_parts(19_859_000, 3534) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:1 w:1) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) fn set_attribute() -> Weight { - // Minimum execution time: 56_407 nanoseconds. - Weight::from_ref_time(58_176_000) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts Attribute (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `539` + // Estimated: `3911` + // Minimum execution time: 51_540_000 picoseconds. + Weight::from_parts(52_663_000, 3911) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:1 w:1) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) fn force_set_attribute() -> Weight { - // Minimum execution time: 36_402 nanoseconds. - Weight::from_ref_time(37_034_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Nfts Attribute (r:1 w:1) - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts ItemConfigOf (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `344` + // Estimated: `3911` + // Minimum execution time: 26_529_000 picoseconds. + Weight::from_parts(27_305_000, 3911) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Nfts Attribute (r:1 w:1) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) fn clear_attribute() -> Weight { - // Minimum execution time: 52_022 nanoseconds. - Weight::from_ref_time(54_059_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Nfts Item (r:1 w:0) - // Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `950` + // Estimated: `3911` + // Minimum execution time: 46_951_000 picoseconds. + Weight::from_parts(48_481_000, 3911) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Nfts Item (r:1 w:0) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) + /// Proof: Nfts ItemAttributesApprovalsOf (max_values: None, max_size: Some(681), added: 3156, mode: MaxEncodedLen) fn approve_item_attributes() -> Weight { - // Minimum execution time: 28_475 nanoseconds. - Weight::from_ref_time(29_162_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Nfts Item (r:1 w:0) - // Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) - // Storage: Nfts Attribute (r:1 w:0) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `381` + // Estimated: `4326` + // Minimum execution time: 17_222_000 picoseconds. + Weight::from_parts(17_819_000, 4326) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Item (r:1 w:0) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) + /// Proof: Nfts ItemAttributesApprovalsOf (max_values: None, max_size: Some(681), added: 3156, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:1001 w:1000) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `n` is `[0, 1000]`. fn cancel_item_attributes_approval(n: u32, ) -> Weight { - // Minimum execution time: 37_529 nanoseconds. - Weight::from_ref_time(38_023_000) - // Standard Error: 8_136 - .saturating_add(Weight::from_ref_time(7_452_872).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(4)) + // Proof Size summary in bytes: + // Measured: `837 + n * (364 ±0)` + // Estimated: `4326 + n * (2921 ±0)` + // Minimum execution time: 26_185_000 picoseconds. + Weight::from_parts(27_038_000, 4326) + // Standard Error: 2_378 + .saturating_add(Weight::from_parts(6_067_888, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(2)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) - } - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemMetadataOf (r:1 w:1) + .saturating_add(Weight::from_parts(0, 2921).saturating_mul(n.into())) + } + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts ItemMetadataOf (r:1 w:1) + /// Proof: Nfts ItemMetadataOf (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) fn set_metadata() -> Weight { - // Minimum execution time: 49_300 nanoseconds. - Weight::from_ref_time(49_790_000) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts ItemMetadataOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `539` + // Estimated: `3605` + // Minimum execution time: 42_120_000 picoseconds. + Weight::from_parts(43_627_000, 3605) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts ItemMetadataOf (r:1 w:1) + /// Proof: Nfts ItemMetadataOf (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn clear_metadata() -> Weight { - // Minimum execution time: 47_248 nanoseconds. - Weight::from_ref_time(48_094_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts Collection (r:1 w:1) - // Storage: Nfts CollectionMetadataOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `642` + // Estimated: `3605` + // Minimum execution time: 40_732_000 picoseconds. + Weight::from_parts(42_760_000, 3605) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionMetadataOf (r:1 w:1) + /// Proof: Nfts CollectionMetadataOf (max_values: None, max_size: Some(87), added: 2562, mode: MaxEncodedLen) fn set_collection_metadata() -> Weight { - // Minimum execution time: 44_137 nanoseconds. - Weight::from_ref_time(44_905_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Nfts Collection (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts CollectionMetadataOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `398` + // Estimated: `3552` + // Minimum execution time: 39_443_000 picoseconds. + Weight::from_parts(40_482_000, 3552) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:0) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts CollectionMetadataOf (r:1 w:1) + /// Proof: Nfts CollectionMetadataOf (max_values: None, max_size: Some(87), added: 2562, mode: MaxEncodedLen) fn clear_collection_metadata() -> Weight { - // Minimum execution time: 43_005 nanoseconds. - Weight::from_ref_time(43_898_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Nfts Item (r:1 w:1) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `509` + // Estimated: `3552` + // Minimum execution time: 37_676_000 picoseconds. + Weight::from_parts(39_527_000, 3552) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) fn approve_transfer() -> Weight { - // Minimum execution time: 36_344 nanoseconds. - Weight::from_ref_time(36_954_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Nfts Item (r:1 w:1) - // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `410` + // Estimated: `4326` + // Minimum execution time: 20_787_000 picoseconds. + Weight::from_parts(21_315_000, 4326) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) fn cancel_approval() -> Weight { - // Minimum execution time: 32_418 nanoseconds. - Weight::from_ref_time(33_029_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Nfts Item (r:1 w:1) - // Storage: Nfts CollectionRoleOf (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `418` + // Estimated: `4326` + // Minimum execution time: 18_200_000 picoseconds. + Weight::from_parts(19_064_000, 4326) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) fn clear_all_transfer_approvals() -> Weight { - // Minimum execution time: 31_448 nanoseconds. - Weight::from_ref_time(31_979_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Nfts OwnershipAcceptance (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `418` + // Estimated: `4326` + // Minimum execution time: 17_128_000 picoseconds. + Weight::from_parts(17_952_000, 4326) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Nfts OwnershipAcceptance (r:1 w:1) + /// Proof: Nfts OwnershipAcceptance (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) fn set_accept_ownership() -> Weight { - // Minimum execution time: 27_487 nanoseconds. - Weight::from_ref_time(28_080_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Nfts CollectionConfigOf (r:1 w:1) - // Storage: Nfts Collection (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `3517` + // Minimum execution time: 14_667_000 picoseconds. + Weight::from_parts(15_262_000, 3517) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Nfts CollectionConfigOf (r:1 w:1) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:0) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) fn set_collection_max_supply() -> Weight { - // Minimum execution time: 28_235 nanoseconds. - Weight::from_ref_time(28_967_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Nfts Collection (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `340` + // Estimated: `3549` + // Minimum execution time: 18_435_000 picoseconds. + Weight::from_parts(18_775_000, 3549) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Nfts CollectionRoleOf (r:1 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:1) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) fn update_mint_settings() -> Weight { - // Minimum execution time: 28_172 nanoseconds. - Weight::from_ref_time(28_636_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Nfts Item (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: Nfts ItemPriceOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `323` + // Estimated: `3538` + // Minimum execution time: 18_125_000 picoseconds. + Weight::from_parts(18_415_000, 3538) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Item (r:1 w:0) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts ItemPriceOf (r:0 w:1) + /// Proof: Nfts ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) fn set_price() -> Weight { - // Minimum execution time: 35_336 nanoseconds. - Weight::from_ref_time(36_026_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Nfts Item (r:1 w:1) - // Storage: Nfts ItemPriceOf (r:1 w:1) - // Storage: Nfts Collection (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Nfts Account (r:0 w:2) - // Storage: Nfts PendingSwapOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `518` + // Estimated: `4326` + // Minimum execution time: 23_237_000 picoseconds. + Weight::from_parts(24_128_000, 4326) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts ItemPriceOf (r:1 w:1) + /// Proof: Nfts ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:0) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:1 w:0) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Account (r:0 w:2) + /// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Nfts PendingSwapOf (r:0 w:1) + /// Proof: Nfts PendingSwapOf (max_values: None, max_size: Some(71), added: 2546, mode: MaxEncodedLen) fn buy_item() -> Weight { - // Minimum execution time: 70_971 nanoseconds. - Weight::from_ref_time(72_036_000) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(6)) + // Proof Size summary in bytes: + // Measured: `705` + // Estimated: `4326` + // Minimum execution time: 53_291_000 picoseconds. + Weight::from_parts(54_614_000, 4326) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// The range of component `n` is `[0, 10]`. fn pay_tips(n: u32, ) -> Weight { - // Minimum execution time: 5_151 nanoseconds. - Weight::from_ref_time(11_822_888) - // Standard Error: 38_439 - .saturating_add(Weight::from_ref_time(3_511_844).saturating_mul(n.into())) - } - // Storage: Nfts Item (r:2 w:0) - // Storage: Nfts PendingSwapOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_192_000 picoseconds. + Weight::from_parts(4_039_901, 0) + // Standard Error: 10_309 + .saturating_add(Weight::from_parts(3_934_017, 0).saturating_mul(n.into())) + } + /// Storage: Nfts Item (r:2 w:0) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts PendingSwapOf (r:0 w:1) + /// Proof: Nfts PendingSwapOf (max_values: None, max_size: Some(71), added: 2546, mode: MaxEncodedLen) fn create_swap() -> Weight { - // Minimum execution time: 33_027 nanoseconds. - Weight::from_ref_time(33_628_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Nfts PendingSwapOf (r:1 w:1) - // Storage: Nfts Item (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `494` + // Estimated: `7662` + // Minimum execution time: 21_011_000 picoseconds. + Weight::from_parts(22_065_000, 7662) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Nfts PendingSwapOf (r:1 w:1) + /// Proof: Nfts PendingSwapOf (max_values: None, max_size: Some(71), added: 2546, mode: MaxEncodedLen) + /// Storage: Nfts Item (r:1 w:0) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) fn cancel_swap() -> Weight { - // Minimum execution time: 35_890 nanoseconds. - Weight::from_ref_time(36_508_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Nfts Item (r:2 w:2) - // Storage: Nfts PendingSwapOf (r:1 w:2) - // Storage: Nfts Collection (r:1 w:0) - // Storage: Nfts CollectionConfigOf (r:1 w:0) - // Storage: Nfts ItemConfigOf (r:2 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Nfts Account (r:0 w:4) - // Storage: Nfts ItemPriceOf (r:0 w:2) + // Proof Size summary in bytes: + // Measured: `513` + // Estimated: `4326` + // Minimum execution time: 21_423_000 picoseconds. + Weight::from_parts(21_743_000, 4326) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Nfts Item (r:2 w:2) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts PendingSwapOf (r:1 w:2) + /// Proof: Nfts PendingSwapOf (max_values: None, max_size: Some(71), added: 2546, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:0) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:2 w:0) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:2 w:0) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Nfts Account (r:0 w:4) + /// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Nfts ItemPriceOf (r:0 w:2) + /// Proof: Nfts ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) fn claim_swap() -> Weight { - // Minimum execution time: 101_076 nanoseconds. - Weight::from_ref_time(101_863_000) - .saturating_add(RocksDbWeight::get().reads(8)) - .saturating_add(RocksDbWeight::get().writes(11)) + // Proof Size summary in bytes: + // Measured: `834` + // Estimated: `7662` + // Minimum execution time: 86_059_000 picoseconds. + Weight::from_parts(88_401_000, 7662) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(10_u64)) + } + /// Storage: Nfts CollectionRoleOf (r:2 w:0) + /// Proof: Nfts CollectionRoleOf (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts Item (r:1 w:1) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts ItemConfigOf (r:1 w:1) + /// Proof: Nfts ItemConfigOf (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:10 w:10) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: Nfts ItemMetadataOf (r:1 w:1) + /// Proof: Nfts ItemMetadataOf (max_values: None, max_size: Some(140), added: 2615, mode: MaxEncodedLen) + /// Storage: Nfts Account (r:0 w:1) + /// Proof: Nfts Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 10]`. + fn mint_pre_signed(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `629` + // Estimated: `6078 + n * (2921 ±0)` + // Minimum execution time: 146_746_000 picoseconds. + Weight::from_parts(152_885_862, 6078) + // Standard Error: 40_442 + .saturating_add(Weight::from_parts(32_887_800, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2921).saturating_mul(n.into())) + } + /// Storage: Nfts Item (r:1 w:0) + /// Proof: Nfts Item (max_values: None, max_size: Some(861), added: 3336, mode: MaxEncodedLen) + /// Storage: Nfts ItemAttributesApprovalsOf (r:1 w:1) + /// Proof: Nfts ItemAttributesApprovalsOf (max_values: None, max_size: Some(681), added: 3156, mode: MaxEncodedLen) + /// Storage: Nfts CollectionConfigOf (r:1 w:0) + /// Proof: Nfts CollectionConfigOf (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Nfts Collection (r:1 w:1) + /// Proof: Nfts Collection (max_values: None, max_size: Some(84), added: 2559, mode: MaxEncodedLen) + /// Storage: Nfts Attribute (r:10 w:10) + /// Proof: Nfts Attribute (max_values: None, max_size: Some(446), added: 2921, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// The range of component `n` is `[0, 10]`. + fn set_attributes_pre_signed(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `659` + // Estimated: `4326 + n * (2921 ±0)` + // Minimum execution time: 83_960_000 picoseconds. + Weight::from_parts(98_609_885, 4326) + // Standard Error: 85_991 + .saturating_add(Weight::from_parts(32_633_495, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2921).saturating_mul(n.into())) } } diff --git a/frame/nicks/Cargo.toml b/frame/nicks/Cargo.toml index 1c829efc2d60..b10dcce92d18 100644 --- a/frame/nicks/Cargo.toml +++ b/frame/nicks/Cargo.toml @@ -13,17 +13,17 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/nicks/README.md b/frame/nicks/README.md index a2a897b044f1..768043ffb9bf 100644 --- a/frame/nicks/README.md +++ b/frame/nicks/README.md @@ -1,7 +1,7 @@ # Nicks Module -- [`nicks::Config`](https://docs.rs/pallet-nicks/latest/pallet_nicks/trait.Config.html) -- [`Call`](https://docs.rs/pallet-nicks/latest/pallet_nicks/enum.Call.html) +- [`Config`](https://docs.rs/pallet-nicks/latest/pallet_nicks/pallet/trait.Config.html) +- [`Call`](https://docs.rs/pallet-nicks/latest/pallet_nicks/pallet/enum.Call.html) ## Overview diff --git a/frame/nicks/src/lib.rs b/frame/nicks/src/lib.rs index 3d8307c43410..612443f5dfd5 100644 --- a/frame/nicks/src/lib.rs +++ b/frame/nicks/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -114,7 +114,6 @@ pub mod pallet { StorageMap<_, Twox64Concat, T::AccountId, (BoundedVec, BalanceOf)>; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::call] @@ -129,14 +128,10 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_. /// - /// # + /// ## Complexity /// - O(1). - /// - At most one balance operation. - /// - One storage read/write. - /// - One event. - /// # #[pallet::call_index(0)] - #[pallet::weight(50_000_000)] + #[pallet::weight({50_000_000})] pub fn set_name(origin: OriginFor, name: Vec) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -162,14 +157,10 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_. /// - /// # + /// ## Complexity /// - O(1). - /// - One balance operation. - /// - One storage read/write. - /// - One event. - /// # #[pallet::call_index(1)] - #[pallet::weight(70_000_000)] + #[pallet::weight({70_000_000})] pub fn clear_name(origin: OriginFor) -> DispatchResult { let sender = ensure_signed(origin)?; @@ -189,14 +180,10 @@ pub mod pallet { /// /// The dispatch origin for this call must match `T::ForceOrigin`. /// - /// # + /// ## Complexity /// - O(1). - /// - One unbalanced handler (probably a balance transfer) - /// - One storage read/write. - /// - One event. - /// # #[pallet::call_index(2)] - #[pallet::weight(70_000_000)] + #[pallet::weight({70_000_000})] pub fn kill_name(origin: OriginFor, target: AccountIdLookupOf) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; @@ -217,14 +204,10 @@ pub mod pallet { /// /// The dispatch origin for this call must match `T::ForceOrigin`. /// - /// # + /// ## Complexity /// - O(1). - /// - At most one balance operation. - /// - One storage read/write. - /// - One event. - /// # #[pallet::call_index(3)] - #[pallet::weight(70_000_000)] + #[pallet::weight({70_000_000})] pub fn force_name( origin: OriginFor, target: AccountIdLookupOf, @@ -256,18 +239,14 @@ mod tests { use frame_system::EnsureSignedBy; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BadOrigin, BlakeTwo256, IdentityLookup}, + BuildStorage, }; - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { System: frame_system, Balances: pallet_balances, @@ -281,14 +260,13 @@ mod tests { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -312,6 +290,10 @@ mod tests { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } ord_parameter_types! { @@ -328,7 +310,7 @@ mod tests { } fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 10)] } .assimilate_storage(&mut t) .unwrap(); diff --git a/frame/nis/Cargo.toml b/frame/nis/Cargo.toml index a5fc29cd7915..717bf4cb260d 100644 --- a/frame/nis/Cargo.toml +++ b/frame/nis/Cargo.toml @@ -13,19 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/nis/README.md b/frame/nis/README.md index 4eaddae1786e..0c3f0c383a16 100644 --- a/frame/nis/README.md +++ b/frame/nis/README.md @@ -1,2 +1,5 @@ +# NIS Module -License: Apache-2.0 +Provides a non-interactiove variant of staking. + +License: Apache-2.0 \ No newline at end of file diff --git a/frame/nis/src/benchmarking.rs b/frame/nis/src/benchmarking.rs index b45c982bd150..0cc9e7421d0e 100644 --- a/frame/nis/src/benchmarking.rs +++ b/frame/nis/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,8 +20,10 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::{account, benchmarks, whitelisted_caller}; -use frame_support::traits::{nonfungible::Inspect, Currency, EnsureOrigin, Get}; +use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller, BenchmarkError}; +use frame_support::traits::{ + fungible::Inspect as FunInspect, nonfungible::Inspect, EnsureOrigin, Get, +}; use frame_system::RawOrigin; use sp_arithmetic::Perquintill; use sp_runtime::{ @@ -35,7 +37,7 @@ use crate::Pallet as Nis; const SEED: u32 = 0; type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; + <::Currency as FunInspect<::AccountId>>::Balance; fn fill_queues() -> Result<(), DispatchError> { // filling queues involves filling the first queue entirely and placing a single item in all @@ -45,10 +47,7 @@ fn fill_queues() -> Result<(), DispatchError> { let bids = T::MaxQueueLen::get(); let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be( - &caller, - T::MinBid::get() * BalanceOf::::from(queues + bids), - ); + T::Currency::set_balance(&caller, T::MinBid::get() * BalanceOf::::from(queues + bids)); for _ in 0..bids { Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; @@ -63,7 +62,9 @@ benchmarks! { place_bid { let l in 0..(T::MaxQueueLen::get() - 1); let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let ed = T::Currency::minimum_balance(); + let bid = T::MinBid::get(); + T::Currency::set_balance(&caller, (ed + bid) * BalanceOf::::from(l + 1) + bid); for i in 0..l { Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; } @@ -75,7 +76,10 @@ benchmarks! { place_bid_max { let caller: T::AccountId = whitelisted_caller(); let origin = RawOrigin::Signed(caller.clone()); - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let ed = T::Currency::minimum_balance(); + let bid = T::MinBid::get(); + let ql = T::MaxQueueLen::get(); + T::Currency::set_balance(&caller, (ed + bid) * BalanceOf::::from(ql + 1) + bid); for i in 0..T::MaxQueueLen::get() { Nis::::place_bid(origin.clone().into(), T::MinBid::get(), 1)?; } @@ -90,7 +94,9 @@ benchmarks! { retract_bid { let l in 1..T::MaxQueueLen::get(); let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let ed = T::Currency::minimum_balance(); + let bid = T::MinBid::get(); + T::Currency::set_balance(&caller, (ed + bid) * BalanceOf::::from(l + 1) + bid); for i in 0..l { Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; } @@ -100,69 +106,85 @@ benchmarks! { } fund_deficit { - let origin = T::FundOrigin::successful_origin(); + let origin = + T::FundOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let caller: T::AccountId = whitelisted_caller(); let bid = T::MinBid::get().max(One::one()); - T::Currency::make_free_balance_be(&caller, bid); + let ed = T::Currency::minimum_balance(); + T::Currency::set_balance(&caller, ed + bid); Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?; Nis::::process_queues(Perquintill::one(), 1, 1, &mut WeightCounter::unlimited()); Nis::::communify(RawOrigin::Signed(caller.clone()).into(), 0)?; - let original = T::Currency::free_balance(&Nis::::account_id()); - T::Currency::make_free_balance_be(&Nis::::account_id(), BalanceOf::::min_value()); + let original = T::Currency::balance(&Nis::::account_id()); + T::Currency::set_balance(&Nis::::account_id(), BalanceOf::::min_value()); }: _(origin) verify { // Must fund at least 99.999% of the required amount. let missing = Perquintill::from_rational( - T::Currency::free_balance(&Nis::::account_id()), original).left_from_one(); + T::Currency::balance(&Nis::::account_id()), original).left_from_one(); assert!(missing <= Perquintill::one() / 100_000); } - thaw_private { + communify { let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, T::MinBid::get() * BalanceOf::::from(3u32)); - Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; - Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; + let bid = T::MinBid::get().max(One::one()) * 100u32.into(); + let ed = T::Currency::minimum_balance(); + T::Currency::set_balance(&caller, ed + bid + bid); + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?; + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?; Nis::::process_queues(Perquintill::one(), 1, 2, &mut WeightCounter::unlimited()); - Receipts::::mutate(0, |m_g| if let Some(ref mut g) = m_g { g.expiry = Zero::zero() }); - }: _(RawOrigin::Signed(caller.clone()), 0, None) + }: _(RawOrigin::Signed(caller.clone()), 0) verify { - assert!(Receipts::::get(0).is_none()); + assert_eq!(Nis::::owner(&0), None); } - thaw_communal { + privatize { let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, T::MinBid::get() * BalanceOf::::from(3u32)); - Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; - Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; + let bid = T::MinBid::get().max(One::one()); + let ed = T::Currency::minimum_balance(); + T::Currency::set_balance(&caller, ed + bid + bid); + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?; + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?; Nis::::process_queues(Perquintill::one(), 1, 2, &mut WeightCounter::unlimited()); - Receipts::::mutate(0, |m_g| if let Some(ref mut g) = m_g { g.expiry = Zero::zero() }); Nis::::communify(RawOrigin::Signed(caller.clone()).into(), 0)?; }: _(RawOrigin::Signed(caller.clone()), 0) verify { - assert!(Receipts::::get(0).is_none()); + assert_eq!(Nis::::owner(&0), Some(caller)); } - privatize { + thaw_private { + let whale: T::AccountId = account("whale", 0, SEED); let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, T::MinBid::get() * BalanceOf::::from(3u32)); - Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; - Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; + let bid = T::MinBid::get().max(One::one()); + let ed = T::Currency::minimum_balance(); + T::Currency::set_balance(&caller, ed + bid + bid); + // Ensure we don't get throttled. + T::Currency::set_balance(&whale, T::ThawThrottle::get().0.saturating_reciprocal_mul_ceil(T::Currency::balance(&caller))); + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?; + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?; Nis::::process_queues(Perquintill::one(), 1, 2, &mut WeightCounter::unlimited()); - Nis::::communify(RawOrigin::Signed(caller.clone()).into(), 0)?; - }: _(RawOrigin::Signed(caller.clone()), 0) + frame_system::Pallet::::set_block_number(Receipts::::get(0).unwrap().expiry); + }: _(RawOrigin::Signed(caller.clone()), 0, None) verify { - assert_eq!(Nis::::owner(&0), Some(caller)); + assert!(Receipts::::get(0).is_none()); } - communify { + thaw_communal { + let whale: T::AccountId = account("whale", 0, SEED); let caller: T::AccountId = whitelisted_caller(); - T::Currency::make_free_balance_be(&caller, T::MinBid::get() * BalanceOf::::from(3u32)); - Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; - Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), T::MinBid::get(), 1)?; + let bid = T::MinBid::get().max(One::one()); + let ed = T::Currency::minimum_balance(); + T::Currency::set_balance(&caller, ed + bid + bid); + // Ensure we don't get throttled. + T::Currency::set_balance(&whale, T::ThawThrottle::get().0.saturating_reciprocal_mul_ceil(T::Currency::balance(&caller))); + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?; + Nis::::place_bid(RawOrigin::Signed(caller.clone()).into(), bid, 1)?; Nis::::process_queues(Perquintill::one(), 1, 2, &mut WeightCounter::unlimited()); + frame_system::Pallet::::set_block_number(Receipts::::get(0).unwrap().expiry); + Nis::::communify(RawOrigin::Signed(caller.clone()).into(), 0)?; }: _(RawOrigin::Signed(caller.clone()), 0) verify { - assert_eq!(Nis::::owner(&0), None); + assert!(Receipts::::get(0).is_none()); } process_queues { @@ -196,6 +218,9 @@ benchmarks! { process_bid { let who = account::("bidder", 0, SEED); + let min_bid = T::MinBid::get().max(One::one()); + let ed = T::Currency::minimum_balance(); + T::Currency::set_balance(&who, ed + min_bid); let bid = Bid { amount: T::MinBid::get(), who, @@ -215,5 +240,5 @@ benchmarks! { ) } - impl_benchmark_test_suite!(Nis, crate::mock::new_test_ext(), crate::mock::Test); + impl_benchmark_test_suite!(Nis, crate::mock::new_test_ext_empty(), crate::mock::Test); } diff --git a/frame/nis/src/lib.rs b/frame/nis/src/lib.rs index fa0163fb60bf..1bf052a39d1e 100644 --- a/frame/nis/src/lib.rs +++ b/frame/nis/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -71,21 +71,21 @@ //! //! ## Terms //! -//! - *Effective total issuance*: The total issuance of balances in the system, including all claims -//! of all outstanding receipts but excluding `IgnoredIssuance`. +//! - *Effective total issuance*: The total issuance of balances in the system, equal to the active +//! issuance plus the value of all outstanding receipts, less `IgnoredIssuance`. #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::{ - dispatch::{DispatchError, DispatchResult}, - traits::fungible::{Inspect as FungibleInspect, Mutate as FungibleMutate}, +use frame_support::traits::{ + fungible::{self, Inspect as FunInspect, Mutate as FunMutate}, + tokens::{DepositConsequence, Fortitude, Preservation, Provenance, WithdrawConsequence}, }; pub use pallet::*; use sp_arithmetic::{traits::Unsigned, RationalArg}; use sp_core::TypedGet; use sp_runtime::{ traits::{Convert, ConvertBack}, - Perquintill, + DispatchError, Perquintill, }; mod benchmarking; @@ -117,7 +117,7 @@ where } pub struct NoCounterpart(sp_std::marker::PhantomData); -impl FungibleInspect for NoCounterpart { +impl FunInspect for NoCounterpart { type Balance = u32; fn total_issuance() -> u32 { 0 @@ -125,34 +125,30 @@ impl FungibleInspect for NoCounterpart { fn minimum_balance() -> u32 { 0 } - fn balance(_who: &T) -> u32 { + fn balance(_: &T) -> u32 { 0 } - fn reducible_balance(_who: &T, _keep_alive: bool) -> u32 { + fn total_balance(_: &T) -> u32 { 0 } - fn can_deposit( - _who: &T, - _amount: u32, - _mint: bool, - ) -> frame_support::traits::tokens::DepositConsequence { - frame_support::traits::tokens::DepositConsequence::Success + fn reducible_balance(_: &T, _: Preservation, _: Fortitude) -> u32 { + 0 } - fn can_withdraw( - _who: &T, - _amount: u32, - ) -> frame_support::traits::tokens::WithdrawConsequence { - frame_support::traits::tokens::WithdrawConsequence::Success + fn can_deposit(_: &T, _: u32, _: Provenance) -> DepositConsequence { + DepositConsequence::Success } -} -impl FungibleMutate for NoCounterpart { - fn mint_into(_who: &T, _amount: u32) -> DispatchResult { - Ok(()) + fn can_withdraw(_: &T, _: u32) -> WithdrawConsequence { + WithdrawConsequence::Success } - fn burn_from(_who: &T, _amount: u32) -> Result { - Ok(0) +} +impl fungible::Unbalanced for NoCounterpart { + fn handle_dust(_: fungible::Dust) {} + fn write_balance(_: &T, _: Self::Balance) -> Result, DispatchError> { + Ok(None) } + fn set_total_issuance(_: Self::Balance) {} } +impl FunMutate for NoCounterpart {} impl Convert for NoCounterpart { fn convert(_: Perquintill) -> u32 { 0 @@ -161,15 +157,20 @@ impl Convert for NoCounterpart { #[frame_support::pallet] pub mod pallet { - use super::{FungibleInspect, FungibleMutate}; + use super::{FunInspect, FunMutate}; pub use crate::weights::WeightInfo; use frame_support::{ pallet_prelude::*, traits::{ - nonfungible::{Inspect as NonfungibleInspect, Transfer as NonfungibleTransfer}, - Currency, Defensive, DefensiveSaturating, - ExistenceRequirement::AllowDeath, - NamedReservableCurrency, OnUnbalanced, + fungible::{self, hold::Mutate as FunHoldMutate, Balanced as FunBalanced}, + nonfungible::{Inspect as NftInspect, Transfer as NftTransfer}, + tokens::{ + Fortitude::Polite, + Precision::{BestEffort, Exact}, + Preservation::Expendable, + Restriction::{Free, OnHold}, + }, + Defensive, DefensiveSaturating, OnUnbalanced, }, PalletId, }; @@ -177,22 +178,18 @@ pub mod pallet { use sp_arithmetic::{PerThing, Perquintill}; use sp_runtime::{ traits::{AccountIdConversion, Bounded, Convert, ConvertBack, Saturating, Zero}, - TokenError, + Rounding, TokenError, }; use sp_std::prelude::*; type BalanceOf = - <::Currency as Currency<::AccountId>>::Balance; - type PositiveImbalanceOf = <::Currency as Currency< - ::AccountId, - >>::PositiveImbalance; - type ReceiptRecordOf = ReceiptRecord< - ::AccountId, - ::BlockNumber, - BalanceOf, - >; + <::Currency as FunInspect<::AccountId>>::Balance; + type DebtOf = + fungible::Debt<::AccountId, ::Currency>; + type ReceiptRecordOf = + ReceiptRecord<::AccountId, BlockNumberFor, BalanceOf>; type IssuanceInfoOf = IssuanceInfo>; - type SummaryRecordOf = SummaryRecord<::BlockNumber, BalanceOf>; + type SummaryRecordOf = SummaryRecord, BalanceOf>; type BidOf = Bid, ::AccountId>; type QueueTotalsTypeOf = BoundedVec<(u32, BalanceOf), ::QueueCount>; @@ -209,7 +206,13 @@ pub mod pallet { type PalletId: Get; /// Currency type that this works on. - type Currency: NamedReservableCurrency; + type Currency: FunInspect + + FunMutate + + FunBalanced + + FunHoldMutate; + + /// Overarching hold reason. + type RuntimeHoldReason: From; /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to /// `From`. @@ -231,7 +234,7 @@ pub mod pallet { type IgnoredIssuance: Get>; /// The accounting system for the fungible counterpart tokens. - type Counterpart: FungibleMutate; + type Counterpart: FunMutate; /// The system to convert an overall proportion of issuance into a number of fungible /// counterpart tokens. @@ -239,12 +242,12 @@ pub mod pallet { /// In general it's best to use `WithMaximumOf`. type CounterpartAmount: ConvertBack< Perquintill, - >::Balance, + >::Balance, >; /// Unbalanced handler to account for funds created (in case of a higher total issuance over /// freezing period). - type Deficit: OnUnbalanced>; + type Deficit: OnUnbalanced>; /// The target sum of all receipts' proportions. type Target: Get; @@ -269,7 +272,7 @@ pub mod pallet { /// The base period for the duration queues. This is the common multiple across all /// supported freezing durations that can be bid upon. #[pallet::constant] - type BasePeriod: Get; + type BasePeriod: Get>; /// The minimum amount of funds that may be placed in a bid. Note that this /// does not actually limit the amount which may be represented in a receipt since bids may @@ -290,7 +293,7 @@ pub mod pallet { /// A larger value results in fewer storage hits each block, but a slower period to get to /// the target. #[pallet::constant] - type IntakePeriod: Get; + type IntakePeriod: Get>; /// The maximum amount of bids that can consolidated into receipts in a single intake. A /// larger value here means less of the block available for transactions should there be a @@ -300,17 +303,10 @@ pub mod pallet { /// The maximum proportion which may be thawed and the period over which it is reset. #[pallet::constant] - type ThawThrottle: Get<(Perquintill, Self::BlockNumber)>; - - /// The name for the reserve ID. - #[pallet::constant] - type ReserveId: Get< - >::ReserveIdentifier, - >; + type ThawThrottle: Get<(Perquintill, BlockNumberFor)>; } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); /// A single bid, an item of a *queue* in `Queues`. @@ -349,7 +345,7 @@ pub mod pallet { /// /// `issuance - frozen + proportion * issuance` /// - /// where `issuance = total_issuance - IgnoredIssuance` + /// where `issuance = active_issuance - IgnoredIssuance` #[derive( Clone, Eq, PartialEq, Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, )] @@ -414,7 +410,7 @@ pub mod pallet { /// The identity of the receipt. index: ReceiptIndex, /// The block number at which the receipt may be thawed. - expiry: T::BlockNumber, + expiry: BlockNumberFor, /// The owner of the receipt. who: T::AccountId, /// The proportion of the effective total issuance which the receipt represents. @@ -476,6 +472,14 @@ pub mod pallet { AlreadyPrivate, } + /// A reason for the NIS pallet placing a hold on funds. + #[pallet::composite_enum] + pub enum HoldReason { + /// The NIS Pallet has reserved it for a non-fungible receipt. + #[codec(index = 0)] + NftReceipt, + } + pub(crate) struct WeightCounter { pub(crate) used: Weight, pub(crate) limit: Weight, @@ -501,7 +505,7 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { - fn on_initialize(n: T::BlockNumber) -> Weight { + fn on_initialize(n: BlockNumberFor) -> Weight { let mut weight_counter = WeightCounter { used: Weight::zero(), limit: T::MaxIntakeWeight::get() }; if T::IntakePeriod::get().is_zero() || (n % T::IntakePeriod::get()).is_zero() { @@ -555,16 +559,17 @@ pub mod pallet { |q| -> Result<(u32, BalanceOf), DispatchError> { let queue_full = q.len() == T::MaxQueueLen::get() as usize; ensure!(!queue_full || q[0].amount < amount, Error::::BidTooLow); - T::Currency::reserve_named(&T::ReserveId::get(), &who, amount)?; + T::Currency::hold(&HoldReason::NftReceipt.into(), &who, amount)?; // queue is let mut bid = Bid { amount, who: who.clone() }; let net = if queue_full { sp_std::mem::swap(&mut q[0], &mut bid); - let _ = T::Currency::unreserve_named( - &T::ReserveId::get(), + let _ = T::Currency::release( + &HoldReason::NftReceipt.into(), &bid.who, bid.amount, + BestEffort, ); Self::deposit_event(Event::::BidDropped { who: bid.who, @@ -616,19 +621,21 @@ pub mod pallet { ensure!(queue_index < queue_count, Error::::DurationTooBig); let bid = Bid { amount, who }; - let new_len = Queues::::try_mutate(duration, |q| -> Result { - let pos = q.iter().position(|i| i == &bid).ok_or(Error::::UnknownBid)?; - q.remove(pos); - Ok(q.len() as u32) - })?; + let mut queue = Queues::::get(duration); + let pos = queue.iter().position(|i| i == &bid).ok_or(Error::::UnknownBid)?; + queue.remove(pos); + let new_len = queue.len() as u32; + + T::Currency::release(&HoldReason::NftReceipt.into(), &bid.who, bid.amount, BestEffort)?; + + Queues::::insert(duration, queue); QueueTotals::::mutate(|qs| { qs.bounded_resize(queue_count, (0, Zero::zero())); qs[queue_index].0 = new_len; qs[queue_index].1.saturating_reduce(bid.amount); }); - T::Currency::unreserve_named(&T::ReserveId::get(), &bid.who, bid.amount); Self::deposit_event(Event::BidRetracted { who: bid.who, amount: bid.amount, duration }); Ok(()) @@ -646,7 +653,7 @@ pub mod pallet { let issuance = Self::issuance_with(&our_account, &summary); let deficit = issuance.required.saturating_sub(issuance.holdings); ensure!(!deficit.is_zero(), Error::::AlreadyFunded); - T::Deficit::on_unbalanced(T::Currency::deposit_creating(&our_account, deficit)); + T::Deficit::on_unbalanced(T::Currency::deposit(&our_account, deficit, Exact)?); Self::deposit_event(Event::::Funded { deficit }); Ok(()) } @@ -703,6 +710,7 @@ pub mod pallet { // Multiply the proportion it is by the total issued. let our_account = Self::account_id(); let effective_issuance = Self::issuance_with(&our_account, &summary).effective; + // let amount = proportion.mul_ceil(effective_issuance); let amount = proportion * effective_issuance; receipt.proportion.saturating_reduce(proportion); @@ -711,46 +719,38 @@ pub mod pallet { let dropped = receipt.proportion.is_zero(); if amount > on_hold { - T::Currency::unreserve_named(&T::ReserveId::get(), &who, on_hold); + T::Currency::release(&HoldReason::NftReceipt.into(), &who, on_hold, Exact)?; let deficit = amount - on_hold; // Try to transfer deficit from pot to receipt owner. summary.receipts_on_hold.saturating_reduce(on_hold); on_hold = Zero::zero(); - T::Currency::transfer(&our_account, &who, deficit, AllowDeath) + T::Currency::transfer(&our_account, &who, deficit, Expendable) .map_err(|_| Error::::Unfunded)?; } else { - T::Currency::unreserve_named(&T::ReserveId::get(), &who, amount); on_hold.saturating_reduce(amount); summary.receipts_on_hold.saturating_reduce(amount); if dropped && !on_hold.is_zero() { // Reclaim any remainder: - // Transfer `excess` to the pot if we have now fully compensated for the - // receipt. - // - // This will legitimately fail if there is no pot account in existance. - // There's nothing we can do about this so we just swallow the error. - // This code is not ideal and could fail in the second phase leaving - // the system in an invalid state. It can be fixed properly with the - // new API in https://github.com/paritytech/substrate/pull/12951 - // - // Below is what it should look like then: - // let _ = T::Currency::repatriate_reserved_named( - // &T::ReserveId::get(), - // &who, - // &our_account, - // excess, - // BalanceStatus::Free, - // ).defensive(); - T::Currency::unreserve_named(&T::ReserveId::get(), &who, on_hold); - // It could theoretically be locked, so really we should be using a more - // forceful variant. But the alternative `repatriate_reserved_named` will - // fail if the destination account doesn't exist. This should be fixed when - // we move to the `fungible::*` traits, which should include a force - // transfer function to transfer the reserved balance into free balance in - // the destination regardless of locks and create it if it doesn't exist. - let _ = T::Currency::transfer(&who, &Self::account_id(), on_hold, AllowDeath); + // Transfer excess of `on_hold` to the pot if we have now fully compensated for + // the receipt. + T::Currency::transfer_on_hold( + &HoldReason::NftReceipt.into(), + &who, + &our_account, + on_hold, + Exact, + Free, + Polite, + ) + .map(|_| ()) + // We ignore this error as it just means the amount we're trying to deposit is + // dust and the beneficiary account doesn't exist. + .or_else( + |e| if e == TokenError::CannotCreate.into() { Ok(()) } else { Err(e) }, + )?; summary.receipts_on_hold.saturating_reduce(on_hold); } + T::Currency::release(&HoldReason::NftReceipt.into(), &who, amount, Exact)?; } if dropped { @@ -798,7 +798,8 @@ pub mod pallet { summary.thawed.saturating_accrue(receipt.proportion); ensure!(summary.thawed <= throttle, Error::::Throttled); - T::Counterpart::burn_from(&who, T::CounterpartAmount::convert(receipt.proportion))?; + let cp_amount = T::CounterpartAmount::convert(receipt.proportion); + T::Counterpart::burn_from(&who, cp_amount, Exact, Polite)?; // Multiply the proportion it is by the total issued. let our_account = Self::account_id(); @@ -808,7 +809,7 @@ pub mod pallet { summary.proportion_owed.saturating_reduce(receipt.proportion); // Try to transfer amount owed from pot to receipt owner. - T::Currency::transfer(&our_account, &who, amount, AllowDeath) + T::Currency::transfer(&our_account, &who, amount, Expendable) .map_err(|_| Error::::Unfunded)?; Receipts::::remove(index); @@ -841,11 +842,10 @@ pub mod pallet { ensure!(owner == who, Error::::NotOwner); // Unreserve and transfer the funds to the pot. - T::Currency::unreserve_named(&T::ReserveId::get(), &who, on_hold); - // Transfer `excess` to the pot if we have now fully compensated for the receipt. - T::Currency::transfer(&who, &Self::account_id(), on_hold, AllowDeath) + let reason = HoldReason::NftReceipt.into(); + let us = Self::account_id(); + T::Currency::transfer_on_hold(&reason, &who, &us, on_hold, Exact, Free, Polite) .map_err(|_| Error::::Unfunded)?; - // TODO #12951: ^^^ The above should be done in a single operation `transfer_on_hold`. // Record that we've moved the amount reserved. let mut summary: SummaryRecordOf = Summary::::get(); @@ -882,16 +882,20 @@ pub mod pallet { let effective_issuance = Self::issuance_with(&our_account, &summary).effective; let max_amount = receipt.proportion * effective_issuance; // Avoid trying to place more in the account's reserve than we have available in the pot - let amount = max_amount.min(T::Currency::free_balance(&our_account)); + let amount = max_amount.min(T::Currency::balance(&our_account)); // Burn fungible counterparts. - T::Counterpart::burn_from(&who, T::CounterpartAmount::convert(receipt.proportion))?; + T::Counterpart::burn_from( + &who, + T::CounterpartAmount::convert(receipt.proportion), + Exact, + Polite, + )?; // Transfer the funds from the pot to the owner and reserve - T::Currency::transfer(&Self::account_id(), &who, amount, AllowDeath) - .map_err(|_| Error::::Unfunded)?; - T::Currency::reserve_named(&T::ReserveId::get(), &who, amount)?; - // TODO: ^^^ The above should be done in a single operation `transfer_and_hold`. + let reason = HoldReason::NftReceipt.into(); + let us = Self::account_id(); + T::Currency::transfer_and_hold(&reason, &us, &who, amount, Exact, Expendable, Polite)?; // Record that we've moved the amount reserved. summary.receipts_on_hold.saturating_accrue(amount); @@ -921,7 +925,7 @@ pub mod pallet { pub required: Balance, } - impl NonfungibleInspect for Pallet { + impl NftInspect for Pallet { type ItemId = ReceiptIndex; fn owner(item: &ReceiptIndex) -> Option { @@ -940,31 +944,19 @@ pub mod pallet { } } - impl NonfungibleTransfer for Pallet { - fn transfer(index: &ReceiptIndex, destination: &T::AccountId) -> DispatchResult { + impl NftTransfer for Pallet { + fn transfer(index: &ReceiptIndex, dest: &T::AccountId) -> DispatchResult { let mut item = Receipts::::get(index).ok_or(TokenError::UnknownAsset)?; let (owner, on_hold) = item.owner.take().ok_or(Error::::AlreadyCommunal)?; - // TODO: This should all be replaced by a single call `transfer_held`. - let shortfall = T::Currency::unreserve_named(&T::ReserveId::get(), &owner, on_hold); - if !shortfall.is_zero() { - let _ = - T::Currency::reserve_named(&T::ReserveId::get(), &owner, on_hold - shortfall); - return Err(TokenError::NoFunds.into()) - } - if let Err(e) = T::Currency::transfer(&owner, destination, on_hold, AllowDeath) { - let _ = T::Currency::reserve_named(&T::ReserveId::get(), &owner, on_hold); - return Err(e) - } - // This can never fail, and if it somehow does, then we can't handle this gracefully. - let _ = - T::Currency::reserve_named(&T::ReserveId::get(), destination, on_hold).defensive(); + let reason = HoldReason::NftReceipt.into(); + T::Currency::transfer_on_hold(&reason, &owner, dest, on_hold, Exact, OnHold, Polite)?; - item.owner = Some((destination.clone(), on_hold)); + item.owner = Some((dest.clone(), on_hold)); Receipts::::insert(&index, &item); Pallet::::deposit_event(Event::::Transferred { from: owner, - to: destination.clone(), + to: dest.clone(), index: *index, }); Ok(()) @@ -998,9 +990,9 @@ pub mod pallet { summary: &SummaryRecordOf, ) -> IssuanceInfo> { let total_issuance = - T::Currency::total_issuance().saturating_sub(T::IgnoredIssuance::get()); + T::Currency::active_issuance().saturating_sub(T::IgnoredIssuance::get()); let holdings = - T::Currency::free_balance(our_account).saturating_add(summary.receipts_on_hold); + T::Currency::balance(our_account).saturating_add(summary.receipts_on_hold); let other = total_issuance.saturating_sub(holdings); let effective = summary.proportion_owed.left_from_one().saturating_reciprocal_mul(other); @@ -1067,7 +1059,7 @@ pub mod pallet { pub(crate) fn process_queue( duration: u32, - now: T::BlockNumber, + now: BlockNumberFor, our_account: &T::AccountId, issuance: &IssuanceInfo>, max_bids: u32, @@ -1111,7 +1103,7 @@ pub mod pallet { pub(crate) fn process_bid( mut bid: BidOf, - expiry: T::BlockNumber, + expiry: BlockNumberFor, _our_account: &T::AccountId, issuance: &IssuanceInfo>, remaining: &mut BalanceOf, @@ -1137,7 +1129,8 @@ pub mod pallet { // Now to activate the bid... let n = amount; let d = issuance.effective; - let proportion = Perquintill::from_rational(n, d); + let proportion = Perquintill::from_rational_with_rounding(n, d, Rounding::Down) + .defensive_unwrap_or_default(); let who = bid.who; let index = summary.index; summary.proportion_owed.defensive_saturating_accrue(proportion); diff --git a/frame/nis/src/mock.rs b/frame/nis/src/mock.rs index 585ed2d8b278..76fdf5f3f069 100644 --- a/frame/nis/src/mock.rs +++ b/frame/nis/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,26 +21,27 @@ use crate::{self as pallet_nis, Perquintill, WithMaximumOf}; use frame_support::{ ord_parameter_types, parameter_types, - traits::{ConstU16, ConstU32, ConstU64, Currency, OnFinalize, OnInitialize, StorageMapShim}, + traits::{ + fungible::Inspect, ConstU16, ConstU32, ConstU64, Everything, OnFinalize, OnInitialize, + StorageMapShim, + }, weights::Weight, PalletId, }; use pallet_balances::{Instance1, Instance2}; use sp_core::{ConstU128, H256}; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; +pub type Balance = u64; + // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { System: frame_system, Balances: pallet_balances::, @@ -50,52 +51,54 @@ frame_support::construct_runtime!( ); impl frame_system::Config for Test { - type BaseCallFilter = frame_support::traits::Everything; + type BaseCallFilter = Everything; type BlockWeights = (); type BlockLength = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type DbWeight = (); type Version = (); type PalletInfo = PalletInfo; - type AccountData = pallet_balances::AccountData; + type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); type SS58Prefix = ConstU16<42>; type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; + type MaxConsumers = ConstU32<16>; } impl pallet_balances::Config for Test { - type Balance = u64; + type Balance = Balance; type DustRemoval = (); type RuntimeEvent = RuntimeEvent; - type ExistentialDeposit = frame_support::traits::ConstU64<1>; + type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); type MaxLocks = (); type MaxReserves = ConstU32<1>; type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = RuntimeHoldReason; + type MaxHolds = ConstU32<1>; } impl pallet_balances::Config for Test { type Balance = u128; type DustRemoval = (); type RuntimeEvent = RuntimeEvent; - type ExistentialDeposit = frame_support::traits::ConstU128<1>; + type ExistentialDeposit = ConstU128<1>; type AccountStore = StorageMapShim< pallet_balances::Account, - frame_system::Provider, u64, pallet_balances::AccountData, >; @@ -103,16 +106,19 @@ impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } parameter_types! { - pub IgnoredIssuance: u64 = Balances::total_balance(&0); // Account zero is ignored. + pub IgnoredIssuance: Balance = Balances::total_balance(&0); // Account zero is ignored. pub const NisPalletId: PalletId = PalletId(*b"py/nis "); pub static Target: Perquintill = Perquintill::zero(); pub const MinReceipt: Perquintill = Perquintill::from_percent(1); pub const ThawThrottle: (Perquintill, u64) = (Perquintill::from_percent(25), 5); - pub static MaxIntakeWeight: Weight = Weight::from_ref_time(2_000_000_000_000); - pub const ReserveId: [u8; 8] = *b"py/nis "; + pub static MaxIntakeWeight: Weight = Weight::from_parts(2_000_000_000_000, 0); } ord_parameter_types! { @@ -140,13 +146,13 @@ impl pallet_nis::Config for Test { type MaxIntakeWeight = MaxIntakeWeight; type MinReceipt = MinReceipt; type ThawThrottle = ThawThrottle; - type ReserveId = ReserveId; + type RuntimeHoldReason = RuntimeHoldReason; } // 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::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100)], } @@ -155,6 +161,13 @@ pub fn new_test_ext() -> sp_io::TestExternalities { t.into() } +// This function basically just builds a genesis storage key/value store according to +// our desired mockup, but without any balances. +#[cfg(feature = "runtime-benchmarks")] +pub fn new_test_ext_empty() -> sp_io::TestExternalities { + frame_system::GenesisConfig::::default().build_storage().unwrap().into() +} + pub fn run_to_block(n: u64) { while System::block_number() < n { Nis::on_finalize(System::block_number()); diff --git a/frame/nis/src/tests.rs b/frame/nis/src/tests.rs index b5feb4df16aa..7350da97dc60 100644 --- a/frame/nis/src/tests.rs +++ b/frame/nis/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,19 +22,22 @@ use crate::{mock::*, Error}; use frame_support::{ assert_noop, assert_ok, traits::{ + fungible::{hold::Inspect as InspectHold, Inspect as FunInspect, Mutate as FunMutate}, nonfungible::{Inspect, Transfer}, - Currency, + tokens::{Fortitude::Force, Precision::Exact}, }, }; -use pallet_balances::{Error as BalancesError, Instance1}; use sp_arithmetic::Perquintill; -use sp_runtime::{Saturating, TokenError}; +use sp_runtime::{ + Saturating, + TokenError::{self, FundsUnavailable}, +}; -fn pot() -> u64 { +fn pot() -> Balance { Balances::free_balance(&Nis::account_id()) } -fn holdings() -> u64 { +fn holdings() -> Balance { Nis::issuance().holdings } @@ -42,8 +45,8 @@ fn signed(who: u64) -> RuntimeOrigin { RuntimeOrigin::signed(who) } -fn enlarge(amount: u64, max_bids: u32) { - let summary: SummaryRecord = Summary::::get(); +fn enlarge(amount: Balance, max_bids: u32) { + let summary: SummaryRecord = Summary::::get(); let increase_in_proportion_owed = Perquintill::from_rational(amount, Nis::issuance().effective); let target = summary.proportion_owed.saturating_add(increase_in_proportion_owed); Nis::process_queues(target, u32::max_value(), max_bids, &mut WeightCounter::unlimited()); @@ -75,10 +78,7 @@ fn place_bid_works() { new_test_ext().execute_with(|| { run_to_block(1); assert_noop!(Nis::place_bid(signed(1), 1, 2), Error::::AmountTooSmall); - assert_noop!( - Nis::place_bid(signed(1), 101, 2), - BalancesError::::InsufficientBalance - ); + assert_noop!(Nis::place_bid(signed(1), 101, 2), FundsUnavailable); assert_noop!(Nis::place_bid(signed(1), 10, 4), Error::::DurationTooBig); assert_ok!(Nis::place_bid(signed(1), 10, 2)); assert_eq!(Balances::reserved_balance(1), 10); @@ -451,16 +451,16 @@ fn communify_works() { assert_noop!(Nis::thaw_communal(signed(1), 1), Error::::UnknownReceipt); // Transfer some of the fungibles away. - assert_ok!(NisBalances::transfer(signed(1), 2, 100_000)); + assert_ok!(NisBalances::transfer_allow_death(signed(1), 2, 100_000)); assert_eq!(NisBalances::free_balance(&1), 2_000_000); assert_eq!(NisBalances::free_balance(&2), 100_000); // Communal thawing with the correct index is not possible now. - assert_noop!(Nis::thaw_communal(signed(1), 0), TokenError::NoFunds); - assert_noop!(Nis::thaw_communal(signed(2), 0), TokenError::NoFunds); + assert_noop!(Nis::thaw_communal(signed(1), 0), TokenError::FundsUnavailable); + assert_noop!(Nis::thaw_communal(signed(2), 0), TokenError::FundsUnavailable); // Transfer the rest to 2... - assert_ok!(NisBalances::transfer(signed(1), 2, 2_000_000)); + assert_ok!(NisBalances::transfer_allow_death(signed(1), 2, 2_000_000)); assert_eq!(NisBalances::free_balance(&1), 0); assert_eq!(NisBalances::free_balance(&2), 2_100_000); @@ -487,8 +487,8 @@ fn privatize_works() { assert_ok!(Nis::communify(signed(1), 0)); // Transfer the fungibles to #2 - assert_ok!(NisBalances::transfer(signed(1), 2, 2_100_000)); - assert_noop!(Nis::privatize(signed(1), 0), TokenError::NoFunds); + assert_ok!(NisBalances::transfer_allow_death(signed(1), 2, 2_100_000)); + assert_noop!(Nis::privatize(signed(1), 0), TokenError::FundsUnavailable); // Privatize assert_ok!(Nis::privatize(signed(2), 0)); @@ -513,16 +513,16 @@ fn privatize_and_thaw_with_another_receipt_works() { assert_ok!(Nis::communify(signed(2), 1)); // Transfer half of fungibles to #3 from each of #1 and #2, and the other half from #2 to #4 - assert_ok!(NisBalances::transfer(signed(1), 3, 1_050_000)); - assert_ok!(NisBalances::transfer(signed(2), 3, 1_050_000)); - assert_ok!(NisBalances::transfer(signed(2), 4, 1_050_000)); + assert_ok!(NisBalances::transfer_allow_death(signed(1), 3, 1_050_000)); + assert_ok!(NisBalances::transfer_allow_death(signed(2), 3, 1_050_000)); + assert_ok!(NisBalances::transfer_allow_death(signed(2), 4, 1_050_000)); // #3 privatizes, partially thaws, then re-communifies with #0, then transfers the fungibles // to #2 assert_ok!(Nis::privatize(signed(3), 0)); assert_ok!(Nis::thaw_private(signed(3), 0, Some(Perquintill::from_percent(5)))); assert_ok!(Nis::communify(signed(3), 0)); - assert_ok!(NisBalances::transfer(signed(3), 1, 1_050_000)); + assert_ok!(NisBalances::transfer_allow_death(signed(3), 1, 1_050_000)); // #1 now has enough to thaw using receipt 1 assert_ok!(Nis::thaw_communal(signed(1), 1)); @@ -536,17 +536,21 @@ fn privatize_and_thaw_with_another_receipt_works() { fn communal_thaw_when_issuance_higher_works() { new_test_ext().execute_with(|| { run_to_block(1); + assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 100, 1)); enlarge(100, 1); + assert_eq!(Balances::total_balance(&1), 101); assert_ok!(Nis::communify(signed(1), 0)); + assert_eq!(Balances::total_balance_on_hold(&1), 0); + assert_eq!(Balances::total_balance(&1), 1); - assert_eq!(NisBalances::free_balance(1), 5_250_000); // (25% of 21m) + assert_eq!(NisBalances::free_balance(1), 5_250_000); // (12.5% of 21m) // Everybody else's balances goes up by 50% - Balances::make_free_balance_be(&2, 150); - Balances::make_free_balance_be(&3, 150); - Balances::make_free_balance_be(&4, 150); + assert_ok!(Balances::mint_into(&2, 50)); + assert_ok!(Balances::mint_into(&3, 50)); + assert_ok!(Balances::mint_into(&4, 50)); run_to_block(4); @@ -556,16 +560,20 @@ fn communal_thaw_when_issuance_higher_works() { assert_ok!(Nis::fund_deficit(signed(1))); // Transfer counterparts away... - assert_ok!(NisBalances::transfer(signed(1), 2, 250_000)); + assert_ok!(NisBalances::transfer_allow_death(signed(1), 2, 125_000)); // ...and it's not thawable. - assert_noop!(Nis::thaw_communal(signed(1), 0), TokenError::NoFunds); + assert_noop!(Nis::thaw_communal(signed(1), 0), TokenError::FundsUnavailable); // Transfer counterparts back... - assert_ok!(NisBalances::transfer(signed(2), 1, 250_000)); + assert_ok!(NisBalances::transfer_allow_death(signed(2), 1, 125_000)); // ...and it is. assert_ok!(Nis::thaw_communal(signed(1), 0)); + assert_eq!(Balances::total_balance(&1), 151); + assert_ok!(Balances::transfer_allow_death(signed(1), 2, 1)); + assert_eq!(Balances::total_balance(&1), 150); assert_eq!(Balances::free_balance(1), 150); + assert_eq!(Balances::total_balance_on_hold(&1), 0); assert_eq!(Balances::reserved_balance(1), 0); }); } @@ -574,13 +582,14 @@ fn communal_thaw_when_issuance_higher_works() { fn private_thaw_when_issuance_higher_works() { new_test_ext().execute_with(|| { run_to_block(1); + assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 100, 1)); enlarge(100, 1); // Everybody else's balances goes up by 50% - Balances::make_free_balance_be(&2, 150); - Balances::make_free_balance_be(&3, 150); - Balances::make_free_balance_be(&4, 150); + assert_ok!(Balances::mint_into(&2, 50)); + assert_ok!(Balances::mint_into(&3, 50)); + assert_ok!(Balances::mint_into(&4, 50)); run_to_block(4); @@ -591,6 +600,7 @@ fn private_thaw_when_issuance_higher_works() { assert_ok!(Nis::thaw_private(signed(1), 0, None)); + assert_ok!(Balances::transfer_allow_death(signed(1), 2, 1)); assert_eq!(Balances::free_balance(1), 150); assert_eq!(Balances::reserved_balance(1), 0); }); @@ -601,15 +611,16 @@ fn thaw_with_ignored_issuance_works() { new_test_ext().execute_with(|| { run_to_block(1); // Give account zero some balance. - Balances::make_free_balance_be(&0, 200); + assert_ok!(Balances::mint_into(&0, 200)); + assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 100, 1)); enlarge(100, 1); // Account zero transfers 50 into everyone else's accounts. - assert_ok!(Balances::transfer(signed(0), 2, 50)); - assert_ok!(Balances::transfer(signed(0), 3, 50)); - assert_ok!(Balances::transfer(signed(0), 4, 50)); + assert_ok!(Balances::transfer_allow_death(signed(0), 2, 50)); + assert_ok!(Balances::transfer_allow_death(signed(0), 3, 50)); + assert_ok!(Balances::transfer_allow_death(signed(0), 4, 50)); run_to_block(4); // Unfunded initially... @@ -620,6 +631,7 @@ fn thaw_with_ignored_issuance_works() { assert_ok!(Nis::thaw_private(signed(1), 0, None)); // Account zero changes have been ignored. + assert_ok!(Balances::transfer_allow_death(signed(1), 2, 1)); assert_eq!(Balances::free_balance(1), 150); assert_eq!(Balances::reserved_balance(1), 0); }); @@ -629,17 +641,19 @@ fn thaw_with_ignored_issuance_works() { fn thaw_when_issuance_lower_works() { new_test_ext().execute_with(|| { run_to_block(1); + assert_ok!(Balances::transfer_allow_death(signed(2), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 100, 1)); enlarge(100, 1); // Everybody else's balances goes down by 25% - Balances::make_free_balance_be(&2, 75); - Balances::make_free_balance_be(&3, 75); - Balances::make_free_balance_be(&4, 75); + assert_ok!(Balances::burn_from(&2, 25, Exact, Force)); + assert_ok!(Balances::burn_from(&3, 25, Exact, Force)); + assert_ok!(Balances::burn_from(&4, 25, Exact, Force)); run_to_block(4); assert_ok!(Nis::thaw_private(signed(1), 0, None)); + assert_ok!(Balances::transfer_allow_death(signed(1), 2, 1)); assert_eq!(Balances::free_balance(1), 75); assert_eq!(Balances::reserved_balance(1), 0); }); @@ -649,15 +663,16 @@ fn thaw_when_issuance_lower_works() { fn multiple_thaws_works() { new_test_ext().execute_with(|| { run_to_block(1); + assert_ok!(Balances::transfer_allow_death(signed(3), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(1), 60, 1)); assert_ok!(Nis::place_bid(signed(2), 50, 1)); enlarge(200, 3); // Double everyone's free balances. - Balances::make_free_balance_be(&2, 100); - Balances::make_free_balance_be(&3, 200); - Balances::make_free_balance_be(&4, 200); + assert_ok!(Balances::mint_into(&2, 50)); + assert_ok!(Balances::mint_into(&3, 100)); + assert_ok!(Balances::mint_into(&4, 100)); assert_ok!(Nis::fund_deficit(signed(1))); run_to_block(4); @@ -667,8 +682,11 @@ fn multiple_thaws_works() { run_to_block(5); assert_ok!(Nis::thaw_private(signed(2), 2, None)); + assert_ok!(Balances::transfer_allow_death(signed(1), 3, 1)); assert_eq!(Balances::free_balance(1), 200); assert_eq!(Balances::free_balance(2), 200); + assert_eq!(Balances::total_balance(&1), 200); + assert_eq!(Balances::total_balance(&2), 200); }); } @@ -676,15 +694,16 @@ fn multiple_thaws_works() { fn multiple_thaws_works_in_alternative_thaw_order() { new_test_ext().execute_with(|| { run_to_block(1); + assert_ok!(Balances::transfer_allow_death(signed(3), 1, 1)); assert_ok!(Nis::place_bid(signed(1), 40, 1)); assert_ok!(Nis::place_bid(signed(1), 60, 1)); assert_ok!(Nis::place_bid(signed(2), 50, 1)); enlarge(200, 3); // Double everyone's free balances. - Balances::make_free_balance_be(&2, 100); - Balances::make_free_balance_be(&3, 200); - Balances::make_free_balance_be(&4, 200); + assert_ok!(Balances::mint_into(&2, 50)); + assert_ok!(Balances::mint_into(&3, 100)); + assert_ok!(Balances::mint_into(&4, 100)); assert_ok!(Nis::fund_deficit(signed(1))); run_to_block(4); @@ -695,8 +714,11 @@ fn multiple_thaws_works_in_alternative_thaw_order() { run_to_block(5); assert_ok!(Nis::thaw_private(signed(1), 1, None)); + assert_ok!(Balances::transfer_allow_death(signed(1), 3, 1)); assert_eq!(Balances::free_balance(1), 200); assert_eq!(Balances::free_balance(2), 200); + assert_eq!(Balances::total_balance(&1), 200); + assert_eq!(Balances::total_balance(&2), 200); }); } diff --git a/frame/nis/src/weights.rs b/frame/nis/src/weights.rs index 769ff795514c..cba2f0049055 100644 --- a/frame/nis/src/weights.rs +++ b/frame/nis/src/weights.rs @@ -1,38 +1,54 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. //! Autogenerated weights for pallet_nis //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-12-15, STEPS: `5`, REPEAT: 2, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `Workhorse.local`, CPU: `` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ../../../target/release/substrate +// ./target/production/substrate // benchmark // pallet -// --chain -// dev -// --steps -// 5 -// --repeat -// 2 -// --pallet -// pallet_nis -// --extrinsic -// * +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_nis +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* // --execution=wasm // --wasm-execution=compiled -// --output -// ../../../frame/nis/src/weights.rs -// --template -// ../../../.maintain/frame-weight-template.hbs +// --heap-pages=4096 +// --output=./frame/nis/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_nis. pub trait WeightInfo { @@ -40,10 +56,10 @@ pub trait WeightInfo { fn place_bid_max() -> Weight; fn retract_bid(l: u32, ) -> Weight; fn fund_deficit() -> Weight; + fn communify() -> Weight; + fn privatize() -> Weight; fn thaw_private() -> Weight; fn thaw_communal() -> Weight; - fn privatize() -> Weight; - fn communify() -> Weight; fn process_queues() -> Weight; fn process_queue() -> Weight; fn process_bid() -> Weight; @@ -52,224 +68,364 @@ pub trait WeightInfo { /// Weights for pallet_nis using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Nis Queues (r:1 w:1) - // Storage: Balances Reserves (r:1 w:1) - // Storage: Nis QueueTotals (r:1 w:1) + /// Storage: Nis Queues (r:1 w:1) + /// Proof: Nis Queues (max_values: None, max_size: Some(48022), added: 50497, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(85), added: 2560, mode: MaxEncodedLen) + /// Storage: Nis QueueTotals (r:1 w:1) + /// Proof: Nis QueueTotals (max_values: Some(1), max_size: Some(6002), added: 6497, mode: MaxEncodedLen) /// The range of component `l` is `[0, 999]`. fn place_bid(l: u32, ) -> Weight { - // Minimum execution time: 47_000 nanoseconds. - Weight::from_ref_time(53_822_030) - // Standard Error: 4_869 - .saturating_add(Weight::from_ref_time(47_431).saturating_mul(l.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `6176 + l * (48 ±0)` + // Estimated: `51487` + // Minimum execution time: 49_410_000 picoseconds. + Weight::from_parts(57_832_282, 51487) + // Standard Error: 288 + .saturating_add(Weight::from_parts(51_621, 0).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - // Storage: Nis Queues (r:1 w:1) - // Storage: Balances Reserves (r:1 w:1) - // Storage: Nis QueueTotals (r:1 w:1) + /// Storage: Nis Queues (r:1 w:1) + /// Proof: Nis Queues (max_values: None, max_size: Some(48022), added: 50497, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(85), added: 2560, mode: MaxEncodedLen) + /// Storage: Nis QueueTotals (r:1 w:1) + /// Proof: Nis QueueTotals (max_values: Some(1), max_size: Some(6002), added: 6497, mode: MaxEncodedLen) fn place_bid_max() -> Weight { - // Minimum execution time: 109_000 nanoseconds. - Weight::from_ref_time(109_000_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `54178` + // Estimated: `51487` + // Minimum execution time: 119_696_000 picoseconds. + Weight::from_parts(121_838_000, 51487) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - // Storage: Nis Queues (r:1 w:1) - // Storage: Nis QueueTotals (r:1 w:1) - // Storage: Balances Reserves (r:1 w:1) + /// Storage: Nis Queues (r:1 w:1) + /// Proof: Nis Queues (max_values: None, max_size: Some(48022), added: 50497, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(85), added: 2560, mode: MaxEncodedLen) + /// Storage: Nis QueueTotals (r:1 w:1) + /// Proof: Nis QueueTotals (max_values: Some(1), max_size: Some(6002), added: 6497, mode: MaxEncodedLen) /// The range of component `l` is `[1, 1000]`. fn retract_bid(l: u32, ) -> Weight { - // Minimum execution time: 50_000 nanoseconds. - Weight::from_ref_time(54_479_879) - // Standard Error: 4_891 - .saturating_add(Weight::from_ref_time(38_224).saturating_mul(l.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `6176 + l * (48 ±0)` + // Estimated: `51487` + // Minimum execution time: 50_843_000 picoseconds. + Weight::from_parts(54_237_365, 51487) + // Standard Error: 243 + .saturating_add(Weight::from_parts(67_732, 0).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - // Storage: Nis Summary (r:1 w:0) - // Storage: System Account (r:1 w:1) + /// Storage: Nis Summary (r:1 w:0) + /// Proof: Nis Summary (max_values: Some(1), max_size: Some(40), added: 535, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn fund_deficit() -> Weight { - // Minimum execution time: 57_000 nanoseconds. - Weight::from_ref_time(62_000_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `191` + // Estimated: `3593` + // Minimum execution time: 40_752_000 picoseconds. + Weight::from_parts(41_899_000, 3593) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Nis Receipts (r:1 w:1) - // Storage: Nis Summary (r:1 w:1) - // Storage: System Account (r:1 w:0) - // Storage: Balances Reserves (r:1 w:1) - fn thaw_private() -> Weight { - // Minimum execution time: 84_000 nanoseconds. - Weight::from_ref_time(85_000_000) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Nis Receipts (r:1 w:1) - // Storage: Nis Summary (r:1 w:1) - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn thaw_communal() -> Weight { - // Minimum execution time: 108_000 nanoseconds. - Weight::from_ref_time(115_000_000) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(5)) + /// Storage: Nis Receipts (r:1 w:1) + /// Proof: Nis Receipts (max_values: None, max_size: Some(81), added: 2556, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(85), added: 2560, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Nis Summary (r:1 w:1) + /// Proof: Nis Summary (max_values: Some(1), max_size: Some(40), added: 535, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + fn communify() -> Weight { + // Proof Size summary in bytes: + // Measured: `668` + // Estimated: `3675` + // Minimum execution time: 79_779_000 picoseconds. + Weight::from_parts(82_478_000, 3675) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) } - // Storage: Nis Receipts (r:1 w:1) - // Storage: Nis Summary (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:1 w:1) - // Storage: Balances Reserves (r:1 w:1) + /// Storage: Nis Receipts (r:1 w:1) + /// Proof: Nis Receipts (max_values: None, max_size: Some(81), added: 2556, mode: MaxEncodedLen) + /// Storage: Nis Summary (r:1 w:1) + /// Proof: Nis Summary (max_values: Some(1), max_size: Some(40), added: 535, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(85), added: 2560, mode: MaxEncodedLen) fn privatize() -> Weight { - // Minimum execution time: 107_000 nanoseconds. - Weight::from_ref_time(110_000_000) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(6)) + // Proof Size summary in bytes: + // Measured: `829` + // Estimated: `3675` + // Minimum execution time: 99_588_000 picoseconds. + Weight::from_parts(102_340_000, 3675) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) } - // Storage: Nis Receipts (r:1 w:1) - // Storage: Balances Reserves (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Nis Summary (r:1 w:1) - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:1 w:1) - fn communify() -> Weight { - // Minimum execution time: 89_000 nanoseconds. - Weight::from_ref_time(89_000_000) - .saturating_add(T::DbWeight::get().reads(6)) - .saturating_add(T::DbWeight::get().writes(6)) + /// Storage: Nis Receipts (r:1 w:1) + /// Proof: Nis Receipts (max_values: None, max_size: Some(81), added: 2556, mode: MaxEncodedLen) + /// Storage: Nis Summary (r:1 w:1) + /// Proof: Nis Summary (max_values: Some(1), max_size: Some(40), added: 535, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(85), added: 2560, mode: MaxEncodedLen) + fn thaw_private() -> Weight { + // Proof Size summary in bytes: + // Measured: `354` + // Estimated: `3593` + // Minimum execution time: 53_094_000 picoseconds. + Weight::from_parts(54_543_000, 3593) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Nis Receipts (r:1 w:1) + /// Proof: Nis Receipts (max_values: None, max_size: Some(81), added: 2556, mode: MaxEncodedLen) + /// Storage: Nis Summary (r:1 w:1) + /// Proof: Nis Summary (max_values: Some(1), max_size: Some(40), added: 535, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn thaw_communal() -> Weight { + // Proof Size summary in bytes: + // Measured: `773` + // Estimated: `3675` + // Minimum execution time: 107_248_000 picoseconds. + Weight::from_parts(109_923_000, 3675) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } - // Storage: Nis Summary (r:1 w:1) - // Storage: System Account (r:1 w:0) - // Storage: Nis QueueTotals (r:1 w:1) + /// Storage: Nis Summary (r:1 w:1) + /// Proof: Nis Summary (max_values: Some(1), max_size: Some(40), added: 535, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Nis QueueTotals (r:1 w:1) + /// Proof: Nis QueueTotals (max_values: Some(1), max_size: Some(6002), added: 6497, mode: MaxEncodedLen) fn process_queues() -> Weight { - // Minimum execution time: 34_000 nanoseconds. - Weight::from_ref_time(38_000_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `6624` + // Estimated: `7487` + // Minimum execution time: 27_169_000 picoseconds. + Weight::from_parts(29_201_000, 7487) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Nis Queues (r:1 w:1) + /// Storage: Nis Queues (r:1 w:1) + /// Proof: Nis Queues (max_values: None, max_size: Some(48022), added: 50497, mode: MaxEncodedLen) fn process_queue() -> Weight { - // Minimum execution time: 6_000 nanoseconds. - Weight::from_ref_time(7_000_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `51487` + // Minimum execution time: 4_540_000 picoseconds. + Weight::from_parts(4_699_000, 51487) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Nis Receipts (r:0 w:1) + /// Storage: Nis Receipts (r:0 w:1) + /// Proof: Nis Receipts (max_values: None, max_size: Some(81), added: 2556, mode: MaxEncodedLen) fn process_bid() -> Weight { - // Minimum execution time: 14_000 nanoseconds. - Weight::from_ref_time(15_000_000) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_085_000 picoseconds. + Weight::from_parts(7_336_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Nis Queues (r:1 w:1) - // Storage: Balances Reserves (r:1 w:1) - // Storage: Nis QueueTotals (r:1 w:1) + /// Storage: Nis Queues (r:1 w:1) + /// Proof: Nis Queues (max_values: None, max_size: Some(48022), added: 50497, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(85), added: 2560, mode: MaxEncodedLen) + /// Storage: Nis QueueTotals (r:1 w:1) + /// Proof: Nis QueueTotals (max_values: Some(1), max_size: Some(6002), added: 6497, mode: MaxEncodedLen) /// The range of component `l` is `[0, 999]`. fn place_bid(l: u32, ) -> Weight { - // Minimum execution time: 47_000 nanoseconds. - Weight::from_ref_time(53_822_030) - // Standard Error: 4_869 - .saturating_add(Weight::from_ref_time(47_431).saturating_mul(l.into())) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `6176 + l * (48 ±0)` + // Estimated: `51487` + // Minimum execution time: 49_410_000 picoseconds. + Weight::from_parts(57_832_282, 51487) + // Standard Error: 288 + .saturating_add(Weight::from_parts(51_621, 0).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } - // Storage: Nis Queues (r:1 w:1) - // Storage: Balances Reserves (r:1 w:1) - // Storage: Nis QueueTotals (r:1 w:1) + /// Storage: Nis Queues (r:1 w:1) + /// Proof: Nis Queues (max_values: None, max_size: Some(48022), added: 50497, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(85), added: 2560, mode: MaxEncodedLen) + /// Storage: Nis QueueTotals (r:1 w:1) + /// Proof: Nis QueueTotals (max_values: Some(1), max_size: Some(6002), added: 6497, mode: MaxEncodedLen) fn place_bid_max() -> Weight { - // Minimum execution time: 109_000 nanoseconds. - Weight::from_ref_time(109_000_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `54178` + // Estimated: `51487` + // Minimum execution time: 119_696_000 picoseconds. + Weight::from_parts(121_838_000, 51487) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } - // Storage: Nis Queues (r:1 w:1) - // Storage: Nis QueueTotals (r:1 w:1) - // Storage: Balances Reserves (r:1 w:1) + /// Storage: Nis Queues (r:1 w:1) + /// Proof: Nis Queues (max_values: None, max_size: Some(48022), added: 50497, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(85), added: 2560, mode: MaxEncodedLen) + /// Storage: Nis QueueTotals (r:1 w:1) + /// Proof: Nis QueueTotals (max_values: Some(1), max_size: Some(6002), added: 6497, mode: MaxEncodedLen) /// The range of component `l` is `[1, 1000]`. fn retract_bid(l: u32, ) -> Weight { - // Minimum execution time: 50_000 nanoseconds. - Weight::from_ref_time(54_479_879) - // Standard Error: 4_891 - .saturating_add(Weight::from_ref_time(38_224).saturating_mul(l.into())) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `6176 + l * (48 ±0)` + // Estimated: `51487` + // Minimum execution time: 50_843_000 picoseconds. + Weight::from_parts(54_237_365, 51487) + // Standard Error: 243 + .saturating_add(Weight::from_parts(67_732, 0).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } - // Storage: Nis Summary (r:1 w:0) - // Storage: System Account (r:1 w:1) + /// Storage: Nis Summary (r:1 w:0) + /// Proof: Nis Summary (max_values: Some(1), max_size: Some(40), added: 535, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn fund_deficit() -> Weight { - // Minimum execution time: 57_000 nanoseconds. - Weight::from_ref_time(62_000_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `191` + // Estimated: `3593` + // Minimum execution time: 40_752_000 picoseconds. + Weight::from_parts(41_899_000, 3593) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Nis Receipts (r:1 w:1) - // Storage: Nis Summary (r:1 w:1) - // Storage: System Account (r:1 w:0) - // Storage: Balances Reserves (r:1 w:1) - fn thaw_private() -> Weight { - // Minimum execution time: 84_000 nanoseconds. - Weight::from_ref_time(85_000_000) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Nis Receipts (r:1 w:1) - // Storage: Nis Summary (r:1 w:1) - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:1 w:1) - // Storage: System Account (r:1 w:1) - fn thaw_communal() -> Weight { - // Minimum execution time: 108_000 nanoseconds. - Weight::from_ref_time(115_000_000) - .saturating_add(RocksDbWeight::get().reads(5)) - .saturating_add(RocksDbWeight::get().writes(5)) + /// Storage: Nis Receipts (r:1 w:1) + /// Proof: Nis Receipts (max_values: None, max_size: Some(81), added: 2556, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(85), added: 2560, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Nis Summary (r:1 w:1) + /// Proof: Nis Summary (max_values: Some(1), max_size: Some(40), added: 535, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + fn communify() -> Weight { + // Proof Size summary in bytes: + // Measured: `668` + // Estimated: `3675` + // Minimum execution time: 79_779_000 picoseconds. + Weight::from_parts(82_478_000, 3675) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } - // Storage: Nis Receipts (r:1 w:1) - // Storage: Nis Summary (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:1 w:1) - // Storage: Balances Reserves (r:1 w:1) + /// Storage: Nis Receipts (r:1 w:1) + /// Proof: Nis Receipts (max_values: None, max_size: Some(81), added: 2556, mode: MaxEncodedLen) + /// Storage: Nis Summary (r:1 w:1) + /// Proof: Nis Summary (max_values: Some(1), max_size: Some(40), added: 535, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(85), added: 2560, mode: MaxEncodedLen) fn privatize() -> Weight { - // Minimum execution time: 107_000 nanoseconds. - Weight::from_ref_time(110_000_000) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(6)) + // Proof Size summary in bytes: + // Measured: `829` + // Estimated: `3675` + // Minimum execution time: 99_588_000 picoseconds. + Weight::from_parts(102_340_000, 3675) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } - // Storage: Nis Receipts (r:1 w:1) - // Storage: Balances Reserves (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Nis Summary (r:1 w:1) - // Storage: Assets Asset (r:1 w:1) - // Storage: Assets Account (r:1 w:1) - fn communify() -> Weight { - // Minimum execution time: 89_000 nanoseconds. - Weight::from_ref_time(89_000_000) - .saturating_add(RocksDbWeight::get().reads(6)) - .saturating_add(RocksDbWeight::get().writes(6)) + /// Storage: Nis Receipts (r:1 w:1) + /// Proof: Nis Receipts (max_values: None, max_size: Some(81), added: 2556, mode: MaxEncodedLen) + /// Storage: Nis Summary (r:1 w:1) + /// Proof: Nis Summary (max_values: Some(1), max_size: Some(40), added: 535, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Balances Holds (r:1 w:1) + /// Proof: Balances Holds (max_values: None, max_size: Some(85), added: 2560, mode: MaxEncodedLen) + fn thaw_private() -> Weight { + // Proof Size summary in bytes: + // Measured: `354` + // Estimated: `3593` + // Minimum execution time: 53_094_000 picoseconds. + Weight::from_parts(54_543_000, 3593) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Nis Receipts (r:1 w:1) + /// Proof: Nis Receipts (max_values: None, max_size: Some(81), added: 2556, mode: MaxEncodedLen) + /// Storage: Nis Summary (r:1 w:1) + /// Proof: Nis Summary (max_values: Some(1), max_size: Some(40), added: 535, mode: MaxEncodedLen) + /// Storage: Assets Asset (r:1 w:1) + /// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen) + /// Storage: Assets Account (r:1 w:1) + /// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn thaw_communal() -> Weight { + // Proof Size summary in bytes: + // Measured: `773` + // Estimated: `3675` + // Minimum execution time: 107_248_000 picoseconds. + Weight::from_parts(109_923_000, 3675) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } - // Storage: Nis Summary (r:1 w:1) - // Storage: System Account (r:1 w:0) - // Storage: Nis QueueTotals (r:1 w:1) + /// Storage: Nis Summary (r:1 w:1) + /// Proof: Nis Summary (max_values: Some(1), max_size: Some(40), added: 535, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Nis QueueTotals (r:1 w:1) + /// Proof: Nis QueueTotals (max_values: Some(1), max_size: Some(6002), added: 6497, mode: MaxEncodedLen) fn process_queues() -> Weight { - // Minimum execution time: 34_000 nanoseconds. - Weight::from_ref_time(38_000_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `6624` + // Estimated: `7487` + // Minimum execution time: 27_169_000 picoseconds. + Weight::from_parts(29_201_000, 7487) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Nis Queues (r:1 w:1) + /// Storage: Nis Queues (r:1 w:1) + /// Proof: Nis Queues (max_values: None, max_size: Some(48022), added: 50497, mode: MaxEncodedLen) fn process_queue() -> Weight { - // Minimum execution time: 6_000 nanoseconds. - Weight::from_ref_time(7_000_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `51487` + // Minimum execution time: 4_540_000 picoseconds. + Weight::from_parts(4_699_000, 51487) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Nis Receipts (r:0 w:1) + /// Storage: Nis Receipts (r:0 w:1) + /// Proof: Nis Receipts (max_values: None, max_size: Some(81), added: 2556, mode: MaxEncodedLen) fn process_bid() -> Weight { - // Minimum execution time: 14_000 nanoseconds. - Weight::from_ref_time(15_000_000) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_085_000 picoseconds. + Weight::from_parts(7_336_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } diff --git a/frame/node-authorization/Cargo.toml b/frame/node-authorization/Cargo.toml index c60ce1d12ce0..77dcbf666d86 100644 --- a/frame/node-authorization/Cargo.toml +++ b/frame/node-authorization/Cargo.toml @@ -12,15 +12,15 @@ description = "FRAME pallet for node authorization" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [features] default = ["std"] diff --git a/frame/node-authorization/src/lib.rs b/frame/node-authorization/src/lib.rs index 543ba24500eb..8a823d29f235 100644 --- a/frame/node-authorization/src/lib.rs +++ b/frame/node-authorization/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -59,7 +59,6 @@ pub mod pallet { use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::without_storage_info] pub struct Pallet(_); @@ -110,19 +109,13 @@ pub mod pallet { StorageMap<_, Blake2_128Concat, PeerId, BTreeSet, ValueQuery>; #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { pub nodes: Vec<(PeerId, T::AccountId)>, } - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - Self { nodes: Vec::new() } - } - } - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { Pallet::::initialize_nodes(&self.nodes); } @@ -176,7 +169,7 @@ pub mod pallet { impl Hooks> for Pallet { /// Set reserved node every block. It may not be enabled depends on the offchain /// worker settings when starting the node. - fn offchain_worker(now: T::BlockNumber) { + fn offchain_worker(now: frame_system::pallet_prelude::BlockNumberFor) { let network_state = sp_io::offchain::network_state(); match network_state { Err(_) => log::error!( diff --git a/frame/node-authorization/src/mock.rs b/frame/node-authorization/src/mock.rs index fcf7ff018933..84e3336b3bd6 100644 --- a/frame/node-authorization/src/mock.rs +++ b/frame/node-authorization/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,25 +22,21 @@ use crate as pallet_node_authorization; use frame_support::{ ord_parameter_types, - traits::{ConstU32, ConstU64, GenesisBuild}, + traits::{ConstU32, ConstU64}, }; use frame_system::EnsureSignedBy; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, NodeAuthorization: pallet_node_authorization::{ Pallet, Call, Storage, Config, Event, }, @@ -53,14 +49,13 @@ impl frame_system::Config for Test { type BlockWeights = (); type BlockLength = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -97,7 +92,7 @@ pub fn test_node(id: u8) -> PeerId { } pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_node_authorization::GenesisConfig:: { nodes: vec![(test_node(10), 10), (test_node(20), 20), (test_node(30), 30)], } diff --git a/frame/node-authorization/src/tests.rs b/frame/node-authorization/src/tests.rs index d9db09db4f46..4704b5adf269 100644 --- a/frame/node-authorization/src/tests.rs +++ b/frame/node-authorization/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/node-authorization/src/weights.rs b/frame/node-authorization/src/weights.rs index 63728c21b65c..a4529c845c7c 100644 --- a/frame/node-authorization/src/weights.rs +++ b/frame/node-authorization/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,13 +37,13 @@ pub trait WeightInfo { } impl WeightInfo for () { - fn add_well_known_node() -> Weight { Weight::from_ref_time(50_000_000) } - fn remove_well_known_node() -> Weight { Weight::from_ref_time(50_000_000) } - fn swap_well_known_node() -> Weight { Weight::from_ref_time(50_000_000) } - fn reset_well_known_nodes() -> Weight { Weight::from_ref_time(50_000_000) } - fn claim_node() -> Weight { Weight::from_ref_time(50_000_000) } - fn remove_claim() -> Weight { Weight::from_ref_time(50_000_000) } - fn transfer_node() -> Weight { Weight::from_ref_time(50_000_000) } - fn add_connections() -> Weight { Weight::from_ref_time(50_000_000) } - fn remove_connections() -> Weight { Weight::from_ref_time(50_000_000) } + fn add_well_known_node() -> Weight { Weight::from_parts(50_000_000, 0) } + fn remove_well_known_node() -> Weight { Weight::from_parts(50_000_000, 0) } + fn swap_well_known_node() -> Weight { Weight::from_parts(50_000_000, 0) } + fn reset_well_known_nodes() -> Weight { Weight::from_parts(50_000_000, 0) } + fn claim_node() -> Weight { Weight::from_parts(50_000_000, 0) } + fn remove_claim() -> Weight { Weight::from_parts(50_000_000, 0) } + fn transfer_node() -> Weight { Weight::from_parts(50_000_000, 0) } + fn add_connections() -> Weight { Weight::from_parts(50_000_000, 0) } + fn remove_connections() -> Weight { Weight::from_parts(50_000_000, 0) } } diff --git a/frame/nomination-pools/Cargo.toml b/frame/nomination-pools/Cargo.toml index d51bcec51f47..524c70fbc25a 100644 --- a/frame/nomination-pools/Cargo.toml +++ b/frame/nomination-pools/Cargo.toml @@ -13,26 +13,26 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # parity -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } # FRAME frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } log = { version = "0.4.0", default-features = false } # Optional: use for testing and/or fuzzing pallet-balances = { version = "4.0.0-dev", path = "../balances", optional = true } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing", optional = true } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing", optional = true } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } [features] default = ["std"] diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml index b96024af7d3c..f3f1ec2d317c 100644 --- a/frame/nomination-pools/benchmarking/Cargo.toml +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -14,8 +14,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # parity -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } # FRAME frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } @@ -27,17 +27,17 @@ pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../. pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../" } # Substrate Primitives -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime-interface" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-runtime-interface = { version = "17.0.0", default-features = false, path = "../../../primitives/runtime-interface" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } -sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../balances" } pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../../primitives/io" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../../primitives/io" } [features] default = ["std"] diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 9b063539152b..30bef6221c96 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,16 +23,22 @@ #[cfg(test)] mod mock; -use frame_benchmarking::{account, frame_support::traits::Currency, vec, whitelist_account, Vec}; +use frame_benchmarking::v1::{ + account, frame_support::traits::Currency, vec, whitelist_account, Vec, +}; use frame_election_provider_support::SortedListProvider; use frame_support::{assert_ok, ensure, traits::Get}; use frame_system::RawOrigin as RuntimeOrigin; use pallet_nomination_pools::{ - BalanceOf, BondExtra, BondedPoolInner, BondedPools, ConfigOp, MaxPoolMembers, + BalanceOf, BondExtra, BondedPoolInner, BondedPools, ClaimPermission, ClaimPermissions, + Commission, CommissionChangeRate, ConfigOp, GlobalMaxCommission, MaxPoolMembers, MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond, Pallet as Pools, PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage, }; -use sp_runtime::traits::{Bounded, StaticLookup, Zero}; +use sp_runtime::{ + traits::{Bounded, StaticLookup, Zero}, + Perbill, +}; use sp_staking::{EraIndex, StakingInterface}; // `frame_benchmarking::benchmarks!` macro needs this use pallet_nomination_pools::Call; @@ -67,6 +73,7 @@ fn create_funded_user_with_balance( fn create_pool_account( n: u32, balance: BalanceOf, + commission: Option, ) -> (T::AccountId, T::AccountId) { let ed = CurrencyOf::::minimum_balance(); let pool_creator: T::AccountId = @@ -82,6 +89,16 @@ fn create_pool_account( ) .unwrap(); + if let Some(c) = commission { + let pool_id = pallet_nomination_pools::LastPoolId::::get(); + Pools::::set_commission( + RuntimeOrigin::Signed(pool_creator.clone()).into(), + pool_id, + Some((c, pool_creator.clone())), + ) + .expect("pool just created, commission can be set by root; qed"); + } + let pool_account = pallet_nomination_pools::BondedPools::::iter() .find(|(_, bonded_pool)| bonded_pool.roles.depositor == pool_creator) .map(|(pool_id, _)| Pools::::create_bonded_account(pool_id)) @@ -132,14 +149,18 @@ impl ListScenario { sp_std::mem::forget(i); // Create accounts with the origin weight - let (pool_creator1, pool_origin1) = create_pool_account::(USER_SEED + 1, origin_weight); + let (pool_creator1, pool_origin1) = + create_pool_account::(USER_SEED + 1, origin_weight, Some(Perbill::from_percent(50))); + T::Staking::nominate( &pool_origin1, // NOTE: these don't really need to be validators. vec![account("random_validator", 0, USER_SEED)], )?; - let (_, pool_origin2) = create_pool_account::(USER_SEED + 2, origin_weight); + let (_, pool_origin2) = + create_pool_account::(USER_SEED + 2, origin_weight, Some(Perbill::from_percent(50))); + T::Staking::nominate( &pool_origin2, vec![account("random_validator", 0, USER_SEED)].clone(), @@ -155,7 +176,9 @@ impl ListScenario { dest_weight_as_vote.try_into().map_err(|_| "could not convert u64 to Balance")?; // Create an account with the worst case destination weight - let (_, pool_dest1) = create_pool_account::(USER_SEED + 3, dest_weight); + let (_, pool_dest1) = + create_pool_account::(USER_SEED + 3, dest_weight, Some(Perbill::from_percent(50))); + T::Staking::nominate(&pool_dest1, vec![account("random_validator", 0, USER_SEED)])?; let weight_of = pallet_staking::Pallet::::weight_of_fn(); @@ -250,33 +273,45 @@ frame_benchmarking::benchmarks! { ); } - bond_extra_reward { + bond_extra_other { + let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0); + let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); let scenario = ListScenario::::new(origin_weight, true)?; let extra = (scenario.dest_weight - origin_weight).max(CurrencyOf::::minimum_balance()); + // set claim preferences to `PermissionlessAll` to any account to bond extra on member's behalf. + let _ = Pools::::set_claim_permission(RuntimeOrigin::Signed(scenario.creator1.clone()).into(), ClaimPermission::PermissionlessAll); + // transfer exactly `extra` to the depositor of the src pool (1), let reward_account1 = Pools::::create_reward_account(1); assert!(extra >= CurrencyOf::::minimum_balance()); CurrencyOf::::deposit_creating(&reward_account1, extra); - }: bond_extra(RuntimeOrigin::Signed(scenario.creator1.clone()), BondExtra::Rewards) + }: _(RuntimeOrigin::Signed(claimer), T::Lookup::unlookup(scenario.creator1.clone()), BondExtra::Rewards) verify { + // commission of 50% deducted here. assert!( T::Staking::active_stake(&scenario.origin1).unwrap() >= - scenario.dest_weight + scenario.dest_weight / 2u32.into() ); } claim_payout { + let claimer: T::AccountId = account("claimer", USER_SEED + 4, 0); + let commission = Perbill::from_percent(50); let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); let ed = CurrencyOf::::minimum_balance(); - let (depositor, pool_account) = create_pool_account::(0, origin_weight); + let (depositor, pool_account) = create_pool_account::(0, origin_weight, Some(commission)); let reward_account = Pools::::create_reward_account(1); // Send funds to the reward account of the pool CurrencyOf::::make_free_balance_be(&reward_account, ed + origin_weight); + // set claim preferences to `PermissionlessAll` so any account can claim rewards on member's + // behalf. + let _ = Pools::::set_claim_permission(RuntimeOrigin::Signed(depositor.clone()).into(), ClaimPermission::PermissionlessAll); + // Sanity check assert_eq!( CurrencyOf::::free_balance(&depositor), @@ -284,18 +319,19 @@ frame_benchmarking::benchmarks! { ); whitelist_account!(depositor); - }:_(RuntimeOrigin::Signed(depositor.clone())) + }:claim_payout_other(RuntimeOrigin::Signed(claimer), depositor.clone()) verify { assert_eq!( CurrencyOf::::free_balance(&depositor), - origin_weight * 2u32.into() + origin_weight + commission * origin_weight ); assert_eq!( CurrencyOf::::free_balance(&reward_account), - ed + Zero::zero() + ed + commission * origin_weight ); } + unbond { // The weight the nominator will start at. The value used here is expected to be // significantly higher than the first position in a list (e.g. the first bag threshold). @@ -331,7 +367,7 @@ frame_benchmarking::benchmarks! { let s in 0 .. MAX_SPANS; let min_create_bond = Pools::::depositor_min_bond(); - let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); // Add a new member let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); @@ -373,7 +409,7 @@ frame_benchmarking::benchmarks! { let s in 0 .. MAX_SPANS; let min_create_bond = Pools::::depositor_min_bond(); - let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); // Add a new member let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); @@ -419,7 +455,7 @@ frame_benchmarking::benchmarks! { let s in 0 .. MAX_SPANS; let min_create_bond = Pools::::depositor_min_bond(); - let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); let depositor_lookup = T::Lookup::unlookup(depositor.clone()); // We set the pool to the destroying state so the depositor can leave @@ -509,15 +545,16 @@ frame_benchmarking::benchmarks! { assert_eq!( new_pool, BondedPoolInner { - points: min_create_bond, - state: PoolState::Open, + commission: Commission::default(), member_counter: 1, + points: min_create_bond, roles: PoolRoles { depositor: depositor.clone(), root: Some(depositor.clone()), nominator: Some(depositor.clone()), - state_toggler: Some(depositor.clone()), + bouncer: Some(depositor.clone()), }, + state: PoolState::Open, } ); assert_eq!( @@ -531,7 +568,7 @@ frame_benchmarking::benchmarks! { // Create a pool let min_create_bond = Pools::::depositor_min_bond() * 2u32.into(); - let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); // Create some accounts to nominate. For the sake of benchmarking they don't need to be // actual validators @@ -548,15 +585,16 @@ frame_benchmarking::benchmarks! { assert_eq!( new_pool, BondedPoolInner { - points: min_create_bond, - state: PoolState::Open, + commission: Commission::default(), member_counter: 1, + points: min_create_bond, roles: PoolRoles { depositor: depositor.clone(), root: Some(depositor.clone()), nominator: Some(depositor.clone()), - state_toggler: Some(depositor.clone()), - } + bouncer: Some(depositor.clone()), + }, + state: PoolState::Open, } ); assert_eq!( @@ -568,10 +606,10 @@ frame_benchmarking::benchmarks! { set_state { // Create a pool let min_create_bond = Pools::::depositor_min_bond(); - let (depositor, pool_account) = create_pool_account::(0, min_create_bond); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); BondedPools::::mutate(&1, |maybe_pool| { // Force the pool into an invalid state - maybe_pool.as_mut().map(|mut pool| pool.points = min_create_bond * 10u32.into()); + maybe_pool.as_mut().map(|pool| pool.points = min_create_bond * 10u32.into()); }); let caller = account("caller", 0, USER_SEED); @@ -585,7 +623,7 @@ frame_benchmarking::benchmarks! { let n in 1 .. ::MaxMetadataLen::get(); // Create a pool - let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into()); + let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); // Create metadata of the max possible size let metadata: Vec = (0..n).map(|_| 42).collect(); @@ -603,18 +641,20 @@ frame_benchmarking::benchmarks! { ConfigOp::Set(BalanceOf::::max_value()), ConfigOp::Set(u32::MAX), ConfigOp::Set(u32::MAX), - ConfigOp::Set(u32::MAX) + ConfigOp::Set(u32::MAX), + ConfigOp::Set(Perbill::max_value()) ) verify { assert_eq!(MinJoinBond::::get(), BalanceOf::::max_value()); assert_eq!(MinCreateBond::::get(), BalanceOf::::max_value()); assert_eq!(MaxPools::::get(), Some(u32::MAX)); assert_eq!(MaxPoolMembers::::get(), Some(u32::MAX)); assert_eq!(MaxPoolMembersPerPool::::get(), Some(u32::MAX)); + assert_eq!(GlobalMaxCommission::::get(), Some(Perbill::max_value())); } update_roles { let first_id = pallet_nomination_pools::LastPoolId::::get() + 1; - let (root, _) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into()); + let (root, _) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); let random: T::AccountId = account("but is anything really random in computers..?", 0, USER_SEED); }:_( RuntimeOrigin::Signed(root.clone()), @@ -628,7 +668,7 @@ frame_benchmarking::benchmarks! { pallet_nomination_pools::PoolRoles { depositor: root, nominator: Some(random.clone()), - state_toggler: Some(random.clone()), + bouncer: Some(random.clone()), root: Some(random), }, ) @@ -636,7 +676,7 @@ frame_benchmarking::benchmarks! { chill { // Create a pool - let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into()); + let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); // Nominate with the pool. let validators: Vec<_> = (0..T::MaxNominations::get()) @@ -644,12 +684,117 @@ frame_benchmarking::benchmarks! { .collect(); assert_ok!(T::Staking::nominate(&pool_account, validators)); - assert!(T::Staking::nominations(Pools::::create_bonded_account(1)).is_some()); + assert!(T::Staking::nominations(&Pools::::create_bonded_account(1)).is_some()); whitelist_account!(depositor); }:_(RuntimeOrigin::Signed(depositor.clone()), 1) verify { - assert!(T::Staking::nominations(Pools::::create_bonded_account(1)).is_none()); + assert!(T::Staking::nominations(&Pools::::create_bonded_account(1)).is_none()); + } + + set_commission { + // Create a pool - do not set a commission yet. + let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); + // set a max commission + Pools::::set_commission_max(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), Perbill::from_percent(50)).unwrap(); + // set a change rate + Pools::::set_commission_change_rate(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into(), CommissionChangeRate { + max_increase: Perbill::from_percent(20), + min_delay: 0u32.into(), + }).unwrap(); + + }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Some((Perbill::from_percent(20), depositor.clone()))) + verify { + assert_eq!(BondedPools::::get(1).unwrap().commission, Commission { + current: Some((Perbill::from_percent(20), depositor)), + max: Some(Perbill::from_percent(50)), + change_rate: Some(CommissionChangeRate { + max_increase: Perbill::from_percent(20), + min_delay: 0u32.into() + }), + throttle_from: Some(1u32.into()), + }); + } + + set_commission_max { + // Create a pool, setting a commission that will update when max commission is set. + let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), Some(Perbill::from_percent(50))); + }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), Perbill::from_percent(50)) + verify { + assert_eq!( + BondedPools::::get(1).unwrap().commission, Commission { + current: Some((Perbill::from_percent(50), depositor)), + max: Some(Perbill::from_percent(50)), + change_rate: None, + throttle_from: Some(0u32.into()), + }); + } + + set_commission_change_rate { + // Create a pool + let (depositor, pool_account) = create_pool_account::(0, Pools::::depositor_min_bond() * 2u32.into(), None); + }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into(), CommissionChangeRate { + max_increase: Perbill::from_percent(50), + min_delay: 1000u32.into(), + }) + verify { + assert_eq!( + BondedPools::::get(1).unwrap().commission, Commission { + current: None, + max: None, + change_rate: Some(CommissionChangeRate { + max_increase: Perbill::from_percent(50), + min_delay: 1000u32.into(), + }), + throttle_from: Some(1_u32.into()), + }); + } + + set_claim_permission { + // Create a pool + let min_create_bond = Pools::::depositor_min_bond(); + let (depositor, pool_account) = create_pool_account::(0, min_create_bond, None); + + // Join pool + let min_join_bond = MinJoinBond::::get().max(CurrencyOf::::minimum_balance()); + let joiner = create_funded_user_with_balance::("joiner", 0, min_join_bond * 4u32.into()); + let joiner_lookup = T::Lookup::unlookup(joiner.clone()); + Pools::::join(RuntimeOrigin::Signed(joiner.clone()).into(), min_join_bond, 1) + .unwrap(); + + // Sanity check join worked + assert_eq!( + T::Staking::active_stake(&pool_account).unwrap(), + min_create_bond + min_join_bond + ); + }:_(RuntimeOrigin::Signed(joiner.clone()), ClaimPermission::PermissionlessAll) + verify { + assert_eq!(ClaimPermissions::::get(joiner), ClaimPermission::PermissionlessAll); + } + + claim_commission { + let claimer: T::AccountId = account("claimer_member", USER_SEED + 4, 0); + let commission = Perbill::from_percent(50); + let origin_weight = Pools::::depositor_min_bond() * 2u32.into(); + let ed = CurrencyOf::::minimum_balance(); + let (depositor, pool_account) = create_pool_account::(0, origin_weight, Some(commission)); + let reward_account = Pools::::create_reward_account(1); + CurrencyOf::::make_free_balance_be(&reward_account, ed + origin_weight); + + // member claims a payout to make some commission available. + let _ = Pools::::claim_payout(RuntimeOrigin::Signed(claimer).into()); + + whitelist_account!(depositor); + }:_(RuntimeOrigin::Signed(depositor.clone()), 1u32.into()) + verify { + assert_eq!( + CurrencyOf::::free_balance(&depositor), + origin_weight + commission * origin_weight + ); + assert_eq!( + CurrencyOf::::free_balance(&reward_account), + ed + commission * origin_weight + ); } impl_benchmark_test_suite!( diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 06a66838594c..2298f611d7ff 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,11 +20,11 @@ use frame_election_provider_support::VoteWeight; use frame_support::{pallet_prelude::*, parameter_types, traits::ConstU64, PalletId}; use sp_runtime::{ traits::{Convert, IdentityLookup}, - FixedU128, + BuildStorage, FixedU128, Perbill, }; type AccountId = u128; -type AccountIndex = u32; +type Nonce = u32; type BlockNumber = u64; type Balance = u128; @@ -34,14 +34,13 @@ impl frame_system::Config for Runtime { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = AccountIndex; - type BlockNumber = BlockNumber; + type Nonce = Nonce; type RuntimeCall = RuntimeCall; type Hash = sp_core::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = sp_runtime::testing::Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type Version = (); @@ -75,6 +74,10 @@ impl pallet_balances::Config for Runtime { type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } pallet_staking_reward_curve::build! { @@ -95,7 +98,7 @@ impl pallet_staking::Config for Runtime { type Currency = Balances; type CurrencyBalance = Balance; type UnixTime = pallet_timestamp::Pallet; - type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type CurrencyToVote = (); type RewardRemainder = (); type RuntimeEvent = RuntimeEvent; type Slash = (); @@ -116,7 +119,7 @@ impl pallet_staking::Config for Runtime { type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type HistoryDepth = ConstU32<84>; - type OnStakerSlash = Pools; + type EventListeners = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } @@ -171,12 +174,9 @@ impl pallet_nomination_pools::Config for Runtime { impl crate::Config for Runtime {} type Block = frame_system::mocking::MockBlock; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { System: frame_system::{Pallet, Call, Event}, Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, @@ -188,13 +188,14 @@ frame_support::construct_runtime!( ); pub fn new_test_ext() -> sp_io::TestExternalities { - let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); let _ = pallet_nomination_pools::GenesisConfig:: { min_join_bond: 2, min_create_bond: 2, max_pools: Some(3), max_members_per_pool: Some(3), max_members: Some(3 * 3), + global_max_commission: Some(Perbill::from_percent(50)), } .assimilate_storage(&mut storage); sp_io::TestExternalities::from(storage) diff --git a/frame/nomination-pools/fuzzer/src/call.rs b/frame/nomination-pools/fuzzer/src/call.rs index b07903609e8a..027fb2b69138 100644 --- a/frame/nomination-pools/fuzzer/src/call.rs +++ b/frame/nomination-pools/fuzzer/src/call.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,11 +33,11 @@ use pallet_nomination_pools::{ mock::*, pallet as pools, pallet::{BondedPools, Call as PoolsCall, Event as PoolsEvents, PoolMembers}, - BondExtra, BondedPool, LastPoolId, MaxPoolMembers, MaxPoolMembersPerPool, MaxPools, - MinCreateBond, MinJoinBond, PoolId, + BondExtra, BondedPool, GlobalMaxCommission, LastPoolId, MaxPoolMembers, MaxPoolMembersPerPool, + MaxPools, MinCreateBond, MinJoinBond, PoolId, }; use rand::{seq::SliceRandom, Rng}; -use sp_runtime::{assert_eq_error_rate, Perquintill}; +use sp_runtime::{assert_eq_error_rate, Perbill, Perquintill}; const ERA: BlockNumber = 1000; const MAX_ED_MULTIPLE: Balance = 10_000; @@ -143,9 +143,9 @@ fn random_call(mut rng: &mut R) -> (pools::Call, RuntimeOrigin) { let amount = random_ed_multiple(&mut rng); fund_account(&mut rng, &who); let root = who; - let state_toggler = who; + let bouncer = who; let nominator = who; - (PoolsCall::::create { amount, root, state_toggler, nominator }, origin) + (PoolsCall::::create { amount, root, bouncer, nominator }, origin) }, 7 => { // nominate @@ -224,6 +224,7 @@ fn main() { MaxPoolMembers::::set(Some(10_000)); MaxPoolMembersPerPool::::set(Some(1000)); MaxPools::::set(Some(1_000)); + GlobalMaxCommission::::set(Some(Perbill::from_percent(25))); MinCreateBond::::set(10 * ExistentialDeposit::get()); MinJoinBond::::set(5 * ExistentialDeposit::get()); diff --git a/frame/nomination-pools/runtime-api/Cargo.toml b/frame/nomination-pools/runtime-api/Cargo.toml index d1e4fbb30df5..f92a303e9bbe 100644 --- a/frame/nomination-pools/runtime-api/Cargo.toml +++ b/frame/nomination-pools/runtime-api/Cargo.toml @@ -13,9 +13,10 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } -sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } +pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../" } [features] default = ["std"] @@ -23,4 +24,5 @@ std = [ "codec/std", "sp-api/std", "sp-std/std", + "pallet-nomination-pools/std", ] diff --git a/frame/nomination-pools/runtime-api/src/lib.rs b/frame/nomination-pools/runtime-api/src/lib.rs index aa3ca57ca5b8..881c3c36331b 100644 --- a/frame/nomination-pools/runtime-api/src/lib.rs +++ b/frame/nomination-pools/runtime-api/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,13 +21,22 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::Codec; +use pallet_nomination_pools::PoolId; sp_api::decl_runtime_apis! { /// Runtime api for accessing information about nomination pools. pub trait NominationPoolsApi - where AccountId: Codec, Balance: Codec + where + AccountId: Codec, + Balance: Codec, { /// Returns the pending rewards for the member that the AccountId was given for. - fn pending_rewards(member: AccountId) -> Balance; + fn pending_rewards(who: AccountId) -> Balance; + + /// Returns the equivalent balance of `points` for a given pool. + fn points_to_balance(pool_id: PoolId, points: Balance) -> Balance; + + /// Returns the equivalent points of `new_funds` for a given pool. + fn balance_to_points(pool_id: PoolId, new_funds: Balance) -> Balance; } } diff --git a/frame/nomination-pools/src/lib.rs b/frame/nomination-pools/src/lib.rs index 3cb8abedda2f..c4bebc5a1d03 100644 --- a/frame/nomination-pools/src/lib.rs +++ b/frame/nomination-pools/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,6 +50,11 @@ //! not nominating proper validators. //! * reward account: A similar key-less account, that is set as the `Payee` account for the bonded //! account for all staking rewards. +//! * change rate: The rate at which pool commission can be changed. A change rate consists of a +//! `max_increase` and `min_delay`, dictating the maximum percentage increase that can be applied +//! to the commission per number of blocks. +//! * throttle: An attempted commission increase is throttled if the attempted change falls outside +//! the change rate bounds. //! //! ## Usage //! @@ -61,6 +66,10 @@ //! //! After joining a pool, a member can claim rewards by calling [`Call::claim_payout`]. //! +//! A pool member can also set a `ClaimPermission` with [`Call::set_claim_permission`], to allow +//! other members to permissionlessly bond or withdraw their rewards by calling +//! [`Call::bond_extra_other`] or [`Call::claim_payout_other`] respectively. +//! //! For design docs see the [reward pool](#reward-pool) section. //! //! ### Leave @@ -120,9 +129,34 @@ //! * Depositor: creates the pool and is the initial member. They can only leave the pool once all //! other members have left. Once they fully withdraw their funds, the pool is destroyed. //! * Nominator: can select which validators the pool nominates. -//! * State-Toggler: can change the pools state and kick members if the pool is blocked. -//! * Root: can change the nominator, state-toggler, or itself and can perform any of the actions -//! the nominator or state-toggler can. +//! * Bouncer: can change the pools state and kick members if the pool is blocked. +//! * Root: can change the nominator, bouncer, or itself, manage and claim commission, and can +//! perform any of the actions the nominator or bouncer can. +//! +//! ### Commission +//! +//! A pool can optionally have a commission configuration, via the `root` role, set with +//! [`Call::set_commission`] and claimed with [`Call::claim_commission`]. A payee account must be +//! supplied with the desired commission percentage. Beyond the commission itself, a pool can have a +//! maximum commission and a change rate. +//! +//! Importantly, both max commission [`Call::set_commission_max`] and change rate +//! [`Call::set_commission_change_rate`] can not be removed once set, and can only be set to more +//! restrictive values (i.e. a lower max commission or a slower change rate) in subsequent updates. +//! +//! If set, a pool's commission is bound to [`GlobalMaxCommission`] at the time it is applied to +//! pending rewards. [`GlobalMaxCommission`] is intended to be updated only via governance. +//! +//! When a pool is dissolved, any outstanding pending commission that has not been claimed will be +//! transferred to the depositor. +//! +//! Implementation note: Commission is analogous to a separate member account of the pool, with its +//! own reward counter in the form of `current_pending_commission`. +//! +//! Crucially, commission is applied to rewards based on the current commission in effect at the +//! time rewards are transferred into the reward pool. This is to prevent the malicious behaviour of +//! changing the commission rate to a very high value after rewards are accumulated, and thus claim +//! an unexpectedly high chunk of the reward. //! //! ### Dismantling //! @@ -228,11 +262,14 @@ //! //! ### Reward pool //! -//! When a pool is first bonded it sets up an deterministic, inaccessible account as its reward -//! destination. +//! When a pool is first bonded it sets up a deterministic, inaccessible account as its reward +//! destination. This reward account combined with `RewardPool` compose a reward pool. //! -//! The reward pool is not really a pool anymore, as it does not track points anymore. Instead, it -//! tracks, a virtual value called `reward_counter`, among a few other values. +//! Reward pools are completely separate entities to bonded pools. Along with its account, a reward +//! pool also tracks its outstanding and claimed rewards as counters, in addition to pending and +//! claimed commission. These counters are updated with `RewardPool::update_records`. The current +//! reward counter of the pool (the total outstanding rewards, in points) is also callable with the +//! `RewardPool::current_reward_counter` method. //! //! See [this link](https://hackmd.io/PFGn6wI5TbCmBYoEA_f2Uw) for an in-depth explanation of the //! reward pool mechanism. @@ -243,13 +280,12 @@ //! //! ### Unbonding sub pools //! -//! When a member unbonds, it's balance is unbonded in the bonded pool's account and tracked in -//! an unbonding pool associated with the active era. If no such pool exists, one is created. To -//! track which unbonding sub pool a member belongs too, a member tracks it's -//! `unbonding_era`. +//! When a member unbonds, it's balance is unbonded in the bonded pool's account and tracked in an +//! unbonding pool associated with the active era. If no such pool exists, one is created. To track +//! which unbonding sub pool a member belongs too, a member tracks it's `unbonding_era`. //! -//! When a member initiates unbonding it's claim on the bonded pool -//! (`balance_to_unbond`) is computed as: +//! When a member initiates unbonding it's claim on the bonded pool (`balance_to_unbond`) is +//! computed as: //! //! ```text //! balance_to_unbond = (bonded_pool.balance / bonded_pool.points) * member.points; @@ -258,8 +294,8 @@ //! If this is the first transfer into an unbonding pool arbitrary amount of points can be issued //! per balance. In this implementation unbonding pools are initialized with a 1 point to 1 balance //! ratio (see [`POINTS_TO_BALANCE_INIT_RATIO`]). Otherwise, the unbonding pools hold the same -//! points to balance ratio properties as the bonded pool, so member points in the -//! unbonding pool are issued based on +//! points to balance ratio properties as the bonded pool, so member points in the unbonding pool +//! are issued based on //! //! ```text //! new_points_issued = (points_before_transfer / balance_before_transfer) * balance_to_unbond; @@ -290,25 +326,21 @@ //! //! This section assumes that the slash computation is executed by //! `pallet_staking::StakingLedger::slash`, which passes the information to this pallet via -//! [`sp_staking::OnStakerSlash::on_slash`]. +//! [`sp_staking::OnStakingUpdate::on_slash`]. //! -//! Unbonding pools need to be slashed to ensure all nominators whom where in the bonded pool -//! while it was backing a validator that equivocated are punished. Without these measures a -//! member could unbond right after a validator equivocated with no consequences. +//! Unbonding pools need to be slashed to ensure all nominators whom where in the bonded pool while +//! it was backing a validator that equivocated are punished. Without these measures a member could +//! unbond right after a validator equivocated with no consequences. //! -//! This strategy is unfair to members who joined after the slash, because they get slashed as -//! well, but spares members who unbond. The latter is much more important for security: if a -//! pool's validators are attacking the network, their members need to unbond fast! Avoiding -//! slashes gives them an incentive to do that if validators get repeatedly slashed. +//! This strategy is unfair to members who joined after the slash, because they get slashed as well, +//! but spares members who unbond. The latter is much more important for security: if a pool's +//! validators are attacking the network, their members need to unbond fast! Avoiding slashes gives +//! them an incentive to do that if validators get repeatedly slashed. //! //! To be fair to joiners, this implementation also need joining pools, which are actively staking, //! in addition to the unbonding pools. For maintenance simplicity these are not implemented. //! Related: //! -//! **Relevant methods:** -//! -//! * [`Pallet::on_slash`] -//! //! ### Limitations //! //! * PoolMembers cannot vote with their staked funds because they are transferred into the pools @@ -328,21 +360,26 @@ use frame_support::{ Currency, Defensive, DefensiveOption, DefensiveResult, DefensiveSaturating, ExistenceRequirement, Get, }, - DefaultNoBound, + DefaultNoBound, PalletError, }; +use frame_system::pallet_prelude::BlockNumberFor; use scale_info::TypeInfo; use sp_core::U256; use sp_runtime::{ traits::{ - AccountIdConversion, CheckedAdd, CheckedSub, Convert, Saturating, StaticLookup, Zero, + AccountIdConversion, Bounded, CheckedAdd, CheckedSub, Convert, Saturating, StaticLookup, + Zero, }, - FixedPointNumber, + FixedPointNumber, Perbill, }; -use sp_staking::{EraIndex, OnStakerSlash, StakingInterface}; +use sp_staking::{EraIndex, StakingInterface}; use sp_std::{collections::btree_map::BTreeMap, fmt::Debug, ops::Div, vec::Vec}; +#[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))] +use sp_runtime::TryRuntimeError; + /// The log target of this pallet. -pub const LOG_TARGET: &'static str = "runtime::nomination-pools"; +pub const LOG_TARGET: &str = "runtime::nomination-pools"; // syntactic sugar for logging. #[macro_export] @@ -411,6 +448,35 @@ enum AccountType { Reward, } +/// The permission a pool member can set for other accounts to claim rewards on their behalf. +#[derive(Encode, Decode, MaxEncodedLen, Clone, Copy, Debug, PartialEq, Eq, TypeInfo)] +pub enum ClaimPermission { + /// Only the pool member themself can claim their rewards. + Permissioned, + /// Anyone can compound rewards on a pool member's behalf. + PermissionlessCompound, + /// Anyone can withdraw rewards on a pool member's behalf. + PermissionlessWithdraw, + /// Anyone can withdraw and compound rewards on a member's behalf. + PermissionlessAll, +} + +impl ClaimPermission { + fn can_bond_extra(&self) -> bool { + matches!(self, ClaimPermission::PermissionlessAll | ClaimPermission::PermissionlessCompound) + } + + fn can_claim_payout(&self) -> bool { + matches!(self, ClaimPermission::PermissionlessAll | ClaimPermission::PermissionlessWithdraw) + } +} + +impl Default for ClaimPermission { + fn default() -> Self { + Self::Permissioned + } +} + /// A member in a pool. #[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, CloneNoBound)] #[cfg_attr(feature = "std", derive(frame_support::PartialEqNoBound, DefaultNoBound))] @@ -573,13 +639,226 @@ pub struct PoolRoles { /// Creates the pool and is the initial member. They can only leave the pool once all other /// members have left. Once they fully leave, the pool is destroyed. pub depositor: AccountId, - /// Can change the nominator, state-toggler, or itself and can perform any of the actions the - /// nominator or state-toggler can. + /// Can change the nominator, bouncer, or itself and can perform any of the actions the + /// nominator or bouncer can. pub root: Option, /// Can select which validators the pool nominates. pub nominator: Option, /// Can change the pools state and kick members if the pool is blocked. - pub state_toggler: Option, + pub bouncer: Option, +} + +/// Pool commission. +/// +/// The pool `root` can set commission configuration after pool creation. By default, all commission +/// values are `None`. Pool `root` can also set `max` and `change_rate` configurations before +/// setting an initial `current` commission. +/// +/// `current` is a tuple of the commission percentage and payee of commission. `throttle_from` +/// keeps track of which block `current` was last updated. A `max` commission value can only be +/// decreased after the initial value is set, to prevent commission from repeatedly increasing. +/// +/// An optional commission `change_rate` allows the pool to set strict limits to how much commission +/// can change in each update, and how often updates can take place. +#[derive( + Encode, Decode, DefaultNoBound, MaxEncodedLen, TypeInfo, DebugNoBound, PartialEq, Copy, Clone, +)] +#[codec(mel_bound(T: Config))] +#[scale_info(skip_type_params(T))] +pub struct Commission { + /// Optional commission rate of the pool along with the account commission is paid to. + pub current: Option<(Perbill, T::AccountId)>, + /// Optional maximum commission that can be set by the pool `root`. Once set, this value can + /// only be updated to a decreased value. + pub max: Option, + /// Optional configuration around how often commission can be updated, and when the last + /// commission update took place. + pub change_rate: Option>>, + /// The block from where throttling should be checked from. This value will be updated on all + /// commission updates and when setting an initial `change_rate`. + pub throttle_from: Option>, +} + +impl Commission { + /// Returns true if the current commission updating to `to` would exhaust the change rate + /// limits. + /// + /// A commission update will be throttled (disallowed) if: + /// 1. not enough blocks have passed since the `throttle_from` block, if exists, or + /// 2. the new commission is greater than the maximum allowed increase. + fn throttling(&self, to: &Perbill) -> bool { + if let Some(t) = self.change_rate.as_ref() { + let commission_as_percent = + self.current.as_ref().map(|(x, _)| *x).unwrap_or(Perbill::zero()); + + // do not throttle if `to` is the same or a decrease in commission. + if *to <= commission_as_percent { + return false + } + // Test for `max_increase` throttling. + // + // Throttled if the attempted increase in commission is greater than `max_increase`. + if (*to).saturating_sub(commission_as_percent) > t.max_increase { + return true + } + + // Test for `min_delay` throttling. + // + // Note: matching `None` is defensive only. `throttle_from` should always exist where + // `change_rate` has already been set, so this scenario should never happen. + return self.throttle_from.map_or_else( + || { + defensive!("throttle_from should exist if change_rate is set"); + true + }, + |f| { + // if `min_delay` is zero (no delay), not throttling. + if t.min_delay == Zero::zero() { + false + } else { + // throttling if blocks passed is less than `min_delay`. + let blocks_surpassed = + >::block_number().saturating_sub(f); + blocks_surpassed < t.min_delay + } + }, + ) + } + false + } + + /// Gets the pool's current commission, or returns Perbill::zero if none is set. + /// Bounded to global max if current is greater than `GlobalMaxCommission`. + fn current(&self) -> Perbill { + self.current + .as_ref() + .map_or(Perbill::zero(), |(c, _)| *c) + .min(GlobalMaxCommission::::get().unwrap_or(Bounded::max_value())) + } + + /// Set the pool's commission. + /// + /// Update commission based on `current`. If a `None` is supplied, allow the commission to be + /// removed without any change rate restrictions. Updates `throttle_from` to the current block. + /// If the supplied commission is zero, `None` will be inserted and `payee` will be ignored. + fn try_update_current(&mut self, current: &Option<(Perbill, T::AccountId)>) -> DispatchResult { + self.current = match current { + None => None, + Some((commission, payee)) => { + ensure!(!self.throttling(commission), Error::::CommissionChangeThrottled); + ensure!( + commission <= &GlobalMaxCommission::::get().unwrap_or(Bounded::max_value()), + Error::::CommissionExceedsGlobalMaximum + ); + ensure!( + self.max.map_or(true, |m| commission <= &m), + Error::::CommissionExceedsMaximum + ); + if commission.is_zero() { + None + } else { + Some((*commission, payee.clone())) + } + }, + }; + self.register_update(); + Ok(()) + } + + /// Set the pool's maximum commission. + /// + /// The pool's maximum commission can initially be set to any value, and only smaller values + /// thereafter. If larger values are attempted, this function will return a dispatch error. + /// + /// If `current.0` is larger than the updated max commission value, `current.0` will also be + /// updated to the new maximum. This will also register a `throttle_from` update. + /// A `PoolCommissionUpdated` event is triggered if `current.0` is updated. + fn try_update_max(&mut self, pool_id: PoolId, new_max: Perbill) -> DispatchResult { + ensure!( + new_max <= GlobalMaxCommission::::get().unwrap_or(Bounded::max_value()), + Error::::CommissionExceedsGlobalMaximum + ); + if let Some(old) = self.max.as_mut() { + if new_max > *old { + return Err(Error::::MaxCommissionRestricted.into()) + } + *old = new_max; + } else { + self.max = Some(new_max) + }; + let updated_current = self + .current + .as_mut() + .map(|(c, _)| { + let u = *c > new_max; + *c = (*c).min(new_max); + u + }) + .unwrap_or(false); + + if updated_current { + if let Some((_, payee)) = self.current.as_ref() { + Pallet::::deposit_event(Event::::PoolCommissionUpdated { + pool_id, + current: Some((new_max, payee.clone())), + }); + } + self.register_update(); + } + Ok(()) + } + + /// Set the pool's commission `change_rate`. + /// + /// Once a change rate configuration has been set, only more restrictive values can be set + /// thereafter. These restrictions translate to increased `min_delay` values and decreased + /// `max_increase` values. + /// + /// Update `throttle_from` to the current block upon setting change rate for the first time, so + /// throttling can be checked from this block. + fn try_update_change_rate( + &mut self, + change_rate: CommissionChangeRate>, + ) -> DispatchResult { + ensure!(!&self.less_restrictive(&change_rate), Error::::CommissionChangeRateNotAllowed); + + if self.change_rate.is_none() { + self.register_update(); + } + self.change_rate = Some(change_rate); + Ok(()) + } + + /// Updates a commission's `throttle_from` field to the current block. + fn register_update(&mut self) { + self.throttle_from = Some(>::block_number()); + } + + /// Checks whether a change rate is less restrictive than the current change rate, if any. + /// + /// No change rate will always be less restrictive than some change rate, so where no + /// `change_rate` is currently set, `false` is returned. + fn less_restrictive(&self, new: &CommissionChangeRate>) -> bool { + self.change_rate + .as_ref() + .map(|c| new.max_increase > c.max_increase || new.min_delay < c.min_delay) + .unwrap_or(false) + } +} + +/// Pool commission change rate preferences. +/// +/// The pool root is able to set a commission change rate for their pool. A commission change rate +/// consists of 2 values; (1) the maximum allowed commission change, and (2) the minimum amount of +/// blocks that must elapse before commission updates are allowed again. +/// +/// Commission change rates are not applied to decreases in commission. +#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, Debug, PartialEq, Copy, Clone)] +pub struct CommissionChangeRate { + /// The maximum amount the commission can be updated by per `min_delay` period. + pub max_increase: Perbill, + /// How often an update can take place. + pub min_delay: BlockNumber, } /// Pool permissions and state @@ -587,20 +866,22 @@ pub struct PoolRoles { #[codec(mel_bound(T: Config))] #[scale_info(skip_type_params(T))] pub struct BondedPoolInner { - /// Total points of all the members in the pool who are actively bonded. - pub points: BalanceOf, - /// The current state of the pool. - pub state: PoolState, + /// The commission rate of the pool. + pub commission: Commission, /// Count of members that belong to the pool. pub member_counter: u32, + /// Total points of all the members in the pool who are actively bonded. + pub points: BalanceOf, /// See [`PoolRoles`]. pub roles: PoolRoles, + /// The current state of the pool. + pub state: PoolState, } /// A wrapper for bonded pools, with utility functions. /// -/// The main purpose of this is to wrap a [`BondedPoolInner`], with the account + id of the pool, -/// for easier access. +/// The main purpose of this is to wrap a [`BondedPoolInner`], with the account +/// + id of the pool, for easier access. #[derive(RuntimeDebugNoBound)] #[cfg_attr(feature = "std", derive(Clone, PartialEq))] pub struct BondedPool { @@ -629,10 +910,11 @@ impl BondedPool { Self { id, inner: BondedPoolInner { + commission: Commission::default(), + member_counter: Zero::zero(), + points: Zero::zero(), roles, state: PoolState::Open, - points: Zero::zero(), - member_counter: Zero::zero(), }, } } @@ -654,7 +936,7 @@ impl BondedPool { /// Consume self and put into storage. fn put(self) { - BondedPools::::insert(self.id, BondedPoolInner { ..self.inner }); + BondedPools::::insert(self.id, self.inner); } /// Consume self and remove from storage. @@ -734,11 +1016,8 @@ impl BondedPool { self.roles.root.as_ref().map_or(false, |root| root == who) } - fn is_state_toggler(&self, who: &T::AccountId) -> bool { - self.roles - .state_toggler - .as_ref() - .map_or(false, |state_toggler| state_toggler == who) + fn is_bouncer(&self, who: &T::AccountId) -> bool { + self.roles.bouncer.as_ref().map_or(false, |bouncer| bouncer == who) } fn can_update_roles(&self, who: &T::AccountId) -> bool { @@ -751,15 +1030,19 @@ impl BondedPool { } fn can_kick(&self, who: &T::AccountId) -> bool { - self.state == PoolState::Blocked && (self.is_root(who) || self.is_state_toggler(who)) + self.state == PoolState::Blocked && (self.is_root(who) || self.is_bouncer(who)) } fn can_toggle_state(&self, who: &T::AccountId) -> bool { - (self.is_root(who) || self.is_state_toggler(who)) && !self.is_destroying() + (self.is_root(who) || self.is_bouncer(who)) && !self.is_destroying() } fn can_set_metadata(&self, who: &T::AccountId) -> bool { - self.is_root(who) || self.is_state_toggler(who) + self.is_root(who) || self.is_bouncer(who) + } + + fn can_manage_commission(&self, who: &T::AccountId) -> bool { + self.is_root(who) } fn is_destroying(&self) -> bool { @@ -914,7 +1197,7 @@ impl BondedPool { // Cache the value let bonded_account = self.bonded_account(); T::Currency::transfer( - &who, + who, &bonded_account, amount, match ty { @@ -973,6 +1256,10 @@ pub struct RewardPool { last_recorded_total_payouts: BalanceOf, /// Total amount that this pool has paid out so far to the members. total_rewards_claimed: BalanceOf, + /// The amount of commission pending to be claimed. + total_commission_pending: BalanceOf, + /// The amount of commission that has been claimed. + total_commission_claimed: BalanceOf, } impl RewardPool { @@ -986,13 +1273,40 @@ impl RewardPool { self.total_rewards_claimed = self.total_rewards_claimed.saturating_add(reward); } - /// Update the recorded values of the pool. - fn update_records(&mut self, id: PoolId, bonded_points: BalanceOf) -> Result<(), Error> { + /// Update the recorded values of the reward pool. + /// + /// This function MUST be called whenever the points in the bonded pool change, AND whenever the + /// the pools commission is updated. The reason for the former is that a change in pool points + /// will alter the share of the reward balance among pool members, and the reason for the latter + /// is that a change in commission will alter the share of the reward balance among the pool. + fn update_records( + &mut self, + id: PoolId, + bonded_points: BalanceOf, + commission: Perbill, + ) -> Result<(), Error> { let balance = Self::current_balance(id); - self.last_recorded_reward_counter = self.current_reward_counter(id, bonded_points)?; + + let (current_reward_counter, new_pending_commission) = + self.current_reward_counter(id, bonded_points, commission)?; + + // Store the reward counter at the time of this update. This is used in subsequent calls to + // `current_reward_counter`, whereby newly pending rewards (in points) are added to this + // value. + self.last_recorded_reward_counter = current_reward_counter; + + // Add any new pending commission that has been calculated from `current_reward_counter` to + // determine the total pending commission at the time of this update. + self.total_commission_pending = + self.total_commission_pending.saturating_add(new_pending_commission); + + // Store the total payouts at the time of this update. Total payouts are essentially the + // entire historical balance of the reward pool, equating to the current balance + the total + // rewards that have left the pool + the total commission that has left the pool. self.last_recorded_total_payouts = balance - .checked_add(&self.total_rewards_claimed) + .checked_add(&self.total_rewards_claimed.saturating_add(self.total_commission_claimed)) .ok_or(Error::::OverflowRisk)?; + Ok(()) } @@ -1002,15 +1316,27 @@ impl RewardPool { &self, id: PoolId, bonded_points: BalanceOf, - ) -> Result> { + commission: Perbill, + ) -> Result<(T::RewardCounter, BalanceOf), Error> { let balance = Self::current_balance(id); - let payouts_since_last_record = balance + + // Calculate the current payout balance. The first 3 values of this calculation added + // together represent what the balance would be if no payouts were made. The + // `last_recorded_total_payouts` is then subtracted from this value to cancel out previously + // recorded payouts, leaving only the remaining payouts that have not been claimed. + let current_payout_balance = balance .saturating_add(self.total_rewards_claimed) + .saturating_add(self.total_commission_claimed) .saturating_sub(self.last_recorded_total_payouts); + // Split the `current_payout_balance` into claimable rewards and claimable commission + // according to the current commission rate. + let new_pending_commission = commission * current_payout_balance; + let new_pending_rewards = current_payout_balance.saturating_sub(new_pending_commission); + // * accuracy notes regarding the multiplication in `checked_from_rational`: - // `payouts_since_last_record` is a subset of the total_issuance at the very - // worse. `bonded_points` are similarly, in a non-slashed pool, have the same granularity as + // `current_payout_balance` is a subset of the total_issuance at the very worse. + // `bonded_points` are similarly, in a non-slashed pool, have the same granularity as // balance, and are thus below within the range of total_issuance. In the worse case // scenario, for `saturating_from_rational`, we have: // @@ -1028,22 +1354,26 @@ impl RewardPool { // represented as `FixedU128`, which means it is less than `total_issuance * 10^18`. // // * accuracy notes regarding `checked_from_rational` collapsing to zero, meaning that no - // reward can be claimed: + // reward can be claimed: // - // largest `bonded_points`, such that the reward counter is non-zero, with `FixedU128` - // will be when the payout is being computed. This essentially means `payout/bonded_points` - // needs to be more than 1/1^18. Thus, assuming that `bonded_points` will always be less - // than `10 * dot_total_issuance`, if the reward_counter is the smallest possible value, - // the value of the reward being calculated is: + // largest `bonded_points`, such that the reward counter is non-zero, with `FixedU128` will + // be when the payout is being computed. This essentially means `payout/bonded_points` needs + // to be more than 1/1^18. Thus, assuming that `bonded_points` will always be less than `10 + // * dot_total_issuance`, if the reward_counter is the smallest possible value, the value of + // the + // reward being calculated is: // // x / 10^20 = 1/ 10^18 // // x = 100 // // which is basically 10^-8 DOTs. See `smallest_claimable_reward` for an example of this. - T::RewardCounter::checked_from_rational(payouts_since_last_record, bonded_points) - .and_then(|ref r| self.last_recorded_reward_counter.checked_add(r)) - .ok_or(Error::::OverflowRisk) + let current_reward_counter = + T::RewardCounter::checked_from_rational(new_pending_rewards, bonded_points) + .and_then(|ref r| self.last_recorded_reward_counter.checked_add(r)) + .ok_or(Error::::OverflowRisk)?; + + Ok((current_reward_counter, new_pending_commission)) } /// Current free balance of the reward pool. @@ -1171,12 +1501,12 @@ pub mod pallet { use super::*; use frame_support::traits::StorageVersion; use frame_system::{ensure_signed, pallet_prelude::*}; + use sp_runtime::Perbill; /// The current storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(3); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(5); #[pallet::pallet] - #[pallet::generate_store(pub(crate) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); @@ -1276,6 +1606,12 @@ pub mod pallet { #[pallet::storage] pub type MaxPoolMembersPerPool = StorageValue<_, u32, OptionQuery>; + /// The maximum commission that can be charged by a pool. Used on commission payouts to bound + /// pool commissions that are > `GlobalMaxCommission`, necessary if a future + /// `GlobalMaxCommission` is lower than some current pool commissions. + #[pallet::storage] + pub type GlobalMaxCommission = StorageValue<_, Perbill, OptionQuery>; + /// Active members. /// /// TWOX-NOTE: SAFE since `AccountId` is a secure hash. @@ -1289,13 +1625,13 @@ pub mod pallet { pub type BondedPools = CountedStorageMap<_, Twox64Concat, PoolId, BondedPoolInner>; - /// Reward pools. This is where there rewards for each pool accumulate. When a members payout - /// is claimed, the balance comes out fo the reward pool. Keyed by the bonded pools account. + /// Reward pools. This is where there rewards for each pool accumulate. When a members payout is + /// claimed, the balance comes out fo the reward pool. Keyed by the bonded pools account. #[pallet::storage] pub type RewardPools = CountedStorageMap<_, Twox64Concat, PoolId, RewardPool>; - /// Groups of unbonding pools. Each group of unbonding pools belongs to a bonded pool, - /// hence the name sub-pools. Keyed by the bonded pools account. + /// Groups of unbonding pools. Each group of unbonding pools belongs to a + /// bonded pool, hence the name sub-pools. Keyed by the bonded pools account. #[pallet::storage] pub type SubPoolsStorage = CountedStorageMap<_, Twox64Concat, PoolId, SubPools>; @@ -1316,6 +1652,11 @@ pub mod pallet { pub type ReversePoolIdLookup = CountedStorageMap<_, Twox64Concat, T::AccountId, PoolId, OptionQuery>; + /// Map from a pool member account to their opted claim permission. + #[pallet::storage] + pub type ClaimPermissions = + StorageMap<_, Twox64Concat, T::AccountId, ClaimPermission, ValueQuery>; + #[pallet::genesis_config] pub struct GenesisConfig { pub min_join_bond: BalanceOf, @@ -1323,9 +1664,9 @@ pub mod pallet { pub max_pools: Option, pub max_members_per_pool: Option, pub max_members: Option, + pub global_max_commission: Option, } - #[cfg(feature = "std")] impl Default for GenesisConfig { fn default() -> Self { Self { @@ -1334,12 +1675,13 @@ pub mod pallet { max_pools: Some(16), max_members_per_pool: Some(32), max_members: Some(16 * 32), + global_max_commission: None, } } } #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { MinJoinBond::::put(self.min_join_bond); MinCreateBond::::put(self.min_create_bond); @@ -1352,6 +1694,9 @@ pub mod pallet { if let Some(max_members) = self.max_members { MaxPoolMembers::::put(max_members); } + if let Some(global_max_commission) = self.global_max_commission { + GlobalMaxCommission::::put(global_max_commission); + } } } @@ -1407,13 +1752,24 @@ pub mod pallet { /// can never change. RolesUpdated { root: Option, - state_toggler: Option, + bouncer: Option, nominator: Option, }, /// The active balance of pool `pool_id` has been slashed to `balance`. PoolSlashed { pool_id: PoolId, balance: BalanceOf }, /// The unbond pool at `era` of pool `pool_id` has been slashed to `balance`. UnbondingPoolSlashed { pool_id: PoolId, era: EraIndex, balance: BalanceOf }, + /// A pool's commission setting has been changed. + PoolCommissionUpdated { pool_id: PoolId, current: Option<(Perbill, T::AccountId)> }, + /// A pool's maximum commission setting has been changed. + PoolMaxCommissionUpdated { pool_id: PoolId, max_commission: Perbill }, + /// A pool's commission `change_rate` has been changed. + PoolCommissionChangeRateUpdated { + pool_id: PoolId, + change_rate: CommissionChangeRate>, + }, + /// Pool commission has been claimed. + PoolCommissionClaimed { pool_id: PoolId, commission: BalanceOf }, } #[pallet::error] @@ -1469,13 +1825,29 @@ pub mod pallet { Defensive(DefensiveError), /// Partial unbonding now allowed permissionlessly. PartialUnbondNotAllowedPermissionlessly, + /// The pool's max commission cannot be set higher than the existing value. + MaxCommissionRestricted, + /// The supplied commission exceeds the max allowed commission. + CommissionExceedsMaximum, + /// The supplied commission exceeds global maximum commission. + CommissionExceedsGlobalMaximum, + /// Not enough blocks have surpassed since the last commission update. + CommissionChangeThrottled, + /// The submitted changes to commission change rate are not allowed. + CommissionChangeRateNotAllowed, + /// There is no pending commission to claim. + NoPendingCommission, + /// No commission current has been set. + NoCommissionCurrentSet, /// Pool id currently in use. PoolIdInUse, /// Pool id provided is not correct/usable. InvalidPoolId, + /// Bonding extra is restricted to the exact pending reward amount. + BondExtraRestricted, } - #[derive(Encode, Decode, PartialEq, TypeInfo, frame_support::PalletError, RuntimeDebug)] + #[derive(Encode, Decode, PartialEq, TypeInfo, PalletError, RuntimeDebug)] pub enum DefensiveError { /// There isn't enough space in the unbond pool. NotEnoughSpaceInUnbondPool, @@ -1527,7 +1899,11 @@ pub mod pallet { let mut reward_pool = RewardPools::::get(pool_id) .defensive_ok_or::>(DefensiveError::RewardPoolNotFound.into())?; // IMPORTANT: reward pool records must be updated with the old points. - reward_pool.update_records(pool_id, bonded_pool.points)?; + reward_pool.update_records( + pool_id, + bonded_pool.points, + bonded_pool.commission.current(), + )?; bonded_pool.try_inc_members()?; let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Later)?; @@ -1563,62 +1939,33 @@ pub mod pallet { /// accumulated rewards, see [`BondExtra`]. /// /// Bonding extra funds implies an automatic payout of all pending rewards as well. + /// See `bond_extra_other` to bond pending rewards of `other` members. // NOTE: this transaction is implemented with the sole purpose of readability and // correctness, not optimization. We read/write several storage items multiple times instead // of just once, in the spirit reusing code. #[pallet::call_index(1)] #[pallet::weight( T::WeightInfo::bond_extra_transfer() - .max(T::WeightInfo::bond_extra_reward()) + .max(T::WeightInfo::bond_extra_other()) )] pub fn bond_extra(origin: OriginFor, extra: BondExtra>) -> DispatchResult { let who = ensure_signed(origin)?; - let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&who)?; - - // payout related stuff: we must claim the payouts, and updated recorded payout data - // before updating the bonded pool points, similar to that of `join` transaction. - reward_pool.update_records(bonded_pool.id, bonded_pool.points)?; - let claimed = - Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; - - let (points_issued, bonded) = match extra { - BondExtra::FreeBalance(amount) => - (bonded_pool.try_bond_funds(&who, amount, BondType::Later)?, amount), - BondExtra::Rewards => - (bonded_pool.try_bond_funds(&who, claimed, BondType::Later)?, claimed), - }; - - bonded_pool.ok_to_be_open()?; - member.points = - member.points.checked_add(&points_issued).ok_or(Error::::OverflowRisk)?; - - Self::deposit_event(Event::::Bonded { - member: who.clone(), - pool_id: member.pool_id, - bonded, - joined: false, - }); - Self::put_member_with_pools(&who, member, bonded_pool, reward_pool); - - Ok(()) + Self::do_bond_extra(who.clone(), who, extra) } /// A bonded member can use this to claim their payout based on the rewards that the pool - /// has accumulated since their last claimed payout (OR since joining if this is there first + /// has accumulated since their last claimed payout (OR since joining if this is their first /// time claiming rewards). The payout will be transferred to the member's account. /// /// The member will earn rewards pro rata based on the members stake vs the sum of the /// members in the pools stake. Rewards do not "expire". + /// + /// See `claim_payout_other` to caim rewards on bahalf of some `other` pool member. #[pallet::call_index(2)] #[pallet::weight(T::WeightInfo::claim_payout())] pub fn claim_payout(origin: OriginFor) -> DispatchResult { - let who = ensure_signed(origin)?; - let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&who)?; - - let _ = Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; - - Self::put_member_with_pools(&who, member, bonded_pool, reward_pool); - Ok(()) + let signer = ensure_signed(origin)?; + Self::do_claim_payout(signer.clone(), signer) } /// Unbond up to `unbonding_points` of the `member_account`'s funds from the pool. It @@ -1630,8 +1977,8 @@ pub mod pallet { /// /// # Conditions for a permissionless dispatch. /// - /// * The pool is blocked and the caller is either the root or state-toggler. This is - /// refereed to as a kick. + /// * The pool is blocked and the caller is either the root or bouncer. This is refereed to + /// as a kick. /// * The pool is destroying and the member is not the depositor. /// * The pool is destroying, the member is the depositor and no other members are in the /// pool. @@ -1669,7 +2016,11 @@ pub mod pallet { // Claim the the payout prior to unbonding. Once the user is unbonding their points no // longer exist in the bonded pool and thus they can no longer claim their payouts. It // is not strictly necessary to claim the rewards, but we do it here for UX. - let _ = reward_pool.update_records(bonded_pool.id, bonded_pool.points)?; + reward_pool.update_records( + bonded_pool.id, + bonded_pool.points, + bonded_pool.commission.current(), + )?; let _ = Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; let current_era = T::Staking::current_era(); @@ -1716,9 +2067,8 @@ pub mod pallet { }); // Now that we know everything has worked write the items to storage. - SubPoolsStorage::insert(&member.pool_id, sub_pools); + SubPoolsStorage::insert(member.pool_id, sub_pools); Self::put_member_with_pools(&member_account, member, bonded_pool, reward_pool); - Ok(()) } @@ -1754,7 +2104,7 @@ pub mod pallet { /// /// * The pool is in destroy mode and the target is not the depositor. /// * The target is the depositor and they are the only member in the sub pools. - /// * The pool is blocked and the caller is either the root or state-toggler. + /// * The pool is blocked and the caller is either the root or bouncer. /// /// # Conditions for permissioned dispatch /// @@ -1806,10 +2156,10 @@ pub mod pallet { .iter() .fold(BalanceOf::::zero(), |accumulator, (era, unlocked_points)| { sum_unlocked_points = sum_unlocked_points.saturating_add(*unlocked_points); - if let Some(era_pool) = sub_pools.with_era.get_mut(&era) { + if let Some(era_pool) = sub_pools.with_era.get_mut(era) { let balance_to_unbond = era_pool.dissolve(*unlocked_points); if era_pool.points.is_zero() { - sub_pools.with_era.remove(&era); + sub_pools.with_era.remove(era); } accumulator.saturating_add(balance_to_unbond) } else { @@ -1843,6 +2193,9 @@ pub mod pallet { }); let post_info_weight = if member.total_points().is_zero() { + // remove any `ClaimPermission` associated with the member. + ClaimPermissions::::remove(&member_account); + // member being reaped. PoolMembers::::remove(&member_account); Self::deposit_event(Event::::MemberRemoved { @@ -1855,12 +2208,12 @@ pub mod pallet { None } else { bonded_pool.dec_members().put(); - SubPoolsStorage::::insert(&member.pool_id, sub_pools); + SubPoolsStorage::::insert(member.pool_id, sub_pools); Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) } } else { // we certainly don't need to delete any pools, because no one is being removed. - SubPoolsStorage::::insert(&member.pool_id, sub_pools); + SubPoolsStorage::::insert(member.pool_id, sub_pools); PoolMembers::::insert(&member_account, member); Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)) }; @@ -1879,7 +2232,7 @@ pub mod pallet { /// creating multiple pools in the same extrinsic. /// * `root` - The account to set as [`PoolRoles::root`]. /// * `nominator` - The account to set as the [`PoolRoles::nominator`]. - /// * `state_toggler` - The account to set as the [`PoolRoles::state_toggler`]. + /// * `bouncer` - The account to set as the [`PoolRoles::bouncer`]. /// /// # Note /// @@ -1892,7 +2245,7 @@ pub mod pallet { #[pallet::compact] amount: BalanceOf, root: AccountIdLookupOf, nominator: AccountIdLookupOf, - state_toggler: AccountIdLookupOf, + bouncer: AccountIdLookupOf, ) -> DispatchResult { let depositor = ensure_signed(origin)?; @@ -1901,7 +2254,7 @@ pub mod pallet { Ok(*id) })?; - Self::do_create(depositor, amount, root, nominator, state_toggler, pool_id) + Self::do_create(depositor, amount, root, nominator, bouncer, pool_id) } /// Create a new delegation pool with a previously used pool id @@ -1917,7 +2270,7 @@ pub mod pallet { #[pallet::compact] amount: BalanceOf, root: AccountIdLookupOf, nominator: AccountIdLookupOf, - state_toggler: AccountIdLookupOf, + bouncer: AccountIdLookupOf, pool_id: PoolId, ) -> DispatchResult { let depositor = ensure_signed(origin)?; @@ -1925,7 +2278,7 @@ pub mod pallet { ensure!(!BondedPools::::contains_key(pool_id), Error::::PoolIdInUse); ensure!(pool_id < LastPoolId::::get(), Error::::InvalidPoolId); - Self::do_create(depositor, amount, root, nominator, state_toggler, pool_id) + Self::do_create(depositor, amount, root, nominator, bouncer, pool_id) } /// Nominate on behalf of the pool. @@ -1955,7 +2308,7 @@ pub mod pallet { /// /// The dispatch origin of this call must be either: /// - /// 1. signed by the state toggler, or the root role of the pool, + /// 1. signed by the bouncer, or the root role of the pool, /// 2. if the pool conditions to be open are NOT met (as described by `ok_to_be_open`), and /// then the state of the pool can be permissionlessly changed to `Destroying`. #[pallet::call_index(9)] @@ -1985,8 +2338,8 @@ pub mod pallet { /// Set a new metadata for the pool. /// - /// The dispatch origin of this call must be signed by the state toggler, or the root role - /// of the pool. + /// The dispatch origin of this call must be signed by the bouncer, or the root role of the + /// pool. #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::set_metadata(metadata.len() as u32))] pub fn set_metadata( @@ -2019,6 +2372,7 @@ pub mod pallet { /// * `max_pools` - Set [`MaxPools`]. /// * `max_members` - Set [`MaxPoolMembers`]. /// * `max_members_per_pool` - Set [`MaxPoolMembersPerPool`]. + /// * `global_max_commission` - Set [`GlobalMaxCommission`]. #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::set_configs())] pub fn set_configs( @@ -2028,6 +2382,7 @@ pub mod pallet { max_pools: ConfigOp, max_members: ConfigOp, max_members_per_pool: ConfigOp, + global_max_commission: ConfigOp, ) -> DispatchResult { ensure_root(origin)?; @@ -2046,6 +2401,7 @@ pub mod pallet { config_op_exp!(MaxPools::, max_pools); config_op_exp!(MaxPoolMembers::, max_members); config_op_exp!(MaxPoolMembersPerPool::, max_members_per_pool); + config_op_exp!(GlobalMaxCommission::, global_max_commission); Ok(()) } @@ -2063,7 +2419,7 @@ pub mod pallet { pool_id: PoolId, new_root: ConfigOp, new_nominator: ConfigOp, - new_state_toggler: ConfigOp, + new_bouncer: ConfigOp, ) -> DispatchResult { let mut bonded_pool = match ensure_root(origin.clone()) { Ok(()) => BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?, @@ -2086,16 +2442,16 @@ pub mod pallet { ConfigOp::Remove => bonded_pool.roles.nominator = None, ConfigOp::Set(v) => bonded_pool.roles.nominator = Some(v), }; - match new_state_toggler { + match new_bouncer { ConfigOp::Noop => (), - ConfigOp::Remove => bonded_pool.roles.state_toggler = None, - ConfigOp::Set(v) => bonded_pool.roles.state_toggler = Some(v), + ConfigOp::Remove => bonded_pool.roles.bouncer = None, + ConfigOp::Set(v) => bonded_pool.roles.bouncer = Some(v), }; Self::deposit_event(Event::::RolesUpdated { root: bonded_pool.roles.root.clone(), nominator: bonded_pool.roles.nominator.clone(), - state_toggler: bonded_pool.roles.state_toggler.clone(), + bouncer: bonded_pool.roles.bouncer.clone(), }); bonded_pool.put(); @@ -2117,12 +2473,170 @@ pub mod pallet { ensure!(bonded_pool.can_nominate(&who), Error::::NotNominator); T::Staking::chill(&bonded_pool.bonded_account()) } + + /// `origin` bonds funds from `extra` for some pool member `member` into their respective + /// pools. + /// + /// `origin` can bond extra funds from free balance or pending rewards when `origin == + /// other`. + /// + /// In the case of `origin != other`, `origin` can only bond extra pending rewards of + /// `other` members assuming set_claim_permission for the given member is + /// `PermissionlessAll` or `PermissionlessCompound`. + #[pallet::call_index(14)] + #[pallet::weight( + T::WeightInfo::bond_extra_transfer() + .max(T::WeightInfo::bond_extra_other()) + )] + pub fn bond_extra_other( + origin: OriginFor, + member: AccountIdLookupOf, + extra: BondExtra>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_bond_extra(who, T::Lookup::lookup(member)?, extra) + } + + /// Allows a pool member to set a claim permission to allow or disallow permissionless + /// bonding and withdrawing. + /// + /// By default, this is `Permissioned`, which implies only the pool member themselves can + /// claim their pending rewards. If a pool member wishes so, they can set this to + /// `PermissionlessAll` to allow any account to claim their rewards and bond extra to the + /// pool. + /// + /// # Arguments + /// + /// * `origin` - Member of a pool. + /// * `actor` - Account to claim reward. // improve this + #[pallet::call_index(15)] + #[pallet::weight(T::DbWeight::get().reads_writes(1, 1))] + pub fn set_claim_permission( + origin: OriginFor, + permission: ClaimPermission, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + + ensure!(PoolMembers::::contains_key(&who), Error::::PoolMemberNotFound); + ClaimPermissions::::mutate(who, |source| { + *source = permission; + }); + Ok(()) + } + + /// `origin` can claim payouts on some pool member `other`'s behalf. + /// + /// Pool member `other` must have a `PermissionlessAll` or `PermissionlessWithdraw` in order + /// for this call to be successful. + #[pallet::call_index(16)] + #[pallet::weight(T::WeightInfo::claim_payout())] + pub fn claim_payout_other(origin: OriginFor, other: T::AccountId) -> DispatchResult { + let signer = ensure_signed(origin)?; + Self::do_claim_payout(signer, other) + } + + /// Set the commission of a pool. + // + /// Both a commission percentage and a commission payee must be provided in the `current` + /// tuple. Where a `current` of `None` is provided, any current commission will be removed. + /// + /// - If a `None` is supplied to `new_commission`, existing commission will be removed. + #[pallet::call_index(17)] + #[pallet::weight(T::WeightInfo::set_commission())] + pub fn set_commission( + origin: OriginFor, + pool_id: PoolId, + new_commission: Option<(Perbill, T::AccountId)>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + ensure!(bonded_pool.can_manage_commission(&who), Error::::DoesNotHavePermission); + + let mut reward_pool = RewardPools::::get(pool_id) + .defensive_ok_or::>(DefensiveError::RewardPoolNotFound.into())?; + // IMPORTANT: make sure that everything up to this point is using the current commission + // before it updates. Note that `try_update_current` could still fail at this point. + reward_pool.update_records( + pool_id, + bonded_pool.points, + bonded_pool.commission.current(), + )?; + RewardPools::insert(pool_id, reward_pool); + + bonded_pool.commission.try_update_current(&new_commission)?; + bonded_pool.put(); + Self::deposit_event(Event::::PoolCommissionUpdated { + pool_id, + current: new_commission, + }); + Ok(()) + } + + /// Set the maximum commission of a pool. + /// + /// - Initial max can be set to any `Perbill`, and only smaller values thereafter. + /// - Current commission will be lowered in the event it is higher than a new max + /// commission. + #[pallet::call_index(18)] + #[pallet::weight(T::WeightInfo::set_commission_max())] + pub fn set_commission_max( + origin: OriginFor, + pool_id: PoolId, + max_commission: Perbill, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + ensure!(bonded_pool.can_manage_commission(&who), Error::::DoesNotHavePermission); + + bonded_pool.commission.try_update_max(pool_id, max_commission)?; + bonded_pool.put(); + + Self::deposit_event(Event::::PoolMaxCommissionUpdated { pool_id, max_commission }); + Ok(()) + } + + /// Set the commission change rate for a pool. + /// + /// Initial change rate is not bounded, whereas subsequent updates can only be more + /// restrictive than the current. + #[pallet::call_index(19)] + #[pallet::weight(T::WeightInfo::set_commission_change_rate())] + pub fn set_commission_change_rate( + origin: OriginFor, + pool_id: PoolId, + change_rate: CommissionChangeRate>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + let mut bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + ensure!(bonded_pool.can_manage_commission(&who), Error::::DoesNotHavePermission); + + bonded_pool.commission.try_update_change_rate(change_rate)?; + bonded_pool.put(); + + Self::deposit_event(Event::::PoolCommissionChangeRateUpdated { + pool_id, + change_rate, + }); + Ok(()) + } + + /// Claim pending commission. + /// + /// The dispatch origin of this call must be signed by the `root` role of the pool. Pending + /// commission is paid out and added to total claimed commission`. Total pending commission + /// is reset to zero. the current. + #[pallet::call_index(20)] + #[pallet::weight(T::WeightInfo::claim_commission())] + pub fn claim_commission(origin: OriginFor, pool_id: PoolId) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_claim_commission(who, pool_id) + } } #[pallet::hooks] impl Hooks> for Pallet { #[cfg(feature = "try-runtime")] - fn try_state(_n: BlockNumberFor) -> Result<(), &'static str> { + fn try_state(_n: BlockNumberFor) -> Result<(), TryRuntimeError> { Self::do_try_state(u8::MAX) } @@ -2142,24 +2656,6 @@ pub mod pallet { } impl Pallet { - /// Returns the pending rewards for the specified `member_account`. - /// - /// In the case of error, `None` is returned. - pub fn pending_rewards(member_account: T::AccountId) -> Option> { - if let Some(pool_member) = PoolMembers::::get(member_account) { - if let Some((reward_pool, bonded_pool)) = RewardPools::::get(pool_member.pool_id) - .zip(BondedPools::::get(pool_member.pool_id)) - { - let current_reward_counter = reward_pool - .current_reward_counter(pool_member.pool_id, bonded_pool.points) - .ok()?; - return pool_member.pending_rewards(current_reward_counter).ok() - } - } - - None - } - /// The amount of bond that MUST REMAIN IN BONDED in ALL POOLS. /// /// It is the responsibility of the depositor to put these funds into the pool initially. Upon @@ -2199,7 +2695,8 @@ impl Pallet { Zero::zero() ); - // This shouldn't fail, but if it does we don't really care + // This shouldn't fail, but if it does we don't really care. Remaining balance can consist + // of unclaimed pending commission, errorneous transfers to the reward account, etc. let reward_pool_remaining = T::Currency::free_balance(&reward_account); let _ = T::Currency::transfer( &reward_account, @@ -2235,7 +2732,7 @@ impl Pallet { fn get_member_with_pools( who: &T::AccountId, ) -> Result<(PoolMember, BondedPool, RewardPool), Error> { - let member = PoolMembers::::get(&who).ok_or(Error::::PoolMemberNotFound)?; + let member = PoolMembers::::get(who).ok_or(Error::::PoolMemberNotFound)?; let bonded_pool = BondedPool::::get(member.pool_id).defensive_ok_or(DefensiveError::PoolNotFound)?; let reward_pool = @@ -2263,8 +2760,8 @@ impl Pallet { current_points: BalanceOf, new_funds: BalanceOf, ) -> BalanceOf { - let u256 = |x| T::BalanceToU256::convert(x); - let balance = |x| T::U256ToBalance::convert(x); + let u256 = T::BalanceToU256::convert; + let balance = T::U256ToBalance::convert; match (current_balance.is_zero(), current_points.is_zero()) { (_, true) => new_funds.saturating_mul(POINTS_TO_BALANCE_INIT_RATIO.into()), (true, false) => { @@ -2291,8 +2788,8 @@ impl Pallet { current_points: BalanceOf, points: BalanceOf, ) -> BalanceOf { - let u256 = |x| T::BalanceToU256::convert(x); - let balance = |x| T::U256ToBalance::convert(x); + let u256 = T::BalanceToU256::convert; + let balance = T::U256ToBalance::convert; if current_balance.is_zero() || current_points.is_zero() || points.is_zero() { // There is nothing to unbond return Zero::zero() @@ -2318,10 +2815,15 @@ impl Pallet { // a member who has no skin in the game anymore cannot claim any rewards. ensure!(!member.active_points().is_zero(), Error::::FullyUnbonding); - let current_reward_counter = - reward_pool.current_reward_counter(bonded_pool.id, bonded_pool.points)?; - let pending_rewards = member.pending_rewards(current_reward_counter)?; + let (current_reward_counter, _) = reward_pool.current_reward_counter( + bonded_pool.id, + bonded_pool.points, + bonded_pool.commission.current(), + )?; + // Determine the pending rewards. In scenarios where commission is 100%, `pending_rewards` + // will be zero. + let pending_rewards = member.pending_rewards(current_reward_counter)?; if pending_rewards.is_zero() { return Ok(pending_rewards) } @@ -2330,14 +2832,13 @@ impl Pallet { member.last_recorded_reward_counter = current_reward_counter; reward_pool.register_claimed_reward(pending_rewards); - // Transfer payout to the member. T::Currency::transfer( &bonded_pool.reward_account(), - &member_account, + member_account, pending_rewards, // defensive: the depositor has put existential deposit into the pool and it stays // untouched, reward account shall not die. - ExistenceRequirement::AllowDeath, + ExistenceRequirement::KeepAlive, )?; Self::deposit_event(Event::::PaidOut { @@ -2354,12 +2855,12 @@ impl Pallet { amount: BalanceOf, root: AccountIdLookupOf, nominator: AccountIdLookupOf, - state_toggler: AccountIdLookupOf, + bouncer: AccountIdLookupOf, pool_id: PoolId, ) -> DispatchResult { let root = T::Lookup::lookup(root)?; let nominator = T::Lookup::lookup(nominator)?; - let state_toggler = T::Lookup::lookup(state_toggler)?; + let bouncer = T::Lookup::lookup(bouncer)?; ensure!(amount >= Pallet::::depositor_min_bond(), Error::::MinimumBondNotMet); ensure!( @@ -2372,7 +2873,7 @@ impl Pallet { PoolRoles { root: Some(root), nominator: Some(nominator), - state_toggler: Some(state_toggler), + bouncer: Some(bouncer), depositor: who.clone(), }, ); @@ -2402,6 +2903,8 @@ impl Pallet { last_recorded_reward_counter: Zero::zero(), last_recorded_total_payouts: Zero::zero(), total_rewards_claimed: Zero::zero(), + total_commission_pending: Zero::zero(), + total_commission_claimed: Zero::zero(), }, ); ReversePoolIdLookup::::insert(bonded_pool.bonded_account(), pool_id); @@ -2419,6 +2922,113 @@ impl Pallet { Ok(()) } + fn do_bond_extra( + signer: T::AccountId, + who: T::AccountId, + extra: BondExtra>, + ) -> DispatchResult { + if signer != who { + ensure!( + ClaimPermissions::::get(&who).can_bond_extra(), + Error::::DoesNotHavePermission + ); + ensure!(extra == BondExtra::Rewards, Error::::BondExtraRestricted); + } + + let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&who)?; + + // payout related stuff: we must claim the payouts, and updated recorded payout data + // before updating the bonded pool points, similar to that of `join` transaction. + reward_pool.update_records( + bonded_pool.id, + bonded_pool.points, + bonded_pool.commission.current(), + )?; + let claimed = + Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; + + let (points_issued, bonded) = match extra { + BondExtra::FreeBalance(amount) => + (bonded_pool.try_bond_funds(&who, amount, BondType::Later)?, amount), + BondExtra::Rewards => + (bonded_pool.try_bond_funds(&who, claimed, BondType::Later)?, claimed), + }; + + bonded_pool.ok_to_be_open()?; + member.points = + member.points.checked_add(&points_issued).ok_or(Error::::OverflowRisk)?; + + Self::deposit_event(Event::::Bonded { + member: who.clone(), + pool_id: member.pool_id, + bonded, + joined: false, + }); + Self::put_member_with_pools(&who, member, bonded_pool, reward_pool); + + Ok(()) + } + + fn do_claim_commission(who: T::AccountId, pool_id: PoolId) -> DispatchResult { + let bonded_pool = BondedPool::::get(pool_id).ok_or(Error::::PoolNotFound)?; + ensure!(bonded_pool.can_manage_commission(&who), Error::::DoesNotHavePermission); + + let mut reward_pool = RewardPools::::get(pool_id) + .defensive_ok_or::>(DefensiveError::RewardPoolNotFound.into())?; + + // IMPORTANT: make sure that any newly pending commission not yet processed is added to + // `total_commission_pending`. + reward_pool.update_records( + pool_id, + bonded_pool.points, + bonded_pool.commission.current(), + )?; + + let commission = reward_pool.total_commission_pending; + ensure!(!commission.is_zero(), Error::::NoPendingCommission); + + let payee = bonded_pool + .commission + .current + .as_ref() + .map(|(_, p)| p.clone()) + .ok_or(Error::::NoCommissionCurrentSet)?; + + // Payout claimed commission. + T::Currency::transfer( + &bonded_pool.reward_account(), + &payee, + commission, + ExistenceRequirement::KeepAlive, + )?; + + // Add pending commission to total claimed counter. + reward_pool.total_commission_claimed = + reward_pool.total_commission_claimed.saturating_add(commission); + // Reset total pending commission counter to zero. + reward_pool.total_commission_pending = Zero::zero(); + // Commit reward pool updates + RewardPools::::insert(pool_id, reward_pool); + + Self::deposit_event(Event::::PoolCommissionClaimed { pool_id, commission }); + Ok(()) + } + + fn do_claim_payout(signer: T::AccountId, who: T::AccountId) -> DispatchResult { + if signer != who { + ensure!( + ClaimPermissions::::get(&who).can_claim_payout(), + Error::::DoesNotHavePermission + ); + } + let (mut member, mut bonded_pool, mut reward_pool) = Self::get_member_with_pools(&who)?; + + let _ = Self::do_reward_payout(&who, &mut member, &mut bonded_pool, &mut reward_pool)?; + + Self::put_member_with_pools(&who, member, bonded_pool, reward_pool); + Ok(()) + } + /// Ensure the correctness of the state of this pallet. /// /// This should be valid before or after each state transition of this pallet. @@ -2455,7 +3065,7 @@ impl Pallet { /// multiple `level`s, where the higher the level, the more checks we performs. So, /// `try_state(255)` is the strongest sanity check, and `0` performs no checks. #[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))] - pub fn do_try_state(level: u8) -> Result<(), &'static str> { + pub fn do_try_state(level: u8) -> Result<(), TryRuntimeError> { if level.is_zero() { return Ok(()) } @@ -2463,12 +3073,24 @@ impl Pallet { // result in the same set of keys, in the same order. let bonded_pools = BondedPools::::iter_keys().collect::>(); let reward_pools = RewardPools::::iter_keys().collect::>(); - assert_eq!(bonded_pools, reward_pools); + ensure!( + bonded_pools == reward_pools, + "`BondedPools` and `RewardPools` must all have the EXACT SAME key-set." + ); - assert!(Metadata::::iter_keys().all(|k| bonded_pools.contains(&k))); - assert!(SubPoolsStorage::::iter_keys().all(|k| bonded_pools.contains(&k))); + ensure!( + SubPoolsStorage::::iter_keys().all(|k| bonded_pools.contains(&k)), + "`SubPoolsStorage` must be a subset of the above superset." + ); + ensure!( + Metadata::::iter_keys().all(|k| bonded_pools.contains(&k)), + "`Metadata` keys must be a subset of the above superset." + ); - assert!(MaxPools::::get().map_or(true, |max| bonded_pools.len() <= (max as usize))); + ensure!( + MaxPools::::get().map_or(true, |max| bonded_pools.len() <= (max as usize)), + Error::::MaxPools + ); for id in reward_pools { let account = Self::create_reward_account(id); @@ -2488,55 +3110,71 @@ impl Pallet { let mut pools_members = BTreeMap::::new(); let mut pools_members_pending_rewards = BTreeMap::>::new(); let mut all_members = 0u32; - PoolMembers::::iter().for_each(|(_, d)| { + PoolMembers::::iter().try_for_each(|(_, d)| -> Result<(), TryRuntimeError> { let bonded_pool = BondedPools::::get(d.pool_id).unwrap(); - assert!(!d.total_points().is_zero(), "no member should have zero points: {:?}", d); + ensure!(!d.total_points().is_zero(), "No member should have zero points"); *pools_members.entry(d.pool_id).or_default() += 1; all_members += 1; let reward_pool = RewardPools::::get(d.pool_id).unwrap(); if !bonded_pool.points.is_zero() { - let current_rc = - reward_pool.current_reward_counter(d.pool_id, bonded_pool.points).unwrap(); - *pools_members_pending_rewards.entry(d.pool_id).or_default() += - d.pending_rewards(current_rc).unwrap(); + let commission = bonded_pool.commission.current(); + let (current_rc, _) = reward_pool + .current_reward_counter(d.pool_id, bonded_pool.points, commission) + .unwrap(); + let pending_rewards = d.pending_rewards(current_rc).unwrap(); + *pools_members_pending_rewards.entry(d.pool_id).or_default() += pending_rewards; } // else this pool has been heavily slashed and cannot have any rewards anymore. - }); - RewardPools::::iter_keys().for_each(|id| { + Ok(()) + })?; + + RewardPools::::iter_keys().try_for_each(|id| -> Result<(), TryRuntimeError> { // the sum of the pending rewards must be less than the leftover balance. Since the // reward math rounds down, we might accumulate some dust here. - log!( - trace, - "pool {:?}, sum pending rewards = {:?}, remaining balance = {:?}", - id, - pools_members_pending_rewards.get(&id), - RewardPool::::current_balance(id) + let pending_rewards_lt_leftover_bal = RewardPool::::current_balance(id) >= + pools_members_pending_rewards.get(&id).copied().unwrap_or_default(); + if !pending_rewards_lt_leftover_bal { + log::warn!( + "pool {:?}, sum pending rewards = {:?}, remaining balance = {:?}", + id, + pools_members_pending_rewards.get(&id), + RewardPool::::current_balance(id) + ); + } + ensure!( + pending_rewards_lt_leftover_bal, + "The sum of the pending rewards must be less than the leftover balance." ); - assert!( - RewardPool::::current_balance(id) >= - pools_members_pending_rewards.get(&id).map(|x| *x).unwrap_or_default() - ) - }); + Ok(()) + })?; - BondedPools::::iter().for_each(|(id, inner)| { + BondedPools::::iter().try_for_each(|(id, inner)| -> Result<(), TryRuntimeError> { let bonded_pool = BondedPool { id, inner }; - assert_eq!( - pools_members.get(&id).map(|x| *x).unwrap_or_default(), - bonded_pool.member_counter + ensure!( + pools_members.get(&id).copied().unwrap_or_default() == + bonded_pool.member_counter, + "Each `BondedPool.member_counter` must be equal to the actual count of members of this pool" + ); + ensure!( + MaxPoolMembersPerPool::::get() + .map_or(true, |max| bonded_pool.member_counter <= max), + Error::::MaxPoolMembers ); - assert!(MaxPoolMembersPerPool::::get() - .map_or(true, |max| bonded_pool.member_counter <= max)); let depositor = PoolMembers::::get(&bonded_pool.roles.depositor).unwrap(); - assert!( + ensure!( bonded_pool.is_destroying_and_only_depositor(depositor.active_points()) || depositor.active_points() >= MinCreateBond::::get(), "depositor must always have MinCreateBond stake in the pool, except for when the \ pool is being destroyed and the depositor is the last member", ); - }); - assert!(MaxPoolMembers::::get().map_or(true, |max| all_members <= max)); + Ok(()) + })?; + ensure!( + MaxPoolMembers::::get().map_or(true, |max| all_members <= max), + Error::::MaxPoolMembers + ); if level <= 1 { return Ok(()) @@ -2579,7 +3217,52 @@ impl Pallet { } } -impl OnStakerSlash> for Pallet { +impl Pallet { + /// Returns the pending rewards for the specified `who` account. + /// + /// In the case of error, `None` is returned. Used by runtime API. + pub fn api_pending_rewards(who: T::AccountId) -> Option> { + if let Some(pool_member) = PoolMembers::::get(who) { + if let Some((reward_pool, bonded_pool)) = RewardPools::::get(pool_member.pool_id) + .zip(BondedPools::::get(pool_member.pool_id)) + { + let commission = bonded_pool.commission.current(); + let (current_reward_counter, _) = reward_pool + .current_reward_counter(pool_member.pool_id, bonded_pool.points, commission) + .ok()?; + return pool_member.pending_rewards(current_reward_counter).ok() + } + } + + None + } + + /// Returns the points to balance conversion for a specified pool. + /// + /// If the pool ID does not exist, it returns 0 ratio points to balance. Used by runtime API. + pub fn api_points_to_balance(pool_id: PoolId, points: BalanceOf) -> BalanceOf { + if let Some(pool) = BondedPool::::get(pool_id) { + pool.points_to_balance(points) + } else { + Zero::zero() + } + } + + /// Returns the equivalent `new_funds` balance to point conversion for a specified pool. + /// + /// If the pool ID does not exist, returns 0 ratio balance to points. Used by runtime API. + pub fn api_balance_to_points(pool_id: PoolId, new_funds: BalanceOf) -> BalanceOf { + if let Some(pool) = BondedPool::::get(pool_id) { + let bonded_balance = + T::Staking::active_stake(&pool.bonded_account()).unwrap_or(Zero::zero()); + Pallet::::balance_to_point(bonded_balance, pool.points, new_funds) + } else { + Zero::zero() + } + } +} + +impl sp_staking::OnStakingUpdate> for Pallet { fn on_slash( pool_account: &T::AccountId, // Bonded balance is always read directly from staking, therefore we don't need to update diff --git a/frame/nomination-pools/src/migration.rs b/frame/nomination-pools/src/migration.rs index b73141c95f72..2ae4cd1b8685 100644 --- a/frame/nomination-pools/src/migration.rs +++ b/frame/nomination-pools/src/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +20,9 @@ use crate::log; use frame_support::traits::OnRuntimeUpgrade; use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + pub mod v1 { use super::*; @@ -28,7 +31,7 @@ pub mod v1 { pub depositor: AccountId, pub root: AccountId, pub nominator: AccountId, - pub state_toggler: AccountId, + pub bouncer: AccountId, } impl OldPoolRoles { @@ -37,7 +40,7 @@ pub mod v1 { depositor: self.depositor, root: Some(self.root), nominator: Some(self.nominator), - state_toggler: Some(self.state_toggler), + bouncer: Some(self.bouncer), } } } @@ -52,9 +55,12 @@ pub mod v1 { impl OldBondedPoolInner { fn migrate_to_v1(self) -> BondedPoolInner { + // Note: `commission` field not introduced to `BondedPoolInner` until + // migration 4. BondedPoolInner { - member_counter: self.member_counter, points: self.points, + commission: Commission::default(), + member_counter: self.member_counter, state: self.state, roles: self.roles.migrate_to_v1(), } @@ -63,7 +69,7 @@ pub mod v1 { /// Trivial migration which makes the roles of each pool optional. /// - /// Note: The depositor is not optional since he can never change. + /// Note: The depositor is not optional since they can never change. pub struct MigrateToV1(sp_std::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV1 { fn on_runtime_upgrade() -> Weight { @@ -97,9 +103,12 @@ pub mod v1 { } #[cfg(feature = "try-runtime")] - fn post_upgrade(_: Vec) -> Result<(), &'static str> { + fn post_upgrade(_: Vec) -> Result<(), TryRuntimeError> { // new version must be set. - assert_eq!(Pallet::::on_chain_storage_version(), 1); + ensure!( + Pallet::::on_chain_storage_version() == 1, + "The onchain version must be updated after the migration." + ); Pallet::::try_state(frame_system::Pallet::::block_number())?; Ok(()) } @@ -307,6 +316,8 @@ pub mod v2 { last_recorded_reward_counter: Zero::zero(), last_recorded_total_payouts: Zero::zero(), total_rewards_claimed: Zero::zero(), + total_commission_claimed: Zero::zero(), + total_commission_pending: Zero::zero(), }) }, ); @@ -347,38 +358,47 @@ pub mod v2 { } #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { + fn pre_upgrade() -> Result, TryRuntimeError> { // all reward accounts must have more than ED. - RewardPools::::iter().for_each(|(id, _)| { - assert!( + RewardPools::::iter().try_for_each(|(id, _)| -> Result<(), TryRuntimeError> { + ensure!( T::Currency::free_balance(&Pallet::::create_reward_account(id)) >= - T::Currency::minimum_balance() - ) - }); + T::Currency::minimum_balance(), + "Reward accounts must have greater balance than ED." + ); + Ok(()) + })?; Ok(Vec::new()) } #[cfg(feature = "try-runtime")] - fn post_upgrade(_: Vec) -> Result<(), &'static str> { + fn post_upgrade(_: Vec) -> Result<(), TryRuntimeError> { // new version must be set. - assert_eq!(Pallet::::on_chain_storage_version(), 2); + ensure!( + Pallet::::on_chain_storage_version() == 2, + "The onchain version must be updated after the migration." + ); // no reward or bonded pool has been skipped. - assert_eq!(RewardPools::::iter().count() as u32, RewardPools::::count()); - assert_eq!(BondedPools::::iter().count() as u32, BondedPools::::count()); + ensure!( + RewardPools::::iter().count() as u32 == RewardPools::::count(), + "The count of reward pools must remain the same after the migration." + ); + ensure!( + BondedPools::::iter().count() as u32 == BondedPools::::count(), + "The count of reward pools must remain the same after the migration." + ); // all reward pools must have exactly ED in them. This means no reward can be claimed, // and that setting reward counters all over the board to zero will work henceforth. - RewardPools::::iter().for_each(|(id, _)| { - assert_eq!( - RewardPool::::current_balance(id), - Zero::zero(), - "reward pool({}) balance is {:?}", - id, - RewardPool::::current_balance(id) + RewardPools::::iter().try_for_each(|(id, _)| -> Result<(), TryRuntimeError> { + ensure!( + RewardPool::::current_balance(id) == Zero::zero(), + "Reward pool balance must be zero.", ); - }); + Ok(()) + })?; log!(info, "post upgrade hook for MigrateToV2 executed."); Ok(()) @@ -396,14 +416,14 @@ pub mod v3 { let current = Pallet::::current_storage_version(); let onchain = Pallet::::on_chain_storage_version(); - log!( - info, - "Running migration with current storage version {:?} / onchain {:?}", - current, - onchain - ); + if onchain == 2 { + log!( + info, + "Running migration with current storage version {:?} / onchain {:?}", + current, + onchain + ); - if current > onchain { let mut metadata_iterated = 0u64; let mut metadata_removed = 0u64; Metadata::::iter_keys() @@ -417,7 +437,7 @@ pub mod v3 { metadata_removed += 1; Metadata::::remove(&id); }); - current.put::>(); + StorageVersion::new(3).put::>(); // metadata iterated + bonded pools read + a storage version read let total_reads = metadata_iterated * 2 + 1; // metadata removed + a storage version write @@ -430,21 +450,277 @@ pub mod v3 { } #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - ensure!( - Pallet::::current_storage_version() > Pallet::::on_chain_storage_version(), - "the on_chain version is equal or more than the current one" - ); + fn pre_upgrade() -> Result, TryRuntimeError> { Ok(Vec::new()) } #[cfg(feature = "try-runtime")] - fn post_upgrade(_: Vec) -> Result<(), &'static str> { + fn post_upgrade(_: Vec) -> Result<(), TryRuntimeError> { ensure!( Metadata::::iter_keys().all(|id| BondedPools::::contains_key(&id)), "not all of the stale metadata has been removed" ); - ensure!(Pallet::::on_chain_storage_version() == 3, "wrong storage version"); + ensure!( + Pallet::::on_chain_storage_version() >= 3, + "nomination-pools::migration::v3: wrong storage version" + ); + Ok(()) + } + } +} + +pub mod v4 { + use super::*; + + #[derive(Decode)] + pub struct OldBondedPoolInner { + pub points: BalanceOf, + pub state: PoolState, + pub member_counter: u32, + pub roles: PoolRoles, + } + + impl OldBondedPoolInner { + fn migrate_to_v4(self) -> BondedPoolInner { + BondedPoolInner { + commission: Commission::default(), + member_counter: self.member_counter, + points: self.points, + state: self.state, + roles: self.roles, + } + } + } + + /// Migrates from `v3` directly to `v5` to avoid the broken `v4` migration. + #[allow(deprecated)] + pub type MigrateV3ToV5 = (v4::MigrateToV4, v5::MigrateToV5); + + /// # Warning + /// + /// To avoid mangled storage please use `MigrateV3ToV5` instead. + /// See: github.com/paritytech/substrate/pull/13715 + /// + /// This migration adds a `commission` field to every `BondedPoolInner`, if + /// any. + #[deprecated( + note = "To avoid mangled storage please use `MigrateV3ToV5` instead. See: github.com/paritytech/substrate/pull/13715" + )] + pub struct MigrateToV4(sp_std::marker::PhantomData<(T, U)>); + #[allow(deprecated)] + impl> OnRuntimeUpgrade for MigrateToV4 { + fn on_runtime_upgrade() -> Weight { + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + + log!( + info, + "Running migration with current storage version {:?} / onchain {:?}", + current, + onchain + ); + + if onchain == 3 { + log!(warn, "Please run MigrateToV5 immediately after this migration. See github.com/paritytech/substrate/pull/13715"); + let initial_global_max_commission = U::get(); + GlobalMaxCommission::::set(Some(initial_global_max_commission)); + log!( + info, + "Set initial global max commission to {:?}.", + initial_global_max_commission + ); + + let mut translated = 0u64; + BondedPools::::translate::, _>(|_key, old_value| { + translated.saturating_inc(); + Some(old_value.migrate_to_v4()) + }); + + StorageVersion::new(4).put::>(); + log!(info, "Upgraded {} pools, storage to version {:?}", translated, current); + + // reads: translated + onchain version. + // writes: translated + current.put + initial global commission. + T::DbWeight::get().reads_writes(translated + 1, translated + 2) + } else { + log!(info, "Migration did not execute. This probably should be removed"); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + Ok(Vec::new()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_: Vec) -> Result<(), TryRuntimeError> { + // ensure all BondedPools items now contain an `inner.commission: Commission` field. + ensure!( + BondedPools::::iter().all(|(_, inner)| + // Check current + (inner.commission.current.is_none() || + inner.commission.current.is_some()) && + // Check max + (inner.commission.max.is_none() || inner.commission.max.is_some()) && + // Check change_rate + (inner.commission.change_rate.is_none() || + inner.commission.change_rate.is_some()) && + // Check throttle_from + (inner.commission.throttle_from.is_none() || + inner.commission.throttle_from.is_some())), + "a commission value has not been set correctly" + ); + ensure!( + GlobalMaxCommission::::get() == Some(U::get()), + "global maximum commission error" + ); + ensure!( + Pallet::::on_chain_storage_version() >= 4, + "nomination-pools::migration::v4: wrong storage version" + ); + Ok(()) + } + } +} + +pub mod v5 { + use super::*; + + #[derive(Decode)] + pub struct OldRewardPool { + last_recorded_reward_counter: T::RewardCounter, + last_recorded_total_payouts: BalanceOf, + total_rewards_claimed: BalanceOf, + } + + impl OldRewardPool { + fn migrate_to_v5(self) -> RewardPool { + RewardPool { + last_recorded_reward_counter: self.last_recorded_reward_counter, + last_recorded_total_payouts: self.last_recorded_total_payouts, + total_rewards_claimed: self.total_rewards_claimed, + total_commission_pending: Zero::zero(), + total_commission_claimed: Zero::zero(), + } + } + } + + /// This migration adds `total_commission_pending` and `total_commission_claimed` field to every + /// `RewardPool`, if any. + pub struct MigrateToV5(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV5 { + fn on_runtime_upgrade() -> Weight { + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + + log!( + info, + "Running migration with current storage version {:?} / onchain {:?}", + current, + onchain + ); + + if current == 5 && onchain == 4 { + let mut translated = 0u64; + RewardPools::::translate::, _>(|_id, old_value| { + translated.saturating_inc(); + Some(old_value.migrate_to_v5()) + }); + + current.put::>(); + log!(info, "Upgraded {} pools, storage to version {:?}", translated, current); + + // reads: translated + onchain version. + // writes: translated + current.put. + T::DbWeight::get().reads_writes(translated + 1, translated + 1) + } else { + log!(info, "Migration did not execute. This probably should be removed"); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + let rpool_keys = RewardPools::::iter_keys().count(); + let rpool_values = RewardPools::::iter_values().count(); + if rpool_keys != rpool_values { + log!(info, "🔥 There are {} undecodable RewardPools in storage. This migration will try to correct them. keys: {}, values: {}", rpool_keys.saturating_sub(rpool_values), rpool_keys, rpool_values); + } + + ensure!( + PoolMembers::::iter_keys().count() == PoolMembers::::iter_values().count(), + "There are undecodable PoolMembers in storage. This migration will not fix that." + ); + ensure!( + BondedPools::::iter_keys().count() == BondedPools::::iter_values().count(), + "There are undecodable BondedPools in storage. This migration will not fix that." + ); + ensure!( + SubPoolsStorage::::iter_keys().count() == + SubPoolsStorage::::iter_values().count(), + "There are undecodable SubPools in storage. This migration will not fix that." + ); + ensure!( + Metadata::::iter_keys().count() == Metadata::::iter_values().count(), + "There are undecodable Metadata in storage. This migration will not fix that." + ); + + Ok((rpool_values as u64).encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(data: Vec) -> Result<(), TryRuntimeError> { + let old_rpool_values: u64 = Decode::decode(&mut &data[..]).unwrap(); + let rpool_keys = RewardPools::::iter_keys().count() as u64; + let rpool_values = RewardPools::::iter_values().count() as u64; + ensure!( + rpool_keys == rpool_values, + "There are STILL undecodable RewardPools - migration failed" + ); + + if old_rpool_values != rpool_values { + log!( + info, + "🎉 Fixed {} undecodable RewardPools.", + rpool_values.saturating_sub(old_rpool_values) + ); + } + + // ensure all RewardPools items now contain `total_commission_pending` and + // `total_commission_claimed` field. + ensure!( + RewardPools::::iter().all(|(_, reward_pool)| reward_pool + .total_commission_pending >= + Zero::zero() && reward_pool + .total_commission_claimed >= + Zero::zero()), + "a commission value has been incorrectly set" + ); + ensure!( + Pallet::::on_chain_storage_version() >= 5, + "nomination-pools::migration::v5: wrong storage version" + ); + + // These should not have been touched - just in case. + ensure!( + PoolMembers::::iter_keys().count() == PoolMembers::::iter_values().count(), + "There are undecodable PoolMembers in storage." + ); + ensure!( + BondedPools::::iter_keys().count() == BondedPools::::iter_values().count(), + "There are undecodable BondedPools in storage." + ); + ensure!( + SubPoolsStorage::::iter_keys().count() == + SubPoolsStorage::::iter_values().count(), + "There are undecodable SubPools in storage." + ); + ensure!( + Metadata::::iter_keys().count() == Metadata::::iter_values().count(), + "There are undecodable Metadata in storage." + ); + Ok(()) } } diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 99d521df3241..7d0d729a40d4 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -2,7 +2,7 @@ use super::*; use crate::{self as pools}; use frame_support::{assert_ok, parameter_types, PalletId}; use frame_system::RawOrigin; -use sp_runtime::FixedU128; +use sp_runtime::{BuildStorage, FixedU128}; use sp_staking::Stake; pub type BlockNumber = u64; @@ -47,6 +47,7 @@ impl StakingMock { impl sp_staking::StakingInterface for StakingMock { type Balance = Balance; type AccountId = AccountId; + type CurrencyToVote = (); fn minimum_nominator_bond() -> Self::Balance { StakingMinBond::get() @@ -67,6 +68,14 @@ impl sp_staking::StakingInterface for StakingMock { BondingDuration::get() } + fn status( + _: &Self::AccountId, + ) -> Result, DispatchError> { + Nominations::get() + .map(|noms| sp_staking::StakerStatus::Nominator(noms)) + .ok_or(DispatchError::Other("NotStash")) + } + fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { let mut x = BondedBalanceMap::get(); x.get_mut(who).map(|v| *v += extra); @@ -108,7 +117,7 @@ impl sp_staking::StakingInterface for StakingMock { } #[cfg(feature = "runtime-benchmarks")] - fn nominations(_: Self::AccountId) -> Option> { + fn nominations(_: &Self::AccountId) -> Option> { Nominations::get() } @@ -116,15 +125,15 @@ impl sp_staking::StakingInterface for StakingMock { unimplemented!("method currently not used in testing") } - fn stake(who: &Self::AccountId) -> Result, DispatchError> { + fn stake(who: &Self::AccountId) -> Result, DispatchError> { match ( - UnbondingBalanceMap::get().get(who).map(|v| *v), - BondedBalanceMap::get().get(who).map(|v| *v), + UnbondingBalanceMap::get().get(who).copied(), + BondedBalanceMap::get().get(who).copied(), ) { (None, None) => Err(DispatchError::Other("balance not found")), - (Some(v), None) => Ok(Stake { total: v, active: 0, stash: *who }), - (None, Some(v)) => Ok(Stake { total: v, active: v, stash: *who }), - (Some(a), Some(b)) => Ok(Stake { total: a + b, active: b, stash: *who }), + (Some(v), None) => Ok(Stake { total: v, active: 0 }), + (None, Some(v)) => Ok(Stake { total: v, active: v }), + (Some(a), Some(b)) => Ok(Stake { total: a + b, active: b }), } } @@ -159,14 +168,13 @@ impl frame_system::Config for Runtime { type SS58Prefix = (); type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = BlockNumber; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = sp_core::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = sp_runtime::traits::IdentityLookup; - type Header = sp_runtime::testing::Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type DbWeight = (); @@ -196,6 +204,10 @@ impl pallet_balances::Config for Runtime { type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } pub struct BalanceToU256; @@ -233,15 +245,11 @@ impl pools::Config for Runtime { type MaxPointsToBalance = frame_support::traits::ConstU8<10>; } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub struct Runtime { - System: frame_system::{Pallet, Call, Storage, Event, Config}, + System: frame_system::{Pallet, Call, Storage, Event, Config}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Pools: pools::{Pallet, Call, Storage, Event}, } @@ -251,11 +259,17 @@ pub struct ExtBuilder { members: Vec<(AccountId, Balance)>, max_members: Option, max_members_per_pool: Option, + global_max_commission: Option, } impl Default for ExtBuilder { fn default() -> Self { - Self { members: Default::default(), max_members: Some(4), max_members_per_pool: Some(3) } + Self { + members: Default::default(), + max_members: Some(4), + max_members_per_pool: Some(3), + global_max_commission: Some(Perbill::from_percent(90)), + } } } @@ -297,10 +311,15 @@ impl ExtBuilder { self } + pub fn global_max_commission(mut self, commission: Option) -> Self { + self.global_max_commission = commission; + self + } + pub fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); let mut storage = - frame_system::GenesisConfig::default().build_storage::().unwrap(); + frame_system::GenesisConfig::::default().build_storage().unwrap(); let _ = crate::GenesisConfig:: { min_join_bond: MinJoinBondConfig::get(), @@ -308,6 +327,7 @@ impl ExtBuilder { max_pools: Some(2), max_members_per_pool: self.max_members_per_pool, max_members: self.max_members, + global_max_commission: self.global_max_commission, } .assimilate_storage(&mut storage); @@ -332,7 +352,7 @@ impl ExtBuilder { ext } - pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + pub fn build_and_execute(self, test: impl FnOnce()) { self.build().execute_with(|| { test(); Pools::do_try_state(CheckLevel::get()).unwrap(); @@ -354,6 +374,23 @@ parameter_types! { storage BalancesEvents: u32 = 0; } +/// Helper to run a specified amount of blocks. +pub fn run_blocks(n: u64) { + let current_block = System::block_number(); + run_to_block(n + current_block); +} + +/// Helper to run to a specific block. +pub fn run_to_block(n: u64) { + let current_block = System::block_number(); + assert!(n > current_block); + while System::block_number() < n { + Pools::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + Pools::on_initialize(System::block_number()); + } +} + /// All events of this pallet. pub fn pool_events_since_last_call() -> Vec> { let events = System::events() @@ -380,7 +417,7 @@ pub fn balances_events_since_last_call() -> Vec> /// Same as `fully_unbond`, in permissioned setting. pub fn fully_unbond_permissioned(member: AccountId) -> DispatchResult { - let points = PoolMembers::::get(&member) + let points = PoolMembers::::get(member) .map(|d| d.active_points()) .unwrap_or_default(); Pools::unbond(RuntimeOrigin::signed(member), member, points) diff --git a/frame/nomination-pools/src/tests.rs b/frame/nomination-pools/src/tests.rs index 7d5d418bbf2c..ac8fa5c4dbc0 100644 --- a/frame/nomination-pools/src/tests.rs +++ b/frame/nomination-pools/src/tests.rs @@ -1,25 +1,25 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at +// 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 +// 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. +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. use super::*; use crate::{mock::*, Event}; use frame_support::{assert_err, assert_noop, assert_ok, assert_storage_noop, bounded_btree_map}; use pallet_balances::Event as BEvent; -use sp_runtime::traits::Dispatchable; +use sp_runtime::{traits::Dispatchable, FixedU128}; macro_rules! unbonding_pools_with_era { ($($k:expr => $v:expr),* $(,)?) => {{ @@ -37,7 +37,17 @@ macro_rules! member_unbonding_eras { } pub const DEFAULT_ROLES: PoolRoles = - PoolRoles { depositor: 10, root: Some(900), nominator: Some(901), state_toggler: Some(902) }; + PoolRoles { depositor: 10, root: Some(900), nominator: Some(901), bouncer: Some(902) }; + +fn deposit_rewards(r: u128) { + let b = Balances::free_balance(&default_reward_account()).checked_add(r).unwrap(); + Balances::make_free_balance_be(&default_reward_account(), b); +} + +fn remove_rewards(r: u128) { + let b = Balances::free_balance(&default_reward_account()).checked_sub(r).unwrap(); + Balances::make_free_balance_be(&default_reward_account(), b); +} #[test] fn test_setup_works() { @@ -55,10 +65,11 @@ fn test_setup_works() { BondedPool:: { id: last_pool, inner: BondedPoolInner { - state: PoolState::Open, - points: 10, + commission: Commission::default(), member_counter: 1, - roles: DEFAULT_ROLES + points: 10, + roles: DEFAULT_ROLES, + state: PoolState::Open, }, } ); @@ -67,7 +78,9 @@ fn test_setup_works() { RewardPool:: { last_recorded_reward_counter: Zero::zero(), last_recorded_total_payouts: 0, - total_rewards_claimed: 0 + total_rewards_claimed: 0, + total_commission_claimed: 0, + total_commission_pending: 0, } ); assert_eq!( @@ -98,10 +111,11 @@ mod bonded_pool { let mut bonded_pool = BondedPool:: { id: 123123, inner: BondedPoolInner { - state: PoolState::Open, - points: 100, + commission: Commission::default(), member_counter: 1, + points: 100, roles: DEFAULT_ROLES, + state: PoolState::Open, }, }; @@ -153,10 +167,11 @@ mod bonded_pool { let mut bonded_pool = BondedPool:: { id: 123123, inner: BondedPoolInner { - state: PoolState::Open, - points: 100, + commission: Commission::default(), member_counter: 1, + points: 100, roles: DEFAULT_ROLES, + state: PoolState::Open, }, }; @@ -195,16 +210,57 @@ mod bonded_pool { }) } + #[test] + fn api_points_to_balance_works() { + ExtBuilder::default().build_and_execute(|| { + assert!(BondedPool::::get(1).is_some()); + assert_eq!(Pallet::::api_points_to_balance(1, 10), 10); + + // slash half of the pool's balance. expected result of `fn api_points_to_balance` + // to be 1/2 of the pool's balance. + StakingMock::set_bonded_balance( + default_bonded_account(), + Pools::depositor_min_bond() / 2, + ); + assert_eq!(Pallet::::api_points_to_balance(1, 10), 5); + + // if pool does not exist, points to balance ratio is 0. + assert_eq!(BondedPool::::get(2), None); + assert_eq!(Pallet::::api_points_to_balance(2, 10), 0); + }) + } + + #[test] + fn api_balance_to_points_works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(Pallet::::api_balance_to_points(1, 0), 0); + assert_eq!(Pallet::::api_balance_to_points(1, 10), 10); + + // slash half of the pool's balance. expect result of `fn api_balance_to_points` + // to be 2 * of the balance to add to the pool. + StakingMock::set_bonded_balance( + default_bonded_account(), + Pools::depositor_min_bond() / 2, + ); + assert_eq!(Pallet::::api_balance_to_points(1, 10), 20); + + // if pool does not exist, balance to points ratio is 0. + assert_eq!(BondedPool::::get(2), None); + assert_eq!(Pallet::::api_points_to_balance(2, 10), 0); + }) + } + #[test] fn ok_to_join_with_works() { ExtBuilder::default().build_and_execute(|| { let pool = BondedPool:: { id: 123, inner: BondedPoolInner { - state: PoolState::Open, - points: 100, + commission: Commission::default(), member_counter: 1, + points: 100, roles: DEFAULT_ROLES, + state: PoolState::Open, }, }; @@ -218,7 +274,7 @@ mod bonded_pool { // Simulate a slashed pool at `MaxPointsToBalance` + 1 slashed pool StakingMock::set_bonded_balance( pool.bonded_account(), - max_points_to_balance.saturating_add(1).into(), + max_points_to_balance.saturating_add(1), ); assert_ok!(pool.ok_to_join()); @@ -423,6 +479,8 @@ mod sub_pools { } mod join { + use sp_runtime::TokenError; + use super::*; #[test] @@ -430,16 +488,17 @@ mod join { let bonded = |points, member_counter| BondedPool:: { id: 1, inner: BondedPoolInner { - state: PoolState::Open, - points, + commission: Commission::default(), member_counter, + points, roles: DEFAULT_ROLES, + state: PoolState::Open, }, }; ExtBuilder::default().with_check(0).build_and_execute(|| { // Given Balances::make_free_balance_be(&11, ExistentialDeposit::get() + 2); - assert!(!PoolMembers::::contains_key(&11)); + assert!(!PoolMembers::::contains_key(11)); // When assert_ok!(Pools::join(RuntimeOrigin::signed(11), 2, 1)); @@ -456,7 +515,7 @@ mod join { ); assert_eq!( - PoolMembers::::get(&11).unwrap(), + PoolMembers::::get(11).unwrap(), PoolMember:: { pool_id: 1, points: 2, ..Default::default() } ); assert_eq!(BondedPool::::get(1).unwrap(), bonded(12, 2)); @@ -467,7 +526,7 @@ mod join { // And Balances::make_free_balance_be(&12, ExistentialDeposit::get() + 12); - assert!(!PoolMembers::::contains_key(&12)); + assert!(!PoolMembers::::contains_key(12)); // When assert_ok!(Pools::join(RuntimeOrigin::signed(12), 12, 1)); @@ -479,7 +538,7 @@ mod join { ); assert_eq!( - PoolMembers::::get(&12).unwrap(), + PoolMembers::::get(12).unwrap(), PoolMember:: { pool_id: 1, points: 24, ..Default::default() } ); assert_eq!(BondedPool::::get(1).unwrap(), bonded(12 + 24, 3)); @@ -490,7 +549,7 @@ mod join { fn join_errors_correctly() { ExtBuilder::default().with_check(0).build_and_execute(|| { // 10 is already part of the default pool created. - assert_eq!(PoolMembers::::get(&10).unwrap().pool_id, 1); + assert_eq!(PoolMembers::::get(10).unwrap().pool_id, 1); assert_noop!( Pools::join(RuntimeOrigin::signed(10), 420, 123), @@ -513,10 +572,11 @@ mod join { BondedPool:: { id: 123, inner: BondedPoolInner { + commission: Commission::default(), member_counter: 1, - state: PoolState::Open, points: 100, roles: DEFAULT_ROLES, + state: PoolState::Open, }, } .put(); @@ -544,7 +604,7 @@ mod join { // Balance needs to be gt Balance::MAX / `MaxPointsToBalance` assert_noop!( Pools::join(RuntimeOrigin::signed(11), 5, 123), - pallet_balances::Error::::InsufficientBalance, + TokenError::FundsUnavailable, ); StakingMock::set_bonded_balance(Pools::create_bonded_account(1), max_points_to_balance); @@ -582,10 +642,11 @@ mod join { BondedPool:: { id: 123, inner: BondedPoolInner { - state: PoolState::Open, - points: 100, + commission: Commission::default(), member_counter: 1, + points: 100, roles: DEFAULT_ROLES, + state: PoolState::Open, }, } .put(); @@ -682,6 +743,8 @@ mod claim_payout { last_recorded_reward_counter: last_recorded_reward_counter.into(), last_recorded_total_payouts, total_rewards_claimed, + total_commission_claimed: 0, + total_commission_pending: 0, } } @@ -698,7 +761,7 @@ mod claim_payout { // and the reward pool has earned 100 in rewards assert_eq!(Balances::free_balance(default_reward_account()), ed); - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100)); + deposit_rewards(100); let _ = pool_events_since_last_call(); @@ -714,7 +777,7 @@ mod claim_payout { assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 1)); // pool's 'last_recorded_reward_counter' and 'last_recorded_total_payouts' don't // really change unless if someone bonds/unbonds. - assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 10)); + assert_eq!(RewardPools::::get(1).unwrap(), rew(0, 0, 10)); assert_eq!(Balances::free_balance(&10), 10); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 90); @@ -727,7 +790,7 @@ mod claim_payout { vec![Event::PaidOut { member: 40, pool_id: 1, payout: 40 }] ); assert_eq!(PoolMembers::::get(40).unwrap(), del(40, 1)); - assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 50)); + assert_eq!(RewardPools::::get(1).unwrap(), rew(0, 0, 50)); assert_eq!(Balances::free_balance(&40), 40); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 50); @@ -740,12 +803,12 @@ mod claim_payout { vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] ); assert_eq!(PoolMembers::::get(50).unwrap(), del(50, 1)); - assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 100)); + assert_eq!(RewardPools::::get(1).unwrap(), rew(0, 0, 100)); assert_eq!(Balances::free_balance(&50), 50); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); + assert_eq!(Balances::free_balance(&default_reward_account()), ed); // Given the reward pool has some new rewards - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); + deposit_rewards(50); // When assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); @@ -756,7 +819,7 @@ mod claim_payout { vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] ); assert_eq!(PoolMembers::::get(10).unwrap(), del_float(10, 1.5)); - assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 105)); + assert_eq!(RewardPools::::get(1).unwrap(), rew(0, 0, 105)); assert_eq!(Balances::free_balance(&10), 10 + 5); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 45); @@ -769,12 +832,12 @@ mod claim_payout { vec![Event::PaidOut { member: 40, pool_id: 1, payout: 20 }] ); assert_eq!(PoolMembers::::get(40).unwrap(), del_float(40, 1.5)); - assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 125)); + assert_eq!(RewardPools::::get(1).unwrap(), rew(0, 0, 125)); assert_eq!(Balances::free_balance(&40), 40 + 20); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); // Given del 50 hasn't claimed and the reward pools has just earned 50 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); + deposit_rewards(50); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 75); // When @@ -786,7 +849,7 @@ mod claim_payout { vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }] ); assert_eq!(PoolMembers::::get(50).unwrap(), del_float(50, 2.0)); - assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 175)); + assert_eq!(RewardPools::::get(1).unwrap(), rew(0, 0, 175)); assert_eq!(Balances::free_balance(&50), 50 + 50); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 25); @@ -799,12 +862,12 @@ mod claim_payout { vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }] ); assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 2)); - assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 180)); + assert_eq!(RewardPools::::get(1).unwrap(), rew(0, 0, 180)); assert_eq!(Balances::free_balance(&10), 15 + 5); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 20); // Given del 40 hasn't claimed and the reward pool has just earned 400 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400)); + deposit_rewards(400); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 420); // When @@ -818,12 +881,12 @@ mod claim_payout { // We expect a payout of 40 assert_eq!(PoolMembers::::get(10).unwrap(), del(10, 6)); - assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 220)); + assert_eq!(RewardPools::::get(1).unwrap(), rew(0, 0, 220)); assert_eq!(Balances::free_balance(&10), 20 + 40); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 380); // Given del 40 + del 50 haven't claimed and the reward pool has earned 20 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20)); + deposit_rewards(20); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 400); // When @@ -835,7 +898,7 @@ mod claim_payout { vec![Event::PaidOut { member: 10, pool_id: 1, payout: 2 }] ); assert_eq!(PoolMembers::::get(10).unwrap(), del_float(10, 6.2)); - assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 222)); + assert_eq!(RewardPools::::get(1).unwrap(), rew(0, 0, 222)); assert_eq!(Balances::free_balance(&10), 60 + 2); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 398); @@ -848,7 +911,7 @@ mod claim_payout { vec![Event::PaidOut { member: 40, pool_id: 1, payout: 188 }] ); assert_eq!(PoolMembers::::get(40).unwrap(), del_float(40, 6.2)); - assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 410)); + assert_eq!(RewardPools::::get(1).unwrap(), rew(0, 0, 410)); assert_eq!(Balances::free_balance(&40), 60 + 188); assert_eq!(Balances::free_balance(&default_reward_account()), ed + 210); @@ -861,9 +924,9 @@ mod claim_payout { vec![Event::PaidOut { member: 50, pool_id: 1, payout: 210 }] ); assert_eq!(PoolMembers::::get(50).unwrap(), del_float(50, 6.2)); - assert_eq!(RewardPools::::get(&1).unwrap(), rew(0, 0, 620)); + assert_eq!(RewardPools::::get(1).unwrap(), rew(0, 0, 620)); assert_eq!(Balances::free_balance(&50), 100 + 210); - assert_eq!(Balances::free_balance(&default_reward_account()), ed + 0); + assert_eq!(Balances::free_balance(&default_reward_account()), ed); }); } @@ -890,6 +953,56 @@ mod claim_payout { }); } + #[test] + fn claim_payout_bounds_commission_above_global() { + ExtBuilder::default().build_and_execute(|| { + let (mut member, bonded_pool, mut reward_pool) = + Pools::get_member_with_pools(&10).unwrap(); + + // top up commission payee account to existential deposit + let _ = Balances::deposit_creating(&2, 5); + + // Set a commission pool 1 to 75%, with a payee set to `2` + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + bonded_pool.id, + Some((Perbill::from_percent(75), 2)), + )); + + // re-introduce the global maximum to 50% - 25% lower than the current commission of the + // pool. + GlobalMaxCommission::::set(Some(Perbill::from_percent(50))); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(75), 2)) + } + ] + ); + + // The pool earns 10 points + deposit_rewards(10); + + assert_ok!(Pools::do_reward_payout( + &10, + &mut member, + &mut BondedPool::::get(1).unwrap(), + &mut reward_pool + )); + + // commission applied is 50%, not 75%. Has been bounded by `GlobalMaxCommission`. + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 },] + ); + }) + } + #[test] fn do_reward_payout_works_with_a_pool_of_1() { let del = |last_recorded_reward_counter| del_float(10, last_recorded_reward_counter); @@ -909,7 +1022,7 @@ mod claim_payout { assert_eq!(reward_pool, rew(0, 0, 0)); // Given the pool has earned some rewards for the first time - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 5)); + deposit_rewards(5); // When let payout = @@ -930,7 +1043,7 @@ mod claim_payout { assert_eq!(member, del(0.5)); // Given the pool has earned rewards again - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 10)); + deposit_rewards(10); // When let payout = @@ -947,7 +1060,7 @@ mod claim_payout { assert_eq!(member, del(1.5)); // Given the pool has earned no new rewards - Balances::make_free_balance_be(&default_reward_account(), ed + 0); + Balances::make_free_balance_be(&default_reward_account(), ed); // When let payout = @@ -989,7 +1102,7 @@ mod claim_payout { assert_eq!(bonded_pool.points, 100); // and the reward pool has earned 100 in rewards - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 100)); + deposit_rewards(100); // When let payout = @@ -1034,7 +1147,7 @@ mod claim_payout { assert_eq!(reward_pool, rew(0, 0, 100)); // Given the reward pool has some new rewards - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); + deposit_rewards(50); // When let payout = @@ -1065,7 +1178,7 @@ mod claim_payout { assert_eq!(reward_pool, rew(0, 0, 125)); // Given del_50 hasn't claimed and the reward pools has just earned 50 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 50)); + deposit_rewards(50); // When let payout = @@ -1096,7 +1209,7 @@ mod claim_payout { assert_eq!(reward_pool, rew(0, 0, 180)); // Given del_40 hasn't claimed and the reward pool has just earned 400 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 400)); + deposit_rewards(400); // When let payout = @@ -1113,7 +1226,7 @@ mod claim_payout { assert_eq!(reward_pool, rew(0, 0, 220)); // Given del_40 + del_50 haven't claimed and the reward pool has earned 20 - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += 20)); + deposit_rewards(20); // When let payout = @@ -1151,14 +1264,14 @@ mod claim_payout { fn rewards_distribution_is_fair_basic() { ExtBuilder::default().build_and_execute(|| { // reward pool by 10. - Balances::mutate_account(&default_reward_account(), |f| f.free += 10).unwrap(); + deposit_rewards(10); // 20 joins afterwards. Balances::make_free_balance_be(&20, Balances::minimum_balance() + 10); assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); // reward by another 20 - Balances::mutate_account(&default_reward_account(), |f| f.free += 20).unwrap(); + deposit_rewards(20); // 10 should claim 10 + 10, 20 should claim 20 / 2. assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); @@ -1175,7 +1288,7 @@ mod claim_payout { ); // any upcoming rewards are shared equally. - Balances::mutate_account(&default_reward_account(), |f| f.free += 20).unwrap(); + deposit_rewards(20); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); @@ -1195,12 +1308,12 @@ mod claim_payout { // basically checks the case where the amount of rewards is less than the pool shares. for // this, we have to rely on fixed point arithmetic. ExtBuilder::default().build_and_execute(|| { - Balances::mutate_account(&default_reward_account(), |f| f.free += 3).unwrap(); + deposit_rewards(3); Balances::make_free_balance_be(&20, Balances::minimum_balance() + 10); assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); - Balances::mutate_account(&default_reward_account(), |f| f.free += 6).unwrap(); + deposit_rewards(6); // 10 should claim 3, 20 should claim 3 + 3. assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); @@ -1218,7 +1331,7 @@ mod claim_payout { ); // any upcoming rewards are shared equally. - Balances::mutate_account(&default_reward_account(), |f| f.free += 8).unwrap(); + deposit_rewards(8); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); @@ -1232,7 +1345,7 @@ mod claim_payout { ); // uneven upcoming rewards are shared equally, rounded down. - Balances::mutate_account(&default_reward_account(), |f| f.free += 7).unwrap(); + deposit_rewards(7); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); @@ -1252,17 +1365,17 @@ mod claim_payout { ExtBuilder::default().build_and_execute(|| { let ed = Balances::minimum_balance(); - Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + deposit_rewards(30); Balances::make_free_balance_be(&20, ed + 10); assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); - Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap(); + deposit_rewards(100); Balances::make_free_balance_be(&30, ed + 10); assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1)); - Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + deposit_rewards(60); // 10 should claim 10, 20 should claim nothing. assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); @@ -1283,7 +1396,7 @@ mod claim_payout { ); // any upcoming rewards are shared equally. - Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + deposit_rewards(30); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); @@ -1305,51 +1418,51 @@ mod claim_payout { ExtBuilder::default().build_and_execute(|| { let ed = Balances::minimum_balance(); - assert_eq!(Pools::pending_rewards(10), Some(0)); - Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); - assert_eq!(Pools::pending_rewards(10), Some(30)); - assert_eq!(Pools::pending_rewards(20), None); + assert_eq!(Pools::api_pending_rewards(10), Some(0)); + deposit_rewards(30); + assert_eq!(Pools::api_pending_rewards(10), Some(30)); + assert_eq!(Pools::api_pending_rewards(20), None); Balances::make_free_balance_be(&20, ed + 10); assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1)); - assert_eq!(Pools::pending_rewards(10), Some(30)); - assert_eq!(Pools::pending_rewards(20), Some(0)); + assert_eq!(Pools::api_pending_rewards(10), Some(30)); + assert_eq!(Pools::api_pending_rewards(20), Some(0)); - Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap(); + deposit_rewards(100); - assert_eq!(Pools::pending_rewards(10), Some(30 + 50)); - assert_eq!(Pools::pending_rewards(20), Some(50)); - assert_eq!(Pools::pending_rewards(30), None); + assert_eq!(Pools::api_pending_rewards(10), Some(30 + 50)); + assert_eq!(Pools::api_pending_rewards(20), Some(50)); + assert_eq!(Pools::api_pending_rewards(30), None); Balances::make_free_balance_be(&30, ed + 10); assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1)); - assert_eq!(Pools::pending_rewards(10), Some(30 + 50)); - assert_eq!(Pools::pending_rewards(20), Some(50)); - assert_eq!(Pools::pending_rewards(30), Some(0)); + assert_eq!(Pools::api_pending_rewards(10), Some(30 + 50)); + assert_eq!(Pools::api_pending_rewards(20), Some(50)); + assert_eq!(Pools::api_pending_rewards(30), Some(0)); - Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + deposit_rewards(60); - assert_eq!(Pools::pending_rewards(10), Some(30 + 50 + 20)); - assert_eq!(Pools::pending_rewards(20), Some(50 + 20)); - assert_eq!(Pools::pending_rewards(30), Some(20)); + assert_eq!(Pools::api_pending_rewards(10), Some(30 + 50 + 20)); + assert_eq!(Pools::api_pending_rewards(20), Some(50 + 20)); + assert_eq!(Pools::api_pending_rewards(30), Some(20)); // 10 should claim 10, 20 should claim nothing. assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); - assert_eq!(Pools::pending_rewards(10), Some(0)); - assert_eq!(Pools::pending_rewards(20), Some(50 + 20)); - assert_eq!(Pools::pending_rewards(30), Some(20)); + assert_eq!(Pools::api_pending_rewards(10), Some(0)); + assert_eq!(Pools::api_pending_rewards(20), Some(50 + 20)); + assert_eq!(Pools::api_pending_rewards(30), Some(20)); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); - assert_eq!(Pools::pending_rewards(10), Some(0)); - assert_eq!(Pools::pending_rewards(20), Some(0)); - assert_eq!(Pools::pending_rewards(30), Some(20)); + assert_eq!(Pools::api_pending_rewards(10), Some(0)); + assert_eq!(Pools::api_pending_rewards(20), Some(0)); + assert_eq!(Pools::api_pending_rewards(30), Some(20)); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30))); - assert_eq!(Pools::pending_rewards(10), Some(0)); - assert_eq!(Pools::pending_rewards(20), Some(0)); - assert_eq!(Pools::pending_rewards(30), Some(0)); + assert_eq!(Pools::api_pending_rewards(10), Some(0)); + assert_eq!(Pools::api_pending_rewards(20), Some(0)); + assert_eq!(Pools::api_pending_rewards(30), Some(0)); }); } @@ -1363,7 +1476,7 @@ mod claim_payout { Balances::make_free_balance_be(&30, ed + 20); assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1)); - Balances::mutate_account(&default_reward_account(), |f| f.free += 40).unwrap(); + deposit_rewards(40); // everyone claims. assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); @@ -1387,7 +1500,7 @@ mod claim_payout { assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(30), BondExtra::FreeBalance(10))); // more rewards come in. - Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap(); + deposit_rewards(100); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); @@ -1413,7 +1526,7 @@ mod claim_payout { Balances::make_free_balance_be(&20, ed + 20); assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1)); - Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + deposit_rewards(30); // everyone claims. assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); @@ -1434,7 +1547,7 @@ mod claim_payout { assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10)); // more rewards come in. - Balances::mutate_account(&default_reward_account(), |f| f.free += 100).unwrap(); + deposit_rewards(100); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); @@ -1461,7 +1574,7 @@ mod claim_payout { assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1)); // 10 gets 10, 20 gets 20, 30 gets 10 - Balances::mutate_account(&default_reward_account(), |f| f.free += 40).unwrap(); + deposit_rewards(40); // some claim. assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); @@ -1480,7 +1593,7 @@ mod claim_payout { ); // 10 gets 20, 20 gets 40, 30 gets 20 - Balances::mutate_account(&default_reward_account(), |f| f.free += 80).unwrap(); + deposit_rewards(80); // some claim. assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); @@ -1495,7 +1608,7 @@ mod claim_payout { ); // 10 gets 20, 20 gets 40, 30 gets 20 - Balances::mutate_account(&default_reward_account(), |f| f.free += 80).unwrap(); + deposit_rewards(80); // some claim. assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); @@ -1528,7 +1641,7 @@ mod claim_payout { assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1)); // 10 gets 10, 20 gets 20, 30 gets 10 - Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + deposit_rewards(30); // some claim. assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); @@ -1544,7 +1657,7 @@ mod claim_payout { ); // 20 has not claimed yet, more reward comes - Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + deposit_rewards(60); // and 20 bonds more -- they should not have more share of this reward. assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::FreeBalance(10))); @@ -1564,7 +1677,7 @@ mod claim_payout { ); // but in the next round of rewards, the extra10 they bonded has an impact. - Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + deposit_rewards(60); // everyone claim. assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); @@ -1594,7 +1707,7 @@ mod claim_payout { assert_eq!(member_10.last_recorded_reward_counter, 0.into()); // transfer some reward to pool 1. - Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + deposit_rewards(60); // create pool 2 Balances::make_free_balance_be(&20, 100); @@ -1678,7 +1791,7 @@ mod claim_payout { } // transfer some reward to pool 1. - Balances::mutate_account(&default_reward_account(), |f| f.free += 60).unwrap(); + deposit_rewards(60); { join(30, 10); @@ -1748,7 +1861,7 @@ mod claim_payout { // 10 bonds extra again with some rewards. This reward should be split equally between // 10 and 20, as they both have equal points now. - Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + deposit_rewards(30); { assert_ok!(Pools::bond_extra( @@ -1804,7 +1917,7 @@ mod claim_payout { MaxPoolMembersPerPool::::set(None); // pool receives some rewards. - Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + deposit_rewards(30); System::reset_events(); // 10 cashes it out, and bonds it. @@ -1874,7 +1987,7 @@ mod claim_payout { } // some rewards come in. - Balances::mutate_account(&default_reward_account(), |f| f.free += 30).unwrap(); + deposit_rewards(30); // and 30 also unbonds half. { @@ -1957,7 +2070,7 @@ mod claim_payout { ); // some rewards come in. - Balances::mutate_account(&default_reward_account(), |f| f.free += 40).unwrap(); + deposit_rewards(40); // everyone claims assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); @@ -2019,8 +2132,7 @@ mod claim_payout { .build_and_execute(|| { // some rewards come in. assert_eq!(Balances::free_balance(&default_reward_account()), unit); - Balances::mutate_account(&default_reward_account(), |f| f.free += unit / 1000) - .unwrap(); + deposit_rewards(unit / 1000); // everyone claims assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); @@ -2064,6 +2176,36 @@ mod claim_payout { ); }) } + + #[test] + fn claim_payout_other_works() { + ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| { + Balances::make_free_balance_be(&default_reward_account(), 8); + // ... of which only 3 are claimable to make sure the reward account does not die. + let claimable_reward = 8 - ExistentialDeposit::get(); + // NOTE: easier to read if we use 3, so let's use the number instead of variable. + assert_eq!(claimable_reward, 3, "test is correct if rewards are divisible by 3"); + + // given + assert_eq!(Balances::free_balance(10), 35); + + // Permissioned by default + assert_noop!( + Pools::claim_payout_other(RuntimeOrigin::signed(80), 10), + Error::::DoesNotHavePermission + ); + + assert_ok!(Pools::set_claim_permission( + RuntimeOrigin::signed(10), + ClaimPermission::PermissionlessWithdraw + )); + assert_ok!(Pools::claim_payout_other(RuntimeOrigin::signed(80), 10)); + + // then + assert_eq!(Balances::free_balance(10), 36); + assert_eq!(Balances::free_balance(&default_reward_account()), 7); + }) + } } mod unbond { @@ -2090,10 +2232,21 @@ mod unbond { Error::::MinimumBondNotMet ); + // Make permissionless + assert_eq!(ClaimPermissions::::get(10), ClaimPermission::Permissioned); + assert_ok!(Pools::set_claim_permission( + RuntimeOrigin::signed(20), + ClaimPermission::PermissionlessAll + )); + // but can go to 0 assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 15)); assert_eq!(PoolMembers::::get(20).unwrap().active_points(), 0); assert_eq!(PoolMembers::::get(20).unwrap().unbonding_points(), 20); + assert_eq!( + ClaimPermissions::::get(20), + ClaimPermission::PermissionlessAll + ); }) } @@ -2108,7 +2261,7 @@ mod unbond { .add_members(vec![(20, 20)]) .build_and_execute(|| { unsafe_set_state(1, PoolState::Blocked); - let kicker = DEFAULT_ROLES.state_toggler.unwrap(); + let kicker = DEFAULT_ROLES.bouncer.unwrap(); // cannot be kicked to above the limit. assert_noop!( @@ -2211,7 +2364,7 @@ mod unbond { // set the stage unsafe_set_state(1, PoolState::Blocked); - let kicker = DEFAULT_ROLES.state_toggler.unwrap(); + let kicker = DEFAULT_ROLES.bouncer.unwrap(); // cannot be kicked to above limit. assert_noop!( @@ -2351,7 +2504,7 @@ mod unbond { assert_eq!( SubPoolsStorage::::get(1).unwrap().with_era, - unbonding_pools_with_era! { 0 + 3 => UnbondPool:: { points: 10, balance: 10 }} + unbonding_pools_with_era! { 3 => UnbondPool:: { points: 10, balance: 10 }} ); assert_eq!( @@ -2359,10 +2512,11 @@ mod unbond { BondedPool { id: 1, inner: BondedPoolInner { - state: PoolState::Destroying, - points: 0, + commission: Commission::default(), member_counter: 1, + points: 0, roles: DEFAULT_ROLES, + state: PoolState::Destroying, } } ); @@ -2388,17 +2542,18 @@ mod unbond { // Then assert_eq!( SubPoolsStorage::::get(1).unwrap().with_era, - unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 6, balance: 6 }} + unbonding_pools_with_era! { 3 => UnbondPool { points: 6, balance: 6 }} ); assert_eq!( BondedPool::::get(1).unwrap(), BondedPool { id: 1, inner: BondedPoolInner { - state: PoolState::Open, - points: 560, + commission: Commission::default(), member_counter: 3, + points: 560, roles: DEFAULT_ROLES, + state: PoolState::Open, } } ); @@ -2417,7 +2572,7 @@ mod unbond { assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 94); assert_eq!( PoolMembers::::get(40).unwrap().unbonding_eras, - member_unbonding_eras!(0 + 3 => 6) + member_unbonding_eras!(3 => 6) ); assert_eq!(Balances::free_balance(&40), 40 + 40); // We claim rewards when unbonding @@ -2427,25 +2582,26 @@ mod unbond { // Then assert_eq!( - SubPoolsStorage::::get(&1).unwrap().with_era, - unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 98, balance: 98 }} + SubPoolsStorage::::get(1).unwrap().with_era, + unbonding_pools_with_era! { 3 => UnbondPool { points: 98, balance: 98 }} ); assert_eq!( BondedPool::::get(1).unwrap(), BondedPool { id: 1, inner: BondedPoolInner { - state: PoolState::Destroying, - points: 10, + commission: Commission::default(), member_counter: 3, - roles: DEFAULT_ROLES + points: 10, + roles: DEFAULT_ROLES, + state: PoolState::Destroying, } } ); assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 2); assert_eq!( PoolMembers::::get(550).unwrap().unbonding_eras, - member_unbonding_eras!(0 + 3 => 92) + member_unbonding_eras!(3 => 92) ); assert_eq!(Balances::free_balance(&550), 550 + 550); assert_eq!( @@ -2478,10 +2634,11 @@ mod unbond { BondedPool { id: 1, inner: BondedPoolInner { - state: PoolState::Destroying, - points: 0, + commission: Commission::default(), member_counter: 1, - roles: DEFAULT_ROLES + points: 0, + roles: DEFAULT_ROLES, + state: PoolState::Destroying, } } ); @@ -2512,7 +2669,7 @@ mod unbond { SubPools { no_era: Default::default(), with_era: unbonding_pools_with_era! { - 0 + 3 => UnbondPool { balance: 10, points: 100 }, + 3 => UnbondPool { balance: 10, points: 100 }, 1 + 3 => UnbondPool { balance: 20, points: 20 }, 2 + 3 => UnbondPool { balance: 101, points: 101} }, @@ -2550,7 +2707,7 @@ mod unbond { #[test] fn unbond_kick_works() { - // Kick: the pool is blocked and the caller is either the root or state-toggler. + // Kick: the pool is blocked and the caller is either the root or bouncer. ExtBuilder::default() .add_members(vec![(100, 100), (200, 200)]) .build_and_execute(|| { @@ -2559,7 +2716,7 @@ mod unbond { let bonded_pool = BondedPool::::get(1).unwrap(); assert_eq!(bonded_pool.roles.root.unwrap(), 900); assert_eq!(bonded_pool.roles.nominator.unwrap(), 901); - assert_eq!(bonded_pool.roles.state_toggler.unwrap(), 902); + assert_eq!(bonded_pool.roles.bouncer.unwrap(), 902); // When the nominator tries to kick, then its a noop assert_noop!( @@ -2587,7 +2744,7 @@ mod unbond { ] ); - // When the state toggler kicks then its ok + // When the bouncer kicks then its ok assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(902), 200)); assert_eq!( @@ -2606,10 +2763,11 @@ mod unbond { BondedPool { id: 1, inner: BondedPoolInner { + commission: Commission::default(), + member_counter: 3, + points: 10, // Only 10 points because 200 + 100 was unbonded roles: DEFAULT_ROLES, state: PoolState::Blocked, - points: 10, // Only 10 points because 200 + 100 was unbonded - member_counter: 3, } } ); @@ -2619,7 +2777,7 @@ mod unbond { SubPools { no_era: Default::default(), with_era: unbonding_pools_with_era! { - 0 + 3 => UnbondPool { points: 100 + 200, balance: 100 + 200 } + 3 => UnbondPool { points: 100 + 200, balance: 100 + 200 } }, } ); @@ -2756,10 +2914,11 @@ mod unbond { BondedPool:: { id: 1, inner: BondedPoolInner { - state: PoolState::Open, - points: 10, + commission: Commission::default(), member_counter: 1, + points: 10, roles: DEFAULT_ROLES, + state: PoolState::Open, }, } .put(); @@ -3181,11 +3340,7 @@ mod withdraw_unbonded { ); assert_eq!( balances_events_since_last_call(), - vec![BEvent::BalanceSet { - who: default_bonded_account(), - free: 300, - reserved: 0 - }] + vec![BEvent::BalanceSet { who: default_bonded_account(), free: 300 }] ); // When @@ -3275,7 +3430,7 @@ mod withdraw_unbonded { assert_ok!(fully_unbond_permissioned(550)); assert_eq!( - SubPoolsStorage::::get(&1).unwrap().with_era, + SubPoolsStorage::::get(1).unwrap().with_era, unbonding_pools_with_era! { 3 => UnbondPool { points: 550 / 2 + 40 / 2, balance: 550 / 2 + 40 / 2 }} ); @@ -3299,11 +3454,7 @@ mod withdraw_unbonded { ); assert_eq!( balances_events_since_last_call(), - vec![BEvent::BalanceSet { - who: default_bonded_account(), - free: 300, - reserved: 0 - },] + vec![BEvent::BalanceSet { who: default_bonded_account(), free: 300 },] ); CurrentEra::set(StakingMock::bonding_duration()); @@ -3325,7 +3476,7 @@ mod withdraw_unbonded { ); assert_eq!( - SubPoolsStorage::::get(&1).unwrap().with_era, + SubPoolsStorage::::get(1).unwrap().with_era, unbonding_pools_with_era! { 3 => UnbondPool { points: 550 / 2, balance: 550 / 2 }} ); @@ -3344,7 +3495,7 @@ mod withdraw_unbonded { Event::MemberRemoved { pool_id: 1, member: 550 } ] ); - assert!(SubPoolsStorage::::get(&1).unwrap().with_era.is_empty()); + assert!(SubPoolsStorage::::get(1).unwrap().with_era.is_empty()); // now, finally, the depositor can take out its share. unsafe_set_state(1, PoolState::Destroying); @@ -3352,7 +3503,7 @@ mod withdraw_unbonded { // because everyone else has left, the points assert_eq!( - SubPoolsStorage::::get(&1).unwrap().with_era, + SubPoolsStorage::::get(1).unwrap().with_era, unbonding_pools_with_era! { 6 => UnbondPool { points: 5, balance: 5 }} ); @@ -3406,10 +3557,10 @@ mod withdraw_unbonded { assert_eq!( SubPoolsStorage::::get(1).unwrap().with_era, //------------------------------balance decrease is not account for - unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 10 } } + unbonding_pools_with_era! { 3 => UnbondPool { points: 10, balance: 10 } } ); - CurrentEra::set(0 + 3); + CurrentEra::set(3); // When assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0)); @@ -3426,7 +3577,7 @@ mod withdraw_unbonded { // Insert the sub-pool let sub_pools = SubPools { no_era: Default::default(), - with_era: unbonding_pools_with_era! { 0 + 3 => UnbondPool { points: 10, balance: 10 }}, + with_era: unbonding_pools_with_era! { 3 => UnbondPool { points: 10, balance: 10 }}, }; SubPoolsStorage::::insert(1, sub_pools.clone()); @@ -3439,7 +3590,7 @@ mod withdraw_unbonded { PoolMembers::::insert(11, member.clone()); // Simulate calling `unbond` - member.unbonding_eras = member_unbonding_eras!(3 + 0 => 10); + member.unbonding_eras = member_unbonding_eras!(3 => 10); PoolMembers::::insert(11, member.clone()); // We are still in the bonding duration @@ -3449,7 +3600,7 @@ mod withdraw_unbonded { ); // If we error the member does not get removed - assert_eq!(PoolMembers::::get(&11), Some(member)); + assert_eq!(PoolMembers::::get(11), Some(member)); // and the sub pools do not get updated. assert_eq!(SubPoolsStorage::::get(1).unwrap(), sub_pools) }); @@ -3468,10 +3619,11 @@ mod withdraw_unbonded { BondedPool { id: 1, inner: BondedPoolInner { + commission: Commission::default(), + member_counter: 3, points: 10, + roles: DEFAULT_ROLES, state: PoolState::Open, - member_counter: 3, - roles: DEFAULT_ROLES } } ); @@ -3518,7 +3670,7 @@ mod withdraw_unbonded { // Can kick as root assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(900), 100, 0)); - // Can kick as state toggler + // Can kick as bouncer assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(900), 200, 0)); assert_eq!(Balances::free_balance(100), 100 + 100); @@ -3548,10 +3700,11 @@ mod withdraw_unbonded { BondedPool { id: 1, inner: BondedPoolInner { - points: 10, - state: PoolState::Open, + commission: Commission::default(), member_counter: 2, + points: 10, roles: DEFAULT_ROLES, + state: PoolState::Open, } } ); @@ -4058,6 +4211,49 @@ mod withdraw_unbonded { assert!(!Metadata::::contains_key(1)); }) } + + #[test] + fn withdraw_unbonded_removes_claim_permissions_on_leave() { + ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| { + // Given + CurrentEra::set(1); + assert_eq!(PoolMembers::::get(20).unwrap().points, 20); + + assert_ok!(Pools::set_claim_permission( + RuntimeOrigin::signed(20), + ClaimPermission::PermissionlessAll + )); + assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 20)); + assert_eq!(ClaimPermissions::::get(20), ClaimPermission::PermissionlessAll); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true }, + Event::Unbonded { member: 20, pool_id: 1, balance: 20, points: 20, era: 4 }, + ] + ); + + CurrentEra::set(5); + + // When + assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0)); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Withdrawn { member: 20, pool_id: 1, balance: 20, points: 20 }, + Event::MemberRemoved { pool_id: 1, member: 20 } + ] + ); + + // Then + assert_eq!(PoolMembers::::get(20), None); + assert_eq!(ClaimPermissions::::contains_key(20), false); + }); + } } mod create { @@ -4073,10 +4269,7 @@ mod create { assert!(!BondedPools::::contains_key(2)); assert!(!RewardPools::::contains_key(2)); assert!(!PoolMembers::::contains_key(11)); - assert_err!( - StakingMock::active_stake(&next_pool_stash), - DispatchError::Other("balance not found") - ); + assert_err!(StakingMock::active_stake(&next_pool_stash), "balance not found"); Balances::make_free_balance_be(&11, StakingMock::minimum_nominator_bond() + ed); assert_ok!(Pools::create( @@ -4101,15 +4294,16 @@ mod create { BondedPool { id: 2, inner: BondedPoolInner { + commission: Commission::default(), points: StakingMock::minimum_nominator_bond(), member_counter: 1, - state: PoolState::Open, roles: PoolRoles { depositor: 11, root: Some(123), nominator: Some(456), - state_toggler: Some(789) - } + bouncer: Some(789) + }, + state: PoolState::Open, } } ); @@ -4165,10 +4359,11 @@ mod create { BondedPool:: { id: 2, inner: BondedPoolInner { - state: PoolState::Open, - points: 10, + commission: Commission::default(), member_counter: 1, + points: 10, roles: DEFAULT_ROLES, + state: PoolState::Open, }, } .put(); @@ -4192,7 +4387,7 @@ mod create { amount: 20, root: 11, nominator: 11, - state_toggler: 11, + bouncer: 11, }); assert_noop!( create.dispatch(RuntimeOrigin::signed(11)), @@ -4240,6 +4435,45 @@ mod create { } } +#[test] +fn set_claimable_actor_works() { + ExtBuilder::default().build_and_execute(|| { + // Given + Balances::make_free_balance_be(&11, ExistentialDeposit::get() + 2); + assert!(!PoolMembers::::contains_key(11)); + + // When + assert_ok!(Pools::join(RuntimeOrigin::signed(11), 2, 1)); + + // Then + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true }, + ] + ); + + // Make permissionless + assert_eq!(ClaimPermissions::::get(11), ClaimPermission::Permissioned); + assert_noop!( + Pools::set_claim_permission( + RuntimeOrigin::signed(12), + ClaimPermission::PermissionlessAll + ), + Error::::PoolMemberNotFound + ); + assert_ok!(Pools::set_claim_permission( + RuntimeOrigin::signed(11), + ClaimPermission::PermissionlessAll + )); + + // then + assert_eq!(ClaimPermissions::::get(11), ClaimPermission::PermissionlessAll); + }); +} + mod nominate { use super::*; @@ -4252,7 +4486,7 @@ mod nominate { Error::::NotNominator ); - // State toggler can't nominate + // bouncer can't nominate assert_noop!( Pools::nominate(RuntimeOrigin::signed(902), 1, vec![21]), Error::::NotNominator @@ -4284,7 +4518,7 @@ mod set_state { // Given assert_ok!(BondedPool::::get(1).unwrap().ok_to_be_open()); - // Only the root and state toggler can change the state when the pool is ok to be open. + // Only the root and bouncer can change the state when the pool is ok to be open. assert_noop!( Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Blocked), Error::::CanNotChangeState @@ -4308,7 +4542,7 @@ mod set_state { assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Blocked); - // State toggler can change state + // bouncer can change state assert_ok!(Pools::set_state(RuntimeOrigin::signed(902), 1, PoolState::Destroying)); assert_eq!(BondedPool::::get(1).unwrap().state, PoolState::Destroying); @@ -4372,7 +4606,7 @@ mod set_metadata { assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1])); assert_eq!(Metadata::::get(1), vec![1, 1]); - // State toggler can set metadata + // bouncer can set metadata assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(902), 1, vec![2, 2])); assert_eq!(Metadata::::get(1), vec![2, 2]); @@ -4411,12 +4645,14 @@ mod set_configs { ConfigOp::Set(3u32), ConfigOp::Set(4u32), ConfigOp::Set(5u32), + ConfigOp::Set(Perbill::from_percent(6)) )); assert_eq!(MinJoinBond::::get(), 1); assert_eq!(MinCreateBond::::get(), 2); assert_eq!(MaxPools::::get(), Some(3)); assert_eq!(MaxPoolMembers::::get(), Some(4)); assert_eq!(MaxPoolMembersPerPool::::get(), Some(5)); + assert_eq!(GlobalMaxCommission::::get(), Some(Perbill::from_percent(6))); // Noop does nothing assert_storage_noop!(assert_ok!(Pools::set_configs( @@ -4426,6 +4662,7 @@ mod set_configs { ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, + ConfigOp::Noop, ))); // Removing works @@ -4436,12 +4673,14 @@ mod set_configs { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, + ConfigOp::Remove, )); assert_eq!(MinJoinBond::::get(), 0); assert_eq!(MinCreateBond::::get(), 0); assert_eq!(MaxPools::::get(), None); assert_eq!(MaxPoolMembers::::get(), None); assert_eq!(MaxPoolMembersPerPool::::get(), None); + assert_eq!(GlobalMaxCommission::::get(), None); }); } } @@ -4552,6 +4791,7 @@ mod bond_extra { // when assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::Rewards)); + assert_eq!(Balances::free_balance(&default_reward_account()), 7); // then assert_eq!(Balances::free_balance(10), 35); @@ -4583,6 +4823,61 @@ mod bond_extra { ); }) } + + #[test] + fn bond_extra_other() { + ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| { + Balances::make_free_balance_be(&default_reward_account(), 8); + // ... of which only 3 are claimable to make sure the reward account does not die. + let claimable_reward = 8 - ExistentialDeposit::get(); + // NOTE: easier to read if we use 3, so let's use the number instead of variable. + assert_eq!(claimable_reward, 3, "test is correct if rewards are divisible by 3"); + + // given + assert_eq!(PoolMembers::::get(10).unwrap().points, 10); + assert_eq!(PoolMembers::::get(20).unwrap().points, 20); + assert_eq!(BondedPools::::get(1).unwrap().points, 30); + assert_eq!(Balances::free_balance(10), 35); + assert_eq!(Balances::free_balance(20), 20); + + // Permissioned by default + assert_noop!( + Pools::bond_extra_other(RuntimeOrigin::signed(80), 20, BondExtra::Rewards), + Error::::DoesNotHavePermission + ); + + assert_ok!(Pools::set_claim_permission( + RuntimeOrigin::signed(10), + ClaimPermission::PermissionlessAll + )); + assert_ok!(Pools::bond_extra_other(RuntimeOrigin::signed(50), 10, BondExtra::Rewards)); + assert_eq!(Balances::free_balance(&default_reward_account()), 7); + + // then + assert_eq!(Balances::free_balance(10), 35); + assert_eq!(PoolMembers::::get(10).unwrap().points, 10 + 1); + assert_eq!(BondedPools::::get(1).unwrap().points, 30 + 1); + + // when + assert_noop!( + Pools::bond_extra_other(RuntimeOrigin::signed(40), 40, BondExtra::Rewards), + Error::::PoolMemberNotFound + ); + + // when + assert_ok!(Pools::bond_extra_other( + RuntimeOrigin::signed(20), + 20, + BondExtra::FreeBalance(10) + )); + + // then + assert_eq!(Balances::free_balance(20), 12); + assert_eq!(Balances::free_balance(&default_reward_account()), 5); + assert_eq!(PoolMembers::::get(20).unwrap().points, 30); + assert_eq!(BondedPools::::get(1).unwrap().points, 41); + }) + } } mod update_roles { @@ -4597,7 +4892,7 @@ mod update_roles { depositor: 10, root: Some(900), nominator: Some(901), - state_toggler: Some(902) + bouncer: Some(902) }, ); @@ -4636,7 +4931,7 @@ mod update_roles { ), Error::::DoesNotHavePermission, ); - // state-toggler + // bouncer assert_noop!( Pools::update_roles( RuntimeOrigin::signed(902), @@ -4662,21 +4957,12 @@ mod update_roles { vec![ Event::Created { depositor: 10, pool_id: 1 }, Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, - Event::RolesUpdated { - root: Some(5), - state_toggler: Some(7), - nominator: Some(6) - } + Event::RolesUpdated { root: Some(5), bouncer: Some(7), nominator: Some(6) } ] ); assert_eq!( BondedPools::::get(1).unwrap().roles, - PoolRoles { - depositor: 10, - root: Some(5), - nominator: Some(6), - state_toggler: Some(7) - }, + PoolRoles { depositor: 10, root: Some(5), nominator: Some(6), bouncer: Some(7) }, ); // also root origin can @@ -4690,20 +4976,11 @@ mod update_roles { assert_eq!( pool_events_since_last_call(), - vec![Event::RolesUpdated { - root: Some(1), - state_toggler: Some(3), - nominator: Some(2) - }] + vec![Event::RolesUpdated { root: Some(1), bouncer: Some(3), nominator: Some(2) }] ); assert_eq!( BondedPools::::get(1).unwrap().roles, - PoolRoles { - depositor: 10, - root: Some(1), - nominator: Some(2), - state_toggler: Some(3) - }, + PoolRoles { depositor: 10, root: Some(1), nominator: Some(2), bouncer: Some(3) }, ); // Noop works @@ -4717,21 +4994,12 @@ mod update_roles { assert_eq!( pool_events_since_last_call(), - vec![Event::RolesUpdated { - root: Some(11), - state_toggler: Some(3), - nominator: Some(2) - }] + vec![Event::RolesUpdated { root: Some(11), bouncer: Some(3), nominator: Some(2) }] ); assert_eq!( BondedPools::::get(1).unwrap().roles, - PoolRoles { - depositor: 10, - root: Some(11), - nominator: Some(2), - state_toggler: Some(3) - }, + PoolRoles { depositor: 10, root: Some(11), nominator: Some(2), bouncer: Some(3) }, ); // Remove works @@ -4745,20 +5013,18 @@ mod update_roles { assert_eq!( pool_events_since_last_call(), - vec![Event::RolesUpdated { root: Some(69), state_toggler: None, nominator: None }] + vec![Event::RolesUpdated { root: Some(69), bouncer: None, nominator: None }] ); assert_eq!( BondedPools::::get(1).unwrap().roles, - PoolRoles { depositor: 10, root: Some(69), nominator: None, state_toggler: None }, + PoolRoles { depositor: 10, root: Some(69), nominator: None, bouncer: None }, ); }) } } mod reward_counter_precision { - use sp_runtime::FixedU128; - use super::*; const DOT: Balance = 10u128.pow(10u32); @@ -4775,10 +5041,12 @@ mod reward_counter_precision { } fn default_pool_reward_counter() -> FixedU128 { - RewardPools::::get(1) + let bonded_pool = BondedPools::::get(1).unwrap(); + RewardPools::::get(1) .unwrap() - .current_reward_counter(1, BondedPools::::get(1).unwrap().points) + .current_reward_counter(1, bonded_pool.points, bonded_pool.commission.current()) .unwrap() + .0 } fn pending_rewards(of: AccountId) -> Option> { @@ -4810,18 +5078,15 @@ mod reward_counter_precision { let expected_smallest_reward = inflation(50) / 10u128.pow(18); // tad bit less. cannot be paid out. - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += - expected_smallest_reward - 1)); + deposit_rewards(expected_smallest_reward - 1); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); assert_eq!(pool_events_since_last_call(), vec![]); // revert it. - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free -= - expected_smallest_reward - 1)); + remove_rewards(expected_smallest_reward - 1); // tad bit more. can be claimed. - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += - expected_smallest_reward + 1)); + deposit_rewards(expected_smallest_reward + 1); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); assert_eq!( pool_events_since_last_call(), @@ -4846,9 +5111,7 @@ mod reward_counter_precision { assert_ok!(Pools::join(RuntimeOrigin::signed(20), tiny_bond / 2, 1)); // Suddenly, add a shit ton of rewards. - assert_ok!( - Balances::mutate_account(&default_reward_account(), |a| a.free += inflation(1)) - ); + deposit_rewards(inflation(1)); // now claim. assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); @@ -4887,8 +5150,7 @@ mod reward_counter_precision { // is earning all of the inflation per year (which is really unrealistic, but worse // case), that will be: let pool_total_earnings_10_years = inflation(10) - POLKADOT_TOTAL_ISSUANCE_GENESIS; - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += - pool_total_earnings_10_years)); + deposit_rewards(pool_total_earnings_10_years); // some whale now joins with the other half ot the total issuance. This will bloat all // the calculation regarding current reward counter. @@ -4918,7 +5180,7 @@ mod reward_counter_precision { assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10 * DOT, 1)); // and give a reasonably small reward to the pool. - assert_ok!(Balances::mutate_account(&default_reward_account(), |a| a.free += DOT)); + deposit_rewards(DOT); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30))); assert_eq!( @@ -4990,9 +5252,7 @@ mod reward_counter_precision { assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10 * DOT, 1)); // earn some small rewards - assert_ok!( - Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000) - ); + deposit_rewards(DOT / 1000); // no point in claiming for 20 (nonetheless, it should be harmless) assert!(pending_rewards(20).unwrap().is_zero()); @@ -5012,9 +5272,7 @@ mod reward_counter_precision { // earn some small more, still nothing can be claimed for 20, but 10 claims their // share. - assert_ok!( - Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000) - ); + deposit_rewards(DOT / 1000); assert!(pending_rewards(20).unwrap().is_zero()); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); assert_eq!( @@ -5023,9 +5281,7 @@ mod reward_counter_precision { ); // earn some more rewards, this time 20 can also claim. - assert_ok!( - Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000) - ); + deposit_rewards(DOT / 1000); assert_eq!(pending_rewards(20).unwrap(), 1); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20))); @@ -5064,9 +5320,7 @@ mod reward_counter_precision { assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10 * DOT, 1)); // earn some small rewards - assert_ok!( - Balances::mutate_account(&default_reward_account(), |a| a.free += DOT / 1000) - ); + deposit_rewards(DOT / 1000); // if 20 claims now, their reward counter should stay the same, so that they have a // chance of claiming this if they let it accumulate. Also see @@ -5100,3 +5354,1376 @@ mod reward_counter_precision { }); } } + +mod commission { + use super::*; + + #[test] + fn set_commission_works() { + ExtBuilder::default().build_and_execute(|| { + let pool_id = 1; + let root = 900; + + // Commission can be set by the `root` role. + + // When: + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(root), + pool_id, + Some((Perbill::from_percent(50), root)) + )); + + // Then: + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id }, + Event::Bonded { member: 10, pool_id, bonded: 10, joined: true }, + Event::PoolCommissionUpdated { + pool_id, + current: Some((Perbill::from_percent(50), root)) + }, + ] + ); + + // Commission can be updated only, while keeping the same payee. + + // When: + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(root), + 1, + Some((Perbill::from_percent(25), root)) + )); + + // Then: + assert_eq!( + pool_events_since_last_call(), + vec![Event::PoolCommissionUpdated { + pool_id, + current: Some((Perbill::from_percent(25), root)) + },] + ); + + // Payee can be updated only, while keeping the same commission. + + // Given: + let payee = 901; + + // When: + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(root), + pool_id, + Some((Perbill::from_percent(25), payee)) + )); + + // Then: + assert_eq!( + pool_events_since_last_call(), + vec![Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(25), payee)) + },] + ); + + // Pool earns 80 points and a payout is triggered. + + // Given: + deposit_rewards(80); + assert_eq!( + PoolMembers::::get(10).unwrap(), + PoolMember:: { pool_id, points: 10, ..Default::default() } + ); + + // When: + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + // Then: + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id, payout: 60 }] + ); + assert_eq!(RewardPool::::current_balance(pool_id), 20); + + // Pending pool commission can be claimed by the root role. + + // When: + assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(root), pool_id)); + + // Then: + assert_eq!(RewardPool::::current_balance(pool_id), 0); + assert_eq!( + pool_events_since_last_call(), + vec![Event::PoolCommissionClaimed { pool_id: 1, commission: 20 }] + ); + + // Commission can be removed from the pool completely. + + // When: + assert_ok!(Pools::set_commission(RuntimeOrigin::signed(root), pool_id, None)); + + // Then: + assert_eq!( + pool_events_since_last_call(), + vec![Event::PoolCommissionUpdated { pool_id, current: None },] + ); + + // Given a pool now has a reward counter history, additional rewards and payouts can be + // made while maintaining a correct ledger of the reward pool. Pool earns 100 points, + // payout is triggered. + // + // Note that the `total_commission_pending` will not be updated until `update_records` + // is next called, which is not done in this test segment.. + + // Given: + deposit_rewards(100); + + // When: + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + // Then: + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id, payout: 100 },] + ); + assert_eq!( + RewardPools::::get(pool_id).unwrap(), + RewardPool { + last_recorded_reward_counter: FixedU128::from_float(6.0), + last_recorded_total_payouts: 80, + total_rewards_claimed: 160, + total_commission_pending: 0, + total_commission_claimed: 20 + } + ); + + // When set commission is called again, update_records is called and + // `total_commission_pending` is updated, based on the current reward counter and pool + // balance. + // + // Note that commission is now 0%, so it should not come into play with subsequent + // payouts. + + // When: + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(root), + 1, + Some((Perbill::from_percent(10), root)) + )); + + // Then: + assert_eq!( + pool_events_since_last_call(), + vec![Event::PoolCommissionUpdated { + pool_id, + current: Some((Perbill::from_percent(10), root)) + },] + ); + assert_eq!( + RewardPools::::get(pool_id).unwrap(), + RewardPool { + last_recorded_reward_counter: FixedU128::from_float(16.0), + last_recorded_total_payouts: 180, + total_rewards_claimed: 160, + total_commission_pending: 0, + total_commission_claimed: 20 + } + ); + + // Supplying a 0% commission along with a payee results in a `None` current value. + + // When: + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(root), + pool_id, + Some((Perbill::from_percent(0), root)) + )); + + // Then: + assert_eq!( + BondedPool::::get(1).unwrap().commission, + Commission { current: None, max: None, change_rate: None, throttle_from: Some(1) } + ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::PoolCommissionUpdated { + pool_id, + current: Some((Perbill::from_percent(0), root)) + },] + ); + + // The payee can be updated even when commission has reached maximum commission. Both + // commission and max commission are set to 10% to test this. + + // Given: + assert_ok!(Pools::set_commission_max( + RuntimeOrigin::signed(root), + pool_id, + Perbill::from_percent(10) + )); + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(root), + pool_id, + Some((Perbill::from_percent(10), root)) + )); + + // When: + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(root), + pool_id, + Some((Perbill::from_percent(10), payee)) + )); + + // Then: + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PoolMaxCommissionUpdated { + pool_id, + max_commission: Perbill::from_percent(10) + }, + Event::PoolCommissionUpdated { + pool_id, + current: Some((Perbill::from_percent(10), root)) + }, + Event::PoolCommissionUpdated { + pool_id, + current: Some((Perbill::from_percent(10), payee)) + } + ] + ); + }); + } + + #[test] + fn commission_reward_counter_works_one_member() { + ExtBuilder::default().build_and_execute(|| { + let pool_id = 1; + let root = 900; + let member = 10; + + // Set the pool commission to 10% to test commission shares. Pool is topped up 40 points + // and `member` immediately claims their pending rewards. Reward pooll should still have + // 10% share. + + // Given: + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(root), + 1, + Some((Perbill::from_percent(10), root)), + )); + deposit_rewards(40); + + // When: + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + // Then: + assert_eq!(RewardPool::::current_balance(pool_id), 4); + + // Set pool commission to 20% and repeat the same process. + + // When: + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(root), + 1, + Some((Perbill::from_percent(20), root)), + )); + + // Then: + assert_eq!( + RewardPools::::get(pool_id).unwrap(), + RewardPool { + last_recorded_reward_counter: FixedU128::from_float(3.6), + last_recorded_total_payouts: 40, + total_rewards_claimed: 36, + total_commission_pending: 4, + total_commission_claimed: 0 + } + ); + + // The current reward counter should yield the correct pending rewards of zero. + + // Given: + let (current_reward_counter, _) = RewardPools::::get(pool_id) + .unwrap() + .current_reward_counter( + pool_id, + BondedPools::::get(pool_id).unwrap().points, + Perbill::from_percent(20), + ) + .unwrap(); + + // Then: + assert_eq!( + PoolMembers::::get(member) + .unwrap() + .pending_rewards(current_reward_counter) + .unwrap(), + 0 + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(10), 900)) + }, + Event::PaidOut { member: 10, pool_id: 1, payout: 36 }, + Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(20), 900)) + } + ] + ); + }) + } + + #[test] + fn set_commission_handles_errors() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + ] + ); + + // Provided pool does not exist. + assert_noop!( + Pools::set_commission( + RuntimeOrigin::signed(900), + 9999, + Some((Perbill::from_percent(1), 900)), + ), + Error::::PoolNotFound + ); + + // Sender does not have permission to set commission. + assert_noop!( + Pools::set_commission( + RuntimeOrigin::signed(1), + 1, + Some((Perbill::from_percent(5), 900)), + ), + Error::::DoesNotHavePermission + ); + + // Commission increases will be throttled if outside of change_rate allowance. + // Commission is set to 5%. + // Change rate is set to 1% max increase, 2 block delay. + + // When: + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(5), 900)), + )); + assert_ok!(Pools::set_commission_change_rate( + RuntimeOrigin::signed(900), + 1, + CommissionChangeRate { max_increase: Perbill::from_percent(1), min_delay: 2_u64 } + )); + assert_eq!( + BondedPool::::get(1).unwrap().commission, + Commission { + current: Some((Perbill::from_percent(5), 900)), + max: None, + change_rate: Some(CommissionChangeRate { + max_increase: Perbill::from_percent(1), + min_delay: 2_u64 + }), + throttle_from: Some(1_u64), + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(5), 900)) + }, + Event::PoolCommissionChangeRateUpdated { + pool_id: 1, + change_rate: CommissionChangeRate { + max_increase: Perbill::from_percent(1), + min_delay: 2 + } + } + ] + ); + + // Now try to increase commission to 10% (5% increase). This should be throttled. + // Then: + assert_noop!( + Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(10), 900)) + ), + Error::::CommissionChangeThrottled + ); + + run_blocks(2); + + // Increase commission by 1% and provide an initial payee. This should succeed and set + // the `throttle_from` field. + + // When: + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(6), 900)) + )); + assert_eq!( + BondedPool::::get(1).unwrap().commission, + Commission { + current: Some((Perbill::from_percent(6), 900)), + max: None, + change_rate: Some(CommissionChangeRate { + max_increase: Perbill::from_percent(1), + min_delay: 2_u64 + }), + throttle_from: Some(3_u64), + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(6), 900)) + },] + ); + + // Attempt to increase the commission an additional 1% (now 7%). This will fail as + // `throttle_from` is now the current block. At least 2 blocks need to pass before we + // can set commission again. + + // Then: + assert_noop!( + Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(7), 900)) + ), + Error::::CommissionChangeThrottled + ); + + run_blocks(2); + + // Can now successfully increase the commission again, to 7%. + + // When: + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(7), 900)), + )); + assert_eq!( + pool_events_since_last_call(), + vec![Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(7), 900)) + },] + ); + + run_blocks(2); + + // Now surpassed the `min_delay` threshold, but the `max_increase` threshold is + // still at play. An attempted commission change now to 8% (+2% increase) should fail. + + // Then: + assert_noop!( + Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(9), 900)), + ), + Error::::CommissionChangeThrottled + ); + + // Now set a max commission to the current 5%. This will also update the current + // commission to 5%. + + // When: + assert_ok!(Pools::set_commission_max( + RuntimeOrigin::signed(900), + 1, + Perbill::from_percent(5) + )); + assert_eq!( + BondedPool::::get(1).unwrap().commission, + Commission { + current: Some((Perbill::from_percent(5), 900)), + max: Some(Perbill::from_percent(5)), + change_rate: Some(CommissionChangeRate { + max_increase: Perbill::from_percent(1), + min_delay: 2 + }), + throttle_from: Some(7) + } + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(5), 900)) + }, + Event::PoolMaxCommissionUpdated { + pool_id: 1, + max_commission: Perbill::from_percent(5) + } + ] + ); + + // Run 2 blocks into the future so we are eligible to update commission again. + run_blocks(2); + + // Now attempt again to increase the commission by 1%, to 6%. This is within the change + // rate allowance, but `max_commission` will now prevent us from going any higher. + + // Then: + assert_noop!( + Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(6), 900)), + ), + Error::::CommissionExceedsMaximum + ); + }); + } + + #[test] + fn set_commission_max_works_with_error_tests() { + ExtBuilder::default().build_and_execute(|| { + // Provided pool does not exist + assert_noop!( + Pools::set_commission_max( + RuntimeOrigin::signed(900), + 9999, + Perbill::from_percent(1) + ), + Error::::PoolNotFound + ); + // Sender does not have permission to set commission + assert_noop!( + Pools::set_commission_max(RuntimeOrigin::signed(1), 1, Perbill::from_percent(5)), + Error::::DoesNotHavePermission + ); + + // Cannot set max commission above GlobalMaxCommission + assert_noop!( + Pools::set_commission_max( + RuntimeOrigin::signed(900), + 1, + Perbill::from_percent(100) + ), + Error::::CommissionExceedsGlobalMaximum + ); + + // Set a max commission commission pool 1 to 80% + assert_ok!(Pools::set_commission_max( + RuntimeOrigin::signed(900), + 1, + Perbill::from_percent(80) + )); + assert_eq!( + BondedPools::::get(1).unwrap().commission.max, + Some(Perbill::from_percent(80)) + ); + + // We attempt to increase the max commission to 90%, but increasing is + // disallowed due to pool's max commission. + assert_noop!( + Pools::set_commission_max(RuntimeOrigin::signed(900), 1, Perbill::from_percent(90)), + Error::::MaxCommissionRestricted + ); + + // We will now set a commission to 75% and then amend the max commission + // to 50%. The max commission change should decrease the current + // commission to 50%. + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(75), 900)) + )); + assert_ok!(Pools::set_commission_max( + RuntimeOrigin::signed(900), + 1, + Perbill::from_percent(50) + )); + assert_eq!( + BondedPools::::get(1).unwrap().commission, + Commission { + current: Some((Perbill::from_percent(50), 900)), + max: Some(Perbill::from_percent(50)), + change_rate: None, + throttle_from: Some(1), + } + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PoolMaxCommissionUpdated { + pool_id: 1, + max_commission: Perbill::from_percent(80) + }, + Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(75), 900)) + }, + Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(50), 900)) + }, + Event::PoolMaxCommissionUpdated { + pool_id: 1, + max_commission: Perbill::from_percent(50) + } + ] + ); + }); + } + + #[test] + fn set_commission_change_rate_works_with_errors() { + ExtBuilder::default().build_and_execute(|| { + // Provided pool does not exist + assert_noop!( + Pools::set_commission_change_rate( + RuntimeOrigin::signed(900), + 9999, + CommissionChangeRate { + max_increase: Perbill::from_percent(5), + min_delay: 1000_u64 + } + ), + Error::::PoolNotFound + ); + // Sender does not have permission to set commission + assert_noop!( + Pools::set_commission_change_rate( + RuntimeOrigin::signed(1), + 1, + CommissionChangeRate { + max_increase: Perbill::from_percent(5), + min_delay: 1000_u64 + } + ), + Error::::DoesNotHavePermission + ); + + // Set a commission change rate for pool 1 + assert_ok!(Pools::set_commission_change_rate( + RuntimeOrigin::signed(900), + 1, + CommissionChangeRate { max_increase: Perbill::from_percent(5), min_delay: 10_u64 } + )); + assert_eq!( + BondedPools::::get(1).unwrap().commission.change_rate, + Some(CommissionChangeRate { + max_increase: Perbill::from_percent(5), + min_delay: 10_u64 + }) + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PoolCommissionChangeRateUpdated { + pool_id: 1, + change_rate: CommissionChangeRate { + max_increase: Perbill::from_percent(5), + min_delay: 10 + } + }, + ] + ); + + // We now try to half the min_delay - this will be disallowed. A greater delay between + // commission changes is seen as more restrictive. + assert_noop!( + Pools::set_commission_change_rate( + RuntimeOrigin::signed(900), + 1, + CommissionChangeRate { + max_increase: Perbill::from_percent(5), + min_delay: 5_u64 + } + ), + Error::::CommissionChangeRateNotAllowed + ); + + // We now try to increase the allowed max_increase - this will fail. A smaller allowed + // commission change is seen as more restrictive. + assert_noop!( + Pools::set_commission_change_rate( + RuntimeOrigin::signed(900), + 1, + CommissionChangeRate { + max_increase: Perbill::from_percent(10), + min_delay: 10_u64 + } + ), + Error::::CommissionChangeRateNotAllowed + ); + + // Successful more restrictive change of min_delay with the current max_increase + assert_ok!(Pools::set_commission_change_rate( + RuntimeOrigin::signed(900), + 1, + CommissionChangeRate { max_increase: Perbill::from_percent(5), min_delay: 20_u64 } + )); + + // Successful more restrictive change of max_increase with the current min_delay + assert_ok!(Pools::set_commission_change_rate( + RuntimeOrigin::signed(900), + 1, + CommissionChangeRate { max_increase: Perbill::from_percent(4), min_delay: 20_u64 } + )); + + // Successful more restrictive change of both max_increase and min_delay + assert_ok!(Pools::set_commission_change_rate( + RuntimeOrigin::signed(900), + 1, + CommissionChangeRate { max_increase: Perbill::from_percent(3), min_delay: 30_u64 } + )); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PoolCommissionChangeRateUpdated { + pool_id: 1, + change_rate: CommissionChangeRate { + max_increase: Perbill::from_percent(5), + min_delay: 20 + } + }, + Event::PoolCommissionChangeRateUpdated { + pool_id: 1, + change_rate: CommissionChangeRate { + max_increase: Perbill::from_percent(4), + min_delay: 20 + } + }, + Event::PoolCommissionChangeRateUpdated { + pool_id: 1, + change_rate: CommissionChangeRate { + max_increase: Perbill::from_percent(3), + min_delay: 30 + } + } + ] + ); + }); + } + + #[test] + fn change_rate_does_not_apply_to_decreasing_commission() { + ExtBuilder::default().build_and_execute(|| { + // set initial commission of the pool to 10%. + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(10), 900)) + )); + + // Set a commission change rate for pool 1, 1% every 10 blocks + assert_ok!(Pools::set_commission_change_rate( + RuntimeOrigin::signed(900), + 1, + CommissionChangeRate { max_increase: Perbill::from_percent(1), min_delay: 10_u64 } + )); + assert_eq!( + BondedPools::::get(1).unwrap().commission.change_rate, + Some(CommissionChangeRate { + max_increase: Perbill::from_percent(1), + min_delay: 10_u64 + }) + ); + + // run `min_delay` blocks to allow a commission update. + run_blocks(10_u64); + + // Test `max_increase`: attempt to decrease the commission by 5%. Should succeed. + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(5), 900)) + )); + + // Test `min_delay`: *immediately* attempt to decrease the commission by 2%. Should + // succeed. + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(3), 900)) + )); + + // Attempt to *increase* the commission by 5%. Should fail. + assert_noop!( + Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(8), 900)) + ), + Error::::CommissionChangeThrottled + ); + + // Sanity check: the resulting pool Commission state. + assert_eq!( + BondedPools::::get(1).unwrap().commission, + Commission { + current: Some((Perbill::from_percent(3), 900)), + max: None, + change_rate: Some(CommissionChangeRate { + max_increase: Perbill::from_percent(1), + min_delay: 10_u64 + }), + throttle_from: Some(11), + } + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(10), 900)) + }, + Event::PoolCommissionChangeRateUpdated { + pool_id: 1, + change_rate: CommissionChangeRate { + max_increase: Perbill::from_percent(1), + min_delay: 10 + } + }, + Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(5), 900)) + }, + Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(3), 900)) + } + ] + ); + }); + } + + #[test] + fn set_commission_max_to_zero_works() { + ExtBuilder::default().build_and_execute(|| { + // 0% max commission test. + // set commission max 0%. + assert_ok!(Pools::set_commission_max(RuntimeOrigin::signed(900), 1, Zero::zero())); + + // a max commission of 0% essentially freezes the current commission, even when None. + // All commission update attempts will fail. + assert_noop!( + Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(1), 900)) + ), + Error::::CommissionExceedsMaximum + ); + }) + } + + #[test] + fn set_commission_change_rate_zero_max_increase_works() { + ExtBuilder::default().build_and_execute(|| { + // set commission change rate to 0% per 10 blocks + assert_ok!(Pools::set_commission_change_rate( + RuntimeOrigin::signed(900), + 1, + CommissionChangeRate { max_increase: Perbill::from_percent(0), min_delay: 10_u64 } + )); + + // even though there is a min delay of 10 blocks, a max increase of 0% essentially + // freezes the commission. All commission update attempts will fail. + assert_noop!( + Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(1), 900)) + ), + Error::::CommissionChangeThrottled + ); + }) + } + + #[test] + fn set_commission_change_rate_zero_min_delay_works() { + ExtBuilder::default().build_and_execute(|| { + // set commission change rate to 1% with a 0 block `min_delay`. + assert_ok!(Pools::set_commission_change_rate( + RuntimeOrigin::signed(900), + 1, + CommissionChangeRate { max_increase: Perbill::from_percent(1), min_delay: 0_u64 } + )); + assert_eq!( + BondedPools::::get(1).unwrap().commission, + Commission { + current: None, + max: None, + change_rate: Some(CommissionChangeRate { + max_increase: Perbill::from_percent(1), + min_delay: 0 + }), + throttle_from: Some(1) + } + ); + + // since there is no min delay, we should be able to immediately set the commission. + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(1), 900)) + )); + + // sanity check: increasing again to more than +1% will fail. + assert_noop!( + Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(3), 900)) + ), + Error::::CommissionChangeThrottled + ); + }) + } + + #[test] + fn set_commission_change_rate_zero_value_works() { + ExtBuilder::default().build_and_execute(|| { + // Check zero values play nice. 0 `min_delay` and 0% max_increase test. + // set commission change rate to 0% per 0 blocks. + assert_ok!(Pools::set_commission_change_rate( + RuntimeOrigin::signed(900), + 1, + CommissionChangeRate { max_increase: Perbill::from_percent(0), min_delay: 0_u64 } + )); + + // even though there is no min delay, a max increase of 0% essentially freezes the + // commission. All commission update attempts will fail. + assert_noop!( + Pools::set_commission( + RuntimeOrigin::signed(900), + 1, + Some((Perbill::from_percent(1), 900)) + ), + Error::::CommissionChangeThrottled + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PoolCommissionChangeRateUpdated { + pool_id: 1, + change_rate: CommissionChangeRate { + max_increase: Perbill::from_percent(0), + min_delay: 0_u64 + } + } + ] + ); + }) + } + + #[test] + fn do_reward_payout_with_various_commissions() { + ExtBuilder::default().build_and_execute(|| { + // turn off GlobalMaxCommission for this test. + GlobalMaxCommission::::set(None); + let pool_id = 1; + + // top up commission payee account to existential deposit + let _ = Balances::deposit_creating(&2, 5); + + // Set a commission pool 1 to 33%, with a payee set to `2` + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + pool_id, + Some((Perbill::from_percent(33), 2)), + )); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(33), 2)) + }, + ] + ); + + // The pool earns 10 points + deposit_rewards(10); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + // Then: + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 7 },] + ); + + // The pool earns 17 points + deposit_rewards(17); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + // Then: + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 11 },] + ); + + // The pool earns 50 points + deposit_rewards(50); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + // Then: + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 34 },] + ); + + // The pool earns 10439 points + deposit_rewards(10439); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + // Then: + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id: 1, payout: 6994 },] + ); + + // Set the commission to 100% and ensure the following payout to the pool member will + // not happen. + + // When: + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + pool_id, + Some((Perbill::from_percent(100), 2)), + )); + + // Given: + deposit_rewards(200); + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + // Then: + assert_eq!( + pool_events_since_last_call(), + vec![Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(100), 2)) + },] + ); + }) + } + + #[test] + fn commission_accumulates_on_multiple_rewards() { + ExtBuilder::default().build_and_execute(|| { + let pool_id = 1; + + // Given: + + // Set initial commission of pool 1 to 10%. + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + pool_id, + Some((Perbill::from_percent(10), 2)), + )); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(10), 2)) + }, + ] + ); + + // When: + + // The pool earns 100 points + deposit_rewards(100); + + // Change commission to 20% + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + pool_id, + Some((Perbill::from_percent(20), 2)), + )); + assert_eq!( + pool_events_since_last_call(), + vec![Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(20), 2)) + },] + ); + + // The pool earns 100 points + deposit_rewards(100); + + // Then: + + // Claim payout: + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + // Claim commission: + assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(900), pool_id)); + + // Then: + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 90 + 80 }, + Event::PoolCommissionClaimed { pool_id: 1, commission: 30 } + ] + ); + }) + } + + #[test] + fn last_recorded_total_payouts_needs_commission() { + ExtBuilder::default().build_and_execute(|| { + let pool_id = 1; + + // Given: + + // Set initial commission of pool 1 to 10%. + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + pool_id, + Some((Perbill::from_percent(10), 2)), + )); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(10), 2)) + }, + ] + ); + + // When: + + // The pool earns 100 points + deposit_rewards(100); + + // Claim payout: + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + + // Claim commission: + assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(900), pool_id)); + + // Then: + + assert_eq!( + RewardPools::::get(1).unwrap().last_recorded_total_payouts, + 90 + 10 + ); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PaidOut { member: 10, pool_id: 1, payout: 90 }, + Event::PoolCommissionClaimed { pool_id: 1, commission: 10 } + ] + ); + }) + } + + #[test] + fn do_reward_payout_with_100_percent_commission() { + ExtBuilder::default().build_and_execute(|| { + // turn off GlobalMaxCommission for this test. + GlobalMaxCommission::::set(None); + + let (mut member, bonded_pool, mut reward_pool) = + Pools::get_member_with_pools(&10).unwrap(); + + // top up commission payee account to existential deposit + let _ = Balances::deposit_creating(&2, 5); + + // Set a commission pool 1 to 100%, with a payee set to `2` + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + bonded_pool.id, + Some((Perbill::from_percent(100), 2)), + )); + + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(100), 2)) + } + ] + ); + + // The pool earns 10 points + deposit_rewards(10); + + // execute the payout + assert_ok!(Pools::do_reward_payout( + &10, + &mut member, + &mut BondedPool::::get(1).unwrap(), + &mut reward_pool + )); + }) + } + + #[test] + fn global_max_caps_max_commission_payout() { + ExtBuilder::default().build_and_execute(|| { + // Note: GlobalMaxCommission is set at 90%. + + let (mut member, bonded_pool, mut reward_pool) = + Pools::get_member_with_pools(&10).unwrap(); + + // top up the commission payee account to existential deposit + let _ = Balances::deposit_creating(&2, 5); + + // Set a commission pool 1 to 100% fails. + assert_noop!( + Pools::set_commission( + RuntimeOrigin::signed(900), + bonded_pool.id, + Some((Perbill::from_percent(100), 2)), + ), + Error::::CommissionExceedsGlobalMaximum + ); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id: 1 }, + Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true }, + ] + ); + + // Set pool commission to 90% and then set global max commission to 80%. + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + bonded_pool.id, + Some((Perbill::from_percent(90), 2)), + )); + GlobalMaxCommission::::set(Some(Perbill::from_percent(80))); + + // The pool earns 10 points + deposit_rewards(10); + + // execute the payout + assert_ok!(Pools::do_reward_payout( + &10, + &mut member, + &mut BondedPool::::get(1).unwrap(), + &mut reward_pool + )); + + // Confirm the commission was only 8 points out of 10 points, and the payout was 2 out + // of 10 points, reflecting the 80% global max commission. + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::PoolCommissionUpdated { + pool_id: 1, + current: Some((Perbill::from_percent(90), 2)) + }, + Event::PaidOut { member: 10, pool_id: 1, payout: 2 }, + ] + ); + }) + } + + #[test] + fn claim_commission_works() { + ExtBuilder::default().build_and_execute(|| { + let pool_id = 1; + + let _ = Balances::deposit_creating(&900, 5); + assert_ok!(Pools::set_commission( + RuntimeOrigin::signed(900), + pool_id, + Some((Perbill::from_percent(50), 900)) + )); + assert_eq!( + pool_events_since_last_call(), + vec![ + Event::Created { depositor: 10, pool_id }, + Event::Bonded { member: 10, pool_id, bonded: 10, joined: true }, + Event::PoolCommissionUpdated { + pool_id, + current: Some((Perbill::from_percent(50), 900)) + }, + ] + ); + + // Pool earns 80 points, payout is triggered. + deposit_rewards(80); + assert_eq!( + PoolMembers::::get(10).unwrap(), + PoolMember:: { pool_id, points: 10, ..Default::default() } + ); + + assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10))); + assert_eq!( + pool_events_since_last_call(), + vec![Event::PaidOut { member: 10, pool_id, payout: 40 }] + ); + + // Given: + assert_eq!(RewardPool::::current_balance(pool_id), 40); + + // Pool does not exist + assert_noop!( + Pools::claim_commission(RuntimeOrigin::signed(900), 9999,), + Error::::PoolNotFound + ); + + // Does not have permission. + assert_noop!( + Pools::claim_commission(RuntimeOrigin::signed(10), pool_id,), + Error::::DoesNotHavePermission + ); + + // When: + assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(900), pool_id)); + + // Then: + assert_eq!(RewardPool::::current_balance(pool_id), 0); + + // No more pending commission. + assert_noop!( + Pools::claim_commission(RuntimeOrigin::signed(900), pool_id,), + Error::::NoPendingCommission + ); + }) + } +} diff --git a/frame/nomination-pools/src/weights.rs b/frame/nomination-pools/src/weights.rs index 1062b1749d41..eb33c9adbbf9 100644 --- a/frame/nomination-pools/src/weights.rs +++ b/frame/nomination-pools/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_nomination_pools //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_nomination_pools +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,15 +45,16 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_nomination_pools. pub trait WeightInfo { fn join() -> Weight; fn bond_extra_transfer() -> Weight; - fn bond_extra_reward() -> Weight; + fn bond_extra_other() -> Weight; fn claim_payout() -> Weight; fn unbond() -> Weight; fn pool_withdraw_unbonded(s: u32, ) -> Weight; @@ -62,497 +67,1074 @@ pub trait WeightInfo { fn set_configs() -> Weight; fn update_roles() -> Weight; fn chill() -> Weight; + fn set_commission() -> Weight; + fn set_commission_max() -> Weight; + fn set_commission_change_rate() -> Weight; + fn set_claim_permission() -> Weight; + fn claim_commission() -> Weight; } /// Weights for pallet_nomination_pools using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: NominationPools MinJoinBond (r:1 w:0) - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: NominationPools RewardPools (r:1 w:1) - // Storage: System Account (r:2 w:1) - // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) - // Storage: NominationPools MaxPoolMembers (r:1 w:0) - // Storage: NominationPools CounterForPoolMembers (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: VoterList ListNodes (r:3 w:3) - // Storage: VoterList ListBags (r:2 w:2) + /// Storage: NominationPools MinJoinBond (r:1 w:0) + /// Proof: NominationPools MinJoinBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: NominationPools PoolMembers (r:1 w:1) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: NominationPools GlobalMaxCommission (r:1 w:0) + /// Proof: NominationPools GlobalMaxCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) + /// Proof: NominationPools MaxPoolMembersPerPool (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools MaxPoolMembers (r:1 w:0) + /// Proof: NominationPools MaxPoolMembers (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForPoolMembers (r:1 w:1) + /// Proof: NominationPools CounterForPoolMembers (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:3 w:3) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:2 w:2) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) fn join() -> Weight { - // Minimum execution time: 159_948 nanoseconds. - Weight::from_ref_time(161_133_000 as u64) - .saturating_add(T::DbWeight::get().reads(17 as u64)) - .saturating_add(T::DbWeight::get().writes(12 as u64)) - } - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: NominationPools RewardPools (r:1 w:1) - // Storage: System Account (r:3 w:2) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: VoterList ListNodes (r:3 w:3) - // Storage: VoterList ListBags (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `3300` + // Estimated: `8877` + // Minimum execution time: 200_966_000 picoseconds. + Weight::from_parts(208_322_000, 8877) + .saturating_add(T::DbWeight::get().reads(19_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)) + } + /// Storage: NominationPools PoolMembers (r:1 w:1) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: NominationPools GlobalMaxCommission (r:1 w:0) + /// Proof: NominationPools GlobalMaxCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:3 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:3 w:3) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:2 w:2) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) fn bond_extra_transfer() -> Weight { - // Minimum execution time: 155_517 nanoseconds. - Weight::from_ref_time(159_101_000 as u64) - .saturating_add(T::DbWeight::get().reads(14 as u64)) - .saturating_add(T::DbWeight::get().writes(12 as u64)) - } - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: NominationPools RewardPools (r:1 w:1) - // Storage: System Account (r:3 w:3) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: VoterList ListNodes (r:3 w:3) - // Storage: VoterList ListBags (r:2 w:2) - fn bond_extra_reward() -> Weight { - // Minimum execution time: 172_788 nanoseconds. - Weight::from_ref_time(174_212_000 as u64) - .saturating_add(T::DbWeight::get().reads(14 as u64)) - .saturating_add(T::DbWeight::get().writes(13 as u64)) - } - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: NominationPools RewardPools (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3310` + // Estimated: `8877` + // Minimum execution time: 197_865_000 picoseconds. + Weight::from_parts(203_085_000, 8877) + .saturating_add(T::DbWeight::get().reads(16_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)) + } + /// Storage: NominationPools ClaimPermissions (r:1 w:0) + /// Proof: NominationPools ClaimPermissions (max_values: None, max_size: Some(41), added: 2516, mode: MaxEncodedLen) + /// Storage: NominationPools PoolMembers (r:1 w:1) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: NominationPools GlobalMaxCommission (r:1 w:0) + /// Proof: NominationPools GlobalMaxCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:3 w:3) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:3 w:3) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:2 w:2) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + fn bond_extra_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `3375` + // Estimated: `8877` + // Minimum execution time: 235_496_000 picoseconds. + Weight::from_parts(242_104_000, 8877) + .saturating_add(T::DbWeight::get().reads(17_u64)) + .saturating_add(T::DbWeight::get().writes(13_u64)) + } + /// Storage: NominationPools ClaimPermissions (r:1 w:0) + /// Proof: NominationPools ClaimPermissions (max_values: None, max_size: Some(41), added: 2516, mode: MaxEncodedLen) + /// Storage: NominationPools PoolMembers (r:1 w:1) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: NominationPools GlobalMaxCommission (r:1 w:0) + /// Proof: NominationPools GlobalMaxCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn claim_payout() -> Weight { - // Minimum execution time: 64_560 nanoseconds. - Weight::from_ref_time(64_950_000 as u64) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - } - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: NominationPools RewardPools (r:1 w:1) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: System Account (r:2 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking Nominators (r:1 w:0) - // Storage: Staking MinNominatorBond (r:1 w:0) - // Storage: Balances Locks (r:1 w:1) - // Storage: VoterList ListNodes (r:3 w:3) - // Storage: VoterList ListBags (r:2 w:2) - // Storage: NominationPools SubPoolsStorage (r:1 w:1) - // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `1171` + // Estimated: `3702` + // Minimum execution time: 81_813_000 picoseconds. + Weight::from_parts(83_277_000, 3702) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: NominationPools PoolMembers (r:1 w:1) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: NominationPools GlobalMaxCommission (r:1 w:0) + /// Proof: NominationPools GlobalMaxCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:0) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking MinNominatorBond (r:1 w:0) + /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:3 w:3) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:2 w:2) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: NominationPools SubPoolsStorage (r:1 w:1) + /// Proof: NominationPools SubPoolsStorage (max_values: None, max_size: Some(24382), added: 26857, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + /// Proof: NominationPools CounterForSubPoolsStorage (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn unbond() -> Weight { - // Minimum execution time: 161_398 nanoseconds. - Weight::from_ref_time(162_991_000 as u64) - .saturating_add(T::DbWeight::get().reads(18 as u64)) - .saturating_add(T::DbWeight::get().writes(13 as u64)) - } - // Storage: NominationPools BondedPools (r:1 w:0) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Balances Locks (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3586` + // Estimated: `27847` + // Minimum execution time: 183_935_000 picoseconds. + Weight::from_parts(186_920_000, 27847) + .saturating_add(T::DbWeight::get().reads(20_u64)) + .saturating_add(T::DbWeight::get().writes(13_u64)) + } + /// Storage: NominationPools BondedPools (r:1 w:0) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `s` is `[0, 100]`. fn pool_withdraw_unbonded(s: u32, ) -> Weight { - // Minimum execution time: 66_036 nanoseconds. - Weight::from_ref_time(67_183_304 as u64) - // Standard Error: 565 - .saturating_add(Weight::from_ref_time(57_830 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: NominationPools SubPoolsStorage (r:1 w:1) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `1687` + // Estimated: `4764` + // Minimum execution time: 64_962_000 picoseconds. + Weight::from_parts(67_936_216, 4764) + // Standard Error: 1_780 + .saturating_add(Weight::from_parts(36_110, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: NominationPools PoolMembers (r:1 w:1) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools SubPoolsStorage (r:1 w:1) + /// Proof: NominationPools SubPoolsStorage (max_values: None, max_size: Some(24382), added: 26857, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForPoolMembers (r:1 w:1) + /// Proof: NominationPools CounterForPoolMembers (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools ClaimPermissions (r:0 w:1) + /// Proof: NominationPools ClaimPermissions (max_values: None, max_size: Some(41), added: 2516, mode: MaxEncodedLen) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - // Minimum execution time: 111_156 nanoseconds. - Weight::from_ref_time(112_507_059 as u64) - // Standard Error: 655 - .saturating_add(Weight::from_ref_time(53_711 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(9 as u64)) - .saturating_add(T::DbWeight::get().writes(7 as u64)) - } - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: NominationPools SubPoolsStorage (r:1 w:1) - // Storage: Staking Bonded (r:1 w:1) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking SlashingSpans (r:1 w:0) - // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:0) - // Storage: System Account (r:2 w:2) - // Storage: Balances Locks (r:1 w:1) - // Storage: NominationPools CounterForPoolMembers (r:1 w:1) - // Storage: NominationPools ReversePoolIdLookup (r:1 w:1) - // Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) - // Storage: NominationPools RewardPools (r:1 w:1) - // Storage: NominationPools CounterForRewardPools (r:1 w:1) - // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) - // Storage: NominationPools Metadata (r:1 w:1) - // Storage: NominationPools CounterForBondedPools (r:1 w:1) - // Storage: Staking Payee (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `2115` + // Estimated: `27847` + // Minimum execution time: 136_073_000 picoseconds. + Weight::from_parts(141_448_439, 27847) + // Standard Error: 2_472 + .saturating_add(Weight::from_parts(75_893, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(10_u64)) + .saturating_add(T::DbWeight::get().writes(8_u64)) + } + /// Storage: NominationPools PoolMembers (r:1 w:1) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools SubPoolsStorage (r:1 w:1) + /// Proof: NominationPools SubPoolsStorage (max_values: None, max_size: Some(24382), added: 26857, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:1) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking SlashingSpans (r:1 w:0) + /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking Validators (r:1 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:0) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForPoolMembers (r:1 w:1) + /// Proof: NominationPools CounterForPoolMembers (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools ReversePoolIdLookup (r:1 w:1) + /// Proof: NominationPools ReversePoolIdLookup (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) + /// Proof: NominationPools CounterForReversePoolIdLookup (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForRewardPools (r:1 w:1) + /// Proof: NominationPools CounterForRewardPools (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + /// Proof: NominationPools CounterForSubPoolsStorage (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools Metadata (r:1 w:1) + /// Proof: NominationPools Metadata (max_values: None, max_size: Some(270), added: 2745, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForBondedPools (r:1 w:1) + /// Proof: NominationPools CounterForBondedPools (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:0 w:1) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: NominationPools ClaimPermissions (r:0 w:1) + /// Proof: NominationPools ClaimPermissions (max_values: None, max_size: Some(41), added: 2516, mode: MaxEncodedLen) /// The range of component `s` is `[0, 100]`. - fn withdraw_unbonded_kill(s: u32, ) -> Weight { - // Minimum execution time: 168_270 nanoseconds. - Weight::from_ref_time(170_059_380 as u64) - // Standard Error: 1_506 - .saturating_add(Weight::from_ref_time(1_258 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(20 as u64)) - .saturating_add(T::DbWeight::get().writes(17 as u64)) - } - // Storage: NominationPools LastPoolId (r:1 w:1) - // Storage: Staking MinNominatorBond (r:1 w:0) - // Storage: NominationPools MinCreateBond (r:1 w:0) - // Storage: NominationPools MinJoinBond (r:1 w:0) - // Storage: NominationPools MaxPools (r:1 w:0) - // Storage: NominationPools CounterForBondedPools (r:1 w:1) - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) - // Storage: NominationPools MaxPoolMembers (r:1 w:0) - // Storage: NominationPools CounterForPoolMembers (r:1 w:1) - // Storage: System Account (r:2 w:2) - // Storage: Staking Bonded (r:1 w:1) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Balances Locks (r:1 w:1) - // Storage: NominationPools RewardPools (r:1 w:1) - // Storage: NominationPools CounterForRewardPools (r:1 w:1) - // Storage: NominationPools ReversePoolIdLookup (r:1 w:1) - // Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: Staking Payee (r:0 w:1) + fn withdraw_unbonded_kill(_s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `2470` + // Estimated: `27847` + // Minimum execution time: 230_871_000 picoseconds. + Weight::from_parts(239_533_976, 27847) + .saturating_add(T::DbWeight::get().reads(21_u64)) + .saturating_add(T::DbWeight::get().writes(18_u64)) + } + /// Storage: NominationPools LastPoolId (r:1 w:1) + /// Proof: NominationPools LastPoolId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking MinNominatorBond (r:1 w:0) + /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: NominationPools MinCreateBond (r:1 w:0) + /// Proof: NominationPools MinCreateBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: NominationPools MinJoinBond (r:1 w:0) + /// Proof: NominationPools MinJoinBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: NominationPools MaxPools (r:1 w:0) + /// Proof: NominationPools MaxPools (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForBondedPools (r:1 w:1) + /// Proof: NominationPools CounterForBondedPools (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools PoolMembers (r:1 w:1) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) + /// Proof: NominationPools MaxPoolMembersPerPool (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools MaxPoolMembers (r:1 w:0) + /// Proof: NominationPools MaxPoolMembers (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForPoolMembers (r:1 w:1) + /// Proof: NominationPools CounterForPoolMembers (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:1) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForRewardPools (r:1 w:1) + /// Proof: NominationPools CounterForRewardPools (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools ReversePoolIdLookup (r:1 w:1) + /// Proof: NominationPools ReversePoolIdLookup (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) + /// Proof: NominationPools CounterForReversePoolIdLookup (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:0 w:1) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) fn create() -> Weight { - // Minimum execution time: 146_153 nanoseconds. - Weight::from_ref_time(146_955_000 as u64) - .saturating_add(T::DbWeight::get().reads(21 as u64)) - .saturating_add(T::DbWeight::get().writes(15 as u64)) - } - // Storage: NominationPools BondedPools (r:1 w:0) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:0) - // Storage: Staking MinNominatorBond (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking MaxNominatorsCount (r:1 w:0) - // Storage: Staking Validators (r:2 w:0) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: VoterList ListNodes (r:1 w:1) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `1289` + // Estimated: `6196` + // Minimum execution time: 194_272_000 picoseconds. + Weight::from_parts(197_933_000, 6196) + .saturating_add(T::DbWeight::get().reads(22_u64)) + .saturating_add(T::DbWeight::get().writes(15_u64)) + } + /// Storage: NominationPools BondedPools (r:1 w:0) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking MinNominatorBond (r:1 w:0) + /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking MaxNominatorsCount (r:1 w:0) + /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:17 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:1 w:1) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - // Minimum execution time: 71_380 nanoseconds. - Weight::from_ref_time(71_060_388 as u64) - // Standard Error: 2_587 - .saturating_add(Weight::from_ref_time(1_185_729 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(12 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(5 as u64)) - } - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `1849` + // Estimated: `4556 + n * (2520 ±0)` + // Minimum execution time: 70_256_000 picoseconds. + Weight::from_parts(71_045_351, 4556) + // Standard Error: 9_898 + .saturating_add(Weight::from_parts(1_592_597, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(12_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(5_u64)) + .saturating_add(Weight::from_parts(0, 2520).saturating_mul(n.into())) + } + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) fn set_state() -> Weight { - // Minimum execution time: 46_275 nanoseconds. - Weight::from_ref_time(46_689_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: NominationPools BondedPools (r:1 w:0) - // Storage: NominationPools Metadata (r:1 w:1) - // Storage: NominationPools CounterForMetadata (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `1438` + // Estimated: `4556` + // Minimum execution time: 36_233_000 picoseconds. + Weight::from_parts(37_114_000, 4556) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: NominationPools BondedPools (r:1 w:0) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools Metadata (r:1 w:1) + /// Proof: NominationPools Metadata (max_values: None, max_size: Some(270), added: 2745, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForMetadata (r:1 w:1) + /// Proof: NominationPools CounterForMetadata (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) /// The range of component `n` is `[1, 256]`. fn set_metadata(n: u32, ) -> Weight { - // Minimum execution time: 19_246 nanoseconds. - Weight::from_ref_time(20_415_018 as u64) - // Standard Error: 95 - .saturating_add(Weight::from_ref_time(2_040 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: NominationPools MinJoinBond (r:0 w:1) - // Storage: NominationPools MaxPoolMembers (r:0 w:1) - // Storage: NominationPools MaxPoolMembersPerPool (r:0 w:1) - // Storage: NominationPools MinCreateBond (r:0 w:1) - // Storage: NominationPools MaxPools (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `531` + // Estimated: `3735` + // Minimum execution time: 14_494_000 picoseconds. + Weight::from_parts(15_445_658, 3735) + // Standard Error: 211 + .saturating_add(Weight::from_parts(1_523, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: NominationPools MinJoinBond (r:0 w:1) + /// Proof: NominationPools MinJoinBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: NominationPools MaxPoolMembers (r:0 w:1) + /// Proof: NominationPools MaxPoolMembers (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools MaxPoolMembersPerPool (r:0 w:1) + /// Proof: NominationPools MaxPoolMembersPerPool (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools MinCreateBond (r:0 w:1) + /// Proof: NominationPools MinCreateBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: NominationPools GlobalMaxCommission (r:0 w:1) + /// Proof: NominationPools GlobalMaxCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools MaxPools (r:0 w:1) + /// Proof: NominationPools MaxPools (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn set_configs() -> Weight { - // Minimum execution time: 9_231 nanoseconds. - Weight::from_ref_time(9_526_000 as u64) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_776_000 picoseconds. + Weight::from_parts(7_033_000, 0) + .saturating_add(T::DbWeight::get().writes(6_u64)) } - // Storage: NominationPools BondedPools (r:1 w:1) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) fn update_roles() -> Weight { - // Minimum execution time: 31_246 nanoseconds. - Weight::from_ref_time(31_762_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: NominationPools BondedPools (r:1 w:0) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:0) - // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: VoterList ListNodes (r:1 w:1) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `531` + // Estimated: `3685` + // Minimum execution time: 19_586_000 picoseconds. + Weight::from_parts(20_287_000, 3685) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: NominationPools BondedPools (r:1 w:0) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:1 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:1 w:1) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn chill() -> Weight { - // Minimum execution time: 73_812 nanoseconds. - Weight::from_ref_time(74_790_000 as u64) - .saturating_add(T::DbWeight::get().reads(9 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + // Proof Size summary in bytes: + // Measured: `2012` + // Estimated: `4556` + // Minimum execution time: 68_086_000 picoseconds. + Weight::from_parts(70_784_000, 4556) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) + } + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: NominationPools GlobalMaxCommission (r:1 w:0) + /// Proof: NominationPools GlobalMaxCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn set_commission() -> Weight { + // Proof Size summary in bytes: + // Measured: `770` + // Estimated: `3685` + // Minimum execution time: 33_353_000 picoseconds. + Weight::from_parts(34_519_000, 3685) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + fn set_commission_max() -> Weight { + // Proof Size summary in bytes: + // Measured: `571` + // Estimated: `3685` + // Minimum execution time: 19_020_000 picoseconds. + Weight::from_parts(19_630_000, 3685) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + fn set_commission_change_rate() -> Weight { + // Proof Size summary in bytes: + // Measured: `531` + // Estimated: `3685` + // Minimum execution time: 19_693_000 picoseconds. + Weight::from_parts(20_114_000, 3685) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: NominationPools PoolMembers (r:1 w:0) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: NominationPools ClaimPermissions (r:1 w:1) + /// Proof: NominationPools ClaimPermissions (max_values: None, max_size: Some(41), added: 2516, mode: MaxEncodedLen) + fn set_claim_permission() -> Weight { + // Proof Size summary in bytes: + // Measured: `542` + // Estimated: `3702` + // Minimum execution time: 14_810_000 picoseconds. + Weight::from_parts(15_526_000, 3702) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: NominationPools BondedPools (r:1 w:0) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: NominationPools GlobalMaxCommission (r:1 w:0) + /// Proof: NominationPools GlobalMaxCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn claim_commission() -> Weight { + // Proof Size summary in bytes: + // Measured: `968` + // Estimated: `3685` + // Minimum execution time: 66_400_000 picoseconds. + Weight::from_parts(67_707_000, 3685) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: NominationPools MinJoinBond (r:1 w:0) - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: NominationPools RewardPools (r:1 w:1) - // Storage: System Account (r:2 w:1) - // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) - // Storage: NominationPools MaxPoolMembers (r:1 w:0) - // Storage: NominationPools CounterForPoolMembers (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: VoterList ListNodes (r:3 w:3) - // Storage: VoterList ListBags (r:2 w:2) + /// Storage: NominationPools MinJoinBond (r:1 w:0) + /// Proof: NominationPools MinJoinBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: NominationPools PoolMembers (r:1 w:1) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: NominationPools GlobalMaxCommission (r:1 w:0) + /// Proof: NominationPools GlobalMaxCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) + /// Proof: NominationPools MaxPoolMembersPerPool (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools MaxPoolMembers (r:1 w:0) + /// Proof: NominationPools MaxPoolMembers (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForPoolMembers (r:1 w:1) + /// Proof: NominationPools CounterForPoolMembers (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:3 w:3) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:2 w:2) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) fn join() -> Weight { - // Minimum execution time: 159_948 nanoseconds. - Weight::from_ref_time(161_133_000 as u64) - .saturating_add(RocksDbWeight::get().reads(17 as u64)) - .saturating_add(RocksDbWeight::get().writes(12 as u64)) - } - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: NominationPools RewardPools (r:1 w:1) - // Storage: System Account (r:3 w:2) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: VoterList ListNodes (r:3 w:3) - // Storage: VoterList ListBags (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `3300` + // Estimated: `8877` + // Minimum execution time: 200_966_000 picoseconds. + Weight::from_parts(208_322_000, 8877) + .saturating_add(RocksDbWeight::get().reads(19_u64)) + .saturating_add(RocksDbWeight::get().writes(12_u64)) + } + /// Storage: NominationPools PoolMembers (r:1 w:1) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: NominationPools GlobalMaxCommission (r:1 w:0) + /// Proof: NominationPools GlobalMaxCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:3 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:3 w:3) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:2 w:2) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) fn bond_extra_transfer() -> Weight { - // Minimum execution time: 155_517 nanoseconds. - Weight::from_ref_time(159_101_000 as u64) - .saturating_add(RocksDbWeight::get().reads(14 as u64)) - .saturating_add(RocksDbWeight::get().writes(12 as u64)) - } - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: NominationPools RewardPools (r:1 w:1) - // Storage: System Account (r:3 w:3) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: VoterList ListNodes (r:3 w:3) - // Storage: VoterList ListBags (r:2 w:2) - fn bond_extra_reward() -> Weight { - // Minimum execution time: 172_788 nanoseconds. - Weight::from_ref_time(174_212_000 as u64) - .saturating_add(RocksDbWeight::get().reads(14 as u64)) - .saturating_add(RocksDbWeight::get().writes(13 as u64)) - } - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: NominationPools RewardPools (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3310` + // Estimated: `8877` + // Minimum execution time: 197_865_000 picoseconds. + Weight::from_parts(203_085_000, 8877) + .saturating_add(RocksDbWeight::get().reads(16_u64)) + .saturating_add(RocksDbWeight::get().writes(12_u64)) + } + /// Storage: NominationPools ClaimPermissions (r:1 w:0) + /// Proof: NominationPools ClaimPermissions (max_values: None, max_size: Some(41), added: 2516, mode: MaxEncodedLen) + /// Storage: NominationPools PoolMembers (r:1 w:1) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: NominationPools GlobalMaxCommission (r:1 w:0) + /// Proof: NominationPools GlobalMaxCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:3 w:3) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:3 w:3) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:2 w:2) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + fn bond_extra_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `3375` + // Estimated: `8877` + // Minimum execution time: 235_496_000 picoseconds. + Weight::from_parts(242_104_000, 8877) + .saturating_add(RocksDbWeight::get().reads(17_u64)) + .saturating_add(RocksDbWeight::get().writes(13_u64)) + } + /// Storage: NominationPools ClaimPermissions (r:1 w:0) + /// Proof: NominationPools ClaimPermissions (max_values: None, max_size: Some(41), added: 2516, mode: MaxEncodedLen) + /// Storage: NominationPools PoolMembers (r:1 w:1) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: NominationPools GlobalMaxCommission (r:1 w:0) + /// Proof: NominationPools GlobalMaxCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) fn claim_payout() -> Weight { - // Minimum execution time: 64_560 nanoseconds. - Weight::from_ref_time(64_950_000 as u64) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - } - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: NominationPools RewardPools (r:1 w:1) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: System Account (r:2 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking Nominators (r:1 w:0) - // Storage: Staking MinNominatorBond (r:1 w:0) - // Storage: Balances Locks (r:1 w:1) - // Storage: VoterList ListNodes (r:3 w:3) - // Storage: VoterList ListBags (r:2 w:2) - // Storage: NominationPools SubPoolsStorage (r:1 w:1) - // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `1171` + // Estimated: `3702` + // Minimum execution time: 81_813_000 picoseconds. + Weight::from_parts(83_277_000, 3702) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: NominationPools PoolMembers (r:1 w:1) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: NominationPools GlobalMaxCommission (r:1 w:0) + /// Proof: NominationPools GlobalMaxCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:0) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking MinNominatorBond (r:1 w:0) + /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:3 w:3) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:2 w:2) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: NominationPools SubPoolsStorage (r:1 w:1) + /// Proof: NominationPools SubPoolsStorage (max_values: None, max_size: Some(24382), added: 26857, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + /// Proof: NominationPools CounterForSubPoolsStorage (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn unbond() -> Weight { - // Minimum execution time: 161_398 nanoseconds. - Weight::from_ref_time(162_991_000 as u64) - .saturating_add(RocksDbWeight::get().reads(18 as u64)) - .saturating_add(RocksDbWeight::get().writes(13 as u64)) - } - // Storage: NominationPools BondedPools (r:1 w:0) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Balances Locks (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3586` + // Estimated: `27847` + // Minimum execution time: 183_935_000 picoseconds. + Weight::from_parts(186_920_000, 27847) + .saturating_add(RocksDbWeight::get().reads(20_u64)) + .saturating_add(RocksDbWeight::get().writes(13_u64)) + } + /// Storage: NominationPools BondedPools (r:1 w:0) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `s` is `[0, 100]`. fn pool_withdraw_unbonded(s: u32, ) -> Weight { - // Minimum execution time: 66_036 nanoseconds. - Weight::from_ref_time(67_183_304 as u64) - // Standard Error: 565 - .saturating_add(Weight::from_ref_time(57_830 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: NominationPools SubPoolsStorage (r:1 w:1) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: NominationPools CounterForPoolMembers (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `1687` + // Estimated: `4764` + // Minimum execution time: 64_962_000 picoseconds. + Weight::from_parts(67_936_216, 4764) + // Standard Error: 1_780 + .saturating_add(Weight::from_parts(36_110, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: NominationPools PoolMembers (r:1 w:1) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools SubPoolsStorage (r:1 w:1) + /// Proof: NominationPools SubPoolsStorage (max_values: None, max_size: Some(24382), added: 26857, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForPoolMembers (r:1 w:1) + /// Proof: NominationPools CounterForPoolMembers (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools ClaimPermissions (r:0 w:1) + /// Proof: NominationPools ClaimPermissions (max_values: None, max_size: Some(41), added: 2516, mode: MaxEncodedLen) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - // Minimum execution time: 111_156 nanoseconds. - Weight::from_ref_time(112_507_059 as u64) - // Standard Error: 655 - .saturating_add(Weight::from_ref_time(53_711 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(9 as u64)) - .saturating_add(RocksDbWeight::get().writes(7 as u64)) - } - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: NominationPools SubPoolsStorage (r:1 w:1) - // Storage: Staking Bonded (r:1 w:1) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking SlashingSpans (r:1 w:0) - // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:0) - // Storage: System Account (r:2 w:2) - // Storage: Balances Locks (r:1 w:1) - // Storage: NominationPools CounterForPoolMembers (r:1 w:1) - // Storage: NominationPools ReversePoolIdLookup (r:1 w:1) - // Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) - // Storage: NominationPools RewardPools (r:1 w:1) - // Storage: NominationPools CounterForRewardPools (r:1 w:1) - // Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) - // Storage: NominationPools Metadata (r:1 w:1) - // Storage: NominationPools CounterForBondedPools (r:1 w:1) - // Storage: Staking Payee (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `2115` + // Estimated: `27847` + // Minimum execution time: 136_073_000 picoseconds. + Weight::from_parts(141_448_439, 27847) + // Standard Error: 2_472 + .saturating_add(Weight::from_parts(75_893, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(10_u64)) + .saturating_add(RocksDbWeight::get().writes(8_u64)) + } + /// Storage: NominationPools PoolMembers (r:1 w:1) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools SubPoolsStorage (r:1 w:1) + /// Proof: NominationPools SubPoolsStorage (max_values: None, max_size: Some(24382), added: 26857, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:1) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking SlashingSpans (r:1 w:0) + /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking Validators (r:1 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:0) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForPoolMembers (r:1 w:1) + /// Proof: NominationPools CounterForPoolMembers (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools ReversePoolIdLookup (r:1 w:1) + /// Proof: NominationPools ReversePoolIdLookup (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) + /// Proof: NominationPools CounterForReversePoolIdLookup (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForRewardPools (r:1 w:1) + /// Proof: NominationPools CounterForRewardPools (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForSubPoolsStorage (r:1 w:1) + /// Proof: NominationPools CounterForSubPoolsStorage (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools Metadata (r:1 w:1) + /// Proof: NominationPools Metadata (max_values: None, max_size: Some(270), added: 2745, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForBondedPools (r:1 w:1) + /// Proof: NominationPools CounterForBondedPools (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:0 w:1) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: NominationPools ClaimPermissions (r:0 w:1) + /// Proof: NominationPools ClaimPermissions (max_values: None, max_size: Some(41), added: 2516, mode: MaxEncodedLen) /// The range of component `s` is `[0, 100]`. - fn withdraw_unbonded_kill(s: u32, ) -> Weight { - // Minimum execution time: 168_270 nanoseconds. - Weight::from_ref_time(170_059_380 as u64) - // Standard Error: 1_506 - .saturating_add(Weight::from_ref_time(1_258 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(20 as u64)) - .saturating_add(RocksDbWeight::get().writes(17 as u64)) - } - // Storage: NominationPools LastPoolId (r:1 w:1) - // Storage: Staking MinNominatorBond (r:1 w:0) - // Storage: NominationPools MinCreateBond (r:1 w:0) - // Storage: NominationPools MinJoinBond (r:1 w:0) - // Storage: NominationPools MaxPools (r:1 w:0) - // Storage: NominationPools CounterForBondedPools (r:1 w:1) - // Storage: NominationPools PoolMembers (r:1 w:1) - // Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) - // Storage: NominationPools MaxPoolMembers (r:1 w:0) - // Storage: NominationPools CounterForPoolMembers (r:1 w:1) - // Storage: System Account (r:2 w:2) - // Storage: Staking Bonded (r:1 w:1) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Balances Locks (r:1 w:1) - // Storage: NominationPools RewardPools (r:1 w:1) - // Storage: NominationPools CounterForRewardPools (r:1 w:1) - // Storage: NominationPools ReversePoolIdLookup (r:1 w:1) - // Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: Staking Payee (r:0 w:1) + fn withdraw_unbonded_kill(_s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `2470` + // Estimated: `27847` + // Minimum execution time: 230_871_000 picoseconds. + Weight::from_parts(239_533_976, 27847) + .saturating_add(RocksDbWeight::get().reads(21_u64)) + .saturating_add(RocksDbWeight::get().writes(18_u64)) + } + /// Storage: NominationPools LastPoolId (r:1 w:1) + /// Proof: NominationPools LastPoolId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking MinNominatorBond (r:1 w:0) + /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: NominationPools MinCreateBond (r:1 w:0) + /// Proof: NominationPools MinCreateBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: NominationPools MinJoinBond (r:1 w:0) + /// Proof: NominationPools MinJoinBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: NominationPools MaxPools (r:1 w:0) + /// Proof: NominationPools MaxPools (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForBondedPools (r:1 w:1) + /// Proof: NominationPools CounterForBondedPools (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools PoolMembers (r:1 w:1) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: NominationPools MaxPoolMembersPerPool (r:1 w:0) + /// Proof: NominationPools MaxPoolMembersPerPool (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools MaxPoolMembers (r:1 w:0) + /// Proof: NominationPools MaxPoolMembers (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForPoolMembers (r:1 w:1) + /// Proof: NominationPools CounterForPoolMembers (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:1) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForRewardPools (r:1 w:1) + /// Proof: NominationPools CounterForRewardPools (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools ReversePoolIdLookup (r:1 w:1) + /// Proof: NominationPools ReversePoolIdLookup (max_values: None, max_size: Some(44), added: 2519, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForReversePoolIdLookup (r:1 w:1) + /// Proof: NominationPools CounterForReversePoolIdLookup (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:0 w:1) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) fn create() -> Weight { - // Minimum execution time: 146_153 nanoseconds. - Weight::from_ref_time(146_955_000 as u64) - .saturating_add(RocksDbWeight::get().reads(21 as u64)) - .saturating_add(RocksDbWeight::get().writes(15 as u64)) - } - // Storage: NominationPools BondedPools (r:1 w:0) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:0) - // Storage: Staking MinNominatorBond (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking MaxNominatorsCount (r:1 w:0) - // Storage: Staking Validators (r:2 w:0) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: VoterList ListNodes (r:1 w:1) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `1289` + // Estimated: `6196` + // Minimum execution time: 194_272_000 picoseconds. + Weight::from_parts(197_933_000, 6196) + .saturating_add(RocksDbWeight::get().reads(22_u64)) + .saturating_add(RocksDbWeight::get().writes(15_u64)) + } + /// Storage: NominationPools BondedPools (r:1 w:0) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking MinNominatorBond (r:1 w:0) + /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking MaxNominatorsCount (r:1 w:0) + /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:17 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:1 w:1) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - // Minimum execution time: 71_380 nanoseconds. - Weight::from_ref_time(71_060_388 as u64) - // Standard Error: 2_587 - .saturating_add(Weight::from_ref_time(1_185_729 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(12 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) - } - // Storage: NominationPools BondedPools (r:1 w:1) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `1849` + // Estimated: `4556 + n * (2520 ±0)` + // Minimum execution time: 70_256_000 picoseconds. + Weight::from_parts(71_045_351, 4556) + // Standard Error: 9_898 + .saturating_add(Weight::from_parts(1_592_597, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(12_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + .saturating_add(Weight::from_parts(0, 2520).saturating_mul(n.into())) + } + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) fn set_state() -> Weight { - // Minimum execution time: 46_275 nanoseconds. - Weight::from_ref_time(46_689_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: NominationPools BondedPools (r:1 w:0) - // Storage: NominationPools Metadata (r:1 w:1) - // Storage: NominationPools CounterForMetadata (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `1438` + // Estimated: `4556` + // Minimum execution time: 36_233_000 picoseconds. + Weight::from_parts(37_114_000, 4556) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: NominationPools BondedPools (r:1 w:0) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools Metadata (r:1 w:1) + /// Proof: NominationPools Metadata (max_values: None, max_size: Some(270), added: 2745, mode: MaxEncodedLen) + /// Storage: NominationPools CounterForMetadata (r:1 w:1) + /// Proof: NominationPools CounterForMetadata (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) /// The range of component `n` is `[1, 256]`. fn set_metadata(n: u32, ) -> Weight { - // Minimum execution time: 19_246 nanoseconds. - Weight::from_ref_time(20_415_018 as u64) - // Standard Error: 95 - .saturating_add(Weight::from_ref_time(2_040 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: NominationPools MinJoinBond (r:0 w:1) - // Storage: NominationPools MaxPoolMembers (r:0 w:1) - // Storage: NominationPools MaxPoolMembersPerPool (r:0 w:1) - // Storage: NominationPools MinCreateBond (r:0 w:1) - // Storage: NominationPools MaxPools (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `531` + // Estimated: `3735` + // Minimum execution time: 14_494_000 picoseconds. + Weight::from_parts(15_445_658, 3735) + // Standard Error: 211 + .saturating_add(Weight::from_parts(1_523, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: NominationPools MinJoinBond (r:0 w:1) + /// Proof: NominationPools MinJoinBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: NominationPools MaxPoolMembers (r:0 w:1) + /// Proof: NominationPools MaxPoolMembers (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools MaxPoolMembersPerPool (r:0 w:1) + /// Proof: NominationPools MaxPoolMembersPerPool (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools MinCreateBond (r:0 w:1) + /// Proof: NominationPools MinCreateBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: NominationPools GlobalMaxCommission (r:0 w:1) + /// Proof: NominationPools GlobalMaxCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: NominationPools MaxPools (r:0 w:1) + /// Proof: NominationPools MaxPools (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn set_configs() -> Weight { - // Minimum execution time: 9_231 nanoseconds. - Weight::from_ref_time(9_526_000 as u64) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_776_000 picoseconds. + Weight::from_parts(7_033_000, 0) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } - // Storage: NominationPools BondedPools (r:1 w:1) + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) fn update_roles() -> Weight { - // Minimum execution time: 31_246 nanoseconds. - Weight::from_ref_time(31_762_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: NominationPools BondedPools (r:1 w:0) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:0) - // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: VoterList ListNodes (r:1 w:1) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `531` + // Estimated: `3685` + // Minimum execution time: 19_586_000 picoseconds. + Weight::from_parts(20_287_000, 3685) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: NominationPools BondedPools (r:1 w:0) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:1 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:1 w:1) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn chill() -> Weight { - // Minimum execution time: 73_812 nanoseconds. - Weight::from_ref_time(74_790_000 as u64) - .saturating_add(RocksDbWeight::get().reads(9 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + // Proof Size summary in bytes: + // Measured: `2012` + // Estimated: `4556` + // Minimum execution time: 68_086_000 picoseconds. + Weight::from_parts(70_784_000, 4556) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) + } + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: NominationPools GlobalMaxCommission (r:1 w:0) + /// Proof: NominationPools GlobalMaxCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:0) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn set_commission() -> Weight { + // Proof Size summary in bytes: + // Measured: `770` + // Estimated: `3685` + // Minimum execution time: 33_353_000 picoseconds. + Weight::from_parts(34_519_000, 3685) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + fn set_commission_max() -> Weight { + // Proof Size summary in bytes: + // Measured: `571` + // Estimated: `3685` + // Minimum execution time: 19_020_000 picoseconds. + Weight::from_parts(19_630_000, 3685) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: NominationPools BondedPools (r:1 w:1) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + fn set_commission_change_rate() -> Weight { + // Proof Size summary in bytes: + // Measured: `531` + // Estimated: `3685` + // Minimum execution time: 19_693_000 picoseconds. + Weight::from_parts(20_114_000, 3685) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: NominationPools PoolMembers (r:1 w:0) + /// Proof: NominationPools PoolMembers (max_values: None, max_size: Some(237), added: 2712, mode: MaxEncodedLen) + /// Storage: NominationPools ClaimPermissions (r:1 w:1) + /// Proof: NominationPools ClaimPermissions (max_values: None, max_size: Some(41), added: 2516, mode: MaxEncodedLen) + fn set_claim_permission() -> Weight { + // Proof Size summary in bytes: + // Measured: `542` + // Estimated: `3702` + // Minimum execution time: 14_810_000 picoseconds. + Weight::from_parts(15_526_000, 3702) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: NominationPools BondedPools (r:1 w:0) + /// Proof: NominationPools BondedPools (max_values: None, max_size: Some(220), added: 2695, mode: MaxEncodedLen) + /// Storage: NominationPools RewardPools (r:1 w:1) + /// Proof: NominationPools RewardPools (max_values: None, max_size: Some(92), added: 2567, mode: MaxEncodedLen) + /// Storage: NominationPools GlobalMaxCommission (r:1 w:0) + /// Proof: NominationPools GlobalMaxCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn claim_commission() -> Weight { + // Proof Size summary in bytes: + // Measured: `968` + // Estimated: `3685` + // Minimum execution time: 66_400_000 picoseconds. + Weight::from_parts(67_707_000, 3685) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } } diff --git a/frame/nomination-pools/test-staking/Cargo.toml b/frame/nomination-pools/test-staking/Cargo.toml index fbe5feca0feb..8ff7895a328d 100644 --- a/frame/nomination-pools/test-staking/Cargo.toml +++ b/frame/nomination-pools/test-staking/Cargo.toml @@ -13,14 +13,14 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dev-dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } scale-info = { version = "2.0.1", features = ["derive"] } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } -sp-io = { version = "7.0.0", path = "../../../primitives/io" } -sp-std = { version = "5.0.0", path = "../../../primitives/std" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } +sp-io = { version = "23.0.0", path = "../../../primitives/io" } +sp-std = { version = "8.0.0", path = "../../../primitives/std" } sp-staking = { version = "4.0.0-dev", path = "../../../primitives/staking" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } frame-system = { version = "4.0.0-dev", path = "../../system" } frame-support = { version = "4.0.0-dev", path = "../../support" } @@ -33,5 +33,5 @@ pallet-bags-list = { version = "4.0.0-dev", path = "../../bags-list" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } pallet-nomination-pools = { version = "1.0.0-dev", path = ".." } -sp-tracing = { version = "6.0.0", path = "../../../primitives/tracing" } +sp-tracing = { version = "10.0.0", path = "../../../primitives/tracing" } log = { version = "0.4.0" } diff --git a/frame/nomination-pools/test-staking/src/lib.rs b/frame/nomination-pools/test-staking/src/lib.rs index 00e0e40ce33b..158749258214 100644 --- a/frame/nomination-pools/test-staking/src/lib.rs +++ b/frame/nomination-pools/test-staking/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -531,7 +531,7 @@ fn pool_slash_proportional() { #[test] fn pool_slash_non_proportional_only_bonded_pool() { - // A typical example where a pool member unbonds in era 99, and he can get away with a slash + // A typical example where a pool member unbonds in era 99, and they can get away with a slash // that happened in era 100, as long as the pool has enough active bond to cover the slash. If // everything else in the slashing/staking system works, this should always be the case. // Nonetheless, `ledger.slash` has been written such that it will slash greedily from any chunk diff --git a/frame/nomination-pools/test-staking/src/mock.rs b/frame/nomination-pools/test-staking/src/mock.rs index c67aec0134b0..ffc1ed56d08c 100644 --- a/frame/nomination-pools/test-staking/src/mock.rs +++ b/frame/nomination-pools/test-staking/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,11 +25,11 @@ use frame_support::{ }; use sp_runtime::{ traits::{Convert, IdentityLookup}, - FixedU128, + BuildStorage, FixedU128, Perbill, }; type AccountId = u128; -type AccountIndex = u32; +type Nonce = u32; type BlockNumber = u64; type Balance = u128; @@ -44,14 +44,13 @@ impl frame_system::Config for Runtime { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = AccountIndex; - type BlockNumber = BlockNumber; + type Nonce = Nonce; type RuntimeCall = RuntimeCall; type Hash = sp_core::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = sp_runtime::testing::Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type Version = (); @@ -86,6 +85,10 @@ impl pallet_balances::Config for Runtime { type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } pallet_staking_reward_curve::build! { @@ -109,7 +112,7 @@ impl pallet_staking::Config for Runtime { type Currency = Balances; type CurrencyBalance = Balance; type UnixTime = pallet_timestamp::Pallet; - type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type CurrencyToVote = (); type RewardRemainder = (); type RuntimeEvent = RuntimeEvent; type Slash = (); @@ -130,7 +133,7 @@ impl pallet_staking::Config for Runtime { type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type HistoryDepth = ConstU32<84>; - type OnStakerSlash = Pools; + type EventListeners = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } @@ -183,13 +186,9 @@ impl pallet_nomination_pools::Config for Runtime { } type Block = frame_system::mocking::MockBlock; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { System: frame_system::{Pallet, Call, Event}, Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, @@ -202,13 +201,14 @@ frame_support::construct_runtime!( pub fn new_test_ext() -> sp_io::TestExternalities { sp_tracing::try_init_simple(); - let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); let _ = pallet_nomination_pools::GenesisConfig:: { min_join_bond: 2, min_create_bond: 2, max_pools: Some(3), max_members_per_pool: Some(5), max_members: Some(3 * 5), + global_max_commission: Some(Perbill::from_percent(90)), } .assimilate_storage(&mut storage) .unwrap(); diff --git a/frame/offences/Cargo.toml b/frame/offences/Cargo.toml index 47ae264a69cf..5d418dfa364b 100644 --- a/frame/offences/Cargo.toml +++ b/frame/offences/Cargo.toml @@ -13,20 +13,20 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", optional = true } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../balances" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/offences/benchmarking/Cargo.toml b/frame/offences/benchmarking/Cargo.toml index 23377f7883da..d59f0da54a81 100644 --- a/frame/offences/benchmarking/Cargo.toml +++ b/frame/offences/benchmarking/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } scale-info = { version = "2.0.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../../election-provider-support" } @@ -26,16 +26,16 @@ pallet-im-online = { version = "4.0.0-dev", default-features = false, path = ".. pallet-offences = { version = "4.0.0-dev", default-features = false, path = "../../offences" } pallet-session = { version = "4.0.0-dev", default-features = false, path = "../../session" } pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../staking" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/staking" } -sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } log = { version = "0.4.17", default-features = false } [dev-dependencies] pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../../primitives/io" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../../primitives/io" } [features] default = ["std"] diff --git a/frame/offences/benchmarking/src/lib.rs b/frame/offences/benchmarking/src/lib.rs index 7e586a96bbe3..92a87fb58b8c 100644 --- a/frame/offences/benchmarking/src/lib.rs +++ b/frame/offences/benchmarking/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ mod mock; use sp_std::{prelude::*, vec}; -use frame_benchmarking::{account, benchmarks}; +use frame_benchmarking::v1::{account, benchmarks}; use frame_support::traits::{Currency, Get, ValidatorSet, ValidatorSetWithIdentification}; use frame_system::{Config as SystemConfig, Pallet as System, RawOrigin}; @@ -36,9 +36,11 @@ use sp_runtime::{ }; use sp_staking::offence::{Offence, ReportOffence}; -use pallet_babe::BabeEquivocationOffence; +use pallet_babe::EquivocationOffence as BabeEquivocationOffence; use pallet_balances::Config as BalancesConfig; -use pallet_grandpa::{GrandpaEquivocationOffence, GrandpaTimeSlot}; +use pallet_grandpa::{ + EquivocationOffence as GrandpaEquivocationOffence, TimeSlot as GrandpaTimeSlot, +}; use pallet_im_online::{Config as ImOnlineConfig, Pallet as ImOnline, UnresponsivenessOffence}; use pallet_offences::{Config as OffencesConfig, Pallet as Offences}; use pallet_session::{ @@ -105,8 +107,7 @@ fn bond_amount() -> BalanceOf { fn create_offender(n: u32, nominators: u32) -> Result, &'static str> { let stash: T::AccountId = account("stash", n, SEED); - let controller: T::AccountId = account("controller", n, SEED); - let controller_lookup: LookupSourceOf = T::Lookup::unlookup(controller.clone()); + let stash_lookup: LookupSourceOf = T::Lookup::unlookup(stash.clone()); let reward_destination = RewardDestination::Staked; let amount = bond_amount::(); // add twice as much balance to prevent the account from being killed. @@ -114,14 +115,13 @@ fn create_offender(n: u32, nominators: u32) -> Result, &' T::Currency::make_free_balance_be(&stash, free_amount); Staking::::bond( RawOrigin::Signed(stash.clone()).into(), - controller_lookup.clone(), amount, reward_destination.clone(), )?; let validator_prefs = ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }; - Staking::::validate(RawOrigin::Signed(controller.clone()).into(), validator_prefs)?; + Staking::::validate(RawOrigin::Signed(stash.clone()).into(), validator_prefs)?; let mut individual_exposures = vec![]; let mut nominator_stashes = vec![]; @@ -129,22 +129,17 @@ fn create_offender(n: u32, nominators: u32) -> Result, &' for i in 0..nominators { let nominator_stash: T::AccountId = account("nominator stash", n * MAX_NOMINATORS + i, SEED); - let nominator_controller: T::AccountId = - account("nominator controller", n * MAX_NOMINATORS + i, SEED); - let nominator_controller_lookup: LookupSourceOf = - T::Lookup::unlookup(nominator_controller.clone()); T::Currency::make_free_balance_be(&nominator_stash, free_amount); Staking::::bond( RawOrigin::Signed(nominator_stash.clone()).into(), - nominator_controller_lookup.clone(), amount, reward_destination.clone(), )?; - let selected_validators: Vec> = vec![controller_lookup.clone()]; + let selected_validators: Vec> = vec![stash_lookup.clone()]; Staking::::nominate( - RawOrigin::Signed(nominator_controller.clone()).into(), + RawOrigin::Signed(nominator_stash.clone()).into(), selected_validators, )?; @@ -157,7 +152,7 @@ fn create_offender(n: u32, nominators: u32) -> Result, &' let current_era = 0u32; Staking::::add_era_stakers(current_era, stash.clone(), exposure); - Ok(Offender { controller, stash, nominator_stashes }) + Ok(Offender { controller: stash.clone(), stash, nominator_stashes }) } fn make_offenders( @@ -333,6 +328,12 @@ benchmarks! { let balance_slash = |id| core::iter::once( ::RuntimeEvent::from(pallet_balances::Event::::Slashed{ who: id, amount: slash_amount.into() }) ); + let balance_locked = |id| core::iter::once( + ::RuntimeEvent::from(pallet_balances::Event::::Locked{ who: id, amount: slash_amount.into() }) + ); + let balance_unlocked = |id| core::iter::once( + ::RuntimeEvent::from(pallet_balances::Event::::Unlocked{ who: id, amount: slash_amount.into() }) + ); let chill = |id| core::iter::once( ::RuntimeEvent::from(StakingEvent::::Chilled{ stash: id }) ); @@ -347,12 +348,15 @@ benchmarks! { let slash_events = raw_offenders.into_iter() .flat_map(|offender| { let nom_slashes = offender.nominator_stashes.into_iter().flat_map(|nom| { - balance_slash(nom.clone()).map(Into::into).chain(slash(nom).map(Into::into)).map(Box::new) + balance_slash(nom.clone()).map(Into::into) + .chain(balance_unlocked(nom.clone()).map(Into::into)) + .chain(slash(nom).map(Into::into)).map(Box::new) }); let events = chill(offender.stash.clone()).map(Into::into).map(Box::new) .chain(slash_report(offender.stash.clone()).map(Into::into).map(Box::new)) .chain(balance_slash(offender.stash.clone()).map(Into::into).map(Box::new)) + .chain(balance_unlocked(offender.stash.clone()).map(Into::into).map(Box::new)) .chain(slash(offender.stash).map(Into::into).map(Box::new)) .chain(nom_slashes) .collect::>(); @@ -425,9 +429,9 @@ benchmarks! { + 1 // offence + 3 // reporter (reward + endowment) + 1 // offenders reported - + 2 // offenders slashed + + 3 // offenders slashed + 1 // offenders chilled - + 2 * n // nominators slashed + + 3 * n // nominators slashed ); } @@ -462,9 +466,9 @@ benchmarks! { + 1 // offence + 3 // reporter (reward + endowment) + 1 // offenders reported - + 2 // offenders slashed + + 3 // offenders slashed + 1 // offenders chilled - + 2 * n // nominators slashed + + 3 * n // nominators slashed ); } diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 592e821a81d8..0e3dfd09fe7a 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,11 +30,11 @@ use pallet_session::historical as pallet_session_historical; use sp_runtime::{ testing::{Header, UintAuthorityId}, traits::IdentityLookup, + BuildStorage, }; type AccountId = u64; -type AccountIndex = u32; -type BlockNumber = u64; +type Nonce = u32; type Balance = u64; impl frame_system::Config for Test { @@ -43,14 +43,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = AccountIndex; - type BlockNumber = BlockNumber; + type Nonce = Nonce; type RuntimeCall = RuntimeCall; type Hash = sp_core::H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = sp_runtime::testing::Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type Version = (); @@ -65,7 +64,7 @@ impl frame_system::Config for Test { } impl pallet_balances::Config for Test { - type MaxLocks = (); + type MaxLocks = ConstU32<128>; type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type Balance = Balance; @@ -74,6 +73,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<10>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } impl pallet_timestamp::Config for Test { @@ -158,7 +161,7 @@ impl pallet_staking::Config for Test { type Currency = Balances; type CurrencyBalance = ::Balance; type UnixTime = pallet_timestamp::Pallet; - type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type CurrencyToVote = (); type RewardRemainder = (); type RuntimeEvent = RuntimeEvent; type Slash = (); @@ -178,7 +181,7 @@ impl pallet_staking::Config for Test { type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type HistoryDepth = ConstU32<84>; - type OnStakerSlash = (); + type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } @@ -193,7 +196,6 @@ impl pallet_im_online::Config for Test { type WeightInfo = (); type MaxKeys = ConstU32<10_000>; type MaxPeerInHeartbeats = ConstU32<10_000>; - type MaxPeerDataEncodingSize = ConstU32<1_000>; } impl pallet_offences::Config for Test { @@ -216,10 +218,7 @@ pub type Block = sp_runtime::generic::Block; pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub enum Test { System: system::{Pallet, Call, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, @@ -232,6 +231,6 @@ frame_support::construct_runtime!( ); pub fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); sp_io::TestExternalities::new(t) } diff --git a/frame/offences/src/lib.rs b/frame/offences/src/lib.rs index 7858b02719c4..1c7ffeca7198 100644 --- a/frame/offences/src/lib.rs +++ b/frame/offences/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,7 +26,9 @@ pub mod migration; mod mock; mod tests; -use codec::{Decode, Encode}; +use core::marker::PhantomData; + +use codec::Encode; use frame_support::weights::Weight; use sp_runtime::{traits::Hash, Perbill}; use sp_staking::{ @@ -43,13 +45,17 @@ type OpaqueTimeSlot = Vec; /// A type alias for a report identifier. type ReportIdOf = ::Hash; +const LOG_TARGET: &str = "runtime::offences"; + #[frame_support::pallet] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; + const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] #[pallet::without_storage_info] pub struct Pallet(_); @@ -86,21 +92,6 @@ pub mod pallet { ValueQuery, >; - /// Enumerates all reports of a kind along with the time they happened. - /// - /// All reports are sorted by the time of offence. - /// - /// Note that the actual type of this mapping is `Vec`, this is because values of - /// different types are not supported at the moment so we are doing the manual serialization. - #[pallet::storage] - pub type ReportsByKindIndex = StorageMap< - _, - Twox64Concat, - Kind, - Vec, // (O::TimeSlot, ReportIdOf) - ValueQuery, - >; - /// Events type. #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -112,10 +103,10 @@ pub mod pallet { } } -impl> - ReportOffence for Pallet +impl ReportOffence for Pallet where - T::IdentificationTuple: Clone, + T: Config, + O: Offence, { fn report_offence(reporters: Vec, offence: O) -> Result<(), OffenceError> { let offenders = offence.offenders(); @@ -191,7 +182,7 @@ impl Pallet { OffenceDetails { offender, reporters: reporters.clone() }, ); - storage.insert(time_slot, report_id); + storage.insert(report_id); } } @@ -226,7 +217,7 @@ struct TriageOutcome { struct ReportIndexStorage> { opaque_time_slot: OpaqueTimeSlot, concurrent_reports: Vec>, - same_kind_reports: Vec<(O::TimeSlot, ReportIdOf)>, + _phantom: PhantomData, } impl> ReportIndexStorage { @@ -234,30 +225,19 @@ impl> ReportIndexStorage { fn load(time_slot: &O::TimeSlot) -> Self { let opaque_time_slot = time_slot.encode(); - let same_kind_reports = ReportsByKindIndex::::get(&O::ID); - let same_kind_reports = - Vec::<(O::TimeSlot, ReportIdOf)>::decode(&mut &same_kind_reports[..]) - .unwrap_or_default(); - let concurrent_reports = >::get(&O::ID, &opaque_time_slot); - Self { opaque_time_slot, concurrent_reports, same_kind_reports } + Self { opaque_time_slot, concurrent_reports, _phantom: Default::default() } } /// Insert a new report to the index. - fn insert(&mut self, time_slot: &O::TimeSlot, report_id: ReportIdOf) { - // Insert the report id into the list while maintaining the ordering by the time - // slot. - let pos = self.same_kind_reports.partition_point(|&(ref when, _)| when <= time_slot); - self.same_kind_reports.insert(pos, (time_slot.clone(), report_id)); - + fn insert(&mut self, report_id: ReportIdOf) { // Update the list of concurrent reports. self.concurrent_reports.push(report_id); } /// Dump the indexes to the storage. fn save(self) { - ReportsByKindIndex::::insert(&O::ID, self.same_kind_reports.encode()); >::insert( &O::ID, &self.opaque_time_slot, diff --git a/frame/offences/src/migration.rs b/frame/offences/src/migration.rs index e8fab1c0babc..3c0d243a55d9 100644 --- a/frame/offences/src/migration.rs +++ b/frame/offences/src/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,11 +15,80 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{Config, OffenceDetails, Perbill, SessionIndex}; -use frame_support::{pallet_prelude::ValueQuery, storage_alias, traits::Get, weights::Weight}; +use super::{Config, Kind, OffenceDetails, Pallet, Perbill, SessionIndex, LOG_TARGET}; +use frame_support::{ + dispatch::GetStorageVersion, + pallet_prelude::ValueQuery, + storage_alias, + traits::{Get, OnRuntimeUpgrade}, + weights::Weight, + Twox64Concat, +}; use sp_staking::offence::{DisableStrategy, OnOffenceHandler}; use sp_std::vec::Vec; +#[cfg(feature = "try-runtime")] +use frame_support::ensure; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + +mod v0 { + use super::*; + + #[storage_alias] + pub type ReportsByKindIndex = StorageMap< + Pallet, + Twox64Concat, + Kind, + Vec, // (O::TimeSlot, ReportIdOf) + ValueQuery, + >; +} + +pub mod v1 { + use frame_support::traits::StorageVersion; + + use super::*; + + pub struct MigrateToV1(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for MigrateToV1 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + log::info!( + target: LOG_TARGET, + "Number of reports to refund and delete: {}", + v0::ReportsByKindIndex::::iter_keys().count() + ); + + Ok(Vec::new()) + } + + fn on_runtime_upgrade() -> Weight { + if Pallet::::on_chain_storage_version() > 0 { + log::info!(target: LOG_TARGET, "pallet_offences::MigrateToV1 should be removed"); + return T::DbWeight::get().reads(1) + } + + let keys_removed = v0::ReportsByKindIndex::::clear(u32::MAX, None).unique as u64; + StorageVersion::new(1).put::>(); + + // + 1 for reading/writing the new storage version + T::DbWeight::get().reads_writes(keys_removed + 1, keys_removed + 1) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { + let onchain = Pallet::::on_chain_storage_version(); + ensure!(onchain == 1, "pallet_offences::MigrateToV1 needs to be run"); + ensure!( + v0::ReportsByKindIndex::::iter_keys().count() == 0, + "there are some dangling reports that need to be destroyed and refunded" + ); + Ok(()) + } + } +} + /// Type of data stored as a deferred offence type DeferredOffenceOf = ( Vec::AccountId, ::IdentificationTuple>>, @@ -36,7 +105,7 @@ type DeferredOffences = pub fn remove_deferred_storage() -> Weight { let mut weight = T::DbWeight::get().reads_writes(1, 1); let deferred = >::take(); - log::info!(target: "runtime::offences", "have {} deferred offences, applying.", deferred.len()); + log::info!(target: LOG_TARGET, "have {} deferred offences, applying.", deferred.len()); for (offences, perbill, session) in deferred.iter() { let consumed = T::OnOffenceHandler::on_offence( offences, @@ -53,10 +122,32 @@ pub fn remove_deferred_storage() -> Weight { #[cfg(test)] mod test { use super::*; - use crate::mock::{new_test_ext, with_on_offence_fractions, Runtime as T}; + use crate::mock::{new_test_ext, with_on_offence_fractions, Runtime as T, KIND}; + use codec::Encode; use sp_runtime::Perbill; use sp_staking::offence::OffenceDetails; + #[test] + fn migration_to_v1_works() { + let mut ext = new_test_ext(); + + ext.execute_with(|| { + >::insert(KIND, 2u32.encode()); + assert!(>::iter_values().count() > 0); + }); + + ext.commit_all().unwrap(); + + ext.execute_with(|| { + assert_eq!( + v1::MigrateToV1::::on_runtime_upgrade(), + ::DbWeight::get().reads_writes(2, 2), + ); + + assert!(>::iter_values().count() == 0); + }) + } + #[test] fn should_resubmit_deferred_offences() { new_test_ext().execute_with(|| { diff --git a/frame/offences/src/mock.rs b/frame/offences/src/mock.rs index 63d2b0de3478..990ceae5ac01 100644 --- a/frame/offences/src/mock.rs +++ b/frame/offences/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,9 +29,8 @@ use frame_support::{ }; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, - Perbill, + BuildStorage, Perbill, }; use sp_staking::{ offence::{self, DisableStrategy, Kind, OffenceDetails}, @@ -66,16 +65,12 @@ pub fn with_on_offence_fractions) -> R>(f: F) -> OnOffencePerbill::mutate(|fractions| f(fractions)) } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub struct Runtime { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Offences: offences::{Pallet, Storage, Event}, } ); @@ -86,14 +81,13 @@ impl frame_system::Config for Runtime { type BlockLength = (); type DbWeight = RocksDbWeight; type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -114,7 +108,7 @@ impl Config for Runtime { } pub fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); ext @@ -134,17 +128,17 @@ pub fn offence_reports(kind: Kind, time_slot: u128) -> Vec { +pub struct Offence { pub validator_set_count: u32, - pub offenders: Vec, + pub offenders: Vec, pub time_slot: u128, } -impl offence::Offence for Offence { +impl offence::Offence for Offence { const ID: offence::Kind = KIND; type TimeSlot = u128; - fn offenders(&self) -> Vec { + fn offenders(&self) -> Vec { self.offenders.clone() } @@ -164,8 +158,3 @@ impl offence::Offence for Offence { Perbill::from_percent(5 + offenders_count * 100 / self.validator_set_count) } } - -/// Create the report id for the given `offender` and `time_slot` combination. -pub fn report_id(time_slot: u128, offender: u64) -> H256 { - Offences::report_id::>(&time_slot, &offender) -} diff --git a/frame/offences/src/tests.rs b/frame/offences/src/tests.rs index 266e05debf05..d525c7c3ab1d 100644 --- a/frame/offences/src/tests.rs +++ b/frame/offences/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +21,8 @@ use super::*; use crate::mock::{ - new_test_ext, offence_reports, report_id, with_on_offence_fractions, Offence, Offences, - RuntimeEvent, System, KIND, + new_test_ext, offence_reports, with_on_offence_fractions, Offence, Offences, RuntimeEvent, + System, KIND, }; use frame_system::{EventRecord, Phase}; use sp_runtime::Perbill; @@ -160,20 +160,18 @@ fn doesnt_deposit_event_for_dups() { #[test] fn reports_if_an_offence_is_dup() { - type TestOffence = Offence; - new_test_ext().execute_with(|| { let time_slot = 42; assert_eq!(offence_reports(KIND, time_slot), vec![]); let offence = - |time_slot, offenders| TestOffence { validator_set_count: 5, time_slot, offenders }; + |time_slot, offenders| Offence { validator_set_count: 5, time_slot, offenders }; let mut test_offence = offence(time_slot, vec![0]); // the report for authority 0 at time slot 42 should not be a known // offence - assert!(!>::is_known_offence( + assert!(!>::is_known_offence( &test_offence.offenders, &test_offence.time_slot )); @@ -182,7 +180,7 @@ fn reports_if_an_offence_is_dup() { Offences::report_offence(vec![], test_offence.clone()).unwrap(); // the same report should be a known offence now - assert!(>::is_known_offence( + assert!(>::is_known_offence( &test_offence.offenders, &test_offence.time_slot )); @@ -197,7 +195,7 @@ fn reports_if_an_offence_is_dup() { test_offence.offenders.push(1); // it should not be a known offence anymore - assert!(!>::is_known_offence( + assert!(!>::is_known_offence( &test_offence.offenders, &test_offence.time_slot )); @@ -208,7 +206,7 @@ fn reports_if_an_offence_is_dup() { // creating a new offence for the same authorities on the next slot // should be considered a new offence and thefore not known let test_offence_next_slot = offence(time_slot + 1, vec![0, 1]); - assert!(!>::is_known_offence( + assert!(!>::is_known_offence( &test_offence_next_slot.offenders, &test_offence_next_slot.time_slot )); @@ -247,48 +245,3 @@ fn should_properly_count_offences() { ); }); } - -/// We insert offences in sorted order using the time slot in the `same_kind_reports`. -/// This test ensures that it works as expected. -#[test] -fn should_properly_sort_offences() { - new_test_ext().execute_with(|| { - // given - let time_slot = 42; - assert_eq!(offence_reports(KIND, time_slot), vec![]); - - let offence1 = Offence { validator_set_count: 5, time_slot, offenders: vec![5] }; - let offence2 = Offence { validator_set_count: 5, time_slot, offenders: vec![4] }; - let offence3 = - Offence { validator_set_count: 5, time_slot: time_slot + 1, offenders: vec![6, 7] }; - let offence4 = - Offence { validator_set_count: 5, time_slot: time_slot - 1, offenders: vec![3] }; - Offences::report_offence(vec![], offence1).unwrap(); - with_on_offence_fractions(|f| { - assert_eq!(f.clone(), vec![Perbill::from_percent(25)]); - f.clear(); - }); - - // when - // report for the second time - Offences::report_offence(vec![], offence2).unwrap(); - Offences::report_offence(vec![], offence3).unwrap(); - Offences::report_offence(vec![], offence4).unwrap(); - - // then - let same_kind_reports = Vec::<(u128, sp_core::H256)>::decode( - &mut &crate::ReportsByKindIndex::::get(KIND)[..], - ) - .unwrap(); - assert_eq!( - same_kind_reports, - vec![ - (time_slot - 1, report_id(time_slot - 1, 3)), - (time_slot, report_id(time_slot, 5)), - (time_slot, report_id(time_slot, 4)), - (time_slot + 1, report_id(time_slot + 1, 6)), - (time_slot + 1, report_id(time_slot + 1, 7)), - ] - ); - }); -} diff --git a/frame/preimage/Cargo.toml b/frame/preimage/Cargo.toml index 6bc6c8c53c01..03aaa089d0c1 100644 --- a/frame/preimage/Cargo.toml +++ b/frame/preimage/Cargo.toml @@ -9,20 +9,20 @@ repository = "https://github.com/paritytech/substrate/" description = "FRAME pallet for storing preimages of hashes" [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "7.0.0", default-features = false, optional = true, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, optional = true, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } log = { version = "0.4.17", default-features = false } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/preimage/src/benchmarking.rs b/frame/preimage/src/benchmarking.rs index 8a61d7d780bf..b719c28be641 100644 --- a/frame/preimage/src/benchmarking.rs +++ b/frame/preimage/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ //! Preimage pallet benchmarking. use super::*; -use frame_benchmarking::{account, benchmarks, whitelist_account}; +use frame_benchmarking::v1::{account, benchmarks, whitelist_account, BenchmarkError}; use frame_support::assert_ok; use frame_system::RawOrigin; use sp_runtime::traits::Bounded; @@ -62,7 +62,11 @@ benchmarks! { let caller = funded_account::("caller", 0); whitelist_account!(caller); let (preimage, hash) = sized_preimage_and_hash::(s); - assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); + assert_ok!(Preimage::::request_preimage( + T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"), + hash, + )); }: note_preimage(RawOrigin::Signed(caller), preimage) verify { assert!(Preimage::::have_preimage(&hash)); @@ -71,9 +75,15 @@ benchmarks! { note_no_deposit_preimage { let s in 0 .. MAX_SIZE; let (preimage, hash) = sized_preimage_and_hash::(s); - assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); - }: note_preimage(T::ManagerOrigin::successful_origin(), preimage) - verify { + assert_ok!(Preimage::::request_preimage( + T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"), + hash, + )); + }: note_preimage( + T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, + preimage + ) verify { assert!(Preimage::::have_preimage(&hash)); } @@ -90,9 +100,15 @@ benchmarks! { // Cheap unnote - will not unreserve since there's no deposit held. unnote_no_deposit_preimage { let (preimage, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::note_preimage(T::ManagerOrigin::successful_origin(), preimage)); - }: unnote_preimage(T::ManagerOrigin::successful_origin(), hash) - verify { + assert_ok!(Preimage::::note_preimage( + T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"), + preimage, + )); + }: unnote_preimage( + T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, + hash + ) verify { assert!(!Preimage::::have_preimage(&hash)); } @@ -102,8 +118,10 @@ benchmarks! { let noter = funded_account::("noter", 0); whitelist_account!(noter); assert_ok!(Preimage::::note_preimage(RawOrigin::Signed(noter.clone()).into(), preimage)); - }: _(T::ManagerOrigin::successful_origin(), hash) - verify { + }: _( + T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, + hash + ) verify { let deposit = T::BaseDeposit::get() + T::ByteDeposit::get() * MAX_SIZE.into(); let s = RequestStatus::Requested { deposit: Some((noter, deposit)), count: 1, len: Some(MAX_SIZE) }; assert_eq!(StatusFor::::get(&hash), Some(s)); @@ -111,26 +129,40 @@ benchmarks! { // Cheap request - would unreserve the deposit but none was held. request_no_deposit_preimage { let (preimage, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::note_preimage(T::ManagerOrigin::successful_origin(), preimage)); - }: request_preimage(T::ManagerOrigin::successful_origin(), hash) - verify { + assert_ok!(Preimage::::note_preimage( + T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"), + preimage, + )); + }: request_preimage( + T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, + hash + ) verify { let s = RequestStatus::Requested { deposit: None, count: 2, len: Some(MAX_SIZE) }; assert_eq!(StatusFor::::get(&hash), Some(s)); } // Cheap request - the preimage is not yet noted, so deposit to unreserve. request_unnoted_preimage { let (_, hash) = preimage_and_hash::(); - }: request_preimage(T::ManagerOrigin::successful_origin(), hash) - verify { + }: request_preimage( + T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, + hash + ) verify { let s = RequestStatus::Requested { deposit: None, count: 1, len: None }; assert_eq!(StatusFor::::get(&hash), Some(s)); } // Cheap request - the preimage is already requested, so just a counter bump. request_requested_preimage { let (_, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); - }: request_preimage(T::ManagerOrigin::successful_origin(), hash) - verify { + assert_ok!(Preimage::::request_preimage( + T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"), + hash, + )); + }: request_preimage( + T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, + hash + ) verify { let s = RequestStatus::Requested { deposit: None, count: 2, len: None }; assert_eq!(StatusFor::::get(&hash), Some(s)); } @@ -138,27 +170,53 @@ benchmarks! { // Expensive unrequest - last reference and it's noted, so will destroy the preimage. unrequest_preimage { let (preimage, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); - assert_ok!(Preimage::::note_preimage(T::ManagerOrigin::successful_origin(), preimage)); - }: _(T::ManagerOrigin::successful_origin(), hash) - verify { + assert_ok!(Preimage::::request_preimage( + T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"), + hash, + )); + assert_ok!(Preimage::::note_preimage( + T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"), + preimage, + )); + }: _( + T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, + hash + ) verify { assert_eq!(StatusFor::::get(&hash), None); } // Cheap unrequest - last reference, but it's not noted. unrequest_unnoted_preimage { let (_, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); - }: unrequest_preimage(T::ManagerOrigin::successful_origin(), hash) - verify { + assert_ok!(Preimage::::request_preimage( + T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"), + hash, + )); + }: unrequest_preimage( + T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, + hash + ) verify { assert_eq!(StatusFor::::get(&hash), None); } // Cheap unrequest - not the last reference. unrequest_multi_referenced_preimage { let (_, hash) = preimage_and_hash::(); - assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); - assert_ok!(Preimage::::request_preimage(T::ManagerOrigin::successful_origin(), hash)); - }: unrequest_preimage(T::ManagerOrigin::successful_origin(), hash) - verify { + assert_ok!(Preimage::::request_preimage( + T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"), + hash, + )); + assert_ok!(Preimage::::request_preimage( + T::ManagerOrigin::try_successful_origin() + .expect("ManagerOrigin has no successful origin required for the benchmark"), + hash, + )); + }: unrequest_preimage( + T::ManagerOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, + hash + ) verify { let s = RequestStatus::Requested { deposit: None, count: 1, len: None }; assert_eq!(StatusFor::::get(&hash), Some(s)); } diff --git a/frame/preimage/src/lib.rs b/frame/preimage/src/lib.rs index bf7d602057ca..5ab1e7643b2e 100644 --- a/frame/preimage/src/lib.rs +++ b/frame/preimage/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -107,9 +107,8 @@ pub mod pallet { } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] - pub struct Pallet(PhantomData); + pub struct Pallet(_); #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] diff --git a/frame/preimage/src/migration.rs b/frame/preimage/src/migration.rs index a5d15c23c758..46e555498cd2 100644 --- a/frame/preimage/src/migration.rs +++ b/frame/preimage/src/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,6 +24,11 @@ use frame_support::{ }; use sp_std::collections::btree_map::BTreeMap; +#[cfg(feature = "try-runtime")] +use frame_support::ensure; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + /// The log target. const TARGET: &'static str = "runtime::preimage::migration::v1"; @@ -78,8 +83,8 @@ pub mod v1 { impl OnRuntimeUpgrade for Migration { #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - assert_eq!(StorageVersion::get::>(), 0, "can only upgrade from version 0"); + fn pre_upgrade() -> Result, TryRuntimeError> { + ensure!(StorageVersion::get::>() == 0, "can only upgrade from version 0"); let images = v0::image_count::().expect("v0 storage corrupted"); log::info!(target: TARGET, "Migrating {} images", &images); @@ -148,7 +153,7 @@ pub mod v1 { } #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), &'static str> { + fn post_upgrade(state: Vec) -> DispatchResult { let old_images: u32 = Decode::decode(&mut &state[..]).expect("pre_upgrade provides a valid state; qed"); let new_images = image_count::().expect("V1 storage corrupted"); @@ -161,7 +166,7 @@ pub mod v1 { old_images ); } - assert_eq!(StorageVersion::get::>(), 1, "must upgrade"); + ensure!(StorageVersion::get::>() == 1, "must upgrade"); Ok(()) } } diff --git a/frame/preimage/src/mock.rs b/frame/preimage/src/mock.rs index d81f5a9c3232..2fb9f36dec45 100644 --- a/frame/preimage/src/mock.rs +++ b/frame/preimage/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,18 +28,14 @@ use frame_support::{ use frame_system::EnsureSignedBy; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { System: frame_system, Balances: pallet_balances, @@ -54,13 +50,12 @@ impl frame_system::Config for Test { type DbWeight = RocksDbWeight; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -84,6 +79,10 @@ impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = ConstU32<50>; type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } ord_parameter_types! { @@ -100,7 +99,7 @@ impl Config for Test { } pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let balances = pallet_balances::GenesisConfig:: { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)], }; diff --git a/frame/preimage/src/tests.rs b/frame/preimage/src/tests.rs index f480b9c36b67..63a178c408a3 100644 --- a/frame/preimage/src/tests.rs +++ b/frame/preimage/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/preimage/src/weights.rs b/frame/preimage/src/weights.rs index e73c986891cc..41e58a102780 100644 --- a/frame/preimage/src/weights.rs +++ b/frame/preimage/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_preimage //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_preimage +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_preimage. pub trait WeightInfo { @@ -64,206 +69,314 @@ pub trait WeightInfo { /// Weights for pallet_preimage using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Preimage StatusFor (r:1 w:1) - // Storage: Preimage PreimageFor (r:0 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Preimage PreimageFor (r:0 w:1) + /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: MaxEncodedLen) /// The range of component `s` is `[0, 4194304]`. fn note_preimage(s: u32, ) -> Weight { - // Minimum execution time: 33_810 nanoseconds. - Weight::from_ref_time(34_299_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_703 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `143` + // Estimated: `3556` + // Minimum execution time: 30_479_000 picoseconds. + Weight::from_parts(23_381_775, 3556) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_670, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) - // Storage: Preimage PreimageFor (r:0 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Preimage PreimageFor (r:0 w:1) + /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: MaxEncodedLen) /// The range of component `s` is `[0, 4194304]`. fn note_requested_preimage(s: u32, ) -> Weight { - // Minimum execution time: 24_398 nanoseconds. - Weight::from_ref_time(24_839_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_702 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `3556` + // Minimum execution time: 16_104_000 picoseconds. + Weight::from_parts(18_393_879, 3556) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_669, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) - // Storage: Preimage PreimageFor (r:0 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Preimage PreimageFor (r:0 w:1) + /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: MaxEncodedLen) /// The range of component `s` is `[0, 4194304]`. fn note_no_deposit_preimage(s: u32, ) -> Weight { - // Minimum execution time: 22_235 nanoseconds. - Weight::from_ref_time(22_473_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_703 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `3556` + // Minimum execution time: 15_652_000 picoseconds. + Weight::from_parts(22_031_627, 3556) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_672, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) - // Storage: Preimage PreimageFor (r:0 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Preimage PreimageFor (r:0 w:1) + /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: MaxEncodedLen) fn unnote_preimage() -> Weight { - // Minimum execution time: 43_241 nanoseconds. - Weight::from_ref_time(44_470_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3556` + // Minimum execution time: 37_148_000 picoseconds. + Weight::from_parts(40_247_000, 3556) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) - // Storage: Preimage PreimageFor (r:0 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Preimage PreimageFor (r:0 w:1) + /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: MaxEncodedLen) fn unnote_no_deposit_preimage() -> Weight { - // Minimum execution time: 29_529 nanoseconds. - Weight::from_ref_time(30_364_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `144` + // Estimated: `3556` + // Minimum execution time: 19_909_000 picoseconds. + Weight::from_parts(21_572_000, 3556) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) fn request_preimage() -> Weight { - // Minimum execution time: 28_914 nanoseconds. - Weight::from_ref_time(30_103_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `188` + // Estimated: `3556` + // Minimum execution time: 17_602_000 picoseconds. + Weight::from_parts(18_899_000, 3556) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) fn request_no_deposit_preimage() -> Weight { - // Minimum execution time: 14_479 nanoseconds. - Weight::from_ref_time(15_244_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `144` + // Estimated: `3556` + // Minimum execution time: 11_253_000 picoseconds. + Weight::from_parts(11_667_000, 3556) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) fn request_unnoted_preimage() -> Weight { - // Minimum execution time: 20_171 nanoseconds. - Weight::from_ref_time(20_806_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `3556` + // Minimum execution time: 14_152_000 picoseconds. + Weight::from_parts(14_652_000, 3556) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) fn request_requested_preimage() -> Weight { - // Minimum execution time: 9_756 nanoseconds. - Weight::from_ref_time(10_115_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `3556` + // Minimum execution time: 8_267_000 picoseconds. + Weight::from_parts(8_969_000, 3556) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) - // Storage: Preimage PreimageFor (r:0 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Preimage PreimageFor (r:0 w:1) + /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: MaxEncodedLen) fn unrequest_preimage() -> Weight { - // Minimum execution time: 28_379 nanoseconds. - Weight::from_ref_time(29_778_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `144` + // Estimated: `3556` + // Minimum execution time: 18_429_000 picoseconds. + Weight::from_parts(18_946_000, 3556) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) fn unrequest_unnoted_preimage() -> Weight { - // Minimum execution time: 9_595 nanoseconds. - Weight::from_ref_time(9_888_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `3556` + // Minimum execution time: 7_910_000 picoseconds. + Weight::from_parts(8_272_000, 3556) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) fn unrequest_multi_referenced_preimage() -> Weight { - // Minimum execution time: 9_642 nanoseconds. - Weight::from_ref_time(9_985_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `3556` + // Minimum execution time: 7_936_000 picoseconds. + Weight::from_parts(8_504_000, 3556) + .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: Preimage StatusFor (r:1 w:1) - // Storage: Preimage PreimageFor (r:0 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Preimage PreimageFor (r:0 w:1) + /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: MaxEncodedLen) /// The range of component `s` is `[0, 4194304]`. fn note_preimage(s: u32, ) -> Weight { - // Minimum execution time: 33_810 nanoseconds. - Weight::from_ref_time(34_299_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_703 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `143` + // Estimated: `3556` + // Minimum execution time: 30_479_000 picoseconds. + Weight::from_parts(23_381_775, 3556) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_670, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) - // Storage: Preimage PreimageFor (r:0 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Preimage PreimageFor (r:0 w:1) + /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: MaxEncodedLen) /// The range of component `s` is `[0, 4194304]`. fn note_requested_preimage(s: u32, ) -> Weight { - // Minimum execution time: 24_398 nanoseconds. - Weight::from_ref_time(24_839_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_702 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `3556` + // Minimum execution time: 16_104_000 picoseconds. + Weight::from_parts(18_393_879, 3556) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_669, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) - // Storage: Preimage PreimageFor (r:0 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Preimage PreimageFor (r:0 w:1) + /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: MaxEncodedLen) /// The range of component `s` is `[0, 4194304]`. fn note_no_deposit_preimage(s: u32, ) -> Weight { - // Minimum execution time: 22_235 nanoseconds. - Weight::from_ref_time(22_473_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_703 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `3556` + // Minimum execution time: 15_652_000 picoseconds. + Weight::from_parts(22_031_627, 3556) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_672, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) - // Storage: Preimage PreimageFor (r:0 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Preimage PreimageFor (r:0 w:1) + /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: MaxEncodedLen) fn unnote_preimage() -> Weight { - // Minimum execution time: 43_241 nanoseconds. - Weight::from_ref_time(44_470_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `289` + // Estimated: `3556` + // Minimum execution time: 37_148_000 picoseconds. + Weight::from_parts(40_247_000, 3556) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) - // Storage: Preimage PreimageFor (r:0 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Preimage PreimageFor (r:0 w:1) + /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: MaxEncodedLen) fn unnote_no_deposit_preimage() -> Weight { - // Minimum execution time: 29_529 nanoseconds. - Weight::from_ref_time(30_364_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `144` + // Estimated: `3556` + // Minimum execution time: 19_909_000 picoseconds. + Weight::from_parts(21_572_000, 3556) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) fn request_preimage() -> Weight { - // Minimum execution time: 28_914 nanoseconds. - Weight::from_ref_time(30_103_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `188` + // Estimated: `3556` + // Minimum execution time: 17_602_000 picoseconds. + Weight::from_parts(18_899_000, 3556) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) fn request_no_deposit_preimage() -> Weight { - // Minimum execution time: 14_479 nanoseconds. - Weight::from_ref_time(15_244_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `144` + // Estimated: `3556` + // Minimum execution time: 11_253_000 picoseconds. + Weight::from_parts(11_667_000, 3556) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) fn request_unnoted_preimage() -> Weight { - // Minimum execution time: 20_171 nanoseconds. - Weight::from_ref_time(20_806_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `42` + // Estimated: `3556` + // Minimum execution time: 14_152_000 picoseconds. + Weight::from_parts(14_652_000, 3556) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) fn request_requested_preimage() -> Weight { - // Minimum execution time: 9_756 nanoseconds. - Weight::from_ref_time(10_115_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `3556` + // Minimum execution time: 8_267_000 picoseconds. + Weight::from_parts(8_969_000, 3556) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) - // Storage: Preimage PreimageFor (r:0 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Preimage PreimageFor (r:0 w:1) + /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: MaxEncodedLen) fn unrequest_preimage() -> Weight { - // Minimum execution time: 28_379 nanoseconds. - Weight::from_ref_time(29_778_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `144` + // Estimated: `3556` + // Minimum execution time: 18_429_000 picoseconds. + Weight::from_parts(18_946_000, 3556) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) fn unrequest_unnoted_preimage() -> Weight { - // Minimum execution time: 9_595 nanoseconds. - Weight::from_ref_time(9_888_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `3556` + // Minimum execution time: 7_910_000 picoseconds. + Weight::from_parts(8_272_000, 3556) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) fn unrequest_multi_referenced_preimage() -> Weight { - // Minimum execution time: 9_642 nanoseconds. - Weight::from_ref_time(9_985_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `106` + // Estimated: `3556` + // Minimum execution time: 7_936_000 picoseconds. + Weight::from_parts(8_504_000, 3556) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } diff --git a/frame/proxy/Cargo.toml b/frame/proxy/Cargo.toml index 5c69fcfb9f55..8ae7f001b4bf 100644 --- a/frame/proxy/Cargo.toml +++ b/frame/proxy/Cargo.toml @@ -13,19 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["max-encoded-len"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["max-encoded-len"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-utility = { version = "4.0.0-dev", path = "../utility" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/proxy/README.md b/frame/proxy/README.md index 2eb83fab6d72..bfe26d9aefbc 100644 --- a/frame/proxy/README.md +++ b/frame/proxy/README.md @@ -6,8 +6,8 @@ The accounts to which permission is delegated may be requied to announce the act wish to execute some duration prior to execution happens. In this case, the target account may reject the announcement and in doing so, veto the execution. -- [`proxy::Config`](https://docs.rs/pallet-proxy/latest/pallet_proxy/trait.Config.html) -- [`Call`](https://docs.rs/pallet-proxy/latest/pallet_proxy/enum.Call.html) +- [`Config`](https://docs.rs/pallet-proxy/latest/pallet_proxy/pallet/trait.Config.html) +- [`Call`](https://docs.rs/pallet-proxy/latest/pallet_proxy/pallet/enum.Call.html) ## Overview diff --git a/frame/proxy/src/benchmarking.rs b/frame/proxy/src/benchmarking.rs index 58c0cb73011d..e0d14163d21b 100644 --- a/frame/proxy/src/benchmarking.rs +++ b/frame/proxy/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,8 +21,8 @@ use super::*; use crate::Pallet as Proxy; -use frame_benchmarking::{account, benchmarks, whitelisted_caller}; -use frame_system::RawOrigin; +use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller}; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use sp_runtime::traits::Bounded; const SEED: u32 = 0; @@ -41,7 +41,7 @@ fn add_proxies(n: u32, maybe_who: Option) -> Result<(), RawOrigin::Signed(caller.clone()).into(), real, T::ProxyType::default(), - T::BlockNumber::zero(), + BlockNumberFor::::zero(), )?; } Ok(()) @@ -64,7 +64,7 @@ fn add_announcements( RawOrigin::Signed(real.clone()).into(), caller_lookup, T::ProxyType::default(), - T::BlockNumber::zero(), + BlockNumberFor::::zero(), )?; real }; @@ -187,7 +187,7 @@ benchmarks! { RawOrigin::Signed(caller.clone()), real, T::ProxyType::default(), - T::BlockNumber::zero() + BlockNumberFor::::zero() ) verify { let (proxies, _) = Proxies::::get(caller); @@ -202,7 +202,7 @@ benchmarks! { RawOrigin::Signed(caller.clone()), delegate, T::ProxyType::default(), - T::BlockNumber::zero() + BlockNumberFor::::zero() ) verify { let (proxies, _) = Proxies::::get(caller); @@ -224,7 +224,7 @@ benchmarks! { }: _( RawOrigin::Signed(caller.clone()), T::ProxyType::default(), - T::BlockNumber::zero(), + BlockNumberFor::::zero(), 0 ) verify { @@ -246,7 +246,7 @@ benchmarks! { Pallet::::create_pure( RawOrigin::Signed(whitelisted_caller()).into(), T::ProxyType::default(), - T::BlockNumber::zero(), + BlockNumberFor::::zero(), 0 )?; let height = system::Pallet::::block_number(); diff --git a/frame/proxy/src/lib.rs b/frame/proxy/src/lib.rs index d98534d16a21..33e9fcfade35 100644 --- a/frame/proxy/src/lib.rs +++ b/frame/proxy/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,7 +40,8 @@ use frame_support::{ traits::{Currency, Get, InstanceFilter, IsSubType, IsType, OriginTrait, ReservableCurrency}, RuntimeDebug, }; -use frame_system::{self as system}; +use frame_system::{self as system, ensure_signed, pallet_prelude::BlockNumberFor}; +pub use pallet::*; use scale_info::TypeInfo; use sp_io::hashing::blake2_256; use sp_runtime::{ @@ -50,8 +51,6 @@ use sp_runtime::{ use sp_std::prelude::*; pub use weights::WeightInfo; -pub use pallet::*; - type CallHashOf = <::CallHasher as Hash>::Output; type BalanceOf = @@ -102,7 +101,6 @@ pub mod pallet { use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); /// Configuration trait. @@ -183,8 +181,6 @@ pub mod pallet { /// Dispatch the given `call` from an account that the sender is authorised for through /// `add_proxy`. /// - /// Removes any corresponding announcement(s). - /// /// The dispatch origin for this call must be _Signed_. /// /// Parameters: @@ -231,7 +227,7 @@ pub mod pallet { origin: OriginFor, delegate: AccountIdLookupOf, proxy_type: T::ProxyType, - delay: T::BlockNumber, + delay: BlockNumberFor, ) -> DispatchResult { let who = ensure_signed(origin)?; let delegate = T::Lookup::lookup(delegate)?; @@ -251,7 +247,7 @@ pub mod pallet { origin: OriginFor, delegate: AccountIdLookupOf, proxy_type: T::ProxyType, - delay: T::BlockNumber, + delay: BlockNumberFor, ) -> DispatchResult { let who = ensure_signed(origin)?; let delegate = T::Lookup::lookup(delegate)?; @@ -268,9 +264,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::remove_proxies(T::MaxProxies::get()))] pub fn remove_proxies(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let (_, old_deposit) = Proxies::::take(&who); - T::Currency::unreserve(&who, old_deposit); - + Self::remove_all_proxy_delegates(&who); Ok(()) } @@ -297,7 +291,7 @@ pub mod pallet { pub fn create_pure( origin: OriginFor, proxy_type: T::ProxyType, - delay: T::BlockNumber, + delay: BlockNumberFor, index: u16, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -347,7 +341,7 @@ pub mod pallet { spawner: AccountIdLookupOf, proxy_type: T::ProxyType, index: u16, - #[pallet::compact] height: T::BlockNumber, + #[pallet::compact] height: BlockNumberFor, #[pallet::compact] ext_index: u32, ) -> DispatchResult { let who = ensure_signed(origin)?; @@ -541,14 +535,14 @@ pub mod pallet { delegator: T::AccountId, delegatee: T::AccountId, proxy_type: T::ProxyType, - delay: T::BlockNumber, + delay: BlockNumberFor, }, /// A proxy was removed. ProxyRemoved { delegator: T::AccountId, delegatee: T::AccountId, proxy_type: T::ProxyType, - delay: T::BlockNumber, + delay: BlockNumberFor, }, } @@ -581,7 +575,10 @@ pub mod pallet { Twox64Concat, T::AccountId, ( - BoundedVec, T::MaxProxies>, + BoundedVec< + ProxyDefinition>, + T::MaxProxies, + >, BalanceOf, ), ValueQuery, @@ -595,7 +592,7 @@ pub mod pallet { Twox64Concat, T::AccountId, ( - BoundedVec, T::BlockNumber>, T::MaxPending>, + BoundedVec, BlockNumberFor>, T::MaxPending>, BalanceOf, ), ValueQuery, @@ -618,7 +615,7 @@ impl Pallet { who: &T::AccountId, proxy_type: &T::ProxyType, index: u16, - maybe_when: Option<(T::BlockNumber, u32)>, + maybe_when: Option<(BlockNumberFor, u32)>, ) -> T::AccountId { let (height, ext_index) = maybe_when.unwrap_or_else(|| { ( @@ -644,7 +641,7 @@ impl Pallet { delegator: &T::AccountId, delegatee: T::AccountId, proxy_type: T::ProxyType, - delay: T::BlockNumber, + delay: BlockNumberFor, ) -> DispatchResult { ensure!(delegator != &delegatee, Error::::NoSelfProxy); Proxies::::try_mutate(delegator, |(ref mut proxies, ref mut deposit)| { @@ -684,7 +681,7 @@ impl Pallet { delegator: &T::AccountId, delegatee: T::AccountId, proxy_type: T::ProxyType, - delay: T::BlockNumber, + delay: BlockNumberFor, ) -> DispatchResult { Proxies::::try_mutate_exists(delegator, |x| { let (mut proxies, old_deposit) = x.take().ok_or(Error::::NotFound)?; @@ -740,7 +737,7 @@ impl Pallet { } fn edit_announcements< - F: FnMut(&Announcement, T::BlockNumber>) -> bool, + F: FnMut(&Announcement, BlockNumberFor>) -> bool, >( delegate: &T::AccountId, f: F, @@ -766,8 +763,8 @@ impl Pallet { real: &T::AccountId, delegate: &T::AccountId, force_proxy_type: Option, - ) -> Result, DispatchError> { - let f = |x: &ProxyDefinition| -> bool { + ) -> Result>, DispatchError> { + let f = |x: &ProxyDefinition>| -> bool { &x.delegate == delegate && force_proxy_type.as_ref().map_or(true, |y| &x.proxy_type == y) }; @@ -775,7 +772,7 @@ impl Pallet { } fn do_proxy( - def: ProxyDefinition, + def: ProxyDefinition>, real: T::AccountId, call: ::RuntimeCall, ) { @@ -802,4 +799,13 @@ impl Pallet { let e = call.dispatch(origin); Self::deposit_event(Event::ProxyExecuted { result: e.map(|_| ()).map_err(|e| e.error) }); } + + /// Removes all proxy delegates for a given delegator. + /// + /// Parameters: + /// - `delegator`: The delegator account. + pub fn remove_all_proxy_delegates(delegator: &T::AccountId) { + let (_, old_deposit) = Proxies::::take(&delegator); + T::Currency::unreserve(&delegator, old_deposit); + } } diff --git a/frame/proxy/src/tests.rs b/frame/proxy/src/tests.rs index 3eb3ab370533..1f4d2617700e 100644 --- a/frame/proxy/src/tests.rs +++ b/frame/proxy/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,20 +31,16 @@ use frame_support::{ }; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Proxy: proxy::{Pallet, Call, Storage, Event}, Utility: pallet_utility::{Pallet, Call, Event}, @@ -57,14 +53,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -88,6 +83,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } impl pallet_utility::Config for Test { type RuntimeEvent = RuntimeEvent; @@ -124,7 +123,10 @@ impl InstanceFilter for ProxyType { match self { ProxyType::Any => true, ProxyType::JustTransfer => { - matches!(c, RuntimeCall::Balances(pallet_balances::Call::transfer { .. })) + matches!( + c, + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. }) + ) }, ProxyType::JustUtility => matches!(c, RuntimeCall::Utility { .. }), } @@ -161,15 +163,15 @@ impl Config for Test { use super::{Call as ProxyCall, Event as ProxyEvent}; use frame_system::Call as SystemCall; -use pallet_balances::{Call as BalancesCall, Error as BalancesError, Event as BalancesEvent}; +use pallet_balances::{Call as BalancesCall, Event as BalancesEvent}; use pallet_utility::{Call as UtilityCall, Event as UtilityEvent}; type SystemError = frame_system::Error; pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { - balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 2)], + balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 3)], } .assimilate_storage(&mut t) .unwrap(); @@ -193,7 +195,7 @@ fn expect_events(e: Vec) { } fn call_transfer(dest: u64, value: u64) -> RuntimeCall { - RuntimeCall::Balances(BalancesCall::transfer { dest, value }) + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value }) } #[test] @@ -288,6 +290,23 @@ fn announcer_must_be_proxy() { }); } +#[test] +fn calling_proxy_doesnt_remove_announcement() { + new_test_ext().execute_with(|| { + assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::Any, 0)); + + let call = Box::new(call_transfer(6, 1)); + let call_hash = BlakeTwo256::hash_of(&call); + + assert_ok!(Proxy::announce(RuntimeOrigin::signed(2), 1, call_hash)); + assert_ok!(Proxy::proxy(RuntimeOrigin::signed(2), 1, None, call)); + + // The announcement is not removed by calling proxy. + let announcements = Announcements::::get(2); + assert_eq!(announcements.0, vec![Announcement { real: 1, call_hash, height: 1 }]); + }); +} + #[test] fn delayed_requires_pre_announcement() { new_test_ext().execute_with(|| { @@ -328,7 +347,7 @@ fn proxy_announced_removes_announcement_and_returns_deposit() { #[test] fn filtering_works() { new_test_ext().execute_with(|| { - assert!(Balances::mutate_account(&1, |a| a.free = 1000).is_ok()); + Balances::make_free_balance_be(&1, 1000); assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::Any, 0)); assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 3, ProxyType::JustTransfer, 0)); assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 4, ProxyType::JustUtility, 0)); @@ -344,7 +363,7 @@ fn filtering_works() { ); let derivative_id = Utility::derivative_account_id(1, 0); - assert!(Balances::mutate_account(&derivative_id, |a| a.free = 1000).is_ok()); + Balances::make_free_balance_be(&derivative_id, 1000); let inner = Box::new(call_transfer(6, 1)); let call = Box::new(RuntimeCall::Utility(UtilityCall::as_derivative { @@ -499,7 +518,7 @@ fn cannot_add_proxy_without_balance() { assert_eq!(Balances::reserved_balance(5), 2); assert_noop!( Proxy::add_proxy(RuntimeOrigin::signed(5), 4, ProxyType::Any, 0), - BalancesError::::InsufficientBalance + DispatchError::ConsumerRemaining, ); }); } @@ -547,6 +566,7 @@ fn proxying_works() { #[test] fn pure_works() { new_test_ext().execute_with(|| { + Balances::make_free_balance_be(&1, 11); // An extra one for the ED. assert_ok!(Proxy::create_pure(RuntimeOrigin::signed(1), ProxyType::Any, 0, 0)); let anon = Proxy::pure_account(&1, &ProxyType::Any, 0, None); System::assert_last_event( @@ -575,7 +595,7 @@ fn pure_works() { assert_ok!(Proxy::create_pure(RuntimeOrigin::signed(1), ProxyType::Any, 0, 0)); let call = Box::new(call_transfer(6, 1)); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(3), anon, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(3), anon, 5)); assert_ok!(Proxy::proxy(RuntimeOrigin::signed(1), anon, None, call)); System::assert_last_event(ProxyEvent::ProxyExecuted { result: Ok(()) }.into()); assert_eq!(Balances::free_balance(6), 1); @@ -594,9 +614,9 @@ fn pure_works() { Proxy::kill_pure(RuntimeOrigin::signed(1), 1, ProxyType::Any, 0, 1, 0), Error::::NoPermission ); - assert_eq!(Balances::free_balance(1), 0); + assert_eq!(Balances::free_balance(1), 1); assert_ok!(Proxy::proxy(RuntimeOrigin::signed(1), anon, None, call.clone())); - assert_eq!(Balances::free_balance(1), 2); + assert_eq!(Balances::free_balance(1), 3); assert_noop!( Proxy::proxy(RuntimeOrigin::signed(1), anon, None, call.clone()), Error::::NotProxy diff --git a/frame/proxy/src/weights.rs b/frame/proxy/src/weights.rs index 706810d3402e..f30fe73d27ae 100644 --- a/frame/proxy/src/weights.rs +++ b/frame/proxy/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_proxy //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_proxy +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_proxy. pub trait WeightInfo { @@ -62,244 +67,334 @@ pub trait WeightInfo { /// Weights for pallet_proxy using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Proxy Proxies (r:1 w:0) + /// Storage: Proxy Proxies (r:1 w:0) + /// Proof: Proxy Proxies (max_values: None, max_size: Some(1241), added: 3716, mode: MaxEncodedLen) /// The range of component `p` is `[1, 31]`. fn proxy(p: u32, ) -> Weight { - // Minimum execution time: 24_285 nanoseconds. - Weight::from_ref_time(25_355_667 as u64) - // Standard Error: 1_468 - .saturating_add(Weight::from_ref_time(38_185 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) + // Proof Size summary in bytes: + // Measured: `161 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 15_182_000 picoseconds. + Weight::from_parts(15_919_146, 4706) + // Standard Error: 1_586 + .saturating_add(Weight::from_parts(31_768, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) } - // Storage: Proxy Proxies (r:1 w:0) - // Storage: Proxy Announcements (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Proxy Proxies (r:1 w:0) + /// Proof: Proxy Proxies (max_values: None, max_size: Some(1241), added: 3716, mode: MaxEncodedLen) + /// Storage: Proxy Announcements (r:1 w:1) + /// Proof: Proxy Announcements (max_values: None, max_size: Some(2233), added: 4708, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `a` is `[0, 31]`. /// The range of component `p` is `[1, 31]`. fn proxy_announced(a: u32, p: u32, ) -> Weight { - // Minimum execution time: 44_948 nanoseconds. - Weight::from_ref_time(44_762_064 as u64) - // Standard Error: 1_778 - .saturating_add(Weight::from_ref_time(118_940 as u64).saturating_mul(a as u64)) - // Standard Error: 1_837 - .saturating_add(Weight::from_ref_time(51_232 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `488 + a * (68 ±0) + p * (37 ±0)` + // Estimated: `5698` + // Minimum execution time: 40_256_000 picoseconds. + Weight::from_parts(40_373_648, 5698) + // Standard Error: 3_978 + .saturating_add(Weight::from_parts(166_936, 0).saturating_mul(a.into())) + // Standard Error: 4_110 + .saturating_add(Weight::from_parts(54_329, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Proxy Announcements (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Proxy Announcements (r:1 w:1) + /// Proof: Proxy Announcements (max_values: None, max_size: Some(2233), added: 4708, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `a` is `[0, 31]`. /// The range of component `p` is `[1, 31]`. fn remove_announcement(a: u32, p: u32, ) -> Weight { - // Minimum execution time: 31_274 nanoseconds. - Weight::from_ref_time(32_219_165 as u64) - // Standard Error: 1_832 - .saturating_add(Weight::from_ref_time(132_454 as u64).saturating_mul(a as u64)) - // Standard Error: 1_893 - .saturating_add(Weight::from_ref_time(9_077 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `403 + a * (68 ±0)` + // Estimated: `5698` + // Minimum execution time: 25_040_000 picoseconds. + Weight::from_parts(25_112_188, 5698) + // Standard Error: 2_143 + .saturating_add(Weight::from_parts(189_027, 0).saturating_mul(a.into())) + // Standard Error: 2_214 + .saturating_add(Weight::from_parts(26_683, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Proxy Announcements (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Proxy Announcements (r:1 w:1) + /// Proof: Proxy Announcements (max_values: None, max_size: Some(2233), added: 4708, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `a` is `[0, 31]`. /// The range of component `p` is `[1, 31]`. fn reject_announcement(a: u32, p: u32, ) -> Weight { - // Minimum execution time: 31_219 nanoseconds. - Weight::from_ref_time(32_439_563 as u64) - // Standard Error: 1_829 - .saturating_add(Weight::from_ref_time(120_251 as u64).saturating_mul(a as u64)) - // Standard Error: 1_890 - .saturating_add(Weight::from_ref_time(8_689 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `403 + a * (68 ±0)` + // Estimated: `5698` + // Minimum execution time: 24_884_000 picoseconds. + Weight::from_parts(25_359_291, 5698) + // Standard Error: 2_019 + .saturating_add(Weight::from_parts(181_470, 0).saturating_mul(a.into())) + // Standard Error: 2_086 + .saturating_add(Weight::from_parts(17_725, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Proxy Proxies (r:1 w:0) - // Storage: Proxy Announcements (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Proxy Proxies (r:1 w:0) + /// Proof: Proxy Proxies (max_values: None, max_size: Some(1241), added: 3716, mode: MaxEncodedLen) + /// Storage: Proxy Announcements (r:1 w:1) + /// Proof: Proxy Announcements (max_values: None, max_size: Some(2233), added: 4708, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `a` is `[0, 31]`. /// The range of component `p` is `[1, 31]`. fn announce(a: u32, p: u32, ) -> Weight { - // Minimum execution time: 40_388 nanoseconds. - Weight::from_ref_time(40_718_245 as u64) - // Standard Error: 1_821 - .saturating_add(Weight::from_ref_time(129_674 as u64).saturating_mul(a as u64)) - // Standard Error: 1_882 - .saturating_add(Weight::from_ref_time(56_001 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `420 + a * (68 ±0) + p * (37 ±0)` + // Estimated: `5698` + // Minimum execution time: 35_039_000 picoseconds. + Weight::from_parts(36_727_868, 5698) + // Standard Error: 4_463 + .saturating_add(Weight::from_parts(167_060, 0).saturating_mul(a.into())) + // Standard Error: 4_611 + .saturating_add(Weight::from_parts(59_836, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Proxy Proxies (r:1 w:1) + /// Storage: Proxy Proxies (r:1 w:1) + /// Proof: Proxy Proxies (max_values: None, max_size: Some(1241), added: 3716, mode: MaxEncodedLen) /// The range of component `p` is `[1, 31]`. fn add_proxy(p: u32, ) -> Weight { - // Minimum execution time: 33_997 nanoseconds. - Weight::from_ref_time(34_840_036 as u64) - // Standard Error: 1_659 - .saturating_add(Weight::from_ref_time(71_349 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `161 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 25_697_000 picoseconds. + Weight::from_parts(26_611_090, 4706) + // Standard Error: 2_306 + .saturating_add(Weight::from_parts(85_165, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Proxy Proxies (r:1 w:1) + /// Storage: Proxy Proxies (r:1 w:1) + /// Proof: Proxy Proxies (max_values: None, max_size: Some(1241), added: 3716, mode: MaxEncodedLen) /// The range of component `p` is `[1, 31]`. fn remove_proxy(p: u32, ) -> Weight { - // Minimum execution time: 33_900 nanoseconds. - Weight::from_ref_time(35_069_110 as u64) - // Standard Error: 1_848 - .saturating_add(Weight::from_ref_time(82_380 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `161 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 25_638_000 picoseconds. + Weight::from_parts(26_904_510, 4706) + // Standard Error: 2_669 + .saturating_add(Weight::from_parts(61_668, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Proxy Proxies (r:1 w:1) + /// Storage: Proxy Proxies (r:1 w:1) + /// Proof: Proxy Proxies (max_values: None, max_size: Some(1241), added: 3716, mode: MaxEncodedLen) /// The range of component `p` is `[1, 31]`. fn remove_proxies(p: u32, ) -> Weight { - // Minimum execution time: 29_627 nanoseconds. - Weight::from_ref_time(30_641_642 as u64) - // Standard Error: 1_495 - .saturating_add(Weight::from_ref_time(51_919 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `161 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 22_737_000 picoseconds. + Weight::from_parts(23_618_441, 4706) + // Standard Error: 1_729 + .saturating_add(Weight::from_parts(44_009, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) - // Storage: Proxy Proxies (r:1 w:1) + /// Storage: Proxy Proxies (r:1 w:1) + /// Proof: Proxy Proxies (max_values: None, max_size: Some(1241), added: 3716, mode: MaxEncodedLen) /// The range of component `p` is `[1, 31]`. fn create_pure(p: u32, ) -> Weight { - // Minimum execution time: 37_761 nanoseconds. - Weight::from_ref_time(38_748_697 as u64) - // Standard Error: 1_594 - .saturating_add(Weight::from_ref_time(19_022 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `173` + // Estimated: `4706` + // Minimum execution time: 27_364_000 picoseconds. + Weight::from_parts(28_632_271, 4706) + // Standard Error: 1_613 + .saturating_add(Weight::from_parts(2_453, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Proxy Proxies (r:1 w:1) + /// Storage: Proxy Proxies (r:1 w:1) + /// Proof: Proxy Proxies (max_values: None, max_size: Some(1241), added: 3716, mode: MaxEncodedLen) /// The range of component `p` is `[0, 30]`. fn kill_pure(p: u32, ) -> Weight { - // Minimum execution time: 31_145 nanoseconds. - Weight::from_ref_time(31_933_568 as u64) - // Standard Error: 1_492 - .saturating_add(Weight::from_ref_time(50_250 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `198 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 23_552_000 picoseconds. + Weight::from_parts(24_874_553, 4706) + // Standard Error: 1_919 + .saturating_add(Weight::from_parts(38_799, 0).saturating_mul(p.into())) + .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: Proxy Proxies (r:1 w:0) + /// Storage: Proxy Proxies (r:1 w:0) + /// Proof: Proxy Proxies (max_values: None, max_size: Some(1241), added: 3716, mode: MaxEncodedLen) /// The range of component `p` is `[1, 31]`. fn proxy(p: u32, ) -> Weight { - // Minimum execution time: 24_285 nanoseconds. - Weight::from_ref_time(25_355_667 as u64) - // Standard Error: 1_468 - .saturating_add(Weight::from_ref_time(38_185 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) + // Proof Size summary in bytes: + // Measured: `161 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 15_182_000 picoseconds. + Weight::from_parts(15_919_146, 4706) + // Standard Error: 1_586 + .saturating_add(Weight::from_parts(31_768, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) } - // Storage: Proxy Proxies (r:1 w:0) - // Storage: Proxy Announcements (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Proxy Proxies (r:1 w:0) + /// Proof: Proxy Proxies (max_values: None, max_size: Some(1241), added: 3716, mode: MaxEncodedLen) + /// Storage: Proxy Announcements (r:1 w:1) + /// Proof: Proxy Announcements (max_values: None, max_size: Some(2233), added: 4708, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `a` is `[0, 31]`. /// The range of component `p` is `[1, 31]`. fn proxy_announced(a: u32, p: u32, ) -> Weight { - // Minimum execution time: 44_948 nanoseconds. - Weight::from_ref_time(44_762_064 as u64) - // Standard Error: 1_778 - .saturating_add(Weight::from_ref_time(118_940 as u64).saturating_mul(a as u64)) - // Standard Error: 1_837 - .saturating_add(Weight::from_ref_time(51_232 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `488 + a * (68 ±0) + p * (37 ±0)` + // Estimated: `5698` + // Minimum execution time: 40_256_000 picoseconds. + Weight::from_parts(40_373_648, 5698) + // Standard Error: 3_978 + .saturating_add(Weight::from_parts(166_936, 0).saturating_mul(a.into())) + // Standard Error: 4_110 + .saturating_add(Weight::from_parts(54_329, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Proxy Announcements (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Proxy Announcements (r:1 w:1) + /// Proof: Proxy Announcements (max_values: None, max_size: Some(2233), added: 4708, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `a` is `[0, 31]`. /// The range of component `p` is `[1, 31]`. fn remove_announcement(a: u32, p: u32, ) -> Weight { - // Minimum execution time: 31_274 nanoseconds. - Weight::from_ref_time(32_219_165 as u64) - // Standard Error: 1_832 - .saturating_add(Weight::from_ref_time(132_454 as u64).saturating_mul(a as u64)) - // Standard Error: 1_893 - .saturating_add(Weight::from_ref_time(9_077 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `403 + a * (68 ±0)` + // Estimated: `5698` + // Minimum execution time: 25_040_000 picoseconds. + Weight::from_parts(25_112_188, 5698) + // Standard Error: 2_143 + .saturating_add(Weight::from_parts(189_027, 0).saturating_mul(a.into())) + // Standard Error: 2_214 + .saturating_add(Weight::from_parts(26_683, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Proxy Announcements (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Proxy Announcements (r:1 w:1) + /// Proof: Proxy Announcements (max_values: None, max_size: Some(2233), added: 4708, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `a` is `[0, 31]`. /// The range of component `p` is `[1, 31]`. fn reject_announcement(a: u32, p: u32, ) -> Weight { - // Minimum execution time: 31_219 nanoseconds. - Weight::from_ref_time(32_439_563 as u64) - // Standard Error: 1_829 - .saturating_add(Weight::from_ref_time(120_251 as u64).saturating_mul(a as u64)) - // Standard Error: 1_890 - .saturating_add(Weight::from_ref_time(8_689 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `403 + a * (68 ±0)` + // Estimated: `5698` + // Minimum execution time: 24_884_000 picoseconds. + Weight::from_parts(25_359_291, 5698) + // Standard Error: 2_019 + .saturating_add(Weight::from_parts(181_470, 0).saturating_mul(a.into())) + // Standard Error: 2_086 + .saturating_add(Weight::from_parts(17_725, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Proxy Proxies (r:1 w:0) - // Storage: Proxy Announcements (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Proxy Proxies (r:1 w:0) + /// Proof: Proxy Proxies (max_values: None, max_size: Some(1241), added: 3716, mode: MaxEncodedLen) + /// Storage: Proxy Announcements (r:1 w:1) + /// Proof: Proxy Announcements (max_values: None, max_size: Some(2233), added: 4708, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `a` is `[0, 31]`. /// The range of component `p` is `[1, 31]`. fn announce(a: u32, p: u32, ) -> Weight { - // Minimum execution time: 40_388 nanoseconds. - Weight::from_ref_time(40_718_245 as u64) - // Standard Error: 1_821 - .saturating_add(Weight::from_ref_time(129_674 as u64).saturating_mul(a as u64)) - // Standard Error: 1_882 - .saturating_add(Weight::from_ref_time(56_001 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `420 + a * (68 ±0) + p * (37 ±0)` + // Estimated: `5698` + // Minimum execution time: 35_039_000 picoseconds. + Weight::from_parts(36_727_868, 5698) + // Standard Error: 4_463 + .saturating_add(Weight::from_parts(167_060, 0).saturating_mul(a.into())) + // Standard Error: 4_611 + .saturating_add(Weight::from_parts(59_836, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Proxy Proxies (r:1 w:1) + /// Storage: Proxy Proxies (r:1 w:1) + /// Proof: Proxy Proxies (max_values: None, max_size: Some(1241), added: 3716, mode: MaxEncodedLen) /// The range of component `p` is `[1, 31]`. fn add_proxy(p: u32, ) -> Weight { - // Minimum execution time: 33_997 nanoseconds. - Weight::from_ref_time(34_840_036 as u64) - // Standard Error: 1_659 - .saturating_add(Weight::from_ref_time(71_349 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `161 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 25_697_000 picoseconds. + Weight::from_parts(26_611_090, 4706) + // Standard Error: 2_306 + .saturating_add(Weight::from_parts(85_165, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Proxy Proxies (r:1 w:1) + /// Storage: Proxy Proxies (r:1 w:1) + /// Proof: Proxy Proxies (max_values: None, max_size: Some(1241), added: 3716, mode: MaxEncodedLen) /// The range of component `p` is `[1, 31]`. fn remove_proxy(p: u32, ) -> Weight { - // Minimum execution time: 33_900 nanoseconds. - Weight::from_ref_time(35_069_110 as u64) - // Standard Error: 1_848 - .saturating_add(Weight::from_ref_time(82_380 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `161 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 25_638_000 picoseconds. + Weight::from_parts(26_904_510, 4706) + // Standard Error: 2_669 + .saturating_add(Weight::from_parts(61_668, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Proxy Proxies (r:1 w:1) + /// Storage: Proxy Proxies (r:1 w:1) + /// Proof: Proxy Proxies (max_values: None, max_size: Some(1241), added: 3716, mode: MaxEncodedLen) /// The range of component `p` is `[1, 31]`. fn remove_proxies(p: u32, ) -> Weight { - // Minimum execution time: 29_627 nanoseconds. - Weight::from_ref_time(30_641_642 as u64) - // Standard Error: 1_495 - .saturating_add(Weight::from_ref_time(51_919 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `161 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 22_737_000 picoseconds. + Weight::from_parts(23_618_441, 4706) + // Standard Error: 1_729 + .saturating_add(Weight::from_parts(44_009, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) - // Storage: Proxy Proxies (r:1 w:1) + /// Storage: Proxy Proxies (r:1 w:1) + /// Proof: Proxy Proxies (max_values: None, max_size: Some(1241), added: 3716, mode: MaxEncodedLen) /// The range of component `p` is `[1, 31]`. fn create_pure(p: u32, ) -> Weight { - // Minimum execution time: 37_761 nanoseconds. - Weight::from_ref_time(38_748_697 as u64) - // Standard Error: 1_594 - .saturating_add(Weight::from_ref_time(19_022 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `173` + // Estimated: `4706` + // Minimum execution time: 27_364_000 picoseconds. + Weight::from_parts(28_632_271, 4706) + // Standard Error: 1_613 + .saturating_add(Weight::from_parts(2_453, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Proxy Proxies (r:1 w:1) + /// Storage: Proxy Proxies (r:1 w:1) + /// Proof: Proxy Proxies (max_values: None, max_size: Some(1241), added: 3716, mode: MaxEncodedLen) /// The range of component `p` is `[0, 30]`. fn kill_pure(p: u32, ) -> Weight { - // Minimum execution time: 31_145 nanoseconds. - Weight::from_ref_time(31_933_568 as u64) - // Standard Error: 1_492 - .saturating_add(Weight::from_ref_time(50_250 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `198 + p * (37 ±0)` + // Estimated: `4706` + // Minimum execution time: 23_552_000 picoseconds. + Weight::from_parts(24_874_553, 4706) + // Standard Error: 1_919 + .saturating_add(Weight::from_parts(38_799, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } diff --git a/frame/ranked-collective/Cargo.toml b/frame/ranked-collective/Cargo.toml index e19aaa443971..1007cae37b02 100644 --- a/frame/ranked-collective/Cargo.toml +++ b/frame/ranked-collective/Cargo.toml @@ -13,17 +13,17 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { version = "0.4.16", default-features = false } scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [features] default = ["std"] diff --git a/frame/ranked-collective/src/benchmarking.rs b/frame/ranked-collective/src/benchmarking.rs index eb629b330abb..b610d10009a0 100644 --- a/frame/ranked-collective/src/benchmarking.rs +++ b/frame/ranked-collective/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,9 @@ use super::*; #[allow(unused_imports)] use crate::Pallet as RankedCollective; -use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller}; +use frame_benchmarking::v1::{ + account, benchmarks_instance_pallet, whitelisted_caller, BenchmarkError, +}; use frame_support::{assert_ok, dispatch::UnfilteredDispatchable}; use frame_system::RawOrigin as SystemOrigin; @@ -35,13 +37,15 @@ fn make_member, I: 'static>(rank: Rank) -> T::AccountId { let who = account::("member", MemberCount::::get(0), SEED); let who_lookup = T::Lookup::unlookup(who.clone()); assert_ok!(Pallet::::add_member( - T::PromoteOrigin::successful_origin(), - who_lookup.clone() + T::PromoteOrigin::try_successful_origin() + .expect("PromoteOrigin has no successful origin required for the benchmark"), + who_lookup.clone(), )); for _ in 0..rank { assert_ok!(Pallet::::promote_member( - T::PromoteOrigin::successful_origin(), - who_lookup.clone() + T::PromoteOrigin::try_successful_origin() + .expect("PromoteOrigin has no successful origin required for the benchmark"), + who_lookup.clone(), )); } who @@ -51,7 +55,8 @@ benchmarks_instance_pallet! { add_member { let who = account::("member", 0, SEED); let who_lookup = T::Lookup::unlookup(who.clone()); - let origin = T::PromoteOrigin::successful_origin(); + let origin = + T::PromoteOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let call = Call::::add_member { who: who_lookup }; }: { call.dispatch_bypass_filter(origin)? } verify { @@ -67,7 +72,8 @@ benchmarks_instance_pallet! { let who_lookup = T::Lookup::unlookup(who.clone()); let last = make_member::(rank); let last_index = (0..=rank).map(|r| IdToIndex::::get(r, &last).unwrap()).collect::>(); - let origin = T::DemoteOrigin::successful_origin(); + let origin = + T::DemoteOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let call = Call::::remove_member { who: who_lookup, min_rank: rank }; }: { call.dispatch_bypass_filter(origin)? } verify { @@ -83,7 +89,8 @@ benchmarks_instance_pallet! { let rank = r as u16; let who = make_member::(rank); let who_lookup = T::Lookup::unlookup(who.clone()); - let origin = T::PromoteOrigin::successful_origin(); + let origin = + T::PromoteOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let call = Call::::promote_member { who: who_lookup }; }: { call.dispatch_bypass_filter(origin)? } verify { @@ -99,7 +106,8 @@ benchmarks_instance_pallet! { let who_lookup = T::Lookup::unlookup(who.clone()); let last = make_member::(rank); let last_index = IdToIndex::::get(rank, &last).unwrap(); - let origin = T::DemoteOrigin::successful_origin(); + let origin = + T::DemoteOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let call = Call::::demote_member { who: who_lookup }; }: { call.dispatch_bypass_filter(origin)? } verify { @@ -115,14 +123,19 @@ benchmarks_instance_pallet! { vote { let caller: T::AccountId = whitelisted_caller(); let caller_lookup = T::Lookup::unlookup(caller.clone()); - assert_ok!(Pallet::::add_member(T::PromoteOrigin::successful_origin(), caller_lookup.clone())); + assert_ok!(Pallet::::add_member( + T::PromoteOrigin::try_successful_origin() + .expect("PromoteOrigin has no successful origin required for the benchmark"), + caller_lookup.clone(), + )); // Create a poll let class = T::Polls::classes().into_iter().next().unwrap(); let rank = T::MinRankOfClass::convert(class.clone()); for _ in 0..rank { assert_ok!(Pallet::::promote_member( - T::PromoteOrigin::successful_origin(), - caller_lookup.clone() + T::PromoteOrigin::try_successful_origin() + .expect("PromoteOrigin has no successful origin required for the benchmark"), + caller_lookup.clone(), )); } diff --git a/frame/ranked-collective/src/lib.rs b/frame/ranked-collective/src/lib.rs index b057a5750802..fe1308cd034f 100644 --- a/frame/ranked-collective/src/lib.rs +++ b/frame/ranked-collective/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -53,8 +53,8 @@ use sp_std::{marker::PhantomData, prelude::*}; use frame_support::{ codec::{Decode, Encode, MaxEncodedLen}, dispatch::{DispatchError, DispatchResultWithPostInfo, PostDispatchInfo}, - ensure, - traits::{EnsureOrigin, PollStatus, Polling, VoteTally}, + ensure, impl_ensure_origin_with_arg_ignoring_arg, + traits::{EnsureOrigin, EnsureOriginWithArg, PollStatus, Polling, RankedMembers, VoteTally}, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; @@ -112,36 +112,39 @@ impl, I: 'static, M: GetMaxVoters> Tally { pub type TallyOf = Tally>; pub type PollIndexOf = <>::Polls as Polling>>::Index; +pub type ClassOf = <>::Polls as Polling>>::Class; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; -impl, I: 'static, M: GetMaxVoters> VoteTally for Tally { - fn new(_: Rank) -> Self { +impl, I: 'static, M: GetMaxVoters>> + VoteTally> for Tally +{ + fn new(_: ClassOf) -> Self { Self { bare_ayes: 0, ayes: 0, nays: 0, dummy: PhantomData } } - fn ayes(&self, _: Rank) -> Votes { + fn ayes(&self, _: ClassOf) -> Votes { self.bare_ayes } - fn support(&self, class: Rank) -> Perbill { + fn support(&self, class: ClassOf) -> Perbill { Perbill::from_rational(self.bare_ayes, M::get_max_voters(class)) } - fn approval(&self, _: Rank) -> Perbill { + fn approval(&self, _: ClassOf) -> Perbill { Perbill::from_rational(self.ayes, 1.max(self.ayes + self.nays)) } #[cfg(feature = "runtime-benchmarks")] - fn unanimity(class: Rank) -> Self { + fn unanimity(class: ClassOf) -> Self { Self { - bare_ayes: M::get_max_voters(class), + bare_ayes: M::get_max_voters(class.clone()), ayes: M::get_max_voters(class), nays: 0, dummy: PhantomData, } } #[cfg(feature = "runtime-benchmarks")] - fn rejection(class: Rank) -> Self { + fn rejection(class: ClassOf) -> Self { Self { bare_ayes: 0, ayes: 0, nays: M::get_max_voters(class), dummy: PhantomData } } #[cfg(feature = "runtime-benchmarks")] - fn from_requirements(support: Perbill, approval: Perbill, class: Rank) -> Self { + fn from_requirements(support: Perbill, approval: Perbill, class: ClassOf) -> Self { let c = M::get_max_voters(class); let ayes = support * c; let nays = ((ayes as u64) * 1_000_000_000u64 / approval.deconstruct() as u64) as u32 - ayes; @@ -149,14 +152,17 @@ impl, I: 'static, M: GetMaxVoters> VoteTally for Tally } #[cfg(feature = "runtime-benchmarks")] - fn setup(class: Rank, granularity: Perbill) { - if M::get_max_voters(class) == 0 { + fn setup(class: ClassOf, granularity: Perbill) { + if M::get_max_voters(class.clone()) == 0 { let max_voters = granularity.saturating_reciprocal_mul(1u32); for i in 0..max_voters { let who: T::AccountId = frame_benchmarking::account("ranked_collective_benchmarking", i, 0); - crate::Pallet::::do_add_member_to_rank(who, class) - .expect("could not add members for benchmarks"); + crate::Pallet::::do_add_member_to_rank( + who, + T::MinRankOfClass::convert(class.clone()), + ) + .expect("could not add members for benchmarks"); } assert_eq!(M::get_max_voters(class), max_voters); } @@ -170,6 +176,13 @@ pub struct MemberRecord { rank: Rank, } +impl MemberRecord { + // Constructs a new instance of [`MemberRecord`]. + pub fn new(rank: Rank) -> Self { + Self { rank } + } +} + /// Record needed for every vote. #[derive(PartialEq, Eq, Clone, Copy, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum VoteRecord { @@ -227,14 +240,17 @@ impl Convert for Geometric { } } -/// Trait for getting the maximum number of voters for a given rank. +/// Trait for getting the maximum number of voters for a given poll class. pub trait GetMaxVoters { - /// Return the maximum number of voters for the rank `r`. - fn get_max_voters(r: Rank) -> MemberIndex; + /// Poll class type. + type Class; + /// Return the maximum number of voters for the poll class `c`. + fn get_max_voters(c: Self::Class) -> MemberIndex; } impl, I: 'static> GetMaxVoters for Pallet { - fn get_max_voters(r: Rank) -> MemberIndex { - MemberCount::::get(r) + type Class = ClassOf; + fn get_max_voters(c: Self::Class) -> MemberIndex { + MemberCount::::get(T::MinRankOfClass::convert(c)) } } @@ -247,7 +263,7 @@ impl, I: 'static, const MIN_RANK: u16> EnsureOrigin Result { - let who = frame_system::EnsureSigned::try_origin(o)?; + let who = as EnsureOrigin<_>>::try_origin(o)?; match Members::::get(&who) { Some(MemberRecord { rank, .. }) if rank >= MIN_RANK => Ok(rank), _ => Err(frame_system::RawOrigin::Signed(who).into()), @@ -256,22 +272,37 @@ impl, I: 'static, const MIN_RANK: u16> EnsureOrigin Result { - let who = IndexToId::::get(MIN_RANK, 0).ok_or(())?; - Ok(frame_system::RawOrigin::Signed(who).into()) + as EnsureOrigin<_>>::try_successful_origin() } +} - #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> T::RuntimeOrigin { - match Self::try_successful_origin() { - Ok(o) => o, - Err(()) => { - let who: T::AccountId = frame_benchmarking::whitelisted_caller(); - crate::Pallet::::do_add_member_to_rank(who.clone(), MIN_RANK) - .expect("failed to add ranked member"); - frame_system::RawOrigin::Signed(who).into() - }, +impl_ensure_origin_with_arg_ignoring_arg! { + impl<{ T: Config, I: 'static, const MIN_RANK: u16, A }> + EnsureOriginWithArg for EnsureRanked + {} +} + +/// Guard to ensure that the given origin is a member of the collective. The rank of the member is +/// the `Success` value. +pub struct EnsureOfRank(PhantomData<(T, I)>); +impl, I: 'static> EnsureOriginWithArg for EnsureOfRank { + type Success = (T::AccountId, Rank); + + fn try_origin(o: T::RuntimeOrigin, min_rank: &Rank) -> Result { + let who = as EnsureOrigin<_>>::try_origin(o)?; + match Members::::get(&who) { + Some(MemberRecord { rank, .. }) if rank >= *min_rank => Ok((who, rank)), + _ => Err(frame_system::RawOrigin::Signed(who).into()), } } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(min_rank: &Rank) -> Result { + let who = frame_benchmarking::account::("successful_origin", 0, 0); + crate::Pallet::::do_add_member_to_rank(who.clone(), *min_rank) + .expect("Could not add members for benchmarks"); + Ok(frame_system::RawOrigin::Signed(who).into()) + } } /// Guard to ensure that the given origin is a member of the collective. The account ID of the @@ -283,7 +314,7 @@ impl, I: 'static, const MIN_RANK: u16> EnsureOrigin Result { - let who = frame_system::EnsureSigned::try_origin(o)?; + let who = as EnsureOrigin<_>>::try_origin(o)?; match Members::::get(&who) { Some(MemberRecord { rank, .. }) if rank >= MIN_RANK => Ok(who), _ => Err(frame_system::RawOrigin::Signed(who).into()), @@ -292,22 +323,14 @@ impl, I: 'static, const MIN_RANK: u16> EnsureOrigin Result { - let who = IndexToId::::get(MIN_RANK, 0).ok_or(())?; - Ok(frame_system::RawOrigin::Signed(who).into()) + as EnsureOrigin<_>>::try_successful_origin() } +} - #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> T::RuntimeOrigin { - match Self::try_successful_origin() { - Ok(o) => o, - Err(()) => { - let who: T::AccountId = frame_benchmarking::whitelisted_caller(); - crate::Pallet::::do_add_member_to_rank(who.clone(), MIN_RANK) - .expect("failed to add ranked member"); - frame_system::RawOrigin::Signed(who).into() - }, - } - } +impl_ensure_origin_with_arg_ignoring_arg! { + impl<{ T: Config, I: 'static, const MIN_RANK: u16, A }> + EnsureOriginWithArg for EnsureMember + {} } /// Guard to ensure that the given origin is a member of the collective. The pair of both the @@ -319,7 +342,7 @@ impl, I: 'static, const MIN_RANK: u16> EnsureOrigin Result { - let who = frame_system::EnsureSigned::try_origin(o)?; + let who = as EnsureOrigin<_>>::try_origin(o)?; match Members::::get(&who) { Some(MemberRecord { rank, .. }) if rank >= MIN_RANK => Ok((who, rank)), _ => Err(frame_system::RawOrigin::Signed(who).into()), @@ -328,22 +351,17 @@ impl, I: 'static, const MIN_RANK: u16> EnsureOrigin Result { - let who = IndexToId::::get(MIN_RANK, 0).ok_or(())?; + let who = frame_benchmarking::account::("successful_origin", 0, 0); + crate::Pallet::::do_add_member_to_rank(who.clone(), MIN_RANK) + .expect("Could not add members for benchmarks"); Ok(frame_system::RawOrigin::Signed(who).into()) } +} - #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> T::RuntimeOrigin { - match Self::try_successful_origin() { - Ok(o) => o, - Err(()) => { - let who: T::AccountId = frame_benchmarking::whitelisted_caller(); - crate::Pallet::::do_add_member_to_rank(who.clone(), MIN_RANK) - .expect("failed to add ranked member"); - frame_system::RawOrigin::Signed(who).into() - }, - } - } +impl_ensure_origin_with_arg_ignoring_arg! { + impl<{ T: Config, I: 'static, const MIN_RANK: u16, A }> + EnsureOriginWithArg for EnsureRankedMember + {} } #[frame_support::pallet] @@ -353,7 +371,6 @@ pub mod pallet { use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(PhantomData<(T, I)>); #[pallet::config] @@ -374,12 +391,12 @@ pub mod pallet { type DemoteOrigin: EnsureOrigin; /// The polling system used for our voting. - type Polls: Polling, Votes = Votes, Moment = Self::BlockNumber>; + type Polls: Polling, Votes = Votes, Moment = BlockNumberFor>; /// Convert the tally class into the minimum rank required to vote on the poll. If /// `Polls::Class` is the same type as `Rank`, then `Identity` can be used here to mean /// "a rank of at least the poll class". - type MinRankOfClass: Convert<>>::Class, Rank>; + type MinRankOfClass: Convert, Rank>; /// Convert a rank_delta into a number of votes the rank gets. /// @@ -504,24 +521,7 @@ pub mod pallet { pub fn demote_member(origin: OriginFor, who: AccountIdLookupOf) -> DispatchResult { let max_rank = T::DemoteOrigin::ensure_origin(origin)?; let who = T::Lookup::lookup(who)?; - let mut record = Self::ensure_member(&who)?; - let rank = record.rank; - ensure!(max_rank >= rank, Error::::NoPermission); - - Self::remove_from_rank(&who, rank)?; - let maybe_rank = rank.checked_sub(1); - match maybe_rank { - None => { - Members::::remove(&who); - Self::deposit_event(Event::MemberRemoved { who, rank: 0 }); - }, - Some(rank) => { - record.rank = rank; - Members::::insert(&who, &record); - Self::deposit_event(Event::RankChanged { who, rank }); - }, - } - Ok(()) + Self::do_demote_member(who, Some(max_rank)) } /// Remove the member entirely. @@ -691,7 +691,7 @@ pub mod pallet { Ok(()) } - /// Promotes a member in the ranked collective into the next role. + /// Promotes a member in the ranked collective into the next higher rank. /// /// A `maybe_max_rank` may be provided to check that the member does not get promoted beyond /// a certain rank. Is `None` is provided, then the rank will be incremented without checks. @@ -713,6 +713,33 @@ pub mod pallet { Ok(()) } + /// Demotes a member in the ranked collective into the next lower rank. + /// + /// A `maybe_max_rank` may be provided to check that the member does not get demoted from + /// a certain rank. Is `None` is provided, then the rank will be decremented without checks. + fn do_demote_member(who: T::AccountId, maybe_max_rank: Option) -> DispatchResult { + let mut record = Self::ensure_member(&who)?; + let rank = record.rank; + if let Some(max_rank) = maybe_max_rank { + ensure!(max_rank >= rank, Error::::NoPermission); + } + + Self::remove_from_rank(&who, rank)?; + let maybe_rank = rank.checked_sub(1); + match maybe_rank { + None => { + Members::::remove(&who); + Self::deposit_event(Event::MemberRemoved { who, rank: 0 }); + }, + Some(rank) => { + record.rank = rank; + Members::::insert(&who, &record); + Self::deposit_event(Event::RankChanged { who, rank }); + }, + } + Ok(()) + } + /// Add a member to the rank collective, and continue to promote them until a certain rank /// is reached. pub fn do_add_member_to_rank(who: T::AccountId, rank: Rank) -> DispatchResult { @@ -722,5 +749,39 @@ pub mod pallet { } Ok(()) } + + /// Determine the rank of the account behind the `Signed` origin `o`, `None` if the account + /// is unknown to this collective or `o` is not `Signed`. + pub fn as_rank( + o: &::PalletsOrigin, + ) -> Option { + use frame_support::traits::CallerTrait; + o.as_signed().and_then(Self::rank_of) + } + } + + impl, I: 'static> RankedMembers for Pallet { + type AccountId = T::AccountId; + type Rank = Rank; + + fn min_rank() -> Self::Rank { + 0 + } + + fn rank_of(who: &Self::AccountId) -> Option { + Some(Self::ensure_member(&who).ok()?.rank) + } + + fn induct(who: &Self::AccountId) -> DispatchResult { + Self::do_add_member(who.clone()) + } + + fn promote(who: &Self::AccountId) -> DispatchResult { + Self::do_promote_member(who.clone(), None) + } + + fn demote(who: &Self::AccountId) -> DispatchResult { + Self::do_demote_member(who.clone(), None) + } } } diff --git a/frame/ranked-collective/src/tests.rs b/frame/ranked-collective/src/tests.rs index c11d4877d357..ba8c5a0f937b 100644 --- a/frame/ranked-collective/src/tests.rs +++ b/frame/ranked-collective/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,25 +25,22 @@ use frame_support::{ parameter_types, traits::{ConstU16, ConstU32, ConstU64, EitherOf, Everything, MapSuccess, Polling}, }; -use sp_core::H256; +use sp_core::{Get, H256}; use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, Identity, IdentityLookup, ReduceBy}, + traits::{BlakeTwo256, IdentityLookup, ReduceBy}, + BuildStorage, }; use super::*; use crate as pallet_ranked_collective; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; +type Class = Rank; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Club: pallet_ranked_collective::{Pallet, Call, Storage, Event}, } ); @@ -54,14 +51,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -95,7 +91,7 @@ impl Polling> for TestPolls { type Index = u8; type Votes = Votes; type Moment = u64; - type Class = Rank; + type Class = Class; fn classes() -> Vec { vec![0, 1, 2] } @@ -164,6 +160,19 @@ impl Polling> for TestPolls { } } +/// Convert the tally class into the minimum rank required to vote on the poll. +/// MinRank(Class) = Class - Delta +pub struct MinRankOfClass(PhantomData); +impl> Convert for MinRankOfClass { + fn convert(a: Class) -> Rank { + a.saturating_sub(Delta::get()) + } +} + +parameter_types! { + pub static MinRankOfClassDelta: Rank = 0; +} + impl Config for Test { type WeightInfo = (); type RuntimeEvent = RuntimeEvent; @@ -180,12 +189,12 @@ impl Config for Test { MapSuccess, ReduceBy>>, >; type Polls = TestPolls; - type MinRankOfClass = Identity; + type MinRankOfClass = MinRankOfClass; type VoteWeight = Geometric; } pub fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); ext @@ -450,34 +459,52 @@ fn ensure_ranked_works() { type Rank2 = EnsureRanked; type Rank3 = EnsureRanked; type Rank4 = EnsureRanked; - assert_eq!(Rank1::try_origin(RuntimeOrigin::signed(1)).unwrap(), 1); - assert_eq!(Rank1::try_origin(RuntimeOrigin::signed(2)).unwrap(), 2); - assert_eq!(Rank1::try_origin(RuntimeOrigin::signed(3)).unwrap(), 3); + assert_eq!(>::try_origin(RuntimeOrigin::signed(1)).unwrap(), 1); + assert_eq!(>::try_origin(RuntimeOrigin::signed(2)).unwrap(), 2); + assert_eq!(>::try_origin(RuntimeOrigin::signed(3)).unwrap(), 3); assert_eq!( - Rank2::try_origin(RuntimeOrigin::signed(1)).unwrap_err().as_signed().unwrap(), + >::try_origin(RuntimeOrigin::signed(1)) + .unwrap_err() + .into_signer() + .unwrap(), 1 ); - assert_eq!(Rank2::try_origin(RuntimeOrigin::signed(2)).unwrap(), 2); - assert_eq!(Rank2::try_origin(RuntimeOrigin::signed(3)).unwrap(), 3); + assert_eq!(>::try_origin(RuntimeOrigin::signed(2)).unwrap(), 2); + assert_eq!(>::try_origin(RuntimeOrigin::signed(3)).unwrap(), 3); assert_eq!( - Rank3::try_origin(RuntimeOrigin::signed(1)).unwrap_err().as_signed().unwrap(), + >::try_origin(RuntimeOrigin::signed(1)) + .unwrap_err() + .into_signer() + .unwrap(), 1 ); assert_eq!( - Rank3::try_origin(RuntimeOrigin::signed(2)).unwrap_err().as_signed().unwrap(), + >::try_origin(RuntimeOrigin::signed(2)) + .unwrap_err() + .into_signer() + .unwrap(), 2 ); - assert_eq!(Rank3::try_origin(RuntimeOrigin::signed(3)).unwrap(), 3); + assert_eq!(>::try_origin(RuntimeOrigin::signed(3)).unwrap(), 3); assert_eq!( - Rank4::try_origin(RuntimeOrigin::signed(1)).unwrap_err().as_signed().unwrap(), + >::try_origin(RuntimeOrigin::signed(1)) + .unwrap_err() + .into_signer() + .unwrap(), 1 ); assert_eq!( - Rank4::try_origin(RuntimeOrigin::signed(2)).unwrap_err().as_signed().unwrap(), + >::try_origin(RuntimeOrigin::signed(2)) + .unwrap_err() + .into_signer() + .unwrap(), 2 ); assert_eq!( - Rank4::try_origin(RuntimeOrigin::signed(3)).unwrap_err().as_signed().unwrap(), + >::try_origin(RuntimeOrigin::signed(3)) + .unwrap_err() + .into_signer() + .unwrap(), 3 ); }); @@ -499,3 +526,43 @@ fn do_add_member_to_rank_works() { assert_eq!(member_count(max_rank + 1), 0); }) } + +#[test] +fn tally_support_correct() { + new_test_ext().execute_with(|| { + // add members, + // rank 1: accounts 1, 2, 3 + // rank 2: accounts 2, 3 + // rank 3: accounts 3. + assert_ok!(Club::add_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 1)); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 2)); + assert_ok!(Club::add_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + assert_ok!(Club::promote_member(RuntimeOrigin::root(), 3)); + + // init tally with 1 aye vote. + let tally: TallyOf = Tally::from_parts(1, 1, 0); + + // with minRank(Class) = Class + // for class 3, 100% support. + MinRankOfClassDelta::set(0); + assert_eq!(tally.support(3), Perbill::from_rational(1u32, 1)); + + // with minRank(Class) = (Class - 1) + // for class 3, ~50% support. + MinRankOfClassDelta::set(1); + assert_eq!(tally.support(3), Perbill::from_rational(1u32, 2)); + + // with minRank(Class) = (Class - 2) + // for class 3, ~33% support. + MinRankOfClassDelta::set(2); + assert_eq!(tally.support(3), Perbill::from_rational(1u32, 3)); + + // reset back. + MinRankOfClassDelta::set(0); + }); +} diff --git a/frame/ranked-collective/src/weights.rs b/frame/ranked-collective/src/weights.rs index c054d200452e..9f1a0a818044 100644 --- a/frame/ranked-collective/src/weights.rs +++ b/frame/ranked-collective/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_ranked_collective //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_ranked_collective +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_ranked_collective. pub trait WeightInfo { @@ -58,154 +63,242 @@ pub trait WeightInfo { /// Weights for pallet_ranked_collective using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: RankedCollective Members (r:1 w:1) - // Storage: RankedCollective MemberCount (r:1 w:1) - // Storage: RankedCollective IndexToId (r:0 w:1) - // Storage: RankedCollective IdToIndex (r:0 w:1) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IndexToId (r:0 w:1) + /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:0 w:1) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) fn add_member() -> Weight { - // Minimum execution time: 24_344 nanoseconds. - Weight::from_ref_time(24_856_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3507` + // Minimum execution time: 17_245_000 picoseconds. + Weight::from_parts(17_930_000, 3507) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } - // Storage: RankedCollective Members (r:1 w:1) - // Storage: RankedCollective MemberCount (r:1 w:1) - // Storage: RankedCollective IdToIndex (r:1 w:1) - // Storage: RankedCollective IndexToId (r:1 w:1) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:11 w:11) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:11 w:11) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: RankedCollective IndexToId (r:11 w:11) + /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) /// The range of component `r` is `[0, 10]`. fn remove_member(r: u32, ) -> Weight { - // Minimum execution time: 36_881 nanoseconds. - Weight::from_ref_time(39_284_238 as u64) - // Standard Error: 16_355 - .saturating_add(Weight::from_ref_time(11_385_424 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().reads((3 as u64).saturating_mul(r as u64))) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(r as u64))) + // Proof Size summary in bytes: + // Measured: `616 + r * (281 ±0)` + // Estimated: `3519 + r * (2529 ±0)` + // Minimum execution time: 29_534_000 picoseconds. + Weight::from_parts(32_847_495, 3519) + // Standard Error: 24_211 + .saturating_add(Weight::from_parts(13_949_639, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(r.into()))) + .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 2529).saturating_mul(r.into())) } - // Storage: RankedCollective Members (r:1 w:1) - // Storage: RankedCollective MemberCount (r:1 w:1) - // Storage: RankedCollective IndexToId (r:0 w:1) - // Storage: RankedCollective IdToIndex (r:0 w:1) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IndexToId (r:0 w:1) + /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:0 w:1) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) /// The range of component `r` is `[0, 10]`. fn promote_member(r: u32, ) -> Weight { - // Minimum execution time: 27_444 nanoseconds. - Weight::from_ref_time(28_576_394 as u64) - // Standard Error: 4_818 - .saturating_add(Weight::from_ref_time(519_056 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `314 + r * (17 ±0)` + // Estimated: `3507` + // Minimum execution time: 20_333_000 picoseconds. + Weight::from_parts(21_592_224, 3507) + // Standard Error: 6_423 + .saturating_add(Weight::from_parts(321_314, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } - // Storage: RankedCollective Members (r:1 w:1) - // Storage: RankedCollective MemberCount (r:1 w:1) - // Storage: RankedCollective IdToIndex (r:1 w:1) - // Storage: RankedCollective IndexToId (r:1 w:1) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:1 w:1) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: RankedCollective IndexToId (r:1 w:1) + /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) /// The range of component `r` is `[0, 10]`. fn demote_member(r: u32, ) -> Weight { - // Minimum execution time: 36_539 nanoseconds. - Weight::from_ref_time(39_339_893 as u64) - // Standard Error: 16_526 - .saturating_add(Weight::from_ref_time(807_457 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `632 + r * (72 ±0)` + // Estimated: `3519` + // Minimum execution time: 29_446_000 picoseconds. + Weight::from_parts(32_447_715, 3519) + // Standard Error: 28_791 + .saturating_add(Weight::from_parts(822_890, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } - // Storage: RankedCollective Members (r:1 w:0) - // Storage: RankedPolls ReferendumInfoFor (r:1 w:1) - // Storage: RankedCollective Voting (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: RankedPolls ReferendumInfoFor (r:1 w:1) + /// Proof: RankedPolls ReferendumInfoFor (max_values: None, max_size: Some(330), added: 2805, mode: MaxEncodedLen) + /// Storage: RankedCollective Voting (r:1 w:1) + /// Proof: RankedCollective Voting (max_values: None, max_size: Some(65), added: 2540, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn vote() -> Weight { - // Minimum execution time: 50_548 nanoseconds. - Weight::from_ref_time(51_276_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `628` + // Estimated: `219984` + // Minimum execution time: 45_474_000 picoseconds. + Weight::from_parts(47_228_000, 219984) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } - // Storage: RankedPolls ReferendumInfoFor (r:1 w:0) - // Storage: RankedCollective VotingCleanup (r:1 w:0) - // Storage: RankedCollective Voting (r:0 w:2) + /// Storage: RankedPolls ReferendumInfoFor (r:1 w:0) + /// Proof: RankedPolls ReferendumInfoFor (max_values: None, max_size: Some(330), added: 2805, mode: MaxEncodedLen) + /// Storage: RankedCollective VotingCleanup (r:1 w:0) + /// Proof: RankedCollective VotingCleanup (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: RankedCollective Voting (r:100 w:100) + /// Proof: RankedCollective Voting (max_values: None, max_size: Some(65), added: 2540, mode: MaxEncodedLen) /// The range of component `n` is `[0, 100]`. fn cleanup_poll(n: u32, ) -> Weight { - // Minimum execution time: 16_222 nanoseconds. - Weight::from_ref_time(22_982_955 as u64) - // Standard Error: 3_863 - .saturating_add(Weight::from_ref_time(1_074_054 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(n as u64))) + // Proof Size summary in bytes: + // Measured: `462 + n * (50 ±0)` + // Estimated: `3795 + n * (2540 ±0)` + // Minimum execution time: 13_903_000 picoseconds. + Weight::from_parts(18_209_102, 3795) + // Standard Error: 2_556 + .saturating_add(Weight::from_parts(1_237_454, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2540).saturating_mul(n.into())) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: RankedCollective Members (r:1 w:1) - // Storage: RankedCollective MemberCount (r:1 w:1) - // Storage: RankedCollective IndexToId (r:0 w:1) - // Storage: RankedCollective IdToIndex (r:0 w:1) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IndexToId (r:0 w:1) + /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:0 w:1) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) fn add_member() -> Weight { - // Minimum execution time: 24_344 nanoseconds. - Weight::from_ref_time(24_856_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `142` + // Estimated: `3507` + // Minimum execution time: 17_245_000 picoseconds. + Weight::from_parts(17_930_000, 3507) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } - // Storage: RankedCollective Members (r:1 w:1) - // Storage: RankedCollective MemberCount (r:1 w:1) - // Storage: RankedCollective IdToIndex (r:1 w:1) - // Storage: RankedCollective IndexToId (r:1 w:1) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:11 w:11) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:11 w:11) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: RankedCollective IndexToId (r:11 w:11) + /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) /// The range of component `r` is `[0, 10]`. fn remove_member(r: u32, ) -> Weight { - // Minimum execution time: 36_881 nanoseconds. - Weight::from_ref_time(39_284_238 as u64) - // Standard Error: 16_355 - .saturating_add(Weight::from_ref_time(11_385_424 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().reads((3 as u64).saturating_mul(r as u64))) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(r as u64))) + // Proof Size summary in bytes: + // Measured: `616 + r * (281 ±0)` + // Estimated: `3519 + r * (2529 ±0)` + // Minimum execution time: 29_534_000 picoseconds. + Weight::from_parts(32_847_495, 3519) + // Standard Error: 24_211 + .saturating_add(Weight::from_parts(13_949_639, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(r.into()))) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(r.into()))) + .saturating_add(Weight::from_parts(0, 2529).saturating_mul(r.into())) } - // Storage: RankedCollective Members (r:1 w:1) - // Storage: RankedCollective MemberCount (r:1 w:1) - // Storage: RankedCollective IndexToId (r:0 w:1) - // Storage: RankedCollective IdToIndex (r:0 w:1) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IndexToId (r:0 w:1) + /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:0 w:1) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) /// The range of component `r` is `[0, 10]`. fn promote_member(r: u32, ) -> Weight { - // Minimum execution time: 27_444 nanoseconds. - Weight::from_ref_time(28_576_394 as u64) - // Standard Error: 4_818 - .saturating_add(Weight::from_ref_time(519_056 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `314 + r * (17 ±0)` + // Estimated: `3507` + // Minimum execution time: 20_333_000 picoseconds. + Weight::from_parts(21_592_224, 3507) + // Standard Error: 6_423 + .saturating_add(Weight::from_parts(321_314, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } - // Storage: RankedCollective Members (r:1 w:1) - // Storage: RankedCollective MemberCount (r:1 w:1) - // Storage: RankedCollective IdToIndex (r:1 w:1) - // Storage: RankedCollective IndexToId (r:1 w:1) + /// Storage: RankedCollective Members (r:1 w:1) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: RankedCollective MemberCount (r:1 w:1) + /// Proof: RankedCollective MemberCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: RankedCollective IdToIndex (r:1 w:1) + /// Proof: RankedCollective IdToIndex (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) + /// Storage: RankedCollective IndexToId (r:1 w:1) + /// Proof: RankedCollective IndexToId (max_values: None, max_size: Some(54), added: 2529, mode: MaxEncodedLen) /// The range of component `r` is `[0, 10]`. fn demote_member(r: u32, ) -> Weight { - // Minimum execution time: 36_539 nanoseconds. - Weight::from_ref_time(39_339_893 as u64) - // Standard Error: 16_526 - .saturating_add(Weight::from_ref_time(807_457 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `632 + r * (72 ±0)` + // Estimated: `3519` + // Minimum execution time: 29_446_000 picoseconds. + Weight::from_parts(32_447_715, 3519) + // Standard Error: 28_791 + .saturating_add(Weight::from_parts(822_890, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } - // Storage: RankedCollective Members (r:1 w:0) - // Storage: RankedPolls ReferendumInfoFor (r:1 w:1) - // Storage: RankedCollective Voting (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: RankedPolls ReferendumInfoFor (r:1 w:1) + /// Proof: RankedPolls ReferendumInfoFor (max_values: None, max_size: Some(330), added: 2805, mode: MaxEncodedLen) + /// Storage: RankedCollective Voting (r:1 w:1) + /// Proof: RankedCollective Voting (max_values: None, max_size: Some(65), added: 2540, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn vote() -> Weight { - // Minimum execution time: 50_548 nanoseconds. - Weight::from_ref_time(51_276_000 as u64) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `628` + // Estimated: `219984` + // Minimum execution time: 45_474_000 picoseconds. + Weight::from_parts(47_228_000, 219984) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } - // Storage: RankedPolls ReferendumInfoFor (r:1 w:0) - // Storage: RankedCollective VotingCleanup (r:1 w:0) - // Storage: RankedCollective Voting (r:0 w:2) + /// Storage: RankedPolls ReferendumInfoFor (r:1 w:0) + /// Proof: RankedPolls ReferendumInfoFor (max_values: None, max_size: Some(330), added: 2805, mode: MaxEncodedLen) + /// Storage: RankedCollective VotingCleanup (r:1 w:0) + /// Proof: RankedCollective VotingCleanup (max_values: None, max_size: Some(114), added: 2589, mode: MaxEncodedLen) + /// Storage: RankedCollective Voting (r:100 w:100) + /// Proof: RankedCollective Voting (max_values: None, max_size: Some(65), added: 2540, mode: MaxEncodedLen) /// The range of component `n` is `[0, 100]`. fn cleanup_poll(n: u32, ) -> Weight { - // Minimum execution time: 16_222 nanoseconds. - Weight::from_ref_time(22_982_955 as u64) - // Standard Error: 3_863 - .saturating_add(Weight::from_ref_time(1_074_054 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(n as u64))) + // Proof Size summary in bytes: + // Measured: `462 + n * (50 ±0)` + // Estimated: `3795 + n * (2540 ±0)` + // Minimum execution time: 13_903_000 picoseconds. + Weight::from_parts(18_209_102, 3795) + // Standard Error: 2_556 + .saturating_add(Weight::from_parts(1_237_454, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2540).saturating_mul(n.into())) } } diff --git a/frame/recovery/Cargo.toml b/frame/recovery/Cargo.toml index 3c25c0002b70..9cc15c14b747 100644 --- a/frame/recovery/Cargo.toml +++ b/frame/recovery/Cargo.toml @@ -13,18 +13,18 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/recovery/src/benchmarking.rs b/frame/recovery/src/benchmarking.rs index 870543d9bd29..2deb55bb69f2 100644 --- a/frame/recovery/src/benchmarking.rs +++ b/frame/recovery/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ use super::*; use crate::Pallet; -use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller}; use frame_support::traits::{Currency, Get}; use frame_system::RawOrigin; use sp_runtime::traits::Bounded; diff --git a/frame/recovery/src/lib.rs b/frame/recovery/src/lib.rs index 9c57ca79d2e4..9c01d25d4f76 100644 --- a/frame/recovery/src/lib.rs +++ b/frame/recovery/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -48,34 +48,30 @@ //! ### Recovery Life Cycle //! //! The intended life cycle of a successful recovery takes the following steps: -//! 1. The account owner calls `create_recovery` to set up a recovery configuration -//! for their account. -//! 2. At some later time, the account owner loses access to their account and wants -//! to recover it. Likely, they will need to create a new account and fund it with -//! enough balance to support the transaction fees and the deposit for the -//! recovery process. -//! 3. Using this new account, they call `initiate_recovery`. -//! 4. Then the account owner would contact their configured friends to vouch for -//! the recovery attempt. The account owner would provide their old account id -//! and the new account id, and friends would call `vouch_recovery` with those -//! parameters. -//! 5. Once a threshold number of friends have vouched for the recovery attempt, -//! the account owner needs to wait until the delay period has passed, starting -//! when they initiated the recovery process. -//! 6. Now the account owner is able to call `claim_recovery`, which subsequently -//! allows them to call `as_recovered` and directly make calls on-behalf-of the lost +//! 1. The account owner calls `create_recovery` to set up a recovery configuration for their //! account. -//! 7. Using the now recovered account, the account owner can call `close_recovery` -//! on the recovery process they opened, reclaiming the recovery deposit they -//! placed. +//! 2. At some later time, the account owner loses access to their account and wants to recover it. +//! Likely, they will need to create a new account and fund it with enough balance to support the +//! transaction fees and the deposit for the recovery process. +//! 3. Using this new account, they call `initiate_recovery`. +//! 4. Then the account owner would contact their configured friends to vouch for the recovery +//! attempt. The account owner would provide their old account id and the new account id, and +//! friends would call `vouch_recovery` with those parameters. +//! 5. Once a threshold number of friends have vouched for the recovery attempt, the account owner +//! needs to wait until the delay period has passed, starting when they initiated the recovery +//! process. +//! 6. Now the account owner is able to call `claim_recovery`, which subsequently allows them to +//! call `as_recovered` and directly make calls on-behalf-of the lost account. +//! 7. Using the now recovered account, the account owner can call `close_recovery` on the recovery +//! process they opened, reclaiming the recovery deposit they placed. //! 8. Then the account owner should then call `remove_recovery` to remove the recovery -//! configuration on the recovered account and reclaim the recovery configuration -//! deposit they placed. -//! 9. Using `as_recovered`, the account owner is able to call any other pallets -//! to clean up their state and reclaim any reserved or locked funds. They -//! can then transfer all funds from the recovered account to the new account. -//! 10. When the recovered account becomes reaped (i.e. its free and reserved -//! balance drops to zero), the final recovery link is removed. +//! configuration on the recovered account and reclaim the recovery configuration deposit they +//! placed. +//! 9. Using `as_recovered`, the account owner is able to call any other pallets to clean up their +//! state and reclaim any reserved or locked funds. They can then transfer all funds from the +//! recovered account to the new account. +//! 10. When the recovered account becomes reaped (i.e. its free and reserved balance drops to +//! zero), the final recovery link is removed. //! //! ### Malicious Recovery Attempts //! @@ -218,7 +214,6 @@ pub mod pallet { use sp_runtime::ArithmeticError; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); /// Configuration trait. @@ -339,7 +334,7 @@ pub mod pallet { _, Twox64Concat, T::AccountId, - RecoveryConfig, FriendsOf>, + RecoveryConfig, BalanceOf, FriendsOf>, >; /// Active recovery attempts. @@ -354,7 +349,7 @@ pub mod pallet { T::AccountId, Twox64Concat, T::AccountId, - ActiveRecovery, FriendsOf>, + ActiveRecovery, BalanceOf, FriendsOf>, >; /// The list of allowed proxy accounts. @@ -445,7 +440,7 @@ pub mod pallet { origin: OriginFor, friends: Vec, threshold: u16, - delay_period: T::BlockNumber, + delay_period: BlockNumberFor, ) -> DispatchResult { let who = ensure_signed(origin)?; // Check account is not already set up for recovery diff --git a/frame/recovery/src/mock.rs b/frame/recovery/src/mock.rs index 05ca8e6a30a0..2f2bd866a719 100644 --- a/frame/recovery/src/mock.rs +++ b/frame/recovery/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,20 +26,16 @@ use frame_support::{ }; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Recovery: recovery::{Pallet, Call, Storage, Event}, } @@ -52,13 +48,12 @@ impl frame_system::Config for Test { type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -86,6 +81,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } parameter_types! { @@ -111,7 +110,7 @@ pub type BalancesCall = pallet_balances::Call; pub type RecoveryCall = super::Call; pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100)], } diff --git a/frame/recovery/src/tests.rs b/frame/recovery/src/tests.rs index b037a8110147..85024d0bc4a9 100644 --- a/frame/recovery/src/tests.rs +++ b/frame/recovery/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -45,7 +45,10 @@ fn set_recovered_works() { // Root can set a recovered account though assert_ok!(Recovery::set_recovered(RuntimeOrigin::root(), 5, 1)); // Account 1 should now be able to make a call through account 5 - let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 1, value: 100 })); + let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death { + dest: 1, + value: 100, + })); assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call)); // Account 1 has successfully drained the funds from account 5 assert_eq!(Balances::free_balance(1), 200); @@ -93,7 +96,10 @@ fn recovery_life_cycle_works() { assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call)); // Account 1 should now be able to make a call through account 5 to get all of their funds assert_eq!(Balances::free_balance(5), 110); - let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer { dest: 1, value: 110 })); + let call = Box::new(RuntimeCall::Balances(BalancesCall::transfer_allow_death { + dest: 1, + value: 110, + })); assert_ok!(Recovery::as_recovered(RuntimeOrigin::signed(1), 5, call)); // All funds have been fully recovered! assert_eq!(Balances::free_balance(1), 200); diff --git a/frame/recovery/src/weights.rs b/frame/recovery/src/weights.rs index 39a8d09a3826..84b19ae694ee 100644 --- a/frame/recovery/src/weights.rs +++ b/frame/recovery/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_recovery //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_recovery +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_recovery. pub trait WeightInfo { @@ -61,172 +66,256 @@ pub trait WeightInfo { /// Weights for pallet_recovery using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Recovery Proxy (r:1 w:0) + /// Storage: Recovery Proxy (r:1 w:0) + /// Proof: Recovery Proxy (max_values: None, max_size: Some(80), added: 2555, mode: MaxEncodedLen) fn as_recovered() -> Weight { - // Minimum execution time: 10_672 nanoseconds. - Weight::from_ref_time(10_946_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) + // Proof Size summary in bytes: + // Measured: `281` + // Estimated: `3545` + // Minimum execution time: 9_360_000 picoseconds. + Weight::from_parts(9_773_000, 3545) + .saturating_add(T::DbWeight::get().reads(1_u64)) } - // Storage: Recovery Proxy (r:0 w:1) + /// Storage: Recovery Proxy (r:0 w:1) + /// Proof: Recovery Proxy (max_values: None, max_size: Some(80), added: 2555, mode: MaxEncodedLen) fn set_recovered() -> Weight { - // Minimum execution time: 17_092 nanoseconds. - Weight::from_ref_time(17_660_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 9_146_000 picoseconds. + Weight::from_parts(9_507_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Recovery Recoverable (r:1 w:1) + /// Storage: Recovery Recoverable (r:1 w:1) + /// Proof: Recovery Recoverable (max_values: None, max_size: Some(351), added: 2826, mode: MaxEncodedLen) /// The range of component `n` is `[1, 9]`. fn create_recovery(n: u32, ) -> Weight { - // Minimum execution time: 32_800 nanoseconds. - Weight::from_ref_time(33_769_078 as u64) - // Standard Error: 4_075 - .saturating_add(Weight::from_ref_time(252_382 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Recovery Recoverable (r:1 w:0) - // Storage: Recovery ActiveRecoveries (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `175` + // Estimated: `3816` + // Minimum execution time: 26_472_000 picoseconds. + Weight::from_parts(27_917_651, 3816) + // Standard Error: 7_129 + .saturating_add(Weight::from_parts(59_239, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Recovery Recoverable (r:1 w:0) + /// Proof: Recovery Recoverable (max_values: None, max_size: Some(351), added: 2826, mode: MaxEncodedLen) + /// Storage: Recovery ActiveRecoveries (r:1 w:1) + /// Proof: Recovery ActiveRecoveries (max_values: None, max_size: Some(389), added: 2864, mode: MaxEncodedLen) fn initiate_recovery() -> Weight { - // Minimum execution time: 39_224 nanoseconds. - Weight::from_ref_time(39_663_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `272` + // Estimated: `3854` + // Minimum execution time: 29_618_000 picoseconds. + Weight::from_parts(30_192_000, 3854) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Recovery Recoverable (r:1 w:0) - // Storage: Recovery ActiveRecoveries (r:1 w:1) + /// Storage: Recovery Recoverable (r:1 w:0) + /// Proof: Recovery Recoverable (max_values: None, max_size: Some(351), added: 2826, mode: MaxEncodedLen) + /// Storage: Recovery ActiveRecoveries (r:1 w:1) + /// Proof: Recovery ActiveRecoveries (max_values: None, max_size: Some(389), added: 2864, mode: MaxEncodedLen) /// The range of component `n` is `[1, 9]`. fn vouch_recovery(n: u32, ) -> Weight { - // Minimum execution time: 27_158 nanoseconds. - Weight::from_ref_time(28_130_506 as u64) - // Standard Error: 4_523 - .saturating_add(Weight::from_ref_time(321_436 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Recovery Recoverable (r:1 w:0) - // Storage: Recovery ActiveRecoveries (r:1 w:0) - // Storage: Recovery Proxy (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `360 + n * (64 ±0)` + // Estimated: `3854` + // Minimum execution time: 19_464_000 picoseconds. + Weight::from_parts(20_642_522, 3854) + // Standard Error: 5_974 + .saturating_add(Weight::from_parts(142_308, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Recovery Recoverable (r:1 w:0) + /// Proof: Recovery Recoverable (max_values: None, max_size: Some(351), added: 2826, mode: MaxEncodedLen) + /// Storage: Recovery ActiveRecoveries (r:1 w:0) + /// Proof: Recovery ActiveRecoveries (max_values: None, max_size: Some(389), added: 2864, mode: MaxEncodedLen) + /// Storage: Recovery Proxy (r:1 w:1) + /// Proof: Recovery Proxy (max_values: None, max_size: Some(80), added: 2555, mode: MaxEncodedLen) /// The range of component `n` is `[1, 9]`. fn claim_recovery(n: u32, ) -> Weight { - // Minimum execution time: 36_269 nanoseconds. - Weight::from_ref_time(36_966_173 as u64) - // Standard Error: 5_016 - .saturating_add(Weight::from_ref_time(223_069 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Recovery ActiveRecoveries (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `392 + n * (64 ±0)` + // Estimated: `3854` + // Minimum execution time: 23_656_000 picoseconds. + Weight::from_parts(24_903_269, 3854) + // Standard Error: 5_771 + .saturating_add(Weight::from_parts(117_343, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Recovery ActiveRecoveries (r:1 w:1) + /// Proof: Recovery ActiveRecoveries (max_values: None, max_size: Some(389), added: 2864, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `n` is `[1, 9]`. fn close_recovery(n: u32, ) -> Weight { - // Minimum execution time: 40_213 nanoseconds. - Weight::from_ref_time(41_140_968 as u64) - // Standard Error: 3_822 - .saturating_add(Weight::from_ref_time(163_217 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Recovery ActiveRecoveries (r:1 w:0) - // Storage: Recovery Recoverable (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `513 + n * (32 ±0)` + // Estimated: `3854` + // Minimum execution time: 34_866_000 picoseconds. + Weight::from_parts(36_368_748, 3854) + // Standard Error: 6_600 + .saturating_add(Weight::from_parts(118_610, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Recovery ActiveRecoveries (r:1 w:0) + /// Proof: Recovery ActiveRecoveries (max_values: None, max_size: Some(389), added: 2864, mode: MaxEncodedLen) + /// Storage: Recovery Recoverable (r:1 w:1) + /// Proof: Recovery Recoverable (max_values: None, max_size: Some(351), added: 2826, mode: MaxEncodedLen) /// The range of component `n` is `[1, 9]`. fn remove_recovery(n: u32, ) -> Weight { - // Minimum execution time: 38_740 nanoseconds. - Weight::from_ref_time(39_710_400 as u64) - // Standard Error: 5_554 - .saturating_add(Weight::from_ref_time(224_200 as u64).saturating_mul(n as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Recovery Proxy (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `270 + n * (32 ±0)` + // Estimated: `3854` + // Minimum execution time: 31_405_000 picoseconds. + Weight::from_parts(32_552_838, 3854) + // Standard Error: 8_043 + .saturating_add(Weight::from_parts(171_605, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Recovery Proxy (r:1 w:1) + /// Proof: Recovery Proxy (max_values: None, max_size: Some(80), added: 2555, mode: MaxEncodedLen) fn cancel_recovered() -> Weight { - // Minimum execution time: 20_316 nanoseconds. - Weight::from_ref_time(20_912_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `281` + // Estimated: `3545` + // Minimum execution time: 11_530_000 picoseconds. + Weight::from_parts(11_851_000, 3545) + .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: Recovery Proxy (r:1 w:0) + /// Storage: Recovery Proxy (r:1 w:0) + /// Proof: Recovery Proxy (max_values: None, max_size: Some(80), added: 2555, mode: MaxEncodedLen) fn as_recovered() -> Weight { - // Minimum execution time: 10_672 nanoseconds. - Weight::from_ref_time(10_946_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) + // Proof Size summary in bytes: + // Measured: `281` + // Estimated: `3545` + // Minimum execution time: 9_360_000 picoseconds. + Weight::from_parts(9_773_000, 3545) + .saturating_add(RocksDbWeight::get().reads(1_u64)) } - // Storage: Recovery Proxy (r:0 w:1) + /// Storage: Recovery Proxy (r:0 w:1) + /// Proof: Recovery Proxy (max_values: None, max_size: Some(80), added: 2555, mode: MaxEncodedLen) fn set_recovered() -> Weight { - // Minimum execution time: 17_092 nanoseconds. - Weight::from_ref_time(17_660_000 as u64) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 9_146_000 picoseconds. + Weight::from_parts(9_507_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Recovery Recoverable (r:1 w:1) + /// Storage: Recovery Recoverable (r:1 w:1) + /// Proof: Recovery Recoverable (max_values: None, max_size: Some(351), added: 2826, mode: MaxEncodedLen) /// The range of component `n` is `[1, 9]`. fn create_recovery(n: u32, ) -> Weight { - // Minimum execution time: 32_800 nanoseconds. - Weight::from_ref_time(33_769_078 as u64) - // Standard Error: 4_075 - .saturating_add(Weight::from_ref_time(252_382 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Recovery Recoverable (r:1 w:0) - // Storage: Recovery ActiveRecoveries (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `175` + // Estimated: `3816` + // Minimum execution time: 26_472_000 picoseconds. + Weight::from_parts(27_917_651, 3816) + // Standard Error: 7_129 + .saturating_add(Weight::from_parts(59_239, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Recovery Recoverable (r:1 w:0) + /// Proof: Recovery Recoverable (max_values: None, max_size: Some(351), added: 2826, mode: MaxEncodedLen) + /// Storage: Recovery ActiveRecoveries (r:1 w:1) + /// Proof: Recovery ActiveRecoveries (max_values: None, max_size: Some(389), added: 2864, mode: MaxEncodedLen) fn initiate_recovery() -> Weight { - // Minimum execution time: 39_224 nanoseconds. - Weight::from_ref_time(39_663_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `272` + // Estimated: `3854` + // Minimum execution time: 29_618_000 picoseconds. + Weight::from_parts(30_192_000, 3854) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Recovery Recoverable (r:1 w:0) - // Storage: Recovery ActiveRecoveries (r:1 w:1) + /// Storage: Recovery Recoverable (r:1 w:0) + /// Proof: Recovery Recoverable (max_values: None, max_size: Some(351), added: 2826, mode: MaxEncodedLen) + /// Storage: Recovery ActiveRecoveries (r:1 w:1) + /// Proof: Recovery ActiveRecoveries (max_values: None, max_size: Some(389), added: 2864, mode: MaxEncodedLen) /// The range of component `n` is `[1, 9]`. fn vouch_recovery(n: u32, ) -> Weight { - // Minimum execution time: 27_158 nanoseconds. - Weight::from_ref_time(28_130_506 as u64) - // Standard Error: 4_523 - .saturating_add(Weight::from_ref_time(321_436 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Recovery Recoverable (r:1 w:0) - // Storage: Recovery ActiveRecoveries (r:1 w:0) - // Storage: Recovery Proxy (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `360 + n * (64 ±0)` + // Estimated: `3854` + // Minimum execution time: 19_464_000 picoseconds. + Weight::from_parts(20_642_522, 3854) + // Standard Error: 5_974 + .saturating_add(Weight::from_parts(142_308, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Recovery Recoverable (r:1 w:0) + /// Proof: Recovery Recoverable (max_values: None, max_size: Some(351), added: 2826, mode: MaxEncodedLen) + /// Storage: Recovery ActiveRecoveries (r:1 w:0) + /// Proof: Recovery ActiveRecoveries (max_values: None, max_size: Some(389), added: 2864, mode: MaxEncodedLen) + /// Storage: Recovery Proxy (r:1 w:1) + /// Proof: Recovery Proxy (max_values: None, max_size: Some(80), added: 2555, mode: MaxEncodedLen) /// The range of component `n` is `[1, 9]`. fn claim_recovery(n: u32, ) -> Weight { - // Minimum execution time: 36_269 nanoseconds. - Weight::from_ref_time(36_966_173 as u64) - // Standard Error: 5_016 - .saturating_add(Weight::from_ref_time(223_069 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Recovery ActiveRecoveries (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `392 + n * (64 ±0)` + // Estimated: `3854` + // Minimum execution time: 23_656_000 picoseconds. + Weight::from_parts(24_903_269, 3854) + // Standard Error: 5_771 + .saturating_add(Weight::from_parts(117_343, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Recovery ActiveRecoveries (r:1 w:1) + /// Proof: Recovery ActiveRecoveries (max_values: None, max_size: Some(389), added: 2864, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `n` is `[1, 9]`. fn close_recovery(n: u32, ) -> Weight { - // Minimum execution time: 40_213 nanoseconds. - Weight::from_ref_time(41_140_968 as u64) - // Standard Error: 3_822 - .saturating_add(Weight::from_ref_time(163_217 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Recovery ActiveRecoveries (r:1 w:0) - // Storage: Recovery Recoverable (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `513 + n * (32 ±0)` + // Estimated: `3854` + // Minimum execution time: 34_866_000 picoseconds. + Weight::from_parts(36_368_748, 3854) + // Standard Error: 6_600 + .saturating_add(Weight::from_parts(118_610, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Recovery ActiveRecoveries (r:1 w:0) + /// Proof: Recovery ActiveRecoveries (max_values: None, max_size: Some(389), added: 2864, mode: MaxEncodedLen) + /// Storage: Recovery Recoverable (r:1 w:1) + /// Proof: Recovery Recoverable (max_values: None, max_size: Some(351), added: 2826, mode: MaxEncodedLen) /// The range of component `n` is `[1, 9]`. fn remove_recovery(n: u32, ) -> Weight { - // Minimum execution time: 38_740 nanoseconds. - Weight::from_ref_time(39_710_400 as u64) - // Standard Error: 5_554 - .saturating_add(Weight::from_ref_time(224_200 as u64).saturating_mul(n as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Recovery Proxy (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `270 + n * (32 ±0)` + // Estimated: `3854` + // Minimum execution time: 31_405_000 picoseconds. + Weight::from_parts(32_552_838, 3854) + // Standard Error: 8_043 + .saturating_add(Weight::from_parts(171_605, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Recovery Proxy (r:1 w:1) + /// Proof: Recovery Proxy (max_values: None, max_size: Some(80), added: 2555, mode: MaxEncodedLen) fn cancel_recovered() -> Weight { - // Minimum execution time: 20_316 nanoseconds. - Weight::from_ref_time(20_912_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `281` + // Estimated: `3545` + // Minimum execution time: 11_530_000 picoseconds. + Weight::from_parts(11_851_000, 3545) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } diff --git a/frame/referenda/Cargo.toml b/frame/referenda/Cargo.toml index ac4c8044da35..a89f641e810f 100644 --- a/frame/referenda/Cargo.toml +++ b/frame/referenda/Cargo.toml @@ -14,18 +14,18 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] assert_matches = { version = "1.5", optional = true } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", features = ["derive"], optional = true } -sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"], optional = true } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../primitives/arithmetic" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } log = { version = "0.4.17", default-features = false } [dev-dependencies] @@ -33,7 +33,7 @@ assert_matches = { version = "1.5" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-preimage = { version = "4.0.0-dev", path = "../preimage" } pallet-scheduler = { version = "4.0.0-dev", path = "../scheduler" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/referenda/README.md b/frame/referenda/README.md index 85031a011303..b9a8b022cbdb 100644 --- a/frame/referenda/README.md +++ b/frame/referenda/README.md @@ -1,8 +1,8 @@ # Referenda Pallet -- [`assembly::Config`](https://docs.rs/pallet-assembly/latest/pallet_assembly/trait.Config.html) -- [`Call`](https://docs.rs/pallet-assembly/latest/pallet_assembly/enum.Call.html) +- [`Config`](https://docs.rs/pallet-referenda/latest/pallet_referenda/pallet/trait.Config.html) +- [`Call`](https://docs.rs/pallet-referenda/latest/pallet_referenda/pallet/enum.Call.html) ## Overview -The Assembly pallet handles the administration of general stakeholder voting. +The Referenda pallet handles the administration of general stakeholder voting. diff --git a/frame/referenda/src/benchmarking.rs b/frame/referenda/src/benchmarking.rs index e57b5f9859e5..d4b273c0307a 100644 --- a/frame/referenda/src/benchmarking.rs +++ b/frame/referenda/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,18 +20,19 @@ use super::*; use crate::Pallet as Referenda; use assert_matches::assert_matches; -use frame_benchmarking::{account, benchmarks_instance_pallet, whitelist_account}; +use frame_benchmarking::v1::{ + account, benchmarks_instance_pallet, whitelist_account, BenchmarkError, +}; use frame_support::{ assert_ok, dispatch::UnfilteredDispatchable, - traits::{Bounded, Currency, EnsureOrigin}, + traits::{Bounded, Currency, EnsureOrigin, EnsureOriginWithArg}, }; use frame_system::RawOrigin; use sp_runtime::traits::Bounded as ArithBounded; const SEED: u32 = 0; -#[allow(dead_code)] fn assert_last_event, I: 'static>(generic_event: >::RuntimeEvent) { frame_system::Pallet::::assert_last_event(generic_event.into()); } @@ -48,8 +49,7 @@ fn dummy_call, I: 'static>() -> Bounded<>::RuntimeCa T::Preimages::bound(call).unwrap() } -fn create_referendum, I: 'static>() -> (T::RuntimeOrigin, ReferendumIndex) { - let origin: T::RuntimeOrigin = T::SubmitOrigin::successful_origin(); +fn create_referendum, I: 'static>(origin: T::RuntimeOrigin) -> ReferendumIndex { if let Ok(caller) = frame_system::ensure_signed(origin.clone()) { T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); whitelist_account!(caller); @@ -61,7 +61,7 @@ fn create_referendum, I: 'static>() -> (T::RuntimeOrigin, Referendu let call = crate::Call::::submit { proposal_origin, proposal, enactment_moment }; assert_ok!(call.dispatch_bypass_filter(origin.clone())); let index = ReferendumCount::::get() - 1; - (origin, index) + index } fn place_deposit, I: 'static>(index: ReferendumIndex) { @@ -75,6 +75,7 @@ fn nudge, I: 'static>(index: ReferendumIndex) { } fn fill_queue, I: 'static>( + origin: T::RuntimeOrigin, index: ReferendumIndex, spaces: u32, pass_after: u32, @@ -82,7 +83,7 @@ fn fill_queue, I: 'static>( // First, create enough other referendums to fill the track. let mut others = vec![]; for _ in 0..info::(index).max_deciding { - let (_origin, index) = create_referendum::(); + let index = create_referendum::(origin.clone()); place_deposit::(index); others.push(index); } @@ -90,7 +91,7 @@ fn fill_queue, I: 'static>( // We will also need enough referenda which are queued and passing, we want `MaxQueued - 1` // in order to force the maximum amount of work to insert ours into the queue. for _ in spaces..T::MaxQueued::get() { - let (_origin, index) = create_referendum::(); + let index = create_referendum::(origin.clone()); place_deposit::(index); make_passing_after::(index, Perbill::from_percent(pass_after)); others.push(index); @@ -171,7 +172,9 @@ fn skip_timeout_period, I: 'static>(index: ReferendumIndex) { frame_system::Pallet::::set_block_number(timeout_period_over); } -fn alarm_time, I: 'static>(index: ReferendumIndex) -> T::BlockNumber { +fn alarm_time, I: 'static>( + index: ReferendumIndex, +) -> frame_system::pallet_prelude::BlockNumberFor { let status = Referenda::::ensure_ongoing(index).unwrap(); status.alarm.unwrap().0 } @@ -194,7 +197,8 @@ fn is_not_confirming, I: 'static>(index: ReferendumIndex) -> bool { benchmarks_instance_pallet! { submit { - let origin: T::RuntimeOrigin = T::SubmitOrigin::successful_origin(); + let origin = + T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()).map_err(|_| BenchmarkError::Weightless)?; if let Ok(caller) = frame_system::ensure_signed(origin.clone()) { T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); whitelist_account!(caller); @@ -210,15 +214,19 @@ benchmarks_instance_pallet! { } place_decision_deposit_preparing { - let (origin, index) = create_referendum::(); + let origin = + T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()).map_err(|_| BenchmarkError::Weightless)?; + let index = create_referendum::(origin.clone()); }: place_decision_deposit(origin, index) verify { assert!(Referenda::::ensure_ongoing(index).unwrap().decision_deposit.is_some()); } place_decision_deposit_queued { - let (origin, index) = create_referendum::(); - fill_queue::(index, 1, 90); + let origin = + T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()).map_err(|_| BenchmarkError::Weightless)?; + let index = create_referendum::(origin.clone()); + fill_queue::(origin.clone(), index, 1, 90); }: place_decision_deposit(origin, index) verify { let track = Referenda::::ensure_ongoing(index).unwrap().track; @@ -227,8 +235,10 @@ benchmarks_instance_pallet! { } place_decision_deposit_not_queued { - let (origin, index) = create_referendum::(); - fill_queue::(index, 0, 90); + let origin = + T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()).map_err(|_| BenchmarkError::Weightless)?; + let index = create_referendum::(origin.clone()); + fill_queue::(origin.clone(), index, 0, 90); let track = Referenda::::ensure_ongoing(index).unwrap().track; assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); assert!(TrackQueue::::get(&track).into_iter().all(|(i, _)| i != index)); @@ -239,7 +249,9 @@ benchmarks_instance_pallet! { } place_decision_deposit_passing { - let (origin, index) = create_referendum::(); + let origin = + T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()).map_err(|_| BenchmarkError::Weightless)?; + let index = create_referendum::(origin.clone()); skip_prepare_period::(index); make_passing::(index); }: place_decision_deposit(origin, index) @@ -248,7 +260,9 @@ benchmarks_instance_pallet! { } place_decision_deposit_failing { - let (origin, index) = create_referendum::(); + let origin = + T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()).map_err(|_| BenchmarkError::Weightless)?; + let index = create_referendum::(origin.clone()); skip_prepare_period::(index); }: place_decision_deposit(origin, index) verify { @@ -256,19 +270,31 @@ benchmarks_instance_pallet! { } refund_decision_deposit { - let (origin, index) = create_referendum::(); + let origin = + T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()).map_err(|_| BenchmarkError::Weightless)?; + let index = create_referendum::(origin.clone()); place_deposit::(index); - assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), index)); + assert_ok!(Referenda::::cancel( + T::CancelOrigin::try_successful_origin() + .expect("CancelOrigin has no successful origin required for the benchmark"), + index, + )); }: _(origin, index) verify { assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(_, _, None))); } refund_submission_deposit { - let (origin, index) = create_referendum::(); + let origin = + T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()).map_err(|_| BenchmarkError::Weightless)?; + let index = create_referendum::(origin.clone()); let caller = frame_system::ensure_signed(origin.clone()).unwrap(); let balance = T::Currency::free_balance(&caller); - assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), index)); + assert_ok!(Referenda::::cancel( + T::CancelOrigin::try_successful_origin() + .expect("CancelOrigin has no successful origin required for the benchmark"), + index, + )); assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(_, Some(_), _))); }: _(origin, index) verify { @@ -279,28 +305,42 @@ benchmarks_instance_pallet! { } cancel { - let (_origin, index) = create_referendum::(); + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin); place_deposit::(index); - }: _(T::CancelOrigin::successful_origin(), index) - verify { + }: _( + T::CancelOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, + index + ) verify { assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Cancelled(..))); } kill { - let (_origin, index) = create_referendum::(); + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin); place_deposit::(index); - }: _(T::KillOrigin::successful_origin(), index) - verify { + }: _( + T::KillOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?, + index + ) verify { assert_matches!(ReferendumInfoFor::::get(index), Some(ReferendumInfo::Killed(..))); } one_fewer_deciding_queue_empty { - let (_origin, index) = create_referendum::(); + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin); place_deposit::(index); skip_prepare_period::(index); nudge::(index); let track = Referenda::::ensure_ongoing(index).unwrap().track; - assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), index)); + assert_ok!(Referenda::::cancel( + T::CancelOrigin::try_successful_origin() + .expect("CancelOrigin has no successful origin required for the benchmark"), + index, + )); assert_eq!(DecidingCount::::get(&track), 1); }: one_fewer_deciding(RawOrigin::Root, track) verify { @@ -308,11 +348,17 @@ benchmarks_instance_pallet! { } one_fewer_deciding_failing { - let (_origin, index) = create_referendum::(); + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin.clone()); // No spaces free in the queue. - let queued = fill_queue::(index, 0, 90); + let queued = fill_queue::(origin, index, 0, 90); let track = Referenda::::ensure_ongoing(index).unwrap().track; - assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), queued[0])); + assert_ok!(Referenda::::cancel( + T::CancelOrigin::try_successful_origin() + .expect("CancelOrigin has no successful origin required for the benchmark"), + queued[0], + )); assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); let deciding_count = DecidingCount::::get(&track); }: one_fewer_deciding(RawOrigin::Root, track) @@ -327,11 +373,17 @@ benchmarks_instance_pallet! { } one_fewer_deciding_passing { - let (_origin, index) = create_referendum::(); + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin.clone()); // No spaces free in the queue. - let queued = fill_queue::(index, 0, 0); + let queued = fill_queue::(origin, index, 0, 0); let track = Referenda::::ensure_ongoing(index).unwrap().track; - assert_ok!(Referenda::::cancel(T::CancelOrigin::successful_origin(), queued[0])); + assert_ok!(Referenda::::cancel( + T::CancelOrigin::try_successful_origin() + .expect("CancelOrigin has no successful origin required for the benchmark"), + queued[0], + )); assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); let deciding_count = DecidingCount::::get(&track); }: one_fewer_deciding(RawOrigin::Root, track) @@ -346,10 +398,12 @@ benchmarks_instance_pallet! { } nudge_referendum_requeued_insertion { + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); // First create our referendum and place the deposit. It will be failing. - let (_origin, index) = create_referendum::(); + let index = create_referendum::(origin.clone()); place_deposit::(index); - fill_queue::(index, 0, 90); + fill_queue::(origin, index, 0, 90); // Now nudge ours, with the track now full and the queue full of referenda with votes, // ours will not be in the queue. @@ -367,10 +421,12 @@ benchmarks_instance_pallet! { } nudge_referendum_requeued_slide { + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); // First create our referendum and place the deposit. It will be failing. - let (_origin, index) = create_referendum::(); + let index = create_referendum::(origin.clone()); place_deposit::(index); - fill_queue::(index, 1, 90); + fill_queue::(origin, index, 1, 90); // Now nudge ours, with the track now full, ours will be queued, but with no votes, it // will have the worst position. @@ -393,10 +449,12 @@ benchmarks_instance_pallet! { // free and this failing. It would result in `QUEUE_SIZE - 1` items being shifted for the // insertion at the beginning. + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); // First create our referendum and place the deposit. It will be failing. - let (_origin, index) = create_referendum::(); + let index = create_referendum::(origin.clone()); place_deposit::(index); - fill_queue::(index, 1, 0); + fill_queue::(origin, index, 1, 0); let track = Referenda::::ensure_ongoing(index).unwrap().track; assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get() - 1); @@ -410,10 +468,12 @@ benchmarks_instance_pallet! { } nudge_referendum_not_queued { + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); // First create our referendum and place the deposit. It will be failing. - let (_origin, index) = create_referendum::(); + let index = create_referendum::(origin.clone()); place_deposit::(index); - fill_queue::(index, 0, 0); + fill_queue::(origin, index, 0, 0); let track = Referenda::::ensure_ongoing(index).unwrap().track; assert_eq!(TrackQueue::::get(&track).len() as u32, T::MaxQueued::get()); @@ -427,7 +487,9 @@ benchmarks_instance_pallet! { } nudge_referendum_no_deposit { - let (_origin, index) = create_referendum::(); + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin); skip_prepare_period::(index); }: nudge_referendum(RawOrigin::Root, index) verify { @@ -436,7 +498,9 @@ benchmarks_instance_pallet! { } nudge_referendum_preparing { - let (_origin, index) = create_referendum::(); + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin); place_deposit::(index); }: nudge_referendum(RawOrigin::Root, index) verify { @@ -445,7 +509,9 @@ benchmarks_instance_pallet! { } nudge_referendum_timed_out { - let (_origin, index) = create_referendum::(); + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin); skip_timeout_period::(index); }: nudge_referendum(RawOrigin::Root, index) verify { @@ -454,7 +520,9 @@ benchmarks_instance_pallet! { } nudge_referendum_begin_deciding_failing { - let (_origin, index) = create_referendum::(); + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin); place_deposit::(index); skip_prepare_period::(index); }: nudge_referendum(RawOrigin::Root, index) @@ -463,7 +531,9 @@ benchmarks_instance_pallet! { } nudge_referendum_begin_deciding_passing { - let (_origin, index) = create_referendum::(); + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin); place_deposit::(index); make_passing::(index); skip_prepare_period::(index); @@ -473,7 +543,9 @@ benchmarks_instance_pallet! { } nudge_referendum_begin_confirming { - let (_origin, index) = create_referendum::(); + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin); place_deposit::(index); skip_prepare_period::(index); nudge::(index); @@ -485,7 +557,9 @@ benchmarks_instance_pallet! { } nudge_referendum_end_confirming { - let (_origin, index) = create_referendum::(); + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin); place_deposit::(index); skip_prepare_period::(index); make_passing::(index); @@ -498,7 +572,9 @@ benchmarks_instance_pallet! { } nudge_referendum_continue_not_confirming { - let (_origin, index) = create_referendum::(); + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin); place_deposit::(index); skip_prepare_period::(index); nudge::(index); @@ -512,7 +588,9 @@ benchmarks_instance_pallet! { } nudge_referendum_continue_confirming { - let (_origin, index) = create_referendum::(); + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin); place_deposit::(index); make_passing::(index); skip_prepare_period::(index); @@ -525,7 +603,9 @@ benchmarks_instance_pallet! { } nudge_referendum_approved { - let (_origin, index) = create_referendum::(); + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin); place_deposit::(index); skip_prepare_period::(index); make_passing::(index); @@ -538,7 +618,9 @@ benchmarks_instance_pallet! { } nudge_referendum_rejected { - let (_origin, index) = create_referendum::(); + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin); place_deposit::(index); skip_prepare_period::(index); make_failing::(index); @@ -550,6 +632,31 @@ benchmarks_instance_pallet! { assert_matches!(info, ReferendumInfo::Rejected(..)); } + set_some_metadata { + use sp_std::borrow::Cow; + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin.clone()); + let hash = T::Preimages::note(Cow::from(vec![5, 6])).unwrap(); + }: set_metadata(origin, index, Some(hash)) + verify { + assert_last_event::(Event::MetadataSet { index, hash }.into()); + } + + clear_metadata { + use sp_std::borrow::Cow; + let origin = T::SubmitOrigin::try_successful_origin(&RawOrigin::Root.into()) + .expect("SubmitOrigin has no successful origin required for the benchmark"); + let index = create_referendum::(origin.clone()); + let hash = T::Preimages::note(Cow::from(vec![6, 7, 8])).unwrap(); + assert_ok!( + Referenda::::set_metadata(origin.clone(), index, Some(hash)) + ); + }: set_metadata(origin, index, None) + verify { + assert_last_event::(Event::MetadataCleared { index, hash }.into()); + } + impl_benchmark_test_suite!( Referenda, crate::mock::new_test_ext(), diff --git a/frame/referenda/src/branch.rs b/frame/referenda/src/branch.rs index d3744979fc54..499e62387831 100644 --- a/frame/referenda/src/branch.rs +++ b/frame/referenda/src/branch.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/referenda/src/lib.rs b/frame/referenda/src/lib.rs index 0b846faf8855..8ff754dc0db8 100644 --- a/frame/referenda/src/lib.rs +++ b/frame/referenda/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -72,14 +72,15 @@ use frame_support::{ v3::{Anon as ScheduleAnon, Named as ScheduleNamed}, DispatchTime, }, - Currency, LockIdentifier, OnUnbalanced, OriginTrait, PollStatus, Polling, QueryPreimage, - ReservableCurrency, StorePreimage, VoteTally, + Currency, Hash as PreimageHash, LockIdentifier, OnUnbalanced, OriginTrait, PollStatus, + Polling, QueryPreimage, ReservableCurrency, StorePreimage, VoteTally, }, BoundedVec, }; +use frame_system::pallet_prelude::BlockNumberFor; use scale_info::TypeInfo; use sp_runtime::{ - traits::{AtLeast32BitUnsigned, Dispatchable, One, Saturating, Zero}, + traits::{AtLeast32BitUnsigned, Bounded, Dispatchable, One, Saturating, Zero}, DispatchError, Perbill, }; use sp_std::{fmt::Debug, prelude::*}; @@ -138,14 +139,13 @@ const ASSEMBLY_ID: LockIdentifier = *b"assembly"; #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::{pallet_prelude::*, traits::EnsureOriginWithArg}; use frame_system::pallet_prelude::*; /// The current storage version. const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); @@ -162,13 +162,17 @@ pub mod pallet { /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; /// The Scheduler. - type Scheduler: ScheduleAnon, PalletsOriginOf> - + ScheduleNamed, PalletsOriginOf>; + type Scheduler: ScheduleAnon, CallOf, PalletsOriginOf> + + ScheduleNamed, CallOf, PalletsOriginOf>; /// Currency type for this pallet. type Currency: ReservableCurrency; // Origins and unbalances. /// Origin from which proposals may be submitted. - type SubmitOrigin: EnsureOrigin; + type SubmitOrigin: EnsureOriginWithArg< + Self::RuntimeOrigin, + PalletsOriginOf, + Success = Self::AccountId, + >; /// Origin from which any vote may be cancelled. type CancelOrigin: EnsureOrigin; /// Origin from which any vote may be killed. @@ -198,25 +202,25 @@ pub mod pallet { /// The number of blocks after submission that a referendum must begin being decided by. /// Once this passes, then anyone may cancel the referendum. #[pallet::constant] - type UndecidingTimeout: Get; + type UndecidingTimeout: Get>; /// Quantization level for the referendum wakeup scheduler. A higher number will result in /// fewer storage reads/writes needed for smaller voters, but also result in delays to the /// automatic referendum status changes. Explicit servicing instructions are unaffected. #[pallet::constant] - type AlarmInterval: Get; + type AlarmInterval: Get>; // The other stuff. /// Information concerning the different referendum tracks. #[pallet::constant] type Tracks: Get< Vec<( - , Self::BlockNumber>>::Id, - TrackInfo, Self::BlockNumber>, + , BlockNumberFor>>::Id, + TrackInfo, BlockNumberFor>, )>, > + TracksInfo< BalanceOf, - Self::BlockNumber, + BlockNumberFor, RuntimeOrigin = ::PalletsOrigin, >; @@ -251,6 +255,16 @@ pub mod pallet { pub type DecidingCount, I: 'static = ()> = StorageMap<_, Twox64Concat, TrackIdOf, u32, ValueQuery>; + /// The metadata is a general information concerning the referendum. + /// The `PreimageHash` refers to the preimage of the `Preimages` provider which can be a JSON + /// dump or IPFS hash of a JSON file. + /// + /// Consider a garbage collection for a metadata of finished referendums to `unrequest` (remove) + /// large preimages. + #[pallet::storage] + pub type MetadataOf, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, ReferendumIndex, PreimageHash>; + #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event, I: 'static = ()> { @@ -356,6 +370,20 @@ pub mod pallet { /// The amount placed by the account. amount: BalanceOf, }, + /// Metadata for a referendum has been set. + MetadataSet { + /// Index of the referendum. + index: ReferendumIndex, + /// Preimage hash. + hash: PreimageHash, + }, + /// Metadata for a referendum has been cleared. + MetadataCleared { + /// Index of the referendum. + index: ReferendumIndex, + /// Preimage hash. + hash: PreimageHash, + }, } #[pallet::error] @@ -384,6 +412,8 @@ pub mod pallet { NoDeposit, /// The referendum status is invalid for this operation. BadStatus, + /// The preimage does not exist. + PreimageNotExist, } #[pallet::call] @@ -403,9 +433,10 @@ pub mod pallet { origin: OriginFor, proposal_origin: Box>, proposal: BoundedCallOf, - enactment_moment: DispatchTime, + enactment_moment: DispatchTime>, ) -> DispatchResult { - let who = T::SubmitOrigin::ensure_origin(origin)?; + let proposal_origin = *proposal_origin; + let who = T::SubmitOrigin::ensure_origin(origin, &proposal_origin)?; let track = T::Tracks::track_for(&proposal_origin).map_err(|_| Error::::NoTrack)?; @@ -420,7 +451,7 @@ pub mod pallet { T::Preimages::bound(CallOf::::from(Call::nudge_referendum { index }))?; let status = ReferendumStatus { track, - origin: *proposal_origin, + origin: proposal_origin, proposal: proposal.clone(), enactment: enactment_moment, submitted: now, @@ -540,6 +571,7 @@ pub mod pallet { Self::deposit_event(Event::::Killed { index, tally: status.tally }); Self::slash_deposit(Some(status.submission_deposit.clone())); Self::slash_deposit(status.decision_deposit.clone()); + Self::do_clear_metadata(index); let info = ReferendumInfo::Killed(frame_system::Pallet::::block_number()); ReferendumInfoFor::::insert(index, info); Ok(()) @@ -633,13 +665,47 @@ pub mod pallet { Self::deposit_event(e); Ok(()) } + + /// Set or clear metadata of a referendum. + /// + /// Parameters: + /// - `origin`: Must be `Signed` by a creator of a referendum or by anyone to clear a + /// metadata of a finished referendum. + /// - `index`: The index of a referendum to set or clear metadata for. + /// - `maybe_hash`: The hash of an on-chain stored preimage. `None` to clear a metadata. + #[pallet::call_index(8)] + #[pallet::weight( + maybe_hash.map_or( + T::WeightInfo::clear_metadata(), |_| T::WeightInfo::set_some_metadata()) + )] + pub fn set_metadata( + origin: OriginFor, + index: ReferendumIndex, + maybe_hash: Option, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + if let Some(hash) = maybe_hash { + let status = Self::ensure_ongoing(index)?; + ensure!(status.submission_deposit.who == who, Error::::NoPermission); + ensure!(T::Preimages::len(&hash).is_some(), Error::::PreimageNotExist); + MetadataOf::::insert(index, hash); + Self::deposit_event(Event::::MetadataSet { index, hash }); + Ok(()) + } else { + if let Some(status) = Self::ensure_ongoing(index).ok() { + ensure!(status.submission_deposit.who == who, Error::::NoPermission); + } + Self::do_clear_metadata(index); + Ok(()) + } + } } } impl, I: 'static> Polling for Pallet { type Index = ReferendumIndex; type Votes = VotesOf; - type Moment = T::BlockNumber; + type Moment = BlockNumberFor; type Class = TrackIdOf; fn classes() -> Vec { @@ -648,7 +714,7 @@ impl, I: 'static> Polling for Pallet { fn access_poll( index: Self::Index, - f: impl FnOnce(PollStatus<&mut T::Tally, T::BlockNumber, TrackIdOf>) -> R, + f: impl FnOnce(PollStatus<&mut T::Tally, BlockNumberFor, TrackIdOf>) -> R, ) -> R { match ReferendumInfoFor::::get(index) { Some(ReferendumInfo::Ongoing(mut status)) => { @@ -667,7 +733,7 @@ impl, I: 'static> Polling for Pallet { fn try_access_poll( index: Self::Index, f: impl FnOnce( - PollStatus<&mut T::Tally, T::BlockNumber, TrackIdOf>, + PollStatus<&mut T::Tally, BlockNumberFor, TrackIdOf>, ) -> Result, ) -> Result { match ReferendumInfoFor::::get(index) { @@ -784,7 +850,7 @@ impl, I: 'static> Pallet { fn schedule_enactment( index: ReferendumIndex, track: &TrackInfoOf, - desired: DispatchTime, + desired: DispatchTime>, origin: PalletsOriginOf, call: BoundedCallOf, ) { @@ -806,30 +872,29 @@ impl, I: 'static> Pallet { /// Set an alarm to dispatch `call` at block number `when`. fn set_alarm( call: BoundedCallOf, - when: T::BlockNumber, - ) -> Option<(T::BlockNumber, ScheduleAddressOf)> { + when: BlockNumberFor, + ) -> Option<(BlockNumberFor, ScheduleAddressOf)> { let alarm_interval = T::AlarmInterval::get().max(One::one()); // Alarm must go off no earlier than `when`. // This rounds `when` upwards to the next multiple of `alarm_interval`. let when = (when.saturating_add(alarm_interval.saturating_sub(One::one())) / alarm_interval) .saturating_mul(alarm_interval); - let maybe_result = T::Scheduler::schedule( + let result = T::Scheduler::schedule( DispatchTime::At(when), None, 128u8, frame_system::RawOrigin::Root.into(), call, - ) - .ok() - .map(|x| (when, x)); + ); debug_assert!( - maybe_result.is_some(), - "Unable to schedule a new alarm at #{:?} (now: #{:?})?!", + result.is_ok(), + "Unable to schedule a new alarm at #{:?} (now: #{:?}), scheduler error: `{:?}`", when, - frame_system::Pallet::::block_number() + frame_system::Pallet::::block_number(), + result.unwrap_err(), ); - maybe_result + result.ok().map(|x| (when, x)) } /// Mutate a referendum's `status` into the correct deciding state. @@ -841,9 +906,9 @@ impl, I: 'static> Pallet { fn begin_deciding( status: &mut ReferendumStatusOf, index: ReferendumIndex, - now: T::BlockNumber, + now: BlockNumberFor, track: &TrackInfoOf, - ) -> (Option, BeginDecidingBranch) { + ) -> (Option>, BeginDecidingBranch) { let is_passing = Self::is_passing( &status.tally, Zero::zero(), @@ -879,11 +944,11 @@ impl, I: 'static> Pallet { /// /// If `None`, then it is queued and should be nudged automatically as the queue gets drained. fn ready_for_deciding( - now: T::BlockNumber, + now: BlockNumberFor, track: &TrackInfoOf, index: ReferendumIndex, status: &mut ReferendumStatusOf, - ) -> (Option, ServiceBranch) { + ) -> (Option>, ServiceBranch) { let deciding_count = DecidingCount::::get(status.track); if deciding_count < track.max_deciding { // Begin deciding. @@ -940,7 +1005,7 @@ impl, I: 'static> Pallet { fn ensure_alarm_at( status: &mut ReferendumStatusOf, index: ReferendumIndex, - alarm: T::BlockNumber, + alarm: BlockNumberFor, ) -> bool { if status.alarm.as_ref().map_or(true, |&(when, _)| when != alarm) { // Either no alarm or one that was different @@ -985,7 +1050,7 @@ impl, I: 'static> Pallet { /// `TrackQueue`. Basically this happens when a referendum is in the deciding queue and receives /// a vote, or when it moves into the deciding queue. fn service_referendum( - now: T::BlockNumber, + now: BlockNumberFor, index: ReferendumIndex, mut status: ReferendumStatusOf, ) -> (ReferendumInfoOf, bool, ServiceBranch) { @@ -995,9 +1060,9 @@ impl, I: 'static> Pallet { Some(x) => x, None => return (ReferendumInfo::Ongoing(status), false, ServiceBranch::Fail), }; + // Default the alarm to the end of the world. let timeout = status.submitted + T::UndecidingTimeout::get(); - // Default the alarm to the submission timeout. - let mut alarm = timeout; + let mut alarm = BlockNumberFor::::max_value(); let branch; match &mut status.deciding { None => { @@ -1038,11 +1103,12 @@ impl, I: 'static> Pallet { ServiceBranch::Preparing } } else { + alarm = timeout; ServiceBranch::NoDeposit } } // If we didn't move into being decided, then check the timeout. - if status.deciding.is_none() && now >= timeout { + if status.deciding.is_none() && now >= timeout && !status.in_queue { // Too long without being decided - end it. Self::ensure_no_alarm(&mut status); Self::deposit_event(Event::::TimedOut { index, tally: status.tally }); @@ -1127,7 +1193,11 @@ impl, I: 'static> Pallet { }, } - let dirty_alarm = Self::ensure_alarm_at(&mut status, index, alarm); + let dirty_alarm = if alarm < BlockNumberFor::::max_value() { + Self::ensure_alarm_at(&mut status, index, alarm) + } else { + Self::ensure_no_alarm(&mut status) + }; (ReferendumInfo::Ongoing(status), dirty_alarm || dirty, branch) } @@ -1138,7 +1208,7 @@ impl, I: 'static> Pallet { tally: &T::Tally, track_id: TrackIdOf, track: &TrackInfoOf, - ) -> T::BlockNumber { + ) -> BlockNumberFor { deciding.confirming.unwrap_or_else(|| { // Set alarm to the point where the current voting would make it pass. let approval = tally.approval(track_id); @@ -1146,15 +1216,18 @@ impl, I: 'static> Pallet { let until_approval = track.min_approval.delay(approval); let until_support = track.min_support.delay(support); let offset = until_support.max(until_approval); - deciding.since.saturating_add(offset * track.decision_period) + deciding.since.saturating_add(offset.mul_ceil(track.decision_period)) }) } /// Cancel the alarm in `status`, if one exists. - fn ensure_no_alarm(status: &mut ReferendumStatusOf) { + fn ensure_no_alarm(status: &mut ReferendumStatusOf) -> bool { if let Some((_, last_alarm)) = status.alarm.take() { // Incorrect alarm - cancel it. let _ = T::Scheduler::cancel(last_alarm); + true + } else { + false } } @@ -1194,8 +1267,8 @@ impl, I: 'static> Pallet { /// `approval_needed`. fn is_passing( tally: &T::Tally, - elapsed: T::BlockNumber, - period: T::BlockNumber, + elapsed: BlockNumberFor, + period: BlockNumberFor, support_needed: &Curve, approval_needed: &Curve, id: TrackIdOf, @@ -1204,4 +1277,11 @@ impl, I: 'static> Pallet { support_needed.passing(x, tally.support(id)) && approval_needed.passing(x, tally.approval(id)) } + + /// Clear metadata if exist for a given referendum index. + fn do_clear_metadata(index: ReferendumIndex) { + if let Some(hash) = MetadataOf::::take(index) { + Self::deposit_event(Event::::MetadataCleared { index, hash }); + } + } } diff --git a/frame/referenda/src/migration.rs b/frame/referenda/src/migration.rs index 39575556e409..6f5e42cd4965 100644 --- a/frame/referenda/src/migration.rs +++ b/frame/referenda/src/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,9 @@ use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; use frame_support::{pallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade}; use log; +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + /// Initial version of storage types. pub mod v0 { use super::*; @@ -34,7 +37,7 @@ pub mod v0 { pub type ReferendumInfoOf = ReferendumInfo< TrackIdOf, PalletsOriginOf, - ::BlockNumber, + frame_system::pallet_prelude::BlockNumberFor, BoundedCallOf, BalanceOf, TallyOf, @@ -95,9 +98,9 @@ pub mod v1 { pub struct MigrateV0ToV1(PhantomData<(T, I)>); impl, I: 'static> OnRuntimeUpgrade for MigrateV0ToV1 { #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { + fn pre_upgrade() -> Result, TryRuntimeError> { let onchain_version = Pallet::::on_chain_storage_version(); - assert_eq!(onchain_version, 0, "migration from version 0 to 1."); + ensure!(onchain_version == 0, "migration from version 0 to 1."); let referendum_count = v0::ReferendumInfoFor::::iter().count(); log::info!( target: TARGET, @@ -147,16 +150,13 @@ pub mod v1 { } #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), &'static str> { + fn post_upgrade(state: Vec) -> Result<(), TryRuntimeError> { let onchain_version = Pallet::::on_chain_storage_version(); - assert_eq!(onchain_version, 1, "must upgrade from version 0 to 1."); + ensure!(onchain_version == 1, "must upgrade from version 0 to 1."); let pre_referendum_count: u32 = Decode::decode(&mut &state[..]) .expect("failed to decode the state from pre-upgrade."); let post_referendum_count = ReferendumInfoFor::::iter().count() as u32; - assert_eq!( - post_referendum_count, pre_referendum_count, - "must migrate all referendums." - ); + ensure!(post_referendum_count == pre_referendum_count, "must migrate all referendums."); log::info!(target: TARGET, "migrated all referendums."); Ok(()) } @@ -191,7 +191,7 @@ pub mod test { #[test] pub fn referendum_status_v0() { // make sure the bytes of the encoded referendum v0 is decodable. - let ongoing_encoded = sp_core::Bytes::from_str("0x00000000013001012a000000000000000400000100000000000000010000000000000001000000000000000a00000000000000000000000000000000000100").unwrap(); + let ongoing_encoded = sp_core::Bytes::from_str("0x00000000012c01082a0000000000000004000100000000000000010000000000000001000000000000000a00000000000000000000000000000000000100").unwrap(); let ongoing_dec = v0::ReferendumInfoOf::::decode(&mut &*ongoing_encoded).unwrap(); let ongoing = v0::ReferendumInfoOf::::Ongoing(create_status_v0()); assert_eq!(ongoing, ongoing_dec); diff --git a/frame/referenda/src/mock.rs b/frame/referenda/src/mock.rs index e957027e561a..d4a23d3e24b4 100644 --- a/frame/referenda/src/mock.rs +++ b/frame/referenda/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,19 +31,14 @@ use frame_support::{ use frame_system::{EnsureRoot, EnsureSignedBy}; use sp_core::H256; use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, - DispatchResult, Perbill, + traits::{BlakeTwo256, Hash, IdentityLookup}, + BuildStorage, DispatchResult, Perbill, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { System: frame_system, Balances: pallet_balances, @@ -57,12 +52,12 @@ frame_support::construct_runtime!( pub struct BaseFilter; impl Contains for BaseFilter { fn contains(call: &RuntimeCall) -> bool { - !matches!(call, &RuntimeCall::Balances(pallet_balances::Call::set_balance { .. })) + !matches!(call, &RuntimeCall::Balances(pallet_balances::Call::force_set_balance { .. })) } } parameter_types! { - pub MaxWeight: Weight = Weight::from_ref_time(2_000_000_000_000); + pub MaxWeight: Weight = Weight::from_parts(2_000_000_000_000, u64::MAX); } impl frame_system::Config for Test { type BaseCallFilter = BaseFilter; @@ -70,14 +65,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -120,6 +114,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } parameter_types! { pub static AlarmInterval: u64 = 1; @@ -229,7 +227,7 @@ impl Config for Test { } pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let balances = vec![(1, 100), (2, 100), (3, 100), (4, 100), (5, 100), (6, 100)]; pallet_balances::GenesisConfig:: { balances } .assimilate_storage(&mut t) @@ -295,19 +293,14 @@ impl VoteTally for Tally { } pub fn set_balance_proposal(value: u64) -> Vec { - RuntimeCall::Balances(pallet_balances::Call::set_balance { - who: 42, - new_free: value, - new_reserved: 0, - }) - .encode() + RuntimeCall::Balances(pallet_balances::Call::force_set_balance { who: 42, new_free: value }) + .encode() } pub fn set_balance_proposal_bounded(value: u64) -> BoundedCallOf { - let c = RuntimeCall::Balances(pallet_balances::Call::set_balance { + let c = RuntimeCall::Balances(pallet_balances::Call::force_set_balance { who: 42, new_free: value, - new_reserved: 0, }); ::bound(c).unwrap() } @@ -470,3 +463,15 @@ impl RefState { index } } + +/// note a new preimage without registering. +pub fn note_preimage(who: u64) -> PreimageHash { + use std::sync::atomic::{AtomicU8, Ordering}; + // note a new preimage on every function invoke. + static COUNTER: AtomicU8 = AtomicU8::new(0); + let data = vec![COUNTER.fetch_add(1, Ordering::Relaxed)]; + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(who), data.clone())); + let hash = BlakeTwo256::hash(&data); + assert!(!Preimage::is_requested(&hash)); + hash +} diff --git a/frame/referenda/src/tests.rs b/frame/referenda/src/tests.rs index c109fafe332e..39f1945bf4f3 100644 --- a/frame/referenda/src/tests.rs +++ b/frame/referenda/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -190,6 +190,8 @@ fn queueing_works() { set_balance_proposal_bounded(i), DispatchTime::After(0), )); + } + for i in [1, 2, 4] { assert_ok!(Referenda::place_decision_deposit(RuntimeOrigin::signed(i), i as u32)); // TODO: decision deposit after some initial votes with a non-highest voted coming // first. @@ -286,6 +288,24 @@ fn alarm_interval_works() { }); } +#[test] +fn decision_time_is_correct() { + new_test_ext().execute_with(|| { + let decision_time = |since: u64| { + Pallet::::decision_time( + &DecidingStatus { since: since.into(), confirming: None }, + &Tally { ayes: 100, nays: 5 }, + TestTracksInfo::tracks()[0].0, + &TestTracksInfo::tracks()[0].1, + ) + }; + + for i in 0u64..=100 { + assert!(decision_time(i) > i, "The decision time should be delayed by the curve"); + } + }); +} + #[test] fn auto_timeout_should_happen_with_nothing_but_submit() { new_test_ext().execute_with(|| { @@ -578,3 +598,75 @@ fn curve_handles_all_inputs() { let threshold = test_curve.threshold(Perbill::one()); assert_eq!(threshold, Perbill::zero()); } + +#[test] +fn set_metadata_works() { + new_test_ext().execute_with(|| { + use frame_support::traits::Hash as PreimageHash; + // invalid preimage hash. + let invalid_hash: PreimageHash = [1u8; 32].into(); + // fails to set metadata for a finished referendum. + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + set_balance_proposal_bounded(1), + DispatchTime::At(1), + )); + let index = ReferendumCount::::get() - 1; + assert_ok!(Referenda::kill(RuntimeOrigin::root(), index)); + assert_noop!( + Referenda::set_metadata(RuntimeOrigin::signed(1), index, Some(invalid_hash)), + Error::::NotOngoing, + ); + // no permission to set metadata. + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + set_balance_proposal_bounded(1), + DispatchTime::At(1), + )); + let index = ReferendumCount::::get() - 1; + assert_noop!( + Referenda::set_metadata(RuntimeOrigin::signed(2), index, Some(invalid_hash)), + Error::::NoPermission, + ); + // preimage does not exist. + let index = ReferendumCount::::get() - 1; + assert_noop!( + Referenda::set_metadata(RuntimeOrigin::signed(1), index, Some(invalid_hash)), + Error::::PreimageNotExist, + ); + // metadata set. + let index = ReferendumCount::::get() - 1; + let hash = note_preimage(1); + assert_ok!(Referenda::set_metadata(RuntimeOrigin::signed(1), index, Some(hash))); + System::assert_last_event(RuntimeEvent::Referenda(crate::Event::MetadataSet { + index, + hash, + })); + }); +} + +#[test] +fn clear_metadata_works() { + new_test_ext().execute_with(|| { + let hash = note_preimage(1); + assert_ok!(Referenda::submit( + RuntimeOrigin::signed(1), + Box::new(RawOrigin::Root.into()), + set_balance_proposal_bounded(1), + DispatchTime::At(1), + )); + let index = ReferendumCount::::get() - 1; + assert_ok!(Referenda::set_metadata(RuntimeOrigin::signed(1), index, Some(hash))); + assert_noop!( + Referenda::set_metadata(RuntimeOrigin::signed(2), index, None), + Error::::NoPermission, + ); + assert_ok!(Referenda::set_metadata(RuntimeOrigin::signed(1), index, None),); + System::assert_last_event(RuntimeEvent::Referenda(crate::Event::MetadataCleared { + index, + hash, + })); + }); +} diff --git a/frame/referenda/src/types.rs b/frame/referenda/src/types.rs index 0763a71a95ba..ba89383888a7 100644 --- a/frame/referenda/src/types.rs +++ b/frame/referenda/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,7 +42,7 @@ pub type PalletsOriginOf = pub type ReferendumInfoOf = ReferendumInfo< TrackIdOf, PalletsOriginOf, - ::BlockNumber, + BlockNumberFor, BoundedCallOf, BalanceOf, TallyOf, @@ -52,22 +52,19 @@ pub type ReferendumInfoOf = ReferendumInfo< pub type ReferendumStatusOf = ReferendumStatus< TrackIdOf, PalletsOriginOf, - ::BlockNumber, + BlockNumberFor, BoundedCallOf, BalanceOf, TallyOf, ::AccountId, ScheduleAddressOf, >; -pub type DecidingStatusOf = DecidingStatus<::BlockNumber>; -pub type TrackInfoOf = - TrackInfo, ::BlockNumber>; -pub type TrackIdOf = <>::Tracks as TracksInfo< - BalanceOf, - ::BlockNumber, ->>::Id; +pub type DecidingStatusOf = DecidingStatus>; +pub type TrackInfoOf = TrackInfo, BlockNumberFor>; +pub type TrackIdOf = + <>::Tracks as TracksInfo, BlockNumberFor>>::Id; pub type ScheduleAddressOf = <>::Scheduler as Anon< - ::BlockNumber, + BlockNumberFor, CallOf, PalletsOriginOf, >>::Address; diff --git a/frame/referenda/src/weights.rs b/frame/referenda/src/weights.rs index f0eae517af74..4b89379b311d 100644 --- a/frame/referenda/src/weights.rs +++ b/frame/referenda/src/weights.rs @@ -1,3 +1,8 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at @@ -13,30 +18,37 @@ //! Autogenerated weights for pallet_referenda //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-27, STEPS: `20`, REPEAT: 1, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `cob`, CPU: `` -//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/release/substrate +// ./target/production/substrate // benchmark // pallet // --chain=dev -// --steps=20 -// --repeat=1 -// --pallet=pallet-referenda +// --steps=50 +// --repeat=20 +// --pallet=pallet_referenda +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* +// --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --output=./frame/referenda/src/._weights.rs +// --output=./frame/referenda/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_referenda. pub trait WeightInfo { @@ -68,487 +80,847 @@ pub trait WeightInfo { fn nudge_referendum_continue_confirming() -> Weight; fn nudge_referendum_approved() -> Weight; fn nudge_referendum_rejected() -> Weight; + fn set_some_metadata() -> Weight; + fn clear_metadata() -> Weight; } /// Weights for pallet_referenda using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Referenda ReferendumCount (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) - // Storage: Referenda ReferendumInfoFor (r:0 w:1) + /// Storage: Referenda ReferendumCount (r:1 w:1) + /// Proof: Referenda ReferendumCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: Referenda ReferendumInfoFor (r:0 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) fn submit() -> Weight { - // Minimum execution time: 29_000 nanoseconds. - Weight::from_ref_time(29_000_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `220` + // Estimated: `110487` + // Minimum execution time: 40_175_000 picoseconds. + Weight::from_parts(41_107_000, 110487) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn place_decision_deposit_preparing() -> Weight { - // Minimum execution time: 35_000 nanoseconds. - Weight::from_ref_time(35_000_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda DecidingCount (r:1 w:0) - // Storage: Referenda TrackQueue (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `473` + // Estimated: `219984` + // Minimum execution time: 50_922_000 picoseconds. + Weight::from_parts(52_179_000, 219984) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:0) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: Referenda TrackQueue (r:1 w:1) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn place_decision_deposit_queued() -> Weight { - // Minimum execution time: 40_000 nanoseconds. - Weight::from_ref_time(40_000_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda DecidingCount (r:1 w:0) - // Storage: Referenda TrackQueue (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3260` + // Estimated: `110487` + // Minimum execution time: 69_559_000 picoseconds. + Weight::from_parts(72_143_000, 110487) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:0) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: Referenda TrackQueue (r:1 w:1) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn place_decision_deposit_not_queued() -> Weight { - // Minimum execution time: 39_000 nanoseconds. - Weight::from_ref_time(39_000_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda DecidingCount (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `3280` + // Estimated: `110487` + // Minimum execution time: 68_833_000 picoseconds. + Weight::from_parts(70_987_000, 110487) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:1) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn place_decision_deposit_passing() -> Weight { - // Minimum execution time: 43_000 nanoseconds. - Weight::from_ref_time(43_000_000) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda DecidingCount (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `473` + // Estimated: `219984` + // Minimum execution time: 61_794_000 picoseconds. + Weight::from_parts(62_846_000, 219984) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:1) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn place_decision_deposit_failing() -> Weight { - // Minimum execution time: 84_000 nanoseconds. - Weight::from_ref_time(84_000_000) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `473` + // Estimated: `219984` + // Minimum execution time: 58_664_000 picoseconds. + Weight::from_parts(60_195_000, 219984) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) fn refund_decision_deposit() -> Weight { - // Minimum execution time: 25_000 nanoseconds. - Weight::from_ref_time(25_000_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3831` + // Minimum execution time: 30_850_000 picoseconds. + Weight::from_parts(32_130_000, 3831) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) fn refund_submission_deposit() -> Weight { - // Minimum execution time: 25_000 nanoseconds. - Weight::from_ref_time(25_000_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `341` + // Estimated: `3831` + // Minimum execution time: 30_747_000 picoseconds. + Weight::from_parts(32_196_000, 3831) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn cancel() -> Weight { - // Minimum execution time: 26_000 nanoseconds. - Weight::from_ref_time(26_000_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `381` + // Estimated: `219984` + // Minimum execution time: 36_139_000 picoseconds. + Weight::from_parts(37_252_000, 219984) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: Referenda MetadataOf (r:1 w:0) + /// Proof: Referenda MetadataOf (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) fn kill() -> Weight { - // Minimum execution time: 47_000 nanoseconds. - Weight::from_ref_time(47_000_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Referenda TrackQueue (r:1 w:0) - // Storage: Referenda DecidingCount (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `622` + // Estimated: `219984` + // Minimum execution time: 80_862_000 picoseconds. + Weight::from_parts(83_045_000, 219984) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Referenda TrackQueue (r:1 w:0) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:1) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) fn one_fewer_deciding_queue_empty() -> Weight { - // Minimum execution time: 8_000 nanoseconds. - Weight::from_ref_time(8_000_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Referenda TrackQueue (r:1 w:1) - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `5477` + // Minimum execution time: 10_136_000 picoseconds. + Weight::from_parts(10_638_000, 5477) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Referenda TrackQueue (r:1 w:1) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn one_fewer_deciding_failing() -> Weight { - // Minimum execution time: 88_000 nanoseconds. - Weight::from_ref_time(88_000_000) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Referenda TrackQueue (r:1 w:1) - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `3150` + // Estimated: `110487` + // Minimum execution time: 52_022_000 picoseconds. + Weight::from_parts(53_910_000, 110487) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Referenda TrackQueue (r:1 w:1) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn one_fewer_deciding_passing() -> Weight { - // Minimum execution time: 75_000 nanoseconds. - Weight::from_ref_time(75_000_000) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda TrackQueue (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3150` + // Estimated: `110487` + // Minimum execution time: 53_683_000 picoseconds. + Weight::from_parts(55_707_000, 110487) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:0) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda TrackQueue (r:1 w:1) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) fn nudge_referendum_requeued_insertion() -> Weight { - // Minimum execution time: 72_000 nanoseconds. - Weight::from_ref_time(72_000_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda TrackQueue (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3011` + // Estimated: `5477` + // Minimum execution time: 24_043_000 picoseconds. + Weight::from_parts(24_512_000, 5477) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:0) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda TrackQueue (r:1 w:1) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) fn nudge_referendum_requeued_slide() -> Weight { - // Minimum execution time: 56_000 nanoseconds. - Weight::from_ref_time(56_000_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda DecidingCount (r:1 w:0) - // Storage: Referenda TrackQueue (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3011` + // Estimated: `5477` + // Minimum execution time: 23_588_000 picoseconds. + Weight::from_parts(24_422_000, 5477) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:0) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: Referenda TrackQueue (r:1 w:1) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) fn nudge_referendum_queued() -> Weight { - // Minimum execution time: 55_000 nanoseconds. - Weight::from_ref_time(55_000_000) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda DecidingCount (r:1 w:0) - // Storage: Referenda TrackQueue (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3015` + // Estimated: `5477` + // Minimum execution time: 31_443_000 picoseconds. + Weight::from_parts(32_725_000, 5477) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:0) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: Referenda TrackQueue (r:1 w:1) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) fn nudge_referendum_not_queued() -> Weight { - // Minimum execution time: 60_000 nanoseconds. - Weight::from_ref_time(60_000_000) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3035` + // Estimated: `5477` + // Minimum execution time: 30_319_000 picoseconds. + Weight::from_parts(31_652_000, 5477) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_no_deposit() -> Weight { - // Minimum execution time: 22_000 nanoseconds. - Weight::from_ref_time(22_000_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `333` + // Estimated: `110487` + // Minimum execution time: 23_062_000 picoseconds. + Weight::from_parts(23_614_000, 110487) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_preparing() -> Weight { - // Minimum execution time: 21_000 nanoseconds. - Weight::from_ref_time(21_000_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `381` + // Estimated: `110487` + // Minimum execution time: 23_537_000 picoseconds. + Weight::from_parts(24_267_000, 110487) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) fn nudge_referendum_timed_out() -> Weight { - // Minimum execution time: 17_000 nanoseconds. - Weight::from_ref_time(17_000_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda DecidingCount (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `278` + // Estimated: `3831` + // Minimum execution time: 16_388_000 picoseconds. + Weight::from_parts(16_676_000, 3831) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:1) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_begin_deciding_failing() -> Weight { - // Minimum execution time: 29_000 nanoseconds. - Weight::from_ref_time(29_000_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda DecidingCount (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `381` + // Estimated: `110487` + // Minimum execution time: 32_801_000 picoseconds. + Weight::from_parts(34_053_000, 110487) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:1) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_begin_deciding_passing() -> Weight { - // Minimum execution time: 39_000 nanoseconds. - Weight::from_ref_time(39_000_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `381` + // Estimated: `110487` + // Minimum execution time: 35_704_000 picoseconds. + Weight::from_parts(36_451_000, 110487) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_begin_confirming() -> Weight { - // Minimum execution time: 31_000 nanoseconds. - Weight::from_ref_time(31_000_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `434` + // Estimated: `110487` + // Minimum execution time: 29_151_000 picoseconds. + Weight::from_parts(30_055_000, 110487) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_end_confirming() -> Weight { - // Minimum execution time: 30_000 nanoseconds. - Weight::from_ref_time(30_000_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `417` + // Estimated: `110487` + // Minimum execution time: 29_265_000 picoseconds. + Weight::from_parts(30_213_000, 110487) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_continue_not_confirming() -> Weight { - // Minimum execution time: 28_000 nanoseconds. - Weight::from_ref_time(28_000_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `434` + // Estimated: `110487` + // Minimum execution time: 27_760_000 picoseconds. + Weight::from_parts(28_381_000, 110487) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_continue_confirming() -> Weight { - // Minimum execution time: 30_000 nanoseconds. - Weight::from_ref_time(30_000_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) - // Storage: Scheduler Lookup (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `438` + // Estimated: `110487` + // Minimum execution time: 25_464_000 picoseconds. + Weight::from_parts(26_348_000, 110487) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: Scheduler Lookup (r:1 w:1) + /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn nudge_referendum_approved() -> Weight { - // Minimum execution time: 45_000 nanoseconds. - Weight::from_ref_time(45_000_000) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `438` + // Estimated: `219984` + // Minimum execution time: 42_629_000 picoseconds. + Weight::from_parts(43_732_000, 219984) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_rejected() -> Weight { - // Minimum execution time: 30_000 nanoseconds. - Weight::from_ref_time(30_000_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `434` + // Estimated: `110487` + // Minimum execution time: 30_015_000 picoseconds. + Weight::from_parts(30_827_000, 110487) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:0) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Preimage StatusFor (r:1 w:0) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Referenda MetadataOf (r:0 w:1) + /// Proof: Referenda MetadataOf (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + fn set_some_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `422` + // Estimated: `3831` + // Minimum execution time: 19_901_000 picoseconds. + Weight::from_parts(20_681_000, 3831) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:0) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda MetadataOf (r:1 w:1) + /// Proof: Referenda MetadataOf (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + fn clear_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `3831` + // Minimum execution time: 17_323_000 picoseconds. + Weight::from_parts(18_227_000, 3831) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Referenda ReferendumCount (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) - // Storage: Referenda ReferendumInfoFor (r:0 w:1) + /// Storage: Referenda ReferendumCount (r:1 w:1) + /// Proof: Referenda ReferendumCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: Referenda ReferendumInfoFor (r:0 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) fn submit() -> Weight { - // Minimum execution time: 29_000 nanoseconds. - Weight::from_ref_time(29_000_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `220` + // Estimated: `110487` + // Minimum execution time: 40_175_000 picoseconds. + Weight::from_parts(41_107_000, 110487) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn place_decision_deposit_preparing() -> Weight { - // Minimum execution time: 35_000 nanoseconds. - Weight::from_ref_time(35_000_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda DecidingCount (r:1 w:0) - // Storage: Referenda TrackQueue (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `473` + // Estimated: `219984` + // Minimum execution time: 50_922_000 picoseconds. + Weight::from_parts(52_179_000, 219984) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:0) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: Referenda TrackQueue (r:1 w:1) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn place_decision_deposit_queued() -> Weight { - // Minimum execution time: 40_000 nanoseconds. - Weight::from_ref_time(40_000_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda DecidingCount (r:1 w:0) - // Storage: Referenda TrackQueue (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3260` + // Estimated: `110487` + // Minimum execution time: 69_559_000 picoseconds. + Weight::from_parts(72_143_000, 110487) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:0) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: Referenda TrackQueue (r:1 w:1) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn place_decision_deposit_not_queued() -> Weight { - // Minimum execution time: 39_000 nanoseconds. - Weight::from_ref_time(39_000_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda DecidingCount (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `3280` + // Estimated: `110487` + // Minimum execution time: 68_833_000 picoseconds. + Weight::from_parts(70_987_000, 110487) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:1) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn place_decision_deposit_passing() -> Weight { - // Minimum execution time: 43_000 nanoseconds. - Weight::from_ref_time(43_000_000) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda DecidingCount (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `473` + // Estimated: `219984` + // Minimum execution time: 61_794_000 picoseconds. + Weight::from_parts(62_846_000, 219984) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:1) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn place_decision_deposit_failing() -> Weight { - // Minimum execution time: 84_000 nanoseconds. - Weight::from_ref_time(84_000_000) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `473` + // Estimated: `219984` + // Minimum execution time: 58_664_000 picoseconds. + Weight::from_parts(60_195_000, 219984) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) fn refund_decision_deposit() -> Weight { - // Minimum execution time: 25_000 nanoseconds. - Weight::from_ref_time(25_000_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `351` + // Estimated: `3831` + // Minimum execution time: 30_850_000 picoseconds. + Weight::from_parts(32_130_000, 3831) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) fn refund_submission_deposit() -> Weight { - // Minimum execution time: 25_000 nanoseconds. - Weight::from_ref_time(25_000_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `341` + // Estimated: `3831` + // Minimum execution time: 30_747_000 picoseconds. + Weight::from_parts(32_196_000, 3831) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn cancel() -> Weight { - // Minimum execution time: 26_000 nanoseconds. - Weight::from_ref_time(26_000_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `381` + // Estimated: `219984` + // Minimum execution time: 36_139_000 picoseconds. + Weight::from_parts(37_252_000, 219984) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: Referenda MetadataOf (r:1 w:0) + /// Proof: Referenda MetadataOf (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) fn kill() -> Weight { - // Minimum execution time: 47_000 nanoseconds. - Weight::from_ref_time(47_000_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Referenda TrackQueue (r:1 w:0) - // Storage: Referenda DecidingCount (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `622` + // Estimated: `219984` + // Minimum execution time: 80_862_000 picoseconds. + Weight::from_parts(83_045_000, 219984) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Referenda TrackQueue (r:1 w:0) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:1) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) fn one_fewer_deciding_queue_empty() -> Weight { - // Minimum execution time: 8_000 nanoseconds. - Weight::from_ref_time(8_000_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Referenda TrackQueue (r:1 w:1) - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `5477` + // Minimum execution time: 10_136_000 picoseconds. + Weight::from_parts(10_638_000, 5477) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Referenda TrackQueue (r:1 w:1) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn one_fewer_deciding_failing() -> Weight { - // Minimum execution time: 88_000 nanoseconds. - Weight::from_ref_time(88_000_000) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Referenda TrackQueue (r:1 w:1) - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `3150` + // Estimated: `110487` + // Minimum execution time: 52_022_000 picoseconds. + Weight::from_parts(53_910_000, 110487) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Referenda TrackQueue (r:1 w:1) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn one_fewer_deciding_passing() -> Weight { - // Minimum execution time: 75_000 nanoseconds. - Weight::from_ref_time(75_000_000) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda TrackQueue (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3150` + // Estimated: `110487` + // Minimum execution time: 53_683_000 picoseconds. + Weight::from_parts(55_707_000, 110487) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:0) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda TrackQueue (r:1 w:1) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) fn nudge_referendum_requeued_insertion() -> Weight { - // Minimum execution time: 72_000 nanoseconds. - Weight::from_ref_time(72_000_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda TrackQueue (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3011` + // Estimated: `5477` + // Minimum execution time: 24_043_000 picoseconds. + Weight::from_parts(24_512_000, 5477) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:0) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda TrackQueue (r:1 w:1) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) fn nudge_referendum_requeued_slide() -> Weight { - // Minimum execution time: 56_000 nanoseconds. - Weight::from_ref_time(56_000_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda DecidingCount (r:1 w:0) - // Storage: Referenda TrackQueue (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3011` + // Estimated: `5477` + // Minimum execution time: 23_588_000 picoseconds. + Weight::from_parts(24_422_000, 5477) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:0) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: Referenda TrackQueue (r:1 w:1) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) fn nudge_referendum_queued() -> Weight { - // Minimum execution time: 55_000 nanoseconds. - Weight::from_ref_time(55_000_000) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda DecidingCount (r:1 w:0) - // Storage: Referenda TrackQueue (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3015` + // Estimated: `5477` + // Minimum execution time: 31_443_000 picoseconds. + Weight::from_parts(32_725_000, 5477) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:0) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: Referenda TrackQueue (r:1 w:1) + /// Proof: Referenda TrackQueue (max_values: None, max_size: Some(2012), added: 4487, mode: MaxEncodedLen) fn nudge_referendum_not_queued() -> Weight { - // Minimum execution time: 60_000 nanoseconds. - Weight::from_ref_time(60_000_000) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `3035` + // Estimated: `5477` + // Minimum execution time: 30_319_000 picoseconds. + Weight::from_parts(31_652_000, 5477) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_no_deposit() -> Weight { - // Minimum execution time: 22_000 nanoseconds. - Weight::from_ref_time(22_000_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `333` + // Estimated: `110487` + // Minimum execution time: 23_062_000 picoseconds. + Weight::from_parts(23_614_000, 110487) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_preparing() -> Weight { - // Minimum execution time: 21_000 nanoseconds. - Weight::from_ref_time(21_000_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `381` + // Estimated: `110487` + // Minimum execution time: 23_537_000 picoseconds. + Weight::from_parts(24_267_000, 110487) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) fn nudge_referendum_timed_out() -> Weight { - // Minimum execution time: 17_000 nanoseconds. - Weight::from_ref_time(17_000_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda DecidingCount (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `278` + // Estimated: `3831` + // Minimum execution time: 16_388_000 picoseconds. + Weight::from_parts(16_676_000, 3831) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:1) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_begin_deciding_failing() -> Weight { - // Minimum execution time: 29_000 nanoseconds. - Weight::from_ref_time(29_000_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Referenda DecidingCount (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `381` + // Estimated: `110487` + // Minimum execution time: 32_801_000 picoseconds. + Weight::from_parts(34_053_000, 110487) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda DecidingCount (r:1 w:1) + /// Proof: Referenda DecidingCount (max_values: None, max_size: Some(14), added: 2489, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_begin_deciding_passing() -> Weight { - // Minimum execution time: 39_000 nanoseconds. - Weight::from_ref_time(39_000_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `381` + // Estimated: `110487` + // Minimum execution time: 35_704_000 picoseconds. + Weight::from_parts(36_451_000, 110487) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_begin_confirming() -> Weight { - // Minimum execution time: 31_000 nanoseconds. - Weight::from_ref_time(31_000_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `434` + // Estimated: `110487` + // Minimum execution time: 29_151_000 picoseconds. + Weight::from_parts(30_055_000, 110487) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_end_confirming() -> Weight { - // Minimum execution time: 30_000 nanoseconds. - Weight::from_ref_time(30_000_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `417` + // Estimated: `110487` + // Minimum execution time: 29_265_000 picoseconds. + Weight::from_parts(30_213_000, 110487) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_continue_not_confirming() -> Weight { - // Minimum execution time: 28_000 nanoseconds. - Weight::from_ref_time(28_000_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `434` + // Estimated: `110487` + // Minimum execution time: 27_760_000 picoseconds. + Weight::from_parts(28_381_000, 110487) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_continue_confirming() -> Weight { - // Minimum execution time: 30_000 nanoseconds. - Weight::from_ref_time(30_000_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:2 w:2) - // Storage: Scheduler Lookup (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `438` + // Estimated: `110487` + // Minimum execution time: 25_464_000 picoseconds. + Weight::from_parts(26_348_000, 110487) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:2 w:2) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: Scheduler Lookup (r:1 w:1) + /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn nudge_referendum_approved() -> Weight { - // Minimum execution time: 45_000 nanoseconds. - Weight::from_ref_time(45_000_000) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Referenda ReferendumInfoFor (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `438` + // Estimated: `219984` + // Minimum execution time: 42_629_000 picoseconds. + Weight::from_parts(43_732_000, 219984) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:1) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) fn nudge_referendum_rejected() -> Weight { - // Minimum execution time: 30_000 nanoseconds. - Weight::from_ref_time(30_000_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `434` + // Estimated: `110487` + // Minimum execution time: 30_015_000 picoseconds. + Weight::from_parts(30_827_000, 110487) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:0) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Preimage StatusFor (r:1 w:0) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) + /// Storage: Referenda MetadataOf (r:0 w:1) + /// Proof: Referenda MetadataOf (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + fn set_some_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `422` + // Estimated: `3831` + // Minimum execution time: 19_901_000 picoseconds. + Weight::from_parts(20_681_000, 3831) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Referenda ReferendumInfoFor (r:1 w:0) + /// Proof: Referenda ReferendumInfoFor (max_values: None, max_size: Some(366), added: 2841, mode: MaxEncodedLen) + /// Storage: Referenda MetadataOf (r:1 w:1) + /// Proof: Referenda MetadataOf (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + fn clear_metadata() -> Weight { + // Proof Size summary in bytes: + // Measured: `355` + // Estimated: `3831` + // Minimum execution time: 17_323_000 picoseconds. + Weight::from_parts(18_227_000, 3831) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } diff --git a/frame/remark/Cargo.toml b/frame/remark/Cargo.toml index 151cc3888449..3183f5a79675 100644 --- a/frame/remark/Cargo.toml +++ b/frame/remark/Cargo.toml @@ -13,19 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", optional = true } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", optional = true } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/remark/src/benchmarking.rs b/frame/remark/src/benchmarking.rs index c0db8d5d3d59..831946834963 100644 --- a/frame/remark/src/benchmarking.rs +++ b/frame/remark/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_benchmarking::v1::{benchmarks, whitelisted_caller}; use frame_system::{EventRecord, Pallet as System, RawOrigin}; use sp_std::*; diff --git a/frame/remark/src/lib.rs b/frame/remark/src/lib.rs index 80fe393c20f4..8ca3cd395afb 100644 --- a/frame/remark/src/lib.rs +++ b/frame/remark/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/remark/src/mock.rs b/frame/remark/src/mock.rs index 22467796cf37..e597a1ca4dfe 100644 --- a/frame/remark/src/mock.rs +++ b/frame/remark/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,22 +21,17 @@ use crate as pallet_remark; use frame_support::traits::{ConstU16, ConstU32, ConstU64}; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; pub type Block = frame_system::mocking::MockBlock; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Remark: pallet_remark::{ Pallet, Call, Event }, } ); @@ -47,13 +42,12 @@ impl frame_system::Config for Test { type BlockLength = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type DbWeight = (); @@ -74,6 +68,6 @@ impl pallet_remark::Config for Test { } pub fn new_test_ext() -> sp_io::TestExternalities { - let t = GenesisConfig { system: Default::default() }.build_storage().unwrap(); + let t = RuntimeGenesisConfig { system: Default::default() }.build_storage().unwrap(); t.into() } diff --git a/frame/remark/src/tests.rs b/frame/remark/src/tests.rs index 2278e3817b48..51fb32b60fc1 100644 --- a/frame/remark/src/tests.rs +++ b/frame/remark/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/remark/src/weights.rs b/frame/remark/src/weights.rs index 0d739657c852..46475db163ff 100644 --- a/frame/remark/src/weights.rs +++ b/frame/remark/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_remark //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_remark +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_remark. pub trait WeightInfo { @@ -53,26 +58,28 @@ pub trait WeightInfo { /// Weights for pallet_remark using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) /// The range of component `l` is `[1, 1048576]`. fn store(l: u32, ) -> Weight { - // Minimum execution time: 17_017 nanoseconds. - Weight::from_ref_time(8_269_935 as u64) - // Standard Error: 1 - .saturating_add(Weight::from_ref_time(1_407 as u64).saturating_mul(l as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_471_000 picoseconds. + Weight::from_parts(8_586_000, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1_359, 0).saturating_mul(l.into())) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) /// The range of component `l` is `[1, 1048576]`. fn store(l: u32, ) -> Weight { - // Minimum execution time: 17_017 nanoseconds. - Weight::from_ref_time(8_269_935 as u64) - // Standard Error: 1 - .saturating_add(Weight::from_ref_time(1_407 as u64).saturating_mul(l as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_471_000 picoseconds. + Weight::from_parts(8_586_000, 0) + // Standard Error: 0 + .saturating_add(Weight::from_parts(1_359, 0).saturating_mul(l.into())) } } diff --git a/frame/root-offences/Cargo.toml b/frame/root-offences/Cargo.toml index fd1f7c6325ea..fa0a724a6bb6 100644 --- a/frame/root-offences/Cargo.toml +++ b/frame/root-offences/Cargo.toml @@ -13,15 +13,15 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } pallet-session = { version = "4.0.0-dev", features = [ "historical" ], path = "../../frame/session", default-features = false } pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../frame/staking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } [dev-dependencies] @@ -29,9 +29,9 @@ pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } diff --git a/frame/root-offences/src/lib.rs b/frame/root-offences/src/lib.rs index ed039f46becc..a93e7ff84871 100644 --- a/frame/root-offences/src/lib.rs +++ b/frame/root-offences/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -57,7 +57,6 @@ pub mod pallet { } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::event] diff --git a/frame/root-offences/src/mock.rs b/frame/root-offences/src/mock.rs index 273fbf614169..92d188f49fb9 100644 --- a/frame/root-offences/src/mock.rs +++ b/frame/root-offences/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,19 +21,19 @@ use crate as root_offences; use frame_election_provider_support::{onchain, SequentialPhragmen}; use frame_support::{ parameter_types, - traits::{ConstU32, ConstU64, GenesisBuild, Hooks, OneSessionHandler}, + traits::{ConstU32, ConstU64, Hooks, OneSessionHandler}, }; use pallet_staking::StakerStatus; use sp_core::H256; use sp_runtime::{ curve::PiecewiseLinear, - testing::{Header, UintAuthorityId}, + testing::UintAuthorityId, traits::{BlakeTwo256, IdentityLookup, Zero}, + BuildStorage, }; use sp_staking::{EraIndex, SessionIndex}; use sp_std::collections::btree_map::BTreeMap; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; type AccountId = u64; type Balance = u64; @@ -43,12 +43,9 @@ pub const INIT_TIMESTAMP: u64 = 30_000; pub const BLOCK_TIME: u64 = 1000; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, @@ -90,14 +87,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -121,6 +117,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } pallet_staking_reward_curve::build! { @@ -145,17 +145,6 @@ impl onchain::Config for OnChainSeqPhragmen { type TargetsBound = ConstU32<{ u32::MAX }>; } -pub struct OnStakerSlashMock(core::marker::PhantomData); -impl sp_staking::OnStakerSlash for OnStakerSlashMock { - fn on_slash( - _pool_account: &AccountId, - slashed_bonded: Balance, - slashed_chunks: &BTreeMap, - ) { - LedgerSlashPerEra::set((slashed_bonded, slashed_chunks.clone())); - } -} - parameter_types! { pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; pub static Offset: BlockNumber = 0; @@ -172,7 +161,7 @@ impl pallet_staking::Config for Test { type Currency = Balances; type CurrencyBalance = ::Balance; type UnixTime = Timestamp; - type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type CurrencyToVote = (); type RewardRemainder = (); type RuntimeEvent = RuntimeEvent; type Slash = (); @@ -192,7 +181,7 @@ impl pallet_staking::Config for Test { type MaxUnlockingChunks = ConstU32<32>; type HistoryDepth = ConstU32<84>; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; - type OnStakerSlash = OnStakerSlashMock; + type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } @@ -251,11 +240,11 @@ impl Default for ExtBuilder { impl ExtBuilder { fn build(self) -> sp_io::TestExternalities { - let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![ - //controllers + // controllers (still used in some tests. Soon to be deprecated). (10, self.balance_factor * 50), (20, self.balance_factor * 50), (30, self.balance_factor * 50), @@ -273,12 +262,12 @@ impl ExtBuilder { let stakers = vec![ // (stash, ctrl, stake, status) // these two will be elected in the default test where we elect 2. - (11, 10, 1000, StakerStatus::::Validator), - (21, 20, 1000, StakerStatus::::Validator), + (11, 11, 1000, StakerStatus::::Validator), + (21, 21, 1000, StakerStatus::::Validator), // a loser validator - (31, 30, 500, StakerStatus::::Validator), + (31, 31, 500, StakerStatus::::Validator), // an idle validator - (41, 40, 1000, StakerStatus::::Idle), + (41, 41, 1000, StakerStatus::::Idle), ]; let _ = pallet_staking::GenesisConfig:: { diff --git a/frame/root-offences/src/tests.rs b/frame/root-offences/src/tests.rs index a8b7d0a6d6ac..f96884d750da 100644 --- a/frame/root-offences/src/tests.rs +++ b/frame/root-offences/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -44,7 +44,7 @@ fn create_offence_works_given_root_origin() { // the slash should be applied right away. assert_eq!(Balances::free_balance(11), 500); - // the other validator should keep his balance, because we only created + // the other validator should keep their balance, because we only created // an offences for the first validator. assert_eq!(Balances::free_balance(21), 1000); }) @@ -68,7 +68,7 @@ fn create_offence_wont_slash_non_active_validators() { // so 31 didn't get slashed. assert_eq!(Balances::free_balance(31), 500); - // but 11 is an active validator so he got slashed. + // but 11 is an active validator so they got slashed. assert_eq!(Balances::free_balance(11), 800); }) } diff --git a/frame/root-testing/Cargo.toml b/frame/root-testing/Cargo.toml index 09262b3d31db..743fdf61027d 100644 --- a/frame/root-testing/Cargo.toml +++ b/frame/root-testing/Cargo.toml @@ -14,14 +14,14 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [features] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/root-testing/src/lib.rs b/frame/root-testing/src/lib.rs index da6790496785..e04c7bfa13d2 100644 --- a/frame/root-testing/src/lib.rs +++ b/frame/root-testing/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,7 +39,6 @@ pub mod pallet { pub trait Config: frame_system::Config {} #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::call] diff --git a/frame/salary/Cargo.toml b/frame/salary/Cargo.toml new file mode 100644 index 000000000000..61ef36f88c4d --- /dev/null +++ b/frame/salary/Cargo.toml @@ -0,0 +1,49 @@ +[package] +name = "pallet-salary" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Paymaster" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +log = { version = "0.4.16", default-features = false } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "log/std", + "scale-info/std", + "sp-arithmetic/std", + "sp-core/std", + "sp-io/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/salary/README.md b/frame/salary/README.md new file mode 100644 index 000000000000..25c1be0e805d --- /dev/null +++ b/frame/salary/README.md @@ -0,0 +1,3 @@ +# Salary + +Make periodic payment to members of a ranked collective according to rank. \ No newline at end of file diff --git a/frame/salary/src/benchmarking.rs b/frame/salary/src/benchmarking.rs new file mode 100644 index 000000000000..7528293506ae --- /dev/null +++ b/frame/salary/src/benchmarking.rs @@ -0,0 +1,183 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Salary pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +use crate::Pallet as Salary; + +use frame_benchmarking::v2::*; +use frame_system::{Pallet as System, RawOrigin}; +use sp_core::Get; + +const SEED: u32 = 0; + +fn ensure_member_with_salary, I: 'static>(who: &T::AccountId) { + // induct if not a member. + if T::Members::rank_of(who).is_none() { + T::Members::induct(who).unwrap(); + } + // promote until they have a salary. + for _ in 0..255 { + let r = T::Members::rank_of(who).expect("prior guard ensures `who` is a member; qed"); + if !T::Salary::get_salary(r, &who).is_zero() { + break + } + T::Members::promote(who).unwrap(); + } +} + +#[instance_benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn init() { + let caller: T::AccountId = whitelisted_caller(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + assert!(Salary::::status().is_some()); + } + + #[benchmark] + fn bump() { + let caller: T::AccountId = whitelisted_caller(); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + Salary::::cycle_period()); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + assert_eq!(Salary::::status().unwrap().cycle_index, 1u32.into()); + } + + #[benchmark] + fn induct() { + let caller = whitelisted_caller(); + ensure_member_with_salary::(&caller); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + assert!(Salary::::last_active(&caller).is_ok()); + } + + #[benchmark] + fn register() { + let caller = whitelisted_caller(); + ensure_member_with_salary::(&caller); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + Salary::::induct(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + Salary::::cycle_period()); + Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + assert_eq!(Salary::::last_active(&caller).unwrap(), 1u32.into()); + } + + #[benchmark] + fn payout() { + let caller = whitelisted_caller(); + ensure_member_with_salary::(&caller); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + Salary::::induct(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + Salary::::cycle_period()); + Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + T::RegistrationPeriod::get()); + + let salary = T::Salary::get_salary(T::Members::rank_of(&caller).unwrap(), &caller); + T::Paymaster::ensure_successful(&caller, (), salary); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + match Claimant::::get(&caller) { + Some(ClaimantStatus { last_active, status: Attempted { id, .. } }) => { + assert_eq!(last_active, 1u32.into()); + assert_ne!(T::Paymaster::check_payment(id), PaymentStatus::Failure); + }, + _ => panic!("No claim made"), + } + assert!(Salary::::payout(RawOrigin::Signed(caller.clone()).into()).is_err()); + } + + #[benchmark] + fn payout_other() { + let caller = whitelisted_caller(); + ensure_member_with_salary::(&caller); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + Salary::::induct(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + Salary::::cycle_period()); + Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + T::RegistrationPeriod::get()); + + let salary = T::Salary::get_salary(T::Members::rank_of(&caller).unwrap(), &caller); + let recipient: T::AccountId = account("recipient", 0, SEED); + T::Paymaster::ensure_successful(&recipient, (), salary); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), recipient.clone()); + + match Claimant::::get(&caller) { + Some(ClaimantStatus { last_active, status: Attempted { id, .. } }) => { + assert_eq!(last_active, 1u32.into()); + assert_ne!(T::Paymaster::check_payment(id), PaymentStatus::Failure); + }, + _ => panic!("No claim made"), + } + assert!(Salary::::payout(RawOrigin::Signed(caller.clone()).into()).is_err()); + } + + #[benchmark] + fn check_payment() { + let caller = whitelisted_caller(); + ensure_member_with_salary::(&caller); + Salary::::init(RawOrigin::Signed(caller.clone()).into()).unwrap(); + Salary::::induct(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + Salary::::cycle_period()); + Salary::::bump(RawOrigin::Signed(caller.clone()).into()).unwrap(); + System::::set_block_number(System::::block_number() + T::RegistrationPeriod::get()); + + let salary = T::Salary::get_salary(T::Members::rank_of(&caller).unwrap(), &caller); + let recipient: T::AccountId = account("recipient", 0, SEED); + T::Paymaster::ensure_successful(&recipient, (), salary); + Salary::::payout(RawOrigin::Signed(caller.clone()).into()).unwrap(); + let id = match Claimant::::get(&caller).unwrap().status { + Attempted { id, .. } => id, + _ => panic!("No claim made"), + }; + T::Paymaster::ensure_concluded(id); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone())); + + assert!(!matches!(Claimant::::get(&caller).unwrap().status, Attempted { .. })); + } + + impl_benchmark_test_suite! { + Salary, + crate::tests::new_test_ext(), + crate::tests::Test, + } +} diff --git a/frame/salary/src/lib.rs b/frame/salary/src/lib.rs new file mode 100644 index 000000000000..53dd7224909a --- /dev/null +++ b/frame/salary/src/lib.rs @@ -0,0 +1,450 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Make periodic payment to members of a ranked collective according to rank. + +#![cfg_attr(not(feature = "std"), no_std)] +#![recursion_limit = "128"] + +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_arithmetic::traits::{Saturating, Zero}; +use sp_runtime::Perbill; +use sp_std::{marker::PhantomData, prelude::*}; + +use frame_support::{ + dispatch::DispatchResultWithPostInfo, + ensure, + traits::{ + tokens::{GetSalary, Pay, PaymentStatus}, + RankedMembers, + }, + RuntimeDebug, +}; + +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod weights; + +pub use pallet::*; +pub use weights::WeightInfo; + +/// Payroll cycle. +pub type Cycle = u32; + +/// The status of the pallet instance. +#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub struct StatusType { + /// The index of the "current cycle" (i.e. the last cycle being processed). + cycle_index: CycleIndex, + /// The first block of the "current cycle" (i.e. the last cycle being processed). + cycle_start: BlockNumber, + /// The total budget available for all payments in the current cycle. + budget: Balance, + /// The total amount of the payments registered in the current cycle. + total_registrations: Balance, + /// The total amount of unregistered payments which have been made in the current cycle. + total_unregistered_paid: Balance, +} + +/// The state of a specific payment claim. +#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub enum ClaimState { + /// No claim recorded. + Nothing, + /// Amount reserved when last active. + Registered(Balance), + /// Amount attempted to be paid when last active as well as the identity of the payment. + Attempted { registered: Option, id: Id, amount: Balance }, +} + +use ClaimState::*; + +/// The status of a single payee/claimant. +#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub struct ClaimantStatus { + /// The most recent cycle in which the claimant was active. + last_active: CycleIndex, + /// The state of the payment/claim with in the above cycle. + status: ClaimState, +} + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::{dispatch::Pays, pallet_prelude::*}; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + + /// The runtime event type. + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + + /// Means by which we can make payments to accounts. This also defines the currency and the + /// balance which we use to denote that currency. + type Paymaster: Pay::AccountId, AssetKind = ()>; + + /// The current membership of payees. + type Members: RankedMembers::AccountId>; + + /// The maximum payout to be made for a single period to an active member of the given rank. + /// + /// The benchmarks require that this be non-zero for some rank at most 255. + type Salary: GetSalary< + ::Rank, + Self::AccountId, + ::Balance, + >; + + /// The number of blocks within a cycle which accounts have to register their intent to + /// claim. + /// + /// The number of blocks between sequential payout cycles is the sum of this and + /// `PayoutPeriod`. + #[pallet::constant] + type RegistrationPeriod: Get>; + + /// The number of blocks within a cycle which accounts have to claim the payout. + /// + /// The number of blocks between sequential payout cycles is the sum of this and + /// `RegistrationPeriod`. + #[pallet::constant] + type PayoutPeriod: Get>; + + /// The total budget per cycle. + /// + /// This may change over the course of a cycle without any problem. + #[pallet::constant] + type Budget: Get>; + } + + pub type CycleIndexOf = BlockNumberFor; + pub type BalanceOf = <>::Paymaster as Pay>::Balance; + pub type IdOf = <>::Paymaster as Pay>::Id; + pub type StatusOf = StatusType, BlockNumberFor, BalanceOf>; + pub type ClaimantStatusOf = ClaimantStatus, BalanceOf, IdOf>; + + /// The overall status of the system. + #[pallet::storage] + pub(super) type Status, I: 'static = ()> = + StorageValue<_, StatusOf, OptionQuery>; + + /// The status of a claimant. + #[pallet::storage] + pub(super) type Claimant, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, ClaimantStatusOf, OptionQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + /// A member is inducted into the payroll. + Inducted { who: T::AccountId }, + /// A member registered for a payout. + Registered { who: T::AccountId, amount: BalanceOf }, + /// A payment happened. + Paid { + who: T::AccountId, + beneficiary: T::AccountId, + amount: BalanceOf, + id: ::Id, + }, + /// The next cycle begins. + CycleStarted { index: CycleIndexOf }, + } + + #[pallet::error] + pub enum Error { + /// The salary system has already been started. + AlreadyStarted, + /// The account is not a ranked member. + NotMember, + /// The account is already inducted. + AlreadyInducted, + // The account is not yet inducted into the system. + NotInducted, + /// The member does not have a current valid claim. + NoClaim, + /// The member's claim is zero. + ClaimZero, + /// Current cycle's registration period is over. + TooLate, + /// Current cycle's payment period is not yet begun. + TooEarly, + /// Cycle is not yet over. + NotYet, + /// The payout cycles have not yet started. + NotStarted, + /// There is no budget left for the payout. + Bankrupt, + /// There was some issue with the mechanism of payment. + PayError, + /// The payment has neither failed nor succeeded yet. + Inconclusive, + /// The cycle is after that in which the payment was made. + NotCurrent, + } + + #[pallet::call] + impl, I: 'static> Pallet { + /// Start the first payout cycle. + /// + /// - `origin`: A `Signed` origin of an account. + #[pallet::weight(T::WeightInfo::init())] + #[pallet::call_index(0)] + pub fn init(origin: OriginFor) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + let now = frame_system::Pallet::::block_number(); + ensure!(!Status::::exists(), Error::::AlreadyStarted); + let status = StatusType { + cycle_index: Zero::zero(), + cycle_start: now, + budget: T::Budget::get(), + total_registrations: Zero::zero(), + total_unregistered_paid: Zero::zero(), + }; + Status::::put(&status); + + Self::deposit_event(Event::::CycleStarted { index: status.cycle_index }); + Ok(Pays::No.into()) + } + + /// Move to next payout cycle, assuming that the present block is now within that cycle. + /// + /// - `origin`: A `Signed` origin of an account. + #[pallet::weight(T::WeightInfo::bump())] + #[pallet::call_index(1)] + pub fn bump(origin: OriginFor) -> DispatchResultWithPostInfo { + let _ = ensure_signed(origin)?; + let now = frame_system::Pallet::::block_number(); + let cycle_period = Self::cycle_period(); + let mut status = Status::::get().ok_or(Error::::NotStarted)?; + status.cycle_start.saturating_accrue(cycle_period); + ensure!(now >= status.cycle_start, Error::::NotYet); + status.cycle_index.saturating_inc(); + status.budget = T::Budget::get(); + status.total_registrations = Zero::zero(); + status.total_unregistered_paid = Zero::zero(); + Status::::put(&status); + + Self::deposit_event(Event::::CycleStarted { index: status.cycle_index }); + Ok(Pays::No.into()) + } + + /// Induct oneself into the payout system. + #[pallet::weight(T::WeightInfo::induct())] + #[pallet::call_index(2)] + pub fn induct(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let cycle_index = Status::::get().ok_or(Error::::NotStarted)?.cycle_index; + let _ = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + ensure!(!Claimant::::contains_key(&who), Error::::AlreadyInducted); + + Claimant::::insert( + &who, + ClaimantStatus { last_active: cycle_index, status: Nothing }, + ); + + Self::deposit_event(Event::::Inducted { who }); + Ok(Pays::No.into()) + } + + /// Register for a payout. + /// + /// Will only work if we are in the first `RegistrationPeriod` blocks since the cycle + /// started. + /// + /// - `origin`: A `Signed` origin of an account which is a member of `Members`. + #[pallet::weight(T::WeightInfo::register())] + #[pallet::call_index(3)] + pub fn register(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + let mut status = Status::::get().ok_or(Error::::NotStarted)?; + let mut claimant = Claimant::::get(&who).ok_or(Error::::NotInducted)?; + let now = frame_system::Pallet::::block_number(); + ensure!( + now < status.cycle_start + T::RegistrationPeriod::get(), + Error::::TooLate + ); + ensure!(claimant.last_active < status.cycle_index, Error::::NoClaim); + let payout = T::Salary::get_salary(rank, &who); + ensure!(!payout.is_zero(), Error::::ClaimZero); + claimant.last_active = status.cycle_index; + claimant.status = Registered(payout); + status.total_registrations.saturating_accrue(payout); + + Claimant::::insert(&who, &claimant); + Status::::put(&status); + + Self::deposit_event(Event::::Registered { who, amount: payout }); + Ok(Pays::No.into()) + } + + /// Request a payout. + /// + /// Will only work if we are after the first `RegistrationPeriod` blocks since the cycle + /// started but by no more than `PayoutPeriod` blocks. + /// + /// - `origin`: A `Signed` origin of an account which is a member of `Members`. + #[pallet::weight(T::WeightInfo::payout())] + #[pallet::call_index(4)] + pub fn payout(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + Self::do_payout(who.clone(), who)?; + Ok(Pays::No.into()) + } + + /// Request a payout to a secondary account. + /// + /// Will only work if we are after the first `RegistrationPeriod` blocks since the cycle + /// started but by no more than `PayoutPeriod` blocks. + /// + /// - `origin`: A `Signed` origin of an account which is a member of `Members`. + /// - `beneficiary`: The account to receive payment. + #[pallet::weight(T::WeightInfo::payout_other())] + #[pallet::call_index(5)] + pub fn payout_other( + origin: OriginFor, + beneficiary: T::AccountId, + ) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + Self::do_payout(who, beneficiary)?; + Ok(Pays::No.into()) + } + + /// Update a payment's status; if it failed, alter the state so the payment can be retried. + /// + /// This must be called within the same cycle as the failed payment. It will fail with + /// `Event::NotCurrent` otherwise. + /// + /// - `origin`: A `Signed` origin of an account which is a member of `Members` who has + /// received a payment this cycle. + #[pallet::weight(T::WeightInfo::check_payment())] + #[pallet::call_index(6)] + pub fn check_payment(origin: OriginFor) -> DispatchResultWithPostInfo { + let who = ensure_signed(origin)?; + + let mut status = Status::::get().ok_or(Error::::NotStarted)?; + let mut claimant = Claimant::::get(&who).ok_or(Error::::NotInducted)?; + ensure!(claimant.last_active == status.cycle_index, Error::::NotCurrent); + let (id, registered, amount) = match claimant.status { + Attempted { id, registered, amount } => (id, registered, amount), + _ => return Err(Error::::NoClaim.into()), + }; + match T::Paymaster::check_payment(id) { + PaymentStatus::Failure => { + // Payment failed: we reset back to the status prior to payment. + if let Some(amount) = registered { + // Account registered; this makes it simple to roll back and allow retry. + claimant.status = ClaimState::Registered(amount); + } else { + // Account didn't register; we set it to `Nothing` but must decrement + // the `last_active` also to ensure a retry works. + claimant.last_active.saturating_reduce(1u32.into()); + claimant.status = ClaimState::Nothing; + // Since it is not registered, we must walk back our counter for what has + // been paid. + status.total_unregistered_paid.saturating_reduce(amount); + } + }, + PaymentStatus::Success => claimant.status = ClaimState::Nothing, + _ => return Err(Error::::Inconclusive.into()), + } + Claimant::::insert(&who, &claimant); + Status::::put(&status); + + Ok(Pays::No.into()) + } + } + + impl, I: 'static> Pallet { + pub fn status() -> Option> { + Status::::get() + } + pub fn last_active(who: &T::AccountId) -> Result, DispatchError> { + Ok(Claimant::::get(&who).ok_or(Error::::NotInducted)?.last_active) + } + pub fn cycle_period() -> BlockNumberFor { + T::RegistrationPeriod::get() + T::PayoutPeriod::get() + } + fn do_payout(who: T::AccountId, beneficiary: T::AccountId) -> DispatchResult { + let mut status = Status::::get().ok_or(Error::::NotStarted)?; + let mut claimant = Claimant::::get(&who).ok_or(Error::::NotInducted)?; + + let now = frame_system::Pallet::::block_number(); + ensure!( + now >= status.cycle_start + T::RegistrationPeriod::get(), + Error::::TooEarly, + ); + + let (payout, registered) = match claimant.status { + Registered(unpaid) if claimant.last_active == status.cycle_index => { + // Registered for this cycle. Pay accordingly. + let payout = if status.total_registrations <= status.budget { + // Can pay in full. + unpaid + } else { + // Must be reduced pro-rata + Perbill::from_rational(status.budget, status.total_registrations) + .mul_floor(unpaid) + }; + (payout, Some(unpaid)) + }, + Nothing | Attempted { .. } if claimant.last_active < status.cycle_index => { + // Not registered for this cycle. Pay from whatever is left. + let rank = T::Members::rank_of(&who).ok_or(Error::::NotMember)?; + let ideal_payout = T::Salary::get_salary(rank, &who); + + let pot = status + .budget + .saturating_sub(status.total_registrations) + .saturating_sub(status.total_unregistered_paid); + + let payout = ideal_payout.min(pot); + ensure!(!payout.is_zero(), Error::::ClaimZero); + + status.total_unregistered_paid.saturating_accrue(payout); + (payout, None) + }, + _ => return Err(Error::::NoClaim.into()), + }; + + claimant.last_active = status.cycle_index; + + let id = + T::Paymaster::pay(&beneficiary, (), payout).map_err(|_| Error::::PayError)?; + + claimant.status = Attempted { registered, id, amount: payout }; + + Claimant::::insert(&who, &claimant); + Status::::put(&status); + + Self::deposit_event(Event::::Paid { who, beneficiary, amount: payout, id }); + Ok(()) + } + } +} diff --git a/frame/salary/src/tests.rs b/frame/salary/src/tests.rs new file mode 100644 index 000000000000..034dce24b8b3 --- /dev/null +++ b/frame/salary/src/tests.rs @@ -0,0 +1,641 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! The crate's tests. + +use std::collections::BTreeMap; + +use frame_support::{ + assert_noop, assert_ok, + pallet_prelude::Weight, + parameter_types, + traits::{tokens::ConvertRank, ConstU32, ConstU64, Everything}, +}; +use sp_core::H256; +use sp_runtime::{ + traits::{BlakeTwo256, Identity, IdentityLookup}, + BuildStorage, DispatchResult, +}; +use sp_std::cell::RefCell; + +use super::*; +use crate as pallet_salary; + +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Salary: pallet_salary::{Pallet, Call, Storage, Event}, + } +); + +parameter_types! { + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1_000_000, 0)); +} +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +thread_local! { + pub static PAID: RefCell> = RefCell::new(BTreeMap::new()); + pub static STATUS: RefCell> = RefCell::new(BTreeMap::new()); + pub static LAST_ID: RefCell = RefCell::new(0u64); +} + +fn paid(who: u64) -> u64 { + PAID.with(|p| p.borrow().get(&who).cloned().unwrap_or(0)) +} +fn unpay(who: u64, amount: u64) { + PAID.with(|p| p.borrow_mut().entry(who).or_default().saturating_reduce(amount)) +} +fn set_status(id: u64, s: PaymentStatus) { + STATUS.with(|m| m.borrow_mut().insert(id, s)); +} + +pub struct TestPay; +impl Pay for TestPay { + type Beneficiary = u64; + type Balance = u64; + type Id = u64; + type AssetKind = (); + type Error = (); + + fn pay( + who: &Self::Beneficiary, + _: Self::AssetKind, + amount: Self::Balance, + ) -> Result { + PAID.with(|paid| *paid.borrow_mut().entry(*who).or_default() += amount); + Ok(LAST_ID.with(|lid| { + let x = *lid.borrow(); + lid.replace(x + 1); + x + })) + } + fn check_payment(id: Self::Id) -> PaymentStatus { + STATUS.with(|s| s.borrow().get(&id).cloned().unwrap_or(PaymentStatus::Unknown)) + } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(_: &Self::Beneficiary, _: Self::AssetKind, _: Self::Balance) {} + #[cfg(feature = "runtime-benchmarks")] + fn ensure_concluded(id: Self::Id) { + set_status(id, PaymentStatus::Failure) + } +} + +thread_local! { + pub static CLUB: RefCell> = RefCell::new(BTreeMap::new()); +} + +pub struct TestClub; +impl RankedMembers for TestClub { + type AccountId = u64; + type Rank = u64; + fn min_rank() -> Self::Rank { + 0 + } + fn rank_of(who: &Self::AccountId) -> Option { + CLUB.with(|club| club.borrow().get(who).cloned()) + } + fn induct(who: &Self::AccountId) -> DispatchResult { + CLUB.with(|club| club.borrow_mut().insert(*who, 0)); + Ok(()) + } + fn promote(who: &Self::AccountId) -> DispatchResult { + CLUB.with(|club| { + club.borrow_mut().entry(*who).and_modify(|r| *r += 1); + }); + Ok(()) + } + fn demote(who: &Self::AccountId) -> DispatchResult { + CLUB.with(|club| match club.borrow().get(who) { + None => Err(sp_runtime::DispatchError::Unavailable), + Some(&0) => { + club.borrow_mut().remove(&who); + Ok(()) + }, + Some(_) => { + club.borrow_mut().entry(*who).and_modify(|x| *x -= 1); + Ok(()) + }, + }) + } +} + +fn set_rank(who: u64, rank: u64) { + CLUB.with(|club| club.borrow_mut().insert(who, rank)); +} + +parameter_types! { + pub static Budget: u64 = 10; +} + +impl Config for Test { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type Paymaster = TestPay; + type Members = TestClub; + type Salary = ConvertRank; + type RegistrationPeriod = ConstU64<2>; + type PayoutPeriod = ConstU64<2>; + type Budget = Budget; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +fn next_block() { + System::set_block_number(System::block_number() + 1); +} + +#[allow(dead_code)] +fn run_to(n: u64) { + while System::block_number() < n { + next_block(); + } +} + +#[test] +fn basic_stuff() { + new_test_ext().execute_with(|| { + assert!(Salary::last_active(&0).is_err()); + assert_eq!(Salary::status(), None); + }); +} + +#[test] +fn can_start() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_eq!( + Salary::status(), + Some(StatusType { + cycle_index: 0, + cycle_start: 1, + budget: 10, + total_registrations: 0, + total_unregistered_paid: 0, + }) + ); + }); +} + +#[test] +fn bump_works() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + run_to(4); + assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); + + run_to(5); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_eq!( + Salary::status(), + Some(StatusType { + cycle_index: 1, + cycle_start: 5, + budget: 10, + total_registrations: 0, + total_unregistered_paid: 0 + }) + ); + + run_to(8); + assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); + + BUDGET.with(|b| b.replace(5)); + run_to(9); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_eq!( + Salary::status(), + Some(StatusType { + cycle_index: 2, + cycle_start: 9, + budget: 5, + total_registrations: 0, + total_unregistered_paid: 0 + }) + ); + }); +} + +#[test] +fn induct_works() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + + assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotMember); + set_rank(1, 1); + assert!(Salary::last_active(&1).is_err()); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + assert_eq!(Salary::last_active(&1).unwrap(), 0); + assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::AlreadyInducted); + }); +} + +#[test] +fn unregistered_payment_works() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NotInducted); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + // No claim on the cycle active during induction. + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::TooEarly); + run_to(3); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::TooEarly); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + run_to(8); + assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); + run_to(9); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(11); + assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10)); + assert_eq!(paid(1), 1); + assert_eq!(paid(10), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + }); +} + +#[test] +fn retry_payment_works() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + // Payment failed. + unpay(1, 1); + set_status(0, PaymentStatus::Failure); + + assert_eq!(paid(1), 0); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + + // Can't just retry. + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + // Check status. + assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1))); + // Allowed to try again. + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + run_to(8); + assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); + run_to(9); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(11); + assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10)); + assert_eq!(paid(1), 1); + assert_eq!(paid(10), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + }); +} + +#[test] +fn retry_registered_payment_works() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + // Payment failed. + unpay(1, 1); + set_status(0, PaymentStatus::Failure); + + assert_eq!(paid(1), 0); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); + + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + // Check status. + assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1))); + // Allowed to try again. + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); + }); +} + +#[test] +fn retry_payment_later_is_not_allowed() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + // Payment failed. + unpay(1, 1); + set_status(0, PaymentStatus::Failure); + + assert_eq!(paid(1), 0); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + + // Can't just retry. + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + + // Next cycle. + run_to(9); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + + // Payment did fail but now too late to retry. + assert_noop!(Salary::check_payment(RuntimeOrigin::signed(1)), Error::::NotCurrent); + + // We do get this cycle's payout, but we must wait for the payout period to start. + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::TooEarly); + + run_to(11); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + }); +} + +#[test] +fn retry_payment_later_without_bump_is_allowed() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + // Payment failed. + unpay(1, 1); + set_status(0, PaymentStatus::Failure); + + // Next cycle. + run_to(9); + + // Payment did fail but we can still retry as long as we don't `bump`. + assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + }); +} + +#[test] +fn retry_payment_to_other_works() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(6); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10)); + + // Payment failed. + unpay(10, 1); + set_status(0, PaymentStatus::Failure); + + // Can't just retry. + assert_noop!(Salary::payout_other(RuntimeOrigin::signed(1), 10), Error::::NoClaim); + // Check status. + assert_ok!(Salary::check_payment(RuntimeOrigin::signed(1))); + // Allowed to try again. + assert_ok!(Salary::payout_other(RuntimeOrigin::signed(1), 10)); + + assert_eq!(paid(10), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + + assert_noop!(Salary::payout_other(RuntimeOrigin::signed(1), 10), Error::::NoClaim); + run_to(8); + assert_noop!(Salary::bump(RuntimeOrigin::signed(1)), Error::::NotYet); + run_to(9); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + run_to(11); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_eq!(paid(1), 1); + assert_eq!(paid(10), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 1); + }); +} + +#[test] +fn registered_payment_works() { + new_test_ext().execute_with(|| { + set_rank(1, 1); + assert_noop!(Salary::induct(RuntimeOrigin::signed(1)), Error::::NotStarted); + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NotInducted); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + // No claim on the cycle active during induction. + assert_noop!(Salary::register(RuntimeOrigin::signed(1)), Error::::NoClaim); + run_to(3); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + + run_to(5); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(1))); + assert_eq!(Salary::status().unwrap().total_registrations, 1); + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_eq!(paid(1), 1); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::NoClaim); + + run_to(9); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_eq!(Salary::status().unwrap().total_registrations, 0); + assert_ok!(Salary::register(RuntimeOrigin::signed(1))); + assert_eq!(Salary::status().unwrap().total_registrations, 1); + run_to(11); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_eq!(paid(1), 2); + assert_eq!(Salary::status().unwrap().total_unregistered_paid, 0); + }); +} + +#[test] +fn zero_payment_fails() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + set_rank(1, 0); + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + run_to(7); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::ClaimZero); + }); +} + +#[test] +fn unregistered_bankrupcy_fails_gracefully() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + set_rank(1, 2); + set_rank(2, 6); + set_rank(3, 12); + + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(3))); + + run_to(7); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); + + assert_eq!(paid(1), 2); + assert_eq!(paid(2), 6); + assert_eq!(paid(3), 2); + }); +} + +#[test] +fn registered_bankrupcy_fails_gracefully() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + set_rank(1, 2); + set_rank(2, 6); + set_rank(3, 12); + + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(3))); + + run_to(5); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(2))); + assert_ok!(Salary::register(RuntimeOrigin::signed(3))); + + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); + + assert_eq!(paid(1), 1); + assert_eq!(paid(2), 3); + assert_eq!(paid(3), 6); + }); +} + +#[test] +fn mixed_bankrupcy_fails_gracefully() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + set_rank(1, 2); + set_rank(2, 6); + set_rank(3, 12); + + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(3))); + + run_to(5); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(2))); + + run_to(7); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(1))); + + assert_eq!(paid(1), 2); + assert_eq!(paid(2), 6); + assert_eq!(paid(3), 2); + }); +} + +#[test] +fn other_mixed_bankrupcy_fails_gracefully() { + new_test_ext().execute_with(|| { + assert_ok!(Salary::init(RuntimeOrigin::signed(1))); + set_rank(1, 2); + set_rank(2, 6); + set_rank(3, 12); + + assert_ok!(Salary::induct(RuntimeOrigin::signed(1))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(2))); + assert_ok!(Salary::induct(RuntimeOrigin::signed(3))); + + run_to(5); + assert_ok!(Salary::bump(RuntimeOrigin::signed(1))); + assert_ok!(Salary::register(RuntimeOrigin::signed(2))); + assert_ok!(Salary::register(RuntimeOrigin::signed(3))); + + run_to(7); + assert_noop!(Salary::payout(RuntimeOrigin::signed(1)), Error::::ClaimZero); + assert_ok!(Salary::payout(RuntimeOrigin::signed(2))); + assert_ok!(Salary::payout(RuntimeOrigin::signed(3))); + + assert_eq!(paid(1), 0); + assert_eq!(paid(2), 3); + assert_eq!(paid(3), 6); + }); +} diff --git a/frame/salary/src/weights.rs b/frame/salary/src/weights.rs new file mode 100644 index 000000000000..3d3b9e315959 --- /dev/null +++ b/frame/salary/src/weights.rs @@ -0,0 +1,265 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_salary +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_salary +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/salary/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for pallet_salary. +pub trait WeightInfo { + fn init() -> Weight; + fn bump() -> Weight; + fn induct() -> Weight; + fn register() -> Weight; + fn payout() -> Weight; + fn payout_other() -> Weight; + fn check_payment() -> Weight; +} + +/// Weights for pallet_salary using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + fn init() -> Weight { + // Proof Size summary in bytes: + // Measured: `4` + // Estimated: `1541` + // Minimum execution time: 10_778_000 picoseconds. + Weight::from_parts(11_084_000, 1541) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + fn bump() -> Weight { + // Proof Size summary in bytes: + // Measured: `86` + // Estimated: `1541` + // Minimum execution time: 12_042_000 picoseconds. + Weight::from_parts(12_645_000, 1541) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Salary Status (r:1 w:0) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) + fn induct() -> Weight { + // Proof Size summary in bytes: + // Measured: `395` + // Estimated: `3543` + // Minimum execution time: 18_374_000 picoseconds. + Weight::from_parts(19_200_000, 3543) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) + fn register() -> Weight { + // Proof Size summary in bytes: + // Measured: `462` + // Estimated: `3543` + // Minimum execution time: 22_696_000 picoseconds. + Weight::from_parts(23_275_000, 3543) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + fn payout() -> Weight { + // Proof Size summary in bytes: + // Measured: `462` + // Estimated: `3543` + // Minimum execution time: 63_660_000 picoseconds. + Weight::from_parts(65_006_000, 3543) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn payout_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `462` + // Estimated: `3593` + // Minimum execution time: 64_706_000 picoseconds. + Weight::from_parts(65_763_000, 3593) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) + fn check_payment() -> Weight { + // Proof Size summary in bytes: + // Measured: `170` + // Estimated: `3543` + // Minimum execution time: 11_838_000 picoseconds. + Weight::from_parts(12_323_000, 3543) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + fn init() -> Weight { + // Proof Size summary in bytes: + // Measured: `4` + // Estimated: `1541` + // Minimum execution time: 10_778_000 picoseconds. + Weight::from_parts(11_084_000, 1541) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + fn bump() -> Weight { + // Proof Size summary in bytes: + // Measured: `86` + // Estimated: `1541` + // Minimum execution time: 12_042_000 picoseconds. + Weight::from_parts(12_645_000, 1541) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Salary Status (r:1 w:0) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) + fn induct() -> Weight { + // Proof Size summary in bytes: + // Measured: `395` + // Estimated: `3543` + // Minimum execution time: 18_374_000 picoseconds. + Weight::from_parts(19_200_000, 3543) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) + fn register() -> Weight { + // Proof Size summary in bytes: + // Measured: `462` + // Estimated: `3543` + // Minimum execution time: 22_696_000 picoseconds. + Weight::from_parts(23_275_000, 3543) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + fn payout() -> Weight { + // Proof Size summary in bytes: + // Measured: `462` + // Estimated: `3543` + // Minimum execution time: 63_660_000 picoseconds. + Weight::from_parts(65_006_000, 3543) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) + /// Storage: RankedCollective Members (r:1 w:0) + /// Proof: RankedCollective Members (max_values: None, max_size: Some(42), added: 2517, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn payout_other() -> Weight { + // Proof Size summary in bytes: + // Measured: `462` + // Estimated: `3593` + // Minimum execution time: 64_706_000 picoseconds. + Weight::from_parts(65_763_000, 3593) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Salary Status (r:1 w:1) + /// Proof: Salary Status (max_values: Some(1), max_size: Some(56), added: 551, mode: MaxEncodedLen) + /// Storage: Salary Claimant (r:1 w:1) + /// Proof: Salary Claimant (max_values: None, max_size: Some(78), added: 2553, mode: MaxEncodedLen) + fn check_payment() -> Weight { + // Proof Size summary in bytes: + // Measured: `170` + // Estimated: `3543` + // Minimum execution time: 11_838_000 picoseconds. + Weight::from_parts(12_323_000, 3543) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } +} diff --git a/frame/scheduler/Cargo.toml b/frame/scheduler/Cargo.toml index 9a67e68a0164..67506fbfee3b 100644 --- a/frame/scheduler/Cargo.toml +++ b/frame/scheduler/Cargo.toml @@ -10,20 +10,20 @@ description = "FRAME Scheduler pallet" readme = "README.md" [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } -sp-weights = { version = "4.0.0", default-features = false, path = "../../primitives/weights" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-weights = { version = "20.0.0", default-features = false, path = "../../primitives/weights" } [dev-dependencies] pallet-preimage = { version = "4.0.0-dev", path = "../preimage" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } [features] diff --git a/frame/scheduler/README.md b/frame/scheduler/README.md index 9a209031d740..bdd2c2226c88 100644 --- a/frame/scheduler/README.md +++ b/frame/scheduler/README.md @@ -31,4 +31,4 @@ then those filter will not be used when dispatching the schedule call. `Vec` parameter that can be used for identification. * `cancel_named` - the named complement to the cancel function. -License: Unlicense +License: Apache 2.0 diff --git a/frame/scheduler/src/benchmarking.rs b/frame/scheduler/src/benchmarking.rs index ca98cabc3c08..b41cea449654 100644 --- a/frame/scheduler/src/benchmarking.rs +++ b/frame/scheduler/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,12 +18,12 @@ //! Scheduler pallet benchmarking. use super::*; -use frame_benchmarking::{account, benchmarks}; +use frame_benchmarking::v1::{account, benchmarks, BenchmarkError}; use frame_support::{ ensure, traits::{schedule::Priority, BoundedInline}, }; -use frame_system::RawOrigin; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use sp_std::{prelude::*, vec}; use crate::Pallet as Scheduler; @@ -42,7 +42,10 @@ type SystemOrigin = ::RuntimeOrigin; /// - `None`: aborted (hash without preimage) /// - `Some(true)`: hash resolves into call if possible, plain call otherwise /// - `Some(false)`: plain call -fn fill_schedule(when: T::BlockNumber, n: u32) -> Result<(), &'static str> { +fn fill_schedule( + when: frame_system::pallet_prelude::BlockNumberFor, + n: u32, +) -> Result<(), &'static str> { let t = DispatchTime::At(when); let origin: ::PalletsOrigin = frame_system::RawOrigin::Root.into(); for i in 0..n { @@ -125,7 +128,7 @@ fn make_origin(signed: bool) -> ::PalletsOrigin { benchmarks! { // `service_agendas` when no work is done. service_agendas_base { - let now = T::BlockNumber::from(BLOCK_NUMBER); + let now = BlockNumberFor::::from(BLOCK_NUMBER); IncompleteSince::::put(now - One::one()); }: { Scheduler::::service_agendas(&mut WeightMeter::max_limit(), now, 0); @@ -160,6 +163,10 @@ benchmarks! { // `service_task` when the task is a non-periodic, non-named, fetched call (with a known // preimage length) and which is not dispatched (e.g. due to being overweight). + #[pov_mode = MaxEncodedLen { + // Use measured PoV size for the Preimages since we pass in a length witness. + Preimage::PreimageFor: Measured + }] service_task_fetched { let s in (BoundedInline::bound() as u32) .. (T::Preimages::MAX_LENGTH as u32); let now = BLOCK_NUMBER.into(); @@ -220,7 +227,7 @@ benchmarks! { schedule { let s in 0 .. (T::MaxScheduledPerBlock::get() - 1); let when = BLOCK_NUMBER.into(); - let periodic = Some((T::BlockNumber::one(), 100)); + let periodic = Some((BlockNumberFor::::one(), 100)); let priority = 0; // Essentially a no-op call. let call = Box::new(SystemCall::set_storage { items: vec![] }.into()); @@ -240,7 +247,8 @@ benchmarks! { fill_schedule::(when, s)?; assert_eq!(Agenda::::get(when).len(), s as usize); - let schedule_origin = T::ScheduleOrigin::successful_origin(); + let schedule_origin = + T::ScheduleOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: _>(schedule_origin, when, 0) verify { ensure!( @@ -262,7 +270,7 @@ benchmarks! { let s in 0 .. (T::MaxScheduledPerBlock::get() - 1); let id = u32_to_name(s); let when = BLOCK_NUMBER.into(); - let periodic = Some((T::BlockNumber::one(), 100)); + let periodic = Some((BlockNumberFor::::one(), 100)); let priority = 0; // Essentially a no-op call. let call = Box::new(SystemCall::set_storage { items: vec![] }.into()); diff --git a/frame/scheduler/src/lib.rs b/frame/scheduler/src/lib.rs index afc4fd66e76c..77adb1814616 100644 --- a/frame/scheduler/src/lib.rs +++ b/frame/scheduler/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -72,7 +72,10 @@ use frame_support::{ }, weights::{Weight, WeightMeter}, }; -use frame_system::{self as system}; +use frame_system::{ + pallet_prelude::BlockNumberFor, + {self as system}, +}; use scale_info::TypeInfo; use sp_io::hashing::blake2_256; use sp_runtime::{ @@ -123,7 +126,7 @@ use crate::{Scheduled as ScheduledV3, Scheduled as ScheduledV2}; pub type ScheduledV2Of = ScheduledV2< Vec, ::RuntimeCall, - ::BlockNumber, + BlockNumberFor, ::PalletsOrigin, ::AccountId, >; @@ -131,7 +134,7 @@ pub type ScheduledV2Of = ScheduledV2< pub type ScheduledV3Of = ScheduledV3< Vec, CallOrHashOf, - ::BlockNumber, + BlockNumberFor, ::PalletsOrigin, ::AccountId, >; @@ -139,7 +142,7 @@ pub type ScheduledV3Of = ScheduledV3< pub type ScheduledOf = Scheduled< TaskName, Bounded<::RuntimeCall>, - ::BlockNumber, + BlockNumberFor, ::PalletsOrigin, ::AccountId, >; @@ -172,7 +175,6 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); @@ -217,6 +219,10 @@ pub mod pallet { type OriginPrivilegeCmp: PrivilegeCmp; /// The maximum number of scheduled calls in the queue for a single block. + /// + /// NOTE: + /// + Dependent pallets' benchmarks might require a higher limit for the setting. Set a + /// higher limit under `runtime-benchmarks` feature. #[pallet::constant] type MaxScheduledPerBlock: Get; @@ -228,14 +234,14 @@ pub mod pallet { } #[pallet::storage] - pub type IncompleteSince = StorageValue<_, T::BlockNumber>; + pub type IncompleteSince = StorageValue<_, BlockNumberFor>; /// Items to be executed, indexed by the block number that they should be executed on. #[pallet::storage] pub type Agenda = StorageMap< _, Twox64Concat, - T::BlockNumber, + BlockNumberFor, BoundedVec>, T::MaxScheduledPerBlock>, ValueQuery, >; @@ -246,28 +252,28 @@ pub mod pallet { /// identities. #[pallet::storage] pub(crate) type Lookup = - StorageMap<_, Twox64Concat, TaskName, TaskAddress>; + StorageMap<_, Twox64Concat, TaskName, TaskAddress>>; /// Events type. #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// Scheduled some task. - Scheduled { when: T::BlockNumber, index: u32 }, + Scheduled { when: BlockNumberFor, index: u32 }, /// Canceled some task. - Canceled { when: T::BlockNumber, index: u32 }, + Canceled { when: BlockNumberFor, index: u32 }, /// Dispatched some task. Dispatched { - task: TaskAddress, + task: TaskAddress>, id: Option, result: DispatchResult, }, /// The call for the provided hash was not found so the task has been aborted. - CallUnavailable { task: TaskAddress, id: Option }, + CallUnavailable { task: TaskAddress>, id: Option }, /// The given task was unable to be renewed since the agenda is full at that block. - PeriodicFailed { task: TaskAddress, id: Option }, + PeriodicFailed { task: TaskAddress>, id: Option }, /// The given task can never be executed since it is overweight. - PermanentlyOverweight { task: TaskAddress, id: Option }, + PermanentlyOverweight { task: TaskAddress>, id: Option }, } #[pallet::error] @@ -287,10 +293,10 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { /// Execute the scheduled calls - fn on_initialize(now: T::BlockNumber) -> Weight { + fn on_initialize(now: BlockNumberFor) -> Weight { let mut weight_counter = WeightMeter::from_limit(T::MaximumWeight::get()); Self::service_agendas(&mut weight_counter, now, u32::max_value()); - weight_counter.consumed + weight_counter.consumed() } } @@ -301,8 +307,8 @@ pub mod pallet { #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] pub fn schedule( origin: OriginFor, - when: T::BlockNumber, - maybe_periodic: Option>, + when: BlockNumberFor, + maybe_periodic: Option>>, priority: schedule::Priority, call: Box<::RuntimeCall>, ) -> DispatchResult { @@ -321,7 +327,7 @@ pub mod pallet { /// Cancel an anonymously scheduled task. #[pallet::call_index(1)] #[pallet::weight(::WeightInfo::cancel(T::MaxScheduledPerBlock::get()))] - pub fn cancel(origin: OriginFor, when: T::BlockNumber, index: u32) -> DispatchResult { + pub fn cancel(origin: OriginFor, when: BlockNumberFor, index: u32) -> DispatchResult { T::ScheduleOrigin::ensure_origin(origin.clone())?; let origin = ::RuntimeOrigin::from(origin); Self::do_cancel(Some(origin.caller().clone()), (when, index))?; @@ -334,8 +340,8 @@ pub mod pallet { pub fn schedule_named( origin: OriginFor, id: TaskName, - when: T::BlockNumber, - maybe_periodic: Option>, + when: BlockNumberFor, + maybe_periodic: Option>>, priority: schedule::Priority, call: Box<::RuntimeCall>, ) -> DispatchResult { @@ -363,16 +369,12 @@ pub mod pallet { } /// Anonymously schedule a task after a delay. - /// - /// # - /// Same as [`schedule`]. - /// # #[pallet::call_index(4)] #[pallet::weight(::WeightInfo::schedule(T::MaxScheduledPerBlock::get()))] pub fn schedule_after( origin: OriginFor, - after: T::BlockNumber, - maybe_periodic: Option>, + after: BlockNumberFor, + maybe_periodic: Option>>, priority: schedule::Priority, call: Box<::RuntimeCall>, ) -> DispatchResult { @@ -389,17 +391,13 @@ pub mod pallet { } /// Schedule a named task after a delay. - /// - /// # - /// Same as [`schedule_named`](Self::schedule_named). - /// # #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::schedule_named(T::MaxScheduledPerBlock::get()))] pub fn schedule_named_after( origin: OriginFor, id: TaskName, - after: T::BlockNumber, - maybe_periodic: Option>, + after: BlockNumberFor, + maybe_periodic: Option>>, priority: schedule::Priority, call: Box<::RuntimeCall>, ) -> DispatchResult { @@ -439,7 +437,7 @@ impl> Pallet { } Agenda::::translate::< - Vec::RuntimeCall, T::BlockNumber>>>, + Vec::RuntimeCall, BlockNumberFor>>>, _, >(|_, agenda| { Some(BoundedVec::truncate_from( @@ -674,7 +672,7 @@ impl Pallet { Scheduled< TaskName, Bounded<::RuntimeCall>, - T::BlockNumber, + BlockNumberFor, OldOrigin, T::AccountId, >, @@ -700,7 +698,9 @@ impl Pallet { }); } - fn resolve_time(when: DispatchTime) -> Result { + fn resolve_time( + when: DispatchTime>, + ) -> Result, DispatchError> { let now = frame_system::Pallet::::block_number(); let when = match when { @@ -718,9 +718,9 @@ impl Pallet { } fn place_task( - when: T::BlockNumber, + when: BlockNumberFor, what: ScheduledOf, - ) -> Result, (DispatchError, ScheduledOf)> { + ) -> Result>, (DispatchError, ScheduledOf)> { let maybe_name = what.maybe_id; let index = Self::push_to_agenda(when, what)?; let address = (when, index); @@ -732,7 +732,7 @@ impl Pallet { } fn push_to_agenda( - when: T::BlockNumber, + when: BlockNumberFor, what: ScheduledOf, ) -> Result)> { let mut agenda = Agenda::::get(when); @@ -754,7 +754,7 @@ impl Pallet { /// Remove trailing `None` items of an agenda at `when`. If all items are `None` remove the /// agenda record entirely. - fn cleanup_agenda(when: T::BlockNumber) { + fn cleanup_agenda(when: BlockNumberFor) { let mut agenda = Agenda::::get(when); match agenda.iter().rposition(|i| i.is_some()) { Some(i) if agenda.len() > i + 1 => { @@ -769,14 +769,16 @@ impl Pallet { } fn do_schedule( - when: DispatchTime, - maybe_periodic: Option>, + when: DispatchTime>, + maybe_periodic: Option>>, priority: schedule::Priority, origin: T::PalletsOrigin, call: Bounded<::RuntimeCall>, - ) -> Result, DispatchError> { + ) -> Result>, DispatchError> { let when = Self::resolve_time(when)?; + let lookup_hash = call.lookup_hash(); + // sanitize maybe_periodic let maybe_periodic = maybe_periodic .filter(|p| p.1 > 1 && !p.0.is_zero()) @@ -790,12 +792,19 @@ impl Pallet { origin, _phantom: PhantomData, }; - Self::place_task(when, task).map_err(|x| x.0) + let res = Self::place_task(when, task).map_err(|x| x.0)?; + + if let Some(hash) = lookup_hash { + // Request the call to be made available. + T::Preimages::request(&hash); + } + + Ok(res) } fn do_cancel( origin: Option, - (when, index): TaskAddress, + (when, index): TaskAddress>, ) -> Result<(), DispatchError> { let scheduled = Agenda::::try_mutate(when, |agenda| { agenda.get_mut(index as usize).map_or( @@ -827,9 +836,9 @@ impl Pallet { } fn do_reschedule( - (when, index): TaskAddress, - new_time: DispatchTime, - ) -> Result, DispatchError> { + (when, index): TaskAddress>, + new_time: DispatchTime>, + ) -> Result>, DispatchError> { let new_time = Self::resolve_time(new_time)?; if new_time == when { @@ -849,12 +858,12 @@ impl Pallet { fn do_schedule_named( id: TaskName, - when: DispatchTime, - maybe_periodic: Option>, + when: DispatchTime>, + maybe_periodic: Option>>, priority: schedule::Priority, origin: T::PalletsOrigin, call: Bounded<::RuntimeCall>, - ) -> Result, DispatchError> { + ) -> Result>, DispatchError> { // ensure id it is unique if Lookup::::contains_key(&id) { return Err(Error::::FailedToSchedule.into()) @@ -862,6 +871,8 @@ impl Pallet { let when = Self::resolve_time(when)?; + let lookup_hash = call.lookup_hash(); + // sanitize maybe_periodic let maybe_periodic = maybe_periodic .filter(|p| p.1 > 1 && !p.0.is_zero()) @@ -876,7 +887,14 @@ impl Pallet { origin, _phantom: Default::default(), }; - Self::place_task(when, task).map_err(|x| x.0) + let res = Self::place_task(when, task).map_err(|x| x.0)?; + + if let Some(hash) = lookup_hash { + // Request the call to be made available. + T::Preimages::request(&hash); + } + + Ok(res) } fn do_cancel_named(origin: Option, id: TaskName) -> DispatchResult { @@ -909,8 +927,8 @@ impl Pallet { fn do_reschedule_named( id: TaskName, - new_time: DispatchTime, - ) -> Result, DispatchError> { + new_time: DispatchTime>, + ) -> Result>, DispatchError> { let new_time = Self::resolve_time(new_time)?; let lookup = Lookup::::get(id); @@ -940,8 +958,8 @@ use ServiceTaskError::*; impl Pallet { /// Service up to `max` agendas queue starting from earliest incompletely executed agenda. - fn service_agendas(weight: &mut WeightMeter, now: T::BlockNumber, max: u32) { - if !weight.check_accrue(T::WeightInfo::service_agendas_base()) { + fn service_agendas(weight: &mut WeightMeter, now: BlockNumberFor, max: u32) { + if weight.try_consume(T::WeightInfo::service_agendas_base()).is_err() { return } @@ -952,7 +970,7 @@ impl Pallet { let max_items = T::MaxScheduledPerBlock::get(); let mut count_down = max; let service_agenda_base_weight = T::WeightInfo::service_agenda_base(max_items); - while count_down > 0 && when <= now && weight.can_accrue(service_agenda_base_weight) { + while count_down > 0 && when <= now && weight.can_consume(service_agenda_base_weight) { if !Self::service_agenda(weight, &mut executed, now, when, u32::max_value()) { incomplete_since = incomplete_since.min(when); } @@ -970,8 +988,8 @@ impl Pallet { fn service_agenda( weight: &mut WeightMeter, executed: &mut u32, - now: T::BlockNumber, - when: T::BlockNumber, + now: BlockNumberFor, + when: BlockNumberFor, max: u32, ) -> bool { let mut agenda = Agenda::::get(when); @@ -983,8 +1001,9 @@ impl Pallet { }) .collect::>(); ordered.sort_by_key(|k| k.1); - let within_limit = - weight.check_accrue(T::WeightInfo::service_agenda_base(ordered.len() as u32)); + let within_limit = weight + .try_consume(T::WeightInfo::service_agenda_base(ordered.len() as u32)) + .is_ok(); debug_assert!(within_limit, "weight limit should have been checked in advance"); // Items which we know can be executed and have postponed for execution in a later block. @@ -1002,7 +1021,7 @@ impl Pallet { task.maybe_id.is_some(), task.maybe_periodic.is_some(), ); - if !weight.can_accrue(base_weight) { + if !weight.can_consume(base_weight) { postponed += 1; break } @@ -1027,6 +1046,7 @@ impl Pallet { } else { Agenda::::remove(when); } + postponed == 0 } @@ -1038,8 +1058,8 @@ impl Pallet { /// - Rescheduling the task for execution in a later agenda if periodic. fn service_task( weight: &mut WeightMeter, - now: T::BlockNumber, - when: T::BlockNumber, + now: BlockNumberFor, + when: BlockNumberFor, agenda_index: u32, is_first: bool, mut task: ScheduledOf, @@ -1053,7 +1073,7 @@ impl Pallet { Err(_) => return Err((Unavailable, Some(task))), }; - weight.check_accrue(T::WeightInfo::service_task( + let _ = weight.try_consume(T::WeightInfo::service_task( lookup_len.map(|x| x as usize), task.maybe_id.is_some(), task.maybe_periodic.is_some(), @@ -1129,7 +1149,7 @@ impl Pallet { // We only allow a scheduled call if it cannot push the weight past the limit. let max_weight = base_weight.saturating_add(call_weight); - if !weight.can_accrue(max_weight) { + if !weight.can_consume(max_weight) { return Err(Overweight) } @@ -1140,21 +1160,21 @@ impl Pallet { (error_and_info.post_info.actual_weight, Err(error_and_info.error)), }; let call_weight = maybe_actual_call_weight.unwrap_or(call_weight); - weight.check_accrue(base_weight); - weight.check_accrue(call_weight); + let _ = weight.try_consume(base_weight); + let _ = weight.try_consume(call_weight); Ok(result) } } impl> - schedule::v2::Anon::RuntimeCall, T::PalletsOrigin> for Pallet + schedule::v2::Anon, ::RuntimeCall, T::PalletsOrigin> for Pallet { - type Address = TaskAddress; + type Address = TaskAddress>; type Hash = T::Hash; fn schedule( - when: DispatchTime, - maybe_periodic: Option>, + when: DispatchTime>, + maybe_periodic: Option>>, priority: schedule::Priority, origin: T::PalletsOrigin, call: CallOrHashOf, @@ -1170,26 +1190,26 @@ impl> fn reschedule( address: Self::Address, - when: DispatchTime, + when: DispatchTime>, ) -> Result { Self::do_reschedule(address, when) } - fn next_dispatch_time((when, index): Self::Address) -> Result { + fn next_dispatch_time((when, index): Self::Address) -> Result, ()> { Agenda::::get(when).get(index as usize).ok_or(()).map(|_| when) } } impl> - schedule::v2::Named::RuntimeCall, T::PalletsOrigin> for Pallet + schedule::v2::Named, ::RuntimeCall, T::PalletsOrigin> for Pallet { - type Address = TaskAddress; + type Address = TaskAddress>; type Hash = T::Hash; fn schedule_named( id: Vec, - when: DispatchTime, - maybe_periodic: Option>, + when: DispatchTime>, + maybe_periodic: Option>>, priority: schedule::Priority, origin: T::PalletsOrigin, call: CallOrHashOf, @@ -1207,13 +1227,13 @@ impl> fn reschedule_named( id: Vec, - when: DispatchTime, + when: DispatchTime>, ) -> Result { let name = blake2_256(&id[..]); Self::do_reschedule_named(name, when) } - fn next_dispatch_time(id: Vec) -> Result { + fn next_dispatch_time(id: Vec) -> Result, ()> { let name = blake2_256(&id[..]); Lookup::::get(name) .and_then(|(when, index)| Agenda::::get(when).get(index as usize).map(|_| when)) @@ -1221,14 +1241,14 @@ impl> } } -impl schedule::v3::Anon::RuntimeCall, T::PalletsOrigin> +impl schedule::v3::Anon, ::RuntimeCall, T::PalletsOrigin> for Pallet { - type Address = TaskAddress; + type Address = TaskAddress>; fn schedule( - when: DispatchTime, - maybe_periodic: Option>, + when: DispatchTime>, + maybe_periodic: Option>>, priority: schedule::Priority, origin: T::PalletsOrigin, call: Bounded<::RuntimeCall>, @@ -1242,12 +1262,14 @@ impl schedule::v3::Anon::RuntimeCall, T fn reschedule( address: Self::Address, - when: DispatchTime, + when: DispatchTime>, ) -> Result { Self::do_reschedule(address, when).map_err(map_err_to_v3_err::) } - fn next_dispatch_time((when, index): Self::Address) -> Result { + fn next_dispatch_time( + (when, index): Self::Address, + ) -> Result, DispatchError> { Agenda::::get(when) .get(index as usize) .ok_or(DispatchError::Unavailable) @@ -1257,15 +1279,15 @@ impl schedule::v3::Anon::RuntimeCall, T use schedule::v3::TaskName; -impl schedule::v3::Named::RuntimeCall, T::PalletsOrigin> +impl schedule::v3::Named, ::RuntimeCall, T::PalletsOrigin> for Pallet { - type Address = TaskAddress; + type Address = TaskAddress>; fn schedule_named( id: TaskName, - when: DispatchTime, - maybe_periodic: Option>, + when: DispatchTime>, + maybe_periodic: Option>>, priority: schedule::Priority, origin: T::PalletsOrigin, call: Bounded<::RuntimeCall>, @@ -1279,12 +1301,12 @@ impl schedule::v3::Named::RuntimeCall, fn reschedule_named( id: TaskName, - when: DispatchTime, + when: DispatchTime>, ) -> Result { Self::do_reschedule_named(id, when).map_err(map_err_to_v3_err::) } - fn next_dispatch_time(id: TaskName) -> Result { + fn next_dispatch_time(id: TaskName) -> Result, DispatchError> { Lookup::::get(id) .and_then(|(when, index)| Agenda::::get(when).get(index as usize).map(|_| when)) .ok_or(DispatchError::Unavailable) diff --git a/frame/scheduler/src/migration.rs b/frame/scheduler/src/migration.rs index f71b9457f516..06259768f0aa 100644 --- a/frame/scheduler/src/migration.rs +++ b/frame/scheduler/src/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +19,10 @@ use super::*; use frame_support::traits::OnRuntimeUpgrade; +use frame_system::pallet_prelude::BlockNumberFor; + +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; /// The log target. const TARGET: &'static str = "runtime::scheduler::migration"; @@ -31,22 +35,14 @@ pub mod v1 { pub(crate) type Agenda = StorageMap< Pallet, Twox64Concat, - ::BlockNumber, - Vec< - Option< - ScheduledV1<::RuntimeCall, ::BlockNumber>, - >, - >, + BlockNumberFor, + Vec::RuntimeCall, BlockNumberFor>>>, ValueQuery, >; #[frame_support::storage_alias] - pub(crate) type Lookup = StorageMap< - Pallet, - Twox64Concat, - Vec, - TaskAddress<::BlockNumber>, - >; + pub(crate) type Lookup = + StorageMap, Twox64Concat, Vec, TaskAddress>>; } pub mod v2 { @@ -57,18 +53,14 @@ pub mod v2 { pub(crate) type Agenda = StorageMap< Pallet, Twox64Concat, - ::BlockNumber, + BlockNumberFor, Vec>>, ValueQuery, >; #[frame_support::storage_alias] - pub(crate) type Lookup = StorageMap< - Pallet, - Twox64Concat, - Vec, - TaskAddress<::BlockNumber>, - >; + pub(crate) type Lookup = + StorageMap, Twox64Concat, Vec, TaskAddress>>; } pub mod v3 { @@ -79,26 +71,22 @@ pub mod v3 { pub(crate) type Agenda = StorageMap< Pallet, Twox64Concat, - ::BlockNumber, + BlockNumberFor, Vec>>, ValueQuery, >; #[frame_support::storage_alias] - pub(crate) type Lookup = StorageMap< - Pallet, - Twox64Concat, - Vec, - TaskAddress<::BlockNumber>, - >; + pub(crate) type Lookup = + StorageMap, Twox64Concat, Vec, TaskAddress>>; /// Migrate the scheduler pallet from V3 to V4. pub struct MigrateToV4(sp_std::marker::PhantomData); impl> OnRuntimeUpgrade for MigrateToV4 { #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - assert_eq!(StorageVersion::get::>(), 3, "Can only upgrade from version 3"); + fn pre_upgrade() -> Result, TryRuntimeError> { + ensure!(StorageVersion::get::>() == 3, "Can only upgrade from version 3"); let agendas = Agenda::::iter_keys().count() as u32; let decodable_agendas = Agenda::::iter_values().count() as u32; @@ -125,7 +113,7 @@ pub mod v3 { agenda.len(), max_scheduled_per_block, ); - return Err("Agenda would overflow `MaxScheduledPerBlock`.") + return Err("Agenda would overflow `MaxScheduledPerBlock`.".into()) } } // Check that bounding the calls will not overflow `MAX_LENGTH`. @@ -142,7 +130,7 @@ pub mod v3 { block_number, l, ); - return Err("Call is too large.") + return Err("Call is too large.".into()) } }, _ => (), @@ -169,12 +157,12 @@ pub mod v3 { } #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), &'static str> { - assert_eq!(StorageVersion::get::>(), 4, "Must upgrade"); + fn post_upgrade(state: Vec) -> Result<(), TryRuntimeError> { + ensure!(StorageVersion::get::>() == 4, "Must upgrade"); // Check that everything decoded fine. for k in crate::Agenda::::iter_keys() { - assert!(crate::Agenda::::try_get(k).is_ok(), "Cannot decode V4 Agenda"); + ensure!(crate::Agenda::::try_get(k).is_ok(), "Cannot decode V4 Agenda"); } let old_agendas: u32 = @@ -210,7 +198,7 @@ pub mod v4 { impl OnRuntimeUpgrade for CleanupAgendas { #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { + fn pre_upgrade() -> Result, TryRuntimeError> { assert_eq!( StorageVersion::get::>(), 4, @@ -285,8 +273,8 @@ pub mod v4 { } #[cfg(feature = "try-runtime")] - fn post_upgrade(state: Vec) -> Result<(), &'static str> { - assert_eq!(StorageVersion::get::>(), 4, "Version must not change"); + fn post_upgrade(state: Vec) -> Result<(), TryRuntimeError> { + ensure!(StorageVersion::get::>() == 4, "Version must not change"); let (old_agendas, non_empty_agendas): (u32, u32) = Decode::decode(&mut state.as_ref()).expect("Must decode pre_upgrade state"); @@ -297,14 +285,15 @@ pub mod v4 { target: TARGET, "Did not clean up any agendas. v4::CleanupAgendas can be removed." ), - Some(n) => - log::info!(target: TARGET, "Cleaned up {} agendas, now {}", n, new_agendas), + Some(n) => { + log::info!(target: TARGET, "Cleaned up {} agendas, now {}", n, new_agendas) + }, None => unreachable!( "Number of agendas cannot increase, old {} new {}", old_agendas, new_agendas ), } - assert_eq!(new_agendas, non_empty_agendas, "Expected to keep all non-empty agendas"); + ensure!(new_agendas == non_empty_agendas, "Expected to keep all non-empty agendas"); Ok(()) } @@ -495,7 +484,7 @@ mod test { // The pre_upgrade hook fails: let err = v3::MigrateToV4::::pre_upgrade().unwrap_err(); - assert!(err.contains("Call is too large")); + assert_eq!(DispatchError::from("Call is too large."), err); // But the migration itself works: let _w = v3::MigrateToV4::::on_runtime_upgrade(); @@ -560,8 +549,9 @@ mod test { "Agenda {} should be removed", i ), - Some(new) => - assert_eq!(Agenda::::get(i as u64), new, "Agenda wrong {}", i), + Some(new) => { + assert_eq!(Agenda::::get(i as u64), new, "Agenda wrong {}", i) + }, } } }); diff --git a/frame/scheduler/src/mock.rs b/frame/scheduler/src/mock.rs index 0aaac56667dc..28e334958d92 100644 --- a/frame/scheduler/src/mock.rs +++ b/frame/scheduler/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,9 +30,8 @@ use frame_support::{ use frame_system::{EnsureRoot, EnsureSignedBy}; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, - Perbill, + BuildStorage, Perbill, }; // Logger module to track execution. @@ -50,8 +49,7 @@ pub mod logger { } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(PhantomData); + pub struct Pallet(_); #[pallet::hooks] impl Hooks> for Pallet {} @@ -94,16 +92,12 @@ pub mod logger { } } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Logger: logger::{Pallet, Call, Event}, Scheduler: scheduler::{Pallet, Call, Storage, Event}, Preimage: pallet_preimage::{Pallet, Call, Storage, Event}, @@ -121,7 +115,7 @@ impl Contains for BaseFilter { parameter_types! { pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max( - Weight::from_ref_time(2_000_000_000_000).set_proof_size(u64::MAX), + Weight::from_parts(2_000_000_000_000, u64::MAX), ); } impl system::Config for Test { @@ -131,13 +125,12 @@ impl system::Config for Test { type DbWeight = RocksDbWeight; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -169,40 +162,40 @@ impl pallet_preimage::Config for Test { pub struct TestWeightInfo; impl WeightInfo for TestWeightInfo { fn service_agendas_base() -> Weight { - Weight::from_ref_time(0b0000_0001) + Weight::from_parts(0b0000_0001, 0) } fn service_agenda_base(i: u32) -> Weight { - Weight::from_ref_time((i << 8) as u64 + 0b0000_0010) + Weight::from_parts((i << 8) as u64 + 0b0000_0010, 0) } fn service_task_base() -> Weight { - Weight::from_ref_time(0b0000_0100) + Weight::from_parts(0b0000_0100, 0) } fn service_task_periodic() -> Weight { - Weight::from_ref_time(0b0000_1100) + Weight::from_parts(0b0000_1100, 0) } fn service_task_named() -> Weight { - Weight::from_ref_time(0b0001_0100) + Weight::from_parts(0b0001_0100, 0) } fn service_task_fetched(s: u32) -> Weight { - Weight::from_ref_time((s << 8) as u64 + 0b0010_0100) + Weight::from_parts((s << 8) as u64 + 0b0010_0100, 0) } fn execute_dispatch_signed() -> Weight { - Weight::from_ref_time(0b0100_0000) + Weight::from_parts(0b0100_0000, 0) } fn execute_dispatch_unsigned() -> Weight { - Weight::from_ref_time(0b1000_0000) + Weight::from_parts(0b1000_0000, 0) } fn schedule(_s: u32) -> Weight { - Weight::from_ref_time(50) + Weight::from_parts(50, 0) } fn cancel(_s: u32) -> Weight { - Weight::from_ref_time(50) + Weight::from_parts(50, 0) } fn schedule_named(_s: u32) -> Weight { - Weight::from_ref_time(50) + Weight::from_parts(50, 0) } fn cancel_named(_s: u32) -> Weight { - Weight::from_ref_time(50) + Weight::from_parts(50, 0) } } parameter_types! { @@ -226,7 +219,7 @@ impl Config for Test { pub type LoggerCall = logger::Call; pub fn new_test_ext() -> sp_io::TestExternalities { - let t = system::GenesisConfig::default().build_storage::().unwrap(); + let t = system::GenesisConfig::::default().build_storage().unwrap(); t.into() } diff --git a/frame/scheduler/src/tests.rs b/frame/scheduler/src/tests.rs index e5467fc8db55..a0cac897d43d 100644 --- a/frame/scheduler/src/tests.rs +++ b/frame/scheduler/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,7 +33,7 @@ use substrate_test_utils::assert_eq_uvec; fn basic_scheduling_works() { new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); assert!(!::BaseCallFilter::contains(&call)); assert_ok!(Scheduler::do_schedule( DispatchTime::At(4), @@ -55,12 +55,13 @@ fn basic_scheduling_works() { fn scheduling_with_preimages_works() { new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); let hash = ::Hashing::hash_of(&call); let len = call.using_encoded(|x| x.len()) as u32; - let hashed = Preimage::pick(hash, len); - assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(0), call.encode())); + // Important to use here `Bounded::Lookup` to ensure that we request the hash. + let hashed = Bounded::Lookup { hash, len }; assert_ok!(Scheduler::do_schedule(DispatchTime::At(4), None, 127, root(), hashed)); + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(0), call.encode())); assert!(Preimage::is_requested(&hash)); run_to_block(3); assert!(logger::log().is_empty()); @@ -78,7 +79,7 @@ fn schedule_after_works() { new_test_ext().execute_with(|| { run_to_block(2); let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); assert!(!::BaseCallFilter::contains(&call)); // This will schedule the call 3 blocks after the next block... so block 3 + 3 = 6 assert_ok!(Scheduler::do_schedule( @@ -102,7 +103,7 @@ fn schedule_after_zero_works() { new_test_ext().execute_with(|| { run_to_block(2); let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); assert!(!::BaseCallFilter::contains(&call)); assert_ok!(Scheduler::do_schedule( DispatchTime::After(0), @@ -130,7 +131,7 @@ fn periodic_scheduling_works() { root(), Preimage::bound(RuntimeCall::Logger(logger::Call::log { i: 42, - weight: Weight::from_ref_time(10) + weight: Weight::from_parts(10, 0) })) .unwrap() )); @@ -155,7 +156,7 @@ fn periodic_scheduling_works() { fn reschedule_works() { new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); assert!(!::BaseCallFilter::contains(&call)); assert_eq!( Scheduler::do_schedule( @@ -194,7 +195,7 @@ fn reschedule_works() { fn reschedule_named_works() { new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); assert!(!::BaseCallFilter::contains(&call)); assert_eq!( Scheduler::do_schedule_named( @@ -234,7 +235,7 @@ fn reschedule_named_works() { fn reschedule_named_perodic_works() { new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); assert!(!::BaseCallFilter::contains(&call)); assert_eq!( Scheduler::do_schedule_named( @@ -292,7 +293,7 @@ fn cancel_named_scheduling_works_with_normal_cancel() { root(), Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 69, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })) .unwrap(), ) @@ -304,7 +305,7 @@ fn cancel_named_scheduling_works_with_normal_cancel() { root(), Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 42, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })) .unwrap(), ) @@ -330,7 +331,7 @@ fn cancel_named_periodic_scheduling_works() { root(), Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 42, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })) .unwrap(), ) @@ -344,7 +345,7 @@ fn cancel_named_periodic_scheduling_works() { root(), Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 69, - weight: Weight::from_ref_time(10) + weight: Weight::from_parts(10, 0) })) .unwrap(), ) @@ -358,7 +359,7 @@ fn cancel_named_periodic_scheduling_works() { root(), Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 69, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })) .unwrap(), ) @@ -479,8 +480,10 @@ fn scheduler_handles_periodic_unavailable_preimage() { let call = RuntimeCall::Logger(LoggerCall::log { i: 42, weight: (max_weight / 3) * 2 }); let hash = ::Hashing::hash_of(&call); let len = call.using_encoded(|x| x.len()) as u32; - let bound = Preimage::pick(hash, len); - assert_ok!(Preimage::note(call.encode().into())); + // Important to use here `Bounded::Lookup` to ensure that we request the hash. + let bound = Bounded::Lookup { hash, len }; + // The preimage isn't requested yet. + assert!(!Preimage::is_requested(&hash)); assert_ok!(Scheduler::do_schedule( DispatchTime::At(4), @@ -489,11 +492,18 @@ fn scheduler_handles_periodic_unavailable_preimage() { root(), bound.clone(), )); + + // The preimage is requested. + assert!(Preimage::is_requested(&hash)); + + // Note the preimage. + assert_ok!(Preimage::note_preimage(RuntimeOrigin::signed(1), call.encode())); + // Executes 1 times till block 4. run_to_block(4); assert_eq!(logger::log().len(), 1); - // Unnote the preimage. + // Unnote the preimage Preimage::unnote(&hash); // Does not ever execute again. @@ -571,12 +581,12 @@ fn scheduler_respects_priority_ordering_with_soft_deadlines() { #[test] fn on_initialize_weight_is_correct() { new_test_ext().execute_with(|| { - let call_weight = Weight::from_ref_time(25); + let call_weight = Weight::from_parts(25, 0); // Named let call = RuntimeCall::Logger(LoggerCall::log { i: 3, - weight: call_weight + Weight::from_ref_time(1), + weight: call_weight + Weight::from_parts(1, 0), }); assert_ok!(Scheduler::do_schedule_named( [1u8; 32], @@ -588,7 +598,7 @@ fn on_initialize_weight_is_correct() { )); let call = RuntimeCall::Logger(LoggerCall::log { i: 42, - weight: call_weight + Weight::from_ref_time(2), + weight: call_weight + Weight::from_parts(2, 0), }); // Anon Periodic assert_ok!(Scheduler::do_schedule( @@ -600,7 +610,7 @@ fn on_initialize_weight_is_correct() { )); let call = RuntimeCall::Logger(LoggerCall::log { i: 69, - weight: call_weight + Weight::from_ref_time(3), + weight: call_weight + Weight::from_parts(3, 0), }); // Anon assert_ok!(Scheduler::do_schedule( @@ -613,7 +623,7 @@ fn on_initialize_weight_is_correct() { // Named Periodic let call = RuntimeCall::Logger(LoggerCall::log { i: 2600, - weight: call_weight + Weight::from_ref_time(4), + weight: call_weight + Weight::from_parts(4, 0), }); assert_ok!(Scheduler::do_schedule_named( [2u8; 32], @@ -631,7 +641,7 @@ fn on_initialize_weight_is_correct() { TestWeightInfo::service_agenda_base(1) + ::service_task(None, true, true) + TestWeightInfo::execute_dispatch_unsigned() + - call_weight + Weight::from_ref_time(4) + call_weight + Weight::from_parts(4, 0) ); assert_eq!(IncompleteSince::::get(), None); assert_eq!(logger::log(), vec![(root(), 2600u32)]); @@ -643,10 +653,10 @@ fn on_initialize_weight_is_correct() { TestWeightInfo::service_agenda_base(2) + ::service_task(None, false, true) + TestWeightInfo::execute_dispatch_unsigned() + - call_weight + Weight::from_ref_time(3) + + call_weight + Weight::from_parts(3, 0) + ::service_task(None, false, false) + TestWeightInfo::execute_dispatch_unsigned() + - call_weight + Weight::from_ref_time(2) + call_weight + Weight::from_parts(2, 0) ); assert_eq!(IncompleteSince::::get(), None); assert_eq!(logger::log(), vec![(root(), 2600u32), (root(), 69u32), (root(), 42u32)]); @@ -658,7 +668,7 @@ fn on_initialize_weight_is_correct() { TestWeightInfo::service_agenda_base(1) + ::service_task(None, true, false) + TestWeightInfo::execute_dispatch_unsigned() + - call_weight + Weight::from_ref_time(1) + call_weight + Weight::from_parts(1, 0) ); assert_eq!(IncompleteSince::::get(), None); assert_eq!( @@ -680,11 +690,11 @@ fn root_calls_works() { new_test_ext().execute_with(|| { let call = Box::new(RuntimeCall::Logger(LoggerCall::log { i: 69, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })); let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log { i: 42, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })); assert_ok!( Scheduler::schedule_named(RuntimeOrigin::root(), [1u8; 32], 4, None, 127, call,) @@ -709,15 +719,15 @@ fn fails_to_schedule_task_in_the_past() { let call1 = Box::new(RuntimeCall::Logger(LoggerCall::log { i: 69, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })); let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log { i: 42, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })); let call3 = Box::new(RuntimeCall::Logger(LoggerCall::log { i: 42, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })); assert_noop!( @@ -742,11 +752,11 @@ fn should_use_origin() { new_test_ext().execute_with(|| { let call = Box::new(RuntimeCall::Logger(LoggerCall::log { i: 69, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })); let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log { i: 42, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })); assert_ok!(Scheduler::schedule_named( system::RawOrigin::Signed(1).into(), @@ -774,11 +784,11 @@ fn should_check_origin() { new_test_ext().execute_with(|| { let call = Box::new(RuntimeCall::Logger(LoggerCall::log { i: 69, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })); let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log { i: 42, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })); assert_noop!( Scheduler::schedule_named( @@ -803,11 +813,11 @@ fn should_check_origin_for_cancel() { new_test_ext().execute_with(|| { let call = Box::new(RuntimeCall::Logger(LoggerCall::log_without_filter { i: 69, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })); let call2 = Box::new(RuntimeCall::Logger(LoggerCall::log_without_filter { i: 42, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })); assert_ok!(Scheduler::schedule_named( system::RawOrigin::Signed(1).into(), @@ -851,7 +861,7 @@ fn migration_to_v4_works() { priority: i as u8 + 10, call: RuntimeCall::Logger(LoggerCall::log { i: 96, - weight: Weight::from_ref_time(100), + weight: Weight::from_parts(100, 0), }), maybe_periodic: None, }), @@ -861,7 +871,7 @@ fn migration_to_v4_works() { priority: 123, call: RuntimeCall::Logger(LoggerCall::log { i: 69, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), }), maybe_periodic: Some((456u64, 10)), }), @@ -882,7 +892,7 @@ fn migration_to_v4_works() { priority: 10, call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 96, - weight: Weight::from_ref_time(100), + weight: Weight::from_parts(100, 0), })) .unwrap(), maybe_periodic: None, @@ -895,7 +905,7 @@ fn migration_to_v4_works() { priority: 123, call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 69, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })) .unwrap(), maybe_periodic: Some((456u64, 10)), @@ -912,7 +922,7 @@ fn migration_to_v4_works() { priority: 11, call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 96, - weight: Weight::from_ref_time(100), + weight: Weight::from_parts(100, 0), })) .unwrap(), maybe_periodic: None, @@ -925,7 +935,7 @@ fn migration_to_v4_works() { priority: 123, call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 69, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })) .unwrap(), maybe_periodic: Some((456u64, 10)), @@ -942,7 +952,7 @@ fn migration_to_v4_works() { priority: 12, call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 96, - weight: Weight::from_ref_time(100), + weight: Weight::from_parts(100, 0), })) .unwrap(), maybe_periodic: None, @@ -955,7 +965,7 @@ fn migration_to_v4_works() { priority: 123, call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 69, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })) .unwrap(), maybe_periodic: Some((456u64, 10)), @@ -988,7 +998,7 @@ fn test_migrate_origin() { priority: i as u8 + 10, call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 96, - weight: Weight::from_ref_time(100), + weight: Weight::from_parts(100, 0), })) .unwrap(), origin: 3u32, @@ -1002,7 +1012,7 @@ fn test_migrate_origin() { origin: 2u32, call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 69, - weight: Weight::from_ref_time(10), + weight: Weight::from_parts(10, 0), })) .unwrap(), maybe_periodic: Some((456u64, 10)), @@ -1035,7 +1045,7 @@ fn test_migrate_origin() { priority: 10, call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 96, - weight: Weight::from_ref_time(100) + weight: Weight::from_parts(100, 0) })) .unwrap(), maybe_periodic: None, @@ -1048,7 +1058,7 @@ fn test_migrate_origin() { priority: 123, call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 69, - weight: Weight::from_ref_time(10) + weight: Weight::from_parts(10, 0) })) .unwrap(), maybe_periodic: Some((456u64, 10)), @@ -1065,7 +1075,7 @@ fn test_migrate_origin() { priority: 11, call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 96, - weight: Weight::from_ref_time(100) + weight: Weight::from_parts(100, 0) })) .unwrap(), maybe_periodic: None, @@ -1078,7 +1088,7 @@ fn test_migrate_origin() { priority: 123, call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 69, - weight: Weight::from_ref_time(10) + weight: Weight::from_parts(10, 0) })) .unwrap(), maybe_periodic: Some((456u64, 10)), @@ -1095,7 +1105,7 @@ fn test_migrate_origin() { priority: 12, call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 96, - weight: Weight::from_ref_time(100) + weight: Weight::from_parts(100, 0) })) .unwrap(), maybe_periodic: None, @@ -1108,7 +1118,7 @@ fn test_migrate_origin() { priority: 123, call: Preimage::bound(RuntimeCall::Logger(LoggerCall::log { i: 69, - weight: Weight::from_ref_time(10) + weight: Weight::from_parts(10, 0) })) .unwrap(), maybe_periodic: Some((456u64, 10)), @@ -1126,10 +1136,11 @@ fn test_migrate_origin() { fn postponed_named_task_cannot_be_rescheduled() { new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(1000) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(1000, 0) }); let hash = ::Hashing::hash_of(&call); let len = call.using_encoded(|x| x.len()) as u32; - let hashed = Preimage::pick(hash, len); + // Important to use here `Bounded::Lookup` to ensure that we request the hash. + let hashed = Bounded::Lookup { hash, len }; let name: [u8; 32] = hash.as_ref().try_into().unwrap(); let address = Scheduler::do_schedule_named( @@ -1193,7 +1204,7 @@ fn scheduler_v3_anon_basic_works() { use frame_support::traits::schedule::v3::Anon; new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); // Schedule a call. let _address = >::schedule( @@ -1222,7 +1233,7 @@ fn scheduler_v3_anon_cancel_works() { use frame_support::traits::schedule::v3::Anon; new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); let bound = Preimage::bound(call).unwrap(); // Schedule a call. @@ -1249,7 +1260,7 @@ fn scheduler_v3_anon_reschedule_works() { use frame_support::traits::schedule::v3::Anon; new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); // Schedule a call. let address = >::schedule( @@ -1296,7 +1307,7 @@ fn scheduler_v3_anon_next_schedule_time_works() { use frame_support::traits::schedule::v3::Anon; new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); let bound = Preimage::bound(call).unwrap(); // Schedule a call. @@ -1333,7 +1344,7 @@ fn scheduler_v3_anon_reschedule_and_next_schedule_time_work() { use frame_support::traits::schedule::v3::Anon; new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); let bound = Preimage::bound(call).unwrap(); // Schedule a call. @@ -1375,7 +1386,7 @@ fn scheduler_v3_anon_schedule_agenda_overflows() { new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); let bound = Preimage::bound(call).unwrap(); // Schedule the maximal number allowed per block. @@ -1411,7 +1422,7 @@ fn scheduler_v3_anon_cancel_and_schedule_fills_holes() { new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); let bound = Preimage::bound(call).unwrap(); let mut addrs = Vec::<_>::default(); @@ -1460,7 +1471,7 @@ fn scheduler_v3_anon_reschedule_fills_holes() { new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); let bound = Preimage::bound(call).unwrap(); let mut addrs = Vec::<_>::default(); @@ -1504,7 +1515,7 @@ fn scheduler_v3_named_basic_works() { new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); let name = [1u8; 32]; // Schedule a call. @@ -1536,7 +1547,7 @@ fn scheduler_v3_named_cancel_named_works() { use frame_support::traits::schedule::v3::Named; new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); let bound = Preimage::bound(call).unwrap(); let name = [1u8; 32]; @@ -1566,7 +1577,7 @@ fn scheduler_v3_named_cancel_without_name_works() { use frame_support::traits::schedule::v3::{Anon, Named}; new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); let bound = Preimage::bound(call).unwrap(); let name = [1u8; 32]; @@ -1596,7 +1607,7 @@ fn scheduler_v3_named_reschedule_named_works() { use frame_support::traits::schedule::v3::{Anon, Named}; new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); let name = [1u8; 32]; // Schedule a call. @@ -1655,7 +1666,7 @@ fn scheduler_v3_named_next_schedule_time_works() { use frame_support::traits::schedule::v3::{Anon, Named}; new_test_ext().execute_with(|| { let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); let bound = Preimage::bound(call).unwrap(); let name = [1u8; 32]; @@ -1700,7 +1711,7 @@ fn cancel_last_task_removes_agenda() { new_test_ext().execute_with(|| { let when = 4; let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); let address = Scheduler::do_schedule( DispatchTime::At(when), None, @@ -1734,7 +1745,7 @@ fn cancel_named_last_task_removes_agenda() { new_test_ext().execute_with(|| { let when = 4; let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); Scheduler::do_schedule_named( [1u8; 32], DispatchTime::At(when), @@ -1770,7 +1781,7 @@ fn reschedule_last_task_removes_agenda() { new_test_ext().execute_with(|| { let when = 4; let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); let address = Scheduler::do_schedule( DispatchTime::At(when), None, @@ -1807,7 +1818,7 @@ fn reschedule_named_last_task_removes_agenda() { new_test_ext().execute_with(|| { let when = 4; let call = - RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_ref_time(10) }); + RuntimeCall::Logger(LoggerCall::log { i: 42, weight: Weight::from_parts(10, 0) }); Scheduler::do_schedule_named( [1u8; 32], DispatchTime::At(when), diff --git a/frame/scheduler/src/weights.rs b/frame/scheduler/src/weights.rs index 5b86e7a143e7..58d711862591 100644 --- a/frame/scheduler/src/weights.rs +++ b/frame/scheduler/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_scheduler //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_scheduler +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_scheduler. pub trait WeightInfo { @@ -64,194 +69,292 @@ pub trait WeightInfo { /// Weights for pallet_scheduler using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Scheduler IncompleteSince (r:1 w:1) + /// Storage: Scheduler IncompleteSince (r:1 w:1) + /// Proof: Scheduler IncompleteSince (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn service_agendas_base() -> Weight { - // Minimum execution time: 5_131 nanoseconds. - Weight::from_ref_time(5_286_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `31` + // Estimated: `1489` + // Minimum execution time: 3_991_000 picoseconds. + Weight::from_parts(4_174_000, 1489) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Scheduler Agenda (r:1 w:1) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) /// The range of component `s` is `[0, 512]`. fn service_agenda_base(s: u32, ) -> Weight { - // Minimum execution time: 4_111 nanoseconds. - Weight::from_ref_time(8_763_440 as u64) - // Standard Error: 783 - .saturating_add(Weight::from_ref_time(372_339 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `81 + s * (177 ±0)` + // Estimated: `110487` + // Minimum execution time: 3_581_000 picoseconds. + Weight::from_parts(7_413_174, 110487) + // Standard Error: 971 + .saturating_add(Weight::from_parts(348_077, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } fn service_task_base() -> Weight { - // Minimum execution time: 10_880 nanoseconds. - Weight::from_ref_time(11_194_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_250_000 picoseconds. + Weight::from_parts(5_549_000, 0) } - // Storage: Preimage PreimageFor (r:1 w:1) - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Preimage PreimageFor (r:1 w:1) + /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) /// The range of component `s` is `[128, 4194304]`. fn service_task_fetched(s: u32, ) -> Weight { - // Minimum execution time: 25_347 nanoseconds. - Weight::from_ref_time(25_717_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_128 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `179 + s * (1 ±0)` + // Estimated: `3644 + s * (1 ±0)` + // Minimum execution time: 20_089_000 picoseconds. + Weight::from_parts(20_376_000, 3644) + // Standard Error: 3 + .saturating_add(Weight::from_parts(1_170, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) } - // Storage: Scheduler Lookup (r:0 w:1) + /// Storage: Scheduler Lookup (r:0 w:1) + /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn service_task_named() -> Weight { - // Minimum execution time: 12_894 nanoseconds. - Weight::from_ref_time(13_108_000 as u64) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_998_000 picoseconds. + Weight::from_parts(7_303_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) } fn service_task_periodic() -> Weight { - // Minimum execution time: 10_667 nanoseconds. - Weight::from_ref_time(10_908_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_078_000 picoseconds. + Weight::from_parts(5_315_000, 0) } fn execute_dispatch_signed() -> Weight { - // Minimum execution time: 4_124 nanoseconds. - Weight::from_ref_time(4_680_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_228_000 picoseconds. + Weight::from_parts(2_352_000, 0) } fn execute_dispatch_unsigned() -> Weight { - // Minimum execution time: 4_156 nanoseconds. - Weight::from_ref_time(4_361_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_226_000 picoseconds. + Weight::from_parts(2_371_000, 0) } - // Storage: Scheduler Agenda (r:1 w:1) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) /// The range of component `s` is `[0, 511]`. fn schedule(s: u32, ) -> Weight { - // Minimum execution time: 20_504 nanoseconds. - Weight::from_ref_time(27_066_818 as u64) - // Standard Error: 1_114 - .saturating_add(Weight::from_ref_time(372_897 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `81 + s * (177 ±0)` + // Estimated: `110487` + // Minimum execution time: 12_683_000 picoseconds. + Weight::from_parts(16_951_846, 110487) + // Standard Error: 1_046 + .saturating_add(Weight::from_parts(380_842, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Scheduler Agenda (r:1 w:1) - // Storage: Scheduler Lookup (r:0 w:1) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: Scheduler Lookup (r:0 w:1) + /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) /// The range of component `s` is `[1, 512]`. fn cancel(s: u32, ) -> Weight { - // Minimum execution time: 21_686 nanoseconds. - Weight::from_ref_time(25_696_496 as u64) - // Standard Error: 1_261 - .saturating_add(Weight::from_ref_time(362_498 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `81 + s * (177 ±0)` + // Estimated: `110487` + // Minimum execution time: 16_201_000 picoseconds. + Weight::from_parts(18_259_422, 110487) + // Standard Error: 1_344 + .saturating_add(Weight::from_parts(545_863, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Scheduler Lookup (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + /// Storage: Scheduler Lookup (r:1 w:1) + /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) /// The range of component `s` is `[0, 511]`. fn schedule_named(s: u32, ) -> Weight { - // Minimum execution time: 23_084 nanoseconds. - Weight::from_ref_time(31_255_518 as u64) - // Standard Error: 1_258 - .saturating_add(Weight::from_ref_time(382_534 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `596 + s * (178 ±0)` + // Estimated: `110487` + // Minimum execution time: 16_180_000 picoseconds. + Weight::from_parts(25_128_925, 110487) + // Standard Error: 1_118 + .saturating_add(Weight::from_parts(375_631, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Scheduler Lookup (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + /// Storage: Scheduler Lookup (r:1 w:1) + /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) /// The range of component `s` is `[1, 512]`. fn cancel_named(s: u32, ) -> Weight { - // Minimum execution time: 23_862 nanoseconds. - Weight::from_ref_time(28_591_336 as u64) - // Standard Error: 742 - .saturating_add(Weight::from_ref_time(369_305 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `709 + s * (177 ±0)` + // Estimated: `110487` + // Minimum execution time: 18_244_000 picoseconds. + Weight::from_parts(21_439_366, 110487) + // Standard Error: 1_084 + .saturating_add(Weight::from_parts(557_691, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Scheduler IncompleteSince (r:1 w:1) + /// Storage: Scheduler IncompleteSince (r:1 w:1) + /// Proof: Scheduler IncompleteSince (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn service_agendas_base() -> Weight { - // Minimum execution time: 5_131 nanoseconds. - Weight::from_ref_time(5_286_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `31` + // Estimated: `1489` + // Minimum execution time: 3_991_000 picoseconds. + Weight::from_parts(4_174_000, 1489) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Scheduler Agenda (r:1 w:1) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) /// The range of component `s` is `[0, 512]`. fn service_agenda_base(s: u32, ) -> Weight { - // Minimum execution time: 4_111 nanoseconds. - Weight::from_ref_time(8_763_440 as u64) - // Standard Error: 783 - .saturating_add(Weight::from_ref_time(372_339 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `81 + s * (177 ±0)` + // Estimated: `110487` + // Minimum execution time: 3_581_000 picoseconds. + Weight::from_parts(7_413_174, 110487) + // Standard Error: 971 + .saturating_add(Weight::from_parts(348_077, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn service_task_base() -> Weight { - // Minimum execution time: 10_880 nanoseconds. - Weight::from_ref_time(11_194_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_250_000 picoseconds. + Weight::from_parts(5_549_000, 0) } - // Storage: Preimage PreimageFor (r:1 w:1) - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Preimage PreimageFor (r:1 w:1) + /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) /// The range of component `s` is `[128, 4194304]`. fn service_task_fetched(s: u32, ) -> Weight { - // Minimum execution time: 25_347 nanoseconds. - Weight::from_ref_time(25_717_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_128 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `179 + s * (1 ±0)` + // Estimated: `3644 + s * (1 ±0)` + // Minimum execution time: 20_089_000 picoseconds. + Weight::from_parts(20_376_000, 3644) + // Standard Error: 3 + .saturating_add(Weight::from_parts(1_170, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(s.into())) } - // Storage: Scheduler Lookup (r:0 w:1) + /// Storage: Scheduler Lookup (r:0 w:1) + /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) fn service_task_named() -> Weight { - // Minimum execution time: 12_894 nanoseconds. - Weight::from_ref_time(13_108_000 as u64) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_998_000 picoseconds. + Weight::from_parts(7_303_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn service_task_periodic() -> Weight { - // Minimum execution time: 10_667 nanoseconds. - Weight::from_ref_time(10_908_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_078_000 picoseconds. + Weight::from_parts(5_315_000, 0) } fn execute_dispatch_signed() -> Weight { - // Minimum execution time: 4_124 nanoseconds. - Weight::from_ref_time(4_680_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_228_000 picoseconds. + Weight::from_parts(2_352_000, 0) } fn execute_dispatch_unsigned() -> Weight { - // Minimum execution time: 4_156 nanoseconds. - Weight::from_ref_time(4_361_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_226_000 picoseconds. + Weight::from_parts(2_371_000, 0) } - // Storage: Scheduler Agenda (r:1 w:1) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) /// The range of component `s` is `[0, 511]`. fn schedule(s: u32, ) -> Weight { - // Minimum execution time: 20_504 nanoseconds. - Weight::from_ref_time(27_066_818 as u64) - // Standard Error: 1_114 - .saturating_add(Weight::from_ref_time(372_897 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `81 + s * (177 ±0)` + // Estimated: `110487` + // Minimum execution time: 12_683_000 picoseconds. + Weight::from_parts(16_951_846, 110487) + // Standard Error: 1_046 + .saturating_add(Weight::from_parts(380_842, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Scheduler Agenda (r:1 w:1) - // Storage: Scheduler Lookup (r:0 w:1) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) + /// Storage: Scheduler Lookup (r:0 w:1) + /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) /// The range of component `s` is `[1, 512]`. fn cancel(s: u32, ) -> Weight { - // Minimum execution time: 21_686 nanoseconds. - Weight::from_ref_time(25_696_496 as u64) - // Standard Error: 1_261 - .saturating_add(Weight::from_ref_time(362_498 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `81 + s * (177 ±0)` + // Estimated: `110487` + // Minimum execution time: 16_201_000 picoseconds. + Weight::from_parts(18_259_422, 110487) + // Standard Error: 1_344 + .saturating_add(Weight::from_parts(545_863, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Scheduler Lookup (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + /// Storage: Scheduler Lookup (r:1 w:1) + /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) /// The range of component `s` is `[0, 511]`. fn schedule_named(s: u32, ) -> Weight { - // Minimum execution time: 23_084 nanoseconds. - Weight::from_ref_time(31_255_518 as u64) - // Standard Error: 1_258 - .saturating_add(Weight::from_ref_time(382_534 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `596 + s * (178 ±0)` + // Estimated: `110487` + // Minimum execution time: 16_180_000 picoseconds. + Weight::from_parts(25_128_925, 110487) + // Standard Error: 1_118 + .saturating_add(Weight::from_parts(375_631, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Scheduler Lookup (r:1 w:1) - // Storage: Scheduler Agenda (r:1 w:1) + /// Storage: Scheduler Lookup (r:1 w:1) + /// Proof: Scheduler Lookup (max_values: None, max_size: Some(48), added: 2523, mode: MaxEncodedLen) + /// Storage: Scheduler Agenda (r:1 w:1) + /// Proof: Scheduler Agenda (max_values: None, max_size: Some(107022), added: 109497, mode: MaxEncodedLen) /// The range of component `s` is `[1, 512]`. fn cancel_named(s: u32, ) -> Weight { - // Minimum execution time: 23_862 nanoseconds. - Weight::from_ref_time(28_591_336 as u64) - // Standard Error: 742 - .saturating_add(Weight::from_ref_time(369_305 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `709 + s * (177 ±0)` + // Estimated: `110487` + // Minimum execution time: 18_244_000 picoseconds. + Weight::from_parts(21_439_366, 110487) + // Standard Error: 1_084 + .saturating_add(Weight::from_parts(557_691, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } } diff --git a/frame/scored-pool/Cargo.toml b/frame/scored-pool/Cargo.toml index d44fc1b2f154..0c8e5b934362 100644 --- a/frame/scored-pool/Cargo.toml +++ b/frame/scored-pool/Cargo.toml @@ -13,17 +13,17 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } [features] default = ["std"] diff --git a/frame/scored-pool/src/lib.rs b/frame/scored-pool/src/lib.rs index 5db9c6506d77..2bf70cbc574c 100644 --- a/frame/scored-pool/src/lib.rs +++ b/frame/scored-pool/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -70,7 +70,7 @@ //! //! #[pallet::call] //! impl Pallet { -//! #[pallet::weight(0)] +//! #[pallet::weight({0})] //! pub fn candidate(origin: OriginFor) -> DispatchResult { //! let who = ensure_signed(origin)?; //! @@ -135,7 +135,6 @@ pub mod pallet { use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::config] @@ -171,7 +170,7 @@ pub mod pallet { /// Every `Period` blocks the `Members` are filled with the highest scoring /// members in the `Pool`. #[pallet::constant] - type Period: Get; + type Period: Get>; /// The receiver of the signal for when the membership has been initialized. /// This happens pre-genesis and will usually be the same as `MembershipChanged`. @@ -250,20 +249,14 @@ pub mod pallet { pub(crate) type MemberCount = StorageValue<_, u32, ValueQuery>; #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig, I: 'static = ()> { pub pool: PoolT, pub member_count: u32, } - #[cfg(feature = "std")] - impl, I: 'static> Default for GenesisConfig { - fn default() -> Self { - Self { pool: Default::default(), member_count: Default::default() } - } - } - #[pallet::genesis_build] - impl, I: 'static> GenesisBuild for GenesisConfig { + impl, I: 'static> BuildGenesisConfig for GenesisConfig { fn build(&self) { let mut pool = self.pool.clone(); @@ -289,7 +282,7 @@ pub mod pallet { impl, I: 'static> Hooks> for Pallet { /// Every `Period` blocks the `Members` set is refreshed from the /// highest scoring members in the pool. - fn on_initialize(n: T::BlockNumber) -> Weight { + fn on_initialize(n: frame_system::pallet_prelude::BlockNumberFor) -> Weight { if n % T::Period::get() == Zero::zero() { let pool = >::get(); >::refresh_members(pool, ChangeReceiver::MembershipChanged); @@ -312,7 +305,7 @@ pub mod pallet { /// The `index` parameter of this function must be set to /// the index of the transactor in the `Pool`. #[pallet::call_index(0)] - #[pallet::weight(0)] + #[pallet::weight({0})] pub fn submit_candidacy(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; ensure!(!>::contains_key(&who), Error::::AlreadyInPool); @@ -342,7 +335,7 @@ pub mod pallet { /// The `index` parameter of this function must be set to /// the index of the transactor in the `Pool`. #[pallet::call_index(1)] - #[pallet::weight(0)] + #[pallet::weight({0})] pub fn withdraw_candidacy(origin: OriginFor, index: u32) -> DispatchResult { let who = ensure_signed(origin)?; @@ -361,7 +354,7 @@ pub mod pallet { /// The `index` parameter of this function must be set to /// the index of `dest` in the `Pool`. #[pallet::call_index(2)] - #[pallet::weight(0)] + #[pallet::weight({0})] pub fn kick( origin: OriginFor, dest: AccountIdLookupOf, @@ -386,7 +379,7 @@ pub mod pallet { /// The `index` parameter of this function must be set to /// the index of the `dest` in the `Pool`. #[pallet::call_index(3)] - #[pallet::weight(0)] + #[pallet::weight({0})] pub fn score( origin: OriginFor, dest: AccountIdLookupOf, @@ -426,7 +419,7 @@ pub mod pallet { /// /// May only be called from root. #[pallet::call_index(4)] - #[pallet::weight(0)] + #[pallet::weight({0})] pub fn change_member_count(origin: OriginFor, count: u32) -> DispatchResult { ensure_root(origin)?; Self::update_member_count(count).map_err(Into::into) diff --git a/frame/scored-pool/src/mock.rs b/frame/scored-pool/src/mock.rs index 951871dd83c5..d8c6ef9b0f44 100644 --- a/frame/scored-pool/src/mock.rs +++ b/frame/scored-pool/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,25 +22,21 @@ use crate as pallet_scored_pool; use frame_support::{ bounded_vec, construct_runtime, ord_parameter_types, parameter_types, - traits::{ConstU32, ConstU64, GenesisBuild}, + traits::{ConstU32, ConstU64}, }; use frame_system::EnsureSignedBy; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, ScoredPool: pallet_scored_pool::{Pallet, Call, Storage, Config, Event}, } @@ -60,14 +56,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -91,6 +86,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } parameter_types! { @@ -136,7 +135,7 @@ impl Config for Test { } pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let mut balances = vec![]; for i in 1..31 { balances.push((i, 500_000)); diff --git a/frame/scored-pool/src/tests.rs b/frame/scored-pool/src/tests.rs index 8f4daff47cc4..96c94a6c1c65 100644 --- a/frame/scored-pool/src/tests.rs +++ b/frame/scored-pool/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/session/Cargo.toml b/frame/session/Cargo.toml index bd28bffffd8d..c4bb09d5cb64 100644 --- a/frame/session/Cargo.toml +++ b/frame/session/Cargo.toml @@ -13,20 +13,20 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive", "serde"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../timestamp" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core", features = ["serde"] } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime", features = ["serde"] } sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } -sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } -sp-trie = { version = "7.0.0", default-features = false, optional = true, path = "../../primitives/trie" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking", features = ["serde"] } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-trie = { version = "22.0.0", default-features = false, optional = true, path = "../../primitives/trie" } [features] default = ["historical", "std"] diff --git a/frame/session/benchmarking/Cargo.toml b/frame/session/benchmarking/Cargo.toml index 4d1d9b4eda49..a693ff331914 100644 --- a/frame/session/benchmarking/Cargo.toml +++ b/frame/session/benchmarking/Cargo.toml @@ -19,19 +19,19 @@ frame-support = { version = "4.0.0-dev", default-features = false, path = "../.. frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } pallet-session = { version = "4.0.0-dev", default-features = false, path = "../../session" } pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../staking" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } sp-session = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/session" } -sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } [dev-dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } scale-info = "2.1.1" frame-election-provider-support = { version = "4.0.0-dev", path = "../../election-provider-support" } pallet-balances = { version = "4.0.0-dev", path = "../../balances" } pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../../staking/reward-curve" } pallet-timestamp = { version = "4.0.0-dev", path = "../../timestamp" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../../primitives/io" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../../primitives/io" } [features] default = ["std"] diff --git a/frame/session/benchmarking/src/lib.rs b/frame/session/benchmarking/src/lib.rs index 9e478ada53cf..f27136e820d4 100644 --- a/frame/session/benchmarking/src/lib.rs +++ b/frame/session/benchmarking/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,12 +26,12 @@ mod mock; use sp_runtime::traits::{One, StaticLookup, TrailingZeroInput}; use sp_std::{prelude::*, vec}; -use frame_benchmarking::benchmarks; +use frame_benchmarking::v1::benchmarks; use frame_support::{ codec::Decode, traits::{Get, KeyOwnerProofSystem, OnInitialize}, }; -use frame_system::RawOrigin; +use frame_system::{pallet_prelude::BlockNumberFor, RawOrigin}; use pallet_session::{historical::Pallet as Historical, Pallet as Session, *}; use pallet_staking::{ benchmarking::create_validator_with_nominators, testing_utils::create_validators, @@ -46,8 +46,8 @@ pub trait Config: { } -impl OnInitialize for Pallet { - fn on_initialize(n: T::BlockNumber) -> frame_support::weights::Weight { +impl OnInitialize> for Pallet { + fn on_initialize(n: BlockNumberFor) -> frame_support::weights::Weight { pallet_session::Pallet::::on_initialize(n) } } @@ -59,6 +59,7 @@ benchmarks! { n, ::MaxNominations::get(), false, + true, RewardDestination::Staked, )?; let v_controller = pallet_staking::Pallet::::bonded(&v_stash).ok_or("not stash")?; @@ -76,7 +77,8 @@ benchmarks! { n, ::MaxNominations::get(), false, - RewardDestination::Staked + true, + RewardDestination::Staked, )?; let v_controller = pallet_staking::Pallet::::bonded(&v_stash).ok_or("not stash")?; let keys = T::Keys::decode(&mut TrailingZeroInput::zeroes()).unwrap(); @@ -154,7 +156,7 @@ fn check_membership_proof_setup( Session::::set_keys(RawOrigin::Signed(controller).into(), keys, proof).unwrap(); } - Pallet::::on_initialize(T::BlockNumber::one()); + Pallet::::on_initialize(frame_system::pallet_prelude::BlockNumberFor::::one()); // skip sessions until the new validator set is enacted while Session::::validators().len() < n as usize { diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index 0699640bc092..dbbd437bbd93 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,23 +24,18 @@ use frame_support::{ parameter_types, traits::{ConstU32, ConstU64}, }; -use sp_runtime::traits::IdentityLookup; +use sp_runtime::{traits::IdentityLookup, BuildStorage}; type AccountId = u64; -type AccountIndex = u32; -type BlockNumber = u64; +type Nonce = u32; type Balance = u64; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, Session: pallet_session::{Pallet, Call, Storage, Event, Config}, @@ -53,14 +48,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = AccountIndex; - type BlockNumber = BlockNumber; + type Nonce = Nonce; type RuntimeCall = RuntimeCall; type Hash = sp_core::H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = sp_runtime::testing::Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type Version = (); @@ -84,6 +78,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<10>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } impl pallet_timestamp::Config for Test { @@ -160,7 +158,7 @@ impl pallet_staking::Config for Test { type Currency = Balances; type CurrencyBalance = ::Balance; type UnixTime = pallet_timestamp::Pallet; - type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type CurrencyToVote = (); type RewardRemainder = (); type RuntimeEvent = RuntimeEvent; type Slash = (); @@ -180,7 +178,7 @@ impl pallet_staking::Config for Test { type HistoryDepth = ConstU32<84>; type VoterList = pallet_staking::UseNominatorsAndValidatorsMap; type TargetList = pallet_staking::UseValidatorsMap; - type OnStakerSlash = (); + type EventListeners = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } @@ -188,6 +186,6 @@ impl pallet_staking::Config for Test { impl crate::Config for Test {} pub fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); sp_io::TestExternalities::new(t) } diff --git a/frame/session/src/historical/mod.rs b/frame/session/src/historical/mod.rs index 45b4ba3c0a79..04129e4eba13 100644 --- a/frame/session/src/historical/mod.rs +++ b/frame/session/src/historical/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,7 +62,6 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); @@ -97,7 +96,7 @@ impl Pallet { /// Prune historical stored session roots up to (but not including) /// `up_to`. pub fn prune_up_to(up_to: SessionIndex) { - ::StoredRange::mutate(|range| { + StoredRange::::mutate(|range| { let (start, end) = match *range { Some(range) => range, None => return, // nothing to prune. @@ -109,7 +108,7 @@ impl Pallet { return // out of bounds. harmless. } - (start..up_to).for_each(::HistoricalSessions::remove); + (start..up_to).for_each(HistoricalSessions::::remove); let new_start = up_to; *range = if new_start == end { @@ -378,17 +377,17 @@ pub(crate) mod tests { force_new_session, set_next_validators, NextValidators, Session, System, Test, }; - use sp_runtime::{key_types::DUMMY, testing::UintAuthorityId}; + use sp_runtime::{key_types::DUMMY, testing::UintAuthorityId, BuildStorage}; use frame_support::{ - traits::{GenesisBuild, KeyOwnerProofSystem, OnInitialize}, + traits::{KeyOwnerProofSystem, OnInitialize}, BasicExternalities, }; type Historical = Pallet; pub(crate) fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let keys: Vec<_> = NextValidators::get() .iter() .cloned() diff --git a/frame/session/src/historical/offchain.rs b/frame/session/src/historical/offchain.rs index ececb8af5ad5..c44eea2f374c 100644 --- a/frame/session/src/historical/offchain.rs +++ b/frame/session/src/historical/offchain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -149,18 +149,18 @@ mod tests { crypto::key_types::DUMMY, offchain::{testing::TestOffchainExt, OffchainDbExt, OffchainWorkerExt, StorageKind}, }; - use sp_runtime::testing::UintAuthorityId; + use sp_runtime::{testing::UintAuthorityId, BuildStorage}; use frame_support::{ - traits::{GenesisBuild, KeyOwnerProofSystem, OnInitialize}, + traits::{KeyOwnerProofSystem, OnInitialize}, BasicExternalities, }; type Historical = Pallet; pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default() - .build_storage::() + let mut t = frame_system::GenesisConfig::::default() + .build_storage() .expect("Failed to create test externalities."); let keys: Vec<_> = NextValidators::get() diff --git a/frame/session/src/historical/onchain.rs b/frame/session/src/historical/onchain.rs index c7160e2fcf53..97a7f02bd096 100644 --- a/frame/session/src/historical/onchain.rs +++ b/frame/session/src/historical/onchain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/session/src/historical/shared.rs b/frame/session/src/historical/shared.rs index faa91f791986..297385dfb426 100644 --- a/frame/session/src/historical/shared.rs +++ b/frame/session/src/historical/shared.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/session/src/lib.rs b/frame/session/src/lib.rs index 4e2caf5e0874..d2b1c2b74467 100644 --- a/frame/session/src/lib.rs +++ b/frame/session/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -126,6 +126,7 @@ use frame_support::{ weights::Weight, Parameter, }; +use frame_system::pallet_prelude::BlockNumberFor; use sp_runtime::{ traits::{AtLeast32BitUnsigned, Convert, Member, One, OpaqueKeys, Zero}, ConsensusEngineId, KeyTypeId, Permill, RuntimeAppPublic, @@ -371,7 +372,6 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] #[pallet::without_storage_info] pub struct Pallet(_); @@ -394,12 +394,12 @@ pub mod pallet { type ValidatorIdOf: Convert>; /// Indicator for when to end the session. - type ShouldEndSession: ShouldEndSession; + type ShouldEndSession: ShouldEndSession>; /// Something that can predict the next session rotation. This should typically come from /// the same logical unit that provides [`ShouldEndSession`], yet, it gives a best effort /// estimate. It is helpful to implement [`EstimateNextNewSession`]. - type NextSessionRotation: EstimateNextSessionRotation; + type NextSessionRotation: EstimateNextSessionRotation>; /// Handler for managing new session. type SessionManager: SessionManager; @@ -415,19 +415,13 @@ pub mod pallet { } #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { pub keys: Vec<(T::AccountId, T::ValidatorId, T::Keys)>, } - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - Self { keys: Default::default() } - } - } - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { if T::SessionHandler::KEY_TYPE_IDS.len() != T::Keys::key_ids().len() { panic!("Number of keys in session handler and session keys does not match"); @@ -566,7 +560,7 @@ pub mod pallet { impl Hooks> for Pallet { /// Called when a block is initialized. Will rotate session if it is the last /// block of the current session. - fn on_initialize(n: T::BlockNumber) -> Weight { + fn on_initialize(n: BlockNumberFor) -> Weight { if T::ShouldEndSession::should_end_session(n) { Self::rotate_session(); T::BlockWeights::get().max_block @@ -587,14 +581,9 @@ pub mod pallet { /// /// The dispatch origin of this function must be signed. /// - /// # - /// - Complexity: `O(1)`. Actual cost depends on the number of length of - /// `T::Keys::key_ids()` which is fixed. - /// - DbReads: `origin account`, `T::ValidatorIdOf`, `NextKeys` - /// - DbWrites: `origin account`, `NextKeys` - /// - DbReads per key id: `KeyOwner` - /// - DbWrites per key id: `KeyOwner` - /// # + /// ## Complexity + /// - `O(1)`. Actual cost depends on the number of length of `T::Keys::key_ids()` which is + /// fixed. #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::set_keys())] pub fn set_keys(origin: OriginFor, keys: T::Keys, proof: Vec) -> DispatchResult { @@ -614,13 +603,9 @@ pub mod pallet { /// means being a controller account) or directly convertible into a validator ID (which /// usually means being a stash account). /// - /// # - /// - Complexity: `O(1)` in number of key types. Actual cost depends on the number of length - /// of `T::Keys::key_ids()` which is fixed. - /// - DbReads: `T::ValidatorIdOf`, `NextKeys`, `origin account` - /// - DbWrites: `NextKeys`, `origin account` - /// - DbWrites per key id: `KeyOwner` - /// # + /// ## Complexity + /// - `O(1)` in number of key types. Actual cost depends on the number of length of + /// `T::Keys::key_ids()` which is fixed. #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::purge_keys())] pub fn purge_keys(origin: OriginFor) -> DispatchResult { @@ -688,7 +673,7 @@ impl Pallet { // since a new validator set always leads to `changed` starting // as true, we can ensure that `now_session_keys` and `next_validators` // have the same length. this function is called once per iteration. - if let Some(&(_, ref old_keys)) = now_session_keys.next() { + if let Some((_, old_keys)) = now_session_keys.next() { if old_keys != keys { changed = true; } @@ -917,14 +902,14 @@ impl ValidatorSet for Pallet { } } -impl EstimateNextNewSession for Pallet { - fn average_session_length() -> T::BlockNumber { +impl EstimateNextNewSession> for Pallet { + fn average_session_length() -> BlockNumberFor { T::NextSessionRotation::average_session_length() } /// This session pallet always calls new_session and next_session at the same time, hence we /// do a simple proxy and pass the function to next rotation. - fn estimate_next_new_session(now: T::BlockNumber) -> (Option, Weight) { + fn estimate_next_new_session(now: BlockNumberFor) -> (Option>, Weight) { T::NextSessionRotation::estimate_next_session_rotation(now) } } diff --git a/frame/session/src/migrations/mod.rs b/frame/session/src/migrations/mod.rs index 5981e87b677c..3b15d0ac4646 100644 --- a/frame/session/src/migrations/mod.rs +++ b/frame/session/src/migrations/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/session/src/migrations/v1.rs b/frame/session/src/migrations/v1.rs index 6689ca7299f1..394a1f4a5227 100644 --- a/frame/session/src/migrations/v1.rs +++ b/frame/session/src/migrations/v1.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/session/src/mock.rs b/frame/session/src/mock.rs index 1138bd954398..b45ebfd25c44 100644 --- a/frame/session/src/mock.rs +++ b/frame/session/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,14 +27,15 @@ use std::collections::BTreeMap; use sp_core::{crypto::key_types::DUMMY, H256}; use sp_runtime::{ impl_opaque_keys, - testing::{Header, UintAuthorityId}, + testing::UintAuthorityId, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; use sp_staking::SessionIndex; use frame_support::{ parameter_types, - traits::{ConstU32, ConstU64, GenesisBuild}, + traits::{ConstU32, ConstU64}, BasicExternalities, }; @@ -75,17 +76,13 @@ impl OpaqueKeys for PreUpgradeMockSessionKeys { } } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; #[cfg(feature = "historical")] frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Session: pallet_session::{Pallet, Call, Storage, Event, Config}, Historical: pallet_session_historical::{Pallet}, } @@ -93,12 +90,9 @@ frame_support::construct_runtime!( #[cfg(not(feature = "historical"))] frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Session: pallet_session::{Pallet, Call, Storage, Event, Config}, } ); @@ -215,7 +209,7 @@ pub fn reset_before_session_end_called() { } pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let keys: Vec<_> = NextValidators::get() .iter() .cloned() @@ -244,14 +238,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); diff --git a/frame/session/src/tests.rs b/frame/session/src/tests.rs index 43809cc3a9de..69337e016ea8 100644 --- a/frame/session/src/tests.rs +++ b/frame/session/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/session/src/weights.rs b/frame/session/src/weights.rs index d29413a33dd1..dd9848fd2c17 100644 --- a/frame/session/src/weights.rs +++ b/frame/session/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_session //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_session +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_session. pub trait WeightInfo { @@ -54,44 +59,68 @@ pub trait WeightInfo { /// Weights for pallet_session using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Staking Ledger (r:1 w:0) - // Storage: Session NextKeys (r:1 w:1) - // Storage: Session KeyOwner (r:4 w:4) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Session NextKeys (r:1 w:1) + /// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured) + /// Storage: Session KeyOwner (r:4 w:4) + /// Proof Skipped: Session KeyOwner (max_values: None, max_size: None, mode: Measured) fn set_keys() -> Weight { - // Minimum execution time: 59_046 nanoseconds. - Weight::from_ref_time(59_934_000 as u64) - .saturating_add(T::DbWeight::get().reads(6 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + // Proof Size summary in bytes: + // Measured: `1924` + // Estimated: `12814` + // Minimum execution time: 55_459_000 picoseconds. + Weight::from_parts(56_180_000, 12814) + .saturating_add(T::DbWeight::get().reads(6_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } - // Storage: Staking Ledger (r:1 w:0) - // Storage: Session NextKeys (r:1 w:1) - // Storage: Session KeyOwner (r:0 w:4) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Session NextKeys (r:1 w:1) + /// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured) + /// Storage: Session KeyOwner (r:0 w:4) + /// Proof Skipped: Session KeyOwner (max_values: None, max_size: None, mode: Measured) fn purge_keys() -> Weight { - // Minimum execution time: 48_872 nanoseconds. - Weight::from_ref_time(49_666_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(5 as u64)) + // Proof Size summary in bytes: + // Measured: `1791` + // Estimated: `5256` + // Minimum execution time: 40_194_000 picoseconds. + Weight::from_parts(41_313_000, 5256) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Staking Ledger (r:1 w:0) - // Storage: Session NextKeys (r:1 w:1) - // Storage: Session KeyOwner (r:4 w:4) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Session NextKeys (r:1 w:1) + /// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured) + /// Storage: Session KeyOwner (r:4 w:4) + /// Proof Skipped: Session KeyOwner (max_values: None, max_size: None, mode: Measured) fn set_keys() -> Weight { - // Minimum execution time: 59_046 nanoseconds. - Weight::from_ref_time(59_934_000 as u64) - .saturating_add(RocksDbWeight::get().reads(6 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + // Proof Size summary in bytes: + // Measured: `1924` + // Estimated: `12814` + // Minimum execution time: 55_459_000 picoseconds. + Weight::from_parts(56_180_000, 12814) + .saturating_add(RocksDbWeight::get().reads(6_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } - // Storage: Staking Ledger (r:1 w:0) - // Storage: Session NextKeys (r:1 w:1) - // Storage: Session KeyOwner (r:0 w:4) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Session NextKeys (r:1 w:1) + /// Proof Skipped: Session NextKeys (max_values: None, max_size: None, mode: Measured) + /// Storage: Session KeyOwner (r:0 w:4) + /// Proof Skipped: Session KeyOwner (max_values: None, max_size: None, mode: Measured) fn purge_keys() -> Weight { - // Minimum execution time: 48_872 nanoseconds. - Weight::from_ref_time(49_666_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(5 as u64)) + // Proof Size summary in bytes: + // Measured: `1791` + // Estimated: `5256` + // Minimum execution time: 40_194_000 picoseconds. + Weight::from_parts(41_313_000, 5256) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } } diff --git a/frame/society/Cargo.toml b/frame/society/Cargo.toml index ddc6ea6aac7d..7f3e55e339fe 100644 --- a/frame/society/Cargo.toml +++ b/frame/society/Cargo.toml @@ -13,33 +13,49 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +hex-literal = "0.3.4" +log = { version = "0.4.17", default-features = false } rand_chacha = { version = "0.2", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } + +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../primitives/arithmetic" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] frame-support-test = { version = "3.0.0", path = "../support/test" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } [features] default = ["std"] +# Enable `VersionedRuntimeUpgrade` for the migrations that is currently still experimental. +experimental = [ + "frame-support/experimental" +] std = [ "codec/std", + "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "rand_chacha/std", "scale-info/std", "sp-runtime/std", "sp-std/std", + "sp-io/std", ] runtime-benchmarks = [ - "frame-system/runtime-benchmarks", + "frame-benchmarking", "sp-runtime/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/society/src/benchmarking.rs b/frame/society/src/benchmarking.rs new file mode 100644 index 000000000000..20af6e35ada5 --- /dev/null +++ b/frame/society/src/benchmarking.rs @@ -0,0 +1,379 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Society pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; + +use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller}; +use frame_system::RawOrigin; + +use sp_runtime::traits::Bounded; + +use crate::Pallet as Society; + +fn mock_balance_deposit, I: 'static>() -> BalanceOf { + T::Currency::minimum_balance().saturating_mul(1_000u32.into()) +} + +fn make_deposit, I: 'static>(who: &T::AccountId) -> BalanceOf { + let amount = mock_balance_deposit::(); + let required = amount.saturating_add(T::Currency::minimum_balance()); + if T::Currency::free_balance(who) < required { + T::Currency::make_free_balance_be(who, required); + } + T::Currency::reserve(who, amount).expect("Pre-funded account; qed"); + amount +} + +fn make_bid, I: 'static>( + who: &T::AccountId, +) -> BidKind> { + BidKind::Deposit(make_deposit::(who)) +} + +fn fund_society, I: 'static>() { + T::Currency::make_free_balance_be( + &Society::::account_id(), + BalanceOf::::max_value(), + ); + Pot::::put(&BalanceOf::::max_value()); +} + +// Set up Society +fn setup_society, I: 'static>() -> Result { + let origin = T::FounderSetOrigin::try_successful_origin().map_err(|_| "No origin")?; + let founder: T::AccountId = account("founder", 0, 0); + let founder_lookup: ::Source = T::Lookup::unlookup(founder.clone()); + let max_members = 5u32; + let max_intake = 3u32; + let max_strikes = 3u32; + Society::::found_society( + origin, + founder_lookup, + max_members, + max_intake, + max_strikes, + mock_balance_deposit::(), + b"benchmarking-society".to_vec(), + )?; + T::Currency::make_free_balance_be( + &Society::::account_id(), + T::Currency::minimum_balance(), + ); + T::Currency::make_free_balance_be(&Society::::payouts(), T::Currency::minimum_balance()); + Ok(founder) +} + +fn setup_funded_society, I: 'static>() -> Result { + let founder = setup_society::()?; + fund_society::(); + Ok(founder) +} + +fn add_candidate, I: 'static>( + name: &'static str, + tally: Tally, + skeptic_struck: bool, +) -> T::AccountId { + let candidate: T::AccountId = account(name, 0, 0); + let candidacy = Candidacy { + round: RoundCount::::get(), + kind: make_bid::(&candidate), + bid: 0u32.into(), + tally, + skeptic_struck, + }; + Candidates::::insert(&candidate, &candidacy); + candidate +} + +fn increment_round, I: 'static>() { + let mut round_count = RoundCount::::get(); + round_count.saturating_inc(); + RoundCount::::put(round_count); +} + +benchmarks_instance_pallet! { + bid { + let founder = setup_society::()?; + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + }: _(RawOrigin::Signed(caller.clone()), 10u32.into()) + verify { + let first_bid: Bid> = Bid { + who: caller.clone(), + kind: BidKind::Deposit(mock_balance_deposit::()), + value: 10u32.into(), + }; + assert_eq!(Bids::::get(), vec![first_bid]); + } + + unbid { + let founder = setup_society::()?; + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let mut bids = Bids::::get(); + Society::::insert_bid(&mut bids, &caller, 10u32.into(), make_bid::(&caller)); + Bids::::put(bids); + }: _(RawOrigin::Signed(caller.clone())) + verify { + assert_eq!(Bids::::get(), vec![]); + } + + vouch { + let founder = setup_society::()?; + let caller: T::AccountId = whitelisted_caller(); + let vouched: T::AccountId = account("vouched", 0, 0); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let _ = Society::::insert_member(&caller, 1u32.into()); + let vouched_lookup: ::Source = T::Lookup::unlookup(vouched.clone()); + }: _(RawOrigin::Signed(caller.clone()), vouched_lookup, 0u32.into(), 0u32.into()) + verify { + let bids = Bids::::get(); + let vouched_bid: Bid> = Bid { + who: vouched.clone(), + kind: BidKind::Vouch(caller.clone(), 0u32.into()), + value: 0u32.into(), + }; + assert_eq!(bids, vec![vouched_bid]); + } + + unvouch { + let founder = setup_society::()?; + let caller: T::AccountId = whitelisted_caller(); + let vouched: T::AccountId = account("vouched", 0, 0); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let mut bids = Bids::::get(); + Society::::insert_bid(&mut bids, &caller, 10u32.into(), BidKind::Vouch(caller.clone(), 0u32.into())); + Bids::::put(bids); + }: _(RawOrigin::Signed(caller.clone())) + verify { + assert_eq!(Bids::::get(), vec![]); + } + + vote { + let founder = setup_society::()?; + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let _ = Society::::insert_member(&caller, 1u32.into()); + let candidate = add_candidate::("candidate", Default::default(), false); + let candidate_lookup: ::Source = T::Lookup::unlookup(candidate.clone()); + }: _(RawOrigin::Signed(caller.clone()), candidate_lookup, true) + verify { + let maybe_vote: Vote = >::get(candidate.clone(), caller).unwrap(); + assert_eq!(maybe_vote.approve, true); + } + + defender_vote { + let founder = setup_society::()?; + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let _ = Society::::insert_member(&caller, 1u32.into()); + let defender: T::AccountId = account("defender", 0, 0); + Defending::::put((defender, caller.clone(), Tally::default())); + }: _(RawOrigin::Signed(caller.clone()), false) + verify { + let round = RoundCount::::get(); + let skeptic_vote: Vote = DefenderVotes::::get(round, &caller).unwrap(); + assert_eq!(skeptic_vote.approve, false); + } + + payout { + let founder = setup_funded_society::()?; + // Payee's account already exists and is a member. + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, mock_balance_deposit::()); + let _ = Society::::insert_member(&caller, 0u32.into()); + // Introduce payout. + Society::::bump_payout(&caller, 0u32.into(), 1u32.into()); + }: _(RawOrigin::Signed(caller.clone())) + verify { + let record = Payouts::::get(caller); + assert!(record.payouts.is_empty()); + } + + waive_repay { + let founder = setup_funded_society::()?; + let caller: T::AccountId = whitelisted_caller(); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + let _ = Society::::insert_member(&caller, 0u32.into()); + Society::::bump_payout(&caller, 0u32.into(), 1u32.into()); + }: _(RawOrigin::Signed(caller.clone()), 1u32.into()) + verify { + let record = Payouts::::get(caller); + assert!(record.payouts.is_empty()); + } + + found_society { + let founder: T::AccountId = whitelisted_caller(); + let can_found = T::FounderSetOrigin::try_successful_origin().map_err(|_| "No origin")?; + let founder_lookup: ::Source = T::Lookup::unlookup(founder.clone()); + }: _(can_found, founder_lookup, 5, 3, 3, mock_balance_deposit::(), b"benchmarking-society".to_vec()) + verify { + assert_eq!(Founder::::get(), Some(founder.clone())); + } + + dissolve { + let founder = setup_society::()?; + let members_and_candidates = vec![("m1", "c1"), ("m2", "c2"), ("m3", "c3"), ("m4", "c4")]; + let members_count = members_and_candidates.clone().len() as u32; + for (m, c) in members_and_candidates { + let member: T::AccountId = account(m, 0, 0); + let _ = Society::::insert_member(&member, 100u32.into()); + let candidate = add_candidate::(c, Tally { approvals: 1u32.into(), rejections: 1u32.into() }, false); + let candidate_lookup: ::Source = T::Lookup::unlookup(candidate); + let _ = Society::::vote(RawOrigin::Signed(member).into(), candidate_lookup, true); + } + // Leaving only Founder member. + MemberCount::::mutate(|i| { i.saturating_reduce(members_count) }); + }: _(RawOrigin::Signed(founder)) + verify { + assert_eq!(Founder::::get(), None); + } + + judge_suspended_member { + let founder = setup_society::()?; + let caller: T::AccountId = whitelisted_caller(); + let caller_lookup: ::Source = T::Lookup::unlookup(caller.clone()); + let _ = Society::::insert_member(&caller, 0u32.into()); + let _ = Society::::suspend_member(&caller); + }: _(RawOrigin::Signed(founder), caller_lookup, false) + verify { + assert_eq!(SuspendedMembers::::contains_key(&caller), false); + } + + set_parameters { + let founder = setup_society::()?; + let max_members = 10u32; + let max_intake = 10u32; + let max_strikes = 10u32; + let candidate_deposit: BalanceOf = 10u32.into(); + let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit }; + }: _(RawOrigin::Signed(founder), max_members, max_intake, max_strikes, candidate_deposit) + verify { + assert_eq!(Parameters::::get(), Some(params)); + } + + punish_skeptic { + let founder = setup_society::()?; + let candidate = add_candidate::("candidate", Default::default(), false); + let skeptic: T::AccountId = account("skeptic", 0, 0); + let _ = Society::::insert_member(&skeptic, 0u32.into()); + Skeptic::::put(&skeptic); + if let Period::Voting { more, .. } = Society::::period() { + frame_system::Pallet::::set_block_number(frame_system::Pallet::::block_number() + more); + } + }: _(RawOrigin::Signed(candidate.clone())) + verify { + let candidacy = Candidates::::get(&candidate).unwrap(); + assert_eq!(candidacy.skeptic_struck, true); + } + + claim_membership { + let founder = setup_society::()?; + let candidate = add_candidate::("candidate", Tally { approvals: 3u32.into(), rejections: 0u32.into() }, false); + increment_round::(); + }: _(RawOrigin::Signed(candidate.clone())) + verify { + assert!(!Candidates::::contains_key(&candidate)); + assert!(Members::::contains_key(&candidate)); + } + + bestow_membership { + let founder = setup_society::()?; + let candidate = add_candidate::("candidate", Tally { approvals: 3u32.into(), rejections: 1u32.into() }, false); + increment_round::(); + }: _(RawOrigin::Signed(founder), candidate.clone()) + verify { + assert!(!Candidates::::contains_key(&candidate)); + assert!(Members::::contains_key(&candidate)); + } + + kick_candidate { + let founder = setup_society::()?; + let candidate = add_candidate::("candidate", Tally { approvals: 1u32.into(), rejections: 1u32.into() }, false); + increment_round::(); + }: _(RawOrigin::Signed(founder), candidate.clone()) + verify { + assert!(!Candidates::::contains_key(&candidate)); + } + + resign_candidacy { + let founder = setup_society::()?; + let candidate = add_candidate::("candidate", Tally { approvals: 0u32.into(), rejections: 0u32.into() }, false); + }: _(RawOrigin::Signed(candidate.clone())) + verify { + assert!(!Candidates::::contains_key(&candidate)); + } + + drop_candidate { + let founder = setup_society::()?; + let candidate = add_candidate::("candidate", Tally { approvals: 0u32.into(), rejections: 3u32.into() }, false); + let caller: T::AccountId = whitelisted_caller(); + let _ = Society::::insert_member(&caller, 0u32.into()); + let mut round_count = RoundCount::::get(); + round_count = round_count.saturating_add(2u32); + RoundCount::::put(round_count); + }: _(RawOrigin::Signed(caller), candidate.clone()) + verify { + assert!(!Candidates::::contains_key(&candidate)); + } + + cleanup_candidacy { + let founder = setup_society::()?; + let candidate = add_candidate::("candidate", Tally { approvals: 0u32.into(), rejections: 0u32.into() }, false); + let member_one: T::AccountId = account("one", 0, 0); + let member_two: T::AccountId = account("two", 0, 0); + let _ = Society::::insert_member(&member_one, 0u32.into()); + let _ = Society::::insert_member(&member_two, 0u32.into()); + let candidate_lookup: ::Source = T::Lookup::unlookup(candidate.clone()); + let _ = Society::::vote(RawOrigin::Signed(member_one.clone()).into(), candidate_lookup.clone(), true); + let _ = Society::::vote(RawOrigin::Signed(member_two.clone()).into(), candidate_lookup, true); + Candidates::::remove(&candidate); + }: _(RawOrigin::Signed(member_one), candidate.clone(), 5) + verify { + assert_eq!(Votes::::get(&candidate, &member_two), None); + } + + cleanup_challenge { + let founder = setup_society::()?; + ChallengeRoundCount::::put(1u32); + let member: T::AccountId = whitelisted_caller(); + let _ = Society::::insert_member(&member, 0u32.into()); + let defender: T::AccountId = account("defender", 0, 0); + Defending::::put((defender.clone(), member.clone(), Tally::default())); + let _ = Society::::defender_vote(RawOrigin::Signed(member.clone()).into(), true); + ChallengeRoundCount::::put(2u32); + let mut challenge_round = ChallengeRoundCount::::get(); + challenge_round = challenge_round.saturating_sub(1u32); + }: _(RawOrigin::Signed(member.clone()), challenge_round, 1u32) + verify { + assert_eq!(DefenderVotes::::get(challenge_round, &defender), None); + } + + impl_benchmark_test_suite!( + Society, + sp_io::TestExternalities::from( + as sp_runtime::BuildStorage>::build_storage( + &frame_system::GenesisConfig::default()).unwrap() + ), + crate::mock::Test + ); +} diff --git a/frame/society/src/lib.rs b/frame/society/src/lib.rs index 0edf00ff80f6..ca8d96e193c8 100644 --- a/frame/society/src/lib.rs +++ b/frame/society/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,7 +30,6 @@ //! At any point, a user in the society can be one of a: //! * Bidder - A user who has submitted intention of joining the society. //! * Candidate - A user who will be voted on to join the society. -//! * Suspended Candidate - A user who failed to win a vote. //! * Member - A user who is a member of the society. //! * Suspended Member - A member of the society who has accumulated too many strikes //! or failed their membership challenge. @@ -251,11 +250,21 @@ mod mock; #[cfg(test)] mod tests; +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; + +pub mod weights; + +pub mod migrations; + use frame_support::{ + impl_ensure_origin_with_arg_ignoring_arg, pallet_prelude::*, + storage::KeyLenOf, traits::{ - BalanceStatus, ChangeMembers, Currency, EnsureOrigin, ExistenceRequirement::AllowDeath, - Imbalance, OnUnbalanced, Randomness, ReservableCurrency, + BalanceStatus, Currency, EnsureOrigin, EnsureOriginWithArg, + ExistenceRequirement::AllowDeath, Imbalance, OnUnbalanced, Randomness, ReservableCurrency, + StorageVersion, }, PalletId, }; @@ -267,13 +276,16 @@ use rand_chacha::{ use scale_info::TypeInfo; use sp_runtime::{ traits::{ - AccountIdConversion, CheckedSub, Hash, IntegerSquareRoot, Saturating, StaticLookup, + AccountIdConversion, CheckedAdd, CheckedSub, Hash, Saturating, StaticLookup, TrailingZeroInput, Zero, }, + ArithmeticError::Overflow, Percent, RuntimeDebug, }; use sp_std::prelude::*; +pub use weights::WeightInfo; + pub use pallet::*; type BalanceOf = @@ -283,15 +295,10 @@ type NegativeImbalanceOf = <>::Currency as Currency< >>::NegativeImbalance; type AccountIdLookupOf = <::Lookup as StaticLookup>::Source; -/// A vote by a member on a candidate application. #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] -pub enum Vote { - /// The member has been chosen to be skeptic and has not yet taken any action. - Skeptic, - /// The member has rejected the candidate's application. - Reject, - /// The member approves of the candidate's application. - Approve, +pub struct Vote { + approve: bool, + weight: u32, } /// A judgement by the suspension judgement origin on a suspended candidate. @@ -342,10 +349,61 @@ pub struct Bid { value: Balance, } +/// The index of a round of candidates. +pub type RoundIndex = u32; + +/// The rank of a member. +pub type Rank = u32; + +/// The number of votes. +pub type VoteCount = u32; + +/// Tally of votes. +#[derive(Default, Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct Tally { + /// The approval votes. + approvals: VoteCount, + /// The rejection votes. + rejections: VoteCount, +} + +impl Tally { + fn more_approvals(&self) -> bool { + self.approvals > self.rejections + } + + fn more_rejections(&self) -> bool { + self.rejections > self.approvals + } + + fn clear_approval(&self) -> bool { + self.approvals >= (2 * self.rejections).max(1) + } + + fn clear_rejection(&self) -> bool { + self.rejections >= (2 * self.approvals).max(1) + } +} + +/// A bid for entry into society. +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct Candidacy { + /// The index of the round where the candidacy began. + round: RoundIndex, + /// The kind of bid placed for this bidder/candidate. See `BidKind`. + kind: BidKind, + /// The reward that the bidder has requested for successfully joining the society. + bid: Balance, + /// The tally of votes so far. + tally: Tally, + /// True if the skeptic was already punished for note voting. + skeptic_struck: bool, +} + /// A vote by a member on a candidate application. #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] pub enum BidKind { - /// The CandidateDeposit was paid for this bid. + /// The given deposit was paid for this bid. Deposit(Balance), /// A member vouched for this bid. The account should be reinstated into `Members` once the /// bid is successful (or if it is rescinded prior to launch). @@ -353,25 +411,64 @@ pub enum BidKind { } impl BidKind { - fn check_voucher(&self, v: &AccountId) -> DispatchResult { - if let BidKind::Vouch(ref a, _) = self { - if a == v { - Ok(()) - } else { - Err("incorrect identity".into()) - } - } else { - Err("not vouched".into()) - } + fn is_vouch(&self, v: &AccountId) -> bool { + matches!(self, BidKind::Vouch(ref a, _) if a == v) } } +pub type PayoutsFor = + BoundedVec<(BlockNumberFor, BalanceOf), >::MaxPayouts>; + +/// Information concerning a member. +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct MemberRecord { + rank: Rank, + strikes: StrikeCount, + vouching: Option, + index: u32, +} + +/// Information concerning a member. +#[derive(Encode, Decode, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo, Default)] +pub struct PayoutRecord { + paid: Balance, + payouts: PayoutsVec, +} + +pub type PayoutRecordFor = PayoutRecord< + BalanceOf, + BoundedVec<(BlockNumberFor, BalanceOf), >::MaxPayouts>, +>; + +/// Record for an individual new member who was elevated from a candidate recently. +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct IntakeRecord { + who: AccountId, + bid: Balance, + round: RoundIndex, +} + +pub type IntakeRecordFor = + IntakeRecord<::AccountId, BalanceOf>; + +#[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] +pub struct GroupParams { + max_members: u32, + max_intake: u32, + max_strikes: u32, + candidate_deposit: Balance, +} + +pub type GroupParamsFor = GroupParams>; + +pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); + #[frame_support::pallet] pub mod pallet { use super::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] #[pallet::without_storage_info] pub struct Pallet(_); @@ -389,56 +486,51 @@ pub mod pallet { type Currency: ReservableCurrency; /// Something that provides randomness in the runtime. - type Randomness: Randomness; - - /// The minimum amount of a deposit required for a bid to be made. - #[pallet::constant] - type CandidateDeposit: Get>; + type Randomness: Randomness>; - /// The amount of the unpaid reward that gets deducted in the case that either a skeptic - /// doesn't vote or someone votes in the wrong way. + /// The maximum number of strikes before a member gets funds slashed. #[pallet::constant] - type WrongSideDeduction: Get>; - - /// The number of times a member may vote the wrong way (or not at all, when they are a - /// skeptic) before they become suspended. - #[pallet::constant] - type MaxStrikes: Get; + type GraceStrikes: Get; /// The amount of incentive paid within each period. Doesn't include VoterTip. #[pallet::constant] type PeriodSpend: Get>; - /// The receiver of the signal for when the members have changed. - type MembershipChanged: ChangeMembers; + /// The number of blocks on which new candidates should be voted on. Together with + /// `ClaimPeriod`, this sums to the number of blocks between candidate intake periods. + #[pallet::constant] + type VotingPeriod: Get>; - /// The number of blocks between candidate/membership rotation periods. + /// The number of blocks on which new candidates can claim their membership and be the + /// named head. #[pallet::constant] - type RotationPeriod: Get; + type ClaimPeriod: Get>; /// The maximum duration of the payout lock. #[pallet::constant] - type MaxLockDuration: Get; + type MaxLockDuration: Get>; /// The origin that is allowed to call `found`. type FounderSetOrigin: EnsureOrigin; - /// The origin that is allowed to make suspension judgements. - type SuspensionJudgementOrigin: EnsureOrigin; - /// The number of blocks between membership challenges. #[pallet::constant] - type ChallengePeriod: Get; + type ChallengePeriod: Get>; + + /// The maximum number of payouts a member may have waiting unclaimed. + #[pallet::constant] + type MaxPayouts: Get; - /// The maximum number of candidates that we accept per round. + /// The maximum number of bids at once. #[pallet::constant] - type MaxCandidateIntake: Get; + type MaxBids: Get; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; } #[pallet::error] pub enum Error { - /// An incorrect position was provided. - BadPosition, /// User is not a member. NotMember, /// User is already a member. @@ -456,7 +548,7 @@ pub mod pallet { /// Member is already vouching or banned from vouching again. AlreadyVouching, /// Member is not vouching. - NotVouching, + NotVouchingOnBidder, /// Cannot remove the head of the chain. Head, /// Cannot remove the founder. @@ -473,6 +565,36 @@ pub mod pallet { NotFounder, /// The caller is not the head. NotHead, + /// The membership cannot be claimed as the candidate was not clearly approved. + NotApproved, + /// The candidate cannot be kicked as the candidate was not clearly rejected. + NotRejected, + /// The candidacy cannot be dropped as the candidate was clearly approved. + Approved, + /// The candidacy cannot be bestowed as the candidate was clearly rejected. + Rejected, + /// The candidacy cannot be concluded as the voting is still in progress. + InProgress, + /// The candidacy cannot be pruned until a full additional intake period has passed. + TooEarly, + /// The skeptic already voted. + Voted, + /// The skeptic need not vote on candidates from expired rounds. + Expired, + /// User is not a bidder. + NotBidder, + /// There is no defender currently. + NoDefender, + /// Group doesn't exist. + NotGroup, + /// The member is already elevated to this rank. + AlreadyElevated, + /// The skeptic has already been punished for this offence. + AlreadyPunished, + /// Funds are insufficient to pay off society debts. + InsufficientFunds, + /// The candidate/defender has no stale votes to remove. + NoVotes, } #[pallet::event] @@ -507,134 +629,157 @@ pub mod pallet { Vote { candidate: T::AccountId, voter: T::AccountId, vote: bool }, /// A vote has been placed for a defending member DefenderVote { voter: T::AccountId, vote: bool }, - /// A new \[max\] member count has been set - NewMaxMembers { max: u32 }, + /// A new set of \[params\] has been set for the group. + NewParams { params: GroupParamsFor }, /// Society is unfounded. Unfounded { founder: T::AccountId }, /// Some funds were deposited into the society account. Deposit { value: BalanceOf }, + /// A \[member\] got elevated to \[rank\]. + Elevated { member: T::AccountId, rank: Rank }, } + /// Old name generated by `decl_event`. + #[deprecated(note = "use `Event` instead")] + pub type RawEvent = Event; + + /// The max number of members for the society at one time. + #[pallet::storage] + pub(super) type Parameters, I: 'static = ()> = + StorageValue<_, GroupParamsFor, OptionQuery>; + + /// Amount of our account balance that is specifically for the next round's bid(s). + #[pallet::storage] + pub type Pot, I: 'static = ()> = StorageValue<_, BalanceOf, ValueQuery>; + /// The first member. #[pallet::storage] - #[pallet::getter(fn founder)] pub type Founder, I: 'static = ()> = StorageValue<_, T::AccountId>; + /// The most primary from the most recently approved rank 0 members in the society. + #[pallet::storage] + pub type Head, I: 'static = ()> = StorageValue<_, T::AccountId>; + /// A hash of the rules of this society concerning membership. Can only be set once and /// only by the founder. #[pallet::storage] - #[pallet::getter(fn rules)] pub type Rules, I: 'static = ()> = StorageValue<_, T::Hash>; - /// The current set of candidates; bidders that are attempting to become members. + /// The current members and their rank. Doesn't include `SuspendedMembers`. #[pallet::storage] - #[pallet::getter(fn candidates)] - pub type Candidates, I: 'static = ()> = - StorageValue<_, Vec>>, ValueQuery>; + pub type Members, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, MemberRecord, OptionQuery>; - /// The set of suspended candidates. + /// Information regarding rank-0 payouts, past and future. #[pallet::storage] - #[pallet::getter(fn suspended_candidate)] - pub type SuspendedCandidates, I: 'static = ()> = StorageMap< - _, - Twox64Concat, - T::AccountId, - (BalanceOf, BidKind>), - >; + pub type Payouts, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, PayoutRecordFor, ValueQuery>; - /// Amount of our account balance that is specifically for the next round's bid(s). + /// The number of items in `Members` currently. (Doesn't include `SuspendedMembers`.) #[pallet::storage] - #[pallet::getter(fn pot)] - pub type Pot, I: 'static = ()> = StorageValue<_, BalanceOf, ValueQuery>; + pub type MemberCount, I: 'static = ()> = StorageValue<_, u32, ValueQuery>; - /// The most primary from the most recently approved members. + /// The current items in `Members` keyed by their unique index. Keys are densely populated + /// `0..MemberCount` (does not include `MemberCount`). #[pallet::storage] - #[pallet::getter(fn head)] - pub type Head, I: 'static = ()> = StorageValue<_, T::AccountId>; + pub type MemberByIndex, I: 'static = ()> = + StorageMap<_, Twox64Concat, u32, T::AccountId, OptionQuery>; - /// The current set of members, ordered. + /// The set of suspended members, with their old membership record. #[pallet::storage] - #[pallet::getter(fn members)] - pub type Members, I: 'static = ()> = - StorageValue<_, Vec, ValueQuery>; + pub type SuspendedMembers, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, MemberRecord, OptionQuery>; - /// The set of suspended members. + /// The number of rounds which have passed. #[pallet::storage] - #[pallet::getter(fn suspended_member)] - pub type SuspendedMembers, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, bool, ValueQuery>; + pub type RoundCount, I: 'static = ()> = StorageValue<_, RoundIndex, ValueQuery>; /// The current bids, stored ordered by the value of the bid. #[pallet::storage] pub(super) type Bids, I: 'static = ()> = - StorageValue<_, Vec>>, ValueQuery>; + StorageValue<_, BoundedVec>, T::MaxBids>, ValueQuery>; + + #[pallet::storage] + pub type Candidates, I: 'static = ()> = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + Candidacy>, + OptionQuery, + >; - /// Members currently vouching or banned from vouching again + /// The current skeptic. #[pallet::storage] - #[pallet::getter(fn vouching)] - pub(super) type Vouching, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, VouchingStatus>; + pub type Skeptic, I: 'static = ()> = StorageValue<_, T::AccountId, OptionQuery>; - /// Pending payouts; ordered by block number, with the amount that should be paid out. + /// Double map from Candidate -> Voter -> (Maybe) Vote. #[pallet::storage] - pub(super) type Payouts, I: 'static = ()> = StorageMap< + pub(super) type Votes, I: 'static = ()> = StorageDoubleMap< _, Twox64Concat, T::AccountId, - Vec<(T::BlockNumber, BalanceOf)>, - ValueQuery, + Twox64Concat, + T::AccountId, + Vote, + OptionQuery, >; - /// The ongoing number of losing votes cast by the member. + /// Clear-cursor for Vote, map from Candidate -> (Maybe) Cursor. #[pallet::storage] - pub(super) type Strikes, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, StrikeCount, ValueQuery>; + pub(super) type VoteClearCursor, I: 'static = ()> = + StorageMap<_, Twox64Concat, T::AccountId, BoundedVec>>>; - /// Double map from Candidate -> Voter -> (Maybe) Vote. + /// At the end of the claim period, this contains the most recently approved members (along with + /// their bid and round ID) who is from the most recent round with the lowest bid. They will + /// become the new `Head`. #[pallet::storage] - pub(super) type Votes, I: 'static = ()> = - StorageDoubleMap<_, Twox64Concat, T::AccountId, Twox64Concat, T::AccountId, Vote>; + pub type NextHead, I: 'static = ()> = + StorageValue<_, IntakeRecordFor, OptionQuery>; - /// The defending member currently being challenged. + /// The number of challenge rounds there have been. Used to identify stale DefenderVotes. #[pallet::storage] - #[pallet::getter(fn defender)] - pub(super) type Defender, I: 'static = ()> = StorageValue<_, T::AccountId>; + pub(super) type ChallengeRoundCount, I: 'static = ()> = + StorageValue<_, RoundIndex, ValueQuery>; - /// Votes for the defender. + /// The defending member currently being challenged, along with a running tally of votes. #[pallet::storage] - pub(super) type DefenderVotes, I: 'static = ()> = - StorageMap<_, Twox64Concat, T::AccountId, Vote>; + pub(super) type Defending, I: 'static = ()> = + StorageValue<_, (T::AccountId, T::AccountId, Tally)>; - /// The max number of members for the society at one time. + /// Votes for the defender, keyed by challenge round. #[pallet::storage] - #[pallet::getter(fn max_members)] - pub(super) type MaxMembers, I: 'static = ()> = StorageValue<_, u32, ValueQuery>; + pub(super) type DefenderVotes, I: 'static = ()> = + StorageDoubleMap<_, Twox64Concat, RoundIndex, Twox64Concat, T::AccountId, Vote>; #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { - fn on_initialize(n: T::BlockNumber) -> Weight { - let mut members = vec![]; - + fn on_initialize(n: BlockNumberFor) -> Weight { let mut weight = Weight::zero(); let weights = T::BlockWeights::get(); - // Run a candidate/membership rotation - if (n % T::RotationPeriod::get()).is_zero() { - members = >::get(); - Self::rotate_period(&mut members); + let phrase = b"society_rotation"; + // we'll need a random seed here. + // TODO: deal with randomness freshness + // https://github.com/paritytech/substrate/issues/8312 + let (seed, _) = T::Randomness::random(phrase); + // seed needs to be guaranteed to be 32 bytes. + let seed = <[u8; 32]>::decode(&mut TrailingZeroInput::new(seed.as_ref())) + .expect("input is padded with zeroes; qed"); + let mut rng = ChaChaRng::from_seed(seed); - weight += weights.max_block / 20; + // Run a candidate/membership rotation + match Self::period() { + Period::Voting { elapsed, .. } if elapsed.is_zero() => { + Self::rotate_intake(&mut rng); + weight.saturating_accrue(weights.max_block / 20); + }, + _ => {}, } // Run a challenge rotation if (n % T::ChallengePeriod::get()).is_zero() { - // Only read members if not already read. - if members.is_empty() { - members = >::get(); - } - Self::rotate_challenge(&mut members); - - weight += weights.max_block / 20; + Self::rotate_challenge(&mut rng); + weight.saturating_accrue(weights.max_block / 20); } weight @@ -642,36 +787,15 @@ pub mod pallet { } #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig, I: 'static = ()> { pub pot: BalanceOf, - pub members: Vec, - pub max_members: u32, - } - - #[cfg(feature = "std")] - impl, I: 'static> Default for GenesisConfig { - fn default() -> Self { - Self { - pot: Default::default(), - members: Default::default(), - max_members: Default::default(), - } - } } #[pallet::genesis_build] - impl, I: 'static> GenesisBuild for GenesisConfig { + impl, I: 'static> BuildGenesisConfig for GenesisConfig { fn build(&self) { Pot::::put(self.pot); - MaxMembers::::put(self.max_members); - let first_member = self.members.first(); - if let Some(member) = first_member { - Founder::::put(member.clone()); - Head::::put(member.clone()); - }; - let mut m = self.members.clone(); - m.sort(); - Members::::put(m); } } @@ -679,55 +803,31 @@ pub mod pallet { impl, I: 'static> Pallet { /// A user outside of the society can make a bid for entry. /// - /// Payment: `CandidateDeposit` will be reserved for making a bid. It is returned + /// Payment: The group's Candidate Deposit will be reserved for making a bid. It is returned /// when the bid becomes a member, or if the bid calls `unbid`. /// /// The dispatch origin for this call must be _Signed_. /// /// Parameters: /// - `value`: A one time payment the bid would like to receive when joining the society. - /// - /// # - /// Key: B (len of bids), C (len of candidates), M (len of members), X (balance reserve) - /// - Storage Reads: - /// - One storage read to check for suspended candidate. O(1) - /// - One storage read to check for suspended member. O(1) - /// - One storage read to retrieve all current bids. O(B) - /// - One storage read to retrieve all current candidates. O(C) - /// - One storage read to retrieve all members. O(M) - /// - Storage Writes: - /// - One storage mutate to add a new bid to the vector O(B) (TODO: possible optimization - /// w/ read) - /// - Up to one storage removal if bid.len() > MAX_BID_COUNT. O(1) - /// - Notable Computation: - /// - O(B + C + log M) search to check user is not already a part of society. - /// - O(log B) search to insert the new bid sorted. - /// - External Pallet Operations: - /// - One balance reserve operation. O(X) - /// - Up to one balance unreserve operation if bids.len() > MAX_BID_COUNT. - /// - Events: - /// - One event for new bid. - /// - Up to one event for AutoUnbid if bid.len() > MAX_BID_COUNT. - /// - /// Total Complexity: O(M + B + C + logM + logB + X) - /// # #[pallet::call_index(0)] - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::bid())] pub fn bid(origin: OriginFor, value: BalanceOf) -> DispatchResult { let who = ensure_signed(origin)?; - ensure!(!>::contains_key(&who), Error::::Suspended); - ensure!(!>::contains_key(&who), Error::::Suspended); - let bids = >::get(); - ensure!(!Self::is_bid(&bids, &who), Error::::AlreadyBid); - let candidates = >::get(); - ensure!(!Self::is_candidate(&candidates, &who), Error::::AlreadyCandidate); - let members = >::get(); - ensure!(!Self::is_member(&members, &who), Error::::AlreadyMember); - - let deposit = T::CandidateDeposit::get(); + + let mut bids = Bids::::get(); + ensure!(!Self::has_bid(&bids, &who), Error::::AlreadyBid); + ensure!(!Candidates::::contains_key(&who), Error::::AlreadyCandidate); + ensure!(!Members::::contains_key(&who), Error::::AlreadyMember); + ensure!(!SuspendedMembers::::contains_key(&who), Error::::Suspended); + + let params = Parameters::::get().ok_or(Error::::NotGroup)?; + let deposit = params.candidate_deposit; + // NOTE: Reserve must happen before `insert_bid` since that could end up unreserving. T::Currency::reserve(&who, deposit)?; + Self::insert_bid(&mut bids, &who, value, BidKind::Deposit(deposit)); - Self::put_bid(bids, &who, value, BidKind::Deposit(deposit)); + Bids::::put(bids); Self::deposit_event(Event::::Bid { candidate_id: who, offer: value }); Ok(()) } @@ -739,44 +839,17 @@ pub mod pallet { /// Payment: The bid deposit is unreserved if the user made a bid. /// /// The dispatch origin for this call must be _Signed_ and a bidder. - /// - /// Parameters: - /// - `pos`: Position in the `Bids` vector of the bid who wants to unbid. - /// - /// # - /// Key: B (len of bids), X (balance unreserve) - /// - One storage read and write to retrieve and update the bids. O(B) - /// - Either one unreserve balance action O(X) or one vouching storage removal. O(1) - /// - One event. - /// - /// Total Complexity: O(B + X) - /// # #[pallet::call_index(1)] - #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn unbid(origin: OriginFor, pos: u32) -> DispatchResult { + #[pallet::weight(T::WeightInfo::unbid())] + pub fn unbid(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let pos = pos as usize; - >::mutate(|b| { - if pos < b.len() && b[pos].who == who { - // Either unreserve the deposit or free up the vouching member. - // In neither case can we do much if the action isn't completable, but there's - // no reason that either should fail. - match b.remove(pos).kind { - BidKind::Deposit(deposit) => { - let err_amount = T::Currency::unreserve(&who, deposit); - debug_assert!(err_amount.is_zero()); - }, - BidKind::Vouch(voucher, _) => { - >::remove(&voucher); - }, - } - Self::deposit_event(Event::::Unbid { candidate: who }); - Ok(()) - } else { - Err(Error::::BadPosition.into()) - } - }) + let mut bids = Bids::::get(); + let pos = bids.iter().position(|bid| bid.who == who).ok_or(Error::::NotBidder)?; + Self::clean_bid(&bids.remove(pos)); + Bids::::put(bids); + Self::deposit_event(Event::::Unbid { candidate: who }); + Ok(()) } /// As a member, vouch for someone to join society by placing a bid on their behalf. @@ -796,36 +869,8 @@ pub mod pallet { /// a member in the society. /// - `tip`: Your cut of the total `value` payout when the candidate is inducted into /// the society. Tips larger than `value` will be saturated upon payout. - /// - /// # - /// Key: B (len of bids), C (len of candidates), M (len of members) - /// - Storage Reads: - /// - One storage read to retrieve all members. O(M) - /// - One storage read to check member is not already vouching. O(1) - /// - One storage read to check for suspended candidate. O(1) - /// - One storage read to check for suspended member. O(1) - /// - One storage read to retrieve all current bids. O(B) - /// - One storage read to retrieve all current candidates. O(C) - /// - Storage Writes: - /// - One storage write to insert vouching status to the member. O(1) - /// - One storage mutate to add a new bid to the vector O(B) (TODO: possible optimization - /// w/ read) - /// - Up to one storage removal if bid.len() > MAX_BID_COUNT. O(1) - /// - Notable Computation: - /// - O(log M) search to check sender is a member. - /// - O(B + C + log M) search to check user is not already a part of society. - /// - O(log B) search to insert the new bid sorted. - /// - External Pallet Operations: - /// - One balance reserve operation. O(X) - /// - Up to one balance unreserve operation if bids.len() > MAX_BID_COUNT. - /// - Events: - /// - One event for vouch. - /// - Up to one event for AutoUnbid if bid.len() > MAX_BID_COUNT. - /// - /// Total Complexity: O(M + B + C + logM + logB + X) - /// # #[pallet::call_index(2)] - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::vouch())] pub fn vouch( origin: OriginFor, who: AccountIdLookupOf, @@ -834,23 +879,28 @@ pub mod pallet { ) -> DispatchResult { let voucher = ensure_signed(origin)?; let who = T::Lookup::lookup(who)?; - // Check user is not suspended. - ensure!(!>::contains_key(&who), Error::::Suspended); - ensure!(!>::contains_key(&who), Error::::Suspended); - // Check user is not a bid or candidate. - let bids = >::get(); - ensure!(!Self::is_bid(&bids, &who), Error::::AlreadyBid); - let candidates = >::get(); - ensure!(!Self::is_candidate(&candidates, &who), Error::::AlreadyCandidate); - // Check user is not already a member. - let members = >::get(); - ensure!(!Self::is_member(&members, &who), Error::::AlreadyMember); + + // Get bids and check user is not bidding. + let mut bids = Bids::::get(); + ensure!(!Self::has_bid(&bids, &who), Error::::AlreadyBid); + + // Check user is not already a candidate, member or suspended member. + ensure!(!Candidates::::contains_key(&who), Error::::AlreadyCandidate); + ensure!(!Members::::contains_key(&who), Error::::AlreadyMember); + ensure!(!SuspendedMembers::::contains_key(&who), Error::::Suspended); + // Check sender can vouch. - ensure!(Self::is_member(&members, &voucher), Error::::NotMember); - ensure!(!>::contains_key(&voucher), Error::::AlreadyVouching); + let mut record = Members::::get(&voucher).ok_or(Error::::NotMember)?; + ensure!(record.vouching.is_none(), Error::::AlreadyVouching); - >::insert(&voucher, VouchingStatus::Vouching); - Self::put_bid(bids, &who, value, BidKind::Vouch(voucher.clone(), tip)); + // Update voucher record. + record.vouching = Some(VouchingStatus::Vouching); + // Update bids + Self::insert_bid(&mut bids, &who, value, BidKind::Vouch(voucher.clone(), tip)); + + // Write new state. + Members::::insert(&voucher, &record); + Bids::::put(bids); Self::deposit_event(Event::::Vouch { candidate_id: who, offer: value, @@ -866,37 +916,22 @@ pub mod pallet { /// /// Parameters: /// - `pos`: Position in the `Bids` vector of the bid who should be unvouched. - /// - /// # - /// Key: B (len of bids) - /// - One storage read O(1) to check the signer is a vouching member. - /// - One storage mutate to retrieve and update the bids. O(B) - /// - One vouching storage removal. O(1) - /// - One event. - /// - /// Total Complexity: O(B) - /// # #[pallet::call_index(3)] - #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn unvouch(origin: OriginFor, pos: u32) -> DispatchResult { + #[pallet::weight(T::WeightInfo::unvouch())] + pub fn unvouch(origin: OriginFor) -> DispatchResult { let voucher = ensure_signed(origin)?; - ensure!( - Self::vouching(&voucher) == Some(VouchingStatus::Vouching), - Error::::NotVouching - ); - let pos = pos as usize; - >::mutate(|b| { - if pos < b.len() { - b[pos].kind.check_voucher(&voucher)?; - >::remove(&voucher); - let who = b.remove(pos).who; - Self::deposit_event(Event::::Unvouch { candidate: who }); - Ok(()) - } else { - Err(Error::::BadPosition.into()) - } - }) + let mut bids = Bids::::get(); + let pos = bids + .iter() + .position(|bid| bid.kind.is_vouch(&voucher)) + .ok_or(Error::::NotVouchingOnBidder)?; + let bid = bids.remove(pos); + Self::clean_bid(&bid); + + Bids::::put(bids); + Self::deposit_event(Event::::Unvouch { candidate: bid.who }); + Ok(()) } /// As a member, vote on a candidate. @@ -907,36 +942,29 @@ pub mod pallet { /// - `candidate`: The candidate that the member would like to bid on. /// - `approve`: A boolean which says if the candidate should be approved (`true`) or /// rejected (`false`). - /// - /// # - /// Key: C (len of candidates), M (len of members) - /// - One storage read O(M) and O(log M) search to check user is a member. - /// - One account lookup. - /// - One storage read O(C) and O(C) search to check that user is a candidate. - /// - One storage write to add vote to votes. O(1) - /// - One event. - /// - /// Total Complexity: O(M + logM + C) - /// # #[pallet::call_index(4)] - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::vote())] pub fn vote( origin: OriginFor, candidate: AccountIdLookupOf, approve: bool, - ) -> DispatchResult { + ) -> DispatchResultWithPostInfo { let voter = ensure_signed(origin)?; let candidate = T::Lookup::lookup(candidate)?; - let candidates = >::get(); - ensure!(Self::is_candidate(&candidates, &candidate), Error::::NotCandidate); - let members = >::get(); - ensure!(Self::is_member(&members, &voter), Error::::NotMember); - let vote = if approve { Vote::Approve } else { Vote::Reject }; - >::insert(&candidate, &voter, vote); + let mut candidacy = + Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + let record = Members::::get(&voter).ok_or(Error::::NotMember)?; + + let first_time = Votes::::mutate(&candidate, &voter, |v| { + let first_time = v.is_none(); + *v = Some(Self::do_vote(*v, approve, record.rank, &mut candidacy.tally)); + first_time + }); + Candidates::::insert(&candidate, &candidacy); Self::deposit_event(Event::::Vote { candidate, voter, vote: approve }); - Ok(()) + Ok(if first_time { Pays::No } else { Pays::Yes }.into()) } /// As a member, vote on the defender. @@ -946,27 +974,24 @@ pub mod pallet { /// Parameters: /// - `approve`: A boolean which says if the candidate should be /// approved (`true`) or rejected (`false`). - /// - /// # - /// - Key: M (len of members) - /// - One storage read O(M) and O(log M) search to check user is a member. - /// - One storage write to add vote to votes. O(1) - /// - One event. - /// - /// Total Complexity: O(M + logM) - /// # #[pallet::call_index(5)] - #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn defender_vote(origin: OriginFor, approve: bool) -> DispatchResult { + #[pallet::weight(T::WeightInfo::defender_vote())] + pub fn defender_vote(origin: OriginFor, approve: bool) -> DispatchResultWithPostInfo { let voter = ensure_signed(origin)?; - let members = >::get(); - ensure!(Self::is_member(&members, &voter), Error::::NotMember); - let vote = if approve { Vote::Approve } else { Vote::Reject }; - >::insert(&voter, vote); + let mut defending = Defending::::get().ok_or(Error::::NoDefender)?; + let record = Members::::get(&voter).ok_or(Error::::NotMember)?; + let round = ChallengeRoundCount::::get(); + let first_time = DefenderVotes::::mutate(round, &voter, |v| { + let first_time = v.is_none(); + *v = Some(Self::do_vote(*v, approve, record.rank, &mut defending.2)); + first_time + }); + + Defending::::put(defending); Self::deposit_event(Event::::DefenderVote { voter, vote: approve }); - Ok(()) + Ok(if first_time { Pays::No } else { Pays::Yes }.into()) } /// Transfer the first matured payout for the sender and remove it from the records. @@ -979,39 +1004,48 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ and a member with /// payouts remaining. - /// - /// # - /// Key: M (len of members), P (number of payouts for a particular member) - /// - One storage read O(M) and O(log M) search to check signer is a member. - /// - One storage read O(P) to get all payouts for a member. - /// - One storage read O(1) to get the current block number. - /// - One currency transfer call. O(X) - /// - One storage write or removal to update the member's payouts. O(P) - /// - /// Total Complexity: O(M + logM + P + X) - /// # #[pallet::call_index(6)] - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::weight(T::WeightInfo::payout())] pub fn payout(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; + ensure!( + Members::::get(&who).ok_or(Error::::NotMember)?.rank == 0, + Error::::NoPayout + ); + let mut record = Payouts::::get(&who); - let members = >::get(); - ensure!(Self::is_member(&members, &who), Error::::NotMember); - - let mut payouts = >::get(&who); - if let Some((when, amount)) = payouts.first() { + if let Some((when, amount)) = record.payouts.first() { if when <= &>::block_number() { + record.paid = record.paid.checked_add(amount).ok_or(Overflow)?; T::Currency::transfer(&Self::payouts(), &who, *amount, AllowDeath)?; - payouts.remove(0); - if payouts.is_empty() { - >::remove(&who); - } else { - >::insert(&who, payouts); - } + record.payouts.remove(0); + Payouts::::insert(&who, record); return Ok(()) } } - Err(Error::::NoPayout.into()) + Err(Error::::NoPayout)? + } + + /// Repay the payment previously given to the member with the signed origin, remove any + /// pending payments, and elevate them from rank 0 to rank 1. + #[pallet::call_index(7)] + #[pallet::weight(T::WeightInfo::waive_repay())] + pub fn waive_repay(origin: OriginFor, amount: BalanceOf) -> DispatchResult { + let who = ensure_signed(origin)?; + let mut record = Members::::get(&who).ok_or(Error::::NotMember)?; + let mut payout_record = Payouts::::get(&who); + ensure!(record.rank == 0, Error::::AlreadyElevated); + ensure!(amount >= payout_record.paid, Error::::InsufficientFunds); + + T::Currency::transfer(&who, &Self::account_id(), payout_record.paid, AllowDeath)?; + payout_record.paid = Zero::zero(); + payout_record.payouts.clear(); + record.rank = 1; + Members::::insert(&who, record); + Payouts::::insert(&who, payout_record); + Self::deposit_event(Event::::Elevated { member: who, rank: 1 }); + + Ok(()) } /// Found the society. @@ -1024,64 +1058,71 @@ pub mod pallet { /// Parameters: /// - `founder` - The first member and head of the newly founded society. /// - `max_members` - The initial max number of members for the society. + /// - `max_intake` - The maximum number of candidates per intake period. + /// - `max_strikes`: The maximum number of strikes a member may get before they become + /// suspended and may only be reinstated by the founder. + /// - `candidate_deposit`: The deposit required to make a bid for membership of the group. /// - `rules` - The rules of this society concerning membership. /// - /// # - /// - Two storage mutates to set `Head` and `Founder`. O(1) - /// - One storage write to add the first member to society. O(1) - /// - One event. - /// - /// Total Complexity: O(1) - /// # - #[pallet::call_index(7)] - #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn found( + /// Complexity: O(1) + #[pallet::call_index(8)] + #[pallet::weight(T::WeightInfo::found_society())] + pub fn found_society( origin: OriginFor, founder: AccountIdLookupOf, max_members: u32, + max_intake: u32, + max_strikes: u32, + candidate_deposit: BalanceOf, rules: Vec, ) -> DispatchResult { T::FounderSetOrigin::ensure_origin(origin)?; let founder = T::Lookup::lookup(founder)?; - ensure!(!>::exists(), Error::::AlreadyFounded); + ensure!(!Head::::exists(), Error::::AlreadyFounded); ensure!(max_members > 1, Error::::MaxMembers); // This should never fail in the context of this function... - >::put(max_members); - Self::add_member(&founder)?; - >::put(&founder); - >::put(&founder); + let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit }; + Parameters::::put(params); + Self::insert_member(&founder, 1)?; + Head::::put(&founder); + Founder::::put(&founder); Rules::::put(T::Hashing::hash(&rules)); Self::deposit_event(Event::::Founded { founder }); Ok(()) } - /// Annul the founding of the society. + /// Dissolve the society and remove all members. /// /// The dispatch origin for this call must be Signed, and the signing account must be both /// the `Founder` and the `Head`. This implies that it may only be done when there is one /// member. - /// - /// # - /// - Two storage reads O(1). - /// - Four storage removals O(1). - /// - One event. - /// - /// Total Complexity: O(1) - /// # - #[pallet::call_index(8)] - #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn unfound(origin: OriginFor) -> DispatchResult { + #[pallet::call_index(9)] + #[pallet::weight(T::WeightInfo::dissolve())] + pub fn dissolve(origin: OriginFor) -> DispatchResult { let founder = ensure_signed(origin)?; - ensure!(Founder::::get() == Some(founder.clone()), Error::::NotFounder); - ensure!(Head::::get() == Some(founder.clone()), Error::::NotHead); - - Members::::kill(); + ensure!(Founder::::get().as_ref() == Some(&founder), Error::::NotFounder); + ensure!(MemberCount::::get() == 1, Error::::NotHead); + + let _ = Members::::clear(u32::MAX, None); + MemberCount::::kill(); + let _ = MemberByIndex::::clear(u32::MAX, None); + let _ = SuspendedMembers::::clear(u32::MAX, None); + let _ = Payouts::::clear(u32::MAX, None); + let _ = Votes::::clear(u32::MAX, None); + let _ = VoteClearCursor::::clear(u32::MAX, None); Head::::kill(); + NextHead::::kill(); Founder::::kill(); Rules::::kill(); - Candidates::::kill(); - #[allow(deprecated)] - SuspendedCandidates::::remove_all(None); + Parameters::::kill(); + Pot::::kill(); + RoundCount::::kill(); + Bids::::kill(); + Skeptic::::kill(); + ChallengeRoundCount::::kill(); + Defending::::kill(); + let _ = DefenderVotes::::clear(u32::MAX, None); + let _ = Candidates::::clear(u32::MAX, None); Self::deposit_event(Event::::Unfounded { founder }); Ok(()) } @@ -1094,193 +1135,235 @@ pub mod pallet { /// If a suspended member is rejected, remove all associated storage items, including /// their payouts, and remove any vouched bids they currently have. /// - /// The dispatch origin for this call must be from the _SuspensionJudgementOrigin_. + /// The dispatch origin for this call must be Signed from the Founder. /// /// Parameters: /// - `who` - The suspended member to be judged. /// - `forgive` - A boolean representing whether the suspension judgement origin forgives /// (`true`) or rejects (`false`) a suspended member. - /// - /// # - /// Key: B (len of bids), M (len of members) - /// - One storage read to check `who` is a suspended member. O(1) - /// - Up to one storage write O(M) with O(log M) binary search to add a member back to - /// society. - /// - Up to 3 storage removals O(1) to clean up a removed member. - /// - Up to one storage write O(B) with O(B) search to remove vouched bid from bids. - /// - Up to one additional event if unvouch takes place. - /// - One storage removal. O(1) - /// - One event for the judgement. - /// - /// Total Complexity: O(M + logM + B) - /// # - #[pallet::call_index(9)] - #[pallet::weight(T::BlockWeights::get().max_block / 10)] + #[pallet::call_index(10)] + #[pallet::weight(T::WeightInfo::judge_suspended_member())] pub fn judge_suspended_member( origin: OriginFor, who: AccountIdLookupOf, forgive: bool, - ) -> DispatchResult { - T::SuspensionJudgementOrigin::ensure_origin(origin)?; + ) -> DispatchResultWithPostInfo { + ensure!( + Some(ensure_signed(origin)?) == Founder::::get(), + Error::::NotFounder + ); let who = T::Lookup::lookup(who)?; - ensure!(>::contains_key(&who), Error::::NotSuspended); - + let record = SuspendedMembers::::get(&who).ok_or(Error::::NotSuspended)?; if forgive { // Try to add member back to society. Can fail with `MaxMembers` limit. - Self::add_member(&who)?; + Self::reinstate_member(&who, record.rank)?; } else { - // Cancel a suspended member's membership, remove their payouts. - >::remove(&who); - >::remove(&who); - // Remove their vouching status, potentially unbanning them in the future. - if >::take(&who) == Some(VouchingStatus::Vouching) { - // Try to remove their bid if they are vouching. - // If their vouch is already a candidate, do nothing. - >::mutate(|bids| - // Try to find the matching bid - if let Some(pos) = bids.iter().position(|b| b.kind.check_voucher(&who).is_ok()) { - // Remove the bid, and emit an event - let vouched = bids.remove(pos).who; - Self::deposit_event(Event::::Unvouch { candidate: vouched }); - } - ); - } + let payout_record = Payouts::::take(&who); + let total = payout_record + .payouts + .into_iter() + .map(|x| x.1) + .fold(Zero::zero(), |acc: BalanceOf, x| acc.saturating_add(x)); + Self::unreserve_payout(total); } - - >::remove(&who); + SuspendedMembers::::remove(&who); Self::deposit_event(Event::::SuspendedMemberJudgement { who, judged: forgive }); - Ok(()) + Ok(Pays::No.into()) } - /// Allow suspended judgement origin to make judgement on a suspended candidate. + /// Change the maximum number of members in society and the maximum number of new candidates + /// in a single intake period. /// - /// If the judgement is `Approve`, we add them to society as a member with the appropriate - /// payment for joining society. - /// - /// If the judgement is `Reject`, we either slash the deposit of the bid, giving it back - /// to the society treasury, or we ban the voucher from vouching again. - /// - /// If the judgement is `Rebid`, we put the candidate back in the bid pool and let them go - /// through the induction process again. - /// - /// The dispatch origin for this call must be from the _SuspensionJudgementOrigin_. + /// The dispatch origin for this call must be Signed by the Founder. /// /// Parameters: - /// - `who` - The suspended candidate to be judged. - /// - `judgement` - `Approve`, `Reject`, or `Rebid`. - /// - /// # - /// Key: B (len of bids), M (len of members), X (balance action) - /// - One storage read to check `who` is a suspended candidate. - /// - One storage removal of the suspended candidate. - /// - Approve Logic - /// - One storage read to get the available pot to pay users with. O(1) - /// - One storage write to update the available pot. O(1) - /// - One storage read to get the current block number. O(1) - /// - One storage read to get all members. O(M) - /// - Up to one unreserve currency action. - /// - Up to two new storage writes to payouts. - /// - Up to one storage write with O(log M) binary search to add a member to society. - /// - Reject Logic - /// - Up to one repatriate reserved currency action. O(X) - /// - Up to one storage write to ban the vouching member from vouching again. - /// - Rebid Logic - /// - Storage mutate with O(log B) binary search to place the user back into bids. - /// - Up to one additional event if unvouch takes place. - /// - One storage removal. - /// - One event for the judgement. - /// - /// Total Complexity: O(M + logM + B + X) - /// # - #[pallet::call_index(10)] - #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn judge_suspended_candidate( + /// - `max_members` - The maximum number of members for the society. This must be no less + /// than the current number of members. + /// - `max_intake` - The maximum number of candidates per intake period. + /// - `max_strikes`: The maximum number of strikes a member may get before they become + /// suspended and may only be reinstated by the founder. + /// - `candidate_deposit`: The deposit required to make a bid for membership of the group. + #[pallet::call_index(11)] + #[pallet::weight(T::WeightInfo::set_parameters())] + pub fn set_parameters( origin: OriginFor, - who: AccountIdLookupOf, - judgement: Judgement, + max_members: u32, + max_intake: u32, + max_strikes: u32, + candidate_deposit: BalanceOf, ) -> DispatchResult { - T::SuspensionJudgementOrigin::ensure_origin(origin)?; - let who = T::Lookup::lookup(who)?; - if let Some((value, kind)) = >::get(&who) { - match judgement { - Judgement::Approve => { - // Suspension Judgement origin has approved this candidate - // Make sure we can pay them - let pot = Self::pot(); - ensure!(pot >= value, Error::::InsufficientPot); - // Try to add user as a member! Can fail with `MaxMember` limit. - Self::add_member(&who)?; - // Reduce next pot by payout - >::put(pot - value); - // Add payout for new candidate - let maturity = >::block_number() + - Self::lock_duration(Self::members().len() as u32); - Self::pay_accepted_candidate(&who, value, kind, maturity); - }, - Judgement::Reject => { - // Founder has rejected this candidate - match kind { - BidKind::Deposit(deposit) => { - // Slash deposit and move it to the society account - let res = T::Currency::repatriate_reserved( - &who, - &Self::account_id(), - deposit, - BalanceStatus::Free, - ); - debug_assert!(res.is_ok()); - }, - BidKind::Vouch(voucher, _) => { - // Ban the voucher from vouching again - >::insert(&voucher, VouchingStatus::Banned); - }, - } - }, - Judgement::Rebid => { - // Founder has taken no judgement, and candidate is placed back into the - // pool. - let bids = >::get(); - Self::put_bid(bids, &who, value, kind); - }, - } - - // Remove suspended candidate - >::remove(who); - } else { - return Err(Error::::NotSuspended.into()) - } + ensure!( + Some(ensure_signed(origin)?) == Founder::::get(), + Error::::NotFounder + ); + ensure!(max_members >= MemberCount::::get(), Error::::MaxMembers); + let params = GroupParams { max_members, max_intake, max_strikes, candidate_deposit }; + Parameters::::put(¶ms); + Self::deposit_event(Event::::NewParams { params }); Ok(()) } - /// Allows root origin to change the maximum number of members in society. - /// Max membership count must be greater than 1. + /// Punish the skeptic with a strike if they did not vote on a candidate. Callable by the + /// candidate. + #[pallet::call_index(12)] + #[pallet::weight(T::WeightInfo::punish_skeptic())] + pub fn punish_skeptic(origin: OriginFor) -> DispatchResultWithPostInfo { + let candidate = ensure_signed(origin)?; + let mut candidacy = + Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + ensure!(!candidacy.skeptic_struck, Error::::AlreadyPunished); + ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); + let punished = Self::check_skeptic(&candidate, &mut candidacy); + Candidates::::insert(&candidate, candidacy); + Ok(if punished { Pays::No } else { Pays::Yes }.into()) + } + + /// Transform an approved candidate into a member. Callable only by the + /// the candidate, and only after the period for voting has ended. + #[pallet::call_index(13)] + #[pallet::weight(T::WeightInfo::claim_membership())] + pub fn claim_membership(origin: OriginFor) -> DispatchResultWithPostInfo { + let candidate = ensure_signed(origin)?; + let candidacy = + Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + ensure!(candidacy.tally.clear_approval(), Error::::NotApproved); + ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); + Self::induct_member(candidate, candidacy, 0)?; + Ok(Pays::No.into()) + } + + /// Transform an approved candidate into a member. Callable only by the Signed origin of the + /// Founder, only after the period for voting has ended and only when the candidate is not + /// clearly rejected. + #[pallet::call_index(14)] + #[pallet::weight(T::WeightInfo::bestow_membership())] + pub fn bestow_membership( + origin: OriginFor, + candidate: T::AccountId, + ) -> DispatchResultWithPostInfo { + ensure!( + Some(ensure_signed(origin)?) == Founder::::get(), + Error::::NotFounder + ); + let candidacy = + Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + ensure!(!candidacy.tally.clear_rejection(), Error::::Rejected); + ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); + Self::induct_member(candidate, candidacy, 0)?; + Ok(Pays::No.into()) + } + + /// Remove the candidate's application from the society. Callable only by the Signed origin + /// of the Founder, only after the period for voting has ended, and only when they do not + /// have a clear approval. /// - /// The dispatch origin for this call must be from _ROOT_. + /// Any bid deposit is lost and voucher is banned. + #[pallet::call_index(15)] + #[pallet::weight(T::WeightInfo::kick_candidate())] + pub fn kick_candidate( + origin: OriginFor, + candidate: T::AccountId, + ) -> DispatchResultWithPostInfo { + ensure!( + Some(ensure_signed(origin)?) == Founder::::get(), + Error::::NotFounder + ); + let mut candidacy = + Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + ensure!(!Self::in_progress(candidacy.round), Error::::InProgress); + ensure!(!candidacy.tally.clear_approval(), Error::::Approved); + Self::check_skeptic(&candidate, &mut candidacy); + Self::reject_candidate(&candidate, &candidacy.kind); + Candidates::::remove(&candidate); + Ok(Pays::No.into()) + } + + /// Remove the candidate's application from the society. Callable only by the candidate. /// - /// Parameters: - /// - `max` - The maximum number of members for the society. + /// Any bid deposit is lost and voucher is banned. + #[pallet::call_index(16)] + #[pallet::weight(T::WeightInfo::resign_candidacy())] + pub fn resign_candidacy(origin: OriginFor) -> DispatchResultWithPostInfo { + let candidate = ensure_signed(origin)?; + let mut candidacy = + Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + if !Self::in_progress(candidacy.round) { + Self::check_skeptic(&candidate, &mut candidacy); + } + Self::reject_candidate(&candidate, &candidacy.kind); + Candidates::::remove(&candidate); + Ok(Pays::No.into()) + } + + /// Remove a `candidate`'s failed application from the society. Callable by any + /// signed origin but only at the end of the subsequent round and only for + /// a candidate with more rejections than approvals. /// - /// # - /// - One storage write to update the max. O(1) - /// - One event. + /// The bid deposit is lost and the voucher is banned. + #[pallet::call_index(17)] + #[pallet::weight(T::WeightInfo::drop_candidate())] + pub fn drop_candidate( + origin: OriginFor, + candidate: T::AccountId, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + let candidacy = + Candidates::::get(&candidate).ok_or(Error::::NotCandidate)?; + ensure!(candidacy.tally.clear_rejection(), Error::::NotRejected); + ensure!(RoundCount::::get() > candidacy.round + 1, Error::::TooEarly); + Self::reject_candidate(&candidate, &candidacy.kind); + Candidates::::remove(&candidate); + Ok(Pays::No.into()) + } + + /// Remove up to `max` stale votes for the given `candidate`. /// - /// Total Complexity: O(1) - /// # - #[pallet::call_index(11)] - #[pallet::weight(T::BlockWeights::get().max_block / 10)] - pub fn set_max_members(origin: OriginFor, max: u32) -> DispatchResult { - ensure_root(origin)?; - ensure!(max > 1, Error::::MaxMembers); - MaxMembers::::put(max); - Self::deposit_event(Event::::NewMaxMembers { max }); - Ok(()) + /// May be called by any Signed origin, but only after the candidate's candidacy is ended. + #[pallet::call_index(18)] + #[pallet::weight(T::WeightInfo::cleanup_candidacy())] + pub fn cleanup_candidacy( + origin: OriginFor, + candidate: T::AccountId, + max: u32, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + ensure!(!Candidates::::contains_key(&candidate), Error::::InProgress); + let maybe_cursor = VoteClearCursor::::get(&candidate); + let r = + Votes::::clear_prefix(&candidate, max, maybe_cursor.as_ref().map(|x| &x[..])); + if let Some(cursor) = r.maybe_cursor { + VoteClearCursor::::insert(&candidate, BoundedVec::truncate_from(cursor)); + } + Ok(if r.loops == 0 { Pays::Yes } else { Pays::No }.into()) + } + + /// Remove up to `max` stale votes for the defender in the given `challenge_round`. + /// + /// May be called by any Signed origin, but only after the challenge round is ended. + #[pallet::call_index(19)] + #[pallet::weight(T::WeightInfo::cleanup_challenge())] + pub fn cleanup_challenge( + origin: OriginFor, + challenge_round: RoundIndex, + max: u32, + ) -> DispatchResultWithPostInfo { + ensure_signed(origin)?; + ensure!( + challenge_round < ChallengeRoundCount::::get(), + Error::::InProgress + ); + let _ = DefenderVotes::::clear_prefix(challenge_round, max, None); + // clear_prefix() v2 is always returning backend = 0, ignoring it till v3. + // let (_, backend, _, _) = r.deconstruct(); + // if backend == 0 { return Err(Error::::NoVotes.into()); }; + Ok(Pays::No.into()) } } } /// Simple ensure origin struct to filter for the founder account. pub struct EnsureFounder(sp_std::marker::PhantomData); -impl EnsureOrigin for EnsureFounder { +impl EnsureOrigin<::RuntimeOrigin> for EnsureFounder { type Success = T::AccountId; fn try_origin(o: T::RuntimeOrigin) -> Result { o.into().and_then(|o| match (o, Founder::::get()) { @@ -1296,378 +1379,525 @@ impl EnsureOrigin for EnsureFounder { } } -/// Pick an item at pseudo-random from the slice, given the `rng`. `None` iff the slice is empty. -fn pick_item<'a, R: RngCore, T>(rng: &mut R, items: &'a [T]) -> Option<&'a T> { - if items.is_empty() { - None - } else { - Some(&items[pick_usize(rng, items.len() - 1)]) +impl_ensure_origin_with_arg_ignoring_arg! { + impl<{ T: Config, A }> + EnsureOriginWithArg for EnsureFounder + {} +} + +struct InputFromRng<'a, T>(&'a mut T); +impl<'a, T: RngCore> codec::Input for InputFromRng<'a, T> { + fn remaining_len(&mut self) -> Result, codec::Error> { + return Ok(None) + } + + fn read(&mut self, into: &mut [u8]) -> Result<(), codec::Error> { + self.0.fill_bytes(into); + Ok(()) } } -/// Pick a new PRN, in the range [0, `max`] (inclusive). -fn pick_usize(rng: &mut R, max: usize) -> usize { - (rng.next_u32() % (max as u32 + 1)) as usize +pub enum Period { + Voting { elapsed: BlockNumber, more: BlockNumber }, + Claim { elapsed: BlockNumber, more: BlockNumber }, } impl, I: 'static> Pallet { - /// Puts a bid into storage ordered by smallest to largest value. - /// Allows a maximum of 1000 bids in queue, removing largest value people first. - fn put_bid( - mut bids: Vec>>, - who: &T::AccountId, - value: BalanceOf, - bid_kind: BidKind>, - ) { - const MAX_BID_COUNT: usize = 1000; - - match bids.binary_search_by(|bid| bid.value.cmp(&value)) { - // Insert new elements after the existing ones. This ensures new bids - // with the same bid value are further down the list than existing ones. - Ok(pos) => { - let different_bid = bids - .iter() - // Easily extract the index we are on - .enumerate() - // Skip ahead to the suggested position - .skip(pos) - // Keep skipping ahead until the position changes - // Get the element when things changed - .find(|(_, x)| x.value > bids[pos].value); - - // If the element is not at the end of the list, insert the new element - // in the spot. - if let Some((p, _)) = different_bid { - bids.insert(p, Bid { value, who: who.clone(), kind: bid_kind }); - // If the element is at the end of the list, push the element on the end. + /// Get the period we are currently in. + fn period() -> Period> { + let claim_period = T::ClaimPeriod::get(); + let voting_period = T::VotingPeriod::get(); + let rotation_period = voting_period + claim_period; + let now = frame_system::Pallet::::block_number(); + let phase = now % rotation_period; + if phase < voting_period { + Period::Voting { elapsed: phase, more: voting_period - phase } + } else { + Period::Claim { elapsed: phase - voting_period, more: rotation_period - phase } + } + } + + /// Returns true if the given `target_round` is still in its initial voting phase. + fn in_progress(target_round: RoundIndex) -> bool { + let round = RoundCount::::get(); + target_round == round && matches!(Self::period(), Period::Voting { .. }) + } + + /// Returns the new vote. + fn do_vote(maybe_old: Option, approve: bool, rank: Rank, tally: &mut Tally) -> Vote { + match maybe_old { + Some(Vote { approve: true, weight }) => tally.approvals.saturating_reduce(weight), + Some(Vote { approve: false, weight }) => tally.rejections.saturating_reduce(weight), + _ => {}, + } + let weight_root = rank + 1; + let weight = weight_root * weight_root; + match approve { + true => tally.approvals.saturating_accrue(1), + false => tally.rejections.saturating_accrue(1), + } + Vote { approve, weight } + } + + /// Returns `true` if a punishment was given. + fn check_skeptic( + candidate: &T::AccountId, + candidacy: &mut Candidacy>, + ) -> bool { + if RoundCount::::get() != candidacy.round || candidacy.skeptic_struck { + return false + } + // We expect the skeptic to have voted. + let skeptic = match Skeptic::::get() { + Some(s) => s, + None => return false, + }; + let maybe_vote = Votes::::get(&candidate, &skeptic); + let approved = candidacy.tally.clear_approval(); + let rejected = candidacy.tally.clear_rejection(); + match (maybe_vote, approved, rejected) { + (None, _, _) | + (Some(Vote { approve: true, .. }), false, true) | + (Some(Vote { approve: false, .. }), true, false) => { + // Can't do much if the punishment doesn't work out. + if Self::strike_member(&skeptic).is_ok() { + candidacy.skeptic_struck = true; + true } else { - bids.push(Bid { value, who: who.clone(), kind: bid_kind }); + false } }, - Err(pos) => bids.insert(pos, Bid { value, who: who.clone(), kind: bid_kind }), + _ => false, } - // Keep it reasonably small. - if bids.len() > MAX_BID_COUNT { - let Bid { who: popped, kind, .. } = bids.pop().expect("b.len() > 1000; qed"); - match kind { - BidKind::Deposit(deposit) => { - let err_amount = T::Currency::unreserve(&popped, deposit); - debug_assert!(err_amount.is_zero()); - }, - BidKind::Vouch(voucher, _) => { - >::remove(&voucher); + } + + /// End the current challenge period and start a new one. + fn rotate_challenge(rng: &mut impl RngCore) { + let mut next_defender = None; + let mut round = ChallengeRoundCount::::get(); + + // End current defender rotation + if let Some((defender, skeptic, tally)) = Defending::::get() { + // We require strictly more approvals, since the member should be voting for themselves. + if !tally.more_approvals() { + // Member has failed the challenge: Suspend them. This will fail if they are Head + // or Founder, in which case we ignore. + let _ = Self::suspend_member(&defender); + } + + // Check defender skeptic voted and that their vote was with the majority. + let skeptic_vote = DefenderVotes::::get(round, &skeptic); + match (skeptic_vote, tally.more_approvals(), tally.more_rejections()) { + (None, _, _) | + (Some(Vote { approve: true, .. }), false, true) | + (Some(Vote { approve: false, .. }), true, false) => { + // Punish skeptic and challenge them next. + let _ = Self::strike_member(&skeptic); + let founder = Founder::::get(); + let head = Head::::get(); + if Some(&skeptic) != founder.as_ref() && Some(&skeptic) != head.as_ref() { + next_defender = Some(skeptic); + } }, + _ => {}, } - Self::deposit_event(Event::::AutoUnbid { candidate: popped }); + round.saturating_inc(); + ChallengeRoundCount::::put(round); } - >::put(bids); + // Avoid challenging if there's only two members since we never challenge the Head or + // the Founder. + if MemberCount::::get() > 2 { + let defender = next_defender + .or_else(|| Self::pick_defendent(rng)) + .expect("exited if members empty; qed"); + let skeptic = + Self::pick_member_except(rng, &defender).expect("exited if members empty; qed"); + Self::deposit_event(Event::::Challenged { member: defender.clone() }); + Defending::::put((defender, skeptic, Tally::default())); + } else { + Defending::::kill(); + } } - /// Check a user is a bid. - fn is_bid(bids: &Vec>>, who: &T::AccountId) -> bool { - // Bids are ordered by `value`, so we cannot binary search for a user. - bids.iter().any(|bid| bid.who == *who) + /// End the current intake period and begin a new one. + /// + /// --------------------------------------------- + /// #10 || #11 _ || #12 + /// || Voting | Claiming || + /// --------------------------------------------- + fn rotate_intake(rng: &mut impl RngCore) { + // We assume there's at least one member or this logic won't work. + let member_count = MemberCount::::get(); + if member_count < 1 { + return + } + let maybe_head = NextHead::::take(); + if let Some(head) = maybe_head { + Head::::put(&head.who); + } + + // Bump the pot by at most `PeriodSpend`, but less if there's not very much left in our + // account. + let mut pot = Pot::::get(); + let unaccounted = T::Currency::free_balance(&Self::account_id()).saturating_sub(pot); + pot.saturating_accrue(T::PeriodSpend::get().min(unaccounted / 2u8.into())); + Pot::::put(&pot); + + // Bump round and create the new intake. + let mut round_count = RoundCount::::get(); + round_count.saturating_inc(); + let candidate_count = Self::select_new_candidates(round_count, member_count, pot); + if candidate_count > 0 { + // Select a member at random and make them the skeptic for this round. + let skeptic = Self::pick_member(rng).expect("exited if members empty; qed"); + Skeptic::::put(skeptic); + } + RoundCount::::put(round_count); } - /// Check a user is a candidate. - fn is_candidate( - candidates: &Vec>>, - who: &T::AccountId, - ) -> bool { - // Looking up a candidate is the same as looking up a bid - Self::is_bid(candidates, who) + /// Remove a selection of bidding accounts such that the total bids is no greater than `Pot` and + /// the number of bids would not surpass `MaxMembers` if all were accepted. At most one bid may + /// be zero. + /// + /// Candidates are inserted from each bidder. + /// + /// The number of candidates inserted are returned. + pub fn select_new_candidates( + round: RoundIndex, + member_count: u32, + pot: BalanceOf, + ) -> u32 { + // Get the number of left-most bidders whose bids add up to less than `pot`. + let mut bids = Bids::::get(); + let params = match Parameters::::get() { + Some(params) => params, + None => return 0, + }; + let max_selections: u32 = params + .max_intake + .min(params.max_members.saturating_sub(member_count)) + .min(bids.len() as u32); + + let mut selections = 0; + // A running total of the cost to onboard these bids + let mut total_cost: BalanceOf = Zero::zero(); + + bids.retain(|bid| { + // We only accept a zero bid as the first selection. + total_cost.saturating_accrue(bid.value); + let accept = selections < max_selections && + (!bid.value.is_zero() || selections == 0) && + total_cost <= pot; + if accept { + let candidacy = Candidacy { + round, + kind: bid.kind.clone(), + bid: bid.value, + tally: Default::default(), + skeptic_struck: false, + }; + Candidates::::insert(&bid.who, candidacy); + selections.saturating_inc(); + } + !accept + }); + + // No need to reset Bids if we're not taking anything. + Bids::::put(&bids); + selections } - /// Check a user is a member. - fn is_member(members: &Vec, who: &T::AccountId) -> bool { - members.binary_search(who).is_ok() + /// Puts a bid into storage ordered by smallest to largest value. + /// Allows a maximum of 1000 bids in queue, removing largest value people first. + fn insert_bid( + bids: &mut BoundedVec>, T::MaxBids>, + who: &T::AccountId, + value: BalanceOf, + bid_kind: BidKind>, + ) { + let pos = bids.iter().position(|bid| bid.value > value).unwrap_or(bids.len()); + let r = bids.force_insert_keep_left(pos, Bid { value, who: who.clone(), kind: bid_kind }); + let maybe_discarded = match r { + Ok(x) => x, + Err(x) => Some(x), + }; + if let Some(discarded) = maybe_discarded { + Self::clean_bid(&discarded); + Self::deposit_event(Event::::AutoUnbid { candidate: discarded.who }); + } } - /// Add a member to the sorted members list. If the user is already a member, do nothing. - /// Can fail when `MaxMember` limit is reached, but has no side-effects. - fn add_member(who: &T::AccountId) -> DispatchResult { - let mut members = >::get(); - ensure!(members.len() < MaxMembers::::get() as usize, Error::::MaxMembers); - match members.binary_search(who) { - // Add the new member - Err(i) => { - members.insert(i, who.clone()); - T::MembershipChanged::change_members_sorted(&[who.clone()], &[], &members); - >::put(members); - Ok(()) + /// Either unreserve the deposit or free up the vouching member. + /// + /// In neither case can we do much if the action isn't completable, but there's + /// no reason that either should fail. + /// + /// WARNING: This alters the voucher item of `Members`. You must ensure that you do not + /// accidentally overwrite it with an older value after calling this. + fn clean_bid(bid: &Bid>) { + match &bid.kind { + BidKind::Deposit(deposit) => { + let err_amount = T::Currency::unreserve(&bid.who, *deposit); + debug_assert!(err_amount.is_zero()); + }, + BidKind::Vouch(voucher, _) => { + Members::::mutate_extant(voucher, |record| record.vouching = None); }, - // User is already a member, do nothing. - Ok(_) => Ok(()), } } - /// Remove a member from the members list, except the Head. + /// Either repatriate the deposit into the Society account or ban the vouching member. + /// + /// In neither case can we do much if the action isn't completable, but there's + /// no reason that either should fail. /// - /// NOTE: This does not correctly clean up a member from storage. It simply - /// removes them from the Members storage item. - pub fn remove_member(m: &T::AccountId) -> DispatchResult { - ensure!(Self::head() != Some(m.clone()), Error::::Head); - ensure!(Self::founder() != Some(m.clone()), Error::::Founder); - - let mut members = >::get(); - match members.binary_search(m) { - Err(_) => Err(Error::::NotMember.into()), - Ok(i) => { - members.remove(i); - T::MembershipChanged::change_members_sorted(&[], &[m.clone()], &members[..]); - >::put(members); - Ok(()) + /// WARNING: This alters the voucher item of `Members`. You must ensure that you do not + /// accidentally overwrite it with an older value after calling this. + fn reject_candidate(who: &T::AccountId, kind: &BidKind>) { + match kind { + BidKind::Deposit(deposit) => { + let pot = Self::account_id(); + let free = BalanceStatus::Free; + let r = T::Currency::repatriate_reserved(&who, &pot, *deposit, free); + debug_assert!(r.is_ok()); + }, + BidKind::Vouch(voucher, _) => { + Members::::mutate_extant(voucher, |record| { + record.vouching = Some(VouchingStatus::Banned) + }); }, } } - /// End the current period and begin a new one. - fn rotate_period(members: &mut Vec) { - let phrase = b"society_rotation"; - - let mut pot = >::get(); - - // we'll need a random seed here. - // TODO: deal with randomness freshness - // https://github.com/paritytech/substrate/issues/8312 - let (seed, _) = T::Randomness::random(phrase); - // seed needs to be guaranteed to be 32 bytes. - let seed = <[u8; 32]>::decode(&mut TrailingZeroInput::new(seed.as_ref())) - .expect("input is padded with zeroes; qed"); - let mut rng = ChaChaRng::from_seed(seed); - - // we assume there's at least one member or this logic won't work. - if !members.is_empty() { - let candidates = >::take(); - // NOTE: This may cause member length to surpass `MaxMembers`, but results in no - // consensus critical issues or side-effects. This is auto-correcting as members fall - // out of society. - members.reserve(candidates.len()); - - let maturity = >::block_number() + - Self::lock_duration(members.len() as u32); - - let mut rewardees = Vec::new(); - let mut total_approvals = 0; - let mut total_slash = >::zero(); - let mut total_payouts = >::zero(); - - let accepted = candidates - .into_iter() - .filter_map(|Bid { value, who: candidate, kind }| { - let mut approval_count = 0; - - // Creates a vector of (vote, member) for the given candidate - // and tallies total number of approve votes for that candidate. - let votes = members - .iter() - .filter_map(|m| >::take(&candidate, m).map(|v| (v, m))) - .inspect(|&(v, _)| { - if v == Vote::Approve { - approval_count += 1 - } - }) - .collect::>(); - - // Select one of the votes at random. - // Note that `Vote::Skeptical` and `Vote::Reject` both reject the candidate. - let is_accepted = - pick_item(&mut rng, &votes).map(|x| x.0) == Some(Vote::Approve); - - let matching_vote = if is_accepted { Vote::Approve } else { Vote::Reject }; - - let bad_vote = |m: &T::AccountId| { - // Voter voted wrong way (or was just a lazy skeptic) then reduce their - // payout and increase their strikes. after MaxStrikes then they go into - // suspension. - let amount = Self::slash_payout(m, T::WrongSideDeduction::get()); - - let strikes = >::mutate(m, |s| { - *s += 1; - *s - }); - if strikes >= T::MaxStrikes::get() { - Self::suspend_member(m); - } - amount - }; - - // Collect the voters who had a matching vote. - rewardees.extend( - votes - .into_iter() - .filter_map(|(v, m)| { - if v == matching_vote { - Some(m) - } else { - total_slash += bad_vote(m); - None - } - }) - .cloned(), - ); - - if is_accepted { - total_approvals += approval_count; - total_payouts += value; - members.push(candidate.clone()); - - Self::pay_accepted_candidate(&candidate, value, kind, maturity); - - // We track here the total_approvals so that every candidate has a unique - // range of numbers from 0 to `total_approvals` with length `approval_count` - // so each candidate is proportionally represented when selecting a - // "primary" below. - Some((candidate, total_approvals, value)) - } else { - // Suspend Candidate - >::insert(&candidate, (value, kind)); - Self::deposit_event(Event::::CandidateSuspended { candidate }); - None - } - }) - .collect::>(); - - // Clean up all votes. - #[allow(deprecated)] - >::remove_all(None); - - // Reward one of the voters who voted the right way. - if !total_slash.is_zero() { - if let Some(winner) = pick_item(&mut rng, &rewardees) { - // If we can't reward them, not much that can be done. - Self::bump_payout(winner, maturity, total_slash); - } else { - // Move the slashed amount back from payouts account to local treasury. - let res = T::Currency::transfer( - &Self::payouts(), - &Self::account_id(), - total_slash, - AllowDeath, - ); - debug_assert!(res.is_ok()); - } - } + /// Check a user has a bid. + fn has_bid(bids: &Vec>>, who: &T::AccountId) -> bool { + // Bids are ordered by `value`, so we cannot binary search for a user. + bids.iter().any(|bid| bid.who == *who) + } - // Fund the total payouts from the local treasury. - if !total_payouts.is_zero() { - // remove payout from pot and shift needed funds to the payout account. - pot = pot.saturating_sub(total_payouts); - - // this should never fail since we ensure we can afford the payouts in a previous - // block, but there's not much we can do to recover if it fails anyway. - let res = T::Currency::transfer( - &Self::account_id(), - &Self::payouts(), - total_payouts, - AllowDeath, - ); - debug_assert!(res.is_ok()); - } + /// Add a member to the members list. If the user is already a member, do nothing. Can fail when + /// `MaxMember` limit is reached, but in that case it has no side-effects. + /// + /// Set the `payouts` for the member. NOTE: This *WILL NOT RESERVE THE FUNDS TO MAKE THE + /// PAYOUT*. Only set this to be non-empty if you already have the funds reserved in the Payouts + /// account. + /// + /// NOTE: Generally you should not use this, and instead use `add_new_member` or + /// `reinstate_member`, whose names clearly match the desired intention. + fn insert_member(who: &T::AccountId, rank: Rank) -> DispatchResult { + let params = Parameters::::get().ok_or(Error::::NotGroup)?; + ensure!(MemberCount::::get() < params.max_members, Error::::MaxMembers); + let index = MemberCount::::mutate(|i| { + i.saturating_accrue(1); + *i - 1 + }); + let record = MemberRecord { rank, strikes: 0, vouching: None, index }; + Members::::insert(who, record); + MemberByIndex::::insert(index, who); + Ok(()) + } - // if at least one candidate was accepted... - if !accepted.is_empty() { - // select one as primary, randomly chosen from the accepted, weighted by approvals. - // Choose a random number between 0 and `total_approvals` - let primary_point = pick_usize(&mut rng, total_approvals - 1); - // Find the zero bid or the user who falls on that point - let primary = accepted - .iter() - .find(|e| e.2.is_zero() || e.1 > primary_point) - .expect( - "e.1 of final item == total_approvals; \ - worst case find will always return that item; qed", - ) - .0 - .clone(); - - let accounts = accepted.into_iter().map(|x| x.0).collect::>(); - - // Then write everything back out, signal the changed membership and leave an event. - members.sort(); - // NOTE: This may cause member length to surpass `MaxMembers`, but results in no - // consensus critical issues or side-effects. This is auto-correcting as members - // fall out of society. - >::put(&members[..]); - >::put(&primary); - - T::MembershipChanged::change_members_sorted(&accounts, &[], members); - Self::deposit_event(Event::::Inducted { primary, candidates: accounts }); - } + /// Add a member back to the members list, setting their `rank` and `payouts`. + /// + /// Can fail when `MaxMember` limit is reached, but in that case it has no side-effects. + /// + /// The `payouts` value must be exactly as it was prior to suspension since no further funds + /// will be reserved. + fn reinstate_member(who: &T::AccountId, rank: Rank) -> DispatchResult { + Self::insert_member(who, rank) + } - // Bump the pot by at most PeriodSpend, but less if there's not very much left in our - // account. - let unaccounted = T::Currency::free_balance(&Self::account_id()).saturating_sub(pot); - pot += T::PeriodSpend::get().min(unaccounted / 2u8.into()); + /// Add a member to the members list. If the user is already a member, do nothing. Can fail when + /// `MaxMember` limit is reached, but in that case it has no side-effects. + fn add_new_member(who: &T::AccountId, rank: Rank) -> DispatchResult { + Self::insert_member(who, rank) + } - >::put(&pot); - } + /// Induct a new member into the set. + fn induct_member( + candidate: T::AccountId, + mut candidacy: Candidacy>, + rank: Rank, + ) -> DispatchResult { + Self::add_new_member(&candidate, rank)?; + Self::check_skeptic(&candidate, &mut candidacy); + + let next_head = NextHead::::get() + .filter(|old| { + old.round > candidacy.round || + old.round == candidacy.round && old.bid < candidacy.bid + }) + .unwrap_or_else(|| IntakeRecord { + who: candidate.clone(), + bid: candidacy.bid, + round: candidacy.round, + }); + NextHead::::put(next_head); - // Setup the candidates for the new intake - let candidates = Self::take_selected(members.len(), pot); - >::put(&candidates); + let now = >::block_number(); + let maturity = now + Self::lock_duration(MemberCount::::get()); + Self::reward_bidder(&candidate, candidacy.bid, candidacy.kind, maturity); - // Select sqrt(n) random members from the society and make them skeptics. - let pick_member = - |_| pick_item(&mut rng, &members[..]).expect("exited if members empty; qed"); - for skeptic in (0..members.len().integer_sqrt()).map(pick_member) { - for Bid { who: c, .. } in candidates.iter() { - >::insert(c, skeptic, Vote::Skeptic); - } + Candidates::::remove(&candidate); + Ok(()) + } + + fn strike_member(who: &T::AccountId) -> DispatchResult { + let mut record = Members::::get(who).ok_or(Error::::NotMember)?; + record.strikes.saturating_inc(); + Members::::insert(who, &record); + // ^^^ Keep the member record mutation self-contained as we might be suspending them later + // in this function. + + if record.strikes >= T::GraceStrikes::get() { + // Too many strikes: slash the payout in half. + let total_payout = Payouts::::get(who) + .payouts + .iter() + .fold(BalanceOf::::zero(), |acc, x| acc.saturating_add(x.1)); + Self::slash_payout(who, total_payout / 2u32.into()); } + + let params = Parameters::::get().ok_or(Error::::NotGroup)?; + if record.strikes >= params.max_strikes { + // Way too many strikes: suspend. + let _ = Self::suspend_member(who); + } + Ok(()) } - /// Attempt to slash the payout of some member. Return the total amount that was deducted. - fn slash_payout(who: &T::AccountId, value: BalanceOf) -> BalanceOf { - let mut rest = value; - let mut payouts = >::get(who); - if !payouts.is_empty() { - let mut dropped = 0; - for (_, amount) in payouts.iter_mut() { - if let Some(new_rest) = rest.checked_sub(amount) { - // not yet totally slashed after this one; drop it completely. - rest = new_rest; - dropped += 1; + /// Remove a member from the members list and return the candidacy. + /// + /// If the member was vouching, then this will be reset. Any bidders that the member was + /// vouching for will be cancelled unless they are already selected as candidates (in which case + /// they will be able to stand). + /// + /// If the member has existing payouts, they will be retained in the resultant `MemberRecord` + /// and the funds will remain reserved. + /// + /// The Head and the Founder may never be removed. + pub fn remove_member(m: &T::AccountId) -> Result { + ensure!(Head::::get().as_ref() != Some(m), Error::::Head); + ensure!(Founder::::get().as_ref() != Some(m), Error::::Founder); + if let Some(mut record) = Members::::get(m) { + let index = record.index; + let last_index = MemberCount::::mutate(|i| { + i.saturating_reduce(1); + *i + }); + if index != last_index { + // Move the member with the last index down to the index of the member to be + // removed. + if let Some(other) = MemberByIndex::::get(last_index) { + MemberByIndex::::insert(index, &other); + Members::::mutate(other, |m_r| { + if let Some(r) = m_r { + r.index = index + } + }); } else { - // whole slash is accounted for. - *amount -= rest; - rest = Zero::zero(); - break + debug_assert!(false, "ERROR: No member at the last index position?"); } } - >::insert(who, &payouts[dropped..]); + + MemberByIndex::::remove(last_index); + Members::::remove(m); + // Remove their vouching status, potentially unbanning them in the future. + if record.vouching.take() == Some(VouchingStatus::Vouching) { + // Try to remove their bid if they are vouching. + // If their vouch is already a candidate, do nothing. + Bids::::mutate(|bids| + // Try to find the matching bid + if let Some(pos) = bids.iter().position(|b| b.kind.is_vouch(&m)) { + // Remove the bid, and emit an event + let vouched = bids.remove(pos).who; + Self::deposit_event(Event::::Unvouch { candidate: vouched }); + } + ); + } + Ok(record) + } else { + Err(Error::::NotMember.into()) } - value - rest } - /// Bump the payout amount of `who`, to be unlocked at the given block number. - fn bump_payout(who: &T::AccountId, when: T::BlockNumber, value: BalanceOf) { - if !value.is_zero() { - >::mutate(who, |payouts| { - match payouts.binary_search_by_key(&when, |x| x.0) { - Ok(index) => payouts[index].1 += value, - Err(index) => payouts.insert(index, (when, value)), - } - }); + /// Remove a member from the members set and add them to the suspended members. + /// + /// If the member was vouching, then this will be reset. Any bidders that the member was + /// vouching for will be cancelled unless they are already selected as candidates (in which case + /// they will be able to stand). + fn suspend_member(who: &T::AccountId) -> DispatchResult { + let record = Self::remove_member(&who)?; + SuspendedMembers::::insert(who, record); + Self::deposit_event(Event::::MemberSuspended { member: who.clone() }); + Ok(()) + } + + /// Select a member at random, given the RNG `rng`. + /// + /// If no members exist (or the state is inconsistent), then `None` may be returned. + fn pick_member(rng: &mut impl RngCore) -> Option { + let member_count = MemberCount::::get(); + if member_count == 0 { + return None + } + let random_index = rng.next_u32() % member_count; + MemberByIndex::::get(random_index) + } + + /// Select a member at random except `exception`, given the RNG `rng`. + /// + /// If `exception` is the only member (or the state is inconsistent), then `None` may be + /// returned. + fn pick_member_except( + rng: &mut impl RngCore, + exception: &T::AccountId, + ) -> Option { + let member_count = MemberCount::::get(); + if member_count <= 1 { + return None + } + let random_index = rng.next_u32() % (member_count - 1); + let pick = MemberByIndex::::get(random_index); + if pick.as_ref() == Some(exception) { + MemberByIndex::::get(member_count - 1) + } else { + pick } } - /// Suspend a user, removing them from the member list. - fn suspend_member(who: &T::AccountId) { - if Self::remove_member(who).is_ok() { - >::insert(who, true); - >::remove(who); - Self::deposit_event(Event::::MemberSuspended { member: who.clone() }); + /// Select a member who is able to defend at random, given the RNG `rng`. + /// + /// If only the Founder and Head members exist (or the state is inconsistent), then `None` + /// may be returned. + fn pick_defendent(rng: &mut impl RngCore) -> Option { + let member_count = MemberCount::::get(); + if member_count <= 2 { + return None + } + // Founder is always at index 0, so we should never pick that one. + // Head will typically but not always be the highest index. We assume it is for now and + // fix it up later if not. + let head = Head::::get(); + let pickable_count = member_count - if head.is_some() { 2 } else { 1 }; + let random_index = rng.next_u32() % pickable_count + 1; + let pick = MemberByIndex::::get(random_index); + if pick == head && head.is_some() { + // Turns out that head was not the last index since we managed to pick it. Exchange our + // pick for the last index. + MemberByIndex::::get(member_count - 1) + } else { + pick } } /// Pay an accepted candidate their bid value. - fn pay_accepted_candidate( + fn reward_bidder( candidate: &T::AccountId, value: BalanceOf, kind: BidKind>, - maturity: T::BlockNumber, + maturity: BlockNumberFor, ) { let value = match kind { BidKind::Deposit(deposit) => { @@ -1680,11 +1910,17 @@ impl, I: 'static> Pallet { BidKind::Vouch(voucher, tip) => { // Check that the voucher is still vouching, else some other logic may have removed // their status. - if >::take(&voucher) == Some(VouchingStatus::Vouching) { - // In the case that a vouched-for bid is accepted we unset the - // vouching status and transfer the tip over to the voucher. - Self::bump_payout(&voucher, maturity, tip.min(value)); - value.saturating_sub(tip) + if let Some(mut record) = Members::::get(&voucher) { + if let Some(VouchingStatus::Vouching) = record.vouching { + // In the case that a vouched-for bid is accepted we unset the + // vouching status and transfer the tip over to the voucher. + record.vouching = None; + Self::bump_payout(&voucher, maturity, tip.min(value)); + Members::::insert(&voucher, record); + value.saturating_sub(tip) + } else { + value + } } else { value } @@ -1694,52 +1930,71 @@ impl, I: 'static> Pallet { Self::bump_payout(candidate, maturity, value); } - /// End the current challenge period and start a new one. - fn rotate_challenge(members: &mut Vec) { - // Assume there are members, else don't run this logic. - if !members.is_empty() { - // End current defender rotation - if let Some(defender) = Self::defender() { - let mut approval_count = 0; - let mut rejection_count = 0; - // Tallies total number of approve and reject votes for the defender. - members.iter().filter_map(>::take).for_each(|v| match v { - Vote::Approve => approval_count += 1, - _ => rejection_count += 1, - }); - - if approval_count <= rejection_count { - // User has failed the challenge - Self::suspend_member(&defender); - *members = Self::members(); + /// Bump the payout amount of `who`, to be unlocked at the given block number. + /// + /// It is the caller's duty to ensure that `who` is already a member. This does nothing if `who` + /// is not a member or if `value` is zero. + fn bump_payout(who: &T::AccountId, when: BlockNumberFor, value: BalanceOf) { + if value.is_zero() { + return + } + if let Some(MemberRecord { rank: 0, .. }) = Members::::get(who) { + Payouts::::mutate(who, |record| { + // Members of rank 1 never get payouts. + match record.payouts.binary_search_by_key(&when, |x| x.0) { + Ok(index) => record.payouts[index].1.saturating_accrue(value), + Err(index) => { + // If they have too many pending payouts, then we take discard the payment. + let _ = record.payouts.try_insert(index, (when, value)); + }, } + }); + Self::reserve_payout(value); + } + } - // Clean up all votes. - #[allow(deprecated)] - >::remove_all(None); - } - - // Avoid challenging if there's only two members since we never challenge the Head or - // the Founder. - if members.len() > 2 { - // Start a new defender rotation - let phrase = b"society_challenge"; - // we'll need a random seed here. - // TODO: deal with randomness freshness - // https://github.com/paritytech/substrate/issues/8312 - let (seed, _) = T::Randomness::random(phrase); - // seed needs to be guaranteed to be 32 bytes. - let seed = <[u8; 32]>::decode(&mut TrailingZeroInput::new(seed.as_ref())) - .expect("input is padded with zeroes; qed"); - let mut rng = ChaChaRng::from_seed(seed); - let chosen = pick_item(&mut rng, &members[1..members.len() - 1]) - .expect("exited if members empty; qed"); - >::put(&chosen); - Self::deposit_event(Event::::Challenged { member: chosen.clone() }); + /// Attempt to slash the payout of some member. Return the total amount that was deducted. + fn slash_payout(who: &T::AccountId, value: BalanceOf) -> BalanceOf { + let mut record = Payouts::::get(who); + let mut rest = value; + while !record.payouts.is_empty() { + if let Some(new_rest) = rest.checked_sub(&record.payouts[0].1) { + // not yet totally slashed after this one; drop it completely. + rest = new_rest; + record.payouts.remove(0); } else { - >::kill(); + // whole slash is accounted for. + record.payouts[0].1.saturating_reduce(rest); + rest = Zero::zero(); + break } } + Payouts::::insert(who, record); + value - rest + } + + /// Transfer some `amount` from the main account into the payouts account and reduce the Pot + /// by this amount. + fn reserve_payout(amount: BalanceOf) { + // Tramsfer payout from the Pot into the payouts account. + Pot::::mutate(|pot| pot.saturating_reduce(amount)); + + // this should never fail since we ensure we can afford the payouts in a previous + // block, but there's not much we can do to recover if it fails anyway. + let res = T::Currency::transfer(&Self::account_id(), &Self::payouts(), amount, AllowDeath); + debug_assert!(res.is_ok()); + } + + /// Transfer some `amount` from the main account into the payouts account and increase the Pot + /// by this amount. + fn unreserve_payout(amount: BalanceOf) { + // Tramsfer payout from the Pot into the payouts account. + Pot::::mutate(|pot| pot.saturating_accrue(amount)); + + // this should never fail since we ensure we can afford the payouts in a previous + // block, but there's not much we can do to recover if it fails anyway. + let res = T::Currency::transfer(&Self::payouts(), &Self::account_id(), amount, AllowDeath); + debug_assert!(res.is_ok()); } /// The account ID of the treasury pot. @@ -1762,74 +2017,10 @@ impl, I: 'static> Pallet { /// /// This is a rather opaque calculation based on the formula here: /// https://www.desmos.com/calculator/9itkal1tce - fn lock_duration(x: u32) -> T::BlockNumber { + fn lock_duration(x: u32) -> BlockNumberFor { let lock_pc = 100 - 50_000 / (x + 500); Percent::from_percent(lock_pc as u8) * T::MaxLockDuration::get() } - - /// Get a selection of bidding accounts such that the total bids is no greater than `Pot` and - /// the number of bids would not surpass `MaxMembers` if all were accepted. - /// - /// May be empty. - pub fn take_selected( - members_len: usize, - pot: BalanceOf, - ) -> Vec>> { - let max_members = MaxMembers::::get() as usize; - let mut max_selections: usize = - (T::MaxCandidateIntake::get() as usize).min(max_members.saturating_sub(members_len)); - - if max_selections > 0 { - // Get the number of left-most bidders whose bids add up to less than `pot`. - let mut bids = >::get(); - - // The list of selected candidates - let mut selected = Vec::new(); - - if bids.len() > 0 { - // Can only select at most the length of bids - max_selections = max_selections.min(bids.len()); - // Number of selected bids so far - let mut count = 0; - // Check if we have already selected a candidate with zero bid - let mut zero_selected = false; - // A running total of the cost to onboard these bids - let mut total_cost: BalanceOf = Zero::zero(); - - bids.retain(|bid| { - if count < max_selections { - // Handle zero bids. We only want one of them. - if bid.value.is_zero() { - // Select only the first zero bid - if !zero_selected { - selected.push(bid.clone()); - zero_selected = true; - count += 1; - return false - } - } else { - total_cost += bid.value; - // Select only as many users as the pot can support. - if total_cost <= pot { - selected.push(bid.clone()); - count += 1; - return false - } - } - } - true - }); - - // No need to reset Bids if we're not taking anything. - if count > 0 { - >::put(bids); - } - } - selected - } else { - vec![] - } - } } impl, I: 'static> OnUnbalanced> for Pallet { diff --git a/frame/society/src/migrations.rs b/frame/society/src/migrations.rs new file mode 100644 index 000000000000..4685167dcbcf --- /dev/null +++ b/frame/society/src/migrations.rs @@ -0,0 +1,358 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Migrations for Society Pallet + +use super::*; +use codec::{Decode, Encode}; +use frame_support::traits::{Defensive, DefensiveOption, Instance, OnRuntimeUpgrade}; + +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + +/// The log target. +const TARGET: &'static str = "runtime::society::migration"; + +/// This migration moves all the state to v2 of Society. +pub struct VersionUncheckedMigrateToV2, I: 'static, PastPayouts>( + sp_std::marker::PhantomData<(T, I, PastPayouts)>, +); + +impl< + T: Config, + I: Instance + 'static, + PastPayouts: Get::AccountId, BalanceOf)>>, + > OnRuntimeUpgrade for VersionUncheckedMigrateToV2 +{ + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + let current = Pallet::::current_storage_version(); + let onchain = Pallet::::on_chain_storage_version(); + ensure!(onchain == 0 && current == 2, "pallet_society: invalid version"); + + Ok((old::Candidates::::get(), old::Members::::get()).encode()) + } + + fn on_runtime_upgrade() -> Weight { + let onchain = Pallet::::on_chain_storage_version(); + if onchain < 2 { + log::info!( + target: TARGET, + "Running migration against onchain version {:?}", + onchain + ); + from_original::(&mut PastPayouts::get()).defensive_unwrap_or(Weight::MAX) + } else { + log::warn!("Unexpected onchain version: {:?} (expected 0)", onchain); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(data: Vec) -> Result<(), TryRuntimeError> { + let old: ( + Vec::AccountId, BalanceOf>>, + Vec<::AccountId>, + ) = Decode::decode(&mut &data[..]).expect("Bad data"); + let mut old_candidates = + old.0.into_iter().map(|x| (x.who, x.kind, x.value)).collect::>(); + let mut old_members = old.1; + let mut candidates = + Candidates::::iter().map(|(k, v)| (k, v.kind, v.bid)).collect::>(); + let mut members = Members::::iter_keys().collect::>(); + + old_candidates.sort_by_key(|x| x.0.clone()); + candidates.sort_by_key(|x| x.0.clone()); + assert_eq!(candidates, old_candidates); + + members.sort(); + old_members.sort(); + assert_eq!(members, old_members); + + ensure!( + Pallet::::on_chain_storage_version() == 2, + "The onchain version must be updated after the migration." + ); + + assert_internal_consistency::(); + Ok(()) + } +} + +/// [`VersionUncheckedMigrateToV2`] wrapped in a +/// [`frame_support::migrations::VersionedRuntimeUpgrade`], ensuring the migration is only performed +/// when on-chain version is 0. +#[cfg(feature = "experimental")] +pub type VersionCheckedMigrateToV2 = + frame_support::migrations::VersionedRuntimeUpgrade< + 0, + 2, + VersionUncheckedMigrateToV2, + crate::pallet::Pallet, + ::DbWeight, + >; + +pub(crate) mod old { + use super::*; + use frame_support::storage_alias; + + /// A vote by a member on a candidate application. + #[derive(Encode, Decode, Copy, Clone, PartialEq, Eq, RuntimeDebug, TypeInfo)] + pub enum Vote { + /// The member has been chosen to be skeptic and has not yet taken any action. + Skeptic, + /// The member has rejected the candidate's application. + Reject, + /// The member approves of the candidate's application. + Approve, + } + + #[storage_alias] + pub type Bids, I: 'static> = StorageValue< + Pallet, + Vec::AccountId, BalanceOf>>, + ValueQuery, + >; + #[storage_alias] + pub type Candidates, I: 'static> = StorageValue< + Pallet, + Vec::AccountId, BalanceOf>>, + ValueQuery, + >; + #[storage_alias] + pub type Votes, I: 'static> = StorageDoubleMap< + Pallet, + Twox64Concat, + ::AccountId, + Twox64Concat, + ::AccountId, + Vote, + >; + #[storage_alias] + pub type SuspendedCandidates, I: 'static> = StorageMap< + Pallet, + Twox64Concat, + ::AccountId, + (BalanceOf, BidKind<::AccountId, BalanceOf>), + >; + #[storage_alias] + pub type Members, I: 'static> = + StorageValue, Vec<::AccountId>, ValueQuery>; + #[storage_alias] + pub type Vouching, I: 'static> = StorageMap< + Pallet, + Twox64Concat, + ::AccountId, + VouchingStatus, + >; + #[storage_alias] + pub type Strikes, I: 'static> = StorageMap< + Pallet, + Twox64Concat, + ::AccountId, + StrikeCount, + ValueQuery, + >; + #[storage_alias] + pub type Payouts, I: 'static> = StorageMap< + Pallet, + Twox64Concat, + ::AccountId, + Vec<(frame_system::pallet_prelude::BlockNumberFor, BalanceOf)>, + ValueQuery, + >; + #[storage_alias] + pub type SuspendedMembers, I: 'static> = StorageMap< + Pallet, + Twox64Concat, + ::AccountId, + bool, + ValueQuery, + >; + #[storage_alias] + pub type Defender, I: 'static> = + StorageValue, ::AccountId>; + #[storage_alias] + pub type DefenderVotes, I: 'static> = + StorageMap, Twox64Concat, ::AccountId, Vote>; +} + +/// Will panic if there are any inconsistencies in the pallet's state or old keys remaining. +pub fn assert_internal_consistency, I: Instance + 'static>() { + // Check all members are valid data. + let mut members = vec![]; + for m in Members::::iter_keys() { + let r = Members::::get(&m).expect("Member data must be valid"); + members.push((m, r)); + } + assert_eq!(MemberCount::::get(), members.len() as u32); + for (who, record) in members.iter() { + assert_eq!(MemberByIndex::::get(record.index).as_ref(), Some(who)); + } + if let Some(founder) = Founder::::get() { + assert_eq!(Members::::get(founder).expect("founder is member").index, 0); + } + if let Some(head) = Head::::get() { + assert!(Members::::contains_key(head)); + } + // Check all votes are valid data. + for (k1, k2) in Votes::::iter_keys() { + assert!(Votes::::get(k1, k2).is_some()); + } + // Check all defender votes are valid data. + for (k1, k2) in DefenderVotes::::iter_keys() { + assert!(DefenderVotes::::get(k1, k2).is_some()); + } + // Check all candidates are valid data. + for k in Candidates::::iter_keys() { + assert!(Candidates::::get(k).is_some()); + } + // Check all suspended members are valid data. + for m in SuspendedMembers::::iter_keys() { + assert!(SuspendedMembers::::get(m).is_some()); + } + // Check all payouts are valid data. + for p in Payouts::::iter_keys() { + let k = Payouts::::hashed_key_for(&p); + let v = frame_support::storage::unhashed::get_raw(&k[..]).expect("value is in map"); + assert!(PayoutRecordFor::::decode(&mut &v[..]).is_ok()); + } + + // We don't use these - make sure they don't exist. + assert_eq!(old::SuspendedCandidates::::iter().count(), 0); + assert_eq!(old::Strikes::::iter().count(), 0); + assert_eq!(old::Vouching::::iter().count(), 0); + assert!(!old::Defender::::exists()); + assert!(!old::Members::::exists()); +} + +pub fn from_original, I: Instance + 'static>( + past_payouts: &mut [(::AccountId, BalanceOf)], +) -> Result { + // Migrate Bids from old::Bids (just a trunctation). + Bids::::put(BoundedVec::<_, T::MaxBids>::truncate_from(old::Bids::::take())); + + // Initialise round counter. + RoundCount::::put(0); + + // Migrate Candidates from old::Candidates + for Bid { who: candidate, kind, value } in old::Candidates::::take().into_iter() { + let mut tally = Tally::default(); + // Migrate Votes from old::Votes + // No need to drain, since we're overwriting values. + for (voter, vote) in old::Votes::::iter_prefix(&candidate) { + Votes::::insert( + &candidate, + &voter, + Vote { approve: vote == old::Vote::Approve, weight: 1 }, + ); + match vote { + old::Vote::Approve => tally.approvals.saturating_inc(), + old::Vote::Reject => tally.rejections.saturating_inc(), + old::Vote::Skeptic => Skeptic::::put(&voter), + } + } + Candidates::::insert( + &candidate, + Candidacy { round: 0, kind, tally, skeptic_struck: false, bid: value }, + ); + } + + // Migrate Members from old::Members old::Strikes old::Vouching + let mut member_count = 0; + for member in old::Members::::take() { + let strikes = old::Strikes::::take(&member); + let vouching = old::Vouching::::take(&member); + let record = MemberRecord { index: member_count, rank: 0, strikes, vouching }; + Members::::insert(&member, record); + MemberByIndex::::insert(member_count, &member); + + // The founder must be the first member in Society V2. If we find the founder not in index + // zero, we swap it with the first member. + if member == Founder::::get().defensive_ok_or("founder must always be set")? && + member_count > 0 + { + let member_to_swap = MemberByIndex::::get(0) + .defensive_ok_or("member_count > 0, we must have at least 1 member")?; + // Swap the founder with the first member in MemberByIndex. + MemberByIndex::::swap(0, member_count); + // Update the indicies of the swapped member MemberRecords. + Members::::mutate(&member, |m| { + if let Some(member) = m { + member.index = 0; + } else { + frame_support::defensive!( + "Member somehow disapeared from storage after it was inserted" + ); + } + }); + Members::::mutate(&member_to_swap, |m| { + if let Some(member) = m { + member.index = member_count; + } else { + frame_support::defensive!( + "Member somehow disapeared from storage after it was queried" + ); + } + }); + } + member_count.saturating_inc(); + } + MemberCount::::put(member_count); + + // Migrate Payouts from: old::Payouts and raw info (needed since we can't query old chain + // state). + past_payouts.sort(); + for (who, mut payouts) in old::Payouts::::iter() { + payouts.truncate(T::MaxPayouts::get() as usize); + // ^^ Safe since we already truncated. + let paid = past_payouts + .binary_search_by_key(&&who, |x| &x.0) + .ok() + .map(|p| past_payouts[p].1) + .unwrap_or(Zero::zero()); + match BoundedVec::try_from(payouts) { + Ok(payouts) => Payouts::::insert(who, PayoutRecord { paid, payouts }), + Err(_) => debug_assert!(false, "Truncation of Payouts ineffective??"), + } + } + + // Migrate SuspendedMembers from old::SuspendedMembers old::Strikes old::Vouching. + for who in old::SuspendedMembers::::iter_keys() { + let strikes = old::Strikes::::take(&who); + let vouching = old::Vouching::::take(&who); + let record = MemberRecord { index: 0, rank: 0, strikes, vouching }; + SuspendedMembers::::insert(&who, record); + } + + // Any suspended candidates remaining are rejected. + let _ = old::SuspendedCandidates::::clear(u32::MAX, None); + + // We give the current defender the benefit of the doubt. + old::Defender::::kill(); + let _ = old::DefenderVotes::::clear(u32::MAX, None); + + Ok(T::BlockWeights::get().max_block) +} + +pub fn from_raw_past_payouts, I: Instance + 'static>( + past_payouts_raw: impl Iterator, +) -> Vec<(::AccountId, BalanceOf)> { + past_payouts_raw + .filter_map(|(x, y)| Some((Decode::decode(&mut &x[..]).ok()?, y.try_into().ok()?))) + .collect() +} diff --git a/frame/society/src/mock.rs b/frame/society/src/mock.rs index 24544bf9e82d..a318c2e794b7 100644 --- a/frame/society/src/mock.rs +++ b/frame/society/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,27 +21,25 @@ use super::*; use crate as pallet_society; use frame_support::{ - ord_parameter_types, parameter_types, + assert_noop, assert_ok, ord_parameter_types, parameter_types, traits::{ConstU32, ConstU64}, }; use frame_support_test::TestRandomness; use frame_system::EnsureSignedBy; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +use RuntimeOrigin as Origin; + type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Society: pallet_society::{Pallet, Call, Storage, Event, Config}, } @@ -52,8 +50,12 @@ parameter_types! { } ord_parameter_types! { + pub const ChallengePeriod: u64 = 8; + pub const ClaimPeriod: u64 = 1; pub const FounderSetAccount: u128 = 1; pub const SuspensionJudgementSetAccount: u128 = 2; + pub const MaxPayouts: u32 = 10; + pub const MaxBids: u32 = 10; } impl frame_system::Config for Test { @@ -62,14 +64,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u128; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -93,38 +94,39 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } impl Config for Test { type RuntimeEvent = RuntimeEvent; + type PalletId = SocietyPalletId; type Currency = pallet_balances::Pallet; type Randomness = TestRandomness; - type CandidateDeposit = ConstU64<25>; - type WrongSideDeduction = ConstU64<2>; - type MaxStrikes = ConstU32<2>; + type GraceStrikes = ConstU32<1>; type PeriodSpend = ConstU64<1000>; - type MembershipChanged = (); - type RotationPeriod = ConstU64<4>; + type VotingPeriod = ConstU64<3>; + type ClaimPeriod = ClaimPeriod; type MaxLockDuration = ConstU64<100>; type FounderSetOrigin = EnsureSignedBy; - type SuspensionJudgementOrigin = EnsureSignedBy; - type ChallengePeriod = ConstU64<8>; - type MaxCandidateIntake = ConstU32<10>; - type PalletId = SocietyPalletId; + type ChallengePeriod = ChallengePeriod; + type MaxPayouts = MaxPayouts; + type MaxBids = MaxBids; + type WeightInfo = (); } pub struct EnvBuilder { - members: Vec, balance: u64, balances: Vec<(u128, u64)>, pot: u64, - max_members: u32, + founded: bool, } impl EnvBuilder { pub fn new() -> Self { Self { - members: vec![10], balance: 10_000, balances: vec![ (10, 50), @@ -138,49 +140,32 @@ impl EnvBuilder { (90, 50), ], pot: 0, - max_members: 100, + founded: true, } } pub fn execute R>(mut self, f: F) -> R { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); self.balances.push((Society::account_id(), self.balance.max(self.pot))); pallet_balances::GenesisConfig:: { balances: self.balances } .assimilate_storage(&mut t) .unwrap(); - pallet_society::GenesisConfig:: { - members: self.members, - pot: self.pot, - max_members: self.max_members, - } - .assimilate_storage(&mut t) - .unwrap(); + pallet_society::GenesisConfig:: { pot: self.pot } + .assimilate_storage(&mut t) + .unwrap(); let mut ext: sp_io::TestExternalities = t.into(); - ext.execute_with(f) - } - #[allow(dead_code)] - pub fn with_members(mut self, m: Vec) -> Self { - self.members = m; - self - } - #[allow(dead_code)] - pub fn with_balances(mut self, b: Vec<(u128, u64)>) -> Self { - self.balances = b; - self - } - #[allow(dead_code)] - pub fn with_pot(mut self, p: u64) -> Self { - self.pot = p; - self - } - #[allow(dead_code)] - pub fn with_balance(mut self, b: u64) -> Self { - self.balance = b; - self + ext.execute_with(|| { + if self.founded { + let r = b"be cool".to_vec(); + assert!(Society::found_society(Origin::signed(1), 10, 10, 8, 2, 25, r).is_ok()); + } + let r = f(); + migrations::assert_internal_consistency::(); + r + }) } - #[allow(dead_code)] - pub fn with_max_members(mut self, n: u32) -> Self { - self.max_members = n; + pub fn founded(mut self, f: bool) -> Self { + self.founded = f; self } } @@ -198,10 +183,121 @@ pub fn run_to_block(n: u64) { } /// Creates a bid struct using input parameters. -pub fn create_bid( - value: Balance, +pub fn bid( who: AccountId, kind: BidKind, + value: Balance, ) -> Bid { Bid { who, kind, value } } + +/// Creates a candidate struct using input parameters. +pub fn candidacy( + round: RoundIndex, + bid: Balance, + kind: BidKind, + approvals: VoteCount, + rejections: VoteCount, +) -> Candidacy { + Candidacy { round, kind, bid, tally: Tally { approvals, rejections }, skeptic_struck: false } +} + +pub fn next_challenge() { + let challenge_period: u64 = ::ChallengePeriod::get(); + let now = System::block_number(); + run_to_block(now + challenge_period - now % challenge_period); +} + +pub fn next_voting() { + if let Period::Voting { more, .. } = Society::period() { + run_to_block(System::block_number() + more); + } +} + +pub fn conclude_intake(allow_resignation: bool, judge_intake: Option) { + next_voting(); + let round = RoundCount::::get(); + for (who, candidacy) in Candidates::::iter() { + if candidacy.tally.clear_approval() { + assert_ok!(Society::claim_membership(Origin::signed(who))); + assert_noop!( + Society::claim_membership(Origin::signed(who)), + Error::::NotCandidate + ); + continue + } + if candidacy.tally.clear_rejection() && allow_resignation { + assert_noop!( + Society::claim_membership(Origin::signed(who)), + Error::::NotApproved + ); + assert_ok!(Society::resign_candidacy(Origin::signed(who))); + continue + } + if let (Some(founder), Some(approve)) = (Founder::::get(), judge_intake) { + if !candidacy.tally.clear_approval() && !approve { + // can be rejected by founder + assert_ok!(Society::kick_candidate(Origin::signed(founder), who)); + continue + } + if !candidacy.tally.clear_rejection() && approve { + // can be rejected by founder + assert_ok!(Society::bestow_membership(Origin::signed(founder), who)); + continue + } + } + if candidacy.tally.clear_rejection() && round > candidacy.round + 1 { + assert_noop!( + Society::claim_membership(Origin::signed(who)), + Error::::NotApproved + ); + assert_ok!(Society::drop_candidate(Origin::signed(0), who)); + assert_noop!( + Society::drop_candidate(Origin::signed(0), who), + Error::::NotCandidate + ); + continue + } + if !candidacy.skeptic_struck { + assert_ok!(Society::punish_skeptic(Origin::signed(who))); + } + } +} + +pub fn next_intake() { + let claim_period: u64 = ::ClaimPeriod::get(); + match Society::period() { + Period::Voting { more, .. } => run_to_block(System::block_number() + more + claim_period), + Period::Claim { more, .. } => run_to_block(System::block_number() + more), + } +} + +pub fn place_members(members: impl AsRef<[u128]>) { + for who in members.as_ref() { + assert_ok!(Society::insert_member(who, 0)); + } +} + +pub fn members() -> Vec { + let mut r = Members::::iter_keys().collect::>(); + r.sort(); + r +} + +pub fn membership() -> Vec<(u128, MemberRecord)> { + let mut r = Members::::iter().collect::>(); + r.sort_by_key(|x| x.0); + r +} + +pub fn candidacies() -> Vec<(u128, Candidacy)> { + let mut r = Candidates::::iter().collect::>(); + r.sort_by_key(|x| x.0); + r +} + +pub fn candidates() -> Vec { + let mut r = Candidates::::iter_keys().collect::>(); + r.sort(); + r +} diff --git a/frame/society/src/tests.rs b/frame/society/src/tests.rs index 864735aa10cc..ea2afef3b32b 100644 --- a/frame/society/src/tests.rs +++ b/frame/society/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,65 +18,196 @@ //! Tests for the module. use super::*; +use migrations::old; use mock::*; use frame_support::{assert_noop, assert_ok}; use sp_core::blake2_256; use sp_runtime::traits::BadOrigin; +use BidKind::*; +use VouchingStatus::*; + +use RuntimeOrigin as Origin; + +#[test] +fn migration_works() { + EnvBuilder::new().founded(false).execute(|| { + use old::Vote::*; + + // Initialise the old storage items. + Founder::::put(10); + Head::::put(30); + old::Members::::put(vec![10, 20, 30]); + old::Vouching::::insert(30, Vouching); + old::Vouching::::insert(40, Banned); + old::Strikes::::insert(20, 1); + old::Strikes::::insert(30, 2); + old::Strikes::::insert(40, 5); + old::Payouts::::insert(20, vec![(1, 1)]); + old::Payouts::::insert( + 30, + (0..=::MaxPayouts::get()) + .map(|i| (i as u64, i as u64)) + .collect::>(), + ); + old::SuspendedMembers::::insert(40, true); + + old::Defender::::put(20); + old::DefenderVotes::::insert(10, Approve); + old::DefenderVotes::::insert(20, Approve); + old::DefenderVotes::::insert(30, Reject); + + old::SuspendedCandidates::::insert(50, (10, Deposit(100))); + + old::Candidates::::put(vec![ + Bid { who: 60, kind: Deposit(100), value: 200 }, + Bid { who: 70, kind: Vouch(30, 30), value: 100 }, + ]); + old::Votes::::insert(60, 10, Approve); + old::Votes::::insert(70, 10, Reject); + old::Votes::::insert(70, 20, Approve); + old::Votes::::insert(70, 30, Approve); + + let bids = (0..=::MaxBids::get()) + .map(|i| Bid { + who: 100u128 + i as u128, + kind: Deposit(20u64 + i as u64), + value: 10u64 + i as u64, + }) + .collect::>(); + old::Bids::::put(bids); + + migrations::from_original::(&mut [][..]).expect("migration failed"); + migrations::assert_internal_consistency::(); + + assert_eq!( + membership(), + vec![ + (10, MemberRecord { rank: 0, strikes: 0, vouching: None, index: 0 }), + (20, MemberRecord { rank: 0, strikes: 1, vouching: None, index: 1 }), + (30, MemberRecord { rank: 0, strikes: 2, vouching: Some(Vouching), index: 2 }), + ] + ); + assert_eq!(Payouts::::get(10), PayoutRecord::default()); + let payouts = vec![(1, 1)].try_into().unwrap(); + assert_eq!(Payouts::::get(20), PayoutRecord { paid: 0, payouts }); + let payouts = (0..::MaxPayouts::get()) + .map(|i| (i as u64, i as u64)) + .collect::>() + .try_into() + .unwrap(); + assert_eq!(Payouts::::get(30), PayoutRecord { paid: 0, payouts }); + assert_eq!( + SuspendedMembers::::iter().collect::>(), + vec![(40, MemberRecord { rank: 0, strikes: 5, vouching: Some(Banned), index: 0 }),] + ); + let bids: BoundedVec<_, ::MaxBids> = (0..::MaxBids::get()) + .map(|i| Bid { + who: 100u128 + i as u128, + kind: Deposit(20u64 + i as u64), + value: 10u64 + i as u64, + }) + .collect::>() + .try_into() + .unwrap(); + assert_eq!(Bids::::get(), bids); + assert_eq!(RoundCount::::get(), 0); + assert_eq!( + candidacies(), + vec![ + ( + 60, + Candidacy { + round: 0, + kind: Deposit(100), + bid: 200, + tally: Tally { approvals: 1, rejections: 0 }, + skeptic_struck: false, + } + ), + ( + 70, + Candidacy { + round: 0, + kind: Vouch(30, 30), + bid: 100, + tally: Tally { approvals: 2, rejections: 1 }, + skeptic_struck: false, + } + ), + ] + ); + assert_eq!(Votes::::get(60, 10), Some(Vote { approve: true, weight: 1 })); + assert_eq!(Votes::::get(70, 10), Some(Vote { approve: false, weight: 1 })); + assert_eq!(Votes::::get(70, 20), Some(Vote { approve: true, weight: 1 })); + assert_eq!(Votes::::get(70, 30), Some(Vote { approve: true, weight: 1 })); + }); +} #[test] fn founding_works() { - EnvBuilder::new().with_max_members(0).with_members(vec![]).execute(|| { + EnvBuilder::new().founded(false).execute(|| { // Not set up initially. - assert_eq!(Society::founder(), None); - assert_eq!(Society::max_members(), 0); - assert_eq!(Society::pot(), 0); + assert_eq!(Founder::::get(), None); + assert_eq!(Parameters::::get(), None); + assert_eq!(Pot::::get(), 0); // Account 1 is set as the founder origin // Account 5 cannot start a society - assert_noop!(Society::found(RuntimeOrigin::signed(5), 20, 100, vec![]), BadOrigin); + assert_noop!( + Society::found_society(Origin::signed(5), 20, 100, 10, 2, 25, vec![]), + BadOrigin + ); // Account 1 can start a society, where 10 is the founding member - assert_ok!(Society::found(RuntimeOrigin::signed(1), 10, 100, b"be cool".to_vec())); + assert_ok!(Society::found_society( + Origin::signed(1), + 10, + 100, + 10, + 2, + 25, + b"be cool".to_vec() + )); // Society members only include 10 - assert_eq!(Society::members(), vec![10]); + assert_eq!(members(), vec![10]); // 10 is the head of the society - assert_eq!(Society::head(), Some(10)); + assert_eq!(Head::::get(), Some(10)); // ...and also the founder - assert_eq!(Society::founder(), Some(10)); + assert_eq!(Founder::::get(), Some(10)); // 100 members max - assert_eq!(Society::max_members(), 100); + assert_eq!(Parameters::::get().unwrap().max_members, 100); // rules are correct - assert_eq!(Society::rules(), Some(blake2_256(b"be cool").into())); + assert_eq!(Rules::::get(), Some(blake2_256(b"be cool").into())); // Pot grows after first rotation period - run_to_block(4); - assert_eq!(Society::pot(), 1000); + next_intake(); + assert_eq!(Pot::::get(), 1000); // Cannot start another society assert_noop!( - Society::found(RuntimeOrigin::signed(1), 20, 100, vec![]), - Error::::AlreadyFounded + Society::found_society(Origin::signed(1), 20, 100, 10, 2, 25, vec![]), + Error::::AlreadyFounded ); }); } #[test] fn unfounding_works() { - EnvBuilder::new().with_max_members(0).with_members(vec![]).execute(|| { + EnvBuilder::new().founded(false).execute(|| { // Account 1 sets the founder... - assert_ok!(Society::found(RuntimeOrigin::signed(1), 10, 100, vec![])); + assert_ok!(Society::found_society(Origin::signed(1), 10, 100, 10, 2, 25, vec![])); // Account 2 cannot unfound it as it's not the founder. - assert_noop!(Society::unfound(RuntimeOrigin::signed(2)), Error::::NotFounder); + assert_noop!(Society::dissolve(Origin::signed(2)), Error::::NotFounder); // Account 10 can, though. - assert_ok!(Society::unfound(RuntimeOrigin::signed(10))); + assert_ok!(Society::dissolve(Origin::signed(10))); // 1 sets the founder to 20 this time - assert_ok!(Society::found(RuntimeOrigin::signed(1), 20, 100, vec![])); + assert_ok!(Society::found_society(Origin::signed(1), 20, 100, 10, 2, 25, vec![])); // Bring in a new member... - assert_ok!(Society::bid(RuntimeOrigin::signed(10), 0)); - run_to_block(4); - assert_ok!(Society::vote(RuntimeOrigin::signed(20), 10, true)); - run_to_block(8); + assert_ok!(Society::bid(Origin::signed(10), 0)); + next_intake(); + assert_ok!(Society::vote(Origin::signed(20), 10, true)); + conclude_intake(true, None); // Unfounding won't work now, even though it's from 20. - assert_noop!(Society::unfound(RuntimeOrigin::signed(20)), Error::::NotHead); + assert_noop!(Society::dissolve(Origin::signed(20)), Error::::NotHead); }); } @@ -89,15 +220,16 @@ fn basic_new_member_works() { assert_eq!(Balances::free_balance(20), 25); assert_eq!(Balances::reserved_balance(20), 25); // Rotate period every 4 blocks - run_to_block(4); + next_intake(); // 20 is now a candidate - assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); + assert_eq!(candidacies(), vec![(20, candidacy(1, 0, Deposit(25), 0, 0))]); // 10 (a member) can vote for the candidate - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 20, true)); + assert_ok!(Society::vote(Origin::signed(10), 20, true)); + conclude_intake(true, None); // Rotate period every 4 blocks - run_to_block(8); + next_intake(); // 20 is now a member of the society - assert_eq!(Society::members(), vec![10, 20]); + assert_eq!(members(), vec![10, 20]); // Reserved balance is returned assert_eq!(Balances::free_balance(20), 50); assert_eq!(Balances::reserved_balance(20), 0); @@ -113,57 +245,60 @@ fn bidding_works() { assert_ok!(Society::bid(RuntimeOrigin::signed(40), 400)); assert_ok!(Society::bid(RuntimeOrigin::signed(30), 300)); // Rotate period - run_to_block(4); + next_intake(); // Pot is 1000 after "PeriodSpend" - assert_eq!(Society::pot(), 1000); + assert_eq!(Pot::::get(), 1000); assert_eq!(Balances::free_balance(Society::account_id()), 10_000); // Choose smallest bidding users whose total is less than pot assert_eq!( - Society::candidates(), + candidacies(), vec![ - create_bid(300, 30, BidKind::Deposit(25)), - create_bid(400, 40, BidKind::Deposit(25)), + (30, candidacy(1, 300, Deposit(25), 0, 0)), + (40, candidacy(1, 400, Deposit(25), 0, 0)), ] ); // A member votes for these candidates to join the society - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 30, true)); - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 40, true)); - run_to_block(8); + assert_ok!(Society::vote(Origin::signed(10), 30, true)); + assert_ok!(Society::vote(Origin::signed(10), 40, true)); + conclude_intake(true, None); + next_intake(); // Candidates become members after a period rotation - assert_eq!(Society::members(), vec![10, 30, 40]); + assert_eq!(members(), vec![10, 30, 40]); // Pot is increased by 1000, but pays out 700 to the members assert_eq!(Balances::free_balance(Society::account_id()), 9_300); - assert_eq!(Society::pot(), 1_300); + assert_eq!(Pot::::get(), 1_300); // Left over from the original bids is 50 who satisfies the condition of bid less than pot. - assert_eq!(Society::candidates(), vec![create_bid(500, 50, BidKind::Deposit(25))]); + assert_eq!(candidacies(), vec![(50, candidacy(2, 500, Deposit(25), 0, 0))]); // 40, now a member, can vote for 50 - assert_ok!(Society::vote(RuntimeOrigin::signed(40), 50, true)); + assert_ok!(Society::vote(Origin::signed(40), 50, true)); + conclude_intake(true, None); run_to_block(12); // 50 is now a member - assert_eq!(Society::members(), vec![10, 30, 40, 50]); + assert_eq!(members(), vec![10, 30, 40, 50]); // Pot is increased by 1000, and 500 is paid out. Total payout so far is 1200. - assert_eq!(Society::pot(), 1_800); + assert_eq!(Pot::::get(), 1_800); assert_eq!(Balances::free_balance(Society::account_id()), 8_800); // No more candidates satisfy the requirements - assert_eq!(Society::candidates(), vec![]); - assert_ok!(Society::defender_vote(RuntimeOrigin::signed(10), true)); // Keep defender around - // Next period + assert_eq!(candidacies(), vec![]); + assert_ok!(Society::defender_vote(Origin::signed(10), true)); // Keep defender around + // Next period run_to_block(16); // Same members - assert_eq!(Society::members(), vec![10, 30, 40, 50]); + assert_eq!(members(), vec![10, 30, 40, 50]); // Pot is increased by 1000 again - assert_eq!(Society::pot(), 2_800); + assert_eq!(Pot::::get(), 2_800); // No payouts assert_eq!(Balances::free_balance(Society::account_id()), 8_800); // Candidate 60 now qualifies based on the increased pot size. - assert_eq!(Society::candidates(), vec![create_bid(1900, 60, BidKind::Deposit(25))]); + assert_eq!(candidacies(), vec![(60, candidacy(4, 1900, Deposit(25), 0, 0))]); // Candidate 60 is voted in. - assert_ok!(Society::vote(RuntimeOrigin::signed(50), 60, true)); + assert_ok!(Society::vote(Origin::signed(50), 60, true)); + conclude_intake(true, None); run_to_block(20); // 60 joins as a member - assert_eq!(Society::members(), vec![10, 30, 40, 50, 60]); + assert_eq!(members(), vec![10, 30, 40, 50, 60]); // Pay them - assert_eq!(Society::pot(), 1_900); + assert_eq!(Pot::::get(), 1_900); assert_eq!(Balances::free_balance(Society::account_id()), 6_900); }); } @@ -177,16 +312,15 @@ fn unbidding_works() { // Balances are reserved assert_eq!(Balances::free_balance(30), 25); assert_eq!(Balances::reserved_balance(30), 25); - // Must know right position to unbid + cannot unbid someone else - assert_noop!(Society::unbid(RuntimeOrigin::signed(30), 1), Error::::BadPosition); // Can unbid themselves with the right position - assert_ok!(Society::unbid(RuntimeOrigin::signed(30), 0)); + assert_ok!(Society::unbid(Origin::signed(30))); + assert_noop!(Society::unbid(Origin::signed(30)), Error::::NotBidder); // Balance is returned assert_eq!(Balances::free_balance(30), 50); assert_eq!(Balances::reserved_balance(30), 0); // 20 wins candidacy - run_to_block(4); - assert_eq!(Society::candidates(), vec![create_bid(1000, 20, BidKind::Deposit(25))]); + next_intake(); + assert_eq!(candidacies(), vec![(20, candidacy(1, 1000, Deposit(25), 0, 0))]); }); } @@ -195,13 +329,13 @@ fn payout_works() { EnvBuilder::new().execute(|| { // Original balance of 50 assert_eq!(Balances::free_balance(20), 50); - assert_ok!(Society::bid(RuntimeOrigin::signed(20), 1000)); - run_to_block(4); - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 20, true)); - run_to_block(8); + assert_ok!(Society::bid(Origin::signed(20), 1000)); + next_intake(); + assert_ok!(Society::vote(Origin::signed(10), 20, true)); + conclude_intake(true, None); // payout not ready - assert_noop!(Society::payout(RuntimeOrigin::signed(20)), Error::::NoPayout); - run_to_block(9); + assert_noop!(Society::payout(Origin::signed(20)), Error::::NoPayout); + next_intake(); // payout should be here assert_ok!(Society::payout(RuntimeOrigin::signed(20))); assert_eq!(Balances::free_balance(20), 1050); @@ -209,15 +343,34 @@ fn payout_works() { } #[test] -fn basic_new_member_skeptic_works() { +fn non_voting_skeptic_is_punished() { EnvBuilder::new().execute(|| { - assert_eq!(Strikes::::get(10), 0); - assert_ok!(Society::bid(RuntimeOrigin::signed(20), 0)); - run_to_block(4); - assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); - run_to_block(8); - assert_eq!(Society::members(), vec![10]); - assert_eq!(Strikes::::get(10), 1); + assert_eq!(Members::::get(10).unwrap().strikes, 0); + assert_ok!(Society::bid(Origin::signed(20), 0)); + next_intake(); + assert_eq!(candidacies(), vec![(20, candidacy(1, 0, Deposit(25), 0, 0))]); + conclude_intake(true, None); + next_intake(); + assert_eq!(members(), vec![10]); + assert_eq!(Members::::get(10).unwrap().strikes, 1); + }); +} + +#[test] +fn rejecting_skeptic_on_approved_is_punished() { + EnvBuilder::new().execute(|| { + place_members([20, 30]); + assert_ok!(Society::bid(Origin::signed(40), 0)); + next_intake(); + let skeptic = Skeptic::::get().unwrap(); + for &i in &[10, 20, 30][..] { + assert_ok!(Society::vote(Origin::signed(i), 40, i != skeptic)); + } + conclude_intake(true, None); + assert_eq!(Members::::get(10).unwrap().strikes, 0); + run_to_block(12); + assert_eq!(members(), vec![10, 20, 30, 40]); + assert_eq!(Members::::get(skeptic).unwrap().strikes, 1); }); } @@ -231,16 +384,17 @@ fn basic_new_member_reject_works() { assert_eq!(Balances::free_balance(20), 25); assert_eq!(Balances::reserved_balance(20), 25); // Rotation Period - run_to_block(4); - assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); + next_intake(); + assert_eq!(candidacies(), vec![(20, candidacy(1, 0, Deposit(25), 0, 0))]); // We say no - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 20, false)); - run_to_block(8); + assert_ok!(Society::vote(Origin::signed(10), 20, false)); + conclude_intake(true, None); + next_intake(); // User is not added as member - assert_eq!(Society::members(), vec![10]); - // User is suspended - assert_eq!(Society::candidates(), vec![]); - assert_eq!(Society::suspended_candidate(20).is_some(), true); + assert_eq!(members(), vec![10]); + // User is rejected. + assert_eq!(candidacies(), vec![]); + assert_eq!(Bids::::get().into_inner(), vec![]); }); } @@ -248,20 +402,30 @@ fn basic_new_member_reject_works() { fn slash_payout_works() { EnvBuilder::new().execute(|| { assert_eq!(Balances::free_balance(20), 50); - assert_ok!(Society::bid(RuntimeOrigin::signed(20), 1000)); - run_to_block(4); - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 20, true)); - run_to_block(8); + assert_ok!(Society::bid(Origin::signed(20), 1000)); + next_intake(); + assert_ok!(Society::vote(Origin::signed(10), 20, true)); + conclude_intake(true, None); // payout in queue - assert_eq!(Payouts::::get(20), vec![(9, 1000)]); - assert_noop!(Society::payout(RuntimeOrigin::signed(20)), Error::::NoPayout); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(8, 1000)].try_into().unwrap() } + ); + assert_noop!(Society::payout(Origin::signed(20)), Error::::NoPayout); // slash payout assert_eq!(Society::slash_payout(&20, 500), 500); - assert_eq!(Payouts::::get(20), vec![(9, 500)]); - run_to_block(9); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(8, 500)].try_into().unwrap() } + ); + run_to_block(8); // payout should be here, but 500 less assert_ok!(Society::payout(RuntimeOrigin::signed(20))); assert_eq!(Balances::free_balance(20), 550); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 500, payouts: Default::default() } + ); }); } @@ -269,19 +433,32 @@ fn slash_payout_works() { fn slash_payout_multi_works() { EnvBuilder::new().execute(|| { assert_eq!(Balances::free_balance(20), 50); + place_members([20]); // create a few payouts Society::bump_payout(&20, 5, 100); Society::bump_payout(&20, 10, 100); Society::bump_payout(&20, 15, 100); Society::bump_payout(&20, 20, 100); // payouts in queue - assert_eq!(Payouts::::get(20), vec![(5, 100), (10, 100), (15, 100), (20, 100)]); + assert_eq!( + Payouts::::get(20), + PayoutRecord { + paid: 0, + payouts: vec![(5, 100), (10, 100), (15, 100), (20, 100)].try_into().unwrap() + } + ); // slash payout assert_eq!(Society::slash_payout(&20, 250), 250); - assert_eq!(Payouts::::get(20), vec![(15, 50), (20, 100)]); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(15, 50), (20, 100)].try_into().unwrap() } + ); // slash again assert_eq!(Society::slash_payout(&20, 50), 50); - assert_eq!(Payouts::::get(20), vec![(20, 100)]); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(20, 100)].try_into().unwrap() } + ); }); } @@ -289,172 +466,226 @@ fn slash_payout_multi_works() { fn suspended_member_life_cycle_works() { EnvBuilder::new().execute(|| { // Add 20 to members, who is not the head and can be suspended/removed. - assert_ok!(Society::add_member(&20)); - assert_eq!(>::get(), vec![10, 20]); - assert_eq!(Strikes::::get(20), 0); - assert_eq!(>::get(20), false); + place_members([20]); + assert_eq!(members(), vec![10, 20]); + assert_eq!(Members::::get(20).unwrap().strikes, 0); + assert!(!SuspendedMembers::::contains_key(20)); // Let's suspend account 20 by giving them 2 strikes by not voting - assert_ok!(Society::bid(RuntimeOrigin::signed(30), 0)); - run_to_block(8); - assert_eq!(Strikes::::get(20), 1); - assert_ok!(Society::bid(RuntimeOrigin::signed(40), 0)); - run_to_block(16); + assert_ok!(Society::bid(Origin::signed(30), 0)); + assert_ok!(Society::bid(Origin::signed(40), 1)); + next_intake(); + conclude_intake(false, None); - // Strike 2 is accumulated, and 20 is suspended :( - assert_eq!(>::get(20), true); - assert_eq!(>::get(), vec![10]); + // 2 strikes are accumulated, and 20 is suspended :( + assert!(SuspendedMembers::::contains_key(20)); + assert_eq!(members(), vec![10]); // Suspended members cannot get payout Society::bump_payout(&20, 10, 100); - assert_noop!(Society::payout(RuntimeOrigin::signed(20)), Error::::NotMember); + assert_noop!(Society::payout(Origin::signed(20)), Error::::NotMember); // Normal people cannot make judgement assert_noop!( - Society::judge_suspended_member(RuntimeOrigin::signed(20), 20, true), - BadOrigin + Society::judge_suspended_member(Origin::signed(20), 20, true), + Error::::NotFounder ); // Suspension judgment origin can judge thee // Suspension judgement origin forgives the suspended member - assert_ok!(Society::judge_suspended_member(RuntimeOrigin::signed(2), 20, true)); - assert_eq!(>::get(20), false); - assert_eq!(>::get(), vec![10, 20]); + assert_ok!(Society::judge_suspended_member(Origin::signed(10), 20, true)); + assert!(!SuspendedMembers::::contains_key(20)); + assert_eq!(members(), vec![10, 20]); // Let's suspend them again, directly - Society::suspend_member(&20); - assert_eq!(>::get(20), true); + assert_ok!(Society::suspend_member(&20)); + assert!(SuspendedMembers::::contains_key(20)); // Suspension judgement origin does not forgive the suspended member - assert_ok!(Society::judge_suspended_member(RuntimeOrigin::signed(2), 20, false)); + assert_ok!(Society::judge_suspended_member(Origin::signed(10), 20, false)); // Cleaned up - assert_eq!(>::get(20), false); - assert_eq!(>::get(), vec![10]); - assert_eq!(>::get(20), vec![]); + assert!(!SuspendedMembers::::contains_key(20)); + assert_eq!(members(), vec![10]); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() } + ); }); } #[test] fn suspended_candidate_rejected_works() { EnvBuilder::new().execute(|| { - // Starting Balance - assert_eq!(Balances::free_balance(20), 50); - assert_eq!(Balances::free_balance(Society::account_id()), 10000); - // 20 makes a bid - assert_ok!(Society::bid(RuntimeOrigin::signed(20), 0)); - assert_eq!(Balances::free_balance(20), 25); - assert_eq!(Balances::reserved_balance(20), 25); - // Rotation Period - run_to_block(4); - assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); - // We say no - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 20, false)); - run_to_block(8); - // User is not added as member - assert_eq!(Society::members(), vec![10]); - // User is suspended - assert_eq!(Society::candidates(), vec![]); - assert_eq!(Society::suspended_candidate(20).is_some(), true); + place_members([20, 30]); + // 40, 50, 60, 70, 80 make bids + for &x in &[40u128, 50, 60, 70] { + assert_ok!(Society::bid(Origin::signed(x), 10)); + assert_eq!(Balances::free_balance(x), 25); + assert_eq!(Balances::reserved_balance(x), 25); + } - // Normal user cannot make judgement on suspended candidate - assert_noop!( - Society::judge_suspended_candidate(RuntimeOrigin::signed(20), 20, Judgement::Approve), - BadOrigin + // Rotation Period + next_intake(); + assert_eq!( + candidacies(), + vec![ + (40, candidacy(1, 10, Deposit(25), 0, 0)), + (50, candidacy(1, 10, Deposit(25), 0, 0)), + (60, candidacy(1, 10, Deposit(25), 0, 0)), + (70, candidacy(1, 10, Deposit(25), 0, 0)), + ] ); - // Suspension judgement origin makes no direct judgement - assert_ok!(Society::judge_suspended_candidate( - RuntimeOrigin::signed(2), - 20, - Judgement::Rebid - )); - // They are placed back in bid pool, repeat suspension process - // Rotation Period - run_to_block(12); - assert_eq!(Society::candidates(), vec![create_bid(0, 20, BidKind::Deposit(25))]); - // We say no - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 20, false)); - run_to_block(16); - // User is not added as member - assert_eq!(Society::members(), vec![10]); - // User is suspended - assert_eq!(Society::candidates(), vec![]); - assert_eq!(Society::suspended_candidate(20).is_some(), true); - - // Suspension judgement origin rejects the candidate - assert_ok!(Society::judge_suspended_candidate( - RuntimeOrigin::signed(2), - 20, - Judgement::Reject - )); - // User is slashed - assert_eq!(Balances::free_balance(20), 25); - assert_eq!(Balances::reserved_balance(20), 0); - // Funds are deposited to society account - assert_eq!(Balances::free_balance(Society::account_id()), 10025); - // Cleaned up - assert_eq!(Society::candidates(), vec![]); - assert_eq!(>::get(20), None); + // Split vote over all. + for &x in &[40, 50, 60, 70] { + assert_ok!(Society::vote(Origin::signed(20), x, false)); + assert_ok!(Society::vote(Origin::signed(30), x, true)); + } + + // Voting continues, as no canidate is clearly accepted yet and the founder chooses not to + // act. + conclude_intake(false, None); + assert_eq!(members(), vec![10, 20, 30]); + assert_eq!(candidates(), vec![40, 50, 60, 70]); + + // 40 gets approved after founder weighs in giving it a clear approval. + // but the founder's rejection of 60 doesn't do much for now. + assert_ok!(Society::vote(Origin::signed(10), 40, true)); + assert_ok!(Society::vote(Origin::signed(10), 60, false)); + conclude_intake(false, None); + assert_eq!(members(), vec![10, 20, 30, 40]); + assert_eq!(candidates(), vec![50, 60, 70]); + assert_eq!(Balances::free_balance(40), 50); + assert_eq!(Balances::reserved_balance(40), 0); + assert_eq!(Balances::free_balance(Society::account_id()), 9990); + + // Founder manually bestows membership on 50 and and kicks 70. + assert_ok!(Society::bestow_membership(Origin::signed(10), 50)); + assert_eq!(members(), vec![10, 20, 30, 40, 50]); + assert_eq!(candidates(), vec![60, 70]); + assert_eq!(Balances::free_balance(50), 50); + assert_eq!(Balances::reserved_balance(50), 0); + assert_eq!(Balances::free_balance(Society::account_id()), 9980); + + assert_eq!(Balances::free_balance(70), 25); + assert_eq!(Balances::reserved_balance(70), 25); + + assert_ok!(Society::kick_candidate(Origin::signed(10), 70)); + assert_eq!(members(), vec![10, 20, 30, 40, 50]); + assert_eq!(candidates(), vec![60]); + assert_eq!(Balances::free_balance(70), 25); + assert_eq!(Balances::reserved_balance(70), 0); + assert_eq!(Balances::free_balance(Society::account_id()), 10005); + + // Next round doesn't make much difference. + next_intake(); + conclude_intake(false, None); + assert_eq!(members(), vec![10, 20, 30, 40, 50]); + assert_eq!(candidates(), vec![60]); + assert_eq!(Balances::free_balance(Society::account_id()), 10005); + + // But after two rounds, the clearly rejected 60 gets dropped and slashed. + next_intake(); + conclude_intake(false, None); + assert_eq!(members(), vec![10, 20, 30, 40, 50]); + assert_eq!(candidates(), vec![]); + assert_eq!(Balances::free_balance(60), 25); + assert_eq!(Balances::reserved_balance(60), 0); + assert_eq!(Balances::free_balance(Society::account_id()), 10030); }); } #[test] -fn vouch_works() { +fn unpaid_vouch_works() { EnvBuilder::new().execute(|| { // 10 is the only member - assert_eq!(Society::members(), vec![10]); + assert_eq!(members(), vec![10]); // A non-member cannot vouch - assert_noop!( - Society::vouch(RuntimeOrigin::signed(1), 20, 1000, 100), - Error::::NotMember - ); + assert_noop!(Society::vouch(Origin::signed(1), 20, 1000, 100), Error::::NotMember); // A member can though - assert_ok!(Society::vouch(RuntimeOrigin::signed(10), 20, 1000, 100)); - assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); + assert_ok!(Society::vouch(Origin::signed(10), 20, 1000, 100)); + assert_eq!(Members::::get(10).unwrap().vouching, Some(VouchingStatus::Vouching)); // A member cannot vouch twice at the same time assert_noop!( - Society::vouch(RuntimeOrigin::signed(10), 30, 100, 0), - Error::::AlreadyVouching + Society::vouch(Origin::signed(10), 30, 100, 0), + Error::::AlreadyVouching ); // Vouching creates the right kind of bid - assert_eq!(>::get(), vec![create_bid(1000, 20, BidKind::Vouch(10, 100))]); + assert_eq!(Bids::::get().into_inner(), vec![bid(20, Vouch(10, 100), 1000)]); // Vouched user can become candidate - run_to_block(4); - assert_eq!(Society::candidates(), vec![create_bid(1000, 20, BidKind::Vouch(10, 100))]); + next_intake(); + assert_eq!(candidacies(), vec![(20, candidacy(1, 1000, Vouch(10, 100), 0, 0))]); // Vote yes assert_ok!(Society::vote(RuntimeOrigin::signed(10), 20, true)); // Vouched user can win - run_to_block(8); - assert_eq!(Society::members(), vec![10, 20]); + conclude_intake(true, None); + assert_eq!(members(), vec![10, 20]); + // Vouched user gets whatever remains after the voucher's reservation. + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(8, 900)].try_into().unwrap() } + ); + // 10 is no longer vouching + assert_eq!(Members::::get(10).unwrap().vouching, None); + }); +} + +#[test] +fn paid_vouch_works() { + EnvBuilder::new().execute(|| { + place_members([20]); + assert_eq!(members(), vec![10, 20]); + + assert_ok!(Society::vouch(Origin::signed(20), 30, 1000, 100)); + assert_eq!(Members::::get(20).unwrap().vouching, Some(VouchingStatus::Vouching)); + assert_eq!(Bids::::get().into_inner(), vec![bid(30, Vouch(20, 100), 1000)]); + + next_intake(); + assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, Vouch(20, 100), 0, 0))]); + assert_ok!(Society::vote(Origin::signed(20), 30, true)); + conclude_intake(true, None); + + assert_eq!(members(), vec![10, 20, 30]); // Voucher wins a portion of the payment - assert_eq!(>::get(10), vec![(9, 100)]); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(8, 100)].try_into().unwrap() } + ); // Vouched user wins the rest - assert_eq!(>::get(20), vec![(9, 900)]); - // 10 is no longer vouching - assert_eq!(>::get(10), None); + assert_eq!( + Payouts::::get(30), + PayoutRecord { paid: 0, payouts: vec![(8, 900)].try_into().unwrap() } + ); + // 20 is no longer vouching + assert_eq!(Members::::get(20).unwrap().vouching, None); }); } #[test] fn voucher_cannot_win_more_than_bid() { EnvBuilder::new().execute(|| { - // 10 is the only member - assert_eq!(Society::members(), vec![10]); - // 10 vouches, but asks for more than the bid - assert_ok!(Society::vouch(RuntimeOrigin::signed(10), 20, 100, 1000)); + place_members([20]); + // 20 vouches, but asks for more than the bid + assert_ok!(Society::vouch(Origin::signed(20), 30, 100, 1000)); // Vouching creates the right kind of bid - assert_eq!(>::get(), vec![create_bid(100, 20, BidKind::Vouch(10, 1000))]); + assert_eq!(Bids::::get().into_inner(), vec![bid(30, Vouch(20, 1000), 100)]); // Vouched user can become candidate - run_to_block(4); - assert_eq!(Society::candidates(), vec![create_bid(100, 20, BidKind::Vouch(10, 1000))]); + next_intake(); + assert_eq!(candidacies(), vec![(30, candidacy(1, 100, Vouch(20, 1000), 0, 0))]); // Vote yes - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 20, true)); + assert_ok!(Society::vote(Origin::signed(20), 30, true)); // Vouched user can win - run_to_block(8); - assert_eq!(Society::members(), vec![10, 20]); + conclude_intake(true, None); + assert_eq!(members(), vec![10, 20, 30]); // Voucher wins as much as the bid - assert_eq!(>::get(10), vec![(9, 100)]); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(8, 100)].try_into().unwrap() } + ); // Vouched user gets nothing - assert_eq!(>::get(20), vec![]); + assert_eq!( + Payouts::::get(30), + PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() } + ); }); } @@ -462,52 +693,49 @@ fn voucher_cannot_win_more_than_bid() { fn unvouch_works() { EnvBuilder::new().execute(|| { // 10 is the only member - assert_eq!(Society::members(), vec![10]); + assert_eq!(members(), vec![10]); // 10 vouches for 20 assert_ok!(Society::vouch(RuntimeOrigin::signed(10), 20, 100, 0)); // 20 has a bid - assert_eq!(>::get(), vec![create_bid(100, 20, BidKind::Vouch(10, 0))]); + assert_eq!(Bids::::get().into_inner(), vec![bid(20, Vouch(10, 0), 100)]); // 10 is vouched - assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); - // To unvouch, you must know the right bid position - assert_noop!(Society::unvouch(RuntimeOrigin::signed(10), 2), Error::::BadPosition); - // 10 can unvouch with the right position - assert_ok!(Society::unvouch(RuntimeOrigin::signed(10), 0)); + assert_eq!(Members::::get(10).unwrap().vouching, Some(VouchingStatus::Vouching)); + // 10 can unvouch + assert_ok!(Society::unvouch(Origin::signed(10))); // 20 no longer has a bid - assert_eq!(>::get(), vec![]); + assert_eq!(Bids::::get().into_inner(), vec![]); // 10 is no longer vouching - assert_eq!(>::get(10), None); + assert_eq!(Members::::get(10).unwrap().vouching, None); // Cannot unvouch after they become candidate - assert_ok!(Society::vouch(RuntimeOrigin::signed(10), 20, 100, 0)); - run_to_block(4); - assert_eq!(Society::candidates(), vec![create_bid(100, 20, BidKind::Vouch(10, 0))]); - assert_noop!(Society::unvouch(RuntimeOrigin::signed(10), 0), Error::::BadPosition); + assert_ok!(Society::vouch(Origin::signed(10), 20, 100, 0)); + next_intake(); + assert_eq!(candidacies(), vec![(20, candidacy(1, 100, Vouch(10, 0), 0, 0))]); + assert_noop!(Society::unvouch(Origin::signed(10)), Error::::NotVouchingOnBidder); + // 10 is still vouching until candidate is approved or rejected - assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); - run_to_block(8); - // In this case candidate is denied and suspended - assert!(Society::suspended_candidate(&20).is_some()); - assert_eq!(Society::members(), vec![10]); - // User is stuck vouching until judgement origin resolves suspended candidate - assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); - // Judge denies candidate - assert_ok!(Society::judge_suspended_candidate( - RuntimeOrigin::signed(2), - 20, - Judgement::Reject - )); - // 10 is banned from vouching - assert_eq!(>::get(10), Some(VouchingStatus::Banned)); - assert_eq!(Society::members(), vec![10]); + assert_eq!(Members::::get(10).unwrap().vouching, Some(VouchingStatus::Vouching)); + // Voucher inexplicably votes against their pick. + assert_ok!(Society::vote(Origin::signed(10), 20, false)); + // But their pick doesn't resign (yet). + conclude_intake(false, None); + // Voting still happening and voucher cannot unvouch. + assert_eq!(candidacies(), vec![(20, candidacy(1, 100, Vouch(10, 0), 0, 1))]); + assert_eq!(Members::::get(10).unwrap().vouching, Some(VouchingStatus::Vouching)); + + // Candidate gives in and resigns. + conclude_intake(true, None); + // Vouxher (10) is banned from vouching. + assert_eq!(Members::::get(10).unwrap().vouching, Some(VouchingStatus::Banned)); + assert_eq!(members(), vec![10]); // 10 cannot vouch again assert_noop!( - Society::vouch(RuntimeOrigin::signed(10), 30, 100, 0), - Error::::AlreadyVouching + Society::vouch(Origin::signed(10), 30, 100, 0), + Error::::AlreadyVouching ); // 10 cannot unvouch either, so they are banned forever. - assert_noop!(Society::unvouch(RuntimeOrigin::signed(10), 0), Error::::NotVouching); + assert_noop!(Society::unvouch(Origin::signed(10)), Error::::NotVouchingOnBidder); }); } @@ -515,18 +743,18 @@ fn unvouch_works() { fn unbid_vouch_works() { EnvBuilder::new().execute(|| { // 10 is the only member - assert_eq!(Society::members(), vec![10]); + assert_eq!(members(), vec![10]); // 10 vouches for 20 assert_ok!(Society::vouch(RuntimeOrigin::signed(10), 20, 100, 0)); // 20 has a bid - assert_eq!(>::get(), vec![create_bid(100, 20, BidKind::Vouch(10, 0))]); + assert_eq!(Bids::::get().into_inner(), vec![bid(20, Vouch(10, 0), 100)]); // 10 is vouched - assert_eq!(>::get(10), Some(VouchingStatus::Vouching)); + assert_eq!(Members::::get(10).unwrap().vouching, Some(VouchingStatus::Vouching)); // 20 doesn't want to be a member and can unbid themselves. - assert_ok!(Society::unbid(RuntimeOrigin::signed(20), 0)); + assert_ok!(Society::unbid(Origin::signed(20))); // Everything is cleaned up - assert_eq!(>::get(10), None); - assert_eq!(>::get(), vec![]); + assert_eq!(Members::::get(10).unwrap().vouching, None); + assert_eq!(Bids::::get().into_inner(), vec![]); }); } @@ -534,60 +762,73 @@ fn unbid_vouch_works() { fn founder_and_head_cannot_be_removed() { EnvBuilder::new().execute(|| { // 10 is the only member, founder, and head - assert_eq!(Society::members(), vec![10]); - assert_eq!(Society::founder(), Some(10)); - assert_eq!(Society::head(), Some(10)); + assert_eq!(members(), vec![10]); + assert_eq!(Founder::::get(), Some(10)); + assert_eq!(Head::::get(), Some(10)); // 10 can still accumulate strikes - assert_ok!(Society::bid(RuntimeOrigin::signed(20), 0)); - run_to_block(8); - assert_eq!(Strikes::::get(10), 1); - assert_ok!(Society::bid(RuntimeOrigin::signed(30), 0)); - run_to_block(16); - assert_eq!(Strikes::::get(10), 2); + assert_ok!(Society::bid(Origin::signed(20), 0)); + next_intake(); + conclude_intake(false, None); + assert_eq!(Members::::get(10).unwrap().strikes, 1); + assert_ok!(Society::bid(Origin::signed(30), 0)); + next_intake(); + conclude_intake(false, None); + assert_eq!(Members::::get(10).unwrap().strikes, 2); // Awkwardly they can obtain more than MAX_STRIKES... - assert_ok!(Society::bid(RuntimeOrigin::signed(40), 0)); - run_to_block(24); - assert_eq!(Strikes::::get(10), 3); + assert_ok!(Society::bid(Origin::signed(40), 0)); + next_intake(); + conclude_intake(false, None); + assert_eq!(Members::::get(10).unwrap().strikes, 3); // Replace the head - assert_ok!(Society::bid(RuntimeOrigin::signed(50), 0)); - run_to_block(28); - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 50, true)); - assert_ok!(Society::defender_vote(RuntimeOrigin::signed(10), true)); // Keep defender around - run_to_block(32); - assert_eq!(Society::members(), vec![10, 50]); - assert_eq!(Society::head(), Some(50)); + assert_ok!(Society::bid(Origin::signed(50), 0)); + next_intake(); + assert_ok!(Society::vote(Origin::signed(10), 50, true)); + conclude_intake(false, None); + assert_eq!(members(), vec![10, 50]); + assert_eq!(Head::::get(), Some(10)); + next_intake(); + assert_eq!(Head::::get(), Some(50)); // Founder is unchanged - assert_eq!(Society::founder(), Some(10)); + assert_eq!(Founder::::get(), Some(10)); // 50 can still accumulate strikes - assert_ok!(Society::bid(RuntimeOrigin::signed(60), 0)); - run_to_block(40); - assert_eq!(Strikes::::get(50), 1); - assert_ok!(Society::bid(RuntimeOrigin::signed(70), 0)); - run_to_block(48); - assert_eq!(Strikes::::get(50), 2); + assert_ok!(Society::bid(Origin::signed(60), 0)); + next_intake(); + // Force 50 to be Skeptic so it gets a strike. + Skeptic::::put(50); + conclude_intake(false, None); + assert_eq!(Members::::get(50).unwrap().strikes, 1); + assert_ok!(Society::bid(Origin::signed(70), 0)); + next_intake(); + // Force 50 to be Skeptic so it gets a strike. + Skeptic::::put(50); + conclude_intake(false, None); + assert_eq!(Members::::get(50).unwrap().strikes, 2); // Replace the head - assert_ok!(Society::bid(RuntimeOrigin::signed(80), 0)); - run_to_block(52); - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 80, true)); - assert_ok!(Society::vote(RuntimeOrigin::signed(50), 80, true)); - assert_ok!(Society::defender_vote(RuntimeOrigin::signed(10), true)); // Keep defender around - run_to_block(56); - assert_eq!(Society::members(), vec![10, 50, 80]); - assert_eq!(Society::head(), Some(80)); - assert_eq!(Society::founder(), Some(10)); + assert_ok!(Society::bid(Origin::signed(80), 0)); + next_intake(); + assert_ok!(Society::vote(Origin::signed(10), 80, true)); + assert_ok!(Society::vote(Origin::signed(50), 80, true)); + conclude_intake(false, None); + next_intake(); + assert_eq!(members(), vec![10, 50, 80]); + assert_eq!(Head::::get(), Some(80)); + assert_eq!(Founder::::get(), Some(10)); // 50 can now be suspended for strikes - assert_ok!(Society::bid(RuntimeOrigin::signed(90), 0)); - run_to_block(60); - // The candidate is rejected, so voting approve will give a strike - assert_ok!(Society::vote(RuntimeOrigin::signed(50), 90, true)); - run_to_block(64); - assert_eq!(Strikes::::get(50), 0); - assert_eq!(>::get(50), true); - assert_eq!(Society::members(), vec![10, 80]); + assert_ok!(Society::bid(Origin::signed(90), 0)); + next_intake(); + // Force 50 to be Skeptic and get it a strike. + Skeptic::::put(50); + conclude_intake(false, None); + next_intake(); + assert_eq!( + SuspendedMembers::::get(50), + Some(MemberRecord { rank: 0, strikes: 3, vouching: None, index: 1 }) + ); + assert_eq!(members(), vec![10, 80]); }); } @@ -595,63 +836,72 @@ fn founder_and_head_cannot_be_removed() { fn challenges_work() { EnvBuilder::new().execute(|| { // Add some members - assert_ok!(Society::add_member(&20)); - assert_ok!(Society::add_member(&30)); - assert_ok!(Society::add_member(&40)); + place_members([20, 30, 40]); // Votes are empty - assert_eq!(>::get(10), None); - assert_eq!(>::get(20), None); - assert_eq!(>::get(30), None); - assert_eq!(>::get(40), None); + assert_eq!(DefenderVotes::::get(0, 10), None); + assert_eq!(DefenderVotes::::get(0, 20), None); + assert_eq!(DefenderVotes::::get(0, 30), None); + assert_eq!(DefenderVotes::::get(0, 40), None); // Check starting point - assert_eq!(Society::members(), vec![10, 20, 30, 40]); - assert_eq!(Society::defender(), None); - // 20 will be challenged during the challenge rotation - run_to_block(8); - assert_eq!(Society::defender(), Some(30)); + assert_eq!(members(), vec![10, 20, 30, 40]); + assert_eq!(Defending::::get(), None); + + // 30 will be challenged during the challenge rotation + next_challenge(); + assert_eq!(Defending::::get().unwrap().0, 30); // They can always free vote for themselves - assert_ok!(Society::defender_vote(RuntimeOrigin::signed(30), true)); + assert_ok!(Society::defender_vote(Origin::signed(30), true)); + // If no one else votes, nothing happens - run_to_block(16); - assert_eq!(Society::members(), vec![10, 20, 30, 40]); + next_challenge(); + assert_eq!(members(), vec![10, 20, 30, 40]); + // Reset votes for last challenge + assert_ok!(Society::cleanup_challenge(Origin::signed(0), 0, 10)); // New challenge period - assert_eq!(Society::defender(), Some(30)); - // Non-member cannot challenge - assert_noop!( - Society::defender_vote(RuntimeOrigin::signed(1), true), - Error::::NotMember - ); + assert_eq!(Defending::::get().unwrap().0, 30); + // Non-member cannot vote + assert_noop!(Society::defender_vote(Origin::signed(1), true), Error::::NotMember); // 3 people say accept, 1 reject - assert_ok!(Society::defender_vote(RuntimeOrigin::signed(10), true)); - assert_ok!(Society::defender_vote(RuntimeOrigin::signed(20), true)); - assert_ok!(Society::defender_vote(RuntimeOrigin::signed(30), true)); - assert_ok!(Society::defender_vote(RuntimeOrigin::signed(40), false)); - run_to_block(24); - // 20 survives - assert_eq!(Society::members(), vec![10, 20, 30, 40]); + assert_ok!(Society::defender_vote(Origin::signed(10), true)); + assert_ok!(Society::defender_vote(Origin::signed(20), true)); + assert_ok!(Society::defender_vote(Origin::signed(30), true)); + assert_ok!(Society::defender_vote(Origin::signed(40), false)); + + next_challenge(); + // 30 survives + assert_eq!(members(), vec![10, 20, 30, 40]); + // Reset votes for last challenge + assert_ok!(Society::cleanup_challenge(Origin::signed(0), 1, 10)); // Votes are reset - assert_eq!(>::get(10), None); - assert_eq!(>::get(20), None); - assert_eq!(>::get(30), None); - assert_eq!(>::get(40), None); + assert_eq!(DefenderVotes::::get(0, 10), None); + assert_eq!(DefenderVotes::::get(0, 20), None); + assert_eq!(DefenderVotes::::get(0, 30), None); + assert_eq!(DefenderVotes::::get(0, 40), None); + // One more time - assert_eq!(Society::defender(), Some(30)); + assert_eq!(Defending::::get().unwrap().0, 30); // 2 people say accept, 2 reject - assert_ok!(Society::defender_vote(RuntimeOrigin::signed(10), true)); - assert_ok!(Society::defender_vote(RuntimeOrigin::signed(20), true)); - assert_ok!(Society::defender_vote(RuntimeOrigin::signed(30), false)); - assert_ok!(Society::defender_vote(RuntimeOrigin::signed(40), false)); - run_to_block(32); - // 20 is suspended - assert_eq!(Society::members(), vec![10, 20, 40]); - assert_eq!(Society::suspended_member(30), true); + assert_ok!(Society::defender_vote(Origin::signed(10), true)); + assert_ok!(Society::defender_vote(Origin::signed(20), true)); + assert_ok!(Society::defender_vote(Origin::signed(30), false)); + assert_ok!(Society::defender_vote(Origin::signed(40), false)); + + next_challenge(); + // 30 is suspended + assert_eq!(members(), vec![10, 20, 40]); + assert_eq!( + SuspendedMembers::::get(30), + Some(MemberRecord { rank: 0, strikes: 0, vouching: None, index: 2 }) + ); + // Reset votes for last challenge + assert_ok!(Society::cleanup_challenge(Origin::signed(0), 2, 10)); // New defender is chosen - assert_eq!(Society::defender(), Some(20)); + assert_eq!(Defending::::get().unwrap().0, 20); // Votes are reset - assert_eq!(>::get(10), None); - assert_eq!(>::get(20), None); - assert_eq!(>::get(30), None); - assert_eq!(>::get(40), None); + assert_eq!(DefenderVotes::::get(0, 10), None); + assert_eq!(DefenderVotes::::get(0, 20), None); + assert_eq!(DefenderVotes::::get(0, 30), None); + assert_eq!(DefenderVotes::::get(0, 40), None); }); } @@ -659,38 +909,62 @@ fn challenges_work() { fn bad_vote_slash_works() { EnvBuilder::new().execute(|| { // Add some members - assert_ok!(Society::add_member(&20)); - assert_ok!(Society::add_member(&30)); - assert_ok!(Society::add_member(&40)); + place_members([20, 30, 40, 50]); + assert_eq!(members(), vec![10, 20, 30, 40, 50]); // Create some payouts - Society::bump_payout(&10, 5, 100); Society::bump_payout(&20, 5, 100); Society::bump_payout(&30, 5, 100); Society::bump_payout(&40, 5, 100); + Society::bump_payout(&50, 5, 100); // Check starting point - assert_eq!(Society::members(), vec![10, 20, 30, 40]); - assert_eq!(>::get(10), vec![(5, 100)]); - assert_eq!(>::get(20), vec![(5, 100)]); - assert_eq!(>::get(30), vec![(5, 100)]); - assert_eq!(>::get(40), vec![(5, 100)]); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() } + ); + assert_eq!( + Payouts::::get(30), + PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() } + ); + assert_eq!( + Payouts::::get(40), + PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() } + ); + assert_eq!( + Payouts::::get(50), + PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() } + ); // Create a new bid - assert_ok!(Society::bid(RuntimeOrigin::signed(50), 1000)); - run_to_block(4); - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 50, false)); - assert_ok!(Society::vote(RuntimeOrigin::signed(20), 50, true)); - assert_ok!(Society::vote(RuntimeOrigin::signed(30), 50, false)); - assert_ok!(Society::vote(RuntimeOrigin::signed(40), 50, false)); - run_to_block(8); + assert_ok!(Society::bid(Origin::signed(60), 1000)); + next_intake(); + // Force 20 to be the skeptic, and make it vote against the settled majority. + Skeptic::::put(20); + assert_ok!(Society::vote(Origin::signed(20), 60, true)); + assert_ok!(Society::vote(Origin::signed(30), 60, false)); + assert_ok!(Society::vote(Origin::signed(40), 60, false)); + assert_ok!(Society::vote(Origin::signed(50), 60, false)); + conclude_intake(false, None); // Wrong voter gained a strike - assert_eq!(>::get(10), 0); - assert_eq!(>::get(20), 1); - assert_eq!(>::get(30), 0); - assert_eq!(>::get(40), 0); + assert_eq!(Members::::get(20).unwrap().strikes, 1); + assert_eq!(Members::::get(30).unwrap().strikes, 0); + assert_eq!(Members::::get(40).unwrap().strikes, 0); + assert_eq!(Members::::get(50).unwrap().strikes, 0); // Their payout is slashed, a random person is rewarded - assert_eq!(>::get(10), vec![(5, 100), (9, 2)]); - assert_eq!(>::get(20), vec![(5, 98)]); - assert_eq!(>::get(30), vec![(5, 100)]); - assert_eq!(>::get(40), vec![(5, 100)]); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(5, 50)].try_into().unwrap() } + ); + assert_eq!( + Payouts::::get(30), + PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() } + ); + assert_eq!( + Payouts::::get(40), + PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() } + ); + assert_eq!( + Payouts::::get(50), + PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() } + ); }); } @@ -698,17 +972,14 @@ fn bad_vote_slash_works() { fn user_cannot_bid_twice() { EnvBuilder::new().execute(|| { // Cannot bid twice - assert_ok!(Society::bid(RuntimeOrigin::signed(20), 100)); - assert_noop!(Society::bid(RuntimeOrigin::signed(20), 100), Error::::AlreadyBid); + assert_ok!(Society::bid(Origin::signed(20), 100)); + assert_noop!(Society::bid(Origin::signed(20), 100), Error::::AlreadyBid); // Cannot bid when vouched - assert_ok!(Society::vouch(RuntimeOrigin::signed(10), 30, 100, 100)); - assert_noop!(Society::bid(RuntimeOrigin::signed(30), 100), Error::::AlreadyBid); + assert_ok!(Society::vouch(Origin::signed(10), 30, 100, 100)); + assert_noop!(Society::bid(Origin::signed(30), 100), Error::::AlreadyBid); // Cannot vouch when already bid - assert_ok!(Society::add_member(&50)); - assert_noop!( - Society::vouch(RuntimeOrigin::signed(50), 20, 100, 100), - Error::::AlreadyBid - ); + place_members([50]); + assert_noop!(Society::vouch(Origin::signed(50), 20, 100, 100), Error::::AlreadyBid); }); } @@ -716,23 +987,19 @@ fn user_cannot_bid_twice() { fn vouching_handles_removed_member_with_bid() { EnvBuilder::new().execute(|| { // Add a member - assert_ok!(Society::add_member(&20)); + place_members([20]); // Have that member vouch for a user assert_ok!(Society::vouch(RuntimeOrigin::signed(20), 30, 1000, 100)); // That user is now a bid and the member is vouching - assert_eq!(>::get(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); - assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); + assert_eq!(Bids::::get().into_inner(), vec![bid(30, Vouch(20, 100), 1000)]); + assert_eq!(Members::::get(20).unwrap().vouching, Some(VouchingStatus::Vouching)); // Suspend that member - Society::suspend_member(&20); - assert_eq!(>::get(20), true); - // Nothing changes yet - assert_eq!(>::get(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); - assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); - // Remove member - assert_ok!(Society::judge_suspended_member(RuntimeOrigin::signed(2), 20, false)); + assert_ok!(Society::suspend_member(&20)); // Bid is removed, vouching status is removed - assert_eq!(>::get(), vec![]); - assert_eq!(>::get(20), None); + let r = MemberRecord { rank: 0, strikes: 0, vouching: None, index: 1 }; + assert_eq!(SuspendedMembers::::get(20), Some(r)); + assert_eq!(Bids::::get().into_inner(), vec![]); + assert_eq!(Members::::get(20), None); }); } @@ -740,127 +1007,153 @@ fn vouching_handles_removed_member_with_bid() { fn vouching_handles_removed_member_with_candidate() { EnvBuilder::new().execute(|| { // Add a member - assert_ok!(Society::add_member(&20)); + place_members([20]); // Have that member vouch for a user assert_ok!(Society::vouch(RuntimeOrigin::signed(20), 30, 1000, 100)); // That user is now a bid and the member is vouching - assert_eq!(>::get(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); - assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); + assert_eq!(Bids::::get().into_inner(), vec![bid(30, Vouch(20, 100), 1000)]); + assert_eq!(Members::::get(20).unwrap().vouching, Some(VouchingStatus::Vouching)); + // Make that bid a candidate - run_to_block(4); - assert_eq!(Society::candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); + next_intake(); + assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, Vouch(20, 100), 0, 0))]); // Suspend that member - Society::suspend_member(&20); - assert_eq!(>::get(20), true); - // Nothing changes yet - assert_eq!(Society::candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); - assert_eq!(>::get(20), Some(VouchingStatus::Vouching)); - // Remove member - assert_ok!(Society::judge_suspended_member(RuntimeOrigin::signed(2), 20, false)); - // Vouching status is removed, but candidate is still in the queue - assert_eq!(>::get(20), None); - assert_eq!(Society::candidates(), vec![create_bid(1000, 30, BidKind::Vouch(20, 100))]); + assert_ok!(Society::suspend_member(&20)); + assert_eq!(SuspendedMembers::::contains_key(20), true); + + // Nothing changes yet in the candidacy, though the member now forgets. + assert_eq!(candidacies(), vec![(30, candidacy(1, 1000, Vouch(20, 100), 0, 0))]); + // Candidate wins - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 30, true)); - run_to_block(8); - assert_eq!(Society::members(), vec![10, 30]); + assert_ok!(Society::vote(Origin::signed(10), 30, true)); + conclude_intake(false, None); + assert_eq!(members(), vec![10, 30]); // Payout does not go to removed member - assert_eq!(>::get(20), vec![]); - assert_eq!(>::get(30), vec![(9, 1000)]); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() } + ); + assert_eq!( + Payouts::::get(30), + PayoutRecord { paid: 0, payouts: vec![(8, 1000)].try_into().unwrap() } + ); }); } #[test] fn votes_are_working() { EnvBuilder::new().execute(|| { + place_members([20]); // Users make bids of various amounts assert_ok!(Society::bid(RuntimeOrigin::signed(50), 500)); assert_ok!(Society::bid(RuntimeOrigin::signed(40), 400)); assert_ok!(Society::bid(RuntimeOrigin::signed(30), 300)); // Rotate period - run_to_block(4); + next_intake(); // A member votes for these candidates to join the society - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 30, true)); - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 40, true)); + assert_ok!(Society::vote(Origin::signed(10), 30, true)); + assert_ok!(Society::vote(Origin::signed(20), 30, true)); + assert_ok!(Society::vote(Origin::signed(10), 40, true)); // You cannot vote for a non-candidate - assert_noop!( - Society::vote(RuntimeOrigin::signed(10), 50, true), - Error::::NotCandidate - ); + assert_noop!(Society::vote(Origin::signed(10), 50, true), Error::::NotCandidate); // Votes are stored - assert_eq!(>::get(30, 10), Some(Vote::Approve)); - assert_eq!(>::get(40, 10), Some(Vote::Approve)); - assert_eq!(>::get(50, 10), None); - run_to_block(8); + assert_eq!(Votes::::get(30, 10), Some(Vote { approve: true, weight: 4 })); + assert_eq!(Votes::::get(30, 20), Some(Vote { approve: true, weight: 1 })); + assert_eq!(Votes::::get(40, 10), Some(Vote { approve: true, weight: 4 })); + assert_eq!(Votes::::get(50, 10), None); + conclude_intake(false, None); + // Cleanup the candidacy + assert_ok!(Society::cleanup_candidacy(Origin::signed(0), 30, 10)); + assert_ok!(Society::cleanup_candidacy(Origin::signed(0), 40, 10)); // Candidates become members after a period rotation - assert_eq!(Society::members(), vec![10, 30, 40]); + assert_eq!(members(), vec![10, 20, 30, 40]); // Votes are cleaned up - assert_eq!(>::get(30, 10), None); - assert_eq!(>::get(40, 10), None); + assert_eq!(Votes::::get(30, 10), None); + assert_eq!(Votes::::get(30, 20), None); + assert_eq!(Votes::::get(40, 10), None); }); } #[test] -fn max_limits_work() { - EnvBuilder::new().with_pot(100000).execute(|| { +fn max_bids_work() { + EnvBuilder::new().execute(|| { // Max bids is 1000, when extra bids come in, it pops the larger ones off the stack. // Try to put 1010 users into the bid pool - for i in (100..1110).rev() { - // Give them some funds - let _ = Balances::make_free_balance_be(&(i as u128), 1000); - assert_ok!(Society::bid(RuntimeOrigin::signed(i as u128), i)); + for i in (0..=10).rev() { + // Give them some funds and bid + let _ = Balances::make_free_balance_be(&((i + 100) as u128), 1000); + assert_ok!(Society::bid(Origin::signed((i + 100) as u128), i)); } - let bids = >::get(); + let bids = Bids::::get(); // Length is 1000 - assert_eq!(bids.len(), 1000); + assert_eq!(bids.len(), 10); // First bid is smallest number (100) - assert_eq!(bids[0], create_bid(100, 100, BidKind::Deposit(25))); + assert_eq!(bids[0], bid(100, Deposit(25), 0)); // Last bid is smallest number + 99 (1099) - assert_eq!(bids[999], create_bid(1099, 1099, BidKind::Deposit(25))); - // Rotate period - run_to_block(4); - // Max of 10 candidates - assert_eq!(Society::candidates().len(), 10); - // Fill up membership, max 100, we will do just 95 - for i in 2000..2095 { - assert_ok!(Society::add_member(&(i as u128))); - } - // Remember there was 1 original member, so 96 total - assert_eq!(Society::members().len(), 96); - // Rotate period - run_to_block(8); - // Only of 4 candidates possible now - assert_eq!(Society::candidates().len(), 4); - // Fill up members with suspended candidates from the first rotation - for i in 100..104 { - assert_ok!(Society::judge_suspended_candidate( - RuntimeOrigin::signed(2), - i, - Judgement::Approve - )); - } - assert_eq!(Society::members().len(), 100); - // Can't add any more members - assert_noop!(Society::add_member(&98), Error::::MaxMembers); - // However, a fringe scenario allows for in-progress candidates to increase the membership - // pool, but it has no real after-effects. - for i in Society::members().iter() { - assert_ok!(Society::vote(RuntimeOrigin::signed(*i), 110, true)); - assert_ok!(Society::vote(RuntimeOrigin::signed(*i), 111, true)); - assert_ok!(Society::vote(RuntimeOrigin::signed(*i), 112, true)); + assert_eq!(bids[9], bid(109, Deposit(25), 9)); + }); +} + +#[test] +fn candidates_are_limited_by_membership_size() { + EnvBuilder::new().execute(|| { + // Fill up some membership + place_members([1, 2, 3, 4, 5, 6, 7, 8]); + // One place left from 10 + assert_eq!(members().len(), 9); + + assert_ok!(Society::bid(Origin::signed(20), 0)); + assert_ok!(Society::bid(Origin::signed(30), 1)); + next_intake(); + assert_eq!(candidates().len(), 1); + }); +} + +#[test] +fn candidates_are_limited_by_maximum() { + EnvBuilder::new().execute(|| { + // Nine places left from 10 + assert_eq!(members().len(), 1); + + // Nine bids + for i in (1..=9).rev() { + // Give them some funds and bid + let _ = Balances::make_free_balance_be(&((i + 100) as u128), 1000); + assert_ok!(Society::bid(Origin::signed((i + 100) as u128), i)); } - // Rotate period - run_to_block(12); - // Members length is over 100, no problem... - assert_eq!(Society::members().len(), 103); - // No candidates because full - assert_eq!(Society::candidates().len(), 0); - // Increase member limit - assert_ok!(Society::set_max_members(RuntimeOrigin::root(), 200)); - // Rotate period - run_to_block(16); - // Candidates are back! - assert_eq!(Society::candidates().len(), 10); + next_intake(); + + // Still only 8 candidates. + assert_eq!(candidates().len(), 8); + }); +} + +#[test] +fn too_many_candidates_cannot_overflow_membership() { + EnvBuilder::new().execute(|| { + // One place left + place_members([1, 2, 3, 4, 5, 6, 7, 8]); + assert_ok!(Society::bid(Origin::signed(20), 0)); + assert_ok!(Society::bid(Origin::signed(30), 1)); + next_intake(); + // Candidate says a candidate. + next_intake(); + // Another candidate taken. + // Both approved. + assert_ok!(Society::vote(Origin::signed(10), 20, true)); + assert_ok!(Society::vote(Origin::signed(10), 30, true)); + next_voting(); + assert_ok!(Society::claim_membership(Origin::signed(20))); + assert_noop!(Society::claim_membership(Origin::signed(30)), Error::::MaxMembers); + + // Maximum members. + assert_eq!(members().len(), 10); + // Still 1 candidate. + assert_eq!(candidates().len(), 1); + + // Increase max-members and the candidate can get in. + assert_ok!(Society::set_parameters(Origin::signed(10), 11, 8, 3, 25)); + assert_ok!(Society::claim_membership(Origin::signed(30))); }); } @@ -878,32 +1171,30 @@ fn zero_bid_works() { assert_ok!(Society::bid(RuntimeOrigin::signed(40), 0)); // Rotate period - run_to_block(4); + next_intake(); // Pot is 1000 after "PeriodSpend" - assert_eq!(Society::pot(), 1000); + assert_eq!(Pot::::get(), 1000); assert_eq!(Balances::free_balance(Society::account_id()), 10_000); // Choose smallest bidding users whose total is less than pot, with only one zero bid. assert_eq!( - Society::candidates(), + candidacies(), vec![ - create_bid(0, 30, BidKind::Deposit(25)), - create_bid(300, 50, BidKind::Deposit(25)), - create_bid(400, 60, BidKind::Deposit(25)), + (30, candidacy(1, 0, Deposit(25), 0, 0)), + (50, candidacy(1, 300, Deposit(25), 0, 0)), + (60, candidacy(1, 400, Deposit(25), 0, 0)), ] ); - assert_eq!( - >::get(), - vec![create_bid(0, 20, BidKind::Deposit(25)), create_bid(0, 40, BidKind::Deposit(25)),] - ); + assert_eq!(Bids::::get(), vec![bid(20, Deposit(25), 0), bid(40, Deposit(25), 0),],); // A member votes for these candidates to join the society - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 30, true)); - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 50, true)); - assert_ok!(Society::vote(RuntimeOrigin::signed(10), 60, true)); - run_to_block(8); + assert_ok!(Society::vote(Origin::signed(10), 30, true)); + assert_ok!(Society::vote(Origin::signed(10), 50, true)); + assert_ok!(Society::vote(Origin::signed(10), 60, true)); + conclude_intake(false, None); // Candidates become members after a period rotation - assert_eq!(Society::members(), vec![10, 30, 50, 60]); + assert_eq!(members(), vec![10, 30, 50, 60]); + next_intake(); // The zero bid is selected as head - assert_eq!(Society::head(), Some(30)); + assert_eq!(Head::::get(), Some(30)); }); } @@ -915,8 +1206,9 @@ fn bids_ordered_correctly() { for i in 0..5 { for j in 0..5 { // Give them some funds - let _ = Balances::make_free_balance_be(&(100 + (i * 5 + j) as u128), 1000); - assert_ok!(Society::bid(RuntimeOrigin::signed(100 + (i * 5 + j) as u128), j)); + let who = 100 + (i * 5 + j) as u128; + let _ = Balances::make_free_balance_be(&who, 1000); + assert_ok!(Society::bid(Origin::signed(who), j)); } } @@ -924,10 +1216,79 @@ fn bids_ordered_correctly() { for j in 0..5 { for i in 0..5 { - final_list.push(create_bid(j, 100 + (i * 5 + j) as u128, BidKind::Deposit(25))); + final_list.push(bid(100 + (i * 5 + j) as u128, Deposit(25), j)); } } + let max_bids: u32 = ::MaxBids::get(); + final_list.truncate(max_bids as usize); + assert_eq!(Bids::::get(), final_list); + }); +} + +#[test] +fn waive_repay_works() { + EnvBuilder::new().execute(|| { + place_members([20, 30]); + Society::bump_payout(&20, 5, 100); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![(5, 100)].try_into().unwrap() } + ); + assert_eq!(Members::::get(20).unwrap().rank, 0); + assert_ok!(Society::waive_repay(Origin::signed(20), 100)); + assert_eq!( + Payouts::::get(20), + PayoutRecord { paid: 0, payouts: vec![].try_into().unwrap() } + ); + assert_eq!(Members::::get(10).unwrap().rank, 1); + assert_eq!(Balances::free_balance(20), 50); + }); +} + +#[test] +fn punish_skeptic_works() { + EnvBuilder::new().execute(|| { + place_members([20]); + assert_ok!(Society::bid(Origin::signed(30), 0)); + next_intake(); + // Force 20 to be Skeptic so it gets a strike. + Skeptic::::put(20); + next_voting(); + // 30 decides to punish the skeptic (20). + assert_ok!(Society::punish_skeptic(Origin::signed(30))); + // 20 gets 1 strike. + assert_eq!(Members::::get(20).unwrap().strikes, 1); + let candidacy = Candidates::::get(&30).unwrap(); + // 30 candidacy has changed. + assert_eq!(candidacy.skeptic_struck, true); + }); +} - assert_eq!(>::get(), final_list); +#[test] +fn resign_candidacy_works() { + EnvBuilder::new().execute(|| { + assert_ok!(Society::bid(Origin::signed(30), 45)); + next_intake(); + assert_eq!(candidates(), vec![30]); + assert_ok!(Society::resign_candidacy(Origin::signed(30))); + // 30 candidacy has gone. + assert_eq!(candidates(), vec![]); + }); +} + +#[test] +fn drop_candidate_works() { + EnvBuilder::new().execute(|| { + place_members([20, 30]); + assert_ok!(Society::bid(Origin::signed(40), 45)); + next_intake(); + assert_eq!(candidates(), vec![40]); + assert_ok!(Society::vote(Origin::signed(10), 40, false)); + assert_ok!(Society::vote(Origin::signed(20), 40, false)); + assert_ok!(Society::vote(Origin::signed(30), 40, false)); + run_to_block(12); + assert_ok!(Society::drop_candidate(Origin::signed(50), 40)); + // 40 candidacy has gone. + assert_eq!(candidates(), vec![]); }); } diff --git a/frame/society/src/weights.rs b/frame/society/src/weights.rs new file mode 100644 index 000000000000..d113f617c886 --- /dev/null +++ b/frame/society/src/weights.rs @@ -0,0 +1,375 @@ +// This file is part of Substrate. + +// Copyright (C) 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. + +//! Autogenerated weights for pallet_society +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-09-13, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_society +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --template=./.maintain/frame-weight-template.hbs +// --header=./HEADER-APACHE2 +// --output=./frame/society/src/weights.rs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_society. +pub trait WeightInfo { + fn bid() -> Weight; + fn unbid() -> Weight; + fn vouch() -> Weight; + fn unvouch() -> Weight; + fn vote() -> Weight; + fn defender_vote() -> Weight; + fn payout() -> Weight; + fn waive_repay() -> Weight; + fn found_society() -> Weight; + fn dissolve() -> Weight; + fn judge_suspended_member() -> Weight; + fn set_parameters() -> Weight; + fn punish_skeptic() -> Weight; + fn claim_membership() -> Weight; + fn bestow_membership() -> Weight; + fn kick_candidate() -> Weight; + fn resign_candidacy() -> Weight; + fn drop_candidate() -> Weight; + fn cleanup_candidacy() -> Weight; + fn cleanup_challenge() -> Weight; +} + +/// Weights for pallet_society using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: Society Bids (r:1 w:1) + // Storage: Society Candidates (r:1 w:0) + // Storage: Society Members (r:1 w:0) + // Storage: Society SuspendedMembers (r:1 w:0) + // Storage: Society Parameters (r:1 w:0) + fn bid() -> Weight { + Weight::zero() + } + // Storage: Society Bids (r:1 w:1) + fn unbid() -> Weight { + Weight::zero() + } + // Storage: Society Bids (r:1 w:1) + // Storage: Society Candidates (r:1 w:0) + // Storage: Society Members (r:2 w:1) + // Storage: Society SuspendedMembers (r:1 w:0) + fn vouch() -> Weight { + Weight::zero() + } + // Storage: Society Bids (r:1 w:1) + // Storage: Society Members (r:1 w:1) + fn unvouch() -> Weight { + Weight::zero() + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society Members (r:1 w:0) + // Storage: Society Votes (r:1 w:1) + fn vote() -> Weight { + Weight::zero() + } + // Storage: Society Defending (r:1 w:1) + // Storage: Society Members (r:1 w:0) + // Storage: Society ChallengeRoundCount (r:1 w:0) + // Storage: Society DefenderVotes (r:1 w:1) + fn defender_vote() -> Weight { + Weight::zero() + } + // Storage: Society Members (r:1 w:0) + // Storage: Society Payouts (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn payout() -> Weight { + Weight::zero() + } + // Storage: Society Members (r:1 w:1) + // Storage: Society Payouts (r:1 w:1) + fn waive_repay() -> Weight { + Weight::zero() + } + // Storage: Society Head (r:1 w:1) + // Storage: Society MemberCount (r:1 w:1) + // Storage: Society MemberByIndex (r:0 w:1) + // Storage: Society Founder (r:0 w:1) + // Storage: Society Rules (r:0 w:1) + // Storage: Society Members (r:0 w:1) + // Storage: Society Parameters (r:0 w:1) + fn found_society() -> Weight { + Weight::zero() + } + // Storage: Society Founder (r:1 w:1) + // Storage: Society MemberCount (r:1 w:1) + // Storage: Society Head (r:0 w:1) + // Storage: Society Defending (r:0 w:1) + // Storage: Society ChallengeRoundCount (r:0 w:1) + // Storage: Society MemberByIndex (r:0 w:5) + // Storage: Society Skeptic (r:0 w:1) + // Storage: Society Candidates (r:0 w:4) + // Storage: Society Pot (r:0 w:1) + // Storage: Society Rules (r:0 w:1) + // Storage: Society Votes (r:0 w:4) + // Storage: Society Members (r:0 w:5) + // Storage: Society RoundCount (r:0 w:1) + // Storage: Society Bids (r:0 w:1) + // Storage: Society Parameters (r:0 w:1) + // Storage: Society NextHead (r:0 w:1) + fn dissolve() -> Weight { + Weight::zero() + } + // Storage: Society Founder (r:1 w:0) + // Storage: Society SuspendedMembers (r:1 w:1) + // Storage: Society Payouts (r:1 w:0) + // Storage: Society Pot (r:1 w:1) + fn judge_suspended_member() -> Weight { + Weight::zero() + } + // Storage: Society Founder (r:1 w:0) + // Storage: Society MemberCount (r:1 w:0) + // Storage: Society Parameters (r:0 w:1) + fn set_parameters() -> Weight { + Weight::zero() + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + // Storage: Society Skeptic (r:1 w:0) + // Storage: Society Votes (r:1 w:0) + // Storage: Society Members (r:1 w:1) + // Storage: Society Parameters (r:1 w:0) + fn punish_skeptic() -> Weight { + Weight::zero() + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + // Storage: Society Parameters (r:1 w:0) + // Storage: Society MemberCount (r:1 w:1) + // Storage: Society NextHead (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: Society MemberByIndex (r:0 w:1) + // Storage: Society Members (r:0 w:1) + fn claim_membership() -> Weight { + Weight::zero() + } + // Storage: Society Founder (r:1 w:0) + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + // Storage: Society Parameters (r:1 w:0) + // Storage: Society MemberCount (r:1 w:1) + // Storage: Society NextHead (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: Society MemberByIndex (r:0 w:1) + // Storage: Society Members (r:0 w:1) + fn bestow_membership() -> Weight { + Weight::zero() + } + // Storage: Society Founder (r:1 w:0) + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + fn kick_candidate() -> Weight { + Weight::zero() + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + fn resign_candidacy() -> Weight { + Weight::zero() + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + fn drop_candidate() -> Weight { + Weight::zero() + } + // Storage: Society Candidates (r:1 w:0) + // Storage: Society VoteClearCursor (r:1 w:0) + // Storage: Society Votes (r:0 w:2) + fn cleanup_candidacy() -> Weight { + Weight::zero() + } + // Storage: Society ChallengeRoundCount (r:1 w:0) + // Storage: Society DefenderVotes (r:0 w:1) + fn cleanup_challenge() -> Weight { + Weight::zero() + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: Society Bids (r:1 w:1) + // Storage: Society Candidates (r:1 w:0) + // Storage: Society Members (r:1 w:0) + // Storage: Society SuspendedMembers (r:1 w:0) + // Storage: Society Parameters (r:1 w:0) + fn bid() -> Weight { + Weight::zero() + } + // Storage: Society Bids (r:1 w:1) + fn unbid() -> Weight { + Weight::zero() + } + // Storage: Society Bids (r:1 w:1) + // Storage: Society Candidates (r:1 w:0) + // Storage: Society Members (r:2 w:1) + // Storage: Society SuspendedMembers (r:1 w:0) + fn vouch() -> Weight { + Weight::zero() + } + // Storage: Society Bids (r:1 w:1) + // Storage: Society Members (r:1 w:1) + fn unvouch() -> Weight { + Weight::zero() + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society Members (r:1 w:0) + // Storage: Society Votes (r:1 w:1) + fn vote() -> Weight { + Weight::zero() + } + // Storage: Society Defending (r:1 w:1) + // Storage: Society Members (r:1 w:0) + // Storage: Society ChallengeRoundCount (r:1 w:0) + // Storage: Society DefenderVotes (r:1 w:1) + fn defender_vote() -> Weight { + Weight::zero() + } + // Storage: Society Members (r:1 w:0) + // Storage: Society Payouts (r:1 w:1) + // Storage: System Account (r:1 w:1) + fn payout() -> Weight { + Weight::zero() + } + // Storage: Society Members (r:1 w:1) + // Storage: Society Payouts (r:1 w:1) + fn waive_repay() -> Weight { + Weight::zero() + } + // Storage: Society Head (r:1 w:1) + // Storage: Society MemberCount (r:1 w:1) + // Storage: Society MemberByIndex (r:0 w:1) + // Storage: Society Founder (r:0 w:1) + // Storage: Society Rules (r:0 w:1) + // Storage: Society Members (r:0 w:1) + // Storage: Society Parameters (r:0 w:1) + fn found_society() -> Weight { + Weight::zero() + } + // Storage: Society Founder (r:1 w:1) + // Storage: Society MemberCount (r:1 w:1) + // Storage: Society Head (r:0 w:1) + // Storage: Society Defending (r:0 w:1) + // Storage: Society ChallengeRoundCount (r:0 w:1) + // Storage: Society MemberByIndex (r:0 w:5) + // Storage: Society Skeptic (r:0 w:1) + // Storage: Society Candidates (r:0 w:4) + // Storage: Society Pot (r:0 w:1) + // Storage: Society Rules (r:0 w:1) + // Storage: Society Votes (r:0 w:4) + // Storage: Society Members (r:0 w:5) + // Storage: Society RoundCount (r:0 w:1) + // Storage: Society Bids (r:0 w:1) + // Storage: Society Parameters (r:0 w:1) + // Storage: Society NextHead (r:0 w:1) + fn dissolve() -> Weight { + Weight::zero() + } + // Storage: Society Founder (r:1 w:0) + // Storage: Society SuspendedMembers (r:1 w:1) + // Storage: Society Payouts (r:1 w:0) + // Storage: Society Pot (r:1 w:1) + fn judge_suspended_member() -> Weight { + Weight::zero() + } + // Storage: Society Founder (r:1 w:0) + // Storage: Society MemberCount (r:1 w:0) + // Storage: Society Parameters (r:0 w:1) + fn set_parameters() -> Weight { + Weight::zero() + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + // Storage: Society Skeptic (r:1 w:0) + // Storage: Society Votes (r:1 w:0) + // Storage: Society Members (r:1 w:1) + // Storage: Society Parameters (r:1 w:0) + fn punish_skeptic() -> Weight { + Weight::zero() + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + // Storage: Society Parameters (r:1 w:0) + // Storage: Society MemberCount (r:1 w:1) + // Storage: Society NextHead (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: Society MemberByIndex (r:0 w:1) + // Storage: Society Members (r:0 w:1) + fn claim_membership() -> Weight { + Weight::zero() + } + // Storage: Society Founder (r:1 w:0) + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + // Storage: Society Parameters (r:1 w:0) + // Storage: Society MemberCount (r:1 w:1) + // Storage: Society NextHead (r:1 w:1) + // Storage: System Account (r:1 w:1) + // Storage: Society MemberByIndex (r:0 w:1) + // Storage: Society Members (r:0 w:1) + fn bestow_membership() -> Weight { + Weight::zero() + } + // Storage: Society Founder (r:1 w:0) + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + fn kick_candidate() -> Weight { + Weight::zero() + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + fn resign_candidacy() -> Weight { + Weight::zero() + } + // Storage: Society Candidates (r:1 w:1) + // Storage: Society RoundCount (r:1 w:0) + fn drop_candidate() -> Weight { + Weight::zero() + } + // Storage: Society Candidates (r:1 w:0) + // Storage: Society VoteClearCursor (r:1 w:0) + // Storage: Society Votes (r:0 w:2) + fn cleanup_candidacy() -> Weight { + Weight::zero() + } + // Storage: Society ChallengeRoundCount (r:1 w:0) + // Storage: Society DefenderVotes (r:0 w:1) + fn cleanup_challenge() -> Weight { + Weight::zero() + } +} diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index 3d5cf1161e8e..b4c2634dd829 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -13,22 +13,22 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.136", optional = true } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +serde = { version = "1.0.163", default-features = false, features = ["alloc", "derive"]} +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } +scale-info = { version = "2.5.0", default-features = false, features = ["derive", "serde"] } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime", features = ["serde"] } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking", features = ["serde"] } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-session = { version = "4.0.0-dev", default-features = false, features = [ "historical", ], path = "../session" } pallet-authorship = { version = "4.0.0-dev", default-features = false, path = "../authorship" } -sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../../primitives/application-crypto", features = ["serde"] } frame-election-provider-support = { version = "4.0.0-dev", default-features = false, path = "../election-provider-support" } log = { version = "0.4.17", default-features = false } @@ -37,8 +37,8 @@ frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = " rand_chacha = { version = "0.2", default-features = false, optional = true } [dev-dependencies] -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" } pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } @@ -53,7 +53,7 @@ rand_chacha = { version = "0.2" } default = ["std"] std = [ "frame-benchmarking?/std", - "serde", + "serde/std", "codec/std", "scale-info/std", "sp-std/std", @@ -74,4 +74,4 @@ runtime-benchmarks = [ "rand_chacha", "sp-staking/runtime-benchmarks", ] -try-runtime = ["frame-support/try-runtime"] +try-runtime = ["frame-support/try-runtime", "frame-election-provider-support/try-runtime"] diff --git a/frame/staking/README.md b/frame/staking/README.md index bbd5bd18f6e8..ccb9901a6796 100644 --- a/frame/staking/README.md +++ b/frame/staking/README.md @@ -50,8 +50,12 @@ used. An account pair can become bonded using the [`bond`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.bond) call. -Stash accounts can change their associated controller using the -[`set_controller`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.set_controller) call. +Stash accounts can update their associated controller back to their stash account using the +[`set_controller`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.set_controller) +call. + +Note: Controller accounts are being deprecated in favor of proxy accounts, so it is no longer +possible to set a unique address for a stash's controller. There are three possible roles that any staked account pair can be in: `Validator`, `Nominator` and `Idle` (defined in [`StakerStatus`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.StakerStatus.html)). There are three diff --git a/frame/staking/reward-curve/Cargo.toml b/frame/staking/reward-curve/Cargo.toml index c761517ea682..b98ab8caef31 100644 --- a/frame/staking/reward-curve/Cargo.toml +++ b/frame/staking/reward-curve/Cargo.toml @@ -16,9 +16,9 @@ proc-macro = true [dependencies] proc-macro-crate = "1.1.3" -proc-macro2 = "1.0.37" -quote = "1.0.10" -syn = { version = "1.0.98", features = ["full", "visit"] } +proc-macro2 = "1.0.56" +quote = "1.0.28" +syn = { version = "2.0.16", features = ["full", "visit"] } [dev-dependencies] -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } diff --git a/frame/staking/reward-curve/src/lib.rs b/frame/staking/reward-curve/src/lib.rs index e1ea8aa7b15d..ecf3af553791 100644 --- a/frame/staking/reward-curve/src/lib.rs +++ b/frame/staking/reward-curve/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/staking/reward-curve/tests/test.rs b/frame/staking/reward-curve/tests/test.rs index aa19b1782453..339e00322249 100644 --- a/frame/staking/reward-curve/tests/test.rs +++ b/frame/staking/reward-curve/tests/test.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/staking/reward-fn/Cargo.toml b/frame/staking/reward-fn/Cargo.toml index 0fb034a17202..3561eeb8a90c 100644 --- a/frame/staking/reward-fn/Cargo.toml +++ b/frame/staking/reward-fn/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] log = { version = "0.4.17", default-features = false } -sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../../primitives/arithmetic" } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../../primitives/arithmetic" } [features] default = ["std"] diff --git a/frame/staking/reward-fn/src/lib.rs b/frame/staking/reward-fn/src/lib.rs index cc9919c28cce..d34a534c0425 100644 --- a/frame/staking/reward-fn/src/lib.rs +++ b/frame/staking/reward-fn/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,7 +41,7 @@ use sp_arithmetic::{ /// The result is meant to be scaled with minimum inflation and maximum inflation. /// /// (as detailed -/// [here](https://research.web3.foundation/en/latest/polkadot/economics/1-token-economics.html#inflation-model-with-parachains)) +/// [here](https://research.web3.foundation/Polkadot/overview/token-economics#inflation-model-with-parachains)) /// /// Arguments are: /// * `stake`: The fraction of total issued tokens that actively staked behind validators. Known as diff --git a/frame/staking/reward-fn/tests/test.rs b/frame/staking/reward-fn/tests/test.rs index a79137716fd2..d76d2ce5e8c0 100644 --- a/frame/staking/reward-fn/tests/test.rs +++ b/frame/staking/reward-fn/tests/test.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/staking/runtime-api/Cargo.toml b/frame/staking/runtime-api/Cargo.toml new file mode 100644 index 000000000000..5c9af0ad3cbe --- /dev/null +++ b/frame/staking/runtime-api/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "pallet-staking-runtime-api" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "RPC runtime API for transaction payment FRAME pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } + +[features] +default = ["std"] +std = [ + "codec/std", + "sp-api/std", +] diff --git a/frame/staking/runtime-api/README.md b/frame/staking/runtime-api/README.md new file mode 100644 index 000000000000..a999e519f8cb --- /dev/null +++ b/frame/staking/runtime-api/README.md @@ -0,0 +1,3 @@ +Runtime API definition for the staking pallet. + +License: Apache-2.0 diff --git a/frame/support/test/tests/decl_storage_ui/config_duplicate.rs b/frame/staking/runtime-api/src/lib.rs similarity index 61% rename from frame/support/test/tests/decl_storage_ui/config_duplicate.rs rename to frame/staking/runtime-api/src/lib.rs index af74823f64ca..378599c66550 100644 --- a/frame/support/test/tests/decl_storage_ui/config_duplicate.rs +++ b/frame/staking/runtime-api/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) 2023 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,17 +15,18 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub trait Config: frame_support_test::Config {} +//! Runtime API definition for the staking pallet. -frame_support::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} -} +#![cfg_attr(not(feature = "std"), no_std)] + +use codec::Codec; -frame_support::decl_storage!{ - trait Store for Module as FinalKeysNone { - pub Value config(value): u32; - pub Value2 config(value): u32; +sp_api::decl_runtime_apis! { + pub trait StakingApi + where + Balance: Codec, + { + /// Returns the nominations quota for a nominator with a given balance. + fn nominations_quota(balance: Balance) -> u32; } } - -fn main() {} diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index b3d32b26ec1f..8d6eb7459074 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,16 +26,16 @@ use frame_election_provider_support::SortedListProvider; use frame_support::{ dispatch::UnfilteredDispatchable, pallet_prelude::*, - traits::{Currency, CurrencyToVote, Get, Imbalance}, + traits::{Currency, Get, Imbalance}, }; use sp_runtime::{ traits::{Bounded, One, StaticLookup, TrailingZeroInput, Zero}, Perbill, Percent, }; -use sp_staking::SessionIndex; +use sp_staking::{currency_to_vote::CurrencyToVote, SessionIndex}; use sp_std::prelude::*; -pub use frame_benchmarking::{ +pub use frame_benchmarking::v1::{ account, benchmarks, impl_benchmark_test_suite, whitelist_account, whitelisted_caller, }; use frame_system::RawOrigin; @@ -71,7 +71,8 @@ pub fn add_slashing_spans(who: &T::AccountId, spans: u32) { pub fn create_validator_with_nominators( n: u32, upper_bound: u32, - dead: bool, + dead_controller: bool, + unique_controller: bool, destination: RewardDestination, ) -> Result<(T::AccountId, Vec<(T::AccountId, T::AccountId)>), &'static str> { // Clean up any existing state. @@ -79,7 +80,12 @@ pub fn create_validator_with_nominators( let mut points_total = 0; let mut points_individual = Vec::new(); - let (v_stash, v_controller) = create_stash_controller::(0, 100, destination.clone())?; + let (v_stash, v_controller) = if unique_controller { + create_unique_stash_controller::(0, 100, destination.clone(), false)? + } else { + create_stash_controller::(0, 100, destination.clone())? + }; + let validator_prefs = ValidatorPrefs { commission: Perbill::from_percent(50), ..Default::default() }; Staking::::validate(RawOrigin::Signed(v_controller).into(), validator_prefs)?; @@ -93,10 +99,10 @@ pub fn create_validator_with_nominators( // Give the validator n nominators, but keep total users in the system the same. for i in 0..upper_bound { - let (n_stash, n_controller) = if !dead { + let (n_stash, n_controller) = if !dead_controller { create_stash_controller::(u32::MAX - i, 100, destination.clone())? } else { - create_stash_and_dead_controller::(u32::MAX - i, 100, destination.clone())? + create_unique_stash_controller::(u32::MAX - i, 100, destination.clone(), true)? }; if i < n { Staking::::nominate( @@ -217,15 +223,13 @@ const USER_SEED: u32 = 999666; benchmarks! { bond { let stash = create_funded_user::("stash", USER_SEED, 100); - let controller = create_funded_user::("controller", USER_SEED, 100); - let controller_lookup = T::Lookup::unlookup(controller.clone()); let reward_destination = RewardDestination::Staked; let amount = T::Currency::minimum_balance() * 10u32.into(); whitelist_account!(stash); - }: _(RawOrigin::Signed(stash.clone()), controller_lookup, amount, reward_destination) + }: _(RawOrigin::Signed(stash.clone()), amount, reward_destination) verify { - assert!(Bonded::::contains_key(stash)); - assert!(Ledger::::contains_key(controller)); + assert!(Bonded::::contains_key(stash.clone())); + assert!(Ledger::::contains_key(stash)); } bond_extra { @@ -470,13 +474,16 @@ benchmarks! { } set_controller { - let (stash, _) = create_stash_controller::(USER_SEED, 100, Default::default())?; - let new_controller = create_funded_user::("new_controller", USER_SEED, 100); - let new_controller_lookup = T::Lookup::unlookup(new_controller.clone()); + let (stash, ctlr) = create_unique_stash_controller::(9000, 100, Default::default(), false)?; + // ensure `ctlr` is the currently stored controller. + assert!(!Ledger::::contains_key(&stash)); + assert!(Ledger::::contains_key(&ctlr)); + assert_eq!(Bonded::::get(&stash), Some(ctlr.clone())); + whitelist_account!(stash); - }: _(RawOrigin::Signed(stash), new_controller_lookup) + }: _(RawOrigin::Signed(stash.clone())) verify { - assert!(Ledger::::contains_key(&new_controller)); + assert!(Ledger::::contains_key(&stash)); } set_validator_count { @@ -551,6 +558,7 @@ benchmarks! { n, T::MaxNominatorRewardedPerValidator::get() as u32, true, + true, RewardDestination::Controller, )?; @@ -584,6 +592,7 @@ benchmarks! { n, T::MaxNominatorRewardedPerValidator::get() as u32, false, + true, RewardDestination::Staked, )?; @@ -978,6 +987,7 @@ mod tests { n, <::MaxNominatorRewardedPerValidator as Get<_>>::get(), false, + false, RewardDestination::Staked, ) .unwrap(); @@ -1007,6 +1017,7 @@ mod tests { n, <::MaxNominatorRewardedPerValidator as Get<_>>::get(), false, + false, RewardDestination::Staked, ) .unwrap(); diff --git a/frame/staking/src/inflation.rs b/frame/staking/src/inflation.rs index c7519683c75d..8b4a85b6c2d8 100644 --- a/frame/staking/src/inflation.rs +++ b/frame/staking/src/inflation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 3672056534b7..99622a64f8dc 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -67,7 +67,7 @@ //! //! An account pair can become bonded using the [`bond`](Call::bond) call. //! -//! Stash accounts can change their associated controller using the +//! Stash accounts can update their associated controller back to the stash account using the //! [`set_controller`](Call::set_controller) call. //! //! There are three possible roles that any staked account pair can be in: `Validator`, `Nominator` @@ -311,14 +311,15 @@ use sp_runtime::{ traits::{AtLeast32BitUnsigned, Convert, Saturating, StaticLookup, Zero}, Perbill, Perquintill, Rounding, RuntimeDebug, }; +pub use sp_staking::StakerStatus; use sp_staking::{ offence::{Offence, OffenceError, ReportOffence}, - EraIndex, SessionIndex, + EraIndex, OnStakingUpdate, SessionIndex, }; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; pub use weights::WeightInfo; -pub use pallet::{pallet::*, *}; +pub use pallet::{pallet::*, UseNominatorsAndValidatorsMap, UseValidatorsMap}; pub(crate) const LOG_TARGET: &str = "runtime::staking"; @@ -381,18 +382,6 @@ impl Default for EraRewardPoints { } } -/// Indicates the initial status of the staker. -#[derive(RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize, Clone))] -pub enum StakerStatus { - /// Chilling. - Idle, - /// Declared desire in validating or already participating in it. - Validator, - /// Nominating for a group of other stakers. - Nominator(Vec), -} - /// A destination account for payment. #[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum RewardDestination { @@ -560,7 +549,7 @@ impl StakingLedger { /// /// `slash_era` is the era in which the slash (which is being enacted now) actually happened. /// - /// This calls `Config::OnStakerSlash::on_slash` with information as to how the slash was + /// This calls `Config::OnStakingUpdate::on_slash` with information as to how the slash was /// applied. pub fn slash( &mut self, @@ -573,7 +562,6 @@ impl StakingLedger { } use sp_runtime::PerThing as _; - use sp_staking::OnStakerSlash as _; let mut remaining_slash = slash_amount; let pre_slash_total = self.total; @@ -678,7 +666,7 @@ impl StakingLedger { // clean unlocking chunks that are set to zero. self.unlocking.retain(|c| !c.value.is_zero()); - T::OnStakerSlash::on_slash(&self.stash, self.active, &slashed_unlocking); + T::EventListeners::on_slash(&self.stash, self.active, &slashed_unlocking); pre_slash_total.saturating_sub(self.total) } } @@ -860,8 +848,19 @@ impl(sp_std::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV13 { #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { + fn pre_upgrade() -> Result, TryRuntimeError> { frame_support::ensure!( StorageVersion::::get() == ObsoleteReleases::V12_0_0, "Required v12 before upgrading to v13" @@ -84,7 +89,7 @@ pub mod v13 { } #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), &'static str> { + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { frame_support::ensure!( Pallet::::on_chain_storage_version() == 13, "v13 not applied" @@ -114,7 +119,7 @@ pub mod v12 { pub struct MigrateToV12(sp_std::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV12 { #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { + fn pre_upgrade() -> Result, TryRuntimeError> { frame_support::ensure!( StorageVersion::::get() == ObsoleteReleases::V11_0_0, "Expected v11 before upgrading to v12" @@ -146,7 +151,7 @@ pub mod v12 { } #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), &'static str> { + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { frame_support::ensure!( StorageVersion::::get() == ObsoleteReleases::V12_0_0, "v12 not applied" @@ -170,7 +175,7 @@ pub mod v11 { for MigrateToV11 { #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { + fn pre_upgrade() -> Result, TryRuntimeError> { frame_support::ensure!( StorageVersion::::get() == ObsoleteReleases::V10_0_0, "must upgrade linearly" @@ -217,7 +222,7 @@ pub mod v11 { } #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), &'static str> { + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { frame_support::ensure!( StorageVersion::::get() == ObsoleteReleases::V11_0_0, "wrong version after the upgrade" @@ -265,7 +270,7 @@ pub mod v10 { impl OnRuntimeUpgrade for MigrateToV10 { fn on_runtime_upgrade() -> frame_support::weights::Weight { if StorageVersion::::get() == ObsoleteReleases::V9_0_0 { - let pending_slashes = as Store>::UnappliedSlashes::iter().take(512); + let pending_slashes = UnappliedSlashes::::iter().take(512); for (era, slashes) in pending_slashes { for slash in slashes { // in the old slashing scheme, the slash era was the key at which we read @@ -332,7 +337,7 @@ pub mod v9 { } #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { + fn pre_upgrade() -> Result, TryRuntimeError> { frame_support::ensure!( StorageVersion::::get() == ObsoleteReleases::V8_0_0, "must upgrade linearly" @@ -343,17 +348,21 @@ pub mod v9 { } #[cfg(feature = "try-runtime")] - fn post_upgrade(prev_count: Vec) -> Result<(), &'static str> { + fn post_upgrade(prev_count: Vec) -> Result<(), TryRuntimeError> { let prev_count: u32 = Decode::decode(&mut prev_count.as_slice()).expect( "the state parameter should be something that was generated by pre_upgrade", ); let post_count = T::VoterList::count(); let validators = Validators::::count(); - assert!(post_count == prev_count + validators); + ensure!( + post_count == prev_count + validators, + "`VoterList` count after the migration must equal to the sum of \ + previous count and the current number of validators" + ); frame_support::ensure!( StorageVersion::::get() == ObsoleteReleases::V9_0_0, - "must upgrade " + "must upgrade" ); Ok(()) } @@ -386,7 +395,6 @@ pub mod v8 { Nominators::::iter().map(|(id, _)| id), Pallet::::weight_of_fn(), ); - debug_assert_eq!(T::VoterList::try_state(), Ok(())); StorageVersion::::put(ObsoleteReleases::V8_0_0); crate::log!( diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index ad27a82910a6..232b37de7c1b 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,8 +22,8 @@ use frame_election_provider_support::{onchain, SequentialPhragmen, VoteWeight}; use frame_support::{ assert_ok, ord_parameter_types, parameter_types, traits::{ - ConstU32, ConstU64, Currency, EitherOfDiverse, FindAuthor, GenesisBuild, Get, Hooks, - Imbalance, OnUnbalanced, OneSessionHandler, + ConstU32, ConstU64, Currency, EitherOfDiverse, FindAuthor, Get, Hooks, Imbalance, + OnUnbalanced, OneSessionHandler, }, weights::constants::RocksDbWeight, }; @@ -32,8 +32,9 @@ use sp_core::H256; use sp_io; use sp_runtime::{ curve::PiecewiseLinear, - testing::{Header, UintAuthorityId}, + testing::UintAuthorityId, traits::{IdentityLookup, Zero}, + BuildStorage, }; use sp_staking::offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}; @@ -42,7 +43,7 @@ pub const BLOCK_TIME: u64 = 1000; /// The AccountId alias in this test module. pub(crate) type AccountId = u64; -pub(crate) type AccountIndex = u64; +pub(crate) type Nonce = u64; pub(crate) type BlockNumber = u64; pub(crate) type Balance = u128; @@ -82,23 +83,19 @@ pub fn is_disabled(controller: AccountId) -> bool { Session::disabled_validators().contains(&validator_index) } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - Authorship: pallet_authorship::{Pallet, Call, Storage, Inherent}, - Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, - Session: pallet_session::{Pallet, Call, Storage, Event, Config}, - Historical: pallet_session::historical::{Pallet, Storage}, - VoterBagsList: pallet_bags_list::::{Pallet, Call, Storage, Event}, + System: frame_system, + Authorship: pallet_authorship, + Timestamp: pallet_timestamp, + Balances: pallet_balances, + Staking: pallet_staking, + Session: pallet_session, + Historical: pallet_session::historical, + VoterBagsList: pallet_bags_list::, } ); @@ -127,14 +124,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = RocksDbWeight; type RuntimeOrigin = RuntimeOrigin; - type Index = AccountIndex; - type BlockNumber = BlockNumber; + type Nonce = Nonce; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = frame_support::traits::ConstU64<250>; type Version = (); @@ -157,6 +153,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } sp_runtime::impl_opaque_keys! { @@ -182,8 +182,6 @@ impl pallet_session::historical::Config for Test { } impl pallet_authorship::Config for Test { type FindAuthor = Author11; - type UncleGenerations = ConstU64<0>; - type FilterUncle = (); type EventHandler = Pallet; } @@ -234,7 +232,6 @@ parameter_types! { pub static HistoryDepth: u32 = 80; pub static MaxUnlockingChunks: u32 = 32; pub static RewardOnUnbalanceWasCalled: bool = false; - pub static LedgerSlashPerEra: (BalanceOf, BTreeMap>) = (Zero::zero(), BTreeMap::new()); pub static MaxWinners: u32 = 100; } @@ -266,8 +263,14 @@ impl OnUnbalanced> for MockReward { } } -pub struct OnStakerSlashMock(core::marker::PhantomData); -impl sp_staking::OnStakerSlash for OnStakerSlashMock { +parameter_types! { + pub static LedgerSlashPerEra: + (BalanceOf, BTreeMap>) = + (Zero::zero(), BTreeMap::new()); +} + +pub struct EventListenerMock; +impl OnStakingUpdate for EventListenerMock { fn on_slash( _pool_account: &AccountId, slashed_bonded: Balance, @@ -282,7 +285,7 @@ impl crate::pallet::pallet::Config for Test { type Currency = Balances; type CurrencyBalance = ::Balance; type UnixTime = Timestamp; - type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + type CurrencyToVote = (); type RewardRemainder = RewardRemainderMock; type RuntimeEvent = RuntimeEvent; type Slash = (); @@ -303,7 +306,7 @@ impl crate::pallet::pallet::Config for Test { type TargetList = UseValidatorsMap; type MaxUnlockingChunks = MaxUnlockingChunks; type HistoryDepth = HistoryDepth; - type OnStakerSlash = OnStakerSlashMock; + type EventListeners = EventListenerMock; type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); } @@ -422,7 +425,7 @@ impl ExtBuilder { } fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); - let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); let _ = pallet_balances::GenesisConfig:: { balances: vec![ @@ -430,7 +433,7 @@ impl ExtBuilder { (2, 20 * self.balance_factor), (3, 300 * self.balance_factor), (4, 400 * self.balance_factor), - // controllers + // controllers (still used in some tests. Soon to be deprecated). (10, self.balance_factor), (20, self.balance_factor), (30, self.balance_factor), @@ -463,18 +466,18 @@ impl ExtBuilder { stakers = vec![ // (stash, ctrl, stake, status) // these two will be elected in the default test where we elect 2. - (11, 10, self.balance_factor * 1000, StakerStatus::::Validator), - (21, 20, self.balance_factor * 1000, StakerStatus::::Validator), + (11, 11, self.balance_factor * 1000, StakerStatus::::Validator), + (21, 21, self.balance_factor * 1000, StakerStatus::::Validator), // a loser validator - (31, 30, self.balance_factor * 500, StakerStatus::::Validator), + (31, 31, self.balance_factor * 500, StakerStatus::::Validator), // an idle validator - (41, 40, self.balance_factor * 1000, StakerStatus::::Idle), + (41, 41, self.balance_factor * 1000, StakerStatus::::Idle), ]; // optionally add a nominator if self.nominate { stakers.push(( 101, - 100, + 101, self.balance_factor * 500, StakerStatus::::Nominator(vec![11, 21]), )) @@ -561,35 +564,24 @@ pub(crate) fn current_era() -> EraIndex { Staking::current_era().unwrap() } -pub(crate) fn bond(stash: AccountId, ctrl: AccountId, val: Balance) { - let _ = Balances::make_free_balance_be(&stash, val); - let _ = Balances::make_free_balance_be(&ctrl, val); - assert_ok!(Staking::bond( - RuntimeOrigin::signed(stash), - ctrl, - val, - RewardDestination::Controller - )); +pub(crate) fn bond(who: AccountId, val: Balance) { + let _ = Balances::make_free_balance_be(&who, val); + assert_ok!(Staking::bond(RuntimeOrigin::signed(who), val, RewardDestination::Controller)); } -pub(crate) fn bond_validator(stash: AccountId, ctrl: AccountId, val: Balance) { - bond(stash, ctrl, val); - assert_ok!(Staking::validate(RuntimeOrigin::signed(ctrl), ValidatorPrefs::default())); +pub(crate) fn bond_validator(who: AccountId, val: Balance) { + bond(who, val); + assert_ok!(Staking::validate(RuntimeOrigin::signed(who), ValidatorPrefs::default())); assert_ok!(Session::set_keys( - RuntimeOrigin::signed(ctrl), - SessionKeys { other: ctrl.into() }, + RuntimeOrigin::signed(who), + SessionKeys { other: who.into() }, vec![] )); } -pub(crate) fn bond_nominator( - stash: AccountId, - ctrl: AccountId, - val: Balance, - target: Vec, -) { - bond(stash, ctrl, val); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(ctrl), target)); +pub(crate) fn bond_nominator(who: AccountId, val: Balance, target: Vec) { + bond(who, val); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(who), target)); } /// Progress to the given block, triggering session and era changes as we progress. diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 7af9b0aaaa04..f2aa48e3316d 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,11 +22,12 @@ use frame_election_provider_support::{ SortedListProvider, VoteWeight, VoterOf, }; use frame_support::{ + defensive, dispatch::WithPostDispatchInfo, pallet_prelude::*, traits::{ - Currency, CurrencyToVote, Defensive, DefensiveResult, EstimateNextNewSession, Get, - Imbalance, LockableCurrency, OnUnbalanced, TryCollect, UnixTime, WithdrawReasons, + Currency, Defensive, DefensiveResult, EstimateNextNewSession, Get, Imbalance, + LockableCurrency, OnUnbalanced, TryCollect, UnixTime, WithdrawReasons, }, weights::Weight, }; @@ -37,6 +38,7 @@ use sp_runtime::{ Perbill, }; use sp_staking::{ + currency_to_vote::CurrencyToVote, offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, EraIndex, SessionIndex, Stake, StakingInterface, }; @@ -50,6 +52,11 @@ use crate::{ use super::{pallet::*, STAKING_ID}; +#[cfg(feature = "try-runtime")] +use frame_support::ensure; +#[cfg(any(test, feature = "try-runtime"))] +use sp_runtime::TryRuntimeError; + /// The maximum number of iterations that we do whilst iterating over `T::VoterList` in /// `get_npos_voters`. /// @@ -668,12 +675,12 @@ impl Pallet { /// Clear all era information for given era. pub(crate) fn clear_era_information(era_index: EraIndex) { - #[allow(deprecated)] - >::remove_prefix(era_index, None); - #[allow(deprecated)] - >::remove_prefix(era_index, None); - #[allow(deprecated)] - >::remove_prefix(era_index, None); + let mut cursor = >::clear_prefix(era_index, u32::MAX, None); + debug_assert!(cursor.maybe_cursor.is_none()); + cursor = >::clear_prefix(era_index, u32::MAX, None); + debug_assert!(cursor.maybe_cursor.is_none()); + cursor = >::clear_prefix(era_index, u32::MAX, None); + debug_assert!(cursor.maybe_cursor.is_none()); >::remove(era_index); >::remove(era_index); >::remove(era_index); @@ -682,7 +689,7 @@ impl Pallet { /// Apply previously-unapplied slashes on the beginning of a new era, after a delay. fn apply_unapplied_slashes(active_era: EraIndex) { - let era_slashes = ::UnappliedSlashes::take(&active_era); + let era_slashes = UnappliedSlashes::::take(&active_era); log!( debug, "found {} slashes scheduled to be executed in era {:?}", @@ -972,6 +979,20 @@ impl Pallet { } } +impl Pallet { + /// Returns the current nominations quota for nominators. + /// + /// Used by the runtime API. + /// Note: for now, this api runtime will always return value of `T::MaxNominations` and thus it + /// is redundant. However, with the upcoming changes in + /// , the nominations quota will change + /// depending on the nominators balance. We're introducing this runtime API now to prepare the + /// community to use it before rolling out PR#12970. + pub fn api_nominations_quota(_balance: BalanceOf) -> u32 { + T::MaxNominations::get() + } +} + impl ElectionDataProvider for Pallet { type AccountId = T::AccountId; type BlockNumber = BlockNumberFor; @@ -1001,7 +1022,7 @@ impl ElectionDataProvider for Pallet { Ok(Self::get_npos_targets(None)) } - fn next_election_prediction(now: T::BlockNumber) -> T::BlockNumber { + fn next_election_prediction(now: BlockNumberFor) -> BlockNumberFor { let current_era = Self::current_era().unwrap_or(0); let current_session = Self::current_planned_session(); let current_era_start_session_index = @@ -1018,7 +1039,7 @@ impl ElectionDataProvider for Pallet { let session_length = T::NextNewSession::average_session_length(); - let sessions_left: T::BlockNumber = match ForceEra::::get() { + let sessions_left: BlockNumberFor = match ForceEra::::get() { Forcing::ForceNone => Bounded::max_value(), Forcing::ForceNew | Forcing::ForceAlways => Zero::zero(), Forcing::NotForcing if era_progress >= T::SessionsPerEra::get() => Zero::zero(), @@ -1216,24 +1237,14 @@ impl historical::SessionManager pallet_authorship::EventHandler for Pallet +/// * 20 points to the block producer for producing a (non-uncle) block, +impl pallet_authorship::EventHandler> for Pallet where T: Config + pallet_authorship::Config + pallet_session::Config, { fn note_author(author: T::AccountId) { Self::reward_by_ids(vec![(author, 20)]) } - fn note_uncle(uncle_author: T::AccountId, _age: T::BlockNumber) { - // defensive-only: block author must exist. - if let Some(block_author) = >::author() { - Self::reward_by_ids(vec![(block_author, 2), (uncle_author, 1)]) - } else { - crate::log!(warn, "block author not set, this should never happen"); - } - } } /// This is intended to be used with `FilterHistoricalOffences`. @@ -1263,7 +1274,7 @@ where disable_strategy: DisableStrategy, ) -> Weight { let reward_proportion = SlashRewardFraction::::get(); - let mut consumed_weight = Weight::from_ref_time(0); + let mut consumed_weight = Weight::from_parts(0, 0); let mut add_db_reads_writes = |reads, writes| { consumed_weight += T::DbWeight::get().reads_writes(reads, writes); }; @@ -1295,8 +1306,8 @@ where add_db_reads_writes(1, 0); // Reverse because it's more likely to find reports from recent eras. - match eras.iter().rev().find(|&&(_, ref sesh)| sesh <= &slash_session) { - Some(&(ref slash_era, _)) => *slash_era, + match eras.iter().rev().find(|&(_, sesh)| sesh <= &slash_session) { + Some((slash_era, _)) => *slash_era, // Before bonding period. defensive - should be filtered out. None => return consumed_weight, } @@ -1365,7 +1376,7 @@ where active_era, slash_era + slash_defer_duration + 1, ); - ::UnappliedSlashes::mutate( + UnappliedSlashes::::mutate( slash_era.saturating_add(slash_defer_duration).saturating_add(One::one()), move |for_later| for_later.push(unapplied), ); @@ -1461,9 +1472,11 @@ impl SortedListProvider for UseValidatorsMap { // nothing to do upon regenerate. 0 } - fn try_state() -> Result<(), &'static str> { + #[cfg(feature = "try-runtime")] + fn try_state() -> Result<(), TryRuntimeError> { Ok(()) } + fn unsafe_clear() { #[allow(deprecated)] Validators::::remove_all(); @@ -1535,7 +1548,9 @@ impl SortedListProvider for UseNominatorsAndValidatorsM // nothing to do upon regenerate. 0 } - fn try_state() -> Result<(), &'static str> { + + #[cfg(feature = "try-runtime")] + fn try_state() -> Result<(), TryRuntimeError> { Ok(()) } @@ -1554,10 +1569,10 @@ impl SortedListProvider for UseNominatorsAndValidatorsM } } -// NOTE: in this entire impl block, the assumption is that `who` is a stash account. impl StakingInterface for Pallet { type AccountId = T::AccountId; type Balance = BalanceOf; + type CurrencyToVote = T::CurrencyToVote; fn minimum_nominator_bond() -> Self::Balance { MinNominatorBond::::get() @@ -1600,10 +1615,10 @@ impl StakingInterface for Pallet { Self::current_era().unwrap_or(Zero::zero()) } - fn stake(who: &Self::AccountId) -> Result, DispatchError> { + fn stake(who: &Self::AccountId) -> Result>, DispatchError> { Self::bonded(who) .and_then(|c| Self::ledger(c)) - .map(|l| Stake { stash: l.stash, total: l.total, active: l.active }) + .map(|l| Stake { total: l.total, active: l.active }) .ok_or(Error::::NotStash.into()) } @@ -1642,7 +1657,6 @@ impl StakingInterface for Pallet { ) -> DispatchResult { Self::bond( RawOrigin::Signed(who.clone()).into(), - T::Lookup::unlookup(who.clone()), value, RewardDestination::Account(payee.clone()), ) @@ -1654,8 +1668,33 @@ impl StakingInterface for Pallet { Self::nominate(RawOrigin::Signed(ctrl).into(), targets) } + fn status( + who: &Self::AccountId, + ) -> Result, DispatchError> { + let is_bonded = Self::bonded(who).is_some(); + if !is_bonded { + return Err(Error::::NotStash.into()) + } + + let is_validator = Validators::::contains_key(&who); + let is_nominator = Nominators::::get(&who); + + use sp_staking::StakerStatus; + match (is_validator, is_nominator.is_some()) { + (false, false) => Ok(StakerStatus::Idle), + (true, false) => Ok(StakerStatus::Validator), + (false, true) => Ok(StakerStatus::Nominator( + is_nominator.expect("is checked above; qed").targets.into_inner(), + )), + (true, true) => { + defensive!("cannot be both validators and nominator"); + Err(Error::::BadState.into()) + }, + } + } + sp_staking::runtime_benchmarks_enabled! { - fn nominations(who: Self::AccountId) -> Option> { + fn nominations(who: &Self::AccountId) -> Option> { Nominators::::get(who).map(|n| n.targets.into_inner()) } @@ -1680,7 +1719,7 @@ impl StakingInterface for Pallet { #[cfg(any(test, feature = "try-runtime"))] impl Pallet { - pub(crate) fn do_try_state(_: BlockNumberFor) -> Result<(), &'static str> { + pub(crate) fn do_try_state(_: BlockNumberFor) -> Result<(), TryRuntimeError> { ensure!( T::VoterList::iter() .all(|x| >::contains_key(&x) || >::contains_key(&x)), @@ -1693,7 +1732,7 @@ impl Pallet { Self::check_count() } - fn check_count() -> Result<(), &'static str> { + fn check_count() -> Result<(), TryRuntimeError> { ensure!( ::VoterList::count() == Nominators::::count() + Validators::::count(), @@ -1706,18 +1745,19 @@ impl Pallet { ensure!( ValidatorCount::::get() <= ::MaxWinners::get(), - "validator count exceeded election max winners" + Error::::TooManyValidators ); Ok(()) } - fn check_ledgers() -> Result<(), &'static str> { + fn check_ledgers() -> Result<(), TryRuntimeError> { Bonded::::iter() .map(|(_, ctrl)| Self::ensure_ledger_consistent(ctrl)) - .collect::>() + .collect::, _>>()?; + Ok(()) } - fn check_exposures() -> Result<(), &'static str> { + fn check_exposures() -> Result<(), TryRuntimeError> { // a check per validator to ensure the exposure struct is always sane. let era = Self::active_era().unwrap().index; ErasStakers::::iter_prefix_values(era) @@ -1733,10 +1773,10 @@ impl Pallet { ); Ok(()) }) - .collect::>() + .collect::>() } - fn check_nominators() -> Result<(), &'static str> { + fn check_nominators() -> Result<(), TryRuntimeError> { // a check per nominator to ensure their entire stake is correctly distributed. Will only // kick-in if the nomination was submitted before the current era. let era = Self::active_era().unwrap().index; @@ -1750,27 +1790,33 @@ impl Pallet { } }, ) - .map(|nominator| { + .map(|nominator| -> Result<(), TryRuntimeError> { // must be bonded. Self::ensure_is_stash(&nominator)?; let mut sum = BalanceOf::::zero(); T::SessionInterface::validators() .iter() .map(|v| Self::eras_stakers(era, v)) - .map(|e| { + .map(|e| -> Result<(), TryRuntimeError> { let individual = e.others.iter().filter(|e| e.who == nominator).collect::>(); let len = individual.len(); match len { 0 => { /* not supporting this validator at all. */ }, 1 => sum += individual[0].value, - _ => return Err("nominator cannot back a validator more than once."), + _ => + return Err( + "nominator cannot back a validator more than once.".into() + ), }; Ok(()) }) - .collect::>() + .collect::, _>>()?; + Ok(()) }) - .collect::>() + .collect::, _>>()?; + + Ok(()) } fn ensure_is_stash(who: &T::AccountId) -> Result<(), &'static str> { @@ -1778,7 +1824,7 @@ impl Pallet { Ok(()) } - fn ensure_ledger_consistent(ctrl: T::AccountId) -> Result<(), &'static str> { + fn ensure_ledger_consistent(ctrl: T::AccountId) -> Result<(), TryRuntimeError> { // ensures ledger.total == ledger.active + sum(ledger.unlocking). let ledger = Self::ledger(ctrl.clone()).ok_or("Not a controller.")?; let real_total: BalanceOf = diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index c0c18b40cf02..ec9805b333ec 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ use frame_support::{ dispatch::Codec, pallet_prelude::*, traits::{ - Currency, CurrencyToVote, Defensive, DefensiveResult, DefensiveSaturating, EnsureOrigin, + Currency, Defensive, DefensiveResult, DefensiveSaturating, EnsureOrigin, EstimateNextNewSession, Get, LockIdentifier, LockableCurrency, OnUnbalanced, TryCollect, UnixTime, }, @@ -68,7 +68,6 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(13); #[pallet::pallet] - #[pallet::generate_store(pub(crate) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); @@ -88,7 +87,7 @@ pub mod pallet { /// The staking balance. type Currency: LockableCurrency< Self::AccountId, - Moment = Self::BlockNumber, + Moment = BlockNumberFor, Balance = Self::CurrencyBalance, >; /// Just the `Currency::Balance` type; we have this item to allow us to constrain it to @@ -114,19 +113,19 @@ pub mod pallet { /// in 128. /// Consequently, the backward convert is used convert the u128s from sp-elections back to a /// [`BalanceOf`]. - type CurrencyToVote: CurrencyToVote>; + type CurrencyToVote: sp_staking::currency_to_vote::CurrencyToVote>; /// Something that provides the election functionality. type ElectionProvider: ElectionProvider< AccountId = Self::AccountId, - BlockNumber = Self::BlockNumber, + BlockNumber = BlockNumberFor, // we only accept an election provider that has staking as data provider. DataProvider = Pallet, >; /// Something that provides the election functionality at genesis. type GenesisElectionProvider: ElectionProvider< AccountId = Self::AccountId, - BlockNumber = Self::BlockNumber, + BlockNumber = BlockNumberFor, DataProvider = Pallet, >; @@ -201,7 +200,7 @@ pub mod pallet { /// Something that can estimate the next session change, accurately or as a best effort /// guess. - type NextNewSession: EstimateNextNewSession; + type NextNewSession: EstimateNextNewSession>; /// The maximum number of nominators rewarded for each validator. /// @@ -262,9 +261,11 @@ pub mod pallet { #[pallet::constant] type MaxUnlockingChunks: Get; - /// A hook called when any staker is slashed. Mostly likely this can be a no-op unless - /// other pallets exist that are affected by slashing per-staker. - type OnStakerSlash: sp_staking::OnStakerSlash>; + /// Something that listens to staking updates and performs actions based on the data it + /// receives. + /// + /// WARNING: this only reports slashing events for the time being. + type EventListeners: sp_staking::OnStakingUpdate>; /// Some parameters of the benchmarking. type BenchmarkingConfig: BenchmarkingConfig; @@ -580,6 +581,7 @@ pub mod pallet { pub(crate) type ChillThreshold = StorageValue<_, Percent, OptionQuery>; #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { pub validator_count: u32, pub minimum_validator_count: u32, @@ -595,27 +597,8 @@ pub mod pallet { pub max_nominator_count: Option, } - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - GenesisConfig { - validator_count: Default::default(), - minimum_validator_count: Default::default(), - invulnerables: Default::default(), - force_era: Default::default(), - slash_reward_fraction: Default::default(), - canceled_payout: Default::default(), - stakers: Default::default(), - min_nominator_bond: Default::default(), - min_validator_bond: Default::default(), - max_validator_count: None, - max_nominator_count: None, - } - } - } - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { ValidatorCount::::put(self.validator_count); MinimumValidatorCount::::put(self.minimum_validator_count); @@ -632,7 +615,7 @@ pub mod pallet { MaxNominatorsCount::::put(x); } - for &(ref stash, ref controller, balance, ref status) in &self.stakers { + for &(ref stash, _, balance, ref status) in &self.stakers { crate::log!( trace, "inserting genesis staker: {:?} => {:?} => {:?}", @@ -646,17 +629,16 @@ pub mod pallet { ); frame_support::assert_ok!(>::bond( T::RuntimeOrigin::from(Some(stash.clone()).into()), - T::Lookup::unlookup(controller.clone()), balance, RewardDestination::Staked, )); frame_support::assert_ok!(match status { crate::StakerStatus::Validator => >::validate( - T::RuntimeOrigin::from(Some(controller.clone()).into()), + T::RuntimeOrigin::from(Some(stash.clone()).into()), Default::default(), ), crate::StakerStatus::Nominator(votes) => >::nominate( - T::RuntimeOrigin::from(Some(controller.clone()).into()), + T::RuntimeOrigin::from(Some(stash.clone()).into()), votes.iter().map(|l| T::Lookup::unlookup(l.clone())).collect(), ), _ => Ok(()), @@ -812,20 +794,16 @@ pub mod pallet { ::MaxWinners::get() ); - sp_std::if_std! { - sp_io::TestExternalities::new_empty().execute_with(|| - assert!( - T::SlashDeferDuration::get() < T::BondingDuration::get() || T::BondingDuration::get() == 0, - "As per documentation, slash defer duration ({}) should be less than bonding duration ({}).", - T::SlashDeferDuration::get(), - T::BondingDuration::get(), - ) - ); - } + assert!( + T::SlashDeferDuration::get() < T::BondingDuration::get() || T::BondingDuration::get() == 0, + "As per documentation, slash defer duration ({}) should be less than bonding duration ({}).", + T::SlashDeferDuration::get(), + T::BondingDuration::get(), + ) } #[cfg(feature = "try-runtime")] - fn try_state(n: BlockNumberFor) -> Result<(), &'static str> { + fn try_state(n: BlockNumberFor) -> Result<(), sp_runtime::TryRuntimeError> { Self::do_try_state(n) } } @@ -840,32 +818,28 @@ pub mod pallet { /// The dispatch origin for this call must be _Signed_ by the stash account. /// /// Emits `Bonded`. - /// # + /// ## Complexity /// - Independent of the arguments. Moderate complexity. /// - O(1). /// - Three extra DB entries. /// /// NOTE: Two of the storage writes (`Self::bonded`, `Self::payee`) are _never_ cleaned /// unless the `origin` falls below _existential deposit_ and gets removed as dust. - /// ------------------ - /// # #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::bond())] pub fn bond( origin: OriginFor, - controller: AccountIdLookupOf, #[pallet::compact] value: BalanceOf, payee: RewardDestination, ) -> DispatchResult { let stash = ensure_signed(origin)?; + let controller_to_be_deprecated = stash.clone(); if >::contains_key(&stash) { return Err(Error::::AlreadyBonded.into()) } - let controller = T::Lookup::lookup(controller)?; - - if >::contains_key(&controller) { + if >::contains_key(&controller_to_be_deprecated) { return Err(Error::::AlreadyPaired.into()) } @@ -878,7 +852,7 @@ pub mod pallet { // You're auto-bonded forever, here. We might improve this by only bonding when // you actually validate/nominate and remove once you unbond __everything__. - >::insert(&stash, &controller); + >::insert(&stash, &stash); >::insert(&stash, payee); let current_era = CurrentEra::::get().unwrap_or(0); @@ -889,7 +863,7 @@ pub mod pallet { let value = value.min(stash_balance); Self::deposit_event(Event::::Bonded { stash: stash.clone(), amount: value }); let item = StakingLedger { - stash, + stash: stash.clone(), total: value, active: value, unlocking: Default::default(), @@ -900,7 +874,7 @@ pub mod pallet { // satisfied. .defensive_map_err(|_| Error::::BoundNotMet)?, }; - Self::update_ledger(&controller, &item); + Self::update_ledger(&controller_to_be_deprecated, &item); Ok(()) } @@ -915,10 +889,9 @@ pub mod pallet { /// /// Emits `Bonded`. /// - /// # + /// ## Complexity /// - Independent of the arguments. Insignificant complexity. /// - O(1). - /// # #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::bond_extra())] pub fn bond_extra( @@ -1031,9 +1004,7 @@ pub mod pallet { // Note: in case there is no current era it is fine to bond one era more. let era = Self::current_era().unwrap_or(0) + T::BondingDuration::get(); - if let Some(mut chunk) = - ledger.unlocking.last_mut().filter(|chunk| chunk.era == era) - { + if let Some(chunk) = ledger.unlocking.last_mut().filter(|chunk| chunk.era == era) { // To keep the chunk count down, we only keep one chunk per era. Since // `unlocking` is a FiFo queue, if a chunk exists for `era` we know that it will // be the last one. @@ -1067,8 +1038,8 @@ pub mod pallet { /// Remove any unlocked chunks from the `unlocking` queue from our management. /// - /// This essentially frees up that balance to be used by the stash account to do - /// whatever it wants. + /// This essentially frees up that balance to be used by the stash account to do whatever + /// it wants. /// /// The dispatch origin for this call must be _Signed_ by the controller. /// @@ -1076,10 +1047,18 @@ pub mod pallet { /// /// See also [`Call::unbond`]. /// - /// # - /// Complexity O(S) where S is the number of slashing spans to remove + /// ## Parameters + /// + /// - `num_slashing_spans` indicates the number of metadata slashing spans to clear when + /// this call results in a complete removal of all the data related to the stash account. + /// In this case, the `num_slashing_spans` must be larger or equal to the number of + /// slashing spans associated with the stash account in the [`SlashingSpans`] storage type, + /// otherwise the call will fail. The call weight is directly propotional to + /// `num_slashing_spans`. + /// + /// ## Complexity + /// O(S) where S is the number of slashing spans to remove /// NOTE: Weight annotation is the kill scenario, we refund otherwise. - /// # #[pallet::call_index(3)] #[pallet::weight(T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans))] pub fn withdraw_unbonded( @@ -1136,11 +1115,10 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. /// - /// # + /// ## Complexity /// - The transaction's complexity is proportional to the size of `targets` (N) /// which is capped at CompactAssignments::LIMIT (T::MaxNominations). /// - Both the reads and writes follow a similar pattern. - /// # #[pallet::call_index(5)] #[pallet::weight(T::WeightInfo::nominate(targets.len() as u32))] pub fn nominate( @@ -1205,11 +1183,10 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. /// - /// # + /// ## Complexity /// - Independent of the arguments. Insignificant complexity. /// - Contains one read. /// - Writes are limited to the `origin` account key. - /// # #[pallet::call_index(6)] #[pallet::weight(T::WeightInfo::chill())] pub fn chill(origin: OriginFor) -> DispatchResult { @@ -1225,16 +1202,12 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_ by the controller, not the stash. /// - /// # + /// ## Complexity + /// - O(1) /// - Independent of the arguments. Insignificant complexity. /// - Contains a limited number of reads. /// - Writes are limited to the `origin` account key. /// --------- - /// - Weight: O(1) - /// - DB Weight: - /// - Read: Ledger - /// - Write: Payee - /// # #[pallet::call_index(7)] #[pallet::weight(T::WeightInfo::set_payee())] pub fn set_payee( @@ -1248,38 +1221,33 @@ pub mod pallet { Ok(()) } - /// (Re-)set the controller of a stash. + /// (Re-)sets the controller of a stash to the stash itself. This function previously + /// accepted a `controller` argument to set the controller to an account other than the + /// stash itself. This functionality has now been removed, now only setting the controller + /// to the stash, if it is not already. /// /// Effects will be felt instantly (as soon as this function is completed successfully). /// /// The dispatch origin for this call must be _Signed_ by the stash, not the controller. /// - /// # + /// ## Complexity + /// O(1) /// - Independent of the arguments. Insignificant complexity. /// - Contains a limited number of reads. /// - Writes are limited to the `origin` account key. - /// ---------- - /// Weight: O(1) - /// DB Weight: - /// - Read: Bonded, Ledger New Controller, Ledger Old Controller - /// - Write: Bonded, Ledger New Controller, Ledger Old Controller - /// # #[pallet::call_index(8)] #[pallet::weight(T::WeightInfo::set_controller())] - pub fn set_controller( - origin: OriginFor, - controller: AccountIdLookupOf, - ) -> DispatchResult { + pub fn set_controller(origin: OriginFor) -> DispatchResult { let stash = ensure_signed(origin)?; let old_controller = Self::bonded(&stash).ok_or(Error::::NotStash)?; - let controller = T::Lookup::lookup(controller)?; - if >::contains_key(&controller) { + + if >::contains_key(&stash) { return Err(Error::::AlreadyPaired.into()) } - if controller != old_controller { - >::insert(&stash, &controller); + if old_controller != stash { + >::insert(&stash, &stash); if let Some(l) = >::take(&old_controller) { - >::insert(&controller, l); + >::insert(&stash, l); } } Ok(()) @@ -1289,10 +1257,8 @@ pub mod pallet { /// /// The dispatch origin must be Root. /// - /// # - /// Weight: O(1) - /// Write: Validator Count - /// # + /// ## Complexity + /// O(1) #[pallet::call_index(9)] #[pallet::weight(T::WeightInfo::set_validator_count())] pub fn set_validator_count( @@ -1315,9 +1281,8 @@ pub mod pallet { /// /// The dispatch origin must be Root. /// - /// # + /// ## Complexity /// Same as [`Self::set_validator_count`]. - /// # #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::set_validator_count())] pub fn increase_validator_count( @@ -1341,9 +1306,8 @@ pub mod pallet { /// /// The dispatch origin must be Root. /// - /// # + /// ## Complexity /// Same as [`Self::set_validator_count`]. - /// # #[pallet::call_index(11)] #[pallet::weight(T::WeightInfo::set_validator_count())] pub fn scale_validator_count(origin: OriginFor, factor: Percent) -> DispatchResult { @@ -1370,11 +1334,9 @@ pub mod pallet { /// Thus the election process may be ongoing when this is called. In this case the /// election will continue until the next era is triggered. /// - /// # + /// ## Complexity /// - No arguments. /// - Weight: O(1) - /// - Write: ForceEra - /// # #[pallet::call_index(12)] #[pallet::weight(T::WeightInfo::force_no_eras())] pub fn force_no_eras(origin: OriginFor) -> DispatchResult { @@ -1394,11 +1356,9 @@ pub mod pallet { /// If this is called just before a new era is triggered, the election process may not /// have enough blocks to get a result. /// - /// # + /// ## Complexity /// - No arguments. /// - Weight: O(1) - /// - Write ForceEra - /// # #[pallet::call_index(13)] #[pallet::weight(T::WeightInfo::force_new_era())] pub fn force_new_era(origin: OriginFor) -> DispatchResult { @@ -1424,6 +1384,11 @@ pub mod pallet { /// Force a current staker to become completely unstaked, immediately. /// /// The dispatch origin must be Root. + /// + /// ## Parameters + /// + /// - `num_slashing_spans`: Refer to comments on [`Call::withdraw_unbonded`] for more + /// details. #[pallet::call_index(15)] #[pallet::weight(T::WeightInfo::force_unstake(*num_slashing_spans))] pub fn force_unstake( @@ -1475,7 +1440,7 @@ pub mod pallet { ensure!(!slash_indices.is_empty(), Error::::EmptyTargets); ensure!(is_sorted_and_unique(&slash_indices), Error::::NotSortedAndUnique); - let mut unapplied = ::UnappliedSlashes::get(&era); + let mut unapplied = UnappliedSlashes::::get(&era); let last_item = slash_indices[slash_indices.len() - 1]; ensure!((last_item as usize) < unapplied.len(), Error::::InvalidSlashIndex); @@ -1484,7 +1449,7 @@ pub mod pallet { unapplied.remove(index); } - ::UnappliedSlashes::insert(&era, &unapplied); + UnappliedSlashes::::insert(&era, &unapplied); Ok(()) } @@ -1497,18 +1462,8 @@ pub mod pallet { /// The origin of this call must be _Signed_. Any account can call this function, even if /// it is not one of the stakers. /// - /// # - /// - Time complexity: at most O(MaxNominatorRewardedPerValidator). - /// - Contains a limited number of reads and writes. - /// ----------- - /// N is the Number of payouts for the validator (including the validator) - /// Weight: - /// - Reward Destination Staked: O(N) - /// - Reward Destination Controller (Creating): O(N) - /// - /// NOTE: weights are assuming that payouts are made to alive stash account (Staked). - /// Paying even a dead controller is cheaper weight-wise. We don't do any refunds here. - /// # + /// ## Complexity + /// - At most O(MaxNominatorRewardedPerValidator). #[pallet::call_index(18)] #[pallet::weight(T::WeightInfo::payout_stakers_alive_staked( T::MaxNominatorRewardedPerValidator::get() @@ -1526,11 +1481,9 @@ pub mod pallet { /// /// The dispatch origin must be signed by the controller. /// - /// # + /// ## Complexity /// - Time complexity: O(L), where L is unlocking chunks /// - Bounded by `MaxUnlockingChunks`. - /// - Storage changes: Can't increase storage, only decrease it. - /// # #[pallet::call_index(19)] #[pallet::weight(T::WeightInfo::rebond(T::MaxUnlockingChunks::get() as u32))] pub fn rebond( @@ -1576,6 +1529,11 @@ pub mod pallet { /// It can be called by anyone, as long as `stash` meets the above requirements. /// /// Refunds the transaction fees upon successful execution. + /// + /// ## Parameters + /// + /// - `num_slashing_spans`: Refer to comments on [`Call::withdraw_unbonded`] for more + /// details. #[pallet::call_index(20)] #[pallet::weight(T::WeightInfo::reap_stash(*num_slashing_spans))] pub fn reap_stash( diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index aeea0a1a58c6..bb02da73f6e5 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,8 +50,9 @@ //! Based on research at use crate::{ - BalanceOf, Config, Error, Exposure, NegativeImbalanceOf, Pallet, Perbill, SessionInterface, - Store, UnappliedSlash, + BalanceOf, Config, Error, Exposure, NegativeImbalanceOf, NominatorSlashInEra, + OffendingValidators, Pallet, Perbill, SessionInterface, SpanSlash, UnappliedSlash, + ValidatorSlashInEra, }; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ @@ -239,14 +240,13 @@ pub(crate) fn compute_slash( return None } - let prior_slash_p = - as Store>::ValidatorSlashInEra::get(¶ms.slash_era, params.stash) - .map_or(Zero::zero(), |(prior_slash_proportion, _)| prior_slash_proportion); + let prior_slash_p = ValidatorSlashInEra::::get(¶ms.slash_era, params.stash) + .map_or(Zero::zero(), |(prior_slash_proportion, _)| prior_slash_proportion); // compare slash proportions rather than slash values to avoid issues due to rounding // error. if params.slash.deconstruct() > prior_slash_p.deconstruct() { - as Store>::ValidatorSlashInEra::insert( + ValidatorSlashInEra::::insert( ¶ms.slash_era, params.stash, &(params.slash, own_slash), @@ -327,7 +327,7 @@ fn kick_out_if_recent(params: SlashParams) { /// If after adding the validator `OffendingValidatorsThreshold` is reached /// a new era will be forced. fn add_offending_validator(stash: &T::AccountId, disable: bool) { - as Store>::OffendingValidators::mutate(|offending| { + OffendingValidators::::mutate(|offending| { let validators = T::SessionInterface::validators(); let validator_index = match validators.iter().position(|i| i == stash) { Some(index) => index, @@ -388,10 +388,9 @@ fn slash_nominators( let own_slash_difference = own_slash_by_validator.saturating_sub(own_slash_prior); let mut era_slash = - as Store>::NominatorSlashInEra::get(¶ms.slash_era, stash) - .unwrap_or_else(Zero::zero); + NominatorSlashInEra::::get(¶ms.slash_era, stash).unwrap_or_else(Zero::zero); era_slash += own_slash_difference; - as Store>::NominatorSlashInEra::insert(¶ms.slash_era, stash, &era_slash); + NominatorSlashInEra::::insert(¶ms.slash_era, stash, &era_slash); era_slash }; @@ -445,9 +444,9 @@ fn fetch_spans<'a, T: Config + 'a>( slash_of: &'a mut BalanceOf, reward_proportion: Perbill, ) -> InspectingSpans<'a, T> { - let spans = as Store>::SlashingSpans::get(stash).unwrap_or_else(|| { + let spans = crate::SlashingSpans::::get(stash).unwrap_or_else(|| { let spans = SlashingSpans::new(window_start); - as Store>::SlashingSpans::insert(stash, &spans); + crate::SlashingSpans::::insert(stash, &spans); spans }); @@ -496,7 +495,7 @@ impl<'a, T: 'a + Config> InspectingSpans<'a, T> { ) -> Option { let target_span = self.era_span(slash_era)?; let span_slash_key = (self.stash.clone(), target_span.index); - let mut span_record = as Store>::SpanSlash::get(&span_slash_key); + let mut span_record = SpanSlash::::get(&span_slash_key); let mut changed = false; let reward = if span_record.slashed < slash { @@ -527,7 +526,7 @@ impl<'a, T: 'a + Config> InspectingSpans<'a, T> { if changed { self.dirty = true; - as Store>::SpanSlash::insert(&span_slash_key, &span_record); + SpanSlash::::insert(&span_slash_key, &span_record); } Some(target_span.index) @@ -543,20 +542,20 @@ impl<'a, T: 'a + Config> Drop for InspectingSpans<'a, T> { if let Some((start, end)) = self.spans.prune(self.window_start) { for span_index in start..end { - as Store>::SpanSlash::remove(&(self.stash.clone(), span_index)); + SpanSlash::::remove(&(self.stash.clone(), span_index)); } } - as Store>::SlashingSpans::insert(self.stash, &self.spans); + crate::SlashingSpans::::insert(self.stash, &self.spans); } } /// Clear slashing metadata for an obsolete era. pub(crate) fn clear_era_metadata(obsolete_era: EraIndex) { #[allow(deprecated)] - as Store>::ValidatorSlashInEra::remove_prefix(&obsolete_era, None); + ValidatorSlashInEra::::remove_prefix(&obsolete_era, None); #[allow(deprecated)] - as Store>::NominatorSlashInEra::remove_prefix(&obsolete_era, None); + NominatorSlashInEra::::remove_prefix(&obsolete_era, None); } /// Clear slashing metadata for a dead account. @@ -564,7 +563,7 @@ pub(crate) fn clear_stash_metadata( stash: &T::AccountId, num_slashing_spans: u32, ) -> DispatchResult { - let spans = match as Store>::SlashingSpans::get(stash) { + let spans = match crate::SlashingSpans::::get(stash) { None => return Ok(()), Some(s) => s, }; @@ -574,7 +573,7 @@ pub(crate) fn clear_stash_metadata( Error::::IncorrectSlashingSpans ); - as Store>::SlashingSpans::remove(stash); + crate::SlashingSpans::::remove(stash); // kill slashing-span metadata for account. // @@ -582,7 +581,7 @@ pub(crate) fn clear_stash_metadata( // in that case, they may re-bond, but it would count again as span 0. Further ancient // slashes would slash into this new bond, since metadata has now been cleared. for span in spans.iter() { - as Store>::SpanSlash::remove(&(stash.clone(), span.index)); + SpanSlash::::remove(&(stash.clone(), span.index)); } Ok(()) diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index 0e0ac7652347..28e08230d701 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -75,17 +75,37 @@ pub fn create_stash_controller( n: u32, balance_factor: u32, destination: RewardDestination, +) -> Result<(T::AccountId, T::AccountId), &'static str> { + let staker = create_funded_user::("stash", n, balance_factor); + let amount = T::Currency::minimum_balance() * (balance_factor / 10).max(1).into(); + Staking::::bond(RawOrigin::Signed(staker.clone()).into(), amount, destination)?; + Ok((staker.clone(), staker)) +} + +/// Create a unique stash and controller pair. +pub fn create_unique_stash_controller( + n: u32, + balance_factor: u32, + destination: RewardDestination, + dead_controller: bool, ) -> Result<(T::AccountId, T::AccountId), &'static str> { let stash = create_funded_user::("stash", n, balance_factor); - let controller = create_funded_user::("controller", n, balance_factor); - let controller_lookup = T::Lookup::unlookup(controller.clone()); + + let controller = if dead_controller { + create_funded_user::("controller", n, 0) + } else { + create_funded_user::("controller", n, balance_factor) + }; let amount = T::Currency::minimum_balance() * (balance_factor / 10).max(1).into(); - Staking::::bond( - RawOrigin::Signed(stash.clone()).into(), - controller_lookup, - amount, - destination, - )?; + Staking::::bond(RawOrigin::Signed(stash.clone()).into(), amount, destination)?; + + // update ledger to be a *different* controller to stash + if let Some(l) = Ledger::::take(&stash) { + >::insert(&controller, l); + } + // update bonded account to be unique controller + >::insert(&stash, &controller); + Ok((stash, controller)) } @@ -95,38 +115,27 @@ pub fn create_stash_controller_with_balance( balance: crate::BalanceOf, destination: RewardDestination, ) -> Result<(T::AccountId, T::AccountId), &'static str> { - let stash = create_funded_user_with_balance::("stash", n, balance); - let controller = create_funded_user_with_balance::("controller", n, balance); - let controller_lookup = T::Lookup::unlookup(controller.clone()); - - Staking::::bond( - RawOrigin::Signed(stash.clone()).into(), - controller_lookup, - balance, - destination, - )?; - Ok((stash, controller)) + let staker = create_funded_user_with_balance::("stash", n, balance); + Staking::::bond(RawOrigin::Signed(staker.clone()).into(), balance, destination)?; + Ok((staker.clone(), staker)) } -/// Create a stash and controller pair, where the controller is dead, and payouts go to controller. -/// This is used to test worst case payout scenarios. -pub fn create_stash_and_dead_controller( +/// Create a stash and controller pair, where payouts go to a dead payee account. This is used to +/// test worst case payout scenarios. +pub fn create_stash_and_dead_payee( n: u32, balance_factor: u32, - destination: RewardDestination, ) -> Result<(T::AccountId, T::AccountId), &'static str> { - let stash = create_funded_user::("stash", n, balance_factor); - // controller has no funds - let controller = create_funded_user::("controller", n, 0); - let controller_lookup = T::Lookup::unlookup(controller.clone()); + let staker = create_funded_user::("stash", n, 0); + // payee has no funds + let payee = create_funded_user::("payee", n, 0); let amount = T::Currency::minimum_balance() * (balance_factor / 10).max(1).into(); Staking::::bond( - RawOrigin::Signed(stash.clone()).into(), - controller_lookup, + RawOrigin::Signed(staker.clone()).into(), amount, - destination, + RewardDestination::Account(payee), )?; - Ok((stash, controller)) + Ok((staker.clone(), staker)) } /// create `max` validators. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index acd41895287c..e3ee4cd1a8e9 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,7 +30,7 @@ use pallet_balances::Error as BalancesError; use sp_runtime::{ assert_eq_error_rate, traits::{BadOrigin, Dispatchable}, - Perbill, Percent, Rounding, + Perbill, Percent, Rounding, TokenError, }; use sp_staking::{ offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, @@ -92,14 +92,14 @@ fn set_staking_configs_works() { #[test] fn force_unstake_works() { ExtBuilder::default().build_and_execute(|| { - // Account 11 is stashed and locked, and account 10 is the controller - assert_eq!(Staking::bonded(&11), Some(10)); + // Account 11 (also controller) is stashed and locked + assert_eq!(Staking::bonded(&11), Some(11)); // Adds 2 slashing spans add_slash(&11); // Cant transfer assert_noop!( - Balances::transfer(RuntimeOrigin::signed(11), 1, 10), - BalancesError::::LiquidityRestrictions + Balances::transfer_allow_death(RuntimeOrigin::signed(11), 1, 10), + TokenError::Frozen, ); // Force unstake requires root. assert_noop!(Staking::force_unstake(RuntimeOrigin::signed(11), 11, 2), BadOrigin); @@ -113,15 +113,15 @@ fn force_unstake_works() { // No longer bonded. assert_eq!(Staking::bonded(&11), None); // Transfer works. - assert_ok!(Balances::transfer(RuntimeOrigin::signed(11), 1, 10)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(11), 1, 10)); }); } #[test] fn kill_stash_works() { ExtBuilder::default().build_and_execute(|| { - // Account 11 is stashed and locked, and account 10 is the controller - assert_eq!(Staking::bonded(&11), Some(10)); + // Account 11 (also controller) is stashed and locked + assert_eq!(Staking::bonded(&11), Some(11)); // Adds 2 slashing spans add_slash(&11); // Only can kill a stash account @@ -139,16 +139,16 @@ fn kill_stash_works() { fn basic_setup_works() { // Verifies initial conditions of mock ExtBuilder::default().build_and_execute(|| { - // Account 11 is stashed and locked, and account 10 is the controller - assert_eq!(Staking::bonded(&11), Some(10)); - // Account 21 is stashed and locked, and account 20 is the controller - assert_eq!(Staking::bonded(&21), Some(20)); + // Account 11 is stashed and locked, and is the controller + assert_eq!(Staking::bonded(&11), Some(11)); + // Account 21 is stashed and locked and is the controller + assert_eq!(Staking::bonded(&21), Some(21)); // Account 1 is not a stashed assert_eq!(Staking::bonded(&1), None); - // Account 10 controls the stash from account 11, which is 100 * balance_factor units + // Account 11 controls its own stash, which is 100 * balance_factor units assert_eq!( - Staking::ledger(&10).unwrap(), + Staking::ledger(&11).unwrap(), StakingLedger { stash: 11, total: 1000, @@ -157,9 +157,9 @@ fn basic_setup_works() { claimed_rewards: bounded_vec![], } ); - // Account 20 controls the stash from account 21, which is 200 * balance_factor units + // Account 21 controls its own stash, which is 200 * balance_factor units assert_eq!( - Staking::ledger(&20), + Staking::ledger(&21), Some(StakingLedger { stash: 21, total: 1000, @@ -182,7 +182,7 @@ fn basic_setup_works() { ); assert_eq!( - Staking::ledger(100), + Staking::ledger(101), Some(StakingLedger { stash: 101, total: 500, @@ -231,15 +231,49 @@ fn basic_setup_works() { #[test] fn change_controller_works() { ExtBuilder::default().build_and_execute(|| { - // 10 and 11 are bonded as stash controller. - assert_eq!(Staking::bonded(&11), Some(10)); + let (stash, controller) = testing_utils::create_unique_stash_controller::( + 0, + 100, + RewardDestination::Staked, + false, + ) + .unwrap(); + + // ensure `stash` and `controller` are bonded as stash controller pair. + assert_eq!(Staking::bonded(&stash), Some(controller)); + + // `controller` can control `stash` who is initially a validator. + assert_ok!(Staking::chill(RuntimeOrigin::signed(controller))); + + // sets controller back to `stash`. + assert_ok!(Staking::set_controller(RuntimeOrigin::signed(stash))); + assert_eq!(Staking::bonded(&stash), Some(stash)); + mock::start_active_era(1); + + // `controller` is no longer in control. `stash` is now controller. + assert_noop!( + Staking::validate(RuntimeOrigin::signed(controller), ValidatorPrefs::default()), + Error::::NotController, + ); + assert_ok!(Staking::validate(RuntimeOrigin::signed(stash), ValidatorPrefs::default())); + }) +} + +#[test] +fn change_controller_already_paired_once_stash() { + ExtBuilder::default().build_and_execute(|| { + // 10 and 11 are bonded as controller and stash respectively. + assert_eq!(Staking::bonded(&11), Some(11)); - // 10 can control 11 who is initially a validator. - assert_ok!(Staking::chill(RuntimeOrigin::signed(10))); + // 11 is initially a validator. + assert_ok!(Staking::chill(RuntimeOrigin::signed(11))); - // change controller - assert_ok!(Staking::set_controller(RuntimeOrigin::signed(11), 5)); - assert_eq!(Staking::bonded(&11), Some(5)); + // Controller cannot change once matching with stash. + assert_noop!( + Staking::set_controller(RuntimeOrigin::signed(11)), + Error::::AlreadyPaired + ); + assert_eq!(Staking::bonded(&11), Some(11)); mock::start_active_era(1); // 10 is no longer in control. @@ -247,18 +281,15 @@ fn change_controller_works() { Staking::validate(RuntimeOrigin::signed(10), ValidatorPrefs::default()), Error::::NotController, ); - assert_ok!(Staking::validate(RuntimeOrigin::signed(5), ValidatorPrefs::default())); + assert_ok!(Staking::validate(RuntimeOrigin::signed(11), ValidatorPrefs::default())); }) } #[test] fn rewards_should_work() { ExtBuilder::default().nominate(true).session_per_era(3).build_and_execute(|| { - let init_balance_10 = Balances::total_balance(&10); let init_balance_11 = Balances::total_balance(&11); - let init_balance_20 = Balances::total_balance(&20); let init_balance_21 = Balances::total_balance(&21); - let init_balance_100 = Balances::total_balance(&100); let init_balance_101 = Balances::total_balance(&101); // Set payees @@ -278,11 +309,8 @@ fn rewards_should_work() { start_session(1); assert_eq_uvec!(Session::validators(), vec![11, 21]); - assert_eq!(Balances::total_balance(&10), init_balance_10); assert_eq!(Balances::total_balance(&11), init_balance_11); - assert_eq!(Balances::total_balance(&20), init_balance_20); assert_eq!(Balances::total_balance(&21), init_balance_21); - assert_eq!(Balances::total_balance(&100), init_balance_100); assert_eq!(Balances::total_balance(&101), init_balance_101); assert_eq!( Staking::eras_reward_points(active_era()), @@ -291,10 +319,10 @@ fn rewards_should_work() { individual: vec![(11, 100), (21, 50)].into_iter().collect(), } ); - let part_for_10 = Perbill::from_rational::(1000, 1125); - let part_for_20 = Perbill::from_rational::(1000, 1375); - let part_for_100_from_10 = Perbill::from_rational::(125, 1125); - let part_for_100_from_20 = Perbill::from_rational::(375, 1375); + let part_for_11 = Perbill::from_rational::(1000, 1125); + let part_for_21 = Perbill::from_rational::(1000, 1375); + let part_for_101_from_11 = Perbill::from_rational::(125, 1125); + let part_for_101_from_21 = Perbill::from_rational::(375, 1375); start_session(2); start_session(3); @@ -312,25 +340,22 @@ fn rewards_should_work() { mock::make_all_reward_payment(0); assert_eq_error_rate!( - Balances::total_balance(&10), - init_balance_10 + part_for_10 * total_payout_0 * 2 / 3, + Balances::total_balance(&11), + init_balance_11 + part_for_11 * total_payout_0 * 2 / 3, 2, ); - assert_eq_error_rate!(Balances::total_balance(&11), init_balance_11, 2); assert_eq_error_rate!( - Balances::total_balance(&20), - init_balance_20 + part_for_20 * total_payout_0 * 1 / 3, + Balances::total_balance(&21), + init_balance_21 + part_for_21 * total_payout_0 * 1 / 3, 2, ); - assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); assert_eq_error_rate!( - Balances::total_balance(&100), - init_balance_100 + - part_for_100_from_10 * total_payout_0 * 2 / 3 + - part_for_100_from_20 * total_payout_0 * 1 / 3, + Balances::total_balance(&101), + init_balance_101 + + part_for_101_from_11 * total_payout_0 * 2 / 3 + + part_for_101_from_21 * total_payout_0 * 1 / 3, 2 ); - assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); assert_eq_uvec!(Session::validators(), vec![11, 21]); Pallet::::reward_by_ids(vec![(11, 1)]); @@ -354,25 +379,22 @@ fn rewards_should_work() { mock::make_all_reward_payment(1); assert_eq_error_rate!( - Balances::total_balance(&10), - init_balance_10 + part_for_10 * (total_payout_0 * 2 / 3 + total_payout_1), + Balances::total_balance(&11), + init_balance_11 + part_for_11 * (total_payout_0 * 2 / 3 + total_payout_1), 2, ); - assert_eq_error_rate!(Balances::total_balance(&11), init_balance_11, 2); assert_eq_error_rate!( - Balances::total_balance(&20), - init_balance_20 + part_for_20 * total_payout_0 * 1 / 3, + Balances::total_balance(&21), + init_balance_21 + part_for_21 * total_payout_0 * 1 / 3, 2, ); - assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); assert_eq_error_rate!( - Balances::total_balance(&100), - init_balance_100 + - part_for_100_from_10 * (total_payout_0 * 2 / 3 + total_payout_1) + - part_for_100_from_20 * total_payout_0 * 1 / 3, + Balances::total_balance(&101), + init_balance_101 + + part_for_101_from_11 * (total_payout_0 * 2 / 3 + total_payout_1) + + part_for_101_from_21 * total_payout_0 * 1 / 3, 2 ); - assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); }); } @@ -380,7 +402,7 @@ fn rewards_should_work() { fn staking_should_work() { ExtBuilder::default().nominate(false).build_and_execute(|| { // remember + compare this along with the test. - assert_eq_uvec!(validator_controllers(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![21, 11]); // put some money in account that we'll use. for i in 1..5 { @@ -390,22 +412,22 @@ fn staking_should_work() { // --- Block 2: start_session(2); // add a new candidate for being a validator. account 3 controlled by 4. - assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 4, 1500, RewardDestination::Controller)); - assert_ok!(Staking::validate(RuntimeOrigin::signed(4), ValidatorPrefs::default())); + assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 1500, RewardDestination::Controller)); + assert_ok!(Staking::validate(RuntimeOrigin::signed(3), ValidatorPrefs::default())); assert_ok!(Session::set_keys( - RuntimeOrigin::signed(4), + RuntimeOrigin::signed(3), SessionKeys { other: 4.into() }, vec![] )); // No effects will be seen so far. - assert_eq_uvec!(validator_controllers(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![21, 11]); // --- Block 3: start_session(3); // No effects will be seen so far. Era has not been yet triggered. - assert_eq_uvec!(validator_controllers(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![21, 11]); // --- Block 4: the validators will now be queued. start_session(4); @@ -417,25 +439,25 @@ fn staking_should_work() { // --- Block 6: the validators will now be changed. start_session(6); - assert_eq_uvec!(validator_controllers(), vec![20, 4]); + assert_eq_uvec!(validator_controllers(), vec![21, 3]); // --- Block 6: Unstake 4 as a validator, freeing up the balance stashed in 3 // 4 will chill - Staking::chill(RuntimeOrigin::signed(4)).unwrap(); + Staking::chill(RuntimeOrigin::signed(3)).unwrap(); - // --- Block 7: nothing. 4 is still there. + // --- Block 7: nothing. 3 is still there. start_session(7); - assert_eq_uvec!(validator_controllers(), vec![20, 4]); + assert_eq_uvec!(validator_controllers(), vec![21, 3]); // --- Block 8: start_session(8); // --- Block 9: 4 will not be a validator. start_session(9); - assert_eq_uvec!(validator_controllers(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![21, 11]); // Note: the stashed value of 4 is still lock assert_eq!( - Staking::ledger(&4), + Staking::ledger(&3), Some(StakingLedger { stash: 3, total: 1500, @@ -459,20 +481,20 @@ fn blocking_and_kicking_works() { .build_and_execute(|| { // block validator 10/11 assert_ok!(Staking::validate( - RuntimeOrigin::signed(10), + RuntimeOrigin::signed(11), ValidatorPrefs { blocked: true, ..Default::default() } )); // attempt to nominate from 100/101... - assert_ok!(Staking::nominate(RuntimeOrigin::signed(100), vec![11])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(101), vec![11])); // should have worked since we're already nominated them assert_eq!(Nominators::::get(&101).unwrap().targets, vec![11]); // kick the nominator - assert_ok!(Staking::kick(RuntimeOrigin::signed(10), vec![101])); + assert_ok!(Staking::kick(RuntimeOrigin::signed(11), vec![101])); // should have been kicked now assert!(Nominators::::get(&101).unwrap().targets.is_empty()); // attempt to nominate from 100/101... assert_noop!( - Staking::nominate(RuntimeOrigin::signed(100), vec![11]), + Staking::nominate(RuntimeOrigin::signed(101), vec![11]), Error::::BadTarget ); }); @@ -487,12 +509,12 @@ fn less_than_needed_candidates_works() { .build_and_execute(|| { assert_eq!(Staking::validator_count(), 4); assert_eq!(Staking::minimum_validator_count(), 1); - assert_eq_uvec!(validator_controllers(), vec![30, 20, 10]); + assert_eq_uvec!(validator_controllers(), vec![31, 21, 11]); mock::start_active_era(1); // Previous set is selected. NO election algorithm is even executed. - assert_eq_uvec!(validator_controllers(), vec![30, 20, 10]); + assert_eq_uvec!(validator_controllers(), vec![31, 21, 11]); // But the exposure is updated in a simple way. No external votes exists. // This is purely self-vote. @@ -510,21 +532,21 @@ fn no_candidate_emergency_condition() { .nominate(false) .build_and_execute(|| { // initial validators - assert_eq_uvec!(validator_controllers(), vec![10, 20, 30, 40]); + assert_eq_uvec!(validator_controllers(), vec![11, 21, 31, 41]); let prefs = ValidatorPrefs { commission: Perbill::one(), ..Default::default() }; - ::Validators::insert(11, prefs.clone()); + Validators::::insert(11, prefs.clone()); // set the minimum validator count. - ::MinimumValidatorCount::put(10); + MinimumValidatorCount::::put(11); // try to chill - let res = Staking::chill(RuntimeOrigin::signed(10)); + let res = Staking::chill(RuntimeOrigin::signed(11)); assert_ok!(res); let current_era = CurrentEra::::get(); // try trigger new era - mock::run_to_block(20); + mock::run_to_block(21); assert_eq!(*staking_events().last().unwrap(), Event::StakingElectionFailed); // No new era is created assert_eq!(current_era, CurrentEra::::get()); @@ -534,9 +556,9 @@ fn no_candidate_emergency_condition() { // Previous ones are elected. chill is not effective in active era (as era hasn't // changed) - assert_eq_uvec!(validator_controllers(), vec![10, 20, 30, 40]); + assert_eq_uvec!(validator_controllers(), vec![11, 21, 31, 41]); // The chill is still pending. - assert!(!::Validators::contains_key(11)); + assert!(!Validators::::contains_key(11)); // No new era is created. assert_eq!(current_era, CurrentEra::::get()); }); @@ -551,52 +573,50 @@ fn nominating_and_rewards_should_work() { .set_status(31, StakerStatus::Idle) .build_and_execute(|| { // initial validators. - assert_eq_uvec!(validator_controllers(), vec![40, 20]); + assert_eq_uvec!(validator_controllers(), vec![41, 21]); // re-validate with 11 and 31. - assert_ok!(Staking::validate(RuntimeOrigin::signed(10), Default::default())); - assert_ok!(Staking::validate(RuntimeOrigin::signed(30), Default::default())); + assert_ok!(Staking::validate(RuntimeOrigin::signed(11), Default::default())); + assert_ok!(Staking::validate(RuntimeOrigin::signed(31), Default::default())); // Set payee to controller. assert_ok!(Staking::set_payee( - RuntimeOrigin::signed(10), + RuntimeOrigin::signed(11), RewardDestination::Controller )); assert_ok!(Staking::set_payee( - RuntimeOrigin::signed(20), + RuntimeOrigin::signed(21), RewardDestination::Controller )); assert_ok!(Staking::set_payee( - RuntimeOrigin::signed(30), + RuntimeOrigin::signed(31), RewardDestination::Controller )); assert_ok!(Staking::set_payee( - RuntimeOrigin::signed(40), + RuntimeOrigin::signed(41), RewardDestination::Controller )); // give the man some money let initial_balance = 1000; - for i in [1, 2, 3, 4, 5, 10, 11, 20, 21].iter() { + for i in [1, 3, 5, 11, 21].iter() { let _ = Balances::make_free_balance_be(i, initial_balance); } // bond two account pairs and state interest in nomination. assert_ok!(Staking::bond( RuntimeOrigin::signed(1), - 2, 1000, RewardDestination::Controller )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(2), vec![11, 21, 31])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(1), vec![11, 21, 31])); assert_ok!(Staking::bond( RuntimeOrigin::signed(3), - 4, 1000, RewardDestination::Controller )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![11, 21, 41])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(3), vec![11, 21, 41])); // the total reward for era 0 let total_payout_0 = current_total_payout_for_duration(reward_time_per_era()); @@ -606,15 +626,15 @@ fn nominating_and_rewards_should_work() { mock::start_active_era(1); // 10 and 20 have more votes, they will be chosen. - assert_eq_uvec!(validator_controllers(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![21, 11]); // old validators must have already received some rewards. - let initial_balance_40 = Balances::total_balance(&40); - let mut initial_balance_20 = Balances::total_balance(&20); + let initial_balance_41 = Balances::total_balance(&41); + let mut initial_balance_21 = Balances::total_balance(&21); mock::make_all_reward_payment(0); - assert_eq!(Balances::total_balance(&40), initial_balance_40 + total_payout_0 / 2); - assert_eq!(Balances::total_balance(&20), initial_balance_20 + total_payout_0 / 2); - initial_balance_20 = Balances::total_balance(&20); + assert_eq!(Balances::total_balance(&41), initial_balance_41 + total_payout_0 / 2); + assert_eq!(Balances::total_balance(&21), initial_balance_21 + total_payout_0 / 2); + initial_balance_21 = Balances::total_balance(&21); assert_eq!(ErasStakers::::iter_prefix_values(active_era()).count(), 2); assert_eq!( @@ -651,34 +671,34 @@ fn nominating_and_rewards_should_work() { // nominators will also be paid. See below mock::make_all_reward_payment(1); - let payout_for_10 = total_payout_1 / 3; - let payout_for_20 = 2 * total_payout_1 / 3; - // Nominator 2: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 20]'s reward. ==> + let payout_for_11 = total_payout_1 / 3; + let payout_for_21 = 2 * total_payout_1 / 3; + // Nominator 2: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 21]'s reward. ==> // 2/9 + 3/11 assert_eq_error_rate!( - Balances::total_balance(&2), - initial_balance + (2 * payout_for_10 / 9 + 3 * payout_for_20 / 11), + Balances::total_balance(&1), + initial_balance + (2 * payout_for_11 / 9 + 3 * payout_for_21 / 11), 2, ); - // Nominator 4: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 20]'s reward. ==> + // Nominator 3: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 21]'s reward. ==> // 2/9 + 3/11 assert_eq_error_rate!( - Balances::total_balance(&4), - initial_balance + (2 * payout_for_10 / 9 + 3 * payout_for_20 / 11), + Balances::total_balance(&3), + initial_balance + (2 * payout_for_11 / 9 + 3 * payout_for_21 / 11), 2, ); - // Validator 10: got 800 / 1800 external stake => 8/18 =? 4/9 => Validator's share = 5/9 + // Validator 11: got 800 / 1800 external stake => 8/18 =? 4/9 => Validator's share = 5/9 assert_eq_error_rate!( - Balances::total_balance(&10), - initial_balance + 5 * payout_for_10 / 9, + Balances::total_balance(&11), + initial_balance + 5 * payout_for_11 / 9, 2, ); - // Validator 20: got 1200 / 2200 external stake => 12/22 =? 6/11 => Validator's share = + // Validator 21: got 1200 / 2200 external stake => 12/22 =? 6/11 => Validator's share = // 5/11 assert_eq_error_rate!( - Balances::total_balance(&20), - initial_balance_20 + 5 * payout_for_20 / 11, + Balances::total_balance(&21), + initial_balance_21 + 5 * payout_for_21 / 11, 2, ); }); @@ -694,9 +714,9 @@ fn nominators_also_get_slashed_pro_rata() { assert_eq!(initial_exposure.others.first().unwrap().who, 101); // staked values; - let nominator_stake = Staking::ledger(100).unwrap().active; + let nominator_stake = Staking::ledger(101).unwrap().active; let nominator_balance = balances(&101).0; - let validator_stake = Staking::ledger(10).unwrap().active; + let validator_stake = Staking::ledger(11).unwrap().active; let validator_balance = balances(&11).0; let exposed_stake = initial_exposure.total; let exposed_validator = initial_exposure.own; @@ -709,8 +729,8 @@ fn nominators_also_get_slashed_pro_rata() { ); // both stakes must have been decreased. - assert!(Staking::ledger(100).unwrap().active < nominator_stake); - assert!(Staking::ledger(10).unwrap().active < validator_stake); + assert!(Staking::ledger(101).unwrap().active < nominator_stake); + assert!(Staking::ledger(11).unwrap().active < validator_stake); let slash_amount = slash_percent * exposed_stake; let validator_share = @@ -723,8 +743,8 @@ fn nominators_also_get_slashed_pro_rata() { assert!(nominator_share > 0); // both stakes must have been decreased pro-rata. - assert_eq!(Staking::ledger(100).unwrap().active, nominator_stake - nominator_share); - assert_eq!(Staking::ledger(10).unwrap().active, validator_stake - validator_share); + assert_eq!(Staking::ledger(101).unwrap().active, nominator_stake - nominator_share); + assert_eq!(Staking::ledger(11).unwrap().active, validator_stake - validator_share); assert_eq!( balances(&101).0, // free balance nominator_balance - nominator_share, @@ -734,7 +754,7 @@ fn nominators_also_get_slashed_pro_rata() { validator_balance - validator_share, ); // Because slashing happened. - assert!(is_disabled(10)); + assert!(is_disabled(11)); }); } @@ -746,56 +766,56 @@ fn double_staking_should_fail() { // * an account already bonded as controller can nominate. ExtBuilder::default().build_and_execute(|| { let arbitrary_value = 5; - // 2 = controller, 1 stashed => ok - assert_ok!(Staking::bond( - RuntimeOrigin::signed(1), - 2, + let (stash, controller) = testing_utils::create_unique_stash_controller::( + 0, arbitrary_value, - RewardDestination::default() - )); - // 4 = not used so far, 1 stashed => not allowed. + RewardDestination::default(), + false, + ) + .unwrap(); + + // 4 = not used so far, stash => not allowed. assert_noop!( Staking::bond( - RuntimeOrigin::signed(1), - 4, - arbitrary_value, + RuntimeOrigin::signed(stash), + arbitrary_value.into(), RewardDestination::default() ), Error::::AlreadyBonded, ); - // 1 = stashed => attempting to nominate should fail. + // stash => attempting to nominate should fail. assert_noop!( - Staking::nominate(RuntimeOrigin::signed(1), vec![1]), + Staking::nominate(RuntimeOrigin::signed(stash), vec![1]), Error::::NotController ); - // 2 = controller => nominating should work. - assert_ok!(Staking::nominate(RuntimeOrigin::signed(2), vec![1])); + // controller => nominating should work. + assert_ok!(Staking::nominate(RuntimeOrigin::signed(controller), vec![1])); }); } #[test] -fn double_controlling_should_fail() { +fn double_controlling_attempt_should_fail() { // should test (in the same order): // * an account already bonded as controller CANNOT be reused as the controller of another // account. ExtBuilder::default().build_and_execute(|| { let arbitrary_value = 5; - // 2 = controller, 1 stashed => ok - assert_ok!(Staking::bond( - RuntimeOrigin::signed(1), - 2, + let (stash, _) = testing_utils::create_unique_stash_controller::( + 0, arbitrary_value, RewardDestination::default(), - )); - // 2 = controller, 3 stashed (Note that 2 is reused.) => no-op + false, + ) + .unwrap(); + + // Note that controller (same as stash) is reused => no-op. assert_noop!( Staking::bond( - RuntimeOrigin::signed(3), - 2, - arbitrary_value, + RuntimeOrigin::signed(stash), + arbitrary_value.into(), RewardDestination::default() ), - Error::::AlreadyPaired, + Error::::AlreadyBonded, ); }); } @@ -953,21 +973,21 @@ fn cannot_transfer_staked_balance() { // Tests that a stash account cannot transfer funds ExtBuilder::default().nominate(false).build_and_execute(|| { // Confirm account 11 is stashed - assert_eq!(Staking::bonded(&11), Some(10)); + assert_eq!(Staking::bonded(&11), Some(11)); // Confirm account 11 has some free balance assert_eq!(Balances::free_balance(11), 1000); - // Confirm account 11 (via controller 10) is totally staked + // Confirm account 11 (via controller) is totally staked assert_eq!(Staking::eras_stakers(active_era(), 11).total, 1000); // Confirm account 11 cannot transfer as a result assert_noop!( - Balances::transfer(RuntimeOrigin::signed(11), 20, 1), - BalancesError::::LiquidityRestrictions + Balances::transfer_allow_death(RuntimeOrigin::signed(11), 21, 1), + TokenError::Frozen, ); // Give account 11 extra free balance let _ = Balances::make_free_balance_be(&11, 10000); // Confirm that account 11 can now transfer some balance - assert_ok!(Balances::transfer(RuntimeOrigin::signed(11), 20, 1)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(11), 21, 1)); }); } @@ -978,17 +998,17 @@ fn cannot_transfer_staked_balance_2() { // 21 has 2000 free balance but 1000 at stake ExtBuilder::default().nominate(false).build_and_execute(|| { // Confirm account 21 is stashed - assert_eq!(Staking::bonded(&21), Some(20)); + assert_eq!(Staking::bonded(&21), Some(21)); // Confirm account 21 has some free balance assert_eq!(Balances::free_balance(21), 2000); - // Confirm account 21 (via controller 20) is totally staked + // Confirm account 21 (via controller) is totally staked assert_eq!(Staking::eras_stakers(active_era(), 21).total, 1000); // Confirm account 21 can transfer at most 1000 assert_noop!( - Balances::transfer(RuntimeOrigin::signed(21), 20, 1001), - BalancesError::::LiquidityRestrictions + Balances::transfer_allow_death(RuntimeOrigin::signed(21), 21, 1001), + TokenError::Frozen, ); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(21), 20, 1000)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(21), 21, 1000)); }); } @@ -997,7 +1017,7 @@ fn cannot_reserve_staked_balance() { // Checks that a bonded account cannot reserve balance from free balance ExtBuilder::default().build_and_execute(|| { // Confirm account 11 is stashed - assert_eq!(Staking::bonded(&11), Some(10)); + assert_eq!(Staking::bonded(&11), Some(11)); // Confirm account 11 has some free balance assert_eq!(Balances::free_balance(11), 1000); // Confirm account 11 (via controller 10) is totally staked @@ -1024,7 +1044,7 @@ fn reward_destination_works() { assert_eq!(Balances::free_balance(11), 1000); // Check how much is at stake assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1047,7 +1067,7 @@ fn reward_destination_works() { assert_eq!(Balances::free_balance(11), 1000 + total_payout_0); // Check that amount at stake increased accordingly assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000 + total_payout_0, @@ -1071,11 +1091,9 @@ fn reward_destination_works() { assert_eq!(Staking::payee(&11), RewardDestination::Stash); // Check that reward went to the stash account assert_eq!(Balances::free_balance(11), 1000 + total_payout_0 + total_payout_1); - // Record this value - let recorded_stash_balance = 1000 + total_payout_0 + total_payout_1; // Check that amount at stake is NOT increased assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000 + total_payout_0, @@ -1089,7 +1107,7 @@ fn reward_destination_works() { >::insert(&11, RewardDestination::Controller); // Check controller balance - assert_eq!(Balances::free_balance(10), 1); + assert_eq!(Balances::free_balance(11), 23150); // Compute total payout now for whole duration as other parameter won't change let total_payout_2 = current_total_payout_for_duration(reward_time_per_era()); @@ -1101,10 +1119,10 @@ fn reward_destination_works() { // Check that RewardDestination is Controller assert_eq!(Staking::payee(&11), RewardDestination::Controller); // Check that reward went to the controller account - assert_eq!(Balances::free_balance(10), 1 + total_payout_2); + assert_eq!(Balances::free_balance(11), 23150 + total_payout_2); // Check that amount at stake is NOT increased assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000 + total_payout_0, @@ -1113,8 +1131,6 @@ fn reward_destination_works() { claimed_rewards: bounded_vec![0, 1, 2], }) ); - // Check that amount in staked account is NOT increased. - assert_eq!(Balances::free_balance(11), recorded_stash_balance); }); } @@ -1134,8 +1150,8 @@ fn validator_payment_prefs_work() { mock::start_active_era(1); mock::make_all_reward_payment(0); - let balance_era_1_10 = Balances::total_balance(&10); - let balance_era_1_100 = Balances::total_balance(&100); + let balance_era_1_11 = Balances::total_balance(&11); + let balance_era_1_101 = Balances::total_balance(&101); // Compute total payout now for whole duration as other parameter won't change let total_payout_1 = current_total_payout_for_duration(reward_time_per_era()); @@ -1149,8 +1165,8 @@ fn validator_payment_prefs_work() { let shared_cut = total_payout_1 - taken_cut; let reward_of_10 = shared_cut * exposure_1.own / exposure_1.total + taken_cut; let reward_of_100 = shared_cut * exposure_1.others[0].value / exposure_1.total; - assert_eq_error_rate!(Balances::total_balance(&10), balance_era_1_10 + reward_of_10, 2); - assert_eq_error_rate!(Balances::total_balance(&100), balance_era_1_100 + reward_of_100, 2); + assert_eq_error_rate!(Balances::total_balance(&11), balance_era_1_11 + reward_of_10, 2); + assert_eq_error_rate!(Balances::total_balance(&101), balance_era_1_101 + reward_of_100, 2); }); } @@ -1163,10 +1179,10 @@ fn bond_extra_works() { // Check that account 10 is a validator assert!(>::contains_key(11)); // Check that account 10 is bonded to account 11 - assert_eq!(Staking::bonded(&11), Some(10)); + assert_eq!(Staking::bonded(&11), Some(11)); // Check how much is at stake assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1183,7 +1199,7 @@ fn bond_extra_works() { assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), 100)); // There should be 100 more `total` and `active` in the ledger assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000 + 100, @@ -1197,7 +1213,7 @@ fn bond_extra_works() { assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), Balance::max_value())); // The full amount of the funds should now be in the total and active assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000000, @@ -1219,7 +1235,7 @@ fn bond_extra_and_withdraw_unbonded_works() { // * Once the unbonding period is done, it can actually take the funds out of the stash. ExtBuilder::default().nominate(false).build_and_execute(|| { // Set payee to controller. avoids confusion - assert_ok!(Staking::set_payee(RuntimeOrigin::signed(10), RewardDestination::Controller)); + assert_ok!(Staking::set_payee(RuntimeOrigin::signed(11), RewardDestination::Controller)); // Give account 11 some large free balance greater than total let _ = Balances::make_free_balance_be(&11, 1000000); @@ -1228,14 +1244,14 @@ fn bond_extra_and_withdraw_unbonded_works() { assert_eq!(active_era(), 0); // check the balance of a validator accounts. - assert_eq!(Balances::total_balance(&10), 1); + assert_eq!(Balances::total_balance(&11), 1000000); // confirm that 10 is a normal validator and gets paid at the end of the era. mock::start_active_era(1); - // Initial state of 10 + // Initial state of 11 assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1253,7 +1269,7 @@ fn bond_extra_and_withdraw_unbonded_works() { Staking::bond_extra(RuntimeOrigin::signed(11), 100).unwrap(); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000 + 100, @@ -1274,7 +1290,7 @@ fn bond_extra_and_withdraw_unbonded_works() { // ledger should be the same. assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000 + 100, @@ -1290,9 +1306,9 @@ fn bond_extra_and_withdraw_unbonded_works() { ); // Unbond almost all of the funds in stash. - Staking::unbond(RuntimeOrigin::signed(10), 1000).unwrap(); + Staking::unbond(RuntimeOrigin::signed(11), 1000).unwrap(); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000 + 100, @@ -1303,9 +1319,9 @@ fn bond_extra_and_withdraw_unbonded_works() { ); // Attempting to free the balances now will fail. 2 eras need to pass. - assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(10), 0)); + assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0)); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000 + 100, @@ -1319,9 +1335,9 @@ fn bond_extra_and_withdraw_unbonded_works() { mock::start_active_era(3); // nothing yet - assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(10), 0)); + assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0)); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000 + 100, @@ -1334,10 +1350,10 @@ fn bond_extra_and_withdraw_unbonded_works() { // trigger next era. mock::start_active_era(5); - assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(10), 0)); + assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0)); // Now the value is free and the staking ledger is updated. assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 100, @@ -1361,7 +1377,7 @@ fn many_unbond_calls_should_work() { // There is only 1 chunk per era, so we need to be in a new era to create a chunk. current_era = i as u32; mock::start_active_era(current_era); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1)); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1)); } current_era += 1; @@ -1369,9 +1385,9 @@ fn many_unbond_calls_should_work() { // This chunk is locked at `current_era` through `current_era + 2` (because // `BondingDuration` == 3). - assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1)); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1)); assert_eq!( - Staking::ledger(&10).map(|l| l.unlocking.len()).unwrap(), + Staking::ledger(&11).map(|l| l.unlocking.len()).unwrap(), <::MaxUnlockingChunks as Get>::get() as usize ); @@ -1381,12 +1397,12 @@ fn many_unbond_calls_should_work() { // There is only 1 chunk per era, so we need to be in a new era to create a chunk. current_era = i as u32; mock::start_active_era(current_era); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1)); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1)); } // only slots within last `BondingDuration` are filled. assert_eq!( - Staking::ledger(&10).map(|l| l.unlocking.len()).unwrap(), + Staking::ledger(&11).map(|l| l.unlocking.len()).unwrap(), <::BondingDuration>::get() as usize ); }) @@ -1404,7 +1420,7 @@ fn auto_withdraw_may_not_unlock_all_chunks() { // fills the chunking slots for account mock::start_active_era(current_era); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1)); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1)); current_era += 1; mock::start_active_era(current_era); @@ -1412,12 +1428,12 @@ fn auto_withdraw_may_not_unlock_all_chunks() { // unbonding will fail because i) there are no remaining chunks and ii) no filled chunks // can be released because current chunk hasn't stay in the queue for at least // `BondingDuration` - assert_noop!(Staking::unbond(RuntimeOrigin::signed(10), 1), Error::::NoMoreChunks); + assert_noop!(Staking::unbond(RuntimeOrigin::signed(11), 1), Error::::NoMoreChunks); // fast-forward a few eras for unbond to be successful with implicit withdraw current_era += 10; mock::start_active_era(current_era); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1)); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1)); }) } @@ -1430,7 +1446,7 @@ fn rebond_works() { // * it can re-bond a portion of the funds scheduled to unlock. ExtBuilder::default().nominate(false).build_and_execute(|| { // Set payee to controller. avoids confusion - assert_ok!(Staking::set_payee(RuntimeOrigin::signed(10), RewardDestination::Controller)); + assert_ok!(Staking::set_payee(RuntimeOrigin::signed(11), RewardDestination::Controller)); // Give account 11 some large free balance greater than total let _ = Balances::make_free_balance_be(&11, 1000000); @@ -1438,9 +1454,9 @@ fn rebond_works() { // confirm that 10 is a normal validator and gets paid at the end of the era. mock::start_active_era(1); - // Initial state of 10 + // Initial state of 11 assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1454,12 +1470,12 @@ fn rebond_works() { assert_eq!(active_era(), 2); // Try to rebond some funds. We get an error since no fund is unbonded. - assert_noop!(Staking::rebond(RuntimeOrigin::signed(10), 500), Error::::NoUnlockChunk); + assert_noop!(Staking::rebond(RuntimeOrigin::signed(11), 500), Error::::NoUnlockChunk); // Unbond almost all of the funds in stash. - Staking::unbond(RuntimeOrigin::signed(10), 900).unwrap(); + Staking::unbond(RuntimeOrigin::signed(11), 900).unwrap(); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1470,9 +1486,9 @@ fn rebond_works() { ); // Re-bond all the funds unbonded. - Staking::rebond(RuntimeOrigin::signed(10), 900).unwrap(); + Staking::rebond(RuntimeOrigin::signed(11), 900).unwrap(); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1483,9 +1499,9 @@ fn rebond_works() { ); // Unbond almost all of the funds in stash. - Staking::unbond(RuntimeOrigin::signed(10), 900).unwrap(); + Staking::unbond(RuntimeOrigin::signed(11), 900).unwrap(); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1496,9 +1512,9 @@ fn rebond_works() { ); // Re-bond part of the funds unbonded. - Staking::rebond(RuntimeOrigin::signed(10), 500).unwrap(); + Staking::rebond(RuntimeOrigin::signed(11), 500).unwrap(); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1509,9 +1525,9 @@ fn rebond_works() { ); // Re-bond the remainder of the funds unbonded. - Staking::rebond(RuntimeOrigin::signed(10), 500).unwrap(); + Staking::rebond(RuntimeOrigin::signed(11), 500).unwrap(); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1522,11 +1538,11 @@ fn rebond_works() { ); // Unbond parts of the funds in stash. - Staking::unbond(RuntimeOrigin::signed(10), 300).unwrap(); - Staking::unbond(RuntimeOrigin::signed(10), 300).unwrap(); - Staking::unbond(RuntimeOrigin::signed(10), 300).unwrap(); + Staking::unbond(RuntimeOrigin::signed(11), 300).unwrap(); + Staking::unbond(RuntimeOrigin::signed(11), 300).unwrap(); + Staking::unbond(RuntimeOrigin::signed(11), 300).unwrap(); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1537,9 +1553,9 @@ fn rebond_works() { ); // Re-bond part of the funds unbonded. - Staking::rebond(RuntimeOrigin::signed(10), 500).unwrap(); + Staking::rebond(RuntimeOrigin::signed(11), 500).unwrap(); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1556,7 +1572,7 @@ fn rebond_is_fifo() { // Rebond should proceed by reversing the most recent bond operations. ExtBuilder::default().nominate(false).build_and_execute(|| { // Set payee to controller. avoids confusion - assert_ok!(Staking::set_payee(RuntimeOrigin::signed(10), RewardDestination::Controller)); + assert_ok!(Staking::set_payee(RuntimeOrigin::signed(11), RewardDestination::Controller)); // Give account 11 some large free balance greater than total let _ = Balances::make_free_balance_be(&11, 1000000); @@ -1566,7 +1582,7 @@ fn rebond_is_fifo() { // Initial state of 10 assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1579,9 +1595,9 @@ fn rebond_is_fifo() { mock::start_active_era(2); // Unbond some of the funds in stash. - Staking::unbond(RuntimeOrigin::signed(10), 400).unwrap(); + Staking::unbond(RuntimeOrigin::signed(11), 400).unwrap(); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1594,9 +1610,9 @@ fn rebond_is_fifo() { mock::start_active_era(3); // Unbond more of the funds in stash. - Staking::unbond(RuntimeOrigin::signed(10), 300).unwrap(); + Staking::unbond(RuntimeOrigin::signed(11), 300).unwrap(); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1612,9 +1628,9 @@ fn rebond_is_fifo() { mock::start_active_era(4); // Unbond yet more of the funds in stash. - Staking::unbond(RuntimeOrigin::signed(10), 200).unwrap(); + Staking::unbond(RuntimeOrigin::signed(11), 200).unwrap(); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1629,9 +1645,9 @@ fn rebond_is_fifo() { ); // Re-bond half of the unbonding funds. - Staking::rebond(RuntimeOrigin::signed(10), 400).unwrap(); + Staking::rebond(RuntimeOrigin::signed(11), 400).unwrap(); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1652,7 +1668,7 @@ fn rebond_emits_right_value_in_event() { // and the rebond event emits the actual value rebonded. ExtBuilder::default().nominate(false).build_and_execute(|| { // Set payee to controller. avoids confusion - assert_ok!(Staking::set_payee(RuntimeOrigin::signed(10), RewardDestination::Controller)); + assert_ok!(Staking::set_payee(RuntimeOrigin::signed(11), RewardDestination::Controller)); // Give account 11 some large free balance greater than total let _ = Balances::make_free_balance_be(&11, 1000000); @@ -1661,9 +1677,9 @@ fn rebond_emits_right_value_in_event() { mock::start_active_era(1); // Unbond almost all of the funds in stash. - Staking::unbond(RuntimeOrigin::signed(10), 900).unwrap(); + Staking::unbond(RuntimeOrigin::signed(11), 900).unwrap(); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1674,9 +1690,9 @@ fn rebond_emits_right_value_in_event() { ); // Re-bond less than the total - Staking::rebond(RuntimeOrigin::signed(10), 100).unwrap(); + Staking::rebond(RuntimeOrigin::signed(11), 100).unwrap(); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1689,9 +1705,9 @@ fn rebond_emits_right_value_in_event() { assert_eq!(*staking_events().last().unwrap(), Event::Bonded { stash: 11, amount: 100 }); // Re-bond way more than available - Staking::rebond(RuntimeOrigin::signed(10), 100_000).unwrap(); + Staking::rebond(RuntimeOrigin::signed(11), 100_000).unwrap(); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -1747,17 +1763,20 @@ fn reward_to_stake_works() { mock::make_all_reward_payment(0); assert_eq!(Staking::eras_stakers(active_era(), 11).total, 1000); - assert_eq!(Staking::eras_stakers(active_era(), 21).total, 69); + assert_eq!(Staking::eras_stakers(active_era(), 21).total, 2000); let _11_balance = Balances::free_balance(&11); + let _21_balance = Balances::free_balance(&21); + assert_eq!(_11_balance, 1000 + total_payout_0 / 2); + assert_eq!(_21_balance, 2000 + total_payout_0 / 2); // Trigger another new era as the info are frozen before the era start. mock::start_active_era(2); // -- new infos - assert_eq!(Staking::eras_stakers(active_era(), 11).total, 1000 + total_payout_0 / 2); - assert_eq!(Staking::eras_stakers(active_era(), 21).total, 69 + total_payout_0 / 2); + assert_eq!(Staking::eras_stakers(active_era(), 11).total, _11_balance); + assert_eq!(Staking::eras_stakers(active_era(), 21).total, _21_balance); }); } @@ -1768,11 +1787,10 @@ fn reap_stash_works() { .balance_factor(10) .build_and_execute(|| { // given - assert_eq!(Balances::free_balance(10), 10); assert_eq!(Balances::free_balance(11), 10 * 1000); - assert_eq!(Staking::bonded(&11), Some(10)); + assert_eq!(Staking::bonded(&11), Some(11)); - assert!(>::contains_key(&10)); + assert!(>::contains_key(&11)); assert!(>::contains_key(&11)); assert!(>::contains_key(&11)); assert!(>::contains_key(&11)); @@ -1782,16 +1800,11 @@ fn reap_stash_works() { Staking::reap_stash(RuntimeOrigin::signed(20), 11, 0), Error::::FundedTarget ); - // controller or any other account is not reapable - assert_noop!( - Staking::reap_stash(RuntimeOrigin::signed(20), 10, 0), - Error::::NotStash - ); // no easy way to cause an account to go below ED, we tweak their staking ledger // instead. Ledger::::insert( - 10, + 11, StakingLedger { stash: 11, total: 5, @@ -1805,7 +1818,7 @@ fn reap_stash_works() { assert_ok!(Staking::reap_stash(RuntimeOrigin::signed(20), 11, 0)); // then - assert!(!>::contains_key(&10)); + assert!(!>::contains_key(&11)); assert!(!>::contains_key(&11)); assert!(!>::contains_key(&11)); assert!(!>::contains_key(&11)); @@ -1818,14 +1831,14 @@ fn switching_roles() { // minimal overhead. ExtBuilder::default().nominate(false).build_and_execute(|| { // Reset reward destination - for i in &[10, 20] { + for i in &[11, 21] { assert_ok!(Staking::set_payee( RuntimeOrigin::signed(*i), RewardDestination::Controller )); } - assert_eq_uvec!(validator_controllers(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![21, 11]); // put some money in account that we'll use. for i in 1..7 { @@ -1833,43 +1846,43 @@ fn switching_roles() { } // add 2 nominators - assert_ok!(Staking::bond(RuntimeOrigin::signed(1), 2, 2000, RewardDestination::Controller)); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(2), vec![11, 5])); + assert_ok!(Staking::bond(RuntimeOrigin::signed(1), 2000, RewardDestination::Controller)); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(1), vec![11, 5])); - assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 4, 500, RewardDestination::Controller)); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![21, 1])); + assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 500, RewardDestination::Controller)); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(3), vec![21, 1])); // add a new validator candidate - assert_ok!(Staking::bond(RuntimeOrigin::signed(5), 6, 1000, RewardDestination::Controller)); - assert_ok!(Staking::validate(RuntimeOrigin::signed(6), ValidatorPrefs::default())); + assert_ok!(Staking::bond(RuntimeOrigin::signed(5), 1000, RewardDestination::Controller)); + assert_ok!(Staking::validate(RuntimeOrigin::signed(5), ValidatorPrefs::default())); assert_ok!(Session::set_keys( - RuntimeOrigin::signed(6), + RuntimeOrigin::signed(5), SessionKeys { other: 6.into() }, vec![] )); mock::start_active_era(1); - // with current nominators 10 and 5 have the most stake - assert_eq_uvec!(validator_controllers(), vec![6, 10]); + // with current nominators 11 and 5 have the most stake + assert_eq_uvec!(validator_controllers(), vec![5, 11]); // 2 decides to be a validator. Consequences: - assert_ok!(Staking::validate(RuntimeOrigin::signed(2), ValidatorPrefs::default())); + assert_ok!(Staking::validate(RuntimeOrigin::signed(1), ValidatorPrefs::default())); assert_ok!(Session::set_keys( - RuntimeOrigin::signed(2), + RuntimeOrigin::signed(1), SessionKeys { other: 2.into() }, vec![] )); // new stakes: - // 10: 1000 self vote - // 20: 1000 self vote + 250 vote - // 6 : 1000 self vote - // 2 : 2000 self vote + 250 vote. - // Winners: 20 and 2 + // 11: 1000 self vote + // 21: 1000 self vote + 250 vote + // 5 : 1000 self vote + // 1 : 2000 self vote + 250 vote. + // Winners: 21 and 1 mock::start_active_era(2); - assert_eq_uvec!(validator_controllers(), vec![2, 20]); + assert_eq_uvec!(validator_controllers(), vec![1, 21]); }); } @@ -1878,7 +1891,7 @@ fn wrong_vote_is_moot() { ExtBuilder::default() .add_staker( 61, - 60, + 61, 500, StakerStatus::Nominator(vec![ 11, 21, // good votes @@ -1890,7 +1903,7 @@ fn wrong_vote_is_moot() { mock::start_active_era(1); // new validators - assert_eq_uvec!(validator_controllers(), vec![20, 10]); + assert_eq_uvec!(validator_controllers(), vec![21, 11]); // our new voter is taken into account assert!(Staking::eras_stakers(active_era(), 11).others.iter().any(|i| i.who == 61)); @@ -1901,7 +1914,7 @@ fn wrong_vote_is_moot() { #[test] fn bond_with_no_staked_value() { // Behavior when someone bonds with no staked value. - // Particularly when she votes and the candidate is elected. + // Particularly when they votes and the candidate is elected. ExtBuilder::default() .validator_count(3) .existential_deposit(5) @@ -1911,22 +1924,17 @@ fn bond_with_no_staked_value() { .build_and_execute(|| { // Can't bond with 1 assert_noop!( - Staking::bond(RuntimeOrigin::signed(1), 2, 1, RewardDestination::Controller), + Staking::bond(RuntimeOrigin::signed(1), 1, RewardDestination::Controller), Error::::InsufficientBond, ); // bonded with absolute minimum value possible. - assert_ok!(Staking::bond( - RuntimeOrigin::signed(1), - 2, - 5, - RewardDestination::Controller - )); + assert_ok!(Staking::bond(RuntimeOrigin::signed(1), 5, RewardDestination::Controller)); assert_eq!(Balances::locks(&1)[0].amount, 5); // unbonding even 1 will cause all to be unbonded. - assert_ok!(Staking::unbond(RuntimeOrigin::signed(2), 1)); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(1), 1)); assert_eq!( - Staking::ledger(2), + Staking::ledger(1), Some(StakingLedger { stash: 1, active: 0, @@ -1940,15 +1948,15 @@ fn bond_with_no_staked_value() { mock::start_active_era(2); // not yet removed. - assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(2), 0)); - assert!(Staking::ledger(2).is_some()); + assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(1), 0)); + assert!(Staking::ledger(1).is_some()); assert_eq!(Balances::locks(&1)[0].amount, 5); mock::start_active_era(3); // poof. Account 1 is removed from the staking system. - assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(2), 0)); - assert!(Staking::ledger(2).is_none()); + assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(1), 0)); + assert!(Staking::ledger(1).is_none()); assert_eq!(Balances::locks(&1).len(), 0); }); } @@ -1961,25 +1969,20 @@ fn bond_with_little_staked_value_bounded() { .minimum_validator_count(1) .build_and_execute(|| { // setup - assert_ok!(Staking::chill(RuntimeOrigin::signed(30))); + assert_ok!(Staking::chill(RuntimeOrigin::signed(31))); assert_ok!(Staking::set_payee( - RuntimeOrigin::signed(10), + RuntimeOrigin::signed(11), RewardDestination::Controller )); - let init_balance_2 = Balances::free_balance(&2); - let init_balance_10 = Balances::free_balance(&10); + let init_balance_1 = Balances::free_balance(&1); + let init_balance_11 = Balances::free_balance(&11); // Stingy validator. - assert_ok!(Staking::bond( - RuntimeOrigin::signed(1), - 2, - 1, - RewardDestination::Controller - )); - assert_ok!(Staking::validate(RuntimeOrigin::signed(2), ValidatorPrefs::default())); + assert_ok!(Staking::bond(RuntimeOrigin::signed(1), 1, RewardDestination::Controller)); + assert_ok!(Staking::validate(RuntimeOrigin::signed(1), ValidatorPrefs::default())); assert_ok!(Session::set_keys( - RuntimeOrigin::signed(2), - SessionKeys { other: 2.into() }, + RuntimeOrigin::signed(1), + SessionKeys { other: 1.into() }, vec![] )); @@ -1992,17 +1995,17 @@ fn bond_with_little_staked_value_bounded() { mock::make_all_reward_payment(0); // 2 is elected. - assert_eq_uvec!(validator_controllers(), vec![20, 10, 2]); + assert_eq_uvec!(validator_controllers(), vec![21, 11, 1]); assert_eq!(Staking::eras_stakers(active_era(), 2).total, 0); // Old ones are rewarded. assert_eq_error_rate!( - Balances::free_balance(10), - init_balance_10 + total_payout_0 / 3, + Balances::free_balance(11), + init_balance_11 + total_payout_0 / 3, 1 ); // no rewards paid to 2. This was initial election. - assert_eq!(Balances::free_balance(2), init_balance_2); + assert_eq!(Balances::free_balance(1), init_balance_1); // reward era 2 let total_payout_1 = current_total_payout_for_duration(reward_time_per_era()); @@ -2010,18 +2013,18 @@ fn bond_with_little_staked_value_bounded() { mock::start_active_era(2); mock::make_all_reward_payment(1); - assert_eq_uvec!(validator_controllers(), vec![20, 10, 2]); + assert_eq_uvec!(validator_controllers(), vec![21, 11, 1]); assert_eq!(Staking::eras_stakers(active_era(), 2).total, 0); // 2 is now rewarded. assert_eq_error_rate!( - Balances::free_balance(2), - init_balance_2 + total_payout_1 / 3, + Balances::free_balance(1), + init_balance_1 + total_payout_1 / 3, 1 ); assert_eq_error_rate!( - Balances::free_balance(&10), - init_balance_10 + total_payout_0 / 3 + total_payout_1 / 3, + Balances::free_balance(&11), + init_balance_11 + total_payout_0 / 3 + total_payout_1 / 3, 2, ); }); @@ -2038,7 +2041,7 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider() { // ensure all have equal stake. assert_eq!( >::iter() - .map(|(v, _)| (v, Staking::ledger(v - 1).unwrap().total)) + .map(|(v, _)| (v, Staking::ledger(v).unwrap().total)) .collect::>(), vec![(31, 1000), (21, 1000), (11, 1000)], ); @@ -2053,19 +2056,17 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider() { assert_ok!(Staking::bond( RuntimeOrigin::signed(1), - 2, 1000, RewardDestination::Controller )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(2), vec![11, 11, 11, 21, 31])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(1), vec![11, 11, 11, 21, 31])); assert_ok!(Staking::bond( RuntimeOrigin::signed(3), - 4, 1000, RewardDestination::Controller )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![21, 31])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(3), vec![21, 31])); // winners should be 21 and 31. Otherwise this election is taking duplicates into // account. @@ -2092,7 +2093,7 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider_elected() { // ensure all have equal stake. assert_eq!( >::iter() - .map(|(v, _)| (v, Staking::ledger(v - 1).unwrap().total)) + .map(|(v, _)| (v, Staking::ledger(v).unwrap().total)) .collect::>(), vec![(31, 1000), (21, 1000), (11, 1000)], ); @@ -2108,19 +2109,17 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider_elected() { assert_ok!(Staking::bond( RuntimeOrigin::signed(1), - 2, 1000, RewardDestination::Controller )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(2), vec![11, 11, 11, 21])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(1), vec![11, 11, 11, 21])); assert_ok!(Staking::bond( RuntimeOrigin::signed(3), - 4, 1000, RewardDestination::Controller )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![21])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(3), vec![21])); // winners should be 21 and 11. let supports = ::ElectionProvider::elect().unwrap(); @@ -2155,15 +2154,15 @@ fn phragmen_should_not_overflow() { let _ = Staking::chill(RuntimeOrigin::signed(10)); let _ = Staking::chill(RuntimeOrigin::signed(20)); - bond_validator(3, 2, Votes::max_value() as Balance); - bond_validator(5, 4, Votes::max_value() as Balance); + bond_validator(3, Votes::max_value() as Balance); + bond_validator(5, Votes::max_value() as Balance); - bond_nominator(7, 6, Votes::max_value() as Balance, vec![3, 5]); - bond_nominator(9, 8, Votes::max_value() as Balance, vec![3, 5]); + bond_nominator(7, Votes::max_value() as Balance, vec![3, 5]); + bond_nominator(9, Votes::max_value() as Balance, vec![3, 5]); mock::start_active_era(1); - assert_eq_uvec!(validator_controllers(), vec![4, 2]); + assert_eq_uvec!(validator_controllers(), vec![3, 5]); // We can safely convert back to values within [u64, u128]. assert!(Staking::eras_stakers(active_era(), 3).total > Votes::max_value() as Balance); @@ -2202,8 +2201,7 @@ fn reward_validator_slashing_validator_does_not_overflow() { let _ = Balances::make_free_balance_be(&2, stake); // only slashes out of bonded stake are applied. without this line, it is 0. - Staking::bond(RuntimeOrigin::signed(2), 20000, stake - 1, RewardDestination::default()) - .unwrap(); + Staking::bond(RuntimeOrigin::signed(2), stake - 1, RewardDestination::default()).unwrap(); // Override exposure of 11 ErasStakers::::insert( 0, @@ -2237,9 +2235,7 @@ fn reward_from_authorship_event_handler_works() { assert_eq!(>::author(), Some(11)); Pallet::::note_author(11); - Pallet::::note_uncle(21, 1); - // Rewarding the same two times works. - Pallet::::note_uncle(11, 1); + Pallet::::note_author(11); // Not mandatory but must be coherent with rewards assert_eq_uvec!(Session::validators(), vec![11, 21]); @@ -2248,10 +2244,7 @@ fn reward_from_authorship_event_handler_works() { // 11 is rewarded as a block producer and uncle referencer and uncle producer assert_eq!( ErasRewardPoints::::get(active_era()), - EraRewardPoints { - individual: vec![(11, 20 + 2 * 2 + 1), (21, 1)].into_iter().collect(), - total: 26, - }, + EraRewardPoints { individual: vec![(11, 20 * 2)].into_iter().collect(), total: 40 }, ); }) } @@ -2279,7 +2272,7 @@ fn unbonded_balance_is_not_slashable() { // total amount staked is slashable. assert_eq!(Staking::slashable_balance_of(&11), 1000); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 800)); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 800)); // only the active portion. assert_eq!(Staking::slashable_balance_of(&11), 200); @@ -2416,7 +2409,7 @@ fn slash_in_old_span_does_not_deselect() { mock::start_active_era(2); - Staking::validate(RuntimeOrigin::signed(10), Default::default()).unwrap(); + Staking::validate(RuntimeOrigin::signed(11), Default::default()).unwrap(); assert_eq!(Staking::force_era(), Forcing::NotForcing); assert!(>::contains_key(11)); assert!(!Session::validators().contains(&11)); @@ -2437,7 +2430,7 @@ fn slash_in_old_span_does_not_deselect() { ); // the validator doesn't get chilled again - assert!(::Validators::iter().any(|(stash, _)| stash == 11)); + assert!(Validators::::iter().any(|(stash, _)| stash == 11)); // but we are still forcing a new era assert_eq!(Staking::force_era(), Forcing::ForceNew); @@ -2454,10 +2447,10 @@ fn slash_in_old_span_does_not_deselect() { ); // the validator doesn't get chilled again - assert!(::Validators::iter().any(|(stash, _)| stash == 11)); + assert!(Validators::::iter().any(|(stash, _)| stash == 11)); // but it's disabled - assert!(is_disabled(10)); + assert!(is_disabled(11)); // and we are still forcing a new era assert_eq!(Staking::force_era(), Forcing::ForceNew); }); @@ -2653,8 +2646,8 @@ fn garbage_collection_after_slashing() { ); assert_eq!(Balances::free_balance(11), 2000 - 200); - assert!(::SlashingSpans::get(&11).is_some()); - assert_eq!(::SpanSlash::get(&(11, 0)).amount(), &200); + assert!(SlashingSpans::::get(&11).is_some()); + assert_eq!(SpanSlash::::get(&(11, 0)).amount(), &200); on_offence_now( &[OffenceDetails { @@ -2670,7 +2663,7 @@ fn garbage_collection_after_slashing() { assert_eq!(Balances::free_balance(11), 2); assert_eq!(Balances::total_balance(&11), 2); - let slashing_spans = ::SlashingSpans::get(&11).unwrap(); + let slashing_spans = SlashingSpans::::get(&11).unwrap(); assert_eq!(slashing_spans.iter().count(), 2); // reap_stash respects num_slashing_spans so that weight is accurate @@ -2680,8 +2673,8 @@ fn garbage_collection_after_slashing() { ); assert_ok!(Staking::reap_stash(RuntimeOrigin::signed(20), 11, 2)); - assert!(::SlashingSpans::get(&11).is_none()); - assert_eq!(::SpanSlash::get(&(11, 0)).amount(), &0); + assert!(SlashingSpans::::get(&11).is_none()); + assert_eq!(SpanSlash::::get(&(11, 0)).amount(), &0); }) } @@ -2707,19 +2700,19 @@ fn garbage_collection_on_window_pruning() { assert_eq!(Balances::free_balance(11), 900); assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10)); - assert!(::ValidatorSlashInEra::get(&now, &11).is_some()); - assert!(::NominatorSlashInEra::get(&now, &101).is_some()); + assert!(ValidatorSlashInEra::::get(&now, &11).is_some()); + assert!(NominatorSlashInEra::::get(&now, &101).is_some()); // + 1 because we have to exit the bonding window. for era in (0..(BondingDuration::get() + 1)).map(|offset| offset + now + 1) { - assert!(::ValidatorSlashInEra::get(&now, &11).is_some()); - assert!(::NominatorSlashInEra::get(&now, &101).is_some()); + assert!(ValidatorSlashInEra::::get(&now, &11).is_some()); + assert!(NominatorSlashInEra::::get(&now, &101).is_some()); mock::start_active_era(era); } - assert!(::ValidatorSlashInEra::get(&now, &11).is_none()); - assert!(::NominatorSlashInEra::get(&now, &101).is_none()); + assert!(ValidatorSlashInEra::::get(&now, &11).is_none()); + assert!(NominatorSlashInEra::::get(&now, &101).is_none()); }) } @@ -2760,7 +2753,7 @@ fn slashing_nominators_by_span_max() { slashing::SlashingSpan { index: 0, start: 0, length: Some(4) }, ]; - let get_span = |account| ::SlashingSpans::get(&account).unwrap(); + let get_span = |account| SlashingSpans::::get(&account).unwrap(); assert_eq!(get_span(11).iter().collect::>(), expected_spans); @@ -2822,7 +2815,7 @@ fn slashes_are_summed_across_spans() { assert_eq!(Balances::free_balance(21), 2000); assert_eq!(Staking::slashable_balance_of(&21), 1000); - let get_span = |account| ::SlashingSpans::get(&account).unwrap(); + let get_span = |account| SlashingSpans::::get(&account).unwrap(); on_offence_now( &[OffenceDetails { @@ -2841,7 +2834,7 @@ fn slashes_are_summed_across_spans() { assert_eq!(Balances::free_balance(21), 1900); // 21 has been force-chilled. re-signal intent to validate. - Staking::validate(RuntimeOrigin::signed(20), Default::default()).unwrap(); + Staking::validate(RuntimeOrigin::signed(21), Default::default()).unwrap(); mock::start_active_era(4); @@ -2972,8 +2965,8 @@ fn retroactive_deferred_slashes_one_before() { // unbond at slash era. mock::start_active_era(2); - assert_ok!(Staking::chill(RuntimeOrigin::signed(10))); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 100)); + assert_ok!(Staking::chill(RuntimeOrigin::signed(11))); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 100)); mock::start_active_era(3); System::reset_events(); @@ -2986,7 +2979,7 @@ fn retroactive_deferred_slashes_one_before() { mock::start_active_era(4); - assert_eq!(Staking::ledger(10).unwrap().total, 1000); + assert_eq!(Staking::ledger(11).unwrap().total, 1000); // slash happens after the next line. mock::start_active_era(5); @@ -3001,9 +2994,9 @@ fn retroactive_deferred_slashes_one_before() { )); // their ledger has already been slashed. - assert_eq!(Staking::ledger(10).unwrap().total, 900); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1000)); - assert_eq!(Staking::ledger(10).unwrap().total, 900); + assert_eq!(Staking::ledger(11).unwrap().total, 900); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1000)); + assert_eq!(Staking::ledger(11).unwrap().total, 900); }) } @@ -3028,14 +3021,14 @@ fn staker_cannot_bail_deferred_slash() { ); // now we chill - assert_ok!(Staking::chill(RuntimeOrigin::signed(100))); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(100), 500)); + assert_ok!(Staking::chill(RuntimeOrigin::signed(101))); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(101), 500)); assert_eq!(Staking::current_era().unwrap(), 1); assert_eq!(active_era(), 1); assert_eq!( - Ledger::::get(100).unwrap(), + Ledger::::get(101).unwrap(), StakingLedger { active: 0, total: 500, @@ -3065,10 +3058,10 @@ fn staker_cannot_bail_deferred_slash() { // and cannot yet unbond: assert_storage_noop!(assert!( - Staking::withdraw_unbonded(RuntimeOrigin::signed(100), 0).is_ok() + Staking::withdraw_unbonded(RuntimeOrigin::signed(101), 0).is_ok() )); assert_eq!( - Ledger::::get(100).unwrap().unlocking.into_inner(), + Ledger::::get(101).unwrap().unlocking.into_inner(), vec![UnlockChunk { era: 4u32, value: 500 as Balance }], ); @@ -3197,7 +3190,7 @@ fn remove_multi_deferred() { &[Perbill::from_percent(25)], ); - assert_eq!(::UnappliedSlashes::get(&4).len(), 5); + assert_eq!(UnappliedSlashes::::get(&4).len(), 5); // fails if list is not sorted assert_noop!( @@ -3217,7 +3210,7 @@ fn remove_multi_deferred() { assert_ok!(Staking::cancel_deferred_slash(RuntimeOrigin::root(), 4, vec![0, 2, 4])); - let slashes = ::UnappliedSlashes::get(&4); + let slashes = UnappliedSlashes::::get(&4); assert_eq!(slashes.len(), 2); assert_eq!(slashes[0].validator, 21); assert_eq!(slashes[1].validator, 42); @@ -3272,10 +3265,10 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid assert_eq!(Balances::free_balance(101), 2000 - nominator_slash_amount_11); // check that validator was chilled. - assert!(::Validators::iter().all(|(stash, _)| stash != 11)); + assert!(Validators::::iter().all(|(stash, _)| stash != 11)); // actually re-bond the slashed validator - assert_ok!(Staking::validate(RuntimeOrigin::signed(10), Default::default())); + assert_ok!(Staking::validate(RuntimeOrigin::signed(11), Default::default())); mock::start_active_era(2); let exposure_11 = Staking::eras_stakers(active_era(), &11); @@ -3341,9 +3334,9 @@ fn non_slashable_offence_doesnt_disable_validator() { ); // the offence for validator 10 wasn't slashable so it wasn't disabled - assert!(!is_disabled(10)); + assert!(!is_disabled(11)); // whereas validator 20 gets disabled - assert!(is_disabled(20)); + assert!(is_disabled(21)); }); } @@ -3404,9 +3397,9 @@ fn slashing_independent_of_disabling_validator() { ); // the offence for validator 10 was explicitly disabled - assert!(is_disabled(10)); - // whereas validator 20 is explicitly not disabled - assert!(!is_disabled(20)); + assert!(is_disabled(11)); + // whereas validator 21 is explicitly not disabled + assert!(!is_disabled(21)); }); } @@ -3480,18 +3473,18 @@ fn disabled_validators_are_kept_disabled_for_whole_era() { // nominations are not updated. assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); - // validator 10 should not be disabled since the offence wasn't slashable - assert!(!is_disabled(10)); - // validator 20 gets disabled since it got slashed - assert!(is_disabled(20)); + // validator 11 should not be disabled since the offence wasn't slashable + assert!(!is_disabled(11)); + // validator 21 gets disabled since it got slashed + assert!(is_disabled(21)); advance_session(); // disabled validators should carry-on through all sessions in the era - assert!(!is_disabled(10)); - assert!(is_disabled(20)); + assert!(!is_disabled(11)); + assert!(is_disabled(21)); - // validator 10 should now get disabled + // validator 11 should now get disabled on_offence_now( &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], &[Perbill::from_percent(25)], @@ -3503,14 +3496,14 @@ fn disabled_validators_are_kept_disabled_for_whole_era() { advance_session(); // and both are disabled in the last session of the era - assert!(is_disabled(10)); - assert!(is_disabled(20)); + assert!(is_disabled(11)); + assert!(is_disabled(21)); mock::start_active_era(2); // when a new era starts disabled validators get cleared - assert!(!is_disabled(10)); - assert!(!is_disabled(20)); + assert!(!is_disabled(11)); + assert!(!is_disabled(21)); }); } @@ -3524,11 +3517,11 @@ fn claim_reward_at_the_last_era_and_no_double_claim_and_invalid_claim() { // Consumed weight for all payout_stakers dispatches that fail let err_weight = ::WeightInfo::payout_stakers_alive_staked(0); - let init_balance_10 = Balances::total_balance(&10); - let init_balance_100 = Balances::total_balance(&100); + let init_balance_11 = Balances::total_balance(&11); + let init_balance_101 = Balances::total_balance(&101); - let part_for_10 = Perbill::from_rational::(1000, 1125); - let part_for_100 = Perbill::from_rational::(125, 1125); + let part_for_11 = Perbill::from_rational::(1000, 1125); + let part_for_101 = Perbill::from_rational::(125, 1125); // Check state Payee::::insert(11, RewardDestination::Controller); @@ -3588,12 +3581,12 @@ fn claim_reward_at_the_last_era_and_no_double_claim_and_invalid_claim() { // only era 1 and 2 can be rewarded. assert_eq!( - Balances::total_balance(&10), - init_balance_10 + part_for_10 * (total_payout_1 + total_payout_2), + Balances::total_balance(&11), + init_balance_11 + part_for_11 * (total_payout_1 + total_payout_2), ); assert_eq!( - Balances::total_balance(&100), - init_balance_100 + part_for_100 * (total_payout_1 + total_payout_2), + Balances::total_balance(&101), + init_balance_101 + part_for_101 * (total_payout_1 + total_payout_2), ); }); } @@ -3617,7 +3610,7 @@ fn zero_slash_keeps_nominators() { assert_eq!(Balances::free_balance(101), 2000); // 11 is still removed.. - assert!(::Validators::iter().all(|(stash, _)| stash != 11)); + assert!(Validators::::iter().all(|(stash, _)| stash != 11)); // but their nominations are kept. assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); }); @@ -3688,16 +3681,14 @@ fn test_max_nominator_rewarded_per_validator_and_cant_steal_someone_else_reward( ExtBuilder::default().build_and_execute(|| { for i in 0..=<::MaxNominatorRewardedPerValidator as Get<_>>::get() { let stash = 10_000 + i as AccountId; - let controller = 20_000 + i as AccountId; let balance = 10_000 + i as Balance; Balances::make_free_balance_be(&stash, balance); assert_ok!(Staking::bond( RuntimeOrigin::signed(stash), - controller, balance, RewardDestination::Stash )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(controller), vec![11])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(stash), vec![11])); } mock::start_active_era(1); @@ -3732,13 +3723,13 @@ fn test_payout_stakers() { // Track the exposure of the validator and the nominators that will get paid out. let mut payout_exposure = balance; // Create a validator: - bond_validator(11, 10, balance); // Default(64) + bond_validator(11, balance); // Default(64) assert_eq!(Validators::::count(), 1); // Create nominators, targeting stash of validators for i in 0..100 { let bond_amount = balance + i as Balance; - bond_nominator(1000 + i, 100 + i, bond_amount, vec![11]); + bond_nominator(1000 + i, bond_amount, vec![11]); total_exposure += bond_amount; if i >= 36 { payout_exposure += bond_amount; @@ -3767,18 +3758,18 @@ fn test_payout_stakers() { // Top 64 nominators of validator 11 automatically paid out, including the validator // Validator payout goes to controller. - assert!(Balances::free_balance(&10) > balance); + assert!(Balances::free_balance(&11) > balance); for i in 36..100 { - assert!(Balances::free_balance(&(100 + i)) > balance + i as Balance); + assert!(Balances::free_balance(&(1000 + i)) > balance + i as Balance); } // The bottom 36 do not for i in 0..36 { - assert_eq!(Balances::free_balance(&(100 + i)), balance + i as Balance); + assert_eq!(Balances::free_balance(&(1000 + i)), balance + i as Balance); } // We track rewards in `claimed_rewards` vec assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -3809,7 +3800,7 @@ fn test_payout_stakers() { // We track rewards in `claimed_rewards` vec assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -3842,7 +3833,7 @@ fn test_payout_stakers() { expected_last_reward_era )); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -3857,7 +3848,7 @@ fn test_payout_stakers() { assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 23)); assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 42)); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -3884,11 +3875,11 @@ fn payout_stakers_handles_basic_errors() { // Same setup as the test above let balance = 1000; - bond_validator(11, 10, balance); // Default(64) + bond_validator(11, balance); // Default(64) // Create nominators, targeting stash for i in 0..100 { - bond_nominator(1000 + i, 100 + i, balance + i as Balance, vec![11]); + bond_nominator(1000 + i, balance + i as Balance, vec![11]); } mock::start_active_era(1); @@ -3980,7 +3971,7 @@ fn payout_stakers_handles_weight_refund() { assert!(max_nom_rewarded_weight.any_gt(half_max_nom_rewarded_weight)); let balance = 1000; - bond_validator(11, 10, balance); + bond_validator(11, balance); // Era 1 start_active_era(1); @@ -3991,7 +3982,7 @@ fn payout_stakers_handles_weight_refund() { // Add some `half_max_nom_rewarded` nominators who will start backing the validator in the // next era. for i in 0..half_max_nom_rewarded { - bond_nominator((1000 + i).into(), (100 + i).into(), balance + i as Balance, vec![11]); + bond_nominator((1000 + i).into(), balance + i as Balance, vec![11]); } // Era 2 @@ -4033,7 +4024,7 @@ fn payout_stakers_handles_weight_refund() { // Add enough nominators so that we are at the limit. They will be active nominators // in the next era. for i in half_max_nom_rewarded..max_nom_rewarded { - bond_nominator((1000 + i).into(), (100 + i).into(), balance + i as Balance, vec![11]); + bond_nominator((1000 + i).into(), balance + i as Balance, vec![11]); } // Era 5 @@ -4067,9 +4058,9 @@ fn payout_stakers_handles_weight_refund() { fn bond_during_era_correctly_populates_claimed_rewards() { ExtBuilder::default().has_stakers(false).build_and_execute(|| { // Era = None - bond_validator(9, 8, 1000); + bond_validator(9, 1000); assert_eq!( - Staking::ledger(&8), + Staking::ledger(&9), Some(StakingLedger { stash: 9, total: 1000, @@ -4079,9 +4070,9 @@ fn bond_during_era_correctly_populates_claimed_rewards() { }) ); mock::start_active_era(5); - bond_validator(11, 10, 1000); + bond_validator(11, 1000); assert_eq!( - Staking::ledger(&10), + Staking::ledger(&11), Some(StakingLedger { stash: 11, total: 1000, @@ -4095,9 +4086,9 @@ fn bond_during_era_correctly_populates_claimed_rewards() { let current_era = 99; let last_reward_era = 99 - HistoryDepth::get(); mock::start_active_era(current_era); - bond_validator(13, 12, 1000); + bond_validator(13, 1000); assert_eq!( - Staking::ledger(&12), + Staking::ledger(&13), Some(StakingLedger { stash: 13, total: 1000, @@ -4186,24 +4177,31 @@ fn payout_creates_controller() { ExtBuilder::default().has_stakers(false).build_and_execute(|| { let balance = 1000; // Create a validator: - bond_validator(11, 10, balance); + bond_validator(11, balance); - // Create a stash/controller pair - bond_nominator(1234, 1337, 100, vec![11]); + // create a stash/controller pair and nominate + let (stash, controller) = testing_utils::create_unique_stash_controller::( + 0, + 100, + RewardDestination::Controller, + false, + ) + .unwrap(); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(controller), vec![11])); // kill controller - assert_ok!(Balances::transfer(RuntimeOrigin::signed(1337), 1234, 100)); - assert_eq!(Balances::free_balance(1337), 0); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(controller), stash, 100)); + assert_eq!(Balances::free_balance(controller), 0); mock::start_active_era(1); Staking::reward_by_ids(vec![(11, 1)]); // compute and ensure the reward amount is greater than zero. let _ = current_total_payout_for_duration(reward_time_per_era()); mock::start_active_era(2); - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 1)); + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(controller), 11, 1)); // Controller is created - assert!(Balances::free_balance(1337) > 0); + assert!(Balances::free_balance(controller) > 0); }) } @@ -4212,13 +4210,13 @@ fn payout_to_any_account_works() { ExtBuilder::default().has_stakers(false).build_and_execute(|| { let balance = 1000; // Create a validator: - bond_validator(11, 10, balance); // Default(64) + bond_validator(11, balance); // Default(64) // Create a stash/controller pair - bond_nominator(1234, 1337, 100, vec![11]); + bond_nominator(1234, 100, vec![11]); // Update payout location - assert_ok!(Staking::set_payee(RuntimeOrigin::signed(1337), RewardDestination::Account(42))); + assert_ok!(Staking::set_payee(RuntimeOrigin::signed(1234), RewardDestination::Account(42))); // Reward Destination account doesn't exist assert_eq!(Balances::free_balance(42), 0); @@ -4340,38 +4338,38 @@ fn session_buffering_no_offset() { #[test] fn cannot_rebond_to_lower_than_ed() { ExtBuilder::default() - .existential_deposit(10) - .balance_factor(10) + .existential_deposit(11) + .balance_factor(11) .build_and_execute(|| { // initial stuff. assert_eq!( - Staking::ledger(&20).unwrap(), + Staking::ledger(&21).unwrap(), StakingLedger { stash: 21, - total: 10 * 1000, - active: 10 * 1000, + total: 11 * 1000, + active: 11 * 1000, unlocking: Default::default(), claimed_rewards: bounded_vec![], } ); // unbond all of it. must be chilled first. - assert_ok!(Staking::chill(RuntimeOrigin::signed(20))); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(20), 10 * 1000)); + assert_ok!(Staking::chill(RuntimeOrigin::signed(21))); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(21), 11 * 1000)); assert_eq!( - Staking::ledger(&20).unwrap(), + Staking::ledger(&21).unwrap(), StakingLedger { stash: 21, - total: 10 * 1000, + total: 11 * 1000, active: 0, - unlocking: bounded_vec![UnlockChunk { value: 10 * 1000, era: 3 }], + unlocking: bounded_vec![UnlockChunk { value: 11 * 1000, era: 3 }], claimed_rewards: bounded_vec![], } ); // now bond a wee bit more assert_noop!( - Staking::rebond(RuntimeOrigin::signed(20), 5), + Staking::rebond(RuntimeOrigin::signed(21), 5), Error::::InsufficientBond ); }) @@ -4380,31 +4378,31 @@ fn cannot_rebond_to_lower_than_ed() { #[test] fn cannot_bond_extra_to_lower_than_ed() { ExtBuilder::default() - .existential_deposit(10) - .balance_factor(10) + .existential_deposit(11) + .balance_factor(11) .build_and_execute(|| { // initial stuff. assert_eq!( - Staking::ledger(&20).unwrap(), + Staking::ledger(&21).unwrap(), StakingLedger { stash: 21, - total: 10 * 1000, - active: 10 * 1000, + total: 11 * 1000, + active: 11 * 1000, unlocking: Default::default(), claimed_rewards: bounded_vec![], } ); // unbond all of it. must be chilled first. - assert_ok!(Staking::chill(RuntimeOrigin::signed(20))); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(20), 10 * 1000)); + assert_ok!(Staking::chill(RuntimeOrigin::signed(21))); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(21), 11 * 1000)); assert_eq!( - Staking::ledger(&20).unwrap(), + Staking::ledger(&21).unwrap(), StakingLedger { stash: 21, - total: 10 * 1000, + total: 11 * 1000, active: 0, - unlocking: bounded_vec![UnlockChunk { value: 10 * 1000, era: 3 }], + unlocking: bounded_vec![UnlockChunk { value: 11 * 1000, era: 3 }], claimed_rewards: bounded_vec![], } ); @@ -4426,7 +4424,7 @@ fn do_not_die_when_active_is_ed() { .build_and_execute(|| { // given assert_eq!( - Staking::ledger(&20).unwrap(), + Staking::ledger(&21).unwrap(), StakingLedger { stash: 21, total: 1000 * ed, @@ -4437,13 +4435,13 @@ fn do_not_die_when_active_is_ed() { ); // when unbond all of it except ed. - assert_ok!(Staking::unbond(RuntimeOrigin::signed(20), 999 * ed)); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(21), 999 * ed)); start_active_era(3); - assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(20), 100)); + assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(21), 100)); // then assert_eq!( - Staking::ledger(&20).unwrap(), + Staking::ledger(&21).unwrap(), StakingLedger { stash: 21, total: ed, @@ -4506,9 +4504,9 @@ mod election_data_provider { fn set_minimum_active_stake_is_correct() { ExtBuilder::default() .nominate(false) - .add_staker(61, 60, 2_000, StakerStatus::::Nominator(vec![21])) - .add_staker(71, 70, 10, StakerStatus::::Nominator(vec![21])) - .add_staker(81, 80, 50, StakerStatus::::Nominator(vec![21])) + .add_staker(61, 61, 2_000, StakerStatus::::Nominator(vec![21])) + .add_staker(71, 71, 10, StakerStatus::::Nominator(vec![21])) + .add_staker(81, 81, 50, StakerStatus::::Nominator(vec![21])) .build_and_execute(|| { assert_ok!(::electing_voters(None)); assert_eq!(MinimumActiveStake::::get(), 10); @@ -4580,19 +4578,19 @@ mod election_data_provider { // ppl, but then lower the MaxNomination limit. .add_staker( 61, - 60, + 61, 2_000, StakerStatus::::Nominator(vec![21, 22, 23, 24, 25]), ) .add_staker( 71, - 70, + 71, 2_000, StakerStatus::::Nominator(vec![21, 22, 23, 24, 25]), ) .add_staker( 81, - 80, + 81, 2_000, StakerStatus::::Nominator(vec![21, 22, 23, 24, 25]), ) @@ -4712,51 +4710,46 @@ fn min_bond_checks_work() { .min_validator_bond(1_500) .build_and_execute(|| { // 500 is not enough for any role - assert_ok!(Staking::bond( - RuntimeOrigin::signed(3), - 4, - 500, - RewardDestination::Controller - )); + assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 500, RewardDestination::Controller)); assert_noop!( - Staking::nominate(RuntimeOrigin::signed(4), vec![1]), + Staking::nominate(RuntimeOrigin::signed(3), vec![1]), Error::::InsufficientBond ); assert_noop!( - Staking::validate(RuntimeOrigin::signed(4), ValidatorPrefs::default()), + Staking::validate(RuntimeOrigin::signed(3), ValidatorPrefs::default()), Error::::InsufficientBond, ); // 1000 is enough for nominator assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(3), 500)); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![1])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(3), vec![1])); assert_noop!( - Staking::validate(RuntimeOrigin::signed(4), ValidatorPrefs::default()), + Staking::validate(RuntimeOrigin::signed(3), ValidatorPrefs::default()), Error::::InsufficientBond, ); // 1500 is enough for validator assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(3), 500)); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![1])); - assert_ok!(Staking::validate(RuntimeOrigin::signed(4), ValidatorPrefs::default())); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(3), vec![1])); + assert_ok!(Staking::validate(RuntimeOrigin::signed(3), ValidatorPrefs::default())); // Can't unbond anything as validator assert_noop!( - Staking::unbond(RuntimeOrigin::signed(4), 500), + Staking::unbond(RuntimeOrigin::signed(3), 500), Error::::InsufficientBond ); // Once they are a nominator, they can unbond 500 - assert_ok!(Staking::nominate(RuntimeOrigin::signed(4), vec![1])); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(4), 500)); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(3), vec![1])); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(3), 500)); assert_noop!( - Staking::unbond(RuntimeOrigin::signed(4), 500), + Staking::unbond(RuntimeOrigin::signed(3), 500), Error::::InsufficientBond ); // Once they are chilled they can unbond everything - assert_ok!(Staking::chill(RuntimeOrigin::signed(4))); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(4), 1000)); + assert_ok!(Staking::chill(RuntimeOrigin::signed(3))); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(3), 1000)); }) } @@ -4772,31 +4765,27 @@ fn chill_other_works() { let initial_nominators = Nominators::::count(); for i in 0..15 { let a = 4 * i; - let b = 4 * i + 1; - let c = 4 * i + 2; - let d = 4 * i + 3; + let b = 4 * i + 2; + let c = 4 * i + 3; Balances::make_free_balance_be(&a, 100_000); Balances::make_free_balance_be(&b, 100_000); Balances::make_free_balance_be(&c, 100_000); - Balances::make_free_balance_be(&d, 100_000); // Nominator assert_ok!(Staking::bond( RuntimeOrigin::signed(a), - b, 1000, RewardDestination::Controller )); - assert_ok!(Staking::nominate(RuntimeOrigin::signed(b), vec![1])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(a), vec![1])); // Validator assert_ok!(Staking::bond( - RuntimeOrigin::signed(c), - d, + RuntimeOrigin::signed(b), 1500, RewardDestination::Controller )); - assert_ok!(Staking::validate(RuntimeOrigin::signed(d), ValidatorPrefs::default())); + assert_ok!(Staking::validate(RuntimeOrigin::signed(b), ValidatorPrefs::default())); } // To chill other users, we need to: @@ -4809,11 +4798,11 @@ fn chill_other_works() { // Can't chill these users assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 1), + Staking::chill_other(RuntimeOrigin::signed(1337), 0), Error::::CannotChillOther ); assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 3), + Staking::chill_other(RuntimeOrigin::signed(1337), 2), Error::::CannotChillOther ); @@ -4830,11 +4819,11 @@ fn chill_other_works() { // Still can't chill these users assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 1), + Staking::chill_other(RuntimeOrigin::signed(1337), 0), Error::::CannotChillOther ); assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 3), + Staking::chill_other(RuntimeOrigin::signed(1337), 2), Error::::CannotChillOther ); @@ -4851,11 +4840,11 @@ fn chill_other_works() { // Still can't chill these users assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 1), + Staking::chill_other(RuntimeOrigin::signed(1337), 0), Error::::CannotChillOther ); assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 3), + Staking::chill_other(RuntimeOrigin::signed(1337), 2), Error::::CannotChillOther ); @@ -4872,11 +4861,11 @@ fn chill_other_works() { // Still can't chill these users assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 1), + Staking::chill_other(RuntimeOrigin::signed(1337), 0), Error::::CannotChillOther ); assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 3), + Staking::chill_other(RuntimeOrigin::signed(1337), 2), Error::::CannotChillOther ); @@ -4898,8 +4887,8 @@ fn chill_other_works() { // Users can now be chilled down to 7 people, so we try to remove 9 of them (starting // with 16) for i in 6..15 { - let b = 4 * i + 1; - let d = 4 * i + 3; + let b = 4 * i; + let d = 4 * i + 2; assert_ok!(Staking::chill_other(RuntimeOrigin::signed(1337), b)); assert_ok!(Staking::chill_other(RuntimeOrigin::signed(1337), d)); } @@ -4907,12 +4896,12 @@ fn chill_other_works() { // chill a nominator. Limit is not reached, not chill-able assert_eq!(Nominators::::count(), 7); assert_noop!( - Staking::chill_other(RuntimeOrigin::signed(1337), 1), + Staking::chill_other(RuntimeOrigin::signed(1337), 0), Error::::CannotChillOther ); // chill a validator. Limit is reached, chill-able. assert_eq!(Validators::::count(), 9); - assert_ok!(Staking::chill_other(RuntimeOrigin::signed(1337), 3)); + assert_ok!(Staking::chill_other(RuntimeOrigin::signed(1337), 2)); }) } @@ -4978,7 +4967,7 @@ fn capped_stakers_works() { some_existing_nominator = controller; } - // one more is too many + // one more is too many. let (_, last_nominator) = testing_utils::create_stash_controller::( 30_000_000, 100, @@ -5019,9 +5008,9 @@ fn capped_stakers_works() { #[test] fn min_commission_works() { ExtBuilder::default().build_and_execute(|| { - // account 10 controls the stash from account 11 + // account 11 controls the stash of itself. assert_ok!(Staking::validate( - RuntimeOrigin::signed(10), + RuntimeOrigin::signed(11), ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false } )); @@ -5047,7 +5036,7 @@ fn min_commission_works() { // can't make it less than 10 now assert_noop!( Staking::validate( - RuntimeOrigin::signed(10), + RuntimeOrigin::signed(11), ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false } ), Error::::CommissionTooLow @@ -5055,12 +5044,12 @@ fn min_commission_works() { // can only change to higher. assert_ok!(Staking::validate( - RuntimeOrigin::signed(10), + RuntimeOrigin::signed(11), ValidatorPrefs { commission: Perbill::from_percent(10), blocked: false } )); assert_ok!(Staking::validate( - RuntimeOrigin::signed(10), + RuntimeOrigin::signed(11), ValidatorPrefs { commission: Perbill::from_percent(15), blocked: false } )); }) @@ -5070,8 +5059,8 @@ fn min_commission_works() { fn change_of_max_nominations() { use frame_election_provider_support::ElectionDataProvider; ExtBuilder::default() - .add_staker(60, 61, 10, StakerStatus::Nominator(vec![1])) - .add_staker(70, 71, 10, StakerStatus::Nominator(vec![1, 2, 3])) + .add_staker(61, 61, 10, StakerStatus::Nominator(vec![1])) + .add_staker(71, 71, 10, StakerStatus::Nominator(vec![1, 2, 3])) .balance_factor(10) .build_and_execute(|| { // pre-condition @@ -5081,7 +5070,7 @@ fn change_of_max_nominations() { Nominators::::iter() .map(|(k, n)| (k, n.targets.len())) .collect::>(), - vec![(70, 3), (101, 2), (60, 1)] + vec![(101, 2), (71, 3), (61, 1)] ); // 3 validators and 3 nominators assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3); @@ -5093,7 +5082,7 @@ fn change_of_max_nominations() { Nominators::::iter() .map(|(k, n)| (k, n.targets.len())) .collect::>(), - vec![(70, 3), (101, 2), (60, 1)] + vec![(101, 2), (71, 3), (61, 1)] ); assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3); @@ -5104,7 +5093,7 @@ fn change_of_max_nominations() { Nominators::::iter() .map(|(k, n)| (k, n.targets.len())) .collect::>(), - vec![(70, 3), (101, 2), (60, 1)] + vec![(101, 2), (71, 3), (61, 1)] ); assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 3); @@ -5116,12 +5105,12 @@ fn change_of_max_nominations() { Nominators::::iter() .map(|(k, n)| (k, n.targets.len())) .collect::>(), - vec![(101, 2), (60, 1)] + vec![(101, 2), (61, 1)] ); // 70 is still in storage.. - assert!(Nominators::::contains_key(70)); + assert!(Nominators::::contains_key(71)); // but its value cannot be decoded and default is returned. - assert!(Nominators::::get(70).is_none()); + assert!(Nominators::::get(71).is_none()); assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 2); assert!(Nominators::::contains_key(101)); @@ -5134,12 +5123,12 @@ fn change_of_max_nominations() { Nominators::::iter() .map(|(k, n)| (k, n.targets.len())) .collect::>(), - vec![(60, 1)] + vec![(61, 1)] ); - assert!(Nominators::::contains_key(70)); - assert!(Nominators::::contains_key(60)); - assert!(Nominators::::get(70).is_none()); - assert!(Nominators::::get(60).is_some()); + assert!(Nominators::::contains_key(71)); + assert!(Nominators::::contains_key(61)); + assert!(Nominators::::get(71).is_none()); + assert!(Nominators::::get(61).is_some()); assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 1); // now one of them can revive themselves by re-nominating to a proper value. @@ -5148,13 +5137,13 @@ fn change_of_max_nominations() { Nominators::::iter() .map(|(k, n)| (k, n.targets.len())) .collect::>(), - vec![(70, 1), (60, 1)] + vec![(71, 1), (61, 1)] ); // or they can be chilled by any account. assert!(Nominators::::contains_key(101)); assert!(Nominators::::get(101).is_none()); - assert_ok!(Staking::chill_other(RuntimeOrigin::signed(70), 100)); + assert_ok!(Staking::chill_other(RuntimeOrigin::signed(71), 101)); assert!(!Nominators::::contains_key(101)); assert!(Nominators::::get(101).is_none()); }) @@ -5178,7 +5167,7 @@ mod sorted_list_provider { ); // when account 101 renominates - assert_ok!(Staking::nominate(RuntimeOrigin::signed(100), vec![41])); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(101), vec![41])); // then counts don't change assert_eq!(::VoterList::count(), pre_insert_voter_count); @@ -5201,7 +5190,7 @@ mod sorted_list_provider { assert_eq!(::VoterList::iter().collect::>(), vec![11, 21, 31]); // when account 11 re-validates - assert_ok!(Staking::validate(RuntimeOrigin::signed(10), Default::default())); + assert_ok!(Staking::validate(RuntimeOrigin::signed(11), Default::default())); // then counts don't change assert_eq!(::VoterList::count(), pre_insert_voter_count); @@ -5216,8 +5205,8 @@ fn force_apply_min_commission_works() { let prefs = |c| ValidatorPrefs { commission: Perbill::from_percent(c), blocked: false }; let validators = || Validators::::iter().collect::>(); ExtBuilder::default().build_and_execute(|| { - assert_ok!(Staking::validate(RuntimeOrigin::signed(30), prefs(10))); - assert_ok!(Staking::validate(RuntimeOrigin::signed(20), prefs(5))); + assert_ok!(Staking::validate(RuntimeOrigin::signed(31), prefs(10))); + assert_ok!(Staking::validate(RuntimeOrigin::signed(21), prefs(5))); // Given assert_eq!(validators(), vec![(31, prefs(10)), (21, prefs(5)), (11, prefs(0))]); @@ -5466,7 +5455,7 @@ fn proportional_ledger_slash_works() { ledger.active = unit; ledger.total = unit * 4 + value; // When - assert_eq!(ledger.slash(slash, 0, 0), slash - 5); + assert_eq!(ledger.slash(slash, 0, 0), slash); // Then // The amount slashed out of `unit` let affected_balance = value + unit * 4; @@ -5482,12 +5471,12 @@ fn proportional_ledger_slash_works() { value - value_slash }; assert_eq!(ledger.active, unit_slashed); - assert_eq!(ledger.unlocking, vec![c(5, value_slashed)]); - assert_eq!(ledger.total, value_slashed); + assert_eq!(ledger.unlocking, vec![c(5, value_slashed), c(7, 32)]); + assert_eq!(ledger.total, value_slashed + 32); assert_eq!(LedgerSlashPerEra::get().0, 0); assert_eq!( LedgerSlashPerEra::get().1, - BTreeMap::from([(4, 0), (5, value_slashed), (6, 0), (7, 0)]) + BTreeMap::from([(4, 0), (5, value_slashed), (6, 0), (7, 32)]) ); } @@ -5509,12 +5498,12 @@ fn pre_bonding_era_cannot_be_claimed() { mock::start_active_era(current_era); // add a new candidate for being a validator. account 3 controlled by 4. - assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 4, 1500, RewardDestination::Controller)); + assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 1500, RewardDestination::Controller)); let claimed_rewards: BoundedVec<_, _> = (start_reward_era..=last_reward_era).collect::>().try_into().unwrap(); assert_eq!( - Staking::ledger(&4).unwrap(), + Staking::ledger(&3).unwrap(), StakingLedger { stash: 3, total: 1500, @@ -5529,14 +5518,14 @@ fn pre_bonding_era_cannot_be_claimed() { mock::start_active_era(current_era); // claiming reward for last era in which validator was active works - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(4), 3, current_era - 1)); + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(3), 3, current_era - 1)); // consumed weight for all payout_stakers dispatches that fail let err_weight = ::WeightInfo::payout_stakers_alive_staked(0); // cannot claim rewards for an era before bonding occured as it is // already marked as claimed. assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(4), 3, current_era - 2), + Staking::payout_stakers(RuntimeOrigin::signed(3), 3, current_era - 2), Error::::AlreadyClaimed.with_weight(err_weight) ); @@ -5546,7 +5535,7 @@ fn pre_bonding_era_cannot_be_claimed() { // make sure stakers still cannot claim rewards that they are not meant to assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(4), 3, current_era - 2), + Staking::payout_stakers(RuntimeOrigin::signed(3), 3, current_era - 2), Error::::NotController ); @@ -5564,7 +5553,7 @@ fn reducing_history_depth_abrupt() { let last_reward_era = current_era - 1; let start_reward_era = current_era - original_history_depth; - // put some money in (stash, controller)=(3,4),(5,6). + // put some money in (stash, controller)=(3,3),(5,5). for i in 3..7 { let _ = Balances::make_free_balance_be(&i, 2000); } @@ -5572,15 +5561,15 @@ fn reducing_history_depth_abrupt() { // start current era mock::start_active_era(current_era); - // add a new candidate for being a staker. account 3 controlled by 4. - assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 4, 1500, RewardDestination::Controller)); + // add a new candidate for being a staker. account 3 controlled by 3. + assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 1500, RewardDestination::Controller)); // all previous era before the bonding action should be marked as // claimed. let claimed_rewards: BoundedVec<_, _> = (start_reward_era..=last_reward_era).collect::>().try_into().unwrap(); assert_eq!( - Staking::ledger(&4).unwrap(), + Staking::ledger(&3).unwrap(), StakingLedger { stash: 3, total: 1500, @@ -5595,7 +5584,7 @@ fn reducing_history_depth_abrupt() { mock::start_active_era(current_era); // claiming reward for last era in which validator was active works - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(4), 3, current_era - 1)); + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(3), 3, current_era - 1)); // next era current_era = current_era + 1; @@ -5606,12 +5595,12 @@ fn reducing_history_depth_abrupt() { HistoryDepth::set(history_depth); // claiming reward does not work anymore assert_noop!( - Staking::payout_stakers(RuntimeOrigin::signed(4), 3, current_era - 1), + Staking::payout_stakers(RuntimeOrigin::signed(3), 3, current_era - 1), Error::::NotController ); // new stakers can still bond - assert_ok!(Staking::bond(RuntimeOrigin::signed(5), 6, 1200, RewardDestination::Controller)); + assert_ok!(Staking::bond(RuntimeOrigin::signed(5), 1200, RewardDestination::Controller)); // new staking ledgers created will be bounded by the current history depth let last_reward_era = current_era - 1; @@ -5619,7 +5608,7 @@ fn reducing_history_depth_abrupt() { let claimed_rewards: BoundedVec<_, _> = (start_reward_era..=last_reward_era).collect::>().try_into().unwrap(); assert_eq!( - Staking::ledger(&6).unwrap(), + Staking::ledger(&5).unwrap(), StakingLedger { stash: 5, total: 1200, @@ -5642,17 +5631,17 @@ fn reducing_max_unlocking_chunks_abrupt() { // given a staker at era=10 and MaxUnlockChunks set to 2 MaxUnlockingChunks::set(2); start_active_era(10); - assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 4, 300, RewardDestination::Staked)); - assert!(matches!(Staking::ledger(4), Some(_))); + assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 300, RewardDestination::Staked)); + assert!(matches!(Staking::ledger(3), Some(_))); // when staker unbonds - assert_ok!(Staking::unbond(RuntimeOrigin::signed(4), 20)); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(3), 20)); // then an unlocking chunk is added at `current_era + bonding_duration` // => 10 + 3 = 13 let expected_unlocking: BoundedVec, MaxUnlockingChunks> = bounded_vec![UnlockChunk { value: 20 as Balance, era: 13 as EraIndex }]; - assert!(matches!(Staking::ledger(4), + assert!(matches!(Staking::ledger(3), Some(StakingLedger { unlocking, .. @@ -5660,11 +5649,11 @@ fn reducing_max_unlocking_chunks_abrupt() { // when staker unbonds at next era start_active_era(11); - assert_ok!(Staking::unbond(RuntimeOrigin::signed(4), 50)); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(3), 50)); // then another unlock chunk is added let expected_unlocking: BoundedVec, MaxUnlockingChunks> = bounded_vec![UnlockChunk { value: 20, era: 13 }, UnlockChunk { value: 50, era: 14 }]; - assert!(matches!(Staking::ledger(4), + assert!(matches!(Staking::ledger(3), Some(StakingLedger { unlocking, .. @@ -5673,13 +5662,13 @@ fn reducing_max_unlocking_chunks_abrupt() { // when staker unbonds further start_active_era(12); // then further unbonding not possible - assert_noop!(Staking::unbond(RuntimeOrigin::signed(4), 20), Error::::NoMoreChunks); + assert_noop!(Staking::unbond(RuntimeOrigin::signed(3), 20), Error::::NoMoreChunks); // when max unlocking chunks is reduced abruptly to a low value MaxUnlockingChunks::set(1); // then unbond, rebond ops are blocked with ledger in corrupt state - assert_noop!(Staking::unbond(RuntimeOrigin::signed(4), 20), Error::::NotController); - assert_noop!(Staking::rebond(RuntimeOrigin::signed(4), 100), Error::::NotController); + assert_noop!(Staking::unbond(RuntimeOrigin::signed(3), 20), Error::::NotController); + assert_noop!(Staking::rebond(RuntimeOrigin::signed(3), 100), Error::::NotController); // reset the ledger corruption MaxUnlockingChunks::set(2); @@ -5766,7 +5755,7 @@ fn set_min_commission_works_with_admin_origin() { // setting commission below min_commission fails assert_noop!( Staking::validate( - RuntimeOrigin::signed(10), + RuntimeOrigin::signed(11), ValidatorPrefs { commission: Perbill::from_percent(14), blocked: false } ), Error::::CommissionTooLow @@ -5774,7 +5763,7 @@ fn set_min_commission_works_with_admin_origin() { // setting commission >= min_commission works assert_ok!(Staking::validate( - RuntimeOrigin::signed(10), + RuntimeOrigin::signed(11), ValidatorPrefs { commission: Perbill::from_percent(15), blocked: false } )); }) @@ -5792,13 +5781,13 @@ mod staking_interface { // without slash let _ = with_storage_layer::<(), _, _>(|| { // bond an account, can unstake - assert_eq!(Staking::bonded(&11), Some(10)); + assert_eq!(Staking::bonded(&11), Some(11)); assert_ok!(::force_unstake(11)); Err(DispatchError::from("revert")) }); // bond again and add a slash, still can unstake. - assert_eq!(Staking::bonded(&11), Some(10)); + assert_eq!(Staking::bonded(&11), Some(11)); add_slash(&11); assert_ok!(::force_unstake(11)); }); @@ -5815,18 +5804,41 @@ mod staking_interface { &[Perbill::from_percent(100)], ); - assert_eq!(Staking::bonded(&11), Some(10)); + assert_eq!(Staking::bonded(&11), Some(11)); assert_noop!( - Staking::withdraw_unbonded(RuntimeOrigin::signed(10), 0), + Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0), Error::::IncorrectSlashingSpans ); let num_slashing_spans = Staking::slashing_spans(&11).map_or(0, |s| s.iter().count()); assert_ok!(Staking::withdraw_unbonded( - RuntimeOrigin::signed(10), + RuntimeOrigin::signed(11), num_slashing_spans as u32 )); }); } + + #[test] + fn status() { + ExtBuilder::default().build_and_execute(|| { + // stash of a validator is identified as a validator + assert_eq!(Staking::status(&11).unwrap(), StakerStatus::Validator); + // .. but not the controller. + assert!(Staking::status(&10).is_err()); + + // stash of nominator is identified as a nominator + assert_eq!(Staking::status(&101).unwrap(), StakerStatus::Nominator(vec![11, 21])); + // .. but not the controller. + assert!(Staking::status(&100).is_err()); + + // stash of chilled is identified as a chilled + assert_eq!(Staking::status(&41).unwrap(), StakerStatus::Idle); + // .. but not the controller. + assert!(Staking::status(&40).is_err()); + + // random other account. + assert!(Staking::status(&42).is_err()); + }) + } } diff --git a/frame/staking/src/weights.rs b/frame/staking/src/weights.rs index 9c283f5a065e..f2c65e677cac 100644 --- a/frame/staking/src/weights.rs +++ b/frame/staking/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,33 +18,37 @@ //! Autogenerated weights for pallet_staking //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-12-25, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// /home/benchbot/cargo_target_dir/production/substrate +// ./target/production/substrate // benchmark // pallet +// --chain=dev // --steps=50 // --repeat=20 +// --pallet=pallet_staking +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json -// --pallet=pallet_staking -// --chain=dev -// --header=./HEADER-APACHE2 // --output=./frame/staking/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_staking. pub trait WeightInfo { @@ -83,838 +87,1420 @@ pub trait WeightInfo { /// Weights for pallet_staking using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Staking Bonded (r:1 w:1) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Balances Locks (r:1 w:1) - // Storage: Staking Payee (r:0 w:1) + /// Storage: Staking Bonded (r:1 w:1) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:0 w:1) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) fn bond() -> Weight { - // Minimum execution time: 54_884 nanoseconds. - Weight::from_ref_time(55_487_000) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(4)) - } - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: VoterList ListNodes (r:3 w:3) - // Storage: VoterList ListBags (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `1047` + // Estimated: `4764` + // Minimum execution time: 53_983_000 picoseconds. + Weight::from_parts(55_296_000, 4764) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:3 w:3) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:2 w:2) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) fn bond_extra() -> Weight { - // Minimum execution time: 95_115 nanoseconds. - Weight::from_ref_time(96_213_000) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(7)) - } - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking Nominators (r:1 w:0) - // Storage: Staking MinNominatorBond (r:1 w:0) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: VoterList ListNodes (r:3 w:3) - // Storage: Staking Bonded (r:1 w:0) - // Storage: VoterList ListBags (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `2028` + // Estimated: `8877` + // Minimum execution time: 96_590_000 picoseconds. + Weight::from_parts(98_921_000, 8877) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) + } + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:0) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking MinNominatorBond (r:1 w:0) + /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:3 w:3) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:2 w:2) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) fn unbond() -> Weight { - // Minimum execution time: 102_031 nanoseconds. - Weight::from_ref_time(102_842_000) - .saturating_add(T::DbWeight::get().reads(12)) - .saturating_add(T::DbWeight::get().writes(8)) - } - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `2233` + // Estimated: `8877` + // Minimum execution time: 99_901_000 picoseconds. + Weight::from_parts(102_919_000, 8877) + .saturating_add(T::DbWeight::get().reads(12_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) + } + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - // Minimum execution time: 46_569 nanoseconds. - Weight::from_ref_time(48_034_493) - // Standard Error: 654 - .saturating_add(Weight::from_ref_time(63_628).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(4)) - .saturating_add(T::DbWeight::get().writes(3)) - } - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking Bonded (r:1 w:1) - // Storage: Staking SlashingSpans (r:1 w:0) - // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: VoterList ListNodes (r:2 w:2) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: Staking Payee (r:0 w:1) - // Storage: Staking SpanSlash (r:0 w:2) + // Proof Size summary in bytes: + // Measured: `1021` + // Estimated: `4764` + // Minimum execution time: 45_230_000 picoseconds. + Weight::from_parts(47_052_829, 4764) + // Standard Error: 1_044 + .saturating_add(Weight::from_parts(43_887, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:1) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking SlashingSpans (r:1 w:1) + /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking Validators (r:1 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:2 w:2) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:0 w:1) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Staking SpanSlash (r:0 w:100) + /// Proof: Staking SpanSlash (max_values: None, max_size: Some(76), added: 2551, mode: MaxEncodedLen) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { - // Minimum execution time: 90_154 nanoseconds. - Weight::from_ref_time(95_725_631) - // Standard Error: 2_491 - .saturating_add(Weight::from_ref_time(1_110_795).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(13)) - .saturating_add(T::DbWeight::get().writes(12)) + // Proof Size summary in bytes: + // Measured: `2294 + s * (4 ±0)` + // Estimated: `6248 + s * (4 ±0)` + // Minimum execution time: 97_534_000 picoseconds. + Weight::from_parts(104_772_163, 6248) + // Standard Error: 3_674 + .saturating_add(Weight::from_parts(1_470_124, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(13_u64)) + .saturating_add(T::DbWeight::get().writes(11_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) + .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } - // Storage: Staking Ledger (r:1 w:0) - // Storage: Staking MinValidatorBond (r:1 w:0) - // Storage: Staking MinCommission (r:1 w:0) - // Storage: Staking Validators (r:1 w:1) - // Storage: Staking MaxValidatorsCount (r:1 w:0) - // Storage: Staking Nominators (r:1 w:0) - // Storage: Staking Bonded (r:1 w:0) - // Storage: VoterList ListNodes (r:1 w:1) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) - // Storage: Staking CounterForValidators (r:1 w:1) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking MinValidatorBond (r:1 w:0) + /// Proof: Staking MinValidatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Staking MinCommission (r:1 w:0) + /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:1 w:1) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking MaxValidatorsCount (r:1 w:0) + /// Proof: Staking MaxValidatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:0) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:1 w:1) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking CounterForValidators (r:1 w:1) + /// Proof: Staking CounterForValidators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn validate() -> Weight { - // Minimum execution time: 67_978 nanoseconds. - Weight::from_ref_time(69_153_000) - .saturating_add(T::DbWeight::get().reads(11)) - .saturating_add(T::DbWeight::get().writes(5)) + // Proof Size summary in bytes: + // Measured: `1414` + // Estimated: `4556` + // Minimum execution time: 57_467_000 picoseconds. + Weight::from_parts(59_437_000, 4556) + .saturating_add(T::DbWeight::get().reads(11_u64)) + .saturating_add(T::DbWeight::get().writes(5_u64)) } - // Storage: Staking Ledger (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:128 w:128) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { - // Minimum execution time: 45_328 nanoseconds. - Weight::from_ref_time(47_719_103) - // Standard Error: 14_458 - .saturating_add(Weight::from_ref_time(6_999_252).saturating_mul(k.into())) - .saturating_add(T::DbWeight::get().reads(1)) + // Proof Size summary in bytes: + // Measured: `1285 + k * (569 ±0)` + // Estimated: `4556 + k * (3033 ±0)` + // Minimum execution time: 32_857_000 picoseconds. + Weight::from_parts(37_116_967, 4556) + // Standard Error: 9_522 + .saturating_add(Weight::from_parts(8_796_167, 0).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 3033).saturating_mul(k.into())) } - // Storage: Staking Ledger (r:1 w:0) - // Storage: Staking MinNominatorBond (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking MaxNominatorsCount (r:1 w:0) - // Storage: Staking Validators (r:2 w:0) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking Bonded (r:1 w:0) - // Storage: VoterList ListNodes (r:2 w:2) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking MinNominatorBond (r:1 w:0) + /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking MaxNominatorsCount (r:1 w:0) + /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:17 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:2 w:2) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - // Minimum execution time: 74_650 nanoseconds. - Weight::from_ref_time(74_350_075) - // Standard Error: 10_527 - .saturating_add(Weight::from_ref_time(2_878_737).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(12)) + // Proof Size summary in bytes: + // Measured: `1908 + n * (102 ±0)` + // Estimated: `6248 + n * (2520 ±0)` + // Minimum execution time: 69_613_000 picoseconds. + Weight::from_parts(68_079_061, 6248) + // Standard Error: 18_554 + .saturating_add(Weight::from_parts(4_012_761, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(6)) - } - // Storage: Staking Ledger (r:1 w:0) - // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: VoterList ListNodes (r:2 w:2) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) + .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(Weight::from_parts(0, 2520).saturating_mul(n.into())) + } + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:1 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:2 w:2) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn chill() -> Weight { - // Minimum execution time: 67_790 nanoseconds. - Weight::from_ref_time(68_738_000) - .saturating_add(T::DbWeight::get().reads(8)) - .saturating_add(T::DbWeight::get().writes(6)) + // Proof Size summary in bytes: + // Measured: `1748` + // Estimated: `6248` + // Minimum execution time: 60_430_000 picoseconds. + Weight::from_parts(62_702_000, 6248) + .saturating_add(T::DbWeight::get().reads(8_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) } - // Storage: Staking Ledger (r:1 w:0) - // Storage: Staking Payee (r:0 w:1) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:0 w:1) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) fn set_payee() -> Weight { - // Minimum execution time: 19_237 nanoseconds. - Weight::from_ref_time(19_534_000) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `808` + // Estimated: `4556` + // Minimum execution time: 14_276_000 picoseconds. + Weight::from_parts(14_766_000, 4556) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Staking Bonded (r:1 w:1) - // Storage: Staking Ledger (r:2 w:2) + /// Storage: Staking Bonded (r:1 w:1) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:2 w:2) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) fn set_controller() -> Weight { - // Minimum execution time: 27_288 nanoseconds. - Weight::from_ref_time(27_667_000) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `907` + // Estimated: `8122` + // Minimum execution time: 21_710_000 picoseconds. + Weight::from_parts(22_430_000, 8122) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - // Storage: Staking ValidatorCount (r:0 w:1) + /// Storage: Staking ValidatorCount (r:0 w:1) + /// Proof: Staking ValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn set_validator_count() -> Weight { - // Minimum execution time: 5_155 nanoseconds. - Weight::from_ref_time(5_464_000) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_970_000 picoseconds. + Weight::from_parts(3_120_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Staking ForceEra (r:0 w:1) + /// Storage: Staking ForceEra (r:0 w:1) + /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) fn force_no_eras() -> Weight { - // Minimum execution time: 5_405 nanoseconds. - Weight::from_ref_time(5_670_000) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 9_362_000 picoseconds. + Weight::from_parts(9_785_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Staking ForceEra (r:0 w:1) + /// Storage: Staking ForceEra (r:0 w:1) + /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) fn force_new_era() -> Weight { - // Minimum execution time: 5_459 nanoseconds. - Weight::from_ref_time(5_616_000) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 9_275_000 picoseconds. + Weight::from_parts(9_678_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Staking ForceEra (r:0 w:1) + /// Storage: Staking ForceEra (r:0 w:1) + /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) fn force_new_era_always() -> Weight { - // Minimum execution time: 5_476 nanoseconds. - Weight::from_ref_time(5_692_000) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 9_414_000 picoseconds. + Weight::from_parts(9_848_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Staking Invulnerables (r:0 w:1) + /// Storage: Staking Invulnerables (r:0 w:1) + /// Proof Skipped: Staking Invulnerables (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `v` is `[0, 1000]`. fn set_invulnerables(v: u32, ) -> Weight { - // Minimum execution time: 5_544 nanoseconds. - Weight::from_ref_time(6_513_190) - // Standard Error: 76 - .saturating_add(Weight::from_ref_time(9_975).saturating_mul(v.into())) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Staking Bonded (r:1 w:1) - // Storage: Staking SlashingSpans (r:1 w:0) - // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: VoterList ListNodes (r:2 w:2) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: Staking Ledger (r:0 w:1) - // Storage: Staking Payee (r:0 w:1) - // Storage: Staking SpanSlash (r:0 w:2) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_061_000 picoseconds. + Weight::from_parts(3_618_535, 0) + // Standard Error: 44 + .saturating_add(Weight::from_parts(10_774, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Staking Bonded (r:1 w:1) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking SlashingSpans (r:1 w:1) + /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking Validators (r:1 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:2 w:2) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:0 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:0 w:1) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Staking SpanSlash (r:0 w:100) + /// Proof: Staking SpanSlash (max_values: None, max_size: Some(76), added: 2551, mode: MaxEncodedLen) /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { - // Minimum execution time: 82_414 nanoseconds. - Weight::from_ref_time(88_511_246) - // Standard Error: 2_622 - .saturating_add(Weight::from_ref_time(1_131_814).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(11)) - .saturating_add(T::DbWeight::get().writes(12)) + // Proof Size summary in bytes: + // Measured: `2018 + s * (4 ±0)` + // Estimated: `6248 + s * (4 ±0)` + // Minimum execution time: 87_914_000 picoseconds. + Weight::from_parts(95_688_129, 6248) + // Standard Error: 5_030 + .saturating_add(Weight::from_parts(1_487_249, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(12_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) + .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } - // Storage: Staking UnappliedSlashes (r:1 w:1) + /// Storage: Staking UnappliedSlashes (r:1 w:1) + /// Proof Skipped: Staking UnappliedSlashes (max_values: None, max_size: None, mode: Measured) /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { - // Minimum execution time: 94_197 nanoseconds. - Weight::from_ref_time(903_418_326) - // Standard Error: 59_354 - .saturating_add(Weight::from_ref_time(4_948_354).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(1)) - .saturating_add(T::DbWeight::get().writes(1)) - } - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking ErasValidatorReward (r:1 w:0) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking ErasStakersClipped (r:1 w:0) - // Storage: Staking ErasRewardPoints (r:1 w:0) - // Storage: Staking ErasValidatorPrefs (r:1 w:0) - // Storage: Staking Payee (r:1 w:0) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `66639` + // Estimated: `70104` + // Minimum execution time: 99_269_000 picoseconds. + Weight::from_parts(1_154_264_637, 70104) + // Standard Error: 76_592 + .saturating_add(Weight::from_parts(6_490_888, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ErasValidatorReward (r:1 w:0) + /// Proof: Staking ErasValidatorReward (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:257 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking ErasStakersClipped (r:1 w:0) + /// Proof Skipped: Staking ErasStakersClipped (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking ErasRewardPoints (r:1 w:0) + /// Proof Skipped: Staking ErasRewardPoints (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking ErasValidatorPrefs (r:1 w:0) + /// Proof: Staking ErasValidatorPrefs (max_values: None, max_size: Some(57), added: 2532, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:257 w:0) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: System Account (r:257 w:257) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `n` is `[0, 256]`. fn payout_stakers_dead_controller(n: u32, ) -> Weight { - // Minimum execution time: 133_065 nanoseconds. - Weight::from_ref_time(197_555_906) - // Standard Error: 19_561 - .saturating_add(Weight::from_ref_time(22_683_426).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(9)) + // Proof Size summary in bytes: + // Measured: `20217 + n * (143 ±0)` + // Estimated: `19844 + n * (2603 ±1)` + // Minimum execution time: 91_767_000 picoseconds. + Weight::from_parts(146_781_264, 19844) + // Standard Error: 31_341 + .saturating_add(Weight::from_parts(30_553_008, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(2)) + .saturating_add(T::DbWeight::get().writes(2_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2603).saturating_mul(n.into())) } - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking ErasValidatorReward (r:1 w:0) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking ErasStakersClipped (r:1 w:0) - // Storage: Staking ErasRewardPoints (r:1 w:0) - // Storage: Staking ErasValidatorPrefs (r:1 w:0) - // Storage: Staking Payee (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ErasValidatorReward (r:1 w:0) + /// Proof: Staking ErasValidatorReward (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:257 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:257 w:257) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking ErasStakersClipped (r:1 w:0) + /// Proof Skipped: Staking ErasStakersClipped (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking ErasRewardPoints (r:1 w:0) + /// Proof Skipped: Staking ErasRewardPoints (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking ErasValidatorPrefs (r:1 w:0) + /// Proof: Staking ErasValidatorPrefs (max_values: None, max_size: Some(57), added: 2532, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:257 w:0) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: System Account (r:257 w:257) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:257 w:257) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:257 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { - // Minimum execution time: 164_719 nanoseconds. - Weight::from_ref_time(226_304_276) - // Standard Error: 31_675 - .saturating_add(Weight::from_ref_time(32_622_427).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(10)) - .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `33190 + n * (377 ±0)` + // Estimated: `30845 + n * (3774 ±0)` + // Minimum execution time: 121_303_000 picoseconds. + Weight::from_parts(151_046_907, 30845) + // Standard Error: 41_899 + .saturating_add(Weight::from_parts(49_837_804, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(11_u64)) + .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(3_u64)) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 3774).saturating_mul(n.into())) } - // Storage: Staking Ledger (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: VoterList ListNodes (r:3 w:3) - // Storage: Staking Bonded (r:1 w:0) - // Storage: VoterList ListBags (r:2 w:2) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:3 w:3) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:2 w:2) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { - // Minimum execution time: 95_631 nanoseconds. - Weight::from_ref_time(96_861_556) - // Standard Error: 2_114 - .saturating_add(Weight::from_ref_time(37_543).saturating_mul(l.into())) - .saturating_add(T::DbWeight::get().reads(9)) - .saturating_add(T::DbWeight::get().writes(8)) - } - // Storage: System Account (r:1 w:1) - // Storage: Staking Bonded (r:1 w:1) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking SlashingSpans (r:1 w:1) - // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: VoterList ListNodes (r:2 w:2) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: Staking Payee (r:0 w:1) - // Storage: Staking SpanSlash (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `2029 + l * (7 ±0)` + // Estimated: `8877` + // Minimum execution time: 90_068_000 picoseconds. + Weight::from_parts(93_137_456, 8877) + // Standard Error: 4_799 + .saturating_add(Weight::from_parts(54_421, 0).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().reads(9_u64)) + .saturating_add(T::DbWeight::get().writes(7_u64)) + } + /// Storage: Staking Bonded (r:1 w:1) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking SlashingSpans (r:1 w:1) + /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking Validators (r:1 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:2 w:2) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:0 w:1) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Staking SpanSlash (r:0 w:100) + /// Proof: Staking SpanSlash (max_values: None, max_size: Some(76), added: 2551, mode: MaxEncodedLen) /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { - // Minimum execution time: 95_251 nanoseconds. - Weight::from_ref_time(97_818_954) - // Standard Error: 2_356 - .saturating_add(Weight::from_ref_time(1_104_695).saturating_mul(s.into())) - .saturating_add(T::DbWeight::get().reads(12)) - .saturating_add(T::DbWeight::get().writes(12)) + // Proof Size summary in bytes: + // Measured: `2294 + s * (4 ±0)` + // Estimated: `6248 + s * (4 ±0)` + // Minimum execution time: 103_139_000 picoseconds. + Weight::from_parts(107_036_296, 6248) + // Standard Error: 3_935 + .saturating_add(Weight::from_parts(1_465_860, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(12_u64)) + .saturating_add(T::DbWeight::get().writes(11_u64)) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(s.into()))) + .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } - // Storage: VoterList CounterForListNodes (r:1 w:0) - // Storage: VoterList ListBags (r:200 w:0) - // Storage: VoterList ListNodes (r:101 w:0) - // Storage: Staking Nominators (r:101 w:0) - // Storage: Staking Validators (r:2 w:0) - // Storage: Staking Bonded (r:101 w:0) - // Storage: Staking Ledger (r:101 w:0) - // Storage: Staking CounterForValidators (r:1 w:0) - // Storage: Staking ValidatorCount (r:1 w:0) - // Storage: Staking MinimumValidatorCount (r:1 w:0) - // Storage: Staking CurrentEra (r:1 w:1) - // Storage: Staking ErasStakersClipped (r:0 w:1) - // Storage: Staking ErasValidatorPrefs (r:0 w:1) - // Storage: Staking ErasStakers (r:0 w:1) - // Storage: Staking ErasTotalStake (r:0 w:1) - // Storage: Staking ErasStartSessionIndex (r:0 w:1) - // Storage: Staking MinimumActiveStake (r:0 w:1) + /// Storage: VoterList CounterForListNodes (r:1 w:0) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:200 w:0) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:110 w:0) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:110 w:0) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:11 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:110 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:110 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking CounterForValidators (r:1 w:0) + /// Proof: Staking CounterForValidators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ValidatorCount (r:1 w:0) + /// Proof: Staking ValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking MinimumValidatorCount (r:1 w:0) + /// Proof: Staking MinimumValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:1) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ErasStakersClipped (r:0 w:10) + /// Proof Skipped: Staking ErasStakersClipped (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking ErasValidatorPrefs (r:0 w:10) + /// Proof: Staking ErasValidatorPrefs (max_values: None, max_size: Some(57), added: 2532, mode: MaxEncodedLen) + /// Storage: Staking ErasStakers (r:0 w:10) + /// Proof Skipped: Staking ErasStakers (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking ErasTotalStake (r:0 w:1) + /// Proof: Staking ErasTotalStake (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) + /// Storage: Staking ErasStartSessionIndex (r:0 w:1) + /// Proof: Staking ErasStartSessionIndex (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) + /// Storage: Staking MinimumActiveStake (r:0 w:1) + /// Proof: Staking MinimumActiveStake (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) /// The range of component `v` is `[1, 10]`. /// The range of component `n` is `[0, 100]`. fn new_era(v: u32, n: u32, ) -> Weight { - // Minimum execution time: 512_923 nanoseconds. - Weight::from_ref_time(514_740_000) - // Standard Error: 1_790_238 - .saturating_add(Weight::from_ref_time(59_320_539).saturating_mul(v.into())) - // Standard Error: 178_387 - .saturating_add(Weight::from_ref_time(13_902_705).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(206)) + // Proof Size summary in bytes: + // Measured: `0 + n * (720 ±0) + v * (3598 ±0)` + // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` + // Minimum execution time: 587_156_000 picoseconds. + Weight::from_parts(590_176_000, 512390) + // Standard Error: 2_008_420 + .saturating_add(Weight::from_parts(64_526_052, 0).saturating_mul(v.into())) + // Standard Error: 200_128 + .saturating_add(Weight::from_parts(18_070_222, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(206_u64)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes(4_u64)) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(v.into()))) + .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(0, 3566).saturating_mul(v.into())) } - // Storage: VoterList CounterForListNodes (r:1 w:0) - // Storage: VoterList ListBags (r:200 w:0) - // Storage: VoterList ListNodes (r:1500 w:0) - // Storage: Staking Nominators (r:1500 w:0) - // Storage: Staking Validators (r:500 w:0) - // Storage: Staking Bonded (r:1500 w:0) - // Storage: Staking Ledger (r:1500 w:0) - // Storage: Staking MinimumActiveStake (r:0 w:1) + /// Storage: VoterList CounterForListNodes (r:1 w:0) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:200 w:0) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:2000 w:0) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:2000 w:0) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:1000 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:2000 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:2000 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking MinimumActiveStake (r:0 w:1) + /// Proof: Staking MinimumActiveStake (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) /// The range of component `v` is `[500, 1000]`. /// The range of component `n` is `[500, 1000]`. fn get_npos_voters(v: u32, n: u32, ) -> Weight { - // Minimum execution time: 24_913_316 nanoseconds. - Weight::from_ref_time(25_053_596_000) - // Standard Error: 324_610 - .saturating_add(Weight::from_ref_time(3_454_859).saturating_mul(v.into())) - // Standard Error: 324_610 - .saturating_add(Weight::from_ref_time(3_020_267).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(201)) + // Proof Size summary in bytes: + // Measured: `3217 + n * (911 ±0) + v * (395 ±0)` + // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` + // Minimum execution time: 34_399_721_000 picoseconds. + Weight::from_parts(34_605_803_000, 512390) + // Standard Error: 380_106 + .saturating_add(Weight::from_parts(5_426_220, 0).saturating_mul(v.into())) + // Standard Error: 380_106 + .saturating_add(Weight::from_parts(3_318_197, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(201_u64)) .saturating_add(T::DbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(0, 3566).saturating_mul(v.into())) } - // Storage: Staking CounterForValidators (r:1 w:0) - // Storage: Staking Validators (r:501 w:0) + /// Storage: Staking CounterForValidators (r:1 w:0) + /// Proof: Staking CounterForValidators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:1001 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { - // Minimum execution time: 4_916_401 nanoseconds. - Weight::from_ref_time(81_160_966) - // Standard Error: 23_829 - .saturating_add(Weight::from_ref_time(9_883_413).saturating_mul(v.into())) - .saturating_add(T::DbWeight::get().reads(2)) + // Proof Size summary in bytes: + // Measured: `983 + v * (50 ±0)` + // Estimated: `3510 + v * (2520 ±0)` + // Minimum execution time: 2_392_849_000 picoseconds. + Weight::from_parts(64_373_879, 3510) + // Standard Error: 8_995 + .saturating_add(Weight::from_parts(4_721_536, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(v.into()))) + .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) } - // Storage: Staking MinCommission (r:0 w:1) - // Storage: Staking MinValidatorBond (r:0 w:1) - // Storage: Staking MaxValidatorsCount (r:0 w:1) - // Storage: Staking ChillThreshold (r:0 w:1) - // Storage: Staking MaxNominatorsCount (r:0 w:1) - // Storage: Staking MinNominatorBond (r:0 w:1) + /// Storage: Staking MinCommission (r:0 w:1) + /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking MinValidatorBond (r:0 w:1) + /// Proof: Staking MinValidatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Staking MaxValidatorsCount (r:0 w:1) + /// Proof: Staking MaxValidatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ChillThreshold (r:0 w:1) + /// Proof: Staking ChillThreshold (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: Staking MaxNominatorsCount (r:0 w:1) + /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking MinNominatorBond (r:0 w:1) + /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) fn set_staking_configs_all_set() -> Weight { - // Minimum execution time: 10_937 nanoseconds. - Weight::from_ref_time(11_324_000) - .saturating_add(T::DbWeight::get().writes(6)) - } - // Storage: Staking MinCommission (r:0 w:1) - // Storage: Staking MinValidatorBond (r:0 w:1) - // Storage: Staking MaxValidatorsCount (r:0 w:1) - // Storage: Staking ChillThreshold (r:0 w:1) - // Storage: Staking MaxNominatorsCount (r:0 w:1) - // Storage: Staking MinNominatorBond (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_529_000 picoseconds. + Weight::from_parts(7_970_000, 0) + .saturating_add(T::DbWeight::get().writes(6_u64)) + } + /// Storage: Staking MinCommission (r:0 w:1) + /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking MinValidatorBond (r:0 w:1) + /// Proof: Staking MinValidatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Staking MaxValidatorsCount (r:0 w:1) + /// Proof: Staking MaxValidatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ChillThreshold (r:0 w:1) + /// Proof: Staking ChillThreshold (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: Staking MaxNominatorsCount (r:0 w:1) + /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking MinNominatorBond (r:0 w:1) + /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) fn set_staking_configs_all_remove() -> Weight { - // Minimum execution time: 9_424 nanoseconds. - Weight::from_ref_time(10_021_000) - .saturating_add(T::DbWeight::get().writes(6)) - } - // Storage: Staking Ledger (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking ChillThreshold (r:1 w:0) - // Storage: Staking MaxNominatorsCount (r:1 w:0) - // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: Staking MinNominatorBond (r:1 w:0) - // Storage: Staking Validators (r:1 w:0) - // Storage: VoterList ListNodes (r:2 w:2) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_011_000 picoseconds. + Weight::from_parts(7_317_000, 0) + .saturating_add(T::DbWeight::get().writes(6_u64)) + } + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking ChillThreshold (r:1 w:0) + /// Proof: Staking ChillThreshold (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: Staking MaxNominatorsCount (r:1 w:0) + /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking MinNominatorBond (r:1 w:0) + /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:1 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:2 w:2) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn chill_other() -> Weight { - // Minimum execution time: 84_495 nanoseconds. - Weight::from_ref_time(85_559_000) - .saturating_add(T::DbWeight::get().reads(11)) - .saturating_add(T::DbWeight::get().writes(6)) + // Proof Size summary in bytes: + // Measured: `1871` + // Estimated: `6248` + // Minimum execution time: 75_982_000 picoseconds. + Weight::from_parts(77_412_000, 6248) + .saturating_add(T::DbWeight::get().reads(11_u64)) + .saturating_add(T::DbWeight::get().writes(6_u64)) } - // Storage: Staking MinCommission (r:1 w:0) - // Storage: Staking Validators (r:1 w:1) + /// Storage: Staking MinCommission (r:1 w:0) + /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:1 w:1) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) fn force_apply_min_commission() -> Weight { - // Minimum execution time: 20_385 nanoseconds. - Weight::from_ref_time(20_824_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `694` + // Estimated: `3510` + // Minimum execution time: 13_923_000 picoseconds. + Weight::from_parts(14_356_000, 3510) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Staking MinCommission (r:0 w:1) + /// Storage: Staking MinCommission (r:0 w:1) + /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn set_min_commission() -> Weight { - // Minimum execution time: 6_995 nanoseconds. - Weight::from_ref_time(7_213_000) - .saturating_add(T::DbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_415_000 picoseconds. + Weight::from_parts(3_679_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Staking Bonded (r:1 w:1) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Balances Locks (r:1 w:1) - // Storage: Staking Payee (r:0 w:1) + /// Storage: Staking Bonded (r:1 w:1) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:0 w:1) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) fn bond() -> Weight { - // Minimum execution time: 54_884 nanoseconds. - Weight::from_ref_time(55_487_000) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(4)) - } - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: VoterList ListNodes (r:3 w:3) - // Storage: VoterList ListBags (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `1047` + // Estimated: `4764` + // Minimum execution time: 53_983_000 picoseconds. + Weight::from_parts(55_296_000, 4764) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:3 w:3) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:2 w:2) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) fn bond_extra() -> Weight { - // Minimum execution time: 95_115 nanoseconds. - Weight::from_ref_time(96_213_000) - .saturating_add(RocksDbWeight::get().reads(8)) - .saturating_add(RocksDbWeight::get().writes(7)) - } - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking Nominators (r:1 w:0) - // Storage: Staking MinNominatorBond (r:1 w:0) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: VoterList ListNodes (r:3 w:3) - // Storage: Staking Bonded (r:1 w:0) - // Storage: VoterList ListBags (r:2 w:2) + // Proof Size summary in bytes: + // Measured: `2028` + // Estimated: `8877` + // Minimum execution time: 96_590_000 picoseconds. + Weight::from_parts(98_921_000, 8877) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) + } + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:0) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking MinNominatorBond (r:1 w:0) + /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:3 w:3) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:2 w:2) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) fn unbond() -> Weight { - // Minimum execution time: 102_031 nanoseconds. - Weight::from_ref_time(102_842_000) - .saturating_add(RocksDbWeight::get().reads(12)) - .saturating_add(RocksDbWeight::get().writes(8)) - } - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `2233` + // Estimated: `8877` + // Minimum execution time: 99_901_000 picoseconds. + Weight::from_parts(102_919_000, 8877) + .saturating_add(RocksDbWeight::get().reads(12_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) + } + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_update(s: u32, ) -> Weight { - // Minimum execution time: 46_569 nanoseconds. - Weight::from_ref_time(48_034_493) - // Standard Error: 654 - .saturating_add(Weight::from_ref_time(63_628).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(4)) - .saturating_add(RocksDbWeight::get().writes(3)) - } - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking Bonded (r:1 w:1) - // Storage: Staking SlashingSpans (r:1 w:0) - // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: VoterList ListNodes (r:2 w:2) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: Staking Payee (r:0 w:1) - // Storage: Staking SpanSlash (r:0 w:2) + // Proof Size summary in bytes: + // Measured: `1021` + // Estimated: `4764` + // Minimum execution time: 45_230_000 picoseconds. + Weight::from_parts(47_052_829, 4764) + // Standard Error: 1_044 + .saturating_add(Weight::from_parts(43_887, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:1) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking SlashingSpans (r:1 w:1) + /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking Validators (r:1 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:2 w:2) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:0 w:1) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Staking SpanSlash (r:0 w:100) + /// Proof: Staking SpanSlash (max_values: None, max_size: Some(76), added: 2551, mode: MaxEncodedLen) /// The range of component `s` is `[0, 100]`. fn withdraw_unbonded_kill(s: u32, ) -> Weight { - // Minimum execution time: 90_154 nanoseconds. - Weight::from_ref_time(95_725_631) - // Standard Error: 2_491 - .saturating_add(Weight::from_ref_time(1_110_795).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(13)) - .saturating_add(RocksDbWeight::get().writes(12)) + // Proof Size summary in bytes: + // Measured: `2294 + s * (4 ±0)` + // Estimated: `6248 + s * (4 ±0)` + // Minimum execution time: 97_534_000 picoseconds. + Weight::from_parts(104_772_163, 6248) + // Standard Error: 3_674 + .saturating_add(Weight::from_parts(1_470_124, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(13_u64)) + .saturating_add(RocksDbWeight::get().writes(11_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) + .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } - // Storage: Staking Ledger (r:1 w:0) - // Storage: Staking MinValidatorBond (r:1 w:0) - // Storage: Staking MinCommission (r:1 w:0) - // Storage: Staking Validators (r:1 w:1) - // Storage: Staking MaxValidatorsCount (r:1 w:0) - // Storage: Staking Nominators (r:1 w:0) - // Storage: Staking Bonded (r:1 w:0) - // Storage: VoterList ListNodes (r:1 w:1) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) - // Storage: Staking CounterForValidators (r:1 w:1) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking MinValidatorBond (r:1 w:0) + /// Proof: Staking MinValidatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Staking MinCommission (r:1 w:0) + /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:1 w:1) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking MaxValidatorsCount (r:1 w:0) + /// Proof: Staking MaxValidatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:0) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:1 w:1) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking CounterForValidators (r:1 w:1) + /// Proof: Staking CounterForValidators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn validate() -> Weight { - // Minimum execution time: 67_978 nanoseconds. - Weight::from_ref_time(69_153_000) - .saturating_add(RocksDbWeight::get().reads(11)) - .saturating_add(RocksDbWeight::get().writes(5)) + // Proof Size summary in bytes: + // Measured: `1414` + // Estimated: `4556` + // Minimum execution time: 57_467_000 picoseconds. + Weight::from_parts(59_437_000, 4556) + .saturating_add(RocksDbWeight::get().reads(11_u64)) + .saturating_add(RocksDbWeight::get().writes(5_u64)) } - // Storage: Staking Ledger (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:128 w:128) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) /// The range of component `k` is `[1, 128]`. fn kick(k: u32, ) -> Weight { - // Minimum execution time: 45_328 nanoseconds. - Weight::from_ref_time(47_719_103) - // Standard Error: 14_458 - .saturating_add(Weight::from_ref_time(6_999_252).saturating_mul(k.into())) - .saturating_add(RocksDbWeight::get().reads(1)) + // Proof Size summary in bytes: + // Measured: `1285 + k * (569 ±0)` + // Estimated: `4556 + k * (3033 ±0)` + // Minimum execution time: 32_857_000 picoseconds. + Weight::from_parts(37_116_967, 4556) + // Standard Error: 9_522 + .saturating_add(Weight::from_parts(8_796_167, 0).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 3033).saturating_mul(k.into())) } - // Storage: Staking Ledger (r:1 w:0) - // Storage: Staking MinNominatorBond (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking MaxNominatorsCount (r:1 w:0) - // Storage: Staking Validators (r:2 w:0) - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking Bonded (r:1 w:0) - // Storage: VoterList ListNodes (r:2 w:2) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking MinNominatorBond (r:1 w:0) + /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking MaxNominatorsCount (r:1 w:0) + /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:17 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:2 w:2) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) /// The range of component `n` is `[1, 16]`. fn nominate(n: u32, ) -> Weight { - // Minimum execution time: 74_650 nanoseconds. - Weight::from_ref_time(74_350_075) - // Standard Error: 10_527 - .saturating_add(Weight::from_ref_time(2_878_737).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(12)) + // Proof Size summary in bytes: + // Measured: `1908 + n * (102 ±0)` + // Estimated: `6248 + n * (2520 ±0)` + // Minimum execution time: 69_613_000 picoseconds. + Weight::from_parts(68_079_061, 6248) + // Standard Error: 18_554 + .saturating_add(Weight::from_parts(4_012_761, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(6)) - } - // Storage: Staking Ledger (r:1 w:0) - // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: VoterList ListNodes (r:2 w:2) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + .saturating_add(Weight::from_parts(0, 2520).saturating_mul(n.into())) + } + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:1 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:2 w:2) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn chill() -> Weight { - // Minimum execution time: 67_790 nanoseconds. - Weight::from_ref_time(68_738_000) - .saturating_add(RocksDbWeight::get().reads(8)) - .saturating_add(RocksDbWeight::get().writes(6)) + // Proof Size summary in bytes: + // Measured: `1748` + // Estimated: `6248` + // Minimum execution time: 60_430_000 picoseconds. + Weight::from_parts(62_702_000, 6248) + .saturating_add(RocksDbWeight::get().reads(8_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } - // Storage: Staking Ledger (r:1 w:0) - // Storage: Staking Payee (r:0 w:1) + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:0 w:1) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) fn set_payee() -> Weight { - // Minimum execution time: 19_237 nanoseconds. - Weight::from_ref_time(19_534_000) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `808` + // Estimated: `4556` + // Minimum execution time: 14_276_000 picoseconds. + Weight::from_parts(14_766_000, 4556) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Staking Bonded (r:1 w:1) - // Storage: Staking Ledger (r:2 w:2) + /// Storage: Staking Bonded (r:1 w:1) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:2 w:2) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) fn set_controller() -> Weight { - // Minimum execution time: 27_288 nanoseconds. - Weight::from_ref_time(27_667_000) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `907` + // Estimated: `8122` + // Minimum execution time: 21_710_000 picoseconds. + Weight::from_parts(22_430_000, 8122) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } - // Storage: Staking ValidatorCount (r:0 w:1) + /// Storage: Staking ValidatorCount (r:0 w:1) + /// Proof: Staking ValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn set_validator_count() -> Weight { - // Minimum execution time: 5_155 nanoseconds. - Weight::from_ref_time(5_464_000) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_970_000 picoseconds. + Weight::from_parts(3_120_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Staking ForceEra (r:0 w:1) + /// Storage: Staking ForceEra (r:0 w:1) + /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) fn force_no_eras() -> Weight { - // Minimum execution time: 5_405 nanoseconds. - Weight::from_ref_time(5_670_000) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 9_362_000 picoseconds. + Weight::from_parts(9_785_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Staking ForceEra (r:0 w:1) + /// Storage: Staking ForceEra (r:0 w:1) + /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) fn force_new_era() -> Weight { - // Minimum execution time: 5_459 nanoseconds. - Weight::from_ref_time(5_616_000) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 9_275_000 picoseconds. + Weight::from_parts(9_678_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Staking ForceEra (r:0 w:1) + /// Storage: Staking ForceEra (r:0 w:1) + /// Proof: Staking ForceEra (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) fn force_new_era_always() -> Weight { - // Minimum execution time: 5_476 nanoseconds. - Weight::from_ref_time(5_692_000) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 9_414_000 picoseconds. + Weight::from_parts(9_848_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Staking Invulnerables (r:0 w:1) + /// Storage: Staking Invulnerables (r:0 w:1) + /// Proof Skipped: Staking Invulnerables (max_values: Some(1), max_size: None, mode: Measured) /// The range of component `v` is `[0, 1000]`. fn set_invulnerables(v: u32, ) -> Weight { - // Minimum execution time: 5_544 nanoseconds. - Weight::from_ref_time(6_513_190) - // Standard Error: 76 - .saturating_add(Weight::from_ref_time(9_975).saturating_mul(v.into())) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Staking Bonded (r:1 w:1) - // Storage: Staking SlashingSpans (r:1 w:0) - // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: VoterList ListNodes (r:2 w:2) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: Staking Ledger (r:0 w:1) - // Storage: Staking Payee (r:0 w:1) - // Storage: Staking SpanSlash (r:0 w:2) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_061_000 picoseconds. + Weight::from_parts(3_618_535, 0) + // Standard Error: 44 + .saturating_add(Weight::from_parts(10_774, 0).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Staking Bonded (r:1 w:1) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking SlashingSpans (r:1 w:1) + /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking Validators (r:1 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:2 w:2) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:0 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:0 w:1) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Staking SpanSlash (r:0 w:100) + /// Proof: Staking SpanSlash (max_values: None, max_size: Some(76), added: 2551, mode: MaxEncodedLen) /// The range of component `s` is `[0, 100]`. fn force_unstake(s: u32, ) -> Weight { - // Minimum execution time: 82_414 nanoseconds. - Weight::from_ref_time(88_511_246) - // Standard Error: 2_622 - .saturating_add(Weight::from_ref_time(1_131_814).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(11)) - .saturating_add(RocksDbWeight::get().writes(12)) + // Proof Size summary in bytes: + // Measured: `2018 + s * (4 ±0)` + // Estimated: `6248 + s * (4 ±0)` + // Minimum execution time: 87_914_000 picoseconds. + Weight::from_parts(95_688_129, 6248) + // Standard Error: 5_030 + .saturating_add(Weight::from_parts(1_487_249, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(12_u64)) + .saturating_add(RocksDbWeight::get().writes(12_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) + .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } - // Storage: Staking UnappliedSlashes (r:1 w:1) + /// Storage: Staking UnappliedSlashes (r:1 w:1) + /// Proof Skipped: Staking UnappliedSlashes (max_values: None, max_size: None, mode: Measured) /// The range of component `s` is `[1, 1000]`. fn cancel_deferred_slash(s: u32, ) -> Weight { - // Minimum execution time: 94_197 nanoseconds. - Weight::from_ref_time(903_418_326) - // Standard Error: 59_354 - .saturating_add(Weight::from_ref_time(4_948_354).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(1)) - .saturating_add(RocksDbWeight::get().writes(1)) - } - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking ErasValidatorReward (r:1 w:0) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking ErasStakersClipped (r:1 w:0) - // Storage: Staking ErasRewardPoints (r:1 w:0) - // Storage: Staking ErasValidatorPrefs (r:1 w:0) - // Storage: Staking Payee (r:1 w:0) - // Storage: System Account (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `66639` + // Estimated: `70104` + // Minimum execution time: 99_269_000 picoseconds. + Weight::from_parts(1_154_264_637, 70104) + // Standard Error: 76_592 + .saturating_add(Weight::from_parts(6_490_888, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ErasValidatorReward (r:1 w:0) + /// Proof: Staking ErasValidatorReward (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:257 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking ErasStakersClipped (r:1 w:0) + /// Proof Skipped: Staking ErasStakersClipped (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking ErasRewardPoints (r:1 w:0) + /// Proof Skipped: Staking ErasRewardPoints (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking ErasValidatorPrefs (r:1 w:0) + /// Proof: Staking ErasValidatorPrefs (max_values: None, max_size: Some(57), added: 2532, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:257 w:0) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: System Account (r:257 w:257) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `n` is `[0, 256]`. fn payout_stakers_dead_controller(n: u32, ) -> Weight { - // Minimum execution time: 133_065 nanoseconds. - Weight::from_ref_time(197_555_906) - // Standard Error: 19_561 - .saturating_add(Weight::from_ref_time(22_683_426).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(9)) + // Proof Size summary in bytes: + // Measured: `20217 + n * (143 ±0)` + // Estimated: `19844 + n * (2603 ±1)` + // Minimum execution time: 91_767_000 picoseconds. + Weight::from_parts(146_781_264, 19844) + // Standard Error: 31_341 + .saturating_add(Weight::from_parts(30_553_008, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(2)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 2603).saturating_mul(n.into())) } - // Storage: Staking CurrentEra (r:1 w:0) - // Storage: Staking ErasValidatorReward (r:1 w:0) - // Storage: Staking Bonded (r:1 w:0) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking ErasStakersClipped (r:1 w:0) - // Storage: Staking ErasRewardPoints (r:1 w:0) - // Storage: Staking ErasValidatorPrefs (r:1 w:0) - // Storage: Staking Payee (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Staking CurrentEra (r:1 w:0) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ErasValidatorReward (r:1 w:0) + /// Proof: Staking ErasValidatorReward (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:257 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:257 w:257) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking ErasStakersClipped (r:1 w:0) + /// Proof Skipped: Staking ErasStakersClipped (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking ErasRewardPoints (r:1 w:0) + /// Proof Skipped: Staking ErasRewardPoints (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking ErasValidatorPrefs (r:1 w:0) + /// Proof: Staking ErasValidatorPrefs (max_values: None, max_size: Some(57), added: 2532, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:257 w:0) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: System Account (r:257 w:257) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:257 w:257) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:257 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `n` is `[0, 256]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { - // Minimum execution time: 164_719 nanoseconds. - Weight::from_ref_time(226_304_276) - // Standard Error: 31_675 - .saturating_add(Weight::from_ref_time(32_622_427).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(10)) - .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(3)) + // Proof Size summary in bytes: + // Measured: `33190 + n * (377 ±0)` + // Estimated: `30845 + n * (3774 ±0)` + // Minimum execution time: 121_303_000 picoseconds. + Weight::from_parts(151_046_907, 30845) + // Standard Error: 41_899 + .saturating_add(Weight::from_parts(49_837_804, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(11_u64)) + .saturating_add(RocksDbWeight::get().reads((6_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 3774).saturating_mul(n.into())) } - // Storage: Staking Ledger (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: VoterList ListNodes (r:3 w:3) - // Storage: Staking Bonded (r:1 w:0) - // Storage: VoterList ListBags (r:2 w:2) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:3 w:3) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:1 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:2 w:2) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) /// The range of component `l` is `[1, 32]`. fn rebond(l: u32, ) -> Weight { - // Minimum execution time: 95_631 nanoseconds. - Weight::from_ref_time(96_861_556) - // Standard Error: 2_114 - .saturating_add(Weight::from_ref_time(37_543).saturating_mul(l.into())) - .saturating_add(RocksDbWeight::get().reads(9)) - .saturating_add(RocksDbWeight::get().writes(8)) - } - // Storage: System Account (r:1 w:1) - // Storage: Staking Bonded (r:1 w:1) - // Storage: Staking Ledger (r:1 w:1) - // Storage: Staking SlashingSpans (r:1 w:1) - // Storage: Staking Validators (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: VoterList ListNodes (r:2 w:2) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: Staking Payee (r:0 w:1) - // Storage: Staking SpanSlash (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `2029 + l * (7 ±0)` + // Estimated: `8877` + // Minimum execution time: 90_068_000 picoseconds. + Weight::from_parts(93_137_456, 8877) + // Standard Error: 4_799 + .saturating_add(Weight::from_parts(54_421, 0).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().reads(9_u64)) + .saturating_add(RocksDbWeight::get().writes(7_u64)) + } + /// Storage: Staking Bonded (r:1 w:1) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:1 w:1) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking SlashingSpans (r:1 w:1) + /// Proof Skipped: Staking SlashingSpans (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking Validators (r:1 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:2 w:2) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: Staking Payee (r:0 w:1) + /// Proof: Staking Payee (max_values: None, max_size: Some(73), added: 2548, mode: MaxEncodedLen) + /// Storage: Staking SpanSlash (r:0 w:100) + /// Proof: Staking SpanSlash (max_values: None, max_size: Some(76), added: 2551, mode: MaxEncodedLen) /// The range of component `s` is `[1, 100]`. fn reap_stash(s: u32, ) -> Weight { - // Minimum execution time: 95_251 nanoseconds. - Weight::from_ref_time(97_818_954) - // Standard Error: 2_356 - .saturating_add(Weight::from_ref_time(1_104_695).saturating_mul(s.into())) - .saturating_add(RocksDbWeight::get().reads(12)) - .saturating_add(RocksDbWeight::get().writes(12)) + // Proof Size summary in bytes: + // Measured: `2294 + s * (4 ±0)` + // Estimated: `6248 + s * (4 ±0)` + // Minimum execution time: 103_139_000 picoseconds. + Weight::from_parts(107_036_296, 6248) + // Standard Error: 3_935 + .saturating_add(Weight::from_parts(1_465_860, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(12_u64)) + .saturating_add(RocksDbWeight::get().writes(11_u64)) .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(s.into()))) + .saturating_add(Weight::from_parts(0, 4).saturating_mul(s.into())) } - // Storage: VoterList CounterForListNodes (r:1 w:0) - // Storage: VoterList ListBags (r:200 w:0) - // Storage: VoterList ListNodes (r:101 w:0) - // Storage: Staking Nominators (r:101 w:0) - // Storage: Staking Validators (r:2 w:0) - // Storage: Staking Bonded (r:101 w:0) - // Storage: Staking Ledger (r:101 w:0) - // Storage: Staking CounterForValidators (r:1 w:0) - // Storage: Staking ValidatorCount (r:1 w:0) - // Storage: Staking MinimumValidatorCount (r:1 w:0) - // Storage: Staking CurrentEra (r:1 w:1) - // Storage: Staking ErasStakersClipped (r:0 w:1) - // Storage: Staking ErasValidatorPrefs (r:0 w:1) - // Storage: Staking ErasStakers (r:0 w:1) - // Storage: Staking ErasTotalStake (r:0 w:1) - // Storage: Staking ErasStartSessionIndex (r:0 w:1) - // Storage: Staking MinimumActiveStake (r:0 w:1) + /// Storage: VoterList CounterForListNodes (r:1 w:0) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:200 w:0) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:110 w:0) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:110 w:0) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:11 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:110 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:110 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking CounterForValidators (r:1 w:0) + /// Proof: Staking CounterForValidators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ValidatorCount (r:1 w:0) + /// Proof: Staking ValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking MinimumValidatorCount (r:1 w:0) + /// Proof: Staking MinimumValidatorCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking CurrentEra (r:1 w:1) + /// Proof: Staking CurrentEra (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ErasStakersClipped (r:0 w:10) + /// Proof Skipped: Staking ErasStakersClipped (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking ErasValidatorPrefs (r:0 w:10) + /// Proof: Staking ErasValidatorPrefs (max_values: None, max_size: Some(57), added: 2532, mode: MaxEncodedLen) + /// Storage: Staking ErasStakers (r:0 w:10) + /// Proof Skipped: Staking ErasStakers (max_values: None, max_size: None, mode: Measured) + /// Storage: Staking ErasTotalStake (r:0 w:1) + /// Proof: Staking ErasTotalStake (max_values: None, max_size: Some(28), added: 2503, mode: MaxEncodedLen) + /// Storage: Staking ErasStartSessionIndex (r:0 w:1) + /// Proof: Staking ErasStartSessionIndex (max_values: None, max_size: Some(16), added: 2491, mode: MaxEncodedLen) + /// Storage: Staking MinimumActiveStake (r:0 w:1) + /// Proof: Staking MinimumActiveStake (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) /// The range of component `v` is `[1, 10]`. /// The range of component `n` is `[0, 100]`. fn new_era(v: u32, n: u32, ) -> Weight { - // Minimum execution time: 512_923 nanoseconds. - Weight::from_ref_time(514_740_000) - // Standard Error: 1_790_238 - .saturating_add(Weight::from_ref_time(59_320_539).saturating_mul(v.into())) - // Standard Error: 178_387 - .saturating_add(Weight::from_ref_time(13_902_705).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(206)) + // Proof Size summary in bytes: + // Measured: `0 + n * (720 ±0) + v * (3598 ±0)` + // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` + // Minimum execution time: 587_156_000 picoseconds. + Weight::from_parts(590_176_000, 512390) + // Standard Error: 2_008_420 + .saturating_add(Weight::from_parts(64_526_052, 0).saturating_mul(v.into())) + // Standard Error: 200_128 + .saturating_add(Weight::from_parts(18_070_222, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(206_u64)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(4)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(v.into()))) + .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(0, 3566).saturating_mul(v.into())) } - // Storage: VoterList CounterForListNodes (r:1 w:0) - // Storage: VoterList ListBags (r:200 w:0) - // Storage: VoterList ListNodes (r:1500 w:0) - // Storage: Staking Nominators (r:1500 w:0) - // Storage: Staking Validators (r:500 w:0) - // Storage: Staking Bonded (r:1500 w:0) - // Storage: Staking Ledger (r:1500 w:0) - // Storage: Staking MinimumActiveStake (r:0 w:1) + /// Storage: VoterList CounterForListNodes (r:1 w:0) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:200 w:0) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:2000 w:0) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:2000 w:0) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:1000 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: Staking Bonded (r:2000 w:0) + /// Proof: Staking Bonded (max_values: None, max_size: Some(72), added: 2547, mode: MaxEncodedLen) + /// Storage: Staking Ledger (r:2000 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking MinimumActiveStake (r:0 w:1) + /// Proof: Staking MinimumActiveStake (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) /// The range of component `v` is `[500, 1000]`. /// The range of component `n` is `[500, 1000]`. fn get_npos_voters(v: u32, n: u32, ) -> Weight { - // Minimum execution time: 24_913_316 nanoseconds. - Weight::from_ref_time(25_053_596_000) - // Standard Error: 324_610 - .saturating_add(Weight::from_ref_time(3_454_859).saturating_mul(v.into())) - // Standard Error: 324_610 - .saturating_add(Weight::from_ref_time(3_020_267).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(201)) + // Proof Size summary in bytes: + // Measured: `3217 + n * (911 ±0) + v * (395 ±0)` + // Estimated: `512390 + n * (3566 ±0) + v * (3566 ±0)` + // Minimum execution time: 34_399_721_000 picoseconds. + Weight::from_parts(34_605_803_000, 512390) + // Standard Error: 380_106 + .saturating_add(Weight::from_parts(5_426_220, 0).saturating_mul(v.into())) + // Standard Error: 380_106 + .saturating_add(Weight::from_parts(3_318_197, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(201_u64)) .saturating_add(RocksDbWeight::get().reads((5_u64).saturating_mul(v.into()))) .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(n.into()))) - .saturating_add(RocksDbWeight::get().writes(1)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 3566).saturating_mul(n.into())) + .saturating_add(Weight::from_parts(0, 3566).saturating_mul(v.into())) } - // Storage: Staking CounterForValidators (r:1 w:0) - // Storage: Staking Validators (r:501 w:0) + /// Storage: Staking CounterForValidators (r:1 w:0) + /// Proof: Staking CounterForValidators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:1001 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) /// The range of component `v` is `[500, 1000]`. fn get_npos_targets(v: u32, ) -> Weight { - // Minimum execution time: 4_916_401 nanoseconds. - Weight::from_ref_time(81_160_966) - // Standard Error: 23_829 - .saturating_add(Weight::from_ref_time(9_883_413).saturating_mul(v.into())) - .saturating_add(RocksDbWeight::get().reads(2)) + // Proof Size summary in bytes: + // Measured: `983 + v * (50 ±0)` + // Estimated: `3510 + v * (2520 ±0)` + // Minimum execution time: 2_392_849_000 picoseconds. + Weight::from_parts(64_373_879, 3510) + // Standard Error: 8_995 + .saturating_add(Weight::from_parts(4_721_536, 0).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(v.into()))) + .saturating_add(Weight::from_parts(0, 2520).saturating_mul(v.into())) } - // Storage: Staking MinCommission (r:0 w:1) - // Storage: Staking MinValidatorBond (r:0 w:1) - // Storage: Staking MaxValidatorsCount (r:0 w:1) - // Storage: Staking ChillThreshold (r:0 w:1) - // Storage: Staking MaxNominatorsCount (r:0 w:1) - // Storage: Staking MinNominatorBond (r:0 w:1) + /// Storage: Staking MinCommission (r:0 w:1) + /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking MinValidatorBond (r:0 w:1) + /// Proof: Staking MinValidatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Staking MaxValidatorsCount (r:0 w:1) + /// Proof: Staking MaxValidatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ChillThreshold (r:0 w:1) + /// Proof: Staking ChillThreshold (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: Staking MaxNominatorsCount (r:0 w:1) + /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking MinNominatorBond (r:0 w:1) + /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) fn set_staking_configs_all_set() -> Weight { - // Minimum execution time: 10_937 nanoseconds. - Weight::from_ref_time(11_324_000) - .saturating_add(RocksDbWeight::get().writes(6)) - } - // Storage: Staking MinCommission (r:0 w:1) - // Storage: Staking MinValidatorBond (r:0 w:1) - // Storage: Staking MaxValidatorsCount (r:0 w:1) - // Storage: Staking ChillThreshold (r:0 w:1) - // Storage: Staking MaxNominatorsCount (r:0 w:1) - // Storage: Staking MinNominatorBond (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_529_000 picoseconds. + Weight::from_parts(7_970_000, 0) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + } + /// Storage: Staking MinCommission (r:0 w:1) + /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking MinValidatorBond (r:0 w:1) + /// Proof: Staking MinValidatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Staking MaxValidatorsCount (r:0 w:1) + /// Proof: Staking MaxValidatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking ChillThreshold (r:0 w:1) + /// Proof: Staking ChillThreshold (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: Staking MaxNominatorsCount (r:0 w:1) + /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking MinNominatorBond (r:0 w:1) + /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) fn set_staking_configs_all_remove() -> Weight { - // Minimum execution time: 9_424 nanoseconds. - Weight::from_ref_time(10_021_000) - .saturating_add(RocksDbWeight::get().writes(6)) - } - // Storage: Staking Ledger (r:1 w:0) - // Storage: Staking Nominators (r:1 w:1) - // Storage: Staking ChillThreshold (r:1 w:0) - // Storage: Staking MaxNominatorsCount (r:1 w:0) - // Storage: Staking CounterForNominators (r:1 w:1) - // Storage: Staking MinNominatorBond (r:1 w:0) - // Storage: Staking Validators (r:1 w:0) - // Storage: VoterList ListNodes (r:2 w:2) - // Storage: VoterList ListBags (r:1 w:1) - // Storage: VoterList CounterForListNodes (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_011_000 picoseconds. + Weight::from_parts(7_317_000, 0) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + } + /// Storage: Staking Ledger (r:1 w:0) + /// Proof: Staking Ledger (max_values: None, max_size: Some(1091), added: 3566, mode: MaxEncodedLen) + /// Storage: Staking Nominators (r:1 w:1) + /// Proof: Staking Nominators (max_values: None, max_size: Some(558), added: 3033, mode: MaxEncodedLen) + /// Storage: Staking ChillThreshold (r:1 w:0) + /// Proof: Staking ChillThreshold (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: Staking MaxNominatorsCount (r:1 w:0) + /// Proof: Staking MaxNominatorsCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking CounterForNominators (r:1 w:1) + /// Proof: Staking CounterForNominators (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking MinNominatorBond (r:1 w:0) + /// Proof: Staking MinNominatorBond (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:1 w:0) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) + /// Storage: VoterList ListNodes (r:2 w:2) + /// Proof: VoterList ListNodes (max_values: None, max_size: Some(154), added: 2629, mode: MaxEncodedLen) + /// Storage: VoterList ListBags (r:1 w:1) + /// Proof: VoterList ListBags (max_values: None, max_size: Some(82), added: 2557, mode: MaxEncodedLen) + /// Storage: VoterList CounterForListNodes (r:1 w:1) + /// Proof: VoterList CounterForListNodes (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn chill_other() -> Weight { - // Minimum execution time: 84_495 nanoseconds. - Weight::from_ref_time(85_559_000) - .saturating_add(RocksDbWeight::get().reads(11)) - .saturating_add(RocksDbWeight::get().writes(6)) + // Proof Size summary in bytes: + // Measured: `1871` + // Estimated: `6248` + // Minimum execution time: 75_982_000 picoseconds. + Weight::from_parts(77_412_000, 6248) + .saturating_add(RocksDbWeight::get().reads(11_u64)) + .saturating_add(RocksDbWeight::get().writes(6_u64)) } - // Storage: Staking MinCommission (r:1 w:0) - // Storage: Staking Validators (r:1 w:1) + /// Storage: Staking MinCommission (r:1 w:0) + /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: Staking Validators (r:1 w:1) + /// Proof: Staking Validators (max_values: None, max_size: Some(45), added: 2520, mode: MaxEncodedLen) fn force_apply_min_commission() -> Weight { - // Minimum execution time: 20_385 nanoseconds. - Weight::from_ref_time(20_824_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `694` + // Estimated: `3510` + // Minimum execution time: 13_923_000 picoseconds. + Weight::from_parts(14_356_000, 3510) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Staking MinCommission (r:0 w:1) + /// Storage: Staking MinCommission (r:0 w:1) + /// Proof: Staking MinCommission (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) fn set_min_commission() -> Weight { - // Minimum execution time: 6_995 nanoseconds. - Weight::from_ref_time(7_213_000) - .saturating_add(RocksDbWeight::get().writes(1)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 3_415_000 picoseconds. + Weight::from_parts(3_679_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } diff --git a/frame/state-trie-migration/Cargo.toml b/frame/state-trie-migration/Cargo.toml index 36b5912a6030..1aa2a391d033 100644 --- a/frame/state-trie-migration/Cargo.toml +++ b/frame/state-trie-migration/Cargo.toml @@ -12,12 +12,12 @@ description = "FRAME pallet migration of trie" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.133", optional = true } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", optional = true } thousands = { version = "0.2.0", optional = true } -zstd = { version = "0.11.2", default-features = false, optional = true } +zstd = { version = "0.12.3", default-features = false, optional = true } frame-benchmarking = { default-features = false, optional = true, path = "../benchmarking" } frame-support = { default-features = false, path = "../support" } frame-system = { default-features = false, path = "../system" } diff --git a/frame/state-trie-migration/src/lib.rs b/frame/state-trie-migration/src/lib.rs index 23f73bb56b17..4bbabef1dd4b 100644 --- a/frame/state-trie-migration/src/lib.rs +++ b/frame/state-trie-migration/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -439,7 +439,6 @@ pub mod pallet { /// The outer Pallet struct. #[pallet::pallet] - #[pallet::generate_store(pub(crate) trait Store)] pub struct Pallet(_); /// Configurations of this pallet. @@ -1063,20 +1062,16 @@ mod mock { }; use sp_runtime::{ traits::{BlakeTwo256, Header as _, IdentityLookup}, - StorageChild, + BuildStorage, StorageChild, }; - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; - type Block = frame_system::mocking::MockBlock; + type Block = frame_system::mocking::MockBlockU32; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Config, Storage, Event}, StateTrieMigration: pallet_state_trie_migration::{Pallet, Call, Storage, Event}, } @@ -1092,13 +1087,12 @@ mod mock { type BlockLength = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u32; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = sp_runtime::generic::Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU32<250>; type DbWeight = (); @@ -1129,6 +1123,10 @@ mod mock { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } /// Test only Weights for state migration. @@ -1136,25 +1134,25 @@ mod mock { impl WeightInfo for StateMigrationTestWeight { fn process_top_key(_: u32) -> Weight { - Weight::from_ref_time(1000000) + Weight::from_parts(1000000, 0) } fn continue_migrate() -> Weight { - Weight::from_ref_time(1000000) + Weight::from_parts(1000000, 0) } fn continue_migrate_wrong_witness() -> Weight { - Weight::from_ref_time(1000000) + Weight::from_parts(1000000, 0) } fn migrate_custom_top_fail() -> Weight { - Weight::from_ref_time(1000000) + Weight::from_parts(1000000, 0) } fn migrate_custom_top_success() -> Weight { - Weight::from_ref_time(1000000) + Weight::from_parts(1000000, 0) } fn migrate_custom_child_fail() -> Weight { - Weight::from_ref_time(1000000) + Weight::from_parts(1000000, 0) } fn migrate_custom_child_success() -> Weight { - Weight::from_ref_time(1000000) + Weight::from_parts(1000000, 0) } } @@ -1238,8 +1236,8 @@ mod mock { }; if with_pallets { - frame_system::GenesisConfig::default() - .assimilate_storage::(&mut custom_storage) + frame_system::GenesisConfig::::default() + .assimilate_storage(&mut custom_storage) .unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 1000)] } .assimilate_storage(&mut custom_storage) @@ -1616,7 +1614,7 @@ pub(crate) mod remote_tests { traits::{Get, Hooks}, weights::Weight, }; - use frame_system::Pallet as System; + use frame_system::{pallet_prelude::BlockNumberFor, Pallet as System}; use remote_externalities::Mode; use sp_core::H256; use sp_runtime::{ @@ -1627,7 +1625,7 @@ pub(crate) mod remote_tests { #[allow(dead_code)] fn run_to_block>( - n: ::BlockNumber, + n: BlockNumberFor, ) -> (H256, Weight) { let mut root = Default::default(); let mut weight_sum = Weight::zero(); @@ -1667,7 +1665,7 @@ pub(crate) mod remote_tests { frame_system::Pallet::::block_number() }); - let mut duration: ::BlockNumber = Zero::zero(); + let mut duration: BlockNumberFor = Zero::zero(); // set the version to 1, as if the upgrade happened. ext.state_version = sp_core::storage::StateVersion::V1; diff --git a/frame/state-trie-migration/src/weights.rs b/frame/state-trie-migration/src/weights.rs index 7414bb9038fd..df3338fdc17d 100644 --- a/frame/state-trie-migration/src/weights.rs +++ b/frame/state-trie-migration/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_state_trie_migration //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_state_trie_migration +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_state_trie_migration. pub trait WeightInfo { @@ -59,100 +64,156 @@ pub trait WeightInfo { /// Weights for pallet_state_trie_migration using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) - // Storage: StateTrieMigration MigrationProcess (r:1 w:1) + /// Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) + /// Proof: StateTrieMigration SignedMigrationMaxLimits (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: StateTrieMigration MigrationProcess (r:1 w:1) + /// Proof: StateTrieMigration MigrationProcess (max_values: Some(1), max_size: Some(1042), added: 1537, mode: MaxEncodedLen) fn continue_migrate() -> Weight { - // Minimum execution time: 23_874 nanoseconds. - Weight::from_ref_time(24_127_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `108` + // Estimated: `2527` + // Minimum execution time: 14_297_000 picoseconds. + Weight::from_parts(14_832_000, 2527) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) + /// Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) + /// Proof: StateTrieMigration SignedMigrationMaxLimits (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) fn continue_migrate_wrong_witness() -> Weight { - // Minimum execution time: 6_119 nanoseconds. - Weight::from_ref_time(6_325_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `1493` + // Minimum execution time: 4_237_000 picoseconds. + Weight::from_parts(4_646_000, 1493) + .saturating_add(T::DbWeight::get().reads(1_u64)) } fn migrate_custom_top_success() -> Weight { - // Minimum execution time: 20_365 nanoseconds. - Weight::from_ref_time(20_790_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_898_000 picoseconds. + Weight::from_parts(9_237_000, 0) } - // Storage: unknown [0x666f6f] (r:1 w:1) + /// Storage: unknown `0x666f6f` (r:1 w:1) + /// Proof Skipped: unknown `0x666f6f` (r:1 w:1) fn migrate_custom_top_fail() -> Weight { - // Minimum execution time: 38_979 nanoseconds. - Weight::from_ref_time(40_271_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `113` + // Estimated: `3578` + // Minimum execution time: 29_291_000 picoseconds. + Weight::from_parts(30_424_000, 3578) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } fn migrate_custom_child_success() -> Weight { - // Minimum execution time: 21_217 nanoseconds. - Weight::from_ref_time(21_526_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 9_094_000 picoseconds. + Weight::from_parts(9_544_000, 0) } - // Storage: unknown [0x666f6f] (r:1 w:1) + /// Storage: unknown `0x666f6f` (r:1 w:1) + /// Proof Skipped: unknown `0x666f6f` (r:1 w:1) fn migrate_custom_child_fail() -> Weight { - // Minimum execution time: 43_853 nanoseconds. - Weight::from_ref_time(44_693_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `105` + // Estimated: `3570` + // Minimum execution time: 30_286_000 picoseconds. + Weight::from_parts(30_948_000, 3570) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: unknown [0x6b6579] (r:1 w:1) + /// Storage: unknown `0x6b6579` (r:1 w:1) + /// Proof Skipped: unknown `0x6b6579` (r:1 w:1) /// The range of component `v` is `[1, 4194304]`. fn process_top_key(v: u32, ) -> Weight { - // Minimum execution time: 5_575 nanoseconds. - Weight::from_ref_time(5_719_000 as u64) - // Standard Error: 3 - .saturating_add(Weight::from_ref_time(1_404 as u64).saturating_mul(v as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `197 + v * (1 ±0)` + // Estimated: `3662 + v * (1 ±0)` + // Minimum execution time: 5_420_000 picoseconds. + Weight::from_parts(5_560_000, 3662) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_139, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(v.into())) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) - // Storage: StateTrieMigration MigrationProcess (r:1 w:1) + /// Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) + /// Proof: StateTrieMigration SignedMigrationMaxLimits (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: StateTrieMigration MigrationProcess (r:1 w:1) + /// Proof: StateTrieMigration MigrationProcess (max_values: Some(1), max_size: Some(1042), added: 1537, mode: MaxEncodedLen) fn continue_migrate() -> Weight { - // Minimum execution time: 23_874 nanoseconds. - Weight::from_ref_time(24_127_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `108` + // Estimated: `2527` + // Minimum execution time: 14_297_000 picoseconds. + Weight::from_parts(14_832_000, 2527) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) + /// Storage: StateTrieMigration SignedMigrationMaxLimits (r:1 w:0) + /// Proof: StateTrieMigration SignedMigrationMaxLimits (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) fn continue_migrate_wrong_witness() -> Weight { - // Minimum execution time: 6_119 nanoseconds. - Weight::from_ref_time(6_325_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `1493` + // Minimum execution time: 4_237_000 picoseconds. + Weight::from_parts(4_646_000, 1493) + .saturating_add(RocksDbWeight::get().reads(1_u64)) } fn migrate_custom_top_success() -> Weight { - // Minimum execution time: 20_365 nanoseconds. - Weight::from_ref_time(20_790_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_898_000 picoseconds. + Weight::from_parts(9_237_000, 0) } - // Storage: unknown [0x666f6f] (r:1 w:1) + /// Storage: unknown `0x666f6f` (r:1 w:1) + /// Proof Skipped: unknown `0x666f6f` (r:1 w:1) fn migrate_custom_top_fail() -> Weight { - // Minimum execution time: 38_979 nanoseconds. - Weight::from_ref_time(40_271_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `113` + // Estimated: `3578` + // Minimum execution time: 29_291_000 picoseconds. + Weight::from_parts(30_424_000, 3578) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn migrate_custom_child_success() -> Weight { - // Minimum execution time: 21_217 nanoseconds. - Weight::from_ref_time(21_526_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 9_094_000 picoseconds. + Weight::from_parts(9_544_000, 0) } - // Storage: unknown [0x666f6f] (r:1 w:1) + /// Storage: unknown `0x666f6f` (r:1 w:1) + /// Proof Skipped: unknown `0x666f6f` (r:1 w:1) fn migrate_custom_child_fail() -> Weight { - // Minimum execution time: 43_853 nanoseconds. - Weight::from_ref_time(44_693_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `105` + // Estimated: `3570` + // Minimum execution time: 30_286_000 picoseconds. + Weight::from_parts(30_948_000, 3570) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: unknown [0x6b6579] (r:1 w:1) + /// Storage: unknown `0x6b6579` (r:1 w:1) + /// Proof Skipped: unknown `0x6b6579` (r:1 w:1) /// The range of component `v` is `[1, 4194304]`. fn process_top_key(v: u32, ) -> Weight { - // Minimum execution time: 5_575 nanoseconds. - Weight::from_ref_time(5_719_000 as u64) - // Standard Error: 3 - .saturating_add(Weight::from_ref_time(1_404 as u64).saturating_mul(v as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `197 + v * (1 ±0)` + // Estimated: `3662 + v * (1 ±0)` + // Minimum execution time: 5_420_000 picoseconds. + Weight::from_parts(5_560_000, 3662) + // Standard Error: 1 + .saturating_add(Weight::from_parts(1_139, 0).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(v.into())) } } diff --git a/frame/statement/Cargo.toml b/frame/statement/Cargo.toml new file mode 100644 index 000000000000..982d688abf52 --- /dev/null +++ b/frame/statement/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "pallet-statement" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME pallet for statement store" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"]} +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +sp-statement-store = { version = "4.0.0-dev", default-features = false, path = "../../primitives/statement-store" } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +log = { version = "0.4.17", default-features = false } + +[dev-dependencies] +pallet-balances = { version = "4.0.0-dev", path = "../balances" } + +[features] +default = [ "std" ] +std = [ + "codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "sp-api/std", + "sp-runtime/std", + "sp-std/std", + "sp-io/std", + "sp-core/std", + "sp-statement-store/std", +] +try-runtime = [ + "frame-support/try-runtime", +] diff --git a/frame/statement/src/lib.rs b/frame/statement/src/lib.rs new file mode 100644 index 000000000000..c68dac2d2972 --- /dev/null +++ b/frame/statement/src/lib.rs @@ -0,0 +1,222 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Supporting pallet for the statement store. +//! +//! - [`Pallet`] +//! +//! ## Overview +//! +//! The Statement pallet provides means to create and validate statements for the statement store. +//! +//! For each statement validation function calculates the following three values based on the +//! statement author balance: +//! `max_count`: Maximum number of statements allowed for the author (signer) of this statement. +//! `max_size`: Maximum total size of statements allowed for the author (signer) of this statement. +//! +//! This pallet also contains an offchain worker that turns on-chain statement events into +//! statements. These statements are placed in the store and propagated over the network. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{ + pallet_prelude::*, + sp_runtime::{traits::CheckedDiv, SaturatedConversion}, + traits::fungible::Inspect, +}; +use frame_system::pallet_prelude::*; +use sp_statement_store::{ + runtime_api::{InvalidStatement, StatementSource, ValidStatement}, + Proof, SignatureVerificationResult, Statement, +}; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +pub use pallet::*; + +const LOG_TARGET: &str = "runtime::statement"; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + pub type BalanceOf = + <::Currency as Inspect<::AccountId>>::Balance; + + pub type AccountIdOf = ::AccountId; + + #[pallet::config] + pub trait Config: frame_system::Config + where + ::AccountId: From, + { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// The currency which is used to calculate account limits. + type Currency: Inspect; + /// Min balance for priority statements. + #[pallet::constant] + type StatementCost: Get>; + /// Cost of data byte used for priority calculation. + #[pallet::constant] + type ByteCost: Get>; + /// Minimum number of statements allowed per account. + #[pallet::constant] + type MinAllowedStatements: Get; + /// Maximum number of statements allowed per account. + #[pallet::constant] + type MaxAllowedStatements: Get; + /// Minimum data bytes allowed per account. + #[pallet::constant] + type MinAllowedBytes: Get; + /// Maximum data bytes allowed per account. + #[pallet::constant] + type MaxAllowedBytes: Get; + } + + #[pallet::pallet] + pub struct Pallet(sp_std::marker::PhantomData); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event + where + ::AccountId: From, + { + /// A new statement is submitted + NewStatement { account: T::AccountId, statement: Statement }, + } + + #[pallet::hooks] + impl Hooks> for Pallet + where + ::AccountId: From, + sp_statement_store::AccountId: From<::AccountId>, + ::RuntimeEvent: From>, + ::RuntimeEvent: TryInto>, + sp_statement_store::BlockHash: From<::Hash>, + { + fn offchain_worker(now: BlockNumberFor) { + log::trace!(target: LOG_TARGET, "Collecting statements at #{:?}", now); + Pallet::::collect_statements(); + } + } +} + +impl Pallet +where + ::AccountId: From, + sp_statement_store::AccountId: From<::AccountId>, + ::RuntimeEvent: From>, + ::RuntimeEvent: TryInto>, + sp_statement_store::BlockHash: From<::Hash>, +{ + /// Validate a statement against current state. This is supposed to be called by the statement + /// store on the host side. + pub fn validate_statement( + _source: StatementSource, + mut statement: Statement, + ) -> Result { + sp_io::init_tracing(); + log::debug!(target: LOG_TARGET, "Validating statement {:?}", statement); + let account: T::AccountId = match statement.proof() { + Some(Proof::OnChain { who, block_hash, event_index }) => { + if frame_system::Pallet::::parent_hash().as_ref() != block_hash.as_slice() { + log::debug!(target: LOG_TARGET, "Bad block hash."); + return Err(InvalidStatement::BadProof) + } + let account: T::AccountId = (*who).into(); + match frame_system::Pallet::::event_no_consensus(*event_index as usize) { + Some(e) => { + statement.remove_proof(); + if let Ok(Event::NewStatement { account: a, statement: s }) = e.try_into() { + if a != account || s != statement { + log::debug!(target: LOG_TARGET, "Event data mismatch"); + return Err(InvalidStatement::BadProof) + } + } else { + log::debug!(target: LOG_TARGET, "Event type mismatch"); + return Err(InvalidStatement::BadProof) + } + }, + _ => { + log::debug!(target: LOG_TARGET, "Bad event index"); + return Err(InvalidStatement::BadProof) + }, + } + account + }, + _ => match statement.verify_signature() { + SignatureVerificationResult::Valid(account) => account.into(), + SignatureVerificationResult::Invalid => { + log::debug!(target: LOG_TARGET, "Bad statement signature."); + return Err(InvalidStatement::BadProof) + }, + SignatureVerificationResult::NoSignature => { + log::debug!(target: LOG_TARGET, "Missing statement signature."); + return Err(InvalidStatement::NoProof) + }, + }, + }; + let statement_cost = T::StatementCost::get(); + let byte_cost = T::ByteCost::get(); + let balance = >>::balance(&account); + let min_allowed_statements = T::MinAllowedStatements::get(); + let max_allowed_statements = T::MaxAllowedStatements::get(); + let min_allowed_bytes = T::MinAllowedBytes::get(); + let max_allowed_bytes = T::MaxAllowedBytes::get(); + let max_count = balance + .checked_div(&statement_cost) + .unwrap_or_default() + .saturated_into::() + .clamp(min_allowed_statements, max_allowed_statements); + let max_size = balance + .checked_div(&byte_cost) + .unwrap_or_default() + .saturated_into::() + .clamp(min_allowed_bytes, max_allowed_bytes); + + Ok(ValidStatement { max_count, max_size }) + } + + /// Submit a statement event. The statement will be picked up by the offchain worker and + /// broadcast to the network. + pub fn submit_statement(account: T::AccountId, statement: Statement) { + Self::deposit_event(Event::NewStatement { account, statement }); + } + + fn collect_statements() { + // Find `NewStatement` events and submit them to the store + for (index, event) in frame_system::Pallet::::read_events_no_consensus().enumerate() { + if let Ok(Event::::NewStatement { account, mut statement }) = event.event.try_into() + { + if statement.proof().is_none() { + let proof = Proof::OnChain { + who: account.into(), + block_hash: frame_system::Pallet::::parent_hash().into(), + event_index: index as u64, + }; + statement.set_proof(proof); + } + sp_statement_store::runtime_api::statement_store::submit_statement(statement); + } + } + } +} diff --git a/frame/statement/src/mock.rs b/frame/statement/src/mock.rs new file mode 100644 index 000000000000..79d2aa7d891d --- /dev/null +++ b/frame/statement/src/mock.rs @@ -0,0 +1,120 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Statement pallet test environment. + +use super::*; + +use crate as pallet_statement; +use frame_support::{ + ord_parameter_types, + traits::{ConstU32, ConstU64, Everything}, + weights::constants::RocksDbWeight, +}; +use sp_core::{Pair, H256}; +use sp_runtime::{ + traits::{BlakeTwo256, IdentityLookup}, + AccountId32, BuildStorage, +}; + +type Block = frame_system::mocking::MockBlock; + +pub const MIN_ALLOWED_STATEMENTS: u32 = 4; +pub const MAX_ALLOWED_STATEMENTS: u32 = 10; +pub const MIN_ALLOWED_BYTES: u32 = 1024; +pub const MAX_ALLOWED_BYTES: u32 = 4096; + +frame_support::construct_runtime!( + pub enum Test + { + System: frame_system, + Balances: pallet_balances, + Statement: pallet_statement, + } +); + +impl frame_system::Config for Test { + type BaseCallFilter = Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = RocksDbWeight; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId32; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type Balance = u64; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU64<5>; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = RuntimeHoldReason; + type MaxHolds = (); +} + +ord_parameter_types! { + pub const One: u64 = 1; +} + +impl Config for Test { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type StatementCost = ConstU64<1000>; + type ByteCost = ConstU64<2>; + type MinAllowedStatements = ConstU32; + type MaxAllowedStatements = ConstU32; + type MinAllowedBytes = ConstU32; + type MaxAllowedBytes = ConstU32; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let balances = pallet_balances::GenesisConfig:: { + balances: vec![ + (sp_core::sr25519::Pair::from_string("//Alice", None).unwrap().public().into(), 6000), + ( + sp_core::sr25519::Pair::from_string("//Charlie", None).unwrap().public().into(), + 500000, + ), + ], + }; + balances.assimilate_storage(&mut t).unwrap(); + t.into() +} diff --git a/frame/statement/src/tests.rs b/frame/statement/src/tests.rs new file mode 100644 index 000000000000..51103caca60f --- /dev/null +++ b/frame/statement/src/tests.rs @@ -0,0 +1,159 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Statement runtime support tests. + +#![cfg(test)] + +use super::*; +use crate::mock::*; +use sp_core::Pair; +use sp_runtime::AccountId32; +use sp_statement_store::{ + runtime_api::{InvalidStatement, StatementSource, ValidStatement}, + Proof, Statement, +}; + +#[test] +fn sign_and_validate_no_balance() { + new_test_ext().execute_with(|| { + let pair = sp_core::sr25519::Pair::from_string("//Bob", None).unwrap(); + let mut statement = Statement::new(); + statement.sign_sr25519_private(&pair); + let result = Pallet::::validate_statement(StatementSource::Chain, statement); + assert_eq!( + Ok(ValidStatement { max_count: MIN_ALLOWED_STATEMENTS, max_size: MIN_ALLOWED_BYTES }), + result + ); + + let pair = sp_core::ed25519::Pair::from_string("//Bob", None).unwrap(); + let mut statement = Statement::new(); + statement.sign_ed25519_private(&pair); + let result = Pallet::::validate_statement(StatementSource::Chain, statement); + assert_eq!( + Ok(ValidStatement { max_count: MIN_ALLOWED_STATEMENTS, max_size: MIN_ALLOWED_BYTES }), + result + ); + + let pair = sp_core::ecdsa::Pair::from_string("//Bob", None).unwrap(); + let mut statement = Statement::new(); + statement.sign_ecdsa_private(&pair); + let result = Pallet::::validate_statement(StatementSource::Chain, statement); + assert_eq!( + Ok(ValidStatement { max_count: MIN_ALLOWED_STATEMENTS, max_size: MIN_ALLOWED_BYTES }), + result + ); + }); +} + +#[test] +fn validate_with_balance() { + new_test_ext().execute_with(|| { + let pair = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap(); + let mut statement = Statement::new(); + statement.sign_sr25519_private(&pair); + let result = Pallet::::validate_statement(StatementSource::Chain, statement); + assert_eq!(Ok(ValidStatement { max_count: 6, max_size: 3000 }), result); + + let pair = sp_core::sr25519::Pair::from_string("//Charlie", None).unwrap(); + let mut statement = Statement::new(); + statement.sign_sr25519_private(&pair); + let result = Pallet::::validate_statement(StatementSource::Chain, statement); + assert_eq!( + Ok(ValidStatement { max_count: MAX_ALLOWED_STATEMENTS, max_size: MAX_ALLOWED_BYTES }), + result + ); + }); +} + +#[test] +fn validate_no_proof_fails() { + new_test_ext().execute_with(|| { + let statement = Statement::new(); + let result = Pallet::::validate_statement(StatementSource::Chain, statement); + assert_eq!(Err(InvalidStatement::NoProof), result); + }); +} + +#[test] +fn validate_bad_signature_fails() { + new_test_ext().execute_with(|| { + let statement = Statement::new_with_proof(Proof::Sr25519 { + signature: [0u8; 64], + signer: Default::default(), + }); + let result = Pallet::::validate_statement(StatementSource::Chain, statement); + assert_eq!(Err(InvalidStatement::BadProof), result); + }); +} + +#[test] +fn validate_event() { + new_test_ext().execute_with(|| { + let parent_hash = sp_core::H256::random(); + System::reset_events(); + System::initialize(&1, &parent_hash, &Default::default()); + let mut statement = Statement::new(); + let pair = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap(); + let account: AccountId32 = pair.public().into(); + Pallet::::submit_statement(account.clone(), statement.clone()); + statement.set_proof(Proof::OnChain { + who: account.clone().into(), + event_index: 0, + block_hash: parent_hash.into(), + }); + let result = Pallet::::validate_statement(StatementSource::Chain, statement.clone()); + assert_eq!(Ok(ValidStatement { max_count: 6, max_size: 3000 }), result); + + // Use wrong event index + statement.set_proof(Proof::OnChain { + who: account.clone().into(), + event_index: 1, + block_hash: parent_hash.into(), + }); + let result = Pallet::::validate_statement(StatementSource::Chain, statement.clone()); + assert_eq!(Err(InvalidStatement::BadProof), result); + + // Use wrong block hash + statement.set_proof(Proof::OnChain { + who: account.clone().into(), + event_index: 0, + block_hash: sp_core::H256::random().into(), + }); + let result = Pallet::::validate_statement(StatementSource::Chain, statement.clone()); + assert_eq!(Err(InvalidStatement::BadProof), result); + }); +} + +#[test] +fn validate_no_event_fails() { + new_test_ext().execute_with(|| { + let parent_hash = sp_core::H256::random(); + System::reset_events(); + System::initialize(&1, &parent_hash, &Default::default()); + let mut statement = Statement::new(); + let pair = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap(); + let account: AccountId32 = pair.public().into(); + statement.set_proof(Proof::OnChain { + who: account.into(), + event_index: 0, + block_hash: parent_hash.into(), + }); + let result = Pallet::::validate_statement(StatementSource::Chain, statement); + assert_eq!(Err(InvalidStatement::BadProof), result); + }); +} diff --git a/frame/sudo/Cargo.toml b/frame/sudo/Cargo.toml index 56d04b172c26..f1ca4a012edf 100644 --- a/frame/sudo/Cargo.toml +++ b/frame/sudo/Cargo.toml @@ -13,21 +13,23 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } [features] default = ["std"] std = [ "codec/std", + "frame-benchmarking?/std", "frame-support/std", "frame-system/std", "scale-info/std", @@ -35,4 +37,9 @@ std = [ "sp-runtime/std", "sp-std/std", ] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/sudo/README.md b/frame/sudo/README.md index 7342832d2d7a..886dc5981778 100644 --- a/frame/sudo/README.md +++ b/frame/sudo/README.md @@ -1,7 +1,7 @@ # 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) +- [`Config`](https://docs.rs/pallet-sudo/latest/pallet_sudo/pallet/trait.Config.html) +- [`Call`](https://docs.rs/pallet-sudo/latest/pallet_sudo/pallet/enum.Call.html) ## Overview diff --git a/frame/sudo/src/benchmarking.rs b/frame/sudo/src/benchmarking.rs new file mode 100644 index 000000000000..6a365c1873c1 --- /dev/null +++ b/frame/sudo/src/benchmarking.rs @@ -0,0 +1,79 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Benchmarks for Sudo Pallet + +use super::*; +use crate::Pallet; +use frame_benchmarking::v2::*; +use frame_system::RawOrigin; + +const SEED: u32 = 0; + +fn assert_last_event(generic_event: ::RuntimeEvent) { + frame_system::Pallet::::assert_last_event(generic_event.into()); +} + +#[benchmarks( where ::RuntimeCall: From>)] +mod benchmarks { + use super::*; + + #[benchmark] + fn set_key() { + let caller: T::AccountId = whitelisted_caller(); + Key::::put(caller.clone()); + + let new_sudoer: T::AccountId = account("sudoer", 0, SEED); + let new_sudoer_lookup = T::Lookup::unlookup(new_sudoer.clone()); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), new_sudoer_lookup); + + assert_last_event::(Event::KeyChanged { old_sudoer: Some(caller) }.into()); + } + + #[benchmark] + fn sudo() { + let caller: T::AccountId = whitelisted_caller(); + Key::::put(caller.clone()); + + let call: ::RuntimeCall = frame_system::Call::remark { remark: vec![] }.into(); + + #[extrinsic_call] + _(RawOrigin::Signed(caller.clone()), Box::new(call.clone())); + + assert_last_event::(Event::Sudid { sudo_result: Ok(()) }.into()) + } + + #[benchmark] + fn sudo_as() { + let caller: T::AccountId = whitelisted_caller(); + Key::::put(caller.clone()); + + let call: ::RuntimeCall = frame_system::Call::remark { remark: vec![] }.into(); + + let who: T::AccountId = account("as", 0, SEED); + let who_lookup = T::Lookup::unlookup(who.clone()); + + #[extrinsic_call] + _(RawOrigin::Signed(caller), who_lookup, Box::new(call.clone())); + + assert_last_event::(Event::SudoAsDone { sudo_result: Ok(()) }.into()) + } + + impl_benchmark_test_suite!(Pallet, crate::mock::new_bench_ext(), crate::mock::Test); +} diff --git a/frame/sudo/src/extension.rs b/frame/sudo/src/extension.rs index 068fa2ed928d..c717ff356726 100644 --- a/frame/sudo/src/extension.rs +++ b/frame/sudo/src/extension.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/sudo/src/lib.rs b/frame/sudo/src/lib.rs index 0867f24b1691..f735469558c7 100644 --- a/frame/sudo/src/lib.rs +++ b/frame/sudo/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -59,7 +59,7 @@ //! use frame_system::pallet_prelude::*; //! //! #[pallet::pallet] -//! pub struct Pallet(PhantomData); +//! pub struct Pallet(_); //! //! #[pallet::config] //! pub trait Config: frame_system::Config {} @@ -110,6 +110,11 @@ mod mock; #[cfg(test)] mod tests; +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod weights; +pub use weights::WeightInfo; + pub use extension::CheckOnlySudoAccount; pub use pallet::*; @@ -130,11 +135,13 @@ pub mod pallet { type RuntimeCall: Parameter + UnfilteredDispatchable + GetDispatchInfo; + + /// Type representing the weight of this pallet + type WeightInfo: WeightInfo; } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(PhantomData); + pub struct Pallet(_); #[pallet::call] impl Pallet { @@ -142,16 +149,15 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_. /// - /// # + /// ## Complexity /// - 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, dispatch_info.class) + ( + T::WeightInfo::sudo().saturating_add(dispatch_info.weight), + dispatch_info.class + ) })] pub fn sudo( origin: OriginFor, @@ -173,10 +179,8 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_. /// - /// # + /// ## Complexity /// - 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( @@ -199,13 +203,10 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_. /// - /// # + /// ## Complexity /// - O(1). - /// - Limited storage reads. - /// - One DB change. - /// # #[pallet::call_index(2)] - #[pallet::weight(0)] + #[pallet::weight(T::WeightInfo::set_key())] pub fn set_key( origin: OriginFor, new: AccountIdLookupOf, @@ -226,19 +227,13 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Signed_. /// - /// # + /// ## Complexity /// - O(1). - /// - Limited storage reads. - /// - One DB write (event). - /// - Weight of derivative `call` execution + 10,000. - /// # #[pallet::call_index(3)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); ( - dispatch_info.weight - // AccountData for inner call origin accountdata. - .saturating_add(T::DbWeight::get().reads_writes(1, 1)), + T::WeightInfo::sudo_as().saturating_add(dispatch_info.weight), dispatch_info.class, ) })] @@ -287,20 +282,14 @@ pub mod pallet { pub(super) type Key = StorageValue<_, T::AccountId, OptionQuery>; #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { /// The `AccountId` of the sudo key. pub key: Option, } - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - Self { key: None } - } - } - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { if let Some(ref key) = self.key { Key::::put(key); diff --git a/frame/sudo/src/mock.rs b/frame/sudo/src/mock.rs index f8a03836f8f0..9e78e474f4e5 100644 --- a/frame/sudo/src/mock.rs +++ b/frame/sudo/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,12 +19,12 @@ use super::*; use crate as sudo; -use frame_support::traits::{ConstU32, ConstU64, Contains, GenesisBuild}; +use frame_support::traits::{ConstU32, ConstU64, Contains}; use sp_core::H256; use sp_io; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; // Logger module to track execution. @@ -39,8 +39,7 @@ pub mod logger { } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(PhantomData); + pub struct Pallet(_); #[pallet::call] impl Pallet { @@ -91,16 +90,12 @@ pub mod logger { pub(super) type I32Log = StorageValue<_, BoundedVec>, ValueQuery>; } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Sudo: sudo::{Pallet, Call, Config, Storage, Event}, Logger: logger::{Pallet, Call, Storage, Event}, } @@ -120,13 +115,12 @@ impl frame_system::Config for Test { type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -149,6 +143,7 @@ impl logger::Config for Test { impl Config for Test { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; + type WeightInfo = (); } // New types for dispatchable functions. @@ -157,9 +152,14 @@ pub type LoggerCall = logger::Call; // Build test environment by setting the root `key` for the Genesis. pub fn new_test_ext(root_key: u64) -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); sudo::GenesisConfig:: { key: Some(root_key) } .assimilate_storage(&mut t) .unwrap(); t.into() } + +#[cfg(feature = "runtime-benchmarks")] +pub fn new_bench_ext() -> sp_io::TestExternalities { + frame_system::GenesisConfig::::default().build_storage().unwrap().into() +} diff --git a/frame/sudo/src/tests.rs b/frame/sudo/src/tests.rs index ae8f19873600..c854fed8f073 100644 --- a/frame/sudo/src/tests.rs +++ b/frame/sudo/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,7 +41,7 @@ fn sudo_basics() { // 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_ref_time(1_000), + weight: Weight::from_parts(1_000, 0), })); assert_ok!(Sudo::sudo(RuntimeOrigin::signed(1), call)); assert_eq!(Logger::i32_log(), vec![42i32]); @@ -49,7 +49,7 @@ fn sudo_basics() { // 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_ref_time(1_000), + weight: Weight::from_parts(1_000, 0), })); assert_noop!(Sudo::sudo(RuntimeOrigin::signed(2), call), Error::::RequireSudo); }); @@ -64,7 +64,7 @@ fn sudo_emits_events_correctly() { // 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_ref_time(1), + weight: Weight::from_parts(1, 0), })); assert_ok!(Sudo::sudo(RuntimeOrigin::signed(1), call)); System::assert_has_event(TestEvent::Sudo(Event::Sudid { sudo_result: Ok(()) })); @@ -77,25 +77,25 @@ fn sudo_unchecked_weight_basics() { // 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_ref_time(1_000), + weight: Weight::from_parts(1_000, 0), })); assert_ok!(Sudo::sudo_unchecked_weight( RuntimeOrigin::signed(1), call, - Weight::from_ref_time(1_000) + 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_ref_time(1_000), + weight: Weight::from_parts(1_000, 0), })); assert_noop!( Sudo::sudo_unchecked_weight( RuntimeOrigin::signed(2), call, - Weight::from_ref_time(1_000) + Weight::from_parts(1_000, 0) ), Error::::RequireSudo, ); @@ -105,12 +105,12 @@ fn sudo_unchecked_weight_basics() { // Controls the dispatched weight. let call = Box::new(RuntimeCall::Logger(LoggerCall::privileged_i32_log { i: 42, - weight: Weight::from_ref_time(1), + weight: Weight::from_parts(1, 0), })); let sudo_unchecked_weight_call = - SudoCall::sudo_unchecked_weight { call, weight: Weight::from_ref_time(1_000) }; + SudoCall::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_ref_time(1_000)); + assert_eq!(info.weight, Weight::from_parts(1_000, 0)); }); } @@ -123,12 +123,12 @@ fn sudo_unchecked_weight_emits_events_correctly() { // 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_ref_time(1), + weight: Weight::from_parts(1, 0), })); assert_ok!(Sudo::sudo_unchecked_weight( RuntimeOrigin::signed(1), call, - Weight::from_ref_time(1_000) + Weight::from_parts(1_000, 0) )); System::assert_has_event(TestEvent::Sudo(Event::Sudid { sudo_result: Ok(()) })); }) @@ -170,7 +170,7 @@ fn sudo_as_basics() { // 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_ref_time(1_000), + weight: Weight::from_parts(1_000, 0), })); assert_ok!(Sudo::sudo_as(RuntimeOrigin::signed(1), 2, call)); assert!(Logger::i32_log().is_empty()); @@ -179,14 +179,14 @@ fn sudo_as_basics() { // 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_ref_time(1), + weight: Weight::from_parts(1, 0), })); assert_noop!(Sudo::sudo_as(RuntimeOrigin::signed(3), 2, call), Error::::RequireSudo); // 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_ref_time(1), + weight: Weight::from_parts(1, 0), })); assert_ok!(Sudo::sudo_as(RuntimeOrigin::signed(1), 2, call)); assert_eq!(Logger::i32_log(), vec![42i32]); @@ -204,7 +204,7 @@ fn sudo_as_emits_events_correctly() { // 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_ref_time(1), + weight: Weight::from_parts(1, 0), })); assert_ok!(Sudo::sudo_as(RuntimeOrigin::signed(1), 2, call)); System::assert_has_event(TestEvent::Sudo(Event::SudoAsDone { sudo_result: Ok(()) })); diff --git a/frame/sudo/src/weights.rs b/frame/sudo/src/weights.rs new file mode 100644 index 000000000000..6a0197d1469b --- /dev/null +++ b/frame/sudo/src/weights.rs @@ -0,0 +1,129 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for pallet_sudo +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/production/substrate +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_sudo +// --no-storage-info +// --no-median-slopes +// --no-min-squares +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=./frame/sudo/src/weights.rs +// --header=./HEADER-APACHE2 +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for pallet_sudo. +pub trait WeightInfo { + fn set_key() -> Weight; + fn sudo() -> Weight; + fn sudo_as() -> Weight; +} + +/// Weights for pallet_sudo using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: Sudo Key (r:1 w:1) + /// Proof: Sudo Key (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + fn set_key() -> Weight { + // Proof Size summary in bytes: + // Measured: `165` + // Estimated: `1517` + // Minimum execution time: 12_918_000 picoseconds. + Weight::from_parts(13_403_000, 1517) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Sudo Key (r:1 w:0) + /// Proof: Sudo Key (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + fn sudo() -> Weight { + // Proof Size summary in bytes: + // Measured: `165` + // Estimated: `1517` + // Minimum execution time: 12_693_000 picoseconds. + Weight::from_parts(13_001_000, 1517) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: Sudo Key (r:1 w:0) + /// Proof: Sudo Key (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + fn sudo_as() -> Weight { + // Proof Size summary in bytes: + // Measured: `165` + // Estimated: `1517` + // Minimum execution time: 12_590_000 picoseconds. + Weight::from_parts(12_994_000, 1517) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: Sudo Key (r:1 w:1) + /// Proof: Sudo Key (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + fn set_key() -> Weight { + // Proof Size summary in bytes: + // Measured: `165` + // Estimated: `1517` + // Minimum execution time: 12_918_000 picoseconds. + Weight::from_parts(13_403_000, 1517) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Sudo Key (r:1 w:0) + /// Proof: Sudo Key (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + fn sudo() -> Weight { + // Proof Size summary in bytes: + // Measured: `165` + // Estimated: `1517` + // Minimum execution time: 12_693_000 picoseconds. + Weight::from_parts(13_001_000, 1517) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: Sudo Key (r:1 w:0) + /// Proof: Sudo Key (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + fn sudo_as() -> Weight { + // Proof Size summary in bytes: + // Measured: `165` + // Estimated: `1517` + // Minimum execution time: 12_590_000 picoseconds. + Weight::from_parts(12_994_000, 1517) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } +} diff --git a/frame/support/Cargo.toml b/frame/support/Cargo.toml index ae2fc358974b..f483c632a969 100644 --- a/frame/support/Cargo.toml +++ b/frame/support/Cargo.toml @@ -13,46 +13,49 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.136", optional = true, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive", "max-encoded-len"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -frame-metadata = { version = "15.0.0", default-features = false, features = ["v14"] } -sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-tracing = { version = "6.0.0", default-features = false, path = "../../primitives/tracing" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../primitives/arithmetic" } +serde = { version = "1.0.163", default-features = false, features = ["alloc", "derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +frame-metadata = { version = "16.0.0", default-features = false, features = ["current"] } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api", features = [ "frame-metadata" ] } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-tracing = { version = "10.0.0", default-features = false, path = "../../primitives/tracing" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../primitives/arithmetic" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } -sp-weights = { version = "4.0.0", default-features = false, path = "../../primitives/weights" } -static_assertions = "1.1.0" +sp-weights = { version = "20.0.0", default-features = false, path = "../../primitives/weights" } +sp-debug-derive = { default-features = false, path = "../../primitives/debug-derive" } tt-call = "1.0.8" +macro_magic = "0.4.1" frame-support-procedural = { version = "4.0.0-dev", default-features = false, path = "./procedural" } paste = "1.0" -once_cell = { version = "1", default-features = false, optional = true } -sp-state-machine = { version = "0.13.0", default-features = false, optional = true, path = "../../primitives/state-machine" } +sp-state-machine = { version = "0.28.0", default-features = false, optional = true, path = "../../primitives/state-machine" } bitflags = "1.3" impl-trait-for-tuples = "0.2.2" smallvec = "1.8.0" log = { version = "0.4.17", default-features = false } -sp-core-hashing-proc-macro = { version = "5.0.0", path = "../../primitives/core/hashing/proc-macro" } -k256 = { version = "0.11.5", default-features = false, features = ["ecdsa"] } +sp-core-hashing-proc-macro = { version = "9.0.0", path = "../../primitives/core/hashing/proc-macro" } +k256 = { version = "0.13.0", default-features = false, features = ["ecdsa"] } +environmental = { version = "1.1.4", default-features = false } + +aquamarine = { version = "0.3.2" } [dev-dependencies] serde_json = "1.0.85" assert_matches = "1.3.0" pretty_assertions = "1.2.1" frame-system = { version = "4.0.0-dev", path = "../system" } +array-bytes = "6.1" [features] default = ["std"] std = [ "sp-core/std", "k256/std", - "once_cell", - "serde", + "serde/std", "sp-api/std", "sp-io/std", "codec/std", @@ -68,16 +71,24 @@ std = [ "sp-weights/std", "frame-support-procedural/std", "log/std", + "environmental/std", +] +runtime-benchmarks = [ + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "sp-staking/runtime-benchmarks" +] +try-runtime = [ + "sp-debug-derive/force-debug" ] -runtime-benchmarks = [] -try-runtime = [] +experimental = [] # By default some types have documentation, `no-metadata-docs` allows to reduce the documentation # in the metadata. -no-metadata-docs = ["frame-support-procedural/no-metadata-docs"] +no-metadata-docs = ["frame-support-procedural/no-metadata-docs", "sp-api/no-metadata-docs"] # By default some types have documentation, `full-metadata-docs` allows to add documentation to # more types in the metadata. full-metadata-docs = ["scale-info/docs"] # Generate impl-trait for tuples with the given number of tuples. Will be needed as the number of # pallets in a runtime grows. Does increase the compile time! -tuples-96 = [] -tuples-128 = [] +tuples-96 = ["frame-support-procedural/tuples-96"] +tuples-128 = ["frame-support-procedural/tuples-128"] diff --git a/frame/support/procedural/Cargo.toml b/frame/support/procedural/Cargo.toml index ee1ca4dff887..5614d8a58aea 100644 --- a/frame/support/procedural/Cargo.toml +++ b/frame/support/procedural/Cargo.toml @@ -17,14 +17,21 @@ proc-macro = true [dependencies] derive-syn-parse = "0.1.5" Inflector = "0.11.4" -cfg-expr = "0.10.3" +cfg-expr = "0.15.1" itertools = "0.10.3" -proc-macro2 = "1.0.37" -quote = "1.0.10" -syn = { version = "1.0.98", features = ["full"] } +proc-macro2 = "1.0.56" +quote = "1.0.28" +syn = { version = "2.0.16", features = ["full"] } frame-support-procedural-tools = { version = "4.0.0-dev", path = "./tools" } +proc-macro-warning = { version = "0.4.1", default-features = false } +macro_magic = { version = "0.4.1", features = ["proc_support"] } +expander = "2.0.0" [features] default = ["std"] std = [] no-metadata-docs = [] +# Generate impl-trait for tuples with the given number of tuples. Will be needed as the number of +# pallets in a runtime grows. Does increase the compile time! +tuples-96 = [] +tuples-128 = [] diff --git a/frame/support/procedural/src/benchmark.rs b/frame/support/procedural/src/benchmark.rs index 43e3e47de525..9f28e7129d2e 100644 --- a/frame/support/procedural/src/benchmark.rs +++ b/frame/support/procedural/src/benchmark.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,15 +21,16 @@ use derive_syn_parse::Parse; use frame_support_procedural_tools::generate_crate_access_2018; use proc_macro::TokenStream; use proc_macro2::{Ident, Span, TokenStream as TokenStream2}; -use quote::{quote, quote_spanned, ToTokens}; +use quote::{quote, ToTokens}; use syn::{ - parenthesized, parse::{Nothing, ParseStream}, + parse_quote, punctuated::Punctuated, spanned::Spanned, - token::{Colon2, Comma, Gt, Lt, Paren}, - Attribute, Error, Expr, ExprBlock, ExprCall, ExprPath, FnArg, Item, ItemFn, ItemMod, LitInt, - Pat, Path, PathArguments, PathSegment, Result, Stmt, Token, Type, WhereClause, + token::{Comma, Gt, Lt, PathSep}, + Attribute, Error, Expr, ExprBlock, ExprCall, ExprPath, FnArg, Item, ItemFn, ItemMod, Pat, Path, + PathArguments, PathSegment, Result, ReturnType, Signature, Stmt, Token, Type, TypePath, + Visibility, WhereClause, }; mod keywords { @@ -41,24 +42,29 @@ mod keywords { custom_keyword!(extra); custom_keyword!(extrinsic_call); custom_keyword!(skip_meta); + custom_keyword!(BenchmarkError); + custom_keyword!(Result); + + pub const BENCHMARK_TOKEN: &str = stringify!(benchmark); + pub const BENCHMARKS_TOKEN: &str = stringify!(benchmarks); } /// This represents the raw parsed data for a param definition such as `x: Linear<10, 20>`. #[derive(Clone)] struct ParamDef { name: String, - typ: Type, - start: u32, - end: u32, + _typ: Type, + start: syn::GenericArgument, + end: syn::GenericArgument, } /// Allows easy parsing of the `<10, 20>` component of `x: Linear<10, 20>`. #[derive(Parse)] struct RangeArgs { _lt_token: Lt, - start: LitInt, + start: syn::GenericArgument, _comma: Comma, - end: LitInt, + end: syn::GenericArgument, _gt_token: Gt, } @@ -91,27 +97,20 @@ impl syn::parse::Parse for BenchmarkAttrKeyword { impl syn::parse::Parse for BenchmarkAttrs { fn parse(input: ParseStream) -> syn::Result { - let lookahead = input.lookahead1(); - if !lookahead.peek(Paren) { - let _nothing: Nothing = input.parse()?; - return Ok(BenchmarkAttrs { skip_meta: false, extra: false }) - } - let content; - let _paren: Paren = parenthesized!(content in input); let mut extra = false; let mut skip_meta = false; - let args = Punctuated::::parse_terminated(&content)?; + let args = Punctuated::::parse_terminated(&input)?; for arg in args.into_iter() { match arg { BenchmarkAttrKeyword::Extra => { if extra { - return Err(content.error("`extra` can only be specified once")) + return Err(input.error("`extra` can only be specified once")) } extra = true; }, BenchmarkAttrKeyword::SkipMeta => { if skip_meta { - return Err(content.error("`skip_meta` can only be specified once")) + return Err(input.error("`skip_meta` can only be specified once")) } skip_meta = true; }, @@ -145,66 +144,114 @@ struct BenchmarkDef { setup_stmts: Vec, call_def: BenchmarkCallDef, verify_stmts: Vec, - extra: bool, - skip_meta: bool, + last_stmt: Option, + fn_sig: Signature, + fn_vis: Visibility, + fn_attrs: Vec, } -impl BenchmarkDef { - /// Constructs a [`BenchmarkDef`] by traversing an existing [`ItemFn`] node. - pub fn from(item_fn: &ItemFn, extra: bool, skip_meta: bool) -> Result { - let mut params: Vec = Vec::new(); +/// used to parse something compatible with `Result` +#[derive(Parse)] +struct ResultDef { + _result_kw: keywords::Result, + _lt: Token![<], + unit: Type, + _comma: Comma, + e_type: TypePath, + _gt: Token![>], +} - // parse params such as "x: Linear<0, 1>" - for arg in &item_fn.sig.inputs { - let invalid_param = |span| { - return Err(Error::new(span, "Invalid benchmark function param. A valid example would be `x: Linear<5, 10>`.", )) - }; +/// Ensures that `ReturnType` is a `Result<(), BenchmarkError>`, if specified +fn ensure_valid_return_type(item_fn: &ItemFn) -> Result<()> { + if let ReturnType::Type(_, typ) = &item_fn.sig.output { + let non_unit = |span| return Err(Error::new(span, "expected `()`")); + let Type::Path(TypePath { path, qself: _ }) = &**typ else { + return Err(Error::new( + typ.span(), + "Only `Result<(), BenchmarkError>` or a blank return type is allowed on benchmark function definitions", + )) + }; + let seg = path + .segments + .last() + .expect("to be parsed as a TypePath, it must have at least one segment; qed"); + let res: ResultDef = syn::parse2(seg.to_token_stream())?; + // ensure T in Result is () + let Type::Tuple(tup) = res.unit else { return non_unit(res.unit.span()) }; + if !tup.elems.is_empty() { + return non_unit(tup.span()) + } + let TypePath { path, qself: _ } = res.e_type; + let seg = path + .segments + .last() + .expect("to be parsed as a TypePath, it must have at least one segment; qed"); + syn::parse2::(seg.to_token_stream())?; + } + Ok(()) +} + +/// Parses params such as `x: Linear<0, 1>` +fn parse_params(item_fn: &ItemFn) -> Result> { + let mut params: Vec = Vec::new(); + for arg in &item_fn.sig.inputs { + let invalid_param = |span| { + return Err(Error::new( + span, + "Invalid benchmark function param. A valid example would be `x: Linear<5, 10>`.", + )) + }; - let FnArg::Typed(arg) = arg else { return invalid_param(arg.span()) }; - let Pat::Ident(ident) = &*arg.pat else { return invalid_param(arg.span()) }; + let FnArg::Typed(arg) = arg else { return invalid_param(arg.span()) }; + let Pat::Ident(ident) = &*arg.pat else { return invalid_param(arg.span()) }; - // check param name - let var_span = ident.span(); - let invalid_param_name = || { - return Err(Error::new( + // check param name + let var_span = ident.span(); + let invalid_param_name = || { + return Err(Error::new( var_span, "Benchmark parameter names must consist of a single lowercase letter (a-z) and no other characters.", - )) - }; - let name = ident.ident.to_token_stream().to_string(); - if name.len() > 1 { - return invalid_param_name() - }; - let Some(name_char) = name.chars().next() else { return invalid_param_name() }; - if !name_char.is_alphabetic() || !name_char.is_lowercase() { - return invalid_param_name() - } + )); + }; + let name = ident.ident.to_token_stream().to_string(); + if name.len() > 1 { + return invalid_param_name() + }; + let Some(name_char) = name.chars().next() else { return invalid_param_name() }; + if !name_char.is_alphabetic() || !name_char.is_lowercase() { + return invalid_param_name() + } - // parse type - let typ = &*arg.ty; - let Type::Path(tpath) = typ else { return invalid_param(typ.span()) }; - let Some(segment) = tpath.path.segments.last() else { return invalid_param(typ.span()) }; - let args = segment.arguments.to_token_stream().into(); - let Ok(args) = syn::parse::(args) else { return invalid_param(typ.span()) }; - let Ok(start) = args.start.base10_parse::() else { return invalid_param(args.start.span()) }; - let Ok(end) = args.end.base10_parse::() else { return invalid_param(args.end.span()) }; - - if end < start { - return Err(Error::new( - args.start.span(), - "The start of a `ParamRange` must be less than or equal to the end", - )) - } + // parse type + let typ = &*arg.ty; + let Type::Path(tpath) = typ else { return invalid_param(typ.span()) }; + let Some(segment) = tpath.path.segments.last() else { return invalid_param(typ.span()) }; + let args = segment.arguments.to_token_stream().into(); + let Ok(args) = syn::parse::(args) else { return invalid_param(typ.span()) }; - params.push(ParamDef { name, typ: typ.clone(), start, end }); - } + params.push(ParamDef { name, _typ: typ.clone(), start: args.start, end: args.end }); + } + Ok(params) +} - // #[extrinsic_call] / #[block] handling - let call_defs = item_fn.block.stmts.iter().enumerate().filter_map(|(i, child)| { - if let Stmt::Semi(Expr::Call(expr_call), _semi) = child { +/// Used in several places where the `#[extrinsic_call]` or `#[body]` annotation is missing +fn missing_call(item_fn: &ItemFn) -> Result { + return Err(Error::new( + item_fn.block.brace_token.span.join(), + "No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body." + )); +} + +/// Finds the `BenchmarkCallDef` and its index (within the list of stmts for the fn) and +/// returns them. Also handles parsing errors for invalid / extra call defs. AKA this is +/// general handling for `#[extrinsic_call]` and `#[block]` +fn parse_call_def(item_fn: &ItemFn) -> Result<(usize, BenchmarkCallDef)> { + // #[extrinsic_call] / #[block] handling + let call_defs = item_fn.block.stmts.iter().enumerate().filter_map(|(i, child)| { + if let Stmt::Expr(Expr::Call(expr_call), _semi) = child { // #[extrinsic_call] case expr_call.attrs.iter().enumerate().find_map(|(k, attr)| { - let segment = attr.path.segments.last()?; + let segment = attr.path().segments.last()?; let _: keywords::extrinsic_call = syn::parse(segment.ident.to_token_stream().into()).ok()?; let mut expr_call = expr_call.clone(); @@ -218,10 +265,10 @@ impl BenchmarkDef { Some(Ok((i, BenchmarkCallDef::ExtrinsicCall { origin, expr_call, attr_span: attr.span() }))) }) - } else if let Stmt::Expr(Expr::Block(block)) = child { + } else if let Stmt::Expr(Expr::Block(block), _) = child { // #[block] case block.attrs.iter().enumerate().find_map(|(k, attr)| { - let segment = attr.path.segments.last()?; + let segment = attr.path().segments.last()?; let _: keywords::block = syn::parse(segment.ident.to_token_stream().into()).ok()?; let mut block = block.clone(); @@ -234,25 +281,58 @@ impl BenchmarkDef { None } }).collect::>>()?; - let (i, call_def) = match &call_defs[..] { - [(i, call_def)] => (*i, call_def.clone()), // = 1 - [] => return Err(Error::new( // = 0 - item_fn.block.brace_token.span, - "No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body." - )), - _ => return Err(Error::new( // > 1 + Ok(match &call_defs[..] { + [(i, call_def)] => (*i, call_def.clone()), // = 1 + [] => return missing_call(item_fn), + _ => + return Err(Error::new( call_defs[1].1.attr_span(), - "Only one #[extrinsic_call] or #[block] attribute is allowed per benchmark." + "Only one #[extrinsic_call] or #[block] attribute is allowed per benchmark.", )), + }) +} + +impl BenchmarkDef { + /// Constructs a [`BenchmarkDef`] by traversing an existing [`ItemFn`] node. + pub fn from(item_fn: &ItemFn) -> Result { + let params = parse_params(item_fn)?; + ensure_valid_return_type(item_fn)?; + let (i, call_def) = parse_call_def(&item_fn)?; + + let (verify_stmts, last_stmt) = match item_fn.sig.output { + ReturnType::Default => + // no return type, last_stmt should be None + (Vec::from(&item_fn.block.stmts[(i + 1)..item_fn.block.stmts.len()]), None), + ReturnType::Type(_, _) => { + // defined return type, last_stmt should be Result<(), BenchmarkError> + // compatible and should not be included in verify_stmts + if i + 1 >= item_fn.block.stmts.len() { + return Err(Error::new( + item_fn.block.span(), + "Benchmark `#[block]` or `#[extrinsic_call]` item cannot be the \ + last statement of your benchmark function definition if you have \ + defined a return type. You should return something compatible \ + with Result<(), BenchmarkError> (i.e. `Ok(())`) as the last statement \ + or change your signature to a blank return type.", + )) + } + let Some(stmt) = item_fn.block.stmts.last() else { return missing_call(item_fn) }; + ( + Vec::from(&item_fn.block.stmts[(i + 1)..item_fn.block.stmts.len() - 1]), + Some(stmt.clone()), + ) + }, }; Ok(BenchmarkDef { params, setup_stmts: Vec::from(&item_fn.block.stmts[0..i]), call_def, - verify_stmts: Vec::from(&item_fn.block.stmts[(i + 1)..item_fn.block.stmts.len()]), - extra, - skip_meta, + verify_stmts, + last_stmt, + fn_sig: item_fn.sig.clone(), + fn_vis: item_fn.vis.clone(), + fn_attrs: item_fn.attrs.clone(), }) } } @@ -277,7 +357,7 @@ pub fn benchmarks( let mod_attrs: Vec<&Attribute> = module .attrs .iter() - .filter(|attr| syn::parse2::(attr.to_token_stream()).is_err()) + .filter(|attr| !attr.path().is_ident(keywords::BENCHMARKS_TOKEN)) .collect(); let mut benchmark_names: Vec = Vec::new(); @@ -293,35 +373,32 @@ pub fn benchmarks( let Item::Fn(func) = stmt else { return None }; // find #[benchmark] attribute on function def - let benchmark_attr = func.attrs.iter().find_map(|attr| { - let seg = attr.path.segments.last()?; - syn::parse::(seg.ident.to_token_stream().into()).ok()?; - Some(attr) - })?; + let benchmark_attr = + func.attrs.iter().find(|attr| attr.path().is_ident(keywords::BENCHMARK_TOKEN))?; Some((benchmark_attr.clone(), func.clone(), stmt)) }); // parse individual benchmark defs and args for (benchmark_attr, func, stmt) in benchmark_fn_metas { - // parse any args provided to #[benchmark] - let attr_tokens = benchmark_attr.tokens.to_token_stream().into(); - let benchmark_args: BenchmarkAttrs = syn::parse(attr_tokens)?; - // parse benchmark def - let benchmark_def = - BenchmarkDef::from(&func, benchmark_args.extra, benchmark_args.skip_meta)?; + let benchmark_def = BenchmarkDef::from(&func)?; // record benchmark name let name = &func.sig.ident; benchmark_names.push(name.clone()); - // record name sets - if benchmark_def.extra { - extra_benchmark_names.push(name.clone()); - } - if benchmark_def.skip_meta { - skip_meta_benchmark_names.push(name.clone()) + // Check if we need to parse any args + if benchmark_attr.meta.require_path_only().is_err() { + // parse any args provided to #[benchmark] + let benchmark_attrs: BenchmarkAttrs = benchmark_attr.parse_args()?; + + // record name sets + if benchmark_attrs.extra { + extra_benchmark_names.push(name.clone()); + } else if benchmark_attrs.skip_meta { + skip_meta_benchmark_names.push(name.clone()); + } } // expand benchmark @@ -438,6 +515,9 @@ pub fn benchmarks( #krate::BenchmarkMetadata { name: benchmark.as_bytes().to_vec(), components, + // TODO: Not supported by V2 syntax as of yet. + // https://github.com/paritytech/substrate/issues/13132 + pov_modes: vec![], } }).collect::<#krate::Vec<_>>() } @@ -466,7 +546,18 @@ pub fn benchmarks( #support::storage::transactional::TRANSACTION_LEVEL_KEY.into(), ); whitelist.push(transactional_layer_key); - #krate::benchmarking::set_whitelist(whitelist); + // Whitelist the `:extrinsic_index`. + let extrinsic_index = #krate::TrackedStorageKey::new( + #krate::well_known_keys::EXTRINSIC_INDEX.into() + ); + whitelist.push(extrinsic_index); + // Whitelist the `:intrablock_entropy`. + let intrablock_entropy = #krate::TrackedStorageKey::new( + #krate::well_known_keys::INTRABLOCK_ENTROPY.into() + ); + whitelist.push(intrablock_entropy); + + #krate::benchmarking::set_whitelist(whitelist.clone()); let mut results: #krate::Vec<#krate::BenchmarkResult> = #krate::Vec::new(); // Always do at least one internal repeat... @@ -489,6 +580,12 @@ pub fn benchmarks( // This will enable worst case scenario for reading from the database. #krate::benchmarking::commit_db(); + // Access all whitelisted keys to get them into the proof recorder since the + // recorder does now have a whitelist. + for key in &whitelist { + #krate::frame_support::storage::unhashed::get_raw(&key.key); + } + // Reset the read/write counter so we don't count operations in the setup process. #krate::benchmarking::reset_read_write_count(); @@ -590,7 +687,6 @@ pub fn benchmarks( struct UnrolledParams { param_ranges: Vec, param_names: Vec, - param_types: Vec, } impl UnrolledParams { @@ -600,8 +696,8 @@ impl UnrolledParams { .iter() .map(|p| { let name = Ident::new(&p.name, Span::call_site()); - let start = p.start; - let end = p.end; + let start = &p.start; + let end = &p.end; quote!(#name, #start, #end) }) .collect(); @@ -612,14 +708,7 @@ impl UnrolledParams { quote!(#name) }) .collect(); - let param_types: Vec = params - .iter() - .map(|p| { - let typ = &p.typ; - quote!(#typ) - }) - .collect(); - UnrolledParams { param_ranges, param_names, param_types } + UnrolledParams { param_ranges, param_names } } } @@ -635,18 +724,17 @@ fn expand_benchmark( Ok(ident) => ident, Err(err) => return err.to_compile_error().into(), }; - let home = quote!(#krate::frame_support::benchmarking); let codec = quote!(#krate::frame_support::codec); let traits = quote!(#krate::frame_support::traits); let setup_stmts = benchmark_def.setup_stmts; let verify_stmts = benchmark_def.verify_stmts; + let last_stmt = benchmark_def.last_stmt; let test_ident = Ident::new(format!("test_{}", name.to_string()).as_str(), Span::call_site()); // unroll params (prepare for quoting) let unrolled = UnrolledParams::from(&benchmark_def.params); let param_names = unrolled.param_names; let param_ranges = unrolled.param_ranges; - let param_types = unrolled.param_types; let type_use_generics = match is_instance { false => quote!(T), @@ -658,7 +746,8 @@ fn expand_benchmark( true => quote!(T: Config, I: 'static), }; - let (pre_call, post_call) = match benchmark_def.call_def { + // used in the benchmarking impls + let (pre_call, post_call, fn_call_body) = match &benchmark_def.call_def { BenchmarkCallDef::ExtrinsicCall { origin, expr_call, attr_span: _ } => { let mut expr_call = expr_call.clone(); @@ -670,21 +759,33 @@ fn expand_benchmark( } expr_call.args = final_args; + let origin = match origin { + Expr::Cast(t) => { + let ty = t.ty.clone(); + quote! { + <::RuntimeOrigin as From<#ty>>::from(#origin); + } + }, + _ => quote! { + #origin.into(); + }, + }; + // determine call name (handles `_` and normal call syntax) let expr_span = expr_call.span(); let call_err = || { - quote_spanned!(expr_span=> "Extrinsic call must be a function call or `_`".to_compile_error()).into() + syn::Error::new(expr_span, "Extrinsic call must be a function call or `_`") + .to_compile_error() }; let call_name = match *expr_call.func { Expr::Path(expr_path) => { // normal function call - let Some(segment) = expr_path.path.segments.last() else { return call_err(); }; + let Some(segment) = expr_path.path.segments.last() else { return call_err() }; segment.ident.to_string() }, - Expr::Verbatim(tokens) => { + Expr::Infer(_) => { // `_` style // replace `_` with fn name - let Ok(_) = syn::parse::(tokens.to_token_stream().into()) else { return call_err(); }; name.to_string() }, _ => return call_err(), @@ -692,7 +793,7 @@ fn expand_benchmark( // modify extrinsic call to be prefixed with "new_call_variant" let call_name = format!("new_call_variant_{}", call_name); - let mut punct: Punctuated = Punctuated::new(); + let mut punct: Punctuated = Punctuated::new(); punct.push(PathSegment { arguments: PathArguments::None, ident: Ident::new(call_name.as_str(), Span::call_site()), @@ -702,36 +803,92 @@ fn expand_benchmark( qself: None, path: Path { leading_colon: None, segments: punct }, }); - + let pre_call = quote! { + let __call = Call::<#type_use_generics>::#expr_call; + let __benchmarked_call_encoded = #codec::Encode::encode(&__call); + }; + let post_call = quote! { + let __call_decoded = as #codec::Decode> + ::decode(&mut &__benchmarked_call_encoded[..]) + .expect("call is encoded above, encoding must be correct"); + let __origin = #origin; + as #traits::UnfilteredDispatchable>::dispatch_bypass_filter( + __call_decoded, + __origin, + ) + }; ( - // (pre_call, post_call): + // (pre_call, post_call, fn_call_body): + pre_call.clone(), + quote!(#post_call?;), quote! { - let __call = Call::<#type_use_generics>::#expr_call; - let __benchmarked_call_encoded = #codec::Encode::encode(&__call); - }, - quote! { - let __call_decoded = as #codec::Decode> - ::decode(&mut &__benchmarked_call_encoded[..]) - .expect("call is encoded above, encoding must be correct"); - let __origin = #origin.into(); - as #traits::UnfilteredDispatchable>::dispatch_bypass_filter( - __call_decoded, - __origin, - )?; + #pre_call + #post_call.unwrap(); }, ) }, - BenchmarkCallDef::Block { block, attr_span: _ } => (quote!(), quote!(#block)), + BenchmarkCallDef::Block { block, attr_span: _ } => + (quote!(), quote!(#block), quote!(#block)), }; - // generate final quoted tokens - let res = quote! { - // compile-time assertions that each referenced param type implements ParamRange + let vis = benchmark_def.fn_vis; + + // remove #[benchmark] attribute + let fn_attrs = benchmark_def + .fn_attrs + .iter() + .filter(|attr| !attr.path().is_ident(keywords::BENCHMARK_TOKEN)); + + // modify signature generics, ident, and inputs, e.g: + // before: `fn bench(u: Linear<1, 100>) -> Result<(), BenchmarkError>` + // after: `fn _bench , I: 'static>(u: u32, verify: bool) -> Result<(), + // BenchmarkError>` + let mut sig = benchmark_def.fn_sig; + sig.generics = parse_quote!(<#type_impl_generics>); + if !where_clause.is_empty() { + sig.generics.where_clause = parse_quote!(where #where_clause); + } + sig.ident = + Ident::new(format!("_{}", name.to_token_stream().to_string()).as_str(), Span::call_site()); + let mut fn_param_inputs: Vec = + param_names.iter().map(|name| quote!(#name: u32)).collect(); + fn_param_inputs.push(quote!(verify: bool)); + sig.inputs = parse_quote!(#(#fn_param_inputs),*); + + // used in instance() impl + let impl_last_stmt = match &last_stmt { + Some(stmt) => quote!(#stmt), + None => quote!(Ok(())), + }; + let fn_attrs_clone = fn_attrs.clone(); + + let fn_def = quote! { #( - #home::assert_impl_all!(#param_types: #home::ParamRange); + #fn_attrs_clone )* + #vis #sig { + #( + #setup_stmts + )* + #fn_call_body + if verify { + #( + #verify_stmts + )* + } + #last_stmt + } + }; + + // generate final quoted tokens + let res = quote! { + // benchmark function definition + #fn_def #[allow(non_camel_case_types)] + #( + #fn_attrs + )* struct #name; #[allow(unused_variables)] @@ -770,7 +927,7 @@ fn expand_benchmark( #verify_stmts )* } - Ok(()) + #impl_last_stmt })) } } @@ -824,6 +981,9 @@ fn expand_benchmark( // Test the lowest, highest (if its different from the lowest) // and up to num_values-2 more equidistant values in between. // For 0..10 and num_values=6 this would mean: [0, 2, 4, 6, 8, 10] + if high < low { + return Err("The start of a `ParamRange` must be less than or equal to the end".into()); + } let mut values = #krate::vec![low]; let diff = (high - low).min(num_values - 1); diff --git a/frame/support/procedural/src/clone_no_bound.rs b/frame/support/procedural/src/clone_no_bound.rs index bd2741c0d47a..bbea2feffa96 100644 --- a/frame/support/procedural/src/clone_no_bound.rs +++ b/frame/support/procedural/src/clone_no_bound.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/construct_runtime/expand/call.rs b/frame/support/procedural/src/construct_runtime/expand/call.rs index 8f07448f5785..5ec665682dda 100644 --- a/frame/support/procedural/src/construct_runtime/expand/call.rs +++ b/frame/support/procedural/src/construct_runtime/expand/call.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/construct_runtime/expand/config.rs b/frame/support/procedural/src/construct_runtime/expand/config.rs index 9b731a5825a3..440e856221a3 100644 --- a/frame/support/procedural/src/construct_runtime/expand/config.rs +++ b/frame/support/procedural/src/construct_runtime/expand/config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +29,7 @@ pub fn expand_outer_config( ) -> TokenStream { let mut types = TokenStream::new(); let mut fields = TokenStream::new(); - let mut build_storage_calls = TokenStream::new(); + let mut genesis_build_calls = TokenStream::new(); let mut query_genesis_config_part_macros = Vec::new(); for decl in pallet_decls { @@ -52,8 +52,8 @@ pub fn expand_outer_config( types.extend(expand_config_types(attr, runtime, decl, &config, part_is_generic)); fields.extend(quote!(#attr pub #field_name: #config,)); - build_storage_calls - .extend(expand_config_build_storage_call(scrate, attr, runtime, decl, field_name)); + genesis_build_calls + .extend(expand_config_build_storage_call(scrate, &config, attr, field_name)); query_genesis_config_part_macros.push(quote! { #path::__substrate_genesis_config_check::is_genesis_config_defined!(#pallet_name); #[cfg(feature = "std")] @@ -67,30 +67,36 @@ pub fn expand_outer_config( #types - #[cfg(any(feature = "std", test))] use #scrate::serde as __genesis_config_serde_import__; - #[cfg(any(feature = "std", test))] #[derive(#scrate::serde::Serialize, #scrate::serde::Deserialize, Default)] #[serde(rename_all = "camelCase")] #[serde(deny_unknown_fields)] #[serde(crate = "__genesis_config_serde_import__")] - pub struct GenesisConfig { + pub struct RuntimeGenesisConfig { #fields } #[cfg(any(feature = "std", test))] - impl #scrate::sp_runtime::BuildStorage for GenesisConfig { + #[deprecated(note = "GenesisConfig is planned to be removed in December 2023. Use `RuntimeGenesisConfig` instead.")] + pub type GenesisConfig = RuntimeGenesisConfig; + + #[cfg(any(feature = "std", test))] + impl #scrate::sp_runtime::BuildStorage for RuntimeGenesisConfig { fn assimilate_storage( &self, storage: &mut #scrate::sp_runtime::Storage, ) -> std::result::Result<(), String> { - #build_storage_calls - #scrate::BasicExternalities::execute_with_storage(storage, || { - ::on_genesis(); - }); + ::build(&self); + Ok(()) + }) + } + } - Ok(()) + impl #scrate::traits::BuildGenesisConfig for RuntimeGenesisConfig { + fn build(&self) { + #genesis_build_calls + ::on_genesis(); } } } @@ -108,17 +114,14 @@ fn expand_config_types( match (decl.instance.as_ref(), part_is_generic) { (Some(inst), true) => quote! { #attr - #[cfg(any(feature = "std", test))] pub type #config = #path::GenesisConfig<#runtime, #path::#inst>; }, (None, true) => quote! { #attr - #[cfg(any(feature = "std", test))] pub type #config = #path::GenesisConfig<#runtime>; }, (_, false) => quote! { #attr - #[cfg(any(feature = "std", test))] pub type #config = #path::GenesisConfig; }, } @@ -126,21 +129,12 @@ fn expand_config_types( fn expand_config_build_storage_call( scrate: &TokenStream, + pallet_genesis_config: &Ident, attr: &TokenStream, - runtime: &Ident, - decl: &Pallet, field_name: &Ident, ) -> TokenStream { - let path = &decl.path; - let instance = if let Some(inst) = decl.instance.as_ref() { - quote!(#path::#inst) - } else { - quote!(#path::__InherentHiddenInstance) - }; - quote! { #attr - #scrate::sp_runtime::BuildModuleGenesisStorage:: - <#runtime, #instance>::build_module_genesis_storage(&self.#field_name, storage)?; + <#pallet_genesis_config as #scrate::traits::BuildGenesisConfig>::build(&self.#field_name); } } diff --git a/frame/support/procedural/src/construct_runtime/expand/event.rs b/frame/support/procedural/src/construct_runtime/expand/event.rs deleted file mode 100644 index b11fcef1bfd5..000000000000 --- a/frame/support/procedural/src/construct_runtime/expand/event.rs +++ /dev/null @@ -1,168 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2021-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 - -use crate::construct_runtime::Pallet; -use proc_macro2::TokenStream; -use quote::quote; -use std::str::FromStr; -use syn::{Generics, Ident}; - -pub fn expand_outer_event( - runtime: &Ident, - pallet_decls: &[Pallet], - scrate: &TokenStream, -) -> syn::Result { - let mut event_variants = TokenStream::new(); - let mut event_conversions = TokenStream::new(); - let mut query_event_part_macros = Vec::new(); - - for pallet_decl in pallet_decls { - if let Some(pallet_entry) = pallet_decl.find_part("Event") { - let path = &pallet_decl.path; - let pallet_name = &pallet_decl.name; - let index = pallet_decl.index; - let instance = pallet_decl.instance.as_ref(); - let generics = &pallet_entry.generics; - - if instance.is_some() && generics.params.is_empty() { - let msg = format!( - "Instantiable pallet with no generic `Event` cannot \ - be constructed: pallet `{}` must have generic `Event`", - pallet_name, - ); - return Err(syn::Error::new(pallet_name.span(), msg)) - } - - let part_is_generic = !generics.params.is_empty(); - let pallet_event = match (instance, part_is_generic) { - (Some(inst), true) => quote!(#path::Event::<#runtime, #path::#inst>), - (Some(inst), false) => quote!(#path::Event::<#path::#inst>), - (None, true) => quote!(#path::Event::<#runtime>), - (None, false) => quote!(#path::Event), - }; - - event_variants.extend(expand_event_variant( - runtime, - pallet_decl, - index, - instance, - generics, - )); - event_conversions.extend(expand_event_conversion(scrate, pallet_decl, &pallet_event)); - query_event_part_macros.push(quote! { - #path::__substrate_event_check::is_event_part_defined!(#pallet_name); - }); - } - } - - Ok(quote! { - #( #query_event_part_macros )* - - #[derive( - Clone, PartialEq, Eq, - #scrate::codec::Encode, - #scrate::codec::Decode, - #scrate::scale_info::TypeInfo, - #scrate::RuntimeDebug, - )] - #[allow(non_camel_case_types)] - pub enum RuntimeEvent { - #event_variants - } - - #event_conversions - }) -} - -fn expand_event_variant( - runtime: &Ident, - pallet: &Pallet, - index: u8, - instance: Option<&Ident>, - generics: &Generics, -) -> TokenStream { - let path = &pallet.path; - let variant_name = &pallet.name; - let part_is_generic = !generics.params.is_empty(); - let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); - - match instance { - Some(inst) if part_is_generic => quote! { - #attr - #[codec(index = #index)] - #variant_name(#path::Event<#runtime, #path::#inst>), - }, - Some(inst) => quote! { - #attr - #[codec(index = #index)] - #variant_name(#path::Event<#path::#inst>), - }, - None if part_is_generic => quote! { - #attr - #[codec(index = #index)] - #variant_name(#path::Event<#runtime>), - }, - None => quote! { - #attr - #[codec(index = #index)] - #variant_name(#path::Event), - }, - } -} - -fn expand_event_conversion( - scrate: &TokenStream, - pallet: &Pallet, - pallet_event: &TokenStream, -) -> TokenStream { - let variant_name = &pallet.name; - let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { - let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) - .expect("was successfully parsed before; qed"); - quote! { - #acc - #attr - } - }); - - quote! { - #attr - impl From<#pallet_event> for RuntimeEvent { - fn from(x: #pallet_event) -> Self { - RuntimeEvent::#variant_name(x) - } - } - #attr - impl TryInto<#pallet_event> for RuntimeEvent { - type Error = (); - - fn try_into(self) -> #scrate::sp_std::result::Result<#pallet_event, Self::Error> { - match self { - Self::#variant_name(evt) => Ok(evt), - _ => Err(()), - } - } - } - } -} diff --git a/frame/support/procedural/src/construct_runtime/expand/freeze_reason.rs b/frame/support/procedural/src/construct_runtime/expand/freeze_reason.rs new file mode 100644 index 000000000000..d357fab72115 --- /dev/null +++ b/frame/support/procedural/src/construct_runtime/expand/freeze_reason.rs @@ -0,0 +1,68 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License + +use crate::construct_runtime::{parse::PalletPath, Pallet}; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +pub fn expand_outer_freeze_reason(pallet_decls: &[Pallet], scrate: &TokenStream) -> TokenStream { + let mut conversion_fns = Vec::new(); + let mut freeze_reason_variants = Vec::new(); + for decl in pallet_decls { + if let Some(_) = decl.find_part("FreezeReason") { + let variant_name = &decl.name; + let path = &decl.path; + let index = decl.index; + + conversion_fns.push(expand_conversion_fn(path, variant_name)); + + freeze_reason_variants.push(expand_variant(index, path, variant_name)); + } + } + + quote! { + /// A reason for placing a freeze on funds. + #[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, + #scrate::codec::Encode, #scrate::codec::Decode, #scrate::codec::MaxEncodedLen, + #scrate::scale_info::TypeInfo, + #scrate::RuntimeDebug, + )] + pub enum RuntimeFreezeReason { + #( #freeze_reason_variants )* + } + + #( #conversion_fns )* + } +} + +fn expand_conversion_fn(path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + impl From<#path::FreezeReason> for RuntimeFreezeReason { + fn from(hr: #path::FreezeReason) -> Self { + RuntimeFreezeReason::#variant_name(hr) + } + } + } +} + +fn expand_variant(index: u8, path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + #[codec(index = #index)] + #variant_name(#path::FreezeReason), + } +} diff --git a/frame/support/procedural/src/construct_runtime/expand/hold_reason.rs b/frame/support/procedural/src/construct_runtime/expand/hold_reason.rs new file mode 100644 index 000000000000..6acfe5382f18 --- /dev/null +++ b/frame/support/procedural/src/construct_runtime/expand/hold_reason.rs @@ -0,0 +1,68 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License + +use crate::construct_runtime::{parse::PalletPath, Pallet}; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +pub fn expand_outer_hold_reason(pallet_decls: &[Pallet], scrate: &TokenStream) -> TokenStream { + let mut conversion_fns = Vec::new(); + let mut hold_reason_variants = Vec::new(); + for decl in pallet_decls { + if let Some(_) = decl.find_part("HoldReason") { + let variant_name = &decl.name; + let path = &decl.path; + let index = decl.index; + + conversion_fns.push(expand_conversion_fn(path, variant_name)); + + hold_reason_variants.push(expand_variant(index, path, variant_name)); + } + } + + quote! { + /// A reason for placing a hold on funds. + #[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, + #scrate::codec::Encode, #scrate::codec::Decode, #scrate::codec::MaxEncodedLen, + #scrate::scale_info::TypeInfo, + #scrate::RuntimeDebug, + )] + pub enum RuntimeHoldReason { + #( #hold_reason_variants )* + } + + #( #conversion_fns )* + } +} + +fn expand_conversion_fn(path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + impl From<#path::HoldReason> for RuntimeHoldReason { + fn from(hr: #path::HoldReason) -> Self { + RuntimeHoldReason::#variant_name(hr) + } + } + } +} + +fn expand_variant(index: u8, path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + #[codec(index = #index)] + #variant_name(#path::HoldReason), + } +} diff --git a/frame/support/procedural/src/construct_runtime/expand/inherent.rs b/frame/support/procedural/src/construct_runtime/expand/inherent.rs index 599b34ba8724..1dc51d9cd2bc 100644 --- a/frame/support/procedural/src/construct_runtime/expand/inherent.rs +++ b/frame/support/procedural/src/construct_runtime/expand/inherent.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,12 +19,12 @@ use crate::construct_runtime::Pallet; use proc_macro2::TokenStream; use quote::quote; use std::str::FromStr; -use syn::{Ident, TypePath}; +use syn::Ident; pub fn expand_outer_inherent( runtime: &Ident, - block: &TypePath, - unchecked_extrinsic: &TypePath, + block: &TokenStream, + unchecked_extrinsic: &TokenStream, pallet_decls: &[Pallet], scrate: &TokenStream, ) -> TokenStream { @@ -58,22 +58,22 @@ pub fn expand_outer_inherent( trait InherentDataExt { fn create_extrinsics(&self) -> - #scrate::inherent::Vec<<#block as #scrate::inherent::BlockT>::Extrinsic>; + #scrate::sp_std::vec::Vec<<#block as #scrate::sp_runtime::traits::Block>::Extrinsic>; fn check_extrinsics(&self, block: &#block) -> #scrate::inherent::CheckInherentsResult; } impl InherentDataExt for #scrate::inherent::InherentData { fn create_extrinsics(&self) -> - #scrate::inherent::Vec<<#block as #scrate::inherent::BlockT>::Extrinsic> + #scrate::sp_std::vec::Vec<<#block as #scrate::sp_runtime::traits::Block>::Extrinsic> { use #scrate::inherent::ProvideInherent; - let mut inherents = Vec::new(); + let mut inherents = #scrate::sp_std::vec::Vec::new(); #( #pallet_attrs if let Some(inherent) = #pallet_names::create_inherent(self) { - let inherent = <#unchecked_extrinsic as #scrate::inherent::Extrinsic>::new( + let inherent = <#unchecked_extrinsic as #scrate::sp_runtime::traits::Extrinsic>::new( inherent.into(), None, ).expect("Runtime UncheckedExtrinsic is not Opaque, so it has to return \ @@ -90,13 +90,41 @@ pub fn expand_outer_inherent( use #scrate::inherent::{ProvideInherent, IsFatalError}; use #scrate::traits::{IsSubType, ExtrinsicCall}; use #scrate::sp_runtime::traits::Block as _; + use #scrate::_private::sp_inherents::Error; + use #scrate::log; let mut result = #scrate::inherent::CheckInherentsResult::new(); + // This handle assume we abort on the first fatal error. + fn handle_put_error_result(res: Result<(), Error>) { + const LOG_TARGET: &str = "runtime::inherent"; + match res { + Ok(()) => (), + Err(Error::InherentDataExists(id)) => + log::debug!( + target: LOG_TARGET, + "Some error already reported for inherent {:?}, new non fatal \ + error is ignored", + id + ), + Err(Error::FatalErrorReported) => + log::error!( + target: LOG_TARGET, + "Fatal error already reported, unexpected considering there is \ + only one fatal error", + ), + Err(_) => + log::error!( + target: LOG_TARGET, + "Unexpected error from `put_error` operation", + ), + } + } + for xt in block.extrinsics() { // Inherents are before any other extrinsics. // And signed extrinsics are not inherents. - if #scrate::inherent::Extrinsic::is_signed(xt).unwrap_or(false) { + if #scrate::sp_runtime::traits::Extrinsic::is_signed(xt).unwrap_or(false) { break } @@ -110,9 +138,9 @@ pub fn expand_outer_inherent( if #pallet_names::is_inherent(call) { is_inherent = true; if let Err(e) = #pallet_names::check_inherent(call, self) { - result.put_error( + handle_put_error_result(result.put_error( #pallet_names::INHERENT_IDENTIFIER, &e - ).expect("There is only one fatal error; qed"); + )); if e.is_fatal_error() { return result; } @@ -134,7 +162,7 @@ pub fn expand_outer_inherent( match #pallet_names::is_inherent_required(self) { Ok(Some(e)) => { let found = block.extrinsics().iter().any(|xt| { - let is_signed = #scrate::inherent::Extrinsic::is_signed(xt) + let is_signed = #scrate::sp_runtime::traits::Extrinsic::is_signed(xt) .unwrap_or(false); if !is_signed { @@ -153,9 +181,9 @@ pub fn expand_outer_inherent( }); if !found { - result.put_error( + handle_put_error_result(result.put_error( #pallet_names::INHERENT_IDENTIFIER, &e - ).expect("There is only one fatal error; qed"); + )); if e.is_fatal_error() { return result; } @@ -163,9 +191,9 @@ pub fn expand_outer_inherent( }, Ok(None) => (), Err(e) => { - result.put_error( + handle_put_error_result(result.put_error( #pallet_names::INHERENT_IDENTIFIER, &e - ).expect("There is only one fatal error; qed"); + )); if e.is_fatal_error() { return result; } @@ -186,7 +214,8 @@ pub fn expand_outer_inherent( let mut first_signed_observed = false; for (i, xt) in block.extrinsics().iter().enumerate() { - let is_signed = #scrate::inherent::Extrinsic::is_signed(xt).unwrap_or(false); + let is_signed = #scrate::sp_runtime::traits::Extrinsic::is_signed(xt) + .unwrap_or(false); let is_inherent = if is_signed { // Signed extrinsics are not inherents. diff --git a/frame/support/procedural/src/construct_runtime/expand/lock_id.rs b/frame/support/procedural/src/construct_runtime/expand/lock_id.rs new file mode 100644 index 000000000000..d7031dcaf552 --- /dev/null +++ b/frame/support/procedural/src/construct_runtime/expand/lock_id.rs @@ -0,0 +1,68 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License + +use crate::construct_runtime::{parse::PalletPath, Pallet}; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +pub fn expand_outer_lock_id(pallet_decls: &[Pallet], scrate: &TokenStream) -> TokenStream { + let mut conversion_fns = Vec::new(); + let mut lock_id_variants = Vec::new(); + for decl in pallet_decls { + if let Some(_) = decl.find_part("LockId") { + let variant_name = &decl.name; + let path = &decl.path; + let index = decl.index; + + conversion_fns.push(expand_conversion_fn(path, variant_name)); + + lock_id_variants.push(expand_variant(index, path, variant_name)); + } + } + + quote! { + /// An identifier for each lock placed on funds. + #[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, + #scrate::codec::Encode, #scrate::codec::Decode, #scrate::codec::MaxEncodedLen, + #scrate::scale_info::TypeInfo, + #scrate::RuntimeDebug, + )] + pub enum RuntimeLockId { + #( #lock_id_variants )* + } + + #( #conversion_fns )* + } +} + +fn expand_conversion_fn(path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + impl From<#path::LockId> for RuntimeLockId { + fn from(hr: #path::LockId) -> Self { + RuntimeLockId::#variant_name(hr) + } + } + } +} + +fn expand_variant(index: u8, path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + #[codec(index = #index)] + #variant_name(#path::LockId), + } +} diff --git a/frame/support/procedural/src/construct_runtime/expand/metadata.rs b/frame/support/procedural/src/construct_runtime/expand/metadata.rs index ec90a0d30f98..0975fedb35d5 100644 --- a/frame/support/procedural/src/construct_runtime/expand/metadata.rs +++ b/frame/support/procedural/src/construct_runtime/expand/metadata.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,17 +15,18 @@ // See the License for the specific language governing permissions and // limitations under the License -use crate::construct_runtime::Pallet; +use crate::construct_runtime::{parse::PalletPath, Pallet}; use proc_macro2::TokenStream; use quote::quote; use std::str::FromStr; -use syn::{Ident, TypePath}; +use syn::Ident; pub fn expand_runtime_metadata( runtime: &Ident, pallet_declarations: &[Pallet], scrate: &TokenStream, - extrinsic: &TypePath, + extrinsic: &TokenStream, + system_path: &PalletPath, ) -> TokenStream { let pallets = pallet_declarations .iter() @@ -48,6 +49,7 @@ pub fn expand_runtime_metadata( let event = expand_pallet_metadata_events(&filtered_names, runtime, scrate, decl); let constants = expand_pallet_metadata_constants(runtime, decl); let errors = expand_pallet_metadata_errors(runtime, decl); + let docs = expand_pallet_metadata_docs(runtime, decl); let attr = decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) .expect("was successfully parsed before; qed"); @@ -59,7 +61,7 @@ pub fn expand_runtime_metadata( quote! { #attr - #scrate::metadata::PalletMetadata { + #scrate::metadata_ir::PalletMetadataIR { name: stringify!(#name), index: #index, storage: #storage, @@ -67,6 +69,7 @@ pub fn expand_runtime_metadata( event: #event, constants: #constants, error: #errors, + docs: #docs, } } }) @@ -74,27 +77,87 @@ pub fn expand_runtime_metadata( quote! { impl #runtime { - pub fn metadata() -> #scrate::metadata::RuntimeMetadataPrefixed { - #scrate::metadata::RuntimeMetadataLastVersion::new( - #scrate::sp_std::vec![ #(#pallets),* ], - #scrate::metadata::ExtrinsicMetadata { - ty: #scrate::scale_info::meta_type::<#extrinsic>(), + fn metadata_ir() -> #scrate::metadata_ir::MetadataIR { + // Each runtime must expose the `runtime_metadata()` to fetch the runtime API metadata. + // The function is implemented by calling `impl_runtime_apis!`. + // + // However, the `construct_runtime!` may be called without calling `impl_runtime_apis!`. + // Rely on the `Deref` trait to differentiate between a runtime that implements + // APIs (by macro impl_runtime_apis!) and a runtime that is simply created (by macro construct_runtime!). + // + // Both `InternalConstructRuntime` and `InternalImplRuntimeApis` expose a `runtime_metadata()` function. + // `InternalConstructRuntime` is implemented by the `construct_runtime!` for Runtime references (`& Runtime`), + // while `InternalImplRuntimeApis` is implemented by the `impl_runtime_apis!` for Runtime (`Runtime`). + // + // Therefore, the `Deref` trait will resolve the `runtime_metadata` from `impl_runtime_apis!` + // when both macros are called; and will resolve an empty `runtime_metadata` when only the `construct_runtime!` + // is called. + // + // `Deref` needs a reference for resolving the function call. + let rt = #runtime; + + let ty = #scrate::scale_info::meta_type::<#extrinsic>(); + let address_ty = #scrate::scale_info::meta_type::< + <<#extrinsic as #scrate::sp_runtime::traits::Extrinsic>::SignaturePayload as #scrate::sp_runtime::traits::SignaturePayload>::SignatureAddress + >(); + let call_ty = #scrate::scale_info::meta_type::< + <#extrinsic as #scrate::sp_runtime::traits::Extrinsic>::Call + >(); + let signature_ty = #scrate::scale_info::meta_type::< + <<#extrinsic as #scrate::sp_runtime::traits::Extrinsic>::SignaturePayload as #scrate::sp_runtime::traits::SignaturePayload>::Signature + >(); + let extra_ty = #scrate::scale_info::meta_type::< + <<#extrinsic as #scrate::sp_runtime::traits::Extrinsic>::SignaturePayload as #scrate::sp_runtime::traits::SignaturePayload>::SignatureExtra + >(); + + #scrate::metadata_ir::MetadataIR { + pallets: #scrate::sp_std::vec![ #(#pallets),* ], + extrinsic: #scrate::metadata_ir::ExtrinsicMetadataIR { + ty, version: <#extrinsic as #scrate::sp_runtime::traits::ExtrinsicMetadata>::VERSION, + address_ty, + call_ty, + signature_ty, + extra_ty, signed_extensions: < < #extrinsic as #scrate::sp_runtime::traits::ExtrinsicMetadata >::SignedExtensions as #scrate::sp_runtime::traits::SignedExtension >::metadata() .into_iter() - .map(|meta| #scrate::metadata::SignedExtensionMetadata { + .map(|meta| #scrate::metadata_ir::SignedExtensionMetadataIR { identifier: meta.identifier, ty: meta.ty, additional_signed: meta.additional_signed, }) .collect(), }, - #scrate::scale_info::meta_type::<#runtime>() - ).into() + ty: #scrate::scale_info::meta_type::<#runtime>(), + apis: (&rt).runtime_metadata(), + outer_enums: #scrate::metadata_ir::OuterEnumsIR { + call_enum_ty: #scrate::scale_info::meta_type::< + <#runtime as #system_path::Config>::RuntimeCall + >(), + event_enum_ty: #scrate::scale_info::meta_type::(), + error_enum_ty: #scrate::scale_info::meta_type::(), + } + } + } + + pub fn metadata() -> #scrate::metadata::RuntimeMetadataPrefixed { + // Note: this always returns the V14 version. The runtime API function + // must be deprecated. + #scrate::metadata_ir::into_v14(#runtime::metadata_ir()) + } + + pub fn metadata_at_version(version: u32) -> Option<#scrate::OpaqueMetadata> { + #scrate::metadata_ir::into_version(#runtime::metadata_ir(), version).map(|prefixed| { + #scrate::OpaqueMetadata::new(prefixed.into()) + }) + } + + pub fn metadata_versions() -> #scrate::sp_std::vec::Vec { + #scrate::metadata_ir::supported_versions() } } } @@ -157,7 +220,7 @@ fn expand_pallet_metadata_events( quote! { Some( - #scrate::metadata::PalletEventMetadata { + #scrate::metadata_ir::PalletEventMetadataIR { ty: #scrate::scale_info::meta_type::<#pallet_event>() } ) @@ -184,3 +247,12 @@ fn expand_pallet_metadata_errors(runtime: &Ident, decl: &Pallet) -> TokenStream #path::Pallet::<#runtime #(, #path::#instance)*>::error_metadata() } } + +fn expand_pallet_metadata_docs(runtime: &Ident, decl: &Pallet) -> TokenStream { + let path = &decl.path; + let instance = decl.instance.as_ref().into_iter(); + + quote! { + #path::Pallet::<#runtime #(, #path::#instance)*>::pallet_documentation_metadata() + } +} diff --git a/frame/support/procedural/src/construct_runtime/expand/mod.rs b/frame/support/procedural/src/construct_runtime/expand/mod.rs index 6c92d2c3444e..830338f9265f 100644 --- a/frame/support/procedural/src/construct_runtime/expand/mod.rs +++ b/frame/support/procedural/src/construct_runtime/expand/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,16 +17,24 @@ mod call; mod config; -mod event; +mod freeze_reason; +mod hold_reason; mod inherent; +mod lock_id; mod metadata; mod origin; +mod outer_enums; +mod slash_reason; mod unsigned; pub use call::expand_outer_dispatch; pub use config::expand_outer_config; -pub use event::expand_outer_event; +pub use freeze_reason::expand_outer_freeze_reason; +pub use hold_reason::expand_outer_hold_reason; pub use inherent::expand_outer_inherent; +pub use lock_id::expand_outer_lock_id; pub use metadata::expand_runtime_metadata; pub use origin::expand_outer_origin; +pub use outer_enums::{expand_outer_enum, OuterEnumType}; +pub use slash_reason::expand_outer_slash_reason; pub use unsigned::expand_outer_validate_unsigned; diff --git a/frame/support/procedural/src/construct_runtime/expand/origin.rs b/frame/support/procedural/src/construct_runtime/expand/origin.rs index 1551d85ea4c9..0eaa73f60306 100644 --- a/frame/support/procedural/src/construct_runtime/expand/origin.rs +++ b/frame/support/procedural/src/construct_runtime/expand/origin.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -418,6 +418,34 @@ fn expand_origin_pallet_conversions( } } } + + #attr + impl<'a> TryFrom<&'a OriginCaller> for &'a #pallet_origin { + type Error = (); + fn try_from( + x: &'a OriginCaller, + ) -> #scrate::sp_std::result::Result<&'a #pallet_origin, ()> { + if let OriginCaller::#variant_name(l) = x { + Ok(&l) + } else { + Err(()) + } + } + } + + #attr + impl<'a> TryFrom<&'a RuntimeOrigin> for &'a #pallet_origin { + type Error = (); + fn try_from( + x: &'a RuntimeOrigin, + ) -> #scrate::sp_std::result::Result<&'a #pallet_origin, ()> { + if let OriginCaller::#variant_name(l) = &x.caller { + Ok(&l) + } else { + Err(()) + } + } + } } } diff --git a/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs b/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs new file mode 100644 index 000000000000..b78360d5fc05 --- /dev/null +++ b/frame/support/procedural/src/construct_runtime/expand/outer_enums.rs @@ -0,0 +1,281 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License + +use crate::construct_runtime::Pallet; +use proc_macro2::{Span, TokenStream}; +use quote::{quote, ToTokens}; +use std::str::FromStr; +use syn::{Generics, Ident}; + +/// Represents the types supported for creating an outer enum. +#[derive(Clone, Copy, PartialEq)] +pub enum OuterEnumType { + /// Collects the Event enums from all pallets. + Event, + /// Collects the Error enums from all pallets. + Error, +} + +impl OuterEnumType { + /// The name of the structure this enum represents. + fn struct_name(&self) -> &str { + match self { + OuterEnumType::Event => "RuntimeEvent", + OuterEnumType::Error => "RuntimeError", + } + } + + /// The name of the variant (ie `Event` or `Error`). + fn variant_name(&self) -> &str { + match self { + OuterEnumType::Event => "Event", + OuterEnumType::Error => "Error", + } + } +} + +impl ToTokens for OuterEnumType { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + OuterEnumType::Event => quote!(Event).to_tokens(tokens), + OuterEnumType::Error => quote!(Error).to_tokens(tokens), + } + } +} + +/// Create an outer enum that encapsulates all pallets as variants. +/// +/// Each variant represents a pallet and contains the corresponding type declared with either: +/// - #[pallet::event] for the [`OuterEnumType::Event`] variant +/// - #[pallet::error] for the [`OuterEnumType::Error`] variant +/// +/// The name of the outer enum is prefixed with Runtime, resulting in names like RuntimeEvent +/// or RuntimeError. +/// +/// This structure facilitates the decoding process by leveraging the metadata. +/// +/// # Example +/// +/// The code generate looks like the following for [`OuterEnumType::Event`]. +/// +/// ```ignore +/// enum RuntimeEvent { +/// #[codec(index = 0)] +/// System(pallet_system::Event), +/// +/// #[codec(index = 5)] +/// Balances(pallet_system::Event), +/// } +/// ``` +/// +/// Notice that the pallet index is preserved using the `#[codec(index = ..)]` attribute. +pub fn expand_outer_enum( + runtime: &Ident, + pallet_decls: &[Pallet], + scrate: &TokenStream, + enum_ty: OuterEnumType, +) -> syn::Result { + // Stores all pallet variants. + let mut enum_variants = TokenStream::new(); + // Generates the enum conversion between the `Runtime` outer enum and the pallet's enum. + let mut enum_conversions = TokenStream::new(); + // Specific for events to query via `is_event_part_defined!`. + let mut query_enum_part_macros = Vec::new(); + + let enum_name_str = enum_ty.variant_name(); + let enum_name_ident = Ident::new(enum_ty.struct_name(), Span::call_site()); + + for pallet_decl in pallet_decls { + let Some(pallet_entry) = pallet_decl.find_part(enum_name_str) else { continue }; + + let path = &pallet_decl.path; + let pallet_name = &pallet_decl.name; + let index = pallet_decl.index; + let instance = pallet_decl.instance.as_ref(); + let generics = &pallet_entry.generics; + + if instance.is_some() && generics.params.is_empty() { + let msg = format!( + "Instantiable pallet with no generic `{}` cannot \ + be constructed: pallet `{}` must have generic `{}`", + enum_name_str, pallet_name, enum_name_str, + ); + return Err(syn::Error::new(pallet_name.span(), msg)) + } + + let part_is_generic = !generics.params.is_empty(); + let pallet_enum = match (instance, part_is_generic) { + (Some(inst), true) => quote!(#path::#enum_ty::<#runtime, #path::#inst>), + (Some(inst), false) => quote!(#path::#enum_ty::<#path::#inst>), + (None, true) => quote!(#path::#enum_ty::<#runtime>), + (None, false) => quote!(#path::#enum_ty), + }; + + enum_variants.extend(expand_enum_variant( + runtime, + pallet_decl, + index, + instance, + generics, + enum_ty, + )); + enum_conversions.extend(expand_enum_conversion( + scrate, + pallet_decl, + &pallet_enum, + &enum_name_ident, + )); + + if enum_ty == OuterEnumType::Event { + query_enum_part_macros.push(quote! { + #path::__substrate_event_check::is_event_part_defined!(#pallet_name); + }); + } + } + + // Derives specific for the event. + let event_custom_derives = + if enum_ty == OuterEnumType::Event { quote!(Clone, PartialEq, Eq,) } else { quote!() }; + + // Implementation specific for errors. + let error_custom_impl = generate_error_impl(scrate, enum_ty); + + Ok(quote! { + #( #query_enum_part_macros )* + + #[derive( + #event_custom_derives + #scrate::codec::Encode, + #scrate::codec::Decode, + #scrate::scale_info::TypeInfo, + #scrate::RuntimeDebug, + )] + #[allow(non_camel_case_types)] + pub enum #enum_name_ident { + #enum_variants + } + + #enum_conversions + + #error_custom_impl + }) +} + +fn expand_enum_variant( + runtime: &Ident, + pallet: &Pallet, + index: u8, + instance: Option<&Ident>, + generics: &Generics, + enum_ty: OuterEnumType, +) -> TokenStream { + let path = &pallet.path; + let variant_name = &pallet.name; + let part_is_generic = !generics.params.is_empty(); + let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); + + match instance { + Some(inst) if part_is_generic => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::#enum_ty<#runtime, #path::#inst>), + }, + Some(inst) => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::#enum_ty<#path::#inst>), + }, + None if part_is_generic => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::#enum_ty<#runtime>), + }, + None => quote! { + #attr + #[codec(index = #index)] + #variant_name(#path::#enum_ty), + }, + } +} + +fn expand_enum_conversion( + scrate: &TokenStream, + pallet: &Pallet, + pallet_enum: &TokenStream, + enum_name_ident: &Ident, +) -> TokenStream { + let variant_name = &pallet.name; + let attr = pallet.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { + let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) + .expect("was successfully parsed before; qed"); + quote! { + #acc + #attr + } + }); + + quote! { + #attr + impl From<#pallet_enum> for #enum_name_ident { + fn from(x: #pallet_enum) -> Self { + #enum_name_ident + ::#variant_name(x) + } + } + #attr + impl TryInto<#pallet_enum> for #enum_name_ident { + type Error = (); + + fn try_into(self) -> #scrate::sp_std::result::Result<#pallet_enum, Self::Error> { + match self { + Self::#variant_name(evt) => Ok(evt), + _ => Err(()), + } + } + } + } +} + +fn generate_error_impl(scrate: &TokenStream, enum_ty: OuterEnumType) -> TokenStream { + // Implementation is specific to `Error`s. + if enum_ty == OuterEnumType::Event { + return quote! {} + } + + let enum_name_ident = Ident::new(enum_ty.struct_name(), Span::call_site()); + + quote! { + impl #enum_name_ident { + /// Optionally convert the `DispatchError` into the `RuntimeError`. + /// + /// Returns `Some` if the error matches the `DispatchError::Module` variant, otherwise `None`. + pub fn from_dispatch_error(err: #scrate::sp_runtime::DispatchError) -> Option { + let #scrate::sp_runtime::DispatchError::Module(module_error) = err else { return None }; + + let bytes = #scrate::codec::Encode::encode(&module_error); + #scrate::codec::Decode::decode(&mut &bytes[..]).ok() + } + } + } +} diff --git a/frame/support/procedural/src/construct_runtime/expand/slash_reason.rs b/frame/support/procedural/src/construct_runtime/expand/slash_reason.rs new file mode 100644 index 000000000000..abffb97d4398 --- /dev/null +++ b/frame/support/procedural/src/construct_runtime/expand/slash_reason.rs @@ -0,0 +1,68 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License + +use crate::construct_runtime::{parse::PalletPath, Pallet}; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +pub fn expand_outer_slash_reason(pallet_decls: &[Pallet], scrate: &TokenStream) -> TokenStream { + let mut conversion_fns = Vec::new(); + let mut slash_reason_variants = Vec::new(); + for decl in pallet_decls { + if let Some(_) = decl.find_part("SlashReason") { + let variant_name = &decl.name; + let path = &decl.path; + let index = decl.index; + + conversion_fns.push(expand_conversion_fn(path, variant_name)); + + slash_reason_variants.push(expand_variant(index, path, variant_name)); + } + } + + quote! { + /// A reason for slashing funds. + #[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, + #scrate::codec::Encode, #scrate::codec::Decode, #scrate::codec::MaxEncodedLen, + #scrate::scale_info::TypeInfo, + #scrate::RuntimeDebug, + )] + pub enum RuntimeSlashReason { + #( #slash_reason_variants )* + } + + #( #conversion_fns )* + } +} + +fn expand_conversion_fn(path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + impl From<#path::SlashReason> for RuntimeSlashReason { + fn from(hr: #path::SlashReason) -> Self { + RuntimeSlashReason::#variant_name(hr) + } + } + } +} + +fn expand_variant(index: u8, path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + #[codec(index = #index)] + #variant_name(#path::SlashReason), + } +} diff --git a/frame/support/procedural/src/construct_runtime/expand/unsigned.rs b/frame/support/procedural/src/construct_runtime/expand/unsigned.rs index 1d528779423a..33aadba0d1f1 100644 --- a/frame/support/procedural/src/construct_runtime/expand/unsigned.rs +++ b/frame/support/procedural/src/construct_runtime/expand/unsigned.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/construct_runtime/mod.rs b/frame/support/procedural/src/construct_runtime/mod.rs index 9e22037a6782..efc224415447 100644 --- a/frame/support/procedural/src/construct_runtime/mod.rs +++ b/frame/support/procedural/src/construct_runtime/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,17 +20,69 @@ //! `construct_runtime` implementation is recursive and can generate code which will call itself in //! order to get all the pallet parts for each pallet. //! -//! Pallets define their parts (`Call`, `Storage`, ..) either explicitly with the syntax -//! `::{Call, ...}` or implicitly. +//! Pallets can define their parts: +//! - Implicitely: `System: frame_system` +//! - Explicitly: `System: frame_system::{Pallet, Call}` //! -//! In case a pallet defines its parts implicitly, then the pallet must provide the -//! `tt_default_parts` macro. `construct_rutime` will generate some code which utilizes `tt_call` -//! to call the `tt_default_parts` macro of the pallet. `tt_default_parts` will then return the -//! default pallet parts as input tokens to the `match_and_replace` macro, which ultimately -//! generates a call to `construct_runtime` again, this time with all the pallet parts explicitly -//! defined. +//! The `construct_runtime` transitions from the implicit definition to the explict one. +//! From the explicit state, Substrate expands the pallets with additional information +//! that is to be included in the runtime metadata. This expansion makes visible some extra +//! parts of the pallets, mainly the `Error` if defined. The expanded state looks like +//! `System: frame_system expanded::{Error} ::{Pallet, Call}` and concatenates the extra expanded +//! parts with the user-provided parts. For example, the `Pallet`, `Call` and `Error` parts are +//! collected. +//! +//! Pallets must provide the `tt_extra_parts` and `tt_default_parts` macros for these transitions. +//! These are automatically implemented by the `#[pallet::pallet]` macro. +//! +//! This macro also generates the following enums for ease of decoding: +//! - `enum RuntimeCall`: This type contains the information needed to decode extrinsics. +//! - `enum RuntimeEvent`: This type contains the information needed to decode events. +//! - `enum RuntimeError`: While this cannot be used directly to decode `sp_runtime::DispatchError` +//! from the chain, it contains the information needed to decode the +//! `sp_runtime::DispatchError::Module`. +//! +//! # State Transitions +//! +//! ```ignore +//! +----------+ +//! | Implicit | -----------+ +//! +----------+ | +//! | | +//! v v +//! +----------+ +------------------+ +//! | Explicit | --> | ExplicitExpanded | +//! +----------+ +------------------+ +//! ``` +//! +//! When all pallet parts are implcit, then the `construct_runtime!` macro expands to its final +//! state, the `ExplicitExpanded`. Otherwise, all implicit parts are converted to an explicit +//! expanded part allow the `construct_runtime!` to expand any remaining explicit parts to an +//! explicit expanded part. +//! +//! # Implicit to Explicit +//! +//! The `construct_runtime` macro transforms the implicit declaration of each pallet +//! `System: frame_system` to an explicit one `System: frame_system::{Pallet, Call}` using the +//! `tt_default_parts` macro. +//! +//! The `tt_default_parts` macro exposes a comma separated list of pallet parts. For example, the +//! `Event` part is exposed only if the pallet implements an event via `#[pallet::event]` macro. +//! The tokens generated by this macro are ` expanded :: { Pallet, Call }` for our example. +//! +//! The `match_and_insert` macro takes in 3 arguments: +//! - target: This is the `TokenStream` that contains the `construct_runtime!` macro. +//! - pattern: The pattern to match against in the target stream. +//! - tokens: The tokens to added after the pattern match. +//! +//! The `construct_runtime` macro uses the `tt_call` to get the default pallet parts via +//! the `tt_default_parts` macro defined by each pallet. The pallet parts are then returned as +//! input to the `match_and_replace` macro. +//! The `match_and_replace` then will modify the the `construct_runtime!` to expand the implicit +//! definition to the explicit one. +//! +//! For example, //! -//! E.g. //! ```ignore //! construct_runtime!( //! //... @@ -106,6 +158,7 @@ //! tokens = [{ ::{Pallet, Call} }] //! } //! ``` +//! //! Which will then finally expand to the following: //! ```ignore //! construct_runtime!( @@ -116,6 +169,7 @@ //! } //! ) //! ``` +//! //! This call has no implicit pallet parts, thus it will expand to the runtime construction: //! ```ignore //! pub struct Runtime { ... } @@ -140,6 +194,19 @@ //! | w/ pallet parts | //! +--------------------+ //! ``` +//! +//! # Explicit to Explicit Expanded +//! +//! Users normally do not care about this transition. +//! +//! Similarly to the previous transition, the macro expansion transforms `System: +//! frame_system::{Pallet, Call}` into `System: frame_system expanded::{Error} ::{Pallet, Call}`. +//! The `expanded` section adds extra parts that the Substrate would like to expose for each pallet +//! by default. This is done to expose the approprite types for metadata construction. +//! +//! This time, instead of calling `tt_default_parts` we are using the `tt_extra_parts` macro. +//! This macro returns the ` :: expanded { Error }` list of additional parts we would like to +//! expose. mod expand; mod parse; @@ -149,15 +216,12 @@ use frame_support_procedural_tools::{ generate_crate_access, generate_crate_access_2018, generate_hidden_includes, }; use itertools::Itertools; -use parse::{ - ExplicitRuntimeDeclaration, ImplicitRuntimeDeclaration, Pallet, RuntimeDeclaration, - WhereSection, -}; +use parse::{ExplicitRuntimeDeclaration, ImplicitRuntimeDeclaration, Pallet, RuntimeDeclaration}; use proc_macro::TokenStream; use proc_macro2::TokenStream as TokenStream2; use quote::quote; use std::{collections::HashSet, str::FromStr}; -use syn::{Ident, Result}; +use syn::{spanned::Spanned, Ident, Result}; /// The fixed name of the system pallet. const SYSTEM_PALLET_NAME: &str = "System"; @@ -170,17 +234,40 @@ pub fn construct_runtime(input: TokenStream) -> TokenStream { let res = match definition { RuntimeDeclaration::Implicit(implicit_def) => - construct_runtime_intermediary_expansion(input_copy.into(), implicit_def), - RuntimeDeclaration::Explicit(explicit_decl) => - construct_runtime_final_expansion(explicit_decl), + check_pallet_number(input_copy.clone().into(), implicit_def.pallets.len()).and_then( + |_| construct_runtime_implicit_to_explicit(input_copy.into(), implicit_def), + ), + RuntimeDeclaration::Explicit(explicit_decl) => check_pallet_number( + input_copy.clone().into(), + explicit_decl.pallets.len(), + ) + .and_then(|_| { + construct_runtime_explicit_to_explicit_expanded(input_copy.into(), explicit_decl) + }), + RuntimeDeclaration::ExplicitExpanded(explicit_decl) => + check_pallet_number(input_copy.into(), explicit_decl.pallets.len()) + .and_then(|_| construct_runtime_final_expansion(explicit_decl)), }; - res.unwrap_or_else(|e| e.to_compile_error()).into() + let res = res.unwrap_or_else(|e| e.to_compile_error()); + + let res = expander::Expander::new("construct_runtime") + .dry(std::env::var("FRAME_EXPAND").is_err()) + .verbose(true) + .write_to_out_dir(res) + .expect("Does not fail because of IO in OUT_DIR; qed"); + + res.into() } -/// When some pallet have implicit parts definition then the macro will expand into a macro call to -/// `construct_runtime_args` of each pallets, see root documentation. -fn construct_runtime_intermediary_expansion( +/// All pallets that have implicit pallet parts (ie `System: frame_system`) are +/// expanded with the default parts defined by the pallet's `tt_default_parts` macro. +/// +/// This function transforms the [`RuntimeDeclaration::Implicit`] into +/// [`RuntimeDeclaration::Explicit`] that is not yet fully expanded. +/// +/// For more details, please refer to the root documentation. +fn construct_runtime_implicit_to_explicit( input: TokenStream2, definition: ImplicitRuntimeDeclaration, ) -> Result { @@ -207,23 +294,54 @@ fn construct_runtime_intermediary_expansion( Ok(expansion) } +/// All pallets that have +/// (I): explicit pallet parts (ie `System: frame_system::{Pallet, Call}`) and +/// (II): are not fully expanded (ie do not include the `Error` expansion part) +/// are fully expanded by including the parts from the pallet's `tt_extra_parts` macro. +/// +/// This function transforms the [`RuntimeDeclaration::Explicit`] that is not yet fully expanded +/// into [`RuntimeDeclaration::ExplicitExpanded`] fully expanded. +/// +/// For more details, please refer to the root documentation. +fn construct_runtime_explicit_to_explicit_expanded( + input: TokenStream2, + definition: ExplicitRuntimeDeclaration, +) -> Result { + let frame_support = generate_crate_access_2018("frame-support")?; + let mut expansion = quote::quote!( + #frame_support::construct_runtime! { #input } + ); + for pallet in definition.pallets.iter().filter(|pallet| !pallet.is_expanded) { + let pallet_path = &pallet.path; + let pallet_name = &pallet.name; + let pallet_instance = pallet.instance.as_ref().map(|instance| quote::quote!(::<#instance>)); + expansion = quote::quote!( + #frame_support::tt_call! { + macro = [{ #pallet_path::tt_extra_parts }] + frame_support = [{ #frame_support }] + ~~> #frame_support::match_and_insert! { + target = [{ #expansion }] + pattern = [{ #pallet_name: #pallet_path #pallet_instance }] + } + } + ); + } + + Ok(expansion) +} + /// All pallets have explicit definition of parts, this will expand to the runtime declaration. fn construct_runtime_final_expansion( definition: ExplicitRuntimeDeclaration, ) -> Result { - let ExplicitRuntimeDeclaration { - name, - where_section: WhereSection { block, node_block, unchecked_extrinsic }, - pallets, - pallets_token, - } = definition; + let ExplicitRuntimeDeclaration { name, pallets, pallets_token, where_section } = definition; let system_pallet = pallets.iter().find(|decl| decl.name == SYSTEM_PALLET_NAME).ok_or_else(|| { syn::Error::new( - pallets_token.span, + pallets_token.span.join(), "`System` pallet declaration is missing. \ - Please add this line: `System: frame_system::{Pallet, Call, Storage, Config, Event},`", + Please add this line: `System: frame_system::{Pallet, Call, Storage, Config, Event},`", ) })?; if !system_pallet.cfg_pattern.is_empty() { @@ -253,22 +371,53 @@ fn construct_runtime_final_expansion( let scrate = generate_crate_access(hidden_crate_name, "frame-support"); let scrate_decl = generate_hidden_includes(hidden_crate_name, "frame-support"); - let outer_event = expand::expand_outer_event(&name, &pallets, &scrate)?; + let frame_system = generate_crate_access_2018("frame-system")?; + let block = quote!(<#name as #frame_system::Config>::Block); + let unchecked_extrinsic = quote!(<#block as #scrate::sp_runtime::traits::Block>::Extrinsic); + + let outer_event = + expand::expand_outer_enum(&name, &pallets, &scrate, expand::OuterEnumType::Event)?; + let outer_error = + expand::expand_outer_enum(&name, &pallets, &scrate, expand::OuterEnumType::Error)?; let outer_origin = expand::expand_outer_origin(&name, system_pallet, &pallets, &scrate)?; let all_pallets = decl_all_pallets(&name, pallets.iter(), &features); let pallet_to_index = decl_pallet_runtime_setup(&name, &pallets, &scrate); let dispatch = expand::expand_outer_dispatch(&name, system_pallet, &pallets, &scrate); - let metadata = expand::expand_runtime_metadata(&name, &pallets, &scrate, &unchecked_extrinsic); + let metadata = expand::expand_runtime_metadata( + &name, + &pallets, + &scrate, + &unchecked_extrinsic, + &system_pallet.path, + ); let outer_config = expand::expand_outer_config(&name, &pallets, &scrate); let inherent = expand::expand_outer_inherent(&name, &block, &unchecked_extrinsic, &pallets, &scrate); let validate_unsigned = expand::expand_outer_validate_unsigned(&name, &pallets, &scrate); + let freeze_reason = expand::expand_outer_freeze_reason(&pallets, &scrate); + let hold_reason = expand::expand_outer_hold_reason(&pallets, &scrate); + let lock_id = expand::expand_outer_lock_id(&pallets, &scrate); + let slash_reason = expand::expand_outer_slash_reason(&pallets, &scrate); let integrity_test = decl_integrity_test(&scrate); let static_assertions = decl_static_assertions(&name, &pallets, &scrate); + let warning = + where_section.map_or(None, |where_section| { + Some(proc_macro_warning::Warning::new_deprecated("WhereSection") + .old("use a `where` clause in `construct_runtime`") + .new("use `frame_system::Config` to set the `Block` type and delete this clause. + It is planned to be removed in December 2023") + .help_links(&["https://github.com/paritytech/substrate/pull/14437"]) + .span(where_section.span) + .build(), + ) + }); + let res = quote!( + #warning + #scrate_decl // Prevent UncheckedExtrinsic to print unused warning. @@ -282,15 +431,39 @@ fn construct_runtime_final_expansion( #scrate::scale_info::TypeInfo )] pub struct #name; - impl #scrate::sp_runtime::traits::GetNodeBlockType for #name { - type NodeBlock = #node_block; - } impl #scrate::sp_runtime::traits::GetRuntimeBlockType for #name { type RuntimeBlock = #block; } + // Each runtime must expose the `runtime_metadata()` to fetch the runtime API metadata. + // The function is implemented by calling `impl_runtime_apis!`. + // + // However, the `construct_runtime!` may be called without calling `impl_runtime_apis!`. + // Rely on the `Deref` trait to differentiate between a runtime that implements + // APIs (by macro impl_runtime_apis!) and a runtime that is simply created (by macro construct_runtime!). + // + // Both `InternalConstructRuntime` and `InternalImplRuntimeApis` expose a `runtime_metadata()` function. + // `InternalConstructRuntime` is implemented by the `construct_runtime!` for Runtime references (`& Runtime`), + // while `InternalImplRuntimeApis` is implemented by the `impl_runtime_apis!` for Runtime (`Runtime`). + // + // Therefore, the `Deref` trait will resolve the `runtime_metadata` from `impl_runtime_apis!` + // when both macros are called; and will resolve an empty `runtime_metadata` when only the `construct_runtime!` + // is called. + + #[doc(hidden)] + trait InternalConstructRuntime { + #[inline(always)] + fn runtime_metadata(&self) -> #scrate::sp_std::vec::Vec<#scrate::metadata_ir::RuntimeApiMetadataIR> { + Default::default() + } + } + #[doc(hidden)] + impl InternalConstructRuntime for &#name {} + #outer_event + #outer_error + #outer_origin #all_pallets @@ -307,6 +480,14 @@ fn construct_runtime_final_expansion( #validate_unsigned + #freeze_reason + + #hold_reason + + #lock_id + + #slash_reason + #integrity_test #static_assertions @@ -579,6 +760,7 @@ fn decl_integrity_test(scrate: &TokenStream2) -> TokenStream2 { #[test] pub fn runtime_integrity_tests() { + #scrate::sp_tracing::try_init_simple(); ::integrity_test(); } } @@ -615,3 +797,34 @@ fn decl_static_assertions( #(#error_encoded_size_check)* } } + +fn check_pallet_number(input: TokenStream2, pallet_num: usize) -> Result<()> { + let max_pallet_num = { + if cfg!(feature = "tuples-96") { + 96 + } else if cfg!(feature = "tuples-128") { + 128 + } else { + 64 + } + }; + + if pallet_num > max_pallet_num { + let no_feature = max_pallet_num == 128; + return Err(syn::Error::new( + input.span(), + format!( + "{} To increase this limit, enable the tuples-{} feature of [frame_support]. {}", + "The number of pallets exceeds the maximum number of tuple elements.", + max_pallet_num + 32, + if no_feature { + "If the feature does not exist - it needs to be implemented." + } else { + "" + }, + ), + )) + } + + Ok(()) +} diff --git a/frame/support/procedural/src/construct_runtime/parse.rs b/frame/support/procedural/src/construct_runtime/parse.rs index 7a5acf43b92b..9b08e1646975 100644 --- a/frame/support/procedural/src/construct_runtime/parse.rs +++ b/frame/support/procedural/src/construct_runtime/parse.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,6 +17,7 @@ use frame_support_procedural_tools::syn_ext as ext; use proc_macro2::{Span, TokenStream}; +use quote::ToTokens; use std::collections::{HashMap, HashSet}; use syn::{ ext::IdentExt, @@ -34,12 +35,18 @@ mod keyword { syn::custom_keyword!(Call); syn::custom_keyword!(Storage); syn::custom_keyword!(Event); + syn::custom_keyword!(Error); syn::custom_keyword!(Config); syn::custom_keyword!(Origin); syn::custom_keyword!(Inherent); syn::custom_keyword!(ValidateUnsigned); + syn::custom_keyword!(FreezeReason); + syn::custom_keyword!(HoldReason); + syn::custom_keyword!(LockId); + syn::custom_keyword!(SlashReason); syn::custom_keyword!(exclude_parts); syn::custom_keyword!(use_parts); + syn::custom_keyword!(expanded); } /// Declaration of a runtime. @@ -51,13 +58,14 @@ mod keyword { pub enum RuntimeDeclaration { Implicit(ImplicitRuntimeDeclaration), Explicit(ExplicitRuntimeDeclaration), + ExplicitExpanded(ExplicitRuntimeDeclaration), } /// Declaration of a runtime with some pallet with implicit declaration of parts. #[derive(Debug)] pub struct ImplicitRuntimeDeclaration { pub name: Ident, - pub where_section: WhereSection, + pub where_section: Option, pub pallets: Vec, } @@ -65,7 +73,7 @@ pub struct ImplicitRuntimeDeclaration { #[derive(Debug)] pub struct ExplicitRuntimeDeclaration { pub name: Ident, - pub where_section: WhereSection, + pub where_section: Option, pub pallets: Vec, pub pallets_token: token::Brace, } @@ -82,7 +90,7 @@ impl Parse for RuntimeDeclaration { } let name = input.parse::()?; - let where_section = input.parse()?; + let where_section = if input.peek(token::Where) { Some(input.parse()?) } else { None }; let pallets = input.parse::>>()?; let pallets_token = pallets.token; @@ -101,12 +109,20 @@ impl Parse for RuntimeDeclaration { pallets, pallets_token, })), + PalletsConversion::ExplicitExpanded(pallets) => + Ok(RuntimeDeclaration::ExplicitExpanded(ExplicitRuntimeDeclaration { + name, + where_section, + pallets, + pallets_token, + })), } } } #[derive(Debug)] pub struct WhereSection { + pub span: Span, pub block: syn::TypePath, pub node_block: syn::TypePath, pub unchecked_extrinsic: syn::TypePath, @@ -115,6 +131,7 @@ pub struct WhereSection { impl Parse for WhereSection { fn parse(input: ParseStream) -> Result { input.parse::()?; + let mut definitions = Vec::new(); while !input.peek(token::Brace) { let definition: WhereDefinition = input.parse()?; @@ -138,7 +155,7 @@ impl Parse for WhereSection { ); return Err(Error::new(*kind_span, msg)) } - Ok(Self { block, node_block, unchecked_extrinsic }) + Ok(Self { span: input.span(), block, node_block, unchecked_extrinsic }) } } @@ -183,6 +200,8 @@ impl Parse for WhereDefinition { /// The declaration of a pallet. #[derive(Debug, Clone)] pub struct PalletDeclaration { + /// Is this pallet fully expanded? + pub is_expanded: bool, /// The name of the pallet, e.g.`System` in `System: frame_system`. pub name: Ident, /// Optional attributes tagged right above a pallet declaration. @@ -228,6 +247,7 @@ impl Parse for PalletDeclaration { let _: Token![>] = input.parse()?; res } else if !(input.peek(Token![::]) && input.peek3(token::Brace)) && + !input.peek(keyword::expanded) && !input.peek(keyword::exclude_parts) && !input.peek(keyword::use_parts) && !input.peek(Token![=]) && @@ -241,10 +261,21 @@ impl Parse for PalletDeclaration { None }; + // Check if the pallet is fully expanded. + let (is_expanded, extra_parts) = if input.peek(keyword::expanded) { + let _: keyword::expanded = input.parse()?; + let _: Token![::] = input.parse()?; + (true, parse_pallet_parts(input)?) + } else { + (false, vec![]) + }; + // Parse for explicit parts let pallet_parts = if input.peek(Token![::]) && input.peek3(token::Brace) { let _: Token![::] = input.parse()?; - Some(parse_pallet_parts(input)?) + let mut parts = parse_pallet_parts(input)?; + parts.extend(extra_parts.into_iter()); + Some(parts) } else if !input.peek(keyword::exclude_parts) && !input.peek(keyword::use_parts) && !input.peek(Token![=]) && @@ -255,7 +286,7 @@ impl Parse for PalletDeclaration { "Unexpected tokens, expected one of `::{`, `exclude_parts`, `use_parts`, `=`, `,`", )) } else { - None + is_expanded.then_some(extra_parts) }; // Parse for specified parts @@ -283,7 +314,7 @@ impl Parse for PalletDeclaration { None }; - Ok(Self { attrs, name, path, instance, pallet_parts, specified_parts, index }) + Ok(Self { is_expanded, attrs, name, path, instance, pallet_parts, specified_parts, index }) } } @@ -366,10 +397,15 @@ pub enum PalletPartKeyword { Call(keyword::Call), Storage(keyword::Storage), Event(keyword::Event), + Error(keyword::Error), Config(keyword::Config), Origin(keyword::Origin), Inherent(keyword::Inherent), ValidateUnsigned(keyword::ValidateUnsigned), + FreezeReason(keyword::FreezeReason), + HoldReason(keyword::HoldReason), + LockId(keyword::LockId), + SlashReason(keyword::SlashReason), } impl Parse for PalletPartKeyword { @@ -384,6 +420,8 @@ impl Parse for PalletPartKeyword { Ok(Self::Storage(input.parse()?)) } else if lookahead.peek(keyword::Event) { Ok(Self::Event(input.parse()?)) + } else if lookahead.peek(keyword::Error) { + Ok(Self::Error(input.parse()?)) } else if lookahead.peek(keyword::Config) { Ok(Self::Config(input.parse()?)) } else if lookahead.peek(keyword::Origin) { @@ -392,6 +430,14 @@ impl Parse for PalletPartKeyword { Ok(Self::Inherent(input.parse()?)) } else if lookahead.peek(keyword::ValidateUnsigned) { Ok(Self::ValidateUnsigned(input.parse()?)) + } else if lookahead.peek(keyword::FreezeReason) { + Ok(Self::FreezeReason(input.parse()?)) + } else if lookahead.peek(keyword::HoldReason) { + Ok(Self::HoldReason(input.parse()?)) + } else if lookahead.peek(keyword::LockId) { + Ok(Self::LockId(input.parse()?)) + } else if lookahead.peek(keyword::SlashReason) { + Ok(Self::SlashReason(input.parse()?)) } else { Err(lookahead.error()) } @@ -406,10 +452,15 @@ impl PalletPartKeyword { Self::Call(_) => "Call", Self::Storage(_) => "Storage", Self::Event(_) => "Event", + Self::Error(_) => "Error", Self::Config(_) => "Config", Self::Origin(_) => "Origin", Self::Inherent(_) => "Inherent", Self::ValidateUnsigned(_) => "ValidateUnsigned", + Self::FreezeReason(_) => "FreezeReason", + Self::HoldReason(_) => "HoldReason", + Self::LockId(_) => "LockId", + Self::SlashReason(_) => "SlashReason", } } @@ -420,21 +471,26 @@ impl PalletPartKeyword { /// Returns the names of all pallet parts that allow to have a generic argument. fn all_generic_arg() -> &'static [&'static str] { - &["Event", "Origin", "Config"] + &["Event", "Error", "Origin", "Config"] } } -impl Spanned for PalletPartKeyword { - fn span(&self) -> Span { +impl ToTokens for PalletPartKeyword { + fn to_tokens(&self, tokens: &mut TokenStream) { match self { - Self::Pallet(inner) => inner.span(), - Self::Call(inner) => inner.span(), - Self::Storage(inner) => inner.span(), - Self::Event(inner) => inner.span(), - Self::Config(inner) => inner.span(), - Self::Origin(inner) => inner.span(), - Self::Inherent(inner) => inner.span(), - Self::ValidateUnsigned(inner) => inner.span(), + Self::Pallet(inner) => inner.to_tokens(tokens), + Self::Call(inner) => inner.to_tokens(tokens), + Self::Storage(inner) => inner.to_tokens(tokens), + Self::Event(inner) => inner.to_tokens(tokens), + Self::Error(inner) => inner.to_tokens(tokens), + Self::Config(inner) => inner.to_tokens(tokens), + Self::Origin(inner) => inner.to_tokens(tokens), + Self::Inherent(inner) => inner.to_tokens(tokens), + Self::ValidateUnsigned(inner) => inner.to_tokens(tokens), + Self::FreezeReason(inner) => inner.to_tokens(tokens), + Self::HoldReason(inner) => inner.to_tokens(tokens), + Self::LockId(inner) => inner.to_tokens(tokens), + Self::SlashReason(inner) => inner.to_tokens(tokens), } } } @@ -529,6 +585,8 @@ fn parse_pallet_parts_no_generic(input: ParseStream) -> Result | Explicit | -> | ExplicitExpanded | +/// +----------+ +----------+ +------------------+ +/// ``` enum PalletsConversion { + /// Pallets implicitely declare parts. + /// + /// `System: frame_system`. Implicit(Vec), + /// Pallets explicitly declare parts. + /// + /// `System: frame_system::{Pallet, Call}` + /// + /// However, for backwards compatibility with Polkadot/Kusama + /// we must propagate some other parts to the pallet by default. Explicit(Vec), + /// Pallets explicitly declare parts that are fully expanded. + /// + /// This is the end state that contains extra parts included by + /// default by Subtrate. + /// + /// `System: frame_system expanded::{Error} ::{Pallet, Call}` + /// + /// For this example, the `Pallet`, `Call` and `Error` parts are collected. + ExplicitExpanded(Vec), } /// Convert from the parsed pallet declaration to their final information. @@ -579,6 +663,7 @@ fn convert_pallets(pallets: Vec) -> syn::Result = None; let mut names = HashMap::new(); + let mut is_expanded = true; let pallets = pallets .into_iter() @@ -657,7 +742,7 @@ fn convert_pallets(pallets: Vec) -> syn::Result) -> syn::Result>>()?; + is_expanded &= pallet.is_expanded; + Ok(Pallet { + is_expanded: pallet.is_expanded, name: pallet.name, index: final_index, path: pallet.path, @@ -684,5 +772,9 @@ fn convert_pallets(pallets: Vec) -> syn::Result>>()?; - Ok(PalletsConversion::Explicit(pallets)) + if is_expanded { + Ok(PalletsConversion::ExplicitExpanded(pallets)) + } else { + Ok(PalletsConversion::Explicit(pallets)) + } } diff --git a/frame/support/procedural/src/crate_version.rs b/frame/support/procedural/src/crate_version.rs index e2be6dd889db..3f728abdb0b0 100644 --- a/frame/support/procedural/src/crate_version.rs +++ b/frame/support/procedural/src/crate_version.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/debug_no_bound.rs b/frame/support/procedural/src/debug_no_bound.rs index 56168edb87e8..ae182829a49e 100644 --- a/frame/support/procedural/src/debug_no_bound.rs +++ b/frame/support/procedural/src/debug_no_bound.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/default_no_bound.rs b/frame/support/procedural/src/default_no_bound.rs index b174a49e9174..da05f19e0f81 100644 --- a/frame/support/procedural/src/default_no_bound.rs +++ b/frame/support/procedural/src/default_no_bound.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,7 +62,7 @@ pub fn derive_default_no_bound(input: proc_macro::TokenStream) -> proc_macro::To let default_variants = enum_ .variants .into_iter() - .filter(|variant| variant.attrs.iter().any(|attr| attr.path.is_ident("default"))) + .filter(|variant| variant.attrs.iter().any(|attr| attr.path().is_ident("default"))) .collect::>(); match &*default_variants { @@ -80,7 +80,7 @@ pub fn derive_default_no_bound(input: proc_macro::TokenStream) -> proc_macro::To let variant_attrs = default_variant .attrs .iter() - .filter(|a| a.path.is_ident("default")) + .filter(|a| a.path().is_ident("default")) .collect::>(); // check that there is only one #[default] attribute on the variant diff --git a/frame/support/procedural/src/derive_impl.rs b/frame/support/procedural/src/derive_impl.rs new file mode 100644 index 000000000000..5ea44c35bb64 --- /dev/null +++ b/frame/support/procedural/src/derive_impl.rs @@ -0,0 +1,175 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Implementation of the `derive_impl` attribute macro. + +use derive_syn_parse::Parse; +use macro_magic::mm_core::ForeignPath; +use proc_macro2::TokenStream as TokenStream2; +use quote::{quote, ToTokens}; +use std::collections::HashSet; +use syn::{parse2, parse_quote, spanned::Spanned, Ident, ImplItem, ItemImpl, Path, Result, Token}; + +#[derive(Parse)] +pub struct DeriveImplAttrArgs { + pub default_impl_path: Path, + _as: Option, + #[parse_if(_as.is_some())] + pub disambiguation_path: Option, +} + +impl ForeignPath for DeriveImplAttrArgs { + fn foreign_path(&self) -> &Path { + &self.default_impl_path + } +} + +impl ToTokens for DeriveImplAttrArgs { + fn to_tokens(&self, tokens: &mut TokenStream2) { + tokens.extend(self.default_impl_path.to_token_stream()); + tokens.extend(self._as.to_token_stream()); + tokens.extend(self.disambiguation_path.to_token_stream()); + } +} + +/// Gets the [`Ident`] representation of the given [`ImplItem`], if one exists. Otherwise +/// returns [`None`]. +/// +/// Used by [`combine_impls`] to determine whether we can compare [`ImplItem`]s by [`Ident`] +/// or not. +fn impl_item_ident(impl_item: &ImplItem) -> Option<&Ident> { + match impl_item { + ImplItem::Const(item) => Some(&item.ident), + ImplItem::Fn(item) => Some(&item.sig.ident), + ImplItem::Type(item) => Some(&item.ident), + ImplItem::Macro(item) => item.mac.path.get_ident(), + _ => None, + } +} + +/// The real meat behind `derive_impl`. Takes in a `local_impl`, which is the impl for which we +/// want to implement defaults (i.e. the one the attribute macro is attached to), and a +/// `foreign_impl`, which is the impl containing the defaults we want to use, and returns an +/// [`ItemImpl`] containing the final generated impl. +/// +/// This process has the following caveats: +/// * Colliding items that have an ident are not copied into `local_impl` +/// * Uncolliding items that have an ident are copied into `local_impl` but are qualified as `type +/// #ident = <#default_impl_path as #disambiguation_path>::#ident;` +/// * Items that lack an ident are de-duplicated so only unique items that lack an ident are copied +/// into `local_impl`. Items that lack an ident and also exist verbatim in `local_impl` are not +/// copied over. +fn combine_impls( + local_impl: ItemImpl, + foreign_impl: ItemImpl, + default_impl_path: Path, + disambiguation_path: Path, +) -> ItemImpl { + let (existing_local_keys, existing_unsupported_items): (HashSet, HashSet) = + local_impl + .items + .iter() + .cloned() + .partition(|impl_item| impl_item_ident(impl_item).is_some()); + let existing_local_keys: HashSet = existing_local_keys + .into_iter() + .filter_map(|item| impl_item_ident(&item).cloned()) + .collect(); + let mut final_impl = local_impl; + let extended_items = foreign_impl.items.into_iter().filter_map(|item| { + if let Some(ident) = impl_item_ident(&item) { + if existing_local_keys.contains(&ident) { + // do not copy colliding items that have an ident + return None + } + if matches!(item, ImplItem::Type(_)) { + // modify and insert uncolliding type items + let modified_item: ImplItem = parse_quote! { + type #ident = <#default_impl_path as #disambiguation_path>::#ident; + }; + return Some(modified_item) + } + // copy uncolliding non-type items that have an ident + Some(item) + } else { + // do not copy colliding items that lack an ident + (!existing_unsupported_items.contains(&item)) + // copy uncolliding items without an ident verbatim + .then_some(item) + } + }); + final_impl.items.extend(extended_items); + final_impl +} + +/// Internal implementation behind [`#[derive_impl(..)]`](`macro@crate::derive_impl`). +/// +/// `default_impl_path`: the module path of the external `impl` statement whose tokens we are +/// importing via `macro_magic` +/// +/// `foreign_tokens`: the tokens for the external `impl` statement +/// +/// `local_tokens`: the tokens for the local `impl` statement this attribute is attached to +/// +/// `disambiguation_path`: the module path of the external trait we will use to qualify +/// defaults imported from the external `impl` statement +pub fn derive_impl( + default_impl_path: TokenStream2, + foreign_tokens: TokenStream2, + local_tokens: TokenStream2, + disambiguation_path: Option, +) -> Result { + let local_impl = parse2::(local_tokens)?; + let foreign_impl = parse2::(foreign_tokens)?; + let default_impl_path = parse2::(default_impl_path)?; + + // have disambiguation_path default to the item being impl'd in the foreign impl if we + // don't specify an `as [disambiguation_path]` in the macro attr + let disambiguation_path = match (disambiguation_path, foreign_impl.clone().trait_) { + (Some(disambiguation_path), _) => disambiguation_path, + (None, Some((_, foreign_impl_path, _))) => foreign_impl_path, + _ => + return Err(syn::Error::new( + foreign_impl.span(), + "Impl statement must have a defined type being implemented \ + for a defined type such as `impl A for B`", + )), + }; + + // generate the combined impl + let combined_impl = + combine_impls(local_impl, foreign_impl, default_impl_path, disambiguation_path); + + Ok(quote!(#combined_impl)) +} + +#[test] +fn test_derive_impl_attr_args_parsing() { + parse2::(quote!( + some::path::TestDefaultConfig as some::path::DefaultConfig + )) + .unwrap(); + parse2::(quote!( + frame_system::prelude::testing::TestDefaultConfig as DefaultConfig + )) + .unwrap(); + parse2::(quote!(Something as some::path::DefaultConfig)).unwrap(); + parse2::(quote!(Something as DefaultConfig)).unwrap(); + parse2::(quote!(DefaultConfig)).unwrap(); + assert!(parse2::(quote!()).is_err()); + assert!(parse2::(quote!(Config Config)).is_err()); +} diff --git a/frame/support/procedural/src/key_prefix.rs b/frame/support/procedural/src/key_prefix.rs index 05582f1297ee..6f793d0e37bd 100644 --- a/frame/support/procedural/src/key_prefix.rs +++ b/frame/support/procedural/src/key_prefix.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index 9bdbbd1f27bb..a0f2c995514a 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +25,7 @@ mod construct_runtime; mod crate_version; mod debug_no_bound; mod default_no_bound; +mod derive_impl; mod dummy_part_checker; mod key_prefix; mod match_and_insert; @@ -36,10 +37,13 @@ mod storage_alias; mod transactional; mod tt_macro; +use frame_support_procedural_tools::generate_crate_access_2018; +use macro_magic::import_tokens_attr; use proc_macro::TokenStream; -use quote::quote; +use quote::{quote, ToTokens}; use std::{cell::RefCell, str::FromStr}; pub(crate) use storage::INHERENT_INSTANCE_NAME; +use syn::{parse_macro_input, Error, ItemImpl, ItemMod}; thread_local! { /// A global counter, can be used to generate a relatively unique identifier. @@ -69,6 +73,12 @@ fn get_cargo_env_var(version_env: &str) -> std::result::Result String { + format!("CounterFor{}", prefix) +} + /// Declares strongly-typed wrappers around codec-compatible types in storage. /// /// ## Example @@ -290,6 +300,8 @@ fn get_cargo_env_var(version_env: &str) -> std::result::Result TokenStream { storage::decl_storage_impl(input) } @@ -456,9 +468,14 @@ pub fn construct_runtime(input: TokenStream) -> TokenStream { /// * Weights no longer need to be specified on every `#[pallet::call]` declaration. By default, dev /// mode pallets will assume a weight of zero (`0`) if a weight is not specified. This is /// equivalent to specifying `#[weight(0)]` on all calls that do not specify a weight. +/// * Call index no longer needs to be specified on every `#[pallet::call]` declaration. By default, +/// dev mode pallets will assume a call index based on the order of the call. /// * All storages are marked as unbounded, meaning you do not need to implement `MaxEncodedLen` on /// storage types. This is equivalent to specifying `#[pallet::unbounded]` on all storage type /// definitions. +/// * Storage hashers no longer need to be specified and can be replaced by `_`. In dev mode, these +/// will be replaced by `Blake2_128Concat`. In case of explicit key-binding, `Hasher` can simply +/// be ignored when in `dev_mode`. /// /// Note that the `dev_mode` argument can only be supplied to the `#[pallet]` or /// `#[frame_support::pallet]` attribute macro that encloses your pallet module. This argument @@ -475,6 +492,57 @@ pub fn construct_runtime(input: TokenStream) -> TokenStream { /// /// /// See `frame_support::pallet` docs for more info. +/// +/// ## Runtime Metadata Documentation +/// +/// The documentation added to this pallet is included in the runtime metadata. +/// +/// The documentation can be defined in the following ways: +/// +/// ```ignore +/// #[pallet::pallet] +/// /// Documentation for pallet 1 +/// #[doc = "Documentation for pallet 2"] +/// #[doc = include_str!("../README.md")] +/// #[pallet_doc("../doc1.md")] +/// #[pallet_doc("../doc2.md")] +/// pub mod pallet {} +/// ``` +/// +/// The runtime metadata for this pallet contains the following +/// - " Documentation for pallet 1" (captured from `///`) +/// - "Documentation for pallet 2" (captured from `#[doc]`) +/// - content of ../README.md (captured from `#[doc]` with `include_str!`) +/// - content of "../doc1.md" (captured from `pallet_doc`) +/// - content of "../doc2.md" (captured from `pallet_doc`) +/// +/// ### `doc` attribute +/// +/// The value of the `doc` attribute is included in the runtime metadata, as well as +/// expanded on the pallet module. The previous example is expanded to: +/// +/// ```ignore +/// /// Documentation for pallet 1 +/// /// Documentation for pallet 2 +/// /// Content of README.md +/// pub mod pallet {} +/// ``` +/// +/// If you want to specify the file from which the documentation is loaded, you can use the +/// `include_str` macro. However, if you only want the documentation to be included in the +/// runtime metadata, use the `pallet_doc` attribute. +/// +/// ### `pallet_doc` attribute +/// +/// Unlike the `doc` attribute, the documentation provided to the `pallet_doc` attribute is +/// not inserted on the module. +/// +/// The `pallet_doc` attribute can only be provided with one argument, +/// which is the file path that holds the documentation to be added to the metadata. +/// +/// This approach is beneficial when you use the `include_str` macro at the beginning of the file +/// and want that documentation to extend to the runtime metadata, without reiterating the +/// documentation on the pallet module itself. #[proc_macro_attribute] pub fn pallet(attr: TokenStream, item: TokenStream) -> TokenStream { pallet::pallet(attr, item) @@ -483,7 +551,7 @@ pub fn pallet(attr: TokenStream, item: TokenStream) -> TokenStream { /// An attribute macro that can be attached to a (non-empty) module declaration. Doing so will /// designate that module as a benchmarking module. /// -/// See `frame_support::benchmarking` for more info. +/// See `frame_benchmarking::v2` for more info. #[proc_macro_attribute] pub fn benchmarks(attr: TokenStream, tokens: TokenStream) -> TokenStream { match benchmark::benchmarks(attr, tokens, false) { @@ -495,7 +563,7 @@ pub fn benchmarks(attr: TokenStream, tokens: TokenStream) -> TokenStream { /// An attribute macro that can be attached to a (non-empty) module declaration. Doing so will /// designate that module as an instance benchmarking module. /// -/// See `frame_support::benchmarking` for more info. +/// See `frame_benchmarking::v2` for more info. #[proc_macro_attribute] pub fn instance_benchmarks(attr: TokenStream, tokens: TokenStream) -> TokenStream { match benchmark::benchmarks(attr, tokens, true) { @@ -508,7 +576,7 @@ pub fn instance_benchmarks(attr: TokenStream, tokens: TokenStream) -> TokenStrea /// attached to a function definition containing an `#[extrinsic_call]` or `#[block]` /// attribute. /// -/// See `frame_support::benchmarking` for more info. +/// See `frame_benchmarking::v2` for more info. #[proc_macro_attribute] pub fn benchmark(_attrs: TokenStream, _tokens: TokenStream) -> TokenStream { quote!(compile_error!( @@ -521,7 +589,7 @@ pub fn benchmark(_attrs: TokenStream, _tokens: TokenStream) -> TokenStream { /// used as a boundary designating where the benchmark setup code ends, and the benchmark /// verification code begins. /// -/// See `frame_support::benchmarking` for more info. +/// See `frame_benchmarking::v2` for more info. #[proc_macro_attribute] pub fn extrinsic_call(_attrs: TokenStream, _tokens: TokenStream) -> TokenStream { quote!(compile_error!( @@ -534,7 +602,7 @@ pub fn extrinsic_call(_attrs: TokenStream, _tokens: TokenStream) -> TokenStream /// enclosing benchmark function, This attribute is also used as a boundary designating where /// the benchmark setup code ends, and the benchmark verification code begins. /// -/// See `frame_support::benchmarking` for more info. +/// See `frame_benchmarking::v2` for more info. #[proc_macro_attribute] pub fn block(_attrs: TokenStream, _tokens: TokenStream) -> TokenStream { quote!(compile_error!( @@ -587,12 +655,13 @@ pub fn derive_debug_no_bound(input: TokenStream) -> TokenStream { } /// Derive [`Debug`], if `std` is enabled it uses `frame_support::DebugNoBound`, if `std` is not -/// enabled it just returns `""`. +/// enabled it just returns `""`. /// This behaviour is useful to prevent bloating the runtime WASM blob from unneeded code. #[proc_macro_derive(RuntimeDebugNoBound)] pub fn derive_runtime_debug_no_bound(input: TokenStream) -> TokenStream { - #[cfg(not(feature = "std"))] - { + if cfg!(any(feature = "std", feature = "try-runtime")) { + debug_no_bound::derive_debug_no_bound(input) + } else { let input: syn::DeriveInput = match syn::parse(input) { Ok(input) => input, Err(e) => return e.to_compile_error().into(), @@ -605,18 +674,13 @@ pub fn derive_runtime_debug_no_bound(input: TokenStream) -> TokenStream { const _: () = { impl #impl_generics core::fmt::Debug for #name #ty_generics #where_clause { fn fmt(&self, fmt: &mut core::fmt::Formatter) -> core::fmt::Result { - fmt.write_str("") + fmt.write_str("") } } }; ) .into() } - - #[cfg(feature = "std")] - { - debug_no_bound::derive_debug_no_bound(input) - } } /// Derive [`PartialEq`] but do not bound any generic. Docs are at @@ -719,6 +783,296 @@ pub fn storage_alias(_: TokenStream, input: TokenStream) -> TokenStream { .into() } +/// This attribute can be used to derive a full implementation of a trait based on a local partial +/// impl and an external impl containing defaults that can be overriden in the local impl. +/// +/// For a full end-to-end example, see [below](#use-case-auto-derive-test-pallet-config-traits). +/// +/// # Usage +/// +/// The attribute should be attached to an impl block (strictly speaking a `syn::ItemImpl`) for +/// which we want to inject defaults in the event of missing trait items in the block. +/// +/// The attribute minimally takes a single `default_impl_path` argument, which should be the module +/// path to an impl registered via [`#[register_default_impl]`](`macro@register_default_impl`) that +/// contains the default trait items we want to potentially inject, with the general form: +/// +/// ```ignore +/// #[derive_impl(default_impl_path)] +/// impl SomeTrait for SomeStruct { +/// ... +/// } +/// ``` +/// +/// Optionally, a `disambiguation_path` can be specified as follows by providing `as path::here` +/// after the `default_impl_path`: +/// +/// ```ignore +/// #[derive_impl(default_impl_path as disambiguation_path)] +/// impl SomeTrait for SomeStruct { +/// ... +/// } +/// ``` +/// +/// The `disambiguation_path`, if specified, should be the path to a trait that will be used to +/// qualify all default entries that are injected into the local impl. For example if your +/// `default_impl_path` is `some::path::TestTraitImpl` and your `disambiguation_path` is +/// `another::path::DefaultTrait`, any items injected into the local impl will be qualified as +/// `::specific_trait_item`. +/// +/// If you omit the `as disambiguation_path` portion, the `disambiguation_path` will internally +/// default to `A` from the `impl A for B` part of the default impl. This is useful for scenarios +/// where all of the relevant types are already in scope via `use` statements. +/// +/// Conversely, the `default_impl_path` argument is required and cannot be omitted. +/// +/// You can also make use of `#[pallet::no_default]` on specific items in your default impl that you +/// want to ensure will not be copied over but that you nonetheless want to use locally in the +/// context of the foreign impl and the pallet (or context) in which it is defined. +/// +/// ## Use-Case Example: Auto-Derive Test Pallet Config Traits +/// +/// The `#[derive_imp(..)]` attribute can be used to derive a test pallet `Config` based on an +/// existing pallet `Config` that has been marked with +/// [`#[pallet::config(with_default)]`](`macro@config`) (which under the hood, generates a +/// `DefaultConfig` trait in the pallet in which the macro was invoked). +/// +/// In this case, the `#[derive_impl(..)]` attribute should be attached to an `impl` block that +/// implements a compatible `Config` such as `frame_system::Config` for a test/mock runtime, and +/// should receive as its first argument the path to a `DefaultConfig` impl that has been registered +/// via [`#[register_default_impl]`](`macro@register_default_impl`), and as its second argument, the +/// path to the auto-generated `DefaultConfig` for the existing pallet `Config` we want to base our +/// test config off of. +/// +/// The following is what the `basic` example pallet would look like with a default testing config: +/// +/// ```ignore +/// #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::pallet::DefaultConfig)] +/// impl frame_system::Config for Test { +/// // These are all defined by system as mandatory. +/// type BaseCallFilter = frame_support::traits::Everything; +/// type RuntimeEvent = RuntimeEvent; +/// type RuntimeCall = RuntimeCall; +/// type RuntimeOrigin = RuntimeOrigin; +/// type OnSetCode = (); +/// type PalletInfo = PalletInfo; +/// type Block = Block; +/// // We decide to override this one. +/// type AccountData = pallet_balances::AccountData; +/// } +/// ``` +/// +/// where `TestDefaultConfig` was defined and registered as follows: +/// +/// ```ignore +/// pub struct TestDefaultConfig; +/// +/// #[register_default_impl(TestDefaultConfig)] +/// impl DefaultConfig for TestDefaultConfig { +/// type Version = (); +/// type BlockWeights = (); +/// type BlockLength = (); +/// type DbWeight = (); +/// type Nonce = u64; +/// type BlockNumber = u64; +/// type Hash = sp_core::hash::H256; +/// type Hashing = sp_runtime::traits::BlakeTwo256; +/// type AccountId = AccountId; +/// type Lookup = IdentityLookup; +/// type BlockHashCount = frame_support::traits::ConstU64<10>; +/// type AccountData = u32; +/// type OnNewAccount = (); +/// type OnKilledAccount = (); +/// type SystemWeightInfo = (); +/// type SS58Prefix = (); +/// type MaxConsumers = frame_support::traits::ConstU32<16>; +/// } +/// ``` +/// +/// The above call to `derive_impl` would expand to roughly the following: +/// +/// ```ignore +/// impl frame_system::Config for Test { +/// use frame_system::config_preludes::TestDefaultConfig; +/// use frame_system::pallet::DefaultConfig; +/// +/// type BaseCallFilter = frame_support::traits::Everything; +/// type RuntimeEvent = RuntimeEvent; +/// type RuntimeCall = RuntimeCall; +/// type RuntimeOrigin = RuntimeOrigin; +/// type OnSetCode = (); +/// type PalletInfo = PalletInfo; +/// type Block = Block; +/// type AccountData = pallet_balances::AccountData; +/// type Version = ::Version; +/// type BlockWeights = ::BlockWeights; +/// type BlockLength = ::BlockLength; +/// type DbWeight = ::DbWeight; +/// type Nonce = ::Nonce; +/// type BlockNumber = ::BlockNumber; +/// type Hash = ::Hash; +/// type Hashing = ::Hashing; +/// type AccountId = ::AccountId; +/// type Lookup = ::Lookup; +/// type BlockHashCount = ::BlockHashCount; +/// type OnNewAccount = ::OnNewAccount; +/// type OnKilledAccount = ::OnKilledAccount; +/// type SystemWeightInfo = ::SystemWeightInfo; +/// type SS58Prefix = ::SS58Prefix; +/// type MaxConsumers = ::MaxConsumers; +/// } +/// ``` +/// +/// You can then use the resulting `Test` config in test scenarios. +/// +/// Note that items that are _not_ present in our local `DefaultConfig` are automatically copied +/// from the foreign trait (in this case `TestDefaultConfig`) into the local trait impl (in this +/// case `Test`), unless the trait item in the local trait impl is marked with +/// [`#[pallet::no_default]`](`macro@no_default`), in which case it cannot be overridden, and any +/// attempts to do so will result in a compiler error. +/// +/// See `frame/examples/default-config/tests.rs` for a runnable end-to-end example pallet that makes +/// use of `derive_impl` to derive its testing config. +/// +/// See [here](`macro@config`) for more information and caveats about the auto-generated +/// `DefaultConfig` trait. +/// +/// ## Optional Conventions +/// +/// Note that as an optional convention, we encourage creating a `config_preludes` module inside of +/// your pallet. This is the convention we follow for `frame_system`'s `TestDefaultConfig` which, as +/// shown above, is located at `frame_system::config_preludes::TestDefaultConfig`. This is just a +/// suggested convention -- there is nothing in the code that expects modules with these names to be +/// in place, so there is no imperative to follow this pattern unless desired. +/// +/// In `config_preludes`, you can place types named like: +/// +/// * `TestDefaultConfig` +/// * `ParachainDefaultConfig` +/// * `SolochainDefaultConfig` +/// +/// Signifying in which context they can be used. +/// +/// # Advanced Usage +/// +/// ## Expansion +/// +/// The `#[derive_impl(default_impl_path as disambiguation_path)]` attribute will expand to the +/// local impl, with any extra items from the foreign impl that aren't present in the local impl +/// also included. In the case of a colliding trait item, the version of the item that exists in the +/// local impl will be retained. All imported items are qualified by the `disambiguation_path`, as +/// discussed above. +/// +/// ## Handling of Unnamed Trait Items +/// +/// Items that lack a `syn::Ident` for whatever reason are first checked to see if they exist, +/// verbatim, in the local/destination trait before they are copied over, so you should not need to +/// worry about collisions between identical unnamed items. +#[import_tokens_attr { + format!( + "{}::macro_magic", + match generate_crate_access_2018("frame-support") { + Ok(path) => Ok(path), + Err(_) => generate_crate_access_2018("frame"), + } + .expect("Failed to find either `frame-support` or `frame` in `Cargo.toml` dependencies.") + .to_token_stream() + .to_string() + ) +}] +#[with_custom_parsing(derive_impl::DeriveImplAttrArgs)] +#[proc_macro_attribute] +pub fn derive_impl(attrs: TokenStream, input: TokenStream) -> TokenStream { + let custom_attrs = parse_macro_input!(__custom_tokens as derive_impl::DeriveImplAttrArgs); + derive_impl::derive_impl( + __source_path.into(), + attrs.into(), + input.into(), + custom_attrs.disambiguation_path, + ) + .unwrap_or_else(|r| r.into_compile_error()) + .into() +} + +/// The optional attribute `#[pallet::no_default]` can be attached to trait items within a +/// `Config` trait impl that has [`#[pallet::config(with_default)]`](`macro@config`) attached. +/// +/// Attaching this attribute to a trait item ensures that that trait item will not be used as a +/// default with the [`#[derive_impl(..)]`](`macro@derive_impl`) attribute macro. +#[proc_macro_attribute] +pub fn no_default(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// Attach this attribute to an impl statement that you want to use with +/// [`#[derive_impl(..)]`](`macro@derive_impl`). +/// +/// You must also provide an identifier/name as the attribute's argument. This is the name you +/// must provide to [`#[derive_impl(..)]`](`macro@derive_impl`) when you import this impl via +/// the `default_impl_path` argument. This name should be unique at the crate-level. +/// +/// ## Example +/// +/// ```ignore +/// pub struct ExampleTestDefaultConfig; +/// +/// #[register_default_impl(ExampleTestDefaultConfig)] +/// impl DefaultConfig for ExampleTestDefaultConfig { +/// type Version = (); +/// type BlockWeights = (); +/// type BlockLength = (); +/// ... +/// type SS58Prefix = (); +/// type MaxConsumers = frame_support::traits::ConstU32<16>; +/// } +/// ``` +/// +/// ## Advanced Usage +/// +/// This macro acts as a thin wrapper around macro_magic's `#[export_tokens]`. See the docs +/// [here](https://docs.rs/macro_magic/latest/macro_magic/attr.export_tokens.html) for more +/// info. +/// +/// There are some caveats when applying a `use` statement to bring a +/// `#[register_default_impl]` item into scope. If you have a `#[register_default_impl]` +/// defined in `my_crate::submodule::MyItem`, it is currently not sufficient to do something +/// like: +/// +/// ```ignore +/// use my_crate::submodule::MyItem; +/// #[derive_impl(MyItem as Whatever)] +/// ``` +/// +/// This will fail with a mysterious message about `__export_tokens_tt_my_item` not being +/// defined. +/// +/// You can, however, do any of the following: +/// ```ignore +/// // partial path works +/// use my_crate::submodule; +/// #[derive_impl(submodule::MyItem as Whatever)] +/// ``` +/// ```ignore +/// // full path works +/// #[derive_impl(my_crate::submodule::MyItem as Whatever)] +/// ``` +/// ```ignore +/// // wild-cards work +/// use my_crate::submodule::*; +/// #[derive_impl(MyItem as Whatever)] +/// ``` +#[proc_macro_attribute] +pub fn register_default_impl(attrs: TokenStream, tokens: TokenStream) -> TokenStream { + // ensure this is a impl statement + let item_impl = syn::parse_macro_input!(tokens as ItemImpl); + + // internally wrap macro_magic's `#[export_tokens]` macro + match macro_magic::mm_core::export_tokens_internal(attrs, item_impl.to_token_stream(), true) { + Ok(tokens) => tokens.into(), + Err(err) => err.to_compile_error().into(), + } +} + /// Used internally to decorate pallet attribute macro stubs when they are erroneously used /// outside of a pallet module fn pallet_macro_stub() -> TokenStream { @@ -751,6 +1105,52 @@ fn pallet_macro_stub() -> TokenStream { /// /// [`pallet::event`](`macro@event`) must be present if `RuntimeEvent` exists as a config item /// in your `#[pallet::config]`. +/// +/// ## Optional: `with_default` +/// +/// An optional `with_default` argument may also be specified. Doing so will automatically +/// generate a `DefaultConfig` trait inside your pallet which is suitable for use with +/// [`[#[derive_impl(..)]`](`macro@derive_impl`) to derive a default testing config: +/// +/// ```ignore +/// #[pallet::config(with_default)] +/// pub trait Config: frame_system::Config { +/// type RuntimeEvent: Parameter +/// + Member +/// + From> +/// + Debug +/// + IsType<::RuntimeEvent>; +/// +/// #[pallet::no_default] +/// type BaseCallFilter: Contains; +/// // ... +/// } +/// ``` +/// +/// As shown above, you may also attach the [`#[pallet::no_default]`](`macro@no_default`) +/// attribute to specify that a particular trait item _cannot_ be used as a default when a test +/// `Config` is derived using the [`#[derive_impl(..)]`](`macro@derive_impl`) attribute macro. +/// This will cause that particular trait item to simply not appear in default testing configs +/// based on this config (the trait item will not be included in `DefaultConfig`). +/// +/// ### `DefaultConfig` Caveats +/// +/// The auto-generated `DefaultConfig` trait: +/// - is always a _subset_ of your pallet's `Config` trait. +/// - can only contain items that don't rely on externalities, such as `frame_system::Config`. +/// +/// Trait items that _do_ rely on externalities should be marked with +/// [`#[pallet::no_default]`](`macro@no_default`) +/// +/// Consequently: +/// - Any items that rely on externalities _must_ be marked with +/// [`#[pallet::no_default]`](`macro@no_default`) or your trait will fail to compile when used +/// with [`derive_impl`](`macro@derive_impl`). +/// - Items marked with [`#[pallet::no_default]`](`macro@no_default`) are entirely excluded from the +/// `DefaultConfig` trait, and therefore any impl of `DefaultConfig` doesn't need to implement +/// such items. +/// +/// For more information, see [`macro@derive_impl`]. #[proc_macro_attribute] pub fn config(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() @@ -808,24 +1208,6 @@ pub fn generate_store(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } -/// To generate the full storage info (used for PoV calculation) use the attribute -/// `#[pallet::generate_storage_info]`, e.g.: -/// -/// ```ignore -/// #[pallet::pallet] -/// #[pallet::generate_storage_info] -/// pub struct Pallet(_); -/// ``` -/// -/// This requires all storage items to implement the trait `StorageInfoTrait`, thus all keys -/// and value types must be bound by `MaxEncodedLen`. Individual storages can opt-out from this -/// constraint by using [`#[pallet::unbounded]`](`macro@unbounded`) (see -/// [`#[pallet::storage]`](`macro@storage`) for more info). -#[proc_macro_attribute] -pub fn generate_storage_info(_: TokenStream, _: TokenStream) -> TokenStream { - pallet_macro_stub() -} - /// Because the `pallet::pallet` macro implements `GetStorageVersion`, the current storage /// version needs to be communicated to the macro. This can be done by using the /// `pallet::storage_version` attribute: @@ -927,7 +1309,8 @@ pub fn compact(_: TokenStream, _: TokenStream) -> TokenStream { /// /// The macro creates an enum `Call` with one variant per dispatchable. This enum implements: /// [`Clone`], [`Eq`], [`PartialEq`], [`Debug`] (with stripped implementation in `not("std")`), -/// `Encode`, `Decode`, `GetDispatchInfo`, `GetCallName`, and `UnfilteredDispatchable`. +/// `Encode`, `Decode`, `GetDispatchInfo`, `GetCallName`, `GetCallIndex` and +/// `UnfilteredDispatchable`. /// /// The macro implements the `Callable` trait on `Pallet` and a function `call_functions` /// which returns the dispatchable metadata. @@ -1176,7 +1559,7 @@ pub fn unbounded(_: TokenStream, _: TokenStream) -> TokenStream { /// ```ignore /// #[pallet::storage] /// #[pallet::whitelist_storage] -/// pub(super) type Number = StorageValue<_, T::BlockNumber, ValueQuery>; +/// pub(super) type Number = StorageValue<_, frame_system::pallet_prelude::BlockNumberFor::, ValueQuery>; /// ``` /// /// NOTE: As with all `pallet::*` attributes, this one _must_ be written as @@ -1268,8 +1651,7 @@ pub fn genesis_config(_: TokenStream, _: TokenStream) -> TokenStream { /// The macro will add the following attribute: /// * `#[cfg(feature = "std")]` /// -/// The macro will implement `sp_runtime::BuildModuleGenesisStorage` using `()` as a second -/// generic for non-instantiable pallets. +/// The macro will implement `sp_runtime::BuildStorage`. #[proc_macro_attribute] pub fn genesis_build(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() @@ -1353,3 +1735,140 @@ pub fn validate_unsigned(_: TokenStream, _: TokenStream) -> TokenStream { pub fn origin(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } + +/// The `#[pallet::composite_enum]` attribute allows you to define an enum that gets composed as an +/// aggregate enum by `construct_runtime`. This is similar in principle with `#[pallet::event]` and +/// `#[pallet::error]`. +/// +/// The attribute currently only supports enum definitions, and identifiers that are named +/// `FreezeReason`, `HoldReason`, `LockId` or `SlashReason`. Arbitrary identifiers for the enum are +/// not supported. The aggregate enum generated by `construct_runtime` will have the name of +/// `RuntimeFreezeReason`, `RuntimeHoldReason`, `RuntimeLockId` and `RuntimeSlashReason` +/// respectively. +/// +/// NOTE: The aggregate enum generated by `construct_runtime` generates a conversion function from +/// the pallet enum to the aggregate enum, and automatically derives the following traits: +/// +/// ```ignore +/// Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, TypeInfo, +/// RuntimeDebug +/// ``` +/// +/// For ease of usage, when no `#[derive]` attributes are found for the enum under +/// `#[pallet::composite_enum]`, the aforementioned traits are automatically derived for it. The +/// inverse is also true: if there are any `#[derive]` attributes found for the enum, then no traits +/// will automatically be derived for it. +#[proc_macro_attribute] +pub fn composite_enum(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + +/// Can be attached to a module. Doing so will declare that module as importable into a pallet +/// via [`#[import_section]`](`macro@import_section`). +/// +/// Note that sections are imported by their module name/ident, and should be referred to by +/// their _full path_ from the perspective of the target pallet. Do not attempt to make use +/// of `use` statements to bring pallet sections into scope, as this will not work (unless +/// you do so as part of a wildcard import, in which case it will work). +/// +/// ## Naming Logistics +/// +/// Also note that because of how `#[pallet_section]` works, pallet section names must be +/// globally unique _within the crate in which they are defined_. For more information on +/// why this must be the case, see macro_magic's +/// [`#[export_tokens]`](https://docs.rs/macro_magic/latest/macro_magic/attr.export_tokens.html) macro. +/// +/// Optionally, you may provide an argument to `#[pallet_section]` such as +/// `#[pallet_section(some_ident)]`, in the event that there is another pallet section in +/// same crate with the same ident/name. The ident you specify can then be used instead of +/// the module's ident name when you go to import it via `#[import_section]`. +#[proc_macro_attribute] +pub fn pallet_section(attr: TokenStream, tokens: TokenStream) -> TokenStream { + let tokens_clone = tokens.clone(); + // ensure this can only be attached to a module + let _mod = parse_macro_input!(tokens_clone as ItemMod); + + // use macro_magic's export_tokens as the internal implementation otherwise + match macro_magic::mm_core::export_tokens_internal(attr, tokens, false) { + Ok(tokens) => tokens.into(), + Err(err) => err.to_compile_error().into(), + } +} + +/// An attribute macro that can be attached to a module declaration. Doing so will +/// Imports the contents of the specified external pallet section that was defined +/// previously using [`#[pallet_section]`](`macro@pallet_section`). +/// +/// ## Example +/// ```ignore +/// #[import_section(some_section)] +/// #[pallet] +/// pub mod pallet { +/// // ... +/// } +/// ``` +/// where `some_section` was defined elsewhere via: +/// ```ignore +/// #[pallet_section] +/// pub mod some_section { +/// // ... +/// } +/// ``` +/// +/// This will result in the contents of `some_section` being _verbatim_ imported into +/// the pallet above. Note that since the tokens for `some_section` are essentially +/// copy-pasted into the target pallet, you cannot refer to imports that don't also +/// exist in the target pallet, but this is easily resolved by including all relevant +/// `use` statements within your pallet section, so they are imported as well, or by +/// otherwise ensuring that you have the same imports on the target pallet. +/// +/// It is perfectly permissible to import multiple pallet sections into the same pallet, +/// which can be done by having multiple `#[import_section(something)]` attributes +/// attached to the pallet. +/// +/// Note that sections are imported by their module name/ident, and should be referred to by +/// their _full path_ from the perspective of the target pallet. +#[import_tokens_attr { + format!( + "{}::macro_magic", + match generate_crate_access_2018("frame-support") { + Ok(path) => Ok(path), + Err(_) => generate_crate_access_2018("frame"), + } + .expect("Failed to find either `frame-support` or `frame` in `Cargo.toml` dependencies.") + .to_token_stream() + .to_string() + ) +}] +#[proc_macro_attribute] +pub fn import_section(attr: TokenStream, tokens: TokenStream) -> TokenStream { + let foreign_mod = parse_macro_input!(attr as ItemMod); + let mut internal_mod = parse_macro_input!(tokens as ItemMod); + + // check that internal_mod is a pallet module + if !internal_mod.attrs.iter().any(|attr| { + if let Some(last_seg) = attr.path().segments.last() { + last_seg.ident == "pallet" + } else { + false + } + }) { + return Error::new( + internal_mod.ident.span(), + "`#[import_section]` can only be applied to a valid pallet module", + ) + .to_compile_error() + .into() + } + + if let Some(ref mut content) = internal_mod.content { + if let Some(foreign_content) = foreign_mod.content { + content.1.extend(foreign_content.1); + } + } + + quote! { + #internal_mod + } + .into() +} diff --git a/frame/support/procedural/src/match_and_insert.rs b/frame/support/procedural/src/match_and_insert.rs index 79d1da7549c1..aa9cc56d769d 100644 --- a/frame/support/procedural/src/match_and_insert.rs +++ b/frame/support/procedural/src/match_and_insert.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet/expand/call.rs b/frame/support/procedural/src/pallet/expand/call.rs index 30ef1a8fca31..031fa02bb9d6 100644 --- a/frame/support/procedural/src/pallet/expand/call.rs +++ b/frame/support/procedural/src/pallet/expand/call.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,8 +15,15 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{pallet::Def, COUNTER}; -use quote::ToTokens; +use crate::{ + pallet::{ + parse::call::{CallVariantDef, CallWeightDef}, + Def, + }, + COUNTER, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::{quote, ToTokens}; use syn::spanned::Spanned; /// @@ -54,32 +61,77 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { .map(|fn_name| format!("Create a call with the variant `{}`.", fn_name)) .collect::>(); - let mut warning_structs = Vec::new(); - let mut warning_names = Vec::new(); + let mut call_index_warnings = Vec::new(); // Emit a warning for each call that is missing `call_index` when not in dev-mode. for method in &methods { if method.explicit_call_index || def.dev_mode { continue } - let name = syn::Ident::new(&format!("{}", method.name), method.name.span()); - let warning: syn::ItemStruct = syn::parse_quote!( - #[deprecated(note = r" - Implicit call indices are deprecated in favour of explicit ones. - Please ensure that all calls have the `pallet::call_index` attribute or that the - `dev-mode` of the pallet is enabled. For more info see: - and - .")] - #[allow(non_camel_case_types)] - struct #name; - ); - warning_names.push(name); - warning_structs.push(warning); + let warning = proc_macro_warning::Warning::new_deprecated("ImplicitCallIndex") + .index(call_index_warnings.len()) + .old("use implicit call indices") + .new("ensure that all calls have a `pallet::call_index` attribute or put the pallet into `dev` mode") + .help_links(&[ + "https://github.com/paritytech/substrate/pull/12891", + "https://github.com/paritytech/substrate/pull/11381" + ]) + .span(method.name.span()) + .build(); + call_index_warnings.push(warning); } - let fn_weight = methods.iter().map(|method| &method.weight); + let mut fn_weight = Vec::::new(); + let mut weight_warnings = Vec::new(); + for method in &methods { + match &method.weight { + CallWeightDef::DevModeDefault => fn_weight.push(syn::parse_quote!(0)), + CallWeightDef::Immediate(e @ syn::Expr::Lit(lit)) if !def.dev_mode => { + let warning = proc_macro_warning::Warning::new_deprecated("ConstantWeight") + .index(weight_warnings.len()) + .old("use hard-coded constant as call weight") + .new("benchmark all calls or put the pallet into `dev` mode") + .help_link("https://github.com/paritytech/substrate/pull/13798") + .span(lit.span()) + .build(); + weight_warnings.push(warning); + fn_weight.push(e.into_token_stream()); + }, + CallWeightDef::Immediate(e) => fn_weight.push(e.into_token_stream()), + CallWeightDef::Inherited => { + let pallet_weight = def + .call + .as_ref() + .expect("we have methods; we have calls; qed") + .inherited_call_weight + .as_ref() + .expect("the parser prevents this"); + + // Expand `<::WeightInfo>::call_name()`. + let t = &pallet_weight.typename; + let n = &method.name; + fn_weight.push(quote!({ < #t > :: #n () })); + }, + } + } + debug_assert_eq!(fn_weight.len(), methods.len()); + + let map_fn_docs = if !def.dev_mode { + // Emit the [`Pallet::method`] documentation only for non-dev modes. + |method: &CallVariantDef| { + let reference = format!("See [`Pallet::{}`].", method.name); + quote!(#reference) + } + } else { + // For the dev-mode do not provide a documenation link as it will break the + // `cargo doc` if the pallet is private inside a test. + |method: &CallVariantDef| { + let reference = format!("See `Pallet::{}`.", method.name); + quote!(#reference) + } + }; - let fn_doc = methods.iter().map(|method| &method.docs).collect::>(); + let fn_doc = methods.iter().map(map_fn_docs).collect::>(); let args_name = methods .iter() @@ -141,9 +193,8 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { .collect::>() }); - let default_docs = [syn::parse_quote!( - r"Contains one variant per dispatchable that can be called by an extrinsic." - )]; + let default_docs = + [syn::parse_quote!(r"Contains a variant per dispatchable extrinsic that this pallet has.")]; let docs = if docs.is_empty() { &default_docs[..] } else { &docs[..] }; let maybe_compile_error = if def.call.is_none() { @@ -171,7 +222,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { .map(|c| &mut def.item.content.as_mut().expect("Checked by def parser").1[c.index]) { item_impl.items.iter_mut().for_each(|i| { - if let syn::ImplItem::Method(method) = i { + if let syn::ImplItem::Fn(method) = i { let block = &method.block; method.block = syn::parse_quote! {{ // We execute all dispatchable in a new storage layer, allowing them @@ -189,13 +240,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { method .attrs .iter() - .find(|attr| { - if let Ok(syn::Meta::List(syn::MetaList { path, .. })) = attr.parse_meta() { - path.segments.last().map(|seg| seg.ident == "allow").unwrap_or(false) - } else { - false - } - }) + .find(|attr| attr.path().is_ident("allow")) .map_or(proc_macro2::TokenStream::new(), |attr| attr.to_token_stream()) }) .collect::>(); @@ -203,9 +248,10 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { quote::quote_spanned!(span => mod warnings { #( - #warning_structs - // This triggers each deprecated warning once. - const _: Option<#warning_names> = None; + #call_index_warnings + )* + #( + #weight_warnings )* } @@ -245,7 +291,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { #frame_support::Never, ), #( - #( #[doc = #fn_doc] )* + #[doc = #fn_doc] #[codec(index = #call_index)] #fn_name { #( @@ -324,6 +370,21 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { } } + impl<#type_impl_gen> #frame_support::dispatch::GetCallIndex for #call_ident<#type_use_gen> + #where_clause + { + fn get_call_index(&self) -> u8 { + match *self { + #( Self::#fn_name { .. } => #call_index, )* + Self::__Ignore(_, _) => unreachable!("__PhantomItem cannot be used."), + } + } + + fn get_call_indices() -> &'static [u8] { + &[ #( #call_index, )* ] + } + } + impl<#type_impl_gen> #frame_support::traits::UnfilteredDispatchable for #call_ident<#type_use_gen> #where_clause @@ -333,22 +394,24 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { self, origin: Self::RuntimeOrigin ) -> #frame_support::dispatch::DispatchResultWithPostInfo { - match self { - #( - Self::#fn_name { #( #args_name_pattern, )* } => { - #frame_support::sp_tracing::enter_span!( - #frame_support::sp_tracing::trace_span!(stringify!(#fn_name)) - ); - #maybe_allow_attrs - <#pallet_ident<#type_use_gen>>::#fn_name(origin, #( #args_name, )* ) - .map(Into::into).map_err(Into::into) + #frame_support::dispatch_context::run_in_context(|| { + match self { + #( + Self::#fn_name { #( #args_name_pattern, )* } => { + #frame_support::sp_tracing::enter_span!( + #frame_support::sp_tracing::trace_span!(stringify!(#fn_name)) + ); + #maybe_allow_attrs + <#pallet_ident<#type_use_gen>>::#fn_name(origin, #( #args_name, )* ) + .map(Into::into).map_err(Into::into) + }, + )* + Self::__Ignore(_, _) => { + let _ = origin; // Use origin for empty Call enum + unreachable!("__PhantomItem cannot be used."); }, - )* - Self::__Ignore(_, _) => { - let _ = origin; // Use origin for empty Call enum - unreachable!("__PhantomItem cannot be used."); - }, - } + } + }) } } @@ -360,7 +423,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream { impl<#type_impl_gen> #pallet_ident<#type_use_gen> #where_clause { #[doc(hidden)] - pub fn call_functions() -> #frame_support::metadata::PalletCallMetadata { + pub fn call_functions() -> #frame_support::metadata_ir::PalletCallMetadataIR { #frame_support::scale_info::meta_type::<#call_ident<#type_use_gen>>().into() } } diff --git a/frame/support/procedural/src/pallet/expand/config.rs b/frame/support/procedural/src/pallet/expand/config.rs index 8ad34361d684..4ae0afdbbc8e 100644 --- a/frame/support/procedural/src/pallet/expand/config.rs +++ b/frame/support/procedural/src/pallet/expand/config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,30 +16,57 @@ // limitations under the License. use crate::pallet::Def; -use frame_support_procedural_tools::get_doc_literals; +use proc_macro2::TokenStream; +use quote::quote; +use syn::{parse_quote, Item}; /// /// * Generate default rust doc -pub fn expand_config(def: &mut Def) -> proc_macro2::TokenStream { +pub fn expand_config(def: &mut Def) -> TokenStream { let config = &def.config; let config_item = { let item = &mut def.item.content.as_mut().expect("Checked by def parser").1[config.index]; - if let syn::Item::Trait(item) = item { + if let Item::Trait(item) = item { item } else { unreachable!("Checked by config parser") } }; - if get_doc_literals(&config_item.attrs).is_empty() { - config_item.attrs.push(syn::parse_quote!( + config_item.attrs.insert( + 0, + parse_quote!( #[doc = r" - Configuration trait of this pallet. +Configuration trait of this pallet. - Implement this type for a runtime in order to customize this pallet. - "] - )); - } +The main purpose of this trait is to act as an interface between this pallet and the runtime in +which it is embedded in. A type, function, or constant in this trait is essentially left to be +configured by the runtime that includes this pallet. + +Consequently, a runtime that wants to include this pallet must implement this trait." + ] + ), + ); - Default::default() + // we only emit `DefaultConfig` if there are trait items, so an empty `DefaultConfig` is + // impossible consequently + if config.default_sub_trait.len() > 0 { + let trait_items = &config.default_sub_trait; + quote!( + /// Based on [`Config`]. Auto-generated by + /// [`#[pallet::config(with_default)]`](`frame_support::pallet_macros::config`). + /// Can be used in tandem with + /// [`#[register_default_config]`](`frame_support::register_default_config`) and + /// [`#[derive_impl]`](`frame_support::derive_impl`) to derive test config traits + /// based on existing pallet config traits in a safe and developer-friendly way. + /// + /// See [here](`frame_support::pallet_macros::config`) for more information and caveats about + /// the auto-generated `DefaultConfig` trait and how it is generated. + pub trait DefaultConfig { + #(#trait_items)* + } + ) + } else { + Default::default() + } } diff --git a/frame/support/procedural/src/pallet/expand/constants.rs b/frame/support/procedural/src/pallet/expand/constants.rs index 1f9526f9cebe..6e1cd3df627f 100644 --- a/frame/support/procedural/src/pallet/expand/constants.rs +++ b/frame/support/procedural/src/pallet/expand/constants.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,7 @@ struct ConstDef { /// The type in Get, e.g. `u32` in `type Foo: Get;`, but `Self` is replaced by `T` pub type_: syn::Type, /// The doc associated - pub doc: Vec, + pub doc: Vec, /// default_byte implementation pub default_byte_impl: proc_macro2::TokenStream, /// Constant name for Metadata (optional) @@ -85,7 +85,7 @@ pub fn expand_constants(def: &mut Def) -> proc_macro2::TokenStream { let default_byte_impl = &const_.default_byte_impl; quote::quote!({ - #frame_support::metadata::PalletConstantMetadata { + #frame_support::metadata_ir::PalletConstantMetadataIR { name: #ident_str, ty: #frame_support::scale_info::meta_type::<#const_type>(), value: { #default_byte_impl }, @@ -99,7 +99,7 @@ pub fn expand_constants(def: &mut Def) -> proc_macro2::TokenStream { #[doc(hidden)] pub fn pallet_constants_metadata() - -> #frame_support::sp_std::vec::Vec<#frame_support::metadata::PalletConstantMetadata> + -> #frame_support::sp_std::vec::Vec<#frame_support::metadata_ir::PalletConstantMetadataIR> { #frame_support::sp_std::vec![ #( #consts ),* ] } diff --git a/frame/support/procedural/src/pallet/expand/doc_only.rs b/frame/support/procedural/src/pallet/expand/doc_only.rs new file mode 100644 index 000000000000..50afeb3ca88c --- /dev/null +++ b/frame/support/procedural/src/pallet/expand/doc_only.rs @@ -0,0 +1,103 @@ +// This file is part of Substrate. + +// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use proc_macro2::Span; + +use crate::pallet::Def; + +pub fn expand_doc_only(def: &mut Def) -> proc_macro2::TokenStream { + let dispatchables = if let Some(call_def) = &def.call { + let type_impl_generics = def.type_impl_generics(Span::call_site()); + call_def + .methods + .iter() + .map(|method| { + let name = &method.name; + let args = &method + .args + .iter() + .map(|(_, arg_name, arg_type)| quote::quote!( #arg_name: #arg_type, )) + .collect::(); + let docs = &method.docs; + + let real = format!(" [`Pallet::{}`].", name); + quote::quote!( + #( #[doc = #docs] )* + /// + /// # Warning: Doc-Only + /// + /// This function is an automatically generated, and is doc-only, uncallable + /// stub. See the real version in + #[ doc = #real ] + pub fn #name<#type_impl_generics>(#args) { unreachable!(); } + ) + }) + .collect::() + } else { + quote::quote!() + }; + + let storage_types = def + .storages + .iter() + .map(|storage| { + let storage_name = &storage.ident; + let storage_type_docs = &storage.docs; + let real = format!("[`pallet::{}`].", storage_name); + quote::quote!( + #( #[doc = #storage_type_docs] )* + /// + /// # Warning: Doc-Only + /// + /// This type is automatically generated, and is doc-only. See the real version in + #[ doc = #real ] + pub struct #storage_name(); + ) + }) + .collect::(); + + quote::quote!( + /// Auto-generated docs-only module listing all (public and private) defined storage types + /// for this pallet. + /// + /// # Warning: Doc-Only + /// + /// Members of this module cannot be used directly and are only provided for documentation + /// purposes. + /// + /// To see the actual storage type, find a struct with the same name at the root of the + /// pallet, in the list of [*Type Definitions*](../index.html#types). + #[cfg(doc)] + pub mod storage_types { + use super::*; + #storage_types + } + + /// Auto-generated docs-only module listing all defined dispatchables for this pallet. + /// + /// # Warning: Doc-Only + /// + /// Members of this module cannot be used directly and are only provided for documentation + /// purposes. To see the real version of each dispatchable, look for them in [`Pallet`] or + /// [`Call`]. + #[cfg(doc)] + pub mod dispatchables { + use super::*; + #dispatchables + } + ) +} diff --git a/frame/support/procedural/src/pallet/expand/documentation.rs b/frame/support/procedural/src/pallet/expand/documentation.rs new file mode 100644 index 000000000000..4e6347e83bb7 --- /dev/null +++ b/frame/support/procedural/src/pallet/expand/documentation.rs @@ -0,0 +1,172 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::pallet::Def; +use proc_macro2::TokenStream; +use quote::ToTokens; +use syn::{spanned::Spanned, Attribute, Lit, LitStr}; + +const DOC: &'static str = "doc"; +const PALLET_DOC: &'static str = "pallet_doc"; + +/// Get the documentation file path from the `pallet_doc` attribute. +/// +/// Supported format: +/// `#[pallet_doc(PATH)]`: The path of the file from which the documentation is loaded +fn parse_pallet_doc_value(attr: &Attribute) -> syn::Result { + let lit: syn::LitStr = attr.parse_args().map_err(|_| { + let msg = "The `pallet_doc` received an unsupported argument. Supported format: `pallet_doc(\"PATH\")`"; + syn::Error::new(attr.span(), msg) + })?; + + Ok(DocMetaValue::Path(lit)) +} + +/// Get the value from the `doc` comment attribute: +/// +/// Supported formats: +/// - `#[doc = "A doc string"]`: Documentation as a string literal +/// - `#[doc = include_str!(PATH)]`: Documentation obtained from a path +fn parse_doc_value(attr: &Attribute) -> syn::Result> { + if !attr.path().is_ident(DOC) { + return Ok(None) + } + + let meta = attr.meta.require_name_value()?; + + match &meta.value { + syn::Expr::Lit(lit) => Ok(Some(DocMetaValue::Lit(lit.lit.clone()))), + syn::Expr::Macro(mac) if mac.mac.path.is_ident("include_str") => + Ok(Some(DocMetaValue::Path(mac.mac.parse_body()?))), + _ => + Err(syn::Error::new(attr.span(), "Expected `= \"docs\"` or `= include_str!(\"PATH\")`")), + } +} + +/// Supported documentation tokens. +#[derive(Debug)] +enum DocMetaValue { + /// Documentation with string literals. + /// + /// `#[doc = "Lit"]` + Lit(Lit), + /// Documentation with `include_str!` macro. + /// + /// The string literal represents the file `PATH`. + /// + /// `#[doc = include_str!(PATH)]` + Path(LitStr), +} + +impl ToTokens for DocMetaValue { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + DocMetaValue::Lit(lit) => lit.to_tokens(tokens), + DocMetaValue::Path(path_lit) => { + let decl = quote::quote!(include_str!(#path_lit)); + tokens.extend(decl) + }, + } + } +} + +/// Extract the documentation from the given pallet definition +/// to include in the runtime metadata. +/// +/// Implement a `pallet_documentation_metadata` function to fetch the +/// documentation that is included in the metadata. +/// +/// The documentation is placed on the pallet similar to: +/// +/// ```ignore +/// #[pallet] +/// /// Documentation for pallet +/// #[doc = "Documentation for pallet"] +/// #[doc = include_str!("../README.md")] +/// #[pallet_doc("../documentation1.md")] +/// #[pallet_doc("../documentation2.md")] +/// pub mod pallet {} +/// ``` +/// +/// # pallet_doc +/// +/// The `pallet_doc` attribute can only be provided with one argument, +/// which is the file path that holds the documentation to be added to the metadata. +/// +/// Unlike the `doc` attribute, the documentation provided to the `proc_macro` attribute is +/// not added to the pallet. +pub fn expand_documentation(def: &mut Def) -> proc_macro2::TokenStream { + let frame_support = &def.frame_support; + let type_impl_gen = &def.type_impl_generics(proc_macro2::Span::call_site()); + let type_use_gen = &def.type_use_generics(proc_macro2::Span::call_site()); + let pallet_ident = &def.pallet_struct.pallet; + let where_clauses = &def.config.where_clause; + + // TODO: Use [drain_filter](https://doc.rust-lang.org/std/vec/struct.Vec.html#method.drain_filter) when it is stable. + + // The `pallet_doc` attributes are excluded from the generation of the pallet, + // but they are included in the runtime metadata. + let mut pallet_docs = Vec::with_capacity(def.item.attrs.len()); + let mut index = 0; + while index < def.item.attrs.len() { + let attr = &def.item.attrs[index]; + if attr.path().get_ident().map_or(false, |i| *i == PALLET_DOC) { + pallet_docs.push(def.item.attrs.remove(index)); + // Do not increment the index, we have just removed the + // element from the attributes. + continue + } + + index += 1; + } + + // Capture the `#[doc = include_str!("../README.md")]` and `#[doc = "Documentation"]`. + let docs = match def + .item + .attrs + .iter() + .filter_map(|v| parse_doc_value(v).transpose()) + .collect::>>() + { + Ok(r) => r, + Err(err) => return err.into_compile_error(), + }; + + // Capture the `#[pallet_doc("../README.md")]`. + let pallet_docs = match pallet_docs + .into_iter() + .map(|attr| parse_pallet_doc_value(&attr)) + .collect::>>() + { + Ok(docs) => docs, + Err(err) => return err.into_compile_error(), + }; + + let docs = docs.iter().chain(pallet_docs.iter()); + + quote::quote!( + impl<#type_impl_gen> #pallet_ident<#type_use_gen> #where_clauses{ + + #[doc(hidden)] + pub fn pallet_documentation_metadata() + -> #frame_support::sp_std::vec::Vec<&'static str> + { + #frame_support::sp_std::vec![ #( #docs ),* ] + } + } + ) +} diff --git a/frame/support/procedural/src/pallet/expand/error.rs b/frame/support/procedural/src/pallet/expand/error.rs index 5a8487b09de5..376a6a9f51c6 100644 --- a/frame/support/procedural/src/pallet/expand/error.rs +++ b/frame/support/procedural/src/pallet/expand/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -110,10 +110,7 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream { if get_doc_literals(&error_item.attrs).is_empty() { error_item.attrs.push(syn::parse_quote!( - #[doc = r" - Custom [dispatch errors](https://docs.substrate.io/main-docs/build/events-errors/) - of this pallet. - "] + #[doc = "The `Error` enum of this pallet."] )); } diff --git a/frame/support/procedural/src/pallet/expand/event.rs b/frame/support/procedural/src/pallet/expand/event.rs index abed680eb245..f94bdef332d9 100644 --- a/frame/support/procedural/src/pallet/expand/event.rs +++ b/frame/support/procedural/src/pallet/expand/event.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -97,12 +97,9 @@ pub fn expand_event(def: &mut Def) -> proc_macro2::TokenStream { } if get_doc_literals(&event_item.attrs).is_empty() { - event_item.attrs.push(syn::parse_quote!( - #[doc = r" - The [event](https://docs.substrate.io/main-docs/build/events-errors/) emitted - by this pallet. - "] - )); + event_item + .attrs + .push(syn::parse_quote!(#[doc = "The `Event` enum of this pallet"])); } // derive some traits because system event require Clone, FullCodec, Eq, PartialEq and Debug diff --git a/frame/support/procedural/src/pallet/expand/genesis_build.rs b/frame/support/procedural/src/pallet/expand/genesis_build.rs index d19476779011..f54d308fce30 100644 --- a/frame/support/procedural/src/pallet/expand/genesis_build.rs +++ b/frame/support/procedural/src/pallet/expand/genesis_build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,7 @@ use crate::pallet::Def; /// -/// * implement the trait `sp_runtime::BuildModuleGenesisStorage` -/// * add #[cfg(feature = "std")] to GenesisBuild implementation. +/// * implement the trait `sp_runtime::BuildStorage` pub fn expand_genesis_build(def: &mut Def) -> proc_macro2::TokenStream { let genesis_config = if let Some(genesis_config) = &def.genesis_config { genesis_config @@ -29,44 +28,22 @@ pub fn expand_genesis_build(def: &mut Def) -> proc_macro2::TokenStream { let genesis_build = def.genesis_build.as_ref().expect("Checked by def parser"); let frame_support = &def.frame_support; - let type_impl_gen = &def.type_impl_generics(genesis_build.attr_span); - let type_use_gen = &def.type_use_generics(genesis_build.attr_span); - let trait_use_gen = if def.config.has_instance { - quote::quote_spanned!(genesis_build.attr_span => T, I) - } else { - // `__InherentHiddenInstance` used by construct_runtime here is alias for `()` - quote::quote_spanned!(genesis_build.attr_span => T, ()) - }; + let type_impl_gen = &genesis_config.gen_kind.type_impl_gen(genesis_build.attr_span); let gen_cfg_ident = &genesis_config.genesis_config; + let gen_cfg_use_gen = &genesis_config.gen_kind.type_use_gen(genesis_build.attr_span); - let gen_cfg_use_gen = genesis_config.gen_kind.type_use_gen(genesis_build.attr_span); - - let genesis_build_item = - &mut def.item.content.as_mut().expect("Checked by def parser").1[genesis_build.index]; - - let genesis_build_item_impl = if let syn::Item::Impl(impl_) = genesis_build_item { - impl_ - } else { - unreachable!("Checked by genesis_build parser") - }; - - genesis_build_item_impl.attrs.push(syn::parse_quote!( #[cfg(feature = "std")] )); let where_clause = &genesis_build.where_clause; quote::quote_spanned!(genesis_build.attr_span => #[cfg(feature = "std")] - impl<#type_impl_gen> #frame_support::sp_runtime::BuildModuleGenesisStorage<#trait_use_gen> - for #gen_cfg_ident<#gen_cfg_use_gen> #where_clause - { - fn build_module_genesis_storage( - &self, - storage: &mut #frame_support::sp_runtime::Storage, - ) -> std::result::Result<(), std::string::String> { - #frame_support::BasicExternalities::execute_with_storage(storage, || { - >::build(self); - Ok(()) - }) + impl<#type_impl_gen> #frame_support::sp_runtime::BuildStorage for #gen_cfg_ident<#gen_cfg_use_gen> #where_clause + { + fn assimilate_storage(&self, storage: &mut sp_runtime::Storage) -> std::result::Result<(), std::string::String> { + #frame_support::BasicExternalities::execute_with_storage(storage, || { + self.build(); + Ok(()) + }) + } } - } ) } diff --git a/frame/support/procedural/src/pallet/expand/genesis_config.rs b/frame/support/procedural/src/pallet/expand/genesis_config.rs index 739e85e0d1ce..cbe47bd8505f 100644 --- a/frame/support/procedural/src/pallet/expand/genesis_config.rs +++ b/frame/support/procedural/src/pallet/expand/genesis_config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -89,12 +89,11 @@ pub fn expand_genesis_config(def: &mut Def) -> proc_macro2::TokenStream { attrs.push(syn::parse_quote!( #[doc = r" Can be used to configure the - [genesis state](https://docs.substrate.io/v3/runtime/chain-specs#the-genesis-state) + [genesis state](https://docs.substrate.io/build/genesis-configuration/) of this pallet. "] )); } - attrs.push(syn::parse_quote!( #[cfg(feature = "std")] )); attrs.push(syn::parse_quote!( #[derive(#frame_support::Serialize, #frame_support::Deserialize)] )); @@ -126,7 +125,7 @@ pub fn expand_genesis_config(def: &mut Def) -> proc_macro2::TokenStream { stringify!($pallet_name), "` does not have the std feature enabled, this will cause the `", $pallet_path, - "::GenesisConfig` type to be undefined." + "::GenesisConfig` type to not implement serde traits." )); }; } diff --git a/frame/support/procedural/src/pallet/expand/hooks.rs b/frame/support/procedural/src/pallet/expand/hooks.rs index c41a65b57b57..d2d2b2967faf 100644 --- a/frame/support/procedural/src/pallet/expand/hooks.rs +++ b/frame/support/procedural/src/pallet/expand/hooks.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -75,61 +75,112 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream { let frame_system = &def.frame_system; quote::quote! { impl<#type_impl_gen> - #frame_support::traits::Hooks<::BlockNumber> - for Pallet<#type_use_gen> {} + #frame_support::traits::Hooks<#frame_system::pallet_prelude::BlockNumberFor::> + for #pallet_ident<#type_use_gen> #where_clause {} } } else { proc_macro2::TokenStream::new() }; + // If a storage version is set, we should ensure that the storage version on chain matches the + // current storage version. This assumes that `Executive` is running custom migrations before + // the pallets are called. + let post_storage_version_check = if def.pallet_struct.storage_version.is_some() { + quote::quote! { + let on_chain_version = ::on_chain_storage_version(); + let current_version = ::current_storage_version(); + + if on_chain_version != current_version { + let pallet_name = < + ::PalletInfo + as + #frame_support::traits::PalletInfo + >::name::().unwrap_or(""); + + #frame_support::log::error!( + target: #frame_support::LOG_TARGET, + "{}: On chain storage version {:?} doesn't match current storage version {:?}.", + pallet_name, + on_chain_version, + current_version, + ); + + return Err("On chain and current storage version do not match. Missing runtime upgrade?".into()); + } + } + } else { + quote::quote! { + let on_chain_version = ::on_chain_storage_version(); + + if on_chain_version != #frame_support::traits::StorageVersion::new(0) { + let pallet_name = < + ::PalletInfo + as + #frame_support::traits::PalletInfo + >::name::().unwrap_or(""); + + #frame_support::log::error!( + target: #frame_support::LOG_TARGET, + "{}: On chain storage version {:?} is set to non zero, \ + while the pallet is missing the `#[pallet::storage_version(VERSION)]` attribute.", + pallet_name, + on_chain_version, + ); + + return Err("On chain storage version set, while the pallet doesn't \ + have the `#[pallet::storage_version(VERSION)]` attribute.".into()); + } + } + }; + quote::quote_spanned!(span => #hooks_impl impl<#type_impl_gen> - #frame_support::traits::OnFinalize<::BlockNumber> + #frame_support::traits::OnFinalize<#frame_system::pallet_prelude::BlockNumberFor::> for #pallet_ident<#type_use_gen> #where_clause { - fn on_finalize(n: ::BlockNumber) { + fn on_finalize(n: #frame_system::pallet_prelude::BlockNumberFor::) { #frame_support::sp_tracing::enter_span!( #frame_support::sp_tracing::trace_span!("on_finalize") ); < Self as #frame_support::traits::Hooks< - ::BlockNumber + #frame_system::pallet_prelude::BlockNumberFor:: > >::on_finalize(n) } } impl<#type_impl_gen> - #frame_support::traits::OnIdle<::BlockNumber> + #frame_support::traits::OnIdle<#frame_system::pallet_prelude::BlockNumberFor::> for #pallet_ident<#type_use_gen> #where_clause { fn on_idle( - n: ::BlockNumber, + n: #frame_system::pallet_prelude::BlockNumberFor::, remaining_weight: #frame_support::weights::Weight ) -> #frame_support::weights::Weight { < Self as #frame_support::traits::Hooks< - ::BlockNumber + #frame_system::pallet_prelude::BlockNumberFor:: > >::on_idle(n, remaining_weight) } } impl<#type_impl_gen> - #frame_support::traits::OnInitialize<::BlockNumber> + #frame_support::traits::OnInitialize<#frame_system::pallet_prelude::BlockNumberFor::> for #pallet_ident<#type_use_gen> #where_clause { fn on_initialize( - n: ::BlockNumber + n: #frame_system::pallet_prelude::BlockNumberFor:: ) -> #frame_support::weights::Weight { #frame_support::sp_tracing::enter_span!( #frame_support::sp_tracing::trace_span!("on_initialize") ); < Self as #frame_support::traits::Hooks< - ::BlockNumber + #frame_system::pallet_prelude::BlockNumberFor:: > >::on_initialize(n) } @@ -154,38 +205,40 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream { < Self as #frame_support::traits::Hooks< - ::BlockNumber + #frame_system::pallet_prelude::BlockNumberFor:: > >::on_runtime_upgrade() } #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result<#frame_support::sp_std::vec::Vec, &'static str> { + fn pre_upgrade() -> Result<#frame_support::sp_std::vec::Vec, #frame_support::sp_runtime::TryRuntimeError> { < Self as - #frame_support::traits::Hooks<::BlockNumber> + #frame_support::traits::Hooks<#frame_system::pallet_prelude::BlockNumberFor::> >::pre_upgrade() } #[cfg(feature = "try-runtime")] - fn post_upgrade(state: #frame_support::sp_std::vec::Vec) -> Result<(), &'static str> { + fn post_upgrade(state: #frame_support::sp_std::vec::Vec) -> Result<(), #frame_support::sp_runtime::TryRuntimeError> { + #post_storage_version_check + < Self as - #frame_support::traits::Hooks<::BlockNumber> + #frame_support::traits::Hooks<#frame_system::pallet_prelude::BlockNumberFor::> >::post_upgrade(state) } } impl<#type_impl_gen> - #frame_support::traits::OffchainWorker<::BlockNumber> + #frame_support::traits::OffchainWorker<#frame_system::pallet_prelude::BlockNumberFor::> for #pallet_ident<#type_use_gen> #where_clause { - fn offchain_worker(n: ::BlockNumber) { + fn offchain_worker(n: #frame_system::pallet_prelude::BlockNumberFor::) { < Self as #frame_support::traits::Hooks< - ::BlockNumber + #frame_system::pallet_prelude::BlockNumberFor:: > >::offchain_worker(n) } @@ -198,28 +251,30 @@ pub fn expand_hooks(def: &mut Def) -> proc_macro2::TokenStream { for #pallet_ident<#type_use_gen> #where_clause { fn integrity_test() { - < - Self as #frame_support::traits::Hooks< - ::BlockNumber - > + #frame_support::sp_io::TestExternalities::default().execute_with(|| { + < + Self as #frame_support::traits::Hooks< + #frame_system::pallet_prelude::BlockNumberFor:: + > >::integrity_test() + }); } } } #[cfg(feature = "try-runtime")] impl<#type_impl_gen> - #frame_support::traits::TryState<::BlockNumber> + #frame_support::traits::TryState<#frame_system::pallet_prelude::BlockNumberFor::> for #pallet_ident<#type_use_gen> #where_clause { fn try_state( - n: ::BlockNumber, + n: #frame_system::pallet_prelude::BlockNumberFor::, _s: #frame_support::traits::TryStateSelect - ) -> Result<(), &'static str> { + ) -> Result<(), #frame_support::sp_runtime::TryRuntimeError> { #log_try_state < Self as #frame_support::traits::Hooks< - ::BlockNumber + #frame_system::pallet_prelude::BlockNumberFor:: > >::try_state(n) } diff --git a/frame/support/procedural/src/pallet/expand/inherent.rs b/frame/support/procedural/src/pallet/expand/inherent.rs index 71d95f958fcd..182d79f5b0a9 100644 --- a/frame/support/procedural/src/pallet/expand/inherent.rs +++ b/frame/support/procedural/src/pallet/expand/inherent.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet/expand/instances.rs b/frame/support/procedural/src/pallet/expand/instances.rs index 52dc88763550..b6dfa7e6d9e9 100644 --- a/frame/support/procedural/src/pallet/expand/instances.rs +++ b/frame/support/procedural/src/pallet/expand/instances.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet/expand/mod.rs b/frame/support/procedural/src/pallet/expand/mod.rs index c33d2386700b..2b998227c1d8 100644 --- a/frame/support/procedural/src/pallet/expand/mod.rs +++ b/frame/support/procedural/src/pallet/expand/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,8 @@ mod call; mod config; mod constants; +mod doc_only; +mod documentation; mod error; mod event; mod genesis_build; @@ -34,7 +36,6 @@ mod type_value; mod validate_unsigned; use crate::pallet::Def; -use frame_support_procedural_tools::get_doc_literals; use quote::ToTokens; /// Merge where clause together, `where` token span is taken from the first not none one. @@ -52,6 +53,8 @@ pub fn merge_where_clauses(clauses: &[&Option]) -> Option proc_macro2::TokenStream { + // Remove the `pallet_doc` attribute first. + let metadata_docs = documentation::expand_documentation(&mut def); let constants = constants::expand_constants(&mut def); let pallet_struct = pallet_struct::expand_pallet_struct(&mut def); let config = config::expand_config(&mut def); @@ -69,19 +72,29 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream { let origins = origin::expand_origins(&mut def); let validate_unsigned = validate_unsigned::expand_validate_unsigned(&mut def); let tt_default_parts = tt_default_parts::expand_tt_default_parts(&mut def); + let doc_only = doc_only::expand_doc_only(&mut def); - if get_doc_literals(&def.item.attrs).is_empty() { - def.item.attrs.push(syn::parse_quote!( - #[doc = r" - The module that hosts all the - [FRAME](https://docs.substrate.io/main-docs/build/events-errors/) - types needed to add this pallet to a - runtime. - "] - )); - } + def.item.attrs.insert( + 0, + syn::parse_quote!( + #[doc = r"The `pallet` module in each FRAME pallet hosts the most important items needed +to construct this pallet. + +The main components of this pallet are: +- [`Pallet`], which implements all of the dispatchable extrinsics of the pallet, among +other public functions. + - The subset of the functions that are dispatchable can be identified either in the + [`dispatchables`] module or in the [`Call`] enum. +- [`storage_types`], which contains the list of all types that are representing a +storage item. Otherwise, all storage items are listed among [*Type Definitions*](#types). +- [`Config`], which contains the configuration trait of this pallet. +- [`Event`] and [`Error`], which are listed among the [*Enums*](#enums). + "] + ), + ); let new_items = quote::quote!( + #metadata_docs #constants #pallet_struct #config @@ -99,6 +112,7 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream { #origins #validate_unsigned #tt_default_parts + #doc_only ); def.item diff --git a/frame/support/procedural/src/pallet/expand/origin.rs b/frame/support/procedural/src/pallet/expand/origin.rs index 721fc781a907..55865b42491a 100644 --- a/frame/support/procedural/src/pallet/expand/origin.rs +++ b/frame/support/procedural/src/pallet/expand/origin.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet/expand/pallet_struct.rs b/frame/support/procedural/src/pallet/expand/pallet_struct.rs index e5941a33fee1..800e23388c1a 100644 --- a/frame/support/procedural/src/pallet/expand/pallet_struct.rs +++ b/frame/support/procedural/src/pallet/expand/pallet_struct.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -62,8 +62,8 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { if get_doc_literals(&pallet_item.attrs).is_empty() { pallet_item.attrs.push(syn::parse_quote!( #[doc = r" - The [pallet](https://docs.substrate.io/reference/frame-pallets/#pallets) implementing - the on-chain logic. + The `Pallet` struct, the main type that implements traits and standalone + functions within the pallet. "] )); } @@ -82,8 +82,8 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { quote::quote_spanned!(def.pallet_struct.attr_span => impl<#type_impl_gen> #pallet_ident<#type_use_gen> #config_where_clause { #[doc(hidden)] - pub fn error_metadata() -> Option<#frame_support::metadata::PalletErrorMetadata> { - Some(#frame_support::metadata::PalletErrorMetadata { + pub fn error_metadata() -> Option<#frame_support::metadata_ir::PalletErrorMetadataIR> { + Some(#frame_support::metadata_ir::PalletErrorMetadataIR { ty: #frame_support::scale_info::meta_type::<#error_ident<#type_use_gen>>() }) } @@ -93,7 +93,7 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { quote::quote_spanned!(def.pallet_struct.attr_span => impl<#type_impl_gen> #pallet_ident<#type_use_gen> #config_where_clause { #[doc(hidden)] - pub fn error_metadata() -> Option<#frame_support::metadata::PalletErrorMetadata> { + pub fn error_metadata() -> Option<#frame_support::metadata_ir::PalletErrorMetadataIR> { None } } @@ -160,11 +160,15 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { } ); - let storage_version = if let Some(v) = def.pallet_struct.storage_version.as_ref() { - quote::quote! { #v } - } else { - quote::quote! { #frame_support::traits::StorageVersion::default() } - }; + let (storage_version, current_storage_version_ty) = + if let Some(v) = def.pallet_struct.storage_version.as_ref() { + (quote::quote! { #v }, quote::quote! { #frame_support::traits::StorageVersion }) + } else { + ( + quote::quote! { core::default::Default::default() }, + quote::quote! { #frame_support::traits::NoStorageVersionSet }, + ) + }; let whitelisted_storage_idents: Vec = def .storages @@ -199,7 +203,9 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { for #pallet_ident<#type_use_gen> #config_where_clause { - fn current_storage_version() -> #frame_support::traits::StorageVersion { + type CurrentStorageVersion = #current_storage_version_ty; + + fn current_storage_version() -> Self::CurrentStorageVersion { #storage_version } @@ -214,7 +220,7 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream { #config_where_clause { fn on_genesis() { - let storage_version = #storage_version; + let storage_version: #frame_support::traits::StorageVersion = #storage_version; storage_version.put::(); } } diff --git a/frame/support/procedural/src/pallet/expand/storage.rs b/frame/support/procedural/src/pallet/expand/storage.rs index 195a62431f27..253b429bb6eb 100644 --- a/frame/support/procedural/src/pallet/expand/storage.rs +++ b/frame/support/procedural/src/pallet/expand/storage.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,10 +15,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::pallet::{ - parse::storage::{Metadata, QueryKind, StorageDef, StorageGenerics}, - Def, +use crate::{ + counter_prefix, + pallet::{ + parse::storage::{Metadata, QueryKind, StorageDef, StorageGenerics}, + Def, + }, }; +use itertools::Itertools; use quote::ToTokens; use std::{collections::HashMap, ops::IndexMut}; use syn::spanned::Spanned; @@ -39,12 +43,6 @@ fn counter_prefix_ident(storage_ident: &syn::Ident) -> syn::Ident { ) } -/// Generate the counter_prefix related to the storage. -/// counter_prefix is used by counted storage map. -fn counter_prefix(prefix: &str) -> String { - format!("CounterFor{}", prefix) -} - /// Check for duplicated storage prefixes. This step is necessary since users can specify an /// alternative storage prefix using the #[pallet::storage_prefix] syntax, and we need to ensure /// that the prefix specified by the user is not a duplicate of an existing one. @@ -272,6 +270,19 @@ pub fn process_generics(def: &mut Def) -> syn::Result (5, 6, 7), }; + if storage_def.use_default_hasher { + let hasher_indices: Vec = match storage_def.metadata { + Metadata::Map { .. } | Metadata::CountedMap { .. } => vec![1], + Metadata::DoubleMap { .. } => vec![1, 3], + _ => vec![], + }; + for hasher_idx in hasher_indices { + args.args[hasher_idx] = syn::GenericArgument::Type( + syn::parse_quote!(#frame_support::Blake2_128Concat), + ); + } + } + if query_idx < args.args.len() { if let syn::GenericArgument::Type(query_kind) = args.args.index_mut(query_idx) { set_result_query_type_parameter(query_kind)?; @@ -300,6 +311,65 @@ pub fn process_generics(def: &mut Def) -> syn::Result t, + _ => unreachable!("Checked by def"), + }; + typ_item.attrs.push(syn::parse_quote!(#[doc = ""])); + typ_item.attrs.push(syn::parse_quote!(#[doc = #doc_line])); + }; + def.storages.iter_mut().for_each(|storage| match &storage.metadata { + Metadata::Value { value } => { + let doc_line = format!( + "Storage type is [`StorageValue`] with value type `{}`.", + value.to_token_stream() + ); + push_string_literal(&doc_line, storage); + }, + Metadata::Map { key, value } => { + let doc_line = format!( + "Storage type is [`StorageMap`] with key type `{}` and value type `{}`.", + key.to_token_stream(), + value.to_token_stream() + ); + push_string_literal(&doc_line, storage); + }, + Metadata::DoubleMap { key1, key2, value } => { + let doc_line = format!( + "Storage type is [`StorageDoubleMap`] with key1 type {}, key2 type {} and value type {}.", + key1.to_token_stream(), + key2.to_token_stream(), + value.to_token_stream() + ); + push_string_literal(&doc_line, storage); + }, + Metadata::NMap { keys, value, .. } => { + let doc_line = format!( + "Storage type is [`StorageNMap`] with keys type ({}) and value type {}.", + keys.iter() + .map(|k| k.to_token_stream().to_string()) + .collect::>() + .join(", "), + value.to_token_stream() + ); + push_string_literal(&doc_line, storage); + }, + Metadata::CountedMap { key, value } => { + let doc_line = format!( + "Storage type is [`CountedStorageMap`] with key type {} and value type {}.", + key.to_token_stream(), + value.to_token_stream() + ); + push_string_literal(&doc_line, storage); + }, + }); +} + /// /// * generate StoragePrefix structs (e.g. for a storage `MyStorage` a struct with the name /// `_GeneratedPrefixForStorage$NameOfStorage` is generated) and implements StorageInstance trait. @@ -313,6 +383,8 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { Err(e) => return e.into_compile_error(), }; + augment_final_docs(def); + // Check for duplicate prefixes let mut prefix_set = HashMap::new(); let mut errors = def @@ -355,10 +427,6 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { if let Some(getter) = &storage.getter { let completed_where_clause = super::merge_where_clauses(&[&storage.where_clause, &def.config.where_clause]); - let docs = storage - .docs - .iter() - .map(|d| quote::quote_spanned!(storage.attr_span => #[doc = #d])); let ident = &storage.ident; let gen = &def.type_use_generics(storage.attr_span); @@ -368,22 +436,30 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { let cfg_attrs = &storage.cfg_attrs; + // If the storage item is public, just link to it rather than copy-pasting the docs. + let getter_doc_line = if matches!(storage.vis, syn::Visibility::Public(_)) { + format!("An auto-generated getter for [`{}`].", storage.ident) + } else { + storage.docs.iter().map(|d| d.into_token_stream().to_string()).join("\n") + }; + match &storage.metadata { Metadata::Value { value } => { let query = match storage.query_kind.as_ref().expect("Checked by def") { QueryKind::OptionQuery => quote::quote_spanned!(storage.attr_span => Option<#value> ), - QueryKind::ResultQuery(error_path, _) => + QueryKind::ResultQuery(error_path, _) => { quote::quote_spanned!(storage.attr_span => Result<#value, #error_path> - ), + ) + }, QueryKind::ValueQuery => quote::quote!(#value), }; quote::quote_spanned!(storage.attr_span => #(#cfg_attrs)* impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause { - #( #docs )* + #[doc = #getter_doc_line] pub fn #getter() -> #query { < #full_ident as #frame_support::storage::StorageValue<#value> @@ -397,16 +473,17 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { QueryKind::OptionQuery => quote::quote_spanned!(storage.attr_span => Option<#value> ), - QueryKind::ResultQuery(error_path, _) => + QueryKind::ResultQuery(error_path, _) => { quote::quote_spanned!(storage.attr_span => Result<#value, #error_path> - ), + ) + }, QueryKind::ValueQuery => quote::quote!(#value), }; quote::quote_spanned!(storage.attr_span => #(#cfg_attrs)* impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause { - #( #docs )* + #[doc = #getter_doc_line] pub fn #getter(k: KArg) -> #query where KArg: #frame_support::codec::EncodeLike<#key>, { @@ -422,16 +499,17 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { QueryKind::OptionQuery => quote::quote_spanned!(storage.attr_span => Option<#value> ), - QueryKind::ResultQuery(error_path, _) => + QueryKind::ResultQuery(error_path, _) => { quote::quote_spanned!(storage.attr_span => Result<#value, #error_path> - ), + ) + }, QueryKind::ValueQuery => quote::quote!(#value), }; quote::quote_spanned!(storage.attr_span => #(#cfg_attrs)* impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause { - #( #docs )* + #[doc = #getter_doc_line] pub fn #getter(k: KArg) -> #query where KArg: #frame_support::codec::EncodeLike<#key>, { @@ -447,16 +525,17 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { QueryKind::OptionQuery => quote::quote_spanned!(storage.attr_span => Option<#value> ), - QueryKind::ResultQuery(error_path, _) => + QueryKind::ResultQuery(error_path, _) => { quote::quote_spanned!(storage.attr_span => Result<#value, #error_path> - ), + ) + }, QueryKind::ValueQuery => quote::quote!(#value), }; quote::quote_spanned!(storage.attr_span => #(#cfg_attrs)* impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause { - #( #docs )* + #[doc = #getter_doc_line] pub fn #getter(k1: KArg1, k2: KArg2) -> #query where KArg1: #frame_support::codec::EncodeLike<#key1>, KArg2: #frame_support::codec::EncodeLike<#key2>, @@ -474,16 +553,17 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { QueryKind::OptionQuery => quote::quote_spanned!(storage.attr_span => Option<#value> ), - QueryKind::ResultQuery(error_path, _) => + QueryKind::ResultQuery(error_path, _) => { quote::quote_spanned!(storage.attr_span => Result<#value, #error_path> - ), + ) + }, QueryKind::ValueQuery => quote::quote!(#value), }; quote::quote_spanned!(storage.attr_span => #(#cfg_attrs)* impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause { - #( #docs )* + #[doc = #getter_doc_line] pub fn #getter(key: KArg) -> #query where KArg: #frame_support::storage::types::EncodeLikeTuple< @@ -642,8 +722,8 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream { #completed_where_clause { #[doc(hidden)] - pub fn storage_metadata() -> #frame_support::metadata::PalletStorageMetadata { - #frame_support::metadata::PalletStorageMetadata { + pub fn storage_metadata() -> #frame_support::metadata_ir::PalletStorageMetadataIR { + #frame_support::metadata_ir::PalletStorageMetadataIR { prefix: < ::PalletInfo as #frame_support::traits::PalletInfo diff --git a/frame/support/procedural/src/pallet/expand/store_trait.rs b/frame/support/procedural/src/pallet/expand/store_trait.rs index 4fb4f46143a1..6635adc98815 100644 --- a/frame/support/procedural/src/pallet/expand/store_trait.rs +++ b/frame/support/procedural/src/pallet/expand/store_trait.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ use syn::spanned::Spanned; /// * generate Store trait with all storages, /// * implement Store trait for Pallet. pub fn expand_store_trait(def: &mut Def) -> proc_macro2::TokenStream { - let (trait_vis, trait_store) = + let (trait_vis, trait_store, attribute_span) = if let Some(store) = &def.pallet_struct.store { store } else { return Default::default() }; let type_impl_gen = &def.type_impl_generics(trait_store.span()); @@ -36,8 +36,19 @@ pub fn expand_store_trait(def: &mut Def) -> proc_macro2::TokenStream { let storage_names = &def.storages.iter().map(|storage| &storage.ident).collect::>(); let storage_cfg_attrs = &def.storages.iter().map(|storage| &storage.cfg_attrs).collect::>(); + let warnig_struct_name = syn::Ident::new("Store", *attribute_span); + let warning: syn::ItemStruct = syn::parse_quote!( + #[deprecated(note = r" + Use of `#[pallet::generate_store(pub(super) trait Store)]` will be removed after July 2023. + Check https://github.com/paritytech/substrate/pull/13535 for more details.")] + struct #warnig_struct_name; + ); quote::quote_spanned!(trait_store.span() => + const _:() = { + #warning + const _: Option<#warnig_struct_name> = None; + }; #trait_vis trait #trait_store { #( #(#storage_cfg_attrs)* diff --git a/frame/support/procedural/src/pallet/expand/tt_default_parts.rs b/frame/support/procedural/src/pallet/expand/tt_default_parts.rs index 173dcc0e2ac3..356bdbf67e92 100644 --- a/frame/support/procedural/src/pallet/expand/tt_default_parts.rs +++ b/frame/support/procedural/src/pallet/expand/tt_default_parts.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{pallet::Def, COUNTER}; +use crate::{ + pallet::{CompositeKeyword, Def}, + COUNTER, +}; use syn::spanned::Spanned; /// Generate the `tt_default_parts` macro. @@ -23,6 +26,8 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { let count = COUNTER.with(|counter| counter.borrow_mut().inc()); let default_parts_unique_id = syn::Ident::new(&format!("__tt_default_parts_{}", count), def.item.span()); + let extra_parts_unique_id = + syn::Ident::new(&format!("__tt_extra_parts_{}", count), def.item.span()); let call_part = def.call.as_ref().map(|_| quote::quote!(Call,)); @@ -33,6 +38,8 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { quote::quote!( Event #gen , ) }); + let error_part = def.error.as_ref().map(|_| quote::quote!(Error,)); + let origin_part = def.origin.as_ref().map(|origin| { let gen = origin.is_generic.then(|| quote::quote!( )); quote::quote!( Origin #gen , ) @@ -48,6 +55,30 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { let validate_unsigned_part = def.validate_unsigned.as_ref().map(|_| quote::quote!(ValidateUnsigned,)); + let freeze_reason_part = def + .composites + .iter() + .any(|c| matches!(c.composite_keyword, CompositeKeyword::FreezeReason(_))) + .then_some(quote::quote!(FreezeReason,)); + + let hold_reason_part = def + .composites + .iter() + .any(|c| matches!(c.composite_keyword, CompositeKeyword::HoldReason(_))) + .then_some(quote::quote!(HoldReason,)); + + let lock_id_part = def + .composites + .iter() + .any(|c| matches!(c.composite_keyword, CompositeKeyword::LockId(_))) + .then_some(quote::quote!(LockId,)); + + let slash_reason_part = def + .composites + .iter() + .any(|c| matches!(c.composite_keyword, CompositeKeyword::SlashReason(_))) + .then_some(quote::quote!(SlashReason,)); + quote::quote!( // This macro follows the conventions as laid out by the `tt-call` crate. It does not // accept any arguments and simply returns the pallet parts, separated by commas, then @@ -68,9 +99,10 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { $($frame_support)*::tt_return! { $caller tokens = [{ - ::{ - Pallet, #call_part #storage_part #event_part #origin_part #config_part - #inherent_part #validate_unsigned_part + expanded::{ + Pallet, #call_part #storage_part #event_part #error_part #origin_part #config_part + #inherent_part #validate_unsigned_part #freeze_reason_part + #hold_reason_part #lock_id_part #slash_reason_part } }] } @@ -78,5 +110,33 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { } pub use #default_parts_unique_id as tt_default_parts; + + + // This macro is similar to the `tt_default_parts!`. It expands the pallets thare are declared + // explicitly (`System: frame_system::{Pallet, Call}`) with extra parts. + // + // For example, after expansion an explicit pallet would look like: + // `System: expanded::{Error} ::{Pallet, Call}`. + // + // The `expanded` keyword is a marker of the final state of the `construct_runtime!`. + #[macro_export] + #[doc(hidden)] + macro_rules! #extra_parts_unique_id { + { + $caller:tt + frame_support = [{ $($frame_support:ident)::* }] + } => { + $($frame_support)*::tt_return! { + $caller + tokens = [{ + expanded::{ + #error_part + } + }] + } + }; + } + + pub use #extra_parts_unique_id as tt_extra_parts; ) } diff --git a/frame/support/procedural/src/pallet/expand/type_value.rs b/frame/support/procedural/src/pallet/expand/type_value.rs index abe52459a791..5dc6309c074a 100644 --- a/frame/support/procedural/src/pallet/expand/type_value.rs +++ b/frame/support/procedural/src/pallet/expand/type_value.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet/expand/validate_unsigned.rs b/frame/support/procedural/src/pallet/expand/validate_unsigned.rs index b49ba2b5b02e..876995585171 100644 --- a/frame/support/procedural/src/pallet/expand/validate_unsigned.rs +++ b/frame/support/procedural/src/pallet/expand/validate_unsigned.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet/mod.rs b/frame/support/procedural/src/pallet/mod.rs index 3f85be81c1f7..3618711051d7 100644 --- a/frame/support/procedural/src/pallet/mod.rs +++ b/frame/support/procedural/src/pallet/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,7 +28,7 @@ mod expand; mod parse; -pub use parse::Def; +pub use parse::{composite::keyword::CompositeKeyword, Def}; use syn::spanned::Spanned; mod keyword { diff --git a/frame/support/procedural/src/pallet/parse/call.rs b/frame/support/procedural/src/pallet/parse/call.rs index 3af3225484fa..90631f264b92 100644 --- a/frame/support/procedural/src/pallet/parse/call.rs +++ b/frame/support/procedural/src/pallet/parse/call.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::helper; +use super::{helper, InheritedCallWeightAttr}; use frame_support_procedural_tools::get_doc_literals; use quote::ToTokens; use std::collections::HashMap; @@ -45,7 +45,24 @@ pub struct CallDef { /// The span of the pallet::call attribute. pub attr_span: proc_macro2::Span, /// Docs, specified on the impl Block. - pub docs: Vec, + pub docs: Vec, + /// The optional `weight` attribute on the `pallet::call`. + pub inherited_call_weight: Option, +} + +/// The weight of a call. +#[derive(Clone)] +pub enum CallWeightDef { + /// Explicitly set on the call itself with `#[pallet::weight(…)]`. This value is used. + Immediate(syn::Expr), + + /// The default value that should be set for dev-mode pallets. Usually zero. + DevModeDefault, + + /// Inherits whatever value is configured on the pallet level. + /// + /// The concrete value is not known at this point. + Inherited, } /// Definition of dispatchable typically: `#[weight...] fn foo(origin .., param1: ...) -> ..` @@ -55,14 +72,14 @@ pub struct CallVariantDef { pub name: syn::Ident, /// Information on args: `(is_compact, name, type)` pub args: Vec<(bool, syn::Ident, Box)>, - /// Weight formula. - pub weight: syn::Expr, + /// Weight for the call. + pub weight: CallWeightDef, /// Call index of the dispatchable. pub call_index: u8, /// Whether an explicit call index was specified. pub explicit_call_index: bool, /// Docs, used for metadata. - pub docs: Vec, + pub docs: Vec, /// Attributes annotated at the top of the dispatchable function. pub attrs: Vec, } @@ -151,6 +168,7 @@ impl CallDef { index: usize, item: &mut syn::Item, dev_mode: bool, + inherited_call_weight: Option, ) -> syn::Result { let item_impl = if let syn::Item::Impl(item) = item { item @@ -173,7 +191,7 @@ impl CallDef { let mut indices = HashMap::new(); let mut last_index: Option = None; for item in &mut item_impl.items { - if let syn::ImplItem::Method(method) = item { + if let syn::ImplItem::Fn(method) = item { if !matches!(method.vis, syn::Visibility::Public(_)) { let msg = "Invalid pallet::call, dispatchable function must be public: \ `pub fn`"; @@ -228,17 +246,23 @@ impl CallDef { weight_attrs.push(FunctionAttr::Weight(empty_weight)); } - if weight_attrs.len() != 1 { - let msg = if weight_attrs.is_empty() { - "Invalid pallet::call, requires weight attribute i.e. `#[pallet::weight($expr)]`" - } else { - "Invalid pallet::call, too many weight attributes given" - }; - return Err(syn::Error::new(method.sig.span(), msg)) - } - let weight = match weight_attrs.pop().unwrap() { - FunctionAttr::Weight(w) => w, - _ => unreachable!("checked during creation of the let binding"), + let weight = match weight_attrs.len() { + 0 if inherited_call_weight.is_some() => CallWeightDef::Inherited, + 0 if dev_mode => CallWeightDef::DevModeDefault, + 0 => return Err(syn::Error::new( + method.sig.span(), + "A pallet::call requires either a concrete `#[pallet::weight($expr)]` or an + inherited weight from the `#[pallet:call(weight($type))]` attribute, but + none were given.", + )), + 1 => match weight_attrs.pop().unwrap() { + FunctionAttr::Weight(w) => CallWeightDef::Immediate(w), + _ => unreachable!("checked during creation of the let binding"), + }, + _ => { + let msg = "Invalid pallet::call, too many weight attributes given"; + return Err(syn::Error::new(method.sig.span(), msg)) + }, }; if call_idx_attrs.len() > 1 { @@ -321,6 +345,7 @@ impl CallDef { methods, where_clause: item_impl.generics.where_clause.clone(), docs: get_doc_literals(&item_impl.attrs), + inherited_call_weight, }) } } diff --git a/frame/support/procedural/src/pallet/parse/composite.rs b/frame/support/procedural/src/pallet/parse/composite.rs new file mode 100644 index 000000000000..2bbfcd2e998a --- /dev/null +++ b/frame/support/procedural/src/pallet/parse/composite.rs @@ -0,0 +1,136 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use quote::ToTokens; +use syn::spanned::Spanned; + +pub mod keyword { + use super::*; + + syn::custom_keyword!(FreezeReason); + syn::custom_keyword!(HoldReason); + syn::custom_keyword!(LockId); + syn::custom_keyword!(SlashReason); + pub enum CompositeKeyword { + FreezeReason(FreezeReason), + HoldReason(HoldReason), + LockId(LockId), + SlashReason(SlashReason), + } + + impl ToTokens for CompositeKeyword { + fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { + use CompositeKeyword::*; + match self { + FreezeReason(inner) => inner.to_tokens(tokens), + HoldReason(inner) => inner.to_tokens(tokens), + LockId(inner) => inner.to_tokens(tokens), + SlashReason(inner) => inner.to_tokens(tokens), + } + } + } + + impl syn::parse::Parse for CompositeKeyword { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + if lookahead.peek(FreezeReason) { + Ok(Self::FreezeReason(input.parse()?)) + } else if lookahead.peek(HoldReason) { + Ok(Self::HoldReason(input.parse()?)) + } else if lookahead.peek(LockId) { + Ok(Self::LockId(input.parse()?)) + } else if lookahead.peek(SlashReason) { + Ok(Self::SlashReason(input.parse()?)) + } else { + Err(lookahead.error()) + } + } + } + + impl std::fmt::Display for CompositeKeyword { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use CompositeKeyword::*; + write!( + f, + "{}", + match self { + FreezeReason(_) => "FreezeReason", + HoldReason(_) => "HoldReason", + LockId(_) => "LockId", + SlashReason(_) => "SlashReason", + } + ) + } + } +} + +pub struct CompositeDef { + /// The index of the HoldReason item in the pallet module. + pub index: usize, + /// The composite keyword used (contains span). + pub composite_keyword: keyword::CompositeKeyword, + /// The span of the pallet::composite_enum attribute. + pub attr_span: proc_macro2::Span, +} + +impl CompositeDef { + pub fn try_from( + attr_span: proc_macro2::Span, + index: usize, + scrate: &proc_macro2::Ident, + item: &mut syn::Item, + ) -> syn::Result { + let item = if let syn::Item::Enum(item) = item { + item + } else { + return Err(syn::Error::new( + item.span(), + "Invalid pallet::composite_enum, expected enum item", + )) + }; + + if !matches!(item.vis, syn::Visibility::Public(_)) { + let msg = format!("Invalid pallet::composite_enum, `{}` must be public", item.ident); + return Err(syn::Error::new(item.span(), msg)) + } + + let has_derive_attr = item.attrs.iter().any(|attr| { + if let syn::Meta::List(syn::MetaList { path, .. }) = &attr.meta { + path.get_ident().map(|ident| ident == "derive").unwrap_or(false) + } else { + false + } + }); + + if !has_derive_attr { + let derive_attr: syn::Attribute = syn::parse_quote! { + #[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, + #scrate::codec::Encode, #scrate::codec::Decode, #scrate::codec::MaxEncodedLen, + #scrate::scale_info::TypeInfo, + #scrate::RuntimeDebug, + )] + }; + item.attrs.push(derive_attr); + } + + let composite_keyword = + syn::parse2::(item.ident.to_token_stream())?; + + Ok(CompositeDef { index, composite_keyword, attr_span }) + } +} diff --git a/frame/support/procedural/src/pallet/parse/config.rs b/frame/support/procedural/src/pallet/parse/config.rs index 0f3aa69b170c..6a3693a3ab0f 100644 --- a/frame/support/procedural/src/pallet/parse/config.rs +++ b/frame/support/procedural/src/pallet/parse/config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ use super::helper; use frame_support_procedural_tools::get_doc_literals; use quote::ToTokens; -use syn::spanned::Spanned; +use syn::{spanned::Spanned, token, Token}; /// List of additional token to be used for parsing. mod keyword { @@ -27,12 +27,14 @@ mod keyword { syn::custom_keyword!(T); syn::custom_keyword!(I); syn::custom_keyword!(config); + syn::custom_keyword!(pallet); syn::custom_keyword!(IsType); syn::custom_keyword!(RuntimeEvent); syn::custom_keyword!(Event); - syn::custom_keyword!(constant); syn::custom_keyword!(frame_system); syn::custom_keyword!(disable_frame_system_supertrait_check); + syn::custom_keyword!(no_default); + syn::custom_keyword!(constant); } /// Input definition for the pallet config. @@ -52,6 +54,12 @@ pub struct ConfigDef { pub where_clause: Option, /// The span of the pallet::config attribute. pub attr_span: proc_macro2::Span, + /// Whether a default sub-trait should be generated. + /// + /// Contains default sub-trait items (instantiated by `#[pallet::config(with_default)]`). + /// Vec will be empty if `#[pallet::config(with_default)]` is not specified or if there are + /// no trait items + pub default_sub_trait: Vec, } /// Input definition for a constant in pallet config. @@ -61,7 +69,7 @@ pub struct ConstMetadataDef { /// The type in Get, e.g. `u32` in `type Foo: Get;`, but `Self` is replaced by `T` pub type_: syn::Type, /// The doc associated - pub doc: Vec, + pub doc: Vec, } impl TryFrom<&syn::TraitItemType> for ConstMetadataDef { @@ -123,28 +131,28 @@ impl syn::parse::Parse for DisableFrameSystemSupertraitCheck { } } -/// Parse for `#[pallet::constant]` -pub struct TypeAttrConst(proc_macro2::Span); - -impl Spanned for TypeAttrConst { - fn span(&self) -> proc_macro2::Span { - self.0 - } +/// Parsing for the `typ` portion of `PalletAttr` +#[derive(derive_syn_parse::Parse, PartialEq, Eq)] +pub enum PalletAttrType { + #[peek(keyword::no_default, name = "no_default")] + NoDefault(keyword::no_default), + #[peek(keyword::constant, name = "constant")] + Constant(keyword::constant), } -impl syn::parse::Parse for TypeAttrConst { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - input.parse::()?; - let content; - syn::bracketed!(content in input); - content.parse::()?; - content.parse::()?; - - Ok(TypeAttrConst(content.parse::()?.span())) - } +/// Parsing for `#[pallet::X]` +#[derive(derive_syn_parse::Parse)] +pub struct PalletAttr { + _pound: Token![#], + #[bracket] + _bracket: token::Bracket, + #[inside(_bracket)] + _pallet: keyword::pallet, + #[prefix(Token![::] in _bracket)] + #[inside(_bracket)] + typ: PalletAttrType, } -/// Parse for `$ident::Config` pub struct ConfigBoundParse(syn::Ident); impl syn::parse::Parse for ConfigBoundParse { @@ -295,6 +303,7 @@ impl ConfigDef { attr_span: proc_macro2::Span, index: usize, item: &mut syn::Item, + enable_default: bool, ) -> syn::Result { let item = if let syn::Item::Trait(item) = item { item @@ -332,38 +341,59 @@ impl ConfigDef { let mut has_event_type = false; let mut consts_metadata = vec![]; + let mut default_sub_trait = vec![]; for trait_item in &mut item.items { - // Parse for event - has_event_type = - has_event_type || check_event_type(frame_system, trait_item, has_instance)?; + let is_event = check_event_type(frame_system, trait_item, has_instance)?; + has_event_type = has_event_type || is_event; - // Parse for constant - let type_attrs_const: Vec = helper::take_item_pallet_attrs(trait_item)?; + let mut already_no_default = false; + let mut already_constant = false; - if type_attrs_const.len() > 1 { - let msg = "Invalid attribute in pallet::config, only one attribute is expected"; - return Err(syn::Error::new(type_attrs_const[1].span(), msg)) - } - - if type_attrs_const.len() == 1 { - match trait_item { - syn::TraitItem::Type(ref type_) => { - let constant = ConstMetadataDef::try_from(type_)?; - consts_metadata.push(constant); + while let Ok(Some(pallet_attr)) = + helper::take_first_item_pallet_attr::(trait_item) + { + match (pallet_attr.typ, &trait_item) { + (PalletAttrType::Constant(_), syn::TraitItem::Type(ref typ)) => { + if already_constant { + return Err(syn::Error::new( + pallet_attr._bracket.span.join(), + "Duplicate #[pallet::constant] attribute not allowed.", + )) + } + already_constant = true; + consts_metadata.push(ConstMetadataDef::try_from(typ)?); }, - _ => { - let msg = - "Invalid pallet::constant in pallet::config, expected type trait \ - item"; - return Err(syn::Error::new(trait_item.span(), msg)) + (PalletAttrType::Constant(_), _) => + return Err(syn::Error::new( + trait_item.span(), + "Invalid #[pallet::constant] in #[pallet::config], expected type item", + )), + (PalletAttrType::NoDefault(_), _) => { + if !enable_default { + return Err(syn::Error::new( + pallet_attr._bracket.span.join(), + "`#[pallet:no_default]` can only be used if `#[pallet::config(with_default)]` \ + has been specified" + )) + } + if already_no_default { + return Err(syn::Error::new( + pallet_attr._bracket.span.join(), + "Duplicate #[pallet::no_default] attribute not allowed.", + )) + } + already_no_default = true; }, } } + + if !already_no_default && !is_event && enable_default { + default_sub_trait.push(trait_item.clone()); + } } let attr: Option = helper::take_first_item_pallet_attr(&mut item.attrs)?; - let disable_system_supertrait_check = attr.is_some(); let has_frame_system_supertrait = item.supertraits.iter().any(|s| { @@ -395,6 +425,14 @@ impl ConfigDef { return Err(syn::Error::new(item.span(), msg)) } - Ok(Self { index, has_instance, consts_metadata, has_event_type, where_clause, attr_span }) + Ok(Self { + index, + has_instance, + consts_metadata, + has_event_type, + where_clause, + attr_span, + default_sub_trait, + }) } } diff --git a/frame/support/procedural/src/pallet/parse/error.rs b/frame/support/procedural/src/pallet/parse/error.rs index c6ce9b37c75a..6f82ce61fc93 100644 --- a/frame/support/procedural/src/pallet/parse/error.rs +++ b/frame/support/procedural/src/pallet/parse/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,7 +37,7 @@ pub struct ErrorDef { /// The index of error item in pallet module. pub index: usize, /// Variants ident, optional field and doc literals (ordered as declaration order) - pub variants: Vec<(syn::Ident, Option, Vec)>, + pub variants: Vec<(syn::Ident, Option, Vec)>, /// A set of usage of instance, must be check for consistency with trait. pub instances: Vec, /// The keyword error used (contains span). diff --git a/frame/support/procedural/src/pallet/parse/event.rs b/frame/support/procedural/src/pallet/parse/event.rs index e046cacac88e..0fb8ee4f5497 100644 --- a/frame/support/procedural/src/pallet/parse/event.rs +++ b/frame/support/procedural/src/pallet/parse/event.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -104,7 +104,7 @@ impl EventDef { let item = if let syn::Item::Enum(item) = item { item } else { - return Err(syn::Error::new(item.span(), "Invalid pallet::event, expected item enum")) + return Err(syn::Error::new(item.span(), "Invalid pallet::event, expected enum item")) }; let event_attrs: Vec = diff --git a/frame/support/procedural/src/pallet/parse/extra_constants.rs b/frame/support/procedural/src/pallet/parse/extra_constants.rs index d8622da08461..2ba6c44b7d15 100644 --- a/frame/support/procedural/src/pallet/parse/extra_constants.rs +++ b/frame/support/procedural/src/pallet/parse/extra_constants.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,7 +50,7 @@ pub struct ExtraConstantDef { /// The type returned by the function pub type_: syn::Type, /// The doc associated - pub doc: Vec, + pub doc: Vec, /// Optional MetaData Name pub metadata_name: Option, } @@ -100,7 +100,7 @@ impl ExtraConstantsDef { let mut extra_constants = vec![]; for impl_item in &mut item.items { - let method = if let syn::ImplItem::Method(method) = impl_item { + let method = if let syn::ImplItem::Fn(method) = impl_item { method } else { let msg = "Invalid pallet::call, only method accepted"; diff --git a/frame/support/procedural/src/pallet/parse/genesis_build.rs b/frame/support/procedural/src/pallet/parse/genesis_build.rs index 9815b8d2203c..d0e1d9ec998e 100644 --- a/frame/support/procedural/src/pallet/parse/genesis_build.rs +++ b/frame/support/procedural/src/pallet/parse/genesis_build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,7 @@ pub struct GenesisBuildDef { /// The index of item in pallet module. pub index: usize, /// A set of usage of instance, must be check for consistency with trait. - pub instances: Vec, + pub instances: Option>, /// The where_clause used. pub where_clause: Option, /// The span of the pallet::genesis_build attribute. @@ -53,7 +53,8 @@ impl GenesisBuildDef { })? .1; - let instances = vec![helper::check_genesis_builder_usage(item_trait)?]; + let instances = + helper::check_genesis_builder_usage(item_trait)?.map(|instances| vec![instances]); Ok(Self { attr_span, index, instances, where_clause: item.generics.where_clause.clone() }) } diff --git a/frame/support/procedural/src/pallet/parse/genesis_config.rs b/frame/support/procedural/src/pallet/parse/genesis_config.rs index 45e765c018aa..62da6ba13b3b 100644 --- a/frame/support/procedural/src/pallet/parse/genesis_config.rs +++ b/frame/support/procedural/src/pallet/parse/genesis_config.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet/parse/helper.rs b/frame/support/procedural/src/pallet/parse/helper.rs index 824407917358..1e6e83d7eeba 100644 --- a/frame/support/procedural/src/pallet/parse/helper.rs +++ b/frame/support/procedural/src/pallet/parse/helper.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,6 +23,7 @@ mod keyword { syn::custom_keyword!(I); syn::custom_keyword!(compact); syn::custom_keyword!(GenesisBuild); + syn::custom_keyword!(BuildGenesisConfig); syn::custom_keyword!(Config); syn::custom_keyword!(T); syn::custom_keyword!(Pallet); @@ -47,14 +48,16 @@ pub trait MutItemAttrs { } /// Take the first pallet attribute (e.g. attribute like `#[pallet..]`) and decode it to `Attr` -pub fn take_first_item_pallet_attr(item: &mut impl MutItemAttrs) -> syn::Result> +pub(crate) fn take_first_item_pallet_attr( + item: &mut impl MutItemAttrs, +) -> syn::Result> where Attr: syn::parse::Parse, { let attrs = if let Some(attrs) = item.mut_item_attrs() { attrs } else { return Ok(None) }; if let Some(index) = attrs.iter().position(|attr| { - attr.path.segments.first().map_or(false, |segment| segment.ident == "pallet") + attr.path().segments.first().map_or(false, |segment| segment.ident == "pallet") }) { let pallet_attr = attrs.remove(index); Ok(Some(syn::parse2(pallet_attr.into_token_stream())?)) @@ -64,7 +67,7 @@ where } /// Take all the pallet attributes (e.g. attribute like `#[pallet..]`) and decode them to `Attr` -pub fn take_item_pallet_attrs(item: &mut impl MutItemAttrs) -> syn::Result> +pub(crate) fn take_item_pallet_attrs(item: &mut impl MutItemAttrs) -> syn::Result> where Attr: syn::parse::Parse, { @@ -82,7 +85,7 @@ pub fn get_item_cfg_attrs(attrs: &[syn::Attribute]) -> Vec { attrs .iter() .filter_map(|attr| { - if attr.path.segments.first().map_or(false, |segment| segment.ident == "cfg") { + if attr.path().segments.first().map_or(false, |segment| segment.ident == "cfg") { Some(attr.clone()) } else { None @@ -101,7 +104,6 @@ impl MutItemAttrs for syn::Item { Self::ForeignMod(item) => Some(item.attrs.as_mut()), Self::Impl(item) => Some(item.attrs.as_mut()), Self::Macro(item) => Some(item.attrs.as_mut()), - Self::Macro2(item) => Some(item.attrs.as_mut()), Self::Mod(item) => Some(item.attrs.as_mut()), Self::Static(item) => Some(item.attrs.as_mut()), Self::Struct(item) => Some(item.attrs.as_mut()), @@ -119,7 +121,7 @@ impl MutItemAttrs for syn::TraitItem { fn mut_item_attrs(&mut self) -> Option<&mut Vec> { match self { Self::Const(item) => Some(item.attrs.as_mut()), - Self::Method(item) => Some(item.attrs.as_mut()), + Self::Fn(item) => Some(item.attrs.as_mut()), Self::Type(item) => Some(item.attrs.as_mut()), Self::Macro(item) => Some(item.attrs.as_mut()), _ => None, @@ -139,7 +141,7 @@ impl MutItemAttrs for syn::ItemMod { } } -impl MutItemAttrs for syn::ImplItemMethod { +impl MutItemAttrs for syn::ImplItemFn { fn mut_item_attrs(&mut self) -> Option<&mut Vec> { Some(&mut self.attrs) } @@ -487,26 +489,32 @@ pub fn check_type_def_gen( /// Check the syntax: /// * either `GenesisBuild` /// * or `GenesisBuild` +/// * or `BuildGenesisConfig` /// -/// return the instance if found. -pub fn check_genesis_builder_usage(type_: &syn::Path) -> syn::Result { +/// return the instance if found for `GenesisBuild` +/// return None for BuildGenesisConfig +pub fn check_genesis_builder_usage(type_: &syn::Path) -> syn::Result> { let expected = "expected `GenesisBuild` or `GenesisBuild`"; - pub struct Checker(InstanceUsage); + pub struct Checker(Option); impl syn::parse::Parse for Checker { fn parse(input: syn::parse::ParseStream) -> syn::Result { let mut instance_usage = InstanceUsage { span: input.span(), has_instance: false }; - input.parse::()?; - input.parse::()?; - input.parse::()?; - if input.peek(syn::Token![,]) { - instance_usage.has_instance = true; - input.parse::()?; - input.parse::()?; + if input.peek(keyword::GenesisBuild) { + input.parse::()?; + input.parse::()?; + input.parse::()?; + if input.peek(syn::Token![,]) { + instance_usage.has_instance = true; + input.parse::()?; + input.parse::()?; + } + input.parse::]>()?; + return Ok(Self(Some(instance_usage))) + } else { + input.parse::()?; + return Ok(Self(None)) } - input.parse::]>()?; - - Ok(Self(instance_usage)) } } diff --git a/frame/support/procedural/src/pallet/parse/hooks.rs b/frame/support/procedural/src/pallet/parse/hooks.rs index 2dc8f4da47c5..37d7d22f4b6b 100644 --- a/frame/support/procedural/src/pallet/parse/hooks.rs +++ b/frame/support/procedural/src/pallet/parse/hooks.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -71,7 +71,7 @@ impl HooksDef { } let has_runtime_upgrade = item.items.iter().any(|i| match i { - syn::ImplItem::Method(method) => method.sig.ident == "on_runtime_upgrade", + syn::ImplItem::Fn(method) => method.sig.ident == "on_runtime_upgrade", _ => false, }); diff --git a/frame/support/procedural/src/pallet/parse/inherent.rs b/frame/support/procedural/src/pallet/parse/inherent.rs index a485eed4c40d..d8641691a40e 100644 --- a/frame/support/procedural/src/pallet/parse/inherent.rs +++ b/frame/support/procedural/src/pallet/parse/inherent.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet/parse/mod.rs b/frame/support/procedural/src/pallet/parse/mod.rs index f91159248281..0f5e5f113661 100644 --- a/frame/support/procedural/src/pallet/parse/mod.rs +++ b/frame/support/procedural/src/pallet/parse/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +20,7 @@ //! Parse the module into `Def` struct through `Def::try_from` function. pub mod call; +pub mod composite; pub mod config; pub mod error; pub mod event; @@ -35,6 +36,7 @@ pub mod storage; pub mod type_value; pub mod validate_unsigned; +use composite::{keyword::CompositeKeyword, CompositeDef}; use frame_support_procedural_tools::generate_crate_access_2018; use syn::spanned::Spanned; @@ -56,6 +58,7 @@ pub struct Def { pub genesis_build: Option, pub validate_unsigned: Option, pub extra_constants: Option, + pub composites: Vec, pub type_values: Vec, pub frame_system: syn::Ident, pub frame_support: syn::Ident, @@ -91,13 +94,20 @@ impl Def { let mut extra_constants = None; let mut storages = vec![]; let mut type_values = vec![]; + let mut composites: Vec = vec![]; for (index, item) in items.iter_mut().enumerate() { let pallet_attr: Option = helper::take_first_item_pallet_attr(item)?; match pallet_attr { - Some(PalletAttr::Config(span)) if config.is_none() => - config = Some(config::ConfigDef::try_from(&frame_system, span, index, item)?), + Some(PalletAttr::Config(span, with_default)) if config.is_none() => + config = Some(config::ConfigDef::try_from( + &frame_system, + span, + index, + item, + with_default, + )?), Some(PalletAttr::Pallet(span)) if pallet_struct.is_none() => { let p = pallet_struct::PalletStructDef::try_from(span, index, item)?; pallet_struct = Some(p); @@ -106,8 +116,8 @@ impl Def { let m = hooks::HooksDef::try_from(span, index, item)?; hooks = Some(m); }, - Some(PalletAttr::RuntimeCall(span)) if call.is_none() => - call = Some(call::CallDef::try_from(span, index, item, dev_mode)?), + Some(PalletAttr::RuntimeCall(cw, span)) if call.is_none() => + call = Some(call::CallDef::try_from(span, index, item, dev_mode, cw)?), Some(PalletAttr::Error(span)) if error.is_none() => error = Some(error::ErrorDef::try_from(span, index, item)?), Some(PalletAttr::RuntimeEvent(span)) if event.is_none() => @@ -135,6 +145,32 @@ impl Def { Some(PalletAttr::ExtraConstants(_)) => extra_constants = Some(extra_constants::ExtraConstantsDef::try_from(index, item)?), + Some(PalletAttr::Composite(span)) => { + let composite = + composite::CompositeDef::try_from(span, index, &frame_support, item)?; + if composites.iter().any(|def| { + match (&def.composite_keyword, &composite.composite_keyword) { + ( + CompositeKeyword::FreezeReason(_), + CompositeKeyword::FreezeReason(_), + ) | + (CompositeKeyword::HoldReason(_), CompositeKeyword::HoldReason(_)) | + (CompositeKeyword::LockId(_), CompositeKeyword::LockId(_)) | + ( + CompositeKeyword::SlashReason(_), + CompositeKeyword::SlashReason(_), + ) => true, + _ => false, + } + }) { + let msg = format!( + "Invalid duplicated `{}` definition", + composite.composite_keyword + ); + return Err(syn::Error::new(composite.composite_keyword.span(), &msg)) + } + composites.push(composite); + }, Some(attr) => { let msg = "Invalid duplicated attribute"; return Err(syn::Error::new(attr.span(), msg)) @@ -171,6 +207,7 @@ impl Def { origin, inherent, storages, + composites, type_values, frame_system, frame_support, @@ -231,7 +268,7 @@ impl Def { instances.extend_from_slice(&genesis_config.instances[..]); } if let Some(genesis_build) = &self.genesis_build { - instances.extend_from_slice(&genesis_build.instances[..]); + genesis_build.instances.as_ref().map(|i| instances.extend_from_slice(&i)); } if let Some(extra_constants) = &self.extra_constants { instances.extend_from_slice(&extra_constants.instances[..]); @@ -371,8 +408,10 @@ impl GenericKind { mod keyword { syn::custom_keyword!(origin); syn::custom_keyword!(call); + syn::custom_keyword!(weight); syn::custom_keyword!(event); syn::custom_keyword!(config); + syn::custom_keyword!(with_default); syn::custom_keyword!(hooks); syn::custom_keyword!(inherent); syn::custom_keyword!(error); @@ -385,15 +424,53 @@ mod keyword { syn::custom_keyword!(generate_store); syn::custom_keyword!(Store); syn::custom_keyword!(extra_constants); + syn::custom_keyword!(composite_enum); } /// Parse attributes for item in pallet module /// syntax must be `pallet::` (e.g. `#[pallet::config]`) enum PalletAttr { - Config(proc_macro2::Span), + Config(proc_macro2::Span, bool), Pallet(proc_macro2::Span), Hooks(proc_macro2::Span), - RuntimeCall(proc_macro2::Span), + /// A `#[pallet::call]` with optional attributes to specialize the behaviour. + /// + /// # Attributes + /// + /// Each attribute `attr` can take the form of `#[pallet::call(attr = …)]` or + /// `#[pallet::call(attr(…))]`. The possible attributes are: + /// + /// ## `weight` + /// + /// Can be used to reduce the repetitive weight annotation in the trivial case. It accepts one + /// argument that is expected to be an implementation of the `WeightInfo` or something that + /// behaves syntactically equivalent. This allows to annotate a `WeightInfo` for all the calls. + /// Now each call does not need to specify its own `#[pallet::weight]` but can instead use the + /// one from the `#[pallet::call]` definition. So instead of having to write it on each call: + /// + /// ```ignore + /// #[pallet::call] + /// impl Pallet { + /// #[pallet::weight(T::WeightInfo::create())] + /// pub fn create( + /// ``` + /// you can now omit it on the call itself, if the name of the weigh function matches the call: + /// + /// ```ignore + /// #[pallet::call(weight = ::WeightInfo)] + /// impl Pallet { + /// pub fn create( + /// ``` + /// + /// It is possible to use this syntax together with instantiated pallets by using `Config` + /// instead. + /// + /// ### Dev Mode + /// + /// Normally the `dev_mode` sets all weights of calls without a `#[pallet::weight]` annotation + /// to zero. Now when there is a `weight` attribute on the `#[pallet::call]`, then that is used + /// instead of the zero weight. So to say: it works together with `dev_mode`. + RuntimeCall(Option, proc_macro2::Span), Error(proc_macro2::Span), RuntimeEvent(proc_macro2::Span), RuntimeOrigin(proc_macro2::Span), @@ -404,15 +481,16 @@ enum PalletAttr { ValidateUnsigned(proc_macro2::Span), TypeValue(proc_macro2::Span), ExtraConstants(proc_macro2::Span), + Composite(proc_macro2::Span), } impl PalletAttr { fn span(&self) -> proc_macro2::Span { match self { - Self::Config(span) => *span, + Self::Config(span, _) => *span, Self::Pallet(span) => *span, Self::Hooks(span) => *span, - Self::RuntimeCall(span) => *span, + Self::RuntimeCall(_, span) => *span, Self::Error(span) => *span, Self::RuntimeEvent(span) => *span, Self::RuntimeOrigin(span) => *span, @@ -423,6 +501,7 @@ impl PalletAttr { Self::ValidateUnsigned(span) => *span, Self::TypeValue(span) => *span, Self::ExtraConstants(span) => *span, + Self::Composite(span) => *span, } } } @@ -437,13 +516,25 @@ impl syn::parse::Parse for PalletAttr { let lookahead = content.lookahead1(); if lookahead.peek(keyword::config) { - Ok(PalletAttr::Config(content.parse::()?.span())) + let span = content.parse::()?.span(); + let with_default = content.peek(syn::token::Paren); + if with_default { + let inside_config; + let _paren = syn::parenthesized!(inside_config in content); + inside_config.parse::()?; + } + Ok(PalletAttr::Config(span, with_default)) } else if lookahead.peek(keyword::pallet) { Ok(PalletAttr::Pallet(content.parse::()?.span())) } else if lookahead.peek(keyword::hooks) { Ok(PalletAttr::Hooks(content.parse::()?.span())) } else if lookahead.peek(keyword::call) { - Ok(PalletAttr::RuntimeCall(content.parse::()?.span())) + let span = content.parse::().expect("peeked").span(); + let attr = match content.is_empty() { + true => None, + false => Some(InheritedCallWeightAttr::parse(&content)?), + }; + Ok(PalletAttr::RuntimeCall(attr, span)) } else if lookahead.peek(keyword::error) { Ok(PalletAttr::Error(content.parse::()?.span())) } else if lookahead.peek(keyword::event) { @@ -464,8 +555,40 @@ impl syn::parse::Parse for PalletAttr { Ok(PalletAttr::TypeValue(content.parse::()?.span())) } else if lookahead.peek(keyword::extra_constants) { Ok(PalletAttr::ExtraConstants(content.parse::()?.span())) + } else if lookahead.peek(keyword::composite_enum) { + Ok(PalletAttr::Composite(content.parse::()?.span())) } else { Err(lookahead.error()) } } } + +/// The optional weight annotation on a `#[pallet::call]` like `#[pallet::call(weight($type))]`. +#[derive(Clone)] +pub struct InheritedCallWeightAttr { + pub typename: syn::Type, + pub span: proc_macro2::Span, +} + +impl syn::parse::Parse for InheritedCallWeightAttr { + // Parses `(weight($type))` or `(weight = $type)`. + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let content; + syn::parenthesized!(content in input); + content.parse::()?; + let lookahead = content.lookahead1(); + + let buffer = if lookahead.peek(syn::token::Paren) { + let inner; + syn::parenthesized!(inner in content); + inner + } else if lookahead.peek(syn::Token![=]) { + content.parse::().expect("peeked"); + content + } else { + return Err(lookahead.error()) + }; + + Ok(Self { typename: buffer.parse()?, span: input.span() }) + } +} diff --git a/frame/support/procedural/src/pallet/parse/origin.rs b/frame/support/procedural/src/pallet/parse/origin.rs index 89929e3e8dbf..76e2a8841196 100644 --- a/frame/support/procedural/src/pallet/parse/origin.rs +++ b/frame/support/procedural/src/pallet/parse/origin.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet/parse/pallet_struct.rs b/frame/support/procedural/src/pallet/parse/pallet_struct.rs index a96c310b6f1c..f4af86aa3e99 100644 --- a/frame/support/procedural/src/pallet/parse/pallet_struct.rs +++ b/frame/support/procedural/src/pallet/parse/pallet_struct.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,7 +38,7 @@ pub struct PalletStructDef { /// The keyword Pallet used (contains span). pub pallet: keyword::Pallet, /// Whether the trait `Store` must be generated. - pub store: Option<(syn::Visibility, keyword::Store)>, + pub store: Option<(syn::Visibility, keyword::Store, proc_macro2::Span)>, /// The span of the pallet::pallet attribute. pub attr_span: proc_macro2::Span, /// Whether to specify the storages max encoded len when implementing `StorageInfoTrait`. @@ -61,8 +61,8 @@ pub enum PalletStructAttr { impl PalletStructAttr { fn span(&self) -> proc_macro2::Span { match self { - Self::GenerateStore { span, .. } => *span, - Self::WithoutStorageInfoTrait(span) => *span, + Self::GenerateStore { span, .. } | + Self::WithoutStorageInfoTrait(span) | Self::StorageVersion { span, .. } => *span, } } @@ -78,13 +78,13 @@ impl syn::parse::Parse for PalletStructAttr { let lookahead = content.lookahead1(); if lookahead.peek(keyword::generate_store) { - let span = content.parse::()?.span(); - + content.parse::()?; let generate_content; syn::parenthesized!(generate_content in content); let vis = generate_content.parse::()?; generate_content.parse::()?; let keyword = generate_content.parse::()?; + let span = content.span(); Ok(Self::GenerateStore { vis, keyword, span }) } else if lookahead.peek(keyword::without_storage_info) { let span = content.parse::()?.span(); @@ -123,8 +123,8 @@ impl PalletStructDef { let struct_attrs: Vec = helper::take_item_pallet_attrs(&mut item.attrs)?; for attr in struct_attrs { match attr { - PalletStructAttr::GenerateStore { vis, keyword, .. } if store.is_none() => { - store = Some((vis, keyword)); + PalletStructAttr::GenerateStore { vis, keyword, span } if store.is_none() => { + store = Some((vis, keyword, span)); }, PalletStructAttr::WithoutStorageInfoTrait(span) if without_storage_info.is_none() => diff --git a/frame/support/procedural/src/pallet/parse/storage.rs b/frame/support/procedural/src/pallet/parse/storage.rs index 8b551ab31d6c..12e06b214b6b 100644 --- a/frame/support/procedural/src/pallet/parse/storage.rs +++ b/frame/support/procedural/src/pallet/parse/storage.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -159,7 +159,7 @@ pub struct StorageDef { /// The keys and value metadata of the storage. pub metadata: Metadata, /// The doc associated to the storage. - pub docs: Vec, + pub docs: Vec, /// A set of usage of instance, must be check for consistency with config. pub instances: Vec, /// Optional getter to generate. If some then query_kind is ensured to be some as well. @@ -185,6 +185,8 @@ pub struct StorageDef { pub unbounded: bool, /// Whether or not reads to this storage key will be ignored by benchmarking pub whitelisted: bool, + /// Whether or not a default hasher is allowed to replace `_` + pub use_default_hasher: bool, } /// The parsed generic from the @@ -268,7 +270,7 @@ enum StorageKind { /// Check the generics in the `map` contains the generics in `gen` may contains generics in /// `optional_gen`, and doesn't contains any other. fn check_generics( - map: &HashMap, + map: &HashMap, mandatory_generics: &[&str], optional_generics: &[&str], storage_type_name: &str, @@ -325,13 +327,14 @@ fn check_generics( } } -/// Returns `(named generics, metadata, query kind)` +/// Returns `(named generics, metadata, query kind, use_default_hasher)` fn process_named_generics( storage: &StorageKind, args_span: proc_macro2::Span, - args: &[syn::Binding], -) -> syn::Result<(Option, Metadata, Option)> { - let mut parsed = HashMap::::new(); + args: &[syn::AssocType], + dev_mode: bool, +) -> syn::Result<(Option, Metadata, Option, bool)> { + let mut parsed = HashMap::::new(); // Ensure no duplicate. for arg in args { @@ -344,6 +347,14 @@ fn process_named_generics( parsed.insert(arg.ident.to_string(), arg.clone()); } + let mut map_mandatory_generics = vec!["Key", "Value"]; + let mut map_optional_generics = vec!["QueryKind", "OnEmpty", "MaxValues"]; + if dev_mode { + map_optional_generics.push("Hasher"); + } else { + map_mandatory_generics.push("Hasher"); + } + let generics = match storage { StorageKind::Value => { check_generics( @@ -366,8 +377,8 @@ fn process_named_generics( StorageKind::Map => { check_generics( &parsed, - &["Hasher", "Key", "Value"], - &["QueryKind", "OnEmpty", "MaxValues"], + &map_mandatory_generics, + &map_optional_generics, "StorageMap", args_span, )?; @@ -376,7 +387,7 @@ fn process_named_generics( hasher: parsed .remove("Hasher") .map(|binding| binding.ty) - .expect("checked above as mandatory generic"), + .unwrap_or(syn::parse_quote!(Blake2_128Concat)), key: parsed .remove("Key") .map(|binding| binding.ty) @@ -393,8 +404,8 @@ fn process_named_generics( StorageKind::CountedMap => { check_generics( &parsed, - &["Hasher", "Key", "Value"], - &["QueryKind", "OnEmpty", "MaxValues"], + &map_mandatory_generics, + &map_optional_generics, "CountedStorageMap", args_span, )?; @@ -403,7 +414,7 @@ fn process_named_generics( hasher: parsed .remove("Hasher") .map(|binding| binding.ty) - .expect("checked above as mandatory generic"), + .unwrap_or(syn::Type::Verbatim(quote::quote! { Blake2_128Concat })), key: parsed .remove("Key") .map(|binding| binding.ty) @@ -418,10 +429,17 @@ fn process_named_generics( } }, StorageKind::DoubleMap => { + let mut double_map_mandatory_generics = vec!["Key1", "Key2", "Value"]; + if dev_mode { + map_optional_generics.extend(["Hasher1", "Hasher2"]); + } else { + double_map_mandatory_generics.extend(["Hasher1", "Hasher2"]); + } + check_generics( &parsed, - &["Hasher1", "Key1", "Hasher2", "Key2", "Value"], - &["QueryKind", "OnEmpty", "MaxValues"], + &double_map_mandatory_generics, + &map_optional_generics, "StorageDoubleMap", args_span, )?; @@ -430,7 +448,7 @@ fn process_named_generics( hasher1: parsed .remove("Hasher1") .map(|binding| binding.ty) - .expect("checked above as mandatory generic"), + .unwrap_or(syn::parse_quote!(Blake2_128Concat)), key1: parsed .remove("Key1") .map(|binding| binding.ty) @@ -438,7 +456,7 @@ fn process_named_generics( hasher2: parsed .remove("Hasher2") .map(|binding| binding.ty) - .expect("checked above as mandatory generic"), + .unwrap_or(syn::parse_quote!(Blake2_128Concat)), key2: parsed .remove("Key2") .map(|binding| binding.ty) @@ -480,15 +498,16 @@ fn process_named_generics( let metadata = generics.metadata()?; let query_kind = generics.query_kind(); - Ok((Some(generics), metadata, query_kind)) + Ok((Some(generics), metadata, query_kind, false)) } -/// Returns `(named generics, metadata, query kind)` +/// Returns `(named generics, metadata, query kind, use_default_hasher)` fn process_unnamed_generics( storage: &StorageKind, args_span: proc_macro2::Span, args: &[syn::Type], -) -> syn::Result<(Option, Metadata, Option)> { + dev_mode: bool, +) -> syn::Result<(Option, Metadata, Option, bool)> { let retrieve_arg = |arg_pos| { args.get(arg_pos).cloned().ok_or_else(|| { let msg = format!( @@ -510,18 +529,34 @@ fn process_unnamed_generics( err })?; + let use_default_hasher = |arg_pos| { + let arg = retrieve_arg(arg_pos)?; + if syn::parse2::(arg.to_token_stream()).is_ok() { + if dev_mode { + Ok(true) + } else { + let msg = "`_` can only be used in dev_mode. Please specify an appropriate hasher."; + Err(syn::Error::new(arg.span(), msg)) + } + } else { + Ok(false) + } + }; + let res = match storage { StorageKind::Value => - (None, Metadata::Value { value: retrieve_arg(1)? }, retrieve_arg(2).ok()), + (None, Metadata::Value { value: retrieve_arg(1)? }, retrieve_arg(2).ok(), false), StorageKind::Map => ( None, Metadata::Map { key: retrieve_arg(2)?, value: retrieve_arg(3)? }, retrieve_arg(4).ok(), + use_default_hasher(1)?, ), StorageKind::CountedMap => ( None, Metadata::CountedMap { key: retrieve_arg(2)?, value: retrieve_arg(3)? }, retrieve_arg(4).ok(), + use_default_hasher(1)?, ), StorageKind::DoubleMap => ( None, @@ -531,21 +566,28 @@ fn process_unnamed_generics( value: retrieve_arg(5)?, }, retrieve_arg(6).ok(), + use_default_hasher(1)? && use_default_hasher(3)?, ), StorageKind::NMap => { let keygen = retrieve_arg(1)?; let keys = collect_keys(&keygen)?; - (None, Metadata::NMap { keys, keygen, value: retrieve_arg(2)? }, retrieve_arg(3).ok()) + ( + None, + Metadata::NMap { keys, keygen, value: retrieve_arg(2)? }, + retrieve_arg(3).ok(), + false, + ) }, }; Ok(res) } -/// Returns `(named generics, metadata, query kind)` +/// Returns `(named generics, metadata, query kind, use_default_hasher)` fn process_generics( segment: &syn::PathSegment, -) -> syn::Result<(Option, Metadata, Option)> { + dev_mode: bool, +) -> syn::Result<(Option, Metadata, Option, bool)> { let storage_kind = match &*segment.ident.to_string() { "StorageValue" => StorageKind::Value, "StorageMap" => StorageKind::Map, @@ -583,17 +625,17 @@ fn process_generics( _ => unreachable!("It is asserted above that all generics are types"), }) .collect::>(); - process_unnamed_generics(&storage_kind, args_span, &args) - } else if args.args.iter().all(|gen| matches!(gen, syn::GenericArgument::Binding(_))) { + process_unnamed_generics(&storage_kind, args_span, &args, dev_mode) + } else if args.args.iter().all(|gen| matches!(gen, syn::GenericArgument::AssocType(_))) { let args = args .args .iter() .map(|gen| match gen { - syn::GenericArgument::Binding(gen) => gen.clone(), + syn::GenericArgument::AssocType(gen) => gen.clone(), _ => unreachable!("It is asserted above that all generics are bindings"), }) .collect::>(); - process_named_generics(&storage_kind, args_span, &args) + process_named_generics(&storage_kind, args_span, &args, dev_mode) } else { let msg = "Invalid pallet::storage, invalid generic declaration for storage. Expect only \ type generics or binding generics, e.g. `` or \ @@ -711,7 +753,8 @@ impl StorageDef { return Err(syn::Error::new(item.ty.span(), msg)) } - let (named_generics, metadata, query_kind) = process_generics(&typ.path.segments[0])?; + let (named_generics, metadata, query_kind, use_default_hasher) = + process_generics(&typ.path.segments[0], dev_mode)?; let query_kind = query_kind .map(|query_kind| { @@ -832,6 +875,7 @@ impl StorageDef { named_generics, unbounded, whitelisted, + use_default_hasher, }) } } diff --git a/frame/support/procedural/src/pallet/parse/type_value.rs b/frame/support/procedural/src/pallet/parse/type_value.rs index a3d004cd8a53..4d9db30b3a78 100644 --- a/frame/support/procedural/src/pallet/parse/type_value.rs +++ b/frame/support/procedural/src/pallet/parse/type_value.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,7 +39,7 @@ pub struct TypeValueDef { /// The span of the pallet::type_value attribute. pub attr_span: proc_macro2::Span, /// Docs on the item. - pub docs: Vec, + pub docs: Vec, } impl TypeValueDef { @@ -57,9 +57,9 @@ impl TypeValueDef { let mut docs = vec![]; for attr in &item.attrs { - if let Ok(syn::Meta::NameValue(meta)) = attr.parse_meta() { + if let syn::Meta::NameValue(meta) = &attr.meta { if meta.path.get_ident().map_or(false, |ident| ident == "doc") { - docs.push(meta.lit); + docs.push(meta.value.clone()); continue } } diff --git a/frame/support/procedural/src/pallet/parse/validate_unsigned.rs b/frame/support/procedural/src/pallet/parse/validate_unsigned.rs index 18d5a2dc4443..2bf0a1b6c188 100644 --- a/frame/support/procedural/src/pallet/parse/validate_unsigned.rs +++ b/frame/support/procedural/src/pallet/parse/validate_unsigned.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/pallet_error.rs b/frame/support/procedural/src/pallet_error.rs index 216168131e43..246a5bd4a219 100644 --- a/frame/support/procedural/src/pallet_error.rs +++ b/frame/support/procedural/src/pallet_error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ use frame_support_procedural_tools::generate_crate_access_2018; use quote::ToTokens; -use std::str::FromStr; // Derive `PalletError` pub fn derive_pallet_error(input: proc_macro::TokenStream) -> proc_macro::TokenStream { @@ -117,38 +116,24 @@ fn generate_field_types( let attrs = &field.attrs; for attr in attrs { - if attr.path.is_ident("codec") { - match attr.parse_meta()? { - syn::Meta::List(ref meta_list) if meta_list.nested.len() == 1 => { - match meta_list - .nested - .first() - .expect("Just checked that there is one item; qed") - { - syn::NestedMeta::Meta(syn::Meta::Path(path)) - if path.get_ident().map_or(false, |i| i == "skip") => - return Ok(None), - - syn::NestedMeta::Meta(syn::Meta::Path(path)) - if path.get_ident().map_or(false, |i| i == "compact") => - { - let field_ty = &field.ty; - return Ok(Some(quote::quote!(#scrate::codec::Compact<#field_ty>))) - }, - - syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue { - path, - lit: syn::Lit::Str(lit_str), - .. - })) if path.get_ident().map_or(false, |i| i == "encoded_as") => { - let ty = proc_macro2::TokenStream::from_str(&lit_str.value())?; - return Ok(Some(ty)) - }, - - _ => (), - } - }, - _ => (), + if attr.path().is_ident("codec") { + let mut res = None; + + attr.parse_nested_meta(|meta| { + if meta.path.is_ident("skip") { + res = Some(None); + } else if meta.path.is_ident("compact") { + let field_ty = &field.ty; + res = Some(Some(quote::quote!(#scrate::codec::Compact<#field_ty>))); + } else if meta.path.is_ident("compact") { + res = Some(Some(meta.value()?.parse()?)); + } + + Ok(()) + })?; + + if let Some(v) = res { + return Ok(v) } } } @@ -163,22 +148,18 @@ fn generate_variant_field_types( let attrs = &variant.attrs; for attr in attrs { - if attr.path.is_ident("codec") { - match attr.parse_meta()? { - syn::Meta::List(ref meta_list) if meta_list.nested.len() == 1 => { - match meta_list - .nested - .first() - .expect("Just checked that there is one item; qed") - { - syn::NestedMeta::Meta(syn::Meta::Path(path)) - if path.get_ident().map_or(false, |i| i == "skip") => - return Ok(None), - - _ => (), - } - }, - _ => (), + if attr.path().is_ident("codec") { + let mut skip = false; + + // We ignore the error intentionally as this isn't `codec(skip)` when + // `parse_nested_meta` fails. + let _ = attr.parse_nested_meta(|meta| { + skip = meta.path.is_ident("skip"); + Ok(()) + }); + + if skip { + return Ok(None) } } } diff --git a/frame/support/procedural/src/partial_eq_no_bound.rs b/frame/support/procedural/src/partial_eq_no_bound.rs index 31930c0c3dae..27f5e98810ec 100644 --- a/frame/support/procedural/src/partial_eq_no_bound.rs +++ b/frame/support/procedural/src/partial_eq_no_bound.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/storage/genesis_config/builder_def.rs b/frame/support/procedural/src/storage/genesis_config/builder_def.rs index 29e2560bc16e..5b8e4eb7603f 100644 --- a/frame/support/procedural/src/storage/genesis_config/builder_def.rs +++ b/frame/support/procedural/src/storage/genesis_config/builder_def.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/storage/genesis_config/genesis_config_def.rs b/frame/support/procedural/src/storage/genesis_config/genesis_config_def.rs index d24e50096f25..31e9996ee51f 100644 --- a/frame/support/procedural/src/storage/genesis_config/genesis_config_def.rs +++ b/frame/support/procedural/src/storage/genesis_config/genesis_config_def.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -133,14 +133,13 @@ impl GenesisConfigDef { .attrs .iter() .map(|attr| { - let meta = attr.parse_meta()?; - if meta.path().is_ident("cfg") { + if attr.meta.path().is_ident("cfg") { return Err(syn::Error::new( - meta.span(), + attr.meta.span(), "extra genesis config items do not support `cfg` attribute", )) } - Ok(meta) + Ok(attr.meta.clone()) }) .collect::>()?; diff --git a/frame/support/procedural/src/storage/genesis_config/mod.rs b/frame/support/procedural/src/storage/genesis_config/mod.rs index d4348ee19171..57c938368bc9 100644 --- a/frame/support/procedural/src/storage/genesis_config/mod.rs +++ b/frame/support/procedural/src/storage/genesis_config/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/storage/getters.rs b/frame/support/procedural/src/storage/getters.rs index 9fe3734e6889..428b2ec45340 100644 --- a/frame/support/procedural/src/storage/getters.rs +++ b/frame/support/procedural/src/storage/getters.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/storage/instance_trait.rs b/frame/support/procedural/src/storage/instance_trait.rs index e9df02e3dc13..8b5aa500550e 100644 --- a/frame/support/procedural/src/storage/instance_trait.rs +++ b/frame/support/procedural/src/storage/instance_trait.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/storage/metadata.rs b/frame/support/procedural/src/storage/metadata.rs index d1879d1509f8..37cd80619298 100644 --- a/frame/support/procedural/src/storage/metadata.rs +++ b/frame/support/procedural/src/storage/metadata.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,7 +27,7 @@ fn storage_line_metadata_type(scrate: &TokenStream, line: &StorageLineDefExt) -> match &line.storage_type { StorageLineTypeDef::Simple(_) => { quote! { - #scrate::metadata::StorageEntryType::Plain( + #scrate::metadata_ir::StorageEntryTypeIR::Plain( #scrate::scale_info::meta_type::<#value_type>() ) } @@ -36,8 +36,8 @@ fn storage_line_metadata_type(scrate: &TokenStream, line: &StorageLineDefExt) -> let hasher = map.hasher.into_metadata(); let key = &map.key; quote! { - #scrate::metadata::StorageEntryType::Map { - hashers: #scrate::sp_std::vec! [ #scrate::metadata::#hasher ], + #scrate::metadata_ir::StorageEntryTypeIR::Map { + hashers: #scrate::sp_std::vec! [ #scrate::metadata_ir::#hasher ], key: #scrate::scale_info::meta_type::<#key>(), value: #scrate::scale_info::meta_type::<#value_type>(), } @@ -49,10 +49,10 @@ fn storage_line_metadata_type(scrate: &TokenStream, line: &StorageLineDefExt) -> let key1 = &map.key1; let key2 = &map.key2; quote! { - #scrate::metadata::StorageEntryType::Map { + #scrate::metadata_ir::StorageEntryTypeIR::Map { hashers: #scrate::sp_std::vec! [ - #scrate::metadata::#hasher1, - #scrate::metadata::#hasher2, + #scrate::metadata_ir::#hasher1, + #scrate::metadata_ir::#hasher2, ], key: #scrate::scale_info::meta_type::<(#key1, #key2)>(), value: #scrate::scale_info::meta_type::<#value_type>(), @@ -67,9 +67,9 @@ fn storage_line_metadata_type(scrate: &TokenStream, line: &StorageLineDefExt) -> .map(|hasher| hasher.to_storage_hasher_struct()) .collect::>(); quote! { - #scrate::metadata::StorageEntryType::Map { + #scrate::metadata_ir::StorageEntryTypeIR::Map { hashers: #scrate::sp_std::vec! [ - #( #scrate::metadata::StorageHasher::#hashers, )* + #( #scrate::metadata_ir::StorageHasherIR::#hashers, )* ], key: #scrate::scale_info::meta_type::<#key_tuple>(), value: #scrate::scale_info::meta_type::<#value_type>(), @@ -114,8 +114,8 @@ fn default_byte_getter( #[cfg(feature = "std")] #[allow(non_upper_case_globals)] - static #cache_name: #scrate::once_cell::sync::OnceCell<#scrate::sp_std::vec::Vec> = - #scrate::once_cell::sync::OnceCell::new(); + static #cache_name: ::core::sync::OnceLock<#scrate::sp_std::vec::Vec> = + ::core::sync::OnceLock::new(); #[cfg(feature = "std")] impl<#runtime_generic: #runtime_trait, #optional_instance_bound> @@ -159,9 +159,9 @@ pub fn impl_metadata(def: &DeclStorageDefExt) -> TokenStream { let str_name = line.name.to_string(); let modifier = if line.is_option { - quote!(#scrate::metadata::StorageEntryModifier::Optional) + quote!(#scrate::metadata_ir::StorageEntryModifierIR::Optional) } else { - quote!(#scrate::metadata::StorageEntryModifier::Default) + quote!(#scrate::metadata_ir::StorageEntryModifierIR::Default) }; let ty = storage_line_metadata_type(scrate, line); @@ -172,7 +172,7 @@ pub fn impl_metadata(def: &DeclStorageDefExt) -> TokenStream { let docs = get_doc_literals(&line.attrs); let entry = quote! { - #scrate::metadata::StorageEntryMetadata { + #scrate::metadata_ir::StorageEntryMetadataIR { name: #str_name, modifier: #modifier, ty: #ty, @@ -194,7 +194,7 @@ pub fn impl_metadata(def: &DeclStorageDefExt) -> TokenStream { }; let store_metadata = quote!( - #scrate::metadata::PalletStorageMetadata { + #scrate::metadata_ir::PalletStorageMetadataIR { prefix: #prefix, entries: #scrate::sp_std::vec![ #entries ], } @@ -209,7 +209,7 @@ pub fn impl_metadata(def: &DeclStorageDefExt) -> TokenStream { impl #module_impl #module_struct #where_clause { #[doc(hidden)] - pub fn storage_metadata() -> #scrate::metadata::PalletStorageMetadata { + pub fn storage_metadata() -> #scrate::metadata_ir::PalletStorageMetadataIR { #store_metadata } } diff --git a/frame/support/procedural/src/storage/mod.rs b/frame/support/procedural/src/storage/mod.rs index e8e2d7529cb3..c04862256a7b 100644 --- a/frame/support/procedural/src/storage/mod.rs +++ b/frame/support/procedural/src/storage/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -332,8 +332,8 @@ impl StorageLineDefExt { let doc_attrs = storage_def .attrs .iter() - .filter_map(|a| a.parse_meta().ok()) - .filter(|m| m.path().is_ident("doc")) + .filter(|a| a.meta.path().is_ident("doc")) + .map(|a| a.meta.clone()) .collect(); Self { @@ -454,13 +454,13 @@ impl HasherKind { fn into_metadata(&self) -> proc_macro2::TokenStream { match self { - HasherKind::Blake2_256 => quote!(StorageHasher::Blake2_256), - HasherKind::Blake2_128 => quote!(StorageHasher::Blake2_128), - HasherKind::Blake2_128Concat => quote!(StorageHasher::Blake2_128Concat), - HasherKind::Twox256 => quote!(StorageHasher::Twox256), - HasherKind::Twox128 => quote!(StorageHasher::Twox128), - HasherKind::Twox64Concat => quote!(StorageHasher::Twox64Concat), - HasherKind::Identity => quote!(StorageHasher::Identity), + HasherKind::Blake2_256 => quote!(StorageHasherIR::Blake2_256), + HasherKind::Blake2_128 => quote!(StorageHasherIR::Blake2_128), + HasherKind::Blake2_128Concat => quote!(StorageHasherIR::Blake2_128Concat), + HasherKind::Twox256 => quote!(StorageHasherIR::Twox256), + HasherKind::Twox128 => quote!(StorageHasherIR::Twox128), + HasherKind::Twox64Concat => quote!(StorageHasherIR::Twox64Concat), + HasherKind::Identity => quote!(StorageHasherIR::Identity), } } } diff --git a/frame/support/procedural/src/storage/parse.rs b/frame/support/procedural/src/storage/parse.rs index 54026b7d78b1..f9f62e0ff312 100644 --- a/frame/support/procedural/src/storage/parse.rs +++ b/frame/support/procedural/src/storage/parse.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/storage/print_pallet_upgrade.rs b/frame/support/procedural/src/storage/print_pallet_upgrade.rs index 03f09a7edb48..4e330fd4b85f 100644 --- a/frame/support/procedural/src/storage/print_pallet_upgrade.rs +++ b/frame/support/procedural/src/storage/print_pallet_upgrade.rs @@ -273,14 +273,12 @@ pub fn maybe_print_pallet_upgrade(def: &super::DeclStorageDefExt) { doc = line.doc_attrs.iter().fold(String::new(), |mut res, attr| { if let syn::Meta::NameValue(name_value) = attr { if name_value.path.is_ident("doc") { - if let syn::Lit::Str(string) = &name_value.lit { - res = format!( - "{} - ///{}", - res, - string.value(), - ); - } + res = format!( + "{} + ///{}", + res, + name_value.value.to_token_stream(), + ); } } res diff --git a/frame/support/procedural/src/storage/storage_info.rs b/frame/support/procedural/src/storage/storage_info.rs index 77515fa739b2..3e851b04231a 100644 --- a/frame/support/procedural/src/storage/storage_info.rs +++ b/frame/support/procedural/src/storage/storage_info.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/storage/storage_struct.rs b/frame/support/procedural/src/storage/storage_struct.rs index 649a41bab5ec..373e72212223 100644 --- a/frame/support/procedural/src/storage/storage_struct.rs +++ b/frame/support/procedural/src/storage/storage_struct.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/storage/store_trait.rs b/frame/support/procedural/src/storage/store_trait.rs index daf933b3b770..5dca502518a3 100644 --- a/frame/support/procedural/src/storage/store_trait.rs +++ b/frame/support/procedural/src/storage/store_trait.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/storage_alias.rs b/frame/support/procedural/src/storage_alias.rs index e0df0123595b..b44a7ee997fe 100644 --- a/frame/support/procedural/src/storage_alias.rs +++ b/frame/support/procedural/src/storage_alias.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,6 +17,7 @@ //! Implementation of the `storage_alias` attribute macro. +use crate::counter_prefix; use frame_support_procedural_tools::generate_crate_access_2018; use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; @@ -136,6 +137,7 @@ impl ToTokens for SimpleGenerics { mod storage_types { syn::custom_keyword!(StorageValue); syn::custom_keyword!(StorageMap); + syn::custom_keyword!(CountedStorageMap); syn::custom_keyword!(StorageDoubleMap); syn::custom_keyword!(StorageNMap); } @@ -168,6 +170,21 @@ enum StorageType { _trailing_comma: Option, _gt_token: Token![>], }, + CountedMap { + _kw: storage_types::CountedStorageMap, + _lt_token: Token![<], + prefix: SimplePath, + prefix_generics: Option, + _hasher_comma: Token![,], + hasher_ty: Type, + _key_comma: Token![,], + key_ty: Type, + _value_comma: Token![,], + value_ty: Type, + query_type: Option<(Token![,], Type)>, + _trailing_comma: Option, + _gt_token: Token![>], + }, DoubleMap { _kw: storage_types::StorageDoubleMap, _lt_token: Token![<], @@ -235,12 +252,22 @@ impl StorageType { >; } }, + Self::CountedMap { + value_ty, query_type, hasher_ty, key_ty, prefix_generics, .. + } | Self::Map { value_ty, query_type, hasher_ty, key_ty, prefix_generics, .. } => { let query_type = query_type.as_ref().map(|(c, t)| quote!(#c #t)); + let map_type = Ident::new( + match self { + Self::Map { .. } => "StorageMap", + _ => "CountedStorageMap", + }, + Span::call_site(), + ); quote! { #( #attributes )* - #visibility type #storage_name #storage_generics = #crate_::storage::types::StorageMap< + #visibility type #storage_name #storage_generics = #crate_::storage::types::#map_type< #storage_instance #prefix_generics, #hasher_ty, #key_ty, @@ -296,6 +323,7 @@ impl StorageType { match self { Self::Value { prefix, .. } | Self::Map { prefix, .. } | + Self::CountedMap { prefix, .. } | Self::NMap { prefix, .. } | Self::DoubleMap { prefix, .. } => prefix, } @@ -306,6 +334,7 @@ impl StorageType { match self { Self::Value { prefix_generics, .. } | Self::Map { prefix_generics, .. } | + Self::CountedMap { prefix_generics, .. } | Self::NMap { prefix_generics, .. } | Self::DoubleMap { prefix_generics, .. } => prefix_generics.as_ref(), } @@ -363,6 +392,22 @@ impl Parse for StorageType { _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, _gt_token: input.parse()?, }) + } else if lookahead.peek(storage_types::CountedStorageMap) { + Ok(Self::CountedMap { + _kw: input.parse()?, + _lt_token: input.parse()?, + prefix: input.parse()?, + prefix_generics: parse_pallet_generics(input)?, + _hasher_comma: input.parse()?, + hasher_ty: input.parse()?, + _key_comma: input.parse()?, + key_ty: input.parse()?, + _value_comma: input.parse()?, + value_ty: input.parse()?, + query_type: parse_query_type(input)?, + _trailing_comma: input.peek(Token![,]).then(|| input.parse()).transpose()?, + _gt_token: input.parse()?, + }) } else if lookahead.peek(storage_types::StorageDoubleMap) { Ok(Self::DoubleMap { _kw: input.parse()?, @@ -476,6 +521,7 @@ pub fn storage_alias(input: TokenStream) -> Result { input.storage_type.prefix(), input.storage_type.prefix_generics(), &input.visibility, + matches!(input.storage_type, StorageType::CountedMap { .. }), )?; let definition = input.storage_type.generate_type_declaration( @@ -511,6 +557,7 @@ fn generate_storage_instance( prefix: &SimplePath, prefix_generics: Option<&TypeGenerics>, visibility: &Visibility, + is_counted_map: bool, ) -> Result { if let Some(ident) = prefix.get_ident().filter(|i| *i == "_") { return Err(Error::new(ident.span(), "`_` is not allowed as prefix by `storage_alias`.")) @@ -546,11 +593,40 @@ fn generate_storage_instance( let where_clause = storage_where_clause.map(|w| quote!(#w)).unwrap_or_default(); - let name = Ident::new(&format!("{}_Storage_Instance", storage_name), Span::call_site()); + let name_str = format!("{}_Storage_Instance", storage_name); + let name = Ident::new(&name_str, Span::call_site()); let storage_name_str = storage_name.to_string(); + let counter_code = is_counted_map.then(|| { + let counter_name = Ident::new(&counter_prefix(&name_str), Span::call_site()); + let counter_storage_name_str = counter_prefix(&storage_name_str); + + quote! { + #visibility struct #counter_name< #impl_generics >( + #crate_::sp_std::marker::PhantomData<(#type_generics)> + ) #where_clause; + + impl<#impl_generics> #crate_::traits::StorageInstance + for #counter_name< #type_generics > #where_clause + { + fn pallet_prefix() -> &'static str { + #pallet_prefix + } + + const STORAGE_PREFIX: &'static str = #counter_storage_name_str; + } + + impl<#impl_generics> #crate_::storage::types::CountedStorageMapInstance + for #name< #type_generics > #where_clause + { + type CounterPrefix = #counter_name < #type_generics >; + } + } + }); + // Implement `StorageInstance` trait. let code = quote! { + #[allow(non_camel_case_types)] #visibility struct #name< #impl_generics >( #crate_::sp_std::marker::PhantomData<(#type_generics)> ) #where_clause; @@ -564,6 +640,8 @@ fn generate_storage_instance( const STORAGE_PREFIX: &'static str = #storage_name_str; } + + #counter_code }; Ok(StorageInstance { name, code }) diff --git a/frame/support/procedural/src/transactional.rs b/frame/support/procedural/src/transactional.rs index eb77a320a509..23117ffa39c4 100644 --- a/frame/support/procedural/src/transactional.rs +++ b/frame/support/procedural/src/transactional.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/src/tt_macro.rs b/frame/support/procedural/src/tt_macro.rs index 0a270a7173cf..69b5eb3d5521 100644 --- a/frame/support/procedural/src/tt_macro.rs +++ b/frame/support/procedural/src/tt_macro.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/tools/Cargo.toml b/frame/support/procedural/tools/Cargo.toml index 755483f7db9a..7caff534fc1f 100644 --- a/frame/support/procedural/tools/Cargo.toml +++ b/frame/support/procedural/tools/Cargo.toml @@ -13,7 +13,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] proc-macro-crate = "1.1.3" -proc-macro2 = "1.0.37" -quote = "1.0.10" -syn = { version = "1.0.98", features = ["full", "visit", "extra-traits"] } +proc-macro2 = "1.0.56" +quote = "1.0.28" +syn = { version = "2.0.16", features = ["full", "visit", "extra-traits"] } frame-support-procedural-tools-derive = { version = "3.0.0", path = "./derive" } diff --git a/frame/support/procedural/tools/derive/Cargo.toml b/frame/support/procedural/tools/derive/Cargo.toml index a688ee13a7d1..193df53f1291 100644 --- a/frame/support/procedural/tools/derive/Cargo.toml +++ b/frame/support/procedural/tools/derive/Cargo.toml @@ -15,6 +15,6 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -proc-macro2 = "1.0.37" -quote = { version = "1.0.10", features = ["proc-macro"] } -syn = { version = "1.0.98", features = ["proc-macro", "full", "extra-traits", "parsing"] } +proc-macro2 = "1.0.56" +quote = { version = "1.0.28", features = ["proc-macro"] } +syn = { version = "2.0.16", features = ["proc-macro", "full", "extra-traits", "parsing"] } diff --git a/frame/support/procedural/tools/derive/src/lib.rs b/frame/support/procedural/tools/derive/src/lib.rs index 392a43834201..f7c57c08674f 100644 --- a/frame/support/procedural/tools/derive/src/lib.rs +++ b/frame/support/procedural/tools/derive/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/procedural/tools/src/lib.rs b/frame/support/procedural/tools/src/lib.rs index 3abde5957411..541accc8ab96 100644 --- a/frame/support/procedural/tools/src/lib.rs +++ b/frame/support/procedural/tools/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -102,16 +102,15 @@ pub fn clean_type_string(input: &str) -> String { } /// Return all doc attributes literals found. -pub fn get_doc_literals(attrs: &[syn::Attribute]) -> Vec { +pub fn get_doc_literals(attrs: &[syn::Attribute]) -> Vec { attrs .iter() .filter_map(|attr| { - if let Ok(syn::Meta::NameValue(meta)) = attr.parse_meta() { - if meta.path.get_ident().map_or(false, |ident| ident == "doc") { - Some(meta.lit) - } else { - None - } + if let syn::Meta::NameValue(meta) = &attr.meta { + meta.path + .get_ident() + .filter(|ident| *ident == "doc") + .map(|_| meta.value.clone()) } else { None } diff --git a/frame/support/procedural/tools/src/syn_ext.rs b/frame/support/procedural/tools/src/syn_ext.rs index 1a2d7c1d372a..833cd17dfacb 100644 --- a/frame/support/procedural/tools/src/syn_ext.rs +++ b/frame/support/procedural/tools/src/syn_ext.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/crypto.rs b/frame/support/src/crypto.rs index 182a784649d0..1957a7bc97ce 100644 --- a/frame/support/src/crypto.rs +++ b/frame/support/src/crypto.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/crypto/ecdsa.rs b/frame/support/src/crypto/ecdsa.rs index a4a04acabe1d..4d50b51049c9 100644 --- a/frame/support/src/crypto/ecdsa.rs +++ b/frame/support/src/crypto/ecdsa.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -47,3 +47,19 @@ impl ECDSAExt for Public { }) } } + +#[cfg(test)] +mod tests { + use super::*; + use sp_core::{ecdsa, Pair}; + + #[test] + fn to_eth_address_works() { + let pair = ecdsa::Pair::from_string("//Alice//password", None).unwrap(); + let eth_address = pair.public().to_eth_address().unwrap(); + assert_eq!( + array_bytes::bytes2hex("0x", ð_address), + "0xdc1cce4263956850a3c8eb349dc6fc3f7792cb27" + ); + } +} diff --git a/frame/support/src/dispatch.rs b/frame/support/src/dispatch.rs index 291abdd12893..7dfac19c9407 100644 --- a/frame/support/src/dispatch.rs +++ b/frame/support/src/dispatch.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,7 +29,8 @@ pub use crate::{ result, }, traits::{ - CallMetadata, GetCallMetadata, GetCallName, GetStorageVersion, UnfilteredDispatchable, + CallMetadata, GetCallIndex, GetCallMetadata, GetCallName, GetStorageVersion, + UnfilteredDispatchable, }, }; #[cfg(feature = "std")] @@ -88,6 +89,26 @@ impl From> for RawOrigin { } } +impl RawOrigin { + /// Returns `Some` with a reference to the `AccountId` if `self` is `Signed`, `None` otherwise. + pub fn as_signed(&self) -> Option<&AccountId> { + match &self { + Self::Signed(x) => Some(x), + _ => None, + } + } + + /// Returns `true` if `self` is `Root`, `None` otherwise. + pub fn is_root(&self) -> bool { + matches!(&self, Self::Root) + } + + /// Returns `true` if `self` is `None`, `None` otherwise. + pub fn is_none(&self) -> bool { + matches!(&self, Self::None) + } +} + /// A type that can be used as a parameter in a dispatchable function. /// /// When using `decl_module` all arguments for call functions must implement this trait. @@ -314,7 +335,7 @@ pub trait WithPostDispatchInfo { /// # Example /// /// ```ignore - /// let who = ensure_signed(origin).map_err(|e| e.with_weight(Weight::from_ref_time(100)))?; + /// let who = ensure_signed(origin).map_err(|e| e.with_weight(Weight::from_parts(100, 0)))?; /// ensure!(who == me, Error::::NotMe.with_weight(200_000)); /// ``` fn with_weight(self, actual_weight: Weight) -> DispatchErrorWithPostInfo; @@ -365,7 +386,7 @@ impl GetDispatchInfo fn get_dispatch_info(&self) -> DispatchInfo { // for testing: weight == size. DispatchInfo { - weight: Weight::from_ref_time(self.encode().len() as _), + weight: Weight::from_parts(self.encode().len() as _, 0), pays_fee: Pays::Yes, class: self.call.get_dispatch_info().class, } @@ -549,27 +570,6 @@ impl ClassifyDispatch for (Weight, DispatchClass, Pays) { // TODO: Eventually remove these -impl From> for PostDispatchInfo { - fn from(maybe_actual_computation: Option) -> Self { - let actual_weight = match maybe_actual_computation { - Some(actual_computation) => Some(Weight::zero().set_ref_time(actual_computation)), - None => None, - }; - Self { actual_weight, pays_fee: Default::default() } - } -} - -impl From<(Option, Pays)> for PostDispatchInfo { - fn from(post_weight_info: (Option, Pays)) -> Self { - let (maybe_actual_time, pays_fee) = post_weight_info; - let actual_weight = match maybe_actual_time { - Some(actual_time) => Some(Weight::zero().set_ref_time(actual_time)), - None => None, - }; - Self { actual_weight, pays_fee } - } -} - impl ClassifyDispatch for u64 { fn classify_dispatch(&self, _: T) -> DispatchClass { DispatchClass::Normal @@ -584,7 +584,7 @@ impl PaysFee for u64 { impl WeighData for u64 { fn weigh_data(&self, _: T) -> Weight { - return Weight::zero().set_ref_time(*self) + return Weight::from_parts(*self, 0) } } @@ -645,260 +645,9 @@ impl PaysFee for (u64, Pays) { // END TODO /// Declares a `Module` struct and a `Call` enum, which implements the dispatch logic. -/// -/// ## Declaration -/// -/// ``` -/// # #[macro_use] -/// # extern crate frame_support; -/// # use frame_support::dispatch; -/// # use frame_system::{Config, ensure_signed}; -/// decl_module! { -/// pub struct Module for enum Call where origin: T::RuntimeOrigin { -/// -/// // Private functions are dispatchable, but not available to other -/// // FRAME pallets. -/// #[weight = 0] -/// fn my_function(origin, var: u64) -> dispatch::DispatchResult { -/// // Your implementation -/// Ok(()) -/// } -/// -/// // Public functions are both dispatchable and available to other -/// // FRAME pallets. -/// #[weight = 0] -/// pub fn my_public_function(origin) -> dispatch::DispatchResult { -/// // Your implementation -/// Ok(()) -/// } -/// } -/// } -/// # fn main() {} -/// ``` -/// -/// The declaration is set with the header where: -/// -/// * `Module`: The struct generated by the macro, with type `Config`. -/// * `Call`: The enum generated for every pallet, which implements -/// [`Callable`](./dispatch/trait.Callable.html). -/// * `origin`: Alias of `T::RuntimeOrigin`. -/// * `Result`: The expected return type from pallet functions. -/// -/// The first parameter of dispatchable functions must always be `origin`. -/// -/// ### Shorthand Example -/// -/// The macro automatically expands a shorthand function declaration to return the -/// [`DispatchResult`] type. These functions are the same: -/// -/// ``` -/// # #[macro_use] -/// # extern crate frame_support; -/// # use frame_support::dispatch; -/// # use frame_system::{Config, ensure_signed}; -/// decl_module! { -/// pub struct Module for enum Call where origin: T::RuntimeOrigin { -/// #[weight = 0] -/// fn my_long_function(origin) -> dispatch::DispatchResult { -/// // Your implementation -/// Ok(()) -/// } -/// -/// #[weight = 0] -/// fn my_short_function(origin) { -/// // Your implementation -/// } -/// } -/// } -/// # fn main() {} -/// ``` -/// -/// ### Consuming only portions of the annotated static weight -/// -/// Per default a callable function consumes all of its static weight as declared via -/// the #\[weight\] attribute. However, there are use cases where only a portion of this -/// weight should be consumed. In that case the static weight is charged pre dispatch and -/// the difference is refunded post dispatch. -/// -/// In order to make use of this feature the function must return `DispatchResultWithPostInfo` -/// in place of the default `DispatchResult`. Then the actually consumed weight can be returned. -/// To consume a non default weight while returning an error -/// [`WithPostDispatchInfo::with_weight`](./weight/trait.WithPostDispatchInfo.html) can be used -/// to augment any error with custom weight information. -/// -/// ``` -/// # #[macro_use] -/// # extern crate frame_support; -/// # use frame_support::{weights::Weight, dispatch::{DispatchResultWithPostInfo, WithPostDispatchInfo}}; -/// # use frame_system::{Config, ensure_signed}; -/// decl_module! { -/// pub struct Module for enum Call where origin: T::RuntimeOrigin { -/// #[weight = 1_000_000] -/// fn my_long_function(origin, do_expensive_calc: bool) -> DispatchResultWithPostInfo { -/// ensure_signed(origin).map_err(|e| e.with_weight(Weight::from_ref_time(100_000)))?; -/// if do_expensive_calc { -/// // do the expensive calculation -/// // ... -/// // return None to indicate that we are using all weight (the default) -/// return Ok(None::.into()); -/// } -/// // expensive calculation not executed: use only a portion of the weight -/// Ok(Some(100_000).into()) -/// } -/// } -/// } -/// # fn main() {} -/// ``` -/// -/// ### Transactional Function Example -/// -/// Transactional function discards all changes to storage if it returns `Err`, or commits if -/// `Ok`, via the #\[transactional\] attribute. Note the attribute must be after #\[weight\]. -/// The #\[transactional\] attribute is deprecated since it is the default behaviour. -/// -/// ``` -/// # #[macro_use] -/// # extern crate frame_support; -/// # use frame_support::transactional; -/// # use frame_system::Config; -/// decl_module! { -/// pub struct Module for enum Call where origin: T::RuntimeOrigin { -/// #[weight = 0] -/// #[transactional] -/// fn my_short_function(origin) { -/// // Your implementation -/// } -/// } -/// } -/// # fn main() {} -/// ``` -/// -/// ### Privileged Function Example -/// -/// A privileged function checks that the origin of the call is `ROOT`. -/// -/// ``` -/// # #[macro_use] -/// # extern crate frame_support; -/// # use frame_support::dispatch; -/// # use frame_system::{Config, ensure_signed, ensure_root}; -/// decl_module! { -/// pub struct Module for enum Call where origin: T::RuntimeOrigin { -/// #[weight = 0] -/// fn my_privileged_function(origin) -> dispatch::DispatchResult { -/// ensure_root(origin)?; -/// // Your implementation -/// Ok(()) -/// } -/// } -/// } -/// # fn main() {} -/// ``` -/// -/// ### Attributes on Functions -/// -/// Attributes on functions are supported, but must be in the order of: -/// 1. Optional #\[doc\] attribute. -/// 2. #\[weight\] attribute. -/// 3. Optional function attributes, for instance #\[transactional\]. Those function attributes will -/// be written only on the dispatchable functions implemented on `Module`, not on the `Call` enum -/// variant. -/// -/// ## Multiple Module Instances Example -/// -/// A Substrate module can be built such that multiple instances of the same module can be used -/// within a single runtime. For example, the [Balances module](../pallet_balances/index.html) can -/// be added multiple times to your runtime in order to support multiple, independent currencies for -/// your blockchain. Here is an example of how you would declare such a module using the -/// `decl_module!` macro: -/// -/// ``` -/// # #[macro_use] -/// # extern crate frame_support; -/// # use frame_support::dispatch; -/// # use frame_system::ensure_signed; -/// # pub struct DefaultInstance; -/// # pub trait Instance: 'static {} -/// # impl Instance for DefaultInstance {} -/// pub trait Config: frame_system::Config {} -/// -/// decl_module! { -/// pub struct Module, I: Instance = DefaultInstance> for enum Call where origin: T::RuntimeOrigin { -/// // Your implementation -/// } -/// } -/// # fn main() {} -/// ``` -/// -/// Note: `decl_storage` must be called to generate `Instance` trait and optionally -/// `DefaultInstance` type. -/// -/// ## Where clause -/// -/// Besides the default `origin: T::RuntimeOrigin`, you can also pass other bounds to the module -/// declaration. This where bound will be replicated to all types generated by this macro. The -/// chaining of multiple trait bounds with `+` is not supported. If multiple bounds for one type are -/// required, it needs to be split up into multiple bounds. -/// -/// ``` -/// # #[macro_use] -/// # extern crate frame_support; -/// # use frame_support::dispatch; -/// # use frame_system::{self as system, ensure_signed}; -/// pub trait Config: system::Config where Self::AccountId: From {} -/// -/// decl_module! { -/// pub struct Module for enum Call where origin: T::RuntimeOrigin, T::AccountId: From { -/// // Your implementation -/// } -/// } -/// # fn main() {} -/// ``` -/// -/// ## Reserved Functions -/// -/// The following are reserved function signatures: -/// -/// * `deposit_event`: Helper function for depositing an [event](https://docs.substrate.io/main-docs/build/events-errors/). -/// The default behavior is to call `deposit_event` from the [System -/// module](../frame_system/index.html). However, you can write your own implementation for events -/// in your runtime. To use the default behavior, add `fn deposit_event() = default;` to your -/// `Module`. -/// -/// The following reserved functions also take the block number (with type `T::BlockNumber`) as an -/// optional input: -/// -/// * `on_runtime_upgrade`: Executes at the beginning of a block prior to on_initialize when there -/// is a runtime upgrade. This allows each module to upgrade its storage before the storage items -/// are used. As such, **calling other modules must be avoided**!! Using this function will -/// implement the [`OnRuntimeUpgrade`](../sp_runtime/traits/trait.OnRuntimeUpgrade.html) trait. -/// Function signature must be `fn on_runtime_upgrade() -> frame_support::weights::Weight`. -/// -/// * `on_initialize`: Executes at the beginning of a block. Using this function will -/// implement the [`OnInitialize`](./trait.OnInitialize.html) trait. -/// Function signature can be either: -/// * `fn on_initialize(n: BlockNumber) -> frame_support::weights::Weight` or -/// * `fn on_initialize() -> frame_support::weights::Weight` -/// -/// * `on_idle`: Executes at the end of a block. Passes a remaining weight to provide a threshold -/// for when to execute non vital functions. Using this function will implement the -/// [`OnIdle`](./traits/trait.OnIdle.html) trait. -/// Function signature is: -/// * `fn on_idle(n: BlockNumber, remaining_weight: Weight) -> frame_support::weights::Weight` -/// -/// * `on_finalize`: Executes at the end of a block. Using this function will -/// implement the [`OnFinalize`](./traits/trait.OnFinalize.html) trait. -/// Function signature can be either: -/// * `fn on_finalize(n: BlockNumber) -> frame_support::weights::Weight` or -/// * `fn on_finalize() -> frame_support::weights::Weight` -/// -/// * `offchain_worker`: Executes at the beginning of a block and produces extrinsics for a future -/// block upon completion. Using this function will implement the -/// [`OffchainWorker`](./traits/trait.OffchainWorker.html) trait. -/// * `integrity_test`: Executes in a test generated by `construct_runtime`, note it doesn't execute -/// in an externalities-provided environment. Implement -/// [`IntegrityTest`](./trait.IntegrityTest.html) trait. #[macro_export] +#[deprecated(note = "Will be removed after July 2023; use the attribute `#[pallet]` macro instead. + For more info, see: ")] macro_rules! decl_module { // Entry point #1. ( @@ -2115,7 +1864,7 @@ macro_rules! decl_module { fn try_state( _: <$trait_instance as $system::Config>::BlockNumber, _: $crate::traits::TryStateSelect, - ) -> Result<(), &'static str> { + ) -> Result<(), $crate::sp_runtime::TryRuntimeError> { let pallet_name = << $trait_instance as @@ -2162,12 +1911,12 @@ macro_rules! decl_module { } #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result<$crate::sp_std::vec::Vec, &'static str> { + fn pre_upgrade() -> Result<$crate::sp_std::vec::Vec, $crate::sp_runtime::TryRuntimeError> { Ok($crate::sp_std::vec::Vec::new()) } #[cfg(feature = "try-runtime")] - fn post_upgrade(_: $crate::sp_std::vec::Vec) -> Result<(), &'static str> { + fn post_upgrade(_: $crate::sp_std::vec::Vec) -> Result<(), $crate::sp_runtime::TryRuntimeError> { Ok(()) } } @@ -2200,12 +1949,12 @@ macro_rules! decl_module { } #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result<$crate::sp_std::vec::Vec, &'static str> { + fn pre_upgrade() -> Result<$crate::sp_std::vec::Vec, $crate::sp_runtime::TryRuntimeError> { Ok($crate::sp_std::vec::Vec::new()) } #[cfg(feature = "try-runtime")] - fn post_upgrade(_: $crate::sp_std::vec::Vec) -> Result<(), &'static str> { + fn post_upgrade(_: $crate::sp_std::vec::Vec) -> Result<(), $crate::sp_runtime::TryRuntimeError> { Ok(()) } } @@ -2520,7 +2269,9 @@ macro_rules! decl_module { impl<$trait_instance: $trait_name $(, $instance: $instantiable)?> $crate::traits::GetStorageVersion for $module<$trait_instance $(, $instance)?> where $( $other_where_bounds )* { - fn current_storage_version() -> $crate::traits::StorageVersion { + type CurrentStorageVersion = $crate::traits::StorageVersion; + + fn current_storage_version() -> Self::CurrentStorageVersion { $( $storage_version )* } @@ -2528,6 +2279,16 @@ macro_rules! decl_module { $crate::traits::StorageVersion::get::() } } + + // Implement `OnGenesis` for `Module` + impl<$trait_instance: $trait_name $(, $instance: $instantiable)?> $crate::traits::OnGenesis + for $module<$trait_instance $(, $instance)?> where $( $other_where_bounds )* + { + fn on_genesis() { + let storage_version = ::current_storage_version(); + storage_version.put::(); + } + } }; // Implementation for `GetStorageVersion` when no storage version is passed. @@ -2539,7 +2300,9 @@ macro_rules! decl_module { impl<$trait_instance: $trait_name $(, $instance: $instantiable)?> $crate::traits::GetStorageVersion for $module<$trait_instance $(, $instance)?> where $( $other_where_bounds )* { - fn current_storage_version() -> $crate::traits::StorageVersion { + type CurrentStorageVersion = $crate::traits::NoStorageVersionSet; + + fn current_storage_version() -> Self::CurrentStorageVersion { Default::default() } @@ -2547,6 +2310,16 @@ macro_rules! decl_module { $crate::traits::StorageVersion::get::() } } + + // Implement `OnGenesis` for `Module` + impl<$trait_instance: $trait_name $(, $instance: $instantiable)?> $crate::traits::OnGenesis + for $module<$trait_instance $(, $instance)?> where $( $other_where_bounds )* + { + fn on_genesis() { + let storage_version = $crate::traits::StorageVersion::default(); + storage_version.put::(); + } + } }; // The main macro expansion that actually renders the module code. @@ -2834,16 +2607,6 @@ macro_rules! decl_module { } } - // Implement `OnGenesis` for `Module` - impl<$trait_instance: $trait_name $(, $instance: $instantiable)?> $crate::traits::OnGenesis - for $mod_type<$trait_instance $(, $instance)?> where $( $other_where_bounds )* - { - fn on_genesis() { - let storage_version = ::current_storage_version(); - storage_version.put::(); - } - } - // manual implementation of clone/eq/partialeq because using derive erroneously requires // clone/eq/partialeq from T. impl<$trait_instance: $trait_name $(, $instance: $instantiable)?> $crate::dispatch::Clone @@ -2948,6 +2711,10 @@ macro_rules! decl_module { { $( $other_where_bounds )* } $( $error_type )* } + $crate::__impl_docs_metadata! { + $mod_type<$trait_instance: $trait_name $(, $instance: $instantiable)?> + { $( $other_where_bounds )* } + } $crate::__impl_module_constants_metadata ! { $mod_type<$trait_instance: $trait_name $(, $instance: $instantiable)?> { $( $other_where_bounds )* } @@ -2973,7 +2740,7 @@ macro_rules! __dispatch_impl_metadata { { #[doc(hidden)] #[allow(dead_code)] - pub fn call_functions() -> $crate::metadata::PalletCallMetadata { + pub fn call_functions() -> $crate::metadata_ir::PalletCallMetadataIR { $crate::scale_info::meta_type::<$call_type<$trait_instance $(, $instance)?>>().into() } } @@ -2994,7 +2761,7 @@ macro_rules! __impl_error_metadata { { #[doc(hidden)] #[allow(dead_code)] - pub fn error_metadata() -> Option<$crate::metadata::PalletErrorMetadata> { + pub fn error_metadata() -> Option<$crate::metadata_ir::PalletErrorMetadataIR> { None } } @@ -3009,8 +2776,8 @@ macro_rules! __impl_error_metadata { { #[doc(hidden)] #[allow(dead_code)] - pub fn error_metadata() -> Option<$crate::metadata::PalletErrorMetadata> { - Some($crate::metadata::PalletErrorMetadata { + pub fn error_metadata() -> Option<$crate::metadata_ir::PalletErrorMetadataIR> { + Some($crate::metadata_ir::PalletErrorMetadataIR { ty: $crate::scale_info::meta_type::<$( $error_type )*>() }) } @@ -3018,6 +2785,26 @@ macro_rules! __impl_error_metadata { }; } +/// Implement metadata for pallet documentation. +#[macro_export] +#[doc(hidden)] +macro_rules! __impl_docs_metadata { + ( + $mod_type:ident<$trait_instance:ident: $trait_name:ident$(, $instance:ident: $instantiable:path)?> + { $( $other_where_bounds:tt )* } + ) => { + impl<$trait_instance: $trait_name $(, $instance: $instantiable)?> $mod_type<$trait_instance $(, $instance)?> + where $( $other_where_bounds )* + { + #[doc(hidden)] + #[allow(dead_code)] + pub fn pallet_documentation_metadata() -> $crate::sp_std::vec::Vec<&'static str> { + $crate::sp_std::vec![] + } + } + }; +} + /// Implement metadata for module constants. #[macro_export] #[doc(hidden)] @@ -3085,7 +2872,7 @@ macro_rules! __impl_module_constants_metadata { { #[doc(hidden)] #[allow(dead_code)] - pub fn pallet_constants_metadata() -> $crate::sp_std::vec::Vec<$crate::metadata::PalletConstantMetadata> { + pub fn pallet_constants_metadata() -> $crate::sp_std::vec::Vec<$crate::metadata_ir::PalletConstantMetadataIR> { // Create the `ByteGetter`s $( #[allow(non_upper_case_types)] @@ -3109,7 +2896,7 @@ macro_rules! __impl_module_constants_metadata { )* $crate::sp_std::vec![ $( - $crate::metadata::PalletConstantMetadata { + $crate::metadata_ir::PalletConstantMetadataIR { name: stringify!($name), ty: $crate::scale_info::meta_type::<$type>(), value: $default_byte_name::<$const_trait_instance $(, $const_instance)?>( @@ -3179,17 +2966,18 @@ macro_rules! __check_reserved_fn_name { #[cfg(test)] // Do not complain about unused `dispatch` and `dispatch_aux`. #[allow(dead_code)] +#[allow(deprecated)] mod tests { use super::*; use crate::{ dispatch::{DispatchClass, DispatchInfo, Pays}, - metadata::*, + metadata_ir::*, traits::{ CallerTrait, CrateVersion, Get, GetCallName, IntegrityTest, OnFinalize, OnIdle, OnInitialize, OnRuntimeUpgrade, PalletInfo, }, }; - use sp_weights::RuntimeDbWeight; + use sp_weights::{RuntimeDbWeight, Weight}; pub trait Config: system::Config + Sized where @@ -3239,13 +3027,13 @@ mod tests { #[weight = (5, DispatchClass::Operational)] fn operational(_origin) { unreachable!() } - fn on_initialize(n: T::BlockNumber,) -> Weight { if n.into() == 42 { panic!("on_initialize") } Weight::from_ref_time(7) } + fn on_initialize(n: T::BlockNumber,) -> Weight { if n.into() == 42 { panic!("on_initialize") } Weight::from_parts(7, 0) } fn on_idle(n: T::BlockNumber, remaining_weight: Weight,) -> Weight { - if n.into() == 42 || remaining_weight == Weight::from_ref_time(42) { panic!("on_idle") } - Weight::from_ref_time(7) + if n.into() == 42 || remaining_weight == Weight::from_parts(42, 0) { panic!("on_idle") } + Weight::from_parts(7, 0) } fn on_finalize(n: T::BlockNumber,) { if n.into() == 42 { panic!("on_finalize") } } - fn on_runtime_upgrade() -> Weight { Weight::from_ref_time(10) } + fn on_runtime_upgrade() -> Weight { Weight::from_parts(10, 0) } fn offchain_worker() {} /// Some doc fn integrity_test() { panic!("integrity_test") } @@ -3381,7 +3169,7 @@ mod tests { fn module_json_metadata() { let metadata = Module::::call_functions(); let expected_metadata = - PalletCallMetadata { ty: scale_info::meta_type::>() }; + PalletCallMetadataIR { ty: scale_info::meta_type::>() }; assert_eq!(expected_metadata, metadata); } @@ -3423,27 +3211,27 @@ mod tests { fn on_initialize_should_work_2() { assert_eq!( as OnInitialize>::on_initialize(10), - Weight::from_ref_time(7) + Weight::from_parts(7, 0) ); } #[test] #[should_panic(expected = "on_idle")] fn on_idle_should_work_1() { - as OnIdle>::on_idle(42, Weight::from_ref_time(9)); + as OnIdle>::on_idle(42, Weight::from_parts(9, 0)); } #[test] #[should_panic(expected = "on_idle")] fn on_idle_should_work_2() { - as OnIdle>::on_idle(9, Weight::from_ref_time(42)); + as OnIdle>::on_idle(9, Weight::from_parts(42, 0)); } #[test] fn on_idle_should_work_3() { assert_eq!( - as OnIdle>::on_idle(10, Weight::from_ref_time(11)), - Weight::from_ref_time(7) + as OnIdle>::on_idle(10, Weight::from_parts(11, 0)), + Weight::from_parts(7, 0) ); } @@ -3458,7 +3246,7 @@ mod tests { sp_io::TestExternalities::default().execute_with(|| { assert_eq!( as OnRuntimeUpgrade>::on_runtime_upgrade(), - Weight::from_ref_time(10) + Weight::from_parts(10, 0) ) }); } @@ -3469,7 +3257,7 @@ mod tests { assert_eq!( Call::::operational {}.get_dispatch_info(), DispatchInfo { - weight: Weight::from_ref_time(5), + weight: Weight::from_parts(5, 0), class: DispatchClass::Operational, pays_fee: Pays::Yes }, @@ -3478,7 +3266,7 @@ mod tests { assert_eq!( Call::::aux_3 {}.get_dispatch_info(), DispatchInfo { - weight: Weight::from_ref_time(3), + weight: Weight::from_parts(3, 0), class: DispatchClass::Normal, pays_fee: Pays::Yes }, @@ -3510,166 +3298,255 @@ mod tests { fn test_new_call_variant() { Call::::new_call_variant_aux_0(); } + + pub fn from_actual_ref_time(ref_time: Option) -> PostDispatchInfo { + PostDispatchInfo { + actual_weight: ref_time.map(|t| Weight::from_all(t)), + pays_fee: Default::default(), + } + } + + pub fn from_post_weight_info(ref_time: Option, pays_fee: Pays) -> PostDispatchInfo { + PostDispatchInfo { actual_weight: ref_time.map(|t| Weight::from_all(t)), pays_fee } + } } #[cfg(test)] // Do not complain about unused `dispatch` and `dispatch_aux`. #[allow(dead_code)] mod weight_tests { - use super::*; - use sp_core::{parameter_types, Get}; + use super::{tests::*, *}; + use sp_core::parameter_types; + use sp_runtime::{generic, traits::BlakeTwo256}; use sp_weights::RuntimeDbWeight; - pub trait Config: 'static { - type RuntimeOrigin; - type Balance; - type BlockNumber; - type DbWeight: Get; - type PalletInfo: crate::traits::PalletInfo; - } + pub use self::frame_system::{Call, Config, Pallet}; - pub struct TraitImpl {} + #[crate::pallet(dev_mode)] + pub mod frame_system { + use super::{frame_system, frame_system::pallet_prelude::*}; + pub use crate::dispatch::RawOrigin; + use crate::pallet_prelude::*; - parameter_types! { - pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { - read: 100, - write: 1000, - }; - } + #[pallet::pallet] + pub struct Pallet(_); - impl Config for TraitImpl { - type RuntimeOrigin = u32; - type BlockNumber = u32; - type Balance = u32; - type DbWeight = DbWeight; - type PalletInfo = crate::tests::PanicPalletInfo; - } + #[pallet::config] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: 'static { + type Block: Parameter + sp_runtime::traits::Block; + type AccountId; + type Balance; + type BaseCallFilter: crate::traits::Contains; + type RuntimeOrigin; + type RuntimeCall; + type PalletInfo: crate::traits::PalletInfo; + type DbWeight: Get; + } - decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self { + #[pallet::error] + pub enum Error { + /// Required by construct_runtime + CallFiltered, + } + + #[pallet::origin] + pub type Origin = RawOrigin<::AccountId>; + + #[pallet::call] + impl Pallet { // no arguments, fixed weight - #[weight = 1000] - fn f00(_origin) { unimplemented!(); } + #[pallet::weight(1000)] + pub fn f00(_origin: OriginFor) -> DispatchResult { + unimplemented!(); + } - #[weight = (1000, DispatchClass::Mandatory)] - fn f01(_origin) { unimplemented!(); } + #[pallet::weight((1000, DispatchClass::Mandatory))] + pub fn f01(_origin: OriginFor) -> DispatchResult { + unimplemented!(); + } - #[weight = (1000, Pays::No)] - fn f02(_origin) { unimplemented!(); } + #[pallet::weight((1000, Pays::No))] + pub fn f02(_origin: OriginFor) -> DispatchResult { + unimplemented!(); + } - #[weight = (1000, DispatchClass::Operational, Pays::No)] - fn f03(_origin) { unimplemented!(); } + #[pallet::weight((1000, DispatchClass::Operational, Pays::No))] + pub fn f03(_origin: OriginFor) -> DispatchResult { + unimplemented!(); + } // weight = a x 10 + b - #[weight = ((_a * 10 + _eb * 1) as u64, DispatchClass::Normal, Pays::Yes)] - fn f11(_origin, _a: u32, _eb: u32) { unimplemented!(); } + #[pallet::weight(((_a * 10 + _eb * 1) as u64, DispatchClass::Normal, Pays::Yes))] + pub fn f11(_origin: OriginFor, _a: u32, _eb: u32) -> DispatchResult { + unimplemented!(); + } + + #[pallet::weight((0, DispatchClass::Operational, Pays::Yes))] + pub fn f12(_origin: OriginFor, _a: u32, _eb: u32) -> DispatchResult { + unimplemented!(); + } + + #[pallet::weight(T::DbWeight::get().reads(3) + T::DbWeight::get().writes(2) + Weight::from_all(10_000))] + pub fn f20(_origin: OriginFor) -> DispatchResult { + unimplemented!(); + } - #[weight = (0, DispatchClass::Operational, Pays::Yes)] - fn f12(_origin, _a: u32, _eb: u32) { unimplemented!(); } + #[pallet::weight(T::DbWeight::get().reads_writes(6, 5) + Weight::from_all(40_000))] + pub fn f21(_origin: OriginFor) -> DispatchResult { + unimplemented!(); + } + } - #[weight = T::DbWeight::get().reads(3) + T::DbWeight::get().writes(2) + Weight::from_ref_time(10_000)] - fn f20(_origin) { unimplemented!(); } + pub mod pallet_prelude { + pub type OriginFor = ::RuntimeOrigin; - #[weight = T::DbWeight::get().reads_writes(6, 5) + Weight::from_ref_time(40_000)] - fn f21(_origin) { unimplemented!(); } + pub type HeaderFor = + <::Block as sp_runtime::traits::HeaderProvider>::HeaderT; + pub type BlockNumberFor = as sp_runtime::traits::Header>::Number; } } + type BlockNumber = u32; + type AccountId = u32; + type Balance = u32; + type Header = generic::Header; + type UncheckedExtrinsic = generic::UncheckedExtrinsic; + type Block = generic::Block; + + crate::construct_runtime!( + pub enum Runtime + { + System: self::frame_system, + } + ); + + parameter_types! { + pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { + read: 100, + write: 1000, + }; + } + + impl Config for Runtime { + type Block = Block; + type AccountId = AccountId; + type Balance = Balance; + type BaseCallFilter = crate::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type DbWeight = DbWeight; + type PalletInfo = PalletInfo; + } + #[test] fn weights_are_correct() { - // #[weight = 1000] - let info = Call::::f00 {}.get_dispatch_info(); - assert_eq!(info.weight, Weight::from_ref_time(1000)); + // #[pallet::weight(1000)] + let info = Call::::f00 {}.get_dispatch_info(); + assert_eq!(info.weight, Weight::from_parts(1000, 0)); assert_eq!(info.class, DispatchClass::Normal); assert_eq!(info.pays_fee, Pays::Yes); - // #[weight = (1000, DispatchClass::Mandatory)] - let info = Call::::f01 {}.get_dispatch_info(); - assert_eq!(info.weight, Weight::from_ref_time(1000)); + // #[pallet::weight((1000, DispatchClass::Mandatory))] + let info = Call::::f01 {}.get_dispatch_info(); + assert_eq!(info.weight, Weight::from_parts(1000, 0)); assert_eq!(info.class, DispatchClass::Mandatory); assert_eq!(info.pays_fee, Pays::Yes); - // #[weight = (1000, Pays::No)] - let info = Call::::f02 {}.get_dispatch_info(); - assert_eq!(info.weight, Weight::from_ref_time(1000)); + // #[pallet::weight((1000, Pays::No))] + let info = Call::::f02 {}.get_dispatch_info(); + assert_eq!(info.weight, Weight::from_parts(1000, 0)); assert_eq!(info.class, DispatchClass::Normal); assert_eq!(info.pays_fee, Pays::No); - // #[weight = (1000, DispatchClass::Operational, Pays::No)] - let info = Call::::f03 {}.get_dispatch_info(); - assert_eq!(info.weight, Weight::from_ref_time(1000)); + // #[pallet::weight((1000, DispatchClass::Operational, Pays::No))] + let info = Call::::f03 {}.get_dispatch_info(); + assert_eq!(info.weight, Weight::from_parts(1000, 0)); assert_eq!(info.class, DispatchClass::Operational); assert_eq!(info.pays_fee, Pays::No); - // #[weight = ((_a * 10 + _eb * 1) as Weight, DispatchClass::Normal, Pays::Yes)] - let info = Call::::f11 { _a: 13, _eb: 20 }.get_dispatch_info(); - assert_eq!(info.weight, Weight::from_ref_time(150)); // 13*10 + 20 + // #[pallet::weight(((_a * 10 + _eb * 1) as u64, DispatchClass::Normal, Pays::Yes))] + let info = Call::::f11 { a: 13, eb: 20 }.get_dispatch_info(); + assert_eq!(info.weight, Weight::from_parts(150, 0)); // 13*10 + 20 assert_eq!(info.class, DispatchClass::Normal); assert_eq!(info.pays_fee, Pays::Yes); - // #[weight = (0, DispatchClass::Operational, Pays::Yes)] - let info = Call::::f12 { _a: 10, _eb: 20 }.get_dispatch_info(); - assert_eq!(info.weight, Weight::from_ref_time(0)); + // #[pallet::weight((0, DispatchClass::Operational, Pays::Yes))] + let info = Call::::f12 { a: 10, eb: 20 }.get_dispatch_info(); + assert_eq!(info.weight, Weight::zero()); assert_eq!(info.class, DispatchClass::Operational); assert_eq!(info.pays_fee, Pays::Yes); - // #[weight = T::DbWeight::get().reads(3) + T::DbWeight::get().writes(2) + 10_000] - let info = Call::::f20 {}.get_dispatch_info(); - assert_eq!(info.weight, Weight::from_ref_time(12300)); // 100*3 + 1000*2 + 10_1000 + // #[pallet::weight(T::DbWeight::get().reads(3) + T::DbWeight::get().writes(2) + + // Weight::from_all(10_000))] + let info = Call::::f20 {}.get_dispatch_info(); + assert_eq!(info.weight, Weight::from_parts(12300, 10000)); // 100*3 + 1000*2 + 10_1000 assert_eq!(info.class, DispatchClass::Normal); assert_eq!(info.pays_fee, Pays::Yes); - // #[weight = T::DbWeight::get().reads_writes(6, 5) + 40_000] - let info = Call::::f21 {}.get_dispatch_info(); - assert_eq!(info.weight, Weight::from_ref_time(45600)); // 100*6 + 1000*5 + 40_1000 + // #[pallet::weight(T::DbWeight::get().reads_writes(6, 5) + Weight::from_all(40_000))] + let info = Call::::f21 {}.get_dispatch_info(); + assert_eq!(info.weight, Weight::from_parts(45600, 40000)); // 100*6 + 1000*5 + 40_1000 assert_eq!(info.class, DispatchClass::Normal); assert_eq!(info.pays_fee, Pays::Yes); } #[test] fn extract_actual_weight_works() { - let pre = DispatchInfo { weight: Weight::from_ref_time(1000), ..Default::default() }; - assert_eq!(extract_actual_weight(&Ok(Some(7).into()), &pre), Weight::from_ref_time(7)); + let pre = DispatchInfo { weight: Weight::from_parts(1000, 0), ..Default::default() }; assert_eq!( - extract_actual_weight(&Ok(Some(1000).into()), &pre), - Weight::from_ref_time(1000) + extract_actual_weight(&Ok(from_actual_ref_time(Some(7))), &pre), + Weight::from_parts(7, 0) + ); + assert_eq!( + extract_actual_weight(&Ok(from_actual_ref_time(Some(1000))), &pre), + Weight::from_parts(1000, 0) ); assert_eq!( extract_actual_weight( - &Err(DispatchError::BadOrigin.with_weight(Weight::from_ref_time(9))), + &Err(DispatchError::BadOrigin.with_weight(Weight::from_parts(9, 0))), &pre ), - Weight::from_ref_time(9) + Weight::from_parts(9, 0) ); } #[test] fn extract_actual_weight_caps_at_pre_weight() { - let pre = DispatchInfo { weight: Weight::from_ref_time(1000), ..Default::default() }; + let pre = DispatchInfo { weight: Weight::from_parts(1000, 0), ..Default::default() }; assert_eq!( - extract_actual_weight(&Ok(Some(1250).into()), &pre), - Weight::from_ref_time(1000) + extract_actual_weight(&Ok(from_actual_ref_time(Some(1250))), &pre), + Weight::from_parts(1000, 0) ); assert_eq!( extract_actual_weight( - &Err(DispatchError::BadOrigin.with_weight(Weight::from_ref_time(1300))), + &Err(DispatchError::BadOrigin.with_weight(Weight::from_parts(1300, 0))), &pre ), - Weight::from_ref_time(1000), + Weight::from_parts(1000, 0), ); } #[test] fn extract_actual_pays_fee_works() { - let pre = DispatchInfo { weight: Weight::from_ref_time(1000), ..Default::default() }; - assert_eq!(extract_actual_pays_fee(&Ok(Some(7).into()), &pre), Pays::Yes); - assert_eq!(extract_actual_pays_fee(&Ok(Some(1000).into()), &pre), Pays::Yes); - assert_eq!(extract_actual_pays_fee(&Ok((Some(1000), Pays::Yes).into()), &pre), Pays::Yes); - assert_eq!(extract_actual_pays_fee(&Ok((Some(1000), Pays::No).into()), &pre), Pays::No); + let pre = DispatchInfo { weight: Weight::from_parts(1000, 0), ..Default::default() }; + assert_eq!(extract_actual_pays_fee(&Ok(from_actual_ref_time(Some(7))), &pre), Pays::Yes); + assert_eq!( + extract_actual_pays_fee(&Ok(from_actual_ref_time(Some(1000)).into()), &pre), + Pays::Yes + ); + assert_eq!( + extract_actual_pays_fee(&Ok(from_post_weight_info(Some(1000), Pays::Yes)), &pre), + Pays::Yes + ); + assert_eq!( + extract_actual_pays_fee(&Ok(from_post_weight_info(Some(1000), Pays::No)), &pre), + Pays::No + ); assert_eq!( extract_actual_pays_fee( - &Err(DispatchError::BadOrigin.with_weight(Weight::from_ref_time(9))), + &Err(DispatchError::BadOrigin.with_weight(Weight::from_parts(9, 0))), &pre ), Pays::Yes @@ -3686,13 +3563,16 @@ mod weight_tests { ); let pre = DispatchInfo { - weight: Weight::from_ref_time(1000), + weight: Weight::from_parts(1000, 0), pays_fee: Pays::No, ..Default::default() }; - assert_eq!(extract_actual_pays_fee(&Ok(Some(7).into()), &pre), Pays::No); - assert_eq!(extract_actual_pays_fee(&Ok(Some(1000).into()), &pre), Pays::No); - assert_eq!(extract_actual_pays_fee(&Ok((Some(1000), Pays::Yes).into()), &pre), Pays::No); + assert_eq!(extract_actual_pays_fee(&Ok(from_actual_ref_time(Some(7))), &pre), Pays::No); + assert_eq!(extract_actual_pays_fee(&Ok(from_actual_ref_time(Some(1000))), &pre), Pays::No); + assert_eq!( + extract_actual_pays_fee(&Ok(from_post_weight_info(Some(1000), Pays::Yes)), &pre), + Pays::No + ); } } diff --git a/frame/support/src/dispatch_context.rs b/frame/support/src/dispatch_context.rs new file mode 100644 index 000000000000..31278ea9f819 --- /dev/null +++ b/frame/support/src/dispatch_context.rs @@ -0,0 +1,232 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Provides functions to interact with the dispatch context. +//! +//! A Dispatch context is created by calling [`run_in_context`] and then the given closure will be +//! executed in this dispatch context. Everyting run in this `closure` will have access to the same +//! dispatch context. This also applies to nested calls of [`run_in_context`]. The dispatch context +//! can be used to store and retrieve information locally in this context. The dispatch context can +//! be accessed by using [`with_context`]. This function will execute the given closure and give it +//! access to the value stored in the dispatch context. +//! +//! # FRAME integration +//! +//! The FRAME macros implement [`UnfilteredDispatchable`](crate::traits::UnfilteredDispatchable) for +//! each pallet `Call` enum. Part of this implementation is the call to [`run_in_context`], so that +//! each call to +//! [`UnfilteredDispatchable::dispatch_bypass_filter`](crate::traits::UnfilteredDispatchable::dispatch_bypass_filter) +//! or [`Dispatchable::dispatch`](crate::dispatch::Dispatchable::dispatch) will run in a dispatch +//! context. +//! +//! # Example +//! +//! ``` +//! use frame_support::dispatch_context::{with_context, run_in_context}; +//! +//! // Not executed in a dispatch context, so it should return `None`. +//! assert!(with_context::<(), _>(|_| println!("Hello")).is_none()); +//! +//! // Run it in a dispatch context and `with_context` returns `Some(_)`. +//! run_in_context(|| { +//! assert!(with_context::<(), _>(|_| println!("Hello")).is_some()); +//! }); +//! +//! #[derive(Default)] +//! struct CustomContext(i32); +//! +//! run_in_context(|| { +//! with_context::(|v| { +//! // Intitialize the value to the default value. +//! assert_eq!(0, v.or_default().0); +//! v.or_default().0 = 10; +//! }); +//! +//! with_context::(|v| { +//! // We are still in the same context and can still access the set value. +//! assert_eq!(10, v.or_default().0); +//! }); +//! +//! run_in_context(|| { +//! with_context::(|v| { +//! // A nested call of `run_in_context` stays in the same dispatch context +//! assert_eq!(10, v.or_default().0); +//! }) +//! }) +//! }); +//! +//! run_in_context(|| { +//! with_context::(|v| { +//! // We left the other context and created a new one, so we should be back +//! // to our default value. +//! assert_eq!(0, v.or_default().0); +//! }); +//! }); +//! ``` +//! +//! In your pallet you will only have to use [`with_context`], because as described above +//! [`run_in_context`] will be handled by FRAME for you. + +use sp_std::{ + any::{Any, TypeId}, + boxed::Box, + collections::btree_map::{BTreeMap, Entry}, +}; + +environmental::environmental!(DISPATCH_CONTEXT: BTreeMap>); + +/// Abstraction over some optional value `T` that is stored in the dispatch context. +pub struct Value<'a, T> { + value: Option<&'a mut T>, + new_value: Option, +} + +impl Value<'_, T> { + /// Get the value as reference. + pub fn get(&self) -> Option<&T> { + self.new_value.as_ref().or_else(|| self.value.as_ref().map(|v| *v as &T)) + } + + /// Get the value as mutable reference. + pub fn get_mut(&mut self) -> Option<&mut T> { + self.new_value.as_mut().or_else(|| self.value.as_mut().map(|v| *v as &mut T)) + } + + /// Set to the given value. + /// + /// [`Self::get`] and [`Self::get_mut`] will return `new_value` afterwards. + pub fn set(&mut self, new_value: T) { + self.value = None; + self.new_value = Some(new_value); + } + + /// Returns a mutable reference to the value. + /// + /// If the internal value isn't initialized, this will set it to [`Default::default()`] before + /// returning the mutable reference. + pub fn or_default(&mut self) -> &mut T + where + T: Default, + { + if let Some(v) = &mut self.value { + return v + } + + self.new_value.get_or_insert_with(|| Default::default()) + } + + /// Clear the internal value. + /// + /// [`Self::get`] and [`Self::get_mut`] will return `None` afterwards. + pub fn clear(&mut self) { + self.new_value = None; + self.value = None; + } +} + +/// Runs the given `callback` in the dispatch context and gives access to some user defined value. +/// +/// Passes the a mutable reference of [`Value`] to the callback. The value will be of type `T` and +/// is identified using the [`TypeId`] of `T`. This means that `T` should be some unique type to +/// make the value unique. If no value is set yet [`Value::get()`] and [`Value::get_mut()`] will +/// return `None`. It is totally valid to have some `T` that is shared between different callers to +/// have access to the same value. +/// +/// Returns `None` if the current context is not a dispatch context. To create a context it is +/// required to call [`run_in_context`] with the closure to execute in this context. So, for example +/// in tests it could be that there isn't any dispatch context or when calling a dispatchable like a +/// normal Rust function from some FRAME hook. +pub fn with_context(callback: impl FnOnce(&mut Value) -> R) -> Option { + DISPATCH_CONTEXT::with(|c| match c.entry(TypeId::of::()) { + Entry::Occupied(mut o) => { + let value = o.get_mut().downcast_mut::(); + + if value.is_none() { + log::error!( + "Failed to downcast value for type {} in dispatch context!", + sp_std::any::type_name::(), + ); + } + + let mut value = Value { value, new_value: None }; + let res = callback(&mut value); + + if value.value.is_none() && value.new_value.is_none() { + o.remove(); + } else if let Some(new_value) = value.new_value { + o.insert(Box::new(new_value) as Box<_>); + } + + res + }, + Entry::Vacant(v) => { + let mut value = Value { value: None, new_value: None }; + + let res = callback(&mut value); + + if let Some(new_value) = value.new_value { + v.insert(Box::new(new_value) as Box<_>); + } + + res + }, + }) +} + +/// Run the given closure `run` in a dispatch context. +/// +/// Nested calls to this function will execute `run` in the same dispatch context as the initial +/// call to this function. In other words, all nested calls of this function will be done in the +/// same dispatch context. +pub fn run_in_context(run: impl FnOnce() -> R) -> R { + DISPATCH_CONTEXT::using_once(&mut Default::default(), run) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn dispatch_context_works() { + // No context, so we don't execute + assert!(with_context::<(), _>(|_| ()).is_none()); + + let ret = run_in_context(|| with_context::<(), _>(|_| 1).unwrap()); + assert_eq!(1, ret); + + #[derive(Default)] + struct Context(i32); + + let res = run_in_context(|| { + with_context::(|v| { + assert_eq!(0, v.or_default().0); + + v.or_default().0 = 100; + }); + + run_in_context(|| { + run_in_context(|| { + run_in_context(|| with_context::(|v| v.or_default().0).unwrap()) + }) + }) + }); + + // Ensure that the initial value set in the context is also accessible after nesting the + // `run_in_context` calls. + assert_eq!(100, res); + } +} diff --git a/frame/support/src/error.rs b/frame/support/src/error.rs index 337bd75895c2..5a7dd73f481e 100644 --- a/frame/support/src/error.rs +++ b/frame/support/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,52 +21,9 @@ pub use sp_runtime::traits::{BadOrigin, LookupError}; /// Declare an error type for a runtime module. -/// -/// `decl_error!` supports only variants that do not hold any data. The dispatchable -/// functions return [`DispatchResult`](sp_runtime::DispatchResult). The error type -/// implements `From for DispatchResult` to make the error type usable as error -/// in the dispatchable functions. -/// -/// It is required that the error type is registered in `decl_module!` to make the error -/// exported in the metadata. -/// -/// # Usage -/// -/// ``` -/// # use frame_support::{decl_error, decl_module}; -/// # -/// decl_error! { -/// /// Errors that can occur in my module. -/// pub enum MyError for Module { -/// /// Hey this is an error message that indicates bla. -/// MyCoolErrorMessage, -/// /// You are just not cool enough for my module! -/// YouAreNotCoolEnough, -/// } -/// } -/// -/// # use frame_system::Config; -/// -/// // You need to register the error type in `decl_module!` as well to make the error -/// // exported in the metadata. -/// -/// decl_module! { -/// pub struct Module for enum Call where origin: T::RuntimeOrigin { -/// type Error = MyError; -/// -/// #[weight = 0] -/// fn do_something(origin) -> frame_support::dispatch::DispatchResult { -/// Err(MyError::::YouAreNotCoolEnough.into()) -/// } -/// } -/// } -/// -/// # fn main() {} -/// ``` -/// -/// For instantiable modules you also need to give the instance generic type and bound to the -/// error declaration. #[macro_export] +#[deprecated(note = "Will be removed after July 2023; use the attribute `#[pallet]` macro instead. + For more info, see: ")] macro_rules! decl_error { ( $(#[$attr:meta])* diff --git a/frame/support/src/event.rs b/frame/support/src/event.rs index dc45f2bf69d9..53a3a68506a6 100644 --- a/frame/support/src/event.rs +++ b/frame/support/src/event.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -101,6 +101,8 @@ /// # fn main() {} /// ``` #[macro_export] +#[deprecated(note = "Will be removed after July 2023; use the attribute `#[pallet]` macro instead. + For more info, see: ")] macro_rules! decl_event { ( $(#[$attr:meta])* diff --git a/frame/support/src/hash.rs b/frame/support/src/hash.rs index 9bdad6d9d59d..115ce605d542 100644 --- a/frame/support/src/hash.rs +++ b/frame/support/src/hash.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ //! Hash utilities. -use crate::metadata; +use crate::metadata_ir; use codec::{Codec, MaxEncodedLen}; use sp_io::hashing::{blake2_128, blake2_256, twox_128, twox_256, twox_64}; use sp_std::prelude::Vec; @@ -59,7 +59,7 @@ impl Hashable for T { /// Hasher to use to hash keys to insert to storage. pub trait StorageHasher: 'static { - const METADATA: metadata::StorageHasher; + const METADATA: metadata_ir::StorageHasherIR; type Output: AsRef<[u8]>; fn hash(x: &[u8]) -> Self::Output; @@ -80,7 +80,7 @@ pub trait ReversibleStorageHasher: StorageHasher { /// Store the key directly. pub struct Identity; impl StorageHasher for Identity { - const METADATA: metadata::StorageHasher = metadata::StorageHasher::Identity; + const METADATA: metadata_ir::StorageHasherIR = metadata_ir::StorageHasherIR::Identity; type Output = Vec; fn hash(x: &[u8]) -> Vec { x.to_vec() @@ -98,7 +98,7 @@ impl ReversibleStorageHasher for Identity { /// Hash storage keys with `concat(twox64(key), key)` pub struct Twox64Concat; impl StorageHasher for Twox64Concat { - const METADATA: metadata::StorageHasher = metadata::StorageHasher::Twox64Concat; + const METADATA: metadata_ir::StorageHasherIR = metadata_ir::StorageHasherIR::Twox64Concat; type Output = Vec; fn hash(x: &[u8]) -> Vec { twox_64(x).iter().chain(x.iter()).cloned().collect::>() @@ -120,7 +120,7 @@ impl ReversibleStorageHasher for Twox64Concat { /// Hash storage keys with `concat(blake2_128(key), key)` pub struct Blake2_128Concat; impl StorageHasher for Blake2_128Concat { - const METADATA: metadata::StorageHasher = metadata::StorageHasher::Blake2_128Concat; + const METADATA: metadata_ir::StorageHasherIR = metadata_ir::StorageHasherIR::Blake2_128Concat; type Output = Vec; fn hash(x: &[u8]) -> Vec { blake2_128(x).iter().chain(x.iter()).cloned().collect::>() @@ -142,7 +142,7 @@ impl ReversibleStorageHasher for Blake2_128Concat { /// Hash storage keys with blake2 128 pub struct Blake2_128; impl StorageHasher for Blake2_128 { - const METADATA: metadata::StorageHasher = metadata::StorageHasher::Blake2_128; + const METADATA: metadata_ir::StorageHasherIR = metadata_ir::StorageHasherIR::Blake2_128; type Output = [u8; 16]; fn hash(x: &[u8]) -> [u8; 16] { blake2_128(x) @@ -155,7 +155,7 @@ impl StorageHasher for Blake2_128 { /// Hash storage keys with blake2 256 pub struct Blake2_256; impl StorageHasher for Blake2_256 { - const METADATA: metadata::StorageHasher = metadata::StorageHasher::Blake2_256; + const METADATA: metadata_ir::StorageHasherIR = metadata_ir::StorageHasherIR::Blake2_256; type Output = [u8; 32]; fn hash(x: &[u8]) -> [u8; 32] { blake2_256(x) @@ -168,7 +168,7 @@ impl StorageHasher for Blake2_256 { /// Hash storage keys with twox 128 pub struct Twox128; impl StorageHasher for Twox128 { - const METADATA: metadata::StorageHasher = metadata::StorageHasher::Twox128; + const METADATA: metadata_ir::StorageHasherIR = metadata_ir::StorageHasherIR::Twox128; type Output = [u8; 16]; fn hash(x: &[u8]) -> [u8; 16] { twox_128(x) @@ -181,7 +181,7 @@ impl StorageHasher for Twox128 { /// Hash storage keys with twox 256 pub struct Twox256; impl StorageHasher for Twox256 { - const METADATA: metadata::StorageHasher = metadata::StorageHasher::Twox256; + const METADATA: metadata_ir::StorageHasherIR = metadata_ir::StorageHasherIR::Twox256; type Output = [u8; 32]; fn hash(x: &[u8]) -> [u8; 32] { twox_256(x) diff --git a/frame/support/src/inherent.rs b/frame/support/src/inherent.rs index 0aa6b9d3f75a..8889c93809c7 100644 --- a/frame/support/src/inherent.rs +++ b/frame/support/src/inherent.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,11 +15,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[doc(hidden)] -pub use crate::sp_runtime::traits::{Block as BlockT, Extrinsic}; -#[doc(hidden)] -pub use crate::sp_std::vec::Vec; - pub use sp_inherents::{ CheckInherentsResult, InherentData, InherentIdentifier, IsFatalError, MakeFatalError, }; diff --git a/frame/support/src/instances.rs b/frame/support/src/instances.rs index 81139f867181..396018d5cbd5 100644 --- a/frame/support/src/instances.rs +++ b/frame/support/src/instances.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,66 +31,83 @@ //! NOTE: [`frame_support::pallet`] will reexport them inside the module, in order to make them //! accessible to [`frame_support::construct_runtime`]. -/// Instance1 to be used for instantiable pallet define with `pallet` macro. +/// `Instance1` to be used for instantiable palllets defined with the +/// [`#[pallet]`](`frame_support::pallet`) macro. Instances 2-16 are also available but are hidden +/// from docs. #[derive(Clone, Copy, PartialEq, Eq, crate::RuntimeDebugNoBound)] pub struct Instance1; -/// Instance2 to be used for instantiable pallet define with `pallet` macro. +/// `Instance2` to be used for instantiable palllets defined with the `#[pallet]` macro. +#[doc(hidden)] #[derive(Clone, Copy, PartialEq, Eq, crate::RuntimeDebugNoBound)] pub struct Instance2; -/// Instance3 to be used for instantiable pallet define with `pallet` macro. +/// `Instance3` to be used for instantiable palllets defined with the `#[pallet]` macro. +#[doc(hidden)] #[derive(Clone, Copy, PartialEq, Eq, crate::RuntimeDebugNoBound)] pub struct Instance3; -/// Instance4 to be used for instantiable pallet define with `pallet` macro. +/// `Instance4` to be used for instantiable palllets defined with the `#[pallet]` macro. +#[doc(hidden)] #[derive(Clone, Copy, PartialEq, Eq, crate::RuntimeDebugNoBound)] pub struct Instance4; -/// Instance5 to be used for instantiable pallet define with `pallet` macro. +/// `Instance5` to be used for instantiable palllets defined with the `#[pallet]` macro. +#[doc(hidden)] #[derive(Clone, Copy, PartialEq, Eq, crate::RuntimeDebugNoBound)] pub struct Instance5; -/// Instance6 to be used for instantiable pallet define with `pallet` macro. +/// `Instance6` to be used for instantiable palllets defined with the `#[pallet]` macro. +#[doc(hidden)] #[derive(Clone, Copy, PartialEq, Eq, crate::RuntimeDebugNoBound)] pub struct Instance6; -/// Instance7 to be used for instantiable pallet define with `pallet` macro. +/// `Instance7` to be used for instantiable palllets defined with the `#[pallet]` macro. +#[doc(hidden)] #[derive(Clone, Copy, PartialEq, Eq, crate::RuntimeDebugNoBound)] pub struct Instance7; -/// Instance8 to be used for instantiable pallet define with `pallet` macro. +/// `Instance8` to be used for instantiable palllets defined with the `#[pallet]` macro. +#[doc(hidden)] #[derive(Clone, Copy, PartialEq, Eq, crate::RuntimeDebugNoBound)] pub struct Instance8; -/// Instance9 to be used for instantiable pallet define with `pallet` macro. +/// `Instance9` to be used for instantiable palllets defined with the `#[pallet]` macro. +#[doc(hidden)] #[derive(Clone, Copy, PartialEq, Eq, crate::RuntimeDebugNoBound)] pub struct Instance9; -/// Instance10 to be used for instantiable pallet define with `pallet` macro. +/// `Instance10` to be used for instantiable palllets defined with the `#[pallet]` macro. +#[doc(hidden)] #[derive(Clone, Copy, PartialEq, Eq, crate::RuntimeDebugNoBound)] pub struct Instance10; -/// Instance11 to be used for instantiable pallet define with `pallet` macro. +/// `Instance11` to be used for instantiable palllets defined with the `#[pallet]` macro. +#[doc(hidden)] #[derive(Clone, Copy, PartialEq, Eq, crate::RuntimeDebugNoBound)] pub struct Instance11; -/// Instance12 to be used for instantiable pallet define with `pallet` macro. +/// `Instance12` to be used for instantiable palllets defined with the `#[pallet]` macro. +#[doc(hidden)] #[derive(Clone, Copy, PartialEq, Eq, crate::RuntimeDebugNoBound)] pub struct Instance12; -/// Instance13 to be used for instantiable pallet define with `pallet` macro. +/// `Instance13` to be used for instantiable palllets defined with the `#[pallet]` macro. +#[doc(hidden)] #[derive(Clone, Copy, PartialEq, Eq, crate::RuntimeDebugNoBound)] pub struct Instance13; -/// Instance14 to be used for instantiable pallet define with `pallet` macro. +/// `Instance14` to be used for instantiable palllets defined with the `#[pallet]` macro. +#[doc(hidden)] #[derive(Clone, Copy, PartialEq, Eq, crate::RuntimeDebugNoBound)] pub struct Instance14; -/// Instance15 to be used for instantiable pallet define with `pallet` macro. +/// `Instance15` to be used for instantiable palllets defined with the `#[pallet]` macro. +#[doc(hidden)] #[derive(Clone, Copy, PartialEq, Eq, crate::RuntimeDebugNoBound)] pub struct Instance15; -/// Instance16 to be used for instantiable pallet define with `pallet` macro. +/// `Instance16` to be used for instantiable palllets defined with the `#[pallet]` macro. +#[doc(hidden)] #[derive(Clone, Copy, PartialEq, Eq, crate::RuntimeDebugNoBound)] pub struct Instance16; diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 40bc878cff36..51477ee384c7 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,16 +41,13 @@ pub use codec; pub use frame_metadata as metadata; #[doc(hidden)] pub use log; -#[cfg(feature = "std")] -#[doc(hidden)] -pub use once_cell; #[doc(hidden)] pub use paste; #[doc(hidden)] pub use scale_info; -#[cfg(feature = "std")] pub use serde; -pub use sp_core::Void; +pub use sp_api::metadata_ir; +pub use sp_core::{OpaqueMetadata, Void}; #[doc(hidden)] pub use sp_core_hashing_proc_macro; #[doc(hidden)] @@ -78,11 +75,11 @@ pub mod inherent; #[macro_use] pub mod error; pub mod crypto; +pub mod dispatch_context; pub mod instances; pub mod migrations; pub mod traits; pub mod weights; - #[doc(hidden)] pub mod unsigned { #[doc(hidden)] @@ -210,6 +207,8 @@ impl TypeId for PalletId { /// ``` pub use frame_support_procedural::storage_alias; +pub use frame_support_procedural::derive_impl; + /// Create new implementations of the [`Get`](crate::traits::Get) trait. /// /// The so-called parameter type can be created in four different ways: @@ -370,16 +369,14 @@ macro_rules! parameter_types { /// Set the value of this parameter type in the storage. /// - /// This needs to be executed in an externalities provided - /// environment. + /// This needs to be executed in an externalities provided environment. pub fn set(value: &$type) { $crate::storage::unhashed::put(&Self::key(), value); } /// Returns the value of this parameter type. /// - /// This needs to be executed in an externalities provided - /// environment. + /// This needs to be executed in an externalities provided environment. #[allow(unused)] pub fn get() -> $type { $crate::storage::unhashed::get(&Self::key()).unwrap_or_else(|| $value) @@ -819,96 +816,181 @@ macro_rules! assert_error_encoded_size { } => {}; } -#[cfg(feature = "std")] #[doc(hidden)] pub use serde::{Deserialize, Serialize}; +#[doc(hidden)] +#[cfg(not(no_std))] +pub use macro_magic; + #[cfg(test)] pub mod tests { use super::*; - use crate::metadata::{ - PalletStorageMetadata, StorageEntryMetadata, StorageEntryModifier, StorageEntryType, - StorageHasher, + use crate::metadata_ir::{ + PalletStorageMetadataIR, StorageEntryMetadataIR, StorageEntryModifierIR, + StorageEntryTypeIR, StorageHasherIR, }; - use codec::{Codec, EncodeLike}; - use frame_support::traits::CrateVersion; use sp_io::{MultiRemovalResults, TestExternalities}; + use sp_runtime::{generic, traits::BlakeTwo256, BuildStorage}; use sp_std::result; - /// A PalletInfo implementation which just panics. - pub struct PanicPalletInfo; + pub use self::frame_system::{pallet_prelude::*, Config, Pallet}; - impl crate::traits::PalletInfo for PanicPalletInfo { - fn index() -> Option { - unimplemented!("PanicPalletInfo mustn't be triggered by tests"); + #[pallet] + pub mod frame_system { + #[allow(unused)] + use super::{frame_system, frame_system::pallet_prelude::*}; + pub use crate::dispatch::RawOrigin; + use crate::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: 'static { + type Block: Parameter + sp_runtime::traits::Block; + type AccountId; + type BaseCallFilter: crate::traits::Contains; + type RuntimeOrigin; + type RuntimeCall; + type PalletInfo: crate::traits::PalletInfo; + type DbWeight: Get; } - fn name() -> Option<&'static str> { - unimplemented!("PanicPalletInfo mustn't be triggered by tests"); + + #[pallet::error] + pub enum Error { + /// Required by construct_runtime + CallFiltered, } - fn module_name() -> Option<&'static str> { - unimplemented!("PanicPalletInfo mustn't be triggered by tests"); + + #[pallet::origin] + pub type Origin = RawOrigin<::AccountId>; + + #[pallet::call] + impl Pallet {} + + #[pallet::storage] + pub type Data = StorageMap<_, Twox64Concat, u32, u64, ValueQuery>; + + #[pallet::storage] + pub type OptionLinkedMap = StorageMap<_, Blake2_128Concat, u32, u32, OptionQuery>; + + #[pallet::storage] + #[pallet::getter(fn generic_data)] + pub type GenericData = + StorageMap<_, Identity, BlockNumberFor, BlockNumberFor, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn generic_data2)] + pub type GenericData2 = + StorageMap<_, Blake2_128Concat, BlockNumberFor, BlockNumberFor, OptionQuery>; + + #[pallet::storage] + pub type DataDM = + StorageDoubleMap<_, Twox64Concat, u32, Blake2_128Concat, u32, u64, ValueQuery>; + + #[pallet::storage] + pub type GenericDataDM = StorageDoubleMap< + _, + Blake2_128Concat, + BlockNumberFor, + Identity, + BlockNumberFor, + BlockNumberFor, + ValueQuery, + >; + + #[pallet::storage] + pub type GenericData2DM = StorageDoubleMap< + _, + Blake2_128Concat, + BlockNumberFor, + Twox64Concat, + BlockNumberFor, + BlockNumberFor, + OptionQuery, + >; + + #[pallet::storage] + #[pallet::unbounded] + pub type AppendableDM = StorageDoubleMap< + _, + Blake2_128Concat, + u32, + Blake2_128Concat, + BlockNumberFor, + Vec, + ValueQuery, + >; + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub data: Vec<(u32, u64)>, + pub test_config: Vec<(u32, u32, u64)>, + #[serde(skip)] + pub _config: sp_std::marker::PhantomData, } - fn crate_version() -> Option { - unimplemented!("PanicPalletInfo mustn't be triggered by tests"); + + impl Default for GenesisConfig { + fn default() -> Self { + Self { + _config: Default::default(), + data: vec![(15u32, 42u64)], + test_config: vec![(15u32, 16u32, 42u64)], + } + } } - } - pub trait Config: 'static { - type BlockNumber: Codec + EncodeLike + Default + TypeInfo; - type RuntimeOrigin; - type PalletInfo: crate::traits::PalletInfo; - type DbWeight: crate::traits::Get; - } + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + for (k, v) in &self.data { + >::insert(k, v); + } + for (k1, k2, v) in &self.test_config { + >::insert(k1, k2, v); + } + } + } - mod module { - #![allow(dead_code)] + pub mod pallet_prelude { + pub type OriginFor = ::RuntimeOrigin; - use super::Config; + pub type HeaderFor = + <::Block as sp_runtime::traits::HeaderProvider>::HeaderT; - decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self {} + pub type BlockNumberFor = as sp_runtime::traits::Header>::Number; } } - use self::module::Module; - - decl_storage! { - trait Store for Module as Test { - pub Data get(fn data) build(|_| vec![(15u32, 42u64)]): - map hasher(twox_64_concat) u32 => u64; - pub OptionLinkedMap: map hasher(blake2_128_concat) u32 => Option; - pub GenericData get(fn generic_data): - map hasher(identity) T::BlockNumber => T::BlockNumber; - pub GenericData2 get(fn generic_data2): - map hasher(blake2_128_concat) T::BlockNumber => Option; - pub DataDM config(test_config) build(|_| vec![(15u32, 16u32, 42u64)]): - double_map hasher(twox_64_concat) u32, hasher(blake2_128_concat) u32 => u64; - pub GenericDataDM: - double_map hasher(blake2_128_concat) T::BlockNumber, hasher(identity) T::BlockNumber - => T::BlockNumber; - pub GenericData2DM: - double_map hasher(blake2_128_concat) T::BlockNumber, hasher(twox_64_concat) T::BlockNumber - => Option; - pub AppendableDM: - double_map hasher(blake2_128_concat) u32, hasher(blake2_128_concat) T::BlockNumber => Vec; - } - } + type BlockNumber = u32; + type AccountId = u32; + type Header = generic::Header; + type UncheckedExtrinsic = generic::UncheckedExtrinsic; + type Block = generic::Block; - struct Test; + crate::construct_runtime!( + pub enum Runtime + { + System: self::frame_system, + } + ); - impl Config for Test { - type BlockNumber = u32; - type RuntimeOrigin = u32; - type PalletInfo = PanicPalletInfo; + impl Config for Runtime { + type Block = Block; + type AccountId = AccountId; + type BaseCallFilter = crate::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type PalletInfo = PalletInfo; type DbWeight = (); } fn new_test_ext() -> TestExternalities { - GenesisConfig::default().build_storage().unwrap().into() + RuntimeGenesisConfig::default().build_storage().unwrap().into() } - type Map = Data; - trait Sorted { fn sorted(self) -> Self; } @@ -924,31 +1006,80 @@ pub mod tests { fn storage_alias_works() { new_test_ext().execute_with(|| { #[crate::storage_alias] - type GenericData2 = StorageMap< - Test, - Blake2_128Concat, - ::BlockNumber, - ::BlockNumber, - >; + type GenericData2 = + StorageMap, BlockNumberFor>; - assert_eq!(Module::::generic_data2(5), None); - GenericData2::::insert(5, 5); - assert_eq!(Module::::generic_data2(5), Some(5)); + assert_eq!(Pallet::::generic_data2(5), None); + GenericData2::::insert(5, 5); + assert_eq!(Pallet::::generic_data2(5), Some(5)); /// Some random docs that ensure that docs are accepted #[crate::storage_alias] - pub type GenericData = StorageMap< - Test2, - Blake2_128Concat, - ::BlockNumber, - ::BlockNumber, - >; + pub type GenericData = + StorageMap, BlockNumberFor>; + }); + } + + #[test] + fn storage_value_mutate_exists_should_work() { + new_test_ext().execute_with(|| { + #[crate::storage_alias] + pub type Value = StorageValue; + + assert!(!Value::exists()); + + Value::mutate_exists(|v| *v = Some(1)); + assert!(Value::exists()); + assert_eq!(Value::get(), Some(1)); + + // removed if mutated to `None` + Value::mutate_exists(|v| *v = None); + assert!(!Value::exists()); + }); + } + + #[test] + fn storage_value_try_mutate_exists_should_work() { + new_test_ext().execute_with(|| { + #[crate::storage_alias] + pub type Value = StorageValue; + + type TestResult = result::Result<(), &'static str>; + + assert!(!Value::exists()); + + // mutated if `Ok` + assert_ok!(Value::try_mutate_exists(|v| -> TestResult { + *v = Some(1); + Ok(()) + })); + assert!(Value::exists()); + assert_eq!(Value::get(), Some(1)); + + // no-op if `Err` + assert_noop!( + Value::try_mutate_exists(|v| -> TestResult { + *v = Some(2); + Err("nah") + }), + "nah" + ); + assert_eq!(Value::get(), Some(1)); + + // removed if mutated to`None` + assert_ok!(Value::try_mutate_exists(|v| -> TestResult { + *v = None; + Ok(()) + })); + assert!(!Value::exists()); }); } #[test] fn map_issue_3318() { new_test_ext().execute_with(|| { + type OptionLinkedMap = self::frame_system::OptionLinkedMap; + OptionLinkedMap::insert(1, 1); assert_eq!(OptionLinkedMap::get(1), Some(1)); OptionLinkedMap::insert(1, 2); @@ -959,6 +1090,8 @@ pub mod tests { #[test] fn map_swap_works() { new_test_ext().execute_with(|| { + type OptionLinkedMap = self::frame_system::OptionLinkedMap; + OptionLinkedMap::insert(0, 0); OptionLinkedMap::insert(1, 1); OptionLinkedMap::insert(2, 2); @@ -988,6 +1121,8 @@ pub mod tests { #[test] fn double_map_swap_works() { new_test_ext().execute_with(|| { + type DataDM = self::frame_system::DataDM; + DataDM::insert(0, 1, 1); DataDM::insert(1, 0, 2); DataDM::insert(1, 1, 3); @@ -1020,6 +1155,8 @@ pub mod tests { #[test] fn map_basic_insert_remove_should_work() { new_test_ext().execute_with(|| { + type Map = self::frame_system::Data; + // initialized during genesis assert_eq!(Map::get(&15u32), 42u64); @@ -1046,6 +1183,8 @@ pub mod tests { #[test] fn map_iteration_should_work() { new_test_ext().execute_with(|| { + type Map = self::frame_system::Data; + assert_eq!(Map::iter().collect::>().sorted(), vec![(15, 42)]); // insert / remove let key = 17u32; @@ -1099,7 +1238,7 @@ pub mod tests { fn double_map_basic_insert_remove_remove_prefix_with_commit_should_work() { let key1 = 17u32; let key2 = 18u32; - type DoubleMap = DataDM; + type DoubleMap = self::frame_system::DataDM; let mut e = new_test_ext(); e.execute_with(|| { // initialized during genesis @@ -1144,7 +1283,7 @@ pub mod tests { new_test_ext().execute_with(|| { let key1 = 17u32; let key2 = 18u32; - type DoubleMap = DataDM; + type DoubleMap = self::frame_system::DataDM; // initialized during genesis assert_eq!(DoubleMap::get(&15u32, &16u32), 42u64); @@ -1192,7 +1331,7 @@ pub mod tests { #[test] fn double_map_append_should_work() { new_test_ext().execute_with(|| { - type DoubleMap = AppendableDM; + type DoubleMap = self::frame_system::AppendableDM; let key1 = 17u32; let key2 = 18u32; @@ -1206,7 +1345,7 @@ pub mod tests { #[test] fn double_map_mutate_exists_should_work() { new_test_ext().execute_with(|| { - type DoubleMap = DataDM; + type DoubleMap = self::frame_system::DataDM; let (key1, key2) = (11, 13); @@ -1223,8 +1362,8 @@ pub mod tests { #[test] fn double_map_try_mutate_exists_should_work() { new_test_ext().execute_with(|| { - type DoubleMap = DataDM; - type TestResult = result::Result<(), &'static str>; + type DoubleMap = self::frame_system::DataDM; + type TestResult = Result<(), &'static str>; let (key1, key2) = (11, 13); @@ -1253,94 +1392,100 @@ pub mod tests { }); } - fn expected_metadata() -> PalletStorageMetadata { - PalletStorageMetadata { - prefix: "Test", + fn expected_metadata() -> PalletStorageMetadataIR { + PalletStorageMetadataIR { + prefix: "System", entries: vec![ - StorageEntryMetadata { + StorageEntryMetadataIR { name: "Data", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Twox64Concat], + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Twox64Concat], key: scale_info::meta_type::(), value: scale_info::meta_type::(), }, default: vec![0, 0, 0, 0, 0, 0, 0, 0], docs: vec![], }, - StorageEntryMetadata { + StorageEntryMetadataIR { name: "OptionLinkedMap", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Blake2_128Concat], + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat], key: scale_info::meta_type::(), value: scale_info::meta_type::(), }, default: vec![0], docs: vec![], }, - StorageEntryMetadata { + StorageEntryMetadataIR { name: "GenericData", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Identity], + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Identity], key: scale_info::meta_type::(), value: scale_info::meta_type::(), }, default: vec![0, 0, 0, 0], docs: vec![], }, - StorageEntryMetadata { + StorageEntryMetadataIR { name: "GenericData2", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Blake2_128Concat], + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat], key: scale_info::meta_type::(), value: scale_info::meta_type::(), }, default: vec![0], docs: vec![], }, - StorageEntryMetadata { + StorageEntryMetadataIR { name: "DataDM", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Twox64Concat, StorageHasher::Blake2_128Concat], + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![ + StorageHasherIR::Twox64Concat, + StorageHasherIR::Blake2_128Concat, + ], key: scale_info::meta_type::<(u32, u32)>(), value: scale_info::meta_type::(), }, default: vec![0, 0, 0, 0, 0, 0, 0, 0], docs: vec![], }, - StorageEntryMetadata { + StorageEntryMetadataIR { name: "GenericDataDM", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Blake2_128Concat, StorageHasher::Identity], + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat, StorageHasherIR::Identity], key: scale_info::meta_type::<(u32, u32)>(), value: scale_info::meta_type::(), }, default: vec![0, 0, 0, 0], docs: vec![], }, - StorageEntryMetadata { + StorageEntryMetadataIR { name: "GenericData2DM", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Blake2_128Concat, StorageHasher::Twox64Concat], + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { + hashers: vec![ + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Twox64Concat, + ], key: scale_info::meta_type::<(u32, u32)>(), value: scale_info::meta_type::(), }, default: vec![0], docs: vec![], }, - StorageEntryMetadata { + StorageEntryMetadataIR { name: "AppendableDM", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { hashers: vec![ - StorageHasher::Blake2_128Concat, - StorageHasher::Blake2_128Concat, + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Blake2_128Concat, ], key: scale_info::meta_type::<(u32, u32)>(), value: scale_info::meta_type::>(), @@ -1354,7 +1499,7 @@ pub mod tests { #[test] fn store_metadata() { - let metadata = Module::::storage_metadata(); + let metadata = Pallet::::storage_metadata(); pretty_assertions::assert_eq!(expected_metadata(), metadata); } @@ -1373,18 +1518,27 @@ pub mod tests { assert_eq!(300, StorageParameter::get()); }) } +} - parameter_types! { - pub const BlockHashCount: u64 = 250; - pub static Members: Vec = vec![]; - pub const Foo: Option = None; - } +/// Private module re-exporting items used by frame support macros. +#[doc(hidden)] +pub mod _private { + pub use sp_inherents; +} + +/// Prelude to be used for pallet testing, for ease of use. +#[cfg(feature = "std")] +pub mod testing_prelude { + pub use super::{ + assert_err, assert_err_ignore_postinfo, assert_err_with_weight, assert_error_encoded_size, + assert_noop, assert_ok, assert_storage_noop, bounded_btree_map, bounded_vec, + parameter_types, traits::Get, + }; + pub use sp_arithmetic::assert_eq_error_rate; } /// Prelude to be used alongside pallet macro, for ease of use. pub mod pallet_prelude { - #[cfg(feature = "std")] - pub use crate::traits::GenesisBuild; pub use crate::{ dispatch::{ DispatchClass, DispatchError, DispatchResult, DispatchResultWithPostInfo, Parameter, @@ -1401,15 +1555,17 @@ pub mod pallet_prelude { }, }, traits::{ - ConstU32, EnsureOrigin, Get, GetDefault, GetStorageVersion, Hooks, IsType, - PalletInfoAccess, StorageInfoTrait, StorageVersion, TypedGet, + BuildGenesisConfig, ConstU32, EnsureOrigin, Get, GetDefault, GetStorageVersion, Hooks, + IsType, PalletInfoAccess, StorageInfoTrait, StorageVersion, TypedGet, }, Blake2_128, Blake2_128Concat, Blake2_256, CloneNoBound, DebugNoBound, EqNoBound, Identity, PartialEqNoBound, RuntimeDebug, RuntimeDebugNoBound, Twox128, Twox256, Twox64Concat, }; pub use codec::{Decode, Encode, MaxEncodedLen}; pub use frame_support::pallet_macros::*; + pub use frame_support_procedural::register_default_impl; pub use scale_info::TypeInfo; + pub use sp_inherents::MakeFatalError; pub use sp_runtime::{ traits::{MaybeSerializeDeserialize, Member, ValidateUnsigned}, transaction_validity::{ @@ -1458,7 +1614,6 @@ pub mod pallet_prelude { /// * [`pallet::constant`](#palletconstant) /// * [`pallet::disable_frame_system_supertrait_check`](#disable_supertrait_check) /// * [`pallet::generate_store($vis trait Store)`](#palletgenerate_storevis-trait-store) -/// * [`pallet::generate_storage_info`](#palletgenerate_storage_info) /// * [`pallet::storage_version`](#palletstorage_version) /// * [`pallet::hooks`](#hooks-pallethooks-optional) /// * [`pallet::call`](#call-palletcall-optional) @@ -1482,6 +1637,7 @@ pub mod pallet_prelude { /// * [`pallet::inherent`](#inherent-palletinherent-optional) /// * [`pallet::validate_unsigned`](#validate-unsigned-palletvalidate_unsigned-optional) /// * [`pallet::origin`](#origin-palletorigin-optional) +/// * [`pallet::composite_enum`](#composite-enum-palletcomposite_enum-optional) /// /// Note that at compile-time, the `#[pallet]` macro will analyze and expand all of these /// attributes, ultimately removing their AST nodes before they can be parsed as real @@ -1661,24 +1817,6 @@ pub mod pallet_prelude { /// /// Also see [`pallet::generate_store`](`frame_support::pallet_macros::generate_store`). /// -/// # `pallet::generate_storage_info` -/// -/// To generate the full storage info (used for PoV calculation) use the attribute -/// `#[pallet::generate_storage_info]`, e.g.: -/// -/// ```ignore -/// #[pallet::pallet] -/// #[pallet::generate_storage_info] -/// pub struct Pallet(_); -/// ``` -/// -/// This requires all storage items to implement the trait [`traits::StorageInfoTrait`], thus -/// all keys and value types must be bound by [`pallet_prelude::MaxEncodedLen`]. Individual -/// storages can opt-out from this constraint by using `#[pallet::unbounded]` (see -/// `#[pallet::storage]` for more info). -/// -/// Also see [`pallet::generate_storage_info`](`frame_support::pallet_macros::generate_storage_info`) -/// /// # `pallet::storage_version` /// /// Because the [`pallet::pallet`](#pallet-struct-placeholder-palletpallet-mandatory) macro @@ -2092,7 +2230,7 @@ pub mod pallet_prelude { /// for the pallet. /// /// Item is defined as either an enum or a struct. It needs to be public and implement the -/// trait [`GenesisBuild`](`traits::GenesisBuild`) with +/// trait [`BuildGenesisConfig`](`traits::BuildGenesisConfig`) with /// [`#[pallet::genesis_build]`](#genesis-build-palletgenesis_build-optional). The type /// generics are constrained to be either none, or `T` or `T: Config`. /// @@ -2207,6 +2345,29 @@ pub mod pallet_prelude { /// /// Also see [`pallet::origin`](`frame_support::pallet_macros::origin`) /// +/// # Composite enum `#[pallet::composite_enum]` (optional) +/// +/// The `#[pallet::composite_enum]` attribute allows you to define an enum on the pallet which +/// will then instruct `construct_runtime` to amalgamate all similarly-named enums from other +/// pallets into an aggregate enum. This is similar in principle with how the aggregate enum is +/// generated for `#[pallet::event]` or `#[pallet::error]`. +/// +/// The item tagged with `#[pallet::composite_enum]` MUST be an enum declaration, and can ONLY +/// be the following identifiers: `FreezeReason`, `HoldReason`, `LockId` or `SlashReason`. +/// Custom identifiers are not supported. +/// +/// NOTE: For ease of usage, when no `#[derive]` attributes are detected, the +/// `#[pallet::composite_enum]` attribute will automatically derive the following traits for +/// the enum: +/// +/// ```ignore +/// Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, TypeInfo, +/// RuntimeDebug +/// ``` +/// +/// The inverse is also true: if there are any #[derive] attributes present for the enum, then +/// the attribute will not automatically derive any of the traits described above. +/// /// # General notes on instantiable pallets /// /// An instantiable pallet is one where Config is generic, i.e. `Config`. This allows @@ -2351,14 +2512,15 @@ pub mod pallet_prelude { /// // /// // Type must implement the `Default` trait. /// #[pallet::genesis_config] -/// #[derive(Default)] -/// pub struct GenesisConfig { +/// #[derive(frame_support::DefaultNoBound)] +/// pub struct GenesisConfig { +/// _config: sp_std::marker::PhantomData, /// _myfield: u32, /// } /// /// // Declare genesis builder. (This is need only if GenesisConfig is declared) /// #[pallet::genesis_build] -/// impl GenesisBuild for GenesisConfig { +/// impl BuildGenesisConfig for GenesisConfig { /// fn build(&self) {} /// } /// @@ -2437,7 +2599,7 @@ pub mod pallet_prelude { /// #[pallet::extra_constants] /// impl, I: 'static> Pallet { /// /// Some description -/// fn exra_constant_name() -> u128 { 4u128 } +/// fn extra_constant_name() -> u128 { 4u128 } /// } /// /// #[pallet::pallet] @@ -2488,13 +2650,14 @@ pub mod pallet_prelude { /// StorageMap; /// /// #[pallet::genesis_config] -/// #[derive(Default)] -/// pub struct GenesisConfig { +/// #[derive(frame_support::DefaultNoBound)] +/// pub struct GenesisConfig, I: 'static = ()> { +/// _config: sp_std::marker::PhantomData<(T,I)>, /// _myfield: u32, /// } /// /// #[pallet::genesis_build] -/// impl, I: 'static> GenesisBuild for GenesisConfig { +/// impl, I: 'static> BuildGenesisConfig for GenesisConfig { /// fn build(&self) {} /// } /// @@ -2552,13 +2715,13 @@ pub mod pallet_prelude { /// - query the metadata using the `state_getMetadata` RPC and curl, or use `subsee -p /// > meta.json` /// 2. Generate the template upgrade for the pallet provided by `decl_storage` with the -/// environment variable `PRINT_PALLET_UPGRADE`: `PRINT_PALLET_UPGRADE=1 cargo check -p -/// my_pallet`. This template can be used as it contains all information for storages, -/// genesis config and genesis build. +/// environment variable `PRINT_PALLET_UPGRADE`: `PRINT_PALLET_UPGRADE=1 cargo check -p +/// my_pallet`. This template can be used as it contains all information for storages, +/// genesis config and genesis build. /// 3. Reorganize the pallet to have the trait `Config`, `decl_*` macros, -/// [`ValidateUnsigned`](`pallet_prelude::ValidateUnsigned`), -/// [`ProvideInherent`](`pallet_prelude::ProvideInherent`), and Origin` all together in one -/// file. Suggested order: +/// [`ValidateUnsigned`](`pallet_prelude::ValidateUnsigned`), +/// [`ProvideInherent`](`pallet_prelude::ProvideInherent`), and Origin` all together in one +/// file. Suggested order: /// * `Config`, /// * `decl_module`, /// * `decl_event`, @@ -2618,8 +2781,8 @@ pub mod pallet_prelude { /// 8. **migrate error**: rewrite it with attribute /// [`#[pallet::error]`](#error-palleterror-optional). /// 9. **migrate storage**: `decl_storage` provide an upgrade template (see 3.). All storages, -/// genesis config, genesis build and default implementation of genesis config can be -/// taken from it directly. +/// genesis config, genesis build and default implementation of genesis config can be taken +/// from it directly. /// /// Otherwise here is the manual process: /// @@ -2738,235 +2901,16 @@ pub use frame_support_procedural::pallet; /// Contains macro stubs for all of the pallet:: macros pub mod pallet_macros { pub use frame_support_procedural::{ - call_index, compact, config, constant, disable_frame_system_supertrait_check, error, event, - extra_constants, generate_deposit, generate_storage_info, generate_store, genesis_build, - genesis_config, getter, hooks, inherent, origin, storage, storage_prefix, storage_version, - type_value, unbounded, validate_unsigned, weight, whitelist_storage, + call_index, compact, composite_enum, config, constant, + disable_frame_system_supertrait_check, error, event, extra_constants, generate_deposit, + generate_store, genesis_build, genesis_config, getter, hooks, import_section, inherent, + no_default, origin, pallet_section, storage, storage_prefix, storage_version, type_value, + unbounded, validate_unsigned, weight, whitelist_storage, }; } -/// Contains macros, structs, and traits associated with v2 of the pallet benchmarking syntax. -/// This module contains macros, structs, and traits associated with v2 of the pallet -/// benchmarking syntax. -/// -/// The [`benchmarking::benchmarks`] and [`benchmarking::instance_benchmarks`] macros can be -/// used to designate a module as a benchmarking module that can contain benchmarks and -/// benchmark tests. The `#[benchmarks]` variant will set up a regular, non-instance -/// benchmarking module, and the `#[instance_benchmarks]` variant will set up the module in -/// instance benchmarking mode. -/// -/// Benchmarking modules should be gated behind a `#[cfg(feature = "runtime-benchmarks")]` -/// feature gate to ensure benchmarking code that is only compiled when the -/// `runtime-benchmarks` feature is enabled is not referenced. -/// -/// The following is the general syntax for a benchmarks (or instance benchmarks) module: -/// -/// ## General Syntax -/// -/// ```ignore -/// #![cfg(feature = "runtime-benchmarks")] -/// -/// use super::{mock_helpers::*, Pallet as MyPallet}; -/// use frame_support::benchmarking::*; -/// use frame_benchmarking::whitelisted_caller; -/// -/// #[benchmarks] -/// mod benchmarks { -/// use super::*; -/// -/// #[benchmark] -/// fn bench_name_1(x: Linear<7, 1_000>, y: Linear<1_000, 100_0000>) { -/// // setup code -/// let z = x + y; -/// let caller = whitelisted_caller(); -/// -/// #[extrinsic_call] -/// extrinsic_name(SystemOrigin::Signed(caller), other, arguments); -/// -/// // verification code -/// assert_eq!(MyPallet::::my_var(), z); -/// } -/// -/// #[benchmark] -/// fn bench_name_2() { -/// // setup code -/// let caller = whitelisted_caller(); -/// -/// #[block] -/// { -/// something(some, thing); -/// my_extrinsic(RawOrigin::Signed(caller), some, argument); -/// something_else(foo, bar); -/// } -/// -/// // verification code -/// assert_eq!(MyPallet::::something(), 37); -/// } -/// } -/// ``` -/// -/// ## Benchmark Definitions -/// -/// Within a `#[benchmarks]` or `#[instance_benchmarks]` module, you can define individual -/// benchmarks using the `#[benchmark]` attribute, as shown in the example above. -/// -/// The `#[benchmark]` attribute expects a function definition with a blank return type and -/// zero or more arguments whose names are valid `frame_benchmarking::BenchmarkParamater` -/// parameters, such as `x`, `y`, `a`, `b`, etc., and whose param types must implement -/// [`benchmarking::ParamRange`]. At the moment the only valid type that implements -/// [`benchmarking::ParamRange`] is [`benchmarking::Linear`]. -/// -/// The valid syntax for defining a `Linear` is `Linear` where `A`, and `B` are -/// valid integer literals (that fit in a `u32`), such that `B` >= `A`. -/// -/// Note that the benchmark function definition does not actually expand as a function -/// definition, but rather is used to automatically create a number of impls and structs -/// required by the benchmarking engine. For this reason, the visibility of the function -/// definition as well as the return type are not used for any purpose and are discarded by the -/// expansion code. -/// -/// Also note that the `// setup code` and `// verification code` comments shown above are not -/// required and are included simply for demonstration purposes. -/// -/// ### `#[extrinsic_call]` and `#[block]` -/// -/// Within the benchmark function body, either an `#[extrinsic_call]` or a `#[block]` -/// annotation is required. These attributes should be attached to a block (shown in -/// `bench_name_2` above) or a one-line function call (shown in `bench_name_1` above, in `syn` -/// parlance this should be an `ExprCall`), respectively. -/// -/// The `#[block]` syntax is broad and will benchmark any code contained within the block the -/// attribute is attached to. If `#[block]` is attached to something other than a block, a -/// compiler error will be emitted. -/// -/// The one-line `#[extrinsic_call]` syntax must consist of a function call to an extrinsic, -/// where the first argument is the origin. If `#[extrinsic_call]` is attached to an item that -/// doesn't meet these requirements, a compiler error will be emitted. -/// -/// As a short-hand, you may substitute the name of the extrinsic call with `_`, such as the -/// following: -/// -/// ```ignore -/// #[extrinsic_call] -/// _(RawOrigin::Signed(whitelisted_caller()), 0u32.into(), 0); -/// ``` -/// -/// The underscore will be substituted with the name of the benchmark (i.e. the name of the -/// function in the benchmark function definition). -/// -/// Regardless of whether `#[extrinsic_call]` or `#[block]` is used, this attribute also serves -/// the purpose of designating the boundary between the setup code portion of the benchmark -/// (everything before the `#[extrinsic_call]` or `#[block]` attribute) and the verification -/// stage (everything after the item that the `#[extrinsic_call]` or `#[block]` attribute is -/// attached to). The setup code section should contain any code that needs to execute before -/// the measured portion of the benchmark executes. The verification section is where you can -/// perform assertions to verify that the extrinsic call (or whatever is happening in your -/// block, if you used the `#[block]` syntax) executed successfully. -/// -/// Note that neither `#[extrinsic_call]` nor `#[block]` are real attribute macros and are -/// instead consumed by the outer macro pattern as part of the enclosing benchmark function -/// definition. This is why we are able to use `#[extrinsic_call]` and `#[block]` within a -/// function definition even though this behavior has not been stabilized -/// yet—`#[extrinsic_call]` and `#[block]` are parsed and consumed as part of the benchmark -/// definition parsing code, so they never expand as their own attribute macros. -/// -/// ### Optional Attributes -/// -/// The keywords `extra` and `skip_meta` can be provided as optional arguments to the -/// `#[benchmark]` attribute, i.e. `#[benchmark(extra, skip_meta)]`. Including either of these -/// will enable the `extra` or `skip_meta` option, respectively. These options enable the same -/// behavior they did in the old benchmarking syntax in `frame_benchmarking`, namely: -/// -/// #### `extra` -/// -/// Specifies that this benchmark should not normally run. To run benchmarks marked with -/// `extra`, you will need to invoke the `frame-benchmarking-cli` with `--extra`. -/// -/// #### `skip_meta` -/// -/// Specifies that the benchmarking framework should not analyze the storage keys that -/// benchmarked code read or wrote. This useful to suppress the prints in the form of unknown -/// 0x… in case a storage key that does not have metadata. Note that this skips the analysis -/// of all accesses, not just ones without metadata. -/// -/// ## Where Clause -/// -/// Some pallets require a where clause specifying constraints on their generics to make -/// writing benchmarks feasible. To accomodate this situation, you can provide such a where -/// clause as the (only) argument to the `#[benchmarks]` or `#[instance_benchmarks]` attribute -/// macros. Below is an example of this taken from the `message-queue` pallet. -/// -/// ```ignore -/// #[benchmarks( -/// where -/// <::MessageProcessor as ProcessMessage>::Origin: From + PartialEq, -/// ::Size: From, -/// )] -/// mod benchmarks { -/// use super::*; -/// // ... -/// } -/// ``` -/// -/// ## Benchmark Tests -/// -/// Benchmark tests can be generated using the old syntax in `frame_benchmarking`, -/// including the `frame_benchmarking::impl_benchmark_test_suite` macro. -/// -/// An example is shown below (taken from the `message-queue` pallet's `benchmarking` module): -/// ```ignore -/// #[benchmarks] -/// mod benchmarks { -/// use super::*; -/// // ... -/// impl_benchmark_test_suite!( -/// MessageQueue, -/// crate::mock::new_test_ext::(), -/// crate::integration_test::Test -/// ); -/// } -/// ``` -pub mod benchmarking { - pub use frame_support_procedural::{ - benchmark, benchmarks, block, extrinsic_call, instance_benchmarks, - }; - - // Used in #[benchmark] implementation to ensure that benchmark function arguments - // implement [`ParamRange`]. - #[doc(hidden)] - pub use static_assertions::assert_impl_all; - - /// Used by the new benchmarking code to specify that a benchmarking variable is linear - /// over some specified range, i.e. `Linear<0, 1_000>` means that the corresponding variable - /// is allowed to range from `0` to `1000`, inclusive. - /// - /// See [`frame_support::benchmarking`] for more info. - pub struct Linear; - - /// Trait that must be implemented by all structs that can be used as parameter range types - /// in the new benchmarking code (i.e. `Linear<0, 1_000>`). Right now there is just - /// [`Linear`] but this could later be extended to support additional non-linear parameter - /// ranges. - /// - /// See [`frame_support::benchmarking`] for more info. - pub trait ParamRange { - /// Represents the (inclusive) starting number of this [`ParamRange`]. - fn start(&self) -> u32; - - /// Represents the (inclusive) ending number of this [`ParamRange`] - fn end(&self) -> u32; - } - - impl ParamRange for Linear { - fn start(&self) -> u32 { - return A - } - - fn end(&self) -> u32 { - return B - } - } -} +#[doc(inline)] +pub use frame_support_procedural::register_default_impl; // Generate a macro that will enable/disable code based on `std` feature being active. sp_core::generate_feature_enabled_macro!(std_enabled, feature = "std", $); diff --git a/frame/support/src/migrations.rs b/frame/support/src/migrations.rs index 63679fd56d66..19eec194a76a 100644 --- a/frame/support/src/migrations.rs +++ b/frame/support/src/migrations.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,17 +16,189 @@ // limitations under the License. use crate::{ - traits::{GetStorageVersion, PalletInfoAccess}, + traits::{GetStorageVersion, NoStorageVersionSet, PalletInfoAccess, StorageVersion}, weights::{RuntimeDbWeight, Weight}, }; use impl_trait_for_tuples::impl_for_tuples; +use sp_core::Get; +use sp_io::{hashing::twox_128, storage::clear_prefix, KillStorageResult}; +use sp_std::marker::PhantomData; + +/// EXPERIMENTAL: The API of this feature may change. +/// +/// Make it easier to write versioned runtime upgrades. +/// +/// [`VersionedRuntimeUpgrade`] allows developers to write migrations without worrying about +/// checking and setting storage versions. Instead, the developer wraps their migration in this +/// struct which takes care of version handling using best practices. +/// +/// It takes 5 type parameters: +/// - `From`: The version being upgraded from. +/// - `To`: The version being upgraded to. +/// - `Inner`: An implementation of `OnRuntimeUpgrade`. +/// - `Pallet`: The Pallet being upgraded. +/// - `Weight`: The runtime's RuntimeDbWeight implementation. +/// +/// When a [`VersionedRuntimeUpgrade`] `on_runtime_upgrade`, `pre_upgrade`, or `post_upgrade` +/// method is called, the on-chain version of the pallet is compared to `From`. If they match, the +/// `Inner` equivalent is called and the pallets on-chain version is set to `To` after the +/// migration. Otherwise, a warning is logged notifying the developer that the upgrade was a noop +/// and should probably be removed. +/// +/// ### Examples +/// ```ignore +/// // In file defining migrations +/// pub struct VersionUncheckedMigrateV5ToV6(sp_std::marker::PhantomData); +/// impl OnRuntimeUpgrade for VersionUncheckedMigrateV5ToV6 { +/// // OnRuntimeUpgrade implementation... +/// } +/// +/// pub type VersionCheckedMigrateV5ToV6 = +/// VersionedRuntimeUpgrade< +/// 5, +/// 6, +/// VersionUncheckedMigrateV5ToV6, +/// crate::pallet::Pallet, +/// ::DbWeight +/// >; +/// +/// // Migrations tuple to pass to the Executive pallet: +/// pub type Migrations = ( +/// // other migrations... +/// VersionCheckedMigrateV5ToV6, +/// // other migrations... +/// ); +/// ``` +#[cfg(feature = "experimental")] +pub struct VersionedRuntimeUpgrade { + _marker: PhantomData<(Inner, Pallet, Weight)>, +} + +/// A helper enum to wrap the pre_upgrade bytes like an Option before passing them to post_upgrade. +/// This enum is used rather than an Option to make the API clearer to the developer. +#[cfg(feature = "experimental")] +#[derive(codec::Encode, codec::Decode)] +pub enum VersionedPostUpgradeData { + /// The migration ran, inner vec contains pre_upgrade data. + MigrationExecuted(sp_std::vec::Vec), + /// This migration is a noop, do not run post_upgrade checks. + Noop, +} + +/// Implementation of the `OnRuntimeUpgrade` trait for `VersionedRuntimeUpgrade`. +/// +/// Its main function is to perform the runtime upgrade in `on_runtime_upgrade` only if the on-chain +/// version of the pallets storage matches `From`, and after the upgrade set the on-chain storage to +/// `To`. If the versions do not match, it writes a log notifying the developer that the migration +/// is a noop. +#[cfg(feature = "experimental")] +impl< + const FROM: u16, + const TO: u16, + Inner: crate::traits::OnRuntimeUpgrade, + Pallet: GetStorageVersion + PalletInfoAccess, + DbWeight: Get, + > crate::traits::OnRuntimeUpgrade for VersionedRuntimeUpgrade +{ + /// Executes pre_upgrade if the migration will run, and wraps the pre_upgrade bytes in + /// [`VersionedPostUpgradeData`] before passing them to post_upgrade, so it knows whether the + /// migration ran or not. + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + use codec::Encode; + let on_chain_version = Pallet::on_chain_storage_version(); + if on_chain_version == FROM { + Ok(VersionedPostUpgradeData::MigrationExecuted(Inner::pre_upgrade()?).encode()) + } else { + Ok(VersionedPostUpgradeData::Noop.encode()) + } + } + + /// Executes the versioned runtime upgrade. + /// + /// First checks if the pallets on-chain storage version matches the version of this upgrade. If + /// it matches, it calls `Inner::on_runtime_upgrade`, updates the on-chain version, and returns + /// the weight. If it does not match, it writes a log notifying the developer that the migration + /// is a noop. + fn on_runtime_upgrade() -> Weight { + let on_chain_version = Pallet::on_chain_storage_version(); + if on_chain_version == FROM { + log::info!( + "Running {} VersionedOnRuntimeUpgrade: version {:?} to {:?}.", + Pallet::name(), + FROM, + TO + ); + + // Execute the migration + let weight = Inner::on_runtime_upgrade(); + + // Update the on-chain version + StorageVersion::new(TO).put::(); + + weight.saturating_add(DbWeight::get().reads_writes(1, 1)) + } else { + log::warn!( + "{} VersionedOnRuntimeUpgrade for version {:?} skipped because current on-chain version is {:?}.", + Pallet::name(), + FROM, + on_chain_version + ); + DbWeight::get().reads(1) + } + } + + /// Executes `Inner::post_upgrade` if the migration just ran. + /// + /// pre_upgrade passes [`VersionedPostUpgradeData::MigrationExecuted`] to post_upgrade if + /// the migration ran, and [`VersionedPostUpgradeData::Noop`] otherwise. + #[cfg(feature = "try-runtime")] + fn post_upgrade( + versioned_post_upgrade_data_bytes: sp_std::vec::Vec, + ) -> Result<(), sp_runtime::TryRuntimeError> { + use codec::DecodeAll; + match ::decode_all(&mut &versioned_post_upgrade_data_bytes[..]) + .map_err(|_| "VersionedRuntimeUpgrade post_upgrade failed to decode PreUpgradeData")? + { + VersionedPostUpgradeData::MigrationExecuted(inner_bytes) => + Inner::post_upgrade(inner_bytes), + VersionedPostUpgradeData::Noop => Ok(()), + } + } +} + +/// Can store the current pallet version in storage. +pub trait StoreCurrentStorageVersion { + /// Write the current storage version to the storage. + fn store_current_storage_version(); +} + +impl + PalletInfoAccess> + StoreCurrentStorageVersion for StorageVersion +{ + fn store_current_storage_version() { + let version = ::current_storage_version(); + version.put::(); + } +} + +impl + PalletInfoAccess> + StoreCurrentStorageVersion for NoStorageVersionSet +{ + fn store_current_storage_version() { + StorageVersion::default().put::(); + } +} /// Trait used by [`migrate_from_pallet_version_to_storage_version`] to do the actual migration. pub trait PalletVersionToStorageVersionHelper { fn migrate(db_weight: &RuntimeDbWeight) -> Weight; } -impl PalletVersionToStorageVersionHelper for T { +impl PalletVersionToStorageVersionHelper for T +where + T::CurrentStorageVersion: StoreCurrentStorageVersion, +{ fn migrate(db_weight: &RuntimeDbWeight) -> Weight { const PALLET_VERSION_STORAGE_KEY_POSTFIX: &[u8] = b":__PALLET_VERSION__:"; @@ -36,8 +208,8 @@ impl PalletVersionToStorageVersionHelpe sp_io::storage::clear(&pallet_version_key(::name())); - let version = ::current_storage_version(); - version.put::(); + >::store_current_storage_version( + ); db_weight.writes(2) } @@ -67,3 +239,113 @@ pub fn migrate_from_pallet_version_to_storage_version< ) -> Weight { Pallets::migrate(db_weight) } + +/// `RemovePallet` is a utility struct used to remove all storage items associated with a specific +/// pallet. +/// +/// This struct is generic over two parameters: +/// - `P` is a type that implements the `Get` trait for a static string, representing the pallet's +/// name. +/// - `DbWeight` is a type that implements the `Get` trait for `RuntimeDbWeight`, providing the +/// weight for database operations. +/// +/// On runtime upgrade, the `on_runtime_upgrade` function will clear all storage items associated +/// with the specified pallet, logging the number of keys removed. If the `try-runtime` feature is +/// enabled, the `pre_upgrade` and `post_upgrade` functions can be used to verify the storage +/// removal before and after the upgrade. +/// +/// # Examples: +/// ```ignore +/// construct_runtime! { +/// pub enum Runtime +/// { +/// System: frame_system::{Pallet, Call, Storage, Config, Event} = 0, +/// +/// SomePalletToRemove: pallet_something::{Pallet, Call, Storage, Event} = 1, +/// AnotherPalletToRemove: pallet_something_else::{Pallet, Call, Storage, Event} = 2, +/// +/// YourOtherPallets... +/// } +/// }; +/// +/// parameter_types! { +/// pub const SomePalletToRemoveStr: &'static str = "SomePalletToRemove"; +/// pub const AnotherPalletToRemoveStr: &'static str = "AnotherPalletToRemove"; +/// } +/// +/// pub type Migrations = ( +/// RemovePallet, +/// RemovePallet, +/// AnyOtherMigrations... +/// ); +/// +/// pub type Executive = frame_executive::Executive< +/// Runtime, +/// Block, +/// frame_system::ChainContext, +/// Runtime, +/// Migrations +/// >; +/// ``` +/// +/// WARNING: `RemovePallet` has no guard rails preventing it from bricking the chain if the +/// operation of removing storage for the given pallet would exceed the block weight limit. +/// +/// If your pallet has too many keys to be removed in a single block, it is advised to wait for +/// a multi-block scheduler currently under development which will allow for removal of storage +/// items (and performing other heavy migrations) over multiple blocks +/// (see ). +pub struct RemovePallet, DbWeight: Get>( + PhantomData<(P, DbWeight)>, +); +impl, DbWeight: Get> frame_support::traits::OnRuntimeUpgrade + for RemovePallet +{ + fn on_runtime_upgrade() -> frame_support::weights::Weight { + let hashed_prefix = twox_128(P::get().as_bytes()); + let keys_removed = match clear_prefix(&hashed_prefix, None) { + KillStorageResult::AllRemoved(value) => value, + KillStorageResult::SomeRemaining(value) => { + log::error!( + "`clear_prefix` failed to remove all keys for {}. THIS SHOULD NEVER HAPPEN! 🚨", + P::get() + ); + value + }, + } as u64; + + log::info!("Removed {} {} keys 🧹", keys_removed, P::get()); + + DbWeight::get().reads_writes(keys_removed + 1, keys_removed) + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + use crate::storage::unhashed::contains_prefixed_key; + + let hashed_prefix = twox_128(P::get().as_bytes()); + match contains_prefixed_key(&hashed_prefix) { + true => log::info!("Found {} keys pre-removal 👀", P::get()), + false => log::warn!( + "Migration RemovePallet<{}> can be removed (no keys found pre-removal).", + P::get() + ), + }; + Ok(sp_std::vec::Vec::new()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: sp_std::vec::Vec) -> Result<(), sp_runtime::TryRuntimeError> { + use crate::storage::unhashed::contains_prefixed_key; + + let hashed_prefix = twox_128(P::get().as_bytes()); + match contains_prefixed_key(&hashed_prefix) { + true => { + log::error!("{} has keys remaining post-removal ❗", P::get()); + return Err("Keys remaining post-removal, this should never happen 🚨".into()) + }, + false => log::info!("No {} keys found post-removal 🎉", P::get()), + }; + Ok(()) + } +} diff --git a/frame/support/src/storage/bounded_btree_map.rs b/frame/support/src/storage/bounded_btree_map.rs index d567faa38fdc..f2f32d890b87 100644 --- a/frame/support/src/storage/bounded_btree_map.rs +++ b/frame/support/src/storage/bounded_btree_map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/storage/bounded_btree_set.rs b/frame/support/src/storage/bounded_btree_set.rs index 9ed129e67c46..52be1bb99f10 100644 --- a/frame/support/src/storage/bounded_btree_set.rs +++ b/frame/support/src/storage/bounded_btree_set.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index 1fa01b44ae6c..59b716b3ae5c 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/storage/child.rs b/frame/support/src/storage/child.rs index 397daaa82a67..76b9c554d65f 100644 --- a/frame/support/src/storage/child.rs +++ b/frame/support/src/storage/child.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/storage/generator/double_map.rs b/frame/support/src/storage/generator/double_map.rs index c95dcee9d7e5..00a3f1bc7c1c 100644 --- a/frame/support/src/storage/generator/double_map.rs +++ b/frame/support/src/storage/generator/double_map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,7 @@ use crate::{ Never, }; use codec::{Decode, Encode, EncodeLike, FullCodec, FullEncode}; -use sp_std::{borrow::Borrow, prelude::*}; +use sp_std::prelude::*; /// Generator for `StorageDoubleMap` used by `decl_storage`. /// @@ -78,7 +78,7 @@ pub trait StorageDoubleMap { KArg1: EncodeLike, { let storage_prefix = storage_prefix(Self::module_prefix(), Self::storage_prefix()); - let key_hashed = k1.borrow().using_encoded(Self::Hasher1::hash); + let key_hashed = k1.using_encoded(Self::Hasher1::hash); let mut final_key = Vec::with_capacity(storage_prefix.len() + key_hashed.as_ref().len()); @@ -95,8 +95,8 @@ pub trait StorageDoubleMap { KArg2: EncodeLike, { let storage_prefix = storage_prefix(Self::module_prefix(), Self::storage_prefix()); - let key1_hashed = k1.borrow().using_encoded(Self::Hasher1::hash); - let key2_hashed = k2.borrow().using_encoded(Self::Hasher2::hash); + let key1_hashed = k1.using_encoded(Self::Hasher1::hash); + let key2_hashed = k2.using_encoded(Self::Hasher2::hash); let mut final_key = Vec::with_capacity( storage_prefix.len() + key1_hashed.as_ref().len() + key2_hashed.as_ref().len(), @@ -198,7 +198,7 @@ where KArg2: EncodeLike, VArg: EncodeLike, { - unhashed::put(&Self::storage_double_map_final_key(k1, k2), &val.borrow()) + unhashed::put(&Self::storage_double_map_final_key(k1, k2), &val) } fn remove(k1: KArg1, k2: KArg2) @@ -233,6 +233,13 @@ where .into() } + fn contains_prefix(k1: KArg1) -> bool + where + KArg1: EncodeLike, + { + unhashed::contains_prefixed_key(Self::storage_double_map_final_key1(k1).as_ref()) + } + fn iter_prefix_values(k1: KArg1) -> storage::PrefixIterator where KArg1: ?Sized + EncodeLike, @@ -329,8 +336,8 @@ where let old_key = { let storage_prefix = storage_prefix(Self::module_prefix(), Self::storage_prefix()); - let key1_hashed = key1.borrow().using_encoded(OldHasher1::hash); - let key2_hashed = key2.borrow().using_encoded(OldHasher2::hash); + let key1_hashed = key1.using_encoded(OldHasher1::hash); + let key2_hashed = key2.using_encoded(OldHasher2::hash); let mut final_key = Vec::with_capacity( storage_prefix.len() + key1_hashed.as_ref().len() + key2_hashed.as_ref().len(), @@ -507,43 +514,12 @@ where mod test_iterators { use crate::{ hash::StorageHasher, - storage::{generator::StorageDoubleMap, unhashed, IterableStorageDoubleMap}, + storage::{ + generator::{tests::*, StorageDoubleMap}, + unhashed, + }, }; - use codec::{Decode, Encode}; - - pub trait Config: 'static { - type RuntimeOrigin; - type BlockNumber; - type PalletInfo: crate::traits::PalletInfo; - type DbWeight: crate::traits::Get; - } - - crate::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self {} - } - - #[derive(PartialEq, Eq, Clone, Encode, Decode)] - struct NoDef(u32); - - crate::decl_storage! { - trait Store for Module as Test { - DoubleMap: double_map hasher(blake2_128_concat) u16, hasher(twox_64_concat) u32 => u64; - } - } - - fn key_before_prefix(mut prefix: Vec) -> Vec { - let last = prefix.iter_mut().last().unwrap(); - assert!(*last != 0, "mock function not implemented for this prefix"); - *last -= 1; - prefix - } - - fn key_after_prefix(mut prefix: Vec) -> Vec { - let last = prefix.iter_mut().last().unwrap(); - assert!(*last != 255, "mock function not implemented for this prefix"); - *last += 1; - prefix - } + use codec::Encode; #[test] fn double_map_iter_from() { @@ -582,6 +558,8 @@ mod test_iterators { #[test] fn double_map_reversible_reversible_iteration() { sp_io::TestExternalities::default().execute_with(|| { + type DoubleMap = self::frame_system::DoubleMap; + // All map iterator let prefix = DoubleMap::prefix_hash(); diff --git a/frame/support/src/storage/generator/map.rs b/frame/support/src/storage/generator/map.rs index f6c8eaa270bb..90fac4b41c75 100644 --- a/frame/support/src/storage/generator/map.rs +++ b/frame/support/src/storage/generator/map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -68,7 +68,7 @@ pub trait StorageMap { KeyArg: EncodeLike, { let storage_prefix = storage_prefix(Self::module_prefix(), Self::storage_prefix()); - let key_hashed = key.borrow().using_encoded(Self::Hasher::hash); + let key_hashed = key.using_encoded(Self::Hasher::hash); let mut final_key = Vec::with_capacity(storage_prefix.len() + key_hashed.as_ref().len()); @@ -178,34 +178,48 @@ where } fn translate Option>(mut f: F) { + let mut previous_key = None; + loop { + previous_key = Self::translate_next(previous_key, &mut f); + if previous_key.is_none() { + break + } + } + } + + fn translate_next Option>( + previous_key: Option>, + mut f: F, + ) -> Option> { let prefix = G::prefix_hash(); - let mut previous_key = prefix.clone(); - while let Some(next) = - sp_io::storage::next_key(&previous_key).filter(|n| n.starts_with(&prefix)) - { - previous_key = next; - let value = match unhashed::get::(&previous_key) { - Some(value) => value, - None => { - log::error!("Invalid translate: fail to decode old value"); - continue - }, - }; - - let mut key_material = G::Hasher::reverse(&previous_key[prefix.len()..]); - let key = match K::decode(&mut key_material) { - Ok(key) => key, - Err(_) => { - log::error!("Invalid translate: fail to decode key"); - continue - }, - }; + let previous_key = previous_key.unwrap_or_else(|| prefix.clone()); - match f(key, value) { - Some(new) => unhashed::put::(&previous_key, &new), - None => unhashed::kill(&previous_key), - } + let current_key = + sp_io::storage::next_key(&previous_key).filter(|n| n.starts_with(&prefix))?; + + let value = match unhashed::get::(¤t_key) { + Some(value) => value, + None => { + log::error!("Invalid translate: fail to decode old value"); + return Some(current_key) + }, + }; + + let mut key_material = G::Hasher::reverse(¤t_key[prefix.len()..]); + let key = match K::decode(&mut key_material) { + Ok(key) => key, + Err(_) => { + log::error!("Invalid translate: fail to decode key"); + return Some(current_key) + }, + }; + + match f(key, value) { + Some(new) => unhashed::put::(¤t_key, &new), + None => unhashed::kill(¤t_key), } + + Some(current_key) } } @@ -327,7 +341,7 @@ impl> storage::StorageMap fn migrate_key>(key: KeyArg) -> Option { let old_key = { let storage_prefix = storage_prefix(Self::module_prefix(), Self::storage_prefix()); - let key_hashed = key.borrow().using_encoded(OldHasher::hash); + let key_hashed = key.using_encoded(OldHasher::hash); let mut final_key = Vec::with_capacity(storage_prefix.len() + key_hashed.as_ref().len()); @@ -349,43 +363,12 @@ impl> storage::StorageMap mod test_iterators { use crate::{ hash::StorageHasher, - storage::{generator::StorageMap, unhashed, IterableStorageMap}, + storage::{ + generator::{tests::*, StorageMap}, + unhashed, + }, }; - use codec::{Decode, Encode}; - - pub trait Config: 'static { - type RuntimeOrigin; - type BlockNumber; - type PalletInfo: crate::traits::PalletInfo; - type DbWeight: crate::traits::Get; - } - - crate::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self {} - } - - #[derive(PartialEq, Eq, Clone, Encode, Decode)] - struct NoDef(u32); - - crate::decl_storage! { - trait Store for Module as Test { - Map: map hasher(blake2_128_concat) u16 => u64; - } - } - - fn key_before_prefix(mut prefix: Vec) -> Vec { - let last = prefix.iter_mut().last().unwrap(); - assert!(*last != 0, "mock function not implemented for this prefix"); - *last -= 1; - prefix - } - - fn key_after_prefix(mut prefix: Vec) -> Vec { - let last = prefix.iter_mut().last().unwrap(); - assert!(*last != 255, "mock function not implemented for this prefix"); - *last += 1; - prefix - } + use codec::Encode; #[test] fn map_iter_from() { @@ -413,6 +396,8 @@ mod test_iterators { #[test] fn map_reversible_reversible_iteration() { sp_io::TestExternalities::default().execute_with(|| { + type Map = self::frame_system::Map; + // All map iterator let prefix = Map::prefix_hash(); diff --git a/frame/support/src/storage/generator/mod.rs b/frame/support/src/storage/generator/mod.rs index 334e9b9e24e8..bac9f642e37d 100644 --- a/frame/support/src/storage/generator/mod.rs +++ b/frame/support/src/storage/generator/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,47 +35,124 @@ pub use nmap::StorageNMap; pub use value::StorageValue; #[cfg(test)] -#[allow(dead_code)] mod tests { + use codec::Encode; + use sp_io::TestExternalities; + use sp_runtime::{generic, traits::BlakeTwo256, BuildStorage}; + use crate::{ assert_noop, assert_ok, - storage::{generator::StorageValue, unhashed, IterableStorageMap}, + storage::{generator::StorageValue, unhashed}, }; - use codec::Encode; - use sp_io::TestExternalities; - struct Runtime; + #[crate::pallet] + pub mod frame_system { + #[allow(unused)] + use super::{frame_system, frame_system::pallet_prelude::*}; + pub use crate::dispatch::RawOrigin; + use crate::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: 'static { + type Block: sp_runtime::traits::Block; + type AccountId; + type BaseCallFilter: crate::traits::Contains; + type RuntimeOrigin; + type RuntimeCall; + type PalletInfo: crate::traits::PalletInfo; + type DbWeight: Get; + } + + #[pallet::origin] + pub type Origin = RawOrigin<::AccountId>; + + #[pallet::error] + pub enum Error { + /// Required by construct_runtime + CallFiltered, + } + + #[pallet::call] + impl Pallet {} + + #[pallet::storage] + pub type Value = StorageValue<_, (u64, u64), ValueQuery>; + + #[pallet::storage] + pub type Map = StorageMap<_, Blake2_128Concat, u16, u64, ValueQuery>; - pub trait Config: 'static { - type RuntimeOrigin; - type BlockNumber; - type PalletInfo: crate::traits::PalletInfo; - type DbWeight: crate::traits::Get; + #[pallet::storage] + pub type NumberMap = StorageMap<_, Identity, u32, u64, ValueQuery>; + + #[pallet::storage] + pub type DoubleMap = + StorageDoubleMap<_, Blake2_128Concat, u16, Twox64Concat, u32, u64, ValueQuery>; + + #[pallet::storage] + pub type NMap = StorageNMap< + _, + (storage::Key, storage::Key), + u64, + ValueQuery, + >; + + pub mod pallet_prelude { + pub type OriginFor = ::RuntimeOrigin; + + pub type HeaderFor = + <::Block as sp_runtime::traits::HeaderProvider>::HeaderT; + + pub type BlockNumberFor = as sp_runtime::traits::Header>::Number; + } } - impl Config for Runtime { - type RuntimeOrigin = u32; - type BlockNumber = u32; - type PalletInfo = crate::tests::PanicPalletInfo; + type BlockNumber = u32; + type AccountId = u32; + type Header = generic::Header; + type UncheckedExtrinsic = generic::UncheckedExtrinsic; + type Block = generic::Block; + + crate::construct_runtime!( + pub enum Runtime + { + System: self::frame_system, + } + ); + + impl self::frame_system::Config for Runtime { + type AccountId = AccountId; + type Block = Block; + type BaseCallFilter = crate::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type PalletInfo = PalletInfo; type DbWeight = (); } - decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self {} + pub fn key_before_prefix(mut prefix: Vec) -> Vec { + let last = prefix.iter_mut().last().unwrap(); + assert_ne!(*last, 0, "mock function not implemented for this prefix"); + *last -= 1; + prefix } - crate::decl_storage! { - trait Store for Module as Runtime { - Value get(fn value) config(): (u64, u64); - NumberMap: map hasher(identity) u32 => u64; - DoubleMap: double_map hasher(identity) u32, hasher(identity) u32 => u64; - } + pub fn key_after_prefix(mut prefix: Vec) -> Vec { + let last = prefix.iter_mut().last().unwrap(); + assert_ne!(*last, 255, "mock function not implemented for this prefix"); + *last += 1; + prefix } #[test] fn value_translate_works() { - let t = GenesisConfig::default().build_storage().unwrap(); + let t = RuntimeGenesisConfig::default().build_storage().unwrap(); TestExternalities::new(t).execute_with(|| { + type Value = self::frame_system::Value; + // put the old value `1111u32` in the storage. let key = Value::storage_value_final_key(); unhashed::put_raw(&key, &1111u32.encode()); @@ -94,9 +171,11 @@ mod tests { #[test] fn map_translate_works() { - let t = GenesisConfig::default().build_storage().unwrap(); + let t = RuntimeGenesisConfig::default().build_storage().unwrap(); TestExternalities::new(t).execute_with(|| { - // start with a map of u32 -> u32. + type NumberMap = self::frame_system::NumberMap; + + // start with a map of u32 -> u64. for i in 0u32..100u32 { unhashed::put(&NumberMap::hashed_key_for(&i), &(i as u64)); } @@ -123,8 +202,12 @@ mod tests { #[test] fn try_mutate_works() { - let t = GenesisConfig::default().build_storage().unwrap(); + let t = RuntimeGenesisConfig::default().build_storage().unwrap(); TestExternalities::new(t).execute_with(|| { + type Value = self::frame_system::Value; + type NumberMap = self::frame_system::NumberMap; + type DoubleMap = self::frame_system::DoubleMap; + assert_eq!(Value::get(), (0, 0)); assert_eq!(NumberMap::get(0), 0); assert_eq!(DoubleMap::get(0, 0), 0); diff --git a/frame/support/src/storage/generator/nmap.rs b/frame/support/src/storage/generator/nmap.rs index 79f3d72044e2..5d3d689aa98a 100755 --- a/frame/support/src/storage/generator/nmap.rs +++ b/frame/support/src/storage/generator/nmap.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -208,6 +208,13 @@ where ) } + fn contains_prefix(partial_key: KP) -> bool + where + K: HasKeyPrefix, + { + unhashed::contains_prefixed_key(&Self::storage_n_map_partial_key(partial_key)) + } + fn iter_prefix_values(partial_key: KP) -> PrefixIterator where K: HasKeyPrefix, @@ -455,43 +462,12 @@ impl> mod test_iterators { use crate::{ hash::StorageHasher, - storage::{generator::StorageNMap, unhashed, IterableStorageNMap}, + storage::{ + generator::{tests::*, StorageNMap}, + unhashed, + }, }; - use codec::{Decode, Encode}; - - pub trait Config: 'static { - type RuntimeOrigin; - type BlockNumber; - type PalletInfo: crate::traits::PalletInfo; - type DbWeight: crate::traits::Get; - } - - crate::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self {} - } - - #[derive(PartialEq, Eq, Clone, Encode, Decode)] - struct NoDef(u32); - - crate::decl_storage! { - trait Store for Module as Test { - NMap: nmap hasher(blake2_128_concat) u16, hasher(twox_64_concat) u32 => u64; - } - } - - fn key_before_prefix(mut prefix: Vec) -> Vec { - let last = prefix.iter_mut().last().unwrap(); - assert!(*last != 0, "mock function not implemented for this prefix"); - *last -= 1; - prefix - } - - fn key_after_prefix(mut prefix: Vec) -> Vec { - let last = prefix.iter_mut().last().unwrap(); - assert!(*last != 255, "mock function not implemented for this prefix"); - *last += 1; - prefix - } + use codec::Encode; #[test] fn n_map_iter_from() { @@ -538,22 +514,18 @@ mod test_iterators { #[test] fn n_map_double_map_identical_key() { sp_io::TestExternalities::default().execute_with(|| { + use crate::hash::{Blake2_128Concat, Twox64Concat}; + + type NMap = self::frame_system::NMap; + NMap::insert((1, 2), 50); let key_hash = NMap::hashed_key_for((1, 2)); { #[crate::storage_alias] - type NMap = StorageDoubleMap< - Test, - crate::Blake2_128Concat, - u16, - crate::Twox64Concat, - u32, - u64, - >; - - let value = NMap::get(1, 2).unwrap(); - assert_eq!(value, 50); + type NMap = StorageDoubleMap; + + assert_eq!(NMap::get(1, 2), Some(50)); assert_eq!(NMap::hashed_key_for(1, 2), key_hash); } }); @@ -562,6 +534,8 @@ mod test_iterators { #[test] fn n_map_reversible_reversible_iteration() { sp_io::TestExternalities::default().execute_with(|| { + type NMap = self::frame_system::NMap; + // All map iterator let prefix = NMap::prefix_hash(); diff --git a/frame/support/src/storage/generator/value.rs b/frame/support/src/storage/generator/value.rs index 55b3487b1324..4ffe40bac53c 100644 --- a/frame/support/src/storage/generator/value.rs +++ b/frame/support/src/storage/generator/value.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -118,6 +118,30 @@ impl> storage::StorageValue for G { ret } + fn mutate_exists(f: F) -> R + where + F: FnOnce(&mut Option) -> R, + { + Self::try_mutate_exists(|v| Ok::(f(v))) + .expect("`Never` can not be constructed; qed") + } + + fn try_mutate_exists(f: F) -> Result + where + F: FnOnce(&mut Option) -> Result, + { + let mut val = G::from_query_to_optional_value(Self::get()); + + let ret = f(&mut val); + if ret.is_ok() { + match val { + Some(ref val) => Self::put(val), + None => Self::kill(), + } + } + ret + } + fn take() -> G::Query { let key = Self::storage_value_final_key(); let value = unhashed::get(&key); diff --git a/frame/support/src/storage/hashed.rs b/frame/support/src/storage/hashed.rs index 19c9d73be868..6633adce8ff6 100644 --- a/frame/support/src/storage/hashed.rs +++ b/frame/support/src/storage/hashed.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/storage/migration.rs b/frame/support/src/storage/migration.rs index 67001fc4e1f4..568c475bdc69 100644 --- a/frame/support/src/storage/migration.rs +++ b/frame/support/src/storage/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,18 +37,16 @@ pub struct StorageIterator { impl StorageIterator { /// Construct iterator to iterate over map items in `module` for the map called `item`. - #[deprecated( - note = "Please use the storage_iter or storage_iter_with_suffix functions instead" - )] + #[deprecated(note = "Will be removed after July 2023; Please use the storage_iter or \ + storage_iter_with_suffix functions instead")] pub fn new(module: &[u8], item: &[u8]) -> Self { #[allow(deprecated)] Self::with_suffix(module, item, &[][..]) } /// Construct iterator to iterate over map items in `module` for the map called `item`. - #[deprecated( - note = "Please use the storage_iter or storage_iter_with_suffix functions instead" - )] + #[deprecated(note = "Will be removed after July 2023; Please use the storage_iter or \ + storage_iter_with_suffix functions instead")] pub fn with_suffix(module: &[u8], item: &[u8], suffix: &[u8]) -> Self { let mut prefix = Vec::new(); let storage_prefix = storage_prefix(module, item); @@ -102,18 +100,16 @@ pub struct StorageKeyIterator { impl StorageKeyIterator { /// Construct iterator to iterate over map items in `module` for the map called `item`. - #[deprecated( - note = "Please use the storage_key_iter or storage_key_iter_with_suffix functions instead" - )] + #[deprecated(note = "Will be removed after July 2023; Please use the storage_key_iter or \ + storage_key_iter_with_suffix functions instead")] pub fn new(module: &[u8], item: &[u8]) -> Self { #[allow(deprecated)] Self::with_suffix(module, item, &[][..]) } /// Construct iterator to iterate over map items in `module` for the map called `item`. - #[deprecated( - note = "Please use the storage_key_iter or storage_key_iter_with_suffix functions instead" - )] + #[deprecated(note = "Will be removed after July 2023; Please use the storage_key_iter or \ + storage_key_iter_with_suffix functions instead")] pub fn with_suffix(module: &[u8], item: &[u8], suffix: &[u8]) -> Self { let mut prefix = Vec::new(); let storage_prefix = storage_prefix(module, item); diff --git a/frame/support/src/storage/mod.rs b/frame/support/src/storage/mod.rs index 8c0d6207c3f4..470af0a1d552 100644 --- a/frame/support/src/storage/mod.rs +++ b/frame/support/src/storage/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -114,6 +114,12 @@ pub trait StorageValue { /// Mutate the value if closure returns `Ok` fn try_mutate Result>(f: F) -> Result; + /// Mutate the value. Deletes the item if mutated to a `None`. + fn mutate_exists) -> R>(f: F) -> R; + + /// Mutate the value if closure returns `Ok`. Deletes the item if mutated to a `None`. + fn try_mutate_exists) -> Result>(f: F) -> Result; + /// Clear the storage value. fn kill(); @@ -196,6 +202,18 @@ pub trait StorageMap { f: F, ) -> Result; + /// Mutate the value under a key if the value already exists. Do nothing and return the default + /// value if not. + fn mutate_extant, R: Default, F: FnOnce(&mut V) -> R>( + key: KeyArg, + f: F, + ) -> R { + Self::mutate_exists(key, |maybe_v| match maybe_v { + Some(ref mut value) => f(value), + None => R::default(), + }) + } + /// Mutate the value under a key. /// /// Deletes the item if mutated to a `None`. @@ -297,6 +315,15 @@ pub trait IterableStorageMap: StorageMap { /// /// NOTE: If a value fail to decode because storage is corrupted then it is skipped. fn translate Option>(f: F); + + /// Translate the next entry following `previous_key` by a function `f`. + /// By returning `None` from `f` for an element, you'll remove it from the map. + /// + /// Returns the next key to iterate from in lexicographical order of the encoded key. + fn translate_next Option>( + previous_key: Option>, + f: F, + ) -> Option>; } /// A strongly-typed double map in storage whose secondary keys and values can be iterated over. @@ -557,6 +584,12 @@ pub trait StorageDoubleMap { where KArg1: ?Sized + EncodeLike; + /// Does any value under the first key `k1` (explicitly) exist in storage? + /// Might have unexpected behaviour with empty keys, e.g. `[]`. + fn contains_prefix(k1: KArg1) -> bool + where + KArg1: EncodeLike; + /// Iterate over values that share the first key. fn iter_prefix_values(k1: KArg1) -> PrefixIterator where @@ -733,6 +766,12 @@ pub trait StorageNMap { where K: HasKeyPrefix; + /// Does any value under a `partial_key` prefix (explicitly) exist in storage? + /// Might have unexpected behaviour with empty keys, e.g. `[]`. + fn contains_prefix(partial_key: KP) -> bool + where + K: HasKeyPrefix; + /// Iterate over values that share the partial prefix key. fn iter_prefix_values(partial_key: KP) -> PrefixIterator where @@ -1479,7 +1518,7 @@ pub fn storage_prefix(pallet_name: &[u8], storage_name: &[u8]) -> [u8; 32] { #[cfg(test)] mod test { use super::*; - use crate::{assert_ok, hash::Identity, Twox128}; + use crate::{assert_ok, hash::Identity, pallet_prelude::NMapKey, Twox128}; use bounded_vec::BoundedVec; use frame_support::traits::ConstU32; use generator::StorageValue as _; @@ -1774,6 +1813,39 @@ mod test { #[crate::storage_alias] type FooDoubleMap = StorageDoubleMap>>; + #[crate::storage_alias] + type FooTripleMap = StorageNMap< + Prefix, + (NMapKey, NMapKey, NMapKey), + u64, + >; + + #[test] + fn contains_prefix_works() { + TestExternalities::default().execute_with(|| { + // Test double maps + assert!(FooDoubleMap::iter_prefix_values(1).next().is_none()); + assert_eq!(FooDoubleMap::contains_prefix(1), false); + + assert_ok!(FooDoubleMap::try_append(1, 1, 4)); + assert_ok!(FooDoubleMap::try_append(2, 1, 4)); + assert!(FooDoubleMap::iter_prefix_values(1).next().is_some()); + assert!(FooDoubleMap::contains_prefix(1)); + FooDoubleMap::remove(1, 1); + assert_eq!(FooDoubleMap::contains_prefix(1), false); + + // Test N Maps + assert!(FooTripleMap::iter_prefix_values((1,)).next().is_none()); + assert_eq!(FooTripleMap::contains_prefix((1,)), false); + + FooTripleMap::insert((1, 1, 1), 4); + FooTripleMap::insert((2, 1, 1), 4); + assert!(FooTripleMap::iter_prefix_values((1,)).next().is_some()); + assert!(FooTripleMap::contains_prefix((1,))); + FooTripleMap::remove((1, 1, 1)); + assert_eq!(FooTripleMap::contains_prefix((1,)), false); + }); + } #[test] fn try_append_works() { diff --git a/frame/support/src/storage/storage_noop_guard.rs b/frame/support/src/storage/storage_noop_guard.rs index 7186c3eaf467..f2fb60027ec3 100644 --- a/frame/support/src/storage/storage_noop_guard.rs +++ b/frame/support/src/storage/storage_noop_guard.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/storage/stream_iter.rs b/frame/support/src/storage/stream_iter.rs index 4e25ca6ca1f6..e784ebd14c52 100644 --- a/frame/support/src/storage/stream_iter.rs +++ b/frame/support/src/storage/stream_iter.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/storage/transactional.rs b/frame/support/src/storage/transactional.rs index 909d5909ed8b..d42e1809e912 100644 --- a/frame/support/src/storage/transactional.rs +++ b/frame/support/src/storage/transactional.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,6 +32,8 @@ use sp_runtime::{DispatchError, TransactionOutcome, TransactionalError}; /// The type that is being used to store the current number of active layers. pub type Layer = u32; /// The key that is holds the current number of active layers. +/// +/// Encodes to `0x3a7472616e73616374696f6e5f6c6576656c3a`. pub const TRANSACTION_LEVEL_KEY: &[u8] = b":transaction_level:"; /// The maximum number of nested layers. pub const TRANSACTIONAL_LIMIT: Layer = 255; diff --git a/frame/support/src/storage/types/counted_map.rs b/frame/support/src/storage/types/counted_map.rs index 3361c4093dce..e57942cbe066 100644 --- a/frame/support/src/storage/types/counted_map.rs +++ b/frame/support/src/storage/types/counted_map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ //! Storage counted map type. use crate::{ - metadata::StorageEntryMetadata, + metadata_ir::StorageEntryMetadataIR, storage::{ generator::StorageMap as _, types::{ @@ -459,7 +459,7 @@ where OnEmpty: Get + 'static, MaxValues: Get>, { - fn build_metadata(docs: Vec<&'static str>, entries: &mut Vec) { + fn build_metadata(docs: Vec<&'static str>, entries: &mut Vec) { ::Map::build_metadata(docs, entries); CounterFor::::build_metadata( if cfg!(feature = "no-metadata-docs") { @@ -512,7 +512,7 @@ mod test { use super::*; use crate::{ hash::*, - metadata::{StorageEntryModifier, StorageEntryType, StorageHasher}, + metadata_ir::{StorageEntryModifierIR, StorageEntryTypeIR, StorageHasherIR}, storage::{bounded_vec::BoundedVec, types::ValueQuery}, traits::ConstU32, }; @@ -543,6 +543,16 @@ mod test { 97 } } + #[crate::storage_alias] + type ExampleCountedMap = CountedStorageMap; + + #[test] + fn storage_alias_works() { + TestExternalities::default().execute_with(|| { + assert_eq!(ExampleCountedMap::count(), 0); + ExampleCountedMap::insert(3, 10); + }) + } #[test] fn test_value_query() { @@ -1137,21 +1147,21 @@ mod test { assert_eq!( entries, vec![ - StorageEntryMetadata { + StorageEntryMetadataIR { name: "foo", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Twox64Concat], + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Twox64Concat], key: scale_info::meta_type::(), value: scale_info::meta_type::(), }, default: 97u32.encode(), docs: vec![], }, - StorageEntryMetadata { + StorageEntryMetadataIR { name: "counter_for_foo", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(scale_info::meta_type::()), + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Plain(scale_info::meta_type::()), default: vec![0, 0, 0, 0], docs: if cfg!(feature = "no-metadata-docs") { vec![] diff --git a/frame/support/src/storage/types/double_map.rs b/frame/support/src/storage/types/double_map.rs index 9ba4cf052e82..08ac1709c4b6 100644 --- a/frame/support/src/storage/types/double_map.rs +++ b/frame/support/src/storage/types/double_map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ //! StoragePrefixedDoubleMap traits and their methods directly. use crate::{ - metadata::{StorageEntryMetadata, StorageEntryType}, + metadata_ir::{StorageEntryMetadataIR, StorageEntryTypeIR}, storage::{ types::{OptionQuery, QueryKindTrait, StorageEntryMetadataBuilder}, KeyLenOf, StorageAppend, StorageDecodeLength, StoragePrefixedMap, StorageTryAppend, @@ -656,13 +656,13 @@ where OnEmpty: Get + 'static, MaxValues: Get>, { - fn build_metadata(docs: Vec<&'static str>, entries: &mut Vec) { + fn build_metadata(docs: Vec<&'static str>, entries: &mut Vec) { let docs = if cfg!(feature = "no-metadata-docs") { vec![] } else { docs }; - let entry = StorageEntryMetadata { + let entry = StorageEntryMetadataIR { name: Prefix::STORAGE_PREFIX, modifier: QueryKind::METADATA, - ty: StorageEntryType::Map { + ty: StorageEntryTypeIR::Map { hashers: vec![Hasher1::METADATA, Hasher2::METADATA], key: scale_info::meta_type::<(Key1, Key2)>(), value: scale_info::meta_type::(), @@ -736,7 +736,7 @@ mod test { use super::*; use crate::{ hash::*, - metadata::{StorageEntryModifier, StorageEntryType, StorageHasher}, + metadata_ir::{StorageEntryModifierIR, StorageEntryTypeIR, StorageHasherIR}, storage::types::ValueQuery, }; use sp_io::{hashing::twox_128, TestExternalities}; @@ -916,13 +916,13 @@ mod test { assert_eq!( entries, vec![ - StorageEntryMetadata { + StorageEntryMetadataIR { name: "foo", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { hashers: vec![ - StorageHasher::Blake2_128Concat, - StorageHasher::Twox64Concat + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Twox64Concat ], key: scale_info::meta_type::<(u16, u8)>(), value: scale_info::meta_type::(), @@ -930,13 +930,13 @@ mod test { default: Option::::None.encode(), docs: vec![], }, - StorageEntryMetadata { + StorageEntryMetadataIR { name: "foo", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { hashers: vec![ - StorageHasher::Blake2_128Concat, - StorageHasher::Twox64Concat + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Twox64Concat ], key: scale_info::meta_type::<(u16, u8)>(), value: scale_info::meta_type::(), diff --git a/frame/support/src/storage/types/key.rs b/frame/support/src/storage/types/key.rs index 182ddbedd9b8..bf87e593b063 100755 --- a/frame/support/src/storage/types/key.rs +++ b/frame/support/src/storage/types/key.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,7 +41,7 @@ pub trait KeyGenerator { type HashFn: FnOnce(&[u8]) -> Vec; type HArg; - const HASHER_METADATA: &'static [crate::metadata::StorageHasher]; + const HASHER_METADATA: &'static [crate::metadata_ir::StorageHasherIR]; /// Given a `key` tuple, calculate the final key by encoding each element individually and /// hashing them using the corresponding hasher in the `KeyGenerator`. @@ -74,7 +74,7 @@ impl KeyGenerator for Key type HashFn = Box Vec>; type HArg = (Self::HashFn,); - const HASHER_METADATA: &'static [crate::metadata::StorageHasher] = &[H::METADATA]; + const HASHER_METADATA: &'static [crate::metadata_ir::StorageHasherIR] = &[H::METADATA]; fn final_key + TupleToEncodedIter>(key: KArg) -> Vec { H::hash(&key.to_encoded_iter().next().expect("should have at least one element!")) @@ -114,7 +114,7 @@ impl KeyGenerator for Tuple { for_tuples!( type HArg = ( #(Tuple::HashFn),* ); ); type HashFn = Box Vec>; - const HASHER_METADATA: &'static [crate::metadata::StorageHasher] = + const HASHER_METADATA: &'static [crate::metadata_ir::StorageHasherIR] = &[for_tuples!( #(Tuple::Hasher::METADATA),* )]; fn final_key + TupleToEncodedIter>(key: KArg) -> Vec { diff --git a/frame/support/src/storage/types/map.rs b/frame/support/src/storage/types/map.rs index 3e2b744d72f8..c919dc6745d7 100644 --- a/frame/support/src/storage/types/map.rs +++ b/frame/support/src/storage/types/map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ //! methods directly. use crate::{ - metadata::{StorageEntryMetadata, StorageEntryType}, + metadata_ir::{StorageEntryMetadataIR, StorageEntryTypeIR}, storage::{ types::{OptionQuery, QueryKindTrait, StorageEntryMetadataBuilder}, KeyLenOf, StorageAppend, StorageDecodeLength, StoragePrefixedMap, StorageTryAppend, @@ -186,6 +186,14 @@ where >::try_mutate(key, f) } + /// Mutate the value under a key iff it exists. Do nothing and return the default value if not. + pub fn mutate_extant, R: Default, F: FnOnce(&mut Value) -> R>( + key: KeyArg, + f: F, + ) -> R { + >::mutate_extant(key, f) + } + /// Mutate the value under a key. Deletes the item if mutated to a `None`. pub fn mutate_exists, R, F: FnOnce(&mut Option) -> R>( key: KeyArg, @@ -366,6 +374,16 @@ where >::iter_from(starting_raw_key) } + /// Enumerate all elements in the map after a specified `starting_key` in no + /// particular order. + /// + /// If you alter the map while doing this, you'll get undefined results. + pub fn iter_from_key( + starting_key: impl EncodeLike, + ) -> crate::storage::PrefixIterator<(Key, Value)> { + Self::iter_from(Self::hashed_key_for(starting_key)) + } + /// Enumerate all keys in the map in no particular order. /// /// If you alter the map while doing this, you'll get undefined results. @@ -381,6 +399,16 @@ where >::iter_keys_from(starting_raw_key) } + /// Enumerate all keys in the map after a specified `starting_key` in no particular + /// order. + /// + /// If you alter the map while doing this, you'll get undefined results. + pub fn iter_keys_from_key( + starting_key: impl EncodeLike, + ) -> crate::storage::KeyPrefixIterator { + Self::iter_keys_from(Self::hashed_key_for(starting_key)) + } + /// Remove all elements from the map and iterate through them in no particular order. /// /// If you add elements to the map while doing this, you'll get undefined results. @@ -409,13 +437,13 @@ where OnEmpty: Get + 'static, MaxValues: Get>, { - fn build_metadata(docs: Vec<&'static str>, entries: &mut Vec) { + fn build_metadata(docs: Vec<&'static str>, entries: &mut Vec) { let docs = if cfg!(feature = "no-metadata-docs") { vec![] } else { docs }; - let entry = StorageEntryMetadata { + let entry = StorageEntryMetadataIR { name: Prefix::STORAGE_PREFIX, modifier: QueryKind::METADATA, - ty: StorageEntryType::Map { + ty: StorageEntryTypeIR::Map { hashers: vec![Hasher::METADATA], key: scale_info::meta_type::(), value: scale_info::meta_type::(), @@ -483,8 +511,8 @@ mod test { use super::*; use crate::{ hash::*, - metadata::{StorageEntryModifier, StorageEntryType, StorageHasher}, - storage::types::ValueQuery, + metadata_ir::{StorageEntryModifierIR, StorageEntryTypeIR, StorageHasherIR}, + storage::{types::ValueQuery, IterableStorageMap}, }; use sp_io::{hashing::twox_128, TestExternalities}; @@ -700,28 +728,37 @@ mod test { A::translate::(|k, v| Some((k * v as u16).into())); assert_eq!(A::iter().collect::>(), vec![(4, 40), (3, 30)]); + let translate_next = |k: u16, v: u8| Some((v as u16 / k).into()); + let k = A::translate_next::(None, translate_next); + let k = A::translate_next::(k, translate_next); + assert_eq!(None, A::translate_next::(k, translate_next)); + assert_eq!(A::iter().collect::>(), vec![(4, 10), (3, 10)]); + + let _ = A::translate_next::(None, |_, _| None); + assert_eq!(A::iter().collect::>(), vec![(3, 10)]); + let mut entries = vec![]; A::build_metadata(vec![], &mut entries); AValueQueryWithAnOnEmpty::build_metadata(vec![], &mut entries); assert_eq!( entries, vec![ - StorageEntryMetadata { + StorageEntryMetadataIR { name: "foo", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Blake2_128Concat], + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat], key: scale_info::meta_type::(), value: scale_info::meta_type::(), }, default: Option::::None.encode(), docs: vec![], }, - StorageEntryMetadata { + StorageEntryMetadataIR { name: "foo", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Blake2_128Concat], + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat], key: scale_info::meta_type::(), value: scale_info::meta_type::(), }, diff --git a/frame/support/src/storage/types/mod.rs b/frame/support/src/storage/types/mod.rs index f87da5de1227..3a5bae2e608b 100644 --- a/frame/support/src/storage/types/mod.rs +++ b/frame/support/src/storage/types/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ //! Storage types to build abstraction on storage, they implements storage traits such as //! StorageMap and others. -use crate::metadata::{StorageEntryMetadata, StorageEntryModifier}; +use crate::metadata_ir::{StorageEntryMetadataIR, StorageEntryModifierIR}; use codec::FullCodec; use sp_std::prelude::*; @@ -50,7 +50,7 @@ pub use value::StorageValue; /// value. pub trait QueryKindTrait { /// Metadata for the storage kind. - const METADATA: StorageEntryModifier; + const METADATA: StorageEntryModifierIR; /// Type returned on query type Query: FullCodec + 'static; @@ -73,7 +73,7 @@ impl QueryKindTrait for OptionQuery where Value: FullCodec + 'static, { - const METADATA: StorageEntryModifier = StorageEntryModifier::Optional; + const METADATA: StorageEntryModifierIR = StorageEntryModifierIR::Optional; type Query = Option; @@ -95,7 +95,7 @@ where Error: FullCodec + 'static, OnEmpty: crate::traits::Get>, { - const METADATA: StorageEntryModifier = StorageEntryModifier::Optional; + const METADATA: StorageEntryModifierIR = StorageEntryModifierIR::Optional; type Query = Result; @@ -118,7 +118,7 @@ where Value: FullCodec + 'static, OnEmpty: crate::traits::Get, { - const METADATA: StorageEntryModifier = StorageEntryModifier::Default; + const METADATA: StorageEntryModifierIR = StorageEntryModifierIR::Default; type Query = Value; @@ -136,5 +136,5 @@ where /// Implemented by each of the storage types: value, map, countedmap, doublemap and nmap. pub trait StorageEntryMetadataBuilder { /// Build into `entries` the storage metadata entries of a storage given some `docs`. - fn build_metadata(doc: Vec<&'static str>, entries: &mut Vec); + fn build_metadata(doc: Vec<&'static str>, entries: &mut Vec); } diff --git a/frame/support/src/storage/types/nmap.rs b/frame/support/src/storage/types/nmap.rs index dcbdac761fe1..9b63ca7b0f41 100755 --- a/frame/support/src/storage/types/nmap.rs +++ b/frame/support/src/storage/types/nmap.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ //! StoragePrefixedDoubleMap traits and their methods directly. use crate::{ - metadata::{StorageEntryMetadata, StorageEntryType}, + metadata_ir::{StorageEntryMetadataIR, StorageEntryTypeIR}, storage::{ types::{ EncodeLikeTuple, HasKeyPrefix, HasReversibleKeyPrefix, OptionQuery, QueryKindTrait, @@ -550,13 +550,13 @@ where OnEmpty: Get + 'static, MaxValues: Get>, { - fn build_metadata(docs: Vec<&'static str>, entries: &mut Vec) { + fn build_metadata(docs: Vec<&'static str>, entries: &mut Vec) { let docs = if cfg!(feature = "no-metadata-docs") { vec![] } else { docs }; - let entry = StorageEntryMetadata { + let entry = StorageEntryMetadataIR { name: Prefix::STORAGE_PREFIX, modifier: QueryKind::METADATA, - ty: StorageEntryType::Map { + ty: StorageEntryTypeIR::Map { key: scale_info::meta_type::(), hashers: Key::HASHER_METADATA.to_vec(), value: scale_info::meta_type::(), @@ -620,7 +620,7 @@ mod test { use super::*; use crate::{ hash::{StorageHasher as _, *}, - metadata::{StorageEntryModifier, StorageHasher}, + metadata_ir::{StorageEntryModifierIR, StorageHasherIR}, storage::types::{Key as NMapKey, ValueQuery}, }; use sp_io::{hashing::twox_128, TestExternalities}; @@ -791,22 +791,22 @@ mod test { assert_eq!( entries, vec![ - StorageEntryMetadata { + StorageEntryMetadataIR { name: "Foo", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Blake2_128Concat], + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat], key: scale_info::meta_type::(), value: scale_info::meta_type::(), }, default: Option::::None.encode(), docs: vec![], }, - StorageEntryMetadata { + StorageEntryMetadataIR { name: "Foo", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Blake2_128Concat], + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Blake2_128Concat], key: scale_info::meta_type::(), value: scale_info::meta_type::(), }, @@ -991,13 +991,13 @@ mod test { assert_eq!( entries, vec![ - StorageEntryMetadata { + StorageEntryMetadataIR { name: "Foo", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { hashers: vec![ - StorageHasher::Blake2_128Concat, - StorageHasher::Twox64Concat + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Twox64Concat ], key: scale_info::meta_type::<(u16, u8)>(), value: scale_info::meta_type::(), @@ -1005,13 +1005,13 @@ mod test { default: Option::::None.encode(), docs: vec![], }, - StorageEntryMetadata { + StorageEntryMetadataIR { name: "Foo", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { hashers: vec![ - StorageHasher::Blake2_128Concat, - StorageHasher::Twox64Concat + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Twox64Concat ], key: scale_info::meta_type::<(u16, u8)>(), value: scale_info::meta_type::(), @@ -1232,14 +1232,14 @@ mod test { assert_eq!( entries, vec![ - StorageEntryMetadata { + StorageEntryMetadataIR { name: "Foo", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Map { hashers: vec![ - StorageHasher::Blake2_128Concat, - StorageHasher::Blake2_128Concat, - StorageHasher::Twox64Concat + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Twox64Concat ], key: scale_info::meta_type::<(u16, u16, u16)>(), value: scale_info::meta_type::(), @@ -1247,14 +1247,14 @@ mod test { default: Option::::None.encode(), docs: vec![], }, - StorageEntryMetadata { + StorageEntryMetadataIR { name: "Foo", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { hashers: vec![ - StorageHasher::Blake2_128Concat, - StorageHasher::Blake2_128Concat, - StorageHasher::Twox64Concat + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Blake2_128Concat, + StorageHasherIR::Twox64Concat ], key: scale_info::meta_type::<(u16, u16, u16)>(), value: scale_info::meta_type::(), diff --git a/frame/support/src/storage/types/value.rs b/frame/support/src/storage/types/value.rs index f145e9fb3041..d5fbb0656bbd 100644 --- a/frame/support/src/storage/types/value.rs +++ b/frame/support/src/storage/types/value.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ //! Storage value type. Implements StorageValue trait and its method directly. use crate::{ - metadata::{StorageEntryMetadata, StorageEntryType}, + metadata_ir::{StorageEntryMetadataIR, StorageEntryTypeIR}, storage::{ generator::StorageValue as StorageValueT, types::{OptionQuery, QueryKindTrait, StorageEntryMetadataBuilder}, @@ -142,6 +142,18 @@ where >::try_mutate(f) } + /// Mutate the value. Deletes the item if mutated to a `None`. + pub fn mutate_exists) -> R>(f: F) -> R { + >::mutate_exists(f) + } + + /// Mutate the value if closure returns `Ok`. Deletes the item if mutated to a `None`. + pub fn try_mutate_exists) -> Result>( + f: F, + ) -> Result { + >::try_mutate_exists(f) + } + /// Clear the storage value. pub fn kill() { >::kill() @@ -209,13 +221,13 @@ where QueryKind: QueryKindTrait, OnEmpty: crate::traits::Get + 'static, { - fn build_metadata(docs: Vec<&'static str>, entries: &mut Vec) { + fn build_metadata(docs: Vec<&'static str>, entries: &mut Vec) { let docs = if cfg!(feature = "no-metadata-docs") { vec![] } else { docs }; - let entry = StorageEntryMetadata { + let entry = StorageEntryMetadataIR { name: Prefix::STORAGE_PREFIX, modifier: QueryKind::METADATA, - ty: StorageEntryType::Plain(scale_info::meta_type::()), + ty: StorageEntryTypeIR::Plain(scale_info::meta_type::()), default: OnEmpty::get().encode(), docs, }; @@ -266,7 +278,7 @@ where #[cfg(test)] mod test { use super::*; - use crate::{metadata::StorageEntryModifier, storage::types::ValueQuery}; + use crate::{metadata_ir::StorageEntryModifierIR, storage::types::ValueQuery}; use sp_io::{hashing::twox_128, TestExternalities}; struct Prefix; @@ -351,17 +363,17 @@ mod test { assert_eq!( entries, vec![ - StorageEntryMetadata { + StorageEntryMetadataIR { name: "foo", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Plain(scale_info::meta_type::()), + modifier: StorageEntryModifierIR::Optional, + ty: StorageEntryTypeIR::Plain(scale_info::meta_type::()), default: Option::::None.encode(), docs: vec![], }, - StorageEntryMetadata { + StorageEntryMetadataIR { name: "foo", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(scale_info::meta_type::()), + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Plain(scale_info::meta_type::()), default: 97u32.encode(), docs: vec![], } diff --git a/frame/support/src/storage/unhashed.rs b/frame/support/src/storage/unhashed.rs index 850e93e7d7fe..aae83034ab71 100644 --- a/frame/support/src/storage/unhashed.rs +++ b/frame/support/src/storage/unhashed.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -154,6 +154,16 @@ pub fn clear_prefix( MultiRemovalResults { maybe_cursor, backend: i, unique: i, loops: i } } +/// Returns `true` if the storage contains any key, which starts with a certain prefix, +/// and is longer than said prefix. +/// This means that a key which equals the prefix will not be counted. +pub fn contains_prefixed_key(prefix: &[u8]) -> bool { + match sp_io::storage::next_key(prefix) { + Some(key) => key.starts_with(prefix), + None => false, + } +} + /// Get a Vec of bytes from storage. pub fn get_raw(key: &[u8]) -> Option> { sp_io::storage::get(key).map(|value| value.to_vec()) diff --git a/frame/support/src/storage/weak_bounded_vec.rs b/frame/support/src/storage/weak_bounded_vec.rs index 72ba8d775a1d..41d27b51a12f 100644 --- a/frame/support/src/storage/weak_bounded_vec.rs +++ b/frame/support/src/storage/weak_bounded_vec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index f7b57f446fee..27396d93487b 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,7 +36,7 @@ pub use members::{AllowAll, DenyAll, Filter}; pub use members::{ AsContains, ChangeMembers, Contains, ContainsLengthBound, ContainsPair, Everything, EverythingBut, FromContainsPair, InitializeMembers, InsideBoth, IsInVec, Nothing, - SortedMembers, TheseExcept, + RankedMembers, SortedMembers, TheseExcept, }; mod validation; @@ -55,8 +55,8 @@ pub use filter::{ClearFilterGuard, FilterStack, FilterStackGuard, InstanceFilter mod misc; pub use misc::{ defensive_prelude::{self, *}, - Backing, ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstU128, ConstU16, - ConstU32, ConstU64, ConstU8, DefensiveMax, DefensiveMin, DefensiveSaturating, + AccountTouch, Backing, ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstU128, + ConstU16, ConstU32, ConstU64, ConstU8, DefensiveMax, DefensiveMin, DefensiveSaturating, DefensiveTruncateFrom, EnsureInherentsAreFirst, EqualPrivilegeOnly, EstimateCallFee, ExecuteBlock, ExtrinsicCall, Get, GetBacking, GetDefault, HandleLifetime, IsSubType, IsType, Len, OffchainWorker, OnKilledAccount, OnNewAccount, PrivilegeCmp, SameOrOther, Time, @@ -74,24 +74,24 @@ pub use randomness::Randomness; mod metadata; pub use metadata::{ - CallMetadata, CrateVersion, GetCallMetadata, GetCallName, GetStorageVersion, PalletInfo, - PalletInfoAccess, PalletInfoData, PalletsInfoAccess, StorageVersion, - STORAGE_VERSION_STORAGE_KEY_POSTFIX, + CallMetadata, CrateVersion, GetCallIndex, GetCallMetadata, GetCallName, GetStorageVersion, + NoStorageVersionSet, PalletInfo, PalletInfoAccess, PalletInfoData, PalletsInfoAccess, + StorageVersion, STORAGE_VERSION_STORAGE_KEY_POSTFIX, }; mod hooks; -#[cfg(feature = "std")] +#[allow(deprecated)] pub use hooks::GenesisBuild; pub use hooks::{ - Hooks, IntegrityTest, OnFinalize, OnGenesis, OnIdle, OnInitialize, OnRuntimeUpgrade, - OnTimestampSet, + BuildGenesisConfig, Hooks, IntegrityTest, OnFinalize, OnGenesis, OnIdle, OnInitialize, + OnRuntimeUpgrade, OnTimestampSet, }; pub mod schedule; mod storage; pub use storage::{ - Instance, PartialStorageInfoTrait, StorageInfo, StorageInfoTrait, StorageInstance, - TrackedStorageKey, WhitelistedStorageKeys, + Incrementable, Instance, PartialStorageInfoTrait, StorageInfo, StorageInfoTrait, + StorageInstance, TrackedStorageKey, WhitelistedStorageKeys, }; mod dispatch; @@ -100,22 +100,19 @@ pub use dispatch::EnsureOneOf; pub use dispatch::{ AsEnsureOriginWithArg, CallerTrait, EitherOf, EitherOfDiverse, EnsureOrigin, EnsureOriginEqualOrHigherPrivilege, EnsureOriginWithArg, MapSuccess, NeverEnsureOrigin, - OriginTrait, TryMapSuccess, UnfilteredDispatchable, + OriginTrait, TryMapSuccess, TryWithMorphedArg, UnfilteredDispatchable, }; mod voting; -pub use voting::{ - ClassCountOf, CurrencyToVote, PollStatus, Polling, SaturatingCurrencyToVote, - U128CurrencyToVote, VoteTally, -}; +pub use voting::{ClassCountOf, PollStatus, Polling, VoteTally}; mod preimages; pub use preimages::{Bounded, BoundedInline, FetchResult, Hash, QueryPreimage, StorePreimage}; mod messages; pub use messages::{ - EnqueueMessage, ExecuteOverweightError, Footprint, ProcessMessage, ProcessMessageError, - ServiceQueues, + EnqueueMessage, ExecuteOverweightError, Footprint, NoopServiceQueues, ProcessMessage, + ProcessMessageError, QueuePausedQuery, ServiceQueues, TransformOrigin, }; #[cfg(feature = "try-runtime")] diff --git a/frame/support/src/traits/dispatch.rs b/frame/support/src/traits/dispatch.rs index 36ddf5b507c0..9ea58479a0df 100644 --- a/frame/support/src/traits/dispatch.rs +++ b/frame/support/src/traits/dispatch.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,38 +40,12 @@ pub trait EnsureOrigin { /// Perform the origin check. fn try_origin(o: OuterOrigin) -> Result; - /// Returns an outer origin capable of passing `try_origin` check. - /// - /// NOTE: This should generally *NOT* be reimplemented. Instead implement - /// `try_successful_origin`. - /// - /// ** Should be used for benchmarking only!!! ** - #[cfg(feature = "runtime-benchmarks")] - fn successful_origin() -> OuterOrigin { - Self::try_successful_origin().expect("No origin exists which can satisfy the guard") - } - - /// Attept to get an outer origin capable of passing `try_origin` check. May return `Err` if it - /// is impossible. Default implementation just uses `successful_origin()`. + /// Attempt to get an outer origin capable of passing `try_origin` check. May return `Err` if it + /// is impossible. /// /// ** Should be used for benchmarking only!!! ** #[cfg(feature = "runtime-benchmarks")] - fn try_successful_origin() -> Result { - Ok(Self::successful_origin()) - } -} - -/// [`EnsureOrigin`] implementation that always fails. -pub struct NeverEnsureOrigin(sp_std::marker::PhantomData); -impl EnsureOrigin for NeverEnsureOrigin { - type Success = Success; - fn try_origin(o: OO) -> Result { - Err(o) - } - #[cfg(feature = "runtime-benchmarks")] - fn try_successful_origin() -> Result { - Err(()) - } + fn try_successful_origin() -> Result; } /// [`EnsureOrigin`] implementation that checks that an origin has equal or higher privilege @@ -171,26 +145,69 @@ pub trait EnsureOriginWithArg { /// Perform the origin check, returning the origin value if unsuccessful. This allows chaining. fn try_origin(o: OuterOrigin, a: &Argument) -> Result; - /// Returns an outer origin capable of passing `try_origin` check. - /// - /// NOTE: This should generally *NOT* be reimplemented. Instead implement - /// `try_successful_origin`. + /// Attempt to get an outer origin capable of passing `try_origin` check. May return `Err` if it + /// is impossible. /// /// ** Should be used for benchmarking only!!! ** #[cfg(feature = "runtime-benchmarks")] - fn successful_origin(a: &Argument) -> OuterOrigin { - Self::try_successful_origin(a).expect("No origin exists which can satisfy the guard") + fn try_successful_origin(a: &Argument) -> Result; +} + +/// Simple macro to explicitly implement [EnsureOriginWithArg] to be used on any type which +/// implements [EnsureOrigin]. This is quick and dirty, so you must use the type parameters `O` +/// (the origin type), `T` (the argument type) and `AccountId` (if you are using the `O: ..` form). +/// +/// The argument is ignored, much like in [AsEnsureOriginWithArg]. +#[macro_export] +macro_rules! impl_ensure_origin_with_arg_ignoring_arg { + ( impl < { O: .., I: 'static, $( $bound:tt )* }> EnsureOriginWithArg for $name:ty {} ) => { + impl_ensure_origin_with_arg_ignoring_arg! { + impl <{ + O: Into, O>> + From>, + I: 'static, + $( $bound )* + }> EnsureOriginWithArg for $name {} + } + }; + ( impl < { O: .. , $( $bound:tt )* }> EnsureOriginWithArg for $name:ty {} ) => { + impl_ensure_origin_with_arg_ignoring_arg! { + impl <{ + O: Into, O>> + From>, + $( $bound )* + }> EnsureOriginWithArg for $name {} + } + }; + ( impl < { $( $bound:tt )* } > EnsureOriginWithArg<$o_param:ty, $t_param:ty> for $name:ty {} ) => { + impl < $( $bound )* > EnsureOriginWithArg<$o_param, $t_param> for $name { + type Success = >::Success; + fn try_origin(o: $o_param, _: &$t_param) -> Result { + >::try_origin(o) + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(_: &$t_param) -> Result<$o_param, ()> { + >::try_successful_origin() + } + } } +} - /// Attept to get an outer origin capable of passing `try_origin` check. May return `Err` if it - /// is impossible. Default implementation just uses `successful_origin()`. - /// - /// ** Should be used for benchmarking only!!! ** +/// [`EnsureOrigin`] implementation that always fails. +pub struct NeverEnsureOrigin(sp_std::marker::PhantomData); +impl EnsureOrigin for NeverEnsureOrigin { + type Success = Success; + fn try_origin(o: OO) -> Result { + Err(o) + } #[cfg(feature = "runtime-benchmarks")] - fn try_successful_origin(a: &Argument) -> Result { - Ok(Self::successful_origin(a)) + fn try_successful_origin() -> Result { + Err(()) } } +impl_ensure_origin_with_arg_ignoring_arg! { + impl<{ OO, Success, A }> + EnsureOriginWithArg for NeverEnsureOrigin + {} +} pub struct AsEnsureOriginWithArg(sp_std::marker::PhantomData); impl> @@ -233,6 +250,18 @@ impl, Mutator: Morph> EnsureOrig Original::try_successful_origin() } } +impl, Mutator: Morph, A> + EnsureOriginWithArg for MapSuccess +{ + type Success = Mutator::Outcome; + fn try_origin(o: O, a: &A) -> Result { + Ok(Mutator::morph(Original::try_origin(o, a)?)) + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(a: &A) -> Result { + Original::try_successful_origin(a) + } +} /// A derivative `EnsureOrigin` implementation. It mutates the `Success` result of an `Original` /// implementation with a given `Mutator`, allowing the possibility of an error to be returned @@ -254,6 +283,43 @@ impl, Mutator: TryMorph> Original::try_successful_origin() } } +impl, Mutator: TryMorph, A> + EnsureOriginWithArg for TryMapSuccess +{ + type Success = Mutator::Outcome; + fn try_origin(o: O, a: &A) -> Result { + let orig = o.clone(); + Mutator::try_morph(Original::try_origin(o, a)?).map_err(|()| orig) + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(a: &A) -> Result { + Original::try_successful_origin(a) + } +} + +pub struct TryWithMorphedArg( + PhantomData<(O, A, Morph, Inner, Success)>, +); +impl< + O, + A, + Morph: for<'a> TryMorph<&'a A>, + Inner: for<'a> EnsureOriginWithArg>::Outcome, Success = Success>, + Success, + > EnsureOriginWithArg for TryWithMorphedArg +{ + type Success = Success; + fn try_origin(o: O, a: &A) -> Result { + match Morph::try_morph(a) { + Ok(x) => Inner::try_origin(o, &x), + _ => return Err(o), + } + } + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(a: &A) -> Result { + Inner::try_successful_origin(&Morph::try_morph(a).map_err(|_| ())?) + } +} /// "OR gate" implementation of `EnsureOrigin` allowing for different `Success` types for `L` /// and `R`, with them combined using an `Either` type. @@ -276,6 +342,24 @@ impl, R: EnsureOrigin> L::try_successful_origin().or_else(|()| R::try_successful_origin()) } } +impl< + OuterOrigin, + L: EnsureOriginWithArg, + R: EnsureOriginWithArg, + Argument, + > EnsureOriginWithArg for EitherOfDiverse +{ + type Success = Either; + fn try_origin(o: OuterOrigin, a: &Argument) -> Result { + L::try_origin(o, a) + .map_or_else(|o| R::try_origin(o, a).map(Either::Right), |o| Ok(Either::Left(o))) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(a: &Argument) -> Result { + L::try_successful_origin(a).or_else(|()| R::try_successful_origin(a)) + } +} /// "OR gate" implementation of `EnsureOrigin` allowing for different `Success` types for `L` /// and `R`, with them combined using an `Either` type. @@ -309,6 +393,23 @@ impl< L::try_successful_origin().or_else(|()| R::try_successful_origin()) } } +impl< + OuterOrigin, + L: EnsureOriginWithArg, + R: EnsureOriginWithArg, + Argument, + > EnsureOriginWithArg for EitherOf +{ + type Success = L::Success; + fn try_origin(o: OuterOrigin, a: &Argument) -> Result { + L::try_origin(o, a).or_else(|o| R::try_origin(o, a)) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(a: &Argument) -> Result { + L::try_successful_origin(a).or_else(|()| R::try_successful_origin(a)) + } +} /// Type that can be dispatched with an origin but without checking the origin filter. /// @@ -332,6 +433,21 @@ pub trait CallerTrait: Parameter + Member + From /// Extract a reference to the system-level `RawOrigin` if it is that. fn as_system_ref(&self) -> Option<&RawOrigin>; + + /// Extract the signer from it if a system `Signed` origin, `None` otherwise. + fn as_signed(&self) -> Option<&AccountId> { + self.as_system_ref().and_then(RawOrigin::as_signed) + } + + /// Returns `true` if `self` is a system `Root` origin, `None` otherwise. + fn is_root(&self) -> bool { + self.as_system_ref().map_or(false, RawOrigin::is_root) + } + + /// Returns `true` if `self` is a system `None` origin, `None` otherwise. + fn is_none(&self) -> bool { + self.as_system_ref().map_or(false, RawOrigin::is_none) + } } /// Methods available on `frame_system::Config::RuntimeOrigin`. @@ -382,7 +498,13 @@ pub trait OriginTrait: Sized { fn signed(by: Self::AccountId) -> Self; /// Extract the signer from the message if it is a `Signed` origin. + #[deprecated = "Use `into_signer` instead"] fn as_signed(self) -> Option { + self.into_signer() + } + + /// Extract the signer from the message if it is a `Signed` origin. + fn into_signer(self) -> Option { self.into_caller().into_system().and_then(|s| { if let RawOrigin::Signed(who) = s { Some(who) diff --git a/frame/support/src/traits/error.rs b/frame/support/src/traits/error.rs index 8e26891669e6..0f30e266da2d 100644 --- a/frame/support/src/traits/error.rs +++ b/frame/support/src/traits/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/filter.rs b/frame/support/src/traits/filter.rs index cdd82a3124e6..91efafef2f97 100644 --- a/frame/support/src/traits/filter.rs +++ b/frame/support/src/traits/filter.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -96,15 +96,6 @@ impl InstanceFilter for () { } } -/// Re-expected for the macro. -#[doc(hidden)] -pub use sp_std::{ - boxed::Box, - cell::RefCell, - mem::{swap, take}, - vec::Vec, -}; - #[macro_export] macro_rules! impl_filter_stack { ($target:ty, $base:ty, $call:ty, $module:ident) => { @@ -112,7 +103,8 @@ macro_rules! impl_filter_stack { mod $module { #[allow(unused_imports)] use super::*; - use $crate::traits::filter::{swap, take, RefCell, Vec, Box, Contains, FilterStack}; + use $crate::sp_std::{boxed::Box, cell::RefCell, mem::{swap, take}, vec::Vec}; + use $crate::traits::filter::{Contains, FilterStack}; thread_local! { static FILTER: RefCell bool + 'static>>> = RefCell::new(Vec::new()); diff --git a/frame/support/src/traits/hooks.rs b/frame/support/src/traits/hooks.rs index 3f7db1fa046b..3f9c6b1507d3 100644 --- a/frame/support/src/traits/hooks.rs +++ b/frame/support/src/traits/hooks.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,25 +15,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Traits for hooking tasks to events in a blockchain's lifecycle. +//! Traits relating to pallet hooks. +//! +//! See [`Hooks`] as the main entry-point. + +#![deny(missing_docs)] use crate::weights::Weight; use impl_trait_for_tuples::impl_for_tuples; use sp_runtime::traits::AtLeast32BitUnsigned; use sp_std::prelude::*; -/// The block initialization trait. -/// -/// Implementing this lets you express what should happen for your pallet when the block is -/// beginning (right before the first extrinsic is executed). +#[cfg(feature = "try-runtime")] +use sp_runtime::TryRuntimeError; + +/// See [`Hooks::on_initialize`]. pub trait OnInitialize { - /// The block is being initialized. Implement to have something happen. - /// - /// Return the non-negotiable weight consumed in the block. - /// - /// NOTE: This function is called BEFORE ANY extrinsic in a block is applied, - /// including inherent extrinsics. Hence for instance, if you runtime includes - /// `pallet_timestamp`, the `timestamp` is not yet up to date at this point. + /// See [`Hooks::on_initialize`]. fn on_initialize(_n: BlockNumber) -> Weight { Weight::zero() } @@ -50,32 +48,18 @@ impl OnInitialize for Tuple { } } -/// The block finalization trait. -/// -/// Implementing this lets you express what should happen for your pallet when the block is ending. +/// See [`Hooks::on_finalize`]. #[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] #[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] #[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] pub trait OnFinalize { - /// The block is being finalized. Implement to have something happen. - /// - /// NOTE: This function is called AFTER ALL extrinsics in a block are applied, - /// including inherent extrinsics. + /// See [`Hooks::on_finalize`]. fn on_finalize(_n: BlockNumber) {} } -/// The block's on idle trait. -/// -/// Implementing this lets you express what should happen for your pallet before -/// block finalization (see `on_finalize` hook) in case any remaining weight is left. +/// See [`Hooks::on_idle`]. pub trait OnIdle { - /// The block is being finalized. - /// Implement to have something happen in case there is leftover weight. - /// Check the passed `remaining_weight` to make sure it is high enough to allow for - /// your pallet's extra computation. - /// - /// NOTE: This function is called AFTER ALL extrinsics - including inherent extrinsics - - /// in a block are applied but before `on_finalize` is executed. + /// See [`Hooks::on_idle`]. fn on_idle(_n: BlockNumber, _remaining_weight: Weight) -> Weight { Weight::zero() } @@ -115,28 +99,16 @@ pub trait OnGenesis { fn on_genesis() {} } -/// The runtime upgrade trait. -/// -/// Implementing this lets you express what should happen when the runtime upgrades, -/// and changes may need to occur to your module. +/// See [`Hooks::on_runtime_upgrade`]. pub trait OnRuntimeUpgrade { - /// Perform a module upgrade. - /// - /// # Warning - /// - /// This function will be called before we initialized any runtime state, aka `on_initialize` - /// wasn't called yet. So, information like the block number and any other - /// block local data are not accessible. - /// - /// Return the non-negotiable weight consumed for runtime upgrade. + /// See [`Hooks::on_runtime_upgrade`]. fn on_runtime_upgrade() -> Weight { Weight::zero() } - /// Same as `on_runtime_upgrade`, but perform the optional `pre_upgrade` and `post_upgrade` as - /// well. + /// See [`Hooks::on_runtime_upgrade`]. #[cfg(feature = "try-runtime")] - fn try_on_runtime_upgrade(checks: bool) -> Result { + fn try_on_runtime_upgrade(checks: bool) -> Result { let maybe_state = if checks { let _guard = frame_support::StorageNoopGuard::default(); let state = Self::pre_upgrade()?; @@ -156,33 +128,15 @@ pub trait OnRuntimeUpgrade { Ok(weight) } - /// Execute some pre-checks prior to a runtime upgrade. - /// - /// Return a `Vec` that can contain arbitrary encoded data (usually some pre-upgrade state), - /// which will be passed to `post_upgrade` after upgrading for post-check. An empty vector - /// should be returned if there is no such need. - /// - /// This hook is never meant to be executed on-chain but is meant to be used by testing tools. - /// - /// This hook must not write to any state, as it would make the main `on_runtime_upgrade` path - /// inaccurate. + /// See [`Hooks::on_runtime_upgrade`]. #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { + fn pre_upgrade() -> Result, TryRuntimeError> { Ok(Vec::new()) } - /// Execute some post-checks after a runtime upgrade. - /// - /// The `state` parameter is the `Vec` returned by `pre_upgrade` before upgrading, which - /// can be used for post-check. NOTE: if `pre_upgrade` is not implemented an empty vector will - /// be passed in, in such case `post_upgrade` should ignore it. - /// - /// This hook is never meant to be executed on-chain but is meant to be used by testing tools. - /// - /// This hook must not write to any state, as it would make the main `on_runtime_upgrade` path - /// inaccurate. + /// See [`Hooks::on_runtime_upgrade`]. #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), &'static str> { + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { Ok(()) } } @@ -197,70 +151,189 @@ impl OnRuntimeUpgrade for Tuple { weight } + // We are executing pre- and post-checks sequentially in order to be able to test several + // consecutive migrations for the same pallet without errors. Therefore pre and post upgrade + // hooks for tuples are a noop. #[cfg(feature = "try-runtime")] - /// We are executing pre- and post-checks sequentially in order to be able to test several - /// consecutive migrations for the same pallet without errors. Therefore pre and post upgrade - /// hooks for tuples are a noop. - fn try_on_runtime_upgrade(checks: bool) -> Result { + fn try_on_runtime_upgrade(checks: bool) -> Result { let mut weight = Weight::zero(); - for_tuples!( #( weight = weight.saturating_add(Tuple::try_on_runtime_upgrade(checks)?); )* ); + + let mut errors = Vec::new(); + + for_tuples!(#( + match Tuple::try_on_runtime_upgrade(checks) { + Ok(weight) => { weight.saturating_add(weight); }, + Err(err) => { errors.push(err); }, + } + )*); + + if errors.len() == 1 { + return Err(errors[0]) + } else if !errors.is_empty() { + log::error!( + target: "try-runtime", + "Detected multiple errors while executing `try_on_runtime_upgrade`:", + ); + + errors.iter().for_each(|err| { + log::error!( + target: "try-runtime", + "{:?}", + err + ); + }); + + return Err("Detected multiple errors while executing `try_on_runtime_upgrade`, check the logs!".into()) + } + Ok(weight) } } -/// Type that provide some integrity tests. -/// -/// This implemented for modules by `decl_module`. +/// See [`Hooks::integrity_test`]. #[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] #[cfg_attr(all(feature = "tuples-96", not(feature = "tuples-128")), impl_for_tuples(96))] #[cfg_attr(feature = "tuples-128", impl_for_tuples(128))] pub trait IntegrityTest { - /// Run integrity test. - /// - /// The test is not executed in a externalities provided environment. + /// See [`Hooks::integrity_test`]. fn integrity_test() {} } -/// The pallet hooks trait. Implementing this lets you express some logic to execute. +#[cfg_attr(doc, aquamarine::aquamarine)] +/// The pallet hooks trait. This is merely an umbrella trait for: +/// +/// - [`OnInitialize`] +/// - [`OnFinalize`] +/// - [`OnRuntimeUpgrade`] +/// - [`crate::traits::misc::OffchainWorker`] +/// - [`OnIdle`] +/// - [`IntegrityTest`] +/// +/// ## Ordering +/// +/// For all hooks, except [`OnIdle`] the order of execution is derived from how the pallets are +/// ordered in [`crate::construct_runtime`]. +/// +/// ## Summary +/// +/// In short, the following diagram shows the flow of hooks in a pallet +/// +/// ```mermaid +/// graph LR +/// Optional --> BeforeExtrinsics +/// BeforeExtrinsics --> Extrinsics +/// Extrinsics --> AfterExtrinsics +/// subgraph Optional +/// OnRuntimeUpgrade +/// end +/// +/// subgraph BeforeExtrinsics +/// OnInitialize +/// end +/// +/// subgraph Extrinsics +/// direction TB +/// Inherent1 +/// Inherent2 +/// Extrinsic1 +/// Extrinsic2 +/// +/// Inherent1 --> Inherent2 +/// Inherent2 --> Extrinsic1 +/// Extrinsic1 --> Extrinsic2 +/// end +/// +/// subgraph AfterExtrinsics +/// OnIdle +/// OnFinalize +/// +/// OnIdle --> OnFinalize +/// end +/// ``` +/// +/// * `OnRuntimeUpgrade` is only executed before everything else if a code +/// * `OnRuntimeUpgrade` is mandatorily at the beginning of the block body (extrinsics) being +/// processed. change is detected. +/// * Extrinsics start with inherents, and continue with other signed or unsigned extrinsics. +/// * `OnIdle` optionally comes after extrinsics. +/// `OnFinalize` mandatorily comes after `OnIdle`. +/// +/// > `OffchainWorker` is not part of this flow, as it is not really part of the consensus/main +/// > block import path, and is called optionally, and in other circumstances. See +/// > [`crate::traits::misc::OffchainWorker`] for more information. +/// +/// To learn more about the execution of hooks see `frame-executive` as this component is is charge +/// of dispatching extrinsics and placing the hooks in the correct order. pub trait Hooks { - /// The block is being finalized. Implement to have something happen. - fn on_finalize(_n: BlockNumber) {} - - /// This will be run when the block is being finalized (before `on_finalize`). - /// Implement to have something happen using the remaining weight. - /// Will not fire if the remaining weight is 0. - /// Return the weight used, the hook will subtract it from current weight used - /// and pass the result to the next `on_idle` hook if it exists. - fn on_idle(_n: BlockNumber, _remaining_weight: Weight) -> Weight { + /// Block initialization hook. This is called at the very beginning of block execution. + /// + /// Must return the non-negotiable weight of both itself and whatever [`Hooks::on_finalize`] + /// wishes to consume. + /// + /// The weight returned by this is treated as `DispatchClass::Mandatory`, meaning that + /// it MUST BE EXECUTED. If this is not the case, consider using [`Hooks::on_idle`] instead. + /// + /// NOTE: This function is called BEFORE ANY extrinsic in a block is applied, including inherent + /// extrinsics. Hence for instance, if you runtime includes `pallet-timestamp`, the `timestamp` + /// is not yet up to date at this point. + fn on_initialize(_n: BlockNumber) -> Weight { Weight::zero() } - /// The block is being initialized. Implement to have something happen. + /// Block finalization hook. This is called at the very end of block execution. /// - /// Return the non-negotiable weight consumed in the block. - fn on_initialize(_n: BlockNumber) -> Weight { + /// Note that this has nothing to do with finality in the "consensus" sense. + /// + /// Note that the non-negotiable weight for this has must have already been returned by + /// [`Hooks::on_initialize`]. It usage alone is not permitted. + /// + /// Similar to [`Hooks::on_initialize`] it should only be used when execution is absolutely + /// necessary. In other cases, consider using [`Hooks::on_idle`] instead. + fn on_finalize(_n: BlockNumber) {} + + /// Hook to consume a block's idle time. This will run when the block is being finalized (before + /// [`Hooks::on_finalize`]). + /// + /// Given that all dispatchables are already executed and noted (and the weight for + /// [`Hooks::on_finalize`], which comes next, is also already accounted for via + /// `on_initialize`), this hook consumes anything that is leftover. + /// + /// Each pallet's `on_idle` is chosen to be the first to execute in a round-robin fashion + /// indexed by the block number. + /// + /// Return the weight used, the caller will use this to calculate the remaining weight and then + /// call the next pallet `on_idle` hook if there is still weight left. + /// + /// Any implementation should always respect `_remaining_weight` and never consume (and + /// therefore return) more than this amount. + fn on_idle(_n: BlockNumber, _remaining_weight: Weight) -> Weight { Weight::zero() } - /// Perform a module upgrade. + /// Hook executed when a code change (aka. a "runtime upgrade") is detected by FRAME. + /// + /// Be aware that this is called before [`Hooks::on_initialize`] of any pallet; therefore, a lot + /// of the critical storage items such as `block_number` in system pallet might have not been + /// set. /// - /// NOTE: this doesn't include all pallet logic triggered on runtime upgrade. For instance it - /// doesn't include the write of the pallet version in storage. The final complete logic - /// triggered on runtime upgrade is given by implementation of `OnRuntimeUpgrade` trait by - /// `Pallet`. + /// Vert similar to [`Hooks::on_initialize`], any code in this block is mandatory and MUST + /// execute. Use with care. /// - /// # Warning + /// ## Implementation Note: Versioning /// - /// This function will be called before we initialized any runtime state, aka `on_initialize` - /// wasn't called yet. So, information like the block number and any other block local data are - /// not accessible. + /// 1. An implementation of this should typically follow a pattern where the version of the + /// pallet is checked against the onchain version, and a decision is made about what needs to be + /// done. This is helpful to prevent accidental repetitive execution of this hook, which can be + /// catastrophic. /// - /// Return the non-negotiable weight consumed for runtime upgrade. + /// Alternatively, `migrations::VersionedRuntimeUpgrade` can be used to assist with + /// this. /// - /// While this function can be freely implemented, using `on_runtime_upgrade` from inside the - /// pallet is discouraged and might get deprecated in the future. Alternatively, export the same - /// logic as a free-function from your pallet, and pass it to `type Executive` from the - /// top-level runtime. + /// ## Implementation Note: Runtime Level Migration + /// + /// Additional "upgrade hooks" can be created by pallets by a manual implementation of + /// [`Hooks::on_runtime_upgrade`] which can be passed on to `Executive` at the top level + /// runtime. fn on_runtime_upgrade() -> Weight { Weight::zero() } @@ -272,7 +345,7 @@ pub trait Hooks { /// /// This hook should not alter any storage. #[cfg(feature = "try-runtime")] - fn try_state(_n: BlockNumber) -> Result<(), &'static str> { + fn try_state(_n: BlockNumber) -> Result<(), TryRuntimeError> { Ok(()) } @@ -284,7 +357,7 @@ pub trait Hooks { /// /// This hook is never meant to be executed on-chain but is meant to be used by testing tools. #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { + fn pre_upgrade() -> Result, TryRuntimeError> { Ok(Vec::new()) } @@ -296,42 +369,63 @@ pub trait Hooks { /// /// This hook is never meant to be executed on-chain but is meant to be used by testing tools. #[cfg(feature = "try-runtime")] - fn post_upgrade(_state: Vec) -> Result<(), &'static str> { + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { Ok(()) } - /// Implementing this function on a module allows you to perform long-running tasks - /// that make (by default) validators generate transactions that feed results - /// of those long-running computations back on chain. + /// Implementing this function on a pallet allows you to perform long-running tasks that are + /// dispatched as separate threads, and entirely independent of the main wasm runtime. /// - /// NOTE: This function runs off-chain, so it can access the block state, - /// but cannot preform any alterations. More specifically alterations are - /// not forbidden, but they are not persisted in any way after the worker - /// has finished. + /// This function can freely read from the state, but any change it makes to the state is + /// meaningless. Writes can be pushed back to the chain by submitting extrinsics from the + /// offchain worker to the transaction pool. See `pallet-example-offchain-worker` for more + /// details on this. /// - /// This function is being called after every block import (when fully synced). + /// Moreover, the code in this function has access to a wider range of host functions in + /// [`sp-io`], namely [`sp_io::offchain`]. This includes exotic operations such as HTTP calls + /// that are not really possible in the rest of the runtime code. /// - /// Implement this and use any of the `Offchain` `sp_io` set of APIs - /// to perform off-chain computations, calls and submit transactions - /// with results to trigger any on-chain changes. - /// Any state alterations are lost and are not persisted. + /// The execution of this hook is entirely optional and is left at the discretion of the + /// node-side software and its configuration. In a normal substrate-cli, look for the CLI + /// flags related to offchain-workers to learn more. fn offchain_worker(_n: BlockNumber) {} - /// Run integrity test. + /// Check the integrity of this pallet's configuration. + /// + /// Any code located in this hook is placed in an auto-generated test, and generated as a part + /// of [`crate::construct_runtime`]'s expansion. Look for a test case with a name along the + /// lines of: `__construct_runtime_integrity_test`. + /// + /// This hook is the location where the values/types provided to the `Config` trait + /// of the pallet can be tested for correctness. For example, if two `type Foo: Get` and + /// `type Bar: Get` where `Foo::get()` must always be greater than `Bar::get()`, such + /// checks can be asserted upon here. /// - /// The test is not executed in a externalities provided environment. + /// Note that this hook is executed in an externality environment, provided by + /// `sp_io::TestExternalities`. This makes it possible to access the storage. fn integrity_test() {} } +/// A trait to define the build function of a genesis config for both runtime and pallets. +/// +/// Replaces deprecated [`GenesisBuild`]. +pub trait BuildGenesisConfig: Default + sp_runtime::traits::MaybeSerializeDeserialize { + /// The build function puts initial `GenesisConfig` keys/values pairs into the storage. + fn build(&self); +} + /// A trait to define the build function of a genesis config, T and I are placeholder for pallet /// trait and pallet instance. -#[cfg(feature = "std")] +#[deprecated( + note = "GenesisBuild is planned to be removed in December 2023. Use BuildGenesisConfig instead of it." +)] pub trait GenesisBuild: Default + sp_runtime::traits::MaybeSerializeDeserialize { /// The build function is called within an externalities allowing storage APIs. /// Thus one can write to storage using regular pallet storages. fn build(&self); /// Build the storage using `build` inside default storage. + #[cfg(feature = "std")] fn build_storage(&self) -> Result { let mut storage = Default::default(); self.assimilate_storage(&mut storage)?; @@ -339,6 +433,7 @@ pub trait GenesisBuild: Default + sp_runtime::traits::MaybeSerializeD } /// Assimilate the storage for this module into pre-existing overlays. + #[cfg(feature = "std")] fn assimilate_storage(&self, storage: &mut sp_runtime::Storage) -> Result<(), String> { sp_state_machine::BasicExternalities::execute_with_storage(storage, || { self.build(); @@ -359,26 +454,80 @@ pub trait OnTimestampSet { #[cfg(test)] mod tests { use super::*; + use sp_io::TestExternalities; + + #[cfg(feature = "try-runtime")] + #[test] + fn on_runtime_upgrade_pre_post_executed_tuple() { + crate::parameter_types! { + pub static Pre: Vec<&'static str> = Default::default(); + pub static Post: Vec<&'static str> = Default::default(); + } + + macro_rules! impl_test_type { + ($name:ident) => { + struct $name; + impl OnRuntimeUpgrade for $name { + fn on_runtime_upgrade() -> Weight { + Default::default() + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + Pre::mutate(|s| s.push(stringify!($name))); + Ok(Vec::new()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_: Vec) -> Result<(), TryRuntimeError> { + Post::mutate(|s| s.push(stringify!($name))); + Ok(()) + } + } + }; + } + + impl_test_type!(Foo); + impl_test_type!(Bar); + impl_test_type!(Baz); + + TestExternalities::default().execute_with(|| { + Foo::try_on_runtime_upgrade(true).unwrap(); + assert_eq!(Pre::take(), vec!["Foo"]); + assert_eq!(Post::take(), vec!["Foo"]); + + <(Foo, Bar, Baz)>::try_on_runtime_upgrade(true).unwrap(); + assert_eq!(Pre::take(), vec!["Foo", "Bar", "Baz"]); + assert_eq!(Post::take(), vec!["Foo", "Bar", "Baz"]); + + <((Foo, Bar), Baz)>::try_on_runtime_upgrade(true).unwrap(); + assert_eq!(Pre::take(), vec!["Foo", "Bar", "Baz"]); + assert_eq!(Post::take(), vec!["Foo", "Bar", "Baz"]); + + <(Foo, (Bar, Baz))>::try_on_runtime_upgrade(true).unwrap(); + assert_eq!(Pre::take(), vec!["Foo", "Bar", "Baz"]); + assert_eq!(Post::take(), vec!["Foo", "Bar", "Baz"]); + }); + } #[test] fn on_initialize_and_on_runtime_upgrade_weight_merge_works() { - use sp_io::TestExternalities; struct Test; impl OnInitialize for Test { fn on_initialize(_n: u8) -> Weight { - Weight::from_ref_time(10) + Weight::from_parts(10, 0) } } impl OnRuntimeUpgrade for Test { fn on_runtime_upgrade() -> Weight { - Weight::from_ref_time(20) + Weight::from_parts(20, 0) } } TestExternalities::default().execute_with(|| { - assert_eq!(<(Test, Test)>::on_initialize(0), Weight::from_ref_time(20)); - assert_eq!(<(Test, Test)>::on_runtime_upgrade(), Weight::from_ref_time(40)); + assert_eq!(<(Test, Test)>::on_initialize(0), Weight::from_parts(20, 0)); + assert_eq!(<(Test, Test)>::on_runtime_upgrade(), Weight::from_parts(40, 0)); }); } diff --git a/frame/support/src/traits/members.rs b/frame/support/src/traits/members.rs index daf2d3aa6517..fbba742ebeb4 100644 --- a/frame/support/src/traits/members.rs +++ b/frame/support/src/traits/members.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,8 @@ //! Traits for dealing with the idea of membership. use impl_trait_for_tuples::impl_for_tuples; +use sp_arithmetic::traits::AtLeast16BitUnsigned; +use sp_runtime::DispatchResult; use sp_std::{marker::PhantomData, prelude::*}; /// A trait for querying whether a type can be said to "contain" a value. @@ -265,6 +267,28 @@ pub trait ContainsLengthBound { fn max_len() -> usize; } +/// Ranked membership data structure. +pub trait RankedMembers { + type AccountId; + type Rank: AtLeast16BitUnsigned; + + /// The lowest rank possible in this membership organisation. + fn min_rank() -> Self::Rank; + + /// Return the rank of the given ID, or `None` if they are not a member. + fn rank_of(who: &Self::AccountId) -> Option; + + /// Add a member to the group at the `min_rank()`. + fn induct(who: &Self::AccountId) -> DispatchResult; + + /// Promote a member to the next higher rank. + fn promote(who: &Self::AccountId) -> DispatchResult; + + /// Demote a member to the next lower rank; demoting beyond the `min_rank` removes the + /// member entirely. + fn demote(who: &Self::AccountId) -> DispatchResult; +} + /// Trait for type that can handle the initialization of account IDs at genesis. pub trait InitializeMembers { /// Initialize the members to the given `members`. diff --git a/frame/support/src/traits/messages.rs b/frame/support/src/traits/messages.rs index 9b86c421ad9e..36fa7957dff7 100644 --- a/frame/support/src/traits/messages.rs +++ b/frame/support/src/traits/messages.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,7 +22,7 @@ use scale_info::TypeInfo; use sp_core::{ConstU32, Get, TypedGet}; use sp_runtime::{traits::Convert, BoundedSlice, RuntimeDebug}; use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; -use sp_weights::Weight; +use sp_weights::{Weight, WeightMeter}; /// Errors that can happen when attempting to process a message with /// [`ProcessMessage::process_message()`]. @@ -38,6 +38,13 @@ pub enum ProcessMessageError { /// would be respected. The parameter gives the maximum weight which the message could take /// to process. Overweight(Weight), + /// The queue wants to give up its current processing slot. + /// + /// Hints the message processor to cease servicing this queue and proceed to the next + /// one. This is seen as a *hint*, not an instruction. Implementations must therefore handle + /// the case that a queue is re-serviced within the same block after *yielding*. A queue is + /// not required to *yield* again when it is being re-serviced withing the same block. + Yield, } /// Can process messages from a specific origin. @@ -45,12 +52,15 @@ pub trait ProcessMessage { /// The transport from where a message originates. type Origin: FullCodec + MaxEncodedLen + Clone + Eq + PartialEq + TypeInfo + Debug; - /// Process the given message, using no more than `weight_limit` in weight to do so. + /// Process the given message, using no more than the remaining `meter` weight to do so. + /// + /// Returns whether the message was processed. fn process_message( message: &[u8], origin: Self::Origin, - weight_limit: Weight, - ) -> Result<(bool, Weight), ProcessMessageError>; + meter: &mut WeightMeter, + id: &mut [u8; 32], + ) -> Result; } /// Errors that can happen when attempting to execute an overweight message with @@ -59,8 +69,18 @@ pub trait ProcessMessage { pub enum ExecuteOverweightError { /// The referenced message was not found. NotFound, + /// The message was already processed. + /// + /// This can be treated as success condition. + AlreadyProcessed, /// The available weight was insufficient to execute the message. InsufficientWeight, + /// The queue is paused and no message can be executed from it. + /// + /// This can change at any time and may resolve in the future by re-trying. + QueuePaused, + /// An unspecified error. + Other, } /// Can service queues and execute overweight messages. @@ -85,6 +105,16 @@ pub trait ServiceQueues { } } +/// Services queues by doing nothing. +pub struct NoopServiceQueues(PhantomData); +impl ServiceQueues for NoopServiceQueues { + type OverweightMessageAddress = OverweightAddr; + + fn service_queues(_: Weight) -> Weight { + Weight::zero() + } +} + /// The resource footprint of a queue. #[derive(Default, Copy, Clone, Eq, PartialEq, RuntimeDebug)] pub struct Footprint { @@ -200,3 +230,15 @@ where E::footprint(O::get()) } } + +/// Provides information on paused queues. +pub trait QueuePausedQuery { + /// Whether this queue is paused. + fn is_paused(origin: &Origin) -> bool; +} + +impl QueuePausedQuery for () { + fn is_paused(_: &Origin) -> bool { + false + } +} diff --git a/frame/support/src/traits/metadata.rs b/frame/support/src/traits/metadata.rs index 42f2d759a597..85d8f9a5a74e 100644 --- a/frame/support/src/traits/metadata.rs +++ b/frame/support/src/traits/metadata.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ use codec::{Decode, Encode}; use impl_trait_for_tuples::impl_for_tuples; use sp_runtime::RuntimeDebug; -use sp_std::prelude::*; +use sp_std::{ops::Add, prelude::*}; /// Provides information about the pallet itself and its setup in the runtime. /// @@ -101,12 +101,20 @@ pub struct CallMetadata { /// Gets the function name of the Call. pub trait GetCallName { - /// Return all function names. + /// Return all function names in the same order as [`GetCallIndex`]. fn get_call_names() -> &'static [&'static str]; /// Return the function name of the Call. fn get_call_name(&self) -> &'static str; } +/// Gets the function index of the Call. +pub trait GetCallIndex { + /// Return all call indices in the same order as [`GetCallName`]. + fn get_call_indices() -> &'static [u8]; + /// Return the index of this Call. + fn get_call_index(&self) -> u8; +} + /// Gets the metadata for the Call - function name and pallet name. pub trait GetCallMetadata { /// Return all module names. @@ -224,6 +232,24 @@ impl PartialOrd for StorageVersion { } } +impl Add for StorageVersion { + type Output = StorageVersion; + + fn add(self, rhs: u16) -> Self::Output { + Self::new(self.0 + rhs) + } +} + +/// Special marker struct if no storage version is set for a pallet. +/// +/// If you (the reader) end up here, it probably means that you tried to compare +/// [`GetStorageVersion::on_chain_storage_version`] against +/// [`GetStorageVersion::current_storage_version`]. This basically means that the +/// [`storage_version`](crate::pallet_macros::storage_version) is missing in the pallet where the +/// mentioned functions are being called. +#[derive(Debug, Default)] +pub struct NoStorageVersionSet; + /// Provides information about the storage version of a pallet. /// /// It differentiates between current and on-chain storage version. Both should be only out of sync @@ -236,8 +262,18 @@ impl PartialOrd for StorageVersion { /// /// It is required to update the on-chain storage version manually when a migration was applied. pub trait GetStorageVersion { + /// This will be filled out by the [`pallet`](crate::pallet) macro. + /// + /// If the [`storage_version`](crate::pallet_macros::storage_version) attribute isn't given + /// this is set to [`NoStorageVersionSet`] to inform the user that the attribute is missing. + /// This should prevent that the user forgets to set a storage version when required. However, + /// this will only work when the user actually tries to call [`Self::current_storage_version`] + /// to compare it against the [`Self::on_chain_storage_version`]. If the attribute is given, + /// this will be set to [`StorageVersion`]. + type CurrentStorageVersion; + /// Returns the current storage version as supported by the pallet. - fn current_storage_version() -> StorageVersion; + fn current_storage_version() -> Self::CurrentStorageVersion; /// Returns the on-chain storage version of the pallet as stored in the storage. fn on_chain_storage_version() -> StorageVersion; } diff --git a/frame/support/src/traits/misc.rs b/frame/support/src/traits/misc.rs index 734b84ebd9d8..85eb7b47e26d 100644 --- a/frame/support/src/traits/misc.rs +++ b/frame/support/src/traits/misc.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ //! Smaller traits used in FRAME which don't need their own file. -use crate::dispatch::Parameter; +use crate::dispatch::{DispatchResult, Parameter}; use codec::{CompactLen, Decode, DecodeLimit, Encode, EncodeLike, Input, MaxEncodedLen}; use impl_trait_for_tuples::impl_for_tuples; use scale_info::{build::Fields, meta_type, Path, Type, TypeInfo, TypeParameter}; @@ -49,7 +49,7 @@ macro_rules! defensive { ); debug_assert!(false, "{}", $crate::traits::DEFENSIVE_OP_INTERNAL_ERROR); }; - ($error:tt) => { + ($error:expr $(,)?) => { frame_support::log::error!( target: "runtime", "{}: {:?}", @@ -58,7 +58,7 @@ macro_rules! defensive { ); debug_assert!(false, "{}: {:?}", $crate::traits::DEFENSIVE_OP_INTERNAL_ERROR, $error); }; - ($error:tt, $proof:tt) => { + ($error:expr, $proof:expr $(,)?) => { frame_support::log::error!( target: "runtime", "{}: {:?}: {:?}", @@ -70,6 +70,25 @@ macro_rules! defensive { } } +/// Trigger a defensive failure if a condition is not met. +/// +/// Similar to [`assert!`] but will print an error without `debug_assertions` instead of silently +/// ignoring it. Only accepts one instead of variable formatting arguments. +/// +/// # Example +/// +/// ```should_panic +/// frame_support::defensive_assert!(1 == 0, "Must fail") +/// ``` +#[macro_export] +macro_rules! defensive_assert { + ($cond:expr $(, $proof:expr )? $(,)?) => { + if !($cond) { + $crate::defensive!(::core::stringify!($cond) $(, $proof )?); + } + }; +} + /// Prelude module for all defensive traits to be imported at once. pub mod defensive_prelude { pub use super::{Defensive, DefensiveOption, DefensiveResult}; @@ -874,7 +893,8 @@ pub trait ExtrinsicCall: sp_runtime::traits::Extrinsic { #[cfg(feature = "std")] impl ExtrinsicCall for sp_runtime::testing::TestXt where - Call: codec::Codec + Sync + Send, + Call: codec::Codec + Sync + Send + TypeInfo, + Extra: TypeInfo, { fn call(&self) -> &Self::Call { &self.call @@ -884,7 +904,10 @@ where impl ExtrinsicCall for sp_runtime::generic::UncheckedExtrinsic where - Extra: sp_runtime::traits::SignedExtension, + Address: TypeInfo, + Call: TypeInfo, + Signature: TypeInfo, + Extra: sp_runtime::traits::SignedExtension + TypeInfo, { fn call(&self) -> &Self::Call { &self.function @@ -1135,12 +1158,46 @@ impl PreimageRecipient for () { fn unnote_preimage(_: &Hash) {} } +/// Trait for creating an asset account with a deposit taken from a designated depositor specified +/// by the client. +pub trait AccountTouch { + /// The type for currency units of the deposit. + type Balance; + + /// The deposit amount of a native currency required for creating an account of the `asset`. + fn deposit_required(asset: AssetId) -> Self::Balance; + + /// Create an account for `who` of the `asset` with a deposit taken from the `depositor`. + fn touch(asset: AssetId, who: AccountId, depositor: AccountId) -> DispatchResult; +} + #[cfg(test)] mod test { use super::*; use sp_core::bounded::{BoundedSlice, BoundedVec}; use sp_std::marker::PhantomData; + #[test] + fn defensive_assert_works() { + defensive_assert!(true); + defensive_assert!(true,); + defensive_assert!(true, "must work"); + defensive_assert!(true, "must work",); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic(expected = "Defensive failure has been triggered!: \"1 == 0\": \"Must fail\"")] + fn defensive_assert_panics() { + defensive_assert!(1 == 0, "Must fail"); + } + + #[test] + #[cfg(not(debug_assertions))] + fn defensive_assert_does_not_panic() { + defensive_assert!(1 == 0, "Must fail"); + } + #[test] #[cfg(not(debug_assertions))] fn defensive_saturating_accrue_works() { diff --git a/frame/support/src/traits/preimages.rs b/frame/support/src/traits/preimages.rs index 594532ba9690..db8abdbdc79b 100644 --- a/frame/support/src/traits/preimages.rs +++ b/frame/support/src/traits/preimages.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,6 +26,9 @@ use sp_std::borrow::Cow; pub type Hash = H256; pub type BoundedInline = crate::BoundedVec>; +/// The maximum we expect a single legacy hash lookup to be. +const MAX_LEGACY_LEN: u32 = 1_000_000; + #[derive( Encode, Decode, MaxEncodedLen, Clone, Eq, PartialEq, scale_info::TypeInfo, RuntimeDebug, )] @@ -67,20 +70,25 @@ impl Bounded { /// Returns the hash of the preimage. /// /// The hash is re-calculated every time if the preimage is inlined. - pub fn hash(&self) -> H256 { + pub fn hash(&self) -> Hash { use Bounded::*; match self { - Legacy { hash, .. } => *hash, + Lookup { hash, .. } | Legacy { hash, .. } => *hash, Inline(x) => blake2_256(x.as_ref()).into(), - Lookup { hash, .. } => *hash, } } -} -// The maximum we expect a single legacy hash lookup to be. -const MAX_LEGACY_LEN: u32 = 1_000_000; + /// Returns the hash to lookup the preimage. + /// + /// If this is a `Bounded::Inline`, `None` is returned as no lookup is required. + pub fn lookup_hash(&self) -> Option { + use Bounded::*; + match self { + Lookup { hash, .. } | Legacy { hash, .. } => Some(*hash), + Inline(_) => None, + } + } -impl Bounded { /// Returns the length of the preimage or `None` if the length is unknown. pub fn len(&self) -> Option { match self { @@ -168,8 +176,11 @@ pub trait QueryPreimage { } } - /// Create a `Bounded` instance based on the `hash` and `len` of the encoded value. This may not - /// be `peek`-able or `realize`-able. + /// Create a `Bounded` instance based on the `hash` and `len` of the encoded value. + /// + /// It also directly requests the given `hash` using [`Self::request`]. + /// + /// This may not be `peek`-able or `realize`-able. fn pick(hash: Hash, len: u32) -> Bounded { Self::request(&hash); Bounded::Lookup { hash, len } @@ -228,10 +239,12 @@ pub trait StorePreimage: QueryPreimage { Self::unrequest(hash) } - /// Convert an otherwise unbounded or large value into a type ready for placing in storage. The - /// result is a type whose `MaxEncodedLen` is 131 bytes. + /// Convert an otherwise unbounded or large value into a type ready for placing in storage. + /// + /// The result is a type whose `MaxEncodedLen` is 131 bytes. /// /// NOTE: Once this API is used, you should use either `drop` or `realize`. + /// The value is also noted using [`Self::note`]. fn bound(t: T) -> Result, DispatchError> { let data = t.encode(); let len = data.len() as u32; diff --git a/frame/support/src/traits/randomness.rs b/frame/support/src/traits/randomness.rs index d68b95b1dfcf..3666a486465f 100644 --- a/frame/support/src/traits/randomness.rs +++ b/frame/support/src/traits/randomness.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/schedule.rs b/frame/support/src/traits/schedule.rs index b8e6a7f80790..74a5951142d5 100644 --- a/frame/support/src/traits/schedule.rs +++ b/frame/support/src/traits/schedule.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -438,6 +438,8 @@ pub mod v3 { /// Schedule a dispatch to happen at the beginning of some block in the future. /// /// - `id`: The identity of the task. This must be unique and will return an error if not. + /// + /// NOTE: This will request `call` to be made available. fn schedule_named( id: TaskName, when: DispatchTime, diff --git a/frame/support/src/traits/storage.rs b/frame/support/src/traits/storage.rs index 24653d189983..5947be57ae1c 100644 --- a/frame/support/src/traits/storage.rs +++ b/frame/support/src/traits/storage.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +20,7 @@ use crate::sp_std::collections::btree_set::BTreeSet; use impl_trait_for_tuples::impl_for_tuples; pub use sp_core::storage::TrackedStorageKey; +use sp_runtime::traits::Saturating; use sp_std::prelude::*; /// An instance of a pallet in the storage. @@ -36,6 +37,12 @@ pub trait Instance: 'static { const INDEX: u8; } +// Dummy implementation for `()`. +impl Instance for () { + const PREFIX: &'static str = ""; + const INDEX: u8 = 0; +} + /// An instance of a storage in a pallet. /// /// Define an instance for an individual storage inside a pallet. @@ -53,7 +60,9 @@ pub trait StorageInstance { } /// Metadata about storage from the runtime. -#[derive(codec::Encode, codec::Decode, crate::RuntimeDebug, Eq, PartialEq, Clone)] +#[derive( + codec::Encode, codec::Decode, crate::RuntimeDebug, Eq, PartialEq, Clone, scale_info::TypeInfo, +)] pub struct StorageInfo { /// Encoded string of pallet name. pub pallet_name: Vec, @@ -118,3 +127,29 @@ impl WhitelistedStorageKeys for Tuple { combined_keys.into_iter().collect::>() } } + +macro_rules! impl_incrementable { + ($($type:ty),+) => { + $( + impl Incrementable for $type { + fn increment(&self) -> Self { + let mut val = self.clone(); + val.saturating_inc(); + val + } + + fn initial_value() -> Self { + 0 + } + } + )+ + }; +} + +/// For example: allows new identifiers to be created in a linear fashion. +pub trait Incrementable { + fn increment(&self) -> Self; + fn initial_value() -> Self; +} + +impl_incrementable!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); diff --git a/frame/support/src/traits/stored_map.rs b/frame/support/src/traits/stored_map.rs index 2aae88096ec7..cbe70f293234 100644 --- a/frame/support/src/traits/stored_map.rs +++ b/frame/support/src/traits/stored_map.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ //! Traits and associated datatypes for managing abstract stored values. -use crate::{storage::StorageMap, traits::misc::HandleLifetime}; +use crate::storage::StorageMap; use codec::FullCodec; use sp_runtime::DispatchError; @@ -81,48 +81,29 @@ pub trait StoredMap { /// be the default value), or where the account is being removed or reset back to the default value /// where previously it did exist (though may have been in a default state). This works well with /// system module's `CallOnCreatedAccount` and `CallKillAccount`. -pub struct StorageMapShim(sp_std::marker::PhantomData<(S, L, K, T)>); -impl< - S: StorageMap, - L: HandleLifetime, - K: FullCodec, - T: FullCodec + Default, - > StoredMap for StorageMapShim +pub struct StorageMapShim(sp_std::marker::PhantomData<(S, K, T)>); +impl, K: FullCodec, T: FullCodec + Default> StoredMap + for StorageMapShim { fn get(k: &K) -> T { S::get(k) } fn insert(k: &K, t: T) -> Result<(), DispatchError> { - if !S::contains_key(&k) { - L::created(k)?; - } S::insert(k, t); Ok(()) } fn remove(k: &K) -> Result<(), DispatchError> { if S::contains_key(&k) { - L::killed(k)?; S::remove(k); } Ok(()) } fn mutate(k: &K, f: impl FnOnce(&mut T) -> R) -> Result { - if !S::contains_key(&k) { - L::created(k)?; - } Ok(S::mutate(k, f)) } fn mutate_exists(k: &K, f: impl FnOnce(&mut Option) -> R) -> Result { S::try_mutate_exists(k, |maybe_value| { - let existed = maybe_value.is_some(); let r = f(maybe_value); - let exists = maybe_value.is_some(); - - if !existed && exists { - L::created(k)?; - } else if existed && !exists { - L::killed(k)?; - } Ok(r) }) } @@ -131,15 +112,7 @@ impl< f: impl FnOnce(&mut Option) -> Result, ) -> Result { S::try_mutate_exists(k, |maybe_value| { - let existed = maybe_value.is_some(); let r = f(maybe_value)?; - let exists = maybe_value.is_some(); - - if !existed && exists { - L::created(k).map_err(E::from)?; - } else if existed && !exists { - L::killed(k).map_err(E::from)?; - } Ok(r) }) } diff --git a/frame/support/src/traits/tokens.rs b/frame/support/src/traits/tokens.rs index 03a24bd3ba9c..253b49c6671f 100644 --- a/frame/support/src/traits/tokens.rs +++ b/frame/support/src/traits/tokens.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,7 +27,10 @@ pub mod nonfungible_v2; pub mod nonfungibles; pub mod nonfungibles_v2; pub use imbalance::Imbalance; +pub mod pay; pub use misc::{ - AssetId, AttributeNamespace, Balance, BalanceConversion, BalanceStatus, DepositConsequence, - ExistenceRequirement, Locker, WithdrawConsequence, WithdrawReasons, + AssetId, Balance, BalanceStatus, ConversionFromAssetBalance, ConversionToAssetBalance, + ConvertRank, DepositConsequence, ExistenceRequirement, Fortitude, GetSalary, Locker, Precision, + Preservation, Provenance, Restriction, WithdrawConsequence, WithdrawReasons, }; +pub use pay::{Pay, PayFromAccount, PaymentStatus}; diff --git a/frame/support/src/traits/tokens/currency.rs b/frame/support/src/traits/tokens/currency.rs index 48247b602179..1bc91b6ae5f9 100644 --- a/frame/support/src/traits/tokens/currency.rs +++ b/frame/support/src/traits/tokens/currency.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -124,8 +124,7 @@ pub trait Currency { /// Transfer some liquid free balance to another staker. /// - /// This is a very high-level function. It will ensure all appropriate fees are paid - /// and no imbalance in the system remains. + /// This is a very high-level function. It will ensure no imbalance in the system remains. fn transfer( source: &AccountId, dest: &AccountId, diff --git a/frame/support/src/traits/tokens/currency/lockable.rs b/frame/support/src/traits/tokens/currency/lockable.rs index a10edd6e3e87..955814f5aa9d 100644 --- a/frame/support/src/traits/tokens/currency/lockable.rs +++ b/frame/support/src/traits/tokens/currency/lockable.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/tokens/currency/reservable.rs b/frame/support/src/traits/tokens/currency/reservable.rs index 53f6764c3a1a..aa097a756d4d 100644 --- a/frame/support/src/traits/tokens/currency/reservable.rs +++ b/frame/support/src/traits/tokens/currency/reservable.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/tokens/fungible.rs b/frame/support/src/traits/tokens/fungible.rs deleted file mode 100644 index a36e63e717f9..000000000000 --- a/frame/support/src/traits/tokens/fungible.rs +++ /dev/null @@ -1,367 +0,0 @@ -// 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. - -//! The traits for dealing with a single fungible token class and any associated types. - -use super::{ - misc::{Balance, DepositConsequence, WithdrawConsequence}, - *, -}; -use crate::{ - dispatch::{DispatchError, DispatchResult}, - traits::misc::Get, -}; -use sp_runtime::traits::Saturating; - -mod balanced; -mod imbalance; -pub use balanced::{Balanced, Unbalanced}; -pub use imbalance::{CreditOf, DebtOf, HandleImbalanceDrop, Imbalance}; - -/// Trait for providing balance-inspection access to a fungible asset. -pub trait Inspect { - /// Scalar type for representing balance of an account. - type Balance: Balance; - - /// The total amount of issuance in the system. - fn total_issuance() -> Self::Balance; - - /// The total amount of issuance in the system excluding those which are controlled by the - /// system. - fn active_issuance() -> Self::Balance { - Self::total_issuance() - } - - /// The minimum balance any single account may have. - fn minimum_balance() -> Self::Balance; - - /// Get the balance of `who`. - fn balance(who: &AccountId) -> Self::Balance; - - /// Get the maximum amount that `who` can withdraw/transfer successfully. - fn reducible_balance(who: &AccountId, keep_alive: bool) -> Self::Balance; - - /// Returns `true` if the balance of `who` may be increased by `amount`. - /// - /// - `who`: The account of which the balance should be increased by `amount`. - /// - `amount`: How much should the balance be increased? - /// - `mint`: Will `amount` be minted to deposit it into `account`? - fn can_deposit(who: &AccountId, amount: Self::Balance, mint: bool) -> DepositConsequence; - - /// Returns `Failed` if the balance of `who` may not be decreased by `amount`, otherwise - /// the consequence. - fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence; -} - -/// Trait for providing an ERC-20 style fungible asset. -pub trait Mutate: Inspect { - /// Increase the balance of `who` by exactly `amount`, minting new tokens. If that isn't - /// possible then an `Err` is returned and nothing is changed. - fn mint_into(who: &AccountId, amount: Self::Balance) -> DispatchResult; - - /// Decrease the balance of `who` by at least `amount`, possibly slightly more in the case of - /// minimum_balance requirements, burning the tokens. If that isn't possible then an `Err` is - /// returned and nothing is changed. If successful, the amount of tokens reduced is returned. - fn burn_from(who: &AccountId, amount: Self::Balance) -> Result; - - // TODO: Remove. - /// Attempt to reduce the balance of `who` by as much as possible up to `amount`, and possibly - /// slightly more due to minimum_balance requirements. If no decrease is possible then an `Err` - /// is returned and nothing is changed. If successful, the amount of tokens reduced is returned. - /// - /// The default implementation just uses `withdraw` along with `reducible_balance` to ensure - /// that it doesn't fail. - fn slash(who: &AccountId, amount: Self::Balance) -> Result { - Self::burn_from(who, Self::reducible_balance(who, false).min(amount)) - } - - /// Transfer funds from one account into another. The default implementation uses `mint_into` - /// and `burn_from` and may generate unwanted events. - fn teleport( - source: &AccountId, - dest: &AccountId, - amount: Self::Balance, - ) -> Result { - let extra = Self::can_withdraw(&source, amount).into_result()?; - // As we first burn and then mint, we don't need to check if `mint` fits into the supply. - // If we can withdraw/burn it, we can also mint it again. - Self::can_deposit(dest, amount.saturating_add(extra), false).into_result()?; - let actual = Self::burn_from(source, amount)?; - debug_assert!( - actual == amount.saturating_add(extra), - "can_withdraw must agree with withdraw; qed" - ); - match Self::mint_into(dest, actual) { - Ok(_) => Ok(actual), - Err(err) => { - debug_assert!(false, "can_deposit returned true previously; qed"); - // attempt to return the funds back to source - let revert = Self::mint_into(source, actual); - debug_assert!(revert.is_ok(), "withdrew funds previously; qed"); - Err(err) - }, - } - } -} - -/// Trait for providing a fungible asset which can only be transferred. -pub trait Transfer: Inspect { - /// Transfer funds from one account into another. - fn transfer( - source: &AccountId, - dest: &AccountId, - amount: Self::Balance, - keep_alive: bool, - ) -> Result; - - /// Reduce the active issuance by some amount. - fn deactivate(_: Self::Balance) {} - - /// Increase the active issuance by some amount, up to the outstanding amount reduced. - fn reactivate(_: Self::Balance) {} -} - -/// Trait for inspecting a fungible asset which can be reserved. -pub trait InspectHold: Inspect { - /// Amount of funds held in reserve by `who`. - fn balance_on_hold(who: &AccountId) -> Self::Balance; - - /// Check to see if some `amount` of funds of `who` may be placed on hold. - fn can_hold(who: &AccountId, amount: Self::Balance) -> bool; -} - -// TODO: Introduce `HoldReason`. -/// Trait for mutating a fungible asset which can be reserved. -pub trait MutateHold: InspectHold + Transfer { - /// Hold some funds in an account. - fn hold(who: &AccountId, amount: Self::Balance) -> DispatchResult; - - /// Release up to `amount` held funds in an account. - /// - /// The actual amount released is returned with `Ok`. - /// - /// If `best_effort` is `true`, then the amount actually unreserved and returned as the inner - /// value of `Ok` may be smaller than the `amount` passed. - fn release( - who: &AccountId, - amount: Self::Balance, - best_effort: bool, - ) -> Result; - - // TODO: Introduce repatriate_held - - /// Transfer held funds into a destination account. - /// - /// If `on_hold` is `true`, then the destination account must already exist and the assets - /// transferred will still be on hold in the destination account. If not, then the destination - /// account need not already exist, but must be creatable. - /// - /// If `best_effort` is `true`, then an amount less than `amount` may be transferred without - /// error. - /// - /// The actual amount transferred is returned, or `Err` in the case of error and nothing is - /// changed. - fn transfer_held( - source: &AccountId, - dest: &AccountId, - amount: Self::Balance, - best_effort: bool, - on_held: bool, - ) -> Result; -} - -/// Trait for slashing a fungible asset which can be reserved. -pub trait BalancedHold: Balanced + MutateHold { - /// Reduce the balance of some funds on hold in an account. - /// - /// The resulting imbalance is the first item of the tuple returned. - /// - /// As much funds that are on hold up to `amount` will be deducted as possible. If this is less - /// than `amount`, then a non-zero second item will be returned. - fn slash_held( - who: &AccountId, - amount: Self::Balance, - ) -> (CreditOf, Self::Balance); -} - -impl + MutateHold> BalancedHold for T { - // TODO: This should be implemented properly, and `slash` should be removed. - fn slash_held( - who: &AccountId, - amount: Self::Balance, - ) -> (CreditOf, Self::Balance) { - let actual = match Self::release(who, amount, true) { - Ok(x) => x, - Err(_) => return (Imbalance::default(), amount), - }; - >::slash(who, actual) - } -} - -/// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying -/// a single item. -pub struct ItemOf< - F: fungibles::Inspect, - A: Get<>::AssetId>, - AccountId, ->(sp_std::marker::PhantomData<(F, A, AccountId)>); - -impl< - F: fungibles::Inspect, - A: Get<>::AssetId>, - AccountId, - > Inspect for ItemOf -{ - type Balance = >::Balance; - fn total_issuance() -> Self::Balance { - >::total_issuance(A::get()) - } - fn active_issuance() -> Self::Balance { - >::active_issuance(A::get()) - } - fn minimum_balance() -> Self::Balance { - >::minimum_balance(A::get()) - } - fn balance(who: &AccountId) -> Self::Balance { - >::balance(A::get(), who) - } - fn reducible_balance(who: &AccountId, keep_alive: bool) -> Self::Balance { - >::reducible_balance(A::get(), who, keep_alive) - } - fn can_deposit(who: &AccountId, amount: Self::Balance, mint: bool) -> DepositConsequence { - >::can_deposit(A::get(), who, amount, mint) - } - fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence { - >::can_withdraw(A::get(), who, amount) - } -} - -impl< - F: fungibles::Mutate, - A: Get<>::AssetId>, - AccountId, - > Mutate for ItemOf -{ - fn mint_into(who: &AccountId, amount: Self::Balance) -> DispatchResult { - >::mint_into(A::get(), who, amount) - } - fn burn_from(who: &AccountId, amount: Self::Balance) -> Result { - >::burn_from(A::get(), who, amount) - } -} - -impl< - F: fungibles::Transfer, - A: Get<>::AssetId>, - AccountId, - > Transfer for ItemOf -{ - fn transfer( - source: &AccountId, - dest: &AccountId, - amount: Self::Balance, - keep_alive: bool, - ) -> Result { - >::transfer(A::get(), source, dest, amount, keep_alive) - } - fn deactivate(amount: Self::Balance) { - >::deactivate(A::get(), amount) - } - fn reactivate(amount: Self::Balance) { - >::reactivate(A::get(), amount) - } -} - -impl< - F: fungibles::InspectHold, - A: Get<>::AssetId>, - AccountId, - > InspectHold for ItemOf -{ - fn balance_on_hold(who: &AccountId) -> Self::Balance { - >::balance_on_hold(A::get(), who) - } - fn can_hold(who: &AccountId, amount: Self::Balance) -> bool { - >::can_hold(A::get(), who, amount) - } -} - -impl< - F: fungibles::MutateHold, - A: Get<>::AssetId>, - AccountId, - > MutateHold for ItemOf -{ - fn hold(who: &AccountId, amount: Self::Balance) -> DispatchResult { - >::hold(A::get(), who, amount) - } - fn release( - who: &AccountId, - amount: Self::Balance, - best_effort: bool, - ) -> Result { - >::release(A::get(), who, amount, best_effort) - } - fn transfer_held( - source: &AccountId, - dest: &AccountId, - amount: Self::Balance, - best_effort: bool, - on_hold: bool, - ) -> Result { - >::transfer_held( - A::get(), - source, - dest, - amount, - best_effort, - on_hold, - ) - } -} - -impl< - F: fungibles::Unbalanced, - A: Get<>::AssetId>, - AccountId, - > Unbalanced for ItemOf -{ - fn set_balance(who: &AccountId, amount: Self::Balance) -> DispatchResult { - >::set_balance(A::get(), who, amount) - } - fn set_total_issuance(amount: Self::Balance) -> () { - >::set_total_issuance(A::get(), amount) - } - fn decrease_balance( - who: &AccountId, - amount: Self::Balance, - ) -> Result { - >::decrease_balance(A::get(), who, amount) - } - fn decrease_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance { - >::decrease_balance_at_most(A::get(), who, amount) - } - fn increase_balance( - who: &AccountId, - amount: Self::Balance, - ) -> Result { - >::increase_balance(A::get(), who, amount) - } - fn increase_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance { - >::increase_balance_at_most(A::get(), who, amount) - } -} diff --git a/frame/support/src/traits/tokens/fungible/balanced.rs b/frame/support/src/traits/tokens/fungible/balanced.rs deleted file mode 100644 index 0e75ccc22d05..000000000000 --- a/frame/support/src/traits/tokens/fungible/balanced.rs +++ /dev/null @@ -1,354 +0,0 @@ -// 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. - -//! The trait and associated types for sets of fungible tokens that manage total issuance without -//! requiring atomic balanced operations. - -use super::{super::Imbalance as ImbalanceT, *}; -use crate::{ - dispatch::{DispatchError, DispatchResult}, - traits::misc::{SameOrOther, TryDrop}, -}; -use sp_runtime::{ - traits::{CheckedAdd, Zero}, - ArithmeticError, TokenError, -}; -use sp_std::marker::PhantomData; - -/// A fungible token class where any creation and deletion of tokens is semi-explicit and where the -/// total supply is maintained automatically. -/// -/// This is auto-implemented when a token class has `Unbalanced` implemented. -pub trait Balanced: Inspect { - /// The type for managing what happens when an instance of `Debt` is dropped without being used. - type OnDropDebt: HandleImbalanceDrop; - /// The type for managing what happens when an instance of `Credit` is dropped without being - /// used. - type OnDropCredit: HandleImbalanceDrop; - - /// Reduce the total issuance by `amount` and return the according imbalance. The imbalance will - /// typically be used to reduce an account by the same amount with e.g. `settle`. - /// - /// This is infallible, but doesn't guarantee that the entire `amount` is burnt, for example - /// in the case of underflow. - fn rescind(amount: Self::Balance) -> DebtOf; - - /// Increase the total issuance by `amount` and return the according imbalance. The imbalance - /// will typically be used to increase an account by the same amount with e.g. - /// `resolve_into_existing` or `resolve_creating`. - /// - /// This is infallible, but doesn't guarantee that the entire `amount` is issued, for example - /// in the case of overflow. - fn issue(amount: Self::Balance) -> CreditOf; - - /// Produce a pair of imbalances that cancel each other out exactly. - /// - /// This is just the same as burning and issuing the same amount and has no effect on the - /// total issuance. - fn pair(amount: Self::Balance) -> (DebtOf, CreditOf) { - (Self::rescind(amount), Self::issue(amount)) - } - - /// Deducts up to `value` from the combined balance of `who`. This function cannot fail. - /// - /// The resulting imbalance is the first item of the tuple returned. - /// - /// As much funds up to `value` will be deducted as possible. If this is less than `value`, - /// then a non-zero second item will be returned. - fn slash(who: &AccountId, amount: Self::Balance) -> (CreditOf, Self::Balance); - - /// Mints exactly `value` into the account of `who`. - /// - /// If `who` doesn't exist, nothing is done and an `Err` returned. This could happen because it - /// the account doesn't yet exist and it isn't possible to create it under the current - /// circumstances and with `value` in it. - fn deposit( - who: &AccountId, - value: Self::Balance, - ) -> Result, DispatchError>; - - /// Removes `value` balance from `who` account if possible. - /// - /// If the removal is not possible, then it returns `Err` and nothing is changed. - /// - /// If the operation is successful, this will return `Ok` with a `NegativeImbalance` whose value - /// is no less than `value`. It may be more in the case that removing it reduced it below - /// `Self::minimum_balance()`. - fn withdraw( - who: &AccountId, - value: Self::Balance, - // TODO: liveness: ExistenceRequirement, - ) -> Result, DispatchError>; - - /// The balance of `who` is increased in order to counter `credit`. If the whole of `credit` - /// cannot be countered, then nothing is changed and the original `credit` is returned in an - /// `Err`. - /// - /// Please note: If `credit.peek()` is less than `Self::minimum_balance()`, then `who` must - /// already exist for this to succeed. - fn resolve( - who: &AccountId, - credit: CreditOf, - ) -> Result<(), CreditOf> { - let v = credit.peek(); - let debt = match Self::deposit(who, v) { - Err(_) => return Err(credit), - Ok(d) => d, - }; - let result = credit.offset(debt).try_drop(); - debug_assert!(result.is_ok(), "ok deposit return must be equal to credit value; qed"); - Ok(()) - } - - /// The balance of `who` is decreased in order to counter `debt`. If the whole of `debt` - /// cannot be countered, then nothing is changed and the original `debt` is returned in an - /// `Err`. - fn settle( - who: &AccountId, - debt: DebtOf, - // TODO: liveness: ExistenceRequirement, - ) -> Result, DebtOf> { - let amount = debt.peek(); - let credit = match Self::withdraw(who, amount) { - Err(_) => return Err(debt), - Ok(d) => d, - }; - match credit.offset(debt) { - SameOrOther::None => Ok(CreditOf::::zero()), - SameOrOther::Same(dust) => Ok(dust), - SameOrOther::Other(rest) => { - debug_assert!(false, "ok withdraw return must be at least debt value; qed"); - Err(rest) - }, - } - } -} - -/// A fungible token class where the balance can be set arbitrarily. -/// -/// **WARNING** -/// Do not use this directly unless you want trouble, since it allows you to alter account balances -/// without keeping the issuance up to date. It has no safeguards against accidentally creating -/// token imbalances in your system leading to accidental imflation or deflation. It's really just -/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to -/// use. -pub trait Unbalanced: Inspect { - /// Set the balance of `who` to `amount`. If this cannot be done for some reason (e.g. - /// because the account cannot be created or an overflow) then an `Err` is returned. - fn set_balance(who: &AccountId, amount: Self::Balance) -> DispatchResult; - - /// Set the total issuance to `amount`. - fn set_total_issuance(amount: Self::Balance); - - /// Reduce the balance of `who` by `amount`. If it cannot be reduced by that amount for - /// some reason, return `Err` and don't reduce it at all. If Ok, return the imbalance. - /// - /// Minimum balance will be respected and the returned imbalance may be up to - /// `Self::minimum_balance() - 1` greater than `amount`. - fn decrease_balance( - who: &AccountId, - amount: Self::Balance, - ) -> Result { - let old_balance = Self::balance(who); - let (mut new_balance, mut amount) = if Self::reducible_balance(who, false) < amount { - return Err(TokenError::NoFunds.into()) - } else { - (old_balance - amount, amount) - }; - if new_balance < Self::minimum_balance() { - amount = amount.saturating_add(new_balance); - new_balance = Zero::zero(); - } - // Defensive only - this should not fail now. - Self::set_balance(who, new_balance)?; - Ok(amount) - } - - /// Reduce the balance of `who` by the most that is possible, up to `amount`. - /// - /// Minimum balance will be respected and the returned imbalance may be up to - /// `Self::minimum_balance() - 1` greater than `amount`. - /// - /// Return the imbalance by which the account was reduced. - fn decrease_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance { - let old_balance = Self::balance(who); - let old_free_balance = Self::reducible_balance(who, false); - let (mut new_balance, mut amount) = if old_free_balance < amount { - (old_balance.saturating_sub(old_free_balance), old_free_balance) - } else { - (old_balance - amount, amount) - }; - let minimum_balance = Self::minimum_balance(); - if new_balance < minimum_balance { - amount = amount.saturating_add(new_balance); - new_balance = Zero::zero(); - } - let mut r = Self::set_balance(who, new_balance); - if r.is_err() { - // Some error, probably because we tried to destroy an account which cannot be - // destroyed. - if new_balance.is_zero() && amount >= minimum_balance { - new_balance = minimum_balance; - amount -= minimum_balance; - r = Self::set_balance(who, new_balance); - } - if r.is_err() { - // Still an error. Apparently it's not possible to reduce at all. - amount = Zero::zero(); - } - } - amount - } - - /// Increase the balance of `who` by `amount`. If it cannot be increased by that amount - /// for some reason, return `Err` and don't increase it at all. If Ok, return the imbalance. - /// - /// Minimum balance will be respected and an error will be returned if - /// `amount < Self::minimum_balance()` when the account of `who` is zero. - fn increase_balance( - who: &AccountId, - amount: Self::Balance, - ) -> Result { - let old_balance = Self::balance(who); - let new_balance = old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?; - if new_balance < Self::minimum_balance() { - return Err(TokenError::BelowMinimum.into()) - } - if old_balance != new_balance { - Self::set_balance(who, new_balance)?; - } - Ok(amount) - } - - /// Increase the balance of `who` by the most that is possible, up to `amount`. - /// - /// Minimum balance will be respected and the returned imbalance will be zero in the case that - /// `amount < Self::minimum_balance()`. - /// - /// Return the imbalance by which the account was increased. - fn increase_balance_at_most(who: &AccountId, amount: Self::Balance) -> Self::Balance { - let old_balance = Self::balance(who); - let mut new_balance = old_balance.saturating_add(amount); - let mut amount = new_balance - old_balance; - if new_balance < Self::minimum_balance() { - new_balance = Zero::zero(); - amount = Zero::zero(); - } - if old_balance == new_balance || Self::set_balance(who, new_balance).is_ok() { - amount - } else { - Zero::zero() - } - } -} - -/// Simple handler for an imbalance drop which increases the total issuance of the system by the -/// imbalance amount. Used for leftover debt. -pub struct IncreaseIssuance(PhantomData<(AccountId, U)>); -impl> HandleImbalanceDrop - for IncreaseIssuance -{ - fn handle(amount: U::Balance) { - U::set_total_issuance(U::total_issuance().saturating_add(amount)) - } -} - -/// Simple handler for an imbalance drop which decreases the total issuance of the system by the -/// imbalance amount. Used for leftover credit. -pub struct DecreaseIssuance(PhantomData<(AccountId, U)>); -impl> HandleImbalanceDrop - for DecreaseIssuance -{ - fn handle(amount: U::Balance) { - U::set_total_issuance(U::total_issuance().saturating_sub(amount)) - } -} - -/// An imbalance type which uses `DecreaseIssuance` to deal with anything `Drop`ed. -/// -/// Basically means that funds in someone's account have been removed and not yet placed anywhere -/// else. If it gets dropped, then those funds will be assumed to be "burned" and the total supply -/// will be accordingly decreased to ensure it equals the sum of the balances of all accounts. -type Credit = Imbalance< - >::Balance, - DecreaseIssuance, - IncreaseIssuance, ->; - -/// An imbalance type which uses `IncreaseIssuance` to deal with anything `Drop`ed. -/// -/// Basically means that there are funds in someone's account whose origin is as yet unaccounted -/// for. If it gets dropped, then those funds will be assumed to be "minted" and the total supply -/// will be accordingly increased to ensure it equals the sum of the balances of all accounts. -type Debt = Imbalance< - >::Balance, - IncreaseIssuance, - DecreaseIssuance, ->; - -/// Create some `Credit` item. Only for internal use. -fn credit>(amount: U::Balance) -> Credit { - Imbalance::new(amount) -} - -/// Create some `Debt` item. Only for internal use. -fn debt>(amount: U::Balance) -> Debt { - Imbalance::new(amount) -} - -impl> Balanced for U { - type OnDropCredit = DecreaseIssuance; - type OnDropDebt = IncreaseIssuance; - fn rescind(amount: Self::Balance) -> Debt { - let old = U::total_issuance(); - let new = old.saturating_sub(amount); - U::set_total_issuance(new); - debt(old - new) - } - fn issue(amount: Self::Balance) -> Credit { - let old = U::total_issuance(); - let new = old.saturating_add(amount); - U::set_total_issuance(new); - credit(new - old) - } - fn slash(who: &AccountId, amount: Self::Balance) -> (Credit, Self::Balance) { - let slashed = U::decrease_balance_at_most(who, amount); - // `slashed` could be less than, greater than or equal to `amount`. - // If slashed == amount, it means the account had at least amount in it and it could all be - // removed without a problem. - // If slashed > amount, it means the account had more than amount in it, but not enough more - // to push it over minimum_balance. - // If slashed < amount, it means the account didn't have enough in it to be reduced by - // `amount` without being destroyed. - (credit(slashed), amount.saturating_sub(slashed)) - } - fn deposit( - who: &AccountId, - amount: Self::Balance, - ) -> Result, DispatchError> { - let increase = U::increase_balance(who, amount)?; - Ok(debt(increase)) - } - fn withdraw( - who: &AccountId, - amount: Self::Balance, - // TODO: liveness: ExistenceRequirement, - ) -> Result, DispatchError> { - let decrease = U::decrease_balance(who, amount)?; - Ok(credit(decrease)) - } -} diff --git a/frame/support/src/traits/tokens/fungible/conformance_tests/inspect_mutate.rs b/frame/support/src/traits/tokens/fungible/conformance_tests/inspect_mutate.rs new file mode 100644 index 000000000000..732742cca9b5 --- /dev/null +++ b/frame/support/src/traits/tokens/fungible/conformance_tests/inspect_mutate.rs @@ -0,0 +1,975 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::traits::{ + fungible::{Inspect, Mutate}, + tokens::{ + DepositConsequence, Fortitude, Precision, Preservation, Provenance, WithdrawConsequence, + }, +}; +use core::fmt::Debug; +use sp_arithmetic::traits::AtLeast8BitUnsigned; +use sp_runtime::traits::{Bounded, Zero}; + +/// Test the `mint_into` function for successful token minting. +/// +/// This test checks the `mint_into` function in the `Mutate` trait implementation for type `T`. +/// It ensures that account balances and total issuance values are updated correctly after minting +/// tokens into two distinct accounts. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn mint_into_success(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + let account_0 = AccountId::from(0); + let account_1 = AccountId::from(1); + + // Test: Mint an amount into each account + let amount_0 = T::minimum_balance(); + let amount_1 = T::minimum_balance() + 5.into(); + T::mint_into(&account_0, amount_0).unwrap(); + T::mint_into(&account_1, amount_1).unwrap(); + + // Verify: Account balances are updated correctly + assert_eq!(T::total_balance(&account_0), amount_0); + assert_eq!(T::total_balance(&account_1), amount_1); + assert_eq!(T::balance(&account_0), amount_0); + assert_eq!(T::balance(&account_1), amount_1); + + // Verify: Total issuance is updated correctly + assert_eq!(T::total_issuance(), initial_total_issuance + amount_0 + amount_1); + assert_eq!(T::active_issuance(), initial_active_issuance + amount_0 + amount_1); +} + +/// Test the `mint_into` function for overflow prevention. +/// +/// This test ensures that minting tokens beyond the maximum balance value for an account +/// returns an error and does not change the account balance or total issuance values. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn mint_into_overflow(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + let account = AccountId::from(10); + let amount = T::Balance::max_value() - 5.into() - initial_total_issuance; + + // Mint just below the maximum balance + T::mint_into(&account, amount).unwrap(); + + // Verify: Minting beyond the maximum balance value returns an Err + T::mint_into(&account, 10.into()).unwrap_err(); + + // Verify: The balance did not change + assert_eq!(T::total_balance(&account), amount); + assert_eq!(T::balance(&account), amount); + + // Verify: The total issuance did not change + assert_eq!(T::total_issuance(), initial_total_issuance + amount); + assert_eq!(T::active_issuance(), initial_active_issuance + amount); +} + +/// Test the `mint_into` function for handling balances below the minimum value. +/// +/// This test verifies that minting tokens below the minimum balance for an account +/// returns an error and has no impact on the account balance or total issuance values. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn mint_into_below_minimum(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + // Skip if there is no minimum balance + if T::minimum_balance() == T::Balance::zero() { + return + } + + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + let account = AccountId::from(10); + let amount = T::minimum_balance() - 1.into(); + + // Verify: Minting below the minimum balance returns Err + T::mint_into(&account, amount).unwrap_err(); + + // Verify: noop + assert_eq!(T::total_balance(&account), T::Balance::zero()); + assert_eq!(T::balance(&account), T::Balance::zero()); + assert_eq!(T::total_issuance(), initial_total_issuance); + assert_eq!(T::active_issuance(), initial_active_issuance); +} + +/// Test the `burn_from` function for successfully burning an exact amount of tokens. +/// +/// This test checks that the `burn_from` function with `Precision::Exact` correctly +/// reduces the account balance and total issuance values by the burned amount. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate` for `AccountId`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn burn_from_exact_success(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + + // Setup account + let account = AccountId::from(5); + let initial_balance = T::minimum_balance() + 10.into(); + T::mint_into(&account, initial_balance).unwrap(); + + // Test: Burn an exact amount from the account + let amount_to_burn = T::Balance::from(5); + let precision = Precision::Exact; + let force = Fortitude::Polite; + T::burn_from(&account, amount_to_burn, precision, force).unwrap(); + + // Verify: The balance and total issuance should be reduced by the burned amount + assert_eq!(T::balance(&account), initial_balance - amount_to_burn); + assert_eq!(T::total_balance(&account), initial_balance - amount_to_burn); + assert_eq!(T::total_issuance(), initial_total_issuance + initial_balance - amount_to_burn); + assert_eq!(T::active_issuance(), initial_active_issuance + initial_balance - amount_to_burn); +} + +/// Test the `burn_from` function for successfully burning tokens with a best-effort approach. +/// +/// This test verifies that the `burn_from` function with `Precision::BestEffort` correctly +/// reduces the account balance and total issuance values by the reducible balance when +/// attempting to burn an amount greater than the reducible balance. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate` for `AccountId`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn burn_from_best_effort_success(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + + // Setup account + let account = AccountId::from(5); + let initial_balance = T::minimum_balance() + 10.into(); + T::mint_into(&account, initial_balance).unwrap(); + + // Get reducible balance + let force = Fortitude::Polite; + let reducible_balance = T::reducible_balance(&account, Preservation::Expendable, force); + + // Test: Burn a best effort amount from the account that is greater than the reducible balance + let amount_to_burn = reducible_balance + 5.into(); + let precision = Precision::BestEffort; + assert!(amount_to_burn > reducible_balance); + assert!(amount_to_burn > T::balance(&account)); + T::burn_from(&account, amount_to_burn, precision, force).unwrap(); + + // Verify: The balance and total issuance should be reduced by the reducible_balance + assert_eq!(T::balance(&account), initial_balance - reducible_balance); + assert_eq!(T::total_balance(&account), initial_balance - reducible_balance); + assert_eq!(T::total_issuance(), initial_total_issuance + initial_balance - reducible_balance); + assert_eq!(T::active_issuance(), initial_active_issuance + initial_balance - reducible_balance); +} + +/// Test the `burn_from` function for handling insufficient funds with `Precision::Exact`. +/// +/// This test verifies that burning an amount greater than the account's balance with +/// `Precision::Exact` returns an error and does not change the account balance or total issuance +/// values. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn burn_from_exact_insufficient_funds(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + // Set up the initial conditions and parameters for the test + let account = AccountId::from(5); + let initial_balance = T::minimum_balance() + 10.into(); + T::mint_into(&account, initial_balance).unwrap(); + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + + // Verify: Burn an amount greater than the account's balance with Exact precision returns Err + let amount_to_burn = initial_balance + 10.into(); + let precision = Precision::Exact; + let force = Fortitude::Polite; + T::burn_from(&account, amount_to_burn, precision, force).unwrap_err(); + + // Verify: The balance and total issuance should remain unchanged + assert_eq!(T::balance(&account), initial_balance); + assert_eq!(T::total_balance(&account), initial_balance); + assert_eq!(T::total_issuance(), initial_total_issuance); + assert_eq!(T::active_issuance(), initial_active_issuance); +} + +/// Test the `restore` function for successful restoration. +/// +/// This test verifies that restoring an amount into each account updates their balances and the +/// total issuance values correctly. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn restore_success(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let account_0 = AccountId::from(0); + let account_1 = AccountId::from(1); + + // Test: Restore an amount into each account + let amount_0 = T::minimum_balance(); + let amount_1 = T::minimum_balance() + 5.into(); + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + T::restore(&account_0, amount_0).unwrap(); + T::restore(&account_1, amount_1).unwrap(); + + // Verify: Account balances are updated correctly + assert_eq!(T::total_balance(&account_0), amount_0); + assert_eq!(T::total_balance(&account_1), amount_1); + assert_eq!(T::balance(&account_0), amount_0); + assert_eq!(T::balance(&account_1), amount_1); + + // Verify: Total issuance is updated correctly + assert_eq!(T::total_issuance(), initial_total_issuance + amount_0 + amount_1); + assert_eq!(T::active_issuance(), initial_active_issuance + amount_0 + amount_1); +} + +/// Test the `restore` function for handling balance overflow. +/// +/// This test verifies that restoring an amount beyond the maximum balance returns an error and +/// does not change the account balance or total issuance values. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn restore_overflow(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + let account = AccountId::from(10); + let amount = T::Balance::max_value() - 5.into() - initial_total_issuance; + + // Restore just below the maximum balance + T::restore(&account, amount).unwrap(); + + // Verify: Restoring beyond the maximum balance returns an Err + T::restore(&account, 10.into()).unwrap_err(); + + // Verify: The balance and total issuance did not change + assert_eq!(T::total_balance(&account), amount); + assert_eq!(T::balance(&account), amount); + assert_eq!(T::total_issuance(), initial_total_issuance + amount); + assert_eq!(T::active_issuance(), initial_active_issuance + amount); +} + +/// Test the `restore` function for handling restoration below the minimum balance. +/// +/// This test verifies that restoring an amount below the minimum balance returns an error and +/// does not change the account balance or total issuance values. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn restore_below_minimum(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + // Skip if there is no minimum balance + if T::minimum_balance() == T::Balance::zero() { + return + } + + let account = AccountId::from(10); + let amount = T::minimum_balance() - 1.into(); + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + + // Verify: Restoring below the minimum balance returns Err + T::restore(&account, amount).unwrap_err(); + + // Verify: noop + assert_eq!(T::total_balance(&account), T::Balance::zero()); + assert_eq!(T::balance(&account), T::Balance::zero()); + assert_eq!(T::total_issuance(), initial_total_issuance); + assert_eq!(T::active_issuance(), initial_active_issuance); +} + +/// Test the `shelve` function for successful shelving. +/// +/// This test verifies that shelving an amount from an account reduces the account balance and +/// total issuance values by the shelved amount. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn shelve_success(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + + // Setup account + let account = AccountId::from(5); + let initial_balance = T::minimum_balance() + 10.into(); + + T::restore(&account, initial_balance).unwrap(); + + // Test: Shelve an amount from the account + let amount_to_shelve = T::Balance::from(5); + T::shelve(&account, amount_to_shelve).unwrap(); + + // Verify: The balance and total issuance should be reduced by the shelved amount + assert_eq!(T::balance(&account), initial_balance - amount_to_shelve); + assert_eq!(T::total_balance(&account), initial_balance - amount_to_shelve); + assert_eq!(T::total_issuance(), initial_total_issuance + initial_balance - amount_to_shelve); + assert_eq!(T::active_issuance(), initial_active_issuance + initial_balance - amount_to_shelve); +} + +/// Test the `shelve` function for handling insufficient funds. +/// +/// This test verifies that attempting to shelve an amount greater than the account's balance +/// returns an error and does not change the account balance or total issuance values. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn shelve_insufficient_funds(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + + // Set up the initial conditions and parameters for the test + let account = AccountId::from(5); + let initial_balance = T::minimum_balance() + 10.into(); + T::restore(&account, initial_balance).unwrap(); + + // Verify: Shelving greater than the balance with Exact precision returns Err + let amount_to_shelve = initial_balance + 10.into(); + T::shelve(&account, amount_to_shelve).unwrap_err(); + + // Verify: The balance and total issuance should remain unchanged + assert_eq!(T::balance(&account), initial_balance); + assert_eq!(T::total_balance(&account), initial_balance); + assert_eq!(T::total_issuance(), initial_total_issuance + initial_balance); + assert_eq!(T::active_issuance(), initial_active_issuance + initial_balance); +} + +/// Test the `transfer` function for a successful transfer. +/// +/// This test verifies that transferring an amount between two accounts with +/// `Preservation::Expendable` updates the account balances and maintains the total issuance and +/// active issuance values. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn transfer_success(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + let account_0 = AccountId::from(0); + let account_1 = AccountId::from(1); + let initial_balance = T::minimum_balance() + 10.into(); + T::set_balance(&account_0, initial_balance); + T::set_balance(&account_1, initial_balance); + + // Test: Transfer an amount from account_0 to account_1 + let transfer_amount = T::Balance::from(3); + T::transfer(&account_0, &account_1, transfer_amount, Preservation::Expendable).unwrap(); + + // Verify: Account balances are updated correctly + assert_eq!(T::total_balance(&account_0), initial_balance - transfer_amount); + assert_eq!(T::total_balance(&account_1), initial_balance + transfer_amount); + assert_eq!(T::balance(&account_0), initial_balance - transfer_amount); + assert_eq!(T::balance(&account_1), initial_balance + transfer_amount); + + // Verify: Total issuance doesn't change + assert_eq!(T::total_issuance(), initial_total_issuance + initial_balance * 2.into()); + assert_eq!(T::active_issuance(), initial_active_issuance + initial_balance * 2.into()); +} + +/// Test the `transfer` function with `Preservation::Expendable` for transferring the entire +/// balance. +/// +/// This test verifies that transferring the entire balance from one account to another with +/// `Preservation::Expendable` updates the account balances and maintains the total issuance and +/// active issuance values. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn transfer_expendable_all(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + let account_0 = AccountId::from(0); + let account_1 = AccountId::from(1); + let initial_balance = T::minimum_balance() + 10.into(); + T::set_balance(&account_0, initial_balance); + T::set_balance(&account_1, initial_balance); + + // Test: Transfer entire balance from account_0 to account_1 + let preservation = Preservation::Expendable; + let transfer_amount = initial_balance; + T::transfer(&account_0, &account_1, transfer_amount, preservation).unwrap(); + + // Verify: Account balances are updated correctly + assert_eq!(T::total_balance(&account_0), T::Balance::zero()); + assert_eq!(T::total_balance(&account_1), initial_balance * 2.into()); + assert_eq!(T::balance(&account_0), T::Balance::zero()); + assert_eq!(T::balance(&account_1), initial_balance * 2.into()); + + // Verify: Total issuance doesn't change + assert_eq!(T::total_issuance(), initial_total_issuance + initial_balance * 2.into()); + assert_eq!(T::active_issuance(), initial_active_issuance + initial_balance * 2.into()); +} + +/// Test the transfer function with Preservation::Expendable for transferring amounts that leaves +/// an account with less than the minimum balance. +/// +/// This test verifies that when transferring an amount using Preservation::Expendable and an +/// account will be left with less than the minimum balance, the account balances are updated, dust +/// is collected properly depending on whether a dust_trap exists, and the total issuance and active +/// issuance values remain consistent. +/// +/// # Parameters +/// +/// - dust_trap: An optional account identifier to which dust will be collected. If None, dust will +/// be removed from the total and active issuance. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn transfer_expendable_dust(dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + if T::minimum_balance() == T::Balance::zero() { + return + } + + let account_0 = AccountId::from(10); + let account_1 = AccountId::from(20); + let initial_balance = T::minimum_balance() + 10.into(); + T::set_balance(&account_0, initial_balance); + T::set_balance(&account_1, initial_balance); + + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + let initial_dust_trap_balance = match dust_trap.clone() { + Some(dust_trap) => T::total_balance(&dust_trap), + None => T::Balance::zero(), + }; + + // Test: Transfer balance + let preservation = Preservation::Expendable; + let transfer_amount = T::Balance::from(11); + T::transfer(&account_0, &account_1, transfer_amount, preservation).unwrap(); + + // Verify: Account balances are updated correctly + assert_eq!(T::total_balance(&account_0), T::Balance::zero()); + assert_eq!(T::total_balance(&account_1), initial_balance + transfer_amount); + assert_eq!(T::balance(&account_0), T::Balance::zero()); + assert_eq!(T::balance(&account_1), initial_balance + transfer_amount); + + match dust_trap { + Some(dust_trap) => { + // Verify: Total issuance and active issuance don't change + assert_eq!(T::total_issuance(), initial_total_issuance); + assert_eq!(T::active_issuance(), initial_active_issuance); + // Verify: Dust is collected into dust trap + assert_eq!( + T::total_balance(&dust_trap), + initial_dust_trap_balance + T::minimum_balance() - 1.into() + ); + assert_eq!( + T::balance(&dust_trap), + initial_dust_trap_balance + T::minimum_balance() - 1.into() + ); + }, + None => { + // Verify: Total issuance and active issuance are reduced by the dust amount + assert_eq!( + T::total_issuance(), + initial_total_issuance - T::minimum_balance() + 1.into() + ); + assert_eq!( + T::active_issuance(), + initial_active_issuance - T::minimum_balance() + 1.into() + ); + }, + } +} + +/// Test the `transfer` function with `Preservation::Protect` and `Preservation::Preserve` for +/// transferring the entire balance. +/// +/// This test verifies that attempting to transfer the entire balance with `Preservation::Protect` +/// or `Preservation::Preserve` returns an error, and the account balances, total issuance, and +/// active issuance values remain unchanged. +/// +/// # Type Parameters +/// +/// ```text +/// - `T`: Implements `Mutate`. +/// - `AccountId`: Account identifier implementing `AtLeast8BitUnsigned`. +/// ``` +pub fn transfer_protect_preserve(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + // This test means nothing if there is no minimum balance + if T::minimum_balance() == T::Balance::zero() { + return + } + + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + let account_0 = AccountId::from(0); + let account_1 = AccountId::from(1); + let initial_balance = T::minimum_balance() + 10.into(); + T::set_balance(&account_0, initial_balance); + T::set_balance(&account_1, initial_balance); + + // Verify: Transfer Protect entire balance from account_0 to account_1 should Err + let preservation = Preservation::Protect; + let transfer_amount = initial_balance; + T::transfer(&account_0, &account_1, transfer_amount, preservation).unwrap_err(); + + // Verify: Noop + assert_eq!(T::total_balance(&account_0), initial_balance); + assert_eq!(T::total_balance(&account_1), initial_balance); + assert_eq!(T::balance(&account_0), initial_balance); + assert_eq!(T::balance(&account_1), initial_balance); + assert_eq!(T::total_issuance(), initial_total_issuance + initial_balance * 2.into()); + assert_eq!(T::active_issuance(), initial_active_issuance + initial_balance * 2.into()); + + // Verify: Transfer Preserve entire balance from account_0 to account_1 should Err + let preservation = Preservation::Preserve; + T::transfer(&account_0, &account_1, transfer_amount, preservation).unwrap_err(); + + // Verify: Noop + assert_eq!(T::total_balance(&account_0), initial_balance); + assert_eq!(T::total_balance(&account_1), initial_balance); + assert_eq!(T::balance(&account_0), initial_balance); + assert_eq!(T::balance(&account_1), initial_balance); + assert_eq!(T::total_issuance(), initial_total_issuance + initial_balance * 2.into()); + assert_eq!(T::active_issuance(), initial_active_issuance + initial_balance * 2.into()); +} + +/// Test the set_balance function for successful minting. +/// +/// This test verifies that minting a balance using set_balance updates the account balance, total +/// issuance, and active issuance correctly. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn set_balance_mint_success(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + let account = AccountId::from(10); + let initial_balance = T::minimum_balance() + 10.into(); + T::mint_into(&account, initial_balance).unwrap(); + + // Test: Increase the account balance with set_balance + let increase_amount: T::Balance = 5.into(); + let new = T::set_balance(&account, initial_balance + increase_amount); + + // Verify: set_balance returned the new balance + let expected_new = initial_balance + increase_amount; + assert_eq!(new, expected_new); + + // Verify: Balance and issuance is updated correctly + assert_eq!(T::total_balance(&account), expected_new); + assert_eq!(T::balance(&account), expected_new); + assert_eq!(T::total_issuance(), initial_total_issuance + expected_new); + assert_eq!(T::active_issuance(), initial_active_issuance + expected_new); +} + +/// Test the set_balance function for successful burning. +/// +/// This test verifies that burning a balance using set_balance updates the account balance, total +/// issuance, and active issuance correctly. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn set_balance_burn_success(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let initial_total_issuance = T::total_issuance(); + let initial_active_issuance = T::active_issuance(); + let account = AccountId::from(10); + let initial_balance = T::minimum_balance() + 10.into(); + T::mint_into(&account, initial_balance).unwrap(); + + // Test: Increase the account balance with set_balance + let burn_amount: T::Balance = 5.into(); + let new = T::set_balance(&account, initial_balance - burn_amount); + + // Verify: set_balance returned the new balance + let expected_new = initial_balance - burn_amount; + assert_eq!(new, expected_new); + + // Verify: Balance and issuance is updated correctly + assert_eq!(T::total_balance(&account), expected_new); + assert_eq!(T::balance(&account), expected_new); + assert_eq!(T::total_issuance(), initial_total_issuance + expected_new); + assert_eq!(T::active_issuance(), initial_active_issuance + expected_new); +} + +/// Test the can_deposit function for returning a success value. +/// +/// This test verifies that the can_deposit function returns DepositConsequence::Success when +/// depositing a reasonable amount. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn can_deposit_success(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let account = AccountId::from(10); + let initial_balance = T::minimum_balance() + 10.into(); + T::mint_into(&account, initial_balance).unwrap(); + + // Test: can_deposit a reasonable amount + let ret = T::can_deposit(&account, 5.into(), Provenance::Minted); + + // Verify: Returns success + assert_eq!(ret, DepositConsequence::Success); +} + +/// Test the can_deposit function for returning a minimum balance error. +/// +/// This test verifies that the can_deposit function returns DepositConsequence::BelowMinimum when +/// depositing below the minimum balance. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn can_deposit_below_minimum(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + // can_deposit always returns Success for amount 0 + if T::minimum_balance() < 2.into() { + return + } + + let account = AccountId::from(10); + + // Test: can_deposit below the minimum + let ret = T::can_deposit(&account, T::minimum_balance() - 1.into(), Provenance::Minted); + + // Verify: Returns success + assert_eq!(ret, DepositConsequence::BelowMinimum); +} + +/// Test the can_deposit function for returning an overflow error. +/// +/// This test verifies that the can_deposit function returns DepositConsequence::Overflow when +/// depositing an amount that would cause an overflow. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn can_deposit_overflow(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let account = AccountId::from(10); + + // Test: Try deposit over the max balance + let initial_balance = T::Balance::max_value() - 5.into() - T::total_issuance(); + T::mint_into(&account, initial_balance).unwrap(); + let ret = T::can_deposit(&account, 10.into(), Provenance::Minted); + + // Verify: Returns success + assert_eq!(ret, DepositConsequence::Overflow); +} + +/// Test the can_withdraw function for returning a success value. +/// +/// This test verifies that the can_withdraw function returns WithdrawConsequence::Success when +/// withdrawing a reasonable amount. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn can_withdraw_success(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let account = AccountId::from(10); + let initial_balance = T::minimum_balance() + 10.into(); + T::mint_into(&account, initial_balance).unwrap(); + + // Test: can_withdraw a reasonable amount + let ret = T::can_withdraw(&account, 5.into()); + + // Verify: Returns success + assert_eq!(ret, WithdrawConsequence::Success); +} + +/// Test the can_withdraw function for withdrawal resulting in a reduced balance of zero. +/// +/// This test verifies that the can_withdraw function returns WithdrawConsequence::ReducedToZero +/// when withdrawing an amount that would reduce the account balance below the minimum balance. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn can_withdraw_reduced_to_zero(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + if T::minimum_balance() == T::Balance::zero() { + return + } + + let account = AccountId::from(10); + let initial_balance = T::minimum_balance(); + T::mint_into(&account, initial_balance).unwrap(); + + // Verify: can_withdraw below the minimum balance returns ReducedToZero + let ret = T::can_withdraw(&account, 1.into()); + assert_eq!(ret, WithdrawConsequence::ReducedToZero(T::minimum_balance() - 1.into())); +} + +/// Test the can_withdraw function for returning a low balance error. +/// +/// This test verifies that the can_withdraw function returns WithdrawConsequence::BalanceLow when +/// withdrawing an amount that would result in an account balance below the current balance. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn can_withdraw_balance_low(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + if T::minimum_balance() == T::Balance::zero() { + return + } + + let account = AccountId::from(10); + let other_account = AccountId::from(100); + let initial_balance = T::minimum_balance() + 5.into(); + T::mint_into(&account, initial_balance).unwrap(); + T::mint_into(&other_account, initial_balance * 2.into()).unwrap(); + + // Verify: can_withdraw below the account balance returns BalanceLow + let ret = T::can_withdraw(&account, initial_balance + 1.into()); + assert_eq!(ret, WithdrawConsequence::BalanceLow); +} + +/// Test the reducible_balance function with Preservation::Expendable. +/// +/// This test verifies that the reducible_balance function returns the full account balance when +/// using Preservation::Expendable. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn reducible_balance_expendable(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let account = AccountId::from(10); + let initial_balance = T::minimum_balance() + 10.into(); + T::mint_into(&account, initial_balance).unwrap(); + + // Verify: reducible_balance returns the full balance + let ret = T::reducible_balance(&account, Preservation::Expendable, Fortitude::Polite); + assert_eq!(ret, initial_balance); +} + +/// Test the reducible_balance function with Preservation::Protect and Preservation::Preserve. +/// +/// This test verifies that the reducible_balance function returns the account balance minus the +/// minimum balance when using either Preservation::Protect or Preservation::Preserve. +/// +/// # Type Parameters +/// +/// ```text +/// - T: Implements Mutate. +/// - AccountId: Account identifier implementing AtLeast8BitUnsigned. +/// ``` +pub fn reducible_balance_protect_preserve(_dust_trap: Option) +where + T: Mutate, + >::Balance: AtLeast8BitUnsigned + Debug, + AccountId: AtLeast8BitUnsigned, +{ + let account = AccountId::from(10); + let initial_balance = T::minimum_balance() + 10.into(); + T::mint_into(&account, initial_balance).unwrap(); + + // Verify: reducible_balance returns the full balance - min balance + let ret = T::reducible_balance(&account, Preservation::Protect, Fortitude::Polite); + assert_eq!(ret, initial_balance - T::minimum_balance()); + let ret = T::reducible_balance(&account, Preservation::Preserve, Fortitude::Polite); + assert_eq!(ret, initial_balance - T::minimum_balance()); +} diff --git a/frame/support/src/traits/tokens/fungible/conformance_tests/mod.rs b/frame/support/src/traits/tokens/fungible/conformance_tests/mod.rs new file mode 100644 index 000000000000..88ba56a6fed0 --- /dev/null +++ b/frame/support/src/traits/tokens/fungible/conformance_tests/mod.rs @@ -0,0 +1 @@ +pub mod inspect_mutate; diff --git a/frame/support/src/traits/tokens/fungible/freeze.rs b/frame/support/src/traits/tokens/fungible/freeze.rs new file mode 100644 index 000000000000..1ec3a5fadf55 --- /dev/null +++ b/frame/support/src/traits/tokens/fungible/freeze.rs @@ -0,0 +1,68 @@ +// 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. + +//! The traits for putting freezes within a single fungible token class. + +use scale_info::TypeInfo; +use sp_runtime::DispatchResult; + +/// Trait for inspecting a fungible asset which can be frozen. Freezing is essentially setting a +/// minimum balance bellow which the total balance (inclusive of any funds placed on hold) may not +/// be normally allowed to drop. Generally, freezers will provide an "update" function such that +/// if the total balance does drop below the limit, then the freezer can update their housekeeping +/// accordingly. +pub trait Inspect: super::Inspect { + /// An identifier for a freeze. + type Id: codec::Encode + TypeInfo + 'static; + + /// Amount of funds held in reserve by `who` for the given `id`. + fn balance_frozen(id: &Self::Id, who: &AccountId) -> Self::Balance; + + /// The amount of the balance which can become frozen. Defaults to `total_balance()`. + fn balance_freezable(who: &AccountId) -> Self::Balance { + Self::total_balance(who) + } + + /// Returns `true` if it's possible to introduce a freeze for the given `id` onto the + /// account of `who`. This will be true as long as the implementor supports as many + /// concurrent freeze locks as there are possible values of `id`. + fn can_freeze(id: &Self::Id, who: &AccountId) -> bool; +} + +/// Trait for introducing, altering and removing locks to freeze an account's funds so they never +/// go below a set minimum. +pub trait Mutate: Inspect { + /// Prevent actions which would reduce the balance of the account of `who` below the given + /// `amount` and identify this restriction though the given `id`. Unlike `extend_freeze`, any + /// outstanding freeze in place for `who` under the `id` are dropped. + /// + /// If `amount` is zero, it is equivalent to using `thaw`. + /// + /// Note that `amount` can be greater than the total balance, if desired. + fn set_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult; + + /// Prevent the balance of the account of `who` from being reduced below the given `amount` and + /// identify this restriction though the given `id`. Unlike `set_freeze`, this does not + /// counteract any pre-existing freezes in place for `who` under the `id`. Also unlike + /// `set_freeze`, in the case that `amount` is zero, this is no-op and never fails. + /// + /// Note that more funds can be locked than the total balance, if desired. + fn extend_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult; + + /// Remove an existing lock. + fn thaw(id: &Self::Id, who: &AccountId) -> DispatchResult; +} diff --git a/frame/support/src/traits/tokens/fungible/hold.rs b/frame/support/src/traits/tokens/fungible/hold.rs new file mode 100644 index 000000000000..ddcb8c6ac1da --- /dev/null +++ b/frame/support/src/traits/tokens/fungible/hold.rs @@ -0,0 +1,393 @@ +// 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. + +//! The traits for putting holds within a single fungible token class. + +use crate::{ + ensure, + traits::tokens::{ + DepositConsequence::Success, + Fortitude::{self, Force}, + Precision::{self, BestEffort, Exact}, + Preservation::{self, Protect}, + Provenance::Extant, + Restriction::{self, Free, OnHold}, + }, +}; +use scale_info::TypeInfo; +use sp_arithmetic::{ + traits::{CheckedAdd, CheckedSub, Zero}, + ArithmeticError, +}; +use sp_runtime::{DispatchError, DispatchResult, Saturating, TokenError}; + +use super::*; + +/// Trait for inspecting a fungible asset whose accounts support partitioning and slashing. +pub trait Inspect: super::Inspect { + /// An identifier for a hold. Used for disambiguating different holds so that + /// they can be individually replaced or removed and funds from one hold don't accidentally + /// become unreserved or slashed for another. + type Reason: codec::Encode + TypeInfo + 'static; + + /// Amount of funds on hold (for all hold reasons) of `who`. + fn total_balance_on_hold(who: &AccountId) -> Self::Balance; + + /// Get the maximum amount that the `total_balance_on_hold` of `who` can be reduced successfully + /// based on whether we are willing to force the reduction and potentially go below user-level + /// restrictions on the minimum amount of the account. Note: This cannot bring the account into + /// an inconsistent state with regards any required existential deposit. + /// + /// Always less than `total_balance_on_hold()`. + fn reducible_total_balance_on_hold(who: &AccountId, force: Fortitude) -> Self::Balance; + + /// Amount of funds on hold (for the given reason) of `who`. + fn balance_on_hold(reason: &Self::Reason, who: &AccountId) -> Self::Balance; + + /// Returns `true` if it's possible to place (additional) funds under a hold of a given + /// `reason`. This may fail if the account has exhausted a limited number of concurrent + /// holds or if it cannot be made to exist (e.g. there is no provider reference). + /// + /// NOTE: This does not take into account changes which could be made to the account of `who` + /// (such as removing a provider reference) after this call is made. Any usage of this should + /// therefore ensure the account is already in the appropriate state prior to calling it. + fn hold_available(reason: &Self::Reason, who: &AccountId) -> bool; + + /// Check to see if some `amount` of funds of `who` may be placed on hold with the given + /// `reason`. Reasons why this may not be true: + /// + /// - The implementor supports only a limited number of concurrent holds on an account which is + /// the possible values of `reason`; + /// - The total balance of the account is less than `amount`; + /// - Removing `amount` from the total balance would kill the account and remove the only + /// provider reference. + /// + /// Note: we pass `true` as the third argument to `reducible_balance` since we assume that if + /// needed the balance can slashed. If we are using a simple non-forcing reserve-transfer, then + /// we really ought to check that we are not reducing the funds below the freeze-limit (if any). + /// + /// NOTE: This does not take into account changes which could be made to the account of `who` + /// (such as removing a provider reference) after this call is made. Any usage of this should + /// therefore ensure the account is already in the appropriate state prior to calling it. + fn ensure_can_hold( + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult { + ensure!(Self::hold_available(reason, who), TokenError::CannotCreateHold); + ensure!( + amount <= Self::reducible_balance(who, Protect, Force), + TokenError::FundsUnavailable + ); + Ok(()) + } + + /// Check to see if some `amount` of funds of `who` may be placed on hold for the given + /// `reason`. Reasons why this may not be true: + /// + /// - The implementor supports only a limited number of concurrernt holds on an account which is + /// the possible values of `reason`; + /// - The main balance of the account is less than `amount`; + /// - Removing `amount` from the main balance would kill the account and remove the only + /// provider reference. + /// + /// NOTE: This does not take into account changes which could be made to the account of `who` + /// (such as removing a provider reference) after this call is made. Any usage of this should + /// therefore ensure the account is already in the appropriate state prior to calling it. + fn can_hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> bool { + Self::ensure_can_hold(reason, who, amount).is_ok() + } +} + +/// A fungible, holdable token class where the balance on hold can be set arbitrarily. +/// +/// **WARNING** +/// Do not use this directly unless you want trouble, since it allows you to alter account balances +/// without keeping the issuance up to date. It has no safeguards against accidentally creating +/// token imbalances in your system leading to accidental imflation or deflation. It's really just +/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to +/// use. +pub trait Unbalanced: Inspect { + /// Forcefully set the balance on hold of `who` to `amount`. This is independent of any other + /// balances on hold or the main ("free") balance. + /// + /// If this call executes successfully, you can `assert_eq!(Self::balance_on_hold(), amount);`. + /// + /// This function does its best to force the balance change through, but will not break system + /// invariants such as any Existential Deposits needed or overflows/underflows. + /// If this cannot be done for some reason (e.g. because the account doesn't exist) then an + /// `Err` is returned. + // Implmentation note: This should increment the consumer refs if it moves total on hold from + // zero to non-zero and decrement in the opposite direction. + // + // Since this was not done in the previous logic, this will need either a migration or a + // state item which tracks whether the account is on the old logic or new. + fn set_balance_on_hold( + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult; + + /// Reduce the balance on hold of `who` by `amount`. + /// + /// If `precision` is `Exact` and it cannot be reduced by that amount for + /// some reason, return `Err` and don't reduce it at all. If `precision` is `BestEffort`, then + /// reduce the balance of `who` by the most that is possible, up to `amount`. + /// + /// In either case, if `Ok` is returned then the inner is the amount by which is was reduced. + fn decrease_balance_on_hold( + reason: &Self::Reason, + who: &AccountId, + mut amount: Self::Balance, + precision: Precision, + ) -> Result { + let old_balance = Self::balance_on_hold(reason, who); + if let BestEffort = precision { + amount = amount.min(old_balance); + } + let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?; + Self::set_balance_on_hold(reason, who, new_balance)?; + Ok(amount) + } + + /// Increase the balance on hold of `who` by `amount`. + /// + /// If it cannot be increased by that amount for some reason, return `Err` and don't increase + /// it at all. If Ok, return the imbalance. + fn increase_balance_on_hold( + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + let old_balance = Self::balance_on_hold(reason, who); + let new_balance = if let BestEffort = precision { + old_balance.saturating_add(amount) + } else { + old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)? + }; + let amount = new_balance.saturating_sub(old_balance); + if !amount.is_zero() { + Self::set_balance_on_hold(reason, who, new_balance)?; + } + Ok(amount) + } +} + +/// Trait for mutating a fungible asset which can be placed on hold. +pub trait Mutate: + Inspect + super::Unbalanced + Unbalanced +{ + /// Hold some funds in an account. If a hold for `reason` is already in place, then this + /// will increase it. + fn hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> DispatchResult { + // NOTE: This doesn't change the total balance of the account so there's no need to + // check liquidity. + + Self::ensure_can_hold(reason, who, amount)?; + // Should be infallible now, but we proceed softly anyway. + Self::decrease_balance(who, amount, Exact, Protect, Force)?; + Self::increase_balance_on_hold(reason, who, amount, BestEffort)?; + Self::done_hold(reason, who, amount); + Ok(()) + } + + /// Release up to `amount` held funds in an account. + /// + /// The actual amount released is returned with `Ok`. + /// + /// If `precision` is `BestEffort`, then the amount actually unreserved and returned as the + /// inner value of `Ok` may be smaller than the `amount` passed. + /// + /// NOTE! The inner of the `Ok` result variant returns the *actual* amount released. This is the + /// opposite of the `ReservableCurrency::unreserve()` result, which gives the amount not able + /// to be released! + fn release( + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + // NOTE: This doesn't change the total balance of the account so there's no need to + // check liquidity. + + // We want to make sure we can deposit the amount in advance. If we can't then something is + // very wrong. + ensure!(Self::can_deposit(who, amount, Extant) == Success, TokenError::CannotCreate); + // Get the amount we can actually take from the hold. This might be less than what we want + // if we're only doing a best-effort. + let amount = Self::decrease_balance_on_hold(reason, who, amount, precision)?; + // Increase the main balance by what we took. We always do a best-effort here because we + // already checked that we can deposit before. + let actual = Self::increase_balance(who, amount, BestEffort)?; + Self::done_release(reason, who, actual); + Ok(actual) + } + + /// Attempt to decrease the balance of `who` which is held for the given `reason` by `amount`. + /// + /// If `precision` is `BestEffort`, then as much as possible is reduced, up to `amount`, and the + /// amount of tokens reduced is returned. Otherwise, if the total amount can be reduced, then it + /// is and the amount returned, and if not, then nothing changes and `Err` is returned. + /// + /// If `force` is `Force`, then locks/freezes will be ignored. This should only be used when + /// conducting slashing or other activity which materially disadvantages the account holder + /// since it could provide a means of circumventing freezes. + fn burn_held( + reason: &Self::Reason, + who: &AccountId, + mut amount: Self::Balance, + precision: Precision, + force: Fortitude, + ) -> Result { + // We must check total-balance requirements if `!force`. + let liquid = Self::reducible_total_balance_on_hold(who, force); + if let BestEffort = precision { + amount = amount.min(liquid); + } else { + ensure!(amount <= liquid, TokenError::Frozen); + } + let amount = Self::decrease_balance_on_hold(reason, who, amount, precision)?; + Self::set_total_issuance(Self::total_issuance().saturating_sub(amount)); + Self::done_burn_held(reason, who, amount); + Ok(amount) + } + + /// Transfer held funds into a destination account. + /// + /// If `on_hold` is `true`, then the destination account must already exist and the assets + /// transferred will still be on hold in the destination account. If not, then the destination + /// account need not already exist, but must be creatable. + /// + /// If `precision` is `BestEffort`, then an amount less than `amount` may be transferred without + /// error. + /// + /// If `force` is `Force`, then other fund-locking mechanisms may be disregarded. It should be + /// left as `Polite` in most circumstances, but when you want the same power as a `slash`, it + /// may be `Force`. + /// + /// The actual amount transferred is returned, or `Err` in the case of error and nothing is + /// changed. + fn transfer_on_hold( + reason: &Self::Reason, + source: &AccountId, + dest: &AccountId, + mut amount: Self::Balance, + precision: Precision, + mode: Restriction, + force: Fortitude, + ) -> Result { + // We must check total-balance requirements if `force` is `Fortitude::Polite`. + let have = Self::balance_on_hold(reason, source); + let liquid = Self::reducible_total_balance_on_hold(source, force); + if let BestEffort = precision { + amount = amount.min(liquid).min(have); + } else { + ensure!(amount <= liquid, TokenError::Frozen); + ensure!(amount <= have, TokenError::FundsUnavailable); + } + + // We want to make sure we can deposit the amount in advance. If we can't then something is + // very wrong. + ensure!(Self::can_deposit(dest, amount, Extant) == Success, TokenError::CannotCreate); + ensure!(mode == Free || Self::hold_available(reason, dest), TokenError::CannotCreateHold); + + let amount = Self::decrease_balance_on_hold(reason, source, amount, precision)?; + let actual = if mode == OnHold { + Self::increase_balance_on_hold(reason, dest, amount, precision)? + } else { + Self::increase_balance(dest, amount, precision)? + }; + Self::done_transfer_on_hold(reason, source, dest, actual); + Ok(actual) + } + + /// Transfer some `amount` of free balance from `source` to become owned by `dest` but on hold + /// for `reason`. + /// + /// If `precision` is `BestEffort`, then an amount less than `amount` may be transferred without + /// error. + /// + /// `source` must obey the requirements of `keep_alive`. + /// + /// If `force` is `Force`, then other fund-locking mechanisms may be disregarded. It should be + /// left as `Polite` in most circumstances, but when you want the same power as a `slash`, it + /// may be `Force`. + /// + /// The amount placed on hold is returned or `Err` in the case of error and nothing is changed. + /// + /// WARNING: This may return an error after a partial storage mutation. It should be used only + /// inside a transactional storage context and an `Err` result must imply a storage rollback. + fn transfer_and_hold( + reason: &Self::Reason, + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + precision: Precision, + expendability: Preservation, + force: Fortitude, + ) -> Result { + ensure!(Self::hold_available(reason, dest), TokenError::CannotCreateHold); + ensure!(Self::can_deposit(dest, amount, Extant) == Success, TokenError::CannotCreate); + let actual = Self::decrease_balance(source, amount, precision, expendability, force)?; + Self::increase_balance_on_hold(reason, dest, actual, precision)?; + Self::done_transfer_on_hold(reason, source, dest, actual); + Ok(actual) + } + + fn done_hold(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {} + fn done_release(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {} + fn done_burn_held(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {} + fn done_transfer_on_hold( + _reason: &Self::Reason, + _source: &AccountId, + _dest: &AccountId, + _amount: Self::Balance, + ) { + } + fn done_transfer_and_hold( + _reason: &Self::Reason, + _source: &AccountId, + _dest: &AccountId, + _transferred: Self::Balance, + ) { + } +} + +/// Trait for slashing a fungible asset which can be place on hold. +pub trait Balanced: super::Balanced + Unbalanced { + /// Reduce the balance of some funds on hold in an account. + /// + /// The resulting imbalance is the first item of the tuple returned. + /// + /// As much funds that are on hold up to `amount` will be deducted as possible. If this is less + /// than `amount`, then a non-zero second item will be returned. + fn slash( + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> (Credit, Self::Balance) { + let decrease = Self::decrease_balance_on_hold(reason, who, amount, BestEffort) + .unwrap_or(Default::default()); + let credit = + Imbalance::::new(decrease); + Self::done_slash(reason, who, decrease); + (credit, amount.saturating_sub(decrease)) + } + + fn done_slash(_reason: &Self::Reason, _who: &AccountId, _amount: Self::Balance) {} +} diff --git a/frame/support/src/traits/tokens/fungible/imbalance.rs b/frame/support/src/traits/tokens/fungible/imbalance.rs index ca911cf12d44..de85924a4de7 100644 --- a/frame/support/src/traits/tokens/fungible/imbalance.rs +++ b/frame/support/src/traits/tokens/fungible/imbalance.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,11 @@ //! The imbalance type and its associates, which handles keeps everything adding up properly with //! unbalanced operations. -use super::{super::Imbalance as ImbalanceT, balanced::Balanced, misc::Balance, *}; -use crate::traits::misc::{SameOrOther, TryDrop}; +use super::{super::Imbalance as ImbalanceT, Balanced, *}; +use crate::traits::{ + misc::{SameOrOther, TryDrop}, + tokens::Balance, +}; use sp_runtime::{traits::Zero, RuntimeDebug}; use sp_std::marker::PhantomData; @@ -30,6 +33,10 @@ pub trait HandleImbalanceDrop { fn handle(amount: Balance); } +impl HandleImbalanceDrop for () { + fn handle(_: Balance) {} +} + /// An imbalance in the system, representing a divergence of recorded token supply from the sum of /// the balances of all accounts. This is `must_use` in order to ensure it gets handled (placing /// into an account, settling from an account or altering the supply). @@ -135,7 +142,7 @@ impl, OppositeOnDrop: HandleImbalance } /// Imbalance implying that the total_issuance value is less than the sum of all account balances. -pub type DebtOf = Imbalance< +pub type Debt = Imbalance< >::Balance, // This will generally be implemented by increasing the total_issuance value. >::OnDropDebt, @@ -144,7 +151,7 @@ pub type DebtOf = Imbalance< /// Imbalance implying that the total_issuance value is greater than the sum of all account /// balances. -pub type CreditOf = Imbalance< +pub type Credit = Imbalance< >::Balance, // This will generally be implemented by decreasing the total_issuance value. >::OnDropCredit, diff --git a/frame/support/src/traits/tokens/fungible/item_of.rs b/frame/support/src/traits/tokens/fungible/item_of.rs new file mode 100644 index 000000000000..cf2d96ef2879 --- /dev/null +++ b/frame/support/src/traits/tokens/fungible/item_of.rs @@ -0,0 +1,451 @@ +// 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. + +//! Adapter to use `fungibles::*` implementations as `fungible::*`. + +use sp_core::Get; +use sp_runtime::{DispatchError, DispatchResult}; + +use super::*; +use crate::traits::tokens::{ + fungibles, DepositConsequence, Fortitude, Imbalance as ImbalanceT, Precision, Preservation, + Provenance, Restriction, WithdrawConsequence, +}; + +/// Convert a `fungibles` trait implementation into a `fungible` trait implementation by identifying +/// a single item. +pub struct ItemOf< + F: fungibles::Inspect, + A: Get<>::AssetId>, + AccountId, +>(sp_std::marker::PhantomData<(F, A, AccountId)>); + +impl< + F: fungibles::Inspect, + A: Get<>::AssetId>, + AccountId, + > Inspect for ItemOf +{ + type Balance = >::Balance; + fn total_issuance() -> Self::Balance { + >::total_issuance(A::get()) + } + fn active_issuance() -> Self::Balance { + >::active_issuance(A::get()) + } + fn minimum_balance() -> Self::Balance { + >::minimum_balance(A::get()) + } + fn balance(who: &AccountId) -> Self::Balance { + >::balance(A::get(), who) + } + fn total_balance(who: &AccountId) -> Self::Balance { + >::total_balance(A::get(), who) + } + fn reducible_balance( + who: &AccountId, + preservation: Preservation, + force: Fortitude, + ) -> Self::Balance { + >::reducible_balance(A::get(), who, preservation, force) + } + fn can_deposit( + who: &AccountId, + amount: Self::Balance, + provenance: Provenance, + ) -> DepositConsequence { + >::can_deposit(A::get(), who, amount, provenance) + } + fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence { + >::can_withdraw(A::get(), who, amount) + } +} + +impl< + F: fungibles::InspectHold, + A: Get<>::AssetId>, + AccountId, + > InspectHold for ItemOf +{ + type Reason = F::Reason; + + fn reducible_total_balance_on_hold(who: &AccountId, force: Fortitude) -> Self::Balance { + >::reducible_total_balance_on_hold( + A::get(), + who, + force, + ) + } + fn hold_available(reason: &Self::Reason, who: &AccountId) -> bool { + >::hold_available(A::get(), reason, who) + } + fn total_balance_on_hold(who: &AccountId) -> Self::Balance { + >::total_balance_on_hold(A::get(), who) + } + fn balance_on_hold(reason: &Self::Reason, who: &AccountId) -> Self::Balance { + >::balance_on_hold(A::get(), reason, who) + } + fn can_hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> bool { + >::can_hold(A::get(), reason, who, amount) + } +} + +impl< + F: fungibles::InspectFreeze, + A: Get<>::AssetId>, + AccountId, + > InspectFreeze for ItemOf +{ + type Id = F::Id; + fn balance_frozen(id: &Self::Id, who: &AccountId) -> Self::Balance { + >::balance_frozen(A::get(), id, who) + } + fn balance_freezable(who: &AccountId) -> Self::Balance { + >::balance_freezable(A::get(), who) + } + fn can_freeze(id: &Self::Id, who: &AccountId) -> bool { + >::can_freeze(A::get(), id, who) + } +} + +impl< + F: fungibles::Unbalanced, + A: Get<>::AssetId>, + AccountId, + > Unbalanced for ItemOf +{ + fn handle_dust(dust: regular::Dust) + where + Self: Sized, + { + >::handle_dust(fungibles::Dust(A::get(), dust.0)) + } + fn write_balance( + who: &AccountId, + amount: Self::Balance, + ) -> Result, DispatchError> { + >::write_balance(A::get(), who, amount) + } + fn set_total_issuance(amount: Self::Balance) -> () { + >::set_total_issuance(A::get(), amount) + } + fn decrease_balance( + who: &AccountId, + amount: Self::Balance, + precision: Precision, + preservation: Preservation, + force: Fortitude, + ) -> Result { + >::decrease_balance( + A::get(), + who, + amount, + precision, + preservation, + force, + ) + } + fn increase_balance( + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + >::increase_balance(A::get(), who, amount, precision) + } +} + +impl< + F: fungibles::UnbalancedHold, + A: Get<>::AssetId>, + AccountId, + > UnbalancedHold for ItemOf +{ + fn set_balance_on_hold( + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult { + >::set_balance_on_hold( + A::get(), + reason, + who, + amount, + ) + } + fn decrease_balance_on_hold( + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + >::decrease_balance_on_hold( + A::get(), + reason, + who, + amount, + precision, + ) + } + fn increase_balance_on_hold( + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + >::increase_balance_on_hold( + A::get(), + reason, + who, + amount, + precision, + ) + } +} + +impl< + F: fungibles::Mutate, + A: Get<>::AssetId>, + AccountId, + > Mutate for ItemOf +{ + fn mint_into(who: &AccountId, amount: Self::Balance) -> Result { + >::mint_into(A::get(), who, amount) + } + fn burn_from( + who: &AccountId, + amount: Self::Balance, + precision: Precision, + force: Fortitude, + ) -> Result { + >::burn_from(A::get(), who, amount, precision, force) + } + fn shelve(who: &AccountId, amount: Self::Balance) -> Result { + >::shelve(A::get(), who, amount) + } + fn restore(who: &AccountId, amount: Self::Balance) -> Result { + >::restore(A::get(), who, amount) + } + fn transfer( + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + preservation: Preservation, + ) -> Result { + >::transfer(A::get(), source, dest, amount, preservation) + } + + fn set_balance(who: &AccountId, amount: Self::Balance) -> Self::Balance { + >::set_balance(A::get(), who, amount) + } +} + +impl< + F: fungibles::MutateHold, + A: Get<>::AssetId>, + AccountId, + > MutateHold for ItemOf +{ + fn hold(reason: &Self::Reason, who: &AccountId, amount: Self::Balance) -> DispatchResult { + >::hold(A::get(), reason, who, amount) + } + fn release( + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + >::release(A::get(), reason, who, amount, precision) + } + fn burn_held( + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + force: Fortitude, + ) -> Result { + >::burn_held( + A::get(), + reason, + who, + amount, + precision, + force, + ) + } + fn transfer_on_hold( + reason: &Self::Reason, + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + precision: Precision, + mode: Restriction, + force: Fortitude, + ) -> Result { + >::transfer_on_hold( + A::get(), + reason, + source, + dest, + amount, + precision, + mode, + force, + ) + } + fn transfer_and_hold( + reason: &Self::Reason, + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + precision: Precision, + preservation: Preservation, + force: Fortitude, + ) -> Result { + >::transfer_and_hold( + A::get(), + reason, + source, + dest, + amount, + precision, + preservation, + force, + ) + } +} + +impl< + F: fungibles::MutateFreeze, + A: Get<>::AssetId>, + AccountId, + > MutateFreeze for ItemOf +{ + fn set_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult { + >::set_freeze(A::get(), id, who, amount) + } + fn extend_freeze(id: &Self::Id, who: &AccountId, amount: Self::Balance) -> DispatchResult { + >::extend_freeze(A::get(), id, who, amount) + } + fn thaw(id: &Self::Id, who: &AccountId) -> DispatchResult { + >::thaw(A::get(), id, who) + } +} + +pub struct ConvertImbalanceDropHandler( + sp_std::marker::PhantomData<(AccountId, Balance, AssetIdType, AssetId, Handler)>, +); + +impl< + AccountId, + Balance, + AssetIdType, + AssetId: Get, + Handler: crate::traits::tokens::fungibles::HandleImbalanceDrop, + > HandleImbalanceDrop + for ConvertImbalanceDropHandler +{ + fn handle(amount: Balance) { + Handler::handle(AssetId::get(), amount) + } +} + +impl< + F: fungibles::Inspect + + fungibles::Unbalanced + + fungibles::Balanced, + A: Get<>::AssetId>, + AccountId, + > Balanced for ItemOf +{ + type OnDropDebt = + ConvertImbalanceDropHandler; + type OnDropCredit = + ConvertImbalanceDropHandler; + fn deposit( + who: &AccountId, + value: Self::Balance, + precision: Precision, + ) -> Result, DispatchError> { + >::deposit(A::get(), who, value, precision) + .map(|debt| Imbalance::new(debt.peek())) + } + fn issue(amount: Self::Balance) -> Credit { + Imbalance::new(>::issue(A::get(), amount).peek()) + } + fn pair(amount: Self::Balance) -> (Debt, Credit) { + let (a, b) = >::pair(A::get(), amount); + (Imbalance::new(a.peek()), Imbalance::new(b.peek())) + } + fn rescind(amount: Self::Balance) -> Debt { + Imbalance::new(>::rescind(A::get(), amount).peek()) + } + fn resolve( + who: &AccountId, + credit: Credit, + ) -> Result<(), Credit> { + let credit = fungibles::Imbalance::new(A::get(), credit.peek()); + >::resolve(who, credit) + .map_err(|credit| Imbalance::new(credit.peek())) + } + fn settle( + who: &AccountId, + debt: Debt, + preservation: Preservation, + ) -> Result, Debt> { + let debt = fungibles::Imbalance::new(A::get(), debt.peek()); + >::settle(who, debt, preservation) + .map(|credit| Imbalance::new(credit.peek())) + .map_err(|debt| Imbalance::new(debt.peek())) + } + fn withdraw( + who: &AccountId, + value: Self::Balance, + precision: Precision, + preservation: Preservation, + force: Fortitude, + ) -> Result, DispatchError> { + >::withdraw( + A::get(), + who, + value, + precision, + preservation, + force, + ) + .map(|credit| Imbalance::new(credit.peek())) + } +} + +impl< + F: fungibles::BalancedHold, + A: Get<>::AssetId>, + AccountId, + > BalancedHold for ItemOf +{ + fn slash( + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> (Credit, Self::Balance) { + let (credit, amount) = + >::slash(A::get(), reason, who, amount); + (Imbalance::new(credit.peek()), amount) + } +} + +#[test] +fn test() {} diff --git a/frame/support/src/traits/tokens/fungible/mod.rs b/frame/support/src/traits/tokens/fungible/mod.rs new file mode 100644 index 000000000000..8ab63ad366f0 --- /dev/null +++ b/frame/support/src/traits/tokens/fungible/mod.rs @@ -0,0 +1,57 @@ +// 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. + +//! The traits for dealing with a single fungible token class and any associated types. +//! +//! ### User-implememted traits +//! - `Inspect`: Regular balance inspector functions. +//! - `Unbalanced`: Low-level balance mutating functions. Does not guarantee proper book-keeping and +//! so should not be called into directly from application code. Other traits depend on this and +//! provide default implementations based on it. +//! - `UnbalancedHold`: Low-level balance mutating functions for balances placed on hold. Does not +//! guarantee proper book-keeping and so should not be called into directly from application code. +//! Other traits depend on this and provide default implementations based on it. +//! - `Mutate`: Regular balance mutator functions. Pre-implemented using `Unbalanced`, though the +//! `done_*` functions should likely be reimplemented in case you want to do something following +//! the operation such as emit events. +//! - `InspectHold`: Inspector functions for balances on hold. +//! - `MutateHold`: Mutator functions for balances on hold. Mostly pre-implemented using +//! `UnbalancedHold`. +//! - `InspectFreeze`: Inspector functions for frozen balance. +//! - `MutateFreeze`: Mutator functions for frozen balance. +//! - `Balanced`: One-sided mutator functions for regular balances, which return imbalance objects +//! which guarantee eventual book-keeping. May be useful for some sophisticated operations where +//! funds must be removed from an account before it is known precisely what should be done with +//! them. + +pub mod conformance_tests; +pub mod freeze; +pub mod hold; +mod imbalance; +mod item_of; +mod regular; + +pub use freeze::{Inspect as InspectFreeze, Mutate as MutateFreeze}; +pub use hold::{ + Balanced as BalancedHold, Inspect as InspectHold, Mutate as MutateHold, + Unbalanced as UnbalancedHold, +}; +pub use imbalance::{Credit, Debt, HandleImbalanceDrop, Imbalance}; +pub use item_of::ItemOf; +pub use regular::{ + Balanced, DecreaseIssuance, Dust, IncreaseIssuance, Inspect, Mutate, Unbalanced, +}; diff --git a/frame/support/src/traits/tokens/fungible/regular.rs b/frame/support/src/traits/tokens/fungible/regular.rs new file mode 100644 index 000000000000..2838bed540aa --- /dev/null +++ b/frame/support/src/traits/tokens/fungible/regular.rs @@ -0,0 +1,507 @@ +// 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. + +//! `Inspect` and `Mutate` traits for working with regular balances. + +use crate::{ + dispatch::DispatchError, + ensure, + traits::{ + tokens::{ + misc::{ + Balance, DepositConsequence, + Fortitude::{self, Force, Polite}, + Precision::{self, BestEffort, Exact}, + Preservation::{self, Expendable}, + Provenance::{self, Extant}, + WithdrawConsequence, + }, + Imbalance as ImbalanceT, + }, + SameOrOther, TryDrop, + }, +}; +use sp_arithmetic::traits::{CheckedAdd, CheckedSub, One}; +use sp_runtime::{traits::Saturating, ArithmeticError, TokenError}; +use sp_std::marker::PhantomData; + +use super::{Credit, Debt, HandleImbalanceDrop, Imbalance}; + +/// Trait for providing balance-inspection access to a fungible asset. +pub trait Inspect: Sized { + /// Scalar type for representing balance of an account. + type Balance: Balance; + + /// The total amount of issuance in the system. + fn total_issuance() -> Self::Balance; + + /// The total amount of issuance in the system excluding those which are controlled by the + /// system. + fn active_issuance() -> Self::Balance { + Self::total_issuance() + } + + /// The minimum balance any single account may have. + fn minimum_balance() -> Self::Balance; + + /// Get the total amount of funds whose ultimate beneficial ownership can be determined as + /// `who`. + /// + /// This may include funds which are wholly inaccessible to `who`, either temporarily or even + /// indefinitely. + /// + /// For the amount of the balance which is currently free to be removed from the account without + /// error, use `reducible_balance`. + /// + /// For the amount of the balance which may eventually be free to be removed from the account, + /// use `balance()`. + fn total_balance(who: &AccountId) -> Self::Balance; + + /// Get the balance of `who` which does not include funds which are exclusively allocated to + /// subsystems of the chain ("on hold" or "reserved"). + /// + /// In general this isn't especially useful outside of tests, and for practical purposes, you'll + /// want to use `reducible_balance()`. + fn balance(who: &AccountId) -> Self::Balance; + + /// Get the maximum amount that `who` can withdraw/transfer successfully based on whether the + /// account should be kept alive (`preservation`) or whether we are willing to force the + /// reduction and potentially go below user-level restrictions on the minimum amount of the + /// account. + /// + /// Always less than or equal to `balance()`. + fn reducible_balance( + who: &AccountId, + preservation: Preservation, + force: Fortitude, + ) -> Self::Balance; + + /// Returns `true` if the balance of `who` may be increased by `amount`. + /// + /// - `who`: The account of which the balance should be increased by `amount`. + /// - `amount`: How much should the balance be increased? + /// - `provenance`: Will `amount` be minted to deposit it into `account` or is it already in the + /// system? + fn can_deposit( + who: &AccountId, + amount: Self::Balance, + provenance: Provenance, + ) -> DepositConsequence; + + /// Returns `Success` if the balance of `who` may be decreased by `amount`, otherwise + /// the consequence. + fn can_withdraw(who: &AccountId, amount: Self::Balance) -> WithdrawConsequence; +} + +/// Special dust type which can be type-safely converted into a `Credit`. +#[must_use] +pub struct Dust>(pub T::Balance); + +impl> Dust { + /// Convert `Dust` into an instance of `Credit`. + pub fn into_credit(self) -> Credit { + Credit::::new(self.0) + } +} + +/// A fungible token class where the balance can be set arbitrarily. +/// +/// **WARNING** +/// Do not use this directly unless you want trouble, since it allows you to alter account balances +/// without keeping the issuance up to date. It has no safeguards against accidentally creating +/// token imbalances in your system leading to accidental inflation or deflation. It's really just +/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to +/// use. +pub trait Unbalanced: Inspect { + /// Create some dust and handle it with `Self::handle_dust`. This is an unbalanced operation + /// and it must only be used when an account is modified in a raw fashion, outside of the entire + /// fungibles API. The `amount` is capped at `Self::minimum_balance() - 1`. + /// + /// This should not be reimplemented. + fn handle_raw_dust(amount: Self::Balance) { + Self::handle_dust(Dust(amount.min(Self::minimum_balance().saturating_sub(One::one())))) + } + + /// Do something with the dust which has been destroyed from the system. `Dust` can be converted + /// into a `Credit` with the `Balanced` trait impl. + fn handle_dust(dust: Dust); + + /// Forcefully set the balance of `who` to `amount`. + /// + /// If this call executes successfully, you can `assert_eq!(Self::balance(), amount);`. + /// + /// For implementations which include one or more balances on hold, then these are *not* + /// included in the `amount`. + /// + /// This function does its best to force the balance change through, but will not break system + /// invariants such as any Existential Deposits needed or overflows/underflows. + /// If this cannot be done for some reason (e.g. because the account cannot be created, deleted + /// or would overflow) then an `Err` is returned. + /// + /// If `Ok` is returned then its inner, if `Some` is the amount which was discarded as dust due + /// to existential deposit requirements. The default implementation of `decrease_balance` and + /// `increase_balance` converts this into an `Imbalance` and then passes it into `handle_dust`. + fn write_balance( + who: &AccountId, + amount: Self::Balance, + ) -> Result, DispatchError>; + + /// Set the total issuance to `amount`. + fn set_total_issuance(amount: Self::Balance); + + /// Reduce the balance of `who` by `amount`. + /// + /// If `precision` is `Exact` and it cannot be reduced by that amount for + /// some reason, return `Err` and don't reduce it at all. If `precision` is `BestEffort`, then + /// reduce the balance of `who` by the most that is possible, up to `amount`. + /// + /// In either case, if `Ok` is returned then the inner is the amount by which is was reduced. + /// Minimum balance will be respected and thus the returned amount may be up to + /// `Self::minimum_balance() - 1` greater than `amount` in the case that the reduction caused + /// the account to be deleted. + fn decrease_balance( + who: &AccountId, + mut amount: Self::Balance, + precision: Precision, + preservation: Preservation, + force: Fortitude, + ) -> Result { + let old_balance = Self::balance(who); + let free = Self::reducible_balance(who, preservation, force); + if let BestEffort = precision { + amount = amount.min(free); + } + let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?; + if let Some(dust) = Self::write_balance(who, new_balance)? { + Self::handle_dust(Dust(dust)); + } + Ok(old_balance.saturating_sub(new_balance)) + } + + /// Increase the balance of `who` by `amount`. + /// + /// If it cannot be increased by that amount for some reason, return `Err` and don't increase + /// it at all. If Ok, return the imbalance. + /// Minimum balance will be respected and an error will be returned if + /// `amount < Self::minimum_balance()` when the account of `who` is zero. + fn increase_balance( + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + let old_balance = Self::balance(who); + let new_balance = if let BestEffort = precision { + old_balance.saturating_add(amount) + } else { + old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)? + }; + if new_balance < Self::minimum_balance() { + // Attempt to increase from 0 to below minimum -> stays at zero. + if let BestEffort = precision { + Ok(Default::default()) + } else { + Err(TokenError::BelowMinimum.into()) + } + } else { + if new_balance == old_balance { + Ok(Default::default()) + } else { + if let Some(dust) = Self::write_balance(who, new_balance)? { + Self::handle_dust(Dust(dust)); + } + Ok(new_balance.saturating_sub(old_balance)) + } + } + } + + /// Reduce the active issuance by some amount. + fn deactivate(_: Self::Balance) {} + + /// Increase the active issuance by some amount, up to the outstanding amount reduced. + fn reactivate(_: Self::Balance) {} +} + +/// Trait for providing a basic fungible asset. +pub trait Mutate: Inspect + Unbalanced { + /// Increase the balance of `who` by exactly `amount`, minting new tokens. If that isn't + /// possible then an `Err` is returned and nothing is changed. + fn mint_into(who: &AccountId, amount: Self::Balance) -> Result { + Self::total_issuance().checked_add(&amount).ok_or(ArithmeticError::Overflow)?; + let actual = Self::increase_balance(who, amount, Exact)?; + Self::set_total_issuance(Self::total_issuance().saturating_add(actual)); + Self::done_mint_into(who, amount); + Ok(actual) + } + + /// Decrease the balance of `who` by at least `amount`, possibly slightly more in the case of + /// minimum-balance requirements, burning the tokens. If that isn't possible then an `Err` is + /// returned and nothing is changed. If successful, the amount of tokens reduced is returned. + fn burn_from( + who: &AccountId, + amount: Self::Balance, + precision: Precision, + force: Fortitude, + ) -> Result { + let actual = Self::reducible_balance(who, Expendable, force).min(amount); + ensure!(actual == amount || precision == BestEffort, TokenError::FundsUnavailable); + Self::total_issuance().checked_sub(&actual).ok_or(ArithmeticError::Overflow)?; + let actual = Self::decrease_balance(who, actual, BestEffort, Expendable, force)?; + Self::set_total_issuance(Self::total_issuance().saturating_sub(actual)); + Self::done_burn_from(who, actual); + Ok(actual) + } + + /// Attempt to decrease the `asset` balance of `who` by `amount`. + /// + /// Equivalent to `burn_from`, except with an expectation that within the bounds of some + /// universal issuance, the total assets `suspend`ed and `resume`d will be equivalent. The + /// implementation may be configured such that the total assets suspended may never be less than + /// the total assets resumed (which is the invariant for an issuing system), or the reverse + /// (which the invariant in a non-issuing system). + /// + /// Because of this expectation, any metadata associated with the asset is expected to survive + /// the suspect-resume cycle. + fn shelve(who: &AccountId, amount: Self::Balance) -> Result { + let actual = Self::reducible_balance(who, Expendable, Polite).min(amount); + ensure!(actual == amount, TokenError::FundsUnavailable); + Self::total_issuance().checked_sub(&actual).ok_or(ArithmeticError::Overflow)?; + let actual = Self::decrease_balance(who, actual, BestEffort, Expendable, Polite)?; + Self::set_total_issuance(Self::total_issuance().saturating_sub(actual)); + Self::done_shelve(who, actual); + Ok(actual) + } + + /// Attempt to increase the `asset` balance of `who` by `amount`. + /// + /// Equivalent to `mint_into`, except with an expectation that within the bounds of some + /// universal issuance, the total assets `suspend`ed and `resume`d will be equivalent. The + /// implementation may be configured such that the total assets suspended may never be less than + /// the total assets resumed (which is the invariant for an issuing system), or the reverse + /// (which the invariant in a non-issuing system). + /// + /// Because of this expectation, any metadata associated with the asset is expected to survive + /// the suspect-resume cycle. + fn restore(who: &AccountId, amount: Self::Balance) -> Result { + Self::total_issuance().checked_add(&amount).ok_or(ArithmeticError::Overflow)?; + let actual = Self::increase_balance(who, amount, Exact)?; + Self::set_total_issuance(Self::total_issuance().saturating_add(actual)); + Self::done_restore(who, amount); + Ok(actual) + } + + /// Transfer funds from one account into another. + fn transfer( + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + preservation: Preservation, + ) -> Result { + let _extra = Self::can_withdraw(source, amount).into_result(preservation != Expendable)?; + Self::can_deposit(dest, amount, Extant).into_result()?; + Self::decrease_balance(source, amount, BestEffort, preservation, Polite)?; + // This should never fail as we checked `can_deposit` earlier. But we do a best-effort + // anyway. + let _ = Self::increase_balance(dest, amount, BestEffort); + Self::done_transfer(source, dest, amount); + Ok(amount) + } + + /// Simple infallible function to force an account to have a particular balance, good for use + /// in tests and benchmarks but not recommended for production code owing to the lack of + /// error reporting. + /// + /// Returns the new balance. + fn set_balance(who: &AccountId, amount: Self::Balance) -> Self::Balance { + let b = Self::balance(who); + if b > amount { + Self::burn_from(who, b - amount, BestEffort, Force).map(|d| b.saturating_sub(d)) + } else { + Self::mint_into(who, amount - b).map(|d| b.saturating_add(d)) + } + .unwrap_or(b) + } + + fn done_mint_into(_who: &AccountId, _amount: Self::Balance) {} + fn done_burn_from(_who: &AccountId, _amount: Self::Balance) {} + fn done_shelve(_who: &AccountId, _amount: Self::Balance) {} + fn done_restore(_who: &AccountId, _amount: Self::Balance) {} + fn done_transfer(_source: &AccountId, _dest: &AccountId, _amount: Self::Balance) {} +} + +/// Simple handler for an imbalance drop which increases the total issuance of the system by the +/// imbalance amount. Used for leftover debt. +pub struct IncreaseIssuance(PhantomData<(AccountId, U)>); +impl> HandleImbalanceDrop + for IncreaseIssuance +{ + fn handle(amount: U::Balance) { + U::set_total_issuance(U::total_issuance().saturating_add(amount)) + } +} + +/// Simple handler for an imbalance drop which decreases the total issuance of the system by the +/// imbalance amount. Used for leftover credit. +pub struct DecreaseIssuance(PhantomData<(AccountId, U)>); +impl> HandleImbalanceDrop + for DecreaseIssuance +{ + fn handle(amount: U::Balance) { + U::set_total_issuance(U::total_issuance().saturating_sub(amount)) + } +} + +/// A fungible token class where any creation and deletion of tokens is semi-explicit and where the +/// total supply is maintained automatically. +/// +/// This is auto-implemented when a token class has `Unbalanced` implemented. +pub trait Balanced: Inspect + Unbalanced { + /// The type for managing what happens when an instance of `Debt` is dropped without being used. + type OnDropDebt: HandleImbalanceDrop; + /// The type for managing what happens when an instance of `Credit` is dropped without being + /// used. + type OnDropCredit: HandleImbalanceDrop; + + /// Reduce the total issuance by `amount` and return the according imbalance. The imbalance will + /// typically be used to reduce an account by the same amount with e.g. `settle`. + /// + /// This is infallible, but doesn't guarantee that the entire `amount` is burnt, for example + /// in the case of underflow. + fn rescind(amount: Self::Balance) -> Debt { + let old = Self::total_issuance(); + let new = old.saturating_sub(amount); + Self::set_total_issuance(new); + let delta = old - new; + Self::done_rescind(delta); + Imbalance::::new(delta) + } + + /// Increase the total issuance by `amount` and return the according imbalance. The imbalance + /// will typically be used to increase an account by the same amount with e.g. + /// `resolve_into_existing` or `resolve_creating`. + /// + /// This is infallible, but doesn't guarantee that the entire `amount` is issued, for example + /// in the case of overflow. + fn issue(amount: Self::Balance) -> Credit { + let old = Self::total_issuance(); + let new = old.saturating_add(amount); + Self::set_total_issuance(new); + let delta = new - old; + Self::done_issue(delta); + Imbalance::::new(delta) + } + + /// Produce a pair of imbalances that cancel each other out exactly. + /// + /// This is just the same as burning and issuing the same amount and has no effect on the + /// total issuance. + fn pair(amount: Self::Balance) -> (Debt, Credit) { + (Self::rescind(amount), Self::issue(amount)) + } + + /// Mints `value` into the account of `who`, creating it as needed. + /// + /// If `precision` is `BestEffort` and `value` in full could not be minted (e.g. due to + /// overflow), then the maximum is minted, up to `value`. If `precision` is `Exact`, then + /// exactly `value` must be minted into the account of `who` or the operation will fail with an + /// `Err` and nothing will change. + /// + /// If the operation is successful, this will return `Ok` with a `Debt` of the total value + /// added to the account. + fn deposit( + who: &AccountId, + value: Self::Balance, + precision: Precision, + ) -> Result, DispatchError> { + let increase = Self::increase_balance(who, value, precision)?; + Self::done_deposit(who, increase); + Ok(Imbalance::::new(increase)) + } + + /// Removes `value` balance from `who` account if possible. + /// + /// If `precision` is `BestEffort` and `value` in full could not be removed (e.g. due to + /// underflow), then the maximum is removed, up to `value`. If `precision` is `Exact`, then + /// exactly `value` must be removed from the account of `who` or the operation will fail with an + /// `Err` and nothing will change. + /// + /// If the removal is needed but not possible, then it returns `Err` and nothing is changed. + /// If the account needed to be deleted, then slightly more than `value` may be removed from the + /// account owning since up to (but not including) minimum balance may also need to be removed. + /// + /// If the operation is successful, this will return `Ok` with a `Credit` of the total value + /// removed from the account. + fn withdraw( + who: &AccountId, + value: Self::Balance, + precision: Precision, + preservation: Preservation, + force: Fortitude, + ) -> Result, DispatchError> { + let decrease = Self::decrease_balance(who, value, precision, preservation, force)?; + Self::done_withdraw(who, decrease); + Ok(Imbalance::::new(decrease)) + } + + /// The balance of `who` is increased in order to counter `credit`. If the whole of `credit` + /// cannot be countered, then nothing is changed and the original `credit` is returned in an + /// `Err`. + /// + /// Please note: If `credit.peek()` is less than `Self::minimum_balance()`, then `who` must + /// already exist for this to succeed. + fn resolve( + who: &AccountId, + credit: Credit, + ) -> Result<(), Credit> { + let v = credit.peek(); + let debt = match Self::deposit(who, v, Exact) { + Err(_) => return Err(credit), + Ok(d) => d, + }; + let result = credit.offset(debt).try_drop(); + debug_assert!(result.is_ok(), "ok deposit return must be equal to credit value; qed"); + Ok(()) + } + + /// The balance of `who` is decreased in order to counter `debt`. If the whole of `debt` + /// cannot be countered, then nothing is changed and the original `debt` is returned in an + /// `Err`. + fn settle( + who: &AccountId, + debt: Debt, + preservation: Preservation, + ) -> Result, Debt> { + let amount = debt.peek(); + let credit = match Self::withdraw(who, amount, Exact, preservation, Polite) { + Err(_) => return Err(debt), + Ok(d) => d, + }; + + match credit.offset(debt) { + SameOrOther::None => Ok(Credit::::zero()), + SameOrOther::Same(dust) => Ok(dust), + SameOrOther::Other(rest) => { + debug_assert!(false, "ok withdraw return must be at least debt value; qed"); + Err(rest) + }, + } + } + + fn done_rescind(_amount: Self::Balance) {} + fn done_issue(_amount: Self::Balance) {} + fn done_deposit(_who: &AccountId, _amount: Self::Balance) {} + fn done_withdraw(_who: &AccountId, _amount: Self::Balance) {} +} diff --git a/frame/support/src/traits/tokens/fungibles.rs b/frame/support/src/traits/tokens/fungibles.rs deleted file mode 100644 index a29cb974fe45..000000000000 --- a/frame/support/src/traits/tokens/fungibles.rs +++ /dev/null @@ -1,332 +0,0 @@ -// 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. - -//! The traits for sets of fungible tokens and any associated types. - -use super::{ - misc::{AssetId, Balance}, - *, -}; -use crate::dispatch::{DispatchError, DispatchResult}; -use sp_runtime::traits::Saturating; -use sp_std::vec::Vec; - -pub mod approvals; -mod balanced; -pub mod enumerable; -pub use enumerable::InspectEnumerable; -pub mod metadata; -pub use balanced::{Balanced, Unbalanced}; -mod imbalance; -pub use imbalance::{CreditOf, DebtOf, HandleImbalanceDrop, Imbalance}; -pub mod roles; - -/// Trait for providing balance-inspection access to a set of named fungible assets. -pub trait Inspect { - /// Means of identifying one asset class from another. - type AssetId: AssetId; - - /// Scalar type for representing balance of an account. - type Balance: Balance; - - /// The total amount of issuance in the system. - fn total_issuance(asset: Self::AssetId) -> Self::Balance; - - /// The total amount of issuance in the system excluding those which are controlled by the - /// system. - fn active_issuance(asset: Self::AssetId) -> Self::Balance { - Self::total_issuance(asset) - } - - /// The minimum balance any single account may have. - fn minimum_balance(asset: Self::AssetId) -> Self::Balance; - - /// Get the `asset` balance of `who`. - fn balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance; - - /// Get the maximum amount of `asset` that `who` can withdraw/transfer successfully. - fn reducible_balance(asset: Self::AssetId, who: &AccountId, keep_alive: bool) -> Self::Balance; - - /// Returns `true` if the `asset` balance of `who` may be increased by `amount`. - /// - /// - `asset`: The asset that should be deposited. - /// - `who`: The account of which the balance should be increased by `amount`. - /// - `amount`: How much should the balance be increased? - /// - `mint`: Will `amount` be minted to deposit it into `account`? - fn can_deposit( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - mint: bool, - ) -> DepositConsequence; - - /// Returns `Failed` if the `asset` balance of `who` may not be decreased by `amount`, otherwise - /// the consequence. - fn can_withdraw( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - ) -> WithdrawConsequence; - - /// Returns `true` if an `asset` exists. - fn asset_exists(asset: Self::AssetId) -> bool; -} - -/// Trait for reading metadata from a fungible asset. -pub trait InspectMetadata: Inspect { - /// Return the name of an asset. - fn name(asset: &Self::AssetId) -> Vec; - - /// Return the symbol of an asset. - fn symbol(asset: &Self::AssetId) -> Vec; - - /// Return the decimals of an asset. - fn decimals(asset: &Self::AssetId) -> u8; -} - -/// Trait for providing a set of named fungible assets which can be created and destroyed. -pub trait Mutate: Inspect { - /// Attempt to increase the `asset` balance of `who` by `amount`. - /// - /// If not possible then don't do anything. Possible reasons for failure include: - /// - Minimum balance not met. - /// - Account cannot be created (e.g. because there is no provider reference and/or the asset - /// isn't considered worth anything). - /// - /// Since this is an operation which should be possible to take alone, if successful it will - /// increase the overall supply of the underlying token. - fn mint_into(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult; - - /// Attempt to reduce the `asset` balance of `who` by `amount`. - /// - /// If not possible then don't do anything. Possible reasons for failure include: - /// - Less funds in the account than `amount` - /// - Liquidity requirements (locks, reservations) prevent the funds from being removed - /// - Operation would require destroying the account and it is required to stay alive (e.g. - /// because it's providing a needed provider reference). - /// - /// Since this is an operation which should be possible to take alone, if successful it will - /// reduce the overall supply of the underlying token. - /// - /// Due to minimum balance requirements, it's possible that the amount withdrawn could be up to - /// `Self::minimum_balance() - 1` more than the `amount`. The total amount withdrawn is returned - /// in an `Ok` result. This may be safely ignored if you don't mind the overall supply reducing. - fn burn_from( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - ) -> Result; - - /// Attempt to reduce the `asset` balance of `who` by as much as possible up to `amount`, and - /// possibly slightly more due to minimum_balance requirements. If no decrease is possible then - /// an `Err` is returned and nothing is changed. If successful, the amount of tokens reduced is - /// returned. - /// - /// The default implementation just uses `withdraw` along with `reducible_balance` to ensure - /// that is doesn't fail. - fn slash( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - ) -> Result { - Self::burn_from(asset, who, Self::reducible_balance(asset, who, false).min(amount)) - } - - /// Transfer funds from one account into another. The default implementation uses `mint_into` - /// and `burn_from` and may generate unwanted events. - fn teleport( - asset: Self::AssetId, - source: &AccountId, - dest: &AccountId, - amount: Self::Balance, - ) -> Result { - let extra = Self::can_withdraw(asset, &source, amount).into_result()?; - // As we first burn and then mint, we don't need to check if `mint` fits into the supply. - // If we can withdraw/burn it, we can also mint it again. - Self::can_deposit(asset, dest, amount.saturating_add(extra), false).into_result()?; - let actual = Self::burn_from(asset, source, amount)?; - debug_assert!( - actual == amount.saturating_add(extra), - "can_withdraw must agree with withdraw; qed" - ); - match Self::mint_into(asset, dest, actual) { - Ok(_) => Ok(actual), - Err(err) => { - debug_assert!(false, "can_deposit returned true previously; qed"); - // attempt to return the funds back to source - let revert = Self::mint_into(asset, source, actual); - debug_assert!(revert.is_ok(), "withdrew funds previously; qed"); - Err(err) - }, - } - } -} - -/// Trait for providing a set of named fungible assets which can only be transferred. -pub trait Transfer: Inspect { - /// Transfer funds from one account into another. - fn transfer( - asset: Self::AssetId, - source: &AccountId, - dest: &AccountId, - amount: Self::Balance, - keep_alive: bool, - ) -> Result; - - /// Reduce the active issuance by some amount. - fn deactivate(_: Self::AssetId, _: Self::Balance) {} - - /// Increase the active issuance by some amount, up to the outstanding amount reduced. - fn reactivate(_: Self::AssetId, _: Self::Balance) {} -} - -/// Trait for inspecting a set of named fungible assets which can be placed on hold. -pub trait InspectHold: Inspect { - /// Amount of funds held in hold. - fn balance_on_hold(asset: Self::AssetId, who: &AccountId) -> Self::Balance; - - /// Check to see if some `amount` of `asset` may be held on the account of `who`. - fn can_hold(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> bool; -} - -/// Trait for mutating a set of named fungible assets which can be placed on hold. -pub trait MutateHold: InspectHold + Transfer { - /// Hold some funds in an account. - fn hold(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult; - - /// Release some funds in an account from being on hold. - /// - /// If `best_effort` is `true`, then the amount actually released and returned as the inner - /// value of `Ok` may be smaller than the `amount` passed. - fn release( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - best_effort: bool, - ) -> Result; - - /// Transfer held funds into a destination account. - /// - /// If `on_hold` is `true`, then the destination account must already exist and the assets - /// transferred will still be on hold in the destination account. If not, then the destination - /// account need not already exist, but must be creatable. - /// - /// If `best_effort` is `true`, then an amount less than `amount` may be transferred without - /// error. - /// - /// The actual amount transferred is returned, or `Err` in the case of error and nothing is - /// changed. - fn transfer_held( - asset: Self::AssetId, - source: &AccountId, - dest: &AccountId, - amount: Self::Balance, - best_effort: bool, - on_hold: bool, - ) -> Result; -} - -/// Trait for mutating one of several types of fungible assets which can be held. -pub trait BalancedHold: Balanced + MutateHold { - /// Release and slash some funds in an account. - /// - /// The resulting imbalance is the first item of the tuple returned. - /// - /// As much funds up to `amount` will be deducted as possible. If this is less than `amount`, - /// then a non-zero second item will be returned. - fn slash_held( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - ) -> (CreditOf, Self::Balance); -} - -impl + MutateHold> BalancedHold for T { - fn slash_held( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - ) -> (CreditOf, Self::Balance) { - let actual = match Self::release(asset, who, amount, true) { - Ok(x) => x, - Err(_) => return (Imbalance::zero(asset), amount), - }; - >::slash(asset, who, actual) - } -} - -/// Trait for providing the ability to create new fungible assets. -pub trait Create: Inspect { - /// Create a new fungible asset. - fn create( - id: Self::AssetId, - admin: AccountId, - is_sufficient: bool, - min_balance: Self::Balance, - ) -> DispatchResult; -} - -/// Trait for providing the ability to destroy existing fungible assets. -pub trait Destroy: Inspect { - /// Start the destruction an existing fungible asset. - /// * `id`: The `AssetId` to be destroyed. successfully. - /// * `maybe_check_owner`: An optional account id that can be used to authorize the destroy - /// command. If not provided, no authorization checks will be performed before destroying - /// asset. - fn start_destroy(id: Self::AssetId, maybe_check_owner: Option) -> DispatchResult; - - /// Destroy all accounts associated with a given asset. - /// `destroy_accounts` should only be called after `start_destroy` has been called, and the - /// asset is in a `Destroying` state - /// - /// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset. - /// * `max_items`: The maximum number of accounts to be destroyed for a given call of the - /// function. This value should be small enough to allow the operation fit into a logical - /// block. - /// - /// Response: - /// * u32: Total number of approvals which were actually destroyed - /// - /// Due to weight restrictions, this function may need to be called multiple - /// times to fully destroy all approvals. It will destroy `max_items` approvals at a - /// time. - fn destroy_accounts(id: Self::AssetId, max_items: u32) -> Result; - /// Destroy all approvals associated with a given asset up to the `max_items` - /// `destroy_approvals` should only be called after `start_destroy` has been called, and the - /// asset is in a `Destroying` state - /// - /// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset. - /// * `max_items`: The maximum number of accounts to be destroyed for a given call of the - /// function. This value should be small enough to allow the operation fit into a logical - /// block. - /// - /// Response: - /// * u32: Total number of approvals which were actually destroyed - /// - /// Due to weight restrictions, this function may need to be called multiple - /// times to fully destroy all approvals. It will destroy `max_items` approvals at a - /// time. - fn destroy_approvals(id: Self::AssetId, max_items: u32) -> Result; - - /// Complete destroying asset and unreserve currency. - /// `finish_destroy` should only be called after `start_destroy` has been called, and the - /// asset is in a `Destroying` state. All accounts or approvals should be destroyed before - /// hand. - /// - /// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset. - fn finish_destroy(id: Self::AssetId) -> DispatchResult; -} diff --git a/frame/support/src/traits/tokens/fungibles/approvals.rs b/frame/support/src/traits/tokens/fungibles/approvals.rs index 48929955d949..7a80279b0198 100644 --- a/frame/support/src/traits/tokens/fungibles/approvals.rs +++ b/frame/support/src/traits/tokens/fungibles/approvals.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/tokens/fungibles/balanced.rs b/frame/support/src/traits/tokens/fungibles/balanced.rs deleted file mode 100644 index 9e50ff834a87..000000000000 --- a/frame/support/src/traits/tokens/fungibles/balanced.rs +++ /dev/null @@ -1,394 +0,0 @@ -// 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. - -//! The trait and associated types for sets of fungible tokens that manage total issuance without -//! requiring atomic balanced operations. - -use super::*; -use crate::{ - dispatch::{DispatchError, DispatchResult}, - traits::misc::{SameOrOther, TryDrop}, -}; -use sp_arithmetic::traits::Saturating; -use sp_runtime::{ - traits::{CheckedAdd, Zero}, - ArithmeticError, TokenError, -}; -use sp_std::marker::PhantomData; - -/// A fungible token class where any creation and deletion of tokens is semi-explicit and where the -/// total supply is maintained automatically. -/// -/// This is auto-implemented when a token class has `Unbalanced` implemented. -pub trait Balanced: Inspect { - /// The type for managing what happens when an instance of `Debt` is dropped without being used. - type OnDropDebt: HandleImbalanceDrop; - /// The type for managing what happens when an instance of `Credit` is dropped without being - /// used. - type OnDropCredit: HandleImbalanceDrop; - - /// Reduce the total issuance by `amount` and return the according imbalance. The imbalance will - /// typically be used to reduce an account by the same amount with e.g. `settle`. - /// - /// This is infallible, but doesn't guarantee that the entire `amount` is burnt, for example - /// in the case of underflow. - fn rescind(asset: Self::AssetId, amount: Self::Balance) -> DebtOf; - - /// Increase the total issuance by `amount` and return the according imbalance. The imbalance - /// will typically be used to increase an account by the same amount with e.g. - /// `resolve_into_existing` or `resolve_creating`. - /// - /// This is infallible, but doesn't guarantee that the entire `amount` is issued, for example - /// in the case of overflow. - fn issue(asset: Self::AssetId, amount: Self::Balance) -> CreditOf; - - /// Produce a pair of imbalances that cancel each other out exactly. - /// - /// This is just the same as burning and issuing the same amount and has no effect on the - /// total issuance. - fn pair( - asset: Self::AssetId, - amount: Self::Balance, - ) -> (DebtOf, CreditOf) { - (Self::rescind(asset, amount), Self::issue(asset, amount)) - } - - /// Deducts up to `value` from the combined balance of `who`, preferring to deduct from the - /// free balance. This function cannot fail. - /// - /// The resulting imbalance is the first item of the tuple returned. - /// - /// As much funds up to `value` will be deducted as possible. If this is less than `value`, - /// then a non-zero second item will be returned. - fn slash( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - ) -> (CreditOf, Self::Balance); - - /// Mints exactly `value` into the `asset` account of `who`. - /// - /// If `who` doesn't exist, nothing is done and an `Err` returned. This could happen because it - /// the account doesn't yet exist and it isn't possible to create it under the current - /// circumstances and with `value` in it. - fn deposit( - asset: Self::AssetId, - who: &AccountId, - value: Self::Balance, - ) -> Result, DispatchError>; - - /// Removes `value` free `asset` balance from `who` account if possible. - /// - /// If the removal is not possible, then it returns `Err` and nothing is changed. - /// - /// If the operation is successful, this will return `Ok` with a `NegativeImbalance` whose value - /// is no less than `value`. It may be more in the case that removing it reduced it below - /// `Self::minimum_balance()`. - fn withdraw( - asset: Self::AssetId, - who: &AccountId, - value: Self::Balance, - // TODO: liveness: ExistenceRequirement, - ) -> Result, DispatchError>; - - /// The balance of `who` is increased in order to counter `credit`. If the whole of `credit` - /// cannot be countered, then nothing is changed and the original `credit` is returned in an - /// `Err`. - /// - /// Please note: If `credit.peek()` is less than `Self::minimum_balance()`, then `who` must - /// already exist for this to succeed. - fn resolve( - who: &AccountId, - credit: CreditOf, - ) -> Result<(), CreditOf> { - let v = credit.peek(); - let debt = match Self::deposit(credit.asset(), who, v) { - Err(_) => return Err(credit), - Ok(d) => d, - }; - if let Ok(result) = credit.offset(debt) { - let result = result.try_drop(); - debug_assert!(result.is_ok(), "ok deposit return must be equal to credit value; qed"); - } else { - debug_assert!(false, "debt.asset is credit.asset; qed"); - } - Ok(()) - } - - /// The balance of `who` is decreased in order to counter `debt`. If the whole of `debt` - /// cannot be countered, then nothing is changed and the original `debt` is returned in an - /// `Err`. - fn settle( - who: &AccountId, - debt: DebtOf, - // TODO: liveness: ExistenceRequirement, - ) -> Result, DebtOf> { - let amount = debt.peek(); - let asset = debt.asset(); - let credit = match Self::withdraw(asset, who, amount) { - Err(_) => return Err(debt), - Ok(d) => d, - }; - match credit.offset(debt) { - Ok(SameOrOther::None) => Ok(CreditOf::::zero(asset)), - Ok(SameOrOther::Same(dust)) => Ok(dust), - Ok(SameOrOther::Other(rest)) => { - debug_assert!(false, "ok withdraw return must be at least debt value; qed"); - Err(rest) - }, - Err(_) => { - debug_assert!(false, "debt.asset is credit.asset; qed"); - Ok(CreditOf::::zero(asset)) - }, - } - } -} - -/// A fungible token class where the balance can be set arbitrarily. -/// -/// **WARNING** -/// Do not use this directly unless you want trouble, since it allows you to alter account balances -/// without keeping the issuance up to date. It has no safeguards against accidentally creating -/// token imbalances in your system leading to accidental inflation or deflation. It's really just -/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to -/// use. -pub trait Unbalanced: Inspect { - /// Set the `asset` balance of `who` to `amount`. If this cannot be done for some reason (e.g. - /// because the account cannot be created or an overflow) then an `Err` is returned. - fn set_balance(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> DispatchResult; - - /// Set the total issuance of `asset` to `amount`. - fn set_total_issuance(asset: Self::AssetId, amount: Self::Balance); - - /// Reduce the `asset` balance of `who` by `amount`. If it cannot be reduced by that amount for - /// some reason, return `Err` and don't reduce it at all. If Ok, return the imbalance. - /// - /// Minimum balance will be respected and the returned imbalance may be up to - /// `Self::minimum_balance() - 1` greater than `amount`. - fn decrease_balance( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - ) -> Result { - let old_balance = Self::balance(asset, who); - let (mut new_balance, mut amount) = if Self::reducible_balance(asset, who, false) < amount { - return Err(TokenError::NoFunds.into()) - } else { - (old_balance - amount, amount) - }; - if new_balance < Self::minimum_balance(asset) { - amount = amount.saturating_add(new_balance); - new_balance = Zero::zero(); - } - // Defensive only - this should not fail now. - Self::set_balance(asset, who, new_balance)?; - Ok(amount) - } - - /// Reduce the `asset` balance of `who` by the most that is possible, up to `amount`. - /// - /// Minimum balance will be respected and the returned imbalance may be up to - /// `Self::minimum_balance() - 1` greater than `amount`. - /// - /// Return the imbalance by which the account was reduced. - fn decrease_balance_at_most( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - ) -> Self::Balance { - let old_balance = Self::balance(asset, who); - let old_free_balance = Self::reducible_balance(asset, who, false); - let (mut new_balance, mut amount) = if old_free_balance < amount { - (old_balance.saturating_sub(old_free_balance), old_free_balance) - } else { - (old_balance - amount, amount) - }; - let minimum_balance = Self::minimum_balance(asset); - if new_balance < minimum_balance { - amount = amount.saturating_add(new_balance); - new_balance = Zero::zero(); - } - let mut r = Self::set_balance(asset, who, new_balance); - if r.is_err() { - // Some error, probably because we tried to destroy an account which cannot be - // destroyed. - if new_balance.is_zero() && amount >= minimum_balance { - new_balance = minimum_balance; - amount -= minimum_balance; - r = Self::set_balance(asset, who, new_balance); - } - if r.is_err() { - // Still an error. Apparently it's not possible to reduce at all. - amount = Zero::zero(); - } - } - amount - } - - /// Increase the `asset` balance of `who` by `amount`. If it cannot be increased by that amount - /// for some reason, return `Err` and don't increase it at all. If Ok, return the imbalance. - /// - /// Minimum balance will be respected and an error will be returned if - /// `amount < Self::minimum_balance()` when the account of `who` is zero. - fn increase_balance( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - ) -> Result { - let old_balance = Self::balance(asset, who); - let new_balance = old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)?; - if new_balance < Self::minimum_balance(asset) { - return Err(TokenError::BelowMinimum.into()) - } - if old_balance != new_balance { - Self::set_balance(asset, who, new_balance)?; - } - Ok(amount) - } - - /// Increase the `asset` balance of `who` by the most that is possible, up to `amount`. - /// - /// Minimum balance will be respected and the returned imbalance will be zero in the case that - /// `amount < Self::minimum_balance()`. - /// - /// Return the imbalance by which the account was increased. - fn increase_balance_at_most( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - ) -> Self::Balance { - let old_balance = Self::balance(asset, who); - let mut new_balance = old_balance.saturating_add(amount); - let mut amount = new_balance - old_balance; - if new_balance < Self::minimum_balance(asset) { - new_balance = Zero::zero(); - amount = Zero::zero(); - } - if old_balance == new_balance || Self::set_balance(asset, who, new_balance).is_ok() { - amount - } else { - Zero::zero() - } - } -} - -/// Simple handler for an imbalance drop which increases the total issuance of the system by the -/// imbalance amount. Used for leftover debt. -pub struct IncreaseIssuance(PhantomData<(AccountId, U)>); -impl> HandleImbalanceDrop - for IncreaseIssuance -{ - fn handle(asset: U::AssetId, amount: U::Balance) { - U::set_total_issuance(asset, U::total_issuance(asset).saturating_add(amount)) - } -} - -/// Simple handler for an imbalance drop which decreases the total issuance of the system by the -/// imbalance amount. Used for leftover credit. -pub struct DecreaseIssuance(PhantomData<(AccountId, U)>); -impl> HandleImbalanceDrop - for DecreaseIssuance -{ - fn handle(asset: U::AssetId, amount: U::Balance) { - U::set_total_issuance(asset, U::total_issuance(asset).saturating_sub(amount)) - } -} - -/// An imbalance type which uses `DecreaseIssuance` to deal with anything `Drop`ed. -/// -/// Basically means that funds in someone's account have been removed and not yet placed anywhere -/// else. If it gets dropped, then those funds will be assumed to be "burned" and the total supply -/// will be accordingly decreased to ensure it equals the sum of the balances of all accounts. -type Credit = Imbalance< - >::AssetId, - >::Balance, - DecreaseIssuance, - IncreaseIssuance, ->; - -/// An imbalance type which uses `IncreaseIssuance` to deal with anything `Drop`ed. -/// -/// Basically means that there are funds in someone's account whose origin is as yet unaccounted -/// for. If it gets dropped, then those funds will be assumed to be "minted" and the total supply -/// will be accordingly increased to ensure it equals the sum of the balances of all accounts. -type Debt = Imbalance< - >::AssetId, - >::Balance, - IncreaseIssuance, - DecreaseIssuance, ->; - -/// Create some `Credit` item. Only for internal use. -fn credit>( - asset: U::AssetId, - amount: U::Balance, -) -> Credit { - Imbalance::new(asset, amount) -} - -/// Create some `Debt` item. Only for internal use. -fn debt>( - asset: U::AssetId, - amount: U::Balance, -) -> Debt { - Imbalance::new(asset, amount) -} - -impl> Balanced for U { - type OnDropCredit = DecreaseIssuance; - type OnDropDebt = IncreaseIssuance; - fn rescind(asset: Self::AssetId, amount: Self::Balance) -> Debt { - U::set_total_issuance(asset, U::total_issuance(asset).saturating_sub(amount)); - debt(asset, amount) - } - fn issue(asset: Self::AssetId, amount: Self::Balance) -> Credit { - U::set_total_issuance(asset, U::total_issuance(asset).saturating_add(amount)); - credit(asset, amount) - } - fn slash( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - ) -> (Credit, Self::Balance) { - let slashed = U::decrease_balance_at_most(asset, who, amount); - // `slashed` could be less than, greater than or equal to `amount`. - // If slashed == amount, it means the account had at least amount in it and it could all be - // removed without a problem. - // If slashed > amount, it means the account had more than amount in it, but not enough more - // to push it over minimum_balance. - // If slashed < amount, it means the account didn't have enough in it to be reduced by - // `amount` without being destroyed. - (credit(asset, slashed), amount.saturating_sub(slashed)) - } - fn deposit( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - ) -> Result, DispatchError> { - let increase = U::increase_balance(asset, who, amount)?; - Ok(debt(asset, increase)) - } - fn withdraw( - asset: Self::AssetId, - who: &AccountId, - amount: Self::Balance, - // TODO: liveness: ExistenceRequirement, - ) -> Result, DispatchError> { - let decrease = U::decrease_balance(asset, who, amount)?; - Ok(credit(asset, decrease)) - } -} diff --git a/frame/support/src/traits/tokens/fungibles/enumerable.rs b/frame/support/src/traits/tokens/fungibles/enumerable.rs index 151d15b3684a..08bb784a7dbf 100644 --- a/frame/support/src/traits/tokens/fungibles/enumerable.rs +++ b/frame/support/src/traits/tokens/fungibles/enumerable.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,10 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::traits::fungibles::Inspect; - /// Interface for enumerating assets in existence or owned by a given account. -pub trait InspectEnumerable: Inspect { +pub trait Inspect: super::Inspect { type AssetsIterator; /// Returns an iterator of the collections in existence. diff --git a/frame/support/src/traits/tokens/fungibles/freeze.rs b/frame/support/src/traits/tokens/fungibles/freeze.rs new file mode 100644 index 000000000000..08549c2d4b74 --- /dev/null +++ b/frame/support/src/traits/tokens/fungibles/freeze.rs @@ -0,0 +1,78 @@ +// 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. + +//! The traits for putting freezes within a single fungible token class. + +use scale_info::TypeInfo; +use sp_runtime::DispatchResult; + +/// Trait for inspecting a fungible asset which can be frozen. Freezing is essentially setting a +/// minimum balance below which the total balance (inclusive of any funds placed on hold) may not +/// be normally allowed to drop. Generally, freezers will provide an "update" function such that +/// if the total balance does drop below the limit, then the freezer can update their housekeeping +/// accordingly. +pub trait Inspect: super::Inspect { + /// An identifier for a freeze. + type Id: codec::Encode + TypeInfo + 'static; + + /// Amount of funds held in reserve by `who` for the given `id`. + fn balance_frozen(asset: Self::AssetId, id: &Self::Id, who: &AccountId) -> Self::Balance; + + /// The amount of the balance which can become frozen. Defaults to `total_balance()`. + fn balance_freezable(asset: Self::AssetId, who: &AccountId) -> Self::Balance { + Self::total_balance(asset, who) + } + + /// Returns `true` if it's possible to introduce a freeze for the given `id` onto the + /// account of `who`. This will be true as long as the implementor supports as many + /// concurrent freeze locks as there are possible values of `id`. + fn can_freeze(asset: Self::AssetId, id: &Self::Id, who: &AccountId) -> bool; +} + +/// Trait for introducing, altering and removing locks to freeze an account's funds so they never +/// go below a set minimum. +pub trait Mutate: Inspect { + /// Prevent actions which would reduce the balance of the account of `who` below the given + /// `amount` and identify this restriction though the given `id`. Unlike `extend_freeze`, any + /// outstanding freeze in place for `who` under the `id` are dropped. + /// + /// If `amount` is zero, it is equivalent to using `thaw`. + /// + /// Note that `amount` can be greater than the total balance, if desired. + fn set_freeze( + asset: Self::AssetId, + id: &Self::Id, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult; + + /// Prevent the balance of the account of `who` from being reduced below the given `amount` and + /// identify this restriction though the given `id`. Unlike `set_freeze`, this does not + /// counteract any pre-existing freezes in place for `who` under the `id`. Also unlike + /// `set_freeze`, in the case that `amount` is zero, this is no-op and never fails. + /// + /// Note that more funds can be locked than the total balance, if desired. + fn extend_freeze( + asset: Self::AssetId, + id: &Self::Id, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult; + + /// Remove an existing lock. + fn thaw(asset: Self::AssetId, id: &Self::Id, who: &AccountId) -> DispatchResult; +} diff --git a/frame/support/src/traits/tokens/fungibles/hold.rs b/frame/support/src/traits/tokens/fungibles/hold.rs new file mode 100644 index 000000000000..8fc038c57be3 --- /dev/null +++ b/frame/support/src/traits/tokens/fungibles/hold.rs @@ -0,0 +1,466 @@ +// 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. + +//! The traits for putting holds within a single fungible token class. + +use crate::{ + ensure, + traits::tokens::{ + DepositConsequence::Success, + Fortitude::{self, Force}, + Precision::{self, BestEffort, Exact}, + Preservation::{self, Protect}, + Provenance::Extant, + Restriction::{self, Free, OnHold}, + }, +}; +use scale_info::TypeInfo; +use sp_arithmetic::{ + traits::{CheckedAdd, CheckedSub, Zero}, + ArithmeticError, +}; +use sp_runtime::{DispatchError, DispatchResult, Saturating, TokenError}; + +use super::*; + +/// Trait for inspecting a fungible asset whose accounts support partitioning and slashing. +pub trait Inspect: super::Inspect { + /// An identifier for a hold. Used for disambiguating different holds so that + /// they can be individually replaced or removed and funds from one hold don't accidentally + /// become unreserved or slashed for another. + type Reason: codec::Encode + TypeInfo + 'static; + + /// Amount of funds on hold (for all hold reasons) of `who`. + fn total_balance_on_hold(asset: Self::AssetId, who: &AccountId) -> Self::Balance; + + /// Get the maximum amount that the `total_balance_on_hold` of `who` can be reduced successfully + /// based on whether we are willing to force the reduction and potentially go below user-level + /// restrictions on the minimum amount of the account. Note: This cannot bring the account into + /// an inconsistent state with regards any required existential deposit. + /// + /// Always less than `total_balance_on_hold()`. + fn reducible_total_balance_on_hold( + asset: Self::AssetId, + who: &AccountId, + force: Fortitude, + ) -> Self::Balance; + + /// Amount of funds on hold (for the given reason) of `who`. + fn balance_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + ) -> Self::Balance; + + /// Returns `true` if it's possible to place (additional) funds under a hold of a given + /// `reason`. This may fail if the account has exhausted a limited number of concurrent + /// holds or if it cannot be made to exist (e.g. there is no provider reference). + /// + /// NOTE: This does not take into account changes which could be made to the account of `who` + /// (such as removing a provider reference) after this call is made. Any usage of this should + /// therefore ensure the account is already in the appropriate state prior to calling it. + fn hold_available(asset: Self::AssetId, reason: &Self::Reason, who: &AccountId) -> bool; + + /// Check to see if some `amount` of funds of `who` may be placed on hold with the given + /// `reason`. Reasons why this may not be true: + /// + /// - The implementor supports only a limited number of concurrent holds on an account which is + /// the possible values of `reason`; + /// - The total balance of the account is less than `amount`; + /// - Removing `amount` from the total balance would kill the account and remove the only + /// provider reference. + /// + /// Note: we pass `Fortitude::Force` as the last argument to `reducible_balance` since we assume + /// that if needed the balance can slashed. If we are using a simple non-forcing + /// reserve-transfer, then we really ought to check that we are not reducing the funds below the + /// freeze-limit (if any). + /// + /// NOTE: This does not take into account changes which could be made to the account of `who` + /// (such as removing a provider reference) after this call is made. Any usage of this should + /// therefore ensure the account is already in the appropriate state prior to calling it. + fn ensure_can_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult { + ensure!(Self::hold_available(asset.clone(), reason, who), TokenError::CannotCreateHold); + ensure!( + amount <= Self::reducible_balance(asset, who, Protect, Force), + TokenError::FundsUnavailable + ); + Ok(()) + } + + /// Check to see if some `amount` of funds of `who` may be placed on hold for the given + /// `reason`. Reasons why this may not be true: + /// + /// - The implementor supports only a limited number of concurrent holds on an account which is + /// the possible values of `reason`; + /// - The main balance of the account is less than `amount`; + /// - Removing `amount` from the main balance would kill the account and remove the only + /// provider reference. + /// + /// NOTE: This does not take into account changes which could be made to the account of `who` + /// (such as removing a provider reference) after this call is made. Any usage of this should + /// therefore ensure the account is already in the appropriate state prior to calling it. + fn can_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> bool { + Self::ensure_can_hold(asset, reason, who, amount).is_ok() + } +} + +/// A fungible, holdable token class where the balance on hold can be set arbitrarily. +/// +/// **WARNING** +/// Do not use this directly unless you want trouble, since it allows you to alter account balances +/// without keeping the issuance up to date. It has no safeguards against accidentally creating +/// token imbalances in your system leading to accidental imflation or deflation. It's really just +/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to +/// use. +pub trait Unbalanced: Inspect { + /// Forcefully set the balance on hold of `who` to `amount`. This is independent of any other + /// balances on hold or the main ("free") balance. + /// + /// If this call executes successfully, you can `assert_eq!(Self::balance_on_hold(), amount);`. + /// + /// This function does its best to force the balance change through, but will not break system + /// invariants such as any Existential Deposits needed or overflows/underflows. + /// If this cannot be done for some reason (e.g. because the account doesn't exist) then an + /// `Err` is returned. + // Implmentation note: This should increment the consumer refs if it moves total on hold from + // zero to non-zero and decrement in the opposite direction. + // + // Since this was not done in the previous logic, this will need either a migration or a + // state item which tracks whether the account is on the old logic or new. + fn set_balance_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult; + + /// Reduce the balance on hold of `who` by `amount`. + /// + /// If `precision` is `Precision::Exact` and it cannot be reduced by that amount for + /// some reason, return `Err` and don't reduce it at all. If `precision` is + /// `Precision::BestEffort`, then reduce the balance of `who` by the most that is possible, up + /// to `amount`. + /// + /// In either case, if `Ok` is returned then the inner is the amount by which is was reduced. + fn decrease_balance_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + mut amount: Self::Balance, + precision: Precision, + ) -> Result { + let old_balance = Self::balance_on_hold(asset.clone(), reason, who); + if let BestEffort = precision { + amount = amount.min(old_balance); + } + let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?; + Self::set_balance_on_hold(asset, reason, who, new_balance)?; + Ok(amount) + } + + /// Increase the balance on hold of `who` by `amount`. + /// + /// If it cannot be increased by that amount for some reason, return `Err` and don't increase + /// it at all. If Ok, return the imbalance. + fn increase_balance_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + let old_balance = Self::balance_on_hold(asset.clone(), reason, who); + let new_balance = if let BestEffort = precision { + old_balance.saturating_add(amount) + } else { + old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)? + }; + let amount = new_balance.saturating_sub(old_balance); + if !amount.is_zero() { + Self::set_balance_on_hold(asset, reason, who, new_balance)?; + } + Ok(amount) + } +} + +/// Trait for slashing a fungible asset which can be place on hold. +pub trait Balanced: super::Balanced + Unbalanced { + /// Reduce the balance of some funds on hold in an account. + /// + /// The resulting imbalance is the first item of the tuple returned. + /// + /// As much funds that are on hold up to `amount` will be deducted as possible. If this is less + /// than `amount`, then a non-zero second item will be returned. + fn slash( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> (Credit, Self::Balance) { + let decrease = + Self::decrease_balance_on_hold(asset.clone(), reason, who, amount, BestEffort) + .unwrap_or(Default::default()); + let credit = + Imbalance::::new( + asset.clone(), + decrease, + ); + Self::done_slash(asset, reason, who, decrease); + (credit, amount.saturating_sub(decrease)) + } + + fn done_slash( + _asset: Self::AssetId, + _reason: &Self::Reason, + _who: &AccountId, + _amount: Self::Balance, + ) { + } +} + +/// Trait for mutating a fungible asset which can be placed on hold. +pub trait Mutate: + Inspect + super::Unbalanced + Unbalanced +{ + /// Hold some funds in an account. If a hold for `reason` is already in place, then this + /// will increase it. + fn hold( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + ) -> DispatchResult { + // NOTE: This doesn't change the total balance of the account so there's no need to + // check liquidity. + + Self::ensure_can_hold(asset.clone(), reason, who, amount)?; + // Should be infallible now, but we proceed softly anyway. + Self::decrease_balance(asset.clone(), who, amount, Exact, Protect, Force)?; + Self::increase_balance_on_hold(asset.clone(), reason, who, amount, BestEffort)?; + Self::done_hold(asset, reason, who, amount); + Ok(()) + } + + /// Release up to `amount` held funds in an account. + /// + /// The actual amount released is returned with `Ok`. + /// + /// If `precision` is `BestEffort`, then the amount actually unreserved and returned as the + /// inner value of `Ok` may be smaller than the `amount` passed. + fn release( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + // NOTE: This doesn't change the total balance of the account so there's no need to + // check liquidity. + + // We want to make sure we can deposit the amount in advance. If we can't then something is + // very wrong. + ensure!( + Self::can_deposit(asset.clone(), who, amount, Extant) == Success, + TokenError::CannotCreate + ); + // Get the amount we can actually take from the hold. This might be less than what we want + // if we're only doing a best-effort. + let amount = Self::decrease_balance_on_hold(asset.clone(), reason, who, amount, precision)?; + // Increase the main balance by what we took. We always do a best-effort here because we + // already checked that we can deposit before. + let actual = Self::increase_balance(asset.clone(), who, amount, BestEffort)?; + Self::done_release(asset, reason, who, actual); + Ok(actual) + } + + /// Attempt to decrease the balance of `who` which is held for the given `reason` by `amount`. + /// + /// If `precision` is true, then as much as possible is reduced, up to `amount`, and the + /// amount of tokens reduced is returned. Otherwise, if the total amount can be reduced, then it + /// is and the amount returned, and if not, then nothing changes and `Err` is returned. + /// + /// If `force` is `Force`, then locks/freezes will be ignored. This should only be used when + /// conducting slashing or other activity which materially disadvantages the account holder + /// since it could provide a means of circumventing freezes. + fn burn_held( + asset: Self::AssetId, + reason: &Self::Reason, + who: &AccountId, + mut amount: Self::Balance, + precision: Precision, + force: Fortitude, + ) -> Result { + // We must check total-balance requirements if `!force`. + let liquid = Self::reducible_total_balance_on_hold(asset.clone(), who, force); + if let BestEffort = precision { + amount = amount.min(liquid); + } else { + ensure!(amount <= liquid, TokenError::Frozen); + } + let amount = Self::decrease_balance_on_hold(asset.clone(), reason, who, amount, precision)?; + Self::set_total_issuance( + asset.clone(), + Self::total_issuance(asset.clone()).saturating_sub(amount), + ); + Self::done_burn_held(asset, reason, who, amount); + Ok(amount) + } + + /// Transfer held funds into a destination account. + /// + /// If `on_hold` is `true`, then the destination account must already exist and the assets + /// transferred will still be on hold in the destination account. If not, then the destination + /// account need not already exist, but must be creatable. + /// + /// If `precision` is `BestEffort`, then an amount less than `amount` may be transferred without + /// error. + /// + /// If `force` is `Force`, then other fund-locking mechanisms may be disregarded. It should be + /// left as `Regular` in most circumstances, but when you want the same power as a `slash`, it + /// may be `Force`. + /// + /// The actual amount transferred is returned, or `Err` in the case of error and nothing is + /// changed. + fn transfer_on_hold( + asset: Self::AssetId, + reason: &Self::Reason, + source: &AccountId, + dest: &AccountId, + mut amount: Self::Balance, + precision: Precision, + mode: Restriction, + force: Fortitude, + ) -> Result { + // We must check total-balance requirements if `!force`. + let have = Self::balance_on_hold(asset.clone(), reason, source); + let liquid = Self::reducible_total_balance_on_hold(asset.clone(), source, force); + if let BestEffort = precision { + amount = amount.min(liquid).min(have); + } else { + ensure!(amount <= liquid, TokenError::Frozen); + ensure!(amount <= have, TokenError::FundsUnavailable); + } + + // We want to make sure we can deposit the amount in advance. If we can't then something is + // very wrong. + ensure!( + Self::can_deposit(asset.clone(), dest, amount, Extant) == Success, + TokenError::CannotCreate + ); + ensure!( + mode == Free || Self::hold_available(asset.clone(), reason, dest), + TokenError::CannotCreateHold + ); + + let amount = + Self::decrease_balance_on_hold(asset.clone(), reason, source, amount, precision)?; + let actual = if mode == OnHold { + Self::increase_balance_on_hold(asset.clone(), reason, dest, amount, precision)? + } else { + Self::increase_balance(asset.clone(), dest, amount, precision)? + }; + Self::done_transfer_on_hold(asset, reason, source, dest, actual); + Ok(actual) + } + + /// Transfer some `amount` of free balance from `source` to become owned by `dest` but on hold + /// for `reason`. + /// for `reason`. + /// + /// If `precision` is `BestEffort`, then an amount less than `amount` may be transferred without + /// error. + /// + /// `source` must obey the requirements of `keep_alive`. + /// + /// If `force` is `Force`, then other fund-locking mechanisms may be disregarded. It should be + /// left as `Regular` in most circumstances, but when you want the same power as a `slash`, it + /// may be `Force`. + /// + /// The amount placed on hold is returned or `Err` in the case of error and nothing is changed. + /// + /// WARNING: This may return an error after a partial storage mutation. It should be used only + /// inside a transactional storage context and an `Err` result must imply a storage rollback. + fn transfer_and_hold( + asset: Self::AssetId, + reason: &Self::Reason, + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + precision: Precision, + expendability: Preservation, + force: Fortitude, + ) -> Result { + ensure!(Self::hold_available(asset.clone(), reason, dest), TokenError::CannotCreateHold); + ensure!( + Self::can_deposit(asset.clone(), dest, amount, Extant) == Success, + TokenError::CannotCreate + ); + let actual = + Self::decrease_balance(asset.clone(), source, amount, precision, expendability, force)?; + Self::increase_balance_on_hold(asset.clone(), reason, dest, actual, precision)?; + Self::done_transfer_on_hold(asset, reason, source, dest, actual); + Ok(actual) + } + + fn done_hold( + _asset: Self::AssetId, + _reason: &Self::Reason, + _who: &AccountId, + _amount: Self::Balance, + ) { + } + fn done_release( + _asset: Self::AssetId, + _reason: &Self::Reason, + _who: &AccountId, + _amount: Self::Balance, + ) { + } + fn done_burn_held( + _asset: Self::AssetId, + _reason: &Self::Reason, + _who: &AccountId, + _amount: Self::Balance, + ) { + } + fn done_transfer_on_hold( + _asset: Self::AssetId, + _reason: &Self::Reason, + _source: &AccountId, + _dest: &AccountId, + _amount: Self::Balance, + ) { + } + fn done_transfer_and_hold( + _asset: Self::AssetId, + _reason: &Self::Reason, + _source: &AccountId, + _dest: &AccountId, + _transferred: Self::Balance, + ) { + } +} diff --git a/frame/support/src/traits/tokens/fungibles/imbalance.rs b/frame/support/src/traits/tokens/fungibles/imbalance.rs index 61bd4a43064e..1668268ea2dc 100644 --- a/frame/support/src/traits/tokens/fungibles/imbalance.rs +++ b/frame/support/src/traits/tokens/fungibles/imbalance.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,12 +18,11 @@ //! The imbalance type and its associates, which handles keeps everything adding up properly with //! unbalanced operations. -use super::{ - balanced::Balanced, - fungibles::{AssetId, Balance}, - *, +use super::*; +use crate::traits::{ + misc::{SameOrOther, TryDrop}, + tokens::{AssetId, Balance}, }; -use crate::traits::misc::{SameOrOther, TryDrop}; use sp_runtime::{traits::Zero, RuntimeDebug}; use sp_std::marker::PhantomData; @@ -60,7 +59,7 @@ impl< { fn drop(&mut self) { if !self.amount.is_zero() { - OnDrop::handle(self.asset, self.amount) + OnDrop::handle(self.asset.clone(), self.amount) } } } @@ -105,9 +104,9 @@ impl< pub fn split(self, amount: B) -> (Self, Self) { let first = self.amount.min(amount); let second = self.amount - first; - let asset = self.asset; + let asset = self.asset.clone(); sp_std::mem::forget(self); - (Imbalance::new(asset, first), Imbalance::new(asset, second)) + (Imbalance::new(asset.clone(), first), Imbalance::new(asset, second)) } pub fn merge(mut self, other: Self) -> Result { if self.asset == other.asset { @@ -136,7 +135,7 @@ impl< > { if self.asset == other.asset { let (a, b) = (self.amount, other.amount); - let asset = self.asset; + let asset = self.asset.clone(); sp_std::mem::forget((self, other)); if a == b { @@ -155,12 +154,12 @@ impl< } pub fn asset(&self) -> A { - self.asset + self.asset.clone() } } /// Imbalance implying that the total_issuance value is less than the sum of all account balances. -pub type DebtOf = Imbalance< +pub type Debt = Imbalance< >::AssetId, >::Balance, // This will generally be implemented by increasing the total_issuance value. @@ -170,7 +169,7 @@ pub type DebtOf = Imbalance< /// Imbalance implying that the total_issuance value is greater than the sum of all account /// balances. -pub type CreditOf = Imbalance< +pub type Credit = Imbalance< >::AssetId, >::Balance, // This will generally be implemented by decreasing the total_issuance value. diff --git a/frame/support/src/traits/tokens/fungibles/lifetime.rs b/frame/support/src/traits/tokens/fungibles/lifetime.rs new file mode 100644 index 000000000000..9e2c306f6f38 --- /dev/null +++ b/frame/support/src/traits/tokens/fungibles/lifetime.rs @@ -0,0 +1,84 @@ +// 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. + +//! Traits for creating and destroying assets. + +use sp_runtime::{DispatchError, DispatchResult}; + +use super::Inspect; + +/// Trait for providing the ability to create new fungible assets. +pub trait Create: Inspect { + /// Create a new fungible asset. + fn create( + id: Self::AssetId, + admin: AccountId, + is_sufficient: bool, + min_balance: Self::Balance, + ) -> DispatchResult; +} + +/// Trait for providing the ability to destroy existing fungible assets. +pub trait Destroy: Inspect { + /// Start the destruction an existing fungible asset. + /// * `id`: The `AssetId` to be destroyed. successfully. + /// * `maybe_check_owner`: An optional account id that can be used to authorize the destroy + /// command. If not provided, no authorization checks will be performed before destroying + /// asset. + fn start_destroy(id: Self::AssetId, maybe_check_owner: Option) -> DispatchResult; + + /// Destroy all accounts associated with a given asset. + /// `destroy_accounts` should only be called after `start_destroy` has been called, and the + /// asset is in a `Destroying` state + /// + /// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset. + /// * `max_items`: The maximum number of accounts to be destroyed for a given call of the + /// function. This value should be small enough to allow the operation fit into a logical + /// block. + /// + /// Response: + /// * u32: Total number of approvals which were actually destroyed + /// + /// Due to weight restrictions, this function may need to be called multiple + /// times to fully destroy all approvals. It will destroy `max_items` approvals at a + /// time. + fn destroy_accounts(id: Self::AssetId, max_items: u32) -> Result; + /// Destroy all approvals associated with a given asset up to the `max_items` + /// `destroy_approvals` should only be called after `start_destroy` has been called, and the + /// asset is in a `Destroying` state + /// + /// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset. + /// * `max_items`: The maximum number of accounts to be destroyed for a given call of the + /// function. This value should be small enough to allow the operation fit into a logical + /// block. + /// + /// Response: + /// * u32: Total number of approvals which were actually destroyed + /// + /// Due to weight restrictions, this function may need to be called multiple + /// times to fully destroy all approvals. It will destroy `max_items` approvals at a + /// time. + fn destroy_approvals(id: Self::AssetId, max_items: u32) -> Result; + + /// Complete destroying asset and unreserve currency. + /// `finish_destroy` should only be called after `start_destroy` has been called, and the + /// asset is in a `Destroying` state. All accounts or approvals should be destroyed before + /// hand. + /// + /// * `id`: The identifier of the asset to be destroyed. This must identify an existing asset. + fn finish_destroy(id: Self::AssetId) -> DispatchResult; +} diff --git a/frame/support/src/traits/tokens/fungibles/metadata.rs b/frame/support/src/traits/tokens/fungibles/metadata.rs index b736ab1489f5..ab310119e584 100644 --- a/frame/support/src/traits/tokens/fungibles/metadata.rs +++ b/frame/support/src/traits/tokens/fungibles/metadata.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,3 +39,8 @@ pub trait Mutate: Inspect { decimals: u8, ) -> DispatchResult; } + +pub trait MetadataDeposit { + // Returns the required deposit amount for a given metadata. + fn calc_metadata_deposit(name: &[u8], symbol: &[u8]) -> DepositBalance; +} diff --git a/frame/support/test/tests/decl_storage_ui/get_duplicate.rs b/frame/support/src/traits/tokens/fungibles/mod.rs similarity index 51% rename from frame/support/test/tests/decl_storage_ui/get_duplicate.rs rename to frame/support/src/traits/tokens/fungibles/mod.rs index 5e0588711053..697eff39ff74 100644 --- a/frame/support/test/tests/decl_storage_ui/get_duplicate.rs +++ b/frame/support/src/traits/tokens/fungibles/mod.rs @@ -15,17 +15,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub trait Config: frame_support_test::Config {} +//! The traits for sets of fungible tokens and any associated types. -frame_support::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} -} +pub mod approvals; +mod enumerable; +pub mod freeze; +pub mod hold; +mod imbalance; +mod lifetime; +pub mod metadata; +mod regular; +pub mod roles; -frame_support::decl_storage!{ - trait Store for Module as FinalKeysNone { - pub Value get(fn value) config(): u32; - pub Value2 get(fn value) config(): u32; - } -} - -fn main() {} +pub use enumerable::Inspect as InspectEnumerable; +pub use freeze::{Inspect as InspectFreeze, Mutate as MutateFreeze}; +pub use hold::{ + Balanced as BalancedHold, Inspect as InspectHold, Mutate as MutateHold, + Unbalanced as UnbalancedHold, +}; +pub use imbalance::{Credit, Debt, HandleImbalanceDrop, Imbalance}; +pub use lifetime::{Create, Destroy}; +pub use regular::{ + Balanced, DecreaseIssuance, Dust, IncreaseIssuance, Inspect, Mutate, Unbalanced, +}; diff --git a/frame/support/src/traits/tokens/fungibles/regular.rs b/frame/support/src/traits/tokens/fungibles/regular.rs new file mode 100644 index 000000000000..5570659e8115 --- /dev/null +++ b/frame/support/src/traits/tokens/fungibles/regular.rs @@ -0,0 +1,585 @@ +// 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. + +//! `Inspect` and `Mutate` traits for working with regular balances. + +use sp_std::marker::PhantomData; + +use crate::{ + dispatch::DispatchError, + ensure, + traits::{ + tokens::{ + misc::{ + Balance, DepositConsequence, + Fortitude::{self, Force, Polite}, + Precision::{self, BestEffort, Exact}, + Preservation::{self, Expendable}, + Provenance::{self, Extant}, + WithdrawConsequence, + }, + AssetId, + }, + SameOrOther, TryDrop, + }, +}; +use sp_arithmetic::traits::{CheckedAdd, CheckedSub, One}; +use sp_runtime::{traits::Saturating, ArithmeticError, TokenError}; + +use super::{Credit, Debt, HandleImbalanceDrop, Imbalance}; + +/// Trait for providing balance-inspection access to a set of named fungible assets. +pub trait Inspect: Sized { + /// Means of identifying one asset class from another. + type AssetId: AssetId; + + /// Scalar type for representing balance of an account. + type Balance: Balance; + + /// The total amount of issuance in the system. + fn total_issuance(asset: Self::AssetId) -> Self::Balance; + + /// The total amount of issuance in the system excluding those which are controlled by the + /// system. + fn active_issuance(asset: Self::AssetId) -> Self::Balance { + Self::total_issuance(asset) + } + + /// The minimum balance any single account may have. + fn minimum_balance(asset: Self::AssetId) -> Self::Balance; + + /// Get the total amount of funds whose ultimate bneficial ownership can be determined as `who`. + /// + /// This may include funds which are wholly inaccessible to `who`, either temporarily or even + /// indefinitely. + /// + /// For the amount of the balance which is currently free to be removed from the account without + /// error, use `reducible_balance`. + /// + /// For the amount of the balance which may eventually be free to be removed from the account, + /// use `balance()`. + fn total_balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance; + + /// Get the balance of `who` which does not include funds which are exclusively allocated to + /// subsystems of the chain ("on hold" or "reserved"). + /// + /// In general this isn't especially useful outside of tests, and for practical purposes, you'll + /// want to use `reducible_balance()`. + fn balance(asset: Self::AssetId, who: &AccountId) -> Self::Balance; + + /// Get the maximum amount that `who` can withdraw/transfer successfully based on whether the + /// account should be kept alive (`preservation`) or whether we are willing to force the + /// transfer and potentially go below user-level restrictions on the minimum amount of the + /// account. + /// + /// Always less than `free_balance()`. + fn reducible_balance( + asset: Self::AssetId, + who: &AccountId, + preservation: Preservation, + force: Fortitude, + ) -> Self::Balance; + + /// Returns `true` if the `asset` balance of `who` may be increased by `amount`. + /// + /// - `asset`: The asset that should be deposited. + /// - `who`: The account of which the balance should be increased by `amount`. + /// - `amount`: How much should the balance be increased? + /// - `mint`: Will `amount` be minted to deposit it into `account`? + fn can_deposit( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + provenance: Provenance, + ) -> DepositConsequence; + + /// Returns `Failed` if the `asset` balance of `who` may not be decreased by `amount`, otherwise + /// the consequence. + fn can_withdraw( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> WithdrawConsequence; + + /// Returns `true` if an `asset` exists. + fn asset_exists(asset: Self::AssetId) -> bool; +} + +/// Special dust type which can be type-safely converted into a `Credit`. +#[must_use] +pub struct Dust>(pub T::AssetId, pub T::Balance); + +impl> Dust { + /// Convert `Dust` into an instance of `Credit`. + pub fn into_credit(self) -> Credit { + Credit::::new(self.0, self.1) + } +} + +/// A fungible token class where the balance can be set arbitrarily. +/// +/// **WARNING** +/// Do not use this directly unless you want trouble, since it allows you to alter account balances +/// without keeping the issuance up to date. It has no safeguards against accidentally creating +/// token imbalances in your system leading to accidental imflation or deflation. It's really just +/// for the underlying datatype to implement so the user gets the much safer `Balanced` trait to +/// use. +pub trait Unbalanced: Inspect { + /// Create some dust and handle it with `Self::handle_dust`. This is an unbalanced operation + /// and it must only be used when an account is modified in a raw fashion, outside of the entire + /// fungibles API. The `amount` is capped at `Self::minimum_balance() - 1`. + /// + /// This should not be reimplemented. + fn handle_raw_dust(asset: Self::AssetId, amount: Self::Balance) { + Self::handle_dust(Dust( + asset.clone(), + amount.min(Self::minimum_balance(asset).saturating_sub(One::one())), + )) + } + + /// Do something with the dust which has been destroyed from the system. `Dust` can be converted + /// into a `Credit` with the `Balanced` trait impl. + fn handle_dust(dust: Dust); + + /// Forcefully set the balance of `who` to `amount`. + /// + /// If this call executes successfully, you can `assert_eq!(Self::balance(), amount);`. + /// + /// For implementations which include one or more balances on hold, then these are *not* + /// included in the `amount`. + /// + /// This function does its best to force the balance change through, but will not break system + /// invariants such as any Existential Deposits needed or overflows/underflows. + /// If this cannot be done for some reason (e.g. because the account cannot be created, deleted + /// or would overflow) then an `Err` is returned. + fn write_balance( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> Result, DispatchError>; + + /// Set the total issuance to `amount`. + fn set_total_issuance(asset: Self::AssetId, amount: Self::Balance); + + /// Reduce the balance of `who` by `amount`. + /// + /// If `precision` is `Exact` and it cannot be reduced by that amount for + /// some reason, return `Err` and don't reduce it at all. If `precision` is `BestEffort`, then + /// reduce the balance of `who` by the most that is possible, up to `amount`. + /// + /// In either case, if `Ok` is returned then the inner is the amount by which is was reduced. + /// Minimum balance will be respected and thus the returned amount may be up to + /// `Self::minimum_balance() - 1` greater than `amount` in the case that the reduction caused + /// the account to be deleted. + fn decrease_balance( + asset: Self::AssetId, + who: &AccountId, + mut amount: Self::Balance, + precision: Precision, + preservation: Preservation, + force: Fortitude, + ) -> Result { + let old_balance = Self::balance(asset.clone(), who); + let free = Self::reducible_balance(asset.clone(), who, preservation, force); + if let BestEffort = precision { + amount = amount.min(free); + } + let new_balance = old_balance.checked_sub(&amount).ok_or(TokenError::FundsUnavailable)?; + if let Some(dust) = Self::write_balance(asset.clone(), who, new_balance)? { + Self::handle_dust(Dust(asset, dust)); + } + Ok(old_balance.saturating_sub(new_balance)) + } + + /// Increase the balance of `who` by `amount`. + /// + /// If it cannot be increased by that amount for some reason, return `Err` and don't increase + /// it at all. If Ok, return the imbalance. + /// Minimum balance will be respected and an error will be returned if + /// `amount < Self::minimum_balance()` when the account of `who` is zero. + fn increase_balance( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + ) -> Result { + let old_balance = Self::balance(asset.clone(), who); + let new_balance = if let BestEffort = precision { + old_balance.saturating_add(amount) + } else { + old_balance.checked_add(&amount).ok_or(ArithmeticError::Overflow)? + }; + if new_balance < Self::minimum_balance(asset.clone()) { + // Attempt to increase from 0 to below minimum -> stays at zero. + if let BestEffort = precision { + Ok(Self::Balance::default()) + } else { + Err(TokenError::BelowMinimum.into()) + } + } else { + if new_balance == old_balance { + Ok(Self::Balance::default()) + } else { + if let Some(dust) = Self::write_balance(asset.clone(), who, new_balance)? { + Self::handle_dust(Dust(asset, dust)); + } + Ok(new_balance.saturating_sub(old_balance)) + } + } + } + + /// Reduce the active issuance by some amount. + fn deactivate(_asset: Self::AssetId, _: Self::Balance) {} + + /// Increase the active issuance by some amount, up to the outstanding amount reduced. + fn reactivate(_asset: Self::AssetId, _: Self::Balance) {} +} + +/// Trait for providing a basic fungible asset. +pub trait Mutate: Inspect + Unbalanced { + /// Increase the balance of `who` by exactly `amount`, minting new tokens. If that isn't + /// possible then an `Err` is returned and nothing is changed. + fn mint_into( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> Result { + Self::total_issuance(asset.clone()) + .checked_add(&amount) + .ok_or(ArithmeticError::Overflow)?; + let actual = Self::increase_balance(asset.clone(), who, amount, Exact)?; + Self::set_total_issuance( + asset.clone(), + Self::total_issuance(asset.clone()).saturating_add(actual), + ); + Self::done_mint_into(asset, who, amount); + Ok(actual) + } + + /// Decrease the balance of `who` by at least `amount`, possibly slightly more in the case of + /// minimum-balance requirements, burning the tokens. If that isn't possible then an `Err` is + /// returned and nothing is changed. If successful, the amount of tokens reduced is returned. + fn burn_from( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + precision: Precision, + force: Fortitude, + ) -> Result { + let actual = Self::reducible_balance(asset.clone(), who, Expendable, force).min(amount); + ensure!(actual == amount || precision == BestEffort, TokenError::FundsUnavailable); + Self::total_issuance(asset.clone()) + .checked_sub(&actual) + .ok_or(ArithmeticError::Overflow)?; + let actual = + Self::decrease_balance(asset.clone(), who, actual, BestEffort, Expendable, force)?; + Self::set_total_issuance( + asset.clone(), + Self::total_issuance(asset.clone()).saturating_sub(actual), + ); + Self::done_burn_from(asset, who, actual); + Ok(actual) + } + + /// Attempt to decrease the `asset` balance of `who` by `amount`. + /// + /// Equivalent to `burn_from`, except with an expectation that within the bounds of some + /// universal issuance, the total assets `suspend`ed and `resume`d will be equivalent. The + /// implementation may be configured such that the total assets suspended may never be less than + /// the total assets resumed (which is the invariant for an issuing system), or the reverse + /// (which the invariant in a non-issuing system). + /// + /// Because of this expectation, any metadata associated with the asset is expected to survive + /// the suspect-resume cycle. + fn shelve( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> Result { + let actual = Self::reducible_balance(asset.clone(), who, Expendable, Polite).min(amount); + ensure!(actual == amount, TokenError::FundsUnavailable); + Self::total_issuance(asset.clone()) + .checked_sub(&actual) + .ok_or(ArithmeticError::Overflow)?; + let actual = + Self::decrease_balance(asset.clone(), who, actual, BestEffort, Expendable, Polite)?; + Self::set_total_issuance( + asset.clone(), + Self::total_issuance(asset.clone()).saturating_sub(actual), + ); + Self::done_shelve(asset, who, actual); + Ok(actual) + } + + /// Attempt to increase the `asset` balance of `who` by `amount`. + /// + /// Equivalent to `mint_into`, except with an expectation that within the bounds of some + /// universal issuance, the total assets `suspend`ed and `resume`d will be equivalent. The + /// implementation may be configured such that the total assets suspended may never be less than + /// the total assets resumed (which is the invariant for an issuing system), or the reverse + /// (which the invariant in a non-issuing system). + /// + /// Because of this expectation, any metadata associated with the asset is expected to survive + /// the suspect-resume cycle. + fn restore( + asset: Self::AssetId, + who: &AccountId, + amount: Self::Balance, + ) -> Result { + Self::total_issuance(asset.clone()) + .checked_add(&amount) + .ok_or(ArithmeticError::Overflow)?; + let actual = Self::increase_balance(asset.clone(), who, amount, Exact)?; + Self::set_total_issuance( + asset.clone(), + Self::total_issuance(asset.clone()).saturating_add(actual), + ); + Self::done_restore(asset, who, amount); + Ok(actual) + } + + /// Transfer funds from one account into another. + fn transfer( + asset: Self::AssetId, + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + preservation: Preservation, + ) -> Result { + let _extra = Self::can_withdraw(asset.clone(), source, amount) + .into_result(preservation != Expendable)?; + Self::can_deposit(asset.clone(), dest, amount, Extant).into_result()?; + Self::decrease_balance(asset.clone(), source, amount, BestEffort, preservation, Polite)?; + // This should never fail as we checked `can_deposit` earlier. But we do a best-effort + // anyway. + let _ = Self::increase_balance(asset.clone(), dest, amount, BestEffort); + Self::done_transfer(asset, source, dest, amount); + Ok(amount) + } + + /// Simple infallible function to force an account to have a particular balance, good for use + /// in tests and benchmarks but not recommended for production code owing to the lack of + /// error reporting. + /// + /// Returns the new balance. + fn set_balance(asset: Self::AssetId, who: &AccountId, amount: Self::Balance) -> Self::Balance { + let b = Self::balance(asset.clone(), who); + if b > amount { + Self::burn_from(asset, who, b - amount, BestEffort, Force).map(|d| b.saturating_sub(d)) + } else { + Self::mint_into(asset, who, amount - b).map(|d| b.saturating_add(d)) + } + .unwrap_or(b) + } + fn done_mint_into(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {} + fn done_burn_from(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {} + fn done_shelve(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {} + fn done_restore(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {} + fn done_transfer( + _asset: Self::AssetId, + _source: &AccountId, + _dest: &AccountId, + _amount: Self::Balance, + ) { + } +} + +/// Simple handler for an imbalance drop which increases the total issuance of the system by the +/// imbalance amount. Used for leftover debt. +pub struct IncreaseIssuance(PhantomData<(AccountId, U)>); +impl> HandleImbalanceDrop + for IncreaseIssuance +{ + fn handle(asset: U::AssetId, amount: U::Balance) { + U::set_total_issuance(asset.clone(), U::total_issuance(asset).saturating_add(amount)) + } +} + +/// Simple handler for an imbalance drop which decreases the total issuance of the system by the +/// imbalance amount. Used for leftover credit. +pub struct DecreaseIssuance(PhantomData<(AccountId, U)>); +impl> HandleImbalanceDrop + for DecreaseIssuance +{ + fn handle(asset: U::AssetId, amount: U::Balance) { + U::set_total_issuance(asset.clone(), U::total_issuance(asset).saturating_sub(amount)) + } +} + +/// A fungible token class where any creation and deletion of tokens is semi-explicit and where the +/// total supply is maintained automatically. +/// +/// This is auto-implemented when a token class has `Unbalanced` implemented. +pub trait Balanced: Inspect + Unbalanced { + /// The type for managing what happens when an instance of `Debt` is dropped without being used. + type OnDropDebt: HandleImbalanceDrop; + /// The type for managing what happens when an instance of `Credit` is dropped without being + /// used. + type OnDropCredit: HandleImbalanceDrop; + + /// Reduce the total issuance by `amount` and return the according imbalance. The imbalance will + /// typically be used to reduce an account by the same amount with e.g. `settle`. + /// + /// This is infallible, but doesn't guarantee that the entire `amount` is burnt, for example + /// in the case of underflow. + fn rescind(asset: Self::AssetId, amount: Self::Balance) -> Debt { + let old = Self::total_issuance(asset.clone()); + let new = old.saturating_sub(amount); + Self::set_total_issuance(asset.clone(), new); + let delta = old - new; + Self::done_rescind(asset.clone(), delta); + Imbalance::::new( + asset, delta, + ) + } + + /// Increase the total issuance by `amount` and return the according imbalance. The imbalance + /// will typically be used to increase an account by the same amount with e.g. + /// `resolve_into_existing` or `resolve_creating`. + /// + /// This is infallible, but doesn't guarantee that the entire `amount` is issued, for example + /// in the case of overflow. + fn issue(asset: Self::AssetId, amount: Self::Balance) -> Credit { + let old = Self::total_issuance(asset.clone()); + let new = old.saturating_add(amount); + Self::set_total_issuance(asset.clone(), new); + let delta = new - old; + Self::done_issue(asset.clone(), delta); + Imbalance::::new( + asset, delta, + ) + } + + /// Produce a pair of imbalances that cancel each other out exactly. + /// + /// This is just the same as burning and issuing the same amount and has no effect on the + /// total issuance. + fn pair( + asset: Self::AssetId, + amount: Self::Balance, + ) -> (Debt, Credit) { + (Self::rescind(asset.clone(), amount), Self::issue(asset, amount)) + } + + /// Mints `value` into the account of `who`, creating it as needed. + /// + /// If `precision` is `BestEffort` and `value` in full could not be minted (e.g. due to + /// overflow), then the maximum is minted, up to `value`. If `precision` is `Exact`, then + /// exactly `value` must be minted into the account of `who` or the operation will fail with an + /// `Err` and nothing will change. + /// + /// If the operation is successful, this will return `Ok` with a `Debt` of the total value + /// added to the account. + fn deposit( + asset: Self::AssetId, + who: &AccountId, + value: Self::Balance, + precision: Precision, + ) -> Result, DispatchError> { + let increase = Self::increase_balance(asset.clone(), who, value, precision)?; + Self::done_deposit(asset.clone(), who, increase); + Ok(Imbalance::::new( + asset, increase, + )) + } + + /// Removes `value` balance from `who` account if possible. + /// + /// If `precision` is `BestEffort` and `value` in full could not be removed (e.g. due to + /// underflow), then the maximum is removed, up to `value`. If `precision` is `Exact`, then + /// exactly `value` must be removed from the account of `who` or the operation will fail with an + /// `Err` and nothing will change. + /// + /// If the removal is needed but not possible, then it returns `Err` and nothing is changed. + /// If the account needed to be deleted, then slightly more than `value` may be removed from the + /// account owning since up to (but not including) minimum balance may also need to be removed. + /// + /// If the operation is successful, this will return `Ok` with a `Credit` of the total value + /// removed from the account. + fn withdraw( + asset: Self::AssetId, + who: &AccountId, + value: Self::Balance, + precision: Precision, + preservation: Preservation, + force: Fortitude, + ) -> Result, DispatchError> { + let decrease = + Self::decrease_balance(asset.clone(), who, value, precision, preservation, force)?; + Self::done_withdraw(asset.clone(), who, decrease); + Ok(Imbalance::::new( + asset, decrease, + )) + } + + /// The balance of `who` is increased in order to counter `credit`. If the whole of `credit` + /// cannot be countered, then nothing is changed and the original `credit` is returned in an + /// `Err`. + /// + /// Please note: If `credit.peek()` is less than `Self::minimum_balance()`, then `who` must + /// already exist for this to succeed. + fn resolve( + who: &AccountId, + credit: Credit, + ) -> Result<(), Credit> { + let v = credit.peek(); + let debt = match Self::deposit(credit.asset(), who, v, Exact) { + Err(_) => return Err(credit), + Ok(d) => d, + }; + if let Ok(result) = credit.offset(debt) { + let result = result.try_drop(); + debug_assert!(result.is_ok(), "ok deposit return must be equal to credit value; qed"); + } else { + debug_assert!(false, "debt.asset is credit.asset; qed"); + } + Ok(()) + } + + /// The balance of `who` is decreased in order to counter `debt`. If the whole of `debt` + /// cannot be countered, then nothing is changed and the original `debt` is returned in an + /// `Err`. + fn settle( + who: &AccountId, + debt: Debt, + preservation: Preservation, + ) -> Result, Debt> { + let amount = debt.peek(); + let asset = debt.asset(); + let credit = match Self::withdraw(asset.clone(), who, amount, Exact, preservation, Polite) { + Err(_) => return Err(debt), + Ok(d) => d, + }; + match credit.offset(debt) { + Ok(SameOrOther::None) => Ok(Credit::::zero(asset)), + Ok(SameOrOther::Same(dust)) => Ok(dust), + Ok(SameOrOther::Other(rest)) => { + debug_assert!(false, "ok withdraw return must be at least debt value; qed"); + Err(rest) + }, + Err(_) => { + debug_assert!(false, "debt.asset is credit.asset; qed"); + Ok(Credit::::zero(asset)) + }, + } + } + + fn done_rescind(_asset: Self::AssetId, _amount: Self::Balance) {} + fn done_issue(_asset: Self::AssetId, _amount: Self::Balance) {} + fn done_deposit(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {} + fn done_withdraw(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {} +} diff --git a/frame/support/src/traits/tokens/fungibles/roles.rs b/frame/support/src/traits/tokens/fungibles/roles.rs index 18fd1cc80121..5cd1228afbce 100644 --- a/frame/support/src/traits/tokens/fungibles/roles.rs +++ b/frame/support/src/traits/tokens/fungibles/roles.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/tokens/imbalance.rs b/frame/support/src/traits/tokens/imbalance.rs index d721beb41494..403321725042 100644 --- a/frame/support/src/traits/tokens/imbalance.rs +++ b/frame/support/src/traits/tokens/imbalance.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/tokens/imbalance/on_unbalanced.rs b/frame/support/src/traits/tokens/imbalance/on_unbalanced.rs index 0125254cefc8..27bfe46e181e 100644 --- a/frame/support/src/traits/tokens/imbalance/on_unbalanced.rs +++ b/frame/support/src/traits/tokens/imbalance/on_unbalanced.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/tokens/imbalance/signed_imbalance.rs b/frame/support/src/traits/tokens/imbalance/signed_imbalance.rs index f969a4363405..03e821b161b6 100644 --- a/frame/support/src/traits/tokens/imbalance/signed_imbalance.rs +++ b/frame/support/src/traits/tokens/imbalance/signed_imbalance.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/tokens/imbalance/split_two_ways.rs b/frame/support/src/traits/tokens/imbalance/split_two_ways.rs index b963895af0de..c1afac35fc93 100644 --- a/frame/support/src/traits/tokens/imbalance/split_two_ways.rs +++ b/frame/support/src/traits/tokens/imbalance/split_two_ways.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/tokens/misc.rs b/frame/support/src/traits/tokens/misc.rs index f9876ef477b8..baf3fd5f3546 100644 --- a/frame/support/src/traits/tokens/misc.rs +++ b/frame/support/src/traits/tokens/misc.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,15 +20,68 @@ use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; use sp_arithmetic::traits::{AtLeast32BitUnsigned, Zero}; use sp_core::RuntimeDebug; -use sp_runtime::{ArithmeticError, DispatchError, TokenError}; +use sp_runtime::{traits::Convert, ArithmeticError, DispatchError, TokenError}; use sp_std::fmt::Debug; +/// The origin of funds to be used for a deposit operation. +#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)] +pub enum Provenance { + /// The funds will be minted into the system, increasing total issuance (and potentially + /// causing an overflow there). + Minted, + /// The funds already exist in the system, therefore will not affect total issuance. + Extant, +} + +/// The mode under which usage of funds may be restricted. +#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)] +pub enum Restriction { + /// Funds are under the normal conditions. + Free, + /// Funds are on hold. + OnHold, +} + +/// The mode by which we describe whether an operation should keep an account alive. +#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)] +pub enum Preservation { + /// We don't care if the account gets killed by this operation. + Expendable, + /// The account may not be killed, but we don't care if the balance gets dusted. + Protect, + /// The account may not be killed and our provider reference must remain (in the context of + /// tokens, this means that the account may not be dusted). + Preserve, +} + +/// The privilege with which a withdraw operation is conducted. +#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)] +pub enum Fortitude { + /// The operation should execute with regular privilege. + Polite, + /// The operation should be forced to succeed if possible. This is usually employed for system- + /// level security-critical events such as slashing. + Force, +} + +/// The precision required of an operation generally involving some aspect of quantitative fund +/// withdrawal or transfer. +#[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)] +pub enum Precision { + /// The operation should must either proceed either exactly according to the amounts involved + /// or not at all. + Exact, + /// The operation may be considered successful even if less than the specified amounts are + /// available to be used. In this case a best effort will be made. + BestEffort, +} + /// One of a number of consequences of withdrawing a fungible from an account. #[derive(Copy, Clone, RuntimeDebug, Eq, PartialEq)] pub enum WithdrawConsequence { /// Withdraw could not happen since the amount to be withdrawn is less than the total funds in /// the account. - NoFunds, + BalanceLow, /// The withdraw would mean the account dying when it needs to exist (usually because it is a /// provider and there are consumer references on it). WouldDie, @@ -53,15 +106,16 @@ pub enum WithdrawConsequence { impl WithdrawConsequence { /// Convert the type into a `Result` with `DispatchError` as the error or the additional /// `Balance` by which the account will be reduced. - pub fn into_result(self) -> Result { + pub fn into_result(self, keep_nonzero: bool) -> Result { use WithdrawConsequence::*; match self { - NoFunds => Err(TokenError::NoFunds.into()), - WouldDie => Err(TokenError::WouldDie.into()), + BalanceLow => Err(TokenError::FundsUnavailable.into()), + WouldDie => Err(TokenError::OnlyProvider.into()), UnknownAsset => Err(TokenError::UnknownAsset.into()), Underflow => Err(ArithmeticError::Underflow.into()), Overflow => Err(ArithmeticError::Overflow.into()), Frozen => Err(TokenError::Frozen.into()), + ReducedToZero(_) if keep_nonzero => Err(TokenError::NotExpendable.into()), ReducedToZero(result) => Ok(result), Success => Ok(Zero::zero()), } @@ -87,6 +141,8 @@ pub enum DepositConsequence { Overflow, /// Account continued in existence. Success, + /// Account cannot receive the assets. + Blocked, } impl DepositConsequence { @@ -98,6 +154,7 @@ impl DepositConsequence { CannotCreate => TokenError::CannotCreate.into(), UnknownAsset => TokenError::UnknownAsset.into(), Overflow => ArithmeticError::Overflow.into(), + Blocked => TokenError::Blocked.into(), Success => return Ok(()), }) } @@ -126,21 +183,6 @@ pub enum BalanceStatus { Reserved, } -/// Attribute namespaces for non-fungible tokens. -#[derive( - Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen, -)] -pub enum AttributeNamespace { - /// An attribute was set by the pallet. - Pallet, - /// An attribute was set by collection's owner. - CollectionOwner, - /// An attribute was set by item's owner. - ItemOwner, - /// An attribute was set by pre-approved account. - Account(AccountId), -} - bitflags::bitflags! { /// Reasons for moving funds out of an account. #[derive(Encode, Decode, MaxEncodedLen)] @@ -179,10 +221,10 @@ impl WithdrawReasons { /// Simple amalgamation trait to collect together properties for an AssetId under one roof. pub trait AssetId: - FullCodec + Copy + Eq + PartialEq + Debug + scale_info::TypeInfo + MaxEncodedLen + FullCodec + Clone + Eq + PartialEq + Debug + scale_info::TypeInfo + MaxEncodedLen { } -impl AssetId +impl AssetId for T { } @@ -205,13 +247,23 @@ impl< } /// Converts a balance value into an asset balance. -pub trait BalanceConversion { +pub trait ConversionToAssetBalance { + type Error; + fn to_asset_balance(balance: InBalance, asset_id: AssetId) + -> Result; +} + +/// Converts an asset balance value into balance. +pub trait ConversionFromAssetBalance { type Error; - fn to_asset_balance(balance: InBalance, asset_id: AssetId) -> Result; + fn from_asset_balance( + balance: AssetBalance, + asset_id: AssetId, + ) -> Result; } -/// Trait to handle asset locking mechanism to ensure interactions with the asset can be implemented -/// downstream to extend logic of Uniques current functionality. +/// Trait to handle NFT locking mechanism to ensure interactions with the asset can be implemented +/// downstream to extend logic of Uniques/Nfts current functionality. pub trait Locker { /// Check if the asset should be locked and prevent interactions with the asset from executing. fn is_locked(collection: CollectionId, item: ItemId) -> bool; @@ -225,3 +277,18 @@ impl Locker for () { false } } + +/// Retrieve the salary for a member of a particular rank. +pub trait GetSalary { + /// Retrieve the salary for a given rank. The account ID is also supplied in case this changes + /// things. + fn get_salary(rank: Rank, who: &AccountId) -> Balance; +} + +/// Adapter for a rank-to-salary `Convert` implementation into a `GetSalary` implementation. +pub struct ConvertRank(sp_std::marker::PhantomData); +impl> GetSalary for ConvertRank { + fn get_salary(rank: R, _: &A) -> B { + C::convert(rank) + } +} diff --git a/frame/support/src/traits/tokens/nonfungible.rs b/frame/support/src/traits/tokens/nonfungible.rs index 46ca57313112..e3fc84f1d57b 100644 --- a/frame/support/src/traits/tokens/nonfungible.rs +++ b/frame/support/src/traits/tokens/nonfungible.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/tokens/nonfungible_v2.rs b/frame/support/src/traits/tokens/nonfungible_v2.rs index e1899020bb33..c4463e0070f9 100644 --- a/frame/support/src/traits/tokens/nonfungible_v2.rs +++ b/frame/support/src/traits/tokens/nonfungible_v2.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,8 +26,8 @@ use super::nonfungibles_v2 as nonfungibles; use crate::{ - dispatch::DispatchResult, - traits::{tokens::misc::AttributeNamespace, Get}, + dispatch::{DispatchResult, Parameter}, + traits::Get, }; use codec::{Decode, Encode}; use sp_runtime::TokenError; @@ -36,7 +36,7 @@ use sp_std::prelude::*; /// Trait for providing an interface to a read-only NFT-like item. pub trait Inspect { /// Type for identifying an item. - type ItemId; + type ItemId: Parameter; /// Returns the owner of `item`, or `None` if the item doesn't exist or has no /// owner. @@ -45,23 +45,53 @@ pub trait Inspect { /// Returns the attribute value of `item` corresponding to `key`. /// /// By default this is `None`; no attributes are defined. - fn attribute( + fn attribute(_item: &Self::ItemId, _key: &[u8]) -> Option> { + None + } + + /// Returns the custom attribute value of `item` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn custom_attribute( + _account: &AccountId, _item: &Self::ItemId, - _namespace: &AttributeNamespace, _key: &[u8], ) -> Option> { None } + /// Returns the system attribute value of `item` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn system_attribute(_item: &Self::ItemId, _key: &[u8]) -> Option> { + None + } + /// Returns the strongly-typed attribute value of `item` corresponding to `key`. /// /// By default this just attempts to use `attribute`. - fn typed_attribute( + fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { + key.using_encoded(|d| Self::attribute(item, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// Returns the strongly-typed custom attribute value of `item` corresponding to `key`. + /// + /// By default this just attempts to use `custom_attribute`. + fn typed_custom_attribute( + account: &AccountId, item: &Self::ItemId, - namespace: &AttributeNamespace, key: &K, ) -> Option { - key.using_encoded(|d| Self::attribute(item, namespace, d)) + key.using_encoded(|d| Self::custom_attribute(account, item, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// Returns the strongly-typed system attribute value of `item` corresponding to `key`. + /// + /// By default this just attempts to use `system_attribute`. + fn typed_system_attribute(item: &Self::ItemId, key: &K) -> Option { + key.using_encoded(|d| Self::system_attribute(item, d)) .and_then(|v| V::decode(&mut &v[..]).ok()) } @@ -143,10 +173,18 @@ pub trait Mutate: Inspect { } } -/// Trait for transferring a non-fungible item. +/// Trait for transferring and controlling the transfer of non-fungible sets of items. pub trait Transfer: Inspect { /// Transfer `item` into `destination` account. fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult; + /// Disable the `item` of `collection` transfer. + /// + /// By default, this is not a supported operation. + fn disable_transfer(item: &Self::ItemId) -> DispatchResult; + /// Re-enable the `item` of `collection` transfer. + /// + /// By default, this is not a supported operation. + fn enable_transfer(item: &Self::ItemId) -> DispatchResult; } /// Convert a `nonfungibles` trait implementation into a `nonfungible` trait implementation by @@ -167,19 +205,32 @@ impl< fn owner(item: &Self::ItemId) -> Option { >::owner(&A::get(), item) } - fn attribute( - item: &Self::ItemId, - namespace: &AttributeNamespace, - key: &[u8], - ) -> Option> { - >::attribute(&A::get(), item, namespace, key) + fn attribute(item: &Self::ItemId, key: &[u8]) -> Option> { + >::attribute(&A::get(), item, key) + } + fn custom_attribute(account: &AccountId, item: &Self::ItemId, key: &[u8]) -> Option> { + >::custom_attribute(account, &A::get(), item, key) + } + fn system_attribute(item: &Self::ItemId, key: &[u8]) -> Option> { + >::system_attribute(&A::get(), item, key) } - fn typed_attribute( + fn typed_attribute(item: &Self::ItemId, key: &K) -> Option { + >::typed_attribute(&A::get(), item, key) + } + fn typed_custom_attribute( + account: &AccountId, item: &Self::ItemId, - namespace: &AttributeNamespace, key: &K, ) -> Option { - >::typed_attribute(&A::get(), item, namespace, key) + >::typed_custom_attribute( + account, + &A::get(), + item, + key, + ) + } + fn typed_system_attribute(item: &Self::ItemId, key: &K) -> Option { + >::typed_system_attribute(&A::get(), item, key) } fn can_transfer(item: &Self::ItemId) -> bool { >::can_transfer(&A::get(), item) @@ -269,4 +320,10 @@ impl< fn transfer(item: &Self::ItemId, destination: &AccountId) -> DispatchResult { >::transfer(&A::get(), item, destination) } + fn disable_transfer(item: &Self::ItemId) -> DispatchResult { + >::disable_transfer(&A::get(), item) + } + fn enable_transfer(item: &Self::ItemId) -> DispatchResult { + >::enable_transfer(&A::get(), item) + } } diff --git a/frame/support/src/traits/tokens/nonfungibles.rs b/frame/support/src/traits/tokens/nonfungibles.rs index ac007b5a67f1..e9538d14f547 100644 --- a/frame/support/src/traits/tokens/nonfungibles.rs +++ b/frame/support/src/traits/tokens/nonfungibles.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/tokens/nonfungibles_v2.rs b/frame/support/src/traits/tokens/nonfungibles_v2.rs index 17c5d887defc..6f428c297e73 100644 --- a/frame/support/src/traits/tokens/nonfungibles_v2.rs +++ b/frame/support/src/traits/tokens/nonfungibles_v2.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,10 +27,7 @@ //! Implementations of these traits may be converted to implementations of corresponding //! `nonfungible` traits by using the `nonfungible::ItemOf` type adapter. -use crate::{ - dispatch::{DispatchError, DispatchResult}, - traits::tokens::misc::AttributeNamespace, -}; +use crate::dispatch::{DispatchError, DispatchResult, Parameter}; use codec::{Decode, Encode}; use sp_runtime::TokenError; use sp_std::prelude::*; @@ -38,11 +35,11 @@ use sp_std::prelude::*; /// Trait for providing an interface to many read-only NFT-like sets of items. pub trait Inspect { /// Type for identifying an item. - type ItemId; + type ItemId: Parameter; /// Type for identifying a collection (an identifier for an independent collection of /// items). - type CollectionId; + type CollectionId: Parameter; /// Returns the owner of `item` of `collection`, or `None` if the item doesn't exist /// (or somehow has no owner). @@ -61,7 +58,29 @@ pub trait Inspect { fn attribute( _collection: &Self::CollectionId, _item: &Self::ItemId, - _namespace: &AttributeNamespace, + _key: &[u8], + ) -> Option> { + None + } + + /// Returns the custom attribute value of `item` of `collection` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn custom_attribute( + _account: &AccountId, + _collection: &Self::CollectionId, + _item: &Self::ItemId, + _key: &[u8], + ) -> Option> { + None + } + + /// Returns the system attribute value of `item` of `collection` corresponding to `key`. + /// + /// By default this is `None`; no attributes are defined. + fn system_attribute( + _collection: &Self::CollectionId, + _item: &Self::ItemId, _key: &[u8], ) -> Option> { None @@ -74,10 +93,36 @@ pub trait Inspect { fn typed_attribute( collection: &Self::CollectionId, item: &Self::ItemId, - namespace: &AttributeNamespace, key: &K, ) -> Option { - key.using_encoded(|d| Self::attribute(collection, item, namespace, d)) + key.using_encoded(|d| Self::attribute(collection, item, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// Returns the strongly-typed custom attribute value of `item` of `collection` corresponding to + /// `key`. + /// + /// By default this just attempts to use `custom_attribute`. + fn typed_custom_attribute( + account: &AccountId, + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &K, + ) -> Option { + key.using_encoded(|d| Self::custom_attribute(account, collection, item, d)) + .and_then(|v| V::decode(&mut &v[..]).ok()) + } + + /// Returns the strongly-typed system attribute value of `item` of `collection` corresponding to + /// `key`. + /// + /// By default this just attempts to use `system_attribute`. + fn typed_system_attribute( + collection: &Self::CollectionId, + item: &Self::ItemId, + key: &K, + ) -> Option { + key.using_encoded(|d| Self::system_attribute(collection, item, d)) .and_then(|v| V::decode(&mut &v[..]).ok()) } @@ -135,6 +180,16 @@ pub trait InspectEnumerable: Inspect { ) -> Self::OwnedInCollectionIterator; } +/// Trait for providing an interface to check the account's role within the collection. +pub trait InspectRole: Inspect { + /// Returns `true` if `who` is the issuer of the `collection`. + fn is_issuer(collection: &Self::CollectionId, who: &AccountId) -> bool; + /// Returns `true` if `who` is the admin of the `collection`. + fn is_admin(collection: &Self::CollectionId, who: &AccountId) -> bool; + /// Returns `true` if `who` is the freezer of the `collection`. + fn is_freezer(collection: &Self::CollectionId, who: &AccountId) -> bool; +} + /// Trait for providing the ability to create collections of nonfungible items. pub trait Create: Inspect { /// Create a `collection` of nonfungible items to be owned by `who` and managed by `admin`. @@ -148,7 +203,7 @@ pub trait Create: Inspect { /// Trait for providing the ability to destroy collections of nonfungible items. pub trait Destroy: Inspect { /// The witness data needed to destroy an item. - type DestroyWitness; + type DestroyWitness: Parameter; /// Provide the appropriate witness data needed to destroy an item. fn get_destroy_witness(collection: &Self::CollectionId) -> Option; @@ -293,4 +348,18 @@ pub trait Transfer: Inspect { item: &Self::ItemId, destination: &AccountId, ) -> DispatchResult; + + /// Disable the `item` of `collection` transfer. + /// + /// By default, this is not a supported operation. + fn disable_transfer(_collection: &Self::CollectionId, _item: &Self::ItemId) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } + + /// Re-enable the `item` of `collection` transfer. + /// + /// By default, this is not a supported operation. + fn enable_transfer(_collection: &Self::CollectionId, _item: &Self::ItemId) -> DispatchResult { + Err(TokenError::Unsupported.into()) + } } diff --git a/frame/support/src/traits/tokens/pay.rs b/frame/support/src/traits/tokens/pay.rs new file mode 100644 index 000000000000..78f8e7b87348 --- /dev/null +++ b/frame/support/src/traits/tokens/pay.rs @@ -0,0 +1,109 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The Pay trait and associated types. + +use codec::{Decode, Encode, FullCodec, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_core::{RuntimeDebug, TypedGet}; +use sp_runtime::DispatchError; +use sp_std::fmt::Debug; + +use super::{fungible, Balance, Preservation::Expendable}; + +/// Can be implemented by `PayFromAccount` using a `fungible` impl, but can also be implemented with +/// XCM/MultiAsset and made generic over assets. +pub trait Pay { + /// The type by which we measure units of the currency in which we make payments. + type Balance: Balance; + /// The type by which we identify the beneficiaries to whom a payment may be made. + type Beneficiary; + /// The type for the kinds of asset that are going to be paid. + /// + /// The unit type can be used here to indicate there's only one kind of asset to do payments + /// with. When implementing, it should be clear from the context what that asset is. + type AssetKind; + /// An identifier given to an individual payment. + type Id: FullCodec + MaxEncodedLen + TypeInfo + Clone + Eq + PartialEq + Debug + Copy; + /// An error which could be returned by the Pay type + type Error: Debug; + /// Make a payment and return an identifier for later evaluation of success in some off-chain + /// mechanism (likely an event, but possibly not on this chain). + fn pay( + who: &Self::Beneficiary, + asset_kind: Self::AssetKind, + amount: Self::Balance, + ) -> Result; + /// Check how a payment has proceeded. `id` must have been previously returned by `pay` for + /// the result of this call to be meaningful. Once this returns anything other than + /// `InProgress` for some `id` it must return `Unknown` rather than the actual result + /// value. + fn check_payment(id: Self::Id) -> PaymentStatus; + /// Ensure that a call to pay with the given parameters will be successful if done immediately + /// after this call. Used in benchmarking code. + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful( + who: &Self::Beneficiary, + asset_kind: Self::AssetKind, + amount: Self::Balance, + ); + /// Ensure that a call to `check_payment` with the given parameters will return either `Success` + /// or `Failure`. + #[cfg(feature = "runtime-benchmarks")] + fn ensure_concluded(id: Self::Id); +} + +/// Status for making a payment via the `Pay::pay` trait function. +#[derive(Encode, Decode, Eq, PartialEq, Clone, TypeInfo, MaxEncodedLen, RuntimeDebug)] +pub enum PaymentStatus { + /// Payment is in progress. Nothing to report yet. + InProgress, + /// Payment status is unknowable. It may already have reported the result, or if not then + /// it will never be reported successful or failed. + Unknown, + /// Payment happened successfully. + Success, + /// Payment failed. It may safely be retried. + Failure, +} + +/// Simple implementation of `Pay` which makes a payment from a "pot" - i.e. a single account. +pub struct PayFromAccount(sp_std::marker::PhantomData<(F, A)>); +impl> Pay for PayFromAccount { + type Balance = F::Balance; + type Beneficiary = A::Type; + type AssetKind = (); + type Id = (); + type Error = DispatchError; + fn pay( + who: &Self::Beneficiary, + _: Self::AssetKind, + amount: Self::Balance, + ) -> Result { + >::transfer(&A::get(), who, amount, Expendable)?; + Ok(()) + } + fn check_payment(_: ()) -> PaymentStatus { + PaymentStatus::Success + } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_successful(_: &Self::Beneficiary, _: Self::AssetKind, amount: Self::Balance) { + >::mint_into(&A::get(), amount).unwrap(); + } + #[cfg(feature = "runtime-benchmarks")] + fn ensure_concluded(_: Self::Id) {} +} diff --git a/frame/support/src/traits/try_runtime.rs b/frame/support/src/traits/try_runtime.rs index 273af09a145e..cb18f9d5b71c 100644 --- a/frame/support/src/traits/try_runtime.rs +++ b/frame/support/src/traits/try_runtime.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,10 +19,11 @@ use impl_trait_for_tuples::impl_for_tuples; use sp_arithmetic::traits::AtLeast32BitUnsigned; +use sp_runtime::TryRuntimeError; use sp_std::prelude::*; /// Which state tests to execute. -#[derive(codec::Encode, codec::Decode, Clone)] +#[derive(codec::Encode, codec::Decode, Clone, scale_info::TypeInfo)] pub enum Select { /// None of them. None, @@ -82,7 +83,7 @@ impl sp_std::str::FromStr for Select { } /// Select which checks should be run when trying a runtime upgrade upgrade. -#[derive(codec::Encode, codec::Decode, Clone, Debug, Copy)] +#[derive(codec::Encode, codec::Decode, Clone, Debug, Copy, scale_info::TypeInfo)] pub enum UpgradeCheckSelect { /// Run no checks. None, @@ -129,7 +130,7 @@ impl core::str::FromStr for UpgradeCheckSelect { /// This hook should not alter any storage. pub trait TryState { /// Execute the state checks. - fn try_state(_: BlockNumber, _: Select) -> Result<(), &'static str>; + fn try_state(_: BlockNumber, _: Select) -> Result<(), TryRuntimeError>; } #[cfg_attr(all(not(feature = "tuples-96"), not(feature = "tuples-128")), impl_for_tuples(64))] @@ -139,7 +140,7 @@ impl TryState Result<(), &'static str> { + fn try_state(n: BlockNumber, targets: Select) -> Result<(), TryRuntimeError> { match targets { Select::None => Ok(()), Select::All => { @@ -148,7 +149,7 @@ impl TryState { - let functions: &[fn(BlockNumber, Select) -> Result<(), &'static str>] = + let functions: &[fn(BlockNumber, Select) -> Result<(), TryRuntimeError>] = &[for_tuples!(#( Tuple::try_state ),*)]; let skip = n.clone() % (functions.len() as u32).into(); let skip: u32 = @@ -163,16 +164,24 @@ impl TryState { let try_state_fns: &[( &'static str, - fn(BlockNumber, Select) -> Result<(), &'static str>, + fn(BlockNumber, Select) -> Result<(), TryRuntimeError>, )] = &[for_tuples!( #( (::name(), Tuple::try_state) ),* )]; let mut result = Ok(()); - for (name, try_state_fn) in try_state_fns { - if pallet_names.iter().any(|n| n == name.as_bytes()) { + pallet_names.iter().for_each(|pallet_name| { + if let Some((name, try_state_fn)) = + try_state_fns.iter().find(|(name, _)| name.as_bytes() == pallet_name) + { result = result.and(try_state_fn(n.clone(), targets.clone())); + } else { + crate::log::warn!( + "Pallet {:?} not found", + sp_std::str::from_utf8(pallet_name).unwrap_or_default() + ); } - } + }); + result }, } diff --git a/frame/support/src/traits/validation.rs b/frame/support/src/traits/validation.rs index 135dd927acbb..84430901b978 100644 --- a/frame/support/src/traits/validation.rs +++ b/frame/support/src/traits/validation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index 49ae3163d0cd..4201b8d48d15 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,81 +20,10 @@ use crate::dispatch::{DispatchError, Parameter}; use codec::{HasCompact, MaxEncodedLen}; -use sp_arithmetic::{ - traits::{SaturatedConversion, UniqueSaturatedFrom, UniqueSaturatedInto}, - Perbill, -}; +use sp_arithmetic::Perbill; use sp_runtime::traits::Member; use sp_std::prelude::*; -/// A trait similar to `Convert` to convert values from `B` an abstract balance type -/// into u64 and back from u128. (This conversion is used in election and other places where complex -/// calculation over balance type is needed) -/// -/// Total issuance of the currency is passed in, but an implementation of this trait may or may not -/// use it. -/// -/// # WARNING -/// -/// the total issuance being passed in implies that the implementation must be aware of the fact -/// that its values can affect the outcome. This implies that if the vote value is dependent on the -/// total issuance, it should never ber written to storage for later re-use. -pub trait CurrencyToVote { - /// Convert balance to u64. - fn to_vote(value: B, issuance: B) -> u64; - - /// Convert u128 to balance. - fn to_currency(value: u128, issuance: B) -> B; -} - -/// An implementation of `CurrencyToVote` tailored for chain's that have a balance type of u128. -/// -/// The factor is the `(total_issuance / u64::MAX).max(1)`, represented as u64. Let's look at the -/// important cases: -/// -/// If the chain's total issuance is less than u64::MAX, this will always be 1, which means that -/// the factor will not have any effect. In this case, any account's balance is also less. Thus, -/// both of the conversions are basically an `as`; Any balance can fit in u64. -/// -/// If the chain's total issuance is more than 2*u64::MAX, then a factor might be multiplied and -/// divided upon conversion. -pub struct U128CurrencyToVote; - -impl U128CurrencyToVote { - fn factor(issuance: u128) -> u128 { - (issuance / u64::MAX as u128).max(1) - } -} - -impl CurrencyToVote for U128CurrencyToVote { - fn to_vote(value: u128, issuance: u128) -> u64 { - (value / Self::factor(issuance)).saturated_into() - } - - fn to_currency(value: u128, issuance: u128) -> u128 { - value.saturating_mul(Self::factor(issuance)) - } -} - -/// A naive implementation of `CurrencyConvert` that simply saturates all conversions. -/// -/// # Warning -/// -/// This is designed to be used mostly for testing. Use with care, and think about the consequences. -pub struct SaturatingCurrencyToVote; - -impl + UniqueSaturatedFrom> CurrencyToVote - for SaturatingCurrencyToVote -{ - fn to_vote(value: B, _: B) -> u64 { - value.unique_saturated_into() - } - - fn to_currency(value: u128, _: B) -> B { - B::unique_saturated_from(value) - } -} - pub trait VoteTally { fn new(_: Class) -> Self; fn ayes(&self, class: Class) -> Votes; diff --git a/frame/support/src/weights.rs b/frame/support/src/weights.rs index d52380b832fc..75eba8fbe988 100644 --- a/frame/support/src/weights.rs +++ b/frame/support/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/weights/block_weights.rs b/frame/support/src/weights/block_weights.rs index b68c1fb508b0..57a68554755a 100644 --- a/frame/support/src/weights/block_weights.rs +++ b/frame/support/src/weights/block_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,8 @@ // limitations under the License. //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07 (Y/M/D) -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16 (Y/M/D) +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! //! SHORT-NAME: `block`, LONG-NAME: `BlockExecution`, RUNTIME: `Development` //! WARMUPS: `10`, REPEAT: `100` @@ -44,17 +44,17 @@ parameter_types! { /// Calculated by multiplying the *Average* with `1.0` and adding `0`. /// /// Stats nanoseconds: - /// Min, Max: 351_000, 392_617 - /// Average: 358_523 - /// Median: 359_836 - /// Std-Dev: 6698.67 + /// Min, Max: 376_949, 622_462 + /// Average: 390_584 + /// Median: 386_322 + /// Std-Dev: 24792.0 /// /// Percentiles nanoseconds: - /// 99th: 390_723 - /// 95th: 365_799 - /// 75th: 361_582 + /// 99th: 433_299 + /// 95th: 402_688 + /// 75th: 391_645 pub const BlockExecutionWeight: Weight = - Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(358_523)); + Weight::from_parts(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(390_584), 0); } #[cfg(test)] diff --git a/frame/support/src/weights/extrinsic_weights.rs b/frame/support/src/weights/extrinsic_weights.rs index ced1fb91621f..a304f089ff78 100644 --- a/frame/support/src/weights/extrinsic_weights.rs +++ b/frame/support/src/weights/extrinsic_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,8 +16,8 @@ // limitations under the License. //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07 (Y/M/D) -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16 (Y/M/D) +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! //! SHORT-NAME: `extrinsic`, LONG-NAME: `ExtrinsicBase`, RUNTIME: `Development` //! WARMUPS: `10`, REPEAT: `100` @@ -44,17 +44,17 @@ parameter_types! { /// Calculated by multiplying the *Average* with `1.0` and adding `0`. /// /// Stats nanoseconds: - /// Min, Max: 98_722, 101_420 - /// Average: 98_974 - /// Median: 98_951 - /// Std-Dev: 271.62 + /// Min, Max: 123_875, 128_419 + /// Average: 124_414 + /// Median: 124_332 + /// Std-Dev: 497.74 /// /// Percentiles nanoseconds: - /// 99th: 99_202 - /// 95th: 99_163 - /// 75th: 99_030 + /// 99th: 125_245 + /// 95th: 124_989 + /// 75th: 124_498 pub const ExtrinsicBaseWeight: Weight = - Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(98_974)); + Weight::from_parts(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(124_414), 0); } #[cfg(test)] diff --git a/frame/support/src/weights/paritydb_weights.rs b/frame/support/src/weights/paritydb_weights.rs index 6fd1112ee294..f69fc0cd93c6 100644 --- a/frame/support/src/weights/paritydb_weights.rs +++ b/frame/support/src/weights/paritydb_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/src/weights/rocksdb_weights.rs b/frame/support/src/weights/rocksdb_weights.rs index b18b387de995..25d2ac1cdec0 100644 --- a/frame/support/src/weights/rocksdb_weights.rs +++ b/frame/support/src/weights/rocksdb_weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/test/Cargo.toml b/frame/support/test/Cargo.toml index 90ef243eed6c..20906f17b018 100644 --- a/frame/support/test/Cargo.toml +++ b/frame/support/test/Cargo.toml @@ -12,43 +12,54 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -serde = { version = "1.0.136", default-features = false, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -sp-arithmetic = { version = "6.0.0", default-features = false, path = "../../../primitives/arithmetic" } -sp-io = { version = "7.0.0", path = "../../../primitives/io", default-features = false } -sp-state-machine = { version = "0.13.0", optional = true, path = "../../../primitives/state-machine" } +static_assertions = "1.1.0" +serde = { version = "1.0.163", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../../primitives/arithmetic" } +sp-io = { version = "23.0.0", path = "../../../primitives/io", default-features = false } +sp-state-machine = { version = "0.28.0", optional = true, path = "../../../primitives/state-machine" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } -sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } -sp-version = { version = "5.0.0", default-features = false, path = "../../../primitives/version" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } +sp-version = { version = "22.0.0", default-features = false, path = "../../../primitives/version" } trybuild = { version = "1.0.74", features = [ "diff" ] } -pretty_assertions = "1.2.1" +pretty_assertions = "1.3.0" rustversion = "1.0.6" frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } +frame-executive = { version = "4.0.0-dev", default-features = false, path = "../../executive" } # The "std" feature for this pallet is never activated on purpose, in order to test construct_runtime error message test-pallet = { package = "frame-support-test-pallet", default-features = false, path = "pallet" } [features] default = ["std"] std = [ - "serde/std", "codec/std", - "scale-info/std", "frame-benchmarking/std", + "frame-executive/std", "frame-support/std", "frame-system/std", + "scale-info/std", + "serde/std", + "sp-api/std", + "sp-arithmetic/std", "sp-core/std", - "sp-std/std", "sp-io/std", "sp-runtime/std", "sp-state-machine", - "sp-arithmetic/std", + "sp-std/std", "sp-version/std", + "test-pallet/std", +] +experimental = ["frame-support/experimental"] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-executive/try-runtime", ] -try-runtime = ["frame-support/try-runtime"] # WARNING: # Only CI runs with this feature enabled. This feature is for testing stuff related to the FRAME macros # in conjunction with rust features. diff --git a/frame/support/test/compile_pass/Cargo.toml b/frame/support/test/compile_pass/Cargo.toml index dd5e1b996db9..5d6caa002b37 100644 --- a/frame/support/test/compile_pass/Cargo.toml +++ b/frame/support/test/compile_pass/Cargo.toml @@ -12,19 +12,19 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -frame-support = { version = "4.0.0-dev", default-features = false, path = "../../" } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +renamed-frame-support = { package = "frame-support", version = "4.0.0-dev", default-features = false, path = "../../" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../system" } -sp-core = { version = "7.0.0", default-features = false, path = "../../../../primitives/core" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../../primitives/runtime" } -sp-version = { version = "5.0.0", default-features = false, path = "../../../../primitives/version" } +sp-core = { version = "21.0.0", default-features = false, path = "../../../../primitives/core" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../../primitives/runtime" } +sp-version = { version = "22.0.0", default-features = false, path = "../../../../primitives/version" } [features] default = ["std"] std = [ "codec/std", - "frame-support/std", + "renamed-frame-support/std", "frame-system/std", "scale-info/std", "sp-core/std", diff --git a/frame/support/test/compile_pass/src/lib.rs b/frame/support/test/compile_pass/src/lib.rs index b46f6c48a6d9..bf90d73acb32 100644 --- a/frame/support/test/compile_pass/src/lib.rs +++ b/frame/support/test/compile_pass/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,15 +16,13 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . +//! Test that `construct_runtime!` also works when `frame-support` is renamed in the `Cargo.toml`. + #![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"] -//! This crate tests that `construct_runtime!` expands the pallet parts -//! correctly even when frame-support is renamed in Cargo.toml -use frame_support::{ +use renamed_frame_support::{ construct_runtime, parameter_types, - traits::{ConstU16, ConstU32, ConstU64}, + traits::{ConstU16, ConstU32, ConstU64, Everything}, }; use sp_core::{sr25519, H256}; use sp_runtime::{ @@ -47,26 +45,24 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { pub type Signature = sr25519::Signature; pub type AccountId = ::Signer; pub type BlockNumber = u64; -pub type Index = u64; parameter_types! { pub const Version: RuntimeVersion = VERSION; } impl frame_system::Config for Runtime { - type BaseCallFilter = frame_support::traits::Everything; + type BaseCallFilter = Everything; type BlockWeights = (); type BlockLength = (); - type Index = u128; + type Nonce = u128; type Hash = H256; type Hashing = BlakeTwo256; - type Header = Header; + type Block = Block; type Lookup = IdentityLookup; type BlockHashCount = ConstU64<2400>; type Version = Version; type AccountData = (); type RuntimeOrigin = RuntimeOrigin; - type BlockNumber = BlockNumber; type AccountId = AccountId; type RuntimeEvent = RuntimeEvent; type PalletInfo = PalletInfo; @@ -85,11 +81,7 @@ pub type Block = generic::Block; pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { + pub struct Runtime { System: frame_system, } ); diff --git a/frame/support/test/pallet/Cargo.toml b/frame/support/test/pallet/Cargo.toml index 135d0e64b8ff..bc171c354e6a 100644 --- a/frame/support/test/pallet/Cargo.toml +++ b/frame/support/test/pallet/Cargo.toml @@ -12,10 +12,12 @@ repository = "https://github.com/paritytech/substrate/" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } scale-info = { version = "2.0.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", default-features = false, features = ["derive"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../system" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../../primitives/runtime" } [features] default = ["std"] @@ -24,4 +26,5 @@ std = [ "frame-support/std", "frame-system/std", "scale-info/std", + "serde/std", ] diff --git a/frame/support/test/pallet/src/lib.rs b/frame/support/test/pallet/src/lib.rs index 37678e056f3e..49450659285d 100644 --- a/frame/support/test/pallet/src/lib.rs +++ b/frame/support/test/pallet/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Testing pallet macro +//! A basic pallet that can be used to test `construct_runtime!`. // Ensure docs are propagated properly by the macros. #![warn(missing_docs)] @@ -24,10 +24,7 @@ pub use pallet::*; #[frame_support::pallet] pub mod pallet { - #[allow(unused_imports)] use frame_support::pallet_prelude::*; - #[allow(unused_imports)] - use frame_system::pallet_prelude::*; #[pallet::pallet] pub struct Pallet(_); @@ -37,14 +34,17 @@ pub mod pallet { /// I'm the documentation #[pallet::storage] - pub type Value = StorageValue; + pub type Value = StorageValue<_, u32>; #[pallet::genesis_config] - #[cfg_attr(feature = "std", derive(Default))] - pub struct GenesisConfig {} + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + #[serde(skip)] + _config: core::marker::PhantomData, + } #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) {} } diff --git a/frame/support/test/src/lib.rs b/frame/support/test/src/lib.rs index 0ceeed42ff98..6b38d42d33d0 100644 --- a/frame/support/test/src/lib.rs +++ b/frame/support/test/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,58 +15,124 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Test crate for frame_support. Allow to make use of `frame_support::decl_storage`. -//! See tests directory. +//! Minimal pallet without `frame_system::Config`-super trait. // Make sure we fail compilation on warnings #![warn(missing_docs)] #![deny(warnings)] -/// The configuration trait -pub trait Config: 'static { - /// The runtime origin type. - type RuntimeOrigin: codec::Codec + codec::EncodeLike + Default + scale_info::TypeInfo; - /// The block number type. - type BlockNumber: codec::Codec + codec::EncodeLike + Default + scale_info::TypeInfo; - /// The information about the pallet setup in the runtime. - type PalletInfo: frame_support::traits::PalletInfo; - /// The db weights. - type DbWeight: frame_support::traits::Get; -} +pub use frame_support::dispatch::RawOrigin; +use frame_system::pallet_prelude::BlockNumberFor; -frame_support::decl_module! { - /// Some test module - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self {} -} +pub use self::pallet::*; + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use super::*; + use crate::{self as frame_system, pallet_prelude::*}; + use frame_support::pallet_prelude::*; -/// A PalletInfo implementation which just panics. -pub struct PanicPalletInfo; + #[pallet::pallet] + pub struct Pallet(_); -impl frame_support::traits::PalletInfo for PanicPalletInfo { - fn index() -> Option { - unimplemented!("PanicPalletInfo mustn't be triggered by tests"); + /// The configuration trait. + #[pallet::config] + #[pallet::disable_frame_system_supertrait_check] + pub trait Config: 'static + Eq + Clone { + /// The block number type. + type BlockNumber: Parameter + Member + Default + MaybeSerializeDeserialize + MaxEncodedLen; + /// The account type. + type AccountId: Parameter + Member + MaxEncodedLen; + /// The basic call filter to use in Origin. + type BaseCallFilter: frame_support::traits::Contains; + /// The runtime origin type. + type RuntimeOrigin: Into, Self::RuntimeOrigin>> + + From>; + /// The runtime call type. + type RuntimeCall; + /// The runtime event type. + type RuntimeEvent: Parameter + + Member + + IsType<::RuntimeEvent> + + From>; + /// The information about the pallet setup in the runtime. + type PalletInfo: frame_support::traits::PalletInfo; + /// The db weights. + type DbWeight: Get; } - fn name() -> Option<&'static str> { - unimplemented!("PanicPalletInfo mustn't be triggered by tests"); + + #[pallet::call] + impl Pallet { + /// A noop call. + pub fn noop(_origin: OriginFor) -> DispatchResult { + Ok(()) + } } - fn module_name() -> Option<&'static str> { - unimplemented!("PanicPalletInfo mustn't be triggered by tests"); + + impl Pallet { + /// A empty method. + pub fn deposit_event(_event: impl Into) {} } - fn crate_version() -> Option { - unimplemented!("PanicPalletInfo mustn't be triggered by tests"); + + /// The origin type. + #[pallet::origin] + pub type Origin = RawOrigin<::AccountId>; + + /// The error type. + #[pallet::error] + pub enum Error { + /// Test error documentation + TestError, + /// Error documentation + /// with multiple lines + AnotherError, + /// Required by construct_runtime + CallFiltered, + } + + /// The event type. + #[pallet::event] + pub enum Event { + /// The extrinsic is successful + ExtrinsicSuccess, + /// The extrinsic is failed + ExtrinsicFailed, + /// The ignored error + Ignore(::BlockNumber), } } +/// Ensure that the origin `o` represents the root. Returns `Ok` or an `Err` otherwise. +pub fn ensure_root(o: OuterOrigin) -> Result<(), &'static str> +where + OuterOrigin: Into, OuterOrigin>>, +{ + o.into().map(|_| ()).map_err(|_| "bad origin: expected to be a root origin") +} + +/// Same semantic as [`frame_system`]. +// Note: we cannot use [`frame_system`] here since the pallet does not depend on +// [`frame_system::Config`]. +pub mod pallet_prelude { + pub use crate::ensure_root; + + /// Type alias for the `Origin` associated type of system config. + pub type OriginFor = ::RuntimeOrigin; + + /// Type alias for the `BlockNumber` associated type of system config. + pub type BlockNumberFor = ::BlockNumber; +} + /// Provides an implementation of [`frame_support::traits::Randomness`] that should only be used in /// tests! pub struct TestRandomness(sp_std::marker::PhantomData); -impl frame_support::traits::Randomness - for TestRandomness +impl + frame_support::traits::Randomness> for TestRandomness where T: frame_system::Config, { - fn random(subject: &[u8]) -> (Output, T::BlockNumber) { + fn random(subject: &[u8]) -> (Output, BlockNumberFor) { use sp_runtime::traits::TrailingZeroInput; ( diff --git a/frame/support/test/src/pallet_version.rs b/frame/support/test/src/pallet_version.rs deleted file mode 100644 index 096289116c41..000000000000 --- a/frame/support/test/src/pallet_version.rs +++ /dev/null @@ -1,29 +0,0 @@ -// 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. - -use frame_support::{crate_to_pallet_version, traits::PalletVersion}; - -#[test] -fn ensure_that_current_pallet_version_is_correct() { - let expected = PalletVersion { - major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(), - minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(), - patch: env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(), - }; - - assert_eq!(expected, crate_to_pallet_version!()) -} diff --git a/frame/support/test/tests/benchmark_ui.rs b/frame/support/test/tests/benchmark_ui.rs index 243030bd07fc..aa5fadd0e27b 100644 --- a/frame/support/test/tests/benchmark_ui.rs +++ b/frame/support/test/tests/benchmark_ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2023 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/test/tests/benchmark_ui/bad_param_name.rs b/frame/support/test/tests/benchmark_ui/bad_param_name.rs index ad4db4f0a4ba..657e481a9430 100644 --- a/frame/support/test/tests/benchmark_ui/bad_param_name.rs +++ b/frame/support/test/tests/benchmark_ui/bad_param_name.rs @@ -1,4 +1,4 @@ -use frame_support::benchmarking::*; +use frame_benchmarking::v2::*; #[allow(unused_imports)] use frame_support_test::Config; diff --git a/frame/support/test/tests/benchmark_ui/bad_param_name_too_long.rs b/frame/support/test/tests/benchmark_ui/bad_param_name_too_long.rs index 50e4dc6fd460..f970126d12e7 100644 --- a/frame/support/test/tests/benchmark_ui/bad_param_name_too_long.rs +++ b/frame/support/test/tests/benchmark_ui/bad_param_name_too_long.rs @@ -1,9 +1,9 @@ -use frame_support::benchmarking::*; +use frame_benchmarking::v2::*; #[allow(unused_imports)] use frame_support_test::Config; #[benchmarks] -mod benches { +mod benchmarks { #[benchmark] fn bench(xx: Linear<1, 2>) { #[block] diff --git a/frame/support/test/tests/benchmark_ui/bad_param_name_upper_case.rs b/frame/support/test/tests/benchmark_ui/bad_param_name_upper_case.rs index 0270582b3b85..9970f3230167 100644 --- a/frame/support/test/tests/benchmark_ui/bad_param_name_upper_case.rs +++ b/frame/support/test/tests/benchmark_ui/bad_param_name_upper_case.rs @@ -1,4 +1,4 @@ -use frame_support::benchmarking::*; +use frame_benchmarking::v2::*; #[allow(unused_imports)] use frame_support_test::Config; diff --git a/frame/support/test/tests/benchmark_ui/bad_param_range.stderr b/frame/support/test/tests/benchmark_ui/bad_param_range.stderr deleted file mode 100644 index 1347af0a07b8..000000000000 --- a/frame/support/test/tests/benchmark_ui/bad_param_range.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: The start of a `ParamRange` must be less than or equal to the end - --> tests/benchmark_ui/bad_param_range.rs:10:21 - | -10 | fn bench(x: Linear<3, 1>) { - | ^ diff --git a/frame/support/test/tests/benchmark_ui/bad_params.rs b/frame/support/test/tests/benchmark_ui/bad_params.rs index a0c9236982c6..5049f2eae2c2 100644 --- a/frame/support/test/tests/benchmark_ui/bad_params.rs +++ b/frame/support/test/tests/benchmark_ui/bad_params.rs @@ -1,4 +1,4 @@ -use frame_support::benchmarking::*; +use frame_benchmarking::v2::*; #[allow(unused_imports)] use frame_support_test::Config; diff --git a/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.rs b/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.rs new file mode 100644 index 000000000000..5e332801df83 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.rs @@ -0,0 +1,19 @@ +use frame_benchmarking::v2::*; +#[allow(unused_imports)] +use frame_support_test::Config; + +#[benchmarks] +mod benches { + use super::*; + + #[benchmark] + fn bench() -> Result<(), BenchmarkException> { + let a = 2 + 2; + #[block] + {} + assert_eq!(a, 4); + Ok(()) + } +} + +fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.stderr b/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.stderr new file mode 100644 index 000000000000..ab0bff54a8a0 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_non_benchmark_err.stderr @@ -0,0 +1,5 @@ +error: expected `BenchmarkError` + --> tests/benchmark_ui/bad_return_non_benchmark_err.rs:10:27 + | +10 | fn bench() -> Result<(), BenchmarkException> { + | ^^^^^^^^^^^^^^^^^^ diff --git a/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.rs b/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.rs new file mode 100644 index 000000000000..a4b0d007eeec --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.rs @@ -0,0 +1,17 @@ +use frame_benchmarking::v2::*; +#[allow(unused_imports)] +use frame_support_test::Config; + +#[benchmarks] +mod benchmarks { + use super::*; + + #[benchmark] + fn bench() -> (String, u32) { + #[block] + {} + (String::from("hey"), 23) + } +} + +fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.stderr b/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.stderr new file mode 100644 index 000000000000..69d61b422915 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_non_type_path.stderr @@ -0,0 +1,5 @@ +error: Only `Result<(), BenchmarkError>` or a blank return type is allowed on benchmark function definitions + --> tests/benchmark_ui/bad_return_non_type_path.rs:10:16 + | +10 | fn bench() -> (String, u32) { + | ^^^^^^^^^^^^^ diff --git a/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.rs b/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.rs new file mode 100644 index 000000000000..15289c298aec --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.rs @@ -0,0 +1,15 @@ +use frame_benchmarking::v2::*; +#[allow(unused_imports)] +use frame_support_test::Config; + +#[benchmarks] +mod benchmarks { + #[benchmark] + fn bench() -> Result { + #[block] + {} + Ok(10) + } +} + +fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.stderr b/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.stderr new file mode 100644 index 000000000000..4181ea099a14 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_non_unit_t.stderr @@ -0,0 +1,5 @@ +error: expected `()` + --> tests/benchmark_ui/bad_return_non_unit_t.rs:8:23 + | +8 | fn bench() -> Result { + | ^^^ diff --git a/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.rs b/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.rs new file mode 100644 index 000000000000..a6a2c61127fa --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.rs @@ -0,0 +1,22 @@ +use frame_benchmarking::v2::*; +#[allow(unused_imports)] +use frame_support_test::Config; + +#[benchmarks] +mod benches { + use super::*; + + fn something() -> Result<(), BenchmarkError> { + Ok(()) + } + + #[benchmark] + fn bench() { + something()?; + #[block] + {} + assert_eq!(2 + 2, 4); + } +} + +fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr b/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr new file mode 100644 index 000000000000..601bbd20fb73 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_type_blank_with_question.stderr @@ -0,0 +1,10 @@ +error[E0277]: the `?` operator can only be used in a function that returns `Result` or `Option` (or another type that implements `FromResidual`) + --> tests/benchmark_ui/bad_return_type_blank_with_question.rs:15:14 + | +5 | #[benchmarks] + | ------------- this function should return `Result` or `Option` to accept `?` +... +15 | something()?; + | ^ cannot use the `?` operator in a function that returns `()` + | + = help: the trait `FromResidual>` is not implemented for `()` diff --git a/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.rs b/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.rs new file mode 100644 index 000000000000..76f129900530 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.rs @@ -0,0 +1,16 @@ +use frame_benchmarking::v2::*; +#[allow(unused_imports)] +use frame_support_test::Config; + +#[benchmarks] +mod benches { + use super::*; + + #[benchmark] + fn bench() -> Result<(), BenchmarkError> { + #[block] + {} + } +} + +fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.stderr b/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.stderr new file mode 100644 index 000000000000..ff501a620fe3 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_type_no_last_stmt.stderr @@ -0,0 +1,9 @@ +error: Benchmark `#[block]` or `#[extrinsic_call]` item cannot be the last statement of your benchmark function definition if you have defined a return type. You should return something compatible with Result<(), BenchmarkError> (i.e. `Ok(())`) as the last statement or change your signature to a blank return type. + --> tests/benchmark_ui/bad_return_type_no_last_stmt.rs:10:43 + | +10 | fn bench() -> Result<(), BenchmarkError> { + | ______________________________________________^ +11 | | #[block] +12 | | {} +13 | | } + | |_____^ diff --git a/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.rs b/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.rs new file mode 100644 index 000000000000..c206ec36a151 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.rs @@ -0,0 +1,19 @@ +use frame_benchmarking::v2::*; +#[allow(unused_imports)] +use frame_support_test::Config; + +#[benchmarks] +mod benches { + use super::*; + + #[benchmark] + fn bench(y: Linear<1, 2>) -> String { + let a = 2 + 2; + #[block] + {} + assert_eq!(a, 4); + String::from("test") + } +} + +fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.stderr b/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.stderr new file mode 100644 index 000000000000..b830b8eb59c6 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_type_non_result.stderr @@ -0,0 +1,5 @@ +error: expected `Result` + --> tests/benchmark_ui/bad_return_type_non_result.rs:10:31 + | +10 | fn bench(y: Linear<1, 2>) -> String { + | ^^^^^^ diff --git a/frame/support/test/tests/benchmark_ui/bad_return_type_option.rs b/frame/support/test/tests/benchmark_ui/bad_return_type_option.rs new file mode 100644 index 000000000000..4b5588593974 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_type_option.rs @@ -0,0 +1,18 @@ +use frame_benchmarking::v2::*; +#[allow(unused_imports)] +use frame_support_test::Config; + +#[benchmarks] +mod benches { + use super::*; + + #[benchmark] + fn bench() -> Option { + #[block] + {} + assert_eq!(2 + 2, 4); + None + } +} + +fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/bad_return_type_option.stderr b/frame/support/test/tests/benchmark_ui/bad_return_type_option.stderr new file mode 100644 index 000000000000..050da1676735 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/bad_return_type_option.stderr @@ -0,0 +1,5 @@ +error: expected `Result` + --> tests/benchmark_ui/bad_return_type_option.rs:10:16 + | +10 | fn bench() -> Option { + | ^^^^^^ diff --git a/frame/support/test/tests/benchmark_ui/dup_block.rs b/frame/support/test/tests/benchmark_ui/dup_block.rs index 4c4a8fc11d30..2c2ef9db9a45 100644 --- a/frame/support/test/tests/benchmark_ui/dup_block.rs +++ b/frame/support/test/tests/benchmark_ui/dup_block.rs @@ -1,4 +1,4 @@ -use frame_support::benchmarking::*; +use frame_benchmarking::v2::*; #[allow(unused_imports)] use frame_support_test::Config; diff --git a/frame/support/test/tests/benchmark_ui/dup_extrinsic_call.rs b/frame/support/test/tests/benchmark_ui/dup_extrinsic_call.rs index 1a91b7c16d6a..4d135d1a04f5 100644 --- a/frame/support/test/tests/benchmark_ui/dup_extrinsic_call.rs +++ b/frame/support/test/tests/benchmark_ui/dup_extrinsic_call.rs @@ -1,4 +1,4 @@ -use frame_support::benchmarking::*; +use frame_benchmarking::v2::*; #[allow(unused_imports)] use frame_support_test::Config; diff --git a/frame/support/test/tests/benchmark_ui/empty_function.rs b/frame/support/test/tests/benchmark_ui/empty_function.rs new file mode 100644 index 000000000000..bc04101dd384 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/empty_function.rs @@ -0,0 +1,13 @@ +use frame_benchmarking::v2::*; +#[allow(unused_imports)] +use frame_support_test::Config; + +#[benchmarks] +mod benches { + use super::*; + + #[benchmark] + fn bench() {} +} + +fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/empty_function.stderr b/frame/support/test/tests/benchmark_ui/empty_function.stderr new file mode 100644 index 000000000000..69d75303613d --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/empty_function.stderr @@ -0,0 +1,5 @@ +error: No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body. + --> tests/benchmark_ui/empty_function.rs:10:13 + | +10 | fn bench() {} + | ^^ diff --git a/frame/support/test/tests/benchmark_ui/extra_extra.rs b/frame/support/test/tests/benchmark_ui/extra_extra.rs index 021106c7afc8..1aa6c9ecb752 100644 --- a/frame/support/test/tests/benchmark_ui/extra_extra.rs +++ b/frame/support/test/tests/benchmark_ui/extra_extra.rs @@ -1,4 +1,4 @@ -use frame_support::benchmarking::*; +use frame_benchmarking::v2::*; #[allow(unused_imports)] use frame_support_test::Config; diff --git a/frame/support/test/tests/benchmark_ui/extra_skip_meta.rs b/frame/support/test/tests/benchmark_ui/extra_skip_meta.rs index 1940f4cf1f04..3418c7af7374 100644 --- a/frame/support/test/tests/benchmark_ui/extra_skip_meta.rs +++ b/frame/support/test/tests/benchmark_ui/extra_skip_meta.rs @@ -1,4 +1,4 @@ -use frame_support::benchmarking::*; +use frame_benchmarking::v2::*; #[allow(unused_imports)] use frame_support_test::Config; diff --git a/frame/support/test/tests/benchmark_ui/extrinsic_call_out_of_fn.rs b/frame/support/test/tests/benchmark_ui/extrinsic_call_out_of_fn.rs index 4cb6bfc34c58..ce360ee7577f 100644 --- a/frame/support/test/tests/benchmark_ui/extrinsic_call_out_of_fn.rs +++ b/frame/support/test/tests/benchmark_ui/extrinsic_call_out_of_fn.rs @@ -1,4 +1,4 @@ -use frame_support::benchmarking::*; +use frame_benchmarking::v2::*; #[extrinsic_call] mod stuff {} diff --git a/frame/support/test/tests/benchmark_ui/invalid_origin.rs b/frame/support/test/tests/benchmark_ui/invalid_origin.rs new file mode 100644 index 000000000000..cfb00e88c00c --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/invalid_origin.rs @@ -0,0 +1,17 @@ +use frame_benchmarking::v2::*; +#[allow(unused_imports)] +use frame_support_test::Config; +use frame_support_test::Call; + +#[benchmarks] +mod benches { + use super::*; + + #[benchmark] + fn bench() { + #[extrinsic_call] + noop(1); + } +} + +fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/invalid_origin.stderr b/frame/support/test/tests/benchmark_ui/invalid_origin.stderr new file mode 100644 index 000000000000..115a8206f58a --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/invalid_origin.stderr @@ -0,0 +1,8 @@ +error[E0277]: the trait bound `::RuntimeOrigin: From<{integer}>` is not satisfied + --> tests/benchmark_ui/invalid_origin.rs:6:1 + | +6 | #[benchmarks] + | ^^^^^^^^^^^^^ the trait `From<{integer}>` is not implemented for `::RuntimeOrigin` + | + = note: required for `{integer}` to implement `Into<::RuntimeOrigin>` + = note: this error originates in the attribute macro `benchmarks` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/benchmark_ui/missing_call.rs b/frame/support/test/tests/benchmark_ui/missing_call.rs index 74370493542c..f39e74286b5c 100644 --- a/frame/support/test/tests/benchmark_ui/missing_call.rs +++ b/frame/support/test/tests/benchmark_ui/missing_call.rs @@ -1,4 +1,4 @@ -use frame_support::benchmarking::*; +use frame_benchmarking::v2::*; #[allow(unused_imports)] use frame_support_test::Config; @@ -7,7 +7,9 @@ mod benches { use super::*; #[benchmark] - fn bench() {} + fn bench() { + assert_eq!(2 + 2, 4); + } } fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/missing_call.stderr b/frame/support/test/tests/benchmark_ui/missing_call.stderr index 3b55f77d4256..908d97043922 100644 --- a/frame/support/test/tests/benchmark_ui/missing_call.stderr +++ b/frame/support/test/tests/benchmark_ui/missing_call.stderr @@ -1,5 +1,8 @@ error: No valid #[extrinsic_call] or #[block] annotation could be found in benchmark function body. --> tests/benchmark_ui/missing_call.rs:10:13 | -10 | fn bench() {} - | ^^ +10 | fn bench() { + | ________________^ +11 | | assert_eq!(2 + 2, 4); +12 | | } + | |_____^ diff --git a/frame/support/test/tests/benchmark_ui/missing_origin.rs b/frame/support/test/tests/benchmark_ui/missing_origin.rs index aad91bc79f82..2aaed756b9a4 100644 --- a/frame/support/test/tests/benchmark_ui/missing_origin.rs +++ b/frame/support/test/tests/benchmark_ui/missing_origin.rs @@ -1,4 +1,4 @@ -use frame_support::benchmarking::*; +use frame_benchmarking::v2::*; #[allow(unused_imports)] use frame_support_test::Config; diff --git a/frame/support/test/tests/benchmark_ui/pass/valid_basic.rs b/frame/support/test/tests/benchmark_ui/pass/valid_basic.rs index 5c84d7f76d87..450ce4f9c50d 100644 --- a/frame/support/test/tests/benchmark_ui/pass/valid_basic.rs +++ b/frame/support/test/tests/benchmark_ui/pass/valid_basic.rs @@ -1,4 +1,4 @@ -use frame_support::benchmarking::*; +use frame_benchmarking::v2::*; use frame_support_test::Config; #[benchmarks] diff --git a/frame/support/test/tests/benchmark_ui/pass/valid_complex_path_benchmark_result.rs b/frame/support/test/tests/benchmark_ui/pass/valid_complex_path_benchmark_result.rs new file mode 100644 index 000000000000..4930aedd6011 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/pass/valid_complex_path_benchmark_result.rs @@ -0,0 +1,17 @@ +use frame_benchmarking::v2::*; +#[allow(unused_imports)] +use frame_support_test::Config; + +#[benchmarks] +mod benches { + use super::*; + + #[benchmark] + fn bench() -> Result<(), frame_benchmarking::v2::BenchmarkError> { + #[block] + {} + Ok(()) + } +} + +fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/pass/valid_const_expr.rs b/frame/support/test/tests/benchmark_ui/pass/valid_const_expr.rs new file mode 100644 index 000000000000..bead3bf277be --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/pass/valid_const_expr.rs @@ -0,0 +1,28 @@ +use frame_benchmarking::v2::*; +use frame_support_test::Config; +use frame_support::parameter_types; + +#[benchmarks] +mod benches { + use super::*; + + const MY_CONST: u32 = 100; + + const fn my_fn() -> u32 { + 200 + } + + parameter_types! { + const MyConst: u32 = MY_CONST; + } + + #[benchmark(skip_meta, extra)] + fn bench(a: Linear<{MY_CONST * 2}, {my_fn() + MyConst::get()}>) { + let a = 2 + 2; + #[block] + {} + assert_eq!(a, 4); + } +} + +fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/bad_param_range.rs b/frame/support/test/tests/benchmark_ui/pass/valid_no_last_stmt.rs similarity index 69% rename from frame/support/test/tests/benchmark_ui/bad_param_range.rs rename to frame/support/test/tests/benchmark_ui/pass/valid_no_last_stmt.rs index aabb9fa7403a..ce09b437a83b 100644 --- a/frame/support/test/tests/benchmark_ui/bad_param_range.rs +++ b/frame/support/test/tests/benchmark_ui/pass/valid_no_last_stmt.rs @@ -1,4 +1,4 @@ -use frame_support::benchmarking::*; +use frame_benchmarking::v2::*; #[allow(unused_imports)] use frame_support_test::Config; @@ -7,7 +7,7 @@ mod benches { use super::*; #[benchmark] - fn bench(x: Linear<3, 1>) { + fn bench() { #[block] {} } diff --git a/frame/support/test/tests/benchmark_ui/pass/valid_path_result_benchmark_error.rs b/frame/support/test/tests/benchmark_ui/pass/valid_path_result_benchmark_error.rs new file mode 100644 index 000000000000..4930aedd6011 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/pass/valid_path_result_benchmark_error.rs @@ -0,0 +1,17 @@ +use frame_benchmarking::v2::*; +#[allow(unused_imports)] +use frame_support_test::Config; + +#[benchmarks] +mod benches { + use super::*; + + #[benchmark] + fn bench() -> Result<(), frame_benchmarking::v2::BenchmarkError> { + #[block] + {} + Ok(()) + } +} + +fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/pass/valid_result.rs b/frame/support/test/tests/benchmark_ui/pass/valid_result.rs new file mode 100644 index 000000000000..33d71ece4a01 --- /dev/null +++ b/frame/support/test/tests/benchmark_ui/pass/valid_result.rs @@ -0,0 +1,18 @@ +use frame_benchmarking::v2::*; +use frame_support_test::Config; + +#[benchmarks] +mod benches { + use super::*; + + #[benchmark] + fn bench() -> Result<(), BenchmarkError> { + let a = 2 + 2; + #[block] + {} + assert_eq!(a, 4); + Ok(()) + } +} + +fn main() {} diff --git a/frame/support/test/tests/benchmark_ui/unrecognized_option.rs b/frame/support/test/tests/benchmark_ui/unrecognized_option.rs index 4c2cea139f80..18cae4d5d5c8 100644 --- a/frame/support/test/tests/benchmark_ui/unrecognized_option.rs +++ b/frame/support/test/tests/benchmark_ui/unrecognized_option.rs @@ -1,4 +1,4 @@ -use frame_support::benchmarking::*; +use frame_benchmarking::v2::*; #[allow(unused_imports)] use frame_support_test::Config; diff --git a/primitives/consensus/vrf/src/lib.rs b/frame/support/test/tests/common/mod.rs similarity index 78% rename from primitives/consensus/vrf/src/lib.rs rename to frame/support/test/tests/common/mod.rs index 07e3f2c31970..b02ecc1b6e1d 100644 --- a/primitives/consensus/vrf/src/lib.rs +++ b/frame/support/test/tests/common/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,5 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Primitives for VRF-based consensus engines. -#![cfg_attr(not(feature = "std"), no_std)] - -pub mod schnorrkel; +///! Common functionality between tests. +pub mod outer_enums; diff --git a/frame/support/test/tests/common/outer_enums.rs b/frame/support/test/tests/common/outer_enums.rs new file mode 100644 index 000000000000..92dc7ac52207 --- /dev/null +++ b/frame/support/test/tests/common/outer_enums.rs @@ -0,0 +1,146 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Create 3 pallets for testing the outer error enum construction: +// +// - `pallet`: declares an error with `#[pallet::error]` +// - `pallet2`: declares an error with `#[pallet::error]` +// - `pallet3`: does not declare an error. + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + } + + #[pallet::event] + pub enum Event, I: 'static = ()> { + /// Something + Something(u32), + } + + #[pallet::pallet] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + phantom: PhantomData<(T, I)>, + } + + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { phantom: Default::default() } + } + } + + #[pallet::genesis_build] + impl, I: 'static> BuildGenesisConfig for GenesisConfig { + fn build(&self) {} + } + + #[pallet::error] + #[derive(PartialEq, Eq)] + pub enum Error { + /// doc comment put into metadata + InsufficientProposersBalance, + NonExistentStorageValue, + } +} + +#[frame_support::pallet] +pub mod pallet2 { + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + } + + #[pallet::event] + pub enum Event, I: 'static = ()> { + /// Something + Something(u32), + } + + #[pallet::pallet] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + phantom: PhantomData<(T, I)>, + } + + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { phantom: Default::default() } + } + } + + #[pallet::genesis_build] + impl, I: 'static> BuildGenesisConfig for GenesisConfig { + fn build(&self) {} + } + + #[pallet::error] + #[derive(PartialEq, Eq)] + pub enum Error { + /// doc comment put into metadata + OtherInsufficientProposersBalance, + OtherNonExistentStorageValue, + } +} + +#[frame_support::pallet] +pub mod pallet3 { + use frame_support::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + } + + #[pallet::event] + pub enum Event, I: 'static = ()> { + /// Something + Something(u32), + } + + #[pallet::pallet] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + phantom: PhantomData<(T, I)>, + } + + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + GenesisConfig { phantom: Default::default() } + } + } + + #[pallet::genesis_build] + impl, I: 'static> BuildGenesisConfig for GenesisConfig { + fn build(&self) {} + } +} diff --git a/frame/support/test/tests/construct_runtime.rs b/frame/support/test/tests/construct_runtime.rs index 18e4af1ac373..ff207ddf977c 100644 --- a/frame/support/test/tests/construct_runtime.rs +++ b/frame/support/test/tests/construct_runtime.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,257 +16,257 @@ // limitations under the License. //! General tests for construct_runtime macro, test for: -//! * error declareed with decl_error works +//! * error declared with decl_error works //! * integrity test is generated #![recursion_limit = "128"] use codec::MaxEncodedLen; -use frame_support::{parameter_types, traits::PalletInfo as _}; +use frame_support::{ + derive_impl, parameter_types, traits::PalletInfo as _, weights::RuntimeDbWeight, +}; +use frame_system::limits::{BlockLength, BlockWeights}; use scale_info::TypeInfo; -use sp_core::{sr25519, H256}; +use sp_api::RuntimeVersion; +use sp_core::{sr25519, ConstU64}; use sp_runtime::{ generic, traits::{BlakeTwo256, Verify}, DispatchError, ModuleError, }; -mod system; - -pub trait Currency {} - parameter_types! { pub static IntegrityTestExec: u32 = 0; } +#[frame_support::pallet(dev_mode)] mod module1 { - use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; - pub trait Config: system::Config {} + #[pallet::pallet] + pub struct Pallet(_); - frame_support::decl_module! { - pub struct Module, I: Instance = DefaultInstance> for enum Call - where origin: ::RuntimeOrigin, system=system - { - #[weight = 0] - pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { - Err(Error::::Something.into()) - } - } + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; } - #[derive( - Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, - )] - pub struct Origin(pub core::marker::PhantomData<(T, I)>); - - frame_support::decl_event! { - pub enum Event where - ::AccountId - { - A(AccountId), + #[pallet::call] + impl, I: 'static> Pallet { + pub fn fail(_origin: OriginFor) -> DispatchResult { + Err(Error::::Something.into()) } } - frame_support::decl_error! { - pub enum Error for Module, I: Instance> { - Something - } + #[pallet::origin] + #[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[scale_info(skip_type_params(I))] + pub struct Origin(pub PhantomData<(T, I)>); + + #[pallet::event] + pub enum Event, I: 'static = ()> { + A(::AccountId), } - frame_support::decl_storage! { - trait Store for Module, I: Instance=DefaultInstance> as Module {} + #[pallet::error] + pub enum Error { + Something, } } +#[frame_support::pallet(dev_mode)] mod module2 { use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; - pub trait Config: system::Config {} + #[pallet::pallet] + pub struct Pallet(_); - frame_support::decl_module! { - pub struct Module for enum Call - where origin: ::RuntimeOrigin, system=system - { - #[weight = 0] - pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { - Err(Error::::Something.into()) - } + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } - fn integrity_test() { - IntegrityTestExec::mutate(|i| *i += 1); - } + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + IntegrityTestExec::mutate(|i| *i += 1); } } - #[derive( - Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, - )] - pub struct Origin; - - frame_support::decl_event! { - pub enum Event { - A, + #[pallet::call] + impl Pallet { + pub fn fail(_origin: OriginFor) -> DispatchResult { + Err(Error::::Something.into()) } } - frame_support::decl_error! { - pub enum Error for Module { - Something - } + #[pallet::origin] + #[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + pub struct Origin; + + #[pallet::event] + pub enum Event { + A, } - frame_support::decl_storage! { - trait Store for Module as Module {} + #[pallet::error] + pub enum Error { + Something, } } mod nested { use super::*; + #[frame_support::pallet(dev_mode)] pub mod module3 { use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); - pub trait Config: system::Config {} + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + } - frame_support::decl_module! { - pub struct Module for enum Call - where origin: ::RuntimeOrigin, system=system - { - #[weight = 0] - pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { - Err(Error::::Something.into()) - } + #[pallet::hooks] + impl Hooks> for Pallet { + fn integrity_test() { + IntegrityTestExec::mutate(|i| *i += 1); + } + } - fn integrity_test() { - IntegrityTestExec::mutate(|i| *i += 1); - } + #[pallet::call] + impl Pallet { + pub fn fail(_origin: OriginFor) -> DispatchResult { + Err(Error::::Something.into()) } } - #[derive( - Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, - )] + #[pallet::origin] + #[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct Origin; - frame_support::decl_event! { - pub enum Event { - A, - } + #[pallet::event] + pub enum Event { + A, } - frame_support::decl_error! { - pub enum Error for Module { - Something - } + #[pallet::error] + pub enum Error { + Something, } - frame_support::decl_storage! { - trait Store for Module as Module {} - add_extra_genesis { - build(|_config| {}) - } + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + #[serde(skip)] + pub _config: sp_std::marker::PhantomData, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) {} } } } +#[frame_support::pallet(dev_mode)] pub mod module3 { use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; - pub trait Config: system::Config {} + #[pallet::pallet] + pub struct Pallet(_); - frame_support::decl_module! { - pub struct Module for enum Call - where origin: ::RuntimeOrigin, system=system - { - #[weight = 0] - pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { - Err(Error::::Something.into()) - } - #[weight = 0] - pub fn aux_1(_origin, #[compact] _data: u32) -> frame_support::dispatch::DispatchResult { - unreachable!() - } - #[weight = 0] - pub fn aux_2(_origin, _data: i32, #[compact] _data2: u32) -> frame_support::dispatch::DispatchResult { - unreachable!() - } - #[weight = 0] - fn aux_3(_origin, _data: i32, _data2: String) -> frame_support::dispatch::DispatchResult { - unreachable!() - } - #[weight = 3] - fn aux_4(_origin) -> frame_support::dispatch::DispatchResult { unreachable!() } - #[weight = (5, frame_support::dispatch::DispatchClass::Operational)] - fn operational(_origin) { unreachable!() } + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::call] + impl Pallet { + pub fn fail(_origin: OriginFor) -> DispatchResult { + Err(Error::::Something.into()) + } + pub fn aux_1(_origin: OriginFor, #[pallet::compact] _data: u32) -> DispatchResult { + unreachable!() + } + pub fn aux_2( + _origin: OriginFor, + _data: i32, + #[pallet::compact] _data2: u32, + ) -> DispatchResult { + unreachable!() + } + #[pallet::weight(0)] + pub fn aux_3(_origin: OriginFor, _data: i32, _data2: String) -> DispatchResult { + unreachable!() + } + #[pallet::weight(3)] + pub fn aux_4(_origin: OriginFor) -> DispatchResult { + unreachable!() + } + #[pallet::weight((5, DispatchClass::Operational))] + pub fn operational(_origin: OriginFor) -> DispatchResult { + unreachable!() } } - #[derive( - Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, - )] - pub struct Origin(pub core::marker::PhantomData); + #[pallet::origin] + #[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + pub struct Origin(pub PhantomData); - frame_support::decl_event! { - pub enum Event { - A, - } + #[pallet::event] + pub enum Event { + A, } - frame_support::decl_error! { - pub enum Error for Module { - Something - } + #[pallet::error] + pub enum Error { + Something, } - frame_support::decl_storage! { - trait Store for Module as Module {} - add_extra_genesis { - build(|_config| {}) - } + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + #[serde(skip)] + pub _config: sp_std::marker::PhantomData, } -} -impl module1::Config for Runtime {} -impl module2::Config for Runtime {} -impl nested::module3::Config for Runtime {} -impl module3::Config for Runtime {} + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) {} + } +} +pub type BlockNumber = u64; pub type Signature = sr25519::Signature; pub type AccountId = ::Signer; -pub type BlockNumber = u64; -pub type Index = u64; - -fn test_pub() -> AccountId { - AccountId::from_raw([0; 32]) -} - -impl system::Config for Runtime { - type BaseCallFilter = frame_support::traits::Everything; - type Hash = H256; - type RuntimeOrigin = RuntimeOrigin; - type BlockNumber = BlockNumber; - type AccountId = AccountId; - type RuntimeEvent = RuntimeEvent; - type PalletInfo = PalletInfo; - type RuntimeCall = RuntimeCall; - type DbWeight = (); -} +pub type Header = generic::Header; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type Block = generic::Block; frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { - System: system::{Pallet, Call, Event, Origin} = 30, + System: frame_system::{Pallet, Call, Event, Origin} = 30, Module1_1: module1::::{Pallet, Call, Storage, Event, Origin}, - Module2: module2::{Pallet, Call, Storage, Event, Origin}, + Module2: module2::{Pallet, Call, Storage, Event, Origin}, Module1_2: module1::::{Pallet, Call, Storage, Event, Origin}, - NestedModule3: nested::module3::{Pallet, Call, Config, Storage, Event, Origin}, - Module3: self::module3::{Pallet, Call, Config, Storage, Event, Origin}, - Module1_3: module1::::{Pallet, Storage} = 6, - Module1_4: module1::::{Pallet, Call} = 3, + NestedModule3: nested::module3::{Pallet, Call, Config, Storage, Event, Origin}, + Module3: self::module3::{Pallet, Call, Config, Storage, Event, Origin}, + Module1_3: module1::::{Pallet, Storage, Event } = 6, + Module1_4: module1::::{Pallet, Call, Event } = 3, Module1_5: module1::::{Pallet, Event}, Module1_6: module1::::{Pallet, Call, Storage, Event, Origin} = 1, Module1_7: module1::::{Pallet, Call, Storage, Event, Origin}, @@ -275,15 +275,66 @@ frame_support::construct_runtime!( } ); -pub type Header = generic::Header; -pub type Block = generic::Block; -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type AccountId = AccountId; + type Lookup = sp_runtime::traits::IdentityLookup; + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type OnSetCode = (); + type Block = Block; + type BlockHashCount = ConstU64<10>; +} + +impl module1::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl module1::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl module1::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl module1::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl module1::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl module1::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl module1::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl module1::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl module1::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl module2::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl nested::module3::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl module3::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + +fn test_pub() -> AccountId { + AccountId::from_raw([0; 32]) +} #[test] fn check_modules_error_type() { sp_io::TestExternalities::default().execute_with(|| { assert_eq!( - Module1_1::fail(system::Origin::::Root.into()), + Module1_1::fail(frame_system::Origin::::Root.into()), Err(DispatchError::Module(ModuleError { index: 31, error: [0; 4], @@ -291,7 +342,7 @@ fn check_modules_error_type() { })), ); assert_eq!( - Module2::fail(system::Origin::::Root.into()), + Module2::fail(frame_system::Origin::::Root.into()), Err(DispatchError::Module(ModuleError { index: 32, error: [0; 4], @@ -299,7 +350,7 @@ fn check_modules_error_type() { })), ); assert_eq!( - Module1_2::fail(system::Origin::::Root.into()), + Module1_2::fail(frame_system::Origin::::Root.into()), Err(DispatchError::Module(ModuleError { index: 33, error: [0; 4], @@ -307,7 +358,7 @@ fn check_modules_error_type() { })), ); assert_eq!( - NestedModule3::fail(system::Origin::::Root.into()), + NestedModule3::fail(frame_system::Origin::::Root.into()), Err(DispatchError::Module(ModuleError { index: 34, error: [0; 4], @@ -315,7 +366,7 @@ fn check_modules_error_type() { })), ); assert_eq!( - Module1_3::fail(system::Origin::::Root.into()), + Module1_3::fail(frame_system::Origin::::Root.into()), Err(DispatchError::Module(ModuleError { index: 6, error: [0; 4], @@ -323,7 +374,7 @@ fn check_modules_error_type() { })), ); assert_eq!( - Module1_4::fail(system::Origin::::Root.into()), + Module1_4::fail(frame_system::Origin::::Root.into()), Err(DispatchError::Module(ModuleError { index: 3, error: [0; 4], @@ -331,7 +382,7 @@ fn check_modules_error_type() { })), ); assert_eq!( - Module1_5::fail(system::Origin::::Root.into()), + Module1_5::fail(frame_system::Origin::::Root.into()), Err(DispatchError::Module(ModuleError { index: 4, error: [0; 4], @@ -339,7 +390,7 @@ fn check_modules_error_type() { })), ); assert_eq!( - Module1_6::fail(system::Origin::::Root.into()), + Module1_6::fail(frame_system::Origin::::Root.into()), Err(DispatchError::Module(ModuleError { index: 1, error: [0; 4], @@ -347,7 +398,7 @@ fn check_modules_error_type() { })), ); assert_eq!( - Module1_7::fail(system::Origin::::Root.into()), + Module1_7::fail(frame_system::Origin::::Root.into()), Err(DispatchError::Module(ModuleError { index: 2, error: [0; 4], @@ -355,7 +406,7 @@ fn check_modules_error_type() { })), ); assert_eq!( - Module1_8::fail(system::Origin::::Root.into()), + Module1_8::fail(frame_system::Origin::::Root.into()), Err(DispatchError::Module(ModuleError { index: 12, error: [0; 4], @@ -363,7 +414,7 @@ fn check_modules_error_type() { })), ); assert_eq!( - Module1_9::fail(system::Origin::::Root.into()), + Module1_9::fail(frame_system::Origin::::Root.into()), Err(DispatchError::Module(ModuleError { index: 13, error: [0; 4], @@ -383,7 +434,7 @@ fn integrity_test_works() { fn origin_codec() { use codec::Encode; - let origin = OriginCaller::system(system::RawOrigin::None); + let origin = OriginCaller::system(frame_system::RawOrigin::None); assert_eq!(origin.encode()[0], 30); let origin = OriginCaller::Module1_1(module1::Origin(Default::default())); @@ -418,7 +469,8 @@ fn origin_codec() { fn event_codec() { use codec::Encode; - let event = system::Event::::ExtrinsicSuccess; + let event = + frame_system::Event::::ExtrinsicSuccess { dispatch_info: Default::default() }; assert_eq!(RuntimeEvent::from(event).encode()[0], 30); let event = module1::Event::::A(test_pub()); @@ -455,7 +507,7 @@ fn event_codec() { #[test] fn call_codec() { use codec::Encode; - assert_eq!(RuntimeCall::System(system::Call::noop {}).encode()[0], 30); + assert_eq!(RuntimeCall::System(frame_system::Call::remark { remark: vec![1] }).encode()[0], 30); assert_eq!(RuntimeCall::Module1_1(module1::Call::fail {}).encode()[0], 31); assert_eq!(RuntimeCall::Module2(module2::Call::fail {}).encode()[0], 32); assert_eq!(RuntimeCall::Module1_2(module1::Call::fail {}).encode()[0], 33); @@ -471,12 +523,12 @@ fn call_codec() { #[test] fn call_compact_attr() { use codec::Encode; - let call: module3::Call = module3::Call::aux_1 { _data: 1 }; + let call: module3::Call = module3::Call::aux_1 { data: 1 }; let encoded = call.encode(); assert_eq!(2, encoded.len()); assert_eq!(vec![1, 4], encoded); - let call: module3::Call = module3::Call::aux_2 { _data: 1, _data2: 2 }; + let call: module3::Call = module3::Call::aux_2 { data: 1, data2: 2 }; let encoded = call.encode(); assert_eq!(6, encoded.len()); assert_eq!(vec![2, 1, 0, 0, 0, 8], encoded); @@ -491,7 +543,7 @@ fn call_encode_is_correct_and_decode_works() { let decoded = module3::Call::::decode(&mut &encoded[..]).unwrap(); assert_eq!(decoded, call); - let call: module3::Call = module3::Call::aux_3 { _data: 32, _data2: "hello".into() }; + let call: module3::Call = module3::Call::aux_3 { data: 32, data2: "hello".into() }; let encoded = call.encode(); assert_eq!(vec![3, 32, 0, 0, 0, 20, 104, 101, 108, 108, 111], encoded); let decoded = module3::Call::::decode(&mut &encoded[..]).unwrap(); @@ -508,7 +560,7 @@ fn call_weight_should_attach_to_call_enum() { assert_eq!( module3::Call::::operational {}.get_dispatch_info(), DispatchInfo { - weight: Weight::from_ref_time(5), + weight: Weight::from_parts(5, 0), class: DispatchClass::Operational, pays_fee: Pays::Yes }, @@ -517,7 +569,7 @@ fn call_weight_should_attach_to_call_enum() { assert_eq!( module3::Call::::aux_4 {}.get_dispatch_info(), DispatchInfo { - weight: Weight::from_ref_time(3), + weight: Weight::from_parts(3, 0), class: DispatchClass::Normal, pays_fee: Pays::Yes }, @@ -584,80 +636,132 @@ fn call_subtype_conversion() { #[test] fn test_metadata() { - use frame_support::metadata::*; + use frame_support::metadata::{v14::*, *}; use scale_info::meta_type; + use sp_core::Encode; + + fn maybe_docs(doc: Vec<&'static str>) -> Vec<&'static str> { + if cfg!(feature = "no-metadata-docs") { + vec![] + } else { + doc + } + } let pallets = vec![ PalletMetadata { name: "System", storage: None, - calls: Some(meta_type::>().into()), - event: Some(meta_type::>().into()), - constants: vec![], - error: None, + calls: Some(meta_type::>().into()), + event: Some(meta_type::>().into()), + constants: vec![ + PalletConstantMetadata { + name: "BlockWeights", + ty: meta_type::(), + value: BlockWeights::default().encode(), + docs: maybe_docs(vec![" Block & extrinsics weights: base values and limits."]), + }, + PalletConstantMetadata { + name: "BlockLength", + ty: meta_type::(), + value: BlockLength::default().encode(), + docs: maybe_docs(vec![" The maximum length of a block (in bytes)."]), + }, + PalletConstantMetadata { + name: "BlockHashCount", + ty: meta_type::(), + value: 10u64.encode(), + docs: maybe_docs(vec![" Maximum number of block number to block hash mappings to keep (oldest pruned first)."]), + }, + PalletConstantMetadata { + name: "DbWeight", + ty: meta_type::(), + value: RuntimeDbWeight::default().encode(), + docs: maybe_docs(vec![" The weight of runtime database operations the runtime can invoke.",]), + }, + PalletConstantMetadata { + name: "Version", + ty: meta_type::(), + value: RuntimeVersion::default().encode(), + docs: maybe_docs(vec![ " Get the chain's current version."]), + }, + PalletConstantMetadata { + name: "SS58Prefix", + ty: meta_type::(), + value: 0u16.encode(), + docs: maybe_docs(vec![ + " The designated SS58 prefix of this chain.", + "", + " This replaces the \"ss58Format\" property declared in the chain spec. Reason is", + " that the runtime should know about the prefix in order to make use of it as", + " an identifier of the chain.", + ]), + }, + ], + error: Some(meta_type::>().into()), index: 30, }, PalletMetadata { name: "Module1_1", - storage: Some(PalletStorageMetadata { prefix: "Instance1Module", entries: vec![] }), + storage: Some(PalletStorageMetadata { prefix: "Module1_1", entries: vec![] }), calls: Some(meta_type::>().into()), event: Some(meta_type::>().into()), constants: vec![], - error: None, + error: Some(meta_type::>().into()), index: 31, }, PalletMetadata { name: "Module2", - storage: Some(PalletStorageMetadata { prefix: "Module", entries: vec![] }), + storage: Some(PalletStorageMetadata { prefix: "Module2", entries: vec![] }), calls: Some(meta_type::>().into()), - event: Some(meta_type::().into()), + event: Some(meta_type::>().into()), constants: vec![], - error: None, + error: Some(meta_type::>().into()), index: 32, }, PalletMetadata { name: "Module1_2", - storage: Some(PalletStorageMetadata { prefix: "Instance2Module", entries: vec![] }), + storage: Some(PalletStorageMetadata { prefix: "Module1_2", entries: vec![] }), calls: Some(meta_type::>().into()), event: Some(meta_type::>().into()), constants: vec![], - error: None, + error: Some(meta_type::>().into()), index: 33, }, PalletMetadata { name: "NestedModule3", - storage: Some(PalletStorageMetadata { prefix: "Module", entries: vec![] }), + storage: Some(PalletStorageMetadata { prefix: "NestedModule3", entries: vec![] }), calls: Some(meta_type::>().into()), - event: Some(meta_type::().into()), + event: Some(meta_type::>().into()), constants: vec![], - error: None, + error: Some(meta_type::>().into()), index: 34, }, PalletMetadata { name: "Module3", - storage: Some(PalletStorageMetadata { prefix: "Module", entries: vec![] }), + storage: Some(PalletStorageMetadata { prefix: "Module3", entries: vec![] }), calls: Some(meta_type::>().into()), - event: Some(meta_type::().into()), + event: Some(meta_type::>().into()), constants: vec![], - error: None, + error: Some(meta_type::>().into()), index: 35, }, PalletMetadata { name: "Module1_3", - storage: Some(PalletStorageMetadata { prefix: "Instance3Module", entries: vec![] }), + storage: Some(PalletStorageMetadata { prefix: "Module1_3", entries: vec![] }), calls: None, - event: None, + event: Some(meta_type::>().into()), constants: vec![], - error: None, + error: Some(meta_type::>().into()), index: 6, }, PalletMetadata { name: "Module1_4", storage: None, calls: Some(meta_type::>().into()), - event: None, + event: Some(meta_type::>().into()), constants: vec![], - error: None, + error: Some(meta_type::>().into()), index: 3, }, PalletMetadata { @@ -666,45 +770,43 @@ fn test_metadata() { calls: None, event: Some(meta_type::>().into()), constants: vec![], - error: None, + error: Some(meta_type::>().into()), index: 4, }, PalletMetadata { name: "Module1_6", - storage: Some(PalletStorageMetadata { prefix: "Instance6Module", entries: vec![] }), + storage: Some(PalletStorageMetadata { prefix: "Module1_6", entries: vec![] }), calls: Some(meta_type::>().into()), event: Some(meta_type::>().into()), constants: vec![], - error: None, + error: Some(meta_type::>().into()), index: 1, }, PalletMetadata { name: "Module1_7", - storage: Some(PalletStorageMetadata { prefix: "Instance7Module", entries: vec![] }), + storage: Some(PalletStorageMetadata { prefix: "Module1_7", entries: vec![] }), calls: Some(meta_type::>().into()), - event: Some(PalletEventMetadata { - ty: meta_type::>(), - }), + event: Some(meta_type::>().into()), constants: vec![], - error: None, + error: Some(meta_type::>().into()), index: 2, }, PalletMetadata { name: "Module1_8", - storage: Some(PalletStorageMetadata { prefix: "Instance8Module", entries: vec![] }), + storage: Some(PalletStorageMetadata { prefix: "Module1_8", entries: vec![] }), calls: Some(meta_type::>().into()), event: Some(meta_type::>().into()), constants: vec![], - error: None, + error: Some(meta_type::>().into()), index: 12, }, PalletMetadata { name: "Module1_9", - storage: Some(PalletStorageMetadata { prefix: "Instance9Module", entries: vec![] }), + storage: Some(PalletStorageMetadata { prefix: "Module1_9", entries: vec![] }), calls: Some(meta_type::>().into()), event: Some(meta_type::>().into()), constants: vec![], - error: None, + error: Some(meta_type::>().into()), index: 13, }, ]; @@ -722,6 +824,7 @@ fn test_metadata() { let expected_metadata: RuntimeMetadataPrefixed = RuntimeMetadataLastVersion::new(pallets, extrinsic, meta_type::()).into(); let actual_metadata = Runtime::metadata(); + pretty_assertions::assert_eq!(actual_metadata, expected_metadata); } @@ -729,7 +832,7 @@ fn test_metadata() { fn pallet_in_runtime_is_correct() { assert_eq!(PalletInfo::index::().unwrap(), 30); assert_eq!(PalletInfo::name::().unwrap(), "System"); - assert_eq!(PalletInfo::module_name::().unwrap(), "system"); + assert_eq!(PalletInfo::module_name::().unwrap(), "frame_system"); assert!(PalletInfo::crate_version::().is_some()); assert_eq!(PalletInfo::index::().unwrap(), 31); diff --git a/frame/support/test/tests/construct_runtime_ui.rs b/frame/support/test/tests/construct_runtime_ui.rs index 42fd87ca95c0..ec6758f4b295 100644 --- a/frame/support/test/tests/construct_runtime_ui.rs +++ b/frame/support/test/tests/construct_runtime_ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/test/tests/construct_runtime_ui/abundant_where_param.rs b/frame/support/test/tests/construct_runtime_ui/abundant_where_param.rs index d5e9f225219d..ab55c22e9fbf 100644 --- a/frame/support/test/tests/construct_runtime_ui/abundant_where_param.rs +++ b/frame/support/test/tests/construct_runtime_ui/abundant_where_param.rs @@ -1,7 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where + pub struct Runtime where Block = Block, NodeBlock = Block, Block = Block1, diff --git a/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.rs b/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.rs index 7a074db9986a..4cb249714650 100644 --- a/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.rs +++ b/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.rs @@ -20,12 +20,9 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic}, + System: system::{Pallet, Call, Storage, Config, Event}, Pallet: pallet exclude_parts { Pallet } use_parts { Pallet }, } } diff --git a/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.stderr b/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.stderr index 5f1fccd43c54..1ea62b7d6fd6 100644 --- a/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.stderr +++ b/frame/support/test/tests/construct_runtime_ui/both_use_and_excluded_parts.stderr @@ -1,16 +1,19 @@ error: Unexpected tokens, expected one of `=`, `,` - --> tests/construct_runtime_ui/both_use_and_excluded_parts.rs:29:43 + --> tests/construct_runtime_ui/both_use_and_excluded_parts.rs:26:43 | -29 | Pallet: pallet exclude_parts { Pallet } use_parts { Pallet }, +26 | Pallet: pallet exclude_parts { Pallet } use_parts { Pallet }, | ^^^^^^^^^ error[E0412]: cannot find type `RuntimeCall` in this scope --> tests/construct_runtime_ui/both_use_and_excluded_parts.rs:18:64 | 18 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; - | - ^^^^^^^^^^^ not found in this scope - | | - | help: you might be missing a type parameter: `` + | ^^^^^^^^^^^ not found in this scope + | +help: you might be missing a type parameter + | +18 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + | +++++++++++++ error[E0412]: cannot find type `Runtime` in this scope --> tests/construct_runtime_ui/both_use_and_excluded_parts.rs:20:25 diff --git a/frame/support/test/tests/construct_runtime_ui/conflicting_index.rs b/frame/support/test/tests/construct_runtime_ui/conflicting_index.rs index a48b4bd09703..712452d1a3a4 100644 --- a/frame/support/test/tests/construct_runtime_ui/conflicting_index.rs +++ b/frame/support/test/tests/construct_runtime_ui/conflicting_index.rs @@ -1,7 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where + pub struct Runtime where UncheckedExtrinsic = UncheckedExtrinsic, Block = Block, NodeBlock = Block, diff --git a/frame/support/test/tests/construct_runtime_ui/conflicting_index_2.rs b/frame/support/test/tests/construct_runtime_ui/conflicting_index_2.rs index c949cb41a23f..6bc6bc5402e9 100644 --- a/frame/support/test/tests/construct_runtime_ui/conflicting_index_2.rs +++ b/frame/support/test/tests/construct_runtime_ui/conflicting_index_2.rs @@ -1,7 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where + pub struct Runtime where UncheckedExtrinsic = UncheckedExtrinsic, Block = Block, NodeBlock = Block, diff --git a/frame/support/test/tests/construct_runtime_ui/conflicting_module_name.rs b/frame/support/test/tests/construct_runtime_ui/conflicting_module_name.rs index 7cc6cbd6bd6e..513fbcfb5135 100644 --- a/frame/support/test/tests/construct_runtime_ui/conflicting_module_name.rs +++ b/frame/support/test/tests/construct_runtime_ui/conflicting_module_name.rs @@ -1,10 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { System: system::{Pallet}, Balance: balances::{Pallet}, diff --git a/frame/support/test/tests/construct_runtime_ui/conflicting_module_name.stderr b/frame/support/test/tests/construct_runtime_ui/conflicting_module_name.stderr index 27c5644e0d73..6fb983f03a96 100644 --- a/frame/support/test/tests/construct_runtime_ui/conflicting_module_name.stderr +++ b/frame/support/test/tests/construct_runtime_ui/conflicting_module_name.stderr @@ -1,11 +1,11 @@ error: Two pallets with the same name! - --> $DIR/conflicting_module_name.rs:10:3 - | -10 | Balance: balances::{Pallet}, - | ^^^^^^^ + --> tests/construct_runtime_ui/conflicting_module_name.rs:7:3 + | +7 | Balance: balances::{Pallet}, + | ^^^^^^^ error: Two pallets with the same name! - --> $DIR/conflicting_module_name.rs:11:3 - | -11 | Balance: balances::{Pallet}, - | ^^^^^^^ + --> tests/construct_runtime_ui/conflicting_module_name.rs:8:3 + | +8 | Balance: balances::{Pallet}, + | ^^^^^^^ diff --git a/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.rs b/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.rs new file mode 100644 index 000000000000..c0e325085b5e --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.rs @@ -0,0 +1,13 @@ +use frame_support::construct_runtime; + +construct_runtime! { + pub struct Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = Uxt, + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr b/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr new file mode 100644 index 000000000000..946277e9068e --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr @@ -0,0 +1,442 @@ +error: use of deprecated constant `WhereSection::_w`: + It is deprecated to use a `where` clause in `construct_runtime`. + Please instead use `frame_system::Config` to set the `Block` type and delete this clause. + It is planned to be removed in December 2023. + + For more info see: + + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | / construct_runtime! { +4 | | pub struct Runtime where +5 | | Block = Block, +6 | | NodeBlock = Block, +... | +10 | | } +11 | | } + | |_^ + | + = note: `-D deprecated` implied by `-D warnings` + = note: this error originates in the macro `frame_support::match_and_insert` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | + | +note: required by a bound in `frame_system::Event` + --> $WORKSPACE/frame/system/src/lib.rs + | + | pub enum Event { + | ^^^^^^ required by this bound in `Event` + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | + | +note: required because it appears within the type `RuntimeEvent` + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | +note: required by a bound in `Clone` + --> $RUST/core/src/clone.rs + | + | pub trait Clone: Sized { + | ^^^^^ required by this bound in `Clone` + = note: this error originates in the derive macro `Clone` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | + | +note: required because it appears within the type `RuntimeEvent` + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | +note: required by a bound in `EncodeLike` + --> $CARGO/parity-scale-codec-3.6.1/src/encode_like.rs + | + | pub trait EncodeLike: Sized + Encode {} + | ^^^^^ required by this bound in `EncodeLike` + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | + | +note: required because it appears within the type `RuntimeEvent` + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | +note: required by a bound in `Decode` + --> $CARGO/parity-scale-codec-3.6.1/src/codec.rs + | + | pub trait Decode: Sized { + | ^^^^^ required by this bound in `Decode` + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied in `frame_system::Event` + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | + | + = note: required because it appears within the type `Event` +note: required by a bound in `From` + --> $RUST/core/src/convert/mod.rs + | + | pub trait From: Sized { + | ^ required by this bound in `From` + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied in `frame_system::Event` + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | + | + = note: required because it appears within the type `Event` +note: required by a bound in `TryInto` + --> $RUST/core/src/convert/mod.rs + | + | pub trait TryInto: Sized { + | ^ required by this bound in `TryInto` + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | + | + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | construct_runtime! { + | ^ the trait `Config` is not implemented for `Runtime` + | + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `RawOrigin<_>: TryFrom` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | + | + = help: the trait `TryFrom` is implemented for `RawOrigin<::AccountId>` + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | + | + = help: the trait `Callable` is implemented for `Pallet` + = note: required for `Pallet` to implement `Callable` + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | + | + = note: required for `Pallet` to implement `Callable` +note: required because it appears within the type `RuntimeCall` + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | +note: required by a bound in `Clone` + --> $RUST/core/src/clone.rs + | + | pub trait Clone: Sized { + | ^^^^^ required by this bound in `Clone` + = note: this error originates in the derive macro `Clone` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | + | + = note: required for `Pallet` to implement `Callable` +note: required because it appears within the type `RuntimeCall` + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | +note: required by a bound in `EncodeLike` + --> $CARGO/parity-scale-codec-3.6.1/src/encode_like.rs + | + | pub trait EncodeLike: Sized + Encode {} + | ^^^^^ required by this bound in `EncodeLike` + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | + | + = note: required for `Pallet` to implement `Callable` +note: required because it appears within the type `RuntimeCall` + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | +note: required by a bound in `Decode` + --> $CARGO/parity-scale-codec-3.6.1/src/codec.rs + | + | pub trait Decode: Sized { + | ^^^^^ required by this bound in `Decode` + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:9:3 + | +9 | System: frame_system::{Pallet, Call, Storage, Config, Event}, + | ^^^^^^ the trait `Config` is not implemented for `Runtime` + | +note: required by a bound in `frame_system::GenesisConfig` + --> $WORKSPACE/frame/system/src/lib.rs + | + | pub struct GenesisConfig { + | ^^^^^^ required by this bound in `GenesisConfig` + +error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | + | +note: required because it appears within the type `RuntimeEvent` + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | +note: required by a bound in `Result` + --> $RUST/core/src/result.rs + | + | pub enum Result { + | ^ required by this bound in `Result` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::codec::Decode` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied in `RuntimeEvent` + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | + | +note: required because it appears within the type `RuntimeEvent` + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | +note: required by a bound in `TryInto` + --> $RUST/core/src/convert/mod.rs + | + | pub trait TryInto: Sized { + | ^^^^^ required by this bound in `TryInto` + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `Runtime: Config` is not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | + | + = note: required for `Pallet` to implement `Callable` +note: required because it appears within the type `RuntimeCall` + --> tests/construct_runtime_ui/deprecated_where_block.rs:3:1 + | +3 | // construct_runtime! { +4 | || pub struct Runtime where +5 | || Block = Block, +6 | || NodeBlock = Block, +... || +10 | || } +11 | || } + | ||_- in this macro invocation +... | +note: required by a bound in `Result` + --> $RUST/core/src/result.rs + | + | pub enum Result { + | ^ required by this bound in `Result` + = note: this error originates in the derive macro `self::sp_api_hidden_includes_construct_runtime::hidden_include::codec::Decode` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/construct_runtime_ui/double_module_parts.rs b/frame/support/test/tests/construct_runtime_ui/double_module_parts.rs index 836af597851d..68a2523d3bcb 100644 --- a/frame/support/test/tests/construct_runtime_ui/double_module_parts.rs +++ b/frame/support/test/tests/construct_runtime_ui/double_module_parts.rs @@ -1,10 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { System: system::{Pallet}, Balance: balances::{Config, Call, Config, Origin}, diff --git a/frame/support/test/tests/construct_runtime_ui/double_module_parts.stderr b/frame/support/test/tests/construct_runtime_ui/double_module_parts.stderr index 9d10474ce85a..e3f694781441 100644 --- a/frame/support/test/tests/construct_runtime_ui/double_module_parts.stderr +++ b/frame/support/test/tests/construct_runtime_ui/double_module_parts.stderr @@ -1,5 +1,5 @@ error: `Config` was already declared before. Please remove the duplicate declaration - --> $DIR/double_module_parts.rs:10:37 - | -10 | Balance: balances::{Config, Call, Config, Origin}, - | ^^^^^^ + --> tests/construct_runtime_ui/double_module_parts.rs:7:37 + | +7 | Balance: balances::{Config, Call, Config, Origin}, + | ^^^^^^ diff --git a/frame/support/test/tests/construct_runtime_ui/duplicate_exclude.rs b/frame/support/test/tests/construct_runtime_ui/duplicate_exclude.rs index 6d21c2a6e170..83e708841aaf 100644 --- a/frame/support/test/tests/construct_runtime_ui/duplicate_exclude.rs +++ b/frame/support/test/tests/construct_runtime_ui/duplicate_exclude.rs @@ -1,7 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where + pub struct Runtime where UncheckedExtrinsic = UncheckedExtrinsic, Block = Block, NodeBlock = Block, diff --git a/frame/support/test/tests/construct_runtime_ui/empty_pallet_path.rs b/frame/support/test/tests/construct_runtime_ui/empty_pallet_path.rs index bc6abfa82b9c..23badd76276e 100644 --- a/frame/support/test/tests/construct_runtime_ui/empty_pallet_path.rs +++ b/frame/support/test/tests/construct_runtime_ui/empty_pallet_path.rs @@ -1,10 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { system: , } diff --git a/frame/support/test/tests/construct_runtime_ui/empty_pallet_path.stderr b/frame/support/test/tests/construct_runtime_ui/empty_pallet_path.stderr index 7102076e5acb..f0c0f17779d6 100644 --- a/frame/support/test/tests/construct_runtime_ui/empty_pallet_path.stderr +++ b/frame/support/test/tests/construct_runtime_ui/empty_pallet_path.stderr @@ -1,5 +1,5 @@ error: expected one of: `crate`, `self`, `super`, identifier - --> $DIR/empty_pallet_path.rs:9:11 + --> tests/construct_runtime_ui/empty_pallet_path.rs:6:11 | -9 | system: , +6 | system: , | ^ diff --git a/frame/support/test/tests/construct_runtime_ui/exclude_missspell.rs b/frame/support/test/tests/construct_runtime_ui/exclude_missspell.rs index 16cbf1e82cf8..441e9c75040c 100644 --- a/frame/support/test/tests/construct_runtime_ui/exclude_missspell.rs +++ b/frame/support/test/tests/construct_runtime_ui/exclude_missspell.rs @@ -1,7 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where + pub struct Runtime where UncheckedExtrinsic = UncheckedExtrinsic, Block = Block, NodeBlock = Block, diff --git a/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.rs b/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.rs index 302b5af7ecd2..10cda7b4e7e8 100644 --- a/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.rs @@ -25,12 +25,9 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic}, + System: system::{Pallet, Call, Storage, Config, Event}, Pallet: pallet exclude_parts { Call }, } } diff --git a/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.stderr b/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.stderr index c623ecfbf4cd..4b85613838ab 100644 --- a/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/exclude_undefined_part.stderr @@ -1,16 +1,19 @@ error: Invalid pallet part specified, the pallet `Pallet` doesn't have the `Call` part. Available parts are: `Pallet`, `Storage`. - --> tests/construct_runtime_ui/exclude_undefined_part.rs:34:34 + --> tests/construct_runtime_ui/exclude_undefined_part.rs:31:34 | -34 | Pallet: pallet exclude_parts { Call }, +31 | Pallet: pallet exclude_parts { Call }, | ^^^^ error[E0412]: cannot find type `RuntimeCall` in this scope --> tests/construct_runtime_ui/exclude_undefined_part.rs:23:64 | 23 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; - | - ^^^^^^^^^^^ not found in this scope - | | - | help: you might be missing a type parameter: `` + | ^^^^^^^^^^^ not found in this scope + | +help: you might be missing a type parameter + | +23 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + | +++++++++++++ error[E0412]: cannot find type `Runtime` in this scope --> tests/construct_runtime_ui/exclude_undefined_part.rs:25:25 diff --git a/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.rs b/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.rs index 79b5632babd9..35d49a4d8a23 100644 --- a/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.rs +++ b/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.rs @@ -1,13 +1,10 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { #[cfg(test)] - System: frame_system::{Pallet, Call, Storage, Config, Event}, + System: frame_system::{Pallet, Call, Storage, Config, Event}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.stderr b/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.stderr index a86a839615aa..6a6c4b415888 100644 --- a/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.stderr +++ b/frame/support/test/tests/construct_runtime_ui/feature_gated_system_pallet.stderr @@ -1,5 +1,5 @@ error: `System` pallet declaration is feature gated, please remove any `#[cfg]` attributes - --> tests/construct_runtime_ui/feature_gated_system_pallet.rs:10:3 - | -10 | System: frame_system::{Pallet, Call, Storage, Config, Event}, - | ^^^^^^ + --> tests/construct_runtime_ui/feature_gated_system_pallet.rs:7:3 + | +7 | System: frame_system::{Pallet, Call, Storage, Config, Event}, + | ^^^^^^ diff --git a/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.rs b/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.rs index b3f0d340d671..1ad1f8e0b1d5 100644 --- a/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.rs +++ b/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.rs @@ -1,10 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { System: system::{Pallet}, Balance: balances::::{Call, Origin}, diff --git a/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.stderr b/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.stderr index 06caa036b91f..a6adb37d0494 100644 --- a/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.stderr +++ b/frame/support/test/tests/construct_runtime_ui/generics_in_invalid_module.stderr @@ -1,5 +1,5 @@ -error: `Call` is not allowed to have generics. Only the following pallets are allowed to have generics: `Event`, `Origin`, `Config`. - --> $DIR/generics_in_invalid_module.rs:10:36 - | -10 | Balance: balances::::{Call, Origin}, - | ^^^^ +error: `Call` is not allowed to have generics. Only the following pallets are allowed to have generics: `Event`, `Error`, `Origin`, `Config`. + --> tests/construct_runtime_ui/generics_in_invalid_module.rs:7:36 + | +7 | Balance: balances::::{Call, Origin}, + | ^^^^ diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.rs b/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.rs index a1d39fa76ea8..bce87c51336e 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.rs +++ b/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.rs @@ -1,10 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { System: system::{Pallet}, #[cfg(feature = 1)] diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.stderr index 68366a3410bf..bfee2910cd2a 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_meta_literal.stderr @@ -1,6 +1,6 @@ error: feature = 1 ^ expected one of ``, `all`, `any`, `not` here - --> tests/construct_runtime_ui/invalid_meta_literal.rs:10:3 - | -10 | #[cfg(feature = 1)] - | ^ + --> tests/construct_runtime_ui/invalid_meta_literal.rs:7:3 + | +7 | #[cfg(feature = 1)] + | ^ diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_module_details.rs b/frame/support/test/tests/construct_runtime_ui/invalid_module_details.rs index 336e27e91517..bf6919f5a58e 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_module_details.rs +++ b/frame/support/test/tests/construct_runtime_ui/invalid_module_details.rs @@ -1,10 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { system: System::(), } diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_module_details.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_module_details.stderr index 0a20cf4e39a8..1f9277c3f0a8 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_module_details.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_module_details.stderr @@ -1,5 +1,5 @@ error: Unexpected tokens, expected one of `::$ident` `::{`, `exclude_parts`, `use_parts`, `=`, `,` - --> tests/construct_runtime_ui/invalid_module_details.rs:9:17 + --> tests/construct_runtime_ui/invalid_module_details.rs:6:17 | -9 | system: System::(), +6 | system: System::(), | ^ diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.rs b/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.rs index 0891483c9211..51f14e6883e4 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.rs +++ b/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.rs @@ -1,10 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { system: System::{enum}, } diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr index 29df6e4bd8cb..dfcc9b8be42c 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_module_details_keyword.stderr @@ -1,5 +1,5 @@ -error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned` - --> $DIR/invalid_module_details_keyword.rs:9:20 +error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Error`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `FreezeReason`, `HoldReason`, `LockId`, `SlashReason` + --> tests/construct_runtime_ui/invalid_module_details_keyword.rs:6:20 | -9 | system: System::{enum}, +6 | system: System::{enum}, | ^^^^ diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.rs b/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.rs index e7d32559a6cc..607741d7823d 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.rs +++ b/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.rs @@ -1,13 +1,10 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { System: system::{Pallet}, - Balance: balances::{Error}, + Balance: balances::{Unexpected}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr index bd3e672dc8b4..9dd849ff0412 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_module_entry.stderr @@ -1,5 +1,5 @@ -error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned` - --> $DIR/invalid_module_entry.rs:10:23 - | -10 | Balance: balances::{Error}, - | ^^^^^ +error: expected one of: `Pallet`, `Call`, `Storage`, `Event`, `Error`, `Config`, `Origin`, `Inherent`, `ValidateUnsigned`, `FreezeReason`, `HoldReason`, `LockId`, `SlashReason` + --> tests/construct_runtime_ui/invalid_module_entry.rs:7:23 + | +7 | Balance: balances::{Unexpected}, + | ^^^^^^^^^^ diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_token_after_module.rs b/frame/support/test/tests/construct_runtime_ui/invalid_token_after_module.rs index 448ae913f3f8..c132fa01b229 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_token_after_module.rs +++ b/frame/support/test/tests/construct_runtime_ui/invalid_token_after_module.rs @@ -1,10 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { system: System ? } diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_token_after_module.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_token_after_module.stderr index 6025de82bd20..80be1b8dd42f 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_token_after_module.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_token_after_module.stderr @@ -1,5 +1,5 @@ error: Unexpected tokens, expected one of `::$ident` `::{`, `exclude_parts`, `use_parts`, `=`, `,` - --> $DIR/invalid_token_after_module.rs:9:18 + --> tests/construct_runtime_ui/invalid_token_after_module.rs:6:18 | -9 | system: System ? +6 | system: System ? | ^ diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_token_after_name.rs b/frame/support/test/tests/construct_runtime_ui/invalid_token_after_name.rs index 43538789f111..42e7759f87f2 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_token_after_name.rs +++ b/frame/support/test/tests/construct_runtime_ui/invalid_token_after_name.rs @@ -1,10 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { system ? } diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_token_after_name.stderr b/frame/support/test/tests/construct_runtime_ui/invalid_token_after_name.stderr index eaae082c8460..8988f8a35b0a 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_token_after_name.stderr +++ b/frame/support/test/tests/construct_runtime_ui/invalid_token_after_name.stderr @@ -1,5 +1,5 @@ error: expected `:` - --> $DIR/invalid_token_after_name.rs:9:10 + --> tests/construct_runtime_ui/invalid_token_after_name.rs:6:10 | -9 | system ? +6 | system ? | ^ diff --git a/frame/support/test/tests/construct_runtime_ui/invalid_where_param.rs b/frame/support/test/tests/construct_runtime_ui/invalid_where_param.rs index dc1dc430ed42..091f0644494f 100644 --- a/frame/support/test/tests/construct_runtime_ui/invalid_where_param.rs +++ b/frame/support/test/tests/construct_runtime_ui/invalid_where_param.rs @@ -1,7 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where + pub struct Runtime where Block = Block, NodeBlock = Block, TypeX = Block, diff --git a/frame/support/test/tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.rs b/frame/support/test/tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.rs index f748e643aa18..bc2039c4e818 100644 --- a/frame/support/test/tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.rs +++ b/frame/support/test/tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.rs @@ -1,13 +1,10 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { - System: system::{Pallet}, - Balance: balances::::{Event}, + System: system expanded::{}::{Pallet}, + Balance: balances:: expanded::{}::{Event}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.stderr b/frame/support/test/tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.stderr index b1aa9b86cd0d..30fcba4c710d 100644 --- a/frame/support/test/tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.stderr +++ b/frame/support/test/tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.stderr @@ -1,5 +1,5 @@ error: Instantiable pallet with no generic `Event` cannot be constructed: pallet `Balance` must have generic `Event` - --> $DIR/missing_event_generic_on_module_with_instance.rs:10:3 - | -10 | Balance: balances::::{Event}, - | ^^^^^^^ + --> tests/construct_runtime_ui/missing_event_generic_on_module_with_instance.rs:7:3 + | +7 | Balance: balances:: expanded::{}::{Event}, + | ^^^^^^^ diff --git a/frame/support/test/tests/construct_runtime_ui/missing_module_instance.rs b/frame/support/test/tests/construct_runtime_ui/missing_module_instance.rs index fbc4b60db8b7..afd96a04854f 100644 --- a/frame/support/test/tests/construct_runtime_ui/missing_module_instance.rs +++ b/frame/support/test/tests/construct_runtime_ui/missing_module_instance.rs @@ -1,10 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { system: System::<>, } diff --git a/frame/support/test/tests/construct_runtime_ui/missing_module_instance.stderr b/frame/support/test/tests/construct_runtime_ui/missing_module_instance.stderr index 6303c74e42e5..5072f718db12 100644 --- a/frame/support/test/tests/construct_runtime_ui/missing_module_instance.stderr +++ b/frame/support/test/tests/construct_runtime_ui/missing_module_instance.stderr @@ -1,5 +1,5 @@ error: expected identifier - --> $DIR/missing_module_instance.rs:9:20 + --> tests/construct_runtime_ui/missing_module_instance.rs:6:20 | -9 | system: System::<>, +6 | system: System::<>, | ^ diff --git a/frame/support/test/tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.rs b/frame/support/test/tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.rs index 7053acc18590..42db63ae90a3 100644 --- a/frame/support/test/tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.rs +++ b/frame/support/test/tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.rs @@ -1,13 +1,10 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { - System: system::{Pallet}, - Balance: balances::::{Origin}, + System: system expanded::{}::{Pallet}, + Balance: balances:: expanded::{}::{Origin}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.stderr b/frame/support/test/tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.stderr index 63bb7442a857..6c076d7b49fc 100644 --- a/frame/support/test/tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.stderr +++ b/frame/support/test/tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.stderr @@ -1,5 +1,5 @@ error: Instantiable pallet with no generic `Origin` cannot be constructed: pallet `Balance` must have generic `Origin` - --> $DIR/missing_origin_generic_on_module_with_instance.rs:10:3 - | -10 | Balance: balances::::{Origin}, - | ^^^^^^^ + --> tests/construct_runtime_ui/missing_origin_generic_on_module_with_instance.rs:7:3 + | +7 | Balance: balances:: expanded::{}::{Origin}, + | ^^^^^^^ diff --git a/frame/support/test/tests/construct_runtime_ui/missing_system_module.rs b/frame/support/test/tests/construct_runtime_ui/missing_system_module.rs index 71dabf91c1d2..685f9059b1be 100644 --- a/frame/support/test/tests/construct_runtime_ui/missing_system_module.rs +++ b/frame/support/test/tests/construct_runtime_ui/missing_system_module.rs @@ -1,10 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { } } diff --git a/frame/support/test/tests/construct_runtime_ui/missing_system_module.stderr b/frame/support/test/tests/construct_runtime_ui/missing_system_module.stderr index 7648f5c1bfb3..c8631f44051c 100644 --- a/frame/support/test/tests/construct_runtime_ui/missing_system_module.stderr +++ b/frame/support/test/tests/construct_runtime_ui/missing_system_module.stderr @@ -1,6 +1,6 @@ -error: `System` pallet declaration is missing. Please add this line: `System: frame_system::{Pallet, Call, Storage, Config, Event},` - --> $DIR/missing_system_module.rs:8:2 +error: `System` pallet declaration is missing. Please add this line: `System: frame_system::{Pallet, Call, Storage, Config, Event},` + --> tests/construct_runtime_ui/missing_system_module.rs:5:2 | -8 | / { -9 | | } +5 | / { +6 | | } | |_____^ diff --git a/frame/support/test/tests/construct_runtime_ui/missing_where_block.rs b/frame/support/test/tests/construct_runtime_ui/missing_where_block.rs deleted file mode 100644 index 5148330ae585..000000000000 --- a/frame/support/test/tests/construct_runtime_ui/missing_where_block.rs +++ /dev/null @@ -1,7 +0,0 @@ -use frame_support::construct_runtime; - -construct_runtime! { - pub enum Runtime {} -} - -fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/missing_where_block.stderr b/frame/support/test/tests/construct_runtime_ui/missing_where_block.stderr deleted file mode 100644 index c6baf8fc24d0..000000000000 --- a/frame/support/test/tests/construct_runtime_ui/missing_where_block.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: expected `where` - --> tests/construct_runtime_ui/missing_where_block.rs:4:19 - | -4 | pub enum Runtime {} - | ^ diff --git a/frame/support/test/tests/construct_runtime_ui/missing_where_param.rs b/frame/support/test/tests/construct_runtime_ui/missing_where_param.rs index 2e311c5ea01a..4d2225a4afbc 100644 --- a/frame/support/test/tests/construct_runtime_ui/missing_where_param.rs +++ b/frame/support/test/tests/construct_runtime_ui/missing_where_param.rs @@ -1,7 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where + pub struct Runtime where Block = Block, NodeBlock = Block, {} diff --git a/frame/support/test/tests/construct_runtime_ui/more_than_256_modules.rs b/frame/support/test/tests/construct_runtime_ui/more_than_256_modules.rs index 4c8331ae442c..7dcbdb9aa4fb 100644 --- a/frame/support/test/tests/construct_runtime_ui/more_than_256_modules.rs +++ b/frame/support/test/tests/construct_runtime_ui/more_than_256_modules.rs @@ -1,7 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where + pub struct Runtime where UncheckedExtrinsic = UncheckedExtrinsic, Block = Block, NodeBlock = Block, diff --git a/frame/support/test/tests/construct_runtime_ui/no_comma_after_where.rs b/frame/support/test/tests/construct_runtime_ui/no_comma_after_where.rs index 954fadefa179..499f9a5cdcd5 100644 --- a/frame/support/test/tests/construct_runtime_ui/no_comma_after_where.rs +++ b/frame/support/test/tests/construct_runtime_ui/no_comma_after_where.rs @@ -1,7 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where + pub struct Runtime where UncheckedExtrinsic = UncheckedExtrinsic Block = Block, NodeBlock = Block, diff --git a/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr b/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr deleted file mode 100644 index d35565fb933a..000000000000 --- a/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.stderr +++ /dev/null @@ -1,46 +0,0 @@ -error: `Pallet` does not have the std feature enabled, this will cause the `test_pallet::GenesisConfig` type to be undefined. - --> tests/construct_runtime_ui/no_std_genesis_config.rs:40:1 - | -40 | / construct_runtime! { -41 | | pub enum Runtime where -42 | | Block = Block, -43 | | NodeBlock = Block, -... | -48 | | } -49 | | } - | |_^ - | - = note: this error originates in the macro `test_pallet::__substrate_genesis_config_check::is_std_enabled_for_genesis` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0412]: cannot find type `GenesisConfig` in crate `test_pallet` - --> tests/construct_runtime_ui/no_std_genesis_config.rs:40:1 - | -40 | / construct_runtime! { -41 | | pub enum Runtime where -42 | | Block = Block, -43 | | NodeBlock = Block, -... | -48 | | } -49 | | } - | |_^ not found in `test_pallet` - | - = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this struct - | -1 | use frame_system::GenesisConfig; - | - -error[E0283]: type annotations needed - --> tests/construct_runtime_ui/no_std_genesis_config.rs:40:1 - | -40 | / construct_runtime! { -41 | | pub enum Runtime where -42 | | Block = Block, -43 | | NodeBlock = Block, -... | -48 | | } -49 | | } - | |_^ cannot infer type - | - = note: cannot satisfy `_: std::default::Default` - = note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs b/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs new file mode 100644 index 000000000000..0d6afbcdc2c6 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs @@ -0,0 +1,165 @@ +use frame_support::construct_runtime; +use sp_core::sr25519; +use sp_runtime::{generic, traits::BlakeTwo256}; + +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); +} + +pub type Signature = sr25519::Signature; +pub type BlockNumber = u32; +pub type Header = generic::Header; +pub type Block = generic::Block; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + +impl pallet::Config for Runtime {} + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = frame_support::traits::ConstU32<250>; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +construct_runtime! { + pub struct Runtime + { + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Pallet1: pallet::{Pallet}, + Pallet2: pallet::{Pallet}, + Pallet3: pallet::{Pallet}, + Pallet4: pallet::{Pallet}, + Pallet5: pallet::{Pallet}, + Pallet6: pallet::{Pallet}, + Pallet7: pallet::{Pallet}, + Pallet8: pallet::{Pallet}, + Pallet9: pallet::{Pallet}, + Pallet10: pallet::{Pallet}, + Pallet11: pallet::{Pallet}, + Pallet12: pallet::{Pallet}, + Pallet13: pallet::{Pallet}, + Pallet14: pallet::{Pallet}, + Pallet15: pallet::{Pallet}, + Pallet16: pallet::{Pallet}, + Pallet17: pallet::{Pallet}, + Pallet18: pallet::{Pallet}, + Pallet19: pallet::{Pallet}, + Pallet20: pallet::{Pallet}, + Pallet21: pallet::{Pallet}, + Pallet22: pallet::{Pallet}, + Pallet23: pallet::{Pallet}, + Pallet24: pallet::{Pallet}, + Pallet25: pallet::{Pallet}, + Pallet26: pallet::{Pallet}, + Pallet27: pallet::{Pallet}, + Pallet28: pallet::{Pallet}, + Pallet29: pallet::{Pallet}, + Pallet30: pallet::{Pallet}, + Pallet31: pallet::{Pallet}, + Pallet32: pallet::{Pallet}, + Pallet33: pallet::{Pallet}, + Pallet34: pallet::{Pallet}, + Pallet35: pallet::{Pallet}, + Pallet36: pallet::{Pallet}, + Pallet37: pallet::{Pallet}, + Pallet38: pallet::{Pallet}, + Pallet39: pallet::{Pallet}, + Pallet40: pallet::{Pallet}, + Pallet41: pallet::{Pallet}, + Pallet42: pallet::{Pallet}, + Pallet43: pallet::{Pallet}, + Pallet44: pallet::{Pallet}, + Pallet45: pallet::{Pallet}, + Pallet46: pallet::{Pallet}, + Pallet47: pallet::{Pallet}, + Pallet48: pallet::{Pallet}, + Pallet49: pallet::{Pallet}, + Pallet50: pallet::{Pallet}, + Pallet51: pallet::{Pallet}, + Pallet52: pallet::{Pallet}, + Pallet53: pallet::{Pallet}, + Pallet54: pallet::{Pallet}, + Pallet55: pallet::{Pallet}, + Pallet56: pallet::{Pallet}, + Pallet57: pallet::{Pallet}, + Pallet58: pallet::{Pallet}, + Pallet59: pallet::{Pallet}, + Pallet60: pallet::{Pallet}, + Pallet61: pallet::{Pallet}, + Pallet62: pallet::{Pallet}, + Pallet63: pallet::{Pallet}, + Pallet64: pallet::{Pallet}, + Pallet65: pallet::{Pallet}, + Pallet66: pallet::{Pallet}, + Pallet67: pallet::{Pallet}, + Pallet68: pallet::{Pallet}, + Pallet69: pallet::{Pallet}, + Pallet70: pallet::{Pallet}, + Pallet71: pallet::{Pallet}, + Pallet72: pallet::{Pallet}, + Pallet73: pallet::{Pallet}, + Pallet74: pallet::{Pallet}, + Pallet75: pallet::{Pallet}, + Pallet76: pallet::{Pallet}, + Pallet77: pallet::{Pallet}, + Pallet78: pallet::{Pallet}, + Pallet79: pallet::{Pallet}, + Pallet80: pallet::{Pallet}, + Pallet81: pallet::{Pallet}, + Pallet82: pallet::{Pallet}, + Pallet83: pallet::{Pallet}, + Pallet84: pallet::{Pallet}, + Pallet85: pallet::{Pallet}, + Pallet86: pallet::{Pallet}, + Pallet87: pallet::{Pallet}, + Pallet88: pallet::{Pallet}, + Pallet89: pallet::{Pallet}, + Pallet90: pallet::{Pallet}, + Pallet91: pallet::{Pallet}, + Pallet92: pallet::{Pallet}, + Pallet93: pallet::{Pallet}, + Pallet94: pallet::{Pallet}, + Pallet95: pallet::{Pallet}, + Pallet96: pallet::{Pallet}, + Pallet97: pallet::{Pallet}, + Pallet98: pallet::{Pallet}, + Pallet99: pallet::{Pallet}, + Pallet100: pallet::{Pallet}, + Pallet101: pallet::{Pallet}, + Pallet102: pallet::{Pallet}, + Pallet103: pallet::{Pallet}, + Pallet104: pallet::{Pallet}, + Pallet105: pallet::{Pallet}, + Pallet106: pallet::{Pallet}, + Pallet107: pallet::{Pallet}, + Pallet108: pallet::{Pallet}, + Pallet109: pallet::{Pallet}, + Pallet110: pallet::{Pallet}, + } +} + +fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr b/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr new file mode 100644 index 000000000000..55cef6704ee3 --- /dev/null +++ b/frame/support/test/tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.stderr @@ -0,0 +1,61 @@ +error: The number of pallets exceeds the maximum number of tuple elements. To increase this limit, enable the tuples-96 feature of [frame_support]. + --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:49:2 + | +49 | pub struct Runtime + | ^^^ + +error[E0412]: cannot find type `RuntimeCall` in this scope + --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:18:64 + | +18 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + | ^^^^^^^^^^^ not found in this scope + | +help: you might be missing a type parameter + | +18 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + | +++++++++++++ + +error[E0412]: cannot find type `Runtime` in this scope + --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:20:25 + | +20 | impl pallet::Config for Runtime {} + | ^^^^^^^ not found in this scope + +error[E0412]: cannot find type `Runtime` in this scope + --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:22:31 + | +22 | impl frame_system::Config for Runtime { + | ^^^^^^^ not found in this scope + +error[E0412]: cannot find type `RuntimeOrigin` in this scope + --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:24:23 + | +24 | type RuntimeOrigin = RuntimeOrigin; + | ^^^^^^^^^^^^^ help: you might have meant to use the associated type: `Self::RuntimeOrigin` + +error[E0412]: cannot find type `RuntimeCall` in this scope + --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:26:21 + | +26 | type RuntimeCall = RuntimeCall; + | ^^^^^^^^^^^ help: you might have meant to use the associated type: `Self::RuntimeCall` + +error[E0412]: cannot find type `RuntimeEvent` in this scope + --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:32:22 + | +32 | type RuntimeEvent = RuntimeEvent; + | ^^^^^^^^^^^^ help: you might have meant to use the associated type: `Self::RuntimeEvent` + +error[E0412]: cannot find type `PalletInfo` in this scope + --> tests/construct_runtime_ui/number_of_pallets_exceeds_tuple_size.rs:38:20 + | +38 | type PalletInfo = PalletInfo; + | ^^^^^^^^^^ + | +help: you might have meant to use the associated type + | +38 | type PalletInfo = Self::PalletInfo; + | ~~~~~~~~~~~~~~~~ +help: consider importing this trait + | +1 + use frame_support::traits::PalletInfo; + | diff --git a/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.rs b/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.rs deleted file mode 100644 index 5691549c20f3..000000000000 --- a/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.rs +++ /dev/null @@ -1,26 +0,0 @@ -use frame_support::construct_runtime; - -mod pallet_old { - pub trait Config: frame_system::Config {} - - decl_storage! { - trait Store for Module as Example {} - } - - decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin {} - } - -} -construct_runtime! { - pub enum Runtime where - UncheckedExtrinsic = UncheckedExtrinsic, - Block = Block, - NodeBlock = Block, - { - System: frame_system, - OldPallet: pallet_old, - } -} - -fn main() {} diff --git a/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.stderr b/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.stderr deleted file mode 100644 index f8ec07e00106..000000000000 --- a/frame/support/test/tests/construct_runtime_ui/old_unsupported_pallet_decl.stderr +++ /dev/null @@ -1,31 +0,0 @@ -error[E0433]: failed to resolve: could not find `tt_default_parts` in `pallet_old` - --> $DIR/old_unsupported_pallet_decl.rs:15:1 - | -15 | / construct_runtime! { -16 | | pub enum Runtime where -17 | | UncheckedExtrinsic = UncheckedExtrinsic, -18 | | Block = Block, -... | -23 | | } -24 | | } - | |_^ could not find `tt_default_parts` in `pallet_old` - | - = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) - -error: cannot find macro `decl_storage` in this scope - --> $DIR/old_unsupported_pallet_decl.rs:6:2 - | -6 | decl_storage! { - | ^^^^^^^^^^^^ - | - = note: consider importing this macro: - frame_support::decl_storage - -error: cannot find macro `decl_module` in this scope - --> $DIR/old_unsupported_pallet_decl.rs:10:2 - | -10 | decl_module! { - | ^^^^^^^^^^^ - | - = note: consider importing this macro: - frame_support::decl_module diff --git a/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.rs b/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.rs index b7ccadb5e3e5..8b3e26bc5e2e 100644 --- a/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.rs +++ b/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.rs @@ -1,6 +1,6 @@ use frame_support::construct_runtime; -use sp_runtime::{generic, traits::BlakeTwo256}; use sp_core::sr25519; +use sp_runtime::{generic, traits::BlakeTwo256}; #[frame_support::pallet] mod pallet { @@ -18,22 +18,22 @@ mod pallet { #[derive(scale_info::TypeInfo, frame_support::PalletError, codec::Encode, codec::Decode)] pub enum Nested1 { - Nested2(Nested2) + Nested2(Nested2), } #[derive(scale_info::TypeInfo, frame_support::PalletError, codec::Encode, codec::Decode)] pub enum Nested2 { - Nested3(Nested3) + Nested3(Nested3), } #[derive(scale_info::TypeInfo, frame_support::PalletError, codec::Encode, codec::Decode)] pub enum Nested3 { - Nested4(Nested4) + Nested4(Nested4), } #[derive(scale_info::TypeInfo, frame_support::PalletError, codec::Encode, codec::Decode)] pub enum Nested4 { - Num(u8) + Num(u8), } pub type Signature = sr25519::Signature; @@ -47,14 +47,13 @@ impl pallet::Config for Runtime {} impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u32; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = sp_runtime::testing::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = sp_runtime::traits::IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = frame_support::traits::ConstU32<250>; type BlockWeights = (); @@ -72,12 +71,9 @@ impl frame_system::Config for Runtime { } construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, + System: frame_system::{Pallet, Call, Storage, Config, Event}, Pallet: pallet::{Pallet}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr b/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr index 99a543eef7a8..47504573515a 100644 --- a/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr +++ b/frame/support/test/tests/construct_runtime_ui/pallet_error_too_large.stderr @@ -1,13 +1,13 @@ error[E0080]: evaluation of constant value failed - --> tests/construct_runtime_ui/pallet_error_too_large.rs:74:1 + --> tests/construct_runtime_ui/pallet_error_too_large.rs:73:1 | -74 | / construct_runtime! { -75 | | pub enum Runtime where -76 | | Block = Block, -77 | | NodeBlock = Block, -... | -82 | | } -83 | | } - | |_^ the evaluated program panicked at 'The maximum encoded size of the error type in the `Pallet` pallet exceeds `MAX_MODULE_ERROR_ENCODED_SIZE`', $DIR/tests/construct_runtime_ui/pallet_error_too_large.rs:74:1 +73 | / construct_runtime! { +74 | | pub struct Runtime +75 | | { +76 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, +77 | | Pallet: pallet::{Pallet}, +78 | | } +79 | | } + | |_^ the evaluated program panicked at 'The maximum encoded size of the error type in the `Pallet` pallet exceeds `MAX_MODULE_ERROR_ENCODED_SIZE`', $DIR/tests/construct_runtime_ui/pallet_error_too_large.rs:73:1 | = note: this error originates in the macro `$crate::panic::panic_2021` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_call_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_call_part.rs index abe5c4cfeb34..25cb5e93f652 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_call_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_call_part.rs @@ -1,6 +1,6 @@ use frame_support::construct_runtime; -use sp_runtime::{generic, traits::BlakeTwo256}; use sp_core::sr25519; +use sp_runtime::{generic, traits::BlakeTwo256}; #[frame_support::pallet] mod pallet { @@ -22,14 +22,13 @@ impl pallet::Config for Runtime {} impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u32; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = sp_runtime::testing::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = sp_runtime::traits::IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = frame_support::traits::ConstU32<250>; type BlockWeights = (); @@ -47,12 +46,9 @@ impl frame_system::Config for Runtime { } construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, + System: frame_system::{Pallet, Call, Storage, Config, Event}, Pallet: pallet::{Pallet, Call}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_call_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_call_part.stderr index 6baf01e866f6..f3f29e4c6955 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_call_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_call_part.stderr @@ -4,13 +4,13 @@ error: `Pallet` does not have #[pallet::call] defined, perhaps you should remove 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ ... -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } +48 | / construct_runtime! { +49 | | pub struct Runtime +50 | | { +51 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet::{Pallet, Call}, +53 | | } +54 | | } | |_- in this macro invocation | = note: this error originates in the macro `pallet::__substrate_call_check::is_call_part_defined` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_event_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_event_part.rs index 9a6ac5c6251f..c44cceef81a1 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_event_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_event_part.rs @@ -1,6 +1,6 @@ use frame_support::construct_runtime; -use sp_runtime::{generic, traits::BlakeTwo256}; use sp_core::sr25519; +use sp_runtime::{generic, traits::BlakeTwo256}; #[frame_support::pallet] mod pallet { @@ -22,14 +22,13 @@ impl pallet::Config for Runtime {} impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u32; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = sp_runtime::testing::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = sp_runtime::traits::IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = frame_support::traits::ConstU32<250>; type BlockWeights = (); @@ -47,13 +46,10 @@ impl frame_system::Config for Runtime { } construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Pallet: pallet::{Pallet, Event}, + System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet expanded::{}::{Pallet, Event}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr index ff8ecf3041bf..81e42cec3b97 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_event_part.stderr @@ -4,31 +4,33 @@ error: `Pallet` does not have #[pallet::event] defined, perhaps you should remov 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ ... -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } +48 | / construct_runtime! { +49 | | pub struct Runtime +50 | | { +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Event}, +53 | | } +54 | | } | |_- in this macro invocation | = note: this error originates in the macro `pallet::__substrate_event_check::is_event_part_defined` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0412]: cannot find type `Event` in module `pallet` - --> tests/construct_runtime_ui/undefined_event_part.rs:49:1 + --> tests/construct_runtime_ui/undefined_event_part.rs:48:1 | -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } +48 | / construct_runtime! { +49 | | pub struct Runtime +50 | | { +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Event}, +53 | | } +54 | | } | |_^ not found in `pallet` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this enum +help: consider importing one of these items | -1 | use frame_system::Event; +1 + use frame_support_test::Event; + | +1 + use frame_system::Event; | diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.rs index 4facf85e280c..4436202f04fc 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.rs @@ -1,6 +1,6 @@ use frame_support::construct_runtime; -use sp_runtime::{generic, traits::BlakeTwo256}; use sp_core::sr25519; +use sp_runtime::{generic, traits::BlakeTwo256}; #[frame_support::pallet] mod pallet { @@ -22,14 +22,13 @@ impl pallet::Config for Runtime {} impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u32; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = sp_runtime::testing::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = sp_runtime::traits::IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = frame_support::traits::ConstU32<250>; type BlockWeights = (); @@ -47,13 +46,10 @@ impl frame_system::Config for Runtime { } construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Pallet: pallet::{Pallet, Config}, + System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet expanded::{}::{Pallet, Config}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr index 046369e1112b..920785fc9629 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_genesis_config_part.stderr @@ -4,46 +4,33 @@ error: `Pallet` does not have #[pallet::genesis_config] defined, perhaps you sho 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ ... -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } +48 | / construct_runtime! { +49 | | pub struct Runtime +50 | | { +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Config}, +53 | | } +54 | | } | |_- in this macro invocation | = note: this error originates in the macro `pallet::__substrate_genesis_config_check::is_genesis_config_defined` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0412]: cannot find type `GenesisConfig` in module `pallet` - --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:49:1 - | -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } + --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:48:1 + | +48 | / construct_runtime! { +49 | | pub struct Runtime +50 | | { +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Config}, +53 | | } +54 | | } | |_^ not found in `pallet` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this struct - | -1 | use frame_system::GenesisConfig; +help: consider importing one of these items | - -error[E0283]: type annotations needed - --> tests/construct_runtime_ui/undefined_genesis_config_part.rs:49:1 +1 + use frame_system::GenesisConfig; | -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } - | |_^ cannot infer type +1 + use test_pallet::GenesisConfig; | - = note: cannot satisfy `_: std::default::Default` - = note: this error originates in the derive macro `Default` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.rs index 322fa2c29728..8b48c4d0d6af 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.rs @@ -1,6 +1,6 @@ use frame_support::construct_runtime; -use sp_runtime::{generic, traits::BlakeTwo256}; use sp_core::sr25519; +use sp_runtime::{generic, traits::BlakeTwo256}; #[frame_support::pallet] mod pallet { @@ -22,14 +22,13 @@ impl pallet::Config for Runtime {} impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u32; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = sp_runtime::testing::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = sp_runtime::traits::IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = frame_support::traits::ConstU32<250>; type BlockWeights = (); @@ -47,13 +46,10 @@ impl frame_system::Config for Runtime { } construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Pallet: pallet::{Pallet, Inherent}, + System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet expanded::{}::{Pallet, Inherent}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr index 74af0c264cd5..659d43b15100 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_inherent_part.stderr @@ -4,31 +4,32 @@ error: `Pallet` does not have #[pallet::inherent] defined, perhaps you should re 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ ... -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } +48 | / construct_runtime! { +49 | | pub struct Runtime +50 | | { +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Inherent}, +53 | | } +54 | | } | |_- in this macro invocation | = note: this error originates in the macro `pallet::__substrate_inherent_check::is_inherent_part_defined` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: no function or associated item named `create_inherent` found for struct `pallet::Pallet` in the current scope - --> tests/construct_runtime_ui/undefined_inherent_part.rs:49:1 + --> tests/construct_runtime_ui/undefined_inherent_part.rs:48:1 | 11 | pub struct Pallet(_); | -------------------- function or associated item `create_inherent` not found for this struct ... -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } - | |_^ function or associated item not found in `pallet::Pallet` +48 | construct_runtime! { + | _^ +49 | | pub struct Runtime +50 | | { +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Inherent}, +53 | | } +54 | | } + | |_^ function or associated item not found in `Pallet` | = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `create_inherent`, perhaps you need to implement it: @@ -36,19 +37,20 @@ error[E0599]: no function or associated item named `create_inherent` found for s = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: no function or associated item named `is_inherent` found for struct `pallet::Pallet` in the current scope - --> tests/construct_runtime_ui/undefined_inherent_part.rs:49:1 + --> tests/construct_runtime_ui/undefined_inherent_part.rs:48:1 | 11 | pub struct Pallet(_); | -------------------- function or associated item `is_inherent` not found for this struct ... -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } - | |_^ function or associated item not found in `pallet::Pallet` +48 | construct_runtime! { + | _^ +49 | | pub struct Runtime +50 | | { +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Inherent}, +53 | | } +54 | | } + | |_^ function or associated item not found in `Pallet` | = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `is_inherent`, perhaps you need to implement it: @@ -56,19 +58,20 @@ error[E0599]: no function or associated item named `is_inherent` found for struc = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: no function or associated item named `check_inherent` found for struct `pallet::Pallet` in the current scope - --> tests/construct_runtime_ui/undefined_inherent_part.rs:49:1 + --> tests/construct_runtime_ui/undefined_inherent_part.rs:48:1 | 11 | pub struct Pallet(_); | -------------------- function or associated item `check_inherent` not found for this struct ... -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } - | |_^ function or associated item not found in `pallet::Pallet` +48 | construct_runtime! { + | _^ +49 | | pub struct Runtime +50 | | { +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Inherent}, +53 | | } +54 | | } + | |_^ function or associated item not found in `Pallet` | = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `check_inherent`, perhaps you need to implement it: @@ -76,19 +79,20 @@ error[E0599]: no function or associated item named `check_inherent` found for st = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: no associated item named `INHERENT_IDENTIFIER` found for struct `pallet::Pallet` in the current scope - --> tests/construct_runtime_ui/undefined_inherent_part.rs:49:1 + --> tests/construct_runtime_ui/undefined_inherent_part.rs:48:1 | 11 | pub struct Pallet(_); | -------------------- associated item `INHERENT_IDENTIFIER` not found for this struct ... -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } - | |_^ associated item not found in `pallet::Pallet` +48 | construct_runtime! { + | _^ +49 | | pub struct Runtime +50 | | { +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Inherent}, +53 | | } +54 | | } + | |_^ associated item not found in `Pallet` | = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `INHERENT_IDENTIFIER`, perhaps you need to implement it: @@ -96,19 +100,20 @@ error[E0599]: no associated item named `INHERENT_IDENTIFIER` found for struct `p = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: no function or associated item named `is_inherent_required` found for struct `pallet::Pallet` in the current scope - --> tests/construct_runtime_ui/undefined_inherent_part.rs:49:1 + --> tests/construct_runtime_ui/undefined_inherent_part.rs:48:1 | 11 | pub struct Pallet(_); | -------------------- function or associated item `is_inherent_required` not found for this struct ... -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } - | |_^ function or associated item not found in `pallet::Pallet` +48 | construct_runtime! { + | _^ +49 | | pub struct Runtime +50 | | { +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Inherent}, +53 | | } +54 | | } + | |_^ function or associated item not found in `Pallet` | = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item `is_inherent_required`, perhaps you need to implement it: diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.rs index 55cd5b545d6b..974928785f74 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.rs @@ -1,6 +1,6 @@ use frame_support::construct_runtime; -use sp_runtime::{generic, traits::BlakeTwo256}; use sp_core::sr25519; +use sp_runtime::{generic, traits::BlakeTwo256}; #[frame_support::pallet] mod pallet { @@ -22,14 +22,13 @@ impl pallet::Config for Runtime {} impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u32; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = sp_runtime::testing::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = sp_runtime::traits::IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = frame_support::traits::ConstU32<250>; type BlockWeights = (); @@ -47,13 +46,10 @@ impl frame_system::Config for Runtime { } construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Pallet: pallet::{Pallet, Origin}, + System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, + Pallet: pallet expanded::{}::{Pallet, Origin}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr index 4907053b1287..c41dbe79421e 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_origin_part.stderr @@ -4,49 +4,33 @@ error: `Pallet` does not have #[pallet::origin] defined, perhaps you should remo 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ ... -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } +48 | / construct_runtime! { +49 | | pub struct Runtime +50 | | { +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Origin}, +53 | | } +54 | | } | |_- in this macro invocation | = note: this error originates in the macro `pallet::__substrate_origin_check::is_origin_part_defined` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0412]: cannot find type `Origin` in module `pallet` - --> tests/construct_runtime_ui/undefined_origin_part.rs:49:1 - | -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } + --> tests/construct_runtime_ui/undefined_origin_part.rs:48:1 + | +48 | / construct_runtime! { +49 | | pub struct Runtime +50 | | { +51 | | System: frame_system expanded::{}::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet expanded::{}::{Pallet, Origin}, +53 | | } +54 | | } | |_^ not found in `pallet` | = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider importing this type alias - | -1 | use frame_system::Origin; +help: consider importing one of these items | - -error[E0282]: type annotations needed - --> tests/construct_runtime_ui/undefined_origin_part.rs:49:1 +1 + use frame_support_test::Origin; | -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } - | |_^ cannot infer type of the type parameter `AccountId` declared on the enum `RawOrigin` - | - = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) -help: consider specifying the generic argument +1 + use frame_system::Origin; | -58 | }:: - | +++++++++++++ diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.rs b/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.rs index 0cf305a7dc05..505b249d92d5 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.rs @@ -1,6 +1,6 @@ use frame_support::construct_runtime; -use sp_runtime::{generic, traits::BlakeTwo256}; use sp_core::sr25519; +use sp_runtime::{generic, traits::BlakeTwo256}; #[frame_support::pallet] mod pallet { @@ -22,14 +22,13 @@ impl pallet::Config for Runtime {} impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u32; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = sp_runtime::testing::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = sp_runtime::traits::IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = frame_support::traits::ConstU32<250>; type BlockWeights = (); @@ -47,12 +46,9 @@ impl frame_system::Config for Runtime { } construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, + System: frame_system::{Pallet, Call, Storage, Config, Event}, Pallet: pallet::{Pallet, ValidateUnsigned}, } } diff --git a/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr b/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr index 6f0b13c58933..007b77250736 100644 --- a/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/undefined_validate_unsigned_part.stderr @@ -4,69 +4,76 @@ error: `Pallet` does not have #[pallet::validate_unsigned] defined, perhaps you 5 | #[frame_support::pallet] | ^^^^^^^^^^^^^^^^^^^^^^^^ ... -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, -... | -57 | | } -58 | | } +48 | / construct_runtime! { +49 | | pub struct Runtime +50 | | { +51 | | System: frame_system::{Pallet, Call, Storage, Config, Event}, +52 | | Pallet: pallet::{Pallet, ValidateUnsigned}, +53 | | } +54 | | } | |_- in this macro invocation | = note: this error originates in the macro `pallet::__substrate_validate_unsigned_check::is_validate_unsigned_part_defined` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: no variant or associated item named `Pallet` found for enum `RuntimeCall` in the current scope - --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:56:3 + --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:52:3 | -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, +48 | // construct_runtime! { +49 | || pub struct Runtime +50 | || { +51 | || System: frame_system::{Pallet, Call, Storage, Config, Event}, +52 | || Pallet: pallet::{Pallet, ValidateUnsigned}, + | || -^^^^^^ variant or associated item not found in `RuntimeCall` + | ||________| + | | ... | -56 | | Pallet: pallet::{Pallet, ValidateUnsigned}, - | | ^^^^^^ variant or associated item not found in `RuntimeCall` -57 | | } -58 | | } - | |_- variant or associated item `Pallet` not found for this enum error[E0599]: no function or associated item named `pre_dispatch` found for struct `pallet::Pallet` in the current scope - --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:49:1 + --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:48:1 | -11 | pub struct Pallet(_); - | -------------------- function or associated item `pre_dispatch` not found for this struct +11 | pub struct Pallet(_); + | -------------------- function or associated item `pre_dispatch` not found for this struct ... -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, +48 | construct_runtime! { + | __^ + | | _| + | || +49 | || pub struct Runtime +50 | || { +51 | || System: frame_system::{Pallet, Call, Storage, Config, Event}, +52 | || Pallet: pallet::{Pallet, ValidateUnsigned}, +53 | || } +54 | || } + | ||_- in this macro invocation ... | -57 | | } -58 | | } - | |_^ function or associated item not found in `pallet::Pallet` | = help: items from traits can only be used if the trait is implemented and in scope = note: the following traits define an item `pre_dispatch`, perhaps you need to implement one of them: candidate #1: `SignedExtension` candidate #2: `ValidateUnsigned` - = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0599]: no function or associated item named `validate_unsigned` found for struct `pallet::Pallet` in the current scope - --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:49:1 + --> tests/construct_runtime_ui/undefined_validate_unsigned_part.rs:48:1 | -11 | pub struct Pallet(_); - | -------------------- function or associated item `validate_unsigned` not found for this struct +11 | pub struct Pallet(_); + | -------------------- function or associated item `validate_unsigned` not found for this struct ... -49 | / construct_runtime! { -50 | | pub enum Runtime where -51 | | Block = Block, -52 | | NodeBlock = Block, +48 | construct_runtime! { + | __^ + | | _| + | || +49 | || pub struct Runtime +50 | || { +51 | || System: frame_system::{Pallet, Call, Storage, Config, Event}, +52 | || Pallet: pallet::{Pallet, ValidateUnsigned}, +53 | || } +54 | || } + | ||_- in this macro invocation ... | -57 | | } -58 | | } - | |_^ function or associated item not found in `pallet::Pallet` | = help: items from traits can only be used if the trait is implemented and in scope = note: the following traits define an item `validate_unsigned`, perhaps you need to implement one of them: candidate #1: `SignedExtension` candidate #2: `ValidateUnsigned` - = note: this error originates in the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + = note: this error originates in the macro `frame_support::construct_runtime` which comes from the expansion of the macro `construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.rs b/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.rs index b93adf9a780a..e4e2d3dca021 100644 --- a/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.rs +++ b/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.rs @@ -1,10 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { System: system::{Pallet}, #[cfg(feature(test))] diff --git a/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.stderr b/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.stderr index 98d99a0d3499..34637269db61 100644 --- a/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.stderr +++ b/frame/support/test/tests/construct_runtime_ui/unsupported_meta_structure.stderr @@ -1,6 +1,6 @@ error: feature(test) ^ expected one of `=`, `,`, `)` here - --> tests/construct_runtime_ui/unsupported_meta_structure.rs:10:3 - | -10 | #[cfg(feature(test))] - | ^ + --> tests/construct_runtime_ui/unsupported_meta_structure.rs:7:3 + | +7 | #[cfg(feature(test))] + | ^ diff --git a/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.rs b/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.rs index 3ec8b9db1d43..491cc2c90533 100644 --- a/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.rs +++ b/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.rs @@ -1,10 +1,7 @@ use frame_support::construct_runtime; construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { System: system::{Pallet}, #[attr] diff --git a/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.stderr b/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.stderr index fceb2b8a99db..da1b61b1c307 100644 --- a/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.stderr +++ b/frame/support/test/tests/construct_runtime_ui/unsupported_pallet_attr.stderr @@ -1,5 +1,5 @@ error: Unsupported attribute, only #[cfg] is supported on pallet declarations in `construct_runtime` - --> tests/construct_runtime_ui/unsupported_pallet_attr.rs:10:3 - | -10 | #[attr] - | ^ + --> tests/construct_runtime_ui/unsupported_pallet_attr.rs:7:3 + | +7 | #[attr] + | ^ diff --git a/frame/support/test/tests/construct_runtime_ui/use_undefined_part.rs b/frame/support/test/tests/construct_runtime_ui/use_undefined_part.rs index c74e29bc0546..8563be1008cd 100644 --- a/frame/support/test/tests/construct_runtime_ui/use_undefined_part.rs +++ b/frame/support/test/tests/construct_runtime_ui/use_undefined_part.rs @@ -25,12 +25,9 @@ pub type UncheckedExtrinsic = generic::UncheckedExtrinsic}, + System: system::{Pallet, Call, Storage, Config, Event}, Pallet: pallet use_parts { Call }, } } diff --git a/frame/support/test/tests/construct_runtime_ui/use_undefined_part.stderr b/frame/support/test/tests/construct_runtime_ui/use_undefined_part.stderr index e289c75fb008..4058ccab2c5d 100644 --- a/frame/support/test/tests/construct_runtime_ui/use_undefined_part.stderr +++ b/frame/support/test/tests/construct_runtime_ui/use_undefined_part.stderr @@ -1,16 +1,19 @@ error: Invalid pallet part specified, the pallet `Pallet` doesn't have the `Call` part. Available parts are: `Pallet`, `Storage`. - --> tests/construct_runtime_ui/use_undefined_part.rs:34:30 + --> tests/construct_runtime_ui/use_undefined_part.rs:31:30 | -34 | Pallet: pallet use_parts { Call }, +31 | Pallet: pallet use_parts { Call }, | ^^^^ error[E0412]: cannot find type `RuntimeCall` in this scope --> tests/construct_runtime_ui/use_undefined_part.rs:23:64 | 23 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; - | - ^^^^^^^^^^^ not found in this scope - | | - | help: you might be missing a type parameter: `` + | ^^^^^^^^^^^ not found in this scope + | +help: you might be missing a type parameter + | +23 | pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; + | +++++++++++++ error[E0412]: cannot find type `Runtime` in this scope --> tests/construct_runtime_ui/use_undefined_part.rs:25:25 diff --git a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs b/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs deleted file mode 100644 index 0f662b96e2b1..000000000000 --- a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs +++ /dev/null @@ -1,7 +0,0 @@ -frame_support::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self { - fn integrity_test() {} - - fn integrity_test() {} - } -} diff --git a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.stderr b/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.stderr deleted file mode 100644 index 4212707599d4..000000000000 --- a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.stderr +++ /dev/null @@ -1,19 +0,0 @@ -error: `integrity_test` can only be passed once as input. - --> tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs:1:1 - | -1 | / frame_support::decl_module! { -2 | | pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self { -3 | | fn integrity_test() {} -4 | | -5 | | fn integrity_test() {} -6 | | } -7 | | } - | |_^ - | - = note: this error originates in the macro `$crate::decl_module` which comes from the expansion of the macro `frame_support::decl_module` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0601]: `main` function not found in crate `$CRATE` - --> tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs:7:2 - | -7 | } - | ^ consider adding a `main` function to `$DIR/tests/decl_module_ui/reserved_keyword_two_times_integrity_test.rs` diff --git a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_on_initialize.rs b/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_on_initialize.rs deleted file mode 100644 index ea0746b1c501..000000000000 --- a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_on_initialize.rs +++ /dev/null @@ -1,13 +0,0 @@ -frame_support::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self { - fn on_initialize() -> Weight { - 0 - } - - fn on_initialize() -> Weight { - 0 - } - } -} - -fn main() {} diff --git a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_on_initialize.stderr b/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_on_initialize.stderr deleted file mode 100644 index 94bde853e4cc..000000000000 --- a/frame/support/test/tests/decl_module_ui/reserved_keyword_two_times_on_initialize.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error: `on_initialize` can only be passed once as input. - --> tests/decl_module_ui/reserved_keyword_two_times_on_initialize.rs:1:1 - | -1 | / frame_support::decl_module! { -2 | | pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self { -3 | | fn on_initialize() -> Weight { -4 | | 0 -... | -10 | | } -11 | | } - | |_^ - | - = note: this error originates in the macro `$crate::decl_module` which comes from the expansion of the macro `frame_support::decl_module` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/decl_storage.rs b/frame/support/test/tests/decl_storage.rs deleted file mode 100644 index a4e420eb454b..000000000000 --- a/frame/support/test/tests/decl_storage.rs +++ /dev/null @@ -1,878 +0,0 @@ -// 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. - -#[cfg(test)] -// Do not complain about unused `dispatch` and `dispatch_aux`. -#[allow(dead_code)] -mod tests { - use frame_support::metadata::*; - use sp_io::TestExternalities; - - frame_support::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} - } - - pub trait Config: frame_support_test::Config { - type Origin2: codec::Codec - + codec::EncodeLike - + Default - + codec::MaxEncodedLen - + scale_info::TypeInfo; - } - - frame_support::decl_storage! { - generate_storage_info - trait Store for Module as TestStorage { - // non-getters: pub / $default - - /// Hello, this is doc! - U32: Option; - pub PUBU32: Option; - U32MYDEF: Option; - pub PUBU32MYDEF: Option; - - // getters: pub / $default - // we need at least one type which uses T, otherwise GenesisConfig will complain. - GETU32 get(fn u32_getter): T::Origin2; - pub PUBGETU32 get(fn pub_u32_getter): u32; - GETU32WITHCONFIG get(fn u32_getter_with_config) config(): u32; - pub PUBGETU32WITHCONFIG get(fn pub_u32_getter_with_config) config(): u32; - GETU32MYDEF get(fn u32_getter_mydef): Option; - pub PUBGETU32MYDEF get(fn pub_u32_getter_mydef) config(): u32 = 3; - GETU32WITHCONFIGMYDEF get(fn u32_getter_with_config_mydef) config(): u32 = 2; - pub PUBGETU32WITHCONFIGMYDEF get(fn pub_u32_getter_with_config_mydef) config(): u32 = 1; - PUBGETU32WITHCONFIGMYDEFOPT get(fn pub_u32_getter_with_config_mydef_opt) config(): Option; - - GetU32WithBuilder get(fn u32_with_builder) build(|_| 1): u32; - GetOptU32WithBuilderSome get(fn opt_u32_with_builder_some) build(|_| Some(1)): Option; - GetOptU32WithBuilderNone get(fn opt_u32_with_builder_none) build(|_| None): Option; - - // map non-getters: pub / $default - MAPU32 max_values(3): map hasher(blake2_128_concat) u32 => Option<[u8; 4]>; - pub PUBMAPU32: map hasher(blake2_128_concat) u32 => Option<[u8; 4]>; - - // map getters: pub / $default - GETMAPU32 get(fn map_u32_getter): map hasher(blake2_128_concat) u32 => [u8; 4]; - pub PUBGETMAPU32 get(fn pub_map_u32_getter): map hasher(blake2_128_concat) u32 => [u8; 4]; - GETMAPU32MYDEF get(fn map_u32_getter_mydef): - map hasher(blake2_128_concat) u32 => [u8; 4] = *b"mapd"; - pub PUBGETMAPU32MYDEF get(fn pub_map_u32_getter_mydef): - map hasher(blake2_128_concat) u32 => [u8; 4] = *b"pubm"; - - DOUBLEMAP max_values(3): double_map - hasher(blake2_128_concat) u32, hasher(blake2_128_concat) u32 => Option<[u8; 4]>; - - DOUBLEMAP2: double_map - hasher(blake2_128_concat) u32, hasher(blake2_128_concat) u32 => Option<[u8; 4]>; - - COMPLEXTYPE1: (::std::option::Option,); - COMPLEXTYPE2: ([[(u16, Option<()>); 32]; 12], u32); - COMPLEXTYPE3: [u32; 25]; - - NMAP: nmap hasher(blake2_128_concat) u32, hasher(twox_64_concat) u16 => u8; - NMAP2: nmap hasher(blake2_128_concat) u32 => u8; - } - add_extra_genesis { - build(|_| {}); - } - } - - struct TraitImpl {} - - impl frame_support_test::Config for TraitImpl { - type RuntimeOrigin = u32; - type BlockNumber = u32; - type PalletInfo = frame_support_test::PanicPalletInfo; - type DbWeight = (); - } - - impl Config for TraitImpl { - type Origin2 = u32; - } - - fn expected_metadata() -> PalletStorageMetadata { - PalletStorageMetadata { - prefix: "TestStorage", - entries: vec![ - StorageEntryMetadata { - name: "U32", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Plain(scale_info::meta_type::()), - default: vec![0], - docs: vec![" Hello, this is doc!"], - }, - StorageEntryMetadata { - name: "PUBU32", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Plain(scale_info::meta_type::()), - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "U32MYDEF", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Plain(scale_info::meta_type::()), - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "PUBU32MYDEF", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Plain(scale_info::meta_type::()), - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "GETU32", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(scale_info::meta_type::()), - default: vec![0, 0, 0, 0], - docs: vec![], - }, - StorageEntryMetadata { - name: "PUBGETU32", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(scale_info::meta_type::()), - default: vec![0, 0, 0, 0], - docs: vec![], - }, - StorageEntryMetadata { - name: "GETU32WITHCONFIG", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(scale_info::meta_type::()), - default: vec![0, 0, 0, 0], - docs: vec![], - }, - StorageEntryMetadata { - name: "PUBGETU32WITHCONFIG", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(scale_info::meta_type::()), - default: vec![0, 0, 0, 0], - docs: vec![], - }, - StorageEntryMetadata { - name: "GETU32MYDEF", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Plain(scale_info::meta_type::()), - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "PUBGETU32MYDEF", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(scale_info::meta_type::()), - default: vec![3, 0, 0, 0], - docs: vec![], - }, - StorageEntryMetadata { - name: "GETU32WITHCONFIGMYDEF", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(scale_info::meta_type::()), - default: vec![2, 0, 0, 0], - docs: vec![], - }, - StorageEntryMetadata { - name: "PUBGETU32WITHCONFIGMYDEF", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(scale_info::meta_type::()), - default: vec![1, 0, 0, 0], - docs: vec![], - }, - StorageEntryMetadata { - name: "PUBGETU32WITHCONFIGMYDEFOPT", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Plain(scale_info::meta_type::()), - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "GetU32WithBuilder", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(scale_info::meta_type::()), - default: vec![0, 0, 0, 0], - docs: vec![], - }, - StorageEntryMetadata { - name: "GetOptU32WithBuilderSome", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Plain(scale_info::meta_type::()), - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "GetOptU32WithBuilderNone", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Plain(scale_info::meta_type::()), - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "MAPU32", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Blake2_128Concat], - key: scale_info::meta_type::(), - value: scale_info::meta_type::<[u8; 4]>(), - }, - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "PUBMAPU32", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Blake2_128Concat], - key: scale_info::meta_type::(), - value: scale_info::meta_type::<[u8; 4]>(), - }, - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "GETMAPU32", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Blake2_128Concat], - key: scale_info::meta_type::(), - value: scale_info::meta_type::<[u8; 4]>(), - }, - default: vec![0, 0, 0, 0], - docs: vec![], - }, - StorageEntryMetadata { - name: "PUBGETMAPU32", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Blake2_128Concat], - key: scale_info::meta_type::(), - value: scale_info::meta_type::<[u8; 4]>(), - }, - default: vec![0, 0, 0, 0], - docs: vec![], - }, - StorageEntryMetadata { - name: "GETMAPU32MYDEF", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Blake2_128Concat], - key: scale_info::meta_type::(), - value: scale_info::meta_type::<[u8; 4]>(), - }, - default: vec![109, 97, 112, 100], // "map" - docs: vec![], - }, - StorageEntryMetadata { - name: "PUBGETMAPU32MYDEF", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Blake2_128Concat], - key: scale_info::meta_type::(), - value: scale_info::meta_type::<[u8; 4]>(), - }, - default: vec![112, 117, 98, 109], // "pubmap" - docs: vec![], - }, - StorageEntryMetadata { - name: "DOUBLEMAP", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - hashers: vec![ - StorageHasher::Blake2_128Concat, - StorageHasher::Blake2_128Concat, - ], - key: scale_info::meta_type::<(u32, u32)>(), - value: scale_info::meta_type::<[u8; 4]>(), - }, - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "DOUBLEMAP2", - modifier: StorageEntryModifier::Optional, - ty: StorageEntryType::Map { - hashers: vec![ - StorageHasher::Blake2_128Concat, - StorageHasher::Blake2_128Concat, - ], - key: scale_info::meta_type::<(u32, u32)>(), - value: scale_info::meta_type::<[u8; 4]>(), - }, - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "COMPLEXTYPE1", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(scale_info::meta_type::<(Option,)>()), - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "COMPLEXTYPE2", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(scale_info::meta_type::<( - [[(u16, Option<()>); 32]; 12], - u32, - )>()), - default: [0u8; 1156].to_vec(), - docs: vec![], - }, - StorageEntryMetadata { - name: "COMPLEXTYPE3", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(scale_info::meta_type::<[u32; 25]>()), - default: [0u8; 100].to_vec(), - docs: vec![], - }, - StorageEntryMetadata { - name: "NMAP", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { - key: scale_info::meta_type::<(u32, u16)>(), - hashers: vec![StorageHasher::Blake2_128Concat, StorageHasher::Twox64Concat], - value: scale_info::meta_type::(), - }, - default: vec![0], - docs: vec![], - }, - StorageEntryMetadata { - name: "NMAP2", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { - key: scale_info::meta_type::(), - hashers: vec![StorageHasher::Blake2_128Concat], - value: scale_info::meta_type::(), - }, - default: vec![0], - docs: vec![], - }, - ], - } - } - - #[test] - fn storage_info() { - use frame_support::{ - storage::storage_prefix as prefix, - traits::{StorageInfo, StorageInfoTrait}, - }; - - pretty_assertions::assert_eq!( - >::storage_info(), - vec![ - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"U32".to_vec(), - prefix: prefix(b"TestStorage", b"U32").to_vec(), - max_values: Some(1), - max_size: Some(4), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"PUBU32".to_vec(), - prefix: prefix(b"TestStorage", b"PUBU32").to_vec(), - max_values: Some(1), - max_size: Some(4), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"U32MYDEF".to_vec(), - prefix: prefix(b"TestStorage", b"U32MYDEF").to_vec(), - max_values: Some(1), - max_size: Some(4), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"PUBU32MYDEF".to_vec(), - prefix: prefix(b"TestStorage", b"PUBU32MYDEF").to_vec(), - max_values: Some(1), - max_size: Some(4), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"GETU32".to_vec(), - prefix: prefix(b"TestStorage", b"GETU32").to_vec(), - max_values: Some(1), - max_size: Some(4), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"PUBGETU32".to_vec(), - prefix: prefix(b"TestStorage", b"PUBGETU32").to_vec(), - max_values: Some(1), - max_size: Some(4), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"GETU32WITHCONFIG".to_vec(), - prefix: prefix(b"TestStorage", b"GETU32WITHCONFIG").to_vec(), - max_values: Some(1), - max_size: Some(4), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"PUBGETU32WITHCONFIG".to_vec(), - prefix: prefix(b"TestStorage", b"PUBGETU32WITHCONFIG").to_vec(), - max_values: Some(1), - max_size: Some(4), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"GETU32MYDEF".to_vec(), - prefix: prefix(b"TestStorage", b"GETU32MYDEF").to_vec(), - max_values: Some(1), - max_size: Some(4), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"PUBGETU32MYDEF".to_vec(), - prefix: prefix(b"TestStorage", b"PUBGETU32MYDEF").to_vec(), - max_values: Some(1), - max_size: Some(4), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"GETU32WITHCONFIGMYDEF".to_vec(), - prefix: prefix(b"TestStorage", b"GETU32WITHCONFIGMYDEF").to_vec(), - max_values: Some(1), - max_size: Some(4), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"PUBGETU32WITHCONFIGMYDEF".to_vec(), - prefix: prefix(b"TestStorage", b"PUBGETU32WITHCONFIGMYDEF").to_vec(), - max_values: Some(1), - max_size: Some(4), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"PUBGETU32WITHCONFIGMYDEFOPT".to_vec(), - prefix: prefix(b"TestStorage", b"PUBGETU32WITHCONFIGMYDEFOPT").to_vec(), - max_values: Some(1), - max_size: Some(4), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"GetU32WithBuilder".to_vec(), - prefix: prefix(b"TestStorage", b"GetU32WithBuilder").to_vec(), - max_values: Some(1), - max_size: Some(4), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"GetOptU32WithBuilderSome".to_vec(), - prefix: prefix(b"TestStorage", b"GetOptU32WithBuilderSome").to_vec(), - max_values: Some(1), - max_size: Some(4), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"GetOptU32WithBuilderNone".to_vec(), - prefix: prefix(b"TestStorage", b"GetOptU32WithBuilderNone").to_vec(), - max_values: Some(1), - max_size: Some(4), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"MAPU32".to_vec(), - prefix: prefix(b"TestStorage", b"MAPU32").to_vec(), - max_values: Some(3), - max_size: Some(8 + 16), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"PUBMAPU32".to_vec(), - prefix: prefix(b"TestStorage", b"PUBMAPU32").to_vec(), - max_values: None, - max_size: Some(8 + 16), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"GETMAPU32".to_vec(), - prefix: prefix(b"TestStorage", b"GETMAPU32").to_vec(), - max_values: None, - max_size: Some(8 + 16), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"PUBGETMAPU32".to_vec(), - prefix: prefix(b"TestStorage", b"PUBGETMAPU32").to_vec(), - max_values: None, - max_size: Some(8 + 16), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"GETMAPU32MYDEF".to_vec(), - prefix: prefix(b"TestStorage", b"GETMAPU32MYDEF").to_vec(), - max_values: None, - max_size: Some(8 + 16), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"PUBGETMAPU32MYDEF".to_vec(), - prefix: prefix(b"TestStorage", b"PUBGETMAPU32MYDEF").to_vec(), - max_values: None, - max_size: Some(8 + 16), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"DOUBLEMAP".to_vec(), - prefix: prefix(b"TestStorage", b"DOUBLEMAP").to_vec(), - max_values: Some(3), - max_size: Some(12 + 16 + 16), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"DOUBLEMAP2".to_vec(), - prefix: prefix(b"TestStorage", b"DOUBLEMAP2").to_vec(), - max_values: None, - max_size: Some(12 + 16 + 16), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"COMPLEXTYPE1".to_vec(), - prefix: prefix(b"TestStorage", b"COMPLEXTYPE1").to_vec(), - max_values: Some(1), - max_size: Some(5), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"COMPLEXTYPE2".to_vec(), - prefix: prefix(b"TestStorage", b"COMPLEXTYPE2").to_vec(), - max_values: Some(1), - max_size: Some(1156), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"COMPLEXTYPE3".to_vec(), - prefix: prefix(b"TestStorage", b"COMPLEXTYPE3").to_vec(), - max_values: Some(1), - max_size: Some(100), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"NMAP".to_vec(), - prefix: prefix(b"TestStorage", b"NMAP").to_vec(), - max_values: None, - max_size: Some(16 + 4 + 8 + 2 + 1), - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"NMAP2".to_vec(), - prefix: prefix(b"TestStorage", b"NMAP2").to_vec(), - max_values: None, - max_size: Some(16 + 4 + 1), - }, - ], - ); - } - - #[test] - fn store_metadata() { - let metadata = Module::::storage_metadata(); - pretty_assertions::assert_eq!(expected_metadata(), metadata); - } - - #[test] - fn check_genesis_config() { - let config = GenesisConfig::default(); - assert_eq!(config.u32_getter_with_config, 0u32); - assert_eq!(config.pub_u32_getter_with_config, 0u32); - - assert_eq!(config.pub_u32_getter_mydef, 3u32); - assert_eq!(config.u32_getter_with_config_mydef, 2u32); - assert_eq!(config.pub_u32_getter_with_config_mydef, 1u32); - assert_eq!(config.pub_u32_getter_with_config_mydef_opt, 0u32); - } - - #[test] - fn check_builder_config() { - let config = GenesisConfig::default(); - let storage = config.build_storage().unwrap(); - TestExternalities::from(storage).execute_with(|| { - assert_eq!(Module::::u32_with_builder(), 1); - assert_eq!(Module::::opt_u32_with_builder_some(), Some(1)); - assert_eq!(Module::::opt_u32_with_builder_none(), None); - }) - } -} - -#[cfg(test)] -#[allow(dead_code)] -mod test2 { - pub trait Config: frame_support_test::Config {} - - frame_support::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} - } - - type PairOf = (T, T); - - frame_support::decl_storage! { - trait Store for Module as TestStorage { - SingleDef : u32; - PairDef : PairOf; - Single : Option; - Pair : (u32, u32); - } - add_extra_genesis { - config(_marker) : ::std::marker::PhantomData; - config(extra_field) : u32 = 32; - build(|_| {}); - } - } - - struct TraitImpl {} - - impl frame_support_test::Config for TraitImpl { - type RuntimeOrigin = u32; - type BlockNumber = u32; - type PalletInfo = frame_support_test::PanicPalletInfo; - type DbWeight = (); - } - - impl Config for TraitImpl {} - - #[test] - fn storage_info() { - use frame_support::{ - storage::storage_prefix as prefix, - traits::{StorageInfo, StorageInfoTrait}, - }; - pretty_assertions::assert_eq!( - >::storage_info(), - vec![ - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"SingleDef".to_vec(), - prefix: prefix(b"TestStorage", b"SingleDef").to_vec(), - max_values: Some(1), - max_size: None, - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"PairDef".to_vec(), - prefix: prefix(b"TestStorage", b"PairDef").to_vec(), - max_values: Some(1), - max_size: None, - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"Single".to_vec(), - prefix: prefix(b"TestStorage", b"Single").to_vec(), - max_values: Some(1), - max_size: None, - }, - StorageInfo { - pallet_name: b"TestStorage".to_vec(), - storage_name: b"Pair".to_vec(), - prefix: prefix(b"TestStorage", b"Pair").to_vec(), - max_values: Some(1), - max_size: None, - }, - ], - ); - } -} - -#[cfg(test)] -#[allow(dead_code)] -mod test3 { - pub trait Config: frame_support_test::Config {} - - frame_support::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} - } - frame_support::decl_storage! { - trait Store for Module as Test { - Foo get(fn foo) config(initial_foo): u32; - } - } - - type PairOf = (T, T); - - struct TraitImpl {} - - impl frame_support_test::Config for TraitImpl { - type RuntimeOrigin = u32; - type BlockNumber = u32; - type PalletInfo = frame_support_test::PanicPalletInfo; - type DbWeight = (); - } - - impl Config for TraitImpl {} -} - -#[cfg(test)] -#[allow(dead_code)] -mod test_append_and_len { - use codec::{Decode, Encode}; - use sp_io::TestExternalities; - - pub trait Config: frame_support_test::Config {} - - frame_support::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} - } - - #[derive(PartialEq, Eq, Clone, Encode, Decode, scale_info::TypeInfo)] - struct NoDef(u32); - - frame_support::decl_storage! { - trait Store for Module as Test { - NoDefault: Option; - - JustVec: Vec; - JustVecWithDefault: Vec = vec![6, 9]; - OptionVec: Option>; - - MapVec: map hasher(blake2_128_concat) u32 => Vec; - MapVecWithDefault: map hasher(blake2_128_concat) u32 => Vec = vec![6, 9]; - OptionMapVec: map hasher(blake2_128_concat) u32 => Option>; - - DoubleMapVec: double_map hasher(blake2_128_concat) u32, hasher(blake2_128_concat) u32 => Vec; - DoubleMapVecWithDefault: double_map hasher(blake2_128_concat) u32, hasher(blake2_128_concat) u32 => Vec = vec![6, 9]; - OptionDoubleMapVec: double_map hasher(blake2_128_concat) u32, hasher(blake2_128_concat) u32 => Option>; - } - } - - struct Test {} - - impl frame_support_test::Config for Test { - type RuntimeOrigin = u32; - type BlockNumber = u32; - type PalletInfo = frame_support_test::PanicPalletInfo; - type DbWeight = (); - } - - impl Config for Test {} - - #[test] - fn default_for_option() { - TestExternalities::default().execute_with(|| { - assert_eq!(OptionVec::get(), None); - assert!(JustVec::get().is_empty()); - }); - } - - #[test] - fn append_works() { - TestExternalities::default().execute_with(|| { - for val in &[1, 2, 3, 4, 5] { - MapVec::append(1, val); - } - assert_eq!(MapVec::get(1), vec![1, 2, 3, 4, 5]); - - MapVec::remove(1); - MapVec::append(1, 1); - assert_eq!(MapVec::get(1), vec![1]); - - for val in &[1, 2, 3, 4, 5] { - JustVec::append(val); - } - assert_eq!(JustVec::get(), vec![1, 2, 3, 4, 5]); - - JustVec::kill(); - JustVec::append(1); - assert_eq!(JustVec::get(), vec![1]); - }); - } - - #[test] - fn append_overwrites_invalid_data() { - TestExternalities::default().execute_with(|| { - let key = JustVec::hashed_key(); - // Set it to some invalid value. - frame_support::storage::unhashed::put_raw(&key, b"1"); - assert!(JustVec::get().is_empty()); - assert_eq!(frame_support::storage::unhashed::get_raw(&key), Some(b"1".to_vec())); - - JustVec::append(1); - JustVec::append(2); - assert_eq!(JustVec::get(), vec![1, 2]); - }); - } - - #[test] - fn append_overwrites_default() { - TestExternalities::default().execute_with(|| { - assert_eq!(JustVecWithDefault::get(), vec![6, 9]); - JustVecWithDefault::append(1); - assert_eq!(JustVecWithDefault::get(), vec![1]); - - assert_eq!(MapVecWithDefault::get(0), vec![6, 9]); - MapVecWithDefault::append(0, 1); - assert_eq!(MapVecWithDefault::get(0), vec![1]); - - assert_eq!(OptionVec::get(), None); - OptionVec::append(1); - assert_eq!(OptionVec::get(), Some(vec![1])); - }); - } - - #[test] - fn len_works() { - TestExternalities::default().execute_with(|| { - JustVec::put(&vec![1, 2, 3, 4]); - OptionVec::put(&vec![1, 2, 3, 4, 5]); - MapVec::insert(1, &vec![1, 2, 3, 4, 5, 6]); - DoubleMapVec::insert(0, 1, &vec![1, 2]); - - assert_eq!(JustVec::decode_len().unwrap(), 4); - assert_eq!(OptionVec::decode_len().unwrap(), 5); - assert_eq!(MapVec::decode_len(1).unwrap(), 6); - assert_eq!(DoubleMapVec::decode_len(0, 1).unwrap(), 2); - }); - } - - // `decode_len` should always return `None` for default assigments - // in `decl_storage!`. - #[test] - fn len_works_ignores_default_assignment() { - TestExternalities::default().execute_with(|| { - // vec - assert!(JustVec::get().is_empty()); - assert_eq!(JustVec::decode_len(), None); - - assert_eq!(JustVecWithDefault::get(), vec![6, 9]); - assert_eq!(JustVecWithDefault::decode_len(), None); - - assert_eq!(OptionVec::get(), None); - assert_eq!(OptionVec::decode_len(), None); - - // map - assert!(MapVec::get(0).is_empty()); - assert_eq!(MapVec::decode_len(0), None); - - assert_eq!(MapVecWithDefault::get(0), vec![6, 9]); - assert_eq!(MapVecWithDefault::decode_len(0), None); - - assert_eq!(OptionMapVec::get(0), None); - assert_eq!(OptionMapVec::decode_len(0), None); - - // Double map - assert!(DoubleMapVec::get(0, 0).is_empty()); - assert_eq!(DoubleMapVec::decode_len(0, 1), None); - - assert_eq!(DoubleMapVecWithDefault::get(0, 0), vec![6, 9]); - assert_eq!(DoubleMapVecWithDefault::decode_len(0, 1), None); - - assert_eq!(OptionDoubleMapVec::get(0, 0), None); - assert_eq!(OptionDoubleMapVec::decode_len(0, 1), None); - }); - } -} diff --git a/frame/support/test/tests/decl_storage_ui/config_duplicate.stderr b/frame/support/test/tests/decl_storage_ui/config_duplicate.stderr deleted file mode 100644 index f6303f277b56..000000000000 --- a/frame/support/test/tests/decl_storage_ui/config_duplicate.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: `config()`/`get()` with the same name already defined. - --> $DIR/config_duplicate.rs:27:21 - | -27 | pub Value2 config(value): u32; - | ^^^^^ diff --git a/frame/support/test/tests/decl_storage_ui/config_get_duplicate.stderr b/frame/support/test/tests/decl_storage_ui/config_get_duplicate.stderr deleted file mode 100644 index 9377b718c066..000000000000 --- a/frame/support/test/tests/decl_storage_ui/config_get_duplicate.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: `config()`/`get()` with the same name already defined. - --> $DIR/config_get_duplicate.rs:27:21 - | -27 | pub Value2 config(value): u32; - | ^^^^^ diff --git a/frame/support/test/tests/decl_storage_ui/get_duplicate.stderr b/frame/support/test/tests/decl_storage_ui/get_duplicate.stderr deleted file mode 100644 index 0039b10fb43b..000000000000 --- a/frame/support/test/tests/decl_storage_ui/get_duplicate.stderr +++ /dev/null @@ -1,5 +0,0 @@ -error: `config()`/`get()` with the same name already defined. - --> $DIR/get_duplicate.rs:27:21 - | -27 | pub Value2 get(fn value) config(): u32; - | ^^^^^ diff --git a/frame/support/test/tests/decl_storage_ui.rs b/frame/support/test/tests/derive_impl_ui.rs similarity index 73% rename from frame/support/test/tests/decl_storage_ui.rs rename to frame/support/test/tests/derive_impl_ui.rs index 34dfea8601ab..ee219d0670aa 100644 --- a/frame/support/test/tests/decl_storage_ui.rs +++ b/frame/support/test/tests/derive_impl_ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,10 +15,12 @@ // See the License for the specific language governing permissions and // limitations under the License. +#![cfg(not(feature = "disable-ui-tests"))] +#![cfg(test)] + #[rustversion::attr(not(stable), ignore)] -#[cfg(not(feature = "disable-ui-tests"))] #[test] -fn decl_storage_ui() { +fn derive_impl_ui() { // Only run the ui tests when `RUN_UI_TESTS` is set. if std::env::var("RUN_UI_TESTS").is_err() { return @@ -27,6 +29,10 @@ fn decl_storage_ui() { // As trybuild is using `cargo check`, we don't need the real WASM binaries. std::env::set_var("SKIP_WASM_BUILD", "1"); + // Deny all warnings since we emit warnings as part of a Pallet's UI. + std::env::set_var("RUSTFLAGS", "--deny warnings"); + let t = trybuild::TestCases::new(); - t.compile_fail("tests/decl_storage_ui/*.rs"); + t.compile_fail("tests/derive_impl_ui/*.rs"); + t.pass("tests/derive_impl_ui/pass/*.rs"); } diff --git a/frame/support/test/tests/derive_impl_ui/attached_to_non_impl.rs b/frame/support/test/tests/derive_impl_ui/attached_to_non_impl.rs new file mode 100644 index 000000000000..3b2791693386 --- /dev/null +++ b/frame/support/test/tests/derive_impl_ui/attached_to_non_impl.rs @@ -0,0 +1,41 @@ +use frame_support::*; + +pub trait Animal { + type Locomotion; + type Diet; + type SleepingStrategy; + type Environment; + + fn animal_name() -> &'static str; +} + +pub type RunsOnFourLegs = (usize, usize, usize, usize); +pub type RunsOnTwoLegs = (usize, usize); +pub type Swims = isize; +pub type Diurnal = bool; +pub type Nocturnal = Option; +pub type Omnivore = char; +pub type Land = ((), ()); +pub type Sea = ((), (), ()); +pub type Carnivore = (char, char); + +pub struct FourLeggedAnimal {} + +#[register_default_impl(FourLeggedAnimal)] +impl Animal for FourLeggedAnimal { + type Locomotion = RunsOnFourLegs; + type Diet = Omnivore; + type SleepingStrategy = Diurnal; + type Environment = Land; + + fn animal_name() -> &'static str { + "A Four-Legged Animal" + } +} + +pub struct AcquaticMammal {} + +#[derive_impl(FourLeggedAnimal as Animal)] +struct Something {} + +fn main() {} diff --git a/frame/support/test/tests/derive_impl_ui/attached_to_non_impl.stderr b/frame/support/test/tests/derive_impl_ui/attached_to_non_impl.stderr new file mode 100644 index 000000000000..735fd7a628e7 --- /dev/null +++ b/frame/support/test/tests/derive_impl_ui/attached_to_non_impl.stderr @@ -0,0 +1,5 @@ +error: expected `impl` + --> tests/derive_impl_ui/attached_to_non_impl.rs:39:1 + | +39 | struct Something {} + | ^^^^^^ diff --git a/frame/support/test/tests/derive_impl_ui/bad_default_impl_path.rs b/frame/support/test/tests/derive_impl_ui/bad_default_impl_path.rs new file mode 100644 index 000000000000..2badd1830033 --- /dev/null +++ b/frame/support/test/tests/derive_impl_ui/bad_default_impl_path.rs @@ -0,0 +1,48 @@ +use frame_support::*; + +pub trait Animal { + type Locomotion; + type Diet; + type SleepingStrategy; + type Environment; + + fn animal_name() -> &'static str; +} + +pub type RunsOnFourLegs = (usize, usize, usize, usize); +pub type RunsOnTwoLegs = (usize, usize); +pub type Swims = isize; +pub type Diurnal = bool; +pub type Nocturnal = Option; +pub type Omnivore = char; +pub type Land = ((), ()); +pub type Sea = ((), (), ()); +pub type Carnivore = (char, char); + +pub struct FourLeggedAnimal {} + +#[register_default_impl(FourLeggedAnimal)] +impl Animal for FourLeggedAnimal { + type Locomotion = RunsOnFourLegs; + type Diet = Omnivore; + type SleepingStrategy = Diurnal; + type Environment = Land; + + fn animal_name() -> &'static str { + "A Four-Legged Animal" + } +} + +pub struct AcquaticMammal {} + +// Should throw: `error: cannot find macro `__export_tokens_tt_tiger` in this scope` +// +// Note that there is really no better way to clean up this error, tt_call suffers from the +// same downside but this is really the only rough edge when using macro magic. +#[derive_impl(Tiger as Animal)] +impl Animal for AcquaticMammal { + type Locomotion = (Swims, RunsOnFourLegs); + type Environment = (Land, Sea); +} + +fn main() {} diff --git a/frame/support/test/tests/derive_impl_ui/bad_default_impl_path.stderr b/frame/support/test/tests/derive_impl_ui/bad_default_impl_path.stderr new file mode 100644 index 000000000000..1cac16624627 --- /dev/null +++ b/frame/support/test/tests/derive_impl_ui/bad_default_impl_path.stderr @@ -0,0 +1,7 @@ +error: cannot find macro `__export_tokens_tt_tiger` in this scope + --> tests/derive_impl_ui/bad_default_impl_path.rs:42:1 + | +42 | #[derive_impl(Tiger as Animal)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `frame_support::macro_magic::forward_tokens` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/derive_impl_ui/bad_disambiguation_path.rs b/frame/support/test/tests/derive_impl_ui/bad_disambiguation_path.rs new file mode 100644 index 000000000000..adc5df23a759 --- /dev/null +++ b/frame/support/test/tests/derive_impl_ui/bad_disambiguation_path.rs @@ -0,0 +1,44 @@ +use frame_support::*; + +pub trait Animal { + type Locomotion; + type Diet; + type SleepingStrategy; + type Environment; + + fn animal_name() -> &'static str; +} + +pub type RunsOnFourLegs = (usize, usize, usize, usize); +pub type RunsOnTwoLegs = (usize, usize); +pub type Swims = isize; +pub type Diurnal = bool; +pub type Nocturnal = Option; +pub type Omnivore = char; +pub type Land = ((), ()); +pub type Sea = ((), (), ()); +pub type Carnivore = (char, char); + +pub struct FourLeggedAnimal {} + +#[register_default_impl(FourLeggedAnimal)] +impl Animal for FourLeggedAnimal { + type Locomotion = RunsOnFourLegs; + type Diet = Omnivore; + type SleepingStrategy = Diurnal; + type Environment = Land; + + fn animal_name() -> &'static str { + "A Four-Legged Animal" + } +} + +pub struct AcquaticMammal {} + +#[derive_impl(FourLeggedAnimal as Insect)] +impl Animal for AcquaticMammal { + type Locomotion = (Swims, RunsOnFourLegs); + type Environment = (Land, Sea); +} + +fn main() {} diff --git a/frame/support/test/tests/derive_impl_ui/bad_disambiguation_path.stderr b/frame/support/test/tests/derive_impl_ui/bad_disambiguation_path.stderr new file mode 100644 index 000000000000..6fd4e431beb5 --- /dev/null +++ b/frame/support/test/tests/derive_impl_ui/bad_disambiguation_path.stderr @@ -0,0 +1,5 @@ +error[E0405]: cannot find trait `Insect` in this scope + --> tests/derive_impl_ui/bad_disambiguation_path.rs:38:35 + | +38 | #[derive_impl(FourLeggedAnimal as Insect)] + | ^^^^^^ not found in this scope diff --git a/frame/support/test/tests/derive_impl_ui/missing_disambiguation_path.rs b/frame/support/test/tests/derive_impl_ui/missing_disambiguation_path.rs new file mode 100644 index 000000000000..21f1cc32009a --- /dev/null +++ b/frame/support/test/tests/derive_impl_ui/missing_disambiguation_path.rs @@ -0,0 +1,44 @@ +use frame_support::*; + +pub trait Animal { + type Locomotion; + type Diet; + type SleepingStrategy; + type Environment; + + fn animal_name() -> &'static str; +} + +pub type RunsOnFourLegs = (usize, usize, usize, usize); +pub type RunsOnTwoLegs = (usize, usize); +pub type Swims = isize; +pub type Diurnal = bool; +pub type Nocturnal = Option; +pub type Omnivore = char; +pub type Land = ((), ()); +pub type Sea = ((), (), ()); +pub type Carnivore = (char, char); + +pub struct FourLeggedAnimal {} + +#[register_default_impl(FourLeggedAnimal)] +impl Animal for FourLeggedAnimal { + type Locomotion = RunsOnFourLegs; + type Diet = Omnivore; + type SleepingStrategy = Diurnal; + type Environment = Land; + + fn animal_name() -> &'static str { + "A Four-Legged Animal" + } +} + +pub struct AcquaticMammal {} + +#[derive_impl(FourLeggedAnimal as)] +impl Animal for AcquaticMammal { + type Locomotion = (Swims, RunsOnFourLegs); + type Environment = (Land, Sea); +} + +fn main() {} diff --git a/frame/support/test/tests/derive_impl_ui/missing_disambiguation_path.stderr b/frame/support/test/tests/derive_impl_ui/missing_disambiguation_path.stderr new file mode 100644 index 000000000000..85cd94ae08ae --- /dev/null +++ b/frame/support/test/tests/derive_impl_ui/missing_disambiguation_path.stderr @@ -0,0 +1,7 @@ +error: unexpected end of input, expected identifier + --> tests/derive_impl_ui/missing_disambiguation_path.rs:38:1 + | +38 | #[derive_impl(FourLeggedAnimal as)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the attribute macro `derive_impl` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/derive_impl_ui/pass/basic_overriding.rs b/frame/support/test/tests/derive_impl_ui/pass/basic_overriding.rs new file mode 100644 index 000000000000..336ddc315f8c --- /dev/null +++ b/frame/support/test/tests/derive_impl_ui/pass/basic_overriding.rs @@ -0,0 +1,69 @@ +use frame_support::*; +use static_assertions::assert_type_eq_all; + +pub trait Animal { + type Locomotion; + type Diet; + type SleepingStrategy; + type Environment; + + fn animal_name() -> &'static str; +} + +pub type RunsOnFourLegs = (usize, usize, usize, usize); +pub type RunsOnTwoLegs = (usize, usize); +pub type Swims = isize; +pub type Diurnal = bool; +pub type Nocturnal = Option; +pub type Omnivore = char; +pub type Land = ((), ()); +pub type Sea = ((), (), ()); +pub type Carnivore = (char, char); + +pub struct FourLeggedAnimal {} + +#[register_default_impl(FourLeggedAnimal)] +impl Animal for FourLeggedAnimal { + type Locomotion = RunsOnFourLegs; + type Diet = Omnivore; + type SleepingStrategy = Diurnal; + type Environment = Land; + + fn animal_name() -> &'static str { + "A Four-Legged Animal" + } +} + +pub struct AcquaticMammal {} + +// without omitting the `as X` +#[derive_impl(FourLeggedAnimal as Animal)] +impl Animal for AcquaticMammal { + type Locomotion = (Swims, RunsOnFourLegs); + type Environment = (Land, Sea); +} + +assert_type_eq_all!(::Locomotion, (Swims, RunsOnFourLegs)); +assert_type_eq_all!(::Environment, (Land, Sea)); +assert_type_eq_all!(::Diet, Omnivore); +assert_type_eq_all!(::SleepingStrategy, Diurnal); + +pub struct Lion {} + +// test omitting the `as X` +#[derive_impl(FourLeggedAnimal)] +impl Animal for Lion { + type Diet = Carnivore; + type SleepingStrategy = Nocturnal; + + fn animal_name() -> &'static str { + "Lion" + } +} + +assert_type_eq_all!(::Diet, Carnivore); +assert_type_eq_all!(::SleepingStrategy, Nocturnal); +assert_type_eq_all!(::Environment, Land); +assert_type_eq_all!(::Locomotion, RunsOnFourLegs); + +fn main() {} diff --git a/frame/support/test/tests/derive_impl_ui/pass/macro_magic_working.rs b/frame/support/test/tests/derive_impl_ui/pass/macro_magic_working.rs new file mode 100644 index 000000000000..ec09bd15e017 --- /dev/null +++ b/frame/support/test/tests/derive_impl_ui/pass/macro_magic_working.rs @@ -0,0 +1,18 @@ +#[frame_support::macro_magic::export_tokens] +struct MyCoolStruct { + field: u32, +} + +// create a test receiver since `proc_support` isn't enabled so we're on our own in terms of +// what we can call +macro_rules! receiver { + ($_tokens_var:ident, $($tokens:tt)*) => { + stringify!($($tokens)*) + }; +} + +fn main() { + let _instance: MyCoolStruct = MyCoolStruct { field: 3 }; + let _str = __export_tokens_tt_my_cool_struct!(tokens, receiver); + // this compiling demonstrates that macro_magic is working properly +} diff --git a/frame/support/test/tests/derive_no_bound.rs b/frame/support/test/tests/derive_no_bound.rs index 9162b5013bcd..dc78027f22eb 100644 --- a/frame/support/test/tests/derive_no_bound.rs +++ b/frame/support/test/tests/derive_no_bound.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,6 +50,23 @@ struct StructNamed { phantom: core::marker::PhantomData<(U, V)>, } +#[rustversion::attr(not(stable), ignore)] +#[cfg(not(feature = "disable-ui-tests"))] +#[test] +fn test_struct_named_debug_print() { + let a_1 = StructNamed:: { + a: 1, + b: 2, + c: 3, + phantom: Default::default(), + }; + + assert_eq!( + format!("{:?}", a_1), + String::from("StructNamed { a: 1, b: 2, c: 3, phantom: PhantomData<(derive_no_bound::ImplNone, derive_no_bound::ImplNone)> }") + ); +} + #[test] fn test_struct_named() { let a_1 = StructNamed:: { @@ -70,10 +87,6 @@ fn test_struct_named() { assert_eq!(a_2.b, 2); assert_eq!(a_2.c, 3); assert_eq!(a_2, a_1); - assert_eq!( - format!("{:?}", a_1), - String::from("StructNamed { a: 1, b: 2, c: 3, phantom: PhantomData<(derive_no_bound::ImplNone, derive_no_bound::ImplNone)> }") - ); let b = StructNamed:: { a: 1, @@ -88,6 +101,14 @@ fn test_struct_named() { #[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] struct StructUnnamed(u32, u64, T::C, core::marker::PhantomData<(U, V)>); +#[rustversion::attr(not(stable), ignore)] +#[cfg(not(feature = "disable-ui-tests"))] +#[test] +fn test_struct_unnamed_debug_print() { + let a_1 = StructUnnamed::(1, 2, 3, Default::default()); + assert_eq!(format!("{:?}", a_1), String::from("StructUnnamed(1, 2, 3, PhantomData<(derive_no_bound::ImplNone, derive_no_bound::ImplNone)>)")); +} + #[test] fn test_struct_unnamed() { let a_1 = StructUnnamed::(1, 2, 3, Default::default()); @@ -103,7 +124,6 @@ fn test_struct_unnamed() { assert_eq!(a_2.1, 2); assert_eq!(a_2.2, 3); assert_eq!(a_2, a_1); - assert_eq!(format!("{:?}", a_1), String::from("StructUnnamed(1, 2, 3, PhantomData<(derive_no_bound::ImplNone, derive_no_bound::ImplNone)>)")); let b = StructUnnamed::(1, 2, 4, Default::default()); @@ -169,6 +189,28 @@ enum Enum3 { VariantUnit2, } +#[rustversion::attr(not(stable), ignore)] +#[cfg(not(feature = "disable-ui-tests"))] +#[test] +fn test_enum_debug_print() { + type TestEnum = Enum; + let variant_0 = TestEnum::VariantUnnamed(1, 2, 3, Default::default()); + let variant_1 = TestEnum::VariantNamed { a: 1, b: 2, c: 3, phantom: Default::default() }; + let variant_2 = TestEnum::VariantUnit; + let variant_3 = TestEnum::VariantUnit2; + + assert_eq!( + format!("{:?}", variant_0), + String::from("Enum::VariantUnnamed(1, 2, 3, PhantomData<(derive_no_bound::ImplNone, derive_no_bound::ImplNone)>)"), + ); + assert_eq!( + format!("{:?}", variant_1), + String::from("Enum::VariantNamed { a: 1, b: 2, c: 3, phantom: PhantomData<(derive_no_bound::ImplNone, derive_no_bound::ImplNone)> }"), + ); + assert_eq!(format!("{:?}", variant_2), String::from("Enum::VariantUnit")); + assert_eq!(format!("{:?}", variant_3), String::from("Enum::VariantUnit2")); +} + #[test] fn test_enum() { type TestEnum = Enum; @@ -208,15 +250,4 @@ fn test_enum() { assert!(variant_1.clone() == variant_1); assert!(variant_2.clone() == variant_2); assert!(variant_3.clone() == variant_3); - - assert_eq!( - format!("{:?}", variant_0), - String::from("Enum::VariantUnnamed(1, 2, 3, PhantomData<(derive_no_bound::ImplNone, derive_no_bound::ImplNone)>)"), - ); - assert_eq!( - format!("{:?}", variant_1), - String::from("Enum::VariantNamed { a: 1, b: 2, c: 3, phantom: PhantomData<(derive_no_bound::ImplNone, derive_no_bound::ImplNone)> }"), - ); - assert_eq!(format!("{:?}", variant_2), String::from("Enum::VariantUnit")); - assert_eq!(format!("{:?}", variant_3), String::from("Enum::VariantUnit2")); } diff --git a/frame/support/test/tests/derive_no_bound_ui.rs b/frame/support/test/tests/derive_no_bound_ui.rs index d714e1113625..b1c9283c0925 100644 --- a/frame/support/test/tests/derive_no_bound_ui.rs +++ b/frame/support/test/tests/derive_no_bound_ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/test/tests/derive_no_bound_ui/eq.stderr b/frame/support/test/tests/derive_no_bound_ui/eq.stderr index 97ac29f96902..eb3345eede50 100644 --- a/frame/support/test/tests/derive_no_bound_ui/eq.stderr +++ b/frame/support/test/tests/derive_no_bound_ui/eq.stderr @@ -1,12 +1,12 @@ error[E0277]: can't compare `Foo` with `Foo` - --> tests/derive_no_bound_ui/eq.rs:6:8 - | -6 | struct Foo { - | ^^^ no implementation for `Foo == Foo` - | - = help: the trait `PartialEq` is not implemented for `Foo` + --> tests/derive_no_bound_ui/eq.rs:6:8 + | +6 | struct Foo { + | ^^^^^^^^^^^^^^ no implementation for `Foo == Foo` + | + = help: the trait `PartialEq` is not implemented for `Foo` note: required by a bound in `std::cmp::Eq` - --> $RUST/core/src/cmp.rs - | - | pub trait Eq: PartialEq { - | ^^^^^^^^^^^^^^^ required by this bound in `std::cmp::Eq` + --> $RUST/core/src/cmp.rs + | + | pub trait Eq: PartialEq { + | ^^^^^^^^^^^^^^^ required by this bound in `Eq` diff --git a/frame/support/test/tests/final_keys.rs b/frame/support/test/tests/final_keys.rs index a7ad88c80100..765afaf1e660 100644 --- a/frame/support/test/tests/final_keys.rs +++ b/frame/support/test/tests/final_keys.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,65 +16,166 @@ // limitations under the License. use codec::Encode; -use frame_support::{ - storage::unhashed, StorageDoubleMap, StorageMap, StoragePrefixedMap, StorageValue, -}; +use frame_support::{derive_impl, storage::unhashed, StoragePrefixedMap}; +use frame_system::pallet_prelude::BlockNumberFor; + +use sp_core::{sr25519, ConstU32}; use sp_io::{ hashing::{blake2_128, twox_128, twox_64}, TestExternalities, }; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, Verify}, +}; +#[frame_support::pallet] mod no_instance { - pub trait Config: frame_support_test::Config {} - - frame_support::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::call] + impl Pallet {} + + #[pallet::storage] + pub type Value = StorageValue<_, u32, ValueQuery>; + + #[pallet::storage] + pub type Map = StorageMap<_, Blake2_128Concat, u32, u32, ValueQuery>; + #[pallet::storage] + pub type Map2 = StorageMap<_, Twox64Concat, u32, u32, ValueQuery>; + + #[pallet::storage] + pub type DoubleMap = + StorageDoubleMap<_, Blake2_128Concat, u32, Blake2_128Concat, u32, u32, ValueQuery>; + #[pallet::storage] + pub type DoubleMap2 = + StorageDoubleMap<_, Twox64Concat, u32, Twox64Concat, u32, u32, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn test_generic_value)] + pub type TestGenericValue = StorageValue<_, BlockNumberFor, OptionQuery>; + #[pallet::storage] + #[pallet::getter(fn foo2)] + pub type TestGenericDoubleMap = StorageDoubleMap< + _, + Blake2_128Concat, + u32, + Blake2_128Concat, + BlockNumberFor, + u32, + ValueQuery, + >; + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub value: u32, + pub test_generic_value: BlockNumberFor, + pub test_generic_double_map: Vec<(u32, BlockNumberFor, u32)>, } - frame_support::decl_storage! { - trait Store for Module as FinalKeysNone { - pub Value config(value): u32; - - pub Map: map hasher(blake2_128_concat) u32 => u32; - pub Map2: map hasher(twox_64_concat) u32 => u32; - - pub DoubleMap: double_map hasher(blake2_128_concat) u32, hasher(blake2_128_concat) u32 => u32; - pub DoubleMap2: double_map hasher(twox_64_concat) u32, hasher(twox_64_concat) u32 => u32; + impl Default for GenesisConfig { + fn default() -> Self { + Self { + value: Default::default(), + test_generic_value: Default::default(), + test_generic_double_map: Default::default(), + } + } + } - pub TestGenericValue get(fn test_generic_value) config(): Option; - pub TestGenericDoubleMap get(fn foo2) config(test_generic_double_map): - double_map hasher(blake2_128_concat) u32, hasher(blake2_128_concat) T::BlockNumber => Option; + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + >::put(self.value); + >::put(&self.test_generic_value); + for (k1, k2, v) in &self.test_generic_double_map { + >::insert(k1, k2, v); + } } } } +#[frame_support::pallet] mod instance { - pub trait Config: frame_support_test::Config {} - - frame_support::decl_module! { - pub struct Module, I: Instance = DefaultInstance> - for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::call] + impl, I: 'static> Pallet {} + + #[pallet::storage] + pub type Value, I: 'static = ()> = StorageValue<_, u32, ValueQuery>; + + #[pallet::storage] + pub type Map, I: 'static = ()> = + StorageMap<_, Blake2_128Concat, u32, u32, ValueQuery>; + #[pallet::storage] + pub type Map2, I: 'static = ()> = + StorageMap<_, Twox64Concat, u32, u32, ValueQuery>; + + #[pallet::storage] + pub type DoubleMap, I: 'static = ()> = + StorageDoubleMap<_, Blake2_128Concat, u32, Blake2_128Concat, u32, u32, ValueQuery>; + #[pallet::storage] + pub type DoubleMap2, I: 'static = ()> = + StorageDoubleMap<_, Twox64Concat, u32, Twox64Concat, u32, u32, ValueQuery>; + + #[pallet::storage] + #[pallet::getter(fn test_generic_value)] + pub type TestGenericValue, I: 'static = ()> = + StorageValue<_, BlockNumberFor, OptionQuery>; + #[pallet::storage] + #[pallet::getter(fn foo2)] + pub type TestGenericDoubleMap, I: 'static = ()> = StorageDoubleMap< + _, + Blake2_128Concat, + u32, + Blake2_128Concat, + BlockNumberFor, + u32, + ValueQuery, + >; + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + pub value: u32, + pub test_generic_value: BlockNumberFor, + pub test_generic_double_map: Vec<(u32, BlockNumberFor, u32)>, + pub phantom: PhantomData, } - frame_support::decl_storage! { - trait Store for Module, I: Instance = DefaultInstance> - as FinalKeysSome - { - pub Value config(value): u32; - - pub Map: map hasher(blake2_128_concat) u32 => u32; - pub Map2: map hasher(twox_64_concat) u32 => u32; - - pub DoubleMap: double_map hasher(blake2_128_concat) u32, hasher(blake2_128_concat) u32 => u32; - pub DoubleMap2: double_map hasher(twox_64_concat) u32, hasher(twox_64_concat) u32 => u32; - - pub TestGenericValue get(fn test_generic_value) config(): Option; - pub TestGenericDoubleMap get(fn foo2) config(test_generic_double_map): - double_map hasher(blake2_128_concat) u32, hasher(blake2_128_concat) T::BlockNumber => Option; + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + Self { + value: Default::default(), + test_generic_value: Default::default(), + test_generic_double_map: Default::default(), + phantom: Default::default(), + } } - add_extra_genesis { - // See `decl_storage` limitation. - config(phantom): core::marker::PhantomData; + } + + #[pallet::genesis_build] + impl, I: 'static> BuildGenesisConfig for GenesisConfig { + fn build(&self) { + >::put(self.value); + >::put(&self.test_generic_value); + for (k1, k2, v) in &self.test_generic_double_map { + >::insert(k1, k2, v); + } } } } @@ -91,107 +192,142 @@ fn blake2_128_concat(d: &[u8]) -> Vec { v } +pub type BlockNumber = u32; +pub type Signature = sr25519::Signature; +pub type AccountId = ::Signer; +pub type Header = generic::Header; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type Block = generic::Block; + +frame_support::construct_runtime!( + pub enum Runtime + + { + System: frame_system, + FinalKeysNone: no_instance, + FinalKeysSome: instance, + Instance2FinalKeysSome: instance::, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type Block = Block; + type BlockHashCount = ConstU32<10>; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type OnSetCode = (); +} + +impl no_instance::Config for Runtime {} + +impl instance::Config for Runtime {} +impl instance::Config for Runtime {} + #[test] fn final_keys_no_instance() { TestExternalities::default().execute_with(|| { - no_instance::Value::put(1); + >::put(1); let k = [twox_128(b"FinalKeysNone"), twox_128(b"Value")].concat(); assert_eq!(unhashed::get::(&k), Some(1u32)); - no_instance::Map::insert(1, 2); + >::insert(1, 2); let mut k = [twox_128(b"FinalKeysNone"), twox_128(b"Map")].concat(); k.extend(1u32.using_encoded(blake2_128_concat)); assert_eq!(unhashed::get::(&k), Some(2u32)); - assert_eq!(&k[..32], &::final_prefix()); + assert_eq!(&k[..32], &>::final_prefix()); - no_instance::Map2::insert(1, 2); + >::insert(1, 2); let mut k = [twox_128(b"FinalKeysNone"), twox_128(b"Map2")].concat(); k.extend(1u32.using_encoded(twox_64_concat)); assert_eq!(unhashed::get::(&k), Some(2u32)); - assert_eq!(&k[..32], &::final_prefix()); + assert_eq!(&k[..32], &>::final_prefix()); - no_instance::DoubleMap::insert(&1, &2, &3); + >::insert(&1, &2, &3); let mut k = [twox_128(b"FinalKeysNone"), twox_128(b"DoubleMap")].concat(); k.extend(1u32.using_encoded(blake2_128_concat)); k.extend(2u32.using_encoded(blake2_128_concat)); assert_eq!(unhashed::get::(&k), Some(3u32)); - assert_eq!(&k[..32], &::final_prefix()); + assert_eq!(&k[..32], &>::final_prefix()); - no_instance::DoubleMap2::insert(&1, &2, &3); + >::insert(&1, &2, &3); let mut k = [twox_128(b"FinalKeysNone"), twox_128(b"DoubleMap2")].concat(); k.extend(1u32.using_encoded(twox_64_concat)); k.extend(2u32.using_encoded(twox_64_concat)); assert_eq!(unhashed::get::(&k), Some(3u32)); - assert_eq!(&k[..32], &::final_prefix()); + assert_eq!(&k[..32], &>::final_prefix()); }); } #[test] fn final_keys_default_instance() { TestExternalities::default().execute_with(|| { - >::put(1); + >::put(1); let k = [twox_128(b"FinalKeysSome"), twox_128(b"Value")].concat(); assert_eq!(unhashed::get::(&k), Some(1u32)); - >::insert(1, 2); + >::insert(1, 2); let mut k = [twox_128(b"FinalKeysSome"), twox_128(b"Map")].concat(); k.extend(1u32.using_encoded(blake2_128_concat)); assert_eq!(unhashed::get::(&k), Some(2u32)); - assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!(&k[..32], &>::final_prefix()); - >::insert(1, 2); + >::insert(1, 2); let mut k = [twox_128(b"FinalKeysSome"), twox_128(b"Map2")].concat(); k.extend(1u32.using_encoded(twox_64_concat)); assert_eq!(unhashed::get::(&k), Some(2u32)); - assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!(&k[..32], &>::final_prefix()); - >::insert(&1, &2, &3); + >::insert(&1, &2, &3); let mut k = [twox_128(b"FinalKeysSome"), twox_128(b"DoubleMap")].concat(); k.extend(1u32.using_encoded(blake2_128_concat)); k.extend(2u32.using_encoded(blake2_128_concat)); assert_eq!(unhashed::get::(&k), Some(3u32)); - assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!(&k[..32], &>::final_prefix()); - >::insert(&1, &2, &3); + >::insert(&1, &2, &3); let mut k = [twox_128(b"FinalKeysSome"), twox_128(b"DoubleMap2")].concat(); k.extend(1u32.using_encoded(twox_64_concat)); k.extend(2u32.using_encoded(twox_64_concat)); assert_eq!(unhashed::get::(&k), Some(3u32)); - assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!(&k[..32], &>::final_prefix()); }); } #[test] fn final_keys_instance_2() { TestExternalities::default().execute_with(|| { - >::put(1); + >::put(1); let k = [twox_128(b"Instance2FinalKeysSome"), twox_128(b"Value")].concat(); assert_eq!(unhashed::get::(&k), Some(1u32)); - >::insert(1, 2); + >::insert(1, 2); let mut k = [twox_128(b"Instance2FinalKeysSome"), twox_128(b"Map")].concat(); k.extend(1u32.using_encoded(blake2_128_concat)); assert_eq!(unhashed::get::(&k), Some(2u32)); - assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!(&k[..32], &>::final_prefix()); - >::insert(1, 2); + >::insert(1, 2); let mut k = [twox_128(b"Instance2FinalKeysSome"), twox_128(b"Map2")].concat(); k.extend(1u32.using_encoded(twox_64_concat)); assert_eq!(unhashed::get::(&k), Some(2u32)); - assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!(&k[..32], &>::final_prefix()); - >::insert(&1, &2, &3); + >::insert(&1, &2, &3); let mut k = [twox_128(b"Instance2FinalKeysSome"), twox_128(b"DoubleMap")].concat(); k.extend(1u32.using_encoded(blake2_128_concat)); k.extend(2u32.using_encoded(blake2_128_concat)); assert_eq!(unhashed::get::(&k), Some(3u32)); - assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!(&k[..32], &>::final_prefix()); - >::insert(&1, &2, &3); + >::insert(&1, &2, &3); let mut k = [twox_128(b"Instance2FinalKeysSome"), twox_128(b"DoubleMap2")].concat(); k.extend(1u32.using_encoded(twox_64_concat)); k.extend(2u32.using_encoded(twox_64_concat)); assert_eq!(unhashed::get::(&k), Some(3u32)); - assert_eq!(&k[..32], &>::final_prefix()); + assert_eq!(&k[..32], &>::final_prefix()); }); } diff --git a/frame/support/test/tests/genesisconfig.rs b/frame/support/test/tests/genesisconfig.rs index f0094b3188ae..c6781220692a 100644 --- a/frame/support/test/tests/genesisconfig.rs +++ b/frame/support/test/tests/genesisconfig.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,30 +15,85 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub trait Config: frame_support_test::Config {} +use frame_support::derive_impl; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_core::{sr25519, ConstU32}; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, Verify}, +}; -frame_support::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} -} +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::call] + impl Pallet {} + + #[pallet::storage] + #[pallet::unbounded] + pub type AppendableDM = + StorageDoubleMap<_, Identity, u32, Identity, BlockNumberFor, Vec>; + + #[pallet::genesis_config] + pub struct GenesisConfig { + pub t: Vec<(u32, BlockNumberFor, Vec)>, + } + + impl Default for GenesisConfig { + fn default() -> Self { + Self { t: Default::default() } + } + } -frame_support::decl_storage! { - trait Store for Module as Test { - pub AppendableDM config(t): double_map hasher(identity) u32, hasher(identity) T::BlockNumber => Vec; + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + for (k1, k2, v) in &self.t { + >::insert(k1, k2, v); + } + } } } -struct Test; +pub type BlockNumber = u32; +pub type Signature = sr25519::Signature; +pub type AccountId = ::Signer; +pub type Header = generic::Header; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type Block = generic::Block; + +frame_support::construct_runtime!( + pub enum Test + + { + System: frame_system, + MyPallet: pallet, + } +); -impl frame_support_test::Config for Test { - type BlockNumber = u32; - type RuntimeOrigin = (); - type PalletInfo = frame_support_test::PanicPalletInfo; - type DbWeight = (); +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type Block = Block; + type BlockHashCount = ConstU32<10>; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type OnSetCode = (); } -impl Config for Test {} +impl pallet::Config for Test {} #[test] fn init_genesis_config() { - GenesisConfig::::default(); + pallet::GenesisConfig::::default(); } diff --git a/frame/support/test/tests/instance.rs b/frame/support/test/tests/instance.rs index 043959b67ee6..2fb6d7658d6a 100644 --- a/frame/support/test/tests/instance.rs +++ b/frame/support/test/tests/instance.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,113 +17,114 @@ #![recursion_limit = "128"] -use codec::{Codec, Decode, Encode, EncodeLike, MaxEncodedLen}; use frame_support::{ + derive_impl, inherent::{InherentData, InherentIdentifier, MakeFatalError, ProvideInherent}, - metadata::{ - PalletStorageMetadata, StorageEntryMetadata, StorageEntryModifier, StorageEntryType, - StorageHasher, + metadata_ir::{ + PalletStorageMetadataIR, StorageEntryMetadataIR, StorageEntryModifierIR, + StorageEntryTypeIR, StorageHasherIR, }, - traits::{ConstU32, Get}, - Parameter, StorageDoubleMap, StorageMap, StorageValue, + traits::ConstU32, }; -use scale_info::TypeInfo; -use sp_core::{sr25519, H256}; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_core::sr25519; use sp_runtime::{ generic, traits::{BlakeTwo256, Verify}, BuildStorage, }; -mod system; - pub trait Currency {} // Test for: // * No default instance // * Origin, Inherent, Event +#[frame_support::pallet(dev_mode)] mod module1 { use super::*; - use sp_std::ops::Add; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; - pub trait Config: system::Config - where - ::BlockNumber: From, - { - type RuntimeEvent: From> + Into<::RuntimeEvent>; + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; type RuntimeOrigin: From>; type SomeParameter: Get; - type GenericType: Default + Clone + Codec + EncodeLike + TypeInfo; + type GenericType: Parameter + Member + MaybeSerializeDeserialize + Default + MaxEncodedLen; } - frame_support::decl_module! { - pub struct Module, I: Instance> for enum Call where - origin: ::RuntimeOrigin, - system = system, - T::BlockNumber: From - { - fn offchain_worker() {} + #[pallet::call] + impl, I: 'static> Pallet { + #[pallet::weight(0)] + pub fn one(origin: OriginFor) -> DispatchResult { + ensure_root(origin)?; + Self::deposit_event(Event::AnotherVariant(3)); + Ok(()) + } + } - fn deposit_event() = default; + #[pallet::storage] + #[pallet::getter(fn value)] + pub type Value, I: 'static = ()> = StorageValue<_, T::GenericType, ValueQuery>; - #[weight = 0] - fn one(origin) { - system::ensure_root(origin)?; - Self::deposit_event(RawEvent::AnotherVariant(3)); - } - } + #[pallet::storage] + #[pallet::getter(fn map)] + pub type Map, I: 'static = ()> = StorageMap<_, Identity, u32, u64, ValueQuery>; + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + pub value: >::GenericType, + pub test: BlockNumberFor, } - frame_support::decl_storage! { - trait Store for Module, I: Instance> as Module1 where - T::BlockNumber: From + std::fmt::Display - { - pub Value config(value): T::GenericType; - pub Map: map hasher(identity) u32 => u64; + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + Self { value: Default::default(), test: Default::default() } } + } - add_extra_genesis { - config(test) : T::BlockNumber; - build(|config: &Self| { - println!("{}", config.test); - }); + #[pallet::genesis_build] + impl, I: 'static> BuildGenesisConfig for GenesisConfig + where + BlockNumberFor: std::fmt::Display, + { + fn build(&self) { + >::put(self.value.clone()); + println!("{}", self.test); } } - frame_support::decl_error! { - pub enum Error for Module, I: Instance> where - T::BlockNumber: From, - T::BlockNumber: Add, - T::AccountId: AsRef<[u8]>, - { - /// Test - Test, - } + #[pallet::error] + pub enum Error { + /// Test + Test, } - frame_support::decl_event! { - pub enum Event where Phantom = std::marker::PhantomData { - _Phantom(Phantom), - AnotherVariant(u32), - } + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event, I: 'static = ()> { + _Phantom(PhantomData), + AnotherVariant(u32), } - #[derive( - PartialEq, Eq, Clone, sp_runtime::RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen, - )] - pub enum Origin, I> - where - T::BlockNumber: From, - { + #[pallet::origin] + #[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[scale_info(skip_type_params(I))] + pub enum Origin { Members(u32), - _Phantom(std::marker::PhantomData<(T, I)>), + _Phantom(PhantomData<(T, I)>), } pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"12345678"; - impl, I: Instance> ProvideInherent for Module + #[pallet::inherent] + impl, I: 'static> ProvideInherent for Pallet where - T::BlockNumber: From, + BlockNumberFor: From, { type Call = Call; type Error = MakeFatalError<()>; @@ -133,10 +134,7 @@ mod module1 { unimplemented!(); } - fn check_inherent( - _: &Self::Call, - _: &InherentData, - ) -> std::result::Result<(), Self::Error> { + fn check_inherent(_: &Self::Call, _: &InherentData) -> Result<(), Self::Error> { unimplemented!(); } @@ -149,51 +147,87 @@ mod module1 { // Test for: // * default instance // * use of no_genesis_config_phantom_data +#[frame_support::pallet] mod module2 { use super::*; + use frame_support::pallet_prelude::*; - pub trait Config: system::Config { - type Amount: Parameter + Default; - type RuntimeEvent: From> + Into<::RuntimeEvent>; + #[pallet::pallet] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::config] + pub trait Config: frame_system::Config { + type Amount: Parameter + MaybeSerializeDeserialize + Default + MaxEncodedLen; + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; type RuntimeOrigin: From>; } - impl, I: Instance> Currency for Module {} + impl, I: 'static> Currency for Pallet {} - frame_support::decl_module! { - pub struct Module, I: Instance=DefaultInstance> for enum Call where - origin: ::RuntimeOrigin, - system = system - { - fn deposit_event() = default; - } + #[pallet::call] + impl, I: 'static> Pallet {} + + #[pallet::storage] + pub type Value, I: 'static = ()> = StorageValue<_, T::Amount, ValueQuery>; + + #[pallet::storage] + pub type Map, I: 'static = ()> = StorageMap<_, Identity, u64, u64, ValueQuery>; + + #[pallet::storage] + pub type DoubleMap, I: 'static = ()> = + StorageDoubleMap<_, Identity, u64, Identity, u64, u64, ValueQuery>; + + #[pallet::genesis_config] + pub struct GenesisConfig, I: 'static = ()> { + pub value: T::Amount, + pub map: Vec<(u64, u64)>, + pub double_map: Vec<(u64, u64, u64)>, } - frame_support::decl_storage! { - trait Store for Module, I: Instance=DefaultInstance> as Module2 { - pub Value config(value): T::Amount; - pub Map config(map): map hasher(identity) u64 => u64; - pub DoubleMap config(double_map): double_map hasher(identity) u64, hasher(identity) u64 => u64; + impl, I: 'static> Default for GenesisConfig { + fn default() -> Self { + Self { + value: Default::default(), + map: Default::default(), + double_map: Default::default(), + } } } - frame_support::decl_event! { - pub enum Event where Amount = >::Amount { - Variant(Amount), + #[pallet::genesis_build] + impl, I: 'static> BuildGenesisConfig for GenesisConfig + where + BlockNumberFor: std::fmt::Display, + { + fn build(&self) { + >::put(self.value.clone()); + for (k, v) in &self.map { + >::insert(k, v); + } + for (k1, k2, v) in &self.double_map { + >::insert(k1, k2, v); + } } } - #[derive( - PartialEq, Eq, Clone, sp_runtime::RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen, - )] - pub enum Origin, I = DefaultInstance> { + #[pallet::event] + pub enum Event, I: 'static = ()> { + Variant(T::Amount), + } + + #[pallet::origin] + #[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + #[scale_info(skip_type_params(I))] + pub enum Origin { Members(u32), - _Phantom(std::marker::PhantomData<(T, I)>), + _Phantom(PhantomData<(T, I)>), } pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"12345678"; - impl, I: Instance> ProvideInherent for Module { + #[pallet::inherent] + impl, I: 'static> ProvideInherent for Pallet { type Call = Call; type Error = MakeFatalError<()>; const INHERENT_IDENTIFIER: InherentIdentifier = INHERENT_IDENTIFIER; @@ -202,10 +236,7 @@ mod module2 { unimplemented!(); } - fn check_inherent( - _call: &Self::Call, - _data: &InherentData, - ) -> std::result::Result<(), Self::Error> { + fn check_inherent(_call: &Self::Call, _data: &InherentData) -> Result<(), Self::Error> { unimplemented!(); } @@ -217,19 +248,67 @@ mod module2 { // Test for: // * Depends on multiple instances of a module with instances +#[frame_support::pallet] mod module3 { use super::*; + use frame_support::pallet_prelude::*; - pub trait Config: - module2::Config + module2::Config + system::Config + #[pallet::pallet] + pub struct Pallet(PhantomData<(T, I)>); + + #[pallet::config] + pub trait Config: + frame_system::Config + module2::Config + module2::Config { type Currency: Currency; type Currency2: Currency; } - frame_support::decl_module! { - pub struct Module for enum Call where origin: ::RuntimeOrigin, system=system {} + #[pallet::call] + impl, I: 'static> Pallet {} +} + +pub type BlockNumber = u32; +pub type Signature = sr25519::Signature; +pub type AccountId = ::Signer; +pub type Header = generic::Header; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type Block = generic::Block; + +frame_support::construct_runtime!( + pub enum Runtime + { + System: frame_system::{Pallet, Call, Event}, + Module1_1: module1::::{ + Pallet, Call, Storage, Event, Config, Origin, Inherent + }, + Module1_2: module1::::{ + Pallet, Call, Storage, Event, Config, Origin, Inherent + }, + Module2: module2::{Pallet, Call, Storage, Event, Config, Origin, Inherent}, + Module2_1: module2::::{ + Pallet, Call, Storage, Event, Config, Origin, Inherent + }, + Module2_2: module2::::{ + Pallet, Call, Storage, Event, Config, Origin, Inherent + }, + Module2_3: module2::::{ + Pallet, Call, Storage, Event, Config, Origin, Inherent + }, + Module3: module3::{Pallet, Call}, } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type Block = Block; + type BlockHashCount = ConstU32<10>; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type OnSetCode = (); } impl module1::Config for Runtime { @@ -269,56 +348,8 @@ impl module3::Config for Runtime { type Currency2 = Module2_3; } -pub type Signature = sr25519::Signature; -pub type AccountId = ::Signer; -pub type BlockNumber = u64; -pub type Index = u64; - -impl system::Config for Runtime { - type BaseCallFilter = frame_support::traits::Everything; - type Hash = H256; - type RuntimeOrigin = RuntimeOrigin; - type BlockNumber = BlockNumber; - type AccountId = AccountId; - type RuntimeEvent = RuntimeEvent; - type PalletInfo = PalletInfo; - type RuntimeCall = RuntimeCall; - type DbWeight = (); -} - -frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - System: system::{Pallet, Call, Event}, - Module1_1: module1::::{ - Pallet, Call, Storage, Event, Config, Origin, Inherent - }, - Module1_2: module1::::{ - Pallet, Call, Storage, Event, Config, Origin, Inherent - }, - Module2: module2::{Pallet, Call, Storage, Event, Config, Origin, Inherent}, - Module2_1: module2::::{ - Pallet, Call, Storage, Event, Config, Origin, Inherent - }, - Module2_2: module2::::{ - Pallet, Call, Storage, Event, Config, Origin, Inherent - }, - Module2_3: module2::::{ - Pallet, Call, Storage, Event, Config, Origin, Inherent - }, - Module3: module3::{Pallet, Call}, - } -); - -pub type Header = generic::Header; -pub type Block = generic::Block; -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; - fn new_test_ext() -> sp_io::TestExternalities { - GenesisConfig { + RuntimeGenesisConfig { module_1_1: module1::GenesisConfig { value: 3, test: 2 }, module_1_2: module1::GenesisConfig { value: 4, test: 5 }, module_2: module2::GenesisConfig { @@ -350,14 +381,14 @@ fn storage_instance_independence() { module2::Value::::put(0); module2::Value::::put(0); module2::Value::::put(0); - module2::Map::::insert(0, 0); - module2::Map::::insert(0, 0); - module2::Map::::insert(0, 0); - module2::Map::::insert(0, 0); - module2::DoubleMap::::insert(&0, &0, &0); - module2::DoubleMap::::insert(&0, &0, &0); - module2::DoubleMap::::insert(&0, &0, &0); - module2::DoubleMap::::insert(&0, &0, &0); + module2::Map::::insert(0, 0); + module2::Map::::insert(0, 0); + module2::Map::::insert(0, 0); + module2::Map::::insert(0, 0); + module2::DoubleMap::::insert(&0, &0, &0); + module2::DoubleMap::::insert(&0, &0, &0); + module2::DoubleMap::::insert(&0, &0, &0); + module2::DoubleMap::::insert(&0, &0, &0); }); // 12 storage values. assert_eq!(storage.top.len(), 12); @@ -367,8 +398,8 @@ fn storage_instance_independence() { fn storage_with_instance_basic_operation() { new_test_ext().execute_with(|| { type Value = module2::Value; - type Map = module2::Map; - type DoubleMap = module2::DoubleMap; + type Map = module2::Map; + type DoubleMap = module2::DoubleMap; assert_eq!(Value::exists(), true); assert_eq!(Value::get(), 4); @@ -410,33 +441,33 @@ fn storage_with_instance_basic_operation() { }); } -fn expected_metadata() -> PalletStorageMetadata { - PalletStorageMetadata { - prefix: "Instance2Module2", +fn expected_metadata() -> PalletStorageMetadataIR { + PalletStorageMetadataIR { + prefix: "Module2_2", entries: vec![ - StorageEntryMetadata { + StorageEntryMetadataIR { name: "Value", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Plain(scale_info::meta_type::()), + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Plain(scale_info::meta_type::()), default: vec![0, 0, 0, 0], docs: vec![], }, - StorageEntryMetadata { + StorageEntryMetadataIR { name: "Map", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Identity], + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Identity], key: scale_info::meta_type::(), value: scale_info::meta_type::(), }, default: [0u8; 8].to_vec(), docs: vec![], }, - StorageEntryMetadata { + StorageEntryMetadataIR { name: "DoubleMap", - modifier: StorageEntryModifier::Default, - ty: StorageEntryType::Map { - hashers: vec![StorageHasher::Identity, StorageHasher::Identity], + modifier: StorageEntryModifierIR::Default, + ty: StorageEntryTypeIR::Map { + hashers: vec![StorageHasherIR::Identity, StorageHasherIR::Identity], key: scale_info::meta_type::<(u64, u64)>(), value: scale_info::meta_type::(), }, diff --git a/frame/support/test/tests/issue2219.rs b/frame/support/test/tests/issue2219.rs index 5d24d54165c1..4016707b51a8 100644 --- a/frame/support/test/tests/issue2219.rs +++ b/frame/support/test/tests/issue2219.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,32 +15,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -use frame_support::{ - codec::{Decode, Encode}, - scale_info::TypeInfo, - sp_runtime::{ - generic, - traits::{BlakeTwo256, Verify}, - }, +use frame_support::derive_impl; +use frame_system::pallet_prelude::BlockNumberFor; +use sp_core::{sr25519, ConstU64}; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, Verify}, }; -use serde::{Deserialize, Serialize}; -use sp_core::{sr25519, H256}; - -mod system; +#[frame_support::pallet] mod module { use super::*; + use frame_support::pallet_prelude::*; - pub type Request = - (::AccountId, Role, ::BlockNumber); + pub type Request = (::AccountId, Role, BlockNumberFor); pub type Requests = Vec>; - #[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Debug, TypeInfo)] + #[derive(Copy, Clone, Eq, PartialEq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub enum Role { Storage, } - #[derive(Encode, Decode, Copy, Clone, Eq, PartialEq, Debug, TypeInfo)] + #[derive(Copy, Clone, Eq, PartialEq, Debug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct RoleParameters { // minimum actors to maintain - if role is unstaking // and remaining actors would be less that this value - prevent or punish for unstaking @@ -50,142 +46,151 @@ mod module { pub max_actors: u32, // payouts are made at this block interval - pub reward_period: T::BlockNumber, + pub reward_period: BlockNumberFor, // minimum amount of time before being able to unstake - pub bonding_period: T::BlockNumber, + pub bonding_period: BlockNumberFor, // how long tokens remain locked for after unstaking - pub unbonding_period: T::BlockNumber, + pub unbonding_period: BlockNumberFor, // minimum period required to be in service. unbonding before this time is highly penalized - pub min_service_period: T::BlockNumber, + pub min_service_period: BlockNumberFor, // "startup" time allowed for roles that need to sync their infrastructure // with other providers before they are considered in service and punishable for // not delivering required level of service. - pub startup_grace_period: T::BlockNumber, + pub startup_grace_period: BlockNumberFor, } impl Default for RoleParameters { fn default() -> Self { Self { max_actors: 10, - reward_period: T::BlockNumber::default(), - unbonding_period: T::BlockNumber::default(), + reward_period: BlockNumberFor::::default(), + unbonding_period: BlockNumberFor::::default(), // not currently used min_actors: 5, - bonding_period: T::BlockNumber::default(), - min_service_period: T::BlockNumber::default(), - startup_grace_period: T::BlockNumber::default(), + bonding_period: BlockNumberFor::::default(), + min_service_period: BlockNumberFor::::default(), + startup_grace_period: BlockNumberFor::::default(), } } } - pub trait Config: system::Config + TypeInfo {} - - frame_support::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=system {} - } - - #[derive(Encode, Decode, Copy, Clone, Serialize, Deserialize)] - pub struct Data { - pub data: T::BlockNumber, - } - - impl Default for Data { - fn default() -> Self { - Self { data: T::BlockNumber::default() } - } + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + TypeInfo {} + + #[pallet::call] + impl Pallet {} + + /// requirements to enter and maintain status in roles + #[pallet::storage] + #[pallet::getter(fn parameters)] + pub type Parameters = + StorageMap<_, Blake2_128Concat, Role, RoleParameters, OptionQuery>; + + /// the roles members can enter into + #[pallet::storage] + #[pallet::getter(fn available_roles)] + #[pallet::unbounded] + pub type AvailableRoles = StorageValue<_, Vec, ValueQuery>; + + /// Actors list + #[pallet::storage] + #[pallet::getter(fn actor_account_ids)] + #[pallet::unbounded] + pub type ActorAccountIds = StorageValue<_, Vec>; + + /// actor accounts associated with a role + #[pallet::storage] + #[pallet::getter(fn account_ids_by_role)] + #[pallet::unbounded] + pub type AccountIdsByRole = StorageMap<_, Blake2_128Concat, Role, Vec>; + + /// tokens locked until given block number + #[pallet::storage] + #[pallet::getter(fn bondage)] + pub type Bondage = StorageMap<_, Blake2_128Concat, T::AccountId, BlockNumberFor>; + + /// First step before enter a role is registering intent with a new account/key. + /// This is done by sending a role_entry_request() from the new account. + /// The member must then send a stake() transaction to approve the request and enter the desired + /// role. The account making the request will be bonded and must have + /// sufficient balance to cover the minimum stake for the role. + /// Bonding only occurs after successful entry into a role. + #[pallet::storage] + #[pallet::getter(fn role_entry_requests)] + #[pallet::unbounded] + pub type RoleEntryRequests = StorageValue<_, Requests>; + + /// Entry request expires after this number of blocks + #[pallet::storage] + #[pallet::getter(fn request_life_time)] + pub type RequestLifeTime = StorageValue<_, u64, ValueQuery, ConstU64<0>>; + + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + pub enable_storage_role: bool, + pub request_life_time: u64, + #[serde(skip)] + pub _config: sp_std::marker::PhantomData, } - frame_support::decl_storage! { - trait Store for Module as Actors { - /// requirements to enter and maintain status in roles - pub Parameters get(fn parameters) build(|config: &GenesisConfig| { - if config.enable_storage_role { - let storage_params: RoleParameters = Default::default(); - vec![(Role::Storage, storage_params)] - } else { - vec![] - } - }): map hasher(blake2_128_concat) Role => Option>; - - /// the roles members can enter into - pub AvailableRoles get(fn available_roles) build(|config: &GenesisConfig| { - if config.enable_storage_role { - vec![(Role::Storage)] - } else { - vec![] - } - }): Vec; - - /// Actors list - pub ActorAccountIds get(fn actor_account_ids) : Vec; - - /// actor accounts associated with a role - pub AccountIdsByRole get(fn account_ids_by_role): - map hasher(blake2_128_concat) Role => Vec; - - /// tokens locked until given block number - pub Bondage get(fn bondage): - map hasher(blake2_128_concat) T::AccountId => T::BlockNumber; - - /// First step before enter a role is registering intent with a new account/key. - /// This is done by sending a role_entry_request() from the new account. - /// The member must then send a stake() transaction to approve the request and enter the desired role. - /// The account making the request will be bonded and must have - /// sufficient balance to cover the minimum stake for the role. - /// Bonding only occurs after successful entry into a role. - pub RoleEntryRequests get(fn role_entry_requests) : Requests; - - /// Entry request expires after this number of blocks - pub RequestLifeTime get(fn request_life_time) config(request_life_time) : u64 = 0; - } - add_extra_genesis { - config(enable_storage_role): bool; + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + if self.enable_storage_role { + >::insert(Role::Storage, >::default()); + >::put(vec![Role::Storage]); + } + >::put(self.request_life_time); } } } +pub type BlockNumber = u64; pub type Signature = sr25519::Signature; pub type AccountId = ::Signer; -pub type BlockNumber = u64; -pub type Index = u64; pub type Header = generic::Header; -pub type Block = generic::Block; pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type Block = generic::Block; -impl system::Config for Runtime { +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; - type Hash = H256; + type Block = Block; + type BlockHashCount = ConstU64<10>; type RuntimeOrigin = RuntimeOrigin; - type BlockNumber = BlockNumber; - type AccountId = AccountId; + type RuntimeCall = RuntimeCall; type RuntimeEvent = RuntimeEvent; type PalletInfo = PalletInfo; - type RuntimeCall = RuntimeCall; - type DbWeight = (); + type OnSetCode = (); } impl module::Config for Runtime {} frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - System: system::{Pallet, Call, Event}, - Module: module::{Pallet, Call, Storage, Config}, + pub struct Runtime { + System: frame_system, + Module: module, } ); #[test] fn create_genesis_config() { - let config = GenesisConfig { - module: module::GenesisConfig { request_life_time: 0, enable_storage_role: true }, + let config = RuntimeGenesisConfig { + system: Default::default(), + module: module::GenesisConfig { + request_life_time: 0, + enable_storage_role: true, + ..Default::default() + }, }; assert_eq!(config.module.request_life_time, 0); assert!(config.module.enable_storage_role); diff --git a/frame/support/test/tests/origin.rs b/frame/support/test/tests/origin.rs index f794cd8be1ce..5682bb500c7e 100644 --- a/frame/support/test/tests/origin.rs +++ b/frame/support/test/tests/origin.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,119 +19,131 @@ #![recursion_limit = "128"] -use codec::MaxEncodedLen; -use frame_support::traits::{Contains, OriginTrait}; -use scale_info::TypeInfo; -use sp_core::{sr25519, H256}; +use frame_support::{ + derive_impl, + traits::{Contains, OriginTrait}, +}; +use sp_core::ConstU32; use sp_runtime::{generic, traits::BlakeTwo256}; -mod system; - mod nested { - use super::*; - + #[frame_support::pallet(dev_mode)] pub mod module { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; - use super::*; + #[pallet::pallet] + pub struct Pallet(_); - pub trait Config: system::Config {} + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + + IsType<::RuntimeEvent>; + } - frame_support::decl_module! { - pub struct Module for enum Call - where origin: ::RuntimeOrigin, system=system - { - #[weight = 0] - pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { - Err(Error::::Something.into()) - } + #[pallet::call] + impl Pallet { + pub fn fail(_origin: OriginFor) -> DispatchResult { + Err(Error::::Something.into()) } } - #[derive( - Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, - )] + #[pallet::origin] + #[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] pub struct Origin; - frame_support::decl_event! { - pub enum Event { - A, - } + #[pallet::event] + pub enum Event { + A, } - frame_support::decl_error! { - pub enum Error for Module { - Something - } + #[pallet::error] + pub enum Error { + Something, } - frame_support::decl_storage! { - trait Store for Module as Module {} - add_extra_genesis { - build(|_config| {}) - } + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + #[serde(skip)] + pub _config: sp_std::marker::PhantomData, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) {} } } } +#[frame_support::pallet(dev_mode)] pub mod module { - use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; - pub trait Config: system::Config {} + #[pallet::pallet] + pub struct Pallet(_); - frame_support::decl_module! { - pub struct Module for enum Call - where origin: ::RuntimeOrigin, system=system - { - #[weight = 0] - pub fn fail(_origin) -> frame_support::dispatch::DispatchResult { - Err(Error::::Something.into()) - } - #[weight = 0] - pub fn aux_1(_origin, #[compact] _data: u32) -> frame_support::dispatch::DispatchResult { - unreachable!() - } - #[weight = 0] - pub fn aux_2(_origin, _data: i32, #[compact] _data2: u32) -> frame_support::dispatch::DispatchResult { - unreachable!() - } - #[weight = 0] - fn aux_3(_origin, _data: i32, _data2: String) -> frame_support::dispatch::DispatchResult { - unreachable!() - } - #[weight = 3] - fn aux_4(_origin) -> frame_support::dispatch::DispatchResult { unreachable!() } - #[weight = (5, frame_support::dispatch::DispatchClass::Operational)] - fn operational(_origin) { unreachable!() } + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::call] + impl Pallet { + pub fn fail(_origin: OriginFor) -> DispatchResult { + Err(Error::::Something.into()) + } + pub fn aux_1(_origin: OriginFor, #[pallet::compact] _data: u32) -> DispatchResult { + unreachable!() + } + pub fn aux_2( + _origin: OriginFor, + _data: i32, + #[pallet::compact] _data2: u32, + ) -> DispatchResult { + unreachable!() + } + #[pallet::weight(0)] + pub fn aux_3(_origin: OriginFor, _data: i32, _data2: String) -> DispatchResult { + unreachable!() + } + #[pallet::weight(3)] + pub fn aux_4(_origin: OriginFor) -> DispatchResult { + unreachable!() + } + #[pallet::weight((5, DispatchClass::Operational))] + pub fn operational(_origin: OriginFor) -> DispatchResult { + unreachable!() } } - #[derive( - Clone, PartialEq, Eq, Debug, codec::Encode, codec::Decode, TypeInfo, MaxEncodedLen, - )] - pub struct Origin(pub core::marker::PhantomData); + #[pallet::origin] + #[derive(Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] + pub struct Origin(pub PhantomData); - frame_support::decl_event! { - pub enum Event { - A, - } + #[pallet::event] + pub enum Event { + A, } - frame_support::decl_error! { - pub enum Error for Module { - Something - } + #[pallet::error] + pub enum Error { + Something, } - frame_support::decl_storage! { - trait Store for Module as Module {} - add_extra_genesis { - build(|_config| {}) - } + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + #[serde(skip)] + pub _config: sp_std::marker::PhantomData, } -} -impl nested::module::Config for RuntimeOriginTest {} -impl module::Config for RuntimeOriginTest {} + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) {} + } +} pub struct BaseCallFilter; impl Contains for BaseCallFilter { @@ -143,35 +155,39 @@ impl Contains for BaseCallFilter { } } -impl system::Config for RuntimeOriginTest { - type BaseCallFilter = BaseCallFilter; - type Hash = H256; - type RuntimeOrigin = RuntimeOrigin; - type BlockNumber = BlockNumber; - type AccountId = u32; - type RuntimeEvent = RuntimeEvent; - type PalletInfo = PalletInfo; - type RuntimeCall = RuntimeCall; - type DbWeight = (); -} +pub type BlockNumber = u32; +pub type AccountId = u32; +pub type Header = generic::Header; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type Block = generic::Block; frame_support::construct_runtime!( - pub enum RuntimeOriginTest where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub enum RuntimeOriginTest { - System: system::{Pallet, Event, Origin}, - NestedModule: nested::module::{Pallet, Origin, Call}, - Module: module::{Pallet, Origin, Call}, + System: frame_system, + NestedModule: nested::module, + Module: module, } ); -pub type Signature = sr25519::Signature; -pub type BlockNumber = u64; -pub type Header = generic::Header; -pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; -pub type Block = generic::Block; +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for RuntimeOriginTest { + type BaseCallFilter = BaseCallFilter; + type Block = Block; + type BlockHashCount = ConstU32<10>; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type OnSetCode = (); +} + +impl nested::module::Config for RuntimeOriginTest { + type RuntimeEvent = RuntimeEvent; +} +impl module::Config for RuntimeOriginTest { + type RuntimeEvent = RuntimeEvent; +} #[test] fn origin_default_filter() { @@ -199,7 +215,7 @@ fn origin_default_filter() { // Now test for root origin and filters: let mut origin = RuntimeOrigin::from(Some(0)); origin.set_caller_from(RuntimeOrigin::root()); - assert!(matches!(origin.caller, OriginCaller::system(system::RawOrigin::Root))); + assert!(matches!(origin.caller, OriginCaller::system(frame_support_test::RawOrigin::Root))); // Root origin bypass all filter. assert_eq!(origin.filter_call(&accepted_call), true); diff --git a/frame/support/test/tests/pallet.rs b/frame/support/test/tests/pallet.rs index c0376d5aa450..99010ffc8dbd 100644 --- a/frame/support/test/tests/pallet.rs +++ b/frame/support/test/tests/pallet.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,14 +16,18 @@ // limitations under the License. use frame_support::{ + assert_ok, dispatch::{ - DispatchClass, DispatchInfo, GetDispatchInfo, Parameter, Pays, UnfilteredDispatchable, + DispatchClass, DispatchInfo, Dispatchable, GetDispatchInfo, Parameter, Pays, + UnfilteredDispatchable, }, - pallet_prelude::ValueQuery, + dispatch_context::with_context, + pallet_prelude::{StorageInfoTrait, ValueQuery}, + parameter_types, storage::unhashed, traits::{ - ConstU32, GetCallName, GetStorageVersion, OnFinalize, OnGenesis, OnInitialize, - OnRuntimeUpgrade, PalletError, PalletInfoAccess, StorageVersion, + ConstU32, GetCallIndex, GetCallName, GetStorageVersion, OnFinalize, OnGenesis, + OnInitialize, OnRuntimeUpgrade, PalletError, PalletInfoAccess, StorageVersion, }, weights::{RuntimeDbWeight, Weight}, }; @@ -32,7 +36,18 @@ use sp_io::{ hashing::{blake2_128, twox_128, twox_64}, TestExternalities, }; -use sp_runtime::{DispatchError, ModuleError}; +use sp_runtime::{ + traits::{Extrinsic as ExtrinsicT, SignaturePayload as SignaturePayloadT}, + DispatchError, ModuleError, +}; + +parameter_types! { + /// Used to control if the storage version should be updated. + storage UpdateStorageVersion: bool = false; +} + +/// Latest stable metadata version used for testing. +const LATEST_METADATA_VERSION: u32 = 15; pub struct SomeType1; impl From for u64 { @@ -98,10 +113,15 @@ impl SomeAssociation2 for u64 { } #[frame_support::pallet] +/// Pallet documentation +// Comments should not be included in the pallet documentation +#[pallet_doc("../../README.md")] +#[doc = include_str!("../../README.md")] pub mod pallet { use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + use sp_runtime::DispatchResult; type BalanceOf = ::Balance; @@ -154,7 +174,6 @@ pub mod pallet { } #[pallet::pallet] - #[pallet::generate_store(pub(crate) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); @@ -167,7 +186,7 @@ pub mod pallet { let _ = T::AccountId::from(SomeType1); // Test for where clause let _ = T::AccountId::from(SomeType2); // Test for where clause Self::deposit_event(Event::Something(10)); - Weight::from_ref_time(10) + Weight::from_parts(10, 0) } fn on_finalize(_: BlockNumberFor) { let _ = T::AccountId::from(SomeType1); // Test for where clause @@ -178,7 +197,7 @@ pub mod pallet { let _ = T::AccountId::from(SomeType1); // Test for where clause let _ = T::AccountId::from(SomeType2); // Test for where clause Self::deposit_event(Event::Something(30)); - Weight::from_ref_time(30) + Weight::from_parts(30, 0) } fn integrity_test() { let _ = T::AccountId::from(SomeType1); // Test for where clause @@ -193,7 +212,7 @@ pub mod pallet { { /// Doc comment put in metadata #[pallet::call_index(0)] - #[pallet::weight(Weight::from_ref_time(*_foo as u64))] + #[pallet::weight(Weight::from_parts(*_foo as u64, 0))] pub fn foo( origin: OriginFor, #[pallet::compact] _foo: u32, @@ -208,7 +227,7 @@ pub mod pallet { /// Doc comment put in metadata #[pallet::call_index(1)] - #[pallet::weight(1)] + #[pallet::weight({1})] pub fn foo_storage_layer( _origin: OriginFor, #[pallet::compact] foo: u32, @@ -221,12 +240,24 @@ pub mod pallet { Ok(().into()) } + #[pallet::call_index(4)] + #[pallet::weight({1})] + pub fn foo_index_out_of_order(_origin: OriginFor) -> DispatchResult { + Ok(()) + } + // Test for DispatchResult return type #[pallet::call_index(2)] - #[pallet::weight(1)] + #[pallet::weight({1})] pub fn foo_no_post_info(_origin: OriginFor) -> DispatchResult { Ok(()) } + + #[pallet::call_index(3)] + #[pallet::weight({1})] + pub fn check_for_dispatch_context(_origin: OriginFor) -> DispatchResult { + with_context::<(), _>(|_| ()).ok_or_else(|| DispatchError::Unavailable) + } } #[pallet::error] @@ -371,13 +402,18 @@ pub mod pallet { pub type Unbounded = StorageValue>; #[pallet::genesis_config] - #[derive(Default)] - pub struct GenesisConfig { + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig + where + T::AccountId: From + SomeAssociation1 + From, + { + #[serde(skip)] + _config: sp_std::marker::PhantomData, _myfield: u32, } #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig + impl BuildGenesisConfig for GenesisConfig where T::AccountId: From + SomeAssociation1 + From, { @@ -454,6 +490,11 @@ pub mod pallet { } } + #[pallet::composite_enum] + pub enum HoldReason { + Staking, + } + #[derive(codec::Encode, sp_runtime::RuntimeDebug)] #[cfg_attr(feature = "std", derive(codec::Decode))] pub enum InherentError { @@ -473,10 +514,12 @@ pub mod pallet { // and that a pallet with the attribute without_storage_info is correctly handled. #[frame_support::pallet] pub mod pallet2 { - use super::{SomeAssociation1, SomeType1}; + use super::{SomeAssociation1, SomeType1, UpdateStorageVersion}; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + pub(crate) const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); + #[pallet::config] pub trait Config: frame_system::Config where @@ -486,7 +529,7 @@ pub mod pallet2 { } #[pallet::pallet] - #[pallet::generate_store(pub(crate) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] #[pallet::without_storage_info] pub struct Pallet(_); @@ -504,6 +547,11 @@ pub mod pallet2 { } fn on_runtime_upgrade() -> Weight { Self::deposit_event(Event::Something(31)); + + if UpdateStorageVersion::get() { + Self::current_storage_version().put::(); + } + Weight::zero() } } @@ -543,12 +591,22 @@ pub mod pallet2 { } #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig + impl BuildGenesisConfig for GenesisConfig where T::AccountId: From + SomeAssociation1, { fn build(&self) {} } + + #[pallet::composite_enum] + pub enum HoldReason { + Governance, + } + + #[pallet::composite_enum] + pub enum SlashReason { + Equivocation, + } } /// Test that the supertrait check works when we pass some parameter to the `frame_system::Config`. @@ -598,14 +656,13 @@ frame_support::parameter_types!( impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u32; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = sp_runtime::testing::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = sp_runtime::traits::IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU32<250>; type BlockWeights = (); @@ -647,13 +704,11 @@ impl pallet5::Config for Runtime { pub type Header = sp_runtime::generic::Header; pub type Block = sp_runtime::generic::Block; -pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; +pub type UncheckedExtrinsic = + sp_runtime::testing::TestXt>; frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { // Exclude part `Storage` in order not to check its metadata in tests. System: frame_system exclude_parts { Pallet, Storage }, @@ -705,7 +760,7 @@ fn call_expand() { assert_eq!( call_foo.get_dispatch_info(), DispatchInfo { - weight: frame_support::weights::Weight::from_ref_time(3), + weight: frame_support::weights::Weight::from_parts(3, 0), class: DispatchClass::Normal, pays_fee: Pays::Yes } @@ -713,8 +768,25 @@ fn call_expand() { assert_eq!(call_foo.get_call_name(), "foo"); assert_eq!( pallet::Call::::get_call_names(), - &["foo", "foo_storage_layer", "foo_no_post_info"], + &[ + "foo", + "foo_storage_layer", + "foo_index_out_of_order", + "foo_no_post_info", + "check_for_dispatch_context" + ], ); + + assert_eq!(call_foo.get_call_index(), 0u8); + assert_eq!(pallet::Call::::get_call_indices(), &[0u8, 1u8, 4u8, 2u8, 3u8]) +} + +#[test] +fn call_expand_index() { + let call_foo = pallet::Call::::foo_index_out_of_order {}; + + assert_eq!(call_foo.get_call_index(), 4u8); + assert_eq!(pallet::Call::::get_call_indices(), &[0u8, 1u8, 4u8, 2u8, 3u8]) } #[test] @@ -746,20 +818,17 @@ fn instance_expand() { #[test] fn inherent_expand() { - use frame_support::{ - inherent::{BlockT, InherentData}, - traits::EnsureInherentsAreFirst, - }; + use frame_support::{inherent::InherentData, traits::EnsureInherentsAreFirst}; use sp_core::Hasher; use sp_runtime::{ - traits::{BlakeTwo256, Header}, + traits::{BlakeTwo256, Block as _, Header}, Digest, }; let inherents = InherentData::new().create_extrinsics(); let expected = vec![UncheckedExtrinsic { - function: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), + call: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), signature: None, }]; assert_eq!(expected, inherents); @@ -774,11 +843,11 @@ fn inherent_expand() { ), vec![ UncheckedExtrinsic { - function: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), + call: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), signature: None, }, UncheckedExtrinsic { - function: RuntimeCall::Example(pallet::Call::foo { foo: 1, bar: 0 }), + call: RuntimeCall::Example(pallet::Call::foo { foo: 1, bar: 0 }), signature: None, }, ], @@ -796,11 +865,11 @@ fn inherent_expand() { ), vec![ UncheckedExtrinsic { - function: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), + call: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), signature: None, }, UncheckedExtrinsic { - function: RuntimeCall::Example(pallet::Call::foo { foo: 0, bar: 0 }), + call: RuntimeCall::Example(pallet::Call::foo { foo: 0, bar: 0 }), signature: None, }, ], @@ -817,7 +886,7 @@ fn inherent_expand() { Digest::default(), ), vec![UncheckedExtrinsic { - function: RuntimeCall::Example(pallet::Call::foo_storage_layer { foo: 0 }), + call: RuntimeCall::Example(pallet::Call::foo_storage_layer { foo: 0 }), signature: None, }], ); @@ -835,8 +904,8 @@ fn inherent_expand() { Digest::default(), ), vec![UncheckedExtrinsic { - function: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), - signature: Some((1, (), ())), + call: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), + signature: Some((1, Default::default())), }], ); @@ -854,11 +923,11 @@ fn inherent_expand() { ), vec![ UncheckedExtrinsic { - function: RuntimeCall::Example(pallet::Call::foo { foo: 1, bar: 1 }), + call: RuntimeCall::Example(pallet::Call::foo { foo: 1, bar: 1 }), signature: None, }, UncheckedExtrinsic { - function: RuntimeCall::Example(pallet::Call::foo_storage_layer { foo: 0 }), + call: RuntimeCall::Example(pallet::Call::foo_storage_layer { foo: 0 }), signature: None, }, ], @@ -876,15 +945,15 @@ fn inherent_expand() { ), vec![ UncheckedExtrinsic { - function: RuntimeCall::Example(pallet::Call::foo { foo: 1, bar: 1 }), + call: RuntimeCall::Example(pallet::Call::foo { foo: 1, bar: 1 }), signature: None, }, UncheckedExtrinsic { - function: RuntimeCall::Example(pallet::Call::foo_storage_layer { foo: 0 }), + call: RuntimeCall::Example(pallet::Call::foo_storage_layer { foo: 0 }), signature: None, }, UncheckedExtrinsic { - function: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), + call: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), signature: None, }, ], @@ -902,15 +971,15 @@ fn inherent_expand() { ), vec![ UncheckedExtrinsic { - function: RuntimeCall::Example(pallet::Call::foo { foo: 1, bar: 1 }), + call: RuntimeCall::Example(pallet::Call::foo { foo: 1, bar: 1 }), signature: None, }, UncheckedExtrinsic { - function: RuntimeCall::Example(pallet::Call::foo { foo: 1, bar: 0 }), - signature: Some((1, (), ())), + call: RuntimeCall::Example(pallet::Call::foo { foo: 1, bar: 0 }), + signature: Some((1, Default::default())), }, UncheckedExtrinsic { - function: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), + call: RuntimeCall::Example(pallet::Call::foo_no_post_info {}), signature: None, }, ], @@ -937,12 +1006,20 @@ fn validate_unsigned_expand() { } #[test] -fn trait_store_expand() { - TestExternalities::default().execute_with(|| { - as pallet::Store>::Value::get(); - as pallet::Store>::Map::get(1); - as pallet::Store>::DoubleMap::get(1, 2); - }) +fn composite_expand() { + use codec::Encode; + + let hold_reason: RuntimeHoldReason = pallet::HoldReason::Staking.into(); + let hold_reason2: RuntimeHoldReason = pallet2::HoldReason::Governance.into(); + let slash_reason: RuntimeSlashReason = pallet2::SlashReason::Equivocation.into(); + + assert_eq!(hold_reason, RuntimeHoldReason::Example(pallet::HoldReason::Staking)); + assert_eq!(hold_reason2, RuntimeHoldReason::Example2(pallet2::HoldReason::Governance)); + assert_eq!(slash_reason, RuntimeSlashReason::Example2(pallet2::SlashReason::Equivocation)); + + assert_eq!(hold_reason.encode(), [1, 0]); + assert_eq!(hold_reason2.encode(), [2, 0]); + assert_eq!(slash_reason.encode(), [2, 0]); } #[test] @@ -1086,10 +1163,10 @@ fn pallet_hooks_expand() { TestExternalities::default().execute_with(|| { frame_system::Pallet::::set_block_number(1); - assert_eq!(AllPalletsWithoutSystem::on_initialize(1), Weight::from_ref_time(10)); + assert_eq!(AllPalletsWithoutSystem::on_initialize(1), Weight::from_parts(10, 0)); AllPalletsWithoutSystem::on_finalize(1); - assert_eq!(AllPalletsWithoutSystem::on_runtime_upgrade(), Weight::from_ref_time(30)); + assert_eq!(AllPalletsWithoutSystem::on_runtime_upgrade(), Weight::from_parts(30, 0)); assert_eq!( frame_system::Pallet::::events()[0].event, @@ -1127,13 +1204,13 @@ fn all_pallets_type_reversed_order_is_correct() { { assert_eq!( AllPalletsWithoutSystemReversed::on_initialize(1), - Weight::from_ref_time(10) + Weight::from_parts(10, 0) ); AllPalletsWithoutSystemReversed::on_finalize(1); assert_eq!( AllPalletsWithoutSystemReversed::on_runtime_upgrade(), - Weight::from_ref_time(30) + Weight::from_parts(30, 0) ); } @@ -1209,7 +1286,7 @@ fn migrate_from_pallet_version_to_storage_version() { }; // `pallet_num` pallets, 2 writes and every write costs 5 weight. - assert_eq!(Weight::from_ref_time(pallet_num * 2 * 5), weight); + assert_eq!(Weight::from_parts(pallet_num * 2 * 5, 0), weight); // All pallet versions should be removed assert!(sp_io::storage::get(&pallet_version_key(Example::name())).is_none()); @@ -1217,14 +1294,15 @@ fn migrate_from_pallet_version_to_storage_version() { assert!(sp_io::storage::get(&pallet_version_key(System::name())).is_none()); assert_eq!(Example::on_chain_storage_version(), pallet::STORAGE_VERSION); - assert_eq!(Example2::on_chain_storage_version(), StorageVersion::new(0)); + assert_eq!(Example2::on_chain_storage_version(), pallet2::STORAGE_VERSION); assert_eq!(System::on_chain_storage_version(), StorageVersion::new(0)); }); } #[test] fn metadata() { - use frame_support::metadata::*; + use codec::Decode; + use frame_support::metadata::{v15::*, *}; fn maybe_docs(doc: Vec<&'static str>) -> Vec<&'static str> { if cfg!(feature = "no-metadata-docs") { @@ -1234,6 +1312,9 @@ fn metadata() { } } + let readme = "Support code for the runtime.\n\nLicense: Apache-2.0"; + let expected_pallet_doc = vec![" Pallet documentation", readme, readme]; + let pallets = vec![ PalletMetadata { index: 1, @@ -1494,6 +1575,7 @@ fn metadata() { }, ], error: Some(PalletErrorMetadata { ty: meta_type::>() }), + docs: expected_pallet_doc, }, PalletMetadata { index: 2, @@ -1532,6 +1614,7 @@ fn metadata() { event: Some(PalletEventMetadata { ty: meta_type::() }), constants: vec![], error: None, + docs: vec![], }, #[cfg(feature = "frame-feature-testing")] PalletMetadata { @@ -1542,6 +1625,7 @@ fn metadata() { event: None, constants: vec![], error: None, + docs: vec![" Test that the supertrait check works when we pass some parameter to the `frame_system::Config`."], }, #[cfg(feature = "frame-feature-testing-2")] PalletMetadata { @@ -1552,12 +1636,13 @@ fn metadata() { event: None, constants: vec![], error: None, + docs: vec![" Test that the supertrait check works when we pass some parameter to the `frame_system::Config`."], }, ]; - let empty_doc = pallets[0].event.as_ref().unwrap().ty.type_info().docs().is_empty() && - pallets[0].error.as_ref().unwrap().ty.type_info().docs().is_empty() && - pallets[0].calls.as_ref().unwrap().ty.type_info().docs().is_empty(); + let empty_doc = pallets[0].event.as_ref().unwrap().ty.type_info().docs.is_empty() && + pallets[0].error.as_ref().unwrap().ty.type_info().docs.is_empty() && + pallets[0].calls.as_ref().unwrap().ty.type_info().docs.is_empty(); if cfg!(feature = "no-metadata-docs") { assert!(empty_doc) @@ -1566,36 +1651,127 @@ fn metadata() { } let extrinsic = ExtrinsicMetadata { - ty: meta_type::(), version: 4, signed_extensions: vec![SignedExtensionMetadata { identifier: "UnitSignedExtension", ty: meta_type::<()>(), additional_signed: meta_type::<()>(), }], + address_ty: meta_type::<<::SignaturePayload as SignaturePayloadT>::SignatureAddress>(), + call_ty: meta_type::<::Call>(), + signature_ty: meta_type::< + <::SignaturePayload as SignaturePayloadT>::Signature + >(), + extra_ty: meta_type::<<::SignaturePayload as SignaturePayloadT>::SignatureExtra>(), + }; + + let outer_enums = OuterEnums { + call_enum_ty: meta_type::(), + event_enum_ty: meta_type::(), + error_enum_ty: meta_type::(), }; - let expected_metadata: RuntimeMetadataPrefixed = - RuntimeMetadataLastVersion::new(pallets, extrinsic, meta_type::()).into(); + let expected_metadata: RuntimeMetadataPrefixed = RuntimeMetadataLastVersion::new( + pallets, + extrinsic, + meta_type::(), + vec![], + outer_enums, + CustomMetadata { map: Default::default() }, + ) + .into(); let expected_metadata = match expected_metadata.1 { - RuntimeMetadata::V14(metadata) => metadata, + RuntimeMetadata::V15(metadata) => metadata, _ => panic!("metadata has been bumped, test needs to be updated"), }; - let actual_metadata = match Runtime::metadata().1 { - RuntimeMetadata::V14(metadata) => metadata, + let bytes = &Runtime::metadata_at_version(LATEST_METADATA_VERSION) + .expect("Metadata must be present; qed"); + + let actual_metadata: RuntimeMetadataPrefixed = + Decode::decode(&mut &bytes[..]).expect("Metadata encoded properly; qed"); + + let actual_metadata = match actual_metadata.1 { + RuntimeMetadata::V15(metadata) => metadata, _ => panic!("metadata has been bumped, test needs to be updated"), }; pretty_assertions::assert_eq!(actual_metadata.pallets, expected_metadata.pallets); } +#[test] +fn metadata_at_version() { + use frame_support::metadata::*; + use sp_core::Decode; + + // Metadata always returns the V14.3 + let metadata = Runtime::metadata(); + let at_metadata = match Runtime::metadata_at_version(14) { + Some(opaque) => { + let bytes = &*opaque; + let metadata: RuntimeMetadataPrefixed = Decode::decode(&mut &bytes[..]).unwrap(); + metadata + }, + _ => panic!("metadata has been bumped, test needs to be updated"), + }; + + assert_eq!(metadata, at_metadata); +} + +#[test] +fn metadata_versions() { + assert_eq!(vec![14, LATEST_METADATA_VERSION], Runtime::metadata_versions()); +} + +#[test] +fn metadata_ir_pallet_runtime_docs() { + let ir = Runtime::metadata_ir(); + let pallet = ir + .pallets + .iter() + .find(|pallet| pallet.name == "Example") + .expect("Pallet should be present"); + + let readme = "Support code for the runtime.\n\nLicense: Apache-2.0"; + let expected = vec![" Pallet documentation", readme, readme]; + assert_eq!(pallet.docs, expected); +} + +#[test] +fn extrinsic_metadata_ir_types() { + let ir = Runtime::metadata_ir().extrinsic; + + assert_eq!(meta_type::<<::SignaturePayload as SignaturePayloadT>::SignatureAddress>(), ir.address_ty); + assert_eq!(meta_type::(), ir.address_ty); + + assert_eq!(meta_type::<::Call>(), ir.call_ty); + assert_eq!(meta_type::(), ir.call_ty); + + assert_eq!( + meta_type::< + <::SignaturePayload as SignaturePayloadT>::Signature, + >(), + ir.signature_ty + ); + assert_eq!(meta_type::<()>(), ir.signature_ty); + + assert_eq!(meta_type::<<::SignaturePayload as SignaturePayloadT>::SignatureExtra>(), ir.extra_ty); + assert_eq!(meta_type::>(), ir.extra_ty); +} + +#[test] +fn test_pallet_runtime_docs() { + let docs = crate::pallet::Pallet::::pallet_documentation_metadata(); + let readme = "Support code for the runtime.\n\nLicense: Apache-2.0"; + let expected = vec![" Pallet documentation", readme, readme]; + assert_eq!(docs, expected); +} + #[test] fn test_pallet_info_access() { assert_eq!(::name(), "System"); assert_eq!(::name(), "Example"); assert_eq!(::name(), "Example2"); - assert_eq!(::index(), 0); assert_eq!(::index(), 1); assert_eq!(::index(), 2); @@ -1906,14 +2082,142 @@ fn assert_type_all_pallets_without_system_reversed_is_correct() { #[test] fn test_storage_alias() { + use frame_support::Twox64Concat; + #[frame_support::storage_alias] type Value where ::AccountId: From + SomeAssociation1, = StorageValue, u32, ValueQuery>; + #[frame_support::storage_alias] + type SomeCountedStorageMap + where + ::AccountId: From + SomeAssociation1, + = CountedStorageMap, Twox64Concat, u8, u32>; + TestExternalities::default().execute_with(|| { pallet::Value::::put(10); assert_eq!(10, Value::::get()); + + pallet2::SomeCountedStorageMap::::insert(10, 100); + assert_eq!(Some(100), SomeCountedStorageMap::::get(10)); + assert_eq!(1, SomeCountedStorageMap::::count()); + assert_eq!( + SomeCountedStorageMap::::storage_info(), + pallet2::SomeCountedStorageMap::::storage_info() + ); }) } + +#[cfg(feature = "try-runtime")] +#[test] +fn post_runtime_upgrade_detects_storage_version_issues() { + use frame_support::traits::UpgradeCheckSelect; + + struct CustomUpgrade; + + impl OnRuntimeUpgrade for CustomUpgrade { + fn on_runtime_upgrade() -> Weight { + Example2::current_storage_version().put::(); + + Default::default() + } + } + + struct CustomUpgradePallet4; + + impl OnRuntimeUpgrade for CustomUpgradePallet4 { + fn on_runtime_upgrade() -> Weight { + StorageVersion::new(100).put::(); + + Default::default() + } + } + + type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, + >; + + type ExecutiveWithUpgrade = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, + CustomUpgrade, + >; + + type ExecutiveWithUpgradePallet4 = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, + CustomUpgradePallet4, + >; + + TestExternalities::default().execute_with(|| { + // Call `on_genesis` to put the storage version of `Example` into the storage. + Example::on_genesis(); + // The version isn't changed, we should detect it. + assert!( + Executive::try_runtime_upgrade(UpgradeCheckSelect::PreAndPost).unwrap_err() == + "On chain and current storage version do not match. Missing runtime upgrade?" + .into() + ); + }); + + TestExternalities::default().execute_with(|| { + // Call `on_genesis` to put the storage version of `Example` into the storage. + Example::on_genesis(); + // We set the new storage version in the pallet and that should be detected. + UpdateStorageVersion::set(&true); + Executive::try_runtime_upgrade(UpgradeCheckSelect::PreAndPost).unwrap(); + }); + + TestExternalities::default().execute_with(|| { + // Call `on_genesis` to put the storage version of `Example` into the storage. + Example::on_genesis(); + // We set the new storage version in the custom upgrade and that should be detected. + ExecutiveWithUpgrade::try_runtime_upgrade(UpgradeCheckSelect::PreAndPost).unwrap(); + }); + + TestExternalities::default().execute_with(|| { + // Call `on_genesis` to put the storage version of `Example` into the storage. + Example::on_genesis(); + // We need to set the correct storage version for `Example2` + UpdateStorageVersion::set(&true); + + // `CustomUpgradePallet4` will set a storage version for `Example4` while this doesn't has + // any storage version "enabled". + assert!( + ExecutiveWithUpgradePallet4::try_runtime_upgrade(UpgradeCheckSelect::PreAndPost) + .unwrap_err() == "On chain storage version set, while the pallet \ + doesn't have the `#[pallet::storage_version(VERSION)]` attribute." + .into() + ); + }); +} + +#[test] +fn test_dispatch_context() { + TestExternalities::default().execute_with(|| { + // By default there is no context + assert!(with_context::<(), _>(|_| ()).is_none()); + + // When not using `dispatch`, there should be no dispatch context + assert_eq!( + DispatchError::Unavailable, + Example::check_for_dispatch_context(RuntimeOrigin::root()).unwrap_err(), + ); + + // When using `dispatch`, there should be a dispatch context + assert_ok!(RuntimeCall::from(pallet::Call::::check_for_dispatch_context {}) + .dispatch(RuntimeOrigin::root())); + }); +} diff --git a/frame/support/test/tests/pallet_compatibility.rs b/frame/support/test/tests/pallet_compatibility.rs deleted file mode 100644 index 300fb9a40cf4..000000000000 --- a/frame/support/test/tests/pallet_compatibility.rs +++ /dev/null @@ -1,371 +0,0 @@ -// 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. - -// Old macros don't support the flag `no-metadata-docs` so the result differs when the feature is -// activated. -#![cfg(not(feature = "no-metadata-docs"))] - -use frame_support::traits::{ConstU32, ConstU64}; - -pub trait SomeAssociation { - type A: frame_support::dispatch::Parameter + Default; -} -impl SomeAssociation for u64 { - type A = u64; -} - -mod pallet_old { - use super::SomeAssociation; - use frame_support::{ - decl_error, decl_event, decl_module, decl_storage, traits::Get, weights::Weight, Parameter, - }; - use frame_system::ensure_root; - - pub trait Config: frame_system::Config { - type SomeConst: Get; - type Balance: Parameter - + codec::HasCompact - + From - + Into - + Default - + SomeAssociation; - type RuntimeEvent: From> + Into<::RuntimeEvent>; - } - - decl_storage! { - trait Store for Module as Example { - /// Some documentation - Dummy get(fn dummy) config(): Option; - Bar get(fn bar) config(): map hasher(blake2_128_concat) T::AccountId => T::Balance; - Foo get(fn foo) config(): T::Balance = 3.into(); - Double get(fn double): double_map - hasher(blake2_128_concat) u32, - hasher(twox_64_concat) u64 - => ::A; - } - } - - decl_event!( - pub enum Event - where - Balance = ::Balance, - { - /// Dummy event, just here so there's a generic type that's used. - Dummy(Balance), - } - ); - - decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin { - type Error = Error; - fn deposit_event() = default; - const SomeConst: T::Balance = T::SomeConst::get(); - - #[weight = >::into(new_value.clone())] - fn set_dummy(origin, #[compact] new_value: T::Balance) { - ensure_root(origin)?; - - >::put(&new_value); - Self::deposit_event(RawEvent::Dummy(new_value)); - } - - fn on_initialize(_n: T::BlockNumber) -> Weight { - >::put(T::Balance::from(10)); - Weight::from_ref_time(10) - } - - fn on_finalize(_n: T::BlockNumber) { - >::put(T::Balance::from(11)); - } - } - } - - decl_error! { - pub enum Error for Module { - /// Some wrong behavior - Wrong, - } - } -} - -#[frame_support::pallet] -pub mod pallet { - use super::SomeAssociation; - use frame_support::{pallet_prelude::*, scale_info}; - use frame_system::{ensure_root, pallet_prelude::*}; - - #[pallet::config] - pub trait Config: frame_system::Config { - type Balance: Parameter - + codec::HasCompact - + From - + Into - + Default - + MaybeSerializeDeserialize - + SomeAssociation - + scale_info::StaticTypeInfo; - #[pallet::constant] - type SomeConst: Get; - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - } - - #[pallet::pallet] - #[pallet::without_storage_info] - pub struct Pallet(_); - - #[pallet::hooks] - impl Hooks for Pallet { - fn on_initialize(_n: T::BlockNumber) -> Weight { - >::put(T::Balance::from(10)); - Weight::from_ref_time(10) - } - - fn on_finalize(_n: T::BlockNumber) { - >::put(T::Balance::from(11)); - } - } - - #[pallet::call] - impl Pallet { - #[pallet::call_index(0)] - #[pallet::weight(>::into(new_value.clone()))] - pub fn set_dummy( - origin: OriginFor, - #[pallet::compact] new_value: T::Balance, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - - >::put(&new_value); - Self::deposit_event(Event::Dummy(new_value)); - - Ok(().into()) - } - } - - #[pallet::error] - pub enum Error { - /// Some wrong behavior - Wrong, - } - - #[pallet::event] - #[pallet::generate_deposit(fn deposit_event)] - pub enum Event { - /// Dummy event, just here so there's a generic type that's used. - Dummy(T::Balance), - } - - #[pallet::storage] - /// Some documentation - type Dummy = StorageValue<_, T::Balance, OptionQuery>; - - #[pallet::storage] - type Bar = StorageMap<_, Blake2_128Concat, T::AccountId, T::Balance, ValueQuery>; - - #[pallet::type_value] - pub fn OnFooEmpty() -> T::Balance { - 3.into() - } - #[pallet::storage] - type Foo = StorageValue<_, T::Balance, ValueQuery, OnFooEmpty>; - - #[pallet::storage] - type Double = StorageDoubleMap< - _, - Blake2_128Concat, - u32, - Twox64Concat, - u64, - ::A, - ValueQuery, - >; - - #[pallet::genesis_config] - pub struct GenesisConfig { - dummy: Option, - bar: Vec<(T::AccountId, T::Balance)>, - foo: T::Balance, - } - - impl Default for GenesisConfig { - fn default() -> Self { - GenesisConfig { - dummy: Default::default(), - bar: Default::default(), - foo: OnFooEmpty::::get(), - } - } - } - - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { - fn build(&self) { - if let Some(dummy) = self.dummy.as_ref() { - >::put(dummy); - } - for (k, v) in &self.bar { - >::insert(k, v); - } - >::put(&self.foo); - } - } -} - -impl frame_system::Config for Runtime { - type BaseCallFilter = frame_support::traits::Everything; - type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u32; - type RuntimeCall = RuntimeCall; - type Hash = sp_runtime::testing::H256; - type Hashing = sp_runtime::traits::BlakeTwo256; - type AccountId = u64; - type Lookup = sp_runtime::traits::IdentityLookup; - type Header = Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU32<250>; - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; -} -impl pallet::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type SomeConst = ConstU64<10>; - type Balance = u64; -} -impl pallet_old::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type SomeConst = ConstU64<10>; - type Balance = u64; -} - -pub type Header = sp_runtime::generic::Header; -pub type Block = sp_runtime::generic::Block; -pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; - -frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - System: frame_system::{Pallet, Call, Event}, - // NOTE: name Example here is needed in order to have same module prefix - Example: pallet::{Pallet, Call, Event, Config, Storage}, - PalletOld: pallet_old::{Pallet, Call, Event, Config, Storage}, - } -); - -#[cfg(test)] -mod test { - use super::{pallet, pallet_old, Runtime}; - use codec::{Decode, Encode}; - use scale_info::{form::PortableForm, Variant}; - - #[test] - fn metadata() { - let metadata = Runtime::metadata(); - let (pallets, types) = match metadata.1 { - frame_support::metadata::RuntimeMetadata::V14(metadata) => - (metadata.pallets, metadata.types), - _ => unreachable!(), - }; - - let assert_meta_types = |ty_id1, ty_id2| { - let ty1 = types.resolve(ty_id1).map(|ty| ty.type_def()); - let ty2 = types.resolve(ty_id2).map(|ty| ty.type_def()); - pretty_assertions::assert_eq!(ty1, ty2); - }; - - let get_enum_variants = |ty_id| match types.resolve(ty_id).map(|ty| ty.type_def()) { - Some(ty) => match ty { - scale_info::TypeDef::Variant(var) => var.variants(), - _ => panic!("Expected variant type"), - }, - _ => panic!("No type found"), - }; - - let assert_enum_variants = |vs1: &[Variant], - vs2: &[Variant]| { - assert_eq!(vs1.len(), vs2.len()); - for i in 0..vs1.len() { - let v1 = &vs2[i]; - let v2 = &vs2[i]; - assert_eq!(v1.fields().len(), v2.fields().len()); - for f in 0..v1.fields().len() { - let f1 = &v1.fields()[f]; - let f2 = &v2.fields()[f]; - pretty_assertions::assert_eq!(f1.name(), f2.name()); - pretty_assertions::assert_eq!(f1.ty(), f2.ty()); - } - } - }; - - pretty_assertions::assert_eq!(pallets[1].storage, pallets[2].storage); - - let calls1 = pallets[1].calls.as_ref().unwrap(); - let calls2 = pallets[2].calls.as_ref().unwrap(); - assert_meta_types(calls1.ty.id(), calls2.ty.id()); - - // event: check variants and fields but ignore the type name which will be different - let event1_variants = get_enum_variants(pallets[1].event.as_ref().unwrap().ty.id()); - let event2_variants = get_enum_variants(pallets[2].event.as_ref().unwrap().ty.id()); - assert_enum_variants(event1_variants, event2_variants); - - let err1 = get_enum_variants(pallets[1].error.as_ref().unwrap().ty.id()) - .iter() - .filter(|v| v.name() == "__Ignore") - .cloned() - .collect::>(); - let err2 = get_enum_variants(pallets[2].error.as_ref().unwrap().ty.id()) - .iter() - .filter(|v| v.name() == "__Ignore") - .cloned() - .collect::>(); - assert_enum_variants(&err1, &err2); - - pretty_assertions::assert_eq!(pallets[1].constants, pallets[2].constants); - } - - #[test] - fn types() { - assert_eq!( - pallet_old::Event::::decode( - &mut &pallet::Event::::Dummy(10).encode()[..] - ) - .unwrap(), - pallet_old::Event::::Dummy(10), - ); - - assert_eq!( - pallet_old::Call::::decode( - &mut &pallet::Call::::set_dummy { new_value: 10 }.encode()[..] - ) - .unwrap(), - pallet_old::Call::::set_dummy { new_value: 10 }, - ); - } -} diff --git a/frame/support/test/tests/pallet_compatibility_instance.rs b/frame/support/test/tests/pallet_compatibility_instance.rs deleted file mode 100644 index 79370d911b94..000000000000 --- a/frame/support/test/tests/pallet_compatibility_instance.rs +++ /dev/null @@ -1,371 +0,0 @@ -// 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. - -// Old macros don't support the flag `no-metadata-docs` so the result differs when the feature is -// activated. -#![cfg(not(feature = "no-metadata-docs"))] - -use frame_support::traits::{ConstU32, ConstU64}; - -mod pallet_old { - use frame_support::{ - decl_error, decl_event, decl_module, decl_storage, traits::Get, weights::Weight, Parameter, - }; - use frame_system::ensure_root; - - pub trait Config: frame_system::Config { - type SomeConst: Get; - type Balance: Parameter + codec::HasCompact + From + Into + Default; - type RuntimeEvent: From> + Into<::RuntimeEvent>; - } - - decl_storage! { - trait Store for Module, I: Instance = DefaultInstance> as Example { - /// Some documentation - Dummy get(fn dummy) config(): Option; - Bar get(fn bar) config(): map hasher(blake2_128_concat) T::AccountId => T::Balance; - Foo get(fn foo) config(): T::Balance = 3.into(); - Double get(fn double): - double_map hasher(blake2_128_concat) u32, hasher(twox_64_concat) u64 => u16; - } - } - - decl_event!( - pub enum Event - where - Balance = >::Balance, - { - /// Dummy event, just here so there's a generic type that's used. - Dummy(Balance), - } - ); - - decl_module! { - pub struct Module, I: Instance = DefaultInstance> for enum Call - where origin: T::RuntimeOrigin - { - type Error = Error; - fn deposit_event() = default; - const SomeConst: T::Balance = T::SomeConst::get(); - - #[weight = >::into(new_value.clone())] - fn set_dummy(origin, #[compact] new_value: T::Balance) { - ensure_root(origin)?; - - >::put(&new_value); - Self::deposit_event(RawEvent::Dummy(new_value)); - } - - fn on_initialize(_n: T::BlockNumber) -> Weight { - >::put(T::Balance::from(10)); - Weight::from_ref_time(10) - } - - fn on_finalize(_n: T::BlockNumber) { - >::put(T::Balance::from(11)); - } - } - } - - decl_error! { - pub enum Error for Module, I: Instance> { - /// Some wrong behavior - Wrong, - } - } -} - -#[frame_support::pallet] -pub mod pallet { - use frame_support::{pallet_prelude::*, scale_info}; - use frame_system::{ensure_root, pallet_prelude::*}; - - #[pallet::config] - pub trait Config: frame_system::Config { - type Balance: Parameter - + codec::HasCompact - + From - + Into - + Default - + MaybeSerializeDeserialize - + scale_info::StaticTypeInfo; - #[pallet::constant] - type SomeConst: Get; - type RuntimeEvent: From> - + IsType<::RuntimeEvent>; - } - - #[pallet::pallet] - #[pallet::without_storage_info] - pub struct Pallet(PhantomData<(T, I)>); - - #[pallet::hooks] - impl, I: 'static> Hooks for Pallet { - fn on_initialize(_n: T::BlockNumber) -> Weight { - >::put(T::Balance::from(10)); - Weight::from_ref_time(10) - } - - fn on_finalize(_n: T::BlockNumber) { - >::put(T::Balance::from(11)); - } - } - - #[pallet::call] - impl, I: 'static> Pallet { - #[pallet::call_index(0)] - #[pallet::weight(>::into(new_value.clone()))] - pub fn set_dummy( - origin: OriginFor, - #[pallet::compact] new_value: T::Balance, - ) -> DispatchResultWithPostInfo { - ensure_root(origin)?; - - >::put(&new_value); - Self::deposit_event(Event::Dummy(new_value)); - - Ok(().into()) - } - } - - #[pallet::error] - pub enum Error { - /// Some wrong behavior - Wrong, - } - - #[pallet::event] - #[pallet::generate_deposit(fn deposit_event)] - pub enum Event, I: 'static = ()> { - /// Dummy event, just here so there's a generic type that's used. - Dummy(T::Balance), - } - - #[pallet::storage] - /// Some documentation - type Dummy, I: 'static = ()> = StorageValue<_, T::Balance, OptionQuery>; - - #[pallet::storage] - type Bar, I: 'static = ()> = - StorageMap<_, Blake2_128Concat, T::AccountId, T::Balance, ValueQuery>; - - #[pallet::storage] - type Foo, I: 'static = ()> = - StorageValue<_, T::Balance, ValueQuery, OnFooEmpty>; - #[pallet::type_value] - pub fn OnFooEmpty, I: 'static>() -> T::Balance { - 3.into() - } - - #[pallet::storage] - type Double = - StorageDoubleMap<_, Blake2_128Concat, u32, Twox64Concat, u64, u16, ValueQuery>; - - #[pallet::genesis_config] - pub struct GenesisConfig, I: 'static = ()> { - dummy: Option, - bar: Vec<(T::AccountId, T::Balance)>, - foo: T::Balance, - } - - impl, I: 'static> Default for GenesisConfig { - fn default() -> Self { - GenesisConfig { - dummy: Default::default(), - bar: Default::default(), - foo: OnFooEmpty::::get(), - } - } - } - - #[pallet::genesis_build] - impl, I: 'static> GenesisBuild for GenesisConfig { - fn build(&self) { - if let Some(dummy) = self.dummy.as_ref() { - >::put(dummy); - } - for (k, v) in &self.bar { - >::insert(k, v); - } - >::put(&self.foo); - } - } -} - -impl frame_system::Config for Runtime { - type BlockWeights = (); - type BlockLength = (); - type DbWeight = (); - type BaseCallFilter = frame_support::traits::Everything; - type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u32; - type RuntimeCall = RuntimeCall; - type Hash = sp_runtime::testing::H256; - type Hashing = sp_runtime::traits::BlakeTwo256; - type AccountId = u64; - type Lookup = sp_runtime::traits::IdentityLookup; - type Header = Header; - type RuntimeEvent = RuntimeEvent; - type BlockHashCount = ConstU32<250>; - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = ConstU32<16>; -} -impl pallet::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type SomeConst = ConstU64<10>; - type Balance = u64; -} -impl pallet::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type SomeConst = ConstU64<10>; - type Balance = u64; -} -impl pallet::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type SomeConst = ConstU64<10>; - type Balance = u64; -} -impl pallet_old::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type SomeConst = ConstU64<10>; - type Balance = u64; -} -impl pallet_old::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type SomeConst = ConstU64<10>; - type Balance = u64; -} -impl pallet_old::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type SomeConst = ConstU64<10>; - type Balance = u64; -} - -pub type Header = sp_runtime::generic::Header; -pub type Block = sp_runtime::generic::Block; -pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; - -frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { - System: frame_system::{Pallet, Call, Event}, - Example: pallet::{Pallet, Call, Event, Config, Storage}, - PalletOld: pallet_old::{Pallet, Call, Event, Config, Storage}, - Instance2Example: pallet::::{Pallet, Call, Event, Config, Storage}, - PalletOld2: pallet_old::::{Pallet, Call, Event, Config, Storage}, - Instance3Example: pallet::::{Pallet, Call, Event, Config, Storage}, - PalletOld3: pallet_old::::{Pallet, Call, Event, Config, Storage}, - } -); - -#[cfg(test)] -mod test { - use super::{pallet, pallet_old, Runtime}; - use codec::{Decode, Encode}; - use scale_info::{form::PortableForm, Variant}; - - #[test] - fn metadata() { - let metadata = Runtime::metadata(); - let (pallets, types) = match metadata.1 { - frame_support::metadata::RuntimeMetadata::V14(metadata) => - (metadata.pallets, metadata.types), - _ => unreachable!(), - }; - - let get_enum_variants = |ty_id| match types.resolve(ty_id).map(|ty| ty.type_def()) { - Some(ty) => match ty { - scale_info::TypeDef::Variant(var) => var.variants(), - _ => panic!("Expected variant type"), - }, - _ => panic!("No type found"), - }; - - let assert_enum_variants = |vs1: &[Variant], - vs2: &[Variant]| { - assert_eq!(vs1.len(), vs2.len()); - for i in 0..vs1.len() { - let v1 = &vs2[i]; - let v2 = &vs2[i]; - assert_eq!(v1.fields().len(), v2.fields().len()); - for f in 0..v1.fields().len() { - let f1 = &v1.fields()[f]; - let f2 = &v2.fields()[f]; - pretty_assertions::assert_eq!(f1.name(), f2.name()); - pretty_assertions::assert_eq!(f1.ty(), f2.ty()); - } - } - }; - - for i in vec![1, 3, 5].into_iter() { - pretty_assertions::assert_eq!(pallets[i].storage, pallets[i + 1].storage); - - let call1_variants = get_enum_variants(pallets[i].calls.as_ref().unwrap().ty.id()); - let call2_variants = get_enum_variants(pallets[i + 1].calls.as_ref().unwrap().ty.id()); - assert_enum_variants(call1_variants, call2_variants); - - // event: check variants and fields but ignore the type name which will be different - let event1_variants = get_enum_variants(pallets[i].event.as_ref().unwrap().ty.id()); - let event2_variants = get_enum_variants(pallets[i + 1].event.as_ref().unwrap().ty.id()); - assert_enum_variants(event1_variants, event2_variants); - - let err1 = get_enum_variants(pallets[i].error.as_ref().unwrap().ty.id()) - .iter() - .filter(|v| v.name() == "__Ignore") - .cloned() - .collect::>(); - let err2 = get_enum_variants(pallets[i + 1].error.as_ref().unwrap().ty.id()) - .iter() - .filter(|v| v.name() == "__Ignore") - .cloned() - .collect::>(); - assert_enum_variants(&err1, &err2); - - pretty_assertions::assert_eq!(pallets[i].constants, pallets[i + 1].constants); - } - } - - #[test] - fn types() { - assert_eq!( - pallet_old::Event::::decode( - &mut &pallet::Event::::Dummy(10).encode()[..] - ) - .unwrap(), - pallet_old::Event::::Dummy(10), - ); - - assert_eq!( - pallet_old::Call::::decode( - &mut &pallet::Call::::set_dummy { new_value: 10 }.encode()[..] - ) - .unwrap(), - pallet_old::Call::::set_dummy { new_value: 10 }, - ); - } -} diff --git a/frame/support/test/tests/pallet_instance.rs b/frame/support/test/tests/pallet_instance.rs index 7fec0aa9d0e0..4ecb9bcb58ae 100644 --- a/frame/support/test/tests/pallet_instance.rs +++ b/frame/support/test/tests/pallet_instance.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,21 +18,23 @@ use frame_support::{ dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, Pays, UnfilteredDispatchable}, pallet_prelude::ValueQuery, + parameter_types, storage::unhashed, traits::{ConstU32, GetCallName, OnFinalize, OnGenesis, OnInitialize, OnRuntimeUpgrade}, + weights::Weight, }; use sp_io::{ hashing::{blake2_128, twox_128, twox_64}, TestExternalities, }; use sp_runtime::{DispatchError, ModuleError}; +use sp_std::any::TypeId; -#[frame_support::pallet] +#[frame_support::pallet(dev_mode)] pub mod pallet { - use codec::MaxEncodedLen; - use frame_support::{pallet_prelude::*, parameter_types, scale_info}; + use super::*; + use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; - use sp_std::any::TypeId; type BalanceOf = >::Balance; @@ -46,7 +48,6 @@ pub mod pallet { } #[pallet::pallet] - #[pallet::generate_store(pub(crate) trait Store)] pub struct Pallet(PhantomData<(T, I)>); #[pallet::hooks] @@ -54,10 +55,10 @@ pub mod pallet { fn on_initialize(_: BlockNumberFor) -> Weight { if TypeId::of::() == TypeId::of::<()>() { Self::deposit_event(Event::Something(10)); - Weight::from_ref_time(10) + Weight::from_parts(10, 0) } else { Self::deposit_event(Event::Something(11)); - Weight::from_ref_time(11) + Weight::from_parts(11, 0) } } fn on_finalize(_: BlockNumberFor) { @@ -70,10 +71,10 @@ pub mod pallet { fn on_runtime_upgrade() -> Weight { if TypeId::of::() == TypeId::of::<()>() { Self::deposit_event(Event::Something(30)); - Weight::from_ref_time(30) + Weight::from_parts(30, 0) } else { Self::deposit_event(Event::Something(31)); - Weight::from_ref_time(31) + Weight::from_parts(31, 0) } } fn integrity_test() {} @@ -83,7 +84,7 @@ pub mod pallet { impl, I: 'static> Pallet { /// Doc comment put in metadata #[pallet::call_index(0)] - #[pallet::weight(Weight::from_ref_time(*_foo as u64))] + #[pallet::weight(Weight::from_parts(*_foo as u64, 0))] pub fn foo( origin: OriginFor, #[pallet::compact] _foo: u32, @@ -184,13 +185,15 @@ pub mod pallet { >; #[pallet::genesis_config] - #[derive(Default)] - pub struct GenesisConfig { + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig, I: 'static = ()> { + #[serde(skip)] + _config: sp_std::marker::PhantomData<(T, I)>, _myfield: u32, } #[pallet::genesis_build] - impl, I: 'static> GenesisBuild for GenesisConfig { + impl, I: 'static> BuildGenesisConfig for GenesisConfig { fn build(&self) {} } @@ -260,7 +263,6 @@ pub mod pallet2 { } #[pallet::pallet] - #[pallet::generate_store(pub(crate) trait Store)] pub struct Pallet(PhantomData<(T, I)>); #[pallet::event] @@ -281,7 +283,7 @@ pub mod pallet2 { } #[pallet::genesis_build] - impl, I: 'static> GenesisBuild for GenesisConfig { + impl, I: 'static> BuildGenesisConfig for GenesisConfig { fn build(&self) {} } } @@ -289,14 +291,13 @@ pub mod pallet2 { impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u32; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = sp_runtime::testing::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = sp_runtime::traits::IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU32<250>; type BlockWeights = (); @@ -334,10 +335,7 @@ pub type Block = sp_runtime::generic::Block; pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { // Exclude part `Storage` in order not to check its metadata in tests. System: frame_system exclude_parts { Storage }, @@ -348,15 +346,13 @@ frame_support::construct_runtime!( } ); -use frame_support::weights::Weight; - #[test] fn call_expand() { let call_foo = pallet::Call::::foo { foo: 3 }; assert_eq!( call_foo.get_dispatch_info(), DispatchInfo { - weight: Weight::from_ref_time(3), + weight: Weight::from_parts(3, 0), class: DispatchClass::Normal, pays_fee: Pays::Yes } @@ -368,7 +364,7 @@ fn call_expand() { assert_eq!( call_foo.get_dispatch_info(), DispatchInfo { - weight: Weight::from_ref_time(3), + weight: Weight::from_parts(3, 0), class: DispatchClass::Normal, pays_fee: Pays::Yes } @@ -421,6 +417,39 @@ fn error_expand() { ); } +#[test] +fn module_error_outer_enum_expand() { + // assert that all variants of the Example pallet are included into the + // RuntimeError definition. + match RuntimeError::Example(pallet::Error::InsufficientProposersBalance) { + RuntimeError::Example(example) => match example { + pallet::Error::InsufficientProposersBalance => (), + pallet::Error::NonExistentStorageValue => (), + // Extra pattern added by `construct_runtime`. + pallet::Error::__Ignore(_, _) => (), + }, + _ => (), + }; +} + +#[test] +fn module_error_from_dispatch_error() { + let dispatch_err = DispatchError::Module(ModuleError { + index: 1, + error: [0; 4], + message: Some("InsufficientProposersBalance"), + }); + let err = RuntimeError::from_dispatch_error(dispatch_err).unwrap(); + + match err { + RuntimeError::Example(pallet::Error::InsufficientProposersBalance) => (), + _ => panic!("Module error constructed incorrectly"), + }; + + // Only `ModuleError` is converted. + assert!(RuntimeError::from_dispatch_error(DispatchError::BadOrigin).is_none()); +} + #[test] fn instance_expand() { // assert same type @@ -649,10 +678,10 @@ fn pallet_hooks_expand() { TestExternalities::default().execute_with(|| { frame_system::Pallet::::set_block_number(1); - assert_eq!(AllPalletsWithoutSystem::on_initialize(1), Weight::from_ref_time(21)); + assert_eq!(AllPalletsWithoutSystem::on_initialize(1), Weight::from_parts(21, 0)); AllPalletsWithoutSystem::on_finalize(1); - assert_eq!(AllPalletsWithoutSystem::on_runtime_upgrade(), Weight::from_ref_time(61)); + assert_eq!(AllPalletsWithoutSystem::on_runtime_upgrade(), Weight::from_parts(61, 0)); assert_eq!( frame_system::Pallet::::events()[0].event, @@ -692,7 +721,7 @@ fn pallet_on_genesis() { #[test] fn metadata() { - use frame_support::metadata::*; + use frame_support::metadata::{v14::*, *}; let system_pallet_metadata = PalletMetadata { index: 0, diff --git a/frame/support/test/tests/pallet_outer_enums_explicit.rs b/frame/support/test/tests/pallet_outer_enums_explicit.rs new file mode 100644 index 000000000000..a8250f8b1532 --- /dev/null +++ b/frame/support/test/tests/pallet_outer_enums_explicit.rs @@ -0,0 +1,122 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use frame_support::{derive_impl, traits::ConstU32}; + +mod common; + +use common::outer_enums::{pallet, pallet2}; + +pub type Header = sp_runtime::generic::Header; +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type Block = Block; + type BlockHashCount = ConstU32<10>; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type OnSetCode = (); +} + +impl common::outer_enums::pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet2::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet2::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet3::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet3::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + +frame_support::construct_runtime!( + pub struct Runtime + { + // Exclude part `Storage` in order not to check its metadata in tests. + System: frame_system::{Pallet, Config, Call, Event }, + + // This pallet exposes the Error type explicitly. + Example: common::outer_enums::pallet::{Pallet, Config, Event, Error}, + Instance1Example: common::outer_enums::pallet::::{ Pallet, Config, Event }, + + // This pallet does not mention the Error type, but it must be propagated (similarly to the polkadot/kusama). + Example2: common::outer_enums::pallet2::{Pallet, Config, Event }, + Instance1Example2: common::outer_enums::pallet2::::{Pallet, Config, Event}, + + // This pallet does not declare any errors. + Example3: common::outer_enums::pallet3::{Pallet, Config, Event}, + Instance1Example3: common::outer_enums::pallet3::::{Pallet, Config, Event}, + } +); + +#[test] +fn module_error_outer_enum_expand_explicit() { + // The Runtime has *all* parts explicitly defined. + + // Check that all error types are propagated + match RuntimeError::Example(pallet::Error::InsufficientProposersBalance) { + // Error passed implicitely to the pallet system. + RuntimeError::System(system) => match system { + frame_system::Error::InvalidSpecName => (), + frame_system::Error::SpecVersionNeedsToIncrease => (), + frame_system::Error::FailedToExtractRuntimeVersion => (), + frame_system::Error::NonDefaultComposite => (), + frame_system::Error::NonZeroRefCount => (), + frame_system::Error::CallFiltered => (), + frame_system::Error::__Ignore(_, _) => (), + }, + + // Error declared explicitly. + RuntimeError::Example(example) => match example { + pallet::Error::InsufficientProposersBalance => (), + pallet::Error::NonExistentStorageValue => (), + pallet::Error::__Ignore(_, _) => (), + }, + // Error declared explicitly. + RuntimeError::Instance1Example(example) => match example { + pallet::Error::InsufficientProposersBalance => (), + pallet::Error::NonExistentStorageValue => (), + pallet::Error::__Ignore(_, _) => (), + }, + + // Error must propagate even if not defined explicitly as pallet part. + RuntimeError::Example2(example) => match example { + pallet2::Error::OtherInsufficientProposersBalance => (), + pallet2::Error::OtherNonExistentStorageValue => (), + pallet2::Error::__Ignore(_, _) => (), + }, + // Error must propagate even if not defined explicitly as pallet part. + RuntimeError::Instance1Example2(example) => match example { + pallet2::Error::OtherInsufficientProposersBalance => (), + pallet2::Error::OtherNonExistentStorageValue => (), + pallet2::Error::__Ignore(_, _) => (), + }, + }; +} diff --git a/frame/support/test/tests/pallet_outer_enums_implicit.rs b/frame/support/test/tests/pallet_outer_enums_implicit.rs new file mode 100644 index 000000000000..191f095f5d78 --- /dev/null +++ b/frame/support/test/tests/pallet_outer_enums_implicit.rs @@ -0,0 +1,122 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use frame_support::{derive_impl, traits::ConstU32}; + +mod common; + +use common::outer_enums::{pallet, pallet2}; + +pub type Header = sp_runtime::generic::Header; +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type Block = Block; + type BlockHashCount = ConstU32<10>; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type OnSetCode = (); +} + +impl common::outer_enums::pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet2::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet2::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet3::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} +impl common::outer_enums::pallet3::Config for Runtime { + type RuntimeEvent = RuntimeEvent; +} + +frame_support::construct_runtime!( + pub struct Runtime + { + // Exclude part `Storage` in order not to check its metadata in tests. + System: frame_system exclude_parts { Storage }, + + // Pallet exposes `Error` implicitely. + Example: common::outer_enums::pallet, + Instance1Example: common::outer_enums::pallet::, + + // Pallet exposes `Error` implicitely. + Example2: common::outer_enums::pallet2, + Instance1Example2: common::outer_enums::pallet2::, + + // Pallet does not implement error. + Example3: common::outer_enums::pallet3, + Instance1Example3: common::outer_enums::pallet3::, + } +); + +#[test] +fn module_error_outer_enum_expand_implicit() { + // The Runtime has *all* parts implicitly defined. + + // Check that all error types are propagated + match RuntimeError::Example(pallet::Error::InsufficientProposersBalance) { + // Error passed implicitely to the pallet system. + RuntimeError::System(system) => match system { + frame_system::Error::InvalidSpecName => (), + frame_system::Error::SpecVersionNeedsToIncrease => (), + frame_system::Error::FailedToExtractRuntimeVersion => (), + frame_system::Error::NonDefaultComposite => (), + frame_system::Error::NonZeroRefCount => (), + frame_system::Error::CallFiltered => (), + frame_system::Error::__Ignore(_, _) => (), + }, + + // Error declared explicitly. + RuntimeError::Example(example) => match example { + pallet::Error::InsufficientProposersBalance => (), + pallet::Error::NonExistentStorageValue => (), + pallet::Error::__Ignore(_, _) => (), + }, + // Error declared explicitly. + RuntimeError::Instance1Example(example) => match example { + pallet::Error::InsufficientProposersBalance => (), + pallet::Error::NonExistentStorageValue => (), + pallet::Error::__Ignore(_, _) => (), + }, + + // Error must propagate even if not defined explicitly as pallet part. + RuntimeError::Example2(example) => match example { + pallet2::Error::OtherInsufficientProposersBalance => (), + pallet2::Error::OtherNonExistentStorageValue => (), + pallet2::Error::__Ignore(_, _) => (), + }, + // Error must propagate even if not defined explicitly as pallet part. + RuntimeError::Instance1Example2(example) => match example { + pallet2::Error::OtherInsufficientProposersBalance => (), + pallet2::Error::OtherNonExistentStorageValue => (), + pallet2::Error::__Ignore(_, _) => (), + }, + }; +} diff --git a/frame/support/test/tests/pallet_ui.rs b/frame/support/test/tests/pallet_ui.rs index 26d016c5a779..466957c9faa6 100644 --- a/frame/support/test/tests/pallet_ui.rs +++ b/frame/support/test/tests/pallet_ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr index aed53b07b5e6..d10bf1359019 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound.stderr @@ -1,3 +1,16 @@ +error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: + It is deprecated to use hard-coded constant as call weight. + Please instead benchmark all calls or put the pallet into `dev` mode. + + For more info see: + + --> tests/pallet_ui/call_argument_invalid_bound.rs:19:20 + | +19 | #[pallet::weight(0)] + | ^ + | + = note: `-D deprecated` implied by `-D warnings` + error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` --> tests/pallet_ui/call_argument_invalid_bound.rs:21:36 | diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr index 3e2e70432a9c..7173cdcd4736 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_2.stderr @@ -1,3 +1,16 @@ +error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: + It is deprecated to use hard-coded constant as call weight. + Please instead benchmark all calls or put the pallet into `dev` mode. + + For more info see: + + --> tests/pallet_ui/call_argument_invalid_bound_2.rs:19:20 + | +19 | #[pallet::weight(0)] + | ^ + | + = note: `-D deprecated` implied by `-D warnings` + error[E0277]: `::Bar` doesn't implement `std::fmt::Debug` --> tests/pallet_ui/call_argument_invalid_bound_2.rs:21:36 | @@ -23,17 +36,11 @@ error[E0369]: binary operation `==` cannot be applied to type `&::Bar: WrapperTypeEncode` is not satisfied --> tests/pallet_ui/call_argument_invalid_bound_2.rs:21:36 | -1 | / #[frame_support::pallet] -2 | | mod pallet { -3 | | use frame_support::pallet_prelude::{Hooks, DispatchResultWithPostInfo}; -4 | | use frame_system::pallet_prelude::{BlockNumberFor, OriginFor}; -... | -16 | | -17 | | #[pallet::call] - | |__________________- required by a bound introduced by this call +1 | #[frame_support::pallet] + | ------------------------ required by a bound introduced by this call ... -21 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { - | ^^^^ the trait `WrapperTypeEncode` is not implemented for `::Bar` +21 | pub fn foo(origin: OriginFor, _bar: T::Bar) -> DispatchResultWithPostInfo { + | ^^^^ the trait `WrapperTypeEncode` is not implemented for `::Bar` | = note: required for `::Bar` to implement `Encode` diff --git a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr index 395da09cb0d6..4cbed3709626 100644 --- a/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr +++ b/frame/support/test/tests/pallet_ui/call_argument_invalid_bound_3.stderr @@ -1,3 +1,16 @@ +error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: + It is deprecated to use hard-coded constant as call weight. + Please instead benchmark all calls or put the pallet into `dev` mode. + + For more info see: + + --> tests/pallet_ui/call_argument_invalid_bound_3.rs:21:20 + | +21 | #[pallet::weight(0)] + | ^ + | + = note: `-D deprecated` implied by `-D warnings` + error[E0277]: `Bar` doesn't implement `std::fmt::Debug` --> tests/pallet_ui/call_argument_invalid_bound_3.rs:23:36 | @@ -10,5 +23,6 @@ error[E0277]: `Bar` doesn't implement `std::fmt::Debug` = note: required for the cast from `&Bar` to the object type `dyn std::fmt::Debug` help: consider annotating `Bar` with `#[derive(Debug)]` | -17 | #[derive(Debug)] +17 + #[derive(Debug)] +18 | struct Bar; | diff --git a/frame/support/test/tests/pallet_ui/call_missing_index.rs b/frame/support/test/tests/pallet_ui/call_missing_index.rs index 7e305a573d62..98c49f493e50 100644 --- a/frame/support/test/tests/pallet_ui/call_missing_index.rs +++ b/frame/support/test/tests/pallet_ui/call_missing_index.rs @@ -15,6 +15,11 @@ mod pallet { pub fn foo(_: OriginFor) -> DispatchResult { Ok(()) } + + #[pallet::weight(0)] + pub fn bar(_: OriginFor) -> DispatchResult { + Ok(()) + } } } diff --git a/frame/support/test/tests/pallet_ui/call_missing_index.stderr b/frame/support/test/tests/pallet_ui/call_missing_index.stderr index 9d0020497921..82dbe1d24c28 100644 --- a/frame/support/test/tests/pallet_ui/call_missing_index.stderr +++ b/frame/support/test/tests/pallet_ui/call_missing_index.stderr @@ -1,12 +1,47 @@ -error: use of deprecated struct `pallet::warnings::foo`: - Implicit call indices are deprecated in favour of explicit ones. - Please ensure that all calls have the `pallet::call_index` attribute or that the - `dev-mode` of the pallet is enabled. For more info see: - and - . +error: use of deprecated constant `pallet::warnings::ImplicitCallIndex_0::_w`: + It is deprecated to use implicit call indices. + Please instead ensure that all calls have a `pallet::call_index` attribute or put the pallet into `dev` mode. + + For more info see: + + --> tests/pallet_ui/call_missing_index.rs:15:10 | 15 | pub fn foo(_: OriginFor) -> DispatchResult { | ^^^ | = note: `-D deprecated` implied by `-D warnings` + +error: use of deprecated constant `pallet::warnings::ImplicitCallIndex_1::_w`: + It is deprecated to use implicit call indices. + Please instead ensure that all calls have a `pallet::call_index` attribute or put the pallet into `dev` mode. + + For more info see: + + + --> tests/pallet_ui/call_missing_index.rs:20:10 + | +20 | pub fn bar(_: OriginFor) -> DispatchResult { + | ^^^ + +error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: + It is deprecated to use hard-coded constant as call weight. + Please instead benchmark all calls or put the pallet into `dev` mode. + + For more info see: + + --> tests/pallet_ui/call_missing_index.rs:14:20 + | +14 | #[pallet::weight(0)] + | ^ + +error: use of deprecated constant `pallet::warnings::ConstantWeight_1::_w`: + It is deprecated to use hard-coded constant as call weight. + Please instead benchmark all calls or put the pallet into `dev` mode. + + For more info see: + + --> tests/pallet_ui/call_missing_index.rs:19:20 + | +19 | #[pallet::weight(0)] + | ^ diff --git a/frame/support/test/tests/pallet_ui/call_missing_weight.stderr b/frame/support/test/tests/pallet_ui/call_missing_weight.stderr index ec45d478870c..0a6cf16571f9 100644 --- a/frame/support/test/tests/pallet_ui/call_missing_weight.stderr +++ b/frame/support/test/tests/pallet_ui/call_missing_weight.stderr @@ -1,5 +1,7 @@ -error: Invalid pallet::call, requires weight attribute i.e. `#[pallet::weight($expr)]` - --> $DIR/call_missing_weight.rs:17:7 +error: A pallet::call requires either a concrete `#[pallet::weight($expr)]` or an + inherited weight from the `#[pallet:call(weight($type))]` attribute, but + none were given. + --> tests/pallet_ui/call_missing_weight.rs:17:7 | 17 | pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo {} | ^^ diff --git a/frame/support/test/tests/pallet_ui/weight_argument_has_suffix.rs b/frame/support/test/tests/pallet_ui/call_weight_argument_has_suffix.rs similarity index 70% rename from frame/support/test/tests/pallet_ui/weight_argument_has_suffix.rs rename to frame/support/test/tests/pallet_ui/call_weight_argument_has_suffix.rs index 99195d21be62..c122877e8a07 100644 --- a/frame/support/test/tests/pallet_ui/weight_argument_has_suffix.rs +++ b/frame/support/test/tests/pallet_ui/call_weight_argument_has_suffix.rs @@ -1,6 +1,6 @@ #[frame_support::pallet] mod pallet { - use frame_support::pallet_prelude::DispatchResultWithPostInfo; + use frame_support::pallet_prelude::DispatchResult; use frame_system::pallet_prelude::OriginFor; #[pallet::config] @@ -13,7 +13,7 @@ mod pallet { impl Pallet { #[pallet::call_index(0)] #[pallet::weight(10_000something)] - pub fn foo(origin: OriginFor) -> DispatchResultWithPostInfo { Ok(().into()) } + pub fn foo(_: OriginFor) -> DispatchResult { Ok(()) } } } diff --git a/frame/support/test/tests/pallet_ui/call_weight_argument_has_suffix.stderr b/frame/support/test/tests/pallet_ui/call_weight_argument_has_suffix.stderr new file mode 100644 index 000000000000..0651f003b9e2 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_weight_argument_has_suffix.stderr @@ -0,0 +1,20 @@ +error: invalid suffix `something` for number literal + --> tests/pallet_ui/call_weight_argument_has_suffix.rs:15:26 + | +15 | #[pallet::weight(10_000something)] + | ^^^^^^^^^^^^^^^ invalid suffix `something` + | + = help: the suffix must be one of the numeric types (`u32`, `isize`, `f32`, etc.) + +error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: + It is deprecated to use hard-coded constant as call weight. + Please instead benchmark all calls or put the pallet into `dev` mode. + + For more info see: + + --> tests/pallet_ui/call_weight_argument_has_suffix.rs:15:26 + | +15 | #[pallet::weight(10_000something)] + | ^^^^^^^^^^^^^^^ + | + = note: `-D deprecated` implied by `-D warnings` diff --git a/frame/support/test/tests/pallet_ui/call_weight_const_warning.rs b/frame/support/test/tests/pallet_ui/call_weight_const_warning.rs new file mode 100644 index 000000000000..2e5dc2a649e7 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_weight_const_warning.rs @@ -0,0 +1,21 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::DispatchResult; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(123_u64)] + pub fn foo(_: OriginFor) -> DispatchResult { Ok(()) } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_weight_const_warning.stderr b/frame/support/test/tests/pallet_ui/call_weight_const_warning.stderr new file mode 100644 index 000000000000..b04c3ec395ce --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_weight_const_warning.stderr @@ -0,0 +1,12 @@ +error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: + It is deprecated to use hard-coded constant as call weight. + Please instead benchmark all calls or put the pallet into `dev` mode. + + For more info see: + + --> tests/pallet_ui/call_weight_const_warning.rs:15:26 + | +15 | #[pallet::weight(123_u64)] + | ^^^^^^^ + | + = note: `-D deprecated` implied by `-D warnings` diff --git a/frame/support/test/tests/pallet_ui/call_weight_const_warning_twice.rs b/frame/support/test/tests/pallet_ui/call_weight_const_warning_twice.rs new file mode 100644 index 000000000000..798911dba33d --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_weight_const_warning_twice.rs @@ -0,0 +1,25 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::DispatchResult; + use frame_system::pallet_prelude::OriginFor; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(123)] + pub fn foo(_: OriginFor) -> DispatchResult { Ok(()) } + + #[pallet::call_index(1)] + #[pallet::weight(123_custom_prefix)] + pub fn bar(_: OriginFor) -> DispatchResult { Ok(()) } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_weight_const_warning_twice.stderr b/frame/support/test/tests/pallet_ui/call_weight_const_warning_twice.stderr new file mode 100644 index 000000000000..c65679020750 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_weight_const_warning_twice.stderr @@ -0,0 +1,31 @@ +error: invalid suffix `custom_prefix` for number literal + --> tests/pallet_ui/call_weight_const_warning_twice.rs:19:26 + | +19 | #[pallet::weight(123_custom_prefix)] + | ^^^^^^^^^^^^^^^^^ invalid suffix `custom_prefix` + | + = help: the suffix must be one of the numeric types (`u32`, `isize`, `f32`, etc.) + +error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: + It is deprecated to use hard-coded constant as call weight. + Please instead benchmark all calls or put the pallet into `dev` mode. + + For more info see: + + --> tests/pallet_ui/call_weight_const_warning_twice.rs:15:26 + | +15 | #[pallet::weight(123)] + | ^^^ + | + = note: `-D deprecated` implied by `-D warnings` + +error: use of deprecated constant `pallet::warnings::ConstantWeight_1::_w`: + It is deprecated to use hard-coded constant as call weight. + Please instead benchmark all calls or put the pallet into `dev` mode. + + For more info see: + + --> tests/pallet_ui/call_weight_const_warning_twice.rs:19:26 + | +19 | #[pallet::weight(123_custom_prefix)] + | ^^^^^^^^^^^^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid.rs b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid.rs new file mode 100644 index 000000000000..ff235e986099 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid.rs @@ -0,0 +1,50 @@ +use frame_support::pallet_prelude::*; + +pub trait WeightInfo { + fn foo() -> Weight; +} + +#[frame_support::pallet] +mod pallet { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type WeightInfo: crate::WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call(invalid)] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +#[frame_support::pallet] +mod assign { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type WeightInfo: crate::WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call = invalid] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid.stderr b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid.stderr new file mode 100644 index 000000000000..7eed646e7b19 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid.stderr @@ -0,0 +1,11 @@ +error: expected `weight` + --> tests/pallet_ui/call_weight_inherited_invalid.rs:19:17 + | +19 | #[pallet::call(invalid)] + | ^^^^^^^ + +error: expected parentheses + --> tests/pallet_ui/call_weight_inherited_invalid.rs:40:17 + | +40 | #[pallet::call = invalid] + | ^ diff --git a/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid2.rs b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid2.rs new file mode 100644 index 000000000000..76ccf5db2201 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid2.rs @@ -0,0 +1,53 @@ +// Weight is an ident instead of a type. + +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; + +pub trait WeightInfo { + fn foo() -> Weight; +} + +#[frame_support::pallet] +mod parentheses { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type WeightInfo: crate::WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call(weight(prefix))] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +#[frame_support::pallet] +mod assign { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type WeightInfo: crate::WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call(weight = prefix)] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid2.stderr b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid2.stderr new file mode 100644 index 000000000000..29f3b6bfd2b0 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid2.stderr @@ -0,0 +1,11 @@ +error[E0412]: cannot find type `prefix` in this scope + --> tests/pallet_ui/call_weight_inherited_invalid2.rs:22:24 + | +22 | #[pallet::call(weight(prefix))] + | ^^^^^^ not found in this scope + +error[E0412]: cannot find type `prefix` in this scope + --> tests/pallet_ui/call_weight_inherited_invalid2.rs:43:26 + | +43 | #[pallet::call(weight = prefix)] + | ^^^^^^ not found in this scope diff --git a/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid3.rs b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid3.rs new file mode 100644 index 000000000000..b31bc0ae234b --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid3.rs @@ -0,0 +1,53 @@ +// Call weight is an LitInt instead of a type. + +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; + +pub trait WeightInfo { + fn foo() -> Weight; +} + +#[frame_support::pallet] +mod parentheses { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type WeightInfo: crate::WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call(weight(123))] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +#[frame_support::pallet] +mod assign { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type WeightInfo: crate::WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call(weight = 123)] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid3.stderr b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid3.stderr new file mode 100644 index 000000000000..fab7acb90dea --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid3.stderr @@ -0,0 +1,19 @@ +error: expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, `dyn`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime + --> tests/pallet_ui/call_weight_inherited_invalid3.rs:22:24 + | +22 | #[pallet::call(weight(123))] + | ^^^ + +error: expected one of: `for`, parentheses, `fn`, `unsafe`, `extern`, identifier, `::`, `<`, `dyn`, square brackets, `*`, `&`, `!`, `impl`, `_`, lifetime + --> tests/pallet_ui/call_weight_inherited_invalid3.rs:43:26 + | +43 | #[pallet::call(weight = 123)] + | ^^^ + +error: unused import: `frame_system::pallet_prelude::*` + --> tests/pallet_ui/call_weight_inherited_invalid3.rs:4:5 + | +4 | use frame_system::pallet_prelude::*; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D unused-imports` implied by `-D warnings` diff --git a/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid4.rs b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid4.rs new file mode 100644 index 000000000000..39c0929d603a --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid4.rs @@ -0,0 +1,52 @@ +// Function does not exist in the trait. + +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; + +pub trait WeightInfo { +} + +#[frame_support::pallet] +mod parentheses { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type WeightInfo: crate::WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call(weight(::WeightInfo))] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +#[frame_support::pallet] +mod assign { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type WeightInfo: crate::WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call(weight = ::WeightInfo)] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid4.stderr b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid4.stderr new file mode 100644 index 000000000000..fbde5c691c5a --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid4.stderr @@ -0,0 +1,11 @@ +error[E0599]: no function or associated item named `foo` found for associated type `::WeightInfo` in the current scope + --> tests/pallet_ui/call_weight_inherited_invalid4.rs:24:10 + | +24 | pub fn foo(_: OriginFor) -> DispatchResult { + | ^^^ function or associated item not found in `::WeightInfo` + +error[E0599]: no function or associated item named `foo` found for associated type `::WeightInfo` in the current scope + --> tests/pallet_ui/call_weight_inherited_invalid4.rs:45:10 + | +45 | pub fn foo(_: OriginFor) -> DispatchResult { + | ^^^ function or associated item not found in `::WeightInfo` diff --git a/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid5.rs b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid5.rs new file mode 100644 index 000000000000..a5b2f5c7f6aa --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid5.rs @@ -0,0 +1,44 @@ +// Stray tokens after good input. + +#[frame_support::pallet] +mod parentheses { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call(weight(::WeightInfo straycat))] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +#[frame_support::pallet] +mod assign { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call(weight = ::WeightInfo straycat)] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid5.stderr b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid5.stderr new file mode 100644 index 000000000000..c0e9ef2d9e9d --- /dev/null +++ b/frame/support/test/tests/pallet_ui/call_weight_inherited_invalid5.stderr @@ -0,0 +1,11 @@ +error: unexpected token + --> tests/pallet_ui/call_weight_inherited_invalid5.rs:14:50 + | +14 | #[pallet::call(weight(::WeightInfo straycat))] + | ^^^^^^^^ + +error: unexpected token + --> tests/pallet_ui/call_weight_inherited_invalid5.rs:34:52 + | +34 | #[pallet::call(weight = ::WeightInfo straycat)] + | ^^^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/compare_unset_storage_version.rs b/frame/support/test/tests/pallet_ui/compare_unset_storage_version.rs new file mode 100644 index 000000000000..e417c619fc45 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/compare_unset_storage_version.rs @@ -0,0 +1,27 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_runtime_upgrade() -> Weight { + if Self::current_storage_version() != Self::on_chain_storage_version() { + + } + + Default::default() + } + } + + #[pallet::call] + impl Pallet {} +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/compare_unset_storage_version.stderr b/frame/support/test/tests/pallet_ui/compare_unset_storage_version.stderr new file mode 100644 index 000000000000..e75aa5226153 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/compare_unset_storage_version.stderr @@ -0,0 +1,7 @@ +error[E0369]: binary operation `!=` cannot be applied to type `NoStorageVersionSet` + --> tests/pallet_ui/compare_unset_storage_version.rs:15:39 + | +15 | if Self::current_storage_version() != Self::on_chain_storage_version() { + | ------------------------------- ^^ -------------------------------- StorageVersion + | | + | NoStorageVersionSet diff --git a/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.rs b/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.rs new file mode 100644 index 000000000000..74692ee94efd --- /dev/null +++ b/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.rs @@ -0,0 +1,13 @@ +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::composite_enum] + pub enum HoldReasons {} +} + +fn main() {} \ No newline at end of file diff --git a/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.stderr b/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.stderr new file mode 100644 index 000000000000..902e8923759b --- /dev/null +++ b/frame/support/test/tests/pallet_ui/composite_enum_unsupported_identifier.stderr @@ -0,0 +1,5 @@ +error: expected one of: `FreezeReason`, `HoldReason`, `LockId`, `SlashReason` + --> tests/pallet_ui/composite_enum_unsupported_identifier.rs:10:11 + | +10 | pub enum HoldReasons {} + | ^^^^^^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/deprecated_store_attr.rs b/frame/support/test/tests/pallet_ui/deprecated_store_attr.rs new file mode 100644 index 000000000000..0799a3fce8d5 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/deprecated_store_attr.rs @@ -0,0 +1,11 @@ +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + #[pallet::generate_store(trait Store)] + pub struct Pallet(core::marker::PhantomData); +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/deprecated_store_attr.stderr b/frame/support/test/tests/pallet_ui/deprecated_store_attr.stderr new file mode 100644 index 000000000000..5d2734b4db60 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/deprecated_store_attr.stderr @@ -0,0 +1,9 @@ +error: use of deprecated struct `pallet::_::Store`: + Use of `#[pallet::generate_store(pub(super) trait Store)]` will be removed after July 2023. + Check https://github.com/paritytech/substrate/pull/13535 for more details. + --> tests/pallet_ui/deprecated_store_attr.rs:7:3 + | +7 | #[pallet::generate_store(trait Store)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D deprecated` implied by `-D warnings` diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg.rs b/frame/support/test/tests/pallet_ui/dev_mode_without_arg.rs index f044ae6d7878..2a413eea9b4a 100644 --- a/frame/support/test/tests/pallet_ui/dev_mode_without_arg.rs +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg.rs @@ -1,7 +1,5 @@ #![cfg_attr(not(feature = "std"), no_std)] -pub use pallet::*; - #[frame_support::pallet] pub mod pallet { use frame_support::pallet_prelude::*; diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg.stderr b/frame/support/test/tests/pallet_ui/dev_mode_without_arg.stderr index fac7fd77df9a..1e2011b2a30c 100644 --- a/frame/support/test/tests/pallet_ui/dev_mode_without_arg.stderr +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg.stderr @@ -1,11 +1,7 @@ -error: Invalid pallet::call, requires weight attribute i.e. `#[pallet::weight($expr)]` - --> tests/pallet_ui/dev_mode_without_arg.rs:24:7 +error: A pallet::call requires either a concrete `#[pallet::weight($expr)]` or an + inherited weight from the `#[pallet:call(weight($type))]` attribute, but + none were given. + --> tests/pallet_ui/dev_mode_without_arg.rs:22:7 | -24 | pub fn my_call(_origin: OriginFor) -> DispatchResult { +22 | pub fn my_call(_origin: OriginFor) -> DispatchResult { | ^^ - -error[E0432]: unresolved import `pallet` - --> tests/pallet_ui/dev_mode_without_arg.rs:3:9 - | -3 | pub use pallet::*; - | ^^^^^^ help: a similar path exists: `test_pallet::pallet` diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_call_index.rs b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_call_index.rs new file mode 100644 index 000000000000..1920b6799de2 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_call_index.rs @@ -0,0 +1,31 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::OriginFor; + + // The struct on which we build all of our Pallet logic. + #[pallet::pallet] + pub struct Pallet(_); + + // Your Pallet's configuration trait, representing custom external types and interfaces. + #[pallet::config] + pub trait Config: frame_system::Config {} + + // Your Pallet's callable functions. + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] + pub fn my_call(_origin: OriginFor) -> DispatchResult { + Ok(()) + } + } + + // Your Pallet's internal functions. + impl Pallet {} +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_call_index.stderr b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_call_index.stderr new file mode 100644 index 000000000000..b75edff1ab5f --- /dev/null +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_call_index.stderr @@ -0,0 +1,24 @@ +error: use of deprecated constant `pallet::warnings::ImplicitCallIndex_0::_w`: + It is deprecated to use implicit call indices. + Please instead ensure that all calls have a `pallet::call_index` attribute or put the pallet into `dev` mode. + + For more info see: + + + --> tests/pallet_ui/dev_mode_without_arg_call_index.rs:22:10 + | +22 | pub fn my_call(_origin: OriginFor) -> DispatchResult { + | ^^^^^^^ + | + = note: `-D deprecated` implied by `-D warnings` + +error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: + It is deprecated to use hard-coded constant as call weight. + Please instead benchmark all calls or put the pallet into `dev` mode. + + For more info see: + + --> tests/pallet_ui/dev_mode_without_arg_call_index.rs:21:20 + | +21 | #[pallet::weight(0)] + | ^ diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_default_hasher.rs b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_default_hasher.rs new file mode 100644 index 000000000000..fb1139479566 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_default_hasher.rs @@ -0,0 +1,33 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + + // The struct on which we build all of our Pallet logic. + #[pallet::pallet] + pub struct Pallet(_); + + // Your Pallet's configuration trait, representing custom external types and interfaces. + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::storage] + type MyStorage = StorageValue<_, Vec>; + + #[pallet::storage] + type MyStorageMap = StorageMap<_, _, u32, u64>; + + #[pallet::storage] + type MyStorageDoubleMap = StorageDoubleMap<_, _, u32, _, u64, u64>; + + #[pallet::storage] + type MyCountedStorageMap = CountedStorageMap<_, _, u32, u64>; + + // Your Pallet's internal functions. + impl Pallet {} +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_default_hasher.stderr b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_default_hasher.stderr new file mode 100644 index 000000000000..e0dbc8c953b4 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_default_hasher.stderr @@ -0,0 +1,11 @@ +error: `_` can only be used in dev_mode. Please specify an appropriate hasher. + --> tests/pallet_ui/dev_mode_without_arg_default_hasher.rs:21:47 + | +21 | type MyStorageMap = StorageMap<_, _, u32, u64>; + | ^ + +error[E0432]: unresolved import `pallet` + --> tests/pallet_ui/dev_mode_without_arg_default_hasher.rs:3:9 + | +3 | pub use pallet::*; + | ^^^^^^ help: a similar path exists: `test_pallet::pallet` diff --git a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr index c9de99113508..cf502ac5be47 100644 --- a/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr +++ b/frame/support/test/tests/pallet_ui/dev_mode_without_arg_max_encoded_len.stderr @@ -1,9 +1,10 @@ -error: use of deprecated struct `pallet::warnings::my_call`: - Implicit call indices are deprecated in favour of explicit ones. - Please ensure that all calls have the `pallet::call_index` attribute or that the - `dev-mode` of the pallet is enabled. For more info see: - and - . +error: use of deprecated constant `pallet::warnings::ImplicitCallIndex_0::_w`: + It is deprecated to use implicit call indices. + Please instead ensure that all calls have a `pallet::call_index` attribute or put the pallet into `dev` mode. + + For more info see: + + --> tests/pallet_ui/dev_mode_without_arg_max_encoded_len.rs:25:10 | 25 | pub fn my_call(_origin: OriginFor) -> DispatchResult { @@ -11,6 +12,17 @@ error: use of deprecated struct `pallet::warnings::my_call`: | = note: `-D deprecated` implied by `-D warnings` +error: use of deprecated constant `pallet::warnings::ConstantWeight_0::_w`: + It is deprecated to use hard-coded constant as call weight. + Please instead benchmark all calls or put the pallet into `dev` mode. + + For more info see: + + --> tests/pallet_ui/dev_mode_without_arg_max_encoded_len.rs:24:20 + | +24 | #[pallet::weight(0)] + | ^ + error[E0277]: the trait bound `Vec: MaxEncodedLen` is not satisfied --> tests/pallet_ui/dev_mode_without_arg_max_encoded_len.rs:11:12 | diff --git a/frame/support/test/tests/pallet_ui/duplicate_call_attr.rs b/frame/support/test/tests/pallet_ui/duplicate_call_attr.rs index b8a32a0bd9f6..6d781a19e674 100644 --- a/frame/support/test/tests/pallet_ui/duplicate_call_attr.rs +++ b/frame/support/test/tests/pallet_ui/duplicate_call_attr.rs @@ -8,7 +8,6 @@ mod pallet { pub trait Config: frame_system::Config {} #[pallet::pallet] - #[pallet::generate_store(trait Store)] pub struct Pallet(core::marker::PhantomData); #[pallet::hooks] diff --git a/frame/support/test/tests/pallet_ui/duplicate_call_attr.stderr b/frame/support/test/tests/pallet_ui/duplicate_call_attr.stderr index c2956717bb2b..2b03f4102435 100644 --- a/frame/support/test/tests/pallet_ui/duplicate_call_attr.stderr +++ b/frame/support/test/tests/pallet_ui/duplicate_call_attr.stderr @@ -1,5 +1,5 @@ error: Invalid duplicated attribute - --> $DIR/duplicate_call_attr.rs:23:12 + --> $DIR/duplicate_call_attr.rs:22:12 | -23 | #[pallet::call] +22 | #[pallet::call] | ^^^^ diff --git a/frame/support/test/tests/pallet_ui/duplicate_storage_prefix.rs b/frame/support/test/tests/pallet_ui/duplicate_storage_prefix.rs index 5e99c84050c9..543c15bd0690 100644 --- a/frame/support/test/tests/pallet_ui/duplicate_storage_prefix.rs +++ b/frame/support/test/tests/pallet_ui/duplicate_storage_prefix.rs @@ -6,7 +6,6 @@ mod pallet { pub trait Config: frame_system::Config {} #[pallet::pallet] - #[pallet::generate_store(trait Store)] pub struct Pallet(core::marker::PhantomData); #[pallet::storage] diff --git a/frame/support/test/tests/pallet_ui/duplicate_storage_prefix.stderr b/frame/support/test/tests/pallet_ui/duplicate_storage_prefix.stderr index 716888c9d8b6..75297dc5a7f7 100644 --- a/frame/support/test/tests/pallet_ui/duplicate_storage_prefix.stderr +++ b/frame/support/test/tests/pallet_ui/duplicate_storage_prefix.stderr @@ -1,47 +1,47 @@ error: Duplicate storage prefixes found for `Foo` - --> $DIR/duplicate_storage_prefix.rs:16:29 + --> $DIR/duplicate_storage_prefix.rs:15:29 | -16 | #[pallet::storage_prefix = "Foo"] +15 | #[pallet::storage_prefix = "Foo"] | ^^^^^ error: Duplicate storage prefixes found for `Foo` - --> $DIR/duplicate_storage_prefix.rs:13:7 + --> $DIR/duplicate_storage_prefix.rs:12:7 | -13 | type Foo = StorageValue<_, u8>; +12 | type Foo = StorageValue<_, u8>; | ^^^ error: Duplicate storage prefixes found for `CounterForBar`, used for counter associated to counted storage map - --> $DIR/duplicate_storage_prefix.rs:23:7 + --> $DIR/duplicate_storage_prefix.rs:22:7 | -23 | type Bar = CountedStorageMap<_, Twox64Concat, u16, u16>; +22 | type Bar = CountedStorageMap<_, Twox64Concat, u16, u16>; | ^^^ error: Duplicate storage prefixes found for `CounterForBar` - --> $DIR/duplicate_storage_prefix.rs:20:7 + --> $DIR/duplicate_storage_prefix.rs:19:7 | -20 | type CounterForBar = StorageValue<_, u16>; +19 | type CounterForBar = StorageValue<_, u16>; | ^^^^^^^^^^^^^ error[E0412]: cannot find type `_GeneratedPrefixForStorageFoo` in this scope - --> $DIR/duplicate_storage_prefix.rs:13:7 + --> $DIR/duplicate_storage_prefix.rs:12:7 | -13 | type Foo = StorageValue<_, u8>; +12 | type Foo = StorageValue<_, u8>; | ^^^ not found in this scope error[E0412]: cannot find type `_GeneratedPrefixForStorageNotFoo` in this scope - --> $DIR/duplicate_storage_prefix.rs:17:7 + --> $DIR/duplicate_storage_prefix.rs:16:7 | -17 | type NotFoo = StorageValue<_, u16>; +16 | type NotFoo = StorageValue<_, u16>; | ^^^^^^ not found in this scope error[E0412]: cannot find type `_GeneratedPrefixForStorageCounterForBar` in this scope - --> $DIR/duplicate_storage_prefix.rs:20:7 + --> $DIR/duplicate_storage_prefix.rs:19:7 | -20 | type CounterForBar = StorageValue<_, u16>; +19 | type CounterForBar = StorageValue<_, u16>; | ^^^^^^^^^^^^^ not found in this scope error[E0412]: cannot find type `_GeneratedPrefixForStorageBar` in this scope - --> $DIR/duplicate_storage_prefix.rs:23:7 + --> $DIR/duplicate_storage_prefix.rs:22:7 | -23 | type Bar = CountedStorageMap<_, Twox64Concat, u16, u16>; +22 | type Bar = CountedStorageMap<_, Twox64Concat, u16, u16>; | ^^^ not found in this scope diff --git a/frame/support/test/tests/pallet_ui/duplicate_store_attr.rs b/frame/support/test/tests/pallet_ui/duplicate_store_attr.rs index d675ddefe985..ab318034aca0 100644 --- a/frame/support/test/tests/pallet_ui/duplicate_store_attr.rs +++ b/frame/support/test/tests/pallet_ui/duplicate_store_attr.rs @@ -22,5 +22,4 @@ mod pallet { type Foo = StorageValue<_, u8>; } -fn main() { -} +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/duplicate_store_attr.stderr b/frame/support/test/tests/pallet_ui/duplicate_store_attr.stderr index 232144b8deac..1c13ee17eb75 100644 --- a/frame/support/test/tests/pallet_ui/duplicate_store_attr.stderr +++ b/frame/support/test/tests/pallet_ui/duplicate_store_attr.stderr @@ -1,5 +1,5 @@ error: Unexpected duplicated attribute - --> $DIR/duplicate_store_attr.rs:12:12 + --> tests/pallet_ui/duplicate_store_attr.rs:12:3 | 12 | #[pallet::generate_store(trait Store)] - | ^^^^^^^^^^^^^^ + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/event_wrong_item.stderr b/frame/support/test/tests/pallet_ui/event_wrong_item.stderr index 21eb0ed35e93..0ef150dfd62e 100644 --- a/frame/support/test/tests/pallet_ui/event_wrong_item.stderr +++ b/frame/support/test/tests/pallet_ui/event_wrong_item.stderr @@ -1,4 +1,4 @@ -error: Invalid pallet::event, expected item enum +error: Invalid pallet::event, expected enum item --> $DIR/event_wrong_item.rs:19:2 | 19 | pub struct Foo; diff --git a/frame/support/test/tests/pallet_ui/genesis_default_not_satisfied.rs b/frame/support/test/tests/pallet_ui/genesis_default_not_satisfied.rs index da5e8d0c4da5..a4a0eb832c9c 100644 --- a/frame/support/test/tests/pallet_ui/genesis_default_not_satisfied.rs +++ b/frame/support/test/tests/pallet_ui/genesis_default_not_satisfied.rs @@ -1,6 +1,6 @@ #[frame_support::pallet] mod pallet { - use frame_support::pallet_prelude::{Hooks, GenesisBuild}; + use frame_support::pallet_prelude::{BuildGenesisConfig, Hooks}; use frame_system::pallet_prelude::BlockNumberFor; #[pallet::config] @@ -19,7 +19,7 @@ mod pallet { pub struct GenesisConfig; #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig {} + impl BuildGenesisConfig for GenesisConfig {} } fn main() { diff --git a/frame/support/test/tests/pallet_ui/genesis_default_not_satisfied.stderr b/frame/support/test/tests/pallet_ui/genesis_default_not_satisfied.stderr index 80903928585b..7245333c9842 100644 --- a/frame/support/test/tests/pallet_ui/genesis_default_not_satisfied.stderr +++ b/frame/support/test/tests/pallet_ui/genesis_default_not_satisfied.stderr @@ -1,15 +1,16 @@ error[E0277]: the trait bound `pallet::GenesisConfig: std::default::Default` is not satisfied - --> tests/pallet_ui/genesis_default_not_satisfied.rs:22:18 - | -22 | impl GenesisBuild for GenesisConfig {} - | ^^^^^^^^^^^^^^^ the trait `std::default::Default` is not implemented for `pallet::GenesisConfig` - | -note: required by a bound in `GenesisBuild` - --> $WORKSPACE/frame/support/src/traits/hooks.rs - | - | pub trait GenesisBuild: Default + sp_runtime::traits::MaybeSerializeDeserialize { - | ^^^^^^^ required by this bound in `GenesisBuild` + --> tests/pallet_ui/genesis_default_not_satisfied.rs:22:30 + | +22 | impl BuildGenesisConfig for GenesisConfig {} + | ^^^^^^^^^^^^^ the trait `std::default::Default` is not implemented for `pallet::GenesisConfig` + | +note: required by a bound in `BuildGenesisConfig` + --> $WORKSPACE/frame/support/src/traits/hooks.rs + | + | pub trait BuildGenesisConfig: Default + sp_runtime::traits::MaybeSerializeDeserialize { + | ^^^^^^^ required by this bound in `BuildGenesisConfig` help: consider annotating `pallet::GenesisConfig` with `#[derive(Default)]` | -19 | #[derive(Default)] +19 + #[derive(Default)] +20 | pub struct GenesisConfig; | diff --git a/frame/support/test/tests/pallet_ui/hold_reason_non_enum.rs b/frame/support/test/tests/pallet_ui/hold_reason_non_enum.rs new file mode 100644 index 000000000000..8008c465e61a --- /dev/null +++ b/frame/support/test/tests/pallet_ui/hold_reason_non_enum.rs @@ -0,0 +1,14 @@ +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::composite_enum] + pub struct HoldReason; +} + +fn main() { +} \ No newline at end of file diff --git a/frame/support/test/tests/pallet_ui/hold_reason_non_enum.stderr b/frame/support/test/tests/pallet_ui/hold_reason_non_enum.stderr new file mode 100644 index 000000000000..7d86b8d4f1bd --- /dev/null +++ b/frame/support/test/tests/pallet_ui/hold_reason_non_enum.stderr @@ -0,0 +1,5 @@ +error: Invalid pallet::composite_enum, expected enum item + --> tests/pallet_ui/hold_reason_non_enum.rs:10:2 + | +10 | pub struct HoldReason; + | ^^^ diff --git a/frame/support/test/tests/pallet_ui/hold_reason_not_pub.rs b/frame/support/test/tests/pallet_ui/hold_reason_not_pub.rs new file mode 100644 index 000000000000..626dad741131 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/hold_reason_not_pub.rs @@ -0,0 +1,14 @@ +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::composite_enum] + enum HoldReason {} +} + +fn main() { +} \ No newline at end of file diff --git a/frame/support/test/tests/pallet_ui/hold_reason_not_pub.stderr b/frame/support/test/tests/pallet_ui/hold_reason_not_pub.stderr new file mode 100644 index 000000000000..e8b0c14e967d --- /dev/null +++ b/frame/support/test/tests/pallet_ui/hold_reason_not_pub.stderr @@ -0,0 +1,5 @@ +error: Invalid pallet::composite_enum, `HoldReason` must be public + --> tests/pallet_ui/hold_reason_not_pub.rs:10:5 + | +10 | enum HoldReason {} + | ^^^^ diff --git a/frame/support/test/tests/pallet_ui/hooks_invalid_item.stderr b/frame/support/test/tests/pallet_ui/hooks_invalid_item.stderr index ff52a094d6f8..9c30179bc11e 100644 --- a/frame/support/test/tests/pallet_ui/hooks_invalid_item.stderr +++ b/frame/support/test/tests/pallet_ui/hooks_invalid_item.stderr @@ -1,15 +1,15 @@ error[E0107]: missing generics for trait `Hooks` - --> tests/pallet_ui/hooks_invalid_item.rs:12:18 - | -12 | impl Hooks for Pallet {} - | ^^^^^ expected 1 generic argument - | + --> tests/pallet_ui/hooks_invalid_item.rs:12:18 + | +12 | impl Hooks for Pallet {} + | ^^^^^ expected 1 generic argument + | note: trait defined here, with 1 generic parameter: `BlockNumber` - --> $WORKSPACE/frame/support/src/traits/hooks.rs - | - | pub trait Hooks { - | ^^^^^ ----------- + --> $WORKSPACE/frame/support/src/traits/hooks.rs + | + | pub trait Hooks { + | ^^^^^ ----------- help: add missing generic argument | 12 | impl Hooks for Pallet {} - | ~~~~~~~~~~~~~~~~~~ + | +++++++++++++ diff --git a/frame/support/test/tests/pallet_ui/lock_id_duplicate.rs b/frame/support/test/tests/pallet_ui/lock_id_duplicate.rs new file mode 100644 index 000000000000..70418efc4142 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/lock_id_duplicate.rs @@ -0,0 +1,17 @@ +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::composite_enum] + pub enum LockId {} + + #[pallet::composite_enum] + pub enum LockId {} +} + +fn main() { +} \ No newline at end of file diff --git a/frame/support/test/tests/pallet_ui/lock_id_duplicate.stderr b/frame/support/test/tests/pallet_ui/lock_id_duplicate.stderr new file mode 100644 index 000000000000..1b7097d0a109 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/lock_id_duplicate.stderr @@ -0,0 +1,5 @@ +error: Invalid duplicated `LockId` definition + --> tests/pallet_ui/lock_id_duplicate.rs:13:14 + | +13 | pub enum LockId {} + | ^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/no_default_but_missing_with_default.rs b/frame/support/test/tests/pallet_ui/no_default_but_missing_with_default.rs new file mode 100644 index 000000000000..5ffa13c22243 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/no_default_but_missing_with_default.rs @@ -0,0 +1,23 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + #[pallet::constant] + #[pallet::no_default] + type MyGetParam2: Get; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/no_default_but_missing_with_default.stderr b/frame/support/test/tests/pallet_ui/no_default_but_missing_with_default.stderr new file mode 100644 index 000000000000..aebde115eb80 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/no_default_but_missing_with_default.stderr @@ -0,0 +1,5 @@ +error: `#[pallet:no_default]` can only be used if `#[pallet::config(with_default)]` has been specified + --> tests/pallet_ui/no_default_but_missing_with_default.rs:9:4 + | +9 | #[pallet::no_default] + | ^^^^^^^^^^^^^^^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/non_dev_mode_storage_map_explicit_key_default_hasher.rs b/frame/support/test/tests/pallet_ui/non_dev_mode_storage_map_explicit_key_default_hasher.rs new file mode 100644 index 000000000000..7d8be8ec0017 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/non_dev_mode_storage_map_explicit_key_default_hasher.rs @@ -0,0 +1,33 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + + // The struct on which we build all of our Pallet logic. + #[pallet::pallet] + pub struct Pallet(_); + + // Your Pallet's configuration trait, representing custom external types and interfaces. + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::storage] + type MyStorage = StorageValue<_, Vec>; + + #[pallet::storage] + type MyStorageMap = StorageMap; + + #[pallet::storage] + type MyStorageDoubleMap = StorageDoubleMap; + + #[pallet::storage] + type MyCountedStorageMap = CountedStorageMap; + + // Your Pallet's internal functions. + impl Pallet {} +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/non_dev_mode_storage_map_explicit_key_default_hasher.stderr b/frame/support/test/tests/pallet_ui/non_dev_mode_storage_map_explicit_key_default_hasher.stderr new file mode 100644 index 000000000000..68751470a3e2 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/non_dev_mode_storage_map_explicit_key_default_hasher.stderr @@ -0,0 +1,11 @@ +error: Invalid pallet::storage, cannot find `Hasher` generic, required for `StorageMap`. + --> tests/pallet_ui/non_dev_mode_storage_map_explicit_key_default_hasher.rs:21:43 + | +21 | type MyStorageMap = StorageMap; + | ^ + +error[E0432]: unresolved import `pallet` + --> tests/pallet_ui/non_dev_mode_storage_map_explicit_key_default_hasher.rs:3:9 + | +3 | pub use pallet::*; + | ^^^^^^ help: a similar path exists: `test_pallet::pallet` diff --git a/frame/support/test/tests/pallet_ui/pallet_doc_arg_non_path.rs b/frame/support/test/tests/pallet_ui/pallet_doc_arg_non_path.rs new file mode 100644 index 000000000000..32df5d618365 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pallet_doc_arg_non_path.rs @@ -0,0 +1,16 @@ +#[frame_support::pallet] +// Must receive a string literal pointing to a path +#[pallet_doc(X)] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config + where + ::Nonce: From, + { + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/pallet_doc_arg_non_path.stderr b/frame/support/test/tests/pallet_ui/pallet_doc_arg_non_path.stderr new file mode 100644 index 000000000000..9a1249dd36f3 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pallet_doc_arg_non_path.stderr @@ -0,0 +1,5 @@ +error: The `pallet_doc` received an unsupported argument. Supported format: `pallet_doc("PATH")` + --> tests/pallet_ui/pallet_doc_arg_non_path.rs:3:1 + | +3 | #[pallet_doc(X)] + | ^ diff --git a/frame/support/test/tests/pallet_ui/pallet_doc_empty.rs b/frame/support/test/tests/pallet_ui/pallet_doc_empty.rs new file mode 100644 index 000000000000..6ff01e9fb44b --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pallet_doc_empty.rs @@ -0,0 +1,16 @@ +#[frame_support::pallet] +// Expected one argument for the doc path. +#[pallet_doc] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config + where + ::Nonce: From, + { + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/pallet_doc_empty.stderr b/frame/support/test/tests/pallet_ui/pallet_doc_empty.stderr new file mode 100644 index 000000000000..a220cbe9e999 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pallet_doc_empty.stderr @@ -0,0 +1,5 @@ +error: The `pallet_doc` received an unsupported argument. Supported format: `pallet_doc("PATH")` + --> tests/pallet_ui/pallet_doc_empty.rs:3:1 + | +3 | #[pallet_doc] + | ^ diff --git a/frame/support/test/tests/pallet_ui/pallet_doc_invalid_arg.rs b/frame/support/test/tests/pallet_ui/pallet_doc_invalid_arg.rs new file mode 100644 index 000000000000..c7d3b556a08e --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pallet_doc_invalid_arg.rs @@ -0,0 +1,16 @@ +#[frame_support::pallet] +// Argument expected as list, not named value. +#[pallet_doc = "invalid"] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config + where + ::Nonce: From, + { + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/pallet_doc_invalid_arg.stderr b/frame/support/test/tests/pallet_ui/pallet_doc_invalid_arg.stderr new file mode 100644 index 000000000000..bee7c708507d --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pallet_doc_invalid_arg.stderr @@ -0,0 +1,5 @@ +error: The `pallet_doc` received an unsupported argument. Supported format: `pallet_doc("PATH")` + --> tests/pallet_ui/pallet_doc_invalid_arg.rs:3:1 + | +3 | #[pallet_doc = "invalid"] + | ^ diff --git a/frame/support/test/tests/pallet_ui/pallet_doc_multiple_args.rs b/frame/support/test/tests/pallet_ui/pallet_doc_multiple_args.rs new file mode 100644 index 000000000000..a799879fe444 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pallet_doc_multiple_args.rs @@ -0,0 +1,16 @@ +#[frame_support::pallet] +// Supports only one argument. +#[pallet_doc("A", "B")] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config + where + ::Nonce: From, + { + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/pallet_doc_multiple_args.stderr b/frame/support/test/tests/pallet_ui/pallet_doc_multiple_args.stderr new file mode 100644 index 000000000000..e769555438e1 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pallet_doc_multiple_args.stderr @@ -0,0 +1,5 @@ +error: The `pallet_doc` received an unsupported argument. Supported format: `pallet_doc("PATH")` + --> tests/pallet_ui/pallet_doc_multiple_args.rs:3:1 + | +3 | #[pallet_doc("A", "B")] + | ^ diff --git a/frame/support/test/tests/pallet_ui/pallet_struct_invalid_attr.rs b/frame/support/test/tests/pallet_ui/pallet_struct_invalid_attr.rs new file mode 100644 index 000000000000..ac52e75a5f48 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pallet_struct_invalid_attr.rs @@ -0,0 +1,15 @@ +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + #[pallet::generate_storage_info] // invalid + pub struct Pallet(_); + + #[pallet::call] + impl Pallet {} +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/pallet_struct_invalid_attr.stderr b/frame/support/test/tests/pallet_ui/pallet_struct_invalid_attr.stderr new file mode 100644 index 000000000000..301a73c000f0 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pallet_struct_invalid_attr.stderr @@ -0,0 +1,5 @@ +error: expected one of: `generate_store`, `without_storage_info`, `storage_version` + --> tests/pallet_ui/pallet_struct_invalid_attr.rs:7:12 + | +7 | #[pallet::generate_storage_info] // invalid + | ^^^^^^^^^^^^^^^^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/pass/dev_mode_valid.rs b/frame/support/test/tests/pallet_ui/pass/dev_mode_valid.rs index 97e0d585d0ce..ed779da80a18 100644 --- a/frame/support/test/tests/pallet_ui/pass/dev_mode_valid.rs +++ b/frame/support/test/tests/pallet_ui/pass/dev_mode_valid.rs @@ -1,5 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] +use frame_support::traits::ConstU32; + pub use pallet::*; #[frame_support::pallet(dev_mode)] @@ -19,6 +21,25 @@ pub mod pallet { #[pallet::storage] type MyStorage = StorageValue<_, Vec>; + // The Hasher requirement skipped by `dev_mode`. + #[pallet::storage] + pub type MyStorageMap = StorageMap<_, _, u32, u64>; + + #[pallet::storage] + type MyStorageDoubleMap = StorageDoubleMap<_, _, u32, _, u64, u64>; + + #[pallet::storage] + type MyCountedStorageMap = CountedStorageMap<_, _, u32, u64>; + + #[pallet::storage] + pub type MyStorageMap2 = StorageMap; + + #[pallet::storage] + type MyStorageDoubleMap2 = StorageDoubleMap; + + #[pallet::storage] + type MyCountedStorageMap2 = CountedStorageMap; + // Your Pallet's callable functions. #[pallet::call] impl Pallet { @@ -32,4 +53,65 @@ pub mod pallet { impl Pallet {} } -fn main() {} +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU32<250>; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +pub type Header = sp_runtime::generic::Header; +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + +frame_support::construct_runtime!( + pub struct Runtime + { + // Exclude part `Storage` in order not to check its metadata in tests. + System: frame_system exclude_parts { Pallet, Storage }, + Example: pallet, + } +); + +impl pallet::Config for Runtime {} + +fn main() { + use frame_support::pallet_prelude::*; + use sp_io::{ + hashing::{blake2_128, twox_128}, + TestExternalities, + }; + use storage::unhashed; + + fn blake2_128_concat(d: &[u8]) -> Vec { + let mut v = blake2_128(d).to_vec(); + v.extend_from_slice(d); + v + } + + TestExternalities::default().execute_with(|| { + pallet::MyStorageMap::::insert(1, 2); + let mut k = [twox_128(b"Example"), twox_128(b"MyStorageMap")].concat(); + k.extend(1u32.using_encoded(blake2_128_concat)); + assert_eq!(unhashed::get::(&k), Some(2u64)); + }); +} diff --git a/frame/support/test/tests/pallet_ui/pass/inherited_call_weight.rs b/frame/support/test/tests/pallet_ui/pass/inherited_call_weight.rs new file mode 100644 index 000000000000..355a1c978df0 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pass/inherited_call_weight.rs @@ -0,0 +1,51 @@ +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; + +pub trait WeightInfo { + fn foo() -> Weight; +} + +#[frame_support::pallet] +mod parentheses { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type WeightInfo: crate::WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call(weight(::WeightInfo))] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +#[frame_support::pallet] +mod assign { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type WeightInfo: crate::WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call(weight = ::WeightInfo)] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/pass/inherited_call_weight2.rs b/frame/support/test/tests/pallet_ui/pass/inherited_call_weight2.rs new file mode 100644 index 000000000000..ae70c295d8db --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pass/inherited_call_weight2.rs @@ -0,0 +1,57 @@ +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; + +pub trait WeightInfo { + fn foo() -> Weight; +} + +impl WeightInfo for () { + fn foo() -> Weight { + Weight::zero() + } +} + +#[frame_support::pallet] +mod parentheses { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + // Crazy man just uses `()`, but it still works ;) + #[pallet::call(weight(()))] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +#[frame_support::pallet] +mod assign { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + // Crazy man just uses `()`, but it still works ;) + #[pallet::call(weight = ())] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/pass/inherited_call_weight3.rs b/frame/support/test/tests/pallet_ui/pass/inherited_call_weight3.rs new file mode 100644 index 000000000000..567fd2e5fa03 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pass/inherited_call_weight3.rs @@ -0,0 +1,54 @@ +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; + +// If, for whatever reason, you dont to not use a `WeightInfo` trait - it will still work. +struct Impl; + +impl Impl { + fn foo() -> Weight { + Weight::zero() + } +} + +#[frame_support::pallet] +mod parentheses { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call(weight(crate::Impl))] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +#[frame_support::pallet] +mod assign { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call(weight = crate::Impl)] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/pallet_ui/pass/inherited_call_weight_dev_mode.rs b/frame/support/test/tests/pallet_ui/pass/inherited_call_weight_dev_mode.rs new file mode 100644 index 000000000000..04ce49ee71e9 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pass/inherited_call_weight_dev_mode.rs @@ -0,0 +1,30 @@ +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; + +pub trait WeightInfo { + fn foo() -> Weight; +} + +#[frame_support::pallet(dev_mode)] +mod pallet { + use super::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + type WeightInfo: WeightInfo; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call(weight(::WeightInfo))] + impl Pallet { + #[pallet::call_index(0)] + pub fn foo(_: OriginFor) -> DispatchResult { + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.rs b/frame/support/test/tests/pallet_ui/pass/no_std_genesis_config.rs similarity index 81% rename from frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.rs rename to frame/support/test/tests/pallet_ui/pass/no_std_genesis_config.rs index 2ca967640657..87659a0bab51 100644 --- a/frame/support/test/tests/construct_runtime_ui/no_std_genesis_config.rs +++ b/frame/support/test/tests/pallet_ui/pass/no_std_genesis_config.rs @@ -1,6 +1,6 @@ use frame_support::construct_runtime; -use sp_runtime::{generic, traits::BlakeTwo256}; use sp_core::sr25519; +use sp_runtime::{generic, traits::BlakeTwo256}; pub type Signature = sr25519::Signature; pub type BlockNumber = u32; @@ -13,14 +13,13 @@ impl test_pallet::Config for Runtime {} impl frame_system::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u32; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = sp_runtime::testing::H256; type Hashing = sp_runtime::traits::BlakeTwo256; type AccountId = u64; type Lookup = sp_runtime::traits::IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = frame_support::traits::ConstU32<250>; type BlockWeights = (); @@ -38,13 +37,10 @@ impl frame_system::Config for Runtime { } construct_runtime! { - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic + pub struct Runtime { - System: frame_system::{Pallet, Call, Storage, Config, Event}, - Pallet: test_pallet::{Pallet, Config}, + System: frame_system::{Pallet, Call, Storage, Config, Event}, + Pallet: test_pallet::{Pallet, Config}, } } diff --git a/frame/support/test/tests/pallet_ui/pass/where_clause_missing_hooks.rs b/frame/support/test/tests/pallet_ui/pass/where_clause_missing_hooks.rs new file mode 100644 index 000000000000..15fff372a1dd --- /dev/null +++ b/frame/support/test/tests/pallet_ui/pass/where_clause_missing_hooks.rs @@ -0,0 +1,26 @@ +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config + where + ::Nonce: From, + { + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::call] + impl Pallet where ::Nonce: From {} + + impl Pallet + where + ::Nonce: From, + { + fn foo(x: u128) { + let _index = ::Nonce::from(x); + } + } +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.stderr b/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.stderr index 98265462bbdf..9e63fd03db52 100644 --- a/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.stderr +++ b/frame/support/test/tests/pallet_ui/storage_result_query_missing_generics.stderr @@ -12,4 +12,4 @@ note: enum defined here, with 1 generic parameter: `T` help: add missing generic argument | 17 | type Foo = StorageValue<_, u8, ResultQuery::NonExistentValue>>; - | ~~~~~~~~ + | +++ diff --git a/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.stderr b/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.stderr index caffd846f272..89ddd1599ac9 100644 --- a/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.stderr +++ b/frame/support/test/tests/pallet_ui/storage_result_query_parenthesized_generics.stderr @@ -1,5 +1,5 @@ -error: Invalid pallet::storage, unexpected generic args for ResultQuery, expected angle-bracketed arguments, found `(NonExistentValue)` +error: expected `,` --> tests/pallet_ui/storage_result_query_parenthesized_generics.rs:18:55 | 18 | type Foo = StorageValue<_, u8, ResultQuery(NonExistentValue)>; - | ^^^^^^^^^^^^^^^^^^ + | ^ diff --git a/frame/support/test/tests/pallet_ui/store_trait_leak_private.stderr b/frame/support/test/tests/pallet_ui/store_trait_leak_private.stderr index d8c62faa303e..a8836bc04823 100644 --- a/frame/support/test/tests/pallet_ui/store_trait_leak_private.stderr +++ b/frame/support/test/tests/pallet_ui/store_trait_leak_private.stderr @@ -1,5 +1,15 @@ +error: use of deprecated struct `pallet::_::Store`: + Use of `#[pallet::generate_store(pub(super) trait Store)]` will be removed after July 2023. + Check https://github.com/paritytech/substrate/pull/13535 for more details. + --> tests/pallet_ui/store_trait_leak_private.rs:11:3 + | +11 | #[pallet::generate_store(pub trait Store)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: `-D deprecated` implied by `-D warnings` + error[E0446]: private type `_GeneratedPrefixForStorageFoo` in public interface - --> $DIR/store_trait_leak_private.rs:11:37 + --> tests/pallet_ui/store_trait_leak_private.rs:11:37 | 11 | #[pallet::generate_store(pub trait Store)] | ^^^^^ can't leak private type diff --git a/frame/support/test/tests/pallet_ui/trait_invalid_item.stderr b/frame/support/test/tests/pallet_ui/trait_invalid_item.stderr index 72495d94b307..e3409a819114 100644 --- a/frame/support/test/tests/pallet_ui/trait_invalid_item.stderr +++ b/frame/support/test/tests/pallet_ui/trait_invalid_item.stderr @@ -1,5 +1,5 @@ -error: Invalid pallet::constant in pallet::config, expected type trait item - --> $DIR/trait_invalid_item.rs:9:3 +error: Invalid #[pallet::constant] in #[pallet::config], expected type item + --> tests/pallet_ui/trait_invalid_item.rs:9:3 | 9 | const U: u8 = 3; | ^^^^^ diff --git a/frame/support/test/tests/pallet_ui/trait_item_duplicate_constant_attr.rs b/frame/support/test/tests/pallet_ui/trait_item_duplicate_constant_attr.rs new file mode 100644 index 000000000000..8f3d9f3f3e2f --- /dev/null +++ b/frame/support/test/tests/pallet_ui/trait_item_duplicate_constant_attr.rs @@ -0,0 +1,23 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + #[pallet::constant] + #[pallet::constant] + type MyGetParam2: Get; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/trait_item_duplicate_constant_attr.stderr b/frame/support/test/tests/pallet_ui/trait_item_duplicate_constant_attr.stderr new file mode 100644 index 000000000000..3679b67f07b5 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/trait_item_duplicate_constant_attr.stderr @@ -0,0 +1,5 @@ +error: Duplicate #[pallet::constant] attribute not allowed. + --> tests/pallet_ui/trait_item_duplicate_constant_attr.rs:9:4 + | +9 | #[pallet::constant] + | ^^^^^^^^^^^^^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/trait_item_duplicate_no_default.rs b/frame/support/test/tests/pallet_ui/trait_item_duplicate_no_default.rs new file mode 100644 index 000000000000..d2040ec74dc4 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/trait_item_duplicate_no_default.rs @@ -0,0 +1,24 @@ +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + #[pallet::constant] + #[pallet::no_default] + #[pallet::no_default] + type MyGetParam2: Get; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} +} + +fn main() {} diff --git a/frame/support/test/tests/pallet_ui/trait_item_duplicate_no_default.stderr b/frame/support/test/tests/pallet_ui/trait_item_duplicate_no_default.stderr new file mode 100644 index 000000000000..77a29c394d62 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/trait_item_duplicate_no_default.stderr @@ -0,0 +1,5 @@ +error: Duplicate #[pallet::no_default] attribute not allowed. + --> tests/pallet_ui/trait_item_duplicate_no_default.rs:10:4 + | +10 | #[pallet::no_default] + | ^^^^^^^^^^^^^^^^^^^^ diff --git a/frame/support/test/tests/pallet_ui/type_value_forgotten_where_clause.stderr b/frame/support/test/tests/pallet_ui/type_value_forgotten_where_clause.stderr index 6288dcd534b6..d955960c315b 100644 --- a/frame/support/test/tests/pallet_ui/type_value_forgotten_where_clause.stderr +++ b/frame/support/test/tests/pallet_ui/type_value_forgotten_where_clause.stderr @@ -1,52 +1,52 @@ error[E0277]: the trait bound `::AccountId: From` is not satisfied - --> $DIR/type_value_forgotten_where_clause.rs:24:34 + --> tests/pallet_ui/type_value_forgotten_where_clause.rs:24:34 | 24 | #[pallet::type_value] fn Foo() -> u32 { 3u32 } | ^^^^^^ the trait `From` is not implemented for `::AccountId` | note: required by a bound in `pallet::Config` - --> $DIR/type_value_forgotten_where_clause.rs:8:51 + --> tests/pallet_ui/type_value_forgotten_where_clause.rs:8:51 | 7 | pub trait Config: frame_system::Config - | ------ required by a bound in this + | ------ required by a bound in this trait 8 | where ::AccountId: From - | ^^^^^^^^^ required by this bound in `pallet::Config` + | ^^^^^^^^^ required by this bound in `Config` help: consider further restricting the associated type | 24 | #[pallet::type_value] fn Foo() -> u32 where ::AccountId: From { 3u32 } | +++++++++++++++++++++++++++++++++++++++++++++++++++++++ error[E0277]: the trait bound `::AccountId: From` is not satisfied - --> $DIR/type_value_forgotten_where_clause.rs:24:12 + --> tests/pallet_ui/type_value_forgotten_where_clause.rs:24:12 | 24 | #[pallet::type_value] fn Foo() -> u32 { 3u32 } | ^^^^^^^^^^ the trait `From` is not implemented for `::AccountId` | note: required by a bound in `pallet::Config` - --> $DIR/type_value_forgotten_where_clause.rs:8:51 + --> tests/pallet_ui/type_value_forgotten_where_clause.rs:8:51 | 7 | pub trait Config: frame_system::Config - | ------ required by a bound in this + | ------ required by a bound in this trait 8 | where ::AccountId: From - | ^^^^^^^^^ required by this bound in `pallet::Config` + | ^^^^^^^^^ required by this bound in `Config` help: consider further restricting the associated type | 24 | #[pallet::type_value where ::AccountId: From] fn Foo() -> u32 { 3u32 } | +++++++++++++++++++++++++++++++++++++++++++++++++++++++ error[E0277]: the trait bound `::AccountId: From` is not satisfied - --> $DIR/type_value_forgotten_where_clause.rs:24:12 + --> tests/pallet_ui/type_value_forgotten_where_clause.rs:24:12 | 24 | #[pallet::type_value] fn Foo() -> u32 { 3u32 } | ^^^^^^^^^^ the trait `From` is not implemented for `::AccountId` | note: required by a bound in `pallet::Config` - --> $DIR/type_value_forgotten_where_clause.rs:8:51 + --> tests/pallet_ui/type_value_forgotten_where_clause.rs:8:51 | 7 | pub trait Config: frame_system::Config - | ------ required by a bound in this + | ------ required by a bound in this trait 8 | where ::AccountId: From - | ^^^^^^^^^ required by this bound in `pallet::Config` + | ^^^^^^^^^ required by this bound in `Config` help: consider further restricting the associated type | 24 | #[pallet::type_value] fn Foo() -> u32 where ::AccountId: From { 3u32 } diff --git a/frame/support/test/tests/pallet_ui/weight_argument_has_suffix.stderr b/frame/support/test/tests/pallet_ui/weight_argument_has_suffix.stderr deleted file mode 100644 index c9b2010f88bb..000000000000 --- a/frame/support/test/tests/pallet_ui/weight_argument_has_suffix.stderr +++ /dev/null @@ -1,41 +0,0 @@ -error: invalid suffix `something` for number literal - --> tests/pallet_ui/weight_argument_has_suffix.rs:15:26 - | -15 | #[pallet::weight(10_000something)] - | ^^^^^^^^^^^^^^^ invalid suffix `something` - | - = help: the suffix must be one of the numeric types (`u32`, `isize`, `f32`, etc.) - -error[E0308]: mismatched types - --> tests/pallet_ui/weight_argument_has_suffix.rs:12:12 - | -12 | #[pallet::call] - | ^^^^ - | | - | expected trait `frame_support::dispatch::ClassifyDispatch`, found trait `frame_support::dispatch::WeighData` - | arguments to this function are incorrect - | - = note: expected reference `&dyn frame_support::dispatch::ClassifyDispatch<()>` - found reference `&dyn frame_support::dispatch::WeighData<()>` -note: associated function defined here - --> $WORKSPACE/frame/support/src/dispatch.rs - | - | fn classify_dispatch(&self, target: T) -> DispatchClass; - | ^^^^^^^^^^^^^^^^^ - -error[E0308]: mismatched types - --> tests/pallet_ui/weight_argument_has_suffix.rs:12:12 - | -12 | #[pallet::call] - | ^^^^ - | | - | expected trait `frame_support::dispatch::PaysFee`, found trait `frame_support::dispatch::WeighData` - | arguments to this function are incorrect - | - = note: expected reference `&dyn frame_support::dispatch::PaysFee<()>` - found reference `&dyn frame_support::dispatch::WeighData<()>` -note: associated function defined here - --> $WORKSPACE/frame/support/src/dispatch.rs - | - | fn pays_fee(&self, _target: T) -> Pays; - | ^^^^^^^^ diff --git a/frame/support/test/tests/pallet_with_name_trait_is_valid.rs b/frame/support/test/tests/pallet_with_name_trait_is_valid.rs deleted file mode 100644 index 0066420566fe..000000000000 --- a/frame/support/test/tests/pallet_with_name_trait_is_valid.rs +++ /dev/null @@ -1,158 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -pub trait Trait: frame_system::Config { - type Balance: frame_support::dispatch::Parameter; - /// The overarching event type. - type RuntimeEvent: From> + Into<::RuntimeEvent>; -} - -frame_support::decl_storage! { - trait Store for Module as Example { - Dummy get(fn dummy) config(): Option; - } -} - -frame_support::decl_event!( - pub enum Event - where - B = ::Balance, - { - Dummy(B), - } -); - -frame_support::decl_error!( - pub enum Error for Module { - Dummy, - } -); - -frame_support::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin { - fn deposit_event() = default; - type Error = Error; - const Foo: u32 = u32::MAX; - - #[weight = 0] - fn accumulate_dummy(_origin, _increase_by: T::Balance) { - unimplemented!(); - } - - fn on_initialize(_n: T::BlockNumber) -> frame_support::weights::Weight { - frame_support::weights::Weight::zero() - } - } -} - -impl sp_runtime::traits::ValidateUnsigned for Module { - type Call = Call; - - fn validate_unsigned( - _source: sp_runtime::transaction_validity::TransactionSource, - _call: &Self::Call, - ) -> sp_runtime::transaction_validity::TransactionValidity { - unimplemented!(); - } -} - -pub const INHERENT_IDENTIFIER: frame_support::inherent::InherentIdentifier = *b"12345678"; - -impl frame_support::inherent::ProvideInherent for Module { - type Call = Call; - type Error = frame_support::inherent::MakeFatalError<()>; - const INHERENT_IDENTIFIER: frame_support::inherent::InherentIdentifier = INHERENT_IDENTIFIER; - - fn create_inherent(_data: &frame_support::inherent::InherentData) -> Option { - unimplemented!(); - } - - fn check_inherent( - _: &Self::Call, - _: &frame_support::inherent::InherentData, - ) -> std::result::Result<(), Self::Error> { - unimplemented!(); - } - - fn is_inherent(_call: &Self::Call) -> bool { - unimplemented!(); - } -} - -#[cfg(test)] -mod tests { - use crate as pallet_test; - - use frame_support::traits::ConstU64; - - type SignedExtra = ( - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - ); - type TestBlock = sp_runtime::generic::Block; - type TestHeader = sp_runtime::generic::Header; - type TestUncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic< - ::AccountId, - ::RuntimeCall, - (), - SignedExtra, - >; - - frame_support::construct_runtime!( - pub enum Runtime where - Block = TestBlock, - NodeBlock = TestBlock, - UncheckedExtrinsic = TestUncheckedExtrinsic - { - System: frame_system::{Pallet, Call, Config, Storage, Event}, - PalletTest: pallet_test::{Pallet, Call, Storage, Event, Config, ValidateUnsigned, Inherent}, - } - ); - - impl frame_system::Config for Runtime { - type BaseCallFilter = frame_support::traits::Everything; - type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; - type Hash = sp_core::H256; - type RuntimeCall = RuntimeCall; - type Hashing = sp_runtime::traits::BlakeTwo256; - type AccountId = u64; - type Lookup = sp_runtime::traits::IdentityLookup; - type Header = TestHeader; - type RuntimeEvent = (); - type BlockHashCount = ConstU64<250>; - type DbWeight = (); - type BlockWeights = (); - type BlockLength = (); - type Version = (); - type PalletInfo = PalletInfo; - type AccountData = (); - type OnNewAccount = (); - type OnKilledAccount = (); - type SystemWeightInfo = (); - type SS58Prefix = (); - type OnSetCode = (); - type MaxConsumers = frame_support::traits::ConstU32<16>; - } - - impl pallet_test::Trait for Runtime { - type Balance = u32; - type RuntimeEvent = (); - } -} diff --git a/frame/support/test/tests/runtime_metadata.rs b/frame/support/test/tests/runtime_metadata.rs new file mode 100644 index 000000000000..50bad87d32eb --- /dev/null +++ b/frame/support/test/tests/runtime_metadata.rs @@ -0,0 +1,217 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use frame_support::{ + metadata_ir::{ + RuntimeApiMetadataIR, RuntimeApiMethodMetadataIR, RuntimeApiMethodParamMetadataIR, + }, + traits::ConstU32, +}; +use scale_info::{form::MetaForm, meta_type}; +use sp_runtime::traits::Block as BlockT; + +pub type BlockNumber = u64; +pub type Header = sp_runtime::generic::Header; +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + +impl frame_system::Config for Runtime { + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU32<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +frame_support::construct_runtime!( + pub enum Runtime + { + System: frame_system, + } +); + +sp_api::decl_runtime_apis! { + /// ApiWithCustomVersion trait documentation + /// + /// Documentation on multiline. + pub trait Api { + fn test(data: u64); + /// something_with_block. + fn something_with_block(block: Block) -> Block; + fn function_with_two_args(data: u64, block: Block); + fn same_name(); + fn wild_card(_: u32); + } +} + +sp_api::impl_runtime_apis! { + impl self::Api for Runtime { + fn test(_data: u64) { + unimplemented!() + } + + fn something_with_block(_: Block) -> Block { + unimplemented!() + } + + fn function_with_two_args(_: u64, _: Block) { + unimplemented!() + } + + fn same_name() {} + + fn wild_card(_: u32) {} + } + + impl sp_api::Core for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + fn execute_block(_: Block) { + unimplemented!() + } + fn initialize_block(_: &::Header) { + unimplemented!() + } + } +} + +#[test] +fn runtime_metadata() { + fn maybe_docs(doc: Vec<&'static str>) -> Vec<&'static str> { + if cfg!(feature = "no-metadata-docs") { + vec![] + } else { + doc + } + } + + let expected_runtime_metadata = vec![ + RuntimeApiMetadataIR { + name: "Api", + methods: vec![ + RuntimeApiMethodMetadataIR { + name: "test", + inputs: vec![RuntimeApiMethodParamMetadataIR:: { + name: "data", + ty: meta_type::(), + }], + output: meta_type::<()>(), + docs: vec![], + }, + RuntimeApiMethodMetadataIR { + name: "something_with_block", + inputs: vec![RuntimeApiMethodParamMetadataIR:: { + name: "block", + ty: meta_type::(), + }], + output: meta_type::(), + docs: maybe_docs(vec![" something_with_block."]), + }, + RuntimeApiMethodMetadataIR { + name: "function_with_two_args", + inputs: vec![ + RuntimeApiMethodParamMetadataIR:: { + name: "data", + ty: meta_type::(), + }, + RuntimeApiMethodParamMetadataIR:: { + name: "block", + ty: meta_type::(), + }, + ], + output: meta_type::<()>(), + docs: vec![], + }, + RuntimeApiMethodMetadataIR { + name: "same_name", + inputs: vec![], + output: meta_type::<()>(), + docs: vec![], + }, + RuntimeApiMethodMetadataIR { + name: "wild_card", + inputs: vec![RuntimeApiMethodParamMetadataIR:: { + name: "_", + ty: meta_type::(), + }], + output: meta_type::<()>(), + docs: vec![], + }, + ], + docs: maybe_docs(vec![ + " ApiWithCustomVersion trait documentation", + "", + " Documentation on multiline.", + ]), + }, + RuntimeApiMetadataIR { + name: "Core", + methods: vec![ + RuntimeApiMethodMetadataIR { + name: "version", + inputs: vec![], + output: meta_type::(), + docs: maybe_docs(vec![" Returns the version of the runtime."]), + }, + RuntimeApiMethodMetadataIR { + name: "execute_block", + inputs: vec![RuntimeApiMethodParamMetadataIR:: { + name: "block", + ty: meta_type::(), + }], + output: meta_type::<()>(), + docs: maybe_docs(vec![" Execute the given block."]), + }, + RuntimeApiMethodMetadataIR { + name: "initialize_block", + inputs: vec![RuntimeApiMethodParamMetadataIR:: { + name: "header", + ty: meta_type::<&::Header>(), + }], + output: meta_type::<()>(), + docs: maybe_docs(vec![" Initialize a block with the given header."]), + }, + ], + docs: maybe_docs(vec![ + " The `Core` runtime api that every Substrate runtime needs to implement.", + ]), + }, + ]; + + let rt = Runtime; + let runtime_metadata = (&rt).runtime_metadata(); + pretty_assertions::assert_eq!(runtime_metadata, expected_runtime_metadata); +} diff --git a/frame/support/test/tests/decl_module_ui.rs b/frame/support/test/tests/split_ui.rs similarity index 79% rename from frame/support/test/tests/decl_module_ui.rs rename to frame/support/test/tests/split_ui.rs index 292451335e7e..14f99b8ecdab 100644 --- a/frame/support/test/tests/decl_module_ui.rs +++ b/frame/support/test/tests/split_ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ #[rustversion::attr(not(stable), ignore)] #[cfg(not(feature = "disable-ui-tests"))] #[test] -fn decl_module_ui() { +fn split_ui() { // Only run the ui tests when `RUN_UI_TESTS` is set. if std::env::var("RUN_UI_TESTS").is_err() { return @@ -27,6 +27,10 @@ fn decl_module_ui() { // As trybuild is using `cargo check`, we don't need the real WASM binaries. std::env::set_var("SKIP_WASM_BUILD", "1"); + // Deny all warnings since we emit warnings as part of a Pallet's UI. + std::env::set_var("RUSTFLAGS", "--deny warnings"); + let t = trybuild::TestCases::new(); - t.compile_fail("tests/decl_module_ui/*.rs"); + t.compile_fail("tests/split_ui/*.rs"); + t.pass("tests/split_ui/pass/*.rs"); } diff --git a/frame/support/test/tests/split_ui/import_without_pallet.rs b/frame/support/test/tests/split_ui/import_without_pallet.rs new file mode 100644 index 000000000000..874a92e46109 --- /dev/null +++ b/frame/support/test/tests/split_ui/import_without_pallet.rs @@ -0,0 +1,17 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::pallet_macros::*; + +#[pallet_section] +mod storages { + #[pallet::storage] + pub type MyStorageMap = StorageMap<_, _, u32, u64>; +} + +#[import_section(storages)] +pub mod pallet { + +} + +fn main() { +} diff --git a/frame/support/test/tests/split_ui/import_without_pallet.stderr b/frame/support/test/tests/split_ui/import_without_pallet.stderr new file mode 100644 index 000000000000..0d7b5414b101 --- /dev/null +++ b/frame/support/test/tests/split_ui/import_without_pallet.stderr @@ -0,0 +1,5 @@ +error: `#[import_section]` can only be applied to a valid pallet module + --> tests/split_ui/import_without_pallet.rs:12:9 + | +12 | pub mod pallet { + | ^^^^^^ diff --git a/frame/support/test/tests/split_ui/no_section_found.rs b/frame/support/test/tests/split_ui/no_section_found.rs new file mode 100644 index 000000000000..fe12c6dc51b7 --- /dev/null +++ b/frame/support/test/tests/split_ui/no_section_found.rs @@ -0,0 +1,29 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::pallet_macros::*; + +pub use pallet::*; + +#[import_section(storages_dev)] +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::call] + impl Pallet { + pub fn my_call(_origin: OriginFor) -> DispatchResult { + MyStorageMap::::insert(1, 2); + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/split_ui/no_section_found.stderr b/frame/support/test/tests/split_ui/no_section_found.stderr new file mode 100644 index 000000000000..e0a9322b188e --- /dev/null +++ b/frame/support/test/tests/split_ui/no_section_found.stderr @@ -0,0 +1,13 @@ +error[E0432]: unresolved import `pallet` + --> tests/split_ui/no_section_found.rs:5:9 + | +5 | pub use pallet::*; + | ^^^^^^ help: a similar path exists: `test_pallet::pallet` + +error: cannot find macro `__export_tokens_tt_storages_dev` in this scope + --> tests/split_ui/no_section_found.rs:7:1 + | +7 | #[import_section(storages_dev)] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: this error originates in the macro `frame_support::macro_magic::forward_tokens` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/frame/support/test/tests/split_ui/pass/split_valid.rs b/frame/support/test/tests/split_ui/pass/split_valid.rs new file mode 100644 index 000000000000..8b5839ecd28a --- /dev/null +++ b/frame/support/test/tests/split_ui/pass/split_valid.rs @@ -0,0 +1,40 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::pallet_macros::*; + +pub use pallet::*; + +#[pallet_section] +mod events { + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + SomethingDone, + } +} + +#[import_section(events)] +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::call] + impl Pallet { + pub fn my_call(_origin: OriginFor) -> DispatchResult { + Self::deposit_event(Event::SomethingDone); + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/split_ui/pass/split_valid_disambiguation.rs b/frame/support/test/tests/split_ui/pass/split_valid_disambiguation.rs new file mode 100644 index 000000000000..8d8d50422e9c --- /dev/null +++ b/frame/support/test/tests/split_ui/pass/split_valid_disambiguation.rs @@ -0,0 +1,61 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::pallet_macros::*; + +pub use pallet::*; + +mod first { + use super::*; + + #[pallet_section] + mod section { + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + SomethingDone, + } + } +} + +mod second { + use super::*; + + #[pallet_section(section2)] + mod section { + #[pallet::error] + pub enum Error { + NoneValue, + } + } +} + +#[import_section(first::section)] +#[import_section(second::section2)] +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::call] + impl Pallet { + pub fn my_call(_origin: OriginFor) -> DispatchResult { + Self::deposit_event(Event::SomethingDone); + Ok(()) + } + + pub fn my_call_2(_origin: OriginFor) -> DispatchResult { + return Err(Error::::NoneValue.into()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/split_ui/section_not_imported.rs b/frame/support/test/tests/split_ui/section_not_imported.rs new file mode 100644 index 000000000000..bcabf6625677 --- /dev/null +++ b/frame/support/test/tests/split_ui/section_not_imported.rs @@ -0,0 +1,34 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::pallet_macros::*; + +pub use pallet::*; + +#[pallet_section] +mod storages { + #[pallet::storage] + pub type MyStorageMap = StorageMap<_, _, u32, u64>; +} + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::call] + impl Pallet { + pub fn my_call(_origin: OriginFor) -> DispatchResult { + MyStorageMap::::insert(1, 2); + Ok(()) + } + } +} + +fn main() { +} diff --git a/frame/support/test/tests/split_ui/section_not_imported.stderr b/frame/support/test/tests/split_ui/section_not_imported.stderr new file mode 100644 index 000000000000..41ac2a5f58d2 --- /dev/null +++ b/frame/support/test/tests/split_ui/section_not_imported.stderr @@ -0,0 +1,8 @@ +error[E0433]: failed to resolve: use of undeclared type `MyStorageMap` + --> tests/split_ui/section_not_imported.rs:27:4 + | +27 | MyStorageMap::::insert(1, 2); + | ^^^^^^^^^^^^ + | | + | use of undeclared type `MyStorageMap` + | help: a struct with a similar name exists: `StorageMap` diff --git a/frame/support/test/tests/storage_alias_ui.rs b/frame/support/test/tests/storage_alias_ui.rs index d45d071578da..b82acd8f3be4 100644 --- a/frame/support/test/tests/storage_alias_ui.rs +++ b/frame/support/test/tests/storage_alias_ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.stderr b/frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.stderr index 3aa517ecfa31..3b5e3e9c23cc 100644 --- a/frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.stderr +++ b/frame/support/test/tests/storage_alias_ui/forbid_underscore_as_prefix.stderr @@ -1,4 +1,4 @@ -error: expected one of: `StorageValue`, `StorageMap`, `StorageDoubleMap`, `StorageNMap` +error: expected one of: `StorageValue`, `StorageMap`, `CountedStorageMap`, `StorageDoubleMap`, `StorageNMap` --> tests/storage_alias_ui/forbid_underscore_as_prefix.rs:2:14 | 2 | type Ident = CustomStorage; diff --git a/frame/support/test/tests/storage_layers.rs b/frame/support/test/tests/storage_layers.rs index cff81c0bea2e..b825c85f9564 100644 --- a/frame/support/test/tests/storage_layers.rs +++ b/frame/support/test/tests/storage_layers.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,15 +16,16 @@ // limitations under the License. use frame_support::{ - assert_noop, assert_ok, dispatch::DispatchResult, pallet_prelude::ConstU32, + assert_noop, assert_ok, dispatch::DispatchResult, ensure, pallet_prelude::ConstU32, storage::with_storage_layer, }; use pallet::*; use sp_io::TestExternalities; -#[frame_support::pallet] +#[frame_support::pallet(dev_mode)] pub mod pallet { - use frame_support::{ensure, pallet_prelude::*}; + use super::*; + use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::pallet] @@ -56,48 +57,28 @@ pub mod pallet { } } -pub mod decl_pallet { - pub trait Config: frame_system::Config {} - - frame_support::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin { - #[weight = 0] - pub fn set_value(_origin, value: u32) { - DeclValue::put(value); - frame_support::ensure!(value != 1, "Revert!"); - } - } - } - - frame_support::decl_storage! { - trait Store for Module as StorageTransactions { - pub DeclValue: u32; - } - } -} - -pub type BlockNumber = u64; -pub type Index = u64; -pub type Header = sp_runtime::generic::Header; -pub type Block = sp_runtime::generic::Block; +pub type BlockNumber = u32; +pub type Nonce = u64; +pub type AccountId = u64; +pub type Header = sp_runtime::generic::Header; pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; +pub type Block = sp_runtime::generic::Block; impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = (); type BlockLength = (); - type DbWeight = (); - type BaseCallFilter = frame_support::traits::Everything; type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u32; type RuntimeCall = RuntimeCall; + type Nonce = Nonce; type Hash = sp_runtime::testing::H256; type Hashing = sp_runtime::traits::BlakeTwo256; - type AccountId = u64; + type AccountId = AccountId; type Lookup = sp_runtime::traits::IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU32<250>; + type DbWeight = (); type Version = (); type PalletInfo = PalletInfo; type AccountData = (); @@ -109,19 +90,12 @@ impl frame_system::Config for Runtime { type MaxConsumers = ConstU32<16>; } -impl pallet::Config for Runtime {} - -impl decl_pallet::Config for Runtime {} +impl Config for Runtime {} frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic - { + pub struct Runtime { System: frame_system, MyPallet: pallet, - DeclPallet: decl_pallet::{Call, Storage}, } ); @@ -264,23 +238,3 @@ fn storage_layer_in_pallet_call() { assert_noop!(call2.dispatch(RuntimeOrigin::signed(0)), Error::::Revert); }); } - -#[test] -fn storage_layer_in_decl_pallet_call() { - TestExternalities::default().execute_with(|| { - use frame_support::StorageValue; - use sp_runtime::traits::Dispatchable; - - let call1 = RuntimeCall::DeclPallet(decl_pallet::Call::set_value { value: 2 }); - assert_ok!(call1.dispatch(RuntimeOrigin::signed(0))); - assert_eq!(decl_pallet::DeclValue::get(), 2); - - let call2 = RuntimeCall::DeclPallet(decl_pallet::Call::set_value { value: 1 }); - assert_noop!(call2.dispatch(RuntimeOrigin::signed(0)), "Revert!"); - // Calling the function directly also works with storage layers. - assert_noop!( - decl_pallet::Module::::set_value(RuntimeOrigin::signed(1), 1), - "Revert!" - ); - }); -} diff --git a/frame/support/test/tests/storage_transaction.rs b/frame/support/test/tests/storage_transaction.rs index 6fedd75019e3..c47743308609 100644 --- a/frame/support/test/tests/storage_transaction.rs +++ b/frame/support/test/tests/storage_transaction.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,48 +19,85 @@ #![allow(deprecated)] use frame_support::{ - assert_noop, assert_ok, assert_storage_noop, - dispatch::{DispatchError, DispatchResult}, + assert_noop, assert_ok, assert_storage_noop, derive_impl, + dispatch::DispatchResult, storage::{with_transaction, TransactionOutcome::*}, - transactional, StorageMap, StorageValue, + transactional, }; +use sp_core::{sr25519, ConstU32}; use sp_io::TestExternalities; -use sp_runtime::TransactionOutcome; -use sp_std::result; +use sp_runtime::{ + generic, + traits::{BlakeTwo256, Verify}, + TransactionOutcome, +}; + +pub use self::pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; -pub trait Config: frame_support_test::Config {} + #[pallet::pallet] + #[pallet::generate_store(pub (super) trait Store)] + pub struct Pallet(_); -frame_support::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test { - #[weight = 0] + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::call] + impl Pallet { + #[pallet::weight(0)] #[transactional] - fn value_commits(_origin, v: u32) { - Value::set(v); + pub fn value_commits(_origin: OriginFor, v: u32) -> DispatchResult { + >::set(v); + Ok(()) } - #[weight = 0] + #[pallet::weight(0)] #[transactional] - fn value_rollbacks(_origin, v: u32) -> DispatchResult { - Value::set(v); + pub fn value_rollbacks(_origin: OriginFor, v: u32) -> DispatchResult { + >::set(v); Err(DispatchError::Other("nah")) } } -} -frame_support::decl_storage! { - trait Store for Module as StorageTransactions { - pub Value: u32; - pub Map: map hasher(twox_64_concat) String => u32; - } + #[pallet::storage] + pub type Value = StorageValue<_, u32, ValueQuery>; + + #[pallet::storage] + #[pallet::unbounded] + pub type Map = StorageMap<_, Twox64Concat, String, u32, ValueQuery>; } -struct Runtime; +pub type BlockNumber = u32; +pub type Signature = sr25519::Signature; +pub type AccountId = ::Signer; +pub type Header = generic::Header; +pub type UncheckedExtrinsic = generic::UncheckedExtrinsic; +pub type Block = generic::Block; + +frame_support::construct_runtime!( + pub enum Runtime -impl frame_support_test::Config for Runtime { - type RuntimeOrigin = u32; - type BlockNumber = u32; - type PalletInfo = frame_support_test::PanicPalletInfo; - type DbWeight = (); + { + System: frame_system, + MyPallet: pallet, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type Block = Block; + type BlockHashCount = ConstU32<10>; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type OnSetCode = (); } impl Config for Runtime {} @@ -68,6 +105,9 @@ impl Config for Runtime {} #[test] fn storage_transaction_basic_commit() { TestExternalities::default().execute_with(|| { + type Value = pallet::Value; + type Map = pallet::Map; + assert_eq!(Value::get(), 0); assert!(!Map::contains_key("val0")); @@ -87,6 +127,9 @@ fn storage_transaction_basic_commit() { #[test] fn storage_transaction_basic_rollback() { TestExternalities::default().execute_with(|| { + type Value = pallet::Value; + type Map = pallet::Map; + assert_eq!(Value::get(), 0); assert_eq!(Map::get("val0"), 0); @@ -119,6 +162,9 @@ fn storage_transaction_basic_rollback() { #[test] fn storage_transaction_rollback_then_commit() { TestExternalities::default().execute_with(|| { + type Value = pallet::Value; + type Map = pallet::Map; + Value::set(1); Map::insert("val1", 1); @@ -162,6 +208,9 @@ fn storage_transaction_rollback_then_commit() { #[test] fn storage_transaction_commit_then_rollback() { TestExternalities::default().execute_with(|| { + type Value = pallet::Value; + type Map = pallet::Map; + Value::set(1); Map::insert("val1", 1); @@ -204,19 +253,21 @@ fn storage_transaction_commit_then_rollback() { #[test] fn transactional_annotation() { + type Value = pallet::Value; + fn set_value(v: u32) -> DispatchResult { Value::set(v); Ok(()) } #[transactional] - fn value_commits(v: u32) -> result::Result { + fn value_commits(v: u32) -> Result { set_value(v)?; Ok(v) } #[transactional] - fn value_rollbacks(v: u32) -> result::Result { + fn value_rollbacks(v: u32) -> Result { set_value(v)?; Err("nah")?; Ok(v) @@ -229,14 +280,3 @@ fn transactional_annotation() { assert_noop!(value_rollbacks(3), "nah"); }); } - -#[test] -fn transactional_annotation_in_decl_module() { - TestExternalities::default().execute_with(|| { - let origin = 0; - assert_ok!(>::value_commits(origin, 2)); - assert_eq!(Value::get(), 2); - - assert_noop!(>::value_rollbacks(origin, 3), "nah"); - }); -} diff --git a/frame/support/test/tests/system.rs b/frame/support/test/tests/system.rs deleted file mode 100644 index eff41242917a..000000000000 --- a/frame/support/test/tests/system.rs +++ /dev/null @@ -1,81 +0,0 @@ -// 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. - -use frame_support::{ - codec::{Decode, Encode, EncodeLike}, - traits::Get, - weights::RuntimeDbWeight, -}; - -pub trait Config: 'static + Eq + Clone { - type RuntimeOrigin: Into, Self::RuntimeOrigin>> - + From>; - - type BaseCallFilter: frame_support::traits::Contains; - type BlockNumber: Decode + Encode + EncodeLike + Clone + Default + scale_info::TypeInfo; - type Hash; - type AccountId: Encode + EncodeLike + Decode + scale_info::TypeInfo; - type RuntimeCall; - type RuntimeEvent: From>; - type PalletInfo: frame_support::traits::PalletInfo; - type DbWeight: Get; -} - -frame_support::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=self { - #[weight = 0] - fn noop(_origin) {} - } -} - -impl Module { - pub fn deposit_event(_event: impl Into) {} -} - -frame_support::decl_event!( - pub enum Event - where - BlockNumber = ::BlockNumber, - { - ExtrinsicSuccess, - ExtrinsicFailed, - Ignore(BlockNumber), - } -); - -frame_support::decl_error! { - pub enum Error for Module { - /// Test error documentation - TestError, - /// Error documentation - /// with multiple lines - AnotherError, - // Required by construct_runtime - CallFiltered, - } -} - -pub use frame_support::dispatch::RawOrigin; -pub type Origin = RawOrigin<::AccountId>; - -#[allow(dead_code)] -pub fn ensure_root(o: OuterOrigin) -> Result<(), &'static str> -where - OuterOrigin: Into, OuterOrigin>>, -{ - o.into().map(|_| ()).map_err(|_| "bad origin: expected to be a root origin") -} diff --git a/frame/support/test/tests/versioned_runtime_upgrade.rs b/frame/support/test/tests/versioned_runtime_upgrade.rs new file mode 100644 index 000000000000..93d87df8ca18 --- /dev/null +++ b/frame/support/test/tests/versioned_runtime_upgrade.rs @@ -0,0 +1,230 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Tests for VersionedRuntimeUpgrade + +#![cfg(all(feature = "experimental", feature = "try-runtime"))] + +use frame_support::{ + construct_runtime, derive_impl, + migrations::VersionedRuntimeUpgrade, + parameter_types, + traits::{GetStorageVersion, OnRuntimeUpgrade, StorageVersion}, + weights::constants::RocksDbWeight, +}; +use frame_system::Config; +use sp_core::ConstU64; +use sp_runtime::BuildStorage; + +type Block = frame_system::mocking::MockBlock; + +#[frame_support::pallet] +mod dummy_pallet { + use frame_support::pallet_prelude::*; + + const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); + + #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::storage] + pub type SomeStorage = StorageValue<_, u32, ValueQuery>; + + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + #[serde(skip)] + _config: sp_std::marker::PhantomData, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) {} + } +} + +impl dummy_pallet::Config for Test {} + +construct_runtime!( + pub enum Test + { + System: frame_system::{Pallet, Call, Config, Storage, Event} = 0, + DummyPallet: dummy_pallet::{Pallet, Config, Storage} = 1, + } +); + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type Block = Block; + type BlockHashCount = ConstU64<10>; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type RuntimeEvent = RuntimeEvent; + type PalletInfo = PalletInfo; + type OnSetCode = (); +} + +pub(crate) fn new_test_ext() -> sp_io::TestExternalities { + let storage = frame_system::GenesisConfig::::default().build_storage().unwrap(); + let mut ext: sp_io::TestExternalities = sp_io::TestExternalities::from(storage); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +/// A dummy migration for testing the `VersionedRuntimeUpgrade` trait. +/// Sets SomeStorage to S. +struct SomeUnversionedMigration(sp_std::marker::PhantomData); + +parameter_types! { + const UpgradeReads: u64 = 4; + const UpgradeWrites: u64 = 2; + const PreUpgradeReturnBytes: [u8; 4] = [0, 1, 2, 3]; + static PreUpgradeCalled: bool = false; + static PostUpgradeCalled: bool = false; + static PostUpgradeCalledWith: Vec = Vec::new(); +} + +/// Implement `OnRuntimeUpgrade` for `SomeUnversionedMigration`. +/// It sets SomeStorage to S, and returns a weight derived from UpgradeReads and UpgradeWrites. +impl OnRuntimeUpgrade for SomeUnversionedMigration { + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + PreUpgradeCalled::set(true); + Ok(PreUpgradeReturnBytes::get().to_vec()) + } + + fn on_runtime_upgrade() -> frame_support::weights::Weight { + dummy_pallet::SomeStorage::::put(S); + RocksDbWeight::get().reads_writes(UpgradeReads::get(), UpgradeWrites::get()) + } + + fn post_upgrade(state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + PostUpgradeCalled::set(true); + PostUpgradeCalledWith::set(state); + Ok(()) + } +} + +type VersionedMigrationV0ToV1 = + VersionedRuntimeUpgrade<0, 1, SomeUnversionedMigration, DummyPallet, RocksDbWeight>; + +type VersionedMigrationV1ToV2 = + VersionedRuntimeUpgrade<1, 2, SomeUnversionedMigration, DummyPallet, RocksDbWeight>; + +type VersionedMigrationV2ToV4 = + VersionedRuntimeUpgrade<2, 4, SomeUnversionedMigration, DummyPallet, RocksDbWeight>; + +#[test] +fn successful_upgrade_path() { + new_test_ext().execute_with(|| { + // on-chain storage version and value in storage start at zero + assert_eq!(DummyPallet::on_chain_storage_version(), StorageVersion::new(0)); + assert_eq!(dummy_pallet::SomeStorage::::get(), 0); + + // Execute the migration from version 0 to 1 and verify it was successful + VersionedMigrationV0ToV1::on_runtime_upgrade(); + assert_eq!(DummyPallet::on_chain_storage_version(), StorageVersion::new(1)); + assert_eq!(dummy_pallet::SomeStorage::::get(), 1); + + // Execute the migration from version 1 to 2 and verify it was successful + VersionedMigrationV1ToV2::on_runtime_upgrade(); + assert_eq!(DummyPallet::on_chain_storage_version(), StorageVersion::new(2)); + assert_eq!(dummy_pallet::SomeStorage::::get(), 2); + + // Execute the migration from version 2 to 4 and verify it was successful + VersionedMigrationV2ToV4::on_runtime_upgrade(); + assert_eq!(DummyPallet::on_chain_storage_version(), StorageVersion::new(4)); + assert_eq!(dummy_pallet::SomeStorage::::get(), 4); + }); +} + +#[test] +fn future_version_upgrade_is_ignored() { + new_test_ext().execute_with(|| { + // Executing V1 to V2 on V0 should be a noop + assert_eq!(DummyPallet::on_chain_storage_version(), StorageVersion::new(0)); + assert_eq!(dummy_pallet::SomeStorage::::get(), 0); + VersionedMigrationV1ToV2::on_runtime_upgrade(); + assert_eq!(DummyPallet::on_chain_storage_version(), StorageVersion::new(0)); + assert_eq!(dummy_pallet::SomeStorage::::get(), 0); + }); +} + +#[test] +fn past_version_upgrade_is_ignored() { + new_test_ext().execute_with(|| { + // Upgrade to V2 + VersionedMigrationV0ToV1::on_runtime_upgrade(); + VersionedMigrationV1ToV2::on_runtime_upgrade(); + assert_eq!(DummyPallet::on_chain_storage_version(), StorageVersion::new(2)); + assert_eq!(dummy_pallet::SomeStorage::::get(), 2); + + // Now, V0 to V1 and V1 to V2 should both be noops + dummy_pallet::SomeStorage::::put(1000); + VersionedMigrationV0ToV1::on_runtime_upgrade(); + assert_eq!(DummyPallet::on_chain_storage_version(), StorageVersion::new(2)); + assert_eq!(dummy_pallet::SomeStorage::::get(), 1000); + VersionedMigrationV1ToV2::on_runtime_upgrade(); + assert_eq!(DummyPallet::on_chain_storage_version(), StorageVersion::new(2)); + assert_eq!(dummy_pallet::SomeStorage::::get(), 1000); + }); +} + +#[test] +fn weights_are_returned_correctly() { + new_test_ext().execute_with(|| { + // Successful upgrade requires 1 additional read and write + let weight = VersionedMigrationV0ToV1::on_runtime_upgrade(); + assert_eq!( + weight, + RocksDbWeight::get().reads_writes(UpgradeReads::get() + 1, UpgradeWrites::get() + 1) + ); + + // Noop upgrade requires only 1 read + let weight = VersionedMigrationV0ToV1::on_runtime_upgrade(); + assert_eq!(weight, RocksDbWeight::get().reads(1)); + }); +} + +#[test] +fn pre_and_post_checks_behave_correctly() { + new_test_ext().execute_with(|| { + // Check initial state + assert_eq!(PreUpgradeCalled::get(), false); + assert_eq!(PostUpgradeCalled::get(), false); + assert_eq!(PostUpgradeCalledWith::get(), Vec::::new()); + + // Check pre/post hooks are called correctly when upgrade occurs. + VersionedMigrationV0ToV1::try_on_runtime_upgrade(true).unwrap(); + assert_eq!(PreUpgradeCalled::get(), true); + assert_eq!(PostUpgradeCalled::get(), true); + assert_eq!(PostUpgradeCalledWith::get(), PreUpgradeReturnBytes::get().to_vec()); + + // Reset hook tracking state. + PreUpgradeCalled::set(false); + PostUpgradeCalled::set(false); + + // Check pre/post hooks are not called when an upgrade is skipped. + VersionedMigrationV0ToV1::try_on_runtime_upgrade(true).unwrap(); + assert_eq!(PreUpgradeCalled::get(), false); + assert_eq!(PostUpgradeCalled::get(), false); + }) +} diff --git a/frame/system/Cargo.toml b/frame/system/Cargo.toml index 7f573a9cbd57..b0a2adb0c834 100644 --- a/frame/system/Cargo.toml +++ b/frame/system/Cargo.toml @@ -13,21 +13,22 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +cfg-if = "1.0" +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", features = ["derive"], optional = true } +scale-info = { version = "2.5.0", default-features = false, features = ["derive", "serde"] } +serde = { version = "1.0.163", default-features = false, features = ["derive", "alloc"] } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } -sp-version = { version = "5.0.0", default-features = false, path = "../../primitives/version" } -sp-weights = { version = "4.0.0", default-features = false, path = "../../primitives/weights" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core", features = ["serde"] } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime", features = ["serde"] } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-version = { version = "22.0.0", default-features = false, path = "../../primitives/version", features = ["serde"] } +sp-weights = { version = "20.0.0", default-features = false, path = "../../primitives/weights", features = ["serde"] } [dev-dependencies] -criterion = "0.3.3" -sp-externalities = { version = "0.13.0", path = "../../primitives/externalities" } +criterion = "0.4.0" +sp-externalities = { version = "0.19.0", path = "../../primitives/externalities" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } [features] @@ -37,7 +38,7 @@ std = [ "frame-support/std", "log/std", "scale-info/std", - "serde", + "serde/std", "sp-core/std", "sp-io/std", "sp-runtime/std", diff --git a/frame/system/README.md b/frame/system/README.md index c22b41e42d79..30b2ea73720c 100644 --- a/frame/system/README.md +++ b/frame/system/README.md @@ -33,7 +33,7 @@ The System module defines the following extensions: - [`CheckWeight`]: Checks the weight and length of the block and ensure that it does not exceed the limits. - [`CheckNonce`]: Checks the nonce of the transaction. Contains a single payload of type - `T::Index`. + `T::Nonce`. - [`CheckEra`]: Checks the era of the transaction. Contains a single payload of type `Era`. - [`CheckGenesis`]: Checks the provided genesis hash of the transaction. Must be a part of the signed payload of the transaction. diff --git a/frame/system/benches/bench.rs b/frame/system/benches/bench.rs index f2acc319d3f5..da8bb10fd4e4 100644 --- a/frame/system/benches/bench.rs +++ b/frame/system/benches/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,17 +19,14 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use frame_support::traits::{ConstU32, ConstU64}; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, - Perbill, + BuildStorage, Perbill, }; - #[frame_support::pallet] mod module { use frame_support::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::config] @@ -44,16 +41,12 @@ mod module { } } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub struct Runtime { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Module: module::{Pallet, Event}, } ); @@ -70,14 +63,13 @@ impl frame_system::Config for Runtime { type BlockLength = BlockLength; type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -96,8 +88,8 @@ impl module::Config for Runtime { } fn new_test_ext() -> sp_io::TestExternalities { - frame_system::GenesisConfig::default() - .build_storage::() + frame_system::GenesisConfig::::default() + .build_storage() .unwrap() .into() } diff --git a/frame/system/benchmarking/Cargo.toml b/frame/system/benchmarking/Cargo.toml index 8f0009725460..27a7d91e3738 100644 --- a/frame/system/benchmarking/Cargo.toml +++ b/frame/system/benchmarking/Cargo.toml @@ -13,17 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } -sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } [dev-dependencies] -sp-io = { version = "7.0.0", path = "../../../primitives/io" } +sp-io = { version = "23.0.0", path = "../../../primitives/io" } +sp-externalities = { version = "0.19.0", path = "../../../primitives/externalities" } +sp-version = { version = "22.0.0", path = "../../../primitives/version" } [features] default = ["std"] diff --git a/frame/system/benchmarking/res/README.md b/frame/system/benchmarking/res/README.md new file mode 100644 index 000000000000..43bb2b5c283e --- /dev/null +++ b/frame/system/benchmarking/res/README.md @@ -0,0 +1,5 @@ +These runtimes are used for benchmarking the `set_code` intrinsic. + +**Don't use them in production environments!** + +To update the just copy the new runtime from `target/release/wbuild/kitchensink-runtime/kitchensink_runtime.compact.compressed.wasm` to here. diff --git a/frame/system/benchmarking/res/kitchensink_runtime.compact.compressed.wasm b/frame/system/benchmarking/res/kitchensink_runtime.compact.compressed.wasm new file mode 100644 index 000000000000..a0d2a4bb04b9 Binary files /dev/null and b/frame/system/benchmarking/res/kitchensink_runtime.compact.compressed.wasm differ diff --git a/frame/system/benchmarking/src/lib.rs b/frame/system/benchmarking/src/lib.rs index 0f7603fe1dd9..d85b631af018 100644 --- a/frame/system/benchmarking/src/lib.rs +++ b/frame/system/benchmarking/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +18,13 @@ // Benchmarks for Utility Pallet #![cfg_attr(not(feature = "std"), no_std)] +#![cfg(feature = "runtime-benchmarks")] use codec::Encode; -use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_benchmarking::{ + v1::{benchmarks, whitelisted_caller}, + BenchmarkError, +}; use frame_support::{dispatch::DispatchClass, storage, traits::Get}; use frame_system::{Call, Pallet as System, RawOrigin}; use sp_core::storage::well_known_keys; @@ -30,7 +34,26 @@ use sp_std::{prelude::*, vec}; mod mock; pub struct Pallet(System); -pub trait Config: frame_system::Config {} +pub trait Config: frame_system::Config { + /// Adds ability to the Runtime to test against their sample code. + /// + /// Default is `../res/kitchensink_runtime.compact.compressed.wasm`. + fn prepare_set_code_data() -> Vec { + include_bytes!("../res/kitchensink_runtime.compact.compressed.wasm").to_vec() + } + + /// Adds ability to the Runtime to prepare/initialize before running benchmark `set_code`. + fn setup_set_code_requirements(_code: &Vec) -> Result<(), BenchmarkError> { + Ok(()) + } + + /// Adds ability to the Runtime to do custom validation after benchmark. + /// + /// Default is checking for `CodeUpdated` event . + fn verify_set_code() { + System::::assert_last_event(frame_system::Event::::CodeUpdated.into()); + } +} benchmarks! { remark { @@ -48,14 +71,19 @@ benchmarks! { set_heap_pages { }: _(RawOrigin::Root, Default::default()) - // `set_code` was not benchmarked because it is pretty hard to come up with a real - // Wasm runtime to test the upgrade with. But this is okay because we will make - // `set_code` take a full block anyway. + set_code { + let runtime_blob = T::prepare_set_code_data(); + T::setup_set_code_requirements(&runtime_blob)?; + }: _(RawOrigin::Root, runtime_blob) + verify { + T::verify_set_code() + } #[extra] set_code_without_checks { // Assume Wasm ~4MB let code = vec![1; 4_000_000 as usize]; + T::setup_set_code_requirements(&code)?; }: _(RawOrigin::Root, code) verify { let current_code = storage::unhashed::get_raw(well_known_keys::CODE).ok_or("Code not stored.")?; diff --git a/frame/system/benchmarking/src/mock.rs b/frame/system/benchmarking/src/mock.rs index a7f28ca30fe8..4e6b1221da35 100644 --- a/frame/system/benchmarking/src/mock.rs +++ b/frame/system/benchmarking/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,22 +19,18 @@ #![cfg(test)] -use sp_runtime::traits::IdentityLookup; +use codec::Encode; +use sp_runtime::{traits::IdentityLookup, BuildStorage}; type AccountId = u64; -type AccountIndex = u32; -type BlockNumber = u64; +type Nonce = u32; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, } ); @@ -44,14 +40,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = AccountIndex; - type BlockNumber = BlockNumber; + type Nonce = Nonce; type RuntimeCall = RuntimeCall; type Hash = sp_core::H256; type Hashing = ::sp_runtime::traits::BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = sp_runtime::testing::Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = (); type Version = (); @@ -67,7 +62,29 @@ impl frame_system::Config for Test { impl crate::Config for Test {} +struct MockedReadRuntimeVersion(Vec); + +impl sp_core::traits::ReadRuntimeVersion for MockedReadRuntimeVersion { + fn read_runtime_version( + &self, + _wasm_code: &[u8], + _ext: &mut dyn sp_externalities::Externalities, + ) -> Result, String> { + Ok(self.0.clone()) + } +} + pub fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); - sp_io::TestExternalities::new(t) + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + + let version = sp_version::RuntimeVersion { + spec_name: "spec_name".into(), + spec_version: 123, + impl_version: 456, + ..Default::default() + }; + let read_runtime_version = MockedReadRuntimeVersion(version.encode()); + let mut ext = sp_io::TestExternalities::new(t); + ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(read_runtime_version)); + ext } diff --git a/frame/system/rpc/runtime-api/Cargo.toml b/frame/system/rpc/runtime-api/Cargo.toml index cedb4e35be0b..aef2cfc6e25f 100644 --- a/frame/system/rpc/runtime-api/Cargo.toml +++ b/frame/system/rpc/runtime-api/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/api" } [features] diff --git a/frame/system/rpc/runtime-api/src/lib.rs b/frame/system/rpc/runtime-api/src/lib.rs index 6e01bdae2d15..f59988d818f0 100644 --- a/frame/system/rpc/runtime-api/src/lib.rs +++ b/frame/system/rpc/runtime-api/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,12 +24,12 @@ #![cfg_attr(not(feature = "std"), no_std)] sp_api::decl_runtime_apis! { - /// The API to query account nonce (aka transaction index). - pub trait AccountNonceApi where + /// The API to query account nonce. + pub trait AccountNonceApi where AccountId: codec::Codec, - Index: codec::Codec, + Nonce: codec::Codec, { /// Get current account nonce of given `AccountId`. - fn account_nonce(account: AccountId) -> Index; + fn account_nonce(account: AccountId) -> Nonce; } } diff --git a/frame/system/src/extensions/check_genesis.rs b/frame/system/src/extensions/check_genesis.rs index f5811f306cfe..76a711a823e7 100644 --- a/frame/system/src/extensions/check_genesis.rs +++ b/frame/system/src/extensions/check_genesis.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{Config, Pallet}; +use crate::{pallet_prelude::BlockNumberFor, Config, Pallet}; use codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime::{ @@ -60,7 +60,7 @@ impl SignedExtension for CheckGenesis { const IDENTIFIER: &'static str = "CheckGenesis"; fn additional_signed(&self) -> Result { - Ok(>::block_hash(T::BlockNumber::zero())) + Ok(>::block_hash(BlockNumberFor::::zero())) } fn pre_dispatch( diff --git a/frame/system/src/extensions/check_mortality.rs b/frame/system/src/extensions/check_mortality.rs index 635ab4ef1d9a..148dfd4aad47 100644 --- a/frame/system/src/extensions/check_mortality.rs +++ b/frame/system/src/extensions/check_mortality.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{BlockHash, Config, Pallet}; +use crate::{pallet_prelude::BlockNumberFor, BlockHash, Config, Pallet}; use codec::{Decode, Encode}; use scale_info::TypeInfo; use sp_runtime::{ @@ -78,7 +78,7 @@ impl SignedExtension for CheckMortality { fn additional_signed(&self) -> Result { let current_u64 = >::block_number().saturated_into::(); - let n = self.0.birth(current_u64).saturated_into::(); + let n = self.0.birth(current_u64).saturated_into::>(); if !>::contains_key(n) { Err(InvalidTransaction::AncientBirthBlock.into()) } else { @@ -130,7 +130,7 @@ mod tests { fn signed_ext_check_era_should_change_longevity() { new_test_ext().execute_with(|| { let normal = DispatchInfo { - weight: Weight::from_ref_time(100), + weight: Weight::from_parts(100, 0), class: DispatchClass::Normal, pays_fee: Pays::Yes, }; diff --git a/frame/system/src/extensions/check_non_zero_sender.rs b/frame/system/src/extensions/check_non_zero_sender.rs index 036f70c2fdd4..92eed60fc66b 100644 --- a/frame/system/src/extensions/check_non_zero_sender.rs +++ b/frame/system/src/extensions/check_non_zero_sender.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ use crate::Config; use codec::{Decode, Encode}; -use frame_support::dispatch::DispatchInfo; +use frame_support::{dispatch::DispatchInfo, DefaultNoBound}; use scale_info::TypeInfo; use sp_runtime::{ traits::{DispatchInfoOf, Dispatchable, SignedExtension}, @@ -28,9 +28,9 @@ use sp_runtime::{ use sp_std::{marker::PhantomData, prelude::*}; /// Check to ensure that the sender is not the zero address. -#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] +#[derive(Encode, Decode, DefaultNoBound, Clone, Eq, PartialEq, TypeInfo)] #[scale_info(skip_type_params(T))] -pub struct CheckNonZeroSender(PhantomData); +pub struct CheckNonZeroSender(PhantomData); impl sp_std::fmt::Debug for CheckNonZeroSender { #[cfg(feature = "std")] diff --git a/frame/system/src/extensions/check_nonce.rs b/frame/system/src/extensions/check_nonce.rs index 1616a2d8a119..2939fd6534c0 100644 --- a/frame/system/src/extensions/check_nonce.rs +++ b/frame/system/src/extensions/check_nonce.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,11 +37,11 @@ use sp_std::vec; /// some kind of priority upon validating transactions. #[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] #[scale_info(skip_type_params(T))] -pub struct CheckNonce(#[codec(compact)] pub T::Index); +pub struct CheckNonce(#[codec(compact)] pub T::Nonce); impl CheckNonce { /// utility constructor. Used only in client/factory code. - pub fn from(nonce: T::Index) -> Self { + pub fn from(nonce: T::Nonce) -> Self { Self(nonce) } } @@ -88,7 +88,7 @@ where } .into()) } - account.nonce += T::Index::one(); + account.nonce += T::Nonce::one(); crate::Account::::insert(who, account); Ok(()) } diff --git a/frame/system/src/extensions/check_spec_version.rs b/frame/system/src/extensions/check_spec_version.rs index ef5f40402692..24d5ef9cafb1 100644 --- a/frame/system/src/extensions/check_spec_version.rs +++ b/frame/system/src/extensions/check_spec_version.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/system/src/extensions/check_tx_version.rs b/frame/system/src/extensions/check_tx_version.rs index be0b8fe2354a..3f9d6a1903fe 100644 --- a/frame/system/src/extensions/check_tx_version.rs +++ b/frame/system/src/extensions/check_tx_version.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/system/src/extensions/check_weight.rs b/frame/system/src/extensions/check_weight.rs index 590d05474f91..1030c8daf7b0 100644 --- a/frame/system/src/extensions/check_weight.rs +++ b/frame/system/src/extensions/check_weight.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -308,7 +308,7 @@ mod tests { new_test_ext().execute_with(|| { let max = DispatchInfo { weight: block_weights().get(DispatchClass::Normal).max_extrinsic.unwrap() + - Weight::from_ref_time(1), + Weight::from_parts(1, 0), class: DispatchClass::Normal, ..Default::default() }; @@ -334,7 +334,7 @@ mod tests { let okay = DispatchInfo { weight, class: DispatchClass::Operational, ..Default::default() }; let max = DispatchInfo { - weight: weight + Weight::from_ref_time(1), + weight: weight + Weight::from_parts(1, 0), class: DispatchClass::Operational, ..Default::default() }; @@ -366,9 +366,9 @@ mod tests { // So normal extrinsic can be 758 weight (-5 for base extrinsic weight) // And Operational can be 246 to produce a full block (-10 for base) let max_normal = - DispatchInfo { weight: Weight::from_ref_time(753), ..Default::default() }; + DispatchInfo { weight: Weight::from_parts(753, 0), ..Default::default() }; let rest_operational = DispatchInfo { - weight: Weight::from_ref_time(246), + weight: Weight::from_parts(246, 0), class: DispatchClass::Operational, ..Default::default() }; @@ -376,9 +376,9 @@ mod tests { let len = 0_usize; assert_ok!(CheckWeight::::do_pre_dispatch(&max_normal, len)); - assert_eq!(System::block_weight().total(), Weight::from_ref_time(768)); + assert_eq!(System::block_weight().total(), Weight::from_parts(768, 0)); assert_ok!(CheckWeight::::do_pre_dispatch(&rest_operational, len)); - assert_eq!(block_weight_limit(), Weight::from_ref_time(1024).set_proof_size(u64::MAX)); + assert_eq!(block_weight_limit(), Weight::from_parts(1024, u64::MAX)); assert_eq!(System::block_weight().total(), block_weight_limit().set_proof_size(0)); // Checking single extrinsic should not take current block weight into account. assert_eq!(CheckWeight::::check_extrinsic_weight(&rest_operational), Ok(())); @@ -390,9 +390,9 @@ mod tests { new_test_ext().execute_with(|| { // We switch the order of `full_block_with_normal_and_operational` let max_normal = - DispatchInfo { weight: Weight::from_ref_time(753), ..Default::default() }; + DispatchInfo { weight: Weight::from_parts(753, 0), ..Default::default() }; let rest_operational = DispatchInfo { - weight: Weight::from_ref_time(246), + weight: Weight::from_parts(246, 0), class: DispatchClass::Operational, ..Default::default() }; @@ -401,9 +401,9 @@ mod tests { assert_ok!(CheckWeight::::do_pre_dispatch(&rest_operational, len)); // Extra 20 here from block execution + base extrinsic weight - assert_eq!(System::block_weight().total(), Weight::from_ref_time(266)); + assert_eq!(System::block_weight().total(), Weight::from_parts(266, 0)); assert_ok!(CheckWeight::::do_pre_dispatch(&max_normal, len)); - assert_eq!(block_weight_limit(), Weight::from_ref_time(1024).set_proof_size(u64::MAX)); + assert_eq!(block_weight_limit(), Weight::from_parts(1024, u64::MAX)); assert_eq!(System::block_weight().total(), block_weight_limit().set_proof_size(0)); }); } @@ -414,12 +414,12 @@ mod tests { // An on_initialize takes up the whole block! (Every time!) System::register_extra_weight_unchecked(Weight::MAX, DispatchClass::Mandatory); let dispatch_normal = DispatchInfo { - weight: Weight::from_ref_time(251), + weight: Weight::from_parts(251, 0), class: DispatchClass::Normal, ..Default::default() }; let dispatch_operational = DispatchInfo { - weight: Weight::from_ref_time(246), + weight: Weight::from_parts(246, 0), class: DispatchClass::Operational, ..Default::default() }; @@ -445,9 +445,9 @@ mod tests { #[test] fn signed_ext_check_weight_works_operational_tx() { new_test_ext().execute_with(|| { - let normal = DispatchInfo { weight: Weight::from_ref_time(100), ..Default::default() }; + let normal = DispatchInfo { weight: Weight::from_parts(100, 0), ..Default::default() }; let op = DispatchInfo { - weight: Weight::from_ref_time(100), + weight: Weight::from_parts(100, 0), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -513,12 +513,12 @@ mod tests { fn signed_ext_check_weight_works_normal_tx() { new_test_ext().execute_with(|| { let normal_limit = normal_weight_limit(); - let small = DispatchInfo { weight: Weight::from_ref_time(100), ..Default::default() }; + let small = DispatchInfo { weight: Weight::from_parts(100, 0), ..Default::default() }; let base_extrinsic = block_weights().get(DispatchClass::Normal).base_extrinsic; let medium = DispatchInfo { weight: normal_limit - base_extrinsic, ..Default::default() }; let big = DispatchInfo { - weight: normal_limit - base_extrinsic + Weight::from_ref_time(1), + weight: normal_limit - base_extrinsic + Weight::from_parts(1, 0), ..Default::default() }; let len = 0_usize; @@ -537,7 +537,7 @@ mod tests { reset_check_weight(&small, false, Weight::zero()); reset_check_weight(&medium, false, Weight::zero()); - reset_check_weight(&big, true, Weight::from_ref_time(1)); + reset_check_weight(&big, true, Weight::from_parts(1, 0)); }) } @@ -545,9 +545,9 @@ mod tests { fn signed_ext_check_weight_refund_works() { new_test_ext().execute_with(|| { // This is half of the max block weight - let info = DispatchInfo { weight: Weight::from_ref_time(512), ..Default::default() }; + let info = DispatchInfo { weight: Weight::from_parts(512, 0), ..Default::default() }; let post_info = PostDispatchInfo { - actual_weight: Some(Weight::from_ref_time(128)), + actual_weight: Some(Weight::from_parts(128, 0)), pays_fee: Default::default(), }; let len = 0_usize; @@ -557,13 +557,13 @@ mod tests { BlockWeight::::mutate(|current_weight| { current_weight.set(Weight::zero(), DispatchClass::Mandatory); current_weight - .set(Weight::from_ref_time(256) - base_extrinsic, DispatchClass::Normal); + .set(Weight::from_parts(256, 0) - base_extrinsic, DispatchClass::Normal); }); let pre = CheckWeight::(PhantomData).pre_dispatch(&1, CALL, &info, len).unwrap(); assert_eq!( BlockWeight::::get().total(), - info.weight + Weight::from_ref_time(256) + info.weight + Weight::from_parts(256, 0) ); assert_ok!(CheckWeight::::post_dispatch( @@ -575,7 +575,7 @@ mod tests { )); assert_eq!( BlockWeight::::get().total(), - post_info.actual_weight.unwrap() + Weight::from_ref_time(256) + post_info.actual_weight.unwrap() + Weight::from_parts(256, 0) ); }) } @@ -583,23 +583,23 @@ mod tests { #[test] fn signed_ext_check_weight_actual_weight_higher_than_max_is_capped() { new_test_ext().execute_with(|| { - let info = DispatchInfo { weight: Weight::from_ref_time(512), ..Default::default() }; + let info = DispatchInfo { weight: Weight::from_parts(512, 0), ..Default::default() }; let post_info = PostDispatchInfo { - actual_weight: Some(Weight::from_ref_time(700)), + actual_weight: Some(Weight::from_parts(700, 0)), pays_fee: Default::default(), }; let len = 0_usize; BlockWeight::::mutate(|current_weight| { current_weight.set(Weight::zero(), DispatchClass::Mandatory); - current_weight.set(Weight::from_ref_time(128), DispatchClass::Normal); + current_weight.set(Weight::from_parts(128, 0), DispatchClass::Normal); }); let pre = CheckWeight::(PhantomData).pre_dispatch(&1, CALL, &info, len).unwrap(); assert_eq!( BlockWeight::::get().total(), info.weight + - Weight::from_ref_time(128) + + Weight::from_parts(128, 0) + block_weights().get(DispatchClass::Normal).base_extrinsic, ); @@ -613,7 +613,7 @@ mod tests { assert_eq!( BlockWeight::::get().total(), info.weight + - Weight::from_ref_time(128) + + Weight::from_parts(128, 0) + block_weights().get(DispatchClass::Normal).base_extrinsic, ); }) @@ -643,9 +643,9 @@ mod tests { // Max normal is 768 (75%) // Max mandatory is unlimited let max_normal = - DispatchInfo { weight: Weight::from_ref_time(753), ..Default::default() }; + DispatchInfo { weight: Weight::from_parts(753, 0), ..Default::default() }; let mandatory = DispatchInfo { - weight: Weight::from_ref_time(1019), + weight: Weight::from_parts(1019, 0), class: DispatchClass::Mandatory, ..Default::default() }; @@ -653,10 +653,10 @@ mod tests { let len = 0_usize; assert_ok!(CheckWeight::::do_pre_dispatch(&max_normal, len)); - assert_eq!(System::block_weight().total(), Weight::from_ref_time(768)); + assert_eq!(System::block_weight().total(), Weight::from_parts(768, 0)); assert_ok!(CheckWeight::::do_pre_dispatch(&mandatory, len)); - assert_eq!(block_weight_limit(), Weight::from_ref_time(1024).set_proof_size(u64::MAX)); - assert_eq!(System::block_weight().total(), Weight::from_ref_time(1024 + 768)); + assert_eq!(block_weight_limit(), Weight::from_parts(1024, u64::MAX)); + assert_eq!(System::block_weight().total(), Weight::from_parts(1024 + 768, 0)); assert_eq!(CheckWeight::::check_extrinsic_weight(&mandatory), Ok(())); }); } @@ -668,30 +668,30 @@ mod tests { .base_block(Weight::zero()) .for_class(DispatchClass::non_mandatory(), |w| { w.base_extrinsic = Weight::zero(); - w.max_total = Some(Weight::from_ref_time(20).set_proof_size(u64::MAX)); + w.max_total = Some(Weight::from_parts(20, u64::MAX)); }) .for_class(DispatchClass::Mandatory, |w| { w.base_extrinsic = Weight::zero(); - w.reserved = Some(Weight::from_ref_time(5).set_proof_size(u64::MAX)); + w.reserved = Some(Weight::from_parts(5, u64::MAX)); w.max_total = None; }) .build_or_panic(); let all_weight = crate::ConsumedWeight::new(|class| match class { - DispatchClass::Normal => Weight::from_ref_time(10), - DispatchClass::Operational => Weight::from_ref_time(10), + DispatchClass::Normal => Weight::from_parts(10, 0), + DispatchClass::Operational => Weight::from_parts(10, 0), DispatchClass::Mandatory => Weight::zero(), }); assert_eq!(maximum_weight.max_block, all_weight.total().set_proof_size(u64::MAX)); // fits into reserved let mandatory1 = DispatchInfo { - weight: Weight::from_ref_time(5), + weight: Weight::from_parts(5, 0), class: DispatchClass::Mandatory, ..Default::default() }; // does not fit into reserved and the block is full. let mandatory2 = DispatchInfo { - weight: Weight::from_ref_time(6), + weight: Weight::from_parts(6, 0), class: DispatchClass::Mandatory, ..Default::default() }; diff --git a/frame/system/src/extensions/mod.rs b/frame/system/src/extensions/mod.rs index 9ba73c5f4d4e..a88c9fbf96eb 100644 --- a/frame/system/src/extensions/mod.rs +++ b/frame/system/src/extensions/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/system/src/lib.rs b/frame/system/src/lib.rs index c5d170600615..56006269ab1d 100644 --- a/frame/system/src/lib.rs +++ b/frame/system/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,7 +50,7 @@ //! - [`CheckWeight`]: Checks the weight and length of the block and ensure that it does not //! exceed the limits. //! - [`CheckNonce`]: Checks the nonce of the transaction. Contains a single payload of type -//! `T::Index`. +//! `T::Nonce`. //! - [`CheckEra`]: Checks the era of the transaction. Contains a single payload of type `Era`. //! - [`CheckGenesis`]: Checks the provided genesis hash of the transaction. Must be a part of the //! signed payload of the transaction. @@ -64,16 +64,18 @@ #![cfg_attr(not(feature = "std"), no_std)] +use pallet_prelude::{BlockNumberFor, HeaderFor}; #[cfg(feature = "std")] use serde::Serialize; +use sp_io::hashing::blake2_256; #[cfg(feature = "runtime-benchmarks")] use sp_runtime::traits::TrailingZeroInput; use sp_runtime::{ generic, traits::{ - self, AtLeast32Bit, AtLeast32BitUnsigned, BadOrigin, BlockNumberProvider, Bounded, - CheckEqual, Dispatchable, Hash, Lookup, LookupError, MaybeDisplay, - MaybeSerializeDeserialize, Member, One, Saturating, SimpleBitOps, StaticLookup, Zero, + self, AtLeast32Bit, BadOrigin, BlockNumberProvider, Bounded, CheckEqual, Dispatchable, + Hash, Header, Lookup, LookupError, MaybeDisplay, MaybeSerializeDeserialize, Member, One, + Saturating, SimpleBitOps, StaticLookup, Zero, }, DispatchError, RuntimeDebug, }; @@ -83,15 +85,18 @@ use sp_std::{fmt::Debug, marker::PhantomData, prelude::*}; use sp_version::RuntimeVersion; use codec::{Decode, Encode, EncodeLike, FullCodec, MaxEncodedLen}; +#[cfg(feature = "std")] +use frame_support::traits::BuildGenesisConfig; use frame_support::{ dispatch::{ extract_actual_pays_fee, extract_actual_weight, DispatchClass, DispatchInfo, DispatchResult, DispatchResultWithPostInfo, PerDispatchClass, }, + impl_ensure_origin_with_arg_ignoring_arg, storage::{self, StorageStreamIter}, traits::{ - ConstU32, Contains, EnsureOrigin, Get, HandleLifetime, OnKilledAccount, OnNewAccount, - OriginTrait, PalletInfo, SortedMembers, StoredMap, TypedGet, + ConstU32, Contains, EnsureOrigin, EnsureOriginWithArg, Get, HandleLifetime, + OnKilledAccount, OnNewAccount, OriginTrait, PalletInfo, SortedMembers, StoredMap, TypedGet, }, Parameter, }; @@ -99,8 +104,6 @@ use scale_info::TypeInfo; use sp_core::storage::well_known_keys; use sp_weights::{RuntimeDbWeight, Weight}; -#[cfg(feature = "std")] -use frame_support::traits::GenesisBuild; #[cfg(any(feature = "std", test))] use sp_io::TestExternalities; @@ -200,12 +203,52 @@ pub mod pallet { use crate::{self as frame_system, pallet_prelude::*, *}; use frame_support::pallet_prelude::*; + /// Contains default types suitable for various environments + pub mod config_preludes { + use super::DefaultConfig; + + /// Provides a viable default config that can be used with + /// [`derive_impl`](`frame_support::derive_impl`) to derive a testing pallet config + /// based on this one. + /// + /// See `Test` in the `default-config` example pallet's `test.rs` for an example of + /// a downstream user of this particular `TestDefaultConfig` + pub struct TestDefaultConfig; + + #[frame_support::register_default_impl(TestDefaultConfig)] + impl DefaultConfig for TestDefaultConfig { + type Nonce = u32; + type Hash = sp_core::hash::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type MaxConsumers = frame_support::traits::ConstU32<16>; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type Version = (); + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + } + } + /// System configuration trait. Implemented by runtime. - #[pallet::config] + #[pallet::config(with_default)] #[pallet::disable_frame_system_supertrait_check] pub trait Config: 'static + Eq + Clone { + /// The aggregated event type of the runtime. + type RuntimeEvent: Parameter + + Member + + From> + + Debug + + IsType<::RuntimeEvent>; + /// The basic call filter to use in Origin. All origins are built with this filter as base, /// except Root. + #[pallet::no_default] type BaseCallFilter: Contains; /// Block & extrinsics weights: base values and limits. @@ -217,20 +260,21 @@ pub mod pallet { type BlockLength: Get; /// The `RuntimeOrigin` type used by dispatchable calls. + #[pallet::no_default] type RuntimeOrigin: Into, Self::RuntimeOrigin>> + From> + Clone - + OriginTrait; + + OriginTrait; /// The aggregated `RuntimeCall` type. + #[pallet::no_default] type RuntimeCall: Parameter + Dispatchable + Debug + From>; - /// Account index (aka nonce) type. This stores the number of previous transactions - /// associated with a sender account. - type Index: Parameter + /// This stores the number of previous transactions associated with a sender account. + type Nonce: Parameter + Member + MaybeSerializeDeserialize + Debug @@ -240,21 +284,6 @@ pub mod pallet { + Copy + MaxEncodedLen; - /// The block number type used by the runtime. - type BlockNumber: Parameter - + Member - + MaybeSerializeDeserialize - + Debug - + MaybeDisplay - + AtLeast32BitUnsigned - + Default - + Bounded - + Copy - + sp_std::hash::Hash - + sp_std::str::FromStr - + MaxEncodedLen - + TypeInfo; - /// The output of the `Hashing` function. type Hash: Parameter + Member @@ -291,19 +320,15 @@ pub mod pallet { /// functional/efficient alternatives. type Lookup: StaticLookup; - /// The block header. - type Header: Parameter + traits::Header; - - /// The aggregated event type of the runtime. - type RuntimeEvent: Parameter - + Member - + From> - + Debug - + IsType<::RuntimeEvent>; + /// The Block type used by the runtime. This is used by `construct_runtime` to retrieve the + /// extrinsics or other block specific data as needed. + #[pallet::no_default] + type Block: Parameter + Member + traits::Block; /// Maximum number of block number to block hash mappings to keep (oldest pruned first). #[pallet::constant] - type BlockHashCount: Get; + #[pallet::no_default] + type BlockHashCount: Get>; /// The weight of runtime database operations the runtime can invoke. #[pallet::constant] @@ -319,6 +344,7 @@ pub mod pallet { /// runtime. /// /// For tests it is okay to use `()` as type, however it will provide "useless" data. + #[pallet::no_default] type PalletInfo: PalletInfo; /// Data to be associated with an account (other than nonce/transaction counter, which this @@ -350,6 +376,7 @@ pub mod pallet { /// [`Pallet::update_code_in_storage`]). /// It's unlikely that this needs to be customized, unless you are writing a parachain using /// `Cumulus`, where the actual code change is deferred. + #[pallet::no_default] type OnSetCode: SetCode; /// The maximum number of consumers allowed on a single account. @@ -357,16 +384,13 @@ pub mod pallet { } #[pallet::pallet] - #[pallet::generate_store(pub (super) trait Store)] pub struct Pallet(_); #[pallet::hooks] impl Hooks> for Pallet { #[cfg(feature = "std")] fn integrity_test() { - sp_io::TestExternalities::default().execute_with(|| { - T::BlockWeights::get().validate().expect("The weights are invalid."); - }); + T::BlockWeights::get().validate().expect("The weights are invalid."); } } @@ -374,13 +398,10 @@ pub mod pallet { impl Pallet { /// Make some on-chain remark. /// - /// # - /// - `O(1)` - /// # + /// Can be executed by every `origin`. #[pallet::call_index(0)] #[pallet::weight(T::SystemWeightInfo::remark(_remark.len() as u32))] - pub fn remark(origin: OriginFor, _remark: Vec) -> DispatchResultWithPostInfo { - ensure_signed_or_root(origin)?; + pub fn remark(_origin: OriginFor, _remark: Vec) -> DispatchResultWithPostInfo { Ok(().into()) } @@ -395,44 +416,26 @@ pub mod pallet { } /// Set the new runtime code. - /// - /// # - /// - `O(C + S)` where `C` length of `code` and `S` complexity of `can_set_code` - /// - 1 call to `can_set_code`: `O(S)` (calls `sp_io::misc::runtime_version` which is - /// expensive). - /// - 1 storage write (codec `O(C)`). - /// - 1 digest item. - /// - 1 event. - /// The weight of this function is dependent on the runtime, but generally this is very - /// expensive. We will treat this as a full block. - /// # #[pallet::call_index(2)] - #[pallet::weight((T::BlockWeights::get().max_block, DispatchClass::Operational))] + #[pallet::weight((T::SystemWeightInfo::set_code(), DispatchClass::Operational))] pub fn set_code(origin: OriginFor, code: Vec) -> DispatchResultWithPostInfo { ensure_root(origin)?; Self::can_set_code(&code)?; T::OnSetCode::set_code(code)?; - Ok(().into()) + // consume the rest of the block to prevent further transactions + Ok(Some(T::BlockWeights::get().max_block).into()) } /// Set the new runtime code without doing any checks of the given `code`. - /// - /// # - /// - `O(C)` where `C` length of `code` - /// - 1 storage write (codec `O(C)`). - /// - 1 digest item. - /// - 1 event. - /// The weight of this function is dependent on the runtime. We will treat this as a full - /// block. # #[pallet::call_index(3)] - #[pallet::weight((T::BlockWeights::get().max_block, DispatchClass::Operational))] + #[pallet::weight((T::SystemWeightInfo::set_code(), DispatchClass::Operational))] pub fn set_code_without_checks( origin: OriginFor, code: Vec, ) -> DispatchResultWithPostInfo { ensure_root(origin)?; T::OnSetCode::set_code(code)?; - Ok(().into()) + Ok(Some(T::BlockWeights::get().max_block).into()) } /// Set some items of storage. @@ -548,7 +551,7 @@ pub mod pallet { _, Blake2_128Concat, T::AccountId, - AccountInfo, + AccountInfo, ValueQuery, >; @@ -570,7 +573,7 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn block_hash)] pub type BlockHash = - StorageMap<_, Twox64Concat, T::BlockNumber, T::Hash, ValueQuery>; + StorageMap<_, Twox64Concat, BlockNumberFor, T::Hash, ValueQuery>; /// Extrinsics data for the current block (maps an extrinsic's index to its data). #[pallet::storage] @@ -583,7 +586,7 @@ pub mod pallet { #[pallet::storage] #[pallet::whitelist_storage] #[pallet::getter(fn block_number)] - pub(super) type Number = StorageValue<_, T::BlockNumber, ValueQuery>; + pub(super) type Number = StorageValue<_, BlockNumberFor, ValueQuery>; /// Hash of the previous block. #[pallet::storage] @@ -622,14 +625,14 @@ pub mod pallet { /// allows light-clients to leverage the changes trie storage tracking mechanism and /// in case of changes fetch the list of events of interest. /// - /// The value has the type `(T::BlockNumber, EventIndex)` because if we used only just + /// The value has the type `(BlockNumberFor, EventIndex)` because if we used only just /// the `EventIndex` then in case if the topic has the same contents on the next block /// no notification will be triggered thus the event might be lost. #[pallet::storage] #[pallet::unbounded] #[pallet::getter(fn event_topics)] pub(super) type EventTopics = - StorageMap<_, Blake2_128Concat, T::Hash, Vec<(T::BlockNumber, EventIndex)>, ValueQuery>; + StorageMap<_, Blake2_128Concat, T::Hash, Vec<(BlockNumberFor, EventIndex)>, ValueQuery>; /// Stores the `spec_version` and `spec_name` of when the last runtime upgrade happened. #[pallet::storage] @@ -650,17 +653,19 @@ pub mod pallet { #[pallet::whitelist_storage] pub(super) type ExecutionPhase = StorageValue<_, Phase>; - #[cfg_attr(feature = "std", derive(Default))] + #[derive(frame_support::DefaultNoBound)] #[pallet::genesis_config] - pub struct GenesisConfig { + pub struct GenesisConfig { #[serde(with = "sp_core::bytes")] pub code: Vec, + #[serde(skip)] + pub _config: sp_std::marker::PhantomData, } #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { - >::insert::<_, T::Hash>(T::BlockNumber::zero(), hash69()); + >::insert::<_, T::Hash>(BlockNumberFor::::zero(), hash69()); >::put::(hash69()); >::put(LastRuntimeUpgradeInfo::from(T::Version::get())); >::put(true); @@ -672,26 +677,6 @@ pub mod pallet { } } -#[cfg(feature = "std")] -impl GenesisConfig { - /// Direct implementation of `GenesisBuild::build_storage`. - /// - /// Kept in order not to break dependency. - pub fn build_storage(&self) -> Result { - >::build_storage(self) - } - - /// Direct implementation of `GenesisBuild::assimilate_storage`. - /// - /// Kept in order not to break dependency. - pub fn assimilate_storage( - &self, - storage: &mut sp_runtime::Storage, - ) -> Result<(), String> { - >::assimilate_storage(self, storage) - } -} - pub type Key = Vec; pub type KeyValue = (Vec, Vec); @@ -727,7 +712,6 @@ pub struct EventRecord { // Create a Hash with 69 for each byte, // only used to build genesis config. -#[cfg(feature = "std")] fn hash69 + Default>() -> T { let mut h = T::default(); h.as_mut().iter_mut().for_each(|byte| *byte = 69); @@ -745,9 +729,9 @@ pub type RefCount = u32; /// Information of an account. #[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)] -pub struct AccountInfo { +pub struct AccountInfo { /// The number of transactions this account has sent. - pub nonce: Index, + pub nonce: Nonce, /// The number of other modules that currently depend on this account's existence. The account /// cannot be reaped until this is zero. pub consumers: RefCount, @@ -805,6 +789,12 @@ impl, O>> + From>, Acco } } +impl_ensure_origin_with_arg_ignoring_arg! { + impl< { O: .., AccountId: Decode, T } > + EnsureOriginWithArg for EnsureRoot + {} +} + /// Ensure the origin is Root and return the provided `Success` value. pub struct EnsureRootWithSuccess( sp_std::marker::PhantomData<(AccountId, Success)>, @@ -829,6 +819,12 @@ impl< } } +impl_ensure_origin_with_arg_ignoring_arg! { + impl< { O: .., AccountId: Decode, Success: TypedGet, T } > + EnsureOriginWithArg for EnsureRootWithSuccess + {} +} + /// Ensure the origin is provided `Ensure` origin and return the provided `Success` value. pub struct EnsureWithSuccess( sp_std::marker::PhantomData<(Ensure, AccountId, Success)>, @@ -874,6 +870,12 @@ impl, O>> + From>, Acco } } +impl_ensure_origin_with_arg_ignoring_arg! { + impl< { O: .., AccountId: Decode, T } > + EnsureOriginWithArg for EnsureSigned + {} +} + /// Ensure the origin is `Signed` origin from the given `AccountId`. pub struct EnsureSignedBy(sp_std::marker::PhantomData<(Who, AccountId)>); impl< @@ -892,17 +894,20 @@ impl< #[cfg(feature = "runtime-benchmarks")] fn try_successful_origin() -> Result { - let zero_account_id = - AccountId::decode(&mut TrailingZeroInput::zeroes()).map_err(|_| ())?; - let members = Who::sorted_members(); - let first_member = match members.get(0) { + let first_member = match Who::sorted_members().first() { Some(account) => account.clone(), - None => zero_account_id, + None => AccountId::decode(&mut TrailingZeroInput::zeroes()).map_err(|_| ())?, }; Ok(O::from(RawOrigin::Signed(first_member))) } } +impl_ensure_origin_with_arg_ignoring_arg! { + impl< { O: .., Who: SortedMembers, AccountId: PartialEq + Clone + Ord + Decode, T } > + EnsureOriginWithArg for EnsureSignedBy + {} +} + /// Ensure the origin is `None`. i.e. unsigned transaction. pub struct EnsureNone(sp_std::marker::PhantomData); impl, O>> + From>, AccountId> @@ -922,10 +927,16 @@ impl, O>> + From>, Acco } } +impl_ensure_origin_with_arg_ignoring_arg! { + impl< { O: .., AccountId, T } > + EnsureOriginWithArg for EnsureNone + {} +} + /// Always fail. -pub struct EnsureNever(sp_std::marker::PhantomData); -impl EnsureOrigin for EnsureNever { - type Success = T; +pub struct EnsureNever(sp_std::marker::PhantomData); +impl EnsureOrigin for EnsureNever { + type Success = Success; fn try_origin(o: O) -> Result { Err(o) } @@ -936,6 +947,12 @@ impl EnsureOrigin for EnsureNever { } } +impl_ensure_origin_with_arg_ignoring_arg! { + impl< { O, Success, T } > + EnsureOriginWithArg for EnsureNever + {} +} + /// Ensure that the origin `o` represents a signed extrinsic (i.e. transaction). /// Returns `Ok` with the account that signed the extrinsic or an `Err` otherwise. pub fn ensure_signed(o: OuterOrigin) -> Result @@ -1240,10 +1257,20 @@ impl Pallet { a.consumers == 0 || a.providers > 1 } - /// True if the account has at least one provider reference. - pub fn can_inc_consumer(who: &T::AccountId) -> bool { + /// True if the account has at least one provider reference and adding `amount` consumer + /// references would not take it above the the maximum. + pub fn can_accrue_consumers(who: &T::AccountId, amount: u32) -> bool { let a = Account::::get(who); - a.providers > 0 && a.consumers < T::MaxConsumers::max_consumers() + match a.consumers.checked_add(amount) { + Some(c) => a.providers > 0 && c <= T::MaxConsumers::max_consumers(), + None => false, + } + } + + /// True if the account has at least one provider reference and fewer consumer references than + /// the maximum. + pub fn can_inc_consumer(who: &T::AccountId) -> bool { + Self::can_accrue_consumers(who, 1) } /// Deposits an event into this block's event record. @@ -1326,10 +1353,12 @@ impl Pallet { } /// Start the execution of a particular block. - pub fn initialize(number: &T::BlockNumber, parent_hash: &T::Hash, digest: &generic::Digest) { + pub fn initialize(number: &BlockNumberFor, parent_hash: &T::Hash, digest: &generic::Digest) { // populate environment ExecutionPhase::::put(Phase::Initialization); storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &0u32); + let entropy = (b"frame_system::initialize", parent_hash).using_encoded(blake2_256); + storage::unhashed::put_raw(well_known_keys::INTRABLOCK_ENTROPY, &entropy[..]); >::put(number); >::put(digest); >::put(parent_hash); @@ -1341,7 +1370,7 @@ impl Pallet { /// Remove temporary "environment" entries in storage, compute the storage root and return the /// resulting header for this block. - pub fn finalize() -> T::Header { + pub fn finalize() -> HeaderFor { log::debug!( target: LOG_TARGET, "[{:?}] {} extrinsics, length: {} (normal {}%, op: {}%, mandatory {}%) / normal weight:\ @@ -1379,6 +1408,7 @@ impl Pallet { ); ExecutionPhase::::kill(); AllExtrinsicsLen::::kill(); + storage::unhashed::kill(well_known_keys::INTRABLOCK_ENTROPY); // The following fields // @@ -1412,21 +1442,10 @@ impl Pallet { let storage_root = T::Hash::decode(&mut &sp_io::storage::root(version)[..]) .expect("Node is configured to use the same hash; qed"); - ::new( - number, - extrinsics_root, - storage_root, - parent_hash, - digest, - ) + HeaderFor::::new(number, extrinsics_root, storage_root, parent_hash, digest) } /// Deposits a log and ensures it matches the block's log data. - /// - /// # - /// - `O(1)` - /// - 1 storage write (codec `O(1)`) - /// # pub fn deposit_log(item: generic::DigestItem) { >::append(item); } @@ -1436,8 +1455,8 @@ impl Pallet { pub fn externalities() -> TestExternalities { TestExternalities::new(sp_core::storage::Storage { top: map![ - >::hashed_key_for(T::BlockNumber::zero()) => [69u8; 32].encode(), - >::hashed_key().to_vec() => T::BlockNumber::one().encode(), + >::hashed_key_for(BlockNumberFor::::zero()) => [69u8; 32].encode(), + >::hashed_key().to_vec() => BlockNumberFor::::one().encode(), >::hashed_key().to_vec() => [69u8; 32].encode() ], children_default: map![], @@ -1462,6 +1481,14 @@ impl Pallet { Self::read_events_no_consensus().map(|e| *e).collect() } + /// Get a single event at specified index. + /// + /// Should only be called if you know what you are doing and outside of the runtime block + /// execution else it can have a large impact on the PoV size of a block. + pub fn event_no_consensus(index: usize) -> Option { + Self::read_events_no_consensus().nth(index).map(|e| e.event.clone()) + } + /// Get the current events deposited by the runtime. /// /// Should only be called if you know what you are doing and outside of the runtime block @@ -1474,7 +1501,7 @@ impl Pallet { /// Set the block number to something in particular. Can be used as an alternative to /// `initialize` for tests that don't need to bother with the other environment entries. #[cfg(any(feature = "std", feature = "runtime-benchmarks", test))] - pub fn set_block_number(n: T::BlockNumber) { + pub fn set_block_number(n: BlockNumberFor) { >::put(n); } @@ -1540,13 +1567,13 @@ impl Pallet { } /// Retrieve the account transaction counter from storage. - pub fn account_nonce(who: impl EncodeLike) -> T::Index { + pub fn account_nonce(who: impl EncodeLike) -> T::Nonce { Account::::get(who).nonce } /// Increment a particular account's nonce by 1. pub fn inc_account_nonce(who: impl EncodeLike) { - Account::::mutate(who, |a| a.nonce += T::Index::one()); + Account::::mutate(who, |a| a.nonce += T::Nonce::one()); } /// Note what the extrinsic data of the current extrinsic index is. @@ -1602,7 +1629,7 @@ impl Pallet { } /// An account is being created. - pub fn on_created_account(who: T::AccountId, _a: &mut AccountInfo) { + pub fn on_created_account(who: T::AccountId, _a: &mut AccountInfo) { T::OnNewAccount::on_new_account(&who); Self::deposit_event(Event::NewAccount { account: who }); } @@ -1624,18 +1651,36 @@ impl Pallet { .and_then(|v| RuntimeVersion::decode(&mut &v[..]).ok()) .ok_or(Error::::FailedToExtractRuntimeVersion)?; - if new_version.spec_name != current_version.spec_name { - return Err(Error::::InvalidSpecName.into()) - } + cfg_if::cfg_if! { + if #[cfg(all(feature = "runtime-benchmarks", not(test)))] { + // Let's ensure the compiler doesn't optimize our fetching of the runtime version away. + core::hint::black_box((new_version, current_version)); + Ok(()) + } else { + if new_version.spec_name != current_version.spec_name { + return Err(Error::::InvalidSpecName.into()) + } - if new_version.spec_version <= current_version.spec_version { - return Err(Error::::SpecVersionNeedsToIncrease.into()) - } + if new_version.spec_version <= current_version.spec_version { + return Err(Error::::SpecVersionNeedsToIncrease.into()) + } - Ok(()) + Ok(()) + } + } } } +/// Returns a 32 byte datum which is guaranteed to be universally unique. `entropy` is provided +/// as a facility to reduce the potential for precalculating results. +pub fn unique(entropy: impl Encode) -> [u8; 32] { + let mut last = [0u8; 32]; + sp_io::storage::read(well_known_keys::INTRABLOCK_ENTROPY, &mut last[..], 0); + let next = (b"frame_system::unique", entropy, last).using_encoded(blake2_256); + sp_io::storage::set(well_known_keys::INTRABLOCK_ENTROPY, &next); + next +} + /// Event handler which registers a provider when created. pub struct Provider(PhantomData); impl HandleLifetime for Provider { @@ -1674,17 +1719,13 @@ impl HandleLifetime for Consumer { } impl BlockNumberProvider for Pallet { - type BlockNumber = ::BlockNumber; + type BlockNumber = BlockNumberFor; fn current_block_number() -> Self::BlockNumber { Pallet::::block_number() } } -fn is_providing(d: &T) -> bool { - d != &T::default() -} - /// Implement StoredMap for a simple single-item, provide-when-not-default system. This works fine /// for storing a single item which allows the account to continue existing as long as it's not /// empty/default. @@ -1700,23 +1741,14 @@ impl StoredMap for Pallet { f: impl FnOnce(&mut Option) -> Result, ) -> Result { let account = Account::::get(k); - let was_providing = is_providing(&account.data); - let mut some_data = if was_providing { Some(account.data) } else { None }; + let is_default = account.data == T::AccountData::default(); + let mut some_data = if is_default { None } else { Some(account.data) }; let result = f(&mut some_data)?; - let is_providing = some_data.is_some(); - if !was_providing && is_providing { - Self::inc_providers(k); - } else if was_providing && !is_providing { - match Self::dec_providers(k)? { - DecRefStatus::Reaped => return Ok(result), - DecRefStatus::Exists => { - // Update value as normal... - }, - } - } else if !was_providing && !is_providing { - return Ok(result) + if Self::providers(k) > 0 || Self::sufficients(k) > 0 { + Account::::mutate(k, |a| a.data = some_data.unwrap_or_default()); + } else { + Account::::remove(k) } - Account::::mutate(k, |a| a.data = some_data.unwrap_or_default()); Ok(result) } } @@ -1758,6 +1790,10 @@ pub mod pallet_prelude { /// Type alias for the `Origin` associated type of system config. pub type OriginFor = ::RuntimeOrigin; + /// Type alias for the `Header`. + pub type HeaderFor = + <::Block as sp_runtime::traits::HeaderProvider>::HeaderT; + /// Type alias for the `BlockNumber` associated type of system config. - pub type BlockNumberFor = ::BlockNumber; + pub type BlockNumberFor = as sp_runtime::traits::Header>::Number; } diff --git a/frame/system/src/limits.rs b/frame/system/src/limits.rs index 54d27c5b9e86..5fd7a5af8757 100644 --- a/frame/system/src/limits.rs +++ b/frame/system/src/limits.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/system/src/migrations/mod.rs b/frame/system/src/migrations/mod.rs index 90b88de1ab11..6f873061dbab 100644 --- a/frame/system/src/migrations/mod.rs +++ b/frame/system/src/migrations/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -31,8 +31,8 @@ type RefCount = u32; /// Information of an account. #[derive(Clone, Eq, PartialEq, Default, RuntimeDebug, Encode, Decode)] -struct AccountInfo { - nonce: Index, +struct AccountInfo { + nonce: Nonce, consumers: RefCount, providers: RefCount, sufficients: RefCount, @@ -47,8 +47,8 @@ pub trait V2ToV3 { /// System config account id type AccountId: 'static + FullCodec; - /// System config index - type Index: 'static + FullCodec + Copy; + /// System config nonce + type Nonce: 'static + FullCodec + Copy; /// System config account data type AccountData: 'static + FullCodec; @@ -65,13 +65,13 @@ type Account = StorageMap< Pallet, Blake2_128Concat, ::AccountId, - AccountInfo<::Index, ::AccountData>, + AccountInfo<::Nonce, ::AccountData>, >; /// Migrate from unique `u8` reference counting to triple `u32` reference counting. pub fn migrate_from_single_u8_to_triple_ref_count() -> Weight { let mut translated: usize = 0; - >::translate::<(V::Index, u8, V::AccountData), _>(|_key, (nonce, rc, data)| { + >::translate::<(V::Nonce, u8, V::AccountData), _>(|_key, (nonce, rc, data)| { translated += 1; Some(AccountInfo { nonce, consumers: rc as RefCount, providers: 1, sufficients: 0, data }) }); @@ -88,7 +88,7 @@ pub fn migrate_from_single_u8_to_triple_ref_count() -> Wei /// Migrate from unique `u32` reference counting to triple `u32` reference counting. pub fn migrate_from_single_to_triple_ref_count() -> Weight { let mut translated: usize = 0; - >::translate::<(V::Index, RefCount, V::AccountData), _>( + >::translate::<(V::Nonce, RefCount, V::AccountData), _>( |_key, (nonce, consumers, data)| { translated += 1; Some(AccountInfo { nonce, consumers, providers: 1, sufficients: 0, data }) @@ -106,7 +106,7 @@ pub fn migrate_from_single_to_triple_ref_count() -> Weight /// Migrate from dual `u32` reference counting to triple `u32` reference counting. pub fn migrate_from_dual_to_triple_ref_count() -> Weight { let mut translated: usize = 0; - >::translate::<(V::Index, RefCount, RefCount, V::AccountData), _>( + >::translate::<(V::Nonce, RefCount, RefCount, V::AccountData), _>( |_key, (nonce, consumers, providers, data)| { translated += 1; Some(AccountInfo { nonce, consumers, providers, sufficients: 0, data }) diff --git a/frame/system/src/mock.rs b/frame/system/src/mock.rs index fb230f66a94f..c016ea9e1cd1 100644 --- a/frame/system/src/mock.rs +++ b/frame/system/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,26 +22,21 @@ use frame_support::{ }; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, BuildStorage, Perbill, }; -type UncheckedExtrinsic = mocking::MockUncheckedExtrinsic; type Block = mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, } ); const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); -const MAX_BLOCK_WEIGHT: Weight = Weight::from_ref_time(1024).set_proof_size(u64::MAX); +const MAX_BLOCK_WEIGHT: Weight = Weight::from_parts(1024, u64::MAX); parameter_types! { pub Version: RuntimeVersion = RuntimeVersion { @@ -59,15 +54,15 @@ parameter_types! { write: 100, }; pub RuntimeBlockWeights: limits::BlockWeights = limits::BlockWeights::builder() - .base_block(Weight::from_ref_time(10)) + .base_block(Weight::from_parts(10, 0)) .for_class(DispatchClass::all(), |weights| { - weights.base_extrinsic = Weight::from_ref_time(5); + weights.base_extrinsic = Weight::from_parts(5, 0); }) .for_class(DispatchClass::Normal, |weights| { weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAX_BLOCK_WEIGHT); }) .for_class(DispatchClass::Operational, |weights| { - weights.base_extrinsic = Weight::from_ref_time(10); + weights.base_extrinsic = Weight::from_parts(10, 0); weights.max_total = Some(MAX_BLOCK_WEIGHT); weights.reserved = Some( MAX_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAX_BLOCK_WEIGHT @@ -96,13 +91,12 @@ impl Config for Test { type BlockLength = RuntimeBlockLength; type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<10>; type DbWeight = DbWeight; @@ -126,7 +120,7 @@ pub const CALL: &::RuntimeCall = /// Create new externalities for `System` module tests. pub fn new_test_ext() -> sp_io::TestExternalities { let mut ext: sp_io::TestExternalities = - GenesisConfig::default().build_storage().unwrap().into(); + RuntimeGenesisConfig::default().build_storage().unwrap().into(); // Add to each test the initial weight of a block ext.execute_with(|| { System::register_extra_weight_unchecked( diff --git a/frame/system/src/mocking.rs b/frame/system/src/mocking.rs index d8cfcb9baf26..833309e05ecc 100644 --- a/frame/system/src/mocking.rs +++ b/frame/system/src/mocking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,6 +29,19 @@ pub type MockUncheckedExtrinsic = generic::Unchec /// An implementation of `sp_runtime::traits::Block` to be used in tests. pub type MockBlock = generic::Block< - generic::Header<::BlockNumber, sp_runtime::traits::BlakeTwo256>, + generic::Header, + MockUncheckedExtrinsic, +>; + +/// An implementation of `sp_runtime::traits::Block` to be used in tests with u32 BlockNumber type. +pub type MockBlockU32 = generic::Block< + generic::Header, + MockUncheckedExtrinsic, +>; + +/// An implementation of `sp_runtime::traits::Block` to be used in tests with u128 BlockNumber +/// type. +pub type MockBlockU128 = generic::Block< + generic::Header, MockUncheckedExtrinsic, >; diff --git a/frame/system/src/offchain.rs b/frame/system/src/offchain.rs index 99a4c1541d30..313a557c44d7 100644 --- a/frame/system/src/offchain.rs +++ b/frame/system/src/offchain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -486,7 +486,7 @@ pub trait CreateSignedTransaction: call: Self::OverarchingCall, public: Self::Public, account: Self::AccountId, - nonce: Self::Index, + nonce: Self::Nonce, ) -> Option<(Self::OverarchingCall, ::SignaturePayload)>; } diff --git a/frame/system/src/tests.rs b/frame/system/src/tests.rs index 8223f6674415..165df688b1c2 100644 --- a/frame/system/src/tests.rs +++ b/frame/system/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,14 +19,36 @@ use crate::*; use frame_support::{ assert_noop, assert_ok, dispatch::{Pays, PostDispatchInfo, WithPostDispatchInfo}, + traits::WhitelistedStorageKeys, }; +use std::collections::BTreeSet; + use mock::{RuntimeOrigin, *}; -use sp_core::H256; +use sp_core::{hexdisplay::HexDisplay, H256}; use sp_runtime::{ traits::{BlakeTwo256, Header}, DispatchError, DispatchErrorWithPostInfo, }; +#[test] +fn check_whitelist() { + let whitelist: BTreeSet = AllPalletsWithSystem::whitelisted_storage_keys() + .iter() + .map(|s| HexDisplay::from(&s.key).to_string()) + .collect(); + + // Block Number + assert!(whitelist.contains("26aa394eea5630e07c48ae0c9558cef702a5c1b19ab7a04f536c519aca4983ac")); + // Execution Phase + assert!(whitelist.contains("26aa394eea5630e07c48ae0c9558cef7ff553b5a9862a516939d82b3d3d8661a")); + // Event Count + assert!(whitelist.contains("26aa394eea5630e07c48ae0c9558cef70a98fdbe9ce6c55837576c60c7af3850")); + // System Events + assert!(whitelist.contains("26aa394eea5630e07c48ae0c9558cef780d41e5e16056765bc8461851072c9d7")); + // System BlockWeight + assert!(whitelist.contains("26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96")); +} + #[test] fn origin_works() { let o = RuntimeOrigin::from(RawOrigin::::Signed(1u64)); @@ -34,9 +56,47 @@ fn origin_works() { assert_eq!(x.unwrap(), RawOrigin::::Signed(1u64)); } +#[test] +fn unique_datum_works() { + new_test_ext().execute_with(|| { + System::initialize(&1, &[0u8; 32].into(), &Default::default()); + assert!(sp_io::storage::exists(well_known_keys::INTRABLOCK_ENTROPY)); + + let h1 = unique(b""); + assert_eq!( + 32, + sp_io::storage::read(well_known_keys::INTRABLOCK_ENTROPY, &mut [], 0).unwrap() + ); + let h2 = unique(b""); + assert_eq!( + 32, + sp_io::storage::read(well_known_keys::INTRABLOCK_ENTROPY, &mut [], 0).unwrap() + ); + assert_ne!(h1, h2); + + let h3 = unique(b"Hello"); + assert_eq!( + 32, + sp_io::storage::read(well_known_keys::INTRABLOCK_ENTROPY, &mut [], 0).unwrap() + ); + assert_ne!(h2, h3); + + let h4 = unique(b"Hello"); + assert_eq!( + 32, + sp_io::storage::read(well_known_keys::INTRABLOCK_ENTROPY, &mut [], 0).unwrap() + ); + assert_ne!(h3, h4); + + System::finalize(); + assert!(!sp_io::storage::exists(well_known_keys::INTRABLOCK_ENTROPY)); + }); +} + #[test] fn stored_map_works() { new_test_ext().execute_with(|| { + assert_eq!(System::inc_providers(&0), IncRefStatus::Created); assert_ok!(System::insert(&0, 42)); assert!(!System::is_provider_required(&0)); @@ -56,6 +116,7 @@ fn stored_map_works() { assert!(Killed::get().is_empty()); assert_ok!(System::remove(&0)); + assert_ok!(System::dec_providers(&0)); assert_eq!(Killed::get(), vec![0u64]); }); } @@ -233,20 +294,26 @@ fn deposit_event_uses_actual_weight_and_pays_fee() { let normal_base = ::BlockWeights::get() .get(DispatchClass::Normal) .base_extrinsic; - let pre_info = DispatchInfo { weight: Weight::from_ref_time(1000), ..Default::default() }; - System::note_applied_extrinsic(&Ok(Some(300).into()), pre_info); - System::note_applied_extrinsic(&Ok(Some(1000).into()), pre_info); + let pre_info = DispatchInfo { weight: Weight::from_parts(1000, 0), ..Default::default() }; + System::note_applied_extrinsic(&Ok(from_actual_ref_time(Some(300))), pre_info); + System::note_applied_extrinsic(&Ok(from_actual_ref_time(Some(1000))), pre_info); System::note_applied_extrinsic( // values over the pre info should be capped at pre dispatch value - &Ok(Some(1200).into()), + &Ok(from_actual_ref_time(Some(1200))), + pre_info, + ); + System::note_applied_extrinsic( + &Ok(from_post_weight_info(Some(2_500_000), Pays::Yes)), pre_info, ); - System::note_applied_extrinsic(&Ok((Some(2_500_000), Pays::Yes).into()), pre_info); System::note_applied_extrinsic(&Ok(Pays::No.into()), pre_info); - System::note_applied_extrinsic(&Ok((Some(2_500_000), Pays::No).into()), pre_info); - System::note_applied_extrinsic(&Ok((Some(500), Pays::No).into()), pre_info); System::note_applied_extrinsic( - &Err(DispatchError::BadOrigin.with_weight(Weight::from_ref_time(999))), + &Ok(from_post_weight_info(Some(2_500_000), Pays::No)), + pre_info, + ); + System::note_applied_extrinsic(&Ok(from_post_weight_info(Some(500), Pays::No)), pre_info); + System::note_applied_extrinsic( + &Err(DispatchError::BadOrigin.with_weight(Weight::from_parts(999, 0))), pre_info, ); @@ -260,7 +327,7 @@ fn deposit_event_uses_actual_weight_and_pays_fee() { System::note_applied_extrinsic( &Err(DispatchErrorWithPostInfo { post_info: PostDispatchInfo { - actual_weight: Some(Weight::from_ref_time(800)), + actual_weight: Some(Weight::from_parts(800, 0)), pays_fee: Pays::Yes, }, error: DispatchError::BadOrigin, @@ -270,7 +337,7 @@ fn deposit_event_uses_actual_weight_and_pays_fee() { System::note_applied_extrinsic( &Err(DispatchErrorWithPostInfo { post_info: PostDispatchInfo { - actual_weight: Some(Weight::from_ref_time(800)), + actual_weight: Some(Weight::from_parts(800, 0)), pays_fee: Pays::No, }, error: DispatchError::BadOrigin, @@ -283,11 +350,11 @@ fn deposit_event_uses_actual_weight_and_pays_fee() { .base_extrinsic; assert!(normal_base != operational_base, "Test pre-condition violated"); let pre_info = DispatchInfo { - weight: Weight::from_ref_time(1000), + weight: Weight::from_parts(1000, 0), class: DispatchClass::Operational, ..Default::default() }; - System::note_applied_extrinsic(&Ok(Some(300).into()), pre_info); + System::note_applied_extrinsic(&Ok(from_actual_ref_time(Some(300))), pre_info); let got = System::events(); let want = vec![ @@ -295,7 +362,7 @@ fn deposit_event_uses_actual_weight_and_pays_fee() { phase: Phase::ApplyExtrinsic(0), event: SysEvent::ExtrinsicSuccess { dispatch_info: DispatchInfo { - weight: Weight::from_ref_time(300).saturating_add(normal_base), + weight: Weight::from_parts(300, 0).saturating_add(normal_base), ..Default::default() }, } @@ -306,7 +373,7 @@ fn deposit_event_uses_actual_weight_and_pays_fee() { phase: Phase::ApplyExtrinsic(1), event: SysEvent::ExtrinsicSuccess { dispatch_info: DispatchInfo { - weight: Weight::from_ref_time(1000).saturating_add(normal_base), + weight: Weight::from_parts(1000, 0).saturating_add(normal_base), ..Default::default() }, } @@ -317,7 +384,7 @@ fn deposit_event_uses_actual_weight_and_pays_fee() { phase: Phase::ApplyExtrinsic(2), event: SysEvent::ExtrinsicSuccess { dispatch_info: DispatchInfo { - weight: Weight::from_ref_time(1000).saturating_add(normal_base), + weight: Weight::from_parts(1000, 0).saturating_add(normal_base), ..Default::default() }, } @@ -328,7 +395,7 @@ fn deposit_event_uses_actual_weight_and_pays_fee() { phase: Phase::ApplyExtrinsic(3), event: SysEvent::ExtrinsicSuccess { dispatch_info: DispatchInfo { - weight: Weight::from_ref_time(1000).saturating_add(normal_base), + weight: Weight::from_parts(1000, 0).saturating_add(normal_base), pays_fee: Pays::Yes, ..Default::default() }, @@ -340,7 +407,7 @@ fn deposit_event_uses_actual_weight_and_pays_fee() { phase: Phase::ApplyExtrinsic(4), event: SysEvent::ExtrinsicSuccess { dispatch_info: DispatchInfo { - weight: Weight::from_ref_time(1000).saturating_add(normal_base), + weight: Weight::from_parts(1000, 0).saturating_add(normal_base), pays_fee: Pays::No, ..Default::default() }, @@ -352,7 +419,7 @@ fn deposit_event_uses_actual_weight_and_pays_fee() { phase: Phase::ApplyExtrinsic(5), event: SysEvent::ExtrinsicSuccess { dispatch_info: DispatchInfo { - weight: Weight::from_ref_time(1000).saturating_add(normal_base), + weight: Weight::from_parts(1000, 0).saturating_add(normal_base), pays_fee: Pays::No, ..Default::default() }, @@ -364,7 +431,7 @@ fn deposit_event_uses_actual_weight_and_pays_fee() { phase: Phase::ApplyExtrinsic(6), event: SysEvent::ExtrinsicSuccess { dispatch_info: DispatchInfo { - weight: Weight::from_ref_time(500).saturating_add(normal_base), + weight: Weight::from_parts(500, 0).saturating_add(normal_base), pays_fee: Pays::No, ..Default::default() }, @@ -377,7 +444,7 @@ fn deposit_event_uses_actual_weight_and_pays_fee() { event: SysEvent::ExtrinsicFailed { dispatch_error: DispatchError::BadOrigin.into(), dispatch_info: DispatchInfo { - weight: Weight::from_ref_time(999).saturating_add(normal_base), + weight: Weight::from_parts(999, 0).saturating_add(normal_base), ..Default::default() }, } @@ -389,7 +456,7 @@ fn deposit_event_uses_actual_weight_and_pays_fee() { event: SysEvent::ExtrinsicFailed { dispatch_error: DispatchError::BadOrigin.into(), dispatch_info: DispatchInfo { - weight: Weight::from_ref_time(1000).saturating_add(normal_base), + weight: Weight::from_parts(1000, 0).saturating_add(normal_base), pays_fee: Pays::Yes, ..Default::default() }, @@ -402,7 +469,7 @@ fn deposit_event_uses_actual_weight_and_pays_fee() { event: SysEvent::ExtrinsicFailed { dispatch_error: DispatchError::BadOrigin.into(), dispatch_info: DispatchInfo { - weight: Weight::from_ref_time(800).saturating_add(normal_base), + weight: Weight::from_parts(800, 0).saturating_add(normal_base), pays_fee: Pays::Yes, ..Default::default() }, @@ -415,7 +482,7 @@ fn deposit_event_uses_actual_weight_and_pays_fee() { event: SysEvent::ExtrinsicFailed { dispatch_error: DispatchError::BadOrigin.into(), dispatch_info: DispatchInfo { - weight: Weight::from_ref_time(800).saturating_add(normal_base), + weight: Weight::from_parts(800, 0).saturating_add(normal_base), pays_fee: Pays::No, ..Default::default() }, @@ -427,7 +494,7 @@ fn deposit_event_uses_actual_weight_and_pays_fee() { phase: Phase::ApplyExtrinsic(11), event: SysEvent::ExtrinsicSuccess { dispatch_info: DispatchInfo { - weight: Weight::from_ref_time(300).saturating_add(operational_base), + weight: Weight::from_parts(300, 0).saturating_add(operational_base), class: DispatchClass::Operational, ..Default::default() }, @@ -542,7 +609,12 @@ fn set_code_checks_works() { ("test", 1, 2, Err(Error::::SpecVersionNeedsToIncrease)), ("test", 1, 1, Err(Error::::SpecVersionNeedsToIncrease)), ("test2", 1, 1, Err(Error::::InvalidSpecName)), - ("test", 2, 1, Ok(PostDispatchInfo::default())), + ( + "test", + 2, + 1, + Ok(Some(::BlockWeights::get().max_block).into()), + ), ("test", 0, 1, Err(Error::::SpecVersionNeedsToIncrease)), ("test", 1, 0, Err(Error::::SpecVersionNeedsToIncrease)), ]; @@ -581,7 +653,7 @@ fn assert_runtime_updated_digest(num: usize) { #[test] fn set_code_with_real_wasm_blob() { - let executor = substrate_test_runtime_client::new_native_executor(); + let executor = substrate_test_runtime_client::new_native_or_wasm_executor(); let mut ext = new_test_ext(); ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(executor)); ext.execute_with(|| { @@ -605,7 +677,7 @@ fn set_code_with_real_wasm_blob() { #[test] fn runtime_upgraded_with_set_storage() { - let executor = substrate_test_runtime_client::new_native_executor(); + let executor = substrate_test_runtime_client::new_native_or_wasm_executor(); let mut ext = new_test_ext(); ext.register_extension(sp_core::traits::ReadRuntimeVersionExt::new(executor)); ext.execute_with(|| { @@ -674,15 +746,30 @@ fn ensure_signed_stuff_works() { } let signed_origin = RuntimeOrigin::signed(0u64); - assert_ok!(EnsureSigned::try_origin(signed_origin.clone())); - assert_ok!(EnsureSignedBy::::try_origin(signed_origin)); + assert_ok!( as EnsureOrigin<_>>::try_origin(signed_origin.clone())); + assert_ok!( as EnsureOrigin<_>>::try_origin(signed_origin)); #[cfg(feature = "runtime-benchmarks")] { - let successful_origin: RuntimeOrigin = EnsureSigned::successful_origin(); - assert_ok!(EnsureSigned::try_origin(successful_origin)); + let successful_origin: RuntimeOrigin = + as EnsureOrigin<_>>::try_successful_origin() + .expect("EnsureSigned has no successful origin required for the test"); + assert_ok!( as EnsureOrigin<_>>::try_origin(successful_origin)); + + let successful_origin: RuntimeOrigin = + as EnsureOrigin<_>>::try_successful_origin() + .expect("EnsureSignedBy has no successful origin required for the test"); + assert_ok!( as EnsureOrigin<_>>::try_origin(successful_origin)); + } +} - let successful_origin: RuntimeOrigin = EnsureSignedBy::::successful_origin(); - assert_ok!(EnsureSignedBy::::try_origin(successful_origin)); +pub fn from_actual_ref_time(ref_time: Option) -> PostDispatchInfo { + PostDispatchInfo { + actual_weight: ref_time.map(|t| Weight::from_all(t)), + pays_fee: Default::default(), } } + +pub fn from_post_weight_info(ref_time: Option, pays_fee: Pays) -> PostDispatchInfo { + PostDispatchInfo { actual_weight: ref_time.map(|t| Weight::from_all(t)), pays_fee } +} diff --git a/frame/system/src/weights.rs b/frame/system/src/weights.rs index 696a6a09b8f8..b79db3654b9f 100644 --- a/frame/system/src/weights.rs +++ b/frame/system/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,38 +18,42 @@ //! Autogenerated weights for frame_system //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-22, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-s7kdgajz-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// ./target/production/substrate +// target/production/substrate // benchmark // pallet -// --chain=dev // --steps=50 // --repeat=20 -// --pallet=frame_system // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --output=./frame/system/src/weights.rs +// --json-file=/builds/parity/mirrors/substrate/.git/.artifacts/bench.json +// --pallet=frame-system +// --chain=dev // --header=./HEADER-APACHE2 +// --output=./frame/system/src/weights.rs // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::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; @@ -60,52 +64,90 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { /// The range of component `b` is `[0, 3932160]`. fn remark(b: u32, ) -> Weight { - // Minimum execution time: 3_951 nanoseconds. - Weight::from_ref_time(1_307_232 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_004_000 picoseconds. + Weight::from_parts(2_119_000, 0) // Standard Error: 0 - .saturating_add(Weight::from_ref_time(363 as u64).saturating_mul(b as u64)) + .saturating_add(Weight::from_parts(390, 0).saturating_mul(b.into())) } /// The range of component `b` is `[0, 3932160]`. fn remark_with_event(b: u32, ) -> Weight { - // Minimum execution time: 14_880 nanoseconds. - Weight::from_ref_time(15_173_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_424 as u64).saturating_mul(b as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_032_000 picoseconds. + Weight::from_parts(8_097_000, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_455, 0).saturating_mul(b.into())) } - // Storage: System Digest (r:1 w:1) - // Storage: unknown [0x3a686561707061676573] (r:0 w:1) + /// Storage: System Digest (r:1 w:1) + /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: unknown `0x3a686561707061676573` (r:0 w:1) + /// Proof Skipped: unknown `0x3a686561707061676573` (r:0 w:1) fn set_heap_pages() -> Weight { - // Minimum execution time: 9_819 nanoseconds. - Weight::from_ref_time(10_513_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1485` + // Minimum execution time: 4_446_000 picoseconds. + Weight::from_parts(4_782_000, 1485) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: System Digest (r:1 w:1) + /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: unknown `0x3a636f6465` (r:0 w:1) + /// Proof Skipped: unknown `0x3a636f6465` (r:0 w:1) + fn set_code() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1485` + // Minimum execution time: 84_000_503_000 picoseconds. + Weight::from_parts(87_586_619_000, 1485) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Skipped Metadata (r:0 w:0) + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) /// The range of component `i` is `[0, 1000]`. fn set_storage(i: u32, ) -> Weight { - // Minimum execution time: 4_038 nanoseconds. - Weight::from_ref_time(4_098_000 as u64) - // Standard Error: 710 - .saturating_add(Weight::from_ref_time(620_813 as u64).saturating_mul(i as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(i as u64))) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_086_000 picoseconds. + Weight::from_parts(2_175_000, 0) + // Standard Error: 1_056 + .saturating_add(Weight::from_parts(841_511, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) } - // Storage: Skipped Metadata (r:0 w:0) + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) /// The range of component `i` is `[0, 1000]`. fn kill_storage(i: u32, ) -> Weight { - // Minimum execution time: 3_972 nanoseconds. - Weight::from_ref_time(4_082_000 as u64) - // Standard Error: 884 - .saturating_add(Weight::from_ref_time(536_923 as u64).saturating_mul(i as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(i as u64))) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(2_255_000, 0) + // Standard Error: 1_425 + .saturating_add(Weight::from_parts(662_473, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) } - // Storage: Skipped Metadata (r:0 w:0) + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) /// The range of component `p` is `[0, 1000]`. fn kill_prefix(p: u32, ) -> Weight { - // Minimum execution time: 5_703 nanoseconds. - Weight::from_ref_time(5_763_000 as u64) - // Standard Error: 1_248 - .saturating_add(Weight::from_ref_time(1_126_062 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(p as u64))) + // Proof Size summary in bytes: + // Measured: `115 + p * (69 ±0)` + // Estimated: `128 + p * (70 ±0)` + // Minimum execution time: 4_189_000 picoseconds. + Weight::from_parts(4_270_000, 128) + // Standard Error: 2_296 + .saturating_add(Weight::from_parts(1_389_650, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(p.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(p.into()))) + .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) } } @@ -113,51 +155,89 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { /// The range of component `b` is `[0, 3932160]`. fn remark(b: u32, ) -> Weight { - // Minimum execution time: 3_951 nanoseconds. - Weight::from_ref_time(1_307_232 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_004_000 picoseconds. + Weight::from_parts(2_119_000, 0) // Standard Error: 0 - .saturating_add(Weight::from_ref_time(363 as u64).saturating_mul(b as u64)) + .saturating_add(Weight::from_parts(390, 0).saturating_mul(b.into())) } /// The range of component `b` is `[0, 3932160]`. fn remark_with_event(b: u32, ) -> Weight { - // Minimum execution time: 14_880 nanoseconds. - Weight::from_ref_time(15_173_000 as u64) - // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_424 as u64).saturating_mul(b as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_032_000 picoseconds. + Weight::from_parts(8_097_000, 0) + // Standard Error: 2 + .saturating_add(Weight::from_parts(1_455, 0).saturating_mul(b.into())) } - // Storage: System Digest (r:1 w:1) - // Storage: unknown [0x3a686561707061676573] (r:0 w:1) + /// Storage: System Digest (r:1 w:1) + /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: unknown `0x3a686561707061676573` (r:0 w:1) + /// Proof Skipped: unknown `0x3a686561707061676573` (r:0 w:1) fn set_heap_pages() -> Weight { - // Minimum execution time: 9_819 nanoseconds. - Weight::from_ref_time(10_513_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1485` + // Minimum execution time: 4_446_000 picoseconds. + Weight::from_parts(4_782_000, 1485) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: System Digest (r:1 w:1) + /// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: unknown `0x3a636f6465` (r:0 w:1) + /// Proof Skipped: unknown `0x3a636f6465` (r:0 w:1) + fn set_code() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `1485` + // Minimum execution time: 84_000_503_000 picoseconds. + Weight::from_parts(87_586_619_000, 1485) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Skipped Metadata (r:0 w:0) + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) /// The range of component `i` is `[0, 1000]`. fn set_storage(i: u32, ) -> Weight { - // Minimum execution time: 4_038 nanoseconds. - Weight::from_ref_time(4_098_000 as u64) - // Standard Error: 710 - .saturating_add(Weight::from_ref_time(620_813 as u64).saturating_mul(i as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64))) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_086_000 picoseconds. + Weight::from_parts(2_175_000, 0) + // Standard Error: 1_056 + .saturating_add(Weight::from_parts(841_511, 0).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(i.into()))) } - // Storage: Skipped Metadata (r:0 w:0) + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) /// The range of component `i` is `[0, 1000]`. fn kill_storage(i: u32, ) -> Weight { - // Minimum execution time: 3_972 nanoseconds. - Weight::from_ref_time(4_082_000 as u64) - // Standard Error: 884 - .saturating_add(Weight::from_ref_time(536_923 as u64).saturating_mul(i as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64))) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(2_255_000, 0) + // Standard Error: 1_425 + .saturating_add(Weight::from_parts(662_473, 0).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(i.into()))) } - // Storage: Skipped Metadata (r:0 w:0) + /// Storage: Skipped Metadata (r:0 w:0) + /// Proof Skipped: Skipped Metadata (max_values: None, max_size: None, mode: Measured) /// The range of component `p` is `[0, 1000]`. fn kill_prefix(p: u32, ) -> Weight { - // Minimum execution time: 5_703 nanoseconds. - Weight::from_ref_time(5_763_000 as u64) - // Standard Error: 1_248 - .saturating_add(Weight::from_ref_time(1_126_062 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(p as u64))) + // Proof Size summary in bytes: + // Measured: `115 + p * (69 ±0)` + // Estimated: `128 + p * (70 ±0)` + // Minimum execution time: 4_189_000 picoseconds. + Weight::from_parts(4_270_000, 128) + // Standard Error: 2_296 + .saturating_add(Weight::from_parts(1_389_650, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(p.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(p.into()))) + .saturating_add(Weight::from_parts(0, 70).saturating_mul(p.into())) } } diff --git a/frame/timestamp/Cargo.toml b/frame/timestamp/Cargo.toml index 84c56da24e9d..6a84381b1784 100644 --- a/frame/timestamp/Cargo.toml +++ b/frame/timestamp/Cargo.toml @@ -14,21 +14,21 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive", "max-encoded-len"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } -sp-io = { version = "7.0.0", default-features = false, optional = true, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, optional = true, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../primitives/timestamp" } [dev-dependencies] -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/timestamp/src/benchmarking.rs b/frame/timestamp/src/benchmarking.rs index 8974eb95692a..8a7febed4d12 100644 --- a/frame/timestamp/src/benchmarking.rs +++ b/frame/timestamp/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::{benchmarks, TrackedStorageKey}; +use frame_benchmarking::v1::{benchmarks, TrackedStorageKey}; use frame_support::{ensure, traits::OnFinalize}; use frame_system::RawOrigin; diff --git a/frame/timestamp/src/lib.rs b/frame/timestamp/src/lib.rs index e859474c2cb9..4eb95941d782 100644 --- a/frame/timestamp/src/lib.rs +++ b/frame/timestamp/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -130,7 +130,7 @@ pub mod pallet { type Moment: Parameter + Default + AtLeast32Bit - + Scale + + Scale, Output = Self::Moment> + Copy + MaxEncodedLen + scale_info::StaticTypeInfo; @@ -151,8 +151,7 @@ pub mod pallet { } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - pub struct Pallet(PhantomData); + pub struct Pallet(_); /// Current time for the current block. #[pallet::storage] @@ -171,10 +170,8 @@ pub mod pallet { T::WeightInfo::on_finalize() } - /// # + /// ## Complexity /// - `O(1)` - /// - 1 storage deletion (codec `O(1)`). - /// # fn on_finalize(_n: BlockNumberFor) { assert!(DidUpdate::::take(), "Timestamp must be updated once in the block"); } @@ -192,12 +189,11 @@ pub mod pallet { /// /// The dispatch origin for this call must be `Inherent`. /// - /// # + /// ## Complexity /// - `O(1)` (Note that implementations of `OnTimestampSet` must also be `O(1)`) /// - 1 storage read and 1 storage mutation (codec `O(1)`). (because of `DidUpdate::take` in /// `on_finalize`) /// - 1 event handler `on_timestamp_set`. Must be `O(1)`. - /// # #[pallet::call_index(0)] #[pallet::weight(( T::WeightInfo::set(), @@ -258,7 +254,7 @@ pub mod pallet { if t > *(data + MAX_TIMESTAMP_DRIFT_MILLIS) { Err(InherentError::TooFarInFuture) } else if t < minimum { - Err(InherentError::ValidAtTimestamp(minimum.into())) + Err(InherentError::TooEarly) } else { Ok(()) } diff --git a/frame/timestamp/src/mock.rs b/frame/timestamp/src/mock.rs index e1ba5cd11e61..418d257b3f00 100644 --- a/frame/timestamp/src/mock.rs +++ b/frame/timestamp/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,21 +27,17 @@ use frame_support::{ use sp_core::H256; use sp_io::TestExternalities; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; type Moment = u64; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent}, } ); @@ -52,14 +48,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -100,7 +95,7 @@ pub(crate) fn get_captured_moment() -> Option { } pub(crate) fn new_test_ext() -> TestExternalities { - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); clear_captured_moment(); TestExternalities::new(t) } diff --git a/frame/timestamp/src/tests.rs b/frame/timestamp/src/tests.rs index 6a76fbc4820e..317631eeb704 100644 --- a/frame/timestamp/src/tests.rs +++ b/frame/timestamp/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/timestamp/src/weights.rs b/frame/timestamp/src/weights.rs index 52123920977d..46c544734869 100644 --- a/frame/timestamp/src/weights.rs +++ b/frame/timestamp/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_timestamp //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_timestamp +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_timestamp. pub trait WeightInfo { @@ -54,32 +59,48 @@ pub trait WeightInfo { /// Weights for pallet_timestamp using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Timestamp Now (r:1 w:1) - // Storage: Babe CurrentSlot (r:1 w:0) + /// Storage: Timestamp Now (r:1 w:1) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Babe CurrentSlot (r:1 w:0) + /// Proof: Babe CurrentSlot (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) fn set() -> Weight { - // Minimum execution time: 11_331 nanoseconds. - Weight::from_ref_time(11_584_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `312` + // Estimated: `1493` + // Minimum execution time: 9_857_000 picoseconds. + Weight::from_parts(10_492_000, 1493) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } fn on_finalize() -> Weight { - // Minimum execution time: 5_280 nanoseconds. - Weight::from_ref_time(5_412_000 as u64) + // Proof Size summary in bytes: + // Measured: `161` + // Estimated: `0` + // Minimum execution time: 4_175_000 picoseconds. + Weight::from_parts(4_334_000, 0) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Timestamp Now (r:1 w:1) - // Storage: Babe CurrentSlot (r:1 w:0) + /// Storage: Timestamp Now (r:1 w:1) + /// Proof: Timestamp Now (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) + /// Storage: Babe CurrentSlot (r:1 w:0) + /// Proof: Babe CurrentSlot (max_values: Some(1), max_size: Some(8), added: 503, mode: MaxEncodedLen) fn set() -> Weight { - // Minimum execution time: 11_331 nanoseconds. - Weight::from_ref_time(11_584_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `312` + // Estimated: `1493` + // Minimum execution time: 9_857_000 picoseconds. + Weight::from_parts(10_492_000, 1493) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } fn on_finalize() -> Weight { - // Minimum execution time: 5_280 nanoseconds. - Weight::from_ref_time(5_412_000 as u64) + // Proof Size summary in bytes: + // Measured: `161` + // Estimated: `0` + // Minimum execution time: 4_175_000 picoseconds. + Weight::from_parts(4_334_000, 0) } } diff --git a/frame/tips/Cargo.toml b/frame/tips/Cargo.toml index c7db61613c03..e7c1819661eb 100644 --- a/frame/tips/Cargo.toml +++ b/frame/tips/Cargo.toml @@ -13,22 +13,22 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", features = ["derive"], optional = true } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"], optional = true } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../treasury" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-storage = { version = "7.0.0", path = "../../primitives/storage" } +sp-storage = { version = "13.0.0", path = "../../primitives/storage" } [features] default = ["std"] diff --git a/frame/tips/src/benchmarking.rs b/frame/tips/src/benchmarking.rs index 312424e5799e..4a991b11b933 100644 --- a/frame/tips/src/benchmarking.rs +++ b/frame/tips/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,9 @@ #![cfg(feature = "runtime-benchmarks")] -use frame_benchmarking::{account, benchmarks_instance_pallet, whitelisted_caller}; +use frame_benchmarking::v1::{ + account, benchmarks_instance_pallet, whitelisted_caller, BenchmarkError, +}; use frame_support::ensure; use frame_system::RawOrigin; use sp_runtime::traits::Saturating; @@ -76,7 +78,7 @@ fn create_tips, I: 'static>( } Tips::::mutate(hash, |maybe_tip| { if let Some(open_tip) = maybe_tip { - open_tip.closes = Some(T::BlockNumber::zero()); + open_tip.closes = Some(frame_system::pallet_prelude::BlockNumberFor::::zero()); } }); Ok(()) @@ -196,7 +198,8 @@ benchmarks_instance_pallet! { let reason_hash = T::Hashing::hash(&reason[..]); let hash = T::Hashing::hash_of(&(&reason_hash, &beneficiary)); ensure!(Tips::::contains_key(hash), "tip does not exist"); - let reject_origin = T::RejectOrigin::successful_origin(); + let reject_origin = + T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: _(reject_origin, hash) impl_benchmark_test_suite!(TipsMod, crate::tests::new_test_ext(), crate::tests::Test); diff --git a/frame/tips/src/lib.rs b/frame/tips/src/lib.rs index 43002c7b5f19..6e8f72e0540e 100644 --- a/frame/tips/src/lib.rs +++ b/frame/tips/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -74,6 +74,7 @@ use frame_support::{ }, Parameter, }; +use frame_system::pallet_prelude::BlockNumberFor; pub use pallet::*; pub use weights::WeightInfo; @@ -121,7 +122,6 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] #[pallet::without_storage_info] pub struct Pallet(_); @@ -144,7 +144,7 @@ pub mod pallet { /// The period for which a tip remains open after is has achieved threshold tippers. #[pallet::constant] - type TipCountdown: Get; + type TipCountdown: Get>; /// The percent of the final tip which goes to the original reporter of the tip. #[pallet::constant] @@ -174,7 +174,7 @@ pub mod pallet { _, Twox64Concat, T::Hash, - OpenTip, T::BlockNumber, T::Hash>, + OpenTip, BlockNumberFor, T::Hash>, OptionQuery, >; @@ -231,12 +231,9 @@ pub mod pallet { /// /// Emits `NewTip` if successful. /// - /// # - /// - Complexity: `O(R)` where `R` length of `reason`. + /// ## Complexity + /// - `O(R)` where `R` length of `reason`. /// - encoding and hashing of 'reason' - /// - DbReads: `Reasons`, `Tips` - /// - DbWrites: `Reasons`, `Tips` - /// # #[pallet::call_index(0)] #[pallet::weight(>::WeightInfo::report_awesome(reason.len() as u32))] pub fn report_awesome( @@ -289,12 +286,9 @@ pub mod pallet { /// /// Emits `TipRetracted` if successful. /// - /// # - /// - Complexity: `O(1)` + /// ## Complexity + /// - `O(1)` /// - Depends on the length of `T::Hash` which is fixed. - /// - DbReads: `Tips`, `origin account` - /// - DbWrites: `Reasons`, `Tips`, `origin account` - /// # #[pallet::call_index(1)] #[pallet::weight(>::WeightInfo::retract_tip())] pub fn retract_tip(origin: OriginFor, hash: T::Hash) -> DispatchResult { @@ -325,15 +319,12 @@ pub mod pallet { /// /// Emits `NewTip` if successful. /// - /// # - /// - Complexity: `O(R + T)` where `R` length of `reason`, `T` is the number of tippers. + /// ## Complexity + /// - `O(R + T)` where `R` length of `reason`, `T` is the number of tippers. /// - `O(T)`: decoding `Tipper` vec of length `T`. `T` is charged as upper bound given by /// `ContainsLengthBound`. The actual cost depends on the implementation of /// `T::Tippers`. /// - `O(R)`: hashing and encoding of reason of length `R` - /// - DbReads: `Tippers`, `Reasons` - /// - DbWrites: `Reasons`, `Tips` - /// # #[pallet::call_index(2)] #[pallet::weight(>::WeightInfo::tip_new(reason.len() as u32, T::Tippers::max_len() as u32))] pub fn tip_new( @@ -379,16 +370,13 @@ pub mod pallet { /// Emits `TipClosing` if the threshold of tippers has been reached and the countdown period /// has started. /// - /// # - /// - Complexity: `O(T)` where `T` is the number of tippers. decoding `Tipper` vec of length - /// `T`, insert tip and check closing, `T` is charged as upper bound given by - /// `ContainsLengthBound`. The actual cost depends on the implementation of `T::Tippers`. + /// ## Complexity + /// - `O(T)` where `T` is the number of tippers. decoding `Tipper` vec of length `T`, insert + /// tip and check closing, `T` is charged as upper bound given by `ContainsLengthBound`. + /// The actual cost depends on the implementation of `T::Tippers`. /// /// Actually weight could be lower as it depends on how many tips are in `OpenTip` but it /// is weighted as if almost full i.e of length `T-1`. - /// - DbReads: `Tippers`, `Tips` - /// - DbWrites: `Tips` - /// # #[pallet::call_index(3)] #[pallet::weight(>::WeightInfo::tip(T::Tippers::max_len() as u32))] pub fn tip( @@ -416,13 +404,10 @@ pub mod pallet { /// - `hash`: The identity of the open tip for which a tip value is declared. This is formed /// as the hash of the tuple of the original tip `reason` and the beneficiary account ID. /// - /// # - /// - Complexity: `O(T)` where `T` is the number of tippers. decoding `Tipper` vec of length - /// `T`. `T` is charged as upper bound given by `ContainsLengthBound`. The actual cost - /// depends on the implementation of `T::Tippers`. - /// - DbReads: `Tips`, `Tippers`, `tip finder` - /// - DbWrites: `Reasons`, `Tips`, `Tippers`, `tip finder` - /// # + /// ## Complexity + /// - : `O(T)` where `T` is the number of tippers. decoding `Tipper` vec of length `T`. `T` + /// is charged as upper bound given by `ContainsLengthBound`. The actual cost depends on + /// the implementation of `T::Tippers`. #[pallet::call_index(4)] #[pallet::weight(>::WeightInfo::close_tip(T::Tippers::max_len() as u32))] pub fn close_tip(origin: OriginFor, hash: T::Hash) -> DispatchResult { @@ -446,10 +431,8 @@ pub mod pallet { /// /// Emits `TipSlashed` if successful. /// - /// # - /// `T` is charged as upper bound given by `ContainsLengthBound`. - /// The actual cost depends on the implementation of `T::Tippers`. - /// # + /// ## Complexity + /// - O(1). #[pallet::call_index(5)] #[pallet::weight(>::WeightInfo::slash_tip(T::Tippers::max_len() as u32))] pub fn slash_tip(origin: OriginFor, hash: T::Hash) -> DispatchResult { @@ -488,7 +471,7 @@ impl, I: 'static> Pallet { /// /// `O(T)` and one storage access. fn insert_tip_and_check_closing( - tip: &mut OpenTip, T::BlockNumber, T::Hash>, + tip: &mut OpenTip, BlockNumberFor, T::Hash>, tipper: T::AccountId, tip_value: BalanceOf, ) -> bool { @@ -533,7 +516,7 @@ impl, I: 'static> Pallet { /// Plus `O(T)` (`T` is Tippers length). fn payout_tip( hash: T::Hash, - tip: OpenTip, T::BlockNumber, T::Hash>, + tip: OpenTip, BlockNumberFor, T::Hash>, ) { let mut tips = tip.tips; Self::retain_active_tips(&mut tips); @@ -595,7 +578,7 @@ impl, I: 'static> Pallet { for (hash, old_tip) in storage_key_iter::< T::Hash, - OldOpenTip, T::BlockNumber, T::Hash>, + OldOpenTip, BlockNumberFor, T::Hash>, Twox64Concat, >(module, item) .drain() diff --git a/frame/tips/src/migrations/mod.rs b/frame/tips/src/migrations/mod.rs index 719bb2f86fdd..9cdd01c17fbf 100644 --- a/frame/tips/src/migrations/mod.rs +++ b/frame/tips/src/migrations/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,3 +21,6 @@ /// before calling this migration. After calling this migration, it will get replaced with /// own storage identifier. pub mod v4; + +/// A migration that unreserves all funds held in the context of this pallet. +pub mod unreserve_deposits; diff --git a/frame/tips/src/migrations/unreserve_deposits.rs b/frame/tips/src/migrations/unreserve_deposits.rs new file mode 100644 index 000000000000..878d80fe1bb4 --- /dev/null +++ b/frame/tips/src/migrations/unreserve_deposits.rs @@ -0,0 +1,254 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A migration that unreserves all deposit and unlocks all stake held in the context of this +//! pallet. + +use core::iter::Sum; +use frame_support::traits::{OnRuntimeUpgrade, ReservableCurrency}; +use pallet_treasury::BalanceOf; +use sp_runtime::{traits::Zero, Saturating}; +use sp_std::collections::btree_map::BTreeMap; + +#[cfg(feature = "try-runtime")] +use sp_std::vec::Vec; + +/// A migration that unreserves all tip deposits. +/// +/// Useful to prevent funds from being locked up when the pallet is deprecated. +/// +/// The pallet should be made inoperable before or immediately after this migration is run. +/// +/// (See also the `RemovePallet` migration in `frame/support/src/migrations.rs`) +pub struct UnreserveDeposits, I: 'static>(sp_std::marker::PhantomData<(T, I)>); + +impl, I: 'static> UnreserveDeposits { + /// Calculates and returns the total amount reserved by each account by this pallet from open + /// tips. + /// + /// # Returns + /// + /// * `BTreeMap`: Map of account IDs to their respective total + /// reserved balance by this pallet + /// * `frame_support::weights::Weight`: The weight of this operation. + fn get_deposits() -> (BTreeMap>, frame_support::weights::Weight) { + use frame_support::traits::Get; + + let mut tips_len = 0; + let account_deposits: BTreeMap> = crate::Tips::::iter() + .map(|(_hash, open_tip)| open_tip) + .fold(BTreeMap::new(), |mut acc, tip| { + // Count the total number of tips + tips_len.saturating_accrue(1); + + // Add the balance to the account's existing deposit in the accumulator + acc.entry(tip.finder).or_insert(Zero::zero()).saturating_accrue(tip.deposit); + acc + }); + + (account_deposits, T::DbWeight::get().reads(tips_len)) + } +} + +impl, I: 'static> OnRuntimeUpgrade for UnreserveDeposits +where + BalanceOf: Sum, +{ + /// Gets the actual reserved amount for each account before the migration, performs integrity + /// checks and prints some summary information. + /// + /// Steps: + /// 1. Gets the deposited balances for each account stored in this pallet. + /// 2. Collects actual pre-migration reserved balances for each account. + /// 3. Checks the integrity of the deposited balances. + /// 4. Prints summary statistics about the state to be migrated. + /// 5. Returns the pre-migration actual reserved balance for each account that will + /// be part of the migration. + /// + /// Fails with a `TryRuntimeError` if somehow the amount reserved by this pallet is greater than + /// the actual total reserved amount for any accounts. + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + use codec::Encode; + use frame_support::ensure; + + // Get the Tips pallet view of balances it has reserved + let (account_deposits, _) = Self::get_deposits(); + + // Get the actual amounts reserved for accounts with open tips + let account_reserved_before: BTreeMap> = account_deposits + .keys() + .map(|account| (account.clone(), T::Currency::reserved_balance(&account))) + .collect(); + + // The deposit amount must be less than or equal to the reserved amount. + // If it is higher, there is either a bug with the pallet or a bug in the calculation of the + // deposit amount. + ensure!( + account_deposits.iter().all(|(account, deposit)| *deposit <= + *account_reserved_before.get(account).unwrap_or(&Zero::zero())), + "Deposit amount is greater than reserved amount" + ); + + // Print some summary stats + let total_deposits_to_unreserve = + account_deposits.clone().into_values().sum::>(); + log::info!("Total accounts: {}", account_deposits.keys().count()); + log::info!("Total amount to unreserve: {:?}", total_deposits_to_unreserve); + + // Return the actual amount reserved before the upgrade to verify integrity of the upgrade + // in the post_upgrade hook. + Ok(account_reserved_before.encode()) + } + + /// Executes the migration, unreserving funds that are locked in Tip deposits. + fn on_runtime_upgrade() -> frame_support::weights::Weight { + use frame_support::traits::Get; + + // Get staked and deposited balances as reported by this pallet. + let (account_deposits, initial_reads) = Self::get_deposits(); + + // Deposited funds need to be unreserved. + for (account, unreserve_amount) in account_deposits.iter() { + if unreserve_amount.is_zero() { + continue + } + T::Currency::unreserve(&account, *unreserve_amount); + } + + T::DbWeight::get() + .reads_writes(account_deposits.len() as u64, account_deposits.len() as u64) + .saturating_add(initial_reads) + } + + /// Verifies that the account reserved balances were reduced by the actual expected amounts. + #[cfg(feature = "try-runtime")] + fn post_upgrade( + account_reserved_before_bytes: Vec, + ) -> Result<(), sp_runtime::TryRuntimeError> { + use codec::Decode; + + let account_reserved_before = BTreeMap::>::decode( + &mut &account_reserved_before_bytes[..], + ) + .map_err(|_| "Failed to decode account_reserved_before_bytes")?; + + // Get deposited balances as reported by this pallet. + let (account_deposits, _) = Self::get_deposits(); + + // Check that the reserved balance is reduced by the expected deposited amount. + for (account, actual_reserved_before) in account_reserved_before { + let actual_reserved_after = T::Currency::reserved_balance(&account); + let expected_amount_deducted = *account_deposits + .get(&account) + .expect("account deposit must exist to be in account_reserved_before, qed"); + let expected_reserved_after = + actual_reserved_before.saturating_sub(expected_amount_deducted); + + if actual_reserved_after != expected_reserved_after { + log::error!( + "Reserved balance for {:?} is incorrect. actual before: {:?}, actual after, {:?}, expected deducted: {:?}", + account, + actual_reserved_before, + actual_reserved_after, + expected_amount_deducted + ); + return Err("Reserved balance is incorrect".into()) + } + } + + Ok(()) + } +} + +#[cfg(all(feature = "try-runtime", test))] +mod test { + use super::*; + use crate::{ + migrations::unreserve_deposits::UnreserveDeposits, + tests::{new_test_ext, RuntimeOrigin, Test, Tips}, + }; + use frame_support::{assert_ok, traits::TypedGet}; + + #[test] + fn unreserve_all_funds_works() { + let tipper_0 = 0; + let tipper_1 = 1; + let tipper_0_initial_reserved = 0; + let tipper_1_initial_reserved = 5; + let recipient = 100; + let tip_0_reason = b"what_is_really_not_awesome".to_vec(); + let tip_1_reason = b"pineapple_on_pizza".to_vec(); + new_test_ext().execute_with(|| { + // Set up + assert_ok!(::Currency::reserve( + &tipper_0, + tipper_0_initial_reserved + )); + assert_ok!(::Currency::reserve( + &tipper_1, + tipper_1_initial_reserved + )); + + // Make some tips + assert_ok!(Tips::report_awesome( + RuntimeOrigin::signed(tipper_0), + tip_0_reason.clone(), + recipient + )); + assert_ok!(Tips::report_awesome( + RuntimeOrigin::signed(tipper_1), + tip_1_reason.clone(), + recipient + )); + + // Verify the expected amount is reserved + assert_eq!( + ::Currency::reserved_balance(&tipper_0), + tipper_0_initial_reserved + + ::TipReportDepositBase::get() + + ::DataDepositPerByte::get() * + tip_0_reason.len() as u64 + ); + assert_eq!( + ::Currency::reserved_balance(&tipper_1), + tipper_1_initial_reserved + + ::TipReportDepositBase::get() + + ::DataDepositPerByte::get() * + tip_1_reason.len() as u64 + ); + + // Execute the migration + let bytes = match UnreserveDeposits::::pre_upgrade() { + Ok(bytes) => bytes, + Err(e) => panic!("pre_upgrade failed: {:?}", e), + }; + UnreserveDeposits::::on_runtime_upgrade(); + assert_ok!(UnreserveDeposits::::post_upgrade(bytes)); + + // Check the deposits were were unreserved + assert_eq!( + ::Currency::reserved_balance(&tipper_0), + tipper_0_initial_reserved + ); + assert_eq!( + ::Currency::reserved_balance(&tipper_1), + tipper_1_initial_reserved + ); + }); + } +} diff --git a/frame/tips/src/migrations/v4.rs b/frame/tips/src/migrations/v4.rs index 0107e96a4116..35569633d1bb 100644 --- a/frame/tips/src/migrations/v4.rs +++ b/frame/tips/src/migrations/v4.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/tips/src/tests.rs b/frame/tips/src/tests.rs index cb0b4458c7fb..a700892d4270 100644 --- a/frame/tips/src/tests.rs +++ b/frame/tips/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,16 +21,13 @@ use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BadOrigin, BlakeTwo256, IdentityLookup}, BuildStorage, Perbill, Permill, }; use sp_storage::Storage; use frame_support::{ - assert_noop, assert_ok, - pallet_prelude::GenesisBuild, - parameter_types, + assert_noop, assert_ok, parameter_types, storage::StoragePrefixedMap, traits::{ConstU32, ConstU64, SortedMembers, StorageVersion}, PalletId, @@ -39,19 +36,15 @@ use frame_support::{ use super::*; use crate::{self as pallet_tips, Event as TipEvent}; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, - Treasury1: pallet_treasury::::{Pallet, Call, Storage, Config, Event}, + Treasury: pallet_treasury::{Pallet, Call, Storage, Config, Event}, + Treasury1: pallet_treasury::::{Pallet, Call, Storage, Config, Event}, Tips: pallet_tips::{Pallet, Call, Storage, Event}, Tips1: pallet_tips::::{Pallet, Call, Storage, Event}, } @@ -66,14 +59,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u128; // u64 is not enough to hold bytes used to generate bounty account type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -97,6 +89,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } parameter_types! { static TenToFourteenTestValue: Vec = vec![10,11,12,13,14]; @@ -192,7 +188,7 @@ impl Config for Test { } pub fn new_test_ext() -> sp_io::TestExternalities { - let mut ext: sp_io::TestExternalities = GenesisConfig { + let mut ext: sp_io::TestExternalities = RuntimeGenesisConfig { system: frame_system::GenesisConfig::default(), balances: pallet_balances::GenesisConfig { balances: vec![(0, 100), (1, 98), (2, 1)] }, treasury: Default::default(), @@ -566,7 +562,7 @@ fn test_migration_v4() { #[test] fn genesis_funding_works() { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let initial_funding = 100; pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized with 100. @@ -574,7 +570,9 @@ fn genesis_funding_works() { } .assimilate_storage(&mut t) .unwrap(); - GenesisBuild::::assimilate_storage(&pallet_treasury::GenesisConfig, &mut t).unwrap(); + pallet_treasury::GenesisConfig::::default() + .assimilate_storage(&mut t) + .unwrap(); let mut t: sp_io::TestExternalities = t.into(); t.execute_with(|| { diff --git a/frame/tips/src/weights.rs b/frame/tips/src/weights.rs index 1aa3fd8fa2eb..ec6228667159 100644 --- a/frame/tips/src/weights.rs +++ b/frame/tips/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_tips //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_tips +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_tips. pub trait WeightInfo { @@ -58,146 +63,218 @@ pub trait WeightInfo { /// Weights for pallet_tips using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Tips Reasons (r:1 w:1) - // Storage: Tips Tips (r:1 w:1) + /// Storage: Tips Reasons (r:1 w:1) + /// Proof Skipped: Tips Reasons (max_values: None, max_size: None, mode: Measured) + /// Storage: Tips Tips (r:1 w:1) + /// Proof Skipped: Tips Tips (max_values: None, max_size: None, mode: Measured) /// The range of component `r` is `[0, 300]`. fn report_awesome(r: u32, ) -> Weight { - // Minimum execution time: 35_458 nanoseconds. - Weight::from_ref_time(36_920_009 as u64) - // Standard Error: 252 - .saturating_add(Weight::from_ref_time(1_835 as u64).saturating_mul(r as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `4` + // Estimated: `3469` + // Minimum execution time: 29_576_000 picoseconds. + Weight::from_parts(30_722_650, 3469) + // Standard Error: 192 + .saturating_add(Weight::from_parts(2_601, 0).saturating_mul(r.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Tips Tips (r:1 w:1) - // Storage: Tips Reasons (r:0 w:1) + /// Storage: Tips Tips (r:1 w:1) + /// Proof Skipped: Tips Tips (max_values: None, max_size: None, mode: Measured) + /// Storage: Tips Reasons (r:0 w:1) + /// Proof Skipped: Tips Reasons (max_values: None, max_size: None, mode: Measured) fn retract_tip() -> Weight { - // Minimum execution time: 34_322 nanoseconds. - Weight::from_ref_time(35_292_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `221` + // Estimated: `3686` + // Minimum execution time: 28_522_000 picoseconds. + Weight::from_parts(29_323_000, 3686) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Elections Members (r:1 w:0) - // Storage: Tips Reasons (r:1 w:1) - // Storage: Tips Tips (r:0 w:1) + /// Storage: Elections Members (r:1 w:0) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Tips Reasons (r:1 w:1) + /// Proof Skipped: Tips Reasons (max_values: None, max_size: None, mode: Measured) + /// Storage: Tips Tips (r:0 w:1) + /// Proof Skipped: Tips Tips (max_values: None, max_size: None, mode: Measured) /// The range of component `r` is `[0, 300]`. /// The range of component `t` is `[1, 13]`. fn tip_new(r: u32, t: u32, ) -> Weight { - // Minimum execution time: 26_691 nanoseconds. - Weight::from_ref_time(27_313_497 as u64) - // Standard Error: 141 - .saturating_add(Weight::from_ref_time(818 as u64).saturating_mul(r as u64)) - // Standard Error: 3_352 - .saturating_add(Weight::from_ref_time(108_557 as u64).saturating_mul(t as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `526 + t * (64 ±0)` + // Estimated: `3991 + t * (64 ±0)` + // Minimum execution time: 19_650_000 picoseconds. + Weight::from_parts(19_837_982, 3991) + // Standard Error: 151 + .saturating_add(Weight::from_parts(1_746, 0).saturating_mul(r.into())) + // Standard Error: 3_588 + .saturating_add(Weight::from_parts(102_359, 0).saturating_mul(t.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 64).saturating_mul(t.into())) } - // Storage: Elections Members (r:1 w:0) - // Storage: Tips Tips (r:1 w:1) + /// Storage: Elections Members (r:1 w:0) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Tips Tips (r:1 w:1) + /// Proof Skipped: Tips Tips (max_values: None, max_size: None, mode: Measured) /// The range of component `t` is `[1, 13]`. fn tip(t: u32, ) -> Weight { - // Minimum execution time: 17_464 nanoseconds. - Weight::from_ref_time(17_621_090 as u64) - // Standard Error: 3_702 - .saturating_add(Weight::from_ref_time(269_919 as u64).saturating_mul(t as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `747 + t * (112 ±0)` + // Estimated: `4212 + t * (112 ±0)` + // Minimum execution time: 15_641_000 picoseconds. + Weight::from_parts(15_745_460, 4212) + // Standard Error: 5_106 + .saturating_add(Weight::from_parts(229_475, 0).saturating_mul(t.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 112).saturating_mul(t.into())) } - // Storage: Tips Tips (r:1 w:1) - // Storage: Elections Members (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Tips Reasons (r:0 w:1) + /// Storage: Tips Tips (r:1 w:1) + /// Proof Skipped: Tips Tips (max_values: None, max_size: None, mode: Measured) + /// Storage: Elections Members (r:1 w:0) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Tips Reasons (r:0 w:1) + /// Proof Skipped: Tips Reasons (max_values: None, max_size: None, mode: Measured) /// The range of component `t` is `[1, 13]`. fn close_tip(t: u32, ) -> Weight { - // Minimum execution time: 52_221 nanoseconds. - Weight::from_ref_time(53_168_303 as u64) - // Standard Error: 6_591 - .saturating_add(Weight::from_ref_time(243_706 as u64).saturating_mul(t as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `786 + t * (112 ±0)` + // Estimated: `4242 + t * (112 ±0)` + // Minimum execution time: 62_059_000 picoseconds. + Weight::from_parts(64_604_554, 4242) + // Standard Error: 11_818 + .saturating_add(Weight::from_parts(116_297, 0).saturating_mul(t.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 112).saturating_mul(t.into())) } - // Storage: Tips Tips (r:1 w:1) - // Storage: Tips Reasons (r:0 w:1) + /// Storage: Tips Tips (r:1 w:1) + /// Proof Skipped: Tips Tips (max_values: None, max_size: None, mode: Measured) + /// Storage: Tips Reasons (r:0 w:1) + /// Proof Skipped: Tips Reasons (max_values: None, max_size: None, mode: Measured) /// The range of component `t` is `[1, 13]`. fn slash_tip(t: u32, ) -> Weight { - // Minimum execution time: 22_911 nanoseconds. - Weight::from_ref_time(23_750_488 as u64) - // Standard Error: 2_561 - .saturating_add(Weight::from_ref_time(12_282 as u64).saturating_mul(t as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `269` + // Estimated: `3734` + // Minimum execution time: 14_133_000 picoseconds. + Weight::from_parts(14_957_547, 3734) + // Standard Error: 2_765 + .saturating_add(Weight::from_parts(22_138, 0).saturating_mul(t.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Tips Reasons (r:1 w:1) - // Storage: Tips Tips (r:1 w:1) + /// Storage: Tips Reasons (r:1 w:1) + /// Proof Skipped: Tips Reasons (max_values: None, max_size: None, mode: Measured) + /// Storage: Tips Tips (r:1 w:1) + /// Proof Skipped: Tips Tips (max_values: None, max_size: None, mode: Measured) /// The range of component `r` is `[0, 300]`. fn report_awesome(r: u32, ) -> Weight { - // Minimum execution time: 35_458 nanoseconds. - Weight::from_ref_time(36_920_009 as u64) - // Standard Error: 252 - .saturating_add(Weight::from_ref_time(1_835 as u64).saturating_mul(r as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `4` + // Estimated: `3469` + // Minimum execution time: 29_576_000 picoseconds. + Weight::from_parts(30_722_650, 3469) + // Standard Error: 192 + .saturating_add(Weight::from_parts(2_601, 0).saturating_mul(r.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Tips Tips (r:1 w:1) - // Storage: Tips Reasons (r:0 w:1) + /// Storage: Tips Tips (r:1 w:1) + /// Proof Skipped: Tips Tips (max_values: None, max_size: None, mode: Measured) + /// Storage: Tips Reasons (r:0 w:1) + /// Proof Skipped: Tips Reasons (max_values: None, max_size: None, mode: Measured) fn retract_tip() -> Weight { - // Minimum execution time: 34_322 nanoseconds. - Weight::from_ref_time(35_292_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `221` + // Estimated: `3686` + // Minimum execution time: 28_522_000 picoseconds. + Weight::from_parts(29_323_000, 3686) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Elections Members (r:1 w:0) - // Storage: Tips Reasons (r:1 w:1) - // Storage: Tips Tips (r:0 w:1) + /// Storage: Elections Members (r:1 w:0) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Tips Reasons (r:1 w:1) + /// Proof Skipped: Tips Reasons (max_values: None, max_size: None, mode: Measured) + /// Storage: Tips Tips (r:0 w:1) + /// Proof Skipped: Tips Tips (max_values: None, max_size: None, mode: Measured) /// The range of component `r` is `[0, 300]`. /// The range of component `t` is `[1, 13]`. fn tip_new(r: u32, t: u32, ) -> Weight { - // Minimum execution time: 26_691 nanoseconds. - Weight::from_ref_time(27_313_497 as u64) - // Standard Error: 141 - .saturating_add(Weight::from_ref_time(818 as u64).saturating_mul(r as u64)) - // Standard Error: 3_352 - .saturating_add(Weight::from_ref_time(108_557 as u64).saturating_mul(t as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `526 + t * (64 ±0)` + // Estimated: `3991 + t * (64 ±0)` + // Minimum execution time: 19_650_000 picoseconds. + Weight::from_parts(19_837_982, 3991) + // Standard Error: 151 + .saturating_add(Weight::from_parts(1_746, 0).saturating_mul(r.into())) + // Standard Error: 3_588 + .saturating_add(Weight::from_parts(102_359, 0).saturating_mul(t.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 64).saturating_mul(t.into())) } - // Storage: Elections Members (r:1 w:0) - // Storage: Tips Tips (r:1 w:1) + /// Storage: Elections Members (r:1 w:0) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: Tips Tips (r:1 w:1) + /// Proof Skipped: Tips Tips (max_values: None, max_size: None, mode: Measured) /// The range of component `t` is `[1, 13]`. fn tip(t: u32, ) -> Weight { - // Minimum execution time: 17_464 nanoseconds. - Weight::from_ref_time(17_621_090 as u64) - // Standard Error: 3_702 - .saturating_add(Weight::from_ref_time(269_919 as u64).saturating_mul(t as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `747 + t * (112 ±0)` + // Estimated: `4212 + t * (112 ±0)` + // Minimum execution time: 15_641_000 picoseconds. + Weight::from_parts(15_745_460, 4212) + // Standard Error: 5_106 + .saturating_add(Weight::from_parts(229_475, 0).saturating_mul(t.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 112).saturating_mul(t.into())) } - // Storage: Tips Tips (r:1 w:1) - // Storage: Elections Members (r:1 w:0) - // Storage: System Account (r:1 w:1) - // Storage: Tips Reasons (r:0 w:1) + /// Storage: Tips Tips (r:1 w:1) + /// Proof Skipped: Tips Tips (max_values: None, max_size: None, mode: Measured) + /// Storage: Elections Members (r:1 w:0) + /// Proof Skipped: Elections Members (max_values: Some(1), max_size: None, mode: Measured) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Tips Reasons (r:0 w:1) + /// Proof Skipped: Tips Reasons (max_values: None, max_size: None, mode: Measured) /// The range of component `t` is `[1, 13]`. fn close_tip(t: u32, ) -> Weight { - // Minimum execution time: 52_221 nanoseconds. - Weight::from_ref_time(53_168_303 as u64) - // Standard Error: 6_591 - .saturating_add(Weight::from_ref_time(243_706 as u64).saturating_mul(t as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `786 + t * (112 ±0)` + // Estimated: `4242 + t * (112 ±0)` + // Minimum execution time: 62_059_000 picoseconds. + Weight::from_parts(64_604_554, 4242) + // Standard Error: 11_818 + .saturating_add(Weight::from_parts(116_297, 0).saturating_mul(t.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 112).saturating_mul(t.into())) } - // Storage: Tips Tips (r:1 w:1) - // Storage: Tips Reasons (r:0 w:1) + /// Storage: Tips Tips (r:1 w:1) + /// Proof Skipped: Tips Tips (max_values: None, max_size: None, mode: Measured) + /// Storage: Tips Reasons (r:0 w:1) + /// Proof Skipped: Tips Reasons (max_values: None, max_size: None, mode: Measured) /// The range of component `t` is `[1, 13]`. fn slash_tip(t: u32, ) -> Weight { - // Minimum execution time: 22_911 nanoseconds. - Weight::from_ref_time(23_750_488 as u64) - // Standard Error: 2_561 - .saturating_add(Weight::from_ref_time(12_282 as u64).saturating_mul(t as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `269` + // Estimated: `3734` + // Minimum execution time: 14_133_000 picoseconds. + Weight::from_parts(14_957_547, 3734) + // Standard Error: 2_765 + .saturating_add(Weight::from_parts(22_138, 0).saturating_mul(t.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } } diff --git a/frame/transaction-payment/Cargo.toml b/frame/transaction-payment/Cargo.toml index 0c98796e4d1a..85592816b2b9 100644 --- a/frame/transaction-payment/Cargo.toml +++ b/frame/transaction-payment/Cargo.toml @@ -13,17 +13,17 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", optional = true } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", optional = true } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] serde_json = "1.0.85" diff --git a/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml b/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml new file mode 100644 index 000000000000..1555f4d91365 --- /dev/null +++ b/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml @@ -0,0 +1,47 @@ +[package] +name = "pallet-asset-conversion-tx-payment" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Pallet to manage transaction payments in assets by converting them to native assets." +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +# Substrate dependencies +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } +frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } +pallet-asset-conversion = { version = "4.0.0-dev", default-features = false, path = "../../asset-conversion" } +pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = ".." } +codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } + +[dev-dependencies] +sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../../primitives/io" } +sp-storage = { version = "13.0.0", default-features = false, path = "../../../primitives/storage" } +pallet-assets = { version = "4.0.0-dev", path = "../../assets" } +pallet-balances = { version = "4.0.0-dev", path = "../../balances" } + +[features] +default = ["std"] +std = [ + "scale-info/std", + "codec/std", + "sp-std/std", + "sp-runtime/std", + "frame-support/std", + "frame-system/std", + "sp-io/std", + "sp-core/std", + "pallet-asset-conversion/std", + "pallet-transaction-payment/std", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/transaction-payment/asset-conversion-tx-payment/README.md b/frame/transaction-payment/asset-conversion-tx-payment/README.md new file mode 100644 index 000000000000..eccba773673e --- /dev/null +++ b/frame/transaction-payment/asset-conversion-tx-payment/README.md @@ -0,0 +1,21 @@ +# pallet-asset-conversion-tx-payment + +## Asset Conversion Transaction Payment Pallet + +This pallet allows runtimes that include it to pay for transactions in assets other than the +native token of the chain. + +### Overview +It does this by extending transactions to include an optional `AssetId` that specifies the asset +to be used for payment (defaulting to the native token on `None`). It expects an +[`OnChargeAssetTransaction`] implementation analogously to [`pallet-transaction-payment`]. The +included [`AssetConversionAdapter`] (implementing [`OnChargeAssetTransaction`]) determines the fee +amount by converting the fee calculated by [`pallet-transaction-payment`] into the desired +asset. + +### Integration +This pallet wraps FRAME's transaction payment pallet and functions as a replacement. This means +you should include both pallets in your `construct_runtime` macro, but only include this +pallet's [`SignedExtension`] ([`ChargeAssetTxPayment`]). + +License: Apache-2.0 diff --git a/frame/transaction-payment/asset-conversion-tx-payment/src/lib.rs b/frame/transaction-payment/asset-conversion-tx-payment/src/lib.rs new file mode 100644 index 000000000000..efc7386ae8c0 --- /dev/null +++ b/frame/transaction-payment/asset-conversion-tx-payment/src/lib.rs @@ -0,0 +1,355 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Asset Conversion Transaction Payment Pallet +//! +//! This pallet allows runtimes that include it to pay for transactions in assets other than the +//! chain's native asset. +//! +//! ## Overview +//! +//! This pallet provides a `SignedExtension` with an optional `AssetId` that specifies the asset +//! to be used for payment (defaulting to the native token on `None`). It expects an +//! [`OnChargeAssetTransaction`] implementation analogous to [`pallet-transaction-payment`]. The +//! included [`AssetConversionAdapter`] (implementing [`OnChargeAssetTransaction`]) determines the +//! fee amount by converting the fee calculated by [`pallet-transaction-payment`] in the native +//! asset into the amount required of the specified asset. +//! +//! ## Pallet API +//! +//! This pallet does not have any dispatchable calls or storage. It wraps FRAME's Transaction +//! Payment pallet and functions as a replacement. This means you should include both pallets in +//! your `construct_runtime` macro, but only include this pallet's [`SignedExtension`] +//! ([`ChargeAssetTxPayment`]). +//! +//! ## Terminology +//! +//! - Native Asset or Native Currency: The asset that a chain considers native, as in its default +//! for transaction fee payment, deposits, inflation, etc. +//! - Other assets: Other assets that may exist on chain, for example under the Assets pallet. + +#![cfg_attr(not(feature = "std"), no_std)] + +use sp_std::prelude::*; + +use codec::{Decode, Encode}; +use frame_support::{ + dispatch::{DispatchInfo, DispatchResult, PostDispatchInfo}, + traits::{ + tokens::fungibles::{Balanced, Inspect}, + IsType, + }, + DefaultNoBound, +}; +use pallet_transaction_payment::OnChargeTransaction; +use scale_info::TypeInfo; +use sp_runtime::{ + traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SignedExtension, Zero}, + transaction_validity::{ + InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction, + }, + FixedPointOperand, +}; + +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +mod payment; +use frame_support::traits::tokens::AssetId; +use pallet_asset_conversion::MultiAssetIdConverter; +pub use payment::*; + +/// Type aliases used for interaction with `OnChargeTransaction`. +pub(crate) type OnChargeTransactionOf = + ::OnChargeTransaction; +/// Balance type alias for balances of the chain's native asset. +pub(crate) type BalanceOf = as OnChargeTransaction>::Balance; +/// Liquidity info type alias. +pub(crate) type LiquidityInfoOf = + as OnChargeTransaction>::LiquidityInfo; + +/// Balance type alias for balances of assets that implement the `fungibles` trait. +pub(crate) type AssetBalanceOf = + <::Fungibles as Inspect<::AccountId>>::Balance; +/// Type alias for Asset IDs. +pub(crate) type AssetIdOf = + <::Fungibles as Inspect<::AccountId>>::AssetId; + +/// Type alias for the interaction of balances with `OnChargeAssetTransaction`. +pub(crate) type ChargeAssetBalanceOf = + <::OnChargeAssetTransaction as OnChargeAssetTransaction>::Balance; +/// Type alias for Asset IDs in their interaction with `OnChargeAssetTransaction`. +pub(crate) type ChargeAssetIdOf = + <::OnChargeAssetTransaction as OnChargeAssetTransaction>::AssetId; +/// Liquidity info type alias for interaction with `OnChargeAssetTransaction`. +pub(crate) type ChargeAssetLiquidityOf = + <::OnChargeAssetTransaction as OnChargeAssetTransaction>::LiquidityInfo; + +/// Used to pass the initial payment info from pre- to post-dispatch. +#[derive(Encode, Decode, DefaultNoBound, TypeInfo)] +pub enum InitialPayment { + /// No initial fee was paid. + #[default] + Nothing, + /// The initial fee was paid in the native currency. + Native(LiquidityInfoOf), + /// The initial fee was paid in an asset. + Asset((LiquidityInfoOf, BalanceOf, AssetBalanceOf)), +} + +pub use pallet::*; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::config] + pub trait Config: + frame_system::Config + pallet_transaction_payment::Config + pallet_asset_conversion::Config + { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// The fungibles instance used to pay for transactions in assets. + type Fungibles: Balanced; + /// The actual transaction charging logic that charges the fees. + type OnChargeAssetTransaction: OnChargeAssetTransaction; + } + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// A transaction fee `actual_fee`, of which `tip` was added to the minimum inclusion fee, + /// has been paid by `who` in an asset `asset_id`. + AssetTxFeePaid { + who: T::AccountId, + actual_fee: AssetBalanceOf, + tip: BalanceOf, + asset_id: ChargeAssetIdOf, + }, + /// A swap of the refund in native currency back to asset failed. + AssetRefundFailed { native_amount_kept: BalanceOf }, + } +} + +/// Require payment for transaction inclusion and optionally include a tip to gain additional +/// priority in the queue. Allows paying via both `Currency` as well as `fungibles::Balanced`. +/// +/// Wraps the transaction logic in [`pallet_transaction_payment`] and extends it with assets. +/// An asset ID of `None` falls back to the underlying transaction payment logic via the native +/// currency. +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct ChargeAssetTxPayment { + #[codec(compact)] + tip: BalanceOf, + asset_id: Option>, +} + +impl ChargeAssetTxPayment +where + T::RuntimeCall: Dispatchable, + AssetBalanceOf: Send + Sync + FixedPointOperand, + BalanceOf: Send + + Sync + + FixedPointOperand + + Into> + + From>, + ChargeAssetIdOf: Send + Sync, +{ + /// Utility constructor. Used only in client/factory code. + pub fn from(tip: BalanceOf, asset_id: Option>) -> Self { + Self { tip, asset_id } + } + + /// Fee withdrawal logic that dispatches to either `OnChargeAssetTransaction` or + /// `OnChargeTransaction`. + fn withdraw_fee( + &self, + who: &T::AccountId, + call: &T::RuntimeCall, + info: &DispatchInfoOf, + len: usize, + ) -> Result<(BalanceOf, InitialPayment), TransactionValidityError> { + let fee = pallet_transaction_payment::Pallet::::compute_fee(len as u32, info, self.tip); + debug_assert!(self.tip <= fee, "tip should be included in the computed fee"); + if fee.is_zero() { + Ok((fee, InitialPayment::Nothing)) + } else if let Some(asset_id) = &self.asset_id { + T::OnChargeAssetTransaction::withdraw_fee( + who, + call, + info, + asset_id.clone(), + fee.into(), + self.tip.into(), + ) + .map(|(used_for_fee, received_exchanged, asset_consumed)| { + ( + fee, + InitialPayment::Asset(( + used_for_fee.into(), + received_exchanged.into(), + asset_consumed.into(), + )), + ) + }) + } else { + as OnChargeTransaction>::withdraw_fee( + who, call, info, fee, self.tip, + ) + .map(|i| (fee, InitialPayment::Native(i))) + .map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() }) + } + } +} + +impl sp_std::fmt::Debug for ChargeAssetTxPayment { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "ChargeAssetTxPayment<{:?}, {:?}>", self.tip, self.asset_id.encode()) + } + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +impl SignedExtension for ChargeAssetTxPayment +where + T::RuntimeCall: Dispatchable, + AssetBalanceOf: Send + Sync + FixedPointOperand, + BalanceOf: Send + + Sync + + From + + FixedPointOperand + + Into> + + Into> + + From>, + ChargeAssetIdOf: Send + Sync, +{ + const IDENTIFIER: &'static str = "ChargeAssetTxPayment"; + type AccountId = T::AccountId; + type Call = T::RuntimeCall; + type AdditionalSigned = (); + type Pre = ( + // tip + BalanceOf, + // who paid the fee + Self::AccountId, + // imbalance resulting from withdrawing the fee + InitialPayment, + // asset_id for the transaction payment + Option>, + ); + + fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> { + Ok(()) + } + + fn validate( + &self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> TransactionValidity { + use pallet_transaction_payment::ChargeTransactionPayment; + let (fee, _) = self.withdraw_fee(who, call, info, len)?; + let priority = ChargeTransactionPayment::::get_priority(info, len, self.tip, fee); + Ok(ValidTransaction { priority, ..Default::default() }) + } + + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &DispatchInfoOf, + len: usize, + ) -> Result { + let (_fee, initial_payment) = self.withdraw_fee(who, call, info, len)?; + Ok((self.tip, who.clone(), initial_payment, self.asset_id)) + } + + fn post_dispatch( + pre: Option, + info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, + len: usize, + result: &DispatchResult, + ) -> Result<(), TransactionValidityError> { + if let Some((tip, who, initial_payment, asset_id)) = pre { + match initial_payment { + InitialPayment::Native(already_withdrawn) => { + debug_assert!( + asset_id.is_none(), + "For that payment type the `asset_id` should be None" + ); + pallet_transaction_payment::ChargeTransactionPayment::::post_dispatch( + Some((tip, who, already_withdrawn)), + info, + post_info, + len, + result, + )?; + }, + InitialPayment::Asset(already_withdrawn) => { + debug_assert!( + asset_id.is_some(), + "For that payment type the `asset_id` should be set" + ); + let actual_fee = pallet_transaction_payment::Pallet::::compute_actual_fee( + len as u32, info, post_info, tip, + ); + + if let Some(asset_id) = asset_id { + let (used_for_fee, received_exchanged, asset_consumed) = already_withdrawn; + let converted_fee = T::OnChargeAssetTransaction::correct_and_deposit_fee( + &who, + info, + post_info, + actual_fee.into(), + tip.into(), + used_for_fee.into(), + received_exchanged.into(), + asset_id.clone(), + asset_consumed.into(), + )?; + + Pallet::::deposit_event(Event::::AssetTxFeePaid { + who, + actual_fee: converted_fee, + tip, + asset_id, + }); + } + }, + InitialPayment::Nothing => { + // `actual_fee` should be zero here for any signed extrinsic. It would be + // non-zero here in case of unsigned extrinsics as they don't pay fees but + // `compute_actual_fee` is not aware of them. In both cases it's fine to just + // move ahead without adjusting the fee, though, so we do nothing. + debug_assert!(tip.is_zero(), "tip should be zero if initial fee was zero."); + }, + } + } + + Ok(()) + } +} diff --git a/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs b/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs new file mode 100644 index 000000000000..bfbe8b4178ce --- /dev/null +++ b/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs @@ -0,0 +1,268 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use crate as pallet_asset_conversion_tx_payment; + +use codec; +use frame_support::{ + dispatch::DispatchClass, + instances::Instance2, + ord_parameter_types, + pallet_prelude::*, + parameter_types, + traits::{AsEnsureOriginWithArg, ConstU32, ConstU64, ConstU8, Imbalance, OnUnbalanced}, + weights::{Weight, WeightToFee as WeightToFeeT}, + PalletId, +}; +use frame_system as system; +use frame_system::{EnsureRoot, EnsureSignedBy}; +use pallet_asset_conversion::{NativeOrAssetId, NativeOrAssetIdConverter}; +use pallet_transaction_payment::CurrencyAdapter; +use sp_core::H256; +use sp_runtime::{ + traits::{AccountIdConversion, BlakeTwo256, IdentityLookup, SaturatedConversion}, + Permill, +}; + +type Block = frame_system::mocking::MockBlock; +type Balance = u64; +type AccountId = u64; + +frame_support::construct_runtime!( + pub enum Runtime + { + System: system, + Balances: pallet_balances, + TransactionPayment: pallet_transaction_payment, + Assets: pallet_assets, + PoolAssets: pallet_assets::, + AssetConversion: pallet_asset_conversion, + AssetTxPayment: pallet_asset_conversion_tx_payment, + } +); + +parameter_types! { + pub(crate) static ExtrinsicBaseWeight: Weight = Weight::zero(); +} + +pub struct BlockWeights; +impl Get for BlockWeights { + fn get() -> frame_system::limits::BlockWeights { + frame_system::limits::BlockWeights::builder() + .base_block(Weight::zero()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get().into(); + }) + .for_class(DispatchClass::non_mandatory(), |weights| { + weights.max_total = Weight::from_parts(1024, u64::MAX).into(); + }) + .build_or_panic() + } +} + +parameter_types! { + pub static WeightToFee: u64 = 1; + pub static TransactionByteFee: u64 = 1; +} + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = BlockWeights; + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +parameter_types! { + pub const ExistentialDeposit: u64 = 10; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU64<10>; + type AccountStore = System; + type MaxLocks = (); + type WeightInfo = (); + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); +} + +impl WeightToFeeT for WeightToFee { + type Balance = u64; + + fn weight_to_fee(weight: &Weight) -> Self::Balance { + Self::Balance::saturated_from(weight.ref_time()) + .saturating_mul(WEIGHT_TO_FEE.with(|v| *v.borrow())) + } +} + +impl WeightToFeeT for TransactionByteFee { + type Balance = u64; + + fn weight_to_fee(weight: &Weight) -> Self::Balance { + Self::Balance::saturated_from(weight.ref_time()) + .saturating_mul(TRANSACTION_BYTE_FEE.with(|v| *v.borrow())) + } +} + +parameter_types! { + pub(crate) static TipUnbalancedAmount: u64 = 0; + pub(crate) static FeeUnbalancedAmount: u64 = 0; +} + +pub struct DealWithFees; +impl OnUnbalanced> for DealWithFees { + fn on_unbalanceds( + mut fees_then_tips: impl Iterator>, + ) { + if let Some(fees) = fees_then_tips.next() { + FeeUnbalancedAmount::mutate(|a| *a += fees.peek()); + if let Some(tips) = fees_then_tips.next() { + TipUnbalancedAmount::mutate(|a| *a += tips.peek()); + } + } + } +} + +impl pallet_transaction_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnChargeTransaction = CurrencyAdapter; + type WeightToFee = WeightToFee; + type LengthToFee = TransactionByteFee; + type FeeMultiplierUpdate = (); + type OperationalFeeMultiplier = ConstU8<5>; +} + +type AssetId = u32; + +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = Balance; + type AssetId = AssetId; + type AssetIdParameter = codec::Compact; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = EnsureRoot; + type AssetDeposit = ConstU64<2>; + type AssetAccountDeposit = ConstU64<2>; + type MetadataDepositBase = ConstU64<0>; + type MetadataDepositPerByte = ConstU64<0>; + type ApprovalDeposit = ConstU64<0>; + type StringLimit = ConstU32<20>; + type Freezer = (); + type Extra = (); + type CallbackHandle = (); + type WeightInfo = (); + type RemoveItemsLimit = ConstU32<1000>; + pallet_assets::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } +} + +impl pallet_assets::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Balance = u64; + type RemoveItemsLimit = ConstU32<1000>; + type AssetId = u32; + type AssetIdParameter = u32; + type Currency = Balances; + type CreateOrigin = AsEnsureOriginWithArg>; + type ForceOrigin = frame_system::EnsureRoot; + type AssetDeposit = ConstU64<0>; + type AssetAccountDeposit = ConstU64<0>; + type MetadataDepositBase = ConstU64<0>; + type MetadataDepositPerByte = ConstU64<0>; + type ApprovalDeposit = ConstU64<0>; + type StringLimit = ConstU32<50>; + type Freezer = (); + type Extra = (); + type WeightInfo = (); + type CallbackHandle = (); + pallet_assets::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } +} + +parameter_types! { + pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon"); + pub storage AllowMultiAssetPools: bool = false; + // should be non-zero if AllowMultiAssetPools is true, otherwise can be zero + pub storage LiquidityWithdrawalFee: Permill = Permill::from_percent(0); + pub const MaxSwapPathLength: u32 = 4; +} + +ord_parameter_types! { + pub const AssetConversionOrigin: u64 = AccountIdConversion::::into_account_truncating(&AssetConversionPalletId::get()); +} + +impl pallet_asset_conversion::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type AssetBalance = ::Balance; + type AssetId = u32; + type PoolAssetId = u32; + type Assets = Assets; + type PoolAssets = PoolAssets; + type PalletId = AssetConversionPalletId; + type WeightInfo = (); + type LPFee = ConstU32<3>; // means 0.3% + type PoolSetupFee = ConstU64<100>; // should be more or equal to the existential deposit + type PoolSetupFeeReceiver = AssetConversionOrigin; + type LiquidityWithdrawalFee = LiquidityWithdrawalFee; + type AllowMultiAssetPools = AllowMultiAssetPools; + type MaxSwapPathLength = MaxSwapPathLength; + type MintMinLiquidity = ConstU64<100>; // 100 is good enough when the main currency has 12 decimals. + + type Balance = u64; + type HigherPrecisionBalance = u128; + + type MultiAssetId = NativeOrAssetId; + type MultiAssetIdConverter = NativeOrAssetIdConverter; + + pallet_asset_conversion::runtime_benchmarks_enabled! { + type BenchmarkHelper = (); + } +} + +impl Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Fungibles = Assets; + type OnChargeAssetTransaction = AssetConversionAdapter; +} diff --git a/frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs b/frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs new file mode 100644 index 000000000000..0d090211d035 --- /dev/null +++ b/frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs @@ -0,0 +1,202 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +///! Traits and default implementation for paying transaction fees in assets. +use super::*; +use crate::Config; + +use frame_support::{ + ensure, + traits::{fungible::Inspect, tokens::Balance}, + unsigned::TransactionValidityError, +}; +use pallet_asset_conversion::Swap; +use sp_runtime::{ + traits::{DispatchInfoOf, PostDispatchInfoOf, Zero}, + transaction_validity::InvalidTransaction, + Saturating, +}; +use sp_std::marker::PhantomData; + +/// Handle withdrawing, refunding and depositing of transaction fees. +pub trait OnChargeAssetTransaction { + /// The underlying integer type in which fees are calculated. + type Balance: Balance; + /// The type used to identify the assets used for transaction payment. + type AssetId: AssetId; + /// The type used to store the intermediate values between pre- and post-dispatch. + type LiquidityInfo; + + /// Secure the payment of the transaction fees before the transaction is executed. + /// + /// Note: The `fee` already includes the `tip`. + fn withdraw_fee( + who: &T::AccountId, + call: &T::RuntimeCall, + dispatch_info: &DispatchInfoOf, + asset_id: Self::AssetId, + fee: Self::Balance, + tip: Self::Balance, + ) -> Result< + (LiquidityInfoOf, Self::LiquidityInfo, AssetBalanceOf), + TransactionValidityError, + >; + + /// Refund any overpaid fees and deposit the corrected amount. + /// The actual fee gets calculated once the transaction is executed. + /// + /// Note: The `fee` already includes the `tip`. + /// + /// Returns the fee and tip in the asset used for payment as (fee, tip). + fn correct_and_deposit_fee( + who: &T::AccountId, + dispatch_info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, + corrected_fee: Self::Balance, + tip: Self::Balance, + fee_paid: LiquidityInfoOf, + received_exchanged: Self::LiquidityInfo, + asset_id: Self::AssetId, + initial_asset_consumed: AssetBalanceOf, + ) -> Result, TransactionValidityError>; +} + +/// Implements the asset transaction for a balance to asset converter (implementing [`Swap`]). +/// +/// The converter is given the complete fee in terms of the asset used for the transaction. +pub struct AssetConversionAdapter(PhantomData<(C, CON)>); + +/// Default implementation for a runtime instantiating this pallet, an asset to native swapper. +impl OnChargeAssetTransaction for AssetConversionAdapter +where + T: Config, + C: Inspect<::AccountId>, + CON: Swap, + T::HigherPrecisionBalance: From> + TryInto>, + T::MultiAssetId: From>, + BalanceOf: IsType<::AccountId>>::Balance>, +{ + type Balance = BalanceOf; + type AssetId = AssetIdOf; + type LiquidityInfo = BalanceOf; + + /// Swap & withdraw the predicted fee from the transaction origin. + /// + /// Note: The `fee` already includes the `tip`. + /// + /// Returns the total amount in native currency received by exchanging the `asset_id` and the + /// amount in native currency used to pay the fee. + fn withdraw_fee( + who: &T::AccountId, + call: &T::RuntimeCall, + info: &DispatchInfoOf, + asset_id: Self::AssetId, + fee: BalanceOf, + tip: BalanceOf, + ) -> Result< + (LiquidityInfoOf, Self::LiquidityInfo, AssetBalanceOf), + TransactionValidityError, + > { + // convert the asset into native currency + let ed = C::minimum_balance(); + let native_asset_required = + if C::balance(&who) >= ed.saturating_add(fee.into()) { fee } else { fee + ed.into() }; + + let asset_consumed = CON::swap_tokens_for_exact_tokens( + who.clone(), + vec![asset_id.into(), T::MultiAssetIdConverter::get_native()], + T::HigherPrecisionBalance::from(native_asset_required), + None, + who.clone(), + true, + ) + .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?; + + let asset_consumed = asset_consumed + .try_into() + .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?; + + ensure!(asset_consumed > Zero::zero(), InvalidTransaction::Payment); + + // charge the fee in native currency + ::withdraw_fee(who, call, info, fee, tip) + .map(|r| (r, native_asset_required, asset_consumed)) + } + + /// Correct the fee and swap the refund back to asset. + /// + /// Note: The `corrected_fee` already includes the `tip`. + /// Note: Is the ED wasn't needed, the `received_exchanged` will be equal to `fee_paid`, or + /// `fee_paid + ed` otherwise. + fn correct_and_deposit_fee( + who: &T::AccountId, + dispatch_info: &DispatchInfoOf, + post_info: &PostDispatchInfoOf, + corrected_fee: BalanceOf, + tip: BalanceOf, + fee_paid: LiquidityInfoOf, + received_exchanged: Self::LiquidityInfo, + asset_id: Self::AssetId, + initial_asset_consumed: AssetBalanceOf, + ) -> Result, TransactionValidityError> { + // Refund the native asset to the account that paid the fees (`who`). + // The `who` account will receive the "fee_paid - corrected_fee" refund. + ::correct_and_deposit_fee( + who, + dispatch_info, + post_info, + corrected_fee, + tip, + fee_paid, + )?; + + // calculate the refund in native asset, to swap back to the desired `asset_id` + let swap_back = received_exchanged.saturating_sub(corrected_fee); + let mut asset_refund = Zero::zero(); + if !swap_back.is_zero() { + // If this fails, the account might have dropped below the existential balance or there + // is not enough liquidity left in the pool. In that case we don't throw an error and + // the account will keep the native currency. + match CON::swap_exact_tokens_for_tokens( + who.clone(), // we already deposited the native to `who` + vec![ + T::MultiAssetIdConverter::get_native(), // we provide the native + asset_id.into(), // we want asset_id back + ], + T::HigherPrecisionBalance::from(swap_back), /* amount of the native asset to + * convert to `asset_id` */ + None, // no minimum amount back + who.clone(), // we will refund to `who` + false, // no need to keep alive + ) + .ok() + { + Some(acquired) => { + asset_refund = acquired + .try_into() + .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?; + }, + None => { + Pallet::::deposit_event(Event::::AssetRefundFailed { + native_amount_kept: swap_back, + }); + }, + } + } + + let actual_paid = initial_asset_consumed.saturating_sub(asset_refund); + Ok(actual_paid) + } +} diff --git a/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs b/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs new file mode 100644 index 000000000000..9e9b74a0ddb2 --- /dev/null +++ b/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs @@ -0,0 +1,708 @@ +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; + +use frame_support::{ + assert_ok, + dispatch::{DispatchInfo, PostDispatchInfo}, + pallet_prelude::*, + traits::{fungible::Inspect, fungibles::Mutate}, + weights::Weight, +}; +use frame_system as system; +use mock::{ExtrinsicBaseWeight, *}; +use pallet_asset_conversion::NativeOrAssetId; +use pallet_balances::Call as BalancesCall; +use sp_runtime::{traits::StaticLookup, BuildStorage}; + +const CALL: &::RuntimeCall = + &RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 2, value: 69 }); + +pub struct ExtBuilder { + balance_factor: u64, + base_weight: Weight, + byte_fee: u64, + weight_to_fee: u64, +} + +impl Default for ExtBuilder { + fn default() -> Self { + Self { + balance_factor: 1, + base_weight: Weight::from_parts(0, 0), + byte_fee: 1, + weight_to_fee: 1, + } + } +} + +impl ExtBuilder { + pub fn base_weight(mut self, base_weight: Weight) -> Self { + self.base_weight = base_weight; + self + } + pub fn balance_factor(mut self, factor: u64) -> Self { + self.balance_factor = factor; + self + } + fn set_constants(&self) { + ExtrinsicBaseWeight::mutate(|v| *v = self.base_weight); + TRANSACTION_BYTE_FEE.with(|v| *v.borrow_mut() = self.byte_fee); + WEIGHT_TO_FEE.with(|v| *v.borrow_mut() = self.weight_to_fee); + } + pub fn build(self) -> sp_io::TestExternalities { + self.set_constants(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); + pallet_balances::GenesisConfig:: { + balances: if self.balance_factor > 0 { + vec![ + (1, 10 * self.balance_factor), + (2, 20 * self.balance_factor), + (3, 30 * self.balance_factor), + (4, 40 * self.balance_factor), + (5, 50 * self.balance_factor), + (6, 60 * self.balance_factor), + ] + } else { + vec![] + }, + } + .assimilate_storage(&mut t) + .unwrap(); + t.into() + } +} + +/// create a transaction info struct from weight. Handy to avoid building the whole struct. +pub fn info_from_weight(w: Weight) -> DispatchInfo { + // pays_fee: Pays::Yes -- class: DispatchClass::Normal + DispatchInfo { weight: w, ..Default::default() } +} + +fn post_info_from_weight(w: Weight) -> PostDispatchInfo { + PostDispatchInfo { actual_weight: Some(w), pays_fee: Default::default() } +} + +fn info_from_pays(p: Pays) -> DispatchInfo { + DispatchInfo { pays_fee: p, ..Default::default() } +} + +fn post_info_from_pays(p: Pays) -> PostDispatchInfo { + PostDispatchInfo { actual_weight: None, pays_fee: p } +} + +fn default_post_info() -> PostDispatchInfo { + PostDispatchInfo { actual_weight: None, pays_fee: Default::default() } +} + +fn setup_lp(asset_id: u32, balance_factor: u64) { + let lp_provider = 5; + assert_ok!(Balances::force_set_balance( + RuntimeOrigin::root(), + lp_provider, + 10_000 * balance_factor + )); + let lp_provider_account = ::Lookup::unlookup(lp_provider); + assert_ok!(Assets::mint_into(asset_id.into(), &lp_provider_account, 10_000 * balance_factor)); + + let token_1 = NativeOrAssetId::Native; + let token_2 = NativeOrAssetId::Asset(asset_id); + assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(lp_provider), token_1, token_2)); + + assert_ok!(AssetConversion::add_liquidity( + RuntimeOrigin::signed(lp_provider), + token_1, + token_2, + 1_000 * balance_factor, // 1 desired + 10_000 * balance_factor, // 2 desired + 1, // 1 min + 1, // 2 min + lp_provider_account, + )); +} + +const WEIGHT_5: Weight = Weight::from_parts(5, 0); +const WEIGHT_50: Weight = Weight::from_parts(50, 0); +const WEIGHT_100: Weight = Weight::from_parts(100, 0); + +#[test] +fn transaction_payment_in_native_possible() { + let base_weight = 5; + let balance_factor = 100; + ExtBuilder::default() + .balance_factor(balance_factor) + .base_weight(Weight::from_parts(base_weight, 0)) + .build() + .execute_with(|| { + let len = 10; + let pre = ChargeAssetTxPayment::::from(0, None) + .pre_dispatch(&1, CALL, &info_from_weight(WEIGHT_5), len) + .unwrap(); + let initial_balance = 10 * balance_factor; + assert_eq!(Balances::free_balance(1), initial_balance - 5 - 5 - 10); + + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_weight(WEIGHT_5), + &default_post_info(), + len, + &Ok(()) + )); + assert_eq!(Balances::free_balance(1), initial_balance - 5 - 5 - 10); + + let pre = ChargeAssetTxPayment::::from(5 /* tipped */, None) + .pre_dispatch(&2, CALL, &info_from_weight(WEIGHT_100), len) + .unwrap(); + let initial_balance_for_2 = 20 * balance_factor; + + assert_eq!(Balances::free_balance(2), initial_balance_for_2 - 5 - 10 - 100 - 5); + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_weight(WEIGHT_100), + &post_info_from_weight(WEIGHT_50), + len, + &Ok(()) + )); + assert_eq!(Balances::free_balance(2), initial_balance_for_2 - 5 - 10 - 50 - 5); + }); +} + +#[test] +fn transaction_payment_in_asset_possible() { + let base_weight = 5; + let balance_factor = 100; + ExtBuilder::default() + .balance_factor(balance_factor) + .base_weight(Weight::from_parts(base_weight, 0)) + .build() + .execute_with(|| { + // create the asset + let asset_id = 1; + let min_balance = 2; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + 42, /* owner */ + true, /* is_sufficient */ + min_balance + )); + + // mint into the caller account + let caller = 1; + let beneficiary = ::Lookup::unlookup(caller); + let balance = 1000; + + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); + assert_eq!(Assets::balance(asset_id, caller), balance); + + let len = 10; + let tx_weight = 5; + + setup_lp(asset_id, balance_factor); + + let fee_in_native = base_weight + tx_weight + len as u64; + let input_quote = AssetConversion::quote_price_tokens_for_exact_tokens( + NativeOrAssetId::Asset(asset_id), + NativeOrAssetId::Native, + fee_in_native, + true, + ); + assert_eq!(input_quote, Some(201)); + + let fee_in_asset = input_quote.unwrap(); + assert_eq!(Assets::balance(asset_id, caller), balance); + + let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)) + .pre_dispatch(&caller, CALL, &info_from_weight(WEIGHT_5), len) + .unwrap(); + // assert that native balance is not used + assert_eq!(Balances::free_balance(caller), 10 * balance_factor); + + // check that fee was charged in the given asset + assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset); + + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_weight(WEIGHT_5), // estimated tx weight + &default_post_info(), // weight actually used == estimated + len, + &Ok(()) + )); + + assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset); + assert_eq!(TipUnbalancedAmount::get(), 0); + assert_eq!(FeeUnbalancedAmount::get(), fee_in_native); + }); +} + +#[test] +fn transaction_payment_in_asset_fails_if_no_pool_for_that_asset() { + let base_weight = 5; + let balance_factor = 100; + ExtBuilder::default() + .balance_factor(balance_factor) + .base_weight(Weight::from_parts(base_weight, 0)) + .build() + .execute_with(|| { + // create the asset + let asset_id = 1; + let min_balance = 2; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + 42, /* owner */ + true, /* is_sufficient */ + min_balance + )); + + // mint into the caller account + let caller = 1; + let beneficiary = ::Lookup::unlookup(caller); + let balance = 1000; + + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); + assert_eq!(Assets::balance(asset_id, caller), balance); + + let len = 10; + let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)).pre_dispatch( + &caller, + CALL, + &info_from_weight(WEIGHT_5), + len, + ); + + // As there is no pool in the dex set up for this asset, conversion should fail. + assert!(pre.is_err()); + }); +} + +#[test] +fn transaction_payment_without_fee() { + let base_weight = 5; + let balance_factor = 100; + ExtBuilder::default() + .balance_factor(balance_factor) + .base_weight(Weight::from_parts(base_weight, 0)) + .build() + .execute_with(|| { + let caller = 1; + + // create the asset + let asset_id = 1; + let balance = 1000; + let min_balance = 2; + + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + 42, /* owner */ + true, /* is_sufficient */ + min_balance, + )); + + setup_lp(asset_id, balance_factor); + + // mint into the caller account + let beneficiary = ::Lookup::unlookup(caller); + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); + assert_eq!(Assets::balance(asset_id, caller), balance); + + let weight = 5; + let len = 10; + let fee_in_native = base_weight + weight + len as u64; + let input_quote = AssetConversion::quote_price_tokens_for_exact_tokens( + NativeOrAssetId::Asset(asset_id), + NativeOrAssetId::Native, + fee_in_native, + true, + ); + assert_eq!(input_quote, Some(201)); + + let fee_in_asset = input_quote.unwrap(); + let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)) + .pre_dispatch(&caller, CALL, &info_from_weight(WEIGHT_5), len) + .unwrap(); + + // assert that native balance is not used + assert_eq!(Balances::free_balance(caller), 10 * balance_factor); + // check that fee was charged in the given asset + assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset); + + let refund = AssetConversion::quote_price_exact_tokens_for_tokens( + NativeOrAssetId::Native, + NativeOrAssetId::Asset(asset_id), + fee_in_native, + true, + ) + .unwrap(); + assert_eq!(refund, 199); + + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_weight(WEIGHT_5), + &post_info_from_pays(Pays::No), + len, + &Ok(()) + )); + + // caller should get refunded + assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset + refund); + assert_eq!(Balances::free_balance(caller), 10 * balance_factor); + }); +} + +#[test] +fn asset_transaction_payment_with_tip_and_refund() { + let base_weight = 5; + let balance_factor = 100; + ExtBuilder::default() + .balance_factor(balance_factor) + .base_weight(Weight::from_parts(base_weight, 0)) + .build() + .execute_with(|| { + // create the asset + let asset_id = 1; + let min_balance = 2; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + 42, /* owner */ + true, /* is_sufficient */ + min_balance, + )); + + setup_lp(asset_id, balance_factor); + + // mint into the caller account + let caller = 2; + let beneficiary = ::Lookup::unlookup(caller); + let balance = 10000; + + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); + assert_eq!(Assets::balance(asset_id, caller), balance); + + let weight = 100; + let tip = 5; + let len = 10; + let fee_in_native = base_weight + weight + len as u64 + tip; + let input_quote = AssetConversion::quote_price_tokens_for_exact_tokens( + NativeOrAssetId::Asset(asset_id), + NativeOrAssetId::Native, + fee_in_native, + true, + ); + assert_eq!(input_quote, Some(1206)); + + let fee_in_asset = input_quote.unwrap(); + let pre = ChargeAssetTxPayment::::from(tip, Some(asset_id)) + .pre_dispatch(&caller, CALL, &info_from_weight(WEIGHT_100), len) + .unwrap(); + assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset); + + let final_weight = 50; + let expected_fee = fee_in_native - final_weight - tip; + let expected_token_refund = AssetConversion::quote_price_exact_tokens_for_tokens( + NativeOrAssetId::Native, + NativeOrAssetId::Asset(asset_id), + fee_in_native - expected_fee - tip, + true, + ) + .unwrap(); + + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_weight(WEIGHT_100), + &post_info_from_weight(WEIGHT_50), + len, + &Ok(()) + )); + + assert_eq!(TipUnbalancedAmount::get(), tip); + assert_eq!(FeeUnbalancedAmount::get(), expected_fee); + + // caller should get refunded + assert_eq!( + Assets::balance(asset_id, caller), + balance - fee_in_asset + expected_token_refund + ); + assert_eq!(Balances::free_balance(caller), 20 * balance_factor); + }); +} + +#[test] +fn payment_from_account_with_only_assets() { + let base_weight = 5; + let balance_factor = 100; + ExtBuilder::default() + .balance_factor(balance_factor) + .base_weight(Weight::from_parts(base_weight, 0)) + .build() + .execute_with(|| { + // create the asset + let asset_id = 1; + let min_balance = 2; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + 42, /* owner */ + true, /* is_sufficient */ + min_balance, + )); + + setup_lp(asset_id, balance_factor); + + // mint into the caller account + let caller = 333; + let beneficiary = ::Lookup::unlookup(caller); + let balance = 1000; + + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); + assert_eq!(Assets::balance(asset_id, caller), balance); + + // assert that native balance is not necessary + assert_eq!(Balances::free_balance(caller), 0); + let weight = 5; + let len = 10; + + let fee_in_native = base_weight + weight + len as u64; + let ed = Balances::minimum_balance(); + let fee_in_asset = AssetConversion::quote_price_tokens_for_exact_tokens( + NativeOrAssetId::Asset(asset_id), + NativeOrAssetId::Native, + fee_in_native + ed, + true, + ) + .unwrap(); + assert_eq!(fee_in_asset, 301); + + let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)) + .pre_dispatch(&caller, CALL, &info_from_weight(WEIGHT_5), len) + .unwrap(); + assert_eq!(Balances::free_balance(caller), ed); + // check that fee was charged in the given asset + assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset); + + let refund = AssetConversion::quote_price_exact_tokens_for_tokens( + NativeOrAssetId::Native, + NativeOrAssetId::Asset(asset_id), + ed, + true, + ) + .unwrap(); + + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_weight(WEIGHT_5), + &default_post_info(), + len, + &Ok(()) + )); + assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset + refund); + assert_eq!(Balances::free_balance(caller), 0); + + assert_eq!(TipUnbalancedAmount::get(), 0); + assert_eq!(FeeUnbalancedAmount::get(), fee_in_native); + }); +} + +#[test] +fn converted_fee_is_never_zero_if_input_fee_is_not() { + let base_weight = 1; + let balance_factor = 100; + ExtBuilder::default() + .balance_factor(balance_factor) + .base_weight(Weight::from_parts(base_weight, 0)) + .build() + .execute_with(|| { + // create the asset + let asset_id = 1; + let min_balance = 1; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + 42, /* owner */ + true, /* is_sufficient */ + min_balance + )); + + setup_lp(asset_id, balance_factor); + + // mint into the caller account + let caller = 2; + let beneficiary = ::Lookup::unlookup(caller); + let balance = 1000; + + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); + assert_eq!(Assets::balance(asset_id, caller), balance); + + let weight = 1; + let len = 1; + + // there will be no conversion when the fee is zero + { + let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)) + .pre_dispatch(&caller, CALL, &info_from_pays(Pays::No), len) + .unwrap(); + // `Pays::No` implies there are no fees + assert_eq!(Assets::balance(asset_id, caller), balance); + + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_pays(Pays::No), + &post_info_from_pays(Pays::No), + len, + &Ok(()) + )); + assert_eq!(Assets::balance(asset_id, caller), balance); + } + + // validate even a small fee gets converted to asset. + let fee_in_native = base_weight + weight + len as u64; + let fee_in_asset = AssetConversion::quote_price_tokens_for_exact_tokens( + NativeOrAssetId::Asset(asset_id), + NativeOrAssetId::Native, + fee_in_native, + true, + ) + .unwrap(); + + let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)) + .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_parts(weight, 0)), len) + .unwrap(); + assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset); + + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_weight(Weight::from_parts(weight, 0)), + &default_post_info(), + len, + &Ok(()) + )); + assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset); + }); +} + +#[test] +fn post_dispatch_fee_is_zero_if_pre_dispatch_fee_is_zero() { + let base_weight = 1; + ExtBuilder::default() + .balance_factor(100) + .base_weight(Weight::from_parts(base_weight, 0)) + .build() + .execute_with(|| { + // create the asset + let asset_id = 1; + let min_balance = 100; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + 42, /* owner */ + true, /* is_sufficient */ + min_balance + )); + + // mint into the caller account + let caller = 333; + let beneficiary = ::Lookup::unlookup(caller); + let balance = 1000; + + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); + assert_eq!(Assets::balance(asset_id, caller), balance); + + let weight = 1; + let len = 1; + let fee = base_weight + weight + len as u64; + + // calculated fee is greater than 0 + assert!(fee > 0); + + let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)) + .pre_dispatch(&caller, CALL, &info_from_pays(Pays::No), len) + .unwrap(); + // `Pays::No` implies no pre-dispatch fees + + assert_eq!(Assets::balance(asset_id, caller), balance); + + let (_tip, _who, initial_payment, _asset_id) = ⪯ + let not_paying = match initial_payment { + &InitialPayment::Nothing => true, + _ => false, + }; + assert!(not_paying, "initial payment should be Nothing if we pass Pays::No"); + + // `Pays::Yes` on post-dispatch does not mean we pay (we never charge more than the + // initial fee) + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + Some(pre), + &info_from_pays(Pays::No), + &post_info_from_pays(Pays::Yes), + len, + &Ok(()) + )); + assert_eq!(Assets::balance(asset_id, caller), balance); + }); +} + +#[test] +fn post_dispatch_fee_is_zero_if_unsigned_pre_dispatch_fee_is_zero() { + let base_weight = 1; + ExtBuilder::default() + .balance_factor(100) + .base_weight(Weight::from_parts(base_weight, 0)) + .build() + .execute_with(|| { + // create the asset + let asset_id = 1; + let min_balance = 100; + assert_ok!(Assets::force_create( + RuntimeOrigin::root(), + asset_id.into(), + 42, /* owner */ + true, /* is_sufficient */ + min_balance + )); + + // mint into the caller account + let caller = 333; + let beneficiary = ::Lookup::unlookup(caller); + let balance = 1000; + + assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance)); + assert_eq!(Assets::balance(asset_id, caller), balance); + + let weight = 1; + let len = 1; + ChargeAssetTxPayment::::pre_dispatch_unsigned( + CALL, + &info_from_weight(Weight::from_parts(weight, 0)), + len, + ) + .unwrap(); + + assert_eq!(Assets::balance(asset_id, caller), balance); + + // `Pays::Yes` on post-dispatch does not mean we pay (we never charge more than the + // initial fee) + assert_ok!(ChargeAssetTxPayment::::post_dispatch( + None, + &info_from_weight(Weight::from_parts(weight, 0)), + &post_info_from_pays(Pays::Yes), + len, + &Ok(()) + )); + assert_eq!(Assets::balance(asset_id, caller), balance); + }); +} diff --git a/frame/transaction-payment/asset-tx-payment/Cargo.toml b/frame/transaction-payment/asset-tx-payment/Cargo.toml index 324e17a0808a..8bbe059a0c67 100644 --- a/frame/transaction-payment/asset-tx-payment/Cargo.toml +++ b/frame/transaction-payment/asset-tx-payment/Cargo.toml @@ -14,10 +14,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # Substrate dependencies -sp-core = { version = "7.0.0", default-features = false, path = "../../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } @@ -25,14 +25,14 @@ pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../../benchmarking", optional = true } # Other dependencies -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", optional = true } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", optional = true } [dev-dependencies] serde_json = "1.0.85" -sp-storage = { version = "7.0.0", default-features = false, path = "../../../primitives/storage" } +sp-storage = { version = "13.0.0", default-features = false, path = "../../../primitives/storage" } pallet-assets = { version = "4.0.0-dev", path = "../../assets" } pallet-authorship = { version = "4.0.0-dev", path = "../../authorship" } diff --git a/frame/transaction-payment/asset-tx-payment/src/lib.rs b/frame/transaction-payment/asset-tx-payment/src/lib.rs index 230b307317f8..86f3025cc313 100644 --- a/frame/transaction-payment/asset-tx-payment/src/lib.rs +++ b/frame/transaction-payment/asset-tx-payment/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,7 +42,7 @@ use frame_support::{ dispatch::{DispatchInfo, DispatchResult, PostDispatchInfo}, traits::{ tokens::{ - fungibles::{Balanced, CreditOf, Inspect}, + fungibles::{Balanced, Credit, Inspect}, WithdrawConsequence, }, IsType, @@ -67,17 +67,17 @@ mod tests; mod payment; pub use payment::*; -// Type aliases used for interaction with `OnChargeTransaction`. +/// Type aliases used for interaction with `OnChargeTransaction`. pub(crate) type OnChargeTransactionOf = ::OnChargeTransaction; -// Balance type alias. +/// Balance type alias. pub(crate) type BalanceOf = as OnChargeTransaction>::Balance; -// Liquity info type alias. +/// Liquidity info type alias. pub(crate) type LiquidityInfoOf = as OnChargeTransaction>::LiquidityInfo; -// Type alias used for interaction with fungibles (assets). -// Balance type alias. +/// Type alias used for interaction with fungibles (assets). +/// Balance type alias. pub(crate) type AssetBalanceOf = <::Fungibles as Inspect<::AccountId>>::Balance; /// Asset id type alias. @@ -85,26 +85,26 @@ pub(crate) type AssetIdOf = <::Fungibles as Inspect<::AccountId>>::AssetId; // Type aliases used for interaction with `OnChargeAssetTransaction`. -// Balance type alias. +/// Balance type alias. pub(crate) type ChargeAssetBalanceOf = <::OnChargeAssetTransaction as OnChargeAssetTransaction>::Balance; -// Asset id type alias. +/// Asset id type alias. pub(crate) type ChargeAssetIdOf = <::OnChargeAssetTransaction as OnChargeAssetTransaction>::AssetId; -// Liquity info type alias. +/// Liquidity info type alias. pub(crate) type ChargeAssetLiquidityOf = <::OnChargeAssetTransaction as OnChargeAssetTransaction>::LiquidityInfo; /// Used to pass the initial payment info from pre- to post-dispatch. #[derive(Encode, Decode, DefaultNoBound, TypeInfo)] pub enum InitialPayment { - /// No initial fee was payed. + /// No initial fee was paid. #[default] Nothing, - /// The initial fee was payed in the native currency. + /// The initial fee was paid in the native currency. Native(LiquidityInfoOf), - /// The initial fee was payed in an asset. - Asset(CreditOf), + /// The initial fee was paid in an asset. + Asset(Credit), } pub use pallet::*; @@ -124,7 +124,6 @@ pub mod pallet { } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::event] @@ -134,8 +133,8 @@ pub mod pallet { /// has been paid by `who` in an asset `asset_id`. AssetTxFeePaid { who: T::AccountId, - actual_fee: BalanceOf, - tip: BalanceOf, + actual_fee: AssetBalanceOf, + tip: AssetBalanceOf, asset_id: Option>, }, } @@ -160,7 +159,7 @@ where AssetBalanceOf: Send + Sync + FixedPointOperand, BalanceOf: Send + Sync + FixedPointOperand + IsType>, ChargeAssetIdOf: Send + Sync, - CreditOf: IsType>, + Credit: IsType>, { /// Utility constructor. Used only in client/factory code. pub fn from(tip: BalanceOf, asset_id: Option>) -> Self { @@ -217,7 +216,7 @@ where AssetBalanceOf: Send + Sync + FixedPointOperand, BalanceOf: Send + Sync + From + FixedPointOperand + IsType>, ChargeAssetIdOf: Send + Sync, - CreditOf: IsType>, + Credit: IsType>, { const IDENTIFIER: &'static str = "ChargeAssetTxPayment"; type AccountId = T::AccountId; @@ -284,18 +283,20 @@ where let actual_fee = pallet_transaction_payment::Pallet::::compute_actual_fee( len as u32, info, post_info, tip, ); - T::OnChargeAssetTransaction::correct_and_deposit_fee( - &who, - info, - post_info, - actual_fee.into(), - tip.into(), - already_withdrawn.into(), - )?; + + let (converted_fee, converted_tip) = + T::OnChargeAssetTransaction::correct_and_deposit_fee( + &who, + info, + post_info, + actual_fee.into(), + tip.into(), + already_withdrawn.into(), + )?; Pallet::::deposit_event(Event::::AssetTxFeePaid { who, - actual_fee, - tip, + actual_fee: converted_fee, + tip: converted_tip, asset_id, }); }, diff --git a/frame/transaction-payment/asset-tx-payment/src/mock.rs b/frame/transaction-payment/asset-tx-payment/src/mock.rs index ddb02f5a611f..b8d7b523ca25 100644 --- a/frame/transaction-payment/asset-tx-payment/src/mock.rs +++ b/frame/transaction-payment/asset-tx-payment/src/mock.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,28 +29,20 @@ use frame_system as system; use frame_system::EnsureRoot; use pallet_transaction_payment::CurrencyAdapter; use sp_core::H256; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, ConvertInto, IdentityLookup, SaturatedConversion}, -}; +use sp_runtime::traits::{BlakeTwo256, ConvertInto, IdentityLookup, SaturatedConversion}; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; type Balance = u64; type AccountId = u64; frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: system::{Pallet, Call, Config, Storage, Event}, - Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, - Assets: pallet_assets::{Pallet, Call, Storage, Event}, - Authorship: pallet_authorship::{Pallet, Call, Storage}, - AssetTxPayment: pallet_asset_tx_payment::{Pallet, Event}, + pub struct Runtime { + System: system, + Balances: pallet_balances, + TransactionPayment: pallet_transaction_payment, + Assets: pallet_assets, + Authorship: pallet_authorship, + AssetTxPayment: pallet_asset_tx_payment, } ); @@ -67,7 +59,7 @@ impl Get for BlockWeights { weights.base_extrinsic = ExtrinsicBaseWeight::get().into(); }) .for_class(DispatchClass::non_mandatory(), |weights| { - weights.max_total = Weight::from_ref_time(1024).set_proof_size(u64::MAX).into(); + weights.max_total = Weight::from_parts(1024, u64::MAX).into(); }) .build_or_panic() } @@ -84,14 +76,13 @@ impl frame_system::Config for Runtime { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = AccountId; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -119,6 +110,10 @@ impl pallet_balances::Config for Runtime { type WeightInfo = (); type MaxReserves = ConstU32<50>; type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } impl WeightToFeeT for WeightToFee { @@ -187,14 +182,12 @@ impl FindAuthor for HardcodedAuthor { impl pallet_authorship::Config for Runtime { type FindAuthor = HardcodedAuthor; - type UncleGenerations = (); - type FilterUncle = (); type EventHandler = (); } pub struct CreditToBlockAuthor; impl HandleCredit for CreditToBlockAuthor { - fn handle_credit(credit: CreditOf) { + fn handle_credit(credit: Credit) { if let Some(author) = pallet_authorship::Pallet::::author() { // What to do in case paying the author fails (e.g. because `fee < min_balance`) // default: drop the result which will trigger the `OnDrop` of the imbalance. diff --git a/frame/transaction-payment/asset-tx-payment/src/payment.rs b/frame/transaction-payment/asset-tx-payment/src/payment.rs index 85d1bec4b275..717114ab6bd0 100644 --- a/frame/transaction-payment/asset-tx-payment/src/payment.rs +++ b/frame/transaction-payment/asset-tx-payment/src/payment.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,8 +20,11 @@ use crate::Config; use codec::FullCodec; use frame_support::{ traits::{ - fungibles::{Balanced, CreditOf, Inspect}, - tokens::{Balance, BalanceConversion}, + fungibles::{Balanced, Credit, Inspect}, + tokens::{ + Balance, ConversionToAssetBalance, Fortitude::Polite, Precision::Exact, + Preservation::Protect, + }, }, unsigned::TransactionValidityError, }; @@ -58,6 +61,8 @@ pub trait OnChargeAssetTransaction { /// the corrected amount. /// /// Note: The `fee` already includes the `tip`. + /// + /// Returns the fee and tip in the asset used for payment as (fee, tip). fn correct_and_deposit_fee( who: &T::AccountId, dispatch_info: &DispatchInfoOf, @@ -65,7 +70,7 @@ pub trait OnChargeAssetTransaction { corrected_fee: Self::Balance, tip: Self::Balance, already_withdrawn: Self::LiquidityInfo, - ) -> Result<(), TransactionValidityError>; + ) -> Result<(AssetBalanceOf, AssetBalanceOf), TransactionValidityError>; } /// Allows specifying what to do with the withdrawn asset fees. @@ -73,17 +78,17 @@ pub trait HandleCredit> { /// Implement to determine what to do with the withdrawn asset fees. /// Default for `CreditOf` from the assets pallet is to burn and /// decrease total issuance. - fn handle_credit(credit: CreditOf); + fn handle_credit(credit: Credit); } /// Default implementation that just drops the credit according to the `OnDrop` in the underlying /// imbalance type. impl> HandleCredit for () { - fn handle_credit(_credit: CreditOf) {} + fn handle_credit(_credit: Credit) {} } /// Implements the asset transaction for a balance to asset converter (implementing -/// [`BalanceConversion`]) and a credit handler (implementing [`HandleCredit`]). +/// [`ConversionToAssetBalance`]) and a credit handler (implementing [`HandleCredit`]). /// /// The credit handler is given the complete fee in terms of the asset used for the transaction. pub struct FungiblesAdapter(PhantomData<(CON, HC)>); @@ -93,13 +98,13 @@ pub struct FungiblesAdapter(PhantomData<(CON, HC)>); impl OnChargeAssetTransaction for FungiblesAdapter where T: Config, - CON: BalanceConversion, AssetIdOf, AssetBalanceOf>, + CON: ConversionToAssetBalance, AssetIdOf, AssetBalanceOf>, HC: HandleCredit, AssetIdOf: FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo, { type Balance = BalanceOf; type AssetId = AssetIdOf; - type LiquidityInfo = CreditOf; + type LiquidityInfo = Credit; /// Withdraw the predicted fee from the transaction origin. /// @@ -121,30 +126,42 @@ where .max(min_converted_fee); let can_withdraw = >::can_withdraw(asset_id, who, converted_fee); - if !matches!(can_withdraw, WithdrawConsequence::Success) { + if can_withdraw != WithdrawConsequence::Success { return Err(InvalidTransaction::Payment.into()) } - >::withdraw(asset_id, who, converted_fee) - .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment)) + >::withdraw( + asset_id, + who, + converted_fee, + Exact, + Protect, + Polite, + ) + .map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment)) } /// Hand the fee and the tip over to the `[HandleCredit]` 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`. + /// + /// Returns the fee and tip in the asset used for payment as (fee, tip). fn correct_and_deposit_fee( who: &T::AccountId, _dispatch_info: &DispatchInfoOf, _post_info: &PostDispatchInfoOf, corrected_fee: Self::Balance, - _tip: Self::Balance, + tip: Self::Balance, paid: Self::LiquidityInfo, - ) -> Result<(), TransactionValidityError> { + ) -> Result<(AssetBalanceOf, AssetBalanceOf), TransactionValidityError> { let min_converted_fee = if corrected_fee.is_zero() { Zero::zero() } else { One::one() }; - // Convert the corrected fee into the asset used for payment. + // Convert the corrected fee and tip into the asset used for payment. let converted_fee = CON::to_asset_balance(corrected_fee, paid.asset()) .map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })? .max(min_converted_fee); + let converted_tip = CON::to_asset_balance(tip, paid.asset()) + .map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })?; + // Calculate how much refund we should return. let (final_fee, refund) = paid.split(converted_fee); // Refund to the account that paid the fees. If this fails, the account might have dropped @@ -152,6 +169,6 @@ where let _ = >::resolve(who, refund); // Handle the final fee, e.g. by transferring to the block author or burning. HC::handle_credit(final_fee); - Ok(()) + Ok((converted_fee, converted_tip)) } } diff --git a/frame/transaction-payment/asset-tx-payment/src/tests.rs b/frame/transaction-payment/asset-tx-payment/src/tests.rs index f76293f45649..8df98ceda997 100644 --- a/frame/transaction-payment/asset-tx-payment/src/tests.rs +++ b/frame/transaction-payment/asset-tx-payment/src/tests.rs @@ -1,4 +1,4 @@ -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,10 +25,10 @@ use frame_support::{ use frame_system as system; use mock::{ExtrinsicBaseWeight, *}; use pallet_balances::Call as BalancesCall; -use sp_runtime::traits::StaticLookup; +use sp_runtime::{traits::StaticLookup, BuildStorage}; const CALL: &::RuntimeCall = - &RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 69 }); + &RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 2, value: 69 }); pub struct ExtBuilder { balance_factor: u64, @@ -41,7 +41,7 @@ impl Default for ExtBuilder { fn default() -> Self { Self { balance_factor: 1, - base_weight: Weight::from_ref_time(0), + base_weight: Weight::from_parts(0, 0), byte_fee: 1, weight_to_fee: 1, } @@ -64,7 +64,7 @@ impl ExtBuilder { } pub fn build(self) -> sp_io::TestExternalities { self.set_constants(); - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: if self.balance_factor > 0 { vec![ @@ -112,19 +112,19 @@ fn transaction_payment_in_native_possible() { let balance_factor = 100; ExtBuilder::default() .balance_factor(balance_factor) - .base_weight(Weight::from_ref_time(5)) + .base_weight(Weight::from_parts(5, 0)) .build() .execute_with(|| { let len = 10; let pre = ChargeAssetTxPayment::::from(0, None) - .pre_dispatch(&1, CALL, &info_from_weight(Weight::from_ref_time(5)), len) + .pre_dispatch(&1, CALL, &info_from_weight(Weight::from_parts(5, 0)), len) .unwrap(); let initial_balance = 10 * balance_factor; assert_eq!(Balances::free_balance(1), initial_balance - 5 - 5 - 10); assert_ok!(ChargeAssetTxPayment::::post_dispatch( Some(pre), - &info_from_weight(Weight::from_ref_time(5)), + &info_from_weight(Weight::from_parts(5, 0)), &default_post_info(), len, &Ok(()) @@ -132,15 +132,15 @@ fn transaction_payment_in_native_possible() { assert_eq!(Balances::free_balance(1), initial_balance - 5 - 5 - 10); let pre = ChargeAssetTxPayment::::from(5 /* tipped */, None) - .pre_dispatch(&2, CALL, &info_from_weight(Weight::from_ref_time(100)), len) + .pre_dispatch(&2, CALL, &info_from_weight(Weight::from_parts(100, 0)), len) .unwrap(); let initial_balance_for_2 = 20 * balance_factor; assert_eq!(Balances::free_balance(2), initial_balance_for_2 - 5 - 10 - 100 - 5); assert_ok!(ChargeAssetTxPayment::::post_dispatch( Some(pre), - &info_from_weight(Weight::from_ref_time(100)), - &post_info_from_weight(Weight::from_ref_time(50)), + &info_from_weight(Weight::from_parts(100, 0)), + &post_info_from_weight(Weight::from_parts(50, 0)), len, &Ok(()) )); @@ -154,7 +154,7 @@ fn transaction_payment_in_asset_possible() { let balance_factor = 100; ExtBuilder::default() .balance_factor(balance_factor) - .base_weight(Weight::from_ref_time(base_weight)) + .base_weight(Weight::from_parts(base_weight, 0)) .build() .execute_with(|| { // create the asset @@ -180,7 +180,7 @@ fn transaction_payment_in_asset_possible() { // existential deposit let fee = (base_weight + weight + len as u64) * min_balance / ExistentialDeposit::get(); let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)) - .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_ref_time(weight)), len) + .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_parts(weight, 0)), len) .unwrap(); // assert that native balance is not used assert_eq!(Balances::free_balance(caller), 10 * balance_factor); @@ -190,7 +190,7 @@ fn transaction_payment_in_asset_possible() { assert_ok!(ChargeAssetTxPayment::::post_dispatch( Some(pre), - &info_from_weight(Weight::from_ref_time(weight)), + &info_from_weight(Weight::from_parts(weight, 0)), &default_post_info(), len, &Ok(()) @@ -207,7 +207,7 @@ fn transaction_payment_without_fee() { let balance_factor = 100; ExtBuilder::default() .balance_factor(balance_factor) - .base_weight(Weight::from_ref_time(base_weight)) + .base_weight(Weight::from_parts(base_weight, 0)) .build() .execute_with(|| { // create the asset @@ -233,7 +233,7 @@ fn transaction_payment_without_fee() { // existential deposit let fee = (base_weight + weight + len as u64) * min_balance / ExistentialDeposit::get(); let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)) - .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_ref_time(weight)), len) + .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_parts(weight, 0)), len) .unwrap(); // assert that native balance is not used assert_eq!(Balances::free_balance(caller), 10 * balance_factor); @@ -243,7 +243,7 @@ fn transaction_payment_without_fee() { assert_ok!(ChargeAssetTxPayment::::post_dispatch( Some(pre), - &info_from_weight(Weight::from_ref_time(weight)), + &info_from_weight(Weight::from_parts(weight, 0)), &post_info_from_pays(Pays::No), len, &Ok(()) @@ -260,7 +260,7 @@ fn asset_transaction_payment_with_tip_and_refund() { let base_weight = 5; ExtBuilder::default() .balance_factor(100) - .base_weight(Weight::from_ref_time(base_weight)) + .base_weight(Weight::from_parts(base_weight, 0)) .build() .execute_with(|| { // create the asset @@ -288,15 +288,15 @@ fn asset_transaction_payment_with_tip_and_refund() { let fee_with_tip = (base_weight + weight + len as u64 + tip) * min_balance / ExistentialDeposit::get(); let pre = ChargeAssetTxPayment::::from(tip, Some(asset_id)) - .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_ref_time(weight)), len) + .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_parts(weight, 0)), len) .unwrap(); assert_eq!(Assets::balance(asset_id, caller), balance - fee_with_tip); let final_weight = 50; assert_ok!(ChargeAssetTxPayment::::post_dispatch( Some(pre), - &info_from_weight(Weight::from_ref_time(weight)), - &post_info_from_weight(Weight::from_ref_time(final_weight)), + &info_from_weight(Weight::from_parts(weight, 0)), + &post_info_from_weight(Weight::from_parts(final_weight, 0)), len, &Ok(()) )); @@ -312,7 +312,7 @@ fn payment_from_account_with_only_assets() { let base_weight = 5; ExtBuilder::default() .balance_factor(100) - .base_weight(Weight::from_ref_time(base_weight)) + .base_weight(Weight::from_parts(base_weight, 0)) .build() .execute_with(|| { // create the asset @@ -340,7 +340,7 @@ fn payment_from_account_with_only_assets() { // existential deposit let fee = (base_weight + weight + len as u64) * min_balance / ExistentialDeposit::get(); let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)) - .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_ref_time(weight)), len) + .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_parts(weight, 0)), len) .unwrap(); assert_eq!(Balances::free_balance(caller), 0); // check that fee was charged in the given asset @@ -348,7 +348,7 @@ fn payment_from_account_with_only_assets() { assert_ok!(ChargeAssetTxPayment::::post_dispatch( Some(pre), - &info_from_weight(Weight::from_ref_time(weight)), + &info_from_weight(Weight::from_parts(weight, 0)), &default_post_info(), len, &Ok(()) @@ -363,7 +363,7 @@ fn payment_only_with_existing_sufficient_asset() { let base_weight = 5; ExtBuilder::default() .balance_factor(100) - .base_weight(Weight::from_ref_time(base_weight)) + .base_weight(Weight::from_parts(base_weight, 0)) .build() .execute_with(|| { let asset_id = 1; @@ -372,7 +372,7 @@ fn payment_only_with_existing_sufficient_asset() { let len = 10; // pre_dispatch fails for non-existent asset assert!(ChargeAssetTxPayment::::from(0, Some(asset_id)) - .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_ref_time(weight)), len) + .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_parts(weight, 0)), len) .is_err()); // create the non-sufficient asset @@ -386,7 +386,7 @@ fn payment_only_with_existing_sufficient_asset() { )); // pre_dispatch fails for non-sufficient asset assert!(ChargeAssetTxPayment::::from(0, Some(asset_id)) - .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_ref_time(weight)), len) + .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_parts(weight, 0)), len) .is_err()); }); } @@ -396,7 +396,7 @@ fn converted_fee_is_never_zero_if_input_fee_is_not() { let base_weight = 1; ExtBuilder::default() .balance_factor(100) - .base_weight(Weight::from_ref_time(base_weight)) + .base_weight(Weight::from_parts(base_weight, 0)) .build() .execute_with(|| { // create the asset @@ -440,14 +440,14 @@ fn converted_fee_is_never_zero_if_input_fee_is_not() { assert_eq!(Assets::balance(asset_id, caller), balance); } let pre = ChargeAssetTxPayment::::from(0, Some(asset_id)) - .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_ref_time(weight)), len) + .pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_parts(weight, 0)), len) .unwrap(); // check that at least one coin was charged in the given asset assert_eq!(Assets::balance(asset_id, caller), balance - 1); assert_ok!(ChargeAssetTxPayment::::post_dispatch( Some(pre), - &info_from_weight(Weight::from_ref_time(weight)), + &info_from_weight(Weight::from_parts(weight, 0)), &default_post_info(), len, &Ok(()) @@ -461,7 +461,7 @@ fn post_dispatch_fee_is_zero_if_pre_dispatch_fee_is_zero() { let base_weight = 1; ExtBuilder::default() .balance_factor(100) - .base_weight(Weight::from_ref_time(base_weight)) + .base_weight(Weight::from_parts(base_weight, 0)) .build() .execute_with(|| { // create the asset @@ -518,7 +518,7 @@ fn post_dispatch_fee_is_zero_if_unsigned_pre_dispatch_fee_is_zero() { let base_weight = 1; ExtBuilder::default() .balance_factor(100) - .base_weight(Weight::from_ref_time(base_weight)) + .base_weight(Weight::from_parts(base_weight, 0)) .build() .execute_with(|| { // create the asset @@ -542,7 +542,7 @@ fn post_dispatch_fee_is_zero_if_unsigned_pre_dispatch_fee_is_zero() { let len = 1; ChargeAssetTxPayment::::pre_dispatch_unsigned( CALL, - &info_from_weight(Weight::from_ref_time(weight)), + &info_from_weight(Weight::from_parts(weight, 0)), len, ) .unwrap(); @@ -553,7 +553,7 @@ fn post_dispatch_fee_is_zero_if_unsigned_pre_dispatch_fee_is_zero() { // initial fee) assert_ok!(ChargeAssetTxPayment::::post_dispatch( None, - &info_from_weight(Weight::from_ref_time(weight)), + &info_from_weight(Weight::from_parts(weight, 0)), &post_info_from_pays(Pays::Yes), len, &Ok(()) diff --git a/frame/transaction-payment/rpc/Cargo.toml b/frame/transaction-payment/rpc/Cargo.toml index b9bf226e2da9..28eb562e97d0 100644 --- a/frame/transaction-payment/rpc/Cargo.toml +++ b/frame/transaction-payment/rpc/Cargo.toml @@ -13,12 +13,12 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } pallet-transaction-payment-rpc-runtime-api = { version = "4.0.0-dev", path = "./runtime-api" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } sp-rpc = { version = "6.0.0", path = "../../../primitives/rpc" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } -sp-weights = { version = "4.0.0", path = "../../../primitives/weights" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } +sp-weights = { version = "20.0.0", path = "../../../primitives/weights" } diff --git a/frame/transaction-payment/rpc/runtime-api/Cargo.toml b/frame/transaction-payment/rpc/runtime-api/Cargo.toml index 854e4310b46d..5cb7ebd7a2b8 100644 --- a/frame/transaction-payment/rpc/runtime-api/Cargo.toml +++ b/frame/transaction-payment/rpc/runtime-api/Cargo.toml @@ -13,11 +13,11 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../transaction-payment" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../../primitives/api" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../../primitives/runtime" } -sp-weights = { version = "4.0.0", default-features = false, path = "../../../../primitives/weights" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../../../primitives/runtime" } +sp-weights = { version = "20.0.0", default-features = false, path = "../../../../primitives/weights" } [features] default = ["std"] diff --git a/frame/transaction-payment/rpc/runtime-api/src/lib.rs b/frame/transaction-payment/rpc/runtime-api/src/lib.rs index 760f9e369341..0d9c3338250e 100644 --- a/frame/transaction-payment/rpc/runtime-api/src/lib.rs +++ b/frame/transaction-payment/rpc/runtime-api/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,12 +25,10 @@ use sp_runtime::traits::MaybeDisplay; pub use pallet_transaction_payment::{FeeDetails, InclusionFee, RuntimeDispatchInfo}; sp_api::decl_runtime_apis! { - #[api_version(3)] + #[api_version(4)] pub trait TransactionPaymentApi where Balance: Codec + MaybeDisplay, { - #[changed_in(2)] - fn query_info(uxt: Block::Extrinsic, len: u32) -> RuntimeDispatchInfo; fn query_info(uxt: Block::Extrinsic, len: u32) -> RuntimeDispatchInfo; fn query_fee_details(uxt: Block::Extrinsic, len: u32) -> FeeDetails; fn query_weight_to_fee(weight: sp_weights::Weight) -> Balance; diff --git a/frame/transaction-payment/rpc/src/lib.rs b/frame/transaction-payment/rpc/src/lib.rs index 19007d37963e..7f8ed4b80267 100644 --- a/frame/transaction-payment/rpc/src/lib.rs +++ b/frame/transaction-payment/rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,14 +26,11 @@ use jsonrpsee::{ types::error::{CallError, ErrorCode, ErrorObject}, }; use pallet_transaction_payment_rpc_runtime_api::{FeeDetails, InclusionFee, RuntimeDispatchInfo}; -use sp_api::{ApiExt, ProvideRuntimeApi}; +use sp_api::ProvideRuntimeApi; use sp_blockchain::HeaderBackend; use sp_core::Bytes; use sp_rpc::number::NumberOrHex; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, MaybeDisplay}, -}; +use sp_runtime::traits::{Block as BlockT, MaybeDisplay}; pub use pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi as TransactionPaymentRuntimeApi; @@ -84,7 +81,7 @@ impl From for i32 { impl TransactionPaymentApiServer< ::Hash, - RuntimeDispatchInfo, + RuntimeDispatchInfo, > for TransactionPayment where Block: BlockT, @@ -96,9 +93,9 @@ where &self, encoded_xt: Bytes, at: Option, - ) -> RpcResult> { + ) -> RpcResult> { let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); + let at_hash = at.unwrap_or_else(|| self.client.info().best_hash); let encoded_len = encoded_xt.len() as u32; @@ -118,32 +115,15 @@ where )) } - let api_version = api - .api_version::>(&at) - .map_err(|e| map_err(e, "Failed to get transaction payment runtime api version"))? - .ok_or_else(|| { - CallError::Custom(ErrorObject::owned( - Error::RuntimeError.into(), - "Transaction payment runtime api wasn't found in the runtime", - None::, - )) - })?; - - if api_version < 2 { - #[allow(deprecated)] - api.query_info_before_version_2(&at, uxt, encoded_len) - .map_err(|e| map_err(e, "Unable to query dispatch info.").into()) - } else { - let res = api - .query_info(&at, uxt, encoded_len) - .map_err(|e| map_err(e, "Unable to query dispatch info."))?; - - Ok(RuntimeDispatchInfo { - weight: sp_weights::OldWeight(res.weight.ref_time()), - class: res.class, - partial_fee: res.partial_fee, - }) - } + let res = api + .query_info(at_hash, uxt, encoded_len) + .map_err(|e| map_err(e, "Unable to query dispatch info."))?; + + Ok(RuntimeDispatchInfo { + weight: res.weight, + class: res.class, + partial_fee: res.partial_fee, + }) } fn query_fee_details( @@ -152,7 +132,7 @@ where at: Option, ) -> RpcResult> { let api = self.client.runtime_api(); - let at = BlockId::hash(at.unwrap_or_else(|| self.client.info().best_hash)); + let at_hash = at.unwrap_or_else(|| self.client.info().best_hash); let encoded_len = encoded_xt.len() as u32; @@ -163,7 +143,7 @@ where Some(format!("{:?}", e)), )) })?; - let fee_details = api.query_fee_details(&at, uxt, encoded_len).map_err(|e| { + let fee_details = api.query_fee_details(at_hash, uxt, encoded_len).map_err(|e| { CallError::Custom(ErrorObject::owned( Error::RuntimeError.into(), "Unable to query fee details.", diff --git a/frame/transaction-payment/src/lib.rs b/frame/transaction-payment/src/lib.rs index fb4381c5242c..0cc7bdad46e0 100644 --- a/frame/transaction-payment/src/lib.rs +++ b/frame/transaction-payment/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -50,6 +50,15 @@ use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; +use frame_support::{ + dispatch::{ + DispatchClass, DispatchInfo, DispatchResult, GetDispatchInfo, Pays, PostDispatchInfo, + }, + traits::{Defensive, EstimateCallFee, Get}, + weights::{Weight, WeightToFee}, +}; +pub use pallet::*; +pub use payment::*; use sp_runtime::{ traits::{ Convert, DispatchInfoOf, Dispatchable, One, PostDispatchInfoOf, SaturatedConversion, @@ -58,17 +67,10 @@ use sp_runtime::{ transaction_validity::{ TransactionPriority, TransactionValidity, TransactionValidityError, ValidTransaction, }, - FixedPointNumber, FixedPointOperand, FixedU128, Perquintill, RuntimeDebug, + FixedPointNumber, FixedPointOperand, FixedU128, Perbill, Perquintill, RuntimeDebug, }; use sp_std::prelude::*; - -use frame_support::{ - dispatch::{ - DispatchClass, DispatchInfo, DispatchResult, GetDispatchInfo, Pays, PostDispatchInfo, - }, - traits::{EstimateCallFee, Get}, - weights::{Weight, WeightToFee}, -}; +pub use types::{FeeDetails, InclusionFee, RuntimeDispatchInfo}; #[cfg(test)] mod mock; @@ -78,10 +80,6 @@ mod tests; mod payment; mod types; -pub use pallet::*; -pub use payment::*; -pub use types::{FeeDetails, InclusionFee, RuntimeDispatchInfo}; - /// Fee multiplier. pub type Multiplier = FixedU128; @@ -108,10 +106,17 @@ type BalanceOf = <::OnChargeTransaction as OnChargeTransaction= v * k * (s - s')` @@ -207,15 +212,30 @@ where let normal_block_weight = current_block_weight.get(DispatchClass::Normal).min(normal_max_weight); - // TODO: Handle all weight dimensions - let normal_max_weight = normal_max_weight.ref_time(); - let normal_block_weight = normal_block_weight.ref_time(); - - let s = S::get(); - let v = V::get(); - - let target_weight = (s * normal_max_weight) as u128; - let block_weight = normal_block_weight as u128; + // Normalize dimensions so they can be compared. Ensure (defensive) max weight is non-zero. + let normalized_ref_time = Perbill::from_rational( + normal_block_weight.ref_time(), + normal_max_weight.ref_time().max(1), + ); + let normalized_proof_size = Perbill::from_rational( + normal_block_weight.proof_size(), + normal_max_weight.proof_size().max(1), + ); + + // Pick the limiting dimension. If the proof size is the limiting dimension, then the + // multiplier is adjusted by the proof size. Otherwise, it is adjusted by the ref time. + let (normal_limiting_dimension, max_limiting_dimension) = + if normalized_ref_time < normalized_proof_size { + (normal_block_weight.proof_size(), normal_max_weight.proof_size()) + } else { + (normal_block_weight.ref_time(), normal_max_weight.ref_time()) + }; + + let target_block_fullness = S::get(); + let adjustment_variable = V::get(); + + let target_weight = (target_block_fullness * max_limiting_dimension) as u128; + let block_weight = normal_limiting_dimension as u128; // determines if the first_term is positive let positive = block_weight >= target_weight; @@ -223,12 +243,13 @@ where // defensive only, a test case assures that the maximum weight diff can fit in Multiplier // without any saturation. - let diff = Multiplier::saturating_from_rational(diff_abs, normal_max_weight.max(1)); + let diff = Multiplier::saturating_from_rational(diff_abs, max_limiting_dimension.max(1)); let diff_squared = diff.saturating_mul(diff); - let v_squared_2 = v.saturating_mul(v) / Multiplier::saturating_from_integer(2); + let v_squared_2 = adjustment_variable.saturating_mul(adjustment_variable) / + Multiplier::saturating_from_integer(2); - let first_term = v.saturating_mul(diff); + let first_term = adjustment_variable.saturating_mul(diff); let second_term = v_squared_2.saturating_mul(diff_squared); if positive { @@ -290,12 +311,12 @@ const MULTIPLIER_DEFAULT_VALUE: Multiplier = Multiplier::from_u32(1); #[frame_support::pallet] pub mod pallet { - use super::*; use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; + use super::*; + #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::config] @@ -359,19 +380,20 @@ pub mod pallet { pub(super) type StorageVersion = StorageValue<_, Releases, ValueQuery>; #[pallet::genesis_config] - pub struct GenesisConfig { + pub struct GenesisConfig { pub multiplier: Multiplier, + #[serde(skip)] + pub _config: sp_std::marker::PhantomData, } - #[cfg(feature = "std")] - impl Default for GenesisConfig { + impl Default for GenesisConfig { fn default() -> Self { - Self { multiplier: MULTIPLIER_DEFAULT_VALUE } + Self { multiplier: MULTIPLIER_DEFAULT_VALUE, _config: Default::default() } } } #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { StorageVersion::::put(Releases::V2); NextFeeMultiplier::::put(self.multiplier); @@ -388,12 +410,13 @@ pub mod pallet { #[pallet::hooks] impl Hooks> for Pallet { - fn on_finalize(_: T::BlockNumber) { + fn on_finalize(_: frame_system::pallet_prelude::BlockNumberFor) { >::mutate(|fm| { *fm = T::FeeMultiplierUpdate::convert(*fm); }); } + #[cfg(feature = "std")] fn integrity_test() { // given weight == u64, we build multipliers from `diff` of two weight values, which can // at most be maximum block weight. Make sure that this can fit in a multiplier without @@ -419,25 +442,21 @@ pub mod pallet { return } - #[cfg(any(feature = "std", test))] - sp_io::TestExternalities::new_empty().execute_with(|| { - // This is the minimum value of the multiplier. Make sure that if we collapse to - // this value, we can recover with a reasonable amount of traffic. For this test we - // assert that if we collapse to minimum, the trend will be positive with a weight - // value which is 1% more than the target. - let min_value = T::FeeMultiplierUpdate::min(); - - let target = target + addition; - - >::set_block_consumed_resources(target, 0); - let next = T::FeeMultiplierUpdate::convert(min_value); - assert!( - next > min_value, - "The minimum bound of the multiplier is too low. When \ - block saturation is more than target by 1% and multiplier is minimal then \ - the multiplier doesn't increase." - ); - }); + // This is the minimum value of the multiplier. Make sure that if we collapse to this + // value, we can recover with a reasonable amount of traffic. For this test we assert + // that if we collapse to minimum, the trend will be positive with a weight value which + // is 1% more than the target. + let min_value = T::FeeMultiplierUpdate::min(); + let target = target + addition; + + >::set_block_consumed_resources(target, 0); + let next = T::FeeMultiplierUpdate::convert(min_value); + assert!( + next > min_value, + "The minimum bound of the multiplier is too low. When \ + block saturation is more than target by 1% and multiplier is minimal then \ + the multiplier doesn't increase." + ); } } } @@ -614,7 +633,7 @@ where /// Compute the length portion of a fee by invoking the configured `LengthToFee` impl. pub fn length_to_fee(length: u32) -> BalanceOf { - T::LengthToFee::weight_to_fee(&Weight::from_ref_time(length as u64)) + T::LengthToFee::weight_to_fee(&Weight::from_parts(length as u64, 0)) } /// Compute the unadjusted portion of the weight fee by invoking the configured `WeightToFee` @@ -712,19 +731,20 @@ where tip: BalanceOf, final_fee: BalanceOf, ) -> TransactionPriority { - // Calculate how many such extrinsics we could fit into an empty block and take - // the limitting factor. + // Calculate how many such extrinsics we could fit into an empty block and take the + // limiting factor. let max_block_weight = T::BlockWeights::get().max_block; let max_block_length = *T::BlockLength::get().max.get(info.class) as u64; - // TODO: Take into account all dimensions of weight - let max_block_weight = max_block_weight.ref_time(); - let info_weight = info.weight.ref_time(); - - let bounded_weight = info_weight.clamp(1, max_block_weight); + // bounded_weight is used as a divisor later so we keep it non-zero. + let bounded_weight = info.weight.max(Weight::from_parts(1, 1)).min(max_block_weight); let bounded_length = (len as u64).clamp(1, max_block_length); - let max_tx_per_block_weight = max_block_weight / bounded_weight; + // returns the scarce resource, i.e. the one that is limiting the number of transactions. + let max_tx_per_block_weight = max_block_weight + .checked_div_per_component(&bounded_weight) + .defensive_proof("bounded_weight is non-zero; qed") + .unwrap_or(1); let max_tx_per_block_length = max_block_length / bounded_length; // Given our current knowledge this value is going to be in a reasonable range - i.e. // less than 10^9 (2^30), so multiplying by the `tip` value is unlikely to overflow the diff --git a/frame/transaction-payment/src/mock.rs b/frame/transaction-payment/src/mock.rs index e214458b3766..97253be46305 100644 --- a/frame/transaction-payment/src/mock.rs +++ b/frame/transaction-payment/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,10 +19,7 @@ use super::*; use crate as pallet_transaction_payment; use sp_core::H256; -use sp_runtime::{ - testing::Header, - traits::{BlakeTwo256, IdentityLookup}, -}; +use sp_runtime::traits::{BlakeTwo256, IdentityLookup}; use frame_support::{ dispatch::DispatchClass, @@ -33,23 +30,19 @@ use frame_support::{ use frame_system as system; use pallet_balances::Call as BalancesCall; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Runtime where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub struct Runtime { - System: system::{Pallet, Call, Config, Storage, Event}, + System: system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, TransactionPayment: pallet_transaction_payment::{Pallet, Storage, Event}, } ); pub(crate) const CALL: &::RuntimeCall = - &RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 69 }); + &RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 2, value: 69 }); parameter_types! { pub(crate) static ExtrinsicBaseWeight: Weight = Weight::zero(); @@ -64,7 +57,7 @@ impl Get for BlockWeights { weights.base_extrinsic = ExtrinsicBaseWeight::get().into(); }) .for_class(DispatchClass::non_mandatory(), |weights| { - weights.max_total = Weight::from_ref_time(1024).set_proof_size(u64::MAX).into(); + weights.max_total = Weight::from_parts(1024, u64::MAX).into(); }) .build_or_panic() } @@ -82,14 +75,13 @@ impl frame_system::Config for Runtime { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -113,6 +105,10 @@ impl pallet_balances::Config for Runtime { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } impl WeightToFeeT for WeightToFee { diff --git a/frame/transaction-payment/src/tests.rs b/frame/transaction-payment/src/tests.rs index ee54c1130e69..d3a1721ccb99 100644 --- a/frame/transaction-payment/src/tests.rs +++ b/frame/transaction-payment/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,12 +20,14 @@ use crate as pallet_transaction_payment; use codec::Encode; -use sp_runtime::{testing::TestXt, traits::One, transaction_validity::InvalidTransaction}; +use sp_runtime::{ + testing::TestXt, traits::One, transaction_validity::InvalidTransaction, BuildStorage, +}; use frame_support::{ assert_noop, assert_ok, dispatch::{DispatchClass, DispatchInfo, GetDispatchInfo, PostDispatchInfo}, - traits::{Currency, GenesisBuild}, + traits::Currency, weights::Weight, }; use frame_system as system; @@ -80,7 +82,7 @@ impl ExtBuilder { } pub fn build(self) -> sp_io::TestExternalities { self.set_constants(); - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: if self.balance_factor > 0 { vec![ @@ -99,8 +101,9 @@ impl ExtBuilder { .unwrap(); if let Some(multiplier) = self.initial_multiplier { - let genesis = pallet::GenesisConfig { multiplier }; - GenesisBuild::::assimilate_storage(&genesis, &mut t).unwrap(); + pallet::GenesisConfig:: { multiplier, ..Default::default() } + .assimilate_storage(&mut t) + .unwrap(); } t.into() @@ -129,18 +132,18 @@ fn default_post_info() -> PostDispatchInfo { fn signed_extension_transaction_payment_work() { ExtBuilder::default() .balance_factor(10) - .base_weight(Weight::from_ref_time(5)) + .base_weight(Weight::from_parts(5, 0)) .build() .execute_with(|| { let len = 10; let pre = ChargeTransactionPayment::::from(0) - .pre_dispatch(&1, CALL, &info_from_weight(Weight::from_ref_time(5)), len) + .pre_dispatch(&1, CALL, &info_from_weight(Weight::from_parts(5, 0)), len) .unwrap(); assert_eq!(Balances::free_balance(1), 100 - 5 - 5 - 10); assert_ok!(ChargeTransactionPayment::::post_dispatch( Some(pre), - &info_from_weight(Weight::from_ref_time(5)), + &info_from_weight(Weight::from_parts(5, 0)), &default_post_info(), len, &Ok(()) @@ -152,14 +155,14 @@ fn signed_extension_transaction_payment_work() { FeeUnbalancedAmount::mutate(|a| *a = 0); let pre = ChargeTransactionPayment::::from(5 /* tipped */) - .pre_dispatch(&2, CALL, &info_from_weight(Weight::from_ref_time(100)), len) + .pre_dispatch(&2, CALL, &info_from_weight(Weight::from_parts(100, 0)), len) .unwrap(); assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 100 - 5); assert_ok!(ChargeTransactionPayment::::post_dispatch( Some(pre), - &info_from_weight(Weight::from_ref_time(100)), - &post_info_from_weight(Weight::from_ref_time(50)), + &info_from_weight(Weight::from_parts(100, 0)), + &post_info_from_weight(Weight::from_parts(50, 0)), len, &Ok(()) )); @@ -173,22 +176,22 @@ fn signed_extension_transaction_payment_work() { fn signed_extension_transaction_payment_multiplied_refund_works() { ExtBuilder::default() .balance_factor(10) - .base_weight(Weight::from_ref_time(5)) + .base_weight(Weight::from_parts(5, 0)) .build() .execute_with(|| { let len = 10; >::put(Multiplier::saturating_from_rational(3, 2)); let pre = ChargeTransactionPayment::::from(5 /* tipped */) - .pre_dispatch(&2, CALL, &info_from_weight(Weight::from_ref_time(100)), len) + .pre_dispatch(&2, CALL, &info_from_weight(Weight::from_parts(100, 0)), len) .unwrap(); // 5 base fee, 10 byte fee, 3/2 * 100 weight fee, 5 tip assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 150 - 5); assert_ok!(ChargeTransactionPayment::::post_dispatch( Some(pre), - &info_from_weight(Weight::from_ref_time(100)), - &post_info_from_weight(Weight::from_ref_time(50)), + &info_from_weight(Weight::from_parts(100, 0)), + &post_info_from_weight(Weight::from_parts(50, 0)), len, &Ok(()) )); @@ -219,7 +222,7 @@ fn signed_extension_transaction_payment_is_bounded() { #[test] fn signed_extension_allows_free_transactions() { ExtBuilder::default() - .base_weight(Weight::from_ref_time(100)) + .base_weight(Weight::from_parts(100, 0)) .balance_factor(0) .build() .execute_with(|| { @@ -230,7 +233,7 @@ fn signed_extension_allows_free_transactions() { // This is a completely free (and thus wholly insecure/DoS-ridden) transaction. let operational_transaction = DispatchInfo { - weight: Weight::from_ref_time(0), + weight: Weight::from_parts(0, 0), class: DispatchClass::Operational, pays_fee: Pays::No, }; @@ -243,7 +246,7 @@ fn signed_extension_allows_free_transactions() { // like a InsecureFreeNormal let free_transaction = DispatchInfo { - weight: Weight::from_ref_time(0), + weight: Weight::from_parts(0, 0), class: DispatchClass::Normal, pays_fee: Pays::Yes, }; @@ -262,7 +265,7 @@ fn signed_extension_allows_free_transactions() { #[test] fn signed_ext_length_fee_is_also_updated_per_congestion() { ExtBuilder::default() - .base_weight(Weight::from_ref_time(5)) + .base_weight(Weight::from_parts(5, 0)) .balance_factor(10) .build() .execute_with(|| { @@ -271,7 +274,7 @@ fn signed_ext_length_fee_is_also_updated_per_congestion() { let len = 10; assert_ok!(ChargeTransactionPayment::::from(10) // tipped - .pre_dispatch(&1, CALL, &info_from_weight(Weight::from_ref_time(3)), len)); + .pre_dispatch(&1, CALL, &info_from_weight(Weight::from_parts(3, 0)), len)); assert_eq!( Balances::free_balance(1), 100 // original @@ -285,7 +288,7 @@ fn signed_ext_length_fee_is_also_updated_per_congestion() { #[test] fn query_info_and_fee_details_works() { - let call = RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 69 }); + let call = RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 2, value: 69 }); let origin = 111111; let extra = (); let xt = TestXt::new(call.clone(), Some((origin, extra))); @@ -297,7 +300,7 @@ fn query_info_and_fee_details_works() { let unsigned_xt_info = unsigned_xt.get_dispatch_info(); ExtBuilder::default() - .base_weight(Weight::from_ref_time(5)) + .base_weight(Weight::from_parts(5, 0)) .weight_fee(2) .build() .execute_with(|| { @@ -348,13 +351,13 @@ fn query_info_and_fee_details_works() { #[test] fn query_call_info_and_fee_details_works() { - let call = RuntimeCall::Balances(BalancesCall::transfer { dest: 2, value: 69 }); + let call = RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 2, value: 69 }); let info = call.get_dispatch_info(); let encoded_call = call.encode(); let len = encoded_call.len() as u32; ExtBuilder::default() - .base_weight(Weight::from_ref_time(5)) + .base_weight(Weight::from_parts(5, 0)) .weight_fee(2) .build() .execute_with(|| { @@ -392,7 +395,7 @@ fn query_call_info_and_fee_details_works() { #[test] fn compute_fee_works_without_multiplier() { ExtBuilder::default() - .base_weight(Weight::from_ref_time(100)) + .base_weight(Weight::from_parts(100, 0)) .byte_fee(10) .balance_factor(0) .build() @@ -402,14 +405,14 @@ fn compute_fee_works_without_multiplier() { // Tip only, no fees works let dispatch_info = DispatchInfo { - weight: Weight::from_ref_time(0), + weight: Weight::from_parts(0, 0), class: DispatchClass::Operational, pays_fee: Pays::No, }; assert_eq!(Pallet::::compute_fee(0, &dispatch_info, 10), 10); // No tip, only base fee works let dispatch_info = DispatchInfo { - weight: Weight::from_ref_time(0), + weight: Weight::from_parts(0, 0), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -420,7 +423,7 @@ fn compute_fee_works_without_multiplier() { assert_eq!(Pallet::::compute_fee(42, &dispatch_info, 0), 520); // Weight fee + base fee works let dispatch_info = DispatchInfo { - weight: Weight::from_ref_time(1000), + weight: Weight::from_parts(1000, 0), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -431,7 +434,7 @@ fn compute_fee_works_without_multiplier() { #[test] fn compute_fee_works_with_multiplier() { ExtBuilder::default() - .base_weight(Weight::from_ref_time(100)) + .base_weight(Weight::from_parts(100, 0)) .byte_fee(10) .balance_factor(0) .build() @@ -440,7 +443,7 @@ fn compute_fee_works_with_multiplier() { >::put(Multiplier::saturating_from_rational(3, 2)); // Base fee is unaffected by multiplier let dispatch_info = DispatchInfo { - weight: Weight::from_ref_time(0), + weight: Weight::from_parts(0, 0), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -448,7 +451,7 @@ fn compute_fee_works_with_multiplier() { // Everything works together :) let dispatch_info = DispatchInfo { - weight: Weight::from_ref_time(123), + weight: Weight::from_parts(123, 0), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -463,7 +466,7 @@ fn compute_fee_works_with_multiplier() { #[test] fn compute_fee_works_with_negative_multiplier() { ExtBuilder::default() - .base_weight(Weight::from_ref_time(100)) + .base_weight(Weight::from_parts(100, 0)) .byte_fee(10) .balance_factor(0) .build() @@ -473,7 +476,7 @@ fn compute_fee_works_with_negative_multiplier() { // Base fee is unaffected by multiplier. let dispatch_info = DispatchInfo { - weight: Weight::from_ref_time(0), + weight: Weight::from_parts(0, 0), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -481,7 +484,7 @@ fn compute_fee_works_with_negative_multiplier() { // Everything works together. let dispatch_info = DispatchInfo { - weight: Weight::from_ref_time(123), + weight: Weight::from_parts(123, 0), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -496,7 +499,7 @@ fn compute_fee_works_with_negative_multiplier() { #[test] fn compute_fee_does_not_overflow() { ExtBuilder::default() - .base_weight(Weight::from_ref_time(100)) + .base_weight(Weight::from_parts(100, 0)) .byte_fee(10) .balance_factor(0) .build() @@ -518,25 +521,29 @@ fn compute_fee_does_not_overflow() { fn refund_does_not_recreate_account() { ExtBuilder::default() .balance_factor(10) - .base_weight(Weight::from_ref_time(5)) + .base_weight(Weight::from_parts(5, 0)) .build() .execute_with(|| { // So events are emitted System::set_block_number(10); let len = 10; let pre = ChargeTransactionPayment::::from(5 /* tipped */) - .pre_dispatch(&2, CALL, &info_from_weight(Weight::from_ref_time(100)), len) + .pre_dispatch(&2, CALL, &info_from_weight(Weight::from_parts(100, 0)), len) .unwrap(); assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 100 - 5); // kill the account between pre and post dispatch - assert_ok!(Balances::transfer(Some(2).into(), 3, Balances::free_balance(2))); + assert_ok!(Balances::transfer_allow_death( + Some(2).into(), + 3, + Balances::free_balance(2) + )); assert_eq!(Balances::free_balance(2), 0); assert_ok!(ChargeTransactionPayment::::post_dispatch( Some(pre), - &info_from_weight(Weight::from_ref_time(100)), - &post_info_from_weight(Weight::from_ref_time(50)), + &info_from_weight(Weight::from_parts(100, 0)), + &post_info_from_weight(Weight::from_parts(50, 0)), len, &Ok(()) )); @@ -558,19 +565,19 @@ fn refund_does_not_recreate_account() { fn actual_weight_higher_than_max_refunds_nothing() { ExtBuilder::default() .balance_factor(10) - .base_weight(Weight::from_ref_time(5)) + .base_weight(Weight::from_parts(5, 0)) .build() .execute_with(|| { let len = 10; let pre = ChargeTransactionPayment::::from(5 /* tipped */) - .pre_dispatch(&2, CALL, &info_from_weight(Weight::from_ref_time(100)), len) + .pre_dispatch(&2, CALL, &info_from_weight(Weight::from_parts(100, 0)), len) .unwrap(); assert_eq!(Balances::free_balance(2), 200 - 5 - 10 - 100 - 5); assert_ok!(ChargeTransactionPayment::::post_dispatch( Some(pre), - &info_from_weight(Weight::from_ref_time(100)), - &post_info_from_weight(Weight::from_ref_time(101)), + &info_from_weight(Weight::from_parts(100, 0)), + &post_info_from_weight(Weight::from_parts(101, 0)), len, &Ok(()) )); @@ -582,14 +589,14 @@ fn actual_weight_higher_than_max_refunds_nothing() { fn zero_transfer_on_free_transaction() { ExtBuilder::default() .balance_factor(10) - .base_weight(Weight::from_ref_time(5)) + .base_weight(Weight::from_parts(5, 0)) .build() .execute_with(|| { // So events are emitted System::set_block_number(10); let len = 10; let dispatch_info = DispatchInfo { - weight: Weight::from_ref_time(100), + weight: Weight::from_parts(100, 0), pays_fee: Pays::No, class: DispatchClass::Normal, }; @@ -621,11 +628,11 @@ fn zero_transfer_on_free_transaction() { fn refund_consistent_with_actual_weight() { ExtBuilder::default() .balance_factor(10) - .base_weight(Weight::from_ref_time(7)) + .base_weight(Weight::from_parts(7, 0)) .build() .execute_with(|| { - let info = info_from_weight(Weight::from_ref_time(100)); - let post_info = post_info_from_weight(Weight::from_ref_time(33)); + let info = info_from_weight(Weight::from_parts(100, 0)); + let post_info = post_info_from_weight(Weight::from_parts(33, 0)); let prev_balance = Balances::free_balance(2); let len = 10; let tip = 5; @@ -662,7 +669,7 @@ fn should_alter_operational_priority() { ExtBuilder::default().balance_factor(100).build().execute_with(|| { let normal = DispatchInfo { - weight: Weight::from_ref_time(100), + weight: Weight::from_parts(100, 0), class: DispatchClass::Normal, pays_fee: Pays::Yes, }; @@ -683,7 +690,7 @@ fn should_alter_operational_priority() { ExtBuilder::default().balance_factor(100).build().execute_with(|| { let op = DispatchInfo { - weight: Weight::from_ref_time(100), + weight: Weight::from_parts(100, 0), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -708,7 +715,7 @@ fn no_tip_has_some_priority() { ExtBuilder::default().balance_factor(100).build().execute_with(|| { let normal = DispatchInfo { - weight: Weight::from_ref_time(100), + weight: Weight::from_parts(100, 0), class: DispatchClass::Normal, pays_fee: Pays::Yes, }; @@ -722,7 +729,7 @@ fn no_tip_has_some_priority() { ExtBuilder::default().balance_factor(100).build().execute_with(|| { let op = DispatchInfo { - weight: Weight::from_ref_time(100), + weight: Weight::from_parts(100, 0), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -742,7 +749,7 @@ fn higher_tip_have_higher_priority() { let len = 10; ExtBuilder::default().balance_factor(100).build().execute_with(|| { let normal = DispatchInfo { - weight: Weight::from_ref_time(100), + weight: Weight::from_parts(100, 0), class: DispatchClass::Normal, pays_fee: Pays::Yes, }; @@ -754,7 +761,7 @@ fn higher_tip_have_higher_priority() { ExtBuilder::default().balance_factor(100).build().execute_with(|| { let op = DispatchInfo { - weight: Weight::from_ref_time(100), + weight: Weight::from_parts(100, 0), class: DispatchClass::Operational, pays_fee: Pays::Yes, }; @@ -781,10 +788,10 @@ fn higher_tip_have_higher_priority() { fn post_info_can_change_pays_fee() { ExtBuilder::default() .balance_factor(10) - .base_weight(Weight::from_ref_time(7)) + .base_weight(Weight::from_parts(7, 0)) .build() .execute_with(|| { - let info = info_from_weight(Weight::from_ref_time(100)); + let info = info_from_weight(Weight::from_parts(100, 0)); let post_info = post_info_from_pays(Pays::No); let prev_balance = Balances::free_balance(2); let len = 10; diff --git a/frame/transaction-payment/src/types.rs b/frame/transaction-payment/src/types.rs index d1a480b64e11..cbe85309b856 100644 --- a/frame/transaction-payment/src/types.rs +++ b/frame/transaction-payment/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,13 +21,15 @@ use codec::{Decode, Encode}; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; +use scale_info::TypeInfo; + use sp_runtime::traits::{AtLeast32BitUnsigned, Zero}; use sp_std::prelude::*; use frame_support::dispatch::DispatchClass; /// The base fee and adjusted weight and length fees constitute the _inclusion fee_. -#[derive(Encode, Decode, Clone, Eq, PartialEq)] +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] #[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] pub struct InclusionFee { @@ -63,7 +65,7 @@ impl InclusionFee { /// - (Optional) `inclusion_fee`: Only the `Pays::Yes` transaction can have the inclusion fee. /// - `tip`: If included in the transaction, the tip will be added on top. Only signed /// transactions can have a tip. -#[derive(Encode, Decode, Clone, Eq, PartialEq)] +#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] #[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] pub struct FeeDetails { @@ -91,7 +93,7 @@ impl FeeDetails { /// Information related to a dispatchable's class, weight, and fee that can be queried from the /// runtime. -#[derive(Eq, PartialEq, Encode, Decode, Default)] +#[derive(Eq, PartialEq, Encode, Decode, Default, TypeInfo)] #[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] #[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] #[cfg_attr( @@ -142,7 +144,7 @@ mod tests { #[test] fn should_serialize_and_deserialize_properly_with_string() { let info = RuntimeDispatchInfo { - weight: Weight::from_ref_time(5), + weight: Weight::from_parts(5, 0), class: DispatchClass::Normal, partial_fee: 1_000_000_u64, }; @@ -160,7 +162,7 @@ mod tests { #[test] fn should_serialize_and_deserialize_properly_large_value() { let info = RuntimeDispatchInfo { - weight: Weight::from_ref_time(5), + weight: Weight::from_parts(5, 0), class: DispatchClass::Normal, partial_fee: u128::max_value(), }; diff --git a/frame/transaction-storage/Cargo.toml b/frame/transaction-storage/Cargo.toml index 527ff4f24016..0d258a3d83bd 100644 --- a/frame/transaction-storage/Cargo.toml +++ b/frame/transaction-storage/Cargo.toml @@ -3,7 +3,7 @@ name = "pallet-transaction-storage" version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" -license = "Unlicense" +license = "Apache-2.0" homepage = "https://substrate.io" repository = "https://github.com/paritytech/substrate/" description = "Storage chain pallet" @@ -13,23 +13,23 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -array-bytes = { version = "4.1", optional = true } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", optional = true } +array-bytes = { version = "6.1", optional = true } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", optional = true } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../balances" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } sp-transaction-storage-proof = { version = "4.0.0-dev", default-features = false, path = "../../primitives/transaction-storage-proof" } log = { version = "0.4.17", default-features = false } [dev-dependencies] -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } sp-transaction-storage-proof = { version = "4.0.0-dev", default-features = true, path = "../../primitives/transaction-storage-proof" } [features] diff --git a/frame/transaction-storage/src/benchmarking.rs b/frame/transaction-storage/src/benchmarking.rs index c7fbd00fb565..fdbaeb1f9518 100644 --- a/frame/transaction-storage/src/benchmarking.rs +++ b/frame/transaction-storage/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,9 +20,9 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_benchmarking::v1::{benchmarks, whitelisted_caller}; use frame_support::traits::{Currency, Get, OnFinalize, OnInitialize}; -use frame_system::{EventRecord, Pallet as System, RawOrigin}; +use frame_system::{pallet_prelude::BlockNumberFor, EventRecord, Pallet as System, RawOrigin}; use sp_runtime::traits::{Bounded, One, Zero}; use sp_std::*; use sp_transaction_storage_proof::TransactionStorageProof; @@ -113,7 +113,7 @@ fn assert_last_event(generic_event: ::RuntimeEvent) { assert_eq!(event, &system_event); } -pub fn run_to_block(n: T::BlockNumber) { +pub fn run_to_block(n: frame_system::pallet_prelude::BlockNumberFor) { while frame_system::Pallet::::block_number() < n { crate::Pallet::::on_finalize(frame_system::Pallet::::block_number()); frame_system::Pallet::::on_finalize(frame_system::Pallet::::block_number()); @@ -144,7 +144,7 @@ benchmarks! { vec![0u8; T::MaxTransactionSize::get() as usize], )?; run_to_block::(1u32.into()); - }: _(RawOrigin::Signed(caller.clone()), T::BlockNumber::zero(), 0) + }: _(RawOrigin::Signed(caller.clone()), BlockNumberFor::::zero(), 0) verify { assert_last_event::(Event::Renewed { index: 0 }.into()); } @@ -159,7 +159,7 @@ benchmarks! { vec![0u8; T::MaxTransactionSize::get() as usize], )?; } - run_to_block::(StoragePeriod::::get() + T::BlockNumber::one()); + run_to_block::(StoragePeriod::::get() + BlockNumberFor::::one()); let encoded_proof = proof(); let proof = TransactionStorageProof::decode(&mut &*encoded_proof).unwrap(); }: check_proof(RawOrigin::None, proof) diff --git a/frame/transaction-storage/src/lib.rs b/frame/transaction-storage/src/lib.rs index cda7610efdf8..e784d20a0cfd 100644 --- a/frame/transaction-storage/src/lib.rs +++ b/frame/transaction-storage/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -141,12 +141,11 @@ pub mod pallet { } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::hooks] impl Hooks> for Pallet { - fn on_initialize(n: T::BlockNumber) -> Weight { + fn on_initialize(n: BlockNumberFor) -> Weight { // Drop obsolete roots. The proof for `obsolete` will be checked later // in this block, so we drop `obsolete` - 1. let period = >::get(); @@ -159,7 +158,7 @@ pub mod pallet { T::DbWeight::get().reads_writes(2, 4) } - fn on_finalize(n: T::BlockNumber) { + fn on_finalize(n: BlockNumberFor) { assert!( >::take() || { // Proof is not required for early or empty blocks. @@ -184,10 +183,9 @@ pub mod pallet { impl Pallet { /// Index and store data off chain. Minimum data size is 1 bytes, maximum is /// `MaxTransactionSize`. Data will be removed after `STORAGE_PERIOD` blocks, unless `renew` - /// is called. # - /// - n*log(n) of data size, as all data is pushed to an in-memory trie. - /// Additionally contains a DB write. - /// # + /// is called. + /// ## Complexity + /// - O(n*log(n)) of data size, as all data is pushed to an in-memory trie. #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::store(data.len() as u32))] pub fn store(origin: OriginFor, data: Vec) -> DispatchResult { @@ -234,14 +232,13 @@ pub mod pallet { /// previous `store` or `renew` call and transaction index within that block. /// Transaction index is emitted in the `Stored` or `Renewed` event. /// Applies same fees as `store`. - /// # - /// - Constant. - /// # + /// ## Complexity + /// - O(1). #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::renew())] pub fn renew( origin: OriginFor, - block: T::BlockNumber, + block: BlockNumberFor, index: u32, ) -> DispatchResultWithPostInfo { let sender = ensure_signed(origin)?; @@ -277,12 +274,10 @@ pub mod pallet { /// Check storage proof for block number `block_number() - StoragePeriod`. /// If such block does not exist the proof is expected to be `None`. - /// # + /// ## Complexity /// - Linear w.r.t the number of indexed transactions in the proved block for random /// probing. /// There's a DB read for each transaction. - /// Here we assume a maximum of 100 probed transactions. - /// # #[pallet::call_index(2)] #[pallet::weight((T::WeightInfo::check_proof_max(), DispatchClass::Mandatory))] pub fn check_proof( @@ -347,7 +342,7 @@ pub mod pallet { pub(super) type Transactions = StorageMap< _, Blake2_128Concat, - T::BlockNumber, + BlockNumberFor, BoundedVec, OptionQuery, >; @@ -355,7 +350,7 @@ pub mod pallet { /// Count indexed chunks for each block. #[pallet::storage] pub(super) type ChunkCount = - StorageMap<_, Blake2_128Concat, T::BlockNumber, u32, ValueQuery>; + StorageMap<_, Blake2_128Concat, BlockNumberFor, u32, ValueQuery>; #[pallet::storage] #[pallet::getter(fn byte_fee)] @@ -370,7 +365,7 @@ pub mod pallet { /// Storage period for data in blocks. Should match `sp_storage_proof::DEFAULT_STORAGE_PERIOD` /// for block authoring. #[pallet::storage] - pub(super) type StoragePeriod = StorageValue<_, T::BlockNumber, ValueQuery>; + pub(super) type StoragePeriod = StorageValue<_, BlockNumberFor, ValueQuery>; // Intermediates #[pallet::storage] @@ -385,10 +380,9 @@ pub mod pallet { pub struct GenesisConfig { pub byte_fee: BalanceOf, pub entry_fee: BalanceOf, - pub storage_period: T::BlockNumber, + pub storage_period: BlockNumberFor, } - #[cfg(feature = "std")] impl Default for GenesisConfig { fn default() -> Self { Self { @@ -400,7 +394,7 @@ pub mod pallet { } #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { >::put(&self.byte_fee); >::put(&self.entry_fee); diff --git a/frame/transaction-storage/src/mock.rs b/frame/transaction-storage/src/mock.rs index 8764b16c31d8..243e26b55905 100644 --- a/frame/transaction-storage/src/mock.rs +++ b/frame/transaction-storage/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,22 +24,17 @@ use crate::{ use frame_support::traits::{ConstU16, ConstU32, ConstU64, OnFinalize, OnInitialize}; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; pub type Block = frame_system::mocking::MockBlock; // Configure a mock runtime to test the pallet. frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Config, Storage, Event}, TransactionStorage: pallet_transaction_storage::{ Pallet, Call, Storage, Config, Inherent, Event @@ -53,13 +48,12 @@ impl frame_system::Config for Test { type BlockLength = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type DbWeight = (); @@ -84,6 +78,10 @@ impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = (); type ReserveIdentifier = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } impl pallet_transaction_storage::Config for Test { @@ -97,7 +95,7 @@ impl pallet_transaction_storage::Config for Test { } pub fn new_test_ext() -> sp_io::TestExternalities { - let t = GenesisConfig { + let t = RuntimeGenesisConfig { system: Default::default(), balances: pallet_balances::GenesisConfig:: { balances: vec![(1, 1000000000), (2, 100), (3, 100), (4, 100)], diff --git a/frame/transaction-storage/src/tests.rs b/frame/transaction-storage/src/tests.rs index 01b71a7851ac..43dfed81f88b 100644 --- a/frame/transaction-storage/src/tests.rs +++ b/frame/transaction-storage/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/transaction-storage/src/weights.rs b/frame/transaction-storage/src/weights.rs index 16d12aa75ab4..519317177c49 100644 --- a/frame/transaction-storage/src/weights.rs +++ b/frame/transaction-storage/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_transaction_storage //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_transaction_storage +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_transaction_storage. pub trait WeightInfo { @@ -55,78 +60,116 @@ pub trait WeightInfo { /// Weights for pallet_transaction_storage using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: TransactionStorage ByteFee (r:1 w:0) - // Storage: TransactionStorage EntryFee (r:1 w:0) - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) - // Storage: TransactionStorage BlockTransactions (r:1 w:1) + /// Storage: TransactionStorage ByteFee (r:1 w:0) + /// Proof: TransactionStorage ByteFee (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: TransactionStorage EntryFee (r:1 w:0) + /// Proof: TransactionStorage EntryFee (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: TransactionStorage BlockTransactions (r:1 w:1) + /// Proof: TransactionStorage BlockTransactions (max_values: Some(1), max_size: Some(36866), added: 37361, mode: MaxEncodedLen) /// The range of component `l` is `[1, 8388608]`. fn store(l: u32, ) -> Weight { - // Minimum execution time: 46_730 nanoseconds. - Weight::from_ref_time(46_922_000 as u64) - // Standard Error: 2 - .saturating_add(Weight::from_ref_time(5_601 as u64).saturating_mul(l as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `176` + // Estimated: `38351` + // Minimum execution time: 34_844_000 picoseconds. + Weight::from_parts(35_489_000, 38351) + // Standard Error: 11 + .saturating_add(Weight::from_parts(6_912, 0).saturating_mul(l.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: TransactionStorage Transactions (r:1 w:0) - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) - // Storage: TransactionStorage ByteFee (r:1 w:0) - // Storage: TransactionStorage EntryFee (r:1 w:0) - // Storage: TransactionStorage BlockTransactions (r:1 w:1) + /// Storage: TransactionStorage Transactions (r:1 w:0) + /// Proof: TransactionStorage Transactions (max_values: None, max_size: Some(36886), added: 39361, mode: MaxEncodedLen) + /// Storage: TransactionStorage ByteFee (r:1 w:0) + /// Proof: TransactionStorage ByteFee (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: TransactionStorage EntryFee (r:1 w:0) + /// Proof: TransactionStorage EntryFee (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: TransactionStorage BlockTransactions (r:1 w:1) + /// Proof: TransactionStorage BlockTransactions (max_values: Some(1), max_size: Some(36866), added: 37361, mode: MaxEncodedLen) fn renew() -> Weight { - // Minimum execution time: 56_802 nanoseconds. - Weight::from_ref_time(58_670_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `326` + // Estimated: `40351` + // Minimum execution time: 48_244_000 picoseconds. + Weight::from_parts(50_939_000, 40351) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: TransactionStorage ProofChecked (r:1 w:1) - // Storage: TransactionStorage StoragePeriod (r:1 w:0) - // Storage: TransactionStorage ChunkCount (r:1 w:0) - // Storage: System ParentHash (r:1 w:0) - // Storage: TransactionStorage Transactions (r:1 w:0) + /// Storage: TransactionStorage ProofChecked (r:1 w:1) + /// Proof: TransactionStorage ProofChecked (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: TransactionStorage StoragePeriod (r:1 w:0) + /// Proof: TransactionStorage StoragePeriod (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: TransactionStorage ChunkCount (r:1 w:0) + /// Proof: TransactionStorage ChunkCount (max_values: None, max_size: Some(24), added: 2499, mode: MaxEncodedLen) + /// Storage: System ParentHash (r:1 w:0) + /// Proof: System ParentHash (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: TransactionStorage Transactions (r:1 w:0) + /// Proof: TransactionStorage Transactions (max_values: None, max_size: Some(36886), added: 39361, mode: MaxEncodedLen) fn check_proof_max() -> Weight { - // Minimum execution time: 74_016 nanoseconds. - Weight::from_ref_time(94_111_000 as u64) - .saturating_add(T::DbWeight::get().reads(5 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `37145` + // Estimated: `40351` + // Minimum execution time: 80_913_000 picoseconds. + Weight::from_parts(84_812_000, 40351) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: TransactionStorage ByteFee (r:1 w:0) - // Storage: TransactionStorage EntryFee (r:1 w:0) - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) - // Storage: TransactionStorage BlockTransactions (r:1 w:1) + /// Storage: TransactionStorage ByteFee (r:1 w:0) + /// Proof: TransactionStorage ByteFee (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: TransactionStorage EntryFee (r:1 w:0) + /// Proof: TransactionStorage EntryFee (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: TransactionStorage BlockTransactions (r:1 w:1) + /// Proof: TransactionStorage BlockTransactions (max_values: Some(1), max_size: Some(36866), added: 37361, mode: MaxEncodedLen) /// The range of component `l` is `[1, 8388608]`. fn store(l: u32, ) -> Weight { - // Minimum execution time: 46_730 nanoseconds. - Weight::from_ref_time(46_922_000 as u64) - // Standard Error: 2 - .saturating_add(Weight::from_ref_time(5_601 as u64).saturating_mul(l as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `176` + // Estimated: `38351` + // Minimum execution time: 34_844_000 picoseconds. + Weight::from_parts(35_489_000, 38351) + // Standard Error: 11 + .saturating_add(Weight::from_parts(6_912, 0).saturating_mul(l.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: TransactionStorage Transactions (r:1 w:0) - // Storage: unknown [0x3a65787472696e7369635f696e646578] (r:1 w:0) - // Storage: TransactionStorage ByteFee (r:1 w:0) - // Storage: TransactionStorage EntryFee (r:1 w:0) - // Storage: TransactionStorage BlockTransactions (r:1 w:1) + /// Storage: TransactionStorage Transactions (r:1 w:0) + /// Proof: TransactionStorage Transactions (max_values: None, max_size: Some(36886), added: 39361, mode: MaxEncodedLen) + /// Storage: TransactionStorage ByteFee (r:1 w:0) + /// Proof: TransactionStorage ByteFee (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: TransactionStorage EntryFee (r:1 w:0) + /// Proof: TransactionStorage EntryFee (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen) + /// Storage: TransactionStorage BlockTransactions (r:1 w:1) + /// Proof: TransactionStorage BlockTransactions (max_values: Some(1), max_size: Some(36866), added: 37361, mode: MaxEncodedLen) fn renew() -> Weight { - // Minimum execution time: 56_802 nanoseconds. - Weight::from_ref_time(58_670_000 as u64) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `326` + // Estimated: `40351` + // Minimum execution time: 48_244_000 picoseconds. + Weight::from_parts(50_939_000, 40351) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: TransactionStorage ProofChecked (r:1 w:1) - // Storage: TransactionStorage StoragePeriod (r:1 w:0) - // Storage: TransactionStorage ChunkCount (r:1 w:0) - // Storage: System ParentHash (r:1 w:0) - // Storage: TransactionStorage Transactions (r:1 w:0) + /// Storage: TransactionStorage ProofChecked (r:1 w:1) + /// Proof: TransactionStorage ProofChecked (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen) + /// Storage: TransactionStorage StoragePeriod (r:1 w:0) + /// Proof: TransactionStorage StoragePeriod (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen) + /// Storage: TransactionStorage ChunkCount (r:1 w:0) + /// Proof: TransactionStorage ChunkCount (max_values: None, max_size: Some(24), added: 2499, mode: MaxEncodedLen) + /// Storage: System ParentHash (r:1 w:0) + /// Proof: System ParentHash (max_values: Some(1), max_size: Some(32), added: 527, mode: MaxEncodedLen) + /// Storage: TransactionStorage Transactions (r:1 w:0) + /// Proof: TransactionStorage Transactions (max_values: None, max_size: Some(36886), added: 39361, mode: MaxEncodedLen) fn check_proof_max() -> Weight { - // Minimum execution time: 74_016 nanoseconds. - Weight::from_ref_time(94_111_000 as u64) - .saturating_add(RocksDbWeight::get().reads(5 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `37145` + // Estimated: `40351` + // Minimum execution time: 80_913_000 picoseconds. + Weight::from_parts(84_812_000, 40351) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } } diff --git a/frame/treasury/Cargo.toml b/frame/treasury/Cargo.toml index 1fd1d7b3d462..e4d06bba6459 100644 --- a/frame/treasury/Cargo.toml +++ b/frame/treasury/Cargo.toml @@ -13,23 +13,24 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", "max-encoded-len", ] } impl-trait-for-tuples = "0.2.2" -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", features = ["derive"], optional = true } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", features = ["derive"], optional = true } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../balances" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } +pallet-utility = { version = "4.0.0-dev", path = "../utility" } [features] default = ["std"] diff --git a/frame/treasury/src/benchmarking.rs b/frame/treasury/src/benchmarking.rs index d718a5fb8952..449549d6c53b 100644 --- a/frame/treasury/src/benchmarking.rs +++ b/frame/treasury/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,7 @@ use super::{Pallet as Treasury, *}; -use frame_benchmarking::{account, benchmarks_instance_pallet}; +use frame_benchmarking::v1::{account, benchmarks_instance_pallet, BenchmarkError}; use frame_support::{ dispatch::UnfilteredDispatchable, ensure, @@ -99,7 +99,8 @@ benchmarks_instance_pallet! { beneficiary_lookup )?; let proposal_id = Treasury::::proposal_count() - 1; - let reject_origin = T::RejectOrigin::successful_origin(); + let reject_origin = + T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: _(reject_origin, proposal_id) approve_proposal { @@ -112,7 +113,8 @@ benchmarks_instance_pallet! { beneficiary_lookup )?; let proposal_id = Treasury::::proposal_count() - 1; - let approve_origin = T::ApproveOrigin::successful_origin(); + let approve_origin = + T::ApproveOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: _(approve_origin, proposal_id) remove_approval { @@ -124,7 +126,8 @@ benchmarks_instance_pallet! { )?; let proposal_id = Treasury::::proposal_count() - 1; Treasury::::approve_proposal(RawOrigin::Root.into(), proposal_id)?; - let reject_origin = T::RejectOrigin::successful_origin(); + let reject_origin = + T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; }: _(reject_origin, proposal_id) on_initialize_proposals { @@ -132,7 +135,7 @@ benchmarks_instance_pallet! { setup_pot_account::(); create_approved_proposals::(p)?; }: { - Treasury::::on_initialize(T::BlockNumber::zero()); + Treasury::::on_initialize(frame_system::pallet_prelude::BlockNumberFor::::zero()); } impl_benchmark_test_suite!(Treasury, crate::tests::new_test_ext(), crate::tests::Test); diff --git a/frame/treasury/src/lib.rs b/frame/treasury/src/lib.rs index 1bde1238191a..49be4d91a456 100644 --- a/frame/treasury/src/lib.rs +++ b/frame/treasury/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -67,10 +67,10 @@ use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{ - traits::{AccountIdConversion, Saturating, StaticLookup, Zero}, + traits::{AccountIdConversion, CheckedAdd, Saturating, StaticLookup, Zero}, Permill, RuntimeDebug, }; -use sp_std::prelude::*; +use sp_std::{collections::btree_map::BTreeMap, prelude::*}; use frame_support::{ print, @@ -136,11 +136,10 @@ pub struct Proposal { #[frame_support::pallet] pub mod pallet { use super::*; - use frame_support::pallet_prelude::*; + use frame_support::{dispatch_context::with_context, pallet_prelude::*}; use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(PhantomData<(T, I)>); #[pallet::config] @@ -176,7 +175,7 @@ pub mod pallet { /// Period between successive spends. #[pallet::constant] - type SpendPeriod: Get; + type SpendPeriod: Get>; /// Percentage of spare funds (if any) that are burnt per spend period. #[pallet::constant] @@ -235,31 +234,14 @@ pub mod pallet { StorageValue<_, BoundedVec, ValueQuery>; #[pallet::genesis_config] - pub struct GenesisConfig; - - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - Self - } - } - - #[cfg(feature = "std")] - impl GenesisConfig { - /// Direct implementation of `GenesisBuild::assimilate_storage`. - #[deprecated( - note = "use ` as GenesisBuild>::assimilate_storage` instead" - )] - pub fn assimilate_storage, I: 'static>( - &self, - storage: &mut sp_runtime::Storage, - ) -> Result<(), String> { - >::assimilate_storage(self, storage) - } + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig, I: 'static = ()> { + #[serde(skip)] + _config: sp_std::marker::PhantomData<(T, I)>, } #[pallet::genesis_build] - impl, I: 'static> GenesisBuild for GenesisConfig { + impl, I: 'static> BuildGenesisConfig for GenesisConfig { fn build(&self) { // Create Treasury account let account_id = >::account_id(); @@ -315,14 +297,9 @@ pub mod pallet { #[pallet::hooks] impl, I: 'static> Hooks> for Pallet { - /// # - /// - Complexity: `O(A)` where `A` is the number of approvals - /// - Db reads and writes: `Approvals`, `pot account data` - /// - Db reads and writes per approval: `Proposals`, `proposer account data`, `beneficiary - /// account data` - /// - The weight is overestimated if some approvals got missed. - /// # - fn on_initialize(n: T::BlockNumber) -> Weight { + /// ## Complexity + /// - `O(A)` where `A` is the number of approvals + fn on_initialize(n: frame_system::pallet_prelude::BlockNumberFor) -> Weight { let pot = Self::pot(); let deactivated = Deactivated::::get(); if pot != deactivated { @@ -344,17 +321,19 @@ pub mod pallet { } } + #[derive(Default)] + struct SpendContext { + spend_in_context: BTreeMap, + } + #[pallet::call] impl, I: 'static> Pallet { /// Put forward a suggestion for spending. A deposit proportional to the value /// is reserved and slashed if the proposal is rejected. It is returned once the /// proposal is awarded. /// - /// # - /// - Complexity: O(1) - /// - DbReads: `ProposalCount`, `origin account` - /// - DbWrites: `ProposalCount`, `Proposals`, `origin account` - /// # + /// ## Complexity + /// - O(1) #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::propose_spend())] pub fn propose_spend( @@ -381,11 +360,8 @@ pub mod pallet { /// /// May only be called from `T::RejectOrigin`. /// - /// # - /// - Complexity: O(1) - /// - DbReads: `Proposals`, `rejected proposer account` - /// - DbWrites: `Proposals`, `rejected proposer account` - /// # + /// ## Complexity + /// - O(1) #[pallet::call_index(1)] #[pallet::weight((T::WeightInfo::reject_proposal(), DispatchClass::Operational))] pub fn reject_proposal( @@ -412,11 +388,8 @@ pub mod pallet { /// /// May only be called from `T::ApproveOrigin`. /// - /// # - /// - Complexity: O(1). - /// - DbReads: `Proposals`, `Approvals` - /// - DbWrite: `Approvals` - /// # + /// ## Complexity + /// - O(1). #[pallet::call_index(2)] #[pallet::weight((T::WeightInfo::approve_proposal(T::MaxApprovals::get()), DispatchClass::Operational))] pub fn approve_proposal( @@ -447,9 +420,29 @@ pub mod pallet { beneficiary: AccountIdLookupOf, ) -> DispatchResult { let max_amount = T::SpendOrigin::ensure_origin(origin)?; - let beneficiary = T::Lookup::lookup(beneficiary)?; - ensure!(amount <= max_amount, Error::::InsufficientPermission); + + with_context::>, _>(|v| { + let context = v.or_default(); + + // We group based on `max_amount`, to dinstinguish between different kind of + // origins. (assumes that all origins have different `max_amount`) + // + // Worst case is that we reject some "valid" request. + let spend = context.spend_in_context.entry(max_amount).or_default(); + + // Ensure that we don't overflow nor use more than `max_amount` + if spend.checked_add(&amount).map(|s| s > max_amount).unwrap_or(true) { + Err(Error::::InsufficientPermission) + } else { + *spend = spend.saturating_add(amount); + + Ok(()) + } + }) + .unwrap_or(Ok(()))?; + + let beneficiary = T::Lookup::lookup(beneficiary)?; let proposal_index = Self::proposal_count(); Approvals::::try_append(proposal_index) .map_err(|_| Error::::TooManyApprovals)?; @@ -472,10 +465,8 @@ pub mod pallet { /// May only be called from `T::RejectOrigin`. /// - `proposal_id`: The index of a proposal /// - /// # - /// - Complexity: O(A) where `A` is the number of approvals - /// - Db reads and writes: `Approvals` - /// # + /// ## Complexity + /// - O(A) where `A` is the number of approvals /// /// Errors: /// - `ProposalNotApproved`: The `proposal_id` supplied was not found in the approval queue, diff --git a/frame/treasury/src/tests.rs b/frame/treasury/src/tests.rs index e6a1ea0f543f..79497d55867d 100644 --- a/frame/treasury/src/tests.rs +++ b/frame/treasury/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,14 +21,12 @@ use sp_core::H256; use sp_runtime::{ - testing::Header, - traits::{BadOrigin, BlakeTwo256, IdentityLookup}, + traits::{BadOrigin, BlakeTwo256, Dispatchable, IdentityLookup}, + BuildStorage, }; use frame_support::{ - assert_noop, assert_ok, - pallet_prelude::GenesisBuild, - parameter_types, + assert_err_ignore_postinfo, assert_noop, assert_ok, parameter_types, traits::{ConstU32, ConstU64, OnInitialize}, PalletId, }; @@ -36,18 +34,17 @@ use frame_support::{ use super::*; use crate as treasury; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; +type UtilityCall = pallet_utility::Call; +type TreasuryCall = crate::Call; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, - Treasury: treasury::{Pallet, Call, Storage, Config, Event}, + Treasury: treasury::{Pallet, Call, Storage, Config, Event}, + Utility: pallet_utility, } ); @@ -57,14 +54,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type RuntimeCall = RuntimeCall; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u128; // u64 is not enough to hold bytes used to generate bounty account type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -87,7 +83,19 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } + +impl pallet_utility::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + type WeightInfo = (); +} + parameter_types! { pub const ProposalBond: Permill = Permill::from_percent(5); pub const Burn: Permill = Permill::from_percent(50); @@ -132,14 +140,14 @@ impl Config for Test { } pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized at ED. balances: vec![(0, 100), (1, 98), (2, 1)], } .assimilate_storage(&mut t) .unwrap(); - GenesisBuild::::assimilate_storage(&crate::GenesisConfig, &mut t).unwrap(); + crate::GenesisConfig::::default().assimilate_storage(&mut t).unwrap(); t.into() } @@ -384,7 +392,7 @@ fn treasury_account_doesnt_get_deleted() { // This is useful for chain that will just update runtime. #[test] fn inexistent_account_works() { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(0, 100), (1, 99), (2, 1)] } .assimilate_storage(&mut t) .unwrap(); @@ -416,7 +424,7 @@ fn inexistent_account_works() { #[test] fn genesis_funding_works() { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let initial_funding = 100; pallet_balances::GenesisConfig:: { // Total issuance will be 200 with treasury account initialized with 100. @@ -424,7 +432,7 @@ fn genesis_funding_works() { } .assimilate_storage(&mut t) .unwrap(); - GenesisBuild::::assimilate_storage(&crate::GenesisConfig, &mut t).unwrap(); + crate::GenesisConfig::::default().assimilate_storage(&mut t).unwrap(); let mut t: sp_io::TestExternalities = t.into(); t.execute_with(|| { @@ -470,3 +478,28 @@ fn remove_already_removed_approval_fails() { ); }); } + +#[test] +fn spending_in_batch_respects_max_total() { + new_test_ext().execute_with(|| { + // Respect the `max_total` for the given origin. + assert_ok!(RuntimeCall::from(UtilityCall::batch_all { + calls: vec![ + RuntimeCall::from(TreasuryCall::spend { amount: 2, beneficiary: 100 }), + RuntimeCall::from(TreasuryCall::spend { amount: 2, beneficiary: 101 }) + ] + }) + .dispatch(RuntimeOrigin::signed(10))); + + assert_err_ignore_postinfo!( + RuntimeCall::from(UtilityCall::batch_all { + calls: vec![ + RuntimeCall::from(TreasuryCall::spend { amount: 2, beneficiary: 100 }), + RuntimeCall::from(TreasuryCall::spend { amount: 4, beneficiary: 101 }) + ] + }) + .dispatch(RuntimeOrigin::signed(10)), + Error::::InsufficientPermission + ); + }) +} diff --git a/frame/treasury/src/weights.rs b/frame/treasury/src/weights.rs index 3ee071ac700f..8f1418f76d96 100644 --- a/frame/treasury/src/weights.rs +++ b/frame/treasury/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_treasury //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_treasury +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_treasury. pub trait WeightInfo { @@ -58,114 +63,194 @@ pub trait WeightInfo { /// Weights for pallet_treasury using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { + /// 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(108), added: 2583, mode: MaxEncodedLen) fn spend() -> Weight { - // Minimum execution time: 137 nanoseconds. - Weight::from_ref_time(153_000 as u64) + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `1887` + // Minimum execution time: 15_057_000 picoseconds. + Weight::from_parts(15_803_000, 1887) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - // Storage: Treasury ProposalCount (r:1 w:1) - // Storage: Treasury Proposals (r:0 w:1) + /// 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(108), added: 2583, mode: MaxEncodedLen) fn propose_spend() -> Weight { - // Minimum execution time: 31_437 nanoseconds. - Weight::from_ref_time(32_241_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `177` + // Estimated: `1489` + // Minimum execution time: 28_923_000 picoseconds. + Weight::from_parts(29_495_000, 1489) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Treasury Proposals (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Treasury Proposals (r:1 w:1) + /// Proof: Treasury Proposals (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(128), added: 2603, mode: MaxEncodedLen) fn reject_proposal() -> Weight { - // Minimum execution time: 38_351 nanoseconds. - Weight::from_ref_time(38_828_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `335` + // Estimated: `3593` + // Minimum execution time: 30_539_000 picoseconds. + Weight::from_parts(30_986_000, 3593) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Treasury Proposals (r:1 w:0) - // Storage: Treasury Approvals (r:1 w:1) + /// Storage: Treasury Proposals (r:1 w:0) + /// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) + /// Storage: Treasury Approvals (r:1 w:1) + /// Proof: Treasury Approvals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen) /// The range of component `p` is `[0, 99]`. fn approve_proposal(p: u32, ) -> Weight { - // Minimum execution time: 11_937 nanoseconds. - Weight::from_ref_time(15_541_763 as u64) - // Standard Error: 1_036 - .saturating_add(Weight::from_ref_time(128_326 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `504 + p * (8 ±0)` + // Estimated: `3573` + // Minimum execution time: 9_320_000 picoseconds. + Weight::from_parts(12_606_599, 3573) + // Standard Error: 1_302 + .saturating_add(Weight::from_parts(71_054, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Treasury Approvals (r:1 w:1) + /// 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 { - // Minimum execution time: 9_611 nanoseconds. - Weight::from_ref_time(10_012_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `161` + // Estimated: `1887` + // Minimum execution time: 7_231_000 picoseconds. + Weight::from_parts(7_459_000, 1887) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } - // Storage: Treasury Approvals (r:1 w:1) - // Storage: Bounties BountyApprovals (r:1 w:1) - // Storage: Treasury Proposals (r:2 w:2) - // Storage: System Account (r:4 w:4) + /// Storage: Treasury Deactivated (r:1 w:1) + /// 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:100 w:100) + /// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) + /// Storage: System Account (r:200 w:200) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Bounties BountyApprovals (r:1 w:1) + /// Proof: Bounties BountyApprovals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen) /// The range of component `p` is `[0, 100]`. fn on_initialize_proposals(p: u32, ) -> Weight { - // Minimum execution time: 43_016 nanoseconds. - Weight::from_ref_time(56_538_751 as u64) - // Standard Error: 14_890 - .saturating_add(Weight::from_ref_time(26_789_120 as u64).saturating_mul(p as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().reads((3 as u64).saturating_mul(p as u64))) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - .saturating_add(T::DbWeight::get().writes((3 as u64).saturating_mul(p as u64))) + // Proof Size summary in bytes: + // Measured: `421 + p * (251 ±0)` + // Estimated: `1887 + p * (5206 ±0)` + // Minimum execution time: 44_769_000 picoseconds. + Weight::from_parts(57_915_572, 1887) + // Standard Error: 59_484 + .saturating_add(Weight::from_parts(42_343_732, 0).saturating_mul(p.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(p.into()))) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(p.into()))) + .saturating_add(Weight::from_parts(0, 5206).saturating_mul(p.into())) } } // 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(108), added: 2583, mode: MaxEncodedLen) fn spend() -> Weight { - // Minimum execution time: 137 nanoseconds. - Weight::from_ref_time(153_000 as u64) + // Proof Size summary in bytes: + // Measured: `76` + // Estimated: `1887` + // Minimum execution time: 15_057_000 picoseconds. + Weight::from_parts(15_803_000, 1887) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } - // Storage: Treasury ProposalCount (r:1 w:1) - // Storage: Treasury Proposals (r:0 w:1) + /// 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(108), added: 2583, mode: MaxEncodedLen) fn propose_spend() -> Weight { - // Minimum execution time: 31_437 nanoseconds. - Weight::from_ref_time(32_241_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `177` + // Estimated: `1489` + // Minimum execution time: 28_923_000 picoseconds. + Weight::from_parts(29_495_000, 1489) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Treasury Proposals (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Treasury Proposals (r:1 w:1) + /// Proof: Treasury Proposals (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(128), added: 2603, mode: MaxEncodedLen) fn reject_proposal() -> Weight { - // Minimum execution time: 38_351 nanoseconds. - Weight::from_ref_time(38_828_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `335` + // Estimated: `3593` + // Minimum execution time: 30_539_000 picoseconds. + Weight::from_parts(30_986_000, 3593) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Treasury Proposals (r:1 w:0) - // Storage: Treasury Approvals (r:1 w:1) + /// Storage: Treasury Proposals (r:1 w:0) + /// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) + /// Storage: Treasury Approvals (r:1 w:1) + /// Proof: Treasury Approvals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen) /// The range of component `p` is `[0, 99]`. fn approve_proposal(p: u32, ) -> Weight { - // Minimum execution time: 11_937 nanoseconds. - Weight::from_ref_time(15_541_763 as u64) - // Standard Error: 1_036 - .saturating_add(Weight::from_ref_time(128_326 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `504 + p * (8 ±0)` + // Estimated: `3573` + // Minimum execution time: 9_320_000 picoseconds. + Weight::from_parts(12_606_599, 3573) + // Standard Error: 1_302 + .saturating_add(Weight::from_parts(71_054, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Treasury Approvals (r:1 w:1) + /// 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 { - // Minimum execution time: 9_611 nanoseconds. - Weight::from_ref_time(10_012_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) + // Proof Size summary in bytes: + // Measured: `161` + // Estimated: `1887` + // Minimum execution time: 7_231_000 picoseconds. + Weight::from_parts(7_459_000, 1887) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } - // Storage: Treasury Approvals (r:1 w:1) - // Storage: Bounties BountyApprovals (r:1 w:1) - // Storage: Treasury Proposals (r:2 w:2) - // Storage: System Account (r:4 w:4) + /// Storage: Treasury Deactivated (r:1 w:1) + /// 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:100 w:100) + /// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) + /// Storage: System Account (r:200 w:200) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Bounties BountyApprovals (r:1 w:1) + /// Proof: Bounties BountyApprovals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen) /// The range of component `p` is `[0, 100]`. fn on_initialize_proposals(p: u32, ) -> Weight { - // Minimum execution time: 43_016 nanoseconds. - Weight::from_ref_time(56_538_751 as u64) - // Standard Error: 14_890 - .saturating_add(Weight::from_ref_time(26_789_120 as u64).saturating_mul(p as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().reads((3 as u64).saturating_mul(p as u64))) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - .saturating_add(RocksDbWeight::get().writes((3 as u64).saturating_mul(p as u64))) + // Proof Size summary in bytes: + // Measured: `421 + p * (251 ±0)` + // Estimated: `1887 + p * (5206 ±0)` + // Minimum execution time: 44_769_000 picoseconds. + Weight::from_parts(57_915_572, 1887) + // Standard Error: 59_484 + .saturating_add(Weight::from_parts(42_343_732, 0).saturating_mul(p.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(p.into()))) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(p.into()))) + .saturating_add(Weight::from_parts(0, 5206).saturating_mul(p.into())) } } diff --git a/frame/try-runtime/Cargo.toml b/frame/try-runtime/Cargo.toml index 042dba5ed88a..f7402301f5d0 100644 --- a/frame/try-runtime/Cargo.toml +++ b/frame/try-runtime/Cargo.toml @@ -12,11 +12,11 @@ description = "FRAME pallet for democracy" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"]} +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"]} frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [features] default = [ "std" ] diff --git a/frame/try-runtime/src/lib.rs b/frame/try-runtime/src/lib.rs index d8b117d1ff05..43292efe2104 100644 --- a/frame/try-runtime/src/lib.rs +++ b/frame/try-runtime/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/uniques/Cargo.toml b/frame/uniques/Cargo.toml index a01ea60d8c3e..3abb1eed9ca2 100644 --- a/frame/uniques/Cargo.toml +++ b/frame/uniques/Cargo.toml @@ -13,20 +13,20 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } -sp-std = { version = "5.0.0", path = "../../primitives/std" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } +sp-std = { version = "8.0.0", path = "../../primitives/std" } [features] default = ["std"] diff --git a/frame/uniques/README.md b/frame/uniques/README.md index 8a91a558b5b5..6cdbcf79f1c9 100644 --- a/frame/uniques/README.md +++ b/frame/uniques/README.md @@ -4,67 +4,71 @@ A simple, secure module for dealing with non-fungible assets. ## Overview -The Uniques module provides functionality for asset management of non-fungible asset classes, including: +The Uniques module provides functionality for non-fungible tokens' management, including: -* Asset Issuance -* Asset Transfer -* Asset Destruction +* Collection Creation +* Item Minting +* Item Transfers +* Item Trading methods +* Attributes Management +* Item Burning -To use it in your runtime, you need to implement the assets [`uniques::Config`](https://paritytech.github.io/substrate/master/pallet_uniques/pallet/trait.Config.html). +To use it in your runtime, you need to implement [`uniques::Config`](https://paritytech.github.io/substrate/master/pallet_uniques/pallet/trait.Config.html). The supported dispatchable functions are documented in the [`uniques::Call`](https://paritytech.github.io/substrate/master/pallet_uniques/pallet/enum.Call.html) enum. ### Terminology -* **Asset issuance:** The creation of a new asset instance. -* **Asset transfer:** The action of transferring an asset instance from one account to another. -* **Asset burning:** The destruction of an asset instance. -* **Non-fungible asset:** An asset for which each unit has unique characteristics. There is exactly - one instance of such an asset in existence and there is exactly one owning account. +* **Collection creation:** The creation of a new collection. +* **Item minting:** The action of creating a new item within a collection. +* **Item transfer:** The action of sending an item from one account to another. +* **Item burning:** The destruction of an item. +* **Non-fungible token (NFT):** An item for which each unit has unique characteristics. There is exactly + one instance of such an item in existence and there is exactly one owning account. ### Goals The Uniques pallet in Substrate is designed to make the following possible: -* Allow accounts to permissionlessly create asset classes (collections of asset instances). -* Allow a named (permissioned) account to mint and burn unique assets within a class. -* Move asset instances between accounts permissionlessly. -* Allow a named (permissioned) account to freeze and unfreeze unique assets within a - class or the entire class. -* Allow the owner of an asset instance to delegate the ability to transfer the asset to some +* Allow accounts to permissionlessly create NFT collections. +* Allow a named (permissioned) account to mint and burn unique items within a collection. +* Move items between accounts permissionlessly. +* Allow a named (permissioned) account to freeze and unfreeze unique items within a + collection or the entire collection. +* Allow the owner of an item to delegate the ability to transfer the item to some named third-party. ## Interface ### Permissionless dispatchables -* `create`: Create a new asset class by placing a deposit. -* `transfer`: Transfer an asset instance to a new owner. -* `redeposit`: Update the deposit amount of an asset instance, potentially freeing funds. +* `create`: Create a new collection by placing a deposit. +* `transfer`: Transfer an item to a new owner. +* `redeposit`: Update the deposit amount of an item, potentially freeing funds. * `approve_transfer`: Name a delegate who may authorise a transfer. * `cancel_approval`: Revert the effects of a previous `approve_transfer`. ### Permissioned dispatchables -* `destroy`: Destroy an asset class. -* `mint`: Mint a new asset instance within an asset class. -* `burn`: Burn an asset instance within an asset class. -* `freeze`: Prevent an individual asset from being transferred. +* `destroy`: Destroy a collection. +* `mint`: Mint a new item within a collection. +* `burn`: Burn an item within a collection. +* `freeze`: Prevent an individual item from being transferred. * `thaw`: Revert the effects of a previous `freeze`. -* `freeze_class`: Prevent all asset within a class from being transferred. -* `thaw_class`: Revert the effects of a previous `freeze_class`. -* `transfer_ownership`: Alter the owner of an asset class, moving all associated deposits. -* `set_team`: Alter the permissioned accounts of an asset class. +* `freeze_collection`: Prevent all items within a collection from being transferred. +* `thaw_collection`: Revert the effects of a previous `freeze_collection`. +* `transfer_ownership`: Alter the owner of a collection, moving all associated deposits. +* `set_team`: Alter the permissioned accounts of a collection. ### Metadata (permissioned) dispatchables -* `set_attribute`: Set a metadata attribute of an asset instance or class. -* `clear_attribute`: Remove a metadata attribute of an asset instance or class. -* `set_metadata`: Set general metadata of an asset instance. -* `clear_metadata`: Remove general metadata of an asset instance. -* `set_class_metadata`: Set general metadata of an asset class. -* `clear_class_metadata`: Remove general metadata of an asset class. +* `set_attribute`: Set an attribute of an item or collection. +* `clear_attribute`: Remove an attribute of an item or collection. +* `set_metadata`: Set general metadata of an item. +* `clear_metadata`: Remove general metadata of an item. +* `set_collection_metadata`: Set general metadata of a collection. +* `clear_collection_metadata`: Remove general metadata of a collection. ### Force (i.e. governance) dispatchables -* `force_create`: Create a new asset class. -* `force_asset_status`: Alter the underlying characteristics of an asset class. +* `force_create`: Create a new collection. +* `force_asset_status`: Alter the underlying characteristics of a collection. Please refer to the [`Call`](https://paritytech.github.io/substrate/master/pallet_uniques/pallet/enum.Call.html) enum and its associated variants for documentation on each function. diff --git a/frame/uniques/src/benchmarking.rs b/frame/uniques/src/benchmarking.rs index ab34558f95eb..4e63f69281e5 100644 --- a/frame/uniques/src/benchmarking.rs +++ b/frame/uniques/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,8 +20,8 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::{ - account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, +use frame_benchmarking::v1::{ + account, benchmarks_instance_pallet, whitelist_account, whitelisted_caller, BenchmarkError, }; use frame_support::{ dispatch::UnfilteredDispatchable, @@ -44,7 +44,7 @@ fn create_collection, I: 'static>( T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); assert!(Uniques::::force_create( SystemOrigin::Root.into(), - collection, + collection.clone(), caller_lookup.clone(), false, ) @@ -137,7 +137,8 @@ fn assert_last_event, I: 'static>(generic_event: >:: benchmarks_instance_pallet! { create { let collection = T::Helper::collection(0); - let origin = T::CreateOrigin::successful_origin(&collection); + let origin = T::CreateOrigin::try_successful_origin(&collection) + .map_err(|_| BenchmarkError::Weightless)?; let caller = T::CreateOrigin::ensure_origin(origin.clone(), &collection).unwrap(); whitelist_account!(caller); let admin = T::Lookup::unlookup(caller.clone()); @@ -172,26 +173,26 @@ benchmarks_instance_pallet! { for i in 0..a { add_item_attribute::(T::Helper::item(i as u16)); } - let witness = Collection::::get(collection).unwrap().destroy_witness(); - }: _(SystemOrigin::Signed(caller), collection, witness) + let witness = Collection::::get(collection.clone()).unwrap().destroy_witness(); + }: _(SystemOrigin::Signed(caller), collection.clone(), witness) verify { - assert_last_event::(Event::Destroyed { collection }.into()); + assert_last_event::(Event::Destroyed { collection: collection.clone() }.into()); } mint { let (collection, caller, caller_lookup) = create_collection::(); let item = T::Helper::item(0); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, caller_lookup) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item, caller_lookup) verify { - assert_last_event::(Event::Issued { collection, item, owner: caller }.into()); + assert_last_event::(Event::Issued { collection: collection.clone(), item, owner: caller }.into()); } burn { let (collection, caller, caller_lookup) = create_collection::(); let (item, ..) = mint_item::(0); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(caller_lookup)) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item, Some(caller_lookup)) verify { - assert_last_event::(Event::Burned { collection, item, owner: caller }.into()); + assert_last_event::(Event::Burned { collection: collection.clone(), item, owner: caller }.into()); } transfer { @@ -200,9 +201,9 @@ benchmarks_instance_pallet! { let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, target_lookup) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item, target_lookup) verify { - assert_last_event::(Event::Transferred { collection, item, from: caller, to: target }.into()); + assert_last_event::(Event::Transferred { collection: collection.clone(), item, from: caller, to: target }.into()); } redeposit { @@ -211,7 +212,7 @@ benchmarks_instance_pallet! { let items = (0..i).map(|x| mint_item::(x as u16).0).collect::>(); Uniques::::force_item_status( SystemOrigin::Root.into(), - collection, + collection.clone(), caller_lookup.clone(), caller_lookup.clone(), caller_lookup.clone(), @@ -219,9 +220,9 @@ benchmarks_instance_pallet! { true, false, )?; - }: _(SystemOrigin::Signed(caller.clone()), collection, items.clone()) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), items.clone()) verify { - assert_last_event::(Event::Redeposited { collection, successful_items: items }.into()); + assert_last_event::(Event::Redeposited { collection: collection.clone(), successful_items: items }.into()); } freeze { @@ -237,28 +238,28 @@ benchmarks_instance_pallet! { let (item, ..) = mint_item::(0); Uniques::::freeze( SystemOrigin::Signed(caller.clone()).into(), - collection, + collection.clone(), item, )?; - }: _(SystemOrigin::Signed(caller.clone()), collection, item) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item) verify { - assert_last_event::(Event::Thawed { collection, item }.into()); + assert_last_event::(Event::Thawed { collection: collection.clone(), item }.into()); } freeze_collection { let (collection, caller, caller_lookup) = create_collection::(); - }: _(SystemOrigin::Signed(caller.clone()), collection) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone()) verify { - assert_last_event::(Event::CollectionFrozen { collection }.into()); + assert_last_event::(Event::CollectionFrozen { collection: collection.clone() }.into()); } thaw_collection { let (collection, caller, caller_lookup) = create_collection::(); let origin = SystemOrigin::Signed(caller.clone()).into(); - Uniques::::freeze_collection(origin, collection)?; - }: _(SystemOrigin::Signed(caller.clone()), collection) + Uniques::::freeze_collection(origin, collection.clone())?; + }: _(SystemOrigin::Signed(caller.clone()), collection.clone()) verify { - assert_last_event::(Event::CollectionThawed { collection }.into()); + assert_last_event::(Event::CollectionThawed { collection: collection.clone() }.into()); } transfer_ownership { @@ -267,10 +268,10 @@ benchmarks_instance_pallet! { let target_lookup = T::Lookup::unlookup(target.clone()); T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); let origin = SystemOrigin::Signed(target.clone()).into(); - Uniques::::set_accept_ownership(origin, Some(collection))?; - }: _(SystemOrigin::Signed(caller), collection, target_lookup) + Uniques::::set_accept_ownership(origin, Some(collection.clone()))?; + }: _(SystemOrigin::Signed(caller), collection.clone(), target_lookup) verify { - assert_last_event::(Event::OwnerChanged { collection, new_owner: target }.into()); + assert_last_event::(Event::OwnerChanged { collection: collection.clone(), new_owner: target }.into()); } set_team { @@ -278,10 +279,10 @@ benchmarks_instance_pallet! { let target0 = T::Lookup::unlookup(account("target", 0, SEED)); let target1 = T::Lookup::unlookup(account("target", 1, SEED)); let target2 = T::Lookup::unlookup(account("target", 2, SEED)); - }: _(SystemOrigin::Signed(caller), collection, target0, target1, target2) + }: _(SystemOrigin::Signed(caller), collection.clone(), target0, target1, target2) verify { assert_last_event::(Event::TeamChanged{ - collection, + collection: collection.clone(), issuer: account("target", 0, SEED), admin: account("target", 1, SEED), freezer: account("target", 2, SEED), @@ -290,9 +291,10 @@ benchmarks_instance_pallet! { force_item_status { let (collection, caller, caller_lookup) = create_collection::(); - let origin = T::ForceOrigin::successful_origin(); + let origin = + T::ForceOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let call = Call::::force_item_status { - collection, + collection: collection.clone(), owner: caller_lookup.clone(), issuer: caller_lookup.clone(), admin: caller_lookup.clone(), @@ -302,7 +304,7 @@ benchmarks_instance_pallet! { }; }: { call.dispatch_bypass_filter(origin)? } verify { - assert_last_event::(Event::ItemStatusChanged { collection }.into()); + assert_last_event::(Event::ItemStatusChanged { collection: collection.clone() }.into()); } set_attribute { @@ -312,9 +314,9 @@ benchmarks_instance_pallet! { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); add_item_metadata::(item); - }: _(SystemOrigin::Signed(caller), collection, Some(item), key.clone(), value.clone()) + }: _(SystemOrigin::Signed(caller), collection.clone(), Some(item), key.clone(), value.clone()) verify { - assert_last_event::(Event::AttributeSet { collection, maybe_item: Some(item), key, value }.into()); + assert_last_event::(Event::AttributeSet { collection: collection.clone(), maybe_item: Some(item), key, value }.into()); } clear_attribute { @@ -322,9 +324,9 @@ benchmarks_instance_pallet! { let (item, ..) = mint_item::(0); add_item_metadata::(item); let (key, ..) = add_item_attribute::(item); - }: _(SystemOrigin::Signed(caller), collection, Some(item), key.clone()) + }: _(SystemOrigin::Signed(caller), collection.clone(), Some(item), key.clone()) verify { - assert_last_event::(Event::AttributeCleared { collection, maybe_item: Some(item), key }.into()); + assert_last_event::(Event::AttributeCleared { collection: collection.clone(), maybe_item: Some(item), key }.into()); } set_metadata { @@ -332,35 +334,35 @@ benchmarks_instance_pallet! { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); - }: _(SystemOrigin::Signed(caller), collection, item, data.clone(), false) + }: _(SystemOrigin::Signed(caller), collection.clone(), item, data.clone(), false) verify { - assert_last_event::(Event::MetadataSet { collection, item, data, is_frozen: false }.into()); + assert_last_event::(Event::MetadataSet { collection: collection.clone(), item, data, is_frozen: false }.into()); } clear_metadata { let (collection, caller, _) = create_collection::(); let (item, ..) = mint_item::(0); add_item_metadata::(item); - }: _(SystemOrigin::Signed(caller), collection, item) + }: _(SystemOrigin::Signed(caller), collection.clone(), item) verify { - assert_last_event::(Event::MetadataCleared { collection, item }.into()); + assert_last_event::(Event::MetadataCleared { collection: collection.clone(), item }.into()); } set_collection_metadata { let data: BoundedVec<_, _> = vec![0u8; T::StringLimit::get() as usize].try_into().unwrap(); let (collection, caller, _) = create_collection::(); - }: _(SystemOrigin::Signed(caller), collection, data.clone(), false) + }: _(SystemOrigin::Signed(caller), collection.clone(), data.clone(), false) verify { - assert_last_event::(Event::CollectionMetadataSet { collection, data, is_frozen: false }.into()); + assert_last_event::(Event::CollectionMetadataSet { collection: collection.clone(), data, is_frozen: false }.into()); } clear_collection_metadata { let (collection, caller, _) = create_collection::(); add_collection_metadata::(); - }: _(SystemOrigin::Signed(caller), collection) + }: _(SystemOrigin::Signed(caller), collection.clone()) verify { - assert_last_event::(Event::CollectionMetadataCleared { collection }.into()); + assert_last_event::(Event::CollectionMetadataCleared { collection: collection.clone() }.into()); } approve_transfer { @@ -368,9 +370,9 @@ benchmarks_instance_pallet! { let (item, ..) = mint_item::(0); let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, delegate_lookup) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item, delegate_lookup) verify { - assert_last_event::(Event::ApprovedTransfer { collection, item, owner: caller, delegate }.into()); + assert_last_event::(Event::ApprovedTransfer { collection: collection.clone(), item, owner: caller, delegate }.into()); } cancel_approval { @@ -379,17 +381,17 @@ benchmarks_instance_pallet! { let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let origin = SystemOrigin::Signed(caller.clone()).into(); - Uniques::::approve_transfer(origin, collection, item, delegate_lookup.clone())?; - }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(delegate_lookup)) + Uniques::::approve_transfer(origin, collection.clone(), item, delegate_lookup.clone())?; + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item, Some(delegate_lookup)) verify { - assert_last_event::(Event::ApprovalCancelled { collection, item, owner: caller, delegate }.into()); + assert_last_event::(Event::ApprovalCancelled { collection: collection.clone(), item, owner: caller, delegate }.into()); } set_accept_ownership { let caller: T::AccountId = whitelisted_caller(); T::Currency::make_free_balance_be(&caller, DepositBalanceOf::::max_value()); let collection = T::Helper::collection(0); - }: _(SystemOrigin::Signed(caller.clone()), Some(collection)) + }: _(SystemOrigin::Signed(caller.clone()), Some(collection.clone())) verify { assert_last_event::(Event::OwnershipAcceptanceChanged { who: caller, @@ -399,10 +401,10 @@ benchmarks_instance_pallet! { set_collection_max_supply { let (collection, caller, _) = create_collection::(); - }: _(SystemOrigin::Signed(caller.clone()), collection, u32::MAX) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), u32::MAX) verify { assert_last_event::(Event::CollectionMaxSupplySet { - collection, + collection: collection.clone(), max_supply: u32::MAX, }.into()); } @@ -413,10 +415,10 @@ benchmarks_instance_pallet! { let delegate: T::AccountId = account("delegate", 0, SEED); let delegate_lookup = T::Lookup::unlookup(delegate.clone()); let price = ItemPrice::::from(100u32); - }: _(SystemOrigin::Signed(caller.clone()), collection, item, Some(price), Some(delegate_lookup)) + }: _(SystemOrigin::Signed(caller.clone()), collection.clone(), item, Some(price), Some(delegate_lookup)) verify { assert_last_event::(Event::ItemPriceSet { - collection, + collection: collection.clone(), item, price, whitelisted_buyer: Some(delegate), @@ -430,12 +432,12 @@ benchmarks_instance_pallet! { let buyer_lookup = T::Lookup::unlookup(buyer.clone()); let price = ItemPrice::::from(0u32); let origin = SystemOrigin::Signed(seller.clone()).into(); - Uniques::::set_price(origin, collection, item, Some(price.clone()), Some(buyer_lookup))?; + Uniques::::set_price(origin, collection.clone(), item, Some(price.clone()), Some(buyer_lookup))?; T::Currency::make_free_balance_be(&buyer, DepositBalanceOf::::max_value()); - }: _(SystemOrigin::Signed(buyer.clone()), collection, item, price.clone()) + }: _(SystemOrigin::Signed(buyer.clone()), collection.clone(), item, price.clone()) verify { assert_last_event::(Event::ItemBought { - collection, + collection: collection.clone(), item, price, seller, diff --git a/frame/uniques/src/functions.rs b/frame/uniques/src/functions.rs index 03dba16eaa12..681ad06a84b8 100644 --- a/frame/uniques/src/functions.rs +++ b/frame/uniques/src/functions.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -37,7 +37,7 @@ impl, I: 'static> Pallet { let collection_details = Collection::::get(&collection).ok_or(Error::::UnknownCollection)?; ensure!(!collection_details.is_frozen, Error::::Frozen); - ensure!(!T::Locker::is_locked(collection, item), Error::::Locked); + ensure!(!T::Locker::is_locked(collection.clone(), item), Error::::Locked); let mut details = Item::::get(&collection, &item).ok_or(Error::::UnknownCollection)?; @@ -50,8 +50,8 @@ impl, I: 'static> Pallet { details.owner = dest; // The approved account has to be reset to None, because otherwise pre-approve attack would - // be possible, where the owner can approve his second account before making the transaction - // and then claiming the item back. + // be possible, where the owner can approve their second account before making the + // transaction and then claiming the item back. details.approved = None; Item::::insert(&collection, &item, &details); @@ -74,12 +74,12 @@ impl, I: 'static> Pallet { free_holding: bool, event: Event, ) -> DispatchResult { - ensure!(!Collection::::contains_key(collection), Error::::InUse); + ensure!(!Collection::::contains_key(collection.clone()), Error::::InUse); T::Currency::reserve(&owner, deposit)?; Collection::::insert( - collection, + collection.clone(), CollectionDetails { owner: owner.clone(), issuer: admin.clone(), @@ -104,7 +104,7 @@ impl, I: 'static> Pallet { witness: DestroyWitness, maybe_check_owner: Option, ) -> Result { - Collection::::try_mutate_exists(collection, |maybe_details| { + Collection::::try_mutate_exists(collection.clone(), |maybe_details| { let collection_details = maybe_details.take().ok_or(Error::::UnknownCollection)?; if let Some(check_owner) = maybe_check_owner { @@ -147,7 +147,10 @@ impl, I: 'static> Pallet { owner: T::AccountId, with_details: impl FnOnce(&CollectionDetailsFor) -> DispatchResult, ) -> DispatchResult { - ensure!(!Item::::contains_key(collection, item), Error::::AlreadyExists); + ensure!( + !Item::::contains_key(collection.clone(), item), + Error::::AlreadyExists + ); Collection::::try_mutate( &collection, @@ -189,7 +192,7 @@ impl, I: 'static> Pallet { item: T::ItemId, with_details: impl FnOnce(&CollectionDetailsFor, &ItemDetailsFor) -> DispatchResult, ) -> DispatchResult { - ensure!(!T::Locker::is_locked(collection, item), Error::::Locked); + ensure!(!T::Locker::is_locked(collection.clone(), item), Error::::Locked); let owner = Collection::::try_mutate( &collection, |maybe_collection_details| -> Result { @@ -268,7 +271,7 @@ impl, I: 'static> Pallet { let old_owner = details.owner.clone(); - Self::do_transfer(collection, item, buyer.clone(), |_, _| Ok(()))?; + Self::do_transfer(collection.clone(), item, buyer.clone(), |_, _| Ok(()))?; Self::deposit_event(Event::ItemBought { collection, diff --git a/frame/uniques/src/impl_nonfungibles.rs b/frame/uniques/src/impl_nonfungibles.rs index 75d193ad1960..0ae055a98d8c 100644 --- a/frame/uniques/src/impl_nonfungibles.rs +++ b/frame/uniques/src/impl_nonfungibles.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -94,12 +94,16 @@ impl, I: 'static> Create<::AccountId> for Pallet admin: &T::AccountId, ) -> DispatchResult { Self::do_create_collection( - *collection, + collection.clone(), who.clone(), admin.clone(), T::CollectionDeposit::get(), false, - Event::Created { collection: *collection, creator: who.clone(), owner: admin.clone() }, + Event::Created { + collection: collection.clone(), + creator: who.clone(), + owner: admin.clone(), + }, ) } } @@ -126,7 +130,7 @@ impl, I: 'static> Mutate<::AccountId> for Pallet item: &Self::ItemId, who: &T::AccountId, ) -> DispatchResult { - Self::do_mint(*collection, *item, who.clone(), |_| Ok(())) + Self::do_mint(collection.clone(), *item, who.clone(), |_| Ok(())) } fn burn( @@ -134,7 +138,7 @@ impl, I: 'static> Mutate<::AccountId> for Pallet item: &Self::ItemId, maybe_check_owner: Option<&T::AccountId>, ) -> DispatchResult { - Self::do_burn(*collection, *item, |_, d| { + Self::do_burn(collection.clone(), *item, |_, d| { if let Some(check_owner) = maybe_check_owner { if &d.owner != check_owner { return Err(Error::::NoPermission.into()) @@ -151,7 +155,7 @@ impl, I: 'static> Transfer for Pallet { item: &Self::ItemId, destination: &T::AccountId, ) -> DispatchResult { - Self::do_transfer(*collection, *item, destination.clone(), |_, _| Ok(())) + Self::do_transfer(collection.clone(), *item, destination.clone(), |_, _| Ok(())) } } diff --git a/frame/uniques/src/lib.rs b/frame/uniques/src/lib.rs index da0f0e056067..95ef8111f1e4 100644 --- a/frame/uniques/src/lib.rs +++ b/frame/uniques/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,11 +43,8 @@ pub mod migration; pub mod weights; use codec::{Decode, Encode}; -use frame_support::{ - traits::{ - tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency, - }, - transactional, +use frame_support::traits::{ + tokens::Locker, BalanceStatus::Reserved, Currency, EnsureOriginWithArg, ReservableCurrency, }; use frame_system::Config as SystemConfig; use sp_runtime::{ @@ -71,7 +68,6 @@ pub mod pallet { use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[cfg(feature = "runtime-benchmarks")] @@ -97,7 +93,7 @@ pub mod pallet { + IsType<::RuntimeEvent>; /// Identifier for the collection of item. - type CollectionId: Member + Parameter + MaxEncodedLen + Copy; + type CollectionId: Member + Parameter + MaxEncodedLen; /// The type used to identify a unique item within a collection. type ItemId: Member + Parameter + MaxEncodedLen + Copy; @@ -462,7 +458,7 @@ pub mod pallet { let admin = T::Lookup::lookup(admin)?; Self::do_create_collection( - collection, + collection.clone(), owner.clone(), admin.clone(), T::CollectionDeposit::get(), @@ -500,7 +496,7 @@ pub mod pallet { let owner = T::Lookup::lookup(owner)?; Self::do_create_collection( - collection, + collection.clone(), owner.clone(), owner.clone(), Zero::zero(), @@ -800,7 +796,7 @@ pub mod pallet { ) -> DispatchResult { let origin = ensure_signed(origin)?; - Collection::::try_mutate(collection, |maybe_details| { + Collection::::try_mutate(collection.clone(), |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; ensure!(origin == details.freezer, Error::::NoPermission); @@ -828,7 +824,7 @@ pub mod pallet { ) -> DispatchResult { let origin = ensure_signed(origin)?; - Collection::::try_mutate(collection, |maybe_details| { + Collection::::try_mutate(collection.clone(), |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; ensure!(origin == details.admin, Error::::NoPermission); @@ -863,7 +859,7 @@ pub mod pallet { let acceptable_collection = OwnershipAcceptance::::get(&owner); ensure!(acceptable_collection.as_ref() == Some(&collection), Error::::Unaccepted); - Collection::::try_mutate(collection, |maybe_details| { + Collection::::try_mutate(collection.clone(), |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; ensure!(origin == details.owner, Error::::NoPermission); if details.owner == owner { @@ -913,7 +909,7 @@ pub mod pallet { let admin = T::Lookup::lookup(admin)?; let freezer = T::Lookup::lookup(freezer)?; - Collection::::try_mutate(collection, |maybe_details| { + Collection::::try_mutate(collection.clone(), |maybe_details| { let details = maybe_details.as_mut().ok_or(Error::::UnknownCollection)?; ensure!(origin == details.owner, Error::::NoPermission); @@ -1061,7 +1057,7 @@ pub mod pallet { ) -> DispatchResult { T::ForceOrigin::ensure_origin(origin)?; - Collection::::try_mutate(collection, |maybe_item| { + Collection::::try_mutate(collection.clone(), |maybe_item| { let mut item = maybe_item.take().ok_or(Error::::UnknownCollection)?; let old_owner = item.owner; let new_owner = T::Lookup::lookup(owner)?; @@ -1116,12 +1112,13 @@ pub mod pallet { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } let maybe_is_frozen = match maybe_item { - None => CollectionMetadataOf::::get(collection).map(|v| v.is_frozen), - Some(item) => ItemMetadataOf::::get(collection, item).map(|v| v.is_frozen), + None => CollectionMetadataOf::::get(collection.clone()).map(|v| v.is_frozen), + Some(item) => + ItemMetadataOf::::get(collection.clone(), item).map(|v| v.is_frozen), }; ensure!(!maybe_is_frozen.unwrap_or(false), Error::::Frozen); - let attribute = Attribute::::get((collection, maybe_item, &key)); + let attribute = Attribute::::get((collection.clone(), maybe_item, &key)); if attribute.is_none() { collection_details.attributes.saturating_inc(); } @@ -1141,7 +1138,7 @@ pub mod pallet { } Attribute::::insert((&collection, maybe_item, &key), (&value, deposit)); - Collection::::insert(collection, &collection_details); + Collection::::insert(collection.clone(), &collection_details); Self::deposit_event(Event::AttributeSet { collection, maybe_item, key, value }); Ok(()) } @@ -1178,16 +1175,19 @@ pub mod pallet { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } let maybe_is_frozen = match maybe_item { - None => CollectionMetadataOf::::get(collection).map(|v| v.is_frozen), - Some(item) => ItemMetadataOf::::get(collection, item).map(|v| v.is_frozen), + None => CollectionMetadataOf::::get(collection.clone()).map(|v| v.is_frozen), + Some(item) => + ItemMetadataOf::::get(collection.clone(), item).map(|v| v.is_frozen), }; ensure!(!maybe_is_frozen.unwrap_or(false), Error::::Frozen); - if let Some((_, deposit)) = Attribute::::take((collection, maybe_item, &key)) { + if let Some((_, deposit)) = + Attribute::::take((collection.clone(), maybe_item, &key)) + { collection_details.attributes.saturating_dec(); collection_details.total_deposit.saturating_reduce(deposit); T::Currency::unreserve(&collection_details.owner, deposit); - Collection::::insert(collection, &collection_details); + Collection::::insert(collection.clone(), &collection_details); Self::deposit_event(Event::AttributeCleared { collection, maybe_item, key }); } Ok(()) @@ -1230,7 +1230,7 @@ pub mod pallet { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { + ItemMetadataOf::::try_mutate_exists(collection.clone(), item, |metadata| { let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); @@ -1290,7 +1290,7 @@ pub mod pallet { ensure!(check_owner == &collection_details.owner, Error::::NoPermission); } - ItemMetadataOf::::try_mutate_exists(collection, item, |metadata| { + ItemMetadataOf::::try_mutate_exists(collection.clone(), item, |metadata| { let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); @@ -1341,7 +1341,7 @@ pub mod pallet { ensure!(check_owner == &details.owner, Error::::NoPermission); } - CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { + CollectionMetadataOf::::try_mutate_exists(collection.clone(), |metadata| { let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); @@ -1397,7 +1397,7 @@ pub mod pallet { ensure!(check_owner == &details.owner, Error::::NoPermission); } - CollectionMetadataOf::::try_mutate_exists(collection, |metadata| { + CollectionMetadataOf::::try_mutate_exists(collection.clone(), |metadata| { let was_frozen = metadata.as_ref().map_or(false, |m| m.is_frozen); ensure!(maybe_check_owner.is_none() || !was_frozen, Error::::Frozen); @@ -1520,7 +1520,6 @@ pub mod pallet { /// Emits `ItemBought` on success. #[pallet::call_index(25)] #[pallet::weight(T::WeightInfo::buy_item())] - #[transactional] pub fn buy_item( origin: OriginFor, collection: T::CollectionId, diff --git a/frame/uniques/src/migration.rs b/frame/uniques/src/migration.rs index fd98c030d7f1..6c92b753b4ac 100644 --- a/frame/uniques/src/migration.rs +++ b/frame/uniques/src/migration.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/uniques/src/mock.rs b/frame/uniques/src/mock.rs index d6ed5cc5cc23..5c44a7ed7a53 100644 --- a/frame/uniques/src/mock.rs +++ b/frame/uniques/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,20 +26,16 @@ use frame_support::{ }; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Uniques: pallet_uniques::{Pallet, Call, Storage, Event}, } @@ -51,13 +47,12 @@ impl frame_system::Config for Test { type BlockLength = (); type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type DbWeight = (); @@ -82,6 +77,10 @@ impl pallet_balances::Config for Test { type MaxLocks = (); type MaxReserves = ConstU32<50>; type ReserveIdentifier = [u8; 8]; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } impl Config for Test { @@ -106,7 +105,7 @@ impl Config for Test { } pub(crate) fn new_test_ext() -> sp_io::TestExternalities { - let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let t = frame_system::GenesisConfig::::default().build_storage().unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); diff --git a/frame/uniques/src/tests.rs b/frame/uniques/src/tests.rs index 7af54ddbb188..993552c3a2aa 100644 --- a/frame/uniques/src/tests.rs +++ b/frame/uniques/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/uniques/src/types.rs b/frame/uniques/src/types.rs index 98e056163d28..5b13998153e0 100644 --- a/frame/uniques/src/types.rs +++ b/frame/uniques/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/uniques/src/weights.rs b/frame/uniques/src/weights.rs index 8a8e1090bb71..9d1b8e4db571 100644 --- a/frame/uniques/src/weights.rs +++ b/frame/uniques/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_uniques //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_uniques +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_uniques. pub trait WeightInfo { @@ -78,486 +83,784 @@ pub trait WeightInfo { /// Weights for pallet_uniques using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassAccount (r:0 w:1) + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques ClassAccount (r:0 w:1) + /// Proof: Uniques ClassAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) fn create() -> Weight { - // Minimum execution time: 35_358 nanoseconds. - Weight::from_ref_time(35_935_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassAccount (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `249` + // Estimated: `3643` + // Minimum execution time: 30_534_000 picoseconds. + Weight::from_parts(31_876_000, 3643) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques ClassAccount (r:0 w:1) + /// Proof: Uniques ClassAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) fn force_create() -> Weight { - // Minimum execution time: 22_767 nanoseconds. - Weight::from_ref_time(23_235_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques Asset (r:1 w:0) - // Storage: Uniques ClassAccount (r:0 w:1) - // Storage: Uniques Attribute (r:0 w:1000) - // Storage: Uniques ClassMetadataOf (r:0 w:1) - // Storage: Uniques InstanceMetadataOf (r:0 w:1000) - // Storage: Uniques CollectionMaxSupply (r:0 w:1) - // Storage: Uniques Account (r:0 w:20) + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `3643` + // Minimum execution time: 14_394_000 picoseconds. + Weight::from_parts(15_042_000, 3643) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques Asset (r:1001 w:1000) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) + /// Storage: Uniques InstanceMetadataOf (r:1000 w:1000) + /// Proof: Uniques InstanceMetadataOf (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) + /// Storage: Uniques Attribute (r:1000 w:1000) + /// Proof: Uniques Attribute (max_values: None, max_size: Some(364), added: 2839, mode: MaxEncodedLen) + /// Storage: Uniques ClassAccount (r:0 w:1) + /// Proof: Uniques ClassAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) + /// Storage: Uniques ClassMetadataOf (r:0 w:1) + /// Proof: Uniques ClassMetadataOf (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Uniques Account (r:0 w:1000) + /// Proof: Uniques Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Uniques CollectionMaxSupply (r:0 w:1) + /// Proof: Uniques CollectionMaxSupply (max_values: None, max_size: Some(24), added: 2499, mode: MaxEncodedLen) /// The range of component `n` is `[0, 1000]`. /// The range of component `m` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - // Minimum execution time: 2_453_194 nanoseconds. - Weight::from_ref_time(2_469_109_000 as u64) - // Standard Error: 27_900 - .saturating_add(Weight::from_ref_time(8_974_176 as u64).saturating_mul(n as u64)) - // Standard Error: 27_900 - .saturating_add(Weight::from_ref_time(344_842 as u64).saturating_mul(m as u64)) - // Standard Error: 27_900 - .saturating_add(Weight::from_ref_time(185_438 as u64).saturating_mul(a as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - .saturating_add(T::DbWeight::get().writes((2 as u64).saturating_mul(n as u64))) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(m as u64))) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(a as u64))) - } - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques CollectionMaxSupply (r:1 w:0) - // Storage: Uniques Account (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `418 + a * (107 ±0) + m * (56 ±0) + n * (76 ±0)` + // Estimated: `3643 + a * (2839 ±0) + m * (2583 ±0) + n * (2597 ±0)` + // Minimum execution time: 3_025_324_000 picoseconds. + Weight::from_parts(3_050_363_000, 3643) + // Standard Error: 30_739 + .saturating_add(Weight::from_parts(6_839_633, 0).saturating_mul(n.into())) + // Standard Error: 30_739 + .saturating_add(Weight::from_parts(291_566, 0).saturating_mul(m.into())) + // Standard Error: 30_739 + .saturating_add(Weight::from_parts(514_334, 0).saturating_mul(a.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(m.into()))) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(m.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 2839).saturating_mul(a.into())) + .saturating_add(Weight::from_parts(0, 2583).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 2597).saturating_mul(n.into())) + } + /// Storage: Uniques Asset (r:1 w:1) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques CollectionMaxSupply (r:1 w:0) + /// Proof: Uniques CollectionMaxSupply (max_values: None, max_size: Some(24), added: 2499, mode: MaxEncodedLen) + /// Storage: Uniques Account (r:0 w:1) + /// Proof: Uniques Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) fn mint() -> Weight { - // Minimum execution time: 45_115 nanoseconds. - Weight::from_ref_time(45_746_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Account (r:0 w:1) - // Storage: Uniques ItemPriceOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `349` + // Estimated: `3643` + // Minimum execution time: 35_959_000 picoseconds. + Weight::from_parts(36_974_000, 3643) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques Asset (r:1 w:1) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) + /// Storage: Uniques Account (r:0 w:1) + /// Proof: Uniques Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Uniques ItemPriceOf (r:0 w:1) + /// Proof: Uniques ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) fn burn() -> Weight { - // Minimum execution time: 46_447 nanoseconds. - Weight::from_ref_time(46_994_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Account (r:0 w:2) - // Storage: Uniques ItemPriceOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `3643` + // Minimum execution time: 37_318_000 picoseconds. + Weight::from_parts(38_531_000, 3643) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Uniques Class (r:1 w:0) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques Asset (r:1 w:1) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) + /// Storage: Uniques Account (r:0 w:2) + /// Proof: Uniques Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Uniques ItemPriceOf (r:0 w:1) + /// Proof: Uniques ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) fn transfer() -> Weight { - // Minimum execution time: 35_953 nanoseconds. - Weight::from_ref_time(36_375_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques Asset (r:102 w:102) + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `3643` + // Minimum execution time: 25_943_000 picoseconds. + Weight::from_parts(27_112_000, 3643) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques Asset (r:5000 w:5000) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - // Minimum execution time: 24_238 nanoseconds. - Weight::from_ref_time(24_788_000 as u64) - // Standard Error: 9_232 - .saturating_add(Weight::from_ref_time(11_322_011 as u64).saturating_mul(i as u64)) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().reads((1 as u64).saturating_mul(i as u64))) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - .saturating_add(T::DbWeight::get().writes((1 as u64).saturating_mul(i as u64))) - } - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Class (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `805 + i * (76 ±0)` + // Estimated: `3643 + i * (2597 ±0)` + // Minimum execution time: 13_946_000 picoseconds. + Weight::from_parts(14_259_000, 3643) + // Standard Error: 14_996 + .saturating_add(Weight::from_parts(16_873_386, 0).saturating_mul(i.into())) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(i.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(i.into()))) + .saturating_add(Weight::from_parts(0, 2597).saturating_mul(i.into())) + } + /// Storage: Uniques Asset (r:1 w:1) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) + /// Storage: Uniques Class (r:1 w:0) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) fn freeze() -> Weight { - // Minimum execution time: 28_595 nanoseconds. - Weight::from_ref_time(29_280_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Class (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `3643` + // Minimum execution time: 17_872_000 picoseconds. + Weight::from_parts(18_398_000, 3643) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Uniques Asset (r:1 w:1) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) + /// Storage: Uniques Class (r:1 w:0) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) fn thaw() -> Weight { - // Minimum execution time: 28_581 nanoseconds. - Weight::from_ref_time(29_038_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Uniques Class (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `3643` + // Minimum execution time: 17_623_000 picoseconds. + Weight::from_parts(18_401_000, 3643) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) fn freeze_collection() -> Weight { - // Minimum execution time: 24_298 nanoseconds. - Weight::from_ref_time(24_742_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Uniques Class (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `349` + // Estimated: `3643` + // Minimum execution time: 12_967_000 picoseconds. + Weight::from_parts(13_462_000, 3643) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) fn thaw_collection() -> Weight { - // Minimum execution time: 24_004 nanoseconds. - Weight::from_ref_time(24_536_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Uniques OwnershipAcceptance (r:1 w:1) - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassAccount (r:0 w:2) + // Proof Size summary in bytes: + // Measured: `349` + // Estimated: `3643` + // Minimum execution time: 12_840_000 picoseconds. + Weight::from_parts(13_375_000, 3643) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Uniques OwnershipAcceptance (r:1 w:1) + /// Proof: Uniques OwnershipAcceptance (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques ClassAccount (r:0 w:2) + /// Proof: Uniques ClassAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) fn transfer_ownership() -> Weight { - // Minimum execution time: 32_599 nanoseconds. - Weight::from_ref_time(33_201_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) - } - // Storage: Uniques Class (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `423` + // Estimated: `3643` + // Minimum execution time: 21_594_000 picoseconds. + Weight::from_parts(22_151_000, 3643) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) fn set_team() -> Weight { - // Minimum execution time: 25_137 nanoseconds. - Weight::from_ref_time(25_877_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassAccount (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `349` + // Estimated: `3643` + // Minimum execution time: 13_388_000 picoseconds. + Weight::from_parts(14_130_000, 3643) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques ClassAccount (r:0 w:1) + /// Proof: Uniques ClassAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) fn force_item_status() -> Weight { - // Minimum execution time: 27_736 nanoseconds. - Weight::from_ref_time(28_279_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques InstanceMetadataOf (r:1 w:0) - // Storage: Uniques Attribute (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `349` + // Estimated: `3643` + // Minimum execution time: 16_539_000 picoseconds. + Weight::from_parts(17_107_000, 3643) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques InstanceMetadataOf (r:1 w:0) + /// Proof: Uniques InstanceMetadataOf (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) + /// Storage: Uniques Attribute (r:1 w:1) + /// Proof: Uniques Attribute (max_values: None, max_size: Some(364), added: 2839, mode: MaxEncodedLen) fn set_attribute() -> Weight { - // Minimum execution time: 51_195 nanoseconds. - Weight::from_ref_time(51_674_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques InstanceMetadataOf (r:1 w:0) - // Storage: Uniques Attribute (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `547` + // Estimated: `3829` + // Minimum execution time: 39_314_000 picoseconds. + Weight::from_parts(40_531_000, 3829) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques InstanceMetadataOf (r:1 w:0) + /// Proof: Uniques InstanceMetadataOf (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) + /// Storage: Uniques Attribute (r:1 w:1) + /// Proof: Uniques Attribute (max_values: None, max_size: Some(364), added: 2839, mode: MaxEncodedLen) fn clear_attribute() -> Weight { - // Minimum execution time: 50_159 nanoseconds. - Weight::from_ref_time(51_412_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques InstanceMetadataOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `936` + // Estimated: `3829` + // Minimum execution time: 37_804_000 picoseconds. + Weight::from_parts(38_790_000, 3829) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques InstanceMetadataOf (r:1 w:1) + /// Proof: Uniques InstanceMetadataOf (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) fn set_metadata() -> Weight { - // Minimum execution time: 42_608 nanoseconds. - Weight::from_ref_time(42_880_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques InstanceMetadataOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `415` + // Estimated: `3643` + // Minimum execution time: 29_583_000 picoseconds. + Weight::from_parts(30_559_000, 3643) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques InstanceMetadataOf (r:1 w:1) + /// Proof: Uniques InstanceMetadataOf (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) fn clear_metadata() -> Weight { - // Minimum execution time: 43_239 nanoseconds. - Weight::from_ref_time(43_752_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassMetadataOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `547` + // Estimated: `3643` + // Minimum execution time: 30_036_000 picoseconds. + Weight::from_parts(31_043_000, 3643) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques ClassMetadataOf (r:1 w:1) + /// Proof: Uniques ClassMetadataOf (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) fn set_collection_metadata() -> Weight { - // Minimum execution time: 41_224 nanoseconds. - Weight::from_ref_time(41_974_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) - } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques ClassMetadataOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `349` + // Estimated: `3643` + // Minimum execution time: 31_063_000 picoseconds. + Weight::from_parts(31_803_000, 3643) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + } + /// Storage: Uniques Class (r:1 w:0) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques ClassMetadataOf (r:1 w:1) + /// Proof: Uniques ClassMetadataOf (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) fn clear_collection_metadata() -> Weight { - // Minimum execution time: 40_836 nanoseconds. - Weight::from_ref_time(41_864_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Asset (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `461` + // Estimated: `3643` + // Minimum execution time: 29_707_000 picoseconds. + Weight::from_parts(30_428_000, 3643) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Uniques Class (r:1 w:0) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques Asset (r:1 w:1) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) fn approve_transfer() -> Weight { - // Minimum execution time: 29_558 nanoseconds. - Weight::from_ref_time(29_948_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Asset (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `3643` + // Minimum execution time: 18_328_000 picoseconds. + Weight::from_parts(18_927_000, 3643) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Uniques Class (r:1 w:0) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques Asset (r:1 w:1) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) fn cancel_approval() -> Weight { - // Minimum execution time: 29_694 nanoseconds. - Weight::from_ref_time(30_156_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Uniques OwnershipAcceptance (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `528` + // Estimated: `3643` + // Minimum execution time: 18_350_000 picoseconds. + Weight::from_parts(19_072_000, 3643) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Uniques OwnershipAcceptance (r:1 w:1) + /// Proof: Uniques OwnershipAcceptance (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) fn set_accept_ownership() -> Weight { - // Minimum execution time: 27_819 nanoseconds. - Weight::from_ref_time(28_245_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Uniques CollectionMaxSupply (r:1 w:1) - // Storage: Uniques Class (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `3517` + // Minimum execution time: 14_729_000 picoseconds. + Weight::from_parts(15_084_000, 3517) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Uniques CollectionMaxSupply (r:1 w:1) + /// Proof: Uniques CollectionMaxSupply (max_values: None, max_size: Some(24), added: 2499, mode: MaxEncodedLen) + /// Storage: Uniques Class (r:1 w:0) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) fn set_collection_max_supply() -> Weight { - // Minimum execution time: 26_317 nanoseconds. - Weight::from_ref_time(26_893_000 as u64) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Uniques Asset (r:1 w:0) - // Storage: Uniques ItemPriceOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `349` + // Estimated: `3643` + // Minimum execution time: 14_907_000 picoseconds. + Weight::from_parts(15_556_000, 3643) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Uniques Asset (r:1 w:0) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) + /// Storage: Uniques ItemPriceOf (r:0 w:1) + /// Proof: Uniques ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) fn set_price() -> Weight { - // Minimum execution time: 26_546 nanoseconds. - Weight::from_ref_time(27_142_000 as u64) - .saturating_add(T::DbWeight::get().reads(1 as u64)) - .saturating_add(T::DbWeight::get().writes(1 as u64)) - } - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques ItemPriceOf (r:1 w:1) - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Account (r:0 w:2) + // Proof Size summary in bytes: + // Measured: `326` + // Estimated: `3587` + // Minimum execution time: 15_312_000 picoseconds. + Weight::from_parts(15_721_000, 3587) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: Uniques Asset (r:1 w:1) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) + /// Storage: Uniques ItemPriceOf (r:1 w:1) + /// Proof: Uniques ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) + /// Storage: Uniques Class (r:1 w:0) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques Account (r:0 w:2) + /// Proof: Uniques Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) fn buy_item() -> Weight { - // Minimum execution time: 49_238 nanoseconds. - Weight::from_ref_time(50_444_000 as u64) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `607` + // Estimated: `3643` + // Minimum execution time: 35_381_000 picoseconds. + Weight::from_parts(36_347_000, 3643) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassAccount (r:0 w:1) + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques ClassAccount (r:0 w:1) + /// Proof: Uniques ClassAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) fn create() -> Weight { - // Minimum execution time: 35_358 nanoseconds. - Weight::from_ref_time(35_935_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassAccount (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `249` + // Estimated: `3643` + // Minimum execution time: 30_534_000 picoseconds. + Weight::from_parts(31_876_000, 3643) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques ClassAccount (r:0 w:1) + /// Proof: Uniques ClassAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) fn force_create() -> Weight { - // Minimum execution time: 22_767 nanoseconds. - Weight::from_ref_time(23_235_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques Asset (r:1 w:0) - // Storage: Uniques ClassAccount (r:0 w:1) - // Storage: Uniques Attribute (r:0 w:1000) - // Storage: Uniques ClassMetadataOf (r:0 w:1) - // Storage: Uniques InstanceMetadataOf (r:0 w:1000) - // Storage: Uniques CollectionMaxSupply (r:0 w:1) - // Storage: Uniques Account (r:0 w:20) + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `3643` + // Minimum execution time: 14_394_000 picoseconds. + Weight::from_parts(15_042_000, 3643) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques Asset (r:1001 w:1000) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) + /// Storage: Uniques InstanceMetadataOf (r:1000 w:1000) + /// Proof: Uniques InstanceMetadataOf (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) + /// Storage: Uniques Attribute (r:1000 w:1000) + /// Proof: Uniques Attribute (max_values: None, max_size: Some(364), added: 2839, mode: MaxEncodedLen) + /// Storage: Uniques ClassAccount (r:0 w:1) + /// Proof: Uniques ClassAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) + /// Storage: Uniques ClassMetadataOf (r:0 w:1) + /// Proof: Uniques ClassMetadataOf (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Uniques Account (r:0 w:1000) + /// Proof: Uniques Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Uniques CollectionMaxSupply (r:0 w:1) + /// Proof: Uniques CollectionMaxSupply (max_values: None, max_size: Some(24), added: 2499, mode: MaxEncodedLen) /// The range of component `n` is `[0, 1000]`. /// The range of component `m` is `[0, 1000]`. /// The range of component `a` is `[0, 1000]`. fn destroy(n: u32, m: u32, a: u32, ) -> Weight { - // Minimum execution time: 2_453_194 nanoseconds. - Weight::from_ref_time(2_469_109_000 as u64) - // Standard Error: 27_900 - .saturating_add(Weight::from_ref_time(8_974_176 as u64).saturating_mul(n as u64)) - // Standard Error: 27_900 - .saturating_add(Weight::from_ref_time(344_842 as u64).saturating_mul(m as u64)) - // Standard Error: 27_900 - .saturating_add(Weight::from_ref_time(185_438 as u64).saturating_mul(a as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - .saturating_add(RocksDbWeight::get().writes((2 as u64).saturating_mul(n as u64))) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(m as u64))) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(a as u64))) - } - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques CollectionMaxSupply (r:1 w:0) - // Storage: Uniques Account (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `418 + a * (107 ±0) + m * (56 ±0) + n * (76 ±0)` + // Estimated: `3643 + a * (2839 ±0) + m * (2583 ±0) + n * (2597 ±0)` + // Minimum execution time: 3_025_324_000 picoseconds. + Weight::from_parts(3_050_363_000, 3643) + // Standard Error: 30_739 + .saturating_add(Weight::from_parts(6_839_633, 0).saturating_mul(n.into())) + // Standard Error: 30_739 + .saturating_add(Weight::from_parts(291_566, 0).saturating_mul(m.into())) + // Standard Error: 30_739 + .saturating_add(Weight::from_parts(514_334, 0).saturating_mul(a.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(m.into()))) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(a.into()))) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(m.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(a.into()))) + .saturating_add(Weight::from_parts(0, 2839).saturating_mul(a.into())) + .saturating_add(Weight::from_parts(0, 2583).saturating_mul(m.into())) + .saturating_add(Weight::from_parts(0, 2597).saturating_mul(n.into())) + } + /// Storage: Uniques Asset (r:1 w:1) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques CollectionMaxSupply (r:1 w:0) + /// Proof: Uniques CollectionMaxSupply (max_values: None, max_size: Some(24), added: 2499, mode: MaxEncodedLen) + /// Storage: Uniques Account (r:0 w:1) + /// Proof: Uniques Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) fn mint() -> Weight { - // Minimum execution time: 45_115 nanoseconds. - Weight::from_ref_time(45_746_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Account (r:0 w:1) - // Storage: Uniques ItemPriceOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `349` + // Estimated: `3643` + // Minimum execution time: 35_959_000 picoseconds. + Weight::from_parts(36_974_000, 3643) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques Asset (r:1 w:1) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) + /// Storage: Uniques Account (r:0 w:1) + /// Proof: Uniques Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Uniques ItemPriceOf (r:0 w:1) + /// Proof: Uniques ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) fn burn() -> Weight { - // Minimum execution time: 46_447 nanoseconds. - Weight::from_ref_time(46_994_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Account (r:0 w:2) - // Storage: Uniques ItemPriceOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `3643` + // Minimum execution time: 37_318_000 picoseconds. + Weight::from_parts(38_531_000, 3643) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Uniques Class (r:1 w:0) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques Asset (r:1 w:1) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) + /// Storage: Uniques Account (r:0 w:2) + /// Proof: Uniques Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) + /// Storage: Uniques ItemPriceOf (r:0 w:1) + /// Proof: Uniques ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) fn transfer() -> Weight { - // Minimum execution time: 35_953 nanoseconds. - Weight::from_ref_time(36_375_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques Asset (r:102 w:102) + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `3643` + // Minimum execution time: 25_943_000 picoseconds. + Weight::from_parts(27_112_000, 3643) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques Asset (r:5000 w:5000) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) /// The range of component `i` is `[0, 5000]`. fn redeposit(i: u32, ) -> Weight { - // Minimum execution time: 24_238 nanoseconds. - Weight::from_ref_time(24_788_000 as u64) - // Standard Error: 9_232 - .saturating_add(Weight::from_ref_time(11_322_011 as u64).saturating_mul(i as u64)) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().reads((1 as u64).saturating_mul(i as u64))) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - .saturating_add(RocksDbWeight::get().writes((1 as u64).saturating_mul(i as u64))) - } - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Class (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `805 + i * (76 ±0)` + // Estimated: `3643 + i * (2597 ±0)` + // Minimum execution time: 13_946_000 picoseconds. + Weight::from_parts(14_259_000, 3643) + // Standard Error: 14_996 + .saturating_add(Weight::from_parts(16_873_386, 0).saturating_mul(i.into())) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(i.into()))) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(i.into()))) + .saturating_add(Weight::from_parts(0, 2597).saturating_mul(i.into())) + } + /// Storage: Uniques Asset (r:1 w:1) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) + /// Storage: Uniques Class (r:1 w:0) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) fn freeze() -> Weight { - // Minimum execution time: 28_595 nanoseconds. - Weight::from_ref_time(29_280_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques Class (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `3643` + // Minimum execution time: 17_872_000 picoseconds. + Weight::from_parts(18_398_000, 3643) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Uniques Asset (r:1 w:1) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) + /// Storage: Uniques Class (r:1 w:0) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) fn thaw() -> Weight { - // Minimum execution time: 28_581 nanoseconds. - Weight::from_ref_time(29_038_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Uniques Class (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `3643` + // Minimum execution time: 17_623_000 picoseconds. + Weight::from_parts(18_401_000, 3643) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) fn freeze_collection() -> Weight { - // Minimum execution time: 24_298 nanoseconds. - Weight::from_ref_time(24_742_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Uniques Class (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `349` + // Estimated: `3643` + // Minimum execution time: 12_967_000 picoseconds. + Weight::from_parts(13_462_000, 3643) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) fn thaw_collection() -> Weight { - // Minimum execution time: 24_004 nanoseconds. - Weight::from_ref_time(24_536_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Uniques OwnershipAcceptance (r:1 w:1) - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassAccount (r:0 w:2) + // Proof Size summary in bytes: + // Measured: `349` + // Estimated: `3643` + // Minimum execution time: 12_840_000 picoseconds. + Weight::from_parts(13_375_000, 3643) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Uniques OwnershipAcceptance (r:1 w:1) + /// Proof: Uniques OwnershipAcceptance (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques ClassAccount (r:0 w:2) + /// Proof: Uniques ClassAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) fn transfer_ownership() -> Weight { - // Minimum execution time: 32_599 nanoseconds. - Weight::from_ref_time(33_201_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) - } - // Storage: Uniques Class (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `423` + // Estimated: `3643` + // Minimum execution time: 21_594_000 picoseconds. + Weight::from_parts(22_151_000, 3643) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) fn set_team() -> Weight { - // Minimum execution time: 25_137 nanoseconds. - Weight::from_ref_time(25_877_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassAccount (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `349` + // Estimated: `3643` + // Minimum execution time: 13_388_000 picoseconds. + Weight::from_parts(14_130_000, 3643) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques ClassAccount (r:0 w:1) + /// Proof: Uniques ClassAccount (max_values: None, max_size: Some(68), added: 2543, mode: MaxEncodedLen) fn force_item_status() -> Weight { - // Minimum execution time: 27_736 nanoseconds. - Weight::from_ref_time(28_279_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques InstanceMetadataOf (r:1 w:0) - // Storage: Uniques Attribute (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `349` + // Estimated: `3643` + // Minimum execution time: 16_539_000 picoseconds. + Weight::from_parts(17_107_000, 3643) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques InstanceMetadataOf (r:1 w:0) + /// Proof: Uniques InstanceMetadataOf (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) + /// Storage: Uniques Attribute (r:1 w:1) + /// Proof: Uniques Attribute (max_values: None, max_size: Some(364), added: 2839, mode: MaxEncodedLen) fn set_attribute() -> Weight { - // Minimum execution time: 51_195 nanoseconds. - Weight::from_ref_time(51_674_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques InstanceMetadataOf (r:1 w:0) - // Storage: Uniques Attribute (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `547` + // Estimated: `3829` + // Minimum execution time: 39_314_000 picoseconds. + Weight::from_parts(40_531_000, 3829) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques InstanceMetadataOf (r:1 w:0) + /// Proof: Uniques InstanceMetadataOf (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) + /// Storage: Uniques Attribute (r:1 w:1) + /// Proof: Uniques Attribute (max_values: None, max_size: Some(364), added: 2839, mode: MaxEncodedLen) fn clear_attribute() -> Weight { - // Minimum execution time: 50_159 nanoseconds. - Weight::from_ref_time(51_412_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques InstanceMetadataOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `936` + // Estimated: `3829` + // Minimum execution time: 37_804_000 picoseconds. + Weight::from_parts(38_790_000, 3829) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques InstanceMetadataOf (r:1 w:1) + /// Proof: Uniques InstanceMetadataOf (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) fn set_metadata() -> Weight { - // Minimum execution time: 42_608 nanoseconds. - Weight::from_ref_time(42_880_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques InstanceMetadataOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `415` + // Estimated: `3643` + // Minimum execution time: 29_583_000 picoseconds. + Weight::from_parts(30_559_000, 3643) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques InstanceMetadataOf (r:1 w:1) + /// Proof: Uniques InstanceMetadataOf (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen) fn clear_metadata() -> Weight { - // Minimum execution time: 43_239 nanoseconds. - Weight::from_ref_time(43_752_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Uniques Class (r:1 w:1) - // Storage: Uniques ClassMetadataOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `547` + // Estimated: `3643` + // Minimum execution time: 30_036_000 picoseconds. + Weight::from_parts(31_043_000, 3643) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Uniques Class (r:1 w:1) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques ClassMetadataOf (r:1 w:1) + /// Proof: Uniques ClassMetadataOf (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) fn set_collection_metadata() -> Weight { - // Minimum execution time: 41_224 nanoseconds. - Weight::from_ref_time(41_974_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) - } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques ClassMetadataOf (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `349` + // Estimated: `3643` + // Minimum execution time: 31_063_000 picoseconds. + Weight::from_parts(31_803_000, 3643) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + } + /// Storage: Uniques Class (r:1 w:0) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques ClassMetadataOf (r:1 w:1) + /// Proof: Uniques ClassMetadataOf (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) fn clear_collection_metadata() -> Weight { - // Minimum execution time: 40_836 nanoseconds. - Weight::from_ref_time(41_864_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Asset (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `461` + // Estimated: `3643` + // Minimum execution time: 29_707_000 picoseconds. + Weight::from_parts(30_428_000, 3643) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Uniques Class (r:1 w:0) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques Asset (r:1 w:1) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) fn approve_transfer() -> Weight { - // Minimum execution time: 29_558 nanoseconds. - Weight::from_ref_time(29_948_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Asset (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `495` + // Estimated: `3643` + // Minimum execution time: 18_328_000 picoseconds. + Weight::from_parts(18_927_000, 3643) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Uniques Class (r:1 w:0) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques Asset (r:1 w:1) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) fn cancel_approval() -> Weight { - // Minimum execution time: 29_694 nanoseconds. - Weight::from_ref_time(30_156_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Uniques OwnershipAcceptance (r:1 w:1) + // Proof Size summary in bytes: + // Measured: `528` + // Estimated: `3643` + // Minimum execution time: 18_350_000 picoseconds. + Weight::from_parts(19_072_000, 3643) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Uniques OwnershipAcceptance (r:1 w:1) + /// Proof: Uniques OwnershipAcceptance (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen) fn set_accept_ownership() -> Weight { - // Minimum execution time: 27_819 nanoseconds. - Weight::from_ref_time(28_245_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Uniques CollectionMaxSupply (r:1 w:1) - // Storage: Uniques Class (r:1 w:0) + // Proof Size summary in bytes: + // Measured: `109` + // Estimated: `3517` + // Minimum execution time: 14_729_000 picoseconds. + Weight::from_parts(15_084_000, 3517) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Uniques CollectionMaxSupply (r:1 w:1) + /// Proof: Uniques CollectionMaxSupply (max_values: None, max_size: Some(24), added: 2499, mode: MaxEncodedLen) + /// Storage: Uniques Class (r:1 w:0) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) fn set_collection_max_supply() -> Weight { - // Minimum execution time: 26_317 nanoseconds. - Weight::from_ref_time(26_893_000 as u64) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Uniques Asset (r:1 w:0) - // Storage: Uniques ItemPriceOf (r:0 w:1) + // Proof Size summary in bytes: + // Measured: `349` + // Estimated: `3643` + // Minimum execution time: 14_907_000 picoseconds. + Weight::from_parts(15_556_000, 3643) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Uniques Asset (r:1 w:0) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) + /// Storage: Uniques ItemPriceOf (r:0 w:1) + /// Proof: Uniques ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) fn set_price() -> Weight { - // Minimum execution time: 26_546 nanoseconds. - Weight::from_ref_time(27_142_000 as u64) - .saturating_add(RocksDbWeight::get().reads(1 as u64)) - .saturating_add(RocksDbWeight::get().writes(1 as u64)) - } - // Storage: Uniques Asset (r:1 w:1) - // Storage: Uniques ItemPriceOf (r:1 w:1) - // Storage: Uniques Class (r:1 w:0) - // Storage: Uniques Account (r:0 w:2) + // Proof Size summary in bytes: + // Measured: `326` + // Estimated: `3587` + // Minimum execution time: 15_312_000 picoseconds. + Weight::from_parts(15_721_000, 3587) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + /// Storage: Uniques Asset (r:1 w:1) + /// Proof: Uniques Asset (max_values: None, max_size: Some(122), added: 2597, mode: MaxEncodedLen) + /// Storage: Uniques ItemPriceOf (r:1 w:1) + /// Proof: Uniques ItemPriceOf (max_values: None, max_size: Some(89), added: 2564, mode: MaxEncodedLen) + /// Storage: Uniques Class (r:1 w:0) + /// Proof: Uniques Class (max_values: None, max_size: Some(178), added: 2653, mode: MaxEncodedLen) + /// Storage: Uniques Account (r:0 w:2) + /// Proof: Uniques Account (max_values: None, max_size: Some(88), added: 2563, mode: MaxEncodedLen) fn buy_item() -> Weight { - // Minimum execution time: 49_238 nanoseconds. - Weight::from_ref_time(50_444_000 as u64) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `607` + // Estimated: `3643` + // Minimum execution time: 35_381_000 picoseconds. + Weight::from_parts(36_347_000, 3643) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } } diff --git a/frame/utility/Cargo.toml b/frame/utility/Cargo.toml index 099526d3dcb9..6c5f7fa48a88 100644 --- a/frame/utility/Cargo.toml +++ b/frame/utility/Cargo.toml @@ -13,22 +13,22 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-root-testing = { version = "1.0.0-dev", path = "../root-testing" } pallet-collective = { version = "4.0.0-dev", path = "../collective" } pallet-timestamp = { version = "4.0.0-dev", path = "../timestamp" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } [features] default = ["std"] @@ -47,5 +47,6 @@ runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", + "pallet-collective/runtime-benchmarks", ] try-runtime = ["frame-support/try-runtime"] diff --git a/frame/utility/README.md b/frame/utility/README.md index 1beeb66733dd..db19b0cf8cf9 100644 --- a/frame/utility/README.md +++ b/frame/utility/README.md @@ -1,8 +1,8 @@ # Utility Module A stateless module with helpers for dispatch management which does no re-authentication. -- [`utility::Config`](https://docs.rs/pallet-utility/latest/pallet_utility/trait.Config.html) -- [`Call`](https://docs.rs/pallet-utility/latest/pallet_utility/enum.Call.html) +- [`utility::Config`](https://docs.rs/pallet-utility/latest/pallet_utility/pallet/trait.Config.html) +- [`Call`](https://docs.rs/pallet-utility/latest/pallet_utility/pallet/enum.Call.html) ## Overview diff --git a/frame/utility/src/benchmarking.rs b/frame/utility/src/benchmarking.rs index 07bc14951cb3..78911fd310e8 100644 --- a/frame/utility/src/benchmarking.rs +++ b/frame/utility/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller}; use frame_system::RawOrigin; const SEED: u32 = 0; diff --git a/frame/utility/src/lib.rs b/frame/utility/src/lib.rs index 2d60ae15679d..af212a31eb97 100644 --- a/frame/utility/src/lib.rs +++ b/frame/utility/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -76,7 +76,6 @@ pub mod pallet { use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); /// Configuration trait. @@ -172,9 +171,8 @@ pub mod pallet { /// If origin is root then the calls are dispatched without checking origin filter. (This /// includes bypassing `frame_system::Config::BaseCallFilter`). /// - /// # - /// - Complexity: O(C) where C is the number of calls to be batched. - /// # + /// ## Complexity + /// - O(C) where C is the number of calls to be batched. /// /// This will return `Ok` in all circumstances. To determine the success of the batch, an /// event is deposited. If a call failed and the batch was interrupted, then the @@ -301,9 +299,8 @@ pub mod pallet { /// If origin is root then the calls are dispatched without checking origin filter. (This /// includes bypassing `frame_system::Config::BaseCallFilter`). /// - /// # - /// - Complexity: O(C) where C is the number of calls to be batched. - /// # + /// ## Complexity + /// - O(C) where C is the number of calls to be batched. #[pallet::call_index(2)] #[pallet::weight({ let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::>(); @@ -374,12 +371,8 @@ pub mod pallet { /// /// The dispatch origin for this call must be _Root_. /// - /// # + /// ## Complexity /// - O(1). - /// - Limited storage reads. - /// - One DB write (event). - /// - Weight of derivative `call` execution + T::WeightInfo::dispatch_as(). - /// # #[pallet::call_index(3)] #[pallet::weight({ let dispatch_info = call.get_dispatch_info(); @@ -415,9 +408,8 @@ pub mod pallet { /// If origin is root then the calls are dispatch without checking origin filter. (This /// includes bypassing `frame_system::Config::BaseCallFilter`). /// - /// # - /// - Complexity: O(C) where C is the number of calls to be batched. - /// # + /// ## Complexity + /// - O(C) where C is the number of calls to be batched. #[pallet::call_index(4)] #[pallet::weight({ let dispatch_infos = calls.iter().map(|call| call.get_dispatch_info()).collect::>(); diff --git a/frame/utility/src/tests.rs b/frame/utility/src/tests.rs index f9d6a16c1a0d..c2fd3a851c31 100644 --- a/frame/utility/src/tests.rs +++ b/frame/utility/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,20 +27,20 @@ use frame_support::{ dispatch::{DispatchError, DispatchErrorWithPostInfo, Dispatchable, Pays}, error::BadOrigin, parameter_types, storage, - traits::{ConstU32, ConstU64, Contains, GenesisBuild}, + traits::{ConstU32, ConstU64, Contains}, weights::Weight, }; use pallet_collective::{EnsureProportionAtLeast, Instance1}; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, Hash, IdentityLookup}, + BuildStorage, TokenError, }; type BlockNumber = u64; // example module to test behaviors. -#[frame_support::pallet] +#[frame_support::pallet(dev_mode)] pub mod example { use frame_support::{dispatch::WithPostDispatchInfo, pallet_prelude::*}; use frame_system::pallet_prelude::*; @@ -90,13 +90,12 @@ pub mod example { mod mock_democracy { pub use pallet::*; - #[frame_support::pallet] + #[frame_support::pallet(dev_mode)] pub mod pallet { use frame_support::pallet_prelude::*; use frame_system::pallet_prelude::*; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::config] @@ -125,16 +124,12 @@ mod mock_democracy { } } -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Timestamp: pallet_timestamp::{Call, Inherent}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, RootTesting: pallet_root_testing::{Pallet, Call, Storage}, @@ -155,14 +150,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -170,7 +164,7 @@ impl frame_system::Config for Test { type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); - type SystemWeightInfo = (); + type SystemWeightInfo = frame_system::weights::SubstrateWeight; type SS58Prefix = (); type OnSetCode = (); type MaxConsumers = ConstU32<16>; @@ -186,6 +180,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } impl pallet_root_testing::Config for Test {} @@ -205,6 +203,7 @@ parameter_types! { pub const MotionDuration: BlockNumber = MOTION_DURATION_IN_BLOCKS; pub const MaxProposals: u32 = 100; pub const MaxMembers: u32 = 100; + pub MaxProposalWeight: Weight = sp_runtime::Perbill::from_percent(50) * BlockWeights::get().max_block; } type CouncilCollective = pallet_collective::Instance1; @@ -217,6 +216,8 @@ impl pallet_collective::Config for Test { type MaxMembers = MaxMembers; type DefaultVote = pallet_collective::PrimeDefaultVote; type WeightInfo = (); + type SetMembersOrigin = frame_system::EnsureRoot; + type MaxProposalWeight = MaxProposalWeight; } impl example::Config for Test {} @@ -226,7 +227,7 @@ impl Contains for TestBaseCallFilter { fn contains(c: &RuntimeCall) -> bool { match *c { // Transfer works. Use `transfer_keep_alive` for a call that doesn't pass the filter. - RuntimeCall::Balances(pallet_balances::Call::transfer { .. }) => true, + RuntimeCall::Balances(pallet_balances::Call::transfer_allow_death { .. }) => true, RuntimeCall::Utility(_) => true, // For benchmarking, this acts as a noop call RuntimeCall::System(frame_system::Call::remark { .. }) => true, @@ -253,12 +254,12 @@ type ExampleCall = example::Call; type UtilityCall = crate::Call; use frame_system::Call as SystemCall; -use pallet_balances::{Call as BalancesCall, Error as BalancesError}; +use pallet_balances::Call as BalancesCall; use pallet_root_testing::Call as RootTestingCall; use pallet_timestamp::Call as TimestampCall; pub fn new_test_ext() -> sp_io::TestExternalities { - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![(1, 10), (2, 10), (3, 10), (4, 10), (5, 2)], } @@ -278,7 +279,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { } fn call_transfer(dest: u64, value: u64) -> RuntimeCall { - RuntimeCall::Balances(BalancesCall::transfer { dest, value }) + RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value }) } fn call_foobar(err: bool, start_weight: Weight, end_weight: Option) -> RuntimeCall { @@ -289,10 +290,10 @@ fn call_foobar(err: bool, start_weight: Weight, end_weight: Option) -> R fn as_derivative_works() { new_test_ext().execute_with(|| { let sub_1_0 = Utility::derivative_account_id(1, 0); - assert_ok!(Balances::transfer(RuntimeOrigin::signed(1), sub_1_0, 5)); + assert_ok!(Balances::transfer_allow_death(RuntimeOrigin::signed(1), sub_1_0, 5)); assert_err_ignore_postinfo!( Utility::as_derivative(RuntimeOrigin::signed(1), 1, Box::new(call_transfer(6, 3)),), - BalancesError::::InsufficientBalance + TokenError::FundsUnavailable, ); assert_ok!(Utility::as_derivative( RuntimeOrigin::signed(1), @@ -307,8 +308,8 @@ fn as_derivative_works() { #[test] fn as_derivative_handles_weight_refund() { new_test_ext().execute_with(|| { - let start_weight = Weight::from_ref_time(100); - let end_weight = Weight::from_ref_time(75); + let start_weight = Weight::from_parts(100, 0); + let end_weight = Weight::from_parts(75, 0); let diff = start_weight - end_weight; // Full weight when ok @@ -494,8 +495,8 @@ fn batch_weight_calculation_doesnt_overflow() { #[test] fn batch_handles_weight_refund() { new_test_ext().execute_with(|| { - let start_weight = Weight::from_ref_time(100); - let end_weight = Weight::from_ref_time(75); + let start_weight = Weight::from_parts(100, 0); + let end_weight = Weight::from_parts(75, 0); let diff = start_weight - end_weight; let batch_len = 4; @@ -599,7 +600,7 @@ fn batch_all_revert() { ), pays_fee: Pays::Yes }, - error: pallet_balances::Error::::InsufficientBalance.into() + error: TokenError::FundsUnavailable.into(), } ); assert_eq!(Balances::free_balance(1), 10); @@ -610,8 +611,8 @@ fn batch_all_revert() { #[test] fn batch_all_handles_weight_refund() { new_test_ext().execute_with(|| { - let start_weight = Weight::from_ref_time(100); - let end_weight = Weight::from_ref_time(75); + let start_weight = Weight::from_parts(100, 0); + let end_weight = Weight::from_parts(75, 0); let diff = start_weight - end_weight; let batch_len = 4; @@ -738,7 +739,7 @@ fn force_batch_works() { RuntimeOrigin::signed(1), vec![ call_transfer(2, 5), - call_foobar(true, Weight::from_ref_time(75), None), + call_foobar(true, Weight::from_parts(75, 0), None), call_transfer(2, 10), call_transfer(2, 5), ] @@ -909,12 +910,16 @@ fn batch_all_works_with_council_origin() { #[test] fn with_weight_works() { new_test_ext().execute_with(|| { + use frame_system::WeightInfo; let upgrade_code_call = Box::new(RuntimeCall::System(frame_system::Call::set_code_without_checks { code: vec![], })); // Weight before is max. - assert_eq!(upgrade_code_call.get_dispatch_info().weight, Weight::MAX); + assert_eq!( + upgrade_code_call.get_dispatch_info().weight, + ::SystemWeightInfo::set_code() + ); assert_eq!( upgrade_code_call.get_dispatch_info().class, frame_support::dispatch::DispatchClass::Operational diff --git a/frame/utility/src/weights.rs b/frame/utility/src/weights.rs index eac94e44b8db..1a3ea6c1f7fc 100644 --- a/frame/utility/src/weights.rs +++ b/frame/utility/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_utility //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_utility +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_utility. pub trait WeightInfo { @@ -59,32 +64,47 @@ pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { /// The range of component `c` is `[0, 1000]`. fn batch(c: u32, ) -> Weight { - // Minimum execution time: 14_470 nanoseconds. - Weight::from_ref_time(17_443_346 as u64) - // Standard Error: 2_037 - .saturating_add(Weight::from_ref_time(3_510_555 as u64).saturating_mul(c as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_763_000 picoseconds. + Weight::from_parts(16_943_157, 0) + // Standard Error: 1_904 + .saturating_add(Weight::from_parts(4_653_855, 0).saturating_mul(c.into())) } fn as_derivative() -> Weight { - // Minimum execution time: 6_799 nanoseconds. - Weight::from_ref_time(6_976_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_149_000 picoseconds. + Weight::from_parts(5_268_000, 0) } /// The range of component `c` is `[0, 1000]`. fn batch_all(c: u32, ) -> Weight { - // Minimum execution time: 14_630 nanoseconds. - Weight::from_ref_time(24_580_656 as u64) - // Standard Error: 2_202 - .saturating_add(Weight::from_ref_time(3_584_516 as u64).saturating_mul(c as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_976_000 picoseconds. + Weight::from_parts(16_448_433, 0) + // Standard Error: 1_834 + .saturating_add(Weight::from_parts(4_796_983, 0).saturating_mul(c.into())) } fn dispatch_as() -> Weight { - // Minimum execution time: 16_597 nanoseconds. - Weight::from_ref_time(16_950_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 9_102_000 picoseconds. + Weight::from_parts(9_353_000, 0) } /// The range of component `c` is `[0, 1000]`. fn force_batch(c: u32, ) -> Weight { - // Minimum execution time: 13_885 nanoseconds. - Weight::from_ref_time(20_147_978 as u64) - // Standard Error: 2_232 - .saturating_add(Weight::from_ref_time(3_516_969 as u64).saturating_mul(c as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_840_000 picoseconds. + Weight::from_parts(17_748_474, 0) + // Standard Error: 2_059 + .saturating_add(Weight::from_parts(4_630_079, 0).saturating_mul(c.into())) } } @@ -92,31 +112,46 @@ impl WeightInfo for SubstrateWeight { impl WeightInfo for () { /// The range of component `c` is `[0, 1000]`. fn batch(c: u32, ) -> Weight { - // Minimum execution time: 14_470 nanoseconds. - Weight::from_ref_time(17_443_346 as u64) - // Standard Error: 2_037 - .saturating_add(Weight::from_ref_time(3_510_555 as u64).saturating_mul(c as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_763_000 picoseconds. + Weight::from_parts(16_943_157, 0) + // Standard Error: 1_904 + .saturating_add(Weight::from_parts(4_653_855, 0).saturating_mul(c.into())) } fn as_derivative() -> Weight { - // Minimum execution time: 6_799 nanoseconds. - Weight::from_ref_time(6_976_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 5_149_000 picoseconds. + Weight::from_parts(5_268_000, 0) } /// The range of component `c` is `[0, 1000]`. fn batch_all(c: u32, ) -> Weight { - // Minimum execution time: 14_630 nanoseconds. - Weight::from_ref_time(24_580_656 as u64) - // Standard Error: 2_202 - .saturating_add(Weight::from_ref_time(3_584_516 as u64).saturating_mul(c as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_976_000 picoseconds. + Weight::from_parts(16_448_433, 0) + // Standard Error: 1_834 + .saturating_add(Weight::from_parts(4_796_983, 0).saturating_mul(c.into())) } fn dispatch_as() -> Weight { - // Minimum execution time: 16_597 nanoseconds. - Weight::from_ref_time(16_950_000 as u64) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 9_102_000 picoseconds. + Weight::from_parts(9_353_000, 0) } /// The range of component `c` is `[0, 1000]`. fn force_batch(c: u32, ) -> Weight { - // Minimum execution time: 13_885 nanoseconds. - Weight::from_ref_time(20_147_978 as u64) - // Standard Error: 2_232 - .saturating_add(Weight::from_ref_time(3_516_969 as u64).saturating_mul(c as u64)) + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_840_000 picoseconds. + Weight::from_parts(17_748_474, 0) + // Standard Error: 2_059 + .saturating_add(Weight::from_parts(4_630_079, 0).saturating_mul(c.into())) } } diff --git a/frame/vesting/Cargo.toml b/frame/vesting/Cargo.toml index c18d1f45e038..c28fb0a55144 100644 --- a/frame/vesting/Cargo.toml +++ b/frame/vesting/Cargo.toml @@ -13,21 +13,21 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", ] } log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/vesting/README.md b/frame/vesting/README.md index b19a60c5b682..1f3744d63592 100644 --- a/frame/vesting/README.md +++ b/frame/vesting/README.md @@ -1,7 +1,7 @@ # Vesting Module -- [`vesting::Config`](https://docs.rs/pallet-vesting/latest/pallet_vesting/trait.Config.html) -- [`Call`](https://docs.rs/pallet-vesting/latest/pallet_vesting/enum.Call.html) +- [`Config`](https://docs.rs/pallet-vesting/latest/pallet_vesting/pallet/trait.Config.html) +- [`Call`](https://docs.rs/pallet-vesting/latest/pallet_vesting/pallet/enum.Call.html) ## Overview diff --git a/frame/vesting/src/benchmarking.rs b/frame/vesting/src/benchmarking.rs index dde5fe3ac756..4af48f5d368d 100644 --- a/frame/vesting/src/benchmarking.rs +++ b/frame/vesting/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,9 +19,9 @@ #![cfg(feature = "runtime-benchmarks")] -use frame_benchmarking::{account, benchmarks, whitelisted_caller}; +use frame_benchmarking::v1::{account, benchmarks, whitelisted_caller}; use frame_support::assert_ok; -use frame_system::{Pallet as System, RawOrigin}; +use frame_system::{pallet_prelude::BlockNumberFor, Pallet as System, RawOrigin}; use sp_runtime::traits::{Bounded, CheckedDiv, CheckedMul}; use super::*; @@ -55,7 +55,7 @@ fn add_vesting_schedules( let source_lookup = T::Lookup::unlookup(source.clone()); T::Currency::make_free_balance_be(&source, BalanceOf::::max_value()); - System::::set_block_number(T::BlockNumber::zero()); + System::::set_block_number(BlockNumberFor::::zero()); let mut total_locked: BalanceOf = Zero::zero(); for _ in 0..n { @@ -88,7 +88,7 @@ benchmarks! { let expected_balance = add_vesting_schedules::(caller_lookup, s)?; // At block zero, everything is vested. - assert_eq!(System::::block_number(), T::BlockNumber::zero()); + assert_eq!(System::::block_number(), BlockNumberFor::::zero()); assert_eq!( Vesting::::vesting_balance(&caller), Some(expected_balance), @@ -139,11 +139,12 @@ benchmarks! { let other: T::AccountId = account("other", 0, SEED); let other_lookup = T::Lookup::unlookup(other.clone()); + T::Currency::make_free_balance_be(&other, T::Currency::minimum_balance()); add_locks::(&other, l as u8); let expected_balance = add_vesting_schedules::(other_lookup.clone(), s)?; // At block zero, everything is vested. - assert_eq!(System::::block_number(), T::BlockNumber::zero()); + assert_eq!(System::::block_number(), BlockNumberFor::::zero()); assert_eq!( Vesting::::vesting_balance(&other), Some(expected_balance), @@ -168,6 +169,7 @@ benchmarks! { let other: T::AccountId = account("other", 0, SEED); let other_lookup = T::Lookup::unlookup(other.clone()); + T::Currency::make_free_balance_be(&other, T::Currency::minimum_balance()); add_locks::(&other, l as u8); add_vesting_schedules::(other_lookup.clone(), s)?; // At block 21 everything is unlocked. @@ -200,8 +202,10 @@ benchmarks! { let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); // Give target existing locks + T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); add_locks::(&target, l as u8); // Add one vesting schedules. + let orig_balance = T::Currency::free_balance(&target); let mut expected_balance = add_vesting_schedules::(target_lookup.clone(), s)?; let transfer_amount = T::MinVestedTransfer::get(); @@ -216,7 +220,7 @@ benchmarks! { }: _(RawOrigin::Signed(caller), target_lookup, vesting_schedule) verify { assert_eq!( - expected_balance, + orig_balance + expected_balance, T::Currency::free_balance(&target), "Transfer didn't happen", ); @@ -238,8 +242,10 @@ benchmarks! { let target: T::AccountId = account("target", 0, SEED); let target_lookup = T::Lookup::unlookup(target.clone()); // Give target existing locks + T::Currency::make_free_balance_be(&target, T::Currency::minimum_balance()); add_locks::(&target, l as u8); // Add one less than max vesting schedules + let orig_balance = T::Currency::free_balance(&target); let mut expected_balance = add_vesting_schedules::(target_lookup.clone(), s)?; let transfer_amount = T::MinVestedTransfer::get(); @@ -254,7 +260,7 @@ benchmarks! { }: _(RawOrigin::Root, source_lookup, target_lookup, vesting_schedule) verify { assert_eq!( - expected_balance, + orig_balance + expected_balance, T::Currency::free_balance(&target), "Transfer didn't happen", ); @@ -272,12 +278,13 @@ benchmarks! { let caller: T::AccountId = account("caller", 0, SEED); let caller_lookup = T::Lookup::unlookup(caller.clone()); // Give target existing locks. + T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance()); add_locks::(&caller, l as u8); // Add max vesting schedules. let expected_balance = add_vesting_schedules::(caller_lookup, s)?; // Schedules are not vesting at block 0. - assert_eq!(System::::block_number(), T::BlockNumber::zero()); + assert_eq!(System::::block_number(), BlockNumberFor::::zero()); assert_eq!( Vesting::::vesting_balance(&caller), Some(expected_balance), @@ -322,6 +329,7 @@ benchmarks! { let caller: T::AccountId = account("caller", 0, SEED); let caller_lookup = T::Lookup::unlookup(caller.clone()); // Give target other locks. + T::Currency::make_free_balance_be(&caller, T::Currency::minimum_balance()); add_locks::(&caller, l as u8); // Add max vesting schedules. let total_transferred = add_vesting_schedules::(caller_lookup, s)?; diff --git a/frame/vesting/src/lib.rs b/frame/vesting/src/lib.rs index 3439608af3ce..eb829121e979 100644 --- a/frame/vesting/src/lib.rs +++ b/frame/vesting/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -67,6 +67,7 @@ use frame_support::{ }, weights::Weight, }; +use frame_system::pallet_prelude::BlockNumberFor; use scale_info::TypeInfo; use sp_runtime::{ traits::{ @@ -127,8 +128,8 @@ impl VestingAction { /// Pick the schedules that this action dictates should continue vesting undisturbed. fn pick_schedules( &self, - schedules: Vec, T::BlockNumber>>, - ) -> impl Iterator, T::BlockNumber>> + '_ { + schedules: Vec, BlockNumberFor>>, + ) -> impl Iterator, BlockNumberFor>> + '_ { schedules.into_iter().enumerate().filter_map(move |(index, schedule)| { if self.should_remove(index) { None @@ -162,7 +163,7 @@ pub mod pallet { type Currency: LockableCurrency; /// Convert the block number into a balance. - type BlockNumberToBalance: Convert>; + type BlockNumberToBalance: Convert, BalanceOf>; /// The minimum amount transferred to call `vested_transfer`. #[pallet::constant] @@ -201,7 +202,7 @@ pub mod pallet { _, Blake2_128Concat, T::AccountId, - BoundedVec, T::BlockNumber>, MaxVestingSchedulesGet>, + BoundedVec, BlockNumberFor>, MaxVestingSchedulesGet>, >; /// Storage version of the pallet. @@ -211,23 +212,16 @@ pub mod pallet { pub(crate) type StorageVersion = StorageValue<_, Releases, ValueQuery>; #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { - pub vesting: Vec<(T::AccountId, T::BlockNumber, T::BlockNumber, BalanceOf)>, - } - - #[cfg(feature = "std")] - impl Default for GenesisConfig { - fn default() -> Self { - GenesisConfig { vesting: Default::default() } - } + pub vesting: Vec<(T::AccountId, BlockNumberFor, BlockNumberFor, BalanceOf)>, } #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { + impl BuildGenesisConfig for GenesisConfig { fn build(&self) { use sp_runtime::traits::Saturating; @@ -297,12 +291,8 @@ pub mod pallet { /// /// Emits either `VestingCompleted` or `VestingUpdated`. /// - /// # + /// ## Complexity /// - `O(1)`. - /// - DbWeight: 2 Reads, 2 Writes - /// - Reads: Vesting Storage, Balances Locks, [Sender Account] - /// - Writes: Vesting Storage, Balances Locks, [Sender Account] - /// # #[pallet::call_index(0)] #[pallet::weight(T::WeightInfo::vest_locked(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) .max(T::WeightInfo::vest_unlocked(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES)) @@ -321,12 +311,8 @@ pub mod pallet { /// /// Emits either `VestingCompleted` or `VestingUpdated`. /// - /// # + /// ## Complexity /// - `O(1)`. - /// - DbWeight: 3 Reads, 3 Writes - /// - Reads: Vesting Storage, Balances Locks, Target Account - /// - Writes: Vesting Storage, Balances Locks, Target Account - /// # #[pallet::call_index(1)] #[pallet::weight(T::WeightInfo::vest_other_locked(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) .max(T::WeightInfo::vest_other_unlocked(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES)) @@ -348,12 +334,8 @@ pub mod pallet { /// /// NOTE: This will unlock all schedules through the current block. /// - /// # + /// ## Complexity /// - `O(1)`. - /// - DbWeight: 3 Reads, 3 Writes - /// - Reads: Vesting Storage, Balances Locks, Target Account, [Sender Account] - /// - Writes: Vesting Storage, Balances Locks, Target Account, [Sender Account] - /// # #[pallet::call_index(2)] #[pallet::weight( T::WeightInfo::vested_transfer(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) @@ -361,7 +343,7 @@ pub mod pallet { pub fn vested_transfer( origin: OriginFor, target: AccountIdLookupOf, - schedule: VestingInfo, T::BlockNumber>, + schedule: VestingInfo, BlockNumberFor>, ) -> DispatchResult { let transactor = ensure_signed(origin)?; let transactor = ::unlookup(transactor); @@ -380,12 +362,8 @@ pub mod pallet { /// /// NOTE: This will unlock all schedules through the current block. /// - /// # + /// ## Complexity /// - `O(1)`. - /// - DbWeight: 4 Reads, 4 Writes - /// - Reads: Vesting Storage, Balances Locks, Target Account, Source Account - /// - Writes: Vesting Storage, Balances Locks, Target Account, Source Account - /// # #[pallet::call_index(3)] #[pallet::weight( T::WeightInfo::force_vested_transfer(MaxLocksOf::::get(), T::MAX_VESTING_SCHEDULES) @@ -394,7 +372,7 @@ pub mod pallet { origin: OriginFor, source: AccountIdLookupOf, target: AccountIdLookupOf, - schedule: VestingInfo, T::BlockNumber>, + schedule: VestingInfo, BlockNumberFor>, ) -> DispatchResult { ensure_root(origin)?; Self::do_vested_transfer(source, target, schedule) @@ -456,10 +434,10 @@ impl Pallet { // Create a new `VestingInfo`, based off of two other `VestingInfo`s. // NOTE: We assume both schedules have had funds unlocked up through the current block. fn merge_vesting_info( - now: T::BlockNumber, - schedule1: VestingInfo, T::BlockNumber>, - schedule2: VestingInfo, T::BlockNumber>, - ) -> Option, T::BlockNumber>> { + now: BlockNumberFor, + schedule1: VestingInfo, BlockNumberFor>, + schedule2: VestingInfo, BlockNumberFor>, + ) -> Option, BlockNumberFor>> { let schedule1_ending_block = schedule1.ending_block_as_balance::(); let schedule2_ending_block = schedule2.ending_block_as_balance::(); let now_as_balance = T::BlockNumberToBalance::convert(now); @@ -506,7 +484,7 @@ impl Pallet { fn do_vested_transfer( source: AccountIdLookupOf, target: AccountIdLookupOf, - schedule: VestingInfo, T::BlockNumber>, + schedule: VestingInfo, BlockNumberFor>, ) -> DispatchResult { // Validate user inputs. ensure!(schedule.locked() >= T::MinVestedTransfer::get(), Error::::AmountLow); @@ -554,9 +532,9 @@ impl Pallet { /// /// NOTE: the amount locked does not include any schedules that are filtered out via `action`. fn report_schedule_updates( - schedules: Vec, T::BlockNumber>>, + schedules: Vec, BlockNumberFor>>, action: VestingAction, - ) -> (Vec, T::BlockNumber>>, BalanceOf) { + ) -> (Vec, BlockNumberFor>>, BalanceOf) { let now = >::block_number(); let mut total_locked_now: BalanceOf = Zero::zero(); @@ -593,10 +571,10 @@ impl Pallet { /// Write an accounts updated vesting schedules to storage. fn write_vesting( who: &T::AccountId, - schedules: Vec, T::BlockNumber>>, + schedules: Vec, BlockNumberFor>>, ) -> Result<(), DispatchError> { let schedules: BoundedVec< - VestingInfo, T::BlockNumber>, + VestingInfo, BlockNumberFor>, MaxVestingSchedulesGet, > = schedules.try_into().map_err(|_| Error::::AtMaxVestingSchedules)?; @@ -625,9 +603,9 @@ impl Pallet { /// Execute a `VestingAction` against the given `schedules`. Returns the updated schedules /// and locked amount. fn exec_action( - schedules: Vec, T::BlockNumber>>, + schedules: Vec, BlockNumberFor>>, action: VestingAction, - ) -> Result<(Vec, T::BlockNumber>>, BalanceOf), DispatchError> { + ) -> Result<(Vec, BlockNumberFor>>, BalanceOf), DispatchError> { let (schedules, locked_now) = match action { VestingAction::Merge { index1: idx1, index2: idx2 } => { // The schedule index is based off of the schedule ordering prior to filtering out @@ -672,7 +650,7 @@ where BalanceOf: MaybeSerializeDeserialize + Debug, { type Currency = T::Currency; - type Moment = T::BlockNumber; + type Moment = BlockNumberFor; /// Get the amount that is currently being vested and cannot be transferred out of this account. fn vesting_balance(who: &T::AccountId) -> Option> { @@ -703,7 +681,7 @@ where who: &T::AccountId, locked: BalanceOf, per_block: BalanceOf, - starting_block: T::BlockNumber, + starting_block: BlockNumberFor, ) -> DispatchResult { if locked.is_zero() { return Ok(()) @@ -736,7 +714,7 @@ where who: &T::AccountId, locked: BalanceOf, per_block: BalanceOf, - starting_block: T::BlockNumber, + starting_block: BlockNumberFor, ) -> DispatchResult { // Check for `per_block` or `locked` of 0. if !VestingInfo::new(locked, per_block, starting_block).is_valid() { diff --git a/frame/vesting/src/migrations.rs b/frame/vesting/src/migrations.rs index 15668425b4b2..cac3c90b403a 100644 --- a/frame/vesting/src/migrations.rs +++ b/frame/vesting/src/migrations.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,12 +40,12 @@ pub mod v1 { pub fn migrate() -> Weight { let mut reads_writes = 0; - Vesting::::translate::, T::BlockNumber>, _>( + Vesting::::translate::, BlockNumberFor>, _>( |_key, vesting_info| { reads_writes += 1; let v: Option< BoundedVec< - VestingInfo, T::BlockNumber>, + VestingInfo, BlockNumberFor>, MaxVestingSchedulesGet, >, > = vec![vesting_info].try_into().ok(); diff --git a/frame/vesting/src/mock.rs b/frame/vesting/src/mock.rs index da9490bea66c..fe1779475a69 100644 --- a/frame/vesting/src/mock.rs +++ b/frame/vesting/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,27 +17,23 @@ use frame_support::{ parameter_types, - traits::{ConstU32, ConstU64, GenesisBuild, WithdrawReasons}, + traits::{ConstU32, ConstU64, WithdrawReasons}, }; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, Identity, IdentityLookup}, + BuildStorage, }; use super::*; use crate as pallet_vesting; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; frame_support::construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { - System: frame_system::{Pallet, Call, Config, Storage, Event}, + System: frame_system::{Pallet, Call, Config, Storage, Event}, Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, Vesting: pallet_vesting::{Pallet, Call, Storage, Event, Config}, } @@ -49,15 +45,14 @@ impl frame_system::Config for Test { type BaseCallFilter = frame_support::traits::Everything; type BlockHashCount = ConstU64<250>; type BlockLength = (); - type BlockNumber = u64; type BlockWeights = (); type RuntimeCall = RuntimeCall; type DbWeight = (); type RuntimeEvent = RuntimeEvent; type Hash = H256; type Hashing = BlakeTwo256; - type Header = Header; - type Index = u64; + type Block = Block; + type Nonce = u64; type Lookup = IdentityLookup; type OnKilledAccount = (); type OnNewAccount = (); @@ -80,12 +75,16 @@ impl pallet_balances::Config for Test { type MaxReserves = (); type ReserveIdentifier = [u8; 8]; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } parameter_types! { pub const MinVestedTransfer: u64 = 256 * 2; pub UnvestedFundsAllowedWithdrawReasons: WithdrawReasons = WithdrawReasons::except(WithdrawReasons::TRANSFER | WithdrawReasons::RESERVE); - pub static ExistentialDeposit: u64 = 0; + pub static ExistentialDeposit: u64 = 1; } impl Config for Test { type BlockNumberToBalance = Identity; @@ -121,7 +120,7 @@ impl ExtBuilder { pub fn build(self) -> sp_io::TestExternalities { EXISTENTIAL_DEPOSIT.with(|v| *v.borrow_mut() = self.existential_deposit); - let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut t = frame_system::GenesisConfig::::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: vec![ (1, 10 * self.existential_deposit), diff --git a/frame/vesting/src/tests.rs b/frame/vesting/src/tests.rs index cbc2e09c8319..46afe895f6fc 100644 --- a/frame/vesting/src/tests.rs +++ b/frame/vesting/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,10 @@ use frame_support::{assert_noop, assert_ok, assert_storage_noop, dispatch::EncodeLike}; use frame_system::RawOrigin; -use sp_runtime::traits::{BadOrigin, Identity}; +use sp_runtime::{ + traits::{BadOrigin, Identity}, + TokenError, +}; use super::{Vesting as VestingStorage, *}; use crate::mock::{Balances, ExtBuilder, System, Test, Vesting}; @@ -180,10 +183,8 @@ fn unvested_balance_should_not_transfer() { assert_eq!(user1_free_balance, 100); // Account 1 has free balance // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Vesting::vesting_balance(&1), Some(45)); - assert_noop!( - Balances::transfer(Some(1).into(), 2, 56), - pallet_balances::Error::::LiquidityRestrictions, - ); // Account 1 cannot send more than vested amount + // Account 1 cannot send more than vested amount... + assert_noop!(Balances::transfer_allow_death(Some(1).into(), 2, 56), TokenError::Frozen); }); } @@ -195,7 +196,7 @@ fn vested_balance_should_transfer() { // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Vesting::vesting_balance(&1), Some(45)); assert_ok!(Vesting::vest(Some(1).into())); - assert_ok!(Balances::transfer(Some(1).into(), 2, 55)); + assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 55)); }); } @@ -213,7 +214,7 @@ fn vested_balance_should_transfer_with_multi_sched() { // Account 1 has only 256 units unlocking at block 1 (plus 1280 already fee). assert_eq!(Vesting::vesting_balance(&1), Some(2304)); assert_ok!(Vesting::vest(Some(1).into())); - assert_ok!(Balances::transfer(Some(1).into(), 2, 1536)); + assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 1536)); }); } @@ -233,7 +234,7 @@ fn vested_balance_should_transfer_using_vest_other() { // Account 1 has only 5 units vested at block 1 (plus 50 unvested) assert_eq!(Vesting::vesting_balance(&1), Some(45)); assert_ok!(Vesting::vest_other(Some(2).into(), 1)); - assert_ok!(Balances::transfer(Some(1).into(), 2, 55)); + assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 55)); }); } @@ -251,7 +252,7 @@ fn vested_balance_should_transfer_using_vest_other_with_multi_sched() { // Account 1 has only 256 units unlocking at block 1 (plus 1280 already free). assert_eq!(Vesting::vesting_balance(&1), Some(2304)); assert_ok!(Vesting::vest_other(Some(2).into(), 1)); - assert_ok!(Balances::transfer(Some(1).into(), 2, 1536)); + assert_ok!(Balances::transfer_allow_death(Some(1).into(), 2, 1536)); }); } @@ -266,8 +267,8 @@ fn non_vested_cannot_vest_other() { #[test] fn extra_balance_should_transfer() { ExtBuilder::default().existential_deposit(10).build().execute_with(|| { - assert_ok!(Balances::transfer(Some(3).into(), 1, 100)); - assert_ok!(Balances::transfer(Some(3).into(), 2, 100)); + assert_ok!(Balances::transfer_allow_death(Some(3).into(), 1, 100)); + assert_ok!(Balances::transfer_allow_death(Some(3).into(), 2, 100)); let user1_free_balance = Balances::free_balance(&1); assert_eq!(user1_free_balance, 200); // Account 1 has 100 more free balance than normal @@ -278,12 +279,13 @@ fn extra_balance_should_transfer() { // Account 1 has only 5 units vested at block 1 (plus 150 unvested) assert_eq!(Vesting::vesting_balance(&1), Some(45)); assert_ok!(Vesting::vest(Some(1).into())); - assert_ok!(Balances::transfer(Some(1).into(), 3, 155)); // Account 1 can send extra units gained + assert_ok!(Balances::transfer_allow_death(Some(1).into(), 3, 155)); // Account 1 can send extra units gained // Account 2 has no units vested at block 1, but gained 100 assert_eq!(Vesting::vesting_balance(&2), Some(200)); assert_ok!(Vesting::vest(Some(2).into())); - assert_ok!(Balances::transfer(Some(2).into(), 3, 100)); // Account 2 can send extra units gained + assert_ok!(Balances::transfer_allow_death(Some(2).into(), 3, 100)); // Account 2 can send extra + // units gained }); } @@ -305,7 +307,7 @@ fn liquid_funds_should_transfer_with_delayed_vesting() { assert_eq!(Vesting::vesting(&12).unwrap(), vec![user12_vesting_schedule]); // Account 12 can still send liquid funds - assert_ok!(Balances::transfer(Some(12).into(), 3, 256 * 5)); + assert_ok!(Balances::transfer_allow_death(Some(12).into(), 3, 256 * 5)); }); } @@ -1144,14 +1146,11 @@ fn vested_transfer_less_than_existential_deposit_fails() { ); // vested_transfer fails. - assert_noop!( - Vesting::vested_transfer(Some(3).into(), 99, sched), - pallet_balances::Error::::ExistentialDeposit, - ); + assert_noop!(Vesting::vested_transfer(Some(3).into(), 99, sched), TokenError::BelowMinimum,); // force_vested_transfer fails. assert_noop!( Vesting::force_vested_transfer(RawOrigin::Root.into(), 3, 99, sched), - pallet_balances::Error::::ExistentialDeposit, + TokenError::BelowMinimum, ); }); } diff --git a/frame/vesting/src/vesting_info.rs b/frame/vesting/src/vesting_info.rs index 9069b6948276..5d5ae31fc324 100644 --- a/frame/vesting/src/vesting_info.rs +++ b/frame/vesting/src/vesting_info.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/frame/vesting/src/weights.rs b/frame/vesting/src/weights.rs index 546244541471..17bde8885777 100644 --- a/frame/vesting/src/weights.rs +++ b/frame/vesting/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,9 @@ //! Autogenerated weights for pallet_vesting //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-11-07, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm2`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: @@ -30,6 +31,9 @@ // --steps=50 // --repeat=20 // --pallet=pallet_vesting +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled @@ -41,9 +45,10 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_vesting. pub trait WeightInfo { @@ -60,244 +65,368 @@ pub trait WeightInfo { /// Weights for pallet_vesting using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Vesting Vesting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Vesting Vesting (r:1 w:1) + /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[1, 28]`. fn vest_locked(l: u32, s: u32, ) -> Weight { - // Minimum execution time: 45_113 nanoseconds. - Weight::from_ref_time(44_114_539 as u64) - // Standard Error: 958 - .saturating_add(Weight::from_ref_time(56_239 as u64).saturating_mul(l as u64)) - // Standard Error: 1_704 - .saturating_add(Weight::from_ref_time(64_926 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `381 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `4764` + // Minimum execution time: 35_336_000 picoseconds. + Weight::from_parts(34_290_169, 4764) + // Standard Error: 1_381 + .saturating_add(Weight::from_parts(76_354, 0).saturating_mul(l.into())) + // Standard Error: 2_457 + .saturating_add(Weight::from_parts(81_362, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Vesting Vesting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Vesting Vesting (r:1 w:1) + /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[1, 28]`. fn vest_unlocked(l: u32, s: u32, ) -> Weight { - // Minimum execution time: 43_918 nanoseconds. - Weight::from_ref_time(43_452_573 as u64) - // Standard Error: 984 - .saturating_add(Weight::from_ref_time(50_162 as u64).saturating_mul(l as u64)) - // Standard Error: 1_752 - .saturating_add(Weight::from_ref_time(42_080 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(2 as u64)) - .saturating_add(T::DbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `381 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `4764` + // Minimum execution time: 38_540_000 picoseconds. + Weight::from_parts(38_893_820, 4764) + // Standard Error: 1_710 + .saturating_add(Weight::from_parts(62_106, 0).saturating_mul(l.into())) + // Standard Error: 3_043 + .saturating_add(Weight::from_parts(41_966, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Vesting Vesting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Vesting Vesting (r:1 w:1) + /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[1, 28]`. fn vest_other_locked(l: u32, s: u32, ) -> Weight { - // Minimum execution time: 43_603 nanoseconds. - Weight::from_ref_time(42_696_097 as u64) - // Standard Error: 996 - .saturating_add(Weight::from_ref_time(65_316 as u64).saturating_mul(l as u64)) - // Standard Error: 1_772 - .saturating_add(Weight::from_ref_time(65_862 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `484 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `4764` + // Minimum execution time: 37_529_000 picoseconds. + Weight::from_parts(36_781_151, 4764) + // Standard Error: 1_490 + .saturating_add(Weight::from_parts(76_322, 0).saturating_mul(l.into())) + // Standard Error: 2_652 + .saturating_add(Weight::from_parts(76_914, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - // Storage: Vesting Vesting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Vesting Vesting (r:1 w:1) + /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[1, 28]`. fn vest_other_unlocked(l: u32, s: u32, ) -> Weight { - // Minimum execution time: 43_099 nanoseconds. - Weight::from_ref_time(42_937_914 as u64) - // Standard Error: 884 - .saturating_add(Weight::from_ref_time(52_079 as u64).saturating_mul(l as u64)) - // Standard Error: 1_573 - .saturating_add(Weight::from_ref_time(36_274 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `484 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `4764` + // Minimum execution time: 41_217_000 picoseconds. + Weight::from_parts(40_942_515, 4764) + // Standard Error: 2_098 + .saturating_add(Weight::from_parts(65_213, 0).saturating_mul(l.into())) + // Standard Error: 3_733 + .saturating_add(Weight::from_parts(63_326, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - // Storage: Vesting Vesting (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Vesting Vesting (r:1 w:1) + /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[0, 27]`. fn vested_transfer(l: u32, s: u32, ) -> Weight { - // Minimum execution time: 59_023 nanoseconds. - Weight::from_ref_time(59_606_862 as u64) - // Standard Error: 2_078 - .saturating_add(Weight::from_ref_time(55_335 as u64).saturating_mul(l as u64)) - // Standard Error: 3_698 - .saturating_add(Weight::from_ref_time(26_743 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `555 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `4764` + // Minimum execution time: 76_396_000 picoseconds. + Weight::from_parts(77_085_336, 4764) + // Standard Error: 2_795 + .saturating_add(Weight::from_parts(88_995, 0).saturating_mul(l.into())) + // Standard Error: 4_974 + .saturating_add(Weight::from_parts(135_384, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - // Storage: Vesting Vesting (r:1 w:1) - // Storage: System Account (r:2 w:2) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Vesting Vesting (r:1 w:1) + /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[0, 27]`. fn force_vested_transfer(l: u32, s: u32, ) -> Weight { - // Minimum execution time: 58_249 nanoseconds. - Weight::from_ref_time(59_025_976 as u64) - // Standard Error: 2_078 - .saturating_add(Weight::from_ref_time(55_736 as u64).saturating_mul(l as u64)) - // Standard Error: 3_697 - .saturating_add(Weight::from_ref_time(24_903 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(4 as u64)) - .saturating_add(T::DbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `658 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `6196` + // Minimum execution time: 77_312_000 picoseconds. + Weight::from_parts(79_600_900, 6196) + // Standard Error: 3_232 + .saturating_add(Weight::from_parts(78_018, 0).saturating_mul(l.into())) + // Standard Error: 5_750 + .saturating_add(Weight::from_parts(100_848, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(4_u64)) } - // Storage: Vesting Vesting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Vesting Vesting (r:1 w:1) + /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[2, 28]`. fn not_unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { - // Minimum execution time: 45_279 nanoseconds. - Weight::from_ref_time(44_197_440 as u64) - // Standard Error: 946 - .saturating_add(Weight::from_ref_time(62_308 as u64).saturating_mul(l as u64)) - // Standard Error: 1_747 - .saturating_add(Weight::from_ref_time(64_473 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `482 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `4764` + // Minimum execution time: 38_769_000 picoseconds. + Weight::from_parts(37_752_437, 4764) + // Standard Error: 1_415 + .saturating_add(Weight::from_parts(78_398, 0).saturating_mul(l.into())) + // Standard Error: 2_614 + .saturating_add(Weight::from_parts(78_922, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } - // Storage: Vesting Vesting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Vesting Vesting (r:1 w:1) + /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[2, 28]`. fn unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { - // Minimum execution time: 44_925 nanoseconds. - Weight::from_ref_time(44_219_676 as u64) - // Standard Error: 889 - .saturating_add(Weight::from_ref_time(60_311 as u64).saturating_mul(l as u64)) - // Standard Error: 1_641 - .saturating_add(Weight::from_ref_time(63_095 as u64).saturating_mul(s as u64)) - .saturating_add(T::DbWeight::get().reads(3 as u64)) - .saturating_add(T::DbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `482 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `4764` + // Minimum execution time: 43_021_000 picoseconds. + Weight::from_parts(42_182_858, 4764) + // Standard Error: 1_747 + .saturating_add(Weight::from_parts(83_938, 0).saturating_mul(l.into())) + // Standard Error: 3_227 + .saturating_add(Weight::from_parts(84_652, 0).saturating_mul(s.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Vesting Vesting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Vesting Vesting (r:1 w:1) + /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[1, 28]`. fn vest_locked(l: u32, s: u32, ) -> Weight { - // Minimum execution time: 45_113 nanoseconds. - Weight::from_ref_time(44_114_539 as u64) - // Standard Error: 958 - .saturating_add(Weight::from_ref_time(56_239 as u64).saturating_mul(l as u64)) - // Standard Error: 1_704 - .saturating_add(Weight::from_ref_time(64_926 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `381 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `4764` + // Minimum execution time: 35_336_000 picoseconds. + Weight::from_parts(34_290_169, 4764) + // Standard Error: 1_381 + .saturating_add(Weight::from_parts(76_354, 0).saturating_mul(l.into())) + // Standard Error: 2_457 + .saturating_add(Weight::from_parts(81_362, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Vesting Vesting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Vesting Vesting (r:1 w:1) + /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[1, 28]`. fn vest_unlocked(l: u32, s: u32, ) -> Weight { - // Minimum execution time: 43_918 nanoseconds. - Weight::from_ref_time(43_452_573 as u64) - // Standard Error: 984 - .saturating_add(Weight::from_ref_time(50_162 as u64).saturating_mul(l as u64)) - // Standard Error: 1_752 - .saturating_add(Weight::from_ref_time(42_080 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(2 as u64)) - .saturating_add(RocksDbWeight::get().writes(2 as u64)) + // Proof Size summary in bytes: + // Measured: `381 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `4764` + // Minimum execution time: 38_540_000 picoseconds. + Weight::from_parts(38_893_820, 4764) + // Standard Error: 1_710 + .saturating_add(Weight::from_parts(62_106, 0).saturating_mul(l.into())) + // Standard Error: 3_043 + .saturating_add(Weight::from_parts(41_966, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Vesting Vesting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Vesting Vesting (r:1 w:1) + /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[1, 28]`. fn vest_other_locked(l: u32, s: u32, ) -> Weight { - // Minimum execution time: 43_603 nanoseconds. - Weight::from_ref_time(42_696_097 as u64) - // Standard Error: 996 - .saturating_add(Weight::from_ref_time(65_316 as u64).saturating_mul(l as u64)) - // Standard Error: 1_772 - .saturating_add(Weight::from_ref_time(65_862 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `484 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `4764` + // Minimum execution time: 37_529_000 picoseconds. + Weight::from_parts(36_781_151, 4764) + // Standard Error: 1_490 + .saturating_add(Weight::from_parts(76_322, 0).saturating_mul(l.into())) + // Standard Error: 2_652 + .saturating_add(Weight::from_parts(76_914, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } - // Storage: Vesting Vesting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Vesting Vesting (r:1 w:1) + /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[1, 28]`. fn vest_other_unlocked(l: u32, s: u32, ) -> Weight { - // Minimum execution time: 43_099 nanoseconds. - Weight::from_ref_time(42_937_914 as u64) - // Standard Error: 884 - .saturating_add(Weight::from_ref_time(52_079 as u64).saturating_mul(l as u64)) - // Standard Error: 1_573 - .saturating_add(Weight::from_ref_time(36_274 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `484 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `4764` + // Minimum execution time: 41_217_000 picoseconds. + Weight::from_parts(40_942_515, 4764) + // Standard Error: 2_098 + .saturating_add(Weight::from_parts(65_213, 0).saturating_mul(l.into())) + // Standard Error: 3_733 + .saturating_add(Weight::from_parts(63_326, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } - // Storage: Vesting Vesting (r:1 w:1) - // Storage: System Account (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Vesting Vesting (r:1 w:1) + /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[0, 27]`. fn vested_transfer(l: u32, s: u32, ) -> Weight { - // Minimum execution time: 59_023 nanoseconds. - Weight::from_ref_time(59_606_862 as u64) - // Standard Error: 2_078 - .saturating_add(Weight::from_ref_time(55_335 as u64).saturating_mul(l as u64)) - // Standard Error: 3_698 - .saturating_add(Weight::from_ref_time(26_743 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `555 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `4764` + // Minimum execution time: 76_396_000 picoseconds. + Weight::from_parts(77_085_336, 4764) + // Standard Error: 2_795 + .saturating_add(Weight::from_parts(88_995, 0).saturating_mul(l.into())) + // Standard Error: 4_974 + .saturating_add(Weight::from_parts(135_384, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } - // Storage: Vesting Vesting (r:1 w:1) - // Storage: System Account (r:2 w:2) - // Storage: Balances Locks (r:1 w:1) + /// Storage: Vesting Vesting (r:1 w:1) + /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) + /// Storage: System Account (r:2 w:2) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[0, 27]`. fn force_vested_transfer(l: u32, s: u32, ) -> Weight { - // Minimum execution time: 58_249 nanoseconds. - Weight::from_ref_time(59_025_976 as u64) - // Standard Error: 2_078 - .saturating_add(Weight::from_ref_time(55_736 as u64).saturating_mul(l as u64)) - // Standard Error: 3_697 - .saturating_add(Weight::from_ref_time(24_903 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(4 as u64)) - .saturating_add(RocksDbWeight::get().writes(4 as u64)) + // Proof Size summary in bytes: + // Measured: `658 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `6196` + // Minimum execution time: 77_312_000 picoseconds. + Weight::from_parts(79_600_900, 6196) + // Standard Error: 3_232 + .saturating_add(Weight::from_parts(78_018, 0).saturating_mul(l.into())) + // Standard Error: 5_750 + .saturating_add(Weight::from_parts(100_848, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(4_u64)) } - // Storage: Vesting Vesting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Vesting Vesting (r:1 w:1) + /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[2, 28]`. fn not_unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { - // Minimum execution time: 45_279 nanoseconds. - Weight::from_ref_time(44_197_440 as u64) - // Standard Error: 946 - .saturating_add(Weight::from_ref_time(62_308 as u64).saturating_mul(l as u64)) - // Standard Error: 1_747 - .saturating_add(Weight::from_ref_time(64_473 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `482 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `4764` + // Minimum execution time: 38_769_000 picoseconds. + Weight::from_parts(37_752_437, 4764) + // Standard Error: 1_415 + .saturating_add(Weight::from_parts(78_398, 0).saturating_mul(l.into())) + // Standard Error: 2_614 + .saturating_add(Weight::from_parts(78_922, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } - // Storage: Vesting Vesting (r:1 w:1) - // Storage: Balances Locks (r:1 w:1) - // Storage: System Account (r:1 w:1) + /// Storage: Vesting Vesting (r:1 w:1) + /// Proof: Vesting Vesting (max_values: None, max_size: Some(1057), added: 3532, mode: MaxEncodedLen) + /// Storage: Balances Locks (r:1 w:1) + /// Proof: Balances Locks (max_values: None, max_size: Some(1299), added: 3774, mode: MaxEncodedLen) + /// Storage: Balances Freezes (r:1 w:0) + /// Proof: Balances Freezes (max_values: None, max_size: Some(49), added: 2524, mode: MaxEncodedLen) + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) /// The range of component `l` is `[0, 49]`. /// The range of component `s` is `[2, 28]`. fn unlocking_merge_schedules(l: u32, s: u32, ) -> Weight { - // Minimum execution time: 44_925 nanoseconds. - Weight::from_ref_time(44_219_676 as u64) - // Standard Error: 889 - .saturating_add(Weight::from_ref_time(60_311 as u64).saturating_mul(l as u64)) - // Standard Error: 1_641 - .saturating_add(Weight::from_ref_time(63_095 as u64).saturating_mul(s as u64)) - .saturating_add(RocksDbWeight::get().reads(3 as u64)) - .saturating_add(RocksDbWeight::get().writes(3 as u64)) + // Proof Size summary in bytes: + // Measured: `482 + l * (25 ±0) + s * (36 ±0)` + // Estimated: `4764` + // Minimum execution time: 43_021_000 picoseconds. + Weight::from_parts(42_182_858, 4764) + // Standard Error: 1_747 + .saturating_add(Weight::from_parts(83_938, 0).saturating_mul(l.into())) + // Standard Error: 3_227 + .saturating_add(Weight::from_parts(84_652, 0).saturating_mul(s.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } } diff --git a/frame/whitelist/Cargo.toml b/frame/whitelist/Cargo.toml index 56ceaca9f81a..77304090c45e 100644 --- a/frame/whitelist/Cargo.toml +++ b/frame/whitelist/Cargo.toml @@ -12,20 +12,20 @@ description = "FRAME pallet for whitelisting call, and dispatch from specific or targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive", "max-encoded-len"] } -scale-info = { version = "2.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] pallet-balances = { version = "4.0.0-dev", path = "../balances" } pallet-preimage = { version = "4.0.0-dev", path = "../preimage" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../primitives/io" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../primitives/io" } [features] default = ["std"] diff --git a/frame/whitelist/src/benchmarking.rs b/frame/whitelist/src/benchmarking.rs index 923adc6ccf8c..1982f5eb8738 100644 --- a/frame/whitelist/src/benchmarking.rs +++ b/frame/whitelist/src/benchmarking.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,7 @@ #![cfg(feature = "runtime-benchmarks")] use super::*; -use frame_benchmarking::benchmarks; +use frame_benchmarking::v1::{benchmarks, BenchmarkError}; use frame_support::{ensure, traits::EnsureOrigin}; #[cfg(test)] @@ -28,7 +28,8 @@ use crate::Pallet as Whitelist; benchmarks! { whitelist_call { - let origin = T::WhitelistOrigin::successful_origin(); + let origin = + T::WhitelistOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let call_hash = Default::default(); }: _(origin, call_hash) verify { @@ -43,7 +44,8 @@ benchmarks! { } remove_whitelisted_call { - let origin = T::WhitelistOrigin::successful_origin(); + let origin = + T::WhitelistOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?; let call_hash = Default::default(); Pallet::::whitelist_call(origin.clone(), call_hash) .expect("whitelisting call must be successful"); @@ -62,11 +64,16 @@ benchmarks! { // We benchmark with the maximum possible size for a call. // If the resulting weight is too big, maybe it worth having a weight which depends // on the size of the call, with a new witness in parameter. + #[pov_mode = MaxEncodedLen { + // Use measured PoV size for the Preimages since we pass in a length witness. + Preimage::PreimageFor: Measured + }] dispatch_whitelisted_call { // NOTE: we remove `10` because we need some bytes to encode the variants and vec length let n in 1 .. T::Preimages::MAX_LENGTH as u32 - 10; - let origin = T::DispatchWhitelistedOrigin::successful_origin(); + let origin = T::DispatchWhitelistedOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; let remark = sp_std::vec![1u8; n as usize]; let call: ::RuntimeCall = frame_system::Call::remark { remark }.into(); let call_weight = call.get_dispatch_info().weight; @@ -94,7 +101,8 @@ benchmarks! { dispatch_whitelisted_call_with_preimage { let n in 1 .. 10_000; - let origin = T::DispatchWhitelistedOrigin::successful_origin(); + let origin = T::DispatchWhitelistedOrigin::try_successful_origin() + .map_err(|_| BenchmarkError::Weightless)?; let remark = sp_std::vec![1u8; n as usize]; let call: ::RuntimeCall = frame_system::Call::remark { remark }.into(); diff --git a/frame/whitelist/src/lib.rs b/frame/whitelist/src/lib.rs index 8a5666331c7e..decf010b0675 100644 --- a/frame/whitelist/src/lib.rs +++ b/frame/whitelist/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -88,7 +88,6 @@ pub mod pallet { } #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] pub struct Pallet(_); #[pallet::event] diff --git a/frame/whitelist/src/mock.rs b/frame/whitelist/src/mock.rs index e08c2875aec5..d91f43b33af9 100644 --- a/frame/whitelist/src/mock.rs +++ b/frame/whitelist/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -28,19 +28,14 @@ use frame_support::{ use frame_system::EnsureRoot; use sp_core::H256; use sp_runtime::{ - testing::Header, traits::{BlakeTwo256, IdentityLookup}, BuildStorage, }; -type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; construct_runtime!( - pub enum Test where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, + pub enum Test { System: frame_system, Balances: pallet_balances, @@ -55,14 +50,13 @@ impl frame_system::Config for Test { type BlockLength = (); type DbWeight = (); type RuntimeOrigin = RuntimeOrigin; - type Index = u64; - type BlockNumber = u64; + type Nonce = u64; type Hash = H256; type RuntimeCall = RuntimeCall; type Hashing = BlakeTwo256; type AccountId = u64; type Lookup = IdentityLookup; - type Header = Header; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<250>; type Version = (); @@ -86,6 +80,10 @@ impl pallet_balances::Config for Test { type ExistentialDeposit = ConstU64<1>; type AccountStore = System; type WeightInfo = (); + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = (); + type MaxHolds = (); } impl pallet_preimage::Config for Test { @@ -107,7 +105,7 @@ impl pallet_whitelist::Config for Test { } pub fn new_test_ext() -> sp_io::TestExternalities { - let t = GenesisConfig::default().build_storage().unwrap(); + let t = RuntimeGenesisConfig::default().build_storage().unwrap(); let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); ext diff --git a/frame/whitelist/src/tests.rs b/frame/whitelist/src/tests.rs index d04bb48c1ec4..3a60adbcfbed 100644 --- a/frame/whitelist/src/tests.rs +++ b/frame/whitelist/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -119,7 +119,7 @@ fn test_whitelist_call_and_execute() { RuntimeOrigin::root(), call_hash, call_encoded_len, - call_weight - Weight::from_ref_time(1) + call_weight - Weight::from_parts(1, 0) ), crate::Error::::InvalidCallWeightWitness, ); diff --git a/frame/whitelist/src/weights.rs b/frame/whitelist/src/weights.rs index efd48d657826..de42c5a5841c 100644 --- a/frame/whitelist/src/weights.rs +++ b/frame/whitelist/src/weights.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,33 +18,37 @@ //! Autogenerated weights for pallet_whitelist //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev -//! DATE: 2022-12-05, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]` -//! HOSTNAME: `bm3`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz` +//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 // Executed Command: -// /home/benchbot/cargo_target_dir/production/substrate +// ./target/production/substrate // benchmark // pallet +// --chain=dev // --steps=50 // --repeat=20 +// --pallet=pallet_whitelist +// --no-storage-info +// --no-median-slopes +// --no-min-squares // --extrinsic=* // --execution=wasm // --wasm-execution=compiled // --heap-pages=4096 -// --json-file=/var/lib/gitlab-runner/builds/zyw4fam_/0/parity/mirrors/substrate/.git/.artifacts/bench.json -// --pallet=pallet_whitelist -// --chain=dev -// --header=./HEADER-APACHE2 // --output=./frame/whitelist/src/weights.rs +// --header=./HEADER-APACHE2 // --template=./.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions needed for pallet_whitelist. pub trait WeightInfo { @@ -57,86 +61,130 @@ pub trait WeightInfo { /// Weights for pallet_whitelist using the Substrate node and recommended hardware. pub struct SubstrateWeight(PhantomData); impl WeightInfo for SubstrateWeight { - // Storage: Whitelist WhitelistedCall (r:1 w:1) - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Whitelist WhitelistedCall (r:1 w:1) + /// Proof: Whitelist WhitelistedCall (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) fn whitelist_call() -> Weight { - // Minimum execution time: 26_261 nanoseconds. - Weight::from_ref_time(26_842_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `217` + // Estimated: `3556` + // Minimum execution time: 19_914_000 picoseconds. + Weight::from_parts(20_892_000, 3556) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Whitelist WhitelistedCall (r:1 w:1) - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Whitelist WhitelistedCall (r:1 w:1) + /// Proof: Whitelist WhitelistedCall (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) fn remove_whitelisted_call() -> Weight { - // Minimum execution time: 25_092 nanoseconds. - Weight::from_ref_time(25_903_000) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `346` + // Estimated: `3556` + // Minimum execution time: 18_142_000 picoseconds. + Weight::from_parts(18_529_000, 3556) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } - // Storage: Whitelist WhitelistedCall (r:1 w:1) - // Storage: Preimage PreimageFor (r:1 w:1) - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Whitelist WhitelistedCall (r:1 w:1) + /// Proof: Whitelist WhitelistedCall (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: Preimage PreimageFor (r:1 w:1) + /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) /// The range of component `n` is `[1, 4194294]`. fn dispatch_whitelisted_call(n: u32, ) -> Weight { - // Minimum execution time: 36_685 nanoseconds. - Weight::from_ref_time(37_167_000) + // Proof Size summary in bytes: + // Measured: `422 + n * (1 ±0)` + // Estimated: `3886 + n * (1 ±0)` + // Minimum execution time: 30_671_000 picoseconds. + Weight::from_parts(31_197_000, 3886) // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_144).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(3)) - .saturating_add(T::DbWeight::get().writes(3)) + .saturating_add(Weight::from_parts(1_163, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } - // Storage: Whitelist WhitelistedCall (r:1 w:1) - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Whitelist WhitelistedCall (r:1 w:1) + /// Proof: Whitelist WhitelistedCall (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) /// The range of component `n` is `[1, 10000]`. fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight { - // Minimum execution time: 29_187 nanoseconds. - Weight::from_ref_time(29_896_714) - // Standard Error: 6 - .saturating_add(Weight::from_ref_time(1_505).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(2)) - .saturating_add(T::DbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `346` + // Estimated: `3556` + // Minimum execution time: 22_099_000 picoseconds. + Weight::from_parts(23_145_477, 3556) + // Standard Error: 5 + .saturating_add(Weight::from_parts(1_422, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) } } // For backwards compatibility and tests impl WeightInfo for () { - // Storage: Whitelist WhitelistedCall (r:1 w:1) - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Whitelist WhitelistedCall (r:1 w:1) + /// Proof: Whitelist WhitelistedCall (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) fn whitelist_call() -> Weight { - // Minimum execution time: 26_261 nanoseconds. - Weight::from_ref_time(26_842_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `217` + // Estimated: `3556` + // Minimum execution time: 19_914_000 picoseconds. + Weight::from_parts(20_892_000, 3556) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Whitelist WhitelistedCall (r:1 w:1) - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Whitelist WhitelistedCall (r:1 w:1) + /// Proof: Whitelist WhitelistedCall (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) fn remove_whitelisted_call() -> Weight { - // Minimum execution time: 25_092 nanoseconds. - Weight::from_ref_time(25_903_000) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `346` + // Estimated: `3556` + // Minimum execution time: 18_142_000 picoseconds. + Weight::from_parts(18_529_000, 3556) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } - // Storage: Whitelist WhitelistedCall (r:1 w:1) - // Storage: Preimage PreimageFor (r:1 w:1) - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Whitelist WhitelistedCall (r:1 w:1) + /// Proof: Whitelist WhitelistedCall (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: Preimage PreimageFor (r:1 w:1) + /// Proof: Preimage PreimageFor (max_values: None, max_size: Some(4194344), added: 4196819, mode: Measured) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) /// The range of component `n` is `[1, 4194294]`. fn dispatch_whitelisted_call(n: u32, ) -> Weight { - // Minimum execution time: 36_685 nanoseconds. - Weight::from_ref_time(37_167_000) + // Proof Size summary in bytes: + // Measured: `422 + n * (1 ±0)` + // Estimated: `3886 + n * (1 ±0)` + // Minimum execution time: 30_671_000 picoseconds. + Weight::from_parts(31_197_000, 3886) // Standard Error: 0 - .saturating_add(Weight::from_ref_time(1_144).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(3)) - .saturating_add(RocksDbWeight::get().writes(3)) + .saturating_add(Weight::from_parts(1_163, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 1).saturating_mul(n.into())) } - // Storage: Whitelist WhitelistedCall (r:1 w:1) - // Storage: Preimage StatusFor (r:1 w:1) + /// Storage: Whitelist WhitelistedCall (r:1 w:1) + /// Proof: Whitelist WhitelistedCall (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen) + /// Storage: Preimage StatusFor (r:1 w:1) + /// Proof: Preimage StatusFor (max_values: None, max_size: Some(91), added: 2566, mode: MaxEncodedLen) /// The range of component `n` is `[1, 10000]`. fn dispatch_whitelisted_call_with_preimage(n: u32, ) -> Weight { - // Minimum execution time: 29_187 nanoseconds. - Weight::from_ref_time(29_896_714) - // Standard Error: 6 - .saturating_add(Weight::from_ref_time(1_505).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(2)) - .saturating_add(RocksDbWeight::get().writes(2)) + // Proof Size summary in bytes: + // Measured: `346` + // Estimated: `3556` + // Minimum execution time: 22_099_000 picoseconds. + Weight::from_parts(23_145_477, 3556) + // Standard Error: 5 + .saturating_add(Weight::from_parts(1_422, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) } } diff --git a/primitives/api/Cargo.toml b/primitives/api/Cargo.toml index 75197bcaea90..c6d197b8d35d 100644 --- a/primitives/api/Cargo.toml +++ b/primitives/api/Cargo.toml @@ -13,17 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } sp-api-proc-macro = { version = "4.0.0-dev", path = "proc-macro" } -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } -sp-version = { version = "5.0.0", default-features = false, path = "../version" } -sp-state-machine = { version = "0.13.0", default-features = false, optional = true, path = "../state-machine" } -sp-trie = { version = "7.0.0", default-features = false, optional = true, path = "../trie" } -hash-db = { version = "0.15.2", optional = true } +sp-core = { version = "21.0.0", default-features = false, path = "../core" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../runtime" } +sp-externalities = { version = "0.19.0", default-features = false, optional = true, path = "../externalities" } +sp-version = { version = "22.0.0", default-features = false, path = "../version" } +sp-state-machine = { version = "0.28.0", default-features = false, optional = true, path = "../state-machine" } +sp-trie = { version = "22.0.0", default-features = false, optional = true, path = "../trie" } +hash-db = { version = "0.16.0", optional = true } thiserror = { version = "1.0.30", optional = true } - +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +sp-metadata-ir = { version = "0.1.0", default-features = false, optional = true, path = "../metadata-ir" } log = { version = "0.4.17", default-features = false } [dev-dependencies] @@ -34,6 +36,7 @@ default = ["std"] std = [ "codec/std", "sp-core/std", + "sp-externalities", "sp-std/std", "sp-runtime/std", "sp-state-machine/std", @@ -42,8 +45,10 @@ std = [ "hash-db", "thiserror", "log/std", + "scale-info/std", + "sp-metadata-ir?/std", ] -# Special feature to disable logging completly. +# Special feature to disable logging completely. # # By default `sp-api` initializes the `RuntimeLogger` for each runtime api function. However, # logging functionality increases the code size. It is recommended to enable this feature when @@ -51,3 +56,9 @@ std = [ # # This sets the max logging level to `off` for `log`. disable-logging = ["log/max_level_off"] +# Do not report the documentation in the metadata. +no-metadata-docs = ["sp-api-proc-macro/no-metadata-docs"] +frame-metadata = [ + "sp-metadata-ir", + "sp-api-proc-macro/frame-metadata" +] diff --git a/primitives/api/proc-macro/Cargo.toml b/primitives/api/proc-macro/Cargo.toml index 8acc15d6a059..fb6513806c81 100644 --- a/primitives/api/proc-macro/Cargo.toml +++ b/primitives/api/proc-macro/Cargo.toml @@ -16,13 +16,20 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -quote = "1.0.10" -syn = { version = "1.0.98", features = ["full", "fold", "extra-traits", "visit"] } -proc-macro2 = "1.0.37" +quote = "1.0.28" +syn = { version = "2.0.16", features = ["full", "fold", "extra-traits", "visit"] } +proc-macro2 = "1.0.56" blake2 = { version = "0.10.4", default-features = false } proc-macro-crate = "1.1.3" +expander = "2.0.0" +Inflector = "0.11.4" + +[dev-dependencies] +assert_matches = "1.3.0" -# Required for the doc tests [features] -default = [ "std" ] +# Required for the doc tests +default = ["std"] std = [] +no-metadata-docs = [] +frame-metadata = [] diff --git a/primitives/api/proc-macro/src/common.rs b/primitives/api/proc-macro/src/common.rs index 10887be61327..725ad166fbe7 100644 --- a/primitives/api/proc-macro/src/common.rs +++ b/primitives/api/proc-macro/src/common.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2024 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,9 +18,6 @@ /// The ident used for the block generic parameter. pub const BLOCK_GENERIC_IDENT: &str = "Block"; -/// Unique identifier used to make the hidden includes unique for this macro. -pub const HIDDEN_INCLUDES_ID: &str = "DECL_RUNTIME_APIS"; - /// The `core_trait` attribute. pub const CORE_TRAIT_ATTRIBUTE: &str = "core_trait"; /// The `api_version` attribute. diff --git a/primitives/api/proc-macro/src/decl_runtime_apis.rs b/primitives/api/proc-macro/src/decl_runtime_apis.rs index 8d46047dbda5..370735819f94 100644 --- a/primitives/api/proc-macro/src/decl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/decl_runtime_apis.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,16 +15,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::utils::{ - extract_parameter_names_types_and_borrows, fold_fn_decl_for_client_side, generate_crate_access, - generate_hidden_includes, generate_runtime_mod_name_for_trait, parse_runtime_api_version, - prefix_function_with_trait, replace_wild_card_parameter_names, return_type_extract_type, - versioned_trait_name, AllowSelfRefInParameters, -}; - -use crate::common::{ - API_VERSION_ATTRIBUTE, BLOCK_GENERIC_IDENT, CHANGED_IN_ATTRIBUTE, CORE_TRAIT_ATTRIBUTE, - HIDDEN_INCLUDES_ID, RENAMED_ATTRIBUTE, SUPPORTED_ATTRIBUTE_NAMES, +use crate::{ + common::{ + API_VERSION_ATTRIBUTE, BLOCK_GENERIC_IDENT, CHANGED_IN_ATTRIBUTE, CORE_TRAIT_ATTRIBUTE, + RENAMED_ATTRIBUTE, SUPPORTED_ATTRIBUTE_NAMES, + }, + utils::{ + extract_parameter_names_types_and_borrows, fold_fn_decl_for_client_side, + generate_crate_access, generate_runtime_mod_name_for_trait, parse_runtime_api_version, + prefix_function_with_trait, replace_wild_card_parameter_names, return_type_extract_type, + versioned_trait_name, AllowSelfRefInParameters, + }, }; use proc_macro2::{Span, TokenStream}; @@ -36,9 +37,10 @@ use syn::{ parse::{Error, Parse, ParseStream, Result}, parse_macro_input, parse_quote, spanned::Spanned, + token::Comma, visit::{self, Visit}, - Attribute, FnArg, GenericParam, Generics, Ident, ItemTrait, Lit, Meta, NestedMeta, TraitBound, - TraitItem, TraitItemMethod, + Attribute, FnArg, GenericParam, Generics, Ident, ItemTrait, LitInt, LitStr, TraitBound, + TraitItem, TraitItemFn, }; use std::collections::{BTreeMap, HashMap}; @@ -62,7 +64,7 @@ impl Parse for RuntimeApiDecls { /// Extend the given generics with `Block: BlockT` as first generic parameter. fn extend_generics_with_block(generics: &mut Generics) { - let c = generate_crate_access(HIDDEN_INCLUDES_ID); + let c = generate_crate_access(); generics.lt_token = Some(Default::default()); generics.params.insert(0, parse_quote!( Block: #c::BlockT )); @@ -74,7 +76,7 @@ fn extend_generics_with_block(generics: &mut Generics) { /// attribute body as `TokenStream`. fn remove_supported_attributes(attrs: &mut Vec) -> HashMap<&'static str, Attribute> { let mut result = HashMap::new(); - attrs.retain(|v| match SUPPORTED_ATTRIBUTE_NAMES.iter().find(|a| v.path.is_ident(a)) { + attrs.retain(|v| match SUPPORTED_ATTRIBUTE_NAMES.iter().find(|a| v.path().is_ident(a)) { Some(attribute) => { result.insert(*attribute, v.clone()); false @@ -85,32 +87,6 @@ fn remove_supported_attributes(attrs: &mut Vec) -> HashMap<&'static s result } -/// Visits the ast and checks if `Block` ident is used somewhere. -struct IsUsingBlock { - result: bool, -} - -impl<'ast> Visit<'ast> for IsUsingBlock { - fn visit_ident(&mut self, i: &'ast Ident) { - if i == BLOCK_GENERIC_IDENT { - self.result = true; - } - } -} - -/// Replace all occurrences of `Block` with `NodeBlock` -struct ReplaceBlockWithNodeBlock {} - -impl Fold for ReplaceBlockWithNodeBlock { - fn fold_ident(&mut self, input: Ident) -> Ident { - if input == BLOCK_GENERIC_IDENT { - Ident::new("NodeBlock", Span::call_site()) - } else { - input - } - } -} - /// Versioned API traits are used to catch missing methods when implementing a specific version of a /// versioned API. They contain all non-versioned methods (aka stable methods) from the main trait /// and all versioned methods for the specific version. This means that there is one trait for each @@ -157,7 +133,7 @@ impl Fold for ReplaceBlockWithNodeBlock { /// ``` fn generate_versioned_api_traits( api: ItemTrait, - methods: BTreeMap>, + methods: BTreeMap>, ) -> Vec { let mut result = Vec::::new(); for (version, _) in &methods { @@ -167,7 +143,7 @@ fn generate_versioned_api_traits( // Add the methods from the current version and all previous one. Versions are sorted so // it's safe to stop early. for (_, m) in methods.iter().take_while(|(v, _)| v <= &version) { - versioned_trait.items.extend(m.iter().cloned().map(|m| TraitItem::Method(m))); + versioned_trait.items.extend(m.iter().cloned().map(|m| TraitItem::Fn(m))); } result.push(versioned_trait); @@ -178,37 +154,29 @@ fn generate_versioned_api_traits( /// Try to parse the given `Attribute` as `renamed` attribute. fn parse_renamed_attribute(renamed: &Attribute) -> Result<(String, u32)> { - let meta = renamed.parse_meta()?; - - let err = Err(Error::new( - meta.span(), + let err = || { + Error::new( + renamed.span(), &format!( - "Unexpected `{renamed}` attribute. The supported format is `{renamed}(\"old_name\", version_it_was_renamed)`", - renamed = RENAMED_ATTRIBUTE, - ) + "Unexpected `{RENAMED_ATTRIBUTE}` attribute. \ + The supported format is `{RENAMED_ATTRIBUTE}(\"old_name\", version_it_was_renamed)`", + ), ) - ); - - match meta { - Meta::List(list) => - if list.nested.len() > 2 && list.nested.is_empty() { - err - } else { - let mut itr = list.nested.iter(); - let old_name = match itr.next() { - Some(NestedMeta::Lit(Lit::Str(i))) => i.value(), - _ => return err, - }; - - let version = match itr.next() { - Some(NestedMeta::Lit(Lit::Int(i))) => i.base10_parse()?, - _ => return err, - }; - - Ok((old_name, version)) - }, - _ => err, - } + }; + + renamed + .parse_args_with(|input: ParseStream| { + let old_name: LitStr = input.parse()?; + let _comma: Comma = input.parse()?; + let version: LitInt = input.parse()?; + + if !input.is_empty() { + return Err(input.error("No more arguments expected")) + } + + Ok((old_name.value(), version.base10_parse()?)) + }) + .map_err(|_| err()) } /// Generate the declaration of the trait for the runtime. @@ -225,14 +193,19 @@ fn generate_runtime_decls(decls: &[ItemTrait]) -> Result { get_api_version(&found_attributes).map(|v| generate_runtime_api_version(v as u32))?; let id = generate_runtime_api_id(&decl.ident.to_string()); + #[cfg(feature = "frame-metadata")] + let metadata = crate::runtime_metadata::generate_decl_runtime_metadata(&decl); + #[cfg(not(feature = "frame-metadata"))] + let metadata = quote!(); + let trait_api_version = get_api_version(&found_attributes)?; - let mut methods_by_version: BTreeMap> = BTreeMap::new(); + let mut methods_by_version: BTreeMap> = BTreeMap::new(); // Process the items in the declaration. The filter_map function below does a lot of stuff // because the method attributes are stripped at this point decl.items.iter_mut().for_each(|i| match i { - TraitItem::Method(ref mut method) => { + TraitItem::Fn(ref mut method) => { let method_attrs = remove_supported_attributes(&mut method.attrs); let mut method_version = trait_api_version; // validate the api version for the method (if any) and generate default @@ -298,12 +271,14 @@ fn generate_runtime_decls(decls: &[ItemTrait]) -> Result { #[allow(dead_code)] #[allow(deprecated)] pub mod #mod_name { - use super::*; + pub use super::*; #( #versioned_api_traits )* pub use #versioned_ident as #main_api_ident; + #metadata + pub #api_version pub #id @@ -316,7 +291,7 @@ fn generate_runtime_decls(decls: &[ItemTrait]) -> Result { /// Modify the given runtime api declaration to be usable on the client side. struct ToClientSideDecl<'a> { - block_id: &'a TokenStream, + block_hash: &'a TokenStream, crate_: &'a TokenStream, found_attributes: &'a mut HashMap<&'static str, Attribute>, /// Any error that we found while converting this declaration. @@ -329,7 +304,7 @@ impl<'a> ToClientSideDecl<'a> { fn process(mut self, decl: ItemTrait) -> ItemTrait { let mut decl = self.fold_item_trait(decl); - let block_id = self.block_id; + let block_hash = self.block_hash; let crate_ = self.crate_; // Add the special method that will be implemented by the `impl_runtime_apis!` macro @@ -339,8 +314,7 @@ impl<'a> ToClientSideDecl<'a> { #[doc(hidden)] fn __runtime_api_internal_call_api_at( &self, - at: &#block_id, - context: #crate_::ExecutionContext, + at: #block_hash, params: std::vec::Vec, fn_name: &dyn Fn(#crate_::RuntimeVersion) -> &'static str, ) -> std::result::Result, #crate_::ApiError>; @@ -359,11 +333,9 @@ impl<'a> ToClientSideDecl<'a> { let mut result = Vec::new(); items.into_iter().for_each(|i| match i { - TraitItem::Method(method) => { - let (fn_decl, fn_decl_ctx) = - self.fold_trait_item_method(method, trait_generics_num); + TraitItem::Fn(method) => { + let fn_decl = self.create_method_decl(method, trait_generics_num); result.push(fn_decl.into()); - result.push(fn_decl_ctx.into()); }, r => result.push(r), }); @@ -371,43 +343,14 @@ impl<'a> ToClientSideDecl<'a> { result } - fn fold_trait_item_method( - &mut self, - method: TraitItemMethod, - trait_generics_num: usize, - ) -> (TraitItemMethod, TraitItemMethod) { - let crate_ = self.crate_; - let context = quote!( #crate_::ExecutionContext::OffchainCall(None) ); - let fn_decl = self.create_method_decl(method.clone(), context, trait_generics_num); - let fn_decl_ctx = self.create_method_decl_with_context(method, trait_generics_num); - - (fn_decl, fn_decl_ctx) - } - - fn create_method_decl_with_context( - &mut self, - method: TraitItemMethod, - trait_generics_num: usize, - ) -> TraitItemMethod { - let crate_ = self.crate_; - let context_arg: syn::FnArg = parse_quote!( context: #crate_::ExecutionContext ); - let mut fn_decl_ctx = self.create_method_decl(method, quote!(context), trait_generics_num); - fn_decl_ctx.sig.ident = - Ident::new(&format!("{}_with_context", &fn_decl_ctx.sig.ident), Span::call_site()); - fn_decl_ctx.sig.inputs.insert(2, context_arg); - - fn_decl_ctx - } - /// Takes the method declared by the user and creates the declaration we require for the runtime /// api client side. This method will call by default the `method_runtime_api_impl` for doing /// the actual call into the runtime. fn create_method_decl( &mut self, - mut method: TraitItemMethod, - context: TokenStream, + mut method: TraitItemFn, trait_generics_num: usize, - ) -> TraitItemMethod { + ) -> TraitItemFn { let params = match extract_parameter_names_types_and_borrows( &method.sig, AllowSelfRefInParameters::No, @@ -420,7 +363,7 @@ impl<'a> ToClientSideDecl<'a> { }; let ret_type = return_type_extract_type(&method.sig.output); - fold_fn_decl_for_client_side(&mut method.sig, self.block_id, self.crate_); + fold_fn_decl_for_client_side(&mut method.sig, self.block_hash, self.crate_); let crate_ = self.crate_; @@ -493,12 +436,11 @@ impl<'a> ToClientSideDecl<'a> { >::__runtime_api_internal_call_api_at( self, __runtime_api_at_param__, - #context, __runtime_api_impl_params_encoded__, - &|version| { + &|_version| { #( // Check if we need to call the function by an old name. - if version.apis.iter().any(|(s, v)| { + if _version.apis.iter().any(|(s, v)| { s == &#runtime_mod::ID && *v < #versions }) { return #old_names @@ -542,8 +484,6 @@ impl<'a> Fold for ToClientSideDecl<'a> { input.supertraits.push(parse_quote!( #crate_::Core<#block_ident> )); } - // The client side trait is only required when compiling with the feature `std` or `test`. - input.attrs.push(parse_quote!( #[cfg(any(feature = "std", test))] )); input.items = self.fold_item_trait_items(input.items, input.generics.params.len()); fold::fold_item_trait(self, input) @@ -569,7 +509,7 @@ fn generate_runtime_api_version(version: u32) -> TokenStream { /// Generates the implementation of `RuntimeApiInfo` for the given trait. fn generate_runtime_info_impl(trait_: &ItemTrait, version: u64) -> TokenStream { let trait_name = &trait_.ident; - let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); + let crate_ = generate_crate_access(); let id = generate_runtime_api_id(&trait_name.to_string()); let version = generate_runtime_api_version(version as u32); @@ -587,12 +527,13 @@ fn generate_runtime_info_impl(trait_: &ItemTrait, version: u64) -> TokenStream { }); quote!( - #[cfg(any(feature = "std", test))] - impl < #( #impl_generics, )* > #crate_::RuntimeApiInfo - for dyn #trait_name < #( #ty_generics, )* > - { - #id - #version + #crate_::std_enabled! { + impl < #( #impl_generics, )* > #crate_::RuntimeApiInfo + for dyn #trait_name < #( #ty_generics, )* > + { + #id + #version + } } ) } @@ -620,15 +561,15 @@ fn generate_client_side_decls(decls: &[ItemTrait]) -> Result { for decl in decls { let decl = decl.clone(); - let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); - let block_id = quote!( #crate_::BlockId ); + let crate_ = generate_crate_access(); + let block_hash = quote!( ::Hash ); let mut found_attributes = HashMap::new(); let mut errors = Vec::new(); let trait_ = decl.ident.clone(); let decl = ToClientSideDecl { crate_: &crate_, - block_id: &block_id, + block_hash: &block_hash, found_attributes: &mut found_attributes, errors: &mut errors, trait_: &trait_, @@ -639,7 +580,11 @@ fn generate_client_side_decls(decls: &[ItemTrait]) -> Result { let runtime_info = api_version.map(|v| generate_runtime_info_impl(&decl, v))?; - result.push(quote!( #decl #runtime_info #( #errors )* )); + result.push(quote!( + #crate_::std_enabled! { #decl } + #runtime_info + #( #errors )* + )); } Ok(quote!( #( #result )* )) @@ -656,7 +601,7 @@ impl CheckTraitDecl { /// All errors will be collected in `self.errors`. fn check(&mut self, trait_: &ItemTrait) { self.check_method_declarations(trait_.items.iter().filter_map(|i| match i { - TraitItem::Method(method) => Some(method), + TraitItem::Fn(method) => Some(method), _ => None, })); @@ -666,10 +611,7 @@ impl CheckTraitDecl { /// Check that the given method declarations are correct. /// /// Any error is stored in `self.errors`. - fn check_method_declarations<'a>( - &mut self, - methods: impl Iterator, - ) { + fn check_method_declarations<'a>(&mut self, methods: impl Iterator) { let mut method_to_signature_changed = HashMap::>>::new(); methods.into_iter().for_each(|method| { @@ -777,15 +719,20 @@ pub fn decl_runtime_apis_impl(input: proc_macro::TokenStream) -> proc_macro::Tok fn decl_runtime_apis_impl_inner(api_decls: &[ItemTrait]) -> Result { check_trait_decls(api_decls)?; - let hidden_includes = generate_hidden_includes(HIDDEN_INCLUDES_ID); let runtime_decls = generate_runtime_decls(api_decls)?; let client_side_decls = generate_client_side_decls(api_decls)?; - Ok(quote!( - #hidden_includes - + let decl = quote! { #runtime_decls #client_side_decls - )) + }; + + let decl = expander::Expander::new("decl_runtime_apis") + .dry(std::env::var("SP_API_EXPAND").is_err()) + .verbose(true) + .write_to_out_dir(decl) + .expect("Does not fail because of IO in OUT_DIR; qed"); + + Ok(decl) } diff --git a/primitives/api/proc-macro/src/impl_runtime_apis.rs b/primitives/api/proc-macro/src/impl_runtime_apis.rs index c3f4e36655d2..e45f05029d9f 100644 --- a/primitives/api/proc-macro/src/impl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/impl_runtime_apis.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,15 +15,16 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::utils::{ - extract_all_signature_types, extract_block_type_from_trait_path, extract_impl_trait, - extract_parameter_names_types_and_borrows, generate_crate_access, generate_hidden_includes, - generate_runtime_mod_name_for_trait, parse_runtime_api_version, prefix_function_with_trait, - versioned_trait_name, AllowSelfRefInParameters, RequireQualifiedTraitPath, +use crate::{ + common::API_VERSION_ATTRIBUTE, + utils::{ + extract_all_signature_types, extract_block_type_from_trait_path, extract_impl_trait, + extract_parameter_names_types_and_borrows, generate_crate_access, + generate_runtime_mod_name_for_trait, parse_runtime_api_version, prefix_function_with_trait, + versioned_trait_name, AllowSelfRefInParameters, RequireQualifiedTraitPath, + }, }; -use crate::common::API_VERSION_ATTRIBUTE; - use proc_macro2::{Span, TokenStream}; use quote::quote; @@ -38,9 +39,6 @@ use syn::{ use std::collections::HashSet; -/// Unique identifier used to make the hidden includes unique for this macro. -const HIDDEN_INCLUDES_ID: &str = "IMPL_RUNTIME_APIS"; - /// The structure used for parsing the runtime api implementations. struct RuntimeApiImpls { impls: Vec, @@ -73,7 +71,7 @@ fn generate_impl_call( let params = extract_parameter_names_types_and_borrows(signature, AllowSelfRefInParameters::No)?; - let c = generate_crate_access(HIDDEN_INCLUDES_ID); + let c = generate_crate_access(); let fn_name = &signature.ident; let fn_name_str = fn_name.to_string(); let pnames = params.iter().map(|v| &v.0); @@ -81,15 +79,40 @@ fn generate_impl_call( let ptypes = params.iter().map(|v| &v.1); let pborrow = params.iter().map(|v| &v.2); + let decode_params = if params.is_empty() { + quote!( + if !#input.is_empty() { + panic!( + "Bad input data provided to {}: expected no parameters, but input buffer is not empty.", + #fn_name_str + ); + } + ) + } else { + let let_binding = if params.len() == 1 { + quote! { + let #( #pnames )* : #( #ptypes )* + } + } else { + quote! { + let ( #( #pnames ),* ) : ( #( #ptypes ),* ) + } + }; + + quote!( + #let_binding = + match #c::DecodeLimit::decode_all_with_depth_limit( + #c::MAX_EXTRINSIC_DEPTH, + &mut #input, + ) { + Ok(res) => res, + Err(e) => panic!("Bad input data provided to {}: {}", #fn_name_str, e), + }; + ) + }; + Ok(quote!( - let (#( #pnames ),*) : ( #( #ptypes ),* ) = - match #c::DecodeLimit::decode_all_with_depth_limit( - #c::MAX_EXTRINSIC_DEPTH, - &mut #input, - ) { - Ok(res) => res, - Err(e) => panic!("Bad input data provided to {}: {}", #fn_name_str, e), - }; + #decode_params #[allow(deprecated)] <#runtime as #impl_trait>::#fn_name(#( #pborrow #pnames2 ),*) @@ -115,7 +138,7 @@ fn generate_impl_calls( .ident; for item in &impl_.items { - if let ImplItem::Method(method) = item { + if let ImplItem::Fn(method) = item { let impl_call = generate_impl_call(&method.sig, &impl_.self_ty, input, &impl_trait)?; @@ -134,8 +157,8 @@ fn generate_impl_calls( /// Generate the dispatch function that is used in native to call into the runtime. fn generate_dispatch_function(impls: &[ItemImpl]) -> Result { - let data = Ident::new("__sp_api__input_data", Span::call_site()); - let c = generate_crate_access(HIDDEN_INCLUDES_ID); + let data = Ident::new("_sp_api_input_data_", Span::call_site()); + let c = generate_crate_access(); let impl_calls = generate_impl_calls(impls, &data)? .into_iter() @@ -148,11 +171,12 @@ fn generate_dispatch_function(impls: &[ItemImpl]) -> Result { }); Ok(quote!( - #[cfg(feature = "std")] - pub fn dispatch(method: &str, mut #data: &[u8]) -> Option> { - match method { - #( #impl_calls )* - _ => None, + #c::std_enabled! { + pub fn dispatch(method: &str, mut #data: &[u8]) -> Option> { + match method { + #( #impl_calls )* + _ => None, + } } } )) @@ -161,7 +185,7 @@ fn generate_dispatch_function(impls: &[ItemImpl]) -> Result { /// Generate the interface functions that are used to call into the runtime in wasm. fn generate_wasm_interface(impls: &[ItemImpl]) -> Result { let input = Ident::new("input", Span::call_site()); - let c = generate_crate_access(HIDDEN_INCLUDES_ID); + let c = generate_crate_access(); let impl_calls = generate_impl_calls(impls, &input)? @@ -171,22 +195,23 @@ fn generate_wasm_interface(impls: &[ItemImpl]) -> Result { Ident::new(&prefix_function_with_trait(&trait_, &fn_name), Span::call_site()); quote!( - #( #attrs )* - #[cfg(not(feature = "std"))] - #[no_mangle] - pub unsafe fn #fn_name(input_data: *mut u8, input_len: usize) -> u64 { - let mut #input = if input_len == 0 { - &[0u8; 0] - } else { - unsafe { - #c::slice::from_raw_parts(input_data, input_len) - } - }; - - #c::init_runtime_logger(); - - let output = (move || { #impl_ })(); - #c::to_substrate_wasm_fn_return_value(&output) + #c::std_disabled! { + #( #attrs )* + #[no_mangle] + pub unsafe fn #fn_name(input_data: *mut u8, input_len: usize) -> u64 { + let mut #input = if input_len == 0 { + &[0u8; 0] + } else { + unsafe { + #c::slice::from_raw_parts(input_data, input_len) + } + }; + + #c::init_runtime_logger(); + + let output = (move || { #impl_ })(); + #c::to_substrate_wasm_fn_return_value(&output) + } } ) }); @@ -195,148 +220,192 @@ fn generate_wasm_interface(impls: &[ItemImpl]) -> Result { } fn generate_runtime_api_base_structures() -> Result { - let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); + let crate_ = generate_crate_access(); Ok(quote!( pub struct RuntimeApi {} - /// Implements all runtime apis for the client side. - #[cfg(any(feature = "std", test))] - pub struct RuntimeApiImpl + 'static> { - call: &'static C, - commit_on_success: std::cell::RefCell, - changes: std::cell::RefCell<#crate_::OverlayedChanges>, - storage_transaction_cache: std::cell::RefCell< - #crate_::StorageTransactionCache - >, - recorder: std::option::Option<#crate_::ProofRecorder>, - } - - #[cfg(any(feature = "std", test))] - impl> #crate_::ApiExt for - RuntimeApiImpl - { - type StateBackend = C::StateBackend; - - fn execute_in_transaction #crate_::TransactionOutcome, R>( - &self, - call: F, - ) -> R where Self: Sized { - #crate_::OverlayedChanges::start_transaction(&mut std::cell::RefCell::borrow_mut(&self.changes)); - *std::cell::RefCell::borrow_mut(&self.commit_on_success) = false; - let res = call(self); - *std::cell::RefCell::borrow_mut(&self.commit_on_success) = true; + #crate_::std_enabled! { + /// Implements all runtime apis for the client side. + pub struct RuntimeApiImpl + 'static> { + call: &'static C, + transaction_depth: std::cell::RefCell, + changes: std::cell::RefCell<#crate_::OverlayedChanges>, + storage_transaction_cache: std::cell::RefCell< + #crate_::StorageTransactionCache + >, + recorder: std::option::Option<#crate_::ProofRecorder>, + call_context: #crate_::CallContext, + extensions: std::cell::RefCell<#crate_::Extensions>, + extensions_generated_for: std::cell::RefCell>, + } - self.commit_or_rollback(std::matches!(res, #crate_::TransactionOutcome::Commit(_))); + impl> #crate_::ApiExt for + RuntimeApiImpl + { + type StateBackend = C::StateBackend; + + fn execute_in_transaction #crate_::TransactionOutcome, R>( + &self, + call: F, + ) -> R where Self: Sized { + self.start_transaction(); + + *std::cell::RefCell::borrow_mut(&self.transaction_depth) += 1; + let res = call(self); + std::cell::RefCell::borrow_mut(&self.transaction_depth) + .checked_sub(1) + .expect("Transactions are opened and closed together; qed"); + + self.commit_or_rollback_transaction( + std::matches!(res, #crate_::TransactionOutcome::Commit(_)) + ); - res.into_inner() - } + res.into_inner() + } - fn has_api( - &self, - at: &#crate_::BlockId, - ) -> std::result::Result where Self: Sized { - #crate_::CallApiAt::::runtime_version_at(self.call, at) + fn has_api( + &self, + at: ::Hash, + ) -> std::result::Result where Self: Sized { + #crate_::CallApiAt::::runtime_version_at(self.call, at) .map(|v| #crate_::RuntimeVersion::has_api_with(&v, &A::ID, |v| v == A::VERSION)) - } + } - fn has_api_with bool>( - &self, - at: &#crate_::BlockId, - pred: P, - ) -> std::result::Result where Self: Sized { - #crate_::CallApiAt::::runtime_version_at(self.call, at) + fn has_api_with bool>( + &self, + at: ::Hash, + pred: P, + ) -> std::result::Result where Self: Sized { + #crate_::CallApiAt::::runtime_version_at(self.call, at) .map(|v| #crate_::RuntimeVersion::has_api_with(&v, &A::ID, pred)) - } + } - fn api_version( - &self, - at: &#crate_::BlockId, - ) -> std::result::Result, #crate_::ApiError> where Self: Sized { - #crate_::CallApiAt::::runtime_version_at(self.call, at) + fn api_version( + &self, + at: ::Hash, + ) -> std::result::Result, #crate_::ApiError> where Self: Sized { + #crate_::CallApiAt::::runtime_version_at(self.call, at) .map(|v| #crate_::RuntimeVersion::api_version(&v, &A::ID)) - } + } - fn record_proof(&mut self) { - self.recorder = std::option::Option::Some(std::default::Default::default()); - } + fn record_proof(&mut self) { + self.recorder = std::option::Option::Some(std::default::Default::default()); + } - fn proof_recorder(&self) -> std::option::Option<#crate_::ProofRecorder> { - std::clone::Clone::clone(&self.recorder) - } + fn proof_recorder(&self) -> std::option::Option<#crate_::ProofRecorder> { + std::clone::Clone::clone(&self.recorder) + } - fn extract_proof( - &mut self, - ) -> std::option::Option<#crate_::StorageProof> { - let recorder = std::option::Option::take(&mut self.recorder); - std::option::Option::map(recorder, |recorder| { - #crate_::ProofRecorder::::drain_storage_proof(recorder) - }) - } + fn extract_proof( + &mut self, + ) -> std::option::Option<#crate_::StorageProof> { + let recorder = std::option::Option::take(&mut self.recorder); + std::option::Option::map(recorder, |recorder| { + #crate_::ProofRecorder::::drain_storage_proof(recorder) + }) + } - fn into_storage_changes( - &self, - backend: &Self::StateBackend, - parent_hash: Block::Hash, - ) -> core::result::Result< - #crate_::StorageChanges, + fn into_storage_changes( + &self, + backend: &Self::StateBackend, + parent_hash: Block::Hash, + ) -> core::result::Result< + #crate_::StorageChanges, String - > where Self: Sized { - let at = #crate_::BlockId::Hash(std::clone::Clone::clone(&parent_hash)); - let state_version = #crate_::CallApiAt::::runtime_version_at(self.call, &at) - .map(|v| #crate_::RuntimeVersion::state_version(&v)) - .map_err(|e| format!("Failed to get state version: {}", e))?; - - #crate_::OverlayedChanges::into_storage_changes( - std::cell::RefCell::take(&self.changes), - backend, - core::cell::RefCell::take(&self.storage_transaction_cache), - state_version, - ) + > where Self: Sized { + let state_version = #crate_::CallApiAt::::runtime_version_at(self.call, std::clone::Clone::clone(&parent_hash)) + .map(|v| #crate_::RuntimeVersion::state_version(&v)) + .map_err(|e| format!("Failed to get state version: {}", e))?; + + #crate_::OverlayedChanges::into_storage_changes( + std::cell::RefCell::take(&self.changes), + backend, + core::cell::RefCell::take(&self.storage_transaction_cache), + state_version, + ) + } + + fn set_call_context(&mut self, call_context: #crate_::CallContext) { + self.call_context = call_context; + } + + fn register_extension(&mut self, extension: E) { + std::cell::RefCell::borrow_mut(&self.extensions).register(extension); + } } - } - #[cfg(any(feature = "std", test))] - impl #crate_::ConstructRuntimeApi - for RuntimeApi - where - C: #crate_::CallApiAt + 'static, - { - type RuntimeApi = RuntimeApiImpl; - - fn construct_runtime_api<'a>( - call: &'a C, - ) -> #crate_::ApiRef<'a, Self::RuntimeApi> { - RuntimeApiImpl { - call: unsafe { std::mem::transmute(call) }, - commit_on_success: true.into(), - changes: std::default::Default::default(), - recorder: std::default::Default::default(), - storage_transaction_cache: std::default::Default::default(), - }.into() + impl #crate_::ConstructRuntimeApi + for RuntimeApi + where + C: #crate_::CallApiAt + 'static, + { + type RuntimeApi = RuntimeApiImpl; + + fn construct_runtime_api<'a>( + call: &'a C, + ) -> #crate_::ApiRef<'a, Self::RuntimeApi> { + RuntimeApiImpl { + call: unsafe { std::mem::transmute(call) }, + transaction_depth: 0.into(), + changes: std::default::Default::default(), + recorder: std::default::Default::default(), + storage_transaction_cache: std::default::Default::default(), + call_context: #crate_::CallContext::Offchain, + extensions: std::default::Default::default(), + extensions_generated_for: std::default::Default::default(), + }.into() + } } - } - #[cfg(any(feature = "std", test))] - impl> RuntimeApiImpl { - fn commit_or_rollback(&self, commit: bool) { - let proof = "\ + impl> RuntimeApiImpl { + fn commit_or_rollback_transaction(&self, commit: bool) { + let proof = "\ We only close a transaction when we opened one ourself. Other parts of the runtime that make use of transactions (state-machine) also balance their transactions. The runtime cannot close client initiated transactions; qed"; - if *std::cell::RefCell::borrow(&self.commit_on_success) { + let res = if commit { - #crate_::OverlayedChanges::commit_transaction( + let res = if let Some(recorder) = &self.recorder { + #crate_::ProofRecorder::::commit_transaction(&recorder) + } else { + Ok(()) + }; + + let res2 = #crate_::OverlayedChanges::commit_transaction( &mut std::cell::RefCell::borrow_mut(&self.changes) - ) + ); + + // Will panic on an `Err` below, however we should call commit + // on the recorder and the changes together. + std::result::Result::and(res, std::result::Result::map_err(res2, drop)) } else { - #crate_::OverlayedChanges::rollback_transaction( + let res = if let Some(recorder) = &self.recorder { + #crate_::ProofRecorder::::rollback_transaction(&recorder) + } else { + Ok(()) + }; + + let res2 = #crate_::OverlayedChanges::rollback_transaction( &mut std::cell::RefCell::borrow_mut(&self.changes) - ) + ); + + // Will panic on an `Err` below, however we should call commit + // on the recorder and the changes together. + std::result::Result::and(res, std::result::Result::map_err(res2, drop)) }; std::result::Result::expect(res, proof); } + + fn start_transaction(&self) { + #crate_::OverlayedChanges::start_transaction( + &mut std::cell::RefCell::borrow_mut(&self.changes) + ); + if let Some(recorder) = &self.recorder { + #crate_::ProofRecorder::::start_transaction(&recorder); + } + } } } )) @@ -415,7 +484,7 @@ impl<'a> ApiRuntimeImplToApiRuntimeApiImpl<'a> { fn process(mut self, input: ItemImpl) -> ItemImpl { let mut input = self.fold_item_impl(input); - let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); + let crate_ = generate_crate_access(); // Delete all functions, because all of them are default implemented by // `decl_runtime_apis!`. We only need to implement the `__runtime_api_internal_call_api_at` @@ -424,40 +493,63 @@ impl<'a> ApiRuntimeImplToApiRuntimeApiImpl<'a> { input.items.push(parse_quote! { fn __runtime_api_internal_call_api_at( &self, - at: &#crate_::BlockId<__SR_API_BLOCK__>, - context: #crate_::ExecutionContext, + at: <__SrApiBlock__ as #crate_::BlockT>::Hash, params: std::vec::Vec, fn_name: &dyn Fn(#crate_::RuntimeVersion) -> &'static str, ) -> std::result::Result, #crate_::ApiError> { - if *std::cell::RefCell::borrow(&self.commit_on_success) { - #crate_::OverlayedChanges::start_transaction( - &mut std::cell::RefCell::borrow_mut(&self.changes) - ); + // If we are not already in a transaction, we should create a new transaction + // and then commit/roll it back at the end! + let transaction_depth = *std::cell::RefCell::borrow(&self.transaction_depth); + + if transaction_depth == 0 { + self.start_transaction(); } let res = (|| { - let version = #crate_::CallApiAt::<__SR_API_BLOCK__>::runtime_version_at( + let version = #crate_::CallApiAt::<__SrApiBlock__>::runtime_version_at( self.call, at, )?; + match &mut *std::cell::RefCell::borrow_mut(&self.extensions_generated_for) { + Some(generated_for) => { + if *generated_for != at { + return std::result::Result::Err( + #crate_::ApiError::UsingSameInstanceForDifferentBlocks + ) + } + }, + generated_for @ None => { + #crate_::CallApiAt::<__SrApiBlock__>::initialize_extensions( + self.call, + at, + &mut std::cell::RefCell::borrow_mut(&self.extensions), + )?; + + *generated_for = Some(at); + } + } + let params = #crate_::CallApiAtParams { at, function: (*fn_name)(version), arguments: params, overlayed_changes: &self.changes, storage_transaction_cache: &self.storage_transaction_cache, - context, + call_context: self.call_context, recorder: &self.recorder, + extensions: &self.extensions, }; - #crate_::CallApiAt::<__SR_API_BLOCK__>::call_api_at( + #crate_::CallApiAt::<__SrApiBlock__>::call_api_at( self.call, params, ) })(); - self.commit_or_rollback(std::result::Result::is_ok(&res)); + if transaction_depth == 0 { + self.commit_or_rollback_transaction(std::result::Result::is_ok(&res)); + } res } @@ -470,7 +562,7 @@ impl<'a> ApiRuntimeImplToApiRuntimeApiImpl<'a> { impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> { fn fold_type_path(&mut self, input: TypePath) -> TypePath { let new_ty_path = - if input == *self.runtime_block { parse_quote!(__SR_API_BLOCK__) } else { input }; + if input == *self.runtime_block { parse_quote!(__SrApiBlock__) } else { input }; fold::fold_type_path(self, new_ty_path) } @@ -481,25 +573,26 @@ impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> { // Before we directly had the final block type and rust could determine that it is unwind // safe, but now we just have a generic parameter `Block`. - let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); + let crate_ = generate_crate_access(); // Implement the trait for the `RuntimeApiImpl` input.self_ty = - Box::new(parse_quote!( RuntimeApiImpl<__SR_API_BLOCK__, RuntimeApiImplCall> )); + Box::new(parse_quote!( RuntimeApiImpl<__SrApiBlock__, RuntimeApiImplCall> )); input.generics.params.push(parse_quote!( - __SR_API_BLOCK__: #crate_::BlockT + std::panic::UnwindSafe + + __SrApiBlock__: #crate_::BlockT + std::panic::UnwindSafe + std::panic::RefUnwindSafe )); - input.generics.params.push( - parse_quote!( RuntimeApiImplCall: #crate_::CallApiAt<__SR_API_BLOCK__> + 'static ), - ); + input + .generics + .params + .push(parse_quote!( RuntimeApiImplCall: #crate_::CallApiAt<__SrApiBlock__> + 'static )); let where_clause = input.generics.make_where_clause(); where_clause.predicates.push(parse_quote! { RuntimeApiImplCall::StateBackend: - #crate_::StateBackend<#crate_::HashFor<__SR_API_BLOCK__>> + #crate_::StateBackend<#crate_::HashFor<__SrApiBlock__>> }); where_clause.predicates.push(parse_quote! { &'static RuntimeApiImplCall: Send }); @@ -512,15 +605,11 @@ impl<'a> Fold for ApiRuntimeImplToApiRuntimeApiImpl<'a> { }); where_clause.predicates.push(parse_quote! { - __SR_API_BLOCK__::Header: std::panic::UnwindSafe + std::panic::RefUnwindSafe + __SrApiBlock__::Header: std::panic::UnwindSafe + std::panic::RefUnwindSafe }); input.attrs = filter_cfg_attrs(&input.attrs); - // The implementation for the `RuntimeApiImpl` is only required when compiling with - // the feature `std` or `test`. - input.attrs.push(parse_quote!( #[cfg(any(feature = "std", test))] )); - fold::fold_item_impl(self, input) } } @@ -541,7 +630,10 @@ fn generate_api_impl_for_runtime_api(impls: &[ItemImpl]) -> Result result.push(processed_impl); } - Ok(quote!( #( #result )* )) + + let crate_ = generate_crate_access(); + + Ok(quote!( #crate_::std_enabled! { #( #result )* } )) } fn populate_runtime_api_versions( @@ -558,13 +650,14 @@ fn populate_runtime_api_versions( )); sections.push(quote!( - #( #attrs )* - const _: () = { - // All sections with the same name are going to be merged by concatenation. - #[cfg(not(feature = "std"))] - #[link_section = "runtime_apis"] - static SECTION_CONTENTS: [u8; 12] = #crate_access::serialize_runtime_api_info(#id, #version); - }; + #crate_access::std_disabled! { + #( #attrs )* + const _: () = { + // All sections with the same name are going to be merged by concatenation. + #[link_section = "runtime_apis"] + static SECTION_CONTENTS: [u8; 12] = #crate_access::serialize_runtime_api_info(#id, #version); + }; + } )); } @@ -575,7 +668,7 @@ fn generate_runtime_api_versions(impls: &[ItemImpl]) -> Result { let mut sections = Vec::::with_capacity(impls.len()); let mut processed_traits = HashSet::new(); - let c = generate_crate_access(HIDDEN_INCLUDES_ID); + let c = generate_crate_access(); for impl_ in impls { let api_ver = extract_api_version(&impl_.attrs, impl_.span())?.map(|a| a as u32); @@ -630,14 +723,16 @@ fn impl_runtime_apis_impl_inner(api_impls: &[ItemImpl]) -> Result { let dispatch_impl = generate_dispatch_function(api_impls)?; let api_impls_for_runtime = generate_api_impl_for_runtime(api_impls)?; let base_runtime_api = generate_runtime_api_base_structures()?; - let hidden_includes = generate_hidden_includes(HIDDEN_INCLUDES_ID); let runtime_api_versions = generate_runtime_api_versions(api_impls)?; let wasm_interface = generate_wasm_interface(api_impls)?; let api_impls_for_runtime_api = generate_api_impl_for_runtime_api(api_impls)?; - Ok(quote!( - #hidden_includes + #[cfg(feature = "frame-metadata")] + let runtime_metadata = crate::runtime_metadata::generate_impl_runtime_metadata(api_impls)?; + #[cfg(not(feature = "frame-metadata"))] + let runtime_metadata = quote!(); + let impl_ = quote!( #base_runtime_api #api_impls_for_runtime @@ -646,6 +741,8 @@ fn impl_runtime_apis_impl_inner(api_impls: &[ItemImpl]) -> Result { #runtime_api_versions + #runtime_metadata + pub mod api { use super::*; @@ -653,12 +750,20 @@ fn impl_runtime_apis_impl_inner(api_impls: &[ItemImpl]) -> Result { #wasm_interface } - )) + ); + + let impl_ = expander::Expander::new("impl_runtime_apis") + .dry(std::env::var("SP_API_EXPAND").is_err()) + .verbose(true) + .write_to_out_dir(impl_) + .expect("Does not fail because of IO in OUT_DIR; qed"); + + Ok(impl_) } // Filters all attributes except the cfg ones. fn filter_cfg_attrs(attrs: &[Attribute]) -> Vec { - attrs.iter().filter(|a| a.path.is_ident("cfg")).cloned().collect() + attrs.iter().filter(|a| a.path().is_ident("cfg")).cloned().collect() } // Extracts the value of `API_VERSION_ATTRIBUTE` and handles errors. @@ -670,7 +775,7 @@ fn extract_api_version(attrs: &Vec, span: Span) -> Result // First fetch all `API_VERSION_ATTRIBUTE` values (should be only one) let api_ver = attrs .iter() - .filter(|a| a.path.is_ident(API_VERSION_ATTRIBUTE)) + .filter(|a| a.path().is_ident(API_VERSION_ATTRIBUTE)) .collect::>(); if api_ver.len() > 1 { diff --git a/primitives/api/proc-macro/src/lib.rs b/primitives/api/proc-macro/src/lib.rs index 31636b8e2d54..06e148880e97 100644 --- a/primitives/api/proc-macro/src/lib.rs +++ b/primitives/api/proc-macro/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +25,8 @@ mod common; mod decl_runtime_apis; mod impl_runtime_apis; mod mock_impl_runtime_apis; +#[cfg(feature = "frame-metadata")] +mod runtime_metadata; mod utils; #[proc_macro] diff --git a/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs b/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs index e43a302e1892..f85be81cc7f8 100644 --- a/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs +++ b/primitives/api/proc-macro/src/mock_impl_runtime_apis.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,8 +17,8 @@ use crate::utils::{ extract_block_type_from_trait_path, extract_impl_trait, - extract_parameter_names_types_and_borrows, generate_crate_access, generate_hidden_includes, - return_type_extract_type, AllowSelfRefInParameters, RequireQualifiedTraitPath, + extract_parameter_names_types_and_borrows, generate_crate_access, return_type_extract_type, + AllowSelfRefInParameters, RequireQualifiedTraitPath, }; use proc_macro2::{Span, TokenStream}; @@ -33,12 +33,9 @@ use syn::{ Attribute, ItemImpl, Pat, Type, TypePath, }; -/// Unique identifier used to make the hidden includes unique for this macro. -const HIDDEN_INCLUDES_ID: &str = "MOCK_IMPL_RUNTIME_APIS"; - /// The `advanced` attribute. /// -/// If this attribute is given to a function, the function gets access to the `BlockId` as first +/// If this attribute is given to a function, the function gets access to the `Hash` as first /// parameter and needs to return a `Result` with the appropriate error type. const ADVANCED_ATTRIBUTE: &str = "advanced"; @@ -65,7 +62,7 @@ impl Parse for RuntimeApiImpls { /// Implement the `ApiExt` trait and the `Core` runtime api. fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result { - let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); + let crate_ = generate_crate_access(); Ok(quote!( impl #crate_::ApiExt<#block_type> for #self_ty { @@ -80,14 +77,14 @@ fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result( &self, - _: &#crate_::BlockId<#block_type>, + _: ::Hash, ) -> std::result::Result where Self: Sized { Ok(true) } fn has_api_with bool>( &self, - _: &#crate_::BlockId<#block_type>, + _: ::Hash, pred: P, ) -> std::result::Result where Self: Sized { Ok(pred(A::VERSION)) @@ -95,7 +92,7 @@ fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result( &self, - _: &#crate_::BlockId<#block_type>, + _: ::Hash, ) -> std::result::Result, #crate_::ApiError> where Self: Sized { Ok(Some(A::VERSION)) } @@ -124,13 +121,20 @@ fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result where Self: Sized { unimplemented!("`into_storage_changes` not implemented for runtime api mocks") } + + fn set_call_context(&mut self, _: #crate_::CallContext) { + unimplemented!("`set_call_context` not implemented for runtime api mocks") + } + + fn register_extension(&mut self, _: E) { + unimplemented!("`register_extension` not implemented for runtime api mocks") + } } impl #crate_::Core<#block_type> for #self_ty { fn __runtime_api_internal_call_api_at( &self, - _: &#crate_::BlockId<#block_type>, - _: #crate_::ExecutionContext, + _: <#block_type as #crate_::BlockT>::Hash, _: std::vec::Vec, _: &dyn Fn(#crate_::RuntimeVersion) -> &'static str, ) -> std::result::Result, #crate_::ApiError> { @@ -139,31 +143,14 @@ fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result, - ) -> std::result::Result<#crate_::RuntimeVersion, #crate_::ApiError> { - unimplemented!("`Core::version` not implemented for runtime api mocks") - } - - fn version_with_context( - &self, - _: &#crate_::BlockId<#block_type>, - _: #crate_::ExecutionContext, + _: <#block_type as #crate_::BlockT>::Hash, ) -> std::result::Result<#crate_::RuntimeVersion, #crate_::ApiError> { unimplemented!("`Core::version` not implemented for runtime api mocks") } fn execute_block( &self, - _: &#crate_::BlockId<#block_type>, - _: #block_type, - ) -> std::result::Result<(), #crate_::ApiError> { - unimplemented!("`Core::execute_block` not implemented for runtime api mocks") - } - - fn execute_block_with_context( - &self, - _: &#crate_::BlockId<#block_type>, - _: #crate_::ExecutionContext, + _: <#block_type as #crate_::BlockT>::Hash, _: #block_type, ) -> std::result::Result<(), #crate_::ApiError> { unimplemented!("`Core::execute_block` not implemented for runtime api mocks") @@ -171,16 +158,7 @@ fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result, - _: &<#block_type as #crate_::BlockT>::Header, - ) -> std::result::Result<(), #crate_::ApiError> { - unimplemented!("`Core::initialize_block` not implemented for runtime api mocks") - } - - fn initialize_block_with_context( - &self, - _: &#crate_::BlockId<#block_type>, - _: #crate_::ExecutionContext, + _: <#block_type as #crate_::BlockT>::Hash, _: &<#block_type as #crate_::BlockT>::Header, ) -> std::result::Result<(), #crate_::ApiError> { unimplemented!("`Core::initialize_block` not implemented for runtime api mocks") @@ -195,7 +173,7 @@ fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result) -> bool { let mut found = false; attributes.retain(|attr| { - if attr.path.is_ident(ADVANCED_ATTRIBUTE) { + if attr.path().is_ident(ADVANCED_ATTRIBUTE) { found = true; false } else { @@ -214,7 +192,7 @@ fn get_at_param_name( param_names: &mut Vec, param_types_and_borrows: &mut Vec<(TokenStream, bool)>, function_span: Span, - default_block_id_type: &TokenStream, + default_hash_type: &TokenStream, ) -> Result<(TokenStream, TokenStream)> { if is_advanced { if param_names.is_empty() { @@ -222,7 +200,7 @@ fn get_at_param_name( function_span, format!( "If using the `{}` attribute, it is required that the function \ - takes at least one argument, the `BlockId`.", + takes at least one argument, the `Hash`.", ADVANCED_ATTRIBUTE, ), )) @@ -232,17 +210,14 @@ fn get_at_param_name( // `param_types` can not be empty as well. let ptype_and_borrows = param_types_and_borrows.remove(0); let span = ptype_and_borrows.1.span(); - if !ptype_and_borrows.1 { - return Err(Error::new( - span, - "`BlockId` needs to be taken by reference and not by value!", - )) + if ptype_and_borrows.1 { + return Err(Error::new(span, "`Hash` needs to be taken by value and not by reference!")) } let name = param_names.remove(0); Ok((quote!( #name ), ptype_and_borrows.0)) } else { - Ok((quote!(_), default_block_id_type.clone())) + Ok((quote!(_), default_hash_type.clone())) } } @@ -259,28 +234,14 @@ impl<'a> FoldRuntimeApiImpl<'a> { fn process(mut self, impl_item: syn::ItemImpl) -> syn::ItemImpl { let mut impl_item = self.fold_item_impl(impl_item); - let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); - - // We also need to overwrite all the `_with_context` methods. To do this, - // we clone all methods and add them again with the new name plus one more argument. - impl_item.items.extend(impl_item.items.clone().into_iter().filter_map(|i| { - if let syn::ImplItem::Method(mut m) = i { - m.sig.ident = quote::format_ident!("{}_with_context", m.sig.ident); - m.sig.inputs.insert(2, parse_quote!( _: #crate_::ExecutionContext )); - - Some(m.into()) - } else { - None - } - })); + let crate_ = generate_crate_access(); let block_type = self.block_type; impl_item.items.push(parse_quote! { fn __runtime_api_internal_call_api_at( &self, - _: &#crate_::BlockId<#block_type>, - _: #crate_::ExecutionContext, + _: <#block_type as #crate_::BlockT>::Hash, _: std::vec::Vec, _: &dyn Fn(#crate_::RuntimeVersion) -> &'static str, ) -> std::result::Result, #crate_::ApiError> { @@ -296,9 +257,9 @@ impl<'a> FoldRuntimeApiImpl<'a> { } impl<'a> Fold for FoldRuntimeApiImpl<'a> { - fn fold_impl_item_method(&mut self, mut input: syn::ImplItemMethod) -> syn::ImplItemMethod { + fn fold_impl_item_fn(&mut self, mut input: syn::ImplItemFn) -> syn::ImplItemFn { let block = { - let crate_ = generate_crate_access(HIDDEN_INCLUDES_ID); + let crate_ = generate_crate_access(); let is_advanced = has_advanced_attribute(&mut input.attrs); let mut errors = Vec::new(); @@ -325,19 +286,19 @@ impl<'a> Fold for FoldRuntimeApiImpl<'a> { }; let block_type = &self.block_type; - let block_id_type = quote!( &#crate_::BlockId<#block_type> ); + let hash_type = quote!( <#block_type as #crate_::BlockT>::Hash ); - let (at_param_name, block_id_type) = match get_at_param_name( + let (at_param_name, hash_type) = match get_at_param_name( is_advanced, &mut param_names, &mut param_types_and_borrows, input.span(), - &block_id_type, + &hash_type, ) { Ok(res) => res, Err(e) => { errors.push(e.to_compile_error()); - (quote!(_), block_id_type) + (quote!(_), hash_type) }, }; @@ -345,7 +306,7 @@ impl<'a> Fold for FoldRuntimeApiImpl<'a> { // Rewrite the input parameters. input.sig.inputs = parse_quote! { &self, - #at_param_name: #block_id_type, + #at_param_name: #hash_type, #( #param_names: #param_types ),* }; @@ -383,7 +344,7 @@ impl<'a> Fold for FoldRuntimeApiImpl<'a> { ) }; - let mut input = fold::fold_impl_item_method(self, input); + let mut input = fold::fold_impl_item_fn(self, input); // We need to set the block, after we modified the rest of the ast, otherwise we would // modify our generated block as well. input.block = block; @@ -472,14 +433,11 @@ pub fn mock_impl_runtime_apis_impl(input: proc_macro::TokenStream) -> proc_macro } fn mock_impl_runtime_apis_impl_inner(api_impls: &[ItemImpl]) -> Result { - let hidden_includes = generate_hidden_includes(HIDDEN_INCLUDES_ID); let GeneratedRuntimeApiImpls { impls, block_type, self_ty } = generate_runtime_api_impls(api_impls)?; let api_traits = implement_common_api_traits(block_type, self_ty)?; Ok(quote!( - #hidden_includes - #impls #api_traits diff --git a/primitives/api/proc-macro/src/runtime_metadata.rs b/primitives/api/proc-macro/src/runtime_metadata.rs new file mode 100644 index 000000000000..41849401291e --- /dev/null +++ b/primitives/api/proc-macro/src/runtime_metadata.rs @@ -0,0 +1,268 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use syn::{parse_quote, ItemImpl, ItemTrait, Result}; + +use crate::{ + common::CHANGED_IN_ATTRIBUTE, + utils::{ + extract_impl_trait, filter_cfg_attributes, generate_crate_access, + generate_runtime_mod_name_for_trait, get_doc_literals, RequireQualifiedTraitPath, + }, +}; + +/// Get the type parameter argument without lifetime or mutability +/// of a runtime metadata function. +/// +/// In the following example, both the `AccountId` and `Nonce` generic +/// type parameters must implement `scale_info::TypeInfo` because they +/// are added into the metadata using `scale_info::meta_type`. +/// +/// ```ignore +/// trait ExampleAccountNonceApi { +/// fn account_nonce<'a>(account: &'a AccountId) -> Nonce; +/// } +/// ``` +/// +/// Instead of returning `&'a AccountId` for the first parameter, this function +/// returns `AccountId` to place bounds around it. +fn get_type_param(ty: &syn::Type) -> syn::Type { + // Remove the lifetime and mutability of the type T to + // place bounds around it. + let ty_elem = match &ty { + syn::Type::Reference(reference) => &reference.elem, + syn::Type::Ptr(ptr) => &ptr.elem, + syn::Type::Slice(slice) => &slice.elem, + syn::Type::Array(arr) => &arr.elem, + _ => ty, + }; + + ty_elem.clone() +} + +/// Extract the documentation from the provided attributes. +/// +/// It takes into account the `no-metadata-docs` feature. +fn collect_docs(attrs: &[syn::Attribute], crate_: &TokenStream2) -> TokenStream2 { + if cfg!(feature = "no-metadata-docs") { + quote!(#crate_::vec![]) + } else { + let docs = get_doc_literals(&attrs); + quote!(#crate_::vec![ #( #docs, )* ]) + } +} + +/// Generate the runtime metadata of the provided trait. +/// +/// The metadata is exposed as a generic function on the hidden module +/// of the trait generated by the `decl_runtime_apis`. +pub fn generate_decl_runtime_metadata(decl: &ItemTrait) -> TokenStream2 { + let crate_ = generate_crate_access(); + let mut methods = Vec::new(); + + // Ensure that any function parameter that relies on the `BlockT` bounds + // also has `TypeInfo + 'static` bounds (required by `scale_info::meta_type`). + // + // For example, if a runtime API defines a method that has an input: + // `fn func(input: ::Header)` + // then the runtime metadata will imply `::Header: TypeInfo + 'static`. + // + // This restricts the bounds at the metadata level, without needing to modify the `BlockT` + // itself, since the concrete implementations are already satisfying `TypeInfo`. + let mut where_clause = Vec::new(); + for item in &decl.items { + // Collect metadata for methods only. + let syn::TraitItem::Fn(method) = item else { continue }; + + // Collect metadata only for the latest methods. + let is_changed_in = + method.attrs.iter().any(|attr| attr.path().is_ident(CHANGED_IN_ATTRIBUTE)); + if is_changed_in { + continue + } + + let mut inputs = Vec::new(); + let signature = &method.sig; + for input in &signature.inputs { + // Exclude `self` from metadata collection. + let syn::FnArg::Typed(typed) = input else { continue }; + + let pat = &typed.pat; + let name = quote!(#pat).to_string(); + let ty = &typed.ty; + + where_clause.push(get_type_param(ty)); + + inputs.push(quote!( + #crate_::metadata_ir::RuntimeApiMethodParamMetadataIR { + name: #name, + ty: #crate_::scale_info::meta_type::<#ty>(), + } + )); + } + + let output = match &signature.output { + syn::ReturnType::Default => quote!(#crate_::scale_info::meta_type::<()>()), + syn::ReturnType::Type(_, ty) => { + where_clause.push(get_type_param(ty)); + quote!(#crate_::scale_info::meta_type::<#ty>()) + }, + }; + + // String method name including quotes for constructing `v15::RuntimeApiMethodMetadata`. + let method_name = signature.ident.to_string(); + let docs = collect_docs(&method.attrs, &crate_); + + // Include the method metadata only if its `cfg` features are enabled. + let attrs = filter_cfg_attributes(&method.attrs); + methods.push(quote!( + #( #attrs )* + #crate_::metadata_ir::RuntimeApiMethodMetadataIR { + name: #method_name, + inputs: #crate_::vec![ #( #inputs, )* ], + output: #output, + docs: #docs, + } + )); + } + + let trait_name_ident = &decl.ident; + let trait_name = trait_name_ident.to_string(); + let docs = collect_docs(&decl.attrs, &crate_); + let attrs = filter_cfg_attributes(&decl.attrs); + // The trait generics where already extended with `Block: BlockT`. + let mut generics = decl.generics.clone(); + for generic_param in generics.params.iter_mut() { + let syn::GenericParam::Type(ty) = generic_param else { continue }; + + // Default type parameters are not allowed in functions. + ty.eq_token = None; + ty.default = None; + } + + where_clause + .into_iter() + .map(|ty| parse_quote!(#ty: #crate_::scale_info::TypeInfo + 'static)) + .for_each(|w| generics.make_where_clause().predicates.push(w)); + + let (impl_generics, _, where_clause) = generics.split_for_impl(); + + quote!( + #( #attrs )* + #[inline(always)] + pub fn runtime_metadata #impl_generics () -> #crate_::metadata_ir::RuntimeApiMetadataIR + #where_clause + { + #crate_::metadata_ir::RuntimeApiMetadataIR { + name: #trait_name, + methods: #crate_::vec![ #( #methods, )* ], + docs: #docs, + } + } + ) +} + +/// Implement the `runtime_metadata` function on the runtime that +/// generates the metadata for the given traits. +/// +/// The metadata of each trait is extracted from the generic function +/// exposed by `generate_decl_runtime_metadata`. +pub fn generate_impl_runtime_metadata(impls: &[ItemImpl]) -> Result { + if impls.is_empty() { + return Ok(quote!()) + } + + let crate_ = generate_crate_access(); + + // Get the name of the runtime for which the traits are implemented. + let runtime_name = &impls + .get(0) + .expect("Traits should contain at least one implementation; qed") + .self_ty; + + let mut metadata = Vec::new(); + + for impl_ in impls { + let mut trait_ = extract_impl_trait(&impl_, RequireQualifiedTraitPath::Yes)?.clone(); + + // Implementation traits are always references with a path `impl client::Core ...` + // The trait name is the last segment of this path. + let trait_name_ident = &trait_ + .segments + .last() + .as_ref() + .expect("Trait path should always contain at least one item; qed") + .ident; + + // Extract the generics from the trait to pass to the `runtime_metadata` + // function on the hidden module. + let generics = trait_ + .segments + .iter() + .find_map(|segment| { + if let syn::PathArguments::AngleBracketed(generics) = &segment.arguments { + Some(generics.clone()) + } else { + None + } + }) + .expect("Trait path should always contain at least one generic parameter; qed"); + + let mod_name = generate_runtime_mod_name_for_trait(&trait_name_ident); + // Get absolute path to the `runtime_decl_for_` module by replacing the last segment. + if let Some(segment) = trait_.segments.last_mut() { + *segment = parse_quote!(#mod_name); + } + + let attrs = filter_cfg_attributes(&impl_.attrs); + metadata.push(quote!( + #( #attrs )* + #trait_::runtime_metadata::#generics() + )); + } + + // Each runtime must expose the `runtime_metadata()` to fetch the runtime API metadata. + // The function is implemented by calling `impl_runtime_apis!`. + // + // However, the `construct_runtime!` may be called without calling `impl_runtime_apis!`. + // Rely on the `Deref` trait to differentiate between a runtime that implements + // APIs (by macro impl_runtime_apis!) and a runtime that is simply created (by macro + // construct_runtime!). + // + // Both `InternalConstructRuntime` and `InternalImplRuntimeApis` expose a `runtime_metadata()` + // function. `InternalConstructRuntime` is implemented by the `construct_runtime!` for Runtime + // references (`& Runtime`), while `InternalImplRuntimeApis` is implemented by the + // `impl_runtime_apis!` for Runtime (`Runtime`). + // + // Therefore, the `Deref` trait will resolve the `runtime_metadata` from `impl_runtime_apis!` + // when both macros are called; and will resolve an empty `runtime_metadata` when only the + // `construct_runtime!` is called. + + Ok(quote!( + #[doc(hidden)] + trait InternalImplRuntimeApis { + #[inline(always)] + fn runtime_metadata(&self) -> #crate_::vec::Vec<#crate_::metadata_ir::RuntimeApiMetadataIR> { + #crate_::vec![ #( #metadata, )* ] + } + } + #[doc(hidden)] + impl InternalImplRuntimeApis for #runtime_name {} + )) +} diff --git a/primitives/api/proc-macro/src/utils.rs b/primitives/api/proc-macro/src/utils.rs index 2ccd050cfb15..c9389154bbf4 100644 --- a/primitives/api/proc-macro/src/utils.rs +++ b/primitives/api/proc-macro/src/utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,29 +24,19 @@ use syn::{ use quote::{format_ident, quote}; -use std::env; - use proc_macro_crate::{crate_name, FoundCrate}; use crate::common::API_VERSION_ATTRIBUTE; -fn generate_hidden_includes_mod_name(unique_id: &'static str) -> Ident { - Ident::new(&format!("sp_api_hidden_includes_{}", unique_id), Span::call_site()) -} +use inflector::Inflector; -/// Generates the hidden includes that are required to make the macro independent from its scope. -pub fn generate_hidden_includes(unique_id: &'static str) -> TokenStream { - let mod_name = generate_hidden_includes_mod_name(unique_id); +/// Generates the access to the `sc_client` crate. +pub fn generate_crate_access() -> TokenStream { match crate_name("sp-api") { - Ok(FoundCrate::Itself) => quote!(), - Ok(FoundCrate::Name(client_name)) => { - let client_name = Ident::new(&client_name, Span::call_site()); - quote!( - #[doc(hidden)] - mod #mod_name { - pub extern crate #client_name as sp_api; - } - ) + Ok(FoundCrate::Itself) => quote!(sp_api), + Ok(FoundCrate::Name(renamed_name)) => { + let renamed_name = Ident::new(&renamed_name, Span::call_site()); + quote!(#renamed_name) }, Err(e) => { let err = Error::new(Span::call_site(), e).to_compile_error(); @@ -55,19 +45,12 @@ pub fn generate_hidden_includes(unique_id: &'static str) -> TokenStream { } } -/// Generates the access to the `sc_client` crate. -pub fn generate_crate_access(unique_id: &'static str) -> TokenStream { - if env::var("CARGO_PKG_NAME").unwrap() == "sp-api" { - quote!(sp_api) - } else { - let mod_name = generate_hidden_includes_mod_name(unique_id); - quote!( self::#mod_name::sp_api ) - } -} - /// Generates the name of the module that contains the trait declaration for the runtime. pub fn generate_runtime_mod_name_for_trait(trait_: &Ident) -> Ident { - Ident::new(&format!("runtime_decl_for_{}", trait_), Span::call_site()) + Ident::new( + &format!("runtime_decl_for_{}", trait_.to_string().to_snake_case()), + Span::call_site(), + ) } /// Get the type of a `syn::ReturnType`. @@ -94,13 +77,13 @@ pub fn replace_wild_card_parameter_names(input: &mut Signature) { /// Fold the given `Signature` to make it usable on the client side. pub fn fold_fn_decl_for_client_side( input: &mut Signature, - block_id: &TokenStream, + block_hash: &TokenStream, crate_: &TokenStream, ) { replace_wild_card_parameter_names(input); - // Add `&self, at:& BlockId` as parameters to each function at the beginning. - input.inputs.insert(0, parse_quote!( __runtime_api_at_param__: &#block_id )); + // Add `&self, at:& Block::Hash` as parameters to each function at the beginning. + input.inputs.insert(0, parse_quote!( __runtime_api_at_param__: #block_hash )); input.inputs.insert(0, parse_quote!(&self)); // Wrap the output in a `Result` @@ -175,7 +158,7 @@ pub fn extract_all_signature_types(items: &[ImplItem]) -> Vec { items .iter() .filter_map(|i| match i { - ImplItem::Method(method) => Some(&method.sig), + ImplItem::Fn(method) => Some(&method.sig), _ => None, }) .flat_map(|sig| { @@ -270,7 +253,78 @@ pub fn parse_runtime_api_version(version: &Attribute) -> Result { version.base10_parse() } -// Each versioned trait is named 'ApiNameVN' where N is the specific version. E.g. ParachainHostV2 +/// Each versioned trait is named 'ApiNameVN' where N is the specific version. E.g. ParachainHostV2 pub fn versioned_trait_name(trait_ident: &Ident, version: u64) -> Ident { format_ident!("{}V{}", trait_ident, version) } + +/// Extract the documentation from the provided attributes. +#[cfg(feature = "frame-metadata")] +pub fn get_doc_literals(attrs: &[syn::Attribute]) -> Vec { + use quote::ToTokens; + + attrs + .iter() + .filter_map(|attr| { + let syn::Meta::NameValue(meta) = &attr.meta else { return None }; + let Ok(lit) = syn::parse2::(meta.value.to_token_stream()) else { + unreachable!("non-lit doc attribute values do not exist"); + }; + meta.path.get_ident().filter(|ident| *ident == "doc").map(|_| lit) + }) + .collect() +} + +/// Filters all attributes except the cfg ones. +#[cfg(feature = "frame-metadata")] +pub fn filter_cfg_attributes(attrs: &[syn::Attribute]) -> Vec { + attrs.iter().filter(|a| a.path().is_ident("cfg")).cloned().collect() +} + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + + use super::*; + + #[test] + fn check_get_doc_literals() { + const FIRST: &'static str = "hello"; + const SECOND: &'static str = "WORLD"; + + let doc: Attribute = parse_quote!(#[doc = #FIRST]); + let doc_world: Attribute = parse_quote!(#[doc = #SECOND]); + + let attrs = vec![ + doc.clone(), + parse_quote!(#[derive(Debug)]), + parse_quote!(#[test]), + parse_quote!(#[allow(non_camel_case_types)]), + doc_world.clone(), + ]; + + let docs = get_doc_literals(&attrs); + assert_eq!(docs.len(), 2); + assert_matches!(&docs[0], syn::Lit::Str(val) if val.value() == FIRST); + assert_matches!(&docs[1], syn::Lit::Str(val) if val.value() == SECOND); + } + + #[test] + fn check_filter_cfg_attributes() { + let cfg_std: Attribute = parse_quote!(#[cfg(feature = "std")]); + let cfg_benchmarks: Attribute = parse_quote!(#[cfg(feature = "runtime-benchmarks")]); + + let attrs = vec![ + cfg_std.clone(), + parse_quote!(#[derive(Debug)]), + parse_quote!(#[test]), + cfg_benchmarks.clone(), + parse_quote!(#[allow(non_camel_case_types)]), + ]; + + let filtered = filter_cfg_attributes(&attrs); + assert_eq!(filtered.len(), 2); + assert_eq!(cfg_std, filtered[0]); + assert_eq!(cfg_benchmarks, filtered[1]); + } +} diff --git a/primitives/api/src/lib.rs b/primitives/api/src/lib.rs index 4ff4becb80f4..e683d7aa637e 100644 --- a/primitives/api/src/lib.rs +++ b/primitives/api/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -76,21 +76,29 @@ pub use codec::{self, Decode, DecodeLimit, Encode}; #[cfg(feature = "std")] pub use hash_db::Hasher; #[doc(hidden)] +pub use scale_info; +#[doc(hidden)] +pub use sp_core::offchain; +#[doc(hidden)] #[cfg(not(feature = "std"))] pub use sp_core::to_substrate_wasm_fn_return_value; +#[doc(hidden)] +#[cfg(feature = "std")] +pub use sp_core::traits::CallContext; use sp_core::OpaqueMetadata; #[doc(hidden)] -pub use sp_core::{offchain, ExecutionContext}; +#[cfg(feature = "std")] +pub use sp_externalities::{Extension, Extensions}; +#[doc(hidden)] +#[cfg(feature = "frame-metadata")] +pub use sp_metadata_ir::{self as metadata_ir, frame_metadata as metadata}; #[doc(hidden)] #[cfg(feature = "std")] pub use sp_runtime::StateVersion; #[doc(hidden)] pub use sp_runtime::{ generic::BlockId, - traits::{ - Block as BlockT, GetNodeBlockType, GetRuntimeBlockType, Hash as HashT, HashFor, - Header as HeaderT, NumberFor, - }, + traits::{Block as BlockT, Hash as HashT, HashFor, Header as HeaderT, NumberFor}, transaction_validity::TransactionValidity, RuntimeString, TransactionOutcome, }; @@ -101,7 +109,7 @@ pub use sp_state_machine::{ StorageProof, TrieBackend, TrieBackendBuilder, }; #[doc(hidden)] -pub use sp_std::{mem, slice}; +pub use sp_std::{mem, slice, vec}; #[doc(hidden)] pub use sp_version::{create_apis_vec, ApiId, ApisVec, RuntimeVersion}; #[cfg(feature = "std")] @@ -115,7 +123,7 @@ pub const MAX_EXTRINSIC_DEPTH: u32 = 256; /// The macro will create two declarations, one for using on the client side and one for using /// on the runtime side. The declaration for the runtime side is hidden in its own module. /// The client side declaration gets two extra parameters per function, -/// `&self` and `at: &BlockId`. The runtime side declaration will match the given trait +/// `&self` and `at: Block::Hash`. The runtime side declaration will match the given trait /// declaration. Besides one exception, the macro adds an extra generic parameter `Block: /// BlockT` to the client side and the runtime side. This generic parameter is usable by the /// user. @@ -182,7 +190,7 @@ pub const MAX_EXTRINSIC_DEPTH: u32 = 256; /// ``` /// /// To check if a given runtime implements a runtime api trait, the `RuntimeVersion` has the -/// function `has_api()`. Also the `ApiExt` provides a function `has_api(at: &BlockId)` +/// function `has_api()`. Also the `ApiExt` provides a function `has_api(at: Hash)` /// to check if the runtime at the given block id implements the requested runtime api trait. /// /// # Declaring multiple api versions @@ -259,15 +267,12 @@ pub use sp_api_proc_macro::decl_runtime_apis; /// ```rust /// use sp_version::create_runtime_str; /// # -/// # use sp_runtime::traits::{GetNodeBlockType, Block as BlockT}; +/// # use sp_runtime::traits::Block as BlockT; /// # use sp_test_primitives::Block; /// # -/// # /// The declaration of the `Runtime` type and the implementation of the `GetNodeBlockType` -/// # /// trait are done by the `construct_runtime!` macro in a real runtime. +/// # /// The declaration of the `Runtime` type is done by the `construct_runtime!` macro +/// # /// in a real runtime. /// # pub struct Runtime {} -/// # impl GetNodeBlockType for Runtime { -/// # type NodeBlock = Block; -/// # } /// # /// # sp_api::decl_runtime_apis! { /// # /// Declare the api trait. @@ -400,16 +405,16 @@ pub use sp_api_proc_macro::impl_runtime_apis; /// /// This attribute can be placed above individual function in the mock implementation to /// request more control over the function declaration. From the client side each runtime api -/// function is called with the `at` parameter that is a [`BlockId`](sp_api::BlockId). When -/// using the `advanced` attribute, the macro expects that the first parameter of the function -/// is this `at` parameter. Besides that the macro also doesn't do the automatic return value -/// rewrite, which means that full return value must be specified. The full return value is -/// constructed like [`Result`]`<, Error>` while `ReturnValue` being the return -/// value that is specified in the trait declaration. +/// function is called with the `at` parameter that is a [`Hash`](sp_runtime::traits::Hash). +/// When using the `advanced` attribute, the macro expects that the first parameter of the +/// function is this `at` parameter. Besides that the macro also doesn't do the automatic +/// return value rewrite, which means that full return value must be specified. The full return +/// value is constructed like [`Result`]`<, Error>` while `ReturnValue` being the +/// return value that is specified in the trait declaration. /// /// ## Example /// ```rust -/// # use sp_runtime::{traits::Block as BlockT, generic::BlockId}; +/// # use sp_runtime::traits::Block as BlockT; /// # use sp_test_primitives::Block; /// # use codec; /// # @@ -429,16 +434,14 @@ pub use sp_api_proc_macro::impl_runtime_apis; /// sp_api::mock_impl_runtime_apis! { /// impl Balance for MockApi { /// #[advanced] -/// fn get_balance(&self, at: &BlockId) -> Result { +/// fn get_balance(&self, at: ::Hash) -> Result { /// println!("Being called at: {}", at); /// /// Ok(self.balance.into()) /// } /// #[advanced] -/// fn set_balance(at: &BlockId, val: u64) -> Result<(), sp_api::ApiError> { -/// if let BlockId::Number(1) = at { -/// println!("Being called to set balance to: {}", val); -/// } +/// fn set_balance(at: ::Hash, val: u64) -> Result<(), sp_api::ApiError> { +/// println!("Being called at: {}", at); /// /// Ok(().into()) /// } @@ -521,6 +524,8 @@ pub enum ApiError { Application(#[from] Box), #[error("Api called for an unknown Block: {0}")] UnknownBlock(String), + #[error("Using the same api instance to call into multiple independent blocks.")] + UsingSameInstanceForDifferentBlocks, } /// Extends the runtime api implementation with some common functionality. @@ -539,14 +544,14 @@ pub trait ApiExt { Self: Sized; /// Checks if the given api is implemented and versions match. - fn has_api(&self, at: &BlockId) -> Result + fn has_api(&self, at_hash: Block::Hash) -> Result where Self: Sized; /// Check if the given api is implemented and the version passes a predicate. fn has_api_with bool>( &self, - at: &BlockId, + at_hash: Block::Hash, pred: P, ) -> Result where @@ -555,7 +560,7 @@ pub trait ApiExt { /// Returns the version of the given api. fn api_version( &self, - at: &BlockId, + at_hash: Block::Hash, ) -> Result, ApiError> where Self: Sized; @@ -584,13 +589,19 @@ pub trait ApiExt { ) -> Result, String> where Self: Sized; + + /// Set the [`CallContext`] to be used by the runtime api calls done by this instance. + fn set_call_context(&mut self, call_context: CallContext); + + /// Register an [`Extension`] that will be accessible while executing a runtime api call. + fn register_extension(&mut self, extension: E); } /// Parameters for [`CallApiAt::call_api_at`]. #[cfg(feature = "std")] pub struct CallApiAtParams<'a, Block: BlockT, Backend: StateBackend>> { /// The block id that determines the state that should be setup when calling the function. - pub at: &'a BlockId, + pub at: Block::Hash, /// The name of the function that should be called. pub function: &'static str, /// The encoded arguments of the function. @@ -599,10 +610,12 @@ pub struct CallApiAtParams<'a, Block: BlockT, Backend: StateBackend, /// The cache for storage transactions. pub storage_transaction_cache: &'a RefCell>, - /// The context this function is executed in. - pub context: ExecutionContext, + /// The call context of this call. + pub call_context: CallContext, /// The optional proof recorder for recording storage accesses. pub recorder: &'a Option>, + /// The extensions that should be used for this call. + pub extensions: &'a RefCell, } /// Something that can call into the an api at a given block. @@ -619,10 +632,17 @@ pub trait CallApiAt { ) -> Result, ApiError>; /// Returns the runtime version at the given block. - fn runtime_version_at(&self, at: &BlockId) -> Result; + fn runtime_version_at(&self, at_hash: Block::Hash) -> Result; /// Get the state `at` the given block. - fn state_at(&self, at: &BlockId) -> Result; + fn state_at(&self, at: Block::Hash) -> Result; + + /// Initialize the `extensions` for the given block `at` by using the global extensions factory. + fn initialize_extensions( + &self, + at: Block::Hash, + extensions: &mut Extensions, + ) -> Result<(), ApiError>; } /// Auxiliary wrapper that holds an api instance and binds it to the given lifetime. @@ -731,8 +751,23 @@ decl_runtime_apis! { } /// The `Metadata` api trait that returns metadata for the runtime. + #[api_version(2)] pub trait Metadata { /// Returns the metadata of a runtime. fn metadata() -> OpaqueMetadata; + + /// Returns the metadata at a given version. + /// + /// If the given `version` isn't supported, this will return `None`. + /// Use [`Self::metadata_versions`] to find out about supported metadata version of the runtime. + fn metadata_at_version(version: u32) -> Option; + + /// Returns the supported metadata versions. + /// + /// This can be used to call `metadata_at_version`. + fn metadata_versions() -> sp_std::vec::Vec; } } + +sp_core::generate_feature_enabled_macro!(std_enabled, feature = "std", $); +sp_core::generate_feature_enabled_macro!(std_disabled, not(feature = "std"), $); diff --git a/primitives/api/test/Cargo.toml b/primitives/api/test/Cargo.toml index b3ceb14fe0d6..87c5eb6199ef 100644 --- a/primitives/api/test/Cargo.toml +++ b/primitives/api/test/Cargo.toml @@ -14,27 +14,24 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] sp-api = { version = "4.0.0-dev", path = "../" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } -sp-version = { version = "5.0.0", path = "../../version" } -sp-tracing = { version = "6.0.0", path = "../../tracing" } -sp-runtime = { version = "7.0.0", path = "../../runtime" } +sp-version = { version = "22.0.0", path = "../../version" } +sp-tracing = { version = "10.0.0", path = "../../tracing" } +sp-runtime = { version = "24.0.0", path = "../../runtime" } sp-consensus = { version = "0.10.0-dev", path = "../../consensus/common" } sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } -codec = { package = "parity-scale-codec", version = "3.2.2" } -sp-state-machine = { version = "0.13.0", path = "../../state-machine" } +codec = { package = "parity-scale-codec", version = "3.6.1" } +sp-state-machine = { version = "0.28.0", path = "../../state-machine" } trybuild = "1.0.74" rustversion = "1.0.6" +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } [dev-dependencies] -criterion = "0.3.0" +criterion = "0.4.0" futures = "0.3.21" log = "0.4.17" -sp-core = { version = "7.0.0", path = "../../core" } +sp-core = { version = "21.0.0", path = "../../core" } +static_assertions = "1.1.0" [[bench]] name = "bench" harness = false - -# We only need this to generate the correct code. -[features] -default = [ "std" ] -std = [] diff --git a/primitives/api/test/benches/bench.rs b/primitives/api/test/benches/bench.rs index 2445a5c07f09..45bea08af6de 100644 --- a/primitives/api/test/benches/bench.rs +++ b/primitives/api/test/benches/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,8 +17,6 @@ use criterion::{criterion_group, criterion_main, Criterion}; use sp_api::ProvideRuntimeApi; -use sp_runtime::generic::BlockId; -use sp_state_machine::ExecutionStrategy; use substrate_test_runtime_client::{ runtime::TestAPI, DefaultTestClientBuilderExt, TestClientBuilder, TestClientBuilderExt, }; @@ -27,49 +25,45 @@ fn sp_api_benchmark(c: &mut Criterion) { c.bench_function("add one with same runtime api", |b| { let client = substrate_test_runtime_client::new(); let runtime_api = client.runtime_api(); - let block_id = BlockId::Hash(client.chain_info().best_hash); + let best_hash = client.chain_info().best_hash; - b.iter(|| runtime_api.benchmark_add_one(&block_id, &1)) + b.iter(|| runtime_api.benchmark_add_one(best_hash, &1)) }); c.bench_function("add one with recreating runtime api", |b| { let client = substrate_test_runtime_client::new(); - let block_id = BlockId::Hash(client.chain_info().best_hash); + let best_hash = client.chain_info().best_hash; - b.iter(|| client.runtime_api().benchmark_add_one(&block_id, &1)) + b.iter(|| client.runtime_api().benchmark_add_one(best_hash, &1)) }); c.bench_function("vector add one with same runtime api", |b| { let client = substrate_test_runtime_client::new(); let runtime_api = client.runtime_api(); - let block_id = BlockId::Hash(client.chain_info().best_hash); + let best_hash = client.chain_info().best_hash; let data = vec![0; 1000]; - b.iter_with_large_drop(|| runtime_api.benchmark_vector_add_one(&block_id, &data)) + b.iter_with_large_drop(|| runtime_api.benchmark_vector_add_one(best_hash, &data)) }); c.bench_function("vector add one with recreating runtime api", |b| { let client = substrate_test_runtime_client::new(); - let block_id = BlockId::Hash(client.chain_info().best_hash); + let best_hash = client.chain_info().best_hash; let data = vec![0; 1000]; - b.iter_with_large_drop(|| client.runtime_api().benchmark_vector_add_one(&block_id, &data)) + b.iter_with_large_drop(|| client.runtime_api().benchmark_vector_add_one(best_hash, &data)) }); c.bench_function("calling function by function pointer in wasm", |b| { - let client = TestClientBuilder::new() - .set_execution_strategy(ExecutionStrategy::AlwaysWasm) - .build(); - let block_id = BlockId::Hash(client.chain_info().best_hash); - b.iter(|| client.runtime_api().benchmark_indirect_call(&block_id).unwrap()) + let client = TestClientBuilder::new().build(); + let best_hash = client.chain_info().best_hash; + b.iter(|| client.runtime_api().benchmark_indirect_call(best_hash).unwrap()) }); - c.bench_function("calling function in wasm", |b| { - let client = TestClientBuilder::new() - .set_execution_strategy(ExecutionStrategy::AlwaysWasm) - .build(); - let block_id = BlockId::Hash(client.chain_info().best_hash); - b.iter(|| client.runtime_api().benchmark_direct_call(&block_id).unwrap()) + c.bench_function("calling function", |b| { + let client = TestClientBuilder::new().build(); + let best_hash = client.chain_info().best_hash; + b.iter(|| client.runtime_api().benchmark_direct_call(best_hash).unwrap()) }); } diff --git a/primitives/api/test/tests/decl_and_impl.rs b/primitives/api/test/tests/decl_and_impl.rs index 42628830cc7f..274f80bd1b46 100644 --- a/primitives/api/test/tests/decl_and_impl.rs +++ b/primitives/api/test/tests/decl_and_impl.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,18 +18,13 @@ use sp_api::{ decl_runtime_apis, impl_runtime_apis, mock_impl_runtime_apis, ApiError, ApiExt, RuntimeApiInfo, }; -use sp_runtime::{ - generic::BlockId, - traits::{Block as BlockT, GetNodeBlockType}, -}; -use substrate_test_runtime_client::runtime::Block; +use sp_runtime::traits::Block as BlockT; + +use substrate_test_runtime_client::runtime::{Block, Hash}; -/// The declaration of the `Runtime` type and the implementation of the `GetNodeBlockType` -/// trait are done by the `construct_runtime!` macro in a real runtime. +/// The declaration of the `Runtime` type is done by the `construct_runtime!` macro in a real +/// runtime. pub struct Runtime {} -impl GetNodeBlockType for Runtime { - type NodeBlock = Block; -} decl_runtime_apis! { pub trait Api { @@ -119,13 +114,13 @@ mock_impl_runtime_apis! { } #[advanced] - fn same_name(_: &BlockId) -> Result<(), ApiError> { + fn same_name(_: ::Hash) -> Result<(), ApiError> { Ok(().into()) } #[advanced] - fn wild_card(at: &BlockId, _: u32) -> Result<(), ApiError> { - if let BlockId::Number(1337) = at { + fn wild_card(at: ::Hash, _: u32) -> Result<(), ApiError> { + if Hash::repeat_byte(0x0f) == at { // yeah Ok(().into()) } else { @@ -150,35 +145,35 @@ type TestClient = substrate_test_runtime_client::client::Client< fn test_client_side_function_signature() { let _test: fn( &RuntimeApiImpl, - &BlockId, + ::Hash, u64, ) -> Result<(), ApiError> = RuntimeApiImpl::::test; let _something_with_block: fn( &RuntimeApiImpl, - &BlockId, + ::Hash, Block, ) -> Result = RuntimeApiImpl::::something_with_block; #[allow(deprecated)] let _same_name_before_version_2: fn( &RuntimeApiImpl, - &BlockId, + ::Hash, ) -> Result = RuntimeApiImpl::::same_name_before_version_2; } #[test] fn check_runtime_api_info() { - assert_eq!(&>::ID, &runtime_decl_for_Api::ID); - assert_eq!(>::VERSION, runtime_decl_for_Api::VERSION); + assert_eq!(&>::ID, &runtime_decl_for_api::ID); + assert_eq!(>::VERSION, runtime_decl_for_api::VERSION); assert_eq!(>::VERSION, 1); assert_eq!( >::VERSION, - runtime_decl_for_ApiWithCustomVersion::VERSION, + runtime_decl_for_api_with_custom_version::VERSION, ); assert_eq!( &>::ID, - &runtime_decl_for_ApiWithCustomVersion::ID, + &runtime_decl_for_api_with_custom_version::ID, ); assert_eq!(>::VERSION, 2); @@ -204,8 +199,8 @@ fn check_runtime_api_versions() { fn mock_runtime_api_has_api() { let mock = MockApi { block: None }; - assert!(mock.has_api::>(&BlockId::Number(0)).unwrap()); - assert!(mock.has_api::>(&BlockId::Number(0)).unwrap()); + assert!(mock.has_api::>(Hash::default()).unwrap()); + assert!(mock.has_api::>(Hash::default()).unwrap()); } #[test] @@ -214,17 +209,17 @@ fn mock_runtime_api_panics_on_calling_old_version() { let mock = MockApi { block: None }; #[allow(deprecated)] - let _ = mock.same_name_before_version_2(&BlockId::Number(0)); + let _ = mock.same_name_before_version_2(Hash::default()); } #[test] fn mock_runtime_api_works_with_advanced() { let mock = MockApi { block: None }; - Api::::same_name(&mock, &BlockId::Number(0)).unwrap(); - mock.wild_card(&BlockId::Number(1337), 1).unwrap(); + Api::::same_name(&mock, Hash::default()).unwrap(); + mock.wild_card(Hash::repeat_byte(0x0f), 1).unwrap(); assert_eq!( "Test error".to_string(), - mock.wild_card(&BlockId::Number(1336), 1).unwrap_err().to_string(), + mock.wild_card(Hash::repeat_byte(0x01), 1).unwrap_err().to_string(), ); } diff --git a/primitives/api/test/tests/runtime_calls.rs b/primitives/api/test/tests/runtime_calls.rs index 2ac88c7e6c04..92118c16cddd 100644 --- a/primitives/api/test/tests/runtime_calls.rs +++ b/primitives/api/test/tests/runtime_calls.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,120 +15,60 @@ // See the License for the specific language governing permissions and // limitations under the License. -use sp_api::{Core, ProvideRuntimeApi}; +use std::panic::UnwindSafe; + +use sp_api::{ApiExt, Core, ProvideRuntimeApi}; use sp_runtime::{ - generic::BlockId, traits::{HashFor, Header as HeaderT}, + TransactionOutcome, }; -use sp_state_machine::{ - create_proof_check_backend, execution_proof_check_on_trie_backend, ExecutionStrategy, -}; +use sp_state_machine::{create_proof_check_backend, execution_proof_check_on_trie_backend}; + use substrate_test_runtime_client::{ prelude::*, runtime::{Block, Header, TestAPI, Transfer}, - DefaultTestClientBuilderExt, TestClientBuilder, + DefaultTestClientBuilderExt, TestClient, TestClientBuilder, }; use codec::Encode; use sc_block_builder::BlockBuilderProvider; use sp_consensus::SelectChain; - -fn calling_function_with_strat(strat: ExecutionStrategy) { - let client = TestClientBuilder::new().set_execution_strategy(strat).build(); - let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); - - assert_eq!(runtime_api.benchmark_add_one(&block_id, &1).unwrap(), 2); -} - -#[test] -fn calling_native_runtime_function() { - calling_function_with_strat(ExecutionStrategy::NativeWhenPossible); -} - -#[test] -fn calling_wasm_runtime_function() { - calling_function_with_strat(ExecutionStrategy::AlwaysWasm); -} +use substrate_test_runtime_client::sc_executor::WasmExecutor; #[test] -fn calling_native_runtime_signature_changed_function() { - let client = TestClientBuilder::new() - .set_execution_strategy(ExecutionStrategy::NativeWhenPossible) - .build(); - let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); - - assert_eq!(runtime_api.function_signature_changed(&block_id).unwrap(), 1); -} - -#[test] -fn calling_wasm_runtime_signature_changed_old_function() { - let client = TestClientBuilder::new() - .set_execution_strategy(ExecutionStrategy::AlwaysWasm) - .build(); +fn calling_runtime_function() { + let client = TestClientBuilder::new().build(); let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); + let best_hash = client.chain_info().best_hash; - #[allow(deprecated)] - let res = runtime_api.function_signature_changed_before_version_2(&block_id).unwrap(); - assert_eq!(&res, &[1, 2]); + assert_eq!(runtime_api.benchmark_add_one(best_hash, &1).unwrap(), 2); } #[test] -fn calling_with_both_strategy_and_fail_on_wasm_should_return_error() { - let client = TestClientBuilder::new().set_execution_strategy(ExecutionStrategy::Both).build(); - let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); - assert!(runtime_api.fail_on_wasm(&block_id).is_err()); -} - -#[test] -fn calling_with_both_strategy_and_fail_on_native_should_work() { - let client = TestClientBuilder::new().set_execution_strategy(ExecutionStrategy::Both).build(); - let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); - assert_eq!(runtime_api.fail_on_native(&block_id).unwrap(), 1); -} - -#[test] -fn calling_with_native_else_wasm_and_fail_on_wasm_should_work() { - let client = TestClientBuilder::new() - .set_execution_strategy(ExecutionStrategy::NativeElseWasm) - .build(); +fn calling_native_runtime_signature_changed_function() { + let client = TestClientBuilder::new().build(); let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); - assert_eq!(runtime_api.fail_on_wasm(&block_id).unwrap(), 1); -} + let best_hash = client.chain_info().best_hash; -#[test] -fn calling_with_native_else_wasm_and_fail_on_native_should_work() { - let client = TestClientBuilder::new() - .set_execution_strategy(ExecutionStrategy::NativeElseWasm) - .build(); - let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); - assert_eq!(runtime_api.fail_on_native(&block_id).unwrap(), 1); + assert_eq!(runtime_api.function_signature_changed(best_hash).unwrap(), 1); } #[test] fn use_trie_function() { - let client = TestClientBuilder::new() - .set_execution_strategy(ExecutionStrategy::AlwaysWasm) - .build(); + let client = TestClientBuilder::new().build(); let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); - assert_eq!(runtime_api.use_trie(&block_id).unwrap(), 2); + let best_hash = client.chain_info().best_hash; + assert_eq!(runtime_api.use_trie(best_hash).unwrap(), 2); } #[test] fn initialize_block_works() { - let client = TestClientBuilder::new().set_execution_strategy(ExecutionStrategy::Both).build(); + let client = TestClientBuilder::new().build(); let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(client.chain_info().best_number); + let best_hash = client.chain_info().best_hash; runtime_api .initialize_block( - &block_id, + best_hash, &Header::new( 1, Default::default(), @@ -138,22 +78,19 @@ fn initialize_block_works() { ), ) .unwrap(); - assert_eq!(runtime_api.get_block_number(&block_id).unwrap(), 1); + assert_eq!(runtime_api.get_block_number(best_hash).unwrap(), 1); } #[test] fn record_proof_works() { - let (client, longest_chain) = TestClientBuilder::new() - .set_execution_strategy(ExecutionStrategy::Both) - .build_with_longest_chain(); + let (client, longest_chain) = TestClientBuilder::new().build_with_longest_chain(); - let block_id = BlockId::Number(client.chain_info().best_number); let storage_root = *futures::executor::block_on(longest_chain.best_chain()).unwrap().state_root(); let runtime_code = sp_core::traits::RuntimeCode { code_fetcher: &sp_core::traits::WrappedRuntimeCode( - client.code_at(&block_id).unwrap().into(), + client.code_at(client.chain_info().best_hash).unwrap().into(), ), hash: vec![1], heap_pages: None, @@ -165,11 +102,11 @@ fn record_proof_works() { from: AccountKeyring::Alice.into(), to: AccountKeyring::Bob.into(), } - .into_signed_tx(); + .into_unchecked_extrinsic(); // Build the block and record proof let mut builder = client - .new_block_at(&block_id, Default::default(), true) + .new_block_at(client.chain_info().best_hash, Default::default(), true) .expect("Creates block builder"); builder.push(transaction.clone()).unwrap(); let (block, _, proof) = builder.build().expect("Bake block").into_inner(); @@ -182,17 +119,13 @@ fn record_proof_works() { // Use the proof backend to execute `execute_block`. let mut overlay = Default::default(); - let executor = NativeElseWasmExecutor::::new( - WasmExecutionMethod::Interpreted, - None, - 8, - 2, + let executor = NativeElseWasmExecutor::::new_with_wasm_executor( + WasmExecutor::builder().build(), ); execution_proof_check_on_trie_backend( &backend, &mut overlay, &executor, - sp_core::testing::TaskExecutor::new(), "Core_execute_block", &block.encode(), &runtime_code, @@ -202,13 +135,13 @@ fn record_proof_works() { #[test] fn call_runtime_api_with_multiple_arguments() { - let client = TestClientBuilder::new().set_execution_strategy(ExecutionStrategy::Both).build(); + let client = TestClientBuilder::new().build(); let data = vec![1, 2, 4, 5, 6, 7, 8, 8, 10, 12]; - let block_id = BlockId::Number(client.chain_info().best_number); + let best_hash = client.chain_info().best_hash; client .runtime_api() - .test_multiple_arguments(&block_id, data.clone(), data.clone(), data.len() as u32) + .test_multiple_arguments(best_hash, data.clone(), data.clone(), data.len() as u32) .unwrap(); } @@ -217,16 +150,16 @@ fn disable_logging_works() { if std::env::var("RUN_TEST").is_ok() { sp_tracing::try_init_simple(); - let mut builder = - TestClientBuilder::new().set_execution_strategy(ExecutionStrategy::AlwaysWasm); + let mut builder = TestClientBuilder::new(); builder.genesis_init_mut().set_wasm_code( substrate_test_runtime_client::runtime::wasm_binary_logging_disabled_unwrap().to_vec(), ); let client = builder.build(); let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(0); - runtime_api.do_trace_log(&block_id).expect("Logging should not fail"); + runtime_api + .do_trace_log(client.chain_info().genesis_hash) + .expect("Logging should not fail"); log::error!("Logging from native works"); } else { let executable = std::env::current_exe().unwrap(); @@ -242,3 +175,46 @@ fn disable_logging_works() { assert!(output.contains("Logging from native works")); } } + +// Certain logic like the transaction handling is not unwind safe. +// +// Ensure that the type is not unwind safe! +static_assertions::assert_not_impl_any!(>::Api: UnwindSafe); + +#[test] +fn ensure_transactional_works() { + const KEY: &[u8] = b"test"; + + let client = TestClientBuilder::new().build(); + let best_hash = client.chain_info().best_hash; + + let runtime_api = client.runtime_api(); + runtime_api.execute_in_transaction(|api| { + api.write_key_value(best_hash, KEY.to_vec(), vec![1, 2, 3], false).unwrap(); + + api.execute_in_transaction(|api| { + api.write_key_value(best_hash, KEY.to_vec(), vec![1, 2, 3, 4], false).unwrap(); + + TransactionOutcome::Commit(()) + }); + + TransactionOutcome::Commit(()) + }); + + let changes = runtime_api + .into_storage_changes(&client.state_at(best_hash).unwrap(), best_hash) + .unwrap(); + assert_eq!(changes.main_storage_changes[0].1, Some(vec![1, 2, 3, 4])); + + let runtime_api = client.runtime_api(); + runtime_api.execute_in_transaction(|api| { + assert!(api.write_key_value(best_hash, KEY.to_vec(), vec![1, 2, 3], true).is_err()); + + TransactionOutcome::Commit(()) + }); + + let changes = runtime_api + .into_storage_changes(&client.state_at(best_hash).unwrap(), best_hash) + .unwrap(); + assert_eq!(changes.main_storage_changes[0].1, Some(vec![1, 2, 3])); +} diff --git a/primitives/api/test/tests/trybuild.rs b/primitives/api/test/tests/trybuild.rs index 13af1ded7dc6..b0a334eb7a22 100644 --- a/primitives/api/test/tests/trybuild.rs +++ b/primitives/api/test/tests/trybuild.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/api/test/tests/ui/changed_in_no_default_method.rs b/primitives/api/test/tests/ui/changed_in_no_default_method.rs index 6af183a4cde9..a0bb4e2830ca 100644 --- a/primitives/api/test/tests/ui/changed_in_no_default_method.rs +++ b/primitives/api/test/tests/ui/changed_in_no_default_method.rs @@ -1,12 +1,6 @@ -use sp_runtime::traits::GetNodeBlockType; -use substrate_test_runtime_client::runtime::Block; - -/// The declaration of the `Runtime` type and the implementation of the `GetNodeBlockType` -/// trait are done by the `construct_runtime!` macro in a real runtime. +/// The declaration of the `Runtime` type is done by the `construct_runtime!` macro in a real +/// runtime. struct Runtime {} -impl GetNodeBlockType for Runtime { - type NodeBlock = Block; -} sp_api::decl_runtime_apis! { #[api_version(2)] diff --git a/primitives/api/test/tests/ui/changed_in_no_default_method.stderr b/primitives/api/test/tests/ui/changed_in_no_default_method.stderr index 096b1091e6f4..2140703a5d2c 100644 --- a/primitives/api/test/tests/ui/changed_in_no_default_method.stderr +++ b/primitives/api/test/tests/ui/changed_in_no_default_method.stderr @@ -1,6 +1,6 @@ error: There is no 'default' method with this name (without `changed_in` attribute). The 'default' method is used to call into the latest implementation. - --> tests/ui/changed_in_no_default_method.rs:15:6 - | -15 | fn test(data: u64); - | ^^^^ + --> tests/ui/changed_in_no_default_method.rs:9:6 + | +9 | fn test(data: u64); + | ^^^^ diff --git a/primitives/api/test/tests/ui/changed_in_unknown_version.rs b/primitives/api/test/tests/ui/changed_in_unknown_version.rs index 151f3e5f4d63..164b91d19422 100644 --- a/primitives/api/test/tests/ui/changed_in_unknown_version.rs +++ b/primitives/api/test/tests/ui/changed_in_unknown_version.rs @@ -1,12 +1,6 @@ -use sp_runtime::traits::GetNodeBlockType; -use substrate_test_runtime_client::runtime::Block; - -/// The declaration of the `Runtime` type and the implementation of the `GetNodeBlockType` -/// trait are done by the `construct_runtime!` macro in a real runtime. +/// The declaration of the `Runtime` type is done by the `construct_runtime!` macro in a real +/// runtime. struct Runtime {} -impl GetNodeBlockType for Runtime { - type NodeBlock = Block; -} sp_api::decl_runtime_apis! { pub trait Api { diff --git a/primitives/api/test/tests/ui/changed_in_unknown_version.stderr b/primitives/api/test/tests/ui/changed_in_unknown_version.stderr index cf03ee4530ab..d4a03bab552e 100644 --- a/primitives/api/test/tests/ui/changed_in_unknown_version.stderr +++ b/primitives/api/test/tests/ui/changed_in_unknown_version.stderr @@ -1,5 +1,5 @@ error: `changed_in` version can not be greater than the `api_version` - --> $DIR/changed_in_unknown_version.rs:14:3 - | -14 | fn test(data: u64); - | ^^ + --> tests/ui/changed_in_unknown_version.rs:8:3 + | +8 | fn test(data: u64); + | ^^ diff --git a/primitives/api/test/tests/ui/empty_impl_runtime_apis_call.rs b/primitives/api/test/tests/ui/empty_impl_runtime_apis_call.rs index 93343fb72ab5..68d84d97fa8e 100644 --- a/primitives/api/test/tests/ui/empty_impl_runtime_apis_call.rs +++ b/primitives/api/test/tests/ui/empty_impl_runtime_apis_call.rs @@ -1,12 +1,6 @@ -use sp_runtime::traits::GetNodeBlockType; -use substrate_test_runtime_client::runtime::Block; - -/// The declaration of the `Runtime` type and the implementation of the `GetNodeBlockType` -/// trait are done by the `construct_runtime!` macro in a real runtime. +/// The declaration of the `Runtime` type is done by the `construct_runtime!` macro in a real +/// runtime. struct Runtime {} -impl GetNodeBlockType for Runtime { - type NodeBlock = Block; -} sp_api::decl_runtime_apis! { pub trait Api { diff --git a/primitives/api/test/tests/ui/empty_impl_runtime_apis_call.stderr b/primitives/api/test/tests/ui/empty_impl_runtime_apis_call.stderr index bf201e8b55a7..96ec09a18554 100644 --- a/primitives/api/test/tests/ui/empty_impl_runtime_apis_call.stderr +++ b/primitives/api/test/tests/ui/empty_impl_runtime_apis_call.stderr @@ -1,7 +1,7 @@ error: No api implementation given! - --> $DIR/empty_impl_runtime_apis_call.rs:17:1 + --> tests/ui/empty_impl_runtime_apis_call.rs:11:1 | -17 | sp_api::impl_runtime_apis! {} +11 | sp_api::impl_runtime_apis! {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in the macro `sp_api::impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/primitives/api/test/tests/ui/impl_incorrect_method_signature.rs b/primitives/api/test/tests/ui/impl_incorrect_method_signature.rs index 19dfdda83623..32501be7855c 100644 --- a/primitives/api/test/tests/ui/impl_incorrect_method_signature.rs +++ b/primitives/api/test/tests/ui/impl_incorrect_method_signature.rs @@ -1,12 +1,9 @@ -use sp_runtime::traits::{GetNodeBlockType, Block as BlockT}; +use sp_runtime::traits::Block as BlockT; use substrate_test_runtime_client::runtime::Block; -/// The declaration of the `Runtime` type and the implementation of the `GetNodeBlockType` -/// trait are done by the `construct_runtime!` macro in a real runtime. +/// The declaration of the `Runtime` type is done by the `construct_runtime!` macro in a real +/// runtime. struct Runtime {} -impl GetNodeBlockType for Runtime { - type NodeBlock = Block; -} sp_api::decl_runtime_apis! { pub trait Api { diff --git a/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr b/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr index f5b6ac1da457..2324be85be4f 100644 --- a/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr +++ b/primitives/api/test/tests/ui/impl_incorrect_method_signature.stderr @@ -1,35 +1,35 @@ error[E0053]: method `test` has an incompatible type for trait - --> tests/ui/impl_incorrect_method_signature.rs:19:17 + --> tests/ui/impl_incorrect_method_signature.rs:16:17 | -19 | fn test(data: String) {} +16 | fn test(data: String) {} | ^^^^^^ | | - | expected `u64`, found struct `std::string::String` + | expected `u64`, found `std::string::String` | help: change the parameter type to match the trait: `u64` | note: type in trait - --> tests/ui/impl_incorrect_method_signature.rs:13:17 + --> tests/ui/impl_incorrect_method_signature.rs:10:17 | -13 | fn test(data: u64); +10 | fn test(data: u64); | ^^^ - = note: expected fn pointer `fn(u64)` - found fn pointer `fn(std::string::String)` + = note: expected signature `fn(u64)` + found signature `fn(std::string::String)` error[E0308]: mismatched types - --> tests/ui/impl_incorrect_method_signature.rs:19:11 + --> tests/ui/impl_incorrect_method_signature.rs:16:11 | -17 | / sp_api::impl_runtime_apis! { -18 | | impl self::Api for Runtime { -19 | | fn test(data: String) {} - | | ^^^^ expected `u64`, found struct `std::string::String` -20 | | } +14 | / sp_api::impl_runtime_apis! { +15 | | impl self::Api for Runtime { +16 | | fn test(data: String) {} + | | ^^^^ expected `u64`, found `String` +17 | | } ... | -32 | | } -33 | | } +29 | | } +30 | | } | |_- arguments to this function are incorrect | note: associated function defined here - --> tests/ui/impl_incorrect_method_signature.rs:13:6 + --> tests/ui/impl_incorrect_method_signature.rs:10:6 | -13 | fn test(data: u64); +10 | fn test(data: u64); | ^^^^ diff --git a/primitives/api/test/tests/ui/impl_missing_version.rs b/primitives/api/test/tests/ui/impl_missing_version.rs index 63e0599622ac..8fd40a400922 100644 --- a/primitives/api/test/tests/ui/impl_missing_version.rs +++ b/primitives/api/test/tests/ui/impl_missing_version.rs @@ -1,10 +1,7 @@ -use sp_runtime::traits::{Block as BlockT, GetNodeBlockType}; +use sp_runtime::traits::Block as BlockT; use substrate_test_runtime_client::runtime::Block; struct Runtime {} -impl GetNodeBlockType for Runtime { - type NodeBlock = Block; -} sp_api::decl_runtime_apis! { #[api_version(2)] diff --git a/primitives/api/test/tests/ui/impl_missing_version.stderr b/primitives/api/test/tests/ui/impl_missing_version.stderr index c0abeffe0ccc..770543aa8875 100644 --- a/primitives/api/test/tests/ui/impl_missing_version.stderr +++ b/primitives/api/test/tests/ui/impl_missing_version.stderr @@ -1,14 +1,8 @@ -error[E0433]: failed to resolve: could not find `ApiV4` in `runtime_decl_for_Api` - --> tests/ui/impl_missing_version.rs:21:13 +error[E0405]: cannot find trait `ApiV4` in module `self::runtime_decl_for_api` + --> tests/ui/impl_missing_version.rs:18:13 | -21 | impl self::Api for Runtime { - | ^^^ could not find `ApiV4` in `runtime_decl_for_Api` - -error[E0405]: cannot find trait `ApiV4` in module `self::runtime_decl_for_Api` - --> tests/ui/impl_missing_version.rs:21:13 - | -11 | pub trait Api { +8 | pub trait Api { | ------------- similarly named trait `ApiV2` defined here ... -21 | impl self::Api for Runtime { +18 | impl self::Api for Runtime { | ^^^ help: a trait with a similar name exists: `ApiV2` diff --git a/primitives/api/test/tests/ui/impl_two_traits_with_same_name.rs b/primitives/api/test/tests/ui/impl_two_traits_with_same_name.rs index 76555a825dc8..cb8f2f493d74 100644 --- a/primitives/api/test/tests/ui/impl_two_traits_with_same_name.rs +++ b/primitives/api/test/tests/ui/impl_two_traits_with_same_name.rs @@ -1,12 +1,6 @@ -use sp_runtime::traits::GetNodeBlockType; -use substrate_test_runtime_client::runtime::Block; - -/// The declaration of the `Runtime` type and the implementation of the `GetNodeBlockType` -/// trait are done by the `construct_runtime!` macro in a real runtime. +/// The declaration of the `Runtime` type is done by the `construct_runtime!` macro in a real +/// runtime. struct Runtime {} -impl GetNodeBlockType for Runtime { - type NodeBlock = Block; -} sp_api::decl_runtime_apis! { pub trait Api { diff --git a/primitives/api/test/tests/ui/impl_two_traits_with_same_name.stderr b/primitives/api/test/tests/ui/impl_two_traits_with_same_name.stderr index 17ee56d409a6..a41f59f36b1f 100644 --- a/primitives/api/test/tests/ui/impl_two_traits_with_same_name.stderr +++ b/primitives/api/test/tests/ui/impl_two_traits_with_same_name.stderr @@ -1,5 +1,5 @@ error: Two traits with the same name detected! The trait name is used to generate its ID. Please rename one trait at the declaration! - --> $DIR/impl_two_traits_with_same_name.rs:30:15 + --> tests/ui/impl_two_traits_with_same_name.rs:24:15 | -30 | impl second::Api for Runtime { +24 | impl second::Api for Runtime { | ^^^ diff --git a/primitives/api/test/tests/ui/missing_block_generic_parameter.rs b/primitives/api/test/tests/ui/missing_block_generic_parameter.rs index e194fdbf4ab6..b69505bfeb09 100644 --- a/primitives/api/test/tests/ui/missing_block_generic_parameter.rs +++ b/primitives/api/test/tests/ui/missing_block_generic_parameter.rs @@ -1,12 +1,6 @@ -use sp_runtime::traits::GetNodeBlockType; -use substrate_test_runtime_client::runtime::Block; - -/// The declaration of the `Runtime` type and the implementation of the `GetNodeBlockType` -/// trait are done by the `construct_runtime!` macro in a real runtime. +/// The declaration of the `Runtime` type is done by the `construct_runtime!` macro in a real +/// runtime. struct Runtime {} -impl GetNodeBlockType for Runtime { - type NodeBlock = Block; -} sp_api::decl_runtime_apis! { pub trait Api { diff --git a/primitives/api/test/tests/ui/missing_block_generic_parameter.stderr b/primitives/api/test/tests/ui/missing_block_generic_parameter.stderr index d626eda2496f..5dc2b993bb1a 100644 --- a/primitives/api/test/tests/ui/missing_block_generic_parameter.stderr +++ b/primitives/api/test/tests/ui/missing_block_generic_parameter.stderr @@ -1,5 +1,5 @@ error: Missing `Block` generic parameter. - --> $DIR/missing_block_generic_parameter.rs:18:13 + --> tests/ui/missing_block_generic_parameter.rs:12:13 | -18 | impl self::Api for Runtime { +12 | impl self::Api for Runtime { | ^^^ diff --git a/primitives/api/test/tests/ui/missing_path_for_trait.rs b/primitives/api/test/tests/ui/missing_path_for_trait.rs index d7540ce88a8a..e47bca1c3f6c 100644 --- a/primitives/api/test/tests/ui/missing_path_for_trait.rs +++ b/primitives/api/test/tests/ui/missing_path_for_trait.rs @@ -1,12 +1,6 @@ -use sp_runtime::traits::GetNodeBlockType; -use substrate_test_runtime_client::runtime::Block; - -/// The declaration of the `Runtime` type and the implementation of the `GetNodeBlockType` -/// trait are done by the `construct_runtime!` macro in a real runtime. +/// The declaration of the `Runtime` type is done by the `construct_runtime!` macro in a real +/// runtime. struct Runtime {} -impl GetNodeBlockType for Runtime { - type NodeBlock = Block; -} sp_api::decl_runtime_apis! { pub trait Api { diff --git a/primitives/api/test/tests/ui/missing_path_for_trait.stderr b/primitives/api/test/tests/ui/missing_path_for_trait.stderr index 729ff0bad18d..cca993501979 100644 --- a/primitives/api/test/tests/ui/missing_path_for_trait.stderr +++ b/primitives/api/test/tests/ui/missing_path_for_trait.stderr @@ -1,5 +1,5 @@ error: The implemented trait has to be referenced with a path, e.g. `impl client::Core for Runtime`. - --> $DIR/missing_path_for_trait.rs:18:7 + --> tests/ui/missing_path_for_trait.rs:12:7 | -18 | impl Api for Runtime { +12 | impl Api for Runtime { | ^^^ diff --git a/primitives/api/test/tests/ui/missing_versioned_method.rs b/primitives/api/test/tests/ui/missing_versioned_method.rs index d973a94c2101..919cef055fe6 100644 --- a/primitives/api/test/tests/ui/missing_versioned_method.rs +++ b/primitives/api/test/tests/ui/missing_versioned_method.rs @@ -1,10 +1,7 @@ -use sp_runtime::traits::{Block as BlockT, GetNodeBlockType}; +use sp_runtime::traits::Block as BlockT; use substrate_test_runtime_client::runtime::Block; struct Runtime {} -impl GetNodeBlockType for Runtime { - type NodeBlock = Block; -} sp_api::decl_runtime_apis! { #[api_version(2)] diff --git a/primitives/api/test/tests/ui/missing_versioned_method.stderr b/primitives/api/test/tests/ui/missing_versioned_method.stderr index e3ace7979c27..b88d903212df 100644 --- a/primitives/api/test/tests/ui/missing_versioned_method.stderr +++ b/primitives/api/test/tests/ui/missing_versioned_method.stderr @@ -1,8 +1,8 @@ error[E0046]: not all trait items implemented, missing: `test3` - --> tests/ui/missing_versioned_method.rs:21:2 + --> tests/ui/missing_versioned_method.rs:18:2 | -15 | fn test3(); +12 | fn test3(); | ----------- `test3` from trait ... -21 | impl self::Api for Runtime { +18 | impl self::Api for Runtime { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `test3` in implementation diff --git a/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs b/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs index 72358b99164d..036bba417f57 100644 --- a/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs +++ b/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.rs @@ -1,10 +1,7 @@ -use sp_runtime::traits::{Block as BlockT, GetNodeBlockType}; +use sp_runtime::traits::Block as BlockT; use substrate_test_runtime_client::runtime::Block; struct Runtime {} -impl GetNodeBlockType for Runtime { - type NodeBlock = Block; -} sp_api::decl_runtime_apis! { #[api_version(2)] diff --git a/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.stderr b/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.stderr index 7354fbd537fd..4afa6856a581 100644 --- a/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.stderr +++ b/primitives/api/test/tests/ui/missing_versioned_method_multiple_vers.stderr @@ -1,8 +1,8 @@ error[E0046]: not all trait items implemented, missing: `test3` - --> tests/ui/missing_versioned_method_multiple_vers.rs:23:2 + --> tests/ui/missing_versioned_method_multiple_vers.rs:20:2 | -15 | fn test3(); +12 | fn test3(); | ----------- `test3` from trait ... -23 | impl self::Api for Runtime { +20 | impl self::Api for Runtime { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ missing `test3` in implementation diff --git a/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.rs b/primitives/api/test/tests/ui/mock_advanced_hash_by_reference.rs similarity index 81% rename from primitives/api/test/tests/ui/mock_advanced_block_id_by_value.rs rename to primitives/api/test/tests/ui/mock_advanced_hash_by_reference.rs index aeef40f4c77d..45e6adb2fcf9 100644 --- a/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.rs +++ b/primitives/api/test/tests/ui/mock_advanced_hash_by_reference.rs @@ -12,7 +12,7 @@ struct MockApi; sp_api::mock_impl_runtime_apis! { impl Api for MockApi { #[advanced] - fn test(&self, _: BlockId) -> Result<(), ApiError> { + fn test(&self, _: &Hash) -> Result<(), ApiError> { Ok(().into()) } } diff --git a/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.stderr b/primitives/api/test/tests/ui/mock_advanced_hash_by_reference.stderr similarity index 60% rename from primitives/api/test/tests/ui/mock_advanced_block_id_by_value.stderr rename to primitives/api/test/tests/ui/mock_advanced_hash_by_reference.stderr index 3b3c4e94c312..234331c9749b 100644 --- a/primitives/api/test/tests/ui/mock_advanced_block_id_by_value.stderr +++ b/primitives/api/test/tests/ui/mock_advanced_hash_by_reference.stderr @@ -1,10 +1,10 @@ -error: `BlockId` needs to be taken by reference and not by value! - --> tests/ui/mock_advanced_block_id_by_value.rs:12:1 +error: `Hash` needs to be taken by value and not by reference! + --> tests/ui/mock_advanced_hash_by_reference.rs:12:1 | 12 | / sp_api::mock_impl_runtime_apis! { 13 | | impl Api for MockApi { 14 | | #[advanced] -15 | | fn test(&self, _: BlockId) -> Result<(), ApiError> { +15 | | fn test(&self, _: &Hash) -> Result<(), ApiError> { ... | 18 | | } 19 | | } diff --git a/primitives/api/test/tests/ui/mock_advanced_missing_blockid.rs b/primitives/api/test/tests/ui/mock_advanced_missing_hash.rs similarity index 100% rename from primitives/api/test/tests/ui/mock_advanced_missing_blockid.rs rename to primitives/api/test/tests/ui/mock_advanced_missing_hash.rs diff --git a/primitives/api/test/tests/ui/mock_advanced_missing_blockid.stderr b/primitives/api/test/tests/ui/mock_advanced_missing_hash.stderr similarity index 56% rename from primitives/api/test/tests/ui/mock_advanced_missing_blockid.stderr rename to primitives/api/test/tests/ui/mock_advanced_missing_hash.stderr index b9ce7324b5ca..48a94a00beae 100644 --- a/primitives/api/test/tests/ui/mock_advanced_missing_blockid.stderr +++ b/primitives/api/test/tests/ui/mock_advanced_missing_hash.stderr @@ -1,5 +1,5 @@ -error: If using the `advanced` attribute, it is required that the function takes at least one argument, the `BlockId`. - --> tests/ui/mock_advanced_missing_blockid.rs:15:3 +error: If using the `advanced` attribute, it is required that the function takes at least one argument, the `Hash`. + --> tests/ui/mock_advanced_missing_hash.rs:15:3 | 15 | fn test(&self) -> Result<(), ApiError> { | ^^ diff --git a/primitives/api/test/tests/ui/mock_only_self_reference.stderr b/primitives/api/test/tests/ui/mock_only_self_reference.stderr index 430f63eee166..f088e8f2de59 100644 --- a/primitives/api/test/tests/ui/mock_only_self_reference.stderr +++ b/primitives/api/test/tests/ui/mock_only_self_reference.stderr @@ -48,42 +48,3 @@ error[E0050]: method `test2` has 2 parameters but the declaration in trait `Api: | |_^ expected 3 parameters, found 2 | = note: this error originates in the macro `sp_api::mock_impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0050]: method `test_with_context` has 3 parameters but the declaration in trait `Api::test_with_context` has 4 - --> tests/ui/mock_only_self_reference.rs:12:1 - | -3 | / sp_api::decl_runtime_apis! { -4 | | pub trait Api { -5 | | fn test(data: u64); - | |_________________________- trait requires 4 parameters -... -12 | / sp_api::mock_impl_runtime_apis! { -13 | | impl Api for MockApi { -14 | | fn test(self, data: u64) {} -15 | | -16 | | fn test2(&mut self, data: u64) {} -17 | | } -18 | | } - | |_^ expected 4 parameters, found 3 - | - = note: this error originates in the macro `sp_api::mock_impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) - -error[E0050]: method `test2_with_context` has 3 parameters but the declaration in trait `Api::test2_with_context` has 4 - --> tests/ui/mock_only_self_reference.rs:12:1 - | -3 | / sp_api::decl_runtime_apis! { -4 | | pub trait Api { -5 | | fn test(data: u64); -6 | | fn test2(data: u64); - | |__________________________- trait requires 4 parameters -... -12 | / sp_api::mock_impl_runtime_apis! { -13 | | impl Api for MockApi { -14 | | fn test(self, data: u64) {} -15 | | -16 | | fn test2(&mut self, data: u64) {} -17 | | } -18 | | } - | |_^ expected 4 parameters, found 3 - | - = note: this error originates in the macro `sp_api::mock_impl_runtime_apis` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/primitives/api/test/tests/ui/positive_cases/custom_where_bound.rs b/primitives/api/test/tests/ui/positive_cases/custom_where_bound.rs new file mode 100644 index 000000000000..b572a3bc30d5 --- /dev/null +++ b/primitives/api/test/tests/ui/positive_cases/custom_where_bound.rs @@ -0,0 +1,43 @@ +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_runtime::traits::Block as BlockT; +use substrate_test_runtime_client::runtime::Block; + +struct Runtime {} + +pub trait CustomTrait: Encode + Decode + TypeInfo {} + +#[derive(Encode, Decode, TypeInfo)] +pub struct SomeImpl; +impl CustomTrait for SomeImpl {} + +#[derive(Encode, Decode, TypeInfo)] +pub struct SomeOtherType(C); + +sp_api::decl_runtime_apis! { + pub trait Api where A: CustomTrait { + fn test() -> A; + fn test2() -> SomeOtherType; + } +} + +sp_api::impl_runtime_apis! { + impl self::Api for Runtime { + fn test() -> SomeImpl { SomeImpl } + fn test2() -> SomeOtherType { SomeOtherType(SomeImpl) } + } + + impl sp_api::Core for Runtime { + fn version() -> sp_version::RuntimeVersion { + unimplemented!() + } + fn execute_block(_: Block) { + unimplemented!() + } + fn initialize_block(_: &::Header) { + unimplemented!() + } + } +} + +fn main() {} diff --git a/primitives/api/test/tests/ui/positive_cases/default_impls.rs b/primitives/api/test/tests/ui/positive_cases/default_impls.rs index 3434db1089f0..58192feb9eca 100644 --- a/primitives/api/test/tests/ui/positive_cases/default_impls.rs +++ b/primitives/api/test/tests/ui/positive_cases/default_impls.rs @@ -1,10 +1,7 @@ -use sp_runtime::traits::{Block as BlockT, GetNodeBlockType}; +use sp_runtime::traits::Block as BlockT; use substrate_test_runtime_client::runtime::Block; struct Runtime {} -impl GetNodeBlockType for Runtime { - type NodeBlock = Block; -} sp_api::decl_runtime_apis! { #[api_version(2)] diff --git a/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.rs b/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.rs index a82fb9a1595d..14a8fa4d4e0b 100644 --- a/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.rs +++ b/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.rs @@ -1,12 +1,9 @@ -use sp_runtime::traits::{GetNodeBlockType, Block as BlockT}; +use sp_runtime::traits::Block as BlockT; use substrate_test_runtime_client::runtime::Block; -/// The declaration of the `Runtime` type and the implementation of the `GetNodeBlockType` -/// trait are done by the `construct_runtime!` macro in a real runtime. +/// The declaration of the `Runtime` type is done by the `construct_runtime!` macro in a real +/// runtime. struct Runtime {} -impl GetNodeBlockType for Runtime { - type NodeBlock = Block; -} sp_api::decl_runtime_apis! { pub trait Api { diff --git a/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr b/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr index 06f8226ec88b..e9d550f3a3bc 100644 --- a/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr +++ b/primitives/api/test/tests/ui/type_reference_in_impl_runtime_apis_call.stderr @@ -1,39 +1,39 @@ error[E0053]: method `test` has an incompatible type for trait - --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:19:17 + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:16:17 | -19 | fn test(data: &u64) { +16 | fn test(data: &u64) { | ^^^^ | | | expected `u64`, found `&u64` | help: change the parameter type to match the trait: `u64` | note: type in trait - --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:13:17 + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:10:17 | -13 | fn test(data: u64); +10 | fn test(data: u64); | ^^^ - = note: expected fn pointer `fn(u64)` - found fn pointer `fn(&u64)` + = note: expected signature `fn(u64)` + found signature `fn(&u64)` error[E0308]: mismatched types - --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:19:11 + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:16:11 | -17 | / sp_api::impl_runtime_apis! { -18 | | impl self::Api for Runtime { -19 | | fn test(data: &u64) { +14 | / sp_api::impl_runtime_apis! { +15 | | impl self::Api for Runtime { +16 | | fn test(data: &u64) { | | ^^^^^^^ expected `u64`, found `&u64` -20 | | unimplemented!() +17 | | unimplemented!() ... | -34 | | } -35 | | } +31 | | } +32 | | } | |_- arguments to this function are incorrect | note: associated function defined here - --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:13:6 + --> tests/ui/type_reference_in_impl_runtime_apis_call.rs:10:6 | -13 | fn test(data: u64); +10 | fn test(data: u64); | ^^^^ help: consider removing the borrow | -19 | fn test(data: &u64) { +16 | fn test(data: &u64) { | diff --git a/primitives/application-crypto/Cargo.toml b/primitives/application-crypto/Cargo.toml index e7aa118dfd6a..7431fc1fd5ff 100644 --- a/primitives/application-crypto/Cargo.toml +++ b/primitives/application-crypto/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-application-crypto" -version = "7.0.0" +version = "23.0.0" authors = ["Parity Technologies "] edition = "2021" description = "Provides facilities for generating application specific crypto wrapper types." @@ -15,12 +15,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", optional = true, features = ["derive"] } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } -sp-io = { version = "7.0.0", default-features = false, path = "../io" } +sp-core = { version = "21.0.0", default-features = false, path = "../core" } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", default-features = false, optional = true, features = ["derive", "alloc"] } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } +sp-io = { version = "23.0.0", default-features = false, path = "../io" } [features] default = [ "std" ] @@ -29,11 +29,18 @@ std = [ "sp-core/std", "codec/std", "scale-info/std", - "serde", + "serde/std", "sp-std/std", "sp-io/std", ] +# Serde support without relying on std features. +serde = [ + "dep:serde", + "sp-core/serde", + "scale-info/serde", +] + # This feature enables all crypto primitives for `no_std` builds like microcontrollers # or Intel SGX. # For the regular wasm runtime builds this should not be used. @@ -44,3 +51,7 @@ full_crypto = [ "sp-io/disable_panic_handler", "sp-io/disable_oom", ] + +# This feature adds BLS crypto primitives. It should not be used in production since +# the BLS implementation and interface may still be subject to significant change. +bls-experimental = [ "sp-core/bls-experimental" ] diff --git a/primitives/core/src/bounded.rs b/primitives/application-crypto/src/bls377.rs similarity index 63% rename from primitives/core/src/bounded.rs rename to primitives/application-crypto/src/bls377.rs index 45b4a9ca623d..7fbbec546605 100644 --- a/primitives/core/src/bounded.rs +++ b/primitives/application-crypto/src/bls377.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,14 +15,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Bounded collection types. +//! BLS12-377 crypto applications. -pub mod bounded_btree_map; -pub mod bounded_btree_set; -pub mod bounded_vec; -pub mod weak_bounded_vec; +pub use sp_core::bls::bls377::*; -pub use bounded_btree_map::BoundedBTreeMap; -pub use bounded_btree_set::BoundedBTreeSet; -pub use bounded_vec::{BoundedSlice, BoundedVec}; -pub use weak_bounded_vec::WeakBoundedVec; +mod app { + crate::app_crypto!(super, sp_core::testing::BLS377); +} + +#[cfg(feature = "full_crypto")] +pub use app::Pair as AppPair; +pub use app::{Public as AppPublic, Signature as AppSignature}; diff --git a/frame/support/test/tests/decl_storage_ui/config_get_duplicate.rs b/primitives/application-crypto/src/bls381.rs similarity index 60% rename from frame/support/test/tests/decl_storage_ui/config_get_duplicate.rs rename to primitives/application-crypto/src/bls381.rs index 8805effd9e72..d990f2e14c8e 100644 --- a/frame/support/test/tests/decl_storage_ui/config_get_duplicate.rs +++ b/primitives/application-crypto/src/bls381.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,17 +15,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub trait Config: frame_support_test::Config {} +//! BLS12-381 crypto applications. -frame_support::decl_module! { - pub struct Module for enum Call where origin: T::RuntimeOrigin, system=frame_support_test {} -} +pub use sp_core::bls::bls381::*; -frame_support::decl_storage!{ - trait Store for Module as FinalKeysNone { - pub Value get(fn value) config(): u32; - pub Value2 config(value): u32; - } +mod app { + crate::app_crypto!(super, sp_core::testing::BLS381); } -fn main() {} +#[cfg(feature = "full_crypto")] +pub use app::Pair as AppPair; +pub use app::{Public as AppPublic, Signature as AppSignature}; diff --git a/primitives/application-crypto/src/ecdsa.rs b/primitives/application-crypto/src/ecdsa.rs index 6356f54a0b08..27ffe12579f5 100644 --- a/primitives/application-crypto/src/ecdsa.rs +++ b/primitives/application-crypto/src/ecdsa.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,13 +24,7 @@ use sp_std::vec::Vec; pub use sp_core::ecdsa::*; mod app { - use sp_core::testing::ECDSA; - - crate::app_crypto!(super, ECDSA); - - impl crate::traits::BoundToRuntimeAppPublic for Public { - type Public = Self; - } + crate::app_crypto!(super, sp_core::testing::ECDSA); } #[cfg(feature = "full_crypto")] diff --git a/primitives/application-crypto/src/ed25519.rs b/primitives/application-crypto/src/ed25519.rs index 199e55383b88..bc05018370ed 100644 --- a/primitives/application-crypto/src/ed25519.rs +++ b/primitives/application-crypto/src/ed25519.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,13 +24,7 @@ use sp_std::vec::Vec; pub use sp_core::ed25519::*; mod app { - use sp_core::testing::ED25519; - - crate::app_crypto!(super, ED25519); - - impl crate::traits::BoundToRuntimeAppPublic for Public { - type Public = Self; - } + crate::app_crypto!(super, sp_core::testing::ED25519); } #[cfg(feature = "full_crypto")] diff --git a/primitives/application-crypto/src/lib.rs b/primitives/application-crypto/src/lib.rs index 05f89c40ef99..95a25c11a717 100644 --- a/primitives/application-crypto/src/lib.rs +++ b/primitives/application-crypto/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,14 +23,13 @@ pub use sp_core::crypto::{key_types, CryptoTypeId, KeyTypeId}; #[doc(hidden)] #[cfg(feature = "full_crypto")] -pub use sp_core::crypto::{DeriveJunction, Pair, SecretStringError, Ss58Codec}; +pub use sp_core::crypto::{DeriveError, Pair, SecretStringError}; +#[cfg(any(feature = "full_crypto", feature = "serde"))] +pub use sp_core::crypto::{DeriveJunction, Ss58Codec}; #[doc(hidden)] pub use sp_core::{ self, - crypto::{ - ByteArray, CryptoType, CryptoTypePublicPair, Derive, IsWrappedBy, Public, UncheckedFrom, - Wraps, - }, + crypto::{ByteArray, CryptoType, Derive, IsWrappedBy, Public, UncheckedFrom, Wraps}, RuntimeDebug, }; @@ -39,11 +38,15 @@ pub use codec; #[doc(hidden)] pub use scale_info; #[doc(hidden)] -#[cfg(feature = "std")] +#[cfg(feature = "serde")] pub use serde; #[doc(hidden)] pub use sp_std::{ops::Deref, vec::Vec}; +#[cfg(feature = "bls-experimental")] +pub mod bls377; +#[cfg(feature = "bls-experimental")] +pub mod bls381; pub mod ecdsa; pub mod ed25519; pub mod sr25519; @@ -51,14 +54,15 @@ mod traits; pub use traits::*; -/// Declares Public, Pair, Signature types which are functionally equivalent to `$pair`, but are new -/// Application-specific types whose identifier is `$key_type`. +/// Declares `Public`, `Pair` and `Signature` types which are functionally equivalent +/// to the corresponding types defined by `$module` but are new application-specific +/// types whose identifier is `$key_type`. /// /// ```rust -/// # use sp_application_crypto::{app_crypto, wrap, ed25519, KeyTypeId}; -/// // Declare a new set of crypto types using Ed25519 logic that identifies as `KeyTypeId` +/// # use sp_application_crypto::{app_crypto, ed25519, KeyTypeId}; +/// // Declare a new set of crypto types using ed25519 logic that identifies as `KeyTypeId` /// // of value `b"fuba"`. -/// app_crypto!(ed25519, KeyTypeId(*b"_uba")); +/// app_crypto!(ed25519, KeyTypeId(*b"fuba")); /// ``` #[cfg(feature = "full_crypto")] #[macro_export] @@ -81,14 +85,15 @@ macro_rules! app_crypto { }; } -/// Declares Public, Pair, Signature types which are functionally equivalent to `$pair`, but are new -/// Application-specific types whose identifier is `$key_type`. +/// Declares `Public`, `Pair` and `Signature` types which are functionally equivalent +/// to the corresponding types defined by `$module` but that are new application-specific +/// types whose identifier is `$key_type`. /// /// ```rust -/// # use sp_application_crypto::{app_crypto, wrap, ed25519, KeyTypeId}; -/// // Declare a new set of crypto types using Ed25519 logic that identifies as `KeyTypeId` +/// # use sp_application_crypto::{app_crypto, ed25519, KeyTypeId}; +/// // Declare a new set of crypto types using ed25519 logic that identifies as `KeyTypeId` /// // of value `b"fuba"`. -/// app_crypto!(ed25519, KeyTypeId(*b"_uba")); +/// app_crypto!(ed25519, KeyTypeId(*b"fuba")); /// ``` #[cfg(not(feature = "full_crypto"))] #[macro_export] @@ -110,8 +115,8 @@ macro_rules! app_crypto { }; } -/// Declares Pair type which is functionally equivalent to `$pair`, but is new -/// Application-specific type whose identifier is `$key_type`. +/// Declares `Pair` type which is functionally equivalent to `$pair`, but is +/// new application-specific type whose identifier is `$key_type`. #[macro_export] macro_rules! app_crypto_pair { ($pair:ty, $key_type:expr, $crypto_type:expr) => { @@ -129,7 +134,6 @@ macro_rules! app_crypto_pair { type Public = Public; type Seed = <$pair as $crate::Pair>::Seed; type Signature = Signature; - type DeriveError = <$pair as $crate::Pair>::DeriveError; $crate::app_crypto_pair_functions_if_std!($pair); @@ -137,7 +141,7 @@ macro_rules! app_crypto_pair { &self, path: Iter, seed: Option, - ) -> Result<(Self, Option), Self::DeriveError> { + ) -> Result<(Self, Option), $crate::DeriveError> { self.0.derive(path, seed).map(|x| (Self(x.0), x.1)) } fn from_seed(seed: &Self::Seed) -> Self { @@ -156,13 +160,6 @@ macro_rules! app_crypto_pair { ) -> bool { <$pair>::verify(&sig.0, message, pubkey.as_ref()) } - fn verify_weak, M: AsRef<[u8]>>( - sig: &[u8], - message: M, - pubkey: P, - ) -> bool { - <$pair>::verify_weak(sig, message, pubkey) - } fn public(&self) -> Self::Public { Public(self.0.public()) } @@ -171,8 +168,7 @@ macro_rules! app_crypto_pair { } } - impl $crate::AppKey for Pair { - type UntypedGeneric = $pair; + impl $crate::AppCrypto for Pair { type Public = Public; type Pair = Pair; type Signature = Signature; @@ -183,6 +179,13 @@ macro_rules! app_crypto_pair { impl $crate::AppPair for Pair { type Generic = $pair; } + + impl Pair { + /// Convert into wrapped generic key pair type. + pub fn into_inner(self) -> $pair { + self.0 + } + } }; } @@ -213,10 +216,10 @@ macro_rules! app_crypto_pair_functions_if_std { ($pair:ty) => {}; } -/// Declares Public type which is functionally equivalent to `$public`, but is new -/// Application-specific type whose identifier is `$key_type`. -/// can only be used together with `full_crypto` feature -/// For full functionality, app_crypto_public_common! must be called too. +/// Declares `Public` type which is functionally equivalent to `$public` but is +/// new application-specific type whose identifier is `$key_type`. +/// For full functionality, `app_crypto_public_common!` must be called too. +/// Can only be used with `full_crypto` feature. #[doc(hidden)] #[macro_export] macro_rules! app_crypto_public_full_crypto { @@ -239,8 +242,7 @@ macro_rules! app_crypto_public_full_crypto { type Pair = Pair; } - impl $crate::AppKey for Public { - type UntypedGeneric = $public; + impl $crate::AppCrypto for Public { type Public = Public; type Pair = Pair; type Signature = Signature; @@ -250,10 +252,10 @@ macro_rules! app_crypto_public_full_crypto { }; } -/// Declares Public type which is functionally equivalent to `$public`, but is new -/// Application-specific type whose identifier is `$key_type`. -/// can only be used without `full_crypto` feature -/// For full functionality, app_crypto_public_common! must be called too. +/// Declares `Public` type which is functionally equivalent to `$public` but is +/// new application-specific type whose identifier is `$key_type`. +/// For full functionality, `app_crypto_public_common!` must be called too. +/// Can only be used without `full_crypto` feature. #[doc(hidden)] #[macro_export] macro_rules! app_crypto_public_not_full_crypto { @@ -273,8 +275,7 @@ macro_rules! app_crypto_public_not_full_crypto { impl $crate::CryptoType for Public {} - impl $crate::AppKey for Public { - type UntypedGeneric = $public; + impl $crate::AppCrypto for Public { type Public = Public; type Signature = Signature; const ID: $crate::KeyTypeId = $key_type; @@ -283,14 +284,14 @@ macro_rules! app_crypto_public_not_full_crypto { }; } -/// Declares Public type which is functionally equivalent to `$public`, but is new -/// Application-specific type whose identifier is `$key_type`. -/// For full functionality, app_crypto_public_(not)_full_crypto! must be called too. +/// Declares `Public` type which is functionally equivalent to `$public` but is +/// new application-specific type whose identifier is `$key_type`. +/// For full functionality, `app_crypto_public_(not)_full_crypto!` must be called too. #[doc(hidden)] #[macro_export] macro_rules! app_crypto_public_common { ($public:ty, $sig:ty, $key_type:expr, $crypto_type:expr) => { - $crate::app_crypto_public_common_if_std!(); + $crate::app_crypto_public_common_if_serde!(); impl AsRef<[u8]> for Public { fn as_ref(&self) -> &[u8] { @@ -307,62 +308,13 @@ macro_rules! app_crypto_public_common { impl $crate::ByteArray for Public { const LEN: usize = <$public>::LEN; } - impl $crate::Public for Public { - fn to_public_crypto_pair(&self) -> $crate::CryptoTypePublicPair { - $crate::CryptoTypePublicPair($crypto_type, $crate::ByteArray::to_raw_vec(self)) - } - } + + impl $crate::Public for Public {} impl $crate::AppPublic for Public { type Generic = $public; } - impl $crate::RuntimeAppPublic for Public - where - $public: $crate::RuntimePublic, - { - const ID: $crate::KeyTypeId = $key_type; - const CRYPTO_ID: $crate::CryptoTypeId = $crypto_type; - - type Signature = Signature; - - fn all() -> $crate::Vec { - <$public as $crate::RuntimePublic>::all($key_type) - .into_iter() - .map(Self) - .collect() - } - - fn generate_pair(seed: Option<$crate::Vec>) -> Self { - Self(<$public as $crate::RuntimePublic>::generate_pair($key_type, seed)) - } - - fn sign>(&self, msg: &M) -> Option { - <$public as $crate::RuntimePublic>::sign(self.as_ref(), $key_type, msg) - .map(Signature) - } - - fn verify>(&self, msg: &M, signature: &Self::Signature) -> bool { - <$public as $crate::RuntimePublic>::verify(self.as_ref(), msg, &signature.as_ref()) - } - - fn to_raw_vec(&self) -> $crate::Vec { - <$public as $crate::RuntimePublic>::to_raw_vec(&self.0) - } - } - - impl From for $crate::CryptoTypePublicPair { - fn from(key: Public) -> Self { - (&key).into() - } - } - - impl From<&Public> for $crate::CryptoTypePublicPair { - fn from(key: &Public) -> Self { - $crate::CryptoTypePublicPair($crypto_type, $crate::ByteArray::to_raw_vec(key)) - } - } - impl<'a> TryFrom<&'a [u8]> for Public { type Error = (); @@ -370,14 +322,29 @@ macro_rules! app_crypto_public_common { <$public>::try_from(data).map(Into::into) } } + + impl Public { + /// Convert into wrapped generic public key type. + pub fn into_inner(self) -> $public { + self.0 + } + } }; } -/// Implements traits for the public key type if `feature = "std"` is enabled. -#[cfg(feature = "std")] +#[doc(hidden)] +pub mod module_format_string_prelude { + #[cfg(all(not(feature = "std"), feature = "serde"))] + pub use sp_std::alloc::{format, string::String}; + #[cfg(feature = "std")] + pub use std::{format, string::String}; +} + +/// Implements traits for the public key type if `feature = "serde"` is enabled. +#[cfg(feature = "serde")] #[doc(hidden)] #[macro_export] -macro_rules! app_crypto_public_common_if_std { +macro_rules! app_crypto_public_common_if_serde { () => { impl $crate::Derive for Public { fn derive>( @@ -388,15 +355,15 @@ macro_rules! app_crypto_public_common_if_std { } } - impl std::fmt::Display for Public { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + impl core::fmt::Display for Public { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { use $crate::Ss58Codec; write!(f, "{}", self.0.to_ss58check()) } } impl $crate::serde::Serialize for Public { - fn serialize(&self, serializer: S) -> std::result::Result + fn serialize(&self, serializer: S) -> core::result::Result where S: $crate::serde::Serializer, { @@ -406,11 +373,12 @@ macro_rules! app_crypto_public_common_if_std { } impl<'de> $crate::serde::Deserialize<'de> for Public { - fn deserialize(deserializer: D) -> std::result::Result + fn deserialize(deserializer: D) -> core::result::Result where D: $crate::serde::Deserializer<'de>, { - use $crate::Ss58Codec; + use $crate::{module_format_string_prelude::*, Ss58Codec}; + Public::from_ss58check(&String::deserialize(deserializer)?) .map_err(|e| $crate::serde::de::Error::custom(format!("{:?}", e))) } @@ -418,10 +386,10 @@ macro_rules! app_crypto_public_common_if_std { }; } -#[cfg(not(feature = "std"))] +#[cfg(not(feature = "serde"))] #[doc(hidden)] #[macro_export] -macro_rules! app_crypto_public_common_if_std { +macro_rules! app_crypto_public_common_if_serde { () => { impl $crate::Derive for Public {} }; @@ -429,8 +397,8 @@ macro_rules! app_crypto_public_common_if_std { /// Declares Signature type which is functionally equivalent to `$sig`, but is new /// Application-specific type whose identifier is `$key_type`. -/// can only be used together with `full_crypto` feature /// For full functionality, app_crypto_public_common! must be called too. +/// Can only be used with `full_crypto` feature #[doc(hidden)] #[macro_export] macro_rules! app_crypto_signature_full_crypto { @@ -451,8 +419,7 @@ macro_rules! app_crypto_signature_full_crypto { type Pair = Pair; } - impl $crate::AppKey for Signature { - type UntypedGeneric = $sig; + impl $crate::AppCrypto for Signature { type Public = Public; type Pair = Pair; type Signature = Signature; @@ -462,10 +429,10 @@ macro_rules! app_crypto_signature_full_crypto { }; } -/// Declares Signature type which is functionally equivalent to `$sig`, but is new -/// Application-specific type whose identifier is `$key_type`. -/// can only be used without `full_crypto` feature -/// For full functionality, app_crypto_public_common! must be called too. +/// Declares `Signature` type which is functionally equivalent to `$sig`, but is new +/// application-specific type whose identifier is `$key_type`. +/// For full functionality, `app_crypto_signature_common` must be called too. +/// Can only be used without `full_crypto` feature. #[doc(hidden)] #[macro_export] macro_rules! app_crypto_signature_not_full_crypto { @@ -475,16 +442,15 @@ macro_rules! app_crypto_signature_not_full_crypto { #[derive(Clone, Eq, PartialEq, $crate::codec::Encode, $crate::codec::Decode, - $crate::scale_info::TypeInfo, $crate::RuntimeDebug, + $crate::scale_info::TypeInfo, )] pub struct Signature($sig); } impl $crate::CryptoType for Signature {} - impl $crate::AppKey for Signature { - type UntypedGeneric = $sig; + impl $crate::AppCrypto for Signature { type Public = Public; type Signature = Signature; const ID: $crate::KeyTypeId = $key_type; @@ -493,9 +459,9 @@ macro_rules! app_crypto_signature_not_full_crypto { }; } -/// Declares Signature type which is functionally equivalent to `$sig`, but is new -/// Application-specific type whose identifier is `$key_type`. -/// For full functionality, app_crypto_public_(not)_full_crypto! must be called too. +/// Declares `Signature` type which is functionally equivalent to `$sig`, but is new +/// application-specific type whose identifier is `$key_type`. +/// For full functionality, app_crypto_signature_(not)_full_crypto! must be called too. #[doc(hidden)] #[macro_export] macro_rules! app_crypto_signature_common { @@ -533,6 +499,13 @@ macro_rules! app_crypto_signature_common { Self::try_from(&data[..]) } } + + impl Signature { + /// Convert into wrapped generic signature type. + pub fn into_inner(self) -> $sig { + self.0 + } + } }; } diff --git a/primitives/application-crypto/src/sr25519.rs b/primitives/application-crypto/src/sr25519.rs index c96e7382eb19..7c91bfa7bb5f 100644 --- a/primitives/application-crypto/src/sr25519.rs +++ b/primitives/application-crypto/src/sr25519.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,13 +24,7 @@ use sp_std::vec::Vec; pub use sp_core::sr25519::*; mod app { - use sp_core::testing::SR25519; - - crate::app_crypto!(super, SR25519); - - impl crate::traits::BoundToRuntimeAppPublic for Public { - type Public = Self; - } + crate::app_crypto!(super, sp_core::testing::SR25519); } #[cfg(feature = "full_crypto")] diff --git a/primitives/application-crypto/src/traits.rs b/primitives/application-crypto/src/traits.rs index 853208bc20cb..88d4bf36915d 100644 --- a/primitives/application-crypto/src/traits.rs +++ b/primitives/application-crypto/src/traits.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,32 +15,38 @@ // See the License for the specific language governing permissions and // limitations under the License. +use codec::Codec; +use scale_info::TypeInfo; + #[cfg(feature = "full_crypto")] use sp_core::crypto::Pair; - -use codec::Codec; use sp_core::crypto::{CryptoType, CryptoTypeId, IsWrappedBy, KeyTypeId, Public}; use sp_std::{fmt::Debug, vec::Vec}; -/// An application-specific key. -pub trait AppKey: 'static + Send + Sync + Sized + CryptoType + Clone { - /// The corresponding type as a generic crypto type. - type UntypedGeneric: IsWrappedBy; +/// An application-specific cryptographic object. +/// +/// Combines all the core types and constants that are defined by a particular +/// cryptographic scheme when it is used in a specific application domain. +/// +/// Typically, the implementers of this trait are its associated types themselves. +/// This provides a convenient way to access generic information about the scheme +/// given any of the associated types. +pub trait AppCrypto: 'static + Send + Sync + Sized + CryptoType + Clone { + /// Identifier for application-specific key type. + const ID: KeyTypeId; + + /// Identifier of the crypto type of this application-specific key type. + const CRYPTO_ID: CryptoTypeId; /// The corresponding public key type in this application scheme. type Public: AppPublic; - /// The corresponding key pair type in this application scheme. - #[cfg(feature = "full_crypto")] - type Pair: AppPair; - /// The corresponding signature type in this application scheme. type Signature: AppSignature; - /// An identifier for this application-specific key type. - const ID: KeyTypeId; - /// The identifier of the crypto type of this application-specific key type. - const CRYPTO_ID: CryptoTypeId; + /// The corresponding key pair type in this application scheme. + #[cfg(feature = "full_crypto")] + type Pair: AppPair; } /// Type which implements Hash in std, not when no-std (std variant). @@ -55,15 +61,9 @@ pub trait MaybeHash {} #[cfg(all(not(feature = "std"), not(feature = "full_crypto")))] impl MaybeHash for T {} -/// Type which implements Debug and Hash in std, not when no-std (no-std variant with crypto). -#[cfg(all(not(feature = "std"), feature = "full_crypto"))] -pub trait MaybeDebugHash: sp_std::hash::Hash {} -#[cfg(all(not(feature = "std"), feature = "full_crypto"))] -impl MaybeDebugHash for T {} - /// A application's public key. pub trait AppPublic: - AppKey + Public + Ord + PartialOrd + Eq + PartialEq + Debug + MaybeHash + codec::Codec + AppCrypto + Public + Ord + PartialOrd + Eq + PartialEq + Debug + MaybeHash + Codec { /// The wrapped type which is just a plain instance of `Public`. type Generic: IsWrappedBy @@ -74,27 +74,28 @@ pub trait AppPublic: + PartialEq + Debug + MaybeHash - + codec::Codec; + + Codec; } /// A application's key pair. #[cfg(feature = "full_crypto")] -pub trait AppPair: AppKey + Pair::Public> { +pub trait AppPair: AppCrypto + Pair::Public> { /// The wrapped type which is just a plain instance of `Pair`. type Generic: IsWrappedBy - + Pair::Public as AppPublic>::Generic>; + + Pair::Public as AppPublic>::Generic> + + Pair::Signature as AppSignature>::Generic>; } /// A application's signature. -pub trait AppSignature: AppKey + Eq + PartialEq + Debug + MaybeHash { +pub trait AppSignature: AppCrypto + Eq + PartialEq + Debug { /// The wrapped type which is just a plain instance of `Signature`. - type Generic: IsWrappedBy + Eq + PartialEq + Debug + MaybeHash; + type Generic: IsWrappedBy + Eq + PartialEq + Debug; } /// A runtime interface for a public key. pub trait RuntimePublic: Sized { /// The signature that will be generated when signing with the corresponding private key. - type Signature: Codec + Debug + MaybeHash + Eq + PartialEq + Clone; + type Signature: Debug + Eq + PartialEq + Clone; /// Returns all public keys for the given key type in the keystore. fn all(key_type: KeyTypeId) -> crate::Vec; @@ -126,11 +127,9 @@ pub trait RuntimePublic: Sized { pub trait RuntimeAppPublic: Sized { /// An identifier for this application-specific key type. const ID: KeyTypeId; - /// The identifier of the crypto type of this application-specific key type. - const CRYPTO_ID: CryptoTypeId; /// The signature that will be generated when signing with the corresponding private key. - type Signature: Codec + Debug + MaybeHash + Eq + PartialEq + Clone + scale_info::TypeInfo; + type Signature: Debug + Eq + PartialEq + Clone + TypeInfo + Codec; /// Returns all public keys for this application in the keystore. fn all() -> crate::Vec; @@ -157,8 +156,50 @@ pub trait RuntimeAppPublic: Sized { fn to_raw_vec(&self) -> Vec; } -/// Something that bound to a fixed [`RuntimeAppPublic`]. +impl RuntimeAppPublic for T +where + T: AppPublic + AsRef<::Generic>, + ::Generic: RuntimePublic, + ::Signature: TypeInfo + + Codec + + From<<::Generic as RuntimePublic>::Signature> + + AsRef<<::Generic as RuntimePublic>::Signature>, +{ + const ID: KeyTypeId = ::ID; + + type Signature = ::Signature; + + fn all() -> crate::Vec { + <::Generic as RuntimePublic>::all(Self::ID) + .into_iter() + .map(|p| p.into()) + .collect() + } + + fn generate_pair(seed: Option>) -> Self { + <::Generic as RuntimePublic>::generate_pair(Self::ID, seed).into() + } + + fn sign>(&self, msg: &M) -> Option { + <::Generic as RuntimePublic>::sign(self.as_ref(), Self::ID, msg) + .map(|s| s.into()) + } + + fn verify>(&self, msg: &M, signature: &Self::Signature) -> bool { + <::Generic as RuntimePublic>::verify(self.as_ref(), msg, signature.as_ref()) + } + + fn to_raw_vec(&self) -> Vec { + <::Generic as RuntimePublic>::to_raw_vec(self.as_ref()) + } +} + +/// Something that is bound to a fixed [`RuntimeAppPublic`]. pub trait BoundToRuntimeAppPublic { /// The [`RuntimeAppPublic`] this type is bound to. type Public: RuntimeAppPublic; } + +impl BoundToRuntimeAppPublic for T { + type Public = Self; +} diff --git a/primitives/application-crypto/test/Cargo.toml b/primitives/application-crypto/test/Cargo.toml index b10b7a3218ba..d4b2ccca909b 100644 --- a/primitives/application-crypto/test/Cargo.toml +++ b/primitives/application-crypto/test/Cargo.toml @@ -14,8 +14,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] sp-api = { version = "4.0.0-dev", path = "../../api" } -sp-application-crypto = { version = "7.0.0", path = "../" } -sp-core = { version = "7.0.0", default-features = false, path = "../../core" } -sp-keystore = { version = "0.13.0", default-features = false, path = "../../keystore" } -sp-runtime = { version = "7.0.0", path = "../../runtime" } +sp-application-crypto = { version = "23.0.0", path = "../" } +sp-core = { version = "21.0.0", default-features = false, path = "../../core" } +sp-keystore = { version = "0.27.0", default-features = false, path = "../../keystore" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../test-utils/runtime/client" } diff --git a/primitives/application-crypto/test/src/ecdsa.rs b/primitives/application-crypto/test/src/ecdsa.rs index e45a3d5f8f86..396683a91ac0 100644 --- a/primitives/application-crypto/test/src/ecdsa.rs +++ b/primitives/application-crypto/test/src/ecdsa.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,11 +16,13 @@ // limitations under the License. //! Integration tests for ecdsa -use sp_api::ProvideRuntimeApi; -use sp_application_crypto::ecdsa::{AppPair, AppPublic}; -use sp_core::{crypto::Pair, testing::ECDSA}; -use sp_keystore::{testing::KeyStore, SyncCryptoStore}; -use sp_runtime::generic::BlockId; +use sp_api::{ApiExt, ProvideRuntimeApi}; +use sp_application_crypto::ecdsa::AppPair; +use sp_core::{ + crypto::{ByteArray, Pair}, + testing::ECDSA, +}; +use sp_keystore::{testing::MemoryKeystore, Keystore, KeystoreExt}; use std::sync::Arc; use substrate_test_runtime_client::{ runtime::TestAPI, DefaultTestClientBuilderExt, TestClientBuilder, TestClientBuilderExt, @@ -28,14 +30,17 @@ use substrate_test_runtime_client::{ #[test] fn ecdsa_works_in_runtime() { - let keystore = Arc::new(KeyStore::new()); - let test_client = TestClientBuilder::new().set_keystore(keystore.clone()).build(); - let (signature, public) = test_client - .runtime_api() - .test_ecdsa_crypto(&BlockId::Number(0)) + let keystore = Arc::new(MemoryKeystore::new()); + let test_client = TestClientBuilder::new().build(); + + let mut runtime_api = test_client.runtime_api(); + runtime_api.register_extension(KeystoreExt::new(keystore.clone())); + + let (signature, public) = runtime_api + .test_ecdsa_crypto(test_client.chain_info().genesis_hash) .expect("Tests `ecdsa` crypto."); - let supported_keys = SyncCryptoStore::keys(&*keystore, ECDSA).unwrap(); - assert!(supported_keys.contains(&public.clone().into())); - assert!(AppPair::verify(&signature, "ecdsa", &AppPublic::from(public))); + let supported_keys = keystore.keys(ECDSA).unwrap(); + assert!(supported_keys.contains(&public.to_raw_vec())); + assert!(AppPair::verify(&signature, "ecdsa", &public)); } diff --git a/primitives/application-crypto/test/src/ed25519.rs b/primitives/application-crypto/test/src/ed25519.rs index ef2df9fe9196..f0ceccdcebfc 100644 --- a/primitives/application-crypto/test/src/ed25519.rs +++ b/primitives/application-crypto/test/src/ed25519.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,11 +17,13 @@ //! Integration tests for ed25519 -use sp_api::ProvideRuntimeApi; -use sp_application_crypto::ed25519::{AppPair, AppPublic}; -use sp_core::{crypto::Pair, testing::ED25519}; -use sp_keystore::{testing::KeyStore, SyncCryptoStore}; -use sp_runtime::generic::BlockId; +use sp_api::{ApiExt, ProvideRuntimeApi}; +use sp_application_crypto::ed25519::AppPair; +use sp_core::{ + crypto::{ByteArray, Pair}, + testing::ED25519, +}; +use sp_keystore::{testing::MemoryKeystore, Keystore, KeystoreExt}; use std::sync::Arc; use substrate_test_runtime_client::{ runtime::TestAPI, DefaultTestClientBuilderExt, TestClientBuilder, TestClientBuilderExt, @@ -29,14 +31,17 @@ use substrate_test_runtime_client::{ #[test] fn ed25519_works_in_runtime() { - let keystore = Arc::new(KeyStore::new()); - let test_client = TestClientBuilder::new().set_keystore(keystore.clone()).build(); - let (signature, public) = test_client - .runtime_api() - .test_ed25519_crypto(&BlockId::Number(0)) + let keystore = Arc::new(MemoryKeystore::new()); + let test_client = TestClientBuilder::new().build(); + + let mut runtime_api = test_client.runtime_api(); + runtime_api.register_extension(KeystoreExt::new(keystore.clone())); + + let (signature, public) = runtime_api + .test_ed25519_crypto(test_client.chain_info().genesis_hash) .expect("Tests `ed25519` crypto."); - let supported_keys = SyncCryptoStore::keys(&*keystore, ED25519).unwrap(); - assert!(supported_keys.contains(&public.clone().into())); - assert!(AppPair::verify(&signature, "ed25519", &AppPublic::from(public))); + let supported_keys = keystore.keys(ED25519).unwrap(); + assert!(supported_keys.contains(&public.to_raw_vec())); + assert!(AppPair::verify(&signature, "ed25519", &public)); } diff --git a/primitives/application-crypto/test/src/lib.rs b/primitives/application-crypto/test/src/lib.rs index 7cc3f8b0780e..90856ee1e596 100644 --- a/primitives/application-crypto/test/src/lib.rs +++ b/primitives/application-crypto/test/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/application-crypto/test/src/sr25519.rs b/primitives/application-crypto/test/src/sr25519.rs index e15ffe82a1c3..3c62270395f0 100644 --- a/primitives/application-crypto/test/src/sr25519.rs +++ b/primitives/application-crypto/test/src/sr25519.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,11 +17,13 @@ //! Integration tests for sr25519 -use sp_api::ProvideRuntimeApi; -use sp_application_crypto::sr25519::{AppPair, AppPublic}; -use sp_core::{crypto::Pair, testing::SR25519}; -use sp_keystore::{testing::KeyStore, SyncCryptoStore}; -use sp_runtime::generic::BlockId; +use sp_api::{ApiExt, ProvideRuntimeApi}; +use sp_application_crypto::sr25519::AppPair; +use sp_core::{ + crypto::{ByteArray, Pair}, + testing::SR25519, +}; +use sp_keystore::{testing::MemoryKeystore, Keystore, KeystoreExt}; use std::sync::Arc; use substrate_test_runtime_client::{ runtime::TestAPI, DefaultTestClientBuilderExt, TestClientBuilder, TestClientBuilderExt, @@ -29,14 +31,17 @@ use substrate_test_runtime_client::{ #[test] fn sr25519_works_in_runtime() { - let keystore = Arc::new(KeyStore::new()); - let test_client = TestClientBuilder::new().set_keystore(keystore.clone()).build(); - let (signature, public) = test_client - .runtime_api() - .test_sr25519_crypto(&BlockId::Number(0)) + let keystore = Arc::new(MemoryKeystore::new()); + let test_client = TestClientBuilder::new().build(); + + let mut runtime_api = test_client.runtime_api(); + runtime_api.register_extension(KeystoreExt::new(keystore.clone())); + + let (signature, public) = runtime_api + .test_sr25519_crypto(test_client.chain_info().genesis_hash) .expect("Tests `sr25519` crypto."); - let supported_keys = SyncCryptoStore::keys(&*keystore, SR25519).unwrap(); - assert!(supported_keys.contains(&public.clone().into())); - assert!(AppPair::verify(&signature, "sr25519", &AppPublic::from(public))); + let supported_keys = keystore.keys(SR25519).unwrap(); + assert!(supported_keys.contains(&public.to_raw_vec())); + assert!(AppPair::verify(&signature, "sr25519", &public)); } diff --git a/primitives/arithmetic/Cargo.toml b/primitives/arithmetic/Cargo.toml index bbb4bbd1776b..e2e2e1c6c283 100644 --- a/primitives/arithmetic/Cargo.toml +++ b/primitives/arithmetic/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-arithmetic" -version = "6.0.0" +version = "16.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -14,21 +14,21 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = [ "derive", "max-encoded-len", ] } integer-sqrt = "0.1.2" num-traits = { version = "0.2.8", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", features = ["derive"], optional = true } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", default-features = false, features = ["derive", "alloc"], optional = true } static_assertions = "1.1.0" -sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } [dev-dependencies] -criterion = "0.3" +criterion = "0.4.0" primitive-types = "0.12.0" -sp-core = { version = "7.0.0", features = ["full_crypto"], path = "../core" } +sp-core = { version = "21.0.0", features = ["full_crypto"], path = "../core" } rand = "0.8.5" [features] @@ -37,9 +37,14 @@ std = [ "codec/std", "num-traits/std", "scale-info/std", - "serde", + "serde/std", "sp-std/std", ] +# Serde support without relying on std features. +serde = [ + "dep:serde", + "scale-info/serde", +] [[bench]] name = "bench" diff --git a/primitives/arithmetic/benches/bench.rs b/primitives/arithmetic/benches/bench.rs index 5551d209098c..6a8abca14e52 100644 --- a/primitives/arithmetic/benches/bench.rs +++ b/primitives/arithmetic/benches/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/arithmetic/fuzzer/Cargo.toml b/primitives/arithmetic/fuzzer/Cargo.toml index 7be800a2e966..976798831797 100644 --- a/primitives/arithmetic/fuzzer/Cargo.toml +++ b/primitives/arithmetic/fuzzer/Cargo.toml @@ -14,10 +14,11 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] +arbitrary = "1.3.0" +fraction = "0.13.1" honggfuzz = "0.5.49" num-bigint = "0.4.3" -primitive-types = "0.12.0" -sp-arithmetic = { version = "6.0.0", path = ".." } +sp-arithmetic = { version = "16.0.0", path = ".." } [[bin]] name = "biguint" @@ -28,8 +29,12 @@ name = "normalize" path = "src/normalize.rs" [[bin]] -name = "per_thing_rational" -path = "src/per_thing_rational.rs" +name = "per_thing_from_rational" +path = "src/per_thing_from_rational.rs" + +[[bin]] +name = "per_thing_mult_fraction" +path = "src/per_thing_mult_fraction.rs" [[bin]] name = "multiply_by_rational_with_rounding" diff --git a/primitives/arithmetic/fuzzer/src/biguint.rs b/primitives/arithmetic/fuzzer/src/biguint.rs index f49743a4b8a6..2f9f54c81000 100644 --- a/primitives/arithmetic/fuzzer/src/biguint.rs +++ b/primitives/arithmetic/fuzzer/src/biguint.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/arithmetic/fuzzer/src/fixed_point.rs b/primitives/arithmetic/fuzzer/src/fixed_point.rs index c1b93f8c63a1..e76dd1503e39 100644 --- a/primitives/arithmetic/fuzzer/src/fixed_point.rs +++ b/primitives/arithmetic/fuzzer/src/fixed_point.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/arithmetic/fuzzer/src/multiply_by_rational_with_rounding.rs b/primitives/arithmetic/fuzzer/src/multiply_by_rational_with_rounding.rs index 474b2d363ecc..5f3f675c971f 100644 --- a/primitives/arithmetic/fuzzer/src/multiply_by_rational_with_rounding.rs +++ b/primitives/arithmetic/fuzzer/src/multiply_by_rational_with_rounding.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,52 +29,77 @@ //! More information about `honggfuzz` can be found //! [here](https://docs.rs/honggfuzz/). +use fraction::prelude::BigFraction as Fraction; use honggfuzz::fuzz; -use sp_arithmetic::{helpers_128bit::multiply_by_rational_with_rounding, traits::Zero, Rounding}; +use sp_arithmetic::{MultiplyRational, Rounding, Rounding::*}; +/// Tries to demonstrate that `multiply_by_rational_with_rounding` is incorrect. fn main() { loop { - fuzz!(|data: ([u8; 16], [u8; 16], [u8; 16])| { - let (a_bytes, b_bytes, c_bytes) = data; - let (a, b, c) = ( - u128::from_be_bytes(a_bytes), - u128::from_be_bytes(b_bytes), - u128::from_be_bytes(c_bytes), - ); + fuzz!(|data: (u128, u128, u128, ArbitraryRounding)| { + let (f, n, d, r) = (data.0, data.1, data.2, data.3 .0); - println!("++ Equation: {} * {} / {}", a, b, c); - - // The point of this fuzzing is to make sure that `multiply_by_rational_with_rounding` - // is 100% accurate as long as the value fits in a u128. - if let Some(result) = multiply_by_rational_with_rounding(a, b, c, Rounding::Down) { - let truth = mul_div(a, b, c); - - if result != truth && result != truth + 1 { - println!("++ Expected {}", truth); - println!("+++++++ Got {}", result); - panic!(); - } - } + check::(f as u8, n as u8, d as u8, r); + check::(f as u16, n as u16, d as u16, r); + check::(f as u32, n as u32, d as u32, r); + check::(f as u64, n as u64, d as u64, r); + check::(f, n, d, r); }) } } -fn mul_div(a: u128, b: u128, c: u128) -> u128 { - use primitive_types::U256; - if a.is_zero() { - return Zero::zero() - } - let c = c.max(1); +fn check(f: N, n: N, d: N, r: Rounding) +where + N: MultiplyRational + Into + Copy + core::fmt::Debug, +{ + let Some(got) = f.multiply_rational(n, d, r) else { return }; + + let (ae, be, ce) = + (Fraction::from(f.into()), Fraction::from(n.into()), Fraction::from(d.into())); + let want = round(ae * be / ce, r); - // e for extended - let ae: U256 = a.into(); - let be: U256 = b.into(); - let ce: U256 = c.into(); + assert_eq!( + Fraction::from(got.into()), + want, + "{:?} * {:?} / {:?} = {:?} != {:?}", + f, + n, + d, + got, + want + ); +} + +/// Round a `Fraction` according to the given mode. +fn round(f: Fraction, r: Rounding) -> Fraction { + match r { + Up => f.ceil(), + NearestPrefUp => + if f.fract() < Fraction::from(0.5) { + f.floor() + } else { + f.ceil() + }, + Down => f.floor(), + NearestPrefDown => + if f.fract() > Fraction::from(0.5) { + f.ceil() + } else { + f.floor() + }, + } +} - let r = ae * be / ce; - if r > u128::MAX.into() { - a - } else { - r.as_u128() +/// An [`arbitrary::Arbitrary`] [`Rounding`] mode. +struct ArbitraryRounding(Rounding); +impl arbitrary::Arbitrary<'_> for ArbitraryRounding { + fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { + Ok(Self(match u.int_in_range(0..=3).unwrap() { + 0 => Up, + 1 => NearestPrefUp, + 2 => Down, + 3 => NearestPrefDown, + _ => unreachable!(), + })) } } diff --git a/primitives/arithmetic/fuzzer/src/normalize.rs b/primitives/arithmetic/fuzzer/src/normalize.rs index 3d2d6eb9acfc..7211819017a8 100644 --- a/primitives/arithmetic/fuzzer/src/normalize.rs +++ b/primitives/arithmetic/fuzzer/src/normalize.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/arithmetic/fuzzer/src/per_thing_from_rational.rs b/primitives/arithmetic/fuzzer/src/per_thing_from_rational.rs new file mode 100644 index 000000000000..93af4df9e8e5 --- /dev/null +++ b/primitives/arithmetic/fuzzer/src/per_thing_from_rational.rs @@ -0,0 +1,105 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Running +//! Running this fuzzer can be done with `cargo hfuzz run per_thing_from_rational`. `honggfuzz` CLI +//! options can be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads. +//! +//! # Debugging a panic +//! Once a panic is found, it can be debugged with +//! `cargo hfuzz run-debug per_thing_from_rational hfuzz_workspace/per_thing_from_rational/*.fuzz`. + +use fraction::prelude::BigFraction as Fraction; +use honggfuzz::fuzz; +use sp_arithmetic::{ + traits::SaturatedConversion, PerThing, Perbill, Percent, Perquintill, Rounding::*, *, +}; + +/// Tries to demonstrate that `from_rational` is incorrect for any rounding modes. +/// +/// NOTE: This `Fraction` library is really slow. Using f128/f256 does not work for the large +/// numbers. But an optimization could be done do use either floats or Fraction depending on the +/// size of the inputs. +fn main() { + loop { + fuzz!(|data: (u128, u128, ArbitraryRounding)| { + let (n, d, r) = (data.0.min(data.1), data.0.max(data.1).max(1), data.2); + + check::(n, d, r.0); + check::(n, d, r.0); + check::(n, d, r.0); + check::(n, d, r.0); + check::(n, d, r.0); + }) + } +} + +/// Assert that the parts of `from_rational` are correct for the given rounding mode. +fn check(a: u128, b: u128, r: Rounding) +where + Per::Inner: Into, +{ + let approx_ratio = Per::from_rational_with_rounding(a, b, r).unwrap(); + let approx_parts = Fraction::from(approx_ratio.deconstruct().saturated_into::()); + + let perfect_ratio = if a == 0 && b == 0 { + Fraction::from(1) + } else { + Fraction::from(a) / Fraction::from(b.max(1)) + }; + let perfect_parts = round(perfect_ratio * Fraction::from(Per::ACCURACY.into()), r); + + assert_eq!( + approx_parts, perfect_parts, + "approx_parts: {}, perfect_parts: {}, a: {}, b: {}", + approx_parts, perfect_parts, a, b + ); +} + +/// Round a `Fraction` according to the given mode. +fn round(f: Fraction, r: Rounding) -> Fraction { + match r { + Up => f.ceil(), + NearestPrefUp => + if f.fract() < Fraction::from(0.5) { + f.floor() + } else { + f.ceil() + }, + Down => f.floor(), + NearestPrefDown => + if f.fract() > Fraction::from(0.5) { + f.ceil() + } else { + f.floor() + }, + } +} + +/// An [`arbitrary::Arbitrary`] [`Rounding`] mode. +struct ArbitraryRounding(Rounding); +impl arbitrary::Arbitrary<'_> for ArbitraryRounding { + fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { + Ok(Self(match u.int_in_range(0..=3).unwrap() { + 0 => Up, + 1 => NearestPrefUp, + 2 => Down, + 3 => NearestPrefDown, + _ => unreachable!(), + })) + } +} diff --git a/primitives/arithmetic/fuzzer/src/per_thing_mult_fraction.rs b/primitives/arithmetic/fuzzer/src/per_thing_mult_fraction.rs new file mode 100644 index 000000000000..9cfe28a7800b --- /dev/null +++ b/primitives/arithmetic/fuzzer/src/per_thing_mult_fraction.rs @@ -0,0 +1,69 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # Running +//! Running this fuzzer can be done with `cargo hfuzz run per_thing_mult_fraction`. `honggfuzz` CLI +//! options can be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads. +//! +//! # Debugging a panic +//! Once a panic is found, it can be debugged with +//! `cargo hfuzz run-debug per_thing_mult_fraction hfuzz_workspace/per_thing_mult_fraction/*.fuzz`. + +use honggfuzz::fuzz; +use sp_arithmetic::{PerThing, Perbill, Percent, Perquintill, *}; + +/// Tries to disprove `(n / d) * d <= n` for any `PerThing`s. +fn main() { + loop { + fuzz!(|data: (u128, u128)| { + let (n, d) = (data.0.min(data.1), data.0.max(data.1).max(1)); + + check_mul::(n, d); + check_mul::(n, d); + check_mul::(n, d); + check_mul::(n, d); + + check_reciprocal_mul::(n, d); + check_reciprocal_mul::(n, d); + check_reciprocal_mul::(n, d); + check_reciprocal_mul::(n, d); + }) + } +} + +/// Checks that `(n / d) * d <= n`. +fn check_mul(n: u128, d: u128) +where + P: PerThing + core::ops::Mul, +{ + let q = P::from_rational_with_rounding(n, d, Rounding::Down).unwrap(); + assert!(q * d <= n, "{:?} * {:?} <= {:?}", q, d, n); +} + +/// Checks that `n / (n / d) >= d`. +fn check_reciprocal_mul(n: u128, d: u128) +where + P: PerThing + core::ops::Mul, +{ + let q = P::from_rational_with_rounding(n, d, Rounding::Down).unwrap(); + if q.is_zero() { + return + } + + let r = q.saturating_reciprocal_mul_floor(n); + assert!(r >= d, "{} / ({} / {}) != {} but {}", n, n, d, d, r); +} diff --git a/primitives/arithmetic/fuzzer/src/per_thing_rational.rs b/primitives/arithmetic/fuzzer/src/per_thing_rational.rs deleted file mode 100644 index 7021c54c0ba0..000000000000 --- a/primitives/arithmetic/fuzzer/src/per_thing_rational.rs +++ /dev/null @@ -1,116 +0,0 @@ -// 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. - -//! # Running -//! Running this fuzzer can be done with `cargo hfuzz run per_thing_rational`. `honggfuzz` CLI -//! options can be used by setting `HFUZZ_RUN_ARGS`, such as `-n 4` to use 4 threads. -//! -//! # Debugging a panic -//! Once a panic is found, it can be debugged with -//! `cargo hfuzz run-debug per_thing_rational hfuzz_workspace/per_thing_rational/*.fuzz`. - -use honggfuzz::fuzz; -use sp_arithmetic::{traits::SaturatedConversion, PerThing, PerU16, Perbill, Percent, Perquintill}; - -fn main() { - loop { - fuzz!(|data: ((u16, u16), (u32, u32), (u64, u64))| { - let (u16_pair, u32_pair, u64_pair) = data; - - // peru16 - let (smaller, bigger) = (u16_pair.0.min(u16_pair.1), u16_pair.0.max(u16_pair.1)); - let ratio = PerU16::from_rational(smaller, bigger); - assert_per_thing_equal_error( - ratio, - PerU16::from_float(smaller as f64 / bigger.max(1) as f64), - 1, - ); - let (smaller, bigger) = (u32_pair.0.min(u32_pair.1), u32_pair.0.max(u32_pair.1)); - let ratio = PerU16::from_rational(smaller, bigger); - assert_per_thing_equal_error( - ratio, - PerU16::from_float(smaller as f64 / bigger.max(1) as f64), - 1, - ); - let (smaller, bigger) = (u64_pair.0.min(u64_pair.1), u64_pair.0.max(u64_pair.1)); - let ratio = PerU16::from_rational(smaller, bigger); - assert_per_thing_equal_error( - ratio, - PerU16::from_float(smaller as f64 / bigger.max(1) as f64), - 1, - ); - - // percent - let (smaller, bigger) = (u16_pair.0.min(u16_pair.1), u16_pair.0.max(u16_pair.1)); - let ratio = Percent::from_rational(smaller, bigger); - assert_per_thing_equal_error( - ratio, - Percent::from_float(smaller as f64 / bigger.max(1) as f64), - 1, - ); - - let (smaller, bigger) = (u32_pair.0.min(u32_pair.1), u32_pair.0.max(u32_pair.1)); - let ratio = Percent::from_rational(smaller, bigger); - assert_per_thing_equal_error( - ratio, - Percent::from_float(smaller as f64 / bigger.max(1) as f64), - 1, - ); - - let (smaller, bigger) = (u64_pair.0.min(u64_pair.1), u64_pair.0.max(u64_pair.1)); - let ratio = Percent::from_rational(smaller, bigger); - assert_per_thing_equal_error( - ratio, - Percent::from_float(smaller as f64 / bigger.max(1) as f64), - 1, - ); - - // perbill - let (smaller, bigger) = (u32_pair.0.min(u32_pair.1), u32_pair.0.max(u32_pair.1)); - let ratio = Perbill::from_rational(smaller, bigger); - assert_per_thing_equal_error( - ratio, - Perbill::from_float(smaller as f64 / bigger.max(1) as f64), - 100, - ); - - let (smaller, bigger) = (u64_pair.0.min(u64_pair.1), u64_pair.0.max(u64_pair.1)); - let ratio = Perbill::from_rational(smaller, bigger); - assert_per_thing_equal_error( - ratio, - Perbill::from_float(smaller as f64 / bigger.max(1) as f64), - 100, - ); - - // perquintillion - let (smaller, bigger) = (u64_pair.0.min(u64_pair.1), u64_pair.0.max(u64_pair.1)); - let ratio = Perquintill::from_rational(smaller, bigger); - assert_per_thing_equal_error( - ratio, - Perquintill::from_float(smaller as f64 / bigger.max(1) as f64), - 1000, - ); - }) - } -} - -fn assert_per_thing_equal_error(a: P, b: P, err: u128) { - let a_abs = a.deconstruct().saturated_into::(); - let b_abs = b.deconstruct().saturated_into::(); - let diff = a_abs.max(b_abs) - a_abs.min(b_abs); - assert!(diff <= err, "{:?} !~ {:?}", a, b); -} diff --git a/primitives/arithmetic/src/biguint.rs b/primitives/arithmetic/src/biguint.rs index 33f0960ee378..d92b08c8eca9 100644 --- a/primitives/arithmetic/src/biguint.rs +++ b/primitives/arithmetic/src/biguint.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/arithmetic/src/fixed_point.rs b/primitives/arithmetic/src/fixed_point.rs index bf3c93cdad60..08b788e9abd7 100644 --- a/primitives/arithmetic/src/fixed_point.rs +++ b/primitives/arithmetic/src/fixed_point.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,9 +32,12 @@ use sp_std::{ prelude::*, }; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +#[cfg(all(not(feature = "std"), feature = "serde"))] +use sp_std::alloc::string::{String, ToString}; + /// Integer types that can be used to interact with `FixedPointNumber` implementations. pub trait FixedPointOperand: Copy @@ -928,14 +931,12 @@ macro_rules! implement_fixed { } } - #[cfg(feature = "std")] impl sp_std::fmt::Display for $name { fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { write!(f, "{}", self.0) } } - #[cfg(feature = "std")] impl sp_std::str::FromStr for $name { type Err = &'static str; @@ -948,7 +949,7 @@ macro_rules! implement_fixed { // Manual impl `Serialize` as serde_json does not support i128. // TODO: remove impl if issue https://github.com/serde-rs/json/issues/548 fixed. - #[cfg(feature = "std")] + #[cfg(feature = "serde")] impl Serialize for $name { fn serialize(&self, serializer: S) -> Result where @@ -960,7 +961,7 @@ macro_rules! implement_fixed { // Manual impl `Deserialize` as serde_json does not support i128. // TODO: remove impl if issue https://github.com/serde-rs/json/issues/548 fixed. - #[cfg(feature = "std")] + #[cfg(feature = "serde")] impl<'de> Deserialize<'de> for $name { fn deserialize(deserializer: D) -> Result where diff --git a/primitives/arithmetic/src/helpers_128bit.rs b/primitives/arithmetic/src/helpers_128bit.rs index 7938c31d1eaa..9b9c74ba5577 100644 --- a/primitives/arithmetic/src/helpers_128bit.rs +++ b/primitives/arithmetic/src/helpers_128bit.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // Some code is based upon Derek Dreery's IntegerSquareRoot impl, used under license. // SPDX-License-Identifier: Apache-2.0 @@ -182,8 +182,8 @@ mod double128 { } } -/// Returns `a * b / c` and `(a * b) % c` (wrapping to 128 bits) or `None` in the case of -/// overflow and c = 0. +/// Returns `a * b / c` (wrapping to 128 bits) or `None` in the case of +/// overflow. pub const fn multiply_by_rational_with_rounding( a: u128, b: u128, diff --git a/primitives/arithmetic/src/lib.rs b/primitives/arithmetic/src/lib.rs index 590aaa316644..900f0b75c3bf 100644 --- a/primitives/arithmetic/src/lib.rs +++ b/primitives/arithmetic/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,12 +40,14 @@ pub mod per_things; pub mod rational; pub mod traits; -pub use fixed_point::{FixedI128, FixedI64, FixedPointNumber, FixedPointOperand, FixedU128}; +pub use fixed_point::{ + FixedI128, FixedI64, FixedPointNumber, FixedPointOperand, FixedU128, FixedU64, +}; pub use per_things::{ InnerOf, MultiplyArg, PerThing, PerU16, Perbill, Percent, Permill, Perquintill, RationalArg, ReciprocalArg, Rounding, SignedRounding, UpperOf, }; -pub use rational::{Rational128, RationalInfinite}; +pub use rational::{MultiplyRational, Rational128, RationalInfinite}; use sp_std::{cmp::Ordering, fmt::Debug, prelude::*}; use traits::{BaseArithmetic, One, SaturatedConversion, Unsigned, Zero}; @@ -53,12 +55,12 @@ use traits::{BaseArithmetic, One, SaturatedConversion, Unsigned, Zero}; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// Arithmetic errors. #[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum ArithmeticError { /// Underflow. Underflow, diff --git a/primitives/arithmetic/src/per_things.rs b/primitives/arithmetic/src/per_things.rs index fc3767761175..fe88b72e24c2 100644 --- a/primitives/arithmetic/src/per_things.rs +++ b/primitives/arithmetic/src/per_things.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use crate::traits::{ @@ -26,7 +26,7 @@ use codec::{CompactAs, Encode}; use num_traits::{Pow, SaturatingAdd, SaturatingSub}; use sp_std::{ fmt, ops, - ops::{Add, AddAssign, Div, Rem, Sub}, + ops::{Add, Sub}, prelude::*, }; @@ -46,6 +46,7 @@ pub trait RationalArg: + Unsigned + Zero + One + + crate::MultiplyRational { } @@ -58,7 +59,8 @@ impl< + ops::AddAssign + Unsigned + Zero - + One, + + One + + crate::MultiplyRational, > RationalArg for T { } @@ -105,7 +107,7 @@ pub trait PerThing: + Pow { /// The data type used to build this per-thingy. - type Inner: BaseArithmetic + Unsigned + Copy + Into + fmt::Debug; + type Inner: BaseArithmetic + Unsigned + Copy + Into + fmt::Debug + crate::MultiplyRational; /// A data type larger than `Self::Inner`, used to avoid overflow in some computations. /// It must be able to compute `ACCURACY^2`. @@ -115,7 +117,8 @@ pub trait PerThing: + TryInto + UniqueSaturatedInto + Unsigned - + fmt::Debug; + + fmt::Debug + + crate::MultiplyRational; /// The accuracy of this type. const ACCURACY: Self::Inner; @@ -524,51 +527,20 @@ where rem_mul_div_inner += 1.into(); } }, - Rounding::NearestPrefDown => + Rounding::NearestPrefDown => { if rem_mul_upper % denom_upper > denom_upper / 2.into() { // `rem * numer / denom` is less than `numer`, so this will not overflow. rem_mul_div_inner += 1.into(); - }, - Rounding::NearestPrefUp => + } + }, + Rounding::NearestPrefUp => { if rem_mul_upper % denom_upper >= denom_upper / 2.into() + denom_upper % 2.into() { // `rem * numer / denom` is less than `numer`, so this will not overflow. rem_mul_div_inner += 1.into(); - }, - } - rem_mul_div_inner.into() -} - -/// Just a simple generic integer divide with custom rounding. -fn div_rounded(n: N, d: N, r: Rounding) -> N -where - N: Clone - + Eq - + Ord - + Zero - + One - + AddAssign - + Add - + Rem - + Div, -{ - let mut o = n.clone() / d.clone(); - use Rounding::*; - let two = || N::one() + N::one(); - if match r { - Up => !((n % d).is_zero()), - NearestPrefDown => { - let rem = n % d.clone(); - rem > d / two() - }, - NearestPrefUp => { - let rem = n % d.clone(); - rem >= d.clone() / two() + d % two() + } }, - Down => false, - } { - o += N::one() } - o + rem_mul_div_inner.into() } macro_rules! implement_per_thing { @@ -584,7 +556,7 @@ macro_rules! implement_per_thing { /// A fixed point representation of a number in the range [0, 1]. /// #[doc = $title] - #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] + #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Encode, Copy, Clone, PartialEq, Eq, codec::MaxEncodedLen, PartialOrd, Ord, scale_info::TypeInfo)] pub struct $name($type); @@ -687,7 +659,8 @@ macro_rules! implement_per_thing { + ops::AddAssign + Unsigned + Zero - + One, + + One + + $crate::MultiplyRational, Self::Inner: Into { // q cannot be zero. @@ -695,30 +668,8 @@ macro_rules! implement_per_thing { // p should not be bigger than q. if p > q { return Err(()) } - let factor = div_rounded::(q.clone(), $max.into(), Rounding::Up).max(One::one()); - - // q cannot overflow: (q / (q/$max)) < $max. p < q hence p also cannot overflow. - let q_reduce: $type = div_rounded(q, factor.clone(), r) - .try_into() - .map_err(|_| "Failed to convert") - .expect( - "`q / ceil(q/$max) < $max`; macro prevents any type being created that \ - does not satisfy this; qed" - ); - let p_reduce: $type = div_rounded(p, factor, r) - .try_into() - .map_err(|_| "Failed to convert") - .expect( - "`p / ceil(p/$max) < $max`; macro prevents any type being created that \ - does not satisfy this; qed" - ); - - // `p_reduced` and `q_reduced` are within `Self::Inner`. Multiplication by another - // `$max` will always fit in `$upper_type`. This is guaranteed by the macro tests. - let n = p_reduce as $upper_type * <$upper_type>::from($max); - let d = q_reduce as $upper_type; - let part = div_rounded(n, d, r); - Ok($name(part as Self::Inner)) + let max: N = $max.into(); + max.multiply_rational(p, q, r).ok_or(())?.try_into().map(|x| $name(x)).map_err(|_| ()) } } @@ -1862,6 +1813,22 @@ fn from_rational_with_rounding_works_in_extreme_case() { } } +#[test] +fn from_rational_with_rounding_breakage() { + let n = 372633774963620730670986667244911905u128; + let d = 512593663333074177468745541591173060u128; + let q = Perquintill::from_rational_with_rounding(n, d, Rounding::Down).unwrap(); + assert!(q * d <= n); +} + +#[test] +fn from_rational_with_rounding_breakage_2() { + let n = 36893488147419103230u128; + let d = 36893488147419103630u128; + let q = Perquintill::from_rational_with_rounding(n, d, Rounding::Up).unwrap(); + assert!(q * d >= n); +} + implement_per_thing!(Percent, test_per_cent, [u32, u64, u128], 100u8, u8, u16, "_Percent_",); implement_per_thing_with_perthousand!( PerU16, diff --git a/primitives/arithmetic/src/rational.rs b/primitives/arithmetic/src/rational.rs index 447b37551bb1..ebd89c615a38 100644 --- a/primitives/arithmetic/src/rational.rs +++ b/primitives/arithmetic/src/rational.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -284,6 +284,54 @@ impl PartialEq for Rational128 { } } +pub trait MultiplyRational: Sized { + fn multiply_rational(self, n: Self, d: Self, r: Rounding) -> Option; +} + +macro_rules! impl_rrm { + ($ulow:ty, $uhi:ty) => { + impl MultiplyRational for $ulow { + fn multiply_rational(self, n: Self, d: Self, r: Rounding) -> Option { + if d.is_zero() { + return None + } + + let sn = (self as $uhi) * (n as $uhi); + let mut result = sn / (d as $uhi); + let remainder = (sn % (d as $uhi)) as $ulow; + if match r { + Rounding::Up => remainder > 0, + // cannot be `(d + 1) / 2` since `d` might be `max_value` and overflow. + Rounding::NearestPrefUp => remainder >= d / 2 + d % 2, + Rounding::NearestPrefDown => remainder > d / 2, + Rounding::Down => false, + } { + result = match result.checked_add(1) { + Some(v) => v, + None => return None, + }; + } + if result > (<$ulow>::max_value() as $uhi) { + None + } else { + Some(result as $ulow) + } + } + } + }; +} + +impl_rrm!(u8, u16); +impl_rrm!(u16, u32); +impl_rrm!(u32, u64); +impl_rrm!(u64, u128); + +impl MultiplyRational for u128 { + fn multiply_rational(self, n: Self, d: Self, r: Rounding) -> Option { + crate::helpers_128bit::multiply_by_rational_with_rounding(self, n, d, r) + } +} + #[cfg(test)] mod tests { use super::{helpers_128bit::*, *}; diff --git a/primitives/arithmetic/src/traits.rs b/primitives/arithmetic/src/traits.rs index dfba04675437..061b11b3e9c7 100644 --- a/primitives/arithmetic/src/traits.rs +++ b/primitives/arithmetic/src/traits.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,6 +32,8 @@ use sp_std::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Rem, RemAssign, Shl, Shr, Sub, SubAssign, }; +use crate::MultiplyRational; + /// A meta trait for arithmetic type operations, regardless of any limitation on size. pub trait BaseArithmetic: From @@ -148,6 +150,34 @@ impl< { } +/// A meta trait for arithmetic. +/// +/// Arithmetic types do all the usual stuff you'd expect numbers to do. They are guaranteed to +/// be able to represent at least `u8` values without loss, hence the trait implies `From` +/// and smaller integers. All other conversions are fallible. +pub trait AtLeast8Bit: BaseArithmetic + From {} + +impl> AtLeast8Bit for T {} + +/// A meta trait for arithmetic. Same as [`AtLeast8Bit `], but also bounded to be unsigned. +pub trait AtLeast8BitUnsigned: AtLeast8Bit + Unsigned {} + +impl AtLeast8BitUnsigned for T {} + +/// A meta trait for arithmetic. +/// +/// Arithmetic types do all the usual stuff you'd expect numbers to do. They are guaranteed to +/// be able to represent at least `u16` values without loss, hence the trait implies `From` +/// and smaller integers. All other conversions are fallible. +pub trait AtLeast16Bit: BaseArithmetic + From {} + +impl> AtLeast16Bit for T {} + +/// A meta trait for arithmetic. Same as [`AtLeast16Bit `], but also bounded to be unsigned. +pub trait AtLeast16BitUnsigned: AtLeast16Bit + Unsigned {} + +impl AtLeast16BitUnsigned for T {} + /// A meta trait for arithmetic. /// /// Arithmetic types do all the usual stuff you'd expect numbers to do. They are guaranteed to @@ -158,9 +188,9 @@ pub trait AtLeast32Bit: BaseArithmetic + From + From {} impl + From> AtLeast32Bit for T {} /// A meta trait for arithmetic. Same as [`AtLeast32Bit `], but also bounded to be unsigned. -pub trait AtLeast32BitUnsigned: AtLeast32Bit + Unsigned {} +pub trait AtLeast32BitUnsigned: AtLeast32Bit + Unsigned + MultiplyRational {} -impl AtLeast32BitUnsigned for T {} +impl AtLeast32BitUnsigned for T {} /// Just like `From` except that if the source value is too big to fit into the destination type /// then it'll saturate the destination. @@ -206,6 +236,24 @@ pub trait Saturating { /// instead of overflowing. fn saturating_pow(self, exp: usize) -> Self; + /// Decrement self by one, saturating at zero. + fn saturating_less_one(mut self) -> Self + where + Self: One, + { + self.saturating_dec(); + self + } + + /// Increment self by one, saturating at the numeric bounds instead of overflowing. + fn saturating_plus_one(mut self) -> Self + where + Self: One, + { + self.saturating_inc(); + self + } + /// Increment self by one, saturating. fn saturating_inc(&mut self) where diff --git a/primitives/authority-discovery/Cargo.toml b/primitives/authority-discovery/Cargo.toml index ff36cf58d366..03c9ef2c7d26 100644 --- a/primitives/authority-discovery/Cargo.toml +++ b/primitives/authority-discovery/Cargo.toml @@ -13,12 +13,12 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-application-crypto = { version = "7.0.0", default-features = false, path = "../application-crypto" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../application-crypto" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } [features] default = ["std"] @@ -30,3 +30,8 @@ std = [ "sp-runtime/std", "sp-std/std", ] +serde = [ + "scale-info/serde", + "sp-application-crypto/serde", + "sp-runtime/serde", +] diff --git a/primitives/authority-discovery/src/lib.rs b/primitives/authority-discovery/src/lib.rs index 95bb458b1be8..3b25e39d4045 100644 --- a/primitives/authority-discovery/src/lib.rs +++ b/primitives/authority-discovery/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/authorship/Cargo.toml b/primitives/authorship/Cargo.toml deleted file mode 100644 index 31ea3b2d4c8f..000000000000 --- a/primitives/authorship/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "sp-authorship" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -description = "Authorship primitives" -edition = "2021" -license = "Apache-2.0" -homepage = "https://substrate.io" -repository = "https://github.com/paritytech/substrate/" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -async-trait = { version = "0.1.57", optional = true } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } - -[features] -default = [ "std" ] -std = [ - "async-trait", - "codec/std", - "sp-inherents/std", - "sp-runtime/std", - "sp-std/std", -] diff --git a/primitives/authorship/README.md b/primitives/authorship/README.md deleted file mode 100644 index 1aa1805cfc5e..000000000000 --- a/primitives/authorship/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Authorship Primitives - -License: Apache-2.0 \ No newline at end of file diff --git a/primitives/authorship/src/lib.rs b/primitives/authorship/src/lib.rs deleted file mode 100644 index 6ff6607a896c..000000000000 --- a/primitives/authorship/src/lib.rs +++ /dev/null @@ -1,101 +0,0 @@ -// 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. - -//! Authorship Primitives - -#![cfg_attr(not(feature = "std"), no_std)] - -use sp_std::{prelude::*, result::Result}; - -#[cfg(feature = "std")] -use codec::Decode; -use codec::Encode; -use sp_inherents::{Error, InherentData, InherentIdentifier, IsFatalError}; -use sp_runtime::{traits::Header as HeaderT, RuntimeString}; - -/// The identifier for the `uncles` inherent. -pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"uncles00"; - -/// Errors that can occur while checking the authorship inherent. -#[derive(Encode, sp_runtime::RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Decode))] -pub enum InherentError { - Uncles(RuntimeString), -} - -impl IsFatalError for InherentError { - fn is_fatal_error(&self) -> bool { - match self { - InherentError::Uncles(_) => true, - } - } -} - -/// Auxiliary trait to extract uncles inherent data. -pub trait UnclesInherentData { - /// Get uncles. - fn uncles(&self) -> Result, Error>; -} - -impl UnclesInherentData for InherentData { - fn uncles(&self) -> Result, Error> { - Ok(self.get_data(&INHERENT_IDENTIFIER)?.unwrap_or_default()) - } -} - -/// Provider for inherent data. -#[cfg(feature = "std")] -pub struct InherentDataProvider { - uncles: Vec, -} - -#[cfg(feature = "std")] -impl InherentDataProvider { - /// Create a new inherent data provider with the given `uncles`. - pub fn new(uncles: Vec) -> Self { - InherentDataProvider { uncles } - } - - /// Create a new instance that is usable for checking inherents. - /// - /// This will always return an empty vec of uncles. - pub fn check_inherents() -> Self { - Self { uncles: Vec::new() } - } -} - -#[cfg(feature = "std")] -#[async_trait::async_trait] -impl sp_inherents::InherentDataProvider for InherentDataProvider { - async fn provide_inherent_data(&self, inherent_data: &mut InherentData) -> Result<(), Error> { - inherent_data.put_data(INHERENT_IDENTIFIER, &self.uncles) - } - - async fn try_handle_error( - &self, - identifier: &InherentIdentifier, - mut error: &[u8], - ) -> Option> { - if *identifier != INHERENT_IDENTIFIER { - return None - } - - let error = InherentError::decode(&mut error).ok()?; - - Some(Err(Error::Application(Box::from(format!("{:?}", error))))) - } -} diff --git a/primitives/beefy/Cargo.toml b/primitives/beefy/Cargo.toml deleted file mode 100644 index 6a22f0383a3d..000000000000 --- a/primitives/beefy/Cargo.toml +++ /dev/null @@ -1,43 +0,0 @@ -[package] -name = "sp-beefy" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2021" -license = "Apache-2.0" -homepage = "https://substrate.io" -repository = "https://github.com/paritytech/substrate" -description = "Primitives for BEEFY protocol." - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", optional = true, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-application-crypto = { version = "7.0.0", default-features = false, path = "../application-crypto" } -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-io = { version = "7.0.0", default-features = false, path = "../io" } -sp-mmr-primitives = { version = "4.0.0-dev", default-features = false, path = "../merkle-mountain-range" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } - -[dev-dependencies] -array-bytes = "4.1" -sp-keystore = { version = "0.13.0", path = "../keystore" } - -[features] -default = ["std"] -std = [ - "codec/std", - "scale-info/std", - "serde", - "sp-api/std", - "sp-application-crypto/std", - "sp-core/std", - "sp-io/std", - "sp-mmr-primitives/std", - "sp-runtime/std", - "sp-std/std", -] diff --git a/primitives/block-builder/Cargo.toml b/primitives/block-builder/Cargo.toml index 6ccb7980df36..ba0e1f041e0d 100644 --- a/primitives/block-builder/Cargo.toml +++ b/primitives/block-builder/Cargo.toml @@ -13,16 +13,14 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] std = [ - "codec/std", "sp-api/std", "sp-inherents/std", "sp-runtime/std", diff --git a/primitives/block-builder/src/lib.rs b/primitives/block-builder/src/lib.rs index cf1bfa256084..29e04857f463 100644 --- a/primitives/block-builder/src/lib.rs +++ b/primitives/block-builder/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/blockchain/Cargo.toml b/primitives/blockchain/Cargo.toml index 68791852d845..6320fab9390b 100644 --- a/primitives/blockchain/Cargo.toml +++ b/primitives/blockchain/Cargo.toml @@ -14,14 +14,14 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } futures = "0.3.21" log = "0.4.17" -lru = "0.8.1" parking_lot = "0.12.1" +schnellru = "0.2.1" thiserror = "1.0.30" sp-api = { version = "4.0.0-dev", path = "../api" } sp-consensus = { version = "0.10.0-dev", path = "../consensus/common" } sp-database = { version = "4.0.0-dev", path = "../database" } -sp-runtime = { version = "7.0.0", path = "../runtime" } -sp-state-machine = { version = "0.13.0", path = "../state-machine" } +sp-runtime = { version = "24.0.0", path = "../runtime" } +sp-state-machine = { version = "0.28.0", path = "../state-machine" } diff --git a/primitives/blockchain/src/backend.rs b/primitives/blockchain/src/backend.rs index ec9c8ac0d578..8208f9128e71 100644 --- a/primitives/blockchain/src/backend.rs +++ b/primitives/blockchain/src/backend.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -183,96 +183,41 @@ pub trait Backend: /// Return hashes of all blocks that are children of the block with `parent_hash`. fn children(&self, parent_hash: Block::Hash) -> Result>; - /// Get the most recent block hash of the best (longest) chains - /// that contain block with the given `target_hash`. + /// Get the most recent block hash of the longest chain that contains + /// a block with the given `base_hash`. /// /// The search space is always limited to blocks which are in the finalized /// chain or descendents of it. /// - /// If `maybe_max_block_number` is `Some(max_block_number)` - /// the search is limited to block `numbers <= max_block_number`. - /// in other words as if there were no blocks greater `max_block_number`. - /// Returns `Ok(None)` if `target_hash` is not found in search space. - /// TODO: document time complexity of this, see [#1444](https://github.com/paritytech/substrate/issues/1444) - fn best_containing( + /// Returns `Ok(None)` if `base_hash` is not found in search space. + // TODO: document time complexity of this, see [#1444](https://github.com/paritytech/substrate/issues/1444) + fn longest_containing( &self, - target_hash: Block::Hash, - maybe_max_number: Option>, + base_hash: Block::Hash, import_lock: &RwLock<()>, ) -> Result> { - let target_header = { - match self.header(target_hash)? { - Some(x) => x, - // target not in blockchain - None => return Ok(None), - } - }; - - if let Some(max_number) = maybe_max_number { - // target outside search range - if target_header.number() > &max_number { - return Ok(None) - } - } + let Some(base_header) = self.header(base_hash)? else { return Ok(None) }; let leaves = { // ensure no blocks are imported during this code block. // an import could trigger a reorg which could change the canonical chain. // we depend on the canonical chain staying the same during this code block. let _import_guard = import_lock.read(); - let info = self.info(); - - // this can be `None` if the best chain is shorter than the target header. - let maybe_canon_hash = self.hash(*target_header.number())?; - - if maybe_canon_hash.as_ref() == Some(&target_hash) { - // if a `max_number` is given we try to fetch the block at the - // given depth, if it doesn't exist or `max_number` is not - // provided, we continue to search from all leaves below. - if let Some(max_number) = maybe_max_number { - if let Some(header) = self.hash(max_number)? { - return Ok(Some(header)) - } - } - } else if info.finalized_number >= *target_header.number() { - // header is on a dead fork. + if info.finalized_number > *base_header.number() { + // `base_header` is on a dead fork. return Ok(None) } - self.leaves()? }; // for each chain. longest chain first. shortest last for leaf_hash in leaves { - // start at the leaf let mut current_hash = leaf_hash; - - // if search is not restricted then the leaf is the best - let mut best_hash = leaf_hash; - - // go backwards entering the search space - // waiting until we are <= max_number - if let Some(max_number) = maybe_max_number { - loop { - let current_header = self - .header(current_hash)? - .ok_or_else(|| Error::MissingHeader(current_hash.to_string()))?; - - if current_header.number() <= &max_number { - best_hash = current_header.hash(); - break - } - - current_hash = *current_header.parent_hash(); - } - } - // go backwards through the chain (via parent links) loop { - // until we find target - if current_hash == target_hash { - return Ok(Some(best_hash)) + if current_hash == base_hash { + return Ok(Some(leaf_hash)) } let current_header = self @@ -280,7 +225,7 @@ pub trait Backend: .ok_or_else(|| Error::MissingHeader(current_hash.to_string()))?; // stop search in this chain once we go below the target's block number - if current_header.number() < target_header.number() { + if current_header.number() < base_header.number() { break } @@ -293,9 +238,8 @@ pub trait Backend: // // FIXME #1558 only issue this warning when not on a dead fork warn!( - "Block {:?} exists in chain but not found when following all \ - leaves backwards. Number limit = {:?}", - target_hash, maybe_max_number, + "Block {:?} exists in chain but not found when following all leaves backwards", + base_hash, ); Ok(None) @@ -342,18 +286,3 @@ pub enum BlockStatus { /// Not in the queue or the blockchain. Unknown, } - -/// A list of all well known keys in the blockchain cache. -pub mod well_known_cache_keys { - /// The type representing cache keys. - pub type Id = sp_consensus::CacheKeyId; - - /// A list of authorities. - pub const AUTHORITIES: Id = *b"auth"; - - /// Current Epoch data. - pub const EPOCH: Id = *b"epch"; - - /// Changes trie configuration. - pub const CHANGES_TRIE_CONFIG: Id = *b"chtr"; -} diff --git a/primitives/blockchain/src/error.rs b/primitives/blockchain/src/error.rs index 6585cc54f158..74a2ed3fba50 100644 --- a/primitives/blockchain/src/error.rs +++ b/primitives/blockchain/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -150,7 +150,7 @@ pub enum Error { #[error("Transaction pool not ready for block production.")] TransactionPoolNotReady, - #[error("Database")] + #[error("Database error: {0}")] DatabaseError(#[from] sp_database::error::DatabaseError), #[error("Failed to get header for hash {0}")] @@ -159,6 +159,9 @@ pub enum Error { #[error("State Database error: {0}")] StateDatabase(String), + #[error("Statement store error: {0}")] + StatementStore(String), + #[error("Failed to set the chain head to a block that's too old.")] SetHeadTooOld, diff --git a/primitives/blockchain/src/header_metadata.rs b/primitives/blockchain/src/header_metadata.rs index 87ac44459987..08b3c9ab3dfb 100644 --- a/primitives/blockchain/src/header_metadata.rs +++ b/primitives/blockchain/src/header_metadata.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,13 +18,12 @@ //! Implements tree backend, cached header metadata and algorithms //! to compute routes efficiently over the tree of headers. -use lru::LruCache; use parking_lot::RwLock; +use schnellru::{ByLength, LruMap}; use sp_runtime::traits::{Block as BlockT, Header, NumberFor, One}; -use std::num::NonZeroUsize; /// Set to the expected max difference between `best` and `finalized` blocks at sync. -const LRU_CACHE_SIZE: usize = 5_000; +const LRU_CACHE_SIZE: u32 = 5_000; /// Get lowest common ancestor between two blocks in the tree. /// @@ -243,20 +242,19 @@ pub trait HeaderMetadata { /// Caches header metadata in an in-memory LRU cache. pub struct HeaderMetadataCache { - cache: RwLock>>, + cache: RwLock>>, } impl HeaderMetadataCache { /// Creates a new LRU header metadata cache with `capacity`. - pub fn new(capacity: NonZeroUsize) -> Self { - HeaderMetadataCache { cache: RwLock::new(LruCache::new(capacity)) } + pub fn new(capacity: u32) -> Self { + HeaderMetadataCache { cache: RwLock::new(LruMap::new(ByLength::new(capacity))) } } } impl Default for HeaderMetadataCache { fn default() -> Self { - let cap = NonZeroUsize::new(LRU_CACHE_SIZE).expect("cache capacity is not zero"); - HeaderMetadataCache { cache: RwLock::new(LruCache::new(cap)) } + HeaderMetadataCache { cache: RwLock::new(LruMap::new(ByLength::new(LRU_CACHE_SIZE))) } } } @@ -266,11 +264,11 @@ impl HeaderMetadataCache { } pub fn insert_header_metadata(&self, hash: Block::Hash, metadata: CachedHeaderMetadata) { - self.cache.write().put(hash, metadata); + self.cache.write().insert(hash, metadata); } pub fn remove_header_metadata(&self, hash: Block::Hash) { - self.cache.write().pop(&hash); + self.cache.write().remove(&hash); } } diff --git a/primitives/blockchain/src/lib.rs b/primitives/blockchain/src/lib.rs index 2fdef6cba9e5..eabbbcf50d9f 100644 --- a/primitives/blockchain/src/lib.rs +++ b/primitives/blockchain/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/consensus/aura/Cargo.toml b/primitives/consensus/aura/Cargo.toml index 001dc4a69aca..b50e43acfd65 100644 --- a/primitives/consensus/aura/Cargo.toml +++ b/primitives/consensus/aura/Cargo.toml @@ -14,15 +14,14 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = { version = "0.1.57", optional = true } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } -sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../application-crypto" } -sp-consensus = { version = "0.10.0-dev", optional = true, path = "../common" } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../../application-crypto" } sp-consensus-slots = { version = "0.10.0-dev", default-features = false, path = "../slots" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../inherents" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../std" } sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../timestamp" } [features] @@ -33,10 +32,17 @@ std = [ "scale-info/std", "sp-api/std", "sp-application-crypto/std", - "sp-consensus", "sp-consensus-slots/std", "sp-inherents/std", "sp-runtime/std", "sp-std/std", "sp-timestamp/std", ] + +# Serde support without relying on std features. +serde = [ + "scale-info/serde", + "sp-application-crypto/serde", + "sp-consensus-slots/serde", + "sp-runtime/serde", +] diff --git a/primitives/consensus/aura/src/digests.rs b/primitives/consensus/aura/src/digests.rs index b71930b6c6b8..13484da2a295 100644 --- a/primitives/consensus/aura/src/digests.rs +++ b/primitives/consensus/aura/src/digests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/primitives/consensus/aura/src/inherents.rs b/primitives/consensus/aura/src/inherents.rs index 135ae2660b32..1ef25feb0ad6 100644 --- a/primitives/consensus/aura/src/inherents.rs +++ b/primitives/consensus/aura/src/inherents.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/consensus/aura/src/lib.rs b/primitives/consensus/aura/src/lib.rs index 2c6a97b93413..78409e84e93a 100644 --- a/primitives/consensus/aura/src/lib.rs +++ b/primitives/consensus/aura/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/consensus/babe/Cargo.toml b/primitives/consensus/babe/Cargo.toml index 334fff281194..b33bae73f62d 100644 --- a/primitives/consensus/babe/Cargo.toml +++ b/primitives/consensus/babe/Cargo.toml @@ -14,20 +14,16 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = { version = "0.1.57", optional = true } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -merlin = { version = "2.0", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", features = ["derive"], optional = true } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", default-features = false, features = ["derive", "alloc"], optional = true } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } -sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../application-crypto" } -sp-consensus = { version = "0.10.0-dev", optional = true, path = "../common" } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../../application-crypto" } sp-consensus-slots = { version = "0.10.0-dev", default-features = false, path = "../slots" } -sp-consensus-vrf = { version = "0.10.0-dev", default-features = false, path = "../vrf" } -sp-core = { version = "7.0.0", default-features = false, path = "../../core" } +sp-core = { version = "21.0.0", default-features = false, path = "../../core" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../inherents" } -sp-keystore = { version = "0.13.0", default-features = false, optional = true, path = "../../keystore" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../std" } sp-timestamp = { version = "4.0.0-dev", optional = true, path = "../../timestamp" } [features] @@ -35,18 +31,24 @@ default = ["std"] std = [ "async-trait", "codec/std", - "merlin/std", "scale-info/std", - "serde", + "serde/std", "sp-api/std", "sp-application-crypto/std", - "sp-consensus", "sp-consensus-slots/std", - "sp-consensus-vrf/std", "sp-core/std", "sp-inherents/std", - "sp-keystore", "sp-runtime/std", "sp-std/std", "sp-timestamp", ] + +# Serde support without relying on std features. +serde = [ + "dep:serde", + "scale-info/serde", + "sp-application-crypto/serde", + "sp-consensus-slots/serde", + "sp-core/serde", + "sp-runtime/serde", +] diff --git a/primitives/consensus/babe/src/digests.rs b/primitives/consensus/babe/src/digests.rs index 1e4c820379d7..afc967e3af39 100644 --- a/primitives/consensus/babe/src/digests.rs +++ b/primitives/consensus/babe/src/digests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,14 +19,15 @@ use super::{ AllowedSlots, AuthorityId, AuthorityIndex, AuthoritySignature, BabeAuthorityWeight, - BabeEpochConfiguration, Slot, BABE_ENGINE_ID, + BabeEpochConfiguration, Randomness, Slot, BABE_ENGINE_ID, }; -use codec::{Decode, Encode, MaxEncodedLen}; -use scale_info::TypeInfo; + +use sp_core::sr25519::vrf::VrfSignature; use sp_runtime::{DigestItem, RuntimeDebug}; use sp_std::vec::Vec; -use sp_consensus_vrf::schnorrkel::{Randomness, VRFOutput, VRFProof}; +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; /// Raw BABE primary slot assignment pre-digest. #[derive(Clone, RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)] @@ -35,10 +36,8 @@ pub struct PrimaryPreDigest { pub authority_index: super::AuthorityIndex, /// Slot pub slot: Slot, - /// VRF output - pub vrf_output: VRFOutput, - /// VRF proof - pub vrf_proof: VRFProof, + /// VRF signature + pub vrf_signature: VrfSignature, } /// BABE secondary slot assignment pre-digest. @@ -62,10 +61,8 @@ pub struct SecondaryVRFPreDigest { pub authority_index: super::AuthorityIndex, /// Slot pub slot: Slot, - /// VRF output - pub vrf_output: VRFOutput, - /// VRF proof - pub vrf_proof: VRFProof, + /// VRF signature + pub vrf_signature: VrfSignature, } /// A BABE pre-runtime digest. This contains all data required to validate a @@ -118,11 +115,10 @@ impl PreDigest { } /// Returns the VRF output and proof, if they exist. - pub fn vrf(&self) -> Option<(&VRFOutput, &VRFProof)> { + pub fn vrf_signature(&self) -> Option<&VrfSignature> { match self { - PreDigest::Primary(primary) => Some((&primary.vrf_output, &primary.vrf_proof)), - PreDigest::SecondaryVRF(secondary) => - Some((&secondary.vrf_output, &secondary.vrf_proof)), + PreDigest::Primary(primary) => Some(&primary.vrf_signature), + PreDigest::SecondaryVRF(secondary) => Some(&secondary.vrf_signature), PreDigest::SecondaryPlain(_) => None, } } diff --git a/primitives/consensus/babe/src/inherents.rs b/primitives/consensus/babe/src/inherents.rs index 2634507968f8..b01bd1c9221f 100644 --- a/primitives/consensus/babe/src/inherents.rs +++ b/primitives/consensus/babe/src/inherents.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/consensus/babe/src/lib.rs b/primitives/consensus/babe/src/lib.rs index cb44afcb8d4e..c083bfd9a313 100644 --- a/primitives/consensus/babe/src/lib.rs +++ b/primitives/consensus/babe/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,22 +23,19 @@ pub mod digests; pub mod inherents; -pub use merlin::Transcript; -pub use sp_consensus_vrf::schnorrkel::{ - Randomness, RANDOMNESS_LENGTH, VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH, -}; - use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; -#[cfg(feature = "std")] -use sp_keystore::vrf::{VRFTranscriptData, VRFTranscriptValue}; use sp_runtime::{traits::Header, ConsensusEngineId, RuntimeDebug}; use sp_std::vec::Vec; use crate::digests::{NextConfigDescriptor, NextEpochDescriptor}; +pub use sp_core::sr25519::vrf::{ + VrfInput, VrfOutput, VrfProof, VrfSignData, VrfSignature, VrfTranscript, +}; + /// Key type for BABE module. pub const KEY_TYPE: sp_core::crypto::KeyTypeId = sp_application_crypto::key_types::BABE; @@ -47,11 +44,14 @@ mod app { app_crypto!(sr25519, BABE); } -/// The prefix used by BABE for its VRF keys. -pub const BABE_VRF_PREFIX: &[u8] = b"substrate-babe-vrf"; +/// VRF context used for per-slot randomness generation. +pub const RANDOMNESS_VRF_CONTEXT: &[u8] = b"BabeVRFInOutContext"; + +/// VRF output length for per-slot randomness. +pub const RANDOMNESS_LENGTH: usize = 32; -/// BABE VRFInOut context. -pub static BABE_VRF_INOUT_CONTEXT: &[u8] = b"BabeVRFInOutContext"; +/// Randomness type required by BABE operations. +pub type Randomness = [u8; RANDOMNESS_LENGTH]; /// A Babe authority keypair. Necessarily equivalent to the schnorrkel public key used in /// the main Babe module. If that ever changes, then this must, too. @@ -96,26 +96,21 @@ pub type BabeAuthorityWeight = u64; /// of 0 (regardless of whether they are plain or vrf secondary blocks). pub type BabeBlockWeight = u32; -/// Make a VRF transcript from given randomness, slot number and epoch. -pub fn make_transcript(randomness: &Randomness, slot: Slot, epoch: u64) -> Transcript { - let mut transcript = Transcript::new(&BABE_ENGINE_ID); - transcript.append_u64(b"slot number", *slot); - transcript.append_u64(b"current epoch", epoch); - transcript.append_message(b"chain randomness", &randomness[..]); - transcript +/// Make VRF input suitable for BABE's randomness generation. +pub fn make_vrf_transcript(randomness: &Randomness, slot: Slot, epoch: u64) -> VrfInput { + VrfInput::new( + &BABE_ENGINE_ID, + &[ + (b"slot number", &slot.to_le_bytes()), + (b"current epoch", &epoch.to_le_bytes()), + (b"chain randomness", randomness), + ], + ) } -/// Make a VRF transcript data container -#[cfg(feature = "std")] -pub fn make_transcript_data(randomness: &Randomness, slot: Slot, epoch: u64) -> VRFTranscriptData { - VRFTranscriptData { - label: &BABE_ENGINE_ID, - items: vec![ - ("slot number", VRFTranscriptValue::U64(*slot)), - ("current epoch", VRFTranscriptValue::U64(epoch)), - ("chain randomness", VRFTranscriptValue::Bytes(randomness.to_vec())), - ], - } +/// Make VRF signing data suitable for BABE's protocol. +pub fn make_vrf_sign_data(randomness: &Randomness, slot: Slot, epoch: u64) -> VrfSignData { + make_vrf_transcript(randomness, slot, epoch).into() } /// An consensus log item for BABE. @@ -184,7 +179,7 @@ impl From for BabeConfiguration { } /// Configuration data used by the BABE consensus engine. -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct BabeConfiguration { /// The slot duration in milliseconds for BABE. Currently, only /// the value provided by this type at genesis will be used. @@ -222,7 +217,7 @@ impl BabeConfiguration { /// Types of allowed slots. #[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum AllowedSlots { /// Only allow primary slots. PrimarySlots, @@ -246,7 +241,7 @@ impl AllowedSlots { /// Configuration data used by the BABE consensus engine that may change with epochs. #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, MaxEncodedLen, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct BabeEpochConfiguration { /// A constant value that is used in the threshold calculation formula. /// Expressed as a rational where the first member of the tuple is the @@ -327,7 +322,7 @@ where /// the runtime API boundary this type is unknown and as such we keep this /// opaque representation, implementors of the runtime API will have to make /// sure that all usages of `OpaqueKeyOwnershipProof` refer to the same type. -#[derive(Decode, Encode, PartialEq)] +#[derive(Decode, Encode, PartialEq, TypeInfo)] pub struct OpaqueKeyOwnershipProof(Vec); impl OpaqueKeyOwnershipProof { /// Create a new `OpaqueKeyOwnershipProof` using the given encoded @@ -344,7 +339,7 @@ impl OpaqueKeyOwnershipProof { } /// BABE epoch information -#[derive(Decode, Encode, PartialEq, Eq, Clone, Debug)] +#[derive(Decode, Encode, PartialEq, Eq, Clone, Debug, TypeInfo)] pub struct Epoch { /// The epoch index. pub epoch_index: u64, @@ -355,7 +350,7 @@ pub struct Epoch { /// The authorities and their weights. pub authorities: Vec<(AuthorityId, BabeAuthorityWeight)>, /// Randomness for this epoch. - pub randomness: [u8; VRF_OUTPUT_LENGTH], + pub randomness: Randomness, /// Configuration of the epoch. pub config: BabeEpochConfiguration, } diff --git a/primitives/consensus/beefy/Cargo.toml b/primitives/consensus/beefy/Cargo.toml new file mode 100644 index 000000000000..3673397b877e --- /dev/null +++ b/primitives/consensus/beefy/Cargo.toml @@ -0,0 +1,54 @@ +[package] +name = "sp-consensus-beefy" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate" +description = "Primitives for BEEFY protocol." + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", default-features = false, optional = true, features = ["derive", "alloc"] } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../../application-crypto" } +sp-core = { version = "21.0.0", default-features = false, path = "../../core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../io" } +sp-mmr-primitives = { version = "4.0.0-dev", default-features = false, path = "../../merkle-mountain-range" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../std" } +strum = { version = "0.24.1", features = ["derive"], default-features = false } +lazy_static = "1.4.0" + +[dev-dependencies] +array-bytes = "6.1" +sp-keystore = { version = "0.27.0", path = "../../keystore" } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "serde/std", + "sp-api/std", + "sp-application-crypto/std", + "sp-core/std", + "sp-io/std", + "sp-mmr-primitives/std", + "sp-runtime/std", + "sp-std/std", +] + +# Serde support without relying on std features. +serde = [ + "dep:serde", + "scale-info/serde", + "sp-application-crypto/serde", + "sp-core/serde", + "sp-runtime/serde", +] diff --git a/primitives/beefy/src/commitment.rs b/primitives/consensus/beefy/src/commitment.rs similarity index 96% rename from primitives/beefy/src/commitment.rs rename to primitives/consensus/beefy/src/commitment.rs index 5765ff3609db..8ad3cc3721a0 100644 --- a/primitives/beefy/src/commitment.rs +++ b/primitives/consensus/beefy/src/commitment.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -77,6 +77,7 @@ where self.validator_set_id .cmp(&other.validator_set_id) .then_with(|| self.block_number.cmp(&other.block_number)) + .then_with(|| self.payload.cmp(&other.payload)) } } @@ -252,7 +253,7 @@ mod tests { use crate::{crypto, known_payloads, KEY_TYPE}; use codec::Decode; use sp_core::{keccak_256, Pair}; - use sp_keystore::{testing::KeyStore, SyncCryptoStore, SyncCryptoStorePtr}; + use sp_keystore::{testing::MemoryKeystore, KeystorePtr}; type TestCommitment = Commitment; type TestSignedCommitment = SignedCommitment; @@ -262,22 +263,16 @@ mod tests { // The mock signatures are equivalent to the ones produced by the BEEFY keystore fn mock_signatures() -> (crypto::Signature, crypto::Signature) { - let store: SyncCryptoStorePtr = KeyStore::new().into(); + let store: KeystorePtr = MemoryKeystore::new().into(); let alice = sp_core::ecdsa::Pair::from_string("//Alice", None).unwrap(); - let _ = - SyncCryptoStore::insert_unknown(&*store, KEY_TYPE, "//Alice", alice.public().as_ref()) - .unwrap(); + store.insert(KEY_TYPE, "//Alice", alice.public().as_ref()).unwrap(); let msg = keccak_256(b"This is the first message"); - let sig1 = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, &alice.public(), &msg) - .unwrap() - .unwrap(); + let sig1 = store.ecdsa_sign_prehashed(KEY_TYPE, &alice.public(), &msg).unwrap().unwrap(); let msg = keccak_256(b"This is the second message"); - let sig2 = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, &alice.public(), &msg) - .unwrap() - .unwrap(); + let sig2 = store.ecdsa_sign_prehashed(KEY_TYPE, &alice.public(), &msg).unwrap().unwrap(); (sig1.into(), sig2.into()) } diff --git a/primitives/beefy/src/lib.rs b/primitives/consensus/beefy/src/lib.rs similarity index 59% rename from primitives/beefy/src/lib.rs rename to primitives/consensus/beefy/src/lib.rs index eb7b8db89b21..6132c01187f0 100644 --- a/primitives/beefy/src/lib.rs +++ b/primitives/consensus/beefy/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,16 +34,20 @@ mod commitment; pub mod mmr; mod payload; +#[cfg(feature = "std")] +mod test_utils; pub mod witness; pub use commitment::{Commitment, SignedCommitment, VersionedFinalityProof}; pub use payload::{known_payloads, BeefyPayloadId, Payload, PayloadProvider}; +#[cfg(feature = "std")] +pub use test_utils::*; use codec::{Codec, Decode, Encode}; use scale_info::TypeInfo; use sp_application_crypto::RuntimeAppPublic; use sp_core::H256; -use sp_runtime::traits::Hash; +use sp_runtime::traits::{Hash, Keccak256, NumberFor}; use sp_std::prelude::*; /// Key type for BEEFY module. @@ -152,6 +156,8 @@ impl ValidatorSet { /// The index of an authority. pub type AuthorityIndex = u32; +/// The Hashing used within MMR. +pub type MmrHashing = Keccak256; /// The type used to represent an MMR root hash. pub type MmrRootHash = H256; @@ -173,7 +179,7 @@ pub enum ConsensusLog { /// /// A vote message is a direct vote created by a BEEFY node on every voting round /// and is gossiped to its peers. -#[derive(Debug, Decode, Encode, TypeInfo)] +#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)] pub struct VoteMessage { /// Commit to information extracted from a finalized block pub commitment: Commitment, @@ -183,6 +189,83 @@ pub struct VoteMessage { pub signature: Signature, } +/// Proof of voter misbehavior on a given set id. Misbehavior/equivocation in +/// BEEFY happens when a voter votes on the same round/block for different payloads. +/// Proving is achieved by collecting the signed commitments of conflicting votes. +#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)] +pub struct EquivocationProof { + /// The first vote in the equivocation. + pub first: VoteMessage, + /// The second vote in the equivocation. + pub second: VoteMessage, +} + +impl EquivocationProof { + /// Returns the authority id of the equivocator. + pub fn offender_id(&self) -> &Id { + &self.first.id + } + /// Returns the round number at which the equivocation occurred. + pub fn round_number(&self) -> &Number { + &self.first.commitment.block_number + } + /// Returns the set id at which the equivocation occurred. + pub fn set_id(&self) -> ValidatorSetId { + self.first.commitment.validator_set_id + } +} + +/// Check a commitment signature by encoding the commitment and +/// verifying the provided signature using the expected authority id. +pub fn check_commitment_signature( + commitment: &Commitment, + authority_id: &Id, + signature: &::Signature, +) -> bool +where + Id: BeefyAuthorityId, + Number: Clone + Encode + PartialEq, + MsgHash: Hash, +{ + let encoded_commitment = commitment.encode(); + BeefyAuthorityId::::verify(authority_id, signature, &encoded_commitment) +} + +/// Verifies the equivocation proof by making sure that both votes target +/// different blocks and that its signatures are valid. +pub fn check_equivocation_proof( + report: &EquivocationProof::Signature>, +) -> bool +where + Id: BeefyAuthorityId + PartialEq, + Number: Clone + Encode + PartialEq, + MsgHash: Hash, +{ + let first = &report.first; + let second = &report.second; + + // if votes + // come from different authorities, + // are for different rounds, + // have different validator set ids, + // or both votes have the same commitment, + // --> the equivocation is invalid. + if first.id != second.id || + first.commitment.block_number != second.commitment.block_number || + first.commitment.validator_set_id != second.commitment.validator_set_id || + first.commitment.payload == second.commitment.payload + { + return false + } + + // check signatures on both votes are valid + let valid_first = check_commitment_signature(&first.commitment, &first.id, &first.signature); + let valid_second = + check_commitment_signature(&second.commitment, &second.id, &second.signature); + + return valid_first && valid_second +} + /// New BEEFY validator set notification hook. pub trait OnNewValidatorSet { /// Function called by the pallet when BEEFY validator set changes. @@ -197,12 +280,68 @@ impl OnNewValidatorSet for () { fn on_new_validator_set(_: &ValidatorSet, _: &ValidatorSet) {} } +/// An opaque type used to represent the key ownership proof at the runtime API +/// boundary. The inner value is an encoded representation of the actual key +/// ownership proof which will be parameterized when defining the runtime. At +/// the runtime API boundary this type is unknown and as such we keep this +/// opaque representation, implementors of the runtime API will have to make +/// sure that all usages of `OpaqueKeyOwnershipProof` refer to the same type. +#[derive(Decode, Encode, PartialEq, TypeInfo)] +pub struct OpaqueKeyOwnershipProof(Vec); +impl OpaqueKeyOwnershipProof { + /// Create a new `OpaqueKeyOwnershipProof` using the given encoded + /// representation. + pub fn new(inner: Vec) -> OpaqueKeyOwnershipProof { + OpaqueKeyOwnershipProof(inner) + } + + /// Try to decode this `OpaqueKeyOwnershipProof` into the given concrete key + /// ownership proof type. + pub fn decode(self) -> Option { + codec::Decode::decode(&mut &self.0[..]).ok() + } +} + sp_api::decl_runtime_apis! { /// API necessary for BEEFY voters. + #[api_version(2)] pub trait BeefyApi { + /// Return the block number where BEEFY consensus is enabled/started + fn beefy_genesis() -> Option>; + /// Return the current active BEEFY validator set fn validator_set() -> Option>; + + /// Submits an unsigned extrinsic to report an equivocation. The caller + /// must provide the equivocation proof and a key ownership proof + /// (should be obtained using `generate_key_ownership_proof`). The + /// extrinsic will be unsigned and should only be accepted for local + /// authorship (not to be broadcast to the network). This method returns + /// `None` when creation of the extrinsic fails, e.g. if equivocation + /// reporting is disabled for the given runtime (i.e. this method is + /// hardcoded to return `None`). Only useful in an offchain context. + fn submit_report_equivocation_unsigned_extrinsic( + equivocation_proof: + EquivocationProof, crypto::AuthorityId, crypto::Signature>, + key_owner_proof: OpaqueKeyOwnershipProof, + ) -> Option<()>; + + /// Generates a proof of key ownership for the given authority in the + /// given set. An example usage of this module is coupled with the + /// session historical module to prove that a given authority key is + /// tied to a given staking identity during a specific session. Proofs + /// of key ownership are necessary for submitting equivocation reports. + /// NOTE: even though the API takes a `set_id` as parameter the current + /// implementations ignores this parameter and instead relies on this + /// method being called at the correct block height, i.e. any point at + /// which the given set id is live on-chain. Future implementations will + /// instead use indexed data through an offchain worker, not requiring + /// older states to be available. + fn generate_key_ownership_proof( + set_id: ValidatorSetId, + authority_id: crypto::AuthorityId, + ) -> Option; } } diff --git a/primitives/beefy/src/mmr.rs b/primitives/consensus/beefy/src/mmr.rs similarity index 96% rename from primitives/beefy/src/mmr.rs rename to primitives/consensus/beefy/src/mmr.rs index 549d2edbdf28..c303cae2fdcc 100644 --- a/primitives/beefy/src/mmr.rs +++ b/primitives/consensus/beefy/src/mmr.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -101,7 +101,7 @@ impl MmrLeafVersion { /// Details of a BEEFY authority set. #[derive(Debug, Default, PartialEq, Eq, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct BeefyAuthoritySet { /// Id of the set. /// @@ -145,7 +145,6 @@ mod mmr_root_provider { use crate::{known_payloads, payload::PayloadProvider, Payload}; use sp_api::{NumberFor, ProvideRuntimeApi}; use sp_mmr_primitives::MmrApi; - use sp_runtime::generic::BlockId; use sp_std::{marker::PhantomData, sync::Arc}; /// A [`crate::Payload`] provider where payload is Merkle Mountain Range root hash. @@ -170,11 +169,7 @@ mod mmr_root_provider { /// Simple wrapper that gets MMR root from header digests or from client state. fn mmr_root_from_digest_or_runtime(&self, header: &B::Header) -> Option { find_mmr_root_digest::(header).or_else(|| { - self.runtime - .runtime_api() - .mmr_root(&BlockId::hash(header.hash())) - .ok() - .and_then(|r| r.ok()) + self.runtime.runtime_api().mmr_root(header.hash()).ok().and_then(|r| r.ok()) }) } } diff --git a/primitives/beefy/src/payload.rs b/primitives/consensus/beefy/src/payload.rs similarity index 98% rename from primitives/beefy/src/payload.rs rename to primitives/consensus/beefy/src/payload.rs index 0f23c3f381f1..d520de445c95 100644 --- a/primitives/beefy/src/payload.rs +++ b/primitives/consensus/beefy/src/payload.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/consensus/beefy/src/test_utils.rs b/primitives/consensus/beefy/src/test_utils.rs new file mode 100644 index 000000000000..9e0758cdeb15 --- /dev/null +++ b/primitives/consensus/beefy/src/test_utils.rs @@ -0,0 +1,110 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg(feature = "std")] + +use crate::{crypto, Commitment, EquivocationProof, Payload, ValidatorSetId, VoteMessage}; +use codec::Encode; +use sp_core::{ecdsa, keccak_256, Pair}; +use std::collections::HashMap; +use strum::IntoEnumIterator; + +/// Set of test accounts using [`crate::crypto`] types. +#[allow(missing_docs)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::Display, strum::EnumIter)] +pub enum Keyring { + Alice, + Bob, + Charlie, + Dave, + Eve, + Ferdie, + One, + Two, +} + +impl Keyring { + /// Sign `msg`. + pub fn sign(self, msg: &[u8]) -> crypto::Signature { + // todo: use custom signature hashing type + let msg = keccak_256(msg); + ecdsa::Pair::from(self).sign_prehashed(&msg).into() + } + + /// Return key pair. + pub fn pair(self) -> crypto::Pair { + ecdsa::Pair::from_string(self.to_seed().as_str(), None).unwrap().into() + } + + /// Return public key. + pub fn public(self) -> crypto::Public { + self.pair().public() + } + + /// Return seed string. + pub fn to_seed(self) -> String { + format!("//{}", self) + } + + /// Get Keyring from public key. + pub fn from_public(who: &crypto::Public) -> Option { + Self::iter().find(|&k| &crypto::Public::from(k) == who) + } +} + +lazy_static::lazy_static! { + static ref PRIVATE_KEYS: HashMap = + Keyring::iter().map(|i| (i, i.pair())).collect(); + static ref PUBLIC_KEYS: HashMap = + PRIVATE_KEYS.iter().map(|(&name, pair)| (name, pair.public())).collect(); +} + +impl From for crypto::Pair { + fn from(k: Keyring) -> Self { + k.pair() + } +} + +impl From for ecdsa::Pair { + fn from(k: Keyring) -> Self { + k.pair().into() + } +} + +impl From for crypto::Public { + fn from(k: Keyring) -> Self { + (*PUBLIC_KEYS).get(&k).cloned().unwrap() + } +} + +/// Create a new `EquivocationProof` based on given arguments. +pub fn generate_equivocation_proof( + vote1: (u64, Payload, ValidatorSetId, &Keyring), + vote2: (u64, Payload, ValidatorSetId, &Keyring), +) -> EquivocationProof { + let signed_vote = |block_number: u64, + payload: Payload, + validator_set_id: ValidatorSetId, + keyring: &Keyring| { + let commitment = Commitment { validator_set_id, block_number, payload }; + let signature = keyring.sign(&commitment.encode()); + VoteMessage { commitment, id: keyring.public(), signature } + }; + let first = signed_vote(vote1.0, vote1.1, vote1.2, vote1.3); + let second = signed_vote(vote2.0, vote2.1, vote2.2, vote2.3); + EquivocationProof { first, second } +} diff --git a/primitives/beefy/src/witness.rs b/primitives/consensus/beefy/src/witness.rs similarity index 90% rename from primitives/beefy/src/witness.rs rename to primitives/consensus/beefy/src/witness.rs index 2c45e0ade90c..ff9a0401dc63 100644 --- a/primitives/beefy/src/witness.rs +++ b/primitives/consensus/beefy/src/witness.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -74,9 +74,8 @@ impl SignedCommitmentWitness (crypto::Signature, crypto::Signature) { - let store: SyncCryptoStorePtr = KeyStore::new().into(); + let store: KeystorePtr = MemoryKeystore::new().into(); let alice = sp_core::ecdsa::Pair::from_string("//Alice", None).unwrap(); - let _ = - SyncCryptoStore::insert_unknown(&*store, KEY_TYPE, "//Alice", alice.public().as_ref()) - .unwrap(); + store.insert(KEY_TYPE, "//Alice", alice.public().as_ref()).unwrap(); let msg = keccak_256(b"This is the first message"); - let sig1 = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, &alice.public(), &msg) - .unwrap() - .unwrap(); + let sig1 = store.ecdsa_sign_prehashed(KEY_TYPE, &alice.public(), &msg).unwrap().unwrap(); let msg = keccak_256(b"This is the second message"); - let sig2 = SyncCryptoStore::ecdsa_sign_prehashed(&*store, KEY_TYPE, &alice.public(), &msg) - .unwrap() - .unwrap(); + let sig2 = store.ecdsa_sign_prehashed(KEY_TYPE, &alice.public(), &msg).unwrap().unwrap(); (sig1.into(), sig2.into()) } diff --git a/primitives/beefy/test-res/large-raw-commitment b/primitives/consensus/beefy/test-res/large-raw-commitment similarity index 100% rename from primitives/beefy/test-res/large-raw-commitment rename to primitives/consensus/beefy/test-res/large-raw-commitment diff --git a/primitives/consensus/common/Cargo.toml b/primitives/consensus/common/Cargo.toml index 83b09f9eb47e..1f52fb1d44f7 100644 --- a/primitives/consensus/common/Cargo.toml +++ b/primitives/consensus/common/Cargo.toml @@ -15,18 +15,13 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = "0.1.57" -codec = { package = "parity-scale-codec", version = "3.2.2", features = [ - "derive", -] } futures = { version = "0.3.21", features = ["thread-pool"] } log = "0.4.17" thiserror = "1.0.30" -sp-core = { version = "7.0.0", path = "../../core" } +sp-core = { version = "21.0.0", path = "../../core" } sp-inherents = { version = "4.0.0-dev", path = "../../inherents" } -sp-runtime = { version = "7.0.0", path = "../../runtime" } -sp-state-machine = { version = "0.13.0", path = "../../state-machine" } -sp-std = { version = "5.0.0", path = "../../std" } -sp-version = { version = "5.0.0", path = "../../version" } +sp-runtime = { version = "24.0.0", path = "../../runtime" } +sp-state-machine = { version = "0.28.0", path = "../../state-machine" } [dev-dependencies] futures = "0.3.21" diff --git a/primitives/consensus/common/src/block_validation.rs b/primitives/consensus/common/src/block_validation.rs index 1dc42104913c..91e5330bbb06 100644 --- a/primitives/consensus/common/src/block_validation.rs +++ b/primitives/consensus/common/src/block_validation.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/consensus/common/src/error.rs b/primitives/consensus/common/src/error.rs index 0656b5761fb3..fb8d0447fe3d 100644 --- a/primitives/consensus/common/src/error.rs +++ b/primitives/consensus/common/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,85 +15,42 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Error types in Consensus -use sp_core::ed25519::Public; -use sp_version::RuntimeVersion; -use std::error; +//! Error types for consensus modules. /// Result type alias. pub type Result = std::result::Result; -/// Error type. +/// The error type for consensus-related operations. #[derive(Debug, thiserror::Error)] -#[non_exhaustive] pub enum Error { /// Missing state at block with given descriptor. #[error("State unavailable at block {0}")] StateUnavailable(String), - /// I/O terminated unexpectedly - #[error("I/O terminated unexpectedly.")] - IoTerminated, /// Intermediate missing. - #[error("Missing intermediate.")] + #[error("Missing intermediate")] NoIntermediate, /// Intermediate is of wrong type. - #[error("Invalid intermediate.")] + #[error("Invalid intermediate")] InvalidIntermediate, - /// Unable to schedule wake-up. - #[error("Timer error: {0}")] - FaultyTimer(#[from] std::io::Error), - /// Error while working with inherent data. - #[error("InherentData error: {0}")] - InherentData(#[from] sp_inherents::Error), - /// Unable to propose a block. - #[error("Unable to create block proposal.")] - CannotPropose, - /// Error checking signature - #[error("Message signature {0:?} by {1:?} is invalid.")] + /// Error checking signature. + #[error("Message signature {0:?} by {1:?} is invalid")] InvalidSignature(Vec, Vec), /// Invalid authorities set received from the runtime. #[error("Current state of blockchain has invalid authorities set")] InvalidAuthoritiesSet, - /// Account is not an authority. - #[error("Message sender {0:?} is not a valid authority")] - InvalidAuthority(Public), - /// Authoring interface does not match the runtime. - #[error( - "Authoring for current \ - runtime is not supported. Native ({native}) cannot author for on-chain ({on_chain})." - )] - IncompatibleAuthoringRuntime { native: RuntimeVersion, on_chain: RuntimeVersion }, - /// Authoring interface does not match the runtime. - #[error("Authoring for current runtime is not supported since it has no version.")] - RuntimeVersionMissing, - /// Authoring interface does not match the runtime. - #[error("Authoring in current build is not supported since it has no runtime.")] - NativeRuntimeMissing, /// Justification requirements not met. - #[error("Invalid justification.")] + #[error("Invalid justification")] InvalidJustification, - /// Some other error. - #[error(transparent)] - Other(#[from] Box), - /// Error from the client while importing + /// Error from the client while importing. #[error("Import failed: {0}")] ClientImport(String), - /// Error from the client while importing + /// Error from the client while fetching some data from the chain. #[error("Chain lookup failed: {0}")] ChainLookup(String), - /// Signing failed - #[error("Failed to sign using key: {0:?}. Reason: {1}")] - CannotSign(Vec, String), -} - -impl From for Error { - fn from(p: Public) -> Self { - Self::InvalidAuthority(p) - } -} - -impl From for Error { - fn from(s: String) -> Self { - Self::StateUnavailable(s) - } + /// Signing failed. + #[error("Failed to sign: {0}")] + CannotSign(String), + /// Some other error. + #[error(transparent)] + Other(#[from] Box), } diff --git a/primitives/consensus/common/src/lib.rs b/primitives/consensus/common/src/lib.rs index 458a5eee259a..d47f3bafc422 100644 --- a/primitives/consensus/common/src/lib.rs +++ b/primitives/consensus/common/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,9 +39,6 @@ pub use select_chain::SelectChain; pub use sp_inherents::InherentData; pub use sp_state_machine::Backend as StateBackend; -/// Type of keys in the blockchain cache that consensus module could use for its needs. -pub type CacheKeyId = [u8; 4]; - /// Block status. #[derive(Debug, PartialEq, Eq)] pub enum BlockStatus { @@ -74,16 +71,6 @@ pub enum BlockOrigin { File, } -impl From for sp_core::ExecutionContext { - fn from(origin: BlockOrigin) -> Self { - if origin == BlockOrigin::NetworkInitialSync { - sp_core::ExecutionContext::Syncing - } else { - sp_core::ExecutionContext::Importing - } - } -} - /// Environment for a Consensus instance. /// /// Creates proposer instance. diff --git a/primitives/consensus/common/src/select_chain.rs b/primitives/consensus/common/src/select_chain.rs index f366cd34c51e..d387cc1ade09 100644 --- a/primitives/consensus/common/src/select_chain.rs +++ b/primitives/consensus/common/src/select_chain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -43,14 +43,14 @@ pub trait SelectChain: Sync + Send + Clone { /// finalize. async fn best_chain(&self) -> Result<::Header, Error>; - /// Get the best descendent of `target_hash` that we should attempt to - /// finalize next, if any. It is valid to return the given `target_hash` + /// Get the best descendent of `base_hash` that we should attempt to + /// finalize next, if any. It is valid to return the given `base_hash` /// itself if no better descendent exists. async fn finality_target( &self, - target_hash: ::Hash, + base_hash: ::Hash, _maybe_max_number: Option>, ) -> Result<::Hash, Error> { - Ok(target_hash) + Ok(base_hash) } } diff --git a/primitives/consensus/grandpa/Cargo.toml b/primitives/consensus/grandpa/Cargo.toml new file mode 100644 index 000000000000..00a5eedfc506 --- /dev/null +++ b/primitives/consensus/grandpa/Cargo.toml @@ -0,0 +1,52 @@ +[package] +name = "sp-consensus-grandpa" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Primitives for GRANDPA integration, suitable for WASM compilation." +documentation = "https://docs.rs/sp-consensus-grandpa" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +grandpa = { package = "finality-grandpa", version = "0.16.2", default-features = false, features = ["derive-codec"] } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", features = ["derive", "alloc"], default-features = false, optional = true } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../../application-crypto" } +sp-core = { version = "21.0.0", default-features = false, path = "../../core" } +sp-keystore = { version = "0.27.0", default-features = false, optional = true, path = "../../keystore" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../std" } + +[features] +default = ["std"] +std = [ + "codec/std", + "grandpa/std", + "log/std", + "scale-info/std", + "serde/std", + "sp-api/std", + "sp-application-crypto/std", + "sp-core/std", + "sp-keystore", + "sp-runtime/std", + "sp-std/std", +] + +# Serde support without relying on std features. +serde = [ + "dep:serde", + "scale-info/serde", + "sp-application-crypto/serde", + "sp-core/serde", + "sp-runtime/serde", +] diff --git a/primitives/finality-grandpa/README.md b/primitives/consensus/grandpa/README.md similarity index 100% rename from primitives/finality-grandpa/README.md rename to primitives/consensus/grandpa/README.md diff --git a/primitives/finality-grandpa/src/lib.rs b/primitives/consensus/grandpa/src/lib.rs similarity index 96% rename from primitives/finality-grandpa/src/lib.rs rename to primitives/consensus/grandpa/src/lib.rs index 7b3b55f49cee..baeaee4738e4 100644 --- a/primitives/finality-grandpa/src/lib.rs +++ b/primitives/consensus/grandpa/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,13 +22,13 @@ #[cfg(not(feature = "std"))] extern crate alloc; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::Serialize; use codec::{Codec, Decode, Encode, Input}; use scale_info::TypeInfo; #[cfg(feature = "std")] -use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; +use sp_keystore::KeystorePtr; use sp_runtime::{ traits::{Header as HeaderT, NumberFor}, ConsensusEngineId, RuntimeDebug, @@ -140,8 +140,8 @@ pub struct GrandpaJustification { } /// A scheduled change of authority set. -#[cfg_attr(feature = "std", derive(Serialize))] #[derive(Clone, Eq, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "serde", derive(Serialize))] pub struct ScheduledChange { /// The new authorities after the change, along with their respective weights. pub next_authorities: AuthorityList, @@ -150,8 +150,8 @@ pub struct ScheduledChange { } /// An consensus log item for GRANDPA. -#[cfg_attr(feature = "std", derive(Serialize))] #[derive(Decode, Encode, PartialEq, Eq, Clone, RuntimeDebug)] +#[cfg_attr(feature = "serde", derive(Serialize))] pub enum ConsensusLog { /// Schedule an authority set change. /// @@ -234,7 +234,7 @@ impl ConsensusLog { /// GRANDPA happens when a voter votes on the same round (either at prevote or /// precommit stage) for different blocks. Proving is achieved by collecting the /// signed messages of conflicting votes. -#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)] +#[derive(Clone, Debug, Decode, Encode, PartialEq, Eq, TypeInfo)] pub struct EquivocationProof { set_id: SetId, equivocation: Equivocation, @@ -268,7 +268,7 @@ impl EquivocationProof { /// Wrapper object for GRANDPA equivocation proofs, useful for unifying prevote /// and precommit equivocations under a common type. -#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)] +#[derive(Clone, Debug, Decode, Encode, PartialEq, Eq, TypeInfo)] pub enum Equivocation { /// Proof of equivocation at prevote stage. Prevote(grandpa::Equivocation, AuthoritySignature>), @@ -441,7 +441,7 @@ where /// Localizes the message to the given set and round and signs the payload. #[cfg(feature = "std")] pub fn sign_message( - keystore: SyncCryptoStorePtr, + keystore: KeystorePtr, message: grandpa::Message, public: AuthorityId, round: RoundNumber, @@ -451,20 +451,15 @@ where H: Encode, N: Encode, { - use sp_application_crypto::AppKey; - use sp_core::crypto::Public; + use sp_application_crypto::AppCrypto; let encoded = localized_payload(round, set_id, &message); - let signature = SyncCryptoStore::sign_with( - &*keystore, - AuthorityId::ID, - &public.to_public_crypto_pair(), - &encoded[..], - ) - .ok() - .flatten()? - .try_into() - .ok()?; + let signature = keystore + .ed25519_sign(AuthorityId::ID, public.as_ref(), &encoded[..]) + .ok() + .flatten()? + .try_into() + .ok()?; Some(grandpa::SignedMessage { message, signature, id: public }) } @@ -529,7 +524,7 @@ impl<'a> Decode for VersionedAuthorityList<'a> { /// the runtime API boundary this type is unknown and as such we keep this /// opaque representation, implementors of the runtime API will have to make /// sure that all usages of `OpaqueKeyOwnershipProof` refer to the same type. -#[derive(Decode, Encode, PartialEq)] +#[derive(Decode, Encode, PartialEq, TypeInfo)] pub struct OpaqueKeyOwnershipProof(Vec); impl OpaqueKeyOwnershipProof { diff --git a/primitives/consensus/pow/Cargo.toml b/primitives/consensus/pow/Cargo.toml index 6cc832b5afa5..1c8c75c4b193 100644 --- a/primitives/consensus/pow/Cargo.toml +++ b/primitives/consensus/pow/Cargo.toml @@ -13,11 +13,11 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../api" } -sp-core = { version = "7.0.0", default-features = false, path = "../../core" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../core" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../std" } [features] default = ["std"] diff --git a/primitives/consensus/pow/src/lib.rs b/primitives/consensus/pow/src/lib.rs index fe10ee808db9..f37aae1c5c01 100644 --- a/primitives/consensus/pow/src/lib.rs +++ b/primitives/consensus/pow/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/consensus/slots/Cargo.toml b/primitives/consensus/slots/Cargo.toml index a4d85dff6ea8..a0404f46abff 100644 --- a/primitives/consensus/slots/Cargo.toml +++ b/primitives/consensus/slots/Cargo.toml @@ -13,10 +13,10 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive", "max-encoded-len"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } scale-info = { version = "2.0.0", default-features = false, features = ["derive"] } -serde = { version = "1.0", features = ["derive"], optional = true } -sp-std = { version = "5.0.0", default-features = false, path = "../../std" } +serde = { version = "1.0", default-features = false, features = ["derive", "alloc"], optional = true } +sp-std = { version = "8.0.0", default-features = false, path = "../../std" } sp-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../timestamp" } [features] @@ -24,7 +24,13 @@ default = ["std"] std = [ "codec/std", "scale-info/std", - "serde", + "serde/std", "sp-std/std", "sp-timestamp/std", ] + +# Serde support without relying on std features. +serde = [ + "dep:serde", + "scale-info/serde", +] diff --git a/primitives/consensus/slots/src/lib.rs b/primitives/consensus/slots/src/lib.rs index 21b3cad1e716..30bb42e2c758 100644 --- a/primitives/consensus/slots/src/lib.rs +++ b/primitives/consensus/slots/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +25,7 @@ use sp_timestamp::Timestamp; /// Unit type wrapper that represents a slot. #[derive(Debug, Encode, MaxEncodedLen, Decode, Eq, Clone, Copy, Default, Ord, TypeInfo)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Slot(u64); impl core::ops::Deref for Slot { @@ -130,7 +130,7 @@ impl SlotDuration { /// produces more than one block on the same slot. The proof of equivocation /// are the given distinct headers that were signed by the validator and which /// include the slot number. -#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo)] +#[derive(Clone, Debug, Decode, Encode, PartialEq, TypeInfo, Eq)] pub struct EquivocationProof { /// Returns the authority id of the equivocator. pub offender: Id, diff --git a/primitives/consensus/vrf/Cargo.toml b/primitives/consensus/vrf/Cargo.toml deleted file mode 100644 index a4f92c30cca0..000000000000 --- a/primitives/consensus/vrf/Cargo.toml +++ /dev/null @@ -1,32 +0,0 @@ -[package] -name = "sp-consensus-vrf" -version = "0.10.0-dev" -authors = ["Parity Technologies "] -description = "Primitives for VRF based consensus" -edition = "2021" -license = "Apache-2.0" -repository = "https://github.com/paritytech/substrate/" -homepage = "https://substrate.io" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -scale-info = { version = "2.1.1", default-features = false } -schnorrkel = { version = "0.9.1", default-features = false, features = ["preaudit_deprecated", "u64_backend"] } -sp-core = { version = "7.0.0", default-features = false, path = "../../core" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../../std" } - -[features] -default = ["std"] -std = [ - "codec/std", - "scale-info/std", - "schnorrkel/std", - "sp-core/std", - "sp-runtime/std", - "sp-std/std", -] diff --git a/primitives/consensus/vrf/README.md b/primitives/consensus/vrf/README.md deleted file mode 100644 index d66490e023b3..000000000000 --- a/primitives/consensus/vrf/README.md +++ /dev/null @@ -1,3 +0,0 @@ -Primitives for VRF-based consensus engines. - -License: Apache-2.0 \ No newline at end of file diff --git a/primitives/consensus/vrf/src/schnorrkel.rs b/primitives/consensus/vrf/src/schnorrkel.rs deleted file mode 100644 index 8666de6c4bc0..000000000000 --- a/primitives/consensus/vrf/src/schnorrkel.rs +++ /dev/null @@ -1,188 +0,0 @@ -// 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. - -//! Schnorrkel-based VRF. - -use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; -use scale_info::TypeInfo; -use schnorrkel::errors::MultiSignatureStage; -use sp_core::U512; -use sp_std::{ - ops::{Deref, DerefMut}, - prelude::*, -}; - -pub use schnorrkel::{ - vrf::{VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH}, - PublicKey, SignatureError, -}; - -/// The length of the Randomness. -pub const RANDOMNESS_LENGTH: usize = VRF_OUTPUT_LENGTH; - -/// VRF output type available for `std` environment, suitable for schnorrkel operations. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct VRFOutput(pub schnorrkel::vrf::VRFOutput); - -impl Deref for VRFOutput { - type Target = schnorrkel::vrf::VRFOutput; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for VRFOutput { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Encode for VRFOutput { - fn encode(&self) -> Vec { - self.0.as_bytes().encode() - } -} - -impl EncodeLike for VRFOutput {} - -impl Decode for VRFOutput { - fn decode(i: &mut R) -> Result { - let decoded = <[u8; VRF_OUTPUT_LENGTH]>::decode(i)?; - Ok(Self(schnorrkel::vrf::VRFOutput::from_bytes(&decoded).map_err(convert_error)?)) - } -} - -impl MaxEncodedLen for VRFOutput { - fn max_encoded_len() -> usize { - <[u8; VRF_OUTPUT_LENGTH]>::max_encoded_len() - } -} - -impl TypeInfo for VRFOutput { - type Identity = [u8; VRF_OUTPUT_LENGTH]; - - fn type_info() -> scale_info::Type { - Self::Identity::type_info() - } -} - -impl TryFrom<[u8; VRF_OUTPUT_LENGTH]> for VRFOutput { - type Error = SignatureError; - - fn try_from(raw: [u8; VRF_OUTPUT_LENGTH]) -> Result { - schnorrkel::vrf::VRFOutput::from_bytes(&raw).map(VRFOutput) - } -} - -/// VRF proof type available for `std` environment, suitable for schnorrkel operations. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct VRFProof(pub schnorrkel::vrf::VRFProof); - -impl PartialOrd for VRFProof { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for VRFProof { - fn cmp(&self, other: &Self) -> core::cmp::Ordering { - U512::from(self.0.to_bytes()).cmp(&U512::from(other.0.to_bytes())) - } -} - -impl Deref for VRFProof { - type Target = schnorrkel::vrf::VRFProof; - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl DerefMut for VRFProof { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.0 - } -} - -impl Encode for VRFProof { - fn encode(&self) -> Vec { - self.0.to_bytes().encode() - } -} - -impl EncodeLike for VRFProof {} - -impl Decode for VRFProof { - fn decode(i: &mut R) -> Result { - let decoded = <[u8; VRF_PROOF_LENGTH]>::decode(i)?; - Ok(Self(schnorrkel::vrf::VRFProof::from_bytes(&decoded).map_err(convert_error)?)) - } -} - -impl MaxEncodedLen for VRFProof { - fn max_encoded_len() -> usize { - <[u8; VRF_PROOF_LENGTH]>::max_encoded_len() - } -} - -impl TypeInfo for VRFProof { - type Identity = [u8; VRF_PROOF_LENGTH]; - - fn type_info() -> scale_info::Type { - Self::Identity::type_info() - } -} - -impl TryFrom<[u8; VRF_PROOF_LENGTH]> for VRFProof { - type Error = SignatureError; - - fn try_from(raw: [u8; VRF_PROOF_LENGTH]) -> Result { - schnorrkel::vrf::VRFProof::from_bytes(&raw).map(VRFProof) - } -} - -fn convert_error(e: SignatureError) -> codec::Error { - use MultiSignatureStage::*; - use SignatureError::*; - match e { - EquationFalse => "Signature error: `EquationFalse`".into(), - PointDecompressionError => "Signature error: `PointDecompressionError`".into(), - ScalarFormatError => "Signature error: `ScalarFormatError`".into(), - NotMarkedSchnorrkel => "Signature error: `NotMarkedSchnorrkel`".into(), - BytesLengthError { .. } => "Signature error: `BytesLengthError`".into(), - MuSigAbsent { musig_stage: Commitment } => - "Signature error: `MuSigAbsent` at stage `Commitment`".into(), - MuSigAbsent { musig_stage: Reveal } => - "Signature error: `MuSigAbsent` at stage `Reveal`".into(), - MuSigAbsent { musig_stage: Cosignature } => - "Signature error: `MuSigAbsent` at stage `Commitment`".into(), - MuSigInconsistent { musig_stage: Commitment, duplicate: true } => - "Signature error: `MuSigInconsistent` at stage `Commitment` on duplicate".into(), - MuSigInconsistent { musig_stage: Commitment, duplicate: false } => - "Signature error: `MuSigInconsistent` at stage `Commitment` on not duplicate".into(), - MuSigInconsistent { musig_stage: Reveal, duplicate: true } => - "Signature error: `MuSigInconsistent` at stage `Reveal` on duplicate".into(), - MuSigInconsistent { musig_stage: Reveal, duplicate: false } => - "Signature error: `MuSigInconsistent` at stage `Reveal` on not duplicate".into(), - MuSigInconsistent { musig_stage: Cosignature, duplicate: true } => - "Signature error: `MuSigInconsistent` at stage `Cosignature` on duplicate".into(), - MuSigInconsistent { musig_stage: Cosignature, duplicate: false } => - "Signature error: `MuSigInconsistent` at stage `Cosignature` on not duplicate".into(), - } -} - -/// Schnorrkel randomness value. Same size as `VRFOutput`. -pub type Randomness = [u8; RANDOMNESS_LENGTH]; diff --git a/primitives/core/Cargo.toml b/primitives/core/Cargo.toml index f9a522178f4c..f02489a09f05 100644 --- a/primitives/core/Cargo.toml +++ b/primitives/core/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-core" -version = "7.0.0" +version = "21.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -13,19 +13,17 @@ documentation = "https://docs.rs/sp-core" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = [ - "derive", - "max-encoded-len", -] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive","max-encoded-len"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } -serde = { version = "1.0.136", optional = true, features = ["derive"] } +serde = { version = "1.0.163", optional = true, default-features = false, features = ["derive", "alloc"] } +bounded-collections = { version = "0.1.8", default-features = false } primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "scale-info"] } -impl-serde = { version = "0.4.0", optional = true } -hash-db = { version = "0.15.2", default-features = false } +impl-serde = { version = "0.4.0", default-features = false, optional = true } +hash-db = { version = "0.16.0", default-features = false } hash256-std-hasher = { version = "0.15.2", default-features = false } -base58 = { version = "0.2.0", optional = true } -rand = { version = "0.8.5", optional = true, features = ["small_rng"] } +bs58 = { version = "0.4.0", default-features = false, optional = true } +rand = { version = "0.8.5", features = ["small_rng"], optional = true } substrate-bip39 = { version = "0.4.4", optional = true } tiny-bip39 = { version = "1.0.0", optional = true } regex = { version = "1.6.0", optional = true } @@ -33,36 +31,36 @@ zeroize = { version = "1.4.3", default-features = false } secrecy = { version = "0.8.0", default-features = false } lazy_static = { version = "1.4.0", default-features = false, optional = true } parking_lot = { version = "0.12.1", optional = true } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } -sp-debug-derive = { version = "5.0.0", default-features = false, path = "../debug-derive" } -sp-storage = { version = "7.0.0", default-features = false, path = "../storage" } -sp-externalities = { version = "0.13.0", optional = true, path = "../externalities" } +ss58-registry = { version = "1.34.0", default-features = false } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } +sp-debug-derive = { version = "8.0.0", default-features = false, path = "../debug-derive" } +sp-storage = { version = "13.0.0", default-features = false, path = "../storage" } +sp-externalities = { version = "0.19.0", optional = true, path = "../externalities" } futures = { version = "0.3.21", optional = true } dyn-clonable = { version = "0.9.0", optional = true } thiserror = { version = "1.0.30", optional = true } +tracing = { version = "0.1.29", optional = true } bitflags = "1.3" +paste = "1.0.7" # full crypto -array-bytes = { version = "4.1", optional = true } +array-bytes = { version = "6.1", optional = true } ed25519-zebra = { version = "3.1.0", default-features = false, optional = true } blake2 = { version = "0.10.4", default-features = false, optional = true } -schnorrkel = { version = "0.9.1", features = [ - "preaudit_deprecated", - "u64_backend", -], default-features = false, optional = true } libsecp256k1 = { version = "0.7", default-features = false, features = ["static-context"], optional = true } -merlin = { version = "2.0", default-features = false, optional = true } +schnorrkel = { version = "0.9.1", features = ["preaudit_deprecated", "u64_backend"], default-features = false } +merlin = { version = "2.0", default-features = false } secp256k1 = { version = "0.24.0", default-features = false, features = ["recovery", "alloc"], optional = true } -ss58-registry = { version = "1.34.0", default-features = false } -sp-core-hashing = { version = "5.0.0", path = "./hashing", default-features = false, optional = true } -sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../runtime-interface" } +sp-core-hashing = { version = "9.0.0", path = "./hashing", default-features = false, optional = true } +sp-runtime-interface = { version = "17.0.0", default-features = false, path = "../runtime-interface" } +# bls crypto +w3f-bls = { version = "0.1.3", default-features = false, optional = true} [dev-dependencies] -sp-serializer = { version = "4.0.0-dev", path = "../serializer" } -rand = "0.8.5" -criterion = "0.3.3" +criterion = "0.4.0" serde_json = "1.0" -sp-core-hashing-proc-macro = { version = "5.0.0", path = "./hashing/proc-macro" } +sp-core-hashing-proc-macro = { version = "9.0.0", path = "./hashing/proc-macro" } +hex-literal = "0.3.4" [[bench]] name = "bench" @@ -74,27 +72,28 @@ bench = false [features] default = ["std"] std = [ - "merlin?/std", + "merlin/std", "full_crypto", "log/std", "thiserror", "lazy_static", "parking_lot", + "bounded-collections/std", "primitive-types/std", "primitive-types/serde", "primitive-types/byteorder", "primitive-types/rustc-hex", - "impl-serde", + "impl-serde/std", "codec/std", "scale-info/std", "hash256-std-hasher/std", "hash-db/std", "sp-std/std", - "serde", + "serde/std", "blake2/std", "array-bytes", "ed25519-zebra/std", - "base58", + "bs58/std", "substrate-bip39", "tiny-bip39", "rand", @@ -114,6 +113,22 @@ std = [ "futures/thread-pool", "libsecp256k1/std", "dyn-clonable", + "tracing", +] + +# Serde support without relying on std features. +serde = [ + "dep:serde", + "array-bytes", + "blake2", + "bounded-collections/serde", + "bs58/alloc", + "scale-info/serde", + "secrecy/alloc", + "impl-serde", + "primitive-types/serde_no_std", + "sp-storage/serde", + "sp-core-hashing", ] # This feature enables all crypto primitives for `no_std` builds like microcontrollers @@ -123,10 +138,12 @@ full_crypto = [ "array-bytes", "ed25519-zebra", "blake2", - "schnorrkel", "libsecp256k1", "secp256k1", "sp-core-hashing", "sp-runtime-interface/disable_target_static_assertions", - "merlin", ] + +# This feature adds BLS crypto primitives. It should not be used in production since +# the BLS implementation and interface may still be subject to significant change. +bls-experimental = ["w3f-bls"] diff --git a/primitives/core/benches/bench.rs b/primitives/core/benches/bench.rs index 53421278dca2..e91c1758c3cb 100644 --- a/primitives/core/benches/bench.rs +++ b/primitives/core/benches/bench.rs @@ -1,4 +1,4 @@ -// Copyright 2019-2020 Parity Technologies +// Copyright Parity Technologies (UK) Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -12,10 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -#[macro_use] -extern crate criterion; - -use criterion::{black_box, Bencher, BenchmarkId, Criterion}; +use criterion::{black_box, criterion_group, criterion_main, Bencher, BenchmarkId, Criterion}; use sp_core::{ crypto::Pair as _, hashing::{blake2_128, twox_128}, diff --git a/primitives/core/hashing/Cargo.toml b/primitives/core/hashing/Cargo.toml index 1bb67ffff514..9b7fdabd3d48 100644 --- a/primitives/core/hashing/Cargo.toml +++ b/primitives/core/hashing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-core-hashing" -version = "5.0.0" +version = "9.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -13,22 +13,20 @@ documentation = "https://docs.rs/sp-core-hashing" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -blake2 = { version = "0.10.4", default-features = false } +blake2b_simd = { version = "1.0.1", default-features = false } byteorder = { version = "1.3.2", default-features = false } digest = { version = "0.10.3", default-features = false } sha2 = { version = "0.10.2", default-features = false } sha3 = { version = "0.10.0", default-features = false } twox-hash = { version = "1.6.3", default-features = false, features = ["digest_0_10"] } -sp-std = { version = "5.0.0", default-features = false, path = "../../std" } [features] default = ["std"] std = [ "digest/std", - "blake2/std", + "blake2b_simd/std", "byteorder/std", "sha2/std", "sha3/std", - "sp-std/std", "twox-hash/std", ] diff --git a/primitives/core/hashing/proc-macro/Cargo.toml b/primitives/core/hashing/proc-macro/Cargo.toml index 6d9747de871c..35bb78249d11 100644 --- a/primitives/core/hashing/proc-macro/Cargo.toml +++ b/primitives/core/hashing/proc-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-core-hashing-proc-macro" -version = "5.0.0" +version = "9.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -16,7 +16,6 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -proc-macro2 = "1.0.37" -quote = "1.0.6" -syn = { version = "1.0.98", features = ["full", "parsing"] } -sp-core-hashing = { version = "5.0.0", default-features = false, path = "../" } +quote = "1.0.28" +syn = { version = "2.0.16", features = ["full", "parsing"] } +sp-core-hashing = { version = "9.0.0", default-features = false, path = "../" } diff --git a/primitives/core/hashing/proc-macro/src/impls.rs b/primitives/core/hashing/proc-macro/src/impls.rs index 3058cf019b14..714852ae3594 100644 --- a/primitives/core/hashing/proc-macro/src/impls.rs +++ b/primitives/core/hashing/proc-macro/src/impls.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/core/hashing/proc-macro/src/lib.rs b/primitives/core/hashing/proc-macro/src/lib.rs index 2db292d8dc0c..69668cadb4e2 100644 --- a/primitives/core/hashing/proc-macro/src/lib.rs +++ b/primitives/core/hashing/proc-macro/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/core/hashing/src/lib.rs b/primitives/core/hashing/src/lib.rs index e6ccd5aaa8fb..33d777f85b01 100644 --- a/primitives/core/hashing/src/lib.rs +++ b/primitives/core/hashing/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,60 +23,41 @@ use core::hash::Hasher; use byteorder::{ByteOrder, LittleEndian}; -use digest::{ - consts::{U16, U32, U8}, - Digest, -}; +use digest::Digest; + +#[inline(always)] +fn blake2(data: &[u8]) -> [u8; N] { + blake2b_simd::Params::new() + .hash_length(N) + .hash(data) + .as_bytes() + .try_into() + .expect("slice is always the necessary length") +} /// Do a Blake2 512-bit hash and place result in `dest`. pub fn blake2_512_into(data: &[u8], dest: &mut [u8; 64]) { - dest.copy_from_slice(blake2::Blake2b512::digest(data).as_slice()); + *dest = blake2(data); } /// Do a Blake2 512-bit hash and return result. pub fn blake2_512(data: &[u8]) -> [u8; 64] { - let mut r = [0; 64]; - blake2_512_into(data, &mut r); - r -} - -/// Do a Blake2 256-bit hash and place result in `dest`. -pub fn blake2_256_into(data: &[u8], dest: &mut [u8; 32]) { - type Blake2b256 = blake2::Blake2b; - dest.copy_from_slice(Blake2b256::digest(data).as_slice()); + blake2(data) } /// Do a Blake2 256-bit hash and return result. pub fn blake2_256(data: &[u8]) -> [u8; 32] { - let mut r = [0; 32]; - blake2_256_into(data, &mut r); - r -} - -/// Do a Blake2 128-bit hash and place result in `dest`. -pub fn blake2_128_into(data: &[u8], dest: &mut [u8; 16]) { - type Blake2b128 = blake2::Blake2b; - dest.copy_from_slice(Blake2b128::digest(data).as_slice()); + blake2(data) } /// Do a Blake2 128-bit hash and return result. pub fn blake2_128(data: &[u8]) -> [u8; 16] { - let mut r = [0; 16]; - blake2_128_into(data, &mut r); - r -} - -/// Do a Blake2 64-bit hash and place result in `dest`. -pub fn blake2_64_into(data: &[u8], dest: &mut [u8; 8]) { - type Blake2b64 = blake2::Blake2b; - dest.copy_from_slice(Blake2b64::digest(data).as_slice()); + blake2(data) } /// Do a Blake2 64-bit hash and return result. pub fn blake2_64(data: &[u8]) -> [u8; 8] { - let mut r = [0; 8]; - blake2_64_into(data, &mut r); - r + blake2(data) } /// Do a XX 64-bit hash and place result in `dest`. @@ -128,21 +109,15 @@ pub fn twox_256(data: &[u8]) -> [u8; 32] { /// Do a keccak 256-bit hash and return result. pub fn keccak_256(data: &[u8]) -> [u8; 32] { - let mut output = [0u8; 32]; - output.copy_from_slice(sha3::Keccak256::digest(data).as_slice()); - output + sha3::Keccak256::digest(data).into() } /// Do a keccak 512-bit hash and return result. pub fn keccak_512(data: &[u8]) -> [u8; 64] { - let mut output = [0u8; 64]; - output.copy_from_slice(sha3::Keccak512::digest(data).as_slice()); - output + sha3::Keccak512::digest(data).into() } /// Do a sha2 256-bit hash and return result. pub fn sha2_256(data: &[u8]) -> [u8; 32] { - let mut output = [0u8; 32]; - output.copy_from_slice(sha2::Sha256::digest(data).as_slice()); - output + sha2::Sha256::digest(data).into() } diff --git a/primitives/core/src/bls.rs b/primitives/core/src/bls.rs new file mode 100644 index 000000000000..770f7d65cea0 --- /dev/null +++ b/primitives/core/src/bls.rs @@ -0,0 +1,675 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Simple BLS (Boneh–Lynn–Shacham) Signature API. + +#[cfg(feature = "std")] +use crate::crypto::Ss58Codec; +use crate::crypto::{ByteArray, CryptoType, Derive, Public as TraitPublic, UncheckedFrom}; +#[cfg(feature = "full_crypto")] +use crate::crypto::{DeriveError, DeriveJunction, Pair as TraitPair, SecretStringError}; + +#[cfg(feature = "full_crypto")] +use sp_std::vec::Vec; + +use codec::{Decode, Encode, MaxEncodedLen}; +use scale_info::TypeInfo; +#[cfg(feature = "std")] +use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +use w3f_bls::{DoublePublicKey, DoubleSignature, EngineBLS, SerializableToBytes, TinyBLS381}; +#[cfg(feature = "full_crypto")] +use w3f_bls::{DoublePublicKeyScheme, Keypair, Message, SecretKey}; + +use sp_runtime_interface::pass_by::PassByInner; +use sp_std::{convert::TryFrom, marker::PhantomData, ops::Deref}; + +/// BLS-377 specialized types +pub mod bls377 { + use crate::crypto::CryptoTypeId; + use w3f_bls::TinyBLS377; + + /// An identifier used to match public keys against BLS12-377 keys + pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"bls7"); + + /// BLS12-377 key pair. + #[cfg(feature = "full_crypto")] + pub type Pair = super::Pair; + /// BLS12-377 public key. + pub type Public = super::Public; + /// BLS12-377 signature. + pub type Signature = super::Signature; + + impl super::HardJunctionId for TinyBLS377 { + const ID: &'static str = "BLS12377HDKD"; + } +} + +/// BLS-381 specialized types +pub mod bls381 { + use crate::crypto::CryptoTypeId; + use w3f_bls::TinyBLS381; + + /// An identifier used to match public keys against BLS12-381 keys + pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"bls8"); + + /// BLS12-381 key pair. + #[cfg(feature = "full_crypto")] + pub type Pair = super::Pair; + /// BLS12-381 public key. + pub type Public = super::Public; + /// BLS12-381 signature. + pub type Signature = super::Signature; + + impl super::HardJunctionId for TinyBLS381 { + const ID: &'static str = "BLS12381HDKD"; + } +} + +trait BlsBound: EngineBLS + HardJunctionId + Send + Sync + 'static {} + +impl BlsBound for T {} + +// Secret key serialized size +#[cfg(feature = "full_crypto")] +const SECRET_KEY_SERIALIZED_SIZE: usize = + as SerializableToBytes>::SERIALIZED_BYTES_SIZE; + +// Public key serialized size +const PUBLIC_KEY_SERIALIZED_SIZE: usize = + as SerializableToBytes>::SERIALIZED_BYTES_SIZE; + +// Signature serialized size +const SIGNATURE_SERIALIZED_SIZE: usize = + as SerializableToBytes>::SERIALIZED_BYTES_SIZE; + +/// A secret seed. +/// +/// It's not called a "secret key" because ring doesn't expose the secret keys +/// of the key pair (yeah, dumb); as such we're forced to remember the seed manually if we +/// will need it later (such as for HDKD). +#[cfg(feature = "full_crypto")] +type Seed = [u8; SECRET_KEY_SERIALIZED_SIZE]; + +/// A public key. +#[derive(Copy, Encode, Decode, MaxEncodedLen, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct Public { + inner: [u8; PUBLIC_KEY_SERIALIZED_SIZE], + _phantom: PhantomData T>, +} + +impl Clone for Public { + fn clone(&self) -> Self { + Self { inner: self.inner, _phantom: PhantomData } + } +} + +impl PartialEq for Public { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +impl Eq for Public {} + +impl PartialOrd for Public { + fn partial_cmp(&self, other: &Self) -> Option { + self.inner.partial_cmp(&other.inner) + } +} + +impl Ord for Public { + fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { + self.inner.cmp(&other.inner) + } +} + +#[cfg(feature = "full_crypto")] +impl sp_std::hash::Hash for Public { + fn hash(&self, state: &mut H) { + self.inner.hash(state) + } +} + +impl ByteArray for Public { + const LEN: usize = PUBLIC_KEY_SERIALIZED_SIZE; +} + +impl PassByInner for Public { + type Inner = [u8; PUBLIC_KEY_SERIALIZED_SIZE]; + + fn into_inner(self) -> Self::Inner { + self.inner + } + + fn inner(&self) -> &Self::Inner { + &self.inner + } + + fn from_inner(inner: Self::Inner) -> Self { + Self { inner, _phantom: PhantomData } + } +} + +impl AsRef<[u8; PUBLIC_KEY_SERIALIZED_SIZE]> for Public { + fn as_ref(&self) -> &[u8; PUBLIC_KEY_SERIALIZED_SIZE] { + &self.inner + } +} + +impl AsRef<[u8]> for Public { + fn as_ref(&self) -> &[u8] { + &self.inner[..] + } +} + +impl AsMut<[u8]> for Public { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.inner[..] + } +} + +impl Deref for Public { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl TryFrom<&[u8]> for Public { + type Error = (); + + fn try_from(data: &[u8]) -> Result { + if data.len() != PUBLIC_KEY_SERIALIZED_SIZE { + return Err(()) + } + let mut r = [0u8; PUBLIC_KEY_SERIALIZED_SIZE]; + r.copy_from_slice(data); + Ok(Self::unchecked_from(r)) + } +} + +impl From> for [u8; PUBLIC_KEY_SERIALIZED_SIZE] { + fn from(x: Public) -> Self { + x.inner + } +} + +#[cfg(feature = "full_crypto")] +impl From> for Public { + fn from(x: Pair) -> Self { + x.public() + } +} + +impl UncheckedFrom<[u8; PUBLIC_KEY_SERIALIZED_SIZE]> for Public { + fn unchecked_from(data: [u8; PUBLIC_KEY_SERIALIZED_SIZE]) -> Self { + Public { inner: data, _phantom: PhantomData } + } +} + +#[cfg(feature = "std")] +impl std::str::FromStr for Public { + type Err = crate::crypto::PublicError; + + fn from_str(s: &str) -> Result { + Self::from_ss58check(s) + } +} + +#[cfg(feature = "std")] +impl std::fmt::Display for Public { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "{}", self.to_ss58check()) + } +} + +#[cfg(feature = "std")] +impl sp_std::fmt::Debug for Public { + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + let s = self.to_ss58check(); + write!(f, "{} ({}...)", crate::hexdisplay::HexDisplay::from(&self.inner), &s[0..8]) + } +} + +#[cfg(not(feature = "std"))] +impl sp_std::fmt::Debug for Public { + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +#[cfg(feature = "std")] +impl Serialize for Public { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_ss58check()) + } +} + +#[cfg(feature = "std")] +impl<'de, T: BlsBound> Deserialize<'de> for Public { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Public::from_ss58check(&String::deserialize(deserializer)?) + .map_err(|e| de::Error::custom(format!("{:?}", e))) + } +} + +impl TraitPublic for Public {} + +impl Derive for Public {} + +impl CryptoType for Public { + #[cfg(feature = "full_crypto")] + type Pair = Pair; +} + +/// A generic BLS signature. +#[derive(Copy, Encode, Decode, MaxEncodedLen, TypeInfo)] +#[scale_info(skip_type_params(T))] +pub struct Signature { + inner: [u8; SIGNATURE_SERIALIZED_SIZE], + _phantom: PhantomData T>, +} + +impl Clone for Signature { + fn clone(&self) -> Self { + Self { inner: self.inner, _phantom: PhantomData } + } +} + +impl PartialEq for Signature { + fn eq(&self, other: &Self) -> bool { + self.inner == other.inner + } +} + +impl Eq for Signature {} + +#[cfg(feature = "full_crypto")] +impl sp_std::hash::Hash for Signature { + fn hash(&self, state: &mut H) { + self.inner.hash(state) + } +} + +impl TryFrom<&[u8]> for Signature { + type Error = (); + + fn try_from(data: &[u8]) -> Result { + if data.len() != SIGNATURE_SERIALIZED_SIZE { + return Err(()) + } + let mut inner = [0u8; SIGNATURE_SERIALIZED_SIZE]; + inner.copy_from_slice(data); + Ok(Signature::unchecked_from(inner)) + } +} + +#[cfg(feature = "std")] +impl Serialize for Signature { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&array_bytes::bytes2hex("", self)) + } +} + +#[cfg(feature = "std")] +impl<'de, T> Deserialize<'de> for Signature { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let signature_hex = array_bytes::hex2bytes(&String::deserialize(deserializer)?) + .map_err(|e| de::Error::custom(format!("{:?}", e)))?; + Signature::try_from(signature_hex.as_ref()) + .map_err(|e| de::Error::custom(format!("{:?}", e))) + } +} + +impl From> for [u8; SIGNATURE_SERIALIZED_SIZE] { + fn from(signature: Signature) -> [u8; SIGNATURE_SERIALIZED_SIZE] { + signature.inner + } +} + +impl AsRef<[u8; SIGNATURE_SERIALIZED_SIZE]> for Signature { + fn as_ref(&self) -> &[u8; SIGNATURE_SERIALIZED_SIZE] { + &self.inner + } +} + +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { + &self.inner[..] + } +} + +impl AsMut<[u8]> for Signature { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.inner[..] + } +} + +impl sp_std::fmt::Debug for Signature { + #[cfg(feature = "std")] + fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + write!(f, "{}", crate::hexdisplay::HexDisplay::from(&self.inner)) + } + + #[cfg(not(feature = "std"))] + fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { + Ok(()) + } +} + +impl UncheckedFrom<[u8; SIGNATURE_SERIALIZED_SIZE]> for Signature { + fn unchecked_from(data: [u8; SIGNATURE_SERIALIZED_SIZE]) -> Self { + Signature { inner: data, _phantom: PhantomData } + } +} + +impl CryptoType for Signature { + #[cfg(feature = "full_crypto")] + type Pair = Pair; +} + +/// A key pair. +#[cfg(feature = "full_crypto")] +pub struct Pair(Keypair); + +#[cfg(feature = "full_crypto")] +impl Clone for Pair { + fn clone(&self) -> Self { + Pair(self.0.clone()) + } +} + +trait HardJunctionId { + const ID: &'static str; +} + +/// Derive a single hard junction. +#[cfg(feature = "full_crypto")] +fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed { + (T::ID, secret_seed, cc).using_encoded(sp_core_hashing::blake2_256) +} + +#[cfg(feature = "full_crypto")] +impl Pair {} + +#[cfg(feature = "full_crypto")] +impl TraitPair for Pair { + type Seed = Seed; + type Public = Public; + type Signature = Signature; + + fn from_seed_slice(seed_slice: &[u8]) -> Result { + if seed_slice.len() != SECRET_KEY_SERIALIZED_SIZE { + return Err(SecretStringError::InvalidSeedLength) + } + let secret = w3f_bls::SecretKey::from_seed(seed_slice); + let public = secret.into_public(); + Ok(Pair(w3f_bls::Keypair { secret, public })) + } + + fn derive>( + &self, + path: Iter, + _seed: Option, + ) -> Result<(Self, Option), DeriveError> { + let mut acc: [u8; SECRET_KEY_SERIALIZED_SIZE] = + self.0.secret.to_bytes().try_into().expect( + "Secret key serializer returns a vector of SECRET_KEY_SERIALIZED_SIZE size", + ); + for j in path { + match j { + DeriveJunction::Soft(_cc) => return Err(DeriveError::SoftKeyInPath), + DeriveJunction::Hard(cc) => acc = derive_hard_junction::(&acc, &cc), + } + } + Ok((Self::from_seed(&acc), Some(acc))) + } + + fn public(&self) -> Self::Public { + let mut raw = [0u8; PUBLIC_KEY_SERIALIZED_SIZE]; + let pk = DoublePublicKeyScheme::into_double_public_key(&self.0).to_bytes(); + raw.copy_from_slice(pk.as_slice()); + Self::Public::unchecked_from(raw) + } + + fn sign(&self, message: &[u8]) -> Self::Signature { + let mut mutable_self = self.clone(); + let r: [u8; SIGNATURE_SERIALIZED_SIZE] = + DoublePublicKeyScheme::sign(&mut mutable_self.0, &Message::new(b"", message)) + .to_bytes() + .try_into() + .expect("Signature serializer returns vectors of SIGNATURE_SERIALIZED_SIZE size"); + Self::Signature::unchecked_from(r) + } + + fn verify>(sig: &Self::Signature, message: M, pubkey: &Self::Public) -> bool { + let pubkey_array: [u8; PUBLIC_KEY_SERIALIZED_SIZE] = + match <[u8; PUBLIC_KEY_SERIALIZED_SIZE]>::try_from(pubkey.as_ref()) { + Ok(pk) => pk, + Err(_) => return false, + }; + let public_key = match w3f_bls::double::DoublePublicKey::::from_bytes(&pubkey_array) { + Ok(pk) => pk, + Err(_) => return false, + }; + + let sig_array = match sig.inner[..].try_into() { + Ok(s) => s, + Err(_) => return false, + }; + let sig = match w3f_bls::double::DoubleSignature::from_bytes(sig_array) { + Ok(s) => s, + Err(_) => return false, + }; + + sig.verify(&Message::new(b"", message.as_ref()), &public_key) + } + + /// Get the seed for this key. + fn to_raw_vec(&self) -> Vec { + self.0 + .secret + .to_bytes() + .try_into() + .expect("Secret key serializer returns a vector of SECRET_KEY_SERIALIZED_SIZE size") + } +} + +#[cfg(feature = "full_crypto")] +impl CryptoType for Pair { + type Pair = Pair; +} + +// Test set exercising the BLS12-377 implementation +#[cfg(test)] +mod test { + use super::*; + use crate::crypto::DEV_PHRASE; + use bls377::{Pair, Signature}; + use hex_literal::hex; + + #[test] + fn default_phrase_should_be_used() { + assert_eq!( + Pair::from_string("//Alice///password", None).unwrap().public(), + Pair::from_string(&format!("{}//Alice", DEV_PHRASE), Some("password")) + .unwrap() + .public(), + ); + } + + // Only passes if the seed = (seed mod ScalarField) + #[test] + fn seed_and_derive_should_work() { + let seed = hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f00"); + let pair = Pair::from_seed(&seed); + // we are using hash to field so this is not going to work + // assert_eq!(pair.seed(), seed); + let path = vec![DeriveJunction::Hard([0u8; 32])]; + let derived = pair.derive(path.into_iter(), None).ok().unwrap().0; + assert_eq!( + derived.to_raw_vec(), + hex!("a4f2269333b3e87c577aa00c4a2cd650b3b30b2e8c286a47c251279ff3a26e0d") + ); + } + + #[test] + fn test_vector_should_work() { + let pair = Pair::from_seed(&hex!( + "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60" + )); + let public = pair.public(); + assert_eq!( + public, + Public::unchecked_from(hex!( + "7a84ca8ce4c37c93c95ecee6a3c0c9a7b9c225093cf2f12dc4f69cbfb847ef9424a18f5755d5a742247d386ff2aabb806bcf160eff31293ea9616976628f77266c8a8cc1d8753be04197bd6cdd8c5c87a148f782c4c1568d599b48833fd539001e580cff64bbc71850605433fcd051f3afc3b74819786f815ffb5272030a8d03e5df61e6183f8fd8ea85f26defa83400" + )) + ); + let message = b""; + let signature = + hex!("d1e3013161991e142d8751017d4996209c2ff8a9ee160f373733eda3b4b785ba6edce9f45f87104bbe07aa6aa6eb2780aa705efb2c13d3b317d6409d159d23bdc7cdd5c2a832d1551cf49d811d49c901495e527dbd532e3a462335ce2686009104aba7bc11c5b22be78f3198d2727a0b" + ); + let signature = Signature::unchecked_from(signature); + assert!(pair.sign(&message[..]) == signature); + assert!(Pair::verify(&signature, &message[..], &public)); + } + + #[test] + fn test_vector_by_string_should_work() { + let pair = Pair::from_string( + "0x9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", + None, + ) + .unwrap(); + let public = pair.public(); + assert_eq!( + public, + Public::unchecked_from(hex!( + "6dc6be608fab3c6bd894a606be86db346cc170db85c733853a371f3db54ae1b12052c0888d472760c81b537572a26f00db865e5963aef8634f9917571c51b538b564b2a9ceda938c8b930969ee3b832448e08e33a79e9ddd28af419a3ce45300f5dbc768b067781f44f3fe05a19e6b07b1c4196151ec3f8ea37e4f89a8963030d2101e931276bb9ebe1f20102239d780" + )) + ); + let message = b""; + let signature = + hex!("bbb395bbdee1a35930912034f5fde3b36df2835a0536c865501b0675776a1d5931a3bea2e66eff73b2546c6af2061a8019223e4ebbbed661b2538e0f5823f2c708eb89c406beca8fcb53a5c13dbc7c0c42e4cf2be2942bba96ea29297915a06bd2b1b979c0e2ac8fd4ec684a6b5d110c" + ); + let expected_signature = Signature::unchecked_from(signature); + println!("signature is {:?}", pair.sign(&message[..])); + let signature = pair.sign(&message[..]); + assert!(signature == expected_signature); + assert!(Pair::verify(&signature, &message[..], &public)); + } + #[test] + fn generated_pair_should_work() { + let (pair, _) = Pair::generate(); + let public = pair.public(); + let message = b"Something important"; + let signature = pair.sign(&message[..]); + assert!(Pair::verify(&signature, &message[..], &public)); + assert!(!Pair::verify(&signature, b"Something else", &public)); + } + + #[test] + fn seeded_pair_should_work() { + let pair = Pair::from_seed(b"12345678901234567890123456789012"); + let public = pair.public(); + assert_eq!( + public, + Public::unchecked_from( + hex!( + "754d2f2bbfa67df54d7e0e951979a18a1e0f45948857752cc2bac6bbb0b1d05e8e48bcc453920bf0c4bbd5993212480112a1fb433f04d74af0a8b700d93dc957ab3207f8d071e948f5aca1a7632c00bdf6d06be05b43e2e6216dccc8a5d55a0071cb2313cfd60b7e9114619cd17c06843b352f0b607a99122f6651df8f02e1ad3697bd208e62af047ddd7b942ba80080") + ) + ); + let message = + hex!("2f8c6129d816cf51c374bc7f08c3e63ed156cf78aefb4a6550d97b87997977ee00000000000000000200d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a4500000000000000" + ); + let signature = pair.sign(&message[..]); + println!("Correct signature: {:?}", signature); + assert!(Pair::verify(&signature, &message[..], &public)); + assert!(!Pair::verify(&signature, "Other message", &public)); + } + + #[test] + fn generate_with_phrase_recovery_possible() { + let (pair1, phrase, _) = Pair::generate_with_phrase(None); + let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); + + assert_eq!(pair1.public(), pair2.public()); + } + + #[test] + fn generate_with_password_phrase_recovery_possible() { + let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password")); + let (pair2, _) = Pair::from_phrase(&phrase, Some("password")).unwrap(); + + assert_eq!(pair1.public(), pair2.public()); + } + + #[test] + fn password_does_something() { + let (pair1, phrase, _) = Pair::generate_with_phrase(Some("password")); + let (pair2, _) = Pair::from_phrase(&phrase, None).unwrap(); + + assert_ne!(pair1.public(), pair2.public()); + } + + #[test] + fn ss58check_roundtrip_works() { + let pair = Pair::from_seed(b"12345678901234567890123456789012"); + let public = pair.public(); + let s = public.to_ss58check(); + println!("Correct: {}", s); + let cmp = Public::from_ss58check(&s).unwrap(); + assert_eq!(cmp, public); + } + + #[test] + fn signature_serialization_works() { + let pair = Pair::from_seed(b"12345678901234567890123456789012"); + let message = b"Something important"; + let signature = pair.sign(&message[..]); + let serialized_signature = serde_json::to_string(&signature).unwrap(); + // Signature is 112 bytes, hexify * 2, so 224 chars + 2 quote chars + assert_eq!(serialized_signature.len(), 226); + let signature = serde_json::from_str(&serialized_signature).unwrap(); + assert!(Pair::verify(&signature, &message[..], &pair.public())); + } + + #[test] + fn signature_serialization_doesnt_panic() { + fn deserialize_signature(text: &str) -> Result { + serde_json::from_str(text) + } + assert!(deserialize_signature("Not valid json.").is_err()); + assert!(deserialize_signature("\"Not an actual signature.\"").is_err()); + // Poorly-sized + assert!(deserialize_signature("\"abc123\"").is_err()); + } +} diff --git a/primitives/core/src/bounded/bounded_btree_map.rs b/primitives/core/src/bounded/bounded_btree_map.rs deleted file mode 100644 index d2c148d6de9c..000000000000 --- a/primitives/core/src/bounded/bounded_btree_map.rs +++ /dev/null @@ -1,625 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! Traits, types and structs to support a bounded BTreeMap. - -use crate::{Get, TryCollect}; -use codec::{Decode, Encode, MaxEncodedLen}; -use sp_std::{borrow::Borrow, collections::btree_map::BTreeMap, marker::PhantomData, ops::Deref}; - -/// A bounded map based on a B-Tree. -/// -/// B-Trees represent a fundamental compromise between cache-efficiency and actually minimizing -/// the amount of work performed in a search. See [`BTreeMap`] for more details. -/// -/// Unlike a standard `BTreeMap`, there is an enforced upper limit to the number of items in the -/// map. All internal operations ensure this bound is respected. -#[derive(Encode, scale_info::TypeInfo)] -#[scale_info(skip_type_params(S))] -pub struct BoundedBTreeMap(BTreeMap, PhantomData); - -impl Decode for BoundedBTreeMap -where - K: Decode + Ord, - V: Decode, - S: Get, -{ - fn decode(input: &mut I) -> Result { - let inner = BTreeMap::::decode(input)?; - if inner.len() > S::get() as usize { - return Err("BoundedBTreeMap exceeds its limit".into()) - } - Ok(Self(inner, PhantomData)) - } - - fn skip(input: &mut I) -> Result<(), codec::Error> { - BTreeMap::::skip(input) - } -} - -impl BoundedBTreeMap -where - S: Get, -{ - /// Get the bound of the type in `usize`. - pub fn bound() -> usize { - S::get() as usize - } -} - -impl BoundedBTreeMap -where - K: Ord, - S: Get, -{ - /// Create `Self` from `t` without any checks. - fn unchecked_from(t: BTreeMap) -> Self { - Self(t, Default::default()) - } - - /// Exactly the same semantics as `BTreeMap::retain`. - /// - /// The is a safe `&mut self` borrow because `retain` can only ever decrease the length of the - /// inner map. - pub fn retain bool>(&mut self, f: F) { - self.0.retain(f) - } - - /// Create a new `BoundedBTreeMap`. - /// - /// Does not allocate. - pub fn new() -> Self { - BoundedBTreeMap(BTreeMap::new(), PhantomData) - } - - /// Consume self, and return the inner `BTreeMap`. - /// - /// This is useful when a mutating API of the inner type is desired, and closure-based mutation - /// such as provided by [`try_mutate`][Self::try_mutate] is inconvenient. - pub fn into_inner(self) -> BTreeMap { - debug_assert!(self.0.len() <= Self::bound()); - self.0 - } - - /// Consumes self and mutates self via the given `mutate` function. - /// - /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is - /// returned. - /// - /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> - /// [`Self::try_from`]. - pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut BTreeMap)) -> Option { - mutate(&mut self.0); - (self.0.len() <= Self::bound()).then(move || self) - } - - /// Clears the map, removing all elements. - pub fn clear(&mut self) { - self.0.clear() - } - - /// Return a mutable reference to the value corresponding to the key. - /// - /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed - /// form _must_ match the ordering on the key type. - pub fn get_mut(&mut self, key: &Q) -> Option<&mut V> - where - K: Borrow, - Q: Ord + ?Sized, - { - self.0.get_mut(key) - } - - /// Exactly the same semantics as [`BTreeMap::insert`], but returns an `Err` (and is a noop) if - /// the new length of the map exceeds `S`. - /// - /// In the `Err` case, returns the inserted pair so it can be further used without cloning. - pub fn try_insert(&mut self, key: K, value: V) -> Result, (K, V)> { - if self.len() < Self::bound() || self.0.contains_key(&key) { - Ok(self.0.insert(key, value)) - } else { - Err((key, value)) - } - } - - /// Remove a key from the map, returning the value at the key if the key was previously in the - /// map. - /// - /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed - /// form _must_ match the ordering on the key type. - pub fn remove(&mut self, key: &Q) -> Option - where - K: Borrow, - Q: Ord + ?Sized, - { - self.0.remove(key) - } - - /// Remove a key from the map, returning the value at the key if the key was previously in the - /// map. - /// - /// The key may be any borrowed form of the map's key type, but the ordering on the borrowed - /// form _must_ match the ordering on the key type. - pub fn remove_entry(&mut self, key: &Q) -> Option<(K, V)> - where - K: Borrow, - Q: Ord + ?Sized, - { - self.0.remove_entry(key) - } - - /// Gets a mutable iterator over the entries of the map, sorted by key. - /// - /// See [`BTreeMap::iter_mut`] for more information. - pub fn iter_mut(&mut self) -> sp_std::collections::btree_map::IterMut { - self.0.iter_mut() - } - - /// Consume the map, applying `f` to each of it's values and returning a new map. - pub fn map(self, mut f: F) -> BoundedBTreeMap - where - F: FnMut((&K, V)) -> T, - { - BoundedBTreeMap::::unchecked_from( - self.0 - .into_iter() - .map(|(k, v)| { - let t = f((&k, v)); - (k, t) - }) - .collect(), - ) - } - - /// Consume the map, applying `f` to each of it's values as long as it returns successfully. If - /// an `Err(E)` is ever encountered, the mapping is short circuited and the error is returned; - /// otherwise, a new map is returned in the contained `Ok` value. - pub fn try_map(self, mut f: F) -> Result, E> - where - F: FnMut((&K, V)) -> Result, - { - Ok(BoundedBTreeMap::::unchecked_from( - self.0 - .into_iter() - .map(|(k, v)| (f((&k, v)).map(|t| (k, t)))) - .collect::, _>>()?, - )) - } -} - -impl Default for BoundedBTreeMap -where - K: Ord, - S: Get, -{ - fn default() -> Self { - Self::new() - } -} - -impl Clone for BoundedBTreeMap -where - BTreeMap: Clone, -{ - fn clone(&self) -> Self { - BoundedBTreeMap(self.0.clone(), PhantomData) - } -} - -impl sp_std::fmt::Debug for BoundedBTreeMap -where - BTreeMap: sp_std::fmt::Debug, - S: Get, -{ - fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { - f.debug_tuple("BoundedBTreeMap").field(&self.0).field(&Self::bound()).finish() - } -} - -impl PartialEq> for BoundedBTreeMap -where - BTreeMap: PartialEq, - S1: Get, - S2: Get, -{ - fn eq(&self, other: &BoundedBTreeMap) -> bool { - S1::get() == S2::get() && self.0 == other.0 - } -} - -impl Eq for BoundedBTreeMap -where - BTreeMap: Eq, - S: Get, -{ -} - -impl PartialEq> for BoundedBTreeMap -where - BTreeMap: PartialEq, -{ - fn eq(&self, other: &BTreeMap) -> bool { - self.0 == *other - } -} - -impl PartialOrd for BoundedBTreeMap -where - BTreeMap: PartialOrd, - S: Get, -{ - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl Ord for BoundedBTreeMap -where - BTreeMap: Ord, - S: Get, -{ - fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl IntoIterator for BoundedBTreeMap { - type Item = (K, V); - type IntoIter = sp_std::collections::btree_map::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl<'a, K, V, S> IntoIterator for &'a BoundedBTreeMap { - type Item = (&'a K, &'a V); - type IntoIter = sp_std::collections::btree_map::Iter<'a, K, V>; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl<'a, K, V, S> IntoIterator for &'a mut BoundedBTreeMap { - type Item = (&'a K, &'a mut V); - type IntoIter = sp_std::collections::btree_map::IterMut<'a, K, V>; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter_mut() - } -} - -impl MaxEncodedLen for BoundedBTreeMap -where - K: MaxEncodedLen, - V: MaxEncodedLen, - S: Get, -{ - fn max_encoded_len() -> usize { - Self::bound() - .saturating_mul(K::max_encoded_len().saturating_add(V::max_encoded_len())) - .saturating_add(codec::Compact(S::get()).encoded_size()) - } -} - -impl Deref for BoundedBTreeMap -where - K: Ord, -{ - type Target = BTreeMap; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl AsRef> for BoundedBTreeMap -where - K: Ord, -{ - fn as_ref(&self) -> &BTreeMap { - &self.0 - } -} - -impl From> for BTreeMap -where - K: Ord, -{ - fn from(map: BoundedBTreeMap) -> Self { - map.0 - } -} - -impl TryFrom> for BoundedBTreeMap -where - K: Ord, - S: Get, -{ - type Error = (); - - fn try_from(value: BTreeMap) -> Result { - (value.len() <= Self::bound()) - .then(move || BoundedBTreeMap(value, PhantomData)) - .ok_or(()) - } -} - -impl codec::DecodeLength for BoundedBTreeMap { - fn len(self_encoded: &[u8]) -> Result { - // `BoundedBTreeMap` is stored just a `BTreeMap`, which is stored as a - // `Compact` with its length followed by an iteration of its items. We can just use - // the underlying implementation. - as codec::DecodeLength>::len(self_encoded) - } -} - -impl codec::EncodeLike> for BoundedBTreeMap where - BTreeMap: Encode -{ -} - -impl TryCollect> for I -where - K: Ord, - I: ExactSizeIterator + Iterator, - Bound: Get, -{ - type Error = &'static str; - - fn try_collect(self) -> Result, Self::Error> { - if self.len() > Bound::get() as usize { - Err("iterator length too big") - } else { - Ok(BoundedBTreeMap::::unchecked_from(self.collect::>())) - } - } -} - -#[cfg(test)] -pub mod test { - use super::*; - use crate::ConstU32; - - fn map_from_keys(keys: &[K]) -> BTreeMap - where - K: Ord + Copy, - { - keys.iter().copied().zip(std::iter::repeat(())).collect() - } - - fn boundedmap_from_keys(keys: &[K]) -> BoundedBTreeMap - where - K: Ord + Copy, - S: Get, - { - map_from_keys(keys).try_into().unwrap() - } - - #[test] - fn try_insert_works() { - let mut bounded = boundedmap_from_keys::>(&[1, 2, 3]); - bounded.try_insert(0, ()).unwrap(); - assert_eq!(*bounded, map_from_keys(&[1, 0, 2, 3])); - - assert!(bounded.try_insert(9, ()).is_err()); - assert_eq!(*bounded, map_from_keys(&[1, 0, 2, 3])); - } - - #[test] - fn deref_coercion_works() { - let bounded = boundedmap_from_keys::>(&[1, 2, 3]); - // these methods come from deref-ed vec. - assert_eq!(bounded.len(), 3); - assert!(bounded.iter().next().is_some()); - assert!(!bounded.is_empty()); - } - - #[test] - fn try_mutate_works() { - let bounded = boundedmap_from_keys::>(&[1, 2, 3, 4, 5, 6]); - let bounded = bounded - .try_mutate(|v| { - v.insert(7, ()); - }) - .unwrap(); - assert_eq!(bounded.len(), 7); - assert!(bounded - .try_mutate(|v| { - v.insert(8, ()); - }) - .is_none()); - } - - #[test] - fn btree_map_eq_works() { - let bounded = boundedmap_from_keys::>(&[1, 2, 3, 4, 5, 6]); - assert_eq!(bounded, map_from_keys(&[1, 2, 3, 4, 5, 6])); - } - - #[test] - fn too_big_fail_to_decode() { - let v: Vec<(u32, u32)> = vec![(1, 1), (2, 2), (3, 3), (4, 4), (5, 5)]; - assert_eq!( - BoundedBTreeMap::>::decode(&mut &v.encode()[..]), - Err("BoundedBTreeMap exceeds its limit".into()), - ); - } - - #[test] - fn unequal_eq_impl_insert_works() { - // given a struct with a strange notion of equality - #[derive(Debug)] - struct Unequal(u32, bool); - - impl PartialEq for Unequal { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } - } - impl Eq for Unequal {} - - impl Ord for Unequal { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.0.cmp(&other.0) - } - } - - impl PartialOrd for Unequal { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } - - let mut map = BoundedBTreeMap::>::new(); - - // when the set is full - - for i in 0..4 { - map.try_insert(Unequal(i, false), i).unwrap(); - } - - // can't insert a new distinct member - map.try_insert(Unequal(5, false), 5).unwrap_err(); - - // but _can_ insert a distinct member which compares equal, though per the documentation, - // neither the set length nor the actual member are changed, but the value is - map.try_insert(Unequal(0, true), 6).unwrap(); - assert_eq!(map.len(), 4); - let (zero_key, zero_value) = map.get_key_value(&Unequal(0, true)).unwrap(); - assert_eq!(zero_key.0, 0); - assert_eq!(zero_key.1, false); - assert_eq!(*zero_value, 6); - } - - #[test] - fn eq_works() { - // of same type - let b1 = boundedmap_from_keys::>(&[1, 2]); - let b2 = boundedmap_from_keys::>(&[1, 2]); - assert_eq!(b1, b2); - - // of different type, but same value and bound. - crate::parameter_types! { - B1: u32 = 7; - B2: u32 = 7; - } - let b1 = boundedmap_from_keys::(&[1, 2]); - let b2 = boundedmap_from_keys::(&[1, 2]); - assert_eq!(b1, b2); - } - - #[test] - fn can_be_collected() { - let b1 = boundedmap_from_keys::>(&[1, 2, 3, 4]); - let b2: BoundedBTreeMap> = - b1.iter().map(|(k, v)| (k + 1, *v)).try_collect().unwrap(); - assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3, 4, 5]); - - // can also be collected into a collection of length 4. - let b2: BoundedBTreeMap> = - b1.iter().map(|(k, v)| (k + 1, *v)).try_collect().unwrap(); - assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3, 4, 5]); - - // can be mutated further into iterators that are `ExactSizedIterator`. - let b2: BoundedBTreeMap> = - b1.iter().map(|(k, v)| (k + 1, *v)).rev().skip(2).try_collect().unwrap(); - // note that the binary tree will re-sort this, so rev() is not really seen - assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3]); - - let b2: BoundedBTreeMap> = - b1.iter().map(|(k, v)| (k + 1, *v)).take(2).try_collect().unwrap(); - assert_eq!(b2.into_iter().map(|(k, _)| k).collect::>(), vec![2, 3]); - - // but these won't work - let b2: Result>, _> = - b1.iter().map(|(k, v)| (k + 1, *v)).try_collect(); - assert!(b2.is_err()); - - let b2: Result>, _> = - b1.iter().map(|(k, v)| (k + 1, *v)).skip(2).try_collect(); - assert!(b2.is_err()); - } - - #[test] - fn test_iter_mut() { - let mut b1: BoundedBTreeMap> = - [1, 2, 3, 4].into_iter().map(|k| (k, k)).try_collect().unwrap(); - - let b2: BoundedBTreeMap> = - [1, 2, 3, 4].into_iter().map(|k| (k, k * 2)).try_collect().unwrap(); - - b1.iter_mut().for_each(|(_, v)| *v *= 2); - - assert_eq!(b1, b2); - } - - #[test] - fn map_retains_size() { - let b1 = boundedmap_from_keys::>(&[1, 2]); - let b2 = b1.clone(); - - assert_eq!(b1.len(), b2.map(|(_, _)| 5_u32).len()); - } - - #[test] - fn map_maps_properly() { - let b1: BoundedBTreeMap> = - [1, 2, 3, 4].into_iter().map(|k| (k, k * 2)).try_collect().unwrap(); - let b2: BoundedBTreeMap> = - [1, 2, 3, 4].into_iter().map(|k| (k, k)).try_collect().unwrap(); - - assert_eq!(b1, b2.map(|(_, v)| v * 2)); - } - - #[test] - fn try_map_retains_size() { - let b1 = boundedmap_from_keys::>(&[1, 2]); - let b2 = b1.clone(); - - assert_eq!(b1.len(), b2.try_map::<_, (), _>(|(_, _)| Ok(5_u32)).unwrap().len()); - } - - #[test] - fn try_map_maps_properly() { - let b1: BoundedBTreeMap> = - [1, 2, 3, 4].into_iter().map(|k| (k, k * 2)).try_collect().unwrap(); - let b2: BoundedBTreeMap> = - [1, 2, 3, 4].into_iter().map(|k| (k, k)).try_collect().unwrap(); - - assert_eq!(b1, b2.try_map::<_, (), _>(|(_, v)| Ok(v * 2)).unwrap()); - } - - #[test] - fn try_map_short_circuit() { - let b1: BoundedBTreeMap> = - [1, 2, 3, 4].into_iter().map(|k| (k, k)).try_collect().unwrap(); - - assert_eq!(Err("overflow"), b1.try_map(|(_, v)| v.checked_mul(100).ok_or("overflow"))); - } - - #[test] - fn try_map_ok() { - let b1: BoundedBTreeMap> = - [1, 2, 3, 4].into_iter().map(|k| (k, k)).try_collect().unwrap(); - let b2: BoundedBTreeMap> = - [1, 2, 3, 4].into_iter().map(|k| (k, (k as u16) * 100)).try_collect().unwrap(); - - assert_eq!(Ok(b2), b1.try_map(|(_, v)| (v as u16).checked_mul(100_u16).ok_or("overflow"))); - } -} diff --git a/primitives/core/src/bounded/bounded_btree_set.rs b/primitives/core/src/bounded/bounded_btree_set.rs deleted file mode 100644 index 5feac6b7150f..000000000000 --- a/primitives/core/src/bounded/bounded_btree_set.rs +++ /dev/null @@ -1,482 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 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. - -//! Traits, types and structs to support a bounded `BTreeSet`. - -use crate::{Get, TryCollect}; -use codec::{Decode, Encode, MaxEncodedLen}; -use sp_std::{borrow::Borrow, collections::btree_set::BTreeSet, marker::PhantomData, ops::Deref}; - -/// A bounded set based on a B-Tree. -/// -/// B-Trees represent a fundamental compromise between cache-efficiency and actually minimizing -/// the amount of work performed in a search. See [`BTreeSet`] for more details. -/// -/// Unlike a standard `BTreeSet`, there is an enforced upper limit to the number of items in the -/// set. All internal operations ensure this bound is respected. -#[derive(Encode, scale_info::TypeInfo)] -#[scale_info(skip_type_params(S))] -pub struct BoundedBTreeSet(BTreeSet, PhantomData); - -impl Decode for BoundedBTreeSet -where - T: Decode + Ord, - S: Get, -{ - fn decode(input: &mut I) -> Result { - let inner = BTreeSet::::decode(input)?; - if inner.len() > S::get() as usize { - return Err("BoundedBTreeSet exceeds its limit".into()) - } - Ok(Self(inner, PhantomData)) - } - - fn skip(input: &mut I) -> Result<(), codec::Error> { - BTreeSet::::skip(input) - } -} - -impl BoundedBTreeSet -where - S: Get, -{ - /// Get the bound of the type in `usize`. - pub fn bound() -> usize { - S::get() as usize - } -} - -impl BoundedBTreeSet -where - T: Ord, - S: Get, -{ - /// Create `Self` from `t` without any checks. - fn unchecked_from(t: BTreeSet) -> Self { - Self(t, Default::default()) - } - - /// Create a new `BoundedBTreeSet`. - /// - /// Does not allocate. - pub fn new() -> Self { - BoundedBTreeSet(BTreeSet::new(), PhantomData) - } - - /// Consume self, and return the inner `BTreeSet`. - /// - /// This is useful when a mutating API of the inner type is desired, and closure-based mutation - /// such as provided by [`try_mutate`][Self::try_mutate] is inconvenient. - pub fn into_inner(self) -> BTreeSet { - debug_assert!(self.0.len() <= Self::bound()); - self.0 - } - - /// Consumes self and mutates self via the given `mutate` function. - /// - /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is - /// returned. - /// - /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> - /// [`Self::try_from`]. - pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut BTreeSet)) -> Option { - mutate(&mut self.0); - (self.0.len() <= Self::bound()).then(move || self) - } - - /// Clears the set, removing all elements. - pub fn clear(&mut self) { - self.0.clear() - } - - /// Exactly the same semantics as [`BTreeSet::insert`], but returns an `Err` (and is a noop) if - /// the new length of the set exceeds `S`. - /// - /// In the `Err` case, returns the inserted item so it can be further used without cloning. - pub fn try_insert(&mut self, item: T) -> Result { - if self.len() < Self::bound() || self.0.contains(&item) { - Ok(self.0.insert(item)) - } else { - Err(item) - } - } - - /// Remove an item from the set, returning whether it was previously in the set. - /// - /// The item may be any borrowed form of the set's item type, but the ordering on the borrowed - /// form _must_ match the ordering on the item type. - pub fn remove(&mut self, item: &Q) -> bool - where - T: Borrow, - Q: Ord + ?Sized, - { - self.0.remove(item) - } - - /// Removes and returns the value in the set, if any, that is equal to the given one. - /// - /// The value may be any borrowed form of the set's value type, but the ordering on the borrowed - /// form _must_ match the ordering on the value type. - pub fn take(&mut self, value: &Q) -> Option - where - T: Borrow + Ord, - Q: Ord + ?Sized, - { - self.0.take(value) - } -} - -impl Default for BoundedBTreeSet -where - T: Ord, - S: Get, -{ - fn default() -> Self { - Self::new() - } -} - -impl Clone for BoundedBTreeSet -where - BTreeSet: Clone, -{ - fn clone(&self) -> Self { - BoundedBTreeSet(self.0.clone(), PhantomData) - } -} - -impl sp_std::fmt::Debug for BoundedBTreeSet -where - BTreeSet: sp_std::fmt::Debug, - S: Get, -{ - fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { - f.debug_tuple("BoundedBTreeSet").field(&self.0).field(&Self::bound()).finish() - } -} - -impl PartialEq> for BoundedBTreeSet -where - BTreeSet: PartialEq, - S1: Get, - S2: Get, -{ - fn eq(&self, other: &BoundedBTreeSet) -> bool { - S1::get() == S2::get() && self.0 == other.0 - } -} - -impl Eq for BoundedBTreeSet -where - BTreeSet: Eq, - S: Get, -{ -} - -impl PartialEq> for BoundedBTreeSet -where - BTreeSet: PartialEq, - S: Get, -{ - fn eq(&self, other: &BTreeSet) -> bool { - self.0 == *other - } -} - -impl PartialOrd for BoundedBTreeSet -where - BTreeSet: PartialOrd, - S: Get, -{ - fn partial_cmp(&self, other: &Self) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl Ord for BoundedBTreeSet -where - BTreeSet: Ord, - S: Get, -{ - fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl IntoIterator for BoundedBTreeSet { - type Item = T; - type IntoIter = sp_std::collections::btree_set::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl<'a, T, S> IntoIterator for &'a BoundedBTreeSet { - type Item = &'a T; - type IntoIter = sp_std::collections::btree_set::Iter<'a, T>; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl MaxEncodedLen for BoundedBTreeSet -where - T: MaxEncodedLen, - S: Get, -{ - fn max_encoded_len() -> usize { - Self::bound() - .saturating_mul(T::max_encoded_len()) - .saturating_add(codec::Compact(S::get()).encoded_size()) - } -} - -impl Deref for BoundedBTreeSet -where - T: Ord, -{ - type Target = BTreeSet; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl AsRef> for BoundedBTreeSet -where - T: Ord, -{ - fn as_ref(&self) -> &BTreeSet { - &self.0 - } -} - -impl From> for BTreeSet -where - T: Ord, -{ - fn from(set: BoundedBTreeSet) -> Self { - set.0 - } -} - -impl TryFrom> for BoundedBTreeSet -where - T: Ord, - S: Get, -{ - type Error = (); - - fn try_from(value: BTreeSet) -> Result { - (value.len() <= Self::bound()) - .then(move || BoundedBTreeSet(value, PhantomData)) - .ok_or(()) - } -} - -impl codec::DecodeLength for BoundedBTreeSet { - fn len(self_encoded: &[u8]) -> Result { - // `BoundedBTreeSet` is stored just a `BTreeSet`, which is stored as a - // `Compact` with its length followed by an iteration of its items. We can just use - // the underlying implementation. - as codec::DecodeLength>::len(self_encoded) - } -} - -impl codec::EncodeLike> for BoundedBTreeSet where BTreeSet: Encode {} - -impl TryCollect> for I -where - T: Ord, - I: ExactSizeIterator + Iterator, - Bound: Get, -{ - type Error = &'static str; - - fn try_collect(self) -> Result, Self::Error> { - if self.len() > Bound::get() as usize { - Err("iterator length too big") - } else { - Ok(BoundedBTreeSet::::unchecked_from(self.collect::>())) - } - } -} - -#[cfg(test)] -pub mod test { - use super::*; - use crate::ConstU32; - - fn set_from_keys(keys: &[T]) -> BTreeSet - where - T: Ord + Copy, - { - keys.iter().copied().collect() - } - - fn boundedset_from_keys(keys: &[T]) -> BoundedBTreeSet - where - T: Ord + Copy, - S: Get, - { - set_from_keys(keys).try_into().unwrap() - } - - #[test] - fn try_insert_works() { - let mut bounded = boundedset_from_keys::>(&[1, 2, 3]); - bounded.try_insert(0).unwrap(); - assert_eq!(*bounded, set_from_keys(&[1, 0, 2, 3])); - - assert!(bounded.try_insert(9).is_err()); - assert_eq!(*bounded, set_from_keys(&[1, 0, 2, 3])); - } - - #[test] - fn deref_coercion_works() { - let bounded = boundedset_from_keys::>(&[1, 2, 3]); - // these methods come from deref-ed vec. - assert_eq!(bounded.len(), 3); - assert!(bounded.iter().next().is_some()); - assert!(!bounded.is_empty()); - } - - #[test] - fn try_mutate_works() { - let bounded = boundedset_from_keys::>(&[1, 2, 3, 4, 5, 6]); - let bounded = bounded - .try_mutate(|v| { - v.insert(7); - }) - .unwrap(); - assert_eq!(bounded.len(), 7); - assert!(bounded - .try_mutate(|v| { - v.insert(8); - }) - .is_none()); - } - - #[test] - fn btree_map_eq_works() { - let bounded = boundedset_from_keys::>(&[1, 2, 3, 4, 5, 6]); - assert_eq!(bounded, set_from_keys(&[1, 2, 3, 4, 5, 6])); - } - - #[test] - fn too_big_fail_to_decode() { - let v: Vec = vec![1, 2, 3, 4, 5]; - assert_eq!( - BoundedBTreeSet::>::decode(&mut &v.encode()[..]), - Err("BoundedBTreeSet exceeds its limit".into()), - ); - } - - #[test] - fn unequal_eq_impl_insert_works() { - // given a struct with a strange notion of equality - #[derive(Debug)] - struct Unequal(u32, bool); - - impl PartialEq for Unequal { - fn eq(&self, other: &Self) -> bool { - self.0 == other.0 - } - } - impl Eq for Unequal {} - - impl Ord for Unequal { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.0.cmp(&other.0) - } - } - - impl PartialOrd for Unequal { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } - } - - let mut set = BoundedBTreeSet::>::new(); - - // when the set is full - - for i in 0..4 { - set.try_insert(Unequal(i, false)).unwrap(); - } - - // can't insert a new distinct member - set.try_insert(Unequal(5, false)).unwrap_err(); - - // but _can_ insert a distinct member which compares equal, though per the documentation, - // neither the set length nor the actual member are changed - set.try_insert(Unequal(0, true)).unwrap(); - assert_eq!(set.len(), 4); - let zero_item = set.get(&Unequal(0, true)).unwrap(); - assert_eq!(zero_item.0, 0); - assert_eq!(zero_item.1, false); - } - - #[test] - fn eq_works() { - // of same type - let b1 = boundedset_from_keys::>(&[1, 2]); - let b2 = boundedset_from_keys::>(&[1, 2]); - assert_eq!(b1, b2); - - // of different type, but same value and bound. - crate::parameter_types! { - B1: u32 = 7; - B2: u32 = 7; - } - let b1 = boundedset_from_keys::(&[1, 2]); - let b2 = boundedset_from_keys::(&[1, 2]); - assert_eq!(b1, b2); - } - - #[test] - fn can_be_collected() { - let b1 = boundedset_from_keys::>(&[1, 2, 3, 4]); - let b2: BoundedBTreeSet> = b1.iter().map(|k| k + 1).try_collect().unwrap(); - assert_eq!(b2.into_iter().collect::>(), vec![2, 3, 4, 5]); - - // can also be collected into a collection of length 4. - let b2: BoundedBTreeSet> = b1.iter().map(|k| k + 1).try_collect().unwrap(); - assert_eq!(b2.into_iter().collect::>(), vec![2, 3, 4, 5]); - - // can be mutated further into iterators that are `ExactSizedIterator`. - let b2: BoundedBTreeSet> = - b1.iter().map(|k| k + 1).rev().skip(2).try_collect().unwrap(); - // note that the binary tree will re-sort this, so rev() is not really seen - assert_eq!(b2.into_iter().collect::>(), vec![2, 3]); - - let b2: BoundedBTreeSet> = - b1.iter().map(|k| k + 1).take(2).try_collect().unwrap(); - assert_eq!(b2.into_iter().collect::>(), vec![2, 3]); - - // but these worn't work - let b2: Result>, _> = - b1.iter().map(|k| k + 1).try_collect(); - assert!(b2.is_err()); - - let b2: Result>, _> = - b1.iter().map(|k| k + 1).skip(2).try_collect(); - assert!(b2.is_err()); - } -} diff --git a/primitives/core/src/bounded/bounded_vec.rs b/primitives/core/src/bounded/bounded_vec.rs deleted file mode 100644 index 6e1e1c7cfda6..000000000000 --- a/primitives/core/src/bounded/bounded_vec.rs +++ /dev/null @@ -1,1305 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! Traits, types and structs to support putting a bounded vector into storage, as a raw value, map -//! or a double map. - -use super::WeakBoundedVec; -use crate::{Get, TryCollect}; -use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; -use core::{ - ops::{Deref, Index, IndexMut, RangeBounds}, - slice::SliceIndex, -}; -#[cfg(feature = "std")] -use serde::{ - de::{Error, SeqAccess, Visitor}, - Deserialize, Deserializer, Serialize, -}; -use sp_std::{marker::PhantomData, prelude::*}; - -/// A bounded vector. -/// -/// It has implementations for efficient append and length decoding, as with a normal `Vec<_>`, once -/// put into storage as a raw value, map or double-map. -/// -/// As the name suggests, the length of the queue is always bounded. All internal operations ensure -/// this bound is respected. -#[cfg_attr(feature = "std", derive(Serialize), serde(transparent))] -#[derive(Encode, scale_info::TypeInfo)] -#[scale_info(skip_type_params(S))] -pub struct BoundedVec( - pub(super) Vec, - #[cfg_attr(feature = "std", serde(skip_serializing))] PhantomData, -); - -/// Create an object through truncation. -pub trait TruncateFrom { - /// Create an object through truncation. - fn truncate_from(unbound: T) -> Self; -} - -#[cfg(feature = "std")] -impl<'de, T, S: Get> Deserialize<'de> for BoundedVec -where - T: Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct VecVisitor>(PhantomData<(T, S)>); - - impl<'de, T, S: Get> Visitor<'de> for VecVisitor - where - T: Deserialize<'de>, - { - type Value = Vec; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a sequence") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let size = seq.size_hint().unwrap_or(0); - let max = match usize::try_from(S::get()) { - Ok(n) => n, - Err(_) => return Err(A::Error::custom("can't convert to usize")), - }; - if size > max { - Err(A::Error::custom("out of bounds")) - } else { - let mut values = Vec::with_capacity(size); - - while let Some(value) = seq.next_element()? { - values.push(value); - if values.len() > max { - return Err(A::Error::custom("out of bounds")) - } - } - - Ok(values) - } - } - } - - let visitor: VecVisitor = VecVisitor(PhantomData); - deserializer - .deserialize_seq(visitor) - .map(|v| BoundedVec::::try_from(v).map_err(|_| Error::custom("out of bounds")))? - } -} - -/// A bounded slice. -/// -/// Similar to a `BoundedVec`, but not owned and cannot be decoded. -#[derive(Encode)] -pub struct BoundedSlice<'a, T, S>(pub(super) &'a [T], PhantomData); - -// This can be replaced with -// #[derive(scale_info::TypeInfo)] -// #[scale_info(skip_type_params(S))] -// again once this issue is fixed in the rust compiler: https://github.com/rust-lang/rust/issues/96956 -// Tracking issues: https://github.com/paritytech/substrate/issues/11915 -impl<'a, T, S> scale_info::TypeInfo for BoundedSlice<'a, T, S> -where - &'a [T]: scale_info::TypeInfo + 'static, - PhantomData: scale_info::TypeInfo + 'static, - T: scale_info::TypeInfo + 'static, - S: 'static, -{ - type Identity = Self; - - fn type_info() -> ::scale_info::Type { - scale_info::Type::builder() - .path(scale_info::Path::new("BoundedSlice", "sp_runtime::bounded::bounded_vec")) - .type_params(<[_]>::into_vec(Box::new([ - scale_info::TypeParameter::new( - "T", - core::option::Option::Some(::scale_info::meta_type::()), - ), - scale_info::TypeParameter::new("S", ::core::option::Option::None), - ]))) - .docs(&[ - "A bounded slice.", - "", - "Similar to a `BoundedVec`, but not owned and cannot be decoded.", - ]) - .composite( - scale_info::build::Fields::unnamed() - .field(|f| f.ty::<&'static [T]>().type_name("&'static[T]").docs(&[])) - .field(|f| f.ty::>().type_name("PhantomData").docs(&[])), - ) - } -} - -// `BoundedSlice`s encode to something which will always decode into a `BoundedVec`, -// `WeakBoundedVec`, or a `Vec`. -impl<'a, T: Encode + Decode, S: Get> EncodeLike> for BoundedSlice<'a, T, S> {} -impl<'a, T: Encode + Decode, S: Get> EncodeLike> - for BoundedSlice<'a, T, S> -{ -} -impl<'a, T: Encode + Decode, S: Get> EncodeLike> for BoundedSlice<'a, T, S> {} - -impl<'a, T, BoundSelf, BoundRhs> PartialEq> - for BoundedSlice<'a, T, BoundSelf> -where - T: PartialEq, - BoundSelf: Get, - BoundRhs: Get, -{ - fn eq(&self, other: &BoundedSlice<'a, T, BoundRhs>) -> bool { - self.0 == other.0 - } -} - -impl<'a, T, BoundSelf, BoundRhs> PartialEq> - for BoundedSlice<'a, T, BoundSelf> -where - T: PartialEq, - BoundSelf: Get, - BoundRhs: Get, -{ - fn eq(&self, other: &BoundedVec) -> bool { - self.0 == other.0 - } -} - -impl<'a, T, BoundSelf, BoundRhs> PartialEq> - for BoundedSlice<'a, T, BoundSelf> -where - T: PartialEq, - BoundSelf: Get, - BoundRhs: Get, -{ - fn eq(&self, other: &WeakBoundedVec) -> bool { - self.0 == other.0 - } -} - -impl<'a, T, S: Get> Eq for BoundedSlice<'a, T, S> where T: Eq {} - -impl<'a, T, BoundSelf, BoundRhs> PartialOrd> - for BoundedSlice<'a, T, BoundSelf> -where - T: PartialOrd, - BoundSelf: Get, - BoundRhs: Get, -{ - fn partial_cmp(&self, other: &BoundedSlice<'a, T, BoundRhs>) -> Option { - self.0.partial_cmp(other.0) - } -} - -impl<'a, T, BoundSelf, BoundRhs> PartialOrd> - for BoundedSlice<'a, T, BoundSelf> -where - T: PartialOrd, - BoundSelf: Get, - BoundRhs: Get, -{ - fn partial_cmp(&self, other: &BoundedVec) -> Option { - self.0.partial_cmp(&*other.0) - } -} - -impl<'a, T, BoundSelf, BoundRhs> PartialOrd> - for BoundedSlice<'a, T, BoundSelf> -where - T: PartialOrd, - BoundSelf: Get, - BoundRhs: Get, -{ - fn partial_cmp(&self, other: &WeakBoundedVec) -> Option { - self.0.partial_cmp(&*other.0) - } -} - -impl<'a, T: Ord, Bound: Get> Ord for BoundedSlice<'a, T, Bound> { - fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl<'a, T, S: Get> TryFrom<&'a [T]> for BoundedSlice<'a, T, S> { - type Error = &'a [T]; - fn try_from(t: &'a [T]) -> Result { - if t.len() <= S::get() as usize { - Ok(BoundedSlice(t, PhantomData)) - } else { - Err(t) - } - } -} - -impl<'a, T, S> From> for &'a [T] { - fn from(t: BoundedSlice<'a, T, S>) -> Self { - t.0 - } -} - -impl<'a, T, S: Get> TruncateFrom<&'a [T]> for BoundedSlice<'a, T, S> { - fn truncate_from(unbound: &'a [T]) -> Self { - BoundedSlice::::truncate_from(unbound) - } -} - -impl<'a, T, S> Clone for BoundedSlice<'a, T, S> { - fn clone(&self) -> Self { - BoundedSlice(self.0, PhantomData) - } -} - -impl<'a, T, S> sp_std::fmt::Debug for BoundedSlice<'a, T, S> -where - &'a [T]: sp_std::fmt::Debug, - S: Get, -{ - fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { - f.debug_tuple("BoundedSlice").field(&self.0).field(&S::get()).finish() - } -} - -// Since a reference `&T` is always `Copy`, so is `BoundedSlice<'a, T, S>`. -impl<'a, T, S> Copy for BoundedSlice<'a, T, S> {} - -// will allow for all immutable operations of `[T]` on `BoundedSlice`. -impl<'a, T, S> Deref for BoundedSlice<'a, T, S> { - type Target = [T]; - - fn deref(&self) -> &Self::Target { - self.0 - } -} - -impl<'a, T, S> sp_std::iter::IntoIterator for BoundedSlice<'a, T, S> { - type Item = &'a T; - type IntoIter = sp_std::slice::Iter<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl<'a, T, S: Get> BoundedSlice<'a, T, S> { - /// Create an instance from the first elements of the given slice (or all of it if it is smaller - /// than the length bound). - pub fn truncate_from(s: &'a [T]) -> Self { - Self(&s[0..(s.len().min(S::get() as usize))], PhantomData) - } -} - -impl> Decode for BoundedVec { - fn decode(input: &mut I) -> Result { - let inner = Vec::::decode(input)?; - if inner.len() > S::get() as usize { - return Err("BoundedVec exceeds its limit".into()) - } - Ok(Self(inner, PhantomData)) - } - - fn skip(input: &mut I) -> Result<(), codec::Error> { - Vec::::skip(input) - } -} - -// `BoundedVec`s encode to something which will always decode as a `Vec`. -impl> EncodeLike> for BoundedVec {} - -impl BoundedVec { - /// Create `Self` from `t` without any checks. - fn unchecked_from(t: Vec) -> Self { - Self(t, Default::default()) - } - - /// Consume self, and return the inner `Vec`. Henceforth, the `Vec<_>` can be altered in an - /// arbitrary way. At some point, if the reverse conversion is required, `TryFrom>` can - /// be used. - /// - /// This is useful for cases if you need access to an internal API of the inner `Vec<_>` which - /// is not provided by the wrapper `BoundedVec`. - pub fn into_inner(self) -> Vec { - self.0 - } - - /// Exactly the same semantics as [`slice::sort_by`]. - /// - /// This is safe since sorting cannot change the number of elements in the vector. - pub fn sort_by(&mut self, compare: F) - where - F: FnMut(&T, &T) -> sp_std::cmp::Ordering, - { - self.0.sort_by(compare) - } - - /// Exactly the same semantics as [`slice::sort_by_key`]. - /// - /// This is safe since sorting cannot change the number of elements in the vector. - pub fn sort_by_key(&mut self, f: F) - where - F: FnMut(&T) -> K, - K: sp_std::cmp::Ord, - { - self.0.sort_by_key(f) - } - - /// Exactly the same semantics as [`slice::sort`]. - /// - /// This is safe since sorting cannot change the number of elements in the vector. - pub fn sort(&mut self) - where - T: sp_std::cmp::Ord, - { - self.0.sort() - } - - /// Exactly the same semantics as `Vec::remove`. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn remove(&mut self, index: usize) -> T { - self.0.remove(index) - } - - /// Exactly the same semantics as `slice::swap_remove`. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn swap_remove(&mut self, index: usize) -> T { - self.0.swap_remove(index) - } - - /// Exactly the same semantics as `Vec::retain`. - pub fn retain bool>(&mut self, f: F) { - self.0.retain(f) - } - - /// Exactly the same semantics as `slice::get_mut`. - pub fn get_mut>( - &mut self, - index: I, - ) -> Option<&mut >::Output> { - self.0.get_mut(index) - } - - /// Exactly the same semantics as `Vec::truncate`. - /// - /// This is safe because `truncate` can never increase the length of the internal vector. - pub fn truncate(&mut self, s: usize) { - self.0.truncate(s); - } - - /// Exactly the same semantics as `Vec::pop`. - /// - /// This is safe since popping can only shrink the inner vector. - pub fn pop(&mut self) -> Option { - self.0.pop() - } - - /// Exactly the same semantics as [`slice::iter_mut`]. - pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, T> { - self.0.iter_mut() - } - - /// Exactly the same semantics as [`slice::last_mut`]. - pub fn last_mut(&mut self) -> Option<&mut T> { - self.0.last_mut() - } - - /// Exact same semantics as [`Vec::drain`]. - pub fn drain(&mut self, range: R) -> sp_std::vec::Drain<'_, T> - where - R: RangeBounds, - { - self.0.drain(range) - } -} - -impl> From> for Vec { - fn from(x: BoundedVec) -> Vec { - x.0 - } -} - -impl> BoundedVec { - /// Pre-allocate `capacity` items in self. - /// - /// If `capacity` is greater than [`Self::bound`], then the minimum of the two is used. - pub fn with_bounded_capacity(capacity: usize) -> Self { - let capacity = capacity.min(Self::bound()); - Self(Vec::with_capacity(capacity), Default::default()) - } - - /// Allocate self with the maximum possible capacity. - pub fn with_max_capacity() -> Self { - Self::with_bounded_capacity(Self::bound()) - } - - /// Consume and truncate the vector `v` in order to create a new instance of `Self` from it. - pub fn truncate_from(mut v: Vec) -> Self { - v.truncate(Self::bound()); - Self::unchecked_from(v) - } - - /// Get the bound of the type in `usize`. - pub fn bound() -> usize { - S::get() as usize - } - - /// Returns true of this collection is full. - pub fn is_full(&self) -> bool { - self.len() >= Self::bound() - } - - /// Forces the insertion of `element` into `self` retaining all items with index at least - /// `index`. - /// - /// If `index == 0` and `self.len() == Self::bound()`, then this is a no-op. - /// - /// If `Self::bound() < index` or `self.len() < index`, then this is also a no-op. - /// - /// Returns `Ok(maybe_removed)` if the item was inserted, where `maybe_removed` is - /// `Some(removed)` if an item was removed to make room for the new one. Returns `Err(())` if - /// `element` cannot be inserted. - pub fn force_insert_keep_right( - &mut self, - index: usize, - mut element: T, - ) -> Result, ()> { - // Check against panics. - if Self::bound() < index || self.len() < index { - Err(()) - } else if self.len() < Self::bound() { - // Cannot panic since self.len() >= index; - self.0.insert(index, element); - Ok(None) - } else { - if index == 0 { - return Err(()) - } - sp_std::mem::swap(&mut self[0], &mut element); - // `[0..index] cannot panic since self.len() >= index. - // `rotate_left(1)` cannot panic because there is at least 1 element. - self[0..index].rotate_left(1); - Ok(Some(element)) - } - } - - /// Forces the insertion of `element` into `self` retaining all items with index at most - /// `index`. - /// - /// If `index == Self::bound()` and `self.len() == Self::bound()`, then this is a no-op. - /// - /// If `Self::bound() < index` or `self.len() < index`, then this is also a no-op. - /// - /// Returns `Ok(maybe_removed)` if the item was inserted, where `maybe_removed` is - /// `Some(removed)` if an item was removed to make room for the new one. Returns `Err(())` if - /// `element` cannot be inserted. - pub fn force_insert_keep_left(&mut self, index: usize, element: T) -> Result, ()> { - // Check against panics. - if Self::bound() < index || self.len() < index || Self::bound() == 0 { - return Err(()) - } - // Noop condition. - if Self::bound() == index && self.len() <= Self::bound() { - return Err(()) - } - let maybe_removed = if self.is_full() { - // defensive-only: since we are at capacity, this is a noop. - self.0.truncate(Self::bound()); - // if we truncate anything, it will be the last one. - self.0.pop() - } else { - None - }; - - // Cannot panic since `self.len() >= index`; - self.0.insert(index, element); - Ok(maybe_removed) - } - - /// Move the position of an item from one location to another in the slice. - /// - /// Except for the item being moved, the order of the slice remains the same. - /// - /// - `index` is the location of the item to be moved. - /// - `insert_position` is the index of the item in the slice which should *immediately follow* - /// the item which is being moved. - /// - /// Returns `true` of the operation was successful, otherwise `false` if a noop. - pub fn slide(&mut self, index: usize, insert_position: usize) -> bool { - // Check against panics. - if self.len() <= index || self.len() < insert_position || index == usize::MAX { - return false - } - // Noop conditions. - if index == insert_position || index + 1 == insert_position { - return false - } - if insert_position < index && index < self.len() { - // --- --- --- === === === === @@@ --- --- --- - // ^-- N ^O^ - // ... - // /-----<<<-----\ - // --- --- --- === === === === @@@ --- --- --- - // >>> >>> >>> >>> - // ... - // --- --- --- @@@ === === === === --- --- --- - // ^N^ - self[insert_position..index + 1].rotate_right(1); - return true - } else if insert_position > 0 && index + 1 < insert_position { - // Note that the apparent asymmetry of these two branches is due to the - // fact that the "new" position is the position to be inserted *before*. - // --- --- --- @@@ === === === === --- --- --- - // ^O^ ^-- N - // ... - // /----->>>-----\ - // --- --- --- @@@ === === === === --- --- --- - // <<< <<< <<< <<< - // ... - // --- --- --- === === === === @@@ --- --- --- - // ^N^ - self[index..insert_position].rotate_left(1); - return true - } - - debug_assert!(false, "all noop conditions should have been covered above"); - false - } - - /// Forces the insertion of `s` into `self` truncating first if necessary. - /// - /// Infallible, but if the bound is zero, then it's a no-op. - pub fn force_push(&mut self, element: T) { - if Self::bound() > 0 { - self.0.truncate(Self::bound() as usize - 1); - self.0.push(element); - } - } - - /// Same as `Vec::resize`, but if `size` is more than [`Self::bound`], then [`Self::bound`] is - /// used. - pub fn bounded_resize(&mut self, size: usize, value: T) - where - T: Clone, - { - let size = size.min(Self::bound()); - self.0.resize(size, value); - } - - /// Exactly the same semantics as [`Vec::extend`], but returns an error and does nothing if the - /// length of the outcome is larger than the bound. - pub fn try_extend( - &mut self, - with: impl IntoIterator + ExactSizeIterator, - ) -> Result<(), ()> { - if with.len().saturating_add(self.len()) <= Self::bound() { - self.0.extend(with); - Ok(()) - } else { - Err(()) - } - } - - /// Exactly the same semantics as [`Vec::append`], but returns an error and does nothing if the - /// length of the outcome is larger than the bound. - pub fn try_append(&mut self, other: &mut Vec) -> Result<(), ()> { - if other.len().saturating_add(self.len()) <= Self::bound() { - self.0.append(other); - Ok(()) - } else { - Err(()) - } - } - - /// Consumes self and mutates self via the given `mutate` function. - /// - /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is - /// returned. - /// - /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> - /// [`Self::try_from`]. - pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut Vec)) -> Option { - mutate(&mut self.0); - (self.0.len() <= Self::bound()).then(move || self) - } - - /// Exactly the same semantics as [`Vec::insert`], but returns an `Err` (and is a noop) if the - /// new length of the vector exceeds `S`. - /// - /// # Panics - /// - /// Panics if `index > len`. - pub fn try_insert(&mut self, index: usize, element: T) -> Result<(), T> { - if self.len() < Self::bound() { - self.0.insert(index, element); - Ok(()) - } else { - Err(element) - } - } - - /// Exactly the same semantics as [`Vec::push`], but returns an `Err` (and is a noop) if the - /// new length of the vector exceeds `S`. - /// - /// # Panics - /// - /// Panics if the new capacity exceeds isize::MAX bytes. - pub fn try_push(&mut self, element: T) -> Result<(), T> { - if self.len() < Self::bound() { - self.0.push(element); - Ok(()) - } else { - Err(element) - } - } -} - -impl BoundedVec { - /// Return a [`BoundedSlice`] with the content and bound of [`Self`]. - pub fn as_bounded_slice(&self) -> BoundedSlice { - BoundedSlice(&self.0[..], PhantomData::default()) - } -} - -impl Default for BoundedVec { - fn default() -> Self { - // the bound cannot be below 0, which is satisfied by an empty vector - Self::unchecked_from(Vec::default()) - } -} - -impl sp_std::fmt::Debug for BoundedVec -where - Vec: sp_std::fmt::Debug, - S: Get, -{ - fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { - f.debug_tuple("BoundedVec").field(&self.0).field(&Self::bound()).finish() - } -} - -impl Clone for BoundedVec -where - T: Clone, -{ - fn clone(&self) -> Self { - // bound is retained - Self::unchecked_from(self.0.clone()) - } -} - -impl> TryFrom> for BoundedVec { - type Error = Vec; - fn try_from(t: Vec) -> Result { - if t.len() <= Self::bound() { - // explicit check just above - Ok(Self::unchecked_from(t)) - } else { - Err(t) - } - } -} - -impl> TruncateFrom> for BoundedVec { - fn truncate_from(unbound: Vec) -> Self { - BoundedVec::::truncate_from(unbound) - } -} - -// It is okay to give a non-mutable reference of the inner vec to anyone. -impl AsRef> for BoundedVec { - fn as_ref(&self) -> &Vec { - &self.0 - } -} - -impl AsRef<[T]> for BoundedVec { - fn as_ref(&self) -> &[T] { - &self.0 - } -} - -impl AsMut<[T]> for BoundedVec { - fn as_mut(&mut self) -> &mut [T] { - &mut self.0 - } -} - -// will allow for all immutable operations of `Vec` on `BoundedVec`. -impl Deref for BoundedVec { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -// Allows for indexing similar to a normal `Vec`. Can panic if out of bound. -impl Index for BoundedVec -where - I: SliceIndex<[T]>, -{ - type Output = I::Output; - - #[inline] - fn index(&self, index: I) -> &Self::Output { - self.0.index(index) - } -} - -impl IndexMut for BoundedVec -where - I: SliceIndex<[T]>, -{ - #[inline] - fn index_mut(&mut self, index: I) -> &mut Self::Output { - self.0.index_mut(index) - } -} - -impl sp_std::iter::IntoIterator for BoundedVec { - type Item = T; - type IntoIter = sp_std::vec::IntoIter; - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl<'a, T, S> sp_std::iter::IntoIterator for &'a BoundedVec { - type Item = &'a T; - type IntoIter = sp_std::slice::Iter<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl<'a, T, S> sp_std::iter::IntoIterator for &'a mut BoundedVec { - type Item = &'a mut T; - type IntoIter = sp_std::slice::IterMut<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter_mut() - } -} - -impl codec::DecodeLength for BoundedVec { - fn len(self_encoded: &[u8]) -> Result { - // `BoundedVec` stored just a `Vec`, thus the length is at the beginning in - // `Compact` form, and same implementation as `Vec` can be used. - as codec::DecodeLength>::len(self_encoded) - } -} - -impl PartialEq> for BoundedVec -where - T: PartialEq, - BoundSelf: Get, - BoundRhs: Get, -{ - fn eq(&self, rhs: &BoundedVec) -> bool { - self.0 == rhs.0 - } -} - -impl PartialEq> for BoundedVec -where - T: PartialEq, - BoundSelf: Get, - BoundRhs: Get, -{ - fn eq(&self, rhs: &WeakBoundedVec) -> bool { - self.0 == rhs.0 - } -} - -impl<'a, T, BoundSelf, BoundRhs> PartialEq> - for BoundedVec -where - T: PartialEq, - BoundSelf: Get, - BoundRhs: Get, -{ - fn eq(&self, rhs: &BoundedSlice<'a, T, BoundRhs>) -> bool { - self.0 == rhs.0 - } -} - -impl<'a, T: PartialEq, S: Get> PartialEq<&'a [T]> for BoundedSlice<'a, T, S> { - fn eq(&self, other: &&'a [T]) -> bool { - &self.0 == other - } -} - -impl> PartialEq> for BoundedVec { - fn eq(&self, other: &Vec) -> bool { - &self.0 == other - } -} - -impl> Eq for BoundedVec where T: Eq {} - -impl PartialOrd> for BoundedVec -where - T: PartialOrd, - BoundSelf: Get, - BoundRhs: Get, -{ - fn partial_cmp(&self, other: &BoundedVec) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl PartialOrd> for BoundedVec -where - T: PartialOrd, - BoundSelf: Get, - BoundRhs: Get, -{ - fn partial_cmp(&self, other: &WeakBoundedVec) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl<'a, T, BoundSelf, BoundRhs> PartialOrd> - for BoundedVec -where - T: PartialOrd, - BoundSelf: Get, - BoundRhs: Get, -{ - fn partial_cmp(&self, other: &BoundedSlice<'a, T, BoundRhs>) -> Option { - (&*self.0).partial_cmp(other.0) - } -} - -impl> Ord for BoundedVec { - fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl MaxEncodedLen for BoundedVec -where - T: MaxEncodedLen, - S: Get, - BoundedVec: Encode, -{ - fn max_encoded_len() -> usize { - // BoundedVec encodes like Vec which encodes like [T], which is a compact u32 - // plus each item in the slice: - // See: https://docs.substrate.io/reference/scale-codec/ - codec::Compact(S::get()) - .encoded_size() - .saturating_add(Self::bound().saturating_mul(T::max_encoded_len())) - } -} - -impl TryCollect> for I -where - I: ExactSizeIterator + Iterator, - Bound: Get, -{ - type Error = &'static str; - - fn try_collect(self) -> Result, Self::Error> { - if self.len() > Bound::get() as usize { - Err("iterator length too big") - } else { - Ok(BoundedVec::::unchecked_from(self.collect::>())) - } - } -} - -#[cfg(test)] -pub mod test { - use super::*; - use crate::{bounded_vec, ConstU32}; - - #[test] - fn slice_truncate_from_works() { - let bounded = BoundedSlice::>::truncate_from(&[1, 2, 3, 4, 5]); - assert_eq!(bounded.deref(), &[1, 2, 3, 4]); - let bounded = BoundedSlice::>::truncate_from(&[1, 2, 3, 4]); - assert_eq!(bounded.deref(), &[1, 2, 3, 4]); - let bounded = BoundedSlice::>::truncate_from(&[1, 2, 3]); - assert_eq!(bounded.deref(), &[1, 2, 3]); - } - - #[test] - fn slide_works() { - let mut b: BoundedVec> = bounded_vec![0, 1, 2, 3, 4, 5]; - assert!(b.slide(1, 5)); - assert_eq!(*b, vec![0, 2, 3, 4, 1, 5]); - assert!(b.slide(4, 0)); - assert_eq!(*b, vec![1, 0, 2, 3, 4, 5]); - assert!(b.slide(0, 2)); - assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); - assert!(b.slide(1, 6)); - assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); - assert!(b.slide(0, 6)); - assert_eq!(*b, vec![2, 3, 4, 5, 1, 0]); - assert!(b.slide(5, 0)); - assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); - assert!(!b.slide(6, 0)); - assert!(!b.slide(7, 0)); - assert_eq!(*b, vec![0, 2, 3, 4, 5, 1]); - - let mut c: BoundedVec> = bounded_vec![0, 1, 2]; - assert!(!c.slide(1, 5)); - assert_eq!(*c, vec![0, 1, 2]); - assert!(!c.slide(4, 0)); - assert_eq!(*c, vec![0, 1, 2]); - assert!(!c.slide(3, 0)); - assert_eq!(*c, vec![0, 1, 2]); - assert!(c.slide(2, 0)); - assert_eq!(*c, vec![2, 0, 1]); - } - - #[test] - fn slide_noops_work() { - let mut b: BoundedVec> = bounded_vec![0, 1, 2, 3, 4, 5]; - assert!(!b.slide(3, 3)); - assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); - assert!(!b.slide(3, 4)); - assert_eq!(*b, vec![0, 1, 2, 3, 4, 5]); - } - - #[test] - fn force_insert_keep_left_works() { - let mut b: BoundedVec> = bounded_vec![]; - assert_eq!(b.force_insert_keep_left(1, 10), Err(())); - assert!(b.is_empty()); - - assert_eq!(b.force_insert_keep_left(0, 30), Ok(None)); - assert_eq!(b.force_insert_keep_left(0, 10), Ok(None)); - assert_eq!(b.force_insert_keep_left(1, 20), Ok(None)); - assert_eq!(b.force_insert_keep_left(3, 40), Ok(None)); - assert_eq!(*b, vec![10, 20, 30, 40]); - // at capacity. - assert_eq!(b.force_insert_keep_left(4, 41), Err(())); - assert_eq!(*b, vec![10, 20, 30, 40]); - assert_eq!(b.force_insert_keep_left(3, 31), Ok(Some(40))); - assert_eq!(*b, vec![10, 20, 30, 31]); - assert_eq!(b.force_insert_keep_left(1, 11), Ok(Some(31))); - assert_eq!(*b, vec![10, 11, 20, 30]); - assert_eq!(b.force_insert_keep_left(0, 1), Ok(Some(30))); - assert_eq!(*b, vec![1, 10, 11, 20]); - - let mut z: BoundedVec> = bounded_vec![]; - assert!(z.is_empty()); - assert_eq!(z.force_insert_keep_left(0, 10), Err(())); - assert!(z.is_empty()); - } - - #[test] - fn force_insert_keep_right_works() { - let mut b: BoundedVec> = bounded_vec![]; - assert_eq!(b.force_insert_keep_right(1, 10), Err(())); - assert!(b.is_empty()); - - assert_eq!(b.force_insert_keep_right(0, 30), Ok(None)); - assert_eq!(b.force_insert_keep_right(0, 10), Ok(None)); - assert_eq!(b.force_insert_keep_right(1, 20), Ok(None)); - assert_eq!(b.force_insert_keep_right(3, 40), Ok(None)); - assert_eq!(*b, vec![10, 20, 30, 40]); - - // at capacity. - assert_eq!(b.force_insert_keep_right(0, 0), Err(())); - assert_eq!(*b, vec![10, 20, 30, 40]); - assert_eq!(b.force_insert_keep_right(1, 11), Ok(Some(10))); - assert_eq!(*b, vec![11, 20, 30, 40]); - assert_eq!(b.force_insert_keep_right(3, 31), Ok(Some(11))); - assert_eq!(*b, vec![20, 30, 31, 40]); - assert_eq!(b.force_insert_keep_right(4, 41), Ok(Some(20))); - assert_eq!(*b, vec![30, 31, 40, 41]); - - assert_eq!(b.force_insert_keep_right(5, 69), Err(())); - assert_eq!(*b, vec![30, 31, 40, 41]); - - let mut z: BoundedVec> = bounded_vec![]; - assert!(z.is_empty()); - assert_eq!(z.force_insert_keep_right(0, 10), Err(())); - assert!(z.is_empty()); - } - - #[test] - fn bound_returns_correct_value() { - assert_eq!(BoundedVec::>::bound(), 7); - } - - #[test] - fn try_insert_works() { - let mut bounded: BoundedVec> = bounded_vec![1, 2, 3]; - bounded.try_insert(1, 0).unwrap(); - assert_eq!(*bounded, vec![1, 0, 2, 3]); - - assert!(bounded.try_insert(0, 9).is_err()); - assert_eq!(*bounded, vec![1, 0, 2, 3]); - } - - #[test] - fn constructor_macro_works() { - // With values. Use some brackets to make sure the macro doesn't expand. - let bv: BoundedVec<(u32, u32), ConstU32<3>> = bounded_vec![(1, 2), (1, 2), (1, 2)]; - assert_eq!(bv, vec![(1, 2), (1, 2), (1, 2)]); - - // With repetition. - let bv: BoundedVec<(u32, u32), ConstU32<3>> = bounded_vec![(1, 2); 3]; - assert_eq!(bv, vec![(1, 2), (1, 2), (1, 2)]); - } - - #[test] - #[should_panic(expected = "insertion index (is 9) should be <= len (is 3)")] - fn try_inert_panics_if_oob() { - let mut bounded: BoundedVec> = bounded_vec![1, 2, 3]; - bounded.try_insert(9, 0).unwrap(); - } - - #[test] - fn try_push_works() { - let mut bounded: BoundedVec> = bounded_vec![1, 2, 3]; - bounded.try_push(0).unwrap(); - assert_eq!(*bounded, vec![1, 2, 3, 0]); - - assert!(bounded.try_push(9).is_err()); - } - - #[test] - fn deref_vec_coercion_works() { - let bounded: BoundedVec> = bounded_vec![1, 2, 3]; - // these methods come from deref-ed vec. - assert_eq!(bounded.len(), 3); - assert!(bounded.iter().next().is_some()); - assert!(!bounded.is_empty()); - } - - #[test] - fn deref_slice_coercion_works() { - let bounded = BoundedSlice::>::try_from(&[1, 2, 3][..]).unwrap(); - // these methods come from deref-ed slice. - assert_eq!(bounded.len(), 3); - assert!(bounded.iter().next().is_some()); - assert!(!bounded.is_empty()); - } - - #[test] - fn try_mutate_works() { - let bounded: BoundedVec> = bounded_vec![1, 2, 3, 4, 5, 6]; - let bounded = bounded.try_mutate(|v| v.push(7)).unwrap(); - assert_eq!(bounded.len(), 7); - assert!(bounded.try_mutate(|v| v.push(8)).is_none()); - } - - #[test] - fn slice_indexing_works() { - let bounded: BoundedVec> = bounded_vec![1, 2, 3, 4, 5, 6]; - assert_eq!(&bounded[0..=2], &[1, 2, 3]); - } - - #[test] - fn vec_eq_works() { - let bounded: BoundedVec> = bounded_vec![1, 2, 3, 4, 5, 6]; - assert_eq!(bounded, vec![1, 2, 3, 4, 5, 6]); - } - - #[test] - fn too_big_vec_fail_to_decode() { - let v: Vec = vec![1, 2, 3, 4, 5]; - assert_eq!( - BoundedVec::>::decode(&mut &v.encode()[..]), - Err("BoundedVec exceeds its limit".into()), - ); - } - - #[test] - fn eq_works() { - // of same type - let b1: BoundedVec> = bounded_vec![1, 2, 3]; - let b2: BoundedVec> = bounded_vec![1, 2, 3]; - assert_eq!(b1, b2); - - // of different type, but same value and bound. - crate::parameter_types! { - B1: u32 = 7; - B2: u32 = 7; - } - let b1: BoundedVec = bounded_vec![1, 2, 3]; - let b2: BoundedVec = bounded_vec![1, 2, 3]; - assert_eq!(b1, b2); - } - - #[test] - fn ord_works() { - use std::cmp::Ordering; - let b1: BoundedVec> = bounded_vec![1, 2, 3]; - let b2: BoundedVec> = bounded_vec![1, 3, 2]; - - // ordering for vec is lexicographic. - assert_eq!(b1.cmp(&b2), Ordering::Less); - assert_eq!(b1.cmp(&b2), b1.into_inner().cmp(&b2.into_inner())); - } - - #[test] - fn try_extend_works() { - let mut b: BoundedVec> = bounded_vec![1, 2, 3]; - - assert!(b.try_extend(vec![4].into_iter()).is_ok()); - assert_eq!(*b, vec![1, 2, 3, 4]); - - assert!(b.try_extend(vec![5].into_iter()).is_ok()); - assert_eq!(*b, vec![1, 2, 3, 4, 5]); - - assert!(b.try_extend(vec![6].into_iter()).is_err()); - assert_eq!(*b, vec![1, 2, 3, 4, 5]); - - let mut b: BoundedVec> = bounded_vec![1, 2, 3]; - assert!(b.try_extend(vec![4, 5].into_iter()).is_ok()); - assert_eq!(*b, vec![1, 2, 3, 4, 5]); - - let mut b: BoundedVec> = bounded_vec![1, 2, 3]; - assert!(b.try_extend(vec![4, 5, 6].into_iter()).is_err()); - assert_eq!(*b, vec![1, 2, 3]); - } - - #[test] - fn test_serializer() { - let c: BoundedVec> = bounded_vec![0, 1, 2]; - assert_eq!(serde_json::json!(&c).to_string(), r#"[0,1,2]"#); - } - - #[test] - fn test_deserializer() { - let c: BoundedVec> = serde_json::from_str(r#"[0,1,2]"#).unwrap(); - - assert_eq!(c.len(), 3); - assert_eq!(c[0], 0); - assert_eq!(c[1], 1); - assert_eq!(c[2], 2); - } - - #[test] - fn test_deserializer_failed() { - let c: Result>, serde_json::error::Error> = - serde_json::from_str(r#"[0,1,2,3,4,5]"#); - - match c { - Err(msg) => assert_eq!(msg.to_string(), "out of bounds at line 1 column 11"), - _ => unreachable!("deserializer must raise error"), - } - } - - #[test] - fn bounded_vec_try_from_works() { - assert!(BoundedVec::>::try_from(vec![0]).is_ok()); - assert!(BoundedVec::>::try_from(vec![0, 1]).is_ok()); - assert!(BoundedVec::>::try_from(vec![0, 1, 2]).is_err()); - } - - #[test] - fn bounded_slice_try_from_works() { - assert!(BoundedSlice::>::try_from(&[0][..]).is_ok()); - assert!(BoundedSlice::>::try_from(&[0, 1][..]).is_ok()); - assert!(BoundedSlice::>::try_from(&[0, 1, 2][..]).is_err()); - } - - #[test] - fn can_be_collected() { - let b1: BoundedVec> = bounded_vec![1, 2, 3, 4]; - let b2: BoundedVec> = b1.iter().map(|x| x + 1).try_collect().unwrap(); - assert_eq!(b2, vec![2, 3, 4, 5]); - - // can also be collected into a collection of length 4. - let b2: BoundedVec> = b1.iter().map(|x| x + 1).try_collect().unwrap(); - assert_eq!(b2, vec![2, 3, 4, 5]); - - // can be mutated further into iterators that are `ExactSizedIterator`. - let b2: BoundedVec> = - b1.iter().map(|x| x + 1).rev().try_collect().unwrap(); - assert_eq!(b2, vec![5, 4, 3, 2]); - - let b2: BoundedVec> = - b1.iter().map(|x| x + 1).rev().skip(2).try_collect().unwrap(); - assert_eq!(b2, vec![3, 2]); - let b2: BoundedVec> = - b1.iter().map(|x| x + 1).rev().skip(2).try_collect().unwrap(); - assert_eq!(b2, vec![3, 2]); - - let b2: BoundedVec> = - b1.iter().map(|x| x + 1).rev().take(2).try_collect().unwrap(); - assert_eq!(b2, vec![5, 4]); - let b2: BoundedVec> = - b1.iter().map(|x| x + 1).rev().take(2).try_collect().unwrap(); - assert_eq!(b2, vec![5, 4]); - - // but these worn't work - let b2: Result>, _> = b1.iter().map(|x| x + 1).try_collect(); - assert!(b2.is_err()); - - let b2: Result>, _> = - b1.iter().map(|x| x + 1).rev().take(2).try_collect(); - assert!(b2.is_err()); - } - - #[test] - fn bounded_vec_debug_works() { - let bound = BoundedVec::>::truncate_from(vec![1, 2, 3]); - assert_eq!(format!("{:?}", bound), "BoundedVec([1, 2, 3], 5)"); - } - - #[test] - fn bounded_slice_debug_works() { - let bound = BoundedSlice::>::truncate_from(&[1, 2, 3]); - assert_eq!(format!("{:?}", bound), "BoundedSlice([1, 2, 3], 5)"); - } - - #[test] - fn bounded_vec_sort_by_key_works() { - let mut v: BoundedVec> = bounded_vec![-5, 4, 1, -3, 2]; - // Sort by absolute value. - v.sort_by_key(|k| k.abs()); - assert_eq!(v, vec![1, 2, -3, 4, -5]); - } - - #[test] - fn bounded_vec_truncate_from_works() { - let unbound = vec![1, 2, 3, 4, 5]; - let bound = BoundedVec::>::truncate_from(unbound.clone()); - assert_eq!(bound, vec![1, 2, 3]); - } - - #[test] - fn bounded_slice_truncate_from_works() { - let unbound = [1, 2, 3, 4, 5]; - let bound = BoundedSlice::>::truncate_from(&unbound); - assert_eq!(bound, &[1, 2, 3][..]); - } - - #[test] - fn bounded_slice_partialeq_slice_works() { - let unbound = [1, 2, 3]; - let bound = BoundedSlice::>::truncate_from(&unbound); - - assert_eq!(bound, &unbound[..]); - assert!(bound == &unbound[..]); - } -} diff --git a/primitives/core/src/bounded/weak_bounded_vec.rs b/primitives/core/src/bounded/weak_bounded_vec.rs deleted file mode 100644 index 5aff35f010c8..000000000000 --- a/primitives/core/src/bounded/weak_bounded_vec.rs +++ /dev/null @@ -1,524 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! Traits, types and structs to support putting a bounded vector into storage, as a raw value, map -//! or a double map. - -use super::{BoundedSlice, BoundedVec}; -use crate::Get; -use codec::{Decode, Encode, MaxEncodedLen}; -use core::{ - ops::{Deref, Index, IndexMut}, - slice::SliceIndex, -}; -#[cfg(feature = "std")] -use serde::{ - de::{Error, SeqAccess, Visitor}, - Deserialize, Deserializer, Serialize, -}; -use sp_std::{marker::PhantomData, prelude::*}; - -/// A weakly bounded vector. -/// -/// It has implementations for efficient append and length decoding, as with a normal `Vec<_>`, once -/// put into storage as a raw value, map or double-map. -/// -/// The length of the vec is not strictly bounded. Decoding a vec with more element that the bound -/// is accepted, and some method allow to bypass the restriction with warnings. -#[cfg_attr(feature = "std", derive(Serialize), serde(transparent))] -#[derive(Encode, scale_info::TypeInfo)] -#[scale_info(skip_type_params(S))] -pub struct WeakBoundedVec( - pub(super) Vec, - #[cfg_attr(feature = "std", serde(skip_serializing))] PhantomData, -); - -#[cfg(feature = "std")] -impl<'de, T, S: Get> Deserialize<'de> for WeakBoundedVec -where - T: Deserialize<'de>, -{ - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct VecVisitor>(PhantomData<(T, S)>); - - impl<'de, T, S: Get> Visitor<'de> for VecVisitor - where - T: Deserialize<'de>, - { - type Value = Vec; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a sequence") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let size = seq.size_hint().unwrap_or(0); - let max = match usize::try_from(S::get()) { - Ok(n) => n, - Err(_) => return Err(A::Error::custom("can't convert to usize")), - }; - if size > max { - log::warn!( - target: "runtime", - "length of a bounded vector while deserializing is not respected.", - ); - } - let mut values = Vec::with_capacity(size); - - while let Some(value) = seq.next_element()? { - values.push(value); - if values.len() > max { - log::warn!( - target: "runtime", - "length of a bounded vector while deserializing is not respected.", - ); - } - } - - Ok(values) - } - } - - let visitor: VecVisitor = VecVisitor(PhantomData); - deserializer.deserialize_seq(visitor).map(|v| { - WeakBoundedVec::::try_from(v).map_err(|_| Error::custom("out of bounds")) - })? - } -} - -impl> Decode for WeakBoundedVec { - fn decode(input: &mut I) -> Result { - let inner = Vec::::decode(input)?; - Ok(Self::force_from(inner, Some("decode"))) - } - - fn skip(input: &mut I) -> Result<(), codec::Error> { - Vec::::skip(input) - } -} - -impl WeakBoundedVec { - /// Create `Self` from `t` without any checks. - fn unchecked_from(t: Vec) -> Self { - Self(t, Default::default()) - } - - /// Consume self, and return the inner `Vec`. Henceforth, the `Vec<_>` can be altered in an - /// arbitrary way. At some point, if the reverse conversion is required, `TryFrom>` can - /// be used. - /// - /// This is useful for cases if you need access to an internal API of the inner `Vec<_>` which - /// is not provided by the wrapper `WeakBoundedVec`. - pub fn into_inner(self) -> Vec { - self.0 - } - - /// Exactly the same semantics as [`Vec::remove`]. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn remove(&mut self, index: usize) -> T { - self.0.remove(index) - } - - /// Exactly the same semantics as [`Vec::swap_remove`]. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn swap_remove(&mut self, index: usize) -> T { - self.0.swap_remove(index) - } - - /// Exactly the same semantics as [`Vec::retain`]. - pub fn retain bool>(&mut self, f: F) { - self.0.retain(f) - } - - /// Exactly the same semantics as [`slice::get_mut`]. - pub fn get_mut>( - &mut self, - index: I, - ) -> Option<&mut >::Output> { - self.0.get_mut(index) - } -} - -impl> WeakBoundedVec { - /// Get the bound of the type in `usize`. - pub fn bound() -> usize { - S::get() as usize - } - - /// Create `Self` from `t` without any checks. Logs warnings if the bound is not being - /// respected. The additional scope can be used to indicate where a potential overflow is - /// happening. - pub fn force_from(t: Vec, scope: Option<&'static str>) -> Self { - if t.len() > Self::bound() { - log::warn!( - target: "runtime", - "length of a bounded vector in scope {} is not respected.", - scope.unwrap_or("UNKNOWN"), - ); - } - - Self::unchecked_from(t) - } - - /// Consumes self and mutates self via the given `mutate` function. - /// - /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is - /// returned. - /// - /// This is essentially a *consuming* shorthand [`Self::into_inner`] -> `...` -> - /// [`Self::try_from`]. - pub fn try_mutate(mut self, mut mutate: impl FnMut(&mut Vec)) -> Option { - mutate(&mut self.0); - (self.0.len() <= Self::bound()).then(move || self) - } - - /// Exactly the same semantics as [`Vec::insert`], but returns an `Err` (and is a noop) if the - /// new length of the vector exceeds `S`. - /// - /// # Panics - /// - /// Panics if `index > len`. - pub fn try_insert(&mut self, index: usize, element: T) -> Result<(), ()> { - if self.len() < Self::bound() { - self.0.insert(index, element); - Ok(()) - } else { - Err(()) - } - } - - /// Exactly the same semantics as [`Vec::push`], but returns an `Err` (and is a noop) if the - /// new length of the vector exceeds `S`. - /// - /// # Panics - /// - /// Panics if the new capacity exceeds isize::MAX bytes. - pub fn try_push(&mut self, element: T) -> Result<(), ()> { - if self.len() < Self::bound() { - self.0.push(element); - Ok(()) - } else { - Err(()) - } - } -} - -impl Default for WeakBoundedVec { - fn default() -> Self { - // the bound cannot be below 0, which is satisfied by an empty vector - Self::unchecked_from(Vec::default()) - } -} - -impl sp_std::fmt::Debug for WeakBoundedVec -where - Vec: sp_std::fmt::Debug, - S: Get, -{ - fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { - f.debug_tuple("WeakBoundedVec").field(&self.0).field(&Self::bound()).finish() - } -} - -impl Clone for WeakBoundedVec -where - T: Clone, -{ - fn clone(&self) -> Self { - // bound is retained - Self::unchecked_from(self.0.clone()) - } -} - -impl> TryFrom> for WeakBoundedVec { - type Error = (); - fn try_from(t: Vec) -> Result { - if t.len() <= Self::bound() { - // explicit check just above - Ok(Self::unchecked_from(t)) - } else { - Err(()) - } - } -} - -// It is okay to give a non-mutable reference of the inner vec to anyone. -impl AsRef> for WeakBoundedVec { - fn as_ref(&self) -> &Vec { - &self.0 - } -} - -impl AsRef<[T]> for WeakBoundedVec { - fn as_ref(&self) -> &[T] { - &self.0 - } -} - -impl AsMut<[T]> for WeakBoundedVec { - fn as_mut(&mut self) -> &mut [T] { - &mut self.0 - } -} - -// will allow for immutable all operations of `Vec` on `WeakBoundedVec`. -impl Deref for WeakBoundedVec { - type Target = Vec; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -// Allows for indexing similar to a normal `Vec`. Can panic if out of bound. -impl Index for WeakBoundedVec -where - I: SliceIndex<[T]>, -{ - type Output = I::Output; - - #[inline] - fn index(&self, index: I) -> &Self::Output { - self.0.index(index) - } -} - -impl IndexMut for WeakBoundedVec -where - I: SliceIndex<[T]>, -{ - #[inline] - fn index_mut(&mut self, index: I) -> &mut Self::Output { - self.0.index_mut(index) - } -} - -impl sp_std::iter::IntoIterator for WeakBoundedVec { - type Item = T; - type IntoIter = sp_std::vec::IntoIter; - fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() - } -} - -impl<'a, T, S> sp_std::iter::IntoIterator for &'a WeakBoundedVec { - type Item = &'a T; - type IntoIter = sp_std::slice::Iter<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - -impl<'a, T, S> sp_std::iter::IntoIterator for &'a mut WeakBoundedVec { - type Item = &'a mut T; - type IntoIter = sp_std::slice::IterMut<'a, T>; - fn into_iter(self) -> Self::IntoIter { - self.0.iter_mut() - } -} - -impl codec::DecodeLength for WeakBoundedVec { - fn len(self_encoded: &[u8]) -> Result { - // `WeakBoundedVec` stored just a `Vec`, thus the length is at the beginning in - // `Compact` form, and same implementation as `Vec` can be used. - as codec::DecodeLength>::len(self_encoded) - } -} - -impl PartialEq> for WeakBoundedVec -where - T: PartialEq, - BoundSelf: Get, - BoundRhs: Get, -{ - fn eq(&self, rhs: &WeakBoundedVec) -> bool { - self.0 == rhs.0 - } -} - -impl PartialEq> for WeakBoundedVec -where - T: PartialEq, - BoundSelf: Get, - BoundRhs: Get, -{ - fn eq(&self, rhs: &BoundedVec) -> bool { - self.0 == rhs.0 - } -} - -impl<'a, T, BoundSelf, BoundRhs> PartialEq> - for WeakBoundedVec -where - T: PartialEq, - BoundSelf: Get, - BoundRhs: Get, -{ - fn eq(&self, rhs: &BoundedSlice<'a, T, BoundRhs>) -> bool { - self.0 == rhs.0 - } -} - -impl> PartialEq> for WeakBoundedVec { - fn eq(&self, other: &Vec) -> bool { - &self.0 == other - } -} - -impl> Eq for WeakBoundedVec where T: Eq {} - -impl PartialOrd> - for WeakBoundedVec -where - T: PartialOrd, - BoundSelf: Get, - BoundRhs: Get, -{ - fn partial_cmp(&self, other: &WeakBoundedVec) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl PartialOrd> for WeakBoundedVec -where - T: PartialOrd, - BoundSelf: Get, - BoundRhs: Get, -{ - fn partial_cmp(&self, other: &BoundedVec) -> Option { - self.0.partial_cmp(&other.0) - } -} - -impl<'a, T, BoundSelf, BoundRhs> PartialOrd> - for WeakBoundedVec -where - T: PartialOrd, - BoundSelf: Get, - BoundRhs: Get, -{ - fn partial_cmp(&self, other: &BoundedSlice<'a, T, BoundRhs>) -> Option { - (&*self.0).partial_cmp(other.0) - } -} - -impl> Ord for WeakBoundedVec { - fn cmp(&self, other: &Self) -> sp_std::cmp::Ordering { - self.0.cmp(&other.0) - } -} - -impl MaxEncodedLen for WeakBoundedVec -where - T: MaxEncodedLen, - S: Get, - WeakBoundedVec: Encode, -{ - fn max_encoded_len() -> usize { - // WeakBoundedVec encodes like Vec which encodes like [T], which is a compact u32 - // plus each item in the slice: - // See: https://docs.substrate.io/reference/scale-codec/ - codec::Compact(S::get()) - .encoded_size() - .saturating_add(Self::bound().saturating_mul(T::max_encoded_len())) - } -} - -#[cfg(test)] -pub mod test { - use super::*; - use crate::ConstU32; - - #[test] - fn bound_returns_correct_value() { - assert_eq!(WeakBoundedVec::>::bound(), 7); - } - - #[test] - fn try_insert_works() { - let mut bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); - bounded.try_insert(1, 0).unwrap(); - assert_eq!(*bounded, vec![1, 0, 2, 3]); - - assert!(bounded.try_insert(0, 9).is_err()); - assert_eq!(*bounded, vec![1, 0, 2, 3]); - } - - #[test] - #[should_panic(expected = "insertion index (is 9) should be <= len (is 3)")] - fn try_inert_panics_if_oob() { - let mut bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); - bounded.try_insert(9, 0).unwrap(); - } - - #[test] - fn try_push_works() { - let mut bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); - bounded.try_push(0).unwrap(); - assert_eq!(*bounded, vec![1, 2, 3, 0]); - - assert!(bounded.try_push(9).is_err()); - } - - #[test] - fn deref_coercion_works() { - let bounded: WeakBoundedVec> = vec![1, 2, 3].try_into().unwrap(); - // these methods come from deref-ed vec. - assert_eq!(bounded.len(), 3); - assert!(bounded.iter().next().is_some()); - assert!(!bounded.is_empty()); - } - - #[test] - fn try_mutate_works() { - let bounded: WeakBoundedVec> = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); - let bounded = bounded.try_mutate(|v| v.push(7)).unwrap(); - assert_eq!(bounded.len(), 7); - assert!(bounded.try_mutate(|v| v.push(8)).is_none()); - } - - #[test] - fn slice_indexing_works() { - let bounded: WeakBoundedVec> = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); - assert_eq!(&bounded[0..=2], &[1, 2, 3]); - } - - #[test] - fn vec_eq_works() { - let bounded: WeakBoundedVec> = vec![1, 2, 3, 4, 5, 6].try_into().unwrap(); - assert_eq!(bounded, vec![1, 2, 3, 4, 5, 6]); - } - - #[test] - fn too_big_succeed_to_decode() { - let v: Vec = vec![1, 2, 3, 4, 5]; - let w = WeakBoundedVec::>::decode(&mut &v.encode()[..]).unwrap(); - assert_eq!(v, *w); - } -} diff --git a/primitives/core/src/crypto.rs b/primitives/core/src/crypto.rs index 52c805bb6119..72f996b33683 100644 --- a/primitives/core/src/crypto.rs +++ b/primitives/core/src/crypto.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,32 +19,30 @@ //! Cryptographic utilities. // end::description[] -#[cfg(feature = "std")] -use crate::hexdisplay::HexDisplay; use crate::{ed25519, sr25519}; #[cfg(feature = "std")] -use base58::{FromBase58, ToBase58}; +use bip39::{Language, Mnemonic, MnemonicType}; use codec::{Decode, Encode, MaxEncodedLen}; #[cfg(feature = "std")] use rand::{rngs::OsRng, RngCore}; #[cfg(feature = "std")] use regex::Regex; use scale_info::TypeInfo; -/// Trait for accessing reference to `SecretString`. -pub use secrecy::ExposeSecret; -/// A store for sensitive data. #[cfg(feature = "std")] -pub use secrecy::SecretString; +pub use secrecy::{ExposeSecret, SecretString}; use sp_runtime_interface::pass_by::PassByInner; #[doc(hidden)] pub use sp_std::ops::Deref; +#[cfg(all(not(feature = "std"), feature = "serde"))] +use sp_std::{ + alloc::{format, string::String}, + vec, +}; use sp_std::{hash::Hash, str, vec::Vec}; +pub use ss58_registry::{from_known_address_format, Ss58AddressFormat, Ss58AddressFormatRegistry}; /// Trait to zeroize a memory buffer. pub use zeroize::Zeroize; -#[cfg(feature = "full_crypto")] -pub use ss58_registry::{from_known_address_format, Ss58AddressFormat, Ss58AddressFormatRegistry}; - /// The root phrase for our publicly known keys. pub const DEV_PHRASE: &str = "bottom drive obey lake curtain smoke basket hold race lonely fit walk"; @@ -52,13 +50,8 @@ pub const DEV_PHRASE: &str = /// The address of the associated root phrase for our publicly known keys. pub const DEV_ADDRESS: &str = "5DfhGyQdFobKM8NsWvEeAKk5EQQgYe9AydgJ7rMB6E1EqRzV"; -/// The infallible type. -#[derive(crate::RuntimeDebug)] -pub enum Infallible {} - /// The length of the junction identifier. Note that this is also referred to as the /// `CHAIN_CODE_LENGTH` in the context of Schnorrkel. -#[cfg(feature = "full_crypto")] pub const JUNCTION_ID_LEN: usize = 32; /// Similar to `From`, except that the onus is on the part of the caller to ensure @@ -108,11 +101,21 @@ pub enum SecretStringError { InvalidPath, } +/// An error when deriving a key. +#[cfg_attr(feature = "std", derive(thiserror::Error))] +#[derive(Debug, Clone, PartialEq, Eq)] +#[cfg(feature = "full_crypto")] +pub enum DeriveError { + /// A soft key was found in the path (and is unsupported). + #[cfg_attr(feature = "std", error("Soft key in path"))] + SoftKeyInPath, +} + /// A since derivation junction description. It is the single parameter used when creating /// a new secret key from an existing secret key and, in the case of `SoftRaw` and `SoftIndex` /// a new public key from an existing public key. #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, Encode, Decode)] -#[cfg(feature = "full_crypto")] +#[cfg(any(feature = "full_crypto", feature = "serde"))] pub enum DeriveJunction { /// Soft (vanilla) derivation. Public keys have a correspondent derivation. Soft([u8; JUNCTION_ID_LEN]), @@ -120,7 +123,7 @@ pub enum DeriveJunction { Hard([u8; JUNCTION_ID_LEN]), } -#[cfg(feature = "full_crypto")] +#[cfg(any(feature = "full_crypto", feature = "serde"))] impl DeriveJunction { /// Consume self to return a soft derive junction with the same chain code. pub fn soften(self) -> Self { @@ -179,7 +182,7 @@ impl DeriveJunction { } } -#[cfg(feature = "full_crypto")] +#[cfg(any(feature = "full_crypto", feature = "serde"))] impl> From for DeriveJunction { fn from(j: T) -> DeriveJunction { let j = j.as_ref(); @@ -207,7 +210,7 @@ impl> From for DeriveJunction { #[cfg_attr(not(feature = "std"), derive(Debug))] #[derive(Clone, Copy, Eq, PartialEq)] #[allow(missing_docs)] -#[cfg(feature = "full_crypto")] +#[cfg(any(feature = "full_crypto", feature = "serde"))] pub enum PublicError { #[cfg_attr(feature = "std", error("Base 58 requirement is violated"))] BadBase58, @@ -246,7 +249,6 @@ impl sp_std::fmt::Debug for PublicError { /// /// See /// for information on the codec. -#[cfg(feature = "full_crypto")] pub trait Ss58Codec: Sized + AsMut<[u8]> + AsRef<[u8]> + ByteArray { /// A format filterer, can be used to ensure that `from_ss58check` family only decode for /// allowed identifiers. By default just refuses the two reserved identifiers. @@ -255,7 +257,7 @@ pub trait Ss58Codec: Sized + AsMut<[u8]> + AsRef<[u8]> + ByteArray { } /// Some if the string is a properly encoded SS58Check address. - #[cfg(feature = "std")] + #[cfg(feature = "serde")] fn from_ss58check(s: &str) -> Result { Self::from_ss58check_with_version(s).and_then(|(r, v)| match v { v if !v.is_custom() => Ok(r), @@ -265,12 +267,12 @@ pub trait Ss58Codec: Sized + AsMut<[u8]> + AsRef<[u8]> + ByteArray { } /// Some if the string is a properly encoded SS58Check address. - #[cfg(feature = "std")] + #[cfg(feature = "serde")] fn from_ss58check_with_version(s: &str) -> Result<(Self, Ss58AddressFormat), PublicError> { const CHECKSUM_LEN: usize = 2; let body_len = Self::LEN; - let data = s.from_base58().map_err(|_| PublicError::BadBase58)?; + let data = bs58::decode(s).into_vec().map_err(|_| PublicError::BadBase58)?; if data.len() < 2 { return Err(PublicError::BadLength) } @@ -320,7 +322,7 @@ pub trait Ss58Codec: Sized + AsMut<[u8]> + AsRef<[u8]> + ByteArray { } /// Return the ss58-check string for this key. - #[cfg(feature = "std")] + #[cfg(feature = "serde")] fn to_ss58check_with_version(&self, version: Ss58AddressFormat) -> String { // We mask out the upper two bits of the ident - SS58 Prefix currently only supports 14-bits let ident: u16 = u16::from(version) & 0b0011_1111_1111_1111; @@ -339,11 +341,11 @@ pub trait Ss58Codec: Sized + AsMut<[u8]> + AsRef<[u8]> + ByteArray { v.extend(self.as_ref()); let r = ss58hash(&v); v.extend(&r[0..2]); - v.to_base58() + bs58::encode(v).into_string() } /// Return the ss58-check string for this key. - #[cfg(feature = "std")] + #[cfg(feature = "serde")] fn to_ss58check(&self) -> String { self.to_ss58check_with_version(default_ss58_version()) } @@ -361,16 +363,16 @@ pub trait Derive: Sized { /// Derive a child key from a series of given junctions. /// /// Will be `None` for public keys if there are any hard junctions in there. - #[cfg(feature = "std")] + #[cfg(feature = "serde")] fn derive>(&self, _path: Iter) -> Option { None } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] const PREFIX: &[u8] = b"SS58PRE"; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] fn ss58hash(data: &[u8]) -> Vec { use blake2::{Blake2b512, Digest}; @@ -381,19 +383,19 @@ fn ss58hash(data: &[u8]) -> Vec { } /// Default prefix number -#[cfg(feature = "std")] -static DEFAULT_VERSION: core::sync::atomic::AtomicU16 = std::sync::atomic::AtomicU16::new( +#[cfg(feature = "serde")] +static DEFAULT_VERSION: core::sync::atomic::AtomicU16 = core::sync::atomic::AtomicU16::new( from_known_address_format(Ss58AddressFormatRegistry::SubstrateAccount), ); /// Returns default SS58 format used by the current active process. -#[cfg(feature = "std")] +#[cfg(feature = "serde")] pub fn default_ss58_version() -> Ss58AddressFormat { - DEFAULT_VERSION.load(std::sync::atomic::Ordering::Relaxed).into() + DEFAULT_VERSION.load(core::sync::atomic::Ordering::Relaxed).into() } /// Returns either the input address format or the default. -#[cfg(feature = "std")] +#[cfg(feature = "serde")] pub fn unwrap_or_default_ss58_version(network: Option) -> Ss58AddressFormat { network.unwrap_or_else(default_ss58_version) } @@ -407,9 +409,9 @@ pub fn unwrap_or_default_ss58_version(network: Option) -> Ss5 /// This will enable the node to decode ss58 addresses with this prefix. /// /// This SS58 version/format is also only used by the node and not by the runtime. -#[cfg(feature = "std")] +#[cfg(feature = "serde")] pub fn set_default_ss58_version(new_default: Ss58AddressFormat) { - DEFAULT_VERSION.store(new_default.into(), std::sync::atomic::Ordering::Relaxed); + DEFAULT_VERSION.store(new_default.into(), core::sync::atomic::Ordering::Relaxed); } #[cfg(feature = "std")] @@ -457,6 +459,11 @@ impl + AsRef<[u8]> + Public + Derive> Ss58Codec for T { } } +// Use the default implementations of the trait in serde feature. +// The std implementation is not available because of std only crate Regex. +#[cfg(all(not(feature = "std"), feature = "serde"))] +impl + AsRef<[u8]> + Public + Derive> Ss58Codec for T {} + /// Trait used for types that are really just a fixed-length array. pub trait ByteArray: AsRef<[u8]> + AsMut<[u8]> + for<'a> TryFrom<&'a [u8], Error = ()> { /// The "length" of the values of this type, which is always the same. @@ -478,11 +485,8 @@ pub trait ByteArray: AsRef<[u8]> + AsMut<[u8]> + for<'a> TryFrom<&'a [u8], Error } } -/// Trait suitable for typical cryptographic PKI key public type. -pub trait Public: ByteArray + Derive + CryptoType + PartialEq + Eq + Clone + Send + Sync { - /// Return `CryptoTypePublicPair` from public key. - fn to_public_crypto_pair(&self) -> CryptoTypePublicPair; -} +/// Trait suitable for typical cryptographic key public type. +pub trait Public: ByteArray + Derive + CryptoType + PartialEq + Eq + Clone + Send + Sync {} /// An opaque 32-byte cryptographic identifier. #[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, TypeInfo)] @@ -509,7 +513,7 @@ impl ByteArray for AccountId32 { const LEN: usize = 32; } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl Ss58Codec for AccountId32 {} impl AsRef<[u8]> for AccountId32 { @@ -593,7 +597,7 @@ impl sp_std::fmt::Debug for AccountId32 { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl serde::Serialize for AccountId32 { fn serialize(&self, serializer: S) -> Result where @@ -603,7 +607,7 @@ impl serde::Serialize for AccountId32 { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for AccountId32 { fn deserialize(deserializer: D) -> Result where @@ -681,50 +685,47 @@ mod dummy { b"" } } - impl Public for Dummy { - fn to_public_crypto_pair(&self) -> CryptoTypePublicPair { - CryptoTypePublicPair(CryptoTypeId(*b"dumm"), ::to_raw_vec(self)) - } - } + impl Public for Dummy {} impl Pair for Dummy { type Public = Dummy; type Seed = Dummy; type Signature = Dummy; - type DeriveError = (); + #[cfg(feature = "std")] fn generate_with_phrase(_: Option<&str>) -> (Self, String, Self::Seed) { Default::default() } + #[cfg(feature = "std")] fn from_phrase(_: &str, _: Option<&str>) -> Result<(Self, Self::Seed), SecretStringError> { Ok(Default::default()) } + fn derive>( &self, _: Iter, _: Option, - ) -> Result<(Self, Option), Self::DeriveError> { + ) -> Result<(Self, Option), DeriveError> { Ok((Self, None)) } - fn from_seed(_: &Self::Seed) -> Self { - Self - } + fn from_seed_slice(_: &[u8]) -> Result { Ok(Self) } + fn sign(&self, _: &[u8]) -> Self::Signature { Self } + fn verify>(_: &Self::Signature, _: M, _: &Self::Public) -> bool { true } - fn verify_weak, M: AsRef<[u8]>>(_: &[u8], _: M, _: P) -> bool { - true - } + fn public(&self) -> Self::Public { Self } + fn to_raw_vec(&self) -> Vec { vec![] } @@ -845,9 +846,6 @@ pub trait Pair: CryptoType + Sized + Clone + Send + Sync + 'static { /// and verified with the message and a public key. type Signature: AsRef<[u8]>; - /// Error returned from the `derive` function. - type DeriveError; - /// Generate new secure (random) key pair. /// /// This is only for ephemeral keys really, since you won't have access to the secret key @@ -866,30 +864,50 @@ pub trait Pair: CryptoType + Sized + Clone + Send + Sync + 'static { /// This is generally slower than `generate()`, so prefer that unless you need to persist /// the key from the current session. #[cfg(feature = "std")] - fn generate_with_phrase(password: Option<&str>) -> (Self, String, Self::Seed); + fn generate_with_phrase(password: Option<&str>) -> (Self, String, Self::Seed) { + let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English); + let phrase = mnemonic.phrase(); + let (pair, seed) = Self::from_phrase(phrase, password) + .expect("All phrases generated by Mnemonic are valid; qed"); + (pair, phrase.to_owned(), seed) + } - /// Returns the KeyPair from the English BIP39 seed `phrase`, or `None` if it's invalid. + /// Returns the KeyPair from the English BIP39 seed `phrase`, or an error if it's invalid. #[cfg(feature = "std")] fn from_phrase( phrase: &str, password: Option<&str>, - ) -> Result<(Self, Self::Seed), SecretStringError>; + ) -> Result<(Self, Self::Seed), SecretStringError> { + let mnemonic = Mnemonic::from_phrase(phrase, Language::English) + .map_err(|_| SecretStringError::InvalidPhrase)?; + let big_seed = + substrate_bip39::seed_from_entropy(mnemonic.entropy(), password.unwrap_or("")) + .map_err(|_| SecretStringError::InvalidSeed)?; + let mut seed = Self::Seed::default(); + let seed_slice = seed.as_mut(); + let seed_len = seed_slice.len(); + debug_assert!(seed_len <= big_seed.len()); + seed_slice[..seed_len].copy_from_slice(&big_seed[..seed_len]); + Self::from_seed_slice(seed_slice).map(|x| (x, seed)) + } /// Derive a child key from a series of given junctions. fn derive>( &self, path: Iter, seed: Option, - ) -> Result<(Self, Option), Self::DeriveError>; + ) -> Result<(Self, Option), DeriveError>; /// Generate new key pair from the provided `seed`. /// /// @WARNING: THIS WILL ONLY BE SECURE IF THE `seed` IS SECURE. If it can be guessed /// by an attacker then they can also derive your key. - fn from_seed(seed: &Self::Seed) -> Self; + fn from_seed(seed: &Self::Seed) -> Self { + Self::from_seed_slice(seed.as_ref()).expect("seed has valid length; qed") + } /// Make a new key pair from secret seed material. The slice must be the correct size or - /// it will return `None`. + /// an error will be returned. /// /// @WARNING: THIS WILL ONLY BE SECURE IF THE `seed` IS SECURE. If it can be guessed /// by an attacker then they can also derive your key. @@ -901,9 +919,6 @@ pub trait Pair: CryptoType + Sized + Clone + Send + Sync + 'static { /// Verify a signature on a message. Returns true if the signature is good. fn verify>(sig: &Self::Signature, message: M, pubkey: &Self::Public) -> bool; - /// Verify a signature on a message. Returns true if the signature is good. - fn verify_weak, M: AsRef<[u8]>>(sig: &[u8], message: M, pubkey: P) -> bool; - /// Get the public key. fn public(&self) -> Self::Public; @@ -934,8 +949,6 @@ pub trait Pair: CryptoType + Sized + Clone + Send + Sync + 'static { /// Notably, integer junction indices may be legally prefixed with arbitrary number of zeros. /// Similarly an empty password (ending the SURI with `///`) is perfectly valid and will /// generally be equivalent to no password at all. - /// - /// `None` is returned if no matches are found. #[cfg(feature = "std")] fn from_string_with_seed( s: &str, @@ -1054,7 +1067,7 @@ pub trait CryptoType { crate::RuntimeDebug, TypeInfo, )] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct KeyTypeId(pub [u8; 4]); impl From for KeyTypeId { @@ -1083,29 +1096,38 @@ impl<'a> TryFrom<&'a str> for KeyTypeId { } } -/// An identifier for a specific cryptographic algorithm used by a key pair -#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct CryptoTypeId(pub [u8; 4]); +/// Trait grouping types shared by a VRF signer and verifiers. +pub trait VrfCrypto { + /// VRF input. + type VrfInput; + /// VRF output. + type VrfOutput; + /// VRF signing data. + type VrfSignData; + /// VRF signature. + type VrfSignature; +} -/// A type alias of CryptoTypeId & a public key -#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub struct CryptoTypePublicPair(pub CryptoTypeId, pub Vec); +/// VRF Secret Key. +pub trait VrfSecret: VrfCrypto { + /// Get VRF-specific output . + fn vrf_output(&self, data: &Self::VrfInput) -> Self::VrfOutput; -#[cfg(feature = "std")] -impl sp_std::fmt::Display for CryptoTypePublicPair { - fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { - let id = match str::from_utf8(&(self.0).0[..]) { - Ok(id) => id.to_string(), - Err(_) => { - format!("{:#?}", self.0) - }, - }; - write!(f, "{}-{}", id, HexDisplay::from(&self.1)) - } + /// Sign VRF-specific data. + fn vrf_sign(&self, input: &Self::VrfSignData) -> Self::VrfSignature; +} + +/// VRF Public Key. +pub trait VrfPublic: VrfCrypto { + /// Verify input data signature. + fn vrf_verify(&self, data: &Self::VrfSignData, signature: &Self::VrfSignature) -> bool; } +/// An identifier for a specific cryptographic algorithm used by a key pair +#[derive(Debug, Copy, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash, Encode, Decode)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct CryptoTypeId(pub [u8; 4]); + /// Known key types; this also functions as a global registry of key types for projects wishing to /// avoid collisions with each other. /// @@ -1128,10 +1150,58 @@ pub mod key_types { pub const AUTHORITY_DISCOVERY: KeyTypeId = KeyTypeId(*b"audi"); /// Key type for staking, built-in. Identified as `stak`. pub const STAKING: KeyTypeId = KeyTypeId(*b"stak"); + /// A key type for signing statements + pub const STATEMENT: KeyTypeId = KeyTypeId(*b"stmt"); /// A key type ID useful for tests. pub const DUMMY: KeyTypeId = KeyTypeId(*b"dumy"); } +/// Create random values of `Self` given a stream of entropy. +pub trait FromEntropy: Sized { + /// Create a random value of `Self` given a stream of random bytes on `input`. May only fail if + /// `input` has an error. + fn from_entropy(input: &mut impl codec::Input) -> Result; +} + +impl FromEntropy for bool { + fn from_entropy(input: &mut impl codec::Input) -> Result { + Ok(input.read_byte()? % 2 == 1) + } +} + +macro_rules! impl_from_entropy { + ($type:ty , $( $others:tt )*) => { + impl_from_entropy!($type); + impl_from_entropy!($( $others )*); + }; + ($type:ty) => { + impl FromEntropy for $type { + fn from_entropy(input: &mut impl codec::Input) -> Result { + ::decode(input) + } + } + } +} + +macro_rules! impl_from_entropy_base { + ($type:ty , $( $others:tt )*) => { + impl_from_entropy_base!($type); + impl_from_entropy_base!($( $others )*); + }; + ($type:ty) => { + impl_from_entropy!($type, + [$type; 1], [$type; 2], [$type; 3], [$type; 4], [$type; 5], [$type; 6], [$type; 7], [$type; 8], + [$type; 9], [$type; 10], [$type; 11], [$type; 12], [$type; 13], [$type; 14], [$type; 15], [$type; 16], + [$type; 17], [$type; 18], [$type; 19], [$type; 20], [$type; 21], [$type; 22], [$type; 23], [$type; 24], + [$type; 25], [$type; 26], [$type; 27], [$type; 28], [$type; 29], [$type; 30], [$type; 31], [$type; 32], + [$type; 36], [$type; 40], [$type; 44], [$type; 48], [$type; 56], [$type; 64], [$type; 72], [$type; 80], + [$type; 96], [$type; 112], [$type; 128], [$type; 160], [$type; 192], [$type; 224], [$type; 256] + ); + } +} + +impl_from_entropy_base!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); + #[cfg(test)] mod tests { use super::*; @@ -1193,23 +1263,20 @@ mod tests { vec![] } } - impl Public for TestPublic { - fn to_public_crypto_pair(&self) -> CryptoTypePublicPair { - CryptoTypePublicPair(CryptoTypeId(*b"dumm"), self.to_raw_vec()) - } - } + impl Public for TestPublic {} impl Pair for TestPair { type Public = TestPublic; type Seed = [u8; 8]; type Signature = [u8; 0]; - type DeriveError = (); fn generate() -> (Self, ::Seed) { (TestPair::Generated, [0u8; 8]) } + fn generate_with_phrase(_password: Option<&str>) -> (Self, String, ::Seed) { (TestPair::GeneratedWithPhrase, "".into(), [0u8; 8]) } + fn from_phrase( phrase: &str, password: Option<&str>, @@ -1222,11 +1289,12 @@ mod tests { [0u8; 8], )) } + fn derive>( &self, path_iter: Iter, _: Option<[u8; 8]>, - ) -> Result<(Self, Option<[u8; 8]>), Self::DeriveError> { + ) -> Result<(Self, Option<[u8; 8]>), DeriveError> { Ok(( match self.clone() { TestPair::Standard { phrase, password, path } => TestPair::Standard { @@ -1240,34 +1308,29 @@ mod tests { if path_iter.count() == 0 { x } else { - return Err(()) + return Err(DeriveError::SoftKeyInPath) }, }, None, )) } - fn from_seed(_seed: &::Seed) -> Self { - TestPair::Seed(_seed.as_ref().to_owned()) - } + fn sign(&self, _message: &[u8]) -> Self::Signature { [] } + fn verify>(_: &Self::Signature, _: M, _: &Self::Public) -> bool { true } - fn verify_weak, M: AsRef<[u8]>>( - _sig: &[u8], - _message: M, - _pubkey: P, - ) -> bool { - true - } + fn public(&self) -> Self::Public { TestPublic } + fn from_seed_slice(seed: &[u8]) -> Result { Ok(TestPair::Seed(seed.to_owned())) } + fn to_raw_vec(&self) -> Vec { vec![] } diff --git a/primitives/core/src/defer.rs b/primitives/core/src/defer.rs index d14b26d59e4d..efa9ee5cebb7 100644 --- a/primitives/core/src/defer.rs +++ b/primitives/core/src/defer.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +25,13 @@ #[must_use] pub struct DeferGuard(pub Option); +impl DeferGuard { + /// Creates a new `DeferGuard` with the given closure. + pub fn new(f: F) -> Self { + Self(Some(f)) + } +} + impl Drop for DeferGuard { fn drop(&mut self) { self.0.take().map(|f| f()); diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index ca6b800625bc..143482e94cd1 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,19 +21,16 @@ use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime_interface::pass_by::PassByInner; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use crate::crypto::Ss58Codec; use crate::crypto::{ - ByteArray, CryptoType, CryptoTypeId, CryptoTypePublicPair, Derive, Public as TraitPublic, - UncheckedFrom, + ByteArray, CryptoType, CryptoTypeId, Derive, Public as TraitPublic, UncheckedFrom, }; #[cfg(feature = "full_crypto")] use crate::{ - crypto::{DeriveJunction, Pair as TraitPair, SecretStringError}, + crypto::{DeriveError, DeriveJunction, Pair as TraitPair, SecretStringError}, hashing::blake2_256, }; -#[cfg(feature = "std")] -use bip39::{Language, Mnemonic, MnemonicType}; #[cfg(all(feature = "full_crypto", not(feature = "std")))] use secp256k1::Secp256k1; #[cfg(feature = "std")] @@ -43,8 +40,10 @@ use secp256k1::{ ecdsa::{RecoverableSignature, RecoveryId}, Message, PublicKey, SecretKey, }; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; +#[cfg(all(not(feature = "std"), feature = "serde"))] +use sp_std::alloc::{format, string::String}; #[cfg(feature = "full_crypto")] use sp_std::vec::Vec; @@ -74,6 +73,14 @@ type Seed = [u8; 32]; )] pub struct Public(pub [u8; 33]); +impl crate::crypto::FromEntropy for Public { + fn from_entropy(input: &mut impl codec::Input) -> Result { + let mut result = Self([0u8; 33]); + input.read(&mut result.0[..])?; + Ok(result) + } +} + impl Public { /// A new instance from the given 33-byte `data`. /// @@ -105,23 +112,7 @@ impl ByteArray for Public { const LEN: usize = 33; } -impl TraitPublic for Public { - fn to_public_crypto_pair(&self) -> CryptoTypePublicPair { - CryptoTypePublicPair(CRYPTO_ID, self.to_raw_vec()) - } -} - -impl From for CryptoTypePublicPair { - fn from(key: Public) -> Self { - (&key).into() - } -} - -impl From<&Public> for CryptoTypePublicPair { - fn from(key: &Public) -> Self { - CryptoTypePublicPair(CRYPTO_ID, key.to_raw_vec()) - } -} +impl TraitPublic for Public {} impl Derive for Public {} @@ -183,7 +174,7 @@ impl sp_std::fmt::Debug for Public { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl Serialize for Public { fn serialize(&self, serializer: S) -> Result where @@ -193,7 +184,7 @@ impl Serialize for Public { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl<'de> Deserialize<'de> for Public { fn deserialize(deserializer: D) -> Result where @@ -223,17 +214,17 @@ impl TryFrom<&[u8]> for Signature { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl Serialize for Signature { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - serializer.serialize_str(&array_bytes::bytes2hex("", self.as_ref())) + serializer.serialize_str(&array_bytes::bytes2hex("", self)) } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl<'de> Deserialize<'de> for Signature { fn deserialize(deserializer: D) -> Result where @@ -367,13 +358,6 @@ fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed { ("Secp256k1HDKD", secret_seed, cc).using_encoded(sp_core_hashing::blake2_256) } -/// An error when deriving a key. -#[cfg(feature = "full_crypto")] -pub enum DeriveError { - /// A soft key was found in the path (and is unsupported). - SoftKeyInPath, -} - /// A key pair. #[cfg(feature = "full_crypto")] #[derive(Clone)] @@ -387,44 +371,6 @@ impl TraitPair for Pair { type Public = Public; type Seed = Seed; type Signature = Signature; - type DeriveError = DeriveError; - - /// Generate new secure (random) key pair and provide the recovery phrase. - /// - /// You can recover the same key later with `from_phrase`. - #[cfg(feature = "std")] - fn generate_with_phrase(password: Option<&str>) -> (Pair, String, Seed) { - let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English); - let phrase = mnemonic.phrase(); - let (pair, seed) = Self::from_phrase(phrase, password) - .expect("All phrases generated by Mnemonic are valid; qed"); - (pair, phrase.to_owned(), seed) - } - - /// Generate key pair from given recovery phrase and password. - #[cfg(feature = "std")] - fn from_phrase( - phrase: &str, - password: Option<&str>, - ) -> Result<(Pair, Seed), SecretStringError> { - let big_seed = substrate_bip39::seed_from_entropy( - Mnemonic::from_phrase(phrase, Language::English) - .map_err(|_| SecretStringError::InvalidPhrase)? - .entropy(), - password.unwrap_or(""), - ) - .map_err(|_| SecretStringError::InvalidSeed)?; - let mut seed = Seed::default(); - seed.copy_from_slice(&big_seed[0..32]); - Self::from_seed_slice(&big_seed[0..32]).map(|x| (x, seed)) - } - - /// Make a new key pair from secret seed material. - /// - /// You should never need to use this; generate(), generate_with_phrase - fn from_seed(seed: &Seed) -> Pair { - Self::from_seed_slice(&seed[..]).expect("seed has valid length; qed") - } /// Make a new key pair from secret seed material. The slice must be 32 bytes long or it /// will return `None`. @@ -471,22 +417,8 @@ impl TraitPair for Pair { } /// Verify a signature on a message. Returns true if the signature is good. - fn verify>(sig: &Self::Signature, message: M, pubkey: &Self::Public) -> bool { - match sig.recover(message) { - Some(actual) => actual == *pubkey, - None => false, - } - } - - /// Verify a signature on a message. Returns true if the signature is good. - /// - /// This doesn't use the type system to ensure that `sig` and `pubkey` are the correct - /// size. Use it only if you're coming from byte buffers and need the speed. - fn verify_weak, M: AsRef<[u8]>>(sig: &[u8], message: M, pubkey: P) -> bool { - match Signature::from_slice(sig).and_then(|sig| sig.recover(message)) { - Some(actual) => actual.as_ref() == pubkey.as_ref(), - None => false, - } + fn verify>(sig: &Self::Signature, message: M, public: &Self::Public) -> bool { + sig.recover(message).map(|actual| actual == *public).unwrap_or_default() } /// Return a vec filled with raw data. @@ -620,7 +552,7 @@ mod test { let derived = pair.derive(path.into_iter(), None).ok().unwrap(); assert_eq!( derived.0.seed(), - array_bytes::hex2array_unchecked::<32>( + array_bytes::hex2array_unchecked::<_, 32>( "b8eefc4937200a8382d00050e050ced2d4ab72cc2ef1b061477afb51564fdd61" ) ); diff --git a/primitives/core/src/ed25519.rs b/primitives/core/src/ed25519.rs index e85eb87c9fd8..d46f2b5043d6 100644 --- a/primitives/core/src/ed25519.rs +++ b/primitives/core/src/ed25519.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,25 +29,23 @@ use crate::{ use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use crate::crypto::Ss58Codec; use crate::crypto::{ - CryptoType, CryptoTypeId, CryptoTypePublicPair, Derive, Public as TraitPublic, UncheckedFrom, + CryptoType, CryptoTypeId, Derive, FromEntropy, Public as TraitPublic, UncheckedFrom, }; #[cfg(feature = "full_crypto")] -use crate::crypto::{DeriveJunction, Pair as TraitPair, SecretStringError}; -#[cfg(feature = "std")] -use bip39::{Language, Mnemonic, MnemonicType}; +use crate::crypto::{DeriveError, DeriveJunction, Pair as TraitPair, SecretStringError}; #[cfg(feature = "full_crypto")] use core::convert::TryFrom; #[cfg(feature = "full_crypto")] use ed25519_zebra::{SigningKey, VerificationKey}; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use sp_runtime_interface::pass_by::PassByInner; +#[cfg(all(not(feature = "std"), feature = "serde"))] +use sp_std::alloc::{format, string::String}; use sp_std::ops::Deref; -#[cfg(feature = "std")] -use substrate_bip39::seed_from_entropy; /// An identifier used to match public keys against ed25519 keys pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ed25"); @@ -83,6 +81,14 @@ pub struct Pair { secret: SigningKey, } +impl FromEntropy for Public { + fn from_entropy(input: &mut impl codec::Input) -> Result { + let mut result = Self([0u8; 32]); + input.read(&mut result.0[..])?; + Ok(result) + } +} + impl AsRef<[u8; 32]> for Public { fn as_ref(&self) -> &[u8; 32] { &self.0 @@ -182,7 +188,7 @@ impl sp_std::fmt::Debug for Public { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl Serialize for Public { fn serialize(&self, serializer: S) -> Result where @@ -192,7 +198,7 @@ impl Serialize for Public { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl<'de> Deserialize<'de> for Public { fn deserialize(deserializer: D) -> Result where @@ -222,17 +228,17 @@ impl TryFrom<&[u8]> for Signature { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl Serialize for Signature { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - serializer.serialize_str(&array_bytes::bytes2hex("", self.as_ref())) + serializer.serialize_str(&array_bytes::bytes2hex("", self)) } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl<'de> Deserialize<'de> for Signature { fn deserialize(deserializer: D) -> Result where @@ -332,16 +338,6 @@ impl Signature { } } -/// A localized signature also contains sender information. -#[cfg(feature = "std")] -#[derive(PartialEq, Eq, Clone, Debug, Encode, Decode)] -pub struct LocalizedSignature { - /// The signer of the signature. - pub signer: Public, - /// The signature itself. - pub signature: Signature, -} - impl Public { /// A new instance from the given 32-byte `data`. /// @@ -369,82 +365,21 @@ impl ByteArray for Public { const LEN: usize = 32; } -impl TraitPublic for Public { - fn to_public_crypto_pair(&self) -> CryptoTypePublicPair { - CryptoTypePublicPair(CRYPTO_ID, self.to_raw_vec()) - } -} +impl TraitPublic for Public {} impl Derive for Public {} -impl From for CryptoTypePublicPair { - fn from(key: Public) -> Self { - (&key).into() - } -} - -impl From<&Public> for CryptoTypePublicPair { - fn from(key: &Public) -> Self { - CryptoTypePublicPair(CRYPTO_ID, key.to_raw_vec()) - } -} - /// Derive a single hard junction. #[cfg(feature = "full_crypto")] fn derive_hard_junction(secret_seed: &Seed, cc: &[u8; 32]) -> Seed { ("Ed25519HDKD", secret_seed, cc).using_encoded(sp_core_hashing::blake2_256) } -/// An error when deriving a key. -#[cfg(feature = "full_crypto")] -pub enum DeriveError { - /// A soft key was found in the path (and is unsupported). - SoftKeyInPath, -} - #[cfg(feature = "full_crypto")] impl TraitPair for Pair { type Public = Public; type Seed = Seed; type Signature = Signature; - type DeriveError = DeriveError; - - /// Generate new secure (random) key pair and provide the recovery phrase. - /// - /// You can recover the same key later with `from_phrase`. - #[cfg(feature = "std")] - fn generate_with_phrase(password: Option<&str>) -> (Pair, String, Seed) { - let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English); - let phrase = mnemonic.phrase(); - let (pair, seed) = Self::from_phrase(phrase, password) - .expect("All phrases generated by Mnemonic are valid; qed"); - (pair, phrase.to_owned(), seed) - } - - /// Generate key pair from given recovery phrase and password. - #[cfg(feature = "std")] - fn from_phrase( - phrase: &str, - password: Option<&str>, - ) -> Result<(Pair, Seed), SecretStringError> { - let big_seed = seed_from_entropy( - Mnemonic::from_phrase(phrase, Language::English) - .map_err(|_| SecretStringError::InvalidPhrase)? - .entropy(), - password.unwrap_or(""), - ) - .map_err(|_| SecretStringError::InvalidSeed)?; - let mut seed = Seed::default(); - seed.copy_from_slice(&big_seed[0..32]); - Self::from_seed_slice(&big_seed[0..32]).map(|x| (x, seed)) - } - - /// Make a new key pair from secret seed material. - /// - /// You should never need to use this; generate(), generate_with_phrase - fn from_seed(seed: &Seed) -> Pair { - Self::from_seed_slice(&seed[..]).expect("seed has valid length; qed") - } /// Make a new key pair from secret seed material. The slice must be 32 bytes long or it /// will return `None`. @@ -483,27 +418,13 @@ impl TraitPair for Pair { Signature::from_raw(self.secret.sign(message).into()) } - /// Verify a signature on a message. Returns true if the signature is good. - fn verify>(sig: &Self::Signature, message: M, pubkey: &Self::Public) -> bool { - Self::verify_weak(&sig.0[..], message.as_ref(), pubkey) - } - - /// Verify a signature on a message. Returns true if the signature is good. + /// Verify a signature on a message. /// - /// This doesn't use the type system to ensure that `sig` and `pubkey` are the correct - /// size. Use it only if you're coming from byte buffers and need the speed. - fn verify_weak, M: AsRef<[u8]>>(sig: &[u8], message: M, pubkey: P) -> bool { - let public_key = match VerificationKey::try_from(pubkey.as_ref()) { - Ok(pk) => pk, - Err(_) => return false, - }; - - let sig = match ed25519_zebra::Signature::try_from(sig) { - Ok(s) => s, - Err(_) => return false, - }; - - public_key.verify(&sig, message.as_ref()).is_ok() + /// Returns true if the signature is good. + fn verify>(sig: &Self::Signature, message: M, public: &Self::Public) -> bool { + let Ok(public) = VerificationKey::try_from(public.as_slice()) else { return false }; + let Ok(signature) = ed25519_zebra::Signature::try_from(sig.as_ref()) else { return false }; + public.verify(&signature, message.as_ref()).is_ok() } /// Return a vec filled with raw data. @@ -574,7 +495,7 @@ mod test { let derived = pair.derive(path.into_iter(), None).ok().unwrap().0; assert_eq!( derived.seed(), - array_bytes::hex2array_unchecked::<32>( + array_bytes::hex2array_unchecked::<_, 32>( "ede3354e133f9c8e337ddd6ee5415ed4b4ffe5fc7d21e933f4930a3730e5b21c" ) ); diff --git a/primitives/core/src/hash.rs b/primitives/core/src/hash.rs index f2974e9372ad..ece9b1af794a 100644 --- a/primitives/core/src/hash.rs +++ b/primitives/core/src/hash.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,7 +32,6 @@ pub fn convert_hash, H2: AsRef<[u8]>>(src: &H2) -> H1 #[cfg(test)] mod tests { use super::*; - use sp_serializer as ser; #[test] fn test_h160() { @@ -47,8 +46,11 @@ mod tests { ]; for (number, expected) in tests { - assert_eq!(format!("{:?}", expected), ser::to_string_pretty(&number)); - assert_eq!(number, ser::from_str(&format!("{:?}", expected)).unwrap()); + assert_eq!( + format!("{:?}", expected), + serde_json::to_string_pretty(&number).expect("Json pretty print failed") + ); + assert_eq!(number, serde_json::from_str(&format!("{:?}", expected)).unwrap()); } } @@ -86,30 +88,33 @@ mod tests { ]; for (number, expected) in tests { - assert_eq!(format!("{:?}", expected), ser::to_string_pretty(&number)); - assert_eq!(number, ser::from_str(&format!("{:?}", expected)).unwrap()); + assert_eq!( + format!("{:?}", expected), + serde_json::to_string_pretty(&number).expect("Json pretty print failed") + ); + assert_eq!(number, serde_json::from_str(&format!("{:?}", expected)).unwrap()); } } #[test] fn test_invalid() { - assert!(ser::from_str::( + assert!(serde_json::from_str::( "\"0x000000000000000000000000000000000000000000000000000000000000000\"" ) .unwrap_err() .is_data()); - assert!(ser::from_str::( + assert!(serde_json::from_str::( "\"0x000000000000000000000000000000000000000000000000000000000000000g\"" ) .unwrap_err() .is_data()); - assert!(ser::from_str::( + assert!(serde_json::from_str::( "\"0x00000000000000000000000000000000000000000000000000000000000000000\"" ) .unwrap_err() .is_data()); - assert!(ser::from_str::("\"\"").unwrap_err().is_data()); - assert!(ser::from_str::("\"0\"").unwrap_err().is_data()); - assert!(ser::from_str::("\"10\"").unwrap_err().is_data()); + assert!(serde_json::from_str::("\"\"").unwrap_err().is_data()); + assert!(serde_json::from_str::("\"0\"").unwrap_err().is_data()); + assert!(serde_json::from_str::("\"10\"").unwrap_err().is_data()); } } diff --git a/primitives/core/src/hasher.rs b/primitives/core/src/hasher.rs index 173bd560ad1e..5c4c40cba8e2 100644 --- a/primitives/core/src/hasher.rs +++ b/primitives/core/src/hasher.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/core/src/hashing.rs b/primitives/core/src/hashing.rs index 1c439355a33c..e71d0b543380 100644 --- a/primitives/core/src/hashing.rs +++ b/primitives/core/src/hashing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/core/src/hexdisplay.rs b/primitives/core/src/hexdisplay.rs index 26c04c433b49..30e045dfc52a 100644 --- a/primitives/core/src/hexdisplay.rs +++ b/primitives/core/src/hexdisplay.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -96,7 +96,7 @@ macro_rules! impl_non_endians { impl_non_endians!( [u8; 1], [u8; 2], [u8; 3], [u8; 4], [u8; 5], [u8; 6], [u8; 7], [u8; 8], [u8; 10], [u8; 12], [u8; 14], [u8; 16], [u8; 20], [u8; 24], [u8; 28], [u8; 32], [u8; 40], [u8; 48], [u8; 56], - [u8; 64], [u8; 65], [u8; 80], [u8; 96], [u8; 112], [u8; 128] + [u8; 64], [u8; 65], [u8; 80], [u8; 96], [u8; 112], [u8; 128], [u8; 144] ); /// Format into ASCII + # + hex, suitable for storage key preimages. diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index 30d0cc199b74..da3b43819095 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,16 +34,16 @@ macro_rules! map { #[doc(hidden)] pub use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] pub use serde; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use sp_runtime_interface::pass_by::{PassByEnum, PassByInner}; use sp_std::{ops::Deref, prelude::*}; pub use sp_debug_derive::RuntimeDebug; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] pub use impl_serde::serialize as bytes; #[cfg(feature = "full_crypto")] @@ -51,10 +51,12 @@ pub mod hashing; #[cfg(feature = "full_crypto")] pub use hashing::{blake2_128, blake2_256, keccak_256, twox_128, twox_256, twox_64}; -pub mod bounded; pub mod crypto; pub mod hexdisplay; +pub use paste; +#[cfg(feature = "bls-experimental")] +pub mod bls; pub mod defer; pub mod ecdsa; pub mod ed25519; @@ -68,6 +70,9 @@ pub mod testing; pub mod traits; pub mod uint; +#[cfg(feature = "bls-experimental")] +pub use bls::{bls377, bls381}; + pub use self::{ hash::{convert_hash, H160, H256, H512}, uint::{U256, U512}, @@ -81,54 +86,22 @@ pub use self::hasher::blake2::Blake2Hasher; pub use self::hasher::keccak::KeccakHasher; pub use hash_db::Hasher; +pub use bounded_collections as bounded; +#[cfg(feature = "std")] +pub use bounded_collections::{bounded_btree_map, bounded_vec}; +pub use bounded_collections::{ + parameter_types, ConstBool, ConstI128, ConstI16, ConstI32, ConstI64, ConstI8, ConstU128, + ConstU16, ConstU32, ConstU64, ConstU8, Get, GetDefault, TryCollect, TypedGet, +}; pub use sp_storage as storage; #[doc(hidden)] pub use sp_std; -/// Context for executing a call into the runtime. -pub enum ExecutionContext { - /// Context used for general block import (including locally authored blocks). - Importing, - /// Context used for importing blocks as part of an initial sync of the blockchain. - /// - /// We distinguish between major sync and import so that validators who are running - /// their initial sync (or catching up after some time offline) can use the faster - /// native runtime (since we can reasonably assume the network as a whole has already - /// come to a broad consensus on the block and it probably hasn't been crafted - /// specifically to attack this node), but when importing blocks at the head of the - /// chain in normal operation they can use the safer Wasm version. - Syncing, - /// Context used for block construction. - BlockConstruction, - /// Context used for offchain calls. - /// - /// This allows passing offchain extension and customizing available capabilities. - OffchainCall(Option<(Box, offchain::Capabilities)>), -} - -impl ExecutionContext { - /// Returns the capabilities of particular context. - pub fn capabilities(&self) -> offchain::Capabilities { - use ExecutionContext::*; - - match self { - Importing | Syncing | BlockConstruction => offchain::Capabilities::empty(), - // Enable keystore, transaction pool and Offchain DB reads by default for offchain - // calls. - OffchainCall(None) => - offchain::Capabilities::KEYSTORE | - offchain::Capabilities::OFFCHAIN_DB_READ | - offchain::Capabilities::TRANSACTION_POOL, - OffchainCall(Some((_, capabilities))) => *capabilities, - } - } -} - /// Hex-serialized shim for `Vec`. #[derive(PartialEq, Eq, Clone, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Hash, PartialOrd, Ord))] -pub struct Bytes(#[cfg_attr(feature = "std", serde(with = "bytes"))] pub Vec); +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, Hash, PartialOrd, Ord))] +pub struct Bytes(#[cfg_attr(feature = "serde", serde(with = "bytes"))] pub Vec); impl From> for Bytes { fn from(s: Vec) -> Self { @@ -165,7 +138,7 @@ impl sp_std::str::FromStr for Bytes { } /// Stores the encoded `RuntimeMetadata` for the native side as opaque type. -#[derive(Encode, Decode, PartialEq)] +#[derive(Encode, Decode, PartialEq, TypeInfo)] pub struct OpaqueMetadata(Vec); impl OpaqueMetadata { @@ -197,7 +170,7 @@ impl sp_std::ops::Deref for OpaqueMetadata { PassByInner, TypeInfo, )] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct OpaquePeerId(pub Vec); impl OpaquePeerId { @@ -382,289 +355,128 @@ macro_rules! impl_maybe_marker { } } -/// The maximum number of bytes that can be allocated at one time. -// The maximum possible allocation size was chosen rather arbitrary, 32 MiB should be enough for -// everybody. -pub const MAX_POSSIBLE_ALLOCATION: u32 = 33554432; // 2^25 bytes, 32 MiB - -/// A trait for querying a single value from a type defined in the trait. -/// -/// It is not required that the value is constant. -pub trait TypedGet { - /// The type which is returned. - type Type; - /// Return the current value. - fn get() -> Self::Type; -} - -/// A trait for querying a single value from a type. -/// -/// It is not required that the value is constant. -pub trait Get { - /// Return the current value. - fn get() -> T; -} - -impl Get for () { - fn get() -> T { - T::default() - } -} - -/// Implement Get by returning Default for any type that implements Default. -pub struct GetDefault; -impl Get for GetDefault { - fn get() -> T { - T::default() - } -} - -macro_rules! impl_const_get { - ($name:ident, $t:ty) => { - #[doc = "Const getter for a basic type."] - #[derive($crate::RuntimeDebug)] - pub struct $name; - impl Get<$t> for $name { - fn get() -> $t { - T - } - } - impl Get> for $name { - fn get() -> Option<$t> { - Some(T) - } - } - impl TypedGet for $name { - type Type = $t; - fn get() -> $t { - T - } - } - }; -} - -impl_const_get!(ConstBool, bool); -impl_const_get!(ConstU8, u8); -impl_const_get!(ConstU16, u16); -impl_const_get!(ConstU32, u32); -impl_const_get!(ConstU64, u64); -impl_const_get!(ConstU128, u128); -impl_const_get!(ConstI8, i8); -impl_const_get!(ConstI16, i16); -impl_const_get!(ConstI32, i32); -impl_const_get!(ConstI64, i64); -impl_const_get!(ConstI128, i128); - -/// Try and collect into a collection `C`. -pub trait TryCollect { - /// The error type that gets returned when a collection can't be made from `self`. - type Error; - /// Consume self and try to collect the results into `C`. - /// - /// This is useful in preventing the undesirable `.collect().try_into()` call chain on - /// collections that need to be converted into a bounded type (e.g. `BoundedVec`). - fn try_collect(self) -> Result; -} - -/// Create new implementations of the [`Get`](crate::Get) trait. -/// -/// The so-called parameter type can be created in four different ways: -/// -/// - Using `const` to create a parameter type that provides a `const` getter. It is required that -/// the `value` is const. -/// -/// - Declare the parameter type without `const` to have more freedom when creating the value. -/// -/// NOTE: A more substantial version of this macro is available in `frame_support` crate which -/// allows mutable and persistant variants. -/// -/// # Examples -/// -/// ``` -/// # use sp_core::Get; -/// # use sp_core::parameter_types; -/// // This function cannot be used in a const context. -/// fn non_const_expression() -> u64 { 99 } +/// Macro for creating `Maybe*` marker traits. /// -/// const FIXED_VALUE: u64 = 10; -/// parameter_types! { -/// pub const Argument: u64 = 42 + FIXED_VALUE; -/// /// Visibility of the type is optional -/// OtherArgument: u64 = non_const_expression(); -/// } +/// Such a maybe-marker trait requires the given bound when either `feature = std` or `feature = +/// serde` is activated. /// -/// trait Config { -/// type Parameter: Get; -/// type OtherParameter: Get; -/// } +/// # Example /// -/// struct Runtime; -/// impl Config for Runtime { -/// type Parameter = Argument; -/// type OtherParameter = OtherArgument; -/// } /// ``` -/// -/// # Invalid example: -/// -/// ```compile_fail -/// # use sp_core::Get; -/// # use sp_core::parameter_types; -/// // This function cannot be used in a const context. -/// fn non_const_expression() -> u64 { 99 } -/// -/// parameter_types! { -/// pub const Argument: u64 = non_const_expression(); +/// sp_core::impl_maybe_marker_std_or_serde! { +/// /// A marker for a type that implements `Debug` when `feature = serde` or `feature = std`. +/// trait MaybeDebug: std::fmt::Debug; +/// /// A marker for a type that implements `Debug + Display` when `feature = serde` or `feature = std`. +/// trait MaybeDebugDisplay: std::fmt::Debug, std::fmt::Display; /// } /// ``` #[macro_export] -macro_rules! parameter_types { - ( - $( #[ $attr:meta ] )* - $vis:vis const $name:ident: $type:ty = $value:expr; - $( $rest:tt )* - ) => ( - $( #[ $attr ] )* - $vis struct $name; - $crate::parameter_types!(@IMPL_CONST $name , $type , $value); - $crate::parameter_types!( $( $rest )* ); - ); +macro_rules! impl_maybe_marker_std_or_serde { ( - $( #[ $attr:meta ] )* - $vis:vis $name:ident: $type:ty = $value:expr; - $( $rest:tt )* - ) => ( - $( #[ $attr ] )* - $vis struct $name; - $crate::parameter_types!(@IMPL $name, $type, $value); - $crate::parameter_types!( $( $rest )* ); - ); - () => (); - (@IMPL_CONST $name:ident, $type:ty, $value:expr) => { - impl $name { - /// Returns the value of this parameter type. - pub const fn get() -> $type { - $value - } - } - - impl> $crate::Get for $name { - fn get() -> I { - I::from(Self::get()) - } - } - - impl $crate::TypedGet for $name { - type Type = $type; - fn get() -> $type { - Self::get() - } - } - }; - (@IMPL $name:ident, $type:ty, $value:expr) => { - impl $name { - /// Returns the value of this parameter type. - pub fn get() -> $type { - $value - } - } - - impl> $crate::Get for $name { - fn get() -> I { - I::from(Self::get()) - } - } - - impl $crate::TypedGet for $name { - type Type = $type; - fn get() -> $type { - Self::get() - } - } - }; -} + $( + $(#[$doc:meta] )+ + trait $trait_name:ident: $( $trait_bound:path ),+; + )+ + ) => { + $( + $(#[$doc])+ + #[cfg(any(feature = "serde", feature = "std"))] + pub trait $trait_name: $( $trait_bound + )+ {} + #[cfg(any(feature = "serde", feature = "std"))] + impl $trait_name for T {} -/// Build a bounded vec from the given literals. -/// -/// The type of the outcome must be known. -/// -/// Will not handle any errors and just panic if the given literals cannot fit in the corresponding -/// bounded vec type. Thus, this is only suitable for testing and non-consensus code. -#[macro_export] -#[cfg(feature = "std")] -macro_rules! bounded_vec { - ($ ($values:expr),* $(,)?) => { - { - $crate::sp_std::vec![$($values),*].try_into().unwrap() - } - }; - ( $value:expr ; $repetition:expr ) => { - { - $crate::sp_std::vec![$value ; $repetition].try_into().unwrap() - } + $(#[$doc])+ + #[cfg(not(any(feature = "serde", feature = "std")))] + pub trait $trait_name {} + #[cfg(not(any(feature = "serde", feature = "std")))] + impl $trait_name for T {} + )+ } } -/// Build a bounded btree-map from the given literals. -/// -/// The type of the outcome must be known. -/// -/// Will not handle any errors and just panic if the given literals cannot fit in the corresponding -/// bounded vec type. Thus, this is only suitable for testing and non-consensus code. -#[macro_export] -#[cfg(feature = "std")] -macro_rules! bounded_btree_map { - ($ ( $key:expr => $value:expr ),* $(,)?) => { - { - $crate::TryCollect::<$crate::bounded::BoundedBTreeMap<_, _, _>>::try_collect( - $crate::sp_std::vec![$(($key, $value)),*].into_iter() - ).unwrap() - } - }; -} +/// The maximum number of bytes that can be allocated at one time. +// The maximum possible allocation size was chosen rather arbitrary, 32 MiB should be enough for +// everybody. +pub const MAX_POSSIBLE_ALLOCATION: u32 = 33554432; // 2^25 bytes, 32 MiB /// Generates a macro for checking if a certain feature is enabled. /// /// These feature checking macros can be used to conditionally enable/disable code in a dependent /// crate based on a feature in the crate where the macro is called. +/// +/// # Example +///``` +/// sp_core::generate_feature_enabled_macro!(check_std_is_enabled, feature = "std", $); +/// sp_core::generate_feature_enabled_macro!(check_std_or_serde_is_enabled, any(feature = "std", feature = "serde"), $); +/// +/// // All the code passed to the macro will then conditionally compiled based on the features +/// // activated for the crate where the macro was generated. +/// check_std_is_enabled! { +/// struct StdEnabled; +/// } +///``` #[macro_export] // We need to skip formatting this macro because of this bug: // https://github.com/rust-lang/rustfmt/issues/5283 #[rustfmt::skip] macro_rules! generate_feature_enabled_macro { ( $macro_name:ident, $feature_name:meta, $d:tt ) => { - /// Enable/disable the given code depending on - #[doc = concat!("`", stringify!($feature_name), "`")] - /// being enabled for the crate or not. - /// - /// # Example - /// - /// ```nocompile - /// // Will add the code depending on the feature being enabled or not. - #[doc = concat!(stringify!($macro_name), "!( println!(\"Hello\") )")] - /// ``` - #[cfg($feature_name)] - #[macro_export] - macro_rules! $macro_name { - ( $d ( $d input:tt )* ) => { - $d ( $d input )* + $crate::paste::paste!{ + /// Enable/disable the given code depending on + #[doc = concat!("`", stringify!($feature_name), "`")] + /// being enabled for the crate or not. + /// + /// # Example + /// + /// ```nocompile + /// // Will add the code depending on the feature being enabled or not. + #[doc = concat!(stringify!($macro_name), "!( println!(\"Hello\") )")] + /// ``` + #[cfg($feature_name)] + #[macro_export] + macro_rules! [<_ $macro_name>] { + ( $d ( $d input:tt )* ) => { + $d ( $d input )* + } } - } - /// Enable/disable the given code depending on - #[doc = concat!("`", stringify!($feature_name), "`")] - /// being enabled for the crate or not. - /// - /// # Example - /// - /// ```nocompile - /// // Will add the code depending on the feature being enabled or not. - #[doc = concat!(stringify!($macro_name), "!( println!(\"Hello\") )")] - /// ``` - #[cfg(not($feature_name))] - #[macro_export] - macro_rules! $macro_name { - ( $d ( $d input:tt )* ) => {}; + /// Enable/disable the given code depending on + #[doc = concat!("`", stringify!($feature_name), "`")] + /// being enabled for the crate or not. + /// + /// # Example + /// + /// ```nocompile + /// // Will add the code depending on the feature being enabled or not. + #[doc = concat!(stringify!($macro_name), "!( println!(\"Hello\") )")] + /// ``` + #[cfg(not($feature_name))] + #[macro_export] + macro_rules! [<_ $macro_name>] { + ( $d ( $d input:tt )* ) => {}; + } + + // Work around for: + #[doc(hidden)] + pub use [<_ $macro_name>] as $macro_name; } }; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + #[should_panic] + fn generate_feature_enabled_macro_panics() { + generate_feature_enabled_macro!(if_test, test, $); + if_test!(panic!("This should panic")); + } + + #[test] + fn generate_feature_enabled_macro_works() { + generate_feature_enabled_macro!(if_not_test, not(test), $); + if_not_test!(panic!("This should not panic")); + } +} diff --git a/primitives/core/src/offchain/mod.rs b/primitives/core/src/offchain/mod.rs index 3f0eed87a3a6..cef495dfaacd 100644 --- a/primitives/core/src/offchain/mod.rs +++ b/primitives/core/src/offchain/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -58,7 +58,7 @@ pub trait OffchainStorage: Clone + Send + Sync { /// A type of supported crypto. #[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, RuntimeDebug, PassByEnum)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[repr(C)] pub enum StorageKind { /// Persistent storage is non-revertible and not fork-aware. It means that any value @@ -208,14 +208,14 @@ impl OpaqueMultiaddr { #[derive( Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Default, RuntimeDebug, PassByInner, Encode, Decode, )] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Timestamp(u64); /// Duration type #[derive( Clone, Copy, PartialEq, Eq, Ord, PartialOrd, Default, RuntimeDebug, PassByInner, Encode, Decode, )] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct Duration(u64); impl Duration { @@ -260,34 +260,22 @@ impl Timestamp { bitflags::bitflags! { /// Execution context extra capabilities. pub struct Capabilities: u32 { - /// Access to transaction pool. - const TRANSACTION_POOL = 0b0000_0000_0001; /// External http calls. - const HTTP = 0b0000_0000_0010; + const HTTP = 1 << 0; /// Keystore access. - const KEYSTORE = 0b0000_0000_0100; + const KEYSTORE = 1 << 2; /// Randomness source. - const RANDOMNESS = 0b0000_0000_1000; + const RANDOMNESS = 1 << 3; /// Access to opaque network state. - const NETWORK_STATE = 0b0000_0001_0000; + const NETWORK_STATE = 1 << 4; /// Access to offchain worker DB (read only). - const OFFCHAIN_DB_READ = 0b0000_0010_0000; + const OFFCHAIN_DB_READ = 1 << 5; /// Access to offchain worker DB (writes). - const OFFCHAIN_DB_WRITE = 0b0000_0100_0000; + const OFFCHAIN_DB_WRITE = 1 << 6; /// Manage the authorized nodes - const NODE_AUTHORIZATION = 0b0000_1000_0000; + const NODE_AUTHORIZATION = 1 << 7; /// Access time related functionality - const TIME = 0b0001_0000_0000; - } -} - -impl Capabilities { - /// Return capabilities for rich offchain calls. - /// - /// Those calls should be allowed to sign and submit transactions - /// and access offchain workers database (but read only!). - pub fn rich_offchain_call() -> Self { - Capabilities::TRANSACTION_POOL | Capabilities::KEYSTORE | Capabilities::OFFCHAIN_DB_READ + const TIME = 1 << 8; } } @@ -793,8 +781,8 @@ mod tests { assert!(!none.contains(Capabilities::KEYSTORE)); assert!(all.contains(Capabilities::KEYSTORE)); assert!(some.contains(Capabilities::KEYSTORE)); - assert!(!none.contains(Capabilities::TRANSACTION_POOL)); - assert!(all.contains(Capabilities::TRANSACTION_POOL)); - assert!(!some.contains(Capabilities::TRANSACTION_POOL)); + assert!(!none.contains(Capabilities::RANDOMNESS)); + assert!(all.contains(Capabilities::RANDOMNESS)); + assert!(!some.contains(Capabilities::TIME)); } } diff --git a/primitives/core/src/offchain/storage.rs b/primitives/core/src/offchain/storage.rs index cf2c93641f24..4db839f1a451 100644 --- a/primitives/core/src/offchain/storage.rs +++ b/primitives/core/src/offchain/storage.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,12 +17,14 @@ //! In-memory implementation of offchain workers database. -use crate::offchain::OffchainStorage; +use crate::offchain::{DbExternalities, OffchainStorage, StorageKind, STORAGE_PREFIX}; use std::{ collections::hash_map::{Entry, HashMap}, iter::Iterator, }; +const LOG_TARGET: &str = "offchain-worker::storage"; + /// In-memory storage for offchain workers. #[derive(Debug, Clone, Default)] pub struct InMemOffchainStorage { @@ -88,3 +90,95 @@ impl OffchainStorage for InMemOffchainStorage { } } } + +fn unavailable_yet(name: &str) -> R { + tracing::error!( + target: LOG_TARGET, + "The {:?} API is not available for offchain workers yet. Follow \ + https://github.com/paritytech/substrate/issues/1458 for details", + name + ); + Default::default() +} + +const LOCAL_DB: &str = "LOCAL (fork-aware) DB"; + +/// Offchain DB that implements [`DbExternalities`] for [`OffchainStorage`]. +#[derive(Debug, Clone)] +pub struct OffchainDb { + /// Persistent storage database. + persistent: Storage, +} + +impl OffchainDb { + /// Create new instance of Offchain DB. + pub fn new(persistent: Storage) -> Self { + Self { persistent } + } +} + +impl DbExternalities for OffchainDb { + fn local_storage_set(&mut self, kind: StorageKind, key: &[u8], value: &[u8]) { + tracing::debug!( + target: LOG_TARGET, + ?kind, + key = ?array_bytes::bytes2hex("", key), + value = ?array_bytes::bytes2hex("", value), + "Write", + ); + match kind { + StorageKind::PERSISTENT => self.persistent.set(STORAGE_PREFIX, key, value), + StorageKind::LOCAL => unavailable_yet(LOCAL_DB), + } + } + + fn local_storage_clear(&mut self, kind: StorageKind, key: &[u8]) { + tracing::debug!( + target: LOG_TARGET, + ?kind, + key = ?array_bytes::bytes2hex("", key), + "Clear", + ); + match kind { + StorageKind::PERSISTENT => self.persistent.remove(STORAGE_PREFIX, key), + StorageKind::LOCAL => unavailable_yet(LOCAL_DB), + } + } + + fn local_storage_compare_and_set( + &mut self, + kind: StorageKind, + key: &[u8], + old_value: Option<&[u8]>, + new_value: &[u8], + ) -> bool { + tracing::debug!( + target: LOG_TARGET, + ?kind, + key = ?array_bytes::bytes2hex("", key), + new_value = ?array_bytes::bytes2hex("", new_value), + old_value = ?old_value.as_ref().map(|s| array_bytes::bytes2hex("", s)), + "CAS", + ); + match kind { + StorageKind::PERSISTENT => + self.persistent.compare_and_set(STORAGE_PREFIX, key, old_value, new_value), + StorageKind::LOCAL => unavailable_yet(LOCAL_DB), + } + } + + fn local_storage_get(&mut self, kind: StorageKind, key: &[u8]) -> Option> { + let result = match kind { + StorageKind::PERSISTENT => self.persistent.get(STORAGE_PREFIX, key), + StorageKind::LOCAL => unavailable_yet(LOCAL_DB), + }; + tracing::debug!( + target: LOG_TARGET, + ?kind, + key = ?array_bytes::bytes2hex("", key), + result = ?result.as_ref().map(|s| array_bytes::bytes2hex("", s)), + "Read", + ); + result + } +} diff --git a/primitives/core/src/offchain/testing.rs b/primitives/core/src/offchain/testing.rs index a2065eb17717..ee3620e70196 100644 --- a/primitives/core/src/offchain/testing.rs +++ b/primitives/core/src/offchain/testing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/core/src/sr25519.rs b/primitives/core/src/sr25519.rs index 9064fb742739..bcd64ed1cc3c 100644 --- a/primitives/core/src/sr25519.rs +++ b/primitives/core/src/sr25519.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,31 +15,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -// tag::description[] //! Simple sr25519 (Schnorr-Ristretto) API. //! //! Note: `CHAIN_CODE_LENGTH` must be equal to `crate::crypto::JUNCTION_ID_LEN` //! for this to work. -// end::description[] -#[cfg(feature = "std")] +#[cfg(any(feature = "full_crypto", feature = "serde"))] +use crate::crypto::DeriveJunction; +#[cfg(feature = "serde")] use crate::crypto::Ss58Codec; #[cfg(feature = "full_crypto")] -use crate::crypto::{DeriveJunction, Infallible, Pair as TraitPair, SecretStringError}; -#[cfg(feature = "std")] -use bip39::{Language, Mnemonic, MnemonicType}; +use crate::crypto::{DeriveError, Pair as TraitPair, SecretStringError}; #[cfg(feature = "full_crypto")] use schnorrkel::{ - derive::{ChainCode, Derivation, CHAIN_CODE_LENGTH}, - signing_context, ExpansionMode, Keypair, MiniSecretKey, PublicKey, SecretKey, + derive::CHAIN_CODE_LENGTH, signing_context, ExpansionMode, Keypair, MiniSecretKey, SecretKey, +}; +#[cfg(any(feature = "full_crypto", feature = "serde"))] +use schnorrkel::{ + derive::{ChainCode, Derivation}, + PublicKey, }; -#[cfg(feature = "full_crypto")] use sp_std::vec::Vec; -#[cfg(feature = "std")] -use substrate_bip39::mini_secret_from_entropy; use crate::{ crypto::{ - ByteArray, CryptoType, CryptoTypeId, CryptoTypePublicPair, Derive, Public as TraitPublic, + ByteArray, CryptoType, CryptoTypeId, Derive, FromEntropy, Public as TraitPublic, UncheckedFrom, }, hash::{H256, H512}, @@ -50,9 +49,11 @@ use sp_std::ops::Deref; #[cfg(feature = "full_crypto")] use schnorrkel::keys::{MINI_SECRET_KEY_LENGTH, SECRET_KEY_LENGTH}; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{de, Deserialize, Deserializer, Serialize, Serializer}; use sp_runtime_interface::pass_by::PassByInner; +#[cfg(all(not(feature = "std"), feature = "serde"))] +use sp_std::alloc::{format, string::String}; // signing context #[cfg(feature = "full_crypto")] @@ -93,6 +94,14 @@ impl Clone for Pair { } } +impl FromEntropy for Public { + fn from_entropy(input: &mut impl codec::Input) -> Result { + let mut result = Self([0u8; 32]); + input.read(&mut result.0[..])?; + Ok(result) + } +} + impl AsRef<[u8; 32]> for Public { fn as_ref(&self) -> &[u8; 32] { &self.0 @@ -185,7 +194,7 @@ impl sp_std::fmt::Debug for Public { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl Serialize for Public { fn serialize(&self, serializer: S) -> Result where @@ -195,7 +204,7 @@ impl Serialize for Public { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl<'de> Deserialize<'de> for Public { fn deserialize(deserializer: D) -> Result where @@ -207,8 +216,6 @@ impl<'de> Deserialize<'de> for Public { } /// An Schnorrkel/Ristretto x25519 ("sr25519") signature. -/// -/// Instead of importing it for the local module, alias it to be available as a public type #[cfg_attr(feature = "full_crypto", derive(Hash))] #[derive(Encode, Decode, MaxEncodedLen, PassByInner, TypeInfo, PartialEq, Eq)] pub struct Signature(pub [u8; 64]); @@ -227,17 +234,17 @@ impl TryFrom<&[u8]> for Signature { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl Serialize for Signature { fn serialize(&self, serializer: S) -> Result where S: Serializer, { - serializer.serialize_str(&array_bytes::bytes2hex("", self.as_ref())) + serializer.serialize_str(&array_bytes::bytes2hex("", self)) } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl<'de> Deserialize<'de> for Signature { fn deserialize(deserializer: D) -> Result where @@ -307,17 +314,6 @@ impl sp_std::fmt::Debug for Signature { } } -/// A localized signature also contains sender information. -/// NOTE: Encode and Decode traits are supported in ed25519 but not possible for now here. -#[cfg(feature = "std")] -#[derive(PartialEq, Eq, Clone, Debug)] -pub struct LocalizedSignature { - /// The signer of the signature. - pub signer: Public, - /// The signature itself. - pub signature: Signature, -} - impl UncheckedFrom<[u8; 64]> for Signature { fn unchecked_from(data: [u8; 64]) -> Signature { Signature(data) @@ -361,7 +357,7 @@ impl Derive for Public { /// Derive a child key from a series of given junctions. /// /// `None` if there are any hard junctions in there. - #[cfg(feature = "std")] + #[cfg(feature = "serde")] fn derive>(&self, path: Iter) -> Option { let mut acc = PublicKey::from_bytes(self.as_ref()).ok()?; for j in path { @@ -401,23 +397,7 @@ impl ByteArray for Public { const LEN: usize = 32; } -impl TraitPublic for Public { - fn to_public_crypto_pair(&self) -> CryptoTypePublicPair { - CryptoTypePublicPair(CRYPTO_ID, self.to_raw_vec()) - } -} - -impl From for CryptoTypePublicPair { - fn from(key: Public) -> Self { - (&key).into() - } -} - -impl From<&Public> for CryptoTypePublicPair { - fn from(key: &Public) -> Self { - CryptoTypePublicPair(CRYPTO_ID, key.to_raw_vec()) - } -} +impl TraitPublic for Public {} #[cfg(feature = "std")] impl From for Pair { @@ -469,16 +449,6 @@ impl TraitPair for Pair { type Public = Public; type Seed = Seed; type Signature = Signature; - type DeriveError = Infallible; - - /// Make a new key pair from raw secret seed material. - /// - /// This is generated using schnorrkel's Mini-Secret-Keys. - /// - /// A MiniSecretKey is literally what Ed25519 calls a SecretKey, which is just 32 random bytes. - fn from_seed(seed: &Seed) -> Pair { - Self::from_seed_slice(&seed[..]).expect("32 bytes can always build a key; qed") - } /// Get the public key. fn public(&self) -> Public { @@ -487,10 +457,12 @@ impl TraitPair for Pair { Public(pk) } - /// Make a new key pair from secret seed material. The slice must be 32 bytes long or it - /// will return `None`. + /// Make a new key pair from raw secret seed material. /// - /// You should never need to use this; generate(), generate_with_phrase(), from_phrase() + /// This is generated using schnorrkel's Mini-Secret-Keys. + /// + /// A `MiniSecretKey` is literally what Ed25519 calls a `SecretKey`, which is just 32 random + /// bytes. fn from_seed_slice(seed: &[u8]) -> Result { match seed.len() { MINI_SECRET_KEY_LENGTH => Ok(Pair( @@ -506,42 +478,16 @@ impl TraitPair for Pair { _ => Err(SecretStringError::InvalidSeedLength), } } - #[cfg(feature = "std")] - fn generate_with_phrase(password: Option<&str>) -> (Pair, String, Seed) { - let mnemonic = Mnemonic::new(MnemonicType::Words12, Language::English); - let phrase = mnemonic.phrase(); - let (pair, seed) = Self::from_phrase(phrase, password) - .expect("All phrases generated by Mnemonic are valid; qed"); - (pair, phrase.to_owned(), seed) - } - #[cfg(feature = "std")] - fn from_phrase( - phrase: &str, - password: Option<&str>, - ) -> Result<(Pair, Seed), SecretStringError> { - Mnemonic::from_phrase(phrase, Language::English) - .map_err(|_| SecretStringError::InvalidPhrase) - .map(|m| Self::from_entropy(m.entropy(), password)) - } fn derive>( &self, path: Iter, seed: Option, - ) -> Result<(Pair, Option), Self::DeriveError> { - let seed = if let Some(s) = seed { - if let Ok(msk) = MiniSecretKey::from_bytes(&s) { - if msk.expand(ExpansionMode::Ed25519) == self.0.secret { - Some(msk) - } else { - None - } - } else { - None - } - } else { - None - }; + ) -> Result<(Pair, Option), DeriveError> { + let seed = seed + .and_then(|s| MiniSecretKey::from_bytes(&s).ok()) + .filter(|msk| msk.expand(ExpansionMode::Ed25519) == self.0.secret); + let init = self.0.secret.clone(); let (result, seed) = path.fold((init, seed), |(acc, acc_seed), j| match (j, acc_seed) { (DeriveJunction::Soft(cc), _) => (acc.derived_key_simple(ChainCode(cc), &[]).0, None), @@ -559,21 +505,9 @@ impl TraitPair for Pair { } fn verify>(sig: &Self::Signature, message: M, pubkey: &Self::Public) -> bool { - Self::verify_weak(&sig.0[..], message, pubkey) - } - - fn verify_weak, M: AsRef<[u8]>>(sig: &[u8], message: M, pubkey: P) -> bool { - let signature = match schnorrkel::Signature::from_bytes(sig) { - Ok(signature) => signature, - Err(_) => return false, - }; - - let pub_key = match PublicKey::from_bytes(pubkey.as_ref()) { - Ok(pub_key) => pub_key, - Err(_) => return false, - }; - - pub_key.verify_simple(SIGNING_CTX, message.as_ref(), &signature).is_ok() + let Ok(signature) = schnorrkel::Signature::from_bytes(sig.as_ref()) else { return false }; + let Ok(public) = PublicKey::from_bytes(pubkey.as_ref()) else { return false }; + public.verify_simple(SIGNING_CTX, message.as_ref(), &signature).is_ok() } fn to_raw_vec(&self) -> Vec { @@ -583,18 +517,6 @@ impl TraitPair for Pair { #[cfg(feature = "std")] impl Pair { - /// Make a new key pair from binary data derived from a valid seed phrase. - /// - /// This uses a key derivation function to convert the entropy into a seed, then returns - /// the pair generated from it. - pub fn from_entropy(entropy: &[u8], password: Option<&str>) -> (Pair, Seed) { - let mini_key: MiniSecretKey = mini_secret_from_entropy(entropy, password.unwrap_or("")) - .expect("32 bytes can always build a key; qed"); - - let kp = mini_key.expand_to_keypair(ExpansionMode::Ed25519); - (Pair(kp), mini_key.to_bytes()) - } - /// Verify a signature on a message. Returns `true` if the signature is good. /// Supports old 0.1.1 deprecated signatures and should be used only for backward /// compatibility. @@ -625,48 +547,294 @@ impl CryptoType for Pair { type Pair = Pair; } -/// Batch verification. -/// -/// `messages`, `signatures` and `pub_keys` should all have equal length. -/// -/// Returns `true` if all signatures are correct, `false` otherwise. -#[cfg(feature = "std")] -pub fn verify_batch( - messages: Vec<&[u8]>, - signatures: Vec<&Signature>, - pub_keys: Vec<&Public>, -) -> bool { - let mut sr_pub_keys = Vec::with_capacity(pub_keys.len()); - for pub_key in pub_keys { - match schnorrkel::PublicKey::from_bytes(pub_key.as_ref()) { - Ok(pk) => sr_pub_keys.push(pk), - Err(_) => return false, - }; +/// Schnorrkel VRF related types and operations. +pub mod vrf { + use super::*; + #[cfg(feature = "full_crypto")] + use crate::crypto::VrfSecret; + use crate::crypto::{VrfCrypto, VrfPublic}; + use schnorrkel::{ + errors::MultiSignatureStage, + vrf::{VRF_OUTPUT_LENGTH, VRF_PROOF_LENGTH}, + SignatureError, + }; + + const DEFAULT_EXTRA_DATA_LABEL: &[u8] = b"VRF"; + + /// Transcript ready to be used for VRF related operations. + #[derive(Clone)] + pub struct VrfTranscript(pub merlin::Transcript); + + impl VrfTranscript { + /// Build a new transcript instance. + /// + /// Each `data` element is a tuple `(domain, message)` composing the transcipt. + pub fn new(label: &'static [u8], data: &[(&'static [u8], &[u8])]) -> Self { + let mut transcript = merlin::Transcript::new(label); + data.iter().for_each(|(l, b)| transcript.append_message(l, b)); + VrfTranscript(transcript) + } + + /// Map transcript to `VrfSignData`. + pub fn into_sign_data(self) -> VrfSignData { + self.into() + } + } + + /// VRF input. + /// + /// Technically a transcript used by the Fiat-Shamir transform. + pub type VrfInput = VrfTranscript; + + /// VRF input ready to be used for VRF sign and verify operations. + #[derive(Clone)] + pub struct VrfSignData { + /// Transcript data contributing to VRF output. + pub(super) transcript: VrfTranscript, + /// Extra transcript data to be signed by the VRF. + pub(super) extra: Option, + } + + impl From for VrfSignData { + fn from(transcript: VrfInput) -> Self { + VrfSignData { transcript, extra: None } + } + } + + // Get a reference to the inner VRF input. + impl AsRef for VrfSignData { + fn as_ref(&self) -> &VrfInput { + &self.transcript + } + } + + impl VrfSignData { + /// Build a new instance ready to be used for VRF signer and verifier. + /// + /// `input` will contribute to the VRF output bytes. + pub fn new(input: VrfTranscript) -> Self { + input.into() + } + + /// Add some extra data to be signed. + /// + /// `extra` will not contribute to the VRF output bytes. + pub fn with_extra(mut self, extra: VrfTranscript) -> Self { + self.extra = Some(extra); + self + } + } + + /// VRF signature data + #[derive(Clone, Debug, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo)] + pub struct VrfSignature { + /// VRF output. + pub output: VrfOutput, + /// VRF proof. + pub proof: VrfProof, + } + + /// VRF output type suitable for schnorrkel operations. + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct VrfOutput(pub schnorrkel::vrf::VRFOutput); + + impl Encode for VrfOutput { + fn encode(&self) -> Vec { + self.0.as_bytes().encode() + } + } + + impl Decode for VrfOutput { + fn decode(i: &mut R) -> Result { + let decoded = <[u8; VRF_OUTPUT_LENGTH]>::decode(i)?; + Ok(Self(schnorrkel::vrf::VRFOutput::from_bytes(&decoded).map_err(convert_error)?)) + } + } + + impl MaxEncodedLen for VrfOutput { + fn max_encoded_len() -> usize { + <[u8; VRF_OUTPUT_LENGTH]>::max_encoded_len() + } + } + + impl TypeInfo for VrfOutput { + type Identity = [u8; VRF_OUTPUT_LENGTH]; + + fn type_info() -> scale_info::Type { + Self::Identity::type_info() + } + } + + /// VRF proof type suitable for schnorrkel operations. + #[derive(Clone, Debug, PartialEq, Eq)] + pub struct VrfProof(pub schnorrkel::vrf::VRFProof); + + impl Encode for VrfProof { + fn encode(&self) -> Vec { + self.0.to_bytes().encode() + } + } + + impl Decode for VrfProof { + fn decode(i: &mut R) -> Result { + let decoded = <[u8; VRF_PROOF_LENGTH]>::decode(i)?; + Ok(Self(schnorrkel::vrf::VRFProof::from_bytes(&decoded).map_err(convert_error)?)) + } + } + + impl MaxEncodedLen for VrfProof { + fn max_encoded_len() -> usize { + <[u8; VRF_PROOF_LENGTH]>::max_encoded_len() + } } - let mut sr_signatures = Vec::with_capacity(signatures.len()); - for signature in signatures { - match schnorrkel::Signature::from_bytes(signature.as_ref()) { - Ok(s) => sr_signatures.push(s), - Err(_) => return false, - }; + impl TypeInfo for VrfProof { + type Identity = [u8; VRF_PROOF_LENGTH]; + + fn type_info() -> scale_info::Type { + Self::Identity::type_info() + } } - let mut messages: Vec = messages - .into_iter() - .map(|msg| signing_context(SIGNING_CTX).bytes(msg)) - .collect(); + #[cfg(feature = "full_crypto")] + impl VrfCrypto for Pair { + type VrfInput = VrfTranscript; + type VrfOutput = VrfOutput; + type VrfSignData = VrfSignData; + type VrfSignature = VrfSignature; + } - schnorrkel::verify_batch(&mut messages, &sr_signatures, &sr_pub_keys, true).is_ok() + #[cfg(feature = "full_crypto")] + impl VrfSecret for Pair { + fn vrf_sign(&self, data: &Self::VrfSignData) -> Self::VrfSignature { + let inout = self.0.vrf_create_hash(data.transcript.0.clone()); + + let extra = data + .extra + .as_ref() + .map(|e| e.0.clone()) + .unwrap_or_else(|| merlin::Transcript::new(DEFAULT_EXTRA_DATA_LABEL)); + + let proof = self.0.dleq_proove(extra, &inout, true).0; + + VrfSignature { output: VrfOutput(inout.to_output()), proof: VrfProof(proof) } + } + + fn vrf_output(&self, input: &Self::VrfInput) -> Self::VrfOutput { + let output = self.0.vrf_create_hash(input.0.clone()).to_output(); + VrfOutput(output) + } + } + + impl VrfCrypto for Public { + type VrfInput = VrfTranscript; + type VrfOutput = VrfOutput; + type VrfSignData = VrfSignData; + type VrfSignature = VrfSignature; + } + + impl VrfPublic for Public { + fn vrf_verify(&self, data: &Self::VrfSignData, signature: &Self::VrfSignature) -> bool { + let do_verify = || { + let public = schnorrkel::PublicKey::from_bytes(self)?; + + let inout = + signature.output.0.attach_input_hash(&public, data.transcript.0.clone())?; + + let extra = data + .extra + .as_ref() + .map(|e| e.0.clone()) + .unwrap_or_else(|| merlin::Transcript::new(DEFAULT_EXTRA_DATA_LABEL)); + + public.dleq_verify(extra, &inout, &signature.proof.0, true) + }; + do_verify().is_ok() + } + } + + fn convert_error(e: SignatureError) -> codec::Error { + use MultiSignatureStage::*; + use SignatureError::*; + match e { + EquationFalse => "Signature error: `EquationFalse`".into(), + PointDecompressionError => "Signature error: `PointDecompressionError`".into(), + ScalarFormatError => "Signature error: `ScalarFormatError`".into(), + NotMarkedSchnorrkel => "Signature error: `NotMarkedSchnorrkel`".into(), + BytesLengthError { .. } => "Signature error: `BytesLengthError`".into(), + MuSigAbsent { musig_stage: Commitment } => + "Signature error: `MuSigAbsent` at stage `Commitment`".into(), + MuSigAbsent { musig_stage: Reveal } => + "Signature error: `MuSigAbsent` at stage `Reveal`".into(), + MuSigAbsent { musig_stage: Cosignature } => + "Signature error: `MuSigAbsent` at stage `Commitment`".into(), + MuSigInconsistent { musig_stage: Commitment, duplicate: true } => + "Signature error: `MuSigInconsistent` at stage `Commitment` on duplicate".into(), + MuSigInconsistent { musig_stage: Commitment, duplicate: false } => + "Signature error: `MuSigInconsistent` at stage `Commitment` on not duplicate".into(), + MuSigInconsistent { musig_stage: Reveal, duplicate: true } => + "Signature error: `MuSigInconsistent` at stage `Reveal` on duplicate".into(), + MuSigInconsistent { musig_stage: Reveal, duplicate: false } => + "Signature error: `MuSigInconsistent` at stage `Reveal` on not duplicate".into(), + MuSigInconsistent { musig_stage: Cosignature, duplicate: true } => + "Signature error: `MuSigInconsistent` at stage `Cosignature` on duplicate".into(), + MuSigInconsistent { musig_stage: Cosignature, duplicate: false } => + "Signature error: `MuSigInconsistent` at stage `Cosignature` on not duplicate" + .into(), + } + } + + #[cfg(feature = "full_crypto")] + impl Pair { + /// Generate output bytes from the given VRF configuration. + pub fn make_bytes(&self, context: &[u8], input: &VrfInput) -> [u8; N] + where + [u8; N]: Default, + { + let inout = self.0.vrf_create_hash(input.0.clone()); + inout.make_bytes::<[u8; N]>(context) + } + } + + impl Public { + /// Generate output bytes from the given VRF configuration. + pub fn make_bytes( + &self, + context: &[u8], + input: &VrfInput, + output: &VrfOutput, + ) -> Result<[u8; N], codec::Error> + where + [u8; N]: Default, + { + let pubkey = schnorrkel::PublicKey::from_bytes(&self.0).map_err(convert_error)?; + let inout = + output.0.attach_input_hash(&pubkey, input.0.clone()).map_err(convert_error)?; + Ok(inout.make_bytes::<[u8; N]>(context)) + } + } + + impl VrfOutput { + /// Generate output bytes from the given VRF configuration. + pub fn make_bytes( + &self, + context: &[u8], + input: &VrfInput, + public: &Public, + ) -> Result<[u8; N], codec::Error> + where + [u8; N]: Default, + { + public.make_bytes(context, input, self) + } + } } #[cfg(test)] -mod compatibility_test { - use super::*; - use crate::crypto::DEV_PHRASE; - - // NOTE: tests to ensure addresses that are created with the `0.1.x` version (pre-audit) are - // still functional. +mod tests { + use super::{vrf::*, *}; + use crate::crypto::{Ss58Codec, VrfPublic, VrfSecret, DEV_ADDRESS, DEV_PHRASE}; + use serde_json; #[test] fn derive_soft_known_pair_should_work() { @@ -701,13 +869,6 @@ mod compatibility_test { assert!(Pair::verify_deprecated(&signature, &message[..], &public)); assert!(!Pair::verify(&signature, &message[..], &public)); } -} - -#[cfg(test)] -mod test { - use super::*; - use crate::crypto::{Ss58Codec, DEV_ADDRESS, DEV_PHRASE}; - use serde_json; #[test] fn default_phrase_should_be_used() { @@ -900,4 +1061,87 @@ mod test { // Poorly-sized assert!(deserialize_signature("\"abc123\"").is_err()); } + + #[test] + fn vrf_sign_verify() { + let pair = Pair::from_seed(b"12345678901234567890123456789012"); + let public = pair.public(); + + let data = VrfTranscript::new(b"label", &[(b"domain1", b"data1")]).into(); + + let signature = pair.vrf_sign(&data); + + assert!(public.vrf_verify(&data, &signature)); + } + + #[test] + fn vrf_sign_verify_with_extra() { + let pair = Pair::from_seed(b"12345678901234567890123456789012"); + let public = pair.public(); + + let extra = VrfTranscript::new(b"extra", &[(b"domain2", b"data2")]); + let data = VrfTranscript::new(b"label", &[(b"domain1", b"data1")]) + .into_sign_data() + .with_extra(extra); + + let signature = pair.vrf_sign(&data); + + assert!(public.vrf_verify(&data, &signature)); + } + + #[test] + fn vrf_make_bytes_matches() { + let pair = Pair::from_seed(b"12345678901234567890123456789012"); + let public = pair.public(); + let ctx = b"vrfbytes"; + + let input = VrfTranscript::new(b"label", &[(b"domain1", b"data1")]); + + let output = pair.vrf_output(&input); + + let out1 = pair.make_bytes::<32>(ctx, &input); + let out2 = output.make_bytes::<32>(ctx, &input, &public).unwrap(); + assert_eq!(out1, out2); + + let extra = VrfTranscript::new(b"extra", &[(b"domain2", b"data2")]); + let data = input.clone().into_sign_data().with_extra(extra); + let signature = pair.vrf_sign(&data); + assert!(public.vrf_verify(&data, &signature)); + + let out3 = public.make_bytes::<32>(ctx, &input, &signature.output).unwrap(); + assert_eq!(out2, out3); + } + + #[test] + fn vrf_backend_compat() { + let pair = Pair::from_seed(b"12345678901234567890123456789012"); + let public = pair.public(); + let ctx = b"vrfbytes"; + + let input = VrfInput::new(b"label", &[(b"domain1", b"data1")]); + let extra = VrfTranscript::new(b"extra", &[(b"domain2", b"data2")]); + + let data = input.clone().into_sign_data().with_extra(extra.clone()); + let signature = pair.vrf_sign(&data); + assert!(public.vrf_verify(&data, &signature)); + + let out1 = pair.make_bytes::<32>(ctx, &input); + let out2 = public.make_bytes::<32>(ctx, &input, &signature.output).unwrap(); + assert_eq!(out1, out2); + + // Direct call to backend version of sign after check with extra params + let (inout, proof, _) = pair + .0 + .vrf_sign_extra_after_check(input.0.clone(), |inout| { + let out3 = inout.make_bytes::<[u8; 32]>(ctx); + assert_eq!(out2, out3); + Some(extra.0.clone()) + }) + .unwrap(); + let signature2 = + VrfSignature { output: VrfOutput(inout.to_output()), proof: VrfProof(proof) }; + + assert!(public.vrf_verify(&data, &signature2)); + assert_eq!(signature.output, signature2.output); + } } diff --git a/primitives/core/src/testing.rs b/primitives/core/src/testing.rs index d3fa3fc86fce..6faf4ffa3042 100644 --- a/primitives/core/src/testing.rs +++ b/primitives/core/src/testing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,6 +25,10 @@ pub const ED25519: KeyTypeId = KeyTypeId(*b"ed25"); pub const SR25519: KeyTypeId = KeyTypeId(*b"sr25"); /// Key type for generic ECDSA key. pub const ECDSA: KeyTypeId = KeyTypeId(*b"ecds"); +/// Key type for generic BLS12-377 key. +pub const BLS377: KeyTypeId = KeyTypeId(*b"bls7"); +/// Key type for generic BLS12-381 key. +pub const BLS381: KeyTypeId = KeyTypeId(*b"bls8"); /// Macro for exporting functions from wasm in with the expected signature for using it with the /// wasm executor. This is useful for tests where you need to call a function in wasm. diff --git a/primitives/core/src/traits.rs b/primitives/core/src/traits.rs index c4b7f20f7e9a..40137053ab75 100644 --- a/primitives/core/src/traits.rs +++ b/primitives/core/src/traits.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,13 +24,27 @@ use std::{ pub use sp_externalities::{Externalities, ExternalitiesExt}; +/// The context in which a call is done. +/// +/// Depending on the context the executor may chooses different kind of heap sizes for the runtime +/// instance. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)] +pub enum CallContext { + /// The call is happening in some offchain context. + Offchain, + /// The call is happening in some on-chain context like building or importing a block. + Onchain, +} + /// Code execution engine. pub trait CodeExecutor: Sized + Send + Sync + ReadRuntimeVersion + Clone + 'static { /// Externalities error type. type Error: Display + Debug + Send + Sync + 'static; - /// Call a given method in the runtime. Returns a tuple of the result (either the output data - /// or an execution error) together with a `bool`, which is true if native execution was used. + /// Call a given method in the runtime. + /// + /// Returns a tuple of the result (either the output data or an execution error) together with a + /// `bool`, which is true if native execution was used. fn call( &self, ext: &mut dyn Externalities, @@ -38,6 +52,7 @@ pub trait CodeExecutor: Sized + Send + Sync + ReadRuntimeVersion + Clone + 'stat method: &str, data: &[u8], use_native: bool, + context: CallContext, ) -> (Result, Self::Error>, bool); } @@ -142,6 +157,16 @@ pub trait ReadRuntimeVersion: Send + Sync { ) -> Result, String>; } +impl ReadRuntimeVersion for std::sync::Arc { + fn read_runtime_version( + &self, + wasm_code: &[u8], + ext: &mut dyn Externalities, + ) -> Result, String> { + (**self).read_runtime_version(wasm_code, ext) + } +} + sp_externalities::decl_extension! { /// An extension that provides functionality to read version information from a given wasm blob. pub struct ReadRuntimeVersionExt(Box); @@ -154,31 +179,6 @@ impl ReadRuntimeVersionExt { } } -sp_externalities::decl_extension! { - /// Task executor extension. - pub struct TaskExecutorExt(Box); -} - -impl TaskExecutorExt { - /// New instance of task executor extension. - pub fn new(spawn_handle: impl SpawnNamed + Send + 'static) -> Self { - Self(Box::new(spawn_handle)) - } -} - -/// Runtime spawn extension. -pub trait RuntimeSpawn: Send { - /// Create new runtime instance and use dynamic dispatch to invoke with specified payload. - /// - /// Returns handle of the spawned task. - /// - /// Function pointers (`dispatcher_ref`, `func`) are WASM pointer types. - fn spawn_call(&self, dispatcher_ref: u32, func: u32, payload: Vec) -> u64; - - /// Join the result of previously created runtime instance invocation. - fn join(&self, handle: u64) -> Vec; -} - /// Something that can spawn tasks (blocking and non-blocking) with an assigned name /// and optional group. #[dyn_clonable::clonable] diff --git a/primitives/core/src/uint.rs b/primitives/core/src/uint.rs index 4bf914bde2ee..b251671dbeea 100644 --- a/primitives/core/src/uint.rs +++ b/primitives/core/src/uint.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,7 +23,6 @@ pub use primitive_types::{U256, U512}; mod tests { use super::*; use codec::{Decode, Encode}; - use sp_serializer as ser; macro_rules! test { ($name: ident, $test_name: ident) => { @@ -43,14 +42,17 @@ mod tests { ]; for (number, expected) in tests { - assert_eq!(format!("{:?}", expected), ser::to_string_pretty(&number)); - assert_eq!(number, ser::from_str(&format!("{:?}", expected)).unwrap()); + assert_eq!( + format!("{:?}", expected), + serde_json::to_string_pretty(&number).expect("Json pretty print failed") + ); + assert_eq!(number, serde_json::from_str(&format!("{:?}", expected)).unwrap()); } // Invalid examples - assert!(ser::from_str::<$name>("\"0x\"").unwrap_err().is_data()); - assert!(ser::from_str::<$name>("\"0xg\"").unwrap_err().is_data()); - assert!(ser::from_str::<$name>("\"\"").unwrap_err().is_data()); + assert!(serde_json::from_str::<$name>("\"0x\"").unwrap_err().is_data()); + assert!(serde_json::from_str::<$name>("\"0xg\"").unwrap_err().is_data()); + assert!(serde_json::from_str::<$name>("\"\"").unwrap_err().is_data()); } }; } @@ -78,10 +80,10 @@ mod tests { #[test] fn test_large_values() { assert_eq!( - ser::to_string_pretty(&!U256::zero()), + serde_json::to_string_pretty(&!U256::zero()).expect("Json pretty print failed"), "\"0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"" ); - assert!(ser::from_str::( + assert!(serde_json::from_str::( "\"0x1ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\"" ) .unwrap_err() diff --git a/primitives/crypto/ec-utils/Cargo.toml b/primitives/crypto/ec-utils/Cargo.toml new file mode 100644 index 000000000000..15b0e38126bd --- /dev/null +++ b/primitives/crypto/ec-utils/Cargo.toml @@ -0,0 +1,61 @@ +[package] +name = "sp-crypto-ec-utils" +version = "0.4.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +ark-serialize = { version = "0.4.2", default-features = false } +ark-ff = { version = "0.4.2", default-features = false } +ark-ec = { version = "0.4.2", default-features = false } +ark-std = { version = "0.4.0", default-features = false } +ark-bls12-377 = { version = "0.4.0", features = ["curve"], default-features = false } +ark-bls12-381 = { version = "0.4.0", features = ["curve"], default-features = false } +ark-bw6-761 = { version = "0.4.0", default-features = false } +ark-ed-on-bls12-381-bandersnatch = { version = "0.4.0", default-features = false } +ark-ed-on-bls12-377 = { version = "0.4.0", default-features = false } +sp-std = { version = "8.0.0", path = "../../std", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +ark-scale = { version = "0.0.3", features = ["hazmat"], default-features = false } +sp-runtime-interface = { version = "17.0.0", default-features = false, path = "../../runtime-interface" } + +[dev-dependencies] +sp-io = { path = "../../io", default-features = false } +ark-algebra-test-templates = { version = "0.4.2", default-features = false } +sp-ark-models = { version = "0.4.0-beta", default-features = false } +sp-ark-bls12-377 = { version = "0.4.0-beta", default-features = false } +sp-ark-bls12-381 = { version = "0.4.0-beta", default-features = false } +sp-ark-bw6-761 = { version = "0.4.0-beta", default-features = false } +sp-ark-ed-on-bls12-377 = { version = "0.4.0-beta", default-features = false } +sp-ark-ed-on-bls12-381-bandersnatch = { version = "0.4.0-beta", default-features = false } + +[features] +default = [ "std" ] +std = [ + "ark-serialize/std", + "ark-ff/std", + "ark-ec/std", + "ark-std/std", + "ark-bls12-377/std", + "ark-bls12-381/std", + "ark-bw6-761/std", + "ark-ed-on-bls12-381-bandersnatch/std", + "ark-ed-on-bls12-377/std", + "sp-std/std", + "codec/std", + "ark-scale/std", + "sp-runtime-interface/std", + "sp-io/std", + "ark-algebra-test-templates/std", + "sp-ark-bls12-377/std", + "sp-ark-bls12-381/std", + "sp-ark-bw6-761/std", + "sp-ark-ed-on-bls12-377/std", + "sp-ark-ed-on-bls12-381-bandersnatch/std", +] diff --git a/primitives/crypto/ec-utils/src/bls12_377.rs b/primitives/crypto/ec-utils/src/bls12_377.rs new file mode 100644 index 000000000000..9230479b3bec --- /dev/null +++ b/primitives/crypto/ec-utils/src/bls12_377.rs @@ -0,0 +1,103 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Support functions for bls12_377 to improve the performance of +//! multi_miller_loop, final_exponentiation, msm's and projective +//! multiplications by host function calls + +use crate::utils::{ + final_exponentiation_generic, msm_sw_generic, mul_projective_generic, multi_miller_loop_generic, +}; +use ark_bls12_377::{g1, g2, Bls12_377}; +use sp_std::vec::Vec; + +/// Compute a multi miller loop through arkworks +pub fn multi_miller_loop(a: Vec, b: Vec) -> Result, ()> { + multi_miller_loop_generic::(a, b) +} + +/// Compute a final exponentiation through arkworks +pub fn final_exponentiation(target: Vec) -> Result, ()> { + final_exponentiation_generic::(target) +} + +/// Compute a multi scalar multiplication for short_weierstrass through +/// arkworks on G1. +pub fn msm_g1(bases: Vec, scalars: Vec) -> Result, ()> { + msm_sw_generic::(bases, scalars) +} + +/// Compute a multi scalar multiplication for short_weierstrass through +/// arkworks on G2. +pub fn msm_g2(bases: Vec, scalars: Vec) -> Result, ()> { + msm_sw_generic::(bases, scalars) +} + +/// Compute a projective scalar multiplication for short_weierstrass +/// through arkworks on G1. +pub fn mul_projective_g1(base: Vec, scalar: Vec) -> Result, ()> { + mul_projective_generic::(base, scalar) +} + +/// Compute a projective scalar multiplication for short_weierstrass +/// through arkworks on G2. +pub fn mul_projective_g2(base: Vec, scalar: Vec) -> Result, ()> { + mul_projective_generic::(base, scalar) +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use sp_ark_bls12_377::{ + Bls12_377 as Bls12_377Host, G1Projective as G1ProjectiveHost, + G2Projective as G2ProjectiveHost, HostFunctions, + }; + + #[derive(PartialEq, Eq)] + struct Host; + + impl HostFunctions for Host { + fn bls12_377_multi_miller_loop(a: Vec, b: Vec) -> Result, ()> { + crate::elliptic_curves::bls12_377_multi_miller_loop(a, b) + } + fn bls12_377_final_exponentiation(f12: Vec) -> Result, ()> { + crate::elliptic_curves::bls12_377_final_exponentiation(f12) + } + fn bls12_377_msm_g1(bases: Vec, bigints: Vec) -> Result, ()> { + crate::elliptic_curves::bls12_377_msm_g1(bases, bigints) + } + fn bls12_377_msm_g2(bases: Vec, bigints: Vec) -> Result, ()> { + crate::elliptic_curves::bls12_377_msm_g2(bases, bigints) + } + fn bls12_377_mul_projective_g1(base: Vec, scalar: Vec) -> Result, ()> { + crate::elliptic_curves::bls12_377_mul_projective_g1(base, scalar) + } + fn bls12_377_mul_projective_g2(base: Vec, scalar: Vec) -> Result, ()> { + crate::elliptic_curves::bls12_377_mul_projective_g2(base, scalar) + } + } + + type Bls12_377 = Bls12_377Host; + type G1Projective = G1ProjectiveHost; + type G2Projective = G2ProjectiveHost; + + test_group!(g1; G1Projective; sw); + test_group!(g2; G2Projective; sw); + test_group!(pairing_output; ark_ec::pairing::PairingOutput; msm); + test_pairing!(pairing; super::Bls12_377); +} diff --git a/primitives/crypto/ec-utils/src/bls12_381.rs b/primitives/crypto/ec-utils/src/bls12_381.rs new file mode 100644 index 000000000000..6c707aa58145 --- /dev/null +++ b/primitives/crypto/ec-utils/src/bls12_381.rs @@ -0,0 +1,219 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Support functions for bls12_381 to improve the performance of +//! multi_miller_loop, final_exponentiation, msm's and projective +//! multiplications by host function calls + +use crate::utils::{ + final_exponentiation_generic, msm_sw_generic, mul_projective_generic, multi_miller_loop_generic, +}; +use ark_bls12_381::{g1, g2, Bls12_381}; +use sp_std::vec::Vec; + +/// Compute a multi miller loop through arkworks +pub fn multi_miller_loop(a: Vec, b: Vec) -> Result, ()> { + multi_miller_loop_generic::(a, b) +} + +/// Compute a final exponentiation through arkworks +pub fn final_exponentiation(target: Vec) -> Result, ()> { + final_exponentiation_generic::(target) +} + +/// Compute a multi scalar multiplication for short_weierstrass through +/// arkworks on G1. +pub fn msm_g1(bases: Vec, scalars: Vec) -> Result, ()> { + msm_sw_generic::(bases, scalars) +} + +/// Compute a multi scalar multiplication for short_weierstrass through +/// arkworks on G2. +pub fn msm_g2(bases: Vec, scalars: Vec) -> Result, ()> { + msm_sw_generic::(bases, scalars) +} + +/// Compute a projective scalar multiplication for short_weierstrass +/// through arkworks on G1. +pub fn mul_projective_g1(base: Vec, scalar: Vec) -> Result, ()> { + mul_projective_generic::(base, scalar) +} + +/// Compute a projective scalar multiplication for short_weierstrass +/// through arkworks on G2. +pub fn mul_projective_g2(base: Vec, scalar: Vec) -> Result, ()> { + mul_projective_generic::(base, scalar) +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use ark_ec::{AffineRepr, CurveGroup, Group}; + use ark_ff::{fields::Field, One, Zero}; + use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, Compress, Validate}; + use ark_std::{rand::Rng, test_rng, vec, UniformRand}; + use sp_ark_bls12_381::{ + fq::Fq, fq2::Fq2, fr::Fr, Bls12_381 as Bls12_381Host, G1Affine as G1AffineHost, + G1Projective as G1ProjectiveHost, G2Affine as G2AffineHost, + G2Projective as G2ProjectiveHost, HostFunctions, + }; + use sp_ark_models::pairing::PairingOutput; + + #[derive(PartialEq, Eq)] + struct Host; + + impl HostFunctions for Host { + fn bls12_381_multi_miller_loop(a: Vec, b: Vec) -> Result, ()> { + crate::elliptic_curves::bls12_381_multi_miller_loop(a, b) + } + fn bls12_381_final_exponentiation(f12: Vec) -> Result, ()> { + crate::elliptic_curves::bls12_381_final_exponentiation(f12) + } + fn bls12_381_msm_g1(bases: Vec, bigints: Vec) -> Result, ()> { + crate::elliptic_curves::bls12_381_msm_g1(bases, bigints) + } + fn bls12_381_msm_g2(bases: Vec, bigints: Vec) -> Result, ()> { + crate::elliptic_curves::bls12_381_msm_g2(bases, bigints) + } + fn bls12_381_mul_projective_g1(base: Vec, scalar: Vec) -> Result, ()> { + crate::elliptic_curves::bls12_381_mul_projective_g1(base, scalar) + } + fn bls12_381_mul_projective_g2(base: Vec, scalar: Vec) -> Result, ()> { + crate::elliptic_curves::bls12_381_mul_projective_g2(base, scalar) + } + } + + type Bls12_381 = Bls12_381Host; + type G1Projective = G1ProjectiveHost; + type G2Projective = G2ProjectiveHost; + type G1Affine = G1AffineHost; + type G2Affine = G2AffineHost; + + test_group!(g1; G1Projective; sw); + test_group!(g2; G2Projective; sw); + test_group!(pairing_output; PairingOutput; msm); + test_pairing!(ark_pairing; super::Bls12_381); + + #[test] + fn test_g1_endomorphism_beta() { + assert!(sp_ark_bls12_381::g1::BETA.pow([3u64]).is_one()); + } + + #[test] + fn test_g1_subgroup_membership_via_endomorphism() { + let mut rng = test_rng(); + let generator = G1Projective::rand(&mut rng).into_affine(); + assert!(generator.is_in_correct_subgroup_assuming_on_curve()); + } + + #[test] + fn test_g1_subgroup_non_membership_via_endomorphism() { + let mut rng = test_rng(); + loop { + let x = Fq::rand(&mut rng); + let greatest = rng.gen(); + + if let Some(p) = G1Affine::get_point_from_x_unchecked(x, greatest) { + if !::is_zero(&p.mul_bigint(Fr::characteristic())) { + assert!(!p.is_in_correct_subgroup_assuming_on_curve()); + return + } + } + } + } + + #[test] + fn test_g2_subgroup_membership_via_endomorphism() { + let mut rng = test_rng(); + let generator = G2Projective::rand(&mut rng).into_affine(); + assert!(generator.is_in_correct_subgroup_assuming_on_curve()); + } + + #[test] + fn test_g2_subgroup_non_membership_via_endomorphism() { + let mut rng = test_rng(); + loop { + let x = Fq2::rand(&mut rng); + let greatest = rng.gen(); + + if let Some(p) = G2Affine::get_point_from_x_unchecked(x, greatest) { + if !::is_zero(&p.mul_bigint(Fr::characteristic())) { + assert!(!p.is_in_correct_subgroup_assuming_on_curve()); + return + } + } + } + } + + // Test vectors and macro adapted from https://github.com/zkcrypto/bls12_381/blob/e224ad4ea1babfc582ccd751c2bf128611d10936/src/test-data/mod.rs + macro_rules! test_vectors { + ($projective:ident, $affine:ident, $compress:expr, $expected:ident) => { + let mut e = $projective::zero(); + + let mut v = vec![]; + { + let mut expected = $expected; + for _ in 0..1000 { + let e_affine = $affine::from(e); + let mut serialized = vec![0u8; e.serialized_size($compress)]; + e_affine.serialize_with_mode(serialized.as_mut_slice(), $compress).unwrap(); + v.extend_from_slice(&serialized[..]); + + let mut decoded = serialized; + let len_of_encoding = decoded.len(); + (&mut decoded[..]).copy_from_slice(&expected[0..len_of_encoding]); + expected = &expected[len_of_encoding..]; + let decoded = + $affine::deserialize_with_mode(&decoded[..], $compress, Validate::Yes) + .unwrap(); + assert_eq!(e_affine, decoded); + + e += &$projective::generator(); + } + } + + assert_eq!(&v[..], $expected); + }; + } + + #[test] + fn g1_compressed_valid_test_vectors() { + let bytes: &'static [u8] = include_bytes!("test-data/g1_compressed_valid_test_vectors.dat"); + test_vectors!(G1Projective, G1Affine, Compress::Yes, bytes); + } + + #[test] + fn g1_uncompressed_valid_test_vectors() { + let bytes: &'static [u8] = + include_bytes!("test-data/g1_uncompressed_valid_test_vectors.dat"); + test_vectors!(G1Projective, G1Affine, Compress::No, bytes); + } + + #[test] + fn g2_compressed_valid_test_vectors() { + let bytes: &'static [u8] = include_bytes!("test-data/g2_compressed_valid_test_vectors.dat"); + test_vectors!(G2Projective, G2Affine, Compress::Yes, bytes); + } + + #[test] + fn g2_uncompressed_valid_test_vectors() { + let bytes: &'static [u8] = + include_bytes!("test-data/g2_uncompressed_valid_test_vectors.dat"); + test_vectors!(G2Projective, G2Affine, Compress::No, bytes); + } +} diff --git a/primitives/crypto/ec-utils/src/bw6_761.rs b/primitives/crypto/ec-utils/src/bw6_761.rs new file mode 100644 index 000000000000..2f3b4c3c9c9a --- /dev/null +++ b/primitives/crypto/ec-utils/src/bw6_761.rs @@ -0,0 +1,103 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Support functions for bw6_761 to improve the performance of +//! multi_miller_loop, final_exponentiation, msm's and projective +//! multiplications by host function calls. + +use crate::utils::{ + final_exponentiation_generic, msm_sw_generic, mul_projective_generic, multi_miller_loop_generic, +}; +use ark_bw6_761::{g1, g2, BW6_761}; +use sp_std::vec::Vec; + +/// Compute a multi miller loop through arkworks +pub fn multi_miller_loop(a: Vec, b: Vec) -> Result, ()> { + multi_miller_loop_generic::(a, b) +} + +/// Compute a final exponentiation through arkworks +pub fn final_exponentiation(target: Vec) -> Result, ()> { + final_exponentiation_generic::(target) +} + +/// Compute a multi scalar multiplication for short_weierstrass through +/// arkworks on G1. +pub fn msm_g1(bases: Vec, scalars: Vec) -> Result, ()> { + msm_sw_generic::(bases, scalars) +} + +/// Compute a multi scalar multiplication for short_weierstrass through +/// arkworks on G2. +pub fn msm_g2(bases: Vec, scalars: Vec) -> Result, ()> { + msm_sw_generic::(bases, scalars) +} + +/// Compute a projective scalar multiplication for short_weierstrass through +/// arkworks on G1. +pub fn mul_projective_g1(base: Vec, scalar: Vec) -> Result, ()> { + mul_projective_generic::(base, scalar) +} + +/// Compute a projective scalar multiplication for short_weierstrass through +/// arkworks on G2. +pub fn mul_projective_g2(base: Vec, scalar: Vec) -> Result, ()> { + mul_projective_generic::(base, scalar) +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use sp_ark_bw6_761::{ + G1Projective as G1ProjectiveHost, G2Projective as G2ProjectiveHost, HostFunctions, + BW6_761 as BW6_761Host, + }; + + #[derive(PartialEq, Eq)] + struct Host; + + impl HostFunctions for Host { + fn bw6_761_multi_miller_loop(a: Vec, b: Vec) -> Result, ()> { + crate::elliptic_curves::bw6_761_multi_miller_loop(a, b) + } + fn bw6_761_final_exponentiation(f12: Vec) -> Result, ()> { + crate::elliptic_curves::bw6_761_final_exponentiation(f12) + } + fn bw6_761_msm_g1(bases: Vec, bigints: Vec) -> Result, ()> { + crate::elliptic_curves::bw6_761_msm_g1(bases, bigints) + } + fn bw6_761_msm_g2(bases: Vec, bigints: Vec) -> Result, ()> { + crate::elliptic_curves::bw6_761_msm_g2(bases, bigints) + } + fn bw6_761_mul_projective_g1(base: Vec, scalar: Vec) -> Result, ()> { + crate::elliptic_curves::bw6_761_mul_projective_g1(base, scalar) + } + fn bw6_761_mul_projective_g2(base: Vec, scalar: Vec) -> Result, ()> { + crate::elliptic_curves::bw6_761_mul_projective_g2(base, scalar) + } + } + + type BW6_761 = BW6_761Host; + type G1Projective = G1ProjectiveHost; + type G2Projective = G2ProjectiveHost; + + test_group!(g1; G1Projective; sw); + test_group!(g2; G2Projective; sw); + test_group!(pairing_output; ark_ec::pairing::PairingOutput; msm); + test_pairing!(pairing; super::BW6_761); +} diff --git a/primitives/crypto/ec-utils/src/ed_on_bls12_377.rs b/primitives/crypto/ec-utils/src/ed_on_bls12_377.rs new file mode 100644 index 000000000000..84a86286180f --- /dev/null +++ b/primitives/crypto/ec-utils/src/ed_on_bls12_377.rs @@ -0,0 +1,56 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Support functions for ed_on_bls12_377 to improve the performance of +//! msm and projective multiplication by host function calls + +use crate::utils::{msm_te_generic, mul_projective_te_generic}; +use ark_ed_on_bls12_377::EdwardsConfig; +use sp_std::vec::Vec; + +/// Compute a multi scalar mulitplication for twisted_edwards through +/// arkworks. +pub fn msm(bases: Vec, scalars: Vec) -> Result, ()> { + msm_te_generic::(bases, scalars) +} + +/// Compute a projective scalar multiplication for twisted_edwards +/// through arkworks. +pub fn mul_projective(base: Vec, scalar: Vec) -> Result, ()> { + mul_projective_te_generic::(base, scalar) +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use sp_ark_ed_on_bls12_377::{EdwardsProjective as EdwardsProjectiveHost, HostFunctions}; + + struct Host {} + + impl HostFunctions for Host { + fn ed_on_bls12_377_msm(bases: Vec, scalars: Vec) -> Result, ()> { + crate::elliptic_curves::ed_on_bls12_377_msm(bases, scalars) + } + fn ed_on_bls12_377_mul_projective(base: Vec, scalar: Vec) -> Result, ()> { + crate::elliptic_curves::ed_on_bls12_377_mul_projective(base, scalar) + } + } + + type EdwardsProjective = EdwardsProjectiveHost; + test_group!(te; EdwardsProjective; te); +} diff --git a/primitives/crypto/ec-utils/src/ed_on_bls12_381_bandersnatch.rs b/primitives/crypto/ec-utils/src/ed_on_bls12_381_bandersnatch.rs new file mode 100644 index 000000000000..72b68c3b4718 --- /dev/null +++ b/primitives/crypto/ec-utils/src/ed_on_bls12_381_bandersnatch.rs @@ -0,0 +1,94 @@ +// This file is part of Substrate. + +// Copyright (C) 2017-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. + +//! Support functions for ed_on_bls12_381_bandersnatch to improve the +//! performance of msm' and projective multiplications by host function +//! calls. + +use crate::utils::{ + msm_sw_generic, msm_te_generic, mul_projective_generic, mul_projective_te_generic, +}; +use ark_ed_on_bls12_381_bandersnatch::BandersnatchConfig; +use sp_std::vec::Vec; + +/// Compute a multi scalar multiplication for short_weierstrass through +/// arkworks. +pub fn sw_msm(bases: Vec, scalars: Vec) -> Result, ()> { + msm_sw_generic::(bases, scalars) +} + +/// Compute a multi scalar mulitplication for twisted_edwards through +/// arkworks. +pub fn te_msm(bases: Vec, scalars: Vec) -> Result, ()> { + msm_te_generic::(bases, scalars) +} + +/// Compute a projective scalar multiplication for short_weierstrass +/// through arkworks. +pub fn sw_mul_projective(base: Vec, scalar: Vec) -> Result, ()> { + mul_projective_generic::(base, scalar) +} + +/// Compute a projective scalar multiplication for twisted_edwards +/// through arkworks. +pub fn te_mul_projective(base: Vec, scalar: Vec) -> Result, ()> { + mul_projective_te_generic::(base, scalar) +} + +#[cfg(test)] +mod tests { + use super::*; + use ark_algebra_test_templates::*; + use sp_ark_ed_on_bls12_381_bandersnatch::{ + EdwardsProjective as EdwardsProjectiveHost, HostFunctions, SWProjective as SWProjectiveHost, + }; + + pub struct Host {} + + impl HostFunctions for Host { + fn ed_on_bls12_381_bandersnatch_te_msm( + bases: Vec, + scalars: Vec, + ) -> Result, ()> { + crate::elliptic_curves::ed_on_bls12_381_bandersnatch_te_msm(bases, scalars) + } + fn ed_on_bls12_381_bandersnatch_sw_msm( + bases: Vec, + scalars: Vec, + ) -> Result, ()> { + crate::elliptic_curves::ed_on_bls12_381_bandersnatch_sw_msm(bases, scalars) + } + fn ed_on_bls12_381_bandersnatch_te_mul_projective( + base: Vec, + scalar: Vec, + ) -> Result, ()> { + crate::elliptic_curves::ed_on_bls12_381_bandersnatch_te_mul_projective(base, scalar) + } + fn ed_on_bls12_381_bandersnatch_sw_mul_projective( + base: Vec, + scalar: Vec, + ) -> Result, ()> { + crate::elliptic_curves::ed_on_bls12_381_bandersnatch_sw_mul_projective(base, scalar) + } + } + + type EdwardsProjective = EdwardsProjectiveHost; + type SWProjective = SWProjectiveHost; + + test_group!(sw; SWProjective; sw); + test_group!(te; EdwardsProjective; te); +} diff --git a/primitives/crypto/ec-utils/src/lib.rs b/primitives/crypto/ec-utils/src/lib.rs new file mode 100644 index 000000000000..c1877dd5b5d7 --- /dev/null +++ b/primitives/crypto/ec-utils/src/lib.rs @@ -0,0 +1,264 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The main elliptic curves trait, allowing Substrate to call into host functions +//! for operations on elliptic curves. + +#![warn(missing_docs)] +#![cfg_attr(not(feature = "std"), no_std)] + +pub mod bls12_377; +pub mod bls12_381; +pub mod bw6_761; +pub mod ed_on_bls12_377; +pub mod ed_on_bls12_381_bandersnatch; +mod utils; + +use sp_runtime_interface::runtime_interface; + +/// Interfaces for working with elliptic curves related types from within the runtime. +/// All type are (de-)serialized through the wrapper types from the ark-scale trait, +/// with ark_scale::{ArkScale, ArkScaleProjective}; +#[runtime_interface] +pub trait EllipticCurves { + /// Compute a multi Miller loop for bls12_37 + /// Receives encoded: + /// a: ArkScale>> + /// b: ArkScale>> + /// Returns encoded: ArkScale>> + fn bls12_377_multi_miller_loop(a: Vec, b: Vec) -> Result, ()> { + bls12_377::multi_miller_loop(a, b) + } + + /// Compute a final exponentiation for bls12_377 + /// Receives encoded: ArkScale>> + /// Returns encoded: ArkScale>> + fn bls12_377_final_exponentiation(f12: Vec) -> Result, ()> { + bls12_377::final_exponentiation(f12) + } + + /// Compute a projective multiplication on G1 for bls12_377 + /// Receives encoded: + /// base: ArkScaleProjective + /// scalar: ArkScale<&[u64]> + /// Returns encoded: ArkScaleProjective + fn bls12_377_mul_projective_g1(base: Vec, scalar: Vec) -> Result, ()> { + bls12_377::mul_projective_g1(base, scalar) + } + + /// Compute a projective multiplication on G2 for bls12_377 + /// through arkworks on G2 + /// Receives encoded: + /// base: ArkScaleProjective + /// scalar: ArkScale<&[u64]> + /// Returns encoded: ArkScaleProjective + fn bls12_377_mul_projective_g2(base: Vec, scalar: Vec) -> Result, ()> { + bls12_377::mul_projective_g2(base, scalar) + } + + /// Compute a msm on G1 for bls12_377 + /// Receives encoded: + /// bases: ArkScale<&[ark_bls12_377::G1Affine]> + /// scalars: ArkScale<&[ark_bls12_377::Fr]> + /// Returns encoded: ArkScaleProjective + fn bls12_377_msm_g1(bases: Vec, scalars: Vec) -> Result, ()> { + bls12_377::msm_g1(bases, scalars) + } + + /// Compute a msm on G2 for bls12_377 + /// Receives encoded: + /// bases: ArkScale<&[ark_bls12_377::G2Affine]> + /// scalars: ArkScale<&[ark_bls12_377::Fr]> + /// Returns encoded: ArkScaleProjective + fn bls12_377_msm_g2(bases: Vec, scalars: Vec) -> Result, ()> { + bls12_377::msm_g2(bases, scalars) + } + + /// Compute a multi Miller loop on bls12_381 + /// Receives encoded: + /// a: ArkScale>> + /// b: ArkScale>> + /// Returns encoded: ArkScale>> + fn bls12_381_multi_miller_loop(a: Vec, b: Vec) -> Result, ()> { + bls12_381::multi_miller_loop(a, b) + } + + /// Compute a final exponentiation on bls12_381 + /// Receives encoded: ArkScale>> + /// Returns encoded:ArkScale>> + fn bls12_381_final_exponentiation(f12: Vec) -> Result, ()> { + bls12_381::final_exponentiation(f12) + } + + /// Compute a projective multiplication on G1 for bls12_381 + /// Receives encoded: + /// base: ArkScaleProjective + /// scalar: ArkScale<&[u64]> + /// Returns encoded: ArkScaleProjective + fn bls12_381_mul_projective_g1(base: Vec, scalar: Vec) -> Result, ()> { + bls12_381::mul_projective_g1(base, scalar) + } + + /// Compute a projective multiplication on G2 for bls12_381 + /// Receives encoded: + /// base: ArkScaleProjective + /// scalar: ArkScale<&[u64]> + /// Returns encoded: ArkScaleProjective + fn bls12_381_mul_projective_g2(base: Vec, scalar: Vec) -> Result, ()> { + bls12_381::mul_projective_g2(base, scalar) + } + + /// Compute a msm on G1 for bls12_381 + /// Receives encoded: + /// bases: ArkScale<&[ark_bls12_381::G1Affine]> + /// scalars: ArkScale<&[ark_bls12_381::Fr]> + /// Returns encoded: ArkScaleProjective + fn bls12_381_msm_g1(bases: Vec, scalars: Vec) -> Result, ()> { + bls12_381::msm_g1(bases, scalars) + } + + /// Compute a msm on G2 for bls12_381 + /// Receives encoded: + /// bases: ArkScale<&[ark_bls12_381::G2Affine]> + /// scalars: ArkScale<&[ark_bls12_381::Fr]> + /// Returns encoded: ArkScaleProjective + fn bls12_381_msm_g2(bases: Vec, scalars: Vec) -> Result, ()> { + bls12_381::msm_g2(bases, scalars) + } + + /// Compute a multi Miller loop on bw6_761 + /// Receives encoded: + /// a: ArkScale>> + /// b: ArkScale>> + /// Returns encoded: ArkScale>> + fn bw6_761_multi_miller_loop(a: Vec, b: Vec) -> Result, ()> { + bw6_761::multi_miller_loop(a, b) + } + + /// Compute a final exponentiation on bw6_761 + /// Receives encoded: ArkScale>> + /// Returns encoded: ArkScale>> + fn bw6_761_final_exponentiation(f12: Vec) -> Result, ()> { + bw6_761::final_exponentiation(f12) + } + + /// Compute a projective multiplication on G1 for bw6_761 + /// Receives encoded: + /// base: ArkScaleProjective + /// scalar: ArkScale<&[u64]> + /// Returns encoded: ArkScaleProjective + fn bw6_761_mul_projective_g1(base: Vec, scalar: Vec) -> Result, ()> { + bw6_761::mul_projective_g1(base, scalar) + } + + /// Compute a projective multiplication on G2 for bw6_761 + /// Receives encoded: + /// base: ArkScaleProjective + /// scalar: ArkScale<&[u64]> + /// Returns encoded: ArkScaleProjective + fn bw6_761_mul_projective_g2(base: Vec, scalar: Vec) -> Result, ()> { + bw6_761::mul_projective_g2(base, scalar) + } + + /// Compute a msm on G1 for bw6_761 + /// Receives encoded: + /// bases: ArkScale<&[ark_bw6_761::G1Affine]> + /// scalars: ArkScale<&[ark_bw6_761::Fr]> + /// Returns encoded: ArkScaleProjective + fn bw6_761_msm_g1(bases: Vec, bigints: Vec) -> Result, ()> { + bw6_761::msm_g1(bases, bigints) + } + + /// Compute a msm on G2 for bw6_761 + /// Receives encoded: + /// bases: ArkScale<&[ark_bw6_761::G2Affine]> + /// scalars: ArkScale<&[ark_bw6_761::Fr]> + /// Returns encoded: ArkScaleProjective + fn bw6_761_msm_g2(bases: Vec, bigints: Vec) -> Result, ()> { + bw6_761::msm_g2(bases, bigints) + } + + /// Compute projective multiplication on ed_on_bls12_377 + /// Receives encoded: + /// base: ArkScaleProjective + /// scalar: ArkScale<&[u64]> + /// Returns encoded: ArkScaleProjective + fn ed_on_bls12_377_mul_projective(base: Vec, scalar: Vec) -> Result, ()> { + ed_on_bls12_377::mul_projective(base, scalar) + } + + /// Compute msm on ed_on_bls12_377 + /// Receives encoded: + /// bases: ArkScale<&[ark_ed_on_bls12_377::EdwardsAffine]> + /// scalars: + /// ArkScale<&[ark_ed_on_bls12_377::Fr]> + /// Returns encoded: + /// ArkScaleProjective + fn ed_on_bls12_377_msm(bases: Vec, scalars: Vec) -> Result, ()> { + ed_on_bls12_377::msm(bases, scalars) + } + + /// Compute short weierstrass projective multiplication on ed_on_bls12_381_bandersnatch + /// Receives encoded: + /// base: ArkScaleProjective + /// scalar: ArkScale<&[u64]> + /// Returns encoded: ArkScaleProjective + fn ed_on_bls12_381_bandersnatch_sw_mul_projective( + base: Vec, + scalar: Vec, + ) -> Result, ()> { + ed_on_bls12_381_bandersnatch::sw_mul_projective(base, scalar) + } + + /// Compute twisted edwards projective multiplication on ed_on_bls12_381_bandersnatch + /// Receives encoded: + /// base: ArkScaleProjective + /// scalar: ArkScale<&[u64]> + /// Returns encoded: ArkScaleProjective + fn ed_on_bls12_381_bandersnatch_te_mul_projective( + base: Vec, + scalar: Vec, + ) -> Result, ()> { + ed_on_bls12_381_bandersnatch::te_mul_projective(base, scalar) + } + + /// Compute short weierstrass msm on ed_on_bls12_381_bandersnatch + /// Receives encoded: + /// bases: ArkScale<&[ark_ed_on_bls12_381_bandersnatch::SWAffine]> + /// scalars: ArkScale<&[ark_ed_on_bls12_381_bandersnatch::Fr]> + /// Returns encoded: + /// ArkScaleProjective + fn ed_on_bls12_381_bandersnatch_sw_msm( + bases: Vec, + scalars: Vec, + ) -> Result, ()> { + ed_on_bls12_381_bandersnatch::sw_msm(bases, scalars) + } + + /// Compute twisted edwards msm on ed_on_bls12_381_bandersnatch + /// Receives encoded: + /// base: ArkScaleProjective + /// scalars: ArkScale<&[ark_ed_on_bls12_381_bandersnatch::Fr]> + /// Returns encoded: + /// ArkScaleProjective + fn ed_on_bls12_381_bandersnatch_te_msm( + bases: Vec, + scalars: Vec, + ) -> Result, ()> { + ed_on_bls12_381_bandersnatch::te_msm(bases, scalars) + } +} diff --git a/primitives/crypto/ec-utils/src/test-data/g1_compressed_valid_test_vectors.dat b/primitives/crypto/ec-utils/src/test-data/g1_compressed_valid_test_vectors.dat new file mode 100644 index 000000000000..ea8cd67652d1 Binary files /dev/null and b/primitives/crypto/ec-utils/src/test-data/g1_compressed_valid_test_vectors.dat differ diff --git a/primitives/crypto/ec-utils/src/test-data/g1_uncompressed_valid_test_vectors.dat b/primitives/crypto/ec-utils/src/test-data/g1_uncompressed_valid_test_vectors.dat new file mode 100644 index 000000000000..86abfba945c7 Binary files /dev/null and b/primitives/crypto/ec-utils/src/test-data/g1_uncompressed_valid_test_vectors.dat differ diff --git a/primitives/crypto/ec-utils/src/test-data/g2_compressed_valid_test_vectors.dat b/primitives/crypto/ec-utils/src/test-data/g2_compressed_valid_test_vectors.dat new file mode 100644 index 000000000000..a40bbe251d90 Binary files /dev/null and b/primitives/crypto/ec-utils/src/test-data/g2_compressed_valid_test_vectors.dat differ diff --git a/primitives/crypto/ec-utils/src/test-data/g2_uncompressed_valid_test_vectors.dat b/primitives/crypto/ec-utils/src/test-data/g2_uncompressed_valid_test_vectors.dat new file mode 100644 index 000000000000..92e4bc528e89 Binary files /dev/null and b/primitives/crypto/ec-utils/src/test-data/g2_uncompressed_valid_test_vectors.dat differ diff --git a/primitives/crypto/ec-utils/src/utils.rs b/primitives/crypto/ec-utils/src/utils.rs new file mode 100644 index 000000000000..5560d5921160 --- /dev/null +++ b/primitives/crypto/ec-utils/src/utils.rs @@ -0,0 +1,130 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! The generic executions of the operations on arkworks elliptic curves +//! which get instantiatied by the corresponding curves. +use ark_ec::{ + pairing::{MillerLoopOutput, Pairing, PairingOutput}, + short_weierstrass, + short_weierstrass::SWCurveConfig, + twisted_edwards, + twisted_edwards::TECurveConfig, + CurveConfig, VariableBaseMSM, +}; +use ark_scale::hazmat::ArkScaleProjective; +use ark_std::vec::Vec; +use codec::{Decode, Encode}; + +const HOST_CALL: ark_scale::Usage = ark_scale::HOST_CALL; +type ArkScale = ark_scale::ArkScale; + +pub(crate) fn multi_miller_loop_generic( + g1: Vec, + g2: Vec, +) -> Result, ()> { + let g1 = ::G1Affine>> as Decode>::decode(&mut g1.as_slice()) + .map_err(|_| ())?; + let g2 = ::G2Affine>> as Decode>::decode(&mut g2.as_slice()) + .map_err(|_| ())?; + + let result = Curve::multi_miller_loop(g1.0, g2.0).0; + + let result: ArkScale<::TargetField> = result.into(); + Ok(result.encode()) +} + +pub(crate) fn final_exponentiation_generic(target: Vec) -> Result, ()> { + let target = + ::TargetField> as Decode>::decode(&mut target.as_slice()) + .map_err(|_| ())?; + + let result = Curve::final_exponentiation(MillerLoopOutput(target.0)).ok_or(())?; + + let result: ArkScale> = result.into(); + Ok(result.encode()) +} + +pub(crate) fn msm_sw_generic( + bases: Vec, + scalars: Vec, +) -> Result, ()> { + let bases = + >> as Decode>::decode(&mut bases.as_slice()) + .map_err(|_| ())?; + let scalars = ::ScalarField>> as Decode>::decode( + &mut scalars.as_slice(), + ) + .map_err(|_| ())?; + + let result = + as VariableBaseMSM>::msm(&bases.0, &scalars.0) + .map_err(|_| ())?; + + let result: ArkScaleProjective> = result.into(); + Ok(result.encode()) +} + +pub(crate) fn msm_te_generic( + bases: Vec, + scalars: Vec, +) -> Result, ()> { + let bases = + >> as Decode>::decode(&mut bases.as_slice()) + .map_err(|_| ())?; + let scalars = ::ScalarField>> as Decode>::decode( + &mut scalars.as_slice(), + ) + .map_err(|_| ())?; + + let result = as VariableBaseMSM>::msm(&bases.0, &scalars.0) + .map_err(|_| ())?; + + let result: ArkScaleProjective> = result.into(); + Ok(result.encode()) +} + +pub(crate) fn mul_projective_generic( + base: Vec, + scalar: Vec, +) -> Result, ()> { + let base = > as Decode>::decode( + &mut base.as_slice(), + ) + .map_err(|_| ())?; + let scalar = > as Decode>::decode(&mut scalar.as_slice()).map_err(|_| ())?; + + let result = ::mul_projective(&base.0, &scalar.0); + + let result: ArkScaleProjective> = result.into(); + Ok(result.encode()) +} + +pub(crate) fn mul_projective_te_generic( + base: Vec, + scalar: Vec, +) -> Result, ()> { + let base = > as Decode>::decode( + &mut base.as_slice(), + ) + .map_err(|_| ())?; + let scalar = > as Decode>::decode(&mut scalar.as_slice()).map_err(|_| ())?; + + let result = ::mul_projective(&base.0, &scalar.0); + + let result: ArkScaleProjective> = result.into(); + Ok(result.encode()) +} diff --git a/primitives/database/src/error.rs b/primitives/database/src/error.rs index 78646427a2b5..0cc1159b21e7 100644 --- a/primitives/database/src/error.rs +++ b/primitives/database/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/database/src/kvdb.rs b/primitives/database/src/kvdb.rs index 5fe5fda307a1..735813c36857 100644 --- a/primitives/database/src/kvdb.rs +++ b/primitives/database/src/kvdb.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/database/src/lib.rs b/primitives/database/src/lib.rs index 3ec62a2b7814..012f699552d7 100644 --- a/primitives/database/src/lib.rs +++ b/primitives/database/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/database/src/mem.rs b/primitives/database/src/mem.rs index 6ecd44c6005b..71ba7a992763 100644 --- a/primitives/database/src/mem.rs +++ b/primitives/database/src/mem.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/debug-derive/Cargo.toml b/primitives/debug-derive/Cargo.toml index a903b704410c..bbac79a84650 100644 --- a/primitives/debug-derive/Cargo.toml +++ b/primitives/debug-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-debug-derive" -version = "5.0.0" +version = "8.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -17,9 +17,9 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -quote = "1.0.10" -syn = "1.0.98" -proc-macro2 = "1.0" +quote = "1.0.28" +syn = "2.0.16" +proc-macro2 = "1.0.56" [features] default = [ "std" ] diff --git a/primitives/debug-derive/src/impls.rs b/primitives/debug-derive/src/impls.rs index 51a4d876c79b..76ef8367277b 100644 --- a/primitives/debug-derive/src/impls.rs +++ b/primitives/debug-derive/src/impls.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/debug-derive/src/lib.rs b/primitives/debug-derive/src/lib.rs index c98610ce4780..639dbb6df189 100644 --- a/primitives/debug-derive/src/lib.rs +++ b/primitives/debug-derive/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/debug-derive/tests/tests.rs b/primitives/debug-derive/tests/tests.rs index 39414da86bf4..da521068e0dc 100644 --- a/primitives/debug-derive/tests/tests.rs +++ b/primitives/debug-derive/tests/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/externalities/Cargo.toml b/primitives/externalities/Cargo.toml index 0777111d88c2..71dbd122bebe 100644 --- a/primitives/externalities/Cargo.toml +++ b/primitives/externalities/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-externalities" -version = "0.13.0" +version = "0.19.0" license = "Apache-2.0" authors = ["Parity Technologies "] edition = "2021" @@ -14,10 +14,10 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } environmental = { version = "1.1.3", default-features = false } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } -sp-storage = { version = "7.0.0", default-features = false, path = "../storage" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } +sp-storage = { version = "13.0.0", default-features = false, path = "../storage" } [features] default = ["std"] diff --git a/primitives/externalities/src/extensions.rs b/primitives/externalities/src/extensions.rs index ecb489e5ec82..8b0bbd2c5921 100644 --- a/primitives/externalities/src/extensions.rs +++ b/primitives/externalities/src/extensions.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,6 +42,12 @@ pub trait Extension: Send + Any { fn as_mut_any(&mut self) -> &mut dyn Any; } +impl Extension for Box { + fn as_mut_any(&mut self) -> &mut dyn Any { + (**self).as_mut_any() + } +} + /// Macro for declaring an extension that usable with [`Extensions`]. /// /// The extension will be an unit wrapper struct that implements [`Extension`], `Deref` and @@ -190,6 +196,14 @@ impl Extensions { pub fn iter_mut(&mut self) -> impl Iterator)> { self.extensions.iter_mut() } + + /// Merge `other` into `self`. + /// + /// If both contain the same extension, the extension instance of `other` will overwrite the + /// instance found in `self`. + pub fn merge(&mut self, other: Self) { + self.extensions.extend(other.extensions); + } } impl Extend for Extensions { diff --git a/primitives/externalities/src/lib.rs b/primitives/externalities/src/lib.rs index b343b8c393e0..411ec97a6b82 100644 --- a/primitives/externalities/src/lib.rs +++ b/primitives/externalities/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/externalities/src/scope_limited.rs b/primitives/externalities/src/scope_limited.rs index 1d2e6863c9e5..2167db7c1528 100644 --- a/primitives/externalities/src/scope_limited.rs +++ b/primitives/externalities/src/scope_limited.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/finality-grandpa/Cargo.toml b/primitives/finality-grandpa/Cargo.toml deleted file mode 100644 index 39bb6713a239..000000000000 --- a/primitives/finality-grandpa/Cargo.toml +++ /dev/null @@ -1,43 +0,0 @@ -[package] -name = "sp-finality-grandpa" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2021" -license = "Apache-2.0" -homepage = "https://substrate.io" -repository = "https://github.com/paritytech/substrate/" -description = "Primitives for GRANDPA integration, suitable for WASM compilation." -documentation = "https://docs.rs/sp-finality-grandpa" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -grandpa = { package = "finality-grandpa", version = "0.16.1", default-features = false, features = ["derive-codec"] } -log = { version = "0.4.17", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", features = ["derive"], optional = true } -sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-application-crypto = { version = "7.0.0", default-features = false, path = "../application-crypto" } -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-keystore = { version = "0.13.0", default-features = false, optional = true, path = "../keystore" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } - -[features] -default = ["std"] -std = [ - "codec/std", - "grandpa/std", - "log/std", - "scale-info/std", - "serde", - "sp-api/std", - "sp-application-crypto/std", - "sp-core/std", - "sp-keystore", - "sp-runtime/std", - "sp-std/std", -] diff --git a/primitives/genesis-builder/Cargo.toml b/primitives/genesis-builder/Cargo.toml new file mode 100644 index 000000000000..de543f0a74ac --- /dev/null +++ b/primitives/genesis-builder/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "sp-genesis-builder" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Substrate GenesisConfig builder API" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +serde_json = { version = "1.0.85", default-features = false, features = ["alloc"] } + +[features] +default = [ "std" ] +std = [ + "sp-api/std", + "sp-std/std", + "serde_json/std", +] diff --git a/primitives/genesis-builder/README.md b/primitives/genesis-builder/README.md new file mode 100644 index 000000000000..4a842c95e358 --- /dev/null +++ b/primitives/genesis-builder/README.md @@ -0,0 +1,5 @@ +Substrate genesis builder. + +Refer to the module doc for more details. + +License: Apache-2.0 diff --git a/primitives/genesis-builder/src/lib.rs b/primitives/genesis-builder/src/lib.rs new file mode 100644 index 000000000000..e002cd3aa6f7 --- /dev/null +++ b/primitives/genesis-builder/src/lib.rs @@ -0,0 +1,54 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +//! Substrate genesis config builder +//! +//! This Runtime API allows to construct `GenesisConfig`, in particular: +//! - serialize the runtime default `GenesisConfig` struct into json format, +//! - put the GenesisConfig struct into the storage. Internally this operation calls +//! `GenesisBuild::build` function for all runtime pallets, which is typically provided by +//! pallet's author. +//! - deserialize the `GenesisConfig` from given json blob and put `GenesisConfig` into the state +//! storage. Allows to build customized configuration. +//! +//! Providing externalities with empty storage and putting `GenesisConfig` into storage allows to +//! catch and build the raw storage of `GenesisConfig` which is the foundation for genesis block. + +/// The result type alias, used in build methods. `Err` contains formatted error message. +pub type Result = core::result::Result<(), sp_runtime::RuntimeString>; + +sp_api::decl_runtime_apis! { + /// API to interact with GenesisConfig for the runtime + pub trait GenesisBuilder { + /// Creates the default `GenesisConfig` and returns it as a JSON blob. + /// + /// This function instantiates the default `GenesisConfig` struct for the runtime and serializes it into a JSON + /// blob. It returns a `Vec` containing the JSON representation of the default `GenesisConfig`. + fn create_default_config() -> sp_std::vec::Vec; + + /// Build `GenesisConfig` from a JSON blob not using any defaults and store it in the storage. + /// + /// This function deserializes the full `GenesisConfig` from the given JSON blob and puts it into the storage. + /// If the provided JSON blob is incorrect or incomplete or the deserialization fails, an error is returned. + /// It is recommended to log any errors encountered during the process. + /// + /// Please note that provided json blob must contain all `GenesisConfig` fields, no defaults will be used. + fn build_config(json: sp_std::vec::Vec) -> Result; + } +} diff --git a/primitives/inherents/Cargo.toml b/primitives/inherents/Cargo.toml index c901a6e68a18..d42588ad21a3 100644 --- a/primitives/inherents/Cargo.toml +++ b/primitives/inherents/Cargo.toml @@ -15,12 +15,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = { version = "0.1.57", optional = true } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" thiserror = { version = "1.0.30", optional = true } -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-runtime = { version = "7.0.0", optional = true, default-features = false, path = "../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-runtime = { version = "24.0.0", optional = true, default-features = false, path = "../runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } [dev-dependencies] futures = "0.3.21" @@ -30,7 +30,7 @@ default = [ "std" ] std = [ "async-trait", "codec/std", - "sp-core/std", + "scale-info/std", "sp-runtime/std", "sp-std/std", "thiserror", diff --git a/primitives/inherents/src/client_side.rs b/primitives/inherents/src/client_side.rs index 1ece7e1e4dea..27479de136f2 100644 --- a/primitives/inherents/src/client_side.rs +++ b/primitives/inherents/src/client_side.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/inherents/src/lib.rs b/primitives/inherents/src/lib.rs index 59db5ef3e880..dd7c294f1e24 100644 --- a/primitives/inherents/src/lib.rs +++ b/primitives/inherents/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -204,7 +204,7 @@ pub enum Error { pub type InherentIdentifier = [u8; 8]; /// Inherent data to include in a block. -#[derive(Clone, Default, Encode, Decode)] +#[derive(Clone, Default, Encode, Decode, scale_info::TypeInfo)] pub struct InherentData { /// All inherent data encoded with parity-scale-codec and an identifier. data: BTreeMap>, @@ -276,7 +276,7 @@ impl InherentData { /// /// When a fatal error occurs, all other errors are removed and the implementation needs to /// abort checking inherents. -#[derive(Encode, Decode, Clone)] +#[derive(Encode, Decode, Clone, scale_info::TypeInfo)] pub struct CheckInherentsResult { /// Did the check succeed? okay: bool, diff --git a/primitives/io/Cargo.toml b/primitives/io/Cargo.toml index e56bfcf56041..55ba753d5988 100644 --- a/primitives/io/Cargo.toml +++ b/primitives/io/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-io" -version = "7.0.0" +version = "23.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -9,6 +9,7 @@ repository = "https://github.com/paritytech/substrate/" description = "I/O for Substrate runtimes" documentation = "https://docs.rs/sp-io" readme = "README.md" +build = "build.rs" [package.metadata.docs.rs] targets = ["x86_64-unknown-linux-gnu"] @@ -16,18 +17,17 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] bytes = { version = "1.1.0", default-features = false } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["bytes"] } -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-keystore = { version = "0.13.0", default-features = false, optional = true, path = "../keystore" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["bytes"] } +sp-core = { version = "21.0.0", default-features = false, path = "../core" } +sp-keystore = { version = "0.27.0", default-features = false, optional = true, path = "../keystore" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } libsecp256k1 = { version = "0.7", optional = true } -sp-state-machine = { version = "0.13.0", default-features = false, optional = true, path = "../state-machine" } -sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../runtime-interface" } -sp-trie = { version = "7.0.0", default-features = false, optional = true, path = "../trie" } -sp-externalities = { version = "0.13.0", default-features = false, path = "../externalities" } -sp-tracing = { version = "6.0.0", default-features = false, path = "../tracing" } +sp-state-machine = { version = "0.28.0", default-features = false, optional = true, path = "../state-machine" } +sp-runtime-interface = { version = "17.0.0", default-features = false, path = "../runtime-interface" } +sp-trie = { version = "22.0.0", default-features = false, optional = true, path = "../trie" } +sp-externalities = { version = "0.19.0", default-features = false, path = "../externalities" } +sp-tracing = { version = "10.0.0", default-features = false, path = "../tracing" } log = { version = "0.4.17", optional = true } -futures = { version = "0.3.21", features = ["thread-pool"], optional = true } secp256k1 = { version = "0.24.0", features = ["recovery", "global-context"], optional = true } tracing = { version = "0.1.29", default-features = false } tracing-core = { version = "0.1.28", default-features = false} @@ -37,6 +37,9 @@ ed25519-dalek = { version = "1.0.1", default-features = false, optional = true } # Force the usage of ed25519, this is being used in `ed25519-dalek`. ed25519 = { version = "1.5.2", optional = true } +[build-dependencies] +rustversion = "1.0.6" + [features] default = ["std"] std = [ @@ -55,7 +58,6 @@ std = [ "tracing/std", "tracing-core/std", "log", - "futures", "ed25519-dalek", "ed25519", ] diff --git a/client/beefy/src/error.rs b/primitives/io/build.rs similarity index 63% rename from client/beefy/src/error.rs rename to primitives/io/build.rs index dd5fd649d52c..8a9c0b6420b2 100644 --- a/client/beefy/src/error.rs +++ b/primitives/io/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 @@ -16,20 +16,12 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . -//! BEEFY gadget specific errors -//! -//! Used for BEEFY gadget interal error handling only - -use std::fmt::Debug; - -#[derive(Debug, thiserror::Error, PartialEq)] -pub enum Error { - #[error("Backend: {0}")] - Backend(String), - #[error("Keystore error: {0}")] - Keystore(String), - #[error("Signature error: {0}")] - Signature(String), - #[error("Session uninitialized")] - UninitSession, +#[rustversion::before(1.68)] +fn main() { + if !cfg!(feature = "std") { + println!("cargo:rustc-cfg=enable_alloc_error_handler"); + } } + +#[rustversion::since(1.68)] +fn main() {} diff --git a/primitives/io/src/batch_verifier.rs b/primitives/io/src/batch_verifier.rs deleted file mode 100644 index 501d986758bf..000000000000 --- a/primitives/io/src/batch_verifier.rs +++ /dev/null @@ -1,211 +0,0 @@ -// 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. - -//! Batch/parallel verification. - -use futures::{channel::oneshot, future::FutureExt}; -use sp_core::{crypto::Pair, ecdsa, ed25519, sr25519, traits::SpawnNamed}; -use std::sync::{ - atomic::{AtomicBool, Ordering as AtomicOrdering}, - Arc, -}; - -#[derive(Debug, Clone)] -struct Sr25519BatchItem { - signature: sr25519::Signature, - pub_key: sr25519::Public, - message: Vec, -} - -/// Batch verifier. -/// -/// Used to parallel-verify signatures for runtime host. Provide task executor and -/// just push (`push_ed25519`, `push_sr25519`) as many signature as you need. At the end, -/// call `verify_and_clear to get a result. After that, batch verifier is ready for the -/// next batching job. -pub struct BatchVerifier { - scheduler: Box, - sr25519_items: Vec, - invalid: Arc, - pending_tasks: Vec>, -} - -impl BatchVerifier { - pub fn new(scheduler: Box) -> Self { - BatchVerifier { - scheduler, - sr25519_items: Default::default(), - invalid: Arc::new(false.into()), - pending_tasks: vec![], - } - } - - /// Spawn a verification task. - /// - /// Returns `false` if there was already an invalid verification or if - /// the verification could not be spawned. - fn spawn_verification_task( - &mut self, - f: impl FnOnce() -> bool + Send + 'static, - name: &'static str, - ) -> bool { - // there is already invalid transaction encountered - if self.invalid.load(AtomicOrdering::Relaxed) { - return false - } - - let invalid_clone = self.invalid.clone(); - let (sender, receiver) = oneshot::channel(); - self.pending_tasks.push(receiver); - - self.scheduler.spawn( - name, - None, - async move { - if !f() { - invalid_clone.store(true, AtomicOrdering::Relaxed); - } - if sender.send(()).is_err() { - // sanity - log::warn!("Verification halted while result was pending"); - invalid_clone.store(true, AtomicOrdering::Relaxed); - } - } - .boxed(), - ); - - true - } - - /// Push ed25519 signature to verify. - /// - /// Returns false if some of the pushed signatures before already failed the check - /// (in this case it won't verify anything else) - pub fn push_ed25519( - &mut self, - signature: ed25519::Signature, - pub_key: ed25519::Public, - message: Vec, - ) -> bool { - self.spawn_verification_task( - move || ed25519::Pair::verify(&signature, &message, &pub_key), - "substrate_ed25519_verify", - ) - } - - /// Push sr25519 signature to verify. - /// - /// Returns false if some of the pushed signatures before already failed the check. - /// (in this case it won't verify anything else) - pub fn push_sr25519( - &mut self, - signature: sr25519::Signature, - pub_key: sr25519::Public, - message: Vec, - ) -> bool { - if self.invalid.load(AtomicOrdering::Relaxed) { - return false - } - self.sr25519_items.push(Sr25519BatchItem { signature, pub_key, message }); - - if self.sr25519_items.len() >= 128 { - let items = std::mem::take(&mut self.sr25519_items); - self.spawn_verification_task( - move || Self::verify_sr25519_batch(items), - "substrate_sr25519_verify", - ) - } else { - true - } - } - - /// Push ecdsa signature to verify. - /// - /// Returns false if some of the pushed signatures before already failed the check - /// (in this case it won't verify anything else) - pub fn push_ecdsa( - &mut self, - signature: ecdsa::Signature, - pub_key: ecdsa::Public, - message: Vec, - ) -> bool { - self.spawn_verification_task( - move || ecdsa::Pair::verify(&signature, &message, &pub_key), - "substrate_ecdsa_verify", - ) - } - - fn verify_sr25519_batch(items: Vec) -> bool { - let messages = items.iter().map(|item| &item.message[..]).collect(); - let signatures = items.iter().map(|item| &item.signature).collect(); - let pub_keys = items.iter().map(|item| &item.pub_key).collect(); - - sr25519::verify_batch(messages, signatures, pub_keys) - } - - /// Verify all previously pushed signatures since last call and return - /// aggregated result. - #[must_use] - pub fn verify_and_clear(&mut self) -> bool { - let pending = std::mem::take(&mut self.pending_tasks); - let started = std::time::Instant::now(); - - log::trace!( - target: "runtime", - "Batch-verification: {} pending tasks, {} sr25519 signatures", - pending.len(), - self.sr25519_items.len(), - ); - - if !Self::verify_sr25519_batch(std::mem::take(&mut self.sr25519_items)) { - return false - } - - if pending.len() > 0 { - let (sender, receiver) = std::sync::mpsc::channel(); - self.scheduler.spawn( - "substrate-batch-verify-join", - None, - async move { - futures::future::join_all(pending).await; - sender.send(()).expect( - "Channel never panics if receiver is live. \ - Receiver is always live until received this data; qed. ", - ); - } - .boxed(), - ); - - if receiver.recv().is_err() { - log::warn!( - target: "runtime", - "Haven't received async result from verification task. Returning false.", - ); - - return false - } - } - - log::trace!( - target: "runtime", - "Finalization of batch verification took {} ms", - started.elapsed().as_millis(), - ); - - !self.invalid.swap(false, AtomicOrdering::Relaxed) - } -} diff --git a/primitives/io/src/lib.rs b/primitives/io/src/lib.rs index bb06c00ee2c6..bd20ee0c917a 100644 --- a/primitives/io/src/lib.rs +++ b/primitives/io/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] -#![cfg_attr(not(feature = "std"), feature(alloc_error_handler))] +#![cfg_attr(enable_alloc_error_handler, feature(alloc_error_handler))] #![cfg_attr( feature = "std", doc = "Substrate runtime standard library as compiled when linked with Rust's standard library." @@ -40,10 +40,9 @@ use sp_core::{ hexdisplay::HexDisplay, offchain::{OffchainDbExt, OffchainWorkerExt, TransactionPoolExt}, storage::ChildInfo, - traits::TaskExecutorExt, }; #[cfg(feature = "std")] -use sp_keystore::{KeystoreExt, SyncCryptoStore}; +use sp_keystore::KeystoreExt; use sp_core::{ crypto::KeyTypeId, @@ -75,12 +74,6 @@ use secp256k1::{ #[cfg(feature = "std")] use sp_externalities::{Externalities, ExternalitiesExt}; -#[cfg(feature = "std")] -mod batch_verifier; - -#[cfg(feature = "std")] -use batch_verifier::BatchVerifier; - pub use sp_externalities::MultiRemovalResults; #[cfg(feature = "std")] @@ -731,10 +724,9 @@ impl Default for UseDalekExt { pub trait Crypto { /// Returns all `ed25519` public keys for the given key id from the keystore. fn ed25519_public_keys(&mut self, id: KeyTypeId) -> Vec { - let keystore = &***self - .extension::() - .expect("No `keystore` associated for the current context!"); - SyncCryptoStore::ed25519_public_keys(keystore, id) + self.extension::() + .expect("No `keystore` associated for the current context!") + .ed25519_public_keys(id) } /// Generate an `ed22519` key for the given key type using an optional `seed` and @@ -745,10 +737,9 @@ pub trait Crypto { /// Returns the public key. fn ed25519_generate(&mut self, id: KeyTypeId, seed: Option>) -> ed25519::Public { let seed = seed.as_ref().map(|s| std::str::from_utf8(s).expect("Seed is valid utf8!")); - let keystore = &***self - .extension::() - .expect("No `keystore` associated for the current context!"); - SyncCryptoStore::ed25519_generate_new(keystore, id, seed) + self.extension::() + .expect("No `keystore` associated for the current context!") + .ed25519_generate_new(id, seed) .expect("`ed25519_generate` failed") } @@ -762,13 +753,11 @@ pub trait Crypto { pub_key: &ed25519::Public, msg: &[u8], ) -> Option { - let keystore = &***self - .extension::() - .expect("No `keystore` associated for the current context!"); - SyncCryptoStore::sign_with(keystore, id, &pub_key.into(), msg) + self.extension::() + .expect("No `keystore` associated for the current context!") + .ed25519_sign(id, pub_key, msg) .ok() .flatten() - .and_then(|sig| ed25519::Signature::from_slice(&sig)) } /// Verify `ed25519` signature. @@ -787,9 +776,7 @@ pub trait Crypto { return false }; - let Ok(sig) = ed25519_dalek::Signature::from_bytes(&sig.0) else { - return false - }; + let Ok(sig) = ed25519_dalek::Signature::from_bytes(&sig.0) else { return false }; public_key.verify(msg, &sig).is_ok() } else { @@ -805,15 +792,25 @@ pub trait Crypto { /// needs to be called. /// /// Returns `true` when the verification is either successful or batched. + /// + /// NOTE: Is tagged with `register_only` to keep the functions around for backwards + /// compatibility with old runtimes, but it should not be used anymore by new runtimes. + /// The implementation emulates the old behavior, but isn't doing any batch verification + /// anymore. + #[version(1, register_only)] fn ed25519_batch_verify( &mut self, sig: &ed25519::Signature, msg: &[u8], pub_key: &ed25519::Public, ) -> bool { - self.extension::() - .map(|extension| extension.push_ed25519(sig.clone(), *pub_key, msg.to_vec())) - .unwrap_or_else(|| ed25519_verify(sig, msg, pub_key)) + let res = ed25519_verify(sig, msg, pub_key); + + if let Some(ext) = self.extension::() { + ext.0 &= res; + } + + res } /// Verify `sr25519` signature. @@ -832,25 +829,36 @@ pub trait Crypto { /// needs to be called. /// /// Returns `true` when the verification is either successful or batched. + /// + /// NOTE: Is tagged with `register_only` to keep the functions around for backwards + /// compatibility with old runtimes, but it should not be used anymore by new runtimes. + /// The implementation emulates the old behavior, but isn't doing any batch verification + /// anymore. + #[version(1, register_only)] fn sr25519_batch_verify( &mut self, sig: &sr25519::Signature, msg: &[u8], pub_key: &sr25519::Public, ) -> bool { - self.extension::() - .map(|extension| extension.push_sr25519(sig.clone(), *pub_key, msg.to_vec())) - .unwrap_or_else(|| sr25519_verify(sig, msg, pub_key)) + let res = sr25519_verify(sig, msg, pub_key); + + if let Some(ext) = self.extension::() { + ext.0 &= res; + } + + res } /// Start verification extension. + /// + /// NOTE: Is tagged with `register_only` to keep the functions around for backwards + /// compatibility with old runtimes, but it should not be used anymore by new runtimes. + /// The implementation emulates the old behavior, but isn't doing any batch verification + /// anymore. + #[version(1, register_only)] fn start_batch_verify(&mut self) { - let scheduler = self - .extension::() - .expect("No task executor associated with the current context!") - .clone(); - - self.register_extension(VerificationExt(BatchVerifier::new(scheduler))) + self.register_extension(VerificationExtDeprecated(true)) .expect("Failed to register required extension: `VerificationExt`"); } @@ -860,13 +868,19 @@ pub trait Crypto { /// deferred by `sr25519_verify`/`ed25519_verify`. /// /// Will panic if no `VerificationExt` is registered (`start_batch_verify` was not called). + /// + /// NOTE: Is tagged with `register_only` to keep the functions around for backwards + /// compatibility with old runtimes, but it should not be used anymore by new runtimes. + /// The implementation emulates the old behavior, but isn't doing any batch verification + /// anymore. + #[version(1, register_only)] fn finish_batch_verify(&mut self) -> bool { let result = self - .extension::() + .extension::() .expect("`finish_batch_verify` should only be called after `start_batch_verify`") - .verify_and_clear(); + .0; - self.deregister_extension::() + self.deregister_extension::() .expect("No verification extension in current context!"); result @@ -874,10 +888,9 @@ pub trait Crypto { /// Returns all `sr25519` public keys for the given key id from the keystore. fn sr25519_public_keys(&mut self, id: KeyTypeId) -> Vec { - let keystore = &***self - .extension::() - .expect("No `keystore` associated for the current context!"); - SyncCryptoStore::sr25519_public_keys(keystore, id) + self.extension::() + .expect("No `keystore` associated for the current context!") + .sr25519_public_keys(id) } /// Generate an `sr22519` key for the given key type using an optional seed and @@ -888,10 +901,9 @@ pub trait Crypto { /// Returns the public key. fn sr25519_generate(&mut self, id: KeyTypeId, seed: Option>) -> sr25519::Public { let seed = seed.as_ref().map(|s| std::str::from_utf8(s).expect("Seed is valid utf8!")); - let keystore = &***self - .extension::() - .expect("No `keystore` associated for the current context!"); - SyncCryptoStore::sr25519_generate_new(keystore, id, seed) + self.extension::() + .expect("No `keystore` associated for the current context!") + .sr25519_generate_new(id, seed) .expect("`sr25519_generate` failed") } @@ -905,13 +917,11 @@ pub trait Crypto { pub_key: &sr25519::Public, msg: &[u8], ) -> Option { - let keystore = &***self - .extension::() - .expect("No `keystore` associated for the current context!"); - SyncCryptoStore::sign_with(keystore, id, &pub_key.into(), msg) + self.extension::() + .expect("No `keystore` associated for the current context!") + .sr25519_sign(id, pub_key, msg) .ok() .flatten() - .and_then(|sig| sr25519::Signature::from_slice(&sig)) } /// Verify an `sr25519` signature. @@ -924,10 +934,9 @@ pub trait Crypto { /// Returns all `ecdsa` public keys for the given key id from the keystore. fn ecdsa_public_keys(&mut self, id: KeyTypeId) -> Vec { - let keystore = &***self - .extension::() - .expect("No `keystore` associated for the current context!"); - SyncCryptoStore::ecdsa_public_keys(keystore, id) + self.extension::() + .expect("No `keystore` associated for the current context!") + .ecdsa_public_keys(id) } /// Generate an `ecdsa` key for the given key type using an optional `seed` and @@ -938,10 +947,10 @@ pub trait Crypto { /// Returns the public key. fn ecdsa_generate(&mut self, id: KeyTypeId, seed: Option>) -> ecdsa::Public { let seed = seed.as_ref().map(|s| std::str::from_utf8(s).expect("Seed is valid utf8!")); - let keystore = &***self - .extension::() - .expect("No `keystore` associated for the current context!"); - SyncCryptoStore::ecdsa_generate_new(keystore, id, seed).expect("`ecdsa_generate` failed") + self.extension::() + .expect("No `keystore` associated for the current context!") + .ecdsa_generate_new(id, seed) + .expect("`ecdsa_generate` failed") } /// Sign the given `msg` with the `ecdsa` key that corresponds to the given public key and @@ -954,13 +963,11 @@ pub trait Crypto { pub_key: &ecdsa::Public, msg: &[u8], ) -> Option { - let keystore = &***self - .extension::() - .expect("No `keystore` associated for the current context!"); - SyncCryptoStore::sign_with(keystore, id, &pub_key.into(), msg) + self.extension::() + .expect("No `keystore` associated for the current context!") + .ecdsa_sign(id, pub_key, msg) .ok() .flatten() - .and_then(|sig| ecdsa::Signature::from_slice(&sig)) } /// Sign the given a pre-hashed `msg` with the `ecdsa` key that corresponds to the given public @@ -973,10 +980,11 @@ pub trait Crypto { pub_key: &ecdsa::Public, msg: &[u8; 32], ) -> Option { - let keystore = &***self - .extension::() - .expect("No `keystore` associated for the current context!"); - SyncCryptoStore::ecdsa_sign_prehashed(keystore, id, pub_key, msg).ok().flatten() + self.extension::() + .expect("No `keystore` associated for the current context!") + .ecdsa_sign_prehashed(id, pub_key, msg) + .ok() + .flatten() } /// Verify `ecdsa` signature. @@ -1015,15 +1023,25 @@ pub trait Crypto { /// needs to be called. /// /// Returns `true` when the verification is either successful or batched. + /// + /// NOTE: Is tagged with `register_only` to keep the functions around for backwards + /// compatibility with old runtimes, but it should not be used anymore by new runtimes. + /// The implementation emulates the old behavior, but isn't doing any batch verification + /// anymore. + #[version(1, register_only)] fn ecdsa_batch_verify( &mut self, sig: &ecdsa::Signature, msg: &[u8], pub_key: &ecdsa::Public, ) -> bool { - self.extension::() - .map(|extension| extension.push_ecdsa(sig.clone(), *pub_key, msg.to_vec())) - .unwrap_or_else(|| ecdsa_verify(sig, msg, pub_key)) + let res = ecdsa_verify(sig, msg, pub_key); + + if let Some(ext) = self.extension::() { + ext.0 &= res; + } + + res } /// Verify and recover a SECP256k1 ECDSA signature. @@ -1196,8 +1214,10 @@ pub trait OffchainIndex { #[cfg(feature = "std")] sp_externalities::decl_extension! { - /// Batch verification extension to register/retrieve from the externalities. - pub struct VerificationExt(BatchVerifier); + /// Deprecated verification context. + /// + /// Stores the combined result of all verifications that are done in the same context. + struct VerificationExtDeprecated(bool); } /// Interface that provides functions to access the offchain functionality. @@ -1386,7 +1406,7 @@ pub trait Offchain { /// Read all response headers. /// /// Returns a vector of pairs `(HeaderKey, HeaderValue)`. - /// NOTE response headers have to be read before response body. + /// NOTE: response headers have to be read before response body. fn http_response_headers(&mut self, request_id: HttpRequestId) -> Vec<(Vec, Vec)> { self.extension::() .expect("http_response_headers can be called only in the offchain worker context") @@ -1399,7 +1419,7 @@ pub trait Offchain { /// is reached or server closed the connection. /// If `0` is returned it means that the response has been fully consumed /// and the `request_id` is now invalid. - /// NOTE this implies that response headers must be read before draining the body. + /// NOTE: this implies that response headers must be read before draining the body. /// Passing `None` as a deadline blocks forever. fn http_response_read_body( &mut self, @@ -1653,7 +1673,7 @@ pub fn panic(info: &core::panic::PanicInfo) -> ! { } /// A default OOM handler for WASM environment. -#[cfg(all(not(feature = "disable_oom"), not(feature = "std")))] +#[cfg(all(not(feature = "disable_oom"), enable_alloc_error_handler))] #[alloc_error_handler] pub fn oom(_: core::alloc::Layout) -> ! { #[cfg(feature = "improved_panic_error_reporting")] @@ -1694,12 +1714,8 @@ pub type SubstrateHostFunctions = ( #[cfg(test)] mod tests { use super::*; - use sp_core::{ - crypto::UncheckedInto, map, storage::Storage, testing::TaskExecutor, - traits::TaskExecutorExt, - }; + use sp_core::{crypto::UncheckedInto, map, storage::Storage}; use sp_state_machine::BasicExternalities; - use std::any::TypeId; #[test] fn storage_works() { @@ -1791,54 +1807,6 @@ mod tests { }); } - #[test] - fn batch_verify_start_finish_works() { - let mut ext = BasicExternalities::default(); - ext.register_extension(TaskExecutorExt::new(TaskExecutor::new())); - - ext.execute_with(|| { - crypto::start_batch_verify(); - }); - - assert!(ext.extensions().get_mut(TypeId::of::()).is_some()); - - ext.execute_with(|| { - assert!(crypto::finish_batch_verify()); - }); - - assert!(ext.extensions().get_mut(TypeId::of::()).is_none()); - } - - #[test] - fn long_sr25519_batching() { - let mut ext = BasicExternalities::default(); - ext.register_extension(TaskExecutorExt::new(TaskExecutor::new())); - ext.execute_with(|| { - let pair = sr25519::Pair::generate_with_phrase(None).0; - let pair_unused = sr25519::Pair::generate_with_phrase(None).0; - crypto::start_batch_verify(); - for it in 0..70 { - let msg = format!("Schnorrkel {}!", it); - let signature = pair.sign(msg.as_bytes()); - crypto::sr25519_batch_verify(&signature, msg.as_bytes(), &pair.public()); - } - - // push invalid - let msg = b"asdf!"; - let signature = pair.sign(msg); - crypto::sr25519_batch_verify(&signature, msg, &pair_unused.public()); - assert!(!crypto::finish_batch_verify()); - - crypto::start_batch_verify(); - for it in 0..70 { - let msg = format!("Schnorrkel {}!", it); - let signature = pair.sign(msg.as_bytes()); - crypto::sr25519_batch_verify(&signature, msg.as_bytes(), &pair.public()); - } - assert!(crypto::finish_batch_verify()); - }); - } - fn zero_ed_pub() -> ed25519::Public { [0u8; 32].unchecked_into() } @@ -1847,90 +1815,6 @@ mod tests { ed25519::Signature::from_raw([0u8; 64]) } - fn zero_sr_pub() -> sr25519::Public { - [0u8; 32].unchecked_into() - } - - fn zero_sr_sig() -> sr25519::Signature { - sr25519::Signature::from_raw([0u8; 64]) - } - - #[test] - fn batching_works() { - let mut ext = BasicExternalities::default(); - ext.register_extension(TaskExecutorExt::new(TaskExecutor::new())); - - ext.execute_with(|| { - // valid ed25519 signature - crypto::start_batch_verify(); - crypto::ed25519_batch_verify(&zero_ed_sig(), &Vec::new(), &zero_ed_pub()); - assert!(crypto::finish_batch_verify()); - - // 2 valid ed25519 signatures - crypto::start_batch_verify(); - - let pair = ed25519::Pair::generate_with_phrase(None).0; - let msg = b"Important message"; - let signature = pair.sign(msg); - crypto::ed25519_batch_verify(&signature, msg, &pair.public()); - - let pair = ed25519::Pair::generate_with_phrase(None).0; - let msg = b"Even more important message"; - let signature = pair.sign(msg); - crypto::ed25519_batch_verify(&signature, msg, &pair.public()); - - assert!(crypto::finish_batch_verify()); - - // 1 valid, 1 invalid ed25519 signature - crypto::start_batch_verify(); - - let pair1 = ed25519::Pair::generate_with_phrase(None).0; - let pair2 = ed25519::Pair::generate_with_phrase(None).0; - let msg = b"Important message"; - let signature = pair1.sign(msg); - - crypto::ed25519_batch_verify(&zero_ed_sig(), &Vec::new(), &zero_ed_pub()); - crypto::ed25519_batch_verify(&signature, msg, &pair1.public()); - crypto::ed25519_batch_verify(&signature, msg, &pair2.public()); - - assert!(!crypto::finish_batch_verify()); - - // 1 valid ed25519, 2 valid sr25519 - crypto::start_batch_verify(); - - let pair = ed25519::Pair::generate_with_phrase(None).0; - let msg = b"Ed25519 batching"; - let signature = pair.sign(msg); - crypto::ed25519_batch_verify(&signature, msg, &pair.public()); - - let pair = sr25519::Pair::generate_with_phrase(None).0; - let msg = b"Schnorrkel rules"; - let signature = pair.sign(msg); - crypto::sr25519_batch_verify(&signature, msg, &pair.public()); - - let pair = sr25519::Pair::generate_with_phrase(None).0; - let msg = b"Schnorrkel batches!"; - let signature = pair.sign(msg); - crypto::sr25519_batch_verify(&signature, msg, &pair.public()); - - assert!(crypto::finish_batch_verify()); - - // 1 valid sr25519, 1 invalid sr25519 - crypto::start_batch_verify(); - - let pair1 = sr25519::Pair::generate_with_phrase(None).0; - let pair2 = sr25519::Pair::generate_with_phrase(None).0; - let msg = b"Schnorrkcel!"; - let signature = pair1.sign(msg); - - crypto::sr25519_batch_verify(&signature, msg, &pair1.public()); - crypto::sr25519_batch_verify(&signature, msg, &pair2.public()); - crypto::sr25519_batch_verify(&zero_sr_sig(), &Vec::new(), &zero_sr_pub()); - - assert!(!crypto::finish_batch_verify()); - }); - } - #[test] fn use_dalek_ext_works() { let mut ext = BasicExternalities::default(); diff --git a/primitives/keyring/Cargo.toml b/primitives/keyring/Cargo.toml index 0646dde4f077..2b08755dba1a 100644 --- a/primitives/keyring/Cargo.toml +++ b/primitives/keyring/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-keyring" -version = "7.0.0" +version = "24.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -15,6 +15,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] lazy_static = "1.4.0" -strum = { version = "0.24.1", features = ["derive"] } -sp-core = { version = "7.0.0", path = "../core" } -sp-runtime = { version = "7.0.0", path = "../runtime" } +strum = { version = "0.24.1", features = ["derive"], default-features = false } +sp-core = { version = "21.0.0", path = "../core" } +sp-runtime = { version = "24.0.0", path = "../runtime" } diff --git a/primitives/keyring/src/ed25519.rs b/primitives/keyring/src/ed25519.rs index 404e4121e71a..c3ad86409e90 100644 --- a/primitives/keyring/src/ed25519.rs +++ b/primitives/keyring/src/ed25519.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/keyring/src/lib.rs b/primitives/keyring/src/lib.rs index 170a4c3d01b2..7432aff12544 100644 --- a/primitives/keyring/src/lib.rs +++ b/primitives/keyring/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/keyring/src/sr25519.rs b/primitives/keyring/src/sr25519.rs index 115a7fce7012..c738cfdc59d9 100644 --- a/primitives/keyring/src/sr25519.rs +++ b/primitives/keyring/src/sr25519.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/keystore/Cargo.toml b/primitives/keystore/Cargo.toml index 9386cb5d104d..1ae4f782042c 100644 --- a/primitives/keystore/Cargo.toml +++ b/primitives/keystore/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-keystore" -version = "0.13.0" +version = "0.27.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -13,16 +13,11 @@ documentation = "https://docs.rs/sp-core" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -async-trait = "0.1.57" -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -futures = "0.3.21" -merlin = { version = "2.0", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } parking_lot = { version = "0.12.1", default-features = false } -schnorrkel = { version = "0.9.1", default-features = false, features = ["preaudit_deprecated", "u64_backend"] } -serde = { version = "1.0", optional = true } thiserror = "1.0" -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-externalities = { version = "0.13.0", default-features = false, path = "../externalities" } +sp-core = { version = "21.0.0", default-features = false, path = "../core" } +sp-externalities = { version = "0.19.0", default-features = false, path = "../externalities" } [dev-dependencies] rand = "0.7.2" @@ -32,9 +27,10 @@ rand_chacha = "0.2.2" default = ["std"] std = [ "codec/std", - "merlin/std", - "schnorrkel/std", - "serde", "sp-core/std", "sp-externalities/std", ] + +# This feature adds BLS crypto primitives. It should not be used in production since +# the BLS implementation and interface may still be subject to significant change. +bls-experimental = ["sp-core/bls-experimental"] diff --git a/primitives/keystore/src/lib.rs b/primitives/keystore/src/lib.rs index 6540e71bc3fe..07583d11d527 100644 --- a/primitives/keystore/src/lib.rs +++ b/primitives/keystore/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,19 +16,19 @@ // limitations under the License. //! Keystore traits + pub mod testing; -pub mod vrf; -use crate::vrf::{VRFSignature, VRFTranscriptData}; -use async_trait::async_trait; -use futures::{executor::block_on, future::join_all}; +#[cfg(feature = "bls-experimental")] +use sp_core::{bls377, bls381}; use sp_core::{ - crypto::{CryptoTypePublicPair, KeyTypeId}, + crypto::{ByteArray, CryptoTypeId, KeyTypeId}, ecdsa, ed25519, sr25519, }; + use std::sync::Arc; -/// CryptoStore error +/// Keystore error #[derive(Debug, thiserror::Error)] pub enum Error { /// Public key type is not supported @@ -45,349 +45,440 @@ pub enum Error { Other(String), } -/// Something that generates, stores and provides access to keys. -#[async_trait] -pub trait CryptoStore: Send + Sync { - /// Returns all sr25519 public keys for the given key type. - async fn sr25519_public_keys(&self, id: KeyTypeId) -> Vec; +/// Something that generates, stores and provides access to secret keys. +pub trait Keystore: Send + Sync { + /// Returns all the sr25519 public keys for the given key type. + fn sr25519_public_keys(&self, key_type: KeyTypeId) -> Vec; + /// Generate a new sr25519 key pair for the given key type and an optional seed. /// - /// If the given seed is `Some(_)`, the key pair will only be stored in memory. - /// - /// Returns the public key of the generated key pair. - async fn sr25519_generate_new( + /// Returns an `sr25519::Public` key of the generated key pair or an `Err` if + /// something failed during key generation. + fn sr25519_generate_new( &self, - id: KeyTypeId, + key_type: KeyTypeId, seed: Option<&str>, ) -> Result; - /// Returns all ed25519 public keys for the given key type. - async fn ed25519_public_keys(&self, id: KeyTypeId) -> Vec; - /// Generate a new ed25519 key pair for the given key type and an optional seed. + + /// Generate an sr25519 signature for a given message. /// - /// If the given seed is `Some(_)`, the key pair will only be stored in memory. + /// Receives [`KeyTypeId`] and an [`sr25519::Public`] key to be able to map + /// them to a private key that exists in the keystore. /// - /// Returns the public key of the generated key pair. - async fn ed25519_generate_new( + /// Returns an [`sr25519::Signature`] or `None` in case the given `key_type` + /// and `public` combination doesn't exist in the keystore. + /// An `Err` will be returned if generating the signature itself failed. + fn sr25519_sign( &self, - id: KeyTypeId, - seed: Option<&str>, - ) -> Result; - /// Returns all ecdsa public keys for the given key type. - async fn ecdsa_public_keys(&self, id: KeyTypeId) -> Vec; - /// Generate a new ecdsa key pair for the given key type and an optional seed. + key_type: KeyTypeId, + public: &sr25519::Public, + msg: &[u8], + ) -> Result, Error>; + + /// Generate an sr25519 VRF signature for the given data. /// - /// If the given seed is `Some(_)`, the key pair will only be stored in memory. + /// Receives [`KeyTypeId`] and an [`sr25519::Public`] key to be able to map + /// them to a private key that exists in the keystore. /// - /// Returns the public key of the generated key pair. - async fn ecdsa_generate_new( + /// Returns `None` if the given `key_type` and `public` combination doesn't + /// exist in the keystore or an `Err` when something failed. + fn sr25519_vrf_sign( &self, - id: KeyTypeId, - seed: Option<&str>, - ) -> Result; + key_type: KeyTypeId, + public: &sr25519::Public, + data: &sr25519::vrf::VrfSignData, + ) -> Result, Error>; - /// Insert a new key. This doesn't require any known of the crypto; but a public key must be - /// manually provided. + /// Generate an sr25519 VRF output for a given input data. /// - /// Places it into the file system store. + /// Receives [`KeyTypeId`] and an [`sr25519::Public`] key to be able to map + /// them to a private key that exists in the keystore. /// - /// `Err` if there's some sort of weird filesystem error, but should generally be `Ok`. - async fn insert_unknown(&self, id: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()>; - - /// Find intersection between provided keys and supported keys - /// - /// Provided a list of (CryptoTypeId,[u8]) pairs, this would return - /// a filtered set of public keys which are supported by the keystore. - async fn supported_keys( + /// Returns `None` if the given `key_type` and `public` combination doesn't + /// exist in the keystore or an `Err` when something failed. + fn sr25519_vrf_output( &self, - id: KeyTypeId, - keys: Vec, - ) -> Result, Error>; - /// List all supported keys - /// - /// Returns a set of public keys the signer supports. - async fn keys(&self, id: KeyTypeId) -> Result, Error>; + key_type: KeyTypeId, + public: &sr25519::Public, + input: &sr25519::vrf::VrfInput, + ) -> Result, Error>; - /// Checks if the private keys for the given public key and key type combinations exist. - /// - /// Returns `true` iff all private keys could be found. - async fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool; + /// Returns all ed25519 public keys for the given key type. + fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec; - /// Sign with key - /// - /// Signs a message with the private key that matches - /// the public key passed. + /// Generate a new ed25519 key pair for the given key type and an optional seed. /// - /// Returns the SCALE encoded signature if key is found and supported, `None` if the key doesn't - /// exist or an error when something failed. - async fn sign_with( + /// Returns an `ed25519::Public` key of the generated key pair or an `Err` if + /// something failed during key generation. + fn ed25519_generate_new( &self, - id: KeyTypeId, - key: &CryptoTypePublicPair, - msg: &[u8], - ) -> Result>, Error>; + key_type: KeyTypeId, + seed: Option<&str>, + ) -> Result; - /// Sign with any key + /// Generate an ed25519 signature for a given message. /// - /// Given a list of public keys, find the first supported key and - /// sign the provided message with that key. + /// Receives [`KeyTypeId`] and an [`ed25519::Public`] key to be able to map + /// them to a private key that exists in the keystore. /// - /// Returns a tuple of the used key and the SCALE encoded signature or `None` if no key could - /// be found to sign. - async fn sign_with_any( + /// Returns an [`ed25519::Signature`] or `None` in case the given `key_type` + /// and `public` combination doesn't exist in the keystore. + /// An `Err` will be returned if generating the signature itself failed. + fn ed25519_sign( &self, - id: KeyTypeId, - keys: Vec, + key_type: KeyTypeId, + public: &ed25519::Public, msg: &[u8], - ) -> Result)>, Error> { - if keys.len() == 1 { - return Ok(self.sign_with(id, &keys[0], msg).await?.map(|s| (keys[0].clone(), s))) - } else { - for k in self.supported_keys(id, keys).await? { - if let Ok(Some(sign)) = self.sign_with(id, &k, msg).await { - return Ok(Some((k, sign))) - } - } - } - - Ok(None) - } + ) -> Result, Error>; - /// Sign with all keys - /// - /// Provided a list of public keys, sign a message with - /// each key given that the key is supported. + /// Returns all ecdsa public keys for the given key type. + fn ecdsa_public_keys(&self, key_type: KeyTypeId) -> Vec; + + /// Generate a new ecdsa key pair for the given key type and an optional seed. /// - /// Returns a list of `Result`s each representing the SCALE encoded - /// signature of each key, `None` if the key doesn't exist or a error when something failed. - async fn sign_with_all( + /// Returns an `ecdsa::Public` key of the generated key pair or an `Err` if + /// something failed during key generation. + fn ecdsa_generate_new( &self, - id: KeyTypeId, - keys: Vec, - msg: &[u8], - ) -> Result>, Error>>, ()> { - let futs = keys.iter().map(|k| self.sign_with(id, k, msg)); - - Ok(join_all(futs).await) - } + key_type: KeyTypeId, + seed: Option<&str>, + ) -> Result; - /// Generate VRF signature for given transcript data. + /// Generate an ecdsa signature for a given message. /// - /// Receives KeyTypeId and Public key to be able to map - /// them to a private key that exists in the keystore which - /// is, in turn, used for signing the provided transcript. - /// - /// Returns a result containing the signature data. - /// Namely, VRFOutput and VRFProof which are returned - /// inside the `VRFSignature` container struct. + /// Receives [`KeyTypeId`] and an [`ecdsa::Public`] key to be able to map + /// them to a private key that exists in the keystore. /// - /// This function will return `None` if the given `key_type` and `public` combination - /// doesn't exist in the keystore or an `Err` when something failed. - async fn sr25519_vrf_sign( + /// Returns an [`ecdsa::Signature`] or `None` in case the given `key_type` + /// and `public` combination doesn't exist in the keystore. + /// An `Err` will be returned if generating the signature itself failed. + fn ecdsa_sign( &self, key_type: KeyTypeId, - public: &sr25519::Public, - transcript_data: VRFTranscriptData, - ) -> Result, Error>; + public: &ecdsa::Public, + msg: &[u8], + ) -> Result, Error>; - /// Generate an ECDSA signature for a given pre-hashed message. + /// Generate an ecdsa signature for a given pre-hashed message. /// /// Receives [`KeyTypeId`] and an [`ecdsa::Public`] key to be able to map - /// them to a private key that exists in the keystore. This private key is, - /// in turn, used for signing the provided pre-hashed message. + /// them to a private key that exists in the keystore. /// - /// The `msg` argument provided should be a hashed message for which an - /// ECDSA signature should be generated. - /// - /// Returns an [`ecdsa::Signature`] or `None` in case the given `id` and - /// `public` combination doesn't exist in the keystore. An `Err` will be - /// returned if generating the signature itself failed. - async fn ecdsa_sign_prehashed( + /// Returns an [`ecdsa::Signature`] or `None` in case the given `key_type` + /// and `public` combination doesn't exist in the keystore. + /// An `Err` will be returned if generating the signature itself failed. + fn ecdsa_sign_prehashed( &self, - id: KeyTypeId, + key_type: KeyTypeId, public: &ecdsa::Public, msg: &[u8; 32], ) -> Result, Error>; -} -/// Sync version of the CryptoStore -/// -/// Some parts of Substrate still rely on a sync version of the `CryptoStore`. -/// To make the transition easier this auto trait wraps any async `CryptoStore` and -/// exposes a `sync` interface using `block_on`. Usage of this is deprecated and it -/// will be removed as soon as the internal usage has transitioned successfully. -/// If you are starting out building something new **do not use this**, -/// instead, use [`CryptoStore`]. -pub trait SyncCryptoStore: CryptoStore + Send + Sync { - /// Returns all sr25519 public keys for the given key type. - fn sr25519_public_keys(&self, id: KeyTypeId) -> Vec; + /// Returns all bls12-381 public keys for the given key type. + #[cfg(feature = "bls-experimental")] + fn bls381_public_keys(&self, id: KeyTypeId) -> Vec; - /// Generate a new sr25519 key pair for the given key type and an optional seed. - /// - /// If the given seed is `Some(_)`, the key pair will only be stored in memory. + /// Returns all bls12-377 public keys for the given key type. + #[cfg(feature = "bls-experimental")] + fn bls377_public_keys(&self, id: KeyTypeId) -> Vec; + + /// Generate a new bls381 key pair for the given key type and an optional seed. /// - /// Returns the public key of the generated key pair. - fn sr25519_generate_new( + /// Returns an `bls381::Public` key of the generated key pair or an `Err` if + /// something failed during key generation. + #[cfg(feature = "bls-experimental")] + fn bls381_generate_new( &self, - id: KeyTypeId, + key_type: KeyTypeId, seed: Option<&str>, - ) -> Result; + ) -> Result; - /// Returns all ed25519 public keys for the given key type. - fn ed25519_public_keys(&self, id: KeyTypeId) -> Vec; - - /// Generate a new ed25519 key pair for the given key type and an optional seed. + /// Generate a new bls377 key pair for the given key type and an optional seed. /// - /// If the given seed is `Some(_)`, the key pair will only be stored in memory. - /// - /// Returns the public key of the generated key pair. - fn ed25519_generate_new( + /// Returns an `bls377::Public` key of the generated key pair or an `Err` if + /// something failed during key generation. + #[cfg(feature = "bls-experimental")] + fn bls377_generate_new( &self, - id: KeyTypeId, + key_type: KeyTypeId, seed: Option<&str>, - ) -> Result; - - /// Returns all ecdsa public keys for the given key type. - fn ecdsa_public_keys(&self, id: KeyTypeId) -> Vec; + ) -> Result; - /// Generate a new ecdsa key pair for the given key type and an optional seed. + /// Generate a bls381 signature for a given message. /// - /// If the given seed is `Some(_)`, the key pair will only be stored in memory. + /// Receives [`KeyTypeId`] and a [`bls381::Public`] key to be able to map + /// them to a private key that exists in the keystore. /// - /// Returns the public key of the generated key pair. - fn ecdsa_generate_new(&self, id: KeyTypeId, seed: Option<&str>) - -> Result; + /// Returns an [`bls381::Signature`] or `None` in case the given `key_type` + /// and `public` combination doesn't exist in the keystore. + /// An `Err` will be returned if generating the signature itself failed. + #[cfg(feature = "bls-experimental")] + fn bls381_sign( + &self, + key_type: KeyTypeId, + public: &bls381::Public, + msg: &[u8], + ) -> Result, Error>; - /// Insert a new key. This doesn't require any known of the crypto; but a public key must be - /// manually provided. - /// - /// Places it into the file system store. + /// Generate a bls377 signature for a given message. /// - /// `Err` if there's some sort of weird filesystem error, but should generally be `Ok`. - fn insert_unknown(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()>; - - /// Find intersection between provided keys and supported keys + /// Receives [`KeyTypeId`] and a [`bls377::Public`] key to be able to map + /// them to a private key that exists in the keystore. /// - /// Provided a list of (CryptoTypeId,[u8]) pairs, this would return - /// a filtered set of public keys which are supported by the keystore. - fn supported_keys( + /// Returns an [`bls377::Signature`] or `None` in case the given `key_type` + /// and `public` combination doesn't exist in the keystore. + /// An `Err` will be returned if generating the signature itself failed. + #[cfg(feature = "bls-experimental")] + fn bls377_sign( &self, - id: KeyTypeId, - keys: Vec, - ) -> Result, Error>; + key_type: KeyTypeId, + public: &bls377::Public, + msg: &[u8], + ) -> Result, Error>; + + /// Insert a new secret key. + fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()>; - /// List all supported keys + /// List all supported keys of a given type. /// - /// Returns a set of public keys the signer supports. - fn keys(&self, id: KeyTypeId) -> Result, Error> { - block_on(CryptoStore::keys(self, id)) - } + /// Returns a set of public keys the signer supports in raw format. + fn keys(&self, key_type: KeyTypeId) -> Result>, Error>; /// Checks if the private keys for the given public key and key type combinations exist. /// /// Returns `true` iff all private keys could be found. fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool; - /// Sign with key + /// Convenience method to sign a message using the given key type and a raw public key + /// for secret lookup. /// - /// Signs a message with the private key that matches - /// the public key passed. + /// The message is signed using the cryptographic primitive specified by `crypto_id`. + /// + /// Schemes supported by the default trait implementation: + /// - sr25519 + /// - ed25519 + /// - ecdsa + /// - bls381 + /// - bls377 + /// + /// To support more schemes you can overwrite this method. /// /// Returns the SCALE encoded signature if key is found and supported, `None` if the key doesn't /// exist or an error when something failed. fn sign_with( &self, id: KeyTypeId, - key: &CryptoTypePublicPair, + crypto_id: CryptoTypeId, + public: &[u8], msg: &[u8], - ) -> Result>, Error>; + ) -> Result>, Error> { + use codec::Encode; + + let signature = match crypto_id { + sr25519::CRYPTO_ID => { + let public = sr25519::Public::from_slice(public) + .map_err(|_| Error::ValidationError("Invalid public key format".into()))?; + self.sr25519_sign(id, &public, msg)?.map(|s| s.encode()) + }, + ed25519::CRYPTO_ID => { + let public = ed25519::Public::from_slice(public) + .map_err(|_| Error::ValidationError("Invalid public key format".into()))?; + self.ed25519_sign(id, &public, msg)?.map(|s| s.encode()) + }, + ecdsa::CRYPTO_ID => { + let public = ecdsa::Public::from_slice(public) + .map_err(|_| Error::ValidationError("Invalid public key format".into()))?; + + self.ecdsa_sign(id, &public, msg)?.map(|s| s.encode()) + }, + #[cfg(feature = "bls-experimental")] + bls381::CRYPTO_ID => { + let public = bls381::Public::from_slice(public) + .map_err(|_| Error::ValidationError("Invalid public key format".into()))?; + self.bls381_sign(id, &public, msg)?.map(|s| s.encode()) + }, + #[cfg(feature = "bls-experimental")] + bls377::CRYPTO_ID => { + let public = bls377::Public::from_slice(public) + .map_err(|_| Error::ValidationError("Invalid public key format".into()))?; + self.bls377_sign(id, &public, msg)?.map(|s| s.encode()) + }, + _ => return Err(Error::KeyNotSupported(id)), + }; + Ok(signature) + } +} - /// Sign with any key - /// - /// Given a list of public keys, find the first supported key and - /// sign the provided message with that key. - /// - /// Returns a tuple of the used key and the SCALE encoded signature or `None` if no key could - /// be found to sign. - fn sign_with_any( +impl Keystore for Arc { + fn sr25519_public_keys(&self, key_type: KeyTypeId) -> Vec { + (**self).sr25519_public_keys(key_type) + } + + fn sr25519_generate_new( &self, - id: KeyTypeId, - keys: Vec, - msg: &[u8], - ) -> Result)>, Error> { - if keys.len() == 1 { - return Ok( - SyncCryptoStore::sign_with(self, id, &keys[0], msg)?.map(|s| (keys[0].clone(), s)) - ) - } else { - for k in SyncCryptoStore::supported_keys(self, id, keys)? { - if let Ok(Some(sign)) = SyncCryptoStore::sign_with(self, id, &k, msg) { - return Ok(Some((k, sign))) - } - } - } - - Ok(None) + key_type: KeyTypeId, + seed: Option<&str>, + ) -> Result { + (**self).sr25519_generate_new(key_type, seed) } - /// Sign with all keys - /// - /// Provided a list of public keys, sign a message with - /// each key given that the key is supported. - /// - /// Returns a list of `Result`s each representing the SCALE encoded - /// signature of each key, `None` if the key doesn't exist or an error when something failed. - fn sign_with_all( + fn sr25519_sign( &self, - id: KeyTypeId, - keys: Vec, + key_type: KeyTypeId, + public: &sr25519::Public, msg: &[u8], - ) -> Result>, Error>>, ()> { - Ok(keys.iter().map(|k| SyncCryptoStore::sign_with(self, id, k, msg)).collect()) + ) -> Result, Error> { + (**self).sr25519_sign(key_type, public, msg) } - /// Generate VRF signature for given transcript data. - /// - /// Receives KeyTypeId and Public key to be able to map - /// them to a private key that exists in the keystore which - /// is, in turn, used for signing the provided transcript. - /// - /// Returns a result containing the signature data. - /// Namely, VRFOutput and VRFProof which are returned - /// inside the `VRFSignature` container struct. - /// - /// This function will return `None` if the given `key_type` and `public` combination - /// doesn't exist in the keystore or an `Err` when something failed. fn sr25519_vrf_sign( &self, key_type: KeyTypeId, public: &sr25519::Public, - transcript_data: VRFTranscriptData, - ) -> Result, Error>; + data: &sr25519::vrf::VrfSignData, + ) -> Result, Error> { + (**self).sr25519_vrf_sign(key_type, public, data) + } + + fn sr25519_vrf_output( + &self, + key_type: KeyTypeId, + public: &sr25519::Public, + input: &sr25519::vrf::VrfInput, + ) -> Result, Error> { + (**self).sr25519_vrf_output(key_type, public, input) + } + + fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec { + (**self).ed25519_public_keys(key_type) + } + + fn ed25519_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> Result { + (**self).ed25519_generate_new(key_type, seed) + } + + fn ed25519_sign( + &self, + key_type: KeyTypeId, + public: &ed25519::Public, + msg: &[u8], + ) -> Result, Error> { + (**self).ed25519_sign(key_type, public, msg) + } + + fn ecdsa_public_keys(&self, key_type: KeyTypeId) -> Vec { + (**self).ecdsa_public_keys(key_type) + } + + fn ecdsa_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> Result { + (**self).ecdsa_generate_new(key_type, seed) + } + + fn ecdsa_sign( + &self, + key_type: KeyTypeId, + public: &ecdsa::Public, + msg: &[u8], + ) -> Result, Error> { + (**self).ecdsa_sign(key_type, public, msg) + } - /// Generate an ECDSA signature for a given pre-hashed message. - /// - /// Receives [`KeyTypeId`] and an [`ecdsa::Public`] key to be able to map - /// them to a private key that exists in the keystore. This private key is, - /// in turn, used for signing the provided pre-hashed message. - /// - /// The `msg` argument provided should be a hashed message for which an - /// ECDSA signature should be generated. - /// - /// Returns an [`ecdsa::Signature`] or `None` in case the given `id` and - /// `public` combination doesn't exist in the keystore. An `Err` will be - /// returned if generating the signature itself failed. fn ecdsa_sign_prehashed( &self, - id: KeyTypeId, + key_type: KeyTypeId, public: &ecdsa::Public, msg: &[u8; 32], - ) -> Result, Error>; + ) -> Result, Error> { + (**self).ecdsa_sign_prehashed(key_type, public, msg) + } + + fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> { + (**self).insert(key_type, suri, public) + } + + fn keys(&self, key_type: KeyTypeId) -> Result>, Error> { + (**self).keys(key_type) + } + + fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool { + (**self).has_keys(public_keys) + } + + #[cfg(feature = "bls-experimental")] + fn bls381_public_keys(&self, id: KeyTypeId) -> Vec { + (**self).bls381_public_keys(id) + } + + #[cfg(feature = "bls-experimental")] + fn bls377_public_keys(&self, id: KeyTypeId) -> Vec { + (**self).bls377_public_keys(id) + } + + #[cfg(feature = "bls-experimental")] + fn bls381_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> Result { + (**self).bls381_generate_new(key_type, seed) + } + + #[cfg(feature = "bls-experimental")] + fn bls377_generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> Result { + (**self).bls377_generate_new(key_type, seed) + } + + #[cfg(feature = "bls-experimental")] + fn bls381_sign( + &self, + key_type: KeyTypeId, + public: &bls381::Public, + msg: &[u8], + ) -> Result, Error> { + (**self).bls381_sign(key_type, public, msg) + } + + #[cfg(feature = "bls-experimental")] + fn bls377_sign( + &self, + key_type: KeyTypeId, + public: &bls377::Public, + msg: &[u8], + ) -> Result, Error> { + (**self).bls377_sign(key_type, public, msg) + } } -/// A pointer to a keystore. -pub type SyncCryptoStorePtr = Arc; +/// A shared pointer to a keystore implementation. +pub type KeystorePtr = Arc; sp_externalities::decl_extension! { /// The keystore extension to register/retrieve from the externalities. - pub struct KeystoreExt(SyncCryptoStorePtr); + pub struct KeystoreExt(KeystorePtr); +} + +impl KeystoreExt { + /// Create a new instance of `KeystoreExt` + /// + /// This is more performant as we don't need to wrap keystore in another [`Arc`]. + pub fn from(keystore: KeystorePtr) -> Self { + Self(keystore) + } + + /// Create a new instance of `KeystoreExt` using the given `keystore`. + pub fn new(keystore: T) -> Self { + Self(Arc::new(keystore)) + } } diff --git a/primitives/keystore/src/testing.rs b/primitives/keystore/src/testing.rs index a9ec6709d912..e18931a7af88 100644 --- a/primitives/keystore/src/testing.rs +++ b/primitives/keystore/src/testing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,383 +17,279 @@ //! Types that should only be used for testing! +use crate::{Error, Keystore, KeystorePtr}; + +#[cfg(feature = "bls-experimental")] +use sp_core::{bls377, bls381}; use sp_core::{ - crypto::{ByteArray, CryptoTypePublicPair, KeyTypeId, Pair}, + crypto::{ByteArray, KeyTypeId, Pair, VrfSecret}, ecdsa, ed25519, sr25519, }; -use crate::{ - vrf::{make_transcript, VRFSignature, VRFTranscriptData}, - CryptoStore, Error, SyncCryptoStore, SyncCryptoStorePtr, -}; -use async_trait::async_trait; use parking_lot::RwLock; -use std::{ - collections::{HashMap, HashSet}, - sync::Arc, -}; +use std::{collections::HashMap, sync::Arc}; /// A keystore implementation usable in tests. #[derive(Default)] -pub struct KeyStore { +pub struct MemoryKeystore { /// `KeyTypeId` maps to public keys and public keys map to private keys. keys: Arc, String>>>>, } -impl KeyStore { +impl MemoryKeystore { /// Creates a new instance of `Self`. pub fn new() -> Self { Self::default() } - fn sr25519_key_pair(&self, id: KeyTypeId, pub_key: &sr25519::Public) -> Option { - self.keys.read().get(&id).and_then(|inner| { - inner.get(pub_key.as_slice()).map(|s| { - sr25519::Pair::from_string(s, None).expect("`sr25519` seed slice is valid") - }) + fn pair(&self, key_type: KeyTypeId, public: &T::Public) -> Option { + self.keys.read().get(&key_type).and_then(|inner| { + inner + .get(public.as_slice()) + .map(|s| T::from_string(s, None).expect("seed slice is valid")) }) } - fn ed25519_key_pair(&self, id: KeyTypeId, pub_key: &ed25519::Public) -> Option { - self.keys.read().get(&id).and_then(|inner| { - inner.get(pub_key.as_slice()).map(|s| { - ed25519::Pair::from_string(s, None).expect("`ed25519` seed slice is valid") + fn public_keys(&self, key_type: KeyTypeId) -> Vec { + self.keys + .read() + .get(&key_type) + .map(|keys| { + keys.values() + .map(|s| T::from_string(s, None).expect("seed slice is valid")) + .map(|p| p.public()) + .collect() }) - }) + .unwrap_or_default() } - fn ecdsa_key_pair(&self, id: KeyTypeId, pub_key: &ecdsa::Public) -> Option { - self.keys.read().get(&id).and_then(|inner| { - inner - .get(pub_key.as_slice()) - .map(|s| ecdsa::Pair::from_string(s, None).expect("`ecdsa` seed slice is valid")) - }) + fn generate_new( + &self, + key_type: KeyTypeId, + seed: Option<&str>, + ) -> Result { + match seed { + Some(seed) => { + let pair = T::from_string(seed, None) + .map_err(|_| Error::ValidationError("Generates a pair.".to_owned()))?; + self.keys + .write() + .entry(key_type) + .or_default() + .insert(pair.public().to_raw_vec(), seed.into()); + Ok(pair.public()) + }, + None => { + let (pair, phrase, _) = T::generate_with_phrase(None); + self.keys + .write() + .entry(key_type) + .or_default() + .insert(pair.public().to_raw_vec(), phrase); + Ok(pair.public()) + }, + } } -} -#[async_trait] -impl CryptoStore for KeyStore { - async fn keys(&self, id: KeyTypeId) -> Result, Error> { - SyncCryptoStore::keys(self, id) + fn sign( + &self, + key_type: KeyTypeId, + public: &T::Public, + msg: &[u8], + ) -> Result, Error> { + let sig = self.pair::(key_type, public).map(|pair| pair.sign(msg)); + Ok(sig) } - async fn sr25519_public_keys(&self, id: KeyTypeId) -> Vec { - SyncCryptoStore::sr25519_public_keys(self, id) + fn vrf_sign( + &self, + key_type: KeyTypeId, + public: &T::Public, + data: &T::VrfSignData, + ) -> Result, Error> { + let sig = self.pair::(key_type, public).map(|pair| pair.vrf_sign(data)); + Ok(sig) } - async fn sr25519_generate_new( + fn vrf_output( &self, - id: KeyTypeId, - seed: Option<&str>, - ) -> Result { - SyncCryptoStore::sr25519_generate_new(self, id, seed) + key_type: KeyTypeId, + public: &T::Public, + input: &T::VrfInput, + ) -> Result, Error> { + let preout = self.pair::(key_type, public).map(|pair| pair.vrf_output(input)); + Ok(preout) } +} - async fn ed25519_public_keys(&self, id: KeyTypeId) -> Vec { - SyncCryptoStore::ed25519_public_keys(self, id) +impl Keystore for MemoryKeystore { + fn sr25519_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) } - async fn ed25519_generate_new( + fn sr25519_generate_new( &self, - id: KeyTypeId, + key_type: KeyTypeId, seed: Option<&str>, - ) -> Result { - SyncCryptoStore::ed25519_generate_new(self, id, seed) + ) -> Result { + self.generate_new::(key_type, seed) } - async fn ecdsa_public_keys(&self, id: KeyTypeId) -> Vec { - SyncCryptoStore::ecdsa_public_keys(self, id) + fn sr25519_sign( + &self, + key_type: KeyTypeId, + public: &sr25519::Public, + msg: &[u8], + ) -> Result, Error> { + self.sign::(key_type, public, msg) } - async fn ecdsa_generate_new( + fn sr25519_vrf_sign( &self, - id: KeyTypeId, - seed: Option<&str>, - ) -> Result { - SyncCryptoStore::ecdsa_generate_new(self, id, seed) + key_type: KeyTypeId, + public: &sr25519::Public, + data: &sr25519::vrf::VrfSignData, + ) -> Result, Error> { + self.vrf_sign::(key_type, public, data) } - async fn insert_unknown(&self, id: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> { - SyncCryptoStore::insert_unknown(self, id, suri, public) + fn sr25519_vrf_output( + &self, + key_type: KeyTypeId, + public: &sr25519::Public, + input: &sr25519::vrf::VrfInput, + ) -> Result, Error> { + self.vrf_output::(key_type, public, input) } - async fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool { - SyncCryptoStore::has_keys(self, public_keys) + fn ed25519_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) } - async fn supported_keys( + fn ed25519_generate_new( &self, - id: KeyTypeId, - keys: Vec, - ) -> std::result::Result, Error> { - SyncCryptoStore::supported_keys(self, id, keys) + key_type: KeyTypeId, + seed: Option<&str>, + ) -> Result { + self.generate_new::(key_type, seed) } - async fn sign_with( + fn ed25519_sign( &self, - id: KeyTypeId, - key: &CryptoTypePublicPair, + key_type: KeyTypeId, + public: &ed25519::Public, msg: &[u8], - ) -> Result>, Error> { - SyncCryptoStore::sign_with(self, id, key, msg) + ) -> Result, Error> { + self.sign::(key_type, public, msg) + } + + fn ecdsa_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) } - async fn sr25519_vrf_sign( + fn ecdsa_generate_new( &self, key_type: KeyTypeId, - public: &sr25519::Public, - transcript_data: VRFTranscriptData, - ) -> Result, Error> { - SyncCryptoStore::sr25519_vrf_sign(self, key_type, public, transcript_data) + seed: Option<&str>, + ) -> Result { + self.generate_new::(key_type, seed) } - async fn ecdsa_sign_prehashed( + fn ecdsa_sign( &self, - id: KeyTypeId, + key_type: KeyTypeId, public: &ecdsa::Public, - msg: &[u8; 32], + msg: &[u8], ) -> Result, Error> { - SyncCryptoStore::ecdsa_sign_prehashed(self, id, public, msg) + self.sign::(key_type, public, msg) } -} -impl SyncCryptoStore for KeyStore { - fn keys(&self, id: KeyTypeId) -> Result, Error> { - self.keys - .read() - .get(&id) - .map(|map| { - Ok(map.keys().fold(Vec::new(), |mut v, k| { - v.push(CryptoTypePublicPair(sr25519::CRYPTO_ID, k.clone())); - v.push(CryptoTypePublicPair(ed25519::CRYPTO_ID, k.clone())); - v.push(CryptoTypePublicPair(ecdsa::CRYPTO_ID, k.clone())); - v - })) - }) - .unwrap_or_else(|| Ok(vec![])) + fn ecdsa_sign_prehashed( + &self, + key_type: KeyTypeId, + public: &ecdsa::Public, + msg: &[u8; 32], + ) -> Result, Error> { + let sig = self.pair::(key_type, public).map(|pair| pair.sign_prehashed(msg)); + Ok(sig) } - fn sr25519_public_keys(&self, id: KeyTypeId) -> Vec { - self.keys - .read() - .get(&id) - .map(|keys| { - keys.values() - .map(|s| { - sr25519::Pair::from_string(s, None).expect("`sr25519` seed slice is valid") - }) - .map(|p| p.public()) - .collect() - }) - .unwrap_or_default() + #[cfg(feature = "bls-experimental")] + fn bls381_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) } - fn sr25519_generate_new( + #[cfg(feature = "bls-experimental")] + fn bls381_generate_new( &self, - id: KeyTypeId, + key_type: KeyTypeId, seed: Option<&str>, - ) -> Result { - match seed { - Some(seed) => { - let pair = sr25519::Pair::from_string(seed, None).map_err(|_| { - Error::ValidationError("Generates an `sr25519` pair.".to_owned()) - })?; - self.keys - .write() - .entry(id) - .or_default() - .insert(pair.public().to_raw_vec(), seed.into()); - Ok(pair.public()) - }, - None => { - let (pair, phrase, _) = sr25519::Pair::generate_with_phrase(None); - self.keys - .write() - .entry(id) - .or_default() - .insert(pair.public().to_raw_vec(), phrase); - Ok(pair.public()) - }, - } + ) -> Result { + self.generate_new::(key_type, seed) } - fn ed25519_public_keys(&self, id: KeyTypeId) -> Vec { - self.keys - .read() - .get(&id) - .map(|keys| { - keys.values() - .map(|s| { - ed25519::Pair::from_string(s, None).expect("`ed25519` seed slice is valid") - }) - .map(|p| p.public()) - .collect() - }) - .unwrap_or_default() - } - - fn ed25519_generate_new( + #[cfg(feature = "bls-experimental")] + fn bls381_sign( &self, - id: KeyTypeId, - seed: Option<&str>, - ) -> Result { - match seed { - Some(seed) => { - let pair = ed25519::Pair::from_string(seed, None).map_err(|_| { - Error::ValidationError("Generates an `ed25519` pair.".to_owned()) - })?; - self.keys - .write() - .entry(id) - .or_default() - .insert(pair.public().to_raw_vec(), seed.into()); - Ok(pair.public()) - }, - None => { - let (pair, phrase, _) = ed25519::Pair::generate_with_phrase(None); - self.keys - .write() - .entry(id) - .or_default() - .insert(pair.public().to_raw_vec(), phrase); - Ok(pair.public()) - }, - } + key_type: KeyTypeId, + public: &bls381::Public, + msg: &[u8], + ) -> Result, Error> { + self.sign::(key_type, public, msg) } - fn ecdsa_public_keys(&self, id: KeyTypeId) -> Vec { - self.keys - .read() - .get(&id) - .map(|keys| { - keys.values() - .map(|s| { - ecdsa::Pair::from_string(s, None).expect("`ecdsa` seed slice is valid") - }) - .map(|p| p.public()) - .collect() - }) - .unwrap_or_default() + #[cfg(feature = "bls-experimental")] + fn bls377_public_keys(&self, key_type: KeyTypeId) -> Vec { + self.public_keys::(key_type) } - fn ecdsa_generate_new( + #[cfg(feature = "bls-experimental")] + fn bls377_generate_new( &self, - id: KeyTypeId, + key_type: KeyTypeId, seed: Option<&str>, - ) -> Result { - match seed { - Some(seed) => { - let pair = ecdsa::Pair::from_string(seed, None) - .map_err(|_| Error::ValidationError("Generates an `ecdsa` pair.".to_owned()))?; - self.keys - .write() - .entry(id) - .or_default() - .insert(pair.public().to_raw_vec(), seed.into()); - Ok(pair.public()) - }, - None => { - let (pair, phrase, _) = ecdsa::Pair::generate_with_phrase(None); - self.keys - .write() - .entry(id) - .or_default() - .insert(pair.public().to_raw_vec(), phrase); - Ok(pair.public()) - }, - } + ) -> Result { + self.generate_new::(key_type, seed) } - fn insert_unknown(&self, id: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> { + #[cfg(feature = "bls-experimental")] + fn bls377_sign( + &self, + key_type: KeyTypeId, + public: &bls377::Public, + msg: &[u8], + ) -> Result, Error> { + self.sign::(key_type, public, msg) + } + + fn insert(&self, key_type: KeyTypeId, suri: &str, public: &[u8]) -> Result<(), ()> { self.keys .write() - .entry(id) + .entry(key_type) .or_default() .insert(public.to_owned(), suri.to_string()); Ok(()) } + fn keys(&self, key_type: KeyTypeId) -> Result>, Error> { + let keys = self + .keys + .read() + .get(&key_type) + .map(|map| map.keys().cloned().collect()) + .unwrap_or_default(); + Ok(keys) + } + fn has_keys(&self, public_keys: &[(Vec, KeyTypeId)]) -> bool { public_keys .iter() .all(|(k, t)| self.keys.read().get(t).and_then(|s| s.get(k)).is_some()) } - - fn supported_keys( - &self, - id: KeyTypeId, - keys: Vec, - ) -> std::result::Result, Error> { - let provided_keys = keys.into_iter().collect::>(); - let all_keys = SyncCryptoStore::keys(self, id)?.into_iter().collect::>(); - - Ok(provided_keys.intersection(&all_keys).cloned().collect()) - } - - fn sign_with( - &self, - id: KeyTypeId, - key: &CryptoTypePublicPair, - msg: &[u8], - ) -> Result>, Error> { - use codec::Encode; - - match key.0 { - ed25519::CRYPTO_ID => { - let key_pair = self - .ed25519_key_pair(id, &ed25519::Public::from_slice(key.1.as_slice()).unwrap()); - - key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() - }, - sr25519::CRYPTO_ID => { - let key_pair = self - .sr25519_key_pair(id, &sr25519::Public::from_slice(key.1.as_slice()).unwrap()); - - key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() - }, - ecdsa::CRYPTO_ID => { - let key_pair = - self.ecdsa_key_pair(id, &ecdsa::Public::from_slice(key.1.as_slice()).unwrap()); - - key_pair.map(|k| k.sign(msg).encode()).map(Ok).transpose() - }, - _ => Err(Error::KeyNotSupported(id)), - } - } - - fn sr25519_vrf_sign( - &self, - key_type: KeyTypeId, - public: &sr25519::Public, - transcript_data: VRFTranscriptData, - ) -> Result, Error> { - let transcript = make_transcript(transcript_data); - let pair = - if let Some(k) = self.sr25519_key_pair(key_type, public) { k } else { return Ok(None) }; - - let (inout, proof, _) = pair.as_ref().vrf_sign(transcript); - Ok(Some(VRFSignature { output: inout.to_output(), proof })) - } - - fn ecdsa_sign_prehashed( - &self, - id: KeyTypeId, - public: &ecdsa::Public, - msg: &[u8; 32], - ) -> Result, Error> { - let pair = self.ecdsa_key_pair(id, public); - pair.map(|k| k.sign_prehashed(msg)).map(Ok).transpose() - } -} - -impl Into for KeyStore { - fn into(self) -> SyncCryptoStorePtr { - Arc::new(self) - } } -impl Into> for KeyStore { - fn into(self) -> Arc { +impl Into for MemoryKeystore { + fn into(self) -> KeystorePtr { Arc::new(self) } } @@ -401,7 +297,6 @@ impl Into> for KeyStore { #[cfg(test)] mod tests { use super::*; - use crate::{vrf::VRFTranscriptValue, SyncCryptoStore}; use sp_core::{ sr25519, testing::{ECDSA, ED25519, SR25519}, @@ -409,67 +304,92 @@ mod tests { #[test] fn store_key_and_extract() { - let store = KeyStore::new(); + let store = MemoryKeystore::new(); - let public = - SyncCryptoStore::ed25519_generate_new(&store, ED25519, None).expect("Generates key"); + let public = store.ed25519_generate_new(ED25519, None).expect("Generates key"); - let public_keys = SyncCryptoStore::keys(&store, ED25519).unwrap(); + let public_keys = store.ed25519_public_keys(ED25519); assert!(public_keys.contains(&public.into())); } #[test] fn store_unknown_and_extract_it() { - let store = KeyStore::new(); + let store = MemoryKeystore::new(); let secret_uri = "//Alice"; let key_pair = sr25519::Pair::from_string(secret_uri, None).expect("Generates key pair"); - SyncCryptoStore::insert_unknown(&store, SR25519, secret_uri, key_pair.public().as_ref()) + store + .insert(SR25519, secret_uri, key_pair.public().as_ref()) .expect("Inserts unknown key"); - let public_keys = SyncCryptoStore::keys(&store, SR25519).unwrap(); + let public_keys = store.sr25519_public_keys(SR25519); assert!(public_keys.contains(&key_pair.public().into())); } #[test] fn vrf_sign() { - let store = KeyStore::new(); + let store = MemoryKeystore::new(); let secret_uri = "//Alice"; let key_pair = sr25519::Pair::from_string(secret_uri, None).expect("Generates key pair"); - let transcript_data = VRFTranscriptData { - label: b"Test", - items: vec![ - ("one", VRFTranscriptValue::U64(1)), - ("two", VRFTranscriptValue::U64(2)), - ("three", VRFTranscriptValue::Bytes("test".as_bytes().to_vec())), + let data = sr25519::vrf::VrfInput::new( + b"Test", + &[ + (b"one", &1_u64.to_le_bytes()), + (b"two", &2_u64.to_le_bytes()), + (b"three", "test".as_bytes()), ], - }; + ) + .into_sign_data(); - let result = SyncCryptoStore::sr25519_vrf_sign( - &store, - SR25519, - &key_pair.public(), - transcript_data.clone(), - ); + let result = store.sr25519_vrf_sign(SR25519, &key_pair.public(), &data); assert!(result.unwrap().is_none()); - SyncCryptoStore::insert_unknown(&store, SR25519, secret_uri, key_pair.public().as_ref()) + store + .insert(SR25519, secret_uri, key_pair.public().as_ref()) .expect("Inserts unknown key"); - let result = - SyncCryptoStore::sr25519_vrf_sign(&store, SR25519, &key_pair.public(), transcript_data); + let result = store.sr25519_vrf_sign(SR25519, &key_pair.public(), &data); assert!(result.unwrap().is_some()); } + #[test] + fn vrf_output() { + let store = MemoryKeystore::new(); + + let secret_uri = "//Alice"; + let pair = sr25519::Pair::from_string(secret_uri, None).expect("Generates key pair"); + + let input = sr25519::vrf::VrfInput::new( + b"Test", + &[ + (b"one", &1_u64.to_le_bytes()), + (b"two", &2_u64.to_le_bytes()), + (b"three", "test".as_bytes()), + ], + ); + + let result = store.sr25519_vrf_output(SR25519, &pair.public(), &input); + assert!(result.unwrap().is_none()); + + store + .insert(SR25519, secret_uri, pair.public().as_ref()) + .expect("Inserts unknown key"); + + let preout = store.sr25519_vrf_output(SR25519, &pair.public(), &input).unwrap().unwrap(); + + let result = preout.make_bytes::<32>(b"rand", &input, &pair.public()); + assert!(result.is_ok()); + } + #[test] fn ecdsa_sign_prehashed_works() { - let store = KeyStore::new(); + let store = MemoryKeystore::new(); let suri = "//Alice"; let pair = ecdsa::Pair::from_string(suri, None).unwrap(); @@ -477,15 +397,13 @@ mod tests { let msg = sp_core::keccak_256(b"this should be a hashed message"); // no key in key store - let res = - SyncCryptoStore::ecdsa_sign_prehashed(&store, ECDSA, &pair.public(), &msg).unwrap(); + let res = store.ecdsa_sign_prehashed(ECDSA, &pair.public(), &msg).unwrap(); assert!(res.is_none()); // insert key, sign again - SyncCryptoStore::insert_unknown(&store, ECDSA, suri, pair.public().as_ref()).unwrap(); + store.insert(ECDSA, suri, pair.public().as_ref()).unwrap(); - let res = - SyncCryptoStore::ecdsa_sign_prehashed(&store, ECDSA, &pair.public(), &msg).unwrap(); + let res = store.ecdsa_sign_prehashed(ECDSA, &pair.public(), &msg).unwrap(); assert!(res.is_some()); } } diff --git a/primitives/keystore/src/vrf.rs b/primitives/keystore/src/vrf.rs deleted file mode 100644 index 7409353afe9f..000000000000 --- a/primitives/keystore/src/vrf.rs +++ /dev/null @@ -1,94 +0,0 @@ -// 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. - -//! VRF-specifc data types and helpers - -use codec::Encode; -use merlin::Transcript; -use schnorrkel::vrf::{VRFOutput, VRFProof}; - -/// An enum whose variants represent possible -/// accepted values to construct the VRF transcript -#[derive(Clone, Encode)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] -pub enum VRFTranscriptValue { - /// Value is an array of bytes - Bytes(Vec), - /// Value is a u64 integer - U64(u64), -} -/// VRF Transcript data -#[derive(Clone, Encode)] -pub struct VRFTranscriptData { - /// The transcript's label - pub label: &'static [u8], - /// Additional data to be registered into the transcript - pub items: Vec<(&'static str, VRFTranscriptValue)>, -} -/// VRF signature data -pub struct VRFSignature { - /// The VRFOutput serialized - pub output: VRFOutput, - /// The calculated VRFProof - pub proof: VRFProof, -} - -/// Construct a `Transcript` object from data. -/// -/// Returns `merlin::Transcript` -pub fn make_transcript(data: VRFTranscriptData) -> Transcript { - let mut transcript = Transcript::new(data.label); - for (label, value) in data.items.into_iter() { - match value { - VRFTranscriptValue::Bytes(bytes) => { - transcript.append_message(label.as_bytes(), &bytes); - }, - VRFTranscriptValue::U64(val) => { - transcript.append_u64(label.as_bytes(), val); - }, - } - } - transcript -} - -#[cfg(test)] -mod tests { - use super::*; - use rand::RngCore; - use rand_chacha::{rand_core::SeedableRng, ChaChaRng}; - - #[test] - fn transcript_creation_matches() { - let mut orig_transcript = Transcript::new(b"My label"); - orig_transcript.append_u64(b"one", 1); - orig_transcript.append_message(b"two", "test".as_bytes()); - - let new_transcript = make_transcript(VRFTranscriptData { - label: b"My label", - items: vec![ - ("one", VRFTranscriptValue::U64(1)), - ("two", VRFTranscriptValue::Bytes("test".as_bytes().to_vec())), - ], - }); - let test = |t: Transcript| -> [u8; 16] { - let mut b = [0u8; 16]; - t.build_rng().finalize(&mut ChaChaRng::from_seed([0u8; 32])).fill_bytes(&mut b); - b - }; - debug_assert!(test(orig_transcript) == test(new_transcript)); - } -} diff --git a/primitives/maybe-compressed-blob/Cargo.toml b/primitives/maybe-compressed-blob/Cargo.toml index 7e2780aa2335..67f93c517274 100644 --- a/primitives/maybe-compressed-blob/Cargo.toml +++ b/primitives/maybe-compressed-blob/Cargo.toml @@ -12,4 +12,4 @@ readme = "README.md" [dependencies] thiserror = "1.0" -zstd = { version = "0.11.2", default-features = false } +zstd = { version = "0.12.3", default-features = false } diff --git a/primitives/maybe-compressed-blob/src/lib.rs b/primitives/maybe-compressed-blob/src/lib.rs index 99c12ed39bc0..add620d4a1a1 100644 --- a/primitives/maybe-compressed-blob/src/lib.rs +++ b/primitives/maybe-compressed-blob/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/merkle-mountain-range/Cargo.toml b/primitives/merkle-mountain-range/Cargo.toml index 97add1ed1d37..9ede77ba40be 100644 --- a/primitives/merkle-mountain-range/Cargo.toml +++ b/primitives/merkle-mountain-range/Cargo.toml @@ -12,20 +12,20 @@ description = "Merkle Mountain Range primitives." targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } log = { version = "0.4.17", default-features = false } mmr-lib = { package = "ckb-merkle-mountain-range", version = "0.5.2", default-features = false } -serde = { version = "1.0.136", features = ["derive"], optional = true } +serde = { version = "1.0.163", features = ["derive", "alloc"], default-features = false, optional = true } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-debug-derive = { version = "5.0.0", default-features = false, path = "../debug-derive" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-core = { version = "21.0.0", default-features = false, path = "../core" } +sp-debug-derive = { version = "8.0.0", default-features = false, path = "../debug-derive" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } thiserror = "1.0" [dev-dependencies] -array-bytes = "4.1" +array-bytes = "6.1" [features] default = ["std"] @@ -33,10 +33,18 @@ std = [ "codec/std", "log/std", "mmr-lib/std", - "serde", + "serde/std", "sp-api/std", "sp-core/std", "sp-debug-derive/std", "sp-runtime/std", "sp-std/std", ] + +# Serde support without relying on std features. +serde = [ + "dep:serde", + "scale-info/serde", + "sp-core/serde", + "sp-runtime/serde", +] diff --git a/primitives/merkle-mountain-range/src/lib.rs b/primitives/merkle-mountain-range/src/lib.rs index 606906185077..6c0e75005ead 100644 --- a/primitives/merkle-mountain-range/src/lib.rs +++ b/primitives/merkle-mountain-range/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -101,13 +101,13 @@ impl FullLeaf /// This type does not implement SCALE encoding/decoding on purpose to avoid confusion, /// it would have to be SCALE-compatible with the concrete leaf type, but due to SCALE limitations /// it's not possible to know how many bytes the encoding of concrete leaf type uses. -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[derive(RuntimeDebug, Clone, PartialEq)] pub struct OpaqueLeaf( /// Raw bytes of the leaf type encoded in its compact form. /// /// NOTE it DOES NOT include length prefix (like `Vec` encoding would). - #[cfg_attr(feature = "std", serde(with = "sp_core::bytes"))] + #[cfg_attr(feature = "serde", serde(with = "sp_core::bytes"))] pub Vec, ); @@ -142,7 +142,7 @@ impl FullLeaf for OpaqueLeaf { /// /// It is different from [`OpaqueLeaf`], because it does implement `Codec` /// and the encoding has to match raw `Vec` encoding. -#[derive(codec::Encode, codec::Decode, RuntimeDebug, PartialEq, Eq)] +#[derive(codec::Encode, codec::Decode, RuntimeDebug, PartialEq, Eq, TypeInfo)] pub struct EncodableOpaqueLeaf(pub Vec); impl EncodableOpaqueLeaf { @@ -361,7 +361,7 @@ pub struct Proof { /// Merkle Mountain Range operation error. #[cfg_attr(feature = "std", derive(thiserror::Error))] -#[derive(RuntimeDebug, codec::Encode, codec::Decode, PartialEq, Eq)] +#[derive(RuntimeDebug, codec::Encode, codec::Decode, PartialEq, Eq, TypeInfo)] pub enum Error { /// Error during translation of a block number into a leaf index. #[cfg_attr(feature = "std", error("Error performing numeric op"))] @@ -422,6 +422,7 @@ impl Error { sp_api::decl_runtime_apis! { /// API to interact with MMR pallet. + #[api_version(2)] pub trait MmrApi { /// Return the on-chain MMR root hash. fn mmr_root() -> Result; diff --git a/primitives/merkle-mountain-range/src/utils.rs b/primitives/merkle-mountain-range/src/utils.rs index 619ca7e98160..b9171c96a620 100644 --- a/primitives/merkle-mountain-range/src/utils.rs +++ b/primitives/merkle-mountain-range/src/utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/metadata-ir/Cargo.toml b/primitives/metadata-ir/Cargo.toml new file mode 100644 index 000000000000..49fd23e208e4 --- /dev/null +++ b/primitives/metadata-ir/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "sp-metadata-ir" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "Intermediate representation of the runtime metadata." +documentation = "https://docs.rs/sp-metadata-ir" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +frame-metadata = { version = "16.0.0", default-features = false, features = ["current"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-metadata/std", + "scale-info/std", + "sp-std/std", +] diff --git a/primitives/metadata-ir/src/lib.rs b/primitives/metadata-ir/src/lib.rs new file mode 100644 index 000000000000..edfa58f86189 --- /dev/null +++ b/primitives/metadata-ir/src/lib.rs @@ -0,0 +1,118 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Intermediate representation of the runtime metadata. + +#![cfg_attr(not(feature = "std"), no_std)] +#![warn(missing_docs)] + +// Re-export. +#[doc(hidden)] +pub use frame_metadata; + +mod types; +use frame_metadata::RuntimeMetadataPrefixed; +pub use types::*; + +mod v14; +mod v15; + +/// Metadata V14. +const V14: u32 = 14; + +/// Metadata V15. +const V15: u32 = 15; + +/// Transform the IR to the specified version. +/// +/// Use [`supported_versions`] to find supported versions. +pub fn into_version(metadata: MetadataIR, version: u32) -> Option { + // Note: Unstable metadata version is `u32::MAX` until stabilized. + match version { + // Latest stable version. + V14 => Some(into_v14(metadata)), + // Unstable metadata. + V15 => Some(into_latest(metadata)), + _ => None, + } +} + +/// Returns the supported metadata versions. +pub fn supported_versions() -> sp_std::vec::Vec { + sp_std::vec![V14, V15] +} + +/// Transform the IR to the latest stable metadata version. +pub fn into_latest(metadata: MetadataIR) -> RuntimeMetadataPrefixed { + let latest: frame_metadata::v15::RuntimeMetadataV15 = metadata.into(); + latest.into() +} + +/// Transform the IR to metadata version 14. +pub fn into_v14(metadata: MetadataIR) -> RuntimeMetadataPrefixed { + let latest: frame_metadata::v14::RuntimeMetadataV14 = metadata.into(); + latest.into() +} + +#[cfg(test)] +mod test { + use super::*; + use frame_metadata::{v14::META_RESERVED, RuntimeMetadata}; + use scale_info::meta_type; + + fn ir_metadata() -> MetadataIR { + MetadataIR { + pallets: vec![], + extrinsic: ExtrinsicMetadataIR { + ty: meta_type::<()>(), + version: 0, + address_ty: meta_type::<()>(), + call_ty: meta_type::<()>(), + signature_ty: meta_type::<()>(), + extra_ty: meta_type::<()>(), + signed_extensions: vec![], + }, + ty: meta_type::<()>(), + apis: vec![], + outer_enums: OuterEnumsIR { + call_enum_ty: meta_type::<()>(), + event_enum_ty: meta_type::<()>(), + error_enum_ty: meta_type::<()>(), + }, + } + } + + #[test] + fn into_version_14() { + let ir = ir_metadata(); + let metadata = into_version(ir, V14).expect("Should return prefixed metadata"); + + assert_eq!(metadata.0, META_RESERVED); + + assert!(matches!(metadata.1, RuntimeMetadata::V14(_))); + } + + #[test] + fn into_version_15() { + let ir = ir_metadata(); + let metadata = into_version(ir, V15).expect("Should return prefixed metadata"); + + assert_eq!(metadata.0, META_RESERVED); + + assert!(matches!(metadata.1, RuntimeMetadata::V15(_))); + } +} diff --git a/primitives/metadata-ir/src/types.rs b/primitives/metadata-ir/src/types.rs new file mode 100644 index 000000000000..b107d20a8e2b --- /dev/null +++ b/primitives/metadata-ir/src/types.rs @@ -0,0 +1,453 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use codec::Encode; +use scale_info::{ + form::{Form, MetaForm, PortableForm}, + prelude::vec::Vec, + IntoPortable, MetaType, Registry, +}; + +/// The intermediate representation for the runtime metadata. +/// Contains the needed context that allows conversion to multiple metadata versions. +/// +/// # Note +/// +/// Further fields could be added or removed to ensure proper conversion. +/// When the IR does not contain enough information to generate a specific version +/// of the runtime metadata an appropriate default value is used (ie, empty vector). +pub struct MetadataIR { + /// Pallet metadata. + pub pallets: Vec>, + /// Metadata of the extrinsic. + pub extrinsic: ExtrinsicMetadataIR, + /// The type of the `Runtime`. + pub ty: T::Type, + /// Metadata of the Runtime API. + pub apis: Vec>, + /// The outer enums types as found in the runtime. + pub outer_enums: OuterEnumsIR, +} + +/// Metadata of a runtime trait. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub struct RuntimeApiMetadataIR { + /// Trait name. + pub name: T::String, + /// Trait methods. + pub methods: Vec>, + /// Trait documentation. + pub docs: Vec, +} + +impl IntoPortable for RuntimeApiMetadataIR { + type Output = RuntimeApiMetadataIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + RuntimeApiMetadataIR { + name: self.name.into_portable(registry), + methods: registry.map_into_portable(self.methods), + docs: registry.map_into_portable(self.docs), + } + } +} + +/// Metadata of a runtime method. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub struct RuntimeApiMethodMetadataIR { + /// Method name. + pub name: T::String, + /// Method parameters. + pub inputs: Vec>, + /// Method output. + pub output: T::Type, + /// Method documentation. + pub docs: Vec, +} + +impl IntoPortable for RuntimeApiMethodMetadataIR { + type Output = RuntimeApiMethodMetadataIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + RuntimeApiMethodMetadataIR { + name: self.name.into_portable(registry), + inputs: registry.map_into_portable(self.inputs), + output: registry.register_type(&self.output), + docs: registry.map_into_portable(self.docs), + } + } +} + +/// Metadata of a runtime method parameter. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub struct RuntimeApiMethodParamMetadataIR { + /// Parameter name. + pub name: T::String, + /// Parameter type. + pub ty: T::Type, +} + +impl IntoPortable for RuntimeApiMethodParamMetadataIR { + type Output = RuntimeApiMethodParamMetadataIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + RuntimeApiMethodParamMetadataIR { + name: self.name.into_portable(registry), + ty: registry.register_type(&self.ty), + } + } +} + +/// The intermediate representation for a pallet metadata. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub struct PalletMetadataIR { + /// Pallet name. + pub name: T::String, + /// Pallet storage metadata. + pub storage: Option>, + /// Pallet calls metadata. + pub calls: Option>, + /// Pallet event metadata. + pub event: Option>, + /// Pallet constants metadata. + pub constants: Vec>, + /// Pallet error metadata. + pub error: Option>, + /// Define the index of the pallet, this index will be used for the encoding of pallet event, + /// call and origin variants. + pub index: u8, + /// Pallet documentation. + pub docs: Vec, +} + +impl IntoPortable for PalletMetadataIR { + type Output = PalletMetadataIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + PalletMetadataIR { + name: self.name.into_portable(registry), + storage: self.storage.map(|storage| storage.into_portable(registry)), + calls: self.calls.map(|calls| calls.into_portable(registry)), + event: self.event.map(|event| event.into_portable(registry)), + constants: registry.map_into_portable(self.constants), + error: self.error.map(|error| error.into_portable(registry)), + index: self.index, + docs: registry.map_into_portable(self.docs), + } + } +} + +/// Metadata of the extrinsic used by the runtime. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub struct ExtrinsicMetadataIR { + /// The type of the extrinsic. + /// + /// Note: Field used for metadata V14 only. + pub ty: T::Type, + /// Extrinsic version. + pub version: u8, + /// The type of the address that signes the extrinsic + pub address_ty: T::Type, + /// The type of the outermost Call enum. + pub call_ty: T::Type, + /// The type of the extrinsic's signature. + pub signature_ty: T::Type, + /// The type of the outermost Extra enum. + pub extra_ty: T::Type, + /// The signed extensions in the order they appear in the extrinsic. + pub signed_extensions: Vec>, +} + +impl IntoPortable for ExtrinsicMetadataIR { + type Output = ExtrinsicMetadataIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + ExtrinsicMetadataIR { + ty: registry.register_type(&self.ty), + version: self.version, + address_ty: registry.register_type(&self.address_ty), + call_ty: registry.register_type(&self.call_ty), + signature_ty: registry.register_type(&self.signature_ty), + extra_ty: registry.register_type(&self.extra_ty), + signed_extensions: registry.map_into_portable(self.signed_extensions), + } + } +} + +/// Metadata of an extrinsic's signed extension. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub struct SignedExtensionMetadataIR { + /// The unique signed extension identifier, which may be different from the type name. + pub identifier: T::String, + /// The type of the signed extension, with the data to be included in the extrinsic. + pub ty: T::Type, + /// The type of the additional signed data, with the data to be included in the signed payload + pub additional_signed: T::Type, +} + +impl IntoPortable for SignedExtensionMetadataIR { + type Output = SignedExtensionMetadataIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + SignedExtensionMetadataIR { + identifier: self.identifier.into_portable(registry), + ty: registry.register_type(&self.ty), + additional_signed: registry.register_type(&self.additional_signed), + } + } +} + +/// All metadata of the pallet's storage. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +/// The common prefix used by all storage entries. +pub struct PalletStorageMetadataIR { + /// The common prefix used by all storage entries. + pub prefix: T::String, + /// Metadata for all storage entries. + pub entries: Vec>, +} + +impl IntoPortable for PalletStorageMetadataIR { + type Output = PalletStorageMetadataIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + PalletStorageMetadataIR { + prefix: self.prefix.into_portable(registry), + entries: registry.map_into_portable(self.entries), + } + } +} + +/// Metadata about one storage entry. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub struct StorageEntryMetadataIR { + /// Variable name of the storage entry. + pub name: T::String, + /// An `Option` modifier of that storage entry. + pub modifier: StorageEntryModifierIR, + /// Type of the value stored in the entry. + pub ty: StorageEntryTypeIR, + /// Default value (SCALE encoded). + pub default: Vec, + /// Storage entry documentation. + pub docs: Vec, +} + +impl IntoPortable for StorageEntryMetadataIR { + type Output = StorageEntryMetadataIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + StorageEntryMetadataIR { + name: self.name.into_portable(registry), + modifier: self.modifier, + ty: self.ty.into_portable(registry), + default: self.default, + docs: registry.map_into_portable(self.docs), + } + } +} + +/// A storage entry modifier indicates how a storage entry is returned when fetched and what the +/// value will be if the key is not present. Specifically this refers to the "return type" when +/// fetching a storage entry, and what the value will be if the key is not present. +/// +/// `Optional` means you should expect an `Option`, with `None` returned if the key is not +/// present. `Default` means you should expect a `T` with the default value of default if the key is +/// not present. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub enum StorageEntryModifierIR { + /// The storage entry returns an `Option`, with `None` if the key is not present. + Optional, + /// The storage entry returns `T::Default` if the key is not present. + Default, +} + +/// Hasher used by storage maps +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub enum StorageHasherIR { + /// 128-bit Blake2 hash. + Blake2_128, + /// 256-bit Blake2 hash. + Blake2_256, + /// Multiple 128-bit Blake2 hashes concatenated. + Blake2_128Concat, + /// 128-bit XX hash. + Twox128, + /// 256-bit XX hash. + Twox256, + /// Multiple 64-bit XX hashes concatenated. + Twox64Concat, + /// Identity hashing (no hashing). + Identity, +} + +/// A type of storage value. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub enum StorageEntryTypeIR { + /// Plain storage entry (just the value). + Plain(T::Type), + /// A storage map. + Map { + /// One or more hashers, should be one hasher per key element. + hashers: Vec, + /// The type of the key, can be a tuple with elements for each of the hashers. + key: T::Type, + /// The type of the value. + value: T::Type, + }, +} + +impl IntoPortable for StorageEntryTypeIR { + type Output = StorageEntryTypeIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + match self { + Self::Plain(plain) => StorageEntryTypeIR::Plain(registry.register_type(&plain)), + Self::Map { hashers, key, value } => StorageEntryTypeIR::Map { + hashers, + key: registry.register_type(&key), + value: registry.register_type(&value), + }, + } + } +} + +/// Metadata for all calls in a pallet +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub struct PalletCallMetadataIR { + /// The corresponding enum type for the pallet call. + pub ty: T::Type, +} + +impl IntoPortable for PalletCallMetadataIR { + type Output = PalletCallMetadataIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + PalletCallMetadataIR { ty: registry.register_type(&self.ty) } + } +} + +impl From for PalletCallMetadataIR { + fn from(ty: MetaType) -> Self { + Self { ty } + } +} + +/// Metadata about the pallet Event type. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub struct PalletEventMetadataIR { + /// The Event type. + pub ty: T::Type, +} + +impl IntoPortable for PalletEventMetadataIR { + type Output = PalletEventMetadataIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + PalletEventMetadataIR { ty: registry.register_type(&self.ty) } + } +} + +impl From for PalletEventMetadataIR { + fn from(ty: MetaType) -> Self { + Self { ty } + } +} + +/// Metadata about one pallet constant. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub struct PalletConstantMetadataIR { + /// Name of the pallet constant. + pub name: T::String, + /// Type of the pallet constant. + pub ty: T::Type, + /// Value stored in the constant (SCALE encoded). + pub value: Vec, + /// Documentation of the constant. + pub docs: Vec, +} + +impl IntoPortable for PalletConstantMetadataIR { + type Output = PalletConstantMetadataIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + PalletConstantMetadataIR { + name: self.name.into_portable(registry), + ty: registry.register_type(&self.ty), + value: self.value, + docs: registry.map_into_portable(self.docs), + } + } +} + +/// Metadata about a pallet error. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub struct PalletErrorMetadataIR { + /// The error type information. + pub ty: T::Type, +} + +impl IntoPortable for PalletErrorMetadataIR { + type Output = PalletErrorMetadataIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + PalletErrorMetadataIR { ty: registry.register_type(&self.ty) } + } +} + +impl From for PalletErrorMetadataIR { + fn from(ty: MetaType) -> Self { + Self { ty } + } +} + +/// The type of the outer enums. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub struct OuterEnumsIR { + /// The type of the outer `RuntimeCall` enum. + pub call_enum_ty: T::Type, + /// The type of the outer `RuntimeEvent` enum. + pub event_enum_ty: T::Type, + /// The module error type of the + /// [`DispatchError::Module`](https://docs.rs/sp-runtime/24.0.0/sp_runtime/enum.DispatchError.html#variant.Module) variant. + /// + /// The `Module` variant will be 5 scale encoded bytes which are normally decoded into + /// an `{ index: u8, error: [u8; 4] }` struct. This type ID points to an enum type which + /// instead interprets the first `index` byte as a pallet variant, and the remaining `error` + /// bytes as the appropriate `pallet::Error` type. It is an equally valid way to decode the + /// error bytes, and can be more informative. + /// + /// # Note + /// + /// - This type cannot be used directly to decode `sp_runtime::DispatchError` from the chain. + /// It provides just the information needed to decode `sp_runtime::DispatchError::Module`. + /// - Decoding the 5 error bytes into this type will not always lead to all of the bytes being + /// consumed; many error types do not require all of the bytes to represent them fully. + pub error_enum_ty: T::Type, +} + +impl IntoPortable for OuterEnumsIR { + type Output = OuterEnumsIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + OuterEnumsIR { + call_enum_ty: registry.register_type(&self.call_enum_ty), + event_enum_ty: registry.register_type(&self.event_enum_ty), + error_enum_ty: registry.register_type(&self.error_enum_ty), + } + } +} diff --git a/primitives/metadata-ir/src/v14.rs b/primitives/metadata-ir/src/v14.rs new file mode 100644 index 000000000000..e1b7a24f7657 --- /dev/null +++ b/primitives/metadata-ir/src/v14.rs @@ -0,0 +1,158 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Convert the IR to V14 metadata. + +use super::types::{ + ExtrinsicMetadataIR, MetadataIR, PalletCallMetadataIR, PalletConstantMetadataIR, + PalletErrorMetadataIR, PalletEventMetadataIR, PalletMetadataIR, PalletStorageMetadataIR, + SignedExtensionMetadataIR, StorageEntryMetadataIR, StorageEntryModifierIR, StorageEntryTypeIR, + StorageHasherIR, +}; + +use frame_metadata::v14::{ + ExtrinsicMetadata, PalletCallMetadata, PalletConstantMetadata, PalletErrorMetadata, + PalletEventMetadata, PalletMetadata, PalletStorageMetadata, RuntimeMetadataV14, + SignedExtensionMetadata, StorageEntryMetadata, StorageEntryModifier, StorageEntryType, + StorageHasher, +}; + +impl From for RuntimeMetadataV14 { + fn from(ir: MetadataIR) -> Self { + RuntimeMetadataV14::new( + ir.pallets.into_iter().map(Into::into).collect(), + ir.extrinsic.into(), + ir.ty, + ) + } +} + +impl From for PalletMetadata { + fn from(ir: PalletMetadataIR) -> Self { + PalletMetadata { + name: ir.name, + storage: ir.storage.map(Into::into), + calls: ir.calls.map(Into::into), + event: ir.event.map(Into::into), + constants: ir.constants.into_iter().map(Into::into).collect(), + error: ir.error.map(Into::into), + index: ir.index, + // Note: ir.docs not part of v14. + } + } +} + +impl From for StorageEntryModifier { + fn from(ir: StorageEntryModifierIR) -> Self { + match ir { + StorageEntryModifierIR::Optional => StorageEntryModifier::Optional, + StorageEntryModifierIR::Default => StorageEntryModifier::Default, + } + } +} + +impl From for StorageHasher { + fn from(ir: StorageHasherIR) -> Self { + match ir { + StorageHasherIR::Blake2_128 => StorageHasher::Blake2_128, + StorageHasherIR::Blake2_256 => StorageHasher::Blake2_256, + StorageHasherIR::Blake2_128Concat => StorageHasher::Blake2_128Concat, + StorageHasherIR::Twox128 => StorageHasher::Twox128, + StorageHasherIR::Twox256 => StorageHasher::Twox256, + StorageHasherIR::Twox64Concat => StorageHasher::Twox64Concat, + StorageHasherIR::Identity => StorageHasher::Identity, + } + } +} + +impl From for StorageEntryType { + fn from(ir: StorageEntryTypeIR) -> Self { + match ir { + StorageEntryTypeIR::Plain(ty) => StorageEntryType::Plain(ty), + StorageEntryTypeIR::Map { hashers, key, value } => StorageEntryType::Map { + hashers: hashers.into_iter().map(Into::into).collect(), + key, + value, + }, + } + } +} + +impl From for StorageEntryMetadata { + fn from(ir: StorageEntryMetadataIR) -> Self { + StorageEntryMetadata { + name: ir.name, + modifier: ir.modifier.into(), + ty: ir.ty.into(), + default: ir.default, + docs: ir.docs, + } + } +} + +impl From for PalletStorageMetadata { + fn from(ir: PalletStorageMetadataIR) -> Self { + PalletStorageMetadata { + prefix: ir.prefix, + entries: ir.entries.into_iter().map(Into::into).collect(), + } + } +} + +impl From for PalletCallMetadata { + fn from(ir: PalletCallMetadataIR) -> Self { + PalletCallMetadata { ty: ir.ty } + } +} + +impl From for PalletEventMetadata { + fn from(ir: PalletEventMetadataIR) -> Self { + PalletEventMetadata { ty: ir.ty } + } +} + +impl From for PalletConstantMetadata { + fn from(ir: PalletConstantMetadataIR) -> Self { + PalletConstantMetadata { name: ir.name, ty: ir.ty, value: ir.value, docs: ir.docs } + } +} + +impl From for PalletErrorMetadata { + fn from(ir: PalletErrorMetadataIR) -> Self { + PalletErrorMetadata { ty: ir.ty } + } +} + +impl From for SignedExtensionMetadata { + fn from(ir: SignedExtensionMetadataIR) -> Self { + SignedExtensionMetadata { + identifier: ir.identifier, + ty: ir.ty, + additional_signed: ir.additional_signed, + } + } +} + +impl From for ExtrinsicMetadata { + fn from(ir: ExtrinsicMetadataIR) -> Self { + ExtrinsicMetadata { + ty: ir.ty, + version: ir.version, + signed_extensions: ir.signed_extensions.into_iter().map(Into::into).collect(), + } + } +} diff --git a/primitives/metadata-ir/src/v15.rs b/primitives/metadata-ir/src/v15.rs new file mode 100644 index 000000000000..a942eb73223b --- /dev/null +++ b/primitives/metadata-ir/src/v15.rs @@ -0,0 +1,121 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Convert the IR to V15 metadata. + +use crate::OuterEnumsIR; + +use super::types::{ + ExtrinsicMetadataIR, MetadataIR, PalletMetadataIR, RuntimeApiMetadataIR, + RuntimeApiMethodMetadataIR, RuntimeApiMethodParamMetadataIR, SignedExtensionMetadataIR, +}; + +use frame_metadata::v15::{ + CustomMetadata, ExtrinsicMetadata, OuterEnums, PalletMetadata, RuntimeApiMetadata, + RuntimeApiMethodMetadata, RuntimeApiMethodParamMetadata, RuntimeMetadataV15, + SignedExtensionMetadata, +}; + +impl From for RuntimeMetadataV15 { + fn from(ir: MetadataIR) -> Self { + RuntimeMetadataV15::new( + ir.pallets.into_iter().map(Into::into).collect(), + ir.extrinsic.into(), + ir.ty, + ir.apis.into_iter().map(Into::into).collect(), + ir.outer_enums.into(), + // Substrate does not collect yet the custom metadata fields. + // This allows us to extend the V15 easily. + CustomMetadata { map: Default::default() }, + ) + } +} + +impl From for RuntimeApiMetadata { + fn from(ir: RuntimeApiMetadataIR) -> Self { + RuntimeApiMetadata { + name: ir.name, + methods: ir.methods.into_iter().map(Into::into).collect(), + docs: ir.docs, + } + } +} + +impl From for RuntimeApiMethodMetadata { + fn from(ir: RuntimeApiMethodMetadataIR) -> Self { + RuntimeApiMethodMetadata { + name: ir.name, + inputs: ir.inputs.into_iter().map(Into::into).collect(), + output: ir.output, + docs: ir.docs, + } + } +} + +impl From for RuntimeApiMethodParamMetadata { + fn from(ir: RuntimeApiMethodParamMetadataIR) -> Self { + RuntimeApiMethodParamMetadata { name: ir.name, ty: ir.ty } + } +} + +impl From for PalletMetadata { + fn from(ir: PalletMetadataIR) -> Self { + PalletMetadata { + name: ir.name, + storage: ir.storage.map(Into::into), + calls: ir.calls.map(Into::into), + event: ir.event.map(Into::into), + constants: ir.constants.into_iter().map(Into::into).collect(), + error: ir.error.map(Into::into), + index: ir.index, + docs: ir.docs, + } + } +} + +impl From for SignedExtensionMetadata { + fn from(ir: SignedExtensionMetadataIR) -> Self { + SignedExtensionMetadata { + identifier: ir.identifier, + ty: ir.ty, + additional_signed: ir.additional_signed, + } + } +} + +impl From for ExtrinsicMetadata { + fn from(ir: ExtrinsicMetadataIR) -> Self { + ExtrinsicMetadata { + version: ir.version, + address_ty: ir.address_ty, + call_ty: ir.call_ty, + signature_ty: ir.signature_ty, + extra_ty: ir.extra_ty, + signed_extensions: ir.signed_extensions.into_iter().map(Into::into).collect(), + } + } +} + +impl From for OuterEnums { + fn from(ir: OuterEnumsIR) -> Self { + OuterEnums { + call_enum_ty: ir.call_enum_ty, + event_enum_ty: ir.event_enum_ty, + error_enum_ty: ir.error_enum_ty, + } + } +} diff --git a/primitives/npos-elections/Cargo.toml b/primitives/npos-elections/Cargo.toml index 7a4bdbd679f0..cd704082f571 100644 --- a/primitives/npos-elections/Cargo.toml +++ b/primitives/npos-elections/Cargo.toml @@ -13,13 +13,13 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", features = ["derive"], optional = true } -sp-arithmetic = { version = "6.0.0", default-features = false, path = "../arithmetic" } -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", default-features = false, features = ["derive", "alloc"], optional = true } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../arithmetic" } +sp-core = { version = "21.0.0", default-features = false, path = "../core" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } [dev-dependencies] rand = "0.8.5" @@ -31,9 +31,18 @@ bench = [] std = [ "codec/std", "scale-info/std", - "serde", + "serde/std", "sp-arithmetic/std", "sp-core/std", "sp-runtime/std", "sp-std/std", ] + +# Serde support without relying on std features. +serde = [ + "dep:serde", + "scale-info/serde", + "sp-arithmetic/serde", + "sp-core/serde", + "sp-runtime/serde", +] diff --git a/primitives/npos-elections/fuzzer/Cargo.toml b/primitives/npos-elections/fuzzer/Cargo.toml index 8a058cc92edc..acb2b7d89a5c 100644 --- a/primitives/npos-elections/fuzzer/Cargo.toml +++ b/primitives/npos-elections/fuzzer/Cargo.toml @@ -14,13 +14,11 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.0.9", features = ["derive"] } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +clap = { version = "4.2.5", features = ["derive"] } honggfuzz = "0.5" rand = { version = "0.8", features = ["std", "small_rng"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } sp-npos-elections = { version = "4.0.0-dev", path = ".." } -sp-runtime = { version = "7.0.0", path = "../../runtime" } +sp-runtime = { version = "24.0.0", path = "../../runtime" } [[bin]] name = "reduce" diff --git a/primitives/npos-elections/fuzzer/src/common.rs b/primitives/npos-elections/fuzzer/src/common.rs index ad9bd43f9bce..ebf103401a62 100644 --- a/primitives/npos-elections/fuzzer/src/common.rs +++ b/primitives/npos-elections/fuzzer/src/common.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs b/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs index e053f9aa0cdd..c7aaf6ceed2a 100644 --- a/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs +++ b/primitives/npos-elections/fuzzer/src/phragmen_balancing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/npos-elections/fuzzer/src/phragmen_pjr.rs b/primitives/npos-elections/fuzzer/src/phragmen_pjr.rs index 0401249a3df1..94e697d9c4d8 100644 --- a/primitives/npos-elections/fuzzer/src/phragmen_pjr.rs +++ b/primitives/npos-elections/fuzzer/src/phragmen_pjr.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs b/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs index 3f114674e29d..067b788f01cb 100644 --- a/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs +++ b/primitives/npos-elections/fuzzer/src/phragmms_balancing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/npos-elections/fuzzer/src/reduce.rs b/primitives/npos-elections/fuzzer/src/reduce.rs index 602467a34388..9ac34189d2e8 100644 --- a/primitives/npos-elections/fuzzer/src/reduce.rs +++ b/primitives/npos-elections/fuzzer/src/reduce.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/npos-elections/src/assignments.rs b/primitives/npos-elections/src/assignments.rs index fc88ef40010b..2ac2b9bebd77 100644 --- a/primitives/npos-elections/src/assignments.rs +++ b/primitives/npos-elections/src/assignments.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,7 @@ //! Structs and helpers for distributing a voter's stake among various winners. use crate::{ExtendedBalance, IdentifierT, PerThing128}; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use codec::{Decode, Encode}; use sp_arithmetic::{ traits::{Bounded, Zero}, @@ -29,7 +29,7 @@ use sp_std::vec::Vec; /// A voter's stake assignment among a set of targets, represented as ratios. #[derive(RuntimeDebug, Clone, Default)] -#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))] +#[cfg_attr(feature = "serde", derive(PartialEq, Eq, Encode, Decode))] pub struct Assignment { /// Voter's identifier. pub who: AccountId, @@ -97,7 +97,7 @@ impl Assignment { /// A voter's stake assignment among a set of targets, represented as absolute values in the scale /// of [`ExtendedBalance`]. #[derive(RuntimeDebug, Clone, Default)] -#[cfg_attr(feature = "std", derive(PartialEq, Eq, Encode, Decode))] +#[cfg_attr(feature = "serde", derive(PartialEq, Eq, Encode, Decode))] pub struct StakedAssignment { /// Voter's identifier pub who: AccountId, diff --git a/primitives/npos-elections/src/balancing.rs b/primitives/npos-elections/src/balancing.rs index 4a713658ad38..90dbe7eb7147 100644 --- a/primitives/npos-elections/src/balancing.rs +++ b/primitives/npos-elections/src/balancing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,7 @@ //! //! Given a committee `A` and an edge weight vector `w`, a balanced solution is one that //! -//! 1. it maximizes the sum of member supports, i.e `Argmax { sum(support(c)) }`. for all `c` in +//! 1. it maximizes the sum of member supports, i.e `Argmax { sum(support(c)) }`. for all `c` in //! `A`. //! 2. it minimizes the sum of supports squared, i.e `Argmin { sum(support(c).pow(2)) }` for all `c` //! in `A`. diff --git a/primitives/npos-elections/src/helpers.rs b/primitives/npos-elections/src/helpers.rs index 598eb3b2ecc2..082491ea0428 100644 --- a/primitives/npos-elections/src/helpers.rs +++ b/primitives/npos-elections/src/helpers.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/npos-elections/src/lib.rs b/primitives/npos-elections/src/lib.rs index d0c9ed18cadd..9239a2d90d30 100644 --- a/primitives/npos-elections/src/lib.rs +++ b/primitives/npos-elections/src/lib.rs @@ -1,6 +1,7 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. SPDX-License-Identifier: Apache-2.0 +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except // in compliance with the License. You may obtain a copy of the License at @@ -76,7 +77,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use sp_arithmetic::{traits::Zero, Normalizable, PerThing, Rational128, ThresholdOrd}; use sp_core::{bounded::BoundedVec, RuntimeDebug}; @@ -143,7 +144,7 @@ pub type ExtendedBalance = u128; /// 2. `sum_stake`. /// 3. `sum_stake_squared`. #[derive(Clone, Copy, PartialEq, Eq, Encode, Decode, MaxEncodedLen, TypeInfo, Debug, Default)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct ElectionScore { /// The minimal winner, in terms of total backing stake. /// @@ -429,7 +430,7 @@ pub struct ElectionResult { /// This, at the current version, resembles the `Exposure` defined in the Staking pallet, yet they /// do not necessarily have to be the same. #[derive(RuntimeDebug, Encode, Decode, Clone, Eq, PartialEq, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Support { /// Total support. pub total: ExtendedBalance, @@ -469,7 +470,7 @@ pub fn to_support_map( // build support struct. for StakedAssignment { who, distribution } in assignments.iter() { for (c, weight_extended) in distribution.iter() { - let mut support = supports.entry(c.clone()).or_default(); + let support = supports.entry(c.clone()).or_default(); support.total = support.total.saturating_add(*weight_extended); support.voters.push((who.clone(), *weight_extended)); } diff --git a/primitives/npos-elections/src/mock.rs b/primitives/npos-elections/src/mock.rs index 5a06e3f3c88c..2fc49fd72cd0 100644 --- a/primitives/npos-elections/src/mock.rs +++ b/primitives/npos-elections/src/mock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/npos-elections/src/node.rs b/primitives/npos-elections/src/node.rs index 6642a9ae3973..caca9561d839 100644 --- a/primitives/npos-elections/src/node.rs +++ b/primitives/npos-elections/src/node.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/npos-elections/src/phragmen.rs b/primitives/npos-elections/src/phragmen.rs index ca32780ed84b..c3578065f364 100644 --- a/primitives/npos-elections/src/phragmen.rs +++ b/primitives/npos-elections/src/phragmen.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/npos-elections/src/phragmms.rs b/primitives/npos-elections/src/phragmms.rs index 3fbbad75e2f8..df6becf47472 100644 --- a/primitives/npos-elections/src/phragmms.rs +++ b/primitives/npos-elections/src/phragmms.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/npos-elections/src/pjr.rs b/primitives/npos-elections/src/pjr.rs index fd7c8ef53924..f0e59a25d440 100644 --- a/primitives/npos-elections/src/pjr.rs +++ b/primitives/npos-elections/src/pjr.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/npos-elections/src/reduce.rs b/primitives/npos-elections/src/reduce.rs index c802a2950470..6a5a0159e4ef 100644 --- a/primitives/npos-elections/src/reduce.rs +++ b/primitives/npos-elections/src/reduce.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/npos-elections/src/tests.rs b/primitives/npos-elections/src/tests.rs index 6f2e4fca7711..72ae9a0222be 100644 --- a/primitives/npos-elections/src/tests.rs +++ b/primitives/npos-elections/src/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/npos-elections/src/traits.rs b/primitives/npos-elections/src/traits.rs index 91026f9de4b6..d49970873b70 100644 --- a/primitives/npos-elections/src/traits.rs +++ b/primitives/npos-elections/src/traits.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/offchain/Cargo.toml b/primitives/offchain/Cargo.toml index cb567893776e..6d73bf921213 100644 --- a/primitives/offchain/Cargo.toml +++ b/primitives/offchain/Cargo.toml @@ -14,8 +14,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } +sp-core = { version = "21.0.0", default-features = false, path = "../core" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../runtime" } [features] default = ["std"] diff --git a/primitives/offchain/src/lib.rs b/primitives/offchain/src/lib.rs index 3e967f16d2a8..56de4f1589f8 100644 --- a/primitives/offchain/src/lib.rs +++ b/primitives/offchain/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/panic-handler/Cargo.toml b/primitives/panic-handler/Cargo.toml index 9da052b4a05e..e73cfa98ca41 100644 --- a/primitives/panic-handler/Cargo.toml +++ b/primitives/panic-handler/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-panic-handler" -version = "5.0.0" +version = "8.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" diff --git a/primitives/panic-handler/src/lib.rs b/primitives/panic-handler/src/lib.rs index e06fe90ad6f3..e2a9bfa195a6 100644 --- a/primitives/panic-handler/src/lib.rs +++ b/primitives/panic-handler/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/rpc/Cargo.toml b/primitives/rpc/Cargo.toml index ef9fdc544301..37840c38e79f 100644 --- a/primitives/rpc/Cargo.toml +++ b/primitives/rpc/Cargo.toml @@ -14,8 +14,8 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] rustc-hash = "1.1.0" -serde = { version = "1.0.136", features = ["derive"] } -sp-core = { version = "7.0.0", path = "../core" } +serde = { version = "1.0.163", features = ["derive"] } +sp-core = { version = "21.0.0", path = "../core" } [dev-dependencies] serde_json = "1.0.85" diff --git a/primitives/rpc/src/lib.rs b/primitives/rpc/src/lib.rs index 915482e5fca7..4dbc629bb967 100644 --- a/primitives/rpc/src/lib.rs +++ b/primitives/rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/rpc/src/list.rs b/primitives/rpc/src/list.rs index 7ec4d3491c7e..860e5161b97c 100644 --- a/primitives/rpc/src/list.rs +++ b/primitives/rpc/src/list.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/rpc/src/number.rs b/primitives/rpc/src/number.rs index 81084a09d4ac..28caa243eb70 100644 --- a/primitives/rpc/src/number.rs +++ b/primitives/rpc/src/number.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/rpc/src/tracing.rs b/primitives/rpc/src/tracing.rs index a923ffcb69e0..eb3fd4784335 100644 --- a/primitives/rpc/src/tracing.rs +++ b/primitives/rpc/src/tracing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/Cargo.toml b/primitives/runtime-interface/Cargo.toml index f2ddc84b1e2c..3248baafae3f 100644 --- a/primitives/runtime-interface/Cargo.toml +++ b/primitives/runtime-interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-runtime-interface" -version = "7.0.0" +version = "17.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -15,22 +15,22 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] bytes = { version = "1.1.0", default-features = false } -sp-wasm-interface = { version = "7.0.0", path = "../wasm-interface", default-features = false } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } -sp-tracing = { version = "6.0.0", default-features = false, path = "../tracing" } -sp-runtime-interface-proc-macro = { version = "6.0.0", path = "proc-macro" } -sp-externalities = { version = "0.13.0", default-features = false, path = "../externalities" } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["bytes"] } +sp-wasm-interface = { version = "14.0.0", path = "../wasm-interface", default-features = false } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } +sp-tracing = { version = "10.0.0", default-features = false, path = "../tracing" } +sp-runtime-interface-proc-macro = { version = "11.0.0", path = "proc-macro" } +sp-externalities = { version = "0.19.0", default-features = false, path = "../externalities" } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["bytes"] } static_assertions = "1.0.0" primitive-types = { version = "0.12.0", default-features = false } -sp-storage = { version = "7.0.0", default-features = false, path = "../storage" } +sp-storage = { version = "13.0.0", default-features = false, path = "../storage" } impl-trait-for-tuples = "0.2.2" [dev-dependencies] sp-runtime-interface-test-wasm = { version = "2.0.0", path = "test-wasm" } -sp-state-machine = { version = "0.13.0", path = "../state-machine" } -sp-core = { version = "7.0.0", path = "../core" } -sp-io = { version = "7.0.0", path = "../io" } +sp-state-machine = { version = "0.28.0", path = "../state-machine" } +sp-core = { version = "21.0.0", path = "../core" } +sp-io = { version = "23.0.0", path = "../io" } rustversion = "1.0.6" trybuild = "1.0.74" diff --git a/primitives/runtime-interface/proc-macro/Cargo.toml b/primitives/runtime-interface/proc-macro/Cargo.toml index 9bc7161012cb..4b50dfe2a7a1 100644 --- a/primitives/runtime-interface/proc-macro/Cargo.toml +++ b/primitives/runtime-interface/proc-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-runtime-interface-proc-macro" -version = "6.0.0" +version = "11.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -18,6 +18,6 @@ proc-macro = true [dependencies] Inflector = "0.11.4" proc-macro-crate = "1.1.3" -proc-macro2 = "1.0.37" -quote = "1.0.10" -syn = { version = "1.0.98", features = ["full", "visit", "fold", "extra-traits"] } +proc-macro2 = "1.0.56" +quote = "1.0.28" +syn = { version = "2.0.16", features = ["full", "visit", "fold", "extra-traits"] } diff --git a/primitives/runtime-interface/proc-macro/src/lib.rs b/primitives/runtime-interface/proc-macro/src/lib.rs index afba38993fe7..e6f060c219e0 100644 --- a/primitives/runtime-interface/proc-macro/src/lib.rs +++ b/primitives/runtime-interface/proc-macro/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/proc-macro/src/pass_by/codec.rs b/primitives/runtime-interface/proc-macro/src/pass_by/codec.rs index 4259b137d67c..a1b7bccd3acc 100644 --- a/primitives/runtime-interface/proc-macro/src/pass_by/codec.rs +++ b/primitives/runtime-interface/proc-macro/src/pass_by/codec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/proc-macro/src/pass_by/enum_.rs b/primitives/runtime-interface/proc-macro/src/pass_by/enum_.rs index e25295fdca5c..0d05dd9aa51e 100644 --- a/primitives/runtime-interface/proc-macro/src/pass_by/enum_.rs +++ b/primitives/runtime-interface/proc-macro/src/pass_by/enum_.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/proc-macro/src/pass_by/inner.rs b/primitives/runtime-interface/proc-macro/src/pass_by/inner.rs index 7a527af12946..cc51fe44f912 100644 --- a/primitives/runtime-interface/proc-macro/src/pass_by/inner.rs +++ b/primitives/runtime-interface/proc-macro/src/pass_by/inner.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -52,7 +52,7 @@ pub fn derive_impl(mut input: DeriveInput) -> Result { #crate_include impl #impl_generics #crate_::pass_by::PassBy for #ident #ty_generics #where_clause { - type PassBy = #crate_::pass_by::Inner<#ident, #inner_ty>; + type PassBy = #crate_::pass_by::Inner; } impl #impl_generics #crate_::pass_by::PassByInner for #ident #ty_generics #where_clause { diff --git a/primitives/runtime-interface/proc-macro/src/pass_by/mod.rs b/primitives/runtime-interface/proc-macro/src/pass_by/mod.rs index e32c2beb8b72..f3d51d36248d 100644 --- a/primitives/runtime-interface/proc-macro/src/pass_by/mod.rs +++ b/primitives/runtime-interface/proc-macro/src/pass_by/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs b/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs index b5745e25deb4..77a29bec3807 100644 --- a/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs +++ b/primitives/runtime-interface/proc-macro/src/runtime_interface/bare_function_interface.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,8 +36,7 @@ use crate::utils::{ }; use syn::{ - parse_quote, spanned::Spanned, FnArg, Ident, ItemTrait, Result, Signature, Token, - TraitItemMethod, + parse_quote, spanned::Spanned, FnArg, Ident, ItemTrait, Result, Signature, Token, TraitItemFn, }; use proc_macro2::{Span, TokenStream}; @@ -116,7 +115,7 @@ fn function_no_std_impl( quote! {} }; - let attrs = method.attrs.iter().filter(|a| !a.path.is_ident("version")); + let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version")); let cfg_wasm_only = if is_wasm_only { quote! { #[cfg(target_arch = "wasm32")] } @@ -139,12 +138,12 @@ fn function_no_std_impl( /// Generate call to latest function version for `cfg((feature = "std")` /// /// This should generate simple `fn func(..) { func_version_(..) }`. -fn function_std_latest_impl(method: &TraitItemMethod, latest_version: u32) -> Result { +fn function_std_latest_impl(method: &TraitItemFn, latest_version: u32) -> Result { let function_name = &method.sig.ident; let args = get_function_arguments(&method.sig).map(FnArg::Typed); let arg_names = get_function_argument_names(&method.sig).collect::>(); let return_value = &method.sig.output; - let attrs = method.attrs.iter().filter(|a| !a.path.is_ident("version")); + let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version")); let latest_function_name = create_function_ident_with_version(&method.sig.ident, latest_version); @@ -162,7 +161,7 @@ fn function_std_latest_impl(method: &TraitItemMethod, latest_version: u32) -> Re /// Generates the bare function implementation for `cfg(feature = "std")`. fn function_std_impl( trait_name: &Ident, - method: &TraitItemMethod, + method: &TraitItemFn, version: u32, is_wasm_only: bool, tracing: bool, @@ -185,7 +184,7 @@ fn function_std_impl( .take(1), ); let return_value = &method.sig.output; - let attrs = method.attrs.iter().filter(|a| !a.path.is_ident("version")); + let attrs = method.attrs.iter().filter(|a| !a.path().is_ident("version")); // Don't make the function public accessible when this is a wasm only interface. let call_to_trait = generate_call_to_trait(trait_name, method, version, is_wasm_only); let call_to_trait = if !tracing { @@ -210,7 +209,7 @@ fn function_std_impl( /// Generate the call to the interface trait. fn generate_call_to_trait( trait_name: &Ident, - method: &TraitItemMethod, + method: &TraitItemFn, version: u32, is_wasm_only: bool, ) -> TokenStream { diff --git a/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs b/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs index 03da0bed5981..77a9e56eecba 100644 --- a/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs +++ b/primitives/runtime-interface/proc-macro/src/runtime_interface/host_function_interface.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,7 +30,7 @@ use crate::utils::{ }; use syn::{ - spanned::Spanned, Error, Ident, ItemTrait, Pat, Result, ReturnType, Signature, TraitItemMethod, + spanned::Spanned, Error, Ident, ItemTrait, Pat, Result, ReturnType, Signature, TraitItemFn, }; use proc_macro2::{Span, TokenStream}; @@ -78,7 +78,7 @@ pub fn generate(trait_def: &ItemTrait, is_wasm_only: bool) -> Result Result { @@ -96,6 +96,7 @@ fn generate_extern_host_function( method.sig.ident, ); let return_value = &method.sig.output; + let cfg_attrs = method.attrs.iter().filter(|a| a.path().is_ident("cfg")); let ffi_return_value = match method.sig.output { ReturnType::Default => quote!(), @@ -112,6 +113,7 @@ fn generate_extern_host_function( }; Ok(quote! { + #(#cfg_attrs)* #[doc = #doc_string] pub fn #function ( #( #args ),* ) #return_value { extern "C" { @@ -136,15 +138,17 @@ fn generate_extern_host_function( } /// Generate the host exchangeable function for the given method. -fn generate_exchangeable_host_function(method: &TraitItemMethod) -> Result { +fn generate_exchangeable_host_function(method: &TraitItemFn) -> Result { let crate_ = generate_crate_access(); let arg_types = get_function_argument_types(&method.sig); let function = &method.sig.ident; let exchangeable_function = create_exchangeable_host_function_ident(&method.sig.ident); let doc_string = format!(" Exchangeable host function used by [`{}`].", method.sig.ident); let output = &method.sig.output; + let cfg_attrs = method.attrs.iter().filter(|a| a.path().is_ident("cfg")); Ok(quote! { + #(#cfg_attrs)* #[cfg(not(feature = "std"))] #[allow(non_upper_case_globals)] #[doc = #doc_string] @@ -163,14 +167,15 @@ fn generate_host_functions_struct( let crate_ = generate_crate_access(); let mut host_function_impls = Vec::new(); - let mut host_function_names = Vec::new(); let mut register_bodies = Vec::new(); + let mut append_hf_bodies = Vec::new(); + for (version, method) in get_runtime_interface(trait_def)?.all_versions() { - let (implementation, name, register_body) = + let (implementation, register_body, append_hf_body) = generate_host_function_implementation(&trait_def.ident, method, version, is_wasm_only)?; host_function_impls.push(implementation); - host_function_names.push(name); register_bodies.push(register_body); + append_hf_bodies.push(append_hf_body); } Ok(quote! { @@ -183,7 +188,9 @@ fn generate_host_functions_struct( #[cfg(feature = "std")] impl #crate_::sp_wasm_interface::HostFunctions for HostFunctions { fn host_functions() -> Vec<&'static dyn #crate_::sp_wasm_interface::Function> { - vec![ #( &#host_function_names as &dyn #crate_::sp_wasm_interface::Function ),* ] + let mut host_functions_list = Vec::new(); + #(#append_hf_bodies)* + host_functions_list } #crate_::sp_wasm_interface::if_wasmtime_is_enabled! { @@ -208,7 +215,7 @@ fn generate_host_function_implementation( method: &RuntimeInterfaceFunction, version: u32, is_wasm_only: bool, -) -> Result<(TokenStream, Ident, TokenStream)> { +) -> Result<(TokenStream, TokenStream, TokenStream)> { let name = create_host_function_ident(&method.sig.ident, version, trait_name).to_string(); let struct_name = Ident::new(&name.to_pascal_case(), Span::call_site()); let crate_ = generate_crate_access(); @@ -323,10 +330,21 @@ fn generate_host_function_implementation( }); } + let cfg_attrs: Vec<_> = + method.attrs.iter().filter(|a| a.path().is_ident("cfg")).cloned().collect(); + if version > 1 && !cfg_attrs.is_empty() { + return Err(Error::new( + method.span(), + "Conditional compilation is not supported for versioned functions", + )) + } + let implementation = quote! { + #(#cfg_attrs)* #[cfg(feature = "std")] struct #struct_name; + #(#cfg_attrs)* #[cfg(feature = "std")] impl #struct_name { fn call( @@ -341,6 +359,7 @@ fn generate_host_function_implementation( } } + #(#cfg_attrs)* #[cfg(feature = "std")] impl #crate_::sp_wasm_interface::Function for #struct_name { fn name(&self) -> &str { @@ -368,17 +387,18 @@ fn generate_host_function_implementation( }; let register_body = quote! { + #(#cfg_attrs)* registry.register_static( #crate_::sp_wasm_interface::Function::name(&#struct_name), |mut caller: #crate_::sp_wasm_interface::wasmtime::Caller, #(#ffi_args_prototype),*| - -> std::result::Result<#ffi_return_ty, #crate_::sp_wasm_interface::wasmtime::Trap> + -> std::result::Result<#ffi_return_ty, #crate_::sp_wasm_interface::anyhow::Error> { T::with_function_context(caller, move |__function_context__| { let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { #struct_name::call( __function_context__, #(#ffi_names,)* - ).map_err(#crate_::sp_wasm_interface::wasmtime::Trap::new) + ).map_err(#crate_::sp_wasm_interface::anyhow::Error::msg) })); match result { Ok(result) => result, @@ -391,7 +411,7 @@ fn generate_host_function_implementation( } else { "host code panicked while being called by the runtime".to_owned() }; - return Err(#crate_::sp_wasm_interface::wasmtime::Trap::new(message)); + return Err(#crate_::sp_wasm_interface::anyhow::Error::msg(message)); } } }) @@ -399,7 +419,12 @@ fn generate_host_function_implementation( )?; }; - Ok((implementation, struct_name, register_body)) + let append_hf_body = quote! { + #(#cfg_attrs)* + host_functions_list.push(&#struct_name as &dyn #crate_::sp_wasm_interface::Function); + }; + + Ok((implementation, register_body, append_hf_body)) } /// Generate the `wasm_interface::Signature` for the given host function `sig`. diff --git a/primitives/runtime-interface/proc-macro/src/runtime_interface/mod.rs b/primitives/runtime-interface/proc-macro/src/runtime_interface/mod.rs index d14c1f67ecff..008d69b32100 100644 --- a/primitives/runtime-interface/proc-macro/src/runtime_interface/mod.rs +++ b/primitives/runtime-interface/proc-macro/src/runtime_interface/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/proc-macro/src/runtime_interface/trait_decl_impl.rs b/primitives/runtime-interface/proc-macro/src/runtime_interface/trait_decl_impl.rs index 0ae0f5260286..540e930b0c14 100644 --- a/primitives/runtime-interface/proc-macro/src/runtime_interface/trait_decl_impl.rs +++ b/primitives/runtime-interface/proc-macro/src/runtime_interface/trait_decl_impl.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,7 +26,7 @@ use crate::utils::{ use syn::{ fold::{self, Fold}, spanned::Spanned, - Error, Generics, ItemTrait, Receiver, Result, TraitItemMethod, Type, Visibility, + Error, Generics, ItemTrait, Receiver, Result, TraitItemFn, Type, Visibility, }; use proc_macro2::TokenStream; @@ -51,7 +51,7 @@ pub fn process(trait_def: &ItemTrait, is_wasm_only: bool) -> Result struct ToEssentialTraitDef { /// All errors found while doing the conversion. errors: Vec, - methods: Vec, + methods: Vec, } impl ToEssentialTraitDef { @@ -59,7 +59,7 @@ impl ToEssentialTraitDef { ToEssentialTraitDef { errors: vec![], methods: vec![] } } - fn into_methods(self) -> Result> { + fn into_methods(self) -> Result> { let mut errors = self.errors; let methods = self.methods; if let Some(first_error) = errors.pop() { @@ -72,8 +72,8 @@ impl ToEssentialTraitDef { } } - fn process(&mut self, method: &TraitItemMethod, version: u32) { - let mut folded = self.fold_trait_item_method(method.clone()); + fn process(&mut self, method: &TraitItemFn, version: u32) { + let mut folded = self.fold_trait_item_fn(method.clone()); folded.sig.ident = create_function_ident_with_version(&folded.sig.ident, version); self.methods.push(folded); } @@ -90,7 +90,7 @@ impl ToEssentialTraitDef { } impl Fold for ToEssentialTraitDef { - fn fold_trait_item_method(&mut self, mut method: TraitItemMethod) -> TraitItemMethod { + fn fold_trait_item_fn(&mut self, mut method: TraitItemFn) -> TraitItemFn { if method.default.take().is_none() { self.push_error(&method, "Methods need to have an implementation."); } @@ -105,9 +105,9 @@ impl Fold for ToEssentialTraitDef { self.error_on_generic_parameters(&method.sig.generics); - method.attrs.retain(|a| !a.path.is_ident("version")); + method.attrs.retain(|a| !a.path().is_ident("version")); - fold::fold_trait_item_method(self, method) + fold::fold_trait_item_fn(self, method) } fn fold_item_trait(&mut self, mut trait_def: ItemTrait) -> ItemTrait { @@ -154,7 +154,7 @@ fn impl_trait_for_externalities(trait_def: &ItemTrait, is_wasm_only: bool) -> Re let interface = get_runtime_interface(trait_def)?; let methods = interface.all_versions().map(|(version, method)| { let mut cloned = (*method).clone(); - cloned.attrs.retain(|a| !a.path.is_ident("version")); + cloned.attrs.retain(|a| !a.path().is_ident("version")); cloned.sig.ident = create_function_ident_with_version(&cloned.sig.ident, version); cloned }); diff --git a/primitives/runtime-interface/proc-macro/src/utils.rs b/primitives/runtime-interface/proc-macro/src/utils.rs index 386eef153f45..9818fd6842a6 100644 --- a/primitives/runtime-interface/proc-macro/src/utils.rs +++ b/primitives/runtime-interface/proc-macro/src/utils.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,7 @@ use proc_macro2::{Span, TokenStream}; use syn::{ parse::Parse, parse_quote, spanned::Spanned, token, Error, FnArg, Ident, ItemTrait, LitInt, - Pat, PatType, Result, Signature, TraitItem, TraitItemMethod, Type, + Pat, PatType, Result, Signature, TraitItem, TraitItemFn, Type, }; use proc_macro_crate::{crate_name, FoundCrate}; @@ -41,23 +41,23 @@ mod attributes { /// A concrete, specific version of a runtime interface function. pub struct RuntimeInterfaceFunction { - item: TraitItemMethod, + item: TraitItemFn, should_trap_on_return: bool, } impl std::ops::Deref for RuntimeInterfaceFunction { - type Target = TraitItemMethod; + type Target = TraitItemFn; fn deref(&self) -> &Self::Target { &self.item } } impl RuntimeInterfaceFunction { - fn new(item: &TraitItemMethod) -> Result { + fn new(item: &TraitItemFn) -> Result { let mut item = item.clone(); let mut should_trap_on_return = false; item.attrs.retain(|attr| { - if attr.path.is_ident("trap_on_return") { + if attr.path().is_ident("trap_on_return") { should_trap_on_return = true; false } else { @@ -87,7 +87,7 @@ struct RuntimeInterfaceFunctionSet { } impl RuntimeInterfaceFunctionSet { - fn new(version: VersionAttribute, trait_item: &TraitItemMethod) -> Result { + fn new(version: VersionAttribute, trait_item: &TraitItemFn) -> Result { Ok(Self { latest_version_to_call: version.is_callable().then(|| version.version), versions: BTreeMap::from([( @@ -115,11 +115,7 @@ impl RuntimeInterfaceFunctionSet { } /// Add a different version of the function. - fn add_version( - &mut self, - version: VersionAttribute, - trait_item: &TraitItemMethod, - ) -> Result<()> { + fn add_version(&mut self, version: VersionAttribute, trait_item: &TraitItemFn) -> Result<()> { if let Some(existing_item) = self.versions.get(&version.version) { let mut err = Error::new(trait_item.span(), "Duplicated version attribute"); err.combine(Error::new( @@ -275,9 +271,9 @@ pub fn get_function_argument_types_ref_and_mut( } /// Returns an iterator over all trait methods for the given trait definition. -fn get_trait_methods(trait_def: &ItemTrait) -> impl Iterator { +fn get_trait_methods(trait_def: &ItemTrait) -> impl Iterator { trait_def.items.iter().filter_map(|i| match i { - TraitItem::Method(ref method) => Some(method), + TraitItem::Fn(ref method) => Some(method), _ => None, }) } @@ -326,10 +322,10 @@ impl Parse for VersionAttribute { } /// Return [`VersionAttribute`], if present. -fn get_item_version(item: &TraitItemMethod) -> Result> { +fn get_item_version(item: &TraitItemFn) -> Result> { item.attrs .iter() - .find(|attr| attr.path.is_ident("version")) + .find(|attr| attr.path().is_ident("version")) .map(|attr| attr.parse_args()) .transpose() } diff --git a/primitives/runtime-interface/src/host.rs b/primitives/runtime-interface/src/host.rs index 36492430266a..914e575539d2 100644 --- a/primitives/runtime-interface/src/host.rs +++ b/primitives/runtime-interface/src/host.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/src/impls.rs b/primitives/runtime-interface/src/impls.rs index e801931c306c..3530b62662a5 100644 --- a/primitives/runtime-interface/src/impls.rs +++ b/primitives/runtime-interface/src/impls.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -276,84 +276,65 @@ impl IntoFFIValue for [T] { } } -/// Implement the traits for the `[u8; N]` arrays, where `N` is the input to this macro. -macro_rules! impl_traits_for_arrays { - ( - $( - $n:expr - ),* - $(,)? - ) => { - $( - /// The type is passed as `u32`. - /// - /// The `u32` is the pointer to the array. - impl RIType for [u8; $n] { - type FFIType = u32; - } - - #[cfg(not(feature = "std"))] - impl IntoFFIValue for [u8; $n] { - type Owned = (); +/// The type is passed as `u32`. +/// +/// The `u32` is the pointer to the array. +impl RIType for [u8; N] { + type FFIType = u32; +} - fn into_ffi_value(&self) -> WrappedFFIValue { - (self.as_ptr() as u32).into() - } - } +#[cfg(not(feature = "std"))] +impl IntoFFIValue for [u8; N] { + type Owned = (); - #[cfg(not(feature = "std"))] - impl FromFFIValue for [u8; $n] { - fn from_ffi_value(arg: u32) -> [u8; $n] { - let mut res = [0u8; $n]; - let data = unsafe { Vec::from_raw_parts(arg as *mut u8, $n, $n) }; + fn into_ffi_value(&self) -> WrappedFFIValue { + (self.as_ptr() as u32).into() + } +} - res.copy_from_slice(&data); +#[cfg(not(feature = "std"))] +impl FromFFIValue for [u8; N] { + fn from_ffi_value(arg: u32) -> [u8; N] { + let mut res = [0u8; N]; + let data = unsafe { Vec::from_raw_parts(arg as *mut u8, N, N) }; - res - } - } + res.copy_from_slice(&data); - #[cfg(feature = "std")] - impl FromFFIValue for [u8; $n] { - type SelfInstance = [u8; $n]; + res + } +} - fn from_ffi_value(context: &mut dyn FunctionContext, arg: u32) -> Result<[u8; $n]> { - let mut res = [0u8; $n]; - context.read_memory_into(Pointer::new(arg), &mut res)?; - Ok(res) - } - } +#[cfg(feature = "std")] +impl FromFFIValue for [u8; N] { + type SelfInstance = [u8; N]; - #[cfg(feature = "std")] - impl IntoFFIValue for [u8; $n] { - fn into_ffi_value(self, context: &mut dyn FunctionContext) -> Result { - let addr = context.allocate_memory($n)?; - context.write_memory(addr, &self)?; - Ok(addr.into()) - } - } + fn from_ffi_value(context: &mut dyn FunctionContext, arg: u32) -> Result<[u8; N]> { + let mut res = [0u8; N]; + context.read_memory_into(Pointer::new(arg), &mut res)?; + Ok(res) + } +} - #[cfg(feature = "std")] - impl IntoPreallocatedFFIValue for [u8; $n] { - type SelfInstance = [u8; $n]; - - fn into_preallocated_ffi_value( - self_instance: Self::SelfInstance, - context: &mut dyn FunctionContext, - allocated: u32, - ) -> Result<()> { - context.write_memory(Pointer::new(allocated), &self_instance) - } - } - )* +#[cfg(feature = "std")] +impl IntoFFIValue for [u8; N] { + fn into_ffi_value(self, context: &mut dyn FunctionContext) -> Result { + let addr = context.allocate_memory(N as u32)?; + context.write_memory(addr, &self)?; + Ok(addr.into()) } } -impl_traits_for_arrays! { - 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, - 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, - 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, - 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, +#[cfg(feature = "std")] +impl IntoPreallocatedFFIValue for [u8; N] { + type SelfInstance = [u8; N]; + + fn into_preallocated_ffi_value( + self_instance: Self::SelfInstance, + context: &mut dyn FunctionContext, + allocated: u32, + ) -> Result<()> { + context.write_memory(Pointer::new(allocated), &self_instance) + } } impl PassBy for sp_std::result::Result { diff --git a/primitives/runtime-interface/src/lib.rs b/primitives/runtime-interface/src/lib.rs index 975d7158b8dc..1f1638880bb6 100644 --- a/primitives/runtime-interface/src/lib.rs +++ b/primitives/runtime-interface/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -180,10 +180,19 @@ pub use sp_std; /// None => self.clear_storage(&[1, 2, 3, 4]), /// } /// } +/// +/// /// A function can be gated behind a configuration (`cfg`) attribute. +/// /// To prevent ambiguity and confusion about what will be the final exposed host +/// /// functions list, conditionally compiled functions can't be versioned. +/// /// That is, conditionally compiled functions with `version`s greater than 1 +/// /// are not allowed. +/// #[cfg(feature = "experimental-function")] +/// fn gated_call(data: &[u8]) -> Vec { +/// [42].to_vec() +/// } /// } /// ``` /// -/// /// The given example will generate roughly the following code for native: /// /// ``` @@ -197,6 +206,8 @@ pub use sp_std; /// fn call_version_2(data: &[u8]) -> Vec; /// fn call_version_3(data: &[u8]) -> Vec; /// fn set_or_clear_version_1(&mut self, optional: Option>); +/// #[cfg(feature = "experimental-function")] +/// fn gated_call_version_1(data: &[u8]) -> Vec; /// } /// /// impl Interface for &mut dyn sp_externalities::Externalities { @@ -209,6 +220,8 @@ pub use sp_std; /// None => self.clear_storage(&[1, 2, 3, 4]), /// } /// } +/// #[cfg(feature = "experimental-function")] +/// fn gated_call_version_1(data: &[u8]) -> Vec { [42].to_vec() } /// } /// /// pub fn call(data: &[u8]) -> Vec { @@ -237,6 +250,16 @@ pub use sp_std; /// .expect("`set_or_clear` called outside of an Externalities-provided environment.") /// } /// +/// #[cfg(feature = "experimental-function")] +/// pub fn gated_call(data: &[u8]) -> Vec { +/// gated_call_version_1(data) +/// } +/// +/// #[cfg(feature = "experimental-function")] +/// fn gated_call_version_1(data: &[u8]) -> Vec { +/// <&mut dyn sp_externalities::Externalities as Interface>::gated_call_version_1(data) +/// } +/// /// /// This type implements the `HostFunctions` trait (from `sp-wasm-interface`) and /// /// provides the host implementation for the wasm side. The host implementation converts the /// /// arguments from wasm to native and calls the corresponding native function. @@ -247,28 +270,43 @@ pub use sp_std; /// } /// ``` /// -/// /// The given example will generate roughly the following code for wasm: /// /// ``` /// mod interface { /// mod extern_host_functions_impls { -/// extern "C" { -/// /// Every function is exported as `ext_TRAIT_NAME_FUNCTION_NAME_version_VERSION`. -/// /// -/// /// `TRAIT_NAME` is converted into snake case. -/// /// -/// /// The type for each argument of the exported function depends on -/// /// `::FFIType`. -/// /// -/// /// `data` holds the pointer and the length to the `[u8]` slice. -/// pub fn ext_Interface_call_version_1(data: u64) -> u64; -/// /// `optional` holds the pointer and the length of the encoded value. -/// pub fn ext_Interface_set_or_clear_version_1(optional: u64); +/// /// Every function is exported by the native code as `ext_FUNCTION_NAME_version_VERSION`. +/// /// +/// /// The type for each argument of the exported function depends on +/// /// `::FFIType`. +/// /// +/// /// `key` holds the pointer and the length to the `data` slice. +/// pub fn call(data: &[u8]) -> Vec { +/// extern "C" { pub fn ext_call_version_2(key: u64); } +/// // Should call into extenal `ext_call_version_2(<[u8] as IntoFFIValue>::into_ffi_value(key))` +/// // But this is too much to replicate in a doc test so here we just return a dummy vector. +/// // Note that we jump into the latest version not marked as `register_only` (i.e. version 2). +/// Vec::new() +/// } +/// +/// /// `key` holds the pointer and the length of the `option` value. +/// pub fn set_or_clear(option: Option>) { +/// extern "C" { pub fn ext_set_or_clear_version_1(key: u64); } +/// // Same as above +/// } +/// +/// /// `key` holds the pointer and the length to the `data` slice. +/// #[cfg(feature = "experimental-function")] +/// pub fn gated_call(data: &[u8]) -> Vec { +/// extern "C" { pub fn ext_gated_call_version_1(key: u64); } +/// /// Same as above +/// Vec::new() /// } /// } /// -/// /// The type is actually `ExchangeableFunction` (from `sp-runtime-interface`). +/// /// The type is actually `ExchangeableFunction` (from `sp-runtime-interface`) and +/// /// by default this is initialized to jump into the corresponding function in +/// /// `extern_host_functions_impls`. /// /// /// /// This can be used to replace the implementation of the `call` function. /// /// Instead of calling into the host, the callee will automatically call the other @@ -279,6 +317,8 @@ pub use sp_std; /// /// `host_call.replace_implementation(some_other_impl)` /// pub static host_call: () = (); /// pub static host_set_or_clear: () = (); +/// #[cfg(feature = "experimental-feature")] +/// pub static gated_call: () = (); /// /// pub fn call(data: &[u8]) -> Vec { /// // This is the actual call: `host_call.get()(data)` @@ -291,6 +331,12 @@ pub use sp_std; /// pub fn set_or_clear(optional: Option>) { /// // Same as above /// } +/// +/// #[cfg(feature = "experimental-feature")] +/// pub fn gated_call(data: &[u8]) -> Vec { +/// // Same as above +/// Vec::new() +/// } /// } /// ``` /// diff --git a/primitives/runtime-interface/src/pass_by.rs b/primitives/runtime-interface/src/pass_by.rs index ac6f0def9cad..8d145669adc3 100644 --- a/primitives/runtime-interface/src/pass_by.rs +++ b/primitives/runtime-interface/src/pass_by.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/src/util.rs b/primitives/runtime-interface/src/util.rs index fe8afe99508a..8db32271a0e7 100644 --- a/primitives/runtime-interface/src/util.rs +++ b/primitives/runtime-interface/src/util.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/src/wasm.rs b/primitives/runtime-interface/src/wasm.rs index 4ed27687a10a..91205addf21a 100644 --- a/primitives/runtime-interface/src/wasm.rs +++ b/primitives/runtime-interface/src/wasm.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml b/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml index ec07481234e9..2ff86086a890 100644 --- a/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml +++ b/primitives/runtime-interface/test-wasm-deprecated/Cargo.toml @@ -13,10 +13,9 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -sp-core = { version = "7.0.0", default-features = false, path = "../../core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../io" } -sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../" } -sp-std = { version = "5.0.0", default-features = false, path = "../../std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../io" } +sp-runtime-interface = { version = "17.0.0", default-features = false, path = "../" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder", optional = true } @@ -27,6 +26,5 @@ std = [ "sp-core/std", "sp-io/std", "sp-runtime-interface/std", - "sp-std/std", "substrate-wasm-builder", ] diff --git a/primitives/runtime-interface/test-wasm-deprecated/build.rs b/primitives/runtime-interface/test-wasm-deprecated/build.rs index 70f84bc00a81..651f57388e0d 100644 --- a/primitives/runtime-interface/test-wasm-deprecated/build.rs +++ b/primitives/runtime-interface/test-wasm-deprecated/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ fn main() { .with_current_project() .export_heap_base() .import_memory() + .disable_runtime_version_section_check() .build(); } } diff --git a/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs b/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs index 512d52214c12..2f42e60504eb 100644 --- a/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs +++ b/primitives/runtime-interface/test-wasm-deprecated/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/test-wasm/Cargo.toml b/primitives/runtime-interface/test-wasm/Cargo.toml index 1061a54d7f1b..82fa35267cf5 100644 --- a/primitives/runtime-interface/test-wasm/Cargo.toml +++ b/primitives/runtime-interface/test-wasm/Cargo.toml @@ -14,10 +14,10 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] bytes = { version = "1.1.0", default-features = false } -sp-core = { version = "7.0.0", default-features = false, path = "../../core" } -sp-io = { version = "7.0.0", default-features = false, path = "../../io" } -sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../" } -sp-std = { version = "5.0.0", default-features = false, path = "../../std" } +sp-core = { version = "21.0.0", default-features = false, path = "../../core" } +sp-io = { version = "23.0.0", default-features = false, path = "../../io" } +sp-runtime-interface = { version = "17.0.0", default-features = false, path = "../" } +sp-std = { version = "8.0.0", default-features = false, path = "../../std" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder", optional = true } diff --git a/primitives/runtime-interface/test-wasm/build.rs b/primitives/runtime-interface/test-wasm/build.rs index 70f84bc00a81..651f57388e0d 100644 --- a/primitives/runtime-interface/test-wasm/build.rs +++ b/primitives/runtime-interface/test-wasm/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,6 +22,7 @@ fn main() { .with_current_project() .export_heap_base() .import_memory() + .disable_runtime_version_section_check() .build(); } } diff --git a/primitives/runtime-interface/test-wasm/src/lib.rs b/primitives/runtime-interface/test-wasm/src/lib.rs index 3720735ac773..cf1ff3bca088 100644 --- a/primitives/runtime-interface/test-wasm/src/lib.rs +++ b/primitives/runtime-interface/test-wasm/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/test/Cargo.toml b/primitives/runtime-interface/test/Cargo.toml index 4e4522fd93dd..e5b34c0d27dd 100644 --- a/primitives/runtime-interface/test/Cargo.toml +++ b/primitives/runtime-interface/test/Cargo.toml @@ -16,9 +16,9 @@ tracing = "0.1.29" tracing-core = "0.1.28" sc-executor = { version = "0.10.0-dev", path = "../../../client/executor" } sc-executor-common = { version = "0.10.0-dev", path = "../../../client/executor/common" } -sp-io = { version = "7.0.0", path = "../../io" } -sp-runtime = { version = "7.0.0", path = "../../runtime" } -sp-runtime-interface = { version = "7.0.0", path = "../" } +sp-io = { version = "23.0.0", path = "../../io" } +sp-runtime = { version = "24.0.0", path = "../../runtime" } +sp-runtime-interface = { version = "17.0.0", path = "../" } sp-runtime-interface-test-wasm = { version = "2.0.0", path = "../test-wasm" } sp-runtime-interface-test-wasm-deprecated = { version = "2.0.0", path = "../test-wasm-deprecated" } -sp-state-machine = { version = "0.13.0", path = "../../state-machine" } +sp-state-machine = { version = "0.28.0", path = "../../state-machine" } diff --git a/primitives/runtime-interface/test/src/lib.rs b/primitives/runtime-interface/test/src/lib.rs index d1db3e064295..215704a11215 100644 --- a/primitives/runtime-interface/test/src/lib.rs +++ b/primitives/runtime-interface/test/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -42,7 +42,8 @@ fn call_wasm_method_with_result( let executor = sc_executor::WasmExecutor::< ExtendedHostFunctions, - >::new(sc_executor::WasmExecutionMethod::Interpreted, Some(8), 8, None, 2); + >::builder() + .build(); let (result, allocation_stats) = executor.uncached_call_with_allocation_stats( RuntimeBlob::uncompress_if_needed(binary).expect("Failed to parse binary"), @@ -108,8 +109,8 @@ fn host_function_not_found() { .0 .unwrap_err(); - assert!(err.contains("Instantiation: Export ")); - assert!(err.contains(" not found")); + assert!(err.contains("test_return_data")); + assert!(err.contains(" Failed to create module")); } #[test] @@ -176,62 +177,85 @@ fn test_versionining_register_only() { call_wasm_method::(wasm_binary_unwrap(), "test_versionning_register_only_works"); } +fn run_test_in_another_process( + test_name: &str, + test_body: impl FnOnce(), +) -> Option { + if std::env::var("RUN_FORKED_TEST").is_ok() { + test_body(); + None + } else { + let output = std::process::Command::new(std::env::current_exe().unwrap()) + .arg(test_name) + .env("RUN_FORKED_TEST", "1") + .output() + .unwrap(); + + assert!(output.status.success()); + Some(output) + } +} + #[test] fn test_tracing() { - use std::fmt; - use tracing::span::Id as SpanId; - use tracing_core::field::{Field, Visit}; - - #[derive(Clone)] - struct TracingSubscriber(Arc>); - - struct FieldConsumer(&'static str, Option); - impl Visit for FieldConsumer { - fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { - if field.name() == self.0 { - self.1 = Some(format!("{:?}", value)) + // Run in a different process to ensure that the `Span` is registered with our local + // `TracingSubscriber`. + run_test_in_another_process("test_tracing", || { + use std::fmt; + use tracing::span::Id as SpanId; + use tracing_core::field::{Field, Visit}; + + #[derive(Clone)] + struct TracingSubscriber(Arc>); + + struct FieldConsumer(&'static str, Option); + impl Visit for FieldConsumer { + fn record_debug(&mut self, field: &Field, value: &dyn fmt::Debug) { + if field.name() == self.0 { + self.1 = Some(format!("{:?}", value)) + } } } - } - #[derive(Default)] - struct Inner { - spans: HashSet, - } - - impl tracing::subscriber::Subscriber for TracingSubscriber { - fn enabled(&self, _: &tracing::Metadata) -> bool { - true + #[derive(Default)] + struct Inner { + spans: HashSet, } - fn new_span(&self, span: &tracing::span::Attributes) -> tracing::Id { - let mut inner = self.0.lock().unwrap(); - let id = SpanId::from_u64((inner.spans.len() + 1) as _); - let mut f = FieldConsumer("name", None); - span.record(&mut f); - inner.spans.insert(f.1.unwrap_or_else(|| span.metadata().name().to_owned())); - id - } + impl tracing::subscriber::Subscriber for TracingSubscriber { + fn enabled(&self, _: &tracing::Metadata) -> bool { + true + } - fn record(&self, _: &SpanId, _: &tracing::span::Record) {} + fn new_span(&self, span: &tracing::span::Attributes) -> tracing::Id { + let mut inner = self.0.lock().unwrap(); + let id = SpanId::from_u64((inner.spans.len() + 1) as _); + let mut f = FieldConsumer("name", None); + span.record(&mut f); + inner.spans.insert(f.1.unwrap_or_else(|| span.metadata().name().to_owned())); + id + } - fn record_follows_from(&self, _: &SpanId, _: &SpanId) {} + fn record(&self, _: &SpanId, _: &tracing::span::Record) {} - fn event(&self, _: &tracing::Event) {} + fn record_follows_from(&self, _: &SpanId, _: &SpanId) {} - fn enter(&self, _: &SpanId) {} + fn event(&self, _: &tracing::Event) {} - fn exit(&self, _: &SpanId) {} - } + fn enter(&self, _: &SpanId) {} - let subscriber = TracingSubscriber(Default::default()); - let _guard = tracing::subscriber::set_default(subscriber.clone()); + fn exit(&self, _: &SpanId) {} + } - // Call some method to generate a trace - call_wasm_method::(wasm_binary_unwrap(), "test_return_data"); + let subscriber = TracingSubscriber(Default::default()); + let _guard = tracing::subscriber::set_default(subscriber.clone()); + + // Call some method to generate a trace + call_wasm_method::(wasm_binary_unwrap(), "test_return_data"); - let inner = subscriber.0.lock().unwrap(); - assert!(inner.spans.contains("return_input_version_1")); + let inner = subscriber.0.lock().unwrap(); + assert!(inner.spans.contains("return_input_version_1")); + }); } #[test] diff --git a/primitives/runtime-interface/tests/ui.rs b/primitives/runtime-interface/tests/ui.rs index f3d6aa59a033..821d0b73f268 100644 --- a/primitives/runtime-interface/tests/ui.rs +++ b/primitives/runtime-interface/tests/ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime-interface/tests/ui/no_feature_gated_method.rs b/primitives/runtime-interface/tests/ui/no_feature_gated_method.rs new file mode 100644 index 000000000000..51e45f178f0c --- /dev/null +++ b/primitives/runtime-interface/tests/ui/no_feature_gated_method.rs @@ -0,0 +1,18 @@ +use sp_runtime_interface::runtime_interface; + +#[runtime_interface] +trait Test { + fn foo() {} + + #[cfg(feature = "bar-feature")] + fn bar() {} + + #[cfg(not(feature = "bar-feature"))] + fn qux() {} +} + +fn main() { + test::foo(); + test::bar(); + test::qux(); +} diff --git a/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr b/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr new file mode 100644 index 000000000000..e8accd62fc68 --- /dev/null +++ b/primitives/runtime-interface/tests/ui/no_feature_gated_method.stderr @@ -0,0 +1,5 @@ +error[E0425]: cannot find function `bar` in module `test` + --> tests/ui/no_feature_gated_method.rs:16:8 + | +16 | test::bar(); + | ^^^ not found in `test` diff --git a/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.rs b/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.rs new file mode 100644 index 000000000000..a4a8a5804bee --- /dev/null +++ b/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.rs @@ -0,0 +1,12 @@ +use sp_runtime_interface::runtime_interface; + +#[runtime_interface] +trait Test { + fn foo() {} + + #[version(2)] + #[cfg(feature = "foo-feature")] + fn foo() {} +} + +fn main() {} diff --git a/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.stderr b/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.stderr new file mode 100644 index 000000000000..6f50e14278de --- /dev/null +++ b/primitives/runtime-interface/tests/ui/no_versioned_conditional_build.stderr @@ -0,0 +1,5 @@ +error: Conditional compilation is not supported for versioned functions + --> tests/ui/no_versioned_conditional_build.rs:7:2 + | +7 | #[version(2)] + | ^ diff --git a/primitives/runtime/Cargo.toml b/primitives/runtime/Cargo.toml index 3976de60cdcf..f9271f0ca67d 100644 --- a/primitives/runtime/Cargo.toml +++ b/primitives/runtime/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-runtime" -version = "7.0.0" +version = "24.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -14,29 +14,29 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive", "max-encoded-len"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive", "max-encoded-len"] } either = { version = "1.5", default-features = false } hash256-std-hasher = { version = "0.15.2", default-features = false } impl-trait-for-tuples = "0.2.2" log = { version = "0.4.17", default-features = false } paste = "1.0" rand = { version = "0.8.5", optional = true } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", features = ["derive"], optional = true } -sp-application-crypto = { version = "7.0.0", default-features = false, path = "../application-crypto" } -sp-arithmetic = { version = "6.0.0", default-features = false, path = "../arithmetic" } -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-io = { version = "7.0.0", default-features = false, path = "../io" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } -sp-weights = { version = "4.0.0", default-features = false, path = "../weights" } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", default-features = false, features = ["derive", "alloc"], optional = true } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../application-crypto" } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../arithmetic" } +sp-core = { version = "21.0.0", default-features = false, path = "../core" } +sp-io = { version = "23.0.0", default-features = false, path = "../io" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } +sp-weights = { version = "20.0.0", default-features = false, path = "../weights" } [dev-dependencies] rand = "0.8.5" serde_json = "1.0.85" -zstd = { version = "0.11.2", default-features = false } +zstd = { version = "0.12.3", default-features = false } sp-api = { version = "4.0.0-dev", path = "../api" } -sp-state-machine = { version = "0.13.0", path = "../state-machine" } -sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +sp-state-machine = { version = "0.28.0", path = "../state-machine" } +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../test-utils/runtime/client" } [features] @@ -50,7 +50,7 @@ std = [ "log/std", "rand", "scale-info/std", - "serde", + "serde/std", "sp-application-crypto/std", "sp-arithmetic/std", "sp-core/std", @@ -58,3 +58,13 @@ std = [ "sp-std/std", "sp-weights/std", ] + +# Serde support without relying on std features. +serde = [ + "dep:serde", + "scale-info/serde", + "sp-application-crypto/serde", + "sp-arithmetic/serde", + "sp-core/serde", + "sp-weights/serde", +] diff --git a/primitives/runtime/src/curve.rs b/primitives/runtime/src/curve.rs index c040b7cf517e..2fc032cfafe2 100644 --- a/primitives/runtime/src/curve.rs +++ b/primitives/runtime/src/curve.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,9 +22,10 @@ use crate::{ Perbill, }; use core::ops::Sub; +use scale_info::TypeInfo; /// Piecewise Linear function in [0, 1] -> [0, 1]. -#[derive(PartialEq, Eq, sp_core::RuntimeDebug)] +#[derive(PartialEq, Eq, sp_core::RuntimeDebug, TypeInfo)] pub struct PiecewiseLinear<'a> { /// Array of points. Must be in order from the lowest abscissas to the highest. pub points: &'a [(Perbill, Perbill)], @@ -32,36 +33,6 @@ pub struct PiecewiseLinear<'a> { pub maximum: Perbill, } -// This can be replaced with -// #[derive(scale_info::TypeInfo)] -// #[scale_info(skip_type_params(S))] -// again once this issue is fixed in the rust compiler: https://github.com/rust-lang/rust/issues/96956 -// Tracking issues: https://github.com/paritytech/substrate/issues/11915 -impl scale_info::TypeInfo for PiecewiseLinear<'static> { - type Identity = Self; - fn type_info() -> ::scale_info::Type { - scale_info::Type::builder() - .path(scale_info::Path::new("PiecewiseLinear", "sp_runtime::curve")) - .type_params(crate::Vec::new()) - .docs(&["Piecewise Linear function in [0, 1] -> [0, 1]."]) - .composite( - scale_info::build::Fields::named() - .field(|f| { - f.ty::<&'static[(Perbill, Perbill)]>() - .name("points") - .type_name("&'static[(Perbill, Perbill)]") - .docs(&["Array of points. Must be in order from the lowest abscissas to the highest."]) - }) - .field(|f| { - f.ty::() - .name("maximum") - .type_name("Perbill") - .docs(&["The maximum value that can be returned."]) - }), - ) - } -} - fn abs_sub + Clone>(a: N, b: N) -> N where { a.clone().max(b.clone()) - a.min(b) } diff --git a/primitives/runtime/src/generic/block.rs b/primitives/runtime/src/generic/block.rs index 3b01633635c2..05146e880cb1 100644 --- a/primitives/runtime/src/generic/block.rs +++ b/primitives/runtime/src/generic/block.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,12 +20,15 @@ #[cfg(feature = "std")] use std::fmt; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use crate::{ codec::{Codec, Decode, Encode}, - traits::{self, Block as BlockT, Header as HeaderT, MaybeSerialize, Member, NumberFor}, + traits::{ + self, Block as BlockT, Header as HeaderT, MaybeSerialize, MaybeSerializeDeserialize, + Member, NumberFor, + }, Justifications, }; use sp_core::RuntimeDebug; @@ -33,9 +36,6 @@ use sp_std::prelude::*; /// Something to identify a block. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Serialize))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] -#[cfg_attr(feature = "std", serde(deny_unknown_fields))] pub enum BlockId { /// Identify by block header hash. Hash(Block::Hash), @@ -45,12 +45,12 @@ pub enum BlockId { impl BlockId { /// Create a block ID from a hash. - pub fn hash(hash: Block::Hash) -> Self { + pub const fn hash(hash: Block::Hash) -> Self { BlockId::Hash(hash) } /// Create a block ID from a number. - pub fn number(number: NumberFor) -> Self { + pub const fn number(number: NumberFor) -> Self { BlockId::Number(number) } @@ -78,20 +78,27 @@ impl fmt::Display for BlockId { } /// Abstraction over a substrate block. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] -#[cfg_attr(feature = "std", serde(deny_unknown_fields))] -pub struct Block { +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] +pub struct Block { /// The block header. pub header: Header, /// The accompanying extrinsics. pub extrinsics: Vec, } -impl traits::Block for Block +impl traits::HeaderProvider for Block where Header: HeaderT, +{ + type HeaderT = Header; +} + +impl traits::Block for Block +where + Header: HeaderT + MaybeSerializeDeserialize, Extrinsic: Member + Codec + traits::Extrinsic, { type Extrinsic = Extrinsic; @@ -117,9 +124,9 @@ where /// Abstraction over a substrate block and justification. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] -#[cfg_attr(feature = "std", serde(deny_unknown_fields))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct SignedBlock { /// Full block. pub block: Block, diff --git a/primitives/runtime/src/generic/checked_extrinsic.rs b/primitives/runtime/src/generic/checked_extrinsic.rs index fd7745c6031f..4b0e017f4517 100644 --- a/primitives/runtime/src/generic/checked_extrinsic.rs +++ b/primitives/runtime/src/generic/checked_extrinsic.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime/src/generic/digest.rs b/primitives/runtime/src/generic/digest.rs index 1d1173057ea8..d7db0f91a482 100644 --- a/primitives/runtime/src/generic/digest.rs +++ b/primitives/runtime/src/generic/digest.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,8 +17,10 @@ //! Generic implementation of a digest. -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; +#[cfg(all(not(feature = "std"), feature = "serde"))] +use sp_std::alloc::format; use sp_std::prelude::*; @@ -34,7 +36,7 @@ use sp_core::RuntimeDebug; /// Generic header digest. #[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, Default)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Digest { /// A list of logs in the digest. pub logs: Vec, @@ -106,7 +108,7 @@ pub enum DigestItem { RuntimeEnvironmentUpdated, } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl serde::Serialize for DigestItem { fn serialize(&self, seq: S) -> Result where @@ -116,7 +118,7 @@ impl serde::Serialize for DigestItem { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl<'a> serde::Deserialize<'a> for DigestItem { fn deserialize(de: D) -> Result where @@ -453,8 +455,8 @@ mod tests { #[test] fn digest_item_type_info() { let type_info = DigestItem::type_info(); - let variants = if let scale_info::TypeDef::Variant(variant) = type_info.type_def() { - variant.variants() + let variants = if let scale_info::TypeDef::Variant(variant) = type_info.type_def { + variant.variants } else { panic!("Should be a TypeDef::TypeDefVariant") }; @@ -475,10 +477,10 @@ mod tests { let encoded = digest_item.encode(); let variant = variants .iter() - .find(|v| v.name() == &variant_name) + .find(|v| v.name == variant_name) .expect(&format!("Variant {} not found", variant_name)); - assert_eq!(encoded[0], variant.index()) + assert_eq!(encoded[0], variant.index) }; check(DigestItemType::Other); diff --git a/primitives/runtime/src/generic/era.rs b/primitives/runtime/src/generic/era.rs index b26545fb8404..bdc7e9930fb1 100644 --- a/primitives/runtime/src/generic/era.rs +++ b/primitives/runtime/src/generic/era.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,7 @@ //! Generic implementation of an unchecked (pre-verification) extrinsic. -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use crate::codec::{Decode, Encode, Error, Input, Output}; @@ -30,7 +30,7 @@ pub type Phase = u64; /// An era to describe the longevity of a transaction. #[derive(PartialEq, Eq, Clone, Copy, sp_core::RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum Era { /// The transaction is valid forever. The genesis hash must be present in the signed content. Immortal, diff --git a/primitives/runtime/src/generic/header.rs b/primitives/runtime/src/generic/header.rs index 04d09f6b1554..82ab9a61f96d 100644 --- a/primitives/runtime/src/generic/header.rs +++ b/primitives/runtime/src/generic/header.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,26 +22,28 @@ use crate::{ generic::Digest, scale_info::TypeInfo, traits::{ - self, AtLeast32BitUnsigned, Hash as HashT, MaybeDisplay, MaybeSerialize, - MaybeSerializeDeserialize, Member, SimpleBitOps, + self, AtLeast32BitUnsigned, Hash as HashT, MaybeDisplay, MaybeFromStr, + MaybeSerializeDeserialize, Member, }, }; -#[cfg(feature = "std")] +use codec::{FullCodec, MaxEncodedLen}; +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use sp_core::U256; use sp_std::fmt::Debug; /// Abstraction over a block header for a substrate chain. #[derive(Encode, Decode, PartialEq, Eq, Clone, sp_core::RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] -#[cfg_attr(feature = "std", serde(deny_unknown_fields))] +#[scale_info(skip_type_params(Hash))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "serde", serde(deny_unknown_fields))] pub struct Header + TryFrom, Hash: HashT> { /// The parent hash. pub parent_hash: Hash::Output, /// The block number. #[cfg_attr( - feature = "std", + feature = "serde", serde(serialize_with = "serialize_number", deserialize_with = "deserialize_number") )] #[codec(compact)] @@ -54,7 +56,7 @@ pub struct Header + TryFrom, Hash: HashT> { pub digest: Digest, } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] pub fn serialize_number + TryFrom>( val: &T, s: S, @@ -66,7 +68,7 @@ where serde::Serialize::serialize(&u256, s) } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] pub fn deserialize_number<'a, D, T: Copy + Into + TryFrom>(d: D) -> Result where D: serde::Deserializer<'a>, @@ -79,55 +81,58 @@ impl traits::Header for Header where Number: Member + MaybeSerializeDeserialize + + MaybeFromStr + Debug + + Default + sp_std::hash::Hash + MaybeDisplay + AtLeast32BitUnsigned - + Codec + + FullCodec + Copy + + MaxEncodedLen + Into + TryFrom - + sp_std::str::FromStr, + + TypeInfo, Hash: HashT, - Hash::Output: Default - + sp_std::hash::Hash - + Copy - + Member - + Ord - + MaybeSerialize - + Debug - + MaybeDisplay - + SimpleBitOps - + Codec, { type Number = Number; type Hash = ::Output; type Hashing = Hash; + fn new( + number: Self::Number, + extrinsics_root: Self::Hash, + state_root: Self::Hash, + parent_hash: Self::Hash, + digest: Digest, + ) -> Self { + Self { number, extrinsics_root, state_root, parent_hash, digest } + } fn number(&self) -> &Self::Number { &self.number } + fn set_number(&mut self, num: Self::Number) { self.number = num } - fn extrinsics_root(&self) -> &Self::Hash { &self.extrinsics_root } + fn set_extrinsics_root(&mut self, root: Self::Hash) { self.extrinsics_root = root } - fn state_root(&self) -> &Self::Hash { &self.state_root } + fn set_state_root(&mut self, root: Self::Hash) { self.state_root = root } - fn parent_hash(&self) -> &Self::Hash { &self.parent_hash } + fn set_parent_hash(&mut self, hash: Self::Hash) { self.parent_hash = hash } @@ -141,16 +146,6 @@ where log::debug!(target: "header", "Retrieving mutable reference to digest"); &mut self.digest } - - fn new( - number: Self::Number, - extrinsics_root: Self::Hash, - state_root: Self::Hash, - parent_hash: Self::Hash, - digest: Digest, - ) -> Self { - Self { number, extrinsics_root, state_root, parent_hash, digest } - } } impl Header @@ -164,8 +159,6 @@ where + Into + TryFrom, Hash: HashT, - Hash::Output: - Default + sp_std::hash::Hash + Copy + Member + MaybeDisplay + SimpleBitOps + Codec, { /// Convenience helper for computing the hash of the header without having /// to import the trait. diff --git a/primitives/runtime/src/generic/mod.rs b/primitives/runtime/src/generic/mod.rs index 049b0e1624e7..3687f7cdb3b2 100644 --- a/primitives/runtime/src/generic/mod.rs +++ b/primitives/runtime/src/generic/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,9 +15,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -// tag::description[] -//! Generic implementations of Extrinsic/Header/Block. -// end::description[] +//! Generic implementations of [`crate::traits::Header`], [`crate::traits::Block`] and +//! [`crate::traits::Extrinsic`]. mod block; mod checked_extrinsic; diff --git a/primitives/runtime/src/generic/tests.rs b/primitives/runtime/src/generic/tests.rs index d0536a567312..b63efeb52212 100644 --- a/primitives/runtime/src/generic/tests.rs +++ b/primitives/runtime/src/generic/tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime/src/generic/unchecked_extrinsic.rs b/primitives/runtime/src/generic/unchecked_extrinsic.rs index bab4411167a7..0b1cd2b54290 100644 --- a/primitives/runtime/src/generic/unchecked_extrinsic.rs +++ b/primitives/runtime/src/generic/unchecked_extrinsic.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,7 @@ use crate::{ generic::CheckedExtrinsic, traits::{ self, Checkable, Extrinsic, ExtrinsicMetadata, IdentifyAccount, MaybeDisplay, Member, - SignedExtension, + SignaturePayload, SignedExtension, }, transaction_validity::{InvalidTransaction, TransactionValidityError}, OpaqueExtrinsic, @@ -29,6 +29,8 @@ use crate::{ use codec::{Compact, Decode, Encode, EncodeLike, Error, Input}; use scale_info::{build::Fields, meta_type, Path, StaticTypeInfo, Type, TypeInfo, TypeParameter}; use sp_io::hashing::blake2_256; +#[cfg(all(not(feature = "std"), feature = "serde"))] +use sp_std::alloc::format; use sp_std::{fmt, prelude::*}; /// Current version of the [`UncheckedExtrinsic`] encoded format. @@ -38,6 +40,9 @@ use sp_std::{fmt, prelude::*}; /// the decoding fails. const EXTRINSIC_FORMAT_VERSION: u8 = 4; +/// The `SingaturePayload` of `UncheckedExtrinsic`. +type UncheckedSignaturePayload = (Address, Signature, Extra); + /// A extrinsic right from the external world. This is unchecked and so /// can contain a signature. #[derive(PartialEq, Eq, Clone)] @@ -48,11 +53,19 @@ where /// The signature, address, number of extrinsics have come before from /// the same signer and an era describing the longevity of this transaction, /// if this is a signed extrinsic. - pub signature: Option<(Address, Signature, Extra)>, + pub signature: Option>, /// The function that should be called. pub function: Call, } +impl SignaturePayload + for UncheckedSignaturePayload +{ + type SignatureAddress = Address; + type Signature = Signature; + type SignatureExtra = Extra; +} + /// Manual [`TypeInfo`] implementation because of custom encoding. The data is a valid encoded /// `Vec`, but requires some logic to extract the signature and payload. /// @@ -101,12 +114,12 @@ impl } } -impl Extrinsic - for UncheckedExtrinsic +impl + Extrinsic for UncheckedExtrinsic { type Call = Call; - type SignaturePayload = (Address, Signature, Extra); + type SignaturePayload = UncheckedSignaturePayload; fn is_signed(&self) -> Option { Some(self.signature.is_some()) @@ -317,7 +330,7 @@ where { } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl serde::Serialize for UncheckedExtrinsic { @@ -329,7 +342,7 @@ impl s } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl<'a, Address: Decode, Signature: Decode, Call: Decode, Extra: SignedExtension> serde::Deserialize<'a> for UncheckedExtrinsic { diff --git a/primitives/runtime/src/legacy.rs b/primitives/runtime/src/legacy.rs index 7bc7c88a7e10..b134038a1286 100644 --- a/primitives/runtime/src/legacy.rs +++ b/primitives/runtime/src/legacy.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime/src/legacy/byte_sized_error.rs b/primitives/runtime/src/legacy/byte_sized_error.rs index 049abff69ff1..a592c751231d 100644 --- a/primitives/runtime/src/legacy/byte_sized_error.rs +++ b/primitives/runtime/src/legacy/byte_sized_error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,12 +20,12 @@ use crate::{ArithmeticError, TokenError}; use codec::{Decode, Encode}; use scale_info::TypeInfo; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; /// [`ModuleError`] type definition before BlockBuilder API version 6. #[derive(Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct ModuleError { /// Module index, matching the metadata module index. pub index: u8, @@ -33,7 +33,7 @@ pub struct ModuleError { pub error: u8, /// Optional error message. #[codec(skip)] - #[cfg_attr(feature = "std", serde(skip_deserializing))] + #[cfg_attr(feature = "serde", serde(skip_deserializing))] pub message: Option<&'static str>, } @@ -45,12 +45,12 @@ impl PartialEq for ModuleError { /// [`DispatchError`] type definition before BlockBuilder API version 6. #[derive(Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo, PartialEq)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum DispatchError { /// Some error occurred. Other( #[codec(skip)] - #[cfg_attr(feature = "std", serde(skip_deserializing))] + #[cfg_attr(feature = "serde", serde(skip_deserializing))] &'static str, ), /// Failed to lookup some data. diff --git a/primitives/runtime/src/lib.rs b/primitives/runtime/src/lib.rs index 8a32d0b9b422..dd861ad05de9 100644 --- a/primitives/runtime/src/lib.rs +++ b/primitives/runtime/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,7 +15,32 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Runtime Modules shared primitive types. +//! # Substrate Runtime Primitives. +//! +//! This crate, among other things, contains a large library of types and utilities that are used in +//! the Substrate runtime, but are not particularly `FRAME`-oriented. +//! +//! ## Block, Header and Extrinsics +//! +//! Most notable, this crate contains some of the types and trait that enable important +//! communication between the client and the runtime. This includes: +//! +//! - A set of traits to declare what any block/header/extrinsic type should provide. +//! - [`traits::Block`], [`traits::Header`], [`traits::Extrinsic`] +//! - A set of types that implement these traits, whilst still providing a high degree of +//! configurability via generics. +//! - [`generic::Block`], [`generic::Header`], [`generic::UncheckedExtrinsic`] and +//! [`generic::CheckedExtrinsic`] +//! +//! ## Runtime API Types +//! +//! This crate also contains some types that are often used in conjuncture with Runtime APIs. Most +//! notable: +//! +//! - [`ApplyExtrinsicResult`], and [`DispatchOutcome`], which dictate how the client and runtime +//! communicate about the success or failure of an extrinsic. +//! - [`transaction_validity`], which dictates how the client and runtime communicate about the +//! validity of an extrinsic while still in the transaction-queue. #![warn(missing_docs)] #![cfg_attr(not(feature = "std"), no_std)] @@ -24,7 +49,7 @@ pub use codec; #[doc(hidden)] pub use scale_info; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] #[doc(hidden)] pub use serde; #[doc(hidden)] @@ -43,7 +68,7 @@ pub use sp_core::storage::StateVersion; pub use sp_core::storage::{Storage, StorageChild}; use sp_core::{ - crypto::{self, ByteArray}, + crypto::{self, ByteArray, FromEntropy}, ecdsa, ed25519, hash::{H256, H512}, sr25519, @@ -52,6 +77,8 @@ use sp_std::prelude::*; use codec::{Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; +#[cfg(all(not(feature = "std"), feature = "serde"))] +use sp_std::alloc::format; pub mod curve; pub mod generic; @@ -94,7 +121,7 @@ pub use sp_arithmetic::helpers_128bit; /// Re-export top-level arithmetic stuff. pub use sp_arithmetic::{ traits::SaturatedConversion, ArithmeticError, FixedI128, FixedI64, FixedPointNumber, - FixedPointOperand, FixedU128, InnerOf, PerThing, PerU16, Perbill, Percent, Permill, + FixedPointOperand, FixedU128, FixedU64, InnerOf, PerThing, PerU16, Perbill, Percent, Permill, Perquintill, Rational128, Rounding, UpperOf, }; @@ -122,7 +149,7 @@ pub type EncodedJustification = Vec; /// Collection of justifications for a given block, multiple justifications may /// be provided by different consensus engines for the same block. -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Debug, Clone, PartialEq, Eq, Encode, Decode)] pub struct Justifications(Vec); @@ -179,7 +206,7 @@ impl From for Justifications { use traits::{Lazy, Verify}; use crate::traits::IdentifyAccount; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] pub use serde::{de::DeserializeOwned, Deserialize, Serialize}; /// Complex storage builder stuff. @@ -197,6 +224,9 @@ pub trait BuildStorage { /// Something that can build the genesis storage of a module. #[cfg(feature = "std")] +#[deprecated( + note = "`BuildModuleGenesisStorage` is planned to be removed in December 2023. Use `BuildStorage` instead of it." +)] pub trait BuildModuleGenesisStorage: Sized { /// Create the module genesis storage into the given `storage` and `child_storage`. fn build_module_genesis_storage( @@ -235,7 +265,7 @@ impl BuildStorage for () { pub type ConsensusEngineId = [u8; 4]; /// Signature verify that can work with any known signature types. -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[derive(Eq, PartialEq, Clone, Encode, Decode, MaxEncodedLen, RuntimeDebug, TypeInfo)] pub enum MultiSignature { /// An Ed25519 signature. @@ -299,7 +329,7 @@ impl TryFrom for ecdsa::Signature { /// Public key for any known crypto algorithm. #[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum MultiSigner { /// An Ed25519 identity. Ed25519(ed25519::Public), @@ -309,6 +339,16 @@ pub enum MultiSigner { Ecdsa(ecdsa::Public), } +impl FromEntropy for MultiSigner { + fn from_entropy(input: &mut impl codec::Input) -> Result { + Ok(match input.read_byte()? % 3 { + 0 => Self::Ed25519(FromEntropy::from_entropy(input)?), + 1 => Self::Sr25519(FromEntropy::from_entropy(input)?), + 2.. => Self::Ecdsa(FromEntropy::from_entropy(input)?), + }) + } +} + /// NOTE: This implementations is required by `SimpleAddressDeterminer`, /// we convert the hash into some AccountId, it's fine to use any scheme. impl> crypto::UncheckedFrom for MultiSigner { @@ -427,7 +467,7 @@ impl Verify for MultiSignature { /// Signature verify that can work with any known signature types.. #[derive(Eq, PartialEq, Clone, Default, Encode, Decode, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct AnySignature(H512); impl Verify for AnySignature { @@ -475,7 +515,7 @@ pub type DispatchResultWithInfo = sp_std::result::Result, } @@ -495,7 +535,7 @@ impl PartialEq for ModuleError { /// Errors related to transactional storage layers. #[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum TransactionalError { /// Too many transactional layers have been spawned. LimitReached, @@ -520,12 +560,12 @@ impl From for DispatchError { /// Reason why a dispatch call failed. #[derive(Eq, Clone, Copy, Encode, Decode, Debug, TypeInfo, PartialEq, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum DispatchError { /// Some error occurred. Other( #[codec(skip)] - #[cfg_attr(feature = "std", serde(skip_deserializing))] + #[cfg_attr(feature = "serde", serde(skip_deserializing))] &'static str, ), /// Failed to lookup some data. @@ -553,6 +593,8 @@ pub enum DispatchError { Corruption, /// Some resource (e.g. a preimage) is unavailable right now. This might fix itself later. Unavailable, + /// Root origin is not allowed. + RootNotAllowed, } /// Result of a `Dispatchable` which contains the `DispatchResult` and additional information about @@ -603,12 +645,13 @@ impl From for DispatchError { /// Description of what went wrong when trying to complete an operation on a token. #[derive(Eq, PartialEq, Clone, Copy, Encode, Decode, Debug, TypeInfo, MaxEncodedLen)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub enum TokenError { /// Funds are unavailable. - NoFunds, - /// Account that must exist would die. - WouldDie, + FundsUnavailable, + /// Some part of the balance gives the only provider reference to the account and thus cannot + /// be (re)moved. + OnlyProvider, /// Account cannot exist with the funds that would be given. BelowMinimum, /// Account cannot be created. @@ -619,18 +662,28 @@ pub enum TokenError { Frozen, /// Operation is not supported by the asset. Unsupported, + /// Account cannot be created for a held balance. + CannotCreateHold, + /// Withdrawal would cause unwanted loss of account. + NotExpendable, + /// Account cannot receive the assets. + Blocked, } impl From for &'static str { fn from(e: TokenError) -> &'static str { match e { - TokenError::NoFunds => "Funds are unavailable", - TokenError::WouldDie => "Account that must exist would die", + TokenError::FundsUnavailable => "Funds are unavailable", + TokenError::OnlyProvider => "Account that must exist would die", TokenError::BelowMinimum => "Account cannot exist with the funds that would be given", TokenError::CannotCreate => "Account cannot be created", TokenError::UnknownAsset => "The asset in question is unknown", TokenError::Frozen => "Funds exist but are frozen", TokenError::Unsupported => "Operation is not supported by the asset", + TokenError::CannotCreateHold => + "Account cannot be created for recording amount on hold", + TokenError::NotExpendable => "Account that is desired to remain would die", + TokenError::Blocked => "Account cannot receive the assets", } } } @@ -670,6 +723,7 @@ impl From for &'static str { Exhausted => "Resources exhausted", Corruption => "State corrupt", Unavailable => "Resource unavailable", + RootNotAllowed => "Root not allowed", } } } @@ -716,6 +770,7 @@ impl traits::Printable for DispatchError { Exhausted => "Resources exhausted".print(), Corruption => "State corrupt".print(), Unavailable => "Resource unavailable".print(), + RootNotAllowed => "Root not allowed".print(), } } } @@ -767,6 +822,9 @@ pub type ApplyExtrinsicResult = pub type ApplyExtrinsicResultWithInfo = Result, transaction_validity::TransactionValidityError>; +/// The error type used as return type in try runtime hooks. +pub type TryRuntimeError = DispatchError; + /// Verify a signature on an encoded value in a lazy manner. This can be /// an optimization if the signature scheme has an "unsigned" escape hash. pub fn verify_encoded_lazy( @@ -864,7 +922,7 @@ impl sp_std::fmt::Debug for OpaqueExtrinsic { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl ::serde::Serialize for OpaqueExtrinsic { fn serialize(&self, seq: S) -> Result where @@ -874,7 +932,7 @@ impl ::serde::Serialize for OpaqueExtrinsic { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl<'a> ::serde::Deserialize<'a> for OpaqueExtrinsic { fn deserialize(de: D) -> Result where @@ -896,40 +954,6 @@ pub fn print(print: impl traits::Printable) { print.print(); } -/// Batching session. -/// -/// To be used in runtime only. Outside of runtime, just construct -/// `BatchVerifier` directly. -#[must_use = "`verify()` needs to be called to finish batch signature verification!"] -pub struct SignatureBatching(bool); - -impl SignatureBatching { - /// Start new batching session. - pub fn start() -> Self { - sp_io::crypto::start_batch_verify(); - SignatureBatching(false) - } - - /// Verify all signatures submitted during the batching session. - #[must_use] - pub fn verify(mut self) -> bool { - self.0 = true; - sp_io::crypto::finish_batch_verify() - } -} - -impl Drop for SignatureBatching { - fn drop(&mut self) { - // Sanity check. If user forgets to actually call `verify()`. - // - // We should not panic if the current thread is already panicking, - // because Rust otherwise aborts the process. - if !self.0 && !sp_std::thread::panicking() { - panic!("Signature verification has not been called before `SignatureBatching::drop`") - } - } -} - /// Describes on what should happen with a storage transaction. pub enum TransactionOutcome { /// Commit the transaction. @@ -954,7 +978,7 @@ mod tests { use super::*; use codec::{Decode, Encode}; - use sp_core::crypto::{Pair, UncheckedFrom}; + use sp_core::crypto::Pair; use sp_io::TestExternalities; use sp_state_machine::create_proof_check_backend; @@ -994,8 +1018,8 @@ mod tests { Module(ModuleError { index: 2, error: [1, 0, 0, 0], message: None }), ConsumerRemaining, NoProviders, - Token(TokenError::NoFunds), - Token(TokenError::WouldDie), + Token(TokenError::FundsUnavailable), + Token(TokenError::OnlyProvider), Token(TokenError::BelowMinimum), Token(TokenError::CannotCreate), Token(TokenError::UnknownAsset), @@ -1037,36 +1061,6 @@ mod tests { assert!(multi_sig.verify(msg, &multi_signer.into_account())); } - #[test] - #[should_panic(expected = "Signature verification has not been called")] - fn batching_still_finishes_when_not_called_directly() { - let mut ext = sp_state_machine::BasicExternalities::default(); - ext.register_extension(sp_core::traits::TaskExecutorExt::new( - sp_core::testing::TaskExecutor::new(), - )); - - ext.execute_with(|| { - let _batching = SignatureBatching::start(); - let dummy = UncheckedFrom::unchecked_from([1; 32]); - let dummy_sig = UncheckedFrom::unchecked_from([1; 64]); - sp_io::crypto::sr25519_verify(&dummy_sig, &Vec::new(), &dummy); - }); - } - - #[test] - #[should_panic(expected = "Hey, I'm an error")] - fn batching_does_not_panic_while_thread_is_already_panicking() { - let mut ext = sp_state_machine::BasicExternalities::default(); - ext.register_extension(sp_core::traits::TaskExecutorExt::new( - sp_core::testing::TaskExecutor::new(), - )); - - ext.execute_with(|| { - let _batching = SignatureBatching::start(); - panic!("Hey, I'm an error"); - }); - } - #[test] fn execute_and_generate_proof_works() { use codec::Encode; @@ -1110,3 +1104,23 @@ mod tests { }); } } + +// NOTE: we have to test the sp_core stuff also from a different crate to check that the macro +// can access the sp_core crate. +#[cfg(test)] +mod sp_core_tests { + use super::*; + + #[test] + #[should_panic] + fn generate_feature_enabled_macro_panics() { + sp_core::generate_feature_enabled_macro!(if_test, test, $); + if_test!(panic!("This should panic")); + } + + #[test] + fn generate_feature_enabled_macro_works() { + sp_core::generate_feature_enabled_macro!(if_not_test, not(test), $); + if_not_test!(panic!("This should not panic")); + } +} diff --git a/primitives/runtime/src/multiaddress.rs b/primitives/runtime/src/multiaddress.rs index b2d46fb106cb..89b0a3bcf8cc 100644 --- a/primitives/runtime/src/multiaddress.rs +++ b/primitives/runtime/src/multiaddress.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime/src/offchain/http.rs b/primitives/runtime/src/offchain/http.rs index dede4db5dd3d..bacc0073825b 100644 --- a/primitives/runtime/src/offchain/http.rs +++ b/primitives/runtime/src/offchain/http.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -343,10 +343,10 @@ impl Response { /// A buffered byte iterator over response body. /// /// Note that reading the body may return `None` in following cases: -/// 1. Either the deadline you've set is reached (check via `#error`; -/// In such case you can resume the reader by setting a new deadline) -/// 2. Or because of IOError. In such case the reader is not resumable and will keep -/// returning `None`. +/// 1. Either the deadline you've set is reached (check via `#error`; In such case you can resume +/// the reader by setting a new deadline) +/// 2. Or because of IOError. In such case the reader is not resumable and will keep returning +/// `None`. /// 3. The body has been returned. The reader will keep returning `None`. #[derive(Clone)] pub struct ResponseBody { @@ -450,7 +450,7 @@ impl Headers { /// and collect them on your own. pub fn find(&self, name: &str) -> Option<&str> { let raw = name.as_bytes(); - for &(ref key, ref val) in &self.raw { + for (key, val) in &self.raw { if &**key == raw { return str::from_utf8(val).ok() } diff --git a/primitives/runtime/src/offchain/mod.rs b/primitives/runtime/src/offchain/mod.rs index bbff693dea03..07abef7c4efa 100644 --- a/primitives/runtime/src/offchain/mod.rs +++ b/primitives/runtime/src/offchain/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime/src/offchain/storage.rs b/primitives/runtime/src/offchain/storage.rs index 38ad268e5894..23ab7433e66a 100644 --- a/primitives/runtime/src/offchain/storage.rs +++ b/primitives/runtime/src/offchain/storage.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime/src/offchain/storage_lock.rs b/primitives/runtime/src/offchain/storage_lock.rs index 47325743bd2f..1b795978447d 100644 --- a/primitives/runtime/src/offchain/storage_lock.rs +++ b/primitives/runtime/src/offchain/storage_lock.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/runtime/src/runtime_logger.rs b/primitives/runtime/src/runtime_logger.rs index 8b5a15762e1f..b7374b8b6f6c 100644 --- a/primitives/runtime/src/runtime_logger.rs +++ b/primitives/runtime/src/runtime_logger.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -65,11 +65,10 @@ impl log::Log for RuntimeLogger { #[cfg(test)] mod tests { - use sp_api::{BlockId, ProvideRuntimeApi}; + use sp_api::ProvideRuntimeApi; use std::{env, str::FromStr}; use substrate_test_runtime_client::{ - runtime::TestAPI, DefaultTestClientBuilderExt, ExecutionStrategy, TestClientBuilder, - TestClientBuilderExt, + runtime::TestAPI, DefaultTestClientBuilderExt, TestClientBuilder, TestClientBuilderExt, }; #[test] @@ -78,12 +77,11 @@ mod tests { sp_tracing::try_init_simple(); log::set_max_level(log::LevelFilter::from_str(&env::var("RUST_LOG").unwrap()).unwrap()); - let client = TestClientBuilder::new() - .set_execution_strategy(ExecutionStrategy::AlwaysWasm) - .build(); + let client = TestClientBuilder::new().build(); let runtime_api = client.runtime_api(); - let block_id = BlockId::Number(0); - runtime_api.do_trace_log(&block_id).expect("Logging should not fail"); + runtime_api + .do_trace_log(client.chain_info().genesis_hash) + .expect("Logging should not fail"); } else { for (level, should_print) in &[("trace", true), ("info", false)] { let executable = std::env::current_exe().unwrap(); diff --git a/primitives/runtime/src/runtime_string.rs b/primitives/runtime/src/runtime_string.rs index 762af0acd925..aa0bd52e56fe 100644 --- a/primitives/runtime/src/runtime_string.rs +++ b/primitives/runtime/src/runtime_string.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -129,7 +129,7 @@ impl std::fmt::Display for RuntimeString { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl serde::Serialize for RuntimeString { fn serialize(&self, serializer: S) -> Result { match self { @@ -139,10 +139,10 @@ impl serde::Serialize for RuntimeString { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl<'de> serde::Deserialize<'de> for RuntimeString { fn deserialize>(de: D) -> Result { - String::deserialize(de).map(Self::Owned) + Ok(Self::Owned(serde::Deserialize::deserialize(de)?)) } } diff --git a/primitives/runtime/src/testing.rs b/primitives/runtime/src/testing.rs index 81762b3fc4f9..5f94c834a8f2 100644 --- a/primitives/runtime/src/testing.rs +++ b/primitives/runtime/src/testing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,10 +23,10 @@ use crate::{ scale_info::TypeInfo, traits::{ self, Applyable, BlakeTwo256, Checkable, DispatchInfoOf, Dispatchable, OpaqueKeys, - PostDispatchInfoOf, SignedExtension, ValidateUnsigned, + PostDispatchInfoOf, SignaturePayload, SignedExtension, ValidateUnsigned, }, transaction_validity::{TransactionSource, TransactionValidity, TransactionValidityError}, - ApplyExtrinsicResultWithInfo, CryptoTypeId, KeyTypeId, + ApplyExtrinsicResultWithInfo, KeyTypeId, }; use serde::{de::Error as DeError, Deserialize, Deserializer, Serialize, Serializer}; use sp_core::{ @@ -115,7 +115,6 @@ impl UintAuthorityId { impl sp_application_crypto::RuntimeAppPublic for UintAuthorityId { const ID: KeyTypeId = key_types::DUMMY; - const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"dumm"); type Signature = TestSignature; @@ -157,10 +156,6 @@ impl OpaqueKeys for UintAuthorityId { } } -impl crate::BoundToRuntimeAppPublic for UintAuthorityId { - type Public = Self; -} - impl traits::IdentifyAccount for UintAuthorityId { type AccountId = u64; @@ -240,7 +235,7 @@ impl Deref for ExtrinsicWrapper { } /// Testing block -#[derive(PartialEq, Eq, Clone, Serialize, Debug, Encode, Decode)] +#[derive(PartialEq, Eq, Clone, Serialize, Debug, Encode, Decode, TypeInfo)] pub struct Block { /// Block header pub header: Header, @@ -248,6 +243,10 @@ pub struct Block { pub extrinsics: Vec, } +impl traits::HeaderProvider for Block { + type HeaderT = Header; +} + impl< Xt: 'static + Codec + Sized + Send + Sync + Serialize + Clone + Eq + Debug + traits::Extrinsic, > traits::Block for Block @@ -284,6 +283,15 @@ where } } +/// The signature payload of a `TestXt`. +type TxSingaturePayload = (u64, Extra); + +impl SignaturePayload for TxSingaturePayload { + type SignatureAddress = u64; + type Signature = (); + type SignatureExtra = Extra; +} + /// Test transaction, tuple of (sender, call, signed_extra) /// with index only used if sender is some. /// @@ -291,7 +299,7 @@ where #[derive(PartialEq, Eq, Clone, Encode, Decode, TypeInfo)] pub struct TestXt { /// Signature of the extrinsic. - pub signature: Option<(u64, Extra)>, + pub signature: Option>, /// Call of the extrinsic. pub call: Call, } @@ -336,9 +344,11 @@ impl Checkable for TestXt traits::Extrinsic for TestXt { +impl traits::Extrinsic + for TestXt +{ type Call = Call; - type SignaturePayload = (u64, Extra); + type SignaturePayload = TxSingaturePayload; fn is_signed(&self) -> Option { Some(self.signature.is_some()) diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 6eb19683b443..51d91958ab53 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,7 +18,6 @@ //! Primitives for the runtime modules. use crate::{ - codec::{Codec, Decode, Encode, MaxEncodedLen}, generic::Digest, scale_info::{MetaType, StaticTypeInfo, TypeInfo}, transaction_validity::{ @@ -27,10 +26,11 @@ use crate::{ }, DispatchResult, }; +use codec::{Codec, Decode, Encode, EncodeLike, FullCodec, MaxEncodedLen}; use impl_trait_for_tuples::impl_for_tuples; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use sp_application_crypto::AppKey; +use sp_application_crypto::AppCrypto; pub use sp_arithmetic::traits::{ checked_pow, ensure_pow, AtLeast32Bit, AtLeast32BitUnsigned, Bounded, CheckedAdd, CheckedDiv, CheckedMul, CheckedShl, CheckedShr, CheckedSub, Ensure, EnsureAdd, EnsureAddAssign, EnsureDiv, @@ -148,10 +148,10 @@ pub trait AppVerify { } impl< - S: Verify::Public as sp_application_crypto::AppPublic>::Generic> + S: Verify::Public as sp_application_crypto::AppPublic>::Generic> + From, T: sp_application_crypto::Wraps - + sp_application_crypto::AppKey + + sp_application_crypto::AppCrypto + sp_application_crypto::AppSignature + AsRef + AsMut @@ -159,16 +159,18 @@ impl< > AppVerify for T where ::Signer: IdentifyAccount::Signer>, - <::Public as sp_application_crypto::AppPublic>::Generic: IdentifyAccount< - AccountId = <::Public as sp_application_crypto::AppPublic>::Generic, + <::Public as sp_application_crypto::AppPublic>::Generic: IdentifyAccount< + AccountId = <::Public as sp_application_crypto::AppPublic>::Generic, >, { - type AccountId = ::Public; - fn verify>(&self, msg: L, signer: &::Public) -> bool { + type AccountId = ::Public; + fn verify>(&self, msg: L, signer: &::Public) -> bool { use sp_application_crypto::IsWrappedBy; let inner: &S = self.as_ref(); let inner_pubkey = - <::Public as sp_application_crypto::AppPublic>::Generic::from_ref(signer); + <::Public as sp_application_crypto::AppPublic>::Generic::from_ref( + signer, + ); Verify::verify(inner, msg, inner_pubkey) } } @@ -319,6 +321,51 @@ impl TryMorph for Identity { } } +/// Implementation of `Morph` which converts between types using `Into`. +pub struct MorphInto(sp_std::marker::PhantomData); +impl> Morph for MorphInto { + type Outcome = T; + fn morph(a: A) -> T { + a.into() + } +} + +/// Implementation of `TryMorph` which attmepts to convert between types using `TryInto`. +pub struct TryMorphInto(sp_std::marker::PhantomData); +impl> TryMorph for TryMorphInto { + type Outcome = T; + fn try_morph(a: A) -> Result { + a.try_into().map_err(|_| ()) + } +} + +/// Implementation of `Morph` to retrieve just the first element of a tuple. +pub struct TakeFirst; +impl Morph<(T1,)> for TakeFirst { + type Outcome = T1; + fn morph(a: (T1,)) -> T1 { + a.0 + } +} +impl Morph<(T1, T2)> for TakeFirst { + type Outcome = T1; + fn morph(a: (T1, T2)) -> T1 { + a.0 + } +} +impl Morph<(T1, T2, T3)> for TakeFirst { + type Outcome = T1; + fn morph(a: (T1, T2, T3)) -> T1 { + a.0 + } +} +impl Morph<(T1, T2, T3, T4)> for TakeFirst { + type Outcome = T1; + fn morph(a: (T1, T2, T3, T4)) -> T1 { + a.0 + } +} + /// Create a `Morph` and/or `TryMorph` impls with a simple closure-like expression. /// /// # Examples @@ -492,13 +539,25 @@ macro_rules! morph_types { morph_types! { /// Morpher to disregard the source value and replace with another. pub type Replace = |_| -> V::Type { V::get() }; + /// Mutator which reduces a scalar by a particular amount. pub type ReduceBy = |r: N::Type| -> N::Type { r.checked_sub(&N::get()).unwrap_or(Zero::zero()) } where N::Type: CheckedSub | Zero; + + /// A `TryMorph` implementation to reduce a scalar by a particular amount, checking for + /// underflow. + pub type CheckedReduceBy: TryMorph = |r: N::Type| -> Result { + r.checked_sub(&N::get()).ok_or(()) + } where N::Type: CheckedSub; + + /// A `TryMorph` implementation to enforce an upper limit for a result of the outer morphed type. + pub type MorphWithUpperLimit: TryMorph = |r: L::Type| -> Result { + M::try_morph(r).map(|m| m.min(L::get())) + } where L::Type: Ord, M: TryMorph; } -/// Extensible conversion trait. Generic over both source and destination types. +/// Infallible conversion trait. Generic over both source and destination types. pub trait Convert { /// Make conversion. fn convert(a: A) -> B; @@ -510,6 +569,161 @@ impl Convert for () { } } +/// Reversing infallible conversion trait. Generic over both source and destination types. +/// +/// This specifically reverses the conversion. +pub trait ConvertBack: Convert { + /// Make conversion back. + fn convert_back(b: B) -> A; +} + +/// Fallible conversion trait returning an [Option]. Generic over both source and destination types. +pub trait MaybeConvert { + /// Attempt to make conversion. + fn maybe_convert(a: A) -> Option; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl MaybeConvert for Tuple { + fn maybe_convert(a: A) -> Option { + for_tuples!( #( + match Tuple::maybe_convert(a.clone()) { + Some(b) => return Some(b), + None => {}, + } + )* ); + None + } +} + +/// Reversing fallible conversion trait returning an [Option]. Generic over both source and +/// destination types. +pub trait MaybeConvertBack: MaybeConvert { + /// Attempt to make conversion back. + fn maybe_convert_back(b: B) -> Option; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl MaybeConvertBack for Tuple { + fn maybe_convert_back(b: B) -> Option { + for_tuples!( #( + match Tuple::maybe_convert_back(b.clone()) { + Some(a) => return Some(a), + None => {}, + } + )* ); + None + } +} + +/// Fallible conversion trait which returns the argument in the case of being unable to convert. +/// Generic over both source and destination types. +pub trait TryConvert { + /// Attempt to make conversion. If returning [Result::Err], the inner must always be `a`. + fn try_convert(a: A) -> Result; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl TryConvert for Tuple { + fn try_convert(a: A) -> Result { + for_tuples!( #( + let a = match Tuple::try_convert(a) { + Ok(b) => return Ok(b), + Err(a) => a, + }; + )* ); + Err(a) + } +} + +/// Reversing fallible conversion trait which returns the argument in the case of being unable to +/// convert back. Generic over both source and destination types. +pub trait TryConvertBack: TryConvert { + /// Attempt to make conversion back. If returning [Result::Err], the inner must always be `b`. + + fn try_convert_back(b: B) -> Result; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl TryConvertBack for Tuple { + fn try_convert_back(b: B) -> Result { + for_tuples!( #( + let b = match Tuple::try_convert_back(b) { + Ok(a) => return Ok(a), + Err(b) => b, + }; + )* ); + Err(b) + } +} + +/// Definition for a bi-directional, fallible conversion between two types. +pub trait MaybeEquivalence { + /// Attempt to convert reference of `A` into value of `B`, returning `None` if not possible. + fn convert(a: &A) -> Option; + /// Attempt to convert reference of `B` into value of `A`, returning `None` if not possible. + fn convert_back(b: &B) -> Option; +} + +#[impl_trait_for_tuples::impl_for_tuples(30)] +impl MaybeEquivalence for Tuple { + fn convert(a: &A) -> Option { + for_tuples!( #( + match Tuple::convert(a) { + Some(b) => return Some(b), + None => {}, + } + )* ); + None + } + fn convert_back(b: &B) -> Option { + for_tuples!( #( + match Tuple::convert_back(b) { + Some(a) => return Some(a), + None => {}, + } + )* ); + None + } +} + +/// Adapter which turns a [Get] implementation into a [Convert] implementation which always returns +/// in the same value no matter the input. +pub struct ConvertToValue(sp_std::marker::PhantomData); +impl> Convert for ConvertToValue { + fn convert(_: X) -> Y { + T::get() + } +} +impl> MaybeConvert for ConvertToValue { + fn maybe_convert(_: X) -> Option { + Some(T::get()) + } +} +impl> MaybeConvertBack for ConvertToValue { + fn maybe_convert_back(_: Y) -> Option { + None + } +} +impl> TryConvert for ConvertToValue { + fn try_convert(_: X) -> Result { + Ok(T::get()) + } +} +impl> TryConvertBack for ConvertToValue { + fn try_convert_back(y: Y) -> Result { + Err(y) + } +} +impl> MaybeEquivalence for ConvertToValue { + fn convert(_: &X) -> Option { + Some(T::get()) + } + fn convert_back(_: &Y) -> Option { + None + } +} + /// A structure that performs identity conversion. pub struct Identity; impl Convert for Identity { @@ -522,19 +736,100 @@ impl ConvertBack for Identity { a } } +impl MaybeConvert for Identity { + fn maybe_convert(a: T) -> Option { + Some(a) + } +} +impl MaybeConvertBack for Identity { + fn maybe_convert_back(a: T) -> Option { + Some(a) + } +} +impl TryConvert for Identity { + fn try_convert(a: T) -> Result { + Ok(a) + } +} +impl TryConvertBack for Identity { + fn try_convert_back(a: T) -> Result { + Ok(a) + } +} +impl MaybeEquivalence for Identity { + fn convert(a: &T) -> Option { + Some(a.clone()) + } + fn convert_back(a: &T) -> Option { + Some(a.clone()) + } +} /// A structure that performs standard conversion using the standard Rust conversion traits. pub struct ConvertInto; -impl> Convert for ConvertInto { +impl, B> Convert for ConvertInto { fn convert(a: A) -> B { a.into() } } +impl, B> MaybeConvert for ConvertInto { + fn maybe_convert(a: A) -> Option { + Some(a.into()) + } +} +impl, B: Into> MaybeConvertBack for ConvertInto { + fn maybe_convert_back(b: B) -> Option { + Some(b.into()) + } +} +impl, B> TryConvert for ConvertInto { + fn try_convert(a: A) -> Result { + Ok(a.into()) + } +} +impl, B: Into> TryConvertBack for ConvertInto { + fn try_convert_back(b: B) -> Result { + Ok(b.into()) + } +} +impl, B: Clone + Into> MaybeEquivalence for ConvertInto { + fn convert(a: &A) -> Option { + Some(a.clone().into()) + } + fn convert_back(b: &B) -> Option { + Some(b.clone().into()) + } +} -/// Extensible conversion trait. Generic over both source and destination types. -pub trait ConvertBack: Convert { - /// Make conversion back. - fn convert_back(b: B) -> A; +/// A structure that performs standard conversion using the standard Rust conversion traits. +pub struct TryConvertInto; +impl, B> MaybeConvert for TryConvertInto { + fn maybe_convert(a: A) -> Option { + a.clone().try_into().ok() + } +} +impl, B: Clone + TryInto> MaybeConvertBack for TryConvertInto { + fn maybe_convert_back(b: B) -> Option { + b.clone().try_into().ok() + } +} +impl, B> TryConvert for TryConvertInto { + fn try_convert(a: A) -> Result { + a.clone().try_into().map_err(|_| a) + } +} +impl, B: Clone + TryInto> TryConvertBack for TryConvertInto { + fn try_convert_back(b: B) -> Result { + b.clone().try_into().map_err(|_| b) + } +} +impl, B: Clone + TryInto> MaybeEquivalence for TryConvertInto { + fn convert(a: &A) -> Option { + a.clone().try_into().ok() + } + fn convert_back(b: &B) -> Option { + b.clone().try_into().ok() + } } /// Convenience type to work around the highly unergonomic syntax needed @@ -664,18 +959,7 @@ pub trait Hash: + Hasher::Output> { /// The hash type produced. - type Output: Member - + MaybeSerializeDeserialize - + Debug - + sp_std::hash::Hash - + AsRef<[u8]> - + AsMut<[u8]> - + Copy - + Default - + Encode - + Decode - + MaxEncodedLen - + TypeInfo; + type Output: HashOutput; /// Produce the hash of some byte-slice. fn hash(s: &[u8]) -> Self::Output { @@ -694,9 +978,50 @@ pub trait Hash: fn trie_root(input: Vec<(Vec, Vec)>, state_version: StateVersion) -> Self::Output; } +/// Super trait with all the attributes for a hashing output. +pub trait HashOutput: + Member + + MaybeSerializeDeserialize + + MaybeDisplay + + MaybeFromStr + + Debug + + sp_std::hash::Hash + + AsRef<[u8]> + + AsMut<[u8]> + + Copy + + Ord + + Default + + Encode + + Decode + + EncodeLike + + MaxEncodedLen + + TypeInfo +{ +} + +impl HashOutput for T where + T: Member + + MaybeSerializeDeserialize + + MaybeDisplay + + MaybeFromStr + + Debug + + sp_std::hash::Hash + + AsRef<[u8]> + + AsMut<[u8]> + + Copy + + Ord + + Default + + Encode + + Decode + + EncodeLike + + MaxEncodedLen + + TypeInfo +{ +} + /// Blake2-256 Hash implementation. #[derive(PartialEq, Eq, Clone, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct BlakeTwo256; impl Hasher for BlakeTwo256 { @@ -712,18 +1037,18 @@ impl Hasher for BlakeTwo256 { impl Hash for BlakeTwo256 { type Output = sp_core::H256; - fn trie_root(input: Vec<(Vec, Vec)>, version: StateVersion) -> Self::Output { - sp_io::trie::blake2_256_root(input, version) - } - fn ordered_trie_root(input: Vec>, version: StateVersion) -> Self::Output { sp_io::trie::blake2_256_ordered_root(input, version) } + + fn trie_root(input: Vec<(Vec, Vec)>, version: StateVersion) -> Self::Output { + sp_io::trie::blake2_256_root(input, version) + } } /// Keccak-256 Hash implementation. #[derive(PartialEq, Eq, Clone, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Keccak256; impl Hasher for Keccak256 { @@ -739,13 +1064,13 @@ impl Hasher for Keccak256 { impl Hash for Keccak256 { type Output = sp_core::H256; - fn trie_root(input: Vec<(Vec, Vec)>, version: StateVersion) -> Self::Output { - sp_io::trie::keccak_256_root(input, version) - } - fn ordered_trie_root(input: Vec>, version: StateVersion) -> Self::Output { sp_io::trie::keccak_256_ordered_root(input, version) } + + fn trie_root(input: Vec<(Vec, Vec)>, version: StateVersion) -> Self::Output { + sp_io::trie::keccak_256_root(input, version) + } } /// Something that can be checked for equality and printed out to a debug channel if bad. @@ -804,11 +1129,13 @@ sp_core::impl_maybe_marker!( /// A type that implements Hash when in std environment. trait MaybeHash: sp_std::hash::Hash; +); - /// A type that implements Serialize when in std environment. +sp_core::impl_maybe_marker_std_or_serde!( + /// A type that implements Serialize when in std environment or serde feature is activated. trait MaybeSerialize: Serialize; - /// A type that implements Serialize, DeserializeOwned and Debug when in std environment. + /// A type that implements Serialize, DeserializeOwned and Debug when in std environment or serde feature is activated. trait MaybeSerializeDeserialize: DeserializeOwned, Serialize; ); @@ -827,31 +1154,24 @@ pub trait IsMember { /// `parent_hash`, as well as a `digest` and a block `number`. /// /// You can also create a `new` one from those fields. -pub trait Header: Clone + Send + Sync + Codec + Eq + MaybeSerialize + Debug + 'static { +pub trait Header: + Clone + Send + Sync + Codec + Eq + MaybeSerialize + Debug + TypeInfo + 'static +{ /// Header number. type Number: Member + MaybeSerializeDeserialize + + MaybeFromStr + Debug + sp_std::hash::Hash + Copy + MaybeDisplay + AtLeast32BitUnsigned - + Codec - + sp_std::str::FromStr; - /// Header hash type - type Hash: Member - + MaybeSerializeDeserialize - + Debug - + sp_std::hash::Hash - + Ord - + Copy - + MaybeDisplay + Default - + SimpleBitOps - + Codec - + AsRef<[u8]> - + AsMut<[u8]> - + TypeInfo; + + TypeInfo + + MaxEncodedLen + + FullCodec; + /// Header hash type + type Hash: HashOutput; /// Hashing algorithm type Hashing: Hash; @@ -895,29 +1215,52 @@ pub trait Header: Clone + Send + Sync + Codec + Eq + MaybeSerialize + Debug + 's } } +// Something that provides the Header Type. Only for internal usage and should only be used +// via `HeaderFor` or `BlockNumberFor`. +// +// This is needed to fix the "cyclical" issue in loading Header/BlockNumber as part of a +// `pallet::call`. Essentially, `construct_runtime` aggregates all calls to create a `RuntimeCall` +// that is then used to define `UncheckedExtrinsic`. +// ```ignore +// pub type UncheckedExtrinsic = +// generic::UncheckedExtrinsic; +// ``` +// This `UncheckedExtrinsic` is supplied to the `Block`. +// ```ignore +// pub type Block = generic::Block; +// ``` +// So, if we do not create a trait outside of `Block` that doesn't have `Extrinsic`, we go into a +// recursive loop leading to a build error. +// +// Note that this is a workaround for a compiler bug and should be removed when the compiler +// bug is fixed. +#[doc(hidden)] +pub trait HeaderProvider { + /// Header type. + type HeaderT: Header; +} + /// Something which fulfills the abstract idea of a Substrate block. It has types for /// `Extrinsic` pieces of information as well as a `Header`. /// /// You can get an iterator over each of the `extrinsics` and retrieve the `header`. -pub trait Block: Clone + Send + Sync + Codec + Eq + MaybeSerialize + Debug + 'static { +pub trait Block: + HeaderProvider::Header> + + Clone + + Send + + Sync + + Codec + + Eq + + MaybeSerialize + + Debug + + 'static +{ /// Type for extrinsics. type Extrinsic: Member + Codec + Extrinsic + MaybeSerialize; /// Header type. - type Header: Header; + type Header: Header + MaybeSerializeDeserialize; /// Block hash type. - type Hash: Member - + MaybeSerializeDeserialize - + Debug - + sp_std::hash::Hash - + Ord - + Copy - + MaybeDisplay - + Default - + SimpleBitOps - + Codec - + AsRef<[u8]> - + AsMut<[u8]> - + TypeInfo; + type Hash: HashOutput; /// Returns a reference to the header. fn header(&self) -> &Self::Header; @@ -939,14 +1282,14 @@ pub trait Block: Clone + Send + Sync + Codec + Eq + MaybeSerialize + Debug + 'st /// Something that acts like an `Extrinsic`. pub trait Extrinsic: Sized { /// The function call. - type Call; + type Call: TypeInfo; /// The payload we carry for signed extrinsics. /// /// Usually it will contain a `Signature` and /// may include some additional data that are specific to signed /// extrinsics. - type SignaturePayload; + type SignaturePayload: SignaturePayload; /// Is this `Extrinsic` signed? /// If no information are available about signed/unsigned, `None` should be returned. @@ -965,6 +1308,31 @@ pub trait Extrinsic: Sized { } } +/// Something that acts like a [`SignaturePayload`](Extrinsic::SignaturePayload) of an +/// [`Extrinsic`]. +pub trait SignaturePayload { + /// The type of the address that signed the extrinsic. + /// + /// Particular to a signed extrinsic. + type SignatureAddress: TypeInfo; + + /// The signature type of the extrinsic. + /// + /// Particular to a signed extrinsic. + type Signature: TypeInfo; + + /// The additional data that is specific to the signed extrinsic. + /// + /// Particular to a signed extrinsic. + type SignatureExtra: TypeInfo; +} + +impl SignaturePayload for () { + type SignatureAddress = (); + type Signature = (); + type SignatureExtra = (); +} + /// Implementor is an [`Extrinsic`] and provides metadata about this extrinsic. pub trait ExtrinsicMetadata { /// The format version of the `Extrinsic`. @@ -1740,7 +2108,7 @@ macro_rules! impl_opaque_keys_inner { /// } /// ``` #[macro_export] -#[cfg(feature = "std")] +#[cfg(any(feature = "serde", feature = "std"))] macro_rules! impl_opaque_keys { { $( #[ $attr:meta ] )* @@ -1770,7 +2138,7 @@ macro_rules! impl_opaque_keys { } #[macro_export] -#[cfg(not(feature = "std"))] +#[cfg(all(not(feature = "std"), not(feature = "serde")))] #[doc(hidden)] macro_rules! impl_opaque_keys { { diff --git a/primitives/runtime/src/transaction_validity.rs b/primitives/runtime/src/transaction_validity.rs index 3b89644c2864..836948493823 100644 --- a/primitives/runtime/src/transaction_validity.rs +++ b/primitives/runtime/src/transaction_validity.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,7 +36,7 @@ pub type TransactionTag = Vec; /// An invalid transaction validity. #[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum InvalidTransaction { /// The call of the transaction is not expected. Call, @@ -119,7 +119,7 @@ impl From for &'static str { /// An unknown transaction validity. #[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum UnknownTransaction { /// Could not lookup some information that is required to validate the transaction. CannotLookup, @@ -143,7 +143,7 @@ impl From for &'static str { /// Errors that can occur while checking the validity of a transaction. #[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum TransactionValidityError { /// The transaction is invalid. Invalid(InvalidTransaction), @@ -226,7 +226,7 @@ impl From for TransactionValidity { /// Depending on the source we might apply different validation schemes. /// For instance we can disallow specific kinds of transactions if they were not produced /// by our local node (for instance off-chain workers). -#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] +#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] pub enum TransactionSource { /// Transaction is already included in block. /// @@ -251,7 +251,7 @@ pub enum TransactionSource { } /// Information concerning a valid transaction. -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] pub struct ValidTransaction { /// Priority of the transaction. /// diff --git a/primitives/serializer/Cargo.toml b/primitives/serializer/Cargo.toml deleted file mode 100644 index 585e4b4e0dc2..000000000000 --- a/primitives/serializer/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "sp-serializer" -version = "4.0.0-dev" -authors = ["Parity Technologies "] -edition = "2021" -license = "Apache-2.0" -homepage = "https://substrate.io" -repository = "https://github.com/paritytech/substrate/" -description = "Substrate customizable serde serializer." -documentation = "https://docs.rs/sp-serializer" -readme = "README.md" - -[package.metadata.docs.rs] -targets = ["x86_64-unknown-linux-gnu"] - -[dependencies] -serde = "1.0.136" -serde_json = "1.0.85" diff --git a/primitives/serializer/README.md b/primitives/serializer/README.md deleted file mode 100644 index 083a0857cc0d..000000000000 --- a/primitives/serializer/README.md +++ /dev/null @@ -1,6 +0,0 @@ -Substrate customizable serde serializer. - -The idea is that we can later change the implementation -to something more compact, but for now we're using JSON. - -License: Apache-2.0 \ No newline at end of file diff --git a/primitives/serializer/src/lib.rs b/primitives/serializer/src/lib.rs deleted file mode 100644 index d1e364a133ba..000000000000 --- a/primitives/serializer/src/lib.rs +++ /dev/null @@ -1,45 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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. - -//! Substrate customizable serde serializer. -//! -//! The idea is that we can later change the implementation -//! to something more compact, but for now we're using JSON. - -#![warn(missing_docs)] - -pub use serde_json::{from_reader, from_slice, from_str, Error, Result}; - -const PROOF: &str = "Serializers are infallible; qed"; - -/// Serialize the given data structure as a pretty-printed String of JSON. -pub fn to_string_pretty(value: &T) -> String { - serde_json::to_string_pretty(value).expect(PROOF) -} - -/// Serialize the given data structure as a JSON byte vector. -pub fn encode(value: &T) -> Vec { - serde_json::to_vec(value).expect(PROOF) -} - -/// Serialize the given data structure as JSON into the IO stream. -pub fn to_writer( - writer: W, - value: &T, -) -> Result<()> { - serde_json::to_writer(writer, value) -} diff --git a/primitives/session/Cargo.toml b/primitives/session/Cargo.toml index 31ec009ab2c6..dc99c356cc19 100644 --- a/primitives/session/Cargo.toml +++ b/primitives/session/Cargo.toml @@ -13,13 +13,14 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-runtime = { version = "7.0.0", optional = true, path = "../runtime" } +sp-core = { version = "21.0.0", default-features = false, path = "../core" } +sp-runtime = { version = "24.0.0", optional = true, path = "../runtime" } sp-staking = { version = "4.0.0-dev", default-features = false, path = "../staking" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } +sp-keystore = { version = "0.27.0", path = "../keystore", optional = true } [features] default = [ "std" ] @@ -31,4 +32,5 @@ std = [ "sp-runtime/std", "sp-staking/std", "sp-std/std", + "sp-keystore", ] diff --git a/primitives/session/src/lib.rs b/primitives/session/src/lib.rs index 9b403409c915..45395e9766f5 100644 --- a/primitives/session/src/lib.rs +++ b/primitives/session/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,7 +24,7 @@ use codec::{Decode, Encode}; #[cfg(feature = "std")] use sp_api::ProvideRuntimeApi; #[cfg(feature = "std")] -use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use sp_runtime::traits::Block as BlockT; use sp_core::{crypto::KeyTypeId, RuntimeDebug}; use sp_staking::SessionIndex; @@ -112,20 +112,25 @@ pub fn generate_initial_session_keys( client: std::sync::Arc, at: Block::Hash, seeds: Vec, + keystore: sp_keystore::KeystorePtr, ) -> Result<(), sp_api::ApiError> where Block: BlockT, T: ProvideRuntimeApi, T::Api: SessionKeys, { + use sp_api::ApiExt; + if seeds.is_empty() { return Ok(()) } - let runtime_api = client.runtime_api(); + let mut runtime_api = client.runtime_api(); + + runtime_api.register_extension(sp_keystore::KeystoreExt::from(keystore)); for seed in seeds { - runtime_api.generate_session_keys(&BlockId::Hash(at), Some(seed.as_bytes().to_vec()))?; + runtime_api.generate_session_keys(at, Some(seed.as_bytes().to_vec()))?; } Ok(()) diff --git a/primitives/staking/Cargo.toml b/primitives/staking/Cargo.toml index a8e5a543dbf7..c6aee3cbcdb3 100644 --- a/primitives/staking/Cargo.toml +++ b/primitives/staking/Cargo.toml @@ -13,15 +13,19 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } +serde = { version = "1.0.163", default-features = false, features = ["derive", "alloc"], optional = true } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +impl-trait-for-tuples = "0.2.2" + +sp-core = { default-features = false, path = "../core" } +sp-runtime = { default-features = false, path = "../runtime" } +sp-std = { default-features = false, path = "../std" } [features] default = ["std"] std = [ + "serde/std", "codec/std", "scale-info/std", "sp-core/std", diff --git a/primitives/staking/src/currency_to_vote.rs b/primitives/staking/src/currency_to_vote.rs new file mode 100644 index 000000000000..556e5bd21042 --- /dev/null +++ b/primitives/staking/src/currency_to_vote.rs @@ -0,0 +1,101 @@ +// 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. + +use sp_runtime::{ + traits::{UniqueSaturatedFrom, UniqueSaturatedInto}, + SaturatedConversion, +}; + +/// A trait similar to `Convert` to convert values from `B` an abstract balance type +/// into u64 and back from u128. (This conversion is used in election and other places where complex +/// calculation over balance type is needed) +/// +/// Total issuance of the currency is passed in, but an implementation of this trait may or may not +/// use it. +/// +/// # WARNING +/// +/// the total issuance being passed in implies that the implementation must be aware of the fact +/// that its values can affect the outcome. This implies that if the vote value is dependent on the +/// total issuance, it should never ber written to storage for later re-use. +pub trait CurrencyToVote { + /// Convert balance to u64. + fn to_vote(value: B, issuance: B) -> u64; + + /// Convert u128 to balance. + fn to_currency(value: u128, issuance: B) -> B; +} + +/// An implementation of `CurrencyToVote` tailored for chain's that have a balance type of u128. +/// +/// The factor is the `(total_issuance / u64::MAX).max(1)`, represented as u64. Let's look at the +/// important cases: +/// +/// If the chain's total issuance is less than u64::MAX, this will always be 1, which means that +/// the factor will not have any effect. In this case, any account's balance is also less. Thus, +/// both of the conversions are basically an `as`; Any balance can fit in u64. +/// +/// If the chain's total issuance is more than 2*u64::MAX, then a factor might be multiplied and +/// divided upon conversion. +pub struct U128CurrencyToVote; + +impl U128CurrencyToVote { + fn factor(issuance: u128) -> u128 { + (issuance / u64::MAX as u128).max(1) + } +} + +impl CurrencyToVote for U128CurrencyToVote { + fn to_vote(value: u128, issuance: u128) -> u64 { + (value / Self::factor(issuance)).saturated_into() + } + + fn to_currency(value: u128, issuance: u128) -> u128 { + value.saturating_mul(Self::factor(issuance)) + } +} + +/// A naive implementation of `CurrencyConvert` that simply saturates all conversions. +/// +/// # Warning +/// +/// This is designed to be used mostly for testing. Use with care, and think about the consequences. +pub struct SaturatingCurrencyToVote; + +impl + UniqueSaturatedFrom> CurrencyToVote + for SaturatingCurrencyToVote +{ + fn to_vote(value: B, _: B) -> u64 { + value.unique_saturated_into() + } + + fn to_currency(value: u128, _: B) -> B { + B::unique_saturated_from(value) + } +} + +#[cfg(feature = "std")] +impl + UniqueSaturatedFrom> CurrencyToVote for () { + fn to_vote(value: B, issuance: B) -> u64 { + SaturatingCurrencyToVote::to_vote(value, issuance) + } + + /// Convert u128 to balance. + fn to_currency(value: u128, issuance: B) -> B { + SaturatingCurrencyToVote::to_currency(value, issuance) + } +} diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 9eb4a4890cdf..1621af164b37 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,46 +20,39 @@ //! A crate which contains primitives that are useful for implementation that uses staking //! approaches in general. Definitions related to sessions, slashing, etc go here. -use sp_runtime::{DispatchError, DispatchResult}; -use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; +use crate::currency_to_vote::CurrencyToVote; +use codec::{FullCodec, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_core::RuntimeDebug; +use sp_runtime::{DispatchError, DispatchResult, Saturating}; +use sp_std::{collections::btree_map::BTreeMap, ops::Sub, vec::Vec}; pub mod offence; +pub mod currency_to_vote; + /// Simple index type with which we can count sessions. pub type SessionIndex = u32; /// Counter for the number of eras that have passed. pub type EraIndex = u32; -/// Trait describing something that implements a hook for any operations to perform when a staker is -/// slashed. -pub trait OnStakerSlash { - /// A hook for any operations to perform when a staker is slashed. - /// - /// # Arguments - /// - /// * `stash` - The stash of the staker whom the slash was applied to. - /// * `slashed_active` - The new bonded balance of the staker after the slash was applied. - /// * `slashed_unlocking` - A map of slashed eras, and the balance of that unlocking chunk after - /// the slash is applied. Any era not present in the map is not affected at all. - fn on_slash( - stash: &AccountId, - slashed_active: Balance, - slashed_unlocking: &BTreeMap, - ); -} - -impl OnStakerSlash for () { - fn on_slash(_: &AccountId, _: Balance, _: &BTreeMap) { - // Nothing to do here - } +/// Representation of the status of a staker. +#[derive(RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone))] +pub enum StakerStatus { + /// Chilling. + Idle, + /// Declaring desire in validate, i.e author blocks. + Validator, + /// Declaring desire to nominate, delegate, or generally approve of the given set of others. + Nominator(Vec), } /// A struct that reflects stake that an account has in the staking system. Provides a set of /// methods to operate on it's properties. Aimed at making `StakingInterface` more concise. -pub struct Stake { - /// The stash account whose balance is actually locked and at stake. - pub stash: T::AccountId, +#[derive(RuntimeDebug, Clone, Copy, Eq, PartialEq, Default)] +pub struct Stake { /// The total stake that `stash` has in the staking system. This includes the /// `active` stake, and any funds currently in the process of unbonding via /// [`StakingInterface::unbond`]. @@ -69,10 +62,71 @@ pub struct Stake { /// This is only guaranteed to reflect the amount locked by the staking system. If there are /// non-staking locks on the bonded pair's balance this amount is going to be larger in /// reality. - pub total: T::Balance, + pub total: Balance, /// The total amount of the stash's balance that will be at stake in any forthcoming /// rounds. - pub active: T::Balance, + pub active: Balance, +} + +/// A generic staking event listener. +/// +/// Note that the interface is designed in a way that the events are fired post-action, so any +/// pre-action data that is needed needs to be passed to interface methods. The rest of the data can +/// be retrieved by using `StakingInterface`. +#[impl_trait_for_tuples::impl_for_tuples(10)] +pub trait OnStakingUpdate { + /// Fired when the stake amount of someone updates. + /// + /// This is effectively any changes to the bond amount, such as bonding more funds, and + /// unbonding. + fn on_stake_update(_who: &AccountId, _prev_stake: Option>) {} + + /// Fired when someone sets their intention to nominate. + /// + /// This should never be fired for existing nominators. + fn on_nominator_add(_who: &AccountId) {} + + /// Fired when an existing nominator updates their nominations. + /// + /// Note that this is not fired when a nominator changes their stake. For that, + /// `on_stake_update` should be used, followed by querying whether `who` was a validator or a + /// nominator. + fn on_nominator_update(_who: &AccountId, _prev_nominations: Vec) {} + + /// Fired when someone removes their intention to nominate, either due to chill or validating. + /// + /// The set of nominations at the time of removal is provided as it can no longer be fetched in + /// any way. + fn on_nominator_remove(_who: &AccountId, _nominations: Vec) {} + + /// Fired when someone sets their intention to validate. + /// + /// Note validator preference changes are not communicated, but could be added if needed. + fn on_validator_add(_who: &AccountId) {} + + /// Fired when an existing validator updates their preferences. + /// + /// Note validator preference changes are not communicated, but could be added if needed. + fn on_validator_update(_who: &AccountId) {} + + /// Fired when someone removes their intention to validate, either due to chill or nominating. + fn on_validator_remove(_who: &AccountId) {} + + /// Fired when someone is fully unstaked. + fn on_unstake(_who: &AccountId) {} + + /// Fired when a staker is slashed. + /// + /// * `stash` - The stash of the staker whom the slash was applied to. + /// * `slashed_active` - The new bonded balance of the staker after the slash was applied. + /// * `slashed_unlocking` - A map of slashed eras, and the balance of that unlocking chunk after + /// the slash is applied. Any era not present in the map is not affected at all. + fn on_slash( + _stash: &AccountId, + _slashed_active: Balance, + _slashed_unlocking: &BTreeMap, + ) { + } } /// A generic representation of a staking implementation. @@ -81,10 +135,21 @@ pub struct Stake { /// implementations as well. pub trait StakingInterface { /// Balance type used by the staking system. - type Balance: PartialEq; - - /// AccountId type used by the staking system - type AccountId; + type Balance: Sub + + Ord + + PartialEq + + Default + + Copy + + MaxEncodedLen + + FullCodec + + TypeInfo + + Saturating; + + /// AccountId type used by the staking system. + type AccountId: Clone + sp_std::fmt::Debug; + + /// Means of converting Currency to VoteWeight. + type CurrencyToVote: CurrencyToVote; /// The minimum amount required to bond in order to set nomination intentions. This does not /// necessarily mean the nomination will be counted in an election, but instead just enough to @@ -111,21 +176,25 @@ pub trait StakingInterface { /// This should be the latest planned era that the staking system knows about. fn current_era() -> EraIndex; - /// Returns the stake of `who`. - fn stake(who: &Self::AccountId) -> Result, DispatchError>; + /// Returns the [`Stake`] of `who`. + fn stake(who: &Self::AccountId) -> Result, DispatchError>; + /// Total stake of a staker, `Err` if not a staker. fn total_stake(who: &Self::AccountId) -> Result { Self::stake(who).map(|s| s.total) } + /// Total active portion of a staker's [`Stake`], `Err` if not a staker. fn active_stake(who: &Self::AccountId) -> Result { Self::stake(who).map(|s| s.active) } + /// Returns whether a staker is unbonding, `Err` if not a staker at all. fn is_unbonding(who: &Self::AccountId) -> Result { Self::stake(who).map(|s| s.active != s.total) } + /// Returns whether a staker is FULLY unbonding, `Err` if not a staker at all. fn fully_unbond(who: &Self::AccountId) -> DispatchResult { Self::unbond(who, Self::stake(who)?.active) } @@ -176,9 +245,21 @@ pub trait StakingInterface { /// Checks whether an account `staker` has been exposed in an era. fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool; + /// Return the status of the given staker, `None` if not staked at all. + fn status(who: &Self::AccountId) -> Result, DispatchError>; + + /// Checks whether or not this is a validator account. + fn is_validator(who: &Self::AccountId) -> bool { + Self::status(who).map(|s| matches!(s, StakerStatus::Validator)).unwrap_or(false) + } + /// Get the nominations of a stash, if they are a nominator, `None` otherwise. - #[cfg(feature = "runtime-benchmarks")] - fn nominations(who: Self::AccountId) -> Option>; + fn nominations(who: &Self::AccountId) -> Option> { + match Self::status(who) { + Ok(StakerStatus::Nominator(t)) => Some(t), + _ => None, + } + } #[cfg(feature = "runtime-benchmarks")] fn add_era_stakers( diff --git a/primitives/staking/src/offence.rs b/primitives/staking/src/offence.rs index f6517b9e9028..8013166374e0 100644 --- a/primitives/staking/src/offence.rs +++ b/primitives/staking/src/offence.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,10 +18,10 @@ //! Common traits and types that are useful for describing offences for usage in environments //! that use staking. -use sp_std::vec::Vec; - use codec::{Decode, Encode}; -use sp_runtime::Perbill; +use sp_core::Get; +use sp_runtime::{transaction_validity::TransactionValidityError, DispatchError, Perbill}; +use sp_std::vec::Vec; use crate::SessionIndex; @@ -209,3 +209,68 @@ pub struct OffenceDetails { /// particular reporters. pub reporters: Vec, } + +/// An abstract system to publish, check and process offence evidences. +/// +/// Implementation details are left opaque and we don't assume any specific usage +/// scenario for this trait at this level. The main goal is to group together some +/// common actions required during a typical offence report flow. +/// +/// Even though this trait doesn't assume too much, this is a general guideline +/// for a typical usage scenario: +/// +/// 1. An offence is detected and an evidence is submitted on-chain via the +/// [`OffenceReportSystem::publish_evidence`] method. This will construct and submit an extrinsic +/// transaction containing the offence evidence. +/// +/// 2. If the extrinsic is unsigned then the transaction receiver may want to perform some +/// preliminary checks before further processing. This is a good place to call the +/// [`OffenceReportSystem::check_evidence`] method. +/// +/// 3. Finally the report extrinsic is executed on-chain. This is where the user calls the +/// [`OffenceReportSystem::process_evidence`] to consume the offence report and enact any +/// required action. +pub trait OffenceReportSystem { + /// Longevity, in blocks, for the evidence report validity. + /// + /// For example, when using the staking pallet this should be set equal + /// to the bonding duration in blocks, not eras. + type Longevity: Get; + + /// Publish an offence evidence. + /// + /// Common usage: submit the evidence on-chain via some kind of extrinsic. + fn publish_evidence(evidence: Evidence) -> Result<(), ()>; + + /// Check an offence evidence. + /// + /// Common usage: preliminary validity check before execution + /// (e.g. for unsigned extrinsic quick checks). + fn check_evidence(evidence: Evidence) -> Result<(), TransactionValidityError>; + + /// Process an offence evidence. + /// + /// Common usage: enact some form of slashing directly or by forwarding + /// the evidence to a lower level specialized subsystem (e.g. a handler + /// implementing `ReportOffence` trait). + fn process_evidence(reporter: Reporter, evidence: Evidence) -> Result<(), DispatchError>; +} + +/// Dummy offence report system. +/// +/// Doesn't do anything special and returns `Ok(())` for all the actions. +impl OffenceReportSystem for () { + type Longevity = (); + + fn publish_evidence(_evidence: Evidence) -> Result<(), ()> { + Ok(()) + } + + fn check_evidence(_evidence: Evidence) -> Result<(), TransactionValidityError> { + Ok(()) + } + + fn process_evidence(_reporter: Reporter, _evidence: Evidence) -> Result<(), DispatchError> { + Ok(()) + } +} diff --git a/primitives/state-machine/Cargo.toml b/primitives/state-machine/Cargo.toml index 595dfe286d22..cffa4236c2a2 100644 --- a/primitives/state-machine/Cargo.toml +++ b/primitives/state-machine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-state-machine" -version = "0.13.0" +version = "0.28.0" authors = ["Parity Technologies "] description = "Substrate State Machine" edition = "2021" @@ -14,26 +14,26 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -hash-db = { version = "0.15.2", default-features = false } -log = { version = "0.4.17", optional = true } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +hash-db = { version = "0.16.0", default-features = false } +log = { version = "0.4.17", default-features = false } parking_lot = { version = "0.12.1", optional = true } rand = { version = "0.8.5", optional = true } smallvec = "1.8.0" thiserror = { version = "1.0.30", optional = true } tracing = { version = "0.1.29", optional = true } -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-externalities = { version = "0.13.0", default-features = false, path = "../externalities" } -sp-panic-handler = { version = "5.0.0", optional = true, path = "../panic-handler" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } -sp-trie = { version = "7.0.0", default-features = false, path = "../trie" } +sp-core = { version = "21.0.0", default-features = false, path = "../core" } +sp-externalities = { version = "0.19.0", default-features = false, path = "../externalities" } +sp-panic-handler = { version = "8.0.0", optional = true, path = "../panic-handler" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } +sp-trie = { version = "22.0.0", default-features = false, path = "../trie" } +trie-db = { version = "0.27.1", default-features = false } [dev-dependencies] -array-bytes = "4.1" +array-bytes = "6.1" pretty_assertions = "1.2.1" rand = "0.8.5" -sp-runtime = { version = "7.0.0", path = "../runtime" } -trie-db = "0.24.0" +sp-runtime = { version = "24.0.0", path = "../runtime" } assert_matches = "1.5" [features] @@ -41,7 +41,7 @@ default = ["std"] std = [ "codec/std", "hash-db/std", - "log", + "log/std", "parking_lot", "rand", "sp-core/std", @@ -49,6 +49,7 @@ std = [ "sp-panic-handler", "sp-std/std", "sp-trie/std", + "trie-db/std", "thiserror", "tracing", ] diff --git a/primitives/state-machine/src/backend.rs b/primitives/state-machine/src/backend.rs index 791183c4d7e4..f3244308a54c 100644 --- a/primitives/state-machine/src/backend.rs +++ b/primitives/state-machine/src/backend.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,12 +24,150 @@ use crate::{ StorageKey, StorageValue, UsageInfo, }; use codec::Encode; +use core::marker::PhantomData; use hash_db::Hasher; use sp_core::storage::{ChildInfo, StateVersion, TrackedStorageKey}; #[cfg(feature = "std")] use sp_core::traits::RuntimeCode; use sp_std::vec::Vec; +/// A struct containing arguments for iterating over the storage. +#[derive(Default)] +#[non_exhaustive] +pub struct IterArgs<'a> { + /// The prefix of the keys over which to iterate. + pub prefix: Option<&'a [u8]>, + + /// The prefix from which to start the iteration from. + /// + /// This is inclusive and the iteration will include the key which is specified here. + pub start_at: Option<&'a [u8]>, + + /// If this is `true` then the iteration will *not* include + /// the key specified in `start_at`, if there is such a key. + pub start_at_exclusive: bool, + + /// The info of the child trie over which to iterate over. + pub child_info: Option, + + /// Whether to stop iteration when a missing trie node is reached. + /// + /// When a missing trie node is reached the iterator will: + /// - return an error if this is set to `false` (default) + /// - return `None` if this is set to `true` + pub stop_on_incomplete_database: bool, +} + +/// A trait for a raw storage iterator. +pub trait StorageIterator +where + H: Hasher, +{ + /// The state backend over which the iterator is iterating. + type Backend; + + /// The error type. + type Error; + + /// Fetches the next key from the storage. + fn next_key( + &mut self, + backend: &Self::Backend, + ) -> Option>; + + /// Fetches the next key and value from the storage. + fn next_pair( + &mut self, + backend: &Self::Backend, + ) -> Option>; + + /// Returns whether the end of iteration was reached without an error. + fn was_complete(&self) -> bool; +} + +/// An iterator over storage keys and values. +pub struct PairsIter<'a, H, I> +where + H: Hasher, + I: StorageIterator, +{ + backend: Option<&'a I::Backend>, + raw_iter: I, + _phantom: PhantomData, +} + +impl<'a, H, I> Iterator for PairsIter<'a, H, I> +where + H: Hasher, + I: StorageIterator, +{ + type Item = Result<(Vec, Vec), >::Error>; + fn next(&mut self) -> Option { + self.raw_iter.next_pair(self.backend.as_ref()?) + } +} + +impl<'a, H, I> Default for PairsIter<'a, H, I> +where + H: Hasher, + I: StorageIterator + Default, +{ + fn default() -> Self { + Self { + backend: Default::default(), + raw_iter: Default::default(), + _phantom: Default::default(), + } + } +} + +impl<'a, H, I> PairsIter<'a, H, I> +where + H: Hasher, + I: StorageIterator + Default, +{ + #[cfg(feature = "std")] + pub(crate) fn was_complete(&self) -> bool { + self.raw_iter.was_complete() + } +} + +/// An iterator over storage keys. +pub struct KeysIter<'a, H, I> +where + H: Hasher, + I: StorageIterator, +{ + backend: Option<&'a I::Backend>, + raw_iter: I, + _phantom: PhantomData, +} + +impl<'a, H, I> Iterator for KeysIter<'a, H, I> +where + H: Hasher, + I: StorageIterator, +{ + type Item = Result, >::Error>; + fn next(&mut self) -> Option { + self.raw_iter.next_key(self.backend.as_ref()?) + } +} + +impl<'a, H, I> Default for KeysIter<'a, H, I> +where + H: Hasher, + I: StorageIterator + Default, +{ + fn default() -> Self { + Self { + backend: Default::default(), + raw_iter: Default::default(), + _phantom: Default::default(), + } + } +} + /// A state backend is used to read state data and can have changes committed /// to it. /// @@ -44,6 +182,9 @@ pub trait Backend: sp_std::fmt::Debug { /// Type of trie backend storage. type TrieBackendStorage: TrieBackendStorage; + /// Type of the raw storage iterator. + type RawIter: StorageIterator; + /// Get keyed storage or None if there is nothing associated. fn storage(&self, key: &[u8]) -> Result, Self::Error>; @@ -88,51 +229,6 @@ pub trait Backend: sp_std::fmt::Debug { key: &[u8], ) -> Result, Self::Error>; - /// Iterate over storage starting at key, for a given prefix and child trie. - /// Aborts as soon as `f` returns false. - /// Warning, this fails at first error when usual iteration skips errors. - /// If `allow_missing` is true, iteration stops when it reaches a missing trie node. - /// Otherwise an error is produced. - /// - /// Returns `true` if trie end is reached. - fn apply_to_key_values_while, Vec) -> bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - start_at: Option<&[u8]>, - f: F, - allow_missing: bool, - ) -> Result; - - /// Retrieve all entries keys of storage and call `f` for each of those keys. - /// Aborts as soon as `f` returns false. - fn apply_to_keys_while bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - start_at: Option<&[u8]>, - f: F, - ); - - /// Retrieve all entries keys which start with the given prefix and - /// call `f` for each of those keys. - fn for_keys_with_prefix(&self, prefix: &[u8], mut f: F) { - self.for_key_values_with_prefix(prefix, |k, _v| f(k)) - } - - /// Retrieve all entries keys and values of which start with the given prefix and - /// call `f` for each of those keys. - fn for_key_values_with_prefix(&self, prefix: &[u8], f: F); - - /// Retrieve all child entries keys which start with the given prefix and - /// call `f` for each of those keys. - fn for_child_keys_with_prefix( - &self, - child_info: &ChildInfo, - prefix: &[u8], - f: F, - ); - /// Calculate the storage root, with given delta over what is already stored in /// the backend, and produce a "transaction" that can be used to commit. /// Does not include child storage updates. @@ -156,21 +252,25 @@ pub trait Backend: sp_std::fmt::Debug { where H::Out: Ord; - /// Get all key/value pairs into a Vec. - fn pairs(&self) -> Vec<(StorageKey, StorageValue)>; + /// Returns a lifetimeless raw storage iterator. + fn raw_iter(&self, args: IterArgs) -> Result; - /// Get all keys with given prefix - fn keys(&self, prefix: &[u8]) -> Vec { - let mut all = Vec::new(); - self.for_keys_with_prefix(prefix, |k| all.push(k.to_vec())); - all + /// Get an iterator over key/value pairs. + fn pairs<'a>(&'a self, args: IterArgs) -> Result, Self::Error> { + Ok(PairsIter { + backend: Some(self), + raw_iter: self.raw_iter(args)?, + _phantom: Default::default(), + }) } - /// Get all keys of child storage with given prefix - fn child_keys(&self, child_info: &ChildInfo, prefix: &[u8]) -> Vec { - let mut all = Vec::new(); - self.for_child_keys_with_prefix(child_info, prefix, |k| all.push(k.to_vec())); - all + /// Get an iterator over keys. + fn keys<'a>(&'a self, args: IterArgs) -> Result, Self::Error> { + Ok(KeysIter { + backend: Some(self), + raw_iter: self.raw_iter(args)?, + _phantom: Default::default(), + }) } /// Calculate the storage root, with given delta over what is already stored @@ -309,7 +409,7 @@ where #[cfg(feature = "std")] pub struct BackendRuntimeCode<'a, B, H> { backend: &'a B, - _marker: std::marker::PhantomData, + _marker: PhantomData, } #[cfg(feature = "std")] @@ -332,7 +432,7 @@ where { /// Create a new instance. pub fn new(backend: &'a B) -> Self { - Self { backend, _marker: std::marker::PhantomData } + Self { backend, _marker: PhantomData } } /// Return the [`RuntimeCode`] build from the wrapped `backend`. diff --git a/primitives/state-machine/src/basic.rs b/primitives/state-machine/src/basic.rs index fdc50e3f8f20..a7adbc8a0dae 100644 --- a/primitives/state-machine/src/basic.rs +++ b/primitives/state-machine/src/basic.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/state-machine/src/error.rs b/primitives/state-machine/src/error.rs index a12b3eae71bd..4e8e02a26025 100644 --- a/primitives/state-machine/src/error.rs +++ b/primitives/state-machine/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/state-machine/src/ext.rs b/primitives/state-machine/src/ext.rs index 1db0ec517015..3c088a217658 100644 --- a/primitives/state-machine/src/ext.rs +++ b/primitives/state-machine/src/ext.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,7 +19,9 @@ #[cfg(feature = "std")] use crate::overlayed_changes::OverlayedExtensions; -use crate::{backend::Backend, IndexOperation, OverlayedChanges, StorageKey, StorageValue}; +use crate::{ + backend::Backend, IndexOperation, IterArgs, OverlayedChanges, StorageKey, StorageValue, +}; use codec::{Decode, Encode, EncodeAppend}; use hash_db::Hasher; #[cfg(feature = "std")] @@ -159,9 +161,10 @@ where use std::collections::HashMap; self.backend - .pairs() - .iter() - .map(|&(ref k, ref v)| (k.to_vec(), Some(v.to_vec()))) + .pairs(Default::default()) + .expect("never fails in tests; qed.") + .map(|key_value| key_value.expect("never fails in tests; qed.")) + .map(|(k, v)| (k, Some(v))) .chain(self.overlay.changes().map(|(k, v)| (k.clone(), v.value().cloned()))) .collect::>() .into_iter() @@ -749,36 +752,56 @@ where { fn limit_remove_from_backend( &mut self, - maybe_child: Option<&ChildInfo>, - maybe_prefix: Option<&[u8]>, + child_info: Option<&ChildInfo>, + prefix: Option<&[u8]>, maybe_limit: Option, - maybe_cursor: Option<&[u8]>, + start_at: Option<&[u8]>, ) -> (Option>, u32, u32) { + let iter = match self.backend.keys(IterArgs { + child_info: child_info.cloned(), + prefix, + start_at, + ..IterArgs::default() + }) { + Ok(iter) => iter, + Err(error) => { + log::debug!(target: "trie", "Error while iterating the storage: {}", error); + return (None, 0, 0) + }, + }; + let mut delete_count: u32 = 0; let mut loop_count: u32 = 0; let mut maybe_next_key = None; - self.backend - .apply_to_keys_while(maybe_child, maybe_prefix, maybe_cursor, |key| { - if maybe_limit.map_or(false, |limit| loop_count == limit) { - maybe_next_key = Some(key.to_vec()); - return false - } - let overlay = match maybe_child { - Some(child_info) => self.overlay.child_storage(child_info, key), - None => self.overlay.storage(key), - }; - if !matches!(overlay, Some(None)) { - // not pending deletion from the backend - delete it. - if let Some(child_info) = maybe_child { - self.overlay.set_child_storage(child_info, key.to_vec(), None); - } else { - self.overlay.set_storage(key.to_vec(), None); - } - delete_count = delete_count.saturating_add(1); + for key in iter { + let key = match key { + Ok(key) => key, + Err(error) => { + log::debug!(target: "trie", "Error while iterating the storage: {}", error); + break + }, + }; + + if maybe_limit.map_or(false, |limit| loop_count == limit) { + maybe_next_key = Some(key); + break + } + let overlay = match child_info { + Some(child_info) => self.overlay.child_storage(child_info, &key), + None => self.overlay.storage(&key), + }; + if !matches!(overlay, Some(None)) { + // not pending deletion from the backend - delete it. + if let Some(child_info) = child_info { + self.overlay.set_child_storage(child_info, key, None); + } else { + self.overlay.set_storage(key, None); } - loop_count = loop_count.saturating_add(1); - true - }); + delete_count = delete_count.saturating_add(1); + } + loop_count = loop_count.saturating_add(1); + } + (maybe_next_key, delete_count, loop_count) } } diff --git a/primitives/state-machine/src/in_memory_backend.rs b/primitives/state-machine/src/in_memory_backend.rs index 06714fb41405..2c3ed7441501 100644 --- a/primitives/state-machine/src/in_memory_backend.rs +++ b/primitives/state-machine/src/in_memory_backend.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/state-machine/src/lib.rs b/primitives/state-machine/src/lib.rs index 188fb2f4b8d9..3ef923851ffd 100644 --- a/primitives/state-machine/src/lib.rs +++ b/primitives/state-machine/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -36,6 +36,8 @@ mod testing; mod trie_backend; mod trie_backend_essence; +pub use trie_backend::TrieCacheProvider; + #[cfg(feature = "std")] pub use std_reexport::*; @@ -123,7 +125,7 @@ impl sp_std::fmt::Display for DefaultError { } pub use crate::{ - backend::Backend, + backend::{Backend, IterArgs, KeysIter, PairsIter, StorageIterator}, error::{Error, ExecutionError}, ext::Ext, overlayed_changes::{ @@ -163,17 +165,10 @@ mod execution { use sp_core::{ hexdisplay::HexDisplay, storage::{ChildInfo, ChildType, PrefixedStorageKey}, - traits::{CodeExecutor, ReadRuntimeVersionExt, RuntimeCode, SpawnNamed}, + traits::{CallContext, CodeExecutor, RuntimeCode}, }; use sp_externalities::Extensions; - use std::{ - collections::{HashMap, HashSet}, - fmt, - }; - - const PROOF_CLOSE_TRANSACTION: &str = "\ - Closing a transaction that was started in this function. Client initiated transactions - are protected from being closed by the runtime. qed"; + use std::collections::{HashMap, HashSet}; pub(crate) type CallResult = Result, E>; @@ -183,21 +178,6 @@ mod execution { /// Trie backend with in-memory storage. pub type InMemoryBackend = TrieBackend, H>; - /// Strategy for executing a call into the runtime. - #[derive(Copy, Clone, Eq, PartialEq, Debug)] - pub enum ExecutionStrategy { - /// Execute with the native equivalent if it is compatible with the given wasm module; - /// otherwise fall back to the wasm. - NativeWhenPossible, - /// Use the given wasm module. - AlwaysWasm, - /// Run with both the wasm and the native variant (if compatible). Report any discrepancy - /// as an error. - Both, - /// First native, then if that fails or is not possible, wasm. - NativeElseWasm, - } - /// Storage backend trust level. #[derive(Debug, Clone)] pub enum BackendTrustLevel { @@ -209,73 +189,6 @@ mod execution { Untrusted, } - /// Like `ExecutionStrategy` only it also stores a handler in case of consensus failure. - #[derive(Clone)] - pub enum ExecutionManager { - /// Execute with the native equivalent if it is compatible with the given wasm module; - /// otherwise fall back to the wasm. - NativeWhenPossible, - /// Use the given wasm module. The backend on which code is executed code could be - /// trusted to provide all storage or not (i.e. the light client cannot be trusted to - /// provide for all storage queries since the storage entries it has come from an external - /// node). - AlwaysWasm(BackendTrustLevel), - /// Run with both the wasm and the native variant (if compatible). Call `F` in the case of - /// any discrepancy. - Both(F), - /// First native, then if that fails or is not possible, wasm. - NativeElseWasm, - } - - impl<'a, F> From<&'a ExecutionManager> for ExecutionStrategy { - fn from(s: &'a ExecutionManager) -> Self { - match *s { - ExecutionManager::NativeWhenPossible => ExecutionStrategy::NativeWhenPossible, - ExecutionManager::AlwaysWasm(_) => ExecutionStrategy::AlwaysWasm, - ExecutionManager::NativeElseWasm => ExecutionStrategy::NativeElseWasm, - ExecutionManager::Both(_) => ExecutionStrategy::Both, - } - } - } - - impl ExecutionStrategy { - /// Gets the corresponding manager for the execution strategy. - pub fn get_manager(self) -> ExecutionManager> { - match self { - ExecutionStrategy::AlwaysWasm => - ExecutionManager::AlwaysWasm(BackendTrustLevel::Trusted), - ExecutionStrategy::NativeWhenPossible => ExecutionManager::NativeWhenPossible, - ExecutionStrategy::NativeElseWasm => ExecutionManager::NativeElseWasm, - ExecutionStrategy::Both => ExecutionManager::Both(|wasm_result, native_result| { - warn!( - "Consensus error between wasm {:?} and native {:?}. Using wasm.", - wasm_result, native_result, - ); - warn!(" Native result {:?}", native_result); - warn!(" Wasm result {:?}", wasm_result); - wasm_result - }), - } - } - } - - /// Evaluate to ExecutionManager::NativeElseWasm, without having to figure out the type. - pub fn native_else_wasm() -> ExecutionManager> { - ExecutionManager::NativeElseWasm - } - - /// Evaluate to ExecutionManager::AlwaysWasm with trusted backend, without having to figure out - /// the type. - fn always_wasm() -> ExecutionManager> { - ExecutionManager::AlwaysWasm(BackendTrustLevel::Trusted) - } - - /// Evaluate ExecutionManager::AlwaysWasm with untrusted backend, without having to figure out - /// the type. - fn always_untrusted_wasm() -> ExecutionManager> { - ExecutionManager::AlwaysWasm(BackendTrustLevel::Untrusted) - } - /// The substrate state machine. pub struct StateMachine<'a, B, H, Exec> where @@ -287,7 +200,7 @@ mod execution { method: &'a str, call_data: &'a [u8], overlay: &'a mut OverlayedChanges, - extensions: Extensions, + extensions: &'a mut Extensions, storage_transaction_cache: Option<&'a mut StorageTransactionCache>, runtime_code: &'a RuntimeCode<'a>, stats: StateMachineStats, @@ -295,6 +208,7 @@ mod execution { /// /// Used for logging. parent_hash: Option, + context: CallContext, } impl<'a, B, H, Exec> Drop for StateMachine<'a, B, H, Exec> @@ -321,13 +235,10 @@ mod execution { exec: &'a Exec, method: &'a str, call_data: &'a [u8], - mut extensions: Extensions, + extensions: &'a mut Extensions, runtime_code: &'a RuntimeCode, - spawn_handle: impl SpawnNamed + Send + 'static, + context: CallContext, ) -> Self { - extensions.register(ReadRuntimeVersionExt::new(exec.clone())); - extensions.register(sp_core::traits::TaskExecutorExt::new(spawn_handle)); - Self { backend, exec, @@ -339,6 +250,7 @@ mod execution { runtime_code, stats: StateMachineStats::default(), parent_hash: None, + context, } } @@ -371,13 +283,7 @@ mod execution { /// blocks (e.g. a transaction at a time), ensure a different method is used. /// /// Returns the SCALE encoded result of the executed function. - pub fn execute(&mut self, strategy: ExecutionStrategy) -> Result, Box> { - // We are not giving a native call and thus we are sure that the result can never be a - // native value. - self.execute_using_consensus_failure_handler(strategy.get_manager()) - } - - fn execute_aux(&mut self, use_native: bool) -> (CallResult, bool) { + pub fn execute(&mut self) -> Result, Box> { let mut cache = StorageTransactionCache::default(); let cache = match self.storage_transaction_cache.as_mut() { @@ -389,7 +295,7 @@ mod execution { .enter_runtime() .expect("StateMachine is never called from the runtime; qed"); - let mut ext = Ext::new(self.overlay, cache, self.backend, Some(&mut self.extensions)); + let mut ext = Ext::new(self.overlay, cache, self.backend, Some(self.extensions)); let ext_id = ext.id; @@ -402,13 +308,10 @@ mod execution { "Call", ); - let (result, was_native) = self.exec.call( - &mut ext, - self.runtime_code, - self.method, - self.call_data, - use_native, - ); + let result = self + .exec + .call(&mut ext, self.runtime_code, self.method, self.call_data, false, self.context) + .0; self.overlay .exit_runtime() @@ -417,101 +320,19 @@ mod execution { trace!( target: "state", ext_id = %HexDisplay::from(&ext_id.to_le_bytes()), - ?was_native, ?result, "Return", ); - (result, was_native) - } - - fn execute_call_with_both_strategy( - &mut self, - on_consensus_failure: Handler, - ) -> CallResult - where - Handler: - FnOnce(CallResult, CallResult) -> CallResult, - { - self.overlay.start_transaction(); - let (result, was_native) = self.execute_aux(true); - - if was_native { - self.overlay.rollback_transaction().expect(PROOF_CLOSE_TRANSACTION); - let (wasm_result, _) = self.execute_aux(false); - - if (result.is_ok() && - wasm_result.is_ok() && result.as_ref().ok() == wasm_result.as_ref().ok()) || - result.is_err() && wasm_result.is_err() - { - result - } else { - on_consensus_failure(wasm_result, result) - } - } else { - self.overlay.commit_transaction().expect(PROOF_CLOSE_TRANSACTION); - result - } - } - - fn execute_call_with_native_else_wasm_strategy(&mut self) -> CallResult { - self.overlay.start_transaction(); - let (result, was_native) = self.execute_aux(true); - - if !was_native || result.is_ok() { - self.overlay.commit_transaction().expect(PROOF_CLOSE_TRANSACTION); - result - } else { - self.overlay.rollback_transaction().expect(PROOF_CLOSE_TRANSACTION); - self.execute_aux(false).0 - } - } - - /// Execute a call using the given state backend, overlayed changes, and call executor. - /// - /// On an error, no prospective changes are written to the overlay. - /// - /// Note: changes to code will be in place if this call is made again. For running partial - /// blocks (e.g. a transaction at a time), ensure a different method is used. - /// - /// Returns the result of the executed function either in native representation `R` or - /// in SCALE encoded representation. - pub fn execute_using_consensus_failure_handler( - &mut self, - manager: ExecutionManager, - ) -> Result, Box> - where - Handler: - FnOnce(CallResult, CallResult) -> CallResult, - { - let result = { - match manager { - ExecutionManager::Both(on_consensus_failure) => - self.execute_call_with_both_strategy(on_consensus_failure), - ExecutionManager::NativeElseWasm => - self.execute_call_with_native_else_wasm_strategy(), - ExecutionManager::AlwaysWasm(trust_level) => { - let _abort_guard = match trust_level { - BackendTrustLevel::Trusted => None, - BackendTrustLevel::Untrusted => - Some(sp_panic_handler::AbortGuard::never_abort()), - }; - self.execute_aux(false).0 - }, - ExecutionManager::NativeWhenPossible => self.execute_aux(true).0, - } - }; - - result.map_err(|e| Box::new(e) as _) + result.map_err(|e| Box::new(e) as Box<_>) } } /// Prove execution using the given state backend, overlayed changes, and call executor. - pub fn prove_execution( + pub fn prove_execution( backend: &mut B, overlay: &mut OverlayedChanges, exec: &Exec, - spawn_handle: Spawn, method: &str, call_data: &[u8], runtime_code: &RuntimeCode, @@ -521,18 +342,16 @@ mod execution { H: Hasher, H::Out: Ord + 'static + codec::Codec, Exec: CodeExecutor + Clone + 'static, - Spawn: SpawnNamed + Send + 'static, { let trie_backend = backend.as_trie_backend(); - prove_execution_on_trie_backend::<_, _, _, _>( + prove_execution_on_trie_backend::<_, _, _>( trie_backend, overlay, exec, - spawn_handle, method, call_data, runtime_code, - Default::default(), + &mut Default::default(), ) } @@ -545,22 +364,20 @@ mod execution { /// /// Note: changes to code will be in place if this call is made again. For running partial /// blocks (e.g. a transaction at a time), ensure a different method is used. - pub fn prove_execution_on_trie_backend( + pub fn prove_execution_on_trie_backend( trie_backend: &TrieBackend, overlay: &mut OverlayedChanges, exec: &Exec, - spawn_handle: Spawn, method: &str, call_data: &[u8], runtime_code: &RuntimeCode, - extensions: Extensions, + extensions: &mut Extensions, ) -> Result<(Vec, StorageProof), Box> where S: trie_backend_essence::TrieBackendStorage, H: Hasher, H::Out: Ord + 'static + codec::Codec, Exec: CodeExecutor + 'static + Clone, - Spawn: SpawnNamed + Send + 'static, { let proving_backend = TrieBackendBuilder::wrap(trie_backend).with_recorder(Default::default()).build(); @@ -573,9 +390,9 @@ mod execution { call_data, extensions, runtime_code, - spawn_handle, + CallContext::Offchain, ) - .execute_using_consensus_failure_handler::<_>(always_wasm())?; + .execute()?; let proof = proving_backend .extract_proof() @@ -585,12 +402,11 @@ mod execution { } /// Check execution proof, generated by `prove_execution` call. - pub fn execution_proof_check( + pub fn execution_proof_check( root: H::Out, proof: StorageProof, overlay: &mut OverlayedChanges, exec: &Exec, - spawn_handle: Spawn, method: &str, call_data: &[u8], runtime_code: &RuntimeCode, @@ -599,14 +415,12 @@ mod execution { H: Hasher + 'static, Exec: CodeExecutor + Clone + 'static, H::Out: Ord + 'static + codec::Codec, - Spawn: SpawnNamed + Send + 'static, { let trie_backend = create_proof_check_backend::(root, proof)?; - execution_proof_check_on_trie_backend::<_, _, _>( + execution_proof_check_on_trie_backend::<_, _>( &trie_backend, overlay, exec, - spawn_handle, method, call_data, runtime_code, @@ -614,11 +428,10 @@ mod execution { } /// Check execution proof on proving backend, generated by `prove_execution` call. - pub fn execution_proof_check_on_trie_backend( + pub fn execution_proof_check_on_trie_backend( trie_backend: &TrieBackend, H>, overlay: &mut OverlayedChanges, exec: &Exec, - spawn_handle: Spawn, method: &str, call_data: &[u8], runtime_code: &RuntimeCode, @@ -627,7 +440,6 @@ mod execution { H: Hasher, H::Out: Ord + 'static + codec::Codec, Exec: CodeExecutor + Clone + 'static, - Spawn: SpawnNamed + Send + 'static, { StateMachine::<_, H, Exec>::new( trie_backend, @@ -635,11 +447,11 @@ mod execution { exec, method, call_data, - Extensions::default(), + &mut Extensions::default(), runtime_code, - spawn_handle, + CallContext::Offchain, ) - .execute_using_consensus_failure_handler(always_untrusted_wasm()) + .execute() } /// Generate storage read proof. @@ -843,48 +655,37 @@ mod execution { let start_at_ref = start_at.as_ref().map(AsRef::as_ref); let mut switch_child_key = None; - let mut first = start_at.is_some(); - let completed = proving_backend - .apply_to_key_values_while( - child_info.as_ref(), - None, - start_at_ref, - |key, value| { - if first && - start_at_ref - .as_ref() - .map(|start| &key.as_slice() > start) - .unwrap_or(true) - { - first = false; - } - - if first { - true - } else if depth < MAX_NESTED_TRIE_DEPTH && - sp_core::storage::well_known_keys::is_child_storage_key( - key.as_slice(), - ) { - count += 1; - if !child_roots.contains(value.as_slice()) { - child_roots.insert(value); - switch_child_key = Some(key); - false - } else { - // do not add two child trie with same root - true - } - } else if recorder.estimate_encoded_size() <= size_limit { - count += 1; - true - } else { - false - } - }, - false, - ) + let mut iter = proving_backend + .pairs(IterArgs { + child_info, + start_at: start_at_ref, + start_at_exclusive: true, + ..IterArgs::default() + }) .map_err(|e| Box::new(e) as Box)?; + while let Some(item) = iter.next() { + let (key, value) = item.map_err(|e| Box::new(e) as Box)?; + + if depth < MAX_NESTED_TRIE_DEPTH && + sp_core::storage::well_known_keys::is_child_storage_key(key.as_slice()) + { + count += 1; + // do not add two child trie with same root + if !child_roots.contains(value.as_slice()) { + child_roots.insert(value); + switch_child_key = Some(key); + break + } + } else if recorder.estimate_encoded_size() <= size_limit { + count += 1; + } else { + break + } + } + + let completed = iter.was_complete(); + if switch_child_key.is_none() { if depth == 1 { break @@ -945,23 +746,27 @@ mod execution { let proving_backend = TrieBackendBuilder::wrap(trie_backend).with_recorder(recorder.clone()).build(); let mut count = 0; - proving_backend - .apply_to_key_values_while( - child_info, + let iter = proving_backend + // NOTE: Even though the loop below doesn't use these values + // this *must* fetch both the keys and the values so that + // the proof is correct. + .pairs(IterArgs { + child_info: child_info.cloned(), prefix, start_at, - |_key, _value| { - if count == 0 || recorder.estimate_encoded_size() <= size_limit { - count += 1; - true - } else { - false - } - }, - false, - ) + ..IterArgs::default() + }) .map_err(|e| Box::new(e) as Box)?; + for item in iter { + item.map_err(|e| Box::new(e) as Box)?; + if count == 0 || recorder.estimate_encoded_size() <= size_limit { + count += 1; + } else { + break + } + } + let proof = proving_backend .extract_proof() .expect("A recorder was set and thus, a storage proof can be extracted; qed"); @@ -1167,20 +972,25 @@ mod execution { H::Out: Ord + Codec, { let mut values = Vec::new(); - let result = proving_backend.apply_to_key_values_while( - child_info, - prefix, - start_at, - |key, value| { - values.push((key.to_vec(), value.to_vec())); - count.as_ref().map_or(true, |c| (values.len() as u32) < *c) - }, - true, - ); - match result { - Ok(completed) => Ok((values, completed)), - Err(e) => Err(Box::new(e) as Box), + let mut iter = proving_backend + .pairs(IterArgs { + child_info: child_info.cloned(), + prefix, + start_at, + stop_on_incomplete_database: true, + ..IterArgs::default() + }) + .map_err(|e| Box::new(e) as Box)?; + + while let Some(item) = iter.next() { + let (key, value) = item.map_err(|e| Box::new(e) as Box)?; + values.push((key, value)); + if !count.as_ref().map_or(true, |c| (values.len() as u32) < *c) { + break + } } + + Ok((values, iter.was_complete())) } /// Check storage range proof on pre-created proving backend. @@ -1249,47 +1059,35 @@ mod execution { }; let start_at_ref = start_at.as_ref().map(AsRef::as_ref); let mut switch_child_key = None; - let mut first = start_at.is_some(); - let completed = proving_backend - .apply_to_key_values_while( - child_info.as_ref(), - None, - start_at_ref, - |key, value| { - if first && - start_at_ref - .as_ref() - .map(|start| &key.as_slice() > start) - .unwrap_or(true) - { - first = false; - } - if !first { - values.push((key.to_vec(), value.to_vec())); - } - if first { - true - } else if depth < MAX_NESTED_TRIE_DEPTH && - sp_core::storage::well_known_keys::is_child_storage_key( - key.as_slice(), - ) { - if child_roots.contains(value.as_slice()) { - // Do not add two chid trie with same root. - true - } else { - child_roots.insert(value.clone()); - switch_child_key = Some((key, value)); - false - } - } else { - true - } - }, - true, - ) + let mut iter = proving_backend + .pairs(IterArgs { + child_info, + start_at: start_at_ref, + start_at_exclusive: true, + stop_on_incomplete_database: true, + ..IterArgs::default() + }) .map_err(|e| Box::new(e) as Box)?; + while let Some(item) = iter.next() { + let (key, value) = item.map_err(|e| Box::new(e) as Box)?; + values.push((key.to_vec(), value.to_vec())); + + if depth < MAX_NESTED_TRIE_DEPTH && + sp_core::storage::well_known_keys::is_child_storage_key(key.as_slice()) + { + // Do not add two chid trie with same root. + if !child_roots.contains(value.as_slice()) { + child_roots.insert(value.clone()); + switch_child_key = Some((key, value)); + break + } + } + } + + let completed = iter.was_complete(); + if switch_child_key.is_none() { if !completed { break depth @@ -1317,11 +1115,14 @@ mod tests { use sp_core::{ map, storage::{ChildInfo, StateVersion}, - testing::TaskExecutor, - traits::{CodeExecutor, Externalities, RuntimeCode}, + traits::{CallContext, CodeExecutor, Externalities, RuntimeCode}, + H256, }; use sp_runtime::traits::BlakeTwo256; - use sp_trie::trie_types::{TrieDBMutBuilderV0, TrieDBMutBuilderV1}; + use sp_trie::{ + trie_types::{TrieDBMutBuilderV0, TrieDBMutBuilderV1}, + KeySpacedDBMut, PrefixedMemoryDB, + }; use std::collections::{BTreeMap, HashMap}; #[derive(Clone)] @@ -1341,6 +1142,7 @@ mod tests { _method: &str, _data: &[u8], use_native: bool, + _: CallContext, ) -> (CallResult, bool) { let using_native = use_native && self.native_available; match (using_native, self.native_succeeds, self.fallback_succeeds) { @@ -1374,6 +1176,7 @@ mod tests { let backend = trie_backend::tests::test_trie(state_version, None, None); let mut overlayed_changes = Default::default(); let wasm_code = RuntimeCode::empty(); + let mut execution_extensions = &mut Default::default(); let mut state_machine = StateMachine::new( &backend, @@ -1385,12 +1188,12 @@ mod tests { }, "test", &[], - Default::default(), + &mut execution_extensions, &wasm_code, - TaskExecutor::new(), + CallContext::Offchain, ); - assert_eq!(state_machine.execute(ExecutionStrategy::NativeWhenPossible).unwrap(), vec![66]); + assert_eq!(state_machine.execute().unwrap(), vec![66]); } #[test] @@ -1402,6 +1205,7 @@ mod tests { let backend = trie_backend::tests::test_trie(state_version, None, None); let mut overlayed_changes = Default::default(); let wasm_code = RuntimeCode::empty(); + let mut execution_extensions = &mut Default::default(); let mut state_machine = StateMachine::new( &backend, @@ -1413,47 +1217,12 @@ mod tests { }, "test", &[], - Default::default(), + &mut execution_extensions, &wasm_code, - TaskExecutor::new(), + CallContext::Offchain, ); - assert_eq!(state_machine.execute(ExecutionStrategy::NativeElseWasm).unwrap(), vec![66]); - } - - #[test] - fn dual_execution_strategy_detects_consensus_failure() { - dual_execution_strategy_detects_consensus_failure_inner(StateVersion::V0); - dual_execution_strategy_detects_consensus_failure_inner(StateVersion::V1); - } - fn dual_execution_strategy_detects_consensus_failure_inner(state_version: StateVersion) { - let mut consensus_failed = false; - let backend = trie_backend::tests::test_trie(state_version, None, None); - let mut overlayed_changes = Default::default(); - let wasm_code = RuntimeCode::empty(); - - let mut state_machine = StateMachine::new( - &backend, - &mut overlayed_changes, - &DummyCodeExecutor { - native_available: true, - native_succeeds: true, - fallback_succeeds: false, - }, - "test", - &[], - Default::default(), - &wasm_code, - TaskExecutor::new(), - ); - - assert!(state_machine - .execute_using_consensus_failure_handler(ExecutionManager::Both(|we, _ne| { - consensus_failed = true; - we - }),) - .is_err()); - assert!(consensus_failed); + assert_eq!(state_machine.execute().unwrap(), vec![66]); } #[test] @@ -1475,7 +1244,6 @@ mod tests { &mut remote_backend, &mut Default::default(), &executor, - TaskExecutor::new(), "test", &[], &RuntimeCode::empty(), @@ -1483,12 +1251,11 @@ mod tests { .unwrap(); // check proof locally - let local_result = execution_proof_check::( + let local_result = execution_proof_check::( remote_root, remote_proof, &mut Default::default(), &executor, - TaskExecutor::new(), "test", &[], &RuntimeCode::empty(), @@ -1943,12 +1710,14 @@ mod tests { // Always contains at least some nodes. assert_eq!(proof.to_memory_db::().drain().len(), 3); assert_eq!(count, 1); + assert_eq!(proof.encoded_size(), 443); let remote_backend = trie_backend::tests::test_trie(state_version, None, None); let (proof, count) = prove_range_read_with_size(remote_backend, None, None, 800, Some(&[])).unwrap(); assert_eq!(proof.to_memory_db::().drain().len(), 9); assert_eq!(count, 85); + assert_eq!(proof.encoded_size(), 857); let (results, completed) = read_range_proof_check::( remote_root, proof.clone(), @@ -1972,6 +1741,8 @@ mod tests { prove_range_read_with_size(remote_backend, None, None, 50000, Some(&[])).unwrap(); assert_eq!(proof.to_memory_db::().drain().len(), 11); assert_eq!(count, 132); + assert_eq!(proof.encoded_size(), 990); + let (results, completed) = read_range_proof_check::(remote_root, proof, None, None, None, None) .unwrap(); @@ -1979,6 +1750,32 @@ mod tests { assert_eq!(completed, true); } + #[test] + fn prove_read_with_size_limit_proof_size() { + let mut root = H256::default(); + let mut mdb = PrefixedMemoryDB::::default(); + { + let mut mdb = KeySpacedDBMut::new(&mut mdb, b""); + let mut trie = TrieDBMutBuilderV1::new(&mut mdb, &mut root).build(); + trie.insert(b"value1", &[123; 1]).unwrap(); + trie.insert(b"value2", &[123; 10]).unwrap(); + trie.insert(b"value3", &[123; 100]).unwrap(); + trie.insert(b"value4", &[123; 1000]).unwrap(); + } + + let remote_backend: TrieBackend, BlakeTwo256> = + TrieBackendBuilder::new(mdb, root) + .with_optional_cache(None) + .with_optional_recorder(None) + .build(); + + let (proof, count) = + prove_range_read_with_size(remote_backend, None, None, 1000, None).unwrap(); + + assert_eq!(proof.encoded_size(), 1267); + assert_eq!(count, 3); + } + #[test] fn inner_state_versioning_switch_proofs() { let mut state_version = StateVersion::V0; diff --git a/primitives/state-machine/src/overlayed_changes/changeset.rs b/primitives/state-machine/src/overlayed_changes/changeset.rs index e5dad7157c73..8f2d02fd6840 100644 --- a/primitives/state-machine/src/overlayed_changes/changeset.rs +++ b/primitives/state-machine/src/overlayed_changes/changeset.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/state-machine/src/overlayed_changes/mod.rs b/primitives/state-machine/src/overlayed_changes/mod.rs index 9eb26d52f79f..b32df635b177 100644 --- a/primitives/state-machine/src/overlayed_changes/mod.rs +++ b/primitives/state-machine/src/overlayed_changes/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/state-machine/src/overlayed_changes/offchain.rs b/primitives/state-machine/src/overlayed_changes/offchain.rs index a9643c265e75..66e7ab5864c0 100644 --- a/primitives/state-machine/src/overlayed_changes/offchain.rs +++ b/primitives/state-machine/src/overlayed_changes/offchain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/state-machine/src/read_only.rs b/primitives/state-machine/src/read_only.rs index e58fb760f4d7..2056bf986635 100644 --- a/primitives/state-machine/src/read_only.rs +++ b/primitives/state-machine/src/read_only.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/state-machine/src/stats.rs b/primitives/state-machine/src/stats.rs index 863ea5cfed7e..7c5510961a37 100644 --- a/primitives/state-machine/src/stats.rs +++ b/primitives/state-machine/src/stats.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/state-machine/src/testing.rs b/primitives/state-machine/src/testing.rs index e94d34b5560c..78e956952f72 100644 --- a/primitives/state-machine/src/testing.rs +++ b/primitives/state-machine/src/testing.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,15 +27,13 @@ use crate::{ StorageTransactionCache, StorageValue, TrieBackendBuilder, }; -use hash_db::Hasher; +use hash_db::{HashDB, Hasher}; use sp_core::{ offchain::testing::TestPersistentOffchainDB, storage::{ well_known_keys::{is_child_storage_key, CODE}, StateVersion, Storage, }, - testing::TaskExecutor, - traits::TaskExecutorExt, }; use sp_externalities::{Extension, ExtensionStore, Extensions}; use sp_trie::StorageProof; @@ -105,9 +103,6 @@ where storage.top.insert(CODE.to_vec(), code.to_vec()); - let mut extensions = Extensions::default(); - extensions.register(TaskExecutorExt::new(TaskExecutor::new())); - let offchain_db = TestPersistentOffchainDB::new(); let backend = (storage, state_version).into(); @@ -115,7 +110,7 @@ where TestExternalities { overlay: OverlayedChanges::default(), offchain_db, - extensions, + extensions: Default::default(), backend, storage_transaction_cache: Default::default(), state_version, @@ -137,6 +132,17 @@ where self.offchain_db.clone() } + /// Batch insert key/values into backend + pub fn batch_insert(&mut self, kvs: I) + where + I: IntoIterator, + { + self.backend.insert( + Some((None, kvs.into_iter().map(|(k, v)| (k, Some(v))).collect())), + self.state_version, + ); + } + /// Insert key/value into backend pub fn insert(&mut self, k: StorageKey, v: StorageValue) { self.backend.insert(vec![(None, vec![(k, Some(v))])], self.state_version); @@ -154,6 +160,41 @@ where self.extensions.register(ext); } + /// Sets raw storage key/values and a root. + /// + /// This can be used as a fast way to restore the storage state from a backup because the trie + /// does not need to be computed. + pub fn from_raw_snapshot( + &mut self, + raw_storage: Vec<(H::Out, (Vec, i32))>, + storage_root: H::Out, + ) { + for (k, (v, ref_count)) in raw_storage { + // Each time .emplace is called the internal MemoryDb ref count increments. + // Repeatedly call emplace to initialise the ref count to the correct value. + for _ in 0..ref_count { + self.backend.backend_storage_mut().emplace(k, hash_db::EMPTY_PREFIX, v.clone()); + } + } + self.backend.set_root(storage_root); + } + + /// Drains the underlying raw storage key/values and returns the root hash. + /// + /// Useful for backing up the storage in a format that can be quickly re-loaded. + /// + /// Note: This DB will be inoperable after this call. + pub fn into_raw_snapshot(mut self) -> (Vec<(H::Out, (Vec, i32))>, H::Out) { + let raw_key_values = self + .backend + .backend_storage_mut() + .drain() + .into_iter() + .collect::, i32))>>(); + + (raw_key_values, *self.backend.root()) + } + /// Return a new backend with all pending changes. /// /// In contrast to [`commit_all`](Self::commit_all) this will not panic if there are open @@ -241,7 +282,12 @@ where H::Out: Ord + codec::Codec, { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "overlay: {:?}\nbackend: {:?}", self.overlay, self.backend.pairs()) + let pairs: Vec<_> = self + .backend + .pairs(Default::default()) + .expect("creating an iterator over all of the pairs doesn't fail in tests") + .collect(); + write!(f, "overlay: {:?}\nbackend: {:?}", self.overlay, pairs) } } @@ -345,12 +391,83 @@ mod tests { ext.set_storage(b"doe".to_vec(), b"reindeer".to_vec()); ext.set_storage(b"dog".to_vec(), b"puppy".to_vec()); ext.set_storage(b"dogglesworth".to_vec(), b"cat".to_vec()); - let root = array_bytes::hex_n_into_unchecked::( + let root = array_bytes::hex_n_into_unchecked::<_, H256, 32>( "ed4d8c799d996add422395a6abd7545491d40bd838d738afafa1b8a4de625489", ); assert_eq!(H256::from_slice(ext.storage_root(Default::default()).as_slice()), root); } + #[test] + fn raw_storage_drain_and_restore() { + // Create a TestExternalities with some data in it. + let mut original_ext = + TestExternalities::::from((Default::default(), Default::default())); + original_ext.insert(b"doe".to_vec(), b"reindeer".to_vec()); + original_ext.insert(b"dog".to_vec(), b"puppy".to_vec()); + original_ext.insert(b"dogglesworth".to_vec(), b"cat".to_vec()); + let child_info = ChildInfo::new_default(&b"test_child"[..]); + original_ext.insert_child(child_info.clone(), b"cattytown".to_vec(), b"is_dark".to_vec()); + original_ext.insert_child(child_info.clone(), b"doggytown".to_vec(), b"is_sunny".to_vec()); + + // Call emplace on one of the keys to increment the MemoryDb refcount, so we can check + // that it is intact in the recovered_ext. + let keys = original_ext.backend.backend_storage_mut().keys(); + let expected_ref_count = 5; + let ref_count_key = keys.into_iter().next().unwrap().0; + for _ in 0..expected_ref_count - 1 { + original_ext.backend.backend_storage_mut().emplace( + ref_count_key, + hash_db::EMPTY_PREFIX, + // We can use anything for the 'value' because it does not affect behavior when + // emplacing an existing key. + (&[0u8; 32]).to_vec(), + ); + } + let refcount = original_ext + .backend + .backend_storage() + .raw(&ref_count_key, hash_db::EMPTY_PREFIX) + .unwrap() + .1; + assert_eq!(refcount, expected_ref_count); + + // Drain the raw storage and root. + let root = *original_ext.backend.root(); + let (raw_storage, storage_root) = original_ext.into_raw_snapshot(); + + // Load the raw storage and root into a new TestExternalities. + let mut recovered_ext = + TestExternalities::::from((Default::default(), Default::default())); + recovered_ext.from_raw_snapshot(raw_storage, storage_root); + + // Check the storage root is the same as the original + assert_eq!(root, *recovered_ext.backend.root()); + + // Check the original storage key/values were recovered correctly + assert_eq!(recovered_ext.backend.storage(b"doe").unwrap(), Some(b"reindeer".to_vec())); + assert_eq!(recovered_ext.backend.storage(b"dog").unwrap(), Some(b"puppy".to_vec())); + assert_eq!(recovered_ext.backend.storage(b"dogglesworth").unwrap(), Some(b"cat".to_vec())); + + // Check the original child storage key/values were recovered correctly + assert_eq!( + recovered_ext.backend.child_storage(&child_info, b"cattytown").unwrap(), + Some(b"is_dark".to_vec()) + ); + assert_eq!( + recovered_ext.backend.child_storage(&child_info, b"doggytown").unwrap(), + Some(b"is_sunny".to_vec()) + ); + + // Check the refcount of the key with > 1 refcount is correct. + let refcount = recovered_ext + .backend + .backend_storage() + .raw(&ref_count_key, hash_db::EMPTY_PREFIX) + .unwrap() + .1; + assert_eq!(refcount, expected_ref_count); + } + #[test] fn set_and_retrieve_code() { let mut ext = TestExternalities::::default(); diff --git a/primitives/state-machine/src/trie_backend.rs b/primitives/state-machine/src/trie_backend.rs index ff5cce24c512..b7940fa8c39d 100644 --- a/primitives/state-machine/src/trie_backend.rs +++ b/primitives/state-machine/src/trie_backend.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,63 +20,146 @@ #[cfg(feature = "std")] use crate::backend::AsTrieBackend; use crate::{ - trie_backend_essence::{TrieBackendEssence, TrieBackendStorage}, + backend::{IterArgs, StorageIterator}, + trie_backend_essence::{RawIter, TrieBackendEssence, TrieBackendStorage}, Backend, StorageKey, StorageValue, }; + use codec::Codec; #[cfg(feature = "std")] use hash_db::HashDB; use hash_db::Hasher; use sp_core::storage::{ChildInfo, StateVersion}; -use sp_std::vec::Vec; -#[cfg(feature = "std")] -use sp_trie::{cache::LocalTrieCache, recorder::Recorder}; #[cfg(feature = "std")] -use sp_trie::{MemoryDB, StorageProof}; - -/// Dummy type to be used in `no_std`. -/// -/// This is required to have the type available for [`TrieBackendBuilder`] and [`TrieBackend`]. +use sp_trie::{ + cache::{LocalTrieCache, TrieCache}, + recorder::Recorder, + MemoryDB, StorageProof, +}; +#[cfg(not(feature = "std"))] +use sp_trie::{Error, NodeCodec}; +use trie_db::TrieCache as TrieCacheT; #[cfg(not(feature = "std"))] -pub struct LocalTrieCache(sp_std::marker::PhantomData); +use trie_db::{node::NodeOwned, CachedValue}; -/// Special trait to support taking the [`LocalTrieCache`] by value or by reference. -/// -/// This trait is internal use only and to emphasize this, the trait is sealed. -pub trait AsLocalTrieCache: sealed::Sealed { - /// Returns `self` as [`LocalTrieCache`]. - #[cfg(feature = "std")] - fn as_local_trie_cache(&self) -> &LocalTrieCache; +/// A provider of trie caches that are compatible with [`trie_db::TrieDB`]. +pub trait TrieCacheProvider { + /// Cache type that implements [`trie_db::TrieCache`]. + type Cache<'a>: TrieCacheT> + 'a + where + Self: 'a; + + /// Return a [`trie_db::TrieDB`] compatible cache. + /// + /// The `storage_root` parameter should be the storage root of the used trie. + fn as_trie_db_cache(&self, storage_root: H::Out) -> Self::Cache<'_>; + + /// Returns a cache that can be used with a [`trie_db::TrieDBMut`]. + /// + /// When finished with the operation on the trie, it is required to call [`Self::merge`] to + /// merge the cached items for the correct `storage_root`. + fn as_trie_db_mut_cache(&self) -> Self::Cache<'_>; + + /// Merge the cached data in `other` into the provider using the given `new_root`. + /// + /// This must be used for the cache returned by [`Self::as_trie_db_mut_cache`] as otherwise the + /// cached data is just thrown away. + fn merge<'a>(&'a self, other: Self::Cache<'a>, new_root: H::Out); } -impl AsLocalTrieCache for LocalTrieCache { - #[cfg(feature = "std")] - fn as_local_trie_cache(&self) -> &LocalTrieCache { - self +#[cfg(feature = "std")] +impl TrieCacheProvider for LocalTrieCache { + type Cache<'a> = TrieCache<'a, H> where H: 'a; + + fn as_trie_db_cache(&self, storage_root: H::Out) -> Self::Cache<'_> { + self.as_trie_db_cache(storage_root) + } + + fn as_trie_db_mut_cache(&self) -> Self::Cache<'_> { + self.as_trie_db_mut_cache() + } + + fn merge<'a>(&'a self, other: Self::Cache<'a>, new_root: H::Out) { + other.merge_into(self, new_root) } } #[cfg(feature = "std")] -impl AsLocalTrieCache for &LocalTrieCache { - fn as_local_trie_cache(&self) -> &LocalTrieCache { - self +impl TrieCacheProvider for &LocalTrieCache { + type Cache<'a> = TrieCache<'a, H> where Self: 'a; + + fn as_trie_db_cache(&self, storage_root: H::Out) -> Self::Cache<'_> { + (*self).as_trie_db_cache(storage_root) + } + + fn as_trie_db_mut_cache(&self) -> Self::Cache<'_> { + (*self).as_trie_db_mut_cache() + } + + fn merge<'a>(&'a self, other: Self::Cache<'a>, new_root: H::Out) { + other.merge_into(self, new_root) } } -/// Special module that contains the `Sealed` trait. -mod sealed { - use super::*; +/// Cache provider that allows construction of a [`TrieBackend`] and satisfies the requirements, but +/// can never be instantiated. +#[cfg(not(feature = "std"))] +pub struct UnimplementedCacheProvider { + // Not strictly necessary, but the H bound allows to use this as a drop-in + // replacement for the `LocalTrieCache` in no-std contexts. + _phantom: core::marker::PhantomData, + // Statically prevents construction. + _infallible: core::convert::Infallible, +} + +#[cfg(not(feature = "std"))] +impl trie_db::TrieCache> for UnimplementedCacheProvider { + fn lookup_value_for_key(&mut self, _key: &[u8]) -> Option<&CachedValue> { + unimplemented!() + } + + fn cache_value_for_key(&mut self, _key: &[u8], _value: CachedValue) { + unimplemented!() + } - /// A special trait which prevents externals to implement the [`AsLocalTrieCache`] outside - /// of this crate. - pub trait Sealed {} + fn get_or_insert_node( + &mut self, + _hash: H::Out, + _fetch_node: &mut dyn FnMut() -> trie_db::Result, H::Out, Error>, + ) -> trie_db::Result<&NodeOwned, H::Out, Error> { + unimplemented!() + } + + fn get_node(&mut self, _hash: &H::Out) -> Option<&NodeOwned> { + unimplemented!() + } +} + +#[cfg(not(feature = "std"))] +impl TrieCacheProvider for UnimplementedCacheProvider { + type Cache<'a> = UnimplementedCacheProvider where H: 'a; - impl Sealed for LocalTrieCache {} - impl Sealed for &LocalTrieCache {} + fn as_trie_db_cache(&self, _storage_root: ::Out) -> Self::Cache<'_> { + unimplemented!() + } + + fn as_trie_db_mut_cache(&self) -> Self::Cache<'_> { + unimplemented!() + } + + fn merge<'a>(&'a self, _other: Self::Cache<'a>, _new_root: ::Out) { + unimplemented!() + } } +#[cfg(feature = "std")] +type DefaultCache = LocalTrieCache; + +#[cfg(not(feature = "std"))] +type DefaultCache = UnimplementedCacheProvider; + /// Builder for creating a [`TrieBackend`]. -pub struct TrieBackendBuilder, H: Hasher, C = LocalTrieCache> { +pub struct TrieBackendBuilder, H: Hasher, C = DefaultCache> { storage: S, root: H::Out, #[cfg(feature = "std")] @@ -84,7 +167,7 @@ pub struct TrieBackendBuilder, H: Hasher, C = LocalTrie cache: Option, } -impl TrieBackendBuilder> +impl TrieBackendBuilder> where S: TrieBackendStorage, H: Hasher, @@ -106,6 +189,16 @@ where S: TrieBackendStorage, H: Hasher, { + /// Create a new builder instance. + pub fn new_with_cache(storage: S, root: H::Out, cache: C) -> Self { + Self { + storage, + root, + #[cfg(feature = "std")] + recorder: None, + cache: Some(cache), + } + } /// Wrap the given [`TrieBackend`]. /// /// This can be used for example if all accesses to the trie should @@ -119,10 +212,7 @@ where root: *other.essence.root(), #[cfg(feature = "std")] recorder: None, - #[cfg(feature = "std")] cache: other.essence.trie_node_cache.as_ref(), - #[cfg(not(feature = "std"))] - cache: None, } } @@ -139,23 +229,23 @@ where } /// Use the given optional `cache` for the to be configured [`TrieBackend`]. - #[cfg(feature = "std")] pub fn with_optional_cache(self, cache: Option) -> TrieBackendBuilder { TrieBackendBuilder { cache, root: self.root, storage: self.storage, + #[cfg(feature = "std")] recorder: self.recorder, } } /// Use the given `cache` for the to be configured [`TrieBackend`]. - #[cfg(feature = "std")] pub fn with_cache(self, cache: LC) -> TrieBackendBuilder { TrieBackendBuilder { cache: Some(cache), root: self.root, storage: self.storage, + #[cfg(feature = "std")] recorder: self.recorder, } } @@ -170,37 +260,90 @@ where self.cache, self.recorder, ), + next_storage_key_cache: Default::default(), } } /// Build the configured [`TrieBackend`]. #[cfg(not(feature = "std"))] pub fn build(self) -> TrieBackend { - let _ = self.cache; + TrieBackend { + essence: TrieBackendEssence::new_with_cache(self.storage, self.root, self.cache), + next_storage_key_cache: Default::default(), + } + } +} + +/// A cached iterator. +struct CachedIter +where + H: Hasher, +{ + last_key: sp_std::vec::Vec, + iter: RawIter, +} - TrieBackend { essence: TrieBackendEssence::new(self.storage, self.root) } +impl Default for CachedIter +where + H: Hasher, +{ + fn default() -> Self { + Self { last_key: Default::default(), iter: Default::default() } } } +#[cfg(feature = "std")] +type CacheCell = parking_lot::Mutex; + +#[cfg(not(feature = "std"))] +type CacheCell = core::cell::RefCell; + +#[cfg(feature = "std")] +fn access_cache(cell: &CacheCell, callback: impl FnOnce(&mut T) -> R) -> R { + callback(&mut *cell.lock()) +} + +#[cfg(not(feature = "std"))] +fn access_cache(cell: &CacheCell, callback: impl FnOnce(&mut T) -> R) -> R { + callback(&mut *cell.borrow_mut()) +} + /// Patricia trie-based backend. Transaction type is an overlay of changes to commit. -pub struct TrieBackend, H: Hasher, C = LocalTrieCache> { +pub struct TrieBackend, H: Hasher, C = DefaultCache> { pub(crate) essence: TrieBackendEssence, + next_storage_key_cache: CacheCell>>, } -impl, H: Hasher, C: AsLocalTrieCache + Send + Sync> TrieBackend +impl, H: Hasher, C: TrieCacheProvider + Send + Sync> + TrieBackend where H::Out: Codec, { + #[cfg(test)] + pub(crate) fn from_essence(essence: TrieBackendEssence) -> Self { + Self { essence, next_storage_key_cache: Default::default() } + } + /// Get backend essence reference. pub fn essence(&self) -> &TrieBackendEssence { &self.essence } + /// Get backend storage reference. + pub fn backend_storage_mut(&mut self) -> &mut S { + self.essence.backend_storage_mut() + } + /// Get backend storage reference. pub fn backend_storage(&self) -> &S { self.essence.backend_storage() } + /// Set trie root. + pub fn set_root(&mut self, root: H::Out) { + self.essence.set_root(root) + } + /// Get trie root. pub fn root(&self) -> &H::Out { self.essence.root() @@ -220,7 +363,7 @@ where } } -impl, H: Hasher, C: AsLocalTrieCache> sp_std::fmt::Debug +impl, H: Hasher, C: TrieCacheProvider> sp_std::fmt::Debug for TrieBackend { fn fmt(&self, f: &mut sp_std::fmt::Formatter<'_>) -> sp_std::fmt::Result { @@ -228,7 +371,7 @@ impl, H: Hasher, C: AsLocalTrieCache> sp_std::fmt::D } } -impl, H: Hasher, C: AsLocalTrieCache + Send + Sync> Backend +impl, H: Hasher, C: TrieCacheProvider + Send + Sync> Backend for TrieBackend where H::Out: Ord + Codec, @@ -236,6 +379,7 @@ where type Error = crate::DefaultError; type Transaction = S::Overlay; type TrieBackendStorage = S; + type RawIter = crate::trie_backend_essence::RawIter; fn storage_hash(&self, key: &[u8]) -> Result, Self::Error> { self.essence.storage_hash(key) @@ -262,7 +406,40 @@ where } fn next_storage_key(&self, key: &[u8]) -> Result, Self::Error> { - self.essence.next_storage_key(key) + let (is_cached, mut cache) = access_cache(&self.next_storage_key_cache, Option::take) + .map(|cache| (cache.last_key == key, cache)) + .unwrap_or_default(); + + if !is_cached { + cache.iter = self.raw_iter(IterArgs { + start_at: Some(key), + start_at_exclusive: true, + ..IterArgs::default() + })? + }; + + let next_key = match cache.iter.next_key(self) { + None => return Ok(None), + Some(Err(error)) => return Err(error), + Some(Ok(next_key)) => next_key, + }; + + cache.last_key.clear(); + cache.last_key.extend_from_slice(&next_key); + access_cache(&self.next_storage_key_cache, |cache_cell| cache_cell.replace(cache)); + + #[cfg(debug_assertions)] + debug_assert_eq!( + self.essence + .next_storage_key_slow(key) + .expect( + "fetching the next key through iterator didn't fail so this shouldn't either" + ) + .as_ref(), + Some(&next_key) + ); + + Ok(Some(next_key)) } fn next_child_storage_key( @@ -273,51 +450,8 @@ where self.essence.next_child_storage_key(child_info, key) } - fn for_keys_with_prefix(&self, prefix: &[u8], f: F) { - self.essence.for_keys_with_prefix(prefix, f) - } - - fn for_key_values_with_prefix(&self, prefix: &[u8], f: F) { - self.essence.for_key_values_with_prefix(prefix, f) - } - - fn apply_to_key_values_while, Vec) -> bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - start_at: Option<&[u8]>, - f: F, - allow_missing: bool, - ) -> Result { - self.essence - .apply_to_key_values_while(child_info, prefix, start_at, f, allow_missing) - } - - fn apply_to_keys_while bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - start_at: Option<&[u8]>, - f: F, - ) { - self.essence.apply_to_keys_while(child_info, prefix, start_at, f) - } - - fn for_child_keys_with_prefix( - &self, - child_info: &ChildInfo, - prefix: &[u8], - f: F, - ) { - self.essence.for_child_keys_with_prefix(child_info, prefix, f) - } - - fn pairs(&self) -> Vec<(StorageKey, StorageValue)> { - self.essence.pairs() - } - - fn keys(&self, prefix: &[u8]) -> Vec { - self.essence.keys(prefix) + fn raw_iter(&self, args: IterArgs) -> Result { + self.essence.raw_iter(args) } fn storage_root<'a>( @@ -397,7 +531,7 @@ pub mod tests { trie_types::{TrieDBBuilder, TrieDBMutBuilderV0, TrieDBMutBuilderV1}, KeySpacedDBMut, PrefixedKey, PrefixedMemoryDB, Trie, TrieCache, TrieMut, }; - use std::{collections::HashSet, iter}; + use std::iter; use trie_db::NodeCodec; const CHILD_KEY_1: &[u8] = b"sub1"; @@ -499,6 +633,29 @@ pub mod tests { (mdb, root) } + pub(crate) fn test_db_with_hex_keys( + state_version: StateVersion, + keys: &[&str], + ) -> (PrefixedMemoryDB, H256) { + let mut root = H256::default(); + let mut mdb = PrefixedMemoryDB::::default(); + match state_version { + StateVersion::V0 => { + let mut trie = TrieDBMutBuilderV0::new(&mut mdb, &mut root).build(); + for (index, key) in keys.iter().enumerate() { + trie.insert(&array_bytes::hex2bytes(key).unwrap(), &[index as u8]).unwrap(); + } + }, + StateVersion::V1 => { + let mut trie = TrieDBMutBuilderV1::new(&mut mdb, &mut root).build(); + for (index, key) in keys.iter().enumerate() { + trie.insert(&array_bytes::hex2bytes(key).unwrap(), &[index as u8]).unwrap(); + } + }, + }; + (mdb, root) + } + pub(crate) fn test_trie( hashed_value: StateVersion, cache: Option, @@ -512,6 +669,20 @@ pub mod tests { .build() } + pub(crate) fn test_trie_with_hex_keys( + hashed_value: StateVersion, + cache: Option, + recorder: Option, + keys: &[&str], + ) -> TrieBackend, BlakeTwo256> { + let (mdb, root) = test_db_with_hex_keys(hashed_value, keys); + + TrieBackendBuilder::new(mdb, root) + .with_optional_cache(cache) + .with_optional_recorder(recorder) + .build() + } + parameterized_test!(read_from_storage_returns_some, read_from_storage_returns_some_inner); fn read_from_storage_returns_some_inner( state_version: StateVersion, @@ -579,7 +750,11 @@ pub mod tests { cache: Option, recorder: Option, ) { - assert!(!test_trie(state_version, cache, recorder).pairs().is_empty()); + assert!(!test_trie(state_version, cache, recorder) + .pairs(Default::default()) + .unwrap() + .next() + .is_none()); } #[test] @@ -589,8 +764,176 @@ pub mod tests { Default::default(), ) .build() - .pairs() - .is_empty()); + .pairs(Default::default()) + .unwrap() + .next() + .is_none()); + } + + parameterized_test!(storage_iteration_works, storage_iteration_works_inner); + fn storage_iteration_works_inner( + state_version: StateVersion, + cache: Option, + recorder: Option, + ) { + let trie = test_trie(state_version, cache, recorder); + + // Fetch everything. + assert_eq!( + trie.keys(Default::default()) + .unwrap() + .map(|result| result.unwrap()) + .take(5) + .collect::>(), + vec![ + b":child_storage:default:sub1".to_vec(), + b":code".to_vec(), + b"key".to_vec(), + b"value1".to_vec(), + b"value2".to_vec(), + ] + ); + + // Fetch starting at a given key (full key). + assert_eq!( + trie.keys(IterArgs { start_at: Some(b"key"), ..IterArgs::default() }) + .unwrap() + .map(|result| result.unwrap()) + .take(3) + .collect::>(), + vec![b"key".to_vec(), b"value1".to_vec(), b"value2".to_vec(),] + ); + + // Fetch starting at a given key (partial key). + assert_eq!( + trie.keys(IterArgs { start_at: Some(b"ke"), ..IterArgs::default() }) + .unwrap() + .map(|result| result.unwrap()) + .take(3) + .collect::>(), + vec![b"key".to_vec(), b"value1".to_vec(), b"value2".to_vec(),] + ); + + // Fetch starting at a given key (empty key). + assert_eq!( + trie.keys(IterArgs { start_at: Some(b""), ..IterArgs::default() }) + .unwrap() + .map(|result| result.unwrap()) + .take(5) + .collect::>(), + vec![ + b":child_storage:default:sub1".to_vec(), + b":code".to_vec(), + b"key".to_vec(), + b"value1".to_vec(), + b"value2".to_vec(), + ] + ); + + // Fetch starting at a given key and with prefix which doesn't match that key. + // (Start *before* the prefix.) + assert_eq!( + trie.keys(IterArgs { + prefix: Some(b"value"), + start_at: Some(b"key"), + ..IterArgs::default() + }) + .unwrap() + .map(|result| result.unwrap()) + .collect::>(), + vec![b"value1".to_vec(), b"value2".to_vec(),] + ); + + // Fetch starting at a given key and with prefix which doesn't match that key. + // (Start *after* the prefix.) + assert!(trie + .keys(IterArgs { + prefix: Some(b"value"), + start_at: Some(b"vblue"), + ..IterArgs::default() + }) + .unwrap() + .map(|result| result.unwrap()) + .next() + .is_none()); + + // Fetch starting at a given key and with prefix which does match that key. + assert_eq!( + trie.keys(IterArgs { + prefix: Some(b"value"), + start_at: Some(b"value"), + ..IterArgs::default() + }) + .unwrap() + .map(|result| result.unwrap()) + .collect::>(), + vec![b"value1".to_vec(), b"value2".to_vec(),] + ); + } + + // This test reproduces an actual real-world issue: https://github.com/polkadot-js/apps/issues/9103 + parameterized_test!( + storage_iter_does_not_return_out_of_prefix_keys, + storage_iter_does_not_return_out_of_prefix_keys_inner + ); + fn storage_iter_does_not_return_out_of_prefix_keys_inner( + state_version: StateVersion, + cache: Option, + recorder: Option, + ) { + let trie = test_trie_with_hex_keys(state_version, cache, recorder, &[ + "6cf4040bbce30824850f1a4823d8c65faeefaa25a5bae16a431719647c1d99da", + "6cf4040bbce30824850f1a4823d8c65ff536928ca5ba50039bc2766a48ddbbab", + "70f943199f1a2dde80afdaf3f447db834e7b9012096b41c4eb3aaf947f6ea429", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d007fc7effcb0c044a0c41fd8a77eb55d2133058a86d1f4d6f8e45612cd271eefd77f91caeaacfe011b8f41540e0a793b0fd51b245dae19382b45386570f2b545fab75e3277910f7324b55f47c29f9965e8298371404e50ac", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d0179c23cd593c770fde9fc7aa8f84b3e401e654b8986c67728844da0080ec9ee222b41a85708a471a511548302870b53f40813d8354b6d2969e1b7ca9e083ecf96f9647e004ecb41c7f26f0110f778bdb3d9da31bef323d9", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d024de296f88310247001277477f4ace4d0aa5685ea2928d518a807956e4806a656520d6520b8ac259f684aa0d91961d76f697716f04e6c997338d03560ab7d703829fe7b9d0e6d7eff8d8412fc428364c2f474a67b36586d", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d13dc5d83f2361c14d05933eb3182a92ac14665718569703baf1da25c7d571843b6489f03d8549c87bfa5709836ba729443c319659e83ad5ee133e6f11af51d883e56216e9e1bbb1e2920c7c6120cbb55cd469b1f95b61601", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d1786d20bbb4b91eb1f5765432d750bd0111a0807c8d04f05110ffaf73f4fa7b360422c13bc97efc3a2324d9fa8f954b424c0bcfce7236a2e8107dd31c2042a9860a964f8472fda49749dec3f146e81470b55aa0f3930d854", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d18c246484ec5335a40903e7cd05771be7c0b8459333f1ae2925c3669fc3e5accd0f38c4711a15544bfa5709836ba729443c319659e83ad5ee133e6f11af51d883e56216e9e1bbb1e2920c7c6120cbb55cd469b1f95b61601", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d1aca749033252ce75245528397430d14cb8e8c09248d81ee5de00b6ae93ee880b6d19a595e6dc106bfa5709836ba729443c319659e83ad5ee133e6f11af51d883e56216e9e1bbb1e2920c7c6120cbb55cd469b1f95b61601", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d1d6bceb91bc07973e7b3296f83af9f1c4300ce9198cc3b44c54dafddb58f4a43aee44a9bef1a2e9dbfa5709836ba729443c319659e83ad5ee133e6f11af51d883e56216e9e1bbb1e2920c7c6120cbb55cd469b1f95b61601", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d203383772f45721232139e1a8863b0f2f8d480bdc15bcc1f2033cf467e137059558da743838f6b58bfa5709836ba729443c319659e83ad5ee133e6f11af51d883e56216e9e1bbb1e2920c7c6120cbb55cd469b1f95b61601", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d2197cc5c3eb3a6a67538e0dc3eaaf8c820d71310d377499c4a5d276381789e0a234475e69cddf709d207458083d6146d3a36fce7f1fe05b232702bf154096e5e3a8c378bdc237d7a27909acd663563917f0f70bb0e8e61a3", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d4f19c117f2ea36100f753c4885aa8d63b4d65a0dc32106f829f89eeabd52c37105c9bdb75f752469729fa3f0e7d907c1d949192c8e264a1a510c32abe3a05ed50be2262d5bfb981673ec80a07fd2ce28c7f27cd0043a788c", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d547d5aaa651bafa63d077560dfe823ac75665ebf1dcfd96a06e45499f03dda31282977706918d4821b8f41540e0a793b0fd51b245dae19382b45386570f2b545fab75e3277910f7324b55f47c29f9965e8298371404e50ac", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d6037207d54d69a082ea225ab4a412e4b87d6f5612053b07c405cf05ea25e482a4908c0713be2998abfa5709836ba729443c319659e83ad5ee133e6f11af51d883e56216e9e1bbb1e2920c7c6120cbb55cd469b1f95b61601", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d63d0920de0c7315ebaed1d639d926961d28af89461c31eca890441e449147d23bb7c9d4fc42d7c16bfa5709836ba729443c319659e83ad5ee133e6f11af51d883e56216e9e1bbb1e2920c7c6120cbb55cd469b1f95b61601", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d7912c66be82a5972e5bc11c8d10551a296ba9aaff8ca6ab22a8cd1987974b87a97121c871f786d2e17e0a629acf01c38947f170b7e02a9ebb4ee60f83779acb99b71114c01a4f0a60694611a1502c399c77214ffa26e955b", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d7aa00f217f3a374a2f1ca0f388719f84099e8157a8a83c5ccf54eae1617f93933fa976baa629e6febfa5709836ba729443c319659e83ad5ee133e6f11af51d883e56216e9e1bbb1e2920c7c6120cbb55cd469b1f95b61601", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d9e1c3c8ab41943cf377b1aa724d7f518a3cfc96a732bdc4658155d09ed2bfc31b5ccbc6d8646b59f1b8f41540e0a793b0fd51b245dae19382b45386570f2b545fab75e3277910f7324b55f47c29f9965e8298371404e50ac", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d9fb8d6d95d5214a3305a4fa07e344eb99fad4be3565d646c8ac5af85514d9c96702c9c207be234958dbdb9185f467d2be3b84e8b2f529f7ec3844b378a889afd6bd31a9b5ed22ffee2019ad82c6692f1736dd41c8bb85726", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8d9fb8d6d95d5214a3305a4fa07e344eb99fad4be3565d646c8ac5af85514d9c96702c9c207be23495ec1caa509591a36a8403684384ce40838c9bd7fc49d933a10d3b26e979273e2f17ebf0bf41cd90e4287e126a59d5a243", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8da7fc066aae2ffe03b36e9a72f9a39cb2befac7e47f320309f31f1c1676288d9596045807304b3d79bfa5709836ba729443c319659e83ad5ee133e6f11af51d883e56216e9e1bbb1e2920c7c6120cbb55cd469b1f95b61601", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8daf3c377b0fddf7c7ad6d390fab0ab45ac16c21645be880af5cab2fbbeb04820401a4c9f766c17bef9fc14a2e16ade86fe26ee81d4497dc6aab81cc5f5bb0458d6149a763ecb09aefec06950dd61db1ba025401d2a04e3b9d", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8daf3c377b0fddf7c7ad6d390fab0ab45ac16c21645be880af5cab2fbbeb04820401a4c9f766c17befbfa5709836ba729443c319659e83ad5ee133e6f11af51d883e56216e9e1bbb1e2920c7c6120cbb55cd469b1f95b61601", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8db60505ba8b77ef03ed805436d3242f26dc828084b12aaf4bcb96af468816a182b5360149398aad6b1dafe949b0918138ceef924f6393d1818a04842301294604972da17b24b31b155e4409a01273733b8d21a156c2e7eb71", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8dbd27136a6e028656073cc840bfabb48fe935880c4c4c990ee98458b2fed308e9765f7f7f717dd3b2862fa5361d3b55afa6040e582687403c852b2d065b24f253276cc581226991f8e1818a78fc64c39da7f0b383c6726e0f", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8dca40d91320edd326500f9e8b5a0b23a8bdf21549f98f0e014f66b6a18bdd78e337a6c05d670c80c88a55d4c7bb6fbae546e2d03ac9ab16e85fe11dad6adfd6a20618905477b831d7d48ca32d0bfd2bdc8dbeba26ffe2c710", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8dd27478512243ed62c1c1f7066021798a464d4cf9099546d5d9907b3369f1b9d7a5aa5d60ca845619bfa5709836ba729443c319659e83ad5ee133e6f11af51d883e56216e9e1bbb1e2920c7c6120cbb55cd469b1f95b61601", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8de6da5659cbbe1489abbe99c4d3a474f4d1e78edb55a9be68d8f52c6fe730388a298e6f6325db3da7bfa5709836ba729443c319659e83ad5ee133e6f11af51d883e56216e9e1bbb1e2920c7c6120cbb55cd469b1f95b61601", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8de6da5659cbbe1489abbe99c4d3a474f4d1e78edb55a9be68d8f52c6fe730388a298e6f6325db3da7e94ca3e8c297d82f71e232a2892992d1f6480475fb797ce64e58f773d8fafd9fbcee4bdf4b14f2a71b6d3a428cf9f24b", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8decdd1760c61ff7234f2876dbe817af803170233320d778b92043b2359e3de6d16c9e5359f6302da31c84d6f551ad2a831263ef956f0cdb3b4810cefcb2d0b57bcce7b82007016ae4fe752c31d1a01b589a7966cea03ec65c", + "7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8df9981ee6b69eb7af2153af34f39ffc06e2daa5272c99798c8849091284dc8905f2a76b65754c2089bfa5709836ba729443c319659e83ad5ee133e6f11af51d883e56216e9e1bbb1e2920c7c6120cbb55cd469b1f95b61601", + "7474449cca95dc5d0c00e71735a6d17d4e7b9012096b41c4eb3aaf947f6ea429", + "89d139e01a5eb2256f222e5fc5dbe6b33c9c1284130706f5aea0c8b3d4c54d89", + "89d139e01a5eb2256f222e5fc5dbe6b36254e9d55588784fa2a62b726696e2b1" + ]); + + let key = array_bytes::hex2bytes("7474449cca95dc5d0c00e71735a6d17d3cd15a3fd6e04e47bee3922dbfa92c8da7dad55cf08ffe8194efa962146801b0503092b1ed6a3fa6aee9107334aefd7965bbe568c3d24c6d").unwrap(); + + assert_eq!( + trie.keys(IterArgs { + prefix: Some(&key), + start_at: Some(&key), + start_at_exclusive: true, + ..IterArgs::default() + }) + .unwrap() + .map(|result| result.unwrap()) + .collect::>(), + Vec::>::new() + ); } parameterized_test!(storage_root_is_non_default, storage_root_is_non_default_inner); @@ -626,26 +969,6 @@ pub mod tests { ); } - parameterized_test!(prefix_walking_works, prefix_walking_works_inner); - fn prefix_walking_works_inner( - state_version: StateVersion, - cache: Option, - recorder: Option, - ) { - let trie = test_trie(state_version, cache, recorder); - - let mut seen = HashSet::new(); - trie.for_keys_with_prefix(b"value", |key| { - let for_first_time = seen.insert(key.to_vec()); - assert!(for_first_time, "Seen key '{:?}' more than once", key); - }); - - let mut expected = HashSet::new(); - expected.insert(b"value1".to_vec()); - expected.insert(b"value2".to_vec()); - assert_eq!(seen, expected); - } - parameterized_test!( keys_with_empty_prefix_returns_all_keys, keys_with_empty_prefix_returns_all_keys_inner @@ -664,7 +987,8 @@ pub mod tests { .collect::>(); let trie = test_trie(state_version, cache, recorder); - let keys = trie.keys(&[]); + let keys: Vec<_> = + trie.keys(Default::default()).unwrap().map(|result| result.unwrap()).collect(); assert_eq!(expected, keys); } @@ -724,7 +1048,18 @@ pub mod tests { .with_recorder(Recorder::default()) .build(); assert_eq!(trie_backend.storage(b"key").unwrap(), proving_backend.storage(b"key").unwrap()); - assert_eq!(trie_backend.pairs(), proving_backend.pairs()); + assert_eq!( + trie_backend + .pairs(Default::default()) + .unwrap() + .map(|result| result.unwrap()) + .collect::>(), + proving_backend + .pairs(Default::default()) + .unwrap() + .map(|result| result.unwrap()) + .collect::>() + ); let (trie_root, mut trie_mdb) = trie_backend.storage_root(std::iter::empty(), state_version); diff --git a/primitives/state-machine/src/trie_backend_essence.rs b/primitives/state-machine/src/trie_backend_essence.rs index cdd1bb0bba05..22c76b56deb0 100644 --- a/primitives/state-machine/src/trie_backend_essence.rs +++ b/primitives/state-machine/src/trie_backend_essence.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,28 +19,26 @@ //! from storage. use crate::{ - backend::Consolidate, debug, trie_backend::AsLocalTrieCache, warn, StorageKey, StorageValue, + backend::{Consolidate, IterArgs, StorageIterator}, + trie_backend::TrieCacheProvider, + warn, StorageKey, StorageValue, }; use codec::Codec; use hash_db::{self, AsHashDB, HashDB, HashDBRef, Hasher, Prefix}; #[cfg(feature = "std")] use parking_lot::RwLock; use sp_core::storage::{ChildInfo, ChildType, StateVersion}; -#[cfg(not(feature = "std"))] -use sp_std::marker::PhantomData; -use sp_std::{boxed::Box, vec::Vec}; +use sp_std::{boxed::Box, marker::PhantomData, vec::Vec}; #[cfg(feature = "std")] use sp_trie::recorder::Recorder; use sp_trie::{ child_delta_trie_root, delta_trie_root, empty_child_trie_root, read_child_trie_hash, read_child_trie_value, read_trie_value, trie_types::{TrieDBBuilder, TrieError}, - DBValue, KeySpacedDB, NodeCodec, Trie, TrieCache, TrieDBIterator, TrieDBKeyIterator, - TrieRecorder, + DBValue, KeySpacedDB, NodeCodec, Trie, TrieCache, TrieDBRawIterator, TrieRecorder, }; #[cfg(feature = "std")] use std::{collections::HashMap, sync::Arc}; - // In this module, we only use layout for read operation and empty root, // where V1 and V0 are equivalent. use sp_trie::LayoutV1 as Layout; @@ -76,6 +74,133 @@ impl Cache { } } +enum IterState { + Pending, + FinishedComplete, + FinishedIncomplete, +} + +/// A raw iterator over the storage. +pub struct RawIter +where + H: Hasher, +{ + stop_on_incomplete_database: bool, + skip_if_first: Option, + root: H::Out, + child_info: Option, + trie_iter: TrieDBRawIterator>, + state: IterState, + _phantom: PhantomData<(S, C)>, +} + +impl RawIter +where + H: Hasher, + S: TrieBackendStorage, + H::Out: Codec + Ord, + C: TrieCacheProvider + Send + Sync, +{ + #[inline] + fn prepare( + &mut self, + backend: &TrieBackendEssence, + callback: impl FnOnce( + &sp_trie::TrieDB>, + &mut TrieDBRawIterator>, + ) -> Option::Out>>>>, + ) -> Option> { + if !matches!(self.state, IterState::Pending) { + return None + } + + let result = backend.with_trie_db(self.root, self.child_info.as_ref(), |db| { + callback(&db, &mut self.trie_iter) + }); + match result { + Some(Ok(key_value)) => Some(Ok(key_value)), + None => { + self.state = IterState::FinishedComplete; + None + }, + Some(Err(error)) => { + self.state = IterState::FinishedIncomplete; + if matches!(*error, TrieError::IncompleteDatabase(_)) && + self.stop_on_incomplete_database + { + None + } else { + Some(Err(format!("TrieDB iteration error: {}", error))) + } + }, + } + } +} + +impl Default for RawIter +where + H: Hasher, +{ + fn default() -> Self { + Self { + stop_on_incomplete_database: false, + skip_if_first: None, + child_info: None, + root: Default::default(), + trie_iter: TrieDBRawIterator::empty(), + state: IterState::FinishedComplete, + _phantom: Default::default(), + } + } +} + +impl StorageIterator for RawIter +where + H: Hasher, + S: TrieBackendStorage, + H::Out: Codec + Ord, + C: TrieCacheProvider + Send + Sync, +{ + type Backend = crate::TrieBackend; + type Error = crate::DefaultError; + + #[inline] + fn next_key(&mut self, backend: &Self::Backend) -> Option> { + let skip_if_first = self.skip_if_first.take(); + self.prepare(&backend.essence, |trie, trie_iter| { + let mut result = trie_iter.next_key(&trie); + if let Some(skipped_key) = skip_if_first { + if let Some(Ok(ref key)) = result { + if *key == skipped_key { + result = trie_iter.next_key(&trie); + } + } + } + result + }) + } + + #[inline] + fn next_pair(&mut self, backend: &Self::Backend) -> Option> { + let skip_if_first = self.skip_if_first.take(); + self.prepare(&backend.essence, |trie, trie_iter| { + let mut result = trie_iter.next_item(&trie); + if let Some(skipped_key) = skip_if_first { + if let Some(Ok((ref key, _))) = result { + if *key == skipped_key { + result = trie_iter.next_item(&trie); + } + } + } + result + }) + } + + fn was_complete(&self) -> bool { + matches!(self.state, IterState::FinishedComplete) + } +} + /// Patricia trie-based pairs storage essence. pub struct TrieBackendEssence, H: Hasher, C> { storage: S, @@ -83,29 +208,28 @@ pub struct TrieBackendEssence, H: Hasher, C> { empty: H::Out, #[cfg(feature = "std")] pub(crate) cache: Arc>>, - #[cfg(feature = "std")] pub(crate) trie_node_cache: Option, #[cfg(feature = "std")] pub(crate) recorder: Option>, - #[cfg(not(feature = "std"))] - _phantom: PhantomData, } impl, H: Hasher, C> TrieBackendEssence { /// Create new trie-based backend. pub fn new(storage: S, root: H::Out) -> Self { + Self::new_with_cache(storage, root, None) + } + + /// Create new trie-based backend. + pub fn new_with_cache(storage: S, root: H::Out, cache: Option) -> Self { TrieBackendEssence { storage, root, empty: H::hash(&[0u8]), #[cfg(feature = "std")] cache: Arc::new(RwLock::new(Cache::new())), - #[cfg(feature = "std")] - trie_node_cache: None, + trie_node_cache: cache, #[cfg(feature = "std")] recorder: None, - #[cfg(not(feature = "std"))] - _phantom: PhantomData, } } @@ -163,11 +287,11 @@ impl, H: Hasher, C> TrieBackendEssence { } } -impl, H: Hasher, C: AsLocalTrieCache> TrieBackendEssence { +impl, H: Hasher, C: TrieCacheProvider> TrieBackendEssence { /// Call the given closure passing it the recorder and the cache. /// /// If the given `storage_root` is `None`, `self.root` will be used. - #[cfg(feature = "std")] + #[inline] fn with_recorder_and_cache( &self, storage_root: Option, @@ -177,31 +301,23 @@ impl, H: Hasher, C: AsLocalTrieCache> TrieBackendEss ) -> R, ) -> R { let storage_root = storage_root.unwrap_or_else(|| self.root); - let mut recorder = self.recorder.as_ref().map(|r| r.as_trie_recorder(storage_root)); - let recorder = match recorder.as_mut() { - Some(recorder) => Some(recorder as &mut dyn TrieRecorder), - None => None, - }; - - let mut cache = self - .trie_node_cache - .as_ref() - .map(|c| c.as_local_trie_cache().as_trie_db_cache(storage_root)); + let mut cache = self.trie_node_cache.as_ref().map(|c| c.as_trie_db_cache(storage_root)); let cache = cache.as_mut().map(|c| c as _); - callback(recorder, cache) - } + #[cfg(feature = "std")] + { + let mut recorder = self.recorder.as_ref().map(|r| r.as_trie_recorder(storage_root)); + let recorder = match recorder.as_mut() { + Some(recorder) => Some(recorder as &mut dyn TrieRecorder), + None => None, + }; + callback(recorder, cache) + } - #[cfg(not(feature = "std"))] - fn with_recorder_and_cache( - &self, - _: Option, - callback: impl FnOnce( - Option<&mut dyn TrieRecorder>, - Option<&mut dyn TrieCache>>, - ) -> R, - ) -> R { - callback(None, None) + #[cfg(not(feature = "std"))] + { + callback(None, cache) + } } /// Call the given closure passing it the recorder and the cache. @@ -228,12 +344,12 @@ impl, H: Hasher, C: AsLocalTrieCache> TrieBackendEss }; let result = if let Some(local_cache) = self.trie_node_cache.as_ref() { - let mut cache = local_cache.as_local_trie_cache().as_trie_db_mut_cache(); + let mut cache = local_cache.as_trie_db_mut_cache(); let (new_root, r) = callback(recorder, Some(&mut cache)); if let Some(new_root) = new_root { - cache.merge_into(local_cache.as_local_trie_cache(), new_root); + local_cache.merge(cache, new_root); } r @@ -247,24 +363,66 @@ impl, H: Hasher, C: AsLocalTrieCache> TrieBackendEss #[cfg(not(feature = "std"))] fn with_recorder_and_cache_for_storage_root( &self, - _: Option, + _storage_root: Option, callback: impl FnOnce( Option<&mut dyn TrieRecorder>, Option<&mut dyn TrieCache>>, ) -> (Option, R), ) -> R { - callback(None, None).1 + if let Some(local_cache) = self.trie_node_cache.as_ref() { + let mut cache = local_cache.as_trie_db_mut_cache(); + + let (new_root, r) = callback(None, Some(&mut cache)); + + if let Some(new_root) = new_root { + local_cache.merge(cache, new_root); + } + + r + } else { + callback(None, None).1 + } } } -impl, H: Hasher, C: AsLocalTrieCache + Send + Sync> +impl, H: Hasher, C: TrieCacheProvider + Send + Sync> TrieBackendEssence where H::Out: Codec + Ord, { - /// Return the next key in the trie i.e. the minimum key that is strictly superior to `key` in + /// Calls the given closure with a [`TrieDb`] constructed for the given + /// storage root and (optionally) child trie. + #[inline] + fn with_trie_db( + &self, + root: H::Out, + child_info: Option<&ChildInfo>, + callback: impl FnOnce(&sp_trie::TrieDB>) -> R, + ) -> R { + let backend = self as &dyn HashDBRef>; + let db = child_info + .as_ref() + .map(|child_info| KeySpacedDB::new(backend, child_info.keyspace())); + let db = db.as_ref().map(|db| db as &dyn HashDBRef>).unwrap_or(backend); + + self.with_recorder_and_cache(Some(root), |recorder, cache| { + let trie = TrieDBBuilder::::new(db, &root) + .with_optional_recorder(recorder) + .with_optional_cache(cache) + .build(); + + callback(&trie) + }) + } + + /// Returns the next key in the trie i.e. the minimum key that is strictly superior to `key` in /// lexicographic order. - pub fn next_storage_key(&self, key: &[u8]) -> Result> { + /// + /// Will always traverse the trie from scratch in search of the key, which is slow. + /// Used only when debug assertions are enabled to crosscheck the results of finding + /// the next key through an iterator. + #[cfg(debug_assertions)] + pub fn next_storage_key_slow(&self, key: &[u8]) -> Result> { self.next_storage_key_from_root(&self.root, None, key) } @@ -316,21 +474,7 @@ where child_info: Option<&ChildInfo>, key: &[u8], ) -> Result> { - let dyn_eph: &dyn HashDBRef<_, _>; - let keyspace_eph; - if let Some(child_info) = child_info.as_ref() { - keyspace_eph = KeySpacedDB::new(self, child_info.keyspace()); - dyn_eph = &keyspace_eph; - } else { - dyn_eph = self; - } - - self.with_recorder_and_cache(Some(*root), |recorder, cache| { - let trie = TrieDBBuilder::::new(dyn_eph, root) - .with_optional_recorder(recorder) - .with_optional_cache(cache) - .build(); - + self.with_trie_db(*root, child_info, |trie| { let mut iter = trie.key_iter().map_err(|e| format!("TrieDB iteration error: {}", e))?; // The key just after the one given in input, basically `key++0`. @@ -429,246 +573,47 @@ where }) } - /// Retrieve all entries keys of storage and call `f` for each of those keys. - /// Aborts as soon as `f` returns false. - /// - /// Returns `true` when all keys were iterated. - pub fn apply_to_key_values_while( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - start_at: Option<&[u8]>, - f: impl FnMut(Vec, Vec) -> bool, - allow_missing_nodes: bool, - ) -> Result { - let root = if let Some(child_info) = child_info.as_ref() { - match self.child_root(child_info)? { - Some(child_root) => child_root, - None => return Ok(true), - } - } else { - self.root - }; - - self.trie_iter_inner(&root, prefix, f, child_info, start_at, allow_missing_nodes) - } - - /// Retrieve all entries keys of a storage and call `f` for each of those keys. - /// Aborts as soon as `f` returns false. - pub fn apply_to_keys_while bool>( - &self, - child_info: Option<&ChildInfo>, - prefix: Option<&[u8]>, - start_at: Option<&[u8]>, - f: F, - ) { - let root = if let Some(child_info) = child_info.as_ref() { - match self.child_root(child_info) { - Ok(Some(v)) => v, - // If the child trie doesn't exist, there is no need to continue. - Ok(None) => return, - Err(e) => { - debug!(target: "trie", "Error while iterating child storage: {}", e); - return - }, - } + /// Create a raw iterator over the storage. + pub fn raw_iter(&self, args: IterArgs) -> Result> { + let root = if let Some(child_info) = args.child_info.as_ref() { + let root = match self.child_root(&child_info)? { + Some(root) => root, + None => return Ok(Default::default()), + }; + root } else { self.root }; - self.trie_iter_key_inner(&root, prefix, f, child_info, start_at) - } - - /// Execute given closure for all keys starting with prefix. - pub fn for_child_keys_with_prefix( - &self, - child_info: &ChildInfo, - prefix: &[u8], - mut f: impl FnMut(&[u8]), - ) { - let root = match self.child_root(child_info) { - Ok(Some(v)) => v, - // If the child trie doesn't exist, there is no need to continue. - Ok(None) => return, - Err(e) => { - debug!(target: "trie", "Error while iterating child storage: {}", e); - return - }, - }; - - self.trie_iter_key_inner( - &root, - Some(prefix), - |k| { - f(k); - true - }, - Some(child_info), - None, - ) - } - - /// Execute given closure for all keys starting with prefix. - pub fn for_keys_with_prefix(&self, prefix: &[u8], mut f: F) { - self.trie_iter_key_inner( - &self.root, - Some(prefix), - |k| { - f(k); - true - }, - None, - None, - ) - } - - fn trie_iter_key_inner bool>( - &self, - root: &H::Out, - maybe_prefix: Option<&[u8]>, - mut f: F, - child_info: Option<&ChildInfo>, - maybe_start_at: Option<&[u8]>, - ) { - let mut iter = move |db| -> sp_std::result::Result<(), Box>> { - self.with_recorder_and_cache(Some(*root), |recorder, cache| { - let trie = TrieDBBuilder::::new(db, root) - .with_optional_recorder(recorder) - .with_optional_cache(cache) - .build(); - let prefix = maybe_prefix.unwrap_or(&[]); - let iter = match maybe_start_at { - Some(start_at) => - TrieDBKeyIterator::new_prefixed_then_seek(&trie, prefix, start_at), - None => TrieDBKeyIterator::new_prefixed(&trie, prefix), - }?; - - for x in iter { - let key = x?; - - debug_assert!(maybe_prefix - .as_ref() - .map(|prefix| key.starts_with(prefix)) - .unwrap_or(true)); - - if !f(&key) { - break - } - } - - Ok(()) - }) - }; - - let result = if let Some(child_info) = child_info { - let db = KeySpacedDB::new(self, child_info.keyspace()); - iter(&db) - } else { - iter(self) - }; - if let Err(e) = result { - debug!(target: "trie", "Error while iterating by prefix: {}", e); + if self.root == Default::default() { + // A special-case for an empty storage root. + return Ok(Default::default()) } - } - fn trie_iter_inner, Vec) -> bool>( - &self, - root: &H::Out, - prefix: Option<&[u8]>, - mut f: F, - child_info: Option<&ChildInfo>, - start_at: Option<&[u8]>, - allow_missing_nodes: bool, - ) -> Result { - let mut iter = move |db| -> sp_std::result::Result>> { - self.with_recorder_and_cache(Some(*root), |recorder, cache| { - let trie = TrieDBBuilder::::new(db, root) - .with_optional_recorder(recorder) - .with_optional_cache(cache) - .build(); - - let prefix = prefix.unwrap_or(&[]); - let iterator = if let Some(start_at) = start_at { - TrieDBIterator::new_prefixed_then_seek(&trie, prefix, start_at)? + let trie_iter = self + .with_trie_db(root, args.child_info.as_ref(), |db| { + let prefix = args.prefix.as_deref().unwrap_or(&[]); + if let Some(start_at) = args.start_at { + TrieDBRawIterator::new_prefixed_then_seek(db, prefix, &start_at) } else { - TrieDBIterator::new_prefixed(&trie, prefix)? - }; - for x in iterator { - let (key, value) = x?; - - debug_assert!(key.starts_with(prefix)); - - if !f(key, value) { - return Ok(false) - } - } - - Ok(true) - }) - }; - - let result = if let Some(child_info) = child_info { - let db = KeySpacedDB::new(self, child_info.keyspace()); - iter(&db) - } else { - iter(self) - }; - match result { - Ok(completed) => Ok(completed), - Err(e) if matches!(*e, TrieError::IncompleteDatabase(_)) && allow_missing_nodes => - Ok(false), - Err(e) => Err(format!("TrieDB iteration error: {}", e)), - } - } - - /// Execute given closure for all key and values starting with prefix. - pub fn for_key_values_with_prefix(&self, prefix: &[u8], mut f: F) { - let _ = self.trie_iter_inner( - &self.root, - Some(prefix), - |k, v| { - f(&k, &v); - true - }, - None, - None, - false, - ); - } - - /// Returns all `(key, value)` pairs in the trie. - pub fn pairs(&self) -> Vec<(StorageKey, StorageValue)> { - let collect_all = || -> sp_std::result::Result<_, Box>> { - self.with_recorder_and_cache(None, |recorder, cache| { - let trie = TrieDBBuilder::::new(self, self.root()) - .with_optional_cache(cache) - .with_optional_recorder(recorder) - .build(); - - let mut v = Vec::new(); - for x in trie.iter()? { - let (key, value) = x?; - v.push((key.to_vec(), value.to_vec())); + TrieDBRawIterator::new_prefixed(db, prefix) } - - Ok(v) }) - }; + .map_err(|e| format!("TrieDB iteration error: {}", e))?; - match collect_all() { - Ok(v) => v, - Err(e) => { - debug!(target: "trie", "Error extracting trie values: {}", e); - Vec::new() + Ok(RawIter { + stop_on_incomplete_database: args.stop_on_incomplete_database, + skip_if_first: if args.start_at_exclusive { + args.start_at.map(|key| key.to_vec()) + } else { + None }, - } - } - - /// Returns all keys that start with the given `prefix`. - pub fn keys(&self, prefix: &[u8]) -> Vec { - let mut keys = Vec::new(); - self.for_keys_with_prefix(prefix, |k| keys.push(k.to_vec())); - keys + child_info: args.child_info, + root, + trie_iter, + state: IterState::Pending, + _phantom: Default::default(), + }) } /// Return the storage root after applying the given `delta`. @@ -860,8 +805,8 @@ where } } -impl, H: Hasher, C: AsLocalTrieCache + Send + Sync> AsHashDB - for TrieBackendEssence +impl, H: Hasher, C: TrieCacheProvider + Send + Sync> + AsHashDB for TrieBackendEssence { fn as_hash_db<'b>(&'b self) -> &'b (dyn HashDB + 'b) { self @@ -871,7 +816,7 @@ impl, H: Hasher, C: AsLocalTrieCache + Send + Sync> } } -impl, H: Hasher, C: AsLocalTrieCache + Send + Sync> HashDB +impl, H: Hasher, C: TrieCacheProvider + Send + Sync> HashDB for TrieBackendEssence { fn get(&self, key: &H::Out, prefix: Prefix) -> Option { @@ -904,7 +849,7 @@ impl, H: Hasher, C: AsLocalTrieCache + Send + Sync> } } -impl, H: Hasher, C: AsLocalTrieCache + Send + Sync> +impl, H: Hasher, C: TrieCacheProvider + Send + Sync> HashDBRef for TrieBackendEssence { fn get(&self, key: &H::Out, prefix: Prefix) -> Option { @@ -919,6 +864,7 @@ impl, H: Hasher, C: AsLocalTrieCache + Send + Sync> #[cfg(test)] mod test { use super::*; + use crate::{Backend, TrieBackend}; use sp_core::{Blake2Hasher, H256}; use sp_trie::{ cache::LocalTrieCache, trie_types::TrieDBMutBuilderV1 as TrieDBMutBuilder, KeySpacedDBMut, @@ -957,6 +903,8 @@ mod test { }; let essence_1 = TrieBackendEssence::<_, _, LocalTrieCache<_>>::new(mdb, root_1); + let mdb = essence_1.backend_storage().clone(); + let essence_1 = TrieBackend::from_essence(essence_1); assert_eq!(essence_1.next_storage_key(b"2"), Ok(Some(b"3".to_vec()))); assert_eq!(essence_1.next_storage_key(b"3"), Ok(Some(b"4".to_vec()))); @@ -964,7 +912,6 @@ mod test { assert_eq!(essence_1.next_storage_key(b"5"), Ok(Some(b"6".to_vec()))); assert_eq!(essence_1.next_storage_key(b"6"), Ok(None)); - let mdb = essence_1.backend_storage().clone(); let essence_2 = TrieBackendEssence::<_, _, LocalTrieCache<_>>::new(mdb, root_2); assert_eq!(essence_2.next_child_storage_key(child_info, b"2"), Ok(Some(b"3".to_vec()))); diff --git a/primitives/statement-store/Cargo.toml b/primitives/statement-store/Cargo.toml new file mode 100644 index 000000000000..02ee6f4c1552 --- /dev/null +++ b/primitives/statement-store/Cargo.toml @@ -0,0 +1,62 @@ +[package] +name = "sp-statement-store" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "A crate which contains primitives related to the statement store" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +sp-core = { version = "21.0.0", default-features = false, path = "../core" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } +sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../application-crypto" } +sp-runtime-interface = { version = "17.0.0", default-features = false, path = "../runtime-interface" } +sp-externalities = { version = "0.19.0", default-features = false, path = "../externalities" } +thiserror = { version = "1.0", optional = true } + +# ECIES dependencies +ed25519-dalek = { version = "1.0", optional = true } +x25519-dalek = { version = "2.0.0-pre.1", optional = true } +curve25519-dalek = { version = "3.2", optional = true } +aes-gcm = { version = "0.10", optional = true } +hkdf = { version = "0.12.0", optional = true } +sha2 = { version = "0.10.0", optional = true } +rand = { version = "0.8.5", features = ["small_rng"], optional = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "sp-core/std", + "sp-runtime/std", + "sp-runtime-interface/std", + "sp-std/std", + "sp-api/std", + "sp-application-crypto/std", + "thiserror", + + "ed25519-dalek", + "x25519-dalek", + "curve25519-dalek", + "aes-gcm", + "hkdf", + "sha2", + "rand", +] +serde = [ + "scale-info/serde", + "sp-application-crypto/serde", + "sp-core/serde", + "sp-runtime/serde", +] diff --git a/primitives/statement-store/README.md b/primitives/statement-store/README.md new file mode 100644 index 000000000000..fb88aaa4ecd9 --- /dev/null +++ b/primitives/statement-store/README.md @@ -0,0 +1,39 @@ +Statement store is an off-chain data-store for signed statements accessible via RPC and OCW. + +Nodes hold a number of statements with a proof of authenticity owing to an account ID. OCWs can place items in the data-store (with valid signatures) for any accounts whose keys they control. Users can also submit pre-signed statements via RPC. Statements can also be submitted from on-chain logic through an on-chain event. + +A new system event `NewStatement` is added to the runtime. This event allows any account on-chain to declare that they want to make a statement for the store. Within the node store and for broadcasting, the statement would be accompanied with the hash of the block and index of the event within it, essentially taking the place of a real signature. + +Statements comprise an optional proof of authenticity (e.g. a signature) and a number of fields. For statements without a proof, nodes would gossip statements randomly with a rate-limiter to minimise the chance of being overrun by a misbehaving node. These will generally be disregarded by nodes unless they are gossiped by several different peers or if a peer pays for it somehow (e.g. gossiping something valuable). + +Each field is effectively a key/value pair. Fields must be sorted and the same field type may not be repeated. Depending on which keys are present, clients may index the message for ease of retrieval. + +Formally, `Statement` is equivalent to the type `Vec` and `Field` is the SCALE-encoded enumeration: +- 0: `AuthenticityProof(Proof)`: The signature of the message. For cryptography where the public key cannot be derived from the signature together with the message data, then this will also include the signer's public key. The message data is all fields of the messages fields except the signature concatenated together *without the length prefix that a `Vec` would usually imply*. This is so that the signature can be derived without needing to re-encode the statement. +- 1: `DecryptionKey([u8; 32])`: The decryption key identifier which should be used to decrypt the statement's data. In the absense of this field `Data` should be treated as not encrypted. +- 2: `Priority(u32)`: Priority specifier. Higher priority statements should be kept around at the cost of lower priority statements if multiple statements from the same sender are competing for persistence or transport. Nodes should disregard when considering unsigned statements. +- 3: `Channel([u8; 32])`: The channel identifier. Only one message of a given channel should be retained at once (the one of highest priority). Nodes should disregard when considering unsigned statements. +- 4: `Topic1([u8; 32]))`: First topic identifier. +- 5: `Topic2([u8; 32]))`: Second topic identifier. +- 6: `Topic3([u8; 32]))`: Third topic identifier. +- 7: `Topic4([u8; 32]))`: Fourth topic identifier. +- 8: `Data(Vec)`: General data segment. No special meaning. + +`Proof` is defined as the SCALE-encoded enumeration: +- 0: `Sr25519 { signature: [u8; 64], signer: [u8; 32] }` +- 1: `Ed25519 { signature: [u8; 64], signer: [u8; 32] )` +- 2: `Secp256k1Ecdsa { signature: [u8; 65], signer: [u8; 33] )` +- 3: `OnChain { who: [u8; 32], block_hash: [u8; 32], event_index: u64 }` + +### Potential uses + +Potential use-cases are various and include: +- ring-signature aggregation; +- messaging; +- state-channels; +- deferral of the initial "advertising" phase of multi-party transactions; +- publication of preimage data whose hash is referenced on-chain; +- effective transferal of fee payment to a second-party. + + +License: Apache-2.0 diff --git a/primitives/statement-store/src/ecies.rs b/primitives/statement-store/src/ecies.rs new file mode 100644 index 000000000000..4afd583e03c8 --- /dev/null +++ b/primitives/statement-store/src/ecies.rs @@ -0,0 +1,174 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// tag::description[] +//! ECIES encryption scheme using x25519 key exchange and AEAD. +// end::description[] + +use aes_gcm::{aead::Aead, AeadCore, KeyInit}; +use rand::rngs::OsRng; +use sha2::Digest; +use sp_core::crypto::Pair; + +/// x25519 secret key. +pub type SecretKey = x25519_dalek::StaticSecret; +/// x25519 public key. +pub type PublicKey = x25519_dalek::PublicKey; + +/// Encryption or decryption error. +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +pub enum Error { + /// Generic AES encryption error. + #[error("Encryption error")] + Encryption, + /// Generic AES decryption error. + #[error("Decryption error")] + Decryption, + /// Error reading key data. Not enough data in the buffer. + #[error("Bad cypher text")] + BadData, +} + +const NONCE_LEN: usize = 12; +const PK_LEN: usize = 32; +const AES_KEY_LEN: usize = 32; + +fn aes_encrypt(key: &[u8; AES_KEY_LEN], nonce: &[u8], plaintext: &[u8]) -> Result, Error> { + let enc = aes_gcm::Aes256Gcm::new(key.into()); + + enc.encrypt(nonce.into(), aes_gcm::aead::Payload { msg: plaintext, aad: b"" }) + .map_err(|_| Error::Encryption) +} + +fn aes_decrypt(key: &[u8; AES_KEY_LEN], nonce: &[u8], ciphertext: &[u8]) -> Result, Error> { + let dec = aes_gcm::Aes256Gcm::new(key.into()); + dec.decrypt(nonce.into(), aes_gcm::aead::Payload { msg: ciphertext, aad: b"" }) + .map_err(|_| Error::Decryption) +} + +fn kdf(shared_secret: &[u8]) -> [u8; AES_KEY_LEN] { + let hkdf = hkdf::Hkdf::::new(None, shared_secret); + let mut aes_key = [0u8; AES_KEY_LEN]; + hkdf.expand(b"", &mut aes_key) + .expect("There's always enough data for derivation. qed."); + aes_key +} + +/// Encrypt `plaintext` with the given public x25519 public key. Decryption can be performed with +/// the matching secret key. +pub fn encrypt_x25519(pk: &PublicKey, plaintext: &[u8]) -> Result, Error> { + let ephemeral_sk = x25519_dalek::StaticSecret::new(OsRng); + let ephemeral_pk = x25519_dalek::PublicKey::from(&ephemeral_sk); + + let mut shared_secret = ephemeral_sk.diffie_hellman(pk).to_bytes().to_vec(); + shared_secret.extend_from_slice(ephemeral_pk.as_bytes()); + + let aes_key = kdf(&shared_secret); + + let nonce = aes_gcm::Aes256Gcm::generate_nonce(OsRng); + let ciphertext = aes_encrypt(&aes_key, &nonce, plaintext)?; + + let mut out = Vec::with_capacity(ciphertext.len() + PK_LEN + NONCE_LEN); + out.extend_from_slice(ephemeral_pk.as_bytes()); + out.extend_from_slice(nonce.as_slice()); + out.extend_from_slice(ciphertext.as_slice()); + + Ok(out) +} + +/// Encrypt `plaintext` with the given ed25519 public key. Decryption can be performed with the +/// matching secret key. +pub fn encrypt_ed25519(pk: &sp_core::ed25519::Public, plaintext: &[u8]) -> Result, Error> { + let ed25519 = curve25519_dalek::edwards::CompressedEdwardsY(pk.0); + let x25519 = ed25519.decompress().ok_or(Error::BadData)?.to_montgomery(); + let montgomery = x25519_dalek::PublicKey::from(x25519.to_bytes()); + encrypt_x25519(&montgomery, plaintext) +} + +/// Decrypt with the given x25519 secret key. +pub fn decrypt_x25519(sk: &SecretKey, encrypted: &[u8]) -> Result, Error> { + if encrypted.len() < PK_LEN + NONCE_LEN { + return Err(Error::BadData) + } + let mut ephemeral_pk: [u8; PK_LEN] = Default::default(); + ephemeral_pk.copy_from_slice(&encrypted[0..PK_LEN]); + let ephemeral_pk = PublicKey::from(ephemeral_pk); + + let mut shared_secret = sk.diffie_hellman(&ephemeral_pk).to_bytes().to_vec(); + shared_secret.extend_from_slice(ephemeral_pk.as_bytes()); + + let aes_key = kdf(&shared_secret); + + let nonce = &encrypted[PK_LEN..PK_LEN + NONCE_LEN]; + aes_decrypt(&aes_key, &nonce, &encrypted[PK_LEN + NONCE_LEN..]) +} + +/// Decrypt with the given ed25519 key pair. +pub fn decrypt_ed25519(pair: &sp_core::ed25519::Pair, encrypted: &[u8]) -> Result, Error> { + let raw = pair.to_raw_vec(); + let hash: [u8; 32] = sha2::Sha512::digest(&raw).as_slice()[..32] + .try_into() + .map_err(|_| Error::Decryption)?; + let secret = x25519_dalek::StaticSecret::from(hash); + decrypt_x25519(&secret, encrypted) +} + +#[cfg(test)] +mod test { + use super::*; + use rand::rngs::OsRng; + use sp_core::crypto::Pair; + + #[test] + fn basic_x25519_encryption() { + let sk = SecretKey::new(OsRng); + let pk = PublicKey::from(&sk); + + let plain_message = b"An important secret message"; + let encrypted = encrypt_x25519(&pk, plain_message).unwrap(); + + let decrypted = decrypt_x25519(&sk, &encrypted).unwrap(); + assert_eq!(plain_message, decrypted.as_slice()); + } + + #[test] + fn basic_ed25519_encryption() { + let (pair, _) = sp_core::ed25519::Pair::generate(); + let pk = pair.into(); + + let plain_message = b"An important secret message"; + let encrypted = encrypt_ed25519(&pk, plain_message).unwrap(); + + let decrypted = decrypt_ed25519(&pair, &encrypted).unwrap(); + assert_eq!(plain_message, decrypted.as_slice()); + } + + #[test] + fn fails_on_bad_data() { + let sk = SecretKey::new(OsRng); + let pk = PublicKey::from(&sk); + + let plain_message = b"An important secret message"; + let encrypted = encrypt_x25519(&pk, plain_message).unwrap(); + + assert_eq!(decrypt_x25519(&sk, &[]), Err(Error::BadData)); + assert_eq!( + decrypt_x25519(&sk, &encrypted[0..super::PK_LEN + super::NONCE_LEN - 1]), + Err(Error::BadData) + ); + } +} diff --git a/primitives/statement-store/src/lib.rs b/primitives/statement-store/src/lib.rs new file mode 100644 index 000000000000..67e7a7b3896b --- /dev/null +++ b/primitives/statement-store/src/lib.rs @@ -0,0 +1,661 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] +#![warn(missing_docs)] + +//! A crate which contains statement-store primitives. + +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_application_crypto::RuntimeAppPublic; +#[cfg(feature = "std")] +use sp_core::Pair; +use sp_runtime_interface::pass_by::PassByCodec; +use sp_std::vec::Vec; + +/// Statement topic. +pub type Topic = [u8; 32]; +/// Decryption key identifier. +pub type DecryptionKey = [u8; 32]; +/// Statement hash. +pub type Hash = [u8; 32]; +/// Block hash. +pub type BlockHash = [u8; 32]; +/// Account id +pub type AccountId = [u8; 32]; +/// Statement channel. +pub type Channel = [u8; 32]; + +/// Total number of topic fields allowed. +pub const MAX_TOPICS: usize = 4; + +#[cfg(feature = "std")] +pub use store_api::{ + Error, NetworkPriority, Result, StatementSource, StatementStore, SubmitResult, +}; + +#[cfg(feature = "std")] +mod ecies; +pub mod runtime_api; +#[cfg(feature = "std")] +mod store_api; + +mod sr25519 { + mod app_sr25519 { + use sp_application_crypto::{app_crypto, key_types::STATEMENT, sr25519}; + app_crypto!(sr25519, STATEMENT); + } + pub type Public = app_sr25519::Public; +} + +/// Statement-store specific ed25519 crypto primitives. +pub mod ed25519 { + mod app_ed25519 { + use sp_application_crypto::{app_crypto, ed25519, key_types::STATEMENT}; + app_crypto!(ed25519, STATEMENT); + } + /// Statement-store specific ed25519 public key. + pub type Public = app_ed25519::Public; + /// Statement-store specific ed25519 key pair. + #[cfg(feature = "std")] + pub type Pair = app_ed25519::Pair; +} + +mod ecdsa { + mod app_ecdsa { + use sp_application_crypto::{app_crypto, ecdsa, key_types::STATEMENT}; + app_crypto!(ecdsa, STATEMENT); + } + pub type Public = app_ecdsa::Public; +} + +/// Returns blake2-256 hash for the encoded statement. +#[cfg(feature = "std")] +pub fn hash_encoded(data: &[u8]) -> [u8; 32] { + sp_core::hashing::blake2_256(data) +} + +/// Statement proof. +#[derive(Encode, Decode, TypeInfo, sp_core::RuntimeDebug, Clone, PartialEq, Eq)] +pub enum Proof { + /// Sr25519 Signature. + Sr25519 { + /// Signature. + signature: [u8; 64], + /// Public key. + signer: [u8; 32], + }, + /// Ed25519 Signature. + Ed25519 { + /// Signature. + signature: [u8; 64], + /// Public key. + signer: [u8; 32], + }, + /// Secp256k1 Signature. + Secp256k1Ecdsa { + /// Signature. + signature: [u8; 65], + /// Public key. + signer: [u8; 33], + }, + /// On-chain event proof. + OnChain { + /// Account identifier associated with the event. + who: AccountId, + /// Hash of block that contains the event. + block_hash: BlockHash, + /// Index of the event in the event list. + event_index: u64, + }, +} + +impl Proof { + /// Return account id for the proof creator. + pub fn account_id(&self) -> AccountId { + match self { + Proof::Sr25519 { signer, .. } => *signer, + Proof::Ed25519 { signer, .. } => *signer, + Proof::Secp256k1Ecdsa { signer, .. } => + ::hash(signer).into(), + Proof::OnChain { who, .. } => *who, + } + } +} + +/// Statement attributes. Each statement is a list of 0 or more fields. Fields may only appear once +/// and in the order declared here. +#[derive(Encode, Decode, TypeInfo, sp_core::RuntimeDebug, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum Field { + /// Statement proof. + AuthenticityProof(Proof) = 0, + /// An identifier for the key that `Data` field may be decrypted with. + DecryptionKey(DecryptionKey) = 1, + /// Priority when competing with other messages from the same sender. + Priority(u32) = 2, + /// Account channel to use. Only one message per `(account, channel)` pair is allowed. + Channel(Channel) = 3, + /// First statement topic. + Topic1(Topic) = 4, + /// Second statement topic. + Topic2(Topic) = 5, + /// Third statement topic. + Topic3(Topic) = 6, + /// Fourth statement topic. + Topic4(Topic) = 7, + /// Additional data. + Data(Vec) = 8, +} + +impl Field { + fn discriminant(&self) -> u8 { + // This is safe for repr(u8) + // see https://doc.rust-lang.org/reference/items/enumerations.html#pointer-casting + unsafe { *(self as *const Self as *const u8) } + } +} + +/// Statement structure. +#[derive(TypeInfo, sp_core::RuntimeDebug, PassByCodec, Clone, PartialEq, Eq, Default)] +pub struct Statement { + proof: Option, + decryption_key: Option, + channel: Option, + priority: Option, + num_topics: u8, + topics: [Topic; MAX_TOPICS], + data: Option>, +} + +impl Decode for Statement { + fn decode(input: &mut I) -> core::result::Result { + // Encoding matches that of Vec. Basically this just means accepting that there + // will be a prefix of vector length. + let num_fields: codec::Compact = Decode::decode(input)?; + let mut tag = 0; + let mut statement = Statement::new(); + for i in 0..num_fields.into() { + let field: Field = Decode::decode(input)?; + if i > 0 && field.discriminant() <= tag { + return Err("Invalid field order or duplicate fields".into()) + } + tag = field.discriminant(); + match field { + Field::AuthenticityProof(p) => statement.set_proof(p), + Field::DecryptionKey(key) => statement.set_decryption_key(key), + Field::Priority(p) => statement.set_priority(p), + Field::Channel(c) => statement.set_channel(c), + Field::Topic1(t) => statement.set_topic(0, t), + Field::Topic2(t) => statement.set_topic(1, t), + Field::Topic3(t) => statement.set_topic(2, t), + Field::Topic4(t) => statement.set_topic(3, t), + Field::Data(data) => statement.set_plain_data(data), + } + } + Ok(statement) + } +} + +impl Encode for Statement { + fn encode(&self) -> Vec { + self.encoded(false) + } +} + +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +/// Result returned by `Statement::verify_signature` +pub enum SignatureVerificationResult { + /// Signature is valid and matches this account id. + Valid(AccountId), + /// Signature has failed verification. + Invalid, + /// No signature in the proof or no proof. + NoSignature, +} + +impl Statement { + /// Create a new empty statement with no proof. + pub fn new() -> Statement { + Default::default() + } + + /// Create a new statement with a proof. + pub fn new_with_proof(proof: Proof) -> Statement { + let mut statement = Self::new(); + statement.set_proof(proof); + statement + } + + /// Sign with a key that matches given public key in the keystore. + /// + /// Returns `true` if signing worked (private key present etc). + /// + /// NOTE: This can only be called from the runtime. + pub fn sign_sr25519_public(&mut self, key: &sr25519::Public) -> bool { + let to_sign = self.signature_material(); + if let Some(signature) = key.sign(&to_sign) { + let proof = Proof::Sr25519 { + signature: signature.into_inner().into(), + signer: key.clone().into_inner().into(), + }; + self.set_proof(proof); + true + } else { + false + } + } + + /// Sign with a given private key and add the signature proof field. + #[cfg(feature = "std")] + pub fn sign_sr25519_private(&mut self, key: &sp_core::sr25519::Pair) { + let to_sign = self.signature_material(); + let proof = + Proof::Sr25519 { signature: key.sign(&to_sign).into(), signer: key.public().into() }; + self.set_proof(proof); + } + + /// Sign with a key that matches given public key in the keystore. + /// + /// Returns `true` if signing worked (private key present etc). + /// + /// NOTE: This can only be called from the runtime. + pub fn sign_ed25519_public(&mut self, key: &ed25519::Public) -> bool { + let to_sign = self.signature_material(); + if let Some(signature) = key.sign(&to_sign) { + let proof = Proof::Ed25519 { + signature: signature.into_inner().into(), + signer: key.clone().into_inner().into(), + }; + self.set_proof(proof); + true + } else { + false + } + } + + /// Sign with a given private key and add the signature proof field. + #[cfg(feature = "std")] + pub fn sign_ed25519_private(&mut self, key: &sp_core::ed25519::Pair) { + let to_sign = self.signature_material(); + let proof = + Proof::Ed25519 { signature: key.sign(&to_sign).into(), signer: key.public().into() }; + self.set_proof(proof); + } + + /// Sign with a key that matches given public key in the keystore. + /// + /// Returns `true` if signing worked (private key present etc). + /// + /// NOTE: This can only be called from the runtime. + /// + /// Returns `true` if signing worked (private key present etc). + /// + /// NOTE: This can only be called from the runtime. + pub fn sign_ecdsa_public(&mut self, key: &ecdsa::Public) -> bool { + let to_sign = self.signature_material(); + if let Some(signature) = key.sign(&to_sign) { + let proof = Proof::Secp256k1Ecdsa { + signature: signature.into_inner().into(), + signer: key.clone().into_inner().0, + }; + self.set_proof(proof); + true + } else { + false + } + } + + /// Sign with a given private key and add the signature proof field. + #[cfg(feature = "std")] + pub fn sign_ecdsa_private(&mut self, key: &sp_core::ecdsa::Pair) { + let to_sign = self.signature_material(); + let proof = + Proof::Secp256k1Ecdsa { signature: key.sign(&to_sign).into(), signer: key.public().0 }; + self.set_proof(proof); + } + + /// Check proof signature, if any. + pub fn verify_signature(&self) -> SignatureVerificationResult { + use sp_runtime::traits::Verify; + + match self.proof() { + Some(Proof::OnChain { .. }) | None => SignatureVerificationResult::NoSignature, + Some(Proof::Sr25519 { signature, signer }) => { + let to_sign = self.signature_material(); + let signature = sp_core::sr25519::Signature(*signature); + let public = sp_core::sr25519::Public(*signer); + if signature.verify(to_sign.as_slice(), &public) { + SignatureVerificationResult::Valid(*signer) + } else { + SignatureVerificationResult::Invalid + } + }, + Some(Proof::Ed25519 { signature, signer }) => { + let to_sign = self.signature_material(); + let signature = sp_core::ed25519::Signature(*signature); + let public = sp_core::ed25519::Public(*signer); + if signature.verify(to_sign.as_slice(), &public) { + SignatureVerificationResult::Valid(*signer) + } else { + SignatureVerificationResult::Invalid + } + }, + Some(Proof::Secp256k1Ecdsa { signature, signer }) => { + let to_sign = self.signature_material(); + let signature = sp_core::ecdsa::Signature(*signature); + let public = sp_core::ecdsa::Public(*signer); + if signature.verify(to_sign.as_slice(), &public) { + let sender_hash = + ::hash(signer); + SignatureVerificationResult::Valid(sender_hash.into()) + } else { + SignatureVerificationResult::Invalid + } + }, + } + } + + /// Calculate statement hash. + #[cfg(feature = "std")] + pub fn hash(&self) -> [u8; 32] { + self.using_encoded(hash_encoded) + } + + /// Returns a topic by topic index. + pub fn topic(&self, index: usize) -> Option { + if index < self.num_topics as usize { + Some(self.topics[index]) + } else { + None + } + } + + /// Returns decryption key if any. + pub fn decryption_key(&self) -> Option { + self.decryption_key + } + + /// Convert to internal data. + pub fn into_data(self) -> Option> { + self.data + } + + /// Get a reference to the statement proof, if any. + pub fn proof(&self) -> Option<&Proof> { + self.proof.as_ref() + } + + /// Get proof account id, if any + pub fn account_id(&self) -> Option { + self.proof.as_ref().map(Proof::account_id) + } + + /// Get plain data. + pub fn data(&self) -> Option<&Vec> { + self.data.as_ref() + } + + /// Get plain data len. + pub fn data_len(&self) -> usize { + self.data().map_or(0, Vec::len) + } + + /// Get channel, if any. + pub fn channel(&self) -> Option { + self.channel + } + + /// Get priority, if any. + pub fn priority(&self) -> Option { + self.priority + } + + /// Return encoded fields that can be signed to construct or verify a proof + fn signature_material(&self) -> Vec { + self.encoded(true) + } + + /// Remove the proof of this statement. + pub fn remove_proof(&mut self) { + self.proof = None; + } + + /// Set statement proof. Any existing proof is overwritten. + pub fn set_proof(&mut self, proof: Proof) { + self.proof = Some(proof) + } + + /// Set statement priority. + pub fn set_priority(&mut self, priority: u32) { + self.priority = Some(priority) + } + + /// Set statement channel. + pub fn set_channel(&mut self, channel: Channel) { + self.channel = Some(channel) + } + + /// Set topic by index. Does noting if index is over `MAX_TOPICS`. + pub fn set_topic(&mut self, index: usize, topic: Topic) { + if index < MAX_TOPICS { + self.topics[index] = topic; + self.num_topics = self.num_topics.max(index as u8 + 1); + } + } + + /// Set decryption key. + pub fn set_decryption_key(&mut self, key: DecryptionKey) { + self.decryption_key = Some(key); + } + + /// Set unencrypted statement data. + pub fn set_plain_data(&mut self, data: Vec) { + self.data = Some(data) + } + + fn encoded(&self, for_signing: bool) -> Vec { + // Encoding matches that of Vec. Basically this just means accepting that there + // will be a prefix of vector length. + let num_fields = if !for_signing && self.proof.is_some() { 1 } else { 0 } + + if self.decryption_key.is_some() { 1 } else { 0 } + + if self.priority.is_some() { 1 } else { 0 } + + if self.channel.is_some() { 1 } else { 0 } + + if self.data.is_some() { 1 } else { 0 } + + self.num_topics as u32; + + let mut output = Vec::new(); + // When encoding signature payload, the length prefix is omitted. + // This is so that the signature for encoded statement can potentially be derived without + // needing to re-encode the statement. + if !for_signing { + let compact_len = codec::Compact::(num_fields); + compact_len.encode_to(&mut output); + + if let Some(proof) = &self.proof { + 0u8.encode_to(&mut output); + proof.encode_to(&mut output); + } + } + if let Some(decryption_key) = &self.decryption_key { + 1u8.encode_to(&mut output); + decryption_key.encode_to(&mut output); + } + if let Some(priority) = &self.priority { + 2u8.encode_to(&mut output); + priority.encode_to(&mut output); + } + if let Some(channel) = &self.channel { + 3u8.encode_to(&mut output); + channel.encode_to(&mut output); + } + for t in 0..self.num_topics { + (4u8 + t).encode_to(&mut output); + self.topics[t as usize].encode_to(&mut output); + } + if let Some(data) = &self.data { + 8u8.encode_to(&mut output); + data.encode_to(&mut output); + } + output + } + + /// Encrypt give data with given key and store both in the statements. + #[cfg(feature = "std")] + pub fn encrypt( + &mut self, + data: &[u8], + key: &sp_core::ed25519::Public, + ) -> core::result::Result<(), ecies::Error> { + let encrypted = ecies::encrypt_ed25519(key, data)?; + self.data = Some(encrypted); + self.decryption_key = Some((*key).into()); + Ok(()) + } + + /// Decrypt data (if any) with the given private key. + #[cfg(feature = "std")] + pub fn decrypt_private( + &self, + key: &sp_core::ed25519::Pair, + ) -> core::result::Result>, ecies::Error> { + self.data.as_ref().map(|d| ecies::decrypt_ed25519(key, d)).transpose() + } +} + +#[cfg(test)] +mod test { + use crate::{hash_encoded, Field, Proof, SignatureVerificationResult, Statement}; + use codec::{Decode, Encode}; + use sp_application_crypto::Pair; + + #[test] + fn statement_encoding_matches_vec() { + let mut statement = Statement::new(); + assert!(statement.proof().is_none()); + let proof = Proof::OnChain { who: [42u8; 32], block_hash: [24u8; 32], event_index: 66 }; + + let decryption_key = [0xde; 32]; + let topic1 = [0x01; 32]; + let topic2 = [0x02; 32]; + let data = vec![55, 99]; + let priority = 999; + let channel = [0xcc; 32]; + + statement.set_proof(proof.clone()); + statement.set_decryption_key(decryption_key); + statement.set_priority(priority); + statement.set_channel(channel); + statement.set_topic(0, topic1); + statement.set_topic(1, topic2); + statement.set_plain_data(data.clone()); + + statement.set_topic(5, [0x55; 32]); + assert_eq!(statement.topic(5), None); + + let fields = vec![ + Field::AuthenticityProof(proof.clone()), + Field::DecryptionKey(decryption_key), + Field::Priority(priority), + Field::Channel(channel), + Field::Topic1(topic1), + Field::Topic2(topic2), + Field::Data(data.clone()), + ]; + + let encoded = statement.encode(); + assert_eq!(statement.hash(), hash_encoded(&encoded)); + assert_eq!(encoded, fields.encode()); + + let decoded = Statement::decode(&mut encoded.as_slice()).unwrap(); + assert_eq!(decoded, statement); + } + + #[test] + fn decode_checks_fields() { + let topic1 = [0x01; 32]; + let topic2 = [0x02; 32]; + let priority = 999; + + let fields = vec![ + Field::Priority(priority), + Field::Topic1(topic1), + Field::Topic1(topic1), + Field::Topic2(topic2), + ] + .encode(); + + assert!(Statement::decode(&mut fields.as_slice()).is_err()); + + let fields = + vec![Field::Topic1(topic1), Field::Priority(priority), Field::Topic2(topic2)].encode(); + + assert!(Statement::decode(&mut fields.as_slice()).is_err()); + } + + #[test] + fn sign_and_verify() { + let mut statement = Statement::new(); + statement.set_plain_data(vec![42]); + + let sr25519_kp = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap(); + let ed25519_kp = sp_core::ed25519::Pair::from_string("//Alice", None).unwrap(); + let secp256k1_kp = sp_core::ecdsa::Pair::from_string("//Alice", None).unwrap(); + + statement.sign_sr25519_private(&sr25519_kp); + assert_eq!( + statement.verify_signature(), + SignatureVerificationResult::Valid(sr25519_kp.public().0) + ); + + statement.sign_ed25519_private(&ed25519_kp); + assert_eq!( + statement.verify_signature(), + SignatureVerificationResult::Valid(ed25519_kp.public().0) + ); + + statement.sign_ecdsa_private(&secp256k1_kp); + assert_eq!( + statement.verify_signature(), + SignatureVerificationResult::Valid(sp_core::hashing::blake2_256( + &secp256k1_kp.public().0 + )) + ); + + // set an invalid signature + statement.set_proof(Proof::Sr25519 { signature: [0u8; 64], signer: [0u8; 32] }); + assert_eq!(statement.verify_signature(), SignatureVerificationResult::Invalid); + + statement.remove_proof(); + assert_eq!(statement.verify_signature(), SignatureVerificationResult::NoSignature); + } + + #[test] + fn encrypt_decrypt() { + let mut statement = Statement::new(); + let (pair, _) = sp_core::ed25519::Pair::generate(); + let plain = b"test data".to_vec(); + + //let sr25519_kp = sp_core::sr25519::Pair::from_string("//Alice", None).unwrap(); + statement.encrypt(&plain, &pair.public()).unwrap(); + assert_ne!(plain.as_slice(), statement.data().unwrap().as_slice()); + + let decrypted = statement.decrypt_private(&pair).unwrap(); + assert_eq!(decrypted, Some(plain)); + } +} diff --git a/primitives/statement-store/src/runtime_api.rs b/primitives/statement-store/src/runtime_api.rs new file mode 100644 index 000000000000..13f88bc977e9 --- /dev/null +++ b/primitives/statement-store/src/runtime_api.rs @@ -0,0 +1,187 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Runtime support for the statement store. + +use crate::{Hash, Statement, Topic}; +use codec::{Decode, Encode}; +use scale_info::TypeInfo; +use sp_runtime::RuntimeDebug; +use sp_runtime_interface::{pass_by::PassByEnum, runtime_interface}; +use sp_std::vec::Vec; + +#[cfg(feature = "std")] +use sp_externalities::ExternalitiesExt; + +/// Information concerning a valid statement. +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct ValidStatement { + /// Max statement count for this account, as calculated by the runtime. + pub max_count: u32, + /// Max total data size for this account, as calculated by the runtime. + pub max_size: u32, +} + +/// An reason for an invalid statement. +#[derive(Clone, PartialEq, Eq, Encode, Decode, Copy, RuntimeDebug, TypeInfo)] +pub enum InvalidStatement { + /// Failed proof validation. + BadProof, + /// Missing proof. + NoProof, + /// Validity could not be checked because of internal error. + InternalError, +} + +/// The source of the statement. +/// +/// Depending on the source we might apply different validation schemes. +#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub enum StatementSource { + /// Statement is coming from the on-chain worker. + Chain, + /// Statement has been received from the gossip network. + Network, + /// Statement has been submitted over the local api. + Local, +} + +impl StatementSource { + /// Check if the source allows the statement to be resubmitted to the store, extending its + /// expiration date. + pub fn can_be_resubmitted(&self) -> bool { + match self { + StatementSource::Chain | StatementSource::Local => true, + StatementSource::Network => false, + } + } +} + +sp_api::decl_runtime_apis! { + /// Runtime API trait for statement validation. + pub trait ValidateStatement { + /// Validate the statement. + fn validate_statement( + source: StatementSource, + statement: Statement, + ) -> Result; + } +} + +#[cfg(feature = "std")] +sp_externalities::decl_extension! { + /// The offchain database extension that will be registered at the Substrate externalities. + pub struct StatementStoreExt(std::sync::Arc); +} + +// Host extensions for the runtime. +#[cfg(feature = "std")] +impl StatementStoreExt { + /// Create new instance of externalities extensions. + pub fn new(store: std::sync::Arc) -> Self { + Self(store) + } +} + +/// Submission result. +#[derive(Debug, Eq, PartialEq, Clone, Copy, Encode, Decode, PassByEnum)] +pub enum SubmitResult { + /// Accepted as new. + OkNew, + /// Known statement + OkKnown, + /// Statement failed validation. + Bad, + /// The store is not available. + NotAvailable, + /// Statement could not be inserted because of priority or size checks. + Full, +} + +/// Export functions for the WASM host. +#[cfg(feature = "std")] +pub type HostFunctions = (statement_store::HostFunctions,); + +/// Host interface +#[runtime_interface] +pub trait StatementStore { + /// Submit a new new statement. The statement will be broadcast to the network. + /// This is meant to be used by the offchain worker. + fn submit_statement(&mut self, statement: Statement) -> SubmitResult { + if let Some(StatementStoreExt(store)) = self.extension::() { + match store.submit(statement, StatementSource::Chain) { + crate::SubmitResult::New(_) => SubmitResult::OkNew, + crate::SubmitResult::Known => SubmitResult::OkKnown, + crate::SubmitResult::Ignored => SubmitResult::Full, + // This should not happen for `StatementSource::Chain`. An existing statement will + // be overwritten. + crate::SubmitResult::KnownExpired => SubmitResult::Bad, + crate::SubmitResult::Bad(_) => SubmitResult::Bad, + crate::SubmitResult::InternalError(_) => SubmitResult::Bad, + } + } else { + SubmitResult::NotAvailable + } + } + + /// Return all statements. + fn statements(&mut self) -> Vec<(Hash, Statement)> { + if let Some(StatementStoreExt(store)) = self.extension::() { + store.statements().unwrap_or_default() + } else { + Vec::default() + } + } + + /// Return the data of all known statements which include all topics and have no `DecryptionKey` + /// field. + fn broadcasts(&mut self, match_all_topics: &[Topic]) -> Vec> { + if let Some(StatementStoreExt(store)) = self.extension::() { + store.broadcasts(match_all_topics).unwrap_or_default() + } else { + Vec::default() + } + } + + /// Return the data of all known statements whose decryption key is identified as `dest` (this + /// will generally be the public key or a hash thereof for symmetric ciphers, or a hash of the + /// private key for symmetric ciphers). + fn posted(&mut self, match_all_topics: &[Topic], dest: [u8; 32]) -> Vec> { + if let Some(StatementStoreExt(store)) = self.extension::() { + store.posted(match_all_topics, dest).unwrap_or_default() + } else { + Vec::default() + } + } + + /// Return the decrypted data of all known statements whose decryption key is identified as + /// `dest`. The key must be available to the client. + fn posted_clear(&mut self, match_all_topics: &[Topic], dest: [u8; 32]) -> Vec> { + if let Some(StatementStoreExt(store)) = self.extension::() { + store.posted_clear(match_all_topics, dest).unwrap_or_default() + } else { + Vec::default() + } + } + + /// Remove a statement from the store by hash. + fn remove(&mut self, hash: &Hash) { + if let Some(StatementStoreExt(store)) = self.extension::() { + store.remove(hash).unwrap_or_default() + } + } +} diff --git a/primitives/statement-store/src/store_api.rs b/primitives/statement-store/src/store_api.rs new file mode 100644 index 000000000000..89daa3e963c5 --- /dev/null +++ b/primitives/statement-store/src/store_api.rs @@ -0,0 +1,90 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub use crate::runtime_api::StatementSource; +use crate::{Hash, Statement, Topic}; + +/// Statement store error. +#[derive(Debug, Eq, PartialEq, thiserror::Error)] +pub enum Error { + /// Database error. + #[error("Database error: {0:?}")] + Db(String), + /// Error decoding statement structure. + #[error("Error decoding statement: {0:?}")] + Decode(String), + /// Error making runtime call. + #[error("Error calling into the runtime")] + Runtime, +} + +#[derive(Debug, PartialEq, Eq)] +/// Network propagation priority. +pub enum NetworkPriority { + /// High priority. Statement should be broadcast to all peers. + High, + /// Low priority. + Low, +} + +/// Statement submission outcome +#[derive(Debug, Eq, PartialEq)] +pub enum SubmitResult { + /// Accepted as new with given score + New(NetworkPriority), + /// Known statement + Known, + /// Known statement that's already expired. + KnownExpired, + /// Priority is too low or the size is too big. + Ignored, + /// Statement failed validation. + Bad(&'static str), + /// Internal store error. + InternalError(Error), +} + +/// Result type for `Error` +pub type Result = std::result::Result; + +/// Statement store API. +pub trait StatementStore: Send + Sync { + /// Return all statements. + fn statements(&self) -> Result>; + + /// Get statement by hash. + fn statement(&self, hash: &Hash) -> Result>; + + /// Return the data of all known statements which include all topics and have no `DecryptionKey` + /// field. + fn broadcasts(&self, match_all_topics: &[Topic]) -> Result>>; + + /// Return the data of all known statements whose decryption key is identified as `dest` (this + /// will generally be the public key or a hash thereof for symmetric ciphers, or a hash of the + /// private key for symmetric ciphers). + fn posted(&self, match_all_topics: &[Topic], dest: [u8; 32]) -> Result>>; + + /// Return the decrypted data of all known statements whose decryption key is identified as + /// `dest`. The key must be available to the client. + fn posted_clear(&self, match_all_topics: &[Topic], dest: [u8; 32]) -> Result>>; + + /// Submit a statement. + fn submit(&self, statement: Statement, source: StatementSource) -> SubmitResult; + + /// Remove a statement from the store. + fn remove(&self, hash: &Hash) -> Result<()>; +} diff --git a/primitives/std/Cargo.toml b/primitives/std/Cargo.toml index 87ab1a46d877..d16b15f0e618 100644 --- a/primitives/std/Cargo.toml +++ b/primitives/std/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-std" -version = "5.0.0" +version = "8.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" diff --git a/primitives/std/src/lib.rs b/primitives/std/src/lib.rs index 6653c3d7eade..c5873b3698de 100644 --- a/primitives/std/src/lib.rs +++ b/primitives/std/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/std/with_std.rs b/primitives/std/with_std.rs index b5fa3f85ed70..b838de07a309 100644 --- a/primitives/std/with_std.rs +++ b/primitives/std/with_std.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/std/without_std.rs b/primitives/std/without_std.rs index 7a6d5851dfac..80baeae44cc9 100755 --- a/primitives/std/without_std.rs +++ b/primitives/std/without_std.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/storage/Cargo.toml b/primitives/storage/Cargo.toml index 72ebc40fc113..412a2b15e059 100644 --- a/primitives/storage/Cargo.toml +++ b/primitives/storage/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-storage" -version = "7.0.0" +version = "13.0.0" authors = ["Parity Technologies "] edition = "2021" description = "Storage related primitives" @@ -14,13 +14,25 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -impl-serde = { version = "0.4.0", optional = true } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +impl-serde = { version = "0.4.0", optional = true, default-features = false } ref-cast = "1.0.0" -serde = { version = "1.0.136", features = ["derive"], optional = true } -sp-debug-derive = { version = "5.0.0", default-features = false, path = "../debug-derive" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } +serde = { version = "1.0.163", default-features = false, features = ["derive", "alloc"], optional = true } +sp-debug-derive = { version = "8.0.0", default-features = false, path = "../debug-derive" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] -std = [ "codec/std", "impl-serde", "serde", "sp-debug-derive/std", "sp-std/std" ] +std = [ + "codec/std", + "impl-serde/std", + "serde/std", + "sp-debug-derive/std", + "sp-std/std", +] + +# Serde support without relying on std features. +serde = [ + "dep:serde", + "impl-serde", +] diff --git a/primitives/storage/src/lib.rs b/primitives/storage/src/lib.rs index 237787710a7e..f8dc40f051c2 100644 --- a/primitives/storage/src/lib.rs +++ b/primitives/storage/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,7 +21,7 @@ use core::fmt::Display; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use sp_debug_derive::RuntimeDebug; @@ -35,11 +35,11 @@ use sp_std::{ /// Storage key. #[derive(PartialEq, Eq, RuntimeDebug)] #[cfg_attr( - feature = "std", + feature = "serde", derive(Serialize, Deserialize, Hash, PartialOrd, Ord, Clone, Encode, Decode) )] pub struct StorageKey( - #[cfg_attr(feature = "std", serde(with = "impl_serde::serialize"))] pub Vec, + #[cfg_attr(feature = "serde", serde(with = "impl_serde::serialize"))] pub Vec, ); impl AsRef<[u8]> for StorageKey { @@ -100,11 +100,11 @@ impl From> for TrackedStorageKey { /// Storage key of a child trie, it contains the prefix to the key. #[derive(PartialEq, Eq, RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Hash, PartialOrd, Ord, Clone))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, Hash, PartialOrd, Ord, Clone))] #[repr(transparent)] #[derive(RefCast)] pub struct PrefixedStorageKey( - #[cfg_attr(feature = "std", serde(with = "impl_serde::serialize"))] Vec, + #[cfg_attr(feature = "serde", serde(with = "impl_serde::serialize"))] Vec, ); impl Deref for PrefixedStorageKey { @@ -142,11 +142,11 @@ impl PrefixedStorageKey { /// Storage data associated to a [`StorageKey`]. #[derive(PartialEq, Eq, RuntimeDebug)] #[cfg_attr( - feature = "std", + feature = "serde", derive(Serialize, Deserialize, Hash, PartialOrd, Ord, Clone, Encode, Decode, Default) )] pub struct StorageData( - #[cfg_attr(feature = "std", serde(with = "impl_serde::serialize"))] pub Vec, + #[cfg_attr(feature = "serde", serde(with = "impl_serde::serialize"))] pub Vec, ); /// Map of data to use in a storage, it is a collection of @@ -178,8 +178,8 @@ pub struct Storage { /// Storage change set #[derive(RuntimeDebug)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize, PartialEq, Eq))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize, PartialEq, Eq))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct StorageChangeSet { /// Block hash pub block: Hash, @@ -200,8 +200,15 @@ pub mod well_known_keys { pub const HEAP_PAGES: &[u8] = b":heappages"; /// Current extrinsic index (u32) is stored under this key. + /// + /// Encodes to `0x3a65787472696e7369635f696e646578`. pub const EXTRINSIC_INDEX: &[u8] = b":extrinsic_index"; + /// Current intra-block entropy (a universally unique `[u8; 32]` value) is stored here. + /// + /// Encodes to `0x3a696e747261626c6f636b5f656e74726f7079`. + pub const INTRABLOCK_ENTROPY: &[u8] = b":intrablock_entropy"; + /// Prefix of child storage keys. pub const CHILD_STORAGE_KEY_PREFIX: &[u8] = b":child_storage:"; @@ -240,7 +247,7 @@ pub const TRIE_VALUE_NODE_THRESHOLD: u32 = 33; /// Information related to a child state. #[derive(Debug, Clone)] -#[cfg_attr(feature = "std", derive(PartialEq, Eq, Hash, PartialOrd, Ord, Encode, Decode))] +#[cfg_attr(feature = "serde", derive(PartialEq, Eq, Hash, PartialOrd, Ord, Encode, Decode))] pub enum ChildInfo { /// This is the one used by default. ParentKeyId(ChildTrieParentKeyId), @@ -271,6 +278,7 @@ impl ChildInfo { /// Returns byte sequence (keyspace) that can be use by underlying db to isolate keys. /// This is a unique id of the child trie. The collision resistance of this value /// depends on the type of child info use. For `ChildInfo::Default` it is and need to be. + #[inline] pub fn keyspace(&self) -> &[u8] { match self { ChildInfo::ParentKeyId(..) => self.storage_key(), @@ -386,7 +394,7 @@ impl ChildType { /// to be a unique id that will be use only once. Those unique id also required to be long enough to /// avoid any unique id to be prefixed by an other unique id. #[derive(Debug, Clone)] -#[cfg_attr(feature = "std", derive(PartialEq, Eq, Hash, PartialOrd, Ord, Encode, Decode))] +#[cfg_attr(feature = "serde", derive(PartialEq, Eq, Hash, PartialOrd, Ord, Encode, Decode))] pub struct ChildTrieParentKeyId { /// Data is the storage key without prefix. data: Vec, diff --git a/primitives/test-primitives/Cargo.toml b/primitives/test-primitives/Cargo.toml index deb120104b71..8a1ae4d01e40 100644 --- a/primitives/test-primitives/Cargo.toml +++ b/primitives/test-primitives/Cargo.toml @@ -12,11 +12,13 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", features = ["derive"], optional = true } -sp-application-crypto = { version = "7.0.0", default-features = false, path = "../application-crypto" } -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", default-features = false, features = ["derive"], optional = true } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../application-crypto" } +sp-core = { version = "21.0.0", default-features = false, path = "../core" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } [features] default = [ @@ -24,8 +26,18 @@ default = [ ] std = [ "codec/std", - "serde", + "serde/std", "sp-application-crypto/std", "sp-core/std", "sp-runtime/std", + "sp-std/std", + "scale-info/std", +] + +# Serde support without relying on std features. +serde = [ + "dep:serde", + "sp-application-crypto/serde", + "sp-core/serde", + "sp-runtime/serde", ] diff --git a/primitives/test-primitives/src/lib.rs b/primitives/test-primitives/src/lib.rs index 9779fe2393c3..82bdb6967b84 100644 --- a/primitives/test-primitives/src/lib.rs +++ b/primitives/test-primitives/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,15 +26,16 @@ use sp_application_crypto::sr25519; pub use sp_core::{hash::H256, RuntimeDebug}; use sp_runtime::traits::{BlakeTwo256, Extrinsic as ExtrinsicT, Verify}; +use sp_std::vec::Vec; /// Extrinsic for test-runtime. -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug)] +#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, scale_info::TypeInfo)] pub enum Extrinsic { IncludeData(Vec), StorageChange(Vec, Option>), } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] impl serde::Serialize for Extrinsic { fn serialize(&self, seq: S) -> Result where @@ -70,7 +71,7 @@ pub type Hash = H256; /// The block number type used in this runtime. pub type BlockNumber = u64; /// Index of a transaction. -pub type Index = u64; +pub type Nonce = u64; /// The item of a block digest. pub type DigestItem = sp_runtime::generic::DigestItem; /// The digest of a block. diff --git a/primitives/timestamp/Cargo.toml b/primitives/timestamp/Cargo.toml index 27e306040f65..731a6a1b5d14 100644 --- a/primitives/timestamp/Cargo.toml +++ b/primitives/timestamp/Cargo.toml @@ -14,21 +14,17 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = { version = "0.1.57", optional = true } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -futures-timer = { version = "3.0.2", optional = true } -log = { version = "0.4.17", optional = true } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } thiserror = { version = "1.0.30", optional = true } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] std = [ "async-trait", "codec/std", - "futures-timer", - "log", "sp-inherents/std", "sp-runtime/std", "sp-std/std", diff --git a/primitives/timestamp/src/lib.rs b/primitives/timestamp/src/lib.rs index 14b06779340f..eeec73efbc8b 100644 --- a/primitives/timestamp/src/lib.rs +++ b/primitives/timestamp/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -134,10 +134,12 @@ impl From for Timestamp { #[derive(Encode, sp_runtime::RuntimeDebug)] #[cfg_attr(feature = "std", derive(Decode, thiserror::Error))] pub enum InherentError { - /// The timestamp is valid in the future. - /// This is a non-fatal-error and will not stop checking the inherents. - #[cfg_attr(feature = "std", error("Block will be valid at {0}."))] - ValidAtTimestamp(InherentType), + /// The time between the blocks is too short. + #[cfg_attr( + feature = "std", + error("The time since the last timestamp is lower than the minimum period.") + )] + TooEarly, /// The block timestamp is too far in the future #[cfg_attr(feature = "std", error("The timestamp of the block is too far in the future."))] TooFarInFuture, @@ -146,7 +148,7 @@ pub enum InherentError { impl IsFatalError for InherentError { fn is_fatal_error(&self) -> bool { match self { - InherentError::ValidAtTimestamp(_) => false, + InherentError::TooEarly => true, InherentError::TooFarInFuture => true, } } @@ -240,34 +242,8 @@ impl sp_inherents::InherentDataProvider for InherentDataProvider { identifier: &InherentIdentifier, error: &[u8], ) -> Option> { - if *identifier != INHERENT_IDENTIFIER { - return None - } - - match InherentError::try_from(&INHERENT_IDENTIFIER, error)? { - InherentError::ValidAtTimestamp(valid) => { - let max_drift = self.max_drift; - let timestamp = self.timestamp; - // halt import until timestamp is valid. - // reject when too far ahead. - if valid > timestamp + max_drift { - return Some(Err(sp_inherents::Error::Application(Box::from( - InherentError::TooFarInFuture, - )))) - } - - let diff = valid.checked_sub(timestamp).unwrap_or_default(); - log::info!( - target: "timestamp", - "halting for block {} milliseconds in the future", - diff.0, - ); - - futures_timer::Delay::new(diff.as_duration()).await; - - Some(Ok(())) - }, - o => Some(Err(sp_inherents::Error::Application(Box::from(o)))), - } + Some(Err(sp_inherents::Error::Application(Box::from(InherentError::try_from( + identifier, error, + )?)))) } } diff --git a/primitives/tracing/Cargo.toml b/primitives/tracing/Cargo.toml index 602f83c7b493..1ef0a0e73ac1 100644 --- a/primitives/tracing/Cargo.toml +++ b/primitives/tracing/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-tracing" -version = "6.0.0" +version = "10.0.0" license = "Apache-2.0" authors = ["Parity Technologies "] edition = "2021" @@ -18,8 +18,8 @@ features = ["with-tracing"] targets = ["x86_64-unknown-linux-gnu", "wasm32-unknown-unknown"] [dependencies] -sp-std = { version = "5.0.0", path = "../std", default-features = false } -codec = { version = "3.2.2", package = "parity-scale-codec", default-features = false, features = [ +sp-std = { version = "8.0.0", path = "../std", default-features = false } +codec = { version = "3.6.1", package = "parity-scale-codec", default-features = false, features = [ "derive", ] } tracing = { version = "0.1.29", default-features = false } diff --git a/primitives/tracing/src/lib.rs b/primitives/tracing/src/lib.rs index 1efae226a546..cc6518368466 100644 --- a/primitives/tracing/src/lib.rs +++ b/primitives/tracing/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/tracing/src/types.rs b/primitives/tracing/src/types.rs index e7d5abfb27d9..003787f310d8 100644 --- a/primitives/tracing/src/types.rs +++ b/primitives/tracing/src/types.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/transaction-pool/Cargo.toml b/primitives/transaction-pool/Cargo.toml index 63b34a10cd09..d65db6a1d32f 100644 --- a/primitives/transaction-pool/Cargo.toml +++ b/primitives/transaction-pool/Cargo.toml @@ -15,7 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] sp-api = { version = "4.0.0-dev", default-features = false, path = "../api" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../runtime" } [features] default = [ "std" ] diff --git a/primitives/transaction-pool/src/lib.rs b/primitives/transaction-pool/src/lib.rs index 143958f06d16..431f429e29f9 100644 --- a/primitives/transaction-pool/src/lib.rs +++ b/primitives/transaction-pool/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/transaction-pool/src/runtime_api.rs b/primitives/transaction-pool/src/runtime_api.rs index 87a0c82e9133..5d321ede40fc 100644 --- a/primitives/transaction-pool/src/runtime_api.rs +++ b/primitives/transaction-pool/src/runtime_api.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/transaction-storage-proof/Cargo.toml b/primitives/transaction-storage-proof/Cargo.toml index 565d3d8d29f1..acf2ed010f51 100644 --- a/primitives/transaction-storage-proof/Cargo.toml +++ b/primitives/transaction-storage-proof/Cargo.toml @@ -14,21 +14,19 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] async-trait = { version = "0.1.57", optional = true } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -log = { version = "0.4.17", optional = true } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -sp-core = { version = "7.0.0", optional = true, path = "../core" } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +sp-core = { version = "21.0.0", optional = true, path = "../core" } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../inherents" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } -sp-trie = { version = "7.0.0", optional = true, path = "../trie" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } +sp-trie = { version = "22.0.0", optional = true, path = "../trie" } [features] default = [ "std" ] std = [ "async-trait", "codec/std", - "log", "scale-info/std", "sp-core", "sp-inherents/std", diff --git a/primitives/transaction-storage-proof/src/lib.rs b/primitives/transaction-storage-proof/src/lib.rs index 43928c83f21e..9d540ae68d16 100644 --- a/primitives/transaction-storage-proof/src/lib.rs +++ b/primitives/transaction-storage-proof/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/trie/Cargo.toml b/primitives/trie/Cargo.toml index 78b0b5d1bbda..bf0d7ddf550a 100644 --- a/primitives/trie/Cargo.toml +++ b/primitives/trie/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-trie" -version = "7.0.0" +version = "22.0.0" authors = ["Parity Technologies "] description = "Patricia trie stuff using a parity-scale-codec node format" repository = "https://github.com/paritytech/substrate/" @@ -19,28 +19,28 @@ harness = false [dependencies] ahash = { version = "0.8.2", optional = true } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } -hashbrown = { version = "0.12.3", optional = true } -hash-db = { version = "0.15.2", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } +hashbrown = { version = "0.14.0", optional = true } +hash-db = { version = "0.16.0", default-features = false } lazy_static = { version = "1.4.0", optional = true } -memory-db = { version = "0.31.0", default-features = false } +memory-db = { version = "0.32.0", default-features = false } nohash-hasher = { version = "0.2.0", optional = true } parking_lot = { version = "0.12.1", optional = true } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } thiserror = { version = "1.0.30", optional = true } tracing = { version = "0.1.29", optional = true } -trie-db = { version = "0.24.0", default-features = false } -trie-root = { version = "0.17.0", default-features = false } -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } +trie-db = { version = "0.27.0", default-features = false } +trie-root = { version = "0.18.0", default-features = false } +sp-core = { version = "21.0.0", default-features = false, path = "../core" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } schnellru = { version = "0.2.1", optional = true } [dev-dependencies] -array-bytes = "4.1" -criterion = "0.3.3" -trie-bench = "0.33.0" -trie-standardmap = "0.15.2" -sp-runtime = { version = "7.0.0", path = "../runtime" } +array-bytes = "6.1" +criterion = "0.4.0" +trie-bench = "0.37.0" +trie-standardmap = "0.16.0" +sp-runtime = { version = "24.0.0", path = "../runtime" } [features] default = ["std"] diff --git a/primitives/trie/benches/bench.rs b/primitives/trie/benches/bench.rs index f1670b0c2fa4..35aa0b808930 100644 --- a/primitives/trie/benches/bench.rs +++ b/primitives/trie/benches/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2015-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/trie/src/cache/mod.rs b/primitives/trie/src/cache/mod.rs index 3c1e5b8d0ff0..01f08a78adcf 100644 --- a/primitives/trie/src/cache/mod.rs +++ b/primitives/trie/src/cache/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -91,9 +91,13 @@ const LOCAL_NODE_CACHE_MAX_INLINE_SIZE: usize = 512 * 1024; const LOCAL_VALUE_CACHE_MAX_INLINE_SIZE: usize = 512 * 1024; /// The maximum size of the memory allocated on the heap by the local cache, in bytes. -const LOCAL_NODE_CACHE_MAX_HEAP_SIZE: usize = 2 * 1024 * 1024; +/// +/// The size of the node cache should always be bigger than the value cache. The value +/// cache is only holding weak references to the actual values found in the nodes and +/// we account for the size of the node as part of the node cache. +const LOCAL_NODE_CACHE_MAX_HEAP_SIZE: usize = 8 * 1024 * 1024; /// Same as [`LOCAL_NODE_CACHE_MAX_HEAP_SIZE`]. -const LOCAL_VALUE_CACHE_MAX_HEAP_SIZE: usize = 4 * 1024 * 1024; +const LOCAL_VALUE_CACHE_MAX_HEAP_SIZE: usize = 2 * 1024 * 1024; /// The size of the shared cache. #[derive(Debug, Clone, Copy)] @@ -438,8 +442,8 @@ enum ValueCache<'a, H: Hasher> { impl ValueCache<'_, H> { /// Get the value for the given `key`. - fn get<'a>( - &'a mut self, + fn get( + &mut self, key: &[u8], shared_cache: &SharedTrieCache, stats: &HitStats, @@ -525,7 +529,7 @@ impl<'a, H: Hasher> TrieCache<'a, H> { /// `storage_root` is the new storage root that was obtained after finishing all operations /// using the [`TrieDBMut`](trie_db::TrieDBMut). pub fn merge_into(self, local: &LocalTrieCache, storage_root: H::Out) { - let cache = if let ValueCache::Fresh(cache) = self.value_cache { cache } else { return }; + let ValueCache::Fresh(cache) = self.value_cache else { return }; if !cache.is_empty() { let mut value_cache = local.value_cache.lock(); diff --git a/primitives/trie/src/cache/shared_cache.rs b/primitives/trie/src/cache/shared_cache.rs index 8c60d5043d06..28b3274fde11 100644 --- a/primitives/trie/src/cache/shared_cache.rs +++ b/primitives/trie/src/cache/shared_cache.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/trie/src/error.rs b/primitives/trie/src/error.rs index a781d408e994..17be556d3489 100644 --- a/primitives/trie/src/error.rs +++ b/primitives/trie/src/error.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2015-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/trie/src/lib.rs b/primitives/trie/src/lib.rs index 01650e9a376b..94155458569b 100644 --- a/primitives/trie/src/lib.rs +++ b/primitives/trie/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2015-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -51,7 +51,7 @@ pub use trie_db::{ nibble_ops, node::{NodePlan, ValuePlan}, CError, DBValue, Query, Recorder, Trie, TrieCache, TrieConfiguration, TrieDBIterator, - TrieDBKeyIterator, TrieLayout, TrieMut, TrieRecorder, + TrieDBKeyIterator, TrieDBRawIterator, TrieLayout, TrieMut, TrieRecorder, }; /// The Substrate format implementation of `TrieStream`. pub use trie_stream::TrieStream; @@ -442,6 +442,7 @@ fn keyspace_as_prefix_alloc(ks: &[u8], prefix: Prefix) -> (Vec, Option) impl<'a, DB: ?Sized, H> KeySpacedDB<'a, DB, H> { /// instantiate new keyspaced db + #[inline] pub fn new(db: &'a DB, ks: &'a [u8]) -> Self { KeySpacedDB(db, ks, PhantomData) } @@ -646,7 +647,7 @@ mod tests { count: 1000, }; let mut d = st.make(); - d.sort_by(|&(ref a, _), &(ref b, _)| a.cmp(b)); + d.sort_by(|(a, _), (b, _)| a.cmp(b)); let dr = d.iter().map(|v| (&v.0[..], &v.1[..])).collect(); check_input(&dr); } @@ -715,10 +716,7 @@ mod tests { t } - fn unpopulate_trie<'db, T: TrieConfiguration>( - t: &mut TrieDBMut<'db, T>, - v: &[(Vec, Vec)], - ) { + fn unpopulate_trie(t: &mut TrieDBMut<'_, T>, v: &[(Vec, Vec)]) { for i in v { let key: &[u8] = &i.0; t.remove(key).unwrap(); diff --git a/primitives/trie/src/node_codec.rs b/primitives/trie/src/node_codec.rs index f632320dd296..46acde77c054 100644 --- a/primitives/trie/src/node_codec.rs +++ b/primitives/trie/src/node_codec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2015-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/trie/src/node_header.rs b/primitives/trie/src/node_header.rs index f3544be65b2e..c118ee07b8cb 100644 --- a/primitives/trie/src/node_header.rs +++ b/primitives/trie/src/node_header.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2015-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/trie/src/recorder.rs b/primitives/trie/src/recorder.rs index bc67cfc28794..728dc836205b 100644 --- a/primitives/trie/src/recorder.rs +++ b/primitives/trie/src/recorder.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +25,7 @@ use codec::Encode; use hash_db::Hasher; use parking_lot::Mutex; use std::{ - collections::HashMap, + collections::{HashMap, HashSet}, marker::PhantomData, mem, ops::DerefMut, @@ -38,17 +38,43 @@ use trie_db::{RecordedForKey, TrieAccess}; const LOG_TARGET: &str = "trie-recorder"; +/// Stores all the information per transaction. +#[derive(Default)] +struct Transaction { + /// Stores transaction information about [`RecorderInner::recorded_keys`]. + /// + /// For each transaction we only store the `storage_root` and the old states per key. `None` + /// state means that the key wasn't recorded before. + recorded_keys: HashMap, Option>>, + /// Stores transaction information about [`RecorderInner::accessed_nodes`]. + /// + /// For each transaction we only store the hashes of added nodes. + accessed_nodes: HashSet, +} + /// The internals of [`Recorder`]. struct RecorderInner { /// The keys for that we have recorded the trie nodes and if we have recorded up to the value. - recorded_keys: HashMap, RecordedForKey>>, + /// + /// Mapping: `StorageRoot -> (Key -> RecordedForKey)`. + recorded_keys: HashMap, RecordedForKey>>, + + /// Currently active transactions. + transactions: Vec>, + /// The encoded nodes we accessed while recording. + /// + /// Mapping: `Hash(Node) -> Node`. accessed_nodes: HashMap>, } impl Default for RecorderInner { fn default() -> Self { - Self { recorded_keys: Default::default(), accessed_nodes: Default::default() } + Self { + recorded_keys: Default::default(), + accessed_nodes: Default::default(), + transactions: Vec::new(), + } } } @@ -83,6 +109,9 @@ impl Recorder { /// /// - `storage_root`: The storage root of the trie for which accesses are recorded. This is /// important when recording access to different tries at once (like top and child tries). + /// + /// NOTE: This locks a mutex that stays locked until the return value is dropped. + #[inline] pub fn as_trie_recorder( &self, storage_root: H::Out, @@ -134,6 +163,72 @@ impl Recorder { mem::take(&mut *self.inner.lock()); self.encoded_size_estimation.store(0, Ordering::Relaxed); } + + /// Start a new transaction. + pub fn start_transaction(&self) { + let mut inner = self.inner.lock(); + inner.transactions.push(Default::default()); + } + + /// Rollback the latest transaction. + /// + /// Returns an error if there wasn't any active transaction. + pub fn rollback_transaction(&self) -> Result<(), ()> { + let mut inner = self.inner.lock(); + + // We locked `inner` and can just update the encoded size locally and then store it back to + // the atomic. + let mut new_encoded_size_estimation = self.encoded_size_estimation.load(Ordering::Relaxed); + let transaction = inner.transactions.pop().ok_or(())?; + + transaction.accessed_nodes.into_iter().for_each(|n| { + if let Some(old) = inner.accessed_nodes.remove(&n) { + new_encoded_size_estimation = + new_encoded_size_estimation.saturating_sub(old.encoded_size()); + } + }); + + transaction.recorded_keys.into_iter().for_each(|(storage_root, keys)| { + keys.into_iter().for_each(|(k, old_state)| { + if let Some(state) = old_state { + inner.recorded_keys.entry(storage_root).or_default().insert(k, state); + } else { + inner.recorded_keys.entry(storage_root).or_default().remove(&k); + } + }); + }); + + self.encoded_size_estimation + .store(new_encoded_size_estimation, Ordering::Relaxed); + + Ok(()) + } + + /// Commit the latest transaction. + /// + /// Returns an error if there wasn't any active transaction. + pub fn commit_transaction(&self) -> Result<(), ()> { + let mut inner = self.inner.lock(); + + let transaction = inner.transactions.pop().ok_or(())?; + + if let Some(parent_transaction) = inner.transactions.last_mut() { + parent_transaction.accessed_nodes.extend(transaction.accessed_nodes); + + transaction.recorded_keys.into_iter().for_each(|(storage_root, keys)| { + keys.into_iter().for_each(|(k, old_state)| { + parent_transaction + .recorded_keys + .entry(storage_root) + .or_default() + .entry(k) + .or_insert(old_state); + }) + }); + } + + Ok(()) + } } /// The [`TrieRecorder`](trie_db::TrieRecorder) implementation. @@ -144,10 +239,54 @@ struct TrieRecorder { _phantom: PhantomData, } +impl>> TrieRecorder { + /// Update the recorded keys entry for the given `full_key`. + fn update_recorded_keys(&mut self, full_key: &[u8], access: RecordedForKey) { + let inner = self.inner.deref_mut(); + + let entry = + inner.recorded_keys.entry(self.storage_root).or_default().entry(full_key.into()); + + let key = entry.key().clone(); + + // We don't need to update the record if we only accessed the `Hash` for the given + // `full_key`. Only `Value` access can be an upgrade from `Hash`. + let entry = if matches!(access, RecordedForKey::Value) { + entry.and_modify(|e| { + if let Some(tx) = inner.transactions.last_mut() { + // Store the previous state only once per transaction. + tx.recorded_keys + .entry(self.storage_root) + .or_default() + .entry(key.clone()) + .or_insert(Some(*e)); + } + + *e = access; + }) + } else { + entry + }; + + entry.or_insert_with(|| { + if let Some(tx) = inner.transactions.last_mut() { + // The key wasn't yet recorded, so there isn't any old state. + tx.recorded_keys + .entry(self.storage_root) + .or_default() + .entry(key) + .or_insert(None); + } + + access + }); + } +} + impl>> trie_db::TrieRecorder for TrieRecorder { - fn record<'b>(&mut self, access: TrieAccess<'b, H::Out>) { + fn record(&mut self, access: TrieAccess) { let mut encoded_size_update = 0; match access { @@ -158,11 +297,17 @@ impl>> trie_db::TrieRecord "Recording node", ); - self.inner.accessed_nodes.entry(hash).or_insert_with(|| { + let inner = self.inner.deref_mut(); + + inner.accessed_nodes.entry(hash).or_insert_with(|| { let node = node_owned.to_encoded::>(); encoded_size_update += node.encoded_size(); + if let Some(tx) = inner.transactions.last_mut() { + tx.accessed_nodes.insert(hash); + } + node }); }, @@ -173,11 +318,17 @@ impl>> trie_db::TrieRecord "Recording node", ); - self.inner.accessed_nodes.entry(hash).or_insert_with(|| { + let inner = self.inner.deref_mut(); + + inner.accessed_nodes.entry(hash).or_insert_with(|| { let node = encoded_node.into_owned(); encoded_size_update += node.encoded_size(); + if let Some(tx) = inner.transactions.last_mut() { + tx.accessed_nodes.insert(hash); + } + node }); }, @@ -189,21 +340,21 @@ impl>> trie_db::TrieRecord "Recording value", ); - self.inner.accessed_nodes.entry(hash).or_insert_with(|| { + let inner = self.inner.deref_mut(); + + inner.accessed_nodes.entry(hash).or_insert_with(|| { let value = value.into_owned(); encoded_size_update += value.encoded_size(); + if let Some(tx) = inner.transactions.last_mut() { + tx.accessed_nodes.insert(hash); + } + value }); - self.inner - .recorded_keys - .entry(self.storage_root) - .or_default() - .entry(full_key.to_vec()) - .and_modify(|e| *e = RecordedForKey::Value) - .or_insert(RecordedForKey::Value); + self.update_recorded_keys(full_key, RecordedForKey::Value); }, TrieAccess::Hash { full_key } => { tracing::trace!( @@ -214,12 +365,7 @@ impl>> trie_db::TrieRecord // We don't need to update the `encoded_size_update` as the hash was already // accounted for by the recorded node that holds the hash. - self.inner - .recorded_keys - .entry(self.storage_root) - .or_default() - .entry(full_key.to_vec()) - .or_insert(RecordedForKey::Hash); + self.update_recorded_keys(full_key, RecordedForKey::Hash); }, TrieAccess::NonExisting { full_key } => { tracing::trace!( @@ -231,13 +377,7 @@ impl>> trie_db::TrieRecord // Non-existing access means we recorded all trie nodes up to the value. // Not the actual value, as it doesn't exist, but all trie nodes to know // that the value doesn't exist in the trie. - self.inner - .recorded_keys - .entry(self.storage_root) - .or_default() - .entry(full_key.to_vec()) - .and_modify(|e| *e = RecordedForKey::Value) - .or_insert(RecordedForKey::Value); + self.update_recorded_keys(full_key, RecordedForKey::Value); }, }; @@ -255,14 +395,15 @@ impl>> trie_db::TrieRecord #[cfg(test)] mod tests { - use trie_db::{Trie, TrieDBBuilder, TrieDBMutBuilder, TrieHash, TrieMut}; + use super::*; + use trie_db::{Trie, TrieDBBuilder, TrieDBMutBuilder, TrieHash, TrieMut, TrieRecorder}; type MemoryDB = crate::MemoryDB; type Layout = crate::LayoutV1; type Recorder = super::Recorder; const TEST_DATA: &[(&[u8], &[u8])] = - &[(b"key1", b"val1"), (b"key2", b"val2"), (b"key3", b"val3"), (b"key4", b"val4")]; + &[(b"key1", &[1; 64]), (b"key2", &[2; 64]), (b"key3", &[3; 64]), (b"key4", &[4; 64])]; fn create_trie() -> (MemoryDB, TrieHash) { let mut db = MemoryDB::default(); @@ -299,4 +440,271 @@ mod tests { let trie = TrieDBBuilder::::new(&memory_db, &root).build(); assert_eq!(TEST_DATA[0].1.to_vec(), trie.get(TEST_DATA[0].0).unwrap().unwrap()); } + + #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] + struct RecorderStats { + accessed_nodes: usize, + recorded_keys: usize, + estimated_size: usize, + } + + impl RecorderStats { + fn extract(recorder: &Recorder) -> Self { + let inner = recorder.inner.lock(); + + let recorded_keys = + inner.recorded_keys.iter().flat_map(|(_, keys)| keys.keys()).count(); + + Self { + recorded_keys, + accessed_nodes: inner.accessed_nodes.len(), + estimated_size: recorder.estimate_encoded_size(), + } + } + } + + #[test] + fn recorder_transactions_rollback_work() { + let (db, root) = create_trie(); + + let recorder = Recorder::default(); + let mut stats = vec![RecorderStats::default()]; + + for i in 0..4 { + recorder.start_transaction(); + { + let mut trie_recorder = recorder.as_trie_recorder(root); + let trie = TrieDBBuilder::::new(&db, &root) + .with_recorder(&mut trie_recorder) + .build(); + + assert_eq!(TEST_DATA[i].1.to_vec(), trie.get(TEST_DATA[i].0).unwrap().unwrap()); + } + stats.push(RecorderStats::extract(&recorder)); + } + + assert_eq!(4, recorder.inner.lock().transactions.len()); + + for i in 0..5 { + assert_eq!(stats[4 - i], RecorderStats::extract(&recorder)); + + let storage_proof = recorder.to_storage_proof(); + let memory_db: MemoryDB = storage_proof.into_memory_db(); + + // Check that we recorded the required data + let trie = TrieDBBuilder::::new(&memory_db, &root).build(); + + // Check that the required data is still present. + for a in 0..4 { + if a < 4 - i { + assert_eq!(TEST_DATA[a].1.to_vec(), trie.get(TEST_DATA[a].0).unwrap().unwrap()); + } else { + // All the data that we already rolled back, should be gone! + assert!(trie.get(TEST_DATA[a].0).is_err()); + } + } + + if i < 4 { + recorder.rollback_transaction().unwrap(); + } + } + + assert_eq!(0, recorder.inner.lock().transactions.len()); + } + + #[test] + fn recorder_transactions_commit_work() { + let (db, root) = create_trie(); + + let recorder = Recorder::default(); + + for i in 0..4 { + recorder.start_transaction(); + { + let mut trie_recorder = recorder.as_trie_recorder(root); + let trie = TrieDBBuilder::::new(&db, &root) + .with_recorder(&mut trie_recorder) + .build(); + + assert_eq!(TEST_DATA[i].1.to_vec(), trie.get(TEST_DATA[i].0).unwrap().unwrap()); + } + } + + let stats = RecorderStats::extract(&recorder); + assert_eq!(4, recorder.inner.lock().transactions.len()); + + for _ in 0..4 { + recorder.commit_transaction().unwrap(); + } + assert_eq!(0, recorder.inner.lock().transactions.len()); + assert_eq!(stats, RecorderStats::extract(&recorder)); + + let storage_proof = recorder.to_storage_proof(); + let memory_db: MemoryDB = storage_proof.into_memory_db(); + + // Check that we recorded the required data + let trie = TrieDBBuilder::::new(&memory_db, &root).build(); + + // Check that the required data is still present. + for i in 0..4 { + assert_eq!(TEST_DATA[i].1.to_vec(), trie.get(TEST_DATA[i].0).unwrap().unwrap()); + } + } + + #[test] + fn recorder_transactions_commit_and_rollback_work() { + let (db, root) = create_trie(); + + let recorder = Recorder::default(); + + for i in 0..2 { + recorder.start_transaction(); + { + let mut trie_recorder = recorder.as_trie_recorder(root); + let trie = TrieDBBuilder::::new(&db, &root) + .with_recorder(&mut trie_recorder) + .build(); + + assert_eq!(TEST_DATA[i].1.to_vec(), trie.get(TEST_DATA[i].0).unwrap().unwrap()); + } + } + + recorder.rollback_transaction().unwrap(); + + for i in 2..4 { + recorder.start_transaction(); + { + let mut trie_recorder = recorder.as_trie_recorder(root); + let trie = TrieDBBuilder::::new(&db, &root) + .with_recorder(&mut trie_recorder) + .build(); + + assert_eq!(TEST_DATA[i].1.to_vec(), trie.get(TEST_DATA[i].0).unwrap().unwrap()); + } + } + + recorder.rollback_transaction().unwrap(); + + assert_eq!(2, recorder.inner.lock().transactions.len()); + + for _ in 0..2 { + recorder.commit_transaction().unwrap(); + } + + assert_eq!(0, recorder.inner.lock().transactions.len()); + + let storage_proof = recorder.to_storage_proof(); + let memory_db: MemoryDB = storage_proof.into_memory_db(); + + // Check that we recorded the required data + let trie = TrieDBBuilder::::new(&memory_db, &root).build(); + + // Check that the required data is still present. + for i in 0..4 { + if i % 2 == 0 { + assert_eq!(TEST_DATA[i].1.to_vec(), trie.get(TEST_DATA[i].0).unwrap().unwrap()); + } else { + assert!(trie.get(TEST_DATA[i].0).is_err()); + } + } + } + + #[test] + fn recorder_transaction_accessed_keys_works() { + let key = TEST_DATA[0].0; + let (db, root) = create_trie(); + + let recorder = Recorder::default(); + + { + let trie_recorder = recorder.as_trie_recorder(root); + assert!(matches!(trie_recorder.trie_nodes_recorded_for_key(key), RecordedForKey::None)); + } + + recorder.start_transaction(); + { + let mut trie_recorder = recorder.as_trie_recorder(root); + let trie = TrieDBBuilder::::new(&db, &root) + .with_recorder(&mut trie_recorder) + .build(); + + assert_eq!( + sp_core::Blake2Hasher::hash(TEST_DATA[0].1), + trie.get_hash(TEST_DATA[0].0).unwrap().unwrap() + ); + assert!(matches!(trie_recorder.trie_nodes_recorded_for_key(key), RecordedForKey::Hash)); + } + + recorder.start_transaction(); + { + let mut trie_recorder = recorder.as_trie_recorder(root); + let trie = TrieDBBuilder::::new(&db, &root) + .with_recorder(&mut trie_recorder) + .build(); + + assert_eq!(TEST_DATA[0].1.to_vec(), trie.get(TEST_DATA[0].0).unwrap().unwrap()); + assert!(matches!( + trie_recorder.trie_nodes_recorded_for_key(key), + RecordedForKey::Value, + )); + } + + recorder.rollback_transaction().unwrap(); + { + let trie_recorder = recorder.as_trie_recorder(root); + assert!(matches!(trie_recorder.trie_nodes_recorded_for_key(key), RecordedForKey::Hash)); + } + + recorder.rollback_transaction().unwrap(); + { + let trie_recorder = recorder.as_trie_recorder(root); + assert!(matches!(trie_recorder.trie_nodes_recorded_for_key(key), RecordedForKey::None)); + } + + recorder.start_transaction(); + { + let mut trie_recorder = recorder.as_trie_recorder(root); + let trie = TrieDBBuilder::::new(&db, &root) + .with_recorder(&mut trie_recorder) + .build(); + + assert_eq!(TEST_DATA[0].1.to_vec(), trie.get(TEST_DATA[0].0).unwrap().unwrap()); + assert!(matches!( + trie_recorder.trie_nodes_recorded_for_key(key), + RecordedForKey::Value, + )); + } + + recorder.start_transaction(); + { + let mut trie_recorder = recorder.as_trie_recorder(root); + let trie = TrieDBBuilder::::new(&db, &root) + .with_recorder(&mut trie_recorder) + .build(); + + assert_eq!( + sp_core::Blake2Hasher::hash(TEST_DATA[0].1), + trie.get_hash(TEST_DATA[0].0).unwrap().unwrap() + ); + assert!(matches!( + trie_recorder.trie_nodes_recorded_for_key(key), + RecordedForKey::Value + )); + } + + recorder.rollback_transaction().unwrap(); + { + let trie_recorder = recorder.as_trie_recorder(root); + assert!(matches!( + trie_recorder.trie_nodes_recorded_for_key(key), + RecordedForKey::Value + )); + } + + recorder.rollback_transaction().unwrap(); + { + let trie_recorder = recorder.as_trie_recorder(root); + assert!(matches!(trie_recorder.trie_nodes_recorded_for_key(key), RecordedForKey::None)); + } + } } diff --git a/primitives/trie/src/storage_proof.rs b/primitives/trie/src/storage_proof.rs index 5351e8de6fd8..6c871d73b043 100644 --- a/primitives/trie/src/storage_proof.rs +++ b/primitives/trie/src/storage_proof.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/trie/src/trie_codec.rs b/primitives/trie/src/trie_codec.rs index d5ae9a43fb1e..f29e009c4761 100644 --- a/primitives/trie/src/trie_codec.rs +++ b/primitives/trie/src/trie_codec.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/trie/src/trie_stream.rs b/primitives/trie/src/trie_stream.rs index 435e6a986722..f57b80f978ff 100644 --- a/primitives/trie/src/trie_stream.rs +++ b/primitives/trie/src/trie_stream.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2015-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/version/Cargo.toml b/primitives/version/Cargo.toml index 7d32c540f9a1..fc59a367ba38 100644 --- a/primitives/version/Cargo.toml +++ b/primitives/version/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-version" -version = "5.0.0" +version = "22.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -14,26 +14,33 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -impl-serde = { version = "0.4.0", optional = true } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +impl-serde = { version = "0.4.0", default-features = false, optional = true } parity-wasm = { version = "0.45", optional = true } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", features = ["derive"], optional = true } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", default-features = false, features = ["derive", "alloc"], optional = true } thiserror = { version = "1.0.30", optional = true } -sp-core-hashing-proc-macro = { version = "5.0.0", path = "../core/hashing/proc-macro" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } -sp-version-proc-macro = { version = "4.0.0-dev", default-features = false, path = "proc-macro" } +sp-core-hashing-proc-macro = { version = "9.0.0", path = "../core/hashing/proc-macro" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../runtime" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } +sp-version-proc-macro = { version = "8.0.0", default-features = false, path = "proc-macro" } [features] default = ["std"] std = [ "codec/std", - "impl-serde", + "impl-serde/std", "parity-wasm", "scale-info/std", - "serde", + "serde/std", "sp-runtime/std", "sp-std/std", "thiserror", ] + +# Serde support without relying on std features. +serde = [ + "dep:serde", + "impl-serde", + "sp-runtime/serde", +] diff --git a/primitives/version/proc-macro/Cargo.toml b/primitives/version/proc-macro/Cargo.toml index abe9e579a20d..33c14c9e715e 100644 --- a/primitives/version/proc-macro/Cargo.toml +++ b/primitives/version/proc-macro/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-version-proc-macro" -version = "4.0.0-dev" +version = "8.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -16,10 +16,10 @@ targets = ["x86_64-unknown-linux-gnu"] proc-macro = true [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", features = [ "derive" ] } -proc-macro2 = "1.0.37" -quote = "1.0.10" -syn = { version = "1.0.98", features = ["full", "fold", "extra-traits", "visit"] } +codec = { package = "parity-scale-codec", version = "3.6.1", features = [ "derive" ] } +proc-macro2 = "1.0.56" +quote = "1.0.28" +syn = { version = "2.0.16", features = ["full", "fold", "extra-traits", "visit"] } [dev-dependencies] -sp-version = { version = "5.0.0", path = ".." } +sp-version = { version = "22.0.0", path = ".." } diff --git a/primitives/version/proc-macro/src/decl_runtime_version.rs b/primitives/version/proc-macro/src/decl_runtime_version.rs index 9a25adfa5fca..7ca2d9b71f60 100644 --- a/primitives/version/proc-macro/src/decl_runtime_version.rs +++ b/primitives/version/proc-macro/src/decl_runtime_version.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/version/proc-macro/src/lib.rs b/primitives/version/proc-macro/src/lib.rs index 8be18b15868f..4f9179f3196a 100644 --- a/primitives/version/proc-macro/src/lib.rs +++ b/primitives/version/proc-macro/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/version/src/embed.rs b/primitives/version/src/embed.rs index c71849238fe3..096a7009a4fc 100644 --- a/primitives/version/src/embed.rs +++ b/primitives/version/src/embed.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/primitives/version/src/lib.rs b/primitives/version/src/lib.rs index 37bb15afb29e..bd8408bb4a48 100644 --- a/primitives/version/src/lib.rs +++ b/primitives/version/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,7 +33,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; #[cfg(feature = "std")] use std::collections::HashSet; @@ -156,8 +156,8 @@ macro_rules! create_apis_vec { /// `authoring_version`, absolutely not `impl_version` since they change the semantics of the /// runtime. #[derive(Clone, PartialEq, Eq, Encode, Default, sp_runtime::RuntimeDebug, TypeInfo)] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "std", serde(rename_all = "camelCase"))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] pub struct RuntimeVersion { /// Identifies the different Substrate runtimes. There'll be at least polkadot and node. /// A different on-chain spec_name to that of the native runtime would normally result @@ -190,7 +190,7 @@ pub struct RuntimeVersion { /// List of supported API "features" along with their versions. #[cfg_attr( - feature = "std", + feature = "serde", serde( serialize_with = "apis_serialize::serialize", deserialize_with = "apis_serialize::deserialize", @@ -389,11 +389,12 @@ impl GetNativeVersion for std::sync::Arc { } } -#[cfg(feature = "std")] +#[cfg(feature = "serde")] mod apis_serialize { use super::*; use impl_serde::serialize as bytes; use serde::{de, ser::SerializeTuple, Serializer}; + use sp_std::vec::Vec; #[derive(Serialize)] struct ApiId<'a>(#[serde(serialize_with = "serialize_bytesref")] &'a super::ApiId, &'a u32); @@ -428,7 +429,7 @@ mod apis_serialize { impl<'de> de::Visitor<'de> for Visitor { type Value = ApisVec; - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + fn expecting(&self, formatter: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result { formatter.write_str("a sequence of api id and version tuples") } diff --git a/primitives/wasm-interface/Cargo.toml b/primitives/wasm-interface/Cargo.toml index 69937e6b0520..50efd6d9d22d 100644 --- a/primitives/wasm-interface/Cargo.toml +++ b/primitives/wasm-interface/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-wasm-interface" -version = "7.0.0" +version = "14.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -14,13 +14,14 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } impl-trait-for-tuples = "0.2.2" log = { version = "0.4.17", optional = true } -wasmi = { version = "0.13", optional = true } -wasmtime = { version = "1.0.0", default-features = false, optional = true } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } +wasmtime = { version = "8.0.1", default-features = false, optional = true } +anyhow = { version = "1.0.68", optional = true } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } [features] -default = [ "std" ] -std = [ "codec/std", "log", "sp-std/std", "wasmi", "wasmtime" ] +default = ["std"] +std = ["codec/std", "log", "sp-std/std", "wasmtime"] +wasmtime = ["dep:wasmtime", "anyhow"] diff --git a/primitives/wasm-interface/src/lib.rs b/primitives/wasm-interface/src/lib.rs index 1ecff5a0ce91..9d5d2bb358d5 100644 --- a/primitives/wasm-interface/src/lib.rs +++ b/primitives/wasm-interface/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,9 +21,6 @@ use sp_std::{borrow::Cow, iter::Iterator, marker::PhantomData, mem, result, vec, vec::Vec}; -#[cfg(feature = "wasmi")] -mod wasmi_impl; - #[cfg(not(all(feature = "std", feature = "wasmtime")))] #[macro_export] macro_rules! if_wasmtime_is_enabled { @@ -41,6 +38,9 @@ macro_rules! if_wasmtime_is_enabled { if_wasmtime_is_enabled! { // Reexport wasmtime so that its types are accessible from the procedural macro. pub use wasmtime; + + // Wasmtime uses anyhow types but doesn't reexport them. + pub use anyhow; } /// Result type used by traits in this crate. diff --git a/primitives/wasm-interface/src/wasmi_impl.rs b/primitives/wasm-interface/src/wasmi_impl.rs deleted file mode 100644 index 977b4fc606fa..000000000000 --- a/primitives/wasm-interface/src/wasmi_impl.rs +++ /dev/null @@ -1,80 +0,0 @@ -// 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. - -//! Implementation of conversions between Substrate and wasmi types. -use crate::{Signature, Value, ValueType}; -use sp_std::vec::Vec; - -impl From for wasmi::RuntimeValue { - fn from(value: Value) -> Self { - match value { - Value::I32(val) => Self::I32(val), - Value::I64(val) => Self::I64(val), - Value::F32(val) => Self::F32(val.into()), - Value::F64(val) => Self::F64(val.into()), - } - } -} - -impl From for Value { - fn from(value: wasmi::RuntimeValue) -> Self { - match value { - wasmi::RuntimeValue::I32(val) => Self::I32(val), - wasmi::RuntimeValue::I64(val) => Self::I64(val), - wasmi::RuntimeValue::F32(val) => Self::F32(val.into()), - wasmi::RuntimeValue::F64(val) => Self::F64(val.into()), - } - } -} - -impl From for wasmi::ValueType { - fn from(value: ValueType) -> Self { - match value { - ValueType::I32 => Self::I32, - ValueType::I64 => Self::I64, - ValueType::F32 => Self::F32, - ValueType::F64 => Self::F64, - } - } -} - -impl From for ValueType { - fn from(value: wasmi::ValueType) -> Self { - match value { - wasmi::ValueType::I32 => Self::I32, - wasmi::ValueType::I64 => Self::I64, - wasmi::ValueType::F32 => Self::F32, - wasmi::ValueType::F64 => Self::F64, - } - } -} - -impl From for wasmi::Signature { - fn from(sig: Signature) -> Self { - let args = sig.args.iter().map(|a| (*a).into()).collect::>(); - wasmi::Signature::new(args, sig.return_value.map(Into::into)) - } -} - -impl From<&wasmi::Signature> for Signature { - fn from(sig: &wasmi::Signature) -> Self { - Signature::new( - sig.params().iter().copied().map(Into::into).collect::>(), - sig.return_type().map(Into::into), - ) - } -} diff --git a/primitives/weights/Cargo.toml b/primitives/weights/Cargo.toml index 2368b913b3b2..32c0d486cb11 100644 --- a/primitives/weights/Cargo.toml +++ b/primitives/weights/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sp-weights" -version = "4.0.0" +version = "20.0.0" authors = ["Parity Technologies "] edition = "2021" license = "Apache-2.0" @@ -13,21 +13,21 @@ documentation = "https://docs.rs/sp-wasm-interface" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -serde = { version = "1.0.136", optional = true, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.163", default-features = false, optional = true, features = ["derive", "alloc"] } smallvec = "1.8.0" -sp-arithmetic = { version = "6.0.0", default-features = false, path = "../arithmetic" } -sp-core = { version = "7.0.0", default-features = false, path = "../core" } -sp-debug-derive = { version = "5.0.0", default-features = false, path = "../debug-derive" } -sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-arithmetic = { version = "16.0.0", default-features = false, path = "../arithmetic" } +sp-core = { version = "21.0.0", default-features = false, path = "../core" } +sp-debug-derive = { version = "8.0.0", default-features = false, path = "../debug-derive" } +sp-std = { version = "8.0.0", default-features = false, path = "../std" } [features] default = [ "std" ] std = [ "codec/std", "scale-info/std", - "serde", + "serde/std", "sp-arithmetic/std", "sp-core/std", "sp-debug-derive/std", @@ -36,3 +36,11 @@ std = [ # By default some types have documentation, `full-metadata-docs` allows to add documentation to # more types in the metadata. full-metadata-docs = ["scale-info/docs"] + +# Serde support without relying on std features. +serde = [ + "dep:serde", + "scale-info/serde", + "sp-arithmetic/serde", + "sp-core/serde", +] diff --git a/primitives/weights/src/lib.rs b/primitives/weights/src/lib.rs index 59852815d7ae..36cf864dd538 100644 --- a/primitives/weights/src/lib.rs +++ b/primitives/weights/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +18,9 @@ //! # Primitives for transaction weighting. #![cfg_attr(not(feature = "std"), no_std)] +// TODO remove once `OldWeight` is gone. I dont know why this is needed, maybe by one of the macros +// of `OldWeight`. +#![allow(deprecated)] extern crate self as sp_weights; @@ -26,11 +29,11 @@ mod weight_v2; use codec::{CompactAs, Decode, Encode, MaxEncodedLen}; use scale_info::TypeInfo; -#[cfg(feature = "std")] +#[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; use smallvec::SmallVec; use sp_arithmetic::{ - traits::{BaseArithmetic, SaturatedConversion, Saturating, Unsigned}, + traits::{BaseArithmetic, SaturatedConversion, Unsigned}, Perbill, }; use sp_core::Get; @@ -66,8 +69,9 @@ pub mod constants { MaxEncodedLen, TypeInfo, )] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "std", serde(transparent))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", serde(transparent))] +#[deprecated(note = "Will be removed soon; use `Weight` instead.")] pub struct OldWeight(pub u64); /// The weight of database operations that the runtime can invoke. @@ -82,17 +86,17 @@ pub struct RuntimeDbWeight { impl RuntimeDbWeight { pub fn reads(self, r: u64) -> Weight { - Weight::from_ref_time(self.read.saturating_mul(r)) + Weight::from_parts(self.read.saturating_mul(r), 0) } pub fn writes(self, w: u64) -> Weight { - Weight::from_ref_time(self.write.saturating_mul(w)) + Weight::from_parts(self.write.saturating_mul(w), 0) } pub fn reads_writes(self, r: u64, w: u64) -> Weight { let read_weight = self.read.saturating_mul(r); let write_weight = self.write.saturating_mul(w); - Weight::from_ref_time(read_weight.saturating_add(write_weight)) + Weight::from_parts(read_weight.saturating_add(write_weight), 0) } } @@ -118,9 +122,77 @@ pub struct WeightToFeeCoefficient { pub degree: u8, } -/// A list of coefficients that represent one polynomial. +impl WeightToFeeCoefficient +where + Balance: BaseArithmetic + From + Copy + Unsigned, +{ + /// Evaluate the term at `x` and saturatingly amalgamate into `result`. + /// + /// The unsigned value for the term is calculated as: + /// ```ignore + /// (frac * x^(degree) + integer * x^(degree)) + /// ``` + /// Depending on the value of `negative`, it is added or subtracted from the `result`. + pub fn saturating_eval(&self, mut result: Balance, x: Balance) -> Balance { + let power = x.saturating_pow(self.degree.into()); + + let frac = self.coeff_frac * power; // Overflow safe. + let integer = self.coeff_integer.saturating_mul(power); + // Do not add them together here to avoid an underflow. + + if self.negative { + result = result.saturating_sub(frac); + result = result.saturating_sub(integer); + } else { + result = result.saturating_add(frac); + result = result.saturating_add(integer); + } + + result + } +} + +/// A list of coefficients that represent a polynomial. pub type WeightToFeeCoefficients = SmallVec<[WeightToFeeCoefficient; 4]>; +/// A list of coefficients that represent a polynomial. +/// +/// Can be [eval](Self::eval)uated at a specific `u64` to get the fee. The evaluations happens by +/// summing up all term [results](`WeightToFeeCoefficient::saturating_eval`). The order of the +/// coefficients matters since it uses saturating arithmetic. This struct does therefore not model a +/// polynomial in the mathematical sense (polynomial ring). +/// +/// For visualization purposes, the formulas of the unsigned terms look like: +/// +/// ```ignore +/// (c[0].frac * x^(c[0].degree) + c[0].integer * x^(c[0].degree)) +/// (c[1].frac * x^(c[1].degree) + c[1].integer * x^(c[1].degree)) +/// ... +/// ``` +/// Depending on the value of `c[i].negative`, each term is added or subtracted from the result. +/// The result is initialized as zero. +pub struct FeePolynomial { + coefficients: SmallVec<[WeightToFeeCoefficient; 4]>, +} + +impl From> for FeePolynomial { + fn from(coefficients: WeightToFeeCoefficients) -> Self { + Self { coefficients } + } +} + +impl FeePolynomial +where + Balance: BaseArithmetic + From + Copy + Unsigned, +{ + /// Evaluate the polynomial at a specific `x`. + pub fn eval(&self, x: u64) -> Balance { + self.coefficients.iter().fold(Balance::zero(), |acc, term| { + term.saturating_eval(acc, Balance::saturated_from(x)) + }) + } +} + /// A trait that describes the weight to fee calculation. pub trait WeightToFee { /// The type that is returned as result from calculation. @@ -157,27 +229,8 @@ where /// This should not be overridden in most circumstances. Calculation is done in the /// `Balance` type and never overflows. All evaluation is saturating. fn weight_to_fee(weight: &Weight) -> Self::Balance { - Self::polynomial() - .iter() - .fold(Self::Balance::saturated_from(0u32), |mut acc, args| { - let w = Self::Balance::saturated_from(weight.ref_time()) - .saturating_pow(args.degree.into()); - - // The sum could get negative. Therefore we only sum with the accumulator. - // The Perbill Mul implementation is non overflowing. - let frac = args.coeff_frac * w; - let integer = args.coeff_integer.saturating_mul(w); - - if args.negative { - acc = acc.saturating_sub(frac); - acc = acc.saturating_sub(integer); - } else { - acc = acc.saturating_add(frac); - acc = acc.saturating_add(integer); - } - - acc - }) + let poly: FeePolynomial = Self::polynomial().into(); + poly.eval(weight.ref_time()) } } @@ -264,15 +317,15 @@ mod tests { #[test] fn polynomial_works() { // 100^3/2=500000 100^2*(2+1/3)=23333 700 -10000 - assert_eq!(Poly::weight_to_fee(&Weight::from_ref_time(100)), 514033); + assert_eq!(Poly::weight_to_fee(&Weight::from_parts(100, 0)), 514033); // 10123^3/2=518677865433 10123^2*(2+1/3)=239108634 70861 -10000 - assert_eq!(Poly::weight_to_fee(&Weight::from_ref_time(10_123)), 518917034928); + assert_eq!(Poly::weight_to_fee(&Weight::from_parts(10_123, 0)), 518917034928); } #[test] fn polynomial_does_not_underflow() { assert_eq!(Poly::weight_to_fee(&Weight::zero()), 0); - assert_eq!(Poly::weight_to_fee(&Weight::from_ref_time(10)), 0); + assert_eq!(Poly::weight_to_fee(&Weight::from_parts(10, 0)), 0); } #[test] @@ -283,7 +336,7 @@ mod tests { #[test] fn identity_fee_works() { assert_eq!(IdentityFee::::weight_to_fee(&Weight::zero()), 0); - assert_eq!(IdentityFee::::weight_to_fee(&Weight::from_ref_time(50)), 50); + assert_eq!(IdentityFee::::weight_to_fee(&Weight::from_parts(50, 0)), 50); assert_eq!(IdentityFee::::weight_to_fee(&Weight::MAX), Balance::max_value()); } @@ -295,20 +348,20 @@ mod tests { 0 ); assert_eq!( - ConstantMultiplier::>::weight_to_fee(&Weight::from_ref_time( - 50 + ConstantMultiplier::>::weight_to_fee(&Weight::from_parts( + 50, 0 )), 500 ); assert_eq!( - ConstantMultiplier::>::weight_to_fee(&Weight::from_ref_time( - 16 + ConstantMultiplier::>::weight_to_fee(&Weight::from_parts( + 16, 0 )), 16384 ); assert_eq!( ConstantMultiplier::>::weight_to_fee( - &Weight::from_ref_time(2) + &Weight::from_parts(2, 0) ), u128::MAX ); diff --git a/primitives/weights/src/weight_meter.rs b/primitives/weights/src/weight_meter.rs index 17c5da1502e9..3b0b21ea8799 100644 --- a/primitives/weights/src/weight_meter.rs +++ b/primitives/weights/src/weight_meter.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,18 +32,21 @@ use sp_arithmetic::Perbill; /// /// // The weight is limited to (10, 0). /// let mut meter = WeightMeter::from_limit(Weight::from_parts(10, 0)); -/// // There is enough weight remaining for an operation with (5, 0) weight. -/// assert!(meter.check_accrue(Weight::from_parts(5, 0))); -/// // There is not enough weight remaining for an operation with (6, 0) weight. -/// assert!(!meter.check_accrue(Weight::from_parts(6, 0))); +/// // There is enough weight remaining for an operation with (6, 0) weight. +/// assert!(meter.try_consume(Weight::from_parts(6, 0)).is_ok()); +/// assert_eq!(meter.remaining(), Weight::from_parts(4, 0)); +/// // There is not enough weight remaining for an operation with (5, 0) weight. +/// assert!(!meter.try_consume(Weight::from_parts(5, 0)).is_ok()); +/// // The total limit is obviously unchanged: +/// assert_eq!(meter.limit(), Weight::from_parts(10, 0)); /// ``` #[derive(Debug, Clone)] pub struct WeightMeter { /// The already consumed weight. - pub consumed: Weight, + consumed: Weight, /// The maximal consumable weight. - pub limit: Weight, + limit: Weight, } impl WeightMeter { @@ -57,6 +60,16 @@ impl WeightMeter { Self::from_limit(Weight::MAX) } + /// The already consumed weight. + pub fn consumed(&self) -> Weight { + self.consumed + } + + /// The limit can ever be accrued. + pub fn limit(&self) -> Weight { + self.limit + } + /// The remaining weight that can still be consumed. pub fn remaining(&self) -> Weight { self.limit.saturating_sub(self.consumed) @@ -65,6 +78,28 @@ impl WeightMeter { /// The ratio of consumed weight to the limit. /// /// Calculates one ratio per component and returns the largest. + /// + /// # Example + /// ```rust + /// use sp_weights::{Weight, WeightMeter}; + /// use sp_arithmetic::Perbill; + /// + /// let mut meter = WeightMeter::from_limit(Weight::from_parts(10, 20)); + /// // Nothing consumed so far: + /// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(0)); + /// meter.consume(Weight::from_parts(5, 5)); + /// // The ref-time is the larger ratio: + /// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(50)); + /// meter.consume(Weight::from_parts(1, 10)); + /// // Now the larger ratio is proof-size: + /// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(75)); + /// // Eventually it reaches 100%: + /// meter.consume(Weight::from_parts(4, 0)); + /// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100)); + /// // Saturating the second component won't change anything anymore: + /// meter.consume(Weight::from_parts(0, 5)); + /// assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100)); + /// ``` pub fn consumed_ratio(&self) -> Perbill { let time = Perbill::from_rational(self.consumed.ref_time(), self.limit.ref_time()); let pov = Perbill::from_rational(self.consumed.proof_size(), self.limit.proof_size()); @@ -72,25 +107,46 @@ impl WeightMeter { } /// Consume some weight and defensively fail if it is over the limit. Saturate in any case. + #[deprecated(note = "Use `consume` instead. Will be removed after December 2023.")] pub fn defensive_saturating_accrue(&mut self, w: Weight) { + self.consume(w); + } + + /// Consume some weight and defensively fail if it is over the limit. Saturate in any case. + pub fn consume(&mut self, w: Weight) { self.consumed.saturating_accrue(w); debug_assert!(self.consumed.all_lte(self.limit), "Weight counter overflow"); } - /// Consume the given weight after checking that it can be consumed. Otherwise do nothing. + /// Consume the given weight after checking that it can be consumed and return `true`. Otherwise + /// do nothing and return `false`. + #[deprecated(note = "Use `try_consume` instead. Will be removed after December 2023.")] pub fn check_accrue(&mut self, w: Weight) -> bool { - self.consumed.checked_add(&w).map_or(false, |test| { + self.try_consume(w).is_ok() + } + + /// Consume the given weight after checking that it can be consumed. + /// + /// Returns `Ok` if the weight can be consumed or otherwise an `Err`. + pub fn try_consume(&mut self, w: Weight) -> Result<(), ()> { + self.consumed.checked_add(&w).map_or(Err(()), |test| { if test.any_gt(self.limit) { - false + Err(()) } else { self.consumed = test; - true + Ok(()) } }) } /// Check if the given weight can be consumed. + #[deprecated(note = "Use `can_consume` instead. Will be removed after December 2023.")] pub fn can_accrue(&self, w: Weight) -> bool { + self.can_consume(w) + } + + /// Check if the given weight can be consumed. + pub fn can_consume(&self, w: Weight) -> bool { self.consumed.checked_add(&w).map_or(false, |t| t.all_lte(self.limit)) } } @@ -98,6 +154,7 @@ impl WeightMeter { #[cfg(test)] mod tests { use crate::*; + use sp_arithmetic::traits::Zero; #[test] fn weight_meter_remaining_works() { @@ -179,4 +236,52 @@ mod tests { assert!(meter.check_accrue(Weight::from_parts(0, 4))); assert_eq!(meter.consumed_ratio(), Perbill::from_percent(100)); } + + #[test] + fn try_consume_works() { + let mut meter = WeightMeter::from_limit(Weight::from_parts(10, 0)); + + assert!(meter.try_consume(Weight::from_parts(11, 0)).is_err()); + assert!(meter.consumed().is_zero(), "No modification"); + + assert!(meter.try_consume(Weight::from_parts(9, 0)).is_ok()); + assert!(meter.try_consume(Weight::from_parts(2, 0)).is_err()); + assert!(meter.try_consume(Weight::from_parts(1, 0)).is_ok()); + assert!(meter.remaining().is_zero()); + assert_eq!(meter.consumed(), Weight::from_parts(10, 0)); + } + + #[test] + fn can_consume_works() { + let mut meter = WeightMeter::from_limit(Weight::from_parts(10, 0)); + + assert!(!meter.can_consume(Weight::from_parts(11, 0))); + assert!(meter.consumed().is_zero(), "No modification"); + + assert!(meter.can_consume(Weight::from_parts(9, 0))); + meter.consume(Weight::from_parts(9, 0)); + assert!(!meter.can_consume(Weight::from_parts(2, 0))); + assert!(meter.can_consume(Weight::from_parts(1, 0))); + } + + #[test] + #[cfg(debug_assertions)] + fn consume_works() { + let mut meter = WeightMeter::from_limit(Weight::from_parts(5, 10)); + + meter.consume(Weight::from_parts(4, 0)); + assert_eq!(meter.remaining(), Weight::from_parts(1, 10)); + meter.consume(Weight::from_parts(1, 0)); + assert_eq!(meter.remaining(), Weight::from_parts(0, 10)); + meter.consume(Weight::from_parts(0, 10)); + assert_eq!(meter.consumed(), Weight::from_parts(5, 10)); + } + + #[test] + #[cfg(debug_assertions)] + #[should_panic(expected = "Weight counter overflow")] + fn consume_defensive_fail() { + let mut meter = WeightMeter::from_limit(Weight::from_parts(10, 0)); + let _ = meter.consume(Weight::from_parts(11, 0)); + } } diff --git a/primitives/weights/src/weight_v2.rs b/primitives/weights/src/weight_v2.rs index 33e61b6954a3..3946cfe42c8d 100644 --- a/primitives/weights/src/weight_v2.rs +++ b/primitives/weights/src/weight_v2.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,7 +25,7 @@ use super::*; #[derive( Encode, Decode, MaxEncodedLen, TypeInfo, Eq, PartialEq, Copy, Clone, RuntimeDebug, Default, )] -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] pub struct Weight { #[codec(compact)] /// The weight of computational time used based on some reference hardware. @@ -35,12 +35,6 @@ pub struct Weight { proof_size: u64, } -impl From for Weight { - fn from(old: OldWeight) -> Self { - Weight::from_ref_time(old.0) - } -} - impl Weight { /// Set the reference time part of the weight. pub const fn set_ref_time(mut self, c: u64) -> Self { @@ -74,6 +68,7 @@ impl Weight { &mut self.proof_size } + /// The maximal weight in all dimensions. pub const MAX: Self = Self { ref_time: u64::MAX, proof_size: u64::MAX }; /// Get the conservative min of `self` and `other` weight. @@ -102,16 +97,6 @@ impl Weight { } } - /// Construct [`Weight`] with reference time weight and 0 storage size weight. - pub const fn from_ref_time(ref_time: u64) -> Self { - Self { ref_time, proof_size: 0 } - } - - /// Construct [`Weight`] with storage size weight and 0 reference time weight. - pub const fn from_proof_size(proof_size: u64) -> Self { - Self { ref_time: 0, proof_size } - } - /// Construct [`Weight`] from weight parts, namely reference time and proof size weights. pub const fn from_parts(ref_time: u64, proof_size: u64) -> Self { Self { ref_time, proof_size } @@ -232,6 +217,38 @@ impl Weight { Some(Self { ref_time, proof_size }) } + /// Calculates how many `other` fit into `self`. + /// + /// Divides each component of `self` against the same component of `other`. Returns the minimum + /// of all those divisions. Returns `None` in case **all** components of `other` are zero. + /// + /// This returns `Some` even if some components of `other` are zero as long as there is at least + /// one non-zero component in `other`. The division for this particular component will then + /// yield the maximum value (e.g u64::MAX). This is because we assume not every operation and + /// hence each `Weight` will necessarily use each resource. + pub const fn checked_div_per_component(self, other: &Self) -> Option { + let mut all_zero = true; + let ref_time = match self.ref_time.checked_div(other.ref_time) { + Some(ref_time) => { + all_zero = false; + ref_time + }, + None => u64::MAX, + }; + let proof_size = match self.proof_size.checked_div(other.proof_size) { + Some(proof_size) => { + all_zero = false; + proof_size + }, + None => u64::MAX, + }; + if all_zero { + None + } else { + Some(if ref_time < proof_size { ref_time } else { proof_size }) + } + } + /// Try to increase `self` by `amount` via checked addition. pub fn checked_accrue(&mut self, amount: Self) -> Option<()> { self.checked_add(&amount).map(|new_self| *self = new_self) @@ -247,18 +264,32 @@ impl Weight { Self { ref_time: 0, proof_size: 0 } } - /// Constant version of Add with u64. + /// Constant version of Add for `ref_time` component with u64. + /// + /// Is only overflow safe when evaluated at compile-time. + pub const fn add_ref_time(self, scalar: u64) -> Self { + Self { ref_time: self.ref_time + scalar, proof_size: self.proof_size } + } + + /// Constant version of Add for `proof_size` component with u64. + /// + /// Is only overflow safe when evaluated at compile-time. + pub const fn add_proof_size(self, scalar: u64) -> Self { + Self { ref_time: self.ref_time, proof_size: self.proof_size + scalar } + } + + /// Constant version of Sub for `ref_time` component with u64. /// /// Is only overflow safe when evaluated at compile-time. - pub const fn add(self, scalar: u64) -> Self { - Self { ref_time: self.ref_time + scalar, proof_size: self.proof_size + scalar } + pub const fn sub_ref_time(self, scalar: u64) -> Self { + Self { ref_time: self.ref_time - scalar, proof_size: self.proof_size } } - /// Constant version of Sub with u64. + /// Constant version of Sub for `proof_size` component with u64. /// /// Is only overflow safe when evaluated at compile-time. - pub const fn sub(self, scalar: u64) -> Self { - Self { ref_time: self.ref_time - scalar, proof_size: self.proof_size - scalar } + pub const fn sub_proof_size(self, scalar: u64) -> Self { + Self { ref_time: self.ref_time, proof_size: self.proof_size - scalar } } /// Constant version of Div with u64. @@ -568,4 +599,48 @@ mod tests { assert!(weight.checked_reduce(Weight::from_parts(0, 18)).is_some()); assert!(weight.is_zero()); } + + #[test] + fn checked_div_per_component_works() { + assert_eq!( + Weight::from_parts(10, 20).checked_div_per_component(&Weight::from_parts(2, 10)), + Some(2) + ); + assert_eq!( + Weight::from_parts(10, 200).checked_div_per_component(&Weight::from_parts(2, 10)), + Some(5) + ); + assert_eq!( + Weight::from_parts(10, 200).checked_div_per_component(&Weight::from_parts(1, 10)), + Some(10) + ); + assert_eq!( + Weight::from_parts(10, 200).checked_div_per_component(&Weight::from_parts(2, 1)), + Some(5) + ); + assert_eq!( + Weight::from_parts(10, 200).checked_div_per_component(&Weight::from_parts(0, 10)), + Some(20) + ); + assert_eq!( + Weight::from_parts(10, 200).checked_div_per_component(&Weight::from_parts(1, 0)), + Some(10) + ); + assert_eq!( + Weight::from_parts(0, 200).checked_div_per_component(&Weight::from_parts(2, 3)), + Some(0) + ); + assert_eq!( + Weight::from_parts(10, 0).checked_div_per_component(&Weight::from_parts(2, 3)), + Some(0) + ); + assert_eq!( + Weight::from_parts(10, 200).checked_div_per_component(&Weight::from_parts(0, 0)), + None, + ); + assert_eq!( + Weight::from_parts(0, 0).checked_div_per_component(&Weight::from_parts(0, 0)), + None, + ); + } } diff --git a/scripts/ci/deny.toml b/scripts/ci/deny.toml index bc41f746cbb1..408a9e55bc40 100644 --- a/scripts/ci/deny.toml +++ b/scripts/ci/deny.toml @@ -1,87 +1,19 @@ -# This template contains all of the possible sections and their default values - -# Note that all fields that take a lint level have these possible values: -# * deny - An error will be produced and the check will fail -# * warn - A warning will be produced, but the check will not fail -# * allow - No warning or error will be produced, though in some cases a note -# will be - -# The values provided in this template are the default values that will be used -# when any section or field is not specified in your own configuration - -# If 1 or more target triples (and optionally, target_features) are specified, -# only the specified targets will be checked when running `cargo deny check`. -# This means, if a particular package is only ever used as a target specific -# dependency, such as, for example, the `nix` crate only being used via the -# `target_family = "unix"` configuration, that only having windows targets in -# this list would mean the nix crate, as well as any of its exclusive -# dependencies not shared by any other crates, would be ignored, as the target -# list here is effectively saying which targets you are building for. -targets = [ - # The triple can be any string, but only the target triples built in to - # rustc (as of 1.40) can be checked against actual config expressions - #{ triple = "x86_64-unknown-linux-musl" }, - # You can also specify which target_features you promise are enabled for a - # particular target. target_features are currently not validated against - # the actual valid features supported by the target architecture. - #{ triple = "wasm32-unknown-unknown", features = ["atomics"] }, -] - -# This section is considered when running `cargo deny check advisories` -# More documentation for the advisories section can be found here: -# https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html -[advisories] -# The path where the advisory database is cloned/fetched into -db-path = "~/.cargo/advisory-db" -# The url of the advisory database to use -db-url = "https://github.com/rustsec/advisory-db" -# The lint level for security vulnerabilities -vulnerability = "deny" -# The lint level for unmaintained crates -unmaintained = "warn" -# The lint level for crates that have been yanked from their source registry -yanked = "warn" -# The lint level for crates with security notices. Note that as of -# 2019-12-17 there are no security notice advisories in -# https://github.com/rustsec/advisory-db -notice = "warn" -# A list of advisory IDs to ignore. Note that ignored advisories will still -# output a note when they are encountered. -ignore = [ - #"RUSTSEC-0000-0000", -] -# Threshold for security vulnerabilities, any vulnerability with a CVSS score -# lower than the range specified will be ignored. Note that ignored advisories -# will still output a note when they are encountered. -# * None - CVSS Score 0.0 -# * Low - CVSS Score 0.1 - 3.9 -# * Medium - CVSS Score 4.0 - 6.9 -# * High - CVSS Score 7.0 - 8.9 -# * Critical - CVSS Score 9.0 - 10.0 -#severity-threshold = - -# This section is considered when running `cargo deny check licenses` -# More documentation for the licenses section can be found here: -# https://embarkstudios.github.io/cargo-deny/checks/licenses/cfg.html [licenses] # The lint level for crates which do not have a detectable license unlicensed = "deny" -# List of explictly allowed licenses +# List of explicitly allowed licenses # See https://spdx.org/licenses/ for list of possible licenses -# [possible values: any SPDX 3.7 short identifier (+ optional exception)]. +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. allow = [ - #"MIT", - #"Apache-2.0", - #"Apache-2.0 WITH LLVM-exception", + "MPL-2.0", ] -# List of explictly disallowed licenses +# List of explicitly disallowed licenses # See https://spdx.org/licenses/ for list of possible licenses -# [possible values: any SPDX 3.7 short identifier (+ optional exception)]. +# [possible values: any SPDX 3.11 short identifier (+ optional exception)]. deny = [ - #"Nokia", ] # Lint level for licenses considered copyleft -copyleft = "allow" +copyleft = "deny" # Blanket approval or denial for OSI-approved or FSF Free/Libre licenses # * both - The license will be approved if it is both OSI-approved *AND* FSF # * either - The license will be approved if it is either OSI-approved *OR* FSF @@ -98,13 +30,72 @@ default = "deny" # The higher the value, the more closely the license text must be to the # canonical license text of a valid SPDX license file. # [possible values: any between 0.0 and 1.0]. -confidence-threshold = 0.9 +confidence-threshold = 0.8 # Allow 1 or more licenses on a per-crate basis, so that particular licenses # aren't accepted for every possible crate as with the normal allow list exceptions = [ - # Each entry is the crate and version constraint, and its specific allow - # list - #{ allow = ["Zlib"], name = "adler32", version = "*" }, + # Each entry is the crate and version constraint, and its specific allow list + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "chain-spec-builder" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "mmr-gadget" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "node-bench" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "node-cli" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "node-inspect" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "node-template-release" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "node-testing" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-authority-discovery" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-basic-authorship" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-block-builder" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-chain-spec" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-chain-spec-derive" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-cli" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-client-api" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-client-db" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-consensus" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-consensus-aura" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-consensus-babe" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-consensus-babe-rpc" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-consensus-beefy" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-consensus-beefy-rpc" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-consensus-epochs" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-consensus-grandpa" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-consensus-grandpa-rpc" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-consensus-manual-seal" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-consensus-pow" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-consensus-slots" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-executor" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-executor-common" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-executor-wasmi" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-executor-wasmtime" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-informant" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-keystore" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-network" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-network-bitswap" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-network-common" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-network-gossip" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-network-light" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-network-sync" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-network-test" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-network-transactions" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-network-statement" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-offchain" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-peerset" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-proposer-metrics" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-rpc" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-rpc-api" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-rpc-server" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-rpc-spec-v2" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-runtime-test" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-service" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-service-test" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-state-db" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-statement-store" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-storage-monitor" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-sysinfo" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-telemetry" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-tracing" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-transaction-pool" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "sc-transaction-pool-api" }, + { allow = ["GPL-3.0 WITH Classpath-exception-2.0"], name = "subkey" }, ] # Some crates don't have (easily) machine readable licensing information, @@ -113,10 +104,8 @@ exceptions = [ [[licenses.clarify]] # The name of the crate the clarification applies to name = "ring" -# THe optional version constraint for the crate -#version = "*" # The SPDX expression for the license requirements of the crate -expression = "OpenSSL" +expression = "MIT AND ISC AND OpenSSL" # One or more files in the crate's source used as the "source of truth" for # the license expression. If the contents match, the clarification will be used # when running the license check, otherwise the clarification will be ignored @@ -126,67 +115,3 @@ license-files = [ # Each entry is a crate relative path, and the (opaque) hash of its contents { path = "LICENSE", hash = 0xbd0eed23 } ] -[[licenses.clarify]] -name = "webpki" -expression = "ISC" -license-files = [{ path = "LICENSE", hash = 0x001c7e6c }] - -[licenses.private] -# If true, ignores workspace crates that aren't published, or are only -# published to private registries -ignore = false -# One or more private registries that you might publish crates to, if a crate -# is only published to private registries, and ignore is true, the crate will -# not have its license(s) checked -registries = [ - #"https://sekretz.com/registry -] - -# This section is considered when running `cargo deny check bans`. -# More documentation about the 'bans' section can be found here: -# https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html -[bans] -# Lint level for when multiple versions of the same crate are detected -multiple-versions = "warn" -# The graph highlighting used when creating dotgraphs for crates -# with multiple versions -# * lowest-version - The path to the lowest versioned duplicate is highlighted -# * simplest-path - The path to the version with the fewest edges is highlighted -# * all - Both lowest-version and simplest-path are used -highlight = "lowest-version" -# List of crates that are allowed. Use with care! -allow = [ - #{ name = "ansi_term", version = "=0.11.0" }, -] -# List of crates to deny -deny = [ - # Each entry the name of a crate and a version range. If version is - # not specified, all versions will be matched. -] -# Certain crates/versions that will be skipped when doing duplicate detection. -skip = [ - #{ name = "ansi_term", version = "=0.11.0" }, -] -# Similarly to `skip` allows you to skip certain crates during duplicate -# detection. Unlike skip, it also includes the entire tree of transitive -# dependencies starting at the specified crate, up to a certain depth, which is -# by default infinite -skip-tree = [ - #{ name = "ansi_term", version = "=0.11.0", depth = 20 }, -] - -# This section is considered when running `cargo deny check sources`. -# More documentation about the 'sources' section can be found here: -# https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html -[sources] -# Lint level for what to happen when a crate from a crate registry that is not -# in the allow list is encountered -unknown-registry = "deny" -# Lint level for what to happen when a crate from a git repository that is not -# in the allow list is encountered -unknown-git = "warn" -# List of URLs for allowed crate registries. Defaults to the crates.io index -# if not specified. If it is specified but empty, no registries are allowed. -allow-registry = ["https://github.com/rust-lang/crates.io-index"] -# List of URLs for allowed Git repositories -allow-git = [] diff --git a/scripts/ci/docker/subkey.Dockerfile.README.md b/scripts/ci/docker/subkey.Dockerfile.README.md new file mode 100644 index 000000000000..30a5e8212150 --- /dev/null +++ b/scripts/ci/docker/subkey.Dockerfile.README.md @@ -0,0 +1,8 @@ +# The `subkey` program is a key management utility for Substrate-based blockchains. You can use the `subkey` program to perform the following tasks: + +* Generate and inspect cryptographically-secure public and private key pairs. +* Restore keys from secret phrases and raw seeds. +* Sign and verify signatures on messages. +* Sign and verify signatures for encoded transactions. +* Derive hierarchical deterministic child key pairs. +* [Documentation](https://docs.substrate.io/reference/command-line-tools/subkey/) \ No newline at end of file diff --git a/scripts/ci/docker/substrate.Dockerfile.README.md b/scripts/ci/docker/substrate.Dockerfile.README.md new file mode 100644 index 000000000000..557fd8f835d7 --- /dev/null +++ b/scripts/ci/docker/substrate.Dockerfile.README.md @@ -0,0 +1 @@ +# Substrate Docker Image \ No newline at end of file diff --git a/scripts/ci/gitlab/crate-publishing-pipeline.yml b/scripts/ci/gitlab/crate-publishing-pipeline.yml deleted file mode 100644 index 9d5303952e6e..000000000000 --- a/scripts/ci/gitlab/crate-publishing-pipeline.yml +++ /dev/null @@ -1 +0,0 @@ -default: !reference [.crate-publishing-pipeline-definitions, default] diff --git a/scripts/ci/gitlab/default-pipeline.yml b/scripts/ci/gitlab/default-pipeline.yml deleted file mode 100644 index 19f6c320c3c2..000000000000 --- a/scripts/ci/gitlab/default-pipeline.yml +++ /dev/null @@ -1 +0,0 @@ -default: !reference [.default-pipeline-definitions, default] diff --git a/scripts/ci/gitlab/pipeline/build.yml b/scripts/ci/gitlab/pipeline/build.yml index ba529569d0fc..0c04abfd5aa3 100644 --- a/scripts/ci/gitlab/pipeline/build.yml +++ b/scripts/ci/gitlab/pipeline/build.yml @@ -4,64 +4,71 @@ # PIPELINE_SCRIPTS_TAG can be found in the project variables .check-dependent-project: - stage: build + stage: build # DAG: this is artificial dependency needs: - - job: cargo-clippy - artifacts: false + - job: cargo-clippy + artifacts: false extends: - .docker-env - .test-refs-no-trigger-prs-only + variables: + RUSTFLAGS: "-D warnings" script: - git clone - --depth=1 - --branch="$PIPELINE_SCRIPTS_TAG" - https://github.com/paritytech/pipeline-scripts + --depth=1 + --branch="$PIPELINE_SCRIPTS_TAG" + https://github.com/paritytech/pipeline-scripts - ./pipeline-scripts/check_dependent_project.sh - --org paritytech - --dependent-repo "$DEPENDENT_REPO" - --github-api-token "$GITHUB_PR_TOKEN" - --extra-dependencies "$EXTRA_DEPENDENCIES" - --companion-overrides "$COMPANION_OVERRIDES" + --org paritytech + --dependent-repo "$DEPENDENT_REPO" + --github-api-token "$GITHUB_PR_TOKEN" + --extra-dependencies "$EXTRA_DEPENDENCIES" + --companion-overrides "$COMPANION_OVERRIDES" # Individual jobs are set up for each dependent project so that they can be ran in parallel. # Arguably we could generate a job for each companion in the PR's description using Gitlab's # parent-child pipelines but that's more complicated. check-dependent-polkadot: - extends: .check-dependent-project + extends: .check-dependent-project variables: - DEPENDENT_REPO: polkadot + DEPENDENT_REPO: polkadot COMPANION_OVERRIDES: | substrate: polkadot-v* polkadot: release-v* + # enable the same feature flags as polkadot's test-linux-stable + COMPANION_CHECK_COMMAND: > + cargo check --all-targets --workspace + --features=runtime-benchmarks,runtime-metrics,try-runtime rules: - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ #PRs + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ #PRs check-dependent-cumulus: - extends: .check-dependent-project + extends: .check-dependent-project variables: - DEPENDENT_REPO: cumulus - EXTRA_DEPENDENCIES: polkadot + DEPENDENT_REPO: cumulus + EXTRA_DEPENDENCIES: polkadot COMPANION_OVERRIDES: | substrate: polkadot-v* polkadot: release-v* rules: - - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ #PRs + - if: $CI_COMMIT_REF_NAME =~ /^[0-9]+$/ #PRs build-linux-substrate: - stage: build + stage: build extends: - .collect-artifacts - .docker-env - .build-refs variables: # this variable gets overriden by "rusty-cachier environment inject", use the value as default - CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" + CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" needs: - - job: test-linux-stable - artifacts: false + - job: test-linux-stable + artifacts: false before_script: + - !reference [.timestamp, before_script] - !reference [.job-switcher, before_script] - mkdir -p ./artifacts/substrate/ - !reference [.rusty-cachier, before_script] @@ -74,10 +81,10 @@ build-linux-substrate: - mv $CARGO_TARGET_DIR/release/substrate ./artifacts/substrate/. - echo -n "Substrate version = " - if [ "${CI_COMMIT_TAG}" ]; then - echo "${CI_COMMIT_TAG}" | tee ./artifacts/substrate/VERSION; + echo "${CI_COMMIT_TAG}" | tee ./artifacts/substrate/VERSION; else - ./artifacts/substrate/substrate --version | - cut -d ' ' -f 2 | tee ./artifacts/substrate/VERSION; + ./artifacts/substrate/substrate --version | + cut -d ' ' -f 2 | tee ./artifacts/substrate/VERSION; fi - sha256sum ./artifacts/substrate/substrate | tee ./artifacts/substrate/substrate.sha256 - cp -r ./scripts/ci/docker/substrate.Dockerfile ./artifacts/substrate/ @@ -86,15 +93,16 @@ build-linux-substrate: - rusty-cachier cache upload .build-subkey: - stage: build + stage: build extends: - .collect-artifacts - .docker-env - .publish-refs variables: # this variable gets overriden by "rusty-cachier environment inject", use the value as default - CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" + CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" before_script: + - !reference [.timestamp, before_script] - !reference [.job-switcher, before_script] - mkdir -p ./artifacts/subkey - !reference [.rusty-cachier, before_script] @@ -106,20 +114,21 @@ build-linux-substrate: - mv $CARGO_TARGET_DIR/release/subkey ./artifacts/subkey/. - echo -n "Subkey version = " - ./artifacts/subkey/subkey --version | - sed -n -E 's/^subkey ([0-9.]+.*)/\1/p' | - tee ./artifacts/subkey/VERSION; + sed -n -E 's/^subkey ([0-9.]+.*)/\1/p' | + tee ./artifacts/subkey/VERSION; - sha256sum ./artifacts/subkey/subkey | tee ./artifacts/subkey/subkey.sha256 - cp -r ./scripts/ci/docker/subkey.Dockerfile ./artifacts/subkey/ - rusty-cachier cache upload build-subkey-linux: - extends: .build-subkey + extends: .build-subkey build-subkey-macos: - extends: .build-subkey + extends: .build-subkey # duplicating before_script & script sections from .build-subkey hidden job # to overwrite rusty-cachier integration as it doesn't work on macos before_script: + # skip timestamp script, the osx bash doesn't support printf %()T - !reference [.job-switcher, before_script] - mkdir -p ./artifacts/subkey script: @@ -129,8 +138,8 @@ build-subkey-macos: - mv ./target/release/subkey ./artifacts/subkey/. - echo -n "Subkey version = " - ./artifacts/subkey/subkey --version | - sed -n -E 's/^subkey ([0-9.]+.*)/\1/p' | - tee ./artifacts/subkey/VERSION; + sed -n -E 's/^subkey ([0-9.]+.*)/\1/p' | + tee ./artifacts/subkey/VERSION; - sha256sum ./artifacts/subkey/subkey | tee ./artifacts/subkey/subkey.sha256 - cp -r ./scripts/ci/docker/subkey.Dockerfile ./artifacts/subkey/ after_script: [""] @@ -138,29 +147,28 @@ build-subkey-macos: - osx build-rustdoc: - stage: build + stage: build extends: - .docker-env - .test-refs variables: - SKIP_WASM_BUILD: 1 - DOC_INDEX_PAGE: "sc_service/index.html" # default redirected page - RUSTY_CACHIER_TOOLCHAIN: nightly + SKIP_WASM_BUILD: 1 + DOC_INDEX_PAGE: "sc_service/index.html" # default redirected page # this variable gets overriden by "rusty-cachier environment inject", use the value as default - CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" + CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" artifacts: - name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}-doc" - when: on_success - expire_in: 7 days + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}-doc" + when: on_success + expire_in: 7 days paths: - - ./crate-docs/ + - ./crate-docs/ # DAG: this is artificial dependency needs: - - job: cargo-clippy - artifacts: false + - job: cargo-clippy + artifacts: false script: - rusty-cachier snapshot create - - time cargo +nightly doc --locked --workspace --all-features --verbose --no-deps + - time cargo doc --locked --workspace --all-features --verbose --no-deps - rm -f $CARGO_TARGET_DIR/doc/.lock - mv $CARGO_TARGET_DIR/doc ./crate-docs # FIXME: remove me after CI image gets nonroot diff --git a/scripts/ci/gitlab/pipeline/check.yml b/scripts/ci/gitlab/pipeline/check.yml index 55f006150107..a29f31d4aa3b 100644 --- a/scripts/ci/gitlab/pipeline/check.yml +++ b/scripts/ci/gitlab/pipeline/check.yml @@ -2,56 +2,56 @@ # Here are all jobs that are executed during "check" stage check-runtime: - stage: check + stage: check extends: - .kubernetes-env - .test-refs-no-trigger-prs-only variables: - CI_IMAGE: "paritytech/tools:latest" - GITLAB_API: "https://gitlab.parity.io/api/v4" - GITHUB_API_PROJECT: "parity%2Finfrastructure%2Fgithub-api" + CI_IMAGE: "paritytech/tools:latest" + GITLAB_API: "https://gitlab.parity.io/api/v4" + GITHUB_API_PROJECT: "parity%2Finfrastructure%2Fgithub-api" script: - ./scripts/ci/gitlab/check_runtime.sh - allow_failure: true + allow_failure: true check-signed-tag: - stage: check - extends: .kubernetes-env + stage: check + extends: .kubernetes-env variables: - CI_IMAGE: "paritytech/tools:latest" + CI_IMAGE: "paritytech/tools:latest" rules: - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ - - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 script: - ./scripts/ci/gitlab/check_signed.sh test-dependency-rules: - stage: check + stage: check extends: - .kubernetes-env - .test-refs-no-trigger-prs-only variables: - CI_IMAGE: "paritytech/tools:latest" + CI_IMAGE: "paritytech/tools:latest" script: - ./scripts/ci/gitlab/ensure-deps.sh test-rust-features: - stage: check + stage: check extends: - .kubernetes-env - .test-refs-no-trigger-prs-only script: - git clone - --depth=1 - --branch="$PIPELINE_SCRIPTS_TAG" - https://github.com/paritytech/pipeline-scripts + --depth=1 + --branch="$PIPELINE_SCRIPTS_TAG" + https://github.com/paritytech/pipeline-scripts - bash ./pipeline-scripts/rust-features.sh . test-prometheus-alerting-rules: - stage: check - extends: .kubernetes-env + stage: check + extends: .kubernetes-env variables: - CI_IMAGE: "paritytech/tools:latest" + CI_IMAGE: "paritytech/tools:latest" rules: - if: $CI_PIPELINE_SOURCE == "pipeline" when: never @@ -62,4 +62,4 @@ test-prometheus-alerting-rules: script: - promtool check rules ./scripts/ci/monitoring/alerting-rules/alerting-rules.yaml - cat ./scripts/ci/monitoring/alerting-rules/alerting-rules.yaml | - promtool test rules ./scripts/ci/monitoring/alerting-rules/alerting-rule-tests.yaml + promtool test rules ./scripts/ci/monitoring/alerting-rules/alerting-rule-tests.yaml diff --git a/scripts/ci/gitlab/pipeline/publish.yml b/scripts/ci/gitlab/pipeline/publish.yml index 4eafb0a05539..c90af7ba347b 100644 --- a/scripts/ci/gitlab/pipeline/publish.yml +++ b/scripts/ci/gitlab/pipeline/publish.yml @@ -1,16 +1,15 @@ - # This file is part of .gitlab-ci.yml # Here are all jobs that are executed during "publish" stage .build-push-docker-image-common: extends: - .kubernetes-env - stage: publish + stage: publish variables: - CI_IMAGE: $BUILDAH_IMAGE - GIT_STRATEGY: none - DOCKERFILE: $PRODUCT.Dockerfile - IMAGE_NAME: docker.io/$IMAGE_PATH + CI_IMAGE: $BUILDAH_IMAGE + GIT_STRATEGY: none + DOCKERFILE: $PRODUCT.Dockerfile + IMAGE_NAME: docker.io/$IMAGE_PATH before_script: - !reference [.kubernetes-env, before_script] - cd ./artifacts/$PRODUCT/ @@ -19,20 +18,20 @@ - test -z "${VERSION}" && exit 1 script: - test "$DOCKER_USER" -a "$DOCKER_PASS" || - ( echo "no docker credentials provided"; exit 1 ) - - buildah bud - --format=docker - --build-arg VCS_REF="${CI_COMMIT_SHA}" - --build-arg BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" - --build-arg IMAGE_NAME="${IMAGE_PATH}" - --tag "$IMAGE_NAME:$VERSION" - --tag "$IMAGE_NAME:latest" - --file "$DOCKERFILE" . + ( echo "no docker credentials provided"; exit 1 ) + - $BUILDAH_COMMAND build + --format=docker + --build-arg VCS_REF="${CI_COMMIT_SHA}" + --build-arg BUILD_DATE="$(date -u '+%Y-%m-%dT%H:%M:%SZ')" + --build-arg IMAGE_NAME="${IMAGE_PATH}" + --tag "$IMAGE_NAME:$VERSION" + --tag "$IMAGE_NAME:latest" + --file "$DOCKERFILE" . - echo "$DOCKER_PASS" | - buildah login --username "$DOCKER_USER" --password-stdin docker.io - - buildah info - - buildah push --format=v2s2 "$IMAGE_NAME:$VERSION" - - buildah push --format=v2s2 "$IMAGE_NAME:latest" + buildah login --username "$DOCKER_USER" --password-stdin docker.io + - $BUILDAH_COMMAND info + - $BUILDAH_COMMAND push --format=v2s2 "$IMAGE_NAME:$VERSION" + - $BUILDAH_COMMAND push --format=v2s2 "$IMAGE_NAME:latest" after_script: - buildah logout --all - echo "SUBSTRATE_IMAGE_NAME=${IMAGE_NAME}" | tee -a ./artifacts/$PRODUCT/build.env @@ -45,10 +44,28 @@ - .publish-refs - .build-push-docker-image-common variables: - IMAGE_PATH: parity/$PRODUCT - DOCKER_USER: $Docker_Hub_User_Parity - DOCKER_PASS: $Docker_Hub_Pass_Parity + IMAGE_PATH: parity/$PRODUCT + DOCKER_USER: $Docker_Hub_User_Parity + DOCKER_PASS: $Docker_Hub_Pass_Parity +.push-docker-image-description: + stage: publish + extends: + - .kubernetes-env + variables: + CI_IMAGE: paritytech/dockerhub-description + DOCKERHUB_REPOSITORY: parity/$PRODUCT + DOCKER_USERNAME: $Docker_Hub_User_Parity + DOCKER_PASSWORD: $Docker_Hub_Pass_Parity + README_FILEPATH: $CI_PROJECT_DIR/scripts/ci/docker/$PRODUCT.Dockerfile.README.md + rules: + - if: $CI_COMMIT_REF_NAME == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push" + changes: + - scripts/ci/docker/$PRODUCT.Dockerfile.README.md + before_script: + - echo + script: + - cd / && sh entrypoint.sh # publish image to docker.io/paritypr, (e.g. for later use in zombienet testing) .build-push-image-temporary: @@ -56,82 +73,94 @@ - .build-refs - .build-push-docker-image-common variables: - IMAGE_PATH: paritypr/$PRODUCT - DOCKER_USER: $PARITYPR_USER - DOCKER_PASS: $PARITYPR_PASS + IMAGE_PATH: paritypr/$PRODUCT + DOCKER_USER: $PARITYPR_USER + DOCKER_PASS: $PARITYPR_PASS publish-docker-substrate: - extends: .build-push-docker-image + extends: .build-push-docker-image needs: - - job: build-linux-substrate - artifacts: true + - job: build-linux-substrate + artifacts: true variables: - PRODUCT: substrate + PRODUCT: substrate + +publish-docker-description-substrate: + extends: .push-docker-image-description + variables: + PRODUCT: substrate + SHORT_DESCRIPTION: "Substrate Docker Image." publish-docker-substrate-temporary: - extends: .build-push-image-temporary + extends: .build-push-image-temporary needs: - - job: build-linux-substrate - artifacts: true + - job: build-linux-substrate + artifacts: true variables: - PRODUCT: substrate + PRODUCT: substrate artifacts: reports: # this artifact is used in zombienet-tests job # https://docs.gitlab.com/ee/ci/multi_project_pipelines.html#with-variable-inheritance dotenv: ./artifacts/$PRODUCT/build.env - expire_in: 24h + expire_in: 24h publish-docker-subkey: - extends: .build-push-docker-image + extends: .build-push-docker-image needs: - - job: build-subkey-linux - artifacts: true + - job: build-subkey-linux + artifacts: true + variables: + PRODUCT: subkey + +publish-docker-description-subkey: + extends: .push-docker-image-description variables: - PRODUCT: subkey + PRODUCT: subkey + SHORT_DESCRIPTION: "The subkey program is a key management utility for Substrate-based blockchains." publish-s3-release: - stage: publish + stage: publish extends: - .publish-refs - .kubernetes-env needs: - - job: build-linux-substrate - artifacts: true - - job: build-subkey-linux - artifacts: true - image: paritytech/awscli:latest + - job: build-linux-substrate + artifacts: true + - job: build-subkey-linux + artifacts: true + image: paritytech/awscli:latest variables: - GIT_STRATEGY: none - BUCKET: "releases.parity.io" - PREFIX: "substrate/${ARCH}-${DOCKER_OS}" + GIT_STRATEGY: none + BUCKET: "releases.parity.io" + PREFIX: "substrate/${ARCH}-${DOCKER_OS}" script: - aws s3 sync ./artifacts/ s3://${BUCKET}/${PREFIX}/$(cat ./artifacts/substrate/VERSION)/ - echo "update objects in latest path" - aws s3 sync s3://${BUCKET}/${PREFIX}/$(cat ./artifacts/substrate/VERSION)/ s3://${BUCKET}/${PREFIX}/latest/ after_script: - aws s3 ls s3://${BUCKET}/${PREFIX}/latest/ - --recursive --human-readable --summarize + --recursive --human-readable --summarize publish-rustdoc: - stage: publish - extends: .kubernetes-env + stage: publish + extends: .kubernetes-env variables: - CI_IMAGE: node:16 - GIT_DEPTH: 100 - RUSTDOCS_DEPLOY_REFS: "master" + CI_IMAGE: node:16 + GIT_DEPTH: 100 + RUSTDOCS_DEPLOY_REFS: "master" rules: - if: $CI_PIPELINE_SOURCE == "pipeline" when: never - if: $CI_PIPELINE_SOURCE == "web" && $CI_COMMIT_REF_NAME == "master" - if: $CI_COMMIT_REF_NAME == "master" - - if: $CI_COMMIT_REF_NAME =~ /^monthly-20[0-9]{2}-[0-9]{2}.*$/ # to support: monthly-2021-09+1 - - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + - if: $CI_COMMIT_REF_NAME =~ /^monthly-20[0-9]{2}-[0-9]{2}.*$/ # to support: monthly-2021-09+1 + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 # `needs:` can be removed after CI image gets nonroot. In this case `needs:` stops other # artifacts from being dowloaded by this job. needs: - - job: build-rustdoc - artifacts: true + - job: build-rustdoc + artifacts: true script: # If $CI_COMMIT_REF_NAME doesn't match one of $RUSTDOCS_DEPLOY_REFS space-separated values, we # exit immediately. @@ -167,53 +196,75 @@ publish-rustdoc: # We don't want to mark the entire job failed if there's nothing to # publish though, hence the `|| true`. - git commit -m "___Updated docs for ${CI_COMMIT_REF_NAME}___" || - echo "___Nothing to commit___" + echo "___Nothing to commit___" - git push origin gh-pages --force after_script: - rm -rf .git/ ./* publish-draft-release: - stage: publish - image: paritytech/tools:latest + stage: publish + image: paritytech/tools:latest rules: - if: $CI_COMMIT_REF_NAME =~ /^ci-release-.*$/ - - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 + - if: $CI_COMMIT_REF_NAME =~ /^v[0-9]+\.[0-9]+.*$/ # i.e. v1.0, v2.1rc1 script: - ./scripts/ci/gitlab/publish_draft_release.sh - allow_failure: true + allow_failure: true .publish-crates-template: - stage: publish + stage: publish extends: - .crates-publishing-template - .crates-publishing-pipeline # We don't want multiple jobs racing to publish crates as it's redundant and they might overwrite # the releases of one another. Use resource_group to ensure that at most one instance of this job # is running at any given time. - resource_group: crates-publishing + resource_group: crates-publishing # crates.io currently rate limits crate publishing at 1 per minute: # https://github.com/paritytech/release-engineering/issues/123#issuecomment-1335509748 # Taking into account the 202 (as of Dec 07, 2022) publishable Substrate crates, in the worst # case, due to the rate limits alone, we'd have to wait through at least 202 minutes of delay. # Taking into account also the verification steps and extra synchronization delays after # publishing the crate, the job needs to have a much higher timeout than average. - timeout: 9h + timeout: 9h # A custom publishing environment is used for us to be able to set up protected secrets # specifically for it - environment: publish-crates + environment: publish-crates script: - rusty-cachier snapshot create - git clone - --depth 1 - --branch "$RELENG_SCRIPTS_BRANCH" - https://github.com/paritytech/releng-scripts.git + --depth 1 + --branch "$RELENG_SCRIPTS_BRANCH" + https://github.com/paritytech/releng-scripts.git - CRATESIO_TARGET_INSTANCE=default ./releng-scripts/publish-crates - rusty-cachier cache upload publish-crates: - extends: .publish-crates-template + extends: .publish-crates-template + # publish-crates should only be run if publish-crates-locally passes + needs: + - job: check-crate-publishing + artifacts: false publish-crates-manual: - extends: .publish-crates-template - when: manual - interruptible: false + extends: .publish-crates-template + when: manual + interruptible: false + +check-crate-publishing: + stage: publish + extends: + - .crates-publishing-template + - .crates-publishing-pipeline + # When lots of crates are taken into account (for example on master where all crates are tested) + # the job might take a long time, as evidenced by: + # https://gitlab.parity.io/parity/mirrors/substrate/-/jobs/2269364 + timeout: 4h + script: + - rusty-cachier snapshot create + - git clone + --depth 1 + --branch "$RELENG_SCRIPTS_BRANCH" + https://github.com/paritytech/releng-scripts.git + - CRATESIO_TARGET_INSTANCE=local ./releng-scripts/publish-crates + - rusty-cachier cache upload diff --git a/scripts/ci/gitlab/pipeline/test.yml b/scripts/ci/gitlab/pipeline/test.yml index 02e05752fd01..0a73e0b4114b 100644 --- a/scripts/ci/gitlab/pipeline/test.yml +++ b/scripts/ci/gitlab/pipeline/test.yml @@ -1,51 +1,53 @@ # This file is part of .gitlab-ci.yml # Here are all jobs that are executed during "test" stage - # It's more like a check and it belongs to the previous stage, but we want to run this job with real tests in parallel find-fail-ci-phrase: - stage: test + stage: test variables: - CI_IMAGE: "paritytech/tools:latest" - ASSERT_REGEX: "FAIL-CI" - GIT_DEPTH: 1 + CI_IMAGE: "paritytech/tools:latest" + ASSERT_REGEX: "FAIL-CI" + GIT_DEPTH: 1 extends: - .kubernetes-env script: - set +e - rg --line-number --hidden --type rust --glob '!{.git,target}' "$ASSERT_REGEX" .; exit_status=$? - if [ $exit_status -eq 0 ]; then - echo "$ASSERT_REGEX was found, exiting with 1"; - exit 1; + echo "$ASSERT_REGEX was found, exiting with 1"; + exit 1; else - echo "No $ASSERT_REGEX was found, exiting with 0"; - exit 0; + echo "No $ASSERT_REGEX was found, exiting with 0"; + exit 0; fi -cargo-deny: - stage: test +cargo-deny-licenses: + stage: test extends: - .docker-env - - .nightly-pipeline + - .test-refs + variables: + CARGO_DENY_CMD: "cargo deny --all-features check licenses -c ./scripts/ci/deny.toml" script: - rusty-cachier snapshot create - - cargo deny check --hide-inclusion-graph -c ./scripts/ci/deny.toml + - $CARGO_DENY_CMD --hide-inclusion-graph - rusty-cachier cache upload after_script: - !reference [.rusty-cachier, after_script] - echo "___The complete log is in the artifacts___" - - cargo deny check -c ./scripts/ci/deny.toml 2> deny.log + - $CARGO_DENY_CMD 2> deny.log + - if [ $CI_JOB_STATUS != 'success' ]; then + echo 'Please check license of your crate or add an exception to scripts/ci/deny.toml'; + fi artifacts: - name: $CI_COMMIT_SHORT_SHA - expire_in: 3 days - when: always + name: $CI_COMMIT_SHORT_SHA + expire_in: 3 days + when: always paths: - deny.log - # FIXME: Temporarily allow to fail. - allow_failure: true cargo-fmt: - stage: test + stage: test variables: RUSTY_CACHIER_TOOLCHAIN: nightly extends: @@ -57,48 +59,47 @@ cargo-fmt: - rusty-cachier cache upload cargo-clippy: - stage: test + stage: test # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs needs: - - job: cargo-fmt - artifacts: false - variables: - RUSTY_CACHIER_TOOLCHAIN: nightly + - job: cargo-fmt + artifacts: false extends: - .docker-env - .test-refs script: - rusty-cachier snapshot create - - SKIP_WASM_BUILD=1 env -u RUSTFLAGS cargo +nightly clippy --all-targets + - SKIP_WASM_BUILD=1 env -u RUSTFLAGS cargo clippy --locked --all-targets - rusty-cachier cache upload cargo-check-benches: - stage: test + stage: test variables: - # Override to use nightly toolchain - RUSTY_CACHIER_TOOLCHAIN: "nightly" - CI_JOB_NAME: "cargo-check-benches" + CI_JOB_NAME: "cargo-check-benches" extends: - .docker-env - .test-refs-check-benches - .collect-artifacts - .pipeline-stopper-artifacts before_script: + - !reference [.timestamp, before_script] # perform rusty-cachier operations before any further modifications to the git repo to make cargo feel cheated not so much - !reference [.rust-info-script, script] - !reference [.job-switcher, before_script] - !reference [.rusty-cachier, before_script] - !reference [.pipeline-stopper-vars, script] - # merges in the master branch on PRs - - | - export BASE=$(curl -s -H "Authorization: Bearer ${GITHUB_PR_TOKEN}" https://api.github.com/repos/paritytech/substrate/pulls/${$CI_COMMIT_REF_NAME} | jq .base.ref) - - if [ $CI_COMMIT_REF_NAME != "master" ]; then - git fetch origin +${BASE}:${BASE}; - git fetch origin +$CI_COMMIT_REF_NAME:$CI_COMMIT_REF_NAME; - git checkout ${BASE}; - git config user.email "ci@gitlab.parity.io"; - git merge $CI_COMMIT_REF_NAME --verbose --no-edit; + # merges in the master branch on PRs. skip if base is not master + - 'if [ $CI_COMMIT_REF_NAME != "master" ]; then + BASE=$(curl -s -H "Authorization: Bearer ${GITHUB_PR_TOKEN}" https://api.github.com/repos/paritytech/substrate/pulls/${CI_COMMIT_REF_NAME} | jq -r .base.ref); + printf "Merging base branch %s\n" "${BASE:=master}"; + if [ $BASE != "master" ]; then + echo "$BASE is not master, skipping merge"; + else + git config user.email "ci@gitlab.parity.io"; + git fetch origin "refs/heads/${BASE}"; + git merge --verbose --no-edit FETCH_HEAD; fi + fi' parallel: 2 script: - rusty-cachier snapshot create @@ -106,78 +107,77 @@ cargo-check-benches: # this job is executed in parallel on two runners - echo "___Running benchmarks___"; - case ${CI_NODE_INDEX} in - 1) - SKIP_WASM_BUILD=1 time cargo +nightly check --locked --benches --all; - cargo run --locked --release -p node-bench -- ::trie::read::small --json - | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::trie::read::small.json; - echo "___Uploading cache for rusty-cachier___"; - rusty-cachier cache upload - ;; - 2) - cargo run --locked --release -p node-bench -- ::node::import::native::sr25519::transfer_keep_alive::paritydb::small --json - | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::node::import::native::sr25519::transfer_keep_alive::paritydb::small.json - ;; - esac - tags: - - linux-docker-benches + 1) + SKIP_WASM_BUILD=1 time cargo check --locked --benches --all; + cargo run --locked --release -p node-bench -- ::trie::read::small --json + | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::trie::read::small.json; + echo "___Uploading cache for rusty-cachier___"; + rusty-cachier cache upload + ;; + 2) + cargo run --locked --release -p node-bench -- ::node::import::sr25519::transfer_keep_alive::paritydb::small --json + | tee ./artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA/::node::import::sr25519::transfer_keep_alive::paritydb::small.json + ;; + esac node-bench-regression-guard: # it's not belong to `build` semantically, but dag jobs can't depend on each other # within the single stage - https://gitlab.com/gitlab-org/gitlab/-/issues/30632 # more: https://github.com/paritytech/substrate/pull/8519#discussion_r608012402 - stage: build + stage: build extends: - .docker-env - .test-refs-no-trigger-prs-only needs: # this is a DAG - - job: cargo-check-benches - artifacts: true + - job: cargo-check-benches + artifacts: true # polls artifact from master to compare with current result # need to specify both parallel jobs from master because of the bug # https://gitlab.com/gitlab-org/gitlab/-/issues/39063 - - project: $CI_PROJECT_PATH - job: "cargo-check-benches 1/2" - ref: master - artifacts: true - - project: $CI_PROJECT_PATH - job: "cargo-check-benches 2/2" - ref: master - artifacts: true + - project: $CI_PROJECT_PATH + job: "cargo-check-benches 1/2" + ref: master + artifacts: true + - project: $CI_PROJECT_PATH + job: "cargo-check-benches 2/2" + ref: master + artifacts: true variables: - CI_IMAGE: "paritytech/node-bench-regression-guard:latest" - before_script: [""] + CI_IMAGE: "paritytech/node-bench-regression-guard:latest" + before_script: + - !reference [.timestamp, before_script] script: - echo "------- IMPORTANT -------" - echo "node-bench-regression-guard depends on the results of a cargo-check-benches job" - echo "In case of this job failure, check your pipeline's cargo-check-benches" - - 'node-bench-regression-guard --reference artifacts/benches/master-* - --compare-with artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA' + - "node-bench-regression-guard --reference artifacts/benches/master-* + --compare-with artifacts/benches/$CI_COMMIT_REF_NAME-$CI_COMMIT_SHORT_SHA" after_script: [""] -cargo-check-try-runtime: - stage: test +cargo-check-try-runtime-and-experimental: + stage: test extends: - .docker-env - .test-refs script: - rusty-cachier snapshot create - - time cargo check --locked --features try-runtime + - time cargo check --locked --features try-runtime,experimental - rusty-cachier cache upload test-deterministic-wasm: - stage: test + stage: test # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs needs: - - job: cargo-check-try-runtime - artifacts: false + - job: cargo-check-try-runtime-and-experimental + artifacts: false extends: - .docker-env - .test-refs variables: - WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_NO_COLOR: 1 # this variable gets overriden by "rusty-cachier environment inject", use the value as default - CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" + CARGO_TARGET_DIR: "$CI_PROJECT_DIR/target" script: - rusty-cachier snapshot create # build runtime @@ -195,7 +195,7 @@ test-deterministic-wasm: - rusty-cachier cache upload test-linux-stable: - stage: test + stage: test extends: - .docker-env - .test-refs @@ -203,68 +203,83 @@ test-linux-stable: variables: # Enable debug assertions since we are running optimized builds for testing # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - RUST_BACKTRACE: 1 - WASM_BUILD_NO_COLOR: 1 - WASM_BUILD_RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + RUSTFLAGS: "-C debug-assertions -D warnings" + RUST_BACKTRACE: 1 + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" # Ensure we run the UI tests. - RUN_UI_TESTS: 1 + RUN_UI_TESTS: 1 # needed for rusty-cachier to keep cache in test-linux-stable folder and not in test-linux-stable-1/3 - CI_JOB_NAME: "test-linux-stable" + CI_JOB_NAME: "test-linux-stable" parallel: 3 script: - rusty-cachier snapshot create # this job runs all tests in former runtime-benchmarks, frame-staking and wasmtime tests # tests are partitioned by nextest and executed in parallel on $CI_NODE_TOTAL runners - # node-cli is excluded until https://github.com/paritytech/substrate/issues/11321 fixed - echo "Node index - ${CI_NODE_INDEX}. Total amount - ${CI_NODE_TOTAL}" - time cargo nextest run --workspace - --locked - --release - --verbose - --features runtime-benchmarks - --manifest-path ./bin/node/cli/Cargo.toml - --exclude node-cli - --partition count:${CI_NODE_INDEX}/${CI_NODE_TOTAL} + --locked + --release + --verbose + --features runtime-benchmarks,try-runtime,experimental + --manifest-path ./bin/node/cli/Cargo.toml + --partition count:${CI_NODE_INDEX}/${CI_NODE_TOTAL} # we need to update cache only from one job - if [ ${CI_NODE_INDEX} == 1 ]; then rusty-cachier cache upload; fi + # Upload tests results to Elasticsearch + - echo "Upload test results to Elasticsearch" + - cat target/nextest/default/junit.xml | xq . > target/nextest/default/junit.json + - | + curl -v -XPOST --http1.1 \ + -u ${ELASTIC_USERNAME}:${ELASTIC_PASSWORD} \ + https://elasticsearch.parity-build.parity.io/unit-tests/_doc/${CI_JOB_ID} \ + -H 'Content-Type: application/json' \ + -d @target/nextest/default/junit.json || echo "failed to upload junit report" + artifacts: + when: always + paths: + - target/nextest/default/junit.xml + reports: + junit: target/nextest/default/junit.xml test-frame-support: - stage: test + stage: test extends: - .docker-env - .test-refs variables: # Enable debug assertions since we are running optimized builds for testing # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - RUST_BACKTRACE: 1 - WASM_BUILD_NO_COLOR: 1 - WASM_BUILD_RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + RUSTFLAGS: "-C debug-assertions -D warnings" + RUST_BACKTRACE: 1 + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" # Ensure we run the UI tests. - RUN_UI_TESTS: 1 + RUN_UI_TESTS: 1 script: - rusty-cachier snapshot create - - time cargo test --locked -p frame-support-test --features=frame-feature-testing,no-metadata-docs --manifest-path ./frame/support/test/Cargo.toml --test pallet # does not reuse cache 1 min 44 sec - - time cargo test --locked -p frame-support-test --features=frame-feature-testing,frame-feature-testing-2,no-metadata-docs --manifest-path ./frame/support/test/Cargo.toml --test pallet # does not reuse cache 1 min 44 sec + - cat /cargo_target_dir/debug/.fingerprint/memory_units-759eddf317490d2b/lib-memory_units.json || true + - time cargo test --verbose --locked -p frame-support-test --features=frame-feature-testing,no-metadata-docs,try-runtime,experimental --manifest-path ./frame/support/test/Cargo.toml + - time cargo test --verbose --locked -p frame-support-test --features=frame-feature-testing,frame-feature-testing-2,no-metadata-docs,try-runtime,experimental --manifest-path ./frame/support/test/Cargo.toml - SUBSTRATE_TEST_TIMEOUT=1 time cargo test -p substrate-test-utils --release --verbose --locked -- --ignored timeout + - cat /cargo_target_dir/debug/.fingerprint/memory_units-759eddf317490d2b/lib-memory_units.json || true - rusty-cachier cache upload # This job runs tests that don't work with cargo-nextest in test-linux-stable test-linux-stable-extra: - stage: test + stage: test extends: - .docker-env - .test-refs variables: # Enable debug assertions since we are running optimized builds for testing # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - RUST_BACKTRACE: 1 - WASM_BUILD_NO_COLOR: 1 - WASM_BUILD_RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + RUSTFLAGS: "-C debug-assertions -D warnings" + RUST_BACKTRACE: 1 + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" # Ensure we run the UI tests. - RUN_UI_TESTS: 1 + RUN_UI_TESTS: 1 script: - rusty-cachier snapshot create # Run node-cli tests @@ -277,45 +292,43 @@ test-linux-stable-extra: # This job runs all benchmarks defined in the `/bin/node/runtime` once to check that there are no errors. quick-benchmarks: - stage: test + stage: test extends: - .docker-env - .test-refs variables: # Enable debug assertions since we are running optimized builds for testing # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - RUST_BACKTRACE: "full" - WASM_BUILD_NO_COLOR: 1 - WASM_BUILD_RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + RUSTFLAGS: "-C debug-assertions -D warnings" + RUST_BACKTRACE: "full" + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" script: - rusty-cachier snapshot create - - time cargo run --locked --release --features runtime-benchmarks -- benchmark pallet --execution wasm --wasm-execution compiled --chain dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 + - time cargo run --locked --release --features runtime-benchmarks -- benchmark pallet --wasm-execution compiled --chain dev --pallet "*" --extrinsic "*" --steps 2 --repeat 1 - rusty-cachier cache upload test-frame-examples-compile-to-wasm: # into one job - stage: test - variables: - RUSTY_CACHIER_TOOLCHAIN: nightly + stage: test extends: - .docker-env - .test-refs variables: # Enable debug assertions since we are running optimized builds for testing # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y" + RUSTFLAGS: "-C debug-assertions" RUST_BACKTRACE: 1 script: - rusty-cachier snapshot create - cd ./frame/examples/offchain-worker/ - - cargo +nightly build --locked --target=wasm32-unknown-unknown --no-default-features + - cargo build --locked --target=wasm32-unknown-unknown --no-default-features - cd ../basic - - cargo +nightly build --locked --target=wasm32-unknown-unknown --no-default-features + - cargo build --locked --target=wasm32-unknown-unknown --no-default-features - rusty-cachier cache upload test-linux-stable-int: - stage: test + stage: test extends: - .docker-env - .test-refs @@ -323,29 +336,27 @@ test-linux-stable-int: variables: # Enable debug assertions since we are running optimized builds for testing # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" - RUST_BACKTRACE: 1 - WASM_BUILD_NO_COLOR: 1 - WASM_BUILD_RUSTFLAGS: "-Cdebug-assertions=y -Dwarnings" + RUSTFLAGS: "-C debug-assertions -D warnings" + RUST_BACKTRACE: 1 + WASM_BUILD_NO_COLOR: 1 + WASM_BUILD_RUSTFLAGS: "-C debug-assertions -D warnings" # Ensure we run the UI tests. - RUN_UI_TESTS: 1 + RUN_UI_TESTS: 1 script: - rusty-cachier snapshot create - WASM_BUILD_NO_COLOR=1 RUST_LOG=sync=trace,consensus=trace,client=trace,state-db=trace,db=trace,forks=trace,state_db=trace,storage_cache=trace - time cargo test -p node-cli --release --verbose --locked -- --ignored + time cargo test -p node-cli --release --verbose --locked -- --ignored - rusty-cachier cache upload # more information about this job can be found here: # https://github.com/paritytech/substrate/pull/6916 check-tracing: - stage: test + stage: test # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs needs: - - job: test-linux-stable-int - artifacts: false - variables: - RUSTY_CACHIER_TOOLCHAIN: nightly + - job: test-linux-stable-int + artifacts: false extends: - .docker-env - .test-refs @@ -353,53 +364,49 @@ check-tracing: script: - rusty-cachier snapshot create # with-tracing must be explicitly activated, we run a test to ensure this works as expected in both cases - - time cargo +nightly test --locked --manifest-path ./primitives/tracing/Cargo.toml --no-default-features - - time cargo +nightly test --locked --manifest-path ./primitives/tracing/Cargo.toml --no-default-features --features=with-tracing + - time cargo test --locked --manifest-path ./primitives/tracing/Cargo.toml --no-default-features + - time cargo test --locked --manifest-path ./primitives/tracing/Cargo.toml --no-default-features --features=with-tracing - rusty-cachier cache upload # more information about this job can be found here: # https://github.com/paritytech/substrate/pull/3778 test-full-crypto-feature: - stage: test + stage: test # this is an artificial job dependency, for pipeline optimization using GitLab's DAGs needs: - - job: check-tracing - artifacts: false - variables: - RUSTY_CACHIER_TOOLCHAIN: nightly + - job: check-tracing + artifacts: false extends: - .docker-env - .test-refs variables: # Enable debug assertions since we are running optimized builds for testing # but still want to have debug assertions. - RUSTFLAGS: "-Cdebug-assertions=y" + RUSTFLAGS: "-C debug-assertions" RUST_BACKTRACE: 1 script: - rusty-cachier snapshot create - cd primitives/core/ - - time cargo +nightly build --locked --verbose --no-default-features --features full_crypto + - time cargo build --locked --verbose --no-default-features --features full_crypto - cd ../application-crypto - - time cargo +nightly build --locked --verbose --no-default-features --features full_crypto + - time cargo build --locked --verbose --no-default-features --features full_crypto - rusty-cachier cache upload check-rustdoc: - stage: test - variables: - RUSTY_CACHIER_TOOLCHAIN: nightly + stage: test extends: - .docker-env - .test-refs variables: - SKIP_WASM_BUILD: 1 - RUSTDOCFLAGS: "-Dwarnings" + SKIP_WASM_BUILD: 1 + RUSTDOCFLAGS: "-Dwarnings" script: - rusty-cachier snapshot create - - time cargo +nightly doc --locked --workspace --all-features --verbose --no-deps + - time cargo doc --locked --workspace --all-features --verbose --no-deps - rusty-cachier cache upload cargo-check-each-crate: - stage: test + stage: test extends: - .docker-env - .test-refs @@ -408,7 +415,7 @@ cargo-check-each-crate: variables: # $CI_JOB_NAME is set manually so that rusty-cachier can share the cache for all # "cargo-check-each-crate I/N" jobs - CI_JOB_NAME: cargo-check-each-crate + CI_JOB_NAME: cargo-check-each-crate script: - rusty-cachier snapshot create - PYTHONUNBUFFERED=x time ./scripts/ci/gitlab/check-each-crate.py "$CI_NODE_INDEX" "$CI_NODE_TOTAL" @@ -416,24 +423,6 @@ cargo-check-each-crate: - if [ "$CI_NODE_INDEX" == 1 ]; then rusty-cachier cache upload; fi parallel: 2 -publish-crates-locally: - stage: test - extends: - - .test-refs - - .crates-publishing-template - # When lots of crates are taken into account (for example on master where all crates are tested) - # the job might take a long time, as evidenced by: - # https://gitlab.parity.io/parity/mirrors/substrate/-/jobs/2269364 - timeout: 4h - script: - - rusty-cachier snapshot create - - git clone - --depth 1 - --branch "$RELENG_SCRIPTS_BRANCH" - https://github.com/paritytech/releng-scripts.git - - CRATESIO_TARGET_INSTANCE=local ./releng-scripts/publish-crates - - rusty-cachier cache upload - cargo-check-each-crate-macos: stage: test extends: @@ -441,11 +430,12 @@ cargo-check-each-crate-macos: - .collect-artifacts - .pipeline-stopper-artifacts before_script: + # skip timestamp script, the osx bash doesn't support printf %()T - !reference [.job-switcher, before_script] - !reference [.rust-info-script, script] - !reference [.pipeline-stopper-vars, script] variables: - SKIP_WASM_BUILD: 1 + SKIP_WASM_BUILD: 1 script: # TODO: enable rusty-cachier once it supports Mac # TODO: use parallel jobs, as per cargo-check-each-crate, once more Mac runners are available @@ -453,3 +443,37 @@ cargo-check-each-crate-macos: - time cargo check --workspace --locked tags: - osx + +cargo-hfuzz: + stage: test + extends: + - .docker-env + - .test-refs + - .pipeline-stopper-artifacts + variables: + # max 10s per iteration, 60s per file + HFUZZ_RUN_ARGS: > + --exit_upon_crash + --exit_code_upon_crash 1 + --timeout 10 + --run_time 60 + # use git version of honggfuzz-rs until v0.5.56 is out, we need a few recent changes: + # https://github.com/rust-fuzz/honggfuzz-rs/pull/75 to avoid breakage on debian + # https://github.com/rust-fuzz/honggfuzz-rs/pull/81 fix to the above pr + # https://github.com/rust-fuzz/honggfuzz-rs/pull/82 fix for handling rusty-cachier's absolute CARGO_TARGET_DIR + HFUZZ_BUILD_ARGS: > + --config=patch.crates-io.honggfuzz.git="https://github.com/altaua/honggfuzz-rs" + --config=patch.crates-io.honggfuzz.rev="205f7c8c059a0d98fe1cb912cdac84f324cb6981" + artifacts: + name: "hfuzz-$CI_COMMIT_SHORT_SHA" + expire_in: 7 days + when: on_failure + paths: + - primitives/arithmetic/fuzzer/hfuzz_workspace/ + script: + - cd ./primitives/arithmetic/fuzzer + - rusty-cachier snapshot create + - cargo hfuzz build + - rusty-cachier cache upload + - for target in $(cargo read-manifest | jq -r '.targets | .[] | .name'); do + cargo hfuzz run "$target" || { printf "fuzzing failure for %s\n" "$target"; exit 1; }; done diff --git a/scripts/ci/gitlab/pipeline/zombienet.yml b/scripts/ci/gitlab/pipeline/zombienet.yml index 7dc98c006808..31ee51034327 100644 --- a/scripts/ci/gitlab/pipeline/zombienet.yml +++ b/scripts/ci/gitlab/pipeline/zombienet.yml @@ -11,20 +11,20 @@ - export DEBUG=zombie,zombie::network-node - export ZOMBIENET_INTEGRATION_TEST_IMAGE=${SUBSTRATE_IMAGE_NAME}:${SUBSTRATE_IMAGE_TAG} - echo "${ZOMBIENET_INTEGRATION_TEST_IMAGE}" - stage: zombienet - image: "${ZOMBIENET_IMAGE}" + stage: zombienet + image: "${ZOMBIENET_IMAGE}" needs: - - job: publish-docker-substrate-temporary + - job: publish-docker-substrate-temporary extends: - .kubernetes-env - .zombienet-refs variables: - GH_DIR: "https://github.com/paritytech/substrate/tree/${CI_COMMIT_SHA}/zombienet" + GH_DIR: "https://github.com/paritytech/substrate/tree/${CI_COMMIT_SHA}/zombienet" FF_DISABLE_UMASK_FOR_DOCKER_EXECUTOR: 1 artifacts: - name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" - when: always - expire_in: 2 days + name: "${CI_JOB_NAME}_${CI_COMMIT_REF_NAME}" + when: always + expire_in: 2 days paths: - ./zombienet-logs after_script: @@ -39,14 +39,29 @@ zombienet-0000-block-building: - .zombienet-common script: - /home/nonroot/zombie-net/scripts/ci/run-test-env-manager.sh - --github-remote-dir="${GH_DIR}/0000-block-building" - --test="block-building.zndsl" - + --github-remote-dir="${GH_DIR}/0000-block-building" + --test="block-building.zndsl" zombienet-0001-basic-warp-sync: extends: - .zombienet-common script: - /home/nonroot/zombie-net/scripts/ci/run-test-env-manager.sh - --github-remote-dir="${GH_DIR}/0001-basic-warp-sync" - --test="test-warp-sync.zndsl" + --github-remote-dir="${GH_DIR}/0001-basic-warp-sync" + --test="test-warp-sync.zndsl" + +zombienet-0002-validators-warp-sync: + extends: + - .zombienet-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-env-manager.sh + --github-remote-dir="${GH_DIR}/0002-validators-warp-sync" + --test="test-validators-warp-sync.zndsl" + +zombienet-0003-block-building-warp-sync: + extends: + - .zombienet-common + script: + - /home/nonroot/zombie-net/scripts/ci/run-test-env-manager.sh + --github-remote-dir="${GH_DIR}/0003-block-building-warp-sync" + --test="test-block-building-warp-sync.zndsl" diff --git a/scripts/ci/gitlab/prettier.sh b/scripts/ci/gitlab/prettier.sh new file mode 100755 index 000000000000..299bbee179dc --- /dev/null +++ b/scripts/ci/gitlab/prettier.sh @@ -0,0 +1,6 @@ +#!/bin/sh + +# meant to be installed via +# git config filter.ci-prettier.clean "scripts/ci/gitlab/prettier.sh" + +prettier --parser yaml diff --git a/scripts/ci/gitlab/skip_if_draft.sh b/scripts/ci/gitlab/skip_if_draft.sh deleted file mode 100755 index cf6ea6a5b311..000000000000 --- a/scripts/ci/gitlab/skip_if_draft.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -url="https://api.github.com/repos/paritytech/substrate/pulls/${CI_COMMIT_REF_NAME}" -echo "[+] API URL: $url" - -draft_state=$(curl -H "Authorization: token ${GITHUB_PR_TOKEN}" "$url" | jq -r .draft) -echo "[+] Draft state: $draft_state" - -if [ "$draft_state" = 'true' ]; then - echo "[!] PR is currently a draft, stopping pipeline" - exit 1 -else - echo "[+] PR is not a draft. Proceeding with CI pipeline" - exit 0 -fi diff --git a/scripts/ci/node-template-release/Cargo.toml b/scripts/ci/node-template-release/Cargo.toml index 668e0f3f62a7..1fc35b63b1df 100644 --- a/scripts/ci/node-template-release/Cargo.toml +++ b/scripts/ci/node-template-release/Cargo.toml @@ -3,7 +3,7 @@ name = "node-template-release" version = "3.0.0" authors = ["Parity Technologies "] edition = "2021" -license = "GPL-3.0" +license = "GPL-3.0 WITH Classpath-exception-2.0" homepage = "https://substrate.io" publish = false @@ -11,13 +11,11 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -clap = { version = "4.0.9", features = ["derive"] } +clap = { version = "4.2.5", features = ["derive"] } flate2 = "1.0" -fs_extra = "1" -git2 = "0.8" -glob = "0.2" +fs_extra = "1.3" +glob = "0.3" tar = "0.4" tempfile = "3" -toml = "0.4" - -[workspace] +toml_edit = "0.19" +itertools = "0.10" diff --git a/scripts/ci/node-template-release/src/main.rs b/scripts/ci/node-template-release/src/main.rs index 91a7e865458c..9681d73fa6fc 100644 --- a/scripts/ci/node-template-release/src/main.rs +++ b/scripts/ci/node-template-release/src/main.rs @@ -1,30 +1,31 @@ -use clap::Parser; - use std::{ collections::HashMap, fs::{self, File, OpenOptions}, - io::{Read, Write}, path::{Path, PathBuf}, process::Command, }; -use glob; - +use clap::Parser; +use flate2::{write::GzEncoder, Compression}; use fs_extra::dir::{self, CopyOptions}; - +use glob; +use itertools::Itertools; +use tar; use tempfile; +use toml_edit::{self, value, Array, Item, Table}; -use git2; - -use toml; - -use tar; +const SUBSTRATE_GIT_URL: &str = "https://github.com/paritytech/substrate.git"; -use flate2::{write::GzEncoder, Compression}; +type CargoToml = toml_edit::Document; -const SUBSTRATE_GIT_URL: &str = "https://github.com/paritytech/substrate.git"; +#[derive(Debug, PartialEq)] +struct Dependency { + name: String, + version: Option, + default_features: Option, +} -type CargoToml = HashMap; +type Dependencies = HashMap>; #[derive(Parser)] struct Options { @@ -36,8 +37,14 @@ struct Options { output: PathBuf, } +/// Copy the `node-template` to the given path. +fn copy_node_template(node_template: &Path, dest_path: &Path) { + let options = CopyOptions::new(); + dir::copy(node_template, dest_path, &options).expect("Copies node-template to tmp dir"); +} + /// Find all `Cargo.toml` files in the given path. -fn find_cargo_tomls(path: PathBuf) -> Vec { +fn find_cargo_tomls(path: &PathBuf) -> Vec { let path = format!("{}/**/*.toml", path.display()); let glob = glob::glob(&path).expect("Generates globbing pattern"); @@ -55,103 +62,157 @@ fn find_cargo_tomls(path: PathBuf) -> Vec { result } -/// Copy the `node-template` to the given path. -fn copy_node_template(node_template: &Path, dest_path: &Path) { - let options = CopyOptions::new(); - dir::copy(node_template, dest_path, &options).expect("Copies node-template to tmp dir"); +/// Parse the given `Cargo.toml`. +fn parse_cargo_toml(file: &Path) -> CargoToml { + fs::read_to_string(file) + .unwrap_or_else(|e| panic!("Failed to read `{}`: {}", file.display(), e)) + .parse() + .unwrap_or_else(|e| panic!("Failed to parse `{}`: {}", file.display(), e)) +} + +/// Write the given `Cargo.toml` to the given path. +fn write_cargo_toml(path: &Path, cargo_toml: CargoToml) { + fs::write(path, cargo_toml.to_string()) + .unwrap_or_else(|e| panic!("Failed to write `{}`: {}", path.display(), e)); } /// Gets the latest commit id of the repository given by `path`. fn get_git_commit_id(path: &Path) -> String { - let repo = git2::Repository::discover(path) - .expect(&format!("Node template ({}) should be in a git repository.", path.display())); - - let commit_id = repo - .head() - .expect("Repository should have a head") - .peel_to_commit() - .expect("Head references a commit") - .id(); - - format!("{}", commit_id) -} + let mut dir = path; + while !dir.join(".git").exists() { + dir = dir + .parent() + .expect(&format!("Node template ({}) should be in a git repository.", path.display())); + } -/// Parse the given `Cargo.toml` into a `HashMap` -fn parse_cargo_toml(file: &Path) -> CargoToml { - let mut content = String::new(); - File::open(file) - .expect("Cargo.toml exists") - .read_to_string(&mut content) - .expect("Reads file"); - toml::from_str(&content).expect("Cargo.toml is a valid toml file") + let git = dir.join(".git"); + let head = git.join("HEAD"); + let head_contents = fs::read_to_string(head).expect("Repository should have a HEAD"); + let branch = head_contents.strip_prefix("ref: ").expect(".git/HEAD to start 'ref: '").trim(); + let mut commit = fs::read_to_string(git.join(branch)).expect("Head references a commit"); + commit.truncate(commit.trim_end().len()); + commit } -/// Replaces all substrate path dependencies with a git dependency. -fn replace_path_dependencies_with_git( - cargo_toml_path: &Path, - commit_id: &str, +/// Rewrites git dependencies: +/// - inserts `workspace = true`; +/// - removes `path`; +/// - removes `version`; +/// - removes `default-features` +/// - and returns the dependencies that were rewritten. +fn update_git_dependencies bool>( cargo_toml: &mut CargoToml, -) { - let mut cargo_toml_path = cargo_toml_path.to_path_buf(); - // remove `Cargo.toml` - cargo_toml_path.pop(); - - for &table in &["dependencies", "build-dependencies", "dev-dependencies"] { - let mut dependencies: toml::value::Table = - match cargo_toml.remove(table).and_then(|v| v.try_into().ok()) { - Some(deps) => deps, - None => continue, - }; - - let deps_rewritten = dependencies - .iter() - .filter_map(|(k, v)| { - v.clone().try_into::().ok().map(move |v| (k, v)) + path_filter: F, +) -> Dependencies { + let process_dep = |dep: (toml_edit::KeyMut, &mut Item)| -> Option { + let (key, value) = dep; + value + .as_table_like_mut() + .filter(|dep| { + dep.get("path").and_then(|path| path.as_str()).map(path_filter).unwrap_or(false) }) - .filter(|t| { - t.1.contains_key("path") && { - // if the path does not exists, we need to add this as git dependency - t.1.get("path") - .unwrap() - .as_str() - .map(|path| !cargo_toml_path.join(path).exists()) - .unwrap_or(false) + .map(|dep| { + dep.insert("workspace", toml_edit::value(true)); + dep.remove("path"); + + Dependency { + name: key.get().to_string(), + version: dep + .remove("version") + .and_then(|version| version.as_str().map(|s| s.to_string())), + default_features: dep.remove("default-features").and_then(|b| b.as_bool()), } }) - .map(|(k, mut v)| { - // remove `path` and add `git` and `rev` - v.remove("path"); - v.insert("git".into(), SUBSTRATE_GIT_URL.into()); - v.insert("rev".into(), commit_id.into()); + }; + + ["dependencies", "build-dependencies", "dev-dependencies"] + .into_iter() + .map(|table| -> (String, HashMap) { + ( + table.to_string(), + cargo_toml[table] + .as_table_mut() + .into_iter() + .flat_map(|deps| deps.iter_mut().filter_map(process_dep)) + .map(|dep| (dep.name.clone(), dep)) + .collect(), + ) + }) + .collect() +} - (k.clone(), v.into()) - }) - .collect::>(); +/// Processes all `Cargo.toml` files, aggregates dependencies and saves the changes. +fn process_cargo_tomls(cargo_tomls: &Vec) -> Dependencies { + /// Merge dependencies from one collection in another. + fn merge_deps(into: &mut Dependencies, from: Dependencies) { + from.into_iter().for_each(|(table, deps)| { + into.entry(table).or_insert_with(HashMap::new).extend(deps); + }); + } - dependencies.extend(deps_rewritten.into_iter()); + cargo_tomls.iter().fold(Dependencies::new(), |mut acc, path| { + let mut cargo_toml = parse_cargo_toml(&path); - cargo_toml.insert(table.into(), dependencies.into()); - } + let mut cargo_toml_path = path.clone(); + cargo_toml_path.pop(); // remove `Cargo.toml` from the path + let deps = update_git_dependencies(&mut cargo_toml, |dep_path| { + !cargo_toml_path.join(dep_path).exists() + }); + + write_cargo_toml(&path, cargo_toml); + merge_deps(&mut acc, deps); + acc + }) } /// Update the top level (workspace) `Cargo.toml` file. /// -/// - Adds `profile.release` = `panic = unwind` /// - Adds `workspace` definition -fn update_top_level_cargo_toml( +/// - Adds dependencies +/// - Adds `profile.release` = `panic = unwind` +fn update_root_cargo_toml( cargo_toml: &mut CargoToml, - workspace_members: Vec<&PathBuf>, - node_template_path: &Path, + members: &[String], + deps: Dependencies, + commit_id: &str, ) { - let mut panic_unwind = toml::value::Table::new(); - panic_unwind.insert("panic".into(), "unwind".into()); - - let mut profile = toml::value::Table::new(); - profile.insert("release".into(), panic_unwind.into()); - - cargo_toml.insert("profile".into(), profile.into()); + let mut workspace = Table::new(); + workspace.insert("members", value(Array::from_iter(members.iter()))); + + let mut workspace_dependencies = Table::new(); + deps.values() + .flatten() + .sorted_by_key(|(name, _)| *name) + .for_each(|(name, dep)| { + if let Some(version) = &dep.version { + workspace_dependencies[name]["version"] = value(version); + } + if let Some(default_features) = dep.default_features { + workspace_dependencies[name]["default-features"] = value(default_features); + } + workspace_dependencies[name]["git"] = value(SUBSTRATE_GIT_URL); + workspace_dependencies[name]["rev"] = value(commit_id); + }); + + workspace.insert("dependencies", Item::Table(workspace_dependencies)); + cargo_toml.insert("workspace", Item::Table(workspace)); + + let mut panic_unwind = Table::new(); + panic_unwind.insert("panic", value("unwind")); + let mut profile = Table::new(); + profile.insert("release", Item::Table(panic_unwind)); + cargo_toml.insert("profile", Item::Table(profile.into())); +} - let members = workspace_members +fn process_root_cargo_toml( + root_cargo_toml_path: &Path, + root_deps: Dependencies, + cargo_tomls: &[PathBuf], + node_template_path: &PathBuf, + commit_id: &str, +) { + let mut root_cargo_toml = parse_cargo_toml(root_cargo_toml_path); + let workspace_members = cargo_tomls .iter() .map(|p| { p.strip_prefix(node_template_path) @@ -165,16 +226,8 @@ fn update_top_level_cargo_toml( }) .collect::>(); - let mut members_section = toml::value::Table::new(); - members_section.insert("members".into(), members.into()); - - cargo_toml.insert("workspace".into(), members_section.into()); -} - -fn write_cargo_toml(path: &Path, cargo_toml: CargoToml) { - let content = toml::to_string_pretty(&cargo_toml).expect("Creates `Cargo.toml`"); - let mut file = File::create(path).expect(&format!("Creates `{}`.", path.display())); - write!(file, "{}", content).expect("Writes `Cargo.toml`"); + update_root_cargo_toml(&mut root_cargo_toml, &workspace_members, root_deps, commit_id); + write_cargo_toml(&root_cargo_toml_path, root_cargo_toml); } /// Build and test the generated node-template @@ -211,8 +264,8 @@ fn build_and_test(path: &Path, cargo_tomls: &[PathBuf]) { fn main() { let options = Options::parse(); + // Copy node-template to a temp build dir. let build_dir = tempfile::tempdir().expect("Creates temp build dir"); - let node_template_folder = options .node_template .canonicalize() @@ -220,46 +273,38 @@ fn main() { .file_name() .expect("Node template folder is last element of path") .to_owned(); + copy_node_template(&options.node_template, build_dir.path()); // The path to the node-template in the build dir. let node_template_path = build_dir.path().join(node_template_folder); + let root_cargo_toml_path = node_template_path.join("Cargo.toml"); - copy_node_template(&options.node_template, build_dir.path()); - let mut cargo_tomls = find_cargo_tomls(build_dir.path().to_owned()); - - let commit_id = get_git_commit_id(&options.node_template); - let top_level_cargo_toml_path = node_template_path.join("Cargo.toml"); + // Get all `Cargo.toml` files in the node-template + let mut cargo_tomls = find_cargo_tomls(&node_template_path); - // Check if top level Cargo.toml exists. If not, create one in the destination - if !cargo_tomls.contains(&top_level_cargo_toml_path) { - // create the top_level_cargo_toml + // Check if top level Cargo.toml exists. If not, create one in the destination, + // else remove it from the list, as this requires some special treatments. + if let Some(index) = cargo_tomls.iter().position(|x| *x == root_cargo_toml_path) { + cargo_tomls.remove(index); + } else { OpenOptions::new() .create(true) .write(true) - .open(top_level_cargo_toml_path.clone()) + .open(root_cargo_toml_path.clone()) .expect("Create root level `Cargo.toml` failed."); - - // push into our data structure - cargo_tomls.push(PathBuf::from(top_level_cargo_toml_path.clone())); } - cargo_tomls.iter().for_each(|t| { - let mut cargo_toml = parse_cargo_toml(&t); - replace_path_dependencies_with_git(&t, &commit_id, &mut cargo_toml); - - // Check if this is the top level `Cargo.toml`, as this requires some special treatments. - if top_level_cargo_toml_path == *t { - // All workspace member `Cargo.toml` file paths. - let workspace_members = - cargo_tomls.iter().filter(|p| **p != top_level_cargo_toml_path).collect(); - - update_top_level_cargo_toml(&mut cargo_toml, workspace_members, &node_template_path); - } - - write_cargo_toml(&t, cargo_toml); - }); + // Process all `Cargo.toml` files. + let root_deps = process_cargo_tomls(&cargo_tomls); + process_root_cargo_toml( + &root_cargo_toml_path, + root_deps, + &cargo_tomls, + &node_template_path, + &get_git_commit_id(&options.node_template), + ); - // adding root rustfmt to node template build path + // Add root rustfmt to node template build path. let node_template_rustfmt_toml_path = node_template_path.join("rustfmt.toml"); let root_rustfmt_toml = &options.node_template.join("../../rustfmt.toml"); if root_rustfmt_toml.exists() { @@ -277,3 +322,103 @@ fn main() { tar.append_dir_all("substrate-node-template", node_template_path) .expect("Writes substrate-node-template archive"); } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_update_git_dependencies() { + let toml = r#" +[dev-dependencies] +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } + +[dependencies] +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +sp-io = { version = "7.0.0", path = "../../../../primitives/io" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../../../../frame/system" } +"#; + let mut cargo_toml = toml.parse::().expect("invalid doc"); + let actual_deps = update_git_dependencies(&mut cargo_toml, |_| true); + + assert_eq!(actual_deps.len(), 3); + assert_eq!(actual_deps.get("dependencies").unwrap().len(), 2); + assert_eq!(actual_deps.get("dev-dependencies").unwrap().len(), 0); + assert_eq!( + actual_deps.get("dependencies").unwrap().get("sp-io").unwrap(), + &Dependency { + name: "sp-io".into(), + version: Some("7.0.0".into()), + default_features: None + } + ); + assert_eq!( + actual_deps.get("dependencies").unwrap().get("frame-system").unwrap(), + &Dependency { + name: "frame-system".into(), + version: Some("4.0.0-dev".into()), + default_features: Some(false), + } + ); + + let expected_toml = r#" +[dev-dependencies] +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } + +[dependencies] +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } +sp-io = { workspace = true } +frame-system = { workspace = true } +"#; + assert_eq!(cargo_toml.to_string(), expected_toml); + } + + #[test] + fn test_update_root_cargo_toml() { + let mut cargo_toml = CargoToml::new(); + update_root_cargo_toml( + &mut cargo_toml, + &vec!["node".into(), "pallets/template".into(), "runtime".into()], + Dependencies::from([ + ( + "dependencies".into(), + HashMap::from([ + ( + "sp-io".into(), + Dependency { + name: "sp-io".into(), + version: Some("7.0.0".into()), + default_features: None, + }, + ), + ( + "frame-system".into(), + Dependency { + name: "frame-system".into(), + version: Some("4.0.0-dev".into()), + default_features: Some(true), + }, + ), + ]), + ), + ("dev-dependencies".into(), HashMap::new()), + ("build-dependencies".into(), HashMap::new()), + ]), + "commit_id", + ); + + let expected_toml = r#"[workspace] +members = ["node", "pallets/template", "runtime"] + +[workspace.dependencies] +frame-system = { version = "4.0.0-dev", default-features = true, git = "https://github.com/paritytech/substrate.git", rev = "commit_id" } +sp-io = { version = "7.0.0", git = "https://github.com/paritytech/substrate.git", rev = "commit_id" } + +[profile] + +[profile.release] +panic = "unwind" +"#; + assert_eq!(cargo_toml.to_string(), expected_toml); + } +} diff --git a/scripts/run_all_benchmarks.sh b/scripts/run_all_benchmarks.sh index dd5d2e182baf..83848100a7e5 100755 --- a/scripts/run_all_benchmarks.sh +++ b/scripts/run_all_benchmarks.sh @@ -1,7 +1,7 @@ #!/usr/bin/env bash # This file is part of Substrate. -# Copyright (C) 2022 Parity Technologies (UK) Ltd. +# Copyright (C) Parity Technologies (UK) Ltd. # SPDX-License-Identifier: Apache-2.0 # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -59,7 +59,7 @@ done if [ "$skip_build" != true ] then echo "[+] Compiling Substrate benchmarks..." - cargo build --profile=production --locked --features=runtime-benchmarks + cargo build --profile=production --locked --features=runtime-benchmarks --bin substrate fi # The executable to use. @@ -74,6 +74,8 @@ EXCLUDED_PALLETS=( "pallet_grandpa" "pallet_mmr" "pallet_offences" + # Only used for testing, does not need real weights. + "frame_benchmarking_pallet_pov" ) # Load all pallet names in an array. @@ -117,7 +119,6 @@ for PALLET in "${PALLETS[@]}"; do --repeat=20 \ --pallet="$PALLET" \ --extrinsic="*" \ - --execution=wasm \ --wasm-execution=compiled \ --heap-pages=4096 \ --output="$WEIGHT_FILE" \ @@ -135,7 +136,6 @@ echo "[+] Benchmarking block and extrinsic overheads..." OUTPUT=$( $SUBSTRATE benchmark overhead \ --chain=dev \ - --execution=wasm \ --wasm-execution=compiled \ --weight-path="./frame/support/src/weights/" \ --header="./HEADER-APACHE2" \ diff --git a/test-utils/cli/Cargo.toml b/test-utils/cli/Cargo.toml new file mode 100644 index 000000000000..ac8c519a65cd --- /dev/null +++ b/test-utils/cli/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "substrate-cli-test-utils" +description = "CLI testing utilities" +version = "0.1.0" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +substrate-rpc-client = { path = "../../utils/frame/rpc/client" } +sp-rpc = { version = "6.0.0", path = "../../primitives/rpc" } +assert_cmd = "2.0.10" +nix = "0.26.2" +regex = "1.7.3" +tokio = { version = "1.22.0", features = ["full"] } +node-primitives = { path = "../../bin/node/primitives" } +futures = "0.3.28" diff --git a/test-utils/cli/src/lib.rs b/test-utils/cli/src/lib.rs new file mode 100644 index 000000000000..b33c0e9b77c7 --- /dev/null +++ b/test-utils/cli/src/lib.rs @@ -0,0 +1,323 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +#![cfg(unix)] + +use assert_cmd::cargo::cargo_bin; +use nix::{ + sys::signal::{kill, Signal, Signal::SIGINT}, + unistd::Pid, +}; +use node_primitives::{Hash, Header}; +use regex::Regex; +use sp_rpc::{list::ListOrValue, number::NumberOrHex}; +use std::{ + env, + io::{BufRead, BufReader, Read}, + ops::{Deref, DerefMut}, + path::{Path, PathBuf}, + process::{self, Child, Command}, + time::Duration, +}; +use tokio::io::{AsyncBufReadExt, AsyncRead}; + +/// Starts a new Substrate node in development mode with a temporary chain. +/// +/// This function creates a new Substrate node using the `substrate` binary. +/// It configures the node to run in development mode (`--dev`) with a temporary chain (`--tmp`), +/// sets the WebSocket port to 45789 (`--ws-port=45789`). +/// +/// # Returns +/// +/// A [`Child`] process representing the spawned Substrate node. +/// +/// # Panics +/// +/// This function will panic if the `substrate` binary is not found or if the node fails to start. +/// +/// # Examples +/// +/// ```ignore +/// use my_crate::start_node; +/// +/// let child = start_node(); +/// // Interact with the Substrate node using the WebSocket port 45789. +/// // When done, the node will be killed when the `child` is dropped. +/// ``` +/// +/// [`Child`]: std::process::Child +pub fn start_node() -> Child { + Command::new(cargo_bin("substrate")) + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()) + .args(&["--dev", "--tmp", "--rpc-port=45789", "--no-hardware-benchmarks"]) + .spawn() + .unwrap() +} + +/// Builds the Substrate project using the provided arguments. +/// +/// This function reads the CARGO_MANIFEST_DIR environment variable to find the root workspace +/// directory. It then runs the `cargo b` command in the root directory with the specified +/// arguments. +/// +/// This can be useful for building the Substrate binary with a desired set of features prior +/// to using the binary in a CLI test. +/// +/// # Arguments +/// +/// * `args: &[&str]` - A slice of string references representing the arguments to pass to the +/// `cargo b` command. +/// +/// # Panics +/// +/// This function will panic if: +/// +/// * The CARGO_MANIFEST_DIR environment variable is not set. +/// * The root workspace directory cannot be determined. +/// * The 'cargo b' command fails to execute. +/// * The 'cargo b' command returns a non-successful status. +/// +/// # Examples +/// +/// ```ignore +/// build_substrate(&["--features=try-runtime"]); +/// ``` +pub fn build_substrate(args: &[&str]) { + // Get the root workspace directory from the CARGO_MANIFEST_DIR environment variable + let manifest_dir = env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set"); + let root_dir = std::path::Path::new(&manifest_dir) + .parent() + .expect("Failed to find root workspace directory"); + let output = Command::new("cargo") + .arg("build") + .args(args) + .current_dir(root_dir) + .output() + .expect(format!("Failed to execute 'cargo b' with args {:?}'", args).as_str()); + + if !output.status.success() { + panic!( + "Failed to execute 'cargo b' with args {:?}': \n{}", + args, + String::from_utf8_lossy(&output.stderr) + ); + } +} + +/// Takes a readable tokio stream (e.g. from a child process `ChildStderr` or `ChildStdout`) and +/// a `Regex` pattern, and checks each line against the given pattern as it is produced. +/// The function returns OK(()) as soon as a line matching the pattern is found, or an Err if +/// the stream ends without any lines matching the pattern. +/// +/// # Arguments +/// +/// * `child_stream` - An async tokio stream, e.g. from a child process `ChildStderr` or +/// `ChildStdout`. +/// * `re` - A `Regex` pattern to search for in the stream. +/// +/// # Returns +/// +/// * `Ok(())` if a line matching the pattern is found. +/// * `Err(String)` if the stream ends without any lines matching the pattern. +/// +/// # Examples +/// +/// ```ignore +/// use regex::Regex; +/// use tokio::process::Command; +/// use tokio::io::AsyncRead; +/// +/// # async fn run() { +/// let child = Command::new("some-command").stderr(std::process::Stdio::piped()).spawn().unwrap(); +/// let stderr = child.stderr.unwrap(); +/// let re = Regex::new("error:").unwrap(); +/// +/// match wait_for_pattern_match_in_stream(stderr, re).await { +/// Ok(()) => println!("Error found in stderr"), +/// Err(e) => println!("Error: {}", e), +/// } +/// # } +/// ``` +pub async fn wait_for_stream_pattern_match(stream: R, re: Regex) -> Result<(), String> +where + R: AsyncRead + Unpin, +{ + let mut stdio_reader = tokio::io::BufReader::new(stream).lines(); + while let Ok(Some(line)) = stdio_reader.next_line().await { + match re.find(line.as_str()) { + Some(_) => return Ok(()), + None => (), + } + } + Err(String::from("Stream closed without any lines matching the regex.")) +} + +/// Run the given `future` and panic if the `timeout` is hit. +pub async fn run_with_timeout(timeout: Duration, future: impl futures::Future) { + tokio::time::timeout(timeout, future).await.expect("Hit timeout"); +} + +/// Wait for at least n blocks to be finalized from a specified node +pub async fn wait_n_finalized_blocks(n: usize, url: &str) { + use substrate_rpc_client::{ws_client, ChainApi}; + + let mut built_blocks = std::collections::HashSet::new(); + let block_duration = Duration::from_secs(2); + let mut interval = tokio::time::interval(block_duration); + let rpc = ws_client(url).await.unwrap(); + + loop { + if let Ok(block) = ChainApi::<(), Hash, Header, ()>::finalized_head(&rpc).await { + built_blocks.insert(block); + if built_blocks.len() > n { + break + } + }; + interval.tick().await; + } +} + +/// Run the node for a while (3 blocks) +pub async fn run_node_for_a_while(base_path: &Path, args: &[&str]) { + run_with_timeout(Duration::from_secs(60 * 10), async move { + let mut cmd = Command::new(cargo_bin("substrate")) + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()) + .args(args) + .arg("-d") + .arg(base_path) + .spawn() + .unwrap(); + + let stderr = cmd.stderr.take().unwrap(); + + let mut child = KillChildOnDrop(cmd); + + let ws_url = extract_info_from_output(stderr).0.ws_url; + + // Let it produce some blocks. + wait_n_finalized_blocks(3, &ws_url).await; + + child.assert_still_running(); + + // Stop the process + child.stop(); + }) + .await +} + +pub async fn block_hash(block_number: u64, url: &str) -> Result { + use substrate_rpc_client::{ws_client, ChainApi}; + + let rpc = ws_client(url).await.unwrap(); + + let result = ChainApi::<(), Hash, Header, ()>::block_hash( + &rpc, + Some(ListOrValue::Value(NumberOrHex::Number(block_number))), + ) + .await + .map_err(|_| "Couldn't get block hash".to_string())?; + + match result { + ListOrValue::Value(maybe_block_hash) if maybe_block_hash.is_some() => + Ok(maybe_block_hash.unwrap()), + _ => Err("Couldn't get block hash".to_string()), + } +} + +pub struct KillChildOnDrop(pub Child); + +impl KillChildOnDrop { + /// Stop the child and wait until it is finished. + /// + /// Asserts if the exit status isn't success. + pub fn stop(&mut self) { + self.stop_with_signal(SIGINT); + } + + /// Same as [`Self::stop`] but takes the `signal` that is sent to stop the child. + pub fn stop_with_signal(&mut self, signal: Signal) { + kill(Pid::from_raw(self.id().try_into().unwrap()), signal).unwrap(); + assert!(self.wait().unwrap().success()); + } + + /// Asserts that the child is still running. + pub fn assert_still_running(&mut self) { + assert!(self.try_wait().unwrap().is_none(), "the process should still be running"); + } +} + +impl Drop for KillChildOnDrop { + fn drop(&mut self) { + let _ = self.0.kill(); + } +} + +impl Deref for KillChildOnDrop { + type Target = Child; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for KillChildOnDrop { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +/// Information extracted from a running node. +pub struct NodeInfo { + pub ws_url: String, + pub db_path: PathBuf, +} + +/// Extract [`NodeInfo`] from a running node by parsing its output. +/// +/// Returns the [`NodeInfo`] and all the read data. +pub fn extract_info_from_output(read: impl Read + Send) -> (NodeInfo, String) { + let mut data = String::new(); + + let ws_url = BufReader::new(read) + .lines() + .find_map(|line| { + let line = line.expect("failed to obtain next line while extracting node info"); + data.push_str(&line); + data.push_str("\n"); + + // does the line contain our port (we expect this specific output from substrate). + let sock_addr = match line.split_once("Running JSON-RPC server: addr=") { + None => return None, + Some((_, after)) => after.split_once(",").unwrap().0, + }; + + Some(format!("ws://{}", sock_addr)) + }) + .unwrap_or_else(|| { + eprintln!("Observed node output:\n{}", data); + panic!("We should get a WebSocket address") + }); + + // Database path is printed before the ws url! + let re = Regex::new(r"Database: .+ at (\S+)").unwrap(); + let db_path = PathBuf::from(re.captures(data.as_str()).unwrap().get(1).unwrap().as_str()); + + (NodeInfo { ws_url, db_path }, data) +} diff --git a/test-utils/client/Cargo.toml b/test-utils/client/Cargo.toml index 682c868f290d..d921a879fba2 100644 --- a/test-utils/client/Cargo.toml +++ b/test-utils/client/Cargo.toml @@ -13,11 +13,11 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -array-bytes = "4.1" +array-bytes = "6.1" async-trait = "0.1.57" -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" -serde = "1.0.136" +serde = "1.0.163" serde_json = "1.0.85" sc-client-api = { version = "4.0.0-dev", path = "../../client/api" } sc-client-db = { version = "0.10.0-dev", default-features = false, features = [ @@ -31,8 +31,8 @@ sc-service = { version = "0.10.0-dev", default-features = false, features = [ ], path = "../../client/service" } sp-blockchain = { version = "4.0.0-dev", path = "../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } -sp-core = { version = "7.0.0", path = "../../primitives/core" } -sp-keyring = { version = "7.0.0", path = "../../primitives/keyring" } -sp-keystore = { version = "0.13.0", path = "../../primitives/keystore" } -sp-runtime = { version = "7.0.0", path = "../../primitives/runtime" } -sp-state-machine = { version = "0.13.0", path = "../../primitives/state-machine" } +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-keyring = { version = "24.0.0", path = "../../primitives/keyring" } +sp-keystore = { version = "0.27.0", path = "../../primitives/keystore" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } +sp-state-machine = { version = "0.28.0", path = "../../primitives/state-machine" } diff --git a/test-utils/client/src/client_ext.rs b/test-utils/client/src/client_ext.rs index 881c50d43426..a258faa5e03e 100644 --- a/test-utils/client/src/client_ext.rs +++ b/test-utils/client/src/client_ext.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ //! Client extension for tests. -use codec::alloc::collections::hash_map::HashMap; use sc_client_api::{backend::Finalizer, client::BlockBackend}; use sc_consensus::{BlockImport, BlockImportParams, ForkChoiceStrategy}; use sc_service::client::Client; @@ -100,7 +99,7 @@ where import.body = Some(extrinsics); import.fork_choice = Some(ForkChoiceStrategy::LongestChain); - BlockImport::import_block(self, import, HashMap::new()).await.map(|_| ()) + BlockImport::import_block(self, import).await.map(|_| ()) } async fn import_as_best( @@ -113,7 +112,7 @@ where import.body = Some(extrinsics); import.fork_choice = Some(ForkChoiceStrategy::Custom(true)); - BlockImport::import_block(self, import, HashMap::new()).await.map(|_| ()) + BlockImport::import_block(self, import).await.map(|_| ()) } async fn import_as_final( @@ -127,7 +126,7 @@ where import.finalized = true; import.fork_choice = Some(ForkChoiceStrategy::Custom(true)); - BlockImport::import_block(self, import, HashMap::new()).await.map(|_| ()) + BlockImport::import_block(self, import).await.map(|_| ()) } async fn import_justified( @@ -143,7 +142,7 @@ where import.finalized = true; import.fork_choice = Some(ForkChoiceStrategy::LongestChain); - BlockImport::import_block(self, import, HashMap::new()).await.map(|_| ()) + BlockImport::import_block(self, import).await.map(|_| ()) } } @@ -162,7 +161,7 @@ where import.body = Some(extrinsics); import.fork_choice = Some(ForkChoiceStrategy::LongestChain); - BlockImport::import_block(self, import, HashMap::new()).await.map(|_| ()) + BlockImport::import_block(self, import).await.map(|_| ()) } async fn import_as_best( @@ -175,7 +174,7 @@ where import.body = Some(extrinsics); import.fork_choice = Some(ForkChoiceStrategy::Custom(true)); - BlockImport::import_block(self, import, HashMap::new()).await.map(|_| ()) + BlockImport::import_block(self, import).await.map(|_| ()) } async fn import_as_final( @@ -189,7 +188,7 @@ where import.finalized = true; import.fork_choice = Some(ForkChoiceStrategy::Custom(true)); - BlockImport::import_block(self, import, HashMap::new()).await.map(|_| ()) + BlockImport::import_block(self, import).await.map(|_| ()) } async fn import_justified( @@ -205,6 +204,6 @@ where import.finalized = true; import.fork_choice = Some(ForkChoiceStrategy::LongestChain); - BlockImport::import_block(self, import, HashMap::new()).await.map(|_| ()) + BlockImport::import_block(self, import).await.map(|_| ()) } } diff --git a/test-utils/client/src/lib.rs b/test-utils/client/src/lib.rs index ff744b80cbff..90e15e0f8d53 100644 --- a/test-utils/client/src/lib.rs +++ b/test-utils/client/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,27 +22,27 @@ pub mod client_ext; pub use self::client_ext::{ClientBlockImportExt, ClientExt}; -pub use sc_client_api::{ - execution_extensions::{ExecutionExtensions, ExecutionStrategies}, - BadBlocks, ForkBlocks, -}; +pub use sc_client_api::{execution_extensions::ExecutionExtensions, BadBlocks, ForkBlocks}; pub use sc_client_db::{self, Backend, BlocksPruning}; -pub use sc_executor::{self, NativeElseWasmExecutor, WasmExecutionMethod}; +pub use sc_executor::{self, NativeElseWasmExecutor, WasmExecutionMethod, WasmExecutor}; pub use sc_service::{client, RpcHandlers}; pub use sp_consensus; pub use sp_keyring::{ ed25519::Keyring as Ed25519Keyring, sr25519::Keyring as Sr25519Keyring, AccountKeyring, }; -pub use sp_keystore::{SyncCryptoStore, SyncCryptoStorePtr}; +pub use sp_keystore::{Keystore, KeystorePtr}; pub use sp_runtime::{Storage, StorageChild}; -pub use sp_state_machine::ExecutionStrategy; use futures::{future::Future, stream::StreamExt}; use sc_client_api::BlockchainEvents; use sc_service::client::{ClientConfig, LocalCallExecutor}; use serde::Deserialize; use sp_core::{storage::ChildInfo, testing::TaskExecutor}; -use sp_runtime::{codec::Encode, traits::Block as BlockT, OpaqueExtrinsic}; +use sp_runtime::{ + codec::Encode, + traits::{Block as BlockT, Header}, + OpaqueExtrinsic, +}; use std::{ collections::{HashMap, HashSet}, pin::Pin, @@ -63,14 +63,12 @@ impl GenesisInit for () { /// A builder for creating a test client instance. pub struct TestClientBuilder { - execution_strategies: ExecutionStrategies, genesis_init: G, /// The key is an unprefixed storage key, this only contains /// default child trie content. child_storage_extension: HashMap, StorageChild>, backend: Arc, _executor: std::marker::PhantomData, - keystore: Option, fork_blocks: ForkBlocks, bad_blocks: BadBlocks, enable_offchain_indexing_api: bool, @@ -115,11 +113,9 @@ impl pub fn with_backend(backend: Arc) -> Self { TestClientBuilder { backend, - execution_strategies: ExecutionStrategies::default(), child_storage_extension: Default::default(), genesis_init: Default::default(), _executor: Default::default(), - keystore: None, fork_blocks: None, bad_blocks: None, enable_offchain_indexing_api: false, @@ -127,12 +123,6 @@ impl } } - /// Set the keystore that should be used by the externalities. - pub fn set_keystore(mut self, keystore: SyncCryptoStorePtr) -> Self { - self.keystore = Some(keystore); - self - } - /// Alter the genesis storage parameters. pub fn genesis_init_mut(&mut self) -> &mut G { &mut self.genesis_init @@ -158,18 +148,6 @@ impl self } - /// Set the execution strategy that should be used by all contexts. - pub fn set_execution_strategy(mut self, execution_strategy: ExecutionStrategy) -> Self { - self.execution_strategies = ExecutionStrategies { - syncing: execution_strategy, - importing: execution_strategy, - block_construction: execution_strategy, - offchain_worker: execution_strategy, - other: execution_strategy, - }; - self - } - /// Sets custom block rules. pub fn set_block_rules( mut self, @@ -286,18 +264,13 @@ impl Backend: sc_client_api::backend::Backend + 'static, { let executor = executor.into().unwrap_or_else(|| { - NativeElseWasmExecutor::new(WasmExecutionMethod::Interpreted, None, 8, 2) + NativeElseWasmExecutor::new_with_wasm_executor(WasmExecutor::builder().build()) }); let executor = LocalCallExecutor::new( self.backend.clone(), - executor, - Box::new(sp_core::testing::TaskExecutor::new()), + executor.clone(), Default::default(), - ExecutionExtensions::new( - self.execution_strategies.clone(), - self.keystore.clone(), - sc_offchain::OffchainDb::factory_from_backend(&*self.backend), - ), + ExecutionExtensions::new(None, Arc::new(executor)), ) .expect("Creates LocalCallExecutor"); @@ -410,7 +383,7 @@ where Box::pin(async move { while let Some(notification) = import_notification_stream.next().await { if notification.is_new_best { - blocks.insert(notification.hash); + blocks.insert(*notification.header.number()); if blocks.len() == count { break } diff --git a/test-utils/derive/Cargo.toml b/test-utils/derive/Cargo.toml index b55ace822ceb..b5d73b8e9939 100644 --- a/test-utils/derive/Cargo.toml +++ b/test-utils/derive/Cargo.toml @@ -11,9 +11,9 @@ publish = false [dependencies] proc-macro-crate = "1.1.3" -proc-macro2 = "1.0.37" -quote = "1.0.10" -syn = { version = "1.0.98", features = ["full"] } +proc-macro2 = "1.0.56" +quote = "1.0.28" +syn = { version = "2.0.16", features = ["full"] } [lib] proc-macro = true diff --git a/test-utils/derive/src/lib.rs b/test-utils/derive/src/lib.rs index 06b7d2463cbd..0291d825e768 100644 --- a/test-utils/derive/src/lib.rs +++ b/test-utils/derive/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/test-utils/runtime/Cargo.toml b/test-utils/runtime/Cargo.toml index 2c943c47f32b..35c26a62be2d 100644 --- a/test-utils/runtime/Cargo.toml +++ b/test-utils/runtime/Cargo.toml @@ -13,50 +13,48 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../primitives/beefy", package = "sp-beefy" } -beefy-merkle-tree = { version = "4.0.0-dev", default-features = false, path = "../../frame/beefy-mmr/primitives" } -sp-application-crypto = { version = "7.0.0", default-features = false, path = "../../primitives/application-crypto" } +sp-application-crypto = { version = "23.0.0", default-features = false, path = "../../primitives/application-crypto" } sp-consensus-aura = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/aura" } sp-consensus-babe = { version = "0.10.0-dev", default-features = false, path = "../../primitives/consensus/babe" } sp-block-builder = { version = "4.0.0-dev", default-features = false, path = "../../primitives/block-builder" } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] } -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"] } +scale-info = { version = "2.5.0", default-features = false, features = ["derive"] } sp-inherents = { version = "4.0.0-dev", default-features = false, path = "../../primitives/inherents" } -sp-keyring = { version = "7.0.0", optional = true, path = "../../primitives/keyring" } -memory-db = { version = "0.31.0", default-features = false } +sp-keyring = { version = "24.0.0", optional = true, path = "../../primitives/keyring" } sp-offchain = { version = "4.0.0-dev", default-features = false, path = "../../primitives/offchain" } -sp-core = { version = "7.0.0", default-features = false, path = "../../primitives/core" } -sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } -sp-runtime-interface = { version = "7.0.0", default-features = false, path = "../../primitives/runtime-interface" } -sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" } +sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" } +sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../../frame/support" } -sp-version = { version = "5.0.0", default-features = false, path = "../../primitives/version" } +sp-version = { version = "22.0.0", default-features = false, path = "../../primitives/version" } sp-session = { version = "4.0.0-dev", default-features = false, path = "../../primitives/session" } sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" } pallet-babe = { version = "4.0.0-dev", default-features = false, path = "../../frame/babe" } +pallet-balances = { version = "4.0.0-dev", default-features = false, path = "../../frame/balances" } +frame-executive = { version = "4.0.0-dev", default-features = false, path = "../../frame/executive" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../../frame/system" } frame-system-rpc-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../frame/system/rpc/runtime-api" } pallet-timestamp = { version = "4.0.0-dev", default-features = false, path = "../../frame/timestamp" } -sp-finality-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../primitives/finality-grandpa" } -sp-trie = { version = "7.0.0", default-features = false, path = "../../primitives/trie" } +sp-consensus-grandpa = { version = "4.0.0-dev", default-features = false, path = "../../primitives/consensus/grandpa" } +sp-trie = { version = "22.0.0", default-features = false, path = "../../primitives/trie" } sp-transaction-pool = { version = "4.0.0-dev", default-features = false, path = "../../primitives/transaction-pool" } -trie-db = { version = "0.24.0", default-features = false } +trie-db = { version = "0.27.0", default-features = false } sc-service = { version = "0.10.0-dev", default-features = false, optional = true, features = ["test-helpers"], path = "../../client/service" } -sp-state-machine = { version = "0.13.0", default-features = false, path = "../../primitives/state-machine" } -sp-externalities = { version = "0.13.0", default-features = false, path = "../../primitives/externalities" } +sp-state-machine = { version = "0.28.0", default-features = false, path = "../../primitives/state-machine" } +sp-externalities = { version = "0.19.0", default-features = false, path = "../../primitives/externalities" } # 3rd party -cfg-if = "1.0" +array-bytes = { version = "6.1", optional = true } log = { version = "0.4.17", default-features = false } -serde = { version = "1.0.136", optional = true, features = ["derive"] } [dev-dependencies] +futures = "0.3.21" sc-block-builder = { version = "0.10.0-dev", path = "../../client/block-builder" } sc-executor = { version = "0.10.0-dev", path = "../../client/executor" } sp-consensus = { version = "0.10.0-dev", path = "../../primitives/consensus/common" } substrate-test-runtime-client = { version = "2.0.0", path = "./client" } -futures = "0.3.21" +sp-tracing = { version = "10.0.0", path = "../../primitives/tracing" } [build-dependencies] substrate-wasm-builder = { version = "5.0.0-dev", path = "../../utils/wasm-builder", optional = true } @@ -66,8 +64,7 @@ default = [ "std", ] std = [ - "beefy-primitives/std", - "beefy-merkle-tree/std", + "array-bytes", "sp-application-crypto/std", "sp-consensus-aura/std", "sp-consensus-babe/std", @@ -77,27 +74,25 @@ std = [ "sp-inherents/std", "sp-keyring", "log/std", - "memory-db/std", "sp-offchain/std", "sp-core/std", "sp-core/std", "sp-std/std", - "sp-runtime-interface/std", "sp-io/std", "frame-support/std", "sp-version/std", - "serde", "sp-session/std", "sp-api/std", "sp-runtime/std", "sp-externalities/std", "sp-state-machine/std", "pallet-babe/std", + "pallet-timestamp/std", + "pallet-balances/std", "frame-system-rpc-runtime-api/std", "frame-system/std", - "pallet-timestamp/std", "sc-service", - "sp-finality-grandpa/std", + "sp-consensus-grandpa/std", "sp-trie/std", "sp-transaction-pool/std", "trie-db/std", diff --git a/test-utils/runtime/build.rs b/test-utils/runtime/build.rs index 870d8ac43825..dd79ce2c5ae8 100644 --- a/test-utils/runtime/build.rs +++ b/test-utils/runtime/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test-utils/runtime/client/Cargo.toml b/test-utils/runtime/client/Cargo.toml index 7d5c9673f6e2..8269fb72fe50 100644 --- a/test-utils/runtime/client/Cargo.toml +++ b/test-utils/runtime/client/Cargo.toml @@ -12,7 +12,6 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2" } futures = "0.3.21" sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" } sc-client-api = { version = "4.0.0-dev", path = "../../../client/api" } @@ -20,7 +19,7 @@ sc-consensus = { version = "0.10.0-dev", path = "../../../client/consensus/commo sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } sp-consensus = { version = "0.10.0-dev", path = "../../../primitives/consensus/common" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } substrate-test-client = { version = "2.0.0", path = "../../client" } substrate-test-runtime = { version = "2.0.0", path = "../../runtime" } diff --git a/test-utils/runtime/client/src/block_builder_ext.rs b/test-utils/runtime/client/src/block_builder_ext.rs index 66b0b4c3b881..a9b0d49f3543 100644 --- a/test-utils/runtime/client/src/block_builder_ext.rs +++ b/test-utils/runtime/client/src/block_builder_ext.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,6 +21,7 @@ use sc_client_api::backend; use sp_api::{ApiExt, ProvideRuntimeApi}; use sc_block_builder::BlockBuilderApi; +use substrate_test_runtime::*; /// Extension trait for test block builder. pub trait BlockBuilderExt { @@ -29,12 +30,19 @@ pub trait BlockBuilderExt { &mut self, transfer: substrate_test_runtime::Transfer, ) -> Result<(), sp_blockchain::Error>; - /// Add storage change extrinsic to the block. + + /// Add unsigned storage change extrinsic to the block. fn push_storage_change( &mut self, key: Vec, value: Option>, ) -> Result<(), sp_blockchain::Error>; + + /// Adds an extrinsic which pushes DigestItem to header's log + fn push_deposit_log_digest_item( + &mut self, + log: sp_runtime::generic::DigestItem, + ) -> Result<(), sp_blockchain::Error>; } impl<'a, A, B> BlockBuilderExt @@ -52,7 +60,7 @@ where &mut self, transfer: substrate_test_runtime::Transfer, ) -> Result<(), sp_blockchain::Error> { - self.push(transfer.into_signed_tx()) + self.push(transfer.into_unchecked_extrinsic()) } fn push_storage_change( @@ -60,6 +68,13 @@ where key: Vec, value: Option>, ) -> Result<(), sp_blockchain::Error> { - self.push(substrate_test_runtime::Extrinsic::StorageChange(key, value)) + self.push(ExtrinsicBuilder::new_storage_change(key, value).build()) + } + + fn push_deposit_log_digest_item( + &mut self, + log: sp_runtime::generic::DigestItem, + ) -> Result<(), sp_blockchain::Error> { + self.push(ExtrinsicBuilder::new_deposit_log_digest_item(log).build()) } } diff --git a/test-utils/runtime/client/src/lib.rs b/test-utils/runtime/client/src/lib.rs index 99d4e1163e27..7428a7de3a09 100644 --- a/test-utils/runtime/client/src/lib.rs +++ b/test-utils/runtime/client/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,13 +30,8 @@ pub use substrate_test_runtime as runtime; pub use self::block_builder_ext::BlockBuilderExt; -use sp_core::{ - sr25519, - storage::{ChildInfo, Storage, StorageChild}, - Pair, -}; -use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT}; -use substrate_test_runtime::genesismap::{additional_storage_with_genesis, GenesisConfig}; +use sp_core::storage::ChildInfo; +use substrate_test_runtime::genesismap::GenesisStorageBuilder; /// A prelude to import in tests. pub mod prelude { @@ -89,28 +84,6 @@ pub struct GenesisParameters { } impl GenesisParameters { - fn genesis_config(&self) -> GenesisConfig { - GenesisConfig::new( - vec![ - sr25519::Public::from(Sr25519Keyring::Alice).into(), - sr25519::Public::from(Sr25519Keyring::Bob).into(), - sr25519::Public::from(Sr25519Keyring::Charlie).into(), - ], - (0..16_usize) - .into_iter() - .map(|i| AccountKeyring::numeric(i).public()) - .chain(vec![ - AccountKeyring::Alice.into(), - AccountKeyring::Bob.into(), - AccountKeyring::Charlie.into(), - ]) - .collect(), - 1000, - self.heap_pages_override, - self.extra_storage.clone(), - ) - } - /// Set the wasm code that should be used at genesis. pub fn set_wasm_code(&mut self, code: Vec) { self.wasm_code = Some(code); @@ -122,36 +95,13 @@ impl GenesisParameters { } } -impl substrate_test_client::GenesisInit for GenesisParameters { +impl GenesisInit for GenesisParameters { fn genesis_storage(&self) -> Storage { - use codec::Encode; - - let mut storage = self.genesis_config().genesis_map(); - - if let Some(ref code) = self.wasm_code { - storage - .top - .insert(sp_core::storage::well_known_keys::CODE.to_vec(), code.clone()); - } - - let child_roots = storage.children_default.values().map(|child_content| { - let state_root = - <<::Header as HeaderT>::Hashing as HashT>::trie_root( - child_content.data.clone().into_iter().collect(), - sp_runtime::StateVersion::V1, - ); - let prefixed_storage_key = child_content.child_info.prefixed_storage_key(); - (prefixed_storage_key.into_inner(), state_root.encode()) - }); - let state_root = - <<::Header as HeaderT>::Hashing as HashT>::trie_root( - storage.top.clone().into_iter().chain(child_roots).collect(), - sp_runtime::StateVersion::V1, - ); - let block: runtime::Block = client::genesis::construct_genesis_block(state_root); - storage.top.extend(additional_storage_with_genesis(&block)); - - storage + GenesisStorageBuilder::default() + .with_heap_pages(self.heap_pages_override) + .with_wasm_code(&self.wasm_code) + .with_extra_storage(self.extra_storage.clone()) + .build() } } @@ -169,7 +119,7 @@ pub type Client = client::Client< client::LocalCallExecutor< substrate_test_runtime::Block, B, - sc_executor::NativeElseWasmExecutor, + NativeElseWasmExecutor, >, substrate_test_runtime::Block, substrate_test_runtime::RuntimeApi, @@ -260,7 +210,7 @@ impl TestClientBuilderExt client::LocalCallExecutor< substrate_test_runtime::Block, B, - sc_executor::NativeElseWasmExecutor, + NativeElseWasmExecutor, >, B, > where @@ -288,11 +238,6 @@ pub fn new() -> Client { } /// Create a new native executor. -pub fn new_native_executor() -> sc_executor::NativeElseWasmExecutor { - sc_executor::NativeElseWasmExecutor::new( - sc_executor::WasmExecutionMethod::Interpreted, - None, - 8, - 2, - ) +pub fn new_native_or_wasm_executor() -> NativeElseWasmExecutor { + NativeElseWasmExecutor::new_with_wasm_executor(WasmExecutor::builder().build()) } diff --git a/test-utils/runtime/client/src/trait_tests.rs b/test-utils/runtime/client/src/trait_tests.rs index 19772c8c9406..5fce7a2860b7 100644 --- a/test-utils/runtime/client/src/trait_tests.rs +++ b/test-utils/runtime/client/src/trait_tests.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2018-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,7 +32,7 @@ use sc_client_api::{ blockchain::{Backend as BlockChainBackendT, HeaderBackend}, }; use sp_consensus::BlockOrigin; -use sp_runtime::{generic::BlockId, traits::Block as BlockT}; +use sp_runtime::traits::Block as BlockT; use substrate_test_runtime::{self, Transfer}; /// helper to test the `leaves` implementation for various backends @@ -60,7 +60,7 @@ where // A1 -> A2 let a2 = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) + .new_block_at(a1.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -71,7 +71,7 @@ where // A2 -> A3 let a3 = client - .new_block_at(&BlockId::Hash(a2.hash()), Default::default(), false) + .new_block_at(a2.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -82,7 +82,7 @@ where // A3 -> A4 let a4 = client - .new_block_at(&BlockId::Hash(a3.hash()), Default::default(), false) + .new_block_at(a3.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -92,7 +92,7 @@ where // A4 -> A5 let a5 = client - .new_block_at(&BlockId::Hash(a4.hash()), Default::default(), false) + .new_block_at(a4.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -102,9 +102,7 @@ where assert_eq!(blockchain.leaves().unwrap(), vec![a5.hash()]); // A1 -> B2 - let mut builder = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) - .unwrap(); + let mut builder = client.new_block_at(a1.hash(), Default::default(), false).unwrap(); // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder @@ -121,7 +119,7 @@ where // B2 -> B3 let b3 = client - .new_block_at(&BlockId::Hash(b2.hash()), Default::default(), false) + .new_block_at(b2.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -132,7 +130,7 @@ where // B3 -> B4 let b4 = client - .new_block_at(&BlockId::Hash(b3.hash()), Default::default(), false) + .new_block_at(b3.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -141,9 +139,7 @@ where assert_eq!(blockchain.leaves().unwrap(), vec![a5.hash(), b4.hash()]); // // B2 -> C3 - let mut builder = client - .new_block_at(&BlockId::Hash(b2.hash()), Default::default(), false) - .unwrap(); + let mut builder = client.new_block_at(b2.hash(), Default::default(), false).unwrap(); // this push is required as otherwise C3 has the same hash as B3 and won't get imported builder .push_transfer(Transfer { @@ -158,9 +154,7 @@ where assert_eq!(blockchain.leaves().unwrap(), vec![a5.hash(), b4.hash(), c3.hash()]); // A1 -> D2 - let mut builder = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) - .unwrap(); + let mut builder = client.new_block_at(a1.hash(), Default::default(), false).unwrap(); // this push is required as otherwise D2 has the same hash as B2 and won't get imported builder .push_transfer(Transfer { @@ -195,7 +189,7 @@ where // A1 -> A2 let a2 = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) + .new_block_at(a1.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -204,7 +198,7 @@ where // A2 -> A3 let a3 = client - .new_block_at(&BlockId::Hash(a2.hash()), Default::default(), false) + .new_block_at(a2.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -213,7 +207,7 @@ where // A3 -> A4 let a4 = client - .new_block_at(&BlockId::Hash(a3.hash()), Default::default(), false) + .new_block_at(a3.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -222,7 +216,7 @@ where // A4 -> A5 let a5 = client - .new_block_at(&BlockId::Hash(a4.hash()), Default::default(), false) + .new_block_at(a4.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -230,9 +224,7 @@ where block_on(client.import(BlockOrigin::Own, a5.clone())).unwrap(); // A1 -> B2 - let mut builder = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) - .unwrap(); + let mut builder = client.new_block_at(a1.hash(), Default::default(), false).unwrap(); // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder .push_transfer(Transfer { @@ -247,7 +239,7 @@ where // B2 -> B3 let b3 = client - .new_block_at(&BlockId::Hash(b2.hash()), Default::default(), false) + .new_block_at(b2.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -256,7 +248,7 @@ where // B3 -> B4 let b4 = client - .new_block_at(&BlockId::Hash(b3.hash()), Default::default(), false) + .new_block_at(b3.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -264,9 +256,7 @@ where block_on(client.import(BlockOrigin::Own, b4)).unwrap(); // // B2 -> C3 - let mut builder = client - .new_block_at(&BlockId::Hash(b2.hash()), Default::default(), false) - .unwrap(); + let mut builder = client.new_block_at(b2.hash(), Default::default(), false).unwrap(); // this push is required as otherwise C3 has the same hash as B3 and won't get imported builder .push_transfer(Transfer { @@ -280,9 +270,7 @@ where block_on(client.import(BlockOrigin::Own, c3.clone())).unwrap(); // A1 -> D2 - let mut builder = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) - .unwrap(); + let mut builder = client.new_block_at(a1.hash(), Default::default(), false).unwrap(); // this push is required as otherwise D2 has the same hash as B2 and won't get imported builder .push_transfer(Transfer { @@ -328,7 +316,7 @@ where // A1 -> A2 let a2 = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) + .new_block_at(a1.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -337,7 +325,7 @@ where // A2 -> A3 let a3 = client - .new_block_at(&BlockId::Hash(a2.hash()), Default::default(), false) + .new_block_at(a2.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -346,7 +334,7 @@ where // A3 -> A4 let a4 = client - .new_block_at(&BlockId::Hash(a3.hash()), Default::default(), false) + .new_block_at(a3.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -355,7 +343,7 @@ where // A4 -> A5 let a5 = client - .new_block_at(&BlockId::Hash(a4.hash()), Default::default(), false) + .new_block_at(a4.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -363,9 +351,7 @@ where block_on(client.import(BlockOrigin::Own, a5.clone())).unwrap(); // A1 -> B2 - let mut builder = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) - .unwrap(); + let mut builder = client.new_block_at(a1.hash(), Default::default(), false).unwrap(); // this push is required as otherwise B2 has the same hash as A2 and won't get imported builder .push_transfer(Transfer { @@ -380,7 +366,7 @@ where // B2 -> B3 let b3 = client - .new_block_at(&BlockId::Hash(b2.hash()), Default::default(), false) + .new_block_at(b2.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -389,7 +375,7 @@ where // B3 -> B4 let b4 = client - .new_block_at(&BlockId::Hash(b3.hash()), Default::default(), false) + .new_block_at(b3.hash(), Default::default(), false) .unwrap() .build() .unwrap() @@ -397,9 +383,7 @@ where block_on(client.import(BlockOrigin::Own, b4)).unwrap(); // // B2 -> C3 - let mut builder = client - .new_block_at(&BlockId::Hash(b2.hash()), Default::default(), false) - .unwrap(); + let mut builder = client.new_block_at(b2.hash(), Default::default(), false).unwrap(); // this push is required as otherwise C3 has the same hash as B3 and won't get imported builder .push_transfer(Transfer { @@ -413,9 +397,7 @@ where block_on(client.import(BlockOrigin::Own, c3)).unwrap(); // A1 -> D2 - let mut builder = client - .new_block_at(&BlockId::Hash(a1.hash()), Default::default(), false) - .unwrap(); + let mut builder = client.new_block_at(a1.hash(), Default::default(), false).unwrap(); // this push is required as otherwise D2 has the same hash as B2 and won't get imported builder .push_transfer(Transfer { diff --git a/test-utils/runtime/src/extrinsic.rs b/test-utils/runtime/src/extrinsic.rs new file mode 100644 index 000000000000..05ffb7db5d5b --- /dev/null +++ b/test-utils/runtime/src/extrinsic.rs @@ -0,0 +1,207 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Provides utils for building the `Extrinsic` instances used with `substrate-test-runtime`. + +use crate::{ + substrate_test_pallet::pallet::Call as PalletCall, AccountId, Balance, BalancesCall, + CheckSubstrateCall, Extrinsic, Nonce, Pair, RuntimeCall, SignedPayload, TransferData, +}; +use codec::Encode; +use frame_system::{CheckNonce, CheckWeight}; +use sp_core::crypto::Pair as TraitPair; +use sp_keyring::AccountKeyring; +use sp_runtime::{transaction_validity::TransactionPriority, Perbill}; +use sp_std::prelude::*; + +/// Transfer used in test substrate pallet. Extrinsic is created and signed using this data. +#[derive(Clone)] +pub struct Transfer { + /// Transfer sender and signer of created extrinsic + pub from: Pair, + /// The recipient of the transfer + pub to: AccountId, + /// Amount of transfer + pub amount: Balance, + /// Sender's account nonce at which transfer is valid + pub nonce: u64, +} + +impl Transfer { + /// Convert into a signed unchecked extrinsic. + pub fn into_unchecked_extrinsic(self) -> Extrinsic { + ExtrinsicBuilder::new_transfer(self).build() + } +} + +impl Default for TransferData { + fn default() -> Self { + Self { + from: AccountKeyring::Alice.into(), + to: AccountKeyring::Bob.into(), + amount: 0, + nonce: 0, + } + } +} + +/// If feasible converts given `Extrinsic` to `TransferData` +impl TryFrom<&Extrinsic> for TransferData { + type Error = (); + fn try_from(uxt: &Extrinsic) -> Result { + match uxt { + Extrinsic { + function: RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest, value }), + signature: Some((from, _, (CheckNonce(nonce), ..))), + } => Ok(TransferData { from: *from, to: *dest, amount: *value, nonce: *nonce }), + Extrinsic { + function: RuntimeCall::SubstrateTest(PalletCall::bench_call { transfer }), + signature: None, + } => Ok(transfer.clone()), + _ => Err(()), + } + } +} + +/// Generates `Extrinsic` +pub struct ExtrinsicBuilder { + function: RuntimeCall, + signer: Option, + nonce: Option, +} + +impl ExtrinsicBuilder { + /// Create builder for given `RuntimeCall`. By default `Extrinsic` will be signed by `Alice`. + pub fn new(function: impl Into) -> Self { + Self { function: function.into(), signer: Some(AccountKeyring::Alice.pair()), nonce: None } + } + + /// Create builder for given `RuntimeCall`. `Extrinsic` will be unsigned. + pub fn new_unsigned(function: impl Into) -> Self { + Self { function: function.into(), signer: None, nonce: None } + } + + /// Create builder for `pallet_call::bench_transfer` from given `TransferData`. + pub fn new_bench_call(transfer: TransferData) -> Self { + Self::new_unsigned(PalletCall::bench_call { transfer }) + } + + /// Create builder for given `Transfer`. Transfer `nonce` will be used as `Extrinsic` nonce. + /// Transfer `from` will be used as Extrinsic signer. + pub fn new_transfer(transfer: Transfer) -> Self { + Self { + nonce: Some(transfer.nonce), + signer: Some(transfer.from.clone()), + ..Self::new(BalancesCall::transfer_allow_death { + dest: transfer.to, + value: transfer.amount, + }) + } + } + + /// Create builder for `PalletCall::include_data` call using given parameters + pub fn new_include_data(data: Vec) -> Self { + Self::new(PalletCall::include_data { data }) + } + + /// Create builder for `PalletCall::storage_change` call using given parameters. Will + /// create unsigned Extrinsic. + pub fn new_storage_change(key: Vec, value: Option>) -> Self { + Self::new_unsigned(PalletCall::storage_change { key, value }) + } + + /// Create builder for `PalletCall::offchain_index_set` call using given parameters + pub fn new_offchain_index_set(key: Vec, value: Vec) -> Self { + Self::new(PalletCall::offchain_index_set { key, value }) + } + + /// Create builder for `PalletCall::offchain_index_clear` call using given parameters + pub fn new_offchain_index_clear(key: Vec) -> Self { + Self::new(PalletCall::offchain_index_clear { key }) + } + + /// Create builder for `PalletCall::indexed_call` call using given parameters + pub fn new_indexed_call(data: Vec) -> Self { + Self::new(PalletCall::indexed_call { data }) + } + + /// Create builder for `PalletCall::new_deposit_log_digest_item` call using given `log` + pub fn new_deposit_log_digest_item(log: sp_runtime::generic::DigestItem) -> Self { + Self::new_unsigned(PalletCall::deposit_log_digest_item { log }) + } + + /// Create builder for `PalletCall::Call::new_deposit_log_digest_item` + pub fn new_fill_block(ratio: Perbill) -> Self { + Self::new(PalletCall::fill_block { ratio }) + } + + /// Create builder for `PalletCall::call_do_not_propagate` call using given parameters + pub fn new_call_do_not_propagate() -> Self { + Self::new(PalletCall::call_do_not_propagate {}) + } + + /// Create builder for `PalletCall::call_with_priority` call using given parameters + pub fn new_call_with_priority(priority: TransactionPriority) -> Self { + Self::new(PalletCall::call_with_priority { priority }) + } + + /// Create builder for `PalletCall::read` call using given parameters + pub fn new_read(count: u32) -> Self { + Self::new_unsigned(PalletCall::read { count }) + } + + /// Create builder for `PalletCall::read` call using given parameters + pub fn new_read_and_panic(count: u32) -> Self { + Self::new_unsigned(PalletCall::read_and_panic { count }) + } + + /// Unsigned `Extrinsic` will be created + pub fn unsigned(mut self) -> Self { + self.signer = None; + self + } + + /// Given `nonce` will be set in `Extrinsic` + pub fn nonce(mut self, nonce: Nonce) -> Self { + self.nonce = Some(nonce); + self + } + + /// Extrinsic will be signed by `signer` + pub fn signer(mut self, signer: Pair) -> Self { + self.signer = Some(signer); + self + } + + /// Build `Extrinsic` using embedded parameters + pub fn build(self) -> Extrinsic { + if let Some(signer) = self.signer { + let extra = ( + CheckNonce::from(self.nonce.unwrap_or(0)), + CheckWeight::new(), + CheckSubstrateCall {}, + ); + let raw_payload = + SignedPayload::from_raw(self.function.clone(), extra.clone(), ((), (), ())); + let signature = raw_payload.using_encoded(|e| signer.sign(e)); + + Extrinsic::new_signed(self.function, signer.public(), signature, extra) + } else { + Extrinsic::new_unsigned(self.function) + } + } +} diff --git a/test-utils/runtime/src/genesismap.rs b/test-utils/runtime/src/genesismap.rs index 42706730eb9a..97504c232aef 100644 --- a/test-utils/runtime/src/genesismap.rs +++ b/test-utils/runtime/src/genesismap.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,75 +17,136 @@ //! Tool for creating the genesis block. -use super::{system, wasm_binary_unwrap, AccountId, AuthorityId, Runtime}; -use codec::{Encode, Joiner, KeyedVec}; -use frame_support::traits::GenesisBuild; -use sc_service::client::genesis; +use super::{ + currency, substrate_test_pallet, wasm_binary_unwrap, AccountId, Balance, RuntimeGenesisConfig, +}; +use codec::Encode; +use sc_service::construct_genesis_block; use sp_core::{ - map, - storage::{well_known_keys, Storage}, + sr25519, + storage::{well_known_keys, StateVersion, Storage}, + Pair, +}; +use sp_keyring::{AccountKeyring, Sr25519Keyring}; +use sp_runtime::{ + traits::{Block as BlockT, Hash as HashT, Header as HeaderT}, + BuildStorage, }; -use sp_io::hashing::{blake2_256, twox_128}; -use sp_runtime::traits::{Block as BlockT, Hash as HashT, Header as HeaderT}; -use std::collections::BTreeMap; -/// Configuration of a general Substrate test genesis block. -pub struct GenesisConfig { - authorities: Vec, +/// Builder for generating storage from substrate-test-runtime genesis config. +/// +/// Default storage can be extended with additional key-value pairs. +pub struct GenesisStorageBuilder { + /// Authorities accounts used by any component requiring an authority set (e.g. babe). + authorities: Vec, + /// Accounts to be endowed with some funds. balances: Vec<(AccountId, u64)>, + /// Override default number of heap pages. heap_pages_override: Option, /// Additional storage key pairs that will be added to the genesis map. extra_storage: Storage, + /// Optional wasm code override. + wasm_code: Option>, +} + +impl Default for GenesisStorageBuilder { + /// Creates a builder with default settings for `substrate_test_runtime`. + fn default() -> Self { + Self::new( + vec![ + Sr25519Keyring::Alice.into(), + Sr25519Keyring::Bob.into(), + Sr25519Keyring::Charlie.into(), + ], + (0..16_usize) + .into_iter() + .map(|i| AccountKeyring::numeric(i).public()) + .chain(vec![ + AccountKeyring::Alice.into(), + AccountKeyring::Bob.into(), + AccountKeyring::Charlie.into(), + ]) + .collect(), + 1000 * currency::DOLLARS, + ) + } } -impl GenesisConfig { +impl GenesisStorageBuilder { + /// Creates a storage builder for genesis config. `substrage test runtime` `GenesisConfig` is + /// initialized with provided `authorities`, `endowed_accounts` with given balance. Key-pairs + /// from `extra_storage` will be injected into built storage. `HEAP_PAGES` key and value will + /// also be placed into storage. pub fn new( - authorities: Vec, + authorities: Vec, endowed_accounts: Vec, - balance: u64, - heap_pages_override: Option, - extra_storage: Storage, + balance: Balance, ) -> Self { - GenesisConfig { + GenesisStorageBuilder { authorities, balances: endowed_accounts.into_iter().map(|a| (a, balance)).collect(), - heap_pages_override, - extra_storage, + heap_pages_override: None, + extra_storage: Default::default(), + wasm_code: None, } } - pub fn genesis_map(&self) -> Storage { - let wasm_runtime = wasm_binary_unwrap().to_vec(); - let mut map: BTreeMap, Vec> = self - .balances - .iter() - .map(|&(ref account, balance)| { - (account.to_keyed_vec(b"balance:"), vec![].and(&balance)) - }) - .map(|(k, v)| (blake2_256(&k[..])[..].to_vec(), v.to_vec())) - .chain( - vec![ - (well_known_keys::CODE.into(), wasm_runtime), - ( - well_known_keys::HEAP_PAGES.into(), - vec![].and(&(self.heap_pages_override.unwrap_or(16_u64))), - ), - ] - .into_iter(), - ) + /// Override default wasm code to be placed into GenesisConfig. + pub fn with_wasm_code(mut self, wasm_code: &Option>) -> Self { + self.wasm_code = wasm_code.clone(); + self + } + + pub fn with_heap_pages(mut self, heap_pages_override: Option) -> Self { + self.heap_pages_override = heap_pages_override; + self + } + + pub fn with_extra_storage(mut self, storage: Storage) -> Self { + self.extra_storage = storage; + self + } + + /// Builds the `GenesisConfig` and returns its storage. + pub fn build(self) -> Storage { + let authorities_sr25519: Vec<_> = self + .authorities + .clone() + .into_iter() + .map(|id| sr25519::Public::from(id)) .collect(); - map.insert(twox_128(&b"sys:auth"[..])[..].to_vec(), self.authorities.encode()); - // Add the extra storage entries. - map.extend(self.extra_storage.top.clone().into_iter()); - - // Assimilate the system genesis config. - let mut storage = - Storage { top: map, children_default: self.extra_storage.children_default.clone() }; - >::assimilate_storage( - &system::GenesisConfig { authorities: self.authorities.clone() }, - &mut storage, - ) - .expect("Adding `system::GensisConfig` to the genesis"); + + let genesis_config = RuntimeGenesisConfig { + system: frame_system::GenesisConfig { + code: self.wasm_code.clone().unwrap_or(wasm_binary_unwrap().to_vec()), + ..Default::default() + }, + babe: pallet_babe::GenesisConfig { + authorities: authorities_sr25519 + .clone() + .into_iter() + .map(|x| (x.into(), 1)) + .collect(), + epoch_config: Some(crate::TEST_RUNTIME_BABE_EPOCH_CONFIGURATION), + ..Default::default() + }, + substrate_test: substrate_test_pallet::GenesisConfig { + authorities: authorities_sr25519.clone(), + ..Default::default() + }, + balances: pallet_balances::GenesisConfig { balances: self.balances.clone() }, + }; + + let mut storage = genesis_config + .build_storage() + .expect("Build storage from substrate-test-runtime GenesisConfig"); + + if let Some(heap_pages) = self.heap_pages_override { + storage.top.insert(well_known_keys::HEAP_PAGES.into(), heap_pages.encode()); + } + + storage.top.extend(self.extra_storage.top.clone()); + storage.children_default.extend(self.extra_storage.children_default.clone()); storage } @@ -106,14 +167,8 @@ pub fn insert_genesis_block(storage: &mut Storage) -> sp_core::hash::H256 { storage.top.clone().into_iter().collect(), sp_runtime::StateVersion::V1, ); - let block: crate::Block = genesis::construct_genesis_block(state_root); + let block: crate::Block = construct_genesis_block(state_root, StateVersion::V1); let genesis_hash = block.header.hash(); - storage.top.extend(additional_storage_with_genesis(&block)); - genesis_hash -} -pub fn additional_storage_with_genesis(genesis_block: &crate::Block) -> BTreeMap, Vec> { - map![ - twox_128(&b"latest"[..]).to_vec() => genesis_block.hash().as_fixed_bytes().to_vec() - ] + genesis_hash } diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 8b64528e243b..b17c31fb3ea0 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,55 +19,60 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "std")] +pub mod extrinsic; #[cfg(feature = "std")] pub mod genesismap; -pub mod system; +pub mod substrate_test_pallet; -use codec::{Decode, Encode, Error, Input, MaxEncodedLen}; +use codec::{Decode, Encode}; +use frame_support::{ + construct_runtime, + dispatch::DispatchClass, + parameter_types, + traits::{ConstU32, ConstU64}, + weights::{ + constants::{BlockExecutionWeight, ExtrinsicBaseWeight, WEIGHT_REF_TIME_PER_SECOND}, + Weight, + }, +}; +use frame_system::{ + limits::{BlockLength, BlockWeights}, + CheckNonce, CheckWeight, +}; use scale_info::TypeInfo; -use sp_std::{marker::PhantomData, prelude::*}; +use sp_std::prelude::*; use sp_application_crypto::{ecdsa, ed25519, sr25519, RuntimeAppPublic}; -use sp_core::{offchain::KeyTypeId, OpaqueMetadata, RuntimeDebug}; +use sp_core::{OpaqueMetadata, RuntimeDebug}; use sp_trie::{ trie_types::{TrieDBBuilder, TrieDBMutBuilderV1}, PrefixedMemoryDB, StorageProof, }; use trie_db::{Trie, TrieMut}; -use cfg_if::cfg_if; -use frame_support::{ - dispatch::RawOrigin, - parameter_types, - traits::{CallerTrait, ConstU32, ConstU64, CrateVersion, KeyOwnerProofSystem}, - weights::{RuntimeDbWeight, Weight}, -}; -use frame_system::limits::{BlockLength, BlockWeights}; use sp_api::{decl_runtime_apis, impl_runtime_apis}; pub use sp_core::hash::H256; use sp_inherents::{CheckInherentsResult, InherentData}; -#[cfg(feature = "std")] -use sp_runtime::traits::NumberFor; use sp_runtime::{ create_runtime_str, impl_opaque_keys, - traits::{ - BlakeTwo256, BlindCheckable, Block as BlockT, Extrinsic as ExtrinsicT, GetNodeBlockType, - GetRuntimeBlockType, IdentityLookup, Verify, - }, - transaction_validity::{ - InvalidTransaction, TransactionSource, TransactionValidity, TransactionValidityError, - ValidTransaction, - }, + traits::{BlakeTwo256, Block as BlockT, DispatchInfoOf, NumberFor, Verify}, + transaction_validity::{TransactionSource, TransactionValidity, TransactionValidityError}, ApplyExtrinsicResult, Perbill, }; #[cfg(any(feature = "std", test))] use sp_version::NativeVersion; use sp_version::RuntimeVersion; -// Ensure Babe and Aura use the same crypto to simplify things a bit. -pub use sp_consensus_babe::{AllowedSlots, AuthorityId, Slot}; +pub use sp_consensus_babe::{AllowedSlots, BabeEpochConfiguration, Slot}; + +pub use pallet_balances::Call as BalancesCall; pub type AuraId = sp_consensus_aura::sr25519::AuthorityId; +#[cfg(feature = "std")] +pub use extrinsic::{ExtrinsicBuilder, Transfer}; + +const LOG_TARGET: &str = "substrate-test-runtime"; // Include the WASM binary #[cfg(feature = "std")] @@ -119,154 +124,31 @@ pub fn native_version() -> NativeVersion { NativeVersion { runtime_version: VERSION, can_author_with: Default::default() } } -/// Calls in transactions. +/// Transfer data extracted from Extrinsic containing `Balances::transfer_allow_death`. #[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct Transfer { +pub struct TransferData { pub from: AccountId, pub to: AccountId, - pub amount: u64, - pub nonce: u64, -} - -impl Transfer { - /// Convert into a signed extrinsic. - #[cfg(feature = "std")] - pub fn into_signed_tx(self) -> Extrinsic { - let signature = sp_keyring::AccountKeyring::from_public(&self.from) - .expect("Creates keyring from public key.") - .sign(&self.encode()); - Extrinsic::Transfer { transfer: self, signature, exhaust_resources_when_not_first: false } - } - - /// Convert into a signed extrinsic, which will only end up included in the block - /// if it's the first transaction. Otherwise it will cause `ResourceExhaustion` error - /// which should be considered as block being full. - #[cfg(feature = "std")] - pub fn into_resources_exhausting_tx(self) -> Extrinsic { - let signature = sp_keyring::AccountKeyring::from_public(&self.from) - .expect("Creates keyring from public key.") - .sign(&self.encode()); - Extrinsic::Transfer { transfer: self, signature, exhaust_resources_when_not_first: true } - } -} - -/// Extrinsic for test-runtime. -#[derive(Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] -pub enum Extrinsic { - AuthoritiesChange(Vec), - Transfer { - transfer: Transfer, - signature: AccountSignature, - exhaust_resources_when_not_first: bool, - }, - IncludeData(Vec), - StorageChange(Vec, Option>), - OffchainIndexSet(Vec, Vec), - OffchainIndexClear(Vec), - Store(Vec), -} - -#[cfg(feature = "std")] -impl serde::Serialize for Extrinsic { - fn serialize(&self, seq: S) -> Result - where - S: ::serde::Serializer, - { - self.using_encoded(|bytes| seq.serialize_bytes(bytes)) - } + pub amount: Balance, + pub nonce: Nonce, } -// rustc can't deduce this trait bound https://github.com/rust-lang/rust/issues/48214 +/// The address format for describing accounts. +pub type Address = sp_core::sr25519::Public; +pub type Signature = sr25519::Signature; #[cfg(feature = "std")] -impl<'a> serde::Deserialize<'a> for Extrinsic { - fn deserialize(de: D) -> Result - where - D: serde::Deserializer<'a>, - { - let r = sp_core::bytes::deserialize(de)?; - Decode::decode(&mut &r[..]) - .map_err(|e| serde::de::Error::custom(format!("Decode error: {}", e))) - } -} - -impl BlindCheckable for Extrinsic { - type Checked = Self; - - fn check(self) -> Result { - match self { - Extrinsic::AuthoritiesChange(new_auth) => Ok(Extrinsic::AuthoritiesChange(new_auth)), - Extrinsic::Transfer { transfer, signature, exhaust_resources_when_not_first } => - if sp_runtime::verify_encoded_lazy(&signature, &transfer, &transfer.from) { - Ok(Extrinsic::Transfer { - transfer, - signature, - exhaust_resources_when_not_first, - }) - } else { - Err(InvalidTransaction::BadProof.into()) - }, - Extrinsic::IncludeData(v) => Ok(Extrinsic::IncludeData(v)), - Extrinsic::StorageChange(key, value) => Ok(Extrinsic::StorageChange(key, value)), - Extrinsic::OffchainIndexSet(key, value) => Ok(Extrinsic::OffchainIndexSet(key, value)), - Extrinsic::OffchainIndexClear(key) => Ok(Extrinsic::OffchainIndexClear(key)), - Extrinsic::Store(data) => Ok(Extrinsic::Store(data)), - } - } -} - -impl ExtrinsicT for Extrinsic { - type Call = Extrinsic; - type SignaturePayload = (); - - fn is_signed(&self) -> Option { - if let Extrinsic::IncludeData(_) = *self { - Some(false) - } else { - Some(true) - } - } - - fn new(call: Self::Call, _signature_payload: Option) -> Option { - Some(call) - } -} - -impl sp_runtime::traits::Dispatchable for Extrinsic { - type RuntimeOrigin = RuntimeOrigin; - type Config = (); - type Info = (); - type PostInfo = (); - fn dispatch( - self, - _origin: Self::RuntimeOrigin, - ) -> sp_runtime::DispatchResultWithInfo { - panic!("This implementation should not be used for actual dispatch."); - } -} - -impl Extrinsic { - /// Convert `&self` into `&Transfer`. - /// - /// Panics if this is no `Transfer` extrinsic. - pub fn transfer(&self) -> &Transfer { - self.try_transfer().expect("cannot convert to transfer ref") - } +pub type Pair = sp_core::sr25519::Pair; - /// Try to convert `&self` into `&Transfer`. - /// - /// Returns `None` if this is no `Transfer` extrinsic. - pub fn try_transfer(&self) -> Option<&Transfer> { - match self { - Extrinsic::Transfer { ref transfer, .. } => Some(transfer), - _ => None, - } - } -} +/// The SignedExtension to the basic transaction logic. +pub type SignedExtra = (CheckNonce, CheckWeight, CheckSubstrateCall); +/// The payload being signed in transactions. +pub type SignedPayload = sp_runtime::generic::SignedPayload; +/// Unchecked extrinsic type as expected by this runtime. +pub type Extrinsic = + sp_runtime::generic::UncheckedExtrinsic; -/// The signature type used by accounts/transactions. -pub type AccountSignature = sr25519::Signature; /// An identifier for an account on this system. -pub type AccountId = ::Signer; +pub type AccountId = ::Signer; /// A simple hash type for all our hashing. pub type Hash = H256; /// The hashing algorithm used. @@ -274,7 +156,7 @@ pub type Hashing = BlakeTwo256; /// The block number type used in this runtime. pub type BlockNumber = u64; /// Index of a transaction. -pub type Index = u64; +pub type Nonce = u64; /// The item of a block digest. pub type DigestItem = sp_runtime::generic::DigestItem; /// The digest of a block. @@ -283,352 +165,198 @@ pub type Digest = sp_runtime::generic::Digest; pub type Block = sp_runtime::generic::Block; /// A test block's header. pub type Header = sp_runtime::generic::Header; - -/// Run whatever tests we have. -pub fn run_tests(mut input: &[u8]) -> Vec { - use sp_runtime::print; - - print("run_tests..."); - let block = Block::decode(&mut input).unwrap(); - print("deserialized block."); - let stxs = block.extrinsics.iter().map(Encode::encode); - print("reserialized transactions."); - [stxs.count() as u8].encode() -} - -/// A type that can not be decoded. -#[derive(PartialEq)] -pub struct DecodeFails { - _phantom: PhantomData, -} - -impl Encode for DecodeFails { - fn encode(&self) -> Vec { - Vec::new() - } -} - -impl codec::EncodeLike for DecodeFails {} - -impl Default for DecodeFails { - /// Create a default instance. - fn default() -> DecodeFails { - DecodeFails { _phantom: Default::default() } - } -} - -impl Decode for DecodeFails { - fn decode(_: &mut I) -> Result { - Err("DecodeFails always fails".into()) - } -} - -cfg_if! { - if #[cfg(feature = "std")] { - decl_runtime_apis! { - #[api_version(2)] - pub trait TestAPI { - /// Return the balance of the given account id. - fn balance_of(id: AccountId) -> u64; - /// A benchmark function that adds one to the given value and returns the result. - fn benchmark_add_one(val: &u64) -> u64; - /// A benchmark function that adds one to each value in the given vector and returns the - /// result. - fn benchmark_vector_add_one(vec: &Vec) -> Vec; - /// A function that always fails to convert a parameter between runtime and node. - fn fail_convert_parameter(param: DecodeFails); - /// A function that always fails to convert its return value between runtime and node. - fn fail_convert_return_value() -> DecodeFails; - /// A function for that the signature changed in version `2`. - #[changed_in(2)] - fn function_signature_changed() -> Vec; - /// The new signature. - fn function_signature_changed() -> u64; - fn fail_on_native() -> u64; - fn fail_on_wasm() -> u64; - /// trie no_std testing - fn use_trie() -> u64; - fn benchmark_indirect_call() -> u64; - fn benchmark_direct_call() -> u64; - fn vec_with_capacity(size: u32) -> Vec; - /// Returns the initialized block number. - fn get_block_number() -> u64; - /// Takes and returns the initialized block number. - fn take_block_number() -> Option; - /// Test that `ed25519` crypto works in the runtime. - /// - /// Returns the signature generated for the message `ed25519` and the public key. - fn test_ed25519_crypto() -> (ed25519::AppSignature, ed25519::AppPublic); - /// Test that `sr25519` crypto works in the runtime. - /// - /// Returns the signature generated for the message `sr25519`. - fn test_sr25519_crypto() -> (sr25519::AppSignature, sr25519::AppPublic); - /// Test that `ecdsa` crypto works in the runtime. - /// - /// Returns the signature generated for the message `ecdsa`. - fn test_ecdsa_crypto() -> (ecdsa::AppSignature, ecdsa::AppPublic); - /// Run various tests against storage. - fn test_storage(); - /// Check a witness. - fn test_witness(proof: StorageProof, root: crate::Hash); - /// Test that ensures that we can call a function that takes multiple - /// arguments. - fn test_multiple_arguments(data: Vec, other: Vec, num: u32); - /// Traces log "Hey I'm runtime." - fn do_trace_log(); - /// Verify the given signature, public & message bundle. - fn verify_ed25519(sig: ed25519::Signature, public: ed25519::Public, message: Vec) -> bool; - } - } - } else { - decl_runtime_apis! { - pub trait TestAPI { - /// Return the balance of the given account id. - fn balance_of(id: AccountId) -> u64; - /// A benchmark function that adds one to the given value and returns the result. - fn benchmark_add_one(val: &u64) -> u64; - /// A benchmark function that adds one to each value in the given vector and returns the - /// result. - fn benchmark_vector_add_one(vec: &Vec) -> Vec; - /// A function that always fails to convert a parameter between runtime and node. - fn fail_convert_parameter(param: DecodeFails); - /// A function that always fails to convert its return value between runtime and node. - fn fail_convert_return_value() -> DecodeFails; - /// In wasm we just emulate the old behavior. - fn function_signature_changed() -> Vec; - fn fail_on_native() -> u64; - fn fail_on_wasm() -> u64; - /// trie no_std testing - fn use_trie() -> u64; - fn benchmark_indirect_call() -> u64; - fn benchmark_direct_call() -> u64; - fn vec_with_capacity(size: u32) -> Vec; - /// Returns the initialized block number. - fn get_block_number() -> u64; - /// Takes and returns the initialized block number. - fn take_block_number() -> Option; - /// Test that `ed25519` crypto works in the runtime. - /// - /// Returns the signature generated for the message `ed25519` and the public key. - fn test_ed25519_crypto() -> (ed25519::AppSignature, ed25519::AppPublic); - /// Test that `sr25519` crypto works in the runtime. - /// - /// Returns the signature generated for the message `sr25519`. - fn test_sr25519_crypto() -> (sr25519::AppSignature, sr25519::AppPublic); - /// Test that `ecdsa` crypto works in the runtime. - /// - /// Returns the signature generated for the message `ecdsa`. - fn test_ecdsa_crypto() -> (ecdsa::AppSignature, ecdsa::AppPublic); - /// Run various tests against storage. - fn test_storage(); - /// Check a witness. - fn test_witness(proof: StorageProof, root: crate::Hash); - /// Test that ensures that we can call a function that takes multiple - /// arguments. - fn test_multiple_arguments(data: Vec, other: Vec, num: u32); - /// Traces log "Hey I'm runtime." - fn do_trace_log(); - /// Verify the given signature, public & message bundle. - fn verify_ed25519(sig: ed25519::Signature, public: ed25519::Public, message: Vec) -> bool; - } - } - } -} - -#[derive(Clone, Eq, PartialEq, TypeInfo)] -pub struct Runtime; - -impl GetNodeBlockType for Runtime { - type NodeBlock = Block; -} - -impl GetRuntimeBlockType for Runtime { - type RuntimeBlock = Block; -} - -#[derive(Clone, RuntimeDebug, Encode, Decode, PartialEq, Eq, TypeInfo, MaxEncodedLen)] -pub struct RuntimeOrigin; - -impl From::AccountId>> for RuntimeOrigin { - fn from(_: RawOrigin<::AccountId>) -> Self { - unimplemented!("Not required in tests!") +/// Balance of an account. +pub type Balance = u64; + +decl_runtime_apis! { + #[api_version(2)] + pub trait TestAPI { + /// Return the balance of the given account id. + fn balance_of(id: AccountId) -> u64; + /// A benchmark function that adds one to the given value and returns the result. + fn benchmark_add_one(val: &u64) -> u64; + /// A benchmark function that adds one to each value in the given vector and returns the + /// result. + fn benchmark_vector_add_one(vec: &Vec) -> Vec; + /// A function for that the signature changed in version `2`. + #[changed_in(2)] + fn function_signature_changed() -> Vec; + /// The new signature. + fn function_signature_changed() -> u64; + /// trie no_std testing + fn use_trie() -> u64; + /// Calls function in the loop using never-inlined function pointer + fn benchmark_indirect_call() -> u64; + /// Calls function in the loop + fn benchmark_direct_call() -> u64; + /// Allocates vector with given capacity. + fn vec_with_capacity(size: u32) -> Vec; + /// Returns the initialized block number. + fn get_block_number() -> u64; + + /// Test that `ed25519` crypto works in the runtime. + /// + /// Returns the signature generated for the message `ed25519` and the public key. + fn test_ed25519_crypto() -> (ed25519::AppSignature, ed25519::AppPublic); + /// Test that `sr25519` crypto works in the runtime. + /// + /// Returns the signature generated for the message `sr25519`. + fn test_sr25519_crypto() -> (sr25519::AppSignature, sr25519::AppPublic); + /// Test that `ecdsa` crypto works in the runtime. + /// + /// Returns the signature generated for the message `ecdsa`. + fn test_ecdsa_crypto() -> (ecdsa::AppSignature, ecdsa::AppPublic); + /// Run various tests against storage. + fn test_storage(); + /// Check a witness. + fn test_witness(proof: StorageProof, root: crate::Hash); + /// Test that ensures that we can call a function that takes multiple + /// arguments. + fn test_multiple_arguments(data: Vec, other: Vec, num: u32); + /// Traces log "Hey I'm runtime." + fn do_trace_log(); + /// Verify the given signature, public & message bundle. + fn verify_ed25519(sig: ed25519::Signature, public: ed25519::Public, message: Vec) -> bool; + /// Write the given `value` under the given `key` into the storage and then optional panic. + fn write_key_value(key: Vec, value: Vec, panic: bool); } } -impl CallerTrait<::AccountId> for RuntimeOrigin { - fn into_system(self) -> Option::AccountId>> { - unimplemented!("Not required in tests!") - } +pub type Executive = frame_executive::Executive< + Runtime, + Block, + frame_system::ChainContext, + Runtime, + AllPalletsWithSystem, +>; - fn as_system_ref(&self) -> Option<&RawOrigin<::AccountId>> { - unimplemented!("Not required in tests!") - } -} +#[derive(Copy, Clone, PartialEq, Eq, Encode, Decode, RuntimeDebug, TypeInfo)] +pub struct CheckSubstrateCall; -impl From for Result, RuntimeOrigin> { - fn from(_origin: RuntimeOrigin) -> Result, RuntimeOrigin> { - unimplemented!("Not required in tests!") +impl sp_runtime::traits::Printable for CheckSubstrateCall { + fn print(&self) { + "CheckSubstrateCall".print() } } -impl frame_support::traits::OriginTrait for RuntimeOrigin { - type Call = ::RuntimeCall; - type PalletsOrigin = RuntimeOrigin; - type AccountId = ::AccountId; - - fn add_filter(&mut self, _filter: impl Fn(&Self::Call) -> bool + 'static) { - unimplemented!("Not required in tests!") - } - - fn reset_filter(&mut self) { - unimplemented!("Not required in tests!") - } - - fn set_caller_from(&mut self, _other: impl Into) { - unimplemented!("Not required in tests!") - } +impl sp_runtime::traits::Dispatchable for CheckSubstrateCall { + type RuntimeOrigin = CheckSubstrateCall; + type Config = CheckSubstrateCall; + type Info = CheckSubstrateCall; + type PostInfo = CheckSubstrateCall; - fn filter_call(&self, _call: &Self::Call) -> bool { - unimplemented!("Not required in tests!") - } - - fn caller(&self) -> &Self::PalletsOrigin { - unimplemented!("Not required in tests!") - } - - fn into_caller(self) -> Self::PalletsOrigin { - unimplemented!("Not required in tests!") - } - - fn try_with_caller( + fn dispatch( self, - _f: impl FnOnce(Self::PalletsOrigin) -> Result, - ) -> Result { - unimplemented!("Not required in tests!") - } - - fn none() -> Self { - unimplemented!("Not required in tests!") - } - fn root() -> Self { - unimplemented!("Not required in tests!") - } - fn signed(_by: Self::AccountId) -> Self { - unimplemented!("Not required in tests!") - } - fn as_signed(self) -> Option { - unimplemented!("Not required in tests!") - } - fn as_system_ref(&self) -> Option<&RawOrigin> { - unimplemented!("Not required in tests!") + _origin: Self::RuntimeOrigin, + ) -> sp_runtime::DispatchResultWithInfo { + panic!("This implementation should not be used for actual dispatch."); } } -#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo)] -pub struct RuntimeEvent; - -impl From> for RuntimeEvent { - fn from(_evt: frame_system::Event) -> Self { - unimplemented!("Not required in tests!") +impl sp_runtime::traits::SignedExtension for CheckSubstrateCall { + type AccountId = AccountId; + type Call = RuntimeCall; + type AdditionalSigned = (); + type Pre = (); + const IDENTIFIER: &'static str = "CheckSubstrateCall"; + + fn additional_signed( + &self, + ) -> sp_std::result::Result { + Ok(()) } -} -impl frame_support::traits::PalletInfo for Runtime { - fn index() -> Option { - let type_id = sp_std::any::TypeId::of::

(); - if type_id == sp_std::any::TypeId::of::>() { - return Some(0) - } - if type_id == sp_std::any::TypeId::of::>() { - return Some(1) - } - if type_id == sp_std::any::TypeId::of::>() { - return Some(2) - } - - None - } - fn name() -> Option<&'static str> { - let type_id = sp_std::any::TypeId::of::

(); - if type_id == sp_std::any::TypeId::of::>() { - return Some("System") - } - if type_id == sp_std::any::TypeId::of::>() { - return Some("Timestamp") - } - if type_id == sp_std::any::TypeId::of::>() { - return Some("Babe") + fn validate( + &self, + _who: &Self::AccountId, + call: &Self::Call, + _info: &DispatchInfoOf, + _len: usize, + ) -> TransactionValidity { + log::trace!(target: LOG_TARGET, "validate"); + match call { + RuntimeCall::SubstrateTest(ref substrate_test_call) => + substrate_test_pallet::validate_runtime_call(substrate_test_call), + _ => Ok(Default::default()), } - - None } - fn module_name() -> Option<&'static str> { - let type_id = sp_std::any::TypeId::of::

(); - if type_id == sp_std::any::TypeId::of::>() { - return Some("system") - } - if type_id == sp_std::any::TypeId::of::>() { - return Some("pallet_timestamp") - } - if type_id == sp_std::any::TypeId::of::>() { - return Some("pallet_babe") - } - None + fn pre_dispatch( + self, + who: &Self::AccountId, + call: &Self::Call, + info: &sp_runtime::traits::DispatchInfoOf, + len: usize, + ) -> Result { + self.validate(who, call, info, len).map(drop) } - fn crate_version() -> Option { - use frame_support::traits::PalletInfoAccess as _; - let type_id = sp_std::any::TypeId::of::

(); - if type_id == sp_std::any::TypeId::of::>() { - return Some(system::Pallet::::crate_version()) - } - if type_id == sp_std::any::TypeId::of::>() { - return Some(pallet_timestamp::Pallet::::crate_version()) - } - if type_id == sp_std::any::TypeId::of::>() { - return Some(pallet_babe::Pallet::::crate_version()) - } +} - None +construct_runtime!( + pub enum Runtime + { + System: frame_system, + Babe: pallet_babe, + SubstrateTest: substrate_test_pallet::pallet, + Balances: pallet_balances, } -} +); + +/// We assume that ~10% of the block weight is consumed by `on_initialize` handlers. +/// This is used to limit the maximal weight of a single extrinsic. +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. +const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); +/// Max weight, actual value does not matter for test runtime. +const MAXIMUM_BLOCK_WEIGHT: Weight = + Weight::from_parts(WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), u64::MAX); parameter_types! { - pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { - read: 100, - write: 1000, - }; - pub RuntimeBlockLength: BlockLength = - BlockLength::max(4 * 1024 * 1024); - pub RuntimeBlockWeights: BlockWeights = - BlockWeights::with_sensible_defaults(Weight::from_ref_time(4 * 1024 * 1024), Perbill::from_percent(75)); -} + pub const BlockHashCount: BlockNumber = 2400; + pub const Version: RuntimeVersion = VERSION; -impl From> for Extrinsic { - fn from(_: frame_system::Call) -> Self { - unimplemented!("Not required in tests!") - } + pub RuntimeBlockLength: BlockLength = + BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + + pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(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( + MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); } impl frame_system::pallet::Config for Runtime { type BaseCallFilter = frame_support::traits::Everything; type BlockWeights = RuntimeBlockWeights; - type BlockLength = RuntimeBlockLength; + type BlockLength = (); type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = Extrinsic; - type Index = u64; - type BlockNumber = u64; + type RuntimeCall = RuntimeCall; + type Nonce = Nonce; type Hash = H256; type Hashing = Hashing; - type AccountId = u64; - type Lookup = IdentityLookup; - type Header = Header; + type AccountId = AccountId; + type Lookup = sp_runtime::traits::IdentityLookup; + type Block = Block; type RuntimeEvent = RuntimeEvent; type BlockHashCount = ConstU64<2400>; type DbWeight = (); type Version = (); - type PalletInfo = Self; - type AccountData = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; type OnNewAccount = (); type OnKilledAccount = (); type SystemWeightInfo = (); @@ -637,14 +365,45 @@ impl frame_system::pallet::Config for Runtime { type MaxConsumers = ConstU32<16>; } -impl system::Config for Runtime {} +pub mod currency { + use crate::Balance; + const MILLICENTS: Balance = 1_000_000_000; + const CENTS: Balance = 1_000 * MILLICENTS; // assume this is worth about a cent. + pub const DOLLARS: Balance = 100 * CENTS; +} +parameter_types! { + pub const ExistentialDeposit: Balance = 1 * currency::DOLLARS; + // For weight estimation, we assume that the most locks on an individual account will be 50. + // This number may need to be adjusted in the future if this assumption no longer holds true. + pub const MaxLocks: u32 = 50; + pub const MaxReserves: u32 = 50; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = MaxLocks; + type MaxReserves = MaxReserves; + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = pallet_balances::weights::SubstrateWeight; + type FreezeIdentifier = (); + type MaxFreezes = (); + type RuntimeHoldReason = RuntimeHoldReason; + type MaxHolds = ConstU32<1>; +} + +impl substrate_test_pallet::Config for Runtime {} + +// Required for `pallet_babe::Config`. impl pallet_timestamp::Config for Runtime { - /// A timestamp: milliseconds since the unix epoch. type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = ConstU64<5>; - type WeightInfo = (); + type OnTimestampSet = Babe; + type MinimumPeriod = ConstU64<500>; + type WeightInfo = pallet_timestamp::weights::SubstrateWeight; } parameter_types! { @@ -654,26 +413,13 @@ parameter_types! { impl pallet_babe::Config for Runtime { type EpochDuration = EpochDuration; type ExpectedBlockTime = ConstU64<10_000>; - // there is no actual runtime in this test-runtime, so testing crates - // are manually adding the digests. normally in this situation you'd use - // pallet_babe::SameAuthoritiesForever. - type EpochChangeTrigger = pallet_babe::ExternalTrigger; + type EpochChangeTrigger = pallet_babe::SameAuthoritiesForever; type DisabledValidators = (); - - type KeyOwnerProofSystem = (); - - type KeyOwnerProof = - >::Proof; - - type KeyOwnerIdentification = >::IdentificationTuple; - - type HandleEquivocation = (); + type KeyOwnerProof = sp_core::Void; + type EquivocationReportSystem = (); type WeightInfo = (); - type MaxAuthorities = ConstU32<10>; + type MaxNominators = ConstU32<100>; } /// Adds one to the given input and returns the final result. @@ -682,11 +428,6 @@ fn benchmark_add_one(i: u64) -> u64 { i + 1 } -/// The `benchmark_add_one` function as function pointer. -#[cfg(not(feature = "std"))] -static BENCHMARK_ADD_ONE: sp_runtime_interface::wasm::ExchangeableFunction u64> = - sp_runtime_interface::wasm::ExchangeableFunction::new(benchmark_add_one); - fn code_using_trie() -> u64 { let pairs = [ (b"0103000000000000000464".to_vec(), b"0400000000".to_vec()), @@ -719,516 +460,262 @@ impl_opaque_keys! { } } -cfg_if! { - if #[cfg(feature = "std")] { - impl_runtime_apis! { - impl sp_api::Core for Runtime { - fn version() -> RuntimeVersion { - version() - } - - fn execute_block(block: Block) { - system::execute_block(block); - } - - fn initialize_block(header: &::Header) { - system::initialize_block(header) - } - } +pub(crate) const TEST_RUNTIME_BABE_EPOCH_CONFIGURATION: BabeEpochConfiguration = + BabeEpochConfiguration { + c: (3, 10), + allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots, + }; - impl sp_api::Metadata for Runtime { - fn metadata() -> OpaqueMetadata { - unimplemented!() - } - } +impl_runtime_apis! { + impl sp_api::Core for Runtime { + fn version() -> RuntimeVersion { + version() + } - impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { - fn validate_transaction( - _source: TransactionSource, - utx: ::Extrinsic, - _: ::Hash, - ) -> TransactionValidity { - if let Extrinsic::IncludeData(data) = utx { - return Ok(ValidTransaction { - priority: data.len() as u64, - requires: vec![], - provides: vec![data], - longevity: 1, - propagate: false, - }); - } - - system::validate_transaction(utx) - } - } + fn execute_block(block: Block) { + log::trace!(target: LOG_TARGET, "execute_block: {block:#?}"); + Executive::execute_block(block); + } - impl sp_block_builder::BlockBuilder for Runtime { - fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { - system::execute_transaction(extrinsic) - } + fn initialize_block(header: &::Header) { + log::trace!(target: LOG_TARGET, "initialize_block: {header:#?}"); + Executive::initialize_block(header); + } + } - fn finalize_block() -> ::Header { - system::finalize_block() - } + impl sp_api::Metadata for Runtime { + fn metadata() -> OpaqueMetadata { + unimplemented!() + } - fn inherent_extrinsics(_data: InherentData) -> Vec<::Extrinsic> { - vec![] - } + fn metadata_at_version(_version: u32) -> Option { + unimplemented!() + } + fn metadata_versions() -> sp_std::vec::Vec { + unimplemented!() + } + } - fn check_inherents(_block: Block, _data: InherentData) -> CheckInherentsResult { - CheckInherentsResult::new() - } - } + impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { + fn validate_transaction( + source: TransactionSource, + utx: ::Extrinsic, + block_hash: ::Hash, + ) -> TransactionValidity { + let validity = Executive::validate_transaction(source, utx.clone(), block_hash); + log::trace!(target: LOG_TARGET, "validate_transaction {:?} {:?}", utx, validity); + validity + } + } - impl self::TestAPI for Runtime { - fn balance_of(id: AccountId) -> u64 { - system::balance_of(id) - } - - fn benchmark_add_one(val: &u64) -> u64 { - val + 1 - } - - fn benchmark_vector_add_one(vec: &Vec) -> Vec { - let mut vec = vec.clone(); - vec.iter_mut().for_each(|v| *v += 1); - vec - } - - fn fail_convert_parameter(_: DecodeFails) {} - - fn fail_convert_return_value() -> DecodeFails { - DecodeFails::default() - } - - fn function_signature_changed() -> u64 { - 1 - } - - fn fail_on_native() -> u64 { - panic!("Failing because we are on native") - } - fn fail_on_wasm() -> u64 { - 1 - } - - fn use_trie() -> u64 { - code_using_trie() - } - - fn benchmark_indirect_call() -> u64 { - let function = benchmark_add_one; - (0..1000).fold(0, |p, i| p + function(i)) - } - fn benchmark_direct_call() -> u64 { - (0..1000).fold(0, |p, i| p + benchmark_add_one(i)) - } - - fn vec_with_capacity(_size: u32) -> Vec { - unimplemented!("is not expected to be invoked from non-wasm builds"); - } - - fn get_block_number() -> u64 { - system::get_block_number().expect("Block number is initialized") - } - - fn take_block_number() -> Option { - system::take_block_number() - } - - fn test_ed25519_crypto() -> (ed25519::AppSignature, ed25519::AppPublic) { - test_ed25519_crypto() - } - - fn test_sr25519_crypto() -> (sr25519::AppSignature, sr25519::AppPublic) { - test_sr25519_crypto() - } - - fn test_ecdsa_crypto() -> (ecdsa::AppSignature, ecdsa::AppPublic) { - test_ecdsa_crypto() - } - - fn test_storage() { - test_read_storage(); - test_read_child_storage(); - } - - fn test_witness(proof: StorageProof, root: crate::Hash) { - test_witness(proof, root); - } - - fn test_multiple_arguments(data: Vec, other: Vec, num: u32) { - assert_eq!(&data[..], &other[..]); - assert_eq!(data.len(), num as usize); - } - - fn do_trace_log() { - log::trace!("Hey I'm runtime"); - } - - fn verify_ed25519(sig: ed25519::Signature, public: ed25519::Public, message: Vec) -> bool { - sp_io::crypto::ed25519_verify(&sig, &message, &public) - } - } + impl sp_block_builder::BlockBuilder for Runtime { + fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { + Executive::apply_extrinsic(extrinsic) + } - impl sp_consensus_aura::AuraApi for Runtime { - fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(1000) - } - - fn authorities() -> Vec { - system::authorities().into_iter().map(|a| { - let authority: sr25519::Public = a.into(); - AuraId::from(authority) - }).collect() - } - } + fn finalize_block() -> ::Header { + log::trace!(target: LOG_TARGET, "finalize_block"); + Executive::finalize_block() + } - impl sp_consensus_babe::BabeApi for Runtime { - fn configuration() -> sp_consensus_babe::BabeConfiguration { - sp_consensus_babe::BabeConfiguration { - slot_duration: 1000, - epoch_length: EpochDuration::get(), - c: (3, 10), - authorities: system::authorities() - .into_iter().map(|x|(x, 1)).collect(), - randomness: >::randomness(), - allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots, - } - } - - fn current_epoch_start() -> Slot { - >::current_epoch_start() - } - - fn current_epoch() -> sp_consensus_babe::Epoch { - >::current_epoch() - } - - fn next_epoch() -> sp_consensus_babe::Epoch { - >::next_epoch() - } - - fn submit_report_equivocation_unsigned_extrinsic( - _equivocation_proof: sp_consensus_babe::EquivocationProof< - ::Header, - >, - _key_owner_proof: sp_consensus_babe::OpaqueKeyOwnershipProof, - ) -> Option<()> { - None - } - - fn generate_key_ownership_proof( - _slot: sp_consensus_babe::Slot, - _authority_id: sp_consensus_babe::AuthorityId, - ) -> Option { - None - } - } + fn inherent_extrinsics(_data: InherentData) -> Vec<::Extrinsic> { + vec![] + } - impl sp_offchain::OffchainWorkerApi for Runtime { - fn offchain_worker(header: &::Header) { - let ex = Extrinsic::IncludeData(header.number.encode()); - sp_io::offchain::submit_transaction(ex.encode()).unwrap(); - } - } + fn check_inherents(_block: Block, _data: InherentData) -> CheckInherentsResult { + CheckInherentsResult::new() + } + } - impl sp_session::SessionKeys for Runtime { - fn generate_session_keys(_: Option>) -> Vec { - SessionKeys::generate(None) - } + impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { + fn account_nonce(account: AccountId) -> Nonce { + System::account_nonce(account) + } + } - fn decode_session_keys( - encoded: Vec, - ) -> Option, sp_core::crypto::KeyTypeId)>> { - SessionKeys::decode_into_raw_public_keys(&encoded) - } - } + impl self::TestAPI for Runtime { + fn balance_of(id: AccountId) -> u64 { + Balances::free_balance(id) + } - impl sp_finality_grandpa::GrandpaApi for Runtime { - fn grandpa_authorities() -> sp_finality_grandpa::AuthorityList { - Vec::new() - } - - fn current_set_id() -> sp_finality_grandpa::SetId { - 0 - } - - fn submit_report_equivocation_unsigned_extrinsic( - _equivocation_proof: sp_finality_grandpa::EquivocationProof< - ::Hash, - NumberFor, - >, - _key_owner_proof: sp_finality_grandpa::OpaqueKeyOwnershipProof, - ) -> Option<()> { - None - } - - fn generate_key_ownership_proof( - _set_id: sp_finality_grandpa::SetId, - _authority_id: sp_finality_grandpa::AuthorityId, - ) -> Option { - None - } - } + fn benchmark_add_one(val: &u64) -> u64 { + val + 1 + } - impl beefy_primitives::BeefyApi for Runtime { - fn validator_set() -> Option> { - None - } - } + fn benchmark_vector_add_one(vec: &Vec) -> Vec { + let mut vec = vec.clone(); + vec.iter_mut().for_each(|v| *v += 1); + vec + } - impl beefy_merkle_tree::BeefyMmrApi for Runtime { - fn authority_set_proof() -> beefy_primitives::mmr::BeefyAuthoritySet { - Default::default() - } + fn function_signature_changed() -> u64 { + 1 + } - fn next_authority_set_proof() -> beefy_primitives::mmr::BeefyNextAuthoritySet { - Default::default() - } - } + fn use_trie() -> u64 { + code_using_trie() + } - impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { - fn account_nonce(_account: AccountId) -> Index { - 0 - } - } + fn benchmark_indirect_call() -> u64 { + let function = benchmark_add_one; + (0..1000).fold(0, |p, i| p + function(i)) + } + fn benchmark_direct_call() -> u64 { + (0..1000).fold(0, |p, i| p + benchmark_add_one(i)) } - } else { - impl_runtime_apis! { - impl sp_api::Core for Runtime { - fn version() -> RuntimeVersion { - version() - } - - fn execute_block(block: Block) { - system::execute_block(block); - } - - fn initialize_block(header: &::Header) { - system::initialize_block(header) - } - } - impl sp_api::Metadata for Runtime { - fn metadata() -> OpaqueMetadata { - unimplemented!() - } - } + fn vec_with_capacity(size: u32) -> Vec { + Vec::with_capacity(size as usize) + } - impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { - fn validate_transaction( - _source: TransactionSource, - utx: ::Extrinsic, - _: ::Hash, - ) -> TransactionValidity { - if let Extrinsic::IncludeData(data) = utx { - return Ok(ValidTransaction{ - priority: data.len() as u64, - requires: vec![], - provides: vec![data], - longevity: 1, - propagate: false, - }); - } - - system::validate_transaction(utx) - } - } + fn get_block_number() -> u64 { + System::block_number() + } - impl sp_block_builder::BlockBuilder for Runtime { - fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { - system::execute_transaction(extrinsic) - } + fn test_ed25519_crypto() -> (ed25519::AppSignature, ed25519::AppPublic) { + test_ed25519_crypto() + } - fn finalize_block() -> ::Header { - system::finalize_block() - } + fn test_sr25519_crypto() -> (sr25519::AppSignature, sr25519::AppPublic) { + test_sr25519_crypto() + } - fn inherent_extrinsics(_data: InherentData) -> Vec<::Extrinsic> { - vec![] - } + fn test_ecdsa_crypto() -> (ecdsa::AppSignature, ecdsa::AppPublic) { + test_ecdsa_crypto() + } - fn check_inherents(_block: Block, _data: InherentData) -> CheckInherentsResult { - CheckInherentsResult::new() - } - } + fn test_storage() { + test_read_storage(); + test_read_child_storage(); + } - impl self::TestAPI for Runtime { - fn balance_of(id: AccountId) -> u64 { - system::balance_of(id) - } - - fn benchmark_add_one(val: &u64) -> u64 { - val + 1 - } - - fn benchmark_vector_add_one(vec: &Vec) -> Vec { - let mut vec = vec.clone(); - vec.iter_mut().for_each(|v| *v += 1); - vec - } - - fn fail_convert_parameter(_: DecodeFails) {} - - fn fail_convert_return_value() -> DecodeFails { - DecodeFails::default() - } - - fn function_signature_changed() -> Vec { - let mut vec = Vec::new(); - vec.push(1); - vec.push(2); - vec - } - - fn fail_on_native() -> u64 { - 1 - } - - fn fail_on_wasm() -> u64 { - panic!("Failing because we are on wasm") - } - - fn use_trie() -> u64 { - code_using_trie() - } - - fn benchmark_indirect_call() -> u64 { - (0..10000).fold(0, |p, i| p + BENCHMARK_ADD_ONE.get()(i)) - } - - fn benchmark_direct_call() -> u64 { - (0..10000).fold(0, |p, i| p + benchmark_add_one(i)) - } - - fn vec_with_capacity(size: u32) -> Vec { - Vec::with_capacity(size as usize) - } - - fn get_block_number() -> u64 { - system::get_block_number().expect("Block number is initialized") - } - - fn take_block_number() -> Option { - system::take_block_number() - } - - fn test_ed25519_crypto() -> (ed25519::AppSignature, ed25519::AppPublic) { - test_ed25519_crypto() - } - - fn test_sr25519_crypto() -> (sr25519::AppSignature, sr25519::AppPublic) { - test_sr25519_crypto() - } - - fn test_ecdsa_crypto() -> (ecdsa::AppSignature, ecdsa::AppPublic) { - test_ecdsa_crypto() - } - - fn test_storage() { - test_read_storage(); - test_read_child_storage(); - } - - fn test_witness(proof: StorageProof, root: crate::Hash) { - test_witness(proof, root); - } - - fn test_multiple_arguments(data: Vec, other: Vec, num: u32) { - assert_eq!(&data[..], &other[..]); - assert_eq!(data.len(), num as usize); - } - - fn do_trace_log() { - log::trace!("Hey I'm runtime: {}", log::STATIC_MAX_LEVEL); - } - - fn verify_ed25519(sig: ed25519::Signature, public: ed25519::Public, message: Vec) -> bool { - sp_io::crypto::ed25519_verify(&sig, &message, &public) - } - } + fn test_witness(proof: StorageProof, root: crate::Hash) { + test_witness(proof, root); + } - impl sp_consensus_aura::AuraApi for Runtime { - fn slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(1000) - } - - fn authorities() -> Vec { - system::authorities().into_iter().map(|a| { - let authority: sr25519::Public = a.into(); - AuraId::from(authority) - }).collect() - } - } + fn test_multiple_arguments(data: Vec, other: Vec, num: u32) { + assert_eq!(&data[..], &other[..]); + assert_eq!(data.len(), num as usize); + } - impl sp_consensus_babe::BabeApi for Runtime { - fn configuration() -> sp_consensus_babe::BabeConfiguration { - sp_consensus_babe::BabeConfiguration { - slot_duration: 1000, - epoch_length: EpochDuration::get(), - c: (3, 10), - authorities: system::authorities() - .into_iter().map(|x|(x, 1)).collect(), - randomness: >::randomness(), - allowed_slots: AllowedSlots::PrimaryAndSecondaryPlainSlots, - } - } - - fn current_epoch_start() -> Slot { - >::current_epoch_start() - } - - fn current_epoch() -> sp_consensus_babe::Epoch { - >::current_epoch() - } - - fn next_epoch() -> sp_consensus_babe::Epoch { - >::next_epoch() - } - - fn submit_report_equivocation_unsigned_extrinsic( - _equivocation_proof: sp_consensus_babe::EquivocationProof< - ::Header, - >, - _key_owner_proof: sp_consensus_babe::OpaqueKeyOwnershipProof, - ) -> Option<()> { - None - } - - fn generate_key_ownership_proof( - _slot: sp_consensus_babe::Slot, - _authority_id: sp_consensus_babe::AuthorityId, - ) -> Option { - None - } - } + fn do_trace_log() { + log::trace!("Hey I'm runtime"); + } - impl sp_offchain::OffchainWorkerApi for Runtime { - fn offchain_worker(header: &::Header) { - let ex = Extrinsic::IncludeData(header.number.encode()); - sp_io::offchain::submit_transaction(ex.encode()).unwrap() - } - } + fn verify_ed25519(sig: ed25519::Signature, public: ed25519::Public, message: Vec) -> bool { + sp_io::crypto::ed25519_verify(&sig, &message, &public) + } - impl sp_session::SessionKeys for Runtime { - fn generate_session_keys(_: Option>) -> Vec { - SessionKeys::generate(None) - } + fn write_key_value(key: Vec, value: Vec, panic: bool) { + sp_io::storage::set(&key, &value); - fn decode_session_keys( - encoded: Vec, - ) -> Option, sp_core::crypto::KeyTypeId)>> { - SessionKeys::decode_into_raw_public_keys(&encoded) - } + if panic { + panic!("I'm just following my master"); } + } + } + + impl sp_consensus_aura::AuraApi for Runtime { + fn slot_duration() -> sp_consensus_aura::SlotDuration { + sp_consensus_aura::SlotDuration::from_millis(1000) + } - impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { - fn account_nonce(_account: AccountId) -> Index { - 0 - } + fn authorities() -> Vec { + SubstrateTest::authorities().into_iter().map(|auth| AuraId::from(auth)).collect() + } + } + + impl sp_consensus_babe::BabeApi for Runtime { + fn configuration() -> sp_consensus_babe::BabeConfiguration { + let epoch_config = Babe::epoch_config().unwrap_or(TEST_RUNTIME_BABE_EPOCH_CONFIGURATION); + sp_consensus_babe::BabeConfiguration { + slot_duration: Babe::slot_duration(), + epoch_length: EpochDuration::get(), + c: epoch_config.c, + authorities: Babe::authorities().to_vec(), + randomness: Babe::randomness(), + allowed_slots: epoch_config.allowed_slots, } } + + fn current_epoch_start() -> Slot { + Babe::current_epoch_start() + } + + fn current_epoch() -> sp_consensus_babe::Epoch { + Babe::current_epoch() + } + + fn next_epoch() -> sp_consensus_babe::Epoch { + Babe::next_epoch() + } + + fn submit_report_equivocation_unsigned_extrinsic( + _equivocation_proof: sp_consensus_babe::EquivocationProof< + ::Header, + >, + _key_owner_proof: sp_consensus_babe::OpaqueKeyOwnershipProof, + ) -> Option<()> { + None + } + + fn generate_key_ownership_proof( + _slot: sp_consensus_babe::Slot, + _authority_id: sp_consensus_babe::AuthorityId, + ) -> Option { + None + } + } + + impl sp_offchain::OffchainWorkerApi for Runtime { + fn offchain_worker(header: &::Header) { + let ext = Extrinsic::new_unsigned( + substrate_test_pallet::pallet::Call::storage_change{ + key:b"some_key".encode(), + value:Some(header.number.encode()) + }.into(), + ); + sp_io::offchain::submit_transaction(ext.encode()).unwrap(); + } + } + + impl sp_session::SessionKeys for Runtime { + fn generate_session_keys(_: Option>) -> Vec { + SessionKeys::generate(None) + } + + fn decode_session_keys( + encoded: Vec, + ) -> Option, sp_core::crypto::KeyTypeId)>> { + SessionKeys::decode_into_raw_public_keys(&encoded) + } + } + + impl sp_consensus_grandpa::GrandpaApi for Runtime { + fn grandpa_authorities() -> sp_consensus_grandpa::AuthorityList { + Vec::new() + } + + fn current_set_id() -> sp_consensus_grandpa::SetId { + 0 + } + + 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: sp_consensus_grandpa::AuthorityId, + ) -> Option { + None + } } } @@ -1328,64 +815,256 @@ fn test_witness(proof: StorageProof, root: crate::Hash) { assert!(ext.storage_root(Default::default()).as_slice() != &root[..]); } +/// Some tests require the hashed keys of the storage. As the values of hashed keys are not trivial +/// to guess, this small module provides the values of the keys, and the code which is required to +/// generate the keys. +#[cfg(feature = "std")] +pub mod storage_key_generator { + use super::*; + use sp_core::Pair; + use sp_keyring::AccountKeyring; + + /// Generate hex string without prefix + pub(super) fn hex(x: T) -> String + where + T: array_bytes::Hex, + { + x.hex(Default::default()) + } + + fn concat_hashes(input: &Vec<&[u8]>) -> String { + input.iter().map(|s| sp_core::hashing::twox_128(s)).map(hex).collect() + } + + fn twox_64_concat(x: &[u8]) -> Vec { + sp_core::hashing::twox_64(x).iter().chain(x.iter()).cloned().collect::>() + } + + /// Generate the hashed storage keys from the raw literals. These keys are expected to be be in + /// storage with given substrate-test runtime. + pub fn generate_expected_storage_hashed_keys(custom_heap_pages: bool) -> Vec { + let mut literals: Vec<&[u8]> = vec![b":code", b":extrinsic_index"]; + + if custom_heap_pages { + literals.push(b":heappages"); + } + + let keys: Vec> = vec![ + vec![b"Babe", b"Authorities"], + vec![b"Babe", b"EpochConfig"], + vec![b"Babe", b"NextAuthorities"], + vec![b"Babe", b"SegmentIndex"], + vec![b"Babe", b":__STORAGE_VERSION__:"], + vec![b"Balances", b":__STORAGE_VERSION__:"], + vec![b"Balances", b"TotalIssuance"], + vec![b"SubstrateTest", b"Authorities"], + vec![b"SubstrateTest", b":__STORAGE_VERSION__:"], + vec![b"System", b"LastRuntimeUpgrade"], + vec![b"System", b"ParentHash"], + vec![b"System", b":__STORAGE_VERSION__:"], + vec![b"System", b"UpgradedToTripleRefCount"], + vec![b"System", b"UpgradedToU32RefCount"], + ]; + + let mut expected_keys = keys.iter().map(concat_hashes).collect::>(); + + expected_keys.extend(literals.into_iter().map(hex)); + + let balances_map_keys = (0..16_usize) + .into_iter() + .map(|i| AccountKeyring::numeric(i).public().to_vec()) + .chain(vec![ + AccountKeyring::Alice.public().to_vec(), + AccountKeyring::Bob.public().to_vec(), + AccountKeyring::Charlie.public().to_vec(), + ]) + .map(|pubkey| { + sp_core::hashing::blake2_128(&pubkey) + .iter() + .chain(pubkey.iter()) + .cloned() + .collect::>() + }) + .map(|hash_pubkey| { + [concat_hashes(&vec![b"System", b"Account"]), hex(hash_pubkey)].concat() + }); + + expected_keys.extend(balances_map_keys); + + expected_keys.push( + [ + concat_hashes(&vec![b"System", b"BlockHash"]), + hex(0u64.using_encoded(twox_64_concat)), + ] + .concat(), + ); + + expected_keys.sort(); + expected_keys + } + + /// Provides the commented list of hashed keys. This contains a hard-coded list of hashed keys + /// that would be generated by `generate_expected_storage_hashed_keys`. This list is provided + /// for the debugging convenience only. Value of each hex-string is documented with the literal + /// origin. + /// + /// `custom_heap_pages`: Should be set to `true` when the state contains the `:heap_pages` key + /// aka when overriding the heap pages to be used by the executor. + pub fn get_expected_storage_hashed_keys(custom_heap_pages: bool) -> Vec<&'static str> { + let mut res = vec![ + //System|:__STORAGE_VERSION__: + "00771836bebdd29870ff246d305c578c4e7b9012096b41c4eb3aaf947f6ea429", + //SubstrateTest|Authorities + "00771836bebdd29870ff246d305c578c5e0621c4869aa60c02be9adcc98a0d1d", + //Babe|:__STORAGE_VERSION__: + "1cb6f36e027abb2091cfb5110ab5087f4e7b9012096b41c4eb3aaf947f6ea429", + //Babe|Authorities + "1cb6f36e027abb2091cfb5110ab5087f5e0621c4869aa60c02be9adcc98a0d1d", + //Babe|SegmentIndex + "1cb6f36e027abb2091cfb5110ab5087f66e8f035c8adbe7f1547b43c51e6f8a4", + //Babe|NextAuthorities + "1cb6f36e027abb2091cfb5110ab5087faacf00b9b41fda7a9268821c2a2b3e4c", + //Babe|EpochConfig + "1cb6f36e027abb2091cfb5110ab5087fdc6b171b77304263c292cc3ea5ed31ef", + //System|:__STORAGE_VERSION__: + "26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429", + //System|UpgradedToU32RefCount + "26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710", + //System|ParentHash + "26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc", + //System::BlockHash|0 + "26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746bb1bdbcacd6ac9340000000000000000", + //System|UpgradedToTripleRefCount + "26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439", + + // System|Account|blake2_128Concat("//11") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da901cae4e3edfbb32c91ed3f01ab964f4eeeab50338d8e5176d3141802d7b010a55dadcd5f23cf8aaafa724627e967e90e", + // System|Account|blake2_128Concat("//4") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da91b614bd4a126f2d5d294e9a8af9da25248d7e931307afb4b68d8d565d4c66e00d856c6d65f5fed6bb82dcfb60e936c67", + // System|Account|blake2_128Concat("//7") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94b21aff9fe1e8b2fc4b0775b8cbeff28ba8e2c7594dd74730f3ca835e95455d199261897edc9735d602ea29615e2b10b", + // System|Account|blake2_128Concat("//Bob") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94f9aea1afa791265fae359272badc1cf8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + // System|Account|blake2_128Concat("//3") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da95786a2916fcb81e1bd5dcd81e0d2452884617f575372edb5a36d85c04cdf2e4699f96fe33eb5f94a28c041b88e398d0c", + // System|Account|blake2_128Concat("//14") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da95b8542d9672c7b7e779cc7c1e6b605691c2115d06120ea2bee32dd601d02f36367564e7ddf84ae2717ca3f097459652e", + // System|Account|blake2_128Concat("//6") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da996c30bdbfab640838e6b6d3c33ab4adb4211b79e34ee8072eab506edd4b93a7b85a14c9a05e5cdd056d98e7dbca87730", + // System|Account|blake2_128Concat("//9") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da99dc65b1339ec388fbf2ca0cdef51253512c6cfd663203ea16968594f24690338befd906856c4d2f4ef32dad578dba20c", + // System|Account|blake2_128Concat("//8") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da99e6eb5abd62f5fd54793da91a47e6af6125d57171ff9241f07acaa1bb6a6103517965cf2cd00e643b27e7599ebccba70", + // System|Account|blake2_128Concat("//Charlie") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b0edae20838083f2cde1c4080db8cf8090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22", + // System|Account|blake2_128Concat("//10") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9d0052993b6f3bd0544fd1f5e4125b9fbde3e789ecd53431fe5c06c12b72137153496dace35c695b5f4d7b41f7ed5763b", + // System|Account|blake2_128Concat("//1") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9d6b7e9a5f12bc571053265dade10d3b4b606fc73f57f03cdb4c932d475ab426043e429cecc2ffff0d2672b0df8398c48", + // System|Account|blake2_128Concat("//Alice") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + // System|Account|blake2_128Concat("//2") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9e1a35f56ee295d39287cbffcfc60c4b346f136b564e1fad55031404dd84e5cd3fa76bfe7cc7599b39d38fd06663bbc0a", + // System|Account|blake2_128Concat("//5") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9e2c1dc507e2035edbbd8776c440d870460c57f0008067cc01c5ff9eb2e2f9b3a94299a915a91198bd1021a6c55596f57", + // System|Account|blake2_128Concat("//0") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9eca0e653a94f4080f6311b4e7b6934eb2afba9278e30ccf6a6ceb3a8b6e336b70068f045c666f2e7f4f9cc5f47db8972", + // System|Account|blake2_128Concat("//13") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9ee8bf7ef90fc56a8aa3b90b344c599550c29b161e27ff8ba45bf6bad4711f326fc506a8803453a4d7e3158e993495f10", + // System|Account|blake2_128Concat("//12") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9f5d6f1c082fe63eec7a71fcad00f4a892e3d43b7b0d04e776e69e7be35247cecdac65504c579195731eaf64b7940966e", + // System|Account|blake2_128Concat("//15") + "26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9fbf0818841edf110e05228a6379763c4fc3c37459d9bdc61f58a5ebc01e9e2305a19d390c0543dc733861ec3cf1de01f", + // System|LastRuntimeUpgrade + "26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8", + // :code + "3a636f6465", + // :extrinsic_index + "3a65787472696e7369635f696e646578", + // Balances|:__STORAGE_VERSION__: + "c2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429", + // Balances|TotalIssuance + "c2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80", + ]; + + if custom_heap_pages { + // :heappages + res.push("3a686561707061676573"); + } + + res + } + + #[test] + fn expected_keys_vec_are_matching() { + assert_eq!( + storage_key_generator::get_expected_storage_hashed_keys(false), + storage_key_generator::generate_expected_storage_hashed_keys(false), + ); + } +} + #[cfg(test)] mod tests { + use super::*; use codec::Encode; + use frame_support::dispatch::DispatchInfo; use sc_block_builder::BlockBuilderProvider; - use sp_api::ProvideRuntimeApi; + use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_consensus::BlockOrigin; - use sp_core::storage::well_known_keys::HEAP_PAGES; - use sp_runtime::generic::BlockId; - use sp_state_machine::ExecutionStrategy; + use sp_core::{storage::well_known_keys::HEAP_PAGES, traits::CallContext}; + use sp_keyring::AccountKeyring; + use sp_runtime::{ + traits::{Hash as _, SignedExtension}, + transaction_validity::{InvalidTransaction, ValidTransaction}, + }; use substrate_test_runtime_client::{ prelude::*, runtime::TestAPI, DefaultTestClientBuilderExt, TestClientBuilder, }; #[test] fn heap_pages_is_respected() { - // This tests that the on-chain HEAP_PAGES parameter is respected. + // This tests that the on-chain `HEAP_PAGES` parameter is respected. // Create a client devoting only 8 pages of wasm memory. This gives us ~512k of heap memory. - let mut client = TestClientBuilder::new() - .set_execution_strategy(ExecutionStrategy::AlwaysWasm) - .set_heap_pages(8) - .build(); - let block_id = BlockId::Hash(client.chain_info().best_hash); + let mut client = TestClientBuilder::new().set_heap_pages(8).build(); + let best_hash = client.chain_info().best_hash; // Try to allocate 1024k of memory on heap. This is going to fail since it is twice larger // than the heap. - let ret = client.runtime_api().vec_with_capacity(&block_id, 1048576); + let mut runtime_api = client.runtime_api(); + // This is currently required to allocate the 1024k of memory as configured above. + runtime_api.set_call_context(CallContext::Onchain); + let ret = runtime_api.vec_with_capacity(best_hash, 1048576); assert!(ret.is_err()); // Create a block that sets the `:heap_pages` to 32 pages of memory which corresponds to // ~2048k of heap memory. - let (new_block_id, block) = { + let (new_at_hash, block) = { let mut builder = client.new_block(Default::default()).unwrap(); builder.push_storage_change(HEAP_PAGES.to_vec(), Some(32u64.encode())).unwrap(); let block = builder.build().unwrap().block; let hash = block.header.hash(); - (BlockId::Hash(hash), block) + (hash, block) }; futures::executor::block_on(client.import(BlockOrigin::Own, block)).unwrap(); // Allocation of 1024k while having ~2048k should succeed. - let ret = client.runtime_api().vec_with_capacity(&new_block_id, 1048576); + let ret = client.runtime_api().vec_with_capacity(new_at_hash, 1048576); assert!(ret.is_ok()); } #[test] fn test_storage() { - let client = - TestClientBuilder::new().set_execution_strategy(ExecutionStrategy::Both).build(); + let client = TestClientBuilder::new().build(); let runtime_api = client.runtime_api(); - let block_id = BlockId::Hash(client.chain_info().best_hash); + let best_hash = client.chain_info().best_hash; - runtime_api.test_storage(&block_id).unwrap(); + runtime_api.test_storage(best_hash).unwrap(); } fn witness_backend() -> (sp_trie::MemoryDB, crate::Hash) { - use sp_trie::TrieMut; let mut root = crate::Hash::default(); let mut mdb = sp_trie::MemoryDB::::default(); { @@ -1403,11 +1082,112 @@ mod tests { let backend = sp_state_machine::TrieBackendBuilder::<_, crate::Hashing>::new(db, root).build(); let proof = sp_state_machine::prove_read(backend, vec![b"value3"]).unwrap(); - let client = - TestClientBuilder::new().set_execution_strategy(ExecutionStrategy::Both).build(); + let client = TestClientBuilder::new().build(); let runtime_api = client.runtime_api(); - let block_id = BlockId::Hash(client.chain_info().best_hash); + let best_hash = client.chain_info().best_hash; + + runtime_api.test_witness(best_hash, proof, root).unwrap(); + } + + pub fn new_test_ext() -> sp_io::TestExternalities { + genesismap::GenesisStorageBuilder::new( + vec![AccountKeyring::One.public().into(), AccountKeyring::Two.public().into()], + vec![AccountKeyring::One.into(), AccountKeyring::Two.into()], + 1000 * currency::DOLLARS, + ) + .build() + .into() + } - runtime_api.test_witness(&block_id, proof, root).unwrap(); + #[test] + fn validate_storage_keys() { + assert_eq!( + genesismap::GenesisStorageBuilder::default() + .build() + .top + .keys() + .cloned() + .map(storage_key_generator::hex) + .collect::>(), + storage_key_generator::get_expected_storage_hashed_keys(false) + ); + } + + #[test] + fn validate_unsigned_works() { + sp_tracing::try_init_simple(); + new_test_ext().execute_with(|| { + let failing_calls = vec![ + substrate_test_pallet::Call::bench_call { transfer: Default::default() }, + substrate_test_pallet::Call::include_data { data: vec![] }, + substrate_test_pallet::Call::fill_block { ratio: Perbill::from_percent(50) }, + ]; + let succeeding_calls = vec![ + substrate_test_pallet::Call::deposit_log_digest_item { + log: DigestItem::Other(vec![]), + }, + substrate_test_pallet::Call::storage_change { key: vec![], value: None }, + substrate_test_pallet::Call::read { count: 0 }, + substrate_test_pallet::Call::read_and_panic { count: 0 }, + ]; + + for call in failing_calls { + assert_eq!( + ::validate_unsigned( + TransactionSource::External, + &call, + ), + InvalidTransaction::Call.into(), + ); + } + + for call in succeeding_calls { + assert_eq!( + ::validate_unsigned( + TransactionSource::External, + &call, + ), + Ok(ValidTransaction { + provides: vec![BlakeTwo256::hash_of(&call).encode()], + ..Default::default() + }) + ); + } + }); + } + + #[test] + fn check_substrate_check_signed_extension_works() { + sp_tracing::try_init_simple(); + new_test_ext().execute_with(|| { + let x = sp_keyring::AccountKeyring::Alice.into(); + let info = DispatchInfo::default(); + let len = 0_usize; + assert_eq!( + CheckSubstrateCall {} + .validate( + &x, + &ExtrinsicBuilder::new_call_with_priority(16).build().function, + &info, + len + ) + .unwrap() + .priority, + 16 + ); + + assert_eq!( + CheckSubstrateCall {} + .validate( + &x, + &ExtrinsicBuilder::new_call_do_not_propagate().build().function, + &info, + len + ) + .unwrap() + .propagate, + false + ); + }) } } diff --git a/test-utils/runtime/src/substrate_test_pallet.rs b/test-utils/runtime/src/substrate_test_pallet.rs new file mode 100644 index 000000000000..ed1ad990472b --- /dev/null +++ b/test-utils/runtime/src/substrate_test_pallet.rs @@ -0,0 +1,252 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! # substrate-test pallet +//! +//! Provides functionality used in unit-tests of numerous modules across substrate that require +//! functioning runtime. Some calls are allowed to be submitted as unsigned extrinsics, however most +//! of them requires signing. Refer to `pallet::Call` for further details. + +use frame_support::{pallet_prelude::*, storage}; +use sp_core::sr25519::Public; +use sp_runtime::{ + traits::Hash, + transaction_validity::{ + InvalidTransaction, TransactionSource, TransactionValidity, ValidTransaction, + }, +}; +use sp_std::prelude::*; + +pub use self::pallet::*; + +const LOG_TARGET: &str = "substrate_test_pallet"; + +#[frame_support::pallet(dev_mode)] +pub mod pallet { + use super::*; + use crate::TransferData; + use frame_system::pallet_prelude::*; + use sp_core::storage::well_known_keys; + use sp_runtime::{traits::BlakeTwo256, transaction_validity::TransactionPriority, Perbill}; + + #[pallet::pallet] + #[pallet::without_storage_info] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::storage] + #[pallet::getter(fn authorities)] + pub type Authorities = StorageValue<_, Vec, ValueQuery>; + + #[pallet::genesis_config] + #[derive(frame_support::DefaultNoBound)] + pub struct GenesisConfig { + pub authorities: Vec, + #[serde(skip)] + pub _config: sp_std::marker::PhantomData, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + >::put(self.authorities.clone()); + } + } + + #[pallet::call] + impl Pallet { + /// Legacy call used in transaction pool benchmarks. + #[pallet::call_index(0)] + #[pallet::weight(100)] + pub fn bench_call(_origin: OriginFor, _transfer: TransferData) -> DispatchResult { + Ok(()) + } + + /// Implicitly fill a block body with some data. + #[pallet::call_index(1)] + #[pallet::weight(100)] + pub fn include_data(origin: OriginFor, _data: Vec) -> DispatchResult { + frame_system::ensure_signed(origin)?; + Ok(()) + } + + /// Put/delete some data from storage. Intended to use as an unsigned extrinsic. + #[pallet::call_index(2)] + #[pallet::weight(100)] + pub fn storage_change( + _origin: OriginFor, + key: Vec, + value: Option>, + ) -> DispatchResult { + match value { + Some(value) => storage::unhashed::put_raw(&key, &value), + None => storage::unhashed::kill(&key), + } + Ok(()) + } + + /// Write a key value pair to the offchain database. + #[pallet::call_index(3)] + #[pallet::weight(100)] + pub fn offchain_index_set( + origin: OriginFor, + key: Vec, + value: Vec, + ) -> DispatchResult { + frame_system::ensure_signed(origin)?; + sp_io::offchain_index::set(&key, &value); + Ok(()) + } + + /// Remove a key and an associated value from the offchain database. + #[pallet::call_index(4)] + #[pallet::weight(100)] + pub fn offchain_index_clear(origin: OriginFor, key: Vec) -> DispatchResult { + frame_system::ensure_signed(origin)?; + sp_io::offchain_index::clear(&key); + Ok(()) + } + + /// Create an index for this call. + #[pallet::call_index(5)] + #[pallet::weight(100)] + pub fn indexed_call(origin: OriginFor, data: Vec) -> DispatchResult { + frame_system::ensure_signed(origin)?; + let content_hash = sp_io::hashing::blake2_256(&data); + let extrinsic_index: u32 = + storage::unhashed::get(well_known_keys::EXTRINSIC_INDEX).unwrap(); + sp_io::transaction_index::index(extrinsic_index, data.len() as u32, content_hash); + Ok(()) + } + + /// Deposit given digest items into the system storage. They will be included in a header + /// during finalization. + #[pallet::call_index(6)] + #[pallet::weight(100)] + pub fn deposit_log_digest_item( + _origin: OriginFor, + log: sp_runtime::generic::DigestItem, + ) -> DispatchResult { + >::deposit_log(log); + Ok(()) + } + + /// This call is validated as `ValidTransaction` with given priority. + #[pallet::call_index(7)] + #[pallet::weight(100)] + pub fn call_with_priority( + _origin: OriginFor, + _priority: TransactionPriority, + ) -> DispatchResult { + Ok(()) + } + + /// This call is validated as non-propagable `ValidTransaction`. + #[pallet::call_index(8)] + #[pallet::weight(100)] + pub fn call_do_not_propagate(_origin: OriginFor) -> DispatchResult { + Ok(()) + } + + /// Fill the block weight up to the given ratio. + #[pallet::call_index(9)] + #[pallet::weight(*_ratio * T::BlockWeights::get().max_block)] + pub fn fill_block(origin: OriginFor, _ratio: Perbill) -> DispatchResult { + ensure_signed(origin)?; + Ok(()) + } + + /// Read X times from the state some data. + /// + /// Panics if it can not read `X` times. + #[pallet::call_index(10)] + #[pallet::weight(100)] + pub fn read(_origin: OriginFor, count: u32) -> DispatchResult { + Self::execute_read(count, false) + } + + /// Read X times from the state some data and then panic! + /// + /// Returns `Ok` if it didn't read anything. + #[pallet::call_index(11)] + #[pallet::weight(100)] + pub fn read_and_panic(_origin: OriginFor, count: u32) -> DispatchResult { + Self::execute_read(count, true) + } + } + + impl Pallet { + fn execute_read(read: u32, panic_at_end: bool) -> DispatchResult { + let mut next_key = vec![]; + for _ in 0..(read as usize) { + if let Some(next) = sp_io::storage::next_key(&next_key) { + // Read the value + sp_io::storage::get(&next); + + next_key = next; + } else { + if panic_at_end { + return Ok(()) + } else { + panic!("Could not read {read} times from the state"); + } + } + } + + if panic_at_end { + panic!("BYE") + } else { + Ok(()) + } + } + } + + #[pallet::validate_unsigned] + impl ValidateUnsigned for Pallet { + type Call = Call; + + fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { + log::trace!(target: LOG_TARGET, "validate_unsigned {call:?}"); + match call { + // Some tests do not need to be complicated with signer and nonce, some need + // reproducible block hash (call signature can't be there). + // Offchain testing requires storage_change. + Call::deposit_log_digest_item { .. } | + Call::storage_change { .. } | + Call::read { .. } | + Call::read_and_panic { .. } => Ok(ValidTransaction { + provides: vec![BlakeTwo256::hash_of(&call).encode()], + ..Default::default() + }), + _ => Err(TransactionValidityError::Invalid(InvalidTransaction::Call)), + } + } + } +} + +pub fn validate_runtime_call(call: &pallet::Call) -> TransactionValidity { + log::trace!(target: LOG_TARGET, "validate_runtime_call {call:?}"); + match call { + Call::call_do_not_propagate {} => + Ok(ValidTransaction { propagate: false, ..Default::default() }), + Call::call_with_priority { priority } => + Ok(ValidTransaction { priority: *priority, ..Default::default() }), + _ => Ok(Default::default()), + } +} diff --git a/test-utils/runtime/src/system.rs b/test-utils/runtime/src/system.rs deleted file mode 100644 index 6e33d5c25fe6..000000000000 --- a/test-utils/runtime/src/system.rs +++ /dev/null @@ -1,548 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) 2017-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 manager: Handles all of the top-level stuff; executing block/transaction, setting code -//! and depositing logs. - -use crate::{ - AccountId, AuthorityId, Block, BlockNumber, Digest, Extrinsic, Header, Runtime, Transfer, - H256 as Hash, -}; -use codec::{Decode, Encode, KeyedVec}; -use frame_support::storage; -use sp_core::storage::well_known_keys; -use sp_io::{hashing::blake2_256, storage::root as storage_root, trie}; -use sp_runtime::{ - generic, - traits::Header as _, - transaction_validity::{ - InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction, - }, - ApplyExtrinsicResult, -}; -use sp_std::prelude::*; - -const NONCE_OF: &[u8] = b"nonce:"; -const BALANCE_OF: &[u8] = b"balance:"; - -pub use self::pallet::*; - -#[frame_support::pallet] -mod pallet { - use super::*; - use frame_support::pallet_prelude::*; - - #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] - #[pallet::without_storage_info] - pub struct Pallet(PhantomData); - - #[pallet::config] - pub trait Config: frame_system::Config {} - - #[pallet::storage] - pub type ExtrinsicData = StorageMap<_, Blake2_128Concat, u32, Vec, ValueQuery>; - - // The current block number being processed. Set by `execute_block`. - #[pallet::storage] - pub type Number = StorageValue<_, BlockNumber, OptionQuery>; - - #[pallet::storage] - pub type ParentHash = StorageValue<_, Hash, ValueQuery>; - - #[pallet::storage] - pub type NewAuthorities = StorageValue<_, Vec, OptionQuery>; - - #[pallet::storage] - pub type StorageDigest = StorageValue<_, Digest, OptionQuery>; - - #[pallet::storage] - pub type Authorities = StorageValue<_, Vec, ValueQuery>; - - #[pallet::genesis_config] - #[cfg_attr(feature = "std", derive(Default))] - pub struct GenesisConfig { - pub authorities: Vec, - } - - #[pallet::genesis_build] - impl GenesisBuild for GenesisConfig { - fn build(&self) { - >::put(self.authorities.clone()); - } - } -} - -pub fn balance_of_key(who: AccountId) -> Vec { - who.to_keyed_vec(BALANCE_OF) -} - -pub fn balance_of(who: AccountId) -> u64 { - storage::hashed::get_or(&blake2_256, &balance_of_key(who), 0) -} - -pub fn nonce_of(who: AccountId) -> u64 { - storage::hashed::get_or(&blake2_256, &who.to_keyed_vec(NONCE_OF), 0) -} - -pub fn initialize_block(header: &Header) { - // populate environment. - >::put(&header.number); - >::put(&header.parent_hash); - >::put(header.digest()); - storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &0u32); - - // try to read something that depends on current header digest - // so that it'll be included in execution proof - if let Some(generic::DigestItem::Other(v)) = header.digest().logs().iter().next() { - let _: Option = storage::unhashed::get(v); - } -} - -pub fn authorities() -> Vec { - >::get() -} - -pub fn get_block_number() -> Option { - >::get() -} - -pub fn take_block_number() -> Option { - >::take() -} - -#[derive(Copy, Clone)] -enum Mode { - Verify, - Overwrite, -} - -/// Actually execute all transitioning for `block`. -pub fn polish_block(block: &mut Block) { - execute_block_with_state_root_handler(block, Mode::Overwrite); -} - -pub fn execute_block(mut block: Block) -> Header { - execute_block_with_state_root_handler(&mut block, Mode::Verify) -} - -fn execute_block_with_state_root_handler(block: &mut Block, mode: Mode) -> Header { - let header = &mut block.header; - - initialize_block(header); - - // execute transactions - block.extrinsics.iter().for_each(|e| { - let _ = execute_transaction(e.clone()).unwrap_or_else(|_| panic!("Invalid transaction")); - }); - - let new_header = finalize_block(); - - if let Mode::Overwrite = mode { - header.state_root = new_header.state_root; - } else { - info_expect_equal_hash(&new_header.state_root, &header.state_root); - assert_eq!( - new_header.state_root, header.state_root, - "Storage root must match that calculated.", - ); - } - - if let Mode::Overwrite = mode { - header.extrinsics_root = new_header.extrinsics_root; - } else { - info_expect_equal_hash(&new_header.extrinsics_root, &header.extrinsics_root); - assert_eq!( - new_header.extrinsics_root, header.extrinsics_root, - "Transaction trie root must be valid.", - ); - } - - new_header -} - -/// The block executor. -pub struct BlockExecutor; - -impl frame_support::traits::ExecuteBlock for BlockExecutor { - fn execute_block(block: Block) { - execute_block(block); - } -} - -/// Execute a transaction outside of the block execution function. -/// This doesn't attempt to validate anything regarding the block. -pub fn validate_transaction(utx: Extrinsic) -> TransactionValidity { - if check_signature(&utx).is_err() { - return InvalidTransaction::BadProof.into() - } - - let tx = utx.transfer(); - let nonce_key = tx.from.to_keyed_vec(NONCE_OF); - let expected_nonce: u64 = storage::hashed::get_or(&blake2_256, &nonce_key, 0); - if tx.nonce < expected_nonce { - return InvalidTransaction::Stale.into() - } - if tx.nonce > expected_nonce + 64 { - return InvalidTransaction::Future.into() - } - - let encode = |from: &AccountId, nonce: u64| (from, nonce).encode(); - let requires = if tx.nonce != expected_nonce && tx.nonce > 0 { - vec![encode(&tx.from, tx.nonce - 1)] - } else { - vec![] - }; - - let provides = vec![encode(&tx.from, tx.nonce)]; - - Ok(ValidTransaction { priority: tx.amount, requires, provides, longevity: 64, propagate: true }) -} - -/// Execute a transaction outside of the block execution function. -/// This doesn't attempt to validate anything regarding the block. -pub fn execute_transaction(utx: Extrinsic) -> ApplyExtrinsicResult { - let extrinsic_index: u32 = - storage::unhashed::get(well_known_keys::EXTRINSIC_INDEX).unwrap_or_default(); - let result = execute_transaction_backend(&utx, extrinsic_index); - >::insert(extrinsic_index, utx.encode()); - storage::unhashed::put(well_known_keys::EXTRINSIC_INDEX, &(extrinsic_index + 1)); - result -} - -/// Finalize the block. -pub fn finalize_block() -> Header { - use sp_core::storage::StateVersion; - let extrinsic_index: u32 = storage::unhashed::take(well_known_keys::EXTRINSIC_INDEX).unwrap(); - let txs: Vec<_> = (0..extrinsic_index).map(>::take).collect(); - let extrinsics_root = trie::blake2_256_ordered_root(txs, StateVersion::V0); - let number = >::take().expect("Number is set by `initialize_block`"); - let parent_hash = >::take(); - let mut digest = - >::take().expect("StorageDigest is set by `initialize_block`"); - - let o_new_authorities = >::take(); - - // This MUST come after all changes to storage are done. Otherwise we will fail the - // “Storage root does not match that calculated” assertion. - let storage_root = Hash::decode(&mut &storage_root(StateVersion::V1)[..]) - .expect("`storage_root` is a valid hash"); - - if let Some(new_authorities) = o_new_authorities { - digest.push(generic::DigestItem::Consensus(*b"aura", new_authorities.encode())); - digest.push(generic::DigestItem::Consensus(*b"babe", new_authorities.encode())); - } - - Header { number, extrinsics_root, state_root: storage_root, parent_hash, digest } -} - -#[inline(always)] -fn check_signature(utx: &Extrinsic) -> Result<(), TransactionValidityError> { - use sp_runtime::traits::BlindCheckable; - utx.clone().check().map_err(|_| InvalidTransaction::BadProof.into()).map(|_| ()) -} - -fn execute_transaction_backend(utx: &Extrinsic, extrinsic_index: u32) -> ApplyExtrinsicResult { - check_signature(utx)?; - match utx { - Extrinsic::Transfer { exhaust_resources_when_not_first: true, .. } - if extrinsic_index != 0 => - Err(InvalidTransaction::ExhaustsResources.into()), - Extrinsic::Transfer { ref transfer, .. } => execute_transfer_backend(transfer), - Extrinsic::AuthoritiesChange(ref new_auth) => execute_new_authorities_backend(new_auth), - Extrinsic::IncludeData(_) => Ok(Ok(())), - Extrinsic::StorageChange(key, value) => - execute_storage_change(key, value.as_ref().map(|v| &**v)), - Extrinsic::OffchainIndexSet(key, value) => { - sp_io::offchain_index::set(key, value); - Ok(Ok(())) - }, - Extrinsic::OffchainIndexClear(key) => { - sp_io::offchain_index::clear(key); - Ok(Ok(())) - }, - Extrinsic::Store(data) => execute_store(data.clone()), - } -} - -fn execute_transfer_backend(tx: &Transfer) -> ApplyExtrinsicResult { - // check nonce - let nonce_key = tx.from.to_keyed_vec(NONCE_OF); - let expected_nonce: u64 = storage::hashed::get_or(&blake2_256, &nonce_key, 0); - if tx.nonce != expected_nonce { - return Err(InvalidTransaction::Stale.into()) - } - - // increment nonce in storage - storage::hashed::put(&blake2_256, &nonce_key, &(expected_nonce + 1)); - - // check sender balance - let from_balance_key = tx.from.to_keyed_vec(BALANCE_OF); - let from_balance: u64 = storage::hashed::get_or(&blake2_256, &from_balance_key, 0); - - // enact transfer - if tx.amount > from_balance { - return Err(InvalidTransaction::Payment.into()) - } - let to_balance_key = tx.to.to_keyed_vec(BALANCE_OF); - let to_balance: u64 = storage::hashed::get_or(&blake2_256, &to_balance_key, 0); - storage::hashed::put(&blake2_256, &from_balance_key, &(from_balance - tx.amount)); - storage::hashed::put(&blake2_256, &to_balance_key, &(to_balance + tx.amount)); - Ok(Ok(())) -} - -fn execute_store(data: Vec) -> ApplyExtrinsicResult { - let content_hash = sp_io::hashing::blake2_256(&data); - let extrinsic_index: u32 = storage::unhashed::get(well_known_keys::EXTRINSIC_INDEX).unwrap(); - sp_io::transaction_index::index(extrinsic_index, data.len() as u32, content_hash); - Ok(Ok(())) -} - -fn execute_new_authorities_backend(new_authorities: &[AuthorityId]) -> ApplyExtrinsicResult { - >::put(new_authorities.to_vec()); - Ok(Ok(())) -} - -fn execute_storage_change(key: &[u8], value: Option<&[u8]>) -> ApplyExtrinsicResult { - match value { - Some(value) => storage::unhashed::put_raw(key, value), - None => storage::unhashed::kill(key), - } - Ok(Ok(())) -} - -#[cfg(feature = "std")] -fn info_expect_equal_hash(given: &Hash, expected: &Hash) { - use sp_core::hexdisplay::HexDisplay; - if given != expected { - println!( - "Hash: given={}, expected={}", - HexDisplay::from(given.as_fixed_bytes()), - HexDisplay::from(expected.as_fixed_bytes()), - ); - } -} - -#[cfg(not(feature = "std"))] -fn info_expect_equal_hash(given: &Hash, expected: &Hash) { - if given != expected { - sp_runtime::print("Hash not equal"); - sp_runtime::print(given.as_bytes()); - sp_runtime::print(expected.as_bytes()); - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use crate::{wasm_binary_unwrap, Header, Transfer}; - use sc_executor::{NativeElseWasmExecutor, WasmExecutionMethod}; - use sp_core::{ - map, - traits::{CodeExecutor, RuntimeCode}, - }; - use sp_io::{hashing::twox_128, TestExternalities}; - use substrate_test_runtime_client::{AccountKeyring, Sr25519Keyring}; - - // Declare an instance of the native executor dispatch for the test runtime. - pub struct NativeDispatch; - - impl sc_executor::NativeExecutionDispatch for NativeDispatch { - type ExtendHostFunctions = (); - - fn dispatch(method: &str, data: &[u8]) -> Option> { - crate::api::dispatch(method, data) - } - - fn native_version() -> sc_executor::NativeVersion { - crate::native_version() - } - } - - fn executor() -> NativeElseWasmExecutor { - NativeElseWasmExecutor::new(WasmExecutionMethod::Interpreted, None, 8, 2) - } - - fn new_test_ext() -> TestExternalities { - let authorities = vec![ - Sr25519Keyring::Alice.to_raw_public(), - Sr25519Keyring::Bob.to_raw_public(), - Sr25519Keyring::Charlie.to_raw_public(), - ]; - - TestExternalities::new_with_code( - wasm_binary_unwrap(), - sp_core::storage::Storage { - top: map![ - twox_128(b"latest").to_vec() => vec![69u8; 32], - twox_128(b"sys:auth").to_vec() => authorities.encode(), - blake2_256(&AccountKeyring::Alice.to_raw_public().to_keyed_vec(b"balance:")).to_vec() => { - vec![111u8, 0, 0, 0, 0, 0, 0, 0] - }, - ], - children_default: map![], - }, - ) - } - - fn block_import_works(block_executor: F) - where - F: Fn(Block, &mut TestExternalities), - { - let h = Header { - parent_hash: [69u8; 32].into(), - number: 1, - state_root: Default::default(), - extrinsics_root: Default::default(), - digest: Default::default(), - }; - let mut b = Block { header: h, extrinsics: vec![] }; - - new_test_ext().execute_with(|| polish_block(&mut b)); - - block_executor(b, &mut new_test_ext()); - } - - #[test] - fn block_import_works_native() { - block_import_works(|b, ext| { - ext.execute_with(|| { - execute_block(b); - }) - }); - } - - #[test] - fn block_import_works_wasm() { - block_import_works(|b, ext| { - let mut ext = ext.ext(); - let runtime_code = RuntimeCode { - code_fetcher: &sp_core::traits::WrappedRuntimeCode(wasm_binary_unwrap().into()), - hash: Vec::new(), - heap_pages: None, - }; - - executor() - .call(&mut ext, &runtime_code, "Core_execute_block", &b.encode(), false) - .0 - .unwrap(); - }) - } - - fn block_import_with_transaction_works(block_executor: F) - where - F: Fn(Block, &mut TestExternalities), - { - let mut b1 = Block { - header: Header { - parent_hash: [69u8; 32].into(), - number: 1, - state_root: Default::default(), - extrinsics_root: Default::default(), - digest: Default::default(), - }, - extrinsics: vec![Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Bob.into(), - amount: 69, - nonce: 0, - } - .into_signed_tx()], - }; - - let mut dummy_ext = new_test_ext(); - dummy_ext.execute_with(|| polish_block(&mut b1)); - - let mut b2 = Block { - header: Header { - parent_hash: b1.header.hash(), - number: 2, - state_root: Default::default(), - extrinsics_root: Default::default(), - digest: Default::default(), - }, - extrinsics: vec![ - Transfer { - from: AccountKeyring::Bob.into(), - to: AccountKeyring::Alice.into(), - amount: 27, - nonce: 0, - } - .into_signed_tx(), - Transfer { - from: AccountKeyring::Alice.into(), - to: AccountKeyring::Charlie.into(), - amount: 69, - nonce: 1, - } - .into_signed_tx(), - ], - }; - - dummy_ext.execute_with(|| polish_block(&mut b2)); - drop(dummy_ext); - - let mut t = new_test_ext(); - - t.execute_with(|| { - assert_eq!(balance_of(AccountKeyring::Alice.into()), 111); - assert_eq!(balance_of(AccountKeyring::Bob.into()), 0); - }); - - block_executor(b1, &mut t); - - t.execute_with(|| { - assert_eq!(balance_of(AccountKeyring::Alice.into()), 42); - assert_eq!(balance_of(AccountKeyring::Bob.into()), 69); - }); - - block_executor(b2, &mut t); - - t.execute_with(|| { - assert_eq!(balance_of(AccountKeyring::Alice.into()), 0); - assert_eq!(balance_of(AccountKeyring::Bob.into()), 42); - assert_eq!(balance_of(AccountKeyring::Charlie.into()), 69); - }); - } - - #[test] - fn block_import_with_transaction_works_native() { - block_import_with_transaction_works(|b, ext| { - ext.execute_with(|| { - execute_block(b); - }) - }); - } - - #[test] - fn block_import_with_transaction_works_wasm() { - block_import_with_transaction_works(|b, ext| { - let mut ext = ext.ext(); - let runtime_code = RuntimeCode { - code_fetcher: &sp_core::traits::WrappedRuntimeCode(wasm_binary_unwrap().into()), - hash: Vec::new(), - heap_pages: None, - }; - - executor() - .call(&mut ext, &runtime_code, "Core_execute_block", &b.encode(), false) - .0 - .unwrap(); - }) - } -} diff --git a/test-utils/runtime/transaction-pool/Cargo.toml b/test-utils/runtime/transaction-pool/Cargo.toml index 5ce397474fec..18efb2c8a0a6 100644 --- a/test-utils/runtime/transaction-pool/Cargo.toml +++ b/test-utils/runtime/transaction-pool/Cargo.toml @@ -12,12 +12,12 @@ publish = false targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } futures = "0.3.21" parking_lot = "0.12.1" thiserror = "1.0" sc-transaction-pool = { version = "4.0.0-dev", path = "../../../client/transaction-pool" } sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../client/transaction-pool/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } substrate-test-runtime-client = { version = "2.0.0", path = "../client" } diff --git a/test-utils/runtime/transaction-pool/src/lib.rs b/test-utils/runtime/transaction-pool/src/lib.rs index 8a648cd00315..7b5292004402 100644 --- a/test-utils/runtime/transaction-pool/src/lib.rs +++ b/test-utils/runtime/transaction-pool/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,7 +35,10 @@ use sp_runtime::{ }; use std::collections::{BTreeMap, HashMap, HashSet}; use substrate_test_runtime_client::{ - runtime::{AccountId, Block, BlockNumber, Extrinsic, Hash, Header, Index, Transfer}, + runtime::{ + AccountId, Block, BlockNumber, Extrinsic, ExtrinsicBuilder, Hash, Header, Nonce, Transfer, + TransferData, + }, AccountKeyring::{self, *}, }; @@ -276,7 +279,7 @@ impl sc_transaction_pool::ChainApi for TestApi { Err(e) => return ready(Err(e)), } - let (requires, provides) = if let Some(transfer) = uxt.try_transfer() { + let (requires, provides) = if let Ok(transfer) = TransferData::try_from(&uxt) { let chain_nonce = self.chain.read().nonces.get(&transfer.from).cloned().unwrap_or(0); let requires = if chain_nonce == transfer.nonce { vec![] } else { vec![vec![chain_nonce as u8]] }; @@ -374,9 +377,8 @@ impl sp_blockchain::HeaderMetadata for TestApi { /// Generate transfer extrinsic with a given nonce. /// /// Part of the test api. -pub fn uxt(who: AccountKeyring, nonce: Index) -> Extrinsic { +pub fn uxt(who: AccountKeyring, nonce: Nonce) -> Extrinsic { let dummy = codec::Decode::decode(&mut TrailingZeroInput::zeroes()).unwrap(); let transfer = Transfer { from: who.into(), to: dummy, nonce, amount: 1 }; - let signature = transfer.using_encoded(|e| who.sign(e)); - Extrinsic::Transfer { transfer, signature, exhaust_resources_when_not_first: false } + ExtrinsicBuilder::new_transfer(transfer).build() } diff --git a/test-utils/src/lib.rs b/test-utils/src/lib.rs index 643985940792..1ad702893683 100644 --- a/test-utils/src/lib.rs +++ b/test-utils/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2017-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/test-utils/test-crate/src/main.rs b/test-utils/test-crate/src/main.rs index 4696e71779c1..cab4cc6e924d 100644 --- a/test-utils/test-crate/src/main.rs +++ b/test-utils/test-crate/src/main.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/test-utils/tests/basic.rs b/test-utils/tests/basic.rs index 4daca836c7fd..e8c46b1e837d 100644 --- a/test-utils/tests/basic.rs +++ b/test-utils/tests/basic.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/test-utils/tests/ui.rs b/test-utils/tests/ui.rs index be3b9c111b3e..baf822bb87a4 100644 --- a/test-utils/tests/ui.rs +++ b/test-utils/tests/ui.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/test-utils/tests/ui/too-many-func-parameters.rs b/test-utils/tests/ui/too-many-func-parameters.rs index 51059f37ab62..0eece5f9e613 100644 --- a/test-utils/tests/ui/too-many-func-parameters.rs +++ b/test-utils/tests/ui/too-many-func-parameters.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // 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 diff --git a/frame/beefy-mmr/primitives/Cargo.toml b/utils/binary-merkle-tree/Cargo.toml similarity index 51% rename from frame/beefy-mmr/primitives/Cargo.toml rename to utils/binary-merkle-tree/Cargo.toml index c087bc2f37cd..596c39401802 100644 --- a/frame/beefy-mmr/primitives/Cargo.toml +++ b/utils/binary-merkle-tree/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "beefy-merkle-tree" +name = "binary-merkle-tree" version = "4.0.0-dev" authors = ["Parity Technologies "] edition = "2021" @@ -9,22 +9,20 @@ description = "A no-std/Substrate compatible library to construct binary merkle homepage = "https://substrate.io" [dependencies] -array-bytes = { version = "4.1", optional = true } +array-bytes = { version = "6.1", optional = true } log = { version = "0.4", default-features = false, optional = true } - -beefy-primitives = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/beefy", package = "sp-beefy" } -sp-api = { version = "4.0.0-dev", default-features = false, path = "../../../primitives/api" } -sp-runtime = { version = "7.0.0", default-features = false, path = "../../../primitives/runtime" } +hash-db = { version = "0.16.0", default-features = false } [dev-dependencies] -array-bytes = "4.1" +array-bytes = "6.1" env_logger = "0.9" +sp-core = { version = "21.0.0", path = "../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../primitives/runtime" } [features] debug = ["array-bytes", "log"] default = ["debug", "std"] std = [ - "beefy-primitives/std", - "sp-api/std", - "sp-runtime/std" + "log/std", + "hash-db/std" ] diff --git a/frame/beefy-mmr/primitives/src/lib.rs b/utils/binary-merkle-tree/src/lib.rs similarity index 84% rename from frame/beefy-mmr/primitives/src/lib.rs rename to utils/binary-merkle-tree/src/lib.rs index e6f8acefb039..0efab9186c25 100644 --- a/frame/beefy-mmr/primitives/src/lib.rs +++ b/utils/binary-merkle-tree/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,44 +27,45 @@ //! Merkle Tree is constructed from arbitrary-length leaves, that are initially hashed using the //! same hasher as the inner nodes. //! Inner nodes are created by concatenating child hashes and hashing again. The implementation -//! sorts each pair of hashes before every hash operation. This makes proof verification more -//! efficient by removing the need to track which side each intermediate hash is concatenated on. +//! does not perform any sorting of the input data (leaves) nor when inner nodes are created. //! //! If the number of leaves is not even, last leaf (hash of) is promoted to the upper layer. +#[cfg(not(feature = "std"))] +extern crate alloc; +#[cfg(not(feature = "std"))] +use alloc::vec; +#[cfg(not(feature = "std"))] +use alloc::vec::Vec; -pub use sp_runtime::traits::Keccak256; -use sp_runtime::{app_crypto::sp_core, sp_std, traits::Hash as HashT}; -use sp_std::{vec, vec::Vec}; - -use beefy_primitives::mmr::{BeefyAuthoritySet, BeefyNextAuthoritySet}; +use hash_db::Hasher; /// Construct a root hash of a Binary Merkle Tree created from given leaves. /// /// See crate-level docs for details about Merkle Tree construction. /// /// In case an empty list of leaves is passed the function returns a 0-filled hash. -pub fn merkle_root(leaves: I) -> H::Output +pub fn merkle_root(leaves: I) -> H::Out where - H: HashT, - H::Output: Default + AsRef<[u8]> + PartialOrd, + H: Hasher, + H::Out: Default + AsRef<[u8]>, I: IntoIterator, I::Item: AsRef<[u8]>, { - let iter = leaves.into_iter().map(|l| ::hash(l.as_ref())); + let iter = leaves.into_iter().map(|l| ::hash(l.as_ref())); merkelize::(iter, &mut ()).into() } -fn merkelize(leaves: I, visitor: &mut V) -> H::Output +fn merkelize(leaves: I, visitor: &mut V) -> H::Out where - H: HashT, - H::Output: Default + AsRef<[u8]> + PartialOrd, - V: Visitor, - I: Iterator, + H: Hasher, + H::Out: Default + AsRef<[u8]>, + V: Visitor, + I: Iterator, { let upper = Vec::with_capacity((leaves.size_hint().1.unwrap_or(0).saturating_add(1)) / 2); let mut next = match merkelize_row::(leaves, upper, visitor) { Ok(root) => return root, - Err(next) if next.is_empty() => return H::Output::default(), + Err(next) if next.is_empty() => return H::Out::default(), Err(next) => next, }; @@ -139,17 +140,17 @@ impl Visitor for () { /// # Panic /// /// The function will panic if given `leaf_index` is greater than the number of leaves. -pub fn merkle_proof(leaves: I, leaf_index: usize) -> MerkleProof +pub fn merkle_proof(leaves: I, leaf_index: usize) -> MerkleProof where - H: HashT, - H::Output: Default + Copy + AsRef<[u8]> + PartialOrd, + H: Hasher, + H::Out: Default + Copy + AsRef<[u8]>, I: IntoIterator, I::IntoIter: ExactSizeIterator, T: AsRef<[u8]>, { let mut leaf = None; let iter = leaves.into_iter().enumerate().map(|(idx, l)| { - let hash = ::hash(l.as_ref()); + let hash = ::hash(l.as_ref()); if idx == leaf_index { leaf = Some(l); } @@ -201,7 +202,7 @@ where collect_proof .proof .iter() - .map(|s| array_bytes::bytes2hex("", s.as_ref())) + .map(|s| array_bytes::bytes2hex("", s)) .collect::>() ); @@ -234,46 +235,50 @@ impl<'a, H, T: AsRef<[u8]>> From<&'a T> for Leaf<'a, H> { /// /// The proof must not contain the root hash. pub fn verify_proof<'a, H, P, L>( - root: &'a H::Output, + root: &'a H::Out, proof: P, number_of_leaves: usize, leaf_index: usize, leaf: L, ) -> bool where - H: HashT, - H::Output: PartialEq + AsRef<[u8]> + PartialOrd, - P: IntoIterator, - L: Into>, + H: Hasher, + H::Out: PartialEq + AsRef<[u8]>, + P: IntoIterator, + L: Into>, { if leaf_index >= number_of_leaves { return false } let leaf_hash = match leaf.into() { - Leaf::Value(content) => ::hash(content), + Leaf::Value(content) => ::hash(content), Leaf::Hash(hash) => hash, }; - let hash_len = ::LENGTH; + let hash_len = ::LENGTH; let mut combined = vec![0_u8; hash_len * 2]; + let mut position = leaf_index; + let mut width = number_of_leaves; let computed = proof.into_iter().fold(leaf_hash, |a, b| { - if a < b { - combined[..hash_len].copy_from_slice(&a.as_ref()); - combined[hash_len..].copy_from_slice(&b.as_ref()); - } else { + if position % 2 == 1 || position + 1 == width { combined[..hash_len].copy_from_slice(&b.as_ref()); combined[hash_len..].copy_from_slice(&a.as_ref()); + } else { + combined[..hash_len].copy_from_slice(&a.as_ref()); + combined[hash_len..].copy_from_slice(&b.as_ref()); } - let hash = ::hash(&combined); + let hash = ::hash(&combined); #[cfg(feature = "debug")] log::debug!( "[verify_proof]: (a, b) {:?}, {:?} => {:?} ({:?}) hash", - array_bytes::bytes2hex("", &a.as_ref()), - array_bytes::bytes2hex("", &b.as_ref()), - array_bytes::bytes2hex("", &hash.as_ref()), - array_bytes::bytes2hex("", &combined.as_ref()) + array_bytes::bytes2hex("", a), + array_bytes::bytes2hex("", b), + array_bytes::bytes2hex("", hash), + array_bytes::bytes2hex("", &combined) ); + position /= 2; + width = ((width - 1) / 2) + 1; hash }); @@ -287,20 +292,20 @@ where /// empty iterator) an `Err` with the inner nodes of upper layer is returned. fn merkelize_row( mut iter: I, - mut next: Vec, + mut next: Vec, visitor: &mut V, -) -> Result> +) -> Result> where - H: HashT, - H::Output: AsRef<[u8]> + PartialOrd, - V: Visitor, - I: Iterator, + H: Hasher, + H::Out: AsRef<[u8]>, + V: Visitor, + I: Iterator, { #[cfg(feature = "debug")] log::debug!("[merkelize_row]"); next.clear(); - let hash_len = ::LENGTH; + let hash_len = ::LENGTH; let mut index = 0; let mut combined = vec![0_u8; hash_len * 2]; loop { @@ -311,22 +316,17 @@ where #[cfg(feature = "debug")] log::debug!( " {:?}\n {:?}", - a.as_ref().map(|s| array_bytes::bytes2hex("", s.as_ref())), - b.as_ref().map(|s| array_bytes::bytes2hex("", s.as_ref())) + a.as_ref().map(|s| array_bytes::bytes2hex("", s)), + b.as_ref().map(|s| array_bytes::bytes2hex("", s)) ); index += 2; match (a, b) { (Some(a), Some(b)) => { - if a < b { - combined[..hash_len].copy_from_slice(a.as_ref()); - combined[hash_len..].copy_from_slice(b.as_ref()); - } else { - combined[..hash_len].copy_from_slice(b.as_ref()); - combined[hash_len..].copy_from_slice(a.as_ref()); - } + combined[..hash_len].copy_from_slice(a.as_ref()); + combined[hash_len..].copy_from_slice(b.as_ref()); - next.push(::hash(&combined)); + next.push(::hash(&combined)); }, // Odd number of items. Promote the item to the upper layer. (Some(a), None) if !next.is_empty() => { @@ -339,7 +339,7 @@ where #[cfg(feature = "debug")] log::debug!( "[merkelize_row] Next: {:?}", - next.iter().map(|s| array_bytes::bytes2hex("", s.as_ref())).collect::>() + next.iter().map(|s| array_bytes::bytes2hex("", s)).collect::>() ); return Err(next) }, @@ -347,24 +347,11 @@ where } } -sp_api::decl_runtime_apis! { - /// API useful for BEEFY light clients. - pub trait BeefyMmrApi - where - BeefyAuthoritySet: sp_api::Decode, - { - /// Return the currently active BEEFY authority set proof. - fn authority_set_proof() -> BeefyAuthoritySet; - - /// Return the next/queued BEEFY authority set proof. - fn next_authority_set_proof() -> BeefyNextAuthoritySet; - } -} - #[cfg(test)] mod tests { use super::*; - use crate::sp_core::H256; + use sp_core::H256; + use sp_runtime::traits::Keccak256; #[test] fn should_generate_empty_root() { @@ -377,7 +364,7 @@ mod tests { // then assert_eq!( - array_bytes::bytes2hex("", out.as_ref()), + array_bytes::bytes2hex("", out), "0000000000000000000000000000000000000000000000000000000000000000" ); } @@ -386,7 +373,7 @@ mod tests { fn should_generate_single_root() { // given let _ = env_logger::try_init(); - let data = vec![array_bytes::hex2array_unchecked::<20>( + let data = vec![array_bytes::hex2array_unchecked::<_, 20>( "E04CC55ebEE1cBCE552f250e85c57B70B2E2625b", )]; @@ -395,7 +382,7 @@ mod tests { // then assert_eq!( - array_bytes::bytes2hex("", out.as_ref()), + array_bytes::bytes2hex("", out), "aeb47a269393297f4b0a3c9c9cfd00c7a4195255274cf39d83dabc2fcc9ff3d7" ); } @@ -405,8 +392,8 @@ mod tests { // given let _ = env_logger::try_init(); let data = vec![ - array_bytes::hex2array_unchecked::<20>("E04CC55ebEE1cBCE552f250e85c57B70B2E2625b"), - array_bytes::hex2array_unchecked::<20>("25451A4de12dcCc2D166922fA938E900fCc4ED24"), + array_bytes::hex2array_unchecked::<_, 20>("E04CC55ebEE1cBCE552f250e85c57B70B2E2625b"), + array_bytes::hex2array_unchecked::<_, 20>("25451A4de12dcCc2D166922fA938E900fCc4ED24"), ]; // when @@ -414,7 +401,7 @@ mod tests { // then assert_eq!( - array_bytes::bytes2hex("", out.as_ref()), + array_bytes::bytes2hex("", out), "697ea2a8fe5b03468548a7a413424a6292ab44a82a6f5cc594c3fa7dda7ce402" ); } @@ -423,19 +410,16 @@ mod tests { fn should_generate_root_complex() { let _ = env_logger::try_init(); let test = |root, data| { - assert_eq!( - array_bytes::bytes2hex("", &merkle_root::(data).as_ref()), - root - ); + assert_eq!(array_bytes::bytes2hex("", &merkle_root::(data)), root); }; test( - "5842148bc6ebeb52af882a317c765fccd3ae80589b21a9b8cbf21abb630e46a7", + "aff1208e69c9e8be9b584b07ebac4e48a1ee9d15ce3afe20b77a4d29e4175aa3", vec!["a", "b", "c"], ); test( - "7b84bec68b13c39798c6c50e9e40a0b268e3c1634db8f4cb97314eb243d4c514", + "b8912f7269068901f231a965adfefbc10f0eedcfa61852b103efd54dac7db3d7", vec!["a", "b", "a"], ); @@ -445,7 +429,7 @@ mod tests { ); test( - "cc50382cfd3c9a617741e9a85efee8752b8feb95a2cbecd6365fb21366ce0c8c", + "fb3b3be94be9e983ba5e094c9c51a7d96a4fa2e5d8e891df00ca89ba05bb1239", vec!["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"], ); } @@ -486,12 +470,12 @@ mod tests { // then assert_eq!( - array_bytes::bytes2hex("", &proof0.root.as_ref()), - array_bytes::bytes2hex("", &proof1.root.as_ref()) + array_bytes::bytes2hex("", &proof0.root), + array_bytes::bytes2hex("", &proof1.root) ); assert_eq!( - array_bytes::bytes2hex("", &proof2.root.as_ref()), - array_bytes::bytes2hex("", &proof1.root.as_ref()) + array_bytes::bytes2hex("", &proof2.root), + array_bytes::bytes2hex("", &proof1.root) ); assert!(!verify_proof::( @@ -763,7 +747,7 @@ mod tests { "0xc26B34D375533fFc4c5276282Fa5D660F3d8cbcB", ]; let root: H256 = array_bytes::hex2array_unchecked( - "7b2c6eebec6e85b2e272325a11c31af71df52bc0534d2d4f903e0ced191f022e", + "72b0acd7c302a84f1f6b6cefe0ba7194b7398afb440e1b44a9dbbe270394ca53", ) .into(); @@ -775,10 +759,7 @@ mod tests { for l in 0..data.len() { // when let proof = merkle_proof::(data.clone(), l); - assert_eq!( - array_bytes::bytes2hex("", &proof.root.as_ref()), - array_bytes::bytes2hex("", &root.as_ref()) - ); + assert_eq!(array_bytes::bytes2hex("", &proof.root), array_bytes::bytes2hex("", &root)); assert_eq!(proof.leaf_index, l); assert_eq!(&proof.leaf, &data[l]); @@ -808,17 +789,17 @@ mod tests { ) .into(), array_bytes::hex2array_unchecked( - "1fad92ed8d0504ef6c0231bbbeeda960a40693f297c64e87b582beb92ecfb00f" + "d02609d2bbdb28aa25f58b85afec937d5a4c85d37925bce6d0cf802f9d76ba79" ) .into(), array_bytes::hex2array_unchecked( - "0b84c852cbcf839d562d826fd935e1b37975ccaa419e1def8d219df4b83dcbf4" + "ae3f8991955ed884613b0a5f40295902eea0e0abe5858fc520b72959bc016d4e" ) .into(), ], number_of_leaves: data.len(), leaf_index: data.len() - 1, - leaf: array_bytes::hex2array_unchecked::<20>( + leaf: array_bytes::hex2array_unchecked::<_, 20>( "c26B34D375533fFc4c5276282Fa5D660F3d8cbcB" ) .to_vec(), diff --git a/utils/build-script-utils/Cargo.toml b/utils/build-script-utils/Cargo.toml index 7150b4fa5adc..35096f282ef0 100644 --- a/utils/build-script-utils/Cargo.toml +++ b/utils/build-script-utils/Cargo.toml @@ -13,4 +13,3 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -platforms = "2.0" diff --git a/utils/build-script-utils/src/git.rs b/utils/build-script-utils/src/git.rs index db9a4b291ffd..057ee0af15f7 100644 --- a/utils/build-script-utils/src/git.rs +++ b/utils/build-script-utils/src/git.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/build-script-utils/src/lib.rs b/utils/build-script-utils/src/lib.rs index 7e69f2ac85d4..7bab35c9879e 100644 --- a/utils/build-script-utils/src/lib.rs +++ b/utils/build-script-utils/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/build-script-utils/src/version.rs b/utils/build-script-utils/src/version.rs index 19b507ba261e..4ee5376ed322 100644 --- a/utils/build-script-utils/src/version.rs +++ b/utils/build-script-utils/src/version.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/fork-tree/Cargo.toml b/utils/fork-tree/Cargo.toml index c60ef8fd33e8..ece7cac8fd30 100644 --- a/utils/fork-tree/Cargo.toml +++ b/utils/fork-tree/Cargo.toml @@ -14,4 +14,4 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1", features = ["derive"] } diff --git a/utils/fork-tree/src/lib.rs b/utils/fork-tree/src/lib.rs index 76c28d910f94..cd175166b9cd 100644 --- a/utils/fork-tree/src/lib.rs +++ b/utils/fork-tree/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,7 +32,7 @@ pub enum Error { UnfinalizedAncestor, /// Imported or finalized node that is an ancestor of previously finalized node. Revert, - /// Error throw by client when checking for node ancestry. + /// Error thrown by user when checking for node ancestry. Client(E), } @@ -48,11 +48,7 @@ impl fmt::Display for Error { } } -impl std::error::Error for Error { - fn cause(&self) -> Option<&dyn std::error::Error> { - None - } -} +impl std::error::Error for Error {} impl From for Error { fn from(err: E) -> Error { @@ -83,7 +79,7 @@ pub enum FilterAction { /// A tree data structure that stores several nodes across multiple branches. /// /// Top-level branches are called roots. The tree has functionality for -/// finalizing nodes, which means that that node is traversed, and all competing +/// finalizing nodes, which means that node is traversed, and all competing /// branches are pruned. It also guarantees that nodes in the tree are finalized /// in order. Each node is uniquely identified by its hash but can be ordered by /// its number. In order to build the tree an external function must be provided @@ -99,12 +95,14 @@ where H: PartialEq, N: Ord, { - /// Create a new empty tree. + /// Create a new empty tree instance. pub fn new() -> ForkTree { ForkTree { roots: Vec::new(), best_finalized_number: None } } - /// Rebalance the tree, i.e. sort child nodes by max branch depth (decreasing). + /// Rebalance the tree. + /// + /// For each tree level sort child nodes by max branch depth (decreasing). /// /// Most operations in the tree are performed with depth-first search /// starting from the leftmost node at every level, since this tree is meant @@ -120,10 +118,12 @@ where } } - /// Import a new node into the tree. The given function `is_descendent_of` - /// should return `true` if the second hash (target) is a descendent of the - /// first hash (base). This method assumes that nodes in the same branch are - /// imported in order. + /// Import a new node into the tree. + /// + /// The given function `is_descendent_of` should return `true` if the second + /// hash (target) is a descendent of the first hash (base). + /// + /// This method assumes that nodes in the same branch are imported in order. /// /// Returns `true` if the imported node is a root. // WARNING: some users of this method (i.e. consensus epoch changes tree) currently silently @@ -232,9 +232,10 @@ where } /// Find a node in the tree that is the deepest ancestor of the given - /// block hash and which passes the given predicate. The given function - /// `is_descendent_of` should return `true` if the second hash (target) - /// is a descendent of the first hash (base). + /// block hash and which passes the given predicate. + /// + /// The given function `is_descendent_of` should return `true` if the + /// second hash (target) is a descendent of the first hash (base). pub fn find_node_where( &self, hash: &H, @@ -281,10 +282,12 @@ where /// Same as [`find_node_where`](ForkTree::find_node_where), but returns indices. /// /// The returned indices represent the full path to reach the matching node starting - /// from first to last, i.e. the earliest index in the traverse path goes first, and the final - /// index in the traverse path goes last. If a node is found that matches the predicate - /// the returned path should always contain at least one index, otherwise `None` is - /// returned. + /// from one of the roots, i.e. the earliest index in the traverse path goes first, + /// and the final index in the traverse path goes last. + /// + /// If a node is found that matches the predicate the returned path should always + /// contain at least one index, otherwise `None` is returned. + // // WARNING: some users of this method (i.e. consensus epoch changes tree) currently silently // rely on a **post-order DFS** traversal. If we are using instead a top-down traversal method // then the `is_descendent_of` closure, when used after a warp-sync, will end up querying the @@ -351,14 +354,16 @@ where }) } - /// Prune the tree, removing all non-canonical nodes. We find the node in the - /// tree that is the deepest ancestor of the given hash and that passes the - /// given predicate. If such a node exists, we re-root the tree to this - /// node. Otherwise the tree remains unchanged. The given function - /// `is_descendent_of` should return `true` if the second hash (target) is a - /// descendent of the first hash (base). + /// Prune the tree, removing all non-canonical nodes. /// - /// Returns all pruned node data. + /// We find the node in the tree that is the deepest ancestor of the given hash + /// and that passes the given predicate. If such a node exists, we re-root the + /// tree to this node. Otherwise the tree remains unchanged. + /// + /// The given function `is_descendent_of` should return `true` if the second + /// hash (target) is a descendent of the first hash (base). + /// + /// Returns all pruned nodes data. pub fn prune( &mut self, hash: &H, @@ -371,42 +376,59 @@ where F: Fn(&H, &H) -> Result, P: Fn(&V) -> bool, { - let root_index = + let new_root_path = match self.find_node_index_where(hash, number, is_descendent_of, predicate)? { - Some(idx) => idx, + Some(path) => path, None => return Ok(RemovedIterator { stack: Vec::new() }), }; - let mut old_roots = std::mem::take(&mut self.roots); + let mut removed = std::mem::take(&mut self.roots); - let curr_children = root_index + // Find and detach the new root from the removed nodes + let root_siblings = new_root_path .iter() - .take(root_index.len() - 1) - .fold(&mut old_roots, |curr, idx| &mut curr[*idx].children); - let mut root = curr_children.remove(root_index[root_index.len() - 1]); - - let mut removed = old_roots; + .take(new_root_path.len() - 1) + .fold(&mut removed, |curr, idx| &mut curr[*idx].children); + let root = root_siblings.remove(new_root_path[new_root_path.len() - 1]); + self.roots = vec![root]; - // we found the deepest ancestor of the finalized block, so we prune - // out any children that don't include the finalized block. - let root_children = std::mem::take(&mut root.children); - let mut is_first = true; + // If, because of the `predicate`, the new root is not the deepest ancestor + // of `hash` then we can remove all the nodes that are descendants of the new + // `root` but not ancestors of `hash`. + let mut curr = &mut self.roots[0]; + loop { + let mut maybe_ancestor_idx = None; + for (idx, child) in curr.children.iter().enumerate() { + if child.number < *number && is_descendent_of(&child.hash, hash)? { + maybe_ancestor_idx = Some(idx); + break + } + } + let Some(ancestor_idx) = maybe_ancestor_idx else { + // Now we are positioned just above block identified by `hash` + break + }; + // Preserve only the ancestor node, the siblings are removed + let mut next_siblings = std::mem::take(&mut curr.children); + let next = next_siblings.remove(ancestor_idx); + curr.children = vec![next]; + removed.append(&mut next_siblings); + curr = &mut curr.children[0]; + } - for child in root_children { - if is_first && - (child.number == *number && child.hash == *hash || - child.number < *number && is_descendent_of(&child.hash, hash)?) + // Curr now points to our direct ancestor, if necessary remove any node that is + // not a descendant of `hash`. + let children = std::mem::take(&mut curr.children); + for child in children { + if child.number == *number && child.hash == *hash || + *number < child.number && is_descendent_of(hash, &child.hash)? { - root.children.push(child); - // assuming that the tree is well formed only one child should pass this - // requirement due to ancestry restrictions (i.e. they must be different forks). - is_first = false; + curr.children.push(child); } else { removed.push(child); } } - self.roots = vec![root]; self.rebalance(); Ok(RemovedIterator { stack: removed }) @@ -836,21 +858,21 @@ mod test { impl std::error::Error for TestError {} fn test_fork_tree<'a>( - ) -> (ForkTree<&'a str, u64, ()>, impl Fn(&&str, &&str) -> Result) { + ) -> (ForkTree<&'a str, u64, u32>, impl Fn(&&str, &&str) -> Result) { let mut tree = ForkTree::new(); #[rustfmt::skip] // - // - B - C - D - E - // / - // / - G - // / / - // A - F - H - I - // \ \ - // \ - L - M - N - // \ \ - // \ - O - // - J - K + // +---B-c-C---D---E + // | + // | +---G + // | | + // 0---A---F---H---I + // | | + // | +---L-m-M---N + // | | + // | +---O + // +---J---K // // (where N is not a part of fork tree) // @@ -859,9 +881,12 @@ mod test { // will be on the leftmost side of the tree. let is_descendent_of = |base: &&str, block: &&str| -> Result { let letters = vec!["B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O"]; - match (*base, *block) { + // This is a trick to have lowercase blocks be direct parents of their + // uppercase correspondent (A excluded) + let block = block.to_uppercase(); + match (*base, block) { ("A", b) => Ok(letters.into_iter().any(|n| n == b)), - ("B", b) => Ok(b == "C" || b == "D" || b == "E"), + ("B" | "c", b) => Ok(b == "C" || b == "D" || b == "E"), ("C", b) => Ok(b == "D" || b == "E"), ("D", b) => Ok(b == "E"), ("E", _) => Ok(false), @@ -872,7 +897,8 @@ mod test { ("I", _) => Ok(false), ("J", b) => Ok(b == "K"), ("K", _) => Ok(false), - ("L", b) => Ok(b == "M" || b == "O" || b == "N"), + ("L", b) => Ok(b == "M" || b == "N" || b == "O"), + ("m", b) => Ok(b == "M" || b == "N"), ("M", b) => Ok(b == "N"), ("O", _) => Ok(false), ("0", _) => Ok(true), @@ -880,24 +906,20 @@ mod test { } }; - tree.import("A", 1, (), &is_descendent_of).unwrap(); - - tree.import("B", 2, (), &is_descendent_of).unwrap(); - tree.import("C", 3, (), &is_descendent_of).unwrap(); - tree.import("D", 4, (), &is_descendent_of).unwrap(); - tree.import("E", 5, (), &is_descendent_of).unwrap(); - - tree.import("F", 2, (), &is_descendent_of).unwrap(); - tree.import("G", 3, (), &is_descendent_of).unwrap(); - - tree.import("H", 3, (), &is_descendent_of).unwrap(); - tree.import("I", 4, (), &is_descendent_of).unwrap(); - tree.import("L", 4, (), &is_descendent_of).unwrap(); - tree.import("M", 5, (), &is_descendent_of).unwrap(); - tree.import("O", 5, (), &is_descendent_of).unwrap(); - - tree.import("J", 2, (), &is_descendent_of).unwrap(); - tree.import("K", 3, (), &is_descendent_of).unwrap(); + tree.import("A", 10, 1, &is_descendent_of).unwrap(); + tree.import("B", 20, 2, &is_descendent_of).unwrap(); + tree.import("C", 30, 3, &is_descendent_of).unwrap(); + tree.import("D", 40, 4, &is_descendent_of).unwrap(); + tree.import("E", 50, 5, &is_descendent_of).unwrap(); + tree.import("F", 20, 2, &is_descendent_of).unwrap(); + tree.import("G", 30, 3, &is_descendent_of).unwrap(); + tree.import("H", 30, 3, &is_descendent_of).unwrap(); + tree.import("I", 40, 4, &is_descendent_of).unwrap(); + tree.import("L", 40, 4, &is_descendent_of).unwrap(); + tree.import("M", 50, 5, &is_descendent_of).unwrap(); + tree.import("O", 50, 5, &is_descendent_of).unwrap(); + tree.import("J", 20, 2, &is_descendent_of).unwrap(); + tree.import("K", 30, 3, &is_descendent_of).unwrap(); (tree, is_descendent_of) } @@ -908,22 +930,22 @@ mod test { tree.finalize_root(&"A"); - assert_eq!(tree.best_finalized_number, Some(1)); + assert_eq!(tree.best_finalized_number, Some(10)); - assert_eq!(tree.import("A", 1, (), &is_descendent_of), Err(Error::Revert)); + assert_eq!(tree.import("A", 10, 1, &is_descendent_of), Err(Error::Revert)); } #[test] fn import_doesnt_add_duplicates() { let (mut tree, is_descendent_of) = test_fork_tree(); - assert_eq!(tree.import("A", 1, (), &is_descendent_of), Err(Error::Duplicate)); + assert_eq!(tree.import("A", 10, 1, &is_descendent_of), Err(Error::Duplicate)); - assert_eq!(tree.import("I", 4, (), &is_descendent_of), Err(Error::Duplicate)); + assert_eq!(tree.import("I", 40, 4, &is_descendent_of), Err(Error::Duplicate)); - assert_eq!(tree.import("G", 3, (), &is_descendent_of), Err(Error::Duplicate)); + assert_eq!(tree.import("G", 30, 3, &is_descendent_of), Err(Error::Duplicate)); - assert_eq!(tree.import("K", 3, (), &is_descendent_of), Err(Error::Duplicate)); + assert_eq!(tree.import("K", 30, 3, &is_descendent_of), Err(Error::Duplicate)); } #[test] @@ -931,14 +953,14 @@ mod test { let finalize_a = || { let (mut tree, ..) = test_fork_tree(); - assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("A", 1)]); + assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("A", 10)]); // finalizing "A" opens up three possible forks tree.finalize_root(&"A"); assert_eq!( tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), - vec![("B", 2), ("F", 2), ("J", 2)], + vec![("B", 20), ("F", 20), ("J", 20)], ); tree @@ -950,7 +972,7 @@ mod test { // finalizing "B" will progress on its fork and remove any other competing forks tree.finalize_root(&"B"); - assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("C", 3)],); + assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("C", 30)],); // all the other forks have been pruned assert!(tree.roots.len() == 1); @@ -962,7 +984,7 @@ mod test { // finalizing "J" will progress on its fork and remove any other competing forks tree.finalize_root(&"J"); - assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("K", 3)],); + assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![("K", 30)],); // all the other forks have been pruned assert!(tree.roots.len() == 1); @@ -982,42 +1004,42 @@ mod test { // finalizing "A" opens up three possible forks assert_eq!( - tree.finalize(&"A", 1, &is_descendent_of), - Ok(FinalizationResult::Changed(Some(()))), + tree.finalize(&"A", 10, &is_descendent_of), + Ok(FinalizationResult::Changed(Some(1))), ); assert_eq!( tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), - vec![("B", 2), ("F", 2), ("J", 2)], + vec![("B", 20), ("F", 20), ("J", 20)], ); // finalizing anything lower than what we observed will fail - assert_eq!(tree.best_finalized_number, Some(1)); + assert_eq!(tree.best_finalized_number, Some(10)); - assert_eq!(tree.finalize(&"Z", 1, &is_descendent_of), Err(Error::Revert)); + assert_eq!(tree.finalize(&"Z", 10, &is_descendent_of), Err(Error::Revert)); // trying to finalize a node without finalizing its ancestors first will fail - assert_eq!(tree.finalize(&"H", 3, &is_descendent_of), Err(Error::UnfinalizedAncestor)); + assert_eq!(tree.finalize(&"H", 30, &is_descendent_of), Err(Error::UnfinalizedAncestor)); // after finalizing "F" we can finalize "H" assert_eq!( - tree.finalize(&"F", 2, &is_descendent_of), - Ok(FinalizationResult::Changed(Some(()))), + tree.finalize(&"F", 20, &is_descendent_of), + Ok(FinalizationResult::Changed(Some(2))), ); assert_eq!( - tree.finalize(&"H", 3, &is_descendent_of), - Ok(FinalizationResult::Changed(Some(()))), + tree.finalize(&"H", 30, &is_descendent_of), + Ok(FinalizationResult::Changed(Some(3))), ); assert_eq!( tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), - vec![("L", 4), ("I", 4)], + vec![("L", 40), ("I", 40)], ); // finalizing a node from another fork that isn't part of the tree clears the tree assert_eq!( - tree.finalize(&"Z", 5, &is_descendent_of), + tree.finalize(&"Z", 50, &is_descendent_of), Ok(FinalizationResult::Changed(None)), ); @@ -1040,13 +1062,13 @@ mod test { // finalizing "A" opens up three possible forks assert_eq!( - tree.finalize_with_ancestors(&"A", 1, &is_descendent_of), - Ok(FinalizationResult::Changed(Some(()))), + tree.finalize_with_ancestors(&"A", 10, &is_descendent_of), + Ok(FinalizationResult::Changed(Some(1))), ); assert_eq!( tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), - vec![("B", 2), ("F", 2), ("J", 2)], + vec![("B", 20), ("F", 20), ("J", 20)], ); // finalizing H: @@ -1054,16 +1076,16 @@ mod test { // 2) opens root that is ancestor of H (F -> G+H) // 3) finalizes the just opened root H (H -> I + L) assert_eq!( - tree.finalize_with_ancestors(&"H", 3, &is_descendent_of), - Ok(FinalizationResult::Changed(Some(()))), + tree.finalize_with_ancestors(&"H", 30, &is_descendent_of), + Ok(FinalizationResult::Changed(Some(3))), ); assert_eq!( tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), - vec![("L", 4), ("I", 4)], + vec![("L", 40), ("I", 40)], ); - assert_eq!(tree.best_finalized_number, Some(3)); + assert_eq!(tree.best_finalized_number, Some(30)); // finalizing N (which is not a part of the tree): // 1) removes roots that are not ancestors/descendants of N (I) @@ -1071,13 +1093,13 @@ mod test { // 3) removes roots that are not ancestors/descendants of N (O) // 4) opens root that is ancestor of N (M -> {}) assert_eq!( - tree.finalize_with_ancestors(&"N", 6, &is_descendent_of), + tree.finalize_with_ancestors(&"N", 60, &is_descendent_of), Ok(FinalizationResult::Changed(None)), ); assert_eq!(tree.roots().map(|(h, n, _)| (*h, *n)).collect::>(), vec![],); - assert_eq!(tree.best_finalized_number, Some(6)); + assert_eq!(tree.best_finalized_number, Some(60)); } #[test] @@ -1209,20 +1231,20 @@ mod test { assert_eq!( tree.iter().map(|(h, n, _)| (*h, *n)).collect::>(), vec![ - ("A", 1), - ("B", 2), - ("C", 3), - ("D", 4), - ("E", 5), - ("F", 2), - ("H", 3), - ("L", 4), - ("M", 5), - ("O", 5), - ("I", 4), - ("G", 3), - ("J", 2), - ("K", 3), + ("A", 10), + ("B", 20), + ("C", 30), + ("D", 40), + ("E", 50), + ("F", 20), + ("H", 30), + ("L", 40), + ("M", 50), + ("O", 50), + ("I", 40), + ("G", 30), + ("J", 20), + ("K", 30), ], ); } @@ -1289,9 +1311,9 @@ mod test { // Extend the single root fork-tree to also excercise the roots order during map. let is_descendent_of = |_: &&str, _: &&str| -> Result { Ok(false) }; - let is_root = tree.import("A1", 1, (), &is_descendent_of).unwrap(); + let is_root = tree.import("A1", 10, 1, &is_descendent_of).unwrap(); assert!(is_root); - let is_root = tree.import("A2", 1, (), &is_descendent_of).unwrap(); + let is_root = tree.import("A2", 10, 1, &is_descendent_of).unwrap(); assert!(is_root); let old_tree = tree.clone(); @@ -1306,10 +1328,10 @@ mod test { } #[test] - fn prune_works() { + fn prune_works_for_in_tree_hashes() { let (mut tree, is_descendent_of) = test_fork_tree(); - let removed = tree.prune(&"C", &3, &is_descendent_of, &|_| true).unwrap(); + let removed = tree.prune(&"C", &30, &is_descendent_of, &|_| true).unwrap(); assert_eq!(tree.roots.iter().map(|node| node.hash).collect::>(), vec!["B"]); @@ -1323,7 +1345,7 @@ mod test { vec!["A", "F", "H", "L", "M", "O", "I", "G", "J", "K"] ); - let removed = tree.prune(&"E", &5, &is_descendent_of, &|_| true).unwrap(); + let removed = tree.prune(&"E", &50, &is_descendent_of, &|_| true).unwrap(); assert_eq!(tree.roots.iter().map(|node| node.hash).collect::>(), vec!["D"]); @@ -1332,6 +1354,61 @@ mod test { assert_eq!(removed.map(|(hash, _, _)| hash).collect::>(), vec!["B", "C"]); } + #[test] + fn prune_works_for_out_of_tree_hashes() { + let (mut tree, is_descendent_of) = test_fork_tree(); + + let removed = tree.prune(&"c", &25, &is_descendent_of, &|_| true).unwrap(); + + assert_eq!(tree.roots.iter().map(|node| node.hash).collect::>(), vec!["B"]); + + assert_eq!( + tree.iter().map(|(hash, _, _)| *hash).collect::>(), + vec!["B", "C", "D", "E"], + ); + + assert_eq!( + removed.map(|(hash, _, _)| hash).collect::>(), + vec!["A", "F", "H", "L", "M", "O", "I", "G", "J", "K"] + ); + } + + #[test] + fn prune_works_for_not_direct_ancestor() { + let (mut tree, is_descendent_of) = test_fork_tree(); + + // This is to re-root the tree not at the immediate ancestor, but the one just before. + let removed = tree.prune(&"m", &45, &is_descendent_of, &|height| *height == 3).unwrap(); + + assert_eq!(tree.roots.iter().map(|node| node.hash).collect::>(), vec!["H"]); + + assert_eq!(tree.iter().map(|(hash, _, _)| *hash).collect::>(), vec!["H", "L", "M"],); + + assert_eq!( + removed.map(|(hash, _, _)| hash).collect::>(), + vec!["O", "I", "A", "B", "C", "D", "E", "F", "G", "J", "K"] + ); + } + + #[test] + fn prune_works_for_far_away_ancestor() { + let (mut tree, is_descendent_of) = test_fork_tree(); + + let removed = tree.prune(&"m", &45, &is_descendent_of, &|height| *height == 2).unwrap(); + + assert_eq!(tree.roots.iter().map(|node| node.hash).collect::>(), vec!["F"]); + + assert_eq!( + tree.iter().map(|(hash, _, _)| *hash).collect::>(), + vec!["F", "H", "L", "M"], + ); + + assert_eq!( + removed.map(|(hash, _, _)| hash).collect::>(), + vec!["O", "I", "G", "A", "B", "C", "D", "E", "J", "K"] + ); + } + #[test] fn find_node_backtracks_after_finding_highest_descending_node() { let mut tree = ForkTree::new(); @@ -1381,7 +1458,7 @@ mod test { } }; - tree.import("P", 6, (), &is_descendent_of).unwrap(); + tree.import("P", 60, 6, &is_descendent_of).unwrap(); // this should re-order the tree, since the branch "A -> B -> C -> D -> E" is no longer tied // with 5 blocks depth. additionally "O" should be visited before "M" now, since it has one @@ -1396,7 +1473,7 @@ mod test { fn drain_filter_works() { let (mut tree, _) = test_fork_tree(); - let filter = |h: &&str, _: &u64, _: &()| match *h { + let filter = |h: &&str, _: &u64, _: &u32| match *h { "A" | "B" | "F" | "G" => FilterAction::KeepNode, "C" => FilterAction::KeepTree, "H" | "J" => FilterAction::Remove, @@ -1421,19 +1498,19 @@ mod test { let (tree, is_descendent_of) = test_fork_tree(); let path = tree - .find_node_index_where(&"D", &4, &is_descendent_of, &|_| true) + .find_node_index_where(&"D", &40, &is_descendent_of, &|_| true) .unwrap() .unwrap(); assert_eq!(path, [0, 0, 0]); let path = tree - .find_node_index_where(&"O", &5, &is_descendent_of, &|_| true) + .find_node_index_where(&"O", &50, &is_descendent_of, &|_| true) .unwrap() .unwrap(); assert_eq!(path, [0, 1, 0, 0]); let path = tree - .find_node_index_where(&"N", &6, &is_descendent_of, &|_| true) + .find_node_index_where(&"N", &60, &is_descendent_of, &|_| true) .unwrap() .unwrap(); assert_eq!(path, [0, 1, 0, 0, 0]); @@ -1489,17 +1566,17 @@ mod test { fn find_node_works() { let (tree, is_descendent_of) = test_fork_tree(); - let node = tree.find_node_where(&"B", &2, &is_descendent_of, &|_| true).unwrap().unwrap(); - assert_eq!((node.hash, node.number), ("A", 1)); + let node = tree.find_node_where(&"B", &20, &is_descendent_of, &|_| true).unwrap().unwrap(); + assert_eq!((node.hash, node.number), ("A", 10)); - let node = tree.find_node_where(&"D", &4, &is_descendent_of, &|_| true).unwrap().unwrap(); - assert_eq!((node.hash, node.number), ("C", 3)); + let node = tree.find_node_where(&"D", &40, &is_descendent_of, &|_| true).unwrap().unwrap(); + assert_eq!((node.hash, node.number), ("C", 30)); - let node = tree.find_node_where(&"O", &5, &is_descendent_of, &|_| true).unwrap().unwrap(); - assert_eq!((node.hash, node.number), ("L", 4)); + let node = tree.find_node_where(&"O", &50, &is_descendent_of, &|_| true).unwrap().unwrap(); + assert_eq!((node.hash, node.number), ("L", 40)); - let node = tree.find_node_where(&"N", &6, &is_descendent_of, &|_| true).unwrap().unwrap(); - assert_eq!((node.hash, node.number), ("M", 5)); + let node = tree.find_node_where(&"N", &60, &is_descendent_of, &|_| true).unwrap().unwrap(); + assert_eq!((node.hash, node.number), ("M", 50)); } #[test] @@ -1516,13 +1593,13 @@ mod test { // Post order traversal requirement for `find_node_index_where` let path = tree - .find_node_index_where(&"N", &6, &is_descendent_of_for_post_order, &|_| true) + .find_node_index_where(&"N", &60, &is_descendent_of_for_post_order, &|_| true) .unwrap() .unwrap(); assert_eq!(path, [0, 1, 0, 0, 0]); // Post order traversal requirement for `import` - let res = tree.import(&"Z", 100, (), &is_descendent_of_for_post_order); + let res = tree.import(&"Z", 100, 10, &is_descendent_of_for_post_order); assert_eq!(res, Ok(false)); assert_eq!( tree.iter().map(|node| *node.0).collect::>(), diff --git a/utils/frame/benchmarking-cli/Cargo.toml b/utils/frame/benchmarking-cli/Cargo.toml index fc23d07b6216..f6dadf4a98a7 100644 --- a/utils/frame/benchmarking-cli/Cargo.toml +++ b/utils/frame/benchmarking-cli/Cargo.toml @@ -13,11 +13,11 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -array-bytes = "4.1" +array-bytes = "6.1" chrono = "0.4" -clap = { version = "4.0.9", features = ["derive"] } -codec = { package = "parity-scale-codec", version = "3.2.2" } -comfy-table = { version = "6.0.0", default-features = false } +clap = { version = "4.2.5", features = ["derive"] } +codec = { package = "parity-scale-codec", version = "3.6.1" } +comfy-table = { version = "7.0.0", default-features = false } handlebars = "4.2.2" Inflector = "0.11.4" itertools = "0.10.3" @@ -26,7 +26,7 @@ linked-hash-map = "0.5.4" log = "0.4.17" rand = { version = "0.8.4", features = ["small_rng"] } rand_pcg = "0.3.1" -serde = "1.0.136" +serde = "1.0.163" serde_json = "1.0.85" thiserror = "1.0.30" thousands = "0.2.0" @@ -42,16 +42,17 @@ sc-service = { version = "0.10.0-dev", default-features = false, path = "../../. sc-sysinfo = { version = "6.0.0-dev", path = "../../../client/sysinfo" } sp-api = { version = "4.0.0-dev", path = "../../../primitives/api" } sp-blockchain = { version = "4.0.0-dev", path = "../../../primitives/blockchain" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } sp-database = { version = "4.0.0-dev", path = "../../../primitives/database" } -sp-externalities = { version = "0.13.0", path = "../../../primitives/externalities" } +sp-externalities = { version = "0.19.0", path = "../../../primitives/externalities" } sp-inherents = { version = "4.0.0-dev", path = "../../../primitives/inherents" } -sp-keystore = { version = "0.13.0", path = "../../../primitives/keystore" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } -sp-state-machine = { version = "0.13.0", path = "../../../primitives/state-machine" } -sp-std = { version = "5.0.0", path = "../../../primitives/std" } -sp-storage = { version = "7.0.0", path = "../../../primitives/storage" } -sp-trie = { version = "7.0.0", path = "../../../primitives/trie" } +sp-keystore = { version = "0.27.0", path = "../../../primitives/keystore" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } +sp-state-machine = { version = "0.28.0", path = "../../../primitives/state-machine" } +sp-storage = { version = "13.0.0", path = "../../../primitives/storage" } +sp-trie = { version = "22.0.0", path = "../../../primitives/trie" } +sp-io = { version = "23.0.0", path = "../../../primitives/io" } +sp-wasm-interface = { version = "14.0.0", path = "../../../primitives/wasm-interface" } gethostname = "0.2.3" [features] diff --git a/utils/frame/benchmarking-cli/build.rs b/utils/frame/benchmarking-cli/build.rs index 434780415681..1545d1e0c21e 100644 --- a/utils/frame/benchmarking-cli/build.rs +++ b/utils/frame/benchmarking-cli/build.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/benchmarking-cli/src/block/bench.rs b/utils/frame/benchmarking-cli/src/block/bench.rs index c69d95725eb1..960056991a19 100644 --- a/utils/frame/benchmarking-cli/src/block/bench.rs +++ b/utils/frame/benchmarking-cli/src/block/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -92,13 +92,12 @@ where for i in self.params.from..=self.params.to { let block_num = BlockId::Number(i.into()); - let parent_num = BlockId::Number(((i - 1) as u32).into()); - let consumed = self.consumed_weight(&block_num)?; let hash = self.client.expect_block_hash_from_id(&block_num)?; + let consumed = self.consumed_weight(hash)?; let block = self.client.block(hash)?.ok_or(format!("Block {} not found", block_num))?; let block = self.unsealed(block.block); - let took = self.measure_block(&block, &parent_num)?; + let took = self.measure_block(&block, *block.header().parent_hash())?; self.log_weight(i, block.extrinsics().len(), consumed, took); } @@ -107,7 +106,7 @@ where } /// Return the average *execution* aka. *import* time of the block. - fn measure_block(&self, block: &Block, parent_num: &BlockId) -> Result { + fn measure_block(&self, block: &Block, parent_hash: Block::Hash) -> Result { let mut record = Vec::::default(); // Interesting part here: // Execute the block multiple times and collect stats about its execution time. @@ -117,7 +116,7 @@ where let start = Instant::now(); runtime_api - .execute_block(&parent_num, block) + .execute_block(parent_hash, block) .map_err(|e| Error::Client(RuntimeApiError(e)))?; record.push(start.elapsed().as_nanos() as NanoSeconds); @@ -131,7 +130,7 @@ where /// /// This is the post-dispatch corrected weight and is only available /// after executing the block. - fn consumed_weight(&self, block: &BlockId) -> Result { + fn consumed_weight(&self, block_hash: Block::Hash) -> Result { // Hard-coded key for System::BlockWeight. It could also be passed in as argument // for the benchmark, but I think this should work as well. let hash = array_bytes::hex2bytes( @@ -139,11 +138,10 @@ where )?; let key = StorageKey(hash); - let block_hash = self.client.expect_block_hash_from_id(block)?; let mut raw_weight = &self .client .storage(block_hash, &key)? - .ok_or(format!("Could not find System::BlockWeight for block: {}", block))? + .ok_or(format!("Could not find System::BlockWeight for block: {}", block_hash))? .0[..]; let weight = ConsumedWeight::decode_all(&mut raw_weight)?; diff --git a/utils/frame/benchmarking-cli/src/block/cmd.rs b/utils/frame/benchmarking-cli/src/block/cmd.rs index 8bac04110f7a..90b71cd78c2a 100644 --- a/utils/frame/benchmarking-cli/src/block/cmd.rs +++ b/utils/frame/benchmarking-cli/src/block/cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,12 +39,12 @@ use super::bench::{Benchmark, BenchmarkParams}; /// did not use more weight than declared which would otherwise be an issue. /// To test this with a dev node, first create one with a temp directory: /// -/// $ substrate --dev -d /tmp/my-dev --execution wasm --wasm-execution compiled +/// $ substrate --dev -d /tmp/my-dev --wasm-execution compiled /// /// And wait some time to let it produce 3 blocks. Then benchmark them with: /// /// $ substrate benchmark-block --from 1 --to 3 --dev -d /tmp/my-dev -/// --execution wasm --wasm-execution compiled --pruning archive +/// --wasm-execution compiled --pruning archive /// /// The output will be similar to this: /// diff --git a/utils/frame/benchmarking-cli/src/block/mod.rs b/utils/frame/benchmarking-cli/src/block/mod.rs index 97fdb6ad2c20..9fbd4ea70801 100644 --- a/utils/frame/benchmarking-cli/src/block/mod.rs +++ b/utils/frame/benchmarking-cli/src/block/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/benchmarking-cli/src/extrinsic/bench.rs b/utils/frame/benchmarking-cli/src/extrinsic/bench.rs index 2a86c10e7ad2..facde14adab5 100644 --- a/utils/frame/benchmarking-cli/src/extrinsic/bench.rs +++ b/utils/frame/benchmarking-cli/src/extrinsic/bench.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,13 +20,13 @@ use sc_block_builder::{BlockBuilderApi, BlockBuilderProvider}; use sc_cli::{Error, Result}; use sc_client_api::Backend as ClientBackend; -use sp_api::{ApiExt, BlockId, Core, ProvideRuntimeApi}; +use sp_api::{ApiExt, Core, ProvideRuntimeApi}; use sp_blockchain::{ ApplyExtrinsicFailed::Validity, Error::{ApplyExtrinsicFailed, RuntimeApiError}, }; use sp_runtime::{ - traits::{Block as BlockT, Zero}, + traits::Block as BlockT, transaction_validity::{InvalidTransaction, TransactionValidityError}, Digest, DigestItem, OpaqueExtrinsic, }; @@ -73,7 +73,9 @@ impl Benchmark where Block: BlockT, BA: ClientBackend, - C: BlockBuilderProvider + ProvideRuntimeApi, + C: BlockBuilderProvider + + ProvideRuntimeApi + + sp_blockchain::HeaderBackend, C::Api: ApiExt + BlockBuilderApi, { /// Create a new [`Self`] from the arguments. @@ -167,13 +169,13 @@ where /// Measures the time that it take to execute a block or an extrinsic. fn measure_block(&self, block: &Block) -> Result { let mut record = BenchRecord::new(); - let genesis = BlockId::Number(Zero::zero()); + let genesis = self.client.info().genesis_hash; info!("Running {} warmups...", self.params.warmup); for _ in 0..self.params.warmup { self.client .runtime_api() - .execute_block(&genesis, block.clone()) + .execute_block(genesis, block.clone()) .map_err(|e| Error::Client(RuntimeApiError(e)))?; } @@ -186,7 +188,7 @@ where let start = Instant::now(); runtime_api - .execute_block(&genesis, block) + .execute_block(genesis, block) .map_err(|e| Error::Client(RuntimeApiError(e)))?; let elapsed = start.elapsed().as_nanos(); diff --git a/utils/frame/benchmarking-cli/src/extrinsic/cmd.rs b/utils/frame/benchmarking-cli/src/extrinsic/cmd.rs index b95cd6b5c2e4..1001958fe0d2 100644 --- a/utils/frame/benchmarking-cli/src/extrinsic/cmd.rs +++ b/utils/frame/benchmarking-cli/src/extrinsic/cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -94,7 +94,9 @@ impl ExtrinsicCmd { where Block: BlockT, BA: ClientBackend, - C: BlockBuilderProvider + ProvideRuntimeApi, + C: BlockBuilderProvider + + ProvideRuntimeApi + + sp_blockchain::HeaderBackend, C::Api: ApiExt + BlockBuilderApi, { // Short circuit if --list was specified. diff --git a/utils/frame/benchmarking-cli/src/extrinsic/extrinsic_factory.rs b/utils/frame/benchmarking-cli/src/extrinsic/extrinsic_factory.rs index 7e1a22ccd141..9a209e9c56cf 100644 --- a/utils/frame/benchmarking-cli/src/extrinsic/extrinsic_factory.rs +++ b/utils/frame/benchmarking-cli/src/extrinsic/extrinsic_factory.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/benchmarking-cli/src/extrinsic/mod.rs b/utils/frame/benchmarking-cli/src/extrinsic/mod.rs index 12a09c3b1af6..4e8a66c0286a 100644 --- a/utils/frame/benchmarking-cli/src/extrinsic/mod.rs +++ b/utils/frame/benchmarking-cli/src/extrinsic/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/benchmarking-cli/src/lib.rs b/utils/frame/benchmarking-cli/src/lib.rs index 5723a8038fdb..0ef2c299de63 100644 --- a/utils/frame/benchmarking-cli/src/lib.rs +++ b/utils/frame/benchmarking-cli/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/benchmarking-cli/src/machine/hardware.rs b/utils/frame/benchmarking-cli/src/machine/hardware.rs index 318c193d7c08..5a4b7c797b6f 100644 --- a/utils/frame/benchmarking-cli/src/machine/hardware.rs +++ b/utils/frame/benchmarking-cli/src/machine/hardware.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -25,9 +25,6 @@ lazy_static! { /// /// These values are provided by Parity, however it is possible /// to use your own requirements if you are running a custom chain. - /// - /// The reference hardware is describe here: - /// pub static ref SUBSTRATE_REFERENCE_HARDWARE: Requirements = { let raw = include_bytes!("reference_hardware.json").as_slice(); serde_json::from_slice(raw).expect("Hardcoded data is known good; qed") @@ -37,8 +34,9 @@ lazy_static! { #[cfg(test)] mod tests { use super::*; + use sc_sysinfo::{Metric, Requirement, Requirements, Throughput}; - /// `SUBSTRATE_REFERENCE_HARDWARE` can be en- and decoded. + /// `SUBSTRATE_REFERENCE_HARDWARE` can be decoded. #[test] fn json_static_data() { let raw = serde_json::to_string(&*SUBSTRATE_REFERENCE_HARDWARE).unwrap(); @@ -46,4 +44,25 @@ mod tests { assert_eq!(decoded, SUBSTRATE_REFERENCE_HARDWARE.clone()); } + + /// The hard-coded values are correct. + #[test] + fn json_static_data_is_correct() { + assert_eq!( + *SUBSTRATE_REFERENCE_HARDWARE, + Requirements(vec![ + Requirement { metric: Metric::Blake2256, minimum: Throughput::from_mibs(783.27) }, + Requirement { + metric: Metric::Sr25519Verify, + minimum: Throughput::from_kibs(560.670000128), + }, + Requirement { + metric: Metric::MemCopy, + minimum: Throughput::from_gibs(11.4925205078125003), + }, + Requirement { metric: Metric::DiskSeqWrite, minimum: Throughput::from_mibs(950.0) }, + Requirement { metric: Metric::DiskRndWrite, minimum: Throughput::from_mibs(420.0) }, + ]) + ); + } } diff --git a/utils/frame/benchmarking-cli/src/machine/mod.rs b/utils/frame/benchmarking-cli/src/machine/mod.rs index bcffef255e5b..fb9f14c9a4af 100644 --- a/utils/frame/benchmarking-cli/src/machine/mod.rs +++ b/utils/frame/benchmarking-cli/src/machine/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/benchmarking-cli/src/machine/reference_hardware.json b/utils/frame/benchmarking-cli/src/machine/reference_hardware.json index 2a451d31403f..c2fb4c7d4a28 100644 --- a/utils/frame/benchmarking-cli/src/machine/reference_hardware.json +++ b/utils/frame/benchmarking-cli/src/machine/reference_hardware.json @@ -1,22 +1,22 @@ [ { "metric": "Blake2256", - "minimum": 1029.0 + "minimum": 783.27 }, { "metric": "Sr25519Verify", - "minimum": 0.650391 + "minimum": 0.547529297 }, { "metric": "MemCopy", - "minimum": 14666.752 + "minimum": 11768.341 }, { "metric": "DiskSeqWrite", - "minimum": 450.0 + "minimum": 950.0 }, { "metric": "DiskRndWrite", - "minimum": 200.0 + "minimum": 420.0 } ] diff --git a/utils/frame/benchmarking-cli/src/overhead/README.md b/utils/frame/benchmarking-cli/src/overhead/README.md index 1584c2affe0a..390bc09e4170 100644 --- a/utils/frame/benchmarking-cli/src/overhead/README.md +++ b/utils/frame/benchmarking-cli/src/overhead/README.md @@ -31,7 +31,7 @@ The file will contain the concrete weight value and various statistics about the /// 95th: 3_595_674 /// 75th: 3_526_435 pub const BlockExecutionWeight: Weight = - Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(3_532_484)); + Weight::from_parts(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(3_532_484), 0); ``` In this example it takes 3.5 ms to execute an empty block. That means that it always takes at least 3.5 ms to execute *any* block. @@ -61,7 +61,7 @@ The relevant section in the output file looks like this: /// 95th: 67_843 /// 75th: 67_749 pub const ExtrinsicBaseWeight: Weight = - Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(67_745)); + Weight::from_parts(WEIGHT_REF_TIME_PER_NANOS.saturating_mul(67_745), 0); ``` In this example it takes 67.7 µs to execute a NO-OP extrinsic. That means that it always takes at least 67.7 µs to execute *any* extrinsic. @@ -103,12 +103,12 @@ Writing weights to "extrinsic_weights.rs" The complete command for Polkadot looks like this: ```sh -cargo run --profile=production -- benchmark overhead --chain=polkadot-dev --execution=wasm --wasm-execution=compiled --weight-path=runtime/polkadot/constants/src/weights/ +cargo run --profile=production -- benchmark overhead --chain=polkadot-dev --wasm-execution=compiled --weight-path=runtime/polkadot/constants/src/weights/ ``` This will overwrite the the [block_weights.rs](https://github.com/paritytech/polkadot/blob/c254e5975711a6497af256f6831e9a6c752d28f5/runtime/polkadot/constants/src/weights/block_weights.rs) and [extrinsic_weights.rs](https://github.com/paritytech/polkadot/blob/c254e5975711a6497af256f6831e9a6c752d28f5/runtime/polkadot/constants/src/weights/extrinsic_weights.rs) files in the Polkadot runtime directory. You can try the same for *Rococo* and to see that the results slightly differ. -👉 It is paramount to use `--profile=production`, `--execution=wasm` and `--wasm-execution=compiled` as the results are otherwise useless. +👉 It is paramount to use `--profile=production` and `--wasm-execution=compiled` as the results are otherwise useless. ## Output Interpretation @@ -122,7 +122,6 @@ Minimizing this is important to have a large transaction throughput. - `--weight-path` Set the output directory or file to write the weights to. - `--repeat` Set the repetitions of both benchmarks. - `--warmup` Set the rounds of warmup before measuring. -- `--execution` Should be set to `wasm` for correct results. - `--wasm-execution` Should be set to `compiled` for correct results. - [`--mul`](../shared/README.md#arguments) - [`--add`](../shared/README.md#arguments) diff --git a/utils/frame/benchmarking-cli/src/overhead/cmd.rs b/utils/frame/benchmarking-cli/src/overhead/cmd.rs index 74c1e7efc24d..70e64cc2b66a 100644 --- a/utils/frame/benchmarking-cli/src/overhead/cmd.rs +++ b/utils/frame/benchmarking-cli/src/overhead/cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -108,7 +108,9 @@ impl OverheadCmd { where Block: BlockT, BA: ClientBackend, - C: BlockBuilderProvider + ProvideRuntimeApi, + C: BlockBuilderProvider + + ProvideRuntimeApi + + sp_blockchain::HeaderBackend, C::Api: ApiExt + BlockBuilderApi, { if ext_builder.pallet() != "system" || ext_builder.extrinsic() != "remark" { diff --git a/utils/frame/benchmarking-cli/src/overhead/mod.rs b/utils/frame/benchmarking-cli/src/overhead/mod.rs index fc3db912f7a3..00cde66fd722 100644 --- a/utils/frame/benchmarking-cli/src/overhead/mod.rs +++ b/utils/frame/benchmarking-cli/src/overhead/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/benchmarking-cli/src/overhead/template.rs b/utils/frame/benchmarking-cli/src/overhead/template.rs index ceed34d1981f..7c8c92b07d74 100644 --- a/utils/frame/benchmarking-cli/src/overhead/template.rs +++ b/utils/frame/benchmarking-cli/src/overhead/template.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/benchmarking-cli/src/overhead/weights.hbs b/utils/frame/benchmarking-cli/src/overhead/weights.hbs index c54393d200bd..6e364facc12f 100644 --- a/utils/frame/benchmarking-cli/src/overhead/weights.hbs +++ b/utils/frame/benchmarking-cli/src/overhead/weights.hbs @@ -35,7 +35,7 @@ parameter_types! { /// 95th: {{underscore stats.p95}} /// 75th: {{underscore stats.p75}} pub const {{long_name}}Weight: Weight = - Weight::from_ref_time(WEIGHT_REF_TIME_PER_NANOS.saturating_mul({{underscore weight}})); + Weight::from_parts(WEIGHT_REF_TIME_PER_NANOS.saturating_mul({{underscore weight}}), 0); } #[cfg(test)] diff --git a/utils/frame/benchmarking-cli/src/pallet/command.rs b/utils/frame/benchmarking-cli/src/pallet/command.rs index 242f0e685290..d98111d1f099 100644 --- a/utils/frame/benchmarking-cli/src/pallet/command.rs +++ b/utils/frame/benchmarking-cli/src/pallet/command.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -23,22 +23,23 @@ use frame_benchmarking::{ }; use frame_support::traits::StorageInfo; use linked_hash_map::LinkedHashMap; -use sc_cli::{ - execution_method_from_cli, CliConfiguration, ExecutionStrategy, Result, SharedParams, -}; +use sc_cli::{execution_method_from_cli, CliConfiguration, Result, SharedParams}; use sc_client_db::BenchmarkingState; -use sc_executor::NativeElseWasmExecutor; -use sc_service::{Configuration, NativeExecutionDispatch}; +use sc_executor::WasmExecutor; +use sc_service::Configuration; use serde::Serialize; -use sp_core::offchain::{ - testing::{TestOffchainExt, TestTransactionPoolExt}, - OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, +use sp_core::{ + offchain::{ + testing::{TestOffchainExt, TestTransactionPoolExt}, + OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, + }, + traits::{CallContext, ReadRuntimeVersionExt}, }; use sp_externalities::Extensions; -use sp_keystore::{testing::KeyStore, KeystoreExt, SyncCryptoStorePtr}; +use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use sp_state_machine::StateMachine; -use std::{collections::HashMap, fmt::Debug, fs, sync::Arc, time}; +use std::{collections::HashMap, fmt::Debug, fs, str::FromStr, time}; /// Logging target const LOG_TARGET: &'static str = "frame::benchmark::pallet"; @@ -54,6 +55,34 @@ pub(crate) struct ComponentRange { max: u32, } +/// How the PoV size of a storage item should be estimated. +#[derive(clap::ValueEnum, Debug, Eq, PartialEq, Clone, Copy)] +pub enum PovEstimationMode { + /// Use the maximal encoded length as provided by [`codec::MaxEncodedLen`]. + MaxEncodedLen, + /// Measure the accessed value size in the pallet benchmarking and add some trie overhead. + Measured, + /// Do not estimate the PoV size for this storage item or benchmark. + Ignored, +} + +impl FromStr for PovEstimationMode { + type Err = &'static str; + + fn from_str(s: &str) -> std::result::Result { + match s { + "MaxEncodedLen" => Ok(Self::MaxEncodedLen), + "Measured" => Ok(Self::Measured), + "Ignored" => Ok(Self::Ignored), + _ => unreachable!("The benchmark! macro should have prevented this"), + } + } +} + +/// Maps (pallet, benchmark) -> ((pallet, storage) -> PovEstimationMode) +pub(crate) type PovModesMap = + HashMap<(Vec, Vec), HashMap<(String, String), PovEstimationMode>>; + // This takes multiple benchmark batches and combines all the results where the pallet, instance, // and benchmark are the same. fn combine_batches( @@ -112,13 +141,22 @@ not created by a node that was compiled with the flag"; impl PalletCmd { /// Runs the command and benchmarks the chain. - pub fn run(&self, config: Configuration) -> Result<()> + pub fn run(&self, config: Configuration) -> Result<()> where BB: BlockT + Debug, <<::Header as HeaderT>::Number as std::str::FromStr>::Err: std::fmt::Debug, - ::Hash: std::str::FromStr, - ExecDispatch: NativeExecutionDispatch + 'static, + ExtraHostFunctions: sp_wasm_interface::HostFunctions, { + let _d = self.execution.as_ref().map(|exec| { + // We print the warning at the end, since there is often A LOT of output. + sp_core::defer::DeferGuard::new(move || { + log::warn!( + target: LOG_TARGET, + "⚠️ Argument `--execution` is deprecated. Its value of `{exec}` has on effect.", + ) + }) + }); + if let Some(output_path) = &self.output { if !output_path.is_dir() && output_path.file_name().is_none() { return Err("Output file or path is invalid!".into()) @@ -152,7 +190,6 @@ impl PalletCmd { } let spec = config.chain_spec; - let strategy = self.execution.unwrap_or(ExecutionStrategy::Native); let pallet = self.pallet.clone().unwrap_or_default(); let pallet = pallet.as_bytes(); let extrinsic = self.extrinsic.clone().unwrap_or_default(); @@ -165,26 +202,43 @@ impl PalletCmd { let state_with_tracking = BenchmarkingState::::new( genesis_storage.clone(), cache_size, - self.record_proof, + // Record proof size + true, + // Enable storage tracking true, )?; - let state_without_tracking = - BenchmarkingState::::new(genesis_storage, cache_size, self.record_proof, false)?; - let executor = NativeElseWasmExecutor::::new( - execution_method_from_cli(self.wasm_method, self.wasmtime_instantiation_strategy), - self.heap_pages, - 2, // The runtime instances cache size. - 2, // The runtime cache size - ); + let state_without_tracking = BenchmarkingState::::new( + genesis_storage, + cache_size, + // Do not record proof size + false, + // Do not enable storage tracking + false, + )?; + + let method = + execution_method_from_cli(self.wasm_method, self.wasmtime_instantiation_strategy); + + let executor = WasmExecutor::<( + sp_io::SubstrateHostFunctions, + frame_benchmarking::benchmarking::HostFunctions, + ExtraHostFunctions, + )>::builder() + .with_execution_method(method) + .with_max_runtime_instances(2) + .with_runtime_cache_size(2) + .build(); let extensions = || -> Extensions { let mut extensions = Extensions::default(); - extensions.register(KeystoreExt(Arc::new(KeyStore::new()) as SyncCryptoStorePtr)); let (offchain, _) = TestOffchainExt::new(); let (pool, _) = TestTransactionPoolExt::new(); + let keystore = MemoryKeystore::new(); + extensions.register(KeystoreExt::new(keystore)); extensions.register(OffchainWorkerExt::new(offchain.clone())); extensions.register(OffchainDbExt::new(offchain)); extensions.register(TransactionPoolExt::new(pool)); + extensions.register(ReadRuntimeVersionExt::new(executor.clone())); extensions }; @@ -196,11 +250,11 @@ impl PalletCmd { &executor, "Benchmark_benchmark_metadata", &(self.extra).encode(), - extensions(), + &mut extensions(), &sp_state_machine::backend::BackendRuntimeCode::new(state).runtime_code()?, - sp_core::testing::TaskExecutor::new(), + CallContext::Offchain, ) - .execute(strategy.into()) + .execute() .map_err(|e| format!("{}: {}", ERROR_METADATA_NOT_FOUND, e))?; let (list, storage_info) = @@ -222,10 +276,27 @@ impl PalletCmd { item.pallet.clone(), benchmark.name.clone(), benchmark.components.clone(), + benchmark.pov_modes.clone(), )) } } }); + // Convert `Vec` to `String` for better readability. + let benchmarks_to_run: Vec<_> = benchmarks_to_run + .into_iter() + .map(|b| { + ( + b.0, + b.1, + b.2, + b.3.into_iter() + .map(|(p, s)| { + (String::from_utf8(p).unwrap(), String::from_utf8(s).unwrap()) + }) + .collect(), + ) + }) + .collect(); if benchmarks_to_run.is_empty() { return Err("No benchmarks found which match your input.".into()) @@ -243,8 +314,9 @@ impl PalletCmd { let mut timer = time::SystemTime::now(); // Maps (pallet, extrinsic) to its component ranges. let mut component_ranges = HashMap::<(Vec, Vec), Vec>::new(); + let pov_modes = Self::parse_pov_modes(&benchmarks_to_run)?; - for (pallet, extrinsic, components) in benchmarks_to_run { + for (pallet, extrinsic, components, _) in benchmarks_to_run.clone() { log::info!( target: LOG_TARGET, "Starting benchmark: {}::{}", @@ -314,12 +386,12 @@ impl PalletCmd { 1, // no need to do internal repeats ) .encode(), - extensions(), + &mut extensions(), &sp_state_machine::backend::BackendRuntimeCode::new(state) .runtime_code()?, - sp_core::testing::TaskExecutor::new(), + CallContext::Offchain, ) - .execute(strategy.into()) + .execute() .map_err(|e| { format!("Error executing and verifying runtime benchmark: {}", e) })?; @@ -354,12 +426,12 @@ impl PalletCmd { self.repeat, ) .encode(), - extensions(), + &mut extensions(), &sp_state_machine::backend::BackendRuntimeCode::new(state) .runtime_code()?, - sp_core::testing::TaskExecutor::new(), + CallContext::Offchain, ) - .execute(strategy.into()) + .execute() .map_err(|e| format!("Error executing runtime benchmark: {}", e))?; let batch = @@ -386,12 +458,12 @@ impl PalletCmd { self.repeat, ) .encode(), - extensions(), + &mut extensions(), &sp_state_machine::backend::BackendRuntimeCode::new(state) .runtime_code()?, - sp_core::testing::TaskExecutor::new(), + CallContext::Offchain, ) - .execute(strategy.into()) + .execute() .map_err(|e| format!("Error executing runtime benchmark: {}", e))?; let batch = @@ -409,7 +481,7 @@ impl PalletCmd { log::info!( target: LOG_TARGET, - "Running Benchmark: {}.{}({} args) {}/{} {}/{}", + "Running benchmark: {}.{}({} args) {}/{} {}/{}", String::from_utf8(pallet.clone()) .expect("Encoded from String; qed"), String::from_utf8(extrinsic.clone()) @@ -429,7 +501,7 @@ impl PalletCmd { // Combine all of the benchmark results, so that benchmarks of the same pallet/function // are together. let batches = combine_batches(batches, batches_db); - self.output(&batches, &storage_info, &component_ranges) + self.output(&batches, &storage_info, &component_ranges, pov_modes) } fn output( @@ -437,21 +509,31 @@ impl PalletCmd { batches: &[BenchmarkBatchSplitResults], storage_info: &[StorageInfo], component_ranges: &HashMap<(Vec, Vec), Vec>, + pov_modes: PovModesMap, ) -> Result<()> { // Jsonify the result and write it to a file or stdout if desired. if !self.jsonify(&batches)? { // Print the summary only if `jsonify` did not write to stdout. - self.print_summary(&batches, &storage_info) + self.print_summary(&batches, &storage_info, pov_modes.clone()) } // Create the weights.rs file. if let Some(output_path) = &self.output { - writer::write_results(&batches, &storage_info, &component_ranges, output_path, self)?; + writer::write_results( + &batches, + &storage_info, + &component_ranges, + pov_modes, + self.default_pov_mode, + output_path, + self, + )?; } Ok(()) } + /// Re-analyze a batch historic benchmark timing data. Will not take the PoV into account. fn output_from_results(&self, batches: &[BenchmarkBatchSplitResults]) -> Result<()> { let mut component_ranges = HashMap::<(Vec, Vec), HashMap>::new(); @@ -484,7 +566,7 @@ impl PalletCmd { }) .collect(); - self.output(batches, &[], &component_ranges) + self.output(batches, &[], &component_ranges, Default::default()) } /// Jsonifies the passed batches and writes them to stdout or into a file. @@ -507,7 +589,12 @@ impl PalletCmd { } /// Prints the results as human-readable summary without raw timing data. - fn print_summary(&self, batches: &[BenchmarkBatchSplitResults], storage_info: &[StorageInfo]) { + fn print_summary( + &self, + batches: &[BenchmarkBatchSplitResults], + storage_info: &[StorageInfo], + pov_modes: PovModesMap, + ) { for batch in batches.iter() { // Print benchmark metadata println!( @@ -526,13 +613,32 @@ impl PalletCmd { } if !self.no_storage_info { - let mut comments: Vec = Default::default(); - writer::add_storage_comments(&mut comments, &batch.db_results, storage_info); + let mut storage_per_prefix = HashMap::, Vec>::new(); + let pov_mode = pov_modes + .get(&(batch.pallet.clone(), batch.benchmark.clone())) + .cloned() + .unwrap_or_default(); + + let comments = writer::process_storage_results( + &mut storage_per_prefix, + &batch.db_results, + storage_info, + &pov_mode, + self.default_pov_mode, + self.worst_case_map_values, + self.additional_trie_layers, + ); println!("Raw Storage Info\n========"); for comment in comments { println!("{}", comment); } println!(); + + println!("-- Proof Sizes --\n"); + for result in batch.db_results.iter() { + println!("{} bytes", result.proof_size); + } + println!(); } // Conduct analysis. @@ -553,6 +659,11 @@ impl PalletCmd { { println!("Writes = {:?}", analysis); } + if let Some(analysis) = + Analysis::median_slopes(&batch.db_results, BenchmarkSelector::ProofSize) + { + println!("Recorded proof Size = {:?}", analysis); + } println!(); } if !self.no_min_squares { @@ -572,10 +683,60 @@ impl PalletCmd { { println!("Writes = {:?}", analysis); } + if let Some(analysis) = + Analysis::min_squares_iqr(&batch.db_results, BenchmarkSelector::ProofSize) + { + println!("Recorded proof Size = {:?}", analysis); + } println!(); } } } + + /// Parses the PoV modes per benchmark that were specified by the `#[pov_mode]` attribute. + fn parse_pov_modes( + benchmarks: &Vec<( + Vec, + Vec, + Vec<(BenchmarkParameter, u32, u32)>, + Vec<(String, String)>, + )>, + ) -> Result { + use std::collections::hash_map::Entry; + let mut parsed = PovModesMap::new(); + + for (pallet, call, _components, pov_modes) in benchmarks { + for (pallet_storage, mode) in pov_modes { + let mode = PovEstimationMode::from_str(&mode)?; + let splits = pallet_storage.split("::").collect::>(); + if splits.is_empty() || splits.len() > 2 { + return Err(format!( + "Expected 'Pallet::Storage' as storage name but got: {}", + pallet_storage + ) + .into()) + } + let (pov_pallet, pov_storage) = (splits[0], splits.get(1).unwrap_or(&"ALL")); + + match parsed + .entry((pallet.clone(), call.clone())) + .or_default() + .entry((pov_pallet.to_string(), pov_storage.to_string())) + { + Entry::Occupied(_) => + return Err(format!( + "Cannot specify pov_mode tag twice for the same key: {}", + pallet_storage + ) + .into()), + Entry::Vacant(e) => { + e.insert(mode); + }, + } + } + } + Ok(parsed) + } } impl CliConfiguration for PalletCmd { @@ -592,9 +753,16 @@ impl CliConfiguration for PalletCmd { } /// List the benchmarks available in the runtime, in a CSV friendly format. -fn list_benchmark(benchmarks_to_run: Vec<(Vec, Vec, Vec<(BenchmarkParameter, u32, u32)>)>) { +fn list_benchmark( + benchmarks_to_run: Vec<( + Vec, + Vec, + Vec<(BenchmarkParameter, u32, u32)>, + Vec<(String, String)>, + )>, +) { println!("pallet, benchmark"); - for (pallet, extrinsic, _components) in benchmarks_to_run { + for (pallet, extrinsic, _, _) in benchmarks_to_run { println!("{}, {}", String::from_utf8_lossy(&pallet), String::from_utf8_lossy(&extrinsic)); } } diff --git a/utils/frame/benchmarking-cli/src/pallet/mod.rs b/utils/frame/benchmarking-cli/src/pallet/mod.rs index b10f531bc0ae..c69ce1765fc9 100644 --- a/utils/frame/benchmarking-cli/src/pallet/mod.rs +++ b/utils/frame/benchmarking-cli/src/pallet/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,8 +20,8 @@ mod writer; use crate::shared::HostInfoParams; use sc_cli::{ - ExecutionStrategy, WasmExecutionMethod, WasmtimeInstantiationStrategy, - DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, DEFAULT_WASM_EXECUTION_METHOD, + WasmExecutionMethod, WasmtimeInstantiationStrategy, DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, + DEFAULT_WASM_EXECUTION_METHOD, }; use std::{fmt::Debug, path::PathBuf}; @@ -43,7 +43,7 @@ pub struct PalletCmd { pub extrinsic: Option, /// Select how many samples we should take across the variable components. - #[arg(short, long, default_value_t = 2)] + #[arg(short, long, default_value_t = 50)] pub steps: u32, /// Indicates lowest values for each of the component ranges. @@ -55,7 +55,7 @@ pub struct PalletCmd { pub highest_range_values: Vec, /// Select how many repetitions of this benchmark should run from within the wasm. - #[arg(short, long, default_value_t = 1)] + #[arg(short, long, default_value_t = 20)] pub repeat: u32, /// Select how many repetitions of this benchmark should run from the client. @@ -103,6 +103,14 @@ pub struct PalletCmd { #[arg(long)] pub output_analysis: Option, + /// Which analysis function to use when analyzing measured proof sizes. + #[arg(long, default_value("median-slopes"))] + pub output_pov_analysis: Option, + + /// The PoV estimation mode of a benchmark if no `pov_mode` attribute is present. + #[arg(long, default_value("max-encoded-len"), value_enum)] + pub default_pov_mode: command::PovEstimationMode, + /// Set the heap pages while running benchmarks. If not set, the default value from the client /// is used. #[arg(long)] @@ -117,18 +125,10 @@ pub struct PalletCmd { #[arg(long)] pub extra: bool, - /// Estimate PoV size. - #[arg(long)] - pub record_proof: bool, - #[allow(missing_docs)] #[clap(flatten)] pub shared_params: sc_cli::SharedParams, - /// The execution strategy that should be used for benchmarks. - #[arg(long, value_name = "STRATEGY", value_enum, ignore_case = true)] - pub execution: Option, - /// Method for executing Wasm runtime code. #[arg( long = "wasm-execution", @@ -150,6 +150,10 @@ pub struct PalletCmd { )] pub wasmtime_instantiation_strategy: WasmtimeInstantiationStrategy, + /// DEPRECATED: This argument has no effect. + #[arg(long = "execution")] + pub execution: Option, + /// Limit the memory the database cache can use. #[arg(long = "db-cache", value_name = "MiB", default_value_t = 1024)] pub database_cache_size: u32, @@ -167,9 +171,34 @@ pub struct PalletCmd { #[arg(long)] pub no_storage_info: bool, + /// The assumed default maximum size of any `StorageMap`. + /// + /// When the maximum size of a map is not defined by the runtime developer, + /// this value is used as a worst case scenario. It will affect the calculated worst case + /// PoV size for accessing a value in a map, since the PoV will need to include the trie + /// nodes down to the underlying value. + #[clap(long = "map-size", default_value = "1000000")] + pub worst_case_map_values: u32, + + /// Adjust the PoV estimation by adding additional trie layers to it. + /// + /// This should be set to `log16(n)` where `n` is the number of top-level storage items in the + /// runtime, eg. `StorageMap`s and `StorageValue`s. A value of 2 to 3 is usually sufficient. + /// Each layer will result in an additional 495 bytes PoV per distinct top-level access. + /// Therefore multiple `StorageMap` accesses only suffer from this increase once. The exact + /// number of storage items depends on the runtime and the deployed pallets. + #[clap(long, default_value = "2")] + pub additional_trie_layers: u8, + /// A path to a `.json` file with existing benchmark results generated with `--json` or /// `--json-file`. When specified the benchmarks are not actually executed, and the data for /// the analysis is read from this file. #[arg(long)] pub json_input: Option, + + /// Allow overwriting a single file with multiple results. + /// + /// This exists only to restore legacy behaviour. It should never actually be needed. + #[arg(long)] + pub unsafe_overwrite_results: bool, } diff --git a/utils/frame/benchmarking-cli/src/pallet/template.hbs b/utils/frame/benchmarking-cli/src/pallet/template.hbs index 1a69a3ae20c5..e524fbd5018e 100644 --- a/utils/frame/benchmarking-cli/src/pallet/template.hbs +++ b/utils/frame/benchmarking-cli/src/pallet/template.hbs @@ -2,9 +2,10 @@ //! 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}}` +//! DATE: {{date}}, STEPS: `{{cmd.steps}}`, REPEAT: `{{cmd.repeat}}`, LOW RANGE: `{{cmd.lowest_range_values}}`, HIGH RANGE: `{{cmd.highest_range_values}}` +//! WORST CASE MAP SIZE: `{{cmd.worst_case_map_values}}` //! HOSTNAME: `{{hostname}}`, CPU: `{{cpuname}}` -//! EXECUTION: {{cmd.execution}}, WASM-EXECUTION: {{cmd.wasm_execution}}, CHAIN: {{cmd.chain}}, DB CACHE: {{cmd.db_cache}} +//! EXECUTION: `{{cmd.execution}}`, WASM-EXECUTION: `{{cmd.wasm_execution}}`, CHAIN: `{{cmd.chain}}`, DB CACHE: {{cmd.db_cache}} // Executed Command: {{#each args as |arg|}} @@ -14,16 +15,17 @@ #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] +#![allow(missing_docs)] use frame_support::{traits::Get, weights::Weight}; -use sp_std::marker::PhantomData; +use core::marker::PhantomData; /// Weight functions for `{{pallet}}`. pub struct WeightInfo(PhantomData); impl {{pallet}}::WeightInfo for WeightInfo { {{#each benchmarks as |benchmark|}} {{#each benchmark.comments as |comment|}} - // {{comment}} + /// {{comment}} {{/each}} {{#each benchmark.component_ranges as |range|}} /// The range of component `{{range.name}}` is `[{{range.min}}, {{range.max}}]`. @@ -33,11 +35,15 @@ impl {{pallet}}::WeightInfo for WeightInfo { {{~#each benchmark.components as |c| ~}} {{~#if (not c.is_used)}}_{{/if}}{{c.name}}: u32, {{/each~}} ) -> Weight { - // Minimum execution time: {{underscore benchmark.min_execution_time}} nanoseconds. - Weight::from_ref_time({{underscore benchmark.base_weight}}) + // Proof Size summary in bytes: + // Measured: `{{benchmark.base_recorded_proof_size}}{{#each benchmark.component_recorded_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` + // Estimated: `{{benchmark.base_calculated_proof_size}}{{#each benchmark.component_calculated_proof_size as |cp|}} + {{cp.name}} * ({{cp.slope}} ±{{underscore cp.error}}){{/each}}` + // Minimum execution time: {{underscore benchmark.min_execution_time}}_000 picoseconds. + Weight::from_parts({{underscore benchmark.base_weight}}, 0) + .saturating_add(Weight::from_parts(0, {{benchmark.base_calculated_proof_size}})) {{#each benchmark.component_weight as |cw|}} // Standard Error: {{underscore cw.error}} - .saturating_add(Weight::from_ref_time({{underscore cw.slope}}).saturating_mul({{cw.name}}.into())) + .saturating_add(Weight::from_parts({{underscore cw.slope}}, 0).saturating_mul({{cw.name}}.into())) {{/each}} {{#if (ne benchmark.base_reads "0")}} .saturating_add(T::DbWeight::get().reads({{benchmark.base_reads}})) @@ -51,6 +57,9 @@ impl {{pallet}}::WeightInfo for WeightInfo { {{#each benchmark.component_writes as |cw|}} .saturating_add(T::DbWeight::get().writes(({{cw.slope}}_u64).saturating_mul({{cw.name}}.into()))) {{/each}} + {{#each benchmark.component_calculated_proof_size as |cp|}} + .saturating_add(Weight::from_parts(0, {{cp.slope}}).saturating_mul({{cp.name}}.into())) + {{/each}} } {{/each}} } diff --git a/utils/frame/benchmarking-cli/src/pallet/writer.rs b/utils/frame/benchmarking-cli/src/pallet/writer.rs index a52bbcd229cb..69c95d13c098 100644 --- a/utils/frame/benchmarking-cli/src/pallet/writer.rs +++ b/utils/frame/benchmarking-cli/src/pallet/writer.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,7 +27,11 @@ use inflector::Inflector; use itertools::Itertools; use serde::Serialize; -use crate::{pallet::command::ComponentRange, shared::UnderscoreHelper, PalletCmd}; +use crate::{ + pallet::command::{ComponentRange, PovEstimationMode, PovModesMap}, + shared::UnderscoreHelper, + PalletCmd, +}; use frame_benchmarking::{ Analysis, AnalysisChoice, BenchmarkBatchSplitResults, BenchmarkResult, BenchmarkSelector, }; @@ -54,7 +58,7 @@ struct TemplateData { } // This was the final data we have about each benchmark. -#[derive(Serialize, Default, Debug, Clone)] +#[derive(Serialize, Default, Debug, Clone, PartialEq)] struct BenchmarkData { name: String, components: Vec, @@ -64,9 +68,15 @@ struct BenchmarkData { base_reads: u128, #[serde(serialize_with = "string_serialize")] base_writes: u128, + #[serde(serialize_with = "string_serialize")] + base_calculated_proof_size: u128, + #[serde(serialize_with = "string_serialize")] + base_recorded_proof_size: u128, component_weight: Vec, component_reads: Vec, component_writes: Vec, + component_calculated_proof_size: Vec, + component_recorded_proof_size: Vec, component_ranges: Vec, comments: Vec, #[serde(serialize_with = "string_serialize")] @@ -80,11 +90,12 @@ struct CmdData { repeat: u32, lowest_range_values: Vec, highest_range_values: Vec, - execution: String, wasm_execution: String, chain: String, db_cache: u32, analysis_choice: String, + worst_case_map_values: u32, + additional_trie_layers: u8, } // This encodes the component name and whether that component is used. @@ -122,7 +133,12 @@ fn map_results( batches: &[BenchmarkBatchSplitResults], storage_info: &[StorageInfo], component_ranges: &HashMap<(Vec, Vec), Vec>, + pov_modes: PovModesMap, + default_pov_mode: PovEstimationMode, analysis_choice: &AnalysisChoice, + pov_analysis_choice: &AnalysisChoice, + worst_case_map_values: u32, + additional_trie_layers: u8, ) -> Result>, std::io::Error> { // Skip if batches is empty. if batches.is_empty() { @@ -139,8 +155,17 @@ fn map_results( let pallet_string = String::from_utf8(batch.pallet.clone()).unwrap(); let instance_string = String::from_utf8(batch.instance.clone()).unwrap(); - let benchmark_data = - get_benchmark_data(batch, storage_info, &component_ranges, analysis_choice); + let benchmark_data = get_benchmark_data( + batch, + storage_info, + &component_ranges, + pov_modes.clone(), + default_pov_mode, + analysis_choice, + pov_analysis_choice, + worst_case_map_values, + additional_trie_layers, + ); let pallet_benchmarks = all_benchmarks.entry((pallet_string, instance_string)).or_default(); pallet_benchmarks.push(benchmark_data); } @@ -164,17 +189,24 @@ fn get_benchmark_data( storage_info: &[StorageInfo], // Per extrinsic component ranges. component_ranges: &HashMap<(Vec, Vec), Vec>, + pov_modes: PovModesMap, + default_pov_mode: PovEstimationMode, analysis_choice: &AnalysisChoice, + pov_analysis_choice: &AnalysisChoice, + worst_case_map_values: u32, + additional_trie_layers: u8, ) -> BenchmarkData { - // You can use this to put any additional comments with the benchmarking output. - let mut comments = Vec::::new(); - // Analyze benchmarks to get the linear regression. let analysis_function = match analysis_choice { AnalysisChoice::MinSquares => Analysis::min_squares_iqr, AnalysisChoice::MedianSlopes => Analysis::median_slopes, AnalysisChoice::Max => Analysis::max, }; + let pov_analysis_function = match pov_analysis_choice { + AnalysisChoice::MinSquares => Analysis::min_squares_iqr, + AnalysisChoice::MedianSlopes => Analysis::median_slopes, + AnalysisChoice::Max => Analysis::max, + }; let extrinsic_time = analysis_function(&batch.time_results, BenchmarkSelector::ExtrinsicTime) .expect("analysis function should return an extrinsic time for valid inputs"); @@ -182,6 +214,9 @@ fn get_benchmark_data( .expect("analysis function should return the number of reads for valid inputs"); let writes = analysis_function(&batch.db_results, BenchmarkSelector::Writes) .expect("analysis function should return the number of writes for valid inputs"); + let recorded_proof_size = + pov_analysis_function(&batch.db_results, BenchmarkSelector::ProofSize) + .expect("analysis function should return proof sizes for valid inputs"); // Analysis data may include components that are not used, this filters out anything whose value // is zero. @@ -189,6 +224,8 @@ fn get_benchmark_data( let mut used_extrinsic_time = Vec::new(); let mut used_reads = Vec::new(); let mut used_writes = Vec::new(); + let mut used_calculated_proof_size = Vec::::new(); + let mut used_recorded_proof_size = Vec::::new(); extrinsic_time .slopes @@ -229,6 +266,78 @@ fn get_benchmark_data( used_writes.push(ComponentSlope { name: name.clone(), slope, error }); } }); + recorded_proof_size + .slopes + .into_iter() + .zip(recorded_proof_size.names.iter()) + .zip(extract_errors(&recorded_proof_size.errors)) + .for_each(|((slope, name), error)| { + if !slope.is_zero() { + // These are only for comments, so don't touch the `used_components`. + used_recorded_proof_size.push(ComponentSlope { name: name.clone(), slope, error }); + } + }); + used_recorded_proof_size.sort_by(|a, b| a.name.cmp(&b.name)); + + // We add additional comments showing which storage items were touched. + // We find the worst case proof size, and use that as the final proof size result. + let mut storage_per_prefix = HashMap::, Vec>::new(); + let pov_mode = pov_modes + .get(&(batch.pallet.clone(), batch.benchmark.clone())) + .cloned() + .unwrap_or_default(); + let comments = process_storage_results( + &mut storage_per_prefix, + &batch.db_results, + storage_info, + &pov_mode, + default_pov_mode, + worst_case_map_values, + additional_trie_layers, + ); + + let proof_size_per_components = storage_per_prefix + .iter() + .map(|(prefix, results)| { + let proof_size = analysis_function(results, BenchmarkSelector::ProofSize) + .expect("analysis function should return proof sizes for valid inputs"); + let slope = proof_size + .slopes + .into_iter() + .zip(proof_size.names.iter()) + .zip(extract_errors(&proof_size.errors)) + .map(|((slope, name), error)| ComponentSlope { name: name.clone(), slope, error }) + .collect::>(); + (prefix.clone(), slope, proof_size.base) + }) + .collect::>(); + + let mut base_calculated_proof_size = 0; + // Sum up the proof sizes per component + for (_, slope, base) in proof_size_per_components.iter() { + base_calculated_proof_size = base_calculated_proof_size.max(*base); + for component in slope.iter() { + let mut found = false; + for used_component in used_calculated_proof_size.iter_mut() { + if used_component.name == component.name { + used_component.slope = used_component.slope.max(component.slope); + found = true; + break + } + } + if !found && !component.slope.is_zero() { + if !used_components.contains(&&component.name) { + used_components.push(&component.name); + } + used_calculated_proof_size.push(ComponentSlope { + name: component.name.clone(), + slope: component.slope, + error: component.error, + }); + } + } + } + used_calculated_proof_size.sort_by(|a, b| a.name.cmp(&b.name)); // This puts a marker on any component which is entirely unused in the weight formula. let components = batch.time_results[0] @@ -241,8 +350,6 @@ fn get_benchmark_data( }) .collect::>(); - // We add additional comments showing which storage items were touched. - add_storage_comments(&mut comments, &batch.db_results, storage_info); let component_ranges = component_ranges .get(&(batch.pallet.clone(), batch.benchmark.clone())) .map(|c| c.clone()) @@ -254,23 +361,29 @@ fn get_benchmark_data( base_weight: extrinsic_time.base, base_reads: reads.base, base_writes: writes.base, + base_calculated_proof_size, + base_recorded_proof_size: recorded_proof_size.base, component_weight: used_extrinsic_time, component_reads: used_reads, component_writes: used_writes, + component_calculated_proof_size: used_calculated_proof_size, + component_recorded_proof_size: used_recorded_proof_size, component_ranges, comments, min_execution_time: extrinsic_time.minimum, } } -// Create weight file from benchmark data and Handlebars template. +/// Create weight file from benchmark data and Handlebars template. pub(crate) fn write_results( batches: &[BenchmarkBatchSplitResults], storage_info: &[StorageInfo], component_ranges: &HashMap<(Vec, Vec), Vec>, + pov_modes: PovModesMap, + default_pov_mode: PovEstimationMode, path: &PathBuf, cmd: &PalletCmd, -) -> Result<(), std::io::Error> { +) -> Result<(), sc_cli::Error> { // Use custom template if provided. let template: String = match &cmd.template { Some(template_file) => fs::read_to_string(template_file)?, @@ -295,6 +408,15 @@ pub(crate) fn write_results( // Which analysis function should be used when outputting benchmarks let analysis_choice: AnalysisChoice = cmd.output_analysis.clone().try_into().map_err(io_error)?; + let pov_analysis_choice: AnalysisChoice = + cmd.output_pov_analysis.clone().try_into().map_err(io_error)?; + + if cmd.additional_trie_layers > 4 { + println!( + "WARNING: `additional_trie_layers` is unexpectedly large. It assumes {} storage items.", + 16f64.powi(cmd.additional_trie_layers as i32) + ) + } // Capture individual args let cmd_data = CmdData { @@ -302,11 +424,12 @@ pub(crate) fn write_results( repeat: cmd.repeat, lowest_range_values: cmd.lowest_range_values.clone(), highest_range_values: cmd.highest_range_values.clone(), - execution: format!("{:?}", cmd.execution), wasm_execution: cmd.wasm_method.to_string(), chain: format!("{:?}", cmd.shared_params.chain), db_cache: cmd.database_cache_size, analysis_choice: format!("{:?}", analysis_choice), + worst_case_map_values: cmd.worst_case_map_values, + additional_trie_layers: cmd.additional_trie_layers, }; // New Handlebars instance with helpers. @@ -317,7 +440,17 @@ pub(crate) fn write_results( handlebars.register_escape_fn(|s| -> String { s.to_string() }); // Organize results by pallet into a JSON map - let all_results = map_results(batches, storage_info, component_ranges, &analysis_choice)?; + let all_results = map_results( + batches, + storage_info, + component_ranges, + pov_modes, + default_pov_mode, + &analysis_choice, + &pov_analysis_choice, + cmd.worst_case_map_values, + cmd.additional_trie_layers, + )?; let mut created_files = Vec::new(); for ((pallet, instance), results) in all_results.iter() { @@ -357,22 +490,40 @@ pub(crate) fn write_results( created_files.push(file_path); } - for file in created_files.iter().duplicates() { - // This can happen when there are multiple instances of a pallet deployed - // and `--output` forces the output of all instances into the same file. - println!("Multiple benchmarks were written to the same file: {:?}.", file); + let overwritten_files = created_files.iter().duplicates().collect::>(); + if !overwritten_files.is_empty() { + let msg = format!( + "Multiple results were written to the same file. This can happen when \ + there are multiple instances of a pallet deployed and `--output` forces the output of all \ + instances into the same file. Use `--unsafe-overwrite-results` to ignore this error. The \ + affected files are: {:?}", + overwritten_files + ); + + if cmd.unsafe_overwrite_results { + println!("{msg}"); + } else { + return Err(msg.into()) + } } Ok(()) } -// This function looks at the keys touched during the benchmark, and the storage info we collected -// from the pallets, and creates comments with information about the storage keys touched during -// each benchmark. -pub(crate) fn add_storage_comments( - comments: &mut Vec, +/// This function looks at the keys touched during the benchmark, and the storage info we collected +/// from the pallets, and creates comments with information about the storage keys touched during +/// each benchmark. +/// +/// It returns informational comments for human consumption. +pub(crate) fn process_storage_results( + storage_per_prefix: &mut HashMap, Vec>, results: &[BenchmarkResult], storage_info: &[StorageInfo], -) { + pov_modes: &HashMap<(String, String), PovEstimationMode>, + default_pov_mode: PovEstimationMode, + worst_case_map_values: u32, + additional_trie_layers: u8, +) -> Vec { + let mut comments = Vec::new(); let mut storage_info_map = storage_info .iter() .map(|info| (info.prefix.clone(), info)) @@ -399,48 +550,246 @@ pub(crate) fn add_storage_comments( storage_info_map.insert(benchmark_override.prefix.clone(), &benchmark_override); // This tracks the keys we already identified, so we only generate a single comment. - let mut identified = HashSet::>::new(); + let mut identified_prefix = HashSet::>::new(); + let mut identified_key = HashSet::>::new(); + + // TODO Emit a warning for unused `pov_mode` attributes. - for result in results { + // We have to iterate in reverse order to catch the largest values for read/write since the + // components start low and then increase and only the first value is used. + for result in results.iter().rev() { for (key, reads, writes, whitelisted) in &result.keys { // skip keys which are whitelisted if *whitelisted { continue } + let prefix_length = key.len().min(32); let prefix = key[0..prefix_length].to_vec(); - if identified.contains(&prefix) { - // skip adding comments for keys we already identified - continue - } else { - // track newly identified keys - identified.insert(prefix.clone()); + let is_key_identified = identified_key.contains(key); + let is_prefix_identified = identified_prefix.contains(&prefix); + + let mut prefix_result = result.clone(); + let key_info = storage_info_map.get(&prefix); + let max_size = key_info.and_then(|k| k.max_size); + + let override_pov_mode = match key_info { + Some(StorageInfo { pallet_name, storage_name, .. }) => { + let pallet_name = + String::from_utf8(pallet_name.clone()).expect("encoded from string"); + let storage_name = + String::from_utf8(storage_name.clone()).expect("encoded from string"); + + // Is there an override for the storage key? + pov_modes.get(&(pallet_name.clone(), storage_name)).or( + // .. or for the storage prefix? + pov_modes.get(&(pallet_name, "ALL".to_string())).or( + // .. or for the benchmark? + pov_modes.get(&("ALL".to_string(), "ALL".to_string())), + ), + ) + }, + None => None, + }; + let is_all_ignored = pov_modes.get(&("ALL".to_string(), "ALL".to_string())) == + Some(&PovEstimationMode::Ignored); + if is_all_ignored && override_pov_mode != Some(&PovEstimationMode::Ignored) { + panic!("The syntax currently does not allow to exclude single keys from a top-level `Ignored` pov-mode."); + } + + let pov_overhead = single_read_pov_overhead( + key_info.and_then(|i| i.max_values), + worst_case_map_values, + ); + + let used_pov_mode = match (override_pov_mode, max_size, default_pov_mode) { + // All is ignored by default and no override: + (None, _, PovEstimationMode::Ignored) => { + prefix_result.proof_size = 0; + PovEstimationMode::Ignored + }, + // Some is ignored by override, maybe all: + (Some(PovEstimationMode::Ignored), _, _) => { + // If this is applied to All keys, then we also remove the base weight and just set all to zero. + if is_all_ignored { + prefix_result.proof_size = 0; + } else { + // Otherwise we just don't *increase* `proof_size` for this key. + } + PovEstimationMode::Ignored + }, + (Some(PovEstimationMode::Measured), _, _)| + (None, _, PovEstimationMode::Measured) | + // Use best effort in this case since failing would be really annoying. + (None, None, PovEstimationMode::MaxEncodedLen) => { + // We add the overhead for a single read each time. In a more advanced version + // we could take node re-using into account and over-estimate a bit less. + prefix_result.proof_size += pov_overhead * *reads; + PovEstimationMode::Measured + }, + (Some(PovEstimationMode::MaxEncodedLen), Some(max_size), _) | + (None, Some(max_size), PovEstimationMode::MaxEncodedLen) => { + prefix_result.proof_size = (pov_overhead + max_size) * *reads; + PovEstimationMode::MaxEncodedLen + }, + (Some(PovEstimationMode::MaxEncodedLen), None, _) => { + panic!("Key does not have MEL bound but MEL PoV estimation mode was specified {:?}", &key); + }, + }; + // Add the additional trie layer overhead for every new prefix. + if *reads > 0 && !is_all_ignored { + prefix_result.proof_size += 15 * 33 * additional_trie_layers as u32; } - match storage_info_map.get(&prefix) { - Some(key_info) => { - let comment = format!( - "Storage: {} {} (r:{} w:{})", - String::from_utf8(key_info.pallet_name.clone()) - .expect("encoded from string"), - String::from_utf8(key_info.storage_name.clone()) - .expect("encoded from string"), - reads, - writes, - ); - comments.push(comment) + storage_per_prefix.entry(prefix.clone()).or_default().push(prefix_result); + + match (is_key_identified, is_prefix_identified) { + // We already did everything, move on... + (true, true) => continue, + (false, true) => { + // track newly identified key + identified_key.insert(key.clone()); }, - None => { - let comment = format!( - "Storage: unknown [0x{}] (r:{} w:{})", - HexDisplay::from(key), - reads, - writes, - ); - comments.push(comment) + (false, false) => { + // track newly identified key and prefix + identified_key.insert(key.clone()); + identified_prefix.insert(prefix.clone()); }, + // Not possible. If the key is known, the prefix is too. + (true, false) => unreachable!(), + } + + // For any new prefix, we should write some comment about the number of reads and + // writes. + if !is_prefix_identified { + match key_info { + Some(key_info) => { + let comment = format!( + "Storage: `{}::{}` (r:{} w:{})", + String::from_utf8(key_info.pallet_name.clone()) + .expect("encoded from string"), + String::from_utf8(key_info.storage_name.clone()) + .expect("encoded from string"), + reads, + writes, + ); + comments.push(comment) + }, + None => { + let comment = format!( + "Storage: UNKNOWN KEY `0x{}` (r:{} w:{})", + HexDisplay::from(key), + reads, + writes, + ); + comments.push(comment) + }, + } + } + + // For any new key, we should add the PoV impact. + if !is_key_identified { + match key_info { + Some(key_info) => { + match worst_case_pov( + key_info.max_values, + key_info.max_size, + !is_prefix_identified, + worst_case_map_values, + ) { + Some(new_pov) => { + let comment = format!( + "Proof: `{}::{}` (`max_values`: {:?}, `max_size`: {:?}, added: {}, mode: `{:?}`)", + String::from_utf8(key_info.pallet_name.clone()) + .expect("encoded from string"), + String::from_utf8(key_info.storage_name.clone()) + .expect("encoded from string"), + key_info.max_values, + key_info.max_size, + new_pov, + used_pov_mode, + ); + comments.push(comment) + }, + None => { + let pallet = String::from_utf8(key_info.pallet_name.clone()) + .expect("encoded from string"); + let item = String::from_utf8(key_info.storage_name.clone()) + .expect("encoded from string"); + let comment = format!( + "Proof: `{}::{}` (`max_values`: {:?}, `max_size`: {:?}, mode: `{:?}`)", + pallet, item, key_info.max_values, key_info.max_size, + used_pov_mode, + ); + comments.push(comment); + }, + } + }, + None => { + let comment = format!( + "Proof: UNKNOWN KEY `0x{}` (r:{} w:{})", + HexDisplay::from(key), + reads, + writes, + ); + comments.push(comment) + }, + } } } } + + comments +} + +/// The PoV overhead when reading a key the first time out of a map with `max_values` entries. +fn single_read_pov_overhead(max_values: Option, worst_case_map_values: u32) -> u32 { + let max_values = max_values.unwrap_or(worst_case_map_values); + let depth: u32 = easy_log_16(max_values); + // Normally we have 16 entries of 32 byte hashes per tree layer. In the new trie + // layout the hashes are prefixed by their compact length, hence 33 instead. The proof + // compaction can compress one node per layer since we send the value itself, + // therefore we end up with a size of `15 * 33` per layer. + depth * 15 * 33 +} + +/// Given the max values and max size of some storage item, calculate the worst +/// case PoV. +/// +/// # Arguments +/// * `max_values`: The maximum number of values in the storage item. `None` for unbounded items. +/// * `max_size`: The maximum size of the value in the storage. `None` for unbounded items. +fn worst_case_pov( + max_values: Option, + max_size: Option, + is_new_prefix: bool, + worst_case_map_values: u32, +) -> Option { + if let Some(max_size) = max_size { + let trie_size: u32 = if is_new_prefix { + single_read_pov_overhead(max_values, worst_case_map_values) + } else { + 0 + }; + + Some(trie_size + max_size) + } else { + None + } +} + +/// A simple match statement which outputs the log 16 of some value. +fn easy_log_16(i: u32) -> u32 { + match i { + i if i == 0 => 0, + i if i <= 16 => 1, + i if i <= 256 => 2, + i if i <= 4_096 => 3, + i if i <= 65_536 => 4, + i if i <= 1_048_576 => 5, + i if i <= 16_777_216 => 6, + i if i <= 268_435_456 => 7, + _ => 8, + } } // A helper to join a string of vectors. @@ -497,15 +846,16 @@ mod test { let mut results = Vec::new(); for i in 0..5 { results.push(BenchmarkResult { - components: vec![(param, i), (BenchmarkParameter::z, 0)], + components: vec![(param, i)], extrinsic_time: (base + slope * i).into(), storage_root_time: (base + slope * i).into(), reads: (base + slope * i).into(), repeat_reads: 0, writes: (base + slope * i).into(), repeat_writes: 0, - proof_size: 0, - keys: vec![], + proof_size: (i + 1) * 1024, + // All R/W come from this key: + keys: vec![(b"bounded".to_vec(), (base + slope * i), (base + slope * i), false)], }) } @@ -518,13 +868,31 @@ mod test { } } + fn test_storage_info() -> Vec { + vec![StorageInfo { + pallet_name: b"bounded".to_vec(), + storage_name: b"bounded".to_vec(), + prefix: b"bounded".to_vec(), + max_values: Some(1 << 20), + max_size: Some(32), + }] + } + + fn test_pov_mode() -> PovModesMap { + let mut map = PovModesMap::new(); + map.entry((b"scheduler".to_vec(), b"first_benchmark".to_vec())) + .or_default() + .insert(("scheduler".into(), "mel".into()), PovEstimationMode::MaxEncodedLen); + map.entry((b"scheduler".to_vec(), b"first_benchmark".to_vec())) + .or_default() + .insert(("scheduler".into(), "measured".into()), PovEstimationMode::Measured); + map + } + fn check_data(benchmark: &BenchmarkData, component: &str, base: u128, slope: u128) { assert_eq!( benchmark.components, - vec![ - Component { name: component.to_string(), is_used: true }, - Component { name: "z".to_string(), is_used: false }, - ], + vec![Component { name: component.to_string(), is_used: true },], ); // Weights multiplied by 1,000 assert_eq!(benchmark.base_weight, base * 1_000); @@ -543,6 +911,294 @@ mod test { benchmark.component_writes, vec![ComponentSlope { name: component.to_string(), slope, error: 0 }] ); + // Measure PoV is correct + assert_eq!(benchmark.base_recorded_proof_size, 1024); + assert_eq!( + benchmark.component_recorded_proof_size, + vec![ComponentSlope { name: component.to_string(), slope: 1024, error: 0 }] + ); + } + + /// We measure a linear proof size but select `pov_mode = MEL` with a present MEL bound for the + /// type. This should result in the measured PoV being ignored and the MEL used instead. + #[test] + fn pov_mode_mel_constant_works() { + let mut results = Vec::new(); + for i in 0..5 { + results.push(BenchmarkResult { + components: vec![(BenchmarkParameter::s, i)], + extrinsic_time: 0, + storage_root_time: 0, + reads: 1, + repeat_reads: 777, + writes: 888, + repeat_writes: 999, + proof_size: i * 1024, + keys: vec![(b"mel".to_vec(), 1, 1, false)], + }) + } + + let data = BenchmarkBatchSplitResults { + pallet: b"scheduler".to_vec(), + instance: b"instance".to_vec(), + benchmark: b"first_benchmark".to_vec(), + time_results: results.clone(), + db_results: results, + }; + + let storage_info = vec![StorageInfo { + pallet_name: b"scheduler".to_vec(), + storage_name: b"mel".to_vec(), + prefix: b"mel".to_vec(), + max_values: None, + max_size: Some(1 << 22), // MEL of 4 MiB + }]; + + let mapped_results = map_results( + &[data], + &storage_info, + &Default::default(), + test_pov_mode(), + PovEstimationMode::MaxEncodedLen, + &AnalysisChoice::default(), + &AnalysisChoice::MedianSlopes, + 1_000_000, + 0, + ) + .unwrap(); + let result = + mapped_results.get(&("scheduler".to_string(), "instance".to_string())).unwrap()[0] + .clone(); + + let base = result.base_calculated_proof_size; + assert!(result.component_calculated_proof_size.is_empty(), "There is no slope"); + // It's a map with 5 layers overhead: + assert_eq!(base, (1 << 22) + 15 * 33 * 5); + } + + /// Record a small linear proof size but since MEL is selected and available it should be used + /// instead. + #[test] + fn pov_mode_mel_linear_works() { + let mut results = Vec::new(); + for i in 0..5 { + results.push(BenchmarkResult { + components: vec![(BenchmarkParameter::s, i)], + extrinsic_time: 0, + storage_root_time: 0, + reads: 123, + repeat_reads: 777, + writes: 888, + repeat_writes: 999, + proof_size: i * 1024, + keys: vec![("mel".as_bytes().to_vec(), i, 1, false)], + }) + } + + let data = BenchmarkBatchSplitResults { + pallet: b"scheduler".to_vec(), + instance: b"instance".to_vec(), + benchmark: b"first_benchmark".to_vec(), + time_results: results.clone(), + db_results: results, + }; + + let storage_info = vec![StorageInfo { + pallet_name: b"scheduler".to_vec(), + storage_name: b"mel".to_vec(), + prefix: b"mel".to_vec(), + max_values: None, + max_size: Some(1 << 22), // MEL of 4 MiB + }]; + + let mapped_results = map_results( + &[data], + &storage_info, + &Default::default(), + test_pov_mode(), + PovEstimationMode::MaxEncodedLen, + &AnalysisChoice::default(), + &AnalysisChoice::MedianSlopes, + 1_000_000, + 0, + ) + .unwrap(); + let result = + mapped_results.get(&("scheduler".to_string(), "instance".to_string())).unwrap()[0] + .clone(); + + let base = result.base_calculated_proof_size; + assert_eq!(result.component_calculated_proof_size.len(), 1, "There is a slope"); + let slope = result.component_calculated_proof_size[0].clone().slope; + assert_eq!(base, 0); + // It's a map with 5 layers overhead: + assert_eq!(slope, (1 << 22) + 15 * 33 * 5); + } + + #[test] + fn pov_mode_measured_const_works() { + let mut results = Vec::new(); + for i in 0..5 { + results.push(BenchmarkResult { + components: vec![(BenchmarkParameter::s, i)], + extrinsic_time: 0, + storage_root_time: 0, + reads: 123, + repeat_reads: 777, + writes: 888, + repeat_writes: 999, + proof_size: 1024, + keys: vec![("measured".as_bytes().to_vec(), 1, 1, false)], + }) + } + + let data = BenchmarkBatchSplitResults { + pallet: b"scheduler".to_vec(), + instance: b"instance".to_vec(), + benchmark: b"first_benchmark".to_vec(), + time_results: results.clone(), + db_results: results, + }; + + let storage_info = vec![StorageInfo { + pallet_name: b"scheduler".to_vec(), + storage_name: b"measured".to_vec(), + prefix: b"measured".to_vec(), + max_values: None, + max_size: Some(1 << 22), // MEL of 4 MiB + }]; + + let mapped_results = map_results( + &[data], + &storage_info, + &Default::default(), + test_pov_mode(), + PovEstimationMode::MaxEncodedLen, + &AnalysisChoice::default(), + &AnalysisChoice::MedianSlopes, + 1_000_000, + 0, + ) + .unwrap(); + let result = + mapped_results.get(&("scheduler".to_string(), "instance".to_string())).unwrap()[0] + .clone(); + + let base = result.base_calculated_proof_size; + assert!(result.component_calculated_proof_size.is_empty(), "There is no slope"); + // 5 Trie layers overhead because of the 1M max elements in that map: + assert_eq!(base, 1024 + 15 * 33 * 5); + } + + #[test] + fn pov_mode_measured_linear_works() { + let mut results = Vec::new(); + for i in 0..5 { + results.push(BenchmarkResult { + components: vec![(BenchmarkParameter::s, i)], + extrinsic_time: 0, + storage_root_time: 0, + reads: 123, + repeat_reads: 777, + writes: 888, + repeat_writes: 999, + proof_size: i * 1024, + keys: vec![("measured".as_bytes().to_vec(), i, 1, false)], + }) + } + + let data = BenchmarkBatchSplitResults { + pallet: b"scheduler".to_vec(), + instance: b"instance".to_vec(), + benchmark: b"first_benchmark".to_vec(), + time_results: results.clone(), + db_results: results, + }; + + let storage_info = vec![StorageInfo { + pallet_name: b"scheduler".to_vec(), + storage_name: b"measured".to_vec(), + prefix: b"measured".to_vec(), + max_values: None, + max_size: Some(1 << 22), // MEL of 4 MiB + }]; + + let mapped_results = map_results( + &[data], + &storage_info, + &Default::default(), + test_pov_mode(), + PovEstimationMode::MaxEncodedLen, + &AnalysisChoice::default(), + &AnalysisChoice::MedianSlopes, + 1_000_000, + 0, + ) + .unwrap(); + let result = + mapped_results.get(&("scheduler".to_string(), "instance".to_string())).unwrap()[0] + .clone(); + + let base = result.base_calculated_proof_size; + assert_eq!(result.component_calculated_proof_size.len(), 1, "There is a slope"); + let slope = result.component_calculated_proof_size[0].clone().slope; + assert_eq!(base, 0); + // It's a map with 5 layers overhead: + assert_eq!(slope, 1024 + 15 * 33 * 5); + } + + #[test] + fn pov_mode_ignored_linear_works() { + let mut results = Vec::new(); + for i in 0..5 { + results.push(BenchmarkResult { + components: vec![(BenchmarkParameter::s, i)], + extrinsic_time: 0, + storage_root_time: 0, + reads: 123, + repeat_reads: 777, + writes: 888, + repeat_writes: 999, + proof_size: i * 1024, + keys: vec![("ignored".as_bytes().to_vec(), i, 1, false)], + }) + } + + let data = BenchmarkBatchSplitResults { + pallet: b"scheduler".to_vec(), + instance: b"instance".to_vec(), + benchmark: b"first_benchmark".to_vec(), + time_results: results.clone(), + db_results: results, + }; + + let storage_info = vec![StorageInfo { + pallet_name: b"scheduler".to_vec(), + storage_name: b"ignored".to_vec(), + prefix: b"ignored".to_vec(), + max_values: None, + max_size: Some(1 << 22), // MEL of 4 MiB + }]; + + let mapped_results = map_results( + &[data], + &storage_info, + &Default::default(), + test_pov_mode(), + PovEstimationMode::Ignored, + &AnalysisChoice::default(), + &AnalysisChoice::MedianSlopes, + 1_000_000, + 0, + ) + .unwrap(); + let result = + mapped_results.get(&("scheduler".to_string(), "instance".to_string())).unwrap()[0] + .clone(); + + let base = result.base_calculated_proof_size; + assert!(result.component_calculated_proof_size.is_empty(), "There is no slope"); + assert_eq!(base, 0); } #[test] @@ -552,10 +1208,16 @@ mod test { test_data(b"first", b"first", BenchmarkParameter::a, 10, 3), test_data(b"first", b"second", BenchmarkParameter::b, 9, 2), test_data(b"second", b"first", BenchmarkParameter::c, 3, 4), + test_data(b"bounded", b"bounded", BenchmarkParameter::d, 4, 6), ], - &[], + &test_storage_info(), &Default::default(), + Default::default(), + PovEstimationMode::MaxEncodedLen, &AnalysisChoice::default(), + &AnalysisChoice::MedianSlopes, + 1_000_000, + 0, ) .unwrap(); @@ -576,5 +1238,118 @@ mod test { .unwrap()[0]; assert_eq!(second_pallet_benchmark.name, "first_benchmark"); check_data(second_pallet_benchmark, "c", 3, 4); + + let bounded_pallet_benchmark = &mapped_results + .get(&("bounded_pallet".to_string(), "instance".to_string())) + .unwrap()[0]; + assert_eq!(bounded_pallet_benchmark.name, "bounded_benchmark"); + check_data(bounded_pallet_benchmark, "d", 4, 6); + // (5 * 15 * 33 + 32) * 4 = 10028 + assert_eq!(bounded_pallet_benchmark.base_calculated_proof_size, 10028); + // (5 * 15 * 33 + 32) * 6 = 15042 + assert_eq!( + bounded_pallet_benchmark.component_calculated_proof_size, + vec![ComponentSlope { name: "d".into(), slope: 15042, error: 0 }] + ); + } + + #[test] + fn additional_trie_layers_work() { + let mapped_results = map_results( + &[test_data(b"first", b"first", BenchmarkParameter::a, 10, 3)], + &test_storage_info(), + &Default::default(), + Default::default(), + PovEstimationMode::MaxEncodedLen, + &AnalysisChoice::default(), + &AnalysisChoice::MedianSlopes, + 1_000_000, + 2, + ) + .unwrap(); + let with_layer = &mapped_results + .get(&("first_pallet".to_string(), "instance".to_string())) + .unwrap()[0]; + let mapped_results = map_results( + &[test_data(b"first", b"first", BenchmarkParameter::a, 10, 3)], + &test_storage_info(), + &Default::default(), + Default::default(), + PovEstimationMode::MaxEncodedLen, + &AnalysisChoice::default(), + &AnalysisChoice::MedianSlopes, + 1_000_000, + 0, + ) + .unwrap(); + let without_layer = &mapped_results + .get(&("first_pallet".to_string(), "instance".to_string())) + .unwrap()[0]; + + assert_eq!( + without_layer.base_calculated_proof_size + 2 * 15 * 33, + with_layer.base_calculated_proof_size + ); + // The additional trie layers ONLY affect the base weight, not the components. + assert_eq!( + without_layer.component_calculated_proof_size, + with_layer.component_calculated_proof_size + ); + } + + #[test] + fn template_works() { + let all_results = map_results( + &[ + test_data(b"first", b"first", BenchmarkParameter::a, 10, 3), + test_data(b"first", b"second", BenchmarkParameter::b, 9, 2), + test_data(b"second", b"first", BenchmarkParameter::c, 3, 4), + ], + &test_storage_info(), + &Default::default(), + Default::default(), + PovEstimationMode::MaxEncodedLen, + &AnalysisChoice::default(), + &AnalysisChoice::MedianSlopes, + 1_000_000, + 0, + ) + .unwrap(); + + // New Handlebars instance with helpers. + let mut handlebars = handlebars::Handlebars::new(); + handlebars.register_helper("underscore", Box::new(UnderscoreHelper)); + handlebars.register_helper("join", Box::new(JoinHelper)); + // Don't HTML escape any characters. + handlebars.register_escape_fn(|s| -> String { s.to_string() }); + + for ((_pallet, _instance), results) in all_results.iter() { + let hbs_data = TemplateData { benchmarks: results.clone(), ..Default::default() }; + + let output = handlebars.render_template(&TEMPLATE, &hbs_data); + assert!(output.is_ok()); + println!("{:?}", output); + } + } + + #[test] + fn easy_log_16_works() { + assert_eq!(easy_log_16(0), 0); + assert_eq!(easy_log_16(1), 1); + assert_eq!(easy_log_16(16), 1); + assert_eq!(easy_log_16(17), 2); + assert_eq!(easy_log_16(16u32.pow(2)), 2); + assert_eq!(easy_log_16(16u32.pow(2) + 1), 3); + assert_eq!(easy_log_16(16u32.pow(3)), 3); + assert_eq!(easy_log_16(16u32.pow(3) + 1), 4); + assert_eq!(easy_log_16(16u32.pow(4)), 4); + assert_eq!(easy_log_16(16u32.pow(4) + 1), 5); + assert_eq!(easy_log_16(16u32.pow(5)), 5); + assert_eq!(easy_log_16(16u32.pow(5) + 1), 6); + assert_eq!(easy_log_16(16u32.pow(6)), 6); + assert_eq!(easy_log_16(16u32.pow(6) + 1), 7); + assert_eq!(easy_log_16(16u32.pow(7)), 7); + assert_eq!(easy_log_16(16u32.pow(7) + 1), 8); + assert_eq!(easy_log_16(u32::MAX), 8); } } diff --git a/utils/frame/benchmarking-cli/src/shared/mod.rs b/utils/frame/benchmarking-cli/src/shared/mod.rs index ea5415f33f02..f8aa49b867f7 100644 --- a/utils/frame/benchmarking-cli/src/shared/mod.rs +++ b/utils/frame/benchmarking-cli/src/shared/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/benchmarking-cli/src/shared/record.rs b/utils/frame/benchmarking-cli/src/shared/record.rs index 79ab37f65152..d82caa172d07 100644 --- a/utils/frame/benchmarking-cli/src/shared/record.rs +++ b/utils/frame/benchmarking-cli/src/shared/record.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/benchmarking-cli/src/shared/stats.rs b/utils/frame/benchmarking-cli/src/shared/stats.rs index ffae4a17724f..5c724456ddb9 100644 --- a/utils/frame/benchmarking-cli/src/shared/stats.rs +++ b/utils/frame/benchmarking-cli/src/shared/stats.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/benchmarking-cli/src/shared/weight_params.rs b/utils/frame/benchmarking-cli/src/shared/weight_params.rs index 030bbfa00d46..10230ba305f4 100644 --- a/utils/frame/benchmarking-cli/src/shared/weight_params.rs +++ b/utils/frame/benchmarking-cli/src/shared/weight_params.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/benchmarking-cli/src/storage/cmd.rs b/utils/frame/benchmarking-cli/src/storage/cmd.rs index ce2d52e57d64..99cadbe8ec34 100644 --- a/utils/frame/benchmarking-cli/src/storage/cmd.rs +++ b/utils/frame/benchmarking-cli/src/storage/cmd.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,7 +20,6 @@ use sc_client_api::{Backend as ClientBackend, StorageProvider, UsageProvider}; use sc_client_db::DbHash; use sc_service::Configuration; use sp_blockchain::HeaderBackend; -use sp_core::storage::StorageKey; use sp_database::{ColumnId, Database}; use sp_runtime::traits::{Block as BlockT, HashFor}; use sp_state_machine::Storage; @@ -192,8 +191,7 @@ impl StorageCmd { BA: ClientBackend, { let hash = client.usage_info().chain.best_hash; - let empty_prefix = StorageKey(Vec::new()); - let mut keys = client.storage_keys(hash, &empty_prefix)?; + let mut keys: Vec<_> = client.storage_keys(hash, None, None)?.collect(); let (mut rng, _) = new_rng(None); keys.shuffle(&mut rng); diff --git a/utils/frame/benchmarking-cli/src/storage/mod.rs b/utils/frame/benchmarking-cli/src/storage/mod.rs index 0c722fdd4702..188cc5e3d4e4 100644 --- a/utils/frame/benchmarking-cli/src/storage/mod.rs +++ b/utils/frame/benchmarking-cli/src/storage/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/benchmarking-cli/src/storage/read.rs b/utils/frame/benchmarking-cli/src/storage/read.rs index 20c41e4a5196..fe72364269d5 100644 --- a/utils/frame/benchmarking-cli/src/storage/read.rs +++ b/utils/frame/benchmarking-cli/src/storage/read.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +17,6 @@ use sc_cli::Result; use sc_client_api::{Backend as ClientBackend, StorageProvider, UsageProvider}; -use sp_core::storage::StorageKey; use sp_runtime::traits::{Block as BlockT, Header as HeaderT}; use log::info; @@ -42,8 +41,7 @@ impl StorageCmd { info!("Preparing keys from block {}", best_hash); // Load all keys and randomly shuffle them. - let empty_prefix = StorageKey(Vec::new()); - let mut keys = client.storage_keys(best_hash, &empty_prefix)?; + let mut keys: Vec<_> = client.storage_keys(best_hash, None, None)?.collect(); let (mut rng, _) = new_rng(None); keys.shuffle(&mut rng); @@ -55,8 +53,7 @@ impl StorageCmd { match (self.params.include_child_trees, self.is_child_key(key.clone().0)) { (true, Some(info)) => { // child tree key - let child_keys = client.child_storage_keys(best_hash, &info, &empty_prefix)?; - for ck in child_keys { + for ck in client.child_storage_keys(best_hash, info.clone(), None, None)? { child_nodes.push((ck.clone(), info.clone())); } }, diff --git a/utils/frame/benchmarking-cli/src/storage/template.rs b/utils/frame/benchmarking-cli/src/storage/template.rs index ebc415ccb818..43aea75b4771 100644 --- a/utils/frame/benchmarking-cli/src/storage/template.rs +++ b/utils/frame/benchmarking-cli/src/storage/template.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/benchmarking-cli/src/storage/write.rs b/utils/frame/benchmarking-cli/src/storage/write.rs index 1917bc5f8fd0..faca3b536b22 100644 --- a/utils/frame/benchmarking-cli/src/storage/write.rs +++ b/utils/frame/benchmarking-cli/src/storage/write.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -61,7 +61,7 @@ impl StorageCmd { info!("Preparing keys from block {}", best_hash); // Load all KV pairs and randomly shuffle them. - let mut kvs = trie.pairs(); + let mut kvs: Vec<_> = trie.pairs(Default::default())?.collect(); let (mut rng, _) = new_rng(None); kvs.shuffle(&mut rng); info!("Writing {} keys", kvs.len()); @@ -70,11 +70,12 @@ impl StorageCmd { // Generate all random values first; Make sure there are no collisions with existing // db entries, so we can rollback all additions without corrupting existing entries. - for (k, original_v) in kvs { + for key_value in kvs { + let (k, original_v) = key_value?; match (self.params.include_child_trees, self.is_child_key(k.to_vec())) { (true, Some(info)) => { let child_keys = - client.child_storage_keys_iter(best_hash, info.clone(), None, None)?; + client.child_storage_keys(best_hash, info.clone(), None, None)?; for ck in child_keys { child_nodes.push((ck.clone(), info.clone())); } diff --git a/utils/frame/frame-utilities-cli/Cargo.toml b/utils/frame/frame-utilities-cli/Cargo.toml index 26e07c79d8f8..f2665cde5142 100644 --- a/utils/frame/frame-utilities-cli/Cargo.toml +++ b/utils/frame/frame-utilities-cli/Cargo.toml @@ -11,12 +11,12 @@ documentation = "https://docs.rs/substrate-frame-cli" readme = "README.md" [dependencies] -clap = { version = "4.0.9", features = ["derive"] } +clap = { version = "4.2.5", features = ["derive"] } frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } sc-cli = { version = "0.10.0-dev", path = "../../../client/cli" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } [features] default = [] diff --git a/utils/frame/frame-utilities-cli/src/lib.rs b/utils/frame/frame-utilities-cli/src/lib.rs index 9a18d39263c9..97129e36f7e9 100644 --- a/utils/frame/frame-utilities-cli/src/lib.rs +++ b/utils/frame/frame-utilities-cli/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/frame-utilities-cli/src/pallet_id.rs b/utils/frame/frame-utilities-cli/src/pallet_id.rs index 2a80e3a3d312..abc0cdb3ff52 100644 --- a/utils/frame/frame-utilities-cli/src/pallet_id.rs +++ b/utils/frame/frame-utilities-cli/src/pallet_id.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/generate-bags/Cargo.toml b/utils/frame/generate-bags/Cargo.toml index a24af6a2704a..e8d7d51ead89 100644 --- a/utils/frame/generate-bags/Cargo.toml +++ b/utils/frame/generate-bags/Cargo.toml @@ -14,8 +14,8 @@ frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } frame-election-provider-support = { version = "4.0.0-dev", path = "../../../frame/election-provider-support" } frame-system = { version = "4.0.0-dev", path = "../../../frame/system" } pallet-staking = { version = "4.0.0-dev", path = "../../../frame/staking" } +sp-staking = { version = "4.0.0-dev", path = "../../../primitives/staking" } # third party chrono = { version = "0.4.19" } -git2 = { version = "0.16.0", default-features = false } num-format = "0.4.3" diff --git a/utils/frame/generate-bags/node-runtime/Cargo.toml b/utils/frame/generate-bags/node-runtime/Cargo.toml index 46d999daba9c..373a31e79d51 100644 --- a/utils/frame/generate-bags/node-runtime/Cargo.toml +++ b/utils/frame/generate-bags/node-runtime/Cargo.toml @@ -14,4 +14,4 @@ kitchensink-runtime = { version = "3.0.0-dev", path = "../../../../bin/node/runt generate-bags = { version = "4.0.0-dev", path = "../" } # third-party -clap = { version = "4.0.9", features = ["derive"] } +clap = { version = "4.2.5", features = ["derive"] } diff --git a/utils/frame/generate-bags/node-runtime/src/main.rs b/utils/frame/generate-bags/node-runtime/src/main.rs index 27e51b205f8c..b8d233814752 100644 --- a/utils/frame/generate-bags/node-runtime/src/main.rs +++ b/utils/frame/generate-bags/node-runtime/src/main.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/generate-bags/src/lib.rs b/utils/frame/generate-bags/src/lib.rs index 23da131a668d..923017261a44 100644 --- a/utils/frame/generate-bags/src/lib.rs +++ b/utils/frame/generate-bags/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +18,7 @@ //! Support code to ease the process of generating bag thresholds. //! //! NOTE: this assume the runtime implements [`pallet_staking::Config`], as it requires an -//! implementation of the traits [`frame_support::traits::Currency`] and -//! [`frame_support::traits::CurrencyToVote`]. +//! implementation of the traits [`frame_support::traits::Currency`] and `CurrencyToVote`. //! //! The process of adding bags to a runtime requires only four steps. //! @@ -70,7 +69,7 @@ fn existential_weight( total_issuance: u128, minimum_balance: u128, ) -> VoteWeight { - use frame_support::traits::CurrencyToVote; + use sp_staking::currency_to_vote::CurrencyToVote; T::CurrencyToVote::to_vote( minimum_balance @@ -89,8 +88,11 @@ fn existential_weight( /// Just searches the git working directory root for files matching certain patterns; it's /// pretty naive. fn path_to_header_file() -> Option { - let repo = git2::Repository::open_from_env().ok()?; - let workdir = repo.workdir()?; + let mut workdir: &Path = &std::env::current_dir().ok()?; + while !workdir.join(".git").exists() { + workdir = workdir.parent()?; + } + for file_name in &["HEADER-APACHE2", "HEADER-GPL3", "HEADER", "file_header.txt"] { let path = workdir.join(file_name); if path.exists() { diff --git a/utils/frame/remote-externalities/Cargo.toml b/utils/frame/remote-externalities/Cargo.toml index 8611ae4980f1..82a258e0ec39 100644 --- a/utils/frame/remote-externalities/Cargo.toml +++ b/utils/frame/remote-externalities/Cargo.toml @@ -12,16 +12,21 @@ description = "An externalities provided environment that can load itself from r targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2" } +jsonrpsee = { version = "0.16.2", features = ["http-client"] } +codec = { package = "parity-scale-codec", version = "3.6.1" } log = "0.4.17" -serde = "1.0.136" +serde = "1.0.163" frame-support = { version = "4.0.0-dev", optional = true, path = "../../../frame/support" } -sp-core = { version = "7.0.0", path = "../../../primitives/core" } -sp-io = { version = "7.0.0", path = "../../../primitives/io" } -sp-runtime = { version = "7.0.0", path = "../../../primitives/runtime" } +sp-core = { version = "21.0.0", path = "../../../primitives/core" } +sp-io = { version = "23.0.0", path = "../../../primitives/io" } +sp-runtime = { version = "24.0.0", path = "../../../primitives/runtime" } tokio = { version = "1.22.0", features = ["macros", "rt-multi-thread"] } substrate-rpc-client = { path = "../rpc/client" } futures = "0.3" +async-recursion = "1.0.4" +indicatif = "0.17.3" +spinners = "4.1.0" +tokio-retry = "0.3.0" [dev-dependencies] frame-support = { version = "4.0.0-dev", path = "../../../frame/support" } diff --git a/utils/frame/remote-externalities/src/lib.rs b/utils/frame/remote-externalities/src/lib.rs index fb63b4275172..7cd3d3750aeb 100644 --- a/utils/frame/remote-externalities/src/lib.rs +++ b/utils/frame/remote-externalities/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,8 +20,13 @@ //! An equivalent of `sp_io::TestExternalities` that can load its state from a remote substrate //! based chain, or a local state snapshot file. -use codec::{Decode, Encode}; -use futures::{channel::mpsc, stream::StreamExt}; +use async_recursion::async_recursion; +use codec::{Compact, Decode, Encode}; +use indicatif::{ProgressBar, ProgressStyle}; +use jsonrpsee::{ + core::params::ArrayParams, + http_client::{HttpClient, HttpClientBuilder}, +}; use log::*; use serde::de::DeserializeOwned; use sp_core::{ @@ -31,37 +36,77 @@ use sp_core::{ well_known_keys::{is_default_child_storage_key, DEFAULT_CHILD_STORAGE_KEY_PREFIX}, ChildInfo, ChildType, PrefixedStorageKey, StorageData, StorageKey, }, + H256, }; pub use sp_io::TestExternalities; use sp_runtime::{traits::Block as BlockT, StateVersion}; +use spinners::{Spinner, Spinners}; use std::{ + cmp::max, fs, - num::NonZeroUsize, ops::{Deref, DerefMut}, path::{Path, PathBuf}, - sync::Arc, - thread, -}; -use substrate_rpc_client::{ - rpc_params, ws_client, BatchRequestBuilder, ChainApi, ClientT, StateApi, WsClient, + time::{Duration, Instant}, }; +use substrate_rpc_client::{rpc_params, BatchRequestBuilder, ChainApi, ClientT, StateApi}; +use tokio_retry::{strategy::FixedInterval, Retry}; type KeyValue = (StorageKey, StorageData); type TopKeyValues = Vec; type ChildKeyValues = Vec<(ChildInfo, Vec)>; +type SnapshotVersion = Compact; const LOG_TARGET: &str = "remote-ext"; -const DEFAULT_WS_ENDPOINT: &str = "wss://rpc.polkadot.io:443"; -const DEFAULT_VALUE_DOWNLOAD_BATCH: usize = 4096; -// NOTE: increasing this value does not seem to impact speed all that much. -const DEFAULT_KEY_DOWNLOAD_PAGE: u32 = 1000; +const DEFAULT_HTTP_ENDPOINT: &str = "https://rpc.polkadot.io:443"; +const SNAPSHOT_VERSION: SnapshotVersion = Compact(2); + /// The snapshot that we store on disk. #[derive(Decode, Encode)] struct Snapshot { + snapshot_version: SnapshotVersion, state_version: StateVersion, block_hash: B::Hash, - top: TopKeyValues, - child: ChildKeyValues, + // > + raw_storage: Vec<(H256, (Vec, i32))>, + storage_root: H256, +} + +impl Snapshot { + pub fn new( + state_version: StateVersion, + block_hash: B::Hash, + raw_storage: Vec<(H256, (Vec, i32))>, + storage_root: H256, + ) -> Self { + Self { + snapshot_version: SNAPSHOT_VERSION, + state_version, + block_hash, + raw_storage, + storage_root, + } + } + + fn load(path: &PathBuf) -> Result, &'static str> { + let bytes = fs::read(path).map_err(|_| "fs::read failed.")?; + // The first item in the SCALE encoded struct bytes is the snapshot version. We decode and + // check that first, before proceeding to decode the rest of the snapshot. + let maybe_version: Result = Decode::decode(&mut &*bytes); + match maybe_version { + Ok(snapshot_version) => { + if snapshot_version != SNAPSHOT_VERSION { + return Err( + "Unsupported snapshot version detected. Please create a new snapshot.", + ) + } + match Decode::decode(&mut &*bytes) { + Ok(snapshot) => return Ok(snapshot), + Err(_) => Err("Decode failed"), + } + }, + Err(_) => Err("Decode failed"), + } + } } /// An externalities that acts exactly the same as [`sp_io::TestExternalities`] but has a few extra @@ -117,36 +162,48 @@ pub struct OfflineConfig { pub enum Transport { /// Use the `URI` to open a new WebSocket connection. Uri(String), - /// Use existing WebSocket connection. - RemoteClient(Arc), + /// Use HTTP connection. + RemoteClient(HttpClient), } impl Transport { - fn as_client(&self) -> Option<&WsClient> { + fn as_client(&self) -> Option<&HttpClient> { match self { Self::RemoteClient(client) => Some(client), _ => None, } } - fn as_client_cloned(&self) -> Option> { - match self { - Self::RemoteClient(client) => Some(client.clone()), - _ => None, - } - } - - // Open a new WebSocket connection if it's not connected. - async fn map_uri(&mut self) -> Result<(), &'static str> { + // Build an HttpClient from a URI. + async fn init(&mut self) -> Result<(), &'static str> { if let Self::Uri(uri) = self { log::debug!(target: LOG_TARGET, "initializing remote client to {:?}", uri); - let ws_client = ws_client(uri).await.map_err(|e| { - log::error!(target: LOG_TARGET, "error: {:?}", e); - "failed to build ws client" - })?; + // If we have a ws uri, try to convert it to an http uri. + // We use an HTTP client rather than WS because WS starts to choke with "accumulated + // message length exceeds maximum" errors after processing ~10k keys when fetching + // from a node running a default configuration. + let uri = if uri.starts_with("ws://") { + let uri = uri.replace("ws://", "http://"); + log::info!(target: LOG_TARGET, "replacing ws:// in uri with http://: {:?} (ws is currently unstable for fetching remote storage, for more see https://github.com/paritytech/jsonrpsee/issues/1086)", uri); + uri + } else if uri.starts_with("wss://") { + let uri = uri.replace("wss://", "https://"); + log::info!(target: LOG_TARGET, "replacing wss:// in uri with https://: {:?} (ws is currently unstable for fetching remote storage, for more see https://github.com/paritytech/jsonrpsee/issues/1086)", uri); + uri + } else { + uri.clone() + }; + let http_client = HttpClientBuilder::default() + .max_request_body_size(u32::MAX) + .request_timeout(std::time::Duration::from_secs(60 * 5)) + .build(uri) + .map_err(|e| { + log::error!(target: LOG_TARGET, "error: {:?}", e); + "failed to build http client" + })?; - *self = Self::RemoteClient(Arc::new(ws_client)) + *self = Self::RemoteClient(http_client) } Ok(()) @@ -159,8 +216,8 @@ impl From for Transport { } } -impl From> for Transport { - fn from(client: Arc) -> Self { +impl From for Transport { + fn from(client: HttpClient) -> Self { Transport::RemoteClient(client) } } @@ -189,18 +246,11 @@ pub struct OnlineConfig { } impl OnlineConfig { - /// Return rpc (ws) client reference. - fn rpc_client(&self) -> &WsClient { + /// Return rpc (http) client reference. + fn rpc_client(&self) -> &HttpClient { self.transport .as_client() - .expect("ws client must have been initialized by now; qed.") - } - - /// Return a cloned rpc (ws) client, suitable for being moved to threads. - fn rpc_client_cloned(&self) -> Arc { - self.transport - .as_client_cloned() - .expect("ws client must have been initialized by now; qed.") + .expect("http client must have been initialized by now; qed.") } fn at_expected(&self) -> B::Hash { @@ -211,7 +261,7 @@ impl OnlineConfig { impl Default for OnlineConfig { fn default() -> Self { Self { - transport: Transport::from(DEFAULT_WS_ENDPOINT.to_owned()), + transport: Transport::from(DEFAULT_HTTP_ENDPOINT.to_owned()), child_trie: true, at: None, state_snapshot: None, @@ -307,11 +357,14 @@ where B::Hash: DeserializeOwned, B::Header: DeserializeOwned, { - /// Get the number of threads to use. - fn threads() -> NonZeroUsize { - thread::available_parallelism() - .unwrap_or(NonZeroUsize::new(4usize).expect("4 is non-zero; qed")) - } + const PARALLEL_REQUESTS: usize = 4; + const BATCH_SIZE_INCREASE_FACTOR: f32 = 1.10; + const BATCH_SIZE_DECREASE_FACTOR: f32 = 0.50; + const INITIAL_BATCH_SIZE: usize = 5000; + // nodes by default will not return more than 1000 keys per request + const DEFAULT_KEY_DOWNLOAD_PAGE: u32 = 1000; + const KEYS_PAGE_MAX_RETRIES: usize = 12; + const KEYS_PAGE_RETRY_INTERVAL: Duration = Duration::from_secs(5); async fn rpc_get_storage( &self, @@ -338,6 +391,22 @@ where }) } + async fn get_keys_single_page( + &self, + prefix: Option, + start_key: Option, + at: B::Hash, + ) -> Result, &'static str> { + self.as_online() + .rpc_client() + .storage_keys_paged(prefix, Self::DEFAULT_KEY_DOWNLOAD_PAGE, start_key, Some(at)) + .await + .map_err(|e| { + error!(target: LOG_TARGET, "Error = {:?}", e); + "rpc get_keys failed" + }) + } + /// Get all the keys at `prefix` at `hash` using the paged, safe RPC methods. async fn rpc_get_keys_paged( &self, @@ -347,25 +416,18 @@ where let mut last_key: Option = None; let mut all_keys: Vec = vec![]; let keys = loop { - let page = self - .as_online() - .rpc_client() - .storage_keys_paged( - Some(prefix.clone()), - DEFAULT_KEY_DOWNLOAD_PAGE, - last_key.clone(), - Some(at), - ) - .await - .map_err(|e| { - error!(target: LOG_TARGET, "Error = {:?}", e); - "rpc get_keys failed" - })?; + // This loop can hit the node with very rapid requests, occasionally causing it to + // error out in CI (https://github.com/paritytech/substrate/issues/14129), so we retry. + let retry_strategy = FixedInterval::new(Self::KEYS_PAGE_RETRY_INTERVAL) + .take(Self::KEYS_PAGE_MAX_RETRIES); + let get_page_closure = + || self.get_keys_single_page(Some(prefix.clone()), last_key.clone(), at); + let page = Retry::spawn(retry_strategy, get_page_closure).await?; let page_len = page.len(); all_keys.extend(page); - if page_len < DEFAULT_KEY_DOWNLOAD_PAGE as usize { + if page_len < Self::DEFAULT_KEY_DOWNLOAD_PAGE as usize { log::debug!(target: LOG_TARGET, "last page received: {}", page_len); break all_keys } else { @@ -378,12 +440,134 @@ where HexDisplay::from(new_last_key) ); last_key = Some(new_last_key.clone()); - } + }; }; Ok(keys) } + /// Fetches storage data from a node using a dynamic batch size. + /// + /// This function adjusts the batch size on the fly to help prevent overwhelming the node with + /// large batch requests, and stay within request size limits enforced by the node. + /// + /// # Arguments + /// + /// * `client` - An `Arc` wrapped `HttpClient` used for making the requests. + /// * `payloads` - A vector of tuples containing a JSONRPC method name and `ArrayParams` + /// * `batch_size` - The initial batch size to use for the request. The batch size will be + /// adjusted dynamically in case of failure. + /// + /// # Returns + /// + /// Returns a `Result` with a vector of `Option`, where each element corresponds to + /// the storage data for the given method and parameters. The result will be an `Err` with a + /// `String` error message if the request fails. + /// + /// # Errors + /// + /// This function will return an error if: + /// * The batch request fails and the batch size is less than 2. + /// * There are invalid batch params. + /// * There is an error in the batch response. + /// + /// # Example + /// + /// ```ignore + /// use your_crate::{get_storage_data_dynamic_batch_size, HttpClient, ArrayParams}; + /// use std::sync::Arc; + /// + /// async fn example() { + /// let client = HttpClient::new(); + /// let payloads = vec![ + /// ("some_method".to_string(), ArrayParams::new(vec![])), + /// ("another_method".to_string(), ArrayParams::new(vec![])), + /// ]; + /// let initial_batch_size = 10; + /// + /// let storage_data = get_storage_data_dynamic_batch_size(client, payloads, batch_size).await; + /// match storage_data { + /// Ok(data) => println!("Storage data: {:?}", data), + /// Err(e) => eprintln!("Error fetching storage data: {}", e), + /// } + /// } + /// ``` + #[async_recursion] + async fn get_storage_data_dynamic_batch_size( + client: &HttpClient, + payloads: Vec<(String, ArrayParams)>, + batch_size: usize, + bar: &ProgressBar, + ) -> Result>, String> { + // All payloads have been processed + if payloads.is_empty() { + return Ok(vec![]) + }; + + log::debug!( + target: LOG_TARGET, + "Remaining payloads: {} Batch request size: {}", + payloads.len(), + batch_size, + ); + + // Payloads to attempt to process this batch + let page = payloads.iter().take(batch_size).cloned().collect::>(); + + // Build the batch request + let mut batch = BatchRequestBuilder::new(); + for (method, params) in page.iter() { + batch + .insert(method, params.clone()) + .map_err(|_| "Invalid batch method and/or params")? + } + let batch_response = match client.batch_request::>(batch).await { + Ok(batch_response) => batch_response, + Err(e) => { + if batch_size < 2 { + return Err(e.to_string()) + } + + log::debug!( + target: LOG_TARGET, + "Batch request failed, trying again with smaller batch size. {}", + e.to_string() + ); + + return Self::get_storage_data_dynamic_batch_size( + client, + payloads, + max(1, (batch_size as f32 * Self::BATCH_SIZE_DECREASE_FACTOR) as usize), + bar, + ) + .await + }, + }; + + // Collect the data from this batch + let mut data: Vec> = vec![]; + let batch_response_len = batch_response.len(); + for item in batch_response.into_iter() { + match item { + Ok(x) => data.push(x), + Err(e) => return Err(e.message().to_string()), + } + } + bar.inc(batch_response_len as u64); + + // Return this data joined with the remaining keys + let remaining_payloads = payloads.iter().skip(batch_size).cloned().collect::>(); + let mut rest = Self::get_storage_data_dynamic_batch_size( + client, + remaining_payloads, + max(batch_size + 1, (batch_size as f32 * Self::BATCH_SIZE_INCREASE_FACTOR) as usize), + bar, + ) + .await?; + data.append(&mut rest); + Ok(data) + } + /// Synonym of `getPairs` that uses paged queries to first get the keys, and then /// map them to values one by one. /// @@ -394,233 +578,150 @@ where at: B::Hash, pending_ext: &mut TestExternalities, ) -> Result, &'static str> { - let keys = self.rpc_get_keys_paged(prefix.clone(), at).await?; + let start = Instant::now(); + let mut sp = Spinner::with_timer(Spinners::Dots, "Scraping keys...".into()); + let keys = self + .rpc_get_keys_paged(prefix.clone(), at) + .await? + .into_iter() + .collect::>(); + sp.stop_with_message(format!( + "✅ Found {} keys ({:.2}s)", + keys.len(), + start.elapsed().as_secs_f32() + )); if keys.is_empty() { return Ok(Default::default()) } - let client = self.as_online().rpc_client_cloned(); - let threads = Self::threads().get(); - let thread_chunk_size = (keys.len() + threads - 1) / threads; + let client = self.as_online().rpc_client(); + let payloads = keys + .iter() + .map(|key| ("state_getStorage".to_string(), rpc_params!(key, at))) + .collect::>(); - log::info!( - target: LOG_TARGET, - "Querying a total of {} keys from prefix {:?}, splitting among {} threads, {} keys per thread", - keys.len(), - HexDisplay::from(&prefix), - threads, - thread_chunk_size, + let bar = ProgressBar::new(payloads.len() as u64); + bar.enable_steady_tick(Duration::from_secs(1)); + bar.set_message("Downloading key values".to_string()); + bar.set_style( + ProgressStyle::with_template( + "[{elapsed_precise}] {msg} {per_sec} [{wide_bar}] {pos}/{len} ({eta})", + ) + .unwrap() + .progress_chars("=>-"), ); + let payloads_chunked = payloads.chunks((&payloads.len() / Self::PARALLEL_REQUESTS).max(1)); + let requests = payloads_chunked.map(|payload_chunk| { + Self::get_storage_data_dynamic_batch_size( + &client, + payload_chunk.to_vec(), + Self::INITIAL_BATCH_SIZE, + &bar, + ) + }); + // Execute the requests and move the Result outside. + let storage_data_result: Result, _> = + futures::future::join_all(requests).await.into_iter().collect(); + // Handle the Result. + let storage_data = match storage_data_result { + Ok(storage_data) => storage_data.into_iter().flatten().collect::>(), + Err(e) => { + log::error!(target: LOG_TARGET, "Error while getting storage data: {}", e); + return Err("Error while getting storage data") + }, + }; + bar.finish_with_message("✅ Downloaded key values"); + print!("\n"); - let mut handles = Vec::new(); - let keys_chunked: Vec> = - keys.chunks(thread_chunk_size).map(|s| s.into()).collect::>(); - - enum Message { - /// This thread completed the assigned work. - Terminated, - /// The thread produced the following batch response. - Batch(Vec<(Vec, Vec)>), - /// A request from the batch failed. - BatchFailed(String), - } - - let (tx, mut rx) = mpsc::unbounded::(); - - for thread_keys in keys_chunked { - let thread_client = client.clone(); - let thread_sender = tx.clone(); - let handle = std::thread::spawn(move || { - let rt = tokio::runtime::Runtime::new().unwrap(); - let mut thread_key_values = Vec::with_capacity(thread_keys.len()); - - for chunk_keys in thread_keys.chunks(DEFAULT_VALUE_DOWNLOAD_BATCH) { - let mut batch = BatchRequestBuilder::new(); - - for key in chunk_keys.iter() { - batch - .insert("state_getStorage", rpc_params![key, at]) - .map_err(|_| "Invalid batch params") - .unwrap(); - } - - let batch_response = rt - .block_on(thread_client.batch_request::>(batch)) - .map_err(|e| { - log::error!( - target: LOG_TARGET, - "failed to execute batch: {:?}. Error: {:?}", - chunk_keys.iter().map(HexDisplay::from).collect::>(), - e - ); - "batch failed." - }) - .unwrap(); - - // Check if we got responses for all submitted requests. - assert_eq!(chunk_keys.len(), batch_response.len()); - - let mut batch_kv = Vec::with_capacity(chunk_keys.len()); - for (key, maybe_value) in chunk_keys.into_iter().zip(batch_response) { - match maybe_value { - Ok(Some(data)) => { - thread_key_values.push((key.clone(), data.clone())); - batch_kv.push((key.clone().0, data.0)); - }, - Ok(None) => { - log::warn!( - target: LOG_TARGET, - "key {:?} had none corresponding value.", - &key - ); - let data = StorageData(vec![]); - thread_key_values.push((key.clone(), data.clone())); - batch_kv.push((key.clone().0, data.0)); - }, - Err(e) => { - let reason = format!("key {:?} failed: {:?}", &key, e); - log::error!(target: LOG_TARGET, "Reason: {}", reason); - // Signal failures to the main thread, stop aggregating (key, value) - // pairs and return immediately an error. - thread_sender.unbounded_send(Message::BatchFailed(reason)).unwrap(); - return Default::default() - }, - }; - - if thread_key_values.len() % (thread_keys.len() / 10).max(1) == 0 { - let ratio: f64 = - thread_key_values.len() as f64 / thread_keys.len() as f64; - log::debug!( - target: LOG_TARGET, - "[thread = {:?}] progress = {:.2} [{} / {}]", - std::thread::current().id(), - ratio, - thread_key_values.len(), - thread_keys.len(), - ); - } - } - - // Send this batch to the main thread to start inserting. - thread_sender.unbounded_send(Message::Batch(batch_kv)).unwrap(); - } - - thread_sender.unbounded_send(Message::Terminated).unwrap(); - thread_key_values - }); - - handles.push(handle); - } + // Check if we got responses for all submitted requests. + assert_eq!(keys.len(), storage_data.len()); - // first, wait until all threads send a `Terminated` message, in the meantime populate - // `pending_ext`. - let mut terminated = 0usize; - let mut batch_failed = false; - loop { - match rx.next().await.unwrap() { - Message::Batch(kv) => { - for (k, v) in kv { - // skip writing the child root data. - if is_default_child_storage_key(k.as_ref()) { - continue - } - pending_ext.insert(k, v); - } - }, - Message::BatchFailed(error) => { - log::error!(target: LOG_TARGET, "Batch processing failed: {:?}", error); - batch_failed = true; - break - }, - Message::Terminated => { - terminated += 1; - if terminated == handles.len() { - break - } + let key_values = keys + .iter() + .zip(storage_data) + .map(|(key, maybe_value)| match maybe_value { + Some(data) => (key.clone(), data), + None => { + log::warn!(target: LOG_TARGET, "key {:?} had none corresponding value.", &key); + let data = StorageData(vec![]); + (key.clone(), data) }, - } - } - - // Ensure all threads finished execution before returning. - let keys_and_values = - handles.into_iter().flat_map(|h| h.join().unwrap()).collect::>(); - - if batch_failed { - return Err("Batch failed.") - } + }) + .collect::>(); - Ok(keys_and_values) + let mut sp = Spinner::with_timer(Spinners::Dots, "Inserting keys into DB...".into()); + let start = Instant::now(); + pending_ext.batch_insert(key_values.clone().into_iter().filter_map(|(k, v)| { + // Don't insert the child keys here, they need to be inserted seperately with all their + // data in the load_child_remote function. + match is_default_child_storage_key(&k.0) { + true => None, + false => Some((k.0, v.0)), + } + })); + sp.stop_with_message(format!( + "✅ Inserted keys into DB ({:.2}s)", + start.elapsed().as_secs_f32() + )); + Ok(key_values) } /// Get the values corresponding to `child_keys` at the given `prefixed_top_key`. pub(crate) async fn rpc_child_get_storage_paged( - client: &WsClient, + client: &HttpClient, prefixed_top_key: &StorageKey, child_keys: Vec, at: B::Hash, ) -> Result, &'static str> { - let mut child_kv_inner = vec![]; - let mut batch_success = true; - - for batch_child_key in child_keys.chunks(DEFAULT_VALUE_DOWNLOAD_BATCH) { - let mut batch_request = BatchRequestBuilder::new(); - - for key in batch_child_key { - batch_request - .insert( - "childstate_getStorage", - rpc_params![ - PrefixedStorageKey::new(prefixed_top_key.as_ref().to_vec()), - key, - at - ], - ) - .map_err(|_| "Invalid batch params")?; - } + let child_keys_len = child_keys.len(); - let batch_response = - client.batch_request::>(batch_request).await.map_err(|e| { - log::error!( - target: LOG_TARGET, - "failed to execute batch: {:?}. Error: {:?}", - batch_child_key, - e - ); - "batch failed." - })?; + let payloads = child_keys + .iter() + .map(|key| { + ( + "childstate_getStorage".to_string(), + rpc_params![ + PrefixedStorageKey::new(prefixed_top_key.as_ref().to_vec()), + key, + at + ], + ) + }) + .collect::>(); - assert_eq!(batch_child_key.len(), batch_response.len()); - - for (key, maybe_value) in batch_child_key.iter().zip(batch_response) { - match maybe_value { - Ok(Some(v)) => { - child_kv_inner.push((key.clone(), v)); - }, - Ok(None) => { - log::warn!( - target: LOG_TARGET, - "key {:?} had none corresponding value.", - &key - ); - child_kv_inner.push((key.clone(), StorageData(vec![]))); - }, - Err(e) => { - log::error!(target: LOG_TARGET, "key {:?} failed: {:?}", &key, e); - batch_success = false; - }, - }; - } - } + let bar = ProgressBar::new(payloads.len() as u64); + let storage_data = match Self::get_storage_data_dynamic_batch_size( + client, + payloads, + Self::INITIAL_BATCH_SIZE, + &bar, + ) + .await + { + Ok(storage_data) => storage_data, + Err(e) => { + log::error!(target: LOG_TARGET, "batch processing failed: {:?}", e); + return Err("batch processing failed") + }, + }; - if batch_success { - Ok(child_kv_inner) - } else { - Err("batch failed.") - } + assert_eq!(child_keys_len, storage_data.len()); + + Ok(child_keys + .iter() + .zip(storage_data) + .map(|(key, maybe_value)| match maybe_value { + Some(v) => (key.clone(), v), + None => { + log::warn!(target: LOG_TARGET, "key {:?} had no corresponding value.", &key); + (key.clone(), StorageData(vec![])) + }, + }) + .collect::>()) } pub(crate) async fn rpc_child_get_keys( - client: &WsClient, + client: &HttpClient, prefixed_top_key: &StorageKey, child_prefix: StorageKey, at: B::Hash, @@ -675,110 +776,50 @@ where .collect::>(); if child_roots.is_empty() { + info!( + target: LOG_TARGET, + "👩‍👦 no child roots found to scrape", + ); return Ok(Default::default()) } - // div-ceil simulation. - let threads = Self::threads().get(); - let child_roots_per_thread = (child_roots.len() + threads - 1) / threads; - info!( target: LOG_TARGET, - "👩‍👦 scraping child-tree data from {} top keys, split among {} threads, {} top keys per thread", + "👩‍👦 scraping child-tree data from {} top keys", child_roots.len(), - threads, - child_roots_per_thread, ); - // NOTE: the threading done here is the simpler, yet slightly un-elegant because we are - // splitting child root among threads, and it is very common for these root to have vastly - // different child tries underneath them, causing some threads to finish way faster than - // others. Certainly still better than single thread though. - let mut handles = vec![]; - let client = self.as_online().rpc_client_cloned(); let at = self.as_online().at_expected(); - enum Message { - Terminated, - Batch((ChildInfo, Vec<(Vec, Vec)>)), - } - let (tx, mut rx) = mpsc::unbounded::(); + let client = self.as_online().rpc_client(); + let mut child_kv = vec![]; + for prefixed_top_key in child_roots { + let child_keys = + Self::rpc_child_get_keys(&client, &prefixed_top_key, StorageKey(vec![]), at) + .await?; - for thread_child_roots in child_roots - .chunks(child_roots_per_thread) - .map(|x| x.into()) - .collect::>>() - { - let thread_client = client.clone(); - let thread_sender = tx.clone(); - let handle = thread::spawn(move || { - let rt = tokio::runtime::Runtime::new().unwrap(); - let mut thread_child_kv = vec![]; - for prefixed_top_key in thread_child_roots { - let child_keys = rt.block_on(Self::rpc_child_get_keys( - &thread_client, - &prefixed_top_key, - StorageKey(vec![]), - at, - ))?; - let child_kv_inner = rt.block_on(Self::rpc_child_get_storage_paged( - &thread_client, - &prefixed_top_key, - child_keys, - at, - ))?; - - let prefixed_top_key = PrefixedStorageKey::new(prefixed_top_key.clone().0); - let un_prefixed = match ChildType::from_prefixed_key(&prefixed_top_key) { - Some((ChildType::ParentKeyId, storage_key)) => storage_key, - None => { - log::error!(target: LOG_TARGET, "invalid key: {:?}", prefixed_top_key); - return Err("Invalid child key") - }, - }; - - thread_sender - .unbounded_send(Message::Batch(( - ChildInfo::new_default(un_prefixed), - child_kv_inner - .iter() - .cloned() - .map(|(k, v)| (k.0, v.0)) - .collect::>(), - ))) - .unwrap(); - thread_child_kv.push((ChildInfo::new_default(un_prefixed), child_kv_inner)); - } + let child_kv_inner = + Self::rpc_child_get_storage_paged(&client, &prefixed_top_key, child_keys, at) + .await?; - thread_sender.unbounded_send(Message::Terminated).unwrap(); - Ok(thread_child_kv) - }); - handles.push(handle); - } - - // first, wait until all threads send a `Terminated` message, in the meantime populate - // `pending_ext`. - let mut terminated = 0usize; - loop { - match rx.next().await.unwrap() { - Message::Batch((info, kvs)) => - for (k, v) in kvs { - pending_ext.insert_child(info.clone(), k, v); - }, - Message::Terminated => { - terminated += 1; - if terminated == handles.len() { - break - } + let prefixed_top_key = PrefixedStorageKey::new(prefixed_top_key.clone().0); + let un_prefixed = match ChildType::from_prefixed_key(&prefixed_top_key) { + Some((ChildType::ParentKeyId, storage_key)) => storage_key, + None => { + log::error!(target: LOG_TARGET, "invalid key: {:?}", prefixed_top_key); + return Err("Invalid child key") }, + }; + + let info = ChildInfo::new_default(un_prefixed); + let key_values = + child_kv_inner.iter().cloned().map(|(k, v)| (k.0, v.0)).collect::>(); + child_kv.push((info.clone(), child_kv_inner)); + for (k, v) in key_values { + pending_ext.insert_child(info.clone(), k, v); } } - let child_kv = handles - .into_iter() - .flat_map(|h| h.join().unwrap()) - .flatten() - .collect::>(); Ok(child_kv) } @@ -805,9 +846,9 @@ where let elapsed = now.elapsed(); log::info!( target: LOG_TARGET, - "adding data for hashed prefix: {:?}, took {:?}s", + "adding data for hashed prefix: {:?}, took {:.2}s", HexDisplay::from(prefix), - elapsed.as_secs() + elapsed.as_secs_f32() ); keys_and_values.extend(additional_key_values); } @@ -841,8 +882,8 @@ where /// /// initializes the remote client in `transport`, and sets the `at` field, if not specified. async fn init_remote_client(&mut self) -> Result<(), &'static str> { - // First, initialize the ws client. - self.as_online_mut().transport.map_uri().await?; + // First, initialize the http client. + self.as_online_mut().transport.init().await?; // Then, if `at` is not set, set it. if self.as_online().at.is_none() { @@ -902,19 +943,22 @@ where Default::default(), self.overwrite_state_version.unwrap_or(state_version), ); + + // Load data from the remote into `pending_ext`. let top_kv = self.load_top_remote(&mut pending_ext).await?; - let child_kv = self.load_child_remote(&top_kv, &mut pending_ext).await?; + self.load_child_remote(&top_kv, &mut pending_ext).await?; + // If we need to save a snapshot, save the raw storage and root hash to the snapshot. if let Some(path) = self.as_online().state_snapshot.clone().map(|c| c.path) { - let snapshot = Snapshot:: { + let (raw_storage, storage_root) = pending_ext.into_raw_snapshot(); + let snapshot = Snapshot::::new( state_version, - top: top_kv, - child: child_kv, - block_hash: self - .as_online() + self.as_online() .at .expect("set to `Some` in `init_remote_client`; must be called before; qed"), - }; + raw_storage.clone(), + storage_root, + ); let encoded = snapshot.encode(); log::info!( target: LOG_TARGET, @@ -923,17 +967,20 @@ where path ); std::fs::write(path, encoded).map_err(|_| "fs::write failed")?; + + // pending_ext was consumed when creating the snapshot, need to reinitailize it + let mut pending_ext = TestExternalities::new_with_code_and_state( + Default::default(), + Default::default(), + self.overwrite_state_version.unwrap_or(state_version), + ); + pending_ext.from_raw_snapshot(raw_storage, storage_root); + return Ok(pending_ext) } Ok(pending_ext) } - fn load_snapshot(&mut self, path: PathBuf) -> Result, &'static str> { - info!(target: LOG_TARGET, "loading data from snapshot {:?}", path); - let bytes = fs::read(path).map_err(|_| "fs::read failed.")?; - Decode::decode(&mut &*bytes).map_err(|_| "decode failed") - } - async fn do_load_remote(&mut self) -> Result, &'static str> { self.init_remote_client().await?; let block_hash = self.as_online().at_expected(); @@ -945,35 +992,19 @@ where &mut self, config: OfflineConfig, ) -> Result, &'static str> { - let Snapshot { block_hash, top, child, state_version } = - self.load_snapshot(config.state_snapshot.path.clone())?; + let mut sp = Spinner::with_timer(Spinners::Dots, "Loading snapshot...".into()); + let start = Instant::now(); + info!(target: LOG_TARGET, "Loading snapshot from {:?}", &config.state_snapshot.path); + let Snapshot { snapshot_version: _, block_hash, state_version, raw_storage, storage_root } = + Snapshot::::load(&config.state_snapshot.path)?; let mut inner_ext = TestExternalities::new_with_code_and_state( Default::default(), Default::default(), self.overwrite_state_version.unwrap_or(state_version), ); - - info!(target: LOG_TARGET, "injecting a total of {} top keys", top.len()); - for (k, v) in top { - // skip writing the child root data. - if is_default_child_storage_key(k.as_ref()) { - continue - } - inner_ext.insert(k.0, v.0); - } - - info!( - target: LOG_TARGET, - "injecting a total of {} child keys", - child.iter().flat_map(|(_, kv)| kv).count() - ); - - for (info, key_values) in child { - for (k, v) in key_values { - inner_ext.insert_child(info.clone(), k.0, v.0); - } - } + inner_ext.from_raw_snapshot(raw_storage, storage_root); + sp.stop_with_message(format!("✅ Loaded snapshot ({:.2}s)", start.elapsed().as_secs_f32())); Ok(RemoteExternalities { inner_ext, block_hash }) } @@ -997,9 +1028,7 @@ where "extending externalities with {} manually injected key-values", self.hashed_key_values.len() ); - for (k, v) in self.hashed_key_values { - ext.insert(k.0, v.0); - } + ext.batch_insert(self.hashed_key_values.into_iter().map(|(k, v)| (k.0, v.0))); } // exclude manual key values. @@ -1091,7 +1120,7 @@ mod test_prelude { mod tests { use super::test_prelude::*; - #[tokio::test(flavor = "multi_thread")] + #[tokio::test] async fn can_load_state_snapshot() { init_logger(); Builder::::new() @@ -1104,7 +1133,7 @@ mod tests { .execute_with(|| {}); } - #[tokio::test(flavor = "multi_thread")] + #[tokio::test] async fn can_exclude_from_snapshot() { init_logger(); @@ -1140,7 +1169,7 @@ mod remote_tests { use super::test_prelude::*; use std::os::unix::fs::MetadataExt; - #[tokio::test(flavor = "multi_thread")] + #[tokio::test] async fn state_version_is_kept_and_can_be_altered() { const CACHE: &'static str = "state_version_is_kept_and_can_be_altered"; init_logger(); @@ -1181,7 +1210,7 @@ mod remote_tests { assert_eq!(cached_ext.state_version, other); } - #[tokio::test(flavor = "multi_thread")] + #[tokio::test] async fn snapshot_block_hash_works() { const CACHE: &'static str = "snapshot_block_hash_works"; init_logger(); @@ -1208,7 +1237,43 @@ mod remote_tests { assert_eq!(ext.block_hash, cached_ext.block_hash); } - #[tokio::test(flavor = "multi_thread")] + #[tokio::test] + async fn child_keys_are_loaded() { + const CACHE: &'static str = "snapshot_retains_storage"; + init_logger(); + + // create an ext with children keys + let child_ext = Builder::::new() + .mode(Mode::Online(OnlineConfig { + pallets: vec!["Proxy".to_owned()], + child_trie: true, + state_snapshot: Some(SnapshotConfig::new(CACHE)), + ..Default::default() + })) + .build() + .await + .unwrap(); + + // create an ext without children keys + let ext = Builder::::new() + .mode(Mode::Online(OnlineConfig { + pallets: vec!["Proxy".to_owned()], + child_trie: false, + state_snapshot: Some(SnapshotConfig::new(CACHE)), + ..Default::default() + })) + .build() + .await + .unwrap(); + + // there should be more keys in the child ext. + assert!( + child_ext.as_backend().backend_storage().keys().len() > + ext.as_backend().backend_storage().keys().len() + ); + } + + #[tokio::test] async fn offline_else_online_works() { const CACHE: &'static str = "offline_else_online_works_data"; init_logger(); @@ -1253,7 +1318,7 @@ mod remote_tests { std::fs::remove_file(to_delete[0].path()).unwrap(); } - #[tokio::test(flavor = "multi_thread")] + #[tokio::test] async fn can_build_one_small_pallet() { init_logger(); Builder::::new() @@ -1268,7 +1333,7 @@ mod remote_tests { .execute_with(|| {}); } - #[tokio::test(flavor = "multi_thread")] + #[tokio::test] async fn can_build_few_pallet() { init_logger(); Builder::::new() @@ -1308,7 +1373,7 @@ mod remote_tests { .collect::>(); let snap: Snapshot = Builder::::new().load_snapshot(CACHE.into()).unwrap(); - assert!(matches!(snap, Snapshot { top, child, .. } if top.len() > 0 && child.len() == 0)); + assert!(matches!(snap, Snapshot { raw_storage, .. } if raw_storage.len() > 0)); assert!(to_delete.len() == 1); let to_delete = to_delete.first().unwrap(); @@ -1316,7 +1381,7 @@ mod remote_tests { std::fs::remove_file(to_delete.path()).unwrap(); } - #[tokio::test(flavor = "multi_thread")] + #[tokio::test] async fn can_create_child_snapshot() { const CACHE: &'static str = "can_create_child_snapshot"; init_logger(); @@ -1340,7 +1405,7 @@ mod remote_tests { .collect::>(); let snap: Snapshot = Builder::::new().load_snapshot(CACHE.into()).unwrap(); - assert!(matches!(snap, Snapshot { top, child, .. } if top.len() > 0 && child.len() > 0)); + assert!(matches!(snap, Snapshot { raw_storage, .. } if raw_storage.len() > 0)); assert!(to_delete.len() == 1); let to_delete = to_delete.first().unwrap(); @@ -1348,7 +1413,7 @@ mod remote_tests { std::fs::remove_file(to_delete.path()).unwrap(); } - #[tokio::test(flavor = "multi_thread")] + #[tokio::test] async fn can_build_big_pallet() { if std::option_env!("TEST_WS").is_none() { return @@ -1367,7 +1432,7 @@ mod remote_tests { .execute_with(|| {}); } - #[tokio::test(flavor = "multi_thread")] + #[tokio::test] async fn can_fetch_all() { if std::option_env!("TEST_WS").is_none() { return diff --git a/utils/frame/remote-externalities/test_data/proxy_test b/utils/frame/remote-externalities/test_data/proxy_test index 6673bd6765ad..de8105ee152d 100644 Binary files a/utils/frame/remote-externalities/test_data/proxy_test and b/utils/frame/remote-externalities/test_data/proxy_test differ diff --git a/utils/frame/rpc/client/Cargo.toml b/utils/frame/rpc/client/Cargo.toml index ee9982971cee..d39fbbdf4860 100644 --- a/utils/frame/rpc/client/Cargo.toml +++ b/utils/frame/rpc/client/Cargo.toml @@ -16,7 +16,7 @@ jsonrpsee = { version = "0.16.2", features = ["ws-client"] } sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } async-trait = "0.1.57" serde = "1" -sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../../../primitives/runtime" } log = "0.4" [dev-dependencies] diff --git a/utils/frame/rpc/client/src/lib.rs b/utils/frame/rpc/client/src/lib.rs index a6f73ba6784b..a6a667bef568 100644 --- a/utils/frame/rpc/client/src/lib.rs +++ b/utils/frame/rpc/client/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml index 17a68e2f4cbe..9eee52aacba7 100644 --- a/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml +++ b/utils/frame/rpc/state-trie-migration-rpc/Cargo.toml @@ -13,22 +13,20 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } -codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false } +codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false } serde = { version = "1", features = ["derive"] } -log = { version = "0.4.17", default-features = false } sp-core = { path = "../../../../primitives/core" } sp-state-machine = { path = "../../../../primitives/state-machine" } sp-trie = { path = "../../../../primitives/trie" } -trie-db = "0.24.0" +trie-db = "0.27.0" jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } # Substrate Dependencies sc-client-api = { version = "4.0.0-dev", path = "../../../../client/api" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } -sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } +sp-runtime = { version = "24.0.0", path = "../../../../primitives/runtime" } [dev-dependencies] serde_json = "1" diff --git a/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs b/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs index 2140ee884562..d1175fdea907 100644 --- a/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs +++ b/utils/frame/rpc/state-trie-migration-rpc/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/rpc/support/Cargo.toml b/utils/frame/rpc/support/Cargo.toml index d75d3a5af5da..032840d457c3 100644 --- a/utils/frame/rpc/support/Cargo.toml +++ b/utils/frame/rpc/support/Cargo.toml @@ -15,17 +15,17 @@ description = "Substrate RPC for FRAME's support" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } jsonrpsee = { version = "0.16.2", features = ["jsonrpsee-types"] } serde = "1" frame-support = { version = "4.0.0-dev", path = "../../../../frame/support" } sc-rpc-api = { version = "0.10.0-dev", path = "../../../../client/rpc-api" } -sp-storage = { version = "7.0.0", path = "../../../../primitives/storage" } +sp-storage = { version = "13.0.0", path = "../../../../primitives/storage" } [dev-dependencies] scale-info = "2.1.1" jsonrpsee = { version = "0.16.2", features = ["ws-client", "jsonrpsee-types"] } tokio = "1.22.0" -sp-core = { version = "7.0.0", path = "../../../../primitives/core" } -sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } +sp-core = { version = "21.0.0", path = "../../../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../../../primitives/runtime" } frame-system = { version = "4.0.0-dev", path = "../../../../frame/system" } diff --git a/utils/frame/rpc/support/src/lib.rs b/utils/frame/rpc/support/src/lib.rs index a13d4f707797..2d8e45cbfc69 100644 --- a/utils/frame/rpc/support/src/lib.rs +++ b/utils/frame/rpc/support/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,12 +40,9 @@ use sp_storage::{StorageData, StorageKey}; /// # use sp_runtime::{traits::{BlakeTwo256, IdentityLookup}, testing::Header}; /// # /// # construct_runtime!( -/// # pub enum TestRuntime where -/// # Block = frame_system::mocking::MockBlock, -/// # NodeBlock = frame_system::mocking::MockBlock, -/// # UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic, +/// # pub enum TestRuntime /// # { -/// # System: frame_system::{Pallet, Call, Config, Storage, Event}, +/// # System: frame_system::{Pallet, Call, Config, Storage, Event}, /// # Test: pallet_test::{Pallet, Storage}, /// # } /// # ); @@ -58,13 +55,12 @@ use sp_storage::{StorageData, StorageKey}; /// # type BlockLength = (); /// # type RuntimeOrigin = RuntimeOrigin; /// # type RuntimeCall = RuntimeCall; -/// # type Index = u64; -/// # type BlockNumber = u64; +/// # type Nonce = u64; /// # type Hash = Hash; /// # type Hashing = BlakeTwo256; /// # type AccountId = u64; /// # type Lookup = IdentityLookup; -/// # type Header = Header; +/// # type Block = frame_system::mocking::MockBlock; /// # type RuntimeEvent = RuntimeEvent; /// # type BlockHashCount = (); /// # type DbWeight = (); @@ -94,8 +90,7 @@ use sp_storage::{StorageData, StorageKey}; /// use frame_support::pallet_prelude::*; /// /// #[pallet::pallet] -/// #[pallet::generate_store(pub(super) trait Store)] -/// pub struct Pallet(PhantomData); +/// pub struct Pallet(_); /// /// #[pallet::config] /// pub trait Config: frame_system::Config {} diff --git a/utils/frame/rpc/system/Cargo.toml b/utils/frame/rpc/system/Cargo.toml index b6848ceb2911..f93f32ad64f2 100644 --- a/utils/frame/rpc/system/Cargo.toml +++ b/utils/frame/rpc/system/Cargo.toml @@ -13,7 +13,7 @@ readme = "README.md" targets = ["x86_64-unknown-linux-gnu"] [dependencies] -codec = { package = "parity-scale-codec", version = "3.2.2" } +codec = { package = "parity-scale-codec", version = "3.6.1" } jsonrpsee = { version = "0.16.2", features = ["client-core", "server", "macros"] } futures = "0.3.21" log = "0.4.17" @@ -23,12 +23,12 @@ sc-transaction-pool-api = { version = "4.0.0-dev", path = "../../../../client/tr sp-api = { version = "4.0.0-dev", path = "../../../../primitives/api" } sp-block-builder = { version = "4.0.0-dev", path = "../../../../primitives/block-builder" } sp-blockchain = { version = "4.0.0-dev", path = "../../../../primitives/blockchain" } -sp-core = { version = "7.0.0", path = "../../../../primitives/core" } -sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } +sp-core = { version = "21.0.0", path = "../../../../primitives/core" } +sp-runtime = { version = "24.0.0", path = "../../../../primitives/runtime" } [dev-dependencies] sc-transaction-pool = { version = "4.0.0-dev", path = "../../../../client/transaction-pool" } tokio = "1.22.0" assert_matches = "1.3.0" -sp-tracing = { version = "6.0.0", path = "../../../../primitives/tracing" } +sp-tracing = { version = "10.0.0", path = "../../../../primitives/tracing" } substrate-test-runtime-client = { version = "2.0.0", path = "../../../../test-utils/runtime/client" } diff --git a/utils/frame/rpc/system/src/lib.rs b/utils/frame/rpc/system/src/lib.rs index 72ad99e435f7..1eff71e3390a 100644 --- a/utils/frame/rpc/system/src/lib.rs +++ b/utils/frame/rpc/system/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,20 +32,20 @@ use sp_api::ApiExt; use sp_block_builder::BlockBuilder; use sp_blockchain::HeaderBackend; use sp_core::{hexdisplay::HexDisplay, Bytes}; -use sp_runtime::{generic::BlockId, legacy, traits}; +use sp_runtime::{legacy, traits}; pub use frame_system_rpc_runtime_api::AccountNonceApi; /// System RPC methods. #[rpc(client, server)] -pub trait SystemApi { +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; + 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"])] @@ -85,25 +85,24 @@ impl System { } #[async_trait] -impl - SystemApiServer<::Hash, AccountId, Index> for System +impl + SystemApiServer<::Hash, AccountId, Nonce> for System where C: sp_api::ProvideRuntimeApi, C: HeaderBackend, C: Send + Sync + 'static, - C::Api: AccountNonceApi, + C::Api: AccountNonceApi, C::Api: BlockBuilder, P: TransactionPool + 'static, Block: traits::Block, AccountId: Clone + Display + Codec + Send + 'static, - Index: Clone + Display + Codec + Send + traits::AtLeast32Bit + 'static, + Nonce: Clone + Display + Codec + Send + traits::AtLeast32Bit + 'static, { - async fn nonce(&self, account: AccountId) -> RpcResult { + async fn nonce(&self, account: AccountId) -> RpcResult { let api = self.client.runtime_api(); let best = self.client.info().best_hash; - let at = BlockId::hash(best); - let nonce = api.account_nonce(&at, account.clone()).map_err(|e| { + let nonce = api.account_nonce(best, account.clone()).map_err(|e| { CallError::Custom(ErrorObject::owned( Error::RuntimeError.into(), "Unable to query nonce.", @@ -120,9 +119,9 @@ where ) -> RpcResult { self.deny_unsafe.check_if_safe()?; let api = self.client.runtime_api(); - let at = BlockId::::hash(at.unwrap_or_else(|| + let best_hash = at.unwrap_or_else(|| // If the block hash is not supplied assume the best block. - self.client.info().best_hash)); + self.client.info().best_hash); let uxt: ::Extrinsic = Decode::decode(&mut &*extrinsic).map_err(|e| { @@ -134,7 +133,7 @@ where })?; let api_version = api - .api_version::>(&at) + .api_version::>(best_hash) .map_err(|e| { CallError::Custom(ErrorObject::owned( Error::RuntimeError.into(), @@ -146,13 +145,13 @@ where CallError::Custom(ErrorObject::owned( Error::RuntimeError.into(), "Unable to dry run extrinsic.", - Some(format!("Could not find `BlockBuilder` api for block `{:?}`.", at)), + Some(format!("Could not find `BlockBuilder` api for block `{:?}`.", best_hash)), )) })?; let result = if api_version < 6 { #[allow(deprecated)] - api.apply_extrinsic_before_version_6(&at, uxt) + api.apply_extrinsic_before_version_6(best_hash, uxt) .map(legacy::byte_sized_error::convert_to_latest) .map_err(|e| { CallError::Custom(ErrorObject::owned( @@ -162,7 +161,7 @@ where )) })? } else { - api.apply_extrinsic(&at, uxt).map_err(|e| { + api.apply_extrinsic(best_hash, uxt).map_err(|e| { CallError::Custom(ErrorObject::owned( Error::RuntimeError.into(), "Unable to dry run extrinsic.", @@ -177,11 +176,11 @@ where /// 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 +fn adjust_nonce(pool: &P, account: AccountId, nonce: Nonce) -> Nonce where P: TransactionPool, AccountId: Clone + std::fmt::Display + Encode, - Index: Clone + std::fmt::Display + Encode + traits::AtLeast32Bit + 'static, + Nonce: 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 @@ -220,6 +219,7 @@ mod tests { use jsonrpsee::{core::Error as JsonRpseeError, types::error::CallError}; use sc_transaction_pool::BasicPool; use sp_runtime::{ + generic::BlockId, transaction_validity::{InvalidTransaction, TransactionValidityError}, ApplyExtrinsicResult, }; @@ -243,7 +243,7 @@ mod tests { amount: 5, nonce, }; - t.into_signed_tx() + t.into_unchecked_extrinsic() }; // Populate the pool let ext0 = new_transaction(0); @@ -297,7 +297,7 @@ mod tests { amount: 5, nonce: 0, } - .into_signed_tx(); + .into_unchecked_extrinsic(); // when let bytes = accounts.dry_run(tx.encode().into(), None).await.expect("Call is successful"); @@ -325,13 +325,13 @@ mod tests { amount: 5, nonce: 100, } - .into_signed_tx(); + .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::Stale))); + assert_eq!(apply_res, Err(TransactionValidityError::Invalid(InvalidTransaction::Future))); } } diff --git a/utils/frame/try-runtime/cli/Cargo.toml b/utils/frame/try-runtime/cli/Cargo.toml index 8c14ddc2916b..41c022c904da 100644 --- a/utils/frame/try-runtime/cli/Cargo.toml +++ b/utils/frame/try-runtime/cli/Cargo.toml @@ -15,34 +15,41 @@ targets = ["x86_64-unknown-linux-gnu"] remote-externalities = { version = "0.10.0-dev", path = "../../remote-externalities", package = "frame-remote-externalities" } sc-cli = { version = "0.10.0-dev", path = "../../../../client/cli" } sc-executor = { version = "0.10.0-dev", path = "../../../../client/executor" } -sc-service = { version = "0.10.0-dev", default-features = false, path = "../../../../client/service" } -sp-core = { version = "7.0.0", path = "../../../../primitives/core" } -sp-externalities = { version = "0.13.0", path = "../../../../primitives/externalities" } -sp-io = { version = "7.0.0", path = "../../../../primitives/io" } -sp-keystore = { version = "0.13.0", path = "../../../../primitives/keystore" } -sp-runtime = { version = "7.0.0", path = "../../../../primitives/runtime" } +sp-consensus-aura = { path = "../../../../primitives/consensus/aura" } +sp-consensus-babe = { path = "../../../../primitives/consensus/babe" } +sp-core = { version = "21.0.0", path = "../../../../primitives/core" } +sp-externalities = { version = "0.19.0", path = "../../../../primitives/externalities" } +sp-inherents = { path = "../../../../primitives/inherents" } +sp-io = { version = "23.0.0", path = "../../../../primitives/io" } +sp-keystore = { version = "0.27.0", path = "../../../../primitives/keystore" } +sp-runtime = { version = "24.0.0", path = "../../../../primitives/runtime" } sp-rpc = { version = "6.0.0", path = "../../../../primitives/rpc" } -sp-state-machine = { version = "0.13.0", path = "../../../../primitives/state-machine" } -sp-version = { version = "5.0.0", path = "../../../../primitives/version" } +sp-state-machine = { version = "0.28.0", path = "../../../../primitives/state-machine" } +sp-timestamp = { path = "../../../../primitives/timestamp" } +sp-transaction-storage-proof = { path = "../../../../primitives/transaction-storage-proof" } +sp-version = { version = "22.0.0", path = "../../../../primitives/version" } sp-debug-derive = { path = "../../../../primitives/debug-derive" } sp-api = { path = "../../../../primitives/api" } -sp-weights = { version = "4.0.0", path = "../../../../primitives/weights" } +sp-weights = { version = "20.0.0", path = "../../../../primitives/weights" } frame-try-runtime = { optional = true, path = "../../../../frame/try-runtime" } substrate-rpc-client = { path = "../../rpc/client" } -clap = { version = "4.0.9", features = ["derive"] } +async-trait = "0.1.57" +clap = { version = "4.2.5", features = ["derive"] } hex = { version = "0.4.3", default-features = false } log = "0.4.17" -parity-scale-codec = "3.2.2" -serde = "1.0.136" +parity-scale-codec = "3.6.1" +serde = "1.0.163" serde_json = "1.0.85" -zstd = { version = "0.11.2", default-features = false } +zstd = { version = "0.12.3", default-features = false } [dev-dependencies] -tokio = "1.22.0" +assert_cmd = "2.0.10" +node-primitives = { path = "../../../../bin/node/primitives" } +regex = "1.7.3" +substrate-cli-test-utils = { path = "../../../../test-utils/cli" } +tempfile = "3.1.0" +tokio = "1.27.0" [features] -try-runtime = [ - "sp-debug-derive/force-debug", - "frame-try-runtime/try-runtime", -] +try-runtime = ["sp-debug-derive/force-debug", "frame-try-runtime/try-runtime"] diff --git a/utils/frame/try-runtime/cli/src/block_building_info.rs b/utils/frame/try-runtime/cli/src/block_building_info.rs new file mode 100644 index 000000000000..db24d06ef0a1 --- /dev/null +++ b/utils/frame/try-runtime/cli/src/block_building_info.rs @@ -0,0 +1,152 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::BlockT; +use parity_scale_codec::Encode; +use sc_cli::Result; +use sp_consensus_aura::{Slot, SlotDuration, AURA_ENGINE_ID}; +use sp_consensus_babe::{ + digests::{PreDigest, SecondaryPlainPreDigest}, + BABE_ENGINE_ID, +}; +use sp_inherents::{InherentData, InherentDataProvider}; +use sp_runtime::{Digest, DigestItem}; +use sp_timestamp::TimestampInherentData; + +/// Something that can create inherent data providers and pre-runtime digest. +/// +/// It is possible for the caller to provide custom arguments to the callee by setting the +/// `ExtraArgs` generic parameter. +/// +/// This module already provides some convenience implementation of this trait for closures. So, it +/// should not be required to implement it directly. +#[async_trait::async_trait] +pub trait BlockBuildingInfoProvider { + type InherentDataProviders: InherentDataProvider; + + async fn get_inherent_providers_and_pre_digest( + &self, + parent_hash: Block::Hash, + extra_args: ExtraArgs, + ) -> Result<(Self::InherentDataProviders, Vec)>; +} + +#[async_trait::async_trait] +impl BlockBuildingInfoProvider for F +where + Block: BlockT, + F: Fn(Block::Hash, ExtraArgs) -> Fut + Sync + Send, + Fut: std::future::Future)>> + Send + 'static, + IDP: InherentDataProvider + 'static, + ExtraArgs: Send + 'static, +{ + type InherentDataProviders = IDP; + + async fn get_inherent_providers_and_pre_digest( + &self, + parent: Block::Hash, + extra_args: ExtraArgs, + ) -> Result<(Self::InherentDataProviders, Vec)> { + (*self)(parent, extra_args).await + } +} + +/// Provides [`BlockBuildingInfoProvider`] implementation for chains that include timestamp inherent +/// and use Aura for a block production. +/// +/// It depends only on the expected block production frequency, i.e. `blocktime_millis`. +pub fn timestamp_with_aura_info( + blocktime_millis: u64, +) -> impl BlockBuildingInfoProvider> { + move |_, maybe_prev_info: Option<(InherentData, Digest)>| async move { + let timestamp_idp = match maybe_prev_info { + Some((inherent_data, _)) => sp_timestamp::InherentDataProvider::new( + inherent_data.timestamp_inherent_data().unwrap().unwrap() + blocktime_millis, + ), + None => sp_timestamp::InherentDataProvider::from_system_time(), + }; + + let slot = + Slot::from_timestamp(*timestamp_idp, SlotDuration::from_millis(blocktime_millis)); + let digest = vec![DigestItem::PreRuntime(AURA_ENGINE_ID, slot.encode())]; + + Ok((timestamp_idp, digest)) + } +} + +/// Provides [`BlockBuildingInfoProvider`] implementation for chains that include timestamp inherent +/// and use Babe for a block production. +/// +/// It depends only on the expected block production frequency, i.e. `blocktime_millis`. +pub fn timestamp_with_babe_info( + blocktime_millis: u64, +) -> impl BlockBuildingInfoProvider> { + move |_, maybe_prev_info: Option<(InherentData, Digest)>| async move { + let timestamp_idp = match maybe_prev_info { + Some((inherent_data, _)) => sp_timestamp::InherentDataProvider::new( + inherent_data.timestamp_inherent_data().unwrap().unwrap() + blocktime_millis, + ), + None => sp_timestamp::InherentDataProvider::from_system_time(), + }; + + let slot = + Slot::from_timestamp(*timestamp_idp, SlotDuration::from_millis(blocktime_millis)); + let slot_idp = sp_consensus_babe::inherents::InherentDataProvider::new(slot); + + let digest = vec![DigestItem::PreRuntime( + BABE_ENGINE_ID, + PreDigest::SecondaryPlain(SecondaryPlainPreDigest { slot, authority_index: 0 }) + .encode(), + )]; + + Ok(((slot_idp, timestamp_idp), digest)) + } +} + +/// Provides [`BlockBuildingInfoProvider`] implementation for chains that use: +/// - timestamp inherent, +/// - Babe for a block production (inherent + digest), +/// - uncles inherent, +/// - storage proof inherent +/// +/// It depends only on the expected block production frequency, i.e. `blocktime_millis`. +pub fn substrate_info( + blocktime_millis: u64, +) -> impl BlockBuildingInfoProvider> { + move |_, maybe_prev_info: Option<(InherentData, Digest)>| async move { + let timestamp_idp = match maybe_prev_info { + Some((inherent_data, _)) => sp_timestamp::InherentDataProvider::new( + inherent_data.timestamp_inherent_data().unwrap().unwrap() + blocktime_millis, + ), + None => sp_timestamp::InherentDataProvider::from_system_time(), + }; + + let slot = + Slot::from_timestamp(*timestamp_idp, SlotDuration::from_millis(blocktime_millis)); + let slot_idp = sp_consensus_babe::inherents::InherentDataProvider::new(slot); + + let storage_proof_idp = sp_transaction_storage_proof::InherentDataProvider::new(None); + + let digest = vec![DigestItem::PreRuntime( + BABE_ENGINE_ID, + PreDigest::SecondaryPlain(SecondaryPlainPreDigest { slot, authority_index: 0 }) + .encode(), + )]; + + Ok(((slot_idp, timestamp_idp, storage_proof_idp), digest)) + } +} diff --git a/utils/frame/try-runtime/cli/src/commands/create_snapshot.rs b/utils/frame/try-runtime/cli/src/commands/create_snapshot.rs index 8d6144f84c1e..102336d64421 100644 --- a/utils/frame/try-runtime/cli/src/commands/create_snapshot.rs +++ b/utils/frame/try-runtime/cli/src/commands/create_snapshot.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -41,7 +41,7 @@ pub(crate) async fn create_snapshot( ) -> sc_cli::Result<()> where Block: BlockT + serde::de::DeserializeOwned, - Block::Hash: FromStr + serde::de::DeserializeOwned, + Block::Hash: serde::de::DeserializeOwned, Block::Header: serde::de::DeserializeOwned, ::Err: Debug, NumberFor: FromStr, diff --git a/utils/frame/try-runtime/cli/src/commands/execute_block.rs b/utils/frame/try-runtime/cli/src/commands/execute_block.rs index e054c8ab8793..1f1b6ec7d9b9 100644 --- a/utils/frame/try-runtime/cli/src/commands/execute_block.rs +++ b/utils/frame/try-runtime/cli/src/commands/execute_block.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -68,7 +68,6 @@ pub struct ExecuteBlockCmd { impl ExecuteBlockCmd { fn block_ws_uri(&self) -> String where - Block::Hash: FromStr, ::Err: Debug, { match (&self.block_ws_uri, &self.state) { @@ -91,7 +90,6 @@ pub(crate) async fn execute_block( ) -> sc_cli::Result<()> where Block: BlockT + serde::de::DeserializeOwned, - Block::Hash: FromStr, ::Err: Debug, Block::Hash: serde::de::DeserializeOwned, Block::Header: serde::de::DeserializeOwned, @@ -133,7 +131,7 @@ where &executor, "TryRuntime_execute_block", &payload, - full_extensions(), + full_extensions(executor.clone()), shared.export_proof, )?; diff --git a/utils/frame/try-runtime/cli/src/commands/fast_forward.rs b/utils/frame/try-runtime/cli/src/commands/fast_forward.rs new file mode 100644 index 000000000000..ea3ae532ca74 --- /dev/null +++ b/utils/frame/try-runtime/cli/src/commands/fast_forward.rs @@ -0,0 +1,267 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{ + block_building_info::BlockBuildingInfoProvider, build_executor, full_extensions, + rpc_err_handler, state_machine_call, BlockT, LiveState, SharedParams, State, +}; +use parity_scale_codec::{Decode, Encode}; +use sc_cli::Result; +use sc_executor::{sp_wasm_interface::HostFunctions, WasmExecutor}; +use serde::de::DeserializeOwned; +use sp_core::H256; +use sp_inherents::{InherentData, InherentDataProvider}; +use sp_io::TestExternalities; +use sp_runtime::{ + traits::{Header, NumberFor, One}, + Digest, +}; +use std::{fmt::Debug, str::FromStr}; +use substrate_rpc_client::{ws_client, ChainApi}; + +/// Configurations of the [`crate::Command::FastForward`]. +#[derive(Debug, Clone, clap::Parser)] +pub struct FastForwardCmd { + /// How many blocks should be processed. If `None`, then blocks will be produced and processed + /// in a loop. + #[arg(long)] + n_blocks: Option, + + /// The state type to use. + #[command(subcommand)] + state: State, + + /// The ws uri from which to fetch the block. + /// + /// If `state` is `Live`, this is ignored. Otherwise, it must not be empty. + #[arg(long, value_parser = crate::parse::url)] + block_ws_uri: Option, + + /// Which try-state targets to execute when running this command. + /// + /// Expected values: + /// - `all` + /// - `none` + /// - A comma separated list of pallets, as per pallet names in `construct_runtime!()` (e.g. + /// `Staking, System`). + /// - `rr-[x]` where `[x]` is a number. Then, the given number of pallets are checked in a + /// round-robin fashion. + #[arg(long, default_value = "all")] + try_state: frame_try_runtime::TryStateSelect, +} + +impl FastForwardCmd { + fn block_ws_uri(&self) -> &str { + match self.state { + State::Live(LiveState { ref uri, .. }) => &uri, + _ => self + .block_ws_uri + .as_ref() + .expect("Either `--block-uri` must be provided, or state must be `live`"), + } + } +} + +/// Read the block number corresponding to `hash` with an RPC call to `ws_uri`. +async fn get_block_number( + hash: Block::Hash, + ws_uri: &str, +) -> Result> +where + Block::Header: DeserializeOwned, +{ + let rpc = ws_client(ws_uri).await?; + Ok(ChainApi::<(), Block::Hash, Block::Header, ()>::header(&rpc, Some(hash)) + .await + .map_err(rpc_err_handler) + .and_then(|maybe_header| maybe_header.ok_or("header_not_found").map(|h| *h.number()))?) +} + +/// Call `method` with `data` and return the result. `externalities` will not change. +async fn dry_run( + externalities: &TestExternalities, + executor: &WasmExecutor, + method: &'static str, + data: &[u8], +) -> Result { + let (_, result) = state_machine_call::( + externalities, + executor, + method, + data, + full_extensions(executor.clone()), + )?; + + Ok(::decode(&mut &*result)?) +} + +/// Call `method` with `data` and actually save storage changes to `externalities`. +async fn run( + externalities: &mut TestExternalities, + executor: &WasmExecutor, + method: &'static str, + data: &[u8], +) -> Result<()> { + let (mut changes, _) = state_machine_call::( + externalities, + executor, + method, + data, + full_extensions(executor.clone()), + )?; + + let storage_changes = changes.drain_storage_changes( + &externalities.backend, + &mut Default::default(), + externalities.state_version, + )?; + + externalities + .backend + .apply_transaction(storage_changes.transaction_storage_root, storage_changes.transaction); + + Ok(()) +} + +/// Produce next empty block. +async fn next_empty_block< + Block: BlockT, + HostFns: HostFunctions, + BBIP: BlockBuildingInfoProvider>, +>( + externalities: &mut TestExternalities, + executor: &WasmExecutor, + parent_height: NumberFor, + parent_hash: Block::Hash, + block_building_info_provider: &Option, + previous_block_building_info: Option<(InherentData, Digest)>, +) -> Result<(Block, Option<(InherentData, Digest)>)> { + let (maybe_inherent_data, pre_digest) = match &block_building_info_provider { + None => (None, Default::default()), + Some(bbip) => { + let (inherent_data_provider, pre_digest) = bbip + .get_inherent_providers_and_pre_digest(parent_hash, previous_block_building_info) + .await?; + let inherent_data = inherent_data_provider + .create_inherent_data() + .await + .map_err(|e| sc_cli::Error::Input(format!("I don't know how to convert {e:?}")))?; + + (Some(inherent_data), Digest { logs: pre_digest }) + }, + }; + + let header = Block::Header::new( + parent_height + One::one(), + Default::default(), + Default::default(), + parent_hash, + pre_digest.clone(), + ); + let mut extrinsics = >::new(); + + run::(externalities, executor, "Core_initialize_block", &header.encode()).await?; + + if let Some(ref inherent_data) = maybe_inherent_data { + extrinsics = dry_run::, Block, _>( + externalities, + executor, + "BlockBuilder_inherent_extrinsics", + &inherent_data.encode(), + ) + .await?; + } + + for xt in &extrinsics { + run::(externalities, executor, "BlockBuilder_apply_extrinsic", &xt.encode()) + .await?; + } + + let header = dry_run::( + externalities, + executor, + "BlockBuilder_finalize_block", + &[0u8; 0], + ) + .await?; + + run::(externalities, executor, "BlockBuilder_finalize_block", &[0u8; 0]).await?; + + Ok((Block::new(header, extrinsics), (maybe_inherent_data.map(|id| (id, pre_digest))))) +} + +pub(crate) async fn fast_forward( + shared: SharedParams, + command: FastForwardCmd, + block_building_info_provider: Option, +) -> Result<()> +where + Block: BlockT + DeserializeOwned, + Block::Header: DeserializeOwned, + ::Err: Debug, + NumberFor: FromStr, + as FromStr>::Err: Debug, + HostFns: HostFunctions, + BBIP: BlockBuildingInfoProvider>, +{ + let executor = build_executor::(&shared); + let ext = command.state.into_ext::(&shared, &executor, None, true).await?; + + let mut last_block_hash = ext.block_hash; + let mut last_block_number = + get_block_number::(last_block_hash, command.block_ws_uri()).await?; + let mut prev_block_building_info = None; + + let mut ext = ext.inner_ext; + + for _ in 1..=command.n_blocks.unwrap_or(u64::MAX) { + // We are saving state before we overwrite it while producing new block. + let backend = ext.as_backend(); + + log::info!("Producing new empty block at height {:?}", last_block_number + One::one()); + + let (next_block, new_block_building_info) = next_empty_block::( + &mut ext, + &executor, + last_block_number, + last_block_hash, + &block_building_info_provider, + prev_block_building_info, + ) + .await?; + + log::info!("Produced a new block: {:?}", next_block.header()); + + // And now we restore previous state. + ext.backend = backend; + + let state_root_check = true; + let signature_check = true; + let payload = + (next_block.clone(), state_root_check, signature_check, command.try_state.clone()) + .encode(); + run::(&mut ext, &executor, "TryRuntime_execute_block", &payload).await?; + + log::info!("Executed the new block"); + + prev_block_building_info = new_block_building_info; + last_block_hash = next_block.hash(); + last_block_number += One::one(); + } + + Ok(()) +} diff --git a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs index d887757b1646..cfcdb4654c8c 100644 --- a/utils/frame/try-runtime/cli/src/commands/follow_chain.rs +++ b/utils/frame/try-runtime/cli/src/commands/follow_chain.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -84,7 +84,6 @@ pub(crate) async fn follow_chain( ) -> sc_cli::Result<()> where Block: BlockT + DeserializeOwned, - Block::Hash: FromStr, Block::Header: DeserializeOwned, ::Err: Debug, NumberFor: FromStr, @@ -108,10 +107,12 @@ where .or_else(|e| { if matches!(e, substrate_rpc_client::Error::ParseError(_)) { log::error!( + target: LOG_TARGET, "failed to parse the block format of remote against the local \ - codebase. The block format has changed, and follow-chain cannot run in \ - this case. Try running this command in a branch of your codebase that has \ - the same block format as the remote chain. For now, we replace the block with an empty one" + codebase. The block format has changed, and follow-chain cannot run in \ + this case. Try running this command in a branch of your codebase that + has the same block format as the remote chain. For now, we replace the \ + block with an empty one." ); } Err(rpc_err_handler(e)) @@ -148,8 +149,10 @@ where state_ext, &executor, "TryRuntime_execute_block", - (block, command.state_root_check, command.try_state.clone()).encode().as_ref(), - full_extensions(), + (block, command.state_root_check, true, command.try_state.clone()) + .encode() + .as_ref(), + full_extensions(executor.clone()), shared .export_proof .as_ref() diff --git a/utils/frame/try-runtime/cli/src/commands/mod.rs b/utils/frame/try-runtime/cli/src/commands/mod.rs index ab0a066585f6..37902e676e3d 100644 --- a/utils/frame/try-runtime/cli/src/commands/mod.rs +++ b/utils/frame/try-runtime/cli/src/commands/mod.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,6 +17,7 @@ pub mod create_snapshot; pub mod execute_block; +pub mod fast_forward; pub mod follow_chain; pub mod offchain_worker; pub mod on_runtime_upgrade; diff --git a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs index 985b04bfb1ca..ac95384fb8aa 100644 --- a/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs +++ b/utils/frame/try-runtime/cli/src/commands/offchain_worker.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -46,7 +46,6 @@ pub struct OffchainWorkerCmd { impl OffchainWorkerCmd { fn header_ws_uri(&self) -> String where - Block::Hash: FromStr, ::Err: Debug, { match (&self.header_ws_uri, &self.state) { @@ -70,7 +69,6 @@ pub(crate) async fn offchain_worker( where Block: BlockT + serde::de::DeserializeOwned, Block::Header: serde::de::DeserializeOwned, - Block::Hash: FromStr, ::Err: Debug, NumberFor: FromStr, as FromStr>::Err: Debug, @@ -97,7 +95,7 @@ where &executor, "OffchainWorkerApi_offchain_worker", &payload, - full_extensions(), + full_extensions(executor.clone()), )?; Ok(()) diff --git a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs index 61b5847ed8ff..67988a3d1aad 100644 --- a/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs +++ b/utils/frame/try-runtime/cli/src/commands/on_runtime_upgrade.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,16 +32,17 @@ pub struct OnRuntimeUpgradeCmd { /// Select which optional checks to perform. Selects all when no value is given. /// - /// - `none`: Perform no checks (default when the arg is not present). - /// - `all`: Perform all checks (default when the arg is present). - /// - `pre-and-post`: Perform pre- and post-upgrade checks. + /// - `none`: Perform no checks. + /// - `all`: Perform all checks (default when --checks is present with no value). + /// - `pre-and-post`: Perform pre- and post-upgrade checks (default when the arg is not + /// present). /// - `try-state`: Perform the try-state checks. /// /// Performing any checks will potentially invalidate the measured PoV/Weight. // NOTE: The clap attributes make it backwards compatible with the previous `--checks` flag. #[clap(long, - default_value = "None", - default_missing_value = "All", + default_value = "pre-and-post", + default_missing_value = "all", num_args = 0..=1, require_equals = true, verbatim_doc_comment)] @@ -54,7 +55,6 @@ pub(crate) async fn on_runtime_upgrade( ) -> sc_cli::Result<()> where Block: BlockT + serde::de::DeserializeOwned, - Block::Hash: FromStr, ::Err: Debug, Block::Header: serde::de::DeserializeOwned, NumberFor: FromStr, diff --git a/utils/frame/try-runtime/cli/src/lib.rs b/utils/frame/try-runtime/cli/src/lib.rs index dea20febb9c5..fbc55ad1dce6 100644 --- a/utils/frame/try-runtime/cli/src/lib.rs +++ b/utils/frame/try-runtime/cli/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,9 +29,9 @@ //! //! Some resources about the above: //! -//! 1. +//! 1. //! 2. -//! 3. +//! 3. //! //! --- //! @@ -97,18 +97,6 @@ //! > If anything, in most cases, we expect spec-versions to NOT match, because try-runtime is all //! > about testing unreleased runtimes. //! -//! ## Note on nodes that respond to `try-runtime` requests. -//! -//! There are a number of flags that need to be preferably set on a running node in order to work -//! well with try-runtime's expensive RPC queries: -//! -//! - set `--rpc-max-response-size 1000` and -//! - `--rpc-max-request-size 1000` to ensure connections are not dropped in case the state is -//! large. -//! - set `--rpc-cors all` to ensure ws connections can come through. -//! -//! Note that *none* of the try-runtime operations need unsafe RPCs. -//! //! ## Note on signature and state-root checks //! //! All of the commands calling into `TryRuntime_execute_block` ([`Command::ExecuteBlock`] and @@ -133,12 +121,11 @@ //! given the right flag: //! //! ```ignore -//! -//! #[cfg(feature = try-runtime)] -//! fn pre_upgrade() -> Result, &'static str> {} +//! #[cfg(feature = "try-runtime")] +//! fn pre_upgrade() -> Result, TryRuntimeError> {} //! -//! #[cfg(feature = try-runtime)] -//! fn post_upgrade(state: Vec) -> Result<(), &'static str> {} +//! #[cfg(feature = "try-runtime")] +//! fn post_upgrade(state: Vec) -> Result<(), TryRuntimeError> {} //! ``` //! //! (The pallet macro syntax will support this simply as a part of `#[pallet::hooks]`). @@ -152,9 +139,9 @@ //! //! Similarly, each pallet can expose a function in `#[pallet::hooks]` section as follows: //! -//! ``` -//! #[cfg(feature = try-runtime)] -//! fn try_state(_) -> Result<(), &'static str> {} +//! ```ignore +//! #[cfg(feature = "try-runtime")] +//! fn try_state(_: BlockNumber) -> Result<(), TryRuntimeError> {} //! ``` //! //! which is called on numerous code paths in the try-runtime tool. These checks should ensure that @@ -261,7 +248,7 @@ //! # assuming there's `./substrate --dev --tmp --ws-port 9999` or similar running. //! ./substrate-try-runtime \ //! try-runtime \ -//! --runtime kitchensink_runtime.wasm \ +//! --runtime runtime-try-runtime.wasm \ //! -lruntime=debug \ //! on-runtime-upgrade \ //! live --uri ws://localhost:9999 @@ -273,7 +260,7 @@ //! ```bash //! ./substrate-try-runtime \ //! try-runtime \ -//! --runtime kitchensink_runtime.wasm \ +//! --runtime runtime-try-runtime.wasm \ //! -lruntime=debug \ //! on-runtime-upgrade \ //! live --uri ws://localhost:9999 \ @@ -308,7 +295,7 @@ //! Then, we can use it to have the same command as before, `on-runtime-upgrade` //! //! ```bash -//! try-runtime \ +//! ./substrate-try-runtime try-runtime \ //! --runtime runtime-try-runtime.wasm \ //! -lruntime=debug \ //! on-runtime-upgrade \ @@ -322,7 +309,7 @@ //! --runtime runtime-try-runtime.wasm \ //! -lruntime=debug \ //! execute-block live \ -//! --uri ws://localhost:999 +//! --uri ws://localhost:9999 //! ``` //! //! This can still be customized at a given block with `--at`. If you want to use a snapshot, you @@ -333,15 +320,22 @@ //! //! ```bash //! ./substrate-try-runtime try-runtime \ -//! --runtime runtime-try-runtime.wasm \ -//! -lruntime=debug \ -//! execute-block live \ -//! --try-state System,Staking \ -//! --uri ws://localhost:999 +//! --runtime runtime-try-runtime.wasm \ +//! -lruntime=debug \ +//! execute-block \ +//! --try-state System,Staking \ +//! live \ +//! --uri ws://localhost:9999 \ +//! --pallet System Staking //! ``` //! -//! Will only run the `try-state` of the two given pallets. See -//! [`frame_try_runtime::TryStateSelect`] for more information. +//! Will only run the `try-state` of the two given pallets. When running `try-state` against +//! some real chain data it can take a long time for the command to execute since it has to +//! query all the key-value pairs. In scenarios like above where we only want to run the +//! `try-state` for some specific pallets, we can use the `--pallet` option to specify from +//! which pallets we want to query the state. This will greatly decrease the execution time. +//! +//! See [`frame_try_runtime::TryStateSelect`] for more information. //! //! * Follow our live chain's blocks using `follow-chain`, whilst running the try-state of 3 pallets //! in a round robin fashion @@ -358,16 +352,20 @@ #![cfg(feature = "try-runtime")] +use crate::block_building_info::BlockBuildingInfoProvider; use parity_scale_codec::Decode; use remote_externalities::{ Builder, Mode, OfflineConfig, OnlineConfig, RemoteExternalities, SnapshotConfig, TestExternalities, }; use sc_cli::{ - CliConfiguration, RuntimeVersion, WasmExecutionMethod, WasmtimeInstantiationStrategy, - DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, DEFAULT_WASM_EXECUTION_METHOD, + execution_method_from_cli, CliConfiguration, RuntimeVersion, WasmExecutionMethod, + WasmtimeInstantiationStrategy, DEFAULT_WASMTIME_INSTANTIATION_STRATEGY, + DEFAULT_WASM_EXECUTION_METHOD, +}; +use sc_executor::{ + sp_wasm_interface::HostFunctions, HeapAllocStrategy, WasmExecutor, DEFAULT_HEAP_ALLOC_STRATEGY, }; -use sc_executor::{sp_wasm_interface::HostFunctions, WasmExecutor}; use sp_api::HashT; use sp_core::{ hexdisplay::HexDisplay, @@ -376,20 +374,21 @@ use sp_core::{ OffchainDbExt, OffchainWorkerExt, TransactionPoolExt, }, storage::well_known_keys, - testing::TaskExecutor, - traits::{ReadRuntimeVersion, TaskExecutorExt}, + traits::{CallContext, ReadRuntimeVersion, ReadRuntimeVersionExt}, twox_128, H256, }; use sp_externalities::Extensions; -use sp_keystore::{testing::KeyStore, KeystoreExt}; +use sp_inherents::InherentData; +use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; use sp_runtime::{ traits::{BlakeTwo256, Block as BlockT, NumberFor}, - DeserializeOwned, + DeserializeOwned, Digest, }; use sp_state_machine::{CompactProof, OverlayedChanges, StateMachine, TrieBackendBuilder}; use sp_version::StateVersion; use std::{fmt::Debug, path::PathBuf, str::FromStr}; +pub mod block_building_info; pub mod commands; pub(crate) mod parse; pub(crate) const LOG_TARGET: &str = "try-runtime::cli"; @@ -445,6 +444,15 @@ pub enum Command { /// tested has remained the same, otherwise block decoding might fail. FollowChain(commands::follow_chain::FollowChainCmd), + /// Produce a series of empty, consecutive blocks and execute them one-by-one. + /// + /// To compare it with [`Command::FollowChain`]: + /// - we don't have the delay of the original blocktime (for Polkadot 6s), but instead, we + /// execute every block immediately + /// - the only data that will be put into blocks are pre-runtime digest items and inherent + /// extrinsics; both things should be defined in your node CLI handling level + FastForward(commands::fast_forward::FastForwardCmd), + /// Create a new snapshot file. CreateSnapshot(commands::create_snapshot::CreateSnapshotCmd), } @@ -587,8 +595,6 @@ pub struct LiveState { #[derive(Debug, Clone, clap::Subcommand)] pub enum State { /// Use a state snapshot as the source of runtime state. - /// - /// This can be crated by passing a value to [`State::Live::snapshot_path`]. Snap { #[arg(short, long)] snapshot_path: PathBuf, @@ -612,9 +618,7 @@ impl State { try_runtime_check: bool, ) -> sc_cli::Result> where - Block::Hash: FromStr, Block::Header: DeserializeOwned, - Block::Hash: DeserializeOwned, ::Err: Debug, { let builder = match self { @@ -719,16 +723,19 @@ impl State { } impl TryRuntimeCmd { - pub async fn run(&self) -> sc_cli::Result<()> + pub async fn run( + &self, + block_building_info_provider: Option, + ) -> sc_cli::Result<()> where Block: BlockT + DeserializeOwned, Block::Header: DeserializeOwned, - Block::Hash: FromStr, ::Err: Debug, as FromStr>::Err: Debug, as TryInto>::Error: Debug, NumberFor: FromStr, HostFns: HostFunctions, + BBIP: BlockBuildingInfoProvider>, { match &self.command { Command::OnRuntimeUpgrade(ref cmd) => @@ -755,6 +762,13 @@ impl TryRuntimeCmd { cmd.clone(), ) .await, + Command::FastForward(cmd) => + commands::fast_forward::fast_forward::( + self.shared.clone(), + cmd.clone(), + block_building_info_provider, + ) + .await, Command::CreateSnapshot(cmd) => commands::create_snapshot::create_snapshot::( self.shared.clone(), @@ -781,7 +795,6 @@ impl CliConfiguration for TryRuntimeCmd { /// Get the hash type of the generic `Block` from a `hash_str`. pub(crate) fn hash_of(hash_str: &str) -> sc_cli::Result where - Block::Hash: FromStr, ::Err: Debug, { hash_str @@ -790,31 +803,34 @@ where } /// Build all extensions that we typically use. -pub(crate) fn full_extensions() -> Extensions { +pub(crate) fn full_extensions(wasm_executor: WasmExecutor) -> Extensions { let mut extensions = Extensions::default(); - extensions.register(TaskExecutorExt::new(TaskExecutor::new())); let (offchain, _offchain_state) = TestOffchainExt::new(); let (pool, _pool_state) = TestTransactionPoolExt::new(); + let keystore = MemoryKeystore::new(); extensions.register(OffchainDbExt::new(offchain.clone())); extensions.register(OffchainWorkerExt::new(offchain)); - extensions.register(KeystoreExt(std::sync::Arc::new(KeyStore::new()))); + extensions.register(KeystoreExt::new(keystore)); extensions.register(TransactionPoolExt::new(pool)); + extensions.register(ReadRuntimeVersionExt::new(wasm_executor)); extensions } +/// Build wasm executor by default config. pub(crate) fn build_executor(shared: &SharedParams) -> WasmExecutor { - let heap_pages = shared.heap_pages.or(Some(2048)); - let max_runtime_instances = 8; - let runtime_cache_size = 2; - - WasmExecutor::new( - sc_executor::WasmExecutionMethod::Interpreted, - heap_pages, - max_runtime_instances, - None, - runtime_cache_size, - ) + let heap_pages = shared + .heap_pages + .map_or(DEFAULT_HEAP_ALLOC_STRATEGY, |p| HeapAllocStrategy::Static { extra_pages: p as _ }); + + WasmExecutor::builder() + .with_execution_method(execution_method_from_cli( + shared.wasm_method, + shared.wasmtime_instantiation_strategy, + )) + .with_onchain_heap_alloc_strategy(heap_pages) + .with_offchain_heap_alloc_strategy(heap_pages) + .build() } /// Ensure that the given `ext` is compiled with `try-runtime` @@ -842,7 +858,7 @@ pub(crate) fn state_machine_call( executor: &WasmExecutor, method: &'static str, data: &[u8], - extensions: Extensions, + mut extensions: Extensions, ) -> sc_cli::Result<(OverlayedChanges, Vec)> { let mut changes = Default::default(); let encoded_results = StateMachine::new( @@ -851,11 +867,11 @@ pub(crate) fn state_machine_call( executor, method, data, - extensions, + &mut extensions, &sp_state_machine::backend::BackendRuntimeCode::new(&ext.backend).runtime_code()?, - sp_core::testing::TaskExecutor::new(), + CallContext::Offchain, ) - .execute(sp_state_machine::ExecutionStrategy::AlwaysWasm) + .execute() .map_err(|e| format!("failed to execute '{}': {}", method, e)) .map_err::(Into::into)?; @@ -871,7 +887,7 @@ pub(crate) fn state_machine_call_with_proof, method: &'static str, data: &[u8], - extensions: Extensions, + mut extensions: Extensions, maybe_export_proof: Option, ) -> sc_cli::Result<(OverlayedChanges, Vec)> { use parity_scale_codec::Encode; @@ -890,11 +906,11 @@ pub(crate) fn state_machine_call_with_proof(Into::into)?; diff --git a/utils/frame/try-runtime/cli/src/parse.rs b/utils/frame/try-runtime/cli/src/parse.rs index 257a99566979..336a36baf241 100644 --- a/utils/frame/try-runtime/cli/src/parse.rs +++ b/utils/frame/try-runtime/cli/src/parse.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2021-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/frame/try-runtime/cli/tests/create_snapshot.rs b/utils/frame/try-runtime/cli/tests/create_snapshot.rs new file mode 100644 index 000000000000..59a36fd702d7 --- /dev/null +++ b/utils/frame/try-runtime/cli/tests/create_snapshot.rs @@ -0,0 +1,95 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +#![cfg(unix)] +#![cfg(feature = "try-runtime")] + +use assert_cmd::cargo::cargo_bin; +use node_primitives::Hash; +use regex::Regex; +use remote_externalities::{Builder, Mode, OfflineConfig, SnapshotConfig}; +use sp_runtime::testing::{Block as RawBlock, ExtrinsicWrapper}; +use std::{ + path::{Path, PathBuf}, + process, + time::Duration, +}; +use substrate_cli_test_utils as common; +use tokio::process::{Child, Command}; + +type Block = RawBlock>; + +#[tokio::test] +async fn create_snapshot_works() { + // Build substrate so binaries used in the test use the latest code. + common::build_substrate(&["--features=try-runtime"]); + + let temp_dir = tempfile::Builder::new() + .prefix("try-runtime-cli-test-dir") + .tempdir() + .expect("Failed to create a tempdir"); + let snap_file_path = temp_dir.path().join("snapshot.snap"); + + common::run_with_timeout(Duration::from_secs(60), async move { + fn create_snapshot(ws_url: &str, snap_file: &PathBuf, at: Hash) -> Child { + Command::new(cargo_bin("substrate")) + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()) + .args(&["try-runtime", "--runtime=existing"]) + .args(&["create-snapshot", format!("--uri={}", ws_url).as_str()]) + .arg(snap_file) + .args(&["--at", format!("{:?}", at).as_str()]) + .kill_on_drop(true) + .spawn() + .unwrap() + } + + // Start a node and wait for it to begin finalizing blocks + let mut node = common::KillChildOnDrop(common::start_node()); + let ws_url = common::extract_info_from_output(node.stderr.take().unwrap()).0.ws_url; + common::wait_n_finalized_blocks(3, &ws_url).await; + + let block_number = 2; + let block_hash = common::block_hash(block_number, &ws_url).await.unwrap(); + + // Try to create a snapshot. + let mut snapshot_creation = create_snapshot(&ws_url, &snap_file_path, block_hash); + + let re = Regex::new(r#".*writing snapshot of (\d+) bytes to .*"#).unwrap(); + let matched = + common::wait_for_stream_pattern_match(snapshot_creation.stderr.take().unwrap(), re) + .await; + + // Assert that the snapshot creation succeded. + assert!(matched.is_ok(), "Failed to create snapshot"); + + let snapshot_is_on_disk = Path::new(&snap_file_path).exists(); + assert!(snapshot_is_on_disk, "Snapshot was not written to disk"); + + // Try and load the snapshot we have created by running `create-snapshot`. + let snapshot_loading_result = Builder::::new() + .mode(Mode::Offline(OfflineConfig { + state_snapshot: SnapshotConfig { path: snap_file_path }, + })) + .build() + .await; + + assert!(snapshot_loading_result.is_ok(), "Snapshot couldn't be loaded"); + }) + .await; +} diff --git a/utils/frame/try-runtime/cli/tests/execute_block.rs b/utils/frame/try-runtime/cli/tests/execute_block.rs new file mode 100644 index 000000000000..025b753be778 --- /dev/null +++ b/utils/frame/try-runtime/cli/tests/execute_block.rs @@ -0,0 +1,70 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +#![cfg(unix)] +#![cfg(feature = "try-runtime")] + +use assert_cmd::cargo::cargo_bin; +use node_primitives::Hash; +use regex::Regex; +use std::{process, time::Duration}; +use substrate_cli_test_utils as common; +use tokio::process::{Child, Command}; + +#[tokio::test] +async fn block_execution_works() { + // Build substrate so binaries used in the test use the latest code. + common::build_substrate(&["--features=try-runtime"]); + + common::run_with_timeout(Duration::from_secs(60), async move { + fn execute_block(ws_url: &str, at: Hash) -> Child { + Command::new(cargo_bin("substrate")) + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()) + .args(&["try-runtime", "--runtime=existing"]) + .args(&["execute-block"]) + .args(&["live", format!("--uri={}", ws_url).as_str()]) + .args(&["--at", format!("{:?}", at).as_str()]) + .kill_on_drop(true) + .spawn() + .unwrap() + } + + // Start a node and wait for it to begin finalizing blocks + let mut node = common::KillChildOnDrop(common::start_node()); + let ws_url = common::extract_info_from_output(node.stderr.take().unwrap()).0.ws_url; + common::wait_n_finalized_blocks(3, &ws_url).await; + + let block_number = 1; + let block_hash = common::block_hash(block_number, &ws_url).await.unwrap(); + + // Try to execute the block. + let mut block_execution = execute_block(&ws_url, block_hash); + + // The execute-block command is actually executing the next block. + let expected_output = + format!(r#".*Block #{} successfully executed"#, block_number.saturating_add(1)); + let re = Regex::new(expected_output.as_str()).unwrap(); + let matched = + common::wait_for_stream_pattern_match(block_execution.stderr.take().unwrap(), re).await; + + // Assert that the block-execution process has executed a block. + assert!(matched.is_ok()); + }) + .await; +} diff --git a/utils/frame/try-runtime/cli/tests/follow_chain.rs b/utils/frame/try-runtime/cli/tests/follow_chain.rs new file mode 100644 index 000000000000..63b8b4c386b0 --- /dev/null +++ b/utils/frame/try-runtime/cli/tests/follow_chain.rs @@ -0,0 +1,63 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 . + +#![cfg(unix)] +#![cfg(feature = "try-runtime")] + +use assert_cmd::cargo::cargo_bin; +use regex::Regex; +use std::{ + process::{self}, + time::Duration, +}; +use substrate_cli_test_utils as common; +use tokio::process::{Child, Command}; + +#[tokio::test] +async fn follow_chain_works() { + // Build substrate so binaries used in the test use the latest code. + common::build_substrate(&["--features=try-runtime"]); + + common::run_with_timeout(Duration::from_secs(60), async move { + fn start_follow(ws_url: &str) -> Child { + Command::new(cargo_bin("substrate")) + .stdout(process::Stdio::piped()) + .stderr(process::Stdio::piped()) + .args(&["try-runtime", "--runtime=existing"]) + .args(&["follow-chain", format!("--uri={}", ws_url).as_str()]) + .kill_on_drop(true) + .spawn() + .unwrap() + } + + // Start a node and wait for it to begin finalizing blocks + let mut node = common::KillChildOnDrop(common::start_node()); + let ws_url = common::extract_info_from_output(node.stderr.take().unwrap()).0.ws_url; + common::wait_n_finalized_blocks(1, &ws_url).await; + + // Kick off the follow-chain process and wait for it to process at least 3 blocks. + let mut follow = start_follow(&ws_url); + let re = Regex::new(r#".*executed block ([3-9]|[1-9]\d+).*"#).unwrap(); + let matched = + common::wait_for_stream_pattern_match(follow.stderr.take().unwrap(), re).await; + + // Assert that the follow-chain process has followed at least 3 blocks. + assert!(matched.is_ok()); + }) + .await; +} diff --git a/utils/prometheus/src/lib.rs b/utils/prometheus/src/lib.rs index 3ea9d45d48b1..581666635ab5 100644 --- a/utils/prometheus/src/lib.rs +++ b/utils/prometheus/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/prometheus/src/sourced.rs b/utils/prometheus/src/sourced.rs index 9f52d1eff47c..8adaefa09d28 100644 --- a/utils/prometheus/src/sourced.rs +++ b/utils/prometheus/src/sourced.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2020-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/utils/wasm-builder/Cargo.toml b/utils/wasm-builder/Cargo.toml index 84693f5eec04..79ff4d19ac67 100644 --- a/utils/wasm-builder/Cargo.toml +++ b/utils/wasm-builder/Cargo.toml @@ -15,11 +15,12 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] ansi_term = "0.12.1" build-helper = "0.1.1" -cargo_metadata = "0.14.2" +cargo_metadata = "0.15.4" strum = { version = "0.24.1", features = ["derive"] } tempfile = "3.1.0" -toml = "0.5.4" +toml = "0.7.3" walkdir = "2.3.2" sp-maybe-compressed-blob = { version = "4.1.0-dev", path = "../../primitives/maybe-compressed-blob" } filetime = "0.2.16" -wasm-opt = "0.111" \ No newline at end of file +wasm-opt = "0.112" +parity-wasm = "0.45" diff --git a/utils/wasm-builder/README.md b/utils/wasm-builder/README.md index a10611cc26a0..b1ccb1b35b10 100644 --- a/utils/wasm-builder/README.md +++ b/utils/wasm-builder/README.md @@ -77,8 +77,13 @@ Wasm builder requires the following prerequisites for building the Wasm binary: - rust nightly + `wasm32-unknown-unknown` toolchain -If a specific rust nightly is installed with `rustup`, it is important that the wasm target is installed -as well. For example if installing the rust nightly from 20.02.2020 using `rustup install nightly-2020-02-20`, -the wasm target needs to be installed as well `rustup target add wasm32-unknown-unknown --toolchain nightly-2020-02-20`. +or + +- rust stable and version at least 1.68.0 + `wasm32-unknown-unknown` toolchain + +If a specific rust is installed with `rustup`, it is important that the wasm target is +installed as well. For example if installing the rust from 20.02.2020 using `rustup +install nightly-2020-02-20`, the wasm target needs to be installed as well `rustup target add +wasm32-unknown-unknown --toolchain nightly-2020-02-20`. License: Apache-2.0 diff --git a/utils/wasm-builder/src/builder.rs b/utils/wasm-builder/src/builder.rs index 81a869396818..208b56077669 100644 --- a/utils/wasm-builder/src/builder.rs +++ b/utils/wasm-builder/src/builder.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -48,6 +48,7 @@ impl WasmBuilderSelectProject { file_name: None, project_cargo_toml: get_manifest_dir().join("Cargo.toml"), features_to_enable: Vec::new(), + disable_runtime_version_section_check: false, } } @@ -63,6 +64,7 @@ impl WasmBuilderSelectProject { file_name: None, project_cargo_toml: path, features_to_enable: Vec::new(), + disable_runtime_version_section_check: false, }) } else { Err("Project path must point to the `Cargo.toml` of the project") @@ -78,8 +80,8 @@ impl WasmBuilderSelectProject { /// /// 1. Call [`WasmBuilder::new`] to create a new builder. /// 2. Select the project to build using the methods of [`WasmBuilderSelectProject`]. -/// 3. Set additional `RUST_FLAGS` or a different name for the file containing the WASM code -/// using methods of [`WasmBuilder`]. +/// 3. Set additional `RUST_FLAGS` or a different name for the file containing the WASM code using +/// methods of [`WasmBuilder`]. /// 4. Build the WASM binary using [`Self::build`]. pub struct WasmBuilder { /// Flags that should be appended to `RUST_FLAGS` env variable. @@ -93,6 +95,8 @@ pub struct WasmBuilder { project_cargo_toml: PathBuf, /// Features that should be enabled when building the wasm binary. features_to_enable: Vec, + /// Should the builder not check that the `runtime_version` section exists in the wasm binary? + disable_runtime_version_section_check: bool, } impl WasmBuilder { @@ -143,6 +147,17 @@ impl WasmBuilder { self } + /// Disable the check for the `runtime_version` wasm section. + /// + /// By default the `wasm-builder` will ensure that the `runtime_version` section will + /// exists in the build wasm binary. This `runtime_version` section is used to get the + /// `RuntimeVersion` without needing to call into the wasm binary. However, for some + /// use cases (like tests) you may want to disable this check. + pub fn disable_runtime_version_section_check(mut self) -> Self { + self.disable_runtime_version_section_check = true; + self + } + /// Build the WASM binary. pub fn build(self) { let out_dir = PathBuf::from(env::var("OUT_DIR").expect("`OUT_DIR` is set by cargo!")); @@ -165,6 +180,7 @@ impl WasmBuilder { self.rust_flags.into_iter().map(|f| format!("{} ", f)).collect(), self.features_to_enable, self.file_name, + !self.disable_runtime_version_section_check, ); // As last step we need to generate our `rerun-if-changed` stuff. If a build fails, we don't @@ -215,7 +231,7 @@ fn generate_rerun_if_changed_instructions() { /// The current project is determined by using the `CARGO_MANIFEST_DIR` environment variable. /// /// `file_name` - The name + path of the file being generated. The file contains the -/// constant `WASM_BINARY`, which contains the built WASM binary. +/// constant `WASM_BINARY`, which contains the built wasm binary. /// /// `project_cargo_toml` - The path to the `Cargo.toml` of the project that should be built. /// @@ -224,14 +240,17 @@ fn generate_rerun_if_changed_instructions() { /// `features_to_enable` - Features that should be enabled for the project. /// /// `wasm_binary_name` - The optional wasm binary name that is extended with -/// /// `.compact.compressed.wasm`. If `None`, the project name will be used. +/// +/// `check_for_runtime_version_section` - Should the wasm binary be checked for the +/// `runtime_version` section? fn build_project( file_name: PathBuf, project_cargo_toml: PathBuf, default_rustflags: String, features_to_enable: Vec, wasm_binary_name: Option, + check_for_runtime_version_section: bool, ) { let cargo_cmd = match crate::prerequisites::check() { Ok(cmd) => cmd, @@ -247,6 +266,7 @@ fn build_project( cargo_cmd, features_to_enable, wasm_binary_name, + check_for_runtime_version_section, ); let (wasm_binary, wasm_binary_bloaty) = if let Some(wasm_binary) = wasm_binary { diff --git a/utils/wasm-builder/src/lib.rs b/utils/wasm-builder/src/lib.rs index fc86a06170a5..c9011f97be71 100644 --- a/utils/wasm-builder/src/lib.rs +++ b/utils/wasm-builder/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -100,8 +100,12 @@ //! //! - rust nightly + `wasm32-unknown-unknown` toolchain //! -//! If a specific rust nightly is installed with `rustup`, it is important that the wasm target is -//! installed as well. For example if installing the rust nightly from 20.02.2020 using `rustup +//! or +//! +//! - rust stable and version at least 1.68.0 + `wasm32-unknown-unknown` toolchain +//! +//! If a specific rust is installed with `rustup`, it is important that the wasm target is +//! installed as well. For example if installing the rust from 20.02.2020 using `rustup //! install nightly-2020-02-20`, the wasm target needs to be installed as well `rustup target add //! wasm32-unknown-unknown --toolchain nightly-2020-02-20`. @@ -111,9 +115,11 @@ use std::{ path::{Path, PathBuf}, process::Command, }; +use version::Version; mod builder; mod prerequisites; +mod version; mod wasm_project; pub use builder::{WasmBuilder, WasmBuilderSelectProject}; @@ -172,53 +178,59 @@ fn copy_file_if_changed(src: PathBuf, dst: PathBuf) { } } -/// Get a cargo command that compiles with nightly -fn get_nightly_cargo() -> CargoCommand { +/// Get a cargo command that should be used to invoke the compilation. +fn get_cargo_command() -> CargoCommand { let env_cargo = CargoCommand::new(&env::var("CARGO").expect("`CARGO` env variable is always set by cargo")); let default_cargo = CargoCommand::new("cargo"); - let rustup_run_nightly = CargoCommand::new_with_args("rustup", &["run", "nightly", "cargo"]); let wasm_toolchain = env::var(WASM_BUILD_TOOLCHAIN).ok(); // First check if the user requested a specific toolchain - if let Some(cmd) = wasm_toolchain.and_then(|t| get_rustup_nightly(Some(t))) { + if let Some(cmd) = + wasm_toolchain.map(|t| CargoCommand::new_with_args("rustup", &["run", &t, "cargo"])) + { cmd - } else if env_cargo.is_nightly() { + } else if env_cargo.supports_substrate_wasm_env() { env_cargo - } else if default_cargo.is_nightly() { + } else if default_cargo.supports_substrate_wasm_env() { default_cargo - } else if rustup_run_nightly.is_nightly() { - rustup_run_nightly } else { - // If no command before provided us with a nightly compiler, we try to search one - // with rustup. If that fails as well, we return the default cargo and let the prequisities - // check fail. - get_rustup_nightly(None).unwrap_or(default_cargo) + // If no command before provided us with a cargo that supports our Substrate wasm env, we + // try to search one with rustup. If that fails as well, we return the default cargo and let + // the prequisities check fail. + get_rustup_command().unwrap_or(default_cargo) } } -/// Get a nightly from rustup. If `selected` is `Some(_)`, a `CargoCommand` using the given -/// nightly is returned. -fn get_rustup_nightly(selected: Option) -> Option { +/// Get the newest rustup command that supports our Substrate wasm env. +/// +/// Stable versions are always favored over nightly versions even if the nightly versions are +/// newer. +fn get_rustup_command() -> Option { let host = format!("-{}", env::var("HOST").expect("`HOST` is always set by cargo")); - let version = match selected { - Some(selected) => selected, - None => { - let output = Command::new("rustup").args(&["toolchain", "list"]).output().ok()?.stdout; - let lines = output.as_slice().lines(); + let output = Command::new("rustup").args(&["toolchain", "list"]).output().ok()?.stdout; + let lines = output.as_slice().lines(); + + let mut versions = Vec::new(); + for line in lines.filter_map(|l| l.ok()) { + let rustup_version = line.trim_end_matches(&host); + + let cmd = CargoCommand::new_with_args("rustup", &["run", &rustup_version, "cargo"]); - let mut latest_nightly = None; - for line in lines.filter_map(|l| l.ok()) { - if line.starts_with("nightly-") && line.ends_with(&host) { - // Rustup prints them sorted - latest_nightly = Some(line.clone()); - } - } + if !cmd.supports_substrate_wasm_env() { + continue + } + + let Some(cargo_version) = cmd.version() else { continue }; - latest_nightly?.trim_end_matches(&host).into() - }, - }; + versions.push((cargo_version, rustup_version.to_string())); + } + + // Sort by the parsed version to get the latest version (greatest version) at the end of the + // vec. + versions.sort_by_key(|v| v.0); + let version = &versions.last()?.1; Some(CargoCommand::new_with_args("rustup", &["run", &version, "cargo"])) } @@ -228,17 +240,23 @@ fn get_rustup_nightly(selected: Option) -> Option { struct CargoCommand { program: String, args: Vec, + version: Option, } impl CargoCommand { fn new(program: &str) -> Self { - CargoCommand { program: program.into(), args: Vec::new() } + let version = Self::extract_version(program, &[]); + + CargoCommand { program: program.into(), args: Vec::new(), version } } fn new_with_args(program: &str, args: &[&str]) -> Self { + let version = Self::extract_version(program, args); + CargoCommand { program: program.into(), args: args.iter().map(ToString::to_string).collect(), + version, } } @@ -248,20 +266,40 @@ impl CargoCommand { cmd } - /// Check if the supplied cargo command is a nightly version - fn is_nightly(&self) -> bool { + fn extract_version(program: &str, args: &[&str]) -> Option { + let version = Command::new(program) + .args(args) + .arg("--version") + .output() + .ok() + .and_then(|o| String::from_utf8(o.stdout).ok())?; + + Version::extract(&version) + } + + /// Returns the version of this cargo command or `None` if it failed to extract the version. + fn version(&self) -> Option { + self.version + } + + /// Check if the supplied cargo command supports our Substrate wasm environment. + /// + /// This means that either the cargo version is at minimum 1.68.0 or this is a nightly cargo. + /// + /// Assumes that cargo version matches the rustc version. + fn supports_substrate_wasm_env(&self) -> bool { // `RUSTC_BOOTSTRAP` tells a stable compiler to behave like a nightly. So, when this env // variable is set, we can assume that whatever rust compiler we have, it is a nightly // compiler. For "more" information, see: // https://github.com/rust-lang/rust/blob/fa0f7d0080d8e7e9eb20aa9cbf8013f96c81287f/src/libsyntax/feature_gate/check.rs#L891 - env::var("RUSTC_BOOTSTRAP").is_ok() || - self.command() - .arg("--version") - .output() - .map_err(|_| ()) - .and_then(|o| String::from_utf8(o.stdout).map_err(|_| ())) - .unwrap_or_default() - .contains("-nightly") + if env::var("RUSTC_BOOTSTRAP").is_ok() { + return true + } + + let Some(version) = self.version() else { return false }; + + // Check if major and minor are greater or equal than 1.68 or this is a nightly. + version.major > 1 || (version.major == 1 && version.minor >= 68) || version.is_nightly } } diff --git a/utils/wasm-builder/src/prerequisites.rs b/utils/wasm-builder/src/prerequisites.rs index fb04dc3c98fb..f5a985ab92b5 100644 --- a/utils/wasm-builder/src/prerequisites.rs +++ b/utils/wasm-builder/src/prerequisites.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,10 +35,13 @@ fn print_error_message(message: &str) -> String { /// /// Returns the versioned cargo command on success. pub(crate) fn check() -> Result { - let cargo_command = crate::get_nightly_cargo(); + let cargo_command = crate::get_cargo_command(); - if !cargo_command.is_nightly() { - return Err(print_error_message("Rust nightly not installed, please install it!")) + if !cargo_command.supports_substrate_wasm_env() { + return Err(print_error_message( + "Cannot compile the WASM runtime: no compatible Rust compiler found!\n\ + Install at least Rust 1.68.0 or a recent nightly version.", + )) } check_wasm_toolchain_installed(cargo_command) diff --git a/utils/wasm-builder/src/version.rs b/utils/wasm-builder/src/version.rs new file mode 100644 index 000000000000..e4f7d98be618 --- /dev/null +++ b/utils/wasm-builder/src/version.rs @@ -0,0 +1,215 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use std::cmp::Ordering; + +/// The version of rustc/cargo. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Version { + pub major: u32, + pub minor: u32, + pub patch: u32, + pub is_nightly: bool, + pub year: Option, + pub month: Option, + pub day: Option, +} + +impl Version { + /// Returns if `self` is a stable version. + pub fn is_stable(&self) -> bool { + !self.is_nightly + } + + /// Return if `self` is a nightly version. + pub fn is_nightly(&self) -> bool { + self.is_nightly + } + + /// Extract from the given `version` string. + pub fn extract(version: &str) -> Option { + let mut is_nightly = false; + let version_parts = version + .trim() + .split(" ") + .nth(1)? + .split(".") + .filter_map(|v| { + if let Some(rest) = v.strip_suffix("-nightly") { + is_nightly = true; + rest.parse().ok() + } else { + v.parse().ok() + } + }) + .collect::>(); + + if version_parts.len() != 3 { + return None + } + + let date_parts = version + .split(" ") + .nth(3) + .map(|date| { + date.split("-") + .filter_map(|v| v.trim().strip_suffix(")").unwrap_or(v).parse().ok()) + .collect::>() + }) + .unwrap_or_default(); + + Some(Version { + major: version_parts[0], + minor: version_parts[1], + patch: version_parts[2], + is_nightly, + year: date_parts.get(0).copied(), + month: date_parts.get(1).copied(), + day: date_parts.get(2).copied(), + }) + } +} + +/// Ordering is done in the following way: +/// +/// 1. `stable` > `nightly` +/// 2. Then compare major, minor and patch. +/// 3. Last compare the date. +impl Ord for Version { + fn cmp(&self, other: &Self) -> Ordering { + if self == other { + return Ordering::Equal + } + + // Ensure that `stable > nightly` + if self.is_stable() && other.is_nightly() { + return Ordering::Greater + } else if self.is_nightly() && other.is_stable() { + return Ordering::Less + } + + let to_compare = [ + (Some(self.major), Some(other.major)), + (Some(self.minor), Some(other.minor)), + (Some(self.patch), Some(other.patch)), + (self.year, other.year), + (self.month, other.month), + (self.day, other.day), + ]; + + to_compare + .iter() + .find_map(|(l, r)| if l != r { l.partial_cmp(&r) } else { None }) + // We already checked this right at the beginning, so we should never return here + // `Equal`. + .unwrap_or(Ordering::Equal) + } +} + +impl PartialOrd for Version { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn version_compare_and_extract_works() { + let version_1_66_0 = Version::extract("cargo 1.66.0 (d65d197ad 2022-11-15)").unwrap(); + let version_1_66_1 = Version::extract("cargo 1.66.1 (d65d197ad 2022-11-15)").unwrap(); + let version_1_66_0_nightly = + Version::extract("cargo 1.66.0-nightly (d65d197ad 2022-10-15)").unwrap(); + let version_1_66_0_nightly_older_date = + Version::extract("cargo 1.66.0-nightly (d65d197ad 2022-10-14)").unwrap(); + let version_1_65_0 = Version::extract("cargo 1.65.0 (d65d197ad 2022-10-15)").unwrap(); + let version_1_65_0_older_date = + Version::extract("cargo 1.65.0 (d65d197ad 2022-10-14)").unwrap(); + + assert!(version_1_66_1 > version_1_66_0); + assert!(version_1_66_1 > version_1_65_0); + assert!(version_1_66_1 > version_1_66_0_nightly); + assert!(version_1_66_1 > version_1_66_0_nightly_older_date); + assert!(version_1_66_1 > version_1_65_0_older_date); + + assert!(version_1_66_0 > version_1_65_0); + assert!(version_1_66_0 > version_1_66_0_nightly); + assert!(version_1_66_0 > version_1_66_0_nightly_older_date); + assert!(version_1_66_0 > version_1_65_0_older_date); + + assert!(version_1_65_0 > version_1_66_0_nightly); + assert!(version_1_65_0 > version_1_66_0_nightly_older_date); + assert!(version_1_65_0 > version_1_65_0_older_date); + + let mut versions = vec![ + version_1_66_0, + version_1_66_0_nightly, + version_1_66_0_nightly_older_date, + version_1_65_0_older_date, + version_1_65_0, + version_1_66_1, + ]; + versions.sort_by(|a, b| b.cmp(a)); + + let expected_versions_order = vec![ + version_1_66_1, + version_1_66_0, + version_1_65_0, + version_1_65_0_older_date, + version_1_66_0_nightly, + version_1_66_0_nightly_older_date, + ]; + assert_eq!(expected_versions_order, versions); + } + + #[test] + fn parse_with_newline() { + let version_1_66_0 = Version::extract("cargo 1.66.0 (d65d197ad 2022-11-15)\n").unwrap(); + assert_eq!( + Version { + major: 1, + minor: 66, + patch: 0, + is_nightly: false, + year: Some(2022), + month: Some(11), + day: Some(15), + }, + version_1_66_0, + ); + } + + #[test] + fn version_without_hash_and_date() { + // Apparently there are installations that print without the hash and date. + let version_1_69_0 = Version::extract("cargo 1.69.0-nightly").unwrap(); + assert_eq!( + Version { + major: 1, + minor: 69, + patch: 0, + is_nightly: true, + year: None, + month: None, + day: None, + }, + version_1_69_0, + ); + } +} diff --git a/utils/wasm-builder/src/wasm_project.rs b/utils/wasm-builder/src/wasm_project.rs index d17997360dee..849af853c6da 100644 --- a/utils/wasm-builder/src/wasm_project.rs +++ b/utils/wasm-builder/src/wasm_project.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,6 +19,7 @@ use crate::{write_file_if_changed, CargoCommandVersioned, OFFLINE}; use build_helper::rerun_if_changed; use cargo_metadata::{CargoOpt, Metadata, MetadataCommand}; +use parity_wasm::elements::{deserialize_buffer, Module}; use std::{ borrow::ToOwned, collections::HashSet, @@ -116,6 +117,7 @@ pub(crate) fn create_and_compile( cargo_cmd: CargoCommandVersioned, features_to_enable: Vec, wasm_binary_name: Option, + check_for_runtime_version_section: bool, ) -> (Option, WasmBinaryBloaty) { let wasm_workspace_root = get_wasm_workspace_root(); let wasm_workspace = wasm_workspace_root.join("wbuild"); @@ -134,6 +136,10 @@ pub(crate) fn create_and_compile( let (wasm_binary, wasm_binary_compressed, bloaty) = compact_wasm_file(&project, profile, project_cargo_toml, wasm_binary_name); + if check_for_runtime_version_section { + ensure_runtime_version_wasm_section_exists(bloaty.wasm_binary_bloaty_path()); + } + wasm_binary .as_ref() .map(|wasm_binary| copy_wasm_to_target_directory(project_cargo_toml, wasm_binary)); @@ -159,6 +165,29 @@ pub(crate) fn create_and_compile( (final_wasm_binary, bloaty) } +/// Ensures that the `runtime_version` wasm section exists in the given wasm file. +/// +/// If the section can not be found, it will print an error and exit the builder. +fn ensure_runtime_version_wasm_section_exists(wasm: &Path) { + let wasm_blob = fs::read(wasm).expect("`{wasm}` was just written and should exist; qed"); + + let module: Module = match deserialize_buffer(&wasm_blob) { + Ok(m) => m, + Err(e) => { + println!("Failed to deserialize `{}`: {e:?}", wasm.display()); + process::exit(1); + }, + }; + + if !module.custom_sections().any(|cs| cs.name() == "runtime_version") { + println!( + "Couldn't find the `runtime_version` wasm section. \ + Please ensure that you are using the `sp_version::runtime_version` attribute macro!" + ); + process::exit(1); + } +} + /// Adjust the mtime of the bloaty and compressed/compact wasm files. /// /// We add the bloaty and the compressed/compact wasm file to the `rerun-if-changed` files. @@ -632,7 +661,7 @@ fn build_project( let mut build_cmd = cargo_cmd.command(); let rustflags = format!( - "-C link-arg=--export-table {} {}", + "-C target-cpu=mvp -C target-feature=-sign-ext -C link-arg=--export-table {} {}", default_rustflags, env::var(crate::WASM_BUILD_RUSTFLAGS_ENV).unwrap_or_default(), ); diff --git a/zombienet/0000-block-building/block-building.zndsl b/zombienet/0000-block-building/block-building.zndsl index 86a54773484b..6ad5f3d89fda 100644 --- a/zombienet/0000-block-building/block-building.zndsl +++ b/zombienet/0000-block-building/block-building.zndsl @@ -2,9 +2,6 @@ Description: Block building Network: ./block-building.toml Creds: config -alice: is up within 30 seconds -bob: is up within 30 seconds - alice: reports node_roles is 4 bob: reports node_roles is 4 diff --git a/zombienet/0000-block-building/transaction-gets-finalized.js b/zombienet/0000-block-building/transaction-gets-finalized.js index a38cf603fc57..d16e85f2c4e6 100644 --- a/zombienet/0000-block-building/transaction-gets-finalized.js +++ b/zombienet/0000-block-building/transaction-gets-finalized.js @@ -14,7 +14,7 @@ async function run(nodeName, networkInfo, args) { const bob = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty'; // Create a extrinsic, transferring 10^20 units to Bob - const transfer = api.tx.balances.transfer(bob, 10n**20n); + const transfer = api.tx.balances.transferAllowDeath(bob, 10n**20n); let transaction_success_event = false; try { diff --git a/zombienet/0001-basic-warp-sync/test-warp-sync.toml b/zombienet/0001-basic-warp-sync/test-warp-sync.toml index ae2810b6eccc..272b5862e8e8 100644 --- a/zombienet/0001-basic-warp-sync/test-warp-sync.toml +++ b/zombienet/0001-basic-warp-sync/test-warp-sync.toml @@ -11,18 +11,18 @@ chain_spec_path = "zombienet/0001-basic-warp-sync/chain-spec.json" [[relaychain.nodes]] name = "alice" validator = false - db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-0bb3f0be2ce41b5615b224215bcc8363aa0416a6.tgz" + db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-a233bbacff650aac6bcb56b4e4ef7db7dc143cfb.tgz" [[relaychain.nodes]] name = "bob" validator = false - db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-0bb3f0be2ce41b5615b224215bcc8363aa0416a6.tgz" + db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-a233bbacff650aac6bcb56b4e4ef7db7dc143cfb.tgz" #we need at least 3 nodes for warp sync [[relaychain.nodes]] name = "charlie" validator = false - db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-0bb3f0be2ce41b5615b224215bcc8363aa0416a6.tgz" + db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-a233bbacff650aac6bcb56b4e4ef7db7dc143cfb.tgz" [[relaychain.nodes]] name = "dave" diff --git a/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl b/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl index 8ceb61c8b039..dc84804b70b0 100644 --- a/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl +++ b/zombienet/0001-basic-warp-sync/test-warp-sync.zndsl @@ -2,11 +2,6 @@ Description: Warp sync Network: ./test-warp-sync.toml Creds: config -alice: is up within 30 seconds -bob: is up within 30 seconds -charlie: is up within 30 seconds -dave: is up within 30 seconds - alice: reports node_roles is 1 bob: reports node_roles is 1 charlie: reports node_roles is 1 diff --git a/zombienet/0002-validators-warp-sync/README.md b/zombienet/0002-validators-warp-sync/README.md new file mode 100644 index 000000000000..662360fbf3c6 --- /dev/null +++ b/zombienet/0002-validators-warp-sync/README.md @@ -0,0 +1,4 @@ +Refer to ../0001-basic-warp-sync/README.md for more details. This test is nearly a clone. We want to warp-sync validators and make sure they can build blocks. +0001-basic-warp-sync chainspec (copied) and database are reused in this test. + + diff --git a/zombienet/0002-validators-warp-sync/chain-spec.json b/zombienet/0002-validators-warp-sync/chain-spec.json new file mode 100644 index 000000000000..8c09e7c7b032 --- /dev/null +++ b/zombienet/0002-validators-warp-sync/chain-spec.json @@ -0,0 +1,192 @@ +{ + "name": "Local Testnet", + "id": "local_testnet", + "chainType": "Local", + "bootNodes": [ + "/ip4/127.0.0.1/tcp/30333/p2p/12D3KooWFvMbTsNZ8peGS8dbnRvNDBspstupzwYC9NVwbzGCLtDt" + ], + "telemetryEndpoints": null, + "protocolId": null, + "properties": null, + "forkBlocks": null, + "badBlocks": null, + "lightSyncState": null, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x074b65e262fcd5bd9c785caf7f42e00a4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x0e7b504e5df47062be129a8958a7a1271689c014e0a5b9a8ca8aafdff753c41c": "0xe8030000000000000000000000000000", + "0x0e7b504e5df47062be129a8958a7a1274e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x0e7b504e5df47062be129a8958a7a127ecf0c2087a354172a7b5a9a7735fe2ff": "0xc0890100", + "0x0e7b504e5df47062be129a8958a7a127fb88d072992a4a52ce055d9181748f1f": "0x0a000000000000000000000000000000", + "0x0f6738a0ee80c8e74cd2c7417c1e25564e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1809d78346727a0ef58c0fa03bafa3234e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1a736d37504c2e3fb73dad160c55b2914e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1cb6f36e027abb2091cfb5110ab5087f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1cb6f36e027abb2091cfb5110ab5087f5e0621c4869aa60c02be9adcc98a0d1d": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480100000000000000", + "0x1cb6f36e027abb2091cfb5110ab5087f66e8f035c8adbe7f1547b43c51e6f8a4": "0x00000000", + "0x1cb6f36e027abb2091cfb5110ab5087faacf00b9b41fda7a9268821c2a2b3e4c": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480100000000000000", + "0x1cb6f36e027abb2091cfb5110ab5087fdc6b171b77304263c292cc3ea5ed31ef": "0x0100000000000000040000000000000001", + "0x2099d7f109d6e535fb000bba623fd4404c014e6bf8b8c2c011e7290b85696bb3": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0x2099d7f109d6e535fb000bba623fd4404e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x2099d7f109d6e535fb000bba623fd4409f99a2ce711f3a31b2fc05604c93f179": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0x267ada16405529c2f7ef2727d71edbde4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x00000000071c0d84db3a00", + "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9007cbc1270b5b091758f9c42f5915b3e8ac59e11963af19174d0b94d5d78041c233f55d2e19324665bafdfb62925af2d": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da923a05cabf6d3bde7ca3ef0d11596b5611cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da932a5935f6edc617ae178fef9eb1e211fbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x000000000300000001000000000000000000a0dec5adc935360000000000000000000000000000000000000000000000000064a7b3b6e00d0000000000000000000064a7b3b6e00d0000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94f9aea1afa791265fae359272badc1cf8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da95ecffd7b6c0f78751baa9d281e0bfa3a6d6f646c70792f74727372790000000000000000000000000000000000000000": "0x0000000000000000010000000000000000407a10f35a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da96f2e33376834a63c86a195bcf685aebbfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x000000000300000001000000000000000000a0dec5adc935360000000000000000000000000000000000000000000000000064a7b3b6e00d0000000000000000000064a7b3b6e00d0000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da98578796c363c105114787203e4d93ca6101191192fc877c24d725b337120fa3edc63d227bbc92705db1e2cb65f56981a": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b0edae20838083f2cde1c4080db8cf8090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b321d16960ce1d9190b61e2421cc60131e07379407fecc4b89eb7dbd287c2c781cfb1907a96947a3eb18e4f8e7198625": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9e5e802737cce3a54b0bc9e3d3e6be26e306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9edeaa42c2163f68084a988529a0e2ec5e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9f3f619a1c2956443880db9cc9a13d058e860f1b1c7227f7c22602f53f15af80747814dffd839719731ee3bba6edc126c": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x3104106e6f6465", + "0x2aeddc77fe58c98d50bd37f1b90840f94e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x2b06af9719ac64d755623cda8ddd9b944e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x2b06af9719ac64d755623cda8ddd9b949f99a2ce711f3a31b2fc05604c93f179": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0x2c5de123c468aef7f3ac2ab3a76f87ce4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x2f85f1e1378cb2d7b83adbaf0b5869c24e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x2f85f1e1378cb2d7b83adbaf0b5869c298ef7dc060436e4ed803af07632b89b65153cb1f00942ff401000000": "0x601de9615313b00e8cea3ef84e79e50b2fb70e2c8a47cff478b9fe8b3fa8f2db02000000", + "0x2f85f1e1378cb2d7b83adbaf0b5869c298ef7dc060436e4ed803af07632b89b6b4def25cfda6ef3a00000000": "0x601de9615313b00e8cea3ef84e79e50b2fb70e2c8a47cff478b9fe8b3fa8f2db02000000", + "0x2f85f1e1378cb2d7b83adbaf0b5869c2ff3ae12770bea2e48d9bde7385e7a25f": "0x0000000002000000", + "0x3a2d6c9353500637d8f8e3e0fa0bb1c54e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x3a2d6c9353500637d8f8e3e0fa0bb1c5ba7fb8745735dc3be2a2c61a72c39e78": "0x00", + "0x3a636f6465": "0x52bc537646db8e0528b52ffd0058dc2705bea5c66d15541040ac6c3ae971bcf5f1040221a8d1f8b7c17e61bf21cce87411480b10bd8daa5e3c2b65f8c4aa364e5a8ddf27c0313827d06a2d6334e074db9ca7d876ac8d052862b9984530f4d340bda4edddd6e4de5ba624536717e1139e14df2d875c7f7086dc3fa3d398b64f50fffc58fbf4fb73ffb0d39cc2b86761cfe6d3073b9c1fcee7fe611b7f4875f9a7d8cfe208fdf493f4d3b07d9a73ad05c8cf39e5c4dde679c3c8391b9ceec4fd78c824d218e4cfb79cb8be9cb301eace3b7f8a274c182edb72e2e83349855e7e4a2a795d7e27998b94f46d92b42eb543afcbcf5f5c9e5bf2d52dfd9b2f93dc33262710e3fc79b943e09fe2d00f8dd7edc4350b92a692a2facb7adddffddd3d89e9babb7b8e0b9d61050b2b4ddf3efdfdb3f629e9edfe9c18c4f8715bebf42d6f6e3ff74be1b9c0c8b9187cb89c83618e5bce6ebf90cb3918729745244063391ee9710cdb87bfbbbffc6047f275725e904518e7f53cec48efa788c4bd67d104a4eb81534cd2bd67cbeebb075b4c32deee59a43da30fd2d0c979410f3db2fb2972cfcad89d0f822c7ab644bade3b59d6eeddc9925eef8f34e8645980eb0fb2f8d9b2cafddec99273d7bf234bee5995292c3051e1fa97dcb31285eb5f76255cff23de917c3f72c8afaeaeac6ef74e96462e7d70864ed2ef48be2c9ef05d8f1cfa6e67cb79dd0ecdabababab5b225d7f4a72cfa6a53d9bcf739903246f521840fe916b813c65e41c0c50b7b60fe76078bafc61ff4c1bbfac7d3887b3bafc37979f73580490ef389f9f5920c8edc3f3cee7075fdc3e4cdba79f1f9c200db97df896f532c31e008355cff83a7dff7e1791b4e5097b400cb89ef19def24533b4524b567fe2c96c02292fe233d5900f2edfed96d8f34d29c2680f3270934e67054b7a4b7bfceaf95fd879b568e777e395e3aa7139fded3ff484f77779fef3ebbbfdeb6364afae9f74c32bf6dd9ef3de714c839e5df7e908f8c208b30f2fbb388847f8a2354c1e170305cb6dcb3b2de7eb0436e9ff9edc33deb3fd24ed29ef50f602cc7dbfda0ac7df8f683331cdba73fd63efefdb2f699b79f45244e492a38b708a9def9f54e6ba3643efdf9d41acdeb964524d396f3ce3fd247fa2f7380e4736a01f2e59c72bcfd606d9fb266c81ce3fc32bc0d3a198071be3f3f8b27cc0f725b7bc6cf3d2be99da45f916a7e3f38c349d21b7211a2597587be4b05e6ceef17b908d1c4dd21112e95f0ceef777111a289bb43225c2ade9ddfffe2224413778744b8f3fb594c4271b83bff48831d3217995ffa73cff86bfb94f3faf3834ed69ef11719fdf9592481f68cdf6ded19ff91063b9c64df908b10cddca5e2ddf9fd2217219ab83b24c29ddf3fc51346dc9d1fb60fdf7e16934cdc9d7fa48ff4913ed24c72cff839a76dd0789903a49e5d06c80dcee7f629f9a7e59ef1f323cd2903720ee774f91b22454f610414ad21610c79c190160c991a32c6102286c0604808865831440c2057860431c469881042da10f28221370c9961881742e6186245881c426630248b1035848421248e214d42de18b224644a081b42bc08a11212032164081943081842d6102203212e102286902e84782184cb902d845821a44ac8154248304520a449080d3d36e8a9ea99a3478e9e387ad4e8f1d233464f0c7a723d5b7aace849414f153d24e839a247aa678b1e287a9ee889ea71a207891e237a44d053821ea89e247a94e81941cf0a7a5ad0c3821d11f448d1d3448f163c3af070a046c68e1a3b69eca0b173c68e193b65ecc060478c9d17ec84b103c64e979d2f76bcd8e982878947069e251e259e241e2b3c31f0c0c083dbb9dab963c78e9d3776e0d891c1ce971d2e3b2ed8e162678b9da99d1becd86087063b74ecccb133831d3976e2d8a963c76aa7063c1be0b9b293c50e163b2cd859c18e0a76a476aed8b162a78a9da89d12ec3cb143821d26768cd82962c769e7879d1b767cd80162c7033b42ec7460070440ec0072c70e13901b00b9da69dab101481640ba00f102880b807001b20490278094000813407e000204902cb51acc20a61390a51a0d80c850fb52bba376558ba3660710a5da1c409280e06a5635388058a9cda026478d8e9a1bb5aada1b35366a30a86d51aba236454d899a096a4c0059a386a546430d57b3528361e76987089d2f74dcd0a9d25943270d1d2a1d18e88ca123860e1b3a73e880a123874e0c74e0d02943270c9d3674ccd0214387063a32d0a9814e171d2b9d3774d0d0a143470d9d17e8cc40270e1d2f3a67e808a143848e077478d0c9a263834e0d3a34e8e0a0d3019da61f6afc80e3c71a3fc2f8d1821f5dfcd8e207173fbc981ef8f1c40f277e90e047133f98f8b1c4ab04f34b8f0c604288573fbebca26a54d0c8f083aa2af9f0c2c71735197e88a1d3c33787ec8e9009f6434d1b36573a869a0bbc2f3a778473d49e422ec2168453e11630203c1bfce0522347784527869a17542d68984219603d7442c086a8f902e34167294cea425023469df26e50e3a5878c70a926d5259b1d6267c8bebc5eb0d303cc03a1d2384307467490a583a70e74a851eaa0033d8e10bfc070a8568c3db8be78687c74f408e2c545b481cb4a8f14e06c51657851856d8c3bc8bc78c5c047d40f2d6476c83421bb416581cb062f2f2f34646ed42d7c24e164f1ec78a9e15223e6c54b63d4e2c38a48c78c1d95a9068d5a11f54aad04b51d5c68b86010adc61a6ad4a0597aa541a39463031033726aa08901c9445e21ade05841c6602307a9f423f827300d1b19f4d4f1657000825718382da827e8b0886ef4808123021c23708ae809030704384ce02c81c3054709215d709cf8f1c1f900ce062a10383e8852b468d0b2c26962c686991a7048b09303ce08704a504bc306861a326ad8b0d9400d0c6ac0c8990187861a3a74385063464fe12ce1c4e003063ec6f831831e40f4f0c04b8c1f67782ce8d104a8460fa79d257afcd003043fcc905981103764a2e8a1030803d005433e007ab179b201818d10f406a01dd48ece868e0956431743dd42bff4e8402743074387ebac74576a0a2a15758aae06b1aadbc07f60c68c971daf2f558a1e5a3aa577aae9a24bea66a077b8946a9868c8a0a1010d1d3435a061636606345e66e2a091c18c1c346bd0a83143c7cc1c3469ec0411ce40860b4d0b605a68585093aa69a131a25ba2c982e605b52d343e8c1bb0c901f6011808604f3c88784551b9c8ae646dc072f820fe88991bbcc0e88ae888e88c70c9d03dc18aa8319851a3f683c8c60c1a325f646e206303992b19ab191a66b0c850f1a282460c9c20707ac00142e60a192b6049302b3255c8a4e03df03f00d181831d4419d4c6b0517a55895334493b30b41534565c4b2e1ccca99682300d980e5e0d6057604cf306e31a3655231b3e62f08241f5e245c66b0c0eb48c408c3f8c1eb08943868b07e2d5454d1aaf2b5e5b6cdc78a9604c0387089c271928402e5e55d86001af6aba8871d4d0a0070f3634d4d8d1c3071b2b21ce268b8d0d3367d4bc01bba1c68d1f2ea859e387979a18c09a6c7880d95073364b3565d0ab9a19d4b2b091e175831a2b180d3859d4d421b6f17ae2d5c4cb891f37a89101784718c3cb0697198e858f261717ae12b846e02ac2b585eb042e295c53b8927081c015e59a72b5c07584ab0617169716ae14b8b270f5e0f2800b0bd710ae255c4eb884706d7129e17ac2b5838b07d70f2e1b5c50b8a270657171c065848b05ae15b8987055e1bac275832b07d7932b042e205c1f7005e15241cf54cd183364b862a8b99a2943ac439cc3e606110e9106e21be21d40bec0711273220a8068b169c3956433c3aca3c6c4b303073cbccee891c30e0e385c841b7899a1e30510aa1a143555b5276a4e4c2d707ee080033b576021d88961a7069c295810af357464086100bfe0203141600387cc13322600bbc0f100e8458f25c02dc030769a00c1787d51b1709d51b3807140acc1cb8b9a06ce11343000c204a40c58075e65d868a9b1012ba9fe00dba1034137448ba047053369bc6cf0838d1e3df464515bd0566ab8ecf0a0d34563f1d9507bc0116205162a544da22e5195a81e783272a2462a9e8b189731052e3b64553eb8689911bee18901048d57171d2d3836e0d48033838e0e325f006143c68b8f06b5a57a458c8b2ae5aa636c72a5d1e303a21db58a1a11b5281f31e058a92800a2850f18ea0a5e50d4cc21dea0a606231a43b8801101f321b4e25b8833a82a10e5a849c2d10296c533e3470c6a5c5073064e08bc1fbc19bc2b5e0c3d33e8b1a367e81a7a072057b4161b1d76b4b8c2a01903e452bb41cf17206a0021a3cb0244059d53a743d7d4d1d075a0d3d2fdd005d1c305080f3d5d741ce87ce87ae878e880e0d1d2edd079a0cba1fb40874377031016e00c61f3464d0daf0d9d1c7ab010b700d7d0b9a1870d73861b367676e0e14167079e2606e060c101838621e3820ea39de8259a896ea29fe82e1dd55f34091a0a97aa5cfc106fc423417304cd123448e41891e38408845884e834163192603402e4400d23c6464c8e980c649e64909029a207971e60f4e822a7889c12e488409c41e4808885468a460b1a15c4ba88c120e60558450503b442d64566860c8c19ab993b66eae8b1460f1af460237644ac043124782cf168e22143ab8adaa56545cb8bda45eb8b2fe245f042f838818f2d3e50f02178103c11b12b625bc4a44627c629c6a81814b12d3113f810c207123e86a8d9e0b9e1d1e1bd018301ac0cd8182316a31763163eacf0d1021f57c8acd00981ec8a0f86efca1783a884480271095087ea0270872a06a8e585e5b5c38b86d612b5205a4c842c08c908b1a835d42cd586fa058d153eb4f808c2070fad21ea0b5a21086f107e69bd117620ecc1d3a2268697453746774617032058d870c026861aaa9a1bd8d450e3854d520d1c364d3638d4dc5113860dae260e1ba69a2f80cca16307101bec28d145f42cd136340d4056d0589aa973681e3acb9ca3710d03902ee6d5acc1b4c184a3c76a5acdaa49032052736ab66066b164a260b2604e31a5983f4ca989c584629e60e6a609261573cbfcc05cc1b46246318998219843cca46965c6309f260c1357c50215983e3c994d4ea8b820a505a1261098400424f08006a49000144e40c001941820090953082300100091282c38c07979c648c16a072a6a528125539a00b1b0640938fd4914a0254b5abf8127529c003dc1f92b5c548401202551a440292a025a00f94c1cbc0c34bf24d4c40914222946455058f0e0956c3e89895a68e1f556b8a80848a8c7c7c0310f03b7700099c7f1e05c090935712224d48400349c3bb848084a1323a0274b80802cf0018e1d2cc50251a02cc10011900b434ba04cb94651805ac04013141ee70b03ad7003067a22c5c992274da4084d71c1060c64d4048a519426464551a0c870ea6021a1264e8c9c3421328a228500301c2b067212454a94254d9c44918200a3282e4871e2e4f6c0a9019429170345500a80814be2d020041c3a7270e660189c19b01ccc82132953806c70e26022175680628128529c2c8132e50255a0894d06a78a8b9e1035b90010d19215a24079120568ca1228536e91901429284481d2821404581c38780a14168a8880a25c40ca120c00057d80f3063f018ab20294a2282e18357122850a14178680961811499902444404c70d7e026589115011910b4e6870dae0222028465088a4b060e44293252b2c8132e53699f2244a1322127ce0c8802f0065ca25020a2292e28013481c361808c80520171290c213a015566892c20f9c35b828c809100b4ebc7091511420222016a240791c35a240b1800d4e1aec044a5114284446558096604068ca94264b88809e084171c116e4448a90ed06070d6e614913a3264f9a18011d0086734606a00835816204250a14191c3338c88914a10a2c013272c109501520189c3298480a01429e404215686214c5050b2c3172024404e54914284b96d0e880367c2083918403c3ca97cb4820e770b83b919cc66355b1aae694f951efc6c6fb273d1bcff3bc27c99b1b8ffc1beadfc75f7f5d6b77adb39bf963effa7d5d2be576e68ffb03e9f4fad1a6def7791fd013fe987a7b5f777fb4995b74ff9add99b2338b226566ffd8fbbe9deea87760f7d755efcfbbfb1b476ef70aced44a99ba7762f56e96d8ac3a99bac74ce64a65cd72cf9d3f0fdbe9d7ec2f661bfe987ecceca4dde9e73895dbbb72f5cf690794fac7edb4d93f76fa71d3ef73f7bc8fb6536766ca9429534ad9fb28f5f6ef83317b53a64c993fca4e653e66a7cced9476bb575ae5cafef19021428430c8eed4e5a20e7a575abbdd0586cc937a5ead1f738fcc9f3b6598bb5377efd1dd2be0eeb0eab5d6dae3d8b4dbdde79c3e67d3f6f6a6d4bbbf6edaecd46beaf5fc2af5c09d3af5599dddbf70c645db43f7daa42ba5ee2ff6f656bb7fee1574265d57ef4a7b093b8d31b37fd3dd41b052a069482b0dc31aef1a760fc309788712a8719ed3bf5ab97b727fdc13e4eb09e21e0176770a1386ddd3b9bb99fba3f9421aeace94db2140a97baeaf7eeef4f3a8473d66af52eade5fb7fb473fef8ffbc6fb3effe6fc981bf600dab4ddbb2937b37fdd1f7bceee7d2008a34054a820303bbfda1dc0cc3e3bef98b9fb989bb9eb66c7ac6eba772c67ee58ecccdd64ef581d77ce9a5dd771d7755d73d779c71db3983b666618666f6731c73033b35831cc95997aadeedd9f7b8e7b7737a51ed0ee80f6cbddbfe6699e6e8fba7703ba76f7a49501ed7937ad1fe5e9cddd1f7f5fc7d49dba376d4f61d20528c0030e9d7f780218051ad6f41ac7a969202774cb7cddb06eeafd79b57e8e0098ce016a351d6f1a86ec734e9eacf6febeef874c7ba594c62adb7ccc95a73bedd0dd457710e6e476b06d427666a6dd4ca33edd41f77228655a29ad94b6f70ca5b45217a5d4044a5db429f5da5e2b58bfaf6777fb6cfe3e9c0efae3e993d99550e7ea6cc3ce6266af71f7cf6b6776f630c63e67c8ede2768ecd364077e8edce727767d51866f7ca732661febc69f3474343536b7dbd5e73ce5adbbd84f6f6aa57efd09d046feaeefe7277eef9f5d7947e1f87cedeee4dab778314ac2085e9eee6ea9f37280382ec3ed929ad15a4947e1ff7d7d49d7d4e0ec3574e873e42483d44e2341e76bb7bddcdeeeeecfc7ddc55669e9f659cf979e8ec79dd4eabd7dab47af5efebaed56937add4a35e53da3da7cf39270c4c7be73c3bca6af72e403b28d33dfba360d7fc79d8dd9336a594b67f4e00dbc7edec3d99bbdd3fff3e76e6767667766f6766a6a0e73ebdbda977f7f4cf999d5266fed89ddb9d32333365fe98bfef6b76ff989d7a5f6f7766767777fed8bddbbbbf23dff7cd6aedafcee97d3319113c99effbbcef930d4000edc950af7e9fabbdfdfbbeefebfebe98f77dfc7d9ff3f77decf4fb78ec9a366d9036edcfdbddbd73ea7ded1fcf39448a2153380f6e0c10496901a805cf8805a448219a0242009e0011013d2192b2c427841e20284446207c0043d323a5a60b72d2a402424252402852d3c3470f1a1e464550aa00dd742d48e981620120a22996031b98284053a634b9f10b00450122ea0e5a806214c50522cb81058c5ce8c14115201696184581423465899322282ddc0cf1e95145ca058c7c3461a1c8475013274e80827c42e8e1c14188f40459d20314d444a8024b5688028585a2262cbc102128413c00f8800807690244e4032868895115a0283a3e3e807922048505274b8c845c686201a027b4ce88a88951d1073d1cfcac00a5888c7190220c00052d710245a8022e4c99111575d0e29ea22220a2292d4871b28408888a142740444b9a143d7102b42448904f083d539c0015fdb0587062d4a4024b58288212058a05661f60c002508880a870608980a200adb00400414208a1e709d0122946533040e4a4039610101520a125559ab480035b418ad09215a240596105294234b3051f0c446401275284849adcc054017a12054acc94169a181501b500c5e87b449102c5c84993a2284d888c96184169a1022e14d1584f80808a96b420e5499426443606c8a88951d1922022291730028a92032bf2596116821150d19227519a2c01226a32a5052943c62640424e9600add084488a51d112a1264ea4088140250a90b338c8922852a014110151594224c5a8c91328424da82c2982b2240a94262c38b18005a42c31aa02c401952840fe03aba3f986d44cf805a466c28dc462c247472c9009b7c20e09e9e8e8ab48ee47484820528b09231d1dbd8e8e68bb33399a4847474794c9d13cbacddb64f2c27c611e1d1d1d1d3192787474243261a48f8f908e909c9190e6d1d16482348f909ac96dde6ecde485f9c20bcd848f90909cc9d13c3a6a2647f36832e1a3a3a323a48e1e1d21cda3598f26d264723499091f1d39133e426a267c74c448481f13a47974e4313942ea98204d24a46682349991281346426a268c8484e44c9026d264c248938905bc342633cf168a9a70ff6aba2dbecb9bc2179ba6703ceccdfb193b61ec6d8ad3fa61f6369d5af6e654377762d95b3bc9d8a6622aae72ea625e8cbd219102ec61fec6fa1bd3105beb6f48a4d0bd3775fb6f48a450bfeb5c0ec6de580fb373b4b709e5b4fe97bd4d97bd7993686fdd14da9ec24d2876c109ea68a220cbde9296425bcbde904881f5f46f93eaf6330db1517b63d949e5b47efa9ebdcd29a7f577f6d6d5766e7a36a8a2065a90800530cca9a9c58c21d4f882082b9f3dea2b9a5002872dbef460a37656517915a572a2534d533447713977a250de975e0555e5509eedb450755fba13d5955367bb266fa25ff6d477757507185faca087266a3ba6cecd37bc08c30c20a4c2b0b1cdbf6cdced8f8b1dbc7842862cdcb0b1cd6d87735a7f39ab6eff1cc138bfec70b7bba9cb1c1c59a4d933361c31c711671c31c611611cc165882e59dc0c8c9c1b4288db0f5ece0d41c5104fcc1a30bbb3b395796543826b58f0c86e3ffffc59fbcc3075fb6f74daa76fffecd86d16fe36cee5c961ce39d9c66a98c3873984d395f5cc2d371260cf8668c26947f0b30d091c10081bb60640bafc2fc840c4b644c4d6cf362bf3cad61609b64773ea809492486310fd0f6e89d41d5dfa25eb006cc6ed2c11b57b26eb7724253b57a2ca562d8eb568a25fb228f9c105278b10f8e01e4da4ae9b0b281f40e625e61c9ad77fde00f293f00738346f3f0f5e6ef8dd3759346f7f8b25f065d9a0f9ac269a5cebdea8de21bebae0b73842f8ad16d87db348ce817ed8e586cf7a10ecc26e3dab45253409ebc33fd2247c0ffe24c16791e1bba8a4fe9c96b936cbb6936bf3590f9265b3a8d43be477e08c269237db8dc9f47f5c3a9f7970bade7f28a8b003166eabf159fbd878dde7cc98b0204667f3d1a38ddbe9b44fe9fd8c3baeef25842a30699f8ea54c689f79bbe7d1c274bb1fd2519edb3117299f9a5bf219b7ab3fe57edffc46deed07694efb74443d45254beef7f5fbe6cf32ebb00559a2094852782dcffb727edf64b51fe99125d2f5823c31c967bfff6cf98267cb79bb497f70762bf16f25d3b34ae677f737e00e425c8fec260cb7accfb9197c18c158fb78df3d1549a85fbe709564c270eb83dcb8fbbd47d68f2cbde73fe21e0c777e7b55f502d6fe793dffd83ee2f3c7daa7f5cc7ff339ed539f7fd63ede23dd16297e25914010fc231d9236f559396cb9f4451dbc07b95cfa1e07ceb8f441fa208834c367b1c00727f8437ec1076b908d92ef3bfab93e259feb03bf3ec86a3d5f56cb8e509ff5ac1db25cfaaddf21e9d2a0eebf0fbf92f3b25c1ca1d56ab56c50f725fd22a4138a906ef841de9f40ffb3a5f7a165f165d52e6431b4e5ed86ef912fd27b89394839935ebfc41cc4fb693be2210976a147fabf48b6555209cb06894f5f7c16910c896314df3df03d5b8a0f5a1bf0bfef94d017ad0d6895d4f7be821ed9fa2fa8d64f49fd6a12fa2c5b7a0f3e089265f836ace70bbe47dab0ec08dd830f5abe1c98e3d2071fecd06b3d68cb794befc3073d6c91e197f386df8d002601c1f767d9a179c57a7ffaa02d5b1f92e0bc1e396f8be43b2ff3ed8618b9fcec359c0168182ebf8b87c8d1446a7568039884410f5924c16d39a75802fbcf13fafd595f562e52bf6479e86439aff760fbe5760ffb32c8a1279e40df6d59af7bef950e7e210d3bd10095eccb22e7edbc9fff89273458c5138edcd6b3c8790d8024c56d3dd80a5926208d10e50e1dd52421cefdaa880024a7db7ab04396480292d31d62725b0fd6b0251a00c986db7a100c5962121eae1259a20948b7f5a02b641da0cbd7fdb8997976aef2a8e97a0f86218b65cbda2e5779e474bd6f91cc3a65ebe787e209fd2d5b4e164356927ecfbacaa39b4588ebfdbc959c17ac618b49e8fb4f71c86f903802d2ed3eebd9f91e34df46498981dbcf17a94e2f892c583c0a0ee4c09405072f138710ecefb7e99e67f7ec9136ddcffbfd07a3ffc1f5ffbec9ee677bedb5c7df5ee59eecfd11f7c8ee821dd90fce59c3d83f3b0f3b1a765dbff7839d38028be17d3357f3dcc976ecd907d791bee95b803c3b3082b387b193b89cf3418ddb405cce7540eace2ee778b04a6264a5ea96485178b774e1062b2eff6bf6303a1597731f58e2ce2d977340bc71752ee780e0c01449686541758798dcaa1feed091eb9646658c2103e296464f408065dcd2688632ba5b168141821f504cddb2286aea561c8eaf2effe56f1dc0a179e98394ecbeebbeb8f300fe6dcb5c08dab8f4e9cfef49da28e9beb8f4bb2f6e598474e977cf7788afaeae1ae9f6776ddc79c4964b5ef2821d3689733bb2fbe24e9184a72233ad512b7927c1eeea5a9b24f3fd4998efd608e4dc105eae51dfce96fd54e6f547bae47592bc6d045e7ea46ef69739ee7cd9eb3ffbf9fd5d3401490adae4fca60d23e76e38e3d6ffde6b7d25cbd607b1be7ef7dffb834d860fba481bd7f32d91aeeb3fb2ecb643af4bc5a8afebc31769e3fa79c70769d80f36699384e270177c7afd82df645f16934c5c8b24b8fe458eff91ae6fd2c317492f48c38f643aee8bf40b76e147f20dee8bec0bd29edc7dd41b3ffc8f643bee8bbbd9d425be5f317c316e48f68fe4eb8ff88bec38e8fbfa6e029214f5b9d63fd260f308f3bf2926f9beda24f11bbedfd0964851e070b749f0c129ce1a46f05d24f8305cb04317093ee86f5c90cac1735cb00b5d64d177a9f45d81685ef0a98864d230820f52710416e3820f7247bb72e0027f0b2e86eb22792ed8627d83e233d75aa0f8618b2cfdb23e64852dd6832451288afd21eb5b248b26b00e8b9fe707ebecd8336aed752beb996b7e5ec029c3482fe76e78c19461ec2ee76e880173ad83eff3bceffb496ae0fbbe2f0d9049708add5cebbabbab5ceb7e921ae8ba67711124fddd73e5225420097c5f0f32333373cf6772dec9af906baf6f3203a3ebc50f1ffc0c8cad677dfdef33307afe7af5bf5edfaf31c05792efbf1659de777d677d7dcfbd7a2cf0abef2495796bfd6f72bbcbfdddfead36a8fef72c1ea0b26c8974599375abee5eff880fdd6ed76675edda3ff9491bfff9fe4ddaf83b59c291cbac537effbd937cbfff2a9151788744b873caadcf621226b77eb5cc3a247c64593fc8ff7bb092966888ab70b86b04735bef6e14ded6d767f1842997f547ba92444322dce69abf91776b5f7f164954dfc8bbad6751c992cb7a9c2111eee41aeb2bcb32ebb83842f7b592f3bf725efe8e2cfbe733b5b5674e8e757e5880ecc1bc7bb0f307a7c86d03bfb625d2ed79fb9899ffe33bc9a1dbfd6693437e5fd33259d25a2793ee43dfd5d5d5975b9b1cf25bed9077cfde7f64f93d95f10a511c0cb7ec1ffabec07075a9d4921544bf3edb72bcfc4d86fd54beb27f8888ca778b90eeb42c2261efdba86febbd6fd9a1f9e5521669d497beb5493273d7bf4524fe1ec9e57cef9da4d29ef7b61cba5dfa5d8974bff9555bde6efd26677777fd598434e423f0f77b437e29b5414aa892f96e83f89b59b3e2d03327768055c6166f787a2663152e32bf6fee52f1441f1f7cf1a5ede37df8fccc7359dc7a2e3243f2b9c673f9bd24f5c93abf4e92ef145b606b04d66b5e904524fcc1c69eb59ef5e04f11c9b425d29d2cb2458295a4326f392feb2749655e16eb2bc99782ae6987fcda8ce077feb4cf35fe792bc9b241f5c1a7e250bdde8321f862fd14e7d2e86f59967f82952cebb35ab644baad89a405daf276c19f248bf49eac33bf340092149759673eeb8ff8d0ed7a3fcb5be490b3fc59e4541afdc726f97e64ed7a6425c119ceff489b6987e6f52c6fc1e1ee8feb8900f426a431a8fb0f6ed9d989a5677c3937830fe0c4d23e7ce9037111229e4b043f12a7f1f399ad6793966a36ef99626c58da2776e9d70c29632c85eec4d233fa3c68a090e4fdfc23fdc2584e2c977ec9685cfa37fed16974f8e004104e4051e17036efa96d92382a821a4010431027b0252dd56cf399626cfc494b6dfb9e6900b6f91ec93400db0d8914e6df50701afd3975e97b7f432285ef3d7b3be234fa9fbd219182f7d3dede69f43dcbc3b9dcfc92a7980632a8800837c230820c2efed21f1283916d483a6071821f7e98a2cb1233b0b18dbfe4b9f4594ce17138dbc4d23e1f5cfa9c436dd06ceaa63f80cc454a1ea457d93e2dfa1da547d73b332eb55de97df43f1bc5b3a5f742ae44d5edacf7a5dbf276a92b612d6cb89e25a2765f1e5d21241fae17450049b9db7d496d59e57a14d6a2e9765f66e052cf46e1336e67a79e50b0f30515f4062df7e672aee9cb053bec48226efc47afabab2b1f6c2c227033632861650d1cfd39e4575797085c0f1a86c8e244d48153125238a3297cc62502b7f30515382a0248ca55c1b1164d36fe3203b77be60030ef949cb3a1896b342f1131f0193696c5f11936b637fea3283f7c000c18bee070b66a7149389c8dbfbcbddb720a48c34af253d2bfc506ec7c31c5958d9fc506c0c81823047758b9bab2f1c7aeb77702d02925bba7f3ab5df7fe951cbbce6b345cece2b8b29e754fc979410fe97b1d9d9776c3c0bf72112f65d77f7a7d67bd7befdefbb3bcaf9ebf7befdfbbfb77e4bca047f2ed90b41bf48f74bff7d37b1bfaf3f6b5a17078df7d5e47ce4b495ab9c6ed06c8dd532ee23d7fe791437ad6bd1997a767ddb3e865ad52df7f5adffdcc7fc0effea67d68cfea774f848b3cd7ea770f9263d3eab7c8dab4caa3fa8fac69f5bb97b50febbbe72db71c72bbe7c145ea3b55e891fcc1eafb43dd9b9ed5ef9f3fc5fa36dffbed6bf37dcfb91ad4b89cab018d2beb597d6f885e96e59ed58e6bfc2c528f9cf7a891ba7625406e9fcf9ffe47cedb91f45924a17be6da08fef4711082c503f8d3af4f6d592ffd7e8a4355926280017787ca0bdcc08eabeb7d11d2f56cd1674bfadd91423529061868951695545b56015924a1a34fc511bc67ae95dd47ceeb911d126a6d92f8d3f7a736a89fdaa1db2dfdf677b353007e0f4e1109f8e1775f49f1998bb8de7bd0458e8074cbf0c1677184ea0aaacf1f9265fdf9e014d9835dced520c6edb8e6b53842f78b64fd16392f08b278807e16eb4592f52d92e7b3888465cb79d996ac6716d90ffe9106990c1f24f9822c92c0367cb043909c9745125a97b956f283e00cc1f08f34872059490c8ce183dfba654d526f688358df2059b27e2ae967cd568be4cb22f956be5e475292f68cc3e95a409e32d22f65b77b9eaee3b9dd773fa47b5913b7fb9bfe695b67e4763ab7fbb17dfabbaeeb9e733a1bc4b923907932cf27207fb0b0673114daa77c1adab8b92573b9f4bbff6af7f7f3bee42ef77b229f4f594b689f7264a17dbe37d23e35f77b15da87733534ddef25d03fd4e6dc85b7dcefb97d6c6e19bb2591fb757fa46fc0263f21a608eef760874df6952171bf07b927bb6b685e5dfe3ed63ef4bfefbfe79ccf0671953fcdda074baee797b21bf6e176ec3e092ebba1fea37fec9200f20e19230d586eff386120bbc029c3487b369f67fc29e92414d0b9b87bcdedfff096e3cd097b46a3009e230826bad0800439dc20031bc760d4b288643e1db17ce9b17d64b73fac3c6d783987a58d4badac67937336009968ec1ab6cf1c3b00b91c1b745dcd30c70d888445871b9e7000a34b1177d85804e7734ec943ce7bc1497b36699f4049f06cf9d7fbca1f8cf6cc96467e5b2f6b1ff0fd6fdae7033f16cb96ac6f3deb0b7b36b6cfc77a7ff023e7055924873f451278dcd096e1b7fe23f986a4915fd683b379baeb23f98224df168be49e95b23bbffacf95b771d3986be5cd9db6f4409fd323f98a30bf9246dd9d21d86195eb3d93652d2b7fb0c9624ecffcbfff9e7d4c52b29cd7238dfcf24f72ec19dbced69ef578bffa0fc5e170389ccd7fc645fc7acee569cdce03eee493b1d6366e7f289bc187db7ff3649ccff34ee110c96bc42b6d5cce5d11e3967ffb0a16f7fd47a769fd8d047c79df5988b40fe7ae7cb9fd46a67882ff7c1681c672d63d11cecdc074fb75fa67dafa8ddcdcb44f3f933c3d6b9d9ef57b47922cce581c615e9d9e4d71d6b37e0a8c47b7b60fe774371d0790c9381ff48e9832e2703818669f30bb7da4670e23e736c0e57e709d72112e1a3977258acb972fd885948bf4e5d2bffbf9a00bb26b24bbfd4e96936888af6ebfd177e9839ea8e4d52473adf4af9cc4fbee5924c1ff3bd24ef2ad64d97d39afff47da28f1bebf6d50f7def3a5245f4f54f2c1f52c73cd9f8a48ba196576fb67dae6d31695700da2cc6e5b3bb9d6efe209b3eb96b9d6967b3691be498d00b9dd7f2673bb2d274e54f2baf49da4422fb5cccecc5ce3205dbc7a7693bd05387f82977332bc60a6d132ccc0bbcb3919eaa0fea0fbd0bc9acf62166f06f1724e863ba693f37e97734c42b02ee7984470653d6363d620037617b9d621d7fa99ccc0c839a62dd72fe7985070cbcadf2f32c9d4c495f1e775d535763b80dc0ee89e1af9ad1d69e437c9c4e170b83b9f92464eef7c4ae7146386ee0f3a39599c7fa4bbeefd5d44e23d73f2eb9fe2fc16bde7a40138b18cf3d9320d387f9224f0052739831657d6b2cbb9a52fae2c1eb688645eced55075653d6b71885ee61a7f8b4886e6d5a5e15a7f8b24cc4b9f4916f927392f9343f3eace771109b5b19ef54f52c6b57e273530724e862362345c846fff28e322ad20fac10e1685174e923d58d833ee597973e7bb2ee764c8e2ca7a361f64ea3db973790e20370876cf3dab5df7471a04596c40bf2d912efd239b3b8b2af8b7bd21d96ef4dbe64ede906cf48f746d9fcafed35f1af95561e4cb29cc777b3bb22d213124c2f57eda3a92da72deee3db2f4db7d93fe47bc6d2ca6e0b6f9479a927c9b641bf74fb521f9517733813c34726ea98ddb5dce2d65b90c63918479fd8fb423cda9c4a70246160d807429b521d29c0570b204e49e913091f8a74842503fdf20b64347977fb200b202e81d2ff8a29d9b8074fbdb863df3f7a79d4742bfff91e6f6b119a1dfdfbd9f4f6bfbb82dbb0ff2a7ef791fb64f5f6fbc2a00590123f8a2bf84e5f2fc72de69c39ed1a70fbe4c18fdfb878e2e1d9a97da0a80dc20b74f7bc824c8394a6d8497a77d4a23df3ee5cc48fbd0f7d7699feffa8fedc3392531ae7fac7fdae639d79fdb27259834ff120097ec88e099bf976002e096d4a1fca71301d3fc69ade328fbffe0961fb478025371fb6b869431f214976f49adae9b20e91af5a5cff405705954d25d6abb6e7e51204319f90b14e42eecf21727e052afecd2f940a0187279faf7a5ce65066282f173da87763f85726eb524bd030a19dcf9a3fffcf4ec87acc6ed9e26d9badbd95228e73e1b23dbe6b482883b3830f586153a9c61732ba048e3baec7e4ed3e603891abdcb3928a86ef9777aed5e80dc3eedd94a96f5d6ffb8c8e77111afe3229d73116f2ed2938b4cae79cfdeb441defb7b5e93b567f57bef67b565add7fbeff3bcaea3d4bdb9487def6792ef76d523eb7b5d3f3bf4dda07e4a96fe4548b7bf497b98028bcbb929a2e829a06e0bd13ede50fcd3494cebff61a6958d85e5c33c2b3bcbed2f6197610f8021978342ea1af5f5279a4d65be006e8b27787742b14e7f0f31f2852a29974b446fc95d54e8d9a463246f433581ce3c9b30da339d9e75397358ac6736582e7d1bcf8ed0d77bf642055f6ef7448c4471fb5930ea1fcff517a07d38f74413b779b84c223ee3ed248ac3a27f9240272438f7441bfd1ef9002b73900fc045910fc045d133be3ced53d621edc329788015b639447c76ec9b9e35ec0131e47ac677bcdec30fce56f25c2682f4c412414081613583191c61632a2ef3cf8ef40fb5f1330466940f4fe0da89266eb71351dc6e9e198ce53febfb6e0ab2489e32185bcfb7f5f3bfd6f741f4e9839d888465025292efa72d83be9f22fd8f0cf23e4a270f6bfa1b15ac19e286d3283c37c83fec197ffdf69e7e29c473ebb31eac64ed591be3f77c9b45d6cbfe536dfdb59e4065de69a98dc2733d1b44bfb32358aeb3677077f7f9eeb3c7985eb610caa17d93dc44c5718650cef422c4236344172210e2f9beeffbbeeffb84be28dce5234beec25d7478867011f0f96ffa67e6f530cf1f8e3d00a6b7c9c4629e5b16461221f331b171e61b1623ebd9477bf67d0c19ebd9f733e437edfb1639eb190cc917f6ef3f32b0960c599bf63d8c1c7bf6fd48f2f4ecfb1739a467dfbbc802f4ec7b912ca167df87a4093dfb1e2451e0daf7ec3f449af67d3f7f8a23f46d59eed95763ba0b0128da92874b17c6e2b22dc51785786e149e1b147eabe4b9b30c6dc973c3cf893dffb70fecf9cbd88d79181869a467fd317248d3622cf8230cf94e9bffca9189e28ffe03f3e3f7c3902c22196d19fe57fe15df4596e3156d19de72e48bc20dfabe1e39d2d8b487cf96b3fb31598e2e11c808c67f1739739aebbb63f7fbf0b97fc2a67dff3d7873bf77912212d1d69e7d7fc443922c6577da3246b2ec67bb882ee3f89e9d43e61879cb9d638cf5997462ccb83c1632def295467e8634adfbafd67194c9fe673c3debc68f2c6796ebd9982582c575e6be7624c039bbdd29ed989cff8948645f8c6cbd18d97a3306237fe7e22a76e8206d752c2fac3d63ffce3fa8b3b567ccb3d6cbcc9fabac72e9e5cbb34507a50e7ed37b1aae75f4e69486d487be77e1e4e2fb96eeee20839f0472b808e55cfade046bc8e4bbe82efd4f4442ffa667fc32aef14f19c930a21d2dbfeffba8571a399805e46286dc1b605b9670895cfa538c71be1091cbcc52e8bc80bad3d9656a4ba4282e7df79f618cf35d88b75c3acb18e74f2e287da702049fa76e39a48cf5112ea202d7c42fdff5e5ec87884effcc9ce6e1f74cbb7eb5338db11c5b3cac073b474906ba30a28ba62bc4c3c5849eb946a74d7186e4bc2209640463498488ffbcdeffc88b046718b2fc27d6347fff58f86a8c446e8b88ff2421517fe96daff72f40fb94d5a7be7f09edd3ad677d68cb1b6f81ef42442eeb5d547253010e42405d21ee7259f4b2c8b23d7b7d2491c953617b4ed417e7943377bbef723b59ff94e0b4ee8938ad7b7e06b2c548ffd6b30ec8d408648ab19c5aa696f6e96f6b342f121fdae7733aa27d3e4b0454fb44b58f4d4f2cb77bbedfe8b4eebbf17295a55c05640463f725a8c045d0184ba6badd3379d23ef5bb670137a56fed530acdeefcfeeeab98506dc95c5c44e259233a9be05af71d4219fb482023602d3dcf184baeba5df7538c4a2255e6531212f497de2644fb94d3a97de8779f0f8b48be5aad106f51a28121b8f042758598ea4e7a3d925e4a7208f363fdc3b66aebcaf176b652e7fa7733f7d9c0df97eeeeddc75dd7f1cc72bcefefc85276f9fbb2eb3aaed1779f1e59c62e7764195eae43b8c66347070d27173d3db2b6c6e8d9dab376a6773ecffe32bc73ea08d7d8081109e480219433847f96c3c577f937936a72573bebc541bbae637ecafcb2aeeb28384da0725ba270fd2759f2dc39e79ce5ec9ad0b37e27857898cbed233deb9fbc85889139bb5c29223d6b4a96425198ea064d8b44a73fa1d333de22813b9fff36c5a8bcb94239977bec596cda92c89ddffd4ce7bdf7294444c65bbce9348020c8df24ebdce6b94de476f9df7739b7ab48c25a8fb450cee5a79c33e43291cbb2d28bf5cc93fdf5a6673b7276bbfca6946b311691944244eefcb6b3fbc299bffbd2f3f83dfe208fe716fc938b7e3ab718e7cf1890e075dd7bd77d10a5f426d67d4929ed485b32b9dd53727631cea746dded3a4b72d9755de74f20b70fbd6077777b7bcb00f697eeee3d6bbfccf432fbf897dd3788a8af5f5d3107807ee9de414e49a2795b095f5d5d6effb2bb8d5a890817c9a76701e98325330d625ba70b2a7391c945f896f4b9b6c6382f7dfa5e7b367ffec806f885970dd0e6394b505582d9ed449313b8ef8201726fc0eaf60c07708740ee12702901167480f38f984c7c694288af7d027039c744976be4d7bfc8fdb84873cd595150b18aeec585f791f1b1bcd4d7e55c1371b0445ccb69006b53a8456c05e1a247bc6050239d02165601d3da2226468232623130323cf50a3855408239aedf2e0196db3fc4c80765c84528002e915bde9044fe4454c6cb3ff4978a5fce5d4af6b8200d295943ca2ee88594e471c12fa4e4cc056b48c9980bb2424ac25cb0155232c805c190923c170c434a861714434ab62ee80a29c9bae02ba4a477c131a46477415848497a419890927ec19890927dc1180c2c2626e3b24446e3ddc9ddd0b580f30aa8286e49a19a8082f280169d86c262ced9ed05ea0caa840f4a24415929d1841249283184a7441b6228a1c496fa8d97734ac8515d31b0bca6568b0360750ac1277189dc12276831e10ae7d42b146364282e505bc46496f012c25c2616de662249092c4b58dd3126b3c41bb5c28493891a6238c74416d8f8728921d8625518170c16e04cc1571954d3edc94dc059c41836146e323373126d24e18593f8726524c87c11d39fad4a9ebaced39673887333b72a798ada72e6b69c35151628d87f3f8e0ffbbcf88f0c4dd9552fc8946d35f323e3f15134b6b574ee3fd9287694d31aca69dd4f4e6ba75c12b95bce2a9eba652f4199ec74923dccccc7bccccfe75156198c2dff636c29b3e5e8d45aaa6664ac66aa50cdcce0bccc30cd3055cd34cd3459c954dd923249d9d1025bcdccd4cc8cac8b4cf6fd4de53f3c686c6c2ccf6a6a2c0d0d0d0f4b79f008656f4566ca66acf7f3a7d37492eae9659c54cf1ff3b3cbf3ebd9a614fb39f52fb2e78e71dad74f29191a1e5353aacb949215999fb132a599b7223369ec0b323b3363a79722a4db39ae75c76c59636c39c2d85206b3e56cb4e5f7bfc8ceb195950b9ef84007d0880304b6594b2db551830db87165fb5c8ba8343c80abc3043656d73147087032e08ab0816c2859e9d2050c4e6063a7b95e7ca79af1c2644714554d6ea585e25ea04e2fd0a714c0616b24a66e4999a46ee75cb3d9d8b2a54c566df9f7b3b9dced9fe5b4388d9d3c5bd6a7ce962394d3f8525bca8e2411e534be6ecbbf50311dd303c83925cddd999f89e241130545c38366522d4eebe741e79cb28ac9c46233333433333c66666a64646256c94aec9564373ff333df3236375f4dcddbd467ffe161797c0d8bc682cf36997d61c6be20f3ad076913283e65125f4f712fd8bbd5b7555b5575eef3a8ea50ac677fe2a9b5bdb853d15792b1de45e6adc4629e6d4a32d6a73e86a45ae69496a9db1ff3e59c1a81d32d1b57c563d556fed34f4eebafa3ac9f6e3f68ad1ad731b69f26ae9fc62a1be7d47c9a5652d3d64f9d1a14fff55552b7ab6e4ff194ffc8c4bac81e05a74d2a1e305e669e9d36be8cccb3d35c6fc563cf4e0bbf63969d063e28533ff6e28c65a7b15ee65f3c2c3badf5330fbbc92ccff7532dfec3c3a2e0b4f93330b6e42e773ecc965566b42553ddf92f5b8e31cb4e73d992bddcf9a22d65a12db9eace07a996dbcfe33f2d5b56962dc7caf3f178b6ac9dadf3eb6521c1348c1c20e780ec3face707adaaa68d454e1b4fd127a7e12ad3d8c44f71fee3ef24d3323631ddae2375f72e5053fec3b62e54d40b7d826a2aae7226ff513a8ab1d5af4fadf0c9296c396979a55ba8a5c97f94beb7e255eb4c4eebffac37b93375d465bafddec5f2fbf7f4bd9fdfb2b63fdb9e65a7cda6a6f2a2e5f6971d75fbbbc97fc62a1bddbbc828eef697dfdf915063e950936d2fa34fd73673d38b97cfab9a39a8692515859bca35531729a7b537a99a3edda64ffed3475831061a1da86105b67e0ae53fae830dbe1061441054d8fa6994ffcca519c0b8b2baf2c2d64f73fe13e40a1ecab85ada62eba752fe339da82307263a70c0d95a4bffd4e23fb76903bf5bdfacff89f21feffba784fd55fc07b42538adb79838ff695914fa2793ffb0ac0afdb3a95a262f789605a7f537f52e5cebf72eb7dfddbd9f8971d2aa0f89f6584515ff61b0b2be9c5a2e2f4d1beb5fb077f173abf52087e2873ffeebcbc974f92b09b333cacb6827d4d2b4bd443b9f467e1496a60df6adfa254b7dc939f04b2297c32f792effcc7f6e283c3f8c9c52e48b9c5d5ce4a422a717d6f3d72a4ef347c169e2f38f22682753cbcea6b6a5cc4e2d4e9bb6fc3bbf9c52f367b30abe3f4f0debfb13a995b258ef5f82ff80e0cf9c361fb4ffd992a5eefcf23d5b724ec6b263674b22d569d4963c7776cd8a01e49cb273255b3dfd92a7a82dd996b3cbb664abeb930463399d9e6ec9565655fed3a50b1595172fe594f29fdb1fc5d8fca793ffdcc4f7befbc207bf64a9af6b3deb4bce755ffdef4b22b79c4e2e7b7b41f4eccc697d7bc1fb723a85f641a95ccbca589c8baa76fc884079b6b2d34a9e6e2175d31480a327fb76da7ddebb9effc3faac16188ab19779feeaaed7088b81791ba6698bc9ccf0a8f9a4a56aa37999272df1d86c6c0d0d199391313c666448598ca4e120f363c8fab15a1c040c6b6c6e38480f1f381cc425be46184ccd746fba8db445df738deb9e7ef8370773b85ce35e56bcfef737f7a77f0bedadab9ce650537846960b1c5b7ff3b1a79ce6b73975e3d9e404c29acaebec6d56fd847a7d2e741c2bd7385c2ee46155415541b1fe6b2a707cb6815fb693d89a4ea3eb4b1e5ff65db64e89a1958176ec26961dab954d1dc5d83e5b9dd6cf36cfb28dfa0b69762b0b9073ca59553faf7bfef71ffafc1ed991569c9256fc236f325fc91b8f3f8ab1252d8936eafdd2b4794d947e47ff567f69daeadfba168bbb5f9ab6ce5b323154ad5f9ab6968cbd252d89b62426116c32bf346d37245288bdccdf78d8db8c5d9a3619ff987f8cbd4d2aa7f9c3d8a5696bf9c3ec14a7f9b3ec8d89d3fc47bb346d9dffcbfecd6597a6addadbe8347fd14ea71b3bcd3fb44bd3e6f9837636396daa7aef8d9dd3ccd129ef998ae7e3aad69793aa658f626cddb3ec518c8d3edbbeafcfb6fae584aae4477a6447ca9c369f92efb4396348b3bfafea8ef44b59f9b79cf5dc31127dcbf5ce62bd7d4104bf5b2dd0bec00a9f3efb4f685f68bd7e3efb8f52d873c73834ef2765530affc624c4f6b22f802efb42f879569e95ff88564914ad67e534ffd02a85e1f753eab3e583560904676eda52d6b24aadb7525fa96539ca69fed47a562cabc47a2bd5be409f6d4a2ccb4eeb9f504efb703d778c44def7cf2546ce2181eb27a927ce2181bb9e37359f9efce766c58f626cb5f5dfaa3259fdd638de723edd9884d858764cd9acf80bec3411aa6c4f3d778c7fdb9673887ecf1de308ccb5b256f6979fd44c629c498ce5e7e459959f5397f9d4b7fca43e29ff994f4ef3af751c65b25b7e52d79ffdffe23ea74fea73e2336ee94d7956de54cf2d3d9cf7e4e13aa82e1d54ec9694aa63a26add9236d128dad4ddd2735e5c8be7bacab5485955b513aea59eaed55339ad7826ee4beec253d6eb7389712a31cedb5fcea7afea32074715a9eb97179066c766c5a9288e15ff52d361ec26e78be9b159695cffd2fc6cfeb2390f9be3d8fc039bdb9bf74c426c9dc454b359712bdc92676ba6b675cbd630b6aeb1b5b53508b6b6b7ee9984786c24c9ca6763e2b1f52fb9cd3b9b8b3697b1790f9b7760737bf36712d24ebbb637fa4731b6ef6bcf7b2b2eeb3acf8e6e6f4c426cfe4c426cb718b7ecb4f94731362621366fd21cf8fec334b6d34f5a7a99edfba4a5d1e63df8fcd57f929886d89262bf44c4d6faa5d096b4f436d6d7e797f94f12d34d28be5c3ffe528e0d7cfe318969664b5a2262837dd2128f2de6976636985f92d9c64f5ae2b179335bc741e6272dbd8d7290f9cc21cce78f215d1c643e0c297290f930f2c541e68fe4c821cc7f91321c64be8b8c7190f92239c341e687240f0e321f24652c647e8bfc38c87c16197290f995ac1c020799ef91351c647e47de7090f994ecc141e63be98383cc6f128783cc3f8a99ad05e8d15f8a6123494c3c36ff256a6ba71d7dfeafb25addf3879dadaed70883f1e78fb111262ac34698c21936c214e3c146986e646c842987868d30e9d8daca9c6684a9deb011a6b1071b6192f960234c6febe7c761234c331b1ba1964536e276ac61236ddf69f3f98fe86c2a9073ca6e0a89b1ec9e3a29f1add05c14ebadb00b4e500714d3101bfd9b9527ffb137b732886013eded86f3eccd7b1944b0b1ec0de9082a64e8ec0d034a503029b18226ae6c371944b0517b3b52c38730b43c75c9c1e6f4bb4f5a0a6d5e8faf55b79f296663799de7e35bd489aae3f134f63c62ddcb4ce13ccc146da2f65dfc1b1229906fff063ef9379b07e9e76ecbbe86e2fe657fc3ddfe9c8b1c7ccecccddf904821e667fed6317ffbfec614b3f98714eab6573914cdbbfe0675fbab6ee7d89bf81cd8b7f63692f6760404dfc6de68aec6dea8ccdefcade778d81bc7622f636f74caa7702c6d82c9be656fd487a5b4875dd2b1d5677d39e658f5a6e66d605ee4f1dd7a98538beaf657279967395131958dbd895f639938ad7fc6de50705a7f8cbdf138adffb3b7251ddbf7a1bd5128af72a886a2b15df53cc5e3c9441e33cf637c2fe6f523d46d575d280e21c40b47257abe100422480d636ff0b0421925c6f9bee3f44b77770edddddd8180618e12351a86620e3a345dfc10c3d02592e0e038b95d25d205e1be42100b1a1d43590c66b090470c1fc0843de0f020268c3131cee79c58c8030b0732618b0576266cbd80e411be154f65a10f2a7068c287818f9a3096450f9b7044c10dbd0963399b1ea10f236a7c846710118387cb0c8be72d3223e390a9537322dca0508e713e75777797c1096169c4ea87231731950c7d60016343191630ca41f82d8d342714a35e1d842010aeea410802217e10be72086761cb0990d2c2708c560521fcae60fd087d00e1e9842d233a2021521312e37ce7aacb5f1391f07750e37c7777770a24a470305bba13864174e5093d2e6690b053639ceffccc3b406a3a3f40a0cd3ef0a0831c0e2cf9383e7adcd8d4d0c878ccc8c46260785c43f3eada24e1b93eaedbd202b608af5e3dfc3aea125fcf2292f18f34c82212d81f6998ef594412c3748c7d0ecf902305402105264fa618e1926653cf3a4bab95e32d5a9ae6985a9a6696f6d1021391cc2ae66c82b7cc288edc1614de64ea59e3dc0027132ea97d7c1ce793771daa00469c3052654a1b56538caabceaf0c1881025108922c2021339bc3079c2822b8e26225a4c1832c5101550f0d20585145410d7984943b0a0c043029e128ed0c152470a5042488615cfc4a5a053021d223c7610e11942045ca30d9d39549849319bbd0c669f336bd54135f322811c26847264630d1e7cb0caea773f4b4452ddb84e2f483a2994735b767eeb33c1c82dd2a8bbad56cbd69b198ce574fad6b784e8a72212f08477f0c90d15df4d8cf5e54d7973c1564bd602bf7ecb96e07f7fc46364949b5b3ef944245f3512c7752b747363be18cb822c0aef5ba4904c268bc53e161945764b16ae7ffdde1391781f8fecc278304484622808bf450ac5ca5819bb2d4a416e9fd67b6194d82d85aeff4796fede772292d0bd1cb742b10b0b613a53c613bce8077e39dedc502af47e0fb6c872bce00bfcd7d3fea14d03fff5e10c403877c07f3d8fafffc872ca108f4f45242f6b44ef28bbe3f82f925ea3f009d7b748a1f00365b2da7a6f916578c177b20caf0b7cd7d719803a77c0773d8b43e1f56c697443f09d74bd8b485ce183e477757555c70dad5078c50767e822e975d156f97018f9bdfb1669e4d73ba8919d9c2c67cb967f5bb29b1bd9188b8de16d7d19b640f0f35bff48b3482a2db2acb7f54e96357c7f3a03c073a7a4b7f5fe21c93d6bfd1107ed90b7ba67916595a1d92292eeb31e299e403b6a67cce4a00890bf2231f2cf0180ff334aa9fd2029891f1b500308aa314b07738cafcb393594408063ac76ccde185d7074808291950608368ca3173433466f8a668391750508698c2214514fb33ad200c20318a4418307b934965890460c52e377830fc8183d393ec861a46f7840c358d360638c0ed0183b30406862ac5b7800c4c892ea800553d0e2780206682cd102348ca08931d6343ad862a4657460c6c802c3832c63ddc28df15b0108478c314e80f0c4c81ac1075d8c303efcf8800e5acec0b2c6194a5db8d8f2410f638d0c3c9861ac29f062fca2e8e089b1b5841523ab08da0c660c7141b0327e977366fc70671c3043cb9d9fc3050f6a9871cbe761dd3c7f09fe439f7f8afff8b3cdf3cf9cffccd6f31f61c17f68cf38cff6f5fc44fcc7d5e3397cfe09e53f391ffb98e757c17f609e3d781e9f5ff6fc28f88f8fe7afe23f33cfa408822deb05e19bb499fd07dfc177e48d55aaf99bf748cbc1dbffff481cabe475f0b3af24cd2a75f7b407c91cab94f3cfc18b648ffeeeebc3480fac07effd072f439256c9929ff33c481f568926f3f56948259b57f2f15978b9207c4cc7d6c3c7dffc8b54f261affcb02989efe36bbe5563afb86c386f639568fef537f68a8c4de9fb9a97d97cec5b1f432ad9c4fee65d37effa98bde236251eff7a16a97463afd46c4ab0bf717deb29a9e4b25704605302df47cb5eb9b129b5bee66dec95984dc9e6e9c3904a32d55ea1b129d5aff9b657aa4dc9e6fb9d66af4c9b52ec6f5ec65ef16c4aaef7f19dbd02635392f9d7cf904a337bc5c6a6d4bdcd07f64a8e4d49f6aff7e04352c9037ba5655382f1ec95974d297c1fdf81bd42c4a6e47dcd8fa4921525245b8ebdf2814dc9e6fddf5ea1d99466fec5fa7ff227a9c481bdd236a5f16fde8a1292cdda2b03b029bdde87556259a5f19560af04f34ab18ff92bd3e6e3497be5c7a6c4fa1aab54f357a6edadd27ca57e257fa5eee9e3904a3656e9cab4d9bc0fabe4bdd2f74aac9fdf83548af91bab646395645e69e69578bc12cdcbfecab4d55825f095c257125fe9f5ae3fe2209047b94b43b2d3f865e4cc69fc3c48149cc63f4356711abf0c399d9cc61f23abd3f863481ea7f1c3902a380de734fe919c4f4e637e1749c469fc22c9c469fc2139999cc60f9213ca69fc2d52e6347e1679c469fc9564c1694d4ee3f7c819e534fe8e7ca7f153b204a7f13b39c569fc4d4e2d4ecb398d1f691ed1ba12017283fcdc73c738c284e25abf0a5cebf6c058765303317ee397b381184ba6fa619cb7ac138a8b14f10f054dfe8f6a4e8f6a2fcfa45d62087e3b8d51dd62d5afbbbb9b54e02245303f14f47abe4530b6a9b856ebe787d97236bebe9c512b98ba8d858bbcec7f2d8e9a358ddf6539b4207b5354546b4671b5519f8dba1dc5b5fe26b6d591868061b3aa4175db73652dc752e6e4d04bfc21309cf3f54114ea5f41f4881b97f841f44926065121626010751abd20ea43cffa893a5b06512d3deb328836f5accb8f36d1b3fe209aa567fded14fbf7be7bfafe9ef39f98a5514eeb87b1ecb40e46479bcbe572b95cee874659772fb7dd8b7b712d5ab468712d2d56fdaa5aacfa55454949494949b5935454545454543b39393939d1262ba7282b2b2b2bab693535353535f534f5f4f4f4f4549f260e377113377113a70277e12edc85bbf0f0f0f0f0f0f0781dad4d342a2a2a2aaafb69136da24d54ae2ef234e322e00fb59c862068cbefd97ca2962d6f7a36653d9bb19ecdb167734eff30ca25866054944b0cc1a828aa16ab7e54542d56fda8a85aacfa514545b9c4108c8a72d9b28ab61cc3a81b2515e54d2d56edffbca9d56255fb7969b1aa179d5c2e97cbb596afcb75aa5a4719ada202499d5b555555552555252525252535a5a6d3749a4efdd3a98a95959595150a3cc5533c35f33a3a6bafe7d3ea55fa51a6fe80538db2060093d314b79c5d3ab2ec926b531c8173458a3c5abba9fba0f97c51e0daa4f1cf6c62f1cfecd234c50880203272a4b9eeb41276fdc18ea47736f98f6cfc88fa0ecddb3f9b6617ff91c9c6b1cbfdbc26a70d5cf3aad9e7d1ef1ec67276995daeff6c721f6532dad44e1e0c40ce017bee1887986b1f0a5c842f5bb23d307a141929b841e1d77cbf47d13e6590f83639d7df6c797d0fa9f17d60017b9c29987f2e314f7ebf77699f92af626fbfdfc5689f3248e639285dcbd7e780742c3debb7a433f5ac9f243da967fd4f3aae67fd3864fb20dbaa67fd3dc8be21bbaa67fd3664d790eda56773d24cd9e43167a60ce9627817e7e2538e854bf916cf79141ee53476b716b0c5aadddddd44aa8f54d6a19792bdcc5cb3e03fb58ea34ce6e57e2587442691d9fd50277573924d5cebff6c39f3d23ebdb47be9597727f5ace7979ef59ca367fd234c25d336960e8ce5f40282b1e78e916f0a5c643ecf8b4596cb392c926ed95e5860753d4bfbf8f45cd3ed7ce1fc5318e2f926b2a9b8083b55d3d7677debc1ef20c6a27ef7a74ced1334ad479111059628ec68ca65d9a245ca072c9ca684e0f2d43e41dee58a1ed13e412ec6d5d597ebdf3e8c657b89baccb5efcb49e54d5eaebf37799337f984729affa4f3b3448ce564bafe93a9a96758faa7b364e99ff6c187fe692184e89f3ea29be8997f3b8d2591eb445eb6ace0d8126d2963fdadb63af27091cf76d4f765375151f97ca69d37a93a8a8b7cefad6e7a96fd6acbff9494df1677529df15f4ad99ca367d496e3d06ca367fe2328999dd481712289930505a5df7bffd510fcd68b1fbee892c17ebc8979988f199a5f24f3b197b132b11818d8f8728921d862d5cfeb6833d163bbab5dec76a7dd77d8c25f6a3d75dbf676e4bdf72fffa6dd1fdd34adfe47acef64fd47ded3efabff512b46bfffa8be077ef7fd51ebfd975a369817addf7fd4bd0763fdf74720cc78043ee85a6ad9446b0567fcdb52cbe6b256705a7fc40261fe1efda3eefb996a6cafbf3deb8fc0ff7ea9651bad151c97bdb1beeffa47fef4996a6c626841cb2dd6d3ee8fbeaf7f3beabe6b59fab72396ad7f3bf2af7ff43dad479f773beae891b7fd0e01363f2840ea03b8d93c538dcded8dfe6dc9b3f90fe11c7d5bd8dcde965c367fa6191b1d9ab4b3f2ba62aab1f9dface02cb53a6aade0dc8ec49f792baf1a5b4d0c667481b0f075139b81195d60cc0b269b81898d2e500616139b898d30957a3132638f99188ceb8fc21f6f40d6d7fd91f74e440f1b9a97752f93592b38b7a3dab2a1b156706e472d5ad9064db69b9a9918cc08fe51f8aebf2db56c36d60a0e8dccde986a6c3ceceda8f5333198d105b2beee8fc2f7bf1dd59f89c18c2e90e57f14fef7b723fa33b1bf1dc93cccdf8e627efcdb11ec5d7f3b7a3df8b723f1bf3f0a9f35637364ec18b31f636330f6066665a395bdec8dcbc6449bf347a17dd08e2dcb2c1b56fbd9da21f4df8e3c1b760821f41f515b3b84f96e6987d0cf7f6433595220fb73fdaf7cb074bdf83cfeeb19e661cfb18f29737a46c690dfb37e1812468ea4cc458a64488e2d16890433671b20b74f10578d4145b8bae38eeb4135280c1a836241b2a09ba0b7422204e5f8d5d51dd7e71060cf1d2351918f21fe21d60fd51ffa7ec8fba1ee87e80f157d53b7bf0809d16422b6e5b39e8865cb9bfa44d596b2ef893e5bc6bc27f26c39764fd4d932ec1913515b52a9b9fe44fddedc6da9ccdc69735873c600e4f629f2bca2aefba12216ab55148645afd758040313f345ee324533333c8a643f34bf48f643343688686868841186e60fc57ec87f08f643ae1fa20fbe92fa4afa8766dbf2f6a4b1a56cdaf279d872e62626634b8fd952068bb1250ccc9631d768cb97cb9623f8546a405b862d5bb2a8d45cfa546a6e2da9d4dccf96df16d7f3efec10ced5d5d5a5b6a45273dd96df16d7bf34f2ae5b2a33b7ad6cd227c09b1eb3c8b44c33f3f26a6c280f598d89c9843d9b45da8e21172132a2b7fba1d7a532c3e2224446dea53f24c2a512bb5d1122a3befe437ea9c844eff95d9e8db5b80895f07645a8d0db15a102738b506951e94bed4dcf66112ae3eda7e2ba5e848a77dde6f46c1669eb47804c9fc8e87b7eda7d5eebf96b6db15ccf1f82a14b7cfe1136fe2bf6fc31d78589bd917763de68e60ebd2e8fe7978557e689a8d0cbe38de89d79a3d81d12e1da1019c15cd91319b52ecd1351f1aecd1bf5ad792222a3f1fa7822a37a7bfccd1bc9ee905f1c2223d725a2d217c736d7e61b7977be0fd2b936bf0749b936ff86ecb836df8664716d7e0d59b9369f86fcb8365f467a5c9bcf830cb9367f8604b9365f866c716d7e8c7c716dd66a23d7605c83e1da0cd764b816e35a0cd768b826e31a0faedd70cd866b355cabd56af3917c4cb7ea26a9154859b1245585941452725f4460031ee478aafae28a10b0ae48828731368e35a717d4000c28a83881162fae08afc8218771e6724eaa885b8eef6b744edd690f358c327777291bae63b1b9ffbbbbf70da3eb724e0a861b5ece5d41c7ed2ee7aea8aa01581241dd1e21c18f449090107fdb2877fe5123cd1e01acedd34dd6f993e79c64cfb9022e5cb8542e5c64df5812b9a38f7fe3e0e978f6bb9c9e7161755688e77a9ee7d93f03acb4769de75531c6f5aa80010c150b1876f0210926a82742d0c515705cd1e50a29afbb2e2d83c7e58aa4eb793ef4522b1db9e08d29b810e38c2fbcb03117ae250fe74ca29e2d5e688163ea06343eb0851561b0ac30e3035dd7759d155bb07458baa2b5250b261e991552d430bb12b028a55356f4205af154831532b8acd0e1735070cbe7f18cc86d69aa62cb16301a872e72d862820f6879838c2b1b6dc1a53fc271691572e8d04ab1cbb92d465cf0726e0b102ab83296fd749bca7f3c2fb3ca9b534ef3ec0b1eae8b6745c5349ba6962a4f4c3cfef37d7f49a47b1b25df69de5447da34807ef79d7da13eb5b10d9ce1870485044c9bf77d47ffb347ded3ee864403e61b245801ce569ffeedc8fbefab3df2fe85a3cfe25c0c1964c1d9e8d3eefb65fe4343701cd94f651f71e73fd934a07befbb299b06d0f79ebe376d1d3992b643eafe3cc0e33f6de3075f4f77faeb5d4fa442eb59df302caa70ab475a01e20b302f3a6883bd5b2414a6c2bcdcf994eaceffaa5ce6e9cc53d807f3627d507c5004ad4d3fab71534e9b0fd3c5a6adee7c5733b55337b5963bbf2389ba17c1fa300f635f807d7db689301ff331f685ef619e6de0b745d2b18f817dec5954e1fb986f62baf3452d777e8cb4f11ec9167bb02389fa55783debfd895468bdebfd871a70033ffcd737e026fef8ad07a7a802eceb4f5185ef611e85a4fa30e1d787b136f4679eaa386d3e8cc4d9d0e974e7cb58a8182975e7c790365e54eeceef4652e634a2a27987868814a0801f2252c1a601ad7f7dab9f48059b06bc5e10fff56c83f9b648baa70ffbef41d8b3a882f81f0c09236dfa916cf5412a2a806d49ad7f3500fcd6b7a882f8af175508bff52caa30da97b5f1774d89644856929dd6226ddca2c01d42974ada34928d9d763422755d5d0072775227c5643763cc47d9181bc7366698819513d440b5851646884ed45063e3e51c1549b0018eded9efd97432a767b418dc510618531da0428a2c7ea82148a25688e7765dd7f97377871b556429430d2f6164810596ee061afe676b7ccefbcfe83f9ffdb276dd8fb8ebdd6ee9765d47ed1af53f27bc9ca30204b77c9e6e066f94c5a8e8c0951e65e5786354d8304367a9b8b2e44d41b798428c18a618c30a8fcbb929d0b8f4726e0a32b8413abf2fc3eb799e57dff33ccff3bc8fa43da39ef7a3ccfb59fb08f15c5a1b07907368bf0da57d3bb8f38914a07f2ad5cc9dc0eacea992e7ce1c0a7077feecce2a140c714b2377caeeb4bad4fb3ec08ad50c99c21e1083975bdbc7610f88c1a9677cc1c9ddaeb6338e31e672ee04572e4f39317af7a5ce0562022f2935c11b9732d9e8c7fa87da28a5b3ac945213b07129a5fea3db8e83cb39135c71c5cb39134071cbf1697b1726f0c10447c891e3e58cf096cb43faa7daf87352545dd8e59c144997c5fe233ddd18633fffa69c1d19eb99900c9c88c1972b1a8050818d5e71e98fb92a2efd9bfee11c0e8793c1463fa77fd8466de95688675c63e45b8eb7dae6fc26c79813a3783907051cb77eb9e5f828005fcc3e77a6902f403a521b92aee58ea45c73bf8d9c7ba2897291eefb3b2e22c7954d27e9144e97a7e8aaae3f38d4fd34724e055d9e6ec9530eb502dca5560ed573c7d8cf391574f1efa4fce7880aa09ac65153b92e5254d75530d534be65e7e5fa774efe43a7a895ffb49414e788102969f5c68e366e8a8bb42dfa7ea8e8b3fec435ff69c535fff9d4a58318dbce2f5c73ce498971cbee72fd5b88b1ac01e01f87fa1ff6e11f0fa54da8c4a9150cb4899a041d536a06680441400353156030482c168e08048a26d7f80114801098b2545a9fcaf42888814a19430821841802000020002030334c0002449b3da1f71f290cec0d6a2eda91cd9c18032ff0dca5a25b87109f947ed71b47ba08981d9bf2eb8274611b9bb453e3e7ac8da1b052cda092de2ca54eb20d304da0082804cda4a1f3c20b1db431f06a0187aaa837b4efb965f92729070114f4bcea59b995635e6ab964472cf4a36ab4b19709c2ca9de559662a421ffc529d1a7b238b3f2aedbd15738daf14666fd4f24795bdb76256f795d26a6f84fde51b60d7db9446a2d08e90226a9d8d122b1a6cb88ea4185e8ab8e828c0b547bbd13679d2e8eb62008f3df7c003341b2a56a1313c5bee8c133f55ef2b6be3a8f64de1d95bb8527d6f859fb221f71f6bcd8b6c8a9d7e5ae19cbd1d83b6ef5dca812c912fb71f29ccf63e3881fc2ddb5d2cbf8e2013a84318cfc5ea0ddb63bb22e7865cad61f213d625e0059b3965d0d0ba019f1b9c8f463543f237858748ee5ea3cc8ce2e9169db250f69c42c4957ef25f05bf927d337805e7a9c127a68ab304437b4fdf1cec75c4976720c5cf675cbc7c9600104ec31deab1daa6da90ea2e68b2d3fb183b47fada1739a348859ad424dfcd9b5a51a636050bdaa24005bb3e09d32a06af898c3ed46bd7ac2df71250bb69135da4b3230e9a600952a3cc8dcc1ef833daf8af458999fb360a2a0b55ccab6b747098d712665194cb54527b1c476b4e091333379dbd6d06867ef58142c14f115b2b940d25b4d25e5dc424683aff6af940c398b7e6d19b796ea7247d57f6f1b29f5a3042643962d66c46aea38ee900a581e4719715dac6a44d115c8616ed20a5c64ae32713d2162995829cb618dba14a67e654b9a7aa0ad972cd3565b969a3ac543a5ca6a717047923c352c8baaad3acab74ac18f070c0da4cd214b5d89b3bab2805208b9e7870c36ad252ece6816850c9a571009a29569705ebaac0e73f4d13750b574673759a8e009465223fd72f4de9a84d861936f12ed4982c4395d1424c35e51c48136a3bb71e1d79b70b521edddfdd699a24380f1faa9b9e956b7b3d41859a768f66a9d89e720e4ef1c4423e57ef896749b13efdcf6ce9e1356c06ce10e06fa93e1305e565f67da0669b831e088c05fa2f19bc2ebf67657f7d691c9ce87d1f5761c8bbe256fe5cb3f027af104fab7322ded5187c0c5e42ee42ce8a270b5b252e3f050235054a7b7bf28ab5bea6c6b4f4964b56e2cb65ff8d2cea2a8a4b3f71ed05c0442127b2aa9aa3d5949d533991365698308826d8568c9a443f916060611e49de48596136deddaaca8651c4e1c762906a01a1b40da9f7888ada2ba960e322b592daa3de0455ba1582813a54d77f8d896cf131506088a9bba9f2e218a5a46f5ec66a4faf1697b1753c30fa2d759189369d38b8d6059246990ab474780e435d3f1772b935304f056102dd56386b9372ef6b781125778254d44995bab3eda025a1b15072bad43cc1afd5682b16ce25195ebae8309c013ed5bbfaa9c7820349259df8681249971dfd2c01ddc3db884b424f3a0126be38a999dec88aa3d05b904b4bd83d2ee6043c1d394cfae52f77a340521e074963371f336152fe576a72774a16cc67fe0188393e5257b75f154c9409184dc7882e8e76c44c617f8bbe191a9eafe4d9243cae15e21df29d5863ca452ef3245a288ba66b90922243c5425aba5c8c845af81354dfb276445e7638df27521ac547dbc3cba9a395ba982649ee347c062f49167dbeb91b7d93f4daeadffcdf3b893749e28ea96747a2251dd12dcddf32d81258ac5e1626fd71cad511914d5d789326ef8c6c39e122d373d120c1974946253fc4bfb63eed5e467fba65f46440959dd836ea6d27bc4a21e599bf34788ad8bd92bc90f8902deffc3049c8b70c9de0629f23c561da895c73531c7471978f3e349ec1402aaeb5a6420bfec49334613ba38eb3267466eb8ce8d59adf4b3e7fa89e2274c0b2c1d5e34047f6fa091d695fe9d1064ba530cecb62fcef245c4b8f43d09f1cfb284d2456bb7aacc819ca1888b7dbe2ca0ff83c8994bef1da1806f9c27a7ebb01f4066e489df2076af97793f05fc779fded8cc8857e1fa2017d627eeefda020eaf90d931dea0bf2ef799e2b75c10b0f8c339059b86124a8db6a81dc435fb43b9890082202d636438c8a86aba073f75df8e3cb00f25ec6f0720a6cd342f71ee185cf3ecc02d4534e7487cf64be6feb6ec18d340400426452f811407ee619e2f0a440bf0d20f1252a42c6506345663530c1585405f964c212073a6ff8b86b0d61a2ae9c0511f5e57ec7bb433e6514688a56697d15b4c70ba4dff9ab8ba44f7a83dc758e4cf30c54e781d89513d83d6c9915014b882ac53f346c1c4b54bdb424b3db0688c95498cb9cd1f3912197cad54afadd7992b8f40e38baf374eb32a5e899740e46de3a43a8f39bfe7c9f77fb734e886a2212d4c175032075d54e4f44663a096901c81b5d71042a0f83841fc18dcfbda063064b2b09936097c6d75db9116b120847d2f7e01baca50e9ee5276548f69e0b460954143ed52ace21ac28e0c83f9e3d68202df581b25a558ab3dea6f7903571f32706ea6f46e7d1e53f6d15283d8ec329093f492b9eb51e5911020f4c6275593de84cc4fa54886d513dfec010bf4ac8111316299d70238b13cb64835f4849c7656d2e0a85cbaf474a1b9ef97cc84dbcad93e7bdf19c407ddfbbdfcb660f84cb303a705bea1c8ca65fb7955df5a2799b464b5499d4ffb010399475a9bf24a1a5aa03ef2c7dd2742b2336842a1b2c0ad87730a7c4ca3f80e15e3855f446d650449477629f313c4f6fb4a4217a4a3923e2f24408ec1d306fe55d3ce2f2f74536516422b51e0064cdd56b59be5baedd1e0d3f181055d0133f7cc1fe93eaa5410f74a11b148f70902a9f4e2676a6b4ac09fe8adc5f2a4f92aa5b8eda56735b937214772fda15e585d2c2a28777e3e755039e14322c5e88726dd471b446e35431e4a16d93141a72c3cc5fc1e0ce7731033d8ed114ad8b28fb8134a6a282c1fc20b6c5bd8caf1dc90d42bb6a4b01633d71b7b117faef2ff0a21c6a04c3e80736d1bbe6e843733cfd43091f91d2457f54f5355960682c21079aa9029fd89d215e307f6a4302057a57c42e62e034e52adf29c32f54f2d3d99d80053b9635ef497c564f733fd658c2b2819f189c57e9ab1ca23f1f2edf474eabbaa0371d966c29c6a30ec7cf09e866b7f88dddf0491ded82d9750976336d482c070633bb3b74b3fb1bd64564faff999dc059947cee8bc9d7815955e5e7a395c65a9988e184cfa2aba11d7a67263257d80c86466de8f6608bbe5ce27fe78cec69c143a2f2fa3064b76765d5c5d29e4f467b4d60793bfa8711d61bcd37bd612e548d115f7e3601fb39192e1fc068ec54b4eb8c9bfbc03f0d32c346d587193b8ea03f89d7c3b0388800ce66685019b0302d438b54cda15e038f79fc73f15bba5e4eefa3854b37d6ed5b9871a4c68d574525fd6e5007eac2a7ab425f0f09b43d6b8bb407bdd007d57dfec15cc52977cc66dbd6aad4cf6ebb25c7c66c7477729dbd1e4102b4911dfc436e9c29b3aad343aa41ef334246749f382e62d8d479cac350015db653088a4e93b24be39fa1beb8e1add7776c9826d28c4e8a96d554e4e62560277dd21e413f73e700c1b10e90a442a210b5c402d206d083c21b9b9a166ce94ac1f82a51040b1f3125afd009698a7b3faa49f4758298e68b352fbaf599bcece122f88bb819e604cc23088b88bd725c06333d184a2fe4285c4adb755ee942c78e0dffe0ca705cd465f6cf41406053e51ba9f5d14894bc014deaee964462db16cd2b60296041e8c4028d9db38eb6acb9a1451d6f72e963f8a96bb4ca7913b7e16a174162118e1d94dcb0593f660ee859b9736261b26643ce67ff9f7cb76152f0f222db9042a421a9d4b642938423414faca9aafca9ac52a4e42fefe1fcb3a9769c4daabe989c05ddc21873df5c3296475701c66f8e8e05e3e4626b0bb90c5c81913b61afcfa5bed9f78b5d667473e9752ece61a6b78bbdc0c0f572d765d9e2566c5112dffe7a7967743bb8568337ee2c091e2cd153c350a1ef642ca8e59a70b18be35a73b227334de8796aa5a5f908ce82566553f0758566843c57684a09a19e832946a089080e73ec42c0e15dffa5d3fc3a22ed6eee702894ef04c694441d3882bab5e3b19b77bb39b9134f94c88af0dacdf39e785c32ce9e98219758ee795b329c4a3283c89609c765679600e120e0da74e43590531452a8370f7acdc890af227ef833ed3f89dd90c8c4212c55c15753ebe2d2d9f8d70034966d14f77a869443d5e7dd4f25387ce285d9fe78cffb02122ba74e6ec3d7316672bde6f746497f635c08deb205c8fba9babffdf05937e10c0ce4b7c43971ab6ccd20005665d40581912a831504cc53d99e2078a2609b21aa271506b5435301ef04c6d6457862dfae833c81651b0f1ba9dcc52a791f2aff85393f5d68a7855ab398698775e08463158fb97bbf52eb4ee24fac01c1a5354f3d257ff4745dd952081fbdd08ea83dcd92f6ae1863a07dd78ba04d4290fed64b1c6014814cd81c90ec593fd8fc8c9e66b6c4284518ca65711cdf049454e5a59a16f34e93c1fd23c2445da0b891957c66f9b7466cbc1fc32b0a382351bc5b631b89834eddb2a8d35d2c4f2261733aaadb940cfacc87c0403e7d8ba238d3f1314c24f4941115f54a5041b2fc58816ae3864ec399039a91f8502577fc5e9918a5c0e752ab49da983b4b752de08a80bf1d76cba9f556393aa8ee96a2d9d76f3a8fec390782b9205334370d4f25d0f29510e2f471c6684044fa496ac299c91e9cf4dad132aba8437a109d4b188b6381dccd4af3d725174dbbca4ba552a85440ef9cd9a2a2ce1b0658827443de4eb17e69e3ebd75c3eacec4710b3ee4c188d391abb468db4fce44553bfd415f77672ba125a64755f54e99547dc8ca391131c80eb8b647d9f2b18d07b17d015be5927b2a40a66bbdc312e16731e49afc6efadd90b887593c8f0b8ab7c48a8837d2918e7b9b2ce4b12cc5211481b78b474713c641762d99a8aa0aa5fb258a62a393fb6320e78f168251db5e5b57aeb0d02ea9adcc80d00245fc80b9b1000f99ebffebcd30bdfd3fe403aa53a590a57f94ebb7d4511a1e9e0442465b695f028e6eba26cab08cce4bd653d01a2494db4487a3dcdc294f84dc59c341ce6f57c976863b57e1c376cd23fea5c09ae5ede3444c64404597f3f3700f12dc40fdbfcc89cb974adddc12545701f469326f207c133d0e620693202b01a031c8f102835953a465db484773104294f553ce22e39a560466b228950fe8594d689cadaff46a6b27f4e6e6b26313ac6365b1a27268a86e41bdb58d392abd83920819849a02631c279e96adda9889d39e3be786535a4ead7082aa2641d98820623ef3a77bcccebe6adc609d7d2eafda7efdb410b95844a8542b575d27ccda880f4c430f8c40f9ca179b09fff92163c85214f868e9a91fe9dcea6fdc336d507b73aaedbf3275509a8ddea008f3a04e5847b6ae6b006c7b4655231cc72021e81cb57cae946139c848cf14f3eac4f842b16b066d8e2354def71ae1498e3b6dac3375d7f1653a41de6187acfef508754220543135350ca52ba889eabb99aed267c66d7a7627acbe02a279711a81bbebaf5eae1c9133973f49e66f9c6835bea8ee90033c0dcb5a3dea5ba9d7c3a65c454448d6eda84c84ecd37120b0d8e18f3bf1491ae04dc9fb8a980d92486aaa7ff1795f166f6ce68079ac865491a2b63250b5eb7728acfc0968cf9f753ed242c871a6a4d98c43393c93d281ecf57fe38bfbe7df0bbf3f5cdd764dc7cffb471273a2c76a9810446a1c374f97bc526978649b85703a35e6a6e02429332ac0d99f08a41bbea26920312ce7a72d4ad4bcfb2682e1d1de0174d36aa0d34abd484d402463d7916b493b31c135971183a4214ae08f2ef4c8d5b16e2ff523225e1d5e2c263d71fa0b1e0e2b9739fbc53e46261cd3e6f7430c2baceab7dacfa6e80f3c78406efc56e47a642c4de674c8b5d7cd35161e98669cd281daea8be38ce44a3a790012e4e78f2da5c1c0ac58c0340db48d6e7a1e13255ead70dd2e4a27c4d3af4bb7a517290d679628884204515a079c8ae88ecb4681903a942059fb88880ddcb8d47415132e20251249bd05aa612adaabd5cc0832e680c9faef141a7ee4abcf6c5613159737bc49cd19fe629a5f1be80b35cf8c79d78d591a96cb121569ae5c61df116e892e4090bded4b1b15628c1f85e2831f5414c8d0aadd23e8950c7d3694885a57ace181a0bd4b93dd6742033b4b982c5cce4c66586c3f3b1f682a9ef939a64b197e260b6c5edfc670847719916f30c7127d7b410344119fa2c0af3f72066cae9a77af860fb5ebbce040579efc9174301ef5ced5da526b5f20689a7905da5c733fcb7f638ffe874cc977a9d16eabcfd6f82734be5bac2249c28ebcc59e54a2bad5c69e546c0080ed310755e92eaa5a74d9537f8921efbc44ac80fa2a7775c46ac323495bf245eec0a02e64b99e2e6f0acc6f7a2625bb6d81124e2f7c857d09d08c9f065f0d70d78e3aabf2c8fe93268e87915e7121768a0c5dae4a80b502002d86d462394dfb084145326330cf6ed07196d69bd5ce3dfbbfbc629e78ff792c4071317a6dd87cd79fab4f436a6ec909a98233ddb29e058b802a16b4c66d537bac5c8aad17412ae3d0c3f3a4f663dabb21d51b393a96c23e41cf7acb5d10044a7e8146bd97e9f43dba6bfb67fefc076d7826dbf2fc01672e0762c4708c5cb2088307d946e52d7cb124cf662a0cb003702adaf04c0345175b811ec78bcd83989788f4e1b4389789fe4dda5ee4d74da8d54fbde74852312ea0c790ec4ce2a98a419d4b735def0dc3f20e6c0177c1f676532c82487b1d5648541255404d0cab3c6bbb9483628e098e6e823a18406276b89152fddabe3ab79a3fb063e06277a710a31197a50d815da9ba850d8633da651e85bf2945d10f58d559a9eb2790fdfb65c841e04bd5afc866cfe55bce1dd1738d1d2526e04e7fd7e59e1a35302c3511cf560c8fbfd080e1da8b05137ebb0e1ee3afc97bf49fc18031abc78bb60536b59514be897565860f2b22db46d52daedf17e0476a962ca623042a2a79ef79b8f2815258014be8610c01b17c89c7a5111ec4bbde5e67255f7ff18b4ef6a23a5e2be22186c1256b5b985809773e3ed2de021e8354add2140e28e298e31c7b19feb0c3fe768aa8c33f9ff2c6895f2fc12c01713c7147e1ed5238b6f429e70f6dd475e5f2f26645005e23ca1bc15f2872c9f985331550a198863031a98da57103656a596bbec59350157ea33a60d0428a78d711bd6a807d49371cc728c1abfb8a6aae18c34d0b2b36fc0533f4c929d6d01572763904996ed490ff3f41c37a44fa6eb8993050330e71b5463223bd8b53788cf6f648d7d2db560290c1051e6450723fb978a0c213f0bd1af931561c271da4881e8397c02f79b1a75b67db28d6f6f20051bdda420fd7c4612b20a3cbf9778f96301c4a70981940a072d2f16feae28627b44a64981e84bd589c105d1406eacd04b583fc2d78b2162e5c2b6b32026ca2794d38464d52211b47f2101ec587dc58e470e0b8bcedc70e31f5e02e2ae12d31f99fbe766657e943ccabf125d627343edde9df62e9de6a5b5838121e3f9fcb21d66a893986b295dbb78e95dca72a153110bcbd2efca9f860203b717b06e1b2a2640dbd133a3c80d3b7f474f176898ceb2b2772e0dab1f460367f61d11795fa715b91d3f17e507b47031d7a432bc7861d1f45db58651491bd0fd197a30a51ffff9fa2ccf40e934aeb4dcfbef2fb76d4e74c7233840be7bb858202832b39cc70328959c10045cbb234e06a2c1281acac44e7a2f84c7f7fa30f4308bbd045612a72f1377d55dae2ba29ef36bc95c195437c1a2cc4313c5528f6790c9120f38157128e41cbf10406a72b85e0507b7e1228a3f2a80e6b85d29554fb1872046ed90deb2d0e170023928856dc52f3671e60d928cf63edf42886fd2d4bcb17359f59a07ce5048edf58f6fd55965d770220ce1fafa445a0c11ddc30c271540d3f8135d170bdba4ded555d8ad4b3534296f2b0a762da4bea83d60d89373d5c742bb07c4cfb76c0b823edb23ecf1871ed27300461a3d38e360ee80d2e338fca6ad682b06481e8e83888d758f9c4f37764f83d8ac023944ead7e0a5664559e8bab6451b120d42088dce7ada71cf4a1c3bed6fcb48193d39482b288abba24643bcec7442d5759e9b3094c52dd1a1390a166713d6a0a37b3c7350481b187e25d71cbfc1feafcda1eb22b5d8730dedcf9be91adb6ecb16144a25631a496c5c66ebcee41d00b4640003eb9b1d394f5f8ee79736e2929ce3791b1cc3df155e6dd80b2bc7f91ad13c1997874e15ac12488acc3f4b56d50582f0423087562b03c5e14cab16d17910cd795ed4c93e496aec1c8e42b259af81988b6fa1b51a5e488049e14d63290d30463343ae3cf120486af08f1c0f37fdceaa01ae14858b4a7b33a1b33c1a12588bd226edc4b4b9e1950f63c6c1b91ed74cdcf08498cac054231dfda990740021df3369b6b917e083f70bd4650c5f4debbf50c304c2c02920f1ff289abb20c24c23f22a7fd0d09b3913960cfa389173cb50b1f33181fd6abcfb34618c82a073353a14dae776fe127aa44a747296904b4dc0cbb7d2462e042552d0f9a7770e103a780df978a841f8cd6e4d18a91eba4e27d15e4e2bbb5fd1ce94b332fc4737a572399f759f2d2de70d92bbdbf391252b54d4f9d2a85d6aa832c5ac0f259bb6e013ba2f6d387fdefbe287834c341e91eaa162ea5470ea969e6aeacab5edc457b0bfcf526f0be7d998d3acb54876ee31fe93b885ca08e03fd1101a9f6283b664cd8b66f7ae81af8c330f168269a3a00a2c0835be103c2365db06607e1b80df50733be2f24002d1af85080efdf2891ce24b98475708d11c44a9ada84e8ec23e5b6540f76a277df5beb0078ff182e145082c0b48e70b745ad2651bbb70bc8e4a44da8bfd261341f8b1b9f0eff3f5bf8f4cecb0fe8637b6414d0fe7dede56801b0c153b3bc732fe6905419785c47e72f5c74dca782f222d82c987d84d46c3f83849577f2f1312d9f7c43231455d231806b26dfb51d8ba02fa1be8a6899e3eccb2f5296a1ce3657f20cd4173ab6d5c6642f29621cbff6f5f3ffef84151fb5da5bee1f5de807e861ef139f4b5d0c8eb07ada9136931878d4dbab9de4a8a783a8672f17e87d089e3d744b63154270cfa78a78fe1cfa704129f5fa478885f56fe8e6cd20e377c600fdf148f36798aa837e0bc17ac305960771d14666f739edab5822869e9f5131173b050a24d269c7bc12eef2af6cfe83165b10677ec60f740aa0e14e8edfa28e6412999eac68e15de1d1fff9b82492156030b289ba7836893213fde4d4ca4d6489c2133a8332edc8f8e483452c05ab127846b65fc5930b858c4f6fd0adc85e18fc675aeafc65d9b9ced088ef36a8602b422833128d4263be2cf94612d8b6809da3fc68f4611417b55ea332ec007171df88497c2d16f589402e30bb79ed859d31d9d83f6904bff7f2431e7120b7517bd94dc57fe1ccc3965094d4c8e8be8a2e5481b575cc5ec4f76122c0318b34f5ea50322def9c366a9a9956b9089d7326c330655b82edbad92b26b29f2fd87ab40b833cdcc711dfc62f9cd7bf9eb0738d4cbeeeec7ddd4b6015639ebc1f7d1c01f716642d54edbb40c3a5c5aa960a7dbe6a063966885fa62d49b28ea00681281ccdce548ebfcbf26e751f1b299a8dc3c21023195d54d3347bd3ce61a69da8605f89c6d161737efe6b5e527544cdf3827f3e62ebf33a7d7520031b79f3b8967ff40822c9467bda6d2bf3c06b0e5b42791c3b04db66ba7aa48f10424ed7cb763ab0cffab6af5e679af3183869c74b4e2f63d2ee48f26712972c2d4e1e4849dc4c75f7a3100f5a165f5e15697dc70a5a3ce1f9d3998e8ee3e110c87ef83a5fbc2a20f64abcebe87293ac719fe5b20ac2afcfa7dd558e9be395d0d972a074512b12391d7a856e1eab046b1542cf803f928923f0350a1d76efe4800c91d65eb9659b051104f7163bedae3d2157fade540eccb48e49522780ed91c88b4882f1e6d5a9e4e956859ef320223ea68a57c7bb565981bb56086007e70335f42c08ff6d1ce6a24850a91251ccf43e9b9617100fee0f149ba9b3bbc944eb403286996ff9774228e77b6f9fcec469e0a3704fee9f7077cbef9c3e93117f8cbd5d915b3c53ad34a76cc7decdc8fce33cb69fd1a3db3d11f9963ec58c302e7288290b5a3614a0be453e048ac50974dcbcf9dbae46a11a2d01ba9d9493d386007defb4108f7504d77601c4925a6daad1c5363942e014ef97b2e7dc4a6bde641f4abcd86c5c178ccae69e39aed0f09980653a7d598a67d87587c06110e005a754854af55ef79fdf2034e5efba7bcfff842e16cb8ad4f524331a30ed6ca4b7d121bc637b804f90307a5c9175644db5e0917add4308549dbd5e51a4b45a208df16be7b50dd54e58545da5ec79bb82174614fea40bba4e5433357ff87c143611e007b7ed55347838820b5364dfd7de952e6fd5bfb344420f8ecf3370f611f1b8ca86c9f1dfc5151f5c3b3de30d55ec60359d2292e926c6a7595f5dbc7300730539f47274c2ffe43306cf8ee6bb2b18693b1dc4095bda26f3360a7d247ec03d03693849c8e3f91a802bad6cc4a9a4620f93c0d3e1a4c744c6cccc1d7a9e955431c43649497d61f3d162d53379f79cee4cbe6bcdc2d61751f09612519cae44ba28c9d9c481806c777a2070465aa43dc7b50ad106e6dadf39cd189d95d647b22a8ca6d931d7cd4da0bf9fe28cc59e705ee3a7e3368e5df8e746a0540ea25c8e769f6dbe1d9b5ece225b7c796066500bf6ebc057520700e429d65a4bf05bd72cead9948242ae7131f21941a627a08c6ee5635749207777ec455eefce4c13a8e956b4509c4a88bb6d7190c73399899730719b5c97974cb960cb578e885a1e3d1e1099dd25a1565d2e3cbce28987d10a8a7d9125c41f0163c50d7bb3bc7eb4a9bec9cc4facbcc08e202dd50f820bd081f446fe007d35b7c60d01b1fa136a46e92cf79aefff226dc939b539589da8912f1a6b4481dfdccad4b23ee88ac7312ca70d9b3f2073aa1a579d8fe7573bc7fa2bef54c696c6fdf7e78d4519e4c70c0c24eb83497faf40dad191c6e70faa3e93ef1d4c0b9eb43ec8716fdaa9ffe9f2dd04c99625a72e36f9ac234b0b681f406702964653482d682f1f31fef22bf1fbfcc0abb32da30bc7887cd6e1080f2d610fdc3c070a05da2ff98797c02832842b37a31486af908247de748b1b685700e2429d718f46ca2b34d19be94d8182562464ef17dbbf66dc9a516a2ef0715879561d631f86bad67db5a330f5d3143db4864349b4e5e439e6a145353bfca7928f2feace6eb5e4f935d923728bcfb8b800028ef0487a83fbe1b680b9a91fbc49d1847f4b0bc6d1d810bd1501affba3eb024258d5b681c5257f7bcf598307101af3bb497916507da43ced9205c51360fdcfa0ec6f9c3d6907a49c3944942ed6b1403321c3f47189122d85e842c1bfea28ac0f786ea654cfca4badd50bc7521af9093e0bb0ce3af8d27af12451912a7e03f9ba0884a04a6f63c436d3e690703b53f8016f8b6c884824a342b9d4bd811eaa40f474e30e1c54b7ce52f91e860f07616aac89882820a69da2f20aa2f5a7e48b6ae869c82ad837cac55af7c9e5a685474730c7618be629379d8a54b4bf5d6354c2dde2d975fc749a699de5bcf32dfe13e06054df534d6608db699e91390cb95e39778d8f185a045c9a6ded78cbb8bebb23c9a1657a9b4865c78a40a224b6ca1b7e087d0ad7113b3ea9dcbf4e5c377949d01abf2c32bba86950ca4d43aa3e271cbfa86608830fed8589fd5f57021ecbd1e54da6438a2454ef04a642a9fd42f4fab6590527517b7dd68956e86bbe299359914199efaf4397ae386da63f6c14e4a36c81f054218576a384b5108b23cbe4cac271234053799dacbd91adcd74df345dce2345c3a9707059cba4dca3537d37ee0ea4cec7a8fe6a79446e06814c687a9c68818f439d39aaeaebcd3e2cfe75774397f32430b66bd3877911495c0b90ec1f01cb2451211e8fac316c1084aca550cac2d1cf43f788f935a21c0a17b13f88290c543e6931996c1e25e5f1cfd818d68f1d718a14012873a7568e4a9c3ace3871dc91a530c3f8b3c08a115bbc421eedf719a144536195e12bdd800777f408e6e9aaf1d3b1b0bb87ffc3c94e084a0819bd9d3abf590030552b44ce0fafb924615dfb78247e8c7fee88ab90e7e2fb0f2f3d0369979b5e7fa727d6eb82cbceccbe6c2cfd363a489149a724e02405120bf626e544ca0fc97abcdba3054a11e20cee642794a24f5032853cbea21072372b64424a011e0df224280a169000737dc72ca147dfb1449e13e8e677e24480e70a695a9813dcdc5b1e3b94335331815de9e233d6bb925f0d7d308a3643d99d31fb370823cab57677ea5ad136b1b45c389b3894ae5f5d2f3b31d2ebb1e1d08906819b867c13fbe7975824bee474b0ed6c4081ae26e36685abb04e98ba00838ccb34b2f802a02014695e898ded7a225c4104411f9f92120a301c48795bcf49412e8bdce70dbf5198df4dc5113a9d52dc7ba1289e4b06191ae36940f6b13dfc92f6f18f669e92e0c3d5136c18859daa4a2557298b148187e29d8705f06809d260a3e5f676e2316ab31df76b55d641610ff61c97b823ed61e45fb25b89ad18d96d9168c1be55674a901cd7174e0498f1a1959721c12bf32c59a6d1b0b7acc96e006f714aaf451669379fb9637e82057cb1e8ba710c43ed117efddc17d4b67ecbec195561f8d195d0b51a654142b29848324853cc777a8fc1a561382e3e232004670bb3cd56762ae8341344618d4d47208f9ae5cc52718b08f889da9a97e06a80f700af8dcf73618428cb7379e7c5b5052b59918b751a4f32224c61a66047a9d609ef5cf7252744b2127b7b0dcd7f4d8d13419201b06df235b024967dc87a922b4aac44fd1ef94ca98805203aced89ba43710323d67c2ffd03dc483c224fe43dbadfcce5aea24ad2bcede482b31e9ab7a13b324162d56635f5aebac9329954e1d1636841199d58b8b0842b667111739611d939f35196b8329c0015252758e78be56e19c3f1d6e7b73d4973a07edee1c8f31d9f1d20c23403b2a69d1afbc501a68c5d04a537f5b6f7f7d8170edf8dfa4ea72f16891514b5d8cbb41345bf16564761d5c89ee29274db5a9a08350f6a2952bd1a9f8631b85a59ce5d056e32625c6c658d19dbc973c83bf165489269f42f7c502bd3c52ebad09cd44b8ab5e1762a868c442724e615c2e6aabeaa3e46c1ab7e6104a9f8312b55e914184f51eface583add07f9da6228d3b514c31ddbaaca1e2c5399ea3000df3342ae2ce01dfebf17217421e038494c4b3f8a52cb91b36c77299fa336043a349a2a5f5d4896a244afe4726e94f2f1cd02e89a8cc400eca22979c08e8fbee7aa0ead455ad470eec33fc85211e56fca0e18f50dbb16823a543c0268c73bdc87d7a13ad4465e7599845368875b45e64e77f9ac29910108941c4df04224bee7e0af26808da05b6f30a0d558ff366302d5b9441918617386790e390d43dd8972b5025c6c003aea8906482493b7eb6987e1c89e66b4cf8d66a6432f225636563d47896aa06320b538703bd7233d2d1529cbe7c7ee2dd4423412b1455299adba9e05be87f440f5e14012d7e44c0a70f243ea516758995fb4641dad229c4b2df55f4ed2569830fb84e55ddc040e7687690240734c5bc05981b9d3ac122aa490e35b08dcf2cb5ca0f9c3affeeca04cbdbedc41302e1f76e456709531f609d1747f497efa36735819d20bb73efffac7e9eff52b0b59e4d3378f92827dfd5044f3097f927bcc27172a13e535873bc7ee971f36e2991b3e5285ca6d09c9b166b6b2383a7107018ab6b541699593207c7925a0c5050de446612391dac6b634082cad3711cadd66844153421af90ca6ec7a352020316d835c08861f77717622817ab926a6137490c2df60c003e46831b23ed3539f988fd966b029475e30ccfdedaa30e92e76fdaf4b1ae25e58bc6623d7b0929aaeac3d7481d9575a46aeda762796905a64186caeb0bd257d996e9fe03c602b168926d04f5b3968ef45f001c25311619ac1114e23fd8b1f382abdc0b49754ab15076aff23f4159bd0fe8d8546f24b812f542c1fab63a0b775563536c8fab6282a5450a59ba64521d033718dbbd107e388c2a55fd0e4711900eb7ffb18f10f7f17ec0bc10d484c1f998edbfa3218494f575857949868393238dd66bb50341c84409381a5c5858a3090a9a5e3b58533aaaedaec495607d3fc465dbb027d826ee187dc2d706a0e058902d099e6bc132daee3c5c5e197d20835cc6942e849f9fd7a20cb3c51f707a9caf9af5c11c19bb3a83385cc47d78e1191d231f8ab3d1d9e783c7a17147e14f2e8cafd98a4cf8f1f9ffe7b1de3e8fd2385fe6e16743bd53565ba155d8b4f40320b9581ece0b38edf24bdebc6ba75c1fcf395c07a846cdd367357e89fe81b277ba6c89bf63ea5ca94e4784b72e43615990f0c0d770aad51c735ee622927c9ac3672eb2a61cb3aceea79f601296b9ee528c6354265d92cf2e03311e97acfc850799408287c1e6ea83720320c2a0e42edff504fcd6828da11b30e5fcaa543794f068cbf16518f78073053f5056c69896d91d70b7f471f180938f2178d0d091153276065fb61fe168fec0a4768f2e52ec14dc5c6281026d6bca4897d4a04f074a38b2710160171b4d2dd41e0245ef7da941f11e1c2d79d6ca006d018b395bfc52b92cb4473910a29ce103749da71e5f1feb322497717a0000ecda2e68e412d0ee340f70aab402bc7fabf24c05ecfed15496fd39a6b658b6f098bce75a34662085a725f364e6fc72b38368ca87693e2ee25a06cab2f13c10633ad73b4610a0edb78bab3efc06f38b29d3540aa3f72bc90efa1e3bf52e0bcd928af40844e9cc78b7df1e7d01f822e608089c4007f1578090ec3b3c1e9b25b89c2b07d48d6c7c7fdb0dc4b3899a32c7456fd13a8082b44dd67413911bf6bc98e8502520b8b90edadeca68de6641375b4cb4535fa9283490fc18de9d4df6e03e9b66b13103cc9ba7d5f3536e4e8814b5d70c2f01c7fb69161128cd4aff928ec7a4ab49e6bab1c7fe7927dc7adda2a08f0d1c37391d73346781c2aefa346499ea2e5d2ede2108fc44a03a8c38166dd0c4a95162db39cd9b834c7d804c4d3ab9eb3635502deab599d5bfb95d72b59c90614a5b0d74eaf9cdfd00982b5aeaeba9ccee837140dac541f5d0f80381e1a6a9730c4f8f7e8b592c539f96e73b7516b998db9b6940b65d5a0f0221eda939af58f93e7d77a24b06c49ff53589f8342ac16f6fc94ff29dfb4a585afee9ea933122e8981223bf3efd4af410088ffca42ee334992d706f527a8d8f9989af067e5a9a2cb1dcf0bea18232260654431dd51726d2483c29add94c217d05bb8f8df072f3290689d49212f3cea2fbc337c2c0a9812ee72cfb1d7acb55f9c35d61e5443e6aff8c447252f4aa7a1c1a61c06ecdca76b8edf64881827726e0601e59b5fb844b99f7ada63a13627730e93ec90310a41ee5c744b0c1870a1844a91155f0f08c4e160f9e8ba0a0decef6d6ced168987d707bfbb50e03073924d44e4238f11b1b9002bcf11b746dce92af119f877e66bce32ede73a19bab999413994040d76bc561c893cec7801149659b8ef301d6256931b573ebcb4b5ce0df690b72f8c242a9586967dfecfd553b34ac214c3dae889f4ff465a916d8849cb108a4dc1bb46d2e4753dc85642762e9d4283287ba83db9657a2e62004f4b0d620a89cb1a8784b54e34aad099ebc1ad7da627aba9597158639a5bfc526e68674a278d7b0587598890192478083dc5015f28582c8c423d6b7df57e2c03e92f99a19231ca14db9243bf11a8bab0a0e52b3bd1d4669e67461f34c00dfd0c48617bd01416c725384c3ab741c7f9c3b6da7fd521d4298563ef8353d0496a901838de4541b4559cbd589b083a13a5fb626ea4c8c042f89535ce839ab143ec88a50e3db04b581c7278f551a8a0c88f2dcc588482abcca00ab84d498b8fe29b401bbbbfa42a4cc226c1723dd88090ae5b22ba8917b5a060f2238fb5182ee2d41fd51783b1ce5e08f16626f5150a104e8d63eb005c41916c3b78ff58b8c4eba0dbf7ccec9aa6d2ccc3187a3a3c3e57243b0440e80824362fcfce297ae40bf0dffc84b19e9a44a14ae41cb024b9744ab18ede9e9e2cac6836fdb89bfc895a99275fac83c1baa87f0efc48166f755a68405144199f467d9e57b559937a996ae5a5f18f8295ee7db7a98a459850aeed35e90a245a0c6575de32d8eb0a2a7de98741e77e219d80119beefd82086712390ec8a2a37e78aa88704aa86a250c2f91d85cfafa9debbb80f82eb54509b6bbbe00e6225374e4a47fb5554c833ca88ba7eec656e9a5a403f7c0843687d500d2e66659f5ac6e26a922e047d0d0c9e25d47b4e48879a0caef173b279536eeea391eaacc1c095d2bce4f5e1a700839dd29cff2c1a158d13d28883594972f14f4bc943dbf18c1486a472445615dbf93f665fd7422e5b5382f6b9d148d0608df5f9209313c53139c979a50c6ad89841cf08101b2c6a84b18fab12f08fbfc2ca794c2c51817d67f36a01b56cb36ad8911d2fe033cc1359926ab888be8b04e6456ecd63805422364c752783210f6ea0dbfbf1607c35e9d40945be8d54d4d7fc90e7571e20cc7f8bda3b6547dd170a3e917606cea48db513251ec0129194d1342637a7ba2387ed867bfe195e002436a2f8f93fd2e79ed9eec513d79d2a185daa39101982a301e09c7c1c225ac2a62d9a3fb42db993f9a3f774e321c6843008dd08f367d3ee56f0543be85e04438800b0a64513803b6ab4a312663f544386e738e6f443e3fc04d0a4f81269f1ce79121e9f9466cad5c5eb475f26834f72cf718638528c3e086107f32ae58b8e29b3c9b8583796e9c240c0faf9435b770d89b007d2397b2746a1d9851f1b9c9b59efeb06afb1620d64dd67687b5745721b673c94adad2a4404c602bddbb2d23658d969318f449527b0f128cac9931845bfc300420011f792e93535fd3ea2126caf2c6b864cbb6bab4d30214e246464078127706dea43bd1db23d6563ced164200c029ecdd9e3330ef7d76092a4c2806c348170dcfdc08625bcb20cdcc4ad32ad2d289bf6af7d119037f605b5afbf130a6092e072dce1beb864023a9f79c5a3a5f98dab3a65515609b404d5ced7bc1375aa94ca605f68846ac4ac88bb194d6f6da7aef92c63733af3c43c30810409578e3d8e22d31d0b06283d6924c9bb4901042a939610c44bd3feb72862b816866ad20a54de52c01758813ca8b6ac36be0c97708225c799a837cb3cccc967080bfaed5163cf3d8382d0a7c5f482bfbcdf0a4ea2513670bc39a5e00fdf19370dc6542f5c3226d04b4d2c0c8a65bae7c481bc49ac926aed78aac0af2cb110aece11b8f2b6fb6cad716f50441029f1ffddefdce253c4708d22604a82b2f5016a63271cd02e506c7ee1c80cb4ced4e47f6383fa6eee82aa0472af9552e176672d5592362440923fda58a8509a950cad878e023c075f585dc3552eaa4af92367ec4659bf5cfd8e6afd04c17df69629aaf19d1855479b5f1d7c722602babecbaa9186e8895c4115b8238bfab23413046e63be7970c971d902fb5ca2ff4dec4a236343bea3abf1845c1b469d73a1582d164635cfcf2a04ec1e215e89b940fca1cbafddc50fca43805600a8e086e85195800ed609459d79690f4a0514c53c0f32c1c1b08222a1950c04972abeed430e0f17eeff06544212756bf8d66c5dac489283fc60846329da5412f28a0a83d30ce860d16f201887c0af84de974d009e0018964aff08d4a9fb14785d023bc804899568df516bfa449263b931b92fb3cd3a4f2708fdd851783d1362ca45ff38a0cded50a959292da82accb2164b0ba6e0d6ae606d2109b53f666359995afa62923bb4c3254b0bef114505603218e9a56528fef28461b3483249d1e9be49f2445b7a3b878eb4cfb1036ffc01f57ad9908c34da333896b780d4f748046462c192b269501842175b0e67d9d29481ba563f82c6b56f3421c637bce9bb65f7017c4cbba2ee7d9616657655095c1319e295d1e00c640eb26f2f431e1ae4587a4343a79b870d4528c8e0f69595a624599e938458ba643a20681980ab6970b017ac0d7efb1b5e83f1b1d1b063926abd58deca36d3565288acc2d4088b8e13896da1e46350c39c5e761abfc4703271e668c1059894bdff08f0614aa4772d9f72e40ca6e04fc34193503ef1c082e75c79095e9839c18185ff4a1551f946cd70456766db1d0a4f39e59c52f62cdbce32ed8a8d3dcc41fbdf155471ab636e474c6eed245703acd50824f44e0d6295915c1412371d0dd2536287265c93a71b58b937d47b6a8671bc106c95d7aab570c244ccba305e40c4d55127dbd9c5a9ba9447fb46c313266191a701c076b04f76ec98206554d22c4669aa6c85c0c762d20bbe3f6a9f1ca611594438f7895240610c58f75aad70cd75470f9a007a30d8b5a66b6cd5bcf836809b187d1ae86248f569bc749900106cc2baf43949fc306c496b9a6a34ec5baddea773dcb7be6e2f04e0e8839f302abd884c807aa696edf973eeaf905e67d6d1ec80e4026696ed140ae4713a22c64b59ea37cbbfe1b35c3ca0e35f07a2990236b360f30ad7e84b23b55ba60505cfdf008b59d44305570b0a6c4b61ed2fb085c6be5482d0a77ff94280fec16f228d04adeeffec810cebc7be2a3943f40c67fd14b0b35c4a3764e1b639249720dd7827cdb22a04e235c9299802b7cfea5b7e9b07a554046400639ac884ba9f7ca5c0315daeae4ad29a4a780f5bd9929780f771215b229624002975ab68241f120a3611a03619244c0cf5d911a6e70449cb23930c93c99c03bb04c229418c1d2547720e3b3f5f11a0e84f0f51fba5af4768708271f0d432dcd8c1f808bb517fca85b7eb18b4ed187e5fe7dfb29277cdf1e9b9278c766e79590745e3c9648a4a94b9f5111b59ff4aad2266bd4dcbb7515195fab0ba485c4b62bd46873652e4fe998bc4fe4ac1a50d4b0eed44010b2540927b78d2ef5310655f3234c947811482fd9c0c6cf8d57d82494189b66697dc8798404ea234f787ffd54f04af4e555e138802ff0328b4c181612b4d80208dbfe6fd60659c42b0cf8af21dfba0e02b4ae205ceb8d55ba19c43d801e50abce08d4456940b0b515ad01552167dd7149183d6d7fcc918d5c22d5a993660a5d87f30da0149d4d0b147d6e525d843a242acd3368170c7568ac5c4dc2c83b5623bc4197daa2e0a13c07552705d4dc3717ea70adad8004b6c51d8766d22d61599fb693ace46d0d7edaf0a79f426834864b8fc06c7faf4e63cff3dfc316a740bde3001f18212b212881d40245ce78e0ec4fbc0ff945338c6d62800ae1449d7404da370037535090fb2dc3507c93e817a9f9b5dc5ba646915052059b0130c385531acd4a53d6ef234cad7165e4983c14920a6b1dd439844c3e49a52035ee80c3f81caf89952fc057db6bc1bff7c84b99672ba5292ae0930bb2ffacb8c8c6eaacceb0de43af7b79575dc5e36931cf83f68165942fb9de822b40f64fb78069f6d9c4665b39d6c5a356dea6c7f23c5f638119327621b2f5921f5a38475344a68af2ac4c0f4c06f1dac3127766da05c31da5ccbcf18b6cf3ecd417178c808cce370407c25219795f8fd540c9cc89977073c9fc4d8638d7f1b18a056b42ba00da1a4ec79674148924d98e4d9dfd73e5260f663507af88dbd72c9b713ed17a919c655c4fca9b71693899e4d54dd0d7fda5dee217f563d1dcec94593801d3a5284a4968d5cda17684dd1ef507c10caf1ca437aceb7d0af69e89afc4bab4a19d5a3cb79d2061cea20e49495ff6ff4f5b6da38298de4dbcf9188b581a927935ae7da7395a63d717c8ac5eb18f1f5915e6dcfb781c4092d23fa99817d889bedeb5190597d71c448f9b70346d64334d17cae44d9fd1cd6abd6a1d1d331ac63e837550d40ae851ee34b314dca48f9de4d3d458cd0c7b422494006ca41798f11c6fefca4879067a76c39bcf38af2791c92ea1e810d041f517ea35690b4c32cccfa2ab67cad629c718014d15d0e79764f1e06fbbee7388d92db5c918955692f2e4e6ce4802bbc02cbc6016b35dabd0b3d7c5e6c50973edb775a83f9f51cc05e6674d572f0bdab7dc19438ace4fcda2c8787ed7cc0094b21c5127c6ed177f6364cdd5e92076b7d9b7f9a4859ac965fd2e454bdb77ab009e205a2b8cf0ec12c3cc6800e2745c3cd5d021e34bfb6cced466231f4446b3b0843de787a01c1c04d7b7380246d95a9d82bd2cae6d967b9edd23a9f8e6a2a61f96d5e277650b2057ad66aa25138cc5e91266e3be80733d781805527e225d91b044f2b3a1cecfabb3ae8e095f8b1eafb2ad148b8c6d410701f6c4dffecb8f431a49e70f337d1dd584b9a6095069ea7d87e14402c5483ccb79c815860349a6fc9eeb639cfd52fe2fcd5808d066a66c6300322d74e199fa8c014c78b5424abc5949dcb81c7e5cae3d6dd813a45c4582bd5bfb8450b03ae76ee5ea2ff5a1a3db944a2d5c638414ed72eec1656b46e2167f77dc9761e4243021bc45f7990b953ad35eda2538395ed7870f350cef1ba503aba18f172dde2d46de2343e22de42d3ea1c2ce175a73bd67f36c108ad619e7fb06bd27262e320e95d21774dc1b8cb65355f3db34c36bbd2f596b1e4334be8265e6d27e6667a8e163336ffaacfec3473d3bbedfd5b8b964210594fd36304cd97cb09daf43e20eaeebe79f08829e0700e685a3f98078c98e302a0f5fdb7d8d3a1ab30521ad8f42dfd27c7f9974f55f2978efe8f1124b194eda34f5fc79a825b8868e3796bf411bfbea58b0afdb042941457f3f321dc53128a36b89dbb2dd85bb9f96faa21961a3448033f29b2ee181443999cc30fea01d5e24ccfd0c5463eb8f51e9a1a8983a30093ba61393e1e8aa5287c92009f1a7058b09acdddee731e7a6c4861afc55c7091f2aa5c8ef698fa39eb79e0af6ba2eabbc5df753a0b36cbdfe42d955a3143cd93271cb8f012652c43e54775a31cc3e3c9b60fe8e495c395937bdfb8422a340a3071b8245a12c4f2480edc87ea8fc93101fed600f825a4314fa0482adf0eacdb07a029c1f2f07de4c4fd3a5c0107a6631078867a3a812c272cd2b97addd7c3b4c0c7c19e13396721783c4f9272ea714669dee5c51d246dee723fecfff678deb7c37756c4538a2b1945250e67c6c117e75d0a619163be94891e7f13ea29b7fad1edb8988bb19ca2af144ac13acdabff82d07dd1a6d72f527d0c1c988f3ed1bf0fa1cdde0bdec42b838035e659a8b2c53e1231ceb843235bfb1d7eb52dac6a6af0dd4feda255bdfbe90a80d5b24ab259f358e4f1fdd42ce99778784529ce1088e9ddb178ac67345e4c80bcce2120715a1ab4e17c0790abaa4b395439b5153ec5ad3fa4018bca598f242c5200df5c3d4ca72f72f4236e70b797355843533b291a40c63ad423ed5c16838c586142cefeaf2f4073fd06fd1198db10d4e327a9454a04fdb0afbd25dbde2d15179920e06664e6986a9551e6d0e750231c91f81065aa5cc56f313373c110c7f022e05a39b2b021b6f59d28733088dc715c31a410483dc68f022e9286016b05ca4a306182b829f5fa3140aaadf5160359b14eae2daaf5d9e0e55d52a97313d46c0ab641de352f092cbacdd880d85e0ee3be096076fa84c23cf3280fe1d4406da0bf9f233e1ea908944b8bd1964c5f8f0f765ac4c0fe9a9e0dbbbe42a6f8fb8b4b9e423fbf56393256669012a4dc005c89325a5ee52f14f98402e22b5bb2ff04692007853f3f3a376c7cf9c23c987b5e443c2d488a05c8295d0f0f84c6ca7360d3f22f3632f0c30377167e8d75b10c515fc21ad07824e0ef2b53971f73edaf1653c5ccca861cc44cae8354b3e92b4d99bbf7e590ab7a982dbfe883b872b2bf4c72841209f0ca3895360cdc5e7e484738d7e4b6886e227c1e2e2f1526d583117d7c654a75688b0c0f1310b6cf6f2c7dd49dc3a84d0010f2b83839852265f1fff3c307404f18b5926493b6de12288b1e703cc5f7f5ea06ef1dd2011202c80ffcea302c6758de23336d03c5d25f3df577790a4de49efd09472ba626a60573fd3320afc9893dbb35cbeb08014003cc61bdff6bb1d5c46590164c2c7a0b82f1707b8f42789a6e9b96640fff53f71c62840ddc5eb34aa4d3f3ffd8b9cc782a993dc2fff8b636b75ee60087bfec016c72cba50c76d30f9da80d8638f029d4db221e00d40a908fb7ca99f32c671387cd3448972d84a69459dd1a17f1b40e5512b04e46cc28b5c8156ad54c13720b0faeb614a97e08bb4a78039ea027bfccff6db0637cdd6e0ec832423ca170ad3deb57ffe3343c34f069a5349fd8eb8de975a56587044472f49b370550fff00b16e9fe39ce7e0afb4b8c0e43e234a4db043c88d04284e5228cca274ff92236ffe0f68ee67be398a65dc407da34fd518f807840732e79b881a06fda39874e17964b0f4742c2714e3e70d92055827725bbc7942d9dc4828d3227c7735267230ed24fbad8e9883907a1e336963125a2366390709741dc89e9448fdd2145be4e6d34a4d1d91470dac20b4eb76b970ca456f8572fc4633fc2598247b792687b1c7b331a3b4461f0012e4e9c9ee9b04dc587dc8371141a655998648ff89e91ea9264bba62c8e825489d9c7b6913443b8b2141bea76f451f489d05cd8241023b948b112a703a173fd65c849ff96ad41837bd93a18a59dd35fee4e6f87a9e5aaf775041f5da2d8112a138c0cb8594454a45ca083886b4e310d0137d663668d80b30a9ecaadb345d561049d459bddf0ae98bcd165f3d1ec3fda3e856e095d7d0800feb329b39f95c9fe291876825792a3203f4cdc3addca09a724f687018a24e104c3dce0bf414d27c0bb2d14bfc1f5860f3bb1d1b84869e0c8949b867e390207d96b9506fdbd486deefac20a0b1fc459983bc0e0b98aee13da113a36ad2fdc654a2f9fd8128409c00ca213bd405f60f97c85f4332738288791db25ca328b4e7b3adb7c0fef638f5cb4b1b31e94e47b9ab1e2e9a13753e1ac41f849edc5777d3e79e911225d79a952d93ac11bc52b67451cd6b87cd75c58e5ec3d2c1790d1637276914bda8b547057b6f09f9056cff04dfd118043bae7b50b13af90a8cf86475d6391ca5b22b0a23d769a35ade080c36174db8df670d0b7555619623dba5f03abe959f440ef0fe46f5c3b243c71d9f7e8a50aea522d08a3a89548150983254d893e3c26c8258f62008eac8e3187c0db5942ae141e62412aec3771cd63e1fa224b42a59847c6e7484ed31b26a1fd8381cc0ac1b86e6873ad8ddff7a50b08836baee156592c2e418a7c6b3294d246683b645f046163430e170d4d989fae7861782e9e85a4a8d677f8bbe23ffc486cbe8efe00a4832bda64d452a3000311df6aaa591268dd750376ae9ae734810613de6931414f2c3d202c82bec35f761dbf0588b62177647db07a0e9f9643862cc51e6403f02914657a27f457d717f19afa00bf6084b6b34731f5b9964ea7cea07d5fc5a85c12a00210272f19b38803a94e9a412f7de54621888be887f213fc7d9c336a07c5cb0bdc4dd73508109047147332fdf6ac19d17d0afb263dea52bf6a651a5a55ca912ffb69c71e36c05dc2484dc2aac4d0f848e18ee5718edeb123600c02b8df0614883dd215e67070e83b980571522882f83a1e9ce8e828ab2bfe34f42558e6a9817745974927ae5333d244821f67d9df9c95495ea16745e861e5f70b72027b74e25f0d125871b44f3463eb9a068bcef7cc2e5516bb9f3328e897121b563e302d606248b579f57b2deaa605316584c1ee99e24c5a257a590124cbde59c00253c501317b40c2afce813c59075b831ad1e47fd01ce1b9ea012264450f8d083cb43541c6b772bb254f0ea63ece3168b9a27ae823339c97bf53bcc40755ef1ad035c9046fc2f43d0730498fc4f82c511b831d1f6f671ffbd0f60aed8d1eda8b0a6c318dd5006684b14fe4a9b0d4289fb208146c66bcfa93837cbdc17268465e11b3bc4ec000eba5b06edffda7bb5b530b8f076b6de88621c368be445efc6934f7da18263e4945fff49415072fdc2432368dc42cd6fde6a9dbc6999eb59bea5f2bc2c1a311322b57dbbc7d66815760a6245b0a8801501fc46d4c86204072ea71310dc2170ece85a1a4e93fdb5f3279ba9ef0d8b214e89733782b3073cd170ba670e47d832f8d44d8cc452cbaaeda04e4bc0c8a5243f98713b02cac0047c88d02d7be6a9e661c47367b2d984668ed2631e89d848a37ff8f8b7466fca64fc614be9c9da3cbc16fce7138d531ba6d1cbc5f82cc7fb4988c4693f6fad48d115a621fa5a1386c3a158c28c25a8ef9dc7afe78faa7a3563650455c591a0d943da8b1854a3284fb24f48f8520da6e6ce0a50ee7b2deced0701ec88adfcbced1801c4f10b605819f80522e20b15084141fbfca155e9687368b7bac253f77f83bbea5c3a0a03ac22aca32e7dfaa5bc33b3ddd928ab92af639675012a1f14c7ac8e4370cde2f184e1ede5465f7e7e567f3ba9313ea13e3b371c0ab55900b58463fa2b623f2c2080b8bd6f0f040552f2a3177953cf405d8d64f1cd6f260cd10bbb1601cf91239eef86da322d064eaba184c4ff115858062197f40b1b91bddc8db374aadd3248036e6107e12d416d5b45e4e0a87f5e7366446c84d16451d93f985e9dc5dbc64394b513bf93b560abd74eb79bac566cdfd7fb0fefe8da81f9fcb266991ede82bcc349e8bf7302175f6abc5a2416043252e160e04c232deba30f24f5b9cec79f61801c199db899468416c198e9adcb709034392d70076c5eaf585cfc1f102ca014081a18ae0a5e32bc6acca708a6c160c57acc3aa20169e2bf91ea91c10aa8a2a58b2d498c69fb5ee647cba92832fa4473bdb495e17f6bf2cdc42a82b2e2bc0f7aa8884b16f147021f23b06554a56a21f9b0d9f235bffb1a1a9edb2e3b012cc3777409541acf5b275561741ece824ac7d026f0c39ff21e284ecaadafa04c34bc72ec88c8082cc287295d9c217ef957de0e1bd90401db8dc3b7361777cd144deb06d680236367a2b055381d645d6330af6005ce078240fe72b7ca0c303286c50f68ac2a419106b6d863a9b97f5cd88fc70aad3b8cb8cad39af5f736e88bd98415ebd8574e562f1c614eacf50215e1c82a930f3e2e82df5a21da4bd7120bd320b8f2eb6cc6d542e995e34d900988f5a94954c33bbca43fb6ade09cc973c836d9230ee66a2c0b4831ad630f49691ee0a1cb1465ec1ae046407f225fa016553e46bfdc4ccf459105440ee49e7da5db84014e2ca337069a8998de3b3cc25aba6db94dac1530fd577f6846e09e4e1f5c9914d6b0c78bf7ff28e9881437cc0e00ac98277405a7e8066963a29156f6a41d2d6a9630c903da922c9d720ff8fba2adf52e117f595cc200ae9ef0e7b51966af7e78c5ca656c549b6d617b8ed4aa9127f7ae7d80630f5536ac75ad2b56300dc9e1bf14a71d6b7850c312c702bfae60d0a6d50f76e07a9e3f6347506f636b13d271468b01ac1605de2217f5f0beb2f2e85c26ab56e6a91a108275b0bf844c958b0af0222ae7ecac2b96ea4f56f9ae0aee3b7845a7dd181ee81575fc1f440eebe579d10078131b2379103566478b904b5f28680a5fc9f3684f84bd7eb99967a9f2278595f330bbc41d10f7088c540d0d19a93b65aabbee34b26f2d6002aa1dd673837adbca65866675b2a125a352fb68384298c38e825f986f052db5bfbd1216aa11855509527d20411442b376f6424aa518d081a9371b6cd032718b22a6f0cc6e9667db39525e5a671575a706e6e5e644bf8b3b158ce06bdd9593f11a0f1d641109c25c9c0ab03b9349633aa57318914c4702a53223eef1923db68cb1c5020d905c01d0dcd38433006ad5255116a3dc5aa7e084d8ab10d1f270d1c786724d6c8760784d109314249181fdeef836d59c79ae748aeb2fadf41ae298d92d0ddba53f1f1f1ff30ea125361a23578ad4b937caa11a384fafa1c4978c19b71ff2f3b609810971bc6be60c0fb31acc3ef21cde2b85fd21aff4958e738b981883b7f30dc7ce85aff73e4ba80d4759108954456b8601d3c93c4010b24ece63159ea22bb69f02b60db70af792a2fa220b1035cc745c6018a5e967edc1bcf2e9a2ead4ce3077810b6f5c2847b43b85511e3ef8dc4dcd31a6158fa8ab3ec035d8bd4f134eeab0bbe1fd52c5c141e06577af66599783ab038af045885f2aa6da28f48408f667f8ea5b59587b5c475a1b1e55262b217157993e50549d4927766119e674a733cbe8caba446bda7cef55a0364a41e1aaa69f965c139b829dc7427e62d36873d7e19081ee1ca699422c92bdf8288e340b8ca33a9a35c58a92263d3b5070c62ba0b6313ba837040f4639a22d34982149ed38fa1204117cc7af4c959e0650dbb161f97ec704463b16a1589a08c096da40464431affdb0d6c0ab84e554db7aea6cbc316ac951f79a8c9459daadaff17f543c9c4859d36334b7518c52e9e06dee6723a41f96840d2386d329eeb316709c1d95caddf882b8cfebf27ee199854b124e1adfcd81b3c1e5f4ccbfe23430cbf72b49bb05c7879ed1aeb2acb1bc7325b900938318c7104d8dd2057eb604426907a637ed7e19c6d7336d83d112b83f3aa5fc6ff0984ad06cc6b576f6ec62c38f37f7b5080ece2c62d3025205eec7ef77c2f5c618669f1e5bf20d792365e6c44d3983f83128179a89c7cce1f7f9add93c736c36eefabbd2f501d1d423199663227edc8b90efa5256ad8c8206dae862489644544aae2b5342f9a2f4b325477667b2fef471db6c1144b15fb04e1f41a87a43f14ba69515b4cab35d5ba326fe8ee6917f867b9bd4e50a8f2230b06c8bba75dfac5785815c846c533e0983e997aac1dc83ff73577f4740df9ca1fcc3bed13b611da3ed055b25b12780f32cbe708a56a79952bc6e3d23c99312246b90d38d3e3c6c2e3c55ff2f04ea1303ae45b3617988af9ecfa86c6808f1b6bdba20c4cfda71e6430c58fb6c0e9c82cc0d0a9a2e63bf8e93eece12a943eb702e748081e9b4d2e37fb920ccbfa6dde536d9f6dee7f7abf2c6c13b0effcd1dd4990a52cc3cc6687ca703675dd1cc074b308e7fa60d57076e56383d5e5f02e1bfe29592fa6ae0e8c90ffbd94e8757f89cbdb196d1502cc598368cba4989beb574a8ac3022354c30a9fcd6a560c9e53c4975fd7757d74334459df724a84d6706975a56094de3e9d39090389951b903fc16fe19e6f8c80716f7b9137f6911e40b5540f57c27a47c68f40475000231e8c613fbde599d0a08d9276c2f58fcb1df2f111428af03f2e9d7d1c942c4fa58c67eae69bd5e51400b097d544b6be51e8c28c66048315e3d5cdf79aebebdca54118d5f32f5e024144027a63c6f162cf51527d130c5a068c8a213318b7f40776de479df22dba62d764b5b57b88c7b864a62d088466307de16f7e2d36751ebc1e2c4d046d2d1b6fb46f14c592589eb6fc5ed105b73692674121b979988efaa673f93b32dfb4641ad085da16e1e3a2cd9a9abf9678d3c6db4ac74c51132a0bb1d858bd9f247c016bf870a2dcb824231408875fda4ef8fa2c52114c7ee586e213834540e622014d6acb682ffcc2cccbfd13571f1266051578669dbd9622319359d66d27f14042feec254952d3549d8b027bad35780315b0657360aa18ef18c10917a0a9937d276cee2e8a4495b1c2d7c6c3ac8a06a69f31324b026b0295fdb3474d5b3c1b606eb62d851a1e2d84fc3576f99a300016aa356b11baba1e0dfec87eb544743ba15c92ed5c3565706ca8b521e5ba8631070dc1d1d63b5307fd2e871acc1268f0da20719202ff0f99f7f0f53aff74b773676415e9b1455e749d291e126bd032798675fd4725a2cb877d0e83d9840c74cc6e93d4c0011ca01a4100e396ad83ad8ba3bf80d3e3a93b4048edd829678d3bf9060af91c49c06c5c92d5d4827b310cc21d75817f02cd8bf4515155f90812d30ee24f69743314677574515b5c0463fd3a49aadec3d61be3067e41755a76cfa96e44d4dce5990ec80cb998ace89e5c8c728af14cbbcad72ef6559e0d65100e879db9934b71c4856f53a49b490509c2984f8612793afe9f285d17338e80a47ce8392239a1c451025f70ff2390538b89a5c1fade36f54e28cb17391294919f780f945fd047b639787c588ff4e9913e77348fd09e8ffcdad96fc16f52f72967b50d2de81ef3037455bec94e6e6f36809804b7306776c639daea9a85fe120c60eb6019526d3493490f65d5c9f4d7260b6dff838b311e1240f849c44e40a8a082a86843461a033df32f2c5f10c13c642b22563f15e83696f71c875294cf5706c5fb10fc7feb9ac57a4bb033ae537d35b204c4a9b0e6569c85a1e4ddc8d48371519ad438a7623eaa621a57934518f2d867b65d6b2582daee8f78be9db3a1076d945cef90d7edc816074cd461f4a71171da3536e38517aa1a7c1bd18433e0d86f82da4eddd6defbdb79452ca5d0c400d950d3e6f5e740975cdbaf8fce0bafc74d1527e6a3d2009cdaad4805cec0431010a42e2484bebc5dc1bc82648c8c802f16f563d929511f2372dbabab4173f59e082b69c28d2dfb188b60555c1e566f563f5fb665d50fe7513f250516bee2322fa4d73376bb0ad185a31b482546188288bad5f42d1b7055b39ad9e18824b4b1f134254c2ef9ba5e2eb0df3d7c777909f766f3368eb9b762b2202cdca2ab38ab0ef25dc8bbffda6159d3d7c0ef45db384be2dc821118538dc02c2f466e122310516687ed3bcb7857a1f74a48505373f852a0e4cb418af5bb8f3ead14bcae8a814abffdc9b8a37472e4a513497545cc446bb40eaa97d282da791bedc219544bafa8f0a8b045ee1ca9aab9b46b33547ab2e54735724faebb5e63edf483aebdece68ebfb66fd75f24559ab8cfa0287c3e170385c08e6d09c035f0c94ecdae541b7d977c6212d0c43f4b34f038be70ebd04bdfcfc9d57443b507e46f78d0f511bdfb9bffd4380fb7dcf0cf913c1d26c212cf594481e99ad79953a532289efe7932c711903e641871ec2768ef7e03fb7ffa15faf52889a427a82c8311ebd3faf8e1f077f9fefc68f553cce589724974cebe1943c3ac4d9cf67b73b1e64d92b566a679633b618b63dc0284f3993415ba7953c3f7aca5717d1a77c89117f4acf522b83ac64a0d287082a673c08959a4a9e59115f10bd3ccd267b96a083e7be9f7d67ff9cc4e78be0f7dfb9af8a526646b495fa871565d00e43258d4b262ac310f2ad34a31495e411162579a445191b221a95f52af9ae9edd82f0b687ceeef80fbed6a3b21c23ba28431c2c5219b4c3952f26309e9030cca68a3fbbfd218005ff7bfc96f715c328b3123746592211934ad04ba6a0fcb47485f1028743ab63c4dde6aa458b8040de6bc3e826eef4bda86851c8b68635ac210882a07f3db0f2c4ad5fdcb4174317efed3b73e842cf81d0bfed1fa29846d31aac9ed1a334dabf3ec6f904b77b6f89b1115a6febad22997b6f0c245555adb5d65a6badb5561c36e24ad1d55e115b9ceb91bd169b35f2077ee1a7f1ecda9b74034caa3c2d74a9e88dfbefdaafe62f15fd7a908ab808a3903f10346b842888e2f88de398c7118fe3388e771ced388e63ddb99eb3d974d10f346b5cf3828bf3675e604bb3863541f333cd6c9a26bed634cdaac5518faf6bab6e6a7ad9d7eb553fd8ce318eaf175996668d7bab47d54d1396c1f0af63fd9a69dacb34807db234611606abb9b437566dbaea982607f60d017009dea233186d26c6625acbec27e1e0dc9a8c6adcb7d5235a53adc6fd593daaae5303415f746b2d3565b058f176496c9408ae180d92967034eecd4ffaa29a878746c3341a8d766d85c1768effd120968afefa6d7f8988e218414ae36a194e998473c63b7767c7eed411b64d2093e1e0e4e4cc664d3bb527043f112747cfececeaecd096684b3c97c77eb3593dd2d1d9d9e1e1b9d1683d3d311f1f5c9251ad4040f8da71c7a67b2abe9687a67b74896bad866bb55bb322cd6c227bcc1a3eb4ffb13f15f7f8d89f5bc340b9abc6fd1bf5a866f0d57b86e53d77c5caa01e55c7251989e8a8e26523cb7bae3022cb219308768b156916b2f29ebbac718d8c4a23f2351a1989a191ad3cb0d247cf4a5befcf4f2da82654ddab9baf4ef6ce1345672a542ccd48aad5665b3134444434a335dd6645452c74ac458b16b0162d5ab468f1657c5bd8da53339b6640b4594dac41b66c02f2201bbed1846c4dee33dab56fab3f43f1b5280ac27e0800fefdedc15f5488f4d3ac71ffdcfaaa5061eb07733f4fb309b5e605e58a3a7487ecd0a7c2244036c25d5bb1b862e887be2811d1ed5654c4a285db6824d228864848e08794f145b24848485506db3986cc26a2dbad286909572b8b92058b580bb3a9fc8c32bed6a88ab08de38988cc1a96bc990618c1772002dcad88450b23b3c6fd5a8f8ef0b5b51e552f329bc4ebf8a2b5bf7bf6778bb6d66a3d8b9f495cadb579cfc0d246ddba3316b4598dd455377f6b43f445599806b06f74a76817481e64fd30d1d769607bdab03a5ba4ea464797cce72e3fdf33a3d8baa88da02fba4b24dc8b180cb3a9c6b03144d8cef147f90343717c91a5098b69d9114ece4c678787d6e3f353030a12f21355615b3144742b62d1c2081f5d5b9182fea21b036f510838216bade5d72a02b25eb2d65a6beda006fe6c91acd7f879eb625d72496bd6c05fe33e07f64350ddf2e8d23d3615b0f315bd17a05ffebefce12f5f20d82ec1570ffa8a6e0c7c05edad6086596b6f40777e8ccb2aabf7265a74e7382f0dda7a07f9b768e27b4b6c7a366bd8372ff89c03fbe641149b4d1fca817d83f3f6e0e782dff96d8da4afb37b13af12ce68ae471fd8af393bd5bd4935b951301e3111b8d136b6641b94c1d1204bc4e0deec6bc6bd595ec525e5d86eb3269d7bab269947c7e78869dc11631c866088437c86a82649fbb9ce5967b9c4d556b0fcc8b004cdcf926399bd963d332c3d2f1d7fc57268268c06334d8f5d530d6e8b5b6f9af6edbe6f1bb797682ce1a01b67f418c750186aa2dfb967dc974ece399b5245659ef50a559e505010134035dfb8194035b4de22dd0d70aa8914ecace68c2ddc52122ee9b7d5cd48b28b8a9d1516b4d45bc344cd05f57a3d23c8a0a939a0ded501add15253a6f6e3fb9644d67ed07a8b2aaff2ec54af17c512d490277dd658121b9cd1cde877e525f9b03caa06c201d56a49fc18fdd45ba4857323029cb3ca8cb8fc86baed77e51971d9d8711a971c4bd1dc719db3feecac9ff3af276b8db465362f7839462f4fdb12aa9209355743894aa1d26bcd95636c24fdf3986fd14400e9a2d3b8d78afd604755c2f51642cdfda061b53acb542bd2ce327d20c7c37842af3527f3d07f646809f5196791b26b748bff64c2d09e2570389e1de7c3e39b66b348d849ecc383d65b647b18e7dcb5d7413e77199e3bf6dbeaac91cffe9df5a6b55366038c6edbe3711cc7711cc7711cc7711cc7711cc7711cc7711cc7711cc7711cc7711cb168e3e91609635ca3618c6bbfadce26264b831f1feffdfcf47a3e3ebd5e8f8bdeb6b7ad4e4bcd56199e1a0d9ce3a9b7c842ddf8aeba6050b3d2b470f4ad67b519361f1eda8f4ea3b550f125e70a638c4b1f2743d78e4d04947ebb91a10ce63c8ebd5a267be537b7dd7078ceb07e20c7c3ec558865e8be619045caf64ac7501ae65ddd5bc6bab7fbc4bb4f50ecd57dba4f3d3eeba9b7483c79f6ca066d2909c7d333e3e1e9e909122228485ba49186e65b346ef1a9b43133bad974199ccbb7a8ac5d6e64afd1b0d49beba191e4575a74973f3aad6ca1a2462dcabec31f259863cfa9b91ec75e2f2ecbb284f59c5697b33c5c9443d27765122b52e915e631c7a6024ab7569bc7ea62386e9964ae694c342c920df4deb255118d2d967bcb8e516ba54f5c6fd86967588bb065aaf8cace0fe44000d66e9178384723866627419a571a6aafb2e72b9cb33d5c943d6334db5e6d076d755769e39cf22e9fae5216af52f8f5b3e346cf5e9ef8142dd2e83430e8e1299e1de4dfb8f2756e1be563073fd06cfa5ce7b43f3bede79cf6abc539edcb4efbfab41fb36fda27edfb540bf47df4733c2e3a46cff1ac16892c1d9f1bc7934e0b63be5b6c15347e94b4efd88f5e6b8ec777ce8fa66fd88f5e5626dbb348d9471f3d8fe8b661f4a1eb30bbcf4e8719a5dd9b0ee7689edd2d137ef3e685bec763354773acb3e3aa84e3d8c39af3713c3a769f33ac45a3e39cb8de46979db51ef59ca387423f3a6d44f78def47c72602467474b4bcb78cbe3e6bbd6df2b1f39cb516edd863a791ddf62c935891b067a2c71e137aecb0d76374db3035ec34cf7db397e716c1dfc01f3a79ee325b2411c561fe7c7739d7fa3882acb67d9f1ef41ad9e2ac35fad14551747b6ea3174791c6c5dd72a45be748ab93d7e2961e74129be6a9c12d02fd737b7ebef556f7d662b3e9739cf4721ad79ed8bf93036c36c57cebaf9bf675fbd77dfb5ac5ebb1f3e7f398cf5c9f3f5976fe64f4670132375d03999b9edd4471c090510db4eba0384cd78e63e632cf91c2ce9f05c47ce63f1faa41cc673f1ac0dc74989b8e73fe7caee339d29cf3e7437f1690e338ae418ee3f8e738280e183e54031cd74171fc7cae7111e839521cc7f1f3a11d68a0e33bce809af2107111e833df4171e0d8f11d5407cd91e6788ed4ace9ec9673d614e7aca9ecaca93e6b1a3b73a4b033475a9e9bf6a0db8bde001d708623e8514435b8b70c666d34f69b331de499e8006e1148e3de40bf4ff4e719e4815e6d20ca81fb145ef108f1c918a9b3f7fa6ffd585b5d35650fd83155af32b82e8a9ad694b484f6f377edda2da0166107a32bc229cdf9bb5e6133837a84773cdd5bfd8dabbab75aedc5b0ab9b45a1abf7aca0abdf1834f62aa6f8695c5e0884d669eb4d28f5d04081ed495276eaa744516b1acd337abbb70e7a3d124dd7dfc09fef0d076c8dd52ef500e7a3862a1e47a8c0c3619db0814956ea81a52188375569b53136f8d6ad1367c04ca5624ef0822e253f304c9a22bc948c9d3ab4d15b8cb5d16f64527052d8eaa51e845ae59945ef4c3eb1a384def982fb6280fce0a578aac8d653ad328e2c09612bbde77dd29638ea1345cce8520f88f45b5bc29523572919dbc9b23fa4802295064037e429256f894f2bc5c30b4e2989afad382b398344554ac684c6e82dead03e2488524826ce2abdb310114ec24e113d85de59d64368081cb687901ca4521c1ec89670aaf1a4d27a29d8b7b7be6342ac4b6f114848ad0b171d78b5fc02480fbc948c65367a67120a718a29466ff127434d255195de1d9c46d8a564d797035010bd45924de53255a54befe39cb64c38ee94142698e8520f5e5fa052187e0c50cbbcf55ae62dd2972bba948c8950f4ce2fc6a0ed25a9ce48e19091e155e68c68268af04c96b18c11b90b9b714144e3842ce36334234378e6074e991e6740087110c93019cb44e19cf9209b717a911973660d4c099c32278468869067ce9489612433251cc28c4e2c53430e9a15341925f98c0d26192e386782d065b68466bc8848f81c09c9408166ce8867ae40abd10c980f4d8f98192be11929ca7811cd2891cfac5006cc2884c606bc4a395bf9e94ed5dedd81533d98dd39ded226c8d381bd114fe55de5e5a97a759fb62c2feb70efbbcabc7b05ea763c7c957b1f0fdcd9a99e6eeadb029f2ecf5e7d4fe0957dc2b96f07c3d75a6b73c618631fc771b4d6da529cfdadfd35fab3c7da9f81bc00202f0dfa96f8626c36651367f382ecd96cca68fdf087718da4c7582b3f59afb5166374976fad78f517e77bd1259eac38e75c7ece18573dce5586f59cdda2376c6dd7f51cd7f9b9680633c7f12eebf312b7c02f5f9f9d77ea52603de7bc5597878bacec94f9f8723287312dd3315849be463104a770ce56657dbc9d9f6497b481ac7b09976b0f43de7a046e4e5bc0ba8d9bab3ceb343078eb349d75c739a61d64e0e95062862dbad4faed955aaf5d5f4d5bbb1e0ce993c4a555a0db03afc2ed49a25eaf52be81ff7a154dd0d77116579cf5ac16c9bd5ea50c8a3324fafaa967444367bdb9c8af67507c95344f415f17aa79d1adde2c0279bd4a2dfc7a06c55709d3b3bf6e33a2d2d7836e462d5814d59bc572f47a958efc7a06c55709d33833da5fc72dbda0afd792968e8c5ab0a8378ba5f6f17a9590fc7a06c55709d338b31d9a8dda5f9f310021fafa0f8d0120b938326a516f164b3d7be1d73328be4a98c699edd07c6a37bebf8e410647f4f59e0364f00287e4e2c8a8de2c967a46e3f17a9562f8f50c8aaf12a671663b349f5a905fd7200241f4759a03221003c60b1c928ba37ab358ea19ad76865eaf12057e3d83e2ab84699cd90ecda716e4a8edf6d737d080127d7dc7021aa02029068c17382417f566b1d4335acdef39cbb93832526a41018ba4a2183718442f86702b906c36ee0ddfb837ace3811b39250a9262c0788143aa378ba59ed16a6ebb275801928ba325a35c0b2516141425dd6210c1187ab10267f37b5d04ba26fa7a8e57b3060e5d054b39250a9262c07881ab378ba59ed16a6ebbc9bc5e2526bf9e41f155c234ce6c87e6530b72d436746361e40297c4c3470cfa3a8e930f2619152ce59428488a01e345bd592cf58c5673dbcde89e00f0eb19145f254ce3cc76683eb520476d433716462e703092969ea060d0d7f5102800cc609251c1524e8982a41830eacd62a967b49adb6e4638b3bc27cceb55b2c0af67507c95308d33dba1f9d4821cb50ddd5818b9c0c148525a9a119544067d3db64b6241000030834946054b39250a9262d49bc552cf6835b7dd8c7049f77cbf9e41f155c234ce6c87e6530b72d436746361e4020723496949c68c1ba0d4d40afabad964ea6958100000cc609251c1524e8982a418305ee0905c1c19b5605174231a5a6143cb7b0ac0af67cb94e4d741cb14c3af8b9609865f7f59a6d4af9796e9855f8759a6f7ebda32e1fc3a8e65a2e1d7679609c9afef58260bfc3acd32b9f0eb3e9629007ebd66998efc7a906502805f77cb64e4d751cb34c3afdb2c530bbf3e649998fcfacd32b1f0eb2c2c930cbf6e64998afcba0bcb54815fc759a69b5f87619996fc7a926522f2eb4a9629e7d7972cd3905f97619994fcfa0ccbb4c2af07c03251e0d76958269ba5baa74f94be356c156aa708e04cc1bffeafbf4e03f6d72dc0f9eb01d8f9eb00f0f9eb3382fe3a13fad7650cfdf50a58fcf525177f3d07e3af2b29fd750ace6a91b68cbf9e74568b743dc6792dd2751867b648d75f9ca245ba8e3b4b8b741de9d416e9ba8b736691ae1f9d348b74dde8ac59a4eb2d4eb748d7599c368b74bde8bc59a4ebb7d3c8225d273a7116e9fad0996491aeaf38972cd275db39c3225d2a1aebecc53832988af346feebe8e9a7d01974029db5f3e7dc3376c5b2abd563a761e7397715f3d8774e9d7357358f7d76eecaf5d873308fc72e3bb7f5f1d8f5193bb78d7aecb073db248fdd3ccb735baab753bef268380c361001cee5d44ae15c4ef56b44f752e589e8ae57352bc420baadcee943b785da6574db295bf5d8df1a599135337ad3bee646b8ab847f30fa559ea59a5279336e8052533396664425594a5a7a824ac2253df62f3b68847bec9fd1cd480dd7cd76eb69d9dc860d4b0c58fb40daacbc38dba83df64f033411dcf0c00c475bb23113c10d6db33133f280164149bb91fd46b9717cf938679ff349578b847d8be5cdd5d42784708a2d25903085ae8717352510117eb50732388ce912c4051282209115e69081d72386ac333a22241ce1252c011128d4c0c584294af092664bf8840b574ee083102e1f2768e120d38da03ba1491256824033259c80133444818035c58820cecc109aa0cd1340bcf940c90d55cc3819ba0a13840a6d94d47082324c3c18a306092134b4095302191680382289102098b9210d162354e0208a287e2051831211ae1491c2f77ddff77df58a13595db6a420458931bd20e80b1282d8f0f402991344208284f033cd0891a5a3049e8e26d23cc4e7127a7cceeeb7abf47ddf057a14896255e487ad11c6bcc981e9f7c1bef2802893132409149128165a5882462b3bd1c2536865245a4bb4866811d1ca45b4f0105a369c3f3014c717599a3272776f37c9bd5d276153f776a9eeedfacd57e92a5da57a95ae52bd4a96eae62c8f3745eae6ecd454b5bb5d92a79bb34f4f96c7cd599dcefaf8ea22b8390fd8b8b90d947a5c5f7d833bf35a97d06abe337af59b118f57c7252dd1bcfa8cab642ac1bcfa007abc3a0607c8c0c7ab6be080085ca5254e9efe78f59f2278158bb556349b6c93aca91b60b8cbb29b013211f3b4f419b9929461c0dac2e117ae7a46d76cca41ed550afa478bdef6ca8a59e2e469cd73fcbac5ba01a85da59cab94e3d76b39388d2ec5253f3f3229f4ec5ce2e3398d0f49c6a2746d498fe7343d262642dbfcdeee121acd34c99c451b2de1e11983e824dcbddd253bbe63863df48ca57bbb4b7496febace87b7f400ee9299e734da62dd227b756fd7730b1a837bbb9e465b191220b09e82af3c20a29e8cfd344086c3c608596276a9cc6b7aa5eece5c404d655ecd05dc5486ea0ba2a065cc4e8b18aa1f231ef49002892198f86063450c1018728200c2abf22901f1c2e585228c40e31466cdd83543ce194957948f1a6648a28627217ed8906f789203c8981c80a0d2040f3acc0026081f6fc2207e8040e583820f614029524723a465e9ab5dad6e8f5bda00575a57ddaa3b874e14d19d6347e3310284fe43b13f4675d809692bc63bfb5773ae18638c6eec1f8a2d768b51119bf12763d65a6badd5daeada005b895759342ec637dc92dbbb4bb1975729fba4a0eb571a37973f2760ce19dd9f874e7edf9773fef2f77d9f8bf9dc95778a637df988e67bab34b1ee172abae39cddd1b2aa156d49e3ae886efdb2ec2097204714f1206a7bb6cc55027368367d4c501122cd6fcbcb4df9bfd3fe0b225c31e2b7dde51d50570682d9e7339b3218fa7590891e65d0fcb6f7011f9cd83f27b3d9f4e126bbf572aa392ecb91c95ce6158699e7380e1c9fe5a01e5c29a63ce1a03a1cc741af0e17d92e5c64f5059900b184807db061041f9cf8d064874f903c7ee5f9f0f47921ab6d83bcb7d5b1e8bef2aef06aef2b0f87153e3f551956ab1e5906dcf442e1a211f4ce4f6015ce7d5074ed74dabfa92a9c9351648b148f87733952eb9fd5778573301d7cd8f2a44bad7f5938b7012ecaca3d9c6bf1c313434ce952eb590be7725479f3e6cd1b2bbef207e22cabd1fdf5defa063897b770917574572dd4b4254aa2dfeeedd3db4f2a4fe5aa9ef54feb09e7729a58c2422fb59ea1b670ce3a815579f76d655e9109469e9cde7e4f34fb41bdfd2eba7317a8ed2786ac968903fb1d84808310fc1af56b3db24cf5abd71564b56dbc55ad16abef8d096d7d6b7bf1ed8729460b153d543867c99f0e4e4e6f26f9c01cb41330ac584211872c16e49088664988e37805a7e3647c895680bebcc8320baef270608ad14205f8611ef766734296592cd83433486966090096f16f997bebf162c24e007cf51ff79910b0d80c724b4c972bfca631415928da105a2643eb9d37329c0a8094c0c9917a5a3d9a4de5cc723c23cc7494b6e8ec7ca1e025c40ecf0f49578e858796f318ee413c5aed19abff70095584e0e7617a024f5a7fdd678773396a7803b5440a13da30712487df3e5c9ae8fd20c148942474e9f6c1f2dba792397383086f8cd031e44dba7d70f8ed63a5c46f9f2b1f2c2d3e44b444fddd3eaf2b9fd795cfabd7f3eaf9bc7a3e3d3c9cb33991850c0f4c7c6872c495df3d25b061099b10ac38a10211e9eeb1f2bba7891d9a10f245ca153247d2ddc3fbdda375e677cf16d70b494f941792297f774fd54baaa7c94baa87ea55d5b3fbdb23f5d7693d9cab5746b6ac382199e2cd90b4ca6f9ad65f29765052f5419a3457e9a6ddf09be6e2062468b2a42cb1cb92ee1e2abf7b9c9e86fcf5dd03d513b5040dcc123431fe9b96d545ebd245c37a39d1aa5e4e34decbc9eaafd3743857a580e255094105104a3062ca6f9a5391d490ac1c74d0ea6242ba694f527ed3a0aad090c58b0b6894880122dd341b7ed3a4fefaa64dfdfd4d1bc1f08c607878d4f070fddd3c5dbdde5f1eadbfceb3c3b96a35441026a0f8e0893354d228bf79a4feeea4b2d2a4f0c31133e9e699aafacd531535044b08266868d811916e9e1a7ef3585d61fdf5cd93c5236664c243e49944fddd3c50533c3ea67886f0ba783c3a1ea7bfbec3c3b9edc48ea9a822524cb540c3ef9d094031460910281cdc2c49f7ce0cbf77ca1c4105c994a91a76a090ee1daadf3b5a7f7def6ced70b5d989d266cadfbd5335ea769a8cba1daaf169076a7cdad98d3b527f5da787732ba24822a48d14179b23a0fcd691009728322d0041c20e27a45b4786df3a5d1948212545083abce1724af7ce93df3b4e7f7def3ced0cd1a203468b98bf5b27eb4aa7cb950e564fa7aaa7c3d3b1faeb3a3a9c2362d3c49724d448c98a4aa77eeb0899a201092c4f5c31a12add3a4e7eeb4021400d1537663e6063048574ebc4f05b47eaaf6f9d291d2a91481b91889bbf7bd625eed4883baea959d6546f4aebafcf76385787f0f1440813c61899c22385e1f74c2acbc9c9852d4e4ca124ddb326bf670228a2862a40a208614c0ee99ebdf07b66f5d7f7ec6a86956646244dd4df3d0bb766e1d64cd4e588ba99a89be5d0c0820845ac10a5072e5f5cf89df332b264ca1a2e19485022dd394c7ee7f040a50405ae3146207993ee1ca9df395a7f7de76ce57069c989a225674a4e931caa9edf39555739505739bb1c29adae9c9d43fb9d133ec1099fe0e0e0d07ee3845538210e1627281ca9bfbe71a672af74e3508541da8441dca8e1e2f92deb0aa1645921542f84d29221ca191f3e64d8c1ab926ed992dfb22aabbfbe6557322c7009191170095994cc876cc8ce6f19545757974cd7e56495a5b7d65c5674142b53feea2ad04a37b1a2cad250597a9725f5d7633d9cab65821882ca961dba9a3ca5b685df312d28944cd165b1c1865dba634a7ec77688b082113b62d0b9a9926e9de4b776faeb5b3f6910490c44128bc5b0fec6b2c0581518e38131abbf1ed3e1dc986508104ce0a16b8824290bbf634e3574545cbe484121871ee98e9de077ac2e2143171790eca6d44049770cc9ef98d45fdfb1a918d5126d9670f317d6f575c1d47c5d5ca0130c7482814e30d80ee7162085cb0f2976f84164f71b56c14441452607246d8c18936ed80abf613ca826ac4ee044854c0ee9861df90ddb3018961518112b517f619f15ecb3827d59e69705fbb260260fe7727c70240c12416cd9c1c41babc26f33688a561b116490a2254cba4d1bf5dbbcfab1e48413aeb658e121dda635f2dbdca6c985c48c8264ca5fb3c95f73eaaf592565424999bb6d4afdf5b287739509146890f232246a2b4d5ae477a95501178e58f9a1042740504977b9f596c8efb28b053655a474ecca2c916e13eab7e9f4d7b7f9640ef97494603e1da598b24b89f5b7ccfa9ccaaacfa9e4edd2eaaf973a9c3b40126b9ca0e1618db92185dfa5539625b4788152c24b0c26a4bb44e17749d543af0c9621b8146193eef284dfa5d45fdfe554ceef92ca0bd9c68b9bbf6457ce22d5e42c2e2d324baba7a5f5d7c91dcee500f2e508167638e10986ad21bfc91ac4944eca0a503790c025dda409bfc9aa024001b36608992b4c82a49b14f29bb4faeb9bbc22b19e90449e9051a40f72c85f12aaaaab8ad46d9287733c49daa059e2e4c7961382fc7e591d200c1145b4803ca52993eed7d5d3ef57d60d26f8b801ca171d2944a5fbd5d3faebfbb5f5e2ca415e517290d794579317d5df57557e41e5d72ebf7a38577b505d4a1429e28d115da92de1f7a895801d2660713a8344d491748f5b16c8ef71851b98b4b015458519a8a4fbf5e3f7cbe9afefd7d36b081ec1e0514cf97bccc263173c62e1b10a8f3c3c5afdf551877320b841480c63c490b01322f5f17b749a8185146a006303971ee91e9f9e84df23d411dd0c312c41262b041de91e7723fc1ea5fefa1ea7462a6c456c83ad886e443522d75fb1cb4accb2ea59ed706e5cd2c4901e42662823432ac26f512a07942f49a69440e5871ebea45b9c72fa2d3ae143161eae7a38d1a149ba45de87f05bb4faeb5bbc12b1701091080e2246bd7e8b50184af4210ec1525d584ad461291ece6de089335474d0d1c2134a52107e873b4c6d21c14b218a3031a43bbcfa1ebfc3ac0d58e04161450b0f880ee90e7b3c7e875a7f7d875b215711619422c229e3efb04a2b6ca215527585505de12e94faeb600fe7ea159e0e2d4360d5d0c312e98edfa09690962c3fd610c9024256bac1ad0f7e835d7fa5da8809a31b42058974873a1dbf43a7bfbec3a7bfe1102d20182d62c474835db0fe825957e0942b100be85402160d52449460f2258874834f7f75bf412850eaaf6f702a074b37487589b4b944dcfcfdbaee4ecddd714d7d5953bd292da9bf417a28618286aa89287a48f737f56f7e7f559fd55fdfdf15ceef0f2bcd47244dd4df0f6aebf3f1776bc85fcf5d9fee86a9526303902e42ac4e904ef13b5bfded42a5851f94b8e92125ddf94a8adf994b0a2c418ad4b0860990746737bfb3d65fdf794bfc9db9bce4285ea6fcddb949aecacabb9da57232fc7802ca8e24ba256914bfb156d5902436403142e40459e9c650fcc65d5d57968082c85615978f74e736bfb3d3d390bfbe3354f83b473dc1609e88d9b88badc245559867b10ee7aa559b34407a3abef850a4eb3776c25039e81842ca933527004937de3df11b4bfdf5df98eaaf8e3656879bbf4eb8488d13d743e1a2acbbc3b95a86479612c82c59410a98d489df57aaea5a31b9bf2fd6022e91ea25eaefbe500ef0f1376bc83bfdf59c0670975205f5667ddb285fc194bf396618a2ebc70a3c64f8b2246de2b7b55a00102427f43802831b2ce9b63daeff6db9feda24346e91f56da3fefa03aad2586fd62ffe3d8af96b9b84e9b643ec1313bfad53056c8c10420424b844e892b27927217e408982cc942d4b6cd9f284e50b1a273a1de9016e4e57b4c7922f639e44118509e9c5b9ba0686105ad0a1c908315c49ef9aaf00981e11212c99024c557a95f8eb38f0affb127acb9ce82dd3e9abb76e6ba2b7eebd75f3047aebdd5b3791e8ada7deba6ea2776cebadeb17f4d6bab74e1ad13b76f5d663af58158faaf382de31a7b7aee382de315b0c7ac3786fdd0683deb0abb76e96a037ece9ad9b3ff48675d1dbecbdf59e2d7a9b5b597a9b5ff436796f9dcca2b7f9f4d66961f42e7b6f9d2684dee5d65bdf71a37739f5d677a0d0bbe4bd7520357a97bab70e8484dee5d35bcf50e84d3ad19b9c7aebb318f426796f9d4704bd49dd5be771d29b7c7aeb2f2bbd5f576ffd9545ef57efadbfb2f47e49bdf557958be8abeaad874588553fbcb8e2458f994da2f3f41ebbdeba57d1fbe5f4d6c75e888ebdb7be0208cee5d4ba4cea2ad5add00a0a940f6d1f3acc6c0a5d08bdc7a9b7ee60f41e796f7db645ef51f7d6675af41e9fdebab6416ff1eaadeb287a8bbdb73ea3a2b7389ba2b7b844efb005bd45dd5b171241eff0eaad8342e81d5ab10e6ee90d5abd75708dde60d65b9711a13728f5d6750f7a7f5d6f5df3a037e8f4d683b4e8fd59bdf5202bbdbfac87f2200882a283601623511ff27ce8a6d9149679d14bb34924cd269066a57768934aef9cf5d64d287a67adb74e12d13b57bd75b288de19eaadc79eacc780e88dbbdefa8bca7ac983de7807bd7110ebe293deb7ebadf7e460bd078bde77eaad0745e96db7de7a5011bdafee15c03583171d4800697325cd497cbe62e4c7679f65bfe1ec349d4ea77b4ab3111fb6f8d05f665398035daca3d9048a6653b6593827d3c9743624f496e9defa96eddeba6c274b72a5080f9539c9745f14fd5f0ef779fd7e979f7fcf3e7b95faecf92ac5f88c6585309fb1ec867c76d06ccab52a7d0f66b3e92ba5d0af2dfd5aa34b157419a5ad1859e13176db8d56b352a489ff3c5f2516ff55a50a840b9fadd98471ff15a1bf5ed1ee65bc1144e9a4e82d9e59e449e1b42a9538691e4b5f29320196b2b09489e505c58ac345d8afc61883a8888b70d5bde1f034deb6eab1ae1be7a2a979accbdcaab13cb5bcca9205b2e441014b59cac298a5e804acdc71428c953b49e81268069aac9465d1db5e5d5d5d5d5d5de1943b5b39a598c4ac14d55ce9944e75b553024599c18856caa674f5945a47f629655bc49f12a8aa572b3f1ab4804a5993a0527fd02554025df172674ded2c3f1d325a962da8284536b652e45a51c246182a452432515926b9955f1ad16e5c510964456f7b7525b228c5234a71a66b34bfb528714cd6f78b5c6599ac0d6f947345dc339e342a7b6a4eac4538173a765c95f1557f5199b2579ede9465caa82da22255e15c88da2b32323c268fca9b9a6faf663a92aed2b6568f1dbb6851ab12682eca0f8af592442a3f5114af704e0cfa7c75f86660448ab637e79c69554697067ffb571df40e1f7c38572cd2ea598de09716c1ec9f69dc100cbf0fbdf13d08826808becf68c6e126a4675c7e58c962d046ed267e9e453dea10c5a9a0ad6f1c4ec4b966d3222449ad5e95212b0d0dbaeb99d6355ba6cf33ba6fe01b2d63dabde1330404b83a5b8fb2676c367dd93400f9e6cd6f1c3add67b483d16f885f91b2571c21b83f82f8419403fc23fadf35b5b4f52dea19cdde1bf8b3df376f42803d3f652c6af03fd75ba4812e82a7067d8b354a54d3b2e72ca2f8f6998681116d7d572b119b4d1f885ad3003a2e00e1c20f016e463b80d3ad21abb5b5625a5355aa32aad7db757d6f5729a7deae5f1ae700ead1f59c8c36d5db1dc0bdbdbea216fca0d19bf6167ccd6d94d72da8325e7fbd8a4f1863a85b84c590062ec24966b5f681f6dcb6123bbe50160a63df007f7e4f31f41c8ae199dfa2153c71a8d634dadeb8c7f5fac65d259427de0f8d756b64abaa76aa4a5dd036a1592a5e2d4700639d2a5230377d8ba55b290fe01c0c45f57d6dcf8ecfd0471bb6a610b551e69fe5b4786934f75bb652958751297b355e9007d1f6642cbbce4fe801dc9cbeb8a58d2b1f04cffab9ad36fb8c73b6fd2dd7a2ecb5cf5b97b6c74ed06c7dfd1aab36cf15004e9ec67cebbc132e8df9e629ddab87d64df00b40ccab93d6afd9047a788a2f7a782e8179ec7412d3d1699c03b8b7ebaf13832ae6e9e7cde8c49d49df396346ebd23fe2579ef884f6df3acb0d51e0d31880d72bf2662ad2b595b69fff88fee342fe23a2190079cd831cfb2549bb23f393e8a4488aa4e8a26f27495288f42092249d244992244992244992c4c9f199a9cb4c9224e943923d64a9494d239dd42167a4e79038a4cb48922449922449922449922435192361a4499664261de8ac9782ecb553c445d97f4e1b2996f072cd45d77f81fe73ddc783fce7a219d4fcc7813c3bf6d1c5fcf432dd6a6151d4bae2a0f3ea70d1cb814edb858b5eaf97bf5e4ee3be5e2ff7e979bd5eafd7ebf57ac972665eea382cc6f3bd5eaf57cfeb4543b7c8836ebdf3d241b7d5fd6bf6ca7939ce4bf6728d6e0bf5afd7ebf57abd5eafd7ebf57abd5e31747f4e30747f4f26fa2a5f24ba3fddbf72d7bfbc76d64bc1e73fa7888b3ef739352efabcda5e2807409ebe2110a4f56ab356c7c32d6ab1b5d8626bb17f19049d043ff0033ff0fbec67cfcff3c901fe2e309f568faef730a9ba53ae9276faeaf9c4dddb122eba8e4bbab71b8446df2a7cabee5548020f540f3ae8a15fbbc4741a97667b9eaaf4005586ace71c6b9175515753ac47d663e7cf75d26d0a3b7fae979ed30bf07360dffc580527bebe66b1a184c7eddb8f6118e210dda367979db11376bedc9a178c2e827ecfcfc76b36ddd0ab79033f017aba9ca17af0a2366e0fba350dd0d3e50c9407d1254e9e9a68d22db2e29b674dabf9a138f14737554afe2ee14e1af5c8fad882f6df22f9354b95247fd1adafeeadbe58af5e170c81b426a8faec3f4cf4b6bb7dadfe7abd17ad36eee9817babb5d6ef73d2def2dbb9da8b737514f475a1223014185bae9ca1d8fc7dd94eb1903f300cc12fc3903f300ca33e10fcb21afb81e0976308c2e6efcb762a7f6018825f7efa2167305ac6e49c79743927f18484fc8161087e590b981cacad923f10fcb2182e536e905a22e74ce6ea03c12f73d1d223e74c43fec03004bf1c953f300cc12f47e50f0c43f0cb4168f8c1bafd40f0cb27a86073b64ba6c879899c57e821e76c06e37b31d8f5808688b77e1b418a2b34a001b3c3e38a4081bcf8e6308761ce91befda56bae1cbdba5895aa522ed14aa215fbdf1d7ef5af32cdb048d7ab93e1f9d5b1de3e7386e863eafce775dfdf33c07afb5e21787cebedcbe07f2478ec7914abcfc6797df55bb357276b48d3222a81c704705b2d8665d4deffdc667cc1effb2c0c58ad479fe31ce22d8390a4bd49f2ae3c2da8bf713f06f2050c9cc2ccfa47adf94a5951b5028be8cb9010222e2229a2158874ecb819290a43a464851344552c3e2822f2e4c663280a16405a90c04209d18e1622ecc06237a445f401113a2f5c3d845022081e0c584154f0a10513ed8344914ec3c614d9a071b89c408b3810e1a0c32945635644515a714249eca981826c9321493497c49001c68b3cdbc2e9c8806481b25c581b9423738891d8f3a5c52c0c0b7287a24c2be2b68308686b684665137980a8105f3ba0e26bcce9563c07e1201494673d00e92a353bbbf2d3c4070cc393412674b20ccd2c6bac9c1cc4038e48e341766b7c195f70ef05f7332fb8f55e6dda10dbc132b93373ec84b22a7dd96dce8ef3bd5d8b5ed4de7b2f112f116a24a9883d59c22024a0f87ae10381b0f98dd0d6efcd366808f26b42c3a277feac944ffa5665dc6a9dc80e38b06ffefa0df1b3571bf9bda0770622a37307e1df10ffe702ac538fac59c3fc8a8af588a719503fbf0ec8e987e2f020e7f0cd14babfe8ce135a3c33ce395f8b03db1efc7dbf5d5cc415875f79686cb8d7f2b2a665dc17ed5ec67b7b86042f5b9248abdb31da46f13194e0a1c3951f8e20a24c5a2b88820b550e3bd82401c142f1d66553aa8cfc96039f4f5da56fcf40073cf67beedb5f7c4fd4c6edb17f32a90f95ed6449eecd7acf11bd653b190f99d355cabd951129222223521455e4a368c8df5d0425dbdd64bb22d9aee8c6c3b91a4c52d0b1c044162042dafcbe59d00684221f38a10b018874dfba7edf96d270e95a808147096fd27d7be2f74debafefdbd64db7b9e936b7db8deaef6ddfa0feeefeea9bd45f27eae15c8e11ca08e11df18147922652277e1369fddd41260a0626929cc9926ea2267e137101e20305365c88981242ba6f5cbf6f4e7f7ddf9e6e43b41081d122e6ef26cad257445df4151196ee1155e91e114ff788acfe3a910ee7aa9a214852c0c1c81a18aa30f19bc8490a0612945c45e124dd444fcfe6371114971f393061e20b5710e926da1149fdf54d3445fe26a2d244da68226efeee21bd1bd2bb213d35a4a786f4d4d0d00ee7f609bcbc312187373a6459e2f7d00b3a2755218871626647ba87b67e0f518006303a6489a2030d27a47b68cdef21abbfbe87ae62bf87b0d20c1149331405e3f710d4d6908fd8d690bfbea24beb86747ac8e9afafe0e15c03c20061c6092423bc014289df2bac9ae84a82ca981c88689921dd2bae92f8bd220b4b6bab0d4f47e449ba57a8f9bd62af58c1a56545142d2ba6ac68b282eaef5e51155b115b115b21f5d76d3d9cc340cc112c167ab0c10d9a1489df362d105c684184351fc80025ddb6237edb7a1f6831028a1e5a745049f70add0aa7bfbe573cc17eaf18127b6203137b621363eb62c3fabb6d595536dedf6db3faeb361dcee588caa18525588c14699225cd6f1b9a188ee872e689d319a874dbb47edbb0b4c023ca09549e5449b70dcd6f9bd414d55fdfb62adb94980e156d623adcfcdd2aba624e2ad43871fd85c2453128adbfae628773357448783494800505efcc6f1552434dfc80626a0a4c1050d2ad62cacc6f1555171082e68a8b10639a48926e15bc32bf555805f15bc595fead020b96460511581a15512a7ca818f277ab80826da16e605b2a78fc75948773475a469280b0638a2921647ea35a47a889828d151d2a40916ef4ca88df6856932294e8a1890f51a8a41bed15f11bd5bafa8d6ea51be5824541a3c0a2a0538e7ea355301eda8447054361280c95faeb670fe76a9b1d08563d429042e607dbfb7d563160a80851040952b89192eed312f1fbb422021b2082d8d1852724dde810bf51a7bfbed12774084c84130c4c04317ff7097b3a614f276c77c276276c779e3b40f1c16e2a4a93273ec6fc3e3da0c5083233f08010c142ba4f31bfcf18d63cb952c1071a76a090ee73f7617e9f527f7d9f53e6ef930a4c1b306efe6eefeaa9e9716d79d6566f4bebaffb0ee754b8e131f1810213bb35a910bf7d04a7204ea08a820c9523e97630bfbd4a032b5ba8a0b0830c4d9049b767fd76abbfbefdcab198381126517fdda1cc29f7313584d7c5731dcfe9af0bf1702ec71122d0a4e08305166ab0f2e5b7908c1dce7039c1c525cc07e9160ae2b75056069c78a932844d1125b449b71010bf85b690902982902982909010d5df2d54650a4199423b5348eaaf07f5700e035d636e6842c9921f74c0fa1d54a138320213676ec04ac2857407fdf03b288745489a10aa70c16221dd423efc1672faeb5be849680898203060c4fcdd4159bda02ebd20acada0aaad20de0eb2faeb413a9c8bc0162740b0a10ae10b0ea997df414e2190828411509cb070440de90ebafa1d046505c5ca4aea863160d21dd4c3ef20a9bfbe83a682a84a266d4a266efe6ea0ae2935535c3ca02c5e8fa7f5d781763817aa41c24409251f9c8025e5e13790140792f0c035029b2e1ba64837d00ebf81a480a1ca13504988d8ca4a375097df401b0808ab0d109136517f37105409e4a3041a52d6ba4a205d09e4f4d76b3c9c7b00970f7690f24215ac255c7ed774a60c3145550a2a84a14a776dcbef5a16053c1c4cb08107066b8a74d7b4fcae69fdf55ddbaa7191b528646d4aad09d5df5dabead5a07ab55d4f4aeba7abe6f4d777ede96f6d08c9e4070cc944ccdf1f72ea879cfa21793f24ef87e4fdfce8702ec79414ab16744873830b4c58fdfe410ae2861b868042450712e9fec9f2fba7eaf0640bcb0a63acae9449f78f0ebf7fa4fefafe99027fff50b569d3c6cddfedd345ea7cd4903a1f2ef2c9278b7cf2e99101a022a814091a0f7349a714325323c00040002317003028180c8a06a328c9b144f90114000e63b65260521e0824f220857110648c210a004000008000008001a588ca00984ddc1a451fbaf9cfb961c8ff7b3289e65cadbdd42222f66c6dd6ab7c1bdf1cf354aef12ef285df769b694d6dc1a333d0b5a3f8c7961c74a0d18c4e3dd24813a7e9f27ff25e9a1dd91c9326343a600a343ef5146b088a2bb11db8e2bd1fc26e9e0acd637d1d476173dfddbcad8b587b7267872f7993e8e7dacf52614b1bdeaafcd3e9be3aa9665dfd2e71d8f38ead7514b0b51baa7d7eab015f23ca901cc98c0413bace4278363584745700bc2f7ca205439e90da644d1af0b2c967765d8da3d3df290edd2f9cf2f48b3bdca118d5d6219b6ae8b4c33159f190f0ad12d23661796dbf0d3d46d64c6096ca0565c41bc82e53925b09e0195e730346a1c0642ac0762c782667a657bd58983e3a8369db82fc14df4c805591c5fbe8a63c420b9f468bce76a6f6e011d37547d5725005fadc4e0fd345637e8e0cb18c4ae6dd1061371328c52b1243f80e949c444a63ba5761a8afe2977ec36594747496e8fef731c433c3eeb1d0d4fcc9fdf6b11859b24e1a25930c61d914d4faba54e98d5ed4fa360a3d63e77d8aa7829e273a89c921b8aed9487bb9631e24bb547289d42e9521f2525d787d157d6f87f49345f26d501e97472c6e0d1eab884ca5d0a58fd4dbf8f3918233d097deff66b59aafda223d13c89e77d3a2ef63da73678d9de076508298328aa1c0f6c8bbaeae0dcca05c424cec1a375b4140a5c3e1a600280a39219be2449bdc19889004b31d38a7abb8d11953520f59b045ae700a7da6a498aed89686f7e93aead269de6e7c31b9abd054b762f40cb42cda90661a5b4a99290828a69def47260d649b6b29dd69b987a21275aada3f4c3616e554ad56832442805a962a68854d48274f4a9379ea14136e4c3e8b34d6377e003f991b3b73e4954f2920b0a5cbaf78b2852ccc7d6f7bc11981e052475c585592380b8d49ed07aad0b6b4f3aee88367545be5f15a2636cf5a225089fc7fc859ff7d41bfda5379385a2293bc6a1e9bc464b9b2a059484323829b897417ddd929969b47ad0a1ddf0339df0f15388d7c1e88a801cce4e69b09988e7b99428bbb2bc51bb64ddf4f022d57f4feb932ef720745e33c403818245b4f786ec3445ff9b020a71299336c04ddadb0b968f1081b6981a7b05a70276f8a3e2475ec520972156ecb28d02b08c266e1ea436434c9b06eb243fd122bf46219028429b3c013b9b6746ab970370f957df6fd2f64f4edc25d7a53f220339b5337a1c145c448f46f9d2f638b9a1288abfe40a3ca171740f87c1da80fc6349a5cb97f64094a383086c0dcae24701e8b0e17ccc5c07accae4233dad06aa3f31c706e046b7e50e7e9012e270c941886884026fa8dd0cb94b8dfd96a9bb854b39853bb78b30b30d209e810e5f256e703a6a041dee10f28cc625cc31ed8dc3520c2f8593c4632196fd7a2a8d9a0739414c4692c11b771f94e059c92aab39bd05bd24ec8189dccbd68b43e246ae26b2169b03bf141a60d200e280b6e6826703a008e9eddaebb7c5c3e5aa63cd390c8884e9e5f203755dec2f9bcaf695e54d5990806af7b6795acc3450fbb5534e322068ed64018e2f83ed9ef25705b193f2eb28fc91190b3c4685e17053dbe12eaaad9e4b683e9e35b32dfa544f1e87e301a1c4a6a5519be059303174506af9813a142da9596c9f9be4025a77ffb80d9de20e5db07286065345dd331684fa9dc4c352704695315322e616356d685f5aa05b043aefcd8882a949190870984a2a0ac1711d1f32bbe380a5169c09fdb553805cb7839203d0e961b5c3b6712d736b0178dc0c8fdc3604458418ef59a1495ec17edbdb0fd93e482a505b98e645884c80cfccaa2ebcc151a4a59593d76476620572e0288eda5954c755846875d40ac7c68114296488107a4231b80358a59fa70b383f0d36f684a5119885bea4c36415da8f7e8e4b551d995b449472234e3e14f58c2e75bbb060e558b7014abdce08fc5bc86dd8d83d18df260375ab882fcb42866eaec9ad651cf204ef9790048beef049781281b24a0b6aa0af03f183259e16a4ae993d60d471ad0220cf65220df03161ea5d63150ac17e5828c4130603d662d2b7bca17cfeb7cb85913df739cc507e3edafa0525d79d0c432ee0611f908d7bbc0a4774701c159ccad6177486e090f8e1c26c2432b74dd77c9109bf448dc2b1a4d2594d3f90b5260b1f31bda13e67ff72b008d1c7b5813333008eabc0ab8e65b7943d79827b130f38fa6b3560131e08352224285d60ad88cf75986348845da5b733b53b4f6b2a722259df684eb8a197029e92f568a2328bff318b4f6bac434abf55c42b357f0955d4e879607e48cb80849dc3cee84f722fe6daedbfd4cc20cbaa08904212285acc9f76b2f9955511db465e06dd0a82d9052fdd53da7c962ca46052153222a0d7e8f7a82fd0cba40458bd8105b97c4fd3bd2728d0b0797559fcbe18279dab5b9a541ae2423f23e971d0fe1d60ef654a3b512932aa0ea8e369d8240358f3d177e410c37ec8b918b4a7e97a243b382ba64e96924e571117f7458b358242b6795f0afb0fa388445fe239e16cf02d1be3e6df042448478e1bc457769af7e9705c560038c239c0132892f0e270e30408e3f8ff30388a864794d9f1d8511e9940a802a88ad15716c87d127116145711ef66089106924c820dc365d58b73e8832248a65ade3ea71a224bf922885053a7b8371eee4c3988b55d516e545bc6e16468f800f27808423fd5291b48db1ea191a76afe23448a6af1277c06994c93b41354182680169c2843ab568f9ce0718fe09d05f3aa1f9b51aeb41cfa0c61be9935ed6841a1da7d44b15439934f91c5c412857b17b4fa0c89dd026c15ae385ea91c9a906505e3acc355dda2e9ea456de8a793d5f3299d80a9230f0ce2c4874d849c8041603445b9e26aed63592e7b51d4bdd4c88c9c39a7cdf40ac46fd4b48daba5523e440b8a4a44147ebe6666fb573dbf1b2359bcc94fbb656bdd1871efcf0d343a5a1cb3f4c545905919fb0e9da13a75a97a7cbe996b30fe5562190c095150871da3088bdfb3b235daf2378c3f1a6df731d7fc8875257a1b79fda8859abab929b323932b3e8059187c810170466ec1a012018f5c16fff5837e1e203cb1a110f85a23d09b23acc203f0818b7cb9b082fa1bd61446f68b5e03d58a54e2bd96a32437b3e817b459c661966891ada83fc2aac6da067c268ad1b58a88c1fa290553f059d3c30638956901de9d54485f5269b10befd432eec6d1a1ab0cc8a15b2453f2562c781526acde7801927b34893f06373533b7d45e6b48c4ba67d09900efee3e23162e238ac260bae8ef40a22b255264c0c3f21ede8b771d884692692e93f0ef41656946de9c9c48df1125a902fedfe302622fdfd23eed8633a1ab3c00a2b65b20fc67d2b913327b3a62b8912564d3781ccfc83713331dd27e782955322330f1ffb9859f8806e76ddde7021ad993051d9d6bd4eec18a7b4644e566a86d4d83864c2c426920dcd9c8895879b326902b2a14d5362e25053a64c2083ffb9b8d56f80b492721ce0ca09e3b13a3efedb415726ac3181ccff810dd5bb28d7e79890af32c6245a2eb3e2976034bd4e6f084d2f5aa0fd257493ff6cedd36c84b4087cd42bdbc44ba029a51dca82990a4767203b454a90ebefba85dbba68e356900edff736e47094354ea2855b898c9edc0ecc225bbb48fd7ee7896122cca36eb0bf3d850d82dae5665cfa9314c8b435b858f38556080139d75cf5da329d6345f4f12ae567c09dfa7213c130a08a4e90d4aa3b149384fed197e3e9477129567cc28f4a3847701d85cefb443f7718209ef39de54064c203cc2e07a9e31d01a934a57080af926fee6e5d37fe1ea521f6c674e5c8b44ed060246968eec115a0bb88e0098dd38be8e0d2a7832ba10eae4b325cf893e13232ffb06dae919aa1bae8d0d1f0b221c40a6ee862a2cb5e4d0714bcfb4a90fa3f3be27e64e92fdd64562c1caf3a87e0e96db1888000949f57014c69543bc7f05861d99cfe52db502d7b1af734c23b289c6182b2422d007b8195c932001c6c3fef3b1b3864f2067bf37cf8579a0c28466851c129e684437b6a7ef03cf06843c5d3cd0fe73ce5b215f54e373ffcb424e03654e12654f6e8fc1f49d92fa264323626bcfaae27342a64a33d4f09e32ed5a62cc85f0aa4c846381b297093222f4d1220fb4f64465e0ca7dcac093f3eaed7c36a7d09d4cc832dc5c15084cae1b96644ed951f18fb6d95930056e34cf3a2ca5b6ed0f6809c9da8b29517da5f008e020bb6ccd9799773b5999cd49eb715def6ec2eac642b4adc5eb185e9adf196977edbbd1331e867a8ce6dcc46cbd39d08edf85e80a3a967fb6a701457642b3b6f5be1855987f1396e9991d913a99868f6a55c23cb36fa9191101754a98a8e72a9f9e4a4cab429fd3a2771ec26d668590e339a39c94d7c687f245174db142e813901572587117075de0f83632315b37d0bf7dd455e649e9be53cf49c7cbecd459dc72821124a79601cc008aab5fec97a88375dfb7681a5aa4fb89d8bc40096e803c76a7ed9b7662c06a3f1327fd75d9dbf0a4f79729b0362ceca7466814a4fe0a9484d16b1b6acfa8cb5ace96f5aaa7f7cca31322ead83cfec42ce1205249bb845c14f9e1b7208f9c389a323aa820a8fe11c95a6a7d5bb5d4e35a7b190a4893578512ea3bed8484ba220a610d20c59aa5dd4c2b01dc5933a08e4bd6993e19922d8c509293b355736992042b58528d3280039fc0c3069eca6881234248338b9553a350df75c7f3956501c5cc0dca0c3f662ca6d9e0cce59f58b491c1aa71572702580c50698b7895da5a3e135e2d7a423024aaf7ca9fa1490b24cddc0e507b86167f263e5cf811e1b086eee70a1da8813507b5a0842b5e38a8fce326bd784108ed6a15f8eb5454850ee1d5a255d5cefb00b5e7a72234919859216dcbf7764b37cf55a32db7f6a6231a9b4c02b7d9478d263aa4ddbec17be80ae16f8bebf5f897e9658d4d7debff96542c658de7725754c5823eb134829f9c0da5cbb9d2c2f7d5f80e06ea244ccb5a3bb789963589fa5bc947dea01e293b509473be1dfe3cd60277f28ed50a59ba77c0c297a28d9e84629d64c80e9b7cd8dac03b02ac31f44477532884c09b00be7de1b71ac0d2c33c2461bcfa848d4983c833663d383393c590b80e9c8d9a95853ab5783c55c9c8c4afafba0347a7d192498e7b38829ddf3b8af967fee0023afbed51f7a5c73008cb8185dd3351a53b62bbd62fc74b02948005829938a905225c98302e795aefd0c2160bd7d113181824a3b8bb4ca79a17108c11faf4be4bcc4963b6b280f107787126fe5ce523a487019daceda0ac19d0a6a982247e0668f07955033fd80f668381a07443e6d854dc63156722aa9d71c9a90824dad985af379e78812b7e0451f161351403e74ad4bf00067c7cb433478e438e06779f3d7a1e4a8d69c1c63c0590bf4ca3e18b9841e8ae086ba00822f9478139895d6ed92e9e2a905b7d2daad989f46def36eeb5a37e2eec5899755eed980d75a2fbf5a1ec6e20b64fffe8681d999cda81e790dd0650bbaebe074bb9e78dd9ed1d4f7ed8e078e4ea2cf3f5cb501ee9b49cce98601063ceab24b4883d670fd72f301ba80ed73332f0edfd2390ff702226192f95a702cd767276ff17a11b87a79bfc81bfca5d02bfbb893eac560d4150bd82989659a32737d06256323d8e61020adc76a3a741101b506a1d501416d1a4abc7b9108bafc12b004f93da056573d9993f7a2822cefcd1034608d60f3be8d22a0a6311121eea938d6e8eda267294070b23e8f64994aafb95edd4708936005ebf2ba4282e2060176d1be11558a6664ac90e4eb923ebd25aee87d0cc9f3a83ff73d935c7e72bb8e794e6025fd5bb33fbcb32b8934164120c8fcb28307face6c2afbdf80ac6a06f640a816ca84a31380a3b138f96debb920689701f19327ae48be359c9b27963eb0f45687fa95f7d888beb4836607bf022c9a71b9d649079a271dd7226be9762643c9730b0cbd1b6d041baa65a447f231f84432ae121000326a32e488a01ce3d051f2d4e8b23e53eb0b4d5cb81712760de61828c35058b8c9ae89eaa85f8293eed0b70104e3e9f0997e4e879232bd392902816b792bf96264f442cf5825da4a24d6cce9a453c8e5188fbf0d508c5d468874c374f7050d8e58726810c81f1ed0062d303d0bc289b5ae7986409d6c586e9331021e874e0afb0e915ab0c8403b0c9e022dd6e974b7b61a60933e2b0c859df4bd48e0839288408cd1e1276550810871c30bd053b50153ff758d83967fd7c1779b9bf0b829c30c98d5c71df838891845b1f4256bc45c2268a33e40408df62df428d7800e4dd0bb0ebe1bdb84c7471986c0549cc2c0e7580fa422decd6a2ff549fb350766b16e063da049a96209dec7134ac46add0c501a430358fbcc7ba82496ad61553a5c3639bc3560d734fb13f0eba8a6f1159dad44f5ecbc6e0501422e8c18c223d80514bdf5e29b1734313cbff05a0820bf9bd124602d479094885b834e18772341028abb2d6616a820739c1305d59fc53323b73b7b3887b2da9def9060bde6570e7f40564ff7e598516f3cd75557c0736d25924e958430a0519a643a183d598fa7c50ae46fa34989ea6fc4fd0b0e5e022ea0b7a585b93f871fe91f0181aa7b8dfdf48a7e78dad35508581b5f75b7bf79898aef7b8724b53680afdbcfa692b72daeb800667fdf778c3232151a33c7dceba9e904e33642b15026e027593b26de1663fd3134c53efd07a705a2a277b6d65845bc8df1b1b0c754631ecc3dc1f622c1f19608e17727da8ed45c7ad38259694776dd713bef6ae7fb8296455859cbd49fe76b64e62ecb46dc35b2015882b80bf432c04c5e97ec9183d0e94053ec7ec53a24c887bbf63b4a30f3ac740cca4f0c4edccf83663764d3dde5118177fdccb8fa922b500b99e0dde9e98efb900cf727b1b715094475dd1506f070f3ccbd5330c7839d0bc2b1355c6351ed1a4b80b8b4fd662f1abe4677a0d39a82504afd79087554074b98613db55a5161c4825d19e5a173b9486a4fe9722201f8ac91b2deb558a83d1560740d10cb71fe45cbb125f1536f657daab26c995ac93ecd5623d0c49c6c29417475c8b7d7da8a49de1ef82e5c50aa85e92e9fa25bd3b59490ec1f182f90220e6c9d3dda03e676f26ef384c027078964f11b3acb9edf188734ab8cdc889bf71abc4041a7fc64e733951f06b49b551777b7f481df974784aced2540054d8bc14ddd38b8a593657a1390701e14b6178848346e0d2431e3b9f0b83b51e02d49bb82aa8d0907e71362838655a8d1c0b798a4fd0858ed8623f4cc7847f758be0bd1a894b489a54176f34af19786657be77fde0a0f66e970f0ab6c323ed33b9a2bd58d8768f535aa686c97b5bd12db1acc955066e47cdbb8b0ee0ffc4b78cf4e1104824ce3ededa5e168f6f0275c41120fe2aa41c4d56795225e82e18437c9f2464bafda3a7acf8825ee5eaf8346b494ff932c0dbfa1873afcaaf0be3f2d304ae5ac84ac7fda4baf5d3cdd94c9fcec4ea83d1ad99600b89ba928719117223081699196c1f058f0e2f458a9bdc172edcc8d6d833ebc5cf929a0ed3937dab80888a2e19ad5d2c6dfac4d842a8fb5515d4e7e278ea572729b1d75d2b8cbe65bd5d20ca72c5e70116d4a919b0c42458a8d1c5c0db95c7147343cd1ce75ae1cf0ddda17484995281ef575a06558fcbcd8f8955b78cedf8f3ad053482da3ca8a8c4268c6388b257d160cad4bb8634a3be743f3403e6b797014b9327ccc02d405f9a2472dc05830a1471a264ea008c968296e2da4bc80fc303462614614dcfcbcd71ac0a1af1cfc79bc233a4ae3c15691501abdc18440c409000d811e89caaf57c4734b1cda101535ae6a08390cc2aa778a44403e75b276fba8b20949531fa481ff8a7176f0e4a8b527acebd2ebc2af8a935aeab85e3b27cbb2643d4aa8d5cf052add460e5d0d50248abd0a309f0bdf883f843ef7e7219a968e64f9d6f7b8cf3334acd044c63a5316e1d0ea89e1ea13c6c0faaefe7c03c4853750c8bf591e6448a6a3dd0734e7ea2652b0b321d516cc98fe5a042bf754a5d6ed54e28ef1de1e6588cc280b1760dfa4b92d322d025f4c6c0f7a1800b8c9a31a8dbf9dc732d59a5caedf273963ca7f5b83ed9bbc38fff45702159e04ad5a41d9980b3d6cfa40f4e6e73096b17843d47887dfdfa867cc0899b16f57b5dc173d407072095ba490be9601d19560e025affd6c3fd7c72b888ae28a0ec089ea787cf622e4038bfc30c9e3b14430b77d3f9e2e9955c9fc53bec34e6b2a7fd9b6ec69a7437cbd112e3a8c52e5115aa5dfe206b53b0407521bc8c62b86fca04e4960f144d7f2f24972c019d6a001e8f5f0884b4f3ea124aab54cbdc12d6d0e7fbe953293a6c1359f3d2593f794c3a2898ed250cacea02ec41ecf08312b999794fafba93070a0883a7b1524cc59fba3bb85a02942bd2e0d4160f1686b98815f8b96ffccd2b406f581661b70e921a0a2ac80c15ff39c9f133aa0204cec49142024cb765397103bf1f92989af2d67116e133ea8a8e86bd323b5c256584661fe1f366d40e3aa20be536254ce39ae4e65f812459779a91ea84a1dc9f0d58533d205820462ad75f484d43151de8b765bb8fb060fd183739f834c8aa4387e483075a3c89f4d8fce0e7a163d0f7bcc9b276611b57122ade7f5a03916a861be49a04068093a094e11deeb2fa00b21795218eb897cf0e59b555f738c92299d7146995ea78b274a69353f632d5c31c39e4cdbdf79a91761bdf08305938617d5efc49f755aa0e6b5c6bf3223c9f9058b33fdf2a88479e96428419e484bfd46a8a741f31cfc7d4d521c11a9a1c680b1bff22b306173095c6d3224274dd22db0e4fd467f9990fff220b758e21b4f406f9acb6eac1c993250de2d35cda3156d71ebb25ef04476e3e0cbea9882590744c3be8cca0ba5146fbe7f3dcef9d7a74ecb14884158704ed2aeff71871939e9874a5c6606af1f80cf558c1857dc4855de7460d4511126c922215c3e742340b31d1b5c3b75c848a71d71e6f411274e3b1e0bbc306801ccafa0c24e30aeeff05bbf99de67a47cb6f9dd2a471819902dbbe4e13f2b700ec818e7d5cfdfb759c0909b0ffb1105e3475cc00d382ed67a2a9321e5eda935e9960ccbc2da161f65fcc009e8efd76dda3fb759ce9ca624b9a51bc1d25470eaec57cd68bdc9e40406221871d87eefc7e074c5b064de1f97a7f874603671b74156e2e9e2b8416234bfabb8b9b1498df26dda1995549ecaa2d6b47e406daf69f5c8824bd11d2bb6581f3f7012b74ee68ea0624c3ef044597e1bbc7bfd047c087376b1bb49a575e6b4ed52531f1ef9d8671e77c8c6f500a0365ddf2ec8f8e58d0177d8d8a52832298678e6f27781d35a3c4fd4c87eb124863e8f644a01a630ffcd5c45a137d57a14fe3a1e2f87d2f10cec055a5753c351b3355520821ae8e90144e65b3d4410b5046da3d54800aed416770de131bea0df5d0e6fa1abac86944d5bcef9a9061f5dc82757e998fdefb399ec5584834dfc4ba4f47db06991bd1c7ea8452443fbd12c9c65979f66093a5ccbe708e1a5498a1722619a93cca0004c355b70edb15d1f5b3a601aa6b8d647cdb147a034da108e24448ff708a5d34b979327258edeeadead7b2edc619199863ed14db7b5b4a7a54adb74f45a1988762a2e1098d81277134d7bae71e526b5473ed7a8729ddb5d0ea3d3a20a57b13c4825aa64ca96e0909c2384d7b0f30fb3de700afb1887675accb6e401711fba90632520fb680e5f28c518b2784dda01017dc01475a245e3560d324047758ea20dadcbe1fa590b641d3051697069985b00ac1cf8a5c1ff7ac920093ab8606e52264cbf63d7e8c034e32699455cf8d1a2ba5a7f80f77c3795940d8de31759c66af7c333103b7fce6182eabd3fe7f051c0a515c513bc87a5ab8b292a6d7b350eabaae8b4584e6d1dc67d85f9bc0c6e912f69209ecf93291272b659e2069d0354c36d4b744bf9d744e05aa98d94d40ef903cf4a33e4751b66cc238a9fba76e1bf2b136819a3be28e6fc16d8293d73b75ddc691a393f7793154d550d2ef70c8c99cf9de43c1fb58b1770835e819d8c590f9c34995e818d4734319dee4a18cbc9a4372002663e275d4283733c6ad80f69361f768fbd9cd1805e454074d487ebd9e398116f14f22810f94f826c01b903e9b463ae9d050e2a6cc801c4ef9e3d7afa8d174f172e44914d8208699d85f0fd77efdf3d7b849e922f52f24d5238bfa59e9be8e411b3cd0ed770f34fa8ee9d7e318632d1761fc03287710e6f86415989ed8e6857b82f81597b2917434d1a72f0423ce20afd758085b152d02e07aba0998e7c9a75993e3701319e70ba52ee49d07e65bb3491c9eb96cd1247779441ad1284a30be8b7a162188d97628898a318ad09485f419f44a2119d7aa6bb5c63b4885cfc65dc284474847c8954964f07f8db960545d6ed93fe8aa96dec8cdbb208c8963dce210dffa58f45a63923feef227c4f48659b27a2452ca04573f369efc7cfaa39e90f7bbc926617be5839d9aec0b85b0765f527ffa39deb652860e5a4a53ab64d6cee1dfd345a0cf0f7f7a54a120d6e3278d78ee1145c456ec76d58f454525baea32bcf14952008939d338160179510ef2ed0324525a2470f499733857cbb5132c3bede02842e5b5347186e8f5b13c3aee74af83818643eb4c23e7f10ee10696b90a07df12444af27d75765c52dfe9211869af2c1dbf0ad9bd768a31c9c3fcdbcc3c39d54a0b24408969c8ba15bc31398af27180bfdd0e47009e8a1a553623bf67d2f281a428e1ee9a70fbb98cbf234b4a0814be188d57bdf432380d3a6a6b1a800cda084184ef999d3843bd8fe96f4ccd7042670597696c382cdffff90137bd36d7bf6cef744ac335c5dd75ccc5250a954a1b600200a758f655bcfca7ca6a2448c340b242dddfbcc5b02ac6c7b62280067d49712319607c255c78b4466610e790eea16ebaf9765fa21c5a198e64260112c05ba9382fcb9573bf730504a0b59cc16f914fb8c640d9009bc94dd1128b260184970ad6d20af4476c883a557250abef836ea1a5820ff10456a84b29c24ca66aecb31dabc89b2d98c4dd50cc571101795fbe9677c2eadbe349b1fd22ad5ba6e6490e96934886376c79567291ccc13c10020c25811ea15691655a0417566a5c163dc6c60336e6cb08ca53e903ee50bf82edd7d5508169b2cc2fb2d9e9e79504455bce4ffa821b3bfa8c1f31faba1f9dfc81875f7b2f2f97fee01ef3f56f6e85c3cb428310494dea4454f03a1f182da2387a597878ae59f070b0b640f124be61e2d7b6459dac9288f438ab90b7f22cd750f74209e10d773bc2495909d36a13d4d4b6a3d5f96f83d9096a87b722d757b542efdf670a1aacfbc40b7f724b207ed9b0edaece34e7d1c4d651552af72881d9803e3f30b266654b461d6259f36125db2a50da697d4b561f8259f3652a5b6db06d3efbd6d24452f97b4ff27e13243ef683da48c43c1cc9b64b5a176d97da9fb86f02816b329924411489c501812d3a8ad606d0146113ecb42bc8b919cbaddae0c66948909a629a246d0683b65dda31d4605f79413619c505c757c809ac4c98c7780d25b9b032f0e313789efa0a4d4e017b329524018d15b9be4dedc4edbd35e8e6568bd34cb99bd4dcbcabd3f9699bd302e137b732c37f56a2c7ff6722c43eea558ceecede84e60ef233ae2c732a8f6c9f92658b9894c8f9099a9b60eeeb0c336dc80f19d7d5f01ff46d5f07393d53373b7ab2b717fa933e5865227e6ce566fcaad56ffc4cdaa0e999ba49e39775b5d99fb4b9d393794c7666ece708a19db4b663659197d7765ccb0571031c520420edda58622d7dec7e859ee4324e8dd874dd0741f3a41cb3edc04cdfb3009baefc32668dd8749d07a1f2641eb3ecc04adfb30095aedc32468dd8771da6509320ea2b50a79f4e6ce337e5499f81713949d4e2aa5969b2ed1e457350cb309e37bf76a8b26acd7e08d4250436914111a5a5020d44082e252c352148e1a84a2286888124553031214c41aaea258d4e08242b0861214911a5aa240d440f2e4510d5ae2ee01b89ff6b6d23e1967470a5b710389bd8a5da7bd22d105c84b6852c3703faab5d09075c08e60a65707108e6cf751226990044e47cae72df795af1f7e3ea1c14bb32978fb2d28a18b0c6a47d24407e86db92a3f3655e1bd88594e436c355481d09f19fba94f2da6039ececa6a2ba1db369304359350a72516ec7cecded51b1f4064ba791aa75bffc0da11fee13708c8b8fe9057483cee7b22cba86e228915ebb46bd2143a590672c82bf340cb89463f793628d9af50fcb3ccf3a6f0b8bada356f807fbe0afeba3c173428774d5b014a5a52d0ba091adaf7b38b308133229f6ab544fb40af14949c3dba11da660d92e10782c84df32ecab758743b1629114645abbf4998a1d0126ffcc3a1d0a04dc094fc10e31d7d7de0fc53c855d810ac644a513cfedad39bb61fa8218009f4549a1f1920122cb3b891ded681030a6e5060c54291c8a20af0068ff9f402097710e4155177efdb30b243496212cd5878d58650fef871238488f6cbdd69070db7f4f8a6dfe1baeea6ecd48060088d15101c0f9d5c2b175389f6f2d540243d884f0d4c47e0373273a72034ec36ae693fb5837a6a3bc959b85c6f026966187a10da300f610d99c0c3e17fb340959320be4a683ed3b530f8496ba55a0a18c527dbd8f347bb0e23647156a450f3431d6b8d4bde9ad9c55d0004012cc62418eb10e39509e6f77f6afa0c86d96fa2c3c27523d82fc5c040b2cf6a34bc06d9c26513a23dd1771a42dacab4a0c038d99f61c00b575eaa0ec8dbe42a773942d82a923816423a58af41b65d0d9528b05dd75c7540561e1611825c33ea0179594483e07d108218e109a7d31460277728bfb887a89155ea5ab6e5ba8f8a83c5a70a6d376ef4bb60dbe55bd971c4df31b61d48189dfdcb5870631e3257e67b5248517ace70b1e4b42253518224f0a8558b7719c4c91b9f837b10637c4cae21c6f8807fee5fb803fa7bfffed9aff4fb7dfddd5fe8f75efcbfdff41f066e8a505cb01b44e6242e3fd2aa67ce67fb8262fc0fd4ca1be1a153cc366ad50a9a5bf58296ab5ed0ecaaf7bfbdeaf5632ad5c303e73572c07558b9880016a73859ad0daa24dc943d1d3b0730078a86e85d311e69d769b1a321ed96873ac9775ba1240f040c27744670d94e8209d3049053f4c9a2850a4137916854b56a84aca506bca0d115e44185048d4dfb3b3d445ff8ba469a36cdbea834cb51a259105e39b72c1132aa7dcc519557243af3b5427e3df49070421cc0fb86d0a65a0059a4a4bfb360f8f49aea084dd5ca39254d8d255aa69a030bf3321dc609fb0966247802d67e10b5461567ff8980bfc97c831d6aad5181183f71c0ed3381b7e2d0b461b383b07dba7e8d2bc92670f843ef8d20f3bd74163e3aebd215d8c890036988238459bfc3263b6c3b889aac2a461f0b643f3cdbb402773be3f5a879120d2393a1c06c8b5a8ff8884d14206f8bb414c7f7bb5b3cf29f8472ec94409bb3e3083f64efc0f9e6d220114a6a02770391b31b86f320afa6d3d819bc6660a35281fc9d170d061a10796ad6000a8c8963684011c2a4395464856a656eef0193c3b662709eeed769bd3e60741b0ec67a837b4feb9f8017f097510e63f42b76fb0a96e306affe1b5299b0e38f9683ff51bd6f4144f551c2889b1838dc22db9a3b1a742e75ee1500231fd565b5adb1efc7afc6600cabdf84713377be876af28f2ccdf3cc6ef971b3ad034709c6ff8b30f1ff76bfc6b81541088d6f53d1f24d25a98f339704038d51be8c4e0ec2745057192a9c444530b38ba8d57ce150d15774a8c0654cf19debe0aaa829a6a8427bbea12b4f1a25b2c63a94b5fe5c9429ec51a617e22b3c2305c05f5104ad034746582c42dccdd1ac5d6ce523a2b383d4e98a83ec19aecd590f1c6097064cf7f0a8bf08d1f78e55b0fa5250c99ea43407d706431cb42248e4122686fc1f674860fd0765139f1526f451b415653c9dc1d2cd5f9fabb4bf2a0d44aea6bf4efd13f89726d653f60e3bfde2c92a1b871196f7f997e85a93fe8ec948d494be0597f2c6a873f780c8ef3c8238380acb747b462003e6463410a1fca9ca27503af986504a7365ea451c3588a5f7fa23c4199a0b1e6661587d821320eb323998176026156a3a0dd04495acf90425724b9c67f441b9413a1b02d145921929d5054711fe604cca4fa26084ead2912aecc4c16a261fabad8747859d19451036ae8d8be5fa1f479d40051d1144f6be80826c7846648a34df0af6f61674cb0d56adce67d8eff71f9ffa18e951bbdf9dfe66d671a0aec902c08e8fe2297c8edf6b823ef1010f7f71a44f2318612539b58ac033df658c0fc3b2747cac106a417305a37baff069cafe733c1a19067707713a6d543abadbed259b5f74247702e053801dca07add35e77ed4f4491c5624ad0abdc05c4d8641e84ef293327c875ab65653d8c81443a571f8e5f74e5a2f240996c441eb6567d64349392e7bfd5e513453d76eee12ea57dda92c2bb35c99f8f44a18ffe04e5aa8be4fa10d8558738bfb1ad28bc5c357cedb7e067861f186dbbb3a245960965927901f5c23f9004a2e27005ecb7c3b9352d4cb0c0b4d2d630d8e32371a64377dbd2af7900d45ecc30950e2643c76ccdb546daec8e6223e2bc9bf8b5425b0a2d24ad75e055472234cb101a81e7180dead1278b1e09abf3c7355aa4d5c0d16e97e81a711646c6975c6c3efbe6e8687a5a20304037f3e9cebda2d2978bd3028f7bcaa1d09e3889a49472e7655bd50428c67731fd43fc2443e0fe4063a48b880b9b75015dc58455fbeb2f81ba2923730cea811425dbd4dff6cb20e76c33e8e4f528506721fffde6ba9dad16a32d3949b4ef1930f8bedc94df49b7c002e4285cc5c8124656ef9021ebe6b9018aa1a7481daef874c19e7d710e52d1640a4809b3490562c75feba6ead42d17c6d2e1fef5aaff8b7d012ca1aa424838d4280e5052d2ff93cc1a20b2394cc889305e324ce2b5c643a06051cc679cbfd0811e503197350f38ded07eadd56cbcc0f9d222a21f14be5741b8ec479bd388ccc517c4d42908d3c398cf37b2ac243a6c78ade57ea7730c51b30273e2e5e637363ec74567635c6056609a3c6b59eecbede479deecd0b2a7a4655f73e3569f2ed324eba9e1ff1520a2b4905af6c0af514daab6ac69232e5637be5091385079cdc2248c063ca1b985149239f1433b06081381f11b81b3a968d90775d6f3e55442a4b8f3f3184fd181e4e6762f0afde111dd22080814703b1e8886fffda8de78dbd5eeba706bb1ed45803c233e05121f04a613dcac7ba7ee2b34ac59a2ca9f439ff06f7fd158f84f8d508a3d4aaca7505c7bd07b54ecaee5bc708619bfaa0ed09709df31c6c4f21310f11bdb3f2bb89df2ba91ee7f7d425116290b19ef5cc3cdf56404b46951c0c8874c678b38986c88b2fb6b2973c13a57269323f5ec79f876612d4b58fb91c44586ac06cedbf7ae14b67fb009cdb3c5318fb90adfbd6c6c48765b0b774cb05656b1ec65762fb6d728b2aad69ad00bc8a0f692348357eb87530467343bffeb1284262a3fb3dff9a8b075fada0b76e05065b0307f14a765f79af62ea7ddb916e23d76c7dee007dca04eb8d8187516c2ffe7f505d3f5abefb8b68c124288937dadb9930f77d395be797c2a4a7e60ab9aced9cc3d4976c77f1682e847b27571f284d8f3a514482b3f37b000b07950c45955f900f31d95c937b99bab8ca47d1e7c83b0bc459f11a19f74fe2c695e72bc8ea395cedac4b38abd2ff977eaf906c0c43eeae22a93032a7870d01e19bfafae85ffa9a0f3e069bc0374bbbe968176173b38e4c5383a35d80e4e863500e21905f53875dd4d78a0a5167e30fe1b87d34b058bcc55e4e22c83fe4f84cac685c81efa79ecae4a310b5946793fd85ad07c983daf78b0522b20105b88e251a85dc8a7348328f4ee44775e3e901eba5928c2e4a900c02be691f12fc17f014a3020f68370e63c242f120f822210281406693e1e897e47446785755bc4c3899b5f5220fb96a21742b45ff1c207c99369b21494b365a757307b070a11cbf3123ecb1bbb242996e907319e05e397b96caa6efbb7658ba41e4135c5f9962f7e0422be3ebe65e6d6cf69234bc15fa008f0780137fab22d3b01f08091f96e154e218c8c7c25fecf1851bcaa7a87a93572ee23fd5c07eb0bee3eb4243ec8d5d3fcfa0ac38931664682b7506be24d5f7927abb13d5220ada0198536889fa4369e97f1b60dea267dc127b17238b89b29de63cf74d19bdeab661fc9728796dd58b981f0f37a98124019be0afeea881ce2a58da5780f662531b30dd0e44035eff384e77c3c53d7204712b82224b5cd96cd69c6e05b61f8a8421f006741af436c36b573e0c32d62067db26a0edbd89616698bd4a2d7a8626f15dc670aaf110701e6f24d03d90aac01b7ee0ce42f957f7fc73b9c40d290d5dc885a1e1b911b9211398e8be81a3454f1994a26a89ea276f884bba7e8290b0bdf5ceff1eb39e70b749210340053aed762ececa2763938c8e3e1624aa79e9a388a5078631b4bf7b1d71b7ff492b03879d24f7d4efe48aced9423421656f270c5dc9b4587fce4a1197487ab5b92b7eb079cdbde043c6b60ce5c3480b4168ea0b335c2e32aebd5c4adfa95ccdb8796f854e097f0abeb4cd2a85d0be6ce5463bb14de77c2eb2d30cc17ea507d6a066e8727d2133c62a286376bbfeb5be8f6d4fe8adc44371685ebf9d843edde3cadb992ad9eae20272ec18fff90f44a602c13a64218756e0affc3ec8919d70f11b86eecadd3e481b5334de453a4ed9ba318a7df66a3366e18e16ffc6386452e0f1cfbe6f61e41b5b6423fb1cd40ae5a884dd68c2e396fc5041cc957f4827e8b796268b7c97c25f80ba7c6f8eb8906c240a52162402c6669a62f6953c77525d241e13a0cfc42e8e9107cddd492b08a452e9845a16eaaa942df3428150915e8e62a0dca2a6af6b7cb3cf2fee2a267cb831de31f1357f1ca9263a3cd0c05c87264e20f192dd59412abdff08bd5bfe8dad4ab0674a81cb631a1a202309ffcee12bd58ed3ab9c01585c3b20eb9aed7ebebae70c6326ed8eb19c1b05d48de221504784f30282bfb3a52e7d0c0ca25601abf79eabe090855f26880bc8b7f1d77c6514ac1f09ab2d29321de96c570d726f6467cde180f895ec3c2fbeae36680c66c08d053d7816d484578de03d64c22457b53b8cfbfa40fab9ad1cfd93a8343d7db99529b5cece2e0853bc7f0c20f348797e8f3bb208f2949e64bd788c9626880e8de2e21d092e906cf31e2bf756d33f27d8cbbf2a0a06916ccac5118b3cf9a15dc265f61f874637a17b3f026abb91651d02e2f74be731cc86d4d32e174bf2bbd84c31a6ae5d3f7e9a129d24422e2b61611f852a505706abe7f21893fd5bfb0c0f606260a06871796246bff54b9b1fb4bf57cfc8cd6d72ff3bf9b31471c84c1a51be4774e5c0a17911e8c35a3f4d281a061e94bb5a75df2b9e1f0f21b6bcb0b38a37a3ef32e549b74181e32230c950bb842061fcd8f6ece4b4178ff27b8b8c17da1eb987ac6c1ac84bb41afa445190652c6d8e21457b8677813399d59d40c9d5e4038edd0eb97992cfd3607ec75d1b5b2ff97ae76d93a041a598b9b42ae5e2a840a72058ab80e854af4e2ecdda6387520473db9c345ba22eaa93d33d6991ffbfc996ee939d17cde47d2d4b73b4d10ad6dc0762b0b626a8a411358f362e89a942c7a8b466cc21770b5061bcf26faa3811df30592b8194850377900d4da4dba5196c6eccab2cb091bf4551eca27df5afccb0a955b5f43ae71df731ec3b02c93ab27a000c7304cb3011bdf2db43bf375f1910d58b6c44e270423b5e480160b8301e05b28c0e2404d403636e0114aef7181ca5dbf17a0e274f34609e59fb6b023a50b969862209d4a0e0d1021305ecb60a13fbb58ce2f81306dd7ab2181409975458129d875694ae7a13c1a35495fc76647f80b310acbb78c4ede3c2812947472f011606919b6ba04dec376b3e734a07b506804377c7ea4f6120c7d851690fbd561c01ae76b8b79e1935c122910b263ec61be06c75bc2c7f611ec1508971615c22641ef0e0e3ae003ddd25a4ccfed87b82815fae19da3573c813479c6997212fe2b215be94be2921a73dec66bb97f25b92510bffa04c25a85bae883704c91cbc82ab3fccda12732d94a36b29823ca4746affa834bc7be26afc809abeb9269b5493fa9e9fd27794d1711a85adc3a93871591dac2ce4cc39a8332dab7290bd7423cbb63678dcf2dd2ecc72f243ec9489834080d2c2ee06f4ea5c20d69b9afee922fe2ef3e4c99e80fa54785d4d32990bee9051f460fe02dda20af7b70bfca7a1e3c1b5c42d2e509961a102745eacb08e8b3019e79dcc019444bd2042d4cef10f1bbc665ee2164051a4adf0d4a278b97dc50d69d387b4fe0a015a2d8d3b95492bbf54b10400af950c9cc5b7910ebaad51e09a0129f5183660db0f579242e6ab4d0e70e98d0590b427ba1969f05e949c2d28061351bf832e63c134bd2945dadf3e36823faa5f19bcce6b8ddb8af81b643b892594f0a4c35ed9b77383083ee8e06ad155ff61359a939f6dc39ad6af842b56b4304c562089a1e5f2b32d21a8a6cbe96cab661da67ce0baed4719fec895af4179814f0741b893bfdc21d275a4ed8a4d0af98d641ff746396e0d669c92fa6ec5094db8a3b10ac697fe3ab8aac1d4318cfa36a0f56e91f6de0579aee033f77e9a757d7284368ce5e8b374a08540c8bfa08d55ca6fa036d8c30ff3d6d8dda98c2b4e9742c68f1ef89c5935b0b930791aa39bc756cb2139b6114d856d9ac9cfa940abbd0abf6d959b56c169cd211c2b1cc72a89f43fb52b0e728ccb64d4b6b4f42e9d600dc0f8542ec300258f2275afd6c32860ea73f82f1d77504183906a9801372d70f56fc6700295b99a83e4cbfa524045a33896df7015237377d9748a0341e5a5faecbdd0d9f902f429913836eddcbeb276ebebbb1d2223beba63e69fe30298f387d87e1f04755200ac8cbc93529bef2bd80962a25ae865e4bafa033bf29b0924957812c228984c2738812d203f65af8ce20c281e940e9c9f0c6b99c4906ff397173ea1f815790e3a55088e78d3fd82f3f7638e30cde40384373c1e02477d9579bd6de1993560dae29e0b12e0624d3639217ed873c6926b3460688d428fad0aeb687db9fcc3b36af336f75806f920060b7c3c01ed9e76b43a67d31330f578187e684b9a7cb0773178070528f9e1be1f3c36a96f3812050cf491a1e414efda1f46dd1c4da13b7dc1614a627d27366d30dfc5e01a52f3212eb208398396a4818d774364bb814be91fd7e27f36f2ed61cbb6f81bae58dfe61a7b19a29978c79f9f1fa23c3da26a9124c81bd8a6e7ee09a5e1f4e6056c1ff6bb2a9131d0e2e5edf6034a59d4dc23beb14d673b55ce77618ad1e91b88a8ce0955291f773ee8be47fca68f44c64612a62db627296d50f74445b47222151142e0235701d166872d863298bad5d9a9bba068a030bedc470fb834cedff4dcca543f36e84515722dd1cdaec754a591e2b2ffe70c334d0c4e12e10f0fe9fbae674b9e743ed0f59d674a45b17c44de1149d4a0078062924580ef266394d70d4f5d436248bd5c5c9156342b9fdf0496988349aea451d356ecc639f8f2322aa0c354933e1aace38c4602b6419d424e27e873cd401f4d74bce890a33e6b831260c899a1be34f709f43f68d58796b80e3fc8d3970d8a29ea3d70353f550561faa2a76315adf09ee7f9414669e1ac9f87261dfdaa4b5ceaef76f9d34f9264711033aaec27ac1e5998e95ba38da9be1f83736f0b9057d0f6477ffff38ab0b9daee2b907688bf35681ab33372cd4431464f335ffd6039d1582957816b91348c0a66623b9422795d797eb7a087edce1b35e0debaa8ab2d53665c8e43880bf0fdeb1a0b38273e5bdfb6c16d52ebcf28db601d146b4bbee54857bec63d10fc2bcb486e7694eeacca8d91307cfbec3935d2d762316c5616d02c1f91553520266aa56949ce2487dd2617ac020d83e9b47f5d677cd0a455a24caaa1244126eee3d6c6ab6d94608dc5d0675658c3861348aac0f46deed4d0e19d1981ecea73f28e2854c62f5f7f5078869f3e18f7e77b2be0843935d50be495e9b5635978c77aade571ee4aac066895332300435e00e79418362cd2cb03f4164a8c1a348b80deec28c1553435e97aeea1a4cd3c0662108b32aeeec32ff109f58914e20c1b742c715e1b50b8b47aee944b64b11a691454887dd9a2316e7f7cdf38d195a2e91aaf97a46fbc29efb5dcbc63c89ffb5143ab8d4b243470a158b07850bf1084295148b422e024817830aa615c8874c208a8537a24385054725d462bfddedb83ccb227cf4b34287970257f545f3d34148e8e6c26fff538458b91ecbfd59229f178a2b395d68da5cf4a30ce4452698f0135306e7d7fcc0348ccaad10b25df686bdc8d131c46309012e115894744663756279538b17891640d460680f95cb89a5ad0434e13905bcf9da7a6fafe672301afef0c3b72d67aedb8ed8f147e68a164c60c4b65906765e564714f0b0d534ee1f1f91da6dfbe05f3109670b76be344f044cc2690898f25de1183e8340e5bd5c2a27a4b585e32b428d90a675a67cf610fe5761228b8e6c029961b36b64a840960ea1c3e9529cb1d6ade5cae23f6bbd671546d28304979c51f7aaac25528ddac359a11f8cd632758f401bfc44fcc19e76ba8832bb11ac80458cd8a6433b88821cbc80374b057dbcf4607d0072504c14434cf61de7feb2515dd597999f9270227dd9d7c9c242e9d5d8436a46d2195accd86d90c28ec236f44864c1363b734f6570ad865fd5edf34f4ffd3e6493b50b84509bff453382097ccf9ada958380befe2966463f86ce83dec5886e8fed484b62352fe2754bb457f411da78891766bcd7832f9eaeac14e4dd693b8c92b34ad0d821e81573b7bbff2205901092429cb9e2d8c841b730abe05b9d32a3f4081928fdab4395960d8a26e70b81d73030ef2a1399f708cc4663780716cc78ab8e56414b0f1139611735caaf157a9f7829f4595c4bcf35fcc61d67507d08f0aca0028e4f508b4b052737bdbf46a0f70a2d9e43d2b24b07dcc6f3052a2470da80d2c50d72204dca81f9246d4034838aeaa262608096de97dfdf00d491f0e8be274f02ca624a3fa3f5fb46af1ea3aa57e5f70ac1d478a4ab7e0f84460013ea1ba3d42517f185f1c7c854681c4218ca810ce011c30d5d205054c58812d1ac946d38671a00b3ff3ed8ad2be3f4f981f8c59712f14b8dcdef20d26d8082b7f139935e9cde131c350ef97b1f3c29f459ac456f6b543bb94b1407cb50d0ae19f0ee428f79935c73999730e15164627a23e8b7fbbc123ecc78bd505090e9a20c2f485f0cb8e074e16edebb753b9c41b74366539b9617f1a0753581eeb44402e93c5cb01ee8215da3f4eabcafc2c639ba18e55f192b29a24f8011a54716beb885ab10a79caa3bb75a690bedad7b9f832fdd5f112d579296fef347fb487e506d2d835fe05988130b3c7ab8058f735360287bee1775848e5400b473d8dee5e5e49e088065727d72673f788ad2d63905209c9c90d5d7970b96494a2733abe136454a48722ed19f392a6caefbe082c109a568ec110cf146020410e6ae3584444cb4d59754ae8b4d22d52c3bf7f2ec907dcda65e4ada081231df4562a7032f2c4d964d10b8a3287c615e89151649e08b97008e464a0867e15286277cb9492b71abf136ff0497ce8808a8c8cbf1e52f8a856dab61e56c1755937951ec7c70dea12eeb5ae848384457212c317d46c942f377f0463b30043b41f0a0f67bb12f4c0807f0c304ea2005dffad0057602fa7c633656d18bf681bda8ae2f7ae73b336c2d034719f0692398353aff7d16ca0671c61d7f5adbc448a1cca51f4de6d571c4751706094f1be1602be5664f6eaa6372071c8ccea8de6a2f06ab83c2273be27727082661878944d76228ea601c1531a01807d7c2f9298f0fa4eb8a4a2f0fa78b91ca7e0a9210dcce16249532a4630746383c072e303df59aa34a019e25af6f2dac4809a77d6f90a29ccfb9f94ca4e3d39473035e61594a49bc296d3ae5b1a804a7323a972a1552b56aa68f12684519f02be794d06c5e9849dd3f5150beac987d80d2572ba1103230c1030ce349618d8519ca772d9921d12a89f2c83103af0fc8b5d189dd90841df9c911ab72d28319cad9b544edabe35886309e107ccc09e96e3284e0c87948b598caa90b022fef9b03726c59b7f23e02bd34ebcc9a185d623f599527957a4846003dda1bbdaba360687323df9d5e9f0a10c911d45c4fb8bdf0e8557fdfb45323174f49bb33036a7046e8deb57e602cf8b890657da8ae5c1cf97c763bfcebb5e62307de5ec47f0a7c307c61702dff064907854ca69a55493da7273bb08222b17eaaee7fda7c0a18644d84505de606da2be649464765d77d111159e96a8c44d66b4d59bf162a1aaecaa8c464fea76cbb117b55a71c378cbdb38edd1e8ca58e00d3c8c97bdd8f12dbf147c13c01a4c9000e26788631d62d3b736036882f57a397a5ee2ebb0a9946b6aaaed0f56182d795941216a947afe632b3848765c00cb0cab8aa3f7797978d3bb54b088a4058bbfbb660377da40625952210c0e8083d1623402060ebdf8dc697ef93118f15190f36c911d9088a78ddb18ef7b6a353d16bfb28952f3df6a520abaf632a00a5115ac909586741495e172c1862cb9af65bd2472be1ad2c5a96093ad05be1d113c4ee6ab50e50d766d3a1021a790b9d20a1b40210d2c9875b8ba95b5b8e1ec3e2bc85d7c5e11ed456d4303b49408a6ed7d5844ddfc2185ed7bba8a265c63e47992ca6fb0e597721f44e1eecc1100b11e6f0b96ea0e4d7a8c35d79e865a32de001606555fe5c25aab513fe434779afb16190168038c20cc496e114c7987343d8adc9976bfb2e689e1e3da4af10f6b6c7f726a9b9abc9339e988169831dbdb1a52af73cf6cc7a83b2f18f6306bcafacb21fa1934c92eeb94f26e9b06b128ceee241f294056a7160f6273c071ad600cf1ad829f164590cd507011264bb0741676e4008d28fece5e0a8f59448e4b35a8b735c1ea2880333f07033d8815bf022e2285b0ca525ce3acac512b4dd1353e1cd97140886b1d48d03a6ca31c2d0b83bf98008c8ddbbd66a6bb192ec0689ed92c93b35f246f69e8e946e6af5e81ce52af6a9c2590a2790e46f6ccf38456a017aca453e93560f09b9981c2e422e2c834864f3545b026987fa5a01d729ba98fe84c0316946fcc4371653dd99f2e560ae0b4bc60162412b0bf3975987ba294b59e2877ece0b71b6c448dc2d99e1f856e1cb4b84699ec1311cea4d29a2e3824bb703480da5c08efee30408bfd6b9272481a39caed7c82402eb3988a6893a3f5f8576a84946a8e2f3600f31f7f2c9fc27aa4a70b6bba8f6609764bb2ae05e43411b34f4ae1e665084258e4c0904698c2d6b9da455f2b3a68c876c6ac722e7cfb22d658f26fe74a9b438d48f51f4529e02b8178f6457c832f4e8ecd3a7c762037552a5ee8b7fca1dee72be675431a99a289668201f6dcd4b37bbce033a3c59e5e115ca00f7702a86df9b5b27915122a7357b4a16bae0ccc855103271dc4dcce6112965d42de6ada72f51909a08351900146c317ee13b27d8bcadc42a860b1b0472ddb272afcdb71e89485044019a3ec47b3ca960463d651eac263959f01380f8f919e2437872c0759b8e5c09264e0dac8e027e662c2abf75c1b1940c21e84e87a754d703713872cf1699547109aec7dcfc3238ff05873f96c1554eaefd0bbe377a29e80de5b85d83700e540b3b9d3ce0b024e41104ba6537aa5820da19a703cdaa62056cecf1c67c309c701df078e6b31026ed56e5e75da84c77bcc89632450232d54fd155fb03b19508b05002a310906a7321517d409d21fef5df3695bbdf86bd0ea43c5562c30411d9ddb6dc52a694640a3f0a930a7a0a49baab77559830c7a7c6777777ff23dbc3af9478632d47f7ec6c867d0c2141fd7d521f305e89359e34a89f2251047586342258a1c41caa2e939821478d31ce58718253da345236ad60819af6464ec125205c6567da9a6823679ae60f3346ee1760de63188691e6d2442a1f46397f985e029991b46dc70b368d944dea038aa0a0f5b943c690109eb08365825dc7d102a232658b9cf1de8a5c2959ca28658c314a12c827121dc3c14475d62d7fb617836c624448d8633f4c4c462c462931a90452d65fdab4171489667452d77b3e856ac618e35c4f6e552aa191f387b9bdb0756d240ff5fbcee380f5e2a3e20ba5336872386b2576d1054432c618a39452f20aba075ac728a594524a19638c2d85849146d05926d823c5467bea07817888c5253e020285fd3c94f1100f4d3efab9d19e192c7105f6386b0510c208218430ce28230ce6466512ccd2a13a91214bb22c215a32549d159fb92f0956fd21843470f5603761a8773295793217c166009b4c5af4d87fb0496d9e73a7a8a87987818ea8e4ba39bc3337ea10c3a28c58ef6059e0cb5f722ea58cceeeebec316e841c9296d25f3e74a34590c51e93f882b9517e652a7882c5abbe22131cfabb7b978461779e654a39290a4b02859a1b151a827232544a29e5bb9498942e4991247f3d967a786c1913844390ec52c2b9514a29254c179bf5c984978c31c618a5941247062ed7faccd15bc818638c314a29a5143782a1b880a3ec22482388e2db8821cd1831677c4c76362f6463e3332c903354df81a33acee6f3bff9f25ff3d001eae8201e1c1d280cf62f5926dd3413a9bd1c1bd4cdac0659616b6bfa4b2468ed30b922b2ca8e48890414230a7a7102f44550955c34fc7e6805917b9c3366934d36516cdc666e34c610b988d18f986a8c928a18e3a909378521a5134fc5fffa6408598b17f8dc286b21e4594c76648c31c618a39452da3cf119a44c22e7d87d85aacb44af46e832665294bde8eeee32b6df444d34708da62db0eedb24accf199dd0969312ca871042093b1a21a53db06c73229b2fe3cbc766b7af611b0efed14b3db0c6a45319d316a94829638cac448c3e027667c87d53e3016a1c4069b7fdba1a3f2e25bfe66103d4d11f9da06c803ca00ed4d16f32c1bc984c5b47eae6954a7ac9f97af17968d55c9ad86f823a7352ba03777a073939f84a7ad13a6e6be277a4eebb40f587ebc507c2258645efe0cf0b9c4570f9a432533024647a073b14935366922435b94919a3141a236f949a4cff9e874205712224b4519b1d2fc28c9874f7f7624449ac0b82094921a1fe190fe551b0852b7f4eefedbf444a229f40a1a6a99238b842f59c2b01d86555ea0cb03451013568d30038f842264d28312c4a8ce314f772b06a7bf79f476c9acc49229988b4eccbcb71c4c663cf0676d99e4b3f622a94d6afb66e07a6e92b70bf34de90e9930336afc9d2073dd18b5adc622946295b40a564e7cd64faf73c14cac66688cfd090f7cc209baf7dc47ebd38434926ca038d5ee4a6324bffa2c52998905e5ed3344eaee2ca575a6f1ad72bcd378de39526378d83ab12b7c90702137fbec3ffe88e27d013101823f1e917bb13adf33f4a2af590118b353edd9afead293881788d2fe9de2ce504b411f85eb77e303e0ebb11f8f1bd3bfdf01adf6b746e7f7dcec738b6dca394524a19638ccd33371aa57494ff6cdfba787ea64419aa47f6cabc828357ef41e95af966d81dd5d97bd97254a7a4ffb68a55ffb83c8440df1bec4a75a70175f7652132a6fb4c4b4d2fa58deb53f6481829e388b0d715eb866c4d7f944d723dc0b9d12b252a425d5ca930702ab141d5dfcbf1cdb81f99a90c650fd8f57a977ac8ff7e6d548fc9b41923b2281bb03303180fadda3ac84af5286dc1da6ca4b4b1e12a090358e9c7d7ecb509975fb46eb78644ca7ac46e8b564bedef8c3126b8628c53ba122637794e789e6f3fa1ad9452c640e73b16b7927c79c1482e47e05b943039355226a794513a9531658c5101793029860e5ec293d2cedddd1fb58355a5f7df4e229fd88169fabf4d9decda1f720e2184117b77c5103b222cbab92f0ec9b51e3b4f2261ba74e9327d90b24a9cb3e3dc4a1fa594b2b4a132cc83d08a3d4a4391509913892e6397b14fe10546fb48caa67ccd378d944d140ff451933b0c7e8f1d9d7f2b976cc881b521e421a2d8cc629c9352938905514a8f95480aa29991668c2065267f9b1f9bf5e933444248299f638c12462965472b40992d605d8c31ff79f43f948cff6c62fcb7ae17d306a580ac173f3edc940ff67a8b2af693ba806e32b8189cc906c8a634b05e1361afb499b48e2483b4aa08114efda0814d01b15e0c5797263e0ce771585420fa8bb4cae6074eed7ad35597083930a8feb04a8e0e1a86e3686afc35000ed51f66dd733562a0fac327f273a35b946d8112030e688cfdc055a4ca773f62aaf29deb266c131554ca29698fd89d9c78ebd35d069a0d8aa46da5196440d9d94da67fcf43a16c6c823c212434434ccbca1333ec68d0abf092242d470fa594d216accf1c359e307a410e9fc2f424e39c93e161d773251e3960a2417c3860abfa0f3940395472a654ef207cc97d471cd6df1a7fe89877cba457c067009aeaa68f729bad1527439aa6df145fba52f7cd2d8618ea4765cdba1f4a23a0f0872cad7187dc7b805576356e7016e1ff91e3d7bec6183fcacb3c59857decbe24ae0435354dbf0c0c5ad008bd015dba74f1ea7567fa68638c1c638cec0426d9a3a0497fe8b0d46362f2bd63a23552168954f9f1715a8b5becbcd483df25d63986b1173b9451c739bfb93941e5c7ee9b1f8397eff0096d71288c8a1f464e760b7fc71328c40176499a9b35967c6c855d2c01d92e409274a90628857579e7d1dda33bc767c618638cf993891bb1ece1aaa8fb84a911a67e94865865e76d4d3f8ce7ad4f19042c71450a32ae1d48265a3254d9c71fa7997bae431f18507f9f506afc2fa6ca0e026d4d3ff30028723dc0fa41a0f5097da40ecf0ff461a21fccd803a13fbbbb7bc78fc31c343b0a9b7e26ff221d7c715075af82ba0fc117047a85754dca01dd57fd7888833a0542106272508fcf60ae63a5ffe8f69f09e6bf5fd5cb7fa41ae767a82428c744fb207860b158ac157d0d869bf1f2428d01a840898ba51a6718a9fe839638b934f1b7971a3fe37cc02a89c657fd7f60553ef683d6ecbd044456d921d92e95d4c5ad8932ebbe10d408975f66e75b13bf582310d57f13105d8947d4e48882d7dce28c524a29638c11670a97bbe646d751805109ab9450a894914b3da27b1258e543f9fcb2f301d6d83918fbc26646f2a098935293e91f6709972b4ae95e7d0521a270c8f88c1118b61ee61553e2476670007d0105e7c0cc10ca8b012966537a703e918f5ef56e08f67ceef28744312487d60acc103cd0c2d74a4f5d00e4386c22e4cffcfb1153f57fef09f5ee6f6f9aa691cf2528930aa69ff14c0330c2144698a65a8475cc3e3e6dd5f6f2f1513b30ac09c576ac176d24875a6f879217ee4b9ea3368e6a26129771cb039511930f8fd8d4f8b00424c6132c5430eb9622076cea129923d9a3f8c8aa7964734286bd76f7ef086f9539ce87a51f70044ba4f8630f5f3efcc861ddbe7663c713a8ffd6f8b3d423da90533d7ae7f3c86684298c30c556034e38c5ad1ddae75da2d8d08bbdf9e582cdcb00d42a197e6d3a195ec0829efa6d3e6775eaee44011e2fc30b51c0db744aa4f42be0bb13c2e36578193a253c3e679504b5f99bf72c45bdcf407b23f7a6eb3e5759157c35cdfec48212e1e324d4fdadb1053627a526d3bfe7a1b00d9680406c8bc79d84c8f0de7b9d12254bf68a26785652fe952ce11f7811b492c2bd005e86cf59cd1550f8de2f8edf6f56b73875fafdd661d5007ebf9b70eac6ef374fab08f0fbddc3291bbfdf4f5895e3f73b0aa74cbfdf3fadaaf9fd06e2548ddf6f2aac2ac0efb7154ed1f8fd0e6a958edf6f214ecdf8fdc6c22ad4afccaf017ebf8b3825e3f77b0bab0ef0fb6dd447ad42c0ef3bcb99b06ac7efbb4e02dc09ab52bfef4fdca755aadf6c67347d402406ea986e0600ddcc4c07b560b07f7a00852e48423dd0228469c70748e899010097030b88cb8145657b80edd4fde778e213f6f69fe366466362c8909941a386c9c60deebbefbaeff41cf75cf7e178fe9cbaa51edfc11e1edc76c3650ae06c38f8ea8fa1fb260cdd0bdd67dac7c191ae905ceced9f38d21047d2c2debe0d8ef4626fdfc491b8705a8bbd7d1a9cd684bdfd199cd623c36932380d88d3acb0b71fc36942eced534ecba215692a4e3b6a9afd14a76d619afd04701a51d3ecefe0342c4cb38f004e0b3a00a751619a7d03701a8ad39e30cdbe0e4ee3699afd02709a0ed3ecd7701aab69f673702423a6d92700472a6a9afd0170a42c4cb34fc391849a66dfe3485698663ff35998ee9b1fddbaefb5eef32aaaf29fb2f98c85531ff6535786bd53d6d1c035a3b2cf2e4edd1862222818d57d2ee22dac62b21169e9a98b62b1154e6d152864a9fb1cd4aa5eed6793b11f4e7d994fdddfcf7c3875823e300a36855318104685539c82ba8fb9302b9c6214d49db20f547f92a01f746547381d006e5718f7f1ab6eadba1f3920ae52f705c02951b2243be2b59272f25f82802a442b2927effc55dd88bde5a95f7624031700ee60506644bf7e99bfeac2eea34b85bdec0802b1b70fbdd89badfb31f0d00fbab69fba0058522327009ce3096d673e7ed57e1e9df992874b5402e52752e12f078027d49344f835c2a036bca5f6f3164ec9f77e6d967a402b4a38cdbac9200832cc1da5a139c51fa462a5967acc1fefce9c29d3027d1b7461a67bf6fa73b0e7ede09784c22a29f377df35274171dbbd79a1eeef600fc21d2c58425e0ef6a012edb727318944ca489dc7218128f6e092c642b4da5ee3966c53ecacb4b721087bf07db0870327f8c649e9d73829d873a9c7ecb6090a3f86236c0802b3391db0454ff5ca838f205bece860c362b9a1ab0366acf37bb6f468d85ee7b373fbb0a9a424519bdc5753e7673fb98fa6cecebe01d4993177c4a662d84f200da8185714a7b01d2cc51ffe7c2deab7afec6b08d1ec49af91b89f29ba52dbf38250448aa854f83bb08ab3879f638d2a3c41fd76d41802d51a0889c33ee3fa39853d01aad82a5330aa6c434424a808a7becdc203a7aa4c014b859f837dec00dbab70cb10e8967ef0e4fab774034fee6bc66d1529bc2a97cd39272966393e9b1a2712d8e5c70ac108203a706a76275a6588a644a938326e071e046da907a8cb8328b450616976a61a50d30ae8e755f8455ab52b081fe38a703bc0af82ae1076762842048876972e15b2480c5305160c62270a3b2b25374c3464b418982f75da155eaed546822e2082cf8af44b78044106aed5e97356383e6705c3e7acfe7356dde7ac6cd4982123062d2979f9527712927de94b9d92d2679fb37ae17356317cce8afb9c55ce6abfe376098e33695028fc18388ffb2f5ee07070a82ef4e3d7eb05d71084ad96d1d18b3d7eb1e746157b21817e04b437b0f2bb0df6463e7f16a2ca4fd8e3fe99e63ed4dc7a6ea6769f2938849c88b23daed791f6f34511f5eb27cd8a3db429ec71048a3fcf13a56391cb81e2eb753464145f928b5e6c0dbf47f7865cb61dac2b5c5b7e98f04e8b75459525ead74fbae30fa7bee8457dd5299c621d13d425aef038d9b6137792008e3f3d8e0e081c7ffa3867fc79acd4235e9103290e903838de86e823e827c6ae0738030714d61ebc7eb185838b91cbbc686f606cb57e34aefc71a855710a512ce510c491177bd35de3ab5571e85539c630047d810138e24fabbce8856e9b669be6f923117b1363b0354540559401a4e1ffbf71e3e78badeee2cfd630571b54e8e745ce434d4ae06abbcf5463cb8615f4e3d76b6f56d59449bffad5a4b2084e1d10a71702ae4ea7ee147f57a7e7f71e5a63c842e3891b628397ba5700fdd41d88e080dee06c70afa5062763050adff4847ef107a855fcc5d2f03387e1aa3c82ca1f9b88ca40ec12e257a603fa404c7810d47120f843839bc1411e2450f832fc32f8618802ece1600c0e965894870d0aabc2180eba60100f7de1e050d3f0938ce0a645e8064e7ca2f84cf9f9b9a1c42d6cd2a567e54da0f09d09744b3574750224799df0b2220bc756dc012ae271c25ed429a27eb149e579e23d3d0ed4aaf8b38329b6ecac3c0bede2a7f26b6fe4de7021d2c21e3fd1162f2aa2c21ebbab7320f6f86100837eb1058de20bbef6a62bbfd6d21b4211da424bebb057a942d4abf8d2db8be051855cd8732bb1ee1536d0c2a499e5452d9d56b39af0ce4ee52dd5f0428d3f4ba05ffca9b0f4a3eb27822abfc4ed4aeb0d08fc5ec8ced5bb6fc9ae366ea32085240cad346e57923302dfdf091d25a854e9c9520f50d7950517d47e6d0d7f3cc5893dbf38152b3f1b716a5d5c742a3f1f754f4761d5328188b9706aab58b1c3af2fbaa2d87eb814a17e3d31e9409cfa62cbc5a9af5f419cfa9c4788535ffca95438c52d22eae7565af5f32b7588fae1a89951fca92cb457e800063552910cd4048d2d4e9dd8c55666165cb17b5d81414cd08f836ef07eed4dec4e34c81a3f762cb435fc469c622c5916faf5e3d756f78a185851bf6671166e827e3cc429ef4e4060779b0d481c13715116e8a6020ae58d1c8c5346da6e24712721a49c95c69d84684a66d3f86b9fb3da2d66d98bdd89867571a1739240af4e12e0df7eeb44407afe5dc1f7d7be243c597e7c589add0f9b1a5535beaac6c7691cc66123d9c7245cb3eef4237ee7035723f2d7c5854e95aa2ab97515fd548ddb1589db55e4785055f83be39299075a16148abf8683e3005665f2d7abf3f73f9b39290d92714998a6794801dd69c2284545285e90b781df6462ef701887530b21dc66d80961af83b0d75c15d42bbf0338f590fd0b66be2144bd3237f110324326eaee76ef6e6feff676ffa1113a8311c3d6ece7d4ad5b82e1051ca71b364c3568cc9091112386c2bc94368d944d4c466f2ef5e330dd6e7709cc61662d1af42c28bfc6dceefe12bb6ef729e95e62a0eda6f87d57d5fcb0dbdd87bb8421057cd7fbb77b994de0b367edc7aa91e6947feeee1b77abdd95800855a0dfd736cd78393835e4dffe93bdc781df886fecf57774c83db3ee7cf8717a924ed215be0b216ef799e0cf55650f29cc370db71d5f76303eec9c3d538c5d0f5e8f982a7c29c828a57c9798c4a4d7d825e10adfa9e0eef0c6468feefe53da9003ab57ffe07fcc70c297330ba1186394d284ea41a1e6464d419a97c327cfdea29412c3b028636904d24b3df8796e14a3a2273e0c2207b1d6374a29a59432c618db05ebf38aefdec88f9a8a42a171dc7a4bda6c6757d9fdd6c4e84fe4dd87d8b418639c73d2dea22d6fee5bd45bd1dca84de5b701428c5146982e3c89b076be3531e70a4e93f4113e3d57e201f17be80453ba54434ef59fd8b70f5cb1d7e6d6a51fde0cfb65ce0e4957ac93bda51b62e745c02eda421e0da1e47c602863e46f1fb8eb41c687ab23fe46eeea48baca6e3958614bc84c76aa9c1897b1883c077bf23df6e4a3d893b5853a28e3fa0af659d7575a8853d8cbff30ef6d4e5658eaf14215146ba12afda769a4730b63b0873fadea16c6e5c022b928afe47bd1ded474517d8b2a3f3641950febe90822dfb4e662d5b00de36690251ab0da8f7158d5b00dfbaef2fb8a8c51e5f79133d991df58a4785265073db3a4b0523fcf1b6b71ea93d9e1d487c3d4c33a9ce256932ad949953cf5c381956ee00f6c6a7bf91ed814cccb1712c406f940ac0afe348d7cf9436a7c3c173c13a623a809aafc60104172bda85236e99d22ec658dfa75508d26823c7a0ad4217f4aee2754f9f0a755dc6a9ad27f35de53f9301bc72d2c3d05f2682c56877ce923a8e93ecfa8fe162d1f05c2d6c82aaaecda4efab1871c0631d8b0bbe6614f76cb3aecc91df65ea874d0d6cc981c82f069177bf25fba06624f9686a0f2fb49fbc497dfadeca589ca2b4c2325ed1a4ba95bf6b6eeeb2c59117bab7133b0275fcb36c8e1d0c05867f30215f49ba9f2bf47710a56f95e0e4ec927717d853d99752dc49efcd975107bf284f51556f50f8bc5626225bf8338b5a9ead3c57a5bbf1a5a6896a5d95a7db02ccdf6c04ed54bd9631c576d7138e36417f49bdf030a7d28ad5fd1ee84fdae5887694ed8c38a75dc624f7e2605fd2ab520bf0eaaf26515f49b55be7c29492cc384b633d88a9df268ad2eedbaf3273d73c23d8a5b76ca9a7f383585819808bef0c354b054c873a542d65261f6ed9287531fb37a38f5b98ff4e1d4d747d2c9d17ef2898c52e1cf1406ba822ea74eeb59b765ddc47100a74a0fd9461e65599665fd5fd72c6866d9c3ecb3eca83a15fea1428ae591b1a00e9f2fc78c8f8d3ce29e8c057970142d9bd977f771fdf88759d079380ae4e14fa08eae87ab951fcc873d1e7e88e91cc9a3c95d0f70739e5651da499ea6c93a2ddb6612f4c37c6a14f6a0ff34cd13c8c3a5581df0a14f2ab606fec7f5f327153e73e73eb16e95574ffde4d12ce2d409e661253dfc8cfbdc0763b52a16613e58cf9be6cb2dc097612c0fd904eae0c2a923188eca14b54af2b46c0279482b5007fc37519e58d4a4d2d9c9a31eb3339966117b5b785a25839a069aa8d4acfbb028f06510d67d33ebac3319f452e567cfcccc2c5bf667533b6bc9035f5a813ca411d4c163a5c2ee301ff68eb6868b34823c6418ab630b5b035f4aa30a7f4615d4ffcb58af0a3356c6246bcd17a78c38d5c3fc24b36a0f7f1e712afb197984b57290fe93473d24a195df3bcf9342f2cad6609143328b0cc2ea80cf9c1cc2d640199471415f062d2adb81611b58689a0679eb66e6c85c12f443d5c9d0e173fd6085023b254b564b20f30d7c23d710429844728c2ee690c400a94020f67aa83affb7aa16ba8999b3ebc8f5bc5a2d14b95b684b0f35d1164e9dd61b4b6751a27ead056ed0d52ad872d5102bb4b235fe730594e10b06cd49a9c9e4cd23e88732d5ee866c8d7f0bb52a87a5f1f77642757f8d3179c56f5467c2aa77412c9cda2a5e04d50f6af988a840e425561550503fc8059311eef070cabf5d9c3aed7812f5834eaa0f55c7e16f62820e6995d734fede3494aae7a2f07fa65f9b09b49dd1b64077c3381a8333f22396ce561172e2a8a8bd198891a8f061aaf93b46977ac4d0b46d7ef940302b2c6931629adbd8830e85edb319323eb26464c9c822bda67dd6efdda576271f5bfd6bd8eadd8e2ffce7e7541c7e79e75e301b0f6774321d5c1a9c263dd6652f349094d841939d1da8e0ca125cb0cdd9bd5b317607444f44396a96cb0a52b297eb78f38f886d1a473f868342e349dd897f57349e76277e58e56fede7a0a02f5d13753dd459d8831f040dae7598a69535a15b321b77f2df1a3f5691385ec8c274c594202afbe7f0bc2a8a2a77a82aa8869580f0b0002654bad30f9727e8a9fe393db43139b05eec6dd53458f267ce077892c00b2780a2490f6b45e3a1d4f8053071558e46b784c6d378cd937005b20026aedabf0026aeda1d9425343a29fd52fc736870529a8b559bdb0cecd9b007677085aaf5e00ea70afd96b469dc565802224fd082dacf251c505cb06a7748d0fdaf5b9bc5e4c072c11765cfa120027af9b0a1499090201042f86a5513f58b6a41e1179b65b0772835a1f8053db65a2d085403048240104b8b080241a01a5d340d7914424643746832b443dfd0465be58ba37ac4a953bf026a8cc161f8f810b9b6e8d76b6f4e2ff4809a2a4c85df437bc3117161556fb12a08e44d2a7426b5bfa85f85232a08b151e6ddea8ceec0f8f30e4c0627c43a974fec7cfae19bbe7e5261d7722d68276169e067f5eb2157510f6d0d7c1c9e4103fa75ab5bada2f1f0db8855d5781853bf6ef5f7cede34511b310d7c58a36b2d760c7ddd1afaba15d3aeee83810f879e40bf6e56d011a8c0840aeb0c20aac1284ee2c62eb81971a9d1680b976ef08d4b409c61bf7c4aaad635769f076af3cbd51f0cfc276cc43615a029157e4701721e226a17bfac20d42e3ea27edde48b55f1503fec9d21232afc9e25885c0bfaf5d0107c085953a0c154bbe3d7d6c077223aeb56b9024f6d22f6e07f5ce3b724015fd590a93dd4aa1a73526a32fd7b5eb76874ddea1935a05a7f01f4858f0637025441ab58a155bd76516bab8071d45b6a0c81c2d20ddc8d6d5d02d23380e82cf558538cbf476e87726009a5f6b3528fd6e1428d5c8c0a4b3ff6bfb81bf483a31ab95861e986fdd8ed7a5e7b873dd82df6206b68ab7c81a5c25ac261bf3b243570edffe1b597d4f68d39fa16fd50d1e18fde2ff8e2d409b6a08e0a9af00e4c41d8f3b335f0fbd43dc4a958e1779626f2963761157ce26a2d9cda2a5db02afc36fa9ce53a15cae8dddae1d4077f2a7c58e448ebc06e52e1cf2c40e8f0676fbabfbb1fb6063e57f8700aa79430810f59ec4116e4610ffe0b3ef48bac0a3fb25a059df4ace0c756dc8952a114ab2f36893d3398013f803e82ec8dac39a0cd5718c0a0ce0c83f6861f3ebcc2a97df804d81af87c621f9cea0adf06f8413875a3c2ff01326115f41112c2a9adc245940ab554b85c2ae48fde28f8397038f511000671ea94e2d60c15ae950ab9637803e7e0d4c9042b8ebd81b50a196d3f75dfb4022a43ab0200b323e8fcd951c685c422f990a6b0caa687c464a82ea949dd8fd4438a5217be738a531ff422b238f57193d3dcdcc888fa3997ba91c984dd476ad5a5375841492d4ef1d4ec8853cc5c482c66c9bc8cd88347ecedcbb05d3c3172ad92a0ff302441bdb731711fac375ef317cddffcab5537bf34de0de7b9ab69b8de781cb6e118688f6cd8f8ccc6bb8d8fcf47a7c7f1f088536e63834279debfc9c4dd112cd530bfbbcf6117b926f4c3a2d47dd3e9e38fbfbef9d1cff4fde7d9645bd85ba34affc37e6a76d4d5a031a3fb4c2fd37defc9e83e0f15a3cb7c62bacf86494033a0de65af0c003a14fe9715d57d2c0aa7e00d6e2b89fb3ca8eed313f7ad95e73e3efad68a0d1a6607b940f11e87e1ab55cc3c5e0c4ad02f3b621ee8a2d014bbece8e326fbabf116006e573e30c110786ab4a005364db30f03125ef72686ffe6c2f01fdd176c1efbf5d77a93813b09f1f6a80a32385a799fb3dad21d11b023051d1e2c75ffe38fb7442f728483ea26e9ee7878310fc31d51b8c2cc5354c4a9d3101d38d126caf8691f3633146193cdc6c0c23b2f226e93a90f6fcae8db05fd3628898fe5d2926a320618b36930193d749bbbbb79316e3eca6b150f41a8cfce07c6f14a060a283f377bd9cb975363f8e285080aff232a0ac2362818803ad355e66e69fc913932fb47766f006b414a2e5b08b9bb7f6fe1eededdc61d68ee1ee72faf296851281b5e158a72499886872a502f6d51108c319cc561140ac53242deeef80298232d998f6188a828089c1886cd396d5c41a8aeeca93999c79cdd3d7fd99bf3677f4fd39c1deca67ceadd67a23d9f605be4a62ab05ed32c033090753f96a6bf3faebe593706d45ffeb7434578f801b2b6ca16a6d4fe21adca59f5ac3a4ca8d72fc9f3421e2fac4a92ac853c26abee7329763fe277334da8425b06d21a5fcec7e128a1b47e356798a6df25778a0feb16e1d82113185a0f4966c57ee1ea805ca3f7472cc6f81873d862bb3bb1b918ff7cf66214babfdf8500eababa7083ba6dc951d7d545a7d22c51c618e30fa2b020fe0c630140a2928ac05cdc0750d7d58527d497baae30ac34c32e30c182164840794161042618c8034b851e72aad0b245123b34d882cb166ae081ed2183c15a931ee7cb462aedd30d81173e3a7861d1d30323aaf0000d586083214461052bf0a4000c29fead28d2c41456149e8085a585a7987eae94d27fd0050c301da7262a432915b02c91456271052be850da052ba84c951c0c5552dd2a2e29ea47a1cc00084940c2105d24618515fcacf81f85c222830758698214a0d0a0084f5c8941fd4c0c797a6044142bb4c0e4f50fc42a28383822092944a1042ef8c7f46f031858e0678b1457086309415491c50b725084235eb005125842f8008b140844c10509b478a9c0083ac8224a9421a8c064052c7801152014094e4c38a009275842093818421255a860c21646208494524a29603efe1447909ad0993e9b18429e40081a26a8e28a294498011458780a200d8b2634a1e352d4efb5d0821cb55d5a5842d69a257091832368e1043d2862d5388c9deabac2e84295755d611ccd1bf8a854b0042030907022075380d2802d88a2f841124f68190285ca228a1ca86c3982082bb610644416cf62b148f099989ffedb000a24559ad0320250115c6042164f88f84901128ae0420411522c9607fc60092ac842062e2eac7041e5a75f8427b60a472062a2488a249a5471e2e708294d3401b40510b0f4e4e04089297494782541c78a19c4ced4b680051e37f104284d749c3cf1411195d5ee20082c28a83882111257acf8b7c4a88ae5ab2b8c292ada335b80bb40a89f8c8e23258e6a503d0c1254f8cd50fd7795850549e86942ca162656fe9453eca4faa338e5bf83532fd5dfc60141f6065688048450b52be99cca51631735da38827ef4a12b0b46a834aa3f34716ab759c56f9dad12418c1bc4bf83b14e8a11f8fdb25392b392bfa520b23bed6a36cd0ef933d511e4c386203f4026426c56cb16ed6bb978a71835838d0e14ff0eeeee6462e61d6c89ddc19c3333c78da61a5136ecb54692dc45d86bced1348d0477b0581a89599cfab6cb163169e7540ff293cc9ab5e64ac6afb79261b392f15f8d19646c1a89967e3f07ccc7bc36a4fecec29d1dffbc36fd98d2cb520fda49622d7d978080a04b972e433576473ccac1ca9da53217a4f4d2750f51c372f6c6bd1669950d4cd3df3ed8b381038fc4b3714170d89afe1b56c79459263bc8ca02876a7f7f6bdf0f593e3f7b8d29cbc77ce85a9adfac5a0972562188f19a73d833a77d89db3824b08b977a68a46f9246fa56890008192f045cc950c9e84e41627c8c4e095c8940c6c7f89c553faa39c80daba37f72386c8db63725ac8b9c0d5bd36d037b1d3b0f870e6850ef7c49b8fa3369776ee0514046272546b7a52031bad3aefabbd44352c8da9ae6536cd2ae2bfdd0497774f611c48686acbdd9da5880426d1c4c38d8eb2cec7cb0c7ff896098860b34fa3bb393a47d64f833c31e435485306633750c8704da4882d6a8ebc2821335d62d4152851c3fbc02856010e5e01574296dda7b85091a407168b66da651ce14d331313486b6c9b4850d7605dcc10a0ae2d407afa87c8581f415f0676e948353ba586db8a810540254c9ec4d12f686fefef73764fc500c380489d81a7e22b008085ba43eaaddb75da01c0cda1a6ef8a7de2c5b8b534df8a196161f5566ee85bc44508761e987d7207ecac54a847f074e1580e115508769b8053b4839ae366e403f1311e80787b4186159960cfbd47cc6985583f636ad8af1a55f2bf205e6b71352408506a87088a6428f63e8da80705543fbfcdbb4fba829497ce1bef764fc6743e6bf18d58cffb25a7ab82a1a5f2a3d8d199c0c27835b4e3ee44affc221698097b8e592b07fc376137bd0bd189d088088f142c0558c183f572d444a975363e4ac62fc6c1ab601fdca8d031aa3c7c6178ed5102b7bf7ecb9e7413813c01ac3e210167b081288e8e0f13b76dddd9d65bf599665998c09676bfcb353961539e2cf03a7369e04fc246152dd1d8baec7c14cd46fdb1b2ed62bdca0faebc0a902547fcd27b0375185f21d5be35e6de480fa0f69d59c1b58ff22fb45f50dc3d4aa97edfd5d1301102faf9562d7dc02125053240075863f1b185c5591a629e2382de793beaf3375b351b798ba65757bb8aa18bf6d1f83dba5217d0c4782e14893f4cd6d5fe2e6f7c675fc2dcde79ecf73766d92b942bfc8114fd235fea08453f3b90577f885dba669f662077f484c89b4dfd83372423a65339048ef836866a872a551f9718208e941e5d78155db05178bc51ac1ba8c822a3f2acb814525f56d91f30a2a2f1121b74d235292341287636b60eb57da2fabb49fbf714a3edc39c334d98e2f28c923751e7b9c7133f3833a7eb9124e4132d2d3b9fbd8cc3aec5b7345923135daccd5fc96cd2afba1f6cb55863d4e3787c4541b07ac7a00a63a56bb33e2b4e58300f34e02dda50bf74bec1b5bb9ecdc5aa0824c38bc1c54a0eb7a15d5cff41aaadf34e1f0729c82ccdf2a84592bed69abb2e79f1a6ccdd57c12c744089bd5fcd65c69118ad691709a94713cd4f8b9bbbb8c08a85b02b253a8db4139f92b59d70a04f34190a5dc9bfffdbb20d97b17815d651d4b8e34d0b65535bad082b5da1b1ac4a11f6a6dd29a33e2b4d4cfdce9477c7eee3ed819e13f75e7fd3935a7f6e9c7e7d4e6706023fdd388bf372cf1b78c43ee02ef6abbcb3c789779975b08389f5f6bcd5586ed66dd94dcf743ed561a49c2d565d4947b330fde8575ee4299b3dba6e95d02ed82aecb8ba2fa4d2f788884306b158d78d1f28275f28f8fbde44eded1b08a5dcb8651edf9afe263bf2dd32a621f39b92dba8a0f4b3460753b1ffacbea72adb98abf252002a8db4101417c1cf6eed285b74b975594400356b1dba659f6b6b9abdc605b9bbc49595e02b72ea6c60ed4fe41a31663cf6de376353921ae7570131081f0ea2708947efb21a56efb21fb3bb650247b94fd16d59c55ce8a3d761fd43826da538ec9f6dc0e3028923daa497ad70092869cdabb8ace958020a062afc94deb6f58fda7c64969d9dddd5e7ac93a1fb8627362327af747d78eb6ad9282fd6e15e209705f2190c562b1e0d27856fd03952727c5dddd7d484b0a129b6a2449d7f95ecd59fde84702bb544e7ec631c95e0829486c6a9236407ce64a35e464dcaebc6309888c2a716858fbb18c93e20363dd3de3cbec7c607f8c1d092bda72ca35081debe07a1108efef770e562d629c0f2cb1f7d681d6bdb1c7ee8ddda8f2b1caed33f6e9c7c23af906d8a57a27a10efeb81a81ff4988c9ae86af2cf706f26824904beddaa51fdeddd6f0c76f23b1ab61ab07b1da7a80eed009ac5446d25db854f88ca4bb7479e903c3ae078c75c0eef3973fbccf5f7e977050e2f18708620948a56fa8442c98ca9a5432442410000000026315000020140a068482f1804cd223dd0714000c7eac486a4a170985610e04318a216388210400001000011019a26d003f377ab5bb613e6773c7b7ad172515035068d34b4714ff6b36f4da4ef25b18a866ebec8c5ed5ae3710351334a1fa1a35d8d8b174c1cb07a3cddcf370caa0a4f18ca71e5e99be454e27a5bbd3b4d9de8b10fe01d9231f0e709c8260969bde5826edd42652b55b6554c5c67c4d7150b093502931c43656081818b8e4e561203835855b6ba3580ab49429c045171e75af299afe39e2b17855625631fe2efcfd492ec320efbd472ac454ac0d48a413ca6d17ac683863bc003292ece4136a8965134b23c12d9811a31c2595432145f489df68ed5a99f95d0190fd1c40642dfdc45792c4d0df4a89242691056950718080448f40d6cd63c0ea76833a10ac5466b185d102a1a4e7355839cb5ebea1d23bd392a94185e4cefe1e6720f9f29400cd831ec228dea517aaa5eaa7fd2751af49dde8252993126f0f9581b70a8d136fe121d0b971d95314e17ec59010fac9bb7099e2554d4da0ea396844a78753a088b156a13c35b0eccd07f5e52c5748f32479dc65943ed57b9e024b7540fa8ab3b360f07ad38f0b4be0d40889f2839aa56db96b347e05a1a45117722763bb0b7ee74d12c4ae40f590b045b8f6bde26caaf3b7f07379cd3891ed63cd382985e53184e3302c1af07ae6302ccb3d0162060a79104d50032e374709d3b2352aa940126a124d79d880190b85fad77cbe4de686e74b00e1da5deb53274074d1fa9f4eff58fe56dfe05ed1b5a4a9dc50998fa3b8bc46dca3da9ac6d4d5e4878a5ce0b424aedccf5ed6175071495bab00615c6cdcc45b2bc936a77e46aec17f6a6741608de59c18565aaddf8f9a98592c3e6b9420226b4510ed8b4a4a44a2c55822d87c10f7293f21a2a941620984b42b51d2a423d1325f18d98a3fb7ecfd3cf49b48e4b259d678e7efbc6f2b21d87ba3151f0b7b8d3b16a8fb5bd8a3de6f575b6f462d367ddb2d0d6d04dfa37731b791b020fce10a45e8bb872140ff05f1fe61eda5fdf7a7cab18fe24cfcadb5fd4971c8d79274cd9c019e3c3c79af8af682a2ea5bc6855f9267ffff6699908005ebd5250ce40f72aa81d73cf979c417a32fa676b7e582309174706aa7ba44be174219de7c7521a5fa8e07609ddd62350b69e245fcb8f7506f9c9f803b330f2be207b12576284b970715b935c7925b216a1fc0e73f6124472a400b158805e52178a5c404a1d65c916f563415684f90bf0fa7309fe49d7c9eba8e64cb5b9784778059d16543dd218e5fd94730d675b81bfe103fef107f1bf9ae58375e39248ecf4625eca9d31ecbc31dff20b894cbdf73e49911aebcb04e34fa05dd270cf385476397d9e7c91acb81c6fc04bfac104d93f05f0f053c667b47348c0523852215fd47c6913073ddc5efb8378a65e143b861c687e438d9cccb30a89f7072af4b2aa5199301ff23dba48c45f6ba662b9a912fbd49533e14de2da404e36389f052a431bf84983ee5c586e2c73345554642e1ca39a4d0220162bd7e23a0b0e6cf0b1f051b8b9c8c9c11da9a91742d3b660994fc5a78fc09fdf2354dc9570ca1f3a798d40b861bc03d4916845d32063d460b76ec4e5674535bb53b1631522eb46873e13419d20412c24cd02ea3b0be9780bb0dd7ad4a4146a9cf9bea45012b4c3d643de2613dab88ed250903698938a8acc6c28cd685da380aef9d36c0f5a0c1815936a268d3e41957e15804be307f9ab066f5d8ef7701c57a22b946c337bfc63cabd55d7e2f1fab31951d9d8aefaa2cc918f74e3519dfba80947184987f5f22e05321df6a8ba96ba6edbaed4521888cb65a410f893c97482b2ed783d89a103583856d1e6108849072e3036ef39f6e76fe4d3a4b6111fa7badec21f45caf9efc9fb8331219df48783a862be17c2466eba2d3679f8e1fa728e84812dcdeb1c4e0d023e7f32e1f0768fbabe1fc896184363d9f314006a945ff67042a23813cc6b53ffaaafeb0f26480e5458c774b0a493fc8f8ead0774708975ca0d9a4069fb21354fc8037cb3c8849c2fa42c7157b89286b2da1f412556e66475199af4e450d6169a8cd68e07a65ffdd4c2857be10f02df592be35e5275857609e02980a7cf32b77ef9dfee5e6ebc6e5c675d3edc675d3f5e6f5e675c3fdc6cd6eb8ff19b99fbff927c4c5f109072ff7473bdd16df1df5eb8de6a5ac0df4edb08ca5579d79dda5ffe12a1d1018cf8bbe172168e0a7bbed5e2abc6d54fe26a8529266ba3c2477e67a6045d4e2ab5ef98173209562a4e71023df1a619548448218499e589ed90975f1c6d5aa394337c2e32f214e547d71aeeef764a028aae6798cce850b3f839ccee4ec37a4be16eb11e22033e67587b863675e130318f1d1602a414f824075f8357b151b85f110723038cb02bd8b462c65d0621e7993f4635267c8ebc5068d3df1cbb41e9b848967f232ed17db6dec092ff3151bc2658e3dfad7841f8a1e56c5e60c4d19dc88b4054a00ece8b87ac0793ebc1000eec281cf8b27615aa01f25e3afd0b8f7ce03c7ef8dd00898ad28e62dcdb11b408e27de71f3321341d9a8deb9f2f2db0471ca006148ec19054ee8302e35d9378e072dfcfd44e840dc61fa2550b52af66a96937c8a8cc5fe737f43c1a2f6591404c2729e645b9487e70efa7ebd05be6287de044badf7666c4ed4feb51193f42bb9613f79e293a96b204aa383eb9f941bed3347eef13eb25af4434358111b3096092ed31eeb06334504a4edf4dbe4ba59e782fc56495355beb7c5d6d9440b7a6e9489855ecce444c882fbbde221ce00c275921c6f93d8ef43ae1d47815c49201edf2aa880b29edd166c1748b11354ac7b365c6a14749454893af162fa8b78e3ab25bbca0ad69dcf3c3ab34d3a48ae6b4bf76e97a6a982c4b8449ac10c405c6cf077dd54940365253560b7ee4c4b4475403c65d28c2c759c53d7097235521fe5f5a8e9429965a43a6f548f83ca70427f05c3a35bed4bf1b4a95da7265b2513beee10942f2ca95c8daa5c073e6030fcbb60f5cff4cb003d2df06405e2d2655def0b0a1a7243423c409eeeb5d22a0f4cd2714d87459b6095b510db21feb7b7b4715c9b615d43bfa65f89bf941b27c80b1f41b48611daa2b11bda36c5bf3224ed2a4ceb1a377d3129b3af4a492815c298e734fd96cd5c056a1ccdb93527c65e4d47f11ac058d550140ca90eb39517638fddaace56822f30768faae5237e1f55c3d08f3a6321bcb90f8a599caf72c97404484e985f8f28e3c813507c10d42ba427ab83cdb76e86856260ed86b23e5a13cfe74041450f6013d516cb0aea6afa82aae6d1ecd1cfaad36d8a85acf24419c50e63da3ec8ee934cc4dd000e177a4df31d369901ab7670dfbd494b27a16dc6c8e8d77f585cfbf0c45bc9c8dc86a4584bbc24c1385289310d0e0cad3efd8a1a862d8b06074838b6a9b52dab0827a2cb60646f16cb4323da13615342c9cbfc866646e513f53a5b4101d92eb63cc811e198a2b76b99d21454e32f02d8a06c56446e4f90783f0d72ad3ca1f7eeb8e6ccfc8361495f79a53e1077d316e7dd8758c5d582199002e666a8a5e5c64aec2e37384615abe9c52a8eb4e915455144c7949fa9b7a7e69d81b275aef28d32a69e4cd4bc11e196cbeec649eb0838cad87536708627419d5799538c0924276af4e10eb867dd6448fcece16ec464d78856b2b40a8b2cb5745685d2544bad13666b4230d19cb5ab9ce9bfabd2a3e7f6df82b02e744bd312434e1c26c68a79f5a8d9c56eeac28360985291f9b020d4a4f30fe1c1da0668eee333f91d4208996271b57e39e7ac239b24d92f61561ee335007e08b3ffb5be396983ea0ed3dfc4f24cf8d3cf8a8f4a662870e057ce0924d031c1ba98bdbbca5dbdb0478352e6478089d96adfb243cd5adb2af412ffa8e85a7cc60d3bf0ed0fed6ebb638efa405bb183551b35f27da1abf6d5864845270eb8c284e1510f8cd8a8212aa13780eca6db6a02a406ccb9c8b7322c2779828efa72238792930b88594e3f7b14bde0bb007708869dc115327065423b7671e35380a8476a70b0c4ac28dc9ae62469a98a29aa88658c1f1fb472ea88de83eee85c72b295165d8922a560e249541274f441cea02cb6b885664052e1022fe209759114609260e06bbe326c2e73e725dc281136b5cf0417abfb3451ae9d83dd52657041ad6e7f70efc4b97ca602623581181f991d06e3e3fa25803a78d12acef6caaf1607127900b7225dc81e81cbf566ab1d005735278e026e041f3d10613981aef008fc75bc277bf243cb94f7af030530431d817a0bf7132e252fb3b6c64c6b5ad53b29290a38096823f22a23961fa0289add9851277f335edb08e90ce950575c2178dff053d927456ddb8c2ef58e68148685b7c4a07a1e429f28a71df96ab8b68d294411dd33ff1137600ab2399b1a2b9a9901fbccaa97fe886263dd8b4ddc7115220416d52dae0c3c04f9388ecca5e76ad5c84d09f60cade4ef45443733f0bcc55e1cf9609b4ac71636e7afcd59e03c36a9e2fe72e2de3108d2f1b75e8556ccf2218a9739721968add49eade57e3ebc15328cd1332c0f0c0e3de997b853c3a6b134da6865fdc8867a7186017dd812a1cb9026f321e49c27f409b26906afb980c5d95ad2042394bb1db3a7a094b4ff2eab417998f6eb7ebbe7bae9369b7521b6c87cd9a666c302ba0e4deea8a6904a0fb8a9b8f920420871230a3a4eb5e4a9282855b3b15f7543117832439fc1e7aaa3166c2bc45db3877a3b80569c606268bf6327093146a0e035134298f03c8edd736412c2ddfea52cb484618163e0b47848662586d1f4e19a6d17da0a561a68af1cf04c62c8760cce06f41f09ea0496fc186c4512cb54d265344864f81c47d58c043438485835d31b1526da80b46a3bda075df8616580db1c5eb11d701de4f37e86a8f11eb472ce4ccedb8884f038e617ccc486b6b4b34d7f39670a0ec2a013ec0b99aee1c2d473e2b1922d5b8deeb2cb9b359ab4ffd1293e64702c53a927288d6ccf579387a48622df94ad0e4540b3c0a4b5007de1ddb3d64caa4bc2bd52c75c9f901a973a0a41850961e52616f6aa27e4cf08c205b20704fa84588c891be84226479e1ac6411715d6dac3c00855e37e8633963f0fc291608a95132c856caa8b9b5a6ec7d62a15ac6fb4fea54299475597523ae48917c093ca6ed24d70138c4e89a67e8df9bcc7b7998fcf8f6c32a4f1d5baeeb2ac984c382b70d3d1c0a154c5dc28d60c2adbc70a01881e7d67c980f2e336c783399058070b2209bd4afd09e929226780fd5baa2ef7ff464031b05824a8ac42e889e85d9e6212732f4c3f30d3d8701ab37ad231a655a1ae023728a303557d5e6e98b68bd7da95b7b15e9ffb6b4de2daade39c9abcbc619885ece1414cb595c3bb9132b41e142c87403adfa706f0daef98af834507c9c0628962ab657a01d6e07e5aa1365d2e248eb7cf47b0a5cb42cc2b3c32f91000102ed721b51bf1602e7cd3d5a4f6606c96b6f3aa1745aa0b1a8ec0285e46f0a01d91c846d82b83100475fa9ed12250e13641b51128e5a69e7e123fea07aeeadb0d1627483797071053b81628c8bb42d0f5bc20cba0c0df9e844854704a116c3714381c7478f64e1daf2838d8fe7fcbb629cbeb2c21c3e060d1d39baf60068327c9284575ec96a8401c4791cc48ca3df2c1a1b421645ace5c1f66139222dce0adad47cea0174ecc3a374aa3d1f2b5e3e8b48748d85e3a940d7352a805bd7c9591e549b6a6330f31d851531a3f2780ea1729b0bb145e920610369e00446da2a428912e1b8509118fb39029bde68cf02093b047533bdf8d114c2ccb956ea7a1b90b6d63684be84d37273b9f06e65553e8ef6db3d2bcdb4e2bfc126edbb54aba3015dbc3e4227b96648af98671ed6d286f375d6dce40cccd60d6b2c34f910ba84dd22aae682a23a4676b7e34f3f9e0f347301ff35be4b1808765246aee8d0193030570510c2d16471e8620985b52f82282c0422a2d603a3d387599e1c247324cd74b6ddc7ca1b1ecb890a1c857454728a2b0474d72a7bb5163fa5bce701076dda943d32e8bea413af0e13253de6aa3fe9ec13016f30b828bf3be7fbcffdf3b36ef11b2a2bf4c8f95fa36aec40f96d5ee2085578b8008530dc2dfe6fa573f21702dcb2686a64fef40160e8738c1810b2ad7c298d37eb0a3953985f0af10e08d9ae67041d50d800c38028a0e38c7b9aad57b24752a67db9f771b5a959570e2efc39c8f900a6290d66f26cab4d3bb540d069ddb31bbb7e96d10cadf7dfd9337b95f65e006f0dbc8f732293c510cb8530fcd93e15e81564ea390a80ac2876b75f24e3ffbc217dd7d05d35bdaa11bc619e7734082034c7c485d2d4041b9610eb221aed05182242cde059461c3c0b818d4a0c03c83bbf864ddfd745f9a937ed2936394287350b4ccac57c6601b65ecfdf0a81c70e6e285dbfa38b2c47654621f002f12c1b33a5d4e23b45da467eeb6578f86f6840cda781b221c3f18b97ee9a2dcfef2a9c7fe16ede06004d067436990090c2585ececc6ee86964e02dec5de68eb3c64cbd016e25746bc18c491188c1d2c7fa4115bb05d1c8285ca51bf7090fe68ac50090f3a140ae70d88afd2d085b8649d146923f1facaa537badbed807b80d662ba08b0b728dd9f2e1193419b1a45bc6bcbd2418c07ae1b037c0b225487c7db855d19d05c4dcbd3537765cba5410df7b2fd08e030a4ac17b207bbd4cb9dfd6baf0e3d31db8918784055bbc3bf077316ea1dbabf74c831e5f7518556550d3e372ac08b28d3082ac26f5b145face5a8253143239e8c214cee939005d1eaf77063aa72b7e1bb6f656eefa5ab54d451f953ed61f788461d8cd50279074d341d1691668feb7ca4858e9d6c907e4e2401c4dc29077416014ac9f1345935b8b59beea17ea7d924471b9efd8c03971ae0d9bd40c663029954e9068f3974208b688caac6b1e90db97312e01d8d8cb97073cce05fcf85ac5fcbe831613a58299f07d9869d42bb3c84f0f448a1d9ea9850a457d590353d8244d423ea95e07fb494530d5ffd538fa03b85484d939dc0d59238cbc4726f7139d3ab699bd6a0aa99ba728aa1226a4235e6c09f038e7d3d3d28ca16ce021a2fbfd85c90e66281bcd2124808e92d5f1f9d46baa3577d93e58ebda38671baa9a212651f8bfc72b61bfc85b80da9f4d6aed850fbe6512c3b1740236bac56463a497df357cace294ae31493ccf5915e9d6313048da035c809e8285a23b711052196a82eca830585de3fc564e83cc0a965dc1b25cc0432e1db47f6d89c0969505151d5bfdfaf4cf02382dec9ea91e409b6411a26b5940a1bafc3da0c14b974a5ae1ba534a5afd108e6341f8f4f86bbabd07300007223fefeef8543fbc207342018e957b88802d6ea17b398ac4cb5a05133f6eab839537f6b0c89133f3a114aecab88bb776e409612419f09dea656c16b5f4125a3cca32a2c0938707e3663d6b8b2c0a982598d9b4c8b30ac2fcf5a71f858c0f121e871a09f8b2b259ddb6219cee464432e93b2143d405bbb5f30dabfb20260b2bad0be4cd45edd8979dc1905b57f50652ccb16ebf50ce68ccacd66f35816138e06406d5a45cfd67d1ff2251c29fb378f11410dcfa5d39886f3d6d2e13838bbbec5936ad912c562945812d4a02926e50fe89ccc29001572fdd5a0bc87c1213e829ac54a8bd5e39294e71e696941c1678698f5c9eb4bbd0c5b3dd834b54bd629eba7d45808c983b1f6556570f3d2ddde0860306105f3ef18b52632fce3f7d8511cd4d129180758890113d29c1f46e3910a1b7fffc8478a7e15a0c8a86510861614af5a86c66240533f6e2f5e79a997bbc05bb70fe5c69bc5c0dbb1242b2018f684a596e6928496b605db8962f91ad2748132247db5855f27989e869b4781c37df347cef335cd61ce99393e9c78dbde19e55099cd48251574931e615d6af68fb1830e742b6141c7347d0fe89f03866d4665cec8e188333df6a8168510f52a929de4180b4dbe97d2302d4dc733d3d057a0d44200eaa8562a464d429005cb804e00d4154df4a7e7572cd8c56c6f35b860416d80f4b164945f3551ee171b0e922ee3dd3746e77432c27457e49c9baf2c463c4eaa5fc1b2e55e84142773acf6cceb70becd25377050d1901fe5dc4f82777651ca2671a0994457372a88617f47e896365fb7d0d2630ef70d250fd8ad269a1d1b8d615d45b0e7947953867fbb8f82093ecc07501ae06f0699fd383e57d23427fda748868d46831683a375a0f2d522b6bb988dd8e5693d693882cad11b7fe74397496c79b7cfacce01232020fa2b13ff0b4017aebb24b37d2cae2ba2c8680ad70684ccc01923369eb6c08b2ccf3050a85baee3b5c7657c13bcbe4df0720766037f093ea6f3a745f7031b3367d24ac6f694e34c533c47838623335f49ad5a5f7b85e1980283841adb9be0a4b930354214391e796c051b3523d11987dbf4ddcc3aab83bc818191c76278c5e9468d01a3e9c3e3d08526d9c53b39e550b80abe04bc2193989f124ca73b3e4d373c7672e2df8d45a78c85608d61c02f81bfde5afab9fb845ddfb173d0e7c7719f65b89e3864524ed14e54887da35e192aa967a49e8712947449ccd465497a1e3f8e5aa4a56122385703260e781dc0e123be0688fcd2b4f655523941ab242aad40c461dc386c160a8ccf39ecc017c52d19ed38d1772b5c70d74e2a53232b020d75c6a0bdcec0d2a7e97114ff66f49aa358b8ab63a4eb451d5230351e20ebcfe4c6470b9825caff1e82b2d7fed3771902c1fbeb6dc2840f31e93b7bb44c18ad3244d152521210510b6e3d507eabfcc8f1169b1c503631c8d29d95fcff95221dc492a06a5d8f55decefef653e219e8d2d9c655507fe0de978befb3b694ecd660db1927c5460fcfea93eab27cbcbee0521ccf339dacac74f9323b149e9e50eba2bbd4e6d6f21c54e2fb489236e3266a0b0db1f27171b70148cf8404324384b17013f43c45f32690e92d3f36d24a32aadf180d936ce5e22a2f52ee46923b44be6ef6fa2e25bc257dc63d0b5ea5d6b7f54ca1e233f12d5a6c3dfac15e865f0aaa33e5d4b3cd44b62b3621c592e312da4c3884468c861b7e9ef5cf32d613a888320b8d506ebd336f404b7532da2bf93ecef13402278d1ab0c3cc38e2faf85a00508c675fc1dda1d407af9c9c9f0cbd45db155167fddc2a4fc702f7dc45e1205759e50691d2bb31c9c6ee900b63ae15f43c32c99c3d7843606f724374933b22405aa81700fabec8d034a6453f305f68eecb3883a579d90febc625194b9a84034fb72a95386aaa662e6b2b1e6353d5e3416ce3ca67e7463845179c43f633199ac4404ccef2ad050cfc2b1c575b368e14d928a0759f34664f3d078e56b17a027f3a62e19cfca5bdc81e5eb0cd7f73aca6a40adc8c45fd6d3ccbdf78362ca2f9e8e2f07e39b571dadfe9b08a959def576d7d7036b8421d45ddf888e6a0cc384bc57126488d1ec6779d9469f4494e60d4467c2e12223a7108ec564e2925b9e8f5c91119c4715a42035b4291474eed855cb294d69f232029db6b03b1a1751d34b038dbf934eb81d46c078fcb474516e9e04e72c4979478a2fb8697715fb18114e0e0320c3bc5e79835ebee5fb3468706b1d93d96fa2a3373ac7e5e28182740483bf5918405222e13641475310e63ac60295206a11fa141d1014101a4fbd0565e8845760fe424294b458a66670dc144aceae853a3009b12770926346606fa01cfd1422cd536bd7a34849d6d331d7370dd0adc365ad720063013bbf393dbc3abab4986280821944fd81837a65850e83eeae9c9e52c5fe403eafebc83775203a14a3d0d351cd0f0b40f6a9bc8340168dec8e6aa8b588885f687a1758a2a894ee7f7e5125d522cbb5f0389764815f3da7467e0e88f3c03ec8d2e15bb01ff53e5fbc22fb4211fc0dbc0472aaa8cee97bafc190b33b3cd1ed1bb281cf6c19c94617c5f496752448656e1abf3e37c3946a25845c2efdc8efe6df563c24334103132586c1aa7627fd95707308f33a693e7849698804bc99b6b0874b5e8ef94414c3320406021455faed68f05376b6290dd288fd50227966070afad681cfbe241ac50155ba00279adfe2a6d842f14c6fe54115efdcd503a264cccfc7dc9095ae909fdbbff6bfb881679b9e3080c4aecffbfcc043d7c2d731a652f60737a209361500c02e0099f2e9befd39d5eabba98626baa854beca1d51cdde819368b67195d12a0c3e7f0bb7df218d6ec85ef0757e764b87d00674148dda870d6e5cbc4d1476b30890f74687b7f64378da481c37183347c4143b0daa1aa4c6cbe35a98d9d61175204c73603e6814dc6cd00953b0f10ce6a5b3cbbba2e703c9fa0879297458793d95ca1575e03ff265f9a9b569383dc5e94df14ba7204f34aa86e51c00df341bc3a8eb57df6b135f16b01bac7da6ca265af0e8ef8e53d2f69dd9cc1b84d0a1337a8cf0901768be4d5a23a82deff1a7f68866d9280d7df16417bfca2f30ce5b117c81c7a2b2cf789ef2372465e31a9524fa965c0b0a2da0c325d0f69720d25bd49038ab1cee418a6e2d4b8a129f9dae8d07e64adaf9cea2485f477f03d72609bb99b0ec600af2a1e0a11946d18910f495872d1fbe0e3090271c03a9e74095da90234ba249616044c3d1038eaa3183a8aa9a8c84d3b673c7d840476830e244d1b19e6fdbd6c05bcf336eafa2128072b3d97dd345061b144c4bbcdafb0b16db59a2bfab1b847ea40d183c1bd7730787bc1f91db15e7b758a966992baa6837608eed4519ba45a1b611564788fce29e0f88d5c11a26746c6ee0a3e3327efd8a31774adafed080d4a49f3b0164903009f97ab9e532770ad0cef7beffcb607216eea8026fb497d6a48e4c0d8706282abeacd1a58bd876d39e5ad8b54e78a5f28881af27653c3539fd06f6d0d66463bd557441440469332b0272793e782eff237c73575fab3a030bfa284526d95d89c1b9c041ff0b68113ba713e191e2a40aaed97bfe44420a1b0954480d8fd5db81aa31e003afc0c292984250ed599b2df105124eb7afef43045f617c544424b07f241d151509992b6f19c09479a5c92185165bc595bc5a2e02b72bdec2eaa386667911b5a54d2c1f9e75886ecda5cc1a5af1ff3203c863f98a20f342da55da62b01ebdc48c93d3cc5b72171df8fcb62b4bcdd533d90d493b07ad2428261984a075387f702adea3ff1446183ca1bd706d870f82d6fdb22c8ef905a7cf0d34ff57570eb262cfa2e6255c331b4028c215f1eef3de545e069eca52d5a3725b57091296aae4b1ffc87987b28b2e591d4717015e6319c4182193e4269ec515993a83aba7c21b935190e56dcbe33aec6d6b501384c590fd7524464c370043c2becd636f574dc18d26ae1c27909b428be71190859d94ba77d6d64151825280178286b0fefe7f6a5cd8af505d0ba29c05bea2556acb0fde62878136fbd99d26e290816ca8afa03477d6be4b0eb4112c5bb7dfdcf962828bee15ebce9ad9bd001771241604289db16074947eaa6201a308eeacb2bbb75360e4ce80c32a2d0de4028c00d3a9ac8af175a42bf564b87579c1cb37f065751b9e53e067a70683c34e02bb34b95736acf6b31605654b86810ee64512a751d395159f52bf1905d156b3ab709f68b94b976cdc737eb6d96d47b816e91230cc51ac78f7de223c70364cc2214a043d4fb9349d26e94309540078b1f1472bbae0e84893f523c31440cda29c352a067983868b1cc4935c0981a7674a89c48fd3daf4524b300b9956e54e9359ca7016a9f6c29b1fedbc99ede995b41617240a0c9ab53a72b7996533f3584d3e2c6a0011346173ec65ec01b23578c0c358c0fc7af9173b081689c5930ee4b510967c602eaa2fbc28f56e66a41854363cad7b2ae0984cfec4fbeb4e96e5d559956ffa7fb02cc8991dc870b08bb8606ad9455950e9406c6b592f9d210950fe58fdb8ac101cff9f60f0b60fdcdebbb473122ef8730552e1479f462dad68a993a7dd1981b21634bbcbe9294d41493c87ffe26c1344572f38ff84900666250402bbe0fc1e86ef30d48521463e973a42085da45dd18ed0c3d59a56446cfe06fb8d63941d79a62f20715800baacf3fb40c769d0a7f0c290dff1764538a7e37f65a996939fba1d841ba9b90abfcc65e757b9488d38f884421da665881021f1c4365f1291e56ef5ec76eeab0d204c54835f82642a13fde36fbb80ebecfe4c8eebb56b13906ed4dd22829b48a0f530b6824a262be77b9171bd08d8c7154f5c0e90fa4691d8fa696a7835020c1841c5d549679d3473b4f07d3774390e8fca31f8c8f30c524a5eff708ec4e0f53b3ac9da378119046f14693918ff3499cbea0e0ffa0d6dcc21857c905c558d5039e1d0505fc174d2faeed90a3d1a24a80c6b50919e7f5af72841c03bd895ea3ec4c84fe85075c307e239f915c38262b98d49612ac3aa4ed0f464480ad3789251b2131012e1bea8efce99b60d8bae984ecd3f6696a61a0541d275d643390998aca62a9789c04de27e5644fe52182ca337614e3961df5ede92c65cbf8a5c6c0cf4045c229310dbfe9ef8a3a42fa34abc7a606e7e69466f8486615ef139d4e257caf13eaf634f6188bd04c037736ea6a7bf783463a23f40802f43bde08c80528aa6422f4b869541100a3ad677c41d28185c86cc4c3b509dc01dd1581a1f09635c547ff7b3bf792cefe3d3104040fea7302438492345dde3ec9cb916a01891323a9c34f5b38ceabe47c1919752dc444711de5670f702142570227e161541c4d1a900e32fc6b09b4e1cf4082e676647ecf0404c70c575bb90384078b340a4f4ae76c5c1ba1088bd518c4fc14d8c32e0cbc65c0a2cee3fc6edd5ccf2228f93a93cd622082c2cd4ee614776d45c67021e28ee6ec77e8c437f85d923ec6375a8d3d1500eaa5d465d08b9eee0ce59b9191dee77e77c37695a57a546d13b9ccf42bc8fc5bbcd2f70952921c20694411858e0225ea94d7d86d48e046833cd401b72ce94a4415233dba5b983e06a662b69f21624abc613d80398ac2177667f56489b48fb45e93c95636dc8c465aab680f040ab0c773e10e05cb129703c1000b5fc0dfc4f32ba4be3d9c738a9d6a4086c0706ffc4d1ec08bb5f9fa7df24a84a3924857410f2cd3e9186b42a0010fc0545b644de56e9ba2f209d9b6f95b8eacddebc8e2dafc10acec466e455d186e27443134b6e27f591f096de8bf365756a7eb12743c33ef03531a8e950b450dbd170524385d6d2ac9469357be0fce6a3401f647c3dbe8e8343f13000f3b624ed16f704855cf53804dc2af0fe7210abd5319f1f6558d5ff40a4323601c95ad2661046d2ec327f660e6b69aedff825b90b6ab01f5354e03d653ebea6270688e20095c6492fb5ebb6268851e95f27e167b77b19797ddaca6518d7283ad3e5eaab8ca880cedc37f667688085d4a29c1d791e002d983b3c31a87ef5229d3083ded95ead402b78f716989e315619b641f11203eb49b1a451b00701383e382e080c84d2c675be7eaf48452879f88d7516ca04baf1d5fa54d610f9798cc7928de56bfaf705389d6ce4438a16b7848ebb9f6ab418369c51f72f5bc16e926628666dc63148df66686deaa9d45aa2f36e54f14538ce105ab35ac04c44567be15387643ff0fdb6cae0ce9c3a85ec15cbe485e0b4e96cb12de946be7319c3527b8cecc58fc1a92e687ca66005e87e91fa5622e978abf8f508316a6ca220445901048167d8e7bedf98347600101d1c120506002028eb1750ab4c2be3473d6e4d0aae7c2cef6f8577a200bb1e6554b26de6e965c2594d76ed3167b341c0c49b48946902a4d808b82e1a36af0594909891cec57a4486581e37c04c89daa2181de7104f1c8308604ddd1e029b747f9b7af0f228b8690cfc0c9b68c5a34fa7c4d078dd5577bdd7bf443d9cca7823fc1635b5c0fae65b621276768c030c35d9d073613591d5a7916ecf5af7cdc0897fb68432d9e93f25ed5318b86a6a0da8e4126a42a011be885f0bf5df2071412bfecef2b8cbdfa90bb7e2ae8702e85bfff1e732bf541dd2cde0f5e39f498b65be3b83933cd624465b722a4e52e3038d4efe310be15d4735ed0570be421de546880fff9d502ea49ddb98dec4eafee9c93fb1bfc77461fdc35fb034092401cf74a125015b6f09e52eed0138c4ecd49ee64380515da49957512dad8ee044eef060b7b1debc48358e596e49f3f82fb38179e4d3fb25cd99af884caab2f6764613ef18030df31784ceab1bb40e9382f4aa696f334f908a370ddc14b70d316e9ae93f82b81b2e66a5742bca027acc94c7f1290aaea9130b9ee841123dcfcf6abbe94fc70621c8af7e09ad8b3cd3258f3621e1622fbd99c661a8431754dd49f7dd1bcc8a40c81628f2c0df6e23fb897841dbc7b82c2ab877739ad4e43f7466a987c20c4a4bf0e286fd1a34c02194c46280ef30eace9cbc62dbb1c21be78599d20979a5d8a4ecb31b5e8125031da50d8b3e645fa062639bd1ba340b2e22fcf06ba4bb2c58424ded7fd7c35068966bc859fe48fbf1c2e770cb89de049fc393e913a487a0c228c6c66f564f60d4c248247a51912a1b023ee9e92d43b3900ace1e1ae30201bdc4f59a8bbe512220a65cab16b6e952e0f98c7750bec33a11b96bb3eab5b03b4e03f338748eb3482e665a6648a6349291d068c522be61da3689a75749551c5c9e82aca10e62b8f05bc3e2882d4a30edb55d8f1b3ffa6f2d10d26f52b9a38e590aaa0115e0992e3406e09270f03a3093b26d5a9feada1e7aabeeccf8fb128be603fd7313c5e9754fd074a6e533d6033b62625ab2cbe7f47642cc719654b3b96f7aef73402b1241399e6f87b63dd6fdad57aa28caa1fc35dc11d0f6abf552a2a6a12b9739d226eb8029cf4a6b57114104a492ad170fa75e807f24c175ecd20a31a3205e51248fbbf7907e1887f013634fc21fb051d6036cf8f6dc220ed39e8d3056beed5f593fa00357b9c17327d14c480683390b57888e0aef319fa56805642d9c58965676340629f1729becedb473059650264b1bf2d6e595e7eda059f595a7edef8792c9e5210ab5322f509ecc5037e759e8242496f119a56d6d7a6b28985d7c10e23dbbbf127227460fc439cd8e9c30874c80c2a1951c1a15259f12644173088300c6b80d8b840cf6bee1f2e0f98414674ee7f323538b29a5f10736aa39743978479579f40c5ef5832fa211e315415578162ec1cfcfc0ae524fbe754e3c15c56adcd7e1d1dcd0cef8b9b7f1278f56762068242138372c5edd1f883f9ec903a12333966a5c71f6293623f7a63648e5e3d7924569765104b58374ad2c2654158b1e94c3ee075fe40b20c52c3da7450fa7a3bbd2830652121f56299af10a5739b87328886e26d810334f30481695bdfdd04650eb668628b8a487a6f045d423a90ad6fbd112059bc812c9791f9c4cdc9d68cbb791b19af03cdb7756b30e9a0d7041b58e091507df2eb039052d947011eab0a72046e9c55bc1a45f708633ce5b5cac508e2018c9a512fd48891f21888094e6ad3ab2bc8cb4ae8c804e0ce9ffcd5004872d3045cce3680e2f676912562a57f6260778ff8100ed544454f6b672e27f5aa0505ba5a648c61c13eb16579c76fa986fd82e6cd63cdfa1606b17d2004f643ae1b0fe0b13f3eac65dfe906376bb02b7e11c17438ca458b01733af6915ee5b8690fcaff7c0af759dd167b599dfc6cf9aabbca6cdcace2a94deebbdfca5c11e917e64cf961cc8c29168c410eca9f3f24fe23cacf51cf6c8317f69c2601b90ca761b92f3e2c063f5df41f9390af587acb014f8a19714c8de40dc0b234c71b2c5a5b8382c0cd9cdd1fc6e1690d48cfbbd17d60e0f325ae3feab4f9b7ac016bad58e96687a308263be84c924c03d663617ef8b2f88ce62381935dbf9a5b275a3ccebc298080083cff471d819e23713fe105c30c203e9ff99ae052fdac47aa6ec924e5a53831a5fdce3251ee85824284e0b1f45676e5cca603a45405eb98fc64f4bf8003d736e869ccd99c66fb4f4b4ef1387e0a145d81eeb6acd91d7ef274b60470a04a32d43fe53f98c2475149ebb0a425a3e87d8a16d664e5073abd6ba43d7748f54757d592b8c4f857b51f9d692a5a1b4620da0d95a48ee1b2626b75cacdb17a34264f91aecdaf6ea5e1a2a381a92af3546c204fac86a40a2f01f606264f4067a902c4261e1af98007c2c46e3148784b0bcd7ec6176197d64b76e104d7236eb577cb88ff4a3f6530b4bd8583a5d100cd02db88c64b6f5a2a456284cd022cfcd2981e8c889eb54464ef1650c1b0ea803974e05d727bc01723ec65d5dbd2c64195bc2f02acfb62165ef97ebcca9295ff6a31bfb96ed8b9c43e70b0e29d3925d43d6d07c1d058398c570da909dd23be25bb2c02c1b9a7f1885a5d0f7bb13317c76a772e698369948411a38a4a8161d0dd8be8eefdb6314702ff4ca8ef02da3500cf1b399dd291d086f008708614a1a5dde41ed364206e13f15fb00e54e5e4fdd7580e5bb09b507c542b09e2f46a82d558e6c640df3164e51d9aac1864cc9b982d6c8057ae2d6fcc31ff22533ccbb95a2df33566e071a306158dc01efd77ca1ff1a7b661af5d15330f2ed55efb73d8008efe6dbea3c03743cf21e8f5a6b0828900370badcd8df6e51610c725378646c5fc300e886b22104343ea1de191b41a05796c850e3b59c32983dd6feabbff874253292c6e464e00442ee1586b0b9452002650b7e147c55ea8aa6ca6129ef227f7b6d7c13442e7b01f48d98c47870328d9f961d9bf49911861d935cb18ce5246b04e0f3058d33e46a9f28d6e156dd62b5c633440c8221d71a5c12bf5b6104acea94e69955e2119ddf65b9fc2ad3d5a0524ec4fd765ec97cb9003135ce31d674d239b0a16a59b8021dd91aa215589618ed0b2e3fe62c57f42d1ed600707b5b764abf009daf8ed9d54d458a4b7d91f51a8961229a4ac01d6cecc9d9c1e0125eec0f2ccd9bc400e3a6640a1683e8596760ae73bcd9469bcef912c2109f0675eca2438a8313e7d4c06e087d1f8a07d8f50fdb963142c75e43d59801921701004f08b3ebee574a90c92066232a037b9fdd097e325f300b6f1d6793961180ca73dc58d9d5654cee84d62027f215736c2462d8e8694ac8039633419c563e7e0eddff8c591c5bec7f5e34d809d010301a55c65272a1d50418c7ed0edd1886e49b62e8e0363bbc8fecbc987fd5bbe31d1805251d88514cdfbf7d1a113504bf64133bbd776d5af1fc3e7785ba58d4334fa0033e20b4ac56974ea8b35bb11b8de12a3f2af067742f0774b2cc99645e6e3ff048887514dae88af3aac78f2257a5c627134c40a14aa10f42c8183d01075a6e60ab94012e58a7164ef0d714ad96b27d0395a7d9df22af6c02461e475fb1c03aca0e515831ce2164b70877ea4f359036289e1c257963aa827eeafd8f1a184f3dbec109f7728a9f03fba01846a9d7121c5bf6957a00a3c95e2e3effc5cc96c214450a383cc00eebde608a37f6e52c53802b6820b1208622bb870204332df8cb425c0230bd3faa6792f514462c0554c96b39301f7bc884c07d0b2b0ded407a6dc6829202e89904385e4a6c2d0bf3550ddf5007e5d4a18311882362ac5792cbf12b0ebc6aa7a358f5b18a72cf9821d7f804ca206bc804a0ae734bc86573c67e1df8b8e5f119274766ce6e6ef295af6706876065bf6b7535256444c58445f70e80901587d9b333a6f9dbf1440173af9e1632565ad9bea741eff3896e64f17d73427b24bcdecc50b80fe31ba345c84fbc7b3d82722a46070faec51879c1e5fd0b854eea3e09e2c0bdb009a06b7d5188c9aa1f904185bb3bf1fe192b7eef5ef7791c7c081d986b0304e3f9d3fd173af2d3bc06736aa03f50ba787f0d497509a0cd7a16629853dc84a8c852cb0c9b1cd72112120e5ac57f339af7448fdfd003dd04ecbefb19fddf55778b869d191581182c84da051f55169196193cd91a6dfa028b2e6411c7309141222c2d61947e5c5230cd69df1391f3312bac67045c1adfa0caa9022f5cab051d5302e36541083110d2abd78e7e1abb6222e1d488dd89a20e75a1112c001c4fc9f8747afa88516463aa48bfbe606a219dc30cef0af2f8d90fbed0344806558cba64997210f779e9e45f2b080c3499416fbb3eac1a978318ed9f4fd34c6fc764a689177105047697acf52d93e8472a3190c6798901b36dadae0864eed48165ce57765d61272031a229d6605be903e38b3ab044730ca7f0bf28c182cb4d892ecfeba20ee72dd13d7bcefcf92a053ca91ffaf301a486ad92fb396cbdebfb3920864116a41a1d9dd27f5ebb53e4dc6a6fb4d656a6f4678b7197132046141040add4b2a135c027da490c22ce2ef5fef5d569ab94275f00b9be8a1055fd0055a19870788f1f37e61e9bc24c8b4f1852ec1a2c3e622a73ac3aa7fe57c0d2ae1bf372047d1dde0b6e46069d71113e34824b9bfd6d7206d706b0df6b389a17441954b45665bad78ec6e24a5d222daa406158ee49db4f3d964f8d7d908a132a805b65b5439f62f2a679c764b7260c2eaecec3a35019a0ad9fa4b35be9dd113fbd9e3858ea8f243c038bf283856a14642f6cba12abd3b9c7df3843e148522d2a5906b93e035ffadf996bdf6c648c45c84aafbada85302d3053b4c9ad653a57490ae578358f6bbea9901146417586e9a11e57cf050a4d574fe8087db4b98035aa6e4c84c416f19832c0737f5578829dfc64c38ce32768adb9c81cc655ac1d12e19d0d696da098f8cc2d10c66a7fb8b7004e71f91376f2f35230988784b8e911eba83ee469078ba79d1cd8097e472ecc0d5f233bb558e6dcb17509010f71c225cab060d5991004bad2014e0ff4eacae4337dc325acb80e63e9c5963b7ad6e52012ef07748b21b7b5be9eb79dc97fd8820c848c8ded194317ba556b9b58e364ef82b6986629f56d4ff65aae25e90e94271f0825996302c5cf2833cb825895c97431e5f417a483b8e7f3a61c3fabec64f8b0688be30a1588d2a337a16eca94e4228e6c5e608586cf6dd13345b408b2650a62073ea6e3038a6e0aece2bf6800513041f4073de9d53888969ae66142d5dfb7e2f0ad8a99607f2deb46db4a1dce01b311608e7562587d1adb9a1e9fcb326025410c629e0aa2148a5863d6a9c76b353c67c43ecb158ff0efaf596570614edba03f98e9f55c31daf8a7f4d8200defa06055e14337a8db6af16049fdd257aba5f76c54556489ad91466f800751c79af635c37aefd3e665d57c830805428f0ecf2fdb2caaaf55e2d8ccb7f96219c2fd5306d931771d8e206a761300e50a937782372767f7edff86c5adea81bb92b8128cd17c23313bca0dc00db8bae083496e215a0de67582f6f20ceb75dac2bb4afd98f2a6cf9dd780eebcd6fcb66203bacfc293944cf93aa72441c23df722f07c07161bebe940464f8a8fbe6f9e3cea2cc834b7c30236d3bd6db17479b14bf63b8442aba68ac1e2573b79536e6155aedfb17b82417273558261b97f9bbaa2a80ea96273f1e8df597d91df824ace3452377c5b96a48bc8b434e838d2d95ec218a35799e81833c067de69ac2836a0f57cb37dc07396ae0bebd0abbb1072bd0fb0a2c30b2d9155b304754b175cd6d94cb4b44758ebaa38dc8aaa5957a9dd012aabf7620fc425d05a30979ef357253b1227b89569b685eac691d45aea740f30701b341b51869572430f1a1c70df06150df65eacc14a1df08d446bd51ba730056b143b086031a500a2a0129211b4f45fc11b311d37b0d9e273fb6b3dbd0f0cea5d13b3a67e5856a51af7835de2fa32904366fad847229dd4af2a72d3ec12cef9e301d8ea34b18c6531867e4127fcacfe419240b427c662b6fbbf0a7b969abd2fe6484fd6da591b8e03cbe21dfae0809d6de5b0418966ea510503b8f3f3599050b6dd7e11924f429dc69b2d1f82d9305a7401e716fbfd53f8d51b0b7fd5f3ec53eb0fe8b76062ed9260a740626f8ccd3cbeb38d646dee589075d039ef7208f83e3373bcec38b05820c05fff0b681ef98a384bfb1b933198f2df981e8445729247cfe186dfc7c756683914e5df31296b162623309c2f86db977e1acb8904f8ef9e3f3665826c1c2ddb5c3411f8424d040afc982042b8845f734d2a7c0bebf0f0da25684e54bcaf4062271a080c3a9b81f601378c9ab74a3a22d8b902256efa8c48c3ad3dfdb295c5fbecf286eab755cb8be7f5205ec61f17bebf8464f817713224da978b44f8dcb9a7cf720626b2a47aa3360b91d712477ed3c61220e6029802a904ef16cba696206f455781a71bb73f0127bbd002670c9ccf1f132ce0a32be28fec58d7418c1f3c5b4b0681c3b73fca26077a88939f4960ee07721f1859e33f9b8b2191022883e724494588741e2fb858c1c823b34c20c2b1b96c609cb396d4ef7891870ac572c5d5c60e60f6709bd613e3809764109305f01adae46105e533602452572f8690f09e8ae449ceaa82386c90d62c22c5236a31aa3589c6ac38f864238ab0bee3a86dfd674db6281557a9e034975d3da35976d6a3a42cd87494821d9dd5ca0cec2753d1a45f6f2dbae438fd077500bd3268389c57793af96d834854b5f02124dca720def1c66d285c63d316ac4bd482bc492147c60720ca0de0c1f5bc4608679e1226a1150a717a45c66468b04714ef17f88ad968169a43c80d6c7cfb8f50dde222eb957e7ddfe5ab517e8d17bc5aa3426097ab67ad2b6cb2d5a56e70494673a465868277ba7d2d3f5654d180fbb6cf727fdadb85260e3383e183ab0db365dd8b661a2189df0a9f93c0fba98640f5699859212933ede180d2056bf8fde789aa1317020d220b5236fb351112c838d25f3bbc054a197e76e4addd2b0edaaa3ba1eae32a39aab52c1a40d04f85e281a69e5f24502deabc3862b004d6ef22c6ef0e97bc0b7d3abccec9f40517bcc06cce9e5e83ca14229120ba21b1e4672f6a60b53543adb6013fd60c89de7bf59622350b5e054260ba5eaf461269d222dbcdbb3b3b1ed46269929396bae63a0ed98db64a35791f5a307c308b242182d1f881335e7470f24145446a9d71065a02759047d439e714783dba45693f8f9de0e490b834bf861c68d9fea284f4cb9c523efe3f9393591e25ec9cfec12f8f37bc8e42dc3664678941ad2768c73927711dea2f42af9256617bd527f339c68c3f95bef715b442c3eef239e07525cc8e68d2590e6d4386b111b42ac60507b9c4048633a390c89ddd42feb4995b8bcc71d27acd4d4452d071d5f1a7327f51b86b8b0a747f1b46faf80789b81f4c323f67256603040095a34d6477f47a17098d4661829be1be804e3d69e95fd3a0a3fdc4457c76de7908b73dfcbd24cb5cf69041be019449a573b5d6a972b1dfaf351e4c20224e70323cde8986d509413cec84b017c7f3de1349a6b78da62b5389060f37d5aa4165bd11c7f6238de1c0c08deb6c760e108897a9e5a53f281e8863d1c4758dc0a15f3e3e927cafb6f5bdcafa920bb86924c119c44e1a482208278e222140bac402068280953cef01e60838b1da8898112ff941a4969c496309ccb300fcdc7ed72457a131f67886587344b15e2c0b8753faf395b432ac1bc82af3a2e0967c250532f0858fb4f6bba4479952b1795a2164a78fa9e3aec4203aa5131281faec613b4489109bfb9d1a6ae516af4f11953d65f3b1bc15f2d66a46ed473caa07e321e697b06483957a0f1aad5dee791b005fc57580cb4b4c0872f597d7d7b5ebe53b9c34983eacd97a18f83cfc5697f8f9d01fc280f2af023efbb1a795135a1fac6d66ae10285bfe7efa17d4911fa650b088dc6e393340ec6cbf9879649c27f59733638e4c1d260fdf808ec9762254e6cb8b66e2694933db23a8052f11526c75083f212ce0b63831f5ffd93acb91f9716d042706c4d6ce06350b72b341cfe54fb30721a97e5a2e203edbeaddc482a634efade976873fe04b93679877fa4e17e1ea812930c4a755365466517cd649e53f9c7dc99406f9687c1620636971419c85cc37539c80823c9c4472e50a7c5e95ec1bd725c02bc768d31154bd70da3b2e7447ae8b438c6387d772904551fb9ec94b8b4ff52c3d9403a6421f946e10197a016be6e401c531497ad9ac3317465d6dc5f676bdecf28a67b05380d5205f05dfcf1ea6bd031f704d3819bb332e8a57cf1d74d66c82470db235f66f96483ee3b6f80b4e6ec1709f26c59aebb28d54b757a3b5e7d4b4ac6f53f596c6d9f0deb1f2ef343311bbeaed4320f22837114d58880d05b250203abbda419731584f001d30e633aa6f8395c04e1597162a0c3ea572e58c5b423eac970b180fa25b770b9380c120bb37e7968ad5da8fb6a58bbe1ec88f368a628a8cee426274f5a20134d933d785697cb3b58859573addaaf3e0adbfe6d182c2aa56d8e52d0270c2b44813560bf3739ee468ebe74e1e8d910885dcc48504a661506fd7744fc0091f8f26d08893dc85b0fc8917e273f129350bc268ac897f1792c2752112c64d3f2c18b4e3fb38782c3354aa0b81689dd087d742895d23c98aa26727e61125f764f4b78d78a6a82c543ed5423d88e395c2ae7e279794d67a6159c0faa5017b0c4bc03d231d53cdfc3abe089dee5c4209fdcb93acd3c802bcda62170d47156adce77cdaec5b6801e07adfe56153b80325d3a37df6fef3bc623423daa63640d08dd15a8b7a2a4f4650951702fcb5307980ef1fa87d53d874931b0d4e7198907b9c0cd1fdf87a79111376184e88554b08143c091235197971cdce96407d2749fc271d286c752fa1981c2871cc049990f6eec5c51a657fcf4ee7862f578562df757d674f80edfa86a826d080405b1ee0246e60e978241a6db45ed6f59a90b82843820294e269e2ea0e6066345992c4a607641c1fb48eabf528b2cd2ed3897fe3be18992734cdf1a1b6a0497443bb6a7688f70e0b3220c5d0c73569e889976c70e0cfdd9111ab1829919d9b8a840e6435dcd7274238cb8689abdba6a0b6abde895655020f6f4d28f7edded0ceedb8d77c64b098026a210ab6a5261ee66b6d73a4369f8001ffcc67b25c6760fbd5a8bb3c4ad8f3f50b00a85ff61a8ec6e96ed2ca5aa537f9b115af98f82f46c15780f44a57409c69884bf8ddc48625c4376874eecaf928426e751d125a4db14af2737e227c98ba713f69909b2b46ff00cdd9c58e647d9b9e1762da520607e8b39baf9b707584ee277c55e51b8ea0e9cb039f2badc3b1acb230d6da22f8525f3054418711c66c69fcae312bc3808f098c17ed489c9861702ecbc2f9b9f4e48bea2914c8c158d1804b30df29af34522307832faf004ec720a0abcb5199a18ecb816e6fc5a14f5090a541d264cf9dfa83c31ead138ca5615b8b72d94b321feedefa56e8c9b4baa64c358a974f9af29134fd304b798243ceb3477b032f2c638f73260c9685623c57ac0b33c450821c78b6be2f9917ef5779b137c3a4e06c28f287d7645cce97c8891825c1d442cfc38af9b9b6e452c90a71f27b55388ff34a4f56b5c5c1cd69fbf19e7947e1aace7b60378e375ee65ddd702f986d9c24c19d1ad712026fe021b3e98b7254c64634364626b2ec625082138c9517441677e0568326eba12b939c04e473641e49c49eb2ecd72bc0cbfbe8aa3a11d91e2c32db1ab2d90dd16b001f8b4d9711d3f684124c6d9bc2a9dd885536613d295d11bc51ac7b2005a4d299a6952a903c06235b2f97dd6c4cb364d41e43bc3b445b4a2afd81b86b222729418a62806ee42af7b877b67f9a1b17ce61f9bc907f2be311b7d74f70f300b4ad76f08480d3b6aca0b48ca68a57a3930f54f438fd046ea82fbda6c6cc76a87ae52a03f006b2b75b0b6f4a832e53d496455c3a42dfa667a8d380a72e8a451812f4964b21b1a67afc228554a8488757a01523d59048a5e4a8b1ee4ef13a45aa4c98dc39ed77642bf094d45c41e83919ef5b28eb06e99e8cf76066173d4b6cf7fe5662d18a8afe4c1c26dea11e79857135d25878abc0a02ec408a62eda2d7a6f861226c9a99b3daaefb4fa3cd07b9199609fb27c0d1af0810bb335c28dda91425542db993dffb7e127725c5002cd793f33197fb82800a076178913b54359a44cf5328e149cde8c1576b83544eaabf341deb45f394da6230e0862a1c7c6c10bdc66358771b5a144dd2db223b595f8cc32bc79225034fc05dbaec1302904c7291091ea791793e7adb41ee4b22564198445745750b274751cfa052e18f550c0b4a60645c6cc6eb748a4836e3e75153787496ff230f6b5fc9abcca6a19ca4288c1185fd1f27988a21f8d1784b0766304d2da89be3781df4b8807fd735387a4299674a0c2e6e005fb953b5ff8b1f6715fe9c71404a246f1cb0f06e96c8897f271a17556c7ba0896568590e2aa41638317bedd969febc50112bfdff8744e43475d6974bdc6118a322f69fdd07493254c9fcb47dc2cf87d89a7cbb65e2d2399dab7667174d7fb21e427b39614e2963155f18b22ebd8ae875c2dcbd8e864b903c13a8d7d9e1fbe7b46df27820759b844fd5bf47cc3a5fe53d3d48a2cb344217916ece7e6c00cb847ed9918a887c5373da3ca224341025ebc9437b903de8bd84d977493f3125774bb0fc9f6fc94e7a4af7397f97d60356babb2939032bef769694d79c291356d658ce5441a67ea118ccafba69a4c8e100d730389aacf46a6a49023bcdd9aef4aecf81a3a0b9144cbe9cf3cedf8d8b4040c3f6dae8e44a38c7acf6e0582b709f90fe38186b3d82ff56b800867b1fc4102bce2ad5ffb856725b82f08a7974f2ac14e52f75422834f1c589c6177166ac552fea5623afd8587d512973a993551454aee3e0ca01b7d9a2ddf44b0a6b563ae335946e1c485a2f149787a4c6e410586f57b1952f53c75be5d87123a0a733d3ae465651a508f172ed5a1768c1cdf1470cd7f1b61fc76c67daff94c3dd33a5de821f08b6d128fc6e72ce24821ea945366e05f0cef469c064ae33bf48912f1b03ff9ea20a5e7d0f2adfdd48d5de69812a8a921ac48f7ff90b832819e2c472c82aa584360633c32185fe92257eed0161b2f8ac8d836c7153f6a51957760b35a1d6d665b58e35a1006dcb37450e0a6bc425ddd4cdb281d5254f991c0a193ae1c1824191b8fc29133cf0f180cc62b2bfee3718f875f909792f7c51c2a228945b5413e15d5515e721576889f942c97657382c29e6bf201584929f4fbd60cb1dedebdc87952e8b516c069a701a15e0bbfed1af9517de5e85ddce3281d07dc20f2a0a65411ca6d54412d5763e3f8443e889a7aa49a831bbfc9c0e84fb1f04feec07c50031c2fe91e2fb02ce28261508e48fcd42027aa03d27da747b3de5f95f95ec1439262726c9fb27a676d70e0ecdb2472570b1e70a40f853569be95cc57bab0d7e57d7a6904c749dd7955cb55d35113aa481be3b98effc3d662f7a595dcbe57cc00cd7abb613a31829756462e2a971b99e8283556ad0469a981590da86f1e14e0c7fef61ea00ddaad8441bcd631e9f68c3ad1e420d59d24d184a3c5e0d1bc3f230a4d24fd4430a2907aebcf28322a0cd9cb368e53a3420f121362dfc4843b190fa026f56f9f006978e6913fe3afa1ee2641ecfd52cb0f1f5f1fa68517d9a4567e0958255c28787f6eb294cc01dde4a82c7c3ddd2934ebde822a2cb0c1d0343a7e4cdbc638a9345a78f9380a96af9ac1131c6d3544e4e734cd33d4e08fe77fbc32638b88b7a61359c79462b131b4a53dc3be738de25aef21dce548b2b462125d6e6e167c409bcfadb1dd3e8fd937017be40b0e2bd58312b13438d9aa92e10148bb1b496aa41d818446d48a688b4fb65947977f6fcb0d6a113a053bbb363a85da5dc6f5f91d54db2892d977f8c2c2e404ed0d403c8d607a6035e915164a5b5ef8bd462a04f24073f528649bab27510f61af5669bd98ac7a15b3d706e751f217e4f4582f9cac9e42a310c3688226076f761f5fbf102f2c9dbe30b573340401dc9edcdd2b3a9459a00021959d441cd1f59b53b2ee7bd62e8382351bbe8b5e3f95e9e282bebe9c48f34a24fe377ca46f559c754e46746ae5a2ec32367eaacf56ea9360b92b31a647542b99da86c3c8a6065c24e5f9d1cac23d41b8810bd2a2ec55f4a52ac3cbe8de1d94916d392497c31c0767828883f938c090f18db508274a4e80d206f0aba97f07f6aaa59b062bfb7c7f5f0f9d381ceb4b9e54103be29ffb4efcd43d6a076e91e7ae3bb79e2b1bc512ae0e54648327e0e3f47d347f5113aec681dfc71800d151773c3aed31c03e45d7f9a7917f144531fb726acec0fa583849fa8d7c36a39a7deb0c8c150884985f402e50b7b384555610150b7afbdda9d5a46c19e6e79ec9e14c835e9685bca997141c73f701cce9d94f4bccd5cfa7e1f25a0411ac7311b08a6e10ab633782e8fc2799debb0454b5adc5dd77ad97aabd3eff8cb373ccf8032ba461549508d74f7062aad45ac7e2012a07bc91ce41a253bfe2463de0e5ea2ef32186121aa3c65fea6c667c55304f776c621c0d99189ab5bd761deaaa021c011a6681084af1e61ffa6420b2a3ba73675505e7428138d47706aaa4b630e1fcde009daea0a84895a12bc1123826f9d69d0f3670778a0320039bc1ba93a43658d41139fd7f296b863e6a6ddf6f5ab75bfe707f1d97eea9fae733268e17e8f703400169994590c6f2da7d55b7a446c4a59e50c62dfac0670f261a87374b894b405cb3623370e0530aaae36982f31f57abf2c2f5de9f0fe8c5d2a226aba51ff0d13108b4f8c9d421ba9030d66aec6d88b0226974e219ccdf3617568483af6fb37485170b6e3977841048aa70bf493fd4a17eab2a20c7d41f4960e91a5af8d09ca7da9a34b62bf95fccb07353915d15cdbb238f53d6dc1b61b6c0ac853ce216c172183d971ebd9e90f19c7ed23efd3e642e95f8488bb55c8d4ec472bff9a8fa8accb8cc5a586990efb70f0b3445113479dd6fd517aa15e36de2204ff7c181ac898bd7129c17017d91cd8f8c57d01cb4ae8995f7043474a9e4fc1062d7f4e8d0d95cadc1a2f4f44999c02d962e8357981ced71fe64968fe72486563b6e1d070aa5f523abca9f9f99ac43fcafaa452c72684492e40ba4e8a2cb3abc21013b23508c34565318e4aada1cf6a645ae0906edf030b6b4936460942c603f5da29ca358473e7d08d0d53c1b5f059559cfae579bc3ada96ccf0fd56cb7a0b921e408d3afdecdd7cf36ea83ab805ede9c4cb1bd30ada118e2828899050829e0fa9afd306d53f0b6b42edb20014e80183d73f9f7d06c69950d436f8cb1be43a12a95a7b1abb94090d790875e92232cb213b516b36f3d9d4a7cbe67d55568fd095c70db0a52b2e0e18dff623b24735d002dad91ad0368e0f6f1b3d4fa3b2384056301a34e6406359f9d408e308c5b70a75db5f395b479ca2b001a2a5104a5f89c922cd4d4482539ddf20d589023bb0f07c70bd3405521b4a86b12036283a768defc7ec32b08a7e63c09cf4316cb6c9e242c56a207c422f4c97cbe0edbc2d3be4c7d3951567afad7bce6a7b8b1f85468f97d63e6fa833944dd5d8d31fd0c5ffe6af822ac9245432b75deeb6296058345f3a8ee36fe42aaef01edd48c541099e3182e76d1f19df1003f14eb997708dc25a42f46fa300ae10edbf73b45f748f002400f93de9fef9fe92c03b48e9e8bb41e92c868328c78eeb53e92908dd85596f04da1383a7b3e6194763d55187e3220780a7a20fd501b31e5cd32052925640135cde8219955aff51ea08c5973acba08816c681569be9120c785e0ed54b9659a071a036ea6b21ef874512193d0141863673402613c2b0ea3c6161312f0007c08161607bd97bc404ddc522b42abdc1a8af10abce2eaa93465d1ca1adaeaeb3535e269beec6977ae59c76d0dd5d5668c92444c02b1f48dc356ae6ac0c9891f597710e8940217ddda28f1d8c815af094c48194d96dec3f397a460e1358919defcbe6b6011f7e04670e96e5928a9e08d2aa04a8852c9ee8faf5dfa748b9b063d7013ed7c8c911b3d8f8da1f3a62a87b4dcb4da545ef17931d8beea0bfc86dcc99160aa56ecd047b54248b2f00ad85d38d7f693afee7fbfa2128d018f99937dce7094db93f6bf0eaafb4d736bb8cbf5b7fb84efb93150127f16a30b353a6a9c3d9273ed790d90ca5cbbea36d80555c025cfa488e25eba5ae7d5c566d8540dcad41c80be280a3def80daccb8be3958f4e144d8a52290017864e4d2fbbeadb30200f88fda044e0c7438d8fde36cc56dab54264ce91140c7cb7ca2835e28748aebdabff0f0c4ff606b69fec88aa00b5dfdc10be5c37db87e3ffe8c858207429d37d1650772c9e7acb15bdc46af3353219b0919c792334a74a3ad9fbfa646340a8961037ab4f5ad1879aedf735e6d092c6b778021529a342acf125a44047eb094a8066fc2803844994b947123c1d4548a7804a9a642a1b99636574b67531884f4068d3f3e84d795368fb21399462c01f79ed4c89f24c58755c8423fd8b821a96257874ce559d8bf92e8f00309587029872e9a0ab108f846a386c245dcd1892b5c3dfd9a86ff085478f350d12b4f9f11b0bcca23d26af3c53ffafc6120f653cc441d675ca61e6384a09a7b75b347b24409633e3bce09011330ca3e3e0a04728a926910a36e0ea91c9b7340c50e2347270ddc2a8da6b816507f0657d532678004a8fc4b13abb08d28a85596f293346d9d8a42a5a70f662fc9cb07a6703bdb242fa139c2d861a9575a8f6207a10f8eee1d6a0d832fcb39f65461e7e734be83f1795bad95d21646a99a9fc5f0bb32d672780c27fdd0bcdd9404d343a7526291ce4238dd835790efd1429da4bf874964572044e403430fa9161dba21f70f703e2f635fe0c77c846b517fd2203039cdcbbf8ad13c6f3af43af3a8c92fc144ad1b4f8bf6a71c1ce9a238d256277e93aac5a4c0521a150bbb01a873c8907479f28d820a51639650d9c6cec2d0885d7724955b670a346725a04e2fb3b33082e55c3191ed6f823abfe671ee28921d919c6a1eb5b11aa0ce8346d739ff81fdf452ae2d74dedecc6bd93c5d0b45f94d5cd1eb40b15ea3a2524197f3ebd198520ab466c97b0231fe8629a8c273399e17e7a23a7bf9b76c281555653a07f986c72bfde7636d4616592a953a79afd01f01286a30f83049e489b395903b0fec128f9f3a074d986f9a4764350f0bcd0e54c6a3cf8b29778b775fa95b50f0af0034e1ca4ed93c4d3206e7541a01c134c62291125625a940b12a8e308e2fab3962764a80735cfc823d0d08aab4728565565d1fc6a205fcdd42228492f1c3a6f17cb0f6b5f9ca0674a8c592f11fc6a98a249a52c1e8f548cc28fbb73bb6324c895f862b2565a60b31ca61cd461f52b523193e27c598477e46d043e87012314e7234649958b8d3ea9fae87ec089af840f1304e061b65736c7d8809075585dad0d3699a7904aa1a3a8cc510ef7d5331ab1aba16bbd2d94d080887f8ce7d5d42f4816d70ed637486f5d00e654ffe14d52c3081021a8cfedc416ba75d7d6b2bc7111729e2409f4a03602f510ad65b33ebea2e98a54988e063a851ae3f3c0ff43b8136206132793b4a3ae32ad5b60358ea4fd3fe03e4746a945183085c6127e3c0b7ae06fc6c0b4bda553f12c9538a0f9f0fb86925a00000393c60b2c3b410cd4532ec09b069c89a819f95ae60af26ec66cb619c502b69188ff2c7ac883445008b909a77b81f97c20e5027487020e1100223817a1ffc7ce380412c0151956c1884e72bf1dba0202a6b25e2f2ee1c876f2f0296bf6a8f790fda128c9ce581e5401caecb39b0605606d6680cb2c05b33e8069569f9150d0ec2f9defae1fd30af0dd01e4ca75f829c81d6a29080cc9667985b76bfbc799a2fb65ce1a43382a9a512bc0c4185d562977ded362468a8be1e9a3fcefaa1f6f33374fecf3ea3e4150b0dc108e5335ad6e317f426e4055dee4b0414ba9e6df0f73f270e3701fbe2031cdf2927723840115c8a08aa8f7072f303bd4bc6554edbb2cedbc12036ce2866200434117ae83a45e59526342d1ead13981147d1b91dea622ef50c8d1f378b005fc06d5ee210b61f92c6267e9cf46efc41b55d37b800f2a619c2c2bc255052399650e2a5a8b1f2818fef77d1ade04b052d45295a6b561d92455efc1981e3260cc568f25a496a164013bf83ab9ba1b17df021668be876f9b61a194cdb92a731269fff309ee834e8563d5ebcde36b8f801bc300c2c1140cbdc12fc28e978889f12dc25299acbd37b8fca11effa843e0b72cf0b0cd474e678844ac4a41aa39b4d6a0d4f43a0bc05c4034134e14ac7444bbd301677a2b10837d4e24cd467058d47aa21cfec762bdb72794a0a2bc0143b90596fb4a0ef9e67afccb35bec6c54f25aaf8d4edf6d2d1e73f3cc18eddc61cfc89ba29bcd1f5aaaaae0952e79f042a58ad68628d515d2a6ed0e9b10e5a42736b3172dad43e4659fcdffe275750c9be96506a066e6d1d839c255446991457248e3597dda864b4f00176959fc0f3a7c6f1bcc672f2b0ccd4a5ccd46ecdb3b9a63a05a85beff49e8f88e98d3890349f92207d338e1337b34472e09a0b098cb43c9c9a034882dd34d893a364f65131a07a2f5da74f592834db75a9b3066030ec30f5f28ce8bf08af571ff4c47e8af12c6196c85b0975f88a3f089143e835b7a1879ec3443460d62b2b60915b88ba0791c6475388f47c398d026d3ab1ee8dce226203e9fb13820fec0fcb9b34f82e16e60a8efc192e0c381a1417306bce978eb8d05e958931c9aa533e8d0116f32e0cbe982b0e6fff77e19ed904fd2ecede624924e8bff0545de88763d4decadb6b07dd6115e9d402b999da3ce19a66317c02215914d15bd016d5cc13e674c27a7d0e2221cbbbccb0092031b42d52749212c297ecf6c4ee983f48ec0756a318aa168b934838d3c3324a0d2498ba3206a7cf8fe2dd13a9a5c29cff5c905caa9469760c80739a75f67f28cd02299e57f46b8623484cb00cbf6859a4298e5117f1cc41a61d1943199dbdeeeef45040870d5d3aa48763433f2370b0cd2a52c4bcf9440c55f3492e2e65f3914b0b380a655cdcd48d49608c64d981a47de3933385535bff5952d9376b4287d3ead35b307663c79d71bb4c007e68fa298c080ae047064b2f636c189278a4bb97784de29675ad1d8042baa85a23f810b601dab69161cf493108bcf7f6a1f67c397db571cdd169caec777dcdda34abfabf6ea70beacb82593c7f680a58f8a09e988f845616d2a7ea2a15bb166d04c1d32ded1ddc3be2562f6c252a7e49de157a2482a4ae92715ea1461a3a0159a990fcb344c37ff81bb0998100252ed801d8b98b067d53d11801145e7a6300e104ebe01405445f37ffe7856b66ce4ca3ef3838e0d6439df1a4e86fe767fd16fdceaa0d20c74fe91f3bcbfe7078fb3cbc7a67e61b6ebc8044cbe1a72fe2f7015aa71bc464cc078b630038a555d985dd73c2ad80e4ca6bd372829564690c857f14f3671d7f4737d7ff5367ec68198a1569f563859f5c60b7443980cc1eca7e4a0419a6ac875bfc598fb890cedf31f430ceb416d344b7eb007ce3eb9c8483faa824c7313b84269850ce88471b05838889064ca57c31ac480678f38f3d94c14718995aed836bbb72986dad40dfe4a65a24f2da03fc25042a6157a95a30d815271df1a9248c74b1cddde5f6e2a8e6ba65edffe82edb4892c5790a2d1d4de854167a7c90fa12714c2f6e57e904a07904ec884081213bc56c66429fcb8f0660d4baf7a451d822a6ced5476bdbdbb61142c82664ef2df70e5c0acb0a680aa44ffa19defe76eeb3fb7cd3cc96d0e74806897e589f475bd55a0e5b49cdfda846f490ca54327d6693e964f2ccd8fc99407307fcdc79d6a51e587b2f0af583e092252c968c138aa9a43de5db8ef2713f7db7441a556b62cd1699b3c3c3e289f9c9c0cf8f063210f343b2dcb5d7dc6f246b4ddb4d1876bf1dc65dfb4c4bebbad6b49efd997ebfd2533ab47387d96e4f43ae3b636283b4414afaf6c990be5dfbec35d35fdc4d18a6530fb3e75ef1ebbe8461a4dfbfb4f697d52c69c3dcc8eb42a6c8403991647cf6299ff6d3c71de5db6efabc97beeea34f74d2177a0cc5560aa9a5b4565219625c99819f1f0dc4fc34bd01249555faa6efe354865da67b718a9e647fffb2bfa7bfda4918b65d3b8cbbb5daed8772ed3bddf4dd8f3e12d54894462f5c6bb3c3eeed33fcd27ec23052ab6297e95dabb3528652ae7b9209d7d488ec4b58dbba90e53c91e88b2dda212d405ba347b38436cbd60c3404a1826290cc913b4a3eee337cde515fe8299fe9324a285f8a9e4895862fb65264a0f0f8087991a0eb22080a1a81b5f7a2503f088a404890b5f7a2503f08364a173af71a138a0a78b225148e3bd1c87daca9924028b3e2ca7cc7a23964b1240e6d3343160d59b8b39ee4b8d039cf74ef332d1b02b283cc7d1b727517322513e9d3208c865d349443287353dcce618ca57470efbec3fbe931e442691f289b18f787931a55ca956a94777fa1bc8b13e58d5f29efde694ff94ccbce16946b32a77be7beee9c0e1947f94bc651304cc71ed65d068ed3e217ca5330eca461da9dfbabe3ba9317fa4ab8a686f49ad125cd0dce4e689433495f6c09098a693d1283a7eba2eb64b4b16449cc85424ae1b6a3bed3657cde51bed2492926ce76295f6c914cae26ae26381abfcc0cfcfc6820b6b5f7a2502c6bea80f47bbbf79a93c52e53d7e1549f8ba6eddd5fdbbbf73b0cd3e14aef7e3acc2bbdf3be9d708aceacab190f8943dba474a4fcf457ca4f18a6d387999ef23a1d05c34a3fe118327dde4b5fca9aeea55ea5772fa5ec7778377d47f7d2a7c384e3f44a385aef761f8de109f974fd1095b1d8be96acb985bc6ca150e157dc0cdfe9a8cfbb8c0fe5a42fe5a510377a7767f8620bf5c5968c2fb648251cb345e6ecc470cc98a99a583af9f991959be2ce4cc167e0e74703ac19ca40cccf9c2d76ce392de5b6a3fcf49a947bdc86537d199f69e9b8489f993bbdfbebd47da615aeae1637660ec498e832f37c8fe862f366a79d38b4f1a7a35e8ee8d29fa7400c65202dba0615dcd3bd7337dd3ee52a1db61da58461a5a7749d0cda60eb64f8e55d0686a5600fc350f009cbe86497b1291ddcb7bf381c67865fa6973009d7d4dcd7885ee39d7bcde8db4bd36efbbad0c813dd98fb0394f568c86b8484db970fa515794e94cf3fe5d38ef2653f7ddb475fe9a42f65ea2ef26228555b285f6c9dbed81a7db165127db1d5c5cc16a984c7496318895df49d8c19dcf0ae061257240e3102c547d9f203b78a2bb3f10f6cc59597178016ae7c877e48d5f3105ba45b207ddaa7407888ad9a1aeddb6bb297de3855b18bf498ede767ccb46444bf9f8f3ab4dbbf2c86e9347e6537615809cb88601b96d191317dbbf6995608e6a923633ae9abd922c2291da56f4ff545b7f6a2dbcf6f2f7dbbd9e7bbd297ea974ebae84be9289df454e35709c716e9a2df7b114e354eed4891020f4ee06447e9262ca3f312bd84613a2f7bd161dab7ef109d034a20e1890b68685a220ccb6e2c7d243cfa745ca2675f4ad3b62f25c23535a1d774afd9aebdc67b0df71afbecb1b5512153356f3fee9345640ecd4d6cd9d8ca7e4cd5fca4204933b33922ba947cb8a23126ad953597559248b0649784e879a29914095aafb821cbdec8228d43959c9a9996906575958078c3ccf017a03daedce0862c1edd129b7559837af2451799c562b1589946a94a5c913ff97451e5345d4e1f0e47e8014fab93dee9935538a20a39ad6e7a13264f70c3049ed6e91fac04318b214a5a76dac027a8a5332f714a0753e9a6cf64d267f2fd4c1e7d268b3e93439fc9de6772f799cc7d266f9fc9f63359fb4cce3e93eb676025c88410d43abd0434c2a0a48582f279b1c13b1285bc8edbec923caf61f19dbea6b1a2a06058095a51a54b5d81e8ec0c51d5f79ec3b3c4a851176eec398305573ec7c7160c4fcc22c9933c9fa387667122cf4320bac4aca5663847573c4fb36f2f8e9117aeec9f79d3459530fee40cdbd0d4e8a4224a33d1a45f77c9bd7bd7494f7bd7c54945b4a3d402cf3ad194807dbdf172afe86691cda2edf6bbdb760f257a2612fde2f08a3ec2212a775ff82cbf98c3d71cb877b7a11ba125fab65f9fbd5a955dc3ae2de85a8c03bf3498612737ac91b3d7a89fcc3294837db5e1fd667827d477950f9c1bee8752da2c6fb3c84c4a8baff59e33cfc69ca2077308e62035d71f2f097349ccfdb03f06c495c9720016b0c1d9ad7883767b613030080c0b813568823c4f6337bc12e8e6d44bbbbdc5b058869686532e6d66a69544fbe254e510cab2932909d3030d59b68087c7933c234b18b178354b2a521b53ce796f63db51074dc3b012b4aad0c5709e74b13b635629cd689665d9a946c4cd88c0a17999aa3ee5d9999167a785fc08d260cd3020ec3c519329f6a6c5d120fd15375ce5db60f78c46c5aee675b16b25907a5601c306701557faddb11a675927c4aca43102945393b29364c9901b213e3f82c0accacab31764d5b07142d59a932787489aec5dcfc62c91d56ca12d1db4b59a2a6a69dcd16c6896987b88c151e4c5460e14ea07c1d5aa8bce15f3c1751dd73dc41479b19143ce6957d765a74a4619623e9a25b4425e2e986649913435437a88e9e1e57ac1bc62ae18ce1671b55a1d86c68ae58ae1e1a3c60b1874151b66a3507395a3596068ac58ae181e3e6abc445a83c62a7a34583456ab154d0b2abda71936d21ce3ca6b344b7871a81c7c71bd7ab061754f36a8fcde19570554a974028971a3125dfaa5db565cd7719dc3c5b259d1a811dba28cb83d344b687bd878c111f323082c87cb7a396c34d8e2613b4fd6060e1b2f3796090f2bba34901cf25324c80f2137384790c07a2889e1e1831557807e04f9010403e2e142175552dd16635d276d78c4f49034f2864804830fa0dcffd16d9ccc71bd9aa55fc0d249906149eece85b4d08dfad260a31a943e70ef1e72f7ded13a49f5fdd436a359c2f853eb411aed57346a744bf40250184836aec6f0a033e28a6cd06bf0d9edd7e125bc73ef66e806d71cf57c34d87fbd77ed5ea4d5d60d5f1be98ec6c566511207896788376cd886384f6933959ecbf203bd3c4d9d81d7f2b8c71c230bf7d07eebece779d693527ad6fbe6799e77ee0b676c0dddf02c8b7cb8bdf33e9abbedd3b66bdb69839a77db2cdbfb1e0e376c716c508b59d34e3ba44376972be64d8cdb03c73498fd93c2ed87f127025523ba1fe2533bc46bef3c731ce5c4167ba459ea61eaadbd17853aaa491399a4c12a711aacb7b3451e69547d9548ecb5a87b14f81588caac1ca27245e57aed8b1905ae583c3eb9c678882d98a9aa625ca9a4c1fa0ccb9d06eb27852ba7ac4d38ddc88356e220f105e20df3d3621c110a793ee4f2f4a1691c71a50c37bcf9d6e89621ded2eebdf7765d4a323379a770c3e8130407bda49138b4bc41d2c8badddd6dfae28692c607874facf206f99b53cd4906f2071d0653b001142d450a72761a5996652846e415dc20d3537a148b434343938216b5414f2fcd810e4c34410c5d48c10bc400a345a3c8947699524a293d856e509397b36cc615395469400e6da091c525648f61b42267437c51051429a8428f0a8030040539fb8a358427677f3122673d446021672f856e64b11e1c81232160082264fa1bba416f2290f4a8c8e189460e67e44c256736e42c873472868224c09cdda2a0276747d9e4ec6f96cca6670814e4ec2356a6032a9a082253c0135114ba9145a146a60f152144cf8c9cdd339249968e4e11c418999e531142103b310959258a205240809cdd460186abc5584d105a90f5d414c139ab91b32ccbb2b39aa53ebac45c4f2917997edacc467616bdb3e50718b2e8c1177e983831452b83226755d080660753f8020c9d24b4b22539ab020956e4ec349a45cb599665d96807379663de71b133bc78b4f79e5033ac56356dc990aa0448f361013da19e68925920b23062064dd505828b18f445391114041a9bae77b64414bc40664dd3b4d3207c11842733d35059eb398112596341ab5efba34bcc5a56c49156c53c8125485a1573682f0a8922f754837278df1141bd44aa3dab59560f20834bca91878b2105f0220603d0c2f572e4e12227c9ed72e4e12249017eae8c1c79b8904245836b73e4e1a209aa2cae28471e2ea4001ee16639f270d185ca095c7ca1251f01345531d397f6420a99e6d873040ec5e962086eec0102498e3d401891b721c71e1300753c59a2c0f86329cd33356dfd992ad9627dfac90a77d0a499a40b00f1939f34283f693a30834c1f76900ea52a903a48faf8045dc004364b08a50f95ed65593f6995fcfc3a2805d75adad59fd962ab882af92b9c3801284b2a04fdcc96d8720214647de28a9ca1226d00c1556356835658a10609e5809a856679ee8ba0661965f90ea743d22df2c80f67827086d70a2f69b4a60f92d84e93243f4e0421cb276450b384bd24cb870d463d92e5659d42969776489697960a4b45db6021b5f0e9427ee15333443eb1ca611fe138eb0364e346e39721cd626f6c911b7b636fec8dbdb145ec8dcdb13b56490ee993e5b9d08dc638ea14341acda6fefc6ca11b5d8f344bcda93b35a7e6d49c9cd9125b3b4a787c6aac8769433726860123ba00e0e7a626cf7469b2836c6caa1a871588c70d9ed9626353257fdb07757d5041d9e736e1b13e1ccd7dae3f3eb21e4e9ef8110426a483105d3c10ad903d309338c2c429ba45a6817e92a6c11a9a2136f2ed4474f1403d9039c693ec236e91a9cf159ee4e863852d72687d7cb6f8c9d6a759a2cf15ac681612901b7d8c009467c8910a3168b20c51a04f9329f6000dede08189533be489c31c62f6c147dc22630fc42d1a89db3c3434b9af10db014dde22d74f1291370dde1829522409ce9128c0901b89f5c9c9d9d949d248942cf16162254f83f230e878bea027490c1189e14e2a633c2af5f80331e4e9cdeed460ac8127db49170d368bc4a913d56205c6624e6c036e0f4d1557b493a8ea8709c832f718bd85c9823c51d060c8bd0a23f7971869b01f672a82368c1134ca7d181905d0184e40d9438382ecc51772a53396819a259c4672374b7711841c71c88a0fc137d8a780912e3418b97b8cdcd12587987f40c5157a525c918fb345c3332da94d19144a1c197b8a6e1cc24dbdb6db6f384efba55ef6da2dd6709099e7e35313ebe85686230eedad8844c99e55d65a7b061157625a637615bb553a5bc64e947a79adec342f917934935996c52aaa2a204a7e5bc7d1d99ce4a2260a81c82f9c91e3b3500e5e68c38d2cdbb8f285b44a9c5c6bf5116333c680c46b73688e0be53033e56e846883f15d77e3dab822bfd0bb9103cd368a3a11271289449b4824125991481365a24a4553d42229baa1b6654207e4071d6e9e77d103c921e6781e628b5ef2f85e1f8c18b7c7c3f80324440b1720413daae2251039595681849decb595fdf0ad842c71b89a715483a89924c6a03b73fc0cdda89a0dba33b4430fc9d95f6237118c394630d59d7b97b7cf005914c5851bc626303dccb709721afc4f13458a18094216326d5ae9875570c3991cbb71634ce9aa473a6822884c7118794ec793a2c65092572231dce77005529ce77972b4aa51524ca49208273b59d2c7fd7a5493dddaa4cf5e633f7ae314c52e92f6793fd39ad9b5bf320dc3742e86d98f0edb9e5d510e8943db68cfaea3bde257f6118689bceedc76ab3d7b66394fdbba5006d469419f3c49e10923908c138acd913ba3fbd347bafd52dda62fd5da97ea6c33b99ab89ac8202020a01e69f1ea2d5a8cdb0f59150810d00fb3abcdbad61ad2b9571d2ac35dfb0764ee7b9cd231bafd0eeeda65745edab9cbd88f2ea3f3e2aee154cb8c6e71aa239008e20f21d16566ae08398434811c01a55ca6d2f6d7e8dbeb372ed53cc4d6a8d5de0f201a6e0cd6de8b42fd2048234392439b5d9f382f456688916232625e4cdadc948e3a9df4a13ce513ddfb42efbe7bee1b75cacb6c9145705c1966ad563a622cc678d0e18a2b73ce3963766b310f40329c6aaef4d36b4c4739c5a9c62e6edbba14ae54ea300ce5a6c34e2f95bafba5fa5d37a58d597d712a7b37c2a90c7bdfb68bbe947d77d1435f4a4777d15316efe82e4aedf01ebaf7d04cce707fb40c10a098a67c3af528dd63cb7b6c99be542d7da98a5d35f735a353981e427e4423576c47644457935db6b86e12ebdee91bef26f49d6cdf7491c6e99c50ece726d6a46f6c6868fa2674d15fba6f668ba88814e130fe28d13e9acf4b17d1377dd32de822fa265e30ff00a4def8a1e254db7881f911a45b9a8633591c25695347afe17eba992dd2364e1ee10d631a2c5bd97723d8ef5354f58c1b0dbbb6bee924ba88d73c681cb412d987fb4b7bf387c3b099968c4d9574d2431a049aaf466822cd32b3226da459ea90fbc596bc29822377244f8e92fbc956119c9c1d253cd927fbaa0d5da4735a49fb344eef74ac6fa66aaebc99ac4b4328994699151de5d34a1fe9a65166b78cc7498c931f4052020102fac1b4b5e8f735da4975db4aa34fd1b7bf441bd660573412e1687d709234362938397207f5d99f3eed29a3934a753b95eeddecb9d7681fdd6ddb4efabc3d75ff6a3761d8e81a9631c9a4bc64b5cba0dc3ebc2b199d17eaf73232ce7df4c9a07e9ff2c1745e28144ee9189d7baabffdde6ff81e86721998fb08cbe8bc4cdf2e83f2d139946f9fe9a3efd6c3d88f0ea37dbbbd8ed1ada69d3e9dfa6ef4d2b7d2a8f4eda4a75edb49df768c4678c7f6d14730edf7b1451a7da5ed2361ea1a5121d2a6484e698676e7822eaa507ab984d2f7982d324e23bd07572cd4c75dc6d73de543f9fd4cb75faa6ea72f551f4a652f7d4b94528a83524a51534529a594d24829a5946625d26b45a174ef122551fa55a9547abfd47587a19ca3b4eb3eee4b357e7ffb4c4bd3629a45667a1e37d248b7a4f8a08f4fa296b58bde94cefd553a87613a5dc2a97afcb2ef308c746b755cf7289f8c8ecc09a7babbe99321754f55d2b9a75ea47327e1780ea72ae621b62eaee941efd18be8b72e34a254f260a193fd05bd7c119a3c67509e4ea69db13c451867f4893e7724d752c293a74f9e9e2c325b66d054cdd9c4499e4524cedca1b8831a6806379c3bdbbc22a88a08d4134e27363c441e160fe6cedc993d789e3ca078eecc9daec1bd84b7c84ee835dc453b9ab5386c9a5463978753edfdb5ddc31b27c21268aa66ccf327679b2d5e1325b365eecc6edaed93adb933adbd77a7795a49fb28992df701dbc3f6699eed8bad0edd706defe6ce74622a993ff38a3c67153dd349c7162d21ea20a01db225c2b09956037dd3e666e27411ee9c4c9a25ec2e0228dfe91b9df4d7e824f0de843f55364e95bc992d323af332a48f2e53fabd7d0da574903efa8bf4d1c4afd22fbe53652f7bfec754d1897f78915e3a0fb1d50316838148b8f4b9a68a9ef48d7e1fc6d8c453c695b8321fa76af4f5177a5c0e1b4702149863e4d0b756a29d3ec66791a9768d6aef6f9f0f2dea6f13e1d40ea2cbe720c2b1c14dbe747fa16f5fe8338481b293af8b741291652ad14a9c14913c3eb14ce9c4892bf41d96390dd27358e2b492cdbbb36b1a4c20bbbbbbbbbbbbbbbbbb5b7677db8bea6f49109bab17128936d971b6a6e6f6a5e67cd7ddc3b0d0bbc33ce9356d7920b0d57bf7852ee2ecf62d13e14f76dc66cb4099306bad5ad4773f0d6e91ab3671e85e8e07d4a9cce0662fd246fa488ce6c5f5b1cae10766c255eede5de6ccda7de2d05ed4bb87e04d7409e52ac5edbcc3f4a13bfbf61cb2778dc3794e6a9f30dfbcac974344962e2282f5b58b7c98d9625bf5ffc47d1357ea43f76e441209e170fbdcae3d9cf7f09d1f6afbdceec3fca9c13aab7d2825f76aad943951552f71c87da806ebbb5a67d7dd13964570779106ebb57ac366a23613b9be999038f4ebb970c32ec2a2d22c757ece39a7176b6863c53ef44e280a1ac11f4184fc90427e48ef2fed7a892ef337c796ee947b188f9029f707704b8cce7d36b6157af7d508c16a856e635ba38732485ba0f7d116987d555f57347c61dc9adac18633d9e2983f32dcdac9195b52d1129ee8a1bf44384e5508a7ec438fadd7a74afb08a76ce83b460f61191d9911aeb161b5ba0b615bdc778444b8665e08dbea1e5b1e76bd7c39aae85b3542dc568c090863121610862203c25845a6bfb305e647e2e6a1b75fa4d69334190aee0bee15fd35bae8f222d14f18069b91f2a5d24f1806bb33a4df4b895ff7270c83cdd819aead10dd4dd3f8c1c34402cd1a1bdc340dd2110eae8ca3340b77da9d5e2a992d293ccdc2a45942a740d1a5e485c8f2d2a01514658a61d1448b08441095f2c596e94727c5ad398c403274e3be93e2663f82d450c4ed3cc369787dd83499c62f9a88c211b01006115f006ad1d3f0499b4f0060dc27cd92d96878862f5632dc50dad8d4e98cd3194e25ce8c2fa5fda92fa5e11f5ea9cf380fb13503c32e4e3db66678f8149ef1451a3ec3e50c34c8af741a3e199fe1433d86487fe9b8a21df5c9f8a44dabe869fb1c9146a8cffd2493d0279334d8207d0acae8933c9fb459c2f45367c212d0b27c04a81722486f634ec99ccc268925d08659ab06c15879f566d4dd4d273fa7cc892bf311cb9d06230f8e1fd927cbf679992371f0e28a892d8748225922f9b6772a49dcbef797e80244e6eef3f9cbd9261a8d46229168f4eddaa7e592c9f4fb1da5d26fe932285f4afef4d8227d2989533a4847f98efb139639fd1e05cbe8bc4ef85e86f4d8eab24fcbda2512890359d3aaf921448ca08efcf8ec072b7d2fae982fb644210dcb1c9b371ddc0a06d7b605940830b8f9b1a146534a299594de076aada596524a297e5981ac06a72b8c44c861244296407f3ed62c210ba6074ae99663cf12262cb511e7ce6740e858a64db46850cb6d93a495f4cee5473b3f3932269bcc16dba26f9be8a265fa1e22b2c03448b7e89698a9a27247f2cc16ee07faa2696a32c5400fc9b49443885e8a6bf397d2d1ede84ec23542bca5935d473bf7fbd40eeef7dc2fae21fdbef4e9a476e8db374ee9307bc2589283344e499be812caf47208a594def9d5a01bf64e2b992da553d3e99ba777327d67fa52db675aa52fb5e11f5ea59bce436c99300cc433ad9055c23a1d82a6aff4d100c38d2daed59deb1b7a243649a64b32bdc1992dd6de4b7f3fd27782d11830cdd243440cf490fb23b5904d1aa4177d92678786505bb48a9e760dcd939f2f32a5b28b4cfbd3b2b599f4c556e672b0b971dbc4151ddc8a5d158cdb8f65ddc5baa8427ff821c8cdf24066196872109bb3937e1fca941ea477c1348823bad8d3eb105950f4acc8928a200522c7a0adfb81c082e0e689551ae4bed1bd4f14fa40d64f4d5cee5c7d78999abfd3fb8bbbf7790fc374267e79bf1816a7273bcf4fcb327423a523f4d15f211ca76a845f9faaec33845753458f23aed07bdfa78abefb5834a54304b79eabe1a6f1a776bdc110c4f0931590e310ba004556a7bc46e965f6507ec391f6a53c8739eece7338dc3ecbc50064798e468a71acb8707978f4f07129b970c3f81379785cba1379e4b51c7ba0c8228777053702653f6861cd29d2826cf1405bf52f1d2324bac8cf8b20f6704d5304371408892b93155d4279561c2382084ed7d67329ae5fccb985161db1d08105686a9db3d67a591bcc76b3a19da15b35cb74e9af441c62bc2114b78883a4efc6e100b27c78036a0000c061cdf2214aeeb8d26f2d6e189b8820e24043ded0ef5f20b6b060a20b6509c30b510b395221776c61916608bfdc8f724e29e7074a1c06106f90af3400904359664ef89899899cac3bf372f6f8653bcc07913ae8e2bbb3ac3d309bc956cd128a38ac5bc5f2f7e44597ea925db6962d146e189b6020ba3421cbb3a6aa87980b441c5e30d1a5de47f2842c1044e6791f335319ca81c6184331c31ffdece7810dc6b366cbed97c4a1c61be2258f96e3c3d8042666d0c5568d7c8c57e9d67b6096090b2cbfb3de5213216ffb96e60909db431ac9939ab860e931a3138a438778c3fcc42c23b05e592fb86174d2835a7befa39356d95a71443de8bad8639ece5833ed2079c64fd3deefb2ac434a2badd92773d5c1cbd9bbfe428d6adaa95671d75f988591ce9a793674233e76ee7e16c3997bebf8b067e7b066cba4b392529a6913d3ec21a594ee44eb7516636a06a81c1f67a593d619638b26a594dad43cdb2c61bc8c93ce39e7ec8e0b5aa0546a418a49062d68215a21597e5a1a032dcc89238ef25352e9a493bdd560c5799b25c6497134d88f361a6c1afd15adda0fae0d2058b38cc2e0aa3cd3ea0ceaa4d32c3dddebd9d922a3aa3e461a7d5e5ec6b85e965846907a1bb789626b53cc0210714111d900446e28c71e17d428e17239f6204186fc9ba172d68344177296654560308a9ba98aa018ee28c71e247e4023dc9b630f1231247cae82044e0ebfaa2790544a2ae795d3e6d974d22a86541837a4d9569b9d5a39a2b2fa7431c618230ebf1c278d94ce397bce49bb637777ce6194360a3244dd21ae7cc7d67ec0dcb4717ee243b905503f8632a8e7082472d8345dd3437213c91da3a1a109a29b8432890872b408430c2ab0410f6243c8a1f4d9410e654c62d1319913ebe8a40540c8a28e4f72d7cc9c808830960862084bc08101931cca224608914389238fe48eabd9e24310648083253b4fb65085560b213721db196b42104f8c410b1e317e5a3d45ee5f9cece54682152a4600c1003f99126122532264c8f43674838239461c0c90b96ba11b5c4619b182fce5ed59e8c6c6534365468352e786a8972b9ab42afeb42a66ea04045a5d11c4a26955cc4ab069d56c41a1622d6092eded9b25a4ff28fd8cdb90e2db9843546e154185ac69a7a59f9c653c4520e9236ed8b19f1690868606081e1a1a3b5bfae7e7a68b4c234f27a42e58790576e1ca10e544e264c9495114f6ca1863dd682127e0450d4ce0042a827002103d4e94411453f8a6894c4f8375a2072567286591e98d0d5880844c8f0adda07726891b90a10c487821451450340f144540913296b08230327d4a8480154f38a2022504f2618cc1586b2d8ceb158b7fb960228d1a8df1f33dc4263e31c618e3a682ac09b3091fa8481423dcb630ae9cc748f923251631461a638cf81469157725c4adb13d7b26d1c959653fac92892b1fb1684a29a594524a69a59452dab19e2554fc4049cc162bd151634716d4fd4e344b89c32c561da02eaaa88c9260439e3f9805716b27133058b58cb3e7258e9f0ae8c5157a0a647f5149bce8327f85ae4f01f0b933f245e9e0cab7066ed7168951d4663c7d7170e5e365164839f6576badb931ea92727833496679f9cc8594e76d6c91a48c73e5a3ca6c25745165e200274f7ff28bbbcad6de3b43301603820f9cb83b477cea1463c874708489c9cf33450bace8eea69466b5577727c623d6c3478cbe6009cd6685c4b559ce9d314615f4102aa707c6cda1bd3f3589631aeca15dbb0f7d1e0dcec693ca39e59453ce4b1cf6889973db68f3e83135ec9a314d9afcc4f468c2c34793263051a820df0cd44595f06e3d37dec881916e8903144f1cb9993f11078e1c4bf29c3c730a2249b6b879d6501a2bf21444bac8da125411f2c431ada5d1e012f6abd962f1276d7b848a1f1b6a4829aba4524ada5236c1d3b35b543ac618e3a3c4df887059f3190d54661744e4f4e2662339311be4a9a494524a29b5d6524b29a594ca15488f70795e6ce0c841a9b53725c79e2558b841ac0875b670349e4e94c3a756d5c7305cfad076f40341672b23cfa3c41b503d327dd5e8274a5c49a963a044707e86700399be5a8066fb6dc3a7b8321fa2783ee4792e8227587e11661553d639e794724e29a7aca11b92ce1ccdd2b3e79c731291854b26aba856c898f4913e32267d60af4019963e71a51e003cb8dbedb577f64bedc09c2af53a890a52b841196a603ae11236b265bfa38629705a4038a086a755821aa6c069592c839aaa6aa7aad630058e9d42078253e5801a9e96c51f0d2f50997d5631a2a422d8cf6c1ad44595b0d264da302b1aa48ff93448e792e812338d5d6be9ec209a0eea201a1e164b071949b89904b61dce9b36d3264ecec4f521d833dbac26b38c7e4183324c812850a6ef9d69edbdf3872aa1c8d4c79b3eddf4e927dd12c21d9b35dd90da759ef499dc073355f4351c71a1d8bcf8dae17ed238b62a9c357348a6dd1de4136b42df1d343b1ac213e7389c4428928963278e3671b28933a5c8f491dad4bad32db932ff40e9251d22aae88bc834894b95c8f41587a84c73ba90e589a94d5ca19f5c1177d263267786120a9c4c3f71664b131155f447e4c4c0064ada2674a3876472c68c8a26964bdac6da7b51a89634422a8de905b4b9276eaf4212e85ca3cf3d71b5980b26ba94327d0f99e6ba7c0d5fdc17fbe1c81eb7e87dc3dd8e6e7b5703127706ad794c00bd10b6c686d5daf01755f442d8d60d2b35e4edadc4a79095b8c6d6c5c5f71e0d43bd6733e4a641b06d6e6ca88fe842730fd17ac81c92da214b452a71673645245132809be8c283835c95c835895cdb87c329b9e18dc3323aafd0b9cb78dfbe830b9df3bc875216bf369cb2dfb8879e7a710f9debcec9fa1a90b832f4e1a896082f727df7b54d6f32c8620964a3c4460229b119722381b61c1b4cd5b7cd270c44a14cf631f10a64693736077da4bb69cfee96565861450e591a62d20994859f5a4317eeaa4392c644972fd3f7882e2497d9b6e3fadb2605e9690d5f5c170ee2dae7a05fb1bacf292ffa4ccb1b8930ec7e34d36a199dd7fde832a48b2e733fa29d6374993f19882afa3801c0832b3ae9323342291da48bbee37ea44374d277884ec2309d1dd20a3be0e2062fa0a16991f09daa968e9c4662cb0be11da593b08c8e8ce8252c33ba5f6c75a21cd1a553de67b28747df2a4774e94c65e69eb85a5c999d3d9c23aed05beef3eea98b9b61f0ab22973ff50b72460e929d9c06737696d41c6a9454a0c9a1a4024dd67a882e33d3675996512a8f9044642a83c840f33386259206692c94e1304621d37758823c710f394d7bd29eb427fd86855bf151e04fd735721a046bf491adfa4e466a73844597104e7419407499b9be91ecc0a20bcd0258c2cd00b073654ed114c52eeedeeb87705fe37c9d33557513e3f60d8e146e289db48d932adae64b7243e9244ea18323b9737de7c4313861840b4b2801c517805a7d443a914e52605c0d770e1ec2adb48b066be3cc961cefa1eff01ec232ddb94e86bb875316df06e736c5d5b004c2d24983f5b80877c2d480c455d5fa6ef694dd54442986c595fa9211892ab5b6760521503124f4840655e8a20a08be0630dc50f2f0742f3ca28b448991690c43a6d18b4c1faf90e9398d4a9ed992b2b4ebcedd5afce2be61d8760ea7ec630bc6bdc3298b6d8b6e53dc8aa5120de322dc50eeb4f66672b51c2fb9d76aaa68c6e4d21c99dc28a5ec2ad2308fb8428f852b314824d2189dc2912194083231b129c1e0081f538e3d4b96a084114422a571efbdac958bd2d31a4b9ac8deb54a744055634224887e712bada75bf670db28addfb66b1bddb62daae86128add46a543bd55e1aa477f9c034cb8b521bcd224fa190020d6f96415759d1a55b624b8cac0b4417fa18558dbae25611df80d8e29aaa992cfbae596bad36f6d859638d353eca1669f871657ef20702ab42f0f9810abaa88223bacc56b7b8af1475b839f440a6b2d5511129ae4452a68d6f5cb93adc4c7f587b5936bae5029dbcaa32ca31267b8e115d648e4d5c54c84c0306c61583b9f75e18d72be67ac1b85e30ac55ccd535ba67ffd8c8227ee479d8c82e71ccd7a3c196f23c7ac4f9f81ecd127177ecd851bb7da77d3d627834d83c9a45daf77b584dc6f0e8d119c681590df6bf9706adb0c28a1cdd3de2974c91c3db3fb8a1cd194d271f35d20580501271ac001d820c60a045567d2ceadd034518446826788191eeeeeeeea63dbb9b3261c2c4a7079f263e384a39f62c319243544f7592592a8c484204303ca9f72ddd979d861abac171b53ed380c9cebd725cf6ec3e7030cdc2d5d75aeb39bc7d309febd560cd7e3badeb05f39cadb8706bbc6aed898248ceea911bdaf823457df7f282ef6db63be77d355a555970fbe14aae7498fdfe61766d2c2348bfaa24c8fd4af30441e719a748fb4bd355c7d16870fb3e55f516b5c44b532f6eb8ca3514325373ca6ccaf53d82748ba8de064eaebd53a35922cf929c5cebcf92277218bb90118b5c3fe9aa59263dcdec8c08223c8944c8f133a24bccf2973474a3b69438e8434dd3341ccda2e16e1c0dea109365966326571b3824061b6c9ea661a359ba098d1e2880624ddc7e54e95ad34515ce4991b8420fd420ed2f32d56692a964ee34487170399ce812caa3df67f473e77ead440060dc89a495a4e44b13091039464ae2cb934a1876b19d2aedb0119e69350e9be7c6fb911d7d8461a393704ccd9e58f2ccb42c0ebdfd3b676862dbc9eef9d9fbd15ff7a36b3a0a86dd9b1ae5a37b6bdff7afd1efe8250c5b8d7034dda27c2f5dfa8bf41289849a2aad278cf4128e5355fae88dbae0fb46dbb8e38a761957343b5517a859681660ec9b21779374644b2b99aaedfd2e64e38624301f7ae82fed63d5d1c073650ed10fc19138589c9e1e239de6cd39499ff6d117e7d612ef207d8465644aa35f1c5bb195ea1da4df7b1930dbefb8279d84654cf8e2b7ec6db9433bb774424316985802354849a49209e5f6d36442e9e429e5744af9c5d7963eba45f9bd2d7df457e9238cf28b47275de6a67c32a593a8a4ac1775414c95a2190120002000c314002020100c870362f18040226b931f14000d83aa4e6e4c1ac9d32888614e216308318400000001181111d2b40920ba942a8cbde81c14c3fdea0a82824b6abf724bd5fff5abba7e36cd081f42841a3e8d589954911c46deff43d4a417d352a5d0bf67278edc51fbabef31ae69679b2f1dd7c53718eb4ee3afb8976666a8e4a974971f14e7bfdf38fbde0ca8c6dc55ec5d42fa48ad8d2ecca73c5f6996e2f723d936103f66e275c9145969712f4612c0b4531b223d07758f54d2fe9d5b254d19c99c68c975561f8a97eb15c2e93bec2a5bbd2d9fac9874f4cbf027ae16f95b9dc32c4f565a0d2dde3ea347a88ea53a6e693af227ab8074de3b2db55046dc750c71d88229e79b6ba4ccb7bf918896d0459cd1e659e21344a9c2c948c1f580b88168fd34359736516b624f824f43d4cbceaa5157c429c9d4203232548654acd9f60cf4d5077d4f4e1249fd985da0b1dca5d7b1915f5fb94fc274ae5616a5b25e6d5170303271e1b05d68bf326a82f8d260bbf471cbaf7a35e9025707ca0043f5fb7ab67a840b8b3263ecc7a7ddcd4ca2c30387d8b46b73f237b8df39c7a7e7683cff2cbcbb2193eee4b46756a84d1c2b465119c01182ea14d3cd3412a64fa67c286e7f82c3ef05221cea799a616fb80a57c5270400df974dea340696b8046bfadbb678cce68e7c8d00faa8552057e02b5fb713560ae861610cf83d2e4e6bf81943810214c7fd0402c44a772326ea421f35d8bb1800341d473566097ecf7143f5b041522aba2f3f8bca1d41f35ad19a653f799877d78ea3a01b13047cb28f6a8a34ecaed45ef33d6bf160c45298e1c6ce52a533d086fe5afa71844882ac39f71a41367c99d302a77f10f59b4bd36df93aa7b2d1552dbc1232add751559116803536225b4b0a121dba218946e5136d0e835a344bf0ba5a7ce6e16d03068bdb3a852a69785b99a7949d32f24b00937601115dbf53e1e5f7a7287eae0f4532a0d76323d926d91bfc7010afc08de1a571209d26f999481337e3352418b104402042636689f359abcf1d93235f48d5872853dc3f7f3ad2bd331c7cdc15c9d26996c706b6d20785c8e81f1f91cf58df09529a6e46d78a0955eed735b51fd8ddbb65e6aae25de354b86647473697135a38ab943b7235353d654ecb64608e63cb11a83e88a9c62b920735e21b7831b5c2f39f289cfded3b3d641d8e781679335c8a9079e768c77461e814e3e0021cc9f25738c7225091cf68dcb2006da4adb010fc4f8c0546d0e76c93b1b3224ecc30c4ff942c4781139cc82ff2293312b3bf47f2aefc7eae52618ec3c09fc1d3df7203bc0c850d1039b68cbddcea7fb8c622d6b9d9c07aab8a1e94e21dbda163626c5317278f0f06ca4ba80220e22ca74839e36d422158da641399e40921df24305e7e82ed435c376bb999e24e1260d285b6a889a0d7228a209187a20001d53b9d38256b37eb7b20a24fc8d251f69ccb4dcf071ea9432bf5f31467bf588e446c442d0a3fcac9456c150de2f062ac465c443a4b26f569e763cc131502652ff50963634a3b7297ed7edca438a27f163d318a37d428c664bf4e2f501236ec7c18c1d4c97aae59be2c689146c1d4d0d90e5552f8216bc9e2b6de2c813ad9c1b7fc487a7c1bc86c4d279e2870207f616f06022e5fd97eca0a0f0c05f5271b51f3d21e15cfb147bb692a72ed7acd5988deeda8b571b821aa63329dc7676b69b753e1e288a4263bd9221bbd1271b345f8a62c23ca5c946d31b0ac65ee7c057af86d8e3ae749202f030baa6ca2cb060cebcdb88593f0733d995bed518ad7b36225a57ae709c23847026329eceed3d0a0b680e29db470a3ac7ce5b80c0d368bc1a3993d949e9a83e1a4319b740b02e58ad6a0820ab7a017adb3d06d00c8ba74dc4ea33e52ebd244af9152de63303c1e2b38e0b66d317bf611b9fb039518c189f8db5f6a5502e2c9210746fba55065f4631a725041fed2511b0da29ec8373c15a5efa4bb7eba86802f5d5a1dc854bae79daf08254c837fc2b36db3ebe78d09d7950dfbe9a5405010ea4de4aa0807a1340e88284e10cb8d90a2f13935479c3f43df595b3440f2889b7f35d87b3356e3b162afe6b4927ec0ebe42392ec9fb999b4fcc2588912d430e4558d174f54bb361c4ff8ef4b163ec0e790f9dff1ed7c29cf8bc9e64f572ff7dd22eca03f609bab34fe18a4762c25593cd178674ea7b502874384956254f085b83fb3e2d7f7e318f08a01cf8b8c49f495ee913b6101418c659b00c640104ede37e2a3a4024733529d721d4003b5e5b786c29a7388450b3a641ac258ad9091b9bb5cff234c89b72c886b043c21cbddb5c6102d24264426ba7fbe73faf96e1dd2e69c50fcb430792c64a0e3ff061b0439e4c8a88794aaf6d299ee70f8fb3a79c779936d6fafa153a11be724991b0069a8fa52ecf46709a6c4435665917ec884383125606c7775c21a035430de4293ac57cbbda062ccfc23f546c2be6049383bd3f83964a411475d24fbd8c9e43a6712183b120f5009fb080eba7d0b0aa3ac2149a8229f3091a94a820c14219ed5f5914b32bf934fb7ac5f128218b94c128b2b691350239d536e907b74ac039f3f2e69dc721927668118607c1b68fc0ab47f85e79026b2ce30f14c29834ec3f14ce5fbe05d0f1e4e60c1d9a1ee42a79c41d659f2f24b0e1f82edfd56bbe680cacf94694a1760c3144333d55bc7f4d9529d5f8489e2350ef480b787f865293c5152fd9ef71c432a358a0829676949b0fbacc62b9b36f623f03ddffb1e3e91531ec8c128d54407a1e2b82880c465afa49d2fbf0fdf099a8b6807bb2f881ee22caa658f6ac1f1616f133d5ca35d2193abb8d4baac97bd1c4cfc3128866f8eafb35ca1ee82f2b005b8575bea9f27ec427885d50214f1fe6277d71b09b07130556a2cabf00ed30c9c20d0dd9fac0dd6696baf139e6c07ca531e1c8c6e1a3084b0e6e0d50ae7275a2e14140c8696734ef8fc243ed3bb27ec253e2366b99804ce1f90b094e00a5826500b1ec278068d9c5bc017c71c307331f2b332ecbea224bdaa14fa6357ab8b300d2dc81d97144029a9c1f87efd592fe367c51b3836c8ebc51fe6b8b85c123e6f92a9fd7f0dd62bcefe2b0f00a51c75250851df3e74ba4e678e5c49d88dacea588556380fce909cff8039b82d811e0585eeffc727834bd8b8dc6601573e8fbbfba51489b97f8a12d8fe4849761f6578df5ffb8cc4a5ec3a3cde9060385227f28411e8b282fb124558a600deed95789138c922928e4de8c03a82eff7fd4d05dc0501e400388efbd2b91920156622d7f214b60721ff5e1fd3f75e121ad75f76fef59fe249d860b505865d44e486c6114580aa86ab911ce832c155ddd186b5ba2279c00a40c89261e14c926b4490364d17d1559fdb6f79d73482d6b400d73fb05aa973a3fa096317964ade506c46db2aa910f91ae70267c83d62a701817aac49d2d8db3bab47cc7b6198bc75a327cfff3695f557dabd673448d45115a151138d2d23dc5a31788916aaf88f3ca1bfcfb1240e0c32ad0eb13dc1f5b6342a3090695c1a87c735bab6f26a0af94b57b5480d4c9b4e91935899a82c662457cf91a7616ff42d541ba3ea0d46724a4729dbe6f5a063af1fcd6708ca048152fdab2efc64e85f9c2f93054524e7a21a743ea9824712d23921a4a1302109b4783609a22ef39407ad727ce30a02ef619ce69d4886502ea8703104510f52d1d961f642d0212b0bc87d6833b8e34fb6f4cdd55aae01a63fa305fe141d7a6b20541e3054642392c0dea3984fb6f7b3a8deb36c1111eba7dfcddbee491bf7dcf1fb590aeab2716cd9993af260eabdf21e3323295a34bf5f9583f6ad63030ccc6e32a2025c429ae047640390374f01822fa23c72ce5a9dfbe386498e706069f4895cc518b49f9069d39b2587875200bdbc830372e4003d57ac6448bab4402224bce39b1d2268de6cfa0e1bef3232dcc66810fa447280227ba8c81694e1b55a060295b66735bec3b69929d90e8d51db54bfaf4008ea06e5f7eec487dcadd53cb657495bff8dbe03d74bad404af68385497799faba6eb2207b6a943a63b3f03c68c80f3275bc1a0bd332ad79416df200bbce6ec7c8551de0ba6939d0f932a171ce482f1de0fb926b94ef5685febe1610cc3edb4aff83805153de8ea2e9f9e5e332a9967feddbc29c5a00e17b75b7d59b2a6d53c249730a749b6540c870f18388f102b085e44bdb44889c2d956a70360395465bd0e34b40ab5c009f2896278258f705fd28c2f2596a79c10de9d6657336b44f658b390e4e4149bb3682dddd2b2fb48dab1750726bbe1a059bc60c0831589ac33f750ad1beca08f5cfcf0a407dd8c09b28fdd3d9ec31fbd5ab1fc54978bcf9e6a857bd794c7bdbb3a4e9298d07ec1c855ad8030b0520d42df0ab770317a3235f78d1281622604401195becbee5143bada08130deea7f2301e3b6172d7a8715e223bcf31645b27e0e558e5e5ebe610ec4a8b73ee667fba15728dd0e2234009a7ff9e6b96579a3d04fc84dbb5a277181fb379315dd0dd7d820afeb4cc69668a8690d493f3a54604f06fcb73e0dafa07befe56198d76bc7e67d47da62698b0f3b1047386e110e2b5d84da07ce4930f25bcd6ec27fb98c2c7e52f5a4e3922fcd05587c3fcf4649449b8fea7cd947df28f9da4f6506fafd229008bdcb101ff1ad4e0493eff08cf720dceb6852e5d40827881130e003c2ad9e54f15794eabae60b5a7d29075d40c7e9c264d7c5c426b00719a1ac3369c1fa29f1c89d192e1304542709ebfd8a15e724912936aa3cbb48ade181c70ae8b4635c3d604a9686e8432bfe909f6365c1ba7ca85fd3c424f9ccd91556dac0a137022b57cbd5615a189f8c4ac403204bbd61ddc4fe568aa1b24eee6676d8e1a50c4e4d3aed42f21d0b0aa73a806cfd3561657979d4c335be15f0579ef847107b0562dc126b454e39443267a441722ac106583e7e06dfbf9704290b659e9e11ec901f31b00dfaf26e5edec1341ee2bb3260a1872ae7b471307bb97c112be0bafc848de2b7e7860d62e517b6f7516b2c5f26493d6f7cc9f2de67c93e44214cb72c04d46277184c4fa5a31bce681153484768618cd68e147553953517ba55c0ea5867a4088e2d480d31ae67c9b6a3c6d6bc9e21a32c85d89a9d4733ec97e5390942daec9e6d357b2609a6c58f20a76b7e0040ca48d0bc2fcea5132e9ece05fbe4669e275c5cdc02de7398513c79190f3ea0d16410e7bec85e7b055beeec354deb090617b64c72f4075e5ce16e590b599ad61cd043b603f9085be0d6af7d586e7359016dfcf26e5e18abfc99b32e4915346d9b76309d25ee98f7da743adce963b25b7eabf7f976cbd9d1af9f2f46a287c8cb79ec0ea37861a60838c00d2ec566bccd4e812f0842d4db134ed77359df06959eceac428239e854052fdd86d14941c71bb9adc208551f39e3329769104e0c8eb24742735b828c2e2247d8d6453ebb881ae45ca8887521833b545886039302b9ac39ebae366307a4d9d9d3f4f40276d073282e3c51e5f510da1cfdeb6ceb0f45bdb38a64320810a8efd61e0527d6290adceecbd775d47ea2b8f51cf6d06b68516b2dc305f11b656f6c3b6d8da64d4c7741a2a6c056cb64713abf8bdccb655be03cf8292f271ec0e8a468391b4dbf66d975a8005c418b8a56bca370a448b59aa1b37294616ca10c59fc130d7187520dc90a6b6864d2b7432d4ac0e3a7c87853c8dacb90c9630d7c2272fa499214ba803da96f7aba412410cca108698f74e0b2513748833427eca73a3e79ca3c54c6451611367efabcdd872406d8d3b28abbd91ab70c0ce1542f4464e957a59993dbeb2518961ee07201170b1049a010c407c01d9643bc42948e676f14b6271a416e05cb140832cd7601a2906a16876605ddbf5228a00b78efaab6662889966d902bfa16467ca55db9c9dcbbf43fcf4ed74d7bad523288f494f1c3dab4e2422c0ac4fbfc7e7d8cfaed6f97981546c5171023e460c447414c1bb1fe1807374da41f78d4240c54885655ce55fd4429484da394f1f7949b0239512fd02c2ba92398bc4d7eefdd94fdcd14532eec727be4e5c7bad11381c7c2544a7d9b67a5826f11b1e4776aae6eb636c2d0bf32ea995bdfb803ffd8a7a565e23587cd66c3986ae179f6b129399f286aad7e4ce26efce8532c01e85850ad58f609eb6370a7dce18b8018c85824f37e545b95df1b7e04638c11546c006ba5c904212833b48c0caac3a21e367370cc100df488d20e221d1c071e06c16c28682b7a877475b6e123dba7c4420809d3747b653928c2fbb55a8614be89a5aec4e20b36b0430939971975ba923d92c54a8fe760f14b5d9e78f6141234b6507fa84b06c87f47c7939de2dfcbe6f910c3d056fa4977b51eee5781c4c536ab4af17fd623b417e3be4c567f18c29353d42534ab4a460b55be0bb5eb85295b16697499d165de1d5f3e402d51563c83a3e25503ecc45343253ef75d71f358bdae4fc65dbd7397c8937530371b34c803d3ce74b464840b595b84ca08671a610281fe8c1b3db9ff89387926d21acaea6967ca3174cadae1dd6585dfb606ab396b2827e0a57195524ff9a1326d427964a27fa8d3dbfbff44e550fac481ee644e1d89552af309c9efe2cf841d2126fee500d5802f22b60411c899e14b0ff836ccb85429ca8a2050d4d623e691184941ee4afa9e29ef8b865110d6861c4e2f305d9a8351979f4ebbbe759ee0cc9bb1637a78285897fd321ecb937ef08cac77666e5caaab72e2355b11416ae100b304799f5d416e59066edc0b833c01a3410c1b9f04959a1b62e320316a9268d7f1774a215ff21b62ef2896b8353335a32903abd275760197aaba2c6d8918d24424eb0f492315abf235c01b1a29a8b713d121f9f32e0d48a952c2882191fd836e085cceac673cad2f847ff40292e3c12f2764d29c4b2931f2c2b489abacd2b32aff72b1b0c58d242fff95c88cc99bd122d147f6d6a270403c0d8569dfe492478fb051fdfd5564286104b78c1c9155c048000d26276925961efdd6b8feae919f1a5de380f474007b4f9149ebe8bb5e229e4fea7ca54e11efda06d5ae5b930406e0d8a94c16bed42ce73e6d217c6bb5fda479247be06b8fabec7b99954952feab89ca34eccc4a3039c527955fd78f12c85f3069a7758287c57c27a9651e4304e07e4acafc3f6b56152fb7f5d0484656d3c378f5ab38346e70735de0461db6ed4d491f9af1699a0cf3dcf6aafbc3b7a77a053433abc351a6af39d59fcba3aea431b19d7571ea22dfb11c71321ace3b8c37fe23c874766ff44a4320a75d54130d80ec7621df0219b8a8d3e116f5e1d0e85a38f0a84c15dd40af3ed4796a5291dcdf7b012b89c528a348a06b5f9db31b4e18d345f36ded3be82a879744cce0847b0e13fba15c013ae892f0ee8c7c6840c1a5ac7318de809d9c2ea43f2e3a5136e8858f82bafc7b14ad066120226191be7ea480658ed5103c85d3224b110359236e647d5163f2e2f9603a6851bd367e395c84b4898196faabf63eebf1e4314493bd14f09eee37c4f42b4b86de4afae186f5b5d75fa5941dcb0ed5afd66c7f4070ab0b4585632d3c0e8de767d1865ea03bf397cd50e4fe0d7e0203a00b656f2003d3f6f591ec2ea1c6ac7542a567e39c366ef52ae892915081e360014adf7a594227c8f4bb03c4371343f4e24ec34bc23ec80cf1e7db1534186dbada216d7b9cce945e87614222eca43e0e33304c05939e44f5754e7ede6ad2f6268fc142619351163e7b34778a5c7807ead0695d07113e7a11c176a94021c3ed9d0cef3e4b274ae293aed5d9d3e9cfc45e35f5dd2d4b4ae761229b05f8c81db99330c207131b319a13d527980cbc82cb0704fc465ea75f02c4cf9bcb9c320a9721e27a4e9c333133737412cf8af2b3692334d3c4a0fa6b0d6eec86f69c1f88582c6d7645c9cbec53a8a47658ec3e1de06c9cf5fa67e4031e6db6895b6ae9315f67339fa290511850ffdf64f1dd5ed66c08cfd53fecfda9b66c7b49d9fa242a835a9da124ac62891871c1fa5be35e0ad9eb2b27ce3cd42e767ed91e4486bba2ce72df8c112bdd0b79b3562ee5f531d0f01a97f64238ea8b28f054fe4f37b6ae859f56d17acea721d3b7eac6f3d7a85f2e8930bf1856b8ac75cde72eadbed59d53f717cb8cb54c56589f60c995801e94391fc4724e35f0551e3b301f17e40584ee415819b3b7fc12d92f3429c3b54079acc852647a023279c2d036a6e29d480618a9df651e150166a137efcc2e309c3f72b8bb590cad436477909f0b8b0d94b9e6c7e1d682d248d67403c55ce1ca82ce8207b626e4ba6d737a16c7612a61e93984dd6c28a46f281fb34122bcb7c248ae39207674ae287a1f79387df61395cb6679443e482493926bf10290ff5666ae014d6b82b6bf139ddd89a9e34460640b3caf0b70485f4d37ab6ab6362429ad0c8658b76b900602ae8ccad4bfa3582bc323ae946d1b75e44d88ca86d0e5f637a6150882ae0f6aae405ea12d6df77ac663e28ab6e63ac9d3b8082c951d2f83df4550f2e645d4892498c52a3dc023f4bbc5500a3dd810a382966ea97d0697cb4fbb338a4f0b078f367ba4938059f5ed83a54f39788bb8404c35309bb49943d730237444bcd1467f268d8c83c6356bd751820ee3c9c576d3e538d5a87d63e0fa4d35ec0ecc6c08976f9c768e593d7229d4f3e4670f7063698fd873ed5712c0bbc2fcb87a08c432d3523798cf0119d51270496dd5e2fe3e56132962dee12d2c2f3c8f41f8fb29fc13975fe24ac35d696fc52bde035f4134584a005936c80a4102fb56d517fbca6b689da0b0cbe0652848599c0737597fbae03741fb0bd756fd40ae849d8c15ebbdb1adcfce40091323cd7f0a2d06f0fcba69a35c0db345b3170d6d03b7cccaec060b39402f8ee244c649b2523db53c25b6b4ce6512e08d2dec88ec22b203a2901ce5f433f6748b6aff81085733a1174ec4a3c55219f0962c00d8dca47fb4953f768e0a7b45f2147cbb8fd0871a2f180d82af43d00b3cb0b88fd63ec21e89e1e2221d69c7bf8a01cac477750127a4d4e99ad4c9bb999ef51e493b8c8e39c305fa0b86284dcf6c1e714c40330316d555eddba7674a83d2bbc8762a22f69d4ae2ff64381a2fe68a6a2e07871d4f27a9df04aff1ee0ba714262ccd301c7c3e4f18ec3e05a0f9d49c0bf6bb287fc7611aaacda61533305e7b488d4e03701f8d26632847e523797198d2ee4c8c8865cf1876e9b2bcfc1cdfbe6271a231922616a2aaf372894f6f30d28b065e3274f89faf3e02a24b0a541f3d5995d509511d0eba57da48e857f40882b2a65683b1a48d59bcd823f34094b754365c2db67dee8857b277a206a21f42de6ed2c55fd11ffd8d665114c780d8b7de7e685dc7f6fb36b05db99afb5fb745d74984524677c6babd41bc08b1228a2288dbc19af134abd271d76de955e0c5f4aee350a12487ed1013310a72ae9bc44af61a650d1a8b3f006044ae90930423b0b45a9d669502b90bdb098f9b4ecc5c8622e7429a0dbcef72b0cf84c865a8e938138af67523985787f0ab9b97da16f9cc888beafe8823e0a02b8b200756d81ee15adeabfc0eab9e6a3180a6f7c7bb1dacc65de75bd082d13b1b5fd055c487c6f1bc423ece879ee7f87348cecb87b6a8513032635bd29017deb751c9e08582117b880f6c355db47ad76de14a141da2bb894063cadd5c70520ba0015b286f61eabed0d88ded949251b40a1a23b151a1877329e86cdbdb1310936f05cb4550e069192a93af62573c4b708ab221d94210b874e5825bd6b2c52e78e8644773420f3759c654ba1e5d258ade0963191b5a1dd2a1ddaca4669666deb77496021ed3a14f0ac93b0c7babdc59789ae8a7280149a8d38d1ff581fa38b94c16ee8654ca1b6507a9a2b8b0bbb06b58d49e109e2d9019766926667e3fb0c2c2a41e783f7e9eff953e86750555e0d24aec890e9951583924fe18987eb161cdb84b13161c8d41caadf7e0bd6f64ee815053ccc00bc967c84584fcdbe49890a747cb647d014d48deeaa024afde198581960957923c48d94b32e93af7f9eecf7c635c68c2742cec1c880f20207dde5f8ad586e55d6f0987ee5b2519fe8cef1379f8dce8b39a42e38889f7e1e4c6aa3066b780d7ef584c3fba1b54cb268ac10ea5db8aaf13f1ee2136dd87325541233fbee10f25b3d1515bfbf8ce2e3c66bb0867ff1ae219f8805caad54b0a3191ed46f7e61d18020c3c9d8f475d077e188218459fd796c46b7598899898b03a0464140a2154bb93cea55f60d6d6742781cb0494b0954483360abf280d7460b52d3fe84d95b60ac9ed9b977cd6b65cc9cfbe7b08dbfe3af58c2e8d8b847271e0bc4542c1989982a0a22f5453e0852eb76d6eb362ffe82e709133431e846acb9184fb4f44918fe97389528384c01f81eefc1a1fb7157b3b864a2007111d5fb554a6d0ff87097af4ccca2db136b7e23f79d6aaad245ff998800442ab84a424ba24ff38599655418db7c07da9376e5a474a9c7d81668f8acdbe6e17c3f49035e01954d57538a98ad3667d2b332916cedefb47e5202d76441e6acdef3c748ad0fcc267698327f32b8331bf3234590be98077ff14537119c4f2dcc7f64c25f2f76bb187dbcba8e5a227b4f41d43a56b6449279adeca752e449362c7d0818fd99a4baa620e7e057426b15b43daf1832c4219959dbc6534b6667d13312c8ab12d38f7f5ea7f8b69498eb8abaf8a716db958eb8706451414f956e505758890ccc4bcdb44e3ae04d1d2a21603c53367a9e6eb009d4171bba4779cba21816fe0ce2c371dbb09f018c59ca15813e0a04119475bab3c1f0d3ca79884a1ba3d8f264367db973ca699a26bba920aeffb8c98d4acd309a8eafc04f52a4f5d4ce1adda7e0dc7d89e759e2ff479463e34a5676f56f796f6ad4e5ee454660dd642cfb5a874d4886a402ce029744bcefe5f81bedb06b1dd5b1f63d6da35bf22636efe884ae42582bf46baf6e0592385a31c853d564e89ab538bc12ed56fceb7dfb3e037b93d70ae7c315edab48a9c561ccd9dc91955660d371eef1548c88cdb8acb9fbe5ddc4cf7e4f461c335e0f8a3e8f2bfa8e3ce1227b17cbb943fec9e811eb3be16b4bb442d3cacf352dc9feb05c0ee0b3f044dca5477f3994cd25088209bf36fdd82424d8d695d0c5a722aee9ecf28c352958b2882a901e93c4c10ca49e7973e17f96fd56b99efa37dc15a2f45ced1cd63b972b18c1f1d7a7fb6dbe4e483b91a6d842d8e141f3bba1277310b41acee62472f184d24aa2e6423a3943c05e408a20231099174373c763e690950b0ea6e31075d414818c4536aca58f98f5ebebe3488b4a1ffd1e023ea9df786666643d2a4b729f0060eb53886182a9f03435cd75f318c2549a79366761dddd71ddc21d5ff01aec0c9a33d4fb649f99759a393764df9c3db4119e8d73b53dac303a8ffa3f92936ca1f4abf8d0629837090a863d85775d3d4e26e99dcf03cca4c9cf32c9d98154e391b6c5ce72f28e912181ee7fda8046181f69caa360506644df207ee55e8a17223bcdb3719518dcbee8796a0a5ca43003a757a841e88563cd4b72eec2c62f2b081a60213e25b810ea0fcea7dad26861dd7ed56c98767fb7b5a5a6aee612b843a9930bfe15bbf25b6c2aecb170361c67faf22a5066ac01b1c4c9f416b13f4a1081604bd2054f9e46363b67866577d0c621046f7188ce6397ac60844e8d8a818ef1fa09e851958bb252381b84afc7f382fbfb9d7c857fcd79fc8acfaa2f78f88ce3899e0cf7146b5011880d1a1946103572b4eb3092ecc77c4d744d03229a4be0599007439da4ef36fc69c5f59b229a46e13c616064829997155caf03bb0fc07dcefba8477adce1d668edabd14a708fa5a496950148901429609dbafafe7d6ee9432376415a9d4ccdeb11446da6a17bfc3015c6c449a0c9a371fbcb7b24c9d75757ffdfca3f0c043d91124c776ec3b5083ff0cf93448ac917acd9ebb2bf44aa4f5d78b138c50cea407ea53887ba4c162dee9ad00aa1e7ae861a8219aedcade0f05b14374f8d12cc7562a4058c2620308902948dfe6099b0d6ab962ca116d6d97668413fe562fd1a6308ac0f25e8034d8366781757775c88417709273b14e5775f4aaae8d7592b848a2eea62cfbfba52884742dff83cb50b1ce52b31c2896077430c9b3e633e9784b6876e32d065276df0451a1d32c0cc6fbd87e75b93049d37e6caa0fc6ca82250742229863dc0c0dd58ea17249bd06bf7856b3e28d0f5959e9dfd0de7552a892821c1e9cebd1b54aae2bd5adcc79d6937997a086f2ac7e8d1491a27ecfa0e6bddf443b797689d04d576469934cd21fc9f80e65b1c8100fbf423a0a1ddf908dd24ff14bf8f05b9fc8506e4b8a90be677da9560c2afc83de491cc4b97f94403abac0f0a194fe64be626019bf8426dca240383b6b8b7f8ee2d330d5efad6939ccd898bd0d0d8e0142389737439d98512164a7c1a6882194e72292abb10cb6590c0ca258fc4eb274ea4e3c342bf379927aa4477b962f150394ea6832831c8fccf69a5d8a0b32128e18a4344f1551cb06d2950222ba23b1c8c89655dddab670f4846308349cb9c0232deec73a8f44e26fe46f03eb45e5818b704bc967528a9e3620ab508089ca2b9588d23a9b664742892c6927e758dc1223fd388b65fae4fb55b416935de3d050b654918b8e9f78d91b49b994a69c179e1a2d80789b4e1a70843111a8d3bb7350fcba57d154838bbc1dea4f9159001b5bdc71f35a629f69448d2650fa0032919423aa1ca27b606a6fb4256346dbd0a60e3384f5bc9e1f177a4205e8be892ca8f6919246029471131f4efa04f69f26a3d23a3ca07a57a993acda25b63457e1254400a61d9e4e5cdfc13199bc835bd22a848d6fe05801ae7b8fa8a10b30bb7d9c8e4cb583e8aa5ae4830c9f14b933354dee709f909c1df1c744e6124d06a7708e5b1dee475f0a2c06719bdddbc88e4e43b50733fb956e4033bddba17c3c2b66047f32f304e54f56db386d4a0d10e52f93b8440a164719928625ef4a683e9666a56b0320717c0f9f14fb5edf9fce986b23cff84a2aee5271ce5f9798bafa4fe6f6c7e7812711692e83363c017b946c0f91d6f8be40eacf2fb827bae3d54d6d4f9387b8e92f3f82a1392b25f22f64174a19e14149a533725048908b6292200567ed9fe2010c1a7ea3347727ee569d472d03e260065991e26af101c251ba23a7d7480643295fb203d5b7a3b3e6e25af36a9c47ad7b9c38498e771dcfcf31574b8390c9191075abab3f89d01bd559effae6fd0c75e488cfc6379b8bb0dd3be1f5374c87758c011ab5b0313a0b34a32375b190eaea9760f336c664952e07beabfc838b9b8a81a5c420bc1d2856a83b67e6ec93c1b6258aaeca26b04145cf218cefd7b613aa83e3b9cdeee9fb3f1f228bbd6ae65f874212705c407752a0b1d740caae552627de4ea66e87360a180e31827b7c9fb0835d9c7ab168706ff865dfc7e1deb6e85d1a544baf53944d11fa0f6cf35c6355e202dbcecc76c52f3d188a43db6cc9dd73a4f8469a824b196f62124d32370f5a85ea45c14abfd037b752383cb7c5ce4725f42251b4ddcef8b6ad07e5449de1e58cb0fe500f6251ca865ad36c08340110ece19144bab4cece6964cfe2cc3bd1d29d1f193260ce92e734034630f2a35dc34103de3a7dd0e8b8c03d6edd8dd277ab4a7785710528ba32afe6a9225ae4c188bc5ebd50634fba8cc59ea7aaf3c7d0d052325daa473d2871e2f3161fccc44876a1c79595f6a46366296ed1b0c1cc49bd5cab8e05f59855139b602dbb0e1825889b2315de3594e6883d720db5558ab61b58efb142ae4c35b26a14972fd034ce7a9c831207f2a17aafe4de3d0bd1dce621babaed2fc8e1f561660b9626a7902a80d0ea0867508c9c0236c2900dd38d595b3d1c5e5d787b3dd92a23edec5716aa1e56d28f1987b215ce02664fa6b212ac3c842ebb7c579b3881c61ebebced8a06acdacb30ccdaaeda6ea8dcf57cbeff0eaa54252f18709be6c46358d9656054aebb848c7cd1b53223f36e94a5262215b004176f6963f05102b736dde71a69ffc688758c0961515525228552c4f630ec468399d151891721bb00a3a1ab5a92fd29ba2f3f465784c904adae0b00190ca8b0ff5cf003e63c258a2dde978f139c25b7f591c064e854521d02336fa3ed5953f9e6b66795c1a3c58affa386f8e752d4d57b8b57a88a4bbd31a297bc82da93217c67f803278cc3b8c6618154445edadf6e512e79dddfd81383ccebeadbe3ecf42e7ad28a18880963254c9e661bff80a7c84f7d7c53aa7fe71afb3c5004ce2e37ce595e88c642599718affcbf64c904780f035931b5c98985693ff9c6403a2b10be6750e6eebc11867ea182f0134ab5bb5c79a5fb7f527750958cb0e08229b4377591d30f91d1a534ff23e87f5c2054c11a57ba12fa3c2b976c00c1346185f5ce14de8ae218266e45ddddf98344364f4f214178f6954bcee6855bd3296b610e0fb69c8bd219aad27987fa31879e4743006e5e0f92d6bd15cf2f4b95fa0d8b7218ab21647b0bb1c0b83fd16ef26d9148d389bb09c5963cf9c9d4f6f539d827fa269cbe81c815c4f0752eb32d86352ba3e3e0427b78244d1e360c5504f830394b40948ccc8686a6578759b6991a4df92984876467262e6636642cd55877420344bc6569715e12071e692a7cb22711d90feb500f83d3df3f1c32f2183ef4fae32de6e8afdcef1facf6bbd531703d0a2a8838a503aabeb506bbf9fb87e2836082e1c9c55b0e5c082c0005b62a7778580858ac9d4d82171888b2c605d24b137281cc4556faf70d786dcbd9ecb11836b15e5fd7ea078399ddbeea4c4394fce1c704fde18a3588ba080cec67ea2bc3312cacb2a500d60b8e54ef07e95b3ef689ededb284e8c9c30b587443c77a1628cb3340ca7796768fafdd32442bc2b182d8c30fa54967c312894179aeb37e6002bf92f76af8adf5c12a0a43a7b419a178ca582d4e7190fa735411f01b8246ddc10d866916bee3dc55b59e58facc2713350bdeec614c937012efd402a99c5ca83b1c801d879c456ec4ae621d58c01d65680e52329ad87700789000a5c802ec7734bd7048b6b03fa009bc98e4a7eb2c34d3bd5b811042424680f5bc6c257cc32726f24995f53cd1ff4c746631f090e60892fa7b9e7d1f0d683121e2fa89b09d8594974064b33d11a9ea517200b2d570355eeb9a39f8cae722f970053879cba5ffb90c551e72adaebbdd15fefdfa0f3cf4e04ef9b67f9fab96bfb5d72d6cbf196fa98f1ee1e878ffa145c9bc4aa3e411954494aff8b80bdd7049fb3c814ec70dbdb414c34db693d6f5787592a0f1ebf7c1b89bfcb3fff936d7d7cc07326a52a57206c33ab8571101a11ce9245a6196e1e5f922edd50a3ad03b8e36fcc21667453c4399b0f4ee39f15550985c911cce8d26cf7ab1b815e7216a8e96b8a812462207f8a51e9a54923138a0ac220bd00bb11ef91e1e79f53ac8a70783e7a6998680e36000d002d53ba97fd51a09774f18d9a4220ce50c39fe527ae1024f2f285848ae29af638a1f5eb056305609f5e15cabd7b90f15442765684a010cfe570db9ba66de5ada9bdcc9456a3957bfb48d7f8051ab2234dcce2b8b01ae557c182a8e9500d4867951925f01016cd52046d8325f45add9128200fcd952cbc13230d6ef0563b0bdaafc71ae3c4f3f6e68d5b873f164fa1b479ae98428bb1e3cc034dc658b8ea4980e0fdf7adf91d61992d50a36597734fece06d591028ac47ccc25d7d988101e6b5cb656c34e362af5094e92c20e0d1f3c36dbe2f8cfa0a84372210ba20357e76f18e47e3e4006f1cfa400d087a61fcd2f101b11befeaafdb2994576a78f75839b6a69174175af485abc0e0a6dd81e12377ef180101d3f6d21c36eb58ba64c26f12a1d97c086ca5846a36eb3462bf35697fffc9bc1b97d0266f85c8118dc6594cf4ca82fb8405433583610c50642728ab66df25b1bb94c5efba828b7df6eeaec9530cc9ea33106e18a71790e0e4e531247379efde8299d39980e50a9176e18ce6aa82ff11b5c7f1694ea16294f737bcff2a0d9b4b26bd39db9d4961a32210e5321c667a95be90acac699a906accaf29ef36762e878789970f13fe6160db446ad1d868fc6857e68ee66d6d085b863ed18846e55e501a8aed72dcf5f08bb7ff70b48e50a144e7a037d214043015c69b0f18f16fdb7f9166dfb55a68480567cadb4fb21fe84559bf57e35285b43e3aeb1e5ac4feee183e701a834a55b0cd4b2f61eb9f37ca8a9e1efdc97921304be01b18e1d8a2d50f2c7978d090590f5c8ad4ace6b78b551e65cea365c8e352c5b4d6ae57c5a32e6eb632426b6e6cfb290198222b29705b4219c632e95124ea3d35f2925e45358733ba4ad75a7490ee8e77f8c623bdd3ba5a7c054e922fb8269e684ae154ecc668738bafe1a0188dd3beda462d254bccbb8187a9cc0af38f7b86ba2750b4a83b7a69da5a8316aec1db8a4f1fae550f37deea4ae9d47ece3f63fcb8abd655a94e4a13c3dcea1c662ff564e18662602ee74df9841be146c1c537454c28c53f4ccdac8c632c4991e8eacc8671782fec92f9b7d2495692f01880d715eeb4be0389c94533b93703818e330155076f9aa9be168007635b1f526d1ae5c92df354b5595c8f6bed3cde41ad916a3ee440ba1b65a93bae9994c4f8e601e079493963ee67061874ed5da40cdc0dad7247f68596979ab7105188584309ab480e699430b5a433b3c3013fbd4a1103ee8b570ddae0e9bc2f4567a94da0d98b2f182c944f3b919caf8c009fb15b91057713ee370272adcd8199de644ded7c908803c28b47eea19f486e21cf39d0576605ad7018612958263f1e672d044026f20eb1998ce3a4eb5704353e56b9b34b852dc948dc9bdbf1e8ea0a92a11c0e69c1ddfe16f39449f730681897cab56c9ac9b7d010005b1a93201adcdeb8a75390c6ff54b5bde37028332de40ec70694ce8fecd780f66c031cbfd4aa7c7d4383e5db1d2451baec921a76bbfceeaaee1530d7bec19525713c63f37a1b18df145dcbed6e981d600a6e1ec26b540e92601e841eb7114379313738f413fbade80b2f501f9bea60d12a43a045780cb4b373aa6d610e206ca386b99ec7fa1ab6681a9320bb86402d6456a8866ef352efd816affa080c327aeb164b11c3a51c467a5c78ee303787b0ac4ff528feec12ca3914f778d57b31d8a7eb2b8763d95fc40662d230d0b38b0e5054c775ff232cca8182b3896e6c8d1a2deb67ef93701b60768ab267073c7c89a070257ab1c771f9859c63414cb4f7b00371efde5d423028016cd0110c06e83069ccc4b26762deb14628080bf62b3700526069166038d95e9b1b1124775efc20bab823a52505ba008f3be66d99009d7730983766a9101f6ec5da1c69405036fe5de161db26d6ea785083ac9114148221fb14858b5f911a561296961b2fb232b0adc013b6802a2df0a766aa0524007f932a585782a54be574ef37a4db1095cce6be681302874a9fb8b1f992e2e0fe8ddbb4e84898cc168153e21d70f2bb2c8b9492782e169a753d9cb525e8d49a2500d34eb0a2e938ed7307563d48b321749501595da7af439ccf5861677e3b90632669029110ba5d59e2bb4b6f832425830e954a04d994421dc8a739c9878282fcdc5a928e20cfb8498b4687f921469938265809e04fd7ad21a0773a96e034b02f066edc0fd0afbd445f88487a72973d333ca5037a2d9f90c5eb0619616dc4c575d80569d9827e26a277d841586f9eef6394a325b5a9da5816b0690c558aa29a1331c55c38ec713216efbfb9322ff24cdc82e4d4034b3d65ea8c84efefbd3e5b116471a9920b77088a9d898fcb49cf8c4db764fbebb39b39d7b7b25b121d7ef7958930de12ab652153b0e893673c23b12b60c96f8a7c28d0ba6b5fccfa50594cfa6890e32988cf4ac0cd60480a57a84e4e25f21ac2eed7c9ed841fad933e2777f7f09a13bcf80988dae86dbe877d4e2e6d57c49d22625db425d9c44611310c5f46514c1a2c0f6b61ff1620410c05dcba6e427b9650d3baa8ab87e031f5a8cab39c1fde0be43302f8404f5571e5af37901145875c1d3fce05d7f777e1ef2f012850594b004888133fac7d0e4dff2b04711364bc49509c2f7e629b3a4f991497d542efa31b4eab2e0625f9d3272247e1c4030f9a19416fb5935622bcace9f200fbd6b7704e655e223135a99f78ff664764b0174a824c4ee3b0192c2768094d7c30fc2ed5f70cc6ab68fa1b493902d21cad675f0934c31e77b79fd45011bd0e6c6fa386f39f942d62ba5adfe773eb958318a52ba4c81e075e1411f7538ae17ad9d89d7f6875940226f6d00f823a9c58ef23480e25949414a5f090ccea2542db643f22c2560e906ce76684a618e231164ebace175f8b31e3bfed98153775b3a2004dc9314832d5ae47daf51ee6a813b073832949408cf1ad7cfcfc7f51cd2bd4bdd969751863183fe118995b7a268f35ce53443b6e12cfa4bc34c084e6d3e9215b6b51e477cf5248998da802478bb3192781efc22bfd2b2f85fb536a2e55e0295c094f92ae205a15bbf77e709b5a6b8157832c2d3e67669fb054c1f37c535fccfa7e409f4cd81fe8fabcf52ea3991ccc37016b661ff5bdd1d362ecb0d547a4351d567e1a839ca9e3c5bf27a9769528d86762e0358b7cb1b5a51115ea31c2afa086c93865c2410096e29f320b21f1c0887f25bcc50b320934fc9daf1d6589cfd66bc267f77d1128003c18b5c9ac345e883ac5ee11c5c41f543df29718dcda23807b1736fcaf2613003844b709a76c35d89173668af77459416981f036830aeef0eaeb1ca09d711bea22350f2a49db81dfe1b5a97886d6a15cb98190dbcbb877a3fdf629c30f16a6dbc2d674285f746fb1c2e4dc81f42b52f064ffb8a32da578d4ce9b76abef5a91947760496d1281804ab6f5bc079e19f02fc1ac63f76402f7a01beda48a08230a9dee6de6b8c8998cece6a4b3d3a37e880dedabe170153c9abc9541140cd00b9be5a0678cfbb72f268f145f265eaa781c34e8572b96327a64fe48d57827d492fa52f48907c0726062fd737640661d835221668cbcfd44c18b8debdf7c4efc016d4def3660e89921e037c27c7cb8c350741ccf2ac9b16c7ec14acc7e4b10d25ebd205d7728224187ea93867da359a8a6ad25d56a6e55c9b56c7b5ee3abe763d6b672725238c8c6037e8435fbcad8120f61617690bb2c454aed5e3c3a96f7d9c09342a3170040ba6e8d29021c70326cb3b8187e14c9aa1465b010a933724962ee485d4a46e1aa9d2d34d485827f3cfe37039050029d6d9625888806cdd36c0e1735c0ef6da4e709518ac074949a3c299b54168abce3198140d1de4d1bd92e48d59daa82abf6efe19c71cbc62f58b4efe1290d3e53381f0fe6915aa39492fad9e830408fabd9f25f0aceff1d41e9f51670e3cde28d1f89dcca8c1205df05b2e6c49ce41a81a9bd1907fb8fd2827f41952e5cb779f1401f3893b6a09bd0aebaab5b136590235e02f74f98fd49390757d8e55df118b7accaebeff5e707b4c2dc06e0cdf03d6444b63ddf3f2b7642e585774130b8433a322f989b485f6947a1e9f961a9dfe9fc3ef02228fa1586503d389ccf054d5c5c75c3bd96e065180487783ece3d78f79fd925ddda2c371ffa5fcce34c1e1462d54090d56ad53fc0090617983fc9778893d016ffdd7d809d440149b3447c6567ab62e4b43a90c784f4d42c777dff2554775e325d1c3100bfaa1bb997aaeae09f5c8ec8418345aed6d18de9855388c3fc992e3dc70ddebbf76cceb7fe39883301da39e36de59900d16d9db725f864746823bb16eb2820f1f6e4f39a3026e90d14a0f773fe6ff0ee3cfc8ba0884e0fed0e24c5a4f97c6d0e5ed7e07606e06aa4d5171cb43105dde426b9f227e69800a77f0797874bc401cf356cbfc83f8ff46cf1a38163d58199403ff40ab0177d994b63771332b0a7adf6674aa05b720dfe350fe87907ce62cfbe711c34b75a3e6eef46582058425ceb36c6e28e091e262054059cac5b54050ce32e8c3174db761ab30fc66972d95cd0d64961633405807e0d007556f5102dc2a49007832771b84cc8253c71bd62b07fea9e4772a7ec7aab58b23316faa9baac9a6f32395c986bd4398077f13cf5d2c0ddf0e6ebc469f46eeaf22f1e21714ecb417406a1b90192da3f1d4495c24cf6ffd5d101283b4649796ce7d5a802891d59ece54cebee724ea25adbb4f35512ba0d03570b1990b77c0578cd3364b7e51def8863d4268d1d6f3ec90d2bab2f557233d0d94e1a364474485c72e07cc9f90dd393aa587200c7680cb7e0011238712be6eddae7d5658a5f87a60432fcf767210978716a2283cb0220e5f9724a85107dbe64019e2763722dc8c6319909ccd96161512e216b137d044536ff5eca2434182c31c0662e620cc56752162d5b3fc894812f3b6eb862b4d7b8da5921f73e5d03dc8599526509cdbda1d6e8d80198018a5a91665a9d9fa7b3e841b769834d97d49932971db5659070201c4116ff76a0ee14eac2ca39ebd5baf84eebbf3cf8c2e3af8bad70d973f1067d12744c4b652ce27c2c1b80112115cdca62fff9e27de482a5ea08c6a63b633c514c73b3b52aaa47b1d79a65ede2f58f5b8cf5caea9070050d4159c71c0b9a4d6b552531ecedfec19e61b69ce0591ff82c6197f73478eb6b8fa7e62743169c92b5dbddd52c185029dfe5b558c7af6c7a919dd947c7492798112fd9772a62303014c62702ef28846a4279d129118662791a8ad1c157b9095dc022165cae6f190824e5841cab8c67a9ccbb432a5f18b8d2d3c89c57251bd22d4b62e4865c1d230c32b4b81b825fb074665699c8507af4c97bf4582ee10a1829b3994710b27e19cc96a1d4a8b8385e9b3b95b13694750eea4820d7917b322e6817dd048ef06d7bb21069cb5e5faae526cc71d9823d58635d49cc3221eb57db3aa779b5d38d94c87261048282fabe0afb000f5c53a3ffdd98b3eee06d37c8536e50fbb6b54f197a94bf8ea7ee7036feb187fbe2dfe1cd57637a54acb74ba3bf009e1bbe308a2041c61584b1644fa562000ee0320f33f50d34ed1900cecb47f91eb82a2ccfce0c8707651e422e913aea2ccee913e8ec318dd4aadfd9f687c8d27e61d30695ca6599c07f7b0a8c03d2f0f4859cb69d082462aac6634c59928942cbed1a5a18e439f5e9a3e8dda433d092d38f4a1e2dac01f36cb7cf9e0ef41b21cd4e9898557d15c1318a884a3090364245b86af30149a19866506f0d5e80d8f946142bd4930761b994c6fee386a891704809442f8b43fa770185123c9dddb3918e6c9ebfae5adfe866f66c0627553982f7cff6112636462ab2aba37c4f6ce5cc77e3570354785c2436e49d1d774ca2b9b8299805896a4a88390ad8d821e0c96f8efe651e4f7da2bab403a1c7fb3c74529390ad51ada3c59fa0b02bd22198a3ea84e291957a0554f22948ae7262f74fc312b1dbfe4363a32c04f7d126561e88d0806e2658c38e83d88ce9ce88bffefba70cad88c090f9ad9c9f5c7f933e80b69862dbf43bfc3f0c726eac154eb71e6a8db358a60670b5a310b6541c22c2d81dd250fea72f899a641570219215d52145c703d39008b6e5d31775ca33c57db4818a6909f69ec5ab55a5a6c680775d281c593cb21efc3dffd4a1b78519a893d259d525ee0a9c8e980c9d339c348dd0315746f14b1d2276e5b34c83c60be9ee5bc173c5a7ef69a5fc66a93482a6cbb45690d0ee970e577ca1998c614abb5f35711e00bcb9f90fc50e84b8e47cabad62526873921bf58b5f8bce088c99cc48a377114c0699796897242ffb2d7e2ff4e334cbc1330d3ae17b92e004a56c937c58a6a44b386bda191846ba8d140d2a09bf46f374987d330d8789607a353f5b84bf979db1fcf591174074e6fa06141bc046725d481b39c60ecd7d1a5cd9246f3485719911c2e8a8cc71ee9b7897ab385dc5836d28aa8de7da5b44a6b16545267a718db680765bde65630bb513be1fd796097da98d13ff347daf38a8e535035682b3c530fca9438f7596c07b04ca338132899c851a09b0f0b9cd22807fd9ad4188c16b68bf28ef36a1b1942d474886d7d87518e2bb7b8fa8cabf6ff95038f39e724b979a600f73526f0bcbc59155c609ab86f5e13a009ef1c201616d9f3dfd08816988631464ea60baa276b8f114a8b25d11aa70871de5928f7c6924e22bfd23f8a9330e5144f429573b65d72c21697a070924a2ba0c96fb84b83bc369809b41dea790ec0ce2205e695cdf5e74f53a268ca6d222b21b134638ff7bc1752482f8c47c3bc972d15be545b56601eaf81b55f0d0d3c1eb7aa26dcaf3831ed471bf02cd247df61dd4a6919688ff15bf80b1f16f4d017a30d7121fccbd875372f28d585797202fbbd9cf499ab0082c1dc4cb12303689fbfa89f73765943e4e9c8615c9ecfbcdcc0554a06fb192ae3bd403e834b1adfb0fbf1e06e4f8e921afe7fe707fd0ec9aa0150249c80dbeadd8c3b00e409f83501c10450830edcb9c578e443ed9a81130731c15e41cc04bb00d0c19e06d28f873915cee7b67fd4bf4f1e56fa0f53a127d00337bef1b0daad2867259ead6dc3926d096c053bbec653f99cfd89e433ef5332b0eab9cf96ec78f7e4b9284330729fc23464064200f9e6a9d1da6b487db3c210763d0b6d3bb3070ea95b5d2604946214aecab35051e2f8bf9262be2bb49125ba70a599fbc0377f029a79f32460dfb0df9ee305211a2b4d190a82130b26a0fdad7270e88a5b455d73dc8bce88e1c9f4f6e02a2d9a558a1631406d2db441b32c513dd7b2c3739be0d2d5ea4b5b4a460d3d90af5f9ec20337c7c2c784cb9fd796b1b4e2ad9615a258bd11ac8e92652188896e7799e57f7f249ec23233294b88c8151b5995739219ef0fa17eea76493ee56ddd7c5cd47ff779c361a83f2d1407c584a4c9b64193f61d14bae8bfdd45d151484fcbd61566cae7b8f1eb51e8c958e398fab2bfa08c2c8f8611bdc7466b23230bc2469be14d2c9aa20de768e4368a6ed3848867296d88a165b009de62a00a4f512c93ff57a70dde11ab1078a74b5115be11e43c39517a5ca141346654353c9094d0d4bf35ceb90f383776c47d57420a0721040966b6c1d62e0e997f7c2d3bab51e0be7549c17982bf84e6950901acbb71a44458ef48191c6a7bc6965f61624b48fcfe41ea4c1d54327cb34f7ac221d063b2e0b25520fb4db3523d39be5f20c85ee5a5d88c7da538110dd9fda0d0a62e2391945220732a02885e7b98d0a58009d00a3a723ffb9e5470aaf7e0ef1eb2012ad42522930da389e2b7b8bf6dd8dd44687c5a40585df8dced13e219fc298c74662d9714039092aa6c3e1642880c6f8d4d5243dd87be90fdcf3fbdd9bf5813124fcd7bf3dbcab63201e88d252c876ac5194b4f6d086296d5935ffcfc5cb99cb54b966906a39cc28695a1851906484b392c27b7d195917dabc9f5b3958969dc0cecb925d8f55639722da479ff0d2c232c87d3981ef17a9ab12cc8a61b04ae12edca7f2a48f363c9d18d6d5819b19ee19003bd2e87455418272156ad11ebe17492f9dfb1d3e128689c15a16844f473e628f61fabd0b88fadbdf124eea6dad220042b32e11d87c6a1d5787dc6761127566bdab8357ed1b4768f90d6611483161d22b4a8389186d6996f643496734388306ce548ec410c36c00198ff25ac2f229b9ed53a29581cc744e2e7d8d92490dd28360c14c93598baac073f86fa123f80c531a66e8affc021df8e749432c82839a3bd28122108a8442bebac3434c8a16512ab55955018555f867b7642b9db597894a75ab0000c81f2d1ba821a1a87a1b3ec952e4d2cb8286934537700c6a8603d545bd127f6cfd3e51b35422d0b2e2f007e9a55c284005ddc70d85b2f8df037d3c75406614e0e6a1c077ed9d8d5632779eb870c6ffea54f21ce636b58c94e3a5c437c7d6976d98b3cfa85bde04e2de1553ab7a214abf4ef299652a954418e423bb0da3b49a2d341cbd563cf535779fd9630e88c4de16c02a17d7c36ffe88c10f29c1558e764ba7925ef6c544493b7cdaa8c64c604378ba3a02986fd92a4acdd341a8c0c7bb3f1f7d9d9b7ee58154979b526944e9f7751faa3274230904d4011379cf228be33625d4cec99b9baef68f972fa9f3211a9576535938589bed476b56a5729cb1d9707d4cf43fd6ed0b4905db78439c4318620aec3353342d9203c745d171e85652ada68c2171b556c98ef3f8b0b54a9d2cc0e83c6e5c83ff7c4a6ff5ba22e18d42cddedce718e217d9a21d08d61bebd244b612990112b9fe9cdec3872d4be6d386a73ebb4db0ba5d9f7e21476f6e9ff929a608f52fc711a40d68bb7fae3b06a76718dc9836f5b2cf5a0eb86b529d33183692b028de22db2127c7201a1522de8f22716d09b6cbf1c49b72fb7bd212cc57280db71fc19b9d210a6aa08410440ef76b5f40f73fdf1b7abb41b532e84a03d65dc1c8fe63badb0f5227ab847eb58772ce7dc80bac4287e1e1f5677e908d91a035e5808e9c907eef7876ccde5de7987da71a8f81ae3402dbce411f4515d24fd23bf26d7005d4794ccb965ba56dd83c7cbdfee0cbda9ff7005a1c6ad912a44754dae1a1da6ac49bf288bb8f89c8e7df2d49d98e0c92a304bed8974b46f3b0846296b6dcdc1910f82adfe392decbeb5711772e9f89cd384dcb39c44a1ee077eac184bcb95133c273b7654fbc32ca2a19d783555687aa08388391ab83687ba635e7c8117a620d2658e203fdd06f835623bdc0a9d1f67ea1899e5ec8ffe6224d1db6b3fab67e1252a5ec256ce229ffd39d41259bad1d69a46ce425a005628427d395c0971d0711cc7443e23f3e34b53066a47ce124d8a70c7fd9bf5d61716309d63bfd3aff346054cd1ea73bf0eefa90a1b8cc407069946ddc5b35f7affb6a2bdaddd9792c056c9aba563027e1dccb3119e25f723815bc87de3cbcc770ccbc8ace611f10e47dbe7bad85a2a77b17249c3cafcdd1ddac81f09bb8f70aaf17cb8ca28a5c3a4ea836ed7153f86773a160bf24457ba03809a6469a292ae03a58981a8b1fd87c42431a23ca944501a3c8be85c339c32aae8344a51882a537362f392112efd0a0f62e1b89ce5abe7b08e50a1a3788f8f5dc9e27d7acefde45394b27f77cd22ffbcb26002665e29161b6a908a2801615842cff2491dc1c32a4319dcf622f9a438eee9a029eb80f8bf9e3507a5b7ea938d7cd82169964b6e7d08118982b1328c5316951d699ce2b967607a94df334ec8db489cde8f03f85c1953dae353bc7580d248af29958f941655da334a4135a24539e0e9b88bd898e944d0005640dfd4fe42691ac8b002d08bb1f63c602bf4fed452c190a7b00d93e16cbbe4d24eef721a64d3c84063a32a86539dde55e826264557fce17c04e130918369ab05f5efa631156d2c60f7469b5ccd7ac47dad4714a4524af2a9586f3908d7b2b8a6803f3d6fb58d5a25a412963ac1417ae9edbe25ae17a05e2debc7b470bbaa96e75c3a5966d9daecef89f8b201277a91b8f1467691344bb74a9292822fb6a2fb56be348509e9fc25c1cdd7a0397fd918aa89d6cdc1d51ce9ea0f12ea90cf71682ee34c3362e86d4438df91269162ec7afe9c55b9446a957c4c54a3ff475145d9a6b6888117db4e5eaef1386c221a36cef00c16a2f045b9975e15386033a40ef364c3a1e6c33b74e531ed4592de4a34a84388d2c3aa5a23ded91e4095e5c759960fcfd6f92aaa77372c47715a99b2c48d256de89b11c09c7b5fe2fa78ff22af4e45b99d2c964e4b11c5a37764aed5b30d611ca8c24441e99f960107b4dd07932c5a866d51a0e0cc1ea511541673c44bb94063254860f2679491490640f8c9fb0eb357dbc7ff6c4c2c0778f245018f4da9eb1ebd01ded5c649568e9c6711d811727595ccd4fe4aa0d30647f6796dca042f24746ee7bbe4b5d8d97893cf04944666375771f159ece94482db13f4715a4cf55369689a23c53e170e0bb06bdfd2ee15f9a89d5fc132ade3713db680b7fc368d4bc6b39f6e1f68e7aabd8882deea7461df0211c5426e8cf441e863fdb02e051f5f2f3a15ba7555ebe67b8fea237905bff2e1c2722a412b7c860c3317b6b882db62da647dcaf31a8838088e0c3c0bf4dc411d807e81401e72bf114dea5c1c6436a1a298f332be146f2fec813f1bb54c5707f06be8707c016c9c5a31013e10581bba3c5858750e91514464413e7e88f71a89c92b29b5ad9859b920d7921c85643e18b4f28a75b7060a7218eca89946b62daab95ec39468e071c3861bd773c5cc8b3b639fea3fa278d65a75b9693f2dc0aae433aa4758816d5be7c52e8f64facaa5e78a359e4e39da8ba28991ecf200a3eef2564310bd5b2cd0bea1978c5a89b1a8f5898c637afb718dded1d2f173809134cf29ee3875e5bfae417608fc2bd3dff223a854b9702f9aedc06302d0681c199142edf24eddd33505133192ea41115d8d5242f14791067755ccd10ec98b1b8c4090d4dc5d1ed2bf804ae8eccba4a2d6b4a1b017080e105656f642bf99a80f71d98b7612a1c337048da3d10769c12373ab3c3866560abe4dddd8a01ebc0f00bf670b36e232e160ed07f6a4794d1f717cc2e2daec2e579baf1eaab862be879a8c33f7b9aeced65941ebf2bfa66d2662585caa87724d639dc1a87662439131371e71259f6307ba194d70fab5f59190a0f057b301220076dcbd02320d558602f7698781f5a609ffc291d339618cdd6f1a69e4fb7eccfae65c7507d1b361d880b2982075b676f64d5f3cc909cf50f833a2ce390eadfaa0d41d7996848ebcfff4333d9cd080eb120ecb0df74f92347bbdddcfae579dc14eb05ec23fe354b5d0f09fe94beb6d672706319ef985e5693703c818356242601effec71d2f204746608c53a1ae490d4a2dcfde8ee032c99d7057d6f635b260e811fd882eb490cbc510f7e6ace0c08a1f8e38406963829bd55db708381155a81a52c2b2b4f67796144d965b2c8d38908aa8f0372c3d91e0559aa37365a669c661380d49c107eb60a4c81440c33ab19a71234d167f8a89dfbed148ec217ed97447ace156b4537e0d460728b5c280385c65d313be39baa27ce3f44a532fcd56577947b2edb9bb24c95b5932778e2a6647b2e9bb4df2267eb0ae8329ef9ff284191aef781769b9575becff7d51c5d028974b4c3981c4f4c7b0fc47614d2a99791868f6331046603f3a2588194fdbbfdd998f8049a565bedaf51df07d6892174d19b4876a3f5b1c4f7c5e414373a2ad9874351fdd092dbf8f6fca5c3c645942809a341e7c16c2ad354c759dd39a81faafd4cefa27aa40a914d3c4a5c0419d12e10e039e1166564e1dfd0d1c11991b2f7075c212b69e1b2b1287a387a133a7945213e6d503ce976a5aaa58ea3cc4b0445b24a2c145bfea82778d1ccd086baae94c9518df7e185e404fdeda70e3b0f2dc53b1928b977cd4d9dfb526cf374b9e4b34e740e4bc975f1ead44b2632e1875773861a996388d9566c8037d19943067691b9d7a429418d5387314e69454c91ae403f99d717c92eb0f78fba6228e61ae2e7d01caed2c9fe645013b56b9926db39f0c4fd6d067c8b3b9c9d0bf72e040195ff6b7d6b8bcb5b78b302017869b8690e2ca1a728c1e17d9d154ae12031f01129fd4e955090f31098d3a1655ba9b87b307a9c2c78af427e7196470eb453410ff9870e70669b60651a221166ba6299bb95571825247aaf92e028c1bb5ebd9915c5215c462c8f4b1dab28a95dde7cf2bd1f5f76a7447dc3fb6521dddb6e7b1bedfd76c53d2eb43eab18b23a4bf06d4503e84054e87e666ae0339f87d1fca555bcfe01459818e053bc99aaafef4d5df2810e8db206e31068459c31538142c9311042036c65e98090f331ed60b0bd4b77f30000ed4636610e83a2a664f2e3592c7144fa89ef3e78fecf4fd4072168168a2c5620a5c60119e48cd8393fded6962b22d5f9091f83626ab1a0d1e47aaf37cc3c647590ca6450844d460c4bd8bcad09294af2f527bf63d9c9457826755ffe4d1494839a39b7aadf80198fd179f99d2260bd213770a450a58b7f4ffabeecb6c0cdf066210570ea29d05b2905aa47413d512980a404195bc1ebcdcb6620b62b120142ba720cdd4c36ebb705bf42098a0afa6b4287782b38f514d22b5829ef4ecd30240311c795e886988048f34acc6009a2dd95a82aafed03326905af4abbd258a09f834a312e5366b35b7eeb502ae405a21b5f8b2e68c3a9e71a28e7c2699908de08a9385ed73ef65351af80faf37f78d8a2473742f6df73d2f508cc790c8c0d23539d37f74bfa2a0254fda72ffa2b3acfe4d2fb99ae727738066ddd0b6eeab8907d00fe444a38ac1e8bf63c67f93a9de4944a54deebc49952878bcccab035b0e4871cbc220a5d7dbdd2aff185a6bd5a118a345a29c6c9111bfd96833283b6839664153dc43646c2deca06755095c82098e30213c985dbc15dd1bb967370078c65577a50302ddea93917f2060be444967424c96f6bf3b2dfcd1002138439e400fe3d66dd0d38969df0f61cfea2fa041ac8aa7cf89b02bb63053c040cd416af653ba2e8b7b9271f0f56f0438a491614917de7208e63e4c926cf514fc97a7884602c348dc20d02d14cd5aac8ec0ce7fba996e2ac4a2dca2a347876f88a3706f6274e21a91ff43596fb4f088360d314b7054c25c127840c00a7338f00ea50f952ca8b5cc6c3619aff0278317a7700498a3053c764c0c85331585d36466ac76e78b7dfa88e42ba633ddd3f1491f84a0c2cc1a6d56f78dbf4bcd48c53b9e6234e32d29acce12f06f793634c236afe1f360c294c78feb8d548dacd6cb5f861ff590b7d8041037cf918c25c5687e0c2b69f30bfeebabd6b76024f11fd04855e36b2f5666de151b8b681cdf2d97088a20388f0aba54c08123bf6982b2ee4a300ffc1bf292f25a5007df8b73a5dc5dea2a3226fb400f91deee720601ab4c63df6dc4ecc54661b9a18c93fcdb0199bc410ee40192b9608f1000b3a986ace01e7823ba0027cc1544ab4a003650d12af1227d66db74c78934d7216f378a3d20711f15723fa7b4bee9a880a1fe5060282738b75afb5661f95cb7cb635f42386dd299b28b9706f6c08b0a10cacf71fe250bd90b246b430972b6cfd8a5727b62119ca3afa9918aa36424121fa1ea0638cd4e4c0200585c1023d5127e93cf585a05dc7d485fbbdef7b68d6911d0ed0f4ab6be285a842ddd051c4958d8d9d89d403560b3e78a2d7cc37d08aac26783f31e607dd375f4c552ac477809712073838d30f4e57b24e729d580b781294d763b25c7d3f55e34fb5ec46922d948d2d364951129ba75bbcdc8592a67fe1355a5a4b42707d14b938467a9d43c5fea7a2ae564c0467281a6e3ce255ab60f0b7eff7f7f24dc7b057f2f02fb5479dd954f90f15063d1b4db248429d627e8f234dc2df15d639592e550b3915c7d8ad4e9e7fdcd4d43480930c450dc942630f3238c2e7b176e912e10d7ba2c590be9da0367e4ce7ca02c63a23dc3e2aec3f6e437fcb0753447260a556f8409e24486b2fa72ded0c624b14b0c53313c40244942de5f76e89cc44f1cd62124f48d18c1a0da12d775e9c40beafc8a36770863c9a58952e03ed141240ba04e0dd4cf88b11507b9bf58bf4746bee50cf3792d1af80db365290f25837da91f429a3d064a195876158a32c6ede883a0e245951777c163106de1822c82f093abda3ce1b5fcf61a77972af3de3f7e2892ef55dd25d25f6ae7df6b01927187925ec17feeca6a64f00c64f1646e4d3b52cf75706bda87454d01115b5f67202741c19b5ea04a3c9ae94a999346ad0e90c74d519c4aa4acc6a4a58affa368c5b66afbde5a6b0f7c4fd1bfb24dbce0df84693c0260f22366cb1b2d2aa24036ac494717f296bd1f4b6ce8e75a0451910c3c3fa2a5e48ebf0e25cebfe2738a50b44f313e9486e6ae1e50cac3f20229fe1934a7005665dabffbc725140d13788984a5c12b0c8095a648699946bc37ad362d2ab79a84cf8d4bc0346227a52b40b2ef6e7f1047bd98022b88e71767e8eb8f61f70c81436bbe0a17abad222d0010c26f5b0f26d4b28a3feb1eb2a34af3ec491d63ff6a7da0aae0fd8ead6977fc02b61961cdecf001a6984e41fe7bc0b9a8ad24dc6f3789b8d3eeddd443de6bd3a12abf34ebc2ee7152cc298369f2720e10371929b3b5c38e9a6d7ea91aa4e14c00115beb87b0aa0464eee762d5eb474e77a11bc9a8fe1462cd018dc18060dc3bc8f0703178b3177ea984321610afab35cdba70e7539a8120460383d11889645987b070c481a35e713a2db71acca2e9d06046a41dc2fff6d4d9c916443b5b83b7d99fda4a3b0998c292787b65e8219da5edc0a63488a1c04c37e1ad4469a2821bb38dc9467f05b70ca3f26e640c9b14675042f5125c54b060c70b3f1264c61d0fd0479034796340290a7230060b9be7969c07dd2fd7c01dd33f8542c67665266e51f59946be712209e1e8bed1847982c4788a20839aeab4cf35ffea55e9eeeae1eed2fe8566c81063f3fc8179dfd25004a41e41e347012c2dfe66809d1d659bc6ed1e4a0977a229ad8ecf356105dc845c1211758837d55fbd9ccbf6f7de4cf0fa6e9423e2ee376b303dd4dca73b490eec3ec7fc1253f4fc898802d593edf976c9b38c5d1f9d37d0f7e6d5cdbac952664913596750ef5805413101401176be1fffa292bce4dfaa2261300a0041254eb1ff38a75645bd4c1ffd0446a281ff6f5b1bfe820ed69d32dd47f8cb6f81843801e0f2ff3b52c103dd60abbff87388133e294c9411fb777bbc9818e26e5b80eedc054c40282b280fca915e313c911aa5a0d8f0348ed7072d0147c898f2317b2490e16c0cdf0f3065c035724a19245de8926f3647db59850f32e8e29fed16aa27df418ae91f5c93911aa0205a29e9c70aea54db6c261a569a8864f1002e89db5bd07ddf76ab8c00c767d1218f40f3db05d211f027cdf7debaa0e731a95df72381ef094df45cb9ca0ad7a81ae600c2df4977fb722de929c7f0922f41298f1cf79d0ae17a3e46a6e16cedff9490356a5d211cf8dde32f949b649f493065afa31f1f34c325c425132d0b9baaa196b8302354527579ef2b759eade6745eacbbc11dc4219cd6f1a01b9f05f82952f8233485c9f57079107a80aeb11def45b06c5b0c46a3c7040677bc09de93c9c3b21be4877e02f90f01e77492f25260e3ee9764df120b830c48376c86d206b5c0b2a4b4311b845fe757b7522f500e92aa636b539c14c01fc2c20fc956ceff9b74fd087c3314dcf81ff8e24c06adf0ffc669634bac29c1cbc0d142588bc3f52de2881b6290ea00750ecb9b2a9e204d45102405fa58af25c02166482632df5cc525c751ac1873aeaf9cbe6893f494c999b4bc2c68837bafa865949bbdc3c445290f6ddfce018de6d9bc19ec01931b1c1bb918f34755fbbd61f4bbb089c60cf9b14b169ff0a077754b47a9c4945a12ba50830f3fae3d6fa0e199f5a77211a942351979eb19cd1b9d97ad5bd340873fc5c19e7a83028fefc6f571883490dfbf1bcc41e89c0b7a404ae77b6b2318c17813d9f0ff29b1b147d49cc7553646feac11a47bd2a20c59eaab4b730085f06c0e6d19fb26922f1ee1f05328d76c7e11f0aa90aad0f7aff603d92d3d5eb7b99ba39ccfad6ee3abc1f400c2d770b0af645bc652dc6666bb4f24b8c93400179e52862beb6a1254c97ce6e9de196adab02b623ddde97d3cc766173290cded705e1a8e81519cc19836b77bda6478edb5ea9fa41ef2ed53561d6a4bef3e62ef84f0ed339b1eb4c9a5677644b12824557b609541f6690976ec64d7456dc1d712ec6fe573990a63537742e586d07b7a6d5d6b7a4f2ce3db2328905014762c809bf622ccbe50ccda3742ad30c9c4c19895b5681e6f338b92895fdc217f2bc408dd02502f9027723ca9b8640abd22b39a4332240a7634d9048838e0c3f5c18acbd36f6f87e8cb9ecc7abbb8089366a7d5eb6c3d01a7842c4af95fd11fb3c48a793e2956b3042e8fe14bba5aebd16b71cce0d5d892645ab16ab19da271bf8b755d4938cdacac96e64da98c20e29cc4acfdf1b4f56851e2d80e1dd277e1fd57c86d05340b602fd52c8059e3e3d7e90b6ca7bb78087c0d1fefd90b1a17a4db4cf817b33eb1d507a83929f9099c169f67c27eb6d83b85f48da368c4ab28db4a76de41bd9967f55e0fba0a4c55aca8222a3b79b53a7cd58a462f6234c2d210c60a7adb9d69370a20993a7fd0e60f22c9a0c84f3c4b8404f8c09a47f412e9456f8babe3c636f2500f28d49dbd3abe8678d4dc2535af179d4d2dce797e0f26881cf05b0ed804922dcabef989484114dad59acd856edb10b41400c10dd2dae21503072d95f2615451580a6f8e3222dfb890d5dbe27e41b46ae2720546ce8947d2e6c8866e7467366def50ac1f37fad5169d263cff7773dbb35f8b563290a2229df50a8df80b2841ed830a81e21ffa07f2435b0652bb8c51a279e60816aed7bbfe5ea35ccc2450eb2b3fb5728bcfdd1e5357ceb26b597bf2b71872231a136e67c36845990d8a959035456032a63d67a5a30145ea503994e4191ec1e2aaa874b6a029bdd3c29cfe5f63c1694a45895bb0f30e2c0a823ea2fd6918ebd058120d24bf33f000cbd49fc7ab310afae7e6b5ab39dd9481695cc0a48eca57e98a0f6edf52dd99b6cb9a59429a51475077307a907d099497bfe9e821dec58885b34dc62a1199ec80765f70df91ece8dd6a03c839ce9b1ecb949cd63268ee59e7a64b0d670344ff31a0d3fe4c73e9e6080cffc5c19cb9e460334199c3a78bc1b2e2e48ba5946506f7a9e8b870e560de892cbb5e41a8ad262c245c9e55a720d452162b96a66686a6866a6ccb2168b8b926bc9e5e2e2ea2f5a0708a00ac46a535b6538fee632c3bda79cbb56eadad59ecfb44be9071757abd56ab55aadafbdfc9801c9e572b95c2e170de887f250ae191a578d2b860ba929caf52ecfb514d3329454f8ec0411bcd4f9f3b3e7e745aa5a8cf7776d32f33edd62209583542c757bc80191595c4e539d5b696e37a05c6568af3f08e7466b50f24d1c0886542ce34080d948e0b404850a25966035a1f90915d0da63da63271858d37e25d39540e6655e93e156503c009fbf04503c003f7b7982811f76363054e881cf3a205eb5af407bdc8f36f39b0e9ff619ae37ae57edf9db88423346d51b29f413126fc00ea73d98b781049d9376a8da62d29e3b92f6dc6d7ca19ee448edf9c6c4855a67be7f659329eb3efe2dcbba8f5ffbd3fbb8c6cd6721b1cec73205fc6ab5f2b195bb130cdb84e924242714a93dc76213047579c1d23bbcf26f2425cf5c9eda9a755f67a9ce318ca53a9defa9786ae79380fe01d37d3ca7ee536d1d4472ee6c0cd18f4e28b95df21d99b70983c6aea886a391e11ac991609ac6096d6ad9fe9d5aa0f72eef4e2dddddbddd0db7683c4c544a9807de2e2a3f90de591f13892641c15ee63faf2148a1f2cecc333fceea6cafbd6927c6f3f3f44ecd0e7d7ede1dcc621cdcee803755724de8d3224f758aa974915cd3a6c970fbe3dc3e5127e7dc1ed128e2e48473ab0326f3098d829af227a1981cd023e8e6dc52716e7150316ebec62fb3859e34de3c2d736e6ad9e6cc6cce6d13319e38b746158339f598266c621b746e8b284e14fac1189b7308f0817e53c6e6dc01686824cdd89c3300adc9e21c8faaf1807ed0b91d4ce6131a0545c5b3a27ad60a9585733d558bfbf313655e724d64ba7ec935a1dd760bdf66b3395700cac5cdc6a43b0b0f1695158fca4f41457902c40a6a1555b138bef8520c2eae2d6973903c65e5886873aee01d9cc4e2e8ef25f848edaf41829eb039b7332305cee9987840bf89c2e65c0b262c9cd3f14d709b7304a89333bd0c87c3ff40f1d7b83dc7b429e0f838c939f61a0aa71ed88d630ebbcdb91cb4051ff4e05df483ceb1508344bf89b339a7faa2699173386a70a0c2e6dc00ba702ea79ab81b4d687c6cbed0af860ad114ed6d4ce8dc0d19977302d887ada453fdcde449eda7e198885b2e88f66085cdb9154e3882b7611ce4362fb5f151eb508ca3f105dd0eded89cf3b024657171692fdd45fb80e68bc780e8077336e70240e96989016ccea98063730e00345a10aacd391c9a1816b0b03997c26f3feb51284a592cae88cce3f03f321d94cdd2a9fe66a22f9a2af58959fbfef321fc98d75638f598a9ceb5f445b562226e716cce97428dc7ada76ab8754a04b3be3d3ba8e5d89ceb564504d89ce3e83c42bfa96373ee6990b02465e1d8c5b192a7963cc553d47e966271d07cf1543f0be80423d06fb6b0615c13da563ad59f718c85e324cec22e14b8078ba3df3914aae909fda04d127636e74e989ee27678886c90406980f563a2130f28d6cdf6b4026cced9548c6b32f3936322a6d21e4fe954bf9c827eb06773ae46c5b826f425c7454c45a66322203467a3ba609a01415a0cef88d5c60bfa4158dd7fba37f48636e0d3de81f06f1847671f7be4c36fde617af89a16398f5c57ad6bafff97d307a8fd156e8e7f370f7ac375e56aeab9f188df577fe7c19bb3a71ed039ef2ab150e89c066dbc58c85b64b2621dbaf70ea0f70e189b9b717c9a9c01e5f7b8bed19b3bc640d3ba09cabfeecec528bb5bba43e85cba4008210c1146e8485d60eb7c44e0af167d1b07be38ba75f7d2254e8de30df30e3861cba97295ef70159beaeee391e1a43d825150e835bdc39b5466a1ca2716aa3470fd3889fd0ae5c5e124e88fb168dee1244ef5735d15eb6c7b9c456d156c75f38e95d1575e4c4a6b5037988c527a49baf0d07d0df04244514a5a355a609dc510c2a8292cbc1861e2c50701e4404f758bbcbc4005197891818cba455cbc1c89777777bd07e70fb5226887eae88bde689df87d80d8d92cff77361d4df1674ee8abb808b63f5175d152151c8a8a54b9010de20e2fb943fc886508b14b0e3b4001862b2a7191c312e10ae6840653d7284912499e4cd102830371050cf08570922cf0935c1159052076eece76842f00156adf50a576078ed73c6ac307e23562988cd371f8bf19a7c296b638951fd6f0135446a11d028f69b9bbbb3b33bbbb4b8feece746803407c77976e7436c18068d105b010e5e66616742bd5b12240bdf26cdbb62c7b53f7e33de53f99fb28636f7a1bf9304fe427eb6cf8f4dadb48e0f4da9f3a1b6c03b5fff43612e663303d8ca9c3ba184e359cba205ac79265475459fea8fd9a8f8f7f5201aa9a71369889b3c13a096c6f83fde989fc642f01eddf068b61c3be86adc3ba186a78223fa66f55e5eefb51650744556568cf67f061ee6ce4f3dbc8ce05acce24b074581d56c27afefdeeee1856dff9cbe6388ab23226d67fb9fbb19c0cfb31b4e72bc38f0fceda407bfbab045b315a051fcc48a23c3e1ac6cfc3a30b4cc6d051162e35c6960ef55bd7567494850918b78a8b1a8150e3df88b2fb9b257e7eac5facd84709b335a2f163e499b2e3a992072e9892648b4d0c7376447ef6b7bb522ca6ae51932c6a7758f74dfac3a2dff415c4000911484a29a5921d322600e580a48909905026a07062d54f7c71021efebf091640a8b6402285d0a2c3115478d1610b0d3a0a2962600458e504592069a2a5dea8a1355ed7a889154e68cbcdcf809d9dfd3d39806de03e22bdf1a9477cc901cd1ff9918ba1573ff297fb4a50bf10c0daaab1f3ee2b410cf4477ef16d1460aab103414c855d13f9eb83aa7a9fe655c542f3d7c10d5f885054b78aba39f0c2a546098f66bc25a32641ac24f9e32f6164057dcc43edb701fa98090f7a4a6d278cb8a27af7b1d26c2c5c8dab803210cebbbbb8b104b19cc548ed25b5d7df37f04e06b86dd89e2dbb535d3c3d3d3972701c8dd861a0f3d99ce6d14259e9a7b99d6ccf3a8abfb8cff6b030b03dbbcdaecd699f9882148a39c675c03b1bf9fe1fdbc82e067ee8e37c54bd93b67648506f43825f4b010a9d2aa03f54a420b60895795136ecaad50a0a0a0ae2968cb0b165a32756b539cb4b9be29f29a8fcdf168157d4abdc3577a94c4434b9b5571ea0a500853a11acb3ed467887e7119aa4f22f2fa9fcaec3f6cce7ef2ebb3337c56f43e5c9df4a73528a4211050505316fcfd3c43fb66e3db55aadf679797fff019bb31f143429ea9b835ac789608f9f6750f959e096f3cfc9cc44ad266a2566a1ee100d910f4d6da373569f1dd42dedbf0ee22a9c72c94e36e5bf4d6c133d3d3972701c0d1adc921dec5848e3b8a5853ad1f6c0f7f7d76a402613b210b77a4a7bfe454de479d0c32dd53f460ff467d51e507b9d05ef2cb7d1f696a2edc1de86af504ba046d002550595c269c81e7b4d4a295fc3ae547e2bb2d002938f715c8922514f691da5a5a6e2de49d5db4b94b0e5cf4cdc280653aaf313db93bdbf133551018a7c9f4062b985663df889ad90fbb88adc42f9e57452a3d5b18daa176d4e134a5390424d2b45e58f2888c8777835dd79c5add841c0f49ac641272a47e4478b5c07606783d9605d0cfd3df810f0518d1c1f55c86dedf721ac6794a0f5c3ca5159541657d2c6e88162bb9caf529042b3ba3d337b7eb66103857228fb5dd230bb4e82e529e69ae81f5a89a0cd518259add36a9d1d6a1d9bfd45d23a41adc384db7699d59e867a42f7d4a36356ebb8bb43d6a232e44056b7d005f2d4f2f2b28fcde1ef1e720be5b81fe887aaad04a891b4cebac01cd81cf6b13dfbb13027a528d47a1116ead7409cc43f27a528d4ffe7f1764db439fc0ee4abf6d8573bfbc395c50de4acd9a720e452ed3a84973a80ba46432c51b5de94e6dc02d63e3a3a6a94114d62a72c22e65ba8a7a8f9654e2527aa44bd71059402142772c5852e872e560a4850fa7209efc415f32fb3b4616510db637afafd72b53dbcf207a982da520719c4e62ce15467dd278be20bb0ee934ac0ee93466cf749225ce82a579bd38f6384d20eb573ebd591efb0ab8196bfa0bec609fde2172d58c787faf4302fabf00ebd82b282816416b55f3e219dec4e5c49a3f8257e9142dbe39b66caa0cb7768bea78ccb559d88a6531a6a1da905c3749fdcc2c8897c2752147b353a510d117465d8546f922bf90e7520fa8fa2be4936d4fe5e6d8f33d2f6ecf397dacc456dee52794b6daea2f6c7952bb9922b05c09536a73fc6b81e520eb2c0d2f289dc1c00218b2bf90b2d9dd48e4ad854b752f003900ae04a87da3648a12446628e5fe2176050a9fd722585a2f40ec792455a678dbe78523b4a24416040513fd94232022308f5934f3e0954fb393252af1118536a7f2cb283fa451e221224107018aed43a6b34840a6abf7b699d156abf73f207949f4f326c4ad0fd4d09caef10908fc3fc99f99898a72fa3c37a0d7b882814a548744e4a55aaae407b5df3343f239fd854bf14b5bf0aa5ba45c21c6a7f0dcd8c944ea4112a0959446dd9fa47a1565f6aff4a0acd2003735e6391f65a0ac52f9bd32fc345256c0bfd948b602c01fa63b818c6b2d06f02a211287e59b9746a2e8af6d2aa58eb6ceda56269b55ad9f0b3c44a11205793fa39abb6890913f57325fda5f6c36521b0d47e9c661254fb864c5d1a59e5d40a75bb1c75bb9e8a6aa2ee6b6e05eb70506bef4af0ce6ca24af52bb5df93f01f76875b9bea4715f1b55210b5df83b647e968624747be33f30dd37d936edd87cae487a25dccd73e6aafffeba30fd68f839edf63a5d78e5ac7ad60cfb1b052db7fe8209f93d25652f220131065a1ae4382a600857eada4a4a198a07bea74a05f077590efa8828e1ec0eea9566d0f7caeada4a41463ec3eba425dba297e5e4e6dce7e9f3a4606da5229a59473e54af98e75b283b07b50d5d6d322c671c58afa8227155279c1120c6050210cb8a85015a5385185922258441942f203ad547845cb912184b8c1d5c5163f108a20c85042b7f20ed3096416355601000b02c1aaf04a0ca290e669fc385ef7d57736528ffec5213254fb97e3f83c1e0742fee6f7719fbee1a9452aea1cf8e112c40227f343082184703e67a6ccb565779ab529ffd9858bea4d5c2bd81d6eb50baad3d4af5932a8fefca4394a7d9e2cd45e2b5b84d94eebce6404ad47726c38438d147600be90b84e57cd1f0ed315e55ff72c4d68821236b129236cc2c464844d684213fc1d09249cedd1da4d0e3388b33dd3835128f4364e1cc6f1f90d1e38aca3539df26a04b039fdb1f3ce9398c4a2d79ee76d4e1838dbb34daca71235e8c8448db1c6e78931c6185315fed60005157ecf18638cd1852a460405810809546cf9899da5c62d6ac46a8c31c61b75594a961c434028e68c188649214241d7a0803c157d67a5e840870844d19212450f11e081d2cb2e2855ab8abad862071981a32f9dda5547e009a04e6d8da927ba50c20a5350d30d01d0820715b878f1c30bbc60b202165e144151188dba454b10a2c2d6124dca0ff55bf85ceccac085123ec16e0e413b104695dcd9f8d8caffc256eeba0f7e0888549f765a5b6653fd348ab8d40c28e8477d053ce79b9ea1474c4e547b0a80d5d4c1d9bc5009e324f562bd39243ec7249ac6eb0ce34f7f08b963b7504b11df414a29e50f5030821720615151855505952baee400042b60384141144140c8a658416b872794ae8851b4608a1e5e43614bc056498e3046080097141900200846528600c011c5ea1a4979c1f4a72ea5f420a548fdbef20c623a5ef50e54edeea851942f5555d7e88820542d46b9c3585aca855a90ca1f3dc98e91e00ffdd86584c414779ec244acb6a2c2671eaaa8f07dc0e7221d42d4f4df9cd23aa687dd42609d3f613f44a9eaee6024f6e13bbcfa767807d3c9ae7321d67e46e21dfdc3e2800f77d48f5d7cc5d57d2aef3b0c728783f763020ee3070af69f10af3aa0a08b650f5f83f1e32e60ba0ff57468534b5424431847da9e609cfba60386f43875db1eb7752bc4abf6262e6e409b5ad60cdad4d68a446b53bbfbbd9bb3b51765377f92be2852fb073e77c1406e6a3f3f7f6b7d6ac23dde0d55054880f3dd78ab03df91000bec7cedfaf2582bf575f8e985e8b54ebf00ab8f28b4b89ec9b471bc9e694d23a830fbc180f6fa3ddf89a153bc43015e60e8f6cec7e6f43fa13efc7df80560e22f41f14585eac1962da4173d4829a554819722546104282e20c2059d2404138694219a8a2a264802bc92859222f1e33671c5bc1206108e6843b8c088c88a14459cc00737cc2956469a8afabd0f2d1e27bc07f55b24a4badf2a1da1a56e171f688110aa68b9c1122d48e2c7b11c61a5ba472104510a8660a2080b58f0e3af4ae1080d848c8c8c5870c38fff0cfe85d6353a62ca1147645a767b365effad3d7f17bcef5437e7f48257f7d30b587517e00bd9e905b9dc524a97129a60fc7d7a6e4e3fbf8908b46b3f991e802350adaed11142d81b1d11449552055a943c95f24265ea1a1dc13a0248059de8eda671bcdef13ac5af52f114151d1d2121c9d87da1df561f7513655c0f821d4542f977773b082184104208216466ee7677a1bb37ef36ec61629cefee32fce973be4318338fd9c4a07c87d9c4645c8a02e5668f2d73df56b185fb37dac389c9fec9e35aa4221ecf3d1dde411f150f8ff612f0791b17949188d062988d8cc0a1327ba89bcd41fd6eaaff5052752f0249f522aaa83b447ebab97768da3069a8e7ac1046201f9610621d01a38a255354f80fb1183101054208e1a90796a37e5b0e8690d0208a2a290a5bd48c410aa0b8e0cb1660e030742503362842020a1f562084112994cc304b99c54e477bce037a315a70a37e5bc56680a562180d84a818851edc2a5b2c11e40589269696fc402427a298004e3a37fac333685529df291d5aad563b1ce105cca473a3d93eb1031dac503f1d15f2582a589c7051e14348045185464418c9c0880c7cf004880a9fc7e7891315be0f1fd6088aab2655da3a271acc099d60590192a7763a1182110864594ae221b1850d50758b8e0045b5d28c8c60c1beb0d59f856ce4b141961bb22fa43d66e6863437ff727333fbe021419613b2b44186c8204286ac8fca0b32a48656dadd20429a0767dbc9757f743e8a509cda36b406d55e3f1fd12bc4c7d6bf0fb70b226408f6dfee90d6599e9352f6b129fe211c7bc41844c8108fa73d5e0a524377b7c733a4756a689d8f37d03142d426e1ec7791cfb055d7263ed728e37d5a4b323284ba2e33011b5a49467e508178980c34b763f4fc966f0f4de5e5d5af9432cbb239b1971c08b03a1f62523a0c1c06ac7c9a0f8ba8d4dddd2625e0869658fd9c60e0971d10afdf7cc871434bac6a9f1c306374b2f380a972c7455a45d0cf592c2424f41d47696f635ed39eb34d487bcb4166e64c2205e140b043b86e0b5c5ea406b3ede90598ff1689b7188eab0d29d4a789ee2e96f6564224ed2d0cea20ab3d56dd77972bb5a40775bdbbefac2f4b4c7e7ddaa769e6b7ab756a7ebf5bbec3937ea8cfe3a11b43083f20f3634e3d32245de602e78145aa1d8043754f351d92b38b9fa6fbe6ce74dfa4b4fb509eb3f6b2fb78ea56f82d1598d36bbf9d7a9c3ade3856c241ededdb9801fd980933691d5eb5b73f817c27884dffd6227d80dd656dcf4cdd7724eec3ee746b53fbbb9f759f2ba9fb2dc4ee386bdfa609f473d6909f1833289d018d988052f92643710f399cbfe1792b6954fb655996bd7c9f6ea19d936ec6dad4b6507b926b26ed2dfca73931804895add6e12a65c74aecda82ee774ba875be5e62d24f5a67c906beb7eaf2903c3ecc25a04832c00717ea4a5ae7e101647436fcfb23e3f77765644e640c3fdadb476a269dda8f5cb7322c5a75f96568cf480333b4b7fbdc7dde02121894df1ec6098912c3a694d23d761edda5ecba854bdaff407dc6d8b967d3e1cbce3d7670c2d8cdb781e653e343b8df8120aafafd00a26a61b98f85baec39eac28f4788ac0e39fad9cc1a5ba8df6e0fef3e8f70dbcb511bbee6de428d1f2b6cd96d1c39288c4125a0220c5457f8f0a8fbd204384cd01d361b6a7cb871c2232a61c224e878f1e85015c173e2068e14aba3bd8552aada833096a0db9768eb1be37f83aaa11bb682083b1d081dee8e7686ee0edd89488e476032bb2123460dcd8c0c8d81396d5a669a37ac563c28254d89c51831d61833bd4f9b30e639e7d6850959c4706664680ccc69d33008fc40a2800c491998c105a4cb15083119a974dca360e3841b9b1a34503364c4a8a139c26225090a5ad26a11212464c4d0901244444d141555393aba828464455252162e97164a4a4e3049e70ca537287884224c663c37363568a066c888514333234363606e58ad8c00011d61b19204052d69b5881012326268688a92942c624f63604e9b86fdf8142dc0c711047e20558007ea42289b586366d6186a52ca8e060d4229a536a1d4349865d9369526f59245ccaba19991a13130a74dc34c33d6d025284609b1a247c1c60937363568a066c888514373c36a650408e8088b9524286849ab458490901143434a101135515454e5e8e80a12921549494a2e083119f9f52f145e365e27bc6e5e36af1a2f1a2fd46bc64bc62bc6abe645f39a79c9bce82be605f33abdb697f632bde62bbea494d2a7943e6334b9c7d8d160828ec5d88ef1438fb086eacf32c687f26394cf9551e093164c663730a74dcb4c533260ca528cd221364d28fcc7e3ff3b00f7df1675ff6d134b2576dca360e3841b9b1a34503364c4a8a19991a13130a7adcad1d11524242b9292b27069a1a464d2dca4b9c91402697a8dddf421e82ae794262d738f99d330db7bcbe0e61daf87612eb3cfa64f475fe218260a26b3f87abd5eafd7ebf57abd5eaf215f6e58ad8c00011d61b19204052d69b59e2879c164c623be6cbc4e78ddbc6c5e355e345ea8d78c978c578c57cd8be635f39279d157cc0be6757a6d2fed458490901143434a101135515464456935a4a3060dd40c19316a686664680ccc69bb61b53202047484c54a1214b4a4d57a22243444833324209df01f8f8dff7a78d838e1c6a6060dd40c19316a686664680ccc11162b4950d092568b0821212386869420226aa2a8c8cad1d109ac19f9de29927d111ca8093c8c307ea0c4c741f50365fe37e99ae69ca1f406857a08461631c905f3bc1c2a158f2e72c5ba2123460dcd8c0c8d81396d1a669a37ac563c0001c5e0998586c9ec690ccc69d332d3941590c115c3d80d3730a74dc34c3332c0c789a50e8a68fca7aaf15f0e9bff74dcfca7c373635383066a868c18353433323406e6b4699c24286849ab458490901143435388888a6cd85484c92cbe5eafd7ebf57abd5eafd7ebf5ca5eaf9764254a4b344c66386b9a7386d21a2854f7bf82e7e550a97a78787e7c8a16e0e308023f902820435206667001d90076b4c264e6c557cd8be635f39279d157cc0be6757a6d2fed95bd4caff992af1a86ac260ab89545ecf41f0ae6bf1a31ffddd0ff6cc8fcf733ff7534ffe1d4fca7428cff3c19ffad30e3bf1ba8ff06a0925c503364c4a8a199e17a78787e7c8a16e0e308023f90282043520666d8b86f034a34045912c1901b562b360204c44798c54a386806674f3099c535cd3943690d14aafbbfa11487344cce68ffd56cffc940c5d7f6d25e3550a8ee7f054f7239542f13cbe70c9a583e67d004a18931c69e1707d65d319618f6d8f27c3c11091aefc8fe9bf03f53fc6f9bffc198fea3d80e5ed39c3394d640a13ac6c138fa2197c734886910c3b48c7971ccdff9b0e7cc76fe3a176fc02e4a68b3757625774787f0e50eddddddddddddddddb0218410368410c2861042c6b1ce45dbd581d07b4743b82b3dba943a107a775dbbbbfb61175386685d232f5a35092b758dbc4842d535f2a2c8b99bf93eeddc56ee36fe1a6817f3dcd1ae06a89d1e06a6b379000dcdcccb3c09683a09c8689d4cb73f9048e537c51334c524787200063f21b0deb4d7031802a5375a68ec4e0dab68b375761f528f54833176b32b4177b300f573a5947008ab1c080dad8290c3135aae349150828014456b28b5f2c5529ce24a500a6a50b4260917248104126134667d2eb62fe1c77d47653bf3bda34e3d078d7f5de030ea6eceeee6f4360cd31b1cd00f35e1eac7dd77329e1f3304414914b7ae11e1f620b07824f49b3b284f39ca860a14109e6ef6807e1e8fd6b9d13ab1759689708344bb0685e3a9183e0a158361ae61355840b1179a4e87e93e140c46f331cc3c8d4cf779357b98ee9b35bca7b29fe9b8209a51ad63de61defdb5a3fa09e98a3dccc7701f911af37dea11f31a9f5c680e7b98a732446a0b8995761d04c6015333339853f7cdb9751fd53a1397753b6fd4ee38f6625454b2551886614934d635222af2bf0423300a2459832d7870a4a3f84f5d28e8f71ec70382b40653f4c0d81596cb1a1cedf02fc591cad403742e3249f7a6e7993ac36e4c4079e3c137aaea0bc0fe7e00c87e46f9180facced5d3a90767ee9ccd1b5e53e1110015e47cacc33887cec59845397fce2e4ad3ab4c3894670df40ab7530fd6d89db9adcced796d08bdb339a2692b6b6b304a6c9adaa3c466a6c9530fafcd4159db9db92f46754ed5ad71a76e9d4f067f3042df1cc6d8157e9dc39999b48dfb6454e7beaefa6b9a6fdb729f7fc3f63e186fdea142a8c44cda688296d10c0000044100f3150000300c0a870342a1509646cab03e14800b739e42745a329ac6b21c4761180419ea8001c010430040000666a4d100d0df795064311ead65739644e40f58d6d7d089910dde5a6450c003bd139a4667ee089c9e430be73a8ea1522c421564652a7fe88484262d4c7de3442e4ef095221ccd1477b1c01742d59e3aa3c6e52e799b1cff4561f37e28ac7fa8340ae0d1068d3c4e120163cf1eea548dfe0bf4beb6593d54af47777ca04379f0fec2dd2023f1904834a15428953881b2ac036fbb044643a4427c471ec9e520e7ad5707e81ea4bb5ba0c62270eea9deb49f2f50599a0e9c6d610d24017d80d3a1af5301defb70de48e57814baa059841ad02f0f1d09b6542fb7009c8d5a04bc01bc8bd6674c5c8a265273a75554ffbfb4fd99a50221497fb1c0cf988a351ed7adecce5f0d696c3c5a6d394f4f1b5f0caab64af889fab749fd56ad7bd06a8b72d83b1130f942ea13c1a34ecbf1812577ca9ce88a0df98353b6bf924231a30d45ba4a4154174554da53a0e06540ddd5bbd80bda6c712eacb2d393e50256644ca044672ac681d5721d57a3d2b36f5405d131f362d02d360f7086530543bdd056e6eddf7109841946237ad1030cc04927252ec439263842eedde300f25ce859e460148ee615029f20850064bd01235ede6813610c8e1b4cabc0b2d70d78539d0182fa1f40e5e7ef2d82ae070ca3b048407507d8f2e7f7f9068c31a46d093d8b956fc0840cd5ae720f788626094a750bd69c9ab65e7b8035c270dd85ce8e1f1576e0f025819324b9ac586f30663ee34bd12d18f376b7cdf40cb4ed162c0d75acb7411c4bb89fd10db699bd050b32b46edfb6b6070ce27a5c241fb61d386cf492097ccc059af8a3d4e4151d119235a768d469e6e1d565b567de6a635441691c91e989be5333902c92ec5e6cd308b17a601a8b6fbcec7c83d96cbb208671c05380771a8d0b0c3fb01ce3f1dc746f70d426ba00501d3f5dec5202669d090c68718fd50413988a3736373ec778e79210a88896951cd1e514514a581fab28c82f151e4da366e94782f3f1cc0644c571be3c65830a381fa0364adf7ce615553fbc65f8f26a72f4d6fb6c8802e65f61435ca39db3f85d08bb1f76ba6c8c3e0712a57d29c52e27e3dfb34edbc418de55a8696282919a75d60a099e5817c4d81b227c19f827ac932a455832c5bf4be1bba015ab1742b8b3290a2e9b9c2a7537624ee69173af4e34ab5c49b99b3acdf78586521b7d628addddc1a36859b8f79b8217b781c8f9a4b6b5827b9a1411aa811218fdc68b5c1c0678ca25b5081c0e3152db45eadd4e8cbd613bbdc991b758dd7a5e765a9b0cd76ba891f5c209e04be9196809ff53ff3f22632d9b47cabef33078b50740ed0f2a020bc8e1eeafe34422364d82264cc67994897afd3465dfe7944a7bc6e2a997e58ef464802fcdee263af90836a2f6456aba9cc6bfcb329d99f17daf7ff2a909b494ce5df9bf31ce92bbc7ccb9e732906ad4ee29a87541ee6d76349eccf5c9d7c0814262d4badc4123a4c6456da52462decc5c576d7df7858901dc2d0dc4e88c8712637cea1d1c82ade48f21dfc7ebb2e7e574b54cc80eba0634f80db44d8dbad324502308500c92e8eef143c24328020d45474dc3a0cc2bd01ea2880f1be68222e47278a9c6fc9382b08621dc09b4729b04c0f0e63d240dad460d77ff87d4595350c42eb728852205a312d2d76990c9f1c8589fe4f5b130e2194834cdcb54bb388c7f977d7a138e3d9ff8d5caeeb69268aa4b9fc0d43bef0c71c3dbcd5f1624ca8388d0b2805287b3f16fd9a66136baef238a7c3280add275d7ef67c499c4f69a30e7bc7f668c2bed1f33779ddbd78c0dc26615d33e7df328ae240e8c8fcf4ce12012810aef8ff1cde2e74124317de6ca06c6e5bbb7a182ad3c47c65067bf7135b1ce697ff70979407b833d413c20ef708f8007b0371c9e8db0f939ecfe9e184a5acd59e91c9d118338d091aa496bc48dd9d280ae2e67272dac33c635f0a1ba7dbaec6ca50c351344113856a3a59b731fa4667cc076c0158307c29479f835bb30b24c4523cb12808b0eedc8e31e260a18082814243048c902d9502516e584269c4fc8e50a923dec5ff2998a842f9ebc68a435962aad1b0ea76b7e793a917275110c2741018cb14be13ce6526387427d424f1e13ba53b4add431c9349f50b3c61f3d8145d6be94aa1b38a8e89213b584e434a1d253546f1617772f5e838c071b9127e40ace55ff197b06f35d83245b85ea3de48088a4bd2c4597f3b16fb3a7c36cec6d56d7b1ae3ad5bdee3ad791f54533837cf5a69a32628daf4f4729ef062f0a7dba733b7c8eb1d214936c081c953662ef163e7f0ef3ac14ee73bd0ef6c2049e1b0bacc8f69514a06b623842675d886316682a7fec877e8fdb7e0ed0eac30edfc049b69a97f05533d1fc3b387705fc2492301889e45595ae072f3c8c7eb33e420846a20a5c82b23668940c50cb45c23d467fb1ea415cba14f47f037a70ee9f8735bc0da0003facb729d6a0d0c8ec68413ee73e37905d0bc6de20c572880da62e3251ffb982d41c8f2e90906532fbc2d2a7d25c6747b0511a9653a66f7ac49be061bed33989e6f61b6c90dc4be5bdc3be29e1aeb4c1a9ef16a63e094e6390511fc83e453f3ea7c9c69d8e1b4b8eb7185e9db238b3ebe78810e2eeb7ad202bcff13112d149f469f42474bea1ab1b469fb67a6fa51577e5b8ba236d484af528b1c59fab24742877332ee36c2b08022aba9cc045685f7e274b3ab3c7df070bf839f6f3aa7f978549c77b6daa1de3baf7c5921f4386ef2ff34066265667e2c45376700089aebde43b96749b8e7fcb4a5ac0119fc22ad2d841b284f0f63f353c88fd675a0f69aedfa8c5f655b6caa866868907981936b27d7f19619686d503d90c97178534ae06ac93a59520ba355be79ccd1cedd67e15497a3c997c443ec9658b23f1b5f79164d52132b29a612c6723d1eb5ac9b8fa9f21e760f1d2c142b937cf45b41e068fac75015587e3e36fb249677e54df3fe83c318075450f819cf4f4b843a55914ca0da4ea296f3f1ff1a510220a5d8ec022b45f7c97acc9a07e477da97c98ae1abe554431dd3ce5725b9888549e545ba8ab119cc04468b444d5f11ddfca9cad6e7c4eb6f3b9105d5d57e996b4191d0e3407e46d068b36518a1d6ea0a3b52f7d87251df3a36fb24a03f7c5db8c5c6302b94aa5206179293f052dc9b3eb7aabc04b4cbb80848953ed738a210d7ccda514ba9cc1476f3ee61d96f4ddad8ad5514f76c840e205457dee2ed0a9479217e90e66bcd8c31079dfd0bd32111d8208e3091f20bf4f6f4332f128b596086ad7af5e09de29efb4e7c07e657f098fbcd34112aabe7b9fe07accc09279f643e3b8541f16f5ba4f32019bcd54c1eb531607796c2d789e33ec72c23216461ffa8ceaf9fa3d866307c0a8f6e8ad6c5a0b1de239f2ed0d923623fbbcb1dd3dc3ab19e1a610974a85f73b73ea315834fa5a42b2f7f88eaca71d4ba180ad2ad6c5c77d213cfc5d4a8cb2c3e633c4f38b0f9052ec8ec2fa0ec23087d612a77d41a8ae25706f71d8146c30a9276ff05138c6b008cc40ba9bf70f9cd2bc6ec67090291a34232045387e6d696d03b96703894d074ec635d0c15fc9cb736850c7ff4f6b98685ccc06e4eff2dd1026323aeaebfaae06e2b6633b998790b4bbadca9fd68700d9338de539cc53839f6a9a8b721d880bd8d9fa196dde968eef8e37297c57dbda24f3c4def97686c7350027db4760ee218dbad664b4c05b1735c77cbc73b876000e0dab04e5c7a3b34a60dddbb1a60214d874e29bba9155e5cd1475faec963ad9889d4abcac18a2bda43ad971332e3cd0f961263b155b63b19f6689acc082ec27d2b72a38bb609078dc85bd576045873172549ad4c600bc0e1706b5bf3426dc6ca74812025330451d81d8dc643a16817af9e13385942e3c6c0de4302501a43bb027fe3d18b6e156e19aebfe6b0bf6e3bcfd181cda0ed654b92a432ba0ffee05f613fc4bea680446ad040f6d0a2ca458b65c050769f94b1fd0ff492b6a0b6fb50ef3bd9063238646f96ae75a893ae15709aad73aacc2b8a7b8fe44d356fdf879ec319ef9985ad49e17cec4538b4e00bfb2c3d8495d1480fd1f1752767a18ee8459454fce06c8b02450d6dcf50dac32e951453b0c98e1fe7e2e3de61566cd3079f4bac8fe404acf07237d0406210e2f7f800dc2534ee689f48eab0fe0af6fc8e60a821759fa76383dad01a00b11f260b7cbad6079e05eabd455f88e608c3615ae191cd60727e7de3b08ee483d60689edf9bb43f765e7e0c47ffd1047d13dac247f1268d2c8e17a765d6ff48fc1fed4b3bb443605a5ee55b42d54464497785436a0f74fa4669dea8b3921012c73af3327dc75f82126a68fa36b0e8b059e0de4009fd6a20d715fda45227b9c1d82e37007702c52cf5cd929535a777269869596a7766301d9a734b98e9a8c8701e3e1711199a014feba06b91cba96b9234a97e3fca33b91d122db83a8d142202854ab2a3232761483f03f31df63de5cc63b58cd8b61252b9507ef8bddb540dc9d8b776b8b3174dfb65a9d626ab4219b01b1018d9146f59ce21a0e7e386986366c79d2552b165518b868187328943d20724055a8b1cded27b78c287b9494110f2e8abfc61794176ce0f999953ec14992ccd6cf66c85b5ffd798751f23458e4c177ae9fb3c30bad18d0ec743b40924b4a1f2b6e89250695e6d01935e3c0dc9ce3f2c61b062e739390cf475be0eccdab1655502b070949a5f499d59b9ac08c19cdf28c1850e5d2d072bd0eb64c442f1810fea0762972bdc05e69f2dca7de0a19b63f62c1f8bad0b49ea1be7c963d85f51195ce9f1bd0aa38a80b89c22216d3e88e7012934b65db8af45671f79eff83301efe623453172d0ba0cd103319619761f18fc4e34707d1358c32015e5b58d2e1774988b84db402d051572d3f9497e57ba965e3e3cad30a43a1f25ca25b43901580108d3e6170ce01806ae1a09ebd08a04b3e1169dbab9c1aff4cc3f15fc8e5e0eb16fc9ca8d0cf9fe54a5da107fa6daa580e007636c7146ebfdc2ae45a23059b58d90f203b48680743b458c6fa916e3a368d1720cdf07855e13bd1f53e9b0853754827d16f96e2d2809ab3cf1bef14fc37c0d36679f5a0ffbeae887b99a3959b64faeddd853285608e2fa0c9bd35febed6fec065e0d983cbd8c187924757664a8a1a52b2b8239682a5f8d825fc5d821967a86390d3f67f8587cc2a46a274bc406449f1052635c7680e4e9e36f918cf459759ebe40f5cdb278094c7a3fa635c163f922471f31902eed7d9e3cde038abad625f7da1ff2405cfb3bfe5813747a1607bc223ff872c27663e9c8113ac4b81c569b8adb78072b471bf497600deaece3631666a400f9d1d4993bd3ba4a98d7598443f529d7b2949584e26a8740af14e66aeb2974dfb2908a269e07a87a3b6d1910f956deffaada1417e463f4834feb0eba9bc4e7a58344bafa5de615bab8fbebd6c604a6c28f82923c423f7b18ddbb6c0d221a0839a40a9884d694588150112893a8a72f7c1b4984fcc1f0b666d4868b0b6f46490710d47defe952a20bbe4d46929a59086fd39989c6ff4e01f33775112dd31f0a1c6b9dcdd8b1e40c4aa7e3578ba4d29742bc796cc7fff3dbea996345ff02b705e253a16c7400479fa29d257c9024a38eeff5a36f386354167a59e86e7d04bd74238bc3fc1c020d8ef2eed09bf0c5ab03ebc5dac26c2670a53b3545997577faa1e952be4843c1d474001a87bb15bb92d4b919809bdcaaf77a2427b0113d4248e67253782de84f09487167295a3aa038e9091ea44ebc09b7b2e963b73bfb07312b55255e0777fb601fbf2ad561399947d7b55b36cadfe8c39eb7f07a606a4c348d4dfb7a7fa07c989f72739c42e096be1e0d3c35525f4799d89fd26273a32b7e1f2a7e01a83b7e4029e125218276b6e5568d97e7d5f78c449bfad2b66c991452890e2ec07bd555f5c99dd50e59212de51211af6cb6827cc6f9b17381056e433a18e4f3c132a4c6fa4708fcf3078143f80a1559a6d8b9d10a0689f4fc61425db27380b00e6cca6a2b6047de0ad42af6b6b7dd3adff083c0360a6214cd9e984c8f0a48f8bab19ea50a2b93d04bd972a3a04c05abcaa20e346732c91b58a5944c1505ca36ebcfae15ad9c5c0ec3f3cb6acc271be14d2927f4afa1282d5fc7673887d6c08a279152ec4292fa2a7e6a99bf32885ef541c4fb45c9d15b181c8511ed5c9d4e44d8b656374146cad1bd6c602b892a816efe15f4234266ea2f44d45a8abd90b06e54939d05055bec508c5b76017b815c893115f164dc0b9432d6417efbd137f214c8b4370813d997f26f2f0e8c44ab8ea402d6defb8d32c1879aad03afdc204fa3b03a8ad00baa06f5196b4e0cf582ba8e2c110856ff46edd4e524583796744266489d9423e3782aa9b25c515402456613775356028df4b348b77d9fab9612c22285022b40352346627a1388015821ea8a747d8ded86809e215402a9017250fd61b106f216d465a4c6e822f930ae7f723dd1c57a98dd91bd0bc3a94b7d01560477c0846323eba415220177449bde08ed83a2c3504e52958936ca2639b5d8e6a71ca5669686015f1c645e520dafad39addb64f3cdbc510127c4e495b55201d0afc324cdd87093bd90b6f83d0e0404a25d82dc0ec66a1bf7b601f80f65bb70edbd5edfeba657cb077f4969d7c5e323f5719a7bfb8662ac24996a8f4ff1a8762f057fc9211fd0cae983efc840ec734e605f5e6509629b18e64620206d58253bb22bb03f480e42ab31a796e0f5c1ef57625322cfd184843e6bcc2a34c1f58df7222a7c3790433b7b6cbfd47646155e420cdf42dfc1a27c366d1293ad92df4a7d01a81cfbfbcc98a3de66d935c8afe64335bf1480a8af8d443af41b0bfe830b940a718fa880fc74a83a85b7952e907385eef739858601124a21e0f9a4c898b21e04eeaab7c59315e9f40da080a04b94bd2403714ee0caf44d35319f9ca87e1a6bbb5f7810a100f957f95485e85713654d9fed3233fca2ae425c10475ce586bfbd55db18562ce13e57a293b11411363781cb950e8e70e38012d6c02e4f06251476c14a2a30e21f8d950860e20028f8bab834f1efae6c6e81c0ec8f4c834273398f06eac1a065717b58410a03c109633270851a1f7636684415fa4cc530efff811b7e2571760fb11ce09e797d0e599012013e376f2e04c6c68d47cf077ad701153f3ea2fa478348e50fcb2e05a1218e862bdf9616d603a8fdfd2adff361b9fef6f8b19edfcf2c1dfee1fc8e86ee47573d83ac16d7c871877bce5751bf1fb09948bf858d43d70f96347b052bf2054008810e84037daf658b41a34c7fc8678abb1b11eb37b2de8f8389e05bcf1fdc816cc736219bae92a531fdcd671b2ea485b6e56463d81ffceeaf34ba5062eb537249b136b9564b2a83373bc8e934bfed3d8c66ad83f0e5d851d6cb23c18832a22f4ef93e2641ddf13c8791f06cb2eb2017ae72138f89fe1c85f2e57f2438348e6999c0ddd0cdf5438622640b6ee30796a4b4d4fb517e425b1aa9de428eb56285414240aa00a418661b5724124e29d1f08af8f6dbc8e834f574e6c085950342ca7209794a794e14fd583f4c807860e87029112a7c58db8915d116499f0af0a1cde3867eba69ca6fec51a16c8bdc9e750c35181d4f7b54624dc6692a9e083ad55f69109ba79b2e5a7c8706b0eef5df4ed13f955833c4d9949e2e0d44894694540fff8cf50e253a26f4c094f4a176a162a8ba2be16d894b1c4dcc572d01e04e8d3fa6c19e007a0886600ed5e5228041dea74138ce05528cba891a70b935ba81b208001a75e29d2736b124a85a6ab50e440b147dcaec352575167876bb07fba9e06f3390bf2e46039aea0188f8653456b810805cd2dfea5fb7bc626194c123e97da879e63bb3be3b830c86661dbc29d29651e32fd9ef156c95128652ab3a47c6b5fad20fe61d18d1220bdbf6ee0dedbcfb5f549e765ee92eaa151908ef72b29baed959920547f96ecf38fded3dcb668c9c703d66f37efaabc092fdb4594b46b673589c67e4df6a2dde5c9525290f9272ccc963edc4cc47133888733c4bcfae643754f248e984f476e63653a7623808c32001a191fc4ede45b04afa77da9e2524d9b7e62f44bdcf8820c421022116874f40f2338146e3137be335016a8271ba53dcbae0485fa7a147dc45253e19c2995fb68b0385cfa8ee0e7b51b0075bd75d651fe7d3ddcd948edd303d9dd149c607b32d0a90174e7fc22f4828b0403040a1d602dc520ec239a1f6ddf523322c9fcbc2ea02585f92dab2683442d7fc519df92e6f0b0a4925c5224814128538ab53962f56acfb068e00628c54edf44a2c23014629036ac7211e07919f61ca1ec7bdd6d8cdc431e8a17b15f65268d5c5b46b84bc09706b708f62613b480bef02adc1b69a6d5bf041afedfb1015a439e3fd36516eae779024b7e607e1162b1ff69c5cfbcc25210fbe20257827019ebd97ca25b9d8c639c913c715a18636bffd41acac736f050da63012a53f6c1bc918b21b064008b7c952b505e20fdf3e6ee9b3487d92442d100326ea15da1162c12039e7aeb3698754ee526c182ac4b327596514a2dd126671513eb609fe787f0c98e03b048881f613a66cb636ef4b84e0d4066f1608439be18e23e60e42ddc1d423ef57f7094587c682ef243319eb1523482389fcaf2eda2e0e9a635ab67b0b161d8c4612ae2a8f7a465881762b3bdb04203e209d4c3a5a1d307d42af73016e1c4a47140d305e5d3d32df9b6780e7f32a5b7b7532ed50614b8316ca9187ff652744db4e18faf3658f9dac1000d88cf283a3fd0e5d6ca229599e6aaf4f8ead73048d5c0803e47a5516e8e80defba2929ced244a30f268398d2e6aca80367861d605b2b6f7881fbc4bb86d0b6c03bb4704782251d3935d398863df66aaf9531ab00649afa44bc478bde633e739abc7aaecee9387af290898538521e1e1160f9429f50769daf79506e85f3d8187c20d89555cbb5c398c502e376a80b5a5d8f853ae59648e99eeba0672e121da01c3f72783de68543eb9f24feb5d9dced675119f758ef2efe309099d5c33536eea5fa0c5960b9ac90694b498f4eea9ce02ea426d14a9b1bcb9bcfe8f743c69175a6d5f83aefd3b8a60d932c24bd6bb1945dc905949138db8b41f2da228efe3e7ddc80a689e77d80398d4b50f1a414ab513a3f9abc133a91623d6d20b8ebfd2d65d872291099a3fb4c807b087aa0c1902bee1e3501cf16b2ca68898a045e3c70974da81b4a0771e81bd8e54cba3ab409e92923b0a21a569701e95f800d2f061c752c3abb3d8debf906bcad8b952344817d84a486d4828201d84bacde7a8aa2948dd5488446ed65d34c3747b3631ad3e9f16735ffc227092f697a856f366b8014bc69557d6a7062139dfcbafc5c7b1c36662ce66ff97d3778d183693ecab98cfbe941602d6e3c60bf6a9388147a45896e29d4925fc7e12c72d32a25b5b256206640e8fbd7224202e584188f52ac593e13fdc7d4a5e81337af345796bbd65894e0b5e86c19d9c8bba0663dc6bf4a4663b167bc14592577c803916b0d4e2467c1a2d12ac1463744b18af81d28cbe8f822dd51c1201c4275d6b79e3acd08b00f475003057088abf5fdaa30571ff0e62e419c8d086994cb5163abb796e86943142a8e622a6efa5ecfab5041db15e26d1ffa69136f7ae3bb9eed26407385b72a02e81f3a2daaa6e094e5208c38a84624c64f80fc81d7d9caad4b0aca4953b0eee10529b4a4272f97ef05b49630273dde627f6121e39afce8e575917826fcc131cf113fbeba77f2a265f3eb38611c8a5418bfdbd916cfaa06f4b95fcc0ca5d1c6e5bc48cc1e5d221e3664f098c008ab98cbc929299bc6dfb89ad30580cb2d69830d9a885934218be71570c08f8aac52d4e32de961368063aa7f72a913d98ffa0e09c9d377ac42652ecfd29241ee6369a7b3f4b167f4d7799ac227378aac77aa1c431796c6cc27a422919556157f6f7e7aa17aef1d26239afbaa735b7f0d4941546e6e0bc27bbd4b848c2b55e3f7b7bd639fbc31ffce601225043bc09205b307f020fbf96e734a7f9fb4919dc3c30f82b8b63a35c88b6e007f6f82ba89c30d72238288599711e000fdfd84d8d05ccce8b02f63a5ef4af63764936f48a9b443fc621cb15e439b6043e98eb58d7d2ba85d0ca354025c35524c05393588c75d4ca621ed4f66197e893b5c4e5b04b9496ea94334d32c446d995da7c8d59335a42c5a0d4ae1cda57b3c3b5b6d2c68cc873e056726b059a22e7435eac8a5889bdd2d456c6bedfe386bf519ac9dcac4740f7724eadde37cf22de3a243b3ed97a4fb834107019bc8b97092036d60095ef0eea350d29cf0be4f1e6dde1691453de32a5469d2e675f91846284cd5ea828a85746cd7346f2f8426a1c19df7cb0815d9e9d35290772034f21c756f88d5006de821cae6851125a5da3dbb48b8725fd3a31a25c3195c2e8ba295521c62e856e66d7b68c3d232e2dfd4170939f514605c2a22f3766d52b5005b92c11cc0bf8acce29789eb33ac008f303651b3ff7c645650391f46d4cb1a87a743c524c5842490101eca0a0b56eb74421541bd9f0ac39683603c50c99efc3704c9bea057ecc451baa944b0822355e3f59108a67da51103337deff97dca62378c1d07f17393775e3800100e73e5ff1c24bfcada77d9fe10226546939a9d4b58d995fe679981f0533832bb11a9dd3eb602c7f65e3d35cb15bed3a6e1d8b11e1aa40177b45ed3199edd77e0a2efd584996c46a8913c6fc8589617fdef789253f9279e76c161cbe4d3a8f439c28cd963cb8163fab40b9a0cd8de13abf7c826040a5241fa1a8fd1324eb40906f1f40f9188944362e983d39ab89b87d622df8b32b19c1dbc4d97c4fb9b4cc287ff50de6ec9487bfc6767b2f1b781c853c9f139e1dcebe1c1d7927514b68eaf2b0abeae8c442c775a20598b91456910c819132d19b30fdce330aae2ef32a90db888ef5e09fd5d747c5e54dbf130af6c29677c120ea94e61812530b76b1e60c6dfa31a3a4230c36a90fbfc8846bf932620e3e28c4e863b7992012014668cca1e7346d593a8ac436de93830077cf915f948917d0c21ec2511d831b32ecb99c77fa3c8bbf8542b01ca252d179aed4c24109a2bba3fce9bce75b80f13a3ddf96af9094e3157edf29411e677afe1127188f442ae59a6a5d2ed26c9cd0ba8d9ae3560790e40cbc829b4950585e97bdad5b3908e9a4e108f6a3274fc7915677b74a16091904c21b546bb01beb7c3fee40c8b41ffd07bca9a1187d1eb35cd2cce60b1cef23d58618b255a12ac6a5f0badb9af5abf40bf5d0f201b91b4b9cafe4b0d66c60f7146a6ee25ef7f750a81d6f935027d00ef70e9f48b75d800b0d305db87b8d9e39a1d152fbd1abe6e6dfe0340c70fd2887af99a04a36f173d5ee7210aaca7db26727a5e5e261dceacd56ffcf3fc2038d07ef4a93c12699dae49de9079616e9b38dadbe6bd1c3bedc967f150ef4f02783de16eb8b74121287049024fef877ebd8de1682faacb7962a8fecacd5370bacee8c2302191b0fdef9a79d4a18cbe5ca5ef3bad66fe55fefd8d9dc35e08e8bb11ddbcb9bfbe9b122a6b76b8dc66d99a064f26faf3096a4de5465ae8dacfdbb4840f9334475b8357d42140802c104bbbfa2fffe827859ed650ccbcf5c7dee37b288b34158a355889067261faf5af94d6d2e2cec9caca7edc1662e68a74fb41d9d5ce39fc9fb38539b89e1ad71a7fe2d658385463eff43d63f106f2171c92c559885af31c0ab7ae85a8e031208e899844278eea1d3da9aa5e4a94e8d9b73549ead2085088f13bdd567e1e2e23923072ab76220881d015e3fb390687449bc8ad829ab6286823d18bb8440dbec107c72d15f281e316e4c0548aad0fd8e9d7e7706a427ae31db9c2732d4cfdd671cb99d5c7ad6555f9f583a60930ec2da44dc5423637f9a3b655c284b8269e9a0fd9e446096468056e7a6d598190398341421726f366ee0261c6d0ad1203198378f1881d9ffe0061319105af7f2f9fe91357cb5033999c0c0f32496a4cf22878368556c146eb1ba76433e3921bfb36dc09e1fa738ef170a634a64ad9c2c93d485a7dd7d38a0895d936e29284af993f425c224c3c22c4916790c311bd120eb970b43307cb9273c98501ead19ca024ddca20ed8a78b797d4b164b3223f8a33790f94f1daaedfcdf523808207e8383cdfc5fa2913507d176d1630a08b2f8a090081c41ad355ea28606ca789cc2aacbe421e13bfffc564ea6e245acb8f7514f6f94da50b60fa95a3d256697aec17c633e1ab278fb4c4ba3acda78ffdd628cc1277f9432d2c681419a8973500ab4ded261d4b42b3edb16f4198e89302822bf11727fe79a5143b43d40dfcb1ef38a6ad491a788c4bda413bde92ef99e44604db7fde66a0ef8a3c4c3a30498e5ec09b2a65abbd1c10e906853cf88a05119aa502b8f8fd03e9647c81e1887ef754ae16fbaf6e387db28643b8b66a4e9e7faf31d087f5e1350257bc5c802a07998bf666a33fc3f60b28d9835dcebf8c5809e622378670814c30b9dd457159fb26b53e1c95e168660809b882ddc1c3d4549288db429866d34c76220154ddd4ab9e5596c13f60e13cf96bf5f831737c841c40aafa8e905cd01d7ab146bfd12aad52c7a685d8515377ebc87b2a9b1c3e6a93e0ac6a54d68a86260ddf635adaf7b09598a7d21c060a04e0c9273540f449264330a1d8d3f2a33cd214b7d4b76489a8eb05246f31457281204fd66872eb4057ed6284e4ad0f8095c26b3f54a17ebbbc427c68b12586e5104ff44053f4ac1f17aa6a8a59a4567d7e30b5394b69ac5dda4b22ba27d1c0251d2e60f627cf05e2c984cf15f84e0a9842896dc232d1347dd9a1fb0cbf58c2c039258059c52731179d1fcb8913b1c1a158858a408172d333c2f894e7877824f0b1507daa2733aaba1cfcbeaf786dfac0cf36624f6146a8173df41f0daaceb7d50e5dc2472c079d5adc8361313e20d8f575c031ede05dddbb7865d982f272dce26837d7fb61c6990e77379061ae6ce5b9fc9f3355bd4ff9a7db73f940fdfe0eb48a1152e89c4928723e9edc14bce90b22779c1b72f36bf24856720cb33f6a809bc9d2ad421fed5227bce558898c88f3f23114a453e7af0b0a760f9483396b571a37a4f255037ea0b2986f9a1f398bc80869d5d7d1de832cc72a10645984cd021c490e97306ef075a842a3791b2ca4816f3d23733ca16e917342c1203d8c846b1ab1be32eafe49924c8e1b81489d892215e90c59026138d530d1577b2f8010057e218bf92446dd05de7d6b9678420e0ffe060cb9412de06edc13e639d5a655b08ac6e3523aadd118ee1b108b5902e9f0cd94f59f9b21b25abadd84112b91a60ef254c0f394da418e64431e6eded88c865a976ae8b6332b92e02c324ce0238aeeeb94f1776b98401f04da5fb7e167fe892753678f7bffbfc1829c2ad70529723034e1bd36b9ca33716114aa3b90bd8c13b4fcdb9fc8a0235715a1463a91d7ef8814842283b1258808f4ad671c0e7611d7ff28f98300b79b63e266b7d0443ff9c99f7ca59936cfb22543b319f4ae0f8612416d7ee803b04c63c275d721f6fad0b60dc7a053fb11c72e33a78424addca64160ec17c71c5f0094cd63d2df90d565873ce629ff1f0bec2412a5253932cb1f931b36f566f757061fee36a8b4c4548793ff9e71cf318743f4a1c9488be3994f7b7e86c6e1281db3e77804bf2c7622474d9d9c4ca648988fed0e6d84bca4aac8479dcd701568470bd61f0c0b2888a1f23a35b559c0b816acfd6c1e53b892f17b9ecdb8650498a2eb11c812c800ff12a02b563beb2ef9efdcaaab337fa9572e92eba5aea0bad4877fce4843427637b7fd88535a30829795afff493e9998b5aaae17cbb6cb83b561a84061beb8619da0ca7a2e5142f10096d9890c65502ac423a1b164c23afa34de779d0ff0ef6d883f44c9628dab15b6d5dd7ce05118633e841f563d65ebd0b2614094a2e1b0de1f9c3f230f167c8dd67e2a20fe7f90ddff7b06f55b7462db05987eac44bfe9432bff0133585b7494c9a4c93b76edaff2e6d22a0e93e1e9e1985c9f8dda50822ae7747a8ab46ba238f438985335129a133fba59fcb64e969414be2ed36efd293956e3c89c5ebf846283b6a2d78cd2a5e3858e18c51cfa4255a0121b4af12ec4a3295fe949668d6907f7b5f0b9f9640b239d6d6367073680ab93f2cb1851ed0aacb307fe3145bdf49df5fbd414e3c225d645aa86c26f5896f1d7aaf13cc0a00cc2dedd5d3453bbba6830d804ad401b8fab7f6510dba3a21d65d5468e1107a7ddd63c06645ce6c7f89e82fb95439384811ad02a1ef00f16bca32421490c87f6d3d140d1ba6234ff9a3b74e66e774aec2807fe18700587a5090a6aa5f0f00e868f459d93831af24186695eb3c33d68cf6df43d2d3d1bd6f6420f1d7ef572d8e5d1d60d9f5071ffaab15d02417b4d6c94ace7a8246fb6bb4c2d45f04ec2f44400dac6fdc84798834340191dc18b137a836fd394798eed4ebfc39be3850e33893de7f928deb834d8cfa9342e2eb1250506f0d555007098d0a21cc8e4e3a636eff171ce4160a842f925ff50c3b1d257d699723b3b0d47225b092139d06c9043b87c0bd370d17064c7fa9b35ecfa6c0acd3aa3ad8170850d3c9a5d2bcf14221d77378e28dce1070af311c3c5a72247d9a43eeae38c0fbede23f500cb8a372df5b2c6a1437725dbb82f94dacfe713b8854c1607ef473d8b437bcc5866cb10a8bf9f7d056e6501d2e89ee8b52546ac2c1153f00b7a765eb435eba65bac805fe592142aeafeb5019d6d0f15a6730ce85837e2a08478c2dbef608b0c7912667f3576817d187ac4a22836bca24addffac70c15cce5604b0d7749d856cf75f7875c50b747ede9a43d3878708283283bf908efb14d4544ac28e96a7ef042a54ed91599496c40bf9fbfeea1a49c7760449c5d4f7008d7224d4b3829aac6b55a3f2295966d31821e20de126a7bc34a17d65f1db753ad4868a847c95a79b6d1b037ca9403f12639c5d94eac2e8087b0d302f914fb068290f77be34a0ead151b5e673273b54c0a986c85101d3616387644d3d535905e9baa83618092c54841479a06043804e081821364af4a5bd7151e77606ae4756e415a6debc80fc31e0f40665cb4521ad0eb25f09c307687630652570d83defe779d94d8bda202dd68b64b7f64c39edea7b911f58b975bfbc2de1c1ccd55bbd5c5d98b3e3a6a44899d4733dd5e73f5091ae98d83cce87a252d710a63b68b6b4d738e78b24b4413463850def2ac43fa0a87b9fae3d84a53df24655d86dd6a367f92eec4f52a2dfe029bf0d39fdf1562f38419a95fa3f1a9cb44d35b944c02b499304de70db1430a535522898b77eb812a54d0a8bdc3eb5c07f12e5c215def461a268a01fc77e2f3d43ae5daaf1092ad043548adba9db475f903985cf2075e1dfe217d44105466f6f107349c0290aa4f7e2f5d3a1dc1c8fde1ca8c79332014b6cc839bc05faa9e5405948cb91955c07e00dadeda8f0dea0a8e6cc6a6d93d20b769821f5aa4db2c0998f274583e6e5a648eee828a996341e1497c63118aab06c06c7195b80aacb62d6be6e8a10fc50ca56b9b94ab03a3c338e3a16be7aaa6b3d385bbea89c81b5b907a0c8c2fed07770ad3b1c8390f4c1215313e6981235160fda3bf0f46421b834856c83c6c42a575cbcd255cf4e9419f56565647bc1406f4aeceaa601732585496c4d0fa05b221b55179c211b90934b756c7c00afe15894635538d2267924c6c244a01bb536e08dcd1b35887db3d746dd7bc14923b54a3497da8637dda356beccdc18696cf58e69ab762792d874aa1e44c5a8dd577974d1c667f1e8a0aa72814bca3f937786b74a13198b9355606f8b11fb174a8b8b2d9c81bbafe90bebec204a3a93050328eaa3285f2f056199597bcc353e14b308bb5c3e22d91edd401124e03727bad236899b8fc26d0b51dcacc2b0da9414aa7683c3af2393cb106ca6223d4bcb37b77706a31bff303252e2c015dd0cd91a28fe85a9c28391e988ce70827c1db78ced988d71963e3e6de4b0588101375f33b79cb8d57c09a1c3637663da52523aab19532c031b883922e4ef836f34b515b3dee861f7907fa04c0cd4e083d68225d86851cb9f671c81672804714a9020acd0ca0be4f9c33eafd9286b844fab9c2a8580b5f79c7ed7992bb34c19eb5ee1aa38ebd620c999b91ffec0902d5fa933c47667c0628272ff3e9d58e623049ae0c3caa0f7223a48bd647a58ce1e95a62ba21b7505121cbd13411982ddf338793177c821ef7e2454dd2180c7e968f24cf53efbf7e962f769ded72c00c7312ea2dbd47ca95ca1eff0aeec8a8f331bd7405b44840cb0ba3b0cfded40a208730fe62e537c99561714de7d4f3c81e0824d7a045db8da8f9c88e1114a9a0c97e1fccb7f0126a95d942350ac132157eed442117c22cd69487a004c08d03eff97569e18adf23d2b54dbc924433b6efe4e20f42311ccdd66943bd5e31db7d4b1052446626486800a178347c5475274751719c7ff66073d2120652cdc9f1affc2a350583ede431cc85ec00dc441a6b529592a8aeea99047d257a05744f281982f8178c9a1364e21c9dd63fdae573e2b47f87b138a4b3597377a507e898492dfa61bb255c40fafc8bd9349896abe0a08b191cf1adb21432660a663656c53bae3b8f7db657878b2bb2d93e62ea399aeb6f5757e775b8e7fb3696da882d24d6f9465f1566edc0d2d6c08d2cd1a5dc56f30f9c47f78fedbd58d09ddac40b279026d513b7f9c1ab721dd1f8a6835218b0c62d8e03c6af48d530d6ba7c74ed3f11b51ac0b9a8f319ced2cd2846b6ed308a1551551dd598fd2f406e8731af3a8da3dc6fbbed744e4b22b3d73d5331bbb7245082ac9df6d72fd59db877d9b8bd442d0f671ef0eb2b872439c1eeb6b92368c52b662536b1e85645705511119f00949d5f2890652d1a71d5457674eb761b9d02657498f9b9f7bd8a2cf1cd6b1d5b45d64c00c7f2be2e546ab6e74d491898addc3e762c4476cddd4e5711f5a4ab6153e333204faa185c56c2a81628ddbf4004ccf4ce649c120d20fa33219d0196dcc0c7bafe2e34eaf327171a72a52c93528559ccc5961e1d502b158a7a701114c47f35cfedff455c23a67f5de66887b46bcda187dcb46f00c16e2225e88fc9fcbe5667a0676bbd527d371bd08eb4abec65801c226e021da0e0526dd1882755901482e8070572a43366ac23da5a14bc96330afb604b5367f64ac9fea96321efb0ddad7c329b3feee015b6a668e4473aa520c94ec95d4e367b50fbcdfcfd81e173052526cb65a34aa748476112c0c25dd60eb7a532d90f541bb948dc26018f4443bc714c6e67c0a18dacd5c6141b2ba8cca6bc820658d6de9523a8a42066fb0b3b56eed8f9ddf2d3735f87fc49ed82542fc2f8c4b1b71b1ae93c62ea07f1cc052c9dfbfa52f2f180943ca05feb7d5dfb04f57d4de9136542f9facb60af908e6c4a047496bf382a1658ec47d3a27be7027ec8555222f9f16dff7029983ba3bf0e99a913ee6f98ae03bf295f86469f935e0c0e89658d116a697009a0ab5a99a7434d55c25a770d4b57a741a55dcd7ae33ee33e819bad15413789fa3f1a6f116bd45c9cfcd8f0de0fe6c71814939633b8cc3bbab36ba416e0fc45a5ede806620506a6e5e222f0eae213c7e3a3a359fa4354a6d9e04dab5ced04bd140a0ed447f9ca458a9899933495f0337ae057093e52ad9aab05c6e4e161a540cc70e4f24bc7140ba401b836897e8651eccabf4fc3a1c0c829668dbbfc3448dd5cb20575753e7b1fb97a098386a10c9fc551021eb23eb681dca3c440b98b9bdc80723afdf5f22bbe5989755e2129648133ac04c1975118af613dfe9883d2982cfae922a19b44a875ca75ed3f5b92f704a2a0c8ac787e9f090f1b0c6f66223985544b68a5696f2afffd15601443e29d2afc6d9ffdb4f32b67e4e8720fb29102cdf4383e58ccbbafb106264e5a2fe47ff0f0e573c2e98b849a9f1b3129686a071e9b57540cbad68dd72e553dca204ee40ed99e0367ca45e42ca058f9c14da69349c183d8fc49d8236a0e81c9daed7082366627e9c95f72f67acacb320ed35c915d1abb8e49340e41bd3c04cc591a115e1664a9b6d8397097d9a6622a7c60e7869194589be5c0c7d6f0854dcb9f535625af328875c6266c3c0ff3df818ade7283bc1aaedee4b29e90666ccfe4e2b0d451f0972105ad695abde4cb846bd99573060701d477496c20f5c8a611cda86e46e6194b8b2974cad6b5fed3af15df6447fedb1922d54f71e325e7f27a8303fa42f60c14c6caab36ec22053599b1091540235472e37e774eaf461a4a64c54b9a482629495326b53b08c825e0d19a725f14d12fa7e2efac47bb2cbdc598af68f24e7f338c5d55950b2d47e09b6037e30247083a29038785da56134b57863725531cd427c13af0298dce83e68f17d13a85c2e28fe7d34a05250583b1a4732ca0090be652819091c161205f7edba145eb9f65351a85ebbce227e9aced84dc34de4cab1af36f52a01f287f1b8d58f35a17306270a19c057809e24ff4b78082df40a77de032f987d7827299f616be6249f4e5a3e190cc6aeb85ef605e27b80001a4ec66357ef621ad06649953c201770cdf8ab794ba2d74500a65aad04b9c8d793237185e79aca34c5b8d3399be558c70e4e907e9e70d798d62cb1888b1b0d6aeea4ab6c90c9a2b045221ec0a1df232b6bec70ebd5accf45927311c96e3a463d03039a27c137caff88b36ad06388da213ec1fc0c75c6c646faa14da7fc748dc01be9e6b62236053d79a50181fffe0700fd7d78815c3b99445ae88140febede19210e7dbb3db72b4c55b63cfd78aaefd598595faa58f9e72d00bd9b102bedc15fd31f33fd3a840c19987a91b1142b1f26df9635230eacc243f27b4fce3b1ef62cbcee2b30f3ac216218376d40db08bc35ad5f4a6f5ac0008a9032ed17e78798de71ba21709c63fddc113f14f320799c29e7ba89871e9225ac953bc3457e45470ede7b9fcd70714abc8dc19a4802bd06ab1acd59d69816778d55e7c9f10ed257d28e9c6e9ae0e3d53485a20ef6c2a46d6355a64ccadb040450dfa5f12c3f535c296e08e1183a685b57ada0ff8960a6a0d895e09c511fc79aacbcd03026b47eeb3ab4eb57beac82492303e129c3e168028c112518bb7ebb9fa4269617cb2ab61484914993a79a3f117434dc928047f2f549bc7fd0a3c4e593c7a554d046a94e549cf505fadc3537301f3be458add522bdcf5fdbc9942dcb34c158cb713e2df0ef0db5c7ef5500bff6bb2822c71a88b2c094d1ffc7e130a53552b4b3fab54e8372be661614df20cb7396a788eb565e30979e3c2b318a51257323b0f99ae03d179247f888ab731227111791f95134c2efd1dd44ab7857440fab93bbe07e6ca3e9b0d5468aa1c478ebc60606f5716b6a1bb53c366fe23fcdd205369178acd5dd230abbb0734270ed92436b5e73d0bfab9e956be48f2c31ad986acd2dbc06dea2763ce85e55ca73fb13e3d12fb5e8267cb7450057e4f4a0d1a7e3436ab139ec637c0cfd8d5bf14cc6e43cc61665973524a86121c07ae6611e43a5adbebe35549b42a4488fa0914243d47d13368da811e38483b67ebe5a4ea2f09a23b70dad0dd3693ba771899dce8da192f9b9536b801d3e0391968da8416b762aac180e30add2c730c85e400c27b419a2af339e481522297e3283566d296aa7691c15a87bd8c3a831ef477e13e9ac3d44e62731757b1838ae3b88ea3a5db9eb9c7b38bf16aa75cbe2ae9faede2366a17f9a208b3da4af81b2af3a7ca7b6a8c874d5f0d59e20a26143e911d4ba99925a47c5cf23c72d788bcb756a75653bd55cb0c13cb93e930b2e4ab236318e85d441b52af6793996abeac4e692961f7d335baf155068a2e99625def90b8434b80357e484381a791ed7ebf981d7dd5c202a15bd2536845660dff3a9a1ca6e9b7a2f904d1920cd7954918812ba863411a026440fbae3dcb3459d8a2c44695d23eb3abb56e2dc1935632787b33989b9876c6c3b03e50021740d3ba464d536e85a486004812ad1544ed10c4751ae13344102c44e50695a569ffe6d4083d5b26c2f46d7c5840c27856f45938bb13bd91e193c4f61c1f5ec2ce8e3a322a3b535a992633c3c82b62e5a3b81eee21d620fab80af635b1f85da35d204b07e5b6455d3db7e352f9f2968edce3a0c34fb0ac425da027b98d509d0862ad351763d7c4c9a4432b2cf741e2385bb26e7acb333b093aed231bc695330b188f140dcb89d752b9f9cf11716a076bf19690e8196f0ac4483e775625500edbb88e78a3befb70cbac0634ae3b998aa0b34db39f0c088f2196550552015a405c20cb171bccd981445b21176c24ebbbc32b2931c15a0f84fdeb8a9650f6af563f60b374da42601a6f7c9e1ac6ecb854712ccd5a9e811cdbe2229d8d517ff7e809e758bf7c03a88884bd33f7f1cff3c4e317d612e57c42af0da0deca619ec7a16f49131aaa68d42c4119565da3af3aab8804cf3e81b9c4cd389bf1c95c229a049b696d2f20b348b68d1c41441158a1a866446164e9fb4367dd861f287bd543eb164f57b1e80985bfbb0a75be18ada1ab4079301502bc7fbc086d2691082f4fd94c90c9429c99a703d724ea4287b70d58759a75ea71106b0e9745fbac0ac1d4b8d6b6c06b885413e3165f0fbea378507ff3c470a9acf63d241777f486386cbc66755d170e8a5a80fdc88d26b018aa35739993e7e8992ccb2ccd4edcba28e6cfb6241efe1689dd2f1aae17a4c1f765e5136a2288db67da39672195e702596a0b70b47299741f4e83390e3f342c046f2545766807f7b5d7ec0d867ba5e086ca36067a3e9c48ae593e101c9b517df65a4d2aaa16d3c996a1fcbe7fceb97a1f5bba29715407900f03f4e072335c34be830a347f75b74431496e01bea0f6767c3e7094be5669b514526fe0e55e808ed29f649542d2b641308cbd9b5f899d3fa8557891cc925bce017ebb6ad0c2fea702cc88dbcee394038c6f0b340db763e5fe15587df7d396da30abb4e3261a3fd41a36b17646fedc3ab94e585e989dab3ab2ccf4601e272bd1340e99ea9ed62eeedc719733b3077aecc88a1a77635003e81c1192d817f52afe089fd96df5f61f2c0f4bcd6490acbb9ee362247b8553860b5192d32b9592343c52f51897b45e3184c2986c2281572bc18a50463a2047c234fe0e7182c4f80600dd2c282209d207027be10db7e706e71f17e9c2ea51fec610fe718a5bca17387fed873dd0ecd822921d280487ffce790df1cf8e538c3d2864ca7532db8208f4bddd4a0fa9fd8b38b87c921d762cbb10b490af8262ecc0eb162b9d4218721a71c19618ec7e0392b9cc9ee104ed99474ae02a21b9af976b7b126696d075bc0c253106ad98fb7112844a6f5c91d2dc74cbb81d0225b2ca6baccdb8a21d08b3c500449aff7cf3bc704827acf1ed25ab076a0756d4f435b96c7b3f78793c311739242dff63a0ea974782d6135d49222f2d66f3a801a9f87eddbbef3d4f8bb9d14697587ef19a190562f768ee29b79563f4a7c3b9f906cf54cd1badba02d2ed7fbd5748988759103732fb186d2d1f2107ee1e8a706dc5e82149d7ed370d425a67c3f61686c652616e5eb6d782798ffff47b0a697a02a767655534c1bc1ecb71534b1798dda5c59adcf3a606a08c470d630c48c04ac34f6fca0baee01628d8b52d35257db44e995f3902ed32a84115168399107f2e4ce29733e5e9885f890cf53bab977906c42053d92b91831f42ac0f9562495c29dabe1162d57b2afc61d53cc893522818ac13cb266406b6659c2b8256401656e6cf45e43faa3b08a05dfd0307e6abd2365db2c7b13cce1b62c296d1b839899fcb4ddd31d17929822870756b93efd48f65ce56c0456ed0d376d7ac275282beacdba789253affafc7c162cb205178b92814a447c0a58563eb212e2c57693c25a42d820dbd5bd9df82c38e2e0cfb37823dbb5eb9924a848c83f5e87d53b324855499615300e8bdfc06beeb90bb2357e729f1f5c9c1ed405708ef046d602192ed4c3314b2e882ca19a175bf3a2cf2fb579ca16a4a008147684d7d10cc978abc1a15df0999d00950b92c34fec9340a25bf95e3b82f74b046c20be1ee9cea051055961e997e5c2e314141177a745c328aba7fafd05149b4430135836eca3cf076cbed9c34c3f7a93b6a8e8231266b24368a8b85c694bf7a30a046b74e25955f3615d404585759d215c38ced97abc0eec14251fd0f3541a6124ebe4b0309aad20459f750910cf1fbaaa49b7ab023a7244815e1ae4f5a299123548226d512bf52995aaf7112bc5470142587bad7258cad0ffbd9e5cd720dddfb467a61cac7cdbf0ba8be4be4d5690cc6bf9c2cc7ba65e8ba0ac2bda65aeb969502aa4a1c78e079b1cab3dcac5fad3618953660b40eb0d3487153decebee55dd0fe8f3da986268e7f980ef85395d02b277eae1a079b0ffe9df8b7a3dc7fa208125193c597c5cffb5f6337d58374fc5fe08c0c11bc6d7d114acff3898afe8a8eb47c8855983dbf2c57e0742f3e909cca9221ca770001bea85283cae48429e1466ca9669a4823e5b2842c89a462f6bad5c9b257e8ba1d1adb45382c5ac695d92683f06fe1838ee8da71a35741d3e208e664abccac8f311e8eaae2702f75199eb32efa0ba3d986638fa160096ff52c6aa2eccb10994de7ca31d1b41e75e765d546f0d29ca1596c66e0a59117b2fffa8965517c79dcc1dca80245f37960f606b468de54d8c53a481070ec5a136a228e38daae7fade7a9901044410a0622afd92bd0f75e4186316e3848d975077b9cd2f9445cdd53f8b12d9e9b84a1253f28e335bb5e6b7ca142fc70ebfb9d1a0160d6b740c7f920cb6156ace9a7ddb1eda66d2526151335eb012482b2559b3dca10980349a6a79919a7f0a27b29ac634589715ec9960c9368a5d1e5ad6a54b502b99f81a72483e6db177ab9e6a388a6350b3c9d4dc1a9568f6ab37f0ca9fdff3cebcd75ed1a9d65ac211bb086247334fbbe72d1f2a1ec892446ff72e425f1bf535a9e352d6237dba279d24f2c4cfcc15f18a38073955ad568e208d5c2fbf44f6991d6bebc2314ef3114de5d33c5825c412fe9b8e1ecfdf5e6895522a11fd82c8520579b6699f756cacb3ad361d18f726f57607606f009de77c45a37eac0a5ef501ad875632f44b65696370613cee04283529bc539ff8ab8dc71609c730c682f997548c6b0f878fa5c3a915d4a414fa995b038582c18ac14cbc38eab69a0cd06cc3829fd1955736bea523608fa040ca5f25a1c3b078bd821a093775a22e45f03aa4e3f578d18e9653a78ee96968390f999a04d4d8dfa929f14585c3c59118acbac5731584424fb8b054105c18503ba616bd91acd517c11902cca023e9b339c2745b1441bf5902189d035786038ef7468d2102e1803ee2d0823380214cdb80aefe2d9542231a63182d1a4d1e8cb9aa95c7dfbec0e45fd99e01260ccc7f6a8797f842dc1b1c4fd00adf9c01213731d793ed1ef15cead98ef8ad8600a4b6c22c0b4a6ed630fc3fa6edab6ff1cdb93da7fdb01482529b0927e00dfd849da00a5f0a3af79ca756dc81bf38b55dd992a5c4b195fbad7879170eb6583ed6e6da9567f48b6c29165e094ac78c9314885ca81dce5e83d20ad5abe1748a85a8d77797f425d0c212ecb2d9c4340080c304ee4ec611bc1b4c746c2028b213cfda833a8ab031bbdc86f1573794918cdba8b9b12446de3ab41f432c06b0473964832934b056df907d4d0886174e93207012ba0510a2a720fbc123df3d7973994a2d37d17bbde65a9052c32ea4a9eeca6376f6892efaccb15f2d2f3147e6323012227e06df99c3290224fffdbefddfc0419b35c43f3ff605622c3f1f637d705c6f85735332539ddf944f08899e9f10db362360f1fd81887d9ff1b19883e160d33e8c43303543637dc2ef471838e675033d6422e75718011fad2d2aa95b88ede49e63515e01b4a67dfdfc10a7bdea9fc44c367b32b5bf1373c93f7408590214f8917fafdac853e0ac06caf29c02ddf46e3e9fe6dd42cbdba99f3443b32c03844d7317c46b3736ff1790c121d27f58d02e031464a79d14cb8fa87a0f36cac25e161ae0c86a579ba9dddc25c9f22a894988eb3f082a4db6364d4124b163c427433707bd65f8749a2fe9e48dbb95ec522ee1532647ef4c3d3914245934d1956b65e62c480e138ea381c008cc36ebe541931164619c0423ad3f1b006b7180a670f8ec42a1c5d23adb34f94e8923b6cbdab6e9bc461c1d4e760aa02c9a58ee57060bdd61e69e418db70caaf5d627c8c28b279b0616573d1156edd31aa896783d6b9d8476c399596f6212b2118d9c4252fe9582c1d7eabfa64b63eda4f02831d2c26877654ba5bff1f7221ac21e1978691d5f0ad133cbae796a41c4cfcee54742b8c32b3b3ebf665920ff9c5064256242f9b3123960dfcd6b8b565b05e6fa4c242869cd191ecea62ea130c4022cc75d73722e90214529268a1116da3c388f7a8444f2e38fdf232d6d937a6f927ff639e46822a590bc08334165ed541fe4df286b04e692b3dab68fccca13f343cf2b7194d964600e40c7e01bd482923517bb537f3dca989c551a8677e92a8dffe401a379e020e8cfc5a50f208cb28d2277a3f0b757346c8c5949910935816cff07c5907aa121737b5ec576ba6eb598ca38df1bf9f9ce58e7bba9a326f1f79c371601885cbf5f869ef7ab61de86aab68cb84ac34d8b00719f0b9d79202e34709ccd22c865f825a4592229932b87c68c24e499a99b9be071737a506e80644bda5c286e0eb35d7619a4a482c4f07da3b2775941a499b0b3f5fbd3a203fa0d071d210b358c4f40f33f26ab5ab24db6090e90ce194403aeaecd0c710dd65795a035a4ffd32be81f52b095c2eab2cc1c50ace483e6767ff07df49f26d32563d8694edff2c0dbe650cc0e0d8a78f71854d8b0dc1758155cfdce898d62e813727465086f016468572999e70c8f7599d3a1354c71f3e8daf43b02ade7568ebf03cb076ca99107f81f04cd8748062fa4fe710ad3539493982f23e810a3cd7cb9e0fafb22b939132219f65f8402a77a4dcdf086e2f299026bd5731088b97434779c3a91f063cc9067807b9929ba5c956722dc00482329780a6bc4934025743e02e0ac2b844c3acf44352203a5947804369db5ca4da286d75d68d6f2422be879c3877257725ca3a84f81a73e5a81878164c130a1b53ab3e07a3aa990fa30144de232cb800f910443088a9cf5087ec0425b0832b30765179ff971f412c48acc27764aa2970c71ea67c2ac841c0264e80213a495c07cd7b548cff5f87453f314772d0b64523a4ac346e9a7638a9778531cbd0ecb60ebf7c801ca4a42628954b6bbfb9fe749bf421efef45f71b970627fd0832f00159414c0fa210346f2d3845a5bc917325360827db8e375bae6c56f410c79ab3a7fd70f1a0fba61c1a621bd058c16c7118420a037f5ba8d7260f0c897b19b656565d633d350859f5648c8160c63f2ff1b56819d5bc90febcf4eda5c0e7a2ef10149d7a5baa45afa7f7d423f54220f4342625a48a5878718f04888ba5c4826dbdcd4555e8075537bc473dff94aefec723d89d81838f646611219e01df5ea5515f5530b358e16783424811bcca5e5316dfb61a8f67d84106c745b0b170bbb7d42d0197e39d1a6d12114ef37488a0715d063e84e98fad7e342e2d5b3b0843cc1f456b10a9a2644297791076801cad64081674c08925bb49b324eabfe08f75c12f46308e710d3219f6941204b87322f0d3164ed50d01c8238460597a83a713ddfa14a8bb5daa0e983775317d21c24c17cc8da373c96620ff27c31f57978213bc7edaf03112cfb1fb3997129eb31d5708c2a6b605a7d4b109540c96803eb938dba7cea21c5edb5ca7255aa480ff3ae4d1d188366b91b984cde973022509fd9d301368dadd383439e5dc7eea2bb0e9cbb660c7bfc0224403fff18a8daaba7fed38c06919eea938ed5adc281643325d1de87365f00aff9d50c68036a020b8313f0a67a971cbb6350e14f0a8c06aef9954c8efb6b4be2fb4a13d688109098852e02fc55bbcc01baf052648cae44a93cca4fca29168dfea1d06b9c9344b8a374f7c1058344a47001c9ffc08ed3d0156652d9f2882615ae76d8d1377224a84dfb298c83f2cfb6c3cef1f849519c9ecf5e7e52e62ce9c66f85a9400c63fbd02b3d37e13f2c89809e12741be003afc88f995ba60b5477650f63df60bd30ad03921bb65b4c84582d912426a153b537c7d824cec8e1dfbb33b2cf30ccb31ffd013232d3d53c691cc262cc171a3ae9da97cf5fe677433728acdc3294ea79dd250cea4d9aaf2bd51a0010b586dce393017e292cc0459a835fc652d7cd66abb0500d95e3f04eb309f3ca90dfec8c1119c691fff1a04a384ddcfceb74e3162940920b76e64c80cf4ce4a5b3cd449c839b19c2e0068dd8c46fc506e9a905a4a16c3d21665a0916014aca5468390f8ebcbb375601afe0544b194eb97283aaa499733fed3944241086a1d82001691b75fc051a011e1c98651318b48b8d109d821cef3663627986bc49ddb55b93d60fd720a8759f315ef947b541981c32c403ad430e5b9cc87be358fe514d4d9b6ed211c77e72c71c136412728fb037faddf07e84a03a91bb28feb3c44a96604d781e3b8d100b51b651e029d19fb6b5261307ebcf27bdadeeee7c8fc6658785cc1323aa2a7f5d12d639ad71e91332f80ceb3111d8e701a815ef294ce7e2d247052c99e9b64413e61c87e1b7e96198f8a6244b94b124e7b2228ef653dc35b48e15a148664a360d21278907392cec5b37ab60d703ccc71f2016f33eaab3fac531ff92d840230d2a038b5e15c3e482c42b2465ee1e0542fa0dda7d5f5241cf8536fe98c4de80910eaa0bffae1397a1967e7b00f2afcddcea6023357028ec884ed5e5dc38da4d704f6259f3058d4bc2614739cc6230c9f1467c35c8bb4b6421680020c365f619a407f45cc5bc0197069e6991aac28e3f1589bb59fc99ca1b5aca2284b08e441eb576f2b708b64d414b2223e46f6fb55225005395bb1c8ef94079a7df2d26437051e149f6544bd10a66585604ef294c9bf9a783b17117ffc3a05ddd7073b629bda6a582270f3ed12e3ca40a9a83cd1d8148aef840e8fde04a4f2942b5900b7c79dd941f72be5dddea884c8421293cf08e6527400099672e1288e1ab957df1ba3090fe9aebeda5b48ef6bac2a39fbca30b5baa3691d4ec7661bcd2bdf5ec4f2d7cc3018e9e7da8f9571aa2501f9d3d5678091ae413e81bc9368f1193fa95ee437cd27447d944094cbd034a04639f5a304cca9a0e7589ed157bf66477b1ff7608fca9fdfbff9b9b3b641b094267b4eaa879bbbe76e5fe3b015a3336b8f3383f78bf2ab4a968fc14f467b68e18009de8475ceb0a13775033867d5ac9e00a1c4d5a6f5ad2f69652ca9452b009ff09ad095eb8a6db7857f58d46d363fa463fc134fa3b11e9f6771a6f2818488a1dd0eba24361a01fecb7e7fa36c95879d371110431afe5b190ebf9bf96d76a7d656fd916b7ba6a3df72a22e7f8661fc8cb2f2f8bcc402dca7903099ea45a6bad319c9925d11b7ab228d140b9602559a94af294a5861dda3ca1823d7102aa82c902ca87294fbaf8e1066773434f1625fa54cd98ca2689c6d0a40b13830c459e89145190512203021688f0c2e48a270f6aa9dadac31319ea931b5ab0b8169a685f3248d3b2da7452da462ebdd1dff1a79452002061e5652d38a4db6f474f0bee86647a59b8e61d679836600c2ef3f08e993bd2f87570154d0a799e770156f23cb6e23a6b711d851f5b85978b599ed8595428782a5c7724fe3c65b26432b71b8070250ed368f7cc73698f10e4ada265209c0cc442f66c09f6e59d1439ce10eded5aed97ec58a3e1c4d2c8c1810e1a3b3d3e1198d4c795cf0a885151974f5e3c88b9590766f6251ffbb0eddd521c6ebf7d7d1222190c84c4ca6019d3ca72c752daa42802f57dfdaa61336d4e2a72370b4f3e661bdddf5a2b8714b8f2fb8bb96c96534a798f6af795efa4cd6d9fbf688773fab794d2369b062510490c2d24619e2419a1460b12aa27ac16992254f8d61a37355902b01801451528a84070c50a2c2c9cd43047c0b086504a29e5374f6e562062840e55508e65864ea1c2e3bc10224bee8e5fab51fc54a5544b96268cf820c92029cb113b6a94c8c2e58ef6a7c097f7dff368fdabf55feb83c8f79ee9eb61cfe3fb9818c4f5dfc3c4204e41c07f3ddb2ae879dffa59f74734f9336bb63694a306866c035fa2c758d36511efc8fb22f0bda7542c7a7deb0556af138b5a2f4523ffd61bc9f73e5777420326282dedc1d7acfb23db8e5927ee402261040d9ad896e779b226c2082f9cec2281af50be8b6afa2443fbce18305f7ed17c5934df138d48903ff4a7c8f2a74a833276bef4dc9334917a35d83673cfeb231b0f31484bb4df75eb7974deb7bee547363adf45084708e97be883e52a11e80a5fff0f03cd9e6be5fb17c97759c522f99e68f47246e5f3f0ef5c9cd978487146c51dadf73a845d96423590d65961e54f4a933ff3bdb09b73d22ecda17817ec8c490342ad9cc639a409dd751fb61ec4c2c73ef20776598706652cdb8c9c8a5c2edfe57bdf2c72bd271a59978b46523462514eeb123d7fb67963ed4efaae679b0ce57bcfc3df25067105b1cd04ccdf0104a9087786ad188bdf54d11c1c047105f9bec500efe543c0e554e412adfce9209ea844993eaf7eeb8beab7bebfd5bd271a79dfbd518bbd8732eb56e87d51cbf3de251abd510ed9f6a1d8514693b2497fd2bcccd77dcff53e88fff7dcef7a1ec2513fc793f0c3856c5071509f6bad5855378765cd1df9ea7aaba3b48a85ead67fa13bb251f9f38be4cff9522c7a19c97f8947362a8e3fe6a5dffd9ce6b4a6893e4eab5fff93e0d0044ad8d1e7d66ffde873d9fffb22ff4ff4c4a27e17787dfa369da2e0512ace18505f3e8fee5d0ce2dfbd14833815f9771f447efdbc56fdee6794b65cf5bb96ab25a97cb16e6fc640954f9fc7fc2a0611833815d59fe2cc16a47b29ce5adcf179dd34f9131e712ff4c1f6f31d6934ff0efdc9c73e9efc99fd4736125adc815482064e6cce69b2d6dc8124dfd6ddb5c4bec964322677cf75e00456260c8ac359ece6f67f356883fb61b1e0e5af6003d806eb6b89c81f0f2ad8facfbb76d878745be0c7be88be053bf96c03c3991465b656947d2c55ee68afec6ab70377c04422f5c1270212fd9275fd170bd9666d38d47d0d3656e7777e26ffa8fb991477207993fce9de99e48fd2d0ed5e03275609e7bb9cefbe8ff8d304dfb1ad7b9a31a03ef822d087e9fc4e0c49e902490a7db0f2f90279cd5eafef81746fd47ddf3ca8bbdd779307517121184fdd3752f7d475dd94dbf591dbfdeb7beadea978902b7dafee9d18a8fb9d1088fce95ea7fb5619eb504eebfe25fa93d3baeec5275676bf430ae588b3ef63247890fcee7548211c71f6893c40fab00f521f7c1eb08f7d4c0c421ff66ceb5e042964bfebbc8060c915c0652f3454dd0e08fbbdcf3748f4896f47be828560d81a796ebf7c2f949d2bcd1725564daa246c7e58ec8e9c5f65ea459b78cdc91d193765b2891d6560083e4ef31acfe52e3f20dd1935d9c48e3469e5abff4db6e1dd4343b50e5e5696071848135684115a6ca39fc19dfb00fea93fd20ec0dba2080c543f47093b7653d61bb621654c6c0b5944807471eb7718b6c1ced46a6e8de25632b70e89b9d5b3b2da4d4b4c52a889d6818586ba69484b3714c4d8ba4ddf3def251ab9bef546df473ba1c8f5df090d609b953ff54d6800dbbc6f00dbe67fa291914cfed43fb2c91f69b24619cbb6d6b36dfee8cdfc8f6cdd14ce5cdc81d438f953bfa15a8326d6f5f903a9ef79d666ddfa1a38b1557efd7e924250d599a450ebeb7b9314aa9f58df25d67770075238df15ca5752656a75538c27ab81be5e3ff4c176cf77eca66eaadfb5833b54af56ef755d68821df7a1b973d87ec3c73edf87c5c23e2c96079c31e08b604871662b3afa22850f30d8ba975fd4bd148bea7bfd23db2d72d96ab0b1ddd7df81d489550d0e25d95116fa60e9f31d87d8fc00a5852cacb842c35ebab9964fbe9295d16e4bffef0322979ce6f5482e8d4777be6d59f3ac1c7b35b28698a24b1a234b7a58c346bc61b2a68d99124c29520596272d1e2693c89552ca1987bbc1063254a4c8a0851a2c2c34998207a90c0e862c5ba496f0ab2b5c58f71cde04eba362ce39e79c73ce2b5cb430320409a41470b0cd3477ce39e79c5fceda862e1560acc8a003939929407089818a2b4c7460340095a58817485071e5031a299d264652b850a384eba1c817567ee5ca085a4c388922aa34fd80431a2f8e988200586438024a0b16397421c50f3e504a29a594524aa1585862045a72534c0461a3515c4a29a594524a7718c87fecb9fe3d6c835d72210127a2f0d084e588ac2dce5c5121a74991264fcc60254a114c707028de804e1c0cab01a3d3c4b160198105154cbcc062c604ac2d9e3c59a214849b27babc98f8110f63de2031c40b356099f29c95282a5ff28d99c27577e22c2bd084952d597c81c4cb5499194baec0f04407295c38391182167bd381e86047cb36baeb36b7c99e76dda54c227b5a7727ca3a83da878f4c27c1cc2cce21b3bae7dfb1879ed37cd049a778c2bc3e68d72d95c99852ea31f3147fd0eb2fbd88adcf5eadf2877af2a7abacb2ca8e79dacbf3798a9465e8e38e3b578eb992622945963ffd1e333f03b5ccaeb1fce3ebdaf80a37bc44ffb8c1652019069779f8877f8a2f83feb43b0ce442dc7d0acbfc1ecdc65a5ecbcbf2644e29a7b5ded7f758a8eb2c9e353c6be49c9d0b3feae59fdf2985dd009e73ce89355a2cdb3fca50b2fd9ed378ced7654aeb0b92a964fa94a7052b2995799776b13801dff33bdf231a39ed8845e1fb88463d1fbe518c17ffc866413863c0051fe3828f312bb2e07bde82ef7919e18cbecf1fd93efc97f145ff329ebe0c19ef231acde81fd964bcd18c8a2714f93c06df00b621c5780c4423230c1e03d1473cb27dcfb3ade747598fc8b618e205e1d10c7370feebd705e1914d0cd916866cfb906d3242b659101ed9186cbd53fda8b7257ad50a9647f430a33f4979d0d4070b9de67f3aaf0e5d5716b2edeb20e779fe6370e771be43fb3ce128de1d91e773489c30271c7d884e8b85f07d7ff0bd8f219e7fe1c8253b8ff339e17874731e0724caa1f43de0707c9e709c97e777c2b1efced3b0861536d49138bf131e5d1d71004d03e76738d6b8389f43e684e0e384ae8785e38c1b7b1b8eaf1b13619f43c2c2fa603895427f721a94d3e4c34022973897785fbdf9e5ca3975a5c089ebc18e8ee39c8e30899daf249bf9a79b6f87732e71c7c578f92ee5423df4e53b94074d318718e29c923f522a9c54f207e7342a6a74772d58bee3c44d9c377138a93495a6d2954e257f80b03f451c8e891b1af2260a12e5e0c08d4dc0e4dcac6f119594586105218c501652104e80bb630dcb1ee9788c283cb4765ecbf5bd4058cce2f4effc8e38e67c8bb30f76bebfc551e777de493af9f3849e6f30e7efe88438a1b58285af304aabdfebafcd3e13c76791efabeeeeeef4ba4f2969f2a7264a29a54bd9b49aacd297a27bd3d04567f70ef61da197ced9ad3a67ab9bd22a25129a90e70c0116e28030420abcbe0cdc7e497fb69c68269a2128173e5f57cb342af2ec98465ff67c815becff4cc1f6bc4c26853eb0e67ac19e6d36146301b874000bb832a7c509dcaf7330609b81a890d9c47b4911dc623f5a69cdf39a44cb3a5969ad34031fecce652d50b840ebc1e6bc01c01bdb61411bc27a6aac99322e18f9e23f1c8687913101a83273c41d2d2dcb0cee8e5ffbe1e3037c9cc3ce7f2a9b0a4ead31235bccdcb6dd563a2a4f539a62f676bbfda84f4d5198a0c0708e48dd8003e788940d51a02cb5129813c58b121f43bdd44a9dd4482f9daa23a0a8d1498dd437201f8f14313bd478a48891d2b7ac36fc3e5c14d8d086871914d8d026046daeac466859e5c614793e63a8f8757850ed3c2a291cd5c74b121c6ef776766f3b291c5dcd832a0d8018294bf89005408c143143b5193e9300307c9962070402c0f0458a199271b47bdbc94f4eca62a488d9e1766f67f7b6eb6a53ef19a45e776f6d37e7f5f0a86b9da68f7db22e8da56d9b27853c2b73a197e7341b7f87b2bbbbbbbb650f0ab6866c9320d10efa9ff77ab9d0ab461bdb59f7b9b13c293d2576ac5df953865eada291101b156fd5c9f45c36bda1e9796fbd9a1372a6dbed96c456eb53857aa2893b565c95fa1feb541d13e5e54d1e99e18e526949ce8f7249e74719e5098a0cd51d254e4aedfc28a7787e9461aebfcc492ba43b4a2cd966821f6516053f4e24a52527aedc7132cd28b21f67138c1f2795eb3f71e39c7222eb8e936a86a9e0c75935fb719eb9fe136b66e59ac0b92345ba1ee329136d9272474aa95cff9142dd1fa9d4f5a754b4eaca1d698e9eb9fea61e79a2883b56a5bae4ba6749284d5f64957c73640a8f73ccb4a24bd40aa557d76d236d731dcc94ebd31d0c133ef5a58601c3d379bcc40d2b9705d21554a67021e608054d535c3db504973b36d51d3bccf5aeea31d7cf4061024a0c848dbe347a13099cd8b9a353b9ee6ee5ba4b5d7f1e9dcaddcb1d3de767ae3f7390bdff3240a2ef8f80673968e4d1555421c2e72ff831079f0b442136a71e3ec4fff7219d942871bbe27ff8168044e1874cc59521e3638044322cf81e90c882d78db17363fc8cf19e991be36d0eeac628c3c28df133902886ec08a5dbf31580443db323eeec618044b30a5e06125500c3092617c6530012c190c97e029048e67329c85d0a9e0724a26082097e07249ac0f29009c1e5791d908867c7b6f33920d18ecef398aaabf33820918e276fce5b9028872685867098c04102078704575c1c0c2e0ece5b2717a704555c9c31572ecec740229c21da0fe530c25a31d61ac0e2b8638d6b9f77b8b600d7dab74c9cb9f66555415cfb9fe4daa73581c4b55f23810dd73e0c24b243ae1b7b10248ac196007361ff02896042e07f2011f8aabae3ce7d8577ecb92fa0fb7abdfd725f4b44b92f3134dcd7bb40a2d77fef72595d97eb3d90c8555b60baadef40a2d6f5c254b99e19cf76168009e256f96092eebcb9dba25b90c30fd5a65ad65e4d8937b6bead696ee5b9b5d65a2bce6b47833bf630a18416258a48820a1f3276f4e6eb460d246a88a105092c544b77fed3a8c0dcf9b5249ceefc184834876aad3a2e14e941892a374954f02d7aa0c0823164a6ee38e34b0e253022052398b418f1e68826ee7c1848346bad74bbdd54a0c2050c324dbc6a2c819bcabaf3655ec4dcf94f4408ee7c5a8d0821eefca122a898829a536d9ca25089c2218b93275c8b2c3cd82f83a924b7be8bca13b0560008d0c5aa4b955bdf135d40a2c9480d527ed022eb4a198f893beef4383144d4ad5f65530529983441040b524800e5a6cb08902845820b9b5b7f7a7fc4a44d15140d48a84052c536b3e8d283102f0baeb0a34cc6369ccb9958106768b398d99c71a700ee9c5235dc2995c59d5c906cc982021d3c18920b5f0a3930890bae214a2fa6262da4a7ef07275e0a2e235ee850a86a458d515289e0bca8354be810ac6ecb902693470a750b0c2f48f6a6c7a30813e52d4d30f01882dba225061e448e6c19f38407529519a8cfc1a2659dd75a31e8d245959f2bb82ea6a46c8ccbb82e8ec840073bbb8ceb220d00907851e40d086a489aea1839cfb16f31096e7b9e4be9628fcbbcad3c07d5583efd503220acec89359a459250e188cd68e785b89b9bcd29f66ddb098500e0c8cde624e5b374cfe8c8858923900b7b0fc7791ba55190204284e0ae7f8e7b61a10907f8e28bac5166dd9ccff910bc98788209fdc585b12d278cbd5b715ae3a0947a1022013cc536e86d98d75f6e5aa8609edc51d4223814b19c7f82ddd35cd88fbe060ad49b9cd0b1c00e625c173dc74057566718c8a75873fb5d7a3206732c35b718cc83c2cd46b080d3ac744bddfe5c4bf9f270d655fe04d368878281fa5b0ffb58ce83d03f9c56c28f9d43a32302c9f27ba38f348d2bb6e1a9e11bb06ffeeee839b631bbfd7e866dc8b7dfef51f08dd8f7fb146c23e763b19b63a517f6d591b173476e57e574ce2879100bf510b7bebaa2e2b97ed3d2c9f11c15da9de1343f83c51dfdeaf663d52072793be8cb9de3a07339e618dbb9b66a346c038b6db4d886ebfb3b8b6d7cdfdf6fd8c6ebfbfde6485af08d9ed234fa463f358dcef9b1dddcfec6826fe4bcce8fdd05dfd0f96ef2254953b8b86cd886f7bf5ad7437965d867ae988172574daca3601a7cf93be92a754e071ba3ba7df908a5db37a96f3495297da3ab9a46f78d1ed3b2a6536e3ff912032931902f5172fb1d89c770fb1d06a6d1af139e00fb1c49a56f3813a6d1ef4e6e3f0e95db6fc39ea2a3601afdb0b0d5308d7e306c2ca75d856dc540cd40fdadb0730cd41f463dd891b360583916a8aec8dd907076ec1bece948d2153bf6111eaa27dc0eadd44b5cb0632bc16e4f550cc19406ca12942476ec250a8ce45098d24c1de5891d9bc93e15914295a62939d8b19b5e4c8ec3228527a6272a4eecd84fb72e5c18e9e2e607282b33d8b1a126c09acaa2089b348d6ba99c1c1555509921d253fd85891d7baa02aa21724cc034558769c18e4da581522e4d19aa9c9baa3141d8b1ab5e4f5db6c0ea92c552e7fa8c153b76ce454595a686a099424a6275f5821ddbeaaabb4db539c3e436e50688a936423456b779b26363d1dc1553b92bca7456bf01811d3b6b3e2d2d21c40dc9c98e7ecb80ea48921f89620a8c4b1451a2b8442186cb992ca52558ece84a4f309eb8c0d0c40506325c9450e1b2e4c51214253bfa12ceed892c24a6284aece84c2da521d24861d5e44e831d5d09b7c50d6bb29ea8f8d304538986a7efc6a40a282b382911d8d1713bb928235f0c29e253fe658a1d7d6a4c054a5262c24889b9024b4a892b29313938958711c28e4e0584a3f2059557f918afaa54b919d8e0925879cecf006147cf71172afa06046ee5576e055261ad5003c814b502ceb1bc0d0f7674ac3617e4864073c5d4c2a73484153c0c8126cab3fc4d0d76f4ac2532aa2e5a246175e982885c973650ba643d7143a2624779a34f4833507961c591a4263bca233838a9b42489ebb66407a5255176944a4762514837798b2262064b0b17555143965882e2831de5d21230b70549d29314a62d48ae6c51ca3145b9c18e92a975cb59110595155c344d81623f6ff965eaf64b2a0f9ae0f6cb2c59180f629c1a9c3c63e5a3c4926fa6740975fb5b090aa766c9f5ef97563c68e6a27c725a3fcf95f2692a197971b5aa8c81fc9bb3f5c29b5b94239ae4a465a85879b57a6d6a509222a5345de1aca865802f4a3cd95ae2914d4a59ef63205bc50ebe1448d0643372bd908ea266cae624ffc8f6b54257c82091d7d55951eb67452d57382b3ab2e590267487d41582ecb68701d9ed6f2155b7fb1a8edd74ab27a56ced2ef4414122da6275797786de6dc9cec12e1fb3003e9642d236a5b777f05506f244727a908b3d7ec82bc48fdc35f0f5f821af8b33297ee0cf01797bf077df78a2f365f28786423a2b21b52648a86c4eb3fea3dbe2cc028d1442e34d53968d7d838f4e1ab29a2b2c4b36dad5f7c49b90fad5c8a9fbfa4642e8cfefaa2864be73d9a0ff6397019070b619b2ed897ddedddd3d0f62a64dc76eda83d28e763de83b9deeeed5ddeb8feeeeb5dbdddd6b37adb46b57698fdad5aed2b047f557ed78ba5e7ab35dca1e3de99c41d26ff4ed1eece8dde9cd3b6ba55cc1c06030180c06832989599c9c17ceeb735e39e0386f4c84812f58ef4cd17df7bb9444b97f9dda703d832074627f77fb9c507ff495df0ce57fde4f7faff6140c21f43c1cf57eca64d68e5eec5d9c9e373de8aff733304862bbefd75b80840bb08dee2995219359ae79907d9ca73fe441f43f1bbe9c063e4e689d063e831f607071c4e934f073b8f5faee2c4fe81b0b4fe80b0bc7d605c597d3acd36efdc4f979a2e7c9efeebb47cb1934a5777b3f767777777beeb3d66e6f47a1e594b8fbf1abc3d677e151ec82633803c17bdcd6bbf75edf0aa86cdf762f1cbd1edefb4f30047f31508ebed19752cfeb72f878c4583b4b782fb95852e537db70792f5d9fc33de48077fd7d4e087ff4edff5e14a42cf4f2a752faccccf45be2d8ba14e7524f1c615cda458a2fb782cb5da4e0e17eee7d47df7bf672ec6875debf14caf90a03ff25be2803ebf7f55ba0582ba0a28e1d2dd11347d98591b3d369948ab30edcbfe6bece6377fed49f320dc77eefe70c9a72fa8d13fab2e7795ef764af2b90c1f673b7cbe512e56d47a1a707fb150463f9c71d7da36f6dbd8be3bc1ce8eb89b30fe4175fb03b56cb6d12174873ce39e79c5ab6d8c2ca953f63c8c7837a9aaefcd7e8197dec3352a52b9fbfbf7850470025b6fb640d17727d5f1084f92ed1fb1ed6a0025ce2d823a3ff8130c348f90a7388bd2bf6333441c8ed76f6d2fa1efc75fd9c52a34b9cb99e8a2f97e89910bbde832fa9c658c936b7a4a5284f56a6c2e4ae661645f22047727ac9a90a8220deefbdbe1fb712ff97962ddadcd77f200852ca693ba29c0af9b6402b85fdc04ff080afeb3d68bf2d90081499698cdffbbf9e411082dcefa7260882df4f94524ee340dfeebdb0ef1667ae7f072a20769981bc10e4944c8281644b34a1e594d32437a5b0f9359461c620774e5152814ad851e2a86efb3355f29c2633b3bff4319fd2e94d3e376faeebe75f5da0cb5adc58b99fbf725caed073c8913c612e51feb844ef1369e174212427ef918abceb8bd06c4eafa7dff77db1b0ef9c733a61c71ebedf2be4eb66ca35c0652d592c5ddc652f97b56425dd9d1e0f8dedfe73c2ca6f70becbe572b93e27ece77a97cbe5daf120970873c55c2e97cbe582c580b03d4ef3d6cb157af7f57a851cf0ae0b043f19c85fac000c247362b1e9e5844e385370fea3af1fb621f6e070c99eb0d8b740d87f1ee8c1843d831dc4fe85a3033bf060de97f862a059072fa7bdc41eb18379633171c86931256ceb7b7a767660b158ec5fb1900023b0b1afd669fd31716708471c3d02046169072fd08a218c7d77c2eef6dc0edc715ae3709a1c62200240d94f1c77ae164f974b70594b9698fbb95e5ec89f1376945d097ecb55410d33f687bc2e79bf594c3c0184f9c1f7531c5dff89de77f70b47ef8b8923901b73b99e9dc6379675edc73e87bb4ce8ae15c7be31317489a06520091359bccc409d43ce5bb6f1bd74fd50d390eff964ff999f681968fe50dfc0611af3a5ce7d39cd479f11e7ce2b5f3db7e70fdba8f7831f06ea8fb90e13669ecccccc93999927f374f38db5878164723e9db3328737586571e48e3b35c6da8ed3e7015f134c9897190887813c7e859681b2eed853c1072c6bc1c2ea71d846d8d7f320b681e463af49e1103dae1d43b519bad25fe08338682fb3d5602199ac7910ecfbb99b89ed86751048170693fd912de719023c3fc11751f032d1c8a973c471e882df6211cf4f70646b71ac5d1004c57e863a73c10745761a4ccce1b19c3f22b23018ec3de070fc11c679753e271cfbe63c2c0473421c9d30f63938e2009a46ecc170ac71639f43dae9b41a56d8873d188e5c55e3824d60e875e883abaeace5b1abbe17569f41b33b12c7bd2b7dc4e290695d66c22acc6526aca42e1017bccc84150cb73bf0b1cf38b9fb56add5937df7934da92696bdebce0f5609c3ef41f6c5b2d0c3f88ffc0e3f9c170e48543f4cad46cee34c1c1c1c9c9fa109325e5447fe13159ec1a23f99d00e25f54bec3895c239c36e9eb7d3dab73c9fc33bb43f799e41229ecfe155f729c0c1365cc8a9e48f95424426f87f22305e06052f3e910a3e94fd054f64f616e4b8209cd1bfe0637c18ce76c470b623ce18207ecf1789013a5970d04982c5883539580069795d03a1b33618207ce5e5ba216e799db367bd4e66ab4e0224104d53c63ccd97d5837c24b7bc4ec7649a2c6786b1afee641b55555521034d1a65ecdc9de241f25fb46f592daf6ba6dab7acac2c20f2a78d1dffceffff7faf8a40fbaffaaaafd2d18940fbaaaffaaa87c23e791015176a1f206118ef2f9e959595e5b4fe2e060f1f5fe0052b6ca6d8e4961cf12d2ee042ae25edeb738d08a49c4fbb70a4728a3d66276957b1c7a44dbb6743f1e4e30bb880c4e6089b26595417545703de352176fb93bc7c7c811a97a358f8d242d3e5a8a42fb788cb5149b7fb3eb42f36ad6efda82bb26e157ff8dd80ec8e406eb7dbfd21bbfd6065a6cdaf5efd49c51ed7ebc6fa7356b1077d797bd4dfd9a9228e2345a46d486773d2c0bcfd7ec3dd6eee21d9d1ab4924a8e4ecd9fde524e9a0ed124bf0dff153af43fee8e4ecd84c5f241353333593753925ede7fcdc71e24d5a86d61c34b5eb81a5843cffe9762967cff939efe998f9d19ef7d211823733f1d43d5826f7a1c5c4403558a6afa1953f4dd5a3c4ec266e1eda9f81b837171b05a621bf134b903fb2c5212530501776ec269ab73463a96fcc97efd1a858fbda6b799d7b1e9691b1ba894f5337e91b93a984eeaef94f0fe508c5a6d15f4397459cb9ea2a69951ba16fd06a7fb214ea3c76b968dcf2baae25d6e48f7ccf7a9d4ca4c91f1d9c1dbba99b9a264e0998cee96a9da41c1e4c24aefc190e154046bb4193d17288d8916941e3ebe6c0b96c2c175a2707cf6b87d2491e340a09b18d66a0a6c91c363de5ec9e3b4ca64fcf0e5fdd4973da1c3a0077a131d0147f6402b8d2935db1e3506d2866c3c7e3bcfde1153bf298dbcccece8cc3c809cb357a54b02c8e3db7ad1447a32b9f8f26f3d16c1ad51ca5c29b2815982e6d1ba54c9f7ed75adbf5dc6ac960f00f7d119cd632cc62e97fb114ecd838cfb356267b991403d13fe3340f8d8dd1c48eb599fc7ae79dc9e725a2d8e2f212516471c7ce758e568a8bb1a455633e076c2376e988c325ca1888fe055d58cf72e541b866c25fd84cf887829d856d34e8f4c2e152974b1c5b864b41d90a3d90a8d5b53a113665297d0ba0b0f5ebe5a823315c7939eac8d2a5f5e7bf6a388d3e1dc01256c7e5c0698dd5420363ce29ddddc5277777779fb25f3bd7ffebe934af041edb609e724e1c9df8441bca5db5acd166481bee58c39d597ef9d8a756ab31820f1c595f86a8aafacd15bed16a9886cc72c75a96cb625fc1a8902de6d0c6835cf4b1e2683ef619fdd603a8358dda320b856dbd477f874cfe8823b05feb86be7f3d0fd7776290ee5dff1283381575ef1267fe3571e6570c54bffa19db594eab5f3f0463a5486b2c97c8a28cdb1d964315bd5a9349211db52a73d4fc26abbecadf1c634ba0973e093e74882085be2394f21f61f9c187bdeb3f4f6cbd80c81fea33f931ab99c46ab562e24bd4d1a2af1cf2879e2063a06efdeb257d5848935d38434ffecc8ac07f3d28b2fc697f7deb5f624b647feec0ed102b4b9039a4d0f7f475009142afa7fc632dacc91ffa3487d653f6a4508d7f78aa255af9c37e3b246ef669320b8ad8d90e24efbbc7b97d5da3cc2fff8de5cbcabef043990cf6fbd7fbcf9d1d2cdfef5fe26cc7f7b306b0ed13dbf6fa198fb6bdbe6d2ff1e58f8c81eac7b6589907dfb7dee8b50309c9f52fa4d67fe26cfec7b8d93462f9e3fad6cf6fb5fe138d5aa251ad32185aacf572d487affe67a590b4d51ffd368d2a59c643cfb2ca0069eb442b9fb0f4bfdb1bcd910d699e68b24203153a8c913514b1446e49c9932f5524c9a4a8335777e7725411365a2e3ffd915d0be36e0ffee9ce74ce661c53dc01986760fe5cccdc62af19a873af9479824c93eedea07706c6175632b851410635b43332e490666882f1c577b2d25aa3ae9248b232bb237380124c0188ba228204575277b4566677c4cb512c48b16f03922dc858d855065b2c5826032d1654a2089b7339aa0a2e071184a91285829cac65b437510c9171396a851ab620411249282811c3840b2637de9021b5d65a398082439aabdb11596b240d4542c026290d45e342eb7294162c53522debbcd25a690650589ccb5157b8d09454f941b2f672d4151c9eb8f2441645a4086965aa0c915a6bad33cc550d449e5a628604c1c290145ddc91712c1525451452e48cd4bce63f4e4fac60c4c2de6470439b0c6ef6438341d513187861e9e5a8dc981f2ad6bb1c950bf3349b0c5551556e789f28235f5c7a390a8ad51dbfd62447dfa33e6ad77a33dcf16b90e18e4338b424943b32d55208eec863ae3c73c44b142fa4a2aa9031442d3de9d45c5145d2940a498419e248ca1b23382c490551440b36f93aa4900796a678818d1a1e8c28d964eeda1ddae5282757dc9ccb514e7277b44f83068c90220d0f52acf04413f904eadbc242adb5562b48da90a0852e7e6823612002ea85c90313921e28a53074435c86247838628635e4b9bc4f9496a66b2f476959bae3d73a2f7ecc60f065c99f389a21c907334850b88105304b6868152d545beb981699289076d6b82e471589e2c2b81c55e48b10b59ad76ad349e9a44961e5e5a8225eee68a558bf1c55a4cb944e8660353de09c308249912f2cc8265145b87407966680c10b93149535b65a8d2a92e58e5fab8146abd59418695fab81a10245052a0c946432c40a0a2570814c0a6b9a904b34704314370481c20878b805e15794c8d8e18b2263b07893c3937f2899e48eb21741d21434ec60868724ff503cd29ccd1a2b68b24082839632b51a184fa3064a22e112e4b0830b6c567022428bff20388cfb5b28773327542a31c0a188353621ae289bd3f48cdc0808bcb06213a2010b36a749a9bf6ad83c30b9a264933571c6a37e97664c931b6c426057b6fa9d68d44e048182954d488e0bb62ad27c83fe23db375f7456548d3c230aacac10c2260414c25637e8d0680055aa18926513e2512bc860abf906fd95f61824b26461a37da553d4154bf787bc19cc78d059d39f4f43ebdede3e1b0528eedeee2e9f711fdb867090f083032a574a29a594b286d736f76e8b3ed3e50d1ffbcc781835154fb6fa4e10e89ebe37c666f48db159f913847d79b98b104977ac5d2995dc18f4c18eb22b7f2e40bb1c756b73a36e672e47ddc2005d8ebae1703f64a11ad45208bac45045893636f90d82d04d10c672d44d863bdfdd8bf8e4532674b87df2d541635df1847e138e6ef747dfe64aafb790759b1d69b1eb1f631b5dc17510013c639c2fbf7f8ade112ebfdab5d2ee64d470afd49bb8e8b47d8bcfbf63a07ec97dfb7b6ebf8e22179281a5c50b2b35866ef317e93c76fdafdee9d806ffcdc969934f5934b998b2d8c273045600545d5c58b20605ef65e50e76b4ddc213160d48724aacc87a7ab09e10b164c8fb2cb1e3d36684c08eb44ea6596a951953ab21c58eb5dbbca209d7550b433898ec3834a9a8b49082d3224b0e0e60b023e3a67094bc48c17991e28497262b2f529ae84840163bf2540c8b6a4892253b8a6cb0235329cd2b8e7c57ec204204b6d891ab3e2c2cb0a4a061040b70ce958b62418a112324f880b2235b794ab92ad250596151c28f2a7664ac1615152b50210390a114a884ccf695412c95b2810000001000a315002020100a87842291480ee4d1b87c14800b759852785295caa34912e428884106194000218400008c31c42034662400841b56c86f71fa51417e37e4a71efd4006fa43a135d3dd9925766406335d98e1f3e42b414e52679ac03f9b8fadb2dee55c91f30e1778182846c5412f0ffa798402a4f097292e3f54c25f9bf50c5a21bfbc262d9f838ccf13512bf4298100996c1df0280bc341994c870ab3476634a005339c3ac35044545f0142aaafcb7d202390cf0e4619ad6342aee4896ff35f326500502460c97e021613232e30b7204cbe25e8d5798ee4187c6478ed99443a591507d17ee3f4044db28af9c22619afd3a4239f17ec93c277f28adcd5b12617c01685ea8c298b4fa3d65432d88bc90533c25157b51de23d6e2949123f0c450d880224a9cbfed72488d92710e8b226ed4321e6827097a4e04bc62e2e0a1d64c3da301fbb474a3afd9bc9cdbfe5fe9b20f199d9f0a907e4e9c9b7781d3914c58569fa7036dea1014008d1ca956e1b0e3c39d5e9635d5bedb8c92cc67d04a3ffd72f4696a91e01014c7d70bb6622a11365cd44828e603513073a02a899d4b7a23e85e5a2bda008ca259ce31112c100d47009363ef61b191bc7c2f60784631d254fd276a92219cdcb8de3125580c78bf433350ff14ba7523a0f1c12370fb631cd1d121e115a9be519f7dd5c922f02fa60dc1de55cb7e4790180facc5afa2332de183dce8bee354b21b45c62d69236ef2117f24b4405fee26f65e12f22c25bca9ece60c8e7d7dee37f2c11ec111aec2cd05792923b459d8e705ea15603cdf780e8559bc0a233c572a11c44f6df3d4f933733e9aca49e091339e428d9152f693fdc3274887c1ed38afac03b8162d70c32bcd8c4cd123276b829764c4c55610929aa9823c6b90c8ce21238edeb5e85faea737c4667331cfe8021ce1b669c6a789678861770b2a3962896746e7d7c7e501f18f50377e6323d27fadba284e56d9c46440ddd99b80e0fb7f5790c79193e6991c8db54f059fb01842936962dba14f3a73b0b4f62b1ec615d9d9c4d6d16f8b58f5f86305e517f4a1e0f6f61257baaffdd9b5ce6c01a6f85ea0dc58fa0ea13706ffa74c43727a1352e52f47229761169b983a3e23a562d7721af8002cbb03f866f0426f1e1801f62f99bb2933ba87670e11faf1e3b5903e40da79c3c87b1f4f819f9e01842de0b853f88576255d8a9ed0af895fd66348f2a07b3a5088497b9551c71de3680ae0322c1aa22931084f63b6492df2705fa031976583f01e568ece8af6c4d79ce5f5c59859e18135340d925f33139bc3a3731fb57177ebc4469508db24e289f37635f6008889323ab619f542c1f4b14be1d67b08fba2b576748c32e764558742cc1c40d583fd07b256ce20f28c3484c020155d8c8e2adb1110e8c9e3a4df7c2b33e58a9a7e91b64ea71afa9de5c7a1477e44288d3969f0dc465e5303d132924e1a10225790e101df415554b9efb7343201ce30b3b7ce9b45930a0b5ea74e296274da48b24628f4c94f004e06a5c089d88ff510c7d32d020ce5749f9dd54b6144708df323539aca15de33859670536ada3ee520a7b26f779c21fd5ae9b08c9298dc99eaebf440c4ff7e789ae30a8d221a2818c72bc07627527953e2e6e5037b9e2df569de38a95a5297d9c4db126b4c17b7054d2185549476bc3a402612353abe2e2d53945376a5cbd34cea40eb305f028f655648a4db4fefda2b4b67bf9d85739d13963ec899175688d8a2d0c33cc15831e10040f02fd3d316878e79a528b78a0c6435da6c25ce340d11c9fbc71456e2794791c94c214d9052118ed20add31cade11088cf3a407de4bbe8102d7d28132a9bec53c4881bd99f47398332c0cd517ecf456fcc1179316f452c7e6c3249c4a0165eba2ba422eae611a5465b3e8a3faf6b35cc5c1a216b649e4bdb8afd3077940ac2635f7eb166dc10edb075d5e1735021fdffd40b51fd28ac28cd742b756b4db53133ec632cb56f5942347191cd7e6302f14fcaa6645482478598055d683c69c1a592c3f6ca1666aeba3d043e40afd031d11c79084d629c932679ad7f8e465826f199954919508baa410c204cca2a60446307875713040b242b6a223352e6452fc5b9374f90ddc0684fc98add717c36c9fa0111575e43c2514e535fab39d6392017714691276deab6621e547308212a62ac848a8d78dae616383eeb0f4326fc1ef4b460d6060eea79b96318c8ea79513259e56c95160e62cec5ac56e93331a81a1639eb4b8a6a687f4c1db40cd5850b11a45db9b946fbfd284085c10500c37e26354723ce43e00d335e227acacae44a6b3539f363cd35fcc772d8726ba3f44ba304f0c2f9722b894eecc9c2da2496672c8770a2ca56c63764ca5738923b8b37d5c42a0251e6b66d6c7d9abe487c1e7231dabedab3215adff85e1fc7f6513aed6eb14bcb70c19aaf24ad82d413e364b531ea42f54cc7858d969f07116be9bb2e0300c32915da84f61a6b1298c5472abd8e95228a1c660a62563268545b54ecd57ae16fc10aeb2c9eb4855e941ec079276aeb134b1c76d03d8f5245b8379f22e82b500939b4bc17639f547948145660c96af5ce8a391dc34fcd46d3fa2b3ea23c6745f799f3c4d42e8952de67a925d8f1189692d24d0f8049f239026bda8b68ce7d62a5c0ccf09ca400f349a7631529ee4e26e7332fc4ca9f623c486b32b20edd172fccb7354c6d032b3b9ecf5a5cb960c5cfc7c12ec65bbbed92983280356d989cbb4ebecd6569020aea7e20352b3bb2fb69aee8a6a51df0d067ce3790361571e73820820437ea77f28791374a8904907a70ad84b30595491009b21887301019d8cde5688095ca3b56317d39894ddf196e1128c5629707ee3949ccd528ef7c4e7a0754c231dbac62e0577390cbd9311f60ec3cb8e96c3d4f60a1b4019771bdde4bd4a7e4b780071829878b16304288c69aebc0d34444f6ad1a07dc9f2a3083f214f54b53511246c4134be6b180219d9fda0be16a1c4d1ca6671df8f1d72fc22fa607704f52c9c810cbce163c7e8bc8843a770b96c958ae5387cb6df7f36b210fbb3459225f06b83e6c7bc8756f9403ba56ccc47c59043d04d3b750a516e60f56928e3860504956004554f6b7a0affb84a0b536980414e880133edd9fbea08010fd5c0918a1ead00e6810c51030e0d6e17c14d3ef862857e0dd2c503b7db37c234497773a5483ec6d7d9fb7beb43b7cf25b7ac86ef370a2c42ce03b60848e6f18930b6f9fe19961a6450ac39d9a55de5cb6943fe226ca981a68d77a989aa17ab483c4dd957bd2ca80f6e5f9f61c0f5a5f5967b02d7f6161d0e58a7a2176bc64f8bbb0ff17797b7a1796d40b092b78d907b9970055e4537e2a6e3713772e07a58a7a326cd600c2add81aba8dc9f1466474ee7e126fe0e021300c97162ca537083d4c88bae6313e61c46bd7ea07e456504707cd1165880b636213703c068645cf205d855307ced1c81043a691c2351e6e571d93375ecebf127be3a3cb2c2094cb8a66d47dfdf26071603385f8661f65d099ddd7c22648f6c5e861de69184ae19ca63ee7a1d68607f56aceb9e369b12e7b1ed37261d9dc1361910e633806d77a8ea7a067d6cc101f39a2503ef46247f505726e242c3d2103140f401986032a030bfdb7ce5a6b1591dbf6381ea31e88dc47b03634fb1be460c0d9d7d91d7965e67c9e70a5c833de5d52cf12f64e94ce265efcac479f97809ff9ba13cdb4d7094635a1a63fa04eba1b1b1d3b71b3f2c2b2716cb92b0aa7922872de90d4be1bcf06ea21cda6653e7f2df5f3aa0b33ebead26ab7618e26cea3040d8b83406460f79390839cf83dcb63d26280333194036c26b5716aa10542667907dbfe5d3f1197b4aa0c4e85596d297f8527defa46280de22946203a49a2b48b363850ff260061072a49e42a7aec7b10d5b4e672716cfee4ad125bfc9741eaeffb3380c34cf02faf2be702901256a5ed80e4f684b87ffd87d962c46bcf7bc11b667d728da01e4f5f17adafc0e7e61baf43e3636b695266a2433559bcf5d7698fc913c59b518aeb4b6c66e8e8c37d30ef931212733329cdbfea178dd2804411f2b6591fffd5144a787f4b3cdb8cfd083257d81bdc4edac8fe5c2844f8228b6cc45da57d72b8fb0f62f30e42bda5dd50fa2e834090f509caf7e35bc5a4d43ba829916776256218d8d43d1094fc580796356c45ef880bdd476bbe9e933150892ef8a81b897fa46de126617d52c23c9d9d0aad823cee0e4f2bbbc0758b2ab0ce0d4d343a89d4779083544521a3b80480319c6f7d9587e08064381416a74e492478bcb0830a90598b90a2e3c115ded0db6fb04a438da79ca254c54bd77558ce2612800a7db2cfe7917076b8df4cfa1cf1faf20a668dde5d128fdca12374ec36fcbb1f77f7f1c9ee2bf12a744b6b53137edcaa9b776ea94f34c1e4cbcdf2bf02092e0a691e2883f16c852b119eb012640e7cb697426b133703df66e6615fc8753cecf57e28ac8ae88a6bf800b5da25343b89b6f868393bf0c79ce2719fb825b9568569be30bc3d5806aaab6a19f2d056857d275d563211d0c11d40d06f65d7a260cfcbafea4a6bcfd422c60ec284abb50a98465537bb4a7c83322aa591b6429b3a6698913153b799a0bf09cbefc7714b60b7ac6d3e20f984bec3620f53ae44153540daa67034fc85b09aff1f29cc015478da5559651122ba8501a67ea0ec02db71dd865bb49b381374fb84a23e44644badb7f17a6c4f606f6befe6b6f89a0510a10677bbb4d7e9e165010e3646b67beb85eb102a628ba121075705e536eaa2d6fe55bf0fbcc4250cf007ea711cfe59e5b6720850484d2890d591e5d12de986e75afceb10620b96f6ac0e27c8ca34fe4ca17b1a0852b392455912f45817f39cf02a968331031c823ed8e022c0d06789c61eda6832e9c192ce590b5ddc885d74fb570828880086f5ba29124dc73c9e1f18f8cb83a1eecc8d51ffbb0bbfbf4e671ea104004c4a97b1aaa346a87e02fe9373371ee006d5a4d4012c166c0e3f40a72c77a96bdde65caa49d3f084aed6f65c532abf408ae08cf3b513491a7e09140d7355c89410f16444cd855626314a3bdd0af929772252f17672207cc8239ddc75418d3aeb0e598d4263cd3eb0c4122b866b042d4fdd6cd32b200b0ab1e8fe08f5ca4dbc302807c2cdd494b91dea769564b1aa9d4d83529d1d786f471db47f9dbf812c3843e433611dcd4b07391db4d6e4db7ecff3cfc0c1ade2f451be34cf7d00d86a482614d1b015dc99437ab043756346f361466ffe977d8c6a4bedf4a37414cf7f8814a98a06a4d11ef139081432eb85e856f033ec62addfa2f91c683f52f6bc17108662b6cf4a899fcbc4e840b858fe3127d9912e4ba84352f88c8d34b4338473dc94c206deef759957c90157d712a0e534a485a8ea755d2acd8ff22b5ec754c21487e5d96551e275b10e20338bb0fc243cdf6511a04d66dbcd9a0c59b1657c9a924c78be832b1242a65c02f041293079ab2c598aeb600211b909fbe694ceb1a91faac3f8037dc370d8af630e4637d8644b517309f9074aba1c8edbbfb857cc617e972f10c672ecac8da1ccbc7c680d4d8e38dc6f83865446736e90b4b6540e1145a7e7d4f201833df2c22317721320a43f922a629b197460b21bac26470e3012cdbc5d384c5bef5209805648a41a4c5126e3c7129dbd1d48718bb316f71012f504fe1687cbffa8358f0667e528e578a0463ab8839ba62bfd36e8216d8d9fdf301723527c20f87cfa612652e6f657fc92d57f98359e733d481e8f1cb9ad14cdf7f725920572c678782d9271c8733aa1629a9c9dea4012e5b0e8117f1bfd81c74324a28d92661c9376196bb0ee04e3883889fd99a18a4131d189d9a0ca03e73d0d6c167d53c7f58db13aca35a323c572473bd03de31c1d69aa4337b8a794cf5ea962fd49c035137dc600bdeb23f5fbb080f30e7bdb3c5de8a24954aa534d0ea27fc20270eede74fc4d270781c39119a859af32ccd19c9ef0875032448bc93808db1c4304694485f53b4dc507b52879a027adf3476269a8434272bd12220505682fbe978702073925255462b3a5cb14d90742cc29592266dcbbf28dd6c3571358bc6428fa28993e333189c60b9821cc89f813ca58b0c6a3da74fcf0387f888f1943a1e3306ee4854577b25500dd28a84ac5fd1026f76933435ece7f38bccb0ef16c744284abf84f3a24b9f03d81a3c09449f6e2e7f684ae73749e08bf4992cfc9d7e4e95f3045ee15746c3aae5ff62b10cd9fb705b9eabaad56ebf5e1d2a4f975dc27965097c833016d59eda716530ad9e29e58e2f2ba171399cc3303e5ae3907584bf5346e6b119f0b0a9fb2f5a617bdefade95f4a36e2861a4ccc047b3a3f655b4ff9f65ed139f7c1308f32a22c6f5b56c0d38054d024fb26ad4b7c6220ffd7912ccfab37b59590f1a3490c53afa5870b8463d7e6251a9448d575e4327039a2416bb65d25cfd6ee136d595e6bf28e96b60a58590a805f166d4d0c8ea09eb431a81be04dcb6a5475dea07f0593afbc7dd138a9ff99aac9778bf440d3e8473fe34a663d0c018faeb09bdd5c1d283ac464cefbebc942b49ee6f4bccf3c2e59248cd7bd3c5c0d93c9b68e34c70403a73e2d839b36ad4b424adcd7eca3a4b34b2e4aa294c847bf977559c58eee92299ace0fabd21d7439c6659d7c74ddc78e9d0710354e45f45f6fe396a247be17fb7067eeb344b000787dca4e003d133fd045d75c97e176320d3264fa91324411d3d6080cb99eb2332a26f204cee4d7ce2d8d399618481dca8d8bb577a183f1ecf971c9b94ee228dce1384ee7c76a9715861055cb09a933ab602b2920d41091eb5c14ff15809430c901aaffcd70ecc051e87fa6d62f9a97e48e4a60347f96d0bce2e8dec8ec3ecafd291b9a2dfdcb1219650d1bcd0b4685a4b6302c3351a5f030a799172a6464d3032889fbe1777d41f42a5959442551cff5242c8247a03750457c416f0662b6041352d1435855348f81566769593b1c8eca46e86ae44ffa4eb90a2af3789d3ab3c893b54778742271342e390c1c686d59a22586fb3ac42439011697dfa5127a83f1412d36e3dcc7697087d07b6fa472b9abfa79535fde6adc820c5f0e65d1cdaec158fcb0fceee5641ba6ea5623b95410ae1367bd55a265c78805db58af2fc062916dff637a9ccd0afce96c349867ae41c7a1686d68f056219aacc81a8c291930f9f4602a9e861aa0af51710e05c594434d56445f23cdee64b8c5d43eafe2e6904e97a8fe59b36c8ad1e7e8574ba29ee1478a180d0bbffe8e5db0eac4b47f9f78d91285fa3414aa7293409848037188c0750cd2a349948b7c32795d0e48b4566f7255249f48d56fb2d1785ec3e4934a3365cb35af90b02ec768685ba199072041c4b3d6fc7f099495ae99da95255adabc5fc6b3ea26ee056a445950003afdc03891bd0964ae84e6f6361b98999515378da4037c2b2dba5d5f91da039afa378fc7a382be0079792ae9fcbb5e2920e4514c155eb025bd08bd142b5bc67fc3772fa6feb355d02c50f969b281e43804804b5b01703c9fc386888d4f3bc808c61412de13a3b70e66518e56ec8de304b2901d789effa31cdc8d12331663a1a9c61f62c0584c99f5ddd782158e3df32f5509d191e06bf97defdf704688d80fb48b68b42b0e009005d8361b758ad1fbcbf655c4b2a965449a359738c91ea7f13a528a83880022c893f51020941815c8f6996c36fdf537c9ae3e11272e9334a57a911f8a563c47089a4f33c6b05bc95c002db0e040089ba31503222b6b54525837bdc6966df58548e790a38941b7b28df87bc6c7c9c66860a15913999769cc125ef58f83203833625062ec5675aafb700b489366786c14d621ca9edb5297f590ca43f71a2c93211cd7e7338dc1dd8ebb1015c5d12424470c46a0dd7a8f2862171319cb482092da1a9866e56b5f20afc1cdfa0247d783368164409d05f95807e1f7fdc143ead9269e916c125dac91d2cc0b267fcb641e4c08a28067b4508b77d106fd4849642696f820982df7344ed9910a2004ccd8704c2cbf10917ad74c242e5808f9e39122299a3ff4dc66f3ef957666564a3225cbec265086bd05351c3ecbff9c41219ab8cf3ef1ac58501442b083c09a8d43b26b8d1aa886ccb046d04e152f471b77432816139a18ff6f20edbde91351ce7c4f4be25a6109812ca62dd66eb558ad28fd6decc03263fc7ce143f60af7575d2048213b5011b78ca3e2c78c64dd6e38291b46b34ae6abc1f188057d17296bbbecd5b08c0395120211f3e9e2d588d34975f8c6cdaa3929ef58fd1f393b085bfb0deb11b433c449961c48195ff4011a493492fba7d4e54e94a3c38d1521280116ba5ff19b627d858898c403b4dcb0d1c80e39fb9acdb1b836d6a7605e93281cb1f64746e6913a8910e27abc301b4ddf69a77038cacf1f36a4ca51706aadebe9e0313585adf901482c0de6b14154724ff40308a4076d024ade8b088441d7fe1d17b15bc8bed23921b6101b05f9436b382ebea409e6be863b340cfb103608a87da97775e8cd056c688353112942790ed56ab9da46322977ba0c426af2046bf4025cfa04ad8809dd04d9321f7087b135cadbaf5126dc5c1182421496f8b50d230f5d9a103f0fa1a2d42b4407f876a92b416be125ac60f0c9c10ead93145134cd1cd8b968768bd42af51da46073c0a19dbb405cc30976c1f3874324bbd425e846303b8c8c01542d6863233a122c1144910a8b2396ad987306a4a2dc8273a40d741b60625150922d0167738bac7e4f6b6ca52ab1cd0ada5e55d5d5752d9019f707206290d49b31c3018af200419258098cd159141c34d1fd803ad74a382a4fd32c57d11a932a0c6d9561fe176513e0cdbb61cee338fb54769c20084255b3ded5093fe0783796b18b85d5d93e650f5e39c53e317bc0e881a75c542a36653e517bc0525f5370c68487183b6e3c4f7e9e9ef258a0d67e09474c70c475e761cbd19bc62cb9714c79e9cc13e1d884e4c226f133e181068456373ae0cdae6b2d141dc72b477b0d864a57f5b5d3f32b6bec7e8d262d8a694a030ea07071418da035f40d4bed45df3c18aaefb09817a51e667e81e97d077849033beb4ecf4be80b504de9baba66bca16141bc89bdeab8bd66af424b266e6fb79d09ce85775615d43a99ddef8ad2f6813a7b0f8603ab8f6e6ff9b3064fef8b1843c8915d4616921e58c93593760469b8dad6017e6b289acf31b8b2ca59570ae8bbb622a1538cf7bcd7e41bf59101d56634d39064217d21d63ec8cce3a1b746ef54fcd124607fb34893792122e5cde06d875e2a7330c20383d2af3b07dc37a3389d1ec6197ad2e0586a46d700cd656a339bdc00792a796e3a07131a01a9f47d872938a326074abd889f81f8fad0f2634c1b8c6eac8af67ff9fc9ad0c1a862748e04fad1c8454e6cee6fe83804a0f16f68a6b4737aed674b5caf706e8ac8ef421e6e7f434e799bdfe8426c359764492900d6a7f1742e3a32a0277731c82430cb7bf2c4fde836c50632ee7cffdec82c03e336a1042d3307239ced5b886bc13a23e0a7bfef22c1721d97214e8c50fceb2b4189a583610eda9ad21a0a60866a165a799184b520baace846a7e79a6ac1dc74cbc53c67f30ebfb622a9586a3714e9592da159a5debe8ef376708fd5dec1b17e83d5ebbdb41814935a69ac4c5194d13927bf7fc501a5f5fbd929251f9ec33800c5632a1236f844a75b49106adad7254e06e48a1b13ae4cc0f09463d6ff283e68cd7a01c52720e0c22c9140447295798afb48c93c242df820ff06d70928fc6959be3a16cac9854b572de7f7006af27ec864b576f3b4222d91ed2aab11e66ae912c1040e0a1cf1cdefec6eaa263bbb85ae81c724145f093520014597200a9710d9127f0b3e6601928453910e17a85f1700038486bd8c132556b894455130a96cab26441c0bc9510a2977e87ff002991331f5aa8d4700632fb2f11ddaef0203ad8c14e2cc2cd7dc0163e03582a78a78074d103cc4790f0325056dc2f044b37a80ea8397ebb9581c5d22123415506f9a081e9424dd3413000460cb144a8f093f619971faf6d98a3bded5c8150e7413166232ed59b2a4f099696c62f47a84c39930d26ba981b41aa55d0469519d6a4acb8269aa5471e45c94652ef611a88460bb31c7bb2596fcf65929e2ca36297de3ff4e6027e4d503135649d8fdd91669d324ed39a1061a3439a53a5a60a326dfeb45eec36f832018b5b444f0bf1524b45cdbcf6133bd96a4dafa1c9efca189f8e1260cb2246d8f02c4842406161ad13ed4a55a87b294eb4789fd175437eed5b028727ffe7439b1c3b4962407c30ae26991404edc855c4a01c5b54010584e81680ce4808f1ba314b00ccfba5f19c02ca4a4e14357bfc41f98a3377238f98de8844d1c3ca022591707774f4352b2a1c5d3e856ac9f5660bcb587e8c3580d6c76fe9c396e06ac5e8040ccaa46037549f8d900d9c29c40f96a1863c86aeb7c375f5d3c484fb534902702304fef264025ab229fe65e509871800b224b20ccea288fe7a38787a3b7442f300101b79ce690583384cadcb2546d468d34318fb3280a853cb6d60bd4f33d2143dfd03c062772ca6c9ba4317a3b61dc658da3e35c91a2fc6e2b1552579c52359a109e27667e7294cffddd04a0937808253b7f84dd977f9a1e00b88a219ea7dc50d329fdf0d9d0ca0c23882d1aad09353bb1ff4a53f3250949c67d1da8d420556b4d399de51a0c1dc77b64dfd5c669e6d84efc158fec2313fe629cacd1733c4a5f8c71a77218f2a56950376c64d7302c4b56c31fd3d44959d6afad7bdd39dbdb4d514855c480f23d106dd50844209b0a546723a4c4c82b2f1d71dc3460338f8d59c139c842d5b22de4709c1317182084200a338990fd59f7ce54cd90991d2128454a0823be45aa93e14446f90d808f06ce74c76f3ed2ed03b0dbf5cb80fce8936eee86dd636dba53a6c66c51b567c1262da575c4e3f6bae55ed01830892898fa9578f4596ef081fc05ad20c6c58fdd0dcf9f316b42eff73de75ae1737f10f5f9aa6898b7c8838197a4cffce9ddb44465c92bb6e77a1f79a172ded3a893f17beec8431da52cf0ecc0f78ae3d6ecd3da29eedf8769669d3e9bc7e6e0e59a160110e4e82af2894800721638c65f60fbe3e80b6d89fc99b52cbfb50292f3c7f513bb221f42234d64197a56260bad1f06df318236cea18bb5700149cb01473e335d7cd6ec47d4ae8c902077106287d67db85809837bb821064c9ae0ff9552405a68929e054cb5af4d845b6d342184b55aa25e7efd55e379ceaa9f8672817762842a1958162306ff629d0bd6d6c7e991d61c00343e2b400244c5f483a163a9a76ef38b8e8750e0b07d4be5716ec971e0eb409db861747134338c8989bd4325f3d7b5c999cf7ff595f841959225f04d5d170522941e4242a972e12f5abc280ccb6738669007b59871ce7597f9596b003b41c233b50f35c950793c3a625898fdada168fd0d18c81db19f51313161545845d8db20b339eb7acc5cfe4119b746d671b1e80c3fcb29a0c86b22088fa77ac035a07eec5bbbd364c0d7c4258c3591cc102844fa863353590206604838b78558ecb075a4cb6738bd2e4c203ef9a6990b8077c627f472d0bee8c2140ef111468d8afa022d9d76117ca87b7f562bd0892f16b574a924ddbee141166977844943c09b3e65004a5bf0c34ccc6c60b70d774f6758b860de80cd08d5dae3d0951c5bda3eb9f3ead78e19b26e2b16cd280641c4a320c051ce540433997b337f5f2110be182a954eee183ef8a93ac4d9f250531e5a921c79e78bbde82d4995da489170a73f9e4db0df0c203655f0e52f41305e35365908131867fabcbe1a54d0198c1d001d11af417a6e0a7e75a7fceb8645563e33fe0ea2aaa4c4d84c7b03c7f05ec37e2bb1e011bca3f17df3076dc4ad2d2d242c42ca857cdaed821a0a95438cb5018b43e305a8d188b890adb3504180c7536a90daa81ec34130865db10c82f9501b67a8f50a0c02fa63b8a8ada4967fcd9d7096f7dce32d854e0e25339b217382d835c555f6d0d840f72d0b1a8f8078f55d7afa537ebccdc6a75981a74f60f7f11b600408a7e417ed66b889f940fdd779c7bd194af212853687378f56a0b70fc89b47aebc32dfaff70e392f74382fd14a06f4e43a74479bd371bbf86a09ecbb77bd6acd63e2673d392f61330dedc85c4531316016c08a1c3ebe72060b7a0df1615da352da4b849123a5f0390e3536b2b020899d99ad0c2ef0d9f963e0d203764cdd806f92e8485ad4537bac7756ed400946897d756a569b00687daf7f519e73b75161f0cddcf79cd4ab672e45a6d8e97637a3b2ed1e520a983782752ec266d22ab3b75479ef5a9ef74f5c2ffefa4523dea50a3ff507dadbdf1bcede271233116c70a9493fb8943b903b84b2b340b63109b327c12914cf9f200eb131b0e0df5f7982482c37bf784881c7d8c294221c8b989cf0dfbd0367f07202207ce08d0590e9507db901a2def4478df167fc7801bebeb7633ccbbe3aea84377b533d6d00c90cf9ffa7e20d8809bcdad167cf47ad4ea2790177825eeea966c020f83dccf087dcc27cd05c1bfe448419ca557c94d7513dd0228139b7ea58561c1ba303064f3b5b90580feab4ac661677cb373f9d52d8044c40e120b5b4459c9f795aac1ed50be1c1d18a72703a42ab317bf108bd2c701a955618b240e2fc14c22221085061c28cbcc83c491780115367c83057491b516cb15c1fefb132bdf617eb942fea8cc97526fc2ba21cd3266d82412876b9a0f83578e7faa3e631c16f8749329eaa03d7cd9718b44d2c8429539cfa8eed9ef2d634692c048645e5f2c641539c83563e8514f758bfd2850d7bfe15024421875917ae698cdc1108304ecd83539766e0b22e820edd45121b336072e67acebcb2e1a9beadb84f06248e4f72b0a00288ba59f67ccca91cd94b56cc6c1e8ea77b385bf2c7ce0aaf026c12fd2d7428a5e482a0fdb2362ec17be85fe971759ffd054919a45cdffa348ab0e5e812030159cc03b19d78b2d57be5c076a2d09f54a91f91a49ba38deefc794483c836ea63ed4252b1312f2617c9948a867d6d7c9f95ab61fd6af15f95d66c265bf9a98fbd75c3e73aa30e0af0f8b1e035b13a9d0bb613e8de0556ae00be92606bc95a90ab20462a7cd6b5b9b5fb252926704257f87f4e3887eccb60620efbe4d17cb84c9424c342f989162ecf423588cf36aad32cc19b0b0eb1b1ace3a87092bd070b0464216d203aab6ddef2b2758374d2f10094b4152c7ba5da4995e89bd4765a7adbaa87d5762846d79d52e6621c10e203ba175e7edf5ede13e761c2cfaa5cc17e5cb64618f69853c091ef9fbbabd8b57c9e2d72a14e23d3ebbb67010d8f472c9805423df1e68dcd526e21891d5b0f235e7a59aab86e75f882fcfd28fe8e2aebdd6d2b19d8b9b19a2807349686eb1606dea25cf97b2e587d203232137924fca1db45dd2be7de55b6a760dbb01e5b2661b3381994eb61a24ccbda60bd322646a7ac4114ab145a1c3473ea36a6628a5fed87a616fc3ea760ca41579e138b62731e66143167f791c88a67820b56292353c9f9f318db9a142be455127972a298055e93315f9e548c6518e1cae6fa1406b6ac8f136f4b0a217d2de576672ea170824b1dcea9dd37431707a0bf6a12d411f163bb30d90b5d972394d7b6c32e87b7b5b7b88f6c88e265c2324565f92fcb17008f6c5f9e99323271165a0aa6a81494490d7b935981dc43c5296da0ad648d16551654ee2b7a98bbe0f8d0a5841d2aaad430aa8cda85a2ddab1be7d2049f241c3e5baf1ec8d341b36f8eb49c1ee56083c6d28f71cd6f4653cbd98507081e795a066e226c0fb4d2c6e1fd7fb7e177bb0d390319b4e0948c720ba8b1ae58c6280247b777a7324eb1d57c9fe7330ae227f9e4a49350e44726936775937367fb46b624b6f69ce3a06bf0e7f9480d29061d72c495e0d7b4c9d66289d944857fc37925368e3c34d03429e1e5273bc27918535a15e7336c140ac8124e9c2e9b561880179812cd08e33ce09ea7ac52197b34c9da3aad9435be0d6aa53357fa1303870de46ddb4358694ca249e4632b25d56f4b458f9b0dcf5c91764bf8a94524c589c3756bc0b0af4f2badd46e6ee0ac4e6156723f6f6746312a50495dfe4d5a1f1fc85412b61363b5b1bf5f6d29e1e2c6946868852701d5bb057df911b71f550481dc191d8901a445c12b698ade5583ae67a0f226d1247d3fa6dce4a78a0cfe98d9b4b0581b47b88f36c34abb8e7737964df85e012e77640ca78265a13f4d3428329c1e53f6e152cf4715f8b129145142156d8dfff644233ff2d175d79f87bd37e1948fae2f574b1d274b0ec13bde05ef8db54f1982cc795a40119ee01099662614e7201635d490e7243caae301d912c0b703d8dbba7a78d299d1fa70293202bf7cd10a7f8bbb3bd231bc8a97b0b0060817e13e747687db9876febade1be570d631772395cb676fab1fce95322ec350d9672423da22ea02b930f3bfa90bda31dfd614047feed67c1e4cf11c4ca91fc2f6b74f5383521e5c1cec9e3d81a8e5d948b5400c87f9a0fac49508eff3e1f95911d5dd13f3f1df388466a3af4b1c397446e11f2630b55b061d9b76f2ff7b71885a8ad424ad8f4da9f5b901f5707b438c2246983018866826cf7eade1b921ec3251f5a6283d37699964998d56472bd1a08fe463da4a91ed118af90bf3a7ff0d49fc554f5aa79e38836f6b19ba777cf37f8241aa725dee6d7fc0f1dbd3e059c6b69b75dc280217426f1f3a251dda3fe726e24a0bd10bda449bd55df63cb9bc0ce77f0a32aef85c78d69978e9de58392a631dd21c0dea658065d1a5c2597763c846ac512e0cd160e9448d1d7cda8a5198cd8a825434d4a0170cc266aaef5a78c7d030ae80f136dc41692473b059d04b1e972943d86086ea019511d615e21272599c051e54e458b5873eb3450437a251dd7fcaa5f6b549f1cd2dd1c82dcb680be32a1af7f38f20067e7a249edaf75ff4699cf5d25b52ccf8c35945cedb620f0b9236b267e804141012da3b543abe6bcde1f1e363621ae94da82f61cda86eeb1b2c0e4523035499201ed5ec1933f73104ebaf827eb8bd1fbaa09f829a7206e8369619a47be9ef37f6563e31271c9cb44bd580747ec37cb50f32629a6233c07239cd8ca0b091cb24cce0bebaba8362701d7570b8d13a960d6ba7316ccb9d12737436617e8cac9921a7e15604cb692da81213980b4742882c2cf0d38cfe221b8deae1d01bb26e4ec56486e02cda2b172a7944cc79857a8744533ecb0e5d8d59a67be46712c48ba5d2fa34fac8fc8b3b12f3bcf5a06844ae2c44462761d752a8de2cb38b0018e868581ebacd22690392810704661b5b3536c8281fcb7e1ca7f216892a1b09abe4260a96cff14138920f514a3e86139bcd09875be55d9b45e8a674980170c2731f53f221f7937c9474a400d35900bc75a41ff4d50beef98cd403e4e7b30a89041d6a107bb376dccbb081368e5d5e64828a2e302ba0b5123c048249c60751ae5c747716ec4bbaecfc1c687ee3c34bae01e5ca8c22e1d791f12a72be28d16c4e6efb85c162741b27f2c9d5c7cf704734892da45aa0cf00062c390c5275fb52c7a2f900c1029afdcedd88cdde606925011a85c42d6f3c70af95c399ca9a7322b5cc23e58543fec4ee52a87532d33106f8a4d0167e9b7db2f16e016a0c86ba70fe6c03a712897083ead0d3b31175c331db5963c30408370c08b11e7f457ea44954a54a6f1c15ed771eb2a0fb1335a42967fd05809a1f6c3c3376a6fc80657a9842eda066c91f9fd9043b889f822f83c11fb28e83fcaf2864e5785d0ddbbf60da73b86539feaada1b551eccde7df9deb238a963666179416234587ffa89eb4e19b2a992ae6d68a07e18a672a958df1c69c1f872a9e2c9b2cea80b223aa626b3b23d81ce4a598d2d7c374cc978fff163eac42c84a6c0cf88ca6c279579ded7d6f621ab72e180f5b0279c7b72dbadf5e04ac30d0764834e83d5f15b1e50a9046a94c6fb653af7ef215a08428809b8a58032e78dad5ab058cafe3124c000144920d85333c62dac934dd83c7060d7dacd811dc81ad13275a020e202f8f9b8632577249dc1a8a2dc25ffb727fdcfdb7f9af9ac6ec8a6f096260f7af709b689a22dd2f187ebb73df5e439d2f9361a14b98c48f9eca0c5259abd3722dbb3bbf98b83ca4f440b6552b1837d7aef7c607aee0a78e18a0ff430591b78f62cd99fa314bf7396733b2e7d946b5ebc34d0d86158bc6175d5cdc99560970f34e1aeff5650cf47e4237cca0b53a981862d0e8931b0ef3d8500496e04dbebbbf15ccae0da299dc0fddc597027e4eaafef71bee2d6d74829fe1dbf3d2d7836115a80cc5aace84838ab19e5714dfb010b8c79b0008c6f8d350d3c8687dcb2e48ec269d8ccfaa9d773e7207e1dd28a3857144a4d52057cfaac8bfd41a34321509e9b52c16358b068917d5832c6654647c4e2d305072df04c35b2178bd08b8c29e91a417128ae5ebb95df1a24a06d1f1b1eab3b5b1ddf1818d613909ce3234242c2aa8cc5e163009bd4403e03732846ceadcc36fa935d62209bad635a69c4e13a71f4bed4f81a9d514aecc664cbfc638763add6f8b99ec84a280cf5e9683e07b20b11f1cb0fede3eb5ea69af42e40ec973ab65fe40f5bf283aeb72556df84c4808cb28231132532d2dca7f78a99c857e7c67ef2b1357b28b994988d90a8640d8ed9083d315e71d5162c7ce8d08d4660ba990109ab991ac399b951ce4c46b3fe7873a39021e7e12b04e653a305ba793a54003c46769dee3197f2ad465a298cbc42f3d7a1f480efe98d0253cd95342c4cd22049cbf0b0c267a104c8b33f961e2af95026fbda6d204736b18b49700f8e87a21d096acb73dc1a7ff29f49032c5c2e7a45594740fd4800a368f7e08758f2450a803a717f5ec012549c04bac9163b8a99ab94cd5941671781c0f8fcfafde5279e14be8e5360e18d26e85740bd89085e1df9bc3189fdd34c6e9538f47d061a2c1fdf2529f4b2494a5f0f6de1e40b3156dff9b59f4299844a507cbb36f7bfda9c35454f47b3690986958f46c80710edd2d116c5e0e4faf7c5a68682439bb925a164969048d9e86f2474aba183af13f6dbee1e839e9c36e0a6aa342c86722b6e171280e1ed4e58531ac8129cc0beaa041af085c63c777a5a66e21c2b58b18379948c3f5e5fb3051517b889348deadb315425536fe5dde70fecbfe37a5e3660efe3c87e8ece9576674bb93db3d0b47f3a771cbd8e7fff13887b7bac15735d7c6d9c6ac15dd22a400f3deccc24949ae9219c9ca1e8fc88fd798cfe5ecc8d259a84fd442be39514c7d0b7fd08ef112fb70df9f231362776ed9a766409b8b809b043c681159bcfb72331de75cdd6bebd4c453fd704bf7dd295499067191511415a4536897bb943a6f5f5397ed9a8ab85ed1cff8782a3e889769e5306737b81249517ee2a105479b54f45be9d580f83d85c8039d9b34ad6312d86c6bc48c30a3dc1c3297d855d49ced13e2f353f9e41f252b6a83e72b6afd2a5e7cf54068ee99f2191935166c7d7135db821b3fb4b5b598bb0413b1a30d8bb731e19f57b969083ee6bf340b5547dc0fc121de1d079ac11336e0cc865d22986d3b95826fa6de54a45e9d09f63f2154a6da26dc785d33b36e6004414424fd95dca6682601fc1d61417713aa6a2e0ad11c03e6cdebe24ef83ac1191f5c77f43135db0e3f7fe515b185873d6f6ceebaf125fef4500e4754569303240d40e6d5c7308cb1d27efe375c5a6d613abbd2b196cb04becd63fd8e1612bdb291848e6089a5258991254c0f4c6f4737f27c3e6fe218f59b7e3fdb5423066cd2eed6fa539b4d0018ae4ad65ef9034d7b1592556e7d5aa8051bbfb112236bc6869385e19ced1e2fc2c4db4625d439cca0a309146071687ec3f7cfc94e3aa282c84f080aaab2e4462f841537eaa96378bb74e2c2c7e24f38a3f720aa5f37b4b34bc5d1b2e38ae1fc869fc8b558a35cb6e509670a24a4fd9d024ff6b2d2ce730e0f0359495fcd98a2199ca42b35613db27865dafbcc75c719767c48708733e65b53d052ea0922b1546b33d76cd9b1b0b11bb1b79aed07a20e8c4a99f0a37132c6b86903f556db9130b354698e3303e50c73aabeaa670a6aa297c1ac7e9c66f708fc5bdbf397474a34274f448d7935d8b040285192b6600cd66042fd41400815fa9e2f838c315c1a02fb820c1dfb36d5fcbedca77aad73ce9420e7e52a757edef19e21909bcbac302c7b1075db767ff16e8c933ab79b7ba4b8938f1ee2e759cbd383aeb18bdc883d21b8d03d0a312cb2c307d754889022acca3169054122d191dec1546eeb6b12b8bc940307c36fdb9105d512b5d631ac344dafd65096e51dab35d1c33fb1c1bdc6f535f8467dc5b54b888c6e58e2894fea19fc684dcc35e22e1126a7520d75d736a8ce3e91655dc1e8f46a99cd05c5411d72b6e1ad6b34e5bcedb860ed62310e96a431fbfcebfa14a444bb134a73251c3c1a33f75c11f66d97c813fadfcf0af55f00efd498ab1bb1ca18dda349098c881046e7989b162888d63841172a5bcf65b42af81887b60e9aee9f27b6eb1a1957e6b1fcc8f807642e568621dbbabae3abaa7d03ec80b915f155d01e6aa08f2f6d3e24bbb4c881a49dbce2de1d45f7f999c8336304838de2d3a57db7f23d8fb29026021268cd3200bd7d8937fdd2052eddeb68d8510bf3562438ea0eb21f5eb5611f4f8e6e0871716e786e552e4b480c5026bcc85b7103d65c7059dd30239ea05a4cc337b658020b061174d5c00e5a19ac7c3bc42c2b9310dc491fd37d50451a67aad59a6f12ea4b65a5077e7ea387589955108d7d838457d82c90bbdf0d2ff79627ff9b604de9d2cb55e10329daae942a4c5eec8c53525c53367b1f3e971b63f4a05e36faa7065ac35eb21ae0dd8dac51b045d4fa68babfece59979cb36c54cef4e4363d86377d0d667bf30263d4d70c9f95d620d1a9b1c584bc3f08a940dbd6db17528dcd1ffe1e68d28b54be1347ce0c91e3abb56dda5ba9b32ed448cfde6368f41910160b19b908f3b5609cd833827c990579298fdbc527868f01aa563851f094847dca8644eeb300d19dd419da702f8bd79b2b77a2d210d5f849bf6f771533a9e9a5593baeae695252629a61fb341478dd8151571873031793548503f53791692d5318d39d93abd6cc5e77f5683bea336673382b647829101cc83b2c49405d10a8a50a5b4ac733cdfd8f55d4a2cc36d2144ac51c95a6878a041fe2c79302e7d69b11112d4214a3cc2d6d54ad40b9b08953842f1ae0352bd98938bb615ce3b9197a8b39e25fc62dfc7ef784695315aceb4724bcf8d3c7a4c0105fdcbd8d8508adeec78aca5d536d34840f2d0d4d33019e7fcb75df9becc703243141d5cf935f9d42f6b36e3f67655845ab79add0f050c0ce40e7e28179673bf54c52950469aafb65303d0e27bae68c18b741ba8f7c1fe8f9633066d49c47b523c6f09d7e441895fe50a71dcfac15e0fcb13f5082d2fc94a5df8965b5e11ddc4d11d36f69cae7e93694e214a0996a0705ea35139b3f6b9177343ab967c6d848a43367be183948083883f4765312b5094103b66e8c8d35cadfe3c776154649a9385a8442c86a391b23bba19ebbbf1d0201471337522399b781b5376b9c3ded4db9a00f271904655ac4ae42474a2cc0b1d0f3528f72c57e5e62f800a2c6c8d19413e51c42f6081bcca51ef3d87f16e16db9597beff6801a76c3adf2793a939d296f302a351902a3a9dffbd185ddc24f9eba8d57dc001be14c69b5749c6f6c6a7eb6163b1e1571c6f107abb3b9a1be3f3fc0f888e69d07bf07c24be2e01913f7df895511c74ed0197681ae1f86559ecb64a09f869c50412a9d8ea3759f356aad509da3617f739cb1b95da1e19b5140e8c0ceca139274d2ad7d41247b275ead5039d27667547520f7755f9e2c5c2c6836e5f4027b0160801bfd3240e73a65d1a97f3bb48e431c241028e49eb846939baec30c42c717f4cae9f89a62d2fc51ad6dae1cccd268f606eca0c96dcdaae574fd31152524bc787c244c69b31543bd1c488b29f7101f8e425eef3c6abd2bc0c55b308b305b30d1e7387462087522117596cf1dfd14a079430e312f137220959a27864c8f7b4a2a1fdecfdf7bce75508ee81679731f32d1f7f3198e3f535bfdfa09170ff6cc0a95719916083a0e8ad9d3e47120c9e98ffc7e9398dff0d74fcb3361b51b4f24d293c722cb7a5ad8954b114192833d3b1953ebc1e8d12fbab74116e56d0e07519ad354ab86056234cef3704aa91c1262b4d3db4fcc12c7a554a10051f52800a0dc718c519effe3fd329057226eee2121d848fe075fc439955a509a5c1ff8506c68d54e6bf338995b6750f9433dbebc89bbc3be0b8c650f2352925dd5b22dfb43834f15aed82f3f70c32a948cfb11b4867088fe0569c2f5ba14c012f23df7076eba3b55e2cc70527d42ab4e57191f8a25ceb41b9d4d01dc64a8efc67fada379ff5d48c33a0e1c3521387fb0952918d8427b695e8862f40c222f20d30a13c7c6cc8a679fc2cb8e469018c977c31168ad502dd28428530d1b630e95ca75d90543b64da9470b0a08ba9b1d45b956360cd7a61e4d9fd5f339de6c508b4831b55a222159f573d55de262c44e3efea22dccd0063826ae5882ffa001f78930142330c4d652c067431fb9a574fd40fc436b4b04d3e9cce778c02569f12dc180e092e8629fc7c38a70867360dc6da0d5e37c9c14733816595ec3e418887e9b9119fe8100374c6823bf5d06d5d1d1469354971be8640850805827dfa6f0475dc530737d8e552701cb47a35498dc2e01285f910aa51583e810d622bd16b450a78089c1dd5731e1cc5bb4fbfb2911b3c8aea094426c16985f27b085e89ed84baf28f85eb3cec2d31c65116978a44a575087b72150ddc6460babe6a332962faeb90c4002887d174dd01bd4194e04488afcfe402d54b05fd70f9d19daddfa35e9427eac7f4ea0c4cd24bbb1fa04e7543df268f32cf221b57b1fe9156c034c4aab5576d7e91820be44060874136b4bcbcc6b4f27681575d3ac2c0f396b3fc9e8913c225bf35b8cbbd3e4825412f53c6b4ce56305961ae0abf8ee5412eae33462bc489b6c28a3821e2f89afcdbc34fac7fd573699e46f2b381daedc84b34f91718ecb7de5268734f547f15d21089c88b5bb1bb1b5df6ff1ba655212acacb9a1d7afc9927abd09fb7493fe49bd010d73e0c6f5597c7babcc094c915c4a1414583e6239f0b75537d975540e73f3e8bc0d1b28c073072a1b0596bb36a69696c514dbe9bed38ca8b624962632461e4a784c64dc0f258d2c383d0fdbca7a08c39224e0dfa6e13d99d70cb5290cec2d725181c86ac2e4dc3fcb2a5e630ac60ac4085d76b162abf6c90a3969274ed348ff8c2493ab00b7251da422eacf82247cb2f0b0f4f31bcd82ce43c6a8160ceab52db4d5aaede3f71f878cd109e6795a01b7ecb23365511cf890ce9a45609fe555ee41680d902141d3cb45b46400cb08e53b688df527b14ec84264d0ca6ed9b3615f55e4f4384d0c6d4489cd75ead20a2db945632752c965d06a402f9ce0b0adae1caf70a77ca665e2b3642cbf93557356ba98b4a46dbea590f6fbcdfe359717f1c465a2b7aefa88894f329c22f04e1e3199189a71955e727edb4638f849d133ea0e747b1f3043400d4a2b8d881794cd24cd299bce6cce65ee63603c56d076a4a427b7b86fc48be1f436ac3cb21d969014c803f0ab5dcf72a72e8293db0c4f0d8b4751349e144df9aaaefdfd875a98cbbf9858aab07326e3e44d91c51813cdb84861117bde4e5dc7a84f9bca8e960801c35d29e3b592543f5bf197982646e510b6d53c697bd3518e462d7788b04d66626952dfc99e652064cf68eb6993eea16ea02c30361a1e69c413590f44cae96e8d6a9e1931a45379a742c246bdb78a11497d6622eb2e0aceb201781a1a22b6c1425319aaaa10a9dad236d99eb1019b884ff186d6759e18948b8fdd46d6cf83f5efbc390a14e97f8781fffb40811dfd57bc684fece0db13aa6a1c214493f19cb0707d9cf72d9602293ca31bbe5b8d3834094a8d0e92feb4aeafad66853ae8c3b6dd410abc5c1a5e94137b325200fcb0545d6121a790afc49a8c8cec757f68f5991b0c00fa1086d54040d1452a819a5c1aef4f3738a6db25505a7b703b11659fd0897b62c36f951a4e0abcf1aa3e10e0c9e0e44fea6c1a95cb7c5a36d9933f9445fd45752fe1f1f680af3e92172c55fc159d0f911f9ed2116a323ffdd64faa074e649017f7cedf28515ce3d46cc6d01611617fae87d82df6df30546c589e9817b9694e4db98c63b0b9ec204eed41186b62e4dc60e69dd9fb5e3db4264293b1111aa1be2bf9bd982f2635445f53c1688e52143233a10d374e01895d072ab317b6e878de15a65f991f93d367e634a8019b4166927c9a24b98ac0725278a7dc9afc616e381782d503be9e49422b45377d34ac9bfc0089b037bdfbb0fc7eddc00dcfcaa597d181e39b7ad17586fa8cdea62d8dd906f56333cc6a831b9d03cc5b90c37339b6227489ea9900dd5e1d5908c28020694d9a3cf024cbee07ac85add847c1aaf2b0b62b303e2d512b7c08fe72e17c603a9b7c581a7b3fda3c522c3e30f080921431e3a4596ffcc941612685df714c506e0b216eac8cc7d9691ade7cde74edc2fe418da8b3fde3224eb882444a4392807d44d161e5d6c51dd3b771dd6a4bb30b91a20eb64766b8540336473dd097dfc3490e8de885f345995c8142fcfc62747bea4983f02bb69e1d567a7c0784e456d326a41e697ca8a8f342c28203df70ea7534e6df76201d93acc2f9951ea250261215ac58348ef6f32e1e7fb5e7e69d9dc8107d9a253b4ce4efbd5c6a8075b23434b293fab8042e9ab62161373a9679132b775394c8c6013489ad462c86a24431ed68abbda932058ddd126a4b088e3b4575752404072d48cef7d4a83e6ba7034ef779bd527ff4906b6a62e15d4869f51bd30047a7b60c9e29522d6b02cbe992283552200559a76552a5cf4d684686376d2b69572d58ef12c2bb560c54a9e17295c8727e5ed5b601eebb2ed7bc9d4f52001b12d5ab85123dd70db77b346b12eba8aa653bdcc49ed987dcd9b6fa9e8073c71a311a06c31442e39f7d6954f05400284403af0c880a71f3a096289f9c97f8756a485baca2066f230e0abc16cc07ca8119caf43d44ed5ccdea9af525b3fa5e9c363121d15e6e898215d6a9dae0764599d66bc88bc037f291c879f31b05e9be4467522ee6d36fb1ce9603202d0f6cbb30532a962f3881e604af270b9d639d622457985216e1657d25bcdcc0df17bd023a5729c4f151162514e1874e3d970be14dd08704172488098370c27a90f74e15b0a5a1612d29feeef192c412454da4040ddcd0722963c254b874dfb158253845fbf7b6015b0833ca2cd024c9200096cc9df8a4a41ca0881011b1392401da68cec795093af2e2e7f83916089733fd4ec303dd9457e17a2e44eca9615f62ccfa3870ce672f2f34a0e92bf60072799442adf708c17c163c0d97b483fce83f12893abad2b3c66c1b1ed0e2484170799a561b5a6c9d6c3fcced52b61e14cab90698d4dc9093b6e09aeb491e278eee4c6f257fe4fbdf77f79a672cd5515c78e49e9dc3e81b0d51db9f4d970bceaa75e531383c16420759d972e8f7a7362410f0a2aeb0014fa0fa76b4546b42c8e15cd4f7c0b5228240e4f0c6ef6007debb09c1b4c8e63e21798d5554c21044bcc2a6e7a2136a6757bd456ea7217bd0e88aa3226d65611c205431747d132602d7f7409cd74c167471f8a7041f60b4d41ee292d551377078a5fb9361961513278cacef52356f1064a44ae356380b8bcb6739254beb5a3a6cc5feae7c4f99dbe81073fa38fd3905bc159d13b4e896bd023bba3f076e276444d87360b3811eeede71b96cee3682a27d35282fd6f44989e73f4138c4a29eaacb47cc28f2c5f2f2933218476c497737ed4501ce3e624598b6ef0efe7de6a37d25a051264094fb366622ad6f1be5ad5c3dd49655f0977c802a55e6e570622135047dd0ac1984ad7c6b0ef865ab2e64d15ba658edf0a89595c2a96470eb134283976accf90a919931e1247f39f15296f667b09700e990ca37ce8a85b9fa32cbecb6238a493a416201f6b24beb3e45371c973744430ae02d85237d510c2a5ba7106eb4106cabf774d04ad3a08c66e84675739dad7b11268a21bf2f7e51baf213f4dbfde2ce71fed0c043d37ab246a288d96ef4131124c9f6aa4eafba1d2355ed51a15711e2b827329ab1415cf7c61cc6d2ff614a2c9d05500b310efc37d8bc05f853d6da21d0eac7e9a91ca31841402d249db15a370a44f75ed3991ca3c7a51a9b84e5d765d316402c11ddb9df587908e08054961f9052c1aa5327002f1e2f8bbc3bb594fff81d8236558cf5548a40c4b1b03721e3275c561aa58b28f1fec346091afc844435828685e71cf35814d50886a0012f3fd0857e3303d15f11b2a0a2b6df2d71a9fac43b0084e11063cb411400f4fb08cf6e9021a498f018f66bab530b20e58f0fcea73f64c6d71906adf320cff8f1556eb4c9bd4570d362d1b7db57e8115a8bfde565a4a2430ff5bee598cd0e01ffea8b3fbeba0db07286a4fa5d61aea62608f6ca5376ca18bca8f3a60585fd47d8cb7ae18b2969adea0748bed7ec7442d0f88f5cae067d899c0b9023a76c6388e22cc050eb0b55801c1634a971b4ddbc2d3ad671a57928a13f321d9e335e56ad02fa724fcae024189cca7587d498df9fb9c978a2eeee7ce73c892459f0452ac026b20f1f08dd4d3daa48b8a508a5ca9aeaa8f67bda8fd57c837877d5924a4caff0f185688c4336beeb1034b700f52ea05eca02e38a3fe60e813522e082ef724cab829511e0fbaca9a8eade836e33d3356e41bf2e1f156984b189f56a464fd8cea525b448225407c1a41c845ff5e3fc49c0fe37ee766f0c0caa2efdeae03bba2c0d1a28b28a1d14abef0274162e109a54fd0279f86e9650d038bd6e5648611f1dbc141d110eea7ae35468598dff70a8a31da0516c45f40b5dd835c419fb1844fc593d343d78a2a68d05794fa08d5630fae41036431b8bb5393e139eba0eba01ea0876ed7f6a93ded9cc30733f55d9e67cedc7e741a20f5ebac0ad89fa43b8cc78aa42c886032cef1c11293cf21d23ea0873c0ef7e5be5179918b97d783cce1c1bb965c4dc30b6244e16bbc3d56deac22f5cd50625a95d3f6e4272609e3c0d85dcf23d66bbc3197e73a096ea919a88b1f0c62eb4eafc28162623cb1e96e523f6f08a1fee78a64f6949d1b4cde3361ea87455776582f7df350a30768c5550bd648d6f4234a66cfcf3696cd337357153e2a1fa3f57a6fad70b369b6766b46d9326ab4ddc3183f9e603c9ac3409017acb59636ad0b1e7a9c65743aceed41ddc86b0f08caf5e4e980dd5f326eabc28e1ce681f62307a2f4b7e2c031765f8931c449e8178688fccfbddebf6456f475de2791e12f8e62c434cdafb0a84cf00b5490a36b4548e59bd7a9a40c4417cad2567e8ab2725dd0bb6e1f31072dbe354b78e4695ab14c41824da053256cf8a8c08e118f0d335bef1d324ab554dc01e4b00962f272e9c092fe1134f4153f7f41f4362306088e5e936642260e35bb9a8a7de6f7833a0ccc26ee9ee69d5c5c48909f1bc01028fe64a0fa55bafe8b02302f0f8c2182c254a9fd584ced26ae066c3c56fa68281489fe8cf0dfcf08cf6a93fa9a83d7186145fb5034485b99724f8a3603b54fca0ba520d87a2151b20f75931dffb2dd128b8d79fa20a8da46ea6b97e8d4c4f2ec12bb07b6ba8d781d3712af3ca4d0b1a9f37c57ccb2bbc4c53fc3324feba1c7745f59ea2c010932994243bd40268841bc44eec3e40bbf5ab275a02b1abe007bd399e0b8ce14b12a28ba0c77864c89e05cb5a78ac626ba25e9bb487c5956c8eb16f42b8423536878352dac51a1c92b25b5f583f3bbd52c50a235958cf45b0497cada21a28ca88424c69bf6a8f5e270e27599802c69fdbc0af25e8f70b6e5d66360f95499904e5516c39e7e3b1486bd57a55ba24e002028c164d3614aecb67d141a5bca59172acfa2819f860aded79af98e2107d4da7fea951f16803630644afff40b27e8911a23916f958d579f7780775556001a64a80c10b3547119c29823c8e6f6221ea62f2cb7e7a16bae65baf063169db7b4451bb545da46b5f77f1ac5df1ff72ef38898164d53b23bebcdcdb71d7d45e3fa3266cca0027f87c8e8eb6bfcd5213c01407ee2810ca8a1ac73a63fc5e345be45174753380949448e786161e1de2863e4c76f32fecbf5443b07c1e31c7c579d4fc94632797586f8f363baa792849914a98484f91255aec102e65ff4a92d5602ffef95c437179a5d2c483b724c403d0a1b84ca08f7fd3ec0a78abdbb7b05e1bf110308fdff06e141927fcbc11f340c27e9bc82fb043e1db113239202307473cf339cb291014307da46022786cfbe62bce9f8877ebfb67134312a92345d24a88082d63e0cdd4b7870203e874255b458fe5f3804f3ed0ec25670af750b50f60fb8acff97d707741ccf770928634dbf63d6dfc3925ff9efe8f32ed70ff71f2dc5907aba4cf35a661875ae222693057ddeb481c668c00656b714d9e0391bc1c8c9983f2c308b2f4e69c1620a6dc6c3cae2e3e1873ea13e31d1fa29cc15806580e534e1a48c319f6f28f00e3c817ccd451c82cc28bb95623f4d8adacb54eb9ad0fe7e0b52385281a4930c28c2a33d1b2b2a141aa219dd6adee1c59b172159e466d0071b092368d658315f9e65c6f6ae63c376adf3309f05ca3411848e14af0347c2e446915951dcb91f2ce9918e09d1cc1201cd1da2e7ebf6c3bca0fd8c40985e2519c892915de2ffee9b50d76e54051d7d21353230092ff24e0dbfbadaacf9f85d56c9873974b4382f424ce4007b93d01a37490155e716103de87055724886d57c68468f440c5ab75317b0c101260d3d3f70b790b4e8a10a3fdd846b40ad1731a868d48bd5dc0ffcaa7c3fd6c0a4ce9e085386670175d056a53161b7b35caf514e93f4e51cd54e6906356c390b8c93dffb07c7b0ca69ffdd83bb7e0d309ed6c788bcd3e3d42584af4e49e103614dba2b78cc23b35b2e8228d81d849c7ff133bab5c8adf8264479f86126e1090b897df48f9b3778171049ff92b44010f1cf03f02e32e196a14fa09717cbc829c7cd619bf03ef832d995f2848d0a265abce8bbc8fa2a2d0e430071e8feb302bccc320a05d15aa8a99e88e414f2212bb81466179e51194d1c5a01d9e274d85782d49cddbc46cacf3a14ca1072be7e9bd1799621c41e050e4f423078e025acca7e2d6603f63ef4d61a3e79518036f0e1b0c4521b6dfb4fedc938308baaa7f8833282e24cc0037ce1b27ccb0a411c3bc735a0907045a6665f80e92ad1a844fd47eb4004ee922f5c7a586c46524242603aec740fe292eaf876d47699158faec3c784d0267142d2b9a4dab63acd4ae53ff7caf051615eaaa09d4293b0233e3498667e97a475e6472d8e9cf44fdb26729a570994d223d1e3a424ed686accf7f895d9459b1d5a95d79fbebf67057248922c6751163a1dcf8471b41c49470c29f1f922a2f4d77eb6d00da4135cb688e9960eb28eac5ac380a376e04b86b623b7b4a2cb1a051f5851ef0489afce94bb94b7a8c3682c00b4882051e8b9aa6aa6bd60eb0fc43410255b19749c08d4091fb20dd8e11bcf3f810ab03e7ccc98341747201268c649bee58110048d3d2aeadf623ef2a1fd0c77c30bd7224a93562dbf6377fad34b0dcc60445373e1ec6b2c591b0a450ea32f78623bba2cdf0ae3b1fb4661d4e68a865e6e4869d9727b371a78e87d7439b3bcf7125c169d47ec8d13da26820b215ca0cafac680bde1e66288d7cd7b5585dcd062272350ef5ab500a3644d368e5aac7379843a174ddfdf46150f926dc60d0fd5eb3b3bd399012c1ba36ad0d9d5d77e88c601cf410e56f83cb43896929e6745a0f8e4a43a30e5501205119577328783ffba93e6ece4b52cdaebea94e26dc7c228703ad2cbcfdedd949bb20ca040b786c441791a45a7c986ca4bca556d2b9a42c273008aa13bcd3309db652f608582348187809e4adfeb4a24d5abd7aa26da0791be3b799c1f3c83586ece4d63a83149309296f959fba783ac627cc009ad18f050c88a30be908eac13d503fbed0f6d981f16de606ff194cd97ee6c43407c894bb5c2ba7ff3102533fe649df333b924c2d5d28bc0149723578881229239fd802a8fdf6a56f1bb53c09a91a913c499649601248acda2845d015c14af42f0c9971346139de39630c20c5a60f651f02f6926b29632b641790516582f9f0a3e42e626228feb4ed6abcdad4a614e3cd57da7fddea64ce6303e4e2e6f26bbe629549943f6d6081ddd6997b9008f0dd4e78b761249f84f5632e4800c4fb05921191a6ca173fd1f17c4af8f2171c00509a48c4f8d960906cb86e3aab7b0e79fef798ef5938c8d822dfec981f2dd3bc31375fdd17635204acfac307a3487d0451171c12ee1a209f103cb2b7c45fe2d358e9c5d2b2dd9d300271d3735bb2514ad3adf67699c2b3ef3f802d0d1a08aebd49361c0f9c5b134d1f214768bc6e1cc588fb0fa3aec1221bbc1bb3757378d82807861fef9c634f13d9e9b9c3891039de80cfae1fb7c30d78212860da6046beb7317bc7e760c7777eaaa046cc4f02ba68314657309c19a50367919a9f3e8ff1b54898cbec5cee96c4aea4aa90266193660efbae272cac2e61f18c5c3202f108aba1620d8c2f020182801ec0f2100a6d1b363a90ae07c5bead6fe25784bcefacfb1bd97b4b29654a32a5145804040483043fc46088142a539e9052c462600b059835f57a71bd18d1a8dee2eba3d6d0f057b6592fdfb24adb238558bf2d8ff555b64232f7d547ad61f92adbac6c7b96678568f9172c991048ac96ab86c52ae960cdd130002a180e31f0dfe20d228a6079ec8b68f9173a2d4ac074b27ccbf7fbf2a88d96502c58e6434bf622d34637e410d866399b65fd0a673839d69095ddc8b1feff2dab94838363e87a1c1c39164ece6549232d9fe2d135faedfa95ccc655ba715d2ba3d168a5aa5aaa2a23addc609a6be5b7ea476f55a51b3682aaef01c7cacacaca252556246f304d2565766394d9b8c134bb7384a3aaaa1cd670dc108bc5fa4d8b2c8bf4625996949794524a79c9eaa5bca4252d69b9110dabaaa48d1ba7972775295ed8cfc52e33d8a5c205529230062e84fc4197f7eeb7efef377f98b53710deedededf0fd2d58227537d6edb0fb750bf166e2ab4143448c023aa19c1f73a113899452d3fea6d550a0c92367f93479ec98fc6e79dcf26c0f931d22c49789155a86425608733770ce71b303c2fda658cb9895df65cbec76cae9fb47d59178c75c6cab8028cbbddb238898a6a94a7a86e43712c800349a517ed49ad1cb6ce3afdee22ac6cc850443c68f5a0343662fe58bc7fe07cbb73c101931dbb8c25eb0582b2596910e2396571942acbcb3947e20a9c0e8c6e829a03256328de44cbfa24026eb480e816de68335ecdaf877db87d90e1cd678d7adfb2ded60cdc9dfc132a2b1d241964fb125df6df22f292fab64bd351ad1b0ac2b46964cd9147fd3118db8325ebfc5b7de8aa5527c7959d688c675c9aa5ac96854d9c51ae3304dccb6fd6aab321c9d384cb32f7fa755551fe3ea5428d4cd0cf86e9b19925e2084f0dab0c931cad2cf4a961a4208e146194ab028f22aa6e10c3de841163d903b4d3451d44415192eb420d4d811420861b8ac86f35ad6b2afe557cb57825143fe84ee1d3e7efb30779e915873dfcc211cbdaedc5d811e1743767e6420a4040c51e2c3f70f5e315648603fa900c217865c1a7b982afe00bc80d980ff227c09af8fa51e3ee1e8537c1919653060f6acbdf752098c0a28ba98efdd0fb01212cc7df5a92bdb80cceb65bc7e4735e05bee610f9faefaf70c2be8a073f3491852c6a7c409cf7c34ccf782fc5021ca3f29ea7192c4542ee693113e148a69f4c97c0f3303017dd2139db0d4d2eb033be3abacebbdeff8de7b3038ab015fbe7ced17b9c03097caba2eab8a80cef71edfc1a8117fbed23eaed8e224307f17d2c77e349c0e3a6174432402b044f38a111d57b3190c2430fc7d78cf26b0ffde87150249673ee8c31762ffbd10485ce603ccd409f1b21b9480adab29a989df14f9f92f2ad8f614d08782a53fb820899423528278ca114454a4f474a11342f8e0eb52ab6624c69d2396c2583e225c1fbb034405888a8bf921fe7a1b403afc36808080522faa1c71717952a031dc3b1f2b78e74365dc0b1f6c53206c8642d127eae40a05fa1915d113344b539518d1641a819f627fcf7746c7369719e1791930eb5ea559c2850648b8ac80861c4c71f1a1a10916eff251a2e779bc873023b06d77b41d229e69b4c741c13a6a138f1c6942e707a55924148a1645222c45c0b6f5f16142476de24f1678f972478029400108a00cb880c6708f4365acc0b41808d335f879453a3a7a042d113bb6e7b5f7105589cd8c065093d4d882699d796665db02d17126f73196743e46f72265541bee1f6804531b87a94d4735997879f898bc58131857035b31777ffee4cb2934c4e0d758c75cd7b80373bd45357e8d8198ebef618ac5ecbbe7c5fa1151889aba00379ddd9d1dc2989b1467bd8d85f7ebf7e083dd3b58b18bb0eeeef6e8839dbf113c9722780dbf9d045a8c7d02191a3b48c2ee4b62b06661caebf7fc79630f0667c5139a05afdff3e73d22c21a28ee04a902617b15c8e0a4bdfbf573944382f9bbfb7bbe648808ca69c60b9b9e417b601a7ffd1b3ee0e4d880d6a80c853c652663587337acdd50044c8b790ce1ab1e84aff2e7f253ec19ac640c6b314eb6d40a42c055c95635974073c7f7b2205e670fbe4cb3de7d8cd556104d8bc1c931a34c4e4142cffb47cac50a6cd3283b7ac44822318dba3410ac7de6c90aa32fafc4101114b70c3720bcea7ae5724f340b53dc498c6bf1c518939c463b38275d181244db97f272a2a3b3fbf3c7cccccc9c392f6d3de0e4871042f8aff478b004999cc1e601c6ccccddbd8ebbd7b97b9db9db77e3c608a360294c3fd74419afdf830f767cbe6193f302d6dd1a83bf479cac32fb1abf63d5de0826bfebeece56659663ec67448c4d42475595a5094c1fbedd1ed696486683b525d2dddd4b647b4a3b647bf4692cec59225de5e4d8d7d26e8411427f858e3fc5d2aa5e96aa4a36aaea5996f5a41cc9fe37aae17fbd0d55eb2d592ac9973f83b5eef15e4544395e5766433f6e31b391a584307d9dfafd02a834aaaa663e58eb1dac45e9d922f05b1deccc331d2d59609b0add70291bdabdddfcbd0203e75cf78e94eeee0f2ecb88c693b0d71f6cd75d42b573ce41e8205ce71e3a084fb09d73fd9e7b19cafd7b7ed1f1ce38e33d84ce41f7dcbdbb25bd406cbe2ef55477f7dff6e7de95600183b96285a5ab990ac1367ac04197c33937849c910ec59a0ed3c4f8d6c1cf6199a3c3555b7e4508e1cbec848270468c314a1d7e8d68b8846e79749f21a584988eb6fab78ebe33f60c2847343cba8dd6d1513df7482fefbd6ee8ba1b967e3ad70dddebe79c73eea4c8589285c9b41ad8defd1cce98d5d10e1c64deb0c97d8a515530ff4d0b01434063b46bf7209c4b30d9f735d7fe5e5dd3b217d6629c2efd8aec4c54877aef596f35c26a4403be3895b9e4dceebe760f3b704d20fad7c8f5298e9594efd9893594945e954634aaca23af389837aad16f392a7ef5562c954e712687bb4bc8f022f5a8068a6922cc50a7c928ae20c3767087df33379bdcbf0c87693ce30c4786d305ecc47f61158f9460694c40ddb8682edac60005d85545bf7e0f3ed81da7a0a0dd06bafbf337c39d80e955554546de17b5e13190610996830b62da0a4642069827fd6bdae904a439130e4370bd03054cf5af61ac397f2a92cc28606e27059c4ea7d3e974caa1323910718299519bbe809a0a1db41196bc7ecf9f7764d40d4980de7bfe9ca4fa5aa3833953ec42843a383848a4229c85333f6e899d9d203b5660c1ee6ed3d06c9a768108c484859a8914371c61db62f8b6f4a66abd9560baccb25bf55e611a46627cf32746830ee183cfc130835876e102b6ad0fff4cf113a39d4e33393a6e8e080d3d39d24414a7d3e9743a9d7252806dec73f26102867f9e3869a593ca9c26f39ed486e7d545c0bc446facbdfbf5731ef1c032e1c764b28ac482e8803b53d5f8da69820f112245889060dbf69c7a86c0a498462f1f2208050535219a1b6a6e1a4ad3b4d7b409401930c371261b54806d0c04c4049e5c0d94d12ce4031b4a2d2d9d6117c768afdff32122283c7e18e9e1f9fa3d7fdefe8a520522fd5cc96cb896ab840035f15725abf480d6d4c4eeb4794096d80373bd6532c63dd107c5348e089122fde4891327575cd1a40993b9699aa6695a0e36b9cfc1010b28a6713e37f8c1ebf79c6558ae3bc4aef184212228afda2f16403cf5b5289e7714495acadc5c5cf80626d8b63f3fcc404344507c5a7b01c9a016211b50bbeb2bcbf16a27227cae3b7b374cf32858ea520936cb5db0b43909a67ff3e0e6e387250622e59584c89c7a55e2ccca162e3b65f08135a1671a850de123826f3be57818ae19bd62a3920e9d3662a808c771fc3d0e0ee78793e1e028451cac392f6055c90de1c877590faca11c6ac6cce30724574604d30a2d85bff7f62d33ef430821fcb74bc4ddfbb9f773efb7ebfebabd776494214bb1845dae08330df7ab7dc7be355a262f06afee41f74f4387f00d114159c93670c7e8842a314b3848151ee22176e278dc107ea2b1141e622a6ec70999ee9f778cc638d818c9a4e289a955d66750356d8c646e3ce42cf0214e986e7bd1b8a8b95002dbbc4811a6790232136ac33e38e889d188f0a1280a17334cd35595654130c5a9d48c869026bed48c979af152d97b103e1acf66a466c05497092ea6d1d601cbdc1f690c6c87e395b9414c8e23d5f6d703936377cb6bf481c9afdad563f2bbaede7d4c75314d1341f99906a51a8cd6905093c78fb9af3a73df470560a647f6c8ea3e715a263f1bb442eba465587b9d2c1729988cf5611f159c6620e9b5d3ebeab6ac6c3b36838197ebe2466dfcc6010e40296a4335401900821e2a93c3110c6fd4c67b0a3e2722448a14e190cc806ddbd333e4f4989999b50984da04055058292ee22286c251e8e400036c632226e22729286e681540b8430522e810bea1202127344e34cd3d01db5648a8899290a48bbb7b555553f66b20e9d40080ad061b38e0520310bbab23c6a7043a84ef46131a7aa2314dccced41dfc41414242397c3a9d4e454551a250494d1e3973377b895d0574081f7c7e5d2e2e2f2f303537dc198a351bae88d1b58fcfe3e0189203a55272a8ecce0a41ddbc6abc82890bd3fd86000dc374bf2da0a43516180d816d27d4699f3323703398579baaa66da3a0408a2387ce0e95713cd4e478a2b041a6734d9ac09c50674e601bbb2017238675cf885b8e20c2c47d69bd04846387b183bebf0efaee3e04feaea43073615363320bd853e1832bccedc47cc4d21f9410d242480b283f509cdbe0101fbb0b86e3cb8be68af5dc624fe7fe456fe791ff65f3b930f02b1f5896aab2bad730ccddbcce9fdd7719863575cead7bfbbc9d332bb373ccbcda0e33332bb36bc15a67d700d8c63666deb25619552765dde3f3d8afbb338deedac58e0d9f7784cfabe817d67bafa5e9570783edebcd5a6520f4ded8cfb5eb6fa7704af9fb98cbac9bb461af99661f3412218470480adae7d160058e073310f99122b60842c2c303173f4158010f5b502942944491dcc1922e9a54d1c5130c3006aca74ed10591c9075d3cd186740125092225446a300322b438a245920dc8dce732401865f7b231b8549300d574360603d4c4598c262f99cca2433cd28177e7f2af940256aa02ebe74b4d2f231c56a24861e90f48489912450b213f54621005c67b0d3a8194a94127e862eacb6a6c357ddae8500485a5038c11f74cddfe0fcb296408172ec32a4c09218410c22020844f4addc27237fb9dafbbba2eb391f79c70c2df69d7775f73777f1d3358da74bc533f3fa90cbfe7ce637c8d84b5c8708d85e53a87cab876ce0531e9288e080b735e776b672eb2d6ceb96f6e02e327aa6ae0fbdf85fff103e27929e60a6cfbf8de1be2ac192f01ebdfddcdfcbb5b477392d7ef515fa639b42efc8659103d3d6329b8a7d52c805c6825444586869c14a181326099b0c412459ae05004b6ad90d0154588526af386869ca4b630379da2331dd217b06d8584ae48a9cd9b4452284263ad3252c2779aec1f137a0b163688bbc2ce921aba055878a719f705f7f71c421dc23f7952852a40010a4c60021290d080b9690dd0344dd3340568cd05d4a4b50d3b9a8f108478788444c1644811137b647f36898e0ecd622a16a652406b58244ba653844da11bd514217c9d18e1f2c408e52e0f0fcff22c4f559d2047336c11e5d3622c2dc6d262acaad262dc568eab7e107655599f62ab6a29a5bcaeb6b690a51baf2606520ca5d186aaaad0ec1b737fc75f2e8b18049ea1a5a8c1080a8542a152286772989017f02e84259e6ced0e0d64fab315a55ef2922069a84b357eec355447d5b875db31f78a7db590114c6af3def3e7fd1bea0028140a8532c0d2686035f7a80ca8cc4a91496db897f939e66d752b9b951cd0c95b6b8f394d406de09394308592b4484280d668cee4da5d8c1849d329801c61f2db080a0a7a95d912065e7a6ce0c6f0a236ec2f4a524db57d8ff1e5a53be3f1638bb83406662f31103f5615b73f8f511b864668fbb2022f6ada1ca4c9e3c7644c4dcbeb8ef0d81d82110f0d63bf0dc86c87fb63eef36a0c765e6776eb56de4329af070584fe5a922c55cdfa8dd1923700f568c4157b0a96b8e0744ab91a949af8af203e3ee48e838367480e873dc43804db3468eaa032ff026cd321140e1dd446a5081eeae66d2378dda81bd40deac6279883a8b48d9b0731ab118004100053160000180c060483e180703c502451f914800f638a3e745234140844418e03298ae228c618420030041963084146a1a14107e32dc3d1252b309dd7fa2bc0260079069572cfdd25f0912778bff438acfd9fc4976d00892659e065144eb16a5cc5703f884abc36ec39236ac9c68d22f6e8dfa9c3a2e0689183a8b9b2e418026f1413a23a74a79d2f4fc72542a96905712f3deb2e4531df19e7c0e10e287f6d526f88756e52b727ec51c9fede104e2c3f650d0d090a14c493773849f2a8d8e49a7722596df977ff473ddd6bf929325352d82d02c772d2f10a1ba7c21bc6f6669c807882078fcaa7754a09af00aca0de2b5c291d08c12261090a0f716772ee185e9a3f5fd7820abd449c3d71a03acfbabca4a9aa8ead156be86d6c98de91fbfda227167382dec6ba64cac7a1273009f2c48c16793ba387921bf88293cc582e19e2e058fbd918441e4d030758968975a6bc2a24ca886e9161b84f4f619fe4cba73e775f2985728c8d7cb80f84baef87cc221d1fdffdc0e8b36261c39ef596fb889522843127c9f02c5bf723e22d9c77103d53a8b3b467cbcd70d441bd9043c416a5b9894e80a28c580a7aad37a643bc85460e67af37ace7611ce4517666e2d1737b287fefbf53b37ddab7920e087a6ada1b2dc7a9222179c8fb465cd2c27124757e6476fb7317b7b070896f2b2a5725683da7ebd52634c1d0d732d25de98b488423cb0a07be5e440b8c3f7173c48ab255a8d4f08920620a0b0262ae3858d66f92bfc215a462f4ab9ec066a74c32c4411c77cbf2f2af5bd97ca2a0b8a4f34ace709d8862ad4f5c15a5f074acb7bc720397ea0e50f9b45f845b7fca5925fe750af5e3c4e039c9be96becf7affca4afd7315cb64d276a998bf5f7aefb6d64f70c4e0497604780aefba6bb9580ed4470635a02ce349bf79a2c87fbf87fb17def7793ec92667a06c322b5a46ccf986e89e52c221293b95c33d2dcf5cc8f9fea810e38a4c257d615f359989c57ed79212a9e20f04fd1e045dcb9619677c2353b809c48787f9c1f4c119a01b0aad3a8006a23623b173de268a308dd1f286643599a366c277fafcc6b304e80fd667c9474b61fd6c82a1d998cf082bb8c6dcf29da4957f3286bcb93f6e6d497669a4a4084e54222410d0d1c543030fd0e326c4c6903bbe3d7731db407a623a41a3d7f5b6943a2bb6fe63d2621e6eafc9cbd408e214fada0c4f7c6b7003b9e22c9ac29d851bda1be502af396da6567eef582742cbf80bdc1756e1974d332d2a90507934be2ddb9916c97c88fae15befd0b1acaa06cf0ae97b65ec5104b13e2b729a5e9c117c6503c78c55cdf8e2fb3bc1ae62becfed8a0aa76989c8e6e84e9bd4a66d308826a604d0a170d1dc5d37e88f9a1937630bed3ffd4ca3899990f005511c1d3999bf833e5ad7136548c3977e96c1f9fa6a22fb7f72e12fbbf9534f8bf7960de7e0b86081a2dc883200ef0046000d7b5424c4bdc443b374118dda2a3008007b99b625e52eda4b3d2180355a9591ca6dae395db38bd5e66ee8ed179ce12c97a46bbb4aff9a361f481487b12943c8e7bb34c577a180145288b34353dc659e999fe6142f6002ea0437a472dcc4d053ade531d56f2303a59c22a6ac0f213e5cc2bdfe40bda80bb0f41c4d73525fb8451d0c2506db1cc6a1c0948b986c243423d52ad65169166cafb029e770de7a25c6100aa75cbd122a5a29d5d2b23f92104ab1bc0d8569282a7dcbffeb66251c490c005c6f1c40ffcf6771f2230b80abf8468a92bcd8da023a0fc26269c3d574e66c9943d7c28614119e4e0a7b71b54a4a2b486bbfa9b8e6c996fb8cec6248db170f020a560765ae9df90214c5b1623cb33588f87c01effd151e26bf3b45650d0e2b18b0b1cd23b04842df3e687fa28de7aaca8436702c7a6e166569adf90da6e9aa1c0fd0c65f0b64812a2d1dc6a63924f3d3e2d13d648d97380ea56509c53526b002511e933543b46cb09463ad4bc5c3e4b121bf622f015742bf76835417224e5ebfc63bceb42200f80579eec1fe471a30905bdcad33ec7e36a33b3436a2617246af64f9543a422f53369a5a6e75eacf6b57307d8410b81e239cb9c130d45d143500ece27e0253baaf3f1095eb97acda7891c5755725c9452e5eaaec3e5fc740e707854b62185f74d26b756a599c8d82bb8126b28f0f4943d59a1c5d415d5597bc2cc0f643055500b84a455a768da16ee34a57696cfed198c89b6c509e0e2635c585229404e76f7147c5b6cc73d03a083a78e2c7ee96fff9aba14e925e458618540e171d3c929a094f3c396483ff9632e1c5ad8bfbb611097a85ea23fdfc13214ef0ee3dd06b67d805306a588029dd59873a9a84882ad100b799bbff483b44a54cf4af18961fe9422b20d9a2bfb20955115995842279365c97d77b145dc2d39b4431b753ab06bd573bad8a76b55d65a779238ae2c748150473a9bf3ce516a3283b03092d42f17d0af4109d54cbb324fe109a8998e40bc4341bb7fb2724ed875bfc04aa78cdf12fad6e0d73bb8370f3cd5dea055906a1472cf2df66e9f801ca39d174db1de9da875f416b7b1951d55a7d0a4681efbd7843f769ca8a55a7535f6dcb1df52df8ad8e82773acf407d7228d28b92851c60e2df939a7e5143b1a3eb0e8048ae7cc8a415bc276c6996d73f790f9f08ff254c4b7403481d0902e40acb349ec5f48407b05d5768d9fbfa0e5cbb88be68dbdc59740bf05ceb10d6785981635a8a871956b99dd85d25bbd2e8f5e5b3c44a83e1edc5b9efbfcb9cf902dac53f413e8a43b0942f521e2d6ab2ba237705f6bbd5d8d797e3f788b6d03d87360c5abaa967e74ab22d93fd226f5ca5b9a19875511abc0d883fdb7f060e520ede22391ba4e86ee181bcc5a1347a63ba29968ffcd16cd8c972382dcec61cc72083962a8ce45efad0611d940195a9b97e0b8a162d6e601507107a40efd12b09f455ff7953a34a57137a9cb7e7d95b8dac2068197c3786d8a8a11ba05213fe306bc99c070670be8a0926ef2a4dcebe3d09ac76ac206317c7c4acf6547dd51313e7f979092027c7951daaf7b24298a116736cfc1a2bcf90a91682571c325595548ae8fda47a62d8eea2670e7dd1a5d91c7abb4b299436ef807641cf19572ecc6fa4662979e1bd145efec0c26e39678c4d6984f45e143130c0071ae448a36758acbcf521560913c2600012135c879bb8dfd75afe6f568c571899c22744fdff38d2fcf8d72b90a691be4dcd0dc71d32953a37d0ae133ec1607304c42cc103e2092858d5301cdc7bdcc2c3d4090109071fa09e20142cd062af13d09f1da11e77a0711b97d0df886c21df7d4f0f23f5faf5021eeaf942bbb853d487048766646b481242ebc5e74d5ed648985208dc9c6a61943e7962a625c2d705fabbf840893302d6346f729c087c7f9d34ff97cb9ae3db3b49a215be9951a728558fcc2564dba3cece45a34799c181b5e2c6f7b03f98984262853fcc2269c93857a68ddd8a01c8a065737585202aa172cbaedd78228cd8f54d2fbcd24774d906e64fd8530170dbff84127f5ffec779c7e2a6a57dac009c357b00c9d63a8cc0c30ea20b6dd2b8916eae0f07db64199a8eecf0cdaa0113a8412866cb1336877f0c3d8a8800487888e03e0b0190ab936dae58cbbf610e0ab9e835963fc570b902d867781327c76d5d876268ff6642042c59415be738c04c48f1d46aeafcb171b9a898ea95bb7d2a142e4816ecfb96e5e67eae12f7965312111560fecd486b761bcb42054664986ac5a6781ce7f85070a611eea49fcda12684238fc73f90dc2d9c46ab11fddd48fd7e86d3d8f119ac2936bf1ceeb6b1424bbba8f0ebb8d1dcd02442fb92aaafd9eb9c028526a29d5fd1d34b5ed9c150cf915b81de6034343ff5e5ec45f1cb8cb82e2a0085772a728112c88b88f98caca38228e0479d8d2c4431a44dd7a4b43f513b037ed8a1167124230bb62434bf47786cad4b5ca2809ffd7af6a6c700b042d3c1d74e0590193f4197f65fb37885d86ff8a710cbc451f3cefb04bb7e13a538db8cf9cea37423d8ac44e016d8cf84c44b9f63f01b170b0a8abd869d824a399c46240043293b24c567b36b5a1df40a3119d4d17b5b2f21c70fbae7aa256159a113e1813f0cd272c8fa1edc833da0ab3910523c2577b1dfaff6cc93c5ac8f5ac33ef680c14786c6fd600f2dc180b9e96f63c896e35e43e13a60b05224f8cf11fed8d85b859e75d0341235f73e60b8d385efe0db1ca614babf3fdd073b7ac3eb03e5e8cf34db305188031b1899e9f30148f444074691256d7fdfcde7e7251e08697175d25b1a2cfc6a69e63eaa08a3bd3e925513b10c766c502eb616e5e2a34545682e7f4b12caf7d6a41c81197553d3185bf0fdaf187ca54fe7cc1af5c6463a41ca786c4d60e76a2cf71294fb0ecdb9da3e2765f679e3157bc48c7b684116d5e76a13a0474db2bae834e93e1408944a3377a0ee3e48e066e1a4b6d164a1f0dad493a8bcda73c8832d256375f7c8bbc3beecc3523ab3ff53c4b59bd5d7aa76a2d617df64d87743d5601cf306f1edcf67a742b2462e652fdecd1fadd5ff5a188847c090cedbe91b07f11be7a0c5ead55724fde695667b2748da1dd4b4ccc16f3ac9d1d12e238451e02a6e568299bbc482a695d7907131f1ff76e69876570e9fbc590547d0a578af61018046788c64f6a36c1c683f55c665e5c597f3f3c04149894547d691cd93a6fd3ead19c602d385a55ae653847f812c071ca623b58cb4aeef98861a58432e499e991c33d61004b102443283018bb8ff7420a1e0a0bfd955e79fdaf57c438b6687a7c382a93a16f78147fb2087a20066d0cc3645e4a6c0a7b6fc3428af50826441e7bccbb84c92567935caca940479dbd78b569d9dae0d693d0c83ae597223bfe23024292c36d1e3bd9d755fb4d7d59db78e75d9ab5c06af0de5582791b223606790bbaa6169cf38fa2caa82a1e46d549938987a9ec3de7df22b693c620f3b885c37d2a604e78dc26796016ef5b81a3274cddff09cdf898d84f9e1e990026ffce732c37cb35fcbf8f8272c3613b9a2b7312caa9b7556e16bd7e8ca811c85d8d59d3e358c1b6f44fa9449687a01a8da8878ccd2b73697f225667ecbbfcd166958d2185041ce9b03b0908070f44e9827440c7aa87653f354fbdd4589ff4ddcb27cffe4a0755aa4b1fd51602dfb3c842dc1c0ab78b748c7afd08d1d5418d874921bd0da8b447839aee22371fa4c2ca4a182ad249704d86db449ed91422fda4311018c7da08645f46d7ed8eb207349a1cedf2e4bed7449c5cf5615c16b887f186aa44e69e0b52cace5e8948b190b19ddab7a7fa22a7778589d3e7e1964ad2fbc142063e0adde6750c238a40d39bd93e3c8087822c869c4cd8e12c6d075ed958fc7e06a0172d99bc4328fcc952c60069e14c70427a91c43d8bd5c650cc874836224f6157563aa8af9ab1e1536d1e6967a2d8409959ff1e03b6ec44f46ae70b6d48d1ce93c1b3d38e1466300a3ec8980e9551ecfc90299124b957dd5062cdf648786c70d8c6596e8b57d287eaa87aaa1116e406df1708a2a03396be27ef79338e1498a8e82c747b099b2a791827cf97bb4b70ee24d612db8f6862a8bdc9553d831114be17ba9a1223d6f919e952174802a755753337a7a7ad63e7a2d9e3a5d723a2d530bb703838b20567f34101fd1c40ba07e60ed212f1bb6b0c89f963bb1133d59dfac88f2eab5c7fc146f51d321f830de09760ed9d8afa6c4e50bc02bc560f6d2f026065cc14107471d3b40b4ca80b76a9697266e6a58b239236ee06ecc25cb4bc5e353bee892da74527310810cc6eb7ac3f1f40c3ed7cba8ddf8e270ed0c53966b98797d06c6e75d7b55014c315617ae4560af12e639aebf60232b7f852ec9d479010b3f328affea318075f5731d1f68f4066060977428b1cc935a8a9681b4b144c394f8719e80db9d01f23a18952387d513181ad082e87264ed2f6ed18a4dc1533d0a35997712718bedb972b873eee40a2a2df8de6739aa9e322bdbc4fb71601e03a5a5c05da08f179dd5c2a1eb3076383d052ba2790b19d992f1001eaa70f61f17adf456b6bccdf0aae90dabb80cb0bf303e5fc1ee1fd129f74198490dd877e7eebf9a2df0e6032432bd0699f0229803debc0da4e58ada9f4979551a51f5a9cac181db016d4dcf17090910000abfb5d7c89f35f803f2af27259ea6f8c3e8c2849e2332697a89d5020f46ef07cd770bf1540fea48ab0fd336905e9c347d3379fe41f248a388bc004a050d072f381513f64f7843802f865ff3fd9805a10e93685d59403122f68d2bd8871d5157867e9b7635842690f6532d70b4cbb22b930548640b9cdccbd0ff1e6928f70d2305472d6a13cd4513f6b46745ff86f54318eef2eb8a023ae06211e0f6923df53f7c36574664c1987ed1df5c5adc085e3e110ce91cb791d1fcfd9903b38d3d6615d3e90e75ef81b27de481e9b454dbc9b4994da8b4ce704616499645e019c21d90d0a3daa60760832019816978fda118ba67f67ea0f930fda00e275eef1c1b17d7973726cecd9b75308f9631508f0ec34f606cb92a5d1ec3dc747e44b22fa88827059ae67f32d7beb422ed85948e4bcfa0b9ae1b7ed355a3792101e67619c9f2cba9c440d7f1c51adf1c21acbd7c46982c09467c4463fad0175f16ad4b1130b9ab20671d412274338313d0cba4caf1202674d2144a9501991d4585c8ac48d00cbc0828bfb15149c7311e1210949f74beb65e81a49403401d6a669621b52ce362a8911f04a60ec415980b78fcfebff641e093465091403aa4206bdfc7767b97cc0c8e6da82e97226ffd6fd40644aefec499a666268996d2ee7f27f50c066d97d2118d118b5f754c83632a791765e500002121ba23234c49e17310ad04be4592f31221ea5faf4ccb80af8c14befea77e4d3e1f1e056111a19ba29bbf688736fcfd4a1f04cecbb170dcf51c542ff6a3df4610e173c2d79d0708f976a75f7e1e6d8476294a62af717c92afe490d6221f5d1cdff520681516f47161480fe89c043257c60ac8635a3c57db15fba1d3cd8d71b5af4b121fa3c30e27fd2562d953e22343c816317e90fcb18784bebf259a8a3705fb249c4a74f0e178190bbe24cd352775911b53f09467fbdb3de80c9424a1f73b15e82895886920ee8147b3d1498a4a123d3191e680cc0aaf7a3295bbc64eda3f32fc3aa08764c2492ec5d59ebf4a002b2f2f1d42a6308a00fda7fef23b5c892317b24b00abd135a58ce5701c5d868642238cf32969f264e28803ac71f5483212c94b66bb0b58a719a181025d17f1cc381b9cd7c3c877d236ab68be2e5ca94cb8a3847b7da3a55af18b0dd460742730c790bc19130f33f6ea11019865b1ec1937ebb98378a26a4085c04b00b21d1a62b6b8e116242c5c303bd0e5971eece4e54230ba9ca99cee962b2e89319da81566eaf50dde148327073f93e9a275a665e938342bfa3843675048d0ef8183ab4bc015775e11722490a02c399a29ba427133a25b618f164b496807784f5d8a5f6ac49349d467320e740c9eb6ea08604874f07b906293e8a71a855298917b7060a845e40401ab40ed74082a31a97080605ae7626e256d4e8ea3bf68987c2f0a7b5c653e6431261a0b3266307673cc330c8fc84441abca6d1a95707d621a2aaaee762b0e01cfc89bbad795fc71b5bfef138ed619cffef71b9d05e2c8d394954c5c21a07be021318035bd3f2dd890eedcdff3d024740c9441a4c228651ee48144a1610886d52f07d52f80046620d17dbf58830fa30d1e086cbdde0e8798379629693a9b107643fd51430f5c20e85f5f16084b0a5f5971dcdabe0696c0fe41e1de64eab245d2a2ccb4811b2b2a5de54b57357a9a80a6bca9a79e1193dd1a9a6098ecf8162ea7b2c26a21484f08d32daa41b3b1a1d9826e575bafb7eb5d74b28dd46ab98f57d2d3712fb55a5bd55b955b518b0fc68ac7b9878e61a3c69c629916a6e6aa084e4fc17d47f48e3f843c7e8010eb6c8a88d184400f1bc4198322c1aaf55863fddb0021d3b3c0cf4fee7b2c685617f49e64884b3014e836c0e02a6c59516affa8446bb119c87ffff6b2aaa2fe3cde9a54204eacc1e38d49f3dfd656dda91b5cafe586f5655de7e4658625b600c1dac54176c7b10e81a2dd75f2a25c9743ec394c8347e10a225c2e0e5c0c26a6015e6aef8bb078fa888329d40b450766d639c041d3fddc1048f99e847b0031f09ff9acd3d97455760c55cee5709ad1a40339f879af2509cd68290c6809d3d0182c4cbbd8f611a40eb62ec1c72557edefdcecdda00e3134b5b8e41fec0a003dd2c3506b4bb2dbcf588398ee49040bab7460f1bf2af4cd0d061dc7126e54988505e64f596be25fc828a0c9f1b62cb4f1c5973f0cf9ed439a4dbe68414db052caeefe64008b2ea722b080ff3d0fad241241999f9468ac3bacc28c2776644250268b7c3a4b3906b4b3350a8d141023797459f6c02ede1b74b98db06bf44e30822aca84daebd80d82c32291cce91ac304a80b4869dfebb7151493169da0c419e2d0edab5813aa2febddd1dcdde643e97ebc2ae881074762fceb1dd4ccf9a57a03ac831397509fb913f02663d6ed03c2e03a41aa387fbbc62ef16d214744bf9d96df55f78392a4d25ccc34c0aaa7ca7c4cf3dd47f508391df3e839db57f7b93838c3dddeb73e8e75cdb8898178c868e325028a86eeb271394e8ca2e9b12fb2f172405d6322ab678a404b091aa52cd18978cd32230fa2e4e6f04ebc34faf5bab8f35e4d11d6ebb2640094da629fae19de32868d75ed825f8864108873194c39ebdf25417c4020d0f721ecc4ef525f3d36e4dd63d62a0b878a91a1662315b440f3b1c0d04140012744991c2f3585dd68bf73bfdc804e394ce17c9801074e8b270c616d0160dfb2e0e6435d3020f45de64f1fd55e1b93dd17ba2e6e4a2592351b44598d732114a764c45c895546af728cc919bc4013ec5dc48094aebc1b477d396c4356aa4cd233dc3dcd322e69295e6e246af8bdbf50b410d86136e0e38c5d2ca1b887f07a99d610e5f6b8c4ce27793c1358ab34ddcf63f2b64b65c6ed06207810576fb591543600107e80e49ca619c14936c4c029fa50a4cc2034bc398da83ab6968452f451c833f45b3558207004dc060e77e437e06a17f2c93d4405316ec26c3066b6c828abeeb80ca85c52c88b8ceeb1012513dd2e298c5625a4edabbd1e8b2b495337085df65eb7f05d12e1d03e1234721541f85546141d233326dafdb85a93ab615864e96c46042f700e6b4e43bc65d454b7798df39359e2681b8b6fcdb223d2723449626f106fb9187e043e9aaea82be91247708ef1111f31a46db2788234ffe29800553e2d3a7e73ec9fdf41e6e95ed35c97a4e28513baadb0ddd952cddfa0b91002c46a7a82d802dc73f66522ad3639f886d7aa52f32bac2f1564b819049fd87d7960cba44dcbaa5453c9144df9d684a708056a596309922cae60b8c123b88fd951b0859816791cc1f7232d01ae4fd5d3ab3bc7e15dab9224dc799c934e89e06d231f5bc475a685d4053a9f3991434416fc83b103490cf4e1d882e64fccb6d4aa3d4d19cee4f91a11002ef25288c2c0085cf053b4e7a81fbd1df14ae177f66382c271f78c0f605635d6b53569ffe09b339fa90e3ba63c08a25eafa97f5c554f65373ad254c359e79ae98404b5570793d20f9a92a323aed1aab908769de5c0dfeb13a808b4a58b832f4c6e2e9d0c890e8aad48e063c1b82e04c83e67e34f67bbe963404148bdd8cf6cc0797ae998c84068f3bb3dfbc592af6268bb39cda9b9bc55513933ef62ea18d2b69b4bb5f9ae515f718a77fed7363393fa1097a126847bbc556df26932a3797968dfd63bc928f7c73575c72fcfcf38e70a93e80f9823096bf7b920c2e2cf69be33486e7c90f78c01a75a0770ff0ea613452340755cd0c83696f19beefa7988f5a0772eca56b3db6b450d829854867462465d88a5b278bdc5b75a2dab07cf2a47b4bfff10bce98576ba64bc72cdc12fbd7cf0e675ff41d002582fdcc5ff80d4bc476130ac8fab9729e746a2c9a23782bb57ee515d01f4036042df31593935ede2305d54eb2bfa4cb64aa752a952f60aca5fc8d4fac98175cd697206c7e0961674fcb1031c28b2c9ae9b2ba8a8e861a009cac598eca6a72c26541f219f2a074cc6b88040008d5234f704bb44fa1a28f8b1a4e0aba961da23eb93ec452e8b1ce6d450c01de456eacbedf8845c0ef1ec507aa684d572a6c6d510fa3569066482f40cbccb6d0046227e45264a74139d3a31106d13764b6562f82fe0673351facb4ffffbdfcfc85a30b99b3f2ceb38a6858a7d5006283ce8d2f9526f7eb942a8c886736df899e7c8c6388c0600b4479ff9751013380d0c640ef9445c2e8a9c8d86e75f4ab0a045b12cdaf1fd0d81171dc4396f2b88149f1d6a714e58857f04b954be2d48cd198cf244150c8431abfa02a551d16db246199ac36327142dfb42e57dd990aead33bf41764b8161274801148d3fd506baed5aa18a3c39ca078d08f8b1823c1e0ebf83af0c5a5697407857d580e32573f717dc8a0325c46a8782e7f10af319c230c422a5c6d4f23bcc18e016b09fe90e1a0ee05072fa07e799abc40b7ee9ebc2ce6d5d835b05c7e17db5c1c813d05ef2d3b9aca1d3388293edc512f0624b95a8ca8a15012abf54b67348462b4efa5ddfb56491804236ead4d9d2ec0c1dfbe85ba3a47ed59b12c675e19308b515132b2601bd62b2a10fdba37550f9451e2bf5c01bcf6e45fb66439bd2239f2773c38739eb5c119d26c9a38d6c7475726dc8f6db7acb1bb48d5cd055a7bbf87b22d3bbaab0937d306a408b289155f748d63683d464341118270e86b631c2cc6448e34abf3904d6b27104f80e8762c398e39db084559ae88c3309a52a999bc40be1c09b8ed0c9bc027fd753935ed3cfe4875ec0a2354aee2fab065e1dc10b9f428621cec5c23e4b9058701c4efcb1d81fa68f5a6cf5b87415cd1fea63d5e8b74bcbbc94593373e9aa8552754554da69cce451d28770459d622b7eaa0e057f5a5b2174ce039c40fde8eedee81339981f5116e4b27de56d4d42d1235897cce28375aa95508b14cccaad574859e1084d0b9d581a637c14aa8b132a66c03d1eadac01a2fae834b083c7f606d911c5fd496059bcab9ff82c12da259eaa2d115f395c2a91f91ed154e22e4aa1a8e05e4527c4a19943cfd1c42accd41525586a01fff8e66fa06c876b2e80be2c22f1aa9c2a57860e18f78704999a83257e5686b197feb97f9ebe47494586c34ff91b587b68ce05700b9b72b94f28f1522f0f08cd2a1be22a83fc826daaa20552baf3c9a9abfa88d7313d5c99767482814d9146cc431edaf7849c12ac1a6f7828737acded992383d8622847980e30d116b00e70eec3043c839259601e7378fab8cd9ccda075ba245fb6a1f0b62cbfa3a69b8c1ae753c677f517c8e52aa5e8f861e54264a2c43e524a8e36d2fbbb8b949adcc2ced9dcfd09e3d82f3fd703acc4077cb8292f5ab61cd830c6d43be9855461ab7e2da48f233d53df31c37a058045c76ea4c7a2046f572f54873bcd2db17e78bd488e4af2002949bd5f3969f7de5f7053dc844a28702de30b8b104411c2f8d6bd527ab3321efe83947ef3318732bcfcc6a74e43800240fb0f028e53de04540b80b7ec1bf15605707140b0c57eafc8efaff8182bffefa5627551462394cd8ff91c3f3e1e87fcedd3cb504cc49a93cded15f9e2eb0f0c27732a2cf846c6cc77da5b3383d3c2a50d9d87b4ed94472ea7b55ba2f2ec0fac5b98fba936aab4bea63e791b40bb270775d8497c078d046d3069d28dbda02aa3e6ae0663e83bd102dceb4464e80d0b8d4bd19f33568075f3d508c1bf060ef9bb8a4caa0d15aa6ea98a120a1b6a6ebf599ab1b74d526edf7c9e00851465f26843bac5df8a0a3f604aa6cca8b088ff5859141a66dc6c506be24347086309fb9f161cc2b035475a3424c0ef3eb0f70bce43c280698aeb960f58fc608e662f4cc046af85219b88f460786c5b78f97aa0fc8f3e34537ebf86a2c0b4f5618d042b5c99c428068c3e79a9c7997d35fbd35c0f93439e48f022875a432755d5cec569a6904097a1a99fc1348413bba428fd672553a40c595da1e721838e0a547b6ab1a1f1a08e8d4265f2f9273258f60379ac15bd15077b8656239310758b422ac3667d1b14f4c91c88f6075e742a98b5ce08d1ce57e2643333ce97e42a76083f8f2cbdc484a152ddf43f9b26368cea0cc58987dbc690527a80ea8cce61cd8db7774a6563a3cc92983ad3564bb0e83be9b52bdbd32aef41193da50cf1e1a4d755997a5ede7288e4d48f43876080bedd047309cf83d2f651054a42719f599343978dd444bc6d0f3ec99f30ea4c0cd2de5e2f642784efe7aa4d595e3d6e3c528d2c3cb1f184132500605e51c9e2642a5db48e116f650905e49ed81373d1e2ed86726604938149e5f33a7293a013e9bd0d43d29f594ca0536166e33987dc61db4e0917329cc93c2833391f398e6a4c0383fe4a1fa1f709d9176bdb92536ab646d751e59dd02db2d7a9e0108409e7170277d5f858dac385bd40bce6e0886a7b4b33bf4924bc1f2423e3cc5698ef272edaa02bca8cb9000e4120fe35b43e7e9c801c0cc5da4a93013dcfb665d05f5a954266e20a9705b8799582f8f8cbf0291e681740caadb6cbcdae5eddd814e49c080bce664cab1d8ba040713f0d3849e898d7e36ac411919fb56018b80d403dfe62d603ad6745769b60d0b6046e9d1b4a9c896ece6ce644c2f6b922273abf60e825b639a8034238450f93f822f4d96630af25b68db1b8be58a8316f2313c5f09c6ac8ececbfff4081a9a938da4e1efe57ab3cfe69115911afa49897f0eff48f450c962168644b1ce7796058c260570ba229b8df9f7e85286cf1d38d5ce78b4a9f82d4b448a0124b1dec0f47e0473a5d8d07d9c13f42734ffcd29c450538c8f6c640d56919fc7f230fdbc387130c9fc8eaf02ed16cb74cf7a4eb3816371ab3192f6d82ce8472e42653d22a5fae0ab296d935e647c5a1c5d9909ec6528cf83cf7a4d00b0f74b0e4355900ed127e64baf6836cb8c18985a0bb48b28e2cddd0c83c05ecf088f45fdc814fd915e9207075dd8303281660411f70733f50a661d57d2dfd5a2a296313ba43c7571ee5c46e464826ccbe1a6d8726b67d0a4e56fc2ee76bf0b85c36dff559c1ecf33d4a32b97f81ac0fb3a1ef5fc8db98723f4cc5d2aff315ce2e0a4010112fb97e9b527f41bd67b2a66d43dd3586207f1d654948151cd44ca321e756c5cba9d44b9e8f724344c4e94814d82314394c28733b4877013d1e6cc237e4d74101c84f49ba9a7d00dcdb668b3c3b507e900887d9115aa991fc797609217b6a3de8f7bcb078659a668ec21166c538fe894eb143b3df42cafec0b8a583182ba2c8ae314afcdff3b215ccc60a21dc18b730d59d0d47ec0245c60a65e5440acc76c0dafc93ae373f8b6f56274e079d995ea79ab561fb4c15a5c307b19a617510b3c20c215e3da04ae61b9036685c3b105026d02ab1278da66627f11990970f347d22e47170894f02c2769f5aa0d5f6db46cf3ee996c68ede1dea816bf49158eb9a3a68ea6f613d4c4edd46a9b295637a4c6b2c8c3fe690d61090ad3aa6ea10321e6b559d6a453f716bf19860ca76fe01e8d8e794643beaf0bf322a0771494a2645a95016531daaf5c7a21b4db01c558749e3e159d952b02652d5f81a59fccaf8ae3f280e727c965e1d3fe67ee427e1fe64f1aab09032d7a09f5aaee06c2d4a34a897c1134d9a220e8e0c9b7fb935ca4401aeb6942732c99b8401ff2aca2398249c590f0cfa0d5639e22ecf1aacee33491a5a4e2d78882659dc06d3f0186b09ff8f4bbec1b6e161d164c9a8e3cafa1ed2278a2c8b283a85988884acd6484b618395181c9ba38abe4f7c57aef001fff311fb36504a8b11e9ae46272f810111dcbb5a62478691b84eb9ecdbb514a69646b4ed801f3e8b78e913b89de9cd902244d9cddf12244db728227795977a1814d065e8752d885edf1b4d65d427ac0550bba5417281eb56c05753720dc997c56a08c44a1332f4b46569e13c0568187d8cd7ac97a158c5720167da760119e015bdf1db7d893e0e4a5e40bf0254db2ec91155413d9cfd912a0242e6d27bb974a01444d99436296944c4dee492cbc1a086f250db52ccd47cf478b501332138f086fd367ba991573affd6bc0382975ff0bd10eeba529c6d9dfc12d987ebc49b7954053fecd8d1725f77d75704fcb258c836faf56b7d1a04702903b5a0a7c4c217feaa9b2dcdda821d7eada0da9766cb4996ebeff3fcb283897ae568417b4bda3e3c4dc729a39ec3e4274184316e842adf06e6a528231d82d3c2ddbf9fefbd06f749602089161601e4093f67c4cfd1e8aca4167104346836752cce382ba0868226f0d9428dfce013ed1da11469c158678cd9c8fbed77cde8287702e8bcb3a5f0b12caeb141701747caf5a6d9e694dbb6c24295241a1aed81a7d7e49e22fc0e4a22756e4548e1a222f3da9d318a387db1ab0d8e54a62bc9d2d48a50e98d7db21c18322e3032dfa2d5df302015bcb687910ac3a23708fb32b3128b03f653dae55d9b7370de05305bf6d5e54ee9488f2e426ce857a5fa9b33c3863b5810a1dd8664aac0b703439ca0d038c18b2c63fa24ce3575b5c4f42430088b140e7fbbf1420712f8bf92043c01bc522774d50a33da91a2f22e476bc05dc5d8a308a9829357e1b15477e171c960dec328cd521dcc3c8330ccdf52910b11223069e672ef024fe761a618befd11814951b2015da51ed54193c22d1d6836a6d706f4a42af4ab275ddb9f6ded2339722c9f3fd029b5304c30b07b0fbc0dd092a386b502f802aafd202f554242caa1059f85c7e41fc1d1b866b4349facaba4255b0f849b225b10a513d496c6ce70ba6cbdf166992fb329cf1f5ceed998770d56174de6f724881cdac946eb34d1c506f8ba86cc69b97d90c8b2a0e4400b54debe16e69c6654d2b9f1d36b34f80272169cbcfeed61c5a22f146da42a9b3b9411cd790b2a8b569c7554557e55e555d10b0d0474d75ce281cd59fa3701545170407dc76070bc03e81f6367175d54da3f9dc3d10534fd8869fea87afb0c7f4c527066fb025c2ebed3f5d8bbae1082a2cb5acb1b7bc659ec2ce0f95ff10d8ba2cb1a3ffe56fcd2e4eac18b5216cad870c9b81f4c29acdf211990dff82d7ad422d52e7f8d8b097358a596dd3f19dad9315fb5c51f1f9ed9a0095a396d7ee36c07d1320aaaa3df05d120362dd8490ce469a55b89b0b1bed5fe317edfa1c0e19106046396ab0934af72be7be909835f651a153c2bf208269ee4270067ee0c6dc0e66a52c7340c6d86da2e5dda9dd30d8f7a34e931b7625a30efb196ea2b8b1a1fac2d3250791c79f3ac92a92684107218119ef83d5374e4d78538b497f7ccc8425ea5cd1b7512f0ee1ba2bd1c5981b9f7741164743808341b91cafa456038417a15c049f2169a44e87cc50fb549a62cdaf0cbe85a4bba4af70adb5d479dca07fc654ad1105120b877c4d66f979e6149a7ceb655888f4b51d4d4f5e6b3a82e00bd423b23d334856ed1997562cca85921dfcc512cd01bffa23b5b88053c7d279720057a0933029fdbc049f8ceef897411a919a624198bea23c89b3211c6c84fd77712c9de5b38cf2cd73423ed15de063ead87a817cb85bbbd8e58ac46a202ce112babb60f648eab22a3e9a71e702d8a5e01192136bc1ef4a619d4d0310ac9c14e8d0e80ff9dcaf887dce2795a48416f4e64ae0188a67cb9d1abc3f1e85157bf3aeb6dbd3f6ae40257f95ec3005d530def59031e6360f6468c30a4cd34514240e08a8cc08961bdbaab75590a5692043c17b8cc3d6780d955c4f1792b781a93f45612b932e221210f17429b00b4b35008145482f218abab9870c30e191d0871727ae944c6ed1571978477328fd986509f1276fa6adad31fc978fc5e96db5b7404db0e7086d5b373be097a1390c8a92e617be7848e0856f5a5e53d4376f12c3967fddfe04ff868cd428131ef2444b573d0e7e7899b2a5a869a2ed19da1b2b70453b883e74394feb55f53ccb510e5e0965e2828492022a8a7f88c6f31c5bc6d72f41dca8744a54c562b22401a5dc368d5ae15980f0da0554a0ec133d8bcc023abb40646344e6d17f486dde77ebb0fe3e49ce0acb6afb6e11cf70d874de6c7c2b484e57efde757ac154c4d53ac149bc8b77f414808b2a8b71a3091a81c3710e2a9d36de5747066a9b54cca337b10ea947944079de1791066e959b3192d15ea7959bc1bb39663dd9cef5dbd1f36f20d6fe7b0949c37b7ae135ef57e70de21042b54266ebe7a743089a783920aa537d88f09208d8b392a4fe77d24793fd3484bf6de780b65b2b3fcdcecaffc48000d252076a2d44fde6c283ac595e0f99f8f389780271ec6852f3f63608b47183942aa757f07421b718522199772b50f07580b7e4e98b848e6d5f6d1d784e7b8b8994d0f2b66bd1ea465724f6ab50fc4157215ed830be3e792b0c908a61ae01875698cef994dcf63b8447532893ef274cc25b7a8230840d1d442bc30f3eccce9485472ab1b3ae7a20a7d40089e7d7287048de485c6ade633549feb12cfec21f13e9b93877f32302dc77ebf0bd6f20325c6590236c9c0eaf156777b282724361d3099eb116948601a6f41f5abf30834ed32338f169e4002b3d026231a48a70bef6d6b72cf3df05dfba0b03e4a357ca678b885f3f542c8fc195b52b4ae1e34df456fc42d28021c8ee01c3cc9ec2b6612d17044c5ad99431c9dd7c65155c85b8e0e8760b300f60e4e41e141743820c73b011acbfeb514662c53121e0e8d5e752eb1e8c639dae88638e4ef0334e50374d508e8b31a74acd35801724e8be6f70cd5f0b3c67854283912626aa3d8f2d861a2813752bb55059354e09025da3184235f983808bc11dd8371668c39d49360a4e81ce740d03cf7a4926726b908468029eb280c08f6ab1f32fde9b9beab2ad2fd2217c8c0946e928e49d8e6f901478ab904994568bd4bcca65a92ea0ac847efaf359dcce8517cca9e42c62506ccd0e8732256e1b0250603d41223da7346b75962b6129b46665962307d9563b9813049b0c4d099ee2b31e19544cd33a34392f9a1db0ccd83c7c7ed9598f484247a2d591f0851bd728d13607891ad099345082e1592e7de18e938315063c05cb5cfda080aa52b2ef8e12134c8015dd4287f456aec4b3a842b858e4e7fe6ac348cb0c17360cc32910022ce688921394672bfa1017c757c0cbb871bd85206f64d3c4530c1c6eee1435f61b0356813951920a5626a4a1ba512eb232940c80ba154db09a4782075e87c5cd399bdf80f8a09358b22c0dc5a65f3af50db313913b5bfc6b70d93104f4ea4707ec5c6413e96a849bb9ce589060b725689d5b4ebba4b5306c0d795eb505495ff90675b2dbbf023381e3271c5631ce9be67ef2d838684d3178625ed2d5be716e72fc965c690602f36295f64baaa5507984d45e3250971d9c0f97211b6e67e44a431bf3e7c09566de3f59fd7289ca0dbe7dcaccc49b6e68d8ea5fe81d2b9f793848c2b35196ee684d15d026e802013ca54a2798ada5b29e4d5fba9398ebcd5ab3db9baad6ac6b702375e8d3dffbf6ed5ec7794ec8b28b88b5a5edf578858ec65deb1b6c4c2ee4bde05c8325f9d3ff17a72db12083265590da06ab3f875b0735971ebe7a0048d4767e330647796454572423045047011cfc903a82dde9bbd4338319785fd38c34e19014a2bd6a920f6480c39aa17de8790334c562cfb61494a90dc14a50a95f01a2a82185e63d9190f0caa75a3daeb79f8025848e472da38c13036ce84eb48a79d0b0d1267a7c5ad0733d0bb050603142edeb4a06044d17427b781b27039abe21b17024b3f273903c4a7401957c90709df19a4451e404221ab48f05ac0914e0556086e15447f7e90309edd83a96a7f9ec3bdd37c87e8970f10043441363f3f769bdc48e299662b229b781c53f7d00a7d39bd8eb771662932fddc90d40a2becfec6c0206fff2a6a8ba62f933111591fefb4367cbab6bcc840d8bdd7e052925a878bc1d1369f88629f75c4729e2c7036f652dea484396fd48124f4187d144429aa57e9106d214e7dc212db4365ac8f4e0b3d24134113d1250b3afd606ad1bf7a141affefcd99530f6640e9dde85108b04474ebd1ab8bd673b51ce328dd4e3cdb314f0c8f962e3106b68589df552e11e7903f87527778c3fef9f4fcef42f7822c7ba86ec3e69ab27316a8f96ee6b9ccca6ac3ab346815144563d07b9be14af51fc21cce51dca2aec0910a5d29a8703331df63198139f9004459a0d90638ef7534c9028ed620e2faca92fcecd3aa57cbaadecbc7a0a9159fefd2ee89f793007820d3418991d907e61baa1fc668ee4f8ddff5c77cf488059b9a9b0f5a277b28842588e75270c1e77fe0d9263e940809a7bf95224078a6804448f2c4ea20521cc7b2171de2603585228bbc91a398df4dd927f690972a8ad928167649d34b116bdb020574c04370ed6da5e7f928bfdf263cf1591a4fb63ec147d880d0555e40c022092959e0e7e9f86014334f7afc874940e1f9d15d61db7c8d45ad7698db4b495d238494df84f7074352761afc8f4dc48da95174cd84f9fc02c0f3da4d53e685159ebab0c2a6e2a7af29fb8cd5b555f091e9d20ac014d8e7a32e34b3a030ff6937ca54596ffa865199e49c4e5257b6a5a843ae49bb2c8a58789c4639fd6e769b086f174d0b1f96a187a41b0f5656b8b80237aa8066169d12eb690944835ef2f9160212ec62378a09d43153c9ef9fef570953dabec02a51439575079e680d23b5a8bfe370020ff32a5909aa828f3b5bdb6fcc8af6d75b9f8ae112eefe561f51602a97225b3580264d105ec6fba8dc052d6f9e2000be6ee3c077f6020aee3d52ab39069c4015d8d9355386e7566dbf237d00b0f9326de98cae0fe1c44c4bcf047ccde6a4baf9a7f8b8d533bab5f2dcbea68193da8289a5d068c162002e93047de2d35e4f07cc52b6a65cbf66f8b536b1aba664e41fa7f67e7823b70894dd5937649a7c0a199526f9928bdf722e8aa444c01c8e31e8882a80d5d7ecedffde4637795161d06d9be4960d40ee103a3ca9aba9ae5e4d9beaa8c18b415d7e790c966213f8dd29d4b54ee55cd580d06b99427ead75fed4d1b1ce42a4ba29a9f41039f542d130eb2a331c41ca853941393e9bd1f7596d963e66506c4e06371334b65b79cfb677d61b6bfce635894c06f9ae1d6b5e9144d0c10622a57a934cbca5f6df39240ddba8073ce6fdc8454b74e43fb229e9f7b5450aa98ba4b85c0b14415cdd4433a0ac0ce309c52d7ad733d845952cc7246bdb406ac822d95096e90d814baa71821cb4e39ec432163f6f305c4fb76c6561cb2923dde27c17e5339145b47fb9bfe450c052d893b99e4f7302364433932e4a5e26b0184387bdb403c6e26e646dcf3ea58269f083ee1911b759e9af8d141c0a6cd24a1473194c228bf562426ae37667ea055cd4fd8b7f6c91b77040e239189a79607acbfa7f1038e3187ac3f9f4eec9bf1ce4f04b67a4aeafff2ed629e6ee031d7679774afa270bbb784175cb0a7644fffacbf40438177bb03ee10ee53346403c1a284430bfdd0de66af05430fe8413a26d6bd791c7bf4ebd1acf637098711efa7eae8a6e73aff547a421587cbf508c9b5271e8a5057d136dff739a1fc05f45ef44d553e2b95ff1eccce39b7c5a8447ae1b78a4a6bdb4a09505a7d8f25280ce2a7cde2f6268062a07c8c3985a0136001bbdb602d5713543e8389debcac84df0f156f61dcf2c3e3e462e49b39d3f16c009f8cecfeab0ee65b2a5ef168a9f21ae25e4061a1c2df63a409ab85a4506a2eee736139612ff79044f62bab0a4750e56dde97248eaf9d745346f5a4d6173849ccc8d5f02c2bd55886162c8a795332c2673c7c857f5d689fd37cba093c0ee588926a1f7f5a071ea3452340d41597682663f39de020bcc3b60b0f2c2a3890a4c893d41f26c7097e679a1bc798c3d4bae849079048866861b2305b8f7e9a4a111607c9ba33fa594b7922cbb620de516aa172e6b0d41c033f890110644963578e61e6eaca32cd2e57caf1825fb93bc6f09705f06dbb88160168ac5fa76296042e17b5cccd9c3720032882ff476ff7000ed4dad02d3186dc25bf0b41b02fc53f5a292fe25a4fac97afe7965a377a9fd09779d3e5eed756bc10b6b61bcdd2d354b43ae5cbdc75a2bdeb3a18b6736ffb671d5d1ba2fd8ed4430fca2edd450aad22c253f88805e2bfb44dc8d4a617f37f49bfeadc8b8a97b69f40ed43003ca5166785b30ce424a07026d0563d3624b9e585045b675d6f88af2f2650b0a863ac375b916ef185a765479f0392e82b1fc2c59c513b167383d6704629dbda1392dda331023d02c5379d0573c7b3edcb730b3f3cff125217befbde59652ca246584074d07580746320713d115f6efcb79c4d8398c0a696811fb8f161b4ac638dc9672bb94c7de85d409ebb17320a5443207fb66451e29d163b74618e76c8d1efb8ff65890635e038243192e13aa3d09276358e289e88a8b89ea10c456bfd76b074de4f9b5d62ded9980c3d13770f4e72e98bdf41c30fbbef454a8e1a763b7d66f10cb8473497966a6492e0b6369e462ff0eea3dadad991765cfdf2457f233f7d6124e32dbe06da5c2c9189688a5114ac3d4c5ac13cf3a33c59dd1692fbdd3da7c9a8a9b96b3fd1de89cc9134f3ba7a5d576fe80ca2b355666f369aa8ca394a336b7a320bdd5e6ebc9c9e916576e9bb3d3c0dc3d719cbc76823033a5a1c8aa524ad90e020eed6e5b314867edeead0739daa7e368bd4a5a6d7733ac6d574a99d17a86a33da59436b576d4a7b596a3b46a1d05b1ecada3603d2339ed4caeb5d64a67d6d7959d2caed5522f5c4a29a574aa524a596badb5562ff294b56a980911194ac9504a260d194ac9504aa64c1a329492212aa30d7112c76a3808f57e64d8478fa60c671680016b43397f3e85644efb0b9e0b2db6c7b4cdb6e0b3b47155051438ceebd19cf34e68c9c26915f734d055630d6431a95c6834b4032171b43f7d0b21731ac914125a80c7a389ebaac469d13d9f4353680aa986c8546b8d6a427c35e31acf4f8b9e4bc920e9124fcf52fca4ac1c4824f1a33c82f59c488c9933e69c530236c4d0303ddd508689d6a444492899dfc4738e538ad1112c871fe7d2e4b2f9386363a6ecf264c9044a82708202d49a94499432b36676b3835c2e170fd31b520288899a49b09fde4b930b2945e68cd9657ece897482a04963ce3969d83c0d973f3c0da741430a134fc367783d34da490d92124dbec6b3d75393b3a7bc9e9c8a42c6a71ce5f5a45028f7bc1e54f6a204f15ee79cd7d3d56c79ce2513cff9e6f5709b4fc9b3d56836afa95ef3aa6945535e4b0204afcde035c75e8ff601d2d0e31b24b47cf655e1ba520550bebcc5625c3aa0043de5e95cf434262a86a1dc7b7dd8c205cd099cd0e204d6bad488bf4d90f99bfa7befbd4fb0ecf82d96780bf298fd090e7ffde27ba99080bbf7de7b594eae08c20cab595e13a323db164e5d132c48b426ae89971c300b6584b6262c38920135925ec91f71ee02043008426a1206d0cbc94f08582043e4c48720169a849c9400a8014c8c9c2001c4c216474e9e48c2020c8c6aceb09ab55a926d5e221991428430b088b20598dcbd443232832422224cf9090a80999c7a89f433f4c50f124ffc40e12c2a9c8ca1095119436528a1b12e7d0447d4aade9609218b3c4e8b2717dbc4e4d3bf2ef26c59b98133d6a2f530296b3e8ba40b94c773eb228bf254b756ecaaff2856cefa9defbdd8859e0be93e50d5a2f5e0c81224436d2d097eccf116477ceb2d5abff19166aef8c287232e578b3a8fc67d5db65b89a399ba09123298ca70e5600229685c689ac660da62362c571966dba25a9dcee974ceef7a67860d25b78f5c0d25198bdc9e5d467ad0d57458ab614f81984d6dbd6066a90e101062b94b5d91658b1d7e39fefbc27de1beb8900497f558fa419f66901018114c245dcff51ed02dcd39bb55c9e35ca22658bf49a1ca505b94d8dbfba99f30cef99b42736922993ba6d2c4d14adf48be5b1cee7a08390dd1272257d1cb08cc8fd4c83775b5d84a734787d163cc2a6aa3986c435a5a4899e8ca4b6e400daf81ef837c4f81c8344fe5ac431910276338c2c818466328b9b8202aa2a34498248941248653bf60cc9827a630929e8ee88f112e308ac0782ae3e933901f080864a466a5d3cf34c9a7e9edb125a963a3c2f17a86a8b5d62aa7d3d945e8104dd3d4dedf5376eaa686758cba6a0f5121fab2e16d1acda514a3445a795b25a34396070934d44343f410ee21cad44df488aeda312c2f1db986a8100da2afef1b1a1a72ad00c263a74cfd31313149a61a86b0d323fa7d97c7d3a1efa3b6bab223629eb9d6229e007044ca5b8b268f19a988230178eb9bd763712de18857099400838594297489985a6bbd7992479694357ec479eb7289b75e7126810adefab7e4ad7bef48a4129c1173344009a4a126ae2d4b6468112788626b0e2290c04494f1d5b3a326967873f563f7b58aaf4844dc60f4d56db64ddd0f752888afd8fab0be1889b1394ba7c77810be7eb92ba3648a122c3708c5944c716448496c042a00b1a4a40b20158496104999ae8c5e896d1a2a6fede4412a8900f6d6dbf5226491062644b691921d0905c5c893525a6b6d254a7d299ca199acd43b0d6b476186f9f5254df333f46b91390d93396d43961842501ef1df681c74fb0cfde3acf9d622736a97e9332db616c9a342af7a8c18305eacf826244c4628500dbb89f369516cba847de0640c4a9c80f102230999ba1565fbd2b7a2dc33295545b65e613926501f75524f1b4ed89c3c351967f2d48425f41c75b6a24c7f86da0364be01648e0d9fa3ab38796daba595565aab05a9137db24ecf3c51273379a430aa449774cc64eb29bf6ec1ceb90c95023d3f9239d4fdb90b6af2a88aa437a1fa388fde8b9f312fd87c7a2798900a1b70bdf819e382f1c7a3fa68a4443cf50e1ca90d638b9982872dc4f450bb9ae480c482a7def1a74e943a5198cca14e614e79a44ed4a99337757aea488410947b86dbde0d7db2734939e8ca488b64e486f5518bd4c71c96671ed1d65affb99ea7516647cd534debd6b954388b64ce9469349350c2f3ba0c0c4d7120ab03459c1651a107bfb528c5a8c529472ddaec5b53ae97bb20933cc11b2d5acf204eaf2c0b9438accf10640e94b7d979b48e0cb717e4c92473cd79e1640c565e62573aa350f2f376ceafe79cf34eebd65a2c28161c754ed6306b9a38a4903a66986d51253b76c5624bb228e72862619c33a543928aa2fbf7e55cf47d2e242a8cf260b7b021dade03ba859faed3d3cde92967b41919519eed888a1f679435c13823a7ad4ace9ae60eae0989630ba9837b42e650a7463267883c72464f7d33a23c0d83e5cf45f1c7cd6886238bba165444e22029112bc5eb2079b420ba72cd1d163671507769413e04c128b5520cd156bdce84a44a4891ba85cd1dd9d3c4419dba164479e813623cb530091be229bd22f494a67ecc96640ef5322dd2cc296b6a91d2316b921486f3e74d14e98970ab2bf2a6183386081afaaa62a4e90a11a22b36c03a06548a10ba62435191e6f055ede029f68b4e9eda524b8d24f258b52c5b142f8f88cb4a749f9883be0d67f3957105ebbf3aaf87528ea31dfe6810875112e9a56be0248f386585ebb42ee348c963ad49c04bf77e988180a41ab13c561a366080687fb8bbbf5fbd33b106ddc7820d3a8c73630fcce4117fddcbe604b9060f073b747043c2d286854a67c991f2583de8a42aab168ff0d966e70be797f5e086d27ce5072c9d7f0902019e6c96f2511e2b88036673251cc019d939620949494a4a5c66d69522a961ad1f783dd5034a716a98002080a7dc45041b2fdd032f64e3d54baf60371ba857edaa7cbb1a68b028c256de13364410800e00bc748e881bf39512411e7108f57573268fb547cf11bf9e50109b624c4fe711ab7d7a3ddc00c258c794478c03e7fc7deea2c89a2fdc83bff4400679c40408ea845445373627050802238fd58397ce8dd065d747c0f325c4f79da1fea42474b5836e5eeee0965db493457d960e0fba9a35a6aa481e717e52a73c6ea3ca54990dcdbd837fcb22675511271c47bbc3584addde7b25186bedbcf25e6b6dadaa5aa59492524a697777eb349d61ed30febeee9e362fa90447a9e5e5ea65d5ba9f86e30cdf614d15f93e8d410c2c92dfe418c460bc6169cf51fc35fb5a29a55d6fd42c7bea17dbea9cd7d3dd466bf52c08e69c93ca7b9db22e9d5d6bad1a94c9e0386fe0b0c2a6b47691a758bbc8bc5dd5a2740dcae4e9b8c5fc72529ab95a25adf5de7b6ba594d6ec95b9f6da5a6bd55cdab95d8b6515d265aaefd3b67a2de6bc32da56b39a6badf55acc755e190f4b9e35f33a6bd5b86dd3327bc95cbfb6d6cc6b9d30d8b56450489cc471ae69ad813797a20b558a9a735a87bb70a4e135d5f7691d963ac2b983a250e36bea8e976da28f4f18176a21e6b84d7bade332ceb3365ece53e2a01fe86eab92af732994ddb814d8e3b3010cb28794a7739b95a2a65d97a207e6a62d4d58a5ce759dd763dd9b3b54ee58e917d549a9a3276c09d6e459dab33a795af0c70a7a2ef584101c1753923c28805053acf84c8eb179db658c278ce0a919a454f514c7a9e32a9e7aa6fe35d1c55377249e22393186d1d3536f297982de6e915f473c15a47ed8a1b3e20955840f1c97208f092642b07561eab0e8a0f9a880897be12d4bdbd22babb8cb8bae2413311f7c52006302063f3419b97af0b1c2c8a8071e7cae10322a010f3e312123293bf87081195d09f2d9f2649368c0c5899c7a896483183a10c94174caf825121120a62042048e75f9d571c660a8a4d7e1f74d27b80a562f05196cd76e3f417e8741ec6fa0abcfcbf4585a2f05d902cf1b94cf49aeadd7c33927bf10c1010636e09f1d7e248fcf3df2f8fe2087782d244a86915d24151f19327f21b9a03c124c2ff50e75ecf61ed12e7287b467465be6b1cb1ef398cb637bc563dfbae49a9caab42937e7061b80372d7c426e1ed0adeb1cf806fe8542a60e2598e2490e5a1414b49e68524b94d74f4bc890ee86d613940fc95460a5f544f331075d217b89969c280f8bae700aa6d43b35eea36db2461f1ead11c67e9fc84c3d63dbaae48cebaa64ef477f295bd5ebf1b9a17e9d83b0c6e80a7b06de50df20ac4c4d7485bd82c92f649fe82d7a7c891edfa1c7d7f5f8be1edfa0c76e63168be4c9fce6b1db29bd73d43b358edd26491ded1f76492d938d8faa1fad91063e5a2a1ebb7d519e2cb446f87fb42fb03ac99c6a46aeb06357961ac6e3ba4457d89f62c9a37db5973cd6a5c7d8ab1592875597a48e49231c3f3259cc58bd7c3351235532b74774855d7a12b86e8de80a3b8fa64979a444449cd68d9eda22d3f0b19f28914d980ac74c54c3f1c35b953c5aa3948fd5db3b98e11b0df0468bd81bbc115bc42e9f01a9b0fac6254fecfd243eb5881bcd63dfb6e4544d78420eab13c83d57ad95c730db84d43c1af2680f92c85928e4490b995f0f85c8319e285af282c9d5ca42ecae9634616e3fbd94134d049916b5883d1552a21631f64b947d70f619e00cf37f481d9b63f7a13ccd4457d8714e61e750a0fc0d94df4cd825b6fdf4f98de6b9d449af3095d22bdc44e66007e50e7a84c48167cf6d4bb6b676addfdced582cd11a69e196ecdf1b68034d9489e9e808069bb1212712a71165fa8d62fc7dce8302a13fcee7b6701cc2f43ee61005b14f30887dab5129bd6ac72115cafe45dbd712722c91bb27f2f4ab9467488d90c917d47ec8d2c49205f038ba99d685deb1dffe02d0b7cf97440193058c7c177d3b0cbd93999424795b6951ef8cd40895d23bd4c9b77737bd14ea1b6997af42fef68e873c9d47d3a3ad67fcc0c918b87079bd5e499cecbd768bead5722057f0c909f2250cf2acc19bad1952f08547af107f5459a194822d4a90b6cc0029055470607008b01553fcde9b44149a8e3a73ac00fa5a3b89134014e32c35e1a46f7e578a7c2c588a319d398382f602054f38bc709072c3d367003f9014b5010542568ee0f2d2ca112fa0f19b19194315463f473724f919ea19d8ae497ec65b6fa30204215d41f2d665116f2d75ba02e5c72e7aebba42c58f3de5ad9d6d9bacf0f58ebdd6553b640fca4f90c67333e90529c8a0797b7611dedfc673d36341f3ee6aafbcdced5a0be19194a6e0f670d8e099107c830078db610f9b947d04e9951dc149cebc1b49061a7d967111d2a29d209216a36fb083f71d72032579eb1f2c6951e2c7b9f401985cbd4738416925e8a5b74292d46225cb4b6f85ab85b20048567cca793427bd9e9437889de835b0bb3661fc04717ce6e998c9de09f4a3b802835f7e823bcf496e6eb2ceaa859f731bd24f5083e7384e7a0270411e7da83e9b1883a3086b7ae9e998495af2d33914cc043f4104fcf474ccc078a8f1bd42167a6d3d2d4b969fa0cd679e0928ef10b7e63ce8cd747933e705718338bc3d7f6bae037163253f41033cc7693ec1d9d1e95d97cf35d6b4c7a1750c66ae81a38f769486a588c14f90009f8155e6d010843eaa03471f5fd8655d9662ca4fb0009f819dccf911a47e56c1f80902e039e9ad90b578a73441f319de4cef97f3a078f47143eb13ec6e28b5b002cac151a565cb4f90f519a881a30534d087eafb06eaa8052f85ccfd2f289194927c8633e585207e82e14b6f85cc259682c84ff07b892d853d75bfa0449a22f617df5c9748515cffe0550f84f31b02998f0a479b4779d722762e93b351362080f01287a30fd5639fe1383c77935ef0d24b013b0daf711e3dbb098af017f452b8a10ccea3db6380e633ac1ecab9eb61efc0f92660e7c0f91c9e586250b68892dfddb44ba4242b2f91b018f113bce990019acb1645781b3fbdae459473d43be1b62009a416bc94621291b493a246218f8465e92798e3bb934851bc7e821fbcf452981b9a414db24703a2b1209ffed2d7c4f7216617cc300df0a6ebba5c3b1478a36929f046736d626d0678c339e79b731c5803e2c61c28df03b78e4e303ec1b994042848f593531e9b60687cf1aa1db2e73304c14249e6f56991bc0d225a1332c7e1a87aec5cf7dc8979b4e4d1b863b11853ac29e6147bdad1f2e29238a80d1b8f026de85c81478c375c446346b1a3182cb684029d9317dc41e6e040cd97ab45eb63b55e8af0e2c48b142f53bc24e96879519239f2b9bbfae9ad1e05ae1ed7a6a6a332f248e56533fcd16203c55c73fe0cd1bc0ba2a7855c665353513657d03c3b613e9a3e4a41f389a4458d869f6e83fc741cf167467dd84c14d8403a5e720fd11f5d6c8ca39e29682ea4458dca167dd864e7d10d94f35cd609c9951cc244b9e833922bed0a2800f05acef3681498f318942a787a097b89d465cb5ff1fa0f5e227591f29cd669e06c0a9bba145104d1b4ce659d06cea7a32b623bb449d64dd488af8e99acb92c0a421fb502ca37405f86d94def84ce35ce51e31606a13f9d73ebf54c07e14a96e102d3fd65389d66538b5665258f3e6cc2116716678fa45fb12c5bd4326e03c7efb7e975285be6e8e94cf753bb94d0b491dea12678eb5369168591517e7d7a5a389f269a89e425c99c4eca8c9b4b8f85e94e7a678e38484b5497d50ba2c29135def0e969e1db67e6bd834275a89a02c709e4538836f1b4931b37a68b3635925e59b7363d28cff7a1c2cc7edf2f160a3d2a6c003fca2d131bb7804af5d35b6cd15a950d68a4c84cd37a32db87b72ee50863f3f0d6fb8824c13a0ec71f8fa7d69a57c3006a3a923a2e3d821e71c32ecd91d4a185fdd2e9925d0852c1ce688777ce39a7ca6f03e970c9b8f2a0181cc37b7de2b468e7b401279a5e596fcda7b7de320686d2f374cb7a27e91eb3e9ad6b7367be756d86b66d87dd3d7dc020dd42d5622e7030c08035d1c2d0a2b52fc0d0a2d5c01c39509895434bbece594785e3073e47f3cf557ee3c60be78652bede8937faa5853e3e8e428b3a54e47b820a3e2dda51e543fccce96d816a5909bda3eda0dcde70b4c05f9f5c4d2da0455b82064e47813e7eb468bd877c9518294f01f58e9d28b7fea3772447bd9e6906956ac187ea512ffc0cc7394e478152f3f1f3c17a1cba4079a63c62593bc1348f8d4fb7311bf035f6ab815ab41bfc04259252166f2d90ccb17e25bf28400515578a145d314274a548100541ca6597edb89c2ca1c6a57fde0898e1d2bb1a0464976e933397dfdc9185aaf98df91167ee58491c7646e8a1386d0d88e92a4bbab2b2f581972cadcfa2deb961c01c737c367f1cef4b691e969559e6371bb54ddbfcef78b9ab6930f40ee79b11a9a3bab5ae0f2ffcca34c47963b264075b953c30d0150c94a731ced7fbfbfa0556c03b08a88786fdea320fe319b628c5021c8a88690103072db67001c5481364c8c08829a264b1c1c9ac4c492c0bc5124058e9258ba6af8edd71b22c82b8588a135e9d91c5ebeb972f6fad95ec3858108de0d3996441c4054b5d50b9610cfa848932342c8ce044d1b26035ab0373c60757181c7c180a134410511813bcc244e941284cd21108588185b102041038620af365070854390ab3050f548ec28c910304a8307d354b61352bca8f1850641a2f917ed26071430f4d727d897403098c18d1e58627696e90f2c31d01ca4285e3acfc7047b63280b250e1b828595c2e233680968881c2868d0fc9880d20d7123150d8b00145cc754ea65259a5d539314c9a74680919c215910255a69794e9ced6789d3dafabf1e6161d949ca0256488a6248a2459e4eebcc65acf7377eebc1a8bf20e47cf6f4620ed27b40ca3e75c8327c8cf35ed1c38c2f039e49c07f57cc2c42c91f97bcdfcbdb79f832a79ec180338bf01e3faf5eb9cedbae92fd3df8ec9ce42f2d0eb5507c943c306d3abebb5c7f8f0372cf3d7c72ac46da1d650c8e4c23485a92564c846054a4b0317326446085ad587cc2f925a4f524ebd86d6061b803588aeae6b0056a126e13a05ebab48f63ce5637dd5787b69bf28cf34fcfb824638662eb0163f4ed88b0bc7d48c70acae26e1067dcd8bf2b417b9ba391c5bcc5fe75ef5f51de34290abd39ee1f811c9f56738ba9099b40488574bc8900d8a0fad27d387cc145cd17a9272ffa8e4b1632160c1b9b763134657d72f1ef353afc92e6174c6955e082687b94e5dbde3f9756aa4773abfde31a963fbeb0d46eab87fc3f1eb2ffee6e6e2efd85ab080f29b145232749e72947b9ed2209c4abdea6eead407369816af7b6037b578bd03fbcced588bb7c77419b09d5abcd733a516efd2bd1e02963aa4c2827389b1c1b9c46c6564e1b632b2282063b744327669e7c4d22b9808902035408310960e175abcb5928ab7d60f80c3ddd039af013318c4aa5a5ce13a0e715ab4dfa7815fd6b44e2a2053df52b9e67a0d28e50afc361c9c3983d8e7401c15c7398fc6a07dae3b2e03f33b9f5e0fc63d1e8a0b551d289f66dbd488641a9e401dd72fa13bb9a32345b9430089a50e96ccb1ae52b1b20b6bae098ec894295b1954a8e08cd475b5495bd21fab4d8b99579f4b9e04a84fd8e80e4a1ded46fc0cfd33f45b2d896cadcd42ae660244965afc2f43418380b250e1a86099b2f9b724872046bea68750648ae937edf480861afc1cbda0a14dca921d9c704438224b2ce162441811652b632b830922b62a990a9d9146738a06e40fa9c3c65b0f2275c8ef495e328738e21979b528640119968c028f164340ca4b4b34bcd1ac07c92fa985eb47ac85ebabedb14e85c821189169add685fe9ac81ab6529cf81a560a2583415ab4aedc2f5d79cb001089c37a0d7fc81ceb1c9c208ffe35ab2e843e43e41fd631ce4ed65a4b438bd67f380d3fc21fd4696302280b150e0cb7440c1436c2d8b8a19ebd06fbdc504f79f69b94d7f80cf73e2a2160e11a8950074dc941720688025d59af01adb5b65abfd5390faad6bab5d67a2a64512c99456dcedfe7f54bf6f10305f6a02b6b9d26e5ead662c0ba0fa9a3bf71fe7c9664787f41ef258ebd55b060fbe284c8921e63230c14ce35440c60c6bc9a7208ba612c81a523de709c4f6593f9d8f5681fcdc3d2116f388e6c1a6dbae9e9e441f274109d403c6da78e01c993e94aeae09eba0fa9a37ef3279341bd83b2b0837c64ce03e48a3a8bae288bf53d2da8d32b8aa3e4116f3fe69f8eb73272ff985fdab6211199f66811ca8f1e48799447d80c182eef4aac0d883c666fcf9e045269d2a449d3aa2d64978b490d6d5a3b22fbc8829247dcdd587032862f498a94c6a83f5e8620f535ef3a07727da401871ba8f116a494524a432099b7822c3d0c643e760e879e0aa31602b93ee2e93ed7418c7f8621902c6442bf7012a79b2a2ddc5c7aee398f6ea9bc0b8b741de6b6ceafd7aef39f1a5a5df893726f0b81648e352628cfc0142acca173da85120cdbb9e7cdeadd9c5e89d0ac67f7a4938657c88bb195d01b88b73072b0bc9e59adb5f3a66a5eb34c63d54bfdc3517553de58a73818cf6ffab8f1d3592de668517a074359ceb00653a6dead754dd7a0647052aee44803f5ea3fd64720c058c38fd22509928451c88fb2a6907f86e38f9febd47f6ca5610e5feb877aed1bfe50ff09c71f6f4306d4fc0ca524806cd51426fee9429e4caf610ea96d812438e2f086d9e29c7399b3b9901a5a5bbb0c41ea03c97cd47c03f4997460f3bb85456a70b58a081992a64a165d80310619695a9bffd4d0dac29fceb9300492b9740b32d9864cd7d150d0cb08111b5a45649ad6ab466b0b73d8fc670b2f38fe781b6e60e4f0b57e36e79c8639e0d60fa62be943648c888c1f2e63ba94f1a4f5f3a47d0ba9cb1b1287d21e8e887ecc4743eea4fd907d74169625260fec4157ed1d8893701044a6432d8a7d5d903af0b7bf30a50ecebb8af1dc4490a7cdb6853917b528851aa1c92315a2453b45429489cc69bf61ca54c85f680474deee0215ea9d9132f9f6a644dfde4e2813a9c33bf0463b42f57dde8114a8574d5f1f6c91c7a654322dea1d3a4a399dfab0348221db4969b13da66971a38ad3b7d6a12cd6b7731ca5aea72727a7a69cf4a8348d1656410b99220800000100c314000028100c08c5429148200fe464df0314000b85903a72549bcb836910a3200a32c0184208008000000800801087aaa60800c804c07847778a62af42045c3eefb1395633e29e034b5cdf2546325fbc9c514f576782761573019d19b546730653acc2cc978365d94273680b7ab0d6ba402a841a45626b2a7826cc0148433ad8c14b87bdb74671d85c2fc8e1cd5df4eff07fc2f3af20fe69ede617637829b6f1ac2acea81d7b731e23d615868225d8d30e4a2fd73a9ee00fdb3bb0379f2c623787d6ff0e60bde4e2429651c41054f1b8e5eb177d93487f1a85c27fffeb02c5ee47001de08d4d4e3da1a43b78d0cd36823dfcfd63ab1559e12971e51e16ad1d9aac40d1b8a1d5c36535fd152cdc1dd743a6d1e713dd022c657f81aa1b1ddbb6ffcae518ad2be036bceaf2921aa2705002f800c7774a43b0ce527d08b6496bed3e58b2761805597beb0c54416617d55f1b56af38d66223fbd0a748bfc1abad4d37eb1cb6c53d2572046012dbc8ad1bb71d254c3a3cb7c2e3dafb5f5a980f5c0bcf895fafada1b01c51473e5f878fa12557bf4d03819c452e6f12e2930139c8d49e428e443fa2428e924dc486bc24fa881878a5eb7e4cac4db4c33e138c559a01664d15c9de976c24c17b5706157cccb889fe8b74a68fdc3dc5d1e105186a265886585229a734336ddc1636a4af7ccaaca6ddc215b168432cb21f5cc0beff97ed10668377117741ea4e0f3bd6094655b7986652629084ecb289add5684280d798e06fe7c76f2dd87b0b80c6d881dede7223cf0df9aabd8beb883623568337f4eabd45978e1e8874268ec27d758f2532d17ba33b412f940e2c3aa0f4ddacc85d4d89299fdb772c99897e86f4d6737bd32d3df748eec647ee3edc273419315e634e5a6c633eb08a285b4d240cd05aab26cd03c1faa733e8e2ddf046250cc643a48e2f4b14500262a0095c55191d8b7e95851ca50daefed250b591c12e1f27cea70f3844a68eaa475d344ce97aef58357f5a422136c8d1fe0da71686bef4fecd995dd063a9faef2bda19214a5b53976c43765fec464fd51921955d1cb6be50b676617246b8478ca199345ed4fe1a90006ac71abdd7e67b3b1780ddfa7087d73d18fc125f72379742ea53aa3087501ed05505f77d03e141d17fdca58a194ae6d0c3040397839e50c2346a68db07d05c2dfa9d7f84c1cf3973f67a0f7a382238e01e438756988ad898382dec85da89415f9d93bad46179f09ec8ef6e037a8ac7145f753d6528ebfe58bb482feed01572e5f6413e4f7eb147d9e7570bc205523acbbe61a3cb0387a5b206768dc06055259084c297f7d579bb1dc111afb5defbbf1d15ee906a5e81033acca506cb4a6e9468ed0563273e2d88d221c33e56bdb1c7160e48a876ce33926373eaf2aa82588be50b23e6366c5704446ae843ca862cdfec095680aab539b47e3f445bdf47b92032a3487f5b76d1e2202c3282bc794d2f216d7b16bf4593d654868899018f277eae3f8faba52e7c31aeaae28fdd02f09d4032d3c3b7b30e9411460b3cab5d78d182d63484b6cb23b69315305a9d9a496b9899c4defdcca32ab49c06efa833c6e110e30e0e8acc1ba2ea4561595cef8a4bb8bc6a5520ef01a4ff440c6bfd79a6d175045316452ce210924922315e8d66b83a70966a8f2962788b7730a48439c0c57b50a3163d96adcaf00e8c58928bf5e783eee9f7b9533683224120d8c6fbce093b5cf4e17bd49cd8ec0dc92480420e5ea6fa84c455798b19cccaf848dce911cba782654134648acd0024ba76416ea60957934c9b2586926a42e08bcc6a3915093a968ed2eaf9561160d5b5a798c41377d03a62ebd9ea92c56e65eaa1e918bb67ad93c4d8c0b7e91209736a37da56e81c540a097037024cb036586efa74a4262ca2d657bba891bea062f9026b07ffcb72aaee241b57c1d563027cdfcda26064625d9adcb55825ca7cd8ab0de4595c62f72e236d5595f588094b439dd8b73177fafc8b8518787ffe52df8a56d030a1819e651fcdea99b504db92a225d1202c6c09f9a13c11231a443ff9b99efa069efd92979f4f702dc291b11f316fdc25965832d7605c31193a2947ff72dc8dd500b70802f66daea38bda1eb816f1d2eb0a28fc2411f77a7d5445cd181120e65663e9ec3d12b9b088f9a365f215c0ced12e609c478f0f8bd0fa58d2f5dfecff35d7e121921b90809b93198aa87ba951c80be1f90036629d5b15c67cebf3d496f120e9df73c60bb3b831906e7825194d91319d9ba2b65720eec3e7e3292168157621f2796903e8cb0cd5ad2b5f85f5a1fdcaa42772ee681b7c3a0209ed17218475fa3928b0401e2bad58841677bc944569126e3f1d148abec182d675042ec67a6f1be04dc4716bf184b398c903621717c280132166226e5f549901c16d963e80b4d295543bcb52fbd3e57f548ddfa59c460ddc5a1efce7908f3af2647087f57258507ea2661a66c937bb28509516795888da0695c34e116d433e305ec12cf9d6912e881c2f8a76b1e2e4ecf4742f3118656312552ce3da13a8f37b4e846503f51520ce9d76b10f2419ee8d88b5e4715bad2ddaf1ad1ae58436353cf49b1a719577186b4bd0c48403564039398b9bd6054b8bf13732349794951afcc2adc2265cc215a8e7c17a2516c9a325940469611fc5ea0aed01c03d8d035ddf7fde924dd6b32b8866256f35fca8886f5a4153c445366f61c0bb452a2ff470b0c4f1ed6dd9bd229998be2910743241261156fbb9feda06160c6201a02782ca826232e4f799922bec0f2794a16588fa930952dc4b3cde1ac6a174df06ff9eaf4e46c7caa56040a429ce1a79f15828970cf01a5c806fd636ead0e9df1953890bde4c8fa4639c05db3e0fc9bc0078c67e52683aba9d5528ec84864fbf4e42879d1ac22c28f0977d2c5cc565455db7c7a7fb5160ce50a67a58ad1e6ef4eae88de23df15a9043b9b5297179c238abd060b40798bca8731db0557b35b51c8304a9d7fb63017622a05e2b0ef967f29d1dda46d5b740d2b9f72b4b4f3ec7829eef860d9217b54045dce60b23d4bc8921e5f33caf44bbbe4c05b55fd24d3c00a7f2fff1bfc02e6c2ccd077a2cd8fffbdf1d54245da179e3a1269fee5b49c77123fea85bba483f55a507b8d50f512c4566c2787a59514b02af6c508fcb0082fed5dd2a26b356ba7ef692c9dd18b6e59500b7524c2cb0722b25d082593da662e301b0d6c39769bfaea7f3ab1244e95c9fce51f95c75b11e383cc210e55194832dc2c4b5bc9a977ff0e205beccea0a7bb4ebd82d1bc3a9fd819da5cd48f959ce2c7298ca44bfa02023e4de912c2ba61389de414c837c8ce7bd587017ae64da958b875084bb77c306b8a0c8520dfddce7e072cc0eed5e62364336c02a841efe26f0bfbc79ca321c8eb8ac8935678c599cc85c898673ca03fa93d052917a94364561bd32d3cefd43de8c5656b538d2e151e9bf018f170ebf7f2a8b5a31a79bf049a3fbc9b00e45dce6d81b8d9b4f45e4053b6c741a8a3e1bb364ec15b7718d488fc3c59449992aaf97a1e24d108166cc200a653607ba74325e1b8f0ec9cb25a87c15b4188aa8fd4673e0d68c56067ed2ea874065176af3ed165484b2fc8b0d2f78c80d7aba42ef9d66e3619d3da9d10bb6562604d5adc509b5dad6be2c0205cc915c8f099118c4d0b0bdec8dfda7d266da81ae89f8a6eb30c3e515b8bd846d985c463fad024185eef147a81aaa23b4b761955b027711d49655df8854ec2ccb62206a45f8763634ff0374b5e02737791f02bb47edfb037296d897de719e91d50b92b27ec7d44debbb2776cef3d762408c1466ccdd991d906cb0586f0947fab9987aba1ed5b7beb899f20401f53a79c084d4abeacd3777a60c137220248aeec16e8db96666627e3c4f1b8a5edaaac1535925a8b17ab9595336c800c368f078dd7c497526858b2f330c953a18d1f199550d5b13b709f66255c42c90748c4d884dd8934a82d7c832674990e2c530523a1d9b70f7dad0da3190be9e8486735baab7af0c6a39cbf1f98f67fecfd9fcd81dc4d9d396b7d5862fb0effae3052bb49f27c3c06e46b2bb92977d4be43a32d513c89ef0644447d1697a3ead73d57eaba307b29d884289744de4937cb65864650997a207f7ac5d11c3553e3ca3005e683cb74e79be7b787c31d6bc2bc6fe1025d57cb72623819ae8eca69232971d645c381841b72de279ee4e7bb7f543841c264b85501bc492424bf1ab2184b7d2f4fe1d48450bac1e91512e1e6e117069f66298d3debb42b8999a261bc135446fa74d27219a5fbe8f45696577c87f6f7096c059546712b1fe7156b03d4428cb242d2b56290caccd8927aa18ab3f7db81bd0a6efc765266b76b4090cb6420f68aab7c5db9a54d8c326602dc6a0c27aa7689e159a148f494829b82bd2ae450757536e95c89b6b4da68e897fa20d4e737aa559493b19ad5675a451d27c785f034d5f3151cba0305840c72ea9cf0c6c3369a9abc998f035696f40605549c942c8c26df2591d8eeaf429fffe619f4f9afb78a10e57cf8e12b4298b459565b6f2ab3f7d5c13cb07ca53ebecf431881471fd49b0a6e0371c7266f4bd1529b546855c5406713d2373ae3a294c82ec1034012c956c95889f36afe71b77ff65bb7c0b05071e5f398d55c13537015f0b2f2f4d3c72bdb337addd708a41eaed5b626d09b49ce479e5be7676bd72ecbaf154a11d448ce1571091657c91cf73dd66c58160b61510851c17d90a9ccd9ffc435888c1f364dba51b11f65404ec8bbcdc945cf7b3d3ca1e758a130681879002bd0332d1149fd31bc9d1f2611754cc59d788b2b4bb40215abd0ca033e579292ea907f6b0f30dcc8540fd0ac55475698fe9fc28286208ab0d0fa1bbf09493889ee0ab19d4f7902815334182359be35cce0ba28918a8dba75202a470349f3cf4a8d00b9b765ec78bbca9679179bc57008ad28b77119b8ae8f0889d582318fa6ac6df0924af10399ca8f6c8047a88b651e3892d22d76e0166603796fd69522a3cb88c59ce36312830d16a88e35a43aae866d58d4c78b4c6f116de4fde286a8d9f2d8eef7d70cf9b28633e3760f046d331d15cb4aaaa766d4ee566fd6e0b1c15b29a870f6c335b3de247e267cd5020fb3c365dab1309af01107d8d4d69ef6824ef12d4b3bc5aac83bef9c636a27113e5bd09531089f5e514c09cbe9d80f8544dcf7bb2fa0f4f65548c2a827812dd36c1f49f2a85ba0337b1e278c26358b274335a3f9e632a2f75accb2bff78f45d8f3c142d34746abde7fb592668e568db0224aa35522c4fe6291670f0e0c7ec1b4e829bda17edc6cca97df589ff45b0aea3e9a6f70bd088d6c0959741423f87f3ec210c7fb93f79e393337d8e59f68ac40b652ef684322aeaea11affcc733676310be2860d8aa0183562eb00b198ec628ecca18edc841e533fa1a31b7a98dae68d094a3396b319997e289ef389d09c251f23296332eb3750fbe3c0bc9ca3b30c10aac1af8a89140feb4560004e758844d71fc26693e5020a5d35da4648213283be0862223c3bacca945e27a0dbb90939ce7d91d5e2ce1e6d2097a28c9a3237d32580e3183187961661ff6771d74aecd538be3c0891d26f0bbd9693dabdb1ce18135f2d7db42833d7a390166c48968397abf9c058ce251b279ee9ffc4c7709ccb077c0d21d4c915ad87c9143300c3c36b277eac6c2929d903beb9d1ca6251aeee830fed76d63844d7b73805b8846ddc7be2d377dd8883064528f0d05219905a70b370113a01227cc8fd4463360be2c62f5093bd1f7881d51fa7b71c69fb1bb1f6ebf00504dd41594d168ee702d81b9c6441d31f97d587ed7abc3b70c382eda4e9d3f64e86010196b8613090d3ea64e7036fb9ab876d5390daada4c74c0dc4fe89e2ec0735d3cedc3754d7b922a6d40410e51a47022c6477f036564555ba430978216ea1681fcc3ea06efa7d32ce98ab73f4823771e2c9f022cc84143aa1bc3913f9b1867c4fd3a27c6eb1d9290cfd457907655d44addcd3ad2726dd351e98a0c2bc831745e3ddff79b9d228ad638ce92273290bdaf47eb54b2d457c6df2226981c5b41cddb291e14d48058de736720fa7151ea79a6bbdb90f3779159bbd577b011680c7a1fe3db4fb5bbcb2ca2b7008ea0d1c22970fd6bae591564070006488eb5cd0bdd12f3a477125293a1fc7515329b1c1be54f53999e874f522b4633c6276e5d27e02bcad0b75c2f4bca4529a44b8ad2231b608b280bb7d9d1771d1baffedc9dcd906edaa43dbf43bac2652cc1c78586e520da1574b3161e043e755680380a511748d8d6c7078d2cc0bb699d6b2ed7e2e4d6a2508536462894188f06f570c60f182a92882b643350c22b85eae96ec9cf1300cf7acb00e1aa2e29a3872975be0ad3f104a126e5d37e640e53bfe7aaae15e74393cbd9984c99b293e9ea4636423e811dcbc585e6a6be97db4f9e187887568ebe04ba8e47375044fe5fac39b7446dbb1b0b196530896fc00c0b012fdbe40d13e5f2172b2dc3af6cfbdd62acd0b13174e13cc126ca5b44c341cc43d9ffda0d0cafd4b8a54ca125724f653e4864d51a797b63037f48c261745311bf8d27c410b60228c13afb6c184965469f25ce033550bbb03ce70d5cf28a2f219f7a30ad2cd72ac3ee52dc9b56a0fb3e5d2bcd8120c1260649621968891b1f1de6a626e14d7bb2f8389faa8a9e97838cc2a67c23bdeb028f6862f1fde349915ac1e69d419e5740f5971f559a4be3a2da91338831fa8dda71568e160f06021dd75dcc772b6802c70aaa5588087ff01d5795baedace066395bf32e13dc7477559200d799c4c717f28a21b82945a1a4c4a077e89a149b94d891d2a377a66d6eeb3aa92dc1ecb576ed1241a96814a46da8ccafbcb081b5736d4e90a666c3368ba6792bef17b6e02e5840dafbbaef7df9df9d84fd950d4f0ab930e0d735d93dbc923f4f19341e447b2ce22fb27ceaeccc67dd6faba4550c78b61925cd7d2fd789e7e41fe6bdc7e451032a442f547a4e64ff4dcea090dfbe5eb2ed04f5427df0d8808ae5bb2e9fa79626f4e2c2a6ec2485e54f251424747308560e11a24343e02eff07ec4961221f799614b8713908596d8a400bf91e5d741db91bf1890c3856a7bba7876e1f6d3501850eaf899c0eb76b50fa51b65876989b8634215a663cc54439d5d27a4999d4fe8270a00a2b30046aec809dddd8368885324c129f3a3ecb8cec18db0f046de988643a34a533b52b5dd140b9df3897c8c0aea401f30ff0892df608b26c5e7eb3b17913026dbfe757a3e7fd88bef645700ce3947c6868e369e8e60eeaf4e746c6e3f409cd6fbb0beb404188aaa6f4c5eae5e8ff89312f6a7d8063c3e008fc34cd74f97e52431d42857dc5f7bf86e214f78389e2464658786b5889d33c91ab278d2add0122ec450ce51ec529e307fb46aa7f96e98f925119ef3bc7d836346338d18bf4859e9d67f907748675be65321a46f3312877a34ffeb3ae7500429de8bc423ea758a55279402155d106a446566f0d684d50b77cc97b4034ecfd11a059e4e91e5cd40a7a78276330ead47a3a32c7886b4e3ab94d2978ca64adde5453ebd46df2f766582a6ae39377da521439479d4e70f03bb70b101a21f8eaa988843762525236d4212d1d31785f3b7f16911f897a0b0d6c4acb63fe03feffdfbe821c0d908bdb83813558b9db6253abec82205adcfe71e401e0897f30f3fd192e534f26736359ab24a959baaee44c446eca96a1729e4b900624533c710c1a95314fb106f15729dd62b74b7345dc242ac46ad0114bff663fc011cc2c16dd093413758ad1246c435c796d434123d5798baff894a6c60626b60750c505840776e32000f44d18748ec3b38dc2a4ef87e1ee4f130e2768b38d45eee75eee9d7faa68889bf22295914c9b43eb80ca335ef4a09e841cb00b2ac34573b342d4722eaf837091d6b4220e1263290f006c2500534ca478a29504aecda8bc7e1090fd63d81a9815af311f338f49bffae5f206385e2bd2ab9d6ccea77fb1b1263ecb428dda194dd756b5fb13ce1ae19532d41669cca28711eb44c86718bfae91a382a1e8ca4e1e502e8686533c04aa4734cc189df5c12594c96eee18d21706858c350a3129a873f7c936755e8ab373a35d90b2ef271673359fba688050318a9644a66737e2b66603097e4005e783656d71eab1b5c79c7dd21453cecaf9e417534333e3c4953dec02c44da472495a0086e29a017e0c5fbdfaee1948d5e4ead29c78c52eaf14fafdf09ec09c7a53a6a8773cae0be90d2b75c3be34dc3c36b6df59dc148c0163f3480746fa027a32c953d04996d1c8beaa2fed6c835c9a56675d1f2a24e731d8cdfc809f20744cc0c56fb7e0139769bd057ae106d0487954bbe51579cc42b303df1f07e42ca58a41714cac5d669e43457c7c387d3fc83975088198b395fd3b4965c42c7b0d20e2d5b231d03ac30961d6b9fa1d12e73b7078799f8412ca3021b4045e3a6b34c976b281e327155f67e5d76489d4243eba0150d3fc9af300926debb7d394b6b121adcbc2a2351465ad06b497059430d46f970d0475cf3c8d605c16094316cdb2c6f7f6a6141f4d5ae01ea3f476502bb9fa2bc04fef37625d89614c480a3732cb6643b984549bf623d33b5ba9fd50a77e8f86edbbc83316810b0ce86f4cd2170aeb9d611ec3d8c718a83c7a20b79dde15dadb7530ded64d4b5dcf38fce242582329f9d6b2d70e7f78add99abfe700b3c0741d6ccd0cb5a8a5e913724fc3a89ba16547d2b3ee0c48e36c1a5dda14e740b8955e03debb344bfccb25c1888524de63c6804b546c419fecb95ea41ae7324275ace92a99695afd356abf5c32d432ae104654a61ceae1cb409c5ec0c6e13a513ea6e67de8101046365b749c25d29e4fb1e193c224dc03ec48d9aad7d9687478395062766f1964e7f75243c98db05269174f145149f816113382d8bf337d028b2ed03b98449e48f1c28f3dd3669d9c17a8a27fcd18d28abdbd3a5057f054a8746ee9fa172a9467a1e7941aefa9f38790b2878f2b85c4ac240b06d9019761ce37d8782db30fc6a6ed3094223a3b96efd290ccd85702a4ecb8dfe069aeee384281f705c1a1710fe9060439b05df3c3f7673f38b24fbb9cccb87ad2e5dc84a75dc77f2bb03a4bb700ffa82a017d09b48e6e1373015469c09357e0894aa22d3a0eb5ecfb078acb222d004e8e1b3f46652b419bedf149815b8cd143402f70220a76046c1c3cdfc8b28ed911a6ce754c718c20fed989007d07389648f2e4f85aad282f0ec26cfd5afdd8478320cdea21884a44e618d514eded2d18e5f51b7c7cbbbeac558751ea844d937d7f1c7664dd10408aa2b23bf87dc57f8f4cb64f04bb2108e6899ef2f7b2e449f3e429669dbd69c5ff705ec8d616781a512a602469f1880c6ac596237d602ce35fdc4d9021269a97fb8074c890fd117dbc587d5ac360906cc07f799d3f17abdd6496cc8a9ebaf5b8c2231097ceef6409e63f2a25cbe306f79b00ea9bab38bfaa9bae97b2a14f6a50fb2156c9331c6dfd2950524d1c0108dcf44aba1002ea6a1ea530eab216c731131b482f52ea75415fe8eabf4f11ada7036dd8081523a17b6010b5ea8ef0795bddeac1e7f42d710371f00d881eecc0d7e8e0900f02927764ca33070ebd12a841d6d3495b380a2fbc346423cc4b11a2fa1021bf567c3a8e3e9ac87f02cc16098bea6ede1074c527dadc29a80ed57ce3eb684659039462ceef603a9ec1dfcbc26832f5e3a27c3b7b8e02b297e8881d9a528a02d01fed9b3c1f8792a5f0570c8e261a5a29713bda9bc0fa4c9b5af332d0bc0988a883f9f1dd4b1e0238b7aa34aaee15f23a90849e7be6af8919888128abd00afa80e839852b88bfa0298f878e0a62a39af6140ea1cda4d37c8baabaf07498239a04deeeb59b8b7f8e43fac96c7e3e11ac7d6fb613d26643189e8c068cba9ac075ffbfd3ffefa4c8f26eb34bc914384b07aba06eb957a21a02402ec0b4ca122d3d00de6c16bd64b04d98ada1ca2c71bf3fda34c0cb603558337619a531f4e786224777da08fe0e54e083ea0f6a48c019fcce2edc4732b16aa203c2dc77eb7ff6b7047b524dd9bdb88b01fab841b4c3cad0ce22e7c8870d31bce93967f09583e635de14fbb8a10fe95aa6101dbc5c4c3b1a7a2722cee3b0e94bdf39ee8dd9608e6e22e92c6e2262907f72f0a5a2b7af8908ea9aa924e41d13cbba7cf1ed1841aec16051a2faed66cf96713f9b05e502dcc5722485ac11dbc3889a08112c5f6b8e72dd57905ecf8891d98ff4fa615b4fca0fe93f56a21e7831d0a3eb363d5ea20b2120942b434bf4c3586b3924d375442c59411d8f91790a16ba8f2bf8322b8631628b3cb201b08dc02eb2f54fc6721cb2beb47ff5f62d01ad019177dcf94bde373d58991cad74b0b37db45e97f03765eb7f06e8641305519801e46dd422aff8c65dd78ce8dc2ae80552e658876613ae24e1e082943fc6ec1f53ebf60bf8acde3e5f9ccba39f2e443c190286bb4b526e5fc38c69be2abd470f15e3505e9f9446dab80712ef019abbfdc16a3d5b818225313471c0b45314fd9706126bc85c3bae0b9f7aecfc80c7ab898fb4456797e9a748e94dd08ceff58f18441e8bd41df47a7022239ffacad6a79325f9421d08f4c81c621fdee3bd9185c78f1768aae42f9d6e74c5d4b33fe454a754ccbd5feae7efb687004e569d79af6cc538eda04341dcdc4c140e22ac17b5270cd37b712b6195f3c0c6c299984e78aaf3be5d8dbd785e18b0b58de4ec46038d545227615983a5ba217ab7a0d400b1624d9fa254b7d8e092f06646c9ea557de72b8dcecda8267603759dc58a7ee097566048cfa4580f790b0e9c2a8de1fc05b48ffaa20600a10388a77d44b4829569202fbe10e6e54dc0d4c39642b6b0f3253ef7dc764c896d897a0ac5ce077d7f2aecb22416ec44e3fcae0cf99f77ecf9e3aa85eb7a50a1bc5a279c40a45cd0732a99fa967fc870cde5cd9865a89362bd5af786349e737b70fe5c54f52bfdf90f829a78072007880b2df682906e611ddbab939104299c0bb78c182642f8909a93e58d3d56002a9242259e0a06a674af7854203f2ff8c55b8f3c911a140630ba6832e7ced3799fa6e2e4f7cb6b2efc26327e028aeb52865b8328bf2745145bd868b7b5fe7bb1c03bda037be8b13faaa9c9da5db6fcf055e7f0fd59ddf66006a1e2b33a815bfc0d1ee7df62545f359ae0a07e001ac56b59a019674b2e0edaf2567a35abb0b40c612f65c21af620c1a8783a09cda88c01c908f688c893cc0e90f70bafdb3a80c7dbfcc574e0a0eefa05637be9d9cfc8856527a829b0207d17ad486c881e0c8074e8e807734523d403e0c4e7dc0a0bb3eb84aaf01df34d2c00069a8d3646224a7f4bce96804ca74d02069812c538bc4772b01b63d1895fb7e7a0771ca22e6efa73d2516251da333310f095608b9b9c9a0f8d8b808268e3d710218289a1293df318ba543521561fb57ccd98eef73e7565c362319d07b57a5c67df699719a4ab70e51235c18d59dcc014df08b1d75653369dc16b7102f4cb534fa828efaef19eec0df9f10389cbb233c06631ff5eb4e25547b21bc5d25e09aefc978e98a5af91dd5a748431d774b0a1d38423d271da3a9e1731f858cfbb6286c152241a667b723024f135d2a9436a035c5633b420f38175f6ecffe8ad869a56c14c78f89712fd35c5357f71bfbdac3104642b5a62f134e7fc8e9f047dd925b4d0e9a709172ca6cd3c344dccf0b5d17f7f882e2b5c81c570bf855cda92d1d97bfe2f103ee264332660fa57807a58f9ab6530c7b6b984bbc3a7daacce621642513e85f37dbcd421e6adf5ae764b9b64c2fdc47187192c68f16f615ca7edf987d117739dc7920de9d2b7f917034b37444f488161133bcb609a6b586cb32d41817cb502f42c96ef625ef6ba7045cb4c8d1c10f8cee86805e63056848534e04b01889d5e7614c1c1d87f9194044906db2bbbcc96d88cbcac930bb02a56de121a107b51eb67c86d66db7b59879ed79faf887449a34573f4bd38525acd4a355852e0e6328aeff41343a7bd285a9515cccf7d27636e3a0f0e755eee1bf5d4c8a716e9212a7d36bd7250d6255d7868f75ceb2510d8a36a14d2abf7abc0bcf2e33585cc2bff3784b890f78fea584dbd84d886ceb93d8a1cf6829147f22daa19d85d5a006e01753f484a99461619a567f1729f286b51e8d3d4205be768d22ca07375905853e5cb8c5a4b3532625f0bb4a1563df02ad233fe0f1e3e88a791ec4e0c07e1d621a9f052c963aa6dde02df6722649340088c26c83ebeb53c49473d0822443767526aa422afce8852fac6080dd90ed81b0290d3f1d8b8e141ac258c26dc469736855636f63e0f251fa4184678bb039ce0aaf5247a5aec47d93d17344b0d701a1168df0926e7551c847111ea00f37ec79d0ab674b5c01a2762198df803b0998561ff956a8114ddeac273b53c808c5774c632e26a1344608e71cd720d901db9334eb6e36e4c27773d9cd0a40de072cf3bd71afef9b1bff0d01737a8dab3ef9dc8cfc89956dd50e5eea663b7e266ff042b22ce2add235ea5628469ad7b50eb988cf020d80fddf977df93f1031f74994cdb4d88c3310197406d0fc1379e3b91421596465040bd500e275b5ef1f77b4bb105afc2c4684e0c6d6abf6227f48e8057c184bf7313933141581e95489902f6f0ea86c643d9ba018febc98694093300f851d8ff52817f69c805c1fe3535bd9ddbbfc400e3c217543950a5c3bc99838e114fd55ea9200b228bac4e9d20d87c15564f00d143d1e00a45954053da15242bfdef7ca2cd69ff60fea8896ff9a4616d6ae84d9408ab8733a87a5e3d642e6bc92e16ef57243a359e83c51d5adf6cf5f51636c9fa8ecac699b2ce78669db8ebe04f43f38bb53adfece5ef666251bd4795bd689716086815ef76c0d8d228343c097efdd4632555de08c69e98e2f91738776ef88d203adf3552b503fd4381385f9f7e7340bc8381da712fb952709199bf570b45069903c77dcb608526e8882724dd1a459affae4d999a1267be00a1f2042ddd899a028be5331860f9b39d001ff509e243d9e636346ad4189c4fd9351d06d4ba128c1c4e91df7f96fd71859cd07c8d378927d0d1040a31184a141722cf8e96d72cadbec660c178944a7106cc3a1b97c824c741f33d137503c1e55575d538db5032717969c7fdcf89d5faa692fe9016f1c7b7477185a06ec6ed03a660b36ec115825628b22f8621fe895c04e36d6f97fb523295d70a2eb1ec2a1c5f8ec2fe9dcad70094b63b890b05bcd173407f092dc8373f25c1420ccde3bc5084c70d5eaf2b2b1995745e4cfec75fa6b882fd09c31e5d36e2a57056efa4d475bc18f007891f83337fd5a9cf262daefb57e56d830562622265b29067fcc42e64867fc71501cfad996841e60f10244b41ee01622fbdbc5615050b9be2b08b4fd7e4f6c06227f720a8d02c410595ffe8cc4aed707f022ecf917284a8d3e871c50cef30328f69c5c526138b14298070215994634fef1cc2213713e90bcda24767b84172513f066652b9074f34a0499a6cd0365769c1a3ff5fa10b3380c970adead64306c51cbfc2d4c44c8c6f2776028b49f11718466c3861e40cfc49a1860067629f36d52fcf1cb222958c8c65269a947067662bf24082adf5d0f3fd9d34938be4e22e1ecb8453df2dfdd0f81c3c52ef28270c1b08feada6f9ec6ddc177b2f24b515021dd74df595bcc35a7aa154bf6e770161d2969a54bee6c3dca42d7cc444372da1542581e840c4c729259e51fb6e753fea441b6c4197cc6d4d57d3b491d68e8e4ad2a8f4d10f99b930a28695aa80c109982d1540289cca45f7267699ac0ccdf594598d370b185a117ac3afa9764d9525b550d12dc6f2143d41137b21f1ed0b31fc430d934ea2a938fdd60ee85c742645f24c6770e3448b59e7344b6a561afc7a76fc1a2efa8da16fd3601b9a6843fc83990156f6746e81836fda5fadef4c52eb54da10792a8e6048592d0b5f7cabe31963ef48b4f0c68b0c4b6a729de7030fc0596904f1b0e25419a225f407768e8253a989fd4306fd71001408f80beb6d3e7bc30755cf9ed10f0a515cb8dac5d4a0939ae50fd5e7fbf71e0fff878bc88e040a46f7c47439f9dc6e9e8430572da2d4efebdbcb59a7df4473e9c6015a84b742c519a8b5b9933a58135dab4d0feea5f0efa413d57737b3dba0de981dfdf23aa51e08db8aa24a7d68ad2caa1283134045f444ce97473271c76572c90aff2b4f10d006149818047cf2a900ef0ef34a6406e1bb9f5e438040a6c27e2ec197c2357c8cec32e231abb48b20407994403688940c4b8d6a0f8470ad695d24aa00c083992857a00d5889d73265894a0307bbc546ba01bf17feb60e2dcf4123a721da5d59f5a47e0d08d949d19d20c5ac7d694b6417916501c89fb0518b0c5ab0be8b3d8f60f15eaadb2a65511273255853cce06e7616db6dc48e79c06e63492d5243690d52ec79a66a0f1ac65d5ea2737b324c0a8a1c72a12768e406d54f8fb5419387a270c6627bab9f3f1cb00774e639333d696ebf6666699a574a283270d17d05d68c8e2eca900eb5e72a7d0e6dbb5a7b23d4c2e99ed7808131b5b04cfa281fc0d38cc5677e55fa3eac13ba2b325f887698d59029138dd3afcec9f950c6578e573973e04e6302a33c7a0a0d10544ea43d5f7eb5b5badbeaf63d4fc2d636e19aecc54ec6a12048226a878b66ff7e35c114c438c0423982df94ada9f80506e3aafa4f90eee5220030d15b746d0ab45b37ed63bda7c0e7241a84a46d79a6c5bbffac788e9119845b83364aabd5e2c1fbb8909f23ffa56001db509ffa3ca57da35c7ccb14d1ad21f89c1735a016407d60518c5b6bbce4e79dadd126ae0f883d80723f9e2e8ccd75990b80529c04ff568059b527b5c82cbb2c14851e7b252464cde9531eddd886ba096c8a904b516ea01f06f80e682234a1071722cffcff7dfafefaa73b2431b31e88271bb7cb93a596e48470ef32d4bfa7486d904fdd7babd89e1756b850ba6318c68f05ef45c6b92ceb47833f2f81950a19ff39c0f1042be5a442ba6fdb738580713b0058618b4685e081a32c8208eed57d50fd0bf271d721eb5a0bec1e753066be82714a0b93aecbb729d0df8ea368741ebec11781c067a6d48821822b370186b2e1e6c20e7b2c4b0e55e7d6e1211a3fd6fa81adfd3a7fd114cee79fd75c7aea2dd2e2aa87632840b2c75d13a4631a87d73c7fed14ebc9f4f604d22c1408ff7d27504fe68e4d0e8860daff1b9cfcecb29a425a63e004b454deceaf5f6e94b49fc775400f1ae021c6b582774c18d280c7cf6732e455798c1f719a3c42e6adf011716a38deb6ca293a57a1d4a51f9412851db92d054f22edb91dfd62e6b52be3a64f92521238d6393565a46cf0fac72f38a3cdc2b60a6ebb3eff89b1b5db544941b46cf64d68ff555c8653cb86ba6753b3e3e87bfcf62aa140be722bc452cec56fc8647d8b01edc7071ba5a04aeb598a2eb5067dc4c375cc81f553a11d586f9bed470db9916d6431708ef4f3f162549b0d8192f36159f20235cad552e822d6f27fb9ca14b1bab94dee7cf81be67aaf913eff0e512b7904afe179466adf109bd74bc0c105251b91557f050b13ed4185b69292a9131b03298da5dfeffdd8be5dda7f65f838d0b6fec298c8c445dfe6421afffc3ab88207974af4deac3c9805243cbe41a85cecba93f8ab6d2ee0a7cf30b091fc739fb7850d190a7289a0c297207f87b159a0561d55400fc6de42bc18db19b0673d45d1fde93d6ad557011883186c0c99f01439e51a213fea6958e784b5d7a89b74a64910c758a38425b9b9e6891c32665313b825909ce7c5cc3498b061edb562b8da00b0d94e3112051023c63848087e41cb9e9d5ef30e6dc1c772c730e92c36a00fb5a76f3184113b3aa680459b4e4ecb6575644e27144f46c93e9c2bb359428dd05fac819963db353defac9ba605f6506ebdadf953895a60f23cf55c2ada02c20c16f4887385383602b23aa16a6b75940d19b404bbd5a2058905cc34b70dadc14519a119bbcabd533f47de6f4756944966a96aac379460aa55231692cbbbba35606d037561a4769f19962d01dd2de5ff4b17dfbc4a04cb20ec7e3ebcb0d8326e0d77e9bfb3ec0b17dec52dc2de60b47c1ec360e0162ddfc82018b50962a7a7efb97cc038347646b2ef453c6af4a9d3b7c9e0df8a5d9018dafeafae65d8b6022d0400ea512bb80f3a3dff9dbddd909c792d289fffd196ffcc3fdc3ed4597e182b1cacc9eeb6de434545066cbdb5fcfdf0b28205170da8032a104199b5451e6a65ea15c6898e681531dd096741dc3546f6fcac4cce11c429574065544d085c2bbc02807993f386b6060ee0b56caafba312fed026f524ceadfc55490d8f41160fb4ed3d702359b320c0ca62944a833e344a8d24079ade725b853b3ac979023504cda981810cfcd6b230540da59783ee0cd4cdd773c16b1aa68f52bccf825fc6bf701c73b73522efe067ae4762dfbaf4b6823c6cd7d522a9b84b597283cfe5ba748c42c1cf9227d8a28d37be1843c54e91e2a7fc5cbce2f689a1b79a4d88898f8afd619ee6883752eee43233b12570db76eb9fb8791bee3ff20c7b5e77fde9c64bd8e8c3146850cf0bb81d74f72ac26303a866811da35388113158a891cc2ed6746a5581aa163ba9c736031e37b54bfb6ff00b424e50102554109567aa16b814cb4ee905800c820287c1512bb241da79cb4f5eb276579a83746181a071f6215b3bcdc90cd057e17fb06e43e19d92ed01df4101d43306b60b4aabaf073e4d10a7844c0d47ccaa06b30fd437488921521de8d354da6d2af9ff5873e4ab190cdc9445e4d629b5bd56d866a7d292256d51804be07f35c336a2a53910aeb12ee3ca68221e00d3909a87a0cf76a55d4140d9c5701eaeb7d05c2475610268207ad0d674f56db3eb62d8e89d800655150174b8a4cf6c747124dc15c9798e5ba0dd62a04e8e67b82f7c950dbf8af6aa9197382704a911aff2e458ab934fd7fda4a5620dc02185e972c93cb2b8a8a8317fdef0a9e7ee03f1babaca7760a941114f43c96ea71bc00f532a6dd63fb497aa8a4421c63eac8ca722f636a27cbbf94ac3a81f000ceb3b97864acd43c2f07fcd2b55c30507b2ef1481f6387c33b5902175de5b8baca2be6f900af631c26ae1c93ad9404691d337188555b4aaf9a70820e53ee9caf1b2f284f62908287bc1abdb7d55357006326a6177890a79642b0a270e94cd629d65e0a1eed0e8d340e901c3d720bdc3a28f01a1876e26db30e0d0323111c7f8b0bfdda65e88a6da16b9af2938582a1a16e9f8eb054a01a50ecf05b2dc5a7619aa6249d865974dfa1f2c69c5524029eb3a05ff382b2573c30077485397975a9e6bfa649a4a8072b7ec42349991b30328d330c6e111aac471c664210ca01ca57db6ceb3b01d9c3196140459b42f70df7bd24d1bac7a753c014e3a407df854e157e11b833c19e613ab7e7da017e16c1ac9bb1df67236ab56c013f48504c3b2dd19931384cc055fddbe83351d074af7da1119538b4332be2ba8406ac94c7cbb2c7e98cb68f08265d17f94244c0dd88c957bc58cecd1313bc1e0f1e3b2361381f1b58920d6618890860db7a321c8724c41d826facc5c56f9705b41d3efda5fbe0f7ca3bdb93099250e495136d2f8211b88202684749f86f3d26820c6c7b0c367a1ca3dbfb31f80986b1c3389b4872593d31a84e6a25b131091974764e01452fb5270637e41bfcc5aaba6632e164e68a2811ec5d17f6b94cd79128a9512b3f2f7dff1f18132dc540b36f1f2ce798daff58ef873e1209e6e2ff9821f26101241e8f47ba58c52875d6ca85dcbd1b72238b540bc0a9c4c41ff33868aa0822c70a8870b08cb5d80914ef160a3378da4c388d1a7335363806bbaf1b0e9cd689327dddfdb327b894b091457748d3dc30e605d63abb2ab27606473b94801791e3d9d0692782546ec582337c490fe09943d3e99e46371c1f788810f32b94acdda5936304a585b3d8ebe64092d1b8f6249989785cf13c6ea70bbd94eba8f281e38bd796c424dbda62fd2f9801690e16b6ccd9b3ff9ae9897b245294ccb71f25effbe4f0256d3f514e532794e792e0fcd5b017a4a02e68b86b0d22a169a1d42b7e20da054ba4f8c4d65fb330f729231de9a258a44895c2f926e4b9b4d16befbb2ff1ae9147cafebbe32523788f504bdc1f95b9f0a5e87423867d9e2e3ee6a44211021d46db7cececa656ddf6338ea0c77719217f192881d7cec3f3a26eef04cf2b1937900bb38f79f45bc95777bbc11636232d8e198a9330be3ec4b4677afd2342b98a22d6285279b49b2f6372f259f84fc38536f3900a4d63351744894b4ae9825da6ef650e1dbde149230482f64abc1250aa1c51c4c304db7323de36c3e6a78de0a5fa496246ecefb657edd048221be03d15baec12e72e34a1727947a66acd961c35368361717ecf9177ea0f15bf4eeb7c60aa1a5baf6e1b4a2445842cc3f8b2078b62df2424e59ac2773075a749661cf085119f8a0d997c8ba1efd3b84aa090b4aeddc42c730500d295c1462e993c7c63b338402843a307e16b2409a6b4bab98505dcd4e53ce1b339ae071f938aacf44853dd9b66c17d5a85e81baead308543ee1e8a1b64badc316b1a0b250d36b5a8c992c45379fda70f3dac5382b687f470b91640b2aebc55cd681f9f24df4b9da3303a61fdb1fbfb1efb93f38193c6d08b48d0946a2a7190202d08b7f65eb7fdd1567da5541540bec7314a250e10a1b2d4b476cccb05764acd82858c857392f532b8eb1de95a82c9df013709342ece1a1371c210791ac5b45b70e0c3f0cd0ba6048e413dc458b593450e32c63edce10ac9ef21ad1b4dc825c1510c4aa3278bea3e8bf7b9893dc9fcdeb4a510b65679f608db85940bad7071b7f83bfcb61d08b211e05b255e8b022ee90812189a69873934cf8e94f469cfc7a7488d51132ab8ae55d90bff0d5f9370ea6dd847c4f2d4d54c8fb6825004dbc32d869a056e9cf3cf3d1e0967579520e3eac734eb2621576dd5ab1ceb9fbaedcb225468fd3df2b473abc7578dcc33786773c1da1ebfe2d2108018ae2f93d957f72016bd2fa30eb64c9b3b9df3046edb284a9e86523040beca619afbfa1fe873c786c0f4d1afbc71928f58fbd1bd3ee2f1ee929a5181c64fc24a351d58b5567b4cfbf0228ec373f0a15834f66191bb7ec622ba406f58ae6ce5637e79d7bc83c632350845dcb8fa14241ea1988f0c41ac20ed17d3ab20e5b721737417532e98891c164717f0ffa66b1f8473f8bb8a6d5d2933cf26b46420f794acdd8e904e22fa4cdb530d8c43be850c85ec143b21c3a6faa21bd41078a8e509e9fa09fc48d5747f37652790bc6f29b60adc9f0faebe7ccc21a69670c58023355c4da98852a2f9ed027db7ca2012bc6021ca6b6fea003423405a3c3330803e7f0debfdd0e7a568ea4e9ae51e930dda296f42ff213df04fd1a5420e567125d4f27dbe5adfc67f4e8544f78a1fc3c78507e5292bb970d1be158484d394607000656bcb5f27a830ff7cc4fdce386ab7ad67147cbc66fd57d56a909c3e850a4adbe8192bd1567310673579364780f5d7da7a17dd2ad247b35580961ee44d288d14f72c84167204f455490aa47a39f955b56be55068365f87bf1307b3fab238801fb59ca41283ad2c16f85ced17dec48590f925ee84396ce2ad70ac400381ca959ff1f4129840a932daab92112788bff75cbcf97abe50844bc4166fbe97b7922617b5dd81a91b9c0a8f46823cb168a75b5eb9aaac37e6260fc40fa3afaefaa5bce19e43d1575e4a0f9d332a29f123aab1a590182d7dc1c5624de05d4508acee1bf9ee36d6a33f3385f347f4a885be7b31e0d4a6bc8249a1641eb43f1c1e6994a2ea3b16eebdec04acde26a27d6aa9d7d0339968e7c8721729d85537e38118519fae8a41c965b23c0b7f5c65a38ef79dbef49cd8f975dc7f87759560ecbcc4026540d90a142e953e571f25927fffde1891b22e3c07c1861cf1f37759230586b499a8f4fe2fa31f5645eedf1841ba6527a492c42d6484b25b7e0c2dc4bea2a70e13459c34274e33def0535ff417a851539ee782ac5db3621073d814cb7acaadf1834c73bb36801f802b4b96acbfa4c5004fe59d2bd32a24576cda0f33b41fdce3ab5eeac4c47c8419b501e22c94532c1899f50743a4adb682ceced311479af705b43f718e08946112c486232f6de6ec16569d1da3ed57174bf973035b52fd4d780b3262a732c1001c35faedf21a1d691354bc3662ee43d9453e5d393070d32b7c62b9f14e0ffb845a6d0e1eb2618d2e1b91d20c36d601620d4c64fff067b9c54891e30824e5f77c1944e8eee66d624d69e5465d667c326a9a8ba9a2a3919ca3bcf9afa0d70300337a9a109c3b77e52e07420b161cb735f8a55dc7c695ac5cb8a2bcea0365560c74f8160b10e879693db13a131f9cb6094a16b4076ba397c4bdcbc011189a6dd73dc7e9483a42ddea5ac11a7b3c0aa9655150eba55a3ba56aba086b51585b9037283c6b65955417cc8a6b514fbce1da452ed2aa0a83fc019c85ce05de6900191589ed772623ad73283a92fbb9b07299c89dca21d452b6537a695a55b286fdfa2e37eb4dda913c7cdc96d8042659533c9707a72ddf0bdd652722739402817887bb96cd8ea77fedf9da12e51aeb58e837b41002d4b7f563ae1f07f355dca70179e36cc41e55f0fde64a823708ae088a69694d4cb5db5f756a26f21b99cd56fa04ed050dde746f06162869244a8605d766074d590e3b5d350f06b68d731be29532bd24979080d78aac31c207fe53614a9e74cb36e5f8b2e5a41bbfbe0783004c77a0a649e1ccdab450f1544688da6ddb016d10a819c2793158997924ecef636c34c36427c29f0648423a59aa0ba9860594c1629f69a5039731c898fda902e17f5ce62e541b56a84c6917cacf72cc67a137ff34927c58ff84b6c8584c68141b6d3fd3a7ab403ecbdd08dc1ca63d9ddb8c2de8e7a9fb6f625140dd123707110a326227ccbc7842bfd5aa9ed027fc155a2ef8da030a6386b3312270f548eefc6c88eb20f5c46663795dd9631dbfe45575d5fa9035af6546b47d679b02036cc01881feaaf032c5b23241b675b41801d8b37be5a8961849b9f56c79e0bda89d700f529926fa3eab15f7351ab8d67a5fae2f6a71630667ced0ad0f9d5a754ef5ec8045c8dc438aefe8c0795557b2db4a93ae0b410281acd793a37b0d66697e15c1ad2f244d537a554bf36d53e160aa8ee9a77eb2053a3ea6ef5d435bcaa77ea8c29e9a373862d17038b3f41b7e342cee7b9e180ce21ee0dbd5838e7e989153353138d77d4903631410ffb91777db7f5b9bebd5d66aec6594aae2ef16fbadf63cd0ed9af01dc57b02ed7f489ab03d5e2eba8caf5d04d511840dce48355be0da1c8c5f65caeb8149a6acb1b59b50a4d8903868729d148e8d4274d7e91cb16b59e82ab10c8c3d714f4f54926c3ac55eb97b72d4ba78b72815187fcebef017dbeeed57b062df85b7df8f28348760806fea24f8c1600b6e5c9ad7e17e32cff29d42cfcaec2457f856ab7f30cd1ae01d321914ab0733a8921566407954b9fba629c0448a6ccf3187fa62750b70c07d89db7702be336f1001b33360acc7a05f2cc374a4f70ad15ff34c52ded921bb5599e5b86ebbb1e41b3117c465946caabcd25666fd1d7edf75c0706e2ab513775f8c8a238c49eb3b0e9c6ae3aba42f60094b7c7cc8ec07834d272dd997f7f44183bab7f6f5178e20479b7216729750a18ebf5677e9a1319e0b0a1c22d9fe55adc259ffaaba8f34f3b131e006b8ffbf47e82d244a6804293a9139147953c8171e7b0be689c35ff633e54ac95e6719ea7ea5cfeaad03f31c63a8dfbe06578c1abc8569ce1f9a00d1ef61d165e0d3e3b347a0ce03d44f4335074d86ad6e6a40f692d9415a01dc76a88ebc9030d41118f33be2ac3146de9345858c0d06ae288f2a7b7ac75eef79d35c84d1a3fd42b5cb7faf16a2a9eab9dcb33059ab867ac513bca348e091af38ee6ed54de90dcac28c86168038b5f599e713240bda2c40402984ace1410fd9c8d4e8fc31570865c574290a98f5768761ed68258807aae184709bfa986db86fa690129e9305a74e884df6c5cd5b45bfdbcca3aa3dea13b9b8e982e1805d21044a969a370889cced618bb32ca38ee18efeab32246355104e21a51ade50290b3206c8cb419c844ea497bff09863dc0adb8f5c3a025dec5e51ea3eec5f3ab824a5955cf590db9a875f9dbb52e735b634d8cfe5f28615474c354f346a0bbc22ab5dae87da94742ea3e7bd28a3aa8f7b0cb9cba91401092aa77b608d3518b2b04a7f1e93e065be6ae931a0e7a6eceb93ea78e57788bfb59d3b2b45ddb286a513fd0a48103ce416d08dd331f771526473435079441ca0a060e40f393b4b3fe77e16b8d6bc1c94b0800756e852bc0ddffa73e8c897dfd3c332ba21c507aed811da3d0be39ff6597a0fe50135a74eddcb7c4f68c863cb1837e4a208af34d561e0d84a7a45fe854b961d0fb1492db9862ac80f5ee5eccac19583283524049dc578b0b8284daec6181e1d4b308b1500584e5ec0504563a1de8fc00ef25a685101547a2aff6ab62886ab5811f4fb00e2005dffd74a21dfbb8debd16188c701514c472d12ca0a93d5e1f575d02cbb6520077f997465f797a2527d4c6dd28f1d1e98419c45d76b78f2af5df5ed4ba5de31127e4f1056f075a72f716d77e2a8d3201b1dacb59d22a61ece1ffd3e3d8e1b87e08fc7890b850fadf1334853acd03b1feb2f824fbc771c050494fee381c7542730b597b5eca41af19d4f9b43c22cee22cb27ed6ae5e0606f772ff849918b7de30b08a57bf21a1b89dcaf66c999507d032610e45d5094d913e842a98144744ccfaca195bcf61b96f9a5b751ca651d0a4af77e835c3e09e0efaf6665efda482573842bfe235b0ea00437d914d93277f0d76c0c97a5fed00b014560b27cbaf94e8e2b343d8869d771c9baaa7963a4f769ec11ae617228b5fedf936d75b7fb9cfda1fd0228a066cddcfde670fef4749f7f3b5ca0133893db5839bb2f69c9cc42d0d9854259c05e179f466ca8f15d9ac52a0401c5d4dbfd1606268457b7af377bdef8524079ed339e267ae1a65478999821828a6fa57108ca3a780a382a55b0558c3136e3bd3fe451731eed7e855d713aab78e9ebec43e6310ea79c7a10ccd187c86d35e9deb96c5adb4826783d21840e3c36048b3f2f96ccef819d125ab0ea24d9807d0535f1c855753cfa7def4a2863a8be3d3d40cac6055abf27b738bdfdba734f729ddb8f894429c05ee3efe25bd1d030a8d5a8e78e0edabb5d667b9d2c5331727cf167c6d5058747388a59844f0157ca85c4730251dfe464324010691bf767e287b21337a01133f1230cfbbac12d43bb40967f3775306b13e6e4fd7097b8694287d46f1a8671fa20cf33d51665f89de15cd6b8d4362574c690ba57187839447c95f69f5120e51e4d95efb0760bfd2c4c8991e1a4a8c0b79d1d614ca444c928a56675ecdaa31aa90931451a4ea464984691d322041bd05ebc8635900f4447809e503a112068034374f2dd364e16a95b85ce610786446efa473d9a0e350be60208cce974a09bb991a6406227c92711c01c1106ceec16c7ef1b218d0285bfb34af34d7b8253083eecc5360a820daa4237e441f071fc464fefd4b27edb0f480ef192ae587fa99a805045574ad5a0dc5149808549b6bb7d73852040dce7cbf0ed5f0721bbdc5824f56bf3875bd756c258d104e1f36f94b1364aa26a2e49ce0a11c1fb9d5e8dcb886abe4f0f9d1f27c0716ed71b1342b3348a599e422f56858ed5a87093683a82fe03022a95e4aad209fcd48eb1059091fa0744853cd044e69ba6c29f460e9f4d8af0a10b7fafb4f5c9c70aaae6f688925618bbc27d09cad29bbd93c37dcf6a3c9ceb309efdd0dd21e25a83645fbbfb2b87a7e93407e31264f89f6af4ed3e4022f4640961cf9d098b56fcb129cf4971dc3b9b8fc70eccca2ef332252979afac760e20af6cf26c6d09b54b42290f17ff4a9eec01dab5aa5d463b5c55573b9c7290ee33b953cc781fe99c81154fbe121e5e66f1750d326827c263ea3a9681e5dc0256d2a52975d400fece0314c8335a32e59980537ba80cf416b433dd6b9df0314bebb17446d0646c629235c5c6393d1b0d157094a1cee84bb1aa393074dbe4f16852fbae8a09db910af7a3bd2adccfeaf013d98587c51c78e96653539b62d4c636398491758c1746ff273716d7e0d78e63aea748bf9a48237a589d9baffe2fb64244480a4014e341c969d8fe6077cff35c998149cea5bd1706c6cfc2de6531ad1aa84ecddc68ae1c37360b95ce355293b8295b9c804f72c761f3b57ef2471fc2e183e90d14afdc70c9ef4e98e0b46643596e995263606c3867f45daee83114e74e50c3149d69fc933c0ed990789aa0b71f54b1c5a01a8afca063e29f4152054595a21e101d395a491ee8c6d9bf1d91eae4913a09d90910ac0513b10b0fc6513e17f96584c7d9cfa6bc86ab1ed366df18e1b4abc30d0efe08f1d3680096bf9b376ff81c43d04dd4cc802601329b3b26b435d2ec04c12fe278c66d20979941b91ab1ac55ace047b55014c0ede4ee29d0d99086c2821556f4a91179d56cd5ec1feaf0054a9fc0ce0af4ec6c917f156a639d91629985b13542d569483751a3cc134e22d614e84dd2049aab3ec31297a79acfdd5b2e77fc3b287baccee5157fcdaa1a2cdd6614f9db583b6abd06e90d50885a23489e7a9857472fefc7a5c475352a3aa08ad5d2d18845d8f0cb2f8859f2d758f0f67af6116888b3c5ade1e2ee78ced3e5f0bfe25940940b99e7bf1e45fb87f843462f3fbb7dfd8eda26c8af81b4b4ac7160a2e7f887edaef3a81912b5cd859c81e11946c72f6ffd0168437131f03a5340e714066e67f66b2ab2f55ed676e84fb27ba9428905ced0a5f7a119ace3a840a9c9af0d903e399ca912505c12cc4f3e6cdaba7e11bbb3135b4b7e36677fd6859830084d8c4b0e74ccd02d03157c021dfd255ff3537e405e3f1d0fe836c2f1cb4deebb392cc02e6d4c05c2dd910d0ffd18e42f674647bf1440f5166266f5d52be6fd9539fc52d487b82459e73b5e0ca251216d76c69e0fb4c4019e9b4a501ccb53158a967955c81eeece059a5b0207fc28eda87de2470627f8af7185a818b9893a24c81f3920a25e196d668589492cc434bde85ba8926c5fcb23c57f3d301937d59731a0a3d2008c28a5324098b2bbd263c0f9a1074fdabe4a85121b72a74d0b1314385c757be1a86b50537998f3efb14999ab9c69afd4fad3badc61eed9499025a69283ac0bb55bdea481d09ad2569bde73c6f6927e76035ee3a7f80c49cde86b0cff1bdd4cd575391b271755e2eb54e6191c58bfec1961029f178aa74497bfdb42aa6e59bc16d58c95073ac95842b62b46fdcbc309d9ee4b424dcaa48bb17c5dc6240e5b6a2ef2c3dba724a79f4ef1490a8797fad94073897f428eb08cc76b86a6bfd465a694e3d7c33fd9efd540e9c72d2ca80743774e246d1c8d47e55be6853f5ae0029283a522f46f93fdac991513ff38b7471c802023738a479ebd9d38e638da04d557dfa8cd4573f8a3b2a13c19e6baf482e8d9bd0885a58cfbe4dc53c107ac456ecd7a087b3bea4276d52a1479441a00505c66377d6fe693682738494b3af989a84b6252287b2d78fccb585b410c90d849cfe568103712239c1704148bbda4b342fe07a46d7b0cec8046a04fa23ff468c009ca41c4565a93b432fe50b46ac56cfc0f4e1840c4894bd3357726ca7c36ac2625d48104b707e126dbac7bd7b49f8958d6af26797c3037fa6c6cc0346872f0c4ee63a8c76faf67cc06f3dc6800e99bd53c3bd80ab522e700c78b0b1e052220427042ab99e8d5920d0ba4a0789afab4ff41b19c0d004ede52fd750e36730b063c54713c06f4eefc3c7a3c396b6910303b55651867664b102a26208e9c012ec964a317dce1fb2c3685af643016533fbd608ac93ef43cd92bd09917b4b99524a018508f708df0842b10b283f09d80a7026ca1f6dd7c69db8c65251523dd29d47d943e3f628b548130a0df5cead0d2e413be2a33efa78247feaf4d5d928234d182baf073e507e2ab859430d7270a60d30747f770d48adeed3292b7b2c958783e3717b685c822653efb304b304b46ecfc9ee45001ea15f7a3cf855de4a51a5b75bca745945c0e3f13649f070910081b74942ebdcd3517785c242d5b059ceb033d592b5e373a64d15b43f643dff7cff1d9fa3faf8fd9ccaebaaeac358551feb78a3c171c1eb5ab8c9056b7596719c4179d75f1d44acf1679051bebfa68b4625dbcfa06d9a8cf125ef96338ceabe6bc7866dba76f917bb5ddd7c0119be7f9bbe84a9fd1d83c12a5f408698c43bcaca2085294354c608856d9763d1b58ed569669943c1e3510f87358ba2acedb185862e1e8623619eb03d98ba6a079d9373b04b3d974a3df34c116997b7b0b33b3fb118f34ef348275baf7cd8fc77cd196936b2f97d2e6a215ca8aed9a51b165073dd125ae01170707890e3f77fb6c7bcf1011097c054780447e22fdf3175d571aa70659bc756783539292635cfd6b8eb8a2fbd6e5df1d928321fc95a167fd398411d2ca9e5c0f11894443c90e190ede01c600237bcdf17ff8d07848448112335ad5a0384f11013edece710a09d1b1e37b5028e54dfd1ce06a3da72ac05b46b57b833f0c6e37df1db78abfdca95c7a0f6d5782ceacfab292f060d77e640abd6f6c531891e69da9176a41db5f66d47234790340fcfded2d89c9ae5ff3411ef91cfdf4d3be397489123acfa987635d4eab837f9cb27106b37d55022c94f2bf2973b41c39648d59be7a979f871b035a1fdcdbd65993bc0cbeadf1851485299b9b977c025008c9ab8b32c97eff29392bf5ddccccc1f9ddbdca4ca42757021703b46f624c68b538cc98a12121528474c1475868c19d4986804b7bbbbbbbbbbbb628290e0b3b31d37f1c8e9b0c8fa1387002f16e06aea6e29a39400309c6c3163e8b5acf164c46dddc8ed07caa072c3531151173750bac1e886a4309cfaece8b19391b9eba279970b2e0422caef33b2affb141b63d4589aa67dfbde55dfe2b66d31fa5744f66ee8d2dd37c61853bdec3451882863163251926127d34f9ca5b9dcd98948501c1276b056aecb65f4d9f136668ccdae038e893613edba4f868cc2704c945f3291f74800d8907af94154bf3d173fa005536c50f9db0f69514aed555f90f6b5e2fbf8cb83e4d78adf0882c8d7be2097bf3c09b87d12d027f555e9d00534e587bf9ab08419d22e873242640bf59fc925987224c3ca4e8c3136e9581500329a54f9a908c61893d0004510485f286d6962058c47c42f0c1369ac18c71655fe54c30555fe46b184991a0863c450e5cbb83748428818638cd10331c610c3a5c6d805e7504950e26a982c66d5c8a096a48005944c8c998c0d1d8ec814dd1023f38215349119a2c80260c82cc124ca0c2616706a22c3e5862f51646650a444068c1b88c898b1c102543c8911030e16f87284130401c058c2e40585872c4ba0b858da07644e4d929a8c692f64311b906c20c306a2346cf0c42436864912d4f2482d6a1e5f933146c9bc05b7756577a4fa737b715d712291cc51b8fd40184e2b706202bba1090c3064326a46d54c8ddf0bdb997aa46b0b488e7aa3c62f4c81f6c9ad9bf632ba103f5617e2e344fab1564434aca9fd610df46de07098a1494abb3f5a23bd758de1f60363c42c812d61d2637ca006514a9c94c8606d300e1cd0fd30d6b8ad4292daea1a6afc3589732fa979ae72724137c62825ff7e2dff50a0a9fd6d6533a4fc5ce88f31da3e8a7493730e628c51070be87ed27d77e516a594a1ac45bad8b030ae0eebf8816effec662835d3b959fb26d77df9752791dc5a382b92b342a3b78203da68a38d36826ccce58713638c5e39d2a7f281c7f766f6707804fed87275687751ca9e1da3267bf8a0b6ab1177706298b56be3152e80baa9fe3c9a67f7a34ea8bbbb7bb84cd9ddd7290046d058c3ae76cbfc3a64386e1fb79f31ca9f3f5b93114afafaf3c7285beef4246ec2f776c13f8747e09f3bf6c5dfa4b5bb7ffb87ee66f160f6ff68bbda8b3c61a41b54ddbbe3ae9432eeee8e4be3393f8489839cda6fcf693cc250ff9652fe87c03b48000c60445c84616262064668a0c90e34389343163b98e2454049f913ef24452bd0e287a41df84003154f8a4fc2d6eeee6167ecd27425873262ae8851014622ea01c64c51c516269808a20897c1961a30c1c1f184aaa0faf7f6e8da3121ae80820c8c95b07240f96ba5c0fe49aa2bb50bb4c0a28595ca7f02901061988490c2534480a2fddaf342c5ff218292b0bf886b5f0b3d2415db2e4998a95df73739b93a21fef869178ba084b6526817be166bc7c7441ab573e4b7eb5d3cdac5eb84a5490dd74989863e4d34b9be38e38aeedeb5cb9d992977c739a7cfe9cfc349a1daefcae572bc82c0bfc06da852deceeaf84f6efa967aeee9b19e3f8efbfdb39eb5e38fb48b3ea1fee1ec7cf8330bdc49c5384e0a0dbbba404fb3a62fbcd4dd528699caaabba50c3195ab91ff020afcdafb884976482843631bd53bc6ed07bc80925153a25423538dae3dafa426b58f5a8c5fa87d2e685c94518b316ada27bf7dc12b7f8575b1e3eaede12cd5df54fe4d6a1abf4ca5bcd5e9dac57146d6b6692b5e1dde38068573c518638c316ad23deeaed4b64ddb18ddbdbbbbb5f8b1bbffa8abd32cd69ccc3fdab5b4bb37d6c6d327b46b3f046ef7a04b4d4ac571aa94a66d8cabc9d9dc944a95da1c57f456673fceb66d4bb172ea4a7777df388c7126d95dc6a86952326fdba64929b79a252dae5edd7ca6acb15dedda326874cf9152935195d32eafc6e75d1fdecdae9bbda5e3563c68341937d50a38c6892915192783855e09796215d7b9bb0fe936329b18db2aa678a4562c16a5fe43687cb058354dc494a879bc86de44d58daaae8a53a53457a9542ae7544e44cf709b23e78ef628b56d5b75504043625c6e95d3a57efce5e3a3c2ea476a05e4bda254bb9a48232202f235eeab7e56af38a75d3f9b8771dac5dbca472af08fa69641798fea5543ed045381add68243bed6146e41d94588004155debc6264a4d2a2a6b9e611082866fdf191ca63236e83c2b33585e91e0f92a4b49556ea587be94d6b2828a82abfc734cf92a9f2f90c17b54bee88d1fed83ba8e03687cb21a3b3ca3258122c06c5240595bafdf6feb1d991aa9b31b5f1a91bd05aa183a13585e6190c62908bf852bb9b53e551cee11f23cb1145c366637b18ccbe787ba8b13c1d667b3aca5f1c420c34aace9fac0fbb29ab27ef612ff6c54fa64ce577352aff93f70ccabfb065bebd87937aefaf7e1a4ff5b1886b7afe2e5bf273dcf5d3190ab5b35379d67ef29eee992b6366820fd00f7ff1bc12e3591028a076f17fd0792cf3177f121495839bbb6b9aa6757c0f716aaf2ae020c7fd148750a2822d757e3fb51857c845705445fac5ef1fab7f44188a05f70a4490534a54881a6e540b322068b850a9c631228b585d1dd4c5e26949cb512c06ab61d80a29507e2d5f30ae82b4a6b0c0f717abc0055b53b45f9065441935dc99bb76a4e7e3af2b43b5178885ba92c52e3b10b4cb818a29a104ae288b448a18d515eb68294a110c3c298a13356428bb9483931a466e77a10122f01ef91de0bf998081e1ac6bc4909e86b1a268f547c2b0a2a51f70a8fe4466ad2f701b2ea31449f9236fc87e78cff4214d3274c5d03fb8e9a57e67474b7d3796d0709b90782472d73aaa8ad66906a2eee9fad5bfaa256e8041c3598dcc663bdb59ca7be6f7e41eeeb7c7c63daad4eaf403790ff7a9f8d5dffcd2bef0c6497ee107b53fb6b7363882c4c89c4dadbdccd45c3a6132543ba85ffd48bc8784f66242ec9e552d41bbf697e3c47b4eb0affe18a692271c90ac0e0fe12f88a328d155f79777797957d3de59ba74e9d2fd71982e6cc27c0cd61ad6aee62149049dc5269a18d2344dd38e905490180263831a59a4903dc1a4c687a218c8a198e44a524a29a5c6b47d318368484a29935c79028b35a6502286882e9c698a303da8be94012545b524a7abcbb483ac0cd72df93442b04147101d0307b34359ff392e1e06604206008b1504c8328500aaa05add25a62e71dd054b4ef0a07ddd25a62975abbbc474e44d96fa0a099ce1e405982b76f8618cff2b5b0f9a51d1126954839438bb03254ee567668900a4d0ad2e130f3708a3868d8728d5290ed30e666aaa2e130f5cd490fe5cc2b7d490f500335fa0aefcb0035292ebf0af7445ffd36c41f77763661cd89318f3182d95dd573e689e7bd54b6edd66bdd77d230e272326ef3d52aa714a793bb1976d979d9aa7bf9bff7d766a9efd7ed058a27fd050da11f6b9a95decd42eae5c436eaa1c26a9dbd51ecafbd347351f3d1b7e72eeb86df536de115f7d8d27a45d71e58366cacdf7cac7ea93acec0d47357925e811627737efcb18a5bbdc26f6d23c2958d38b81daba1f3fb1d2c7499c5457c5ad8c454e9abcb84daae3525d978ace3aeac14ac7a58a624d4c36f1db091bf911151db02751b5417e2e95e4b1cacf58f8dba9297e69721813466a29ed62362ac2b1030d39eae663682855ee1b8fc911af4ffc73fbc2f9313f0d214284610cf5ad80aabe85e6514d6905b9f1fbb552d829ad203537bf5f6b63128eea9e7801f2976cc98ebbc48393ea41e51e1ba39913a3213889872d356c2a957bd7c9b35476a2863c2602aea3024ab43d36080530dcfc0d0d409ce7e728efc1f982664802a200e7614801ced3c020ce1704821fc09b1b5f1008a2802b213734006f40d615fafc9579da5c000605fb2a0e28d4c5507352eecf63a80f7ca0fba9d4f37a5b395ef5735c149ff11a66039d1f721217e73782197dcad971a620c314685a6afbfdf678ac34cf7e0c2526da5c00f6648408d582862b2352a47b3e0097a3b8547e3ed3b05dda010a6e871f87057787a356879f9f78f093cbd5319bce4983f382168c17322881422c45d1f442b4040a0c1a018b2d305088c13c394206c65640abb7d0c0cc0cecd14314483118c845efe834b16558230b224e76ee05ad0da03043060a390d4ead15be209e728319a240a1550d33d03f4619b460e6040aad5e8004fac72f8865e0a48918284473c611e8df6eabb112276494c0f616148adc38f4182bc0785a2e509834711919506902395f42c31846a0d026a31b460a18249093ee04ca4a1328d49d39e268dab08596797483153230888198c129060af90e47a076460a1033b06938228a285003a306982890eb68342d6b1c29f10ed6698b2632e712f58453ff10454313c8f96ed114451228d400323d84891a0c6a98411328b4d383981844275c684004725ad79e27110326723003b9ad6b2f686540d4c00914ea34aa84818019c0cc402e654506284ea0d00e94334dc09c56407b4108c0818c245068aba0828015283102b9ae6b0f0135d8900472abaebd03b4414311c8d174ed711618cc10052e93922e512047bbf6dc8719c8000672365d7b41ae0519658a40a1950e4f14dd74ed79d10f349881dc8daebd20f985071a66a0d03a1501469caebd78650b2d92400e47d79e63a146124720373dd0d13550e982040a31bb1358cc38710472dcb5d76a982992815b85194dc840cebbf63887299e20818cc31145c8c0a03ea3062398b604092b92402166bec107eb7bfa98dc75cf751dcd8faf7e3e7b21d8597561f5f397b43871dead9ea5303333f3b6d1d4d43c57b3d56c5b951f4b69573f8dec35e856b93528576e0d2a2b173bd51a5a975aa1a0aadbef340f8b45a993eb3ff0e19b67fbfa1989f2cf9cb88455ad41b515ffae363ea2195237b903a49582fc14f091ea4377e276f2399dbc67c1e94a1ee522384143f752bbdfbdc8afb5fd5657bf54f28e6d338d9e3ef8f0114be1595dfdacabd5b3e72b2f7e77146bd84523e0da0a838645e2d6004abfbf95be161fb5cb632967d0bf12553525ba8572636957c76a33c5ca1ed7560ae189324ddf708184adc7713268e0fa4bbed7efde1159b1a0618b69a7586d294c5868377d1dfbacd0fdb063304683e3a464667ed9d139c4bfb065a21605f8b55f41fe7e30eccbd73e18aa04d967180cf2f96b2cabe3bffef2e7dcb3820ec94f0890cd0d686a5ffeae239c5e66476a0e7ad05251430e780085b62cd0007fb4a630ac700128b45d010350bef605ad164d4e4a80429b1210a0fcb61510f2a8fc49407f7fad207f7ba92465c497d60a9f267ffbb4179a33dc40e70e166d0591d383f6092e567801063cec2fe5e73c1454fbd9cd5782d5c310ef7922eeda1e5e0b44b6ede5a1fd7aa14fd55ee09e9db5d0fc0d51a29188d742bbfaa714dba30544fbf8da47262a667599a880aa05a8cb4485953a9b87a97e5da62f522a173b5f49b949606b94c0566d86b2b216b5fed82cab06a46b7f093c1026a9fd496a7f3f0da4df05ed350f4800aafcb65d75733c32e198be38a9367599bec0bedd1853af799b53a5b7af79aa8f5e00ea265dc6df3eae50e89f807cd9d5cda95acec6af43a22e4c76256e66228d33c47df3040d5766831ab342458a07b6c7cff668ee9f2d1e83a7d82f8f45fc34ff682e445aca5ad6b226d24da4894c2dd409112832994cf603886a319b1cbac848e7b0cd3245462277195f70cf0356cbf67cfbe2af41ab600b1d587186146210000a0454f61e95dd0b5e4f29a985763191fe3eb023f87f42768c2d4d2e040d295454932225d174515c03494a9467686790ea0f6dc30119cda4ddcc8e934f4c16ee0c8b152ad508926d9a91718f4b4c14ef88f11943c61828471e738fcd8cf88b998d301bd16464239a9c363a90329ba5543355145c1a45508c60b1b2b9fcc53db7d6a28be23aee3eea280dc4aa8b7240c39d4d1ab9e7169ba3a0b151236ca53a9bcd664a9369622370699f1e4858b8a3847009fc710c438cb00c5a40b9913468d8b00ebba836a9bc89bc67f51edf699a703cde11ba8eedf3dee6711e9fe20d2e768cf8f8f8e4482aa28b895ec0eb4eaed1ced9a60eb149678a494a95834f556a23cee1a870dd8d0edd0a071a32d18a0627079a9a9b266ae80d26166a08b5c942e30e269b9b3528efcd0d336ee044a191c331e273860f0e19d0f8fc383aefe5222226f282fafbac8f07396e80918395c5647573a3abe33f844be0cf6647b8f1c54cc756417dce9821c30a13319940820924101111f9fcf08f65e5d0f16388dbe112f8a70008b0d9016b87fba6053d77543bce83c71b5de2dc51ed481c208af8949a130d37c6f270a763b2e3a345dec0a0e12209194af59f1d1f7f2d2e0e1d380e070dcabbbbdddd3631a0fc5a7c5985f24edffdb40f8165357edddd0d44eceeeed8dd317677f4c254f566efd8da1641f9e3abee8dbadb366b65709b13c68e9ddca6cdeac4df1882d4be6c44b0994046d9695b472937d9cd8a6f33048d326a51eedd44d149bbfa7520456ece2dceda1efc5df9bb591d790304d555fbea7d495509ba3d761dcbdf5d6af365949299e76b9d9c40ac470da58ddae51f1849d13185c6a32e4654b99aee612727560b8d1144598c9115437efae019c939c9830f5929c7c7c8454382e2508d2f24d620699ed6c718a353c4e98f1f7fb6d8a02def9b7e1869887ff0852c1d5f485939be90e5c117521c319c2f9c4621f59b161b34ec66ee365f386521ad29f21dde3a79cb521f51f50ffac610aec99afb2c5839ff0eff863e55b00d4766662766e06ea2061f93962dae2899a8419558a65489455665134330f183ecc9a104462e4dfeb38625a6b8e2a5ba6a36c1344e57643a7fce7905cb15236a76708ff8f30aac4625246afcd6ca47a4341011e58b0e9e58d0448728b248d1344dfb20f5a44445893035be8e958f488bf83259719f60cae149e205498c5102a96a4890a9dae758f9d05c55601113638cd183950f197657e454eda3f6338caa3d3d628caa3dcb4a1355fb474287236ea8dae358f9d05c581c01e6468c31e270d951441a463451841155fb1b2b1f1aa54bc49002eae8090dba28e150182104b793ce8ede6c971a76496324c5a0c6b759f9883963681874e5438b677471b50668da965043126a6842d59629842a59aab021832664f005112d20a28baafd5639aadad3346591a166a90a518df157ed84937c4dd3344d8bb17972545966aea6254a142f4a4a645c3465c9c2c251e4da1e4ee5892acc1237a8a416638c93a67eb99c06d490c7135a96ac3040d57ed218aaf62ced9f49d5de25c41455fb9d1554ed7d8670aadaffb0a06a4b31b8220b6e4372a2c6d7b4170c41668d1fd6b8f2e505dec5883238b9f221674e036a5c42ea414c8d1fe94e943151d4700415596600e35c9460c0006003d452e3c777aa695786543896a65099223c99da26b7edc809903094acec6003a8288c80c8949682f8c208880d8a5831a4a52c456c6abc92e84ddda52c2f4882eea8bb9445869913b3073821bb517709872527742883f67b6fef2e37af7cf65ee8caa05dc39f6550aee1772d5710e83a3dafda1534741de99e4e721e924907d89ebef9f83bdbc3e6e9872b5b2f56b387f86b6adae86071f7ae0ebf8dd749de53a45ff1e36b5ff1a39918d3a871e38259286a7cd5174bc5b88076aa317e38239176bd14347424588def48716e7c1f6a16941a9dd438654c20f0c154606b05185ab45d314aeaaa3102404643972b27d2744a8b18a4356541d6ae363eefbbfcb5fc85eb64820f945251513c8a694d4949fc1c0d1b51c16691e324ef6125fca4f247a548c44944f10b3916e78cb18bab18e5aa536ddf7393316a1a5be917d7481cda3d8d6ba7fb58feab5fb0fb9045e4323281e8a7f37ef82b16c41be22f21575443463256412d05015b828192f61a0c90ea9a517749ca957f0a43bd1899c0af02f7778c8cdcc87dce55d1eaf0774882f00f274569cc7bf84914218ae0278e19ad0e3bed60b1b4546a4e6e7ee4542b1fa9e7150af3b7d7e616a3f69af69aa63113738e07e1bef3f9e13e97efa83e745556815540a010fe9091d888a1b831565081db17d16c38aa53b62f88c1d60adbcf9f1f0cf3b7af06d97efe377bfe825b7703e12f2e0b9847c130e03d2278012606b6806cbb0aa27dad0952d54fef705610ede517d49ab2e3affe7eed7b97bf3af60fa02b50fef953d4a10b19454c45f3e733c1b468991d390c894a0f35eca893665d44651cac0b0065a2f88b37f2392a95f21ae6af281c2ccfaefc56b24df4042b4b37015086527e9ab2d509829fae18b183862573445136b6879f6417557ed8f116dbc3515c832adb68abf69325a1629d7cf7f7afe352352b1fbefdc79523f1fc6ececa3554cb5a42495977547f61cf729e15030d218a2e5964e02165e84aaee44a4a3e49d876bee7a4dd51fb926f6482f4915be891595bfd496a557dd8505256fe9c50ac1c28c74aded34ff6253f4a1155be92944e6db43a12ca09a71ad20a15c4a0ea7f3809003464074d367e8c9fe302628107e530ed35951f877754aef1fd9f66a73efc9bac3e2028eddf811ea9d1a4055328b78ceeee21cc7c0758aabbbbcbb8d345530c607bf47b7f5d506e57bb583a50ce3bd512b4e917a309f1f311e10a6522a8b7093f3eed62614157c77b806145b1c58747102933dcb66a620201fafddc0e2fa85d60031318b8c30b0a126223643cc4c02af377bc083aa040b8974254ee93d4766a9e30cda3e359bff9c66a0f7ad6b0380fa85d1bf7add607df4c1e4ebb5aad0f85ae2dae6edf5468533bb598ed7b4cf77094eddb8a53dd24b87deb03ee08168668336a9eb0a554212a109ff1c2c40994ad35bb99460d0ef7d1766d34eaf01a69fbe875d20e19286c7b96c74349dbb3d3f639be90a56a1c8ff385cfbaf185ac8d05866e543620a5e6a1f9ed1b4bf3a8d0db37d3f6d07e93cf79e1576ec8eaa777f3d10b8b2c540739d2757e3fde4fa14fbd01e16d3cd5afbcd67caef341f05a292198d8526b1a69076b7b6e87b75d10310403819a67fe4685053210a680f03b3ca0e6f9eeb7e7be1049e57e7a21923a9fdd0be777c441f85af31764a15fdb775f6b7eacf313c2c445cd99f3fb8f80fcd5d378ad14668802a85d714f65e5f168d79684d4aeed456883ce57a179e647a45ddbeff0b81ccc62e0ae2ab06df377dab5bd099e4fbbb64fe5405fc906757e612bd52d6ca4bafdb46b73b6cf0587511568dd1fb076a054287f1bbfbd233ea5fb58e8178f61a24e89d95e917d4261bfdfbd10ec537561bf91585f682b050f859eb457a576f77bce20ede2ef3d3e94ca3ba5fbcd2bb24fb55d8b71a7985591e45af4199130fd19699e0d970cba83052fa85ffcdb218942fb1b22a45d5f147de2518195449178096a62183f50f3c8a3caaf42f3b0a050fe1fdc2d7911537b3581fd0e6218128e88902726e2e13a21baacccccac04c6520c5566662ade4cad1d12b444f146f9f8ec33d7354a7032071774ab5c41802b03d16a2b85cf058d9bdb24abbf0a5400474b5ebc8ce1a63db369cbd462ed3021a6ba6e897a73fc58bb1878ea2e49115565f4a8e6e100fd10a10dca83846ddad9d99e6caf71e1769ca191080b438eb4a66d5d1847f45059fb8aac530a5d18473cf199554e56c2bf9d26e8d6d8cf0e25bc20edeaa75cc352146264f4d6d7d6b25e987d90419a877f9be1e989334a0051c39e2d085150ee57cfadbc86f217b39ed0d404bb75da50298772198c363bcc7bbaafe53df39eedb74a46cbbff9dd07c30743151852afe27ece397ffe0043270aaa1224154ed91eb67d42f361f8d4b79cea22f53d09c181ea43156c1f02559db27d3020f1d747c45faa2e54dffa2bf53191bf70c044f3abe2dfbdea4356179d8fbf1884293445f35bcdb7622b7e53bad80c8a6ac84620704137075dc6a00106c230bf05443ff54940fa0555f1dfbe06a95d9cdac2cbb1652876aa4d5e08183014ab468a30602856a7c020b4c9c0d46f9fda425b260216da5fd84f7593810ba63ed82603b7afc5338ef217d450c38e70f217ffa6c9dfbe4e1ad3444c5a16726da6767592bfa648bad2a4863d6b275dc4db5a9750ba87953419542a379da91cbdc3e6d6b61766a6957ffb884af3eccf28109cbf12b4d5bfe0a6b630abfab0f40416232c2d20db02c2ad9fa2fa84546095fe24759ff793d424b50544abfdb552bfadedabac1b695af27342bf260155e3ee744ee8cece67478436e886446d6666d5582bc149521db3fc6c36cf458c9663a9e90cb7d8ab7f10edd931455f869cd849141f691d817e0489c2e15504ba87edd1411b336307513c81421cc59725c02afda0062435811f4ea369fd3fedfdf0819ef00389f70075511cd43024ebf403c9eab88deae7871bd5b56b7bdea889a30534649db0f2a1fdaea226a36c7d0ed777252809ed72c7a9db05091e6ac84426f8d4241dabbae05a40fd49407e76fe1610f3b7fe62b0fb19ec8fbef6fe24e0802e9fa04203b58be3d57e394ee809f6e5ef2f85913e3e8a3a8a95c1450551fb439b96c02a02ea360d9d51696da212e539417168a1336e67bc8b1815e18ef0c38ebf711ff990d632b1d2f7d43d4def8186ac1424a9306385cb11182454c3c3141780f4855a32a411036b825a01fe351e9256803390d404e47327c5867f0b5989abef121458aa4735cc4bf56f22265090a9feeda47b968b24180c16c32e4d61a63ae8cf6d6c85612dd7e2af6a8e1d1224341c551aa2ddb3d2d675bef2d1d1acc47450fcb44350f304bdabc29dca4afcd3dda7f23ba11060a15382ae334cb56a354143b6c25492c4a86ea00cc54fedf2ff9ad0a54fabe36fa3023a47c0f5e90857b9339f676ca679564b104c75fe74549a4154fde9a0e6d96fa2213fc57797a2fab399edb1b5bb9f6284220a29b5bf9c6f9783536cb49fb6f16a94bcd4137eac50212cc4232ebfbbe3f7d7fa515598693c01390e5de718e2a3d0709f7866c84c5c8e01161242991583630c50804eeddf1edb7136b553b59fb74788105f7d47646aeaa8f36dea4cd5f9d3c6f358db0b0ee0df55737bf7053c046739fd82a05c4f5804833e3319e23d3f29eb818474169403dcc37dfc211bd81ea98ff353de065627fe72805ff1e38c7b465c356a8c435627c6c8c31237cca0e14ecafa77ad53051888029302abd31598abc57eb1646a8e9ae372fa6643b5046d9edd1e110c6003fbf20f67eaeb6f00db83045c9989b9690af1971764684531c6d2594c7fe9981ce3588f0bac0e06f8e5afa37e21463885e4b0802ec736a6e5c0a996a04cc6b22064a1a87c44d096dde430146e98392965b11c025b7dceee2c88b4d0aed412f42b83f277165886b828d13c6100b8abe9ed129959939b9c526eb29b638c31ae4777d75ec02a6f5d3235b70c547fee8eac52b1b721d3344d8bd107ae6776a8a3ae4df476775f01e1182a4ebf2825c3a293d9bb70817243ebcfb5e3f3365f3379720ddbf0cd17b2ea46433a3f74edf8a4bc3db9bddbb67c21847803a75ffbad1415f46342d070ce36a2e669178f76c9ee62f7a87e555e0bed496de7495bfe9fde96507e2d885b80431950606090d08241620656892f5423464b1358a5bf0532644370a2a88b47f7a89e559a27ff0b1d0214a479b69701a6a8a8a42ef64435756646000000008314000020100a860462b1482c1a1236691e14000c7b9a427c56990a84519203310a21630c01000000000008608066c4120025949beb342a08da99c14bd40aed6e5ae059c18bece3e31c32031202481e301737c55a5b07996a4ef450ab8c2c9344c846b0b4ed9c39f2e3e8455d22c6257bcaade8167c40387c9bf12f38617fdf5e8a2ea2c7bab614bd54564dd3a17670f89de9fe9a112a22d00169d178fb531749f72dd5877d220f48ff91ab95c5b8268c8f25c178a7404d68694635be7478476851009fafed2039e96ab767e9e83ac46814d3d4ca2b497301e6338da96dea5e2729b3d48a6dc9a6d87dbc21cfb8d6f41ed4a8735b32a40039082e76c6040c40bc1a446ba2c5f59ca560a3c5451c72bd6bfb325a370f2c9d559ff0d6f893ab4fd1f12021e520069a0b14516b6e0305a22945032da57ff829965ede15325b2ab9320edd598e99f7b3e45d98eb05bf6f44c1bec7a7860ed09932b335007cc3d6f09de42cb657c656d9edf2369de1cd2eb564b865d9fe0321ce12d8312c8509375c4f2a62f0cdeeadbcbcc761d82f2f5b80e6ca90018b2a9e704d2c94fcd6a170ad44d4ffa4faf775322fa1579b46436aa18636555991c2c3a06467aa2d27554715e24a3d38b2aa1d61f45d7398236e5d57b35a4d3011bd71c6ca5d175f0d215d1be0ea7eb60eabdea5ebdff72ed14081ac7342bb16eb2a84f3643b2707906d419451504e2ab2460bd8267ba6abe7e26782a7d0550e3d1b826450ce28e53d3d325785889bb5f17648aaa8e011338940c4c03c2be6fa9427e5ada20d6ada7118b4f507f07d0990ce6c835a1d9ae8d8ad303a4cc1a7975b6ce2a9b378a1cab18bef6e11bd7db76dbecc43830b5d3737dee3e5f06013567dcda06c34d1438de4423fe6f985ea5ba9bcea516c58a060cd26846d2b9abe4bc1c98b08d69255f68221b9a75c9018be647c0e5de9f24a5ba6844ed731fba7e47ac6fdb12cd543dedbe1c3b10ff92d4219b935d7485d0b8bfc446c94d299a4f87b9af0a838b0978f3481cab9933c6cc36f4cb86d52f3c61d63d307c9b28680d1f364b9b1c8dab150383a7222ac62492a5c686a9835a0d513a08dda2463c144a490ed1dcd06dc77f16e3c9d0b94dcde994b0f13d5069433403a4834ae2c03f76ac35318415d3f208e3748e1ee9ff10a859fab119769aa2a63ba9a7823d9c72f4b1ef903730a7a46d9d2cdbd77ff8367e2e871594a6e75dd028f7b9086bad736b6247c859011fa399f401c43b8a742cd9ae6cdcac91ac54a1c9e7cd241bea75782ae3bc9e53f9b7ca7c8f0ad8edc40a297681b2d32f574b0ddd1eae80e0eedbdd61d69e3390e359ea48a393985c96d8397d570f74d7e6e9ed7872bdd8f299815a6d5721d05c17845597d8bf9e64c8ebc5d33126c9c35b5d7011ee389bb5e19b8808cfe9055e606eed1795297ca82966dc8ba3532dba779e7f91f14c0f25e8460c9a4ee4f238247a07f5d624d25548d8f0d15d6dfaa3bf4afced947f0ddad0d1d9ac8b9254dc21f769e44db7bba0f96354d7b45a7e0d89523a666da797c33b17572127faf0ff3ac99d0088b7e966765ee61590731521e813f07311b2251a5959c858f9601aa44d11f53fa49a8e62fa06a5efff21a07a2647b5bd85e31a465ec3b2be336f94ed4db158173fe8719450e01f99cdb3486540eb93edb6036baa5853fc5f8d312491a6cad9896d0271892f12dfa7b8f0388017beb01c3faaadfd32a4b189b72ab2daed67499490dd426de2a40d1f7ec5d56271ae571b680d48d341c3269ee8a96442280579123815fd02e97bddeabd35bb7257ea4ec1af879df3f74ae0c01c6f56c7076fe3ef7e05bf0d552ccd2c733c7a0cbe9546053c312a870ee00db5f3ee538a969fca0ce58e672824025a679ce5216e6eeacf3f505820175ed3be7a5bcba5460dbf1ebd60ef02d610f3c3f90cba9e7648b4187455f724c5f9c9f7aec75da96555116ecf3f3fdc218649260d406174def4f2e2fd267e81cfc8d7881d1c92dbf9ea3f5f154e81832cc46cf1cb0344b7962e31fbd5471f728928e93e69d692670a3c84259f220c66045f1cf5b75baaa7cb0e7ecd804c5afcdb5bfc7c4f8c8ed88d8eed88eda0a6629be786b8048da6d3a2a29f2aa7d965a1550cac60aa52bbbd72c665f54ca6a5d5b2a96f02790f000dea89a31fdf32a9091c6cff0c4eb1f6fc6781d349876f8e1ac2b23bdd70d43348a09c35e3b22bba9ac6486b4de48388afd0203ed374b0346ef63650a0c0dc33fbcd6e3d1f550526c252c313beee7f8815c809c0a0aa431bf552228eabf8864a558b2c1139bec6d35a49e2b7d9d3289cb40cb3832960e6ae2a903bbface4bb5a99fd5b1a8bb24e897e62cdaa3518a0424df95d2146611e29742899449e0927ee966cd051005f36cf42e0bbaf5ffab32b0e7b5e892a665351b2023232ae6f5e8a59da1ce9b54d67b4c6ff3b1810cae5b9a154514266a98cf040645efb3dfb63aa5270217648623142fec434f1bc7e85c4dff0c03f4375fc9cec82941e533bd73a889b932f829ce7b343e6be751fa1fc5b7e5f5bb99af0842199218465909134e78404490113e247b12328fd15b10e40b4a9e70ba961ca33ab1a8098923d29288f1ad6ae190497d2538c52748d3ca7c63276961871ed89e44d2d781b8272a8b89eb47f24007038b2fdac94fb49cd859854ac757cd192a8c4346a01c6f3f0e55f0bfaa33cc79463166a2e2d1fe7b623b52ef03e2560e813f1ee2f13c9f125a1f49f259abf95b4add92ed9b2ad2ec303c41789bb1cfb3c9cff949653f6f967f462b7175d4919e65f021189b2c40db4cb6012c6db9462b70690208ef7dd6070191f7d000dbb022c630093cd3257f81b2d2c2e9d2bac7194ea88f4228766636ca966cd997ec13b20c7fa672291090e7a370e3b5dbe826dbb9f546e4fbca1d20597c57c7ba254d4a3b5c6f625ec79561012156c77d581878412c89f76401ecf2e684804aaab91525138b7d42255a4069cdb5d2c4e54234282ea76b05d96b6772ce236068cef19c5b0438ac8c37895c8352cc0fda0bc3add46852950dc1164546f8b853f1099b9fa9ddd38cd922481aa35c12ea5e1bdca18b577f1567d391fc64b7d5d04361bfdaa5c6536772f6e5db87efc213fdca1b38f20bf81d2a7918c74a5e0c6d3fdd4dee3c6db28791d667823316ac4e01b4e407aaac34edc907d790cc1353c67accaa7380c9a937fb58ef3630662d90e7b39b38c0101b606183eb5d5da9267fcd6801bc20d79528a571e3cce59b3b2e8da18fdf55806a3cb16fea53e3728b07eebc2979e108f8d2d9b532423d231e506c0546b2a7793284d9a07aaea5d03ecd9446d6a27f8b551dc233b1d869e85f654fe929477169c237d13542ad3810ef416c39e0e4dec26d093682873d6d909dd8cee81c07f72add41da08950eb414712285b63d1fa6a0a99b1d2a80bcf4dcbd8f407f3492cb86b4901141ade321c9b7147c20ec43306cf6d8d72b2fb3c962a2e1b547d18e239f4b9c43d65fb58a181e09a0e43ecef88bf95cc7858a6ddc8c0e8c8a8d3b3fb407e37d2a17cc05832d064a4779f1d3f346b74ff69f0a5e88cc5b7a56d6113aa55ac246f96bae29a6cbd62131b56973b7bae3bf81fa0ccafbde42e1996212a081d6d47b6807ed3a5d9186d4afe6d31535b3a71dd6fc41cb83f1906ef84eb21afbc487d95f16dcbf9301f03dc10cd19ab81ad9be7bd9a68b4a4e5d70d8d7d77789302ac51db2bf43f53ca3647b4bf3064c51ec0f5f5dcd40dfdd28bc2d94d132cc829d13349c9e6a0571e3848b2b0228222491c2ac0d7b31e56c942f5ed68bea2724deac0271b0c2dd8677f707d4b0636408e0eb619c5e89def5ccec3304378e622ca32ea91fbbdeb97849da203b157a8951e1ea4bda398ff50195f042fa29a464c76126be7191fe2365e7bef6f5adc696f30c5ab448edc05019b47533efd2d1a8e9a98fe2d2f4f71da215cb33d6b5527132174d202ce7f46eed2682782c472200bc296330a43d6164f4a4c0e8841e907f3bae7b61a9f00deeda2385a7ee777a3bf1f67c65f55c81ccbec3e582f22f2897432d2dbd6e071d5bd68678bb4562d645c79950edd76dc7ce9a26712c3b788875f6b7daf8dee73f79737f314c2623a24acbdbb193958ffe8c3f834c659dad64901b437b736e823824d066221f9f296c4b07c961e8431238518c86e4a20ad19a8e14bd1b772234e0568b6c690bd31935380b6cc9f50b5ef9eed3af09fb477359a3f226e6087793d5e49ba0343cc5e6b8e41534c1a6015eeda31e3871306aa5075471542bd9b324417e10f09789e663bae8f6464847544fb56fde9f8810ece2aa1af6df348888352d3d2dcfc0c19efe4445b65721baa97730f665fe1e3dfdd9cae5c7281e553868b48c43cc863540a8eec6422cb36620147aa6a07e8df4865b62bf0336f71d99058aa51aa302fb2e3f26aa795233cce1adc34ea64029ba611fb03070bf8f5c3a619cf28f282efe9deed6ef22142bd61d9e15c360e4df2b90b3e25e590b9ba564e2944a4966e7e334c64c71ec10163ed9c9e45ebe08a3940066b96bb75cab2c3020cade315faa88964168ae278fe6ffece6898f92a4e4820a84fc04da65026092b58d522dda43c703d746f61f198300635f47dd5640f3d07c15de9c05c05bf87e463e11867141e6949cc905cd84de3175e64b8918fb9a60297b654e8828a4e5dc978c3208b52a14f34a234086da23ae5427fb012be53d3d5a260e45755659458c9624ad84813ef999b24c3c05a4f1ac88e0580b437f4f001f7df7c344cc226f07dd43cb0e8832b6a330c0d5b89160d968c8795b246065516f958c9faad7f64532ef0c20c72d393ca7a0a63afd936835c53836e3c4949708cca9bd452f24d2df384ad2fba0636872482b2c72ca73a8272d5dca3081bb49022b312925c717be76cd4b20ba54d6fde0ba3a34ec9f520e42634a59f83ece07341c16e490bd7cb0ee488705fce61b00ba28282f14fe49dada049e96d3ea7c5ee08bb984c8896d16a83aee3aeb7255296f21b01bc43a2dd4eb06ba5e208fc5c2b343b691b96aba210c06ce91439f09ad60ecdbb596d885999b6758b9ecdbf8f96e966fc3af177a1efd6fd9197f55050c0a93ae7f5a4a1c38e855b462930a7282f7008aeb9d694f6e4134dc359085fda9ca36c205db9fd3aebe8bbbd5bec90cdb3b005c809d8af3aa4c1024a2afaf5bddab8f94a50ae23b4b5bd00fb9edb99bcf1fb0f3deeb6cf16ee2cb310f4b679d9470be66b7b4fa11a845eb2acde4687d1e0b19128fdc2d8815c222041cf6282cc4f7e2dfeb3a3aa13890fe4c19e223788914aa52082578214a027dcd12ac9558d59bce8bb718ba3592a6df05eabe9e83256bcfe2a00c114f513ffa81d1064684b0c9f8d488f913d518c08a9634f09e6a0c8776bd68e7868808832a1300ed1db4ab2719cdc4c6e4f8e2bc55702d411c351c29d2643738ee67ed12ce5da1d099d57590eabf96ecea7ed375516973bc9234af21d3c0c197d0b45a662133912d18caafcb44a09a5a3cfff99748e07dd590c5fce43d50631be3ab47936ee1c4e43f171f2b865fab372a60d956cc4025d6c93bb8afc259439ed9cf38f7a7c95fa2d7f7e3afb63e43b904d6f2cd12090ba11dd3ab90d08eee8d49b8fee235238d8d3275a8ff44962c4d00eb1cd41d2dc54ab0a41d7d86d60c93152cfbef5c0ca3f803831d0f1c14100c64a9a260ed6fe43e2dd41788795cab5e373e1ade5f354f858ec509d2b6e59736cb4d25f37749703ce7f6664f47ba82c21f2386b55aa9b427218d1195ca79f8072e7f0dee8d09f0cf7b868d14061a2cfaeea8d67791bb916f7e8d2a87b05c112730a4b0a9c064ab8134c6cafebabf20156ca9840a38e7e53975f061c76e8335c021ef521f7554e8eadb7bc726ff20a4c4297221642e3a7c4923b854f6888a9985fe6e3f1ca48296385683c5b9372c609068569459c9d9ece3d452826ef245008c09814804fc097aa40a7e0db1dca621041bd08207ca708c3c03537af66d52f4608fbf49c0a56e5d87c734e39a451c7c975a07d5a65aaf63005548b6b82b9645db2d6e4133b46cec7067eb8bac33f2dcee9e22479ed878b866b68fc75e51439457c42aec219b0401884a6f8db72bcd0a662f4e275c520f663565a10610135f5f4924e9d54a0a03d2ffde49039c684b95741e0f7b35c173c8f915aff228137912fba39a768cd4575b788bff724102b929155b91d74001393cbe84af7d46552fb7a9c52b92d86500fd41e4d4cf7465372c8bad7d15db2043fc9b7ca2df98cbaa90d1df75b504a7b82a3705423a17d22acc5c8de3444edef7c41e47995091bbf47fa343924752e6e3b51e210f1248a5960a79c3590933410efcd04a52001a73ea87c9a461d9fb476859a4d64a28c651056b2c93ce99e7f422751d21738ad857212e2baea08158efd8f47ce8b74460d6c928136aba46bc3674d853601026ad5fdfb2d485780be8da5b0d82c98714b6cde9d72a2a747fe6eb29389c40fa75c127812ad3afd5a15622fb55022f8dd0bf5fdfb79adbf08ae2de79131d476b7216818d133fecb35f3bb4c5eab12efed4e125daf8f0ac0a48e6cec99c4225ae4d913c5730186a081236616611d094d3b7fea045756a779eeade5c2c38cf99e5555de212e3506614343492f7f178072b698d77c124eb990fa12f80143ea745a46ed73e8b667d6a9e072e3d5c99a821918b3615c7932c85874236e8590a7814a3b9d8f6fb2c018c5ed045f9c37885b5beab2e5b8cbb05c4af5965bbd6e21ffd0ee71dde62559804070273904a0699d07634d6f373743836c68a5c0d27fb5c9e87ee55f6408aac5ea7aecf5dd543218acee9ca3183041a7993b42da88179c1a657f3a43bb3ff16241b519e8aeee937e80d4b00f892ea09bdeb76f908cb0d81e3984e42e8dcc0df59bde0448e5e5bba48437c1cca8aa4cd69bfc138a9ae8ff0b3c352ed1a489dd720555deb55f05ad0bc8a62a5cde8a82746aca9da02342d18aafaa63fde1c98e7ac68a3f832adaa9ebc594f70aeafaac5157d6d9584ce11b5b130cc2dd872dbfff93d0977f91bebfab4ed7f92c13c8ea7b2bb6425a6e7a501c819b76864eb305b27e004786d71a2e9b0b85a64b2a2326c55e60a586adefaeb15aa6d9357b542755b45cb27431f53206ce52c11f46ee6f22641001d83e28b801e4bad813e7e83c6bbe1a6ab456384f19723c140319846d7353b6e4ca01ffb7e5aa88ce57bfb4af38781b2d388b03864df8bef400bff1517482428067bd2f285bf43ac8593f54788b404370c757647705a15ba1e5d940cdae74c7623905d6b5d3cb356ac38badeff3e899a34deb7dce17f9cfc173613dcc9b62b1cdb0672d64a954dd90370d22c6d65e89c77c72b462014370f9bd57b49938dd17e6c4464aea2d0e63124ab89c6ffe10468ce7b23c828bc51a3ba7cc28d1457fd50bddec21b738340b0b5e6b9c8ab8416d01f89e36b76a667160d9a0039dfd885d35ad84264c9525aa531ae66dcef1822f703e3e909141e29f26325a0830c7b4972be562788ac5e50af9dfb99749cb544e0b21c3ae1cb1cfb44254cb8b162973bc17279c94215d5f4479d86f94f8910b633d6797d8a16adeeff1b126d6707c99d073ce089ddf3c475fc2a328917169d7c614d649c541a659155f523e44858fac6a804a31bd1252950804e844a430f64e0a203890bab444fd84425209496c5fa43d5f5452064482029c6c6c018f069d30577d018ba006fb44e4a0635422efbca3586892c0e01d0b35bed3abf1d02d9e32d02c907e9e4f9dbed8e5bf60a28c955dbd51dafad029bbfb829da53a655c8c3673ded35b7901c98c56a904b8bc557acbc264f38771909f90b2a20f79d8f24e37e41fa898068686235cae85ba9fc32e243e234fc3995bfa3d27ff8520bb491c0f356193557716b353a0f65e5b46acbf4e4a14b7f341b9dcd6122f9aba0e6aea55d3db4def82787882021ed08665181b35084f519f86b4e720e159d04c9c9915281e135c3f2ac4a1a790a67f591b01779214bf9b7e6f9f2c0b6ed88c1c907eda0e9cf552c6448692ed9829c2dafa593b32e2f7f20f94c41ca6c8098ab43537adcb9637d4d2e3afbd12931e875882aabe67a1c034211b575c5725fbbe98e4ae1aaec5aa44f1a9bcdbae868366c43968525459198fe89d368482d8d6154c0314849580abc34bfed219f171d08173991342b2b9dd3ce1e298e3dd5c87f634ea72ac6ac5253062cc4418b5c03af73adf7319068415775135d19ca4dfaafcc4a1c5c81382f689458b144d2d5c857a4fe9a012ce69b6398cbb8979b4c177f6f4478bb92e09f033f032c1b817d99e0968ef9f95b8a3b600ff4850c4830c8f226cdb4ac9b8192a138a12d32453786e4be7eb4abe343063e6a6a64454aea7632ec613dc872b3858fa4b2d29b1de3b111d8c1f1121540970999c73821623ba927afb9abd090c65daf437e1760de8977cda703e043f7016a128c2b17c2fb5ae246408ab280827dbf5525565507bc02ca56089384d007cb9dc4e649c9a4f4870137dea2611fe7dc54f09e2d0e2e83b67ec873c64dfd44f7bd25bceb52d08662389cedba8f174db93c24b7a6721170eb804d6953da084a6454c61780a8c4cc9a0ec816d26c19f0cfd5edf20086e037fb3905d490114c107aa59a08a3e09642f8f8197b7a5f121a6c61a3f3d1a3aa7e1fb7dc2974485f021eb0064da66515b41b77e0b4503d6c6351629f55f7be94859f4620c6aef4d34e2730255574883f33b4de348e09fd3089be315e1e775506b75455c5acd7517256aceb4a9650d1a6990f713b44512526d3cfdb064df0b0ce8788ddb2a433a265c67a6bf5a5fac5a5ee633008cc4271d044ca40e994420cb1412dff0b2c65b479dd20679a6ea5268e8a0d33b48530ad1b822111b4f312a4322ae2e4b7b00c2b8c4f1094fa7dc55798f01141d6eed89f88089cdb288315db50f1477f64b48c6fa1819c38e03e52e5eb563ad94a729dcdb8916a4794fb78dfa9c28d182a0ea12e5a560bfd34be1c3c413f13c52106e604b814f23f18ad3826a0109fb14c1adce0345831d39700d6f4f9ef22e8ceb46855c3609b0cc5c34a5fc546a97b74c5f3bd84ebede1e798de74ad54d963625cc00fb45e7652481dceacbd7280c30786067bd7b7228a0cd0c3427087aad96ab11684e8b59865373102a628605471a476023c9e79ca1096d1ef01f9fbc1519c55e19286e4909ec13dc20318f434c047a6ed0887a1a973835ff0ac7b757ccadbf24febc63a53cf5add643da960a5ad78b12623637959940ca7185a5b314e5ca1a9e7d777cd5002f682e05d3fc21e1624774086d5451b9420ad8b3d042e38b90079d78565be5185a68c4b113da0abdf7a758c35cc636ee7dddd53e078d77b8cc54701536b979491600bab02832332def1bcd73b34a441b525ae77aae318aa2786532684dc5ee13f7fc52a0cade15b086cb861edbf21f0907b2e73f50fdd3509799bb117522d3a5cc643cff9d5b07ae50f188441bee420d8a2efc3bb013f582b22439a5d0464919944fd8816edd694d827541a5c8eaa3aa7bbadc27391b82d8f442b5487f7100a186818d67031ea3103db375899b1eba23b3c0f6d41fee2eb18cc795a8c2723a26145beb918792112cdc5f5dcb93e11f748f18cc525a3459f474c7744bd5ea5b1a251ca33f199bbc87ccef9c0a3a43e2b82dedd3102ab17f0dec9d281fe0969c5f175eb90dbeaf92e061b667cf5c3819e1e3d5a91eef7b6a4d5466fdf2091eed321ceb78bea8e395573066d8df8b650215771b9d3f2e4fcc354a4fda1bf4d7fe5901903be8bc7696fe436a72e2e8cdc1205e754af016edf187a8f2cd2a2da3b335ddb47bd83949000d9743dc04ad0621d42471455f2322331e744ce7246dcca279a5f9f30bfc72d4e7de8a02d24930240280457b0cf212f465b7ae951c7d7ba1a54bbd28aa579375d3f3161a89c3df9f1740356e578b1b364aba992121962e36f2b96d4178dc1142d1d4cc5174e9b11030f6fef82541da218bdc49b6b115f967ed929fb019f2df5f51ea9c0a59fc6beb7ea99c30d08093d31f49ea9856bdb17eb1a73fb953ce362203eb7d3cde1b8d1a55f81233dff4a54ca3f60bb856793a855571004363099abf71a092bf3ea3fbf7f5b7241379a3b1347c603b0489995f60fd279c973f6b9f9d8cace18f2b7991591bef90fdf5283c21e65ad024fabac0de9a61af0fb69f268e8d69b5b5e0ae4001907a8715eb5c7f5c2d8d95830bc02ac5bbf059791c0fd28d1d2e2f6ceae94de1767058dc86d82e7bad218af6ba59fb1dd951974c274a43de9236d7f5c60a4394104c85b024e99140ec6a5baaede28bfaa256336b4274ab905c6e8bee5e92c55e8dc917c966015c6315f07c1d8c822d5dd036180a5dd2db3f6508ed8ee32a6a0efb0a4d1b8c59f26981f70b811ed26818c7004fe0ccf44e758c8c28ef019448be469422514de3fe4603f0cbb8dcd405029c7a63ae74a886e4ed392f374f742d4cdaab4d326160d47d559b8ec21f725d1cb9827bbec15599ab7191ffd09b514b246b7b50404b68ae576ccf2fb6198ff0ea591a7f15355e43a11af4c61625ea302a805b39d7fd8fcd97ca915b53c989655b2ca666bce5d2fc384096bc3a55d4e0d9d79607e829a708e888f7960bd6ee06c2e4e11948c2d35afc04b88cd50ce4c06ebbd07bf54999018bf9b2fa092d1e9436b4c1f0947123b27e5137858ab55b1a9f8913f3667d1c588d362065cdeb656cb0dfc1b1fea4b7e2eefa2e38ef09126c9a39926a40db466bedf2df53dd0201e00c914bb4cefe797353039141de0269ab7719d9d477b184ff63bb7634274af6fc5169f78fc65d1aba9b8216107bbb67bdaa3bcda654722add7f2b32582e798f8d074c838ae7b8c34017e30c87f5764d82bd020bd72d544a4fb2170061c19ce40d10fe8d11d5892a4db3331f055a757e6b3a42ea973efdf1e1ea6436755027ee967a0499b762cba390a1923da2978706703f28e3fb01456f3d5352c18644f16ec55e38a3907a13f2f63ca41b75383a855119e5d22fd60bdf1809d7e30f4b62beb9cad5d2717d97d700fa14480cbb8d6053ed86b2b618c5b3f680a00518dfc4ad6236c9a4128c8187c71bfab5c0c7568b51b2aac54d62b9fd604dba152c7c2b27606c3f8091367de8d04b703d3c3a2800cbb8b3b1b0cc50561fe09059ab97233a827577a435192fe00e8f1917008723551ac688068fe5c75081e33c62a23786cfed5a5c10b1e9b763cf4b7f384e8a9f382882aaef4263e8d4ca32106eddab2f7c87f7f15ef114577e0f1e0e3d5aea118158415f930e6b0f3135a77016c65f3dc810d64d161a9bef1611b7308fa516d96747743f8bf9a60decb6dd4e9f7ad9df420c25084dad62257f963e49288ee0dd309c854f73cc34d4a53edf54d654c49047b23a59c6f13f65cedb3902a226b61bff68b3b6e64a6f8c5330da8e636e0b819c0e76fd426132dbb12436a4cd280afedaed90e772f3c5c46c76cdb6fe6e7b770a521270dd6eb1510e59713fedbaf874434bd78f297bdcc42cb4c2f4da9fa018003cbcdddfab79b432e77cb8972a130a8b4c689d23017dd1753f7dad7c48de8d85e3933f5c9d718897119891c38d4a3a93e7454abb7808c8e4fc4fa230ef4925d0339e92d1ada7b5cc5a482df4a054fd1603548234a7443bc9a2d7b664967c3b5b84b6d9825e27f0fe8f3626ac41985f54807237a95cb2a9af87b01a7462cf43556b902a33b335cd17153b08780528d56871402c5a3107b1f40f1d62548b38b53346b7434ac301d5218b2a5b1a02078a1dfd4d75a27499398d1584dae3a7627d2e53de5c3b6bbb8222ad23a904863d68982a4646b027e21f6a8d5574398bd6f30a6c913027faee3b880a4f5c038a0eb7d3af133f96273c64c408a4001870a93384474678001a88789795ef91a3f214823ddfe8b833f64f61344e9ef3c8923fa1d2ea35927e1030b0a70921842374fe8cfbe1379c0d23c06024954fd4b4b77c98c8ef93beb96c6b165aa1669d32a0897eb705a9031f9636fc9ed8e98610a24ea878b8349198945285005435485833e707b3a91bf6907506f119aa4da0503c5dc7baee6e855ba98c7cc33abae0e23dae57cb8654e1e90fcbaa1ca35697a25061d225ba2b626699b4802515fdf271f324a3af9dac3fceeab0339bfbbf00934912ab1c05f80cfbfae102cd39c978437c61b17d391788a0b0613b86b51d8f5079a32f8498224025e835077f57ff1f055af2846245427d4cb4fd97e6c526a9d7c417c0f6e051b2e1d3520a45b6865bf53b05dfacaddc4a39ab1ac06657ae2fa80ffd19003e2448de43cef866a44d59b0e211264338bca0bc480308fee2ff9950660a2813029a85836ebc758abecd548fc96c020eefd2ae1ef93435cd692b26b72e3ec803eefaa7b8a908bcfa74e939ba82f50a49a1c4e45ef0b6b610f37911f8d28d01f455cf17588d9db97c47780cefd4d32be7c9a4309d31fa2710d4d0bb3c847e8b43cbae682a2974c01ad8fc5563954ec75094bf3f4e55e28728bc425160f4e9621a3a41486dc4c641c08ba3bf170b715d486e355d5059277007297c7a8dda904aab666a411a92a8a88e7314819cda8089653c38ac789fa7e1dfa0bcae9a9aa3eacf03368ca0f59a47e94af0b09a564602734b3e768e48e6b05eaf7be03586a5fa03c73121085295397903994b3c0238fad942ffa17bca3a774abdddc278de187079b60e00a4c775409e3b61dbf5a59b02df8b77ed1fb2ed8041dbf88f9028e36c78b20037a5d2bf60b1d8f58638e3c743289260a5bfcae7946951cd640be6cb80612796f746cdd76977522c4126101d3f0bb0b1f94bcbc9728cab904397719205ed21ee1ac9409052ed54e3e46cf391009956f1884a89c7f0c2b4dce98a5419cb78067dd7a1822b46777dd07945d68d4d7d34698a1a3f7ca2c4998835212e4ad403fdae85a02c45e226b8318abcff5b791182b64c1c90b118d9c29d91dc147d5818b3681154d32d3b33ff8e56ac2192d4052527cf7e8566b489135c78286ae6ac87a295df5d6561a9eeb3038836b58f8a563643afa1680440925d5398883f10508741be320ac6c58d230f8eeaf63140d6c60ac900d24f5c6b5b60f6d813edb94c991f5676157320f3db6fd33e2a8ab87e92f7f7c7718fb507399a85adafe3942f8267216486910f6787ad1b455993c05fb075a0fb6f048d01a502d8b9da96b3752356a944339ceded31007ea022e0d4796471fe2661040587232758e46d6714f81fa53883bcf0ea5590a9ec9e7c4b94a6ec399aad3b81f60e8a2a07f924efc52701ac53eb8e25718c301e16a373b10cd997f7a2ecce6f270ec04dd4c27a2c5636b6b20889e4e6911108764a8e48c2739d83a4e8e66f95a9121899625efaba6cd348aba1fe5b60ec1d6bd2803cb5eac75efab6472a9bddfb6453bc98dadc6c246a1152484c31352cd7785baa371a8785cf59f64fb0e840acc2dd9de1a64d17903e5cc707fc986271dbdba632caf479829eeaa6708945d4003ced1017b4769ff7038f83f32d21b67a3ee778fa1a5d2ebd33776dd796da8466ee68d07f35ee4107b61f00bed982f94d8069451b9b042e78e5ac365fc4816c2c9923ed63c483c7d639aa16e7ad4fbcb8354f748bc3bb67a5bafe70cb48ac175ddd9f9c6d86d58e0911e9bb5f7e9d598012f3135733a0c8a9106358d429ad64603bbf6c57c5d0a3a391c591dda9168bd1feae8de23640a41f1d7d9cee48798e21d0fb4a8d7cd8d7652d991a246b6d57d338d7f965d7e4683f0ae48eccecf0a475245f9f33bad5ec4c688943553a0f94b496de195b5eea86adad1adc0afc65dda9f70e43d853a184df9eed774a322a97d8d1b82304263067b17a5d50928baa384fae2bc007929c22c05bb74e3c7435330aec68df4a40e9622462881a9fa160a6e9493aaf00fc665c2baf1eca494086ee05b59cc74e4cfd91dd0818fcea0f53dbb50b75965b402dbd1e82664a592af2089980799ac343ec0b70c36754b9f7c82d237008b46994e8be4c6a00b545bd1186e370dad3e398e61abf20f980dd4eedf53003f9d3307b30ea7105590a29d2fe55e02388339be7d0cb8a243fd572bb4197e5fc5f5b912a69f09b6a5294848bdd19a652da4fd5c638d9d7879c04bdd441cca7a25a80da135373b5febeaec2f36b2fa4a33d143679e4eac89f15e2cf2ef34550c590c40426a854a627bd9c5ffa2c85cd2e43423ae40c1764a6b07acfe6be5040cc3477250b9fa725f4382e57e0ee2babe440e36434ab19b0ec2f001cbcf821788c2a02e8b2a23b525a7a7f01cfced901640d87c3a48bac72169d92d2c178fbc4881c1d8e2a9f1f411bbb578950b6c5b34af6013cc16af496938044b983981a97ef8031371ae8f028eef7d649f4a0560e70153ff6ccbb72c807b1f39e473de24b6d27d7b6808376b7553223fdc63ed22eaaa595c26e9f3ecb30fc6c9e1586a14773bdeaf2da5c6003e783ec65dfc25bcf354afb64368e9c538d3708e5dc635e2b62ba7a9f62344331510b7652f60e04fa399fe5723935d44d6cb78be7ebe96b57e7dd384d61e2b3cbfcd739dd13ee46959a537091d60b373acef04a3889286789287b060ed76d189fee6708c5f8f1af9119ca241f6b934f0b9a78d487f72912d943ee74fdf7267f2670e6ecc8bee4c2ace3dc3d6f61e3de2e92d993450d10cd5e29ac7e91c0f47c4e6b142258e075356271a64b122348e072762ada3f11899d5b71c8c0680a6b61aa142de67025b57640e35c577d937953d1791e1d34ec1529e7ec01535b875f87417cf4194e36843f664d5c7f72aa31765900be30d760fdad5245d4696d35a6cc05ea853173e50fac202a4e2d903b3d876b34f6ba09ce6eda810b6e5e0d47a664072d09967c18f71f37917aeb9e47cd1c5d02f5da2c0618aeba171719a03ceac7f94aeac2f10363f09a5cb8a3240703d1b6afe0d3ea0d00a72bee9acff19cbac28be12012a37d55b79fba3705763b75d16aac8209bd164771411b3ec96c0e7bfaaf604074e71336e1b60d8bb53801ae258c2d9659d9780514b9319fb8eef09bae972ceed2bfe91f7ce41c861b3c186be4119306f94d9b0a183ab0e0a0c20dd5e217cdecac65113f8a7c320e177544e6eee8b3ff4eb76ca1196eec3b722bd0a16ebefe705ef777ff4e86ed751c6a7f80562f1419b9e7dd01f7f9c0f7b919407316d824964ee2c14663b3c3b0602565dae13059336dd9afcfd9042c4af74abf25b5ad9e4bd49795ef3e01371a5e84084494823b8f93191d14a1c2b9d059be8169866222ab532b233f7a0aaf1613517cada50f71057d6880e118bdca0d8f6265ba1f8582faf97b1d04d8d85e26b1b22f3a501ab930aca7129a05aa7e8f4ab2d68110d0f9684cdac3af5332c753213deaa98a7f079eefce267cb69f7c67634c74eada140b30b3ed83a0d2f8ccf65e73d3259942d1c3dd5de409608612869a198e21c51598cb5b07d674daceb0df4a58640716e2e139fcfedf43da2de3a64a4ce6e7a8f672f3590a1a2cda3c2c4edcb1a885b09343439a15c62b11b26b44f4ad05935722fb4c98cdae873c9be541c207437ffbe2b7a80227b064572552f878e39f838734fde73ba21d8466f8d62c83321d354a10c3a6dbc81ae53c57bf03dee816b7de38c4144b98983e71d9d05fec4c08c53622a70f80c9b268928b659aa6cfd95fe6fa451d021789a9630a71f7c2b1d20e73624d3e7892591d9cae0654052e5caa40aa689c66b111f6e70ef4d98382a0e3123192eedaeb23de64a2f22e68081e533cb840c42db55de4a3e5646c9e0d7b3fb842f7a640dca6fee3abcbedb40f6ab311494961ee2e823189bf6d8aa91f46c333d9f384d3cd43ffbabfadefdf7a7e9a6f7baebf73a483ce68c775af34e846ae009318e83646edb920a31ecd4ff3bb0eaa9badfe6a1d26c66b86ee69e23c67abe4eef8638a6abac625605dda1229853cfc2c31fcc562179a6078db6a82a38e22c3cab143ecf1866d9fc41d5ce8cfbf1cef0eb67d989dc40836246e5e88331b479959983f4ec91d24061c0c88cb7f03c78a1b76e4f9303a2690f0308542bef979959e06f78a4152eca5f9899424b086320ad320025b893310c4c36aa0be34532d9f025a88b5e11e2b4b986b90c4e0511aa0e2c65d06de49d85bd1d7823cc819504f80b6327913e2b8b5aaa491829ec66d9b75ba5ddf5a6bb6ababc950d1046004fb04ffca53a79d8673d695468bb6cee9eed83d101e0cabce36add74ba5730ed7f04876e862fd07a2998c14a0bfe2c9d6d25bc8bb8873707e97197eaf78390a080744790bd61d5dece8360f156da94a3a61d225f68c92e2b0da4a59817bf7f81ddcd16f3a20d649f82cbd38e18637da7306f8968aca7de2c16072626fed7cfff047607001a7bdafcdb52cda29897aea74c59402564d11e9ea0edae44872ee60a8011867cc53fa38401594a3d38a32e060d84c0329b53e1d9a8190a9358f4863544efc945a1d737cd77f5c6e598605956b7c9211e4d8f880b2d8053511601d6289cef348e1f9727598a21a57f99e830d030a2db74ca7e0d1cfc621fc81a05087dedc0b7a3f4a302b63e5799e50d3d9de36534afd1d5e209d6756923916010f94fa44f079fd8f6e6f27645bb0abecf1eec911e1204f529db990528d435293524904cc5362a533d278ca267085ddd44ffcc2a0a023425dd41764b719cbe5b5c8989b40def1417caf775738ae660db10b4982064272a70aadeb92dc7720ca6dddf8625ab312058ced1922592bacd722d17566d4d8ef5212210a86c8c5910aaa06b43de1ea53aa011450c12e84bc58a1acf17b2a014a92eee2b8505288b880fcf8ddbfe7da413467a0a29635d3a6e4a9141bffafa63cdfb21d124df06b2aa82585ee1f178722d4de0931e88aeb9b0823ef4a4ad0bc04c97d02358404c8075ccb9bb71364352ac4a9223a55e009f548037d98f8c6f0d53715938b3fc607983a7990531605ff47322a511a7b7507eecf4889acbe8f2957dd5a2a53137b4ef3e920a2769834e23931f45f70321dc7a425b6b131a229a8c4d7c1087699c1814d751c7c806eb22fa120e07a3f7a59fdbf51c00494d1d9cc947b14ef3103aacfd51a0268469c76a628182e6b170dd8ce36f52b329f89543d85cf0d0c5b87ea24384f2242bcc35317d53e0a91c15d1c921657c05e1098611d2fda349760ac815b7c61d2da030e67aacd20358f54c255add543f22e5afee28bbeb969a661934590ecc1c3d8cd66633ed6207c7d8605a2c9d72bbf8fd6cb6652f37b51120e634c0a438938dfd68ddc941519e8545fea41fbcb2e65e77158f7bbce6c561a951b4ee6bbab3cdbb36ff2ff8e1048422aad5099f9617438ede1979361032887ff2ce2c2f28aa01f692116994dc05450f572ab435778731277000f48ae60288cb01609ca1ca60dbb48e35c1a85824934c9a7b8eede5408e3714b88fead02c511c162502bc4d0c8f33da4166c16acc1884336e123675460d171c33f20955c894bf05737a68e3081b5882f7441a2625daeef79ec96b244423a4f992628be30db48815a9dae2eecdab262f1f9c54df3603a419ed5fd8dd7e0353181c3035dfda84a06270e3db5c4e2a3ee695b2bf5391bb2008094dc4bf417547e0785c17ea56c1e7abbf5cafb40f9c428f8cb35370ab5092b38bd2b33cc88dbe85f7cfaff06771e4fe664cd89bda8f8a9e4695f4fbaa5b0bf148c6373dc77cebc0d8009c05031722303e7ff6e4f49660202ec20d6d6924d8b716364cc8ff60c837ad1086f484fcf64cf71f747bd0a72ab48b7c8ff61fc403adde3253fe6b6f8a726b20c9ab2b8efe03e6a22dcfe30f004b2cf5219f79fb881931be2dd5a5236b3bb3413e02548b28ad729b6e263c444c41d42d4162cf4e3c83e1c6985e84939a8c4c9f5995613fb353ea5f49acb808fb872f2f5d2382f451541b6b0eaa46057fae52c9d7ad15b2d2034ca8f82444098cd644d68c4d5003622b41cff80eaba989a54f1d9ee1be8515cd41caa27c85bd30875ef752e2e27d934f9f74d2d4eb94637968ef5a420a63676c83c942fe03f1dbd1b2aa608faa1a1093d9fde94e3e835ae1c40e29251a85e4ed40c2f5b7a2fcf62768650af7634c8153203024dd6d63c3d085c0a8ab720b66f022d1b9674dfc98e0ea5fdd854d48270b796c935102cc32ed0a389e79cb3fe3261537e741cfcb5209c5d4725161d2f4cb199737debea6b7497d5883567c00c967fa432b1fd511822a907ab5019e2d0289ce79d25561c95064a3e73bd54470bae829825cf1278dee01931314fb3ab93e573807ed7df3f655050ad51597e4d494aa55c08775be61c6c62ac7336454bad1bc8b9dcef3f6b8a88107f8c87dd0ac3c0d5372513f5887335905743d39cd66f20abde60bc0b96623dfec7c1d6c1962fa06495d9723946b1318609cbb7c87c1e952fe8f61beb352ab1f0b4b6cb14e970f48a730b69ff7be4afd9000572c6a83bf45168cb9948bebece7098e2b02e89b0e00141ca8e9e7517c3804c3f1d2233509100b5fb01138f8e50cde1414cb5d33fdaff786acd6f64854ff0d635bacff49aee8a62db2afdd05736e4ccd26ead9b99086bb046250c0de2a53b12a29e81b8f8241c33f737b506823c7259e9eb37ce7c50783ec24d858cf5503b8b044c962af1be529c79f2e1de78d64a728d02ec4e9360d7d69d5bbdd9a13e2a865e9cb6f9bf2c09a5bf98e7345747df37c335dea394143d2fec9091d3d3d237fdabbb4d21429ac60a0b83fa9db382dda7f0d444ecd5ac1009aec380b5a7b6264b9c4ad50b67575dac4a28e96686004f2371267be1cac849ac5b900cc51370d5d6aa11aab3d73dc102d91f329a5e94a60cca5740a9e02531d35e7231558b28fc6455d11b6b3e88dd9b8a59160dd646746125cdae8f19ccb41a47d33083e93073e67ab23d4a04f5c2f76d4b02340183720f52439083195a2964f8b661b7fd05146c1c6f783896e3b5cbf64a24aca407f3791dcf76103bf47343720029dbe239d897bb2d66d766a8ab1710ce4a1ca111d11e17ab3f29168f315d41338845bbe3ed30f5c75a0406c1d3755155a9860511bb05e8b5410dbd6836f0429687d10ae29b2171d0c2b8be71c06322ddee6cad1b6b6b3593a77a724184499a59a00198b83e1ea944ae574f00753d64191bb00d69db13db487946f03d97a9aee4b3ec6e105a0c771062349ee4d32acb30cadac76d5c68b00b26e956528f2d474a188a03489aa88744fd630f0c889b512bc993046919a97480e96ba2dd6525bfd567f95c5588e14d5c2c540393d7e11c9438e07e00141700610229138e94f92980321ba75df15de13b17d10e4d5947122e217d488590ba802142d1069ba847f060bfd6de1bcf10cce23a22515b86c6ca7f037aae88cecb0e252c5d76c113b07eb5a463ca95028861a0890bc3bd228cb77efd30b68fc8b134492b42b5a16b0b292a3ba2247d457844ba775fcefb05bdd7771fb79f40bb60f48de308a953c093a5b3986c06e0f0ca3785e0060295a59529e69007c7a688d815deb50230adfc90c3ed1df4a9f65e27c05a7272ba58f5a9165cfdf07119fcc228e0f5a32eda758fc690ef6a65b7b887b584bfbf54e7fb559202c7a6fc628c82cba006ea866cf288c7d4990c4ad44b8242bbf80f3d13658f4f1f8397349b61ae4dd616dc24d0fb3fd0df0e267c2409806439d14807a0ac4ebfb84803c3cd0d92b0ba2495fa4f8489ce8057625be66fd40cc99539c98f7849193aab82767fd2b4badcdf70d57dd86b0f670d89c24a97da7eaf510b6a73a95592b287824eb7cf21563f0f8630f767e80c3b04c202ee36580fee19fa80abc78dd28017a134093722db8b5aa122173ed4d1eb0765605bda2e82347487c78e065a20c52862cab7d059dff69c5f01ee140330163df3fbc36f989f9dfb39868fa608cd0dcf17b9e3bbe44b4bb31b790bfdee40d8f62d1c6a9e8674ab6d0bf11c151d4dc4808ced08fc03094f0edf5f73123f877967921b0529148db8f49180049836fed2ff509ab28b58de46010ecd1603680f0a687626ff97a5ffc01f63081d6063f69385b5c8b40c0c68c1efd500410a4292106afc743ae5b054936ccab21c7946f899678d57c8ea324d84228c2ca0b24b7fbf3354bc8d5697571ce0c47744492a2e906aaefa9bd2ac2084e205dd58e25d5427ae63f65c905c2b7c3c6abe82e8380334204dbdb66c32722f645b8d0e054d27abcd5773280234feeb8c7fd907c568dedd247c2bed67d547dd6fc58faa6486f50f4da612bbd9cd68c4d0b24ce2df10a2eecd4183753131eb191859768ecef0c32a5f9e26c8e85b068c9a922bfa9e216304d37c2c49984d16450e0d2584440f91edaa6ae7e6e7abb661f436758f067a9f0f43ca9b82c4edb5583aef9da02a6d0f520c3655db6916cfd8a3d8de73cb6618ae56ffdc2e16de5e4b3c86881d48934b73db6b47f144e2d42659034479b1b398fa2ad90ecf6bdf22c923de9e304070793b4d32ba7f4f05535aeb3a0af4fe3c90442323b331a5416dcbea2edc590ea359fbaa8982e73e28485e86401edc51b72560afee7c4d79f87d7e6e1703d360707fc01f17f8a23bc24e98d19d9f37fe1d19ddc2754123e878ba26547402e9367359d40ef916bc289f36a5b63bdf336a63f4e23f211a3cd0afa6763ce09237066874ccc77ddcdde214e406275aec9eecc7c85ecd666d54ff08a508af33c958cb1086f0b63ad85815eab4617d34a71f486d835602504ab82ba172b559755e6a200fe09e25d55b12d49ab4db4f4f5d134688b22e755a7f75d6316f4edcd88f87dcb8a6bcc9eaddd31d9508289271d64c956743aff3f6666f80b7774d44f5d6b35f10c31f47f6a420450c0ba0f235c80f6b5078dc5ea1abce0cb0144cf60f5634e6c24cdea5f4140b581448b6c70950995fbb5d96499dec3c12dd1260f26c81e98ce7a6dcc40398b9d1efc62c46908768c6ce60b04be82c2510f5411ac9898bcc04f469e842ab79b2abfc0aa369e9d993f37457a2adaedfcd84f52a127b854b038b174e6fabd668ec33cd8540aff6c73381732d54b5bcb6ceb2ef10336390e0747247032da9db6b275f196bf65f80d0d1eb6d06713bbab48bcef4cf78011abaca99c0df1f237ddf34a5e09a75fe1563e09425e2e4291d50bfcf6b85dd25fdd9d0174f9e654440e991296c8c395ac6bdf03d54827a437cdf8e61b7995adb6f1d5cacfe2faeaa368be7d37036b4d40fd91900ad7ea668fc0e614417bb16e62111cf5be8ccfd95a17d877a6c900fae797fa3b6162824adedf38dd59cfa949e797cf8b8bc5369bd3ab017d2cbb95fd6637117c08582a0cbc17ffefb5307a512ac322d9888ba36bf4d8928241cf69e0f866146accac95aa59a7e06115f5db1c732e7198d5ee86186709000915a5c78030966bd95c6e53e079ebff20f65f7bdadae2348880900c1229433ec0c5ce1f22c7302a32cbe860549dbb89890f917ac4141fdcb2900eb0f840f1bc1d8e083e5d9dbd3b38a12338345581e53068a07be6a1bc605617b87986124952ced9c7d199b4eaccb366dd4338b70248db90936bada8a417ae357cf54bd41e70fdce12f05eb40b47bb8bb066a3e7b3bafe7aa82e1c32f14ec3876d9f5e0b54e66fc603929bafeaa0799547265e800859f28a0671ab0c8588b015dfc2a4149d09dc8ed197e74a58fbd597ce28b43d56d8a126e508db508dc28b8da17a6880368dd884c0d1276cb9fdfaa549ba4096d30b58c41ad1338387b3479b791627ffcd9c542a65ba49232d7f94ef54d18c3fc60c29d0187e228387598f15337b64bef3f3133e8bc367a01df3e65efd14c8373c245195380ac2cb2b8760b8278470c4b1bfb75fcd9e523f5a69d9d962b4695b5f659afe6d24dbb26a0b67b857083f7f0f718a0110eb879fff0c79a80e5f72c13b64e8e206a4b36259d14fa31cf01a80617e5deb4ef092503ee1a01ad90e7115ef89ba7a92c55196d0509b6c6c15918d738fc1326da169974f6b0da7b77da8ca462181b27d75157b757490457220a7c7e93d31862e1230c578f8f11538a54148ac72338db3c11c6e494345f7bbd3ba0a486f151bb72402f641e321251c08b82a2bccfbd4d8d93204752583dddc8fb36dea1a80fda6693d38b3489ac43440f55b8337d5b3d8412ea35480cab494dd2cc3a8bfd913492750a35ea78a25bae3221590a8d5a8e054e53c9e926120c63daed779ca354b86dbdd5d09ed5fcabdfce30c9a53329e8517715ffb89d3a89a331d37ce70673158cc04e5b75f43be68d78ddf9e63b8b35435bc3812844648c36b4a836601cbad9bd754cc0f875158a627e48f6f316538fb309532b7fbc1a3051b2bc7c9bad1889397415ebdf556be1c587513ad8b04aa66e5dccfb36bc57e03853f52e73bf1b4aeedafadf7756fe07ce66fcc8a14cd8af03069a0e5e6135045f0f8830b3cd3e2e5f23b91378af1b2a22c5b4340fac8db4456a1b165d76975caf3d4c263abeff92b6ee9dd9cc379e8321258ce4322bee5b7bee0ec05357568db17dbf4df684a239ad0b678c40147e73faf5ddf2440e92142682ba82b54297ff48f1a8839dc62cd524a62e932e39d0eb2cc7db4254f210a70f80640df0aabcea7beedc496baa5f87d2a3be20124f456a1262990e93d1e850e0568ed0354f4128424504d600334278146ffc052846424e6ebb233bdb1c561709b182ec7a67e371eaefc4a09c650d7785c588455d3e5c6f2f0cf70799278a685419eda16fa0adc03883ecb9d71f53d3a353038b29fb48fd6c4c804b09e8be4de9f0749e18fc80065694ec1662cf44de97f2fe4fa9fd0cc32b04279c3b1a3bbb18924c98b524a55a7c5f4053bb65713d07dbcf74182e8fa8a7cb1291209a58deb47997b5fb5f281f6028515af21904215cab456e90c004aceac3e2a1757b8a8963d9a271a80e1856533c3844253679cc8f495dda6addbc98d8d8f0b1903b797e6f8d6736902e535e5d7084de8b98a98a265d60f091a0ff8f0415c654877ded29c9eeb95b9aa26abb6c7a21076ac8db903f358e94ce38b0adbe7f61714fa8c028340bcf92b80126181f30a082d88ac0a07c42fa4435ee0dee6fc6917dc0a0ad0db573c024d1ab868d72e876b0e428624964c952ef79fef55f85355821db4fe1e1136dacd26825abeef7971ec42509da3370ea5ff31971d5590e03de35096ccad299602a55047c8ab3485d41563196c5b4d3880ab605c73eb5eb8165ff5ee5473820e93e46d42cc71f700982a32ecf64ff1a565e58ae1a4649dfab17096718e6ff5df6dc059e9c65c4538072336a6ee1ab29d9138d017da1971335e8a71a433f6d13781bea4c80279d2656c0fae2b011198cfa13b0989e228fe2a8c29061d85155d69b51814235b03427276ee228c79a9ff8a3005e3260a942392a9b62ac89181456c40e9665a906e6ea42ea99d4a632c67f6c7958b10662b01541fa53b2e865f3641515d2a685d408db977496d8a610f8977abfbdd27d941c2add24edb67d0d86a7304cdb9637b75f1a054d228c600ff5466a4df03cf5a2b53c367050e8e7179be9e76e8946b9c3917d146c43a78af0756011af07245aa3d74c84752f9b0d08024c532b077221ee8e695d66e572a66042e65d47a505dca98578c8adb02f7d1c2a94c83f459d58f5ed936a9274805dc4ec5d29788edb8b6818d3108f0b73f733a623b08c15e176e8a40cc179e8371f04a55418fc081e5a9e9d2dec90b69cfb0ede5be668284e337c7a6cbc3209698fa366c5c413e3bc0b199c31409c729abef337ee7073b9b1e529d3ae0418054371e9da6979973be3c56b36e12c85374fe238865cfcb55e5f30ba80ef00d2bfc2c79022c8fcc31cb137c6a878f9969921d604ae98899e9d519c75d95171073f12adf00a2ae17d0940407ba4cc0cee2bc82ef564a87d5b0701df35e417cb9a113ced67f0af049aa5953c3aaa3bd40a5a8d56c8280c0b12d4f0a3ffaafae3503637c889eda78e7faebe83bb2b7c353ac04ad8f36c3d4e587c5ec1e0d63c2484b0aab8b297ccddaa1987d96be1209facf100166d11a7d17203d6950cf1cb3bef2ef2d4039120464f7f828f3f7bb99bf82d170ffc320f5f5bda400fc6a7f300e446033bc5f7ebbd4217d853f379fe1b197a3e1b3e02166f4d54394fd3a014b1ef6f3eeaed1cf8a538051b192df38f161a1afda14768b792c609e0b612c2826995414413f65cd5c6f827e922e8b8d5cdf5016dc62db320a212aac3d02a4c3cbd86b6a4381d27be74b4b718502e89886aa485784f20d6eb07a878886e2ced02a9f34d42016b8114d55eb0ce2efe413d6f0336ad4294fa6683acfcdb79520688fd7a2db9fcb9f3e430a24b3ef359467b858b3ac4aae79ec836c62184c2d666d2b1dc3ccc21db6dfcf566689f87e5872e7938524312c563545c8c7c187124f3dd1d4bbcccb0cb654d32efe785d4afa96f5a6ff9879ad1f93bea579049e46edab020ab9e30a9e8e9be68f76e2bc02c51b24328eb2a6d0a3bcce9e4625b0a9bab52b203b50fceeb063d898cf3184f400447443eda016f1874631edd5311134d726608a4523e091b7b8b7eedb22d9826274cf4700aef3f177196bfc1f92e1042be2439c77b4bbc1dee8e5ca28a69448a9014a1a9fd2d5d14b3103bd7cee397fa9d4e34f097f3491a49d50a5fe66e00e92942740839994ff9a88db1c00b8e28102f61d2e0a37e228424a33e8cb64024d34a8de220cfd2c51ba0091c2a227dc84714a60f0f9e4a0fc04cc852542216c5a756e33c914e18e3a297556ac19ad2d796e633de97daa53ed0d66ebb8153f8cd2fd01b197f9c8f3bf89805c694edf192fd134eb942f37820971df1bf096eddbe609563e7637891ef1ffd8c1f557afbaadc7de1cf4545647ce41e51444591b072431c4ec6cf094c2716c365ed2498a81a44f4f10314b15927dfea8c012dc089549f11800b76680a4dd946003ade35ac309a4c420a7517cb1399d5f8cfd91ce8e8d92dcd680b66e6dde5902a4f86df016007d489413689c72c2d4088f5535aadd953295be45b2edf2282593b14f9e77cdd4af04c2d7473b1d3b02ddf76169695ba6320271a1e0d33a00f50c024622c7f6010ece88e03e0e238af667af9d72582dd973e84bc5477593c5611cde8d79285b41c7096313bdb1431520acdae2c1368a2fa5fc7e2012f1d23d355c2bbd319471b77a22844af1566f668bd54b52b4500bd8887a037a843b747bd46dc97689bb32b00fdc518add482328b30461954db7edd1b158a3a2d97caa11059a1bf397ff81b971bd24e8ad96067edffbfa3093d7e9f34639eb5551f3d6e9bc1743d8d6f98a66c9311a14a86f761171f931200ce45d62c34341ff4f677a24b563309b3441b799c2d66b755ed84c571ac1a1747a8e2d460ae239814c142b6ef7d0f1328e11b620b956b5c5996372b9305d001ff4bbf6197fbe40f62d19396234191ad09cd27dcfa0a1410ece3a7355b4e59a6ec71da467360f0a80b9a47fb212dda2a3b2823822511289a69f3a48f6b913207d55a2735a07333f283287b4177c55d1a309be5287b23b94b4f30597ae7a843797dfc04d9d32f3826138f772116631bcdc1f0f252214c0be07a269d9f92e09071221d12e2f4df8e846d6265f78dcf95a07a6a66b2e054cb1f401ad7abdf81bc410532741465850b06d00a282c81561daac2a7945894d2e9ddb80ec7e84dab9c9444ec9a48aeb0bee07eecfb87b305762aefe45e613a62b2e1b5cd2125b945bf346aedb4f1dcb01ff20a70b9d55d445464330a0365ca78ef64c280c25e0f2dfd1f90e9b3dfb8255db412b2793cf91a914d66ee574d8c5481275f2ffc0bd20907ed232e40cf572f650f3b9b20df3c1da05cc511eb1cafc01f3460520d59540608d83bee506c9d49c5dae91f91cd4bf6af4e7ba7ca1913e0e3d22f3fed6f9fba77096a4bfcd09b47f26f1ff64785634666c682e0a2113af42955a70d68095e9567d8d65a7af37ef9d705ad91428222b88276c01a82e425c044cceb2e4d950d3109d1d1f0497bfa5308782de55e93648329c06aa7c4d0216142a4371d0ca5e10e08afc4eda47d343c37b63dd056e5202e3d397c0d212e66a5f1d50406d8c0eb047a8727d7aa0a6f5c24f27e3be894a262862960318f1e3b8ba8a44433f13932945e0108d3ff43439f5743501b415c76d031acc351ca63299f8987ed37e74d7b5365d9057d7fc72a5f8baaaf0b107b854887b09cf2a1c25a684211017a66506fdcacdf4067505129a0c93cdf33143f0cc6615644217bf6c9e21138174877b948dfba3fbef1ab9e99d5a1fdd4493b98040f186aedfef02e1be02e6ee091b958ce8cb0165eaf629331622a5c9705475faced7e17483e220dfa71cf2ae6b04bcf9ba76ad7f2fd8a07d23a8dac6a99ba8bfc929b403f86c64991af57d8c68d8c820645189038691069634454286100704555d647db9828d0007fe4409c9c35e1cb0fa283519dbd3bed45025b60a8e83c487025fca91d8e3e3d828d9a182ff331ebf1bf5a7244fe1edbbe172ea0af4ffa4445a91d168896e42b9f108de91866970c31b6d4d496e00d6f321c933781981aab303bddd364c6ee14978f6dfb448cb4882843516417bbcb6abb0d800d812d2bbdea9d3863af8d9e6af982d0f4e95a1d53ea25534bf23080f6077ca0a2579a5a810dd8615d6ed065d0981a13d10e0215410049c4f949dfc343736edaad8a42d443d0b0ef5b0a342564da2959dcae5d02014c8de1b16f15c0c4db6129e6bc1c1ae74efc756d3d74aa2eadf1236ba95ac256450dcbd78111bc9e38c0aa0c989f6c0d11169ce76ad9f1603eda75de1a7994705748b07e2fe33f707576c80983a3ab82d9235e63dcee29eace753bd8d2e766cb8966be84d9350bc082cb4f71db2e50c1912acf8766193de087a5cb51ae7897a423429b350e314fb57d0fdf3b7419d72c257df3587c919b6dec98ef14278a6c0eecdaebf830875e591f4aa98d9b15c99aff07071b1a98938496a230159b9bcb091b064d44276ac168268246ec19bd67d56453181b12e453ce92666d5ec0b18008d0f925f8188354108fa508834cfec3f84fb836fd40b3b6ca1d7915beb26c88ea44fc9c7de840d8d65cb149ac6bc1655923cb48c449a36e94c256ab6baad13070991588e4e61aba968630df9cf5783a7a0991a8593f0fc7f9fa01769feebb28e8dcdcc77b10a795b0650b956a94597120825018daaab3023cf6091bf7a9cb98ac96078f008731512a3caacf61e5f764d8a57acf0ee61661527361ead048be13be85ad95733129ab05e4d521a1da81f08107e2bfe8bd92533c9d8a756ecdf5b6b9619430b0fb5e4684d0260048bba35ef5daf623938731956b3eacf37ddcd8e268e3bc5351aa6b85367d7da0c0cf37fef0cac309182b6571fdb08775d887e75489865b6380c599551204a0a21e51e7cec8299a31180f4158b573c5cb6d3a8a3bd61f668c36f7337443d231cc60778b249ed011f3c5ac38142cb3e46fc604d40d33084180a7e4b128d0b5c89e25c439179318c088359b3777d2c4443159057c6c5043471bdde99710b04c10999538670a290935cbabc7a2129441b426c046d33421a35e9168b8d4b1b1c6ced3adaecf038539611e5a92bb4657cb937e775325d2e0b5953b448e7b9cbe586af04d7c1aa4442e8813ee399c5fa3a9f58ba7fdc6865bd27df63692294d041a158343aa41f4686de6c3e92630fd906b4c3b16cceb174e2ea7e58ebd7447d3a3d11dc5cff66820d6a0c7364092aa0be5bf0f8d6923bab94fe830956799e47c1c430c9bb6b6dfc9a649ec90baa78b2b359b288af9334259a3bd8c3437ed7c884fb94a0e5eb7ac86471d57f865326cb15618681198e001ac8822833ea83b2095fea41c8af99be51ad2c97308e88facd973845286a98a592e86f5b93f2a6bbcb72ccbe03c7c21009ed392c86cb1f97b056bad56aede49fe6489ad9dff61d489f59571c88fb64a6d7c2019ae3e980265cb43a44e8da2111a8258140c31e93c5d14503913a705a1c1e81edfa348e06d9c28b4dda02d98185868145cc43d72187082c388ace7e19ee9e2b30f4f18f25c9545830235b46ed5838ed0b2b8193b8058cc0bf5abe9b3733a735e2e61ad6a11e435ea32f77530455aa65c06be6d1c74b9e9d385d4be1e41856f62df0cff507ef5f99717c8c98ee151f72a828472a17e73b66c303054cab0965aca620beb0a43996cd91427081d585376c4f04f58e5f74387aca95daaeb2130dbc08acb4d9b81a4f98eeeed61c050bbc0e59d0a18f9927bc5170e403da59663b295e98a1797e5434df7d8cc39f785b7492fb31a65b4ef8b791f26acc252111837b448fef81b9bf0e6d24b9fd718abc11dc1bbb103a5643d45d504b467364907c7186df40c585352abf72519e8c73abc055a8e44e4d0681cd89dcad907254e84ce83b718a1b643a6b564004b9eb36b7340e834a08ad0caecb1f2576c400be6034647b248d2bd3f8a7c6b037034edf8fbeff6c3bd04f3e952fa3d0bd93673bc332b1abaa31b2ac2792fb742fbfc6d477c02204423ea1dd55eb3a0e9acedbed6f182dc80747f488a4d9cccd280f6355226e6e0473f3805489e77c3ac8b832401011f97b6283996d347e97f98d8d639e1142626365a3f186eca5da5a39712ac1ff366e63f74e8f5c4e13f40915f3e1fee2add369bb880ea668564c2d1748035a13c14f42f65c88732a26859a8fcae53f0d84c593fc8d9754df6639d539cb612b90e9ebcb9b0909caf258aa70fccddcefdfb7e12bbe4d9662936922e31bb927817be35362efaafff252f493f97580725810213b3af048ed0b1089729f1705dc2c1572a9060e1314454b57bad6f3505cbbea99fd98490fb88dbaa56ff2221f6faee10835f6e4584e8152668d1715677ec9c70bac658c2dbae6a9a8f01e6ba59b85df1cc63c0c893e04faf53af156a76bf77c707478cc177b71e5f90f9cb422f1428467b6a7c812ac44e81a38c9cdf192c85cb74c727bb016f9e40385c5114e99bc48d5cc0fa09cd763a9d80b5647ba643b0d85d1e288954c35328f1647bae4a7915db438f2f8e3f6d2aa88718ffcaa62af6cad51d435dff35f2d98c61ed1ee68399e8afdbb60aec14c7baa63ef8c38b03110d8fb44b1c5e9b293cd10f5bb0c7c3973f7ca907fdbd72ff83ff35011d0d1029a63110d1702f9d7e61827adddc5b1068d4a3879c252e494d88c7c0e1a77a7df12934e405e00f4e2145990404c44e284bed917c65a083982e55463d73ee058051ed2b5260c501812236ed8137268cfddf25e5ec40b74cacb8c2684c0df7a19fdd7df1346813994b61fb52800d5cdc878571b448da9320894a8269813e96b19fd3ae5a76a4990fa817caff7b9521724243f889eaac25cc377d6df64c9e501d8d44b30f71aba685f85b1ae9857a23d133cb47196b807d4ebb8766936c28bd58d5acad1ceb31cdb59a6826f25cf866d430c3b34af96d174df7a6dfc252cbb0dcf5fc15946a3b3253380be242e3e40079cd26d11c9e730ed48043047f81bca6523027f88a364ecd66b2c083a3dc3139907225dc66263da3513e49bef5ae844c9e4ba4abd3a2ef82f0c31bbbdff516f9d879bba8a8ce59133464d5043e1b1101a552c9f29661e6cc3031b478bd4e1bf7fac6b79e6c09125ce59988a7d1c1b7c278ac13b79f275fe53c8675a7509e6bd4ab87674206cedde3e9af682da4c76bc8b33292013c4ec41e5cf0e71b5becfb528190b58832fdcdee718987cb6ab679bff3d31809e48628a7269abf99e4ec376cdba18aadb642c990f12d3a017e0d26c617db836d2d0296a36cd4fc189050713c7bd7963750bf1059b73b1614aafae80bd0c6e2552abbfb352a34f2639499d226177b22adfa30429c14dd992354800c4b451a374f55490bd6d02d03663f3f0da91fb52c671634ceafb7b7f3e7499c3699c130f75afedc1ddee42e5940f73ce5463f96b857f0073f31476dcc42929adeeacf672f9325ba53c189fe2ebc162efc1a8478fdcd805796455cedf8ed31e87b932b751b006a33246cb2beb94ebe44a9257a5983fd1a9d9f317d3dd10bda11438323283335bc746ed4bb74f4e3bb98d559d000d490ec843cfbdd7587557d462f09ed0620f52c5c6fff7c54e4d593c3341f68405791c1e9b01ca233f029263ce65ea386b180f1e7a9cc5eee69154479bd017484f03269e4cd7f60226e2801227012f713d6abea192cfa6089383c856b4ccbc3c3e3845a507a5051dd9be7c885138d7a1dee1dd6afda4d84aec3b7fbc31c6232af11234b3a0c95c4e4a4e5cad9b56b8b325d69c257a037f38d293feb77a59c1102504f5040612f6a96cd6bc5ea000a626249aa85d0b0fd854c68217e9db8b6e0b1fe833d13f5fbbd6b0788e64961dca7c6a9a362aaa58d508313cc5fb368dbb57f48d9921633a92256bf49a13e4c00369f7dc4566a66f46732945a49baa75198abc50e170373ce3a381cb3bad4b040285654572e6c8213e2d2b8b4407160d4c17695a5d5d9d653b16a12e39ba490ef49ca9c075f2e01c701f725a5198095803ef4601ec5432026c8ed9d33dc38ba021458c24323bfc2345e006b5b029b0434f28e04280ed1443058f53228aaa314cfd1918c2abe80c4aae3f5582bef8576e4ca7a78912fdf9be2ac9d690852cd504e07e59c8faf35bca2a785417339674292cb5a19659a8a98ce9641c48c35dc831bf61d24b09c78c2b67abfade50ef405c288b036d6e4cac9c1271411af959235f602dc3e4e7e882406110f4e47555b44d04e788956e33439d81f33e5c318e1c156e148c10e003d6008641c26d0593cee1282e5acf7dcc4eccee4ed4b36b48ab3ed1b274046a83a5f102f808aa17062dec3324a1e1e6d415ee1e2257f3f93400cb5a6efc1bcfc517024fef04e0a95b3da94d40aab810f24943854a10dfb89b9cf9d0d10fb68a61085800ba14581100af53c3c326fb103c3c9ff012d91db2d44c31579f29f522175a470c89e8adc62bc1a57113839711395c94188003edf748e477550387d636b3f04debf1d65c1974b39c75202235b2ce9e5c5743148224632df292b88bae45c77ccde542c9ca16864ebfae169e04da5a365b1714c72460d4d4203e9385ac86be04c99b090bd2a071e28570b2f71a360324c0357adbd7fcebc8d65c1693c74956ea85bf6e7d823c0268c84ce6a57c7b48e23205e15bd2a68f43606ef4e8d3fc2a1fce594df950c95717af73c00c845b10e3e2fd1d168bedd362759654d113902de61c25859292b152529cfa200dfdaf6161dd96585be5a6dd9ec6e5decfcbbc5f78b99b18b9481fc8db9327b1a384991109d01a804f38756aba10baa0324c6487e339fe2b0db6acdc1096c1051cb303af0c8278869ad1439702bca63293634ddac1856f0147b082fe6332ecad08829f34a72cbcf969c5a24cb1dca073bde74e0cfd183631dbdb1870c0ffaa61d82f80de0c6c4d616a3117574a82809c06a4883ca63e0314342550efc395355540dd8bb8e49c844818784274bfd86465350a8987586625e41bcff984f6edbb440f1ceb42edf8c759aa184f3ee9fd0f06fa6769f98062978f7438d36e72e55d9ab190ef09b27037e477193157ae7b8cf9e808c3bfab668ec1acaf224cf21a41ae7068a34c2bc22a518129f7794f966369dbaf0984c3b4f88d070ca32d4b988c3a18d7bef087233d6ec03cf20d768d41c5e1eca012e434aa1412d08026a5de46c24cc370f8d387030657ef46d4e6744f342070c77771be046210bda3e075d27a4626f16e6a87afb45c95c524aa536f00ae2782aa0915cf5e3bb038159038a58ba5d1824cb04a237d7a94a842d3bd075a37770c7edf0dd11b20a17fb12c05a8223278d57788109a721b8052b0d3cbed9eac8c48e4ffc83e97bff6a9b184118e2c09169e79e16881127df462fbb4f581099a6031e7d8380b9f5499640dbb1f1a445d02d3e9450f96f9c8a5b43eb4e3205527da9fbc799eb5597bd8c761808add79c2e59d0a2161e02f60bcb96d2013f78d97a519f5be94a7827ddae8f6af915671ae50716b64098a2d9b0e309d7158b269248dafcf3666c47cb08d52854803e6e20ad4173e1d7a683d63b9b487204bce22a2f18a2ab87f0a44fc846a3e4c728c2691caa226263b1fcd6a821e279269d0e38ffd30d852947008a180bd1ab7e431a8fc83585c8d8968ee6fb2d6daa3f695837eb558064ce81d3e4f23362c7a30b8def0e1cad6b527ada0df038ed347461d956e7fcd60a7c98bd36b3021513a403c8148f181c03d8fc3f04cdd2b8f8e4124121c408f9f8cc945ef9169c0c8bf072c6826823242e077f2325d6dd0f5de852dd2af1fcfe123cefd014de7237839ef4ae393752bfc43bc6ef06a2339046561c81a5026660293186a703631fe24a76b13323b1509dcd7be0ae1cf6c6922f8013756e8d71108f02ac42473f21f80d70e710e3953f5f1690e22bbb3b8594837409e3eb7519699ede13d5ca6b0aa33765c198d4c0180b2cfbd743afc36777a346276e9fd27c0fc58e10dbdadf3cd45540204d718cd52f8d87313d44fe9b6e9993e519127981878a32203437695f21a5d421d01a8a5c68de7c23042494758ef5d344ca8c3d063d4cb9a570e736ec3e824f575e5d59093de70b271359c5491b5997d2825a5af0112b89a96897d93dccb15ecfeb0488b83d0f731cda2e920e388728daea48b29a32b1adf517079f3345fb296e47365df70aa9598a735e317e060f8a9eeb5c2bdaa2372c3bf5bb2fec4059703a62a0185f0cfa94cf734cd26ed49c06f7a2149d0e7eaa628f588e5d5afc74815b24c04d3e0d306e0ee0a4954f2290d94e7163d5722252b18561f1f5c1476d6b1d75d000c0cf8de0aef25ba24d976d7a7d128d4e2f9b1b41e6d54d5d5bf9397ceef6c311ae8b3a08fad0dcb013a1424a22a65bf7de311aa487bbdd42e7d12797fb46016fd41de6effc87ea790049b86cd186e31c56c448b8554c416482c96d423bf7e449250ff8f1f35d52e07b3188ea7379df6ec42bf30129fdbe06e08eee3481d8888e43ee481bca4363aa6800f53f4877e2ead080625b3e0bd0c71f16640996830d79e5340e91ca80a94e565d94bf4c8134504c581b878cf64f3e87f979ebf9c6a8858786e028568580422ba525584c8b24ea4711b217034fbbe97b3aa73824ddb74df7baaea1fb54b7cc6659acd7c2df81ee5b88c1e7e6b93c82fb04af84b5d583e3e4cb2ebb9a32191397f13b648140cb165fd0803256269bfe1d4020e0c01cdab84f30c5d2b11d43a57c7c3ef5747814508607ec20ca4645a1a0a3e2445d96ef6dc5f7ec5c7c73a9caf42252370b34617b92974017f9924b66764b4e4ecd2fb5814afb2dd413a8a9b12183ec19fed07b5d3839a2643229051139805de8821b962e301161321b1823398e7c3ad40cf155bbbe254af605d610aa6b9f2635b5c065c443d515fd49594053ac6d54ef8875631679637d65f12cbd9542812b57722090da030df62683e30095887a65d7f4e28219f3ed27bfefdab21a31b1665fe59101154698ba9327ee232453eabc5cf943d7e14e33ef703e4b00f891b0e62579567ea8df6063588001fc8edadb34c76cc8f49fbbdaf152e5f60e11285d13358869156283211fb0dd002962200707290494e6a6d07fd22cf3888e40b44d7f8222df9eef51ad11a94117a706c241cbf573f44f6fd502fc345b68cd12ed202b8b89d1072dcd76855a410c889923400d1f68ab640c0ce55e27d8f1e5dee76480523b45abf216863e158ace23579ac4fdbf069fab8476a3afc580859d170050d2d39086df7be33bda9d9881e7a788de0dcf7bea40905a6bca36bc2d04dc81d692a5e11a7cc3a6a7777731c40651a2b5da7acbcd00680c5d476a74321c9fff4ff3085b7eb36103cb0bff1e9b38d0e398e1f9d67c22288c1257ceb910d0f5942b26aa0e31b08bff4c1aad1cf151a952ad91ac6889b2a79e076ac00df04183578208305475e7453d6abf66c03ede7bd3a9bcbe386ec8b3e56e5b3643d97164a06a4e1e04ea7c6b945071fd73c4e12275384f4fbee39271612add7343edcd19048e3dbc72cf78b90bb2927d989274122064d39691326317055439506bf101c5c763070f87fc5cd8233b1c229fdd02a64bbf2792b86d864eccddd7c21a133846ad737deacbba40342702d3ffc1e8b83dd2c273e0cd7bd2cc7691dd290118a64c3965e1615df8bda12fb1218f3b5a0c9f0f79153f4e4590436f6311a08ee66044bd8b8a534c674385771d0d513fcef14b43098489126497774c0ff59ddd5de28df632ccb1a353b40ffa1709a40a073b303ddb557532422aa850e31949a768b4e7603cde96d075c87e492d291418bed49b96cf69e3c651f33247a5e9400c698bfb665b3d5a4addecee71c0057c84847a65a4b0af2f42660d02ce4a8a8c907484038ac40e43092aa4d050bddcb00afbbd70b253bac8233b753990da2223c17f2f03354f4778c8fd6d8d8b1f43c2b7c02e809c0ad65822e5b1688c1f70f0685d0c31742c2886793bb122ab3cc047594d387930b10adfb88ce10d84703ddb75f393dfaa24bc903643b889611c7b03d9b88faf7060b5c43ff8464a42514c8237f1d8fb11bc6c44ddc952f56954350945a02a4fd537c9caad27f120bc8e3278f91bf2b68b998991b1955f374dfadf87a978aacf0fa253743b735678f36bf52b1ff1783f92589e1394568e5c7ea924048eb632facbb07aff3456fa96ce301c4904173ec82c728007a2e7b9402e3ae81b1fe51e980cd41b5b83e079181389507cff283a610c1d4b0aa06641344933bcadf5f6248acdba454bb054066aa3aac8f40a4701a20ad8594a6fdd14a6db03b961347f407058cce7c18cdb50661b842a081ccd7fdc0de7a0f4428b37fb09ff1da08e912d083ab20f3e53fbaad162aed0f8bbff263bcfd34978d2a78e08efca093ac15d8011337bd7617c001d4b5c45253fd83edf9151f59b9b280dd531b3a560b21d2aa37c77c9d25b83a343b993740bc5e568e49f22546a8599d622874e276aae802166b2583a5b5503cef5ae8f1af4c6e5e1314be8ffdaa512705cf7008f849a45cb4c473585e6f892c06f620954f76a077b3c92cc62ca368f2de1847a8b96d7eee47ec584bd8514a43f8ffab21bc07b41104daeb82995c30ccba9a0e251d2a102168e36481dce2c0d163f09bf18284e080c6488505424ba5c256ad2ca04381c9334548b60574387598b96fd7f1d76954156f4dfc445d8b88ed585ffc2104c7858e5cabde0b8da691496b7bb735b9a594322519d9095c094609453b64fad3484b9e93274b261268594af776caad6e49f5bc77ded7bfec7c4dcb4442510f0da81f5e8061ca0b9313198511605831005330a165cc172699116392e4f900f0e1872ac44801922d26808225082856386fd4336008c0194c9c0126d7c7e027063450810214966e505a413a0d71c39c73ce1e6f68ae7274ace8c24486972a5670c40a9acab0a18c33c4389921e50c1472fd1ede50ad2408a28647ec124446d1d413793e8f8e183c5480058a29a35c7f07930a78c091eb5f20450aa6b0c01baa1b971c1480494111b97eec36f9114956cc39e704bda109801e39cc914222d725505084822272fd1d6fa8ea3821b50206b9be0e6fa85a219ca0c97133986fc7c832c60a79be8e37346f5217343806500031a41003863c1fbf5e4c4a00c316585280c20a098510355b2b7e07bb5b8c10d4e4f99f373473c894084396411846e4fa9f4385eea28ee45adf460d91ebdff0866a17f50421b07202a65cdf863754b7271d308ec895e96b784375b3628a1f2576f80835608a57a2162f9074783145982861b6f862875cbf07e88b2e72fd1f4082e4784375bb225a10ee00a36403cc152610c204b55c1fe60d551d1474395dd472858a92ebbfc010e5fa34b0121266b821072eb40c219f20620515ce1417f8708145174170a143aeffa242b48af7755dafa09ba6e2eb17242e50f8c224d7b7f186aa1135d4787122cf7719d9e450e143835ca3960aab61b8d428c68a14acb0a28b5cffbeac3023d7ff2b9ac8f5412c72c8f57bb01893eb03d5b8e4fa3fb21022d707b294450a725dd2e2895c1f842d74c8f52d90812d98727d225e6878bd6aad482899894133edcaf34a9729baa45053830e1722b0105197b8c872ad5aaae821572c5e86115dbcfbbaae976b56c18214797ef73a620bee36518124cf5f7d3184956b9b9e92c8138bce9309b9d65a6badb5d65a6badb566b92087ae297c9862cb0979fedd52459e4f5f3f905133a3531679ce295158b5d65aeb4e9e45042db1f7058ba2498597164734a2c832bbe1128563b7486293b76d73bdb098820595d78d422a494d97292b6437d784992b8b231b1750a0a9c2a866861566b67072452992a935d14c79e1621135c3840b2d2e453258acc0d262ea66f922ca66191343b785118b89122e8b266f04ab2b689d9621544f9c653a62b718ad66d32ef1586cf2c4633187f7554bb2802a9cc25861015a142d69197364015434a9b0002b8ea4a02149920287a2055cf1248514240be082f4399a4c6dfdb295564aab8e272e8d1c9762b872e472392ec5c0c50a3184912403d113198a7ab85549d365d174b020430e3218b1e446278ea0e59d2519a05892a1cb7dffc273bd9c80b224d39f5132998c05195680a48b4ed470845ad0c2ad39c18223eeab091597660af874594a607155392e359122ff52932b4ca83a4930189d48622ad31c645cbb45cce8da1c979a7851c495c971a9c91832382162e2ae725c7292828e14eecc71c9c9121b392ecd30460fa782eef60ebcd0c20d2fc8eeeeeeee186eb6736d312846cfb12f6b73fe25c494835105827e3ac3c9184252b6a726b8ddb382843d665cebb13e2342dd07ab5f2b16c27a0e17693deb8b70dfbd11211ae74cfeb45e7ebc2dfce2aecc7bcf61ef693ecad8ccdf6dd24209667e437ab2bf63a97463c60d6d035ac8fb8ef5716db749eb240eb7928c612bb7f3be48eb65dec345baafc1600d4c9c47642d066daf3c49c8128757929125966164f9f3732207392ca5c615d785a5918cc999bf1f8258cc602040637ce0032d5c448a90d6771f65ec65f007baaeebbec320f88005eeb7f0c5edf4dd87f1098b16b64464cc7bcff3dec320f80057996e7fe8442d2ca50616f7bd0fbb080bcfc718dc772c25077fc95509799f0ad75f853530a5d3978bc3885b6bb88e8d3c87e0affaf9e1dce0aade5ff5dc6c99a3acfdc8300ee563c70e9dad8ba1edec6b2a064510f42efa916c21f9124a3a5296b827cfa2e8d2684a1c76b307190b5142f16f0ba7466beed3126ace6613ffc148618ca498299b4573891f492319258732cad1e752554297e240f553314b563df7f50bc17f72d063f2064b407dfa1c547f0e749e2a2712e33e2a1c420d679e218ce0b92ec0e248bf10fc371a427d7ffbd14b802a048b00bc7909880e4aa9fac54c9b62507de95f8b3c3f93c8634bc41f9985962b57644d8350d67cec78bd646454d39f1479d8978831f95934451ee9a59f6a2a2b8e43764327f269244a9e1ff3c4b52831b2d6535ee38c58ff1870f800e4a5c28d1ebee1fe7020b2d25aa794524a29a594725229250754f5616cfaad1be2c2ac94db263729e595745220b449bc575c07e2df06af136ef430941b46a60dbaa393d2de6c383d95f3757c839e5a5cfb405cdaeda3106c73f78a9baacb7be50c0264851b9970dd95ef6d822c4109b61792bace1b6a2d0e7abb3bf548bd9dce0c8c9cb41c4d1a35256530dbb918db5dab238f1affc3c01e5f7212931d6a7af48064d2069bff74cddbe994d14a14145d249ddd1435e53eb2a1c254713159bef5861c07894eb20271b3d70f241079e4ab96b81209cba317686e28f9e3a2f93c8aca55ad7c48f58ae72742a41f1a41c24074caa111a41cc2fce6def5c1fc6dde8810cd07f3a22330999f79966c72c1904fb27cf9c47d4287923469e4a054d17db9c47dbc972f84e65d2ff33580e65db2fcf120dfb12bf9fd9c8a741a6e3515717dcdfb27858585b8de0617b179d7d77c456a9ee6af1897bebf74288fa202459372b0dae028635d83a3941bcd93311eb974d93d329c51a8bfd0a964f93964dc501ee121aa57492877a92807a5fb181c675cff469247625c9d12ae7f9d5df9b11eb9f263457271686404528aecb8a37040b961877941b9f2b54c18910c4b40e71fc8c854ca96c1acb8321ca1e6f72f00cdcb775acf644c65853e31e2b8012021414145351d4509239dc8a14b1d4d41426a922f9f38b9d374b8f2639634ea94ce9671b21286caac84f2c88a54b7d42487b28b4c924c5dee510cdab23c8a729fbcf84f902c2e618162924eb24ba52d619dd454a46b4992f2a89564902ea16a209157dca71fe93aa883918ecff151e76b212ec77795acbd5729c7176b2c71e5bbcf741a89ce21c7e74daee40298b4d3f94c68a1300ecea8e9f704b7a1ba269b24b9928d25f746de799d0f811e082e4ebcc0c504a83184e6cbaf6b2e80f939e6ebf86291ceefe0225284e4f81cb888fc4d8a101daff345e4e7f8fb51fee8e022fe3a709431d6b33ec71765ecfe90b1f96ef3fae834609f63ff7e278536bce12b13c1f11dfed5f7aaff1b4f3f4aafc6b79e88fc71190e1cda0f87afc7e1dfc021986de01a3804ca3fe48f7b7197cdcfc1a187c9f36138b434f00b872e26cfbfc1e1cb06879228cfafc1e1bb300d0ec1192c83c39efc6ae110c89540599d5f2beee1121163f3ab402d912796dc4991677e9ddee4e0fc39abf48e945575be28e4440e4eaf1263f39700b1e449c5865d8989eb8e83882b8dde5949465d6ef8b1ff2a03c04028f7a7179e73832f8932461bc1eebb1122d87d47c4ebb82279f52dc580b2524e282eea6c51c6de5e4e1d392bddbe5b750772b95b712b1528bd0d883ba3cbfd2d3d9244942a35262644a13f29729f965522134d2abb90ea48460a555fc6c9eede906398fc2f7706024b0e5545d51bf24ee6606fde90e3d8e28676ca4e5931d6be5ecf7943931e1539d81d11d20d3b5957f41a81aabeaf2bea64f4ad980fcaf673fb8ec83b99fb548fe2b1d9b9937545ee13ab4a36e52086bae12dba45ee538418395415d12197dbff70ba750d1c2e4d8e5bb68c206ba0fd48ca7268a537646be470433f72cae176d4e3847b73e5aafeb574433b7593c4a54734949472d8c972ffd1a447b9df33923f5b918cf51fe5de68b93ff490727b49b99f4611eb4b0e3d2bb228748b84b8e1565494fbb7221bb7a2dc5fa162cc63ac50381c52a1324c7e14f2280e937f24e6fe94e4e1bae34e8a314a96c450b6ed3b6a2a777f03a6d086ed94dc8eecd496d4b84706e972016b2f2d6ba5a824d418e6452fd418d64d2e112b640a3586f513b95063291c44107463c08893a21cc470e1c315412c49117534a3a2d4b049c0095210484b556c01430b162b284e0006892701133932753ed0b8187f36b757c6a8c521fd4ae9eb65ed638b438c514afa93cef8515c8cd14b8eef4a3551b76337998a5a26b65de692f27cf9752698fd5eb38836ec47fa21e0d0beea6df521752d8675509f6e1d1ac9f439efbc0ebed0089df282b813417ff9e188608f18f3ffc99e43c38716f24608f3733831ac83cff53fd74975b044e83179e27f9593ab56d56e5ceb762c4f86c635f3381fbd211a35df3eab5d754e62065ef4688841d5d5e3a36bdd97f9f845dc87f5f17be623cdc7ef27add44c34c2d6f28adf556206eda57bd87cd84b3ff149e0c2cf0cc949f48f42d481e8df474070086c890ae81efea01733d8d13dfc77d830176af310503b281a384d5e5e37b89b6c70e85e831baa9110e0b9c6042e9a9e4599cd198f95813e12f552e6b6820c6179386c31de6db647013d3dcd75d4d7611c7429f7b73874a2ecbfe1b0c5647f2f76646fa8c8e30ffb0eaa280639ecbbbb63b7fc09c31d06aa4a4375e1331c9791eaf999423019ea8e0f2304514970a92d2185822c4d542c2b54289c4890274427011347d4b6265078188e7208eee8f172648261492da1d229636cc5e860fc5983129b5015a24253889b1dad1bcb0acc4686f5aacd93a492c478fc8ebaf225c7ee22c76f2f3108a78d9cdae829c6be32a5ca94a51cbf9562108d8f4528e49824472639badebf9fc87cd84744eda489688841351f9f7af6145bd64443c808139f480ceafe87178f31c6da1621518aece4b129c420c7171283bcf701a44190dc9175a2df9cebcaaf0ee7a3dfece2099c4fe626314a5d3c71c3062c49eaca97dcef44b98bdc8f63e3833dec6dfc8d1b30182ceac6d719e7eb6c03ca0b8d8f7ef3937949889c1a9fd3942a35a4a7a51a9f8dcdc33ee7b9ce1bcab1b1b1b992f375a6f175863129d1f862c6e9dc45ce247751e4a9f968cd376b3e995b0437af2f03dd4e723f95dc35af4fe665dee66f6e6464648e6ebece355f679b36ea2691a7ff8bd945a7ccb532c098a1f9641f74d110329a06d5b35a19341f8bc5adbca119160bcfc88821e27d747a9fcc718ad6931721518adc2fd55bb2773f554b019dbd272191a7df931ff50264a7ded413000d72a62a4f0146b2ca474f4b47d6513f2ff7fb8edcefad93376fa8d6ce5de4e98f3987e8f63bd9e818153b2bad95ee94e0cae4c8d40253d1f572646a61881c2a6801e97e0b32b8a0baafeb7a41c9335a700188abca91a98523605a20daa1dd9a239392339894444d2f48321633a53b3f5c1a53e8d002450e9fcab53932295122ef38a14409dc40491253922472f83d3d4877f98fcb63fd4aaab9b13c57c751a970707ab23eefeb56d64655b3727134b6bb92eb64951b9098251ec113d063c68c19d31eebb65a57a6a37165a51c9d3ca95743bd9a1ad652afa6c6b40102410bf6006d30326e38694c98bcd1572f8f4e2b99ed7dfda42c20dc6c09c620dfe13e1debc8838100727f3880ec1d1b87e6ffc69cb37538295dca28a594aa18a5a4966e73d6bad139638c314629e5cab9961de99cb3fb557fce3a65f458364c5c8cf12565534a698cf1467635b8bc26add1c53bf2dc2e8a09e46084d9703d3796db6fc3ed0cb3e1469b29433a5d730c4ae30ced628c118740e594b1975295ce6c49c97594524aa99452ca158beb28a59452cab1ba19b59aad6e46a9ec94895bcf50cab9bae8451c4ae0ca6ec76f88ce53eaea62e49194524a29a594524a6d7c8cbe2ddc7ef9b4bb439d1c256aa5b5ba1cc442d4248fe058667904efa93c82e777106f5b0e558ed5089ee387f5e72aabb6ca7539e2d75a6bac35c6aeb6fa7632b5d63a536ba5b17155cea69b33da5a9fbb795557df7412a0945219a3d71fa2e30dcdc773ce9b5965f64c719a4637a51ae6e4258fe0596a86f4e5770f05e7e33c75725e37382e53029d9bc5e95577f7a455ca2e5772b9b252dc45b3abb8928a2b7174b0b3a1a1abf1c2d0b470984564ae6cda9a6290dd2d251829c1483926090d5232f13061e84c1266d231b015b88a3b779e71b13dc33a885fe414814ead9bc579e5f8bde2b8ef5b71ddcb7d54acf8ed31c8fecafb4ef515c144ba2d534d42e4f1b7dd4d718f74d11204c82c71cfa43062510f37ad9b5e03a4d00a379216aeb3e96a3c8fe5b2400d04164d6bc6a99b76901ef03a9879cadc1cbf08526e26d6cacdd0b4726a60d5d874d14b6edc9187bacd0d2701073f887315243ae174919b9a9a542a2f9c4b94d3ac553d7d57c96d82e5745064efa6d844ec2cd97d065a4e8dce02c10a883c37861a366cdc90403d3d3df267c571dfcabfb8f118470caa407afac7693247cca0a3ba87bf183b56756b50345f254d164af4a7adc53baa33e825620f7f2f5d8c7237214613f7e1d078dde0ccc701135bc1cba8f20512313098925049929464ca28989220fd1b4d773a414b122c2fe2a27325994cb66405160a52b49294548832836486d73243c76ab574726b47abd5f25a1f8fc8ad9f2d26246372eb6f95dc6afdaf00456e3d8824b7be87052772eb8156c8ad99536e254912a3cf67cff33c4f0531d93be2c4a4c2112ecb9373652e27733f392e8527994ba18bcc7939ca036042a10935b273fb28c656837664ea23533a001aaf6090c3a8a50bcd804ad126f2c9611fc9b49768403f66c994bebd24a420d37f2165fa1f0231327db0042032fd9e12aec8f481984a1893e903214acaf48310a520d3ff000428993299e045a69f01fa44729089c08ba21e32653ae14ba64c011f975799f33af9a9b2cd5cab93df96b9dbc9af669ab9998e867375359d8d8b66a6e686151ac9b00e6e3a37f68091d92ea6a38e7bccb9e1c69f2a748c1d63eceed8dd91c6ee27dca42f99f8f4affc22209bd33d467713b8f84831469d3927954e1d989c2955b6fb72e5538a6d94f5ba125b1d816a256537b74fd2f9d58f66d5eab33182f4adfcb939f5bd5a6abf036bdf1b52596c75b0364c4509d9c4001de3733b5f1402bf22345000e3884c68e7b92e020267e14b91d0055f040400e0e2284a28f616c8b80fd991675ec6bf70479699f91fee9fcccc476f68c6a6c647cf068d4fc7fff0170a3766fcd6b5dd0be686b2c88fcc8e5a375afff26f73ffa68efc6cae09c1fdf79ca9c30d5f5eebe2e0d3d40d250ddf7cbbbe717c7b43aee75c2e3a66cc182c3947a5aea5399af33b1ff70dfe0e88fb776cb8709f06ced30f07e77f382725c5e108343ffd6cd89367ce174aa770484f479de04a263188fecb071d72c5a1acc1667b4614f9c4084bda279b3868cdb8345ff3f6551f4a1a77b3a972a85e7a43aa8ae36b8e987138def960f279fccecf0f0653bdfc0dfc602adc001ebff330893de081779ee7c1876dd8039e079f0783df12fcac8c71bf5357cfad6256e178ca89f023cf77bdcb254254ca12873ff2c421b85ec7ff70ff5cafc3e70f77bc4d11b6ed13c100746772b223e24ad294f6297d42975c7243b90ffdde41c096a964b1581deb6547955a0afcb61b9c100115b42652fe944e93d2734fda6c311996fb6553948a8a4c4952b925131130e4502a5dc9dd3fa52b2146892b5f02259a6c9c4d173d0e74bcebfd73fd8effe1396f9f03df88d0dc79f085ec3c08f3e78932362516b20373ac0121e0f3e0223fb003a2101019eb0fb283ed86a5c03e0af1f0ce87bcf20f5fcd6ff5f60b3fabb0857d21d4bff987b45f97f375dfe3e3b103fbf09837d46117eb69bca1d5bbbca1ee5d1feb7574cf7a6ef5dd2865afcd9a94a2ae92d39383c0fe90f31ba1f3dc70e0f78f7b9a83c3435e19c7db1c3d1cffc3c31c78484fc6f1f83f2917e7693c87f3859db717a5b1610cf4641d1adfde100dd8f7309d8fded01724e7619653b9761e076f3938b4301cde1756816190fcfaf971a0f3f7fd0f7f7da10e6e3d8daff307e5869396fb713c1ed293bf9f1dfe6cfe3e188eef93dfde3f27bbefc3f1850d73dc0135caf4bf8f59ca9c9e6a505153535150b5272737f223a424a5a626a524243f8a9a729917f98ce6349f4d452935393dd5a0a06a4f4e4d4a3d6b5a1bf511525212521fb551d37a166b112a2a6c595117b56c2aaa07282af5e0f1f100b953e6fe87b7de88833c3c7a7c3c3e1e108bb0e5b033067a3287c320f987e3577d18d85e7a43f8fffb42780cebc0cbfe5ef6778c819e4c7108ad973df95b2663fd4684769e87061c108544e0de0151087c1eb8481110708fdf791e1fde1e9fe411b66ce7a3d08e0f63144f186b418840bc835fd25a7eadf7aff53c1f2cc88e8771d8015168078e3226b4f3300f5a68e75b6807f74cc65a166b29b5fec6e79f94eb5fc36dd4a851a3468d1a356a7c9d6dd8f8ba461b19b9769ea3f9c2ceafb7f9866e705e62b007e855bf3dad6559a353df5ce539387cc1f04603d315b698c3385f3842cd37ef72798d95351f63cd4b0cf6b83e04ba71bd7f353fbff0e6dd1baab979aebb1b8c3d8cc76a5c51f2d1915f71b05fe67ede34d4c29ee47ddde746df852b4b9afc89427d5434739fb02ff87c890ba0df82afa75c00fdb12fba00e4e7448d50f392dcdfb289b4b966881b4db511c881bff33e80b2a4d5ecef3407dbc886eb4d6a9fdcf9bc04c42758c88edd6886f2481a6530cb270787b8b20a491483a2fc96e1953e2e254dd27c892bb32ca72021d9194c49c6fa618e3bc98ac4342d4edd4a2d3b9a328a6a1a54cf6a71aae73df6c41028dc3ae8595681d093e50f395718033d5985c320d27654023c3066cc18a7bce1213de0ce178566974b8b415dee974631a8c34cf9f7909e3ca427cfef28077b4ae9facff61026ee2929cfe17243497bb2f3f40bb0b3f30dfefc064123f7a118e66fb37fcfef89a11c543d216950547ac8b2cbeca79def0989bdd41cd4a1a5fcc7c378acbfa71c3bd40ac9fd9e42eb3092d6500ef63794910e9f8018545219e5766f6dec6926e647fc3b3c947712dd34504a95a69cd035ed0195e74feb2428e28c07a294522a71f28422cf773a060633c6488113279c7084a48288a93a43539e566a92ea4b511493932f96cc4878400b3424478c9824c961f60025494c3308a1e40149a03a147a3607bfdc558e4b4a4e782d2d51a2134ea5ec7ee3c5f599776f68e667e48cfc5519575a4e46e6391919ee7ee190b7ffc3657dcb71975e7ae9a5dc8febe387b688f8e3c231c65898091a6c25bbccf159cfd18ef51ce775ed7160bf22c929e57c6eca29e5949999796e662674654a714711417a1c788e52463e6c2e711f9783bd4d23ff79e43e51fce72c923f52d692fcc9bbc830eee332647feb0d61e9bc8aab57e6b8c8bd27399555197766d673fe0aba00c8d2822c71228e3f4e8b5f0c8ab97b80a4bfc45362a6a46c6aca5a6855c6ad1a90b28fa48f309e6864f23a80cd23da7d2865f3894c5a7bedfde849a4c8e3f208c71137743146fe75e6412ae54a2499342391ac375d262af5d1d7aa54ef17be72eb0b6d9e42ba1269db5e25b1f538f0fc23f2f8d323f789b2b5979e3295dda5cb29739f302acd22f70925929c9262e4530e27d13c92fdeb064c4d9eb237921e20b047de97b419fcc0b2a7c9ad1543a14940ac9227f6e13d34df061e73202986a43aef9da424e33a50dc6071c3e8247ffec445a450db42f2e9c4b097833b40f901f184b8501123424032d61d6b4e515f28c150c5dd9d521a630c928daf74f9caf366712b0e87b8f0063fb3e31fab14eee7f0a504be72f74b8544189d6adb10d50417471814700722bb39a7063a87ffeee3ca9f41087bfe3669ca91a949d2528e4c3248e56792c14bae3f9da4bcf1722703e2529ef89b4febcfa7923b32790cd8de4a0fa828e01404073fd672e22b7b7f54124d94a69c90bd8833b2632b59e847dbf445fbff8cecafe39fe3378cdb0f5edfc8c8b4bf2ed78d6e5b75291b241f5e3c76d92af5188a5290fd75bc658c42e215c52aa1d65aeb96335a485070220c192238b2811ba0c6a0a054c2962db63cbde045f17483122c6242d081394185e7cc781d95540ebfc722795b69ad7495040d546011a3c313a229b6b43013c5161c9a98f7755df7c6315a48b1f0c2d30c4e2c4df1658b628a3050ee65e264c3840b2624a0b92e262652324e8e4c4c96e43075d2ba59d58aeb58565e4111003916c522abba325f6c2266895c7e8b1cff00d18bfebb86a5e3a4322a6b9b71cd0a04fe903f351f7ffb2800426dc8c416a0ab4d8c4c36400ad16022644c7d2ec47577bfeebc8e2e57adcb39e9e0ae643890b96d0ab7a7cc8c4a2929a986a456cb1b627dcbe3c0b3ea672c919679e71ff88f17452872fc05b8064282282006a9b647723b9b4569b55adf2d3cbd157655dd6a48b547b2b328dbaccea2047902c2cd7433ded0f6331e079eebd7d86c1988535ea441d105397eacd66a5ddbac962405f84f276d76f65ed4b575c6acb596da95da14990fa88773b95c3536b55be5b420cad9500f4c1b1b9b9bd7ecc95ed4b5eee17f5f2f3abda8e633236a2de946fecc7cf8caf672f78b4232ab4c693fab85284ebfcc95f9eca521f18412391a2d9623b1fc6bb1d6760a0009285cbe2c51b3a20769c50f38d81e2b434c996925994c76245ec9f406a554504a63971fa464fa3f4801b104105053aabc10c409327d20415ec8f43f10022aceef07223b65afed0be48354cd0d510aa4207e90c2f85025d79ff186aa0f5b7c209190e75f6f68ced083095a35381080872ab2f42025d767d9d04981c20309b9bee70dd59a274c5290d0a8c9218a19b9d6e7bca16a8520aa5959e014254b14151130b02c8f284620c5084ac8f3b74b850b5b6badb56576108369075a9e4fef9c53096fce39e7bcb88acfa18f5cc15cebd10db91e89c9f51f9c814987292865e40a14047777a0687941060c0bbe9ed490d84cc9987df2850a355a9c6c9e4c417125d560c181862317682470410897a32892ccb658418896a3308a8e987020e30425990bd6056e8b5703926ddbb62a68b40aa3cbc311551551dc2b872232a5db539ae569cb52dbb2406d59a2b62c531edbc026b668a7bd82a6ea525d2f2b64326673f86205da258a902e53b61cca6032a5a02205d385b26426048b1294274a8438d5a008a144c8144d509a8e087932fa9e90b4525a2d78e1d21c979a5ce1056dc9c9931811b7cb71c9490f3b29383102cc618b135c1a26a42d3a90f0e1dae4b8e4c48a0b922c39215a7232c61126883c168fd825b7c9eb89c72213513c167308c68e80628cb991e3120d496a947d4efeb42f3fa39634c924c6ecab24cdeca2ea644e30fb8b946c5f1eb98c1283aa945f3b7ded25f2d8a75fd71cac12833afbdbb73548a61dd94a22b2f5215b1db27d570e97ba8e6e2869921655ef82726592fb6c336f5f1a49c9e1502a65fb5151dcaa5379181e1585fa03e2ceef1cca999cd99ff6673e6924690edadfbedc9837498b3cf6372eaea3107d5648a28f6a87f6993cb40fd744f4a150449feeedbb0939b81231a8b3fd767ab7eff5972cf1a7919a3a83bed26664db28c8b6bfc816894bb6d6beffd05092c49f0cb808da3e12d97eec22b2d521cfc0c47ef55c4f6e3f573d06d477e170ade522875deba4b559becc7be00f0b222303f3e02dcd5f07ed47a1d7a5c130efa310cd47a10bf360e6657e06c33c0ca5e6a390eb6530ac6228331f856476d4a18a4130dbaff97a64ccbeeb039231fb349f53b5efbd878138686393ccf7818cd9bfd5daaf6f0107ed4365fb8d74a53390b3ee61df43ba32abb556ce3bdac9aea37226674d29fdd6b4b3283db3cf51caf2b2773e27d79a3d25f79cf4eaacd6fca6b56fbf8f74068dd43da2676d876d1711b4bffa7a8b085a29eb1ef66d7b1163f64390eddb318c5a397497451e6b27b6ff61b92b4b439a6f71c8765af943b61c9e13e987a7dd502737533fe9eeee4e8105df9698d88beb3fbb890d377a584a13f7799913dc59420e6bf2fceb8a41dbcfe96097a6fe58002e72ecc902a4e2b1bc96c8516210e33fd83f7d45c7fc6b5051397ed857f48f3ba970c8c3e250878d1cf65383919d8bee6de4d067339f55e069979b504ca7f9e43ee184623a4d2db3cb84c2a7c824242f90b4f09991cf7cd61598451e7f3148536492ac85f19f98524c420b2d4d579cb2cba7ecd24b76da4e336fba22bbf8ac27a7450e25174dd28718f39759f4500162f9255e55b23b11f24be4e95a3fe1d87a67350e7f83fe69619914f2c861010ac0fa2ede4b4b77e3150ed491436fca8e7978412412f672c3396b2d1e94a0d2c4b484d64158ac8779e081f71b863540f5dcdb8779c0c1a4c530676158c51e744f571836b1072a98c450ba0dc31c43517d14da3ebef7592b1de3eb25f8515122bb0612c8c106fa2181787ceaea73082eea7034a600f7f11d3948ac9229b5d4e577c45de51824a3d71e02bc2c710da4d537fd0f885bbf33dd24f716e3d55bfc41883807e3aa704ffe0fe346196b9a8392ca98162eead4af8fe867420bb551c4c07f03e346198b42dcd32e6da3c8e3bf857139b74fffe949e23e52616b44b123893cfed1480a8beb53fe3886dbd88bdb180a7dbd7c4274dd1f470b37eca7a7277f9232e9754a8c31c87c70c782e848efc84d73374d36b1ed3845e48686eb665cffb069e0911b85a2500824611207110a6b8ac8589b715be68ec3042476c05e2491d2332497888346dce80de1143a02f57b9c6d447051e795dc28e3d634e36f3fed4f4e2623e2e07cee05c38d1edee186718a88aa7fc619e777b9b218b42306adf2acfba99247befba28cad6cb2dcf856c61f1b0011f20108ee038bf88bdb0f10199b1f8292a88b7e7090fac89c54c62ed9670e88423f646cc65509d1845b69fd721c9c8d03c98d320b064ce2da1c999e3ce5f056c182e40913201597e6c8f424891a25d01daed3ea3ae5d65deb477fabdfbd75779d34470259b9d19c6a5f39decf6ce395e3edd0696d9ea55c51fadaaebd76a3af1c6f874e6b524bbb4b819b52caa77f03f7e8f89c73ce493ff6f4a89aa136ede89cb3937246c5182395b56ba594527a2da5946e1b1254c61869ddac8a524a6b8c91d6788c2f393b2be5fcea54553a372e0a183060c0cc31130c183060c08099748699632818304761cee0a24ed8b5235b172d99cd365b8ba88848a9e46aca71f2f1a4211670d07f840c38e81fb59a12e03e90dd1054d77943abef9e257b8040ff97f781fe4a5468f2a7993ce6b6afb41114775292060e42791deba35252126773582a57917163ed86317078c891c9e315a6274732b54adc1085c6ebd4c2930d275ca61b54d0a5eaac40083a850d4972e4fa53e74a0d5276e4fa968a5ceb2da2883bbf062436d01ca851134b84bcd06484059329a3291784b880549bd87ccc923282b833487408716768b8e0890f31302e54d9e1b6725c72018a1d2ff7e6b8e4c21535725c9aa184292a318733c69c79811873ea2f7d6e34b640e44972fb093339790f3d8b41396250cc114714b3f944b325f46d8c71c39ecd9ad6b2af8b965c99d0d3f7f1f229a5b321b87e1a36b636b926bb66b24cbe99db3af9b532cbcb5de6b84e06a5d8a8248df745392ad19400000001004315000028100c088402814828cd835db10f14800c7292427c5c369708b324c661144286180288018400038801305354e3007682e79eee7040c7e23542471cab3e8a5010746afc592d0001442be9be613fda755f3bff06fc67ae8bc9ca997de96f1d991bfee11f78851670b5b4b95668efef23aa28d84cff32f9e73389ff847f590a00db04c296af65c491abe3239626006d70d79e1b50b2a8e531e915c542949b08c52a423b61c2fda524c05831051a9755895e1a838004331485d7366eea40ea1053646b76a6b3398ffa7bdf548259fbb75d0a2641fe4b53720ccb00a8e2a12e7265874ac9fe1c9598a0014341e72b10ad74c411e5592742c7d0d6560d6e2bbf532d6e3b93e5cf47dc076b5e091e66f6ebc986754743fd6af4aef6ee3a306529b3e65cb17656e46bdba337a7172f212c7dc14a71782b98036879eeceedf35b87e932858369f19ae72d42f10e70a33e4b73084ebd9d2541774ed8808b5d2434c37bda30b660efdfadf39df20e4b9134759d5195464f7fb9e90642fa50bcfeadd576406b94d4941d98490e3b8a18b89e601e2ed493990b7f6bffedf0b768360bc28de26d84ef4c2c1b9172118272f62bf53f842898cdd30bf1538cacebbc17da9c752b2af19a22e18b48ae093c0f38d82df097090c7ee2c06ebb421126d1c4266c9b584e3c7f902d10fe7e96eaa9665ede7e0556184ef9038e40695d5a9bee647059f5c304ffb41a0a73d6cc0dab4cf8e8360a629cfb0b172ee198e0fd13b5ec739f7986cba0f06980554f3cc2a3e4ce8d29a624ccef6d7e09c0c5ebbbe055339800b6ce564edabd1fe63ccffb6a773d2d6812470681e7aa4a32e34cbc41a0e63582ace32718f8bf1771e47685e7db024b98c5a8beaf54ac4ac370b5782cf42ce8c0c190ae653f34ad15f5188a6168b383498461a09652f82a8a1aa1f530afbd18776ff8850cc83b11315bd5bb196e62e3475875ea6f3047d5822bc946de632dd8cd6fca0e287071e47bd136ad41b10b8496a6ae3876c5d8e381bd6dbb1a94ced56d323cf21353bf045b32e525b0e326964b11344d8aeaf6cd8ef23836171e31b7f7181a9ba0ec6525215513c42d561f3f0827d887c190d0652c7d110d751f2ed6c67f011590d1e39c94531a937ed345eba0b992ce21a48d4ebb9d0de364478d859e0dbda75473f819729f61af4905bd86cabb1f27206f3be97e06d9d1a73187a168b7bd811eadd8953b27b00c5eaee05d885db156a8ef6c4a801525026cbac229c472aef2aaafce5ff7a8e79abd1b6ac827e7e458f481e0b791cdb5c7f5f950f5398db7a4ac5ee6ecd3bf9a2e4992188fc8ba142ef93b5a0a7c7e357dea81219fc92fad06e6605d5d4d80d86ad2b506e218a887b4fa9197525f82253494a9a1008b27403dd97f83a2a960c9c57308bcec5825f0250c6b865ee6cc2648aa84018432a72efcd72da302166301ea4f56e9f2fc80f0006b6507f724242c13f630a8cb1277843665c572e9161bda2a4a65e24c5e3a7ad532b5a171a3e3d87cbe23531d81d99a4173d6b28ff6c42bf6eac3fb72bb9e3f0af4d2a42c1e84a9a0d1d271a7a0a2820c7211ea675bb18b356681e60a28be3fcb0dd198b81c2c7e2e1a9237deb891f2eedf971cade6de3593c824b140b528b206f9efcba4738f2be492128ee5401d147f2401103782899c198323182f9deffd9181ebed7962c5bd5e3f9f34fbf6646fcde6b006d3cb342c8618f122078e02c77d27119ff066b4918c31fd57e2130e7400fee29e9010d144ddb296c9d0665c9e3de67ccd0f513362b26ab5abbdcfc31ce12a2a91e98b3314671fc95f9df63b3690ea1759b18b9beb2b84fac899206d6a80ff66d73863c79ea7f39021fc9c8496fc360d121079a2fedbe07955b2751aa6e1516b55bd982b283d37eb7f959c43c9ce3d7b94f494eec30ab16d60d0efbc2cffa157921afe127cfe10a146fa8484968cfa0eefe665107151aad7b6bff957f9f0ec3779c898698d4fca929b73074ea4faf5c8a6a7171bd3263cf97a169e1908adeac7b3d95cfe1802fa74270a3034694de89423dc548ff858e8f22388b915badc1b04d8457c32b64b9051960a5d4ad684b31c2d8543afa5672e85b633537b399a97485d4468f74985ad28b5b624dbe2c011bc1599cb1b98061bd21eec7b63b66924c3254f187d00232fafeee16f3ca502a79ae6c4fbb6e25592463f7ee7eb145dbaa4d7d32ebe4b4548db2520e3682c79a7ee34470ecbdfb4b6755443935f664bb4907bf10c5275e485cd70559b01953e147c477f91bd24db739898e47a03d6e9afa1cd9355437f3a3d2b083c3a44addb3f8613544a052804c2e75674fdbcf836c4d3755fcedc7f2488e7a9e99fe1cc82de48ad6ac17a0a9a2ad38b03d2c57bceb38484b1678aa278d574415bd3e6d355d12f02dca12677f77775506d0f844e3b3a0f4c3cce3ad081b09c3e394db22955bbe392310cc33bc5e76903c2c46541748969f6655e1986febd04cf474b1ea758dec9030fcecc1dc1a3e4d967545d7a00ef220e1a127b326d585dba2f30da5da00b4181abc7c1e3f67a9a30b085434fcca8d848267ba21a7b1a5b7634663975ce777d0892c1cd96672a6b1a646c7ab01d863c832c9b47fc7478a0b3f07bd39e11d8da08cbb2787c4e2e6f903d2721e5ef59a2811fa7065e19d64e69e1f9a1440147a8ee0650083dc729a4b0d3609e47f7f829fa1a9b59b9b06fb672f996d43780981c3e3e300d247841f76494956d40f9529c791dfac074537bf78efd8e017a8e05b986019ea70b521a24024e68b525b16986d6d2f8379c909608178633e390fea3617618e8ce5b833088c111f6d03156e9f1f4d3059f58598669a8eb0aa278eb07425e9348a17472bbe5c27f5a8051a853dc0ccfd5c3f685f13ccd4c7efd7c0d1bd3f30cfcd2c234fc5f4a29d24b5f0de189f6d67d5a7f0a77c48e1b9ca785d1c02c3ac557cd2538f11ac84f54fdc5c9644c2dd0dcec2dd598292c602d2549c45282c581096f4b4a00d08cdf503b70b33e37b54fbdaf4b987b72da3501da27b5656979f7f0420c11b354d47d34f86cb3aebf5aa992819b0fae6e8298e599d5534b776bb1fb69b56639ddd2f2aefee859ef4c5b631a772e7e416d168dd080816f4fd409a194df7faed7765a51b58218501608f5029f9714501e090a281889343c139186a783736bc48c699aa58307a278acbb86e5b1f66a05c6510922d111a0f480a0693a2251a2d0b98b75edf55113c98962c939d21cd9dd11df61ad460643519705930224aa3114acfee91f2d9232e9c8889f12ac2aee97fe262a7299b7c18236e7650740e0ae8889418646be6239c3650c8de14c6d503763dfffb5a45c0222af22c67151eb9627e2686df42edb06e6ed1ccd30c81ab17f10b428b7dc6eb066b5548744dbc33f10c037b9857b166eee6ed0cd9098c744cbb8398241b38253d37584776b7a5f479db366551e4b586f720b7d00ead598bb921c4548f49d9368273500ad01c914a8456dd005a4346cdb2057ce80cec123fbe5bb3d48520c69b367b8e6d864ab81193fa1555ccc08780686a0370be40cf22acc428360e24f018b180e2a27e9598631272b0bff68a6b02531fb6da3a61530c5aa804575c7573c8709557022f7456c2b33629a26c076c98a98a8452242558c13416ad78b4adb2bd2b972a4d3358abf44f088dbf9111c7ec6fc9212c729bc88767724d99ccae0891bde99eae80c2589ee2b3a033d301d63b22c5e209172ea6b02edfd11af599309227ee974f38d9b77ce70a4bd642ef160ffd76e853d9c1fc7a27858ccacb653af36aa478d9a6f9cbf904d437de6771b70b761da17701c138f5f1a1d2dd0930c9e5c7a4e0356f1bd0e429b2144dcc345511955bb471514771f22a53bb5ca1a178b9229cec2760c5cfe549c60fc54b0ddb8f484553cf035eafe0c508b0837671ee314c11f1dbef267fe402024b97b32b40b954080a3e7323f2eb8e58cb71652bab0c1b246bb912190ee56422e3831b6f3a6698d4051b311192485e565aa53f2eb292848669909823d1852be4142869a19752359346daf96e7e49f85f2e152cadd4259339af1658d6e457bf0fd7cced16c8e686434a2fc5c0f89c62d589da28a96b452660a78d87aa8711faae79d2732592b2bd0b3a560cad54c9a9b086b9046972729f17d53d04f9d32a9fbe5d9d0d908c1782bba71f3f0f9749abda0edb1edce33b191de79aeeeb9ab4f6b7c7dbbf1932d9ccca879c11ede04d96dfcc4994ac73d4d8f5e5701ed1038133feef02f4c01a2c8d3d5b4e767a9b512c33c16dd7dd359f04802fa2e9903f2486ce933a53a9b84348997508e10fa63c50dc57d0accadc6004dd37791cffe308d2ec05ce42c60dc60b4484ece62c7a24c495346cc8ddd71984877f02bf4c338d4ef0a2d4ce2746bfc5a212aea38545c1937008cd03e20b4e81887a20b2d0a6a8a96870bdef54f5e943587d33c9eeca1511962ec300aabdd9167a2c59048797045a4b002a626c624e4da50a2635de6b619b2af32e3e0351635b254d4870607f183290cecd3271a8835433d7d10d129b194928d6a5c06bd2e94e589e4ca8ae13c03d9183a63394e3cb220787b8289aad9c6236d35b496023a27e85ed60a14c75328b3ddb69c7cd0ee4c4def6f1b7289089795a029bafe62f7ce585bd3cd1814cfd090cb0c560f7d370d91184594cd06fbea69afccbf1f1c20c31e15c1783c4b3c144da257695487eb793d0041d88b8f60186bfc9b607f2a2446999673216bd2cc7060ac49b079ca05bbe83f83a41dce6fce9afc7b24af4d55481f59de189fff0535159aad329a44a05626ebdbf1d94d18b2657037dec74412c9c000fa5ade20e3e01f49376b6fda09b8b5de67fdf22bc3e040e1efca4a25ebf80f3d8e03da89ea107709c74a49082e39f7944784c2a29479a83c742be426d4c95779174ed814c5b3a66b151ae1a52ddb3355aa2a22cdbff22ae7695c30090c2ffb9b46e33a0b1bd500d69169831b46cb5b9673d57c01c481fef575d16dd22ff5b558de252620cd732e68421f18fbd2c2e9a3624a981e56fb684f0e296392b0ef94ef77a13cc5b4a2eac92d805d51f3011019024b492496b40ea0a2a8b318473fb9a6221970200b4311c39cdffe6832770f85569c58da0be34673e2388156f074aa42edd75f3e468273306993682db5767d3711a0d14ba75655a4c64b9061bb831b6e5c2d0c58872fdab87baed90ef512c184250ebafeae305c60f020fb802c6a169db66651ab9d677a2c971c9b71531809c1c1d0bb6b59a74c33f30e2b32200b2328513dba98e30a73f4d20c3c18193dc02cb46e52684d3f209ae0d801de4b70167bc98f5ddf0885aa2e28ab142cfbc01c99be142caf3b3bd8e8b5c202df8b1ed4fe24aeaefd6b0420ce74816c33135f48233dc4a011ab8dc4cc13fdf762bbedf24257875e2e2d15818a38cb6617283417a8aa6c6a6c36642298cc7f1a17b65ce4902eb64bf247cf84abe02942ec4cbeed68e0b9cdecb995a32ec72683c4029fe4b2d82e946d458721ffa229c887196ddddcce7e20f42f24fb7f012c40c24526581348d93aa66c09d96d1e908df8643270af9e9fd8fd613e256b8b7ff340eef5bdc6597eeaf034621d3137ce4684aeb817bc76a051dcebf2691a296732082ccb6120336e69948ee703f4c50062ec8392a5adb464c004ec477fb56f457a5ae661068608d04ecbb556b577da687a7e67c73627c4c47aae74210fea689cbca5da2f031b5e06d625c1430ac87df6533504dc9f0c5370b383172d82d93511b9d759c7bf935d5230c213962a52a49bcf748044e59be91433e2ee556ff6685ab8fe3b938be2d57db7285684e1e0eae17fb4a2d6ad272da5b729a7c2517ab1e0f550c977ae955c16cb20bbbbbc5a26a2798f5e302ead4183265370dfbeb229a4ecf2398fcdac89e9634f4c60978681e5712a064bb8e2b07a43ccacf7ee08b4af294d8f986bcb304ff82a3204a0a487b72a8f45bd8aba3f8a2333137420ecc71c2d1692e24dee4c63ffef09b67e1dfbcb222422bebb4e1009cb8e2d1463a353529bcdd97bcebd6da2f11c86089e3f20c4a63341ce245ce4ab3de841b081865ded615e8354b45a77bbd8704905a06d50c1c17bf0bb072fa79fa07617bdb52f5d68ae777e033d8ae8be6829de3a20183ee72485f73b921cd56b5a6a3ba036a1e1c4c89355856c4d4b69133ada1997ee7915b0e6e73677a1b48bbefb015595566054de1ea727e2d6513f74c7d52107fd901691a4019422bf1809938bfcf6ec19ab951431acb030ab3dc52a071498643d027a1d41adfdb31dec49acefd200401d66f30435fd0a757f15e7340f89aeee55f573e065bbffc9b3d275ee4ed7862f610d47edc0997caa88ef533a590fc52f082a472957c27cdc09651811966a5ae676a1250a11f912080ede317449c7c84ddfe6659d5d5933c22814764fce43e42cae02fdf3340f4d0a601843d8cae7219a9acf4314d4a5cb93c2eddba4f9bbc9bbcc7effb373a88f34a5bf444435f11eb76f3934a4f404add9282378e3dc15b06c008f766fed68d1e15bcb27a976bd9fa54ea0201d986bb5317f40c31cade6f4b4c203aa967d2871214ee5765e88c8b4b6401faba2b4b2302c0e529385d90b4430c657b15531b57f4c8e3373b01c225065c016998af985014e5c5e8fe99893b300b96ed219b6b5468e4ef9a1f0b960456e299e138695be55514f1e6ae3bc8ef5f525b9884aed0674da427341e50163e1da6e72a2a2674d8678cd01634473defc80520b38ba14dca438d0617ae9642afba9ad3834305f7fd74cacc9ebafac2f4c74d3bd68ee6e827179f52d9725e6599d2f0d79b5668c53cc76a502e1223c729f20893b2cbc85ed5c02ace565c9c6e2d7161e9c96a6eb1ecdb01c9f549f260cd4884599906444059193ea5b27495be531a64c60a375d1caf782b9c6e87deee7a2be5754434dfe11dd10ad41374608285e23ed834a44e2442f4e804e885b556e7a41a3684600e6bd80bc9a21aba2f51024dd1f6491dde1fa6f198ccd8f5ce8fc56d8af9c6fc798adcb52e96572e90a051e1bf94dd36e4bf40d851920eb87be3ed26ac19d32effd0d51947f1956cbfeb13b0fea0e74630915d1f79bb786363b3637f9f3709f894cc5513391db01cce56e827f34738c26fbb06043b3cfc08152ba04e20bc4bafcb94bcc791e7560f054820f3226c58715957015472b5aeaa6bd0fe0a0be6b328e14e95a1ec28928d1d3dea3d85b07d9f2a685333acfd0f4e4f9800a911d2b83accca03ee80bade8909cafa71bc4a738b05d56839f7681b365070707c19a06900a71851ab10e5454f3ca17c793b41cb38e1a70bd96a2c38d9d84c3172032436bf3e81030d26d35c2b1de54350d7f18f24fc4c70898a8ffebb14d378a256defdfec1fbfba3e5bc54472d344fc1ef7b392fda337fb208aa4acba81da0b9e03f9d71871cc1142e40f09f57a2afe442b077d05dd98921afa5289630a4f699afb81ac776a99dcdbb919728241c8b28701b5eddb2aef8e245b88407bea83b0ea893442c61370021a03e5048ea818f2e90f81ed46c7cd0e25827071b00f4a01223fa40171e1642ac94c184e212486ce299b2988515b187ca3ad1c9f45d85bea3fd4705443952de219720fac7e540a77ca02d466980e21dd140d2927d38979029180f10de1c8fed5d368ccc6f401978475de5e12091c62d0149f64c615150fd58f30407cc2bb3923d8da06da6a8b5f52d1529eb9929b32843ea11c5c8baeec819c8afc5848763657f84f6dfa63fbf050b3fd82485e49c30e29d917c29f5946fff9e6b80897fce88e0688ca7e2b878ca1e44fb4b4905502d0458041ce4cd42f948099266302ece1ed26d686d49881806021ce9e148021c6b21d0293424136d047df2ab35930927f1f41c73f42e9bea61765b54af114dde4b3e26234ebf42bd1108696765729a8927443bb53311537c043102fa466c854a4a4955ae5800c7d479abab134b365e59e9c72e83c8c8ab35a728248464794c4858ad14339ce8d4cc57b142dda3497775dca2c1ba3412840d623bf28f6448b4c2ea5d1f283948cb54924b2d423df8761c4f5782c3b12487582925c3a10153ea7bd91708e9196f57115ee4bb4f19708bd23d391a472b981285e486766209210e2c518ca341884bc22cc84267e004cdfe15cac0d7a1062ced52d1d44e1117ca4230a6fb57bc33300f36e9906b476c724b6c13c72ffaa5528c0b5b45764c747b46715688ca847a7153213de367f2b0c7eba016e299455b69d31e82bade1a677fbc3b702fae29a5689e2c90351cd1ce808b12257a0a81e02d7aa7e65f88953bf44ab5bdb6dff66251830ca3f25bf540dbac8dc7b78cdd7e29378071b040de96448ca954ebfe545842dc40a712887f35eb0cd32fdb6489e6cfe715c0e903e8b212b7ef048abd9d66053fe02d60f5ae16d1233a2cd5519050a92992ce98aefb72c163e35ba664a259f6c17ff66fe19891cfcf0254ce9853a8f6884c068031b553d9eb990ec723dc06ceb9419689a14aef187d74ef71675614fb9d753af8feec9d97c89abab3ef8f450f55e13ac0e480edd2f0fc0e53e256809d011cf6b854a84eed9c432a33f348725593419df15eb381a2e730121a881abf7b29e600d6419a008340565bd7c866e6e10a1ccb6a9049ee9a94d1fa35c5c19720b6e25f54c61821879078a03ceeaedc6362bcd0898557b693f587a70971581fd0518dd6a83d2bf9cf1f05f2b30a5f0ba290f83849587a3b795c2d1e350b25e273b5953d8ae5c8858743b5a09d0a2e9d5f772ec8f1226703dd47ef160622d3fd2b5787f17963393184a243ee6fe0861e8d48cd1f97505d6701512ef41da07bb277bf79bb973027fefc9c8a34ce27aa50a99e207e54bb40970cf63c88c8104068a675d515910d24b777619dac0b09b022b7f8e6caf8ef2c2d7c91f476f89de58c90d4c611143a7f772ca2955eb543ff0d7ef5bec1d40cd2b6d4ef97926ae1b98d709ae9a819389e26445727323fc7664bdb1c8f5bad2ef90f1dc82ce6a4682b7a9effaf169adbcc3db015b357fbfa8f9e773061c9d42eb22cd795192f4da985833de501e775f14cd58af70cd2a2c1e71849602cf265dc8a5c654411f73d1dce6997c1c6a02633edfb26da42c6a7006728ab1068f80141fa8d5f9630084026b2524186b34ccf9e3fea705dde4606a797e1c3435a1fbfe8764e4e5ee211e55cd368926eca0437fe0040fe3bf12861dc21bc736891b23112631099456512cba94706e56f50e175c1153d36ed93c85ce8ba194161efb5f9269bdf86ffa35e626f65655e0e1bbd9becbf1853d6bed5685f3fe1b2a08c3be820f2853b5c9e83c66f28530d135641343e3b06498fde7e68de70837ca49b801d8a91e9c8164e23f964979e99766431b86ab2fb4785dac439876afc34491ae029c7548401ee818d7bdda9e00a795c303b679c9ff49ea7d00f5587c8faa59e6744926d643ad35d84e2f8a67ade9c476dcc8c19d56ad6ed1456ae4f0284815427c49f2277f59fb1faa29c6500f1baeefe9cd0df345e1a66bacd6adfec55cf2a2d2383816b48b6efa53ba673f121e915511ebf3ce246deea71578581a904a5ebe1692a8b71ea8fd9635ac82084625148c9fe6698191dd9556b745f8214b257c41a3962df35058ee57b6dac70ad887e76b6dbb35515cdb62c320d3b8f284cf94ba2e53c8f8a6cb4ce4a49b607125112ae54105d918f8b80670e8ac050a41562c3c73436c8a928bba6694c99dd5d1bad666879203eaadd31cb33292999a40d16d26befc6fdb6bc931622ea8fa0333843a397118a10892309441a83b82f84ae0fca6987d56972dd553229fe2ae1e48a80b6370d030bb131fbdfdc1f63437bca62fd35c4790ed5ddf0f1b3079231a9926c135072c58740cefedf2ad536922e3012d7482fc2ed5ae6a401c11aebcb0dd28a0149095ca1f27a8c13722c782698351ae6f7819686799733555a5b22029891d732a365be636474ae59cd31dad8c2972e42c6ab1a04b0a43a9bdcf1e6f1edc8420f10b7c2b189e148c60f05ca77d8ab184e0ae977fc8ebca2c4ccd8d7d03e8f46a4f7797ade07877c1459732a0898f62f1dc0476fbbe4421c6e7dafd02c2b577a4753979b583072ac9739b79ee952aaa965e31bc34a7ce2441f7605457739c0ecf54e8cb11c9604679b01faa0191869a98bd942d11ab993285655dbe54e2e920ed5038c202d18cb7a9baba3dbe66e2dd025ec5685e2738e3c10001ee0f0f548a71e4b47890280b675a028de6f66a8082cdf4a539cbdcf2959c1a2dc148efd55218fce24d410d401b64e5a43a2bb4bb8876c75828931a1b86da756dc2ecae9c4c4d22396129a76274c92f0df2e88753bef5b4ab8fa9a8ced53e2352c16b9b005ee3b5dc14855520f2f59fe210e0b82351c0517c1d8d16927c681b2c7bd1d42a6962093373ae9294fb7cf176cc3eefca7eb2ada239f4609bbae5bbab837ef2156d40c830203efcd2f0e289aab0a138ff68540dc0712551149b4586f631ba07d6f85004ea84b28c1cda635af7c3b231733798faee680fb909cf1c6f9322da48d17072fe433b64c60b256950312dd51b932087bea40ff0ae20ea468cad25d79fc0ed37b3b9305f7864b111ba58cc4ff7d23fbab72170baa458590fde830231d98caf2be60abacfa9150243bb86c0db1504281c9fb9679f33eb505ed5204a9fb7e5c8b56b3dd3788d328e2a9d15b445f9ff60a133f4c6c319fc1dbf886d6c98a2097041870447cdd7738b7c81424106fa275153a68206ee7f21d92da4153cd83c944f827cb786538db0334224640a096050075a01044200eb135ea712472cf3fbf96a67d9180f6ef247f0f89939e90b159fe1f3786adfdd6aeca0e092c224b8006b17751584ee068b5f5695e2966b684aef5b11aeb8a8fd2044275161210d9acfc8998ee926e7ca16cf38eee05303f39a2bca2f8e081954032bb78bcc3ab956a5b2060648fff8b5177eabbb704f1b5555b5de8b80ab10947cf52c1909c7784b8239e60d25455f35de3fd4ad6f6614d6fad5e596c59cf48c98787491785c7725a7fae719da7e6bcf43499db4b14a3120bb794bd99f31404a139f93b8e9ffe6e401b7e200b3e4c9f6d32310473021aeb814e8916c3ada7a5e82ce39246b052eb9219e1ff4d5a00e37140b96dd9d18fde32874334ae6ce2a22db0e31318acf98f351ab5b0e4637282483576f3906ac7c90a40d2a7fac82d52e208be812eb4e05fbc939496122560a7be667e82763b7d60d6150c7f09e10c14ac9c62cf551c45ce322e62fade950371aef2f37cebcb8e1ff09d302d90e9b85ea974e892d929f383ade43a8d7738129fb5388e388ce8a429783d6602f1c1c6cdb7128cb14afaac30283d87f5745aae4a74e5a72feb93da962033d6f4f49400fbe0ecd5bb40adce3f8fe41abec029d5eedae924d0da2df33912e07e09f40d9f998872bb06deca410f84f025f99f02af712e86d3e5e800f7a80da30889e99d62f5887d0410931c807f70ae12790ff941721df1fc85f92a6917c4653c41dc00c7af993400209ecc4d0e8e3c3d743aa198220e45935903411f05e4a033024f88724ea8178fba0eda71999c2c00f2bf73c888875bf17f96c824604cc1688c602e13361145d9ddf11cd5f1edb9e71f5ee2def31510c1578593b75c86c6585b6ae3f2e89d822507a1f7af736133c8d1ff6c9f89d3a09cd2e1ad8dd53a91b9970430699a8da912a6efc3c19d82fa8091e28d703c37d8d2293a3d6baea2445212e8cb1dde0ca857e13257ea888071729b78a170b6b6a805335edc1fe09946f59fd029ef046003dcdf9b02b2272d82721cbecd40d0a8ac8ae0b4e9f4c71c63076a3faa191aafbf096b33ea760bc74d5d78f0826b7b03657aab5b67d9f2636ccfcfcea5d9fe9b312e155a8d0971a78288f78b6a0bbd1384f40366383733f59657cd3151592a4d7a6132cd704c3f025224170cb3172fd66f49efce417cac2ea4f2882d651d3824f9a0320b493df7b410a38ad2722032a9921d06c655cc0c2b5bc8a6476b27e132c237d22d5880275f6a48444be5584281c0d76afacbd2820a766209cdadbb523838535cc5f7f69be0f34e59ebc9bb6ef9327a24e79b60b0105382b5b7ba673ffe1251c2995cbef1ee3d6efe8251ca5e5deafb2c9df5904a401033dbd0f3fb4d09a24b9d5f28b29ae396b4fd7100b6eb4d50e62d1af29f018930e0f5809082b0abd17f1eb8baaa0211f2bbf6cf1e2a51528b71bcac670d6df28e3618c8419dc63d4d5bda899e4bd4c004f13e7bc6f6b88e287de654a32417a8e273a90c588404653687716d675302d3f58513fb6b293e9a9d0c6c868a99a129e3627bcf25cb531d7a79d5aab4413871c8a9ea543d5bd16dd3b811b6886772a65c3bebc39d4e59378f1d63a2f67d48c6dea1f6cfa455dd8fbc38e939e2406d4e7130095f732b307d98aa190a4a32a92510592198e817211b43bf640b8b795b262a9a6274227d840f7605b33181316faf3b49fab71ea06b846f07f4a95baa2ea6e24a28c514dfa730b88da2c698641005672ee338672213a88f17441710a9858c9c64d6c7755689596291866eaf2d7c09c76433cadc46400fc522429928a004ad3f748042adfd9513c207c0208048295808ce7992216693e00b57b8c4bd24614389fb2c86feeb60dff532bfdedb74da93f268e4dbffbe3c0db41aa8ac39399b7d3b0b60d8879bebff4edf4cbc52e84ff592521a3dfc3b01e98d42e1cb57d1fd6de43e9f77534d6b2922a8ca937a0a6bcfa4c70be0000da13c8dabd6777bcdc073e0e08b95f3a3efec6cef025145dd1fa7493930c8561e121457a653ff46c225f1a56edc767b0e61e236c3529f19beba65e2362ccd2715117aab698829e1af7ca222bc91bc101aa9b0a5012ad6c9d4a35dbb130eeadaab51978c5cd5b04f0a4494fc432b1e485fe3c4b8c0d42060d5018f79b3642e4bb017fa604a00a25016e62f72ad335a8ca08516f516201b7d116571092a6413bfd03e3b1714d83eb6f2aceff06eea44745634c4d44826fbef2f7f372da0ee6bbc34cea1033ff82d7fa2a831f0067ed02f0f1a0d79cb26b4d0521da7259f234b25b8b5532c12659de38f719861703dc9b4cd3083324e0478259e42483a61d6a44e84fc3285bfbeb65c2c8e4960dd0feb02655f7418d81ccc99288abf0486318c8fa04c0d82cdb46adc20600e841e4168baa44f5bdfe615c66395e141f76d45e31599ccd9189afb234564a1a787c5e1395ec8cdf4a3a863cd134f55e49409e6f4446d3c18058fafd1295b2e19d0666b4fa2a7268932d307ef6a9cc86e1ad21da32942b1593c2c3462f9d1ffb36b2d88683ba6d7bc918b34cd5f1d412cddcc2f8228b34e1d96933c6c39bea970472655c53f977348e1412f7d0e124b478cecbd58f4a5063ce2059e9536ea47b0e43172c2a7c76a8e7bfb3d84327cd7ca671c957cb5c8b8d14530791061faa95277e8410eb3bcbc6f38daf0d97399e6467bddaa5c55dbd309a32e6fce3ad2e8940f2360dfc8f77b194d2381e55a9a4823a2ea3238041a4238e8bba966123f548a933c8501b10bc6d8639be1053a756c20e46310cbd5bb056ac50edf1ec389f8ec1c041074590c964a2122ade63a3c7857efae06971a23a26a0b18a4cd9c2e94cb146434a928b73a7aa6f71001ae4f61d0884ab43299117bb1626ba9b503f81a302380eb1c9275de3002749e05f112d29f5d476e25b9f97b905787cb7c70ea14f39909099a739c17f7b532216e5dda5a7156e2d907722b66c54ab23a69e0506039a33a18a3abaf859e19e1609d7b3115a4a4bcb6e690e459150b416a587202d30bb5395677059bfda741626babedf072474110c7b8a843a17a216ac57a9925142b179f89e537d0cd57f3739f9ffd50a6f4c02ac5d8443fddc2aaf53bd9f0fdb018c53db11a97405ea87b44b59c85014fdf0a398caf91c670cdb0805bcdf1e741ec4d36ab77e48531bd6afffbb96e6218f79d358a84e313c7bf4805660c38f76e0aeb106fee5197976f2f9cb0e6dc90e1c35fd09f9c68e5f8e3f45d8115ec6f8fff05f772c8dbb5c0d44f4d90ce03336d579bd68d22101ef72a9805e9c7b152bfaac0ea15c8f3ac21b1420c820d9dd580163fdd743b0ebe9a26bd0d6d2197296225e7b18f3e55d8d26538c5728edc98dd85567797aef9298e00f13c8e9bb80a2d956c944b42de90731553906f8f41be77af8ad36e7cc4a58f165b262e9b30466e1f817175d3a1d9dfc36ff68a0da3121ebb820ef72888c0331d0f06b1326d76979d477ad0438ee97710cc1c86a03a21ec778c4bb7b18fa9e7c854df32ed9658c0734da9507b8e628f4f3aed61101d928b65bdcc84cd9c70ed896190f58bfb1be0d489a4b52dc003987a0b4689642758041032aa394fa7b8e04b3011a01c11d020944b2cdab16776f0de19e3fe91e0df5af3b067f760e97245383f4d217e44a815cac4131dc5fedd83e89302d9d38c2997ed03f868d997f3a0083ba568c8ec8cd91082f34175f74c9c9b37df2627262079eed831048b345613f15c2ba1b20bbe40549874805d1635716205574248f2001195a9bd87dde611de01cccf96932a3f51475930903145ed5a5c481416fce8ae0eeda25e966034254c8624db7a5376772e735476cbb0ae36264e044f4ff53f3870f21a7199cd1764918a0c628b054e915e41fdf34d9aec399197827917b2bba39c0471ee85ad4c56d1aad9f2aabf35eecf0240817f197d0ca5844acee815619c6afc4df5c393e8c306f5f26aad7528f80321577e391950e62d8679be6e43120e186df31a8091719dd077c32d82a2f7a90a2ee14eeacd131aa6dec47637b1ea59f52ec76e63a9cc1200f666325d107dcb41239f72f0ced12982d2b6cea4f16bb6f4fbd6aed01f661c877a80610ee1007f5e1cbc83c9507725a9068bd3f8136bfaef15da0f27966aa17cf9afe610771f678c5f70f79d0bb5bfb4b693b9ffd0928c0297ed92f60f06563a4fe924757622554f32d673493135cf3a9ca668e7ec46e23ab4b01735857d18a6bf06981958d7ce7829fe0c12c34c646732fa1ce96141a65e5b99ec6ac4b8c131eb053b459b91ab9b09cd1a223bd789ae6ed3e1cf17e0b2c76b90d34f1cbcd5a002d8354ef18c01cc9dec8dce67e989119841dc514f427ec2d505453fe1d4a932f46bb1f8e7d9a65cb9fecc307362a5d7eafc3e4605e7b2364673ba95043a78cce173ac38c710b2620e06c5370120c15869299bc88b48eefe98278a83d35c46168fbdea497c34c613b31d4e9cdedd4628c4639bf2526fe1121a1277b03dff90605746b735eaeaef73fe08ef8e7e333bdc8e9c00c245840db48aa3beadf5538b3ae43f098870d05125cea03bd43f6cd4c055eb8547bc74ca32d6378bd0af58a846ac0965870a49fc74a7ca9f4369cfad9c21870220d76c42fd4eab9ac029dd336c0b6afec4f3a48c58551d04c6d15b9bf18fb9c2c2890ada1f046b88fec1391e17c60ff0bb9bedc271d6fac69610f17e8ea37fe74a176c82702f4cbb1c1ed44071421a6e296a1945363ceff1ebdb3943e72452c35ccf7765f378c4103cca698204c23884cb6048f4576c7cfd9de75667441d29740018de41d9e68004915b9a715dcb142480469f44b298bcad5d7f50a110472c98efa412de53ca818e04066d57415b8ca81013cdb59a006c157ea511efbdfcdc58d86cd3b2d4b96b209a53591cc17e3cb00762d5f62255618be056110092945a09310fe890d789b2c106b8b6c8abd6b54066246732f7e5575628795bf7d4e503358f93505bf1c30f136a43740e99322ce49bf9fd6ab4181a0f1fbb2755d8d993d3827107d6d6f8cdfd7691de9d8ee7f96212cc5df2af3bf437a2663719d788c39bb36a1d9a59a7b5d7ed4428581c248863bc71c33f3f8a9064a504f41af47904373c583e04b971b4996907e236289a21265b375a329fd217223815c2182639a4c54c03f038f01f2cf302b8c1df57710c4e41e58a8dbc444ee2cdc05aa4c168ff056099f7d1a9626c762e9992b459eb28c3f7db9ef9d4b63160422801b711f0413e0bb555a08336aaccb2aeedd111bb913ca118319b0061ca2bde5d6bb509adcceeaf11d2ed69fc6874cf7fd4021d8d7c8d7da0c3db868499997379cb22c41b8a7a408e7e5c1c5450835fa9e10d3cfd2fc9740245f260d4a0d2bd85cf0292cc27e7fe4858cddebd82137957308175dc04811a349e7f85f2c1ca58dfb9e04cc843b66042e0b40443f01f505360e79d0c0d0d7493ce4b4e7c2e00192cfd717b0b39e5ac2e940012072d8772106cfa90d9bc180a1c6dbb3ee876a19952000e51dc6450c02caa5ca0a11a12014e4239947749683af4f55352056abb144395d9df4d8bf67d4526925451039bdda0d1c744da5e4575145989108407320efdfe34a1165aa3c4608c0bd1a333bc68ea071363b64f8e228a9734727b43eb87df8410c0c1b05ea3eaddbcd7f53bd179817454c7ad158667143be6f7c025410386475aa646816fe4d30e6510abe49c585a5e9fc61e6262a17ce2b153c192ed05ac637a34db89f52a7180f9e0022be561ff29bd901111b5f22a98fd0ffb698270bdc19718833fa847c615f253be815c2570c5c9f87fb061cc63cc50741a52160eb2f6154dd41a74658a480420a1ddcd214acfa5f54f2dc0f108c4753a11d7a9b8641e7c9fe3744ed3d183a6b888fda6417d1f27c53cac96ec2cbf62c6268b240234e4283f07323b1a7617f726151f2e16c668a78cfc22507b1a164f81e2b0746cdce2d0db3c63f04f82456189b9469d6768dd409c370526c6d3fbcaf1af7d8dc7675b6bd63e05444eea6d5e41628db2035349dc171e3af02be77f81a6822d85c29bd5dea00b6d9e45f2afcd18f0622fd179c0f05209da3435174bb789ccebe4a627f11a95692eda3d858310b7a036d4a0eef435c74a5c9fa344a9cb37983b97343b4d2f50a5a42bb88854055d14828fd99e672694f309aa168d63b7a1e5b8441d5f799ae4fafb4c7945d8cca24d40999fd8981889073c7dc3bbca9b060a0ca2ed5c130920b3b0c03893bb8fbd3b5b9a79d92bd31cff9a8fe53deca91d0f3f5c3f7deb1221d58a894e0ff93eeabe670a146b2f18ac119a02b2c74edce75900eb87b2573e091034eb1c8daf9c7a131de8429320e6d3e9116e789e4a53fc619cea1e670b48305bcfb27d8dc05452e1f851c3978d076bcee70dfb8620318b1fe7c9fe3ac3305d737f7f51a738a500a392e7c8e0f2fd2dd7930e5392daaae1ff131cdc3800f1bbd40978907523751d460f1df51c0e8c8b51d9a0dab61b7f580489eeeac852ff6e8ffc6f468e8f9d780a6a650e9ebc3abe5d797e021a0f7597f2c6346252b7e8861070c1159adf9955993bd1eeb7d0ac155864f4094f9ac658dc85f2f32cd9eea110fb58512ceb393c8ae90aa9e31012d08ece5927312938ad0669c0a1230408bf6bafc395ff6d965c0aa9b0687a66639093cba3c5e8251e5f1fce2502da951447c43d5d1acf9985260f7a092b7a5b3e929375e635bd6a8829a1a1ceb4768a912f6249f6ec062a0ba9c0334b4b0017de97017fd2a7c2de60ae95f3be46460010ee4bce35e1f7ef15fbc0ba6970f8dc202fc1c3937fa6771c8185e65fa062b7e1b7f9a2ba3e7cb829abe5f5c3a527f60fecd62d271dddf1b7ea1c4ff19dc0dce809f26476b9f37b55b8f74012f2352683abb408053415a89e912a48b399f0b7df5a15cfb7436c94ccbe6d7ea3c94e2464e51a7e438a5ddba6f93ad9369884be0034d000a401ecd5b43a68ae644e7761f535b081cc420ec76b50165d94ae85a8e6da6892538117e41ed23cd75272cd98b6ac35925b03a17492c23875231abfc0f16f7b90c3b58f4f92d8b361d31696d704bf459abd1cbbc3aea66baab105f7b49b8bc79e7193aa07e696442f3a327c5d097f0bfb2f6cec225cd137807a8f87cba1b9fedcc816e1a0bdd6e2948f07e2b6fb0b71e3cb8886f37e0bea940119a8208ebb4d06106338d04081291cdd753b1eecdaa2270c16b2093c71d53aeff1494867d0c4bd4606785e24b7ac400301901f4209c54a911466b494b4fb183f347178c0747ff1dc6432d6b1093dbb36e292cae9e35fdae32ca9486c3c0b15441ccc27a90c062c03766fd07d77ae2ba283ea85f829e46c0231099d836da229458d5c700d3aa092d510b2b4ceba1ec11650b985ba1709604ca8804c3d3c18a4811fc433980e7d99df65515c5790f10410b8052080ef208b145ab7ea40214168461ea56d93d45a8426c30cc2699e812ec620d71cddbf31eb3974c35025e7acde5dc0ef5ac054a6e30841c805986cc1a42d1a501c0abb8e6934e1b56a40d70d0499de386a66287de65e84962c86645ecad5379ea2882449035d512649a6f6c88a91085de595f4c3b2ebab803d7323aa00c0a7358a47d83f2bb9094955494fe6853495c4e52133840988ab8a72b948578afcb2b209c126a344d56cfe56b2ae33b5b89f96c7d6aa35b788bd06ef573fb71a06075868e4744af71a4aa0b665a4fe79576219e41f2ddb8c77c498ad2167f11aca8c9376a0a199cffa877ba059bf4f461a1373a93c450293c3300103d08642af2b8c1c1ccfbe3fe4442194786d3ce3ca22015bc59259a5ff83908dcbaf623efd2c0a908191fff58aa355d48b44b1a82dce31b36a3abf6837882daf5047d4f5d546677f894b956242799e3a525b7ec3e825f4e003055567cbcee91b265e040d82581a7c19f7952547b41f157e5752f88bf60c5fe62ba56d0de6f45d130007c4de68ffc66af75ea5e89235a5f4b90a00647e79a3acbc9a0e33691e4cb1f18d0a7810442b49bfba1eb77450bc27fca89d6c3f2e631e3c2d040cca1f5579891fe1752f493555ccfb29e675fb7d5ada7e02c6d026d049a3534b342196b075e135ddc6078dcfdf4422e62400d5830233ab6b24105ab164512913578343620f3713d31a6f014b4850811dbeaf506122feb2041250a77720f52e746a6002ff074c9ee00829ad2ddcfcb8abe4b1d6d9832f9427673e9b8bc63de25ceab83be3aae9778918a1b82d6549683187c900bbe43571c4dc3673c480a3de7c9a8fff20bb320c84f2d20ae2bd98dc9cbd220330e27a78bcb1ba279b35e39ee18806cdfe7d7f6e3af5bb03401362a78d994227fd90ae65a5d63996280d857a8999b02e215da8d623fa12f3444ac98a94831d38b07e90f2cef0ec21ef7edb0e575a855ebb0a5aa777093bcf435601b82bca2b22949166a751e5ff79b046b76d730b000dcc8d75f56e7380e810bee5775d244a05f5b8c9568504f39dedff95b53ce24e68fa1e98af8cb77e5631e6dbff8f3deb62620b04ae1561aec71c8df9ce27bb7b7d64bc0b8de3c2d8525778fcfa4252e51ee6f1f366889365494a0853aac874dcde127c9941911b8606f46a9cd6a85dc027b7bc9cda337d5bfb04fa7bc193d2da402cb1e9c3f3d053b6301360a3806692be13812076fb9bb56f519ce815cca7aad43300992772fbabc34a7c2a30dcb375080de4403dc5a109d0ecc2f8de6f19c8b263398b839708952cd354ce68dcf2c6561110235908d65ef5eb404f77f97fb948b8c82108e41d4337ca53dfbe15f31db3ad487386435507d773e59f90a5e5a1a3651cd45b02773d8f71e4f8064efa3a902e815e1a0fd98446a1491f84d8df6c8e384115fa6d1ffa3265a184d3b98ccb21e885831ecddd4ec90663367c0620ab7810a0dcb19f101baeec58e171e4713603e69ef69c946a041c7cc1ecf088ea2956b55c8a9ff8b619d52cd08da70aed2f47a20fdafcb5d4f8912fdb914b328670ad64da9b0d335190d3399de7d611b9c6256b3bb128914c3ce18b34abefb09db252f2107fcd82efc9bc30190853c512156166a03f52a45922e9892f7c017e58bb3e8a7f1f538840317ab8bd931dd20cf69c64b4ab6af0cc35c56e786ead61cc8ccc8bb3c3c6bca3a0808ac03b8822f4747141704e0a783b94aaf7b16327a9787226d4f90786a832c5f2ef2e6b64111d9015073f4c8d67687a45db95651307ee1df2974ca5ace5481e1f1ea2bd6e4e41cd4036b4097fc8ca3467e030e785833f87654bb53570c28f61d52ec5aa93e37dc9f72ad8a2a30058319f8abeee5846d454267c231c982bd9a760a09dfb7824dd7ffec872384b01efb78653f9d84baa3e492c22a741d1b90fce50e79a877a28b80eb23061ef2c3b623cfcc0cb8a13c819cfe049b0b0d376bd0ddc1b8d2df6f249735a5754b6d16b42607981549d74e0538d751a716192777bd23357bf88fc3141d404bae76ddf7fbea845d6c0ab2472301c1a6d12274f3d662d91f3cb94222f8cc10e161063d204f11cc2152ae5a6116fc294cf47811e202a8296874577f8aa65c7d838c4cd9169052fe26a6382fa9f4ce52e92a659d41b6312bd1b50d2860b1c7dee8375c43ec40900223d9e35bdae127c9b459ed75dbe4f9caba443d2926514b6440a11362a04ee0cf73e407280e6d607f3e5c0646148228296f92815124cfd761bf584c90ee09ba17ae0d0cb94f414a65808a15360559c408fd0cc8bbfce46765eaaed9a4e726de66914211222d901d6fe45ce9b8162f1f60d9a90b31dd0d49972673afb202fed1ef043630776898c49ef67adab577e509a0a1a6f28f7e3d12288332ed022898c6716206176d8c9a61024a9e442adc7adb34ec8046447b1d0af89e0853c9491e871012940af7d30558190e150fba91bafea662a298bc726de678e109046cdf7ca2beaa64534234650af2c3b3f5e39580f8c8288adc95090e0a7bcfb26a7f1d3cc0c815e96e2a1c24bb699ab000e8a08753cbb53d45367116130238a4daea5d766deab0cf1b75376b3e3790f21a524a274bd517334da91689bf73e3cdda52854175b0e0d9058bc99101266105f0a24f29c8ac91420af246a264fb2ba2e0ca20e361414424f9a4529d1b34ddb9ce7af76f6c977df90edf25fe1517815c1e021de8c90025f64a97a0722d9afad4ab8766758d2f4461bfd50c9a91f8ceb27952a466a7f01bf4aebb3ee739ec2fd7e2d0f1eab3336d22b5a8dbdbe802362b14496f6a6c747ac0ae1dd13c1d084eb33d7fc1acfb77623477a5acb3db14f36572abe60141fbd4b56ac608bd45462f44d28fbf432647e2bb105443529eeacc5fec45b665f5009d400c76dd6d24a99a7a4ff2fff533bf9e931085aa92fc5c9ed7875032bb3d523bf8851b154cd57fcedfe464e908c1e984fafeff246af3f8082094a92b8d10302f001af04abaea3892e90d9085a04ce7c686dc64c9bae933ebadf1c556abf84f6d6b9fbeed692fa12c6e906264ffce27a9be730150c76ac24735756313f79fd5693f399bf50c823d5fdd8d7230d389afa31f829183f079f5e68822f1d0e891fc03b0bc384550475718205b5f5b1ca2d4b5c15acb571dd3d99de1e346147958693b1945054ede4800f78c357974c4168ce71a78703d63ec9996fdd4e911cc6cf43a941c14e66f2fbd3c0433497b45546e9e244fe17ba133022791eac05f9ae3d27fbbac14e401ef94321f6e0129fd0b936f4f125978873d5d6931e6fb576a4cba0c7834c7a451d8baaa5eb7149cf7d86c43fd55e3a9d311ea3b297f54193d741dfa7b5d024499735d23e4ed0a810018b7437735935ab0d0162850f01b1be88f6f6124892448377a29ce8e4149cace7186409e9cf99d90090b3d007d3a83ff4b652280772fb9f3f4eb0ac5888c06fe1d7745769d312fdb9ed0186a13b03b13d2e241a1e91ff2371de143e982ca0b4d99b74296e47b9671e3b7d90bf9858be611a5f1d433e0fbb5be5000c03405ca82d8034ad2afe97b6ab6421cb6877e2a8000308f393007a7bf13403087068176e16a16ad61a4b5cd0546a601961a263f922adfda6950131b301632e0ea696c1921c32e654b658285a2c267501907930620641a2a028dac050e0d103627a2a6a154304de401721c3443c231988cecf63b6b853ab5ad4c32b4a1086b4c8b1e013ba52f26c96c8254ac201f85eb73d53a56eda2e4b2caf05ca4c90a9ba417e111b6d17c84082370b7e0a0951594cdc1e211d33c11eefd09653f4028ce32b8697b8837bd56e216e73d71669328718c2499b453ee458745c462c386b8f3f129382e3a4c54217bb2156cdc131286d9b68bcc7af32731a8f03d4bf602a4fbf9e14869f0aac5dbd281de986bcd23c2e00a91599f302ff39f6947aab6a329011f584191a58637c7a44f105d6b39b41947686418dd33a36df60e621c91e2ea9e0558073f7a20385eb359ee016a9c697a17ecb78ca760da0b1330a7de30694c6518311058a8502e3862e507779d14149c4409ae8b10be32d70ba038201102ba80e295da21db101ad32a508796f5e0597a49c03c04f33f9c108268c861275bbb0e12a9d57425832dc42b682d64e3c9af3e157aa4408cc48d4f16a59a7f4860950172077d2b4693275be446499064f113846ef55aa755b06cac23d2179e3a951b528866f2872a4b5b599367e0c9625babf1919b3891836321e74c30a7701b2cc68608ba2ffbd986139883a47e937ab0972719d0813605bde34bd62e2106baafa345c03d6a07b8c8181ec883bdea7d6e5aac78414ae6402e608477a9f3d5d2f190f817d8be1a80f78d2a304e01ec631400b92f577d585967ac41f03511ffc78bb54a33cfc21317011a46c1b076f84cc4f06b42fae1fcfaa409ad5b93e5f6be0f0a60b02ea5024162856e3c263758b91287a95b1ff7d6ef443b6df104560a3fcd454f76fa30a5bbf6aec7afed8950e5e899ceb586b5f7e4f112f5abb49705059d4f2339a709d226c4e8ecf57e300d060b4e753e7571a0a71d7262c974dbd14d16f4995241fa9b9f8abc9305b81022f8747fd0ebfd7d49c6700535909afa36bcdee8cf1f8f5e757f4fe51c4d37b2d23a58abe1027bf70c445d4fe540143f0dac1bdd65477e96679efb045119353e2d8bbb87ebef4ccba05460030dec2f105e4ec8cbdb6237badf6c66c009d4d9c048aad6cf8274b5789c809dc8bf4c5b48141010977d55ed8a5c1c6b1cef254fe8837b1d0617638e48474d70cf38724d6d5e7442e4cb0185415aa02b463002d052bd4ee3f92b7641c1a33e609262611486e3caa3a7671e275e84e803b408c408f922470b1129c58a563941e8d4eb1784a614805c5a56e547b2637062a1cda4e737ccc41f1487874e0309c8c081dabffb52347eef0de26a73abb341f976f82ea82297612051a4f8240150ea5fa0b07c6521572b4cb827e4657c57e9e8e08362e68612514b75efc86b805af9cff34cf78d74f7f808d471506be09d0488297c168917044de93c68c2776e578ca6b1108d79429a8ff8c721a520192bfd560ceca52390b16d06c8b649aca0bd9e9f3cce24d2bf1fc6353aca13f95b1a555740685bf7fd9400615e76b6f64221419a31d5f762466068051630aa8fb144f2f15024c36d239a2efba2522a7d8ab84d563805cb19ddcd91c716b476c0f1538cbd034ad0fa73f29ea898652f712d060cbc2717a50dee8a4f2fbb86a350796e8676ae1da0737a83736090944627c91775baffeeed78a33f8478fa68e3cb496e129e41da07bd6fb2c41f17c7c24f803372c542a0ad7e46a7e0d0c753d95e60e8913ce694113eec95d280ad4b55488db6b71350fc6de4d477aee70885b8a22964602ac0351fd7aaae7c3a0f414239d150aecb50c07754bdb8c7e566470a58f38ae4f6b4eab52ea4ee2b6cdf970e2ee609179338d5ff506d46a52621fd44f5cb1550d507443dd1044e7f22bfade45b97690906d19f6788ce2a4383b64a791dbf395d0b1cf9076b1cda679908ee4a7ddc45a14b725101b386b17e493b0ccfdf647a01f5f115273c2eb9eb9aff8e20c7c2708091262211eddc41434516ff09dbc28c36fc11b19c6e44181070af03b30f8bf4fe41e69b27bc186871d360d27b5775f93b7fe8721b28ac5d814875eade81f8b656c69e00ed7eb1819534d1248ab61d52f84f1e004a72caf5c8df0846beaa4e8e599823d78eb383bdaf47f70ff92ba6b4fe16b352a57b370777ddc87ae43f2b9f7b3a8f5c39a96f3ef7bc82ba51620f89561aeeb936e806e0f3b426a4ef0626813dcc52aaafa76fc47a5c2b943ae8594d04366b1672b3486073d7e298e802078e9464a56fe77548afa6d78f996245e623568888b98e57b1a016f3f15d1cb17a2bef10cc2089fbe5a2203f6c505886104be9b8c6c5e7b3c3b18404d5ee37d254851d495a77ab348f0ff4755e0a38081f1fb738cb5864735903ab0523cfe065236f1e6a206dd86effeb3c58565744e24d26f813aad283c6dca20283303724143ef81dbd25b625c47fb7cb478cbdbf53d6391e4a6d6df4cea26900b91062025a850b70b0feb918ff7d43dcd06e9ac72b0e7bcb43f5db7e8be0200c2df9156ef91f7aae0f97fb8463080cadce9d4507be5cad2ab54adc68bc22e64fb3e3ae2ba58f99e8639fad36371b889e7f899d214da0418dbc3c4470b87552d8508a14b7f7201090f522b13a64b765e2c42ba472b5e0395681c8fa2b272207738c4439377e2832a87bb14b6fbe7d90342e9dc69a3d8051c6fdd602dd5d1f197f862a4023426643e8f0b3361c5ec2fb76cf0e47838fed41ff25114f917fe9d96bcbee1dcb3ec976a9e2e55e8ab01abb7afb39eba50a9506845e5ae168b13b2f55f0d9347482ed87540484d3856f17b81f7bf0f1edbc3eef5b7723b06311d84617dd769000aff6c2305767740a4a44260d4408d5ee0fe6d867ac9080b378cf41c80f1cd3aaf18fe8fcd5b15f203da2df112c103d3c4083f9f184b6ad1f22425b1f188cdccc215f8e0311a8fa202ecef048fa9f97471db630271fa81ced5d8f4788d350c0f1d85832c9e7611a63ba30e31db103e2140cc352ee720e4d6201c01a96173a37e3478a59d6a046d5afa24b3473f368af8bf0dac0ed40cc1f7ce3c1647ae998804bb353ac7f65881feb846f681650c856ae1a2c6cbe71190bbe14c0a948ae24765407b168ed6f25d85c22b7f10051d8aead42d4d915b70599c57662a8d5fba727034a91af6d705d432a5c688a0389ff69164787251c00f9217ef9fa4cf00c2536ceb1c039db86ca2ba39f1b63921b941e1d42c7e98f0e290db381b42d7ada32fb6f4b07127ddcc992e2f178a610d5acc487934e0578d07d1b0a5d9bffb75972851df09b982e8340f18705f0af40143c3db451d0821600952638b05900c965a2ad665938d625123f9ac3673cdedc791d7704c3f318a08aed37e99f7eb27bafc2bc49f93446b28ebf749c27dd644f8fe245a0d21ad06b16b3f185ccabd017d1bfee04c4ca16d92a3c2f8fefe16819161cbdbbbde145c0dfd72491f31d25c6b9fc661a5904589eb84a1c48aff52a360ddb3ce0dcc9cf64740d1ea822a08b2145aae2a0119063906e4ecc3706571d595ebb63ea13034012f21fddadc68769823642b863f9d33bc241523b221ad37dd29893b8c5161422e46ffdbf0e78800eadb9da845c5d5321bf1534213fd2430dee2aaa31910b88427e2c9a42eba7ebc52dab082a09142ae109f9f5bc392a22e4574a880323c0132228988b0dcd98faf8eb8698bb6160aaf3c5322c010bf6e1688bca65d5f791f5236bc930cf7abe3dd00096f3efbcbf8ffab1f34b365ec1dabd2b1f859c7c9f062b49ed5ec2009bf90f50ea4db1f983d5fec538dc358a2ce630a96d0040a8af12bbc73b971fafe33d98a6985f5def9b6c8c9cff80b847f6e144d0ccc5f0073f57bec3432e0bc4d7de3c05290edbdec0777bd31e7a83466405a3dec93b3ca3f9a7ced23c5961574568d370293ffe64c7bf559302ea6bd0fc16c7f59d372fca6ef20e24d26a65efbf96721e4889cb4e610520543158643f43b18598499627fa4acc8632bf9e5317994a99918f8c654d4bec62411b76f68680ade5500215d49acb8331cf63018d51fee0f665d01bc3cec895d05bb6cf8f0603014c2a1eca7afa0a2e282b7fdc63b094f293a1b6ca80026526290d98197b521e2b5175ad6723ec71cece2e6f650dbeac74610154c7c1fce1f0dcc713a6a426afe73692097042f0c1b9b7bc4c4e61bbefe2818a979ceb0404fe60a730e8b8df770815465b5d7ec234ef8aeec66d14ad2f25102170c8efacccff3a93946f7350143368afa0bc65de027db0db117978c073317d4838c9a5b140abfd68d9f1b136937a1edd86604f095f1cb094709e6b6ff66212ff29c0ac106d161bccb4aa66f383263bc66c924793909b3ec7e6fca29f9fcbbb7ebedd1cd0d5102f4146359f1cc362905d0faf32a863bcfc016927c1f6486b31193dd5ca7f3de37561ed4e18f14f39343c148c978b5be431c4349b511fd220e726eee022de4cdddff171d956914f188382e5c83b2317ca21266bc761aae7648b2d7a3243c29d96e3df7bcfcee559dea2f52f31654561fbfccab1390e4c2c6a436a66f9a453f8c2af2e647b577788ee386ae6984d68ebb3227edc559781751ea0320e1626b75aff587289ce04a6a1fe04acd230ac85f9ab58b9994a8732bc22c2e25434e69f88b34f68cd3e09a383c228647930c903f88fa2ca6b8fc7f5ae2cf46b99da9e6efd7c3855406ecd3ab83abdf8edcfda9283ccd95d17b4e500ede81967fcd7798c2dc9759894ed46ae3ac9c1dbf228ad8f15742c2b2efbfdb287e574822c51140eae7983d9958e40ca8f689de5361685c20eff44f0cf0923d8f5b6de6d28c2e0239e15dc55ecd392dc68f1063679df00b8178502d7943780674dcf5dcfcabbcbb2853615c4130b5706aec20b074d2a6058565c0b7a12cd0e6880fcc221f9d6c184ad336ae21d71f187bb0014d600f2f7ae93a764722d60ed8c96e12105a8acc09ca0daa53269ab0da10827c5d8ba322b2943953bad198cbd714bd93c06f0e017104a2c08a56cf5121755040e68aafc11ca5d2e8308d62a4d761ff257a5119a63a485624ca89162a01abf7f0227d8ca07dac6e54fc06b236e672345f07a03828daf27705a77964ba15db181912376b9765ddf9a2a0f62ec03df15083d9931fed94972de4bbaf6ca45018aaeb49502ae6ab41461c6b50e512be7bb36d590068a55045c1753e0c41d92d11e096329a5642b273ea91f7042794928ba2d92398d9365aca43b40c99ce0b2868983fdbb5e3e4091bc516848fb6fa85f6f2700a287d93874069b8600d101529681fa52890e537f331a79ac32b1930a41f18503666875a87be7b3ffb93266f78c182cebde965b2b114b67b21ee8bfdc3486d1cf380d17c52788ddb06dce4c298834dd14a032f791edf2b096f83022fc0d5420b69ebb9d739239f424090556cc8139082419ac90fc030f91dcfef2df8cefae62c01c9631f1a7706681e1139c7d25a2e6541cad2507ceb3c088487e8de6a0d60124f07f32d518c14e9f35c322793b89c74d60b6ac7988061044eff19ab982d0070f5134d2e1d5176be0e189470c26bb94e465eaa37f5564fdd6f2b75fa8449b964adbf95efda31a0786ddbb54e6337fe6a6a0a8cd8b5ad81cdc83b9e42899eda552da5c0fbe43b7bd40430ca4db196904acd59fc9dd6d2d9a1fcd896416c28677ba6b741f49e36cce89abb946c8b658402d3e71673f0deda41bc7fb6b6487bf446e944f04d7cdd1fe35d8c11f1136e2238274e338fdb982477072d596b83f0a4c56bc1a294ab6c403d4e2c93beb83bb2afa51d50f603704486579fca1a56c6823972cf7886c78bd09b99251d1dfe0684bbc1415c309ff09a348ea73477cd24f1fd2a87a78f2cb1003fde4e3b9786600eb4976e066c3f6418e25dc6ee42f9e587de0d233524e872570d07162edfa3770a0049c1c792098b8807e3f23b04569b2e5383648e10fb93b0c8d790e1a0b5bd85a7ac29d15fc5d77ed867f1310cc37fe6e37ca687a518c88dd5535345fb8b4719f20719615a0db182cb74f9576b3b8f1f8a439c04172a293f0c4eb34759cf7cb302e6ad0ad0527e05891b90c3b5122cf06957b37846e12038225f01bf6dfbe94e31b675c36c9b0c65c9db294c0154cad324a36b72413ee68af50c5e8f7076af3628b2230fd72bf44a721bf4cf9cddf65c027d642a18db7dddc0dbf1212ee4327657149c8b0189933b3a83a5da495d9fd6cb0a1c2f4520419ba8a26d79528d715f10335a0d4101d4f6da509a3c2e3cd32c0efef852114644575a7ed150c66813c0b7511e9463182517aa608fbdcab48d152bb9ddcb0494171f5253c86f8ff85cb1c905e4780910708422172cacc0962edd2f9d7b08e56756ef9d0965228d5d7c128c278b456c403c5756467cf3764a5987e0fd0693cf5cc8e75bca9ad130782410cab4d51691bf04f3b4130cb514f4de8b4e1acfb85ef18c3e689b2bbc07cbdd64ccb88fab44ba4cb9d7d7312ae0d4ebf8015fa4f9842c83268f2444bac6cb057717048eda2907d68174555cfc1c5bdd6069ec460da69962490f44e20041f43c23ac04964388e08d1c2db030734c10e6e84b17ad86bda100f678967dc6a9c8523469f1f362c7c1def807f5914d1397b254a21ca9d98dab989fadad6fb853412dfc3f63168e9e253ba61b39b2e3ce0165c5fe2de1730d6028030b144c3b3db810f773ce4f98dcb3ee5084a62ad2d076a12515d790197eee0cb0dadcd55cad4614d7d13468234b78b4d29625faab6768dc806c139011752dd36d096c57a4d9e4086915bb9d019673286ae37b02506283f13f2e272a31dad8dfb9eec4e2b48541ab683441fd5854d498757c5e0b0d2b3e31dd9b58a129671243444a72bb75e92fe7f32779320b042e8e1a908fea768a3c292e09320b415865054d17da8168015f0721de685faae1564ee66900744c59ed23d2a839c6cbf582341f8eb41120661b5363188a5ee9431a8bc29caac697344b6abe260a5d304be621a9a00cf171446d6d1de576bbb97864c7792b05161c7c34f455397eba9c509cac949bf71399bdb9ea97fb38277204bd22964ed90a0c0612060c56f7106af32da9f387ffe107d9d099771e509554c1fde9c36a4b4ecd4358bcebae2e29ed99f953e910b5891b7269cc9067f2c06a842afd18d157f86eac1be24a29c9c436ded017fa41a3a4adf75d5bb1cd61b35b2545070086dfb96b246bee7424b3323f379e3e398a527611ea627fa2423ee559034f58e01c67c1573971e42675fb637bbd0f9490d8d8b0a05275651c73d0f6ef35656b58fcfc8a09ba244e2a820769e1316ef82788093076d1e342e595f2b663217e4d2f3260cc6b1c74d844440bba0e4feb2798c03a0a0d045af4a16f65038739c54ee840df2fb9561d32cc8ff709213191d6631e313b95fff0de876c058e23f9d1eba5ffc141640f445c520680c96e8bf7431062921c5a5eb5f402efa7db7efb7998497e7c0caabbc6bfa51e7311ef277185fec4d8e91e0c8d8438e9c651be58019da1fdcbe97d1a046b9c0114488dd928618f71202672c33234fce51cb15f4a6b2733eedc8590044a72aa83fe8a3f79eb1e1bbbdbeb00bab7b2282e801957a693a4f4f0e5954cf88326ca59fb19c002656c12b5dd22e710cc14e72a947050735a09f168582d782d459c5424944948151263cf65b3ae5271bf377e11f235e82966524b1e9aeed22dc3ae3544ae660aae370792182f07d7767b4cc861193b79adf7c8716ae767b1760f958be5290b6fd1b48b0a12bc79be3a6e2330038bf7268d478f83cfefa3baef16f279a6717e616d586e693846fc49462ee5936632e0aa65b05834ea6fa78e6d4a9384bb905a1fe1b5d4fb6788a109113d9be875cdb7e33350e15d038d44b485c55f637f4cf50b8b6ac9769e400d78ff8f8a3ae6c7eba2a4be0763fc70e6350c6a1a2a64a18fb9c64cdfbd52428255502e0858de1a04ee3297136e9c5e588b50cda1cdac118452315f3282082e86b0c4be17073913718bb8346dd8756a47e59845d9901718d53a7b58ded58f442d2a6f1fb1d35b07fdb0cc2cddc5f0e64eebfa18b0b5e6ce5fe687cf1f030bf7c5146dd74c83b1ab7e104cf47ec0bf01947660ca532a778c02772a1cf4d23827d2b9dd3c79e0b9409d0d91cadfaca5056eca7204ef42ed44eb1b1e9443d81d17aee5f0fcc95723961d6d5f6389e86a1ca0b1ee7a30f463e39954207a5049280d2beffa264e30192325a59caeb9573eae34741cc635bdd2322b11cf113ad00461732dfa3458d5bf81200a66f9f79779c844e20845b8a8b7b67a0e64edafa20858c3567d3fe433a7954287fee03240d983a7cb93b46c5fc80ea253a622866a994c01d4bdfc3e235e551392b064b4a434e40d45950006c38b448ab40ae852f9b52c45604235dec6145f6a66bcd709c71396c903a196258591d6cd9bcb7b7bf70b0240106b47b5949f02490c1450002bbf9e312d0fcda6ce5b27e8a6a82192700054155ccf1a118a0d84c91603ba9075492bc4d380275e43651228582481a1a7ea22c35f20be435a52b664b1bbfaa8bbc86861e077f48c84fc8df7b8eee2f522c5b0e756df61474d3e13a95c6fc96e92aaf5da158307abecace3ca29e5b7066b2db049290491380a641f3bf5a87f12ac448a27990676571ffc4a4d4d1446a7bc8f25f5c5bd609ddd79ac0120dfd0520f4f3382e28dfeab834166f4bb27af544399c117df06450565394917c364a517a7a10c310d18f3bf7ebec3605b704ca42242be2532b00166d6a3534d74dbf16085d493c6852eff9a4b002f6098923ea8707857e1f08087b0e69cab39090569ab59df8e5f1035e4e4d21673831592b446a05eba947988535ba06b3ba45bccd54845d26d36a4a8acf5b6782e12a55d90e55fede0fe5f443b57e811114263c668e91806da972a6f6d3b8e2c66237699a1c5a1ca4396a91b1531d458adee2592247d21fa938709647b1b428d7ec9e783b807ae1f835a8327aefc9f4b359364f9d6fcbcb1c4870c0034c53b7dbd09452e449bd224f1854843f7d54000a7d288613225a6c66381050261fa72a2f9d7912240201ac735ebf77e5210e5befb703ca4a6300d7c0424639ed8d09e06c41ea3a21a87b3950392a2f783faac210726220b492b38f90b45b46e671223379a6e606cbe98bf66488c5223c665151290838b8850524ef971ab8d918c70948a62be09aadea955b105ae642224c6762c520cfc44a86db2908536e7ac4782fd7d2f4c4a151aee54982a1b4485cf6b84f88ccc5ad420dba073aa574da294dcc89232b951f5cbcd89abb9eee2a80c5a3ed2114c21e24537f47c7e52b0555d7db24b300e5acb4375ed269673149d42a5e1e64bd778792ebe96445ed33c946f00b0b1182dfe1aa888605d893c398d48892d0fe6016fa3de9665a7422503d10995676f1b4991112141d832422f23ce245c08198b451c7bf9d64b9989e4b5575dc3f70d5b95746a1b8fb26c7ca9490670908d724c3708996af3b9d664b044f7e4950210a2896d13e54a8c1f7d0e6e72c37b53195b11f30d1a620462c02c65470738455bddff7ffb1cd289338ee9cf3bffd169a7650f9ee481544605c7ce19063a87777f180eb77f908f230738a822bfc675d2a4ec8efe0d24fe7fb14a0ea168af7e41bdc822cd1cb72f73e212f3f3d8cffa62197a2795d2857d0aad8af5771079c68935f2727d1930466652cce0dd947aac90e430b2a3bfcce0326298cd480b9a03f2f721eeb4506f5df67403669ae6c7688576cb9dc4aaa0f7f05a11db6c48af522f74a69b506f740c4ff115f2cea0c5d2a93d8d591175445daab3c24e17e0da5b6dda585683494afb3c7423874a235a89de6ea4dacc0a3c48d6e06d83687faff2fcea016a2822cc25d578c75ac9f9b68ebb531307f8ba97f3645361c921369f25656f9b38e547bae0685105b34d0d0e8cd3879973c2ee6f1d99e63a669534d9fa7baadbc97ff89fa9f161ae23dab1cf8e388d355433c816fd73404dcc6901c10c6cef13b9b5bd7fbd03dfab0c3100e6e3dd88187d78b3922eaa803e0ecbe3e6c374cb85dac51f0233f075337912e82e1f884e4018ae22005352d4d3d159d81bd40a5ae914f07f26926a2af720f373be19c36239f2e9b50cbda9dfc7533208a04eae2cc68431bfa0418a0b976d3a204f71c2820c4ce2db4dfa5f66d2e966114c59d143841bc7e82ce9a2347ba002a6d5777fa5f8588185503bd70362e15a845d103230c240cddcb4d0edada2be960d095aff43f20d68f34271cc69cb4d40260c368066823d03b837e0f44c57a602ca1b70703dfdc05bf56f0ef9acd46bad10b58aaae821f91b114da682e1ad3556f4c28875c2e5a3bee0378c12624825c4cc305d20b162ccf76502e3450747f0795210099606813a90863cea75133814bae7767698a76c016faa3ab21b61c5498633d55466f2d990760c00fe48e7d64417e3a431358598fc88f0b78fa200165b40e5ebd40e35decaee6fc696ba546c929c72813384f43923a4138742a443c24c5dbaaf6184b6badb6dd32450eb05ae99032bc5b1fd2204ee4f1d149f836b084573b97cfbba9bdf45b42f81f25223bd427fa528e3718e6baca056bc083b6f89994c838f910eb9b2ef60e2c3f59f9f05b1c6442df0844cb906aa7aff0291659954c1dd510d9dc99bcb9576e21e297124d771adeab31478adee2e34f27e5352d8a43e8499e2f3b07c51babeb5ae7491b4183a66bd84a1b6b4d124e87456ea612a4da147ad49b5fde21ac34ce49c8ac1e364fccf0bb099a75af914e1f60166008fdef134e3999488461f9bb227825df4ad75ca08ec27ed200cd0419131ba6335bbeb9bd623b4d9ee96dcb4126f427e8a458567b7d36f13487dc140bb2587fca48162adc67cd0769bcc09d309aa6d09b23650adcfe97652e00d1e24f1d8d2105e0523ce3deba68e3eab113c5aa633637cf7b1ee413dc288de57f0588ed27cf8ba0732e5a90bcfef8419f1acf73f6318f7610ab07902dddbd5bf84a4068f5249e6d5f6fb9fce44c661759ea3ccc76c031e814453d05cb71461b84aeb028b6143ea3e0dd4231e614650d452a423b5a601db29685d05370e369f4939aaef07c8c1f73f17a51b5e82296cd0299904a098a7c97025eee55d14b29519e75f12f5dc09f4baf55bb8e58c4e061c80dcee40b8cbdcb662a596e9daea7bb60c8ac2a08053ea37f2584c6a1ef5e2e02133d8abe25848e94e61af5381192c627f54a121934a703ca941684582056b24a07f26597e77e2fbd1946fd6760593e9d316307dd13c1cc5cd5030680fd9fbbe537813459e0f28c39d16d1f823a43ee45b6a9ce15d5c0480fb04a5f9178aeb1adec2563e0008b56d2882464efbdb7945bca94640a5b08c60743081ce4f6734b7e06e248c2862e84f373dbd21591dbec59f971fca3e6421e61422e452bdd18390e23bba217535a0907ee2d6e8112ba4e646253fc3b3ed9a64c791225174c1d9138454e59698ea223ae742e713764a6302ea9b9f1a8874b32b211cb9187e32db9f52475d1e32ff6f1e12e1ab1cb3f1d2c6b05298505a41591c202ca5e7e2133b9118c9175771e7e88a6cb11e7684493021bc62746401af40634583445baccf58f5a6a8818298b28ee91cb754cce90c4cbb821af31847361c368148d5ac8cb183e2c09643e3afed286a28ff69b0d3c1ae25127c05ffb0858edab6133faed63a282cff63dfda33de649d93e123009f2d15e7a524640030e358c407b157c827cb4eff12d763ea48c407b29db47821afa4be0c3dd8ff6d1107dbc411c7f09619deab2b1cdd67991b91c5efbacd142b14bc7a13a2939be3d1c1e7bebf4598eff7bc45f375eba14563e51617fc3a38149f439c206907fe3dba321faf41f21295d59fdfca8e58b928ba568f4c5a42f1e6151f2d45ccd7c03b13457ab29af944fa43c41698d2bbfe5af66e6929116a52ba59371e5473594ba7858572f041bc6a335a6b02c1fc6a75fcaaf76257db61928bffa9939e77cb2f20060836d8761793a9ebb16747c7b355ef4d1e1235e2935afc36379dad778ec53738477a6cc873a2e657b2b106ff968ec816032cf1e83fdcc175aec6bea65bc50478c97144384f1629037642615f6a9172f05ca97f259be7c50e25c2925273f8787c36b216fa1bc76d2445de3f5136fc9674d91bf153971e5ef68b234f453895d20d05f03096e3c0ed1e7c627a58c5db279e5c735983a87bbf27b4a3bd12f0008f5122d574a19a5a419364390cef1252197622e3197984bac8bcc6426a5c8921ca9df94321da386e79814cbe15c5bf7896126a0946294821527d6ba5a514a674b4ab9c8cc94524add23bf5ff756c3a5cce73c654f2ffce60c1bf39f93e38db367bd724e2b95e0eeee320773777777c7648c4ba639991963c6d8c3306c323327ed650c0929e7a44fe56cda22e5e7be45d62cab5ce4dc82512e1bc7338b8f25e900c93927bb2acd2a9d53c75f2f74ab29c0376d01be2264bac7cde71727e63b1a0431df3cbbaa7508114782b5f94a31ba84ad52003fd8d465242cb818c3dacb485850017e81050d70c02209ab821dfac1b7018b28f216635800d104c3304c054b2ea840cb009ec06207af08e78a0757b2ca6abff21855bca06b34acd8c1698d1b97a051458aada2835395266b650bee06537448582cc9ace4dcb0e6aa9e7170552a555293ab4ada82b7204aa56a6eea6d6eea53a92b7eb8a92bb0dcd40d82e801a8e57e0123515f918070516fad407151cf483c705284aa904191aae474485600a1564a6b2e0dc0a5b40a1f5c6a45511534e01e84418333785812f2c1aa38d21281d0c419480c61791c39515153ac649538f2e5eae80834260c1555ee2cc09d54a471670db09ce69c734e71842b6bffc089271ac663871459dcf9727e8d72e7dbd51457eefc0771a494524a1b3421a59472dae008cf0656a42062fe9cd3872f9cd8d003305a70848b31e79c938389020c1c512461f5824d8b5a6b553d11860f9688a2972788d0030a0a96d4134d442726cb830f9d1051504634e1a29c9218628342d4219d407b22c479e1244b41940d0a317aa84318695014e107fa6429b341070c668582a21ac50e3c4c178448a25140c941c2ac86bcc5350a24807061082d5140e1c4851428457185095c2062298a1cb8f0c4c82ba2310304cfb03097ab141145006a14e1c40e4554013a29420758952280e0a40726b4748b992a135dacd0ca8526869e0041145104af6856b1b10d10b12b82cd0013e19db8a2497d31c41bc6a42c66d56312bb20c03e31bfc5206ef99be0faf340c4bf566bc31587ffba212f6d552c0c6dc41bbb1e3f319f51c1b9acdb4b09e2e8a834f6d3b3bfb8b69f2aa5a1bef03b893e33fd98e4d2db3310fcae54e6d3a25beeed1ef81e84f8e27acc47c45b3ee3840d75e8e09d7ea2973e19b1c5dfee1a18c6d3088ecb1db8ae354ad17ee35a6b0ff6b1ead724d8ad0df382284563217e7dea15c9324ae9ccbe30d2c7aa473f529b6ed9e37b5c62536c395060b19f397eb0fcac060cc0a0728512582831f9c497b10138f2ebc962fd5b7d5f911217507ef09dd8b7d773a77f8fdb2db8ee188350843aa5ff832ffdb02f94f383b0aaed60bfb9234e82d54b3df6d9e2a89b882f1a88a3f3d957d4a9e3e637bfe370b4f9126befda59c72cd6e834de19b99b2f1fabe37f968b3407da4d2ccc6524a3265a2db0db6524a3237880646485910e6a58ae09891a24a108260840440d8a3080218bb5d16a52032d60036ab0342f23f5604526c5d32a0c16e2ad9dc9c308e01300b0e38fc9bef10a41ba57b4303100f35e6830e29c80009efbb12b7edff82c9b8bbf79949977ce9106e30bcd205906c18285b1f4910e8e4dc3505046df12843484b0d20eb604218d20323a16383b7078ecc03140eb1aa051a8020b50d244af95ab594dd178de110eb361aad1b204be846c327a1a42b00ca94228c01d19315b380de756ab93d0a3b16d31cae8b1b52933efda36d4dddd1b7377d7399239b20747c75fd8ecee7677f7d8e5deb1bbb7d34c0d5863c0c72efe2ea6408169107b49b1ae07f65e986d15fb9ec6b288bd0cd2458be130c7d2c9132a08bbf87980dc18262d8679956a84cc74b78de3ce89329f64661bba7f5766cccc9699ebe4103ca1583ff5a6645469c1a114aa4255e8052e7e55a494b40dda066dc3c6c7e8d3261b9f37ea853acc1fdfa56b524a29a594d99c2fa59452babb3b7f3dd2203fe52ed863d8d4a2820df2cca47429a5942ea59492c1adde60e3b39452b2942e8fe0a72da69c5272ec32659729bbc8c984c904c90425cc39e53bc628bb4da13ad8f82e6990a997ecc8c86808a329514d4d72367199b2a949ce262e534e2967939499724a2e7236bd787c9fcc2e19e717b99c3c2544e614fb28eb9cdfe3d393b3004e6cbc731ac166e34dfbacca4c6699967dd56483dbf7f4f6d17e9edf13fc8d7a717e766adadcb6d7e6c7e9fde0bb3df5f8c6d82c03468d0d573d9be296ffc74e0c7bbb1f7fe727d303ebcf752bf2965d8fbdf97d27869d9f7db6413965fd2f321fa6d3b25ff96fb3004e3c09bddac70273d3cdb0d9036773c28a0f4337895fcb567e58c18dac0323a594527ea1119c989898ee7e791a2ffcf9617eb5aa55af52a9bed008cecf4f4c4c0ceb5931ac2fe6865dad98c8bc3c8dca0b8de0e0dca059794c66647e62626038bbe2602a0eb7aa41b5adf2d26112192691292dd5b5bf2249e4f5b76ebdf0486b1d0a853a9d78e774ea3adee93a7669cff18ee4b4df78476e9ba6f18ea66519bbb4fad2abecd29ef2cee9b59fbcc33ba9d75ef24ecef61ec63c18df68df9ff23076f59f3cca3c926ffab5774fb24b7be94d76699f3ac9937fca637679cbe7387bfacedf7e117f2939a97a4ec5a99ef30fc9e993d244f5aa6fe6552a99998ff9f92b7fc1fcacdf9e6b2108baf6b9af5e9124f2da47755f68bf7e6ec69389f12c8cb73a72398ee338958ae35e5eec498a8d6deab680edd95b42a53e0eef43c5e3e4ec943f9fb9c8a30c9b7d6cba217d4d03927d614552e443fa49d9a40cbdcff695808a7c2acdaa57de8af960ed7d8006dd47639b5f04491874432925a0529fcab645ee7ab40ffaee7fd2b447c27d32d05cfb1aa7d99e6e6603c8741880dbf27b1c8dc3636e75fbf3553d9fbea755a7af764e11ecb3eeadfdf6c278bbdf5e3e36ddd46f1b6fcdc9ab39e7f9957d65fd4217279f7947fbd3b7abbf507b1b54a47941454d57e39a93af79bd3d12fae10cb79381e6729f7aee1e502f12fa2128a27df745b4efad3f24f44329f39bfc90d0f65cd62bf3fdf621e13e294d645ee6e96f350e78ee7e707c4fcb78d55b13c717840da07a99afab42ca7ccdcfaffeaac121f3f337d5b7b7a341557fdfa00ae6653c1c3532ab97f998d43ccd3391f91baa9f7926aabff1d65f333fbfc6b3dc52fdca5f377ebe8ca7fa1e87f1c2005cd557a47bbea87ff14223ac74431074bbdf842eb32bf585ddd7bf3d5feb6dcfbae1d1784c561f0dda0a8c98811041508183283e3cf3d9adfe0d1e68d8fadb56fd45c3126ea3c947e66f883e43d48749908fcca67dbff4b46f85fafd80faf2bd5b81ded63efec9930dbe62bee3623cebad292718563e8b85af97952ee60019e2cd3ed60af5d23742af0c7eabd2152e98ecfcdeb6ea890436f617951824109b7cba3de6965797df0af5fac73e415d82d82d3ce5d842561ffbfa19e6dc7caed7bbc9427dac3275006d6a6aea72b125d46fc1a873642b5df9d987f5a994a1f7d1be12b4920f957eacf3d15fdb9b9f451d77730ef3fafb01fd45b0fa2d18e9b863a0e2df6c1d4485c7d073e707f57cf71c9fe3f6e750419eece9f69f7430337318743d36ddeed9f568f693b3fcf9b2b080db31b05658c0ed5f0037dd3032bbead1c468cda6c635d87111d6294fb27315e8b8f543a2e3d69f3eec4512996e8d4b1c79e29491a79550ac6ea1fc0ef2e137ec5898efdc39a0629f8fed6bc7c2fc2abd96e26e5e91ecf96a5e91ec63617ddd0b5d855b3f242adc1a65bb3c85a0af0cd87d8a7d2cd82eb6f86c5314b1b4165d6a72f02407368842ce03d2164b785ec7288f36c6529c174497df89cbafbafc85245801d9380496b1ec807451dc391e6ebf6d918b6ddcf95d3ba7b2e06d08ddb0044aee6cbadb77b92f8caf6ddf378c9fbd772c047953d3edf19edee16a1e17a1cfb77a45e8a3629016fb045b3fdbcc96ed2dd333bd6a55c3744cdb4ef54bc746f5a9bba6f9d67a6bae6bd3c63a76bcf1fd95c586a561fec81ff957917f26c67ce10ae60b2dbfea88e872eab35fb862b141842516a13e2e62ed86c74ad1ebc7589c915fc6d58ddc754f0bd51f6859776466e62fbef65d2337333773a41f63a4941e493243e7546c6a571ac4d260fc8c1081e185af71aea051609b99397baf34f8168b62ccb237d2491dbb3b7667f1e5b6fdb66ddb16330f89c77844e784418f02db4410613771fd91ba09de6116e294ed88066583ee99d751fa8829ae431147252d0baef30be79c7372546c3f7fa1ac517af4b420ba31f7eb12b26460e33b958d19bb825d91f259baf7732fb179bb7bccbbbbe3f01a77f71befac777799777787797777777777777777f71fe2eeeeee38eeeed57d453383a326d3b04a3794e54e5d4a05f3728325a3847583663523a324060c18d58b92ee74ea524a2cead4714a68ad7453a26595e6f84e89ccd1a111d939e4155b70f4cc112d34976a15ba3e31724c3774a62e10601fd7c23e397450d9857dcbef3a439d4bdd85bbfb7b8f9618f6ad79365b11b91ec52676e4e3dec2cf7077af2291d8f86347b99b73dd845fb6871bb95c356a3e8c6b5cd6cfcbc894f8f2acfad4c7eae476dc5ba6d6128bbe88568b78426dcfcf21022e53c746452ef126bef64cde8a356b27f1e6f9c75f9e14562391c7cd8845456e9dc8976826262277125b49a9a8b97051f21797c8d35ac49bf85a70616a2791a7978837d1ad75a2d84eb468a9f9c215eb0bbfc64931ed84da3acebea852b3fb81dd1b344966acbea4994fe67333623a1b7c4b1a75094530315b542fee728975a225525cc7c4c4e44e2ceaa4c4a553e294b86c182dd2a2a8480bed732dcec4a5c1a8466f914b6e8cbec68d436057fcb05d0bbba2141cdfd960f943d7d2ccccccfdee9fbb96226c8cfc6125aae9192628fe4a713aa7c38814bbfe1b31c8db3caefcdd3d41b1f4b18fcffda6715bf65aad5ddb3bebd495479388669986f5b66958779fcd8dfbc2eefbe91cbed147c3beb0d2d45b54ca9e1e75d2ba8ab179f577e93289bd4831c62aabd58a777a48b768582b76d9558d0d965ab1974fc32f03649ad6f508b2e21dd495cf383f1f9006654c0f36642c38b458f98eb1a0c4963ddbb24cc5230a214123a5947ed635f6fe713e730dd2efe94a64bdf31174315a44f7a675cbe03124b1ec924f3d1aafa65bf235cfa65bf2376fff56ec929b7b2cff90afd86028aba1c807fdc21a8a2ef5519ffea84f7fe3066b8cb49212b726c1aefab1eafb576badf463cb4ddb5b6bc79ae6f0dad12d6eb5f8c64fa0975ff4f945bf1dddf2c7be70bb61bdf38b4166a928bd1d382028b9e8fdfd2bedea9d8d4de6784dc5309a2465e87d28f6a5d0a04c815d724a4c0576c95700dfc8df648f14748c6cc8615c39a473c2958e01704de45100dfe4e01d156e5060f0a5c32ef9f3637c92d1afaf7d3603a25f310cab7269cae0d421a961c69578d4f9e062d1d6f9d8ae4f28ba3f82f5892311b5eb9e4f1fa134189f28b1db0c341c894e78a76ff7f18412b2dda35efb6c72bc63bbbc46c0adce7e339c86387d7dfab1566befe9749ac15f40521f0ca8cf4883324a2cf758f730af7ad99dbeb3dffd0cfe02bdd57d4d755b87717d7aa95e4e24e097eaebbe1b724e9f0eb77b1450b7bae77ff18274ab63c1ef0a7cd371dc6f282f88f5f8caedfef49da058ec27e8af15b8d53d19b7ebba0f756ef7ec5de7eabea79fd8fef89143eef6e985ffe40569b0fb976e736ee66562663e2b1353ebdfeebbfb5902d3bdeafbf156f7b103d260f72727acfcd87d4c2452fda97b98d3cbab4e1f0bc54259d4dbd3a34ef6843afdca5bdd9f76b0a7eec3d5ed7e86ced1713becbbeed9a6fb8ac4a2d9a726b82d08cb0daa61c615ad8b51b4eb9f7591c84776fd6b17857cd4eb12099273b7813962a3bf69596c162bef64cca33d6fd66b40b7e847af278506290fbb280a7c43a906d84561f016cd00dfd06ae90b7449763d2aa5f4638c315a22fea2f1894486b0f6535f14fa58402fac98b7cf3e2f1fe423559f7ad5c7024ad1c09280ea6154413ed9cbcbfc4b4d7d2f1f7b2b4e919bac1cc795b7e8c378ff42e7d00869907e7b400d6adf4f8354c6041de01d19ec0698c79f3e911778277bfa30748ea66d377adacb08d9909788f00eeae96780794e4f5f039cd33dfd0e700ef7f48b70cef6f441c02f1d6fd19fd1e1ccaefa272ff24dfdce7306eb731e65b0fee66d0cd6d7f1178a932e7df6fada47fbeb117dbff42fdfd0fae9b08bfe098adddc7ba1418abaf523c22e4a9ffd6fb59f7ad587e32dfa2f40f653f56365f5b38f95696fa495b4ac6a35ab0f7a8bfe69079b42e760b9d90734a5c1faf1eb740e4d143af97067add507ea9e7cb0b171b0f10ec7925f5cb71ca65b1e39de9827e31b7faec1d6b2d8adb9ca2ed724d75ff4954817684a833cc5c636dd200873a3f6608b5f37cb7ad8254176a11a1004e3c0cee96f4757ae87777a08107f116100ef58805d3c14e01bf9d2524ecb62b51b72d6f9f00360b1f236734efdf60360b1f5fde7e9bbed39a0d367bb49e9c7a2d91bd9329a51a6239dd3577e922c56de21424e4416fb285bf7eb9efbd67efbee7b0e48fbad3f963b7d23ade44ddb9b8f72ecc43a23421ab1c929de6ae951640fb51091d785c9859c49cb1253176ff563515af2566b51aa4a995151d2512735166ff5917614c588e3a4d745de92d2eb28a827442dd44344714888a94b149ae1486bf196901f18881c252d49e9e9e000e9a95bcc13194e7a9168860501f601bde5ffdef228e4adf68e784b7a44bc253d21de92de4f0d273d203838e9f5e4e0a4a7a383931e8eb72427bdff38e9adbc253d6bc349af7a4b7a2c80959dc14a8f0501f6a9de72d90918f216c8498f05243f18bcd51fe4b371ebb4590103e946302e237df1837cc55c19666e7c18dee98f30ec8a32d4db57cb627d75e58d4fead7803d7d09843dfd3e1616560d45379c6fa495e6d785d2f53fed6081a68438389d1373c4079106fd67f7d3e0ec6910c7877f83cd4bb6f0c6652d872318bff0bbf1f9dbbfb0ef3718afea200aa101668c31c6c8aef81e5bf16f7c8c4a4a428ef4e0064764ecd84c8012853ef56cda61981c815235246717135309619c77c5894f66b0f20810b656cac4c474e4c81115143664a623a4e61df9354b42a6c1e460436682b2bd98c15c8cf47041709d92ee74c56e27948a092818daaa6cb02133f15175e94920423a8bd44b1362528aeb1893224f04d231e945b506962337f222214474705216757a51ca89b261d5ae62d738fdc2f9be756c5ec13d874d32738c07f0c2c2740eb78a1dc6b0c63f295629b4610392152cb0e189248c80c40dabd665a4329eb8a7cb483cc8610d185bd1e83064b208c21658ace08c2cbef0342a30450926306083871900c194c4134af8b0050d2c43284388223ce1c5510f16a5140013765eb69203130428b2f5b2951c90a04866108159c961e86e97ad3461a3d7702d6104932266a0841437306a1fb41449741835a805549a8cb6a8428910d478c2a70a2a866842858c13302142900c94a0820aa32fcc6062061a407409c3084e4c21c4141d7079820344d0c0062e6010c590920fc188043734f1448726286109679cc14df082cb900cb890b16404dfe25a18074c8c8104222ba46c9101ced72c965042b542c4e4486594016385480757bb6c85080b256af31bb513467d74e5092b188a201714c1a8054760b1c610429e250442f0392aa3890a9a60804614a38612436432a4022f24242424b4c589932daf4587036891afc396e82de4d19b9c6c1c5fd873830d13bfc8c3374c126f94427fbb51250ebd0f76e304966f84e996cb1b3e08aff2eb7e649c445edf912de994dd4a3d61e3cf2fda25fcf894131b8db055ece5639827035d210addfeab55e6d56fbb014e56b5c7b4ac22f19034d03863256cf62b599379d4c3784828babbbdddbbbbe79c18f6cec4564694324eb0c619647421c50bc808828c1b968ca0ba8cb434c6cc65a4a5a4251a2c4959ea610c36c640c21867c85c461ae34ad218518cf182315430c6500e4968a265088b09c4c8811855c49881182e10e30731726023e632d20fb460f9c11596062eb8302c38228816fc608805b76b821117264b142c2bc0a2888b824205b7a302e5724610714f3f5c19228b10f7e53252184a9edc4e8d2b29c88282ab9d11c44d8101c44565f1c3f51b5cf1214b0f17ab82e8a68a78b8f40527b8d5881d6e0784934b2f23f960071dae652387db3135b95996a1bb8561828bf10087bbe180c99d575c5992e5869b4971c5862c4aee160329743b175c69cad2c69546b071ab1057d6c8d2846b2f23f5a0c915266459c2dddab8d2258b126e97c695247041421635ee16c6e730c3fa11ae5f4602030c30ae706a051ea51777f2004540480bbe04549f521a238db45aa01f20ef6d9a4cc005e6f1e71d5a7fae1a9c5f6570b0212bfdb4108109580003fcd2a94a3ab5cef97702fdb06b2ae18029ac341f5c71346b28c5408ad534987d2c1ae96fb4522c64d51f33d6844666c4e6a45fa433624f2316e7b4737e2c6c3ef6b1300c9b139bd954025cd5ac583435362be660cc38b2e2332a3a02408d0d8b66b5a259ad6c914dcdaf56384c4c61f31d0489c5528079f855bf21f4931f0e24f586b4101d6cb8ba14c0b1d915c22effd313db92597eac39b1297f4a39a5bf5c213e46619198babbab7588191860977c29dc775ab86f488331604f7f089117302f89bd1660579d31e98b47f128c3c17182e3afa885192bf94f6ad147b153e2508348d90c6c4c3aba72a59d81893c1790e11d0cb08b89020cde0879626bad4cb1c8a4e9c0daa0209ffea98951b3ac9ed0f276340fb0f22bc3aa579dc60acb3ef533d495f5ce2484628c1c86b591235e926e1561977c0ff08d740ebc61977c29db73de06ee68d088c300faeb87d6c73a1fddfd61970c13ab02bf56de927f02efcc977fc32f06230ff6f209b023f2642fbf00bc739379ae1df6d26f1579b0afc53749270800c3b00108615c6a0a51877fe8b44efc8142ff40c17da0905d9f73fa9c523be9d0d9d9c8ee0737d8fdf8f958f7c39b92310c8b18d6da09094a763f5cca1fddfdf0ee47164baf96c56675326d6cc3c1649bc7d91b564d567bad734194514a83505c4249c02ef923881995523fab948619581d1d22f3669d8feee228b294526efab1d7e3587772c262f3d3316fd8d42b6a566b96d55a9bb677cd6ad0715cd771b33d1edbaf6aa66d9f0e8ef6e9e0b827b3a4db9e28875d591b1362dbb8b94d8fe0abc5add9fecdbf524952cfa32e62b14b3e0ddf14619eac12bb5babf5c3191b1c21754bbe94ec352fab300d629c7bf18838c58b4b744b7e0b8cc06a8c825df2e3137ce351a278378c464652fb19fcc57df1894fbe51f7b410e9c355dcd0fa0d1e6558aee2b27bad350bc08d4bd5ca0b53f10bfb6cb8cda673e263fcc2dea625a57c7e9e99e117b317bf5bf2d3b055e9a773429d2b5f2fa49342e7c897cf2e798212df0ba5d58a5d2ef4db2b1f8877fcca1701bf70b8255fca9608e057c8d3b15f401d060dbf70bcc53b4da441f92f6ec97fe1c89ffad4bb55e7006159527ab9e1eaa66eb7425fec5babdd0a7db5f7a858edb3a206811a940f34a48508b04bbe8f1f2a4a1e15cb376a58b20d468679b19734d2ce86108d146157e5f100dfd49ae5f0e82e07a7c1fa59e61969100625360ee1a0842c8f06eb6799bcfd5e03d8557f93c9c1cae01749bdfda83fbd4f57bff690c2699dd6f568b2213788a37b97a74e2bc2afd3179d74ab7efd803882f635e1d6df28277383fd10e7662feeebc7a1531c7a20feb2df0a25843d7ddfea22a95d8f9f25a8afbb38d460fd9313566bc2adfa462c0961939e685eb5ded250d93357351dd8556313bea99cb7e4861208f970acec51570af771675f1c8252638dde12ee71781feea3a269bf753d32da0509ba5aac7555ad91f9b1b28f393ef9e2a2938e43cd796c0486ce89482292be8dfad3671f89bacf501fe8adfaa78cfb2efb581a4bd37ed3b22dd3b2f756cdbefa30b06ecdadb1e885093892b20f549f94b08dc958794cff64171bdb586b51d7b24beee8168f6ea598a7a5e29d137ce686f64acb3b280cfb0dbba134bf08f65549c106a5ddd1a0b49db303a6b31b3b20a306ab538e51c29678c7256c138505ddd3a3b03f7d2c20fb27d59f9e7dec07f954d4a7b0477d2c20d4a73ef5d150c30bea837ca27d98b7dfc37c8ca1de3eca4e89ac18a3576f4d4c27059dce99f1e8d3c6e8a96e276f47b7e6470f64d7041d4ba1c1d9e3adf9123b256193db61d32d8f6fe6a901dd9acf7f9a73887d0cf08e0cf602cce33f5fc810dea13fbb1e18a555767d213abcd3fd7c149887fbf93cac02338073b297101d1b73dee63983d86b1e6510fbccdb18c45e88bf5013fbfeaa576de67c9fdf29609f0ebba6ce9d18b6f2d6c4308c6215a3d869074b3f7c1bfa014d9961a60e8abc314ab13232d94894813b40558a6f60bac6dadbcf2cff7eb4f6406fe1780b356def4aaded1c2d126141bc96659a963d517dd243dc72a795ba67350832ab35cbaa740fc889069f68f5b34dab9fd1da5d295b966173238e1b639aa6a27ca06e7fab2817a7ad88b26939750bbaa7362ccca79efd25e3431f27a394f60edaa53c3ab90da6e63198ba55ced2e7b1e3045a6586629847867a67784786d5b5abae5d8f0dcbbebb8a79dbad35dda230dd52a5f8867efd2cbbe8cbd86035201de407e41dfbf45bcc7302e7dc20e0456774548b3a751c10c60246ad2bede8f73418843a901d0e82dd0ad86b0ff3aa1798afaa52ffb2bd9156da364e92b9f483d0d73c5ee2a688b2aac926d53e97a27dd65e0c49e4bffc9e35bc722fa2b6a8546d58743de910d100000020004315000020100c884302a1503420c8b2de1b14000e7398486654170aa491280762140541c6186000200018628c31c814155501adb64ad121e68267de05dc66414b0192b2c99904d78b62811c63441cefdd086fa45d23c4ea2dfddcce1af0e3e2b9ddea3d2e65bba93f07f1c59389cdc74e88304acf3892d9bd7ed743c5fea13601808dedfdb5ee3a724f371604e9c3c41cdd767795a0debb90827bb90a030e1cc1dfd6227666abe0f2476168b85d92c8105e546b2f625141bc561002aaaf419ad79bce52906ea36db067f465719d7696e225044a49a1944f2cdfe99b65d335ff143e31df13fd157505f6c8d55161dfe7a963941dcf6f1784367056b8ac3af7f5e79d56a67d2f31b99adab1b456bcbb4c903d55af6a1b08d975cc1839f970c4cb19b8540775c70c497c8614df17d3c1c6d92d3c4d6a393e768969844f72dd9378672fe1767d6bacf8ad3187b109d62936792a4714f90f447c46d1e2d714ab200cd582025ecea6c41c44b72991bca4827d5eec13d91c86d14333279be7f2495e5cfe611bd1395a911d89829d4138382b157cd759061644b49a4e62f79a67d563ed009d4439372611ab04b89027da8ae8fc1ff9e26894452a0dbbe52e816fa154a9015b680808e3231f1967445fa0b6cade0e21f5af7a2a63c6f04da34723774ec0661b00eae40fa42b09e00adc0c738033ff5aa9da41b66d1628c3473c368cb861967c284e6ed6536b5c83e56f0afa8ef4f7cddc1a78472bb7cafdc0cd1ea4be39889d426dfec678ad6e36fe4714fb0764e72713109a3682a42799ba8702b186ad25858f05f5a92721cae4265b49c34890713baeea52c19ed0afc09751e5e6ef9a776299d9a453d27c28b6d2355b92920b120a556c08a7573e95b04b50e1768a299818d10d1b0c6c9094a98d6413960a1f7d66434bbaebd38f3365d2a4e280a540453a1f4223920964ead75e64f80f9c31489363b51bec9705cc492215a5821e42ff28aa40794a95ec20868a655ee4de7e1ef6aa196f3308396955d43125fe9c24ee23ccea8dc5fb48a9b02aa67ae73fc284c2609569dd126f259e793a6f130c229054df76fe23a5da284618ad2a2dff2317080f55553d41afaeaa8f31e88530de31d809fa84afaa4060733b7d1bbf0812ed7d9a4f721a0b43615996bf1a0bcc6c2c9be85ea43187100ad1db7a5cd23cf6d3b7237ea15d8920f8b6e0e4e44545daeab9aaa6a6ed7c4376879ae63d2a103dd7c5635ee02e00a1ba53d33c3261721a8cbb66166c7dabed3e7250b2a2a6ef6d24b56610f6ce4092ec34e08ea0665aa101b9c74981c65fd001986baac1f9dbe5571fee8f6c82a53d14e384038a60c7307d62d8b99de4e9fd119b6f92533eb4f10b70844a04b294dccdb7e86271bc4248c5c9bdc46104c2ad0a53bc49089f8ed65274b951adbe23bca97a17d3c55b2a3bfe96cea8ff886e2bdee6f48d59ac02cb337148f8447e94a314117c400081ef43b57446fd8ef046e1073101741cf7fbaa0f1a426cf4d4f6d3ce32478e9123d9a85d65b97488fb2b10ff4fb208463a19fef524dc6d95a7f517e1247a350b3c63ca17cc9225685af1777215bb009538521557088e156155fde15749a2fb1fda51ca913828e380c705dd90b1e1dfc973a3131b1fb72a9273e09dd2385222a2e495d4d3a53572a27eaa1238cd2ff51cac5ffcc2c02dee07d88becb4ad30ba7c4c04939deefa2503d75407a767ec217e38393a0d2ecf49581f5c84d31163c62fd866c581093f60b2fae4815d615ccc74ee1cab7492e25400f5556ac244f267636c9c4c5a21172f42a8d6d8eec256c4133059d5c53e682798c6086212a1c7d55774db521a0de1cbf3f0492386efe7d1829b3be6d50b1989f68af2264c77d78fb6a38d6a9c27006900141c17f7879b60a74e64ee56fdce02cd8d5c25722d2792c9aa1029170c2cc372bced2357f48696905c5b53955c7a659029067ae3531fc7d7743cfc82d23c48c4f99892ea7d184ebd733a70c2ddc33752b2931059f67e41a0075b4a3d17bd2ff0d11279a121c0060dfc30b1a449f612e33d5811f7c63a03f81b633dad03f9c8c43144ba623cf0f60698c9ffc860781e4b6f04f7cdb33ef6831aa06f4f6824798609e339957358a4ed42f6d777928c205fa88959fe9dec7f8532db336203713f4f21d6f6122f9eb016717b46bb9d8860ae03f2d212f52006a300f31944876959f48a5ee16d91baab620d705407c31ceb3a6b42f00c255d2f3e828625d3895b49675f695e8b66fb98b5d05afd4ad7ffb9888231160f42447bf7eff30819e938f6a52fb9c04363bf97f4c5414a13b208bacdc17ead0456093b7fb2369a244f849654e9cfc340c471993d1e3ab3f4915eb311a22d0cece2fa919be8a2597a4427954bb9076a0ae69b0469c6f4ba1c2e318db7fe076d243429592c90a360e2dc889d999d7816b9a63b122e48508aa84dddbc4d8426aeeca2bee81e187d8e2ec0b9b82e9a2c9180968426a6bfdc06cee6d17abac1dde03a9d6af0e0874d72e753e993263b2599500d08d944ab5564051a10a0e536a7a935297c985d2d09317920b5ea574b1c6c8bec7e6d7fdb98a68581de0e38630036c1409bae8df108a9707885b5cf0841d69cd09abf9996b5e475633d494e1796c700dcbd74d64ce9220c66c32b3318a4b1d38094d572a390f65320c24d4888c4645a8f34dc7b6a50fee352779e8d90e9d5cdac20e0515835cb03a819cd61724debd0383ec328abe6512ca6ca1d684b5b019cce4d17ce46e18a5a67051498bf3c42745579427c8cbd19fca8535393bf3c0c30d98f04bb8a1c99dc07da1bd04d135b11285200767ed4373f87768a660c91e51fdb1a3351b43537b613c1fb9897b4daf87bf6fa10f414709dd2eb20176c1b52428f071c987b563edd726f7db9bee413acee6ab92da64de4e204dcc74dee9719701c95c6e4a7dcfd5af771184a209a9e80673226882b136a71e609ded70c7293ad46c8ff9c02da7c12cb3a4bc537060ac28867731a823c30cb62de804a186026e937c80e8d8880ba0be97ba24805564deccf2c3189b4708e543a4c873322aff11800904e9f33992cb4e7d1ab04dd12cd9fdbedcad5d25ba2a1880fd567ea77c9c6fb1f2bd2f8e183f6e3661f61095d5e898811624806b71b7404e5dc74b1721e353576697b85e24374b78019f0682b25c779f32b527ad3ab93c88f129b3e91629438eb288db08a33af8d489679378eec838c52b407733003a8d84b998d7aefb39b1c8ff18a370f083f6387460aa09c7450bc0f9ccffad59784491efb640622727de772c5c53b83bfeee59482dfd0fe0fbd8df278a45fe95bbd3494825c2edcab25571d2ff195b756811c65ebd0ee80dd600de274489d0478a5be74fd39a8710e7f8883209e926300270effe1fec05672780d41612633cb6420debb15c6145af01e1cf8c6086f233dff5925dc97cd2a89d2737e74b0da8c0486db67243089c587de1bee42ec1977d1d457f9b75e6d988f6918108c7903495e191c21938a52ca0c9a9206bc47c4eba798be72f9126bea561470f21dcf76cae091812455de57f9ae30636ed4780e78c80b3a0b51ac20e4ff375b4ccc4461e33a8f9757b5442becc0ae8e6b5b44f0baa99036063b7a01449fc12f37561d1b4abffaf89c7c334fdb47ec4c8583da031303907e0c0ebf2822dd5a76606d278a1290fb30e62c38198f6223336d8d446d4a84f0bbd96ea93fc0d692e291744ef4a5abe72ecf1c701ae35ea15f7ce98282df4617edc6792a00342b698b3cdc68cc68811bb8c9834dbe9e0e80d0cd163845b9deb2f766c63b035027ca22d3f1f1dcfadadb8f41baf53035988f851911e8388cdf2e14ecd1836bc77cb2d42ec19e2fb8b618f8c753a78ba803d726d770eaed0d2abec200ae69c04841c15acf60477b213035f72181ab0df77b1950886d7508753c4fdb715f84de16f81a75e871e4c8eef68aa3afcbca306f854f66fea2058670914c556f22e4a605436e3c7711a6f46d5689f0857025c46f40d7dcb1690d86eb0ed6ef3fbe5e0f2e675455277bfe33b09faea9cdbd144a8ecc79184ae29cf48108699f0b4546079e4ae536a8ba24518ebd7590fff3d5acab0ee3fd99e4785c854bbc5fe7da3f5e51385d2e4cc359dc6c2b5c6007cfbf530e4f6badedda96bac0a8dc5e65acc9060fcd1e884af99d102840b2889fc592780161a9fc48c9ec92d1135bebaeab1e128bb0e2f5a2754031f01f99813b925df2c51916e2e5916f54534ad849b34878346003e9e8ccf0862e789ad4622e0f787d25ddaa0d32b567dccc81cebf28d4d01ceb64e80ff753e06a17c926f695e315c8eeb067b0aac0b1c17e279a274126463c1405028fd83ae65490e83d07acb41b2bba5c77791d0e30cac1fce9593115b6c3ab017f630febcbdeb4ab1c8f6d07a11303b00a2588377ca35d5e3249d05b7df5b3a7f4e9480bca93a37c5f3fedb1c13033cc30977e65451a3fe467460782ea48f33c064d1c205bea5ffa8c8062a29a2e099410c688dfa9a8bc4f5a2351bf3bef231a131067799eb8701bc28dc269b4d1ed39974520347759ec502f5f64da015297d3d44a061e3980b1382529b252f07a8a5250cea15aa665fb39567afdb3d572caf7f4dcaf12c9544ae24cfd6e58c1665ac9e7e1c2ad8d443dbc5bb11c29cccaeee8ac42ead27cad770c705b238b0194f77d1224f4ff98a3f5be76679f43e25fa36fac564ba76f7031e90c2e784569c8108926ff075cb8cf2724ae60015922a80725d5ca5e1643a09baad146efb7067e9775a7280d3018a3669f97a2df154d33d91e2ff7915a4a843e9101efc20aaa1a6aea475f8218eb02e890ad1c1cb35d3128ec83d9fc7bd90fe5a986c6357f2f5c8ca05a42dbfa326ab7abd56140040ee4b61880aeb680b262f26d892d77bb8201cb17fd3ea5f8856001f3fbc730d37871d2ce69bb586c81023668967136e0855989f92d9fe7e00fc4115af2a153ff6fc50dda5ef69cb5ca4449242229cdd65bea42e6b98b1fe19b5fd97e2cca219806b17e256a29dd21abf2c5a85aceca816ff5a0c047b9a61ac37eafb0745fed28bddf11e121364a1cb3bbbc25dbde661380e0e1b560fe8ac69bab1dfe152fd34065008520c9b786783b260a71f5cc0c2e2ea9181258ac2ed1a304f87482e43e6bbe10be1f62f503d9bbd6555b413880ac444e5950c55824043303e24b9a14e88e01f039367aa38829dc22721d41e9087c55132be348e63d79760ed90aea7594db9092374ff8bdffc17dfe3f900f918b50be49a3baeca61c67aa5ee82a44e4a4b03eaa10d417c9352f79377dc556e52fdab60fb7b7f6633243665d9cbc2a5241b08105b2cb54003d221bae9b613dc03494c0766d500442ded0e59de875a6c587ee0a74f2aa900d62133981d7054a017527e1f0f2a06c24b0d09d98d9974d8d5509d26f0d3c5afae09ac6de5252536db18b420ccc31649a76401ba124e338af97d71bed34ac4c443d3a945ce163c4a5626061cdccea9b9bc5257d612abdf0c8eee23157458c013cb859286cad6913651d62ac3d81248287f84b32b7467f315b964d1334658a30c4f14179e5026ec2c30e555529436899953c9042b8a196a30443e8928434c31bb0efa6f29c2f8be7108532c9c2b2b46c512afe309045c5c359a7787ef4fdc5a47e10f4a8b7cb995cef769a4287ea4bcc0cbf885d16451964d9a120287346f660b332662cc7b009228c2e8ea9f60038f4fe8609930924b6b08bd84ec56ef9cfc564d605c0cc00e265e7cfe14cae009cb6834f16d93eccdeacdac6e7badadff84e6c80581f20f8e006e0920d64bed46d7fae47a365d89ad71888580282957c19cc55afc347edefecd2522ea26e9562f84e8a0efec7f898c7f05b82743f8330556be75c0a83a32f0a3606e18b3ff9f21b94ee15ae92f9909564b4a664fa72d8ae413223ee34f5d5b3235e659f265839a27af614ffa38a62d4b7908e526cd4e302122709f68f32f74c44a55ba2c2801b66cb199a2515823dcc7436d4ffd033d5e1fa0cf433b2288df9a147dc9467920a29652fcdb29c7d717dc576943ea90ae9b5a393d6b8586d1fc1477b670ff67fd09b5be4022b7abac859474bd8f8f3d572cabb9051ca547247b43489d339d63655bc6d135e4da61ebabc149f9e78da150e02c0babf5038f0984c13ac07120f20ee09c6ddd03f0793a6e622558c994e7350bb00e8c2b5289e6e87320ffcfb3a3651291984486dca9ec8c2026bfbcb4d17507c28e255828ff01373c36d96c97cedecc0f218558981e52cc73e11745d12c98d7911f1c725cce10339321080c68a01c3e2e169e025ac61e52c0dd4e40ed7c9ebe735ef9c229d8ea7602e75bb6746c2147b735a8f6dab5a5cce634f36a737250853531be9023fd4d6412b2ea58c16d055735928677d97ef181c51d170a405c87bdbd0303e102542a9d9cc2bcf71b7ad1510d99a6e46652c3b8814a911bd16ed4a7d54e68bf20e3139ada9727a4e9a9c38266f73f25e37616b53baa7d3700fa4b8be61852ea87ac2866267e524a5f6ee3bb7850cf10c827bafc0e323f1eb2172333df3fac7db189cf11534610db4d6878f9488975c1f807ca7f89490b55688ee625de50dd8f723baa37d137d2cb7308955aef02a312aab1c241566c843bf93971b4a0c204b0368881ca0f66b5b9315e03b1c21b1b9099cc267dbae4c880810c1b6b327dcf250596c7ee5b245b8f5413cc7ec90a515690fb5ed3d481a4ae6f921227349940c5eab5055d3ee976c2df0f28f139d11f79e009ac4356c279cf2cea91f97cb5259350bf1d01e415490060ea217f56038b5eb9720beac1f0a1580333275cc8321a48f3f56cf51a4cde698c6ee246ea9720e33741ce4896eea7e1064be8e5f1af395463b9f5f6c897e46a50beffc65d9de65f9a2089e92ab7563fe32447a76dfdc327447f73c7768904d29ea15a8fb875b887ea37cd48faedccec84b239330201baba091952963db90c6b647284ff145022c78827f368bcfa805afcbd21dad9bb5038dd18a525d890d66dadc241f6f5f9db575cb72d7aadf9226ab13f21ebb5db2531b4ecd5c5128f533a23b6a0f316bcb43b1c1c9cc7f9942b4ca940591cb9e30593dba7c433808c4f8be55c8ae40d9d65665e5e62aad402a16067347e61199a6ea79cc66469f814d222d5ff2bb7c9b8c57abe74cfc6b28ec2ff16e5f25f651950b8030be98a3471a2526937a7861824678cf2ce12598826f90418662e0bc08f16b98634d494152d84ac034164541dbe4fb9cadbe4fee6b7cc78068379dc47ea9c254c405103b6043ec3fead90926710f3308ff84f1136f67c325046fb54929860fef5f78814810def2b736ecf4905408dfa1edd3b5de549bf3f1a4386ed588914572373e39f97624c928c7d70ce2163b877e832e42e37d95662462f1a151f92456c6b1376575d94a073e27e88926e56164c9e080932201e22870aec4f0c82d17db623ebae21e5737c31a6b9d2e8f7100f88439071749e7fe15ae9019e9ae42a0f1fde6566a86c70c401fa9ff88bc6c56ce0d1291dd59ca6af157694989109ab1135f55a0bf4a48848a84f530fd807566601257b5c69e65a6a604c1629fd592047719a9ad3a62a03329ed2cc44008eded2255f5788fe8b5aaee082198d2b9a1adeabb68c2653b8f97e223ff0504209cfcc68b282f391956f3119447462097f8f0ba3f829baa8a2e753b3998e04b19eb69768d834492eb6ab304eebc55f81fbbcb106dc30f6c73a519118cc5c727402f623799cddace960f82405c571733987fd652151a3e2dc672a6c82c8cf00fb5b90d120ea5bc07bf90052ded24bc664dc2ab48b011b8342029f269b27c25ad618cacd1bedbb6b08d15389380628fcca383855077c9f05240b84a5fe97bb1953970691b0e8d2390119a1fb73ea4e2a7ee2fc69a47de3be80ed10cd7bfdbdb14595a4812c1b73cb5d5a5ba1c22541ff01ddcc21637b4c16039b5ded8fc5898f0fb2a61f8d2f8d4e7997da2c1a87d294411c4841c895063d1b18d2bc9b0dc7622c8eb38e4b6eaf2ef320c2212517cb0355b225730078d29efd51fa19501929e8404d46243906e2e2130820ee00bd1d849608dbca4c3a9e8af6c44e94aa48cf5654545e9ad7dec1694481feb72680fdbdbf95c7b5ada979c17bafb3627669d6b9fd619aabf60454228a10a99049276403b5ef266d8eb8c6d72d4ec508fa46480706267d42db00390a997cb11ce81dbfc0f933c6269fce244e40d07b56c8f0044ec12f8a0b04f841f5bd44e0d78ca9ee0f15fbfe1f46c4afc03960b83c37d2f39cb0d72e32b1f8f4bf484e0860a68f0d8b3c41ddf909ac4d5b3d7c4efc8b43203dcf66091b18d51d5571c1cc96ad2d143b0af68bfab3e5d4ee37684807983a730fc53308f6defe48bb53c323e088b6577647305e24fef966301e90a8069f6baaa0b8f8dfc62ca6e549205fa498f73f5a072a63fb44b9a65c2c9671d4e2449d69e6152e8a55413f93f1d45fbe3807240fa2aeb6f1dd3b012952a8cdea136be7f017723423763dd2e089475f1cc07836e2257cfe8d6d87ecb14bd347803ad37dc04bd4b2373e530869e3987d28e0b90da2b6e8d11e6cd13e2624a0435dc5918eed3963801269036d9865a6fcf642e7879d00c41e3acd8c1eb2aec691f6f09175953d90e4ea56c95e12cf98f2223f98da000b884322cb57388bde3d7d37f2fbdb1546cc2a629a8fb19cba1c6e8096f901986ef2c866c542a7d55a36a6bc989714cebf4e431d9d4e22ff30446d7fbc0309b5031b1c02f9d325d223f547373c4ca349e14e85d4a1b3088d1c495b2f512eb8cd15c2b4ea598736625782d600b2ae7656bd9342e3daa030e11f6b920538960f0433605e6d4c429c58e5b5ed7bfb49d1eeeb626c5adb2a3bd190add64b6f9dc3ae55e2967fb870b36e23b7c15a9ef35868843a7040a0d0fe2c8c274b532400074cd3e7589ca6efe4e1090bb5e41edef21799337a627531afbcdf552abeac42877bbb8dc9d4676f5168d0bfaad782f8cfd708ac2ead7ad138cb686279f1fdf58c2adee4230e4922ff48ab7bc062f5c6918f6a5c6de614613e9845d2f0077fa24f9d1216161a06adcaff4a07ec9fe0bf3a2c5fedf35b591b95d4d828f73c0e7f59cb747b99dd3433e5e7ae32e0ade2052abe4e2f48e1b05c76d2499cfcef01427b00d67698b8c733dc2d75cd9d3409a95111c306b748692c9fc74350d6799be551adaa09f1ad6dcda31f5e460f67e06d768e40982e7c212b64c78ce98542022fd9d591bd1baf2af3a3509b3535fe31ec1534b6aed8d163f98515298ab2701dc47f1f8e09304514bca92271f6bc8602f2344424b6b410bb69a5d25195bb6d069b038e72e7dffe79f1a0291759d5af51869fbba2e0cb6557f1cfae22d5c38aed8f33da45e3d6bcd4fcd23789dd5f9f932c35b64723ef8a8190638c965735fcb400bbad5954a73a8c234c889bb84260ab13d138fc0263bedbbc479fc2d424ead3a735ec5b0801198ddb80f317dfbe84185ef5f797d5d72b4f71128e641cab789ee105cfb0926a4e1a3f9baaf81ef7204ac1ab00b46fbc9842e6bdd997b8a37df849ed4f629028c36dab6c4a0e27780273135f619abf468d8ea3a25814a8018c095d7dfc0a0172efb7c644cbbb14db5f8333d0a7bbd70a52ad448f0384cd77a3f94c31ec258a0ce37115eeaa6b62fed5c8123453809a1c9fc806db38a1541d04aecbd593c6b1e741868764c5a2be5aa9cced303dc9beeb68e7fc96a24806f4d2f280266d6441fc0224b8013194bd13672fc08ace2cb7e9d8980e600b48298cd5a82536396eeb9763cedf26a5ed9196bd43d8256c3b3137ce8115dc16aac3b3da26bc2ab9bda444d3e8dca5723d26fe0cfff259dfce3ed4e52f89d622c00da53bec616d0ebab2a57679a4a8b308dbaa51107220787df9fc594871e687d0bb91b84a09f0f852e130c5298ceeab1d2296b741008acc500d2860ce0915269fd47af8f2426765090f88f0aa002935b708a26a24d69c34cd679875cf46442ae65fb0b6b7e0c85f7784c7bb4f5a60ab94e4a36b8ec67b913e3d663357bc76370accd64011443fa9075a9ca02e057292f0212b3ca46c4623f62b16dae1310cca836227de1f3f0ba40ebb4eb0a4ab6602343222920b151e9c4011810a15a07891df8552bf72e6f46af2dfd45d79c959d1c436dc6b9462fb146636125915027582c380ebf73ff2b8b15c4b21f5b99c20f88ddb81a26f132129d80aa81fc55bf1f29a6f0e4e5c65bf4a1d4a0fa451f7ef43b950a357809a10c1ed1501fb92e447ab44f63d4280ad6a45825d233d36c5c670900d0800ae3e0a84454517d6bc1f0b675e01689ffc156ec75ddfda8eb165d41ec411210803abe492e5762f10b9a44d2105d304dbab22281535e5ff570c6d6c398506803328abbc52fbf5767e510813d60c0bd1d8880f6a88b0fe38c8435d6314374378d2ea937b345e32dec1a0cfb0c64638ff8ec2e832bc99eadfc949ec1a97b43bb51da987e301e73ffda8b2ecfb60ff0470255263069a9afefd7c75e269c8d53f0dd46d6ccbb46069ba65888e6badaac8978a31f6724276962195df087fc6bc8469a4ae1790419e2309b486b830f7fe6c287e366db1c398976a79c60c23b7c94ed66e92f54bb61a1a0ebf6a041d011c6cab0994fd80df84d6118e080f072715ee03402bdc9398f078c494144629728edfbc61e26d6c7beea2568c0fccba7712839197799683e359db3710b91ed5f2cbc9e562fc4e1a2d9bf82e69c065b3b29ac139f03ad191fabdb544755e606c59807b6ce4ad4051decf223d840e4f63b7d0a1a32a79de8f01280c02a9217855d88b7bfdcb0f8416432b3539da890547adf85fc5cd367d9db1189e6c79a1bddb73b701b47c89d971c8749d7ddfc708959b6991af53cf421c9d88a5ff1a8c041cc7e94b926f561a859dd417c8d7ce169ccd495447322793499b97668088d899696344ad964cd94c471f80f97ea5642249afbae784d8003a07ee8f588253271f1eed3bd520722eb4fa2eea55059ea65e34572c60fa52489a4e31c029a0f034a1d449eb87d3284abd77858bf7910ea8d0cc0b7827a976b3908cda0a84d632c7ee4a5397c9856ee019f8f93fc0dbf3f374b2434789796b181d99859d3e78045f0898bbabeac292c9a545a931df70e40d3cc108ae2ea732bde581ec67f91cd860cf94218ed63941b1bbbdc4fd443a5b62dee7ebbf1f00a35d1737488a096ca649d118a1b4d3b9498adf1a3d0a72afaa62821639eb7643eccb5934e8354cde6328608204703abd6c8d5c2d26e460381bfacfdea6ca2abd188b4f1a98a5e8431f228e4e82658cb6f2909c49ae72832e8ebd09c1c2a0a588bf490349614ba270fdb94388ce49c8ccca1e2b500804140f54409267342c105bea3c6c787eead27322e8ca09703b7413cdcf6296ffb438c6ae1a6c4620cefe2dd0b16dd6d017ddce8457134e2f89d58c06608bbe299be9dd91f77c028813b6cd53f6debf885c5d45685a42d035744bf77cb128c4b0fb85749000dd0ca4671b26a9648fd3c8fbfafc1bfab8be9ebaee200227bc4810dd6aacd9fb6609ca331e54b5303b58838f39784477c80e04aeb1a1fd63e7fa6568a434d675854cfc190e922ff5583d0e4cf56d8a50700a5b1f67300b8f8fef2acf72005a950f12153f194cbaaea80d5385e9236b0fc53260aa582e69b4c3f9f5b0e6a47e28e780b556eb6774b1f75697f0c45228a60bd608a3285395b9f5c9770e8a5c11c8afbf79196a854a92a0f3e007ca8bf14964495ec65eebdfe97c592c509f57fab9624a345ee8168e76a430992fc7925a4275aac61493bf12f7ced7cd3a14aa7c8dc29266086ad7aa7ab69713dd9fa418fd0e5a43525fb1f68ee103df4f52a6d29277d51feeeea86fa830366bf0b8c6fa5571d881a7e82d4b7f487a654152f6d6029d504dcd60514232d84fc790a245899364d944207c8cfad8b9481c41508e11432bbb4052abc4912e8c4fa20d487d1964bec0be49224d60508e5afc2ac903e6389431276c6cf4656be7cd5b53049747e5bfa2b47d50cf5b4756c87dc9af30459dab09fa931fc9c6362339ad3c729ae13189ad538c8097f53267fa52eea1cd7e6b8dc4427c36849da699955b4abd18d3b10ec3623922d495487fd38e540871a69719a77935618f66795442194fede38075d3600e6df797f38ff491e753bd31af6c0f3cacea884c0b62d2e308534f9baa53cf4f3e5230a1dc6ab00acf66bdc6a44760ee75fee62a1767184c852534d5f02e11faca1429800d2da226c42b82e5fac89218067bee9365942ca8f9693285aaa98290a7ccd55451b4b16d0fc17a9f6376cacda42282939115732d20dfdb177005c20e7852a4b161d6e606ae2f88288556c41dd19c2656b546a0e1af15f5368f63290e3f7530684c80f0a0d5cca8c20d0c64a6723f8375283355aecd0230444f6f2a3a5b156c0707b2635d948ffe2859410fcb05738007aa0e41cd060ff20c26e70688999e52c7b1dba716ccce36dea3d70b3e32e43a5cf047de3b057cdc1870e24f15294408c659de16cbd937e9fd7e20de08df9f7b9efa7f44dd9472a8b6a1fb9a3ad6e63eb28fa6d7a82af61245a5d9cc21d594eb670bc70a733c7edf70a37de7d861b0943417285ad6b0f7c87899235a892bcf25a37fbc12012fe0d24d2c019271832ab12589d0d0de81914ef9d89d32619862bd7cbc405bd0881e34267976cb18c62d7f6fe6a72609ee8c08d197f801e1b472b1b870ca0515125e4bc0247be43bb959925b41055db1eaa204d6529dcb4368c5d004ccf7a58a350d93a81a91b4f866489865e0e490cef54ffbecafd5e6ed8d3bcacc32a2c7d08c9bc9f98ce0ebc6b93518c87e66046b0290723c5047bc773cd7751259e35ece5c880b04f85d4ce873736e648efe11bc1b823a07a0ff8c6cbf15387b4dfa0fd0cd4568450cf468d17d01dd01b417d705b3ceb5d86f4809e7e944382e532d99288ebe023480bdc17ba8673146075ff3031ec93ee9516c2a5800de32ec7604bd311ff959320b7dd88de83bc20d850d3425eda31286533a02cd628228c8e90348bf749ec40b5d1716c7e6d63e3a0be77621d86bc3b3a3d3ac1620065ef4e25130215a49ea8312c4681a2c548d8d9bd093366d3c923ceb86320b56ef84d29fb1691ea673a311a883e16bab6cbc5d5005e5311fff76d61b68bb4a32bddd3f9fba7c509e04fba8aee29d02ba40ffbd2c4266443877cba9dc18c1a6324baca6a6b54cc41b42d11647d8917a260fdb911d5872dab819f72ae6f3576c00e3169ba0b7a2dd06765bb67df0ada8a1eb412527444d27d3f13f384249ed547b85a70d8e6f00196d3e19b44dce1f5b152463de577952cb1238023a973ea392ed895683a8ff364ca5ac37c06fe436ec91c7e91363dc4b0fa194012ccf166557804f9c12774e1dcb6907d2f262a4b0dbacb21e3d984bb725b3e48298103c97ead4dc958e5925bef578d69df4cc37b97869977947d3f49ba071cacc65159dc11837e50daf12e5969b9a52ea613769a65137ab11bc0d15eccc7d7eb98c71173f5cd548f06eb0e8507ae04d7993b34407d389b0a046f24e192c012306b9ab5b4c9497985185430ba511aada683db869352a37e93948b2a7d75a2bc72588d54f13ec7b886a558258208e4005e7a0317a721a57fd6d2c4ac6bae4f1ecb93fd1dfd72debb8f7da4b986d26aa055d2d9e9a1b15c89c62097166f2cca5b3a133abcdd152d69d1f13ca0b4708c264c5d7a298aa5cce299e281ec9c95755b33a1c3d7a046d78c7eaa14df33458b642a6636cdfc0f925561f68c1cc7e868d2d31b1e5d318e419e28d73ec1a206cd694ce0cd4637956caeb778630bee52691252958def97a88f4c3aad9b29133b3502c73de0622b8cfd9e6539decdb3f356040796bd8c9de8ce0360969877b531636694a48b063b8f350a503e60104c598f2d42d2b6ffefed4f83e4086a7fa7690b095f867784e99130efc98a39f8cd322714603e072f41cc2b49570722375e631a8691cb32fac31c84d7a81b490326246e5f6ee53ff2cd0cc62ccee594191a781e3d5336d7e62414c70fdce8119c04507bc741436c56eed8131be3bf04142815e29f05287bf5d88dd0c1ff041428adf0500f7145225d5811a6a8be261118c32af7549e53f4b10727da65702dc41aef0918cbc8d75e20ce7e46c2fd7e7145e2efba17a3fd5e865964119a2785102c1a4fba30e0cb8ba128ef9c7a3c105fac94e51d1a8c47c90c093a298eec3b4afea3144832ea58b6392476b2121a8ffa50ca8d8bb0670db8b8bf39f59d71c67b6c1908f997201f05d1dd5320ef3fa889c07809a87005eacd497a6beb9e40754d45411cece865cba7ce6cab1d0f5c2bcc6dd14e7cc8d0fbcda8f446893948924e8e4ada01d87f81fe586edeab3fc61c06a3fe889ed179c01d18d5d18d2ca2ebf4de4266f1487235dcbec04c382e67b7b96ef263cbcd0f5b8f5eee165a5e307294f35ca0b6a470ecb238880379efcd3e57479b64b06c7436450b6dd38353709ed7addc9e61dca4a69349350ec90158c7056d52fdcf2165e2cd8ea06c8159dc20fb1aab5358f186f8496864aa2fb7f005b0e68e5ea7d58ad596be96ddb6ffa8d03e968b2c599ff49bff40ffe4a100dab2e0a9af88c8853470c0b0be4eb3e44d03decaf0c2b3e77f3586658099743074546596ddaa0f76fda9c4576f061cfcaba691d20d34551d89838c9f287169bdf7a4bf6bab4d845f295b1759ddf06cb72e0349af25df4e61f83407bf273295f84e712fdcfad67d4700ee6cfba8da7c724e0280c0e8b0bd5e9d94ea5ebe3e17a537ba9c0e50d67bb3fb4e4c16f50ea8b649a343cbe72b77dbd59ec588bf915790250202dd10f3b3f5a50972bfce46145a844fce93e7fc3a39bb8f5f264b404e0ce551ca7db9000ac3d471939e1d78d29affeb6fccac41e45d7d96559b1735cae8cf7c7e32787ac64f1ee0e073c0db24b015ab3b2a040c13fb4192420401a03bd87f85ab8ccbf80c0f825328c1db39c105b8d2c7ddbefa6b69cd34d5e75f828f543c76043dab30ea62387e6e257fb20e6dcc2e0c8590fd3bc6422653a582abae4dd1377c1cd6bf4a2fbf9833b99e91e1780bda61e32a47450ddf17b06d347aff92d1002e27f177b97751520827491b639036dbb483a0260741e109786e48b8c1938126b895de91ee94f073103aac2a3a58a092880cb739c1c63990bbb160442a3ea3c62822c92e1bbcf5ca26b114a958abc8eb6545beb3e93e13811d69f52c6a3098a336a024881eda852369d6f069c73edb6142c63e1d67607007b6d37ba494603064488c2daea01d2b542de8252cc39514caad8a98241538c42d959de569c1d2043992ca71ed59de90b91da4276a68a9517336254b041d202d4f04a74d3667d95d91e101d0cacdd3355fe86abbe8f112a05b31216225b114f63ebc9204d051d2d54657c80e661124a008d546542d2876804db8da86114099b19ad230811972a196bbc76e1bff4075832ac084ce91367c1114663bdd207c10dcd7173d7a546cb41df09517639272d6252f3816b5df0fca75b5c4f20c91f11d27b70b9d6685b97b46dea073b67f373cef11aee84a07dfcfa75c162efce3b33b576f50e490717440fc336493572651ed9e11b947e382ba64f94f246d31b51f82b02feaa8bc90c19b6e19edf9263c972dca1a4cce2958cc9eb47ac4e5b1d3955b8509952b50c7206e1c58b711a3b1b681f110d3e6d5afe66c8b57ec6d4bdb581cb1e2d6a07081b6d004bc7562e5ae4ebd8b034407c6818a6f6dccf49d99c77e7b2e19908c76bcf3ec12b3376af1385165842115ca7d4162644fc0db3354e80124b5c291206763fa8c50a3a19a0ff051e0d46afdc5e8ce608ab25032d760a1582320769b439a3f958fa294c79177a4f57c85992e6f3436e23cb9017e935732528fa7661829afa83372d51a7c63e1e946cf4c9e62260743436ea102941ae82cba04a9ea739b550998cc385506db268a9455c911e0a1099f755b167af95ca0b468a8913e7f6bd90809aa429c7d68c7104577883f1c22743ef09ec7395f5b533a8095be91b7c9ab6884769131081dfbf48ef8bbc4076e824f3ec51b460b1911a8cf3eb1dbf14b696738203d5ce2bba17f45fe1b4f14d02061486500a292c46becc7a688168df040c9ab1ee8fc6d4030c3e6f06e851cc54c881c14a82fea782b1315b22c7e3f0a1da2e5db0942dd642a7c7c9940b4f47485651d9a6fbc6cb86ed4f5f1d8d99ff922828c2ea50270ee3ea4c864cecdda00a894bdebdcce2c3901ed0dc601830aa276ea90198204146accfa749a74ac40d5b82a7b648d8c36d9abfa31146cdb284a60288bfcaababadf938f67b1bb223a1bfa81f0fbcbebd24daf4ba41751fdb6e4265c5a50b07ce69c4f2f3d96c35863d4445e188acf83e5cf76bc841596f86ae16f47e8f8cb1f57579c38b976e7a7386e8e6850cf778585e553a3945e61e54050923ad7eea7639ef52f26ce0d6766887c1f62202a1b850ca3c3b44a77cb04563b30ba57dbaf2e29ff450ebf5d60f83379c5af0e7e4ac01de828db26c4c2f5c11d7591706c756f2b6d1098b852de236e555494090d706fc11a3f4f9a2d115702c72bf211b3cbd8f052044e28f4f2f71c27711916715ad96cf035d1ed588efd2d17b20b02ced01a87272c05cfc99b9fdae190b3b92357cd5e4051d5b2bec459428e4cb0ede41e848a0a31aa809208bb531e032aa797e4824596da07f99972ff6d6c1b72c911655b01530221bb10c2b953e7a1c9bc808e751fafb34db23cb349ca12bb9f2833ab1ab5b1a51c2da99cf600917e6264060e10d2db35099904f932622ed48360ac334c7788c3528e668b152e9262d802cacf413df119443727ec3effea4468662fa6c6994d02159159f0220b73496906732ec3c4ca4c9bac873dc90c479ace8f52b71ccb7e549494666658251ccf8556c7adfcfdb9655ee704514a262c1076cd70929dce25c836573b069cc26986c3c855824339d761e4357d482f5640c956a733fe214b6c0357bc247fd8e9246eb9a256a20d5a10bf7b0deab4b241a92618c793f688a1f36b0c2160ea2b2d41f66678881af4dd91058ef52f662ad0a49d75da4073814c7c601c230ea59d524fb8f911e0a6546af7d6c7288c3da58aeac4e1abfb894811e212a2f9f541864d249c4332429e0c0667e463279474f277789105b5ba03273f0f319c7185205b18b4f073144a1ed081c9162ef18d71c652f74befe7313d130941952cf4312338f095163075c91785af6c8229f2c609c6909e6bbf117892f9794b67514f2c8254fdfa5786d5bf042b9922ce2ede45dfb6bcc6e440e2aeed12788c349eaadbd22cefc184c663b1b99ede3681cc52ae0d81cb51eaa92636f966ccca8c7177fb0a21faf331ec03e29eb4c3edcdd5bc2e7db0f29f2972668872cddeb0ddc7983c7f851cb6636bc52acf62d283689e1e66284e079ee6d3604f18f8125ff92b0dad007677a43ae102dc6c21b4cc31599e26d03c5b9ca1f1596a631cc2d92c13d014259da4ad2e027bdd2cf25b72dd0dab9e41995cef1085c2e8e1846f3154a4943ce33f92ba6d1a07ce238606c1c4d415fa78a046d7e0df504df6fd099d112f6c0c1f19b231d8c628f450668f1e31a7f2c61646b6c63c3fdc39cd49cd21853a22d40c66a6e5378e82de2ab0b477d1bf8d50f0081e4bf415caf82e04b895aa342b9e4c0cdec3ebefca5da545faa162628888333b7e32c0bdde0c1343f62ec2f9a2fc23a456eea48de8cb3a9a2a4085821f2c7f749c27d2144afc426878d64829245fc75edaefaa2dd0ffb94809272fcb776b2568c433069df6a5c64d440f06516539e2630cd50282cfbd9151695da13950349541d22b29fbf936778ca390a27727165319701ea0b8b8418f0b4184b4725cd3eedafeaea12984cca61a44b77dde4195dfc8a2b3a9ab1133bc251204e8e5a24da39f82f2c64ec3ba41a7e5ec7bf828440be64073bfd17448438419f0b6399282f2a2d4f335f9ed7d9f748b379c2faf344abcbd42e3a08c648a4205fdb037ea9dc48708675685c539e9c2b48b9dbd38ddb1d9033fc41311c35b9ade22a86dfb93415cada5646b6c67c3aa017ab5942c744be4a33edc089d7bc163c224b0452f3b7dd495a10eb1d480b7428d6dbdca88a504d368f6125ac15350d6440b60f3a0a10450858ca80f93b44da4c27a10973cd323ef86199932585250a6a3e3ab408c035814190e76cf93da062b5f087c8bfc22fb91edddf66ead5450e2ef9130068d78f485c86be9802553e8e34a70ad2545eca03885633881b85507c65bca2ab747b2992bd03624b6a5933bde7fab0482b16282171c76f6fd013b2c3daa3e7d5174bf8726d2c3f402cae5f240f5be729d8b024a95fb49ae6245f6f02dd49fa511b5de45ea6b4f0d8ff5d402193912b3f96999b4a7fcbed572147e0b02f1d3a78f1cfdd6300f93b9c4b18a64efc1b649cdc0cf873692ab249306c87e27d39c136c828b94163fbed501f392989ddd99c817503616ba9c453b3b8e4326516407572dece4e4c04dbcfe9bd76face522671f1318415ad5cd38fc7c1b1972199ab7e90fdd6bab8d63d3613af408f6acc76b054415c06050726e684ada91064409aa081d145761ec27d891df81e195b0e08e8640b68e126acc23247508b79e7e9cadc730a3f451abd483f01cb6e938758216c0f6b1d6e7b67d8a99acd8c1ef1dab92d2d14c977ecec03b872a74f7632a5e1e318749d923b9f8ddf0c0ecee5b9f0b1774b7b81e91b5fb1d59fcc13c0f16993cdb78a2457e657edd9541476670d447538be79fefe945f0fedfaf24b79b030ebb1a16536bd66b87b23a3f4d763b8291db1cdbe4ba366ccaeeb324d91b78a2f533bed512a63ce35b5b9320b643214a6f2c69c45535c7fd1a3bd0b3495a59e1ae8b2f85bf3a39f3efac8e3503fe01ace8c5fd565ff5cf207620c2a1899148d9ed0816bcdbb2ab8574fd54c84d97cd4442c0ee14e3dffa83e65df4b17607ef01dddcbcac52c88363913dcc2efa57b03a6637ec44a8c1e252214e728d915c0a7184b632a3e4f1a92b520c34488a45b57a27c2e98c991743e0fd8f916ca874364abaee5744c61f4024966ccc69482af7d817c4cdd00369285a7debf869fe646fd0903753b1440995a3bab952f91393f1401d6392fc643e213146ba6cc79940cdbdb35310d4d85382a2c1205e528bb24263e850170540fc1d071c4223b441f1eb8d783220681329d1c25b35480311bfad007d8e9b85425956c67b19e2bb4e62b0f6ec495595b37a96aa053d0b7dc54a2ae2223e1227e8fc3a16229e4f9a904103e2376898c56a6f7a032fb17cce48c298faaf103b433e7edb7f4222a671a47800d25d0fe2f39f7db4aa0119b7166a9cbcf1abe35869c52f801b803a759266018ae3c349ca05756310a64203f48905146a81f8a3d9cd0091a8fa7cf378525e350aef1ee3876289b166c10187b9126d8af1ce95b482fc95d47b29b11bf70efaebc56ae3378d2f9b3160907c76f80ed08d5ae32628efbe6f216cab582a0f693e8bbed4236c06aea5d886c3d4f7195033546dfc22d731aa95b038fdcedc79f827b4567e0de2d7f86c79e4fd0d491165bef6b031f83e2078847b52d21fa6fa6975f37cd0e50f3e25e893f704b6f06cba4e4846580f8e70590cc30660c596bd291935361fa23f00803277514e5f585709a71b88be0d5a2b051cc41a5135402416e307d5081959e85af76050b453227611f6d9ddd028dad8805a3c286405eb92f010a8654a7c03183c1bee9173a43bafc2b0cd34f7018360fbec1d0bd46f215fff365159de3047f461830b66b6527eea31baa317a4b27b24ab6013d434467cef156bd3416aea227b5386c11a18768f51f45ef9fb511cfb92dc127a24336c6045d3788951c6fb2c2ea14a73206b7c69d03a2991f563a9eec1fae7d42f793b61295862a7028bafee1b9d5546ff60d8350e56ba071b7bec964b30f4e0008ec1849bd088e1cb28b17366a110a6b39796408140386ffb8d4603830dd32cb4117f70da94ddce89ff8cc0fe4ae0b3355fb90b1c620fa6d85c3b8416e8cad051825ba064cf27979bed061b0132ada91b9f19259f83e5f419918b2f05695e690b64343c382988ebc69bffae31fa57d1b713111465030aacea73174d4f0873094cbc96817ef66cd4eb81c1eae442ebe8792b388a68062c039d16071cf244c4f191a07aeb5a30019c2c8d6d40facb5538438d0c749e5dbfe78487138767b40b47f499790075b8a5dff57c70bd67f03dc5f73a18871de36aecfd292bcb0a89b6c0e33ddbe35e6a541904c0b8eea67145f9cdb4fa0a79d7a0ce50d812bf579259679360cff1398fce1dc06c4599a00332959a472a319a46141cd07805c010c58555e054c56ea14b6087e11af74c0f6b99c82e26cf88199b95e03e0cfb2cbb193dd27233fff25e5bbfe64f9863c1118beb13239fdbe3500f77e4e02606e9c7d3191b6cfef270462df39dedf9430cd093d0f85ed3d0f22b457186870c036cdf239b5d177d05d79dd4a761af2ea46b3a50409e56da306422f4b0162347a7e79e9468c192c3ef773fe6412629dd9b6b0ebb48335deeaccf31a17bbc45df14f123bbb6deb3dbf854a3fb0037f6c88f7c3482bf8b4da1b802ff1b8322f64cf455f2d0b10f9ed120f1c93dac20d0838de107eb9bd1318a61f2dfd8936104efbac5f6ffb673562329227240b2ad50f65441ee15ec930dc74b087552f254753f9ecebe2d63bd77ce6ba9a6cababa926796bdfd5652976455dc1da7ad0ff873349ef3dd10a1a5e0b92b3a6014df06d80dfa692cc8407d163b5c0c9a8ace50d1bcf164636ba49b92b7d49931405be577114347cf1c4d85c2609126025ce2080783f01737879249de7aeaad6f33e091d54feb4c45d2830f88efa01fb32f0fcf67dc66ba98b2060acf504d0ea5df09eecc5a501ca7ec9901871d044145cceec8277a8728ea34483fc480f6cca223019c9c7261427a4b418e1d0ac40dfa170603193598cea1359b3291ce8085318a7cb67548aa1f1243db75719da8f17135064b984b9e5fa96ff056f638837e169bc103dc360a284c06d3e68e79a91ee5dc14ca97ba4242d3b7e09ce531fef4582ef2383131ff5803a36ba05e260984eab61be17d5607d49ce5f6db11c50e90df29def9b44e9e48227c5a6c8a1dd37b5cd59da7f40644fbfe795e5712064042e259063b3b834aa169f3cecdbc2c2fe16ed9d588942a664f6d17a9869f6532680f3b09fb420a875c225fff1fa2e26754b84fb4cc1ae54e4101457dce3d4b2c9177c78f5caba7b5498756bd6345b18f2d3b9a9a7e56289a368f4893b362ea98799b4a76a89cf3261cc1909ba5470d3e078872f1b7287a36c986d2113a29a50964d5c80bf87f3ca481cd6748a91c31e94e8ee8cfda615ab9249eff787bc615e014a11e75cae53f9f9573e8604bff5b880ef8499703463db5a71b093adf60981bfbfd00fbaa8c6690d0fc27b2aa75d58570bf3c1fd77912aba09dbff32274e73c575865d5bc73f9f1cdce8066f925ccd31049c777be01dad7452349408566f4c2810c13b95805792513d0236b4ba7e142e5c8c7dc26e0a628396c45861b614c5f8d627587adb6574d276e68b0f25bf0e7f10e439e80db2c8370813b4d2b4a03c6a5817767d5b328d1092ce45033748f570d0ca32b19d25fb6ac12c6f8b484a8e2ea509f4a11b7996c9e6d3abfe2f1d89ce3a2705f2a1adfeef2ec141f1f894bd5d5fb9b605bec011c4753b25c4f2d4c5d35cecd409ca6d290eaa52e416cb0d601918098a9a9dfea46cbe6e4ef61e67094b313f04448a5e0706d49ecccaf23d083dc8f4c8eaaffb8451746ce85851894960e7df687bd24babc959bb71084e93a0b575db7897628675b9139ab6a9c31bddd58c014a860a64103053ed333214a4e8f928459e521ba4bc1f1936e7e2c0ad3a4f35f5545288345ed1072a302aaa4bb422d041ae5518deaf10d869d939ae43d6bde03b25085483e13311288a27ab6e211079d9141ef89664ce6289cb2dc18097b444a5268fc230a81bbf4e01aedd925ce2d1e73a809f5c1bc6a33e91b28059632d1c7feac77df453a38b5357cdffa0d234d2a86ed0fc34d019e7ded9d87cd6dbd537b474d448af3cb74aa45ec1c6bd7c59a7078820f11e8bd9e8e8edeacb24372e4dde9451ae6649bf4abd605cd6730703437a2f36916deb13b8e8d183bb4d7c32da9720e9f1c23850663bda3d8c268471de84d892fbc28e8ee22e3369c196fbda6124cfab2c54e15440c4e27e4429bce9c4297287f8ebe939dea95324eeb981a14897bece61e35701393eb3ab659a3159ecb19e6d09dc0d2a630a58bdbbac1e0c536f2b6e604eea740054e425bf34f8895719a7be3a4117580571087f49088652af6450ec20fd209020693e31fe58b18eacda49b3b245dfb0ae0aeb0d1c0f036de53c8096582b813db096f3df7aa38da5803fe591c14166bee99118e227b2c81149322489a56a696ae3e550e23f88224c8db8ca0f048d246c9507ce33f901b859235a225e26bd0f35a74a6a7d85d95745ff6d45ea5aae51ba42f5fafea5d64f7201f905155e743d561cab3f0dc8d03398852b8e24a1e27558266a7a868de044c72d3d1d064775bc9de806a8eb7d2d735d70e37f34c33e4c1a08828f333130106f69c60f5648d4f4419bd6cccd57188417521dbd1ea4f0ae2257032749fe196667ffecbb3497ed10ad35873c44db037ff18123195fe916afebf28a5fb90d91c91c702b4c4f23e4e9fc6badbf1b678fe76ebc11333a6db4340e9da126dc84c2ca815ebeda2f7ecb7654df3d1958a7778ee2678bf64578ccb93cca228d770494f0d59ec89f1b2800756690429060430e77c0b88fd7c2214c559d44474b39fe269ae6fee2ef6a35d70b219cea7f04bcd11706e2cd1f56fa8ba8d399c0cce55b18a3e995882733ff0c63c5cf184f3a38a91a902b433dd3c2d3a0dc4c50e30d5f3f9bfd7f60519ffeea0ac96d4ab84c8b8f329161f4622656f01f7252006f24ca1f23d8ce243c4d070a30183f5e4f1779703a65a48da90553b6e410738c84db270eb129c82a62c26f812d9628a15f45a8427f6f0c14100e398c488633d2ac86151388697db61992fc4948da136040ecbc0eb76aa7aa07908b72cb2bcb719a54414c3755229bed338306bd4b5491fa29ee3a16525db5c1007033db451d5cf0da302052fbb766de86f3569af6e46ff0945858f9dd52f2a72b2966d7c54ca7131aeab8b67266b94d3871c513d52e3b532b00feac4bc93fc6399237d0ee6d69665b3af154bfa646a3a578b9f98f7d858893507adf1221b073f2e4c50b274ab280e1b26a169886e2bcd4893c15548f11c7cd830505306779061add04b1e5734bc67df84980acd74574cc205543f991f118d7284ccb4c4d9f54d1871a00c6268a48f9b59dd4ddd4bf8f861a3cbbcb5748660a8182c6cb3e1d6bdecb54f56c6933817ba12afd74b8e8ff631203fa294aa2091f40e4914355da6c1f0bac085388d17345766808ae5abfa59218950dd369c6e256870bfb5789f5e1d4a97340f9a962e4937aeeb0defd980e20598bdd68425518b018d37067d1c365bbc247730db31695240b2e2883c58a8edf1d9e37108b7c5f576fa2ac269b26883f541a7dffb25146bbae4fe3900301fcadcb8bc0a00b0fa316ff8a04a6c55209fb127da406f9674009c2e20db614114fc5743383c2122c52f27daa70ead2d5e00ed2a50de4b67d8542b08675fb39539adaee2338e8fe19846d9a18e6d9682122c8a0a622a5b835db000d3d8318a87a76d769d872a8ccf01072f5f71058770d1b1dd5cfc5a5af457e62cd01da88297319668c5a8645c102158c5e7dfad2932711789a44979ad7e7337a69b7dcc29c8b1279b4704172adcadf81ae682647fc684351ae5da0ecf6ee9d900cf8d7d29e7ceda7385b98b2c05f069991061bfb00b1c7dc668886954bffb0ec5a198982e4ec77869fc79da7d95b92cd901d8c36f6771a64ed81afc8fd0c0d8d5f79d4f919ecb4506badedaf8fda5e3a9de7f8dea577def88c1e0f1b0e14e08a47ff85c2a928f95663d6e2f5d1e9dcff9f001c1306db2243ab0c34ceb26432c877597602bb2e8bf463d1f9afa8e39de67b3b43f54e80bae59896dfc7569b043319cb53e379215b0c8c8b8a7c67f2e6698d72a343f303e715db24d77ad2acfe83d3fa1a65d1f94d719c8d4516b021ae0a488cbc52b862b54cc3b2a858be86a9437856a978eeecc851c8b0c0845cb597ce6b093dcebe0c05b375a5c823aa03e0449c9772e35bcc145c1bd7e095afdec8396017017895285b31782509314209e3c79ad3a9d80b0f7f54390d54ba0699d23e53fc0eb266fa7d777fa244a7dd9dfd7d9313f8cd3980076e5ab1e62c6aefd049aecb887ddf2323fe90df38b2bf76a36f305ee2b430ceb0b2879c63800ca9dca94a5b0dd6ff768d516224ee7d9fbe04d42ae16f8967182d55155f0125526c8cbb1f651ff2867c8f301621f65dc3334fe96b5ef49f9d913c71420533ac86d769173c2e9401ab74c243696de8db47068b7e0de56b63be10cab5a6f09f7740021fb9be99a60307a90676a65c5578f9963a4cc256829811fb2b45cd13085e33c2390f21f9cce78ace23cf2788527024e1041bdc1a430f6cdc57f36b617c1a45267990addfc14380ddb3fff321b0eb61bae24af2ad33de982b0901cf760ec0bdc59cf2a1859e9f0e4269c3e82c06c354ac7552230d26f955da5df27b1957e9570eb687b724da2ea8a377bd068ab26ca9cad6d50abd1d87be51c5d15186b4e0c898c7db513b0bdaf74df89b18fc88770e32e0263df00adfa8afab90f160e7f96c33e89d3533fbe0dfbf6e1d1e0799638472e75e01a2dadec7ff6d3dabd5fa79f3d5d3d74f1ae2ed72793031290757dbbc7136a782db59ff8c85370b2c08025dfb768a8d487797c1fb3b0568af71ac2cf2ce91822fcbebae6b7b154747d6cc2855262fc201294547a187f5db21858930fc157c57f9721204018aeef0bd79ec5d93271325891eb61f06fb70e6d4618abc0efceae62c20310a9063371e5cac44185c78c67254f531638d9c61ee5fa2a4f02dd8b8327c40b271ecc475f411375ed945f2bce50803d41f84bba76bb3e48fa814d610b83136a0589682a57d852f601a757fc627000fbc4d2e2e9035492d7aef38f07aa0b2ab0e8d444d6ca9f2c5b1f92253de07ffb0a17c7990a05665d1cd97f57c089001d3e627598132e8b9e963589234398950076b0876b316eec96c8148b8a2ff3fabccc76c488cdd72a928f6a53ed3a95296833ab8fec8b77be7f10bb1c14ee36c17ebf3108468039b7fdb468bf2f0c6982bfa45e09d88356659a47e95f2fb4952ad29dbc046ad40ea9aed96027fd6243fa4b0d4e2927b06ef2cb77bbf1ca0d66055146aadf8c967858499ee6c85ec3cddf1155c4970cdad5eaedf0afd1c9e7425871e5a1f0d03aed0f042a96b81bb19772dff69f012dbe5d5ef90d2c5052b493fd9c2d0f032d9ebfbc648d5c887cf90b1fd5c93647b4628a8da8d0bfabfc0cb568faf2926d741670a2bb384b7884da13df42a20b91ba8da822951014bda239c58d4e16a4e7d046c7082e741a1510b98c376c596d71227b03550f4cc767bf3c414bc2219c7e2a1cf049bd21cab406de78576d9be0c7aa08b2c24dbc34c752a6466671dd05bb9d0ee2a35b3d5b5828f3e0ce7b48de3f4021657c997e98db2004a823b5e7f98e5fa7d010d6422a023d7850f982512a0248837c6eff2322fb7b77b6862b712b3d094eb835559d72a14689b014ad943bcd3d978942bd1c5059ead3f763339184e10a9303b6b117e2de1eb3003d7064ebabfe52136cb6c999065ecee0380588c7743c848580967695f6c8e67a51a1286565e2a01fb69b104d74be196c30e4270505ef4491e8c8d0103116093c84414cd8697484bb6357465350c180a4eefc6a8399ef384bb39ab2e52f8ac410f13741d8c52f0d467a5462e4852c208ec49f65635abc2251f3a9d7975774e076aaf7971790b78aa394f1587b377075ef14d6ef3acd7c03a7b805f3151bb09847b4045c0a2921ce4e448fc1e0dc2c7833bc5cd4b76de9187654f65e78de35e0c3db36a464fff70f295b04c0fa2c4b03f660a5e875f1acafe4333cc07706290abc7fbae88ae1fa8cd454ee1150b6833e437fc7cf59c28dcf99d07123860aebf1e36f1d25f5c7f4dc3fc578f615dc25340ec59fc27b0c0a5d2b229a3bc7446e313534d9156ccf28a2dd7366ce439da125c124aa8cce12f62f49b57b190c0efedee7d9f49ec8f91013f863474e2df0cd8782d978db791f84b9027bc804916bea310002f7ec933a7f00d10af21d1176aef1a78249ff41ca4603b67ded5bb46ecb6c3e084b465e733cba989d90a92c839bedf72b51108d7bba586494b95c5d41906474ca12e87a9f88e102f0ec7d9ccb23027534bd71218b224a5fa1f21b21679bbaae9094339aa79ca68e9ce5e4cfe626d7cc98484f35db7ba5bc1409044b5e3f6888dce2e8709efd2283327ea95f037c4cfb2082e526e713fb8ebda8417eeab48e9d8c78fbfe74e0bc08129ddc3895533a2926798009eb2f19c884e2256983882467d876d1f6ac0bbf5027e24ef0dacc9967895c09a803e858799a4ad6e5a458b7cc3f73d68527f36ef3f2659d96c945ab4137241ae4280559092d502d739c30917b3263e7321b53bc2e15fe5fa9065ddc2b81d2bc11822fff8ff012c384fdbef667b7a3e8aa276126810f2237fa9a7b1c96c32b20937963729eb527ea326982b5017234ce330a12ebd1c93d05fc06decd5abc694d046e0a517950fe005ef86dc66238610be6716b67e54d9f427c845c9965a3146553794fc236b2b050d7aecc440a00c3d46c8a5149dbd902256a1b4b8073bf68521e6bc186c392b6864311d21bbbd72684415e21f0428891655e96eeae276a1958c8fa7ee031e2bba2d75ab525ae2d3100a20741823747d892c46835803c1dd38ef419474d9d0573d303239b10fd6c104891cb98554459d2ba41382241d86db4049638b1e9eef17edc3332e9038f51b15123f95134c575d2ac7e8921a48d38f7e5a9ad6d25c0793498c25347c80cc92f6de08be9c5092506dbc9a1c9a1e1646cbf7d21f9bba110741ecf26d9ede1aba247b7fb02fb8e80f773c6af5dd4807de848e57fa0fef3595cdf71010152e67c0b4988a28d431faf8a0ba01dd423e3e3d1030f29643fe17bb72f29a1dd80324a5f92ae414d907655e1d97f688b0140738afa5d1ea610b5a8d7348a508cfaeaf2bb8924e462b6516cf15a5f882b1df813803c4cbc0f1b821ff974364e3e312ee21c2a6d3fd0854d8e2e086979292a2dbf43d3ae08a016cfc384680f66cb64c97e8fef67917d2d3e34228ee539f47ce0595825361a264187504b2920c1182013784d06d418b89dafc1469d6f4fab5d8386e940ef11e46fe685cce87337929155f15006687de0d1b1bfd9f9487c4e77d623bbcb9c96da745ffe477dd362e7f6bbb7f37690c388529cab9ccbc577ef96cd0d32ec9981730cf47b2753fcc26407e27aa5b32a229ca3613c02806859097b75cc96c46655fbfa208aa902232821c2402e780cf933608b98f0f3c52f79b15ec196283f481d006bd43fce075e5618be101a717c810b2e073914c711549fe22c39bcf25f1d0b4c636ef92c2fc97651011b59f99c6a05157c7f7aab0fa1645cba9e7395dff61e13e6552bfbb2ef415f46d5ae71f0f7cfe65f3c5b39b5d34db04c5f166702b2fdce5f04c487ed3028accbf3da510c05cf104bcf5633ab4a44ea32df7814acbd72fefaa3f1da9fff62974d833c0b732ba563634168a75cbabd4b4785c762393ca75efeff7bf5150478509df5f8709fd330df110ca2198b5e4ce92fc5c613e4d41c21e79029de18c01ba130972557153d76b21c04b50d6e1a013158f5c47e8a1c77b0aa5ed18ecedc4a523a5d6b0aee7ea0a75572a0b54ad2cf96489577c86edf4e906b1913dea529cd7e9dda1310019c6dc51b4641a038ffd7924b80b9af6a12dc7f8ab09770612152004efca8a65395dd248c96aa4845452b884ae48ac74d7eca51b40e9bbca282e14a265938706d6b101dd7f5337d4b725c61e1a79e46a29303b78d512adfefeb0b1e7540df32f7e513b51def3dc7ce29176722ebd991ab5acf9d7617403c6985cef3601ee5496c7582ae4ec98593a5b746586fde02aac4a2065be599adbe20edb135e9d8ab13ac00add4fa50eb592d5630bfc4c2d325598bb97ee38956233d1eda5602601bdeeaaa8d2904764322021c3a64a144cdc2e8b76ca3b9825144e9ef8a5b9cb2ad281db08caad09dc51d19670b975128d5832d4a44ecab2dd4e392396eadfd322f9479c637e4fc10b55b2afc66e6de7ea30ddfb56bfed8d3e05e0a4d2751f49310c4419262269bc9a2ebf4f8bba0bb94d452fc60a2ed6bb0e578e4916ebbe0ff95ce265a115d4be44b6bf650b5a4eb94fa77341969b3c0ebcb07bb0c7821bc0709a93b4f2478454b741087a35cd07a13a685675ccc2eabc25d8ad3c7f5af234e7241f623b133afa9489f8453227b0a7a259d43721100714d2857f9615cb846ba363ef789bdee76bd438c40c7fea960a8bd55513033348699b7961cad63f6f42d66cc431e660e48bfc1adf43aa0dccd01e4b1ef20decfb4d5cd47021fdceea9e16328a0de9e33187ba04011bf12683b2ff85e8602730da0eeb96e5e1f2ce64112bcf9258ca1ec60093077f690daf1f0b4e95233a4d87c092d8bb758a856be895afdedce82e5148085f7e1aa71e279b8609cc1f89c8f2dfe7201868e754b57eca92a074b05ec878cd5db88ef975ce4906255b3199ffd16caf0fdd4c9cab70ae3872cd807b8bf4a364babb7acd6a989c89eeec35172fc47e398a85490a272b8feb983e69ba725f66a4d10556db21599d82805f6b186bec99ae06d8c068dcca64cd4d34b339b5e9076ab1a4fbd8d93d0a378cece535fb0b6fa581f66561d14b99b374f18c63063e1e7b7d1bb02ce395772621fda74625592a1509bfb7476f1089809039b7c2a26c2d64c320a5252f91e4414ba9061109f2860c2df52966398af4e4c210b6b269869702a909a19ba3482e066d50f8860673122de6304c1e9f974482078faffbbd3a24d040a2498cde2b5b943e20e430a95b7f307517543042edd7efaf93c99abbf5337fa85bcb7c53361c8bcf45bd1df499767c06e6b73ca5a7d4e06d6ccd0628eb48813e87d6a4cf519c1022b4dc4c5bb33bd6f5ca7d45509dffd2e6e76fcc88325810881ff9d11147fbc6a2a1b97fcfb9b5426003eae371b2992d95c0190ebc3bd6ef09e792f93e061a6ea994b32d401febadd90de2a5a6af4658c78c6298fb31bdcce8148b972d98b7cf0d3731e6d91f840062b4afac78bfb64b56e65b2de22231bbb313dfa49704fbf7cb077598514e25e43409fbc3b4e65db3b0c0ce5b835402e66bb09dd55a86ea57be1e582366d67ea58d365943c4cca29101ce70af911d6cc3cc204eb2fbcd07079a098c6fb99d632db6ed099b602002ae7000c4231c70832c87bf8b916facb0016104069e0297a228e4f24b1ffc9e37e8e604403ce491e4469cd0592bce9a5fcd2c1a960d2a96176952e6e6c7c53c950e2cc3efe17f4f8c0215f7f4c9d81c615e7c112cf37234325a9c6a041abee4f2e1483525ed7ec6f864c25985ac92e2b098f6ae42ee429a451305383627096bcf95018cb22cd9a1140ec78319de29c8b5211874042a1a2b6b2005f714d3c5688ad364ebf96c59216c32d325bd66b4149e8acf9817483c9f7264dee0ee6c17252fab5f1afb4e41f5d513ba4cd18c818c557174f6142668932111ae8886235d28355e8ffca886470becade505fa99472c03cdb64e9c76d20e2fcf77d002c69b653060cc13920b703afa67b36190193abfae2edea2b75ba65a8a94f147cb10c388915d2e620c813591476b40526bb0487d18e28d882d588be8c1b6c01b8c92b4423a80279b31e14408086d27f1795adaaee62273c4a2c09674948107482445230d40c2d51200613e4a7801e2d09158503d1c63e1913580a22056a8523239fd8b5eb7ad4d6688b65dd946c63c5e0343e9e207b744fe28390bebe69ab19bce1671db644f5c4d706294bfcf23b1045c0dae24a1b548f832d704c1171d668e046a29055765f64cd122c83349280207f713ec6d7e3362320df75136a5305a49103ba72b27f5820cc524a0783fcc92540a636b56512542300ffde127ca48755ca33eaeb4a3fcad21b18b0a6c2379d0b877c96e7020070fc623abe006cc2c593f76252b6698f12d8255385b3bfd9974e5664117d49c8de9b6cb9a59429c914a5072608a0075147ff084d17cc60fd434a95744317dfa1ba4da541305656914d0db60632d8fed8b42a74adb921786955adb4caab6ed3afb7386cf5c686d996964d76f5943d656b3626b253d844a20f1a8cb25bf64b6db5fa6d255772255752ca8f88a505d7552ec431eea87bee91b6ef65b41db17e7ba46e7941a26cc8532ecf3661d3d8909f7eeb51b2e1dfe84d0d4697c2cac299764c0505b9504389c5a729a62b2f08cce0cb7f8a89415f6a3042b9fd852745cd08fb5d7563172737642a9f6bacff1642080db28ff94db5213e710038c4184fa85f0485a4bb7aeaf54fda0c6020d611c6ebd3f3b95556bd12b3f14fe85437723e47b1bbbbbbbbd65a8f1c8b952ae254413535e54d0daa9973e7fa57ab34cd397343ae9a33e6c63f8a4b110a0b8ff598d8138cd8eb62c0b08e68e958869283e5862e0575e39c7136cd836f9443e7862e0525e78d508cc5f735cdc3d930778cd1ca78dd3b07c1e7cf8f0760c6cacfe353f3a8f5b6b1215715b1d61381244b57d8ec271b3b35c8c15b8a032d754d70f76fd67529068acfba633da64de9857c879f7a46664f0492d0a9725d2a2e71749a6efca3168a11ca56e5912d2dc50bf0e0cebe6106ee3c2a21def9448e14462b979fbb1d57b08f69d085441063fc1a638c532ed56044622b2c53c9e8a166fcb1f62012740130c813b99c6c1524a61b72951179b9cf6c56242bc29572d42ff71e0d36acd9d31d2bb0f46764ee699023a458f71d21c5584158314887f41212e359ffa21f9f6ebf75de11ffa3d677cff25e42fc488c7b7aa4f5befa55fcd949a0e76dacdcd3a220dd778414eb7ea394fb20acef28c6aac32207e97edba851358af1acefa8f531e867b46d528f04fc59efe560c31a3bef48f7f6e30a859e6fcdcf0bdfc5747ef835fb59c429cdb49f45fcd2af48ac5f18b38c9f64025a5e52cf97a5df7e475adfbd7722b4be9751f7bd84b0de9f553b8ff5ac2772cab1e91971cecac609e89eebd35823042e223e25d80b7024d4d0831bbff5b343aa1cbd19bdd67b566c5867b6630536fbf8f463adcd7dfb4e57146375a4cbfacc7b01b14fff957df721d2cd4ca81fae3e3ef55e402a53ddd667b1fbb1fafac9ee595ef79af44224ae5ab95c3dd0faa61835b0c165282aae5031755b9f3da07b1a56f6da15458f487f186fd6fd583deb930dfa0784fb2cf3340ef403fceb7b25b02dd66bdd1722c9d80de95fc9d0e76637a3f4b3c9f28a6419fd2c760ff0cb755e9178b9af886f5fe85e96d198d4d967c46fbf5c791ce8d73e1328fd5e3fb2ef05a4be7f28bb1fdc60483fd2be6d3912f4110cc443ce1e0dcaafe39c2cb93010f7fb833d5b4ae9359b93cae539e753c96c3ca8536c910a7318b558f0d6a128050a150af9cd11055888d983f8c523ba5ce9e5f613c1e011285879310822bedcfe1f18944188b90d24defa452706923f2c61bd58a575c569ea7664ea9aaa24314c5a2614eedc6e40ebfa73d26d8fdedefe8d0245d29a52b1796cee98bd6d904e4a4106ca420a6d308f301eea98655946a3dc3e6e7960b0ee7e10e0fa34ccdd12b1be8492a684ceb9a3259973ceb92599f354c164712749994e09d5a6821455ca93357162369973cec9056dd2e4882964d07ab0a658a1e7888642082396cc07ffe75da87ea54049ec3084154ed04045455e6102059dc354032b3141f650dcb007d4144e6ec84f5041526fc258a3a429a19452eaa50a2a6a157652a8e20e8d32e79c93ce4922abd0228a0a3ee78a2e0d0b090834349440872aaeb0e2c9ffbc79f39455b14605695dd6c75a3071bca0f64d1a1c59bd0cf5a6cc0dedcb293e9b72ce3748ace077ce39c12ede68916f787012f98d0d4b2cc8374a5254ddd0be1072e4c869aefad81aaed8a4e887941bebf0c840459fc6a0c76e16f16def039d75d7009f1d9dd545fc4a59f4fa31affc79e52743eeba0fa25e451ded2c078a28a5947215853806350f8dbff0bbfc610ed7c7618ccb40714d0458a8c793c043f139c64e36f3478e1cb24ce33ea7fb941d7cffaa6a610ed787c1ed8ed1234ecd28986fb33a76374f9613d162aea05b30a160cc2c2920e924e0d0691ea2d0c18e17477899410c1776a40e52bc10a5704297b0cc610799e509a5945236db9299a539e79c5bb42496ce6001536689cc218a3c38bab80e021531a490c50b7660708a414561270a298c608edededebc00792363e08a715737ba96ed0410f884c8df3c98b71a3dc6e81ee8910ad12a1f4941fbd51fadbe97907aa4fdeab35d100d29cb8b525b836050a305954be3b21a2d92ee2ae9fa1663e762a0d690b02110b96e8df8ae27567e3abc8ce66fd33b92c27cfa47d9d3ef25847efdfa1dc93efb65e1587741e6571d5614841dd6e06dfe6929a557ab11af3134e2d78b8df1ce90b261e50924ebd95aedd71e840e8df8cdbe703aa5596c31db7caef40b91aee6bd7bfdd4cbe873d461f4fba83639cce46fad785d57fb8d3fb0d9d3cf3edba4c746b0f68528d4a75e18847ff5fcbd7ec4d79efbedbbedbd82dbb7f7fc85aeeab18725dc50fe10d7d5e86fabee39efc8eb47f6abd5e7dacb8c662f1becbc7e5f7df4b28f9da669da735e1d12224529dfdcec7fd0cf68a5af7df54ca0affd466b5d7daf9708fedd1bf5733f02c7942cf15fd2cf7decf50be97f202265b4289b6a98628c5c966dcd5572488332f3a10648a2c776231e1b729c127c931cc75b59997dd7de40a615bc3be7ee3e65dcb05e4966862eab276a758f05451837c6b8c48a4be365f3e07169bcab2ba2e1fa0bba22d7054b66b817bc055dd10516fc8caec88219cf65ee8cf7e98a66f8bc8caec8a70032a264e0b83264bc1d7365442941726524a9f2fc262a05237ccca9f3863560d77d6d73bb28244144252d0d453279b258cde76a3f57d3c0b8e26a618401834c0e28305c30c01765044085c31ad7816ef8737de8ba7781e7ba175dae7fd142175baeef7822e9e9d28832521084d405cf972f7440a2a44649621142c4192584b83367694af6f0f1adab8b2b61b8cda52fe9db00ac10b9aa252e7d59b9b8e1d26f592323ae9ca9a109942e26903c68b182e32ba59492c55dea83c10d6ba4d962882a2855415cfaac05144f292d028719435c5ca5d1428d16488ae48f1a353c74c31a2b9872a91aa815dc61228b2f97feaa2ba23f7628163c0061a1e6d2d7ba22ea0202aa8b3ca410183c58e0e1e14a2d5f5ab42b9a63ae78e2ca9f49288f2b942e6582cea5575cb9d44529a55d0d3b6899e109a8289cc81d82684329a551892b5fca6a0410ad035cd9a4c7d2a89366050bc2dc71d59942698618a70bead059c167aaca823a5eac20038cd30c3b4e96629471f2b1d342cf1a19766e7062a798a69a62d82c515a524f3d45b0c022f364c57462a85a76d840e1ca24b1ecc459dac254e9ecdc5995e1ec78a18599daa044a960a856539c645254da0d4fe8d454f58185d9942121472853459960c14798da32a558a14700c324a1acc013471853e577902cb198a5762d599f79394a69e986f63fa62a9d3063c4065466c2c0943063260c336f02800233564099c90206eec1ff018518d3303ec3e6cd0842d0f17721fb50679e38d9accbfab8a82e33c1b441155b5f0077b03d337050c6763ec048605746dc60b2aea81e2c8dcb5067daa839a3050726b0f632141a261c0461e56528343074b0049a18b8a0a90216292555283478aebc1cc504889a467ec040524af9335e4687c085667cd149c6e7b4f919b9c5c9bfa393537b216850dadb5e062cd8beaf7e19fff28ff148b157ff8c7ff97794c5fd339a71e413a570623346082f8208f326d6de14368c4edcd5b2854bf3882f1166bc8c1732e37bcd8f21e435bf2016bccf736c8637c5f09a5ef367fc8bbecf7c19ffa2f463cc789fef288606868d5ba2d30caf4e4f460c3b592d5bc442710aa7f0001cc56418cd16da5aadebbb19a760a16efa81857aca9d78a8e7ed6f5fc86fb80d0b0d198e3ac00da35310cc73f226060aa3d395641836e5423e45471b100d19eb4d55ea80815efdc2a7e14dd2c730907ce9f235505ae2d132a9f565c78ac54ce632475d3e40fd3008e65da6c15a7da21030900bc52c0c237ac3fa421d6d40344f66fb42171f400b0a75c0408f7e215755d9f8452c0cd4ffa6898d71a83450a2ba336b20e221edca0fe10703a434c240f2a50c6a50d24062c3a0fb2fc45588104519a5121a141465f5a15f0d5783f25d4f49e23031c5263c24b770b95a8c04312221c85410128884800d150fc9205808749893161ee2d897332e3251472465494957f8d2cf3e99cd7eea713569529b94b4c4431dbf081ceefcfe0a033592fe72fb6392152b6e686fff2775a1a2a27092ee002e434901c3dd5afc2359eeeeeeee524af9bc926337aebb197d38fd6ea8a8a44b68b0692029212669a064e9ef7822e9f6c73b3db8ef8f639a47f7fd318a7d90109f044025e9c647bafd118a87b4efef383da85a055f6e57cf9eabb56ea1e5cbf52f32d1225abb4da5795471a1d677670083657deb8dcc4b89da87442974f3c85483a51f910f095bff82276c1899989a877c252c12b21ecd636e3884c7c8cee306394f3210fdc85402fd76b99828c779803ffd6a784149f0af3f6bade4b1f2c75c60bb3c86f5bdfc39e641c3fae5d743d362ff06d1e686b1ca8e226c18745b4f36fed956726cebb21cdf1144801e02906c226080201ed22efd1e3c945d4a298d94ee6890067957580b368fb075e9d3a78fa379bc44982fdf68befc5e6e24ffe534c8ebe16058f0fb06a9f7848dcf2d1ef2a74fdfe5424eb15d8bd78fecf267998f23ff1b82af20b1cb135c945f12ebb098041737ecf1ce64c3ef6e23b6b8758d06df07b38e0b6e5cc1b11cfbf0501d29b788237f4c6a9fd78fbeb7430f5caf21bb600561c35aabec8af43512af4767f9137511bffef93050af56b4bfc6cbe95397b3bbb3ec9d66efd9f7aaaf5a332dabd98a66b4468334fb5c0c445d48946905875da8d6af66f139ca961d65ec2867d3bebe84c423d947a4d8ecd7be5f46dad14b483752cc575fbfc8c45ffdfce339992753c97d822596b0239528a5948af912a6c91c33658b3995d426d9145a9d25b78261c527fa7cf4c1ee75d655cd42326c3ef5a6cf902bbbd3b4164d8a1224a14a73ce3971e0acc89aa024c86850a299d214154c4f64c8d8d089939da1d366632e5aa89374450c5f5889f23fadcb5074cad0792242894f09cbb97a5c574baa25852403fbf23753f431bf768add020ff9d7276020f95576fe0555ac43d9f935d34ba766f26abd9db8a71c2669d59572cb93ed0fa55383528bbbbbbb3bcd6a56b39a65533a15f13c72cb4c63a58cc997f225a5d3952fe593749a5912175bcfc8ec2ebf38c34b66f6cf04feea00bcd8500ea08b0debe54827ba942edb3f966ea4bb7b411a74b11c638f066316fbf10586e5675d6139cbc62a39dde8aa11b5029b1b81e460f969ac60f9392e2c3f8d26cbcf6961f95759587ece0acbbfb962b0fff1fb698d952fbf23f225443b32ebcbef6524e94bffa691cf77ed031d16654357c631d72ae9e9c95aaa6ecf9e3cd2aae8c3bf6ea25df050fc3a06a9c36dd9e4591303655318d6df2c75f0ac04d3091bcaa62a4a45a9eacd92a20fff4ec2427d03c3fc5b0505ddb8a18106afd70d69950c1937a455d90978c83f1b32a7e8237e5d259eccb2afc618a43583e5a0fb65d7bf12c2c84005eb3f4370ffa0c12f96a5571692672453ca9c65f2e7c7ccccccb17f91735e13e6471dfe8573ce497d4e77671f43bf3f32f3836c57aeeceb17cea79ff48d29fff44c4aeab37e6c3e98b579d0d9834ab13dc3fa4dfef931d07c39db063bb3e7ccb3f7ccdd9d4ee7a4b75fa553122e9862fde51070052a3fd9a046e99c93524a69651b73596599ad2bfbfa8594d2f7d64aa394e9277f8d95ae7243db1c1c1b3fb4769b2fa5d632217b7bf9f20db3eff5c35efa56725b99e1b2496da756b3596687317fdfd1b9fb0423d90581afc6401c65fdd8ddac3e0b8a3f4ebf6d153ffa8842d661433e30069b7d17f13c79eefc000085ed9e7b18a5dfd4697baf9e3b23f1fec8600525a8103e2019aca084822bb39a7943828e6ef6e4759ed39698c47dafd5b31e2976f45a716fd4fd5196d567c4fd113bac23f24ceb07540e04095a7536a66672c505b9da576ff34222770baa755bfde6199137669ccc3167556b500fb0d6ef2a653a72b7a05b3b22dfbc217eb72d7ba2ae73ca5b2f145d8146e1c6f899c07a9e3c4c3d3f645580e3b2428aa156d13452cb45a105153e3b1df87abe0adc215fec351b27a06a14a87d6b833de4a7860fb6bdea853528e0dae28324c5dee1c6d42b77a2a0246c2a777efce2d2276a10dc3afdab3744bbd37f2efd420adc213f57360f198ba2b4dca960cbe001f0fc2e1a6c28a5dc7e0078ff1978367078ee0ee53ad3f5a5eb9ffca8e5d29f1edc3bd32bc2527e7d27a7ef1efd1e4abf5b2cfa1db73dffdd322e7695b6d25418a8df29d392cfa5c914e91a4ad5604f6c2a3ce4b7eb58b9dc2da0bbf30e03f5bf1abae6f627e08654eaf6bf7ec40fe79ddb318e8cef5a2d8871e89ae843aa79b00b517bc5e9125df2f61933aef4e61b0094b131261719a582944be372940a4e57724f13cac6383ca4cddbf3126183e7e085800febe46f4a80beefe037f080707836dec3f130cffb0e827cde14d1ffcf87fce4c6c67fff46dfff3ced5efe7d363ea39feffb8838ac8170bcc77770bcf737bc0e1ec8037a22df9aa311c777248b3f8ef77e03ef7bd0bbf1409e8defc0fbe7c0037a98d7c17bde919f07df3da39fcf3e0eef083b6c83204a6c7c47b218d978d81fa9ff1db1f1c19ea877b8c1f13fdfbfc38d8dff0fedcbdfc8c6e378a3ff9fcfba977f22e0781b6f64e38d7ebeff1d58d8ef6f3c070ffbf083e6f7e1f81b9ff73bb0b040dfc16ff0e0051f4409d077f03fde911bfff33872bc87c3fb1c38be1b379e633f39fe7ba31bfff3396e7c471e8eef891c47920601781168e0e1d0e003f044fd9a36bca58ed9f8f794304140ccc62f75ec7f7a416cbcf446e0988d774f88c7fe6f784772fc8d07f28eb218e9f80ebe23254c121003faa518ebe0757c2fff7f8e1d2dc518d0bfdc3b92c528c7f7f2cff11cbbf1475efebd44d0f138de28c7df781ddf5116a3ff1cdf5107ff7f04f4369e63389ec8813c8ebd67c36387edc0638b7018d703485787d7081cc3f147fc1413c18d17018ee7588c85910948098eefc637af743061de5cf22607de065e00bc977c8efd7cf18af7925facf27dec30cfe398e6c5022eaf25468cbf315c3786651692317e2e7dffe4c243f6dbc7e743ebf3af2f74f97cf8e0f599532e7480eaae9fe2823a6e28a76e5be0c509b91bd2a513a40c2fdc71c3b914ce23b8c1cc7e1e3050f6d26b58f63b82b2677d2178b3ef6453b67d2bd9247d4269b02753f6cd2522ac9cefcde33d8e6d36bc23b6a28a229262287448a05012cb223f86b2c98697c1bf07802506eaa5a8a464632ff150d75ab2f1ff05a87e8a4f36bc230e63878ba6180a1d12306462598808804ad27d01f1f94929cdc3bf5794ba52f70ca5734d1c7fd31fa9629517e3c8407a75065a9c4e4ae716d65e8e6a52e786d6095ba700f3d849b54113343770b074095818bb41c1e21811c3096ec860b5cb514d7668e2060ea6d8ec7254132a514d5450a1d1ac71185882a8264b3026b65e8e6a920456452d2dddb0c5883d3fa0952c00781d830e9391d3b48ca39ccb67b2b495c7ae39f8f3355c96ebef18db0220ba6ece07ce4fad2e17e8deeede2e996bdb6e29a59452cae972d5eadeeedeedeeee1e4677daddddddddddddddddddddcc1e6b5633dbdd168c891cbf7ee2cb96d3a7f469a5f4e8eeee3eddbdcacccd63dbb81827c7e1abde9e3be7b43d5c10e7838dcfb1e9996105671f9d9eb4eecc2d2959b29bdc26b4d2404f4ae9eeeeb23a33334797524aca75c58172cb28375db2b816e703c7c6f7766666890597c5c697cccc2c8362581968dce5486d4a275a9b34b35c6b955a46679eace59e676a92524a3549bfd612cb0d4a2d8fbb9c4efb0b7c95ae71ee56f6d0430f31e41472dce568529335a3938ed46a46670f313030e2c96378d0608f1e2c7b7a68cca0b2517a936696de217aa259cbcdb362b78a110c8a42b109cf2acb326e3e07a475b32b5b4a6c7ca2bea2803b1bb7e23a6d55a90780411804ae7b0ccee7ba1ff2d20910b9b856d6b2140ef89e8bc613cfa6553775537f37b5f74c4bd497c8870ab7327cba46833e3e5dd17c9f0e8478b7ef91351109e103063491e8e3b2eb2a8081180f118765868c18b56948d597b2baaafd2a41f974b716c72f906881a10446125c1e2a3d75f2741191b461a590c38c242e2851a25e71dac2c275289f0f5d6acc8d5467dec42dc6a51b5f32c9a6282638dc5056915602bf3557b533ae1aff036a727eae5ae7ac9e03f31712b9fcee69316ace9e11d97dabf5acdf9cc7cf5baf46c36495a42b5c4cda925445ca690b978e49639a4774aae1b6969854030f456b5f0cd419e5be90b3cd43d3be6d83fe5af36885ace7063b20ddb79e763f58dffac2ee877faf1fff99c07d7d13581ff7b1731edfcd0bebbdb776f6e372fd5416c45663cb6f296b6b5ab665f57385f2fac88fb7b0fd527619374565839d7d3472552e06a795317e1f2748ea10db25730d1777f4b136f2701e91fb6958fbc795a53733478dc50c245faae988d4d1842de4fa823556b0ab973cf4bacc1139ac351937cdbb526a14b8f5d3a17b2217b2600e192f49b1fe10788abb27c81bdaebc3403e633608d1076d14f2164720982fe70bf22708f65a2f7b0c9ad81882526280c5ceefad2b821297ec09f2b6d2f471811c59f6072d4eb23069c70d665d7da275355ab027c8195d51f3990f78a559502de058210a777b9f79675cfeee4090cf711fe76d2bad66744abf1bc771ab8de32c6bd3a17ba1eeb3b55ad75f30eb16602f064d408860ac50b9fe12c808ee7c39878c97a41b9f8ee04a2963dd04bf61816a4900b458d762fde517065961c0c4fafbac33ae3ee7e0baea75f66f48d06dd939a0748d6ec6bba29ef7aec8468dfb0171ece8211bbcdb13750cbff5930de82be3eb0848323821c1020625b2a071136386ad7ef3647c2b2f94778be1c5cd0b8feecb7ecfc70ddacf6708775b9f0d3595c16deb3ce95b08384f3e6fab2f3cbad525b38e63441a6563fa723e9dd3887b992165432458fce49c1f90ee8847bb62e3e253c5f6f21b48bf34322f064454adc72552c788878a14173684b1248822b0a041bac10b2a52f0c49aa617a03883a51ef1a5890b4b5bd4489971e20e9427636a70a18a34f38212197c50228a175e52e044132c984c0933450a253985a5bb301126490e5cac5461d102945c420e1a178620c10ada784953c606364ddacc3047094c1d32588a50f183152956cc14d1650c92152469b2f06108252b5188711c783042c30209dc2041c39a3261d88b28787e386229890c45c43c0a9326f06899414e5211601070c50a2a2559e84c8142aa480f767218020571b248e14b12121f78c0f24445c78d12333411461b20f0a8417243145216195878d374c4164c501af00186285190c0f3660a05484c48be809344c40b4c5978d12189282827a0a0c9d243122e28beb800c60b4b9078e1e9d5449326a29ea2c890844422ce1b26a0f2cc80e4c9982e3d749b33499871a24c0a787e807204961f5334c8f004551518d0482e0ee7688a9492921b92ec70c5882fb248c1c1ce114f9c48228bb02c3081a50c1e20bcd040440d5257527053039425e4c82db379b0001c12acd0c59c1380791a824a082849ba8c31c2890c42fccc2084933a2e8019834706b1c110456c21e70b0c4b3c915a6201e29c3b1828488cfd183dba7497d2047fcbf9b894f2db06e0c532736c61e39fed7fdbd1e08423e55a96a282caf5e8cd0a8faef605c99ecfb4af861b1b1a91d7ff33af2bb259966599b31cab7d4fd8ec897ca57d6683953fe78330f61101d6317b0cdd98414d82c3e6134008cb178479ab77b780ecb320acfab357f6b5c197f6f433ff97f63103655fa5fac2cc5282534018b22ccb3249b92b9f5969a35936e79c349392f9d9a564965688bc0cfb6ab55a497777e9e2ea5dc732b3e5724193f53959f21429a59430068a43eee04f8d70ce39259d35f32624b394cccc524a29a5945232b313ac2ab6c15d00fed81579db62a27f5a16fb63fdceb2efe88f8ef891621c97fa6d2cfb5ef30ba284484c7298fc1c6e2cc7b4cf92e52542f6ab37ca7e95511965af7d47447e81a7ec439ee25864a21aeadb58babeb1bde56ba706e57b66ac7f3f295929656520fe4660398a853b37690496a35a48ba1a0f55285151b4cc1fb0a85158f82259e79c73ce6f86a72c2988422a4e9dd804e2ce29ef9c73ce282876aeac320aedb3d5afe25ca0bbbb2b9a9991797d6a9a94ac56967d8b2f708d4c6f0f071b5dabff5c1f1176f55eb4aa736a9aa6d1baad3c49572bc642c677df3801514c4e709e3ace5add7cf7b1ddd9fc699817ff6a6dfe8b962459b65e45f5301c193b4607a37750501838e4f72ef97e411359d34829074d3a77528e41ae17eefc1a3be6fc1a656c92940bc5e8c7675895b65d9f552297d218437214644727e8e4b084ba5952479ea15382254f986e08e2887af2e4facf1326782403eaeac78316cab6678ea8041ea25f2debf983feae42ba27ee7cae85826a341577c670fd2368d3f0100b2399f0e1fa00b4a13b9fd9cb9d5db24b3f01309003a8940278c0f5e79e3ba9f3263b20ad1f0a6c2baa553a23730deb276aae824be0a1eab12bd2ec4ac4bc91835d71e1c1f6e02786e2a9356ce2dc59410855f47294933437fca02dcc45d5103e709102869730f2045b7871030c29b0810c1236fc8025c825c50a2edc2c718309a27832473acd3373a20cf15b94133237e427285e739dd9506597a39c28f1840b0c6a2995a9e4b201e76e6c86c0c00e65bab860064e17282658f26632b1444e0d486aa05343d2172820020b1f9e9023656e804a933356a058b303179985c9d28a09262d38993f9f6813a529b436dbea7294132e576cccee85ae382465fff42bd2baac8fb54feca65d8e7202844cc1c7449b945ad5b41ab34863f4e862ea8dee4aaec7a406659426868ac2f247270957be7b174cb1fc52724bf68e6eb60c54b0fc910121307f80d4b1973cc39841068a3f35a4e5cf66d44bb03def60201b622cf3d768707a4b2943ee5a8e6777fc9658304ba794ba9c9e0e4456b2c75ab2f27988cf8df23959e98c69985d0ce4534379e7bd7bd8f8e7b3cc595096654fc199516a5d73d6ca94259d328b9369fc9a3d675ea0eda8044e974435e9d0d4000000404000e314000020100a07042281301c1ed2e55dee14000d7992466e5c9ccc635190c32808216388210410000801060898a9a1210e00686d76025bc4ea29c66fddb6a2625ef32650a17d51929d62e56f757534b5b9e7b69a46ae0504606dd7041902b0ed88c9166c7edae2f917205454e88abc5ac38df461653ceac3153de72f6d48708df4bdc8f5494b6e6f0e589b8d05e1a32f599c0eb4703af805f5e7ba19063c3213553b890b1e3413f8104878e29d927f86f3302866c12b308e9971a5f0e562c1f5372fe18aaf8e2e40d0dbf772add0bb868d736f6fa555237dfae0d3b0d09a62de1bf91eb8496d23c5ea087cc370f1123ee8c4dd3f680660bf4b8ff0ee53012625341a6c7d3a262ef00e23d8b42b83ae8125ec0359d869361d26e8998536ce15c471e4ef808d8917825f6f8af2787bd0dea78a4cc528c7fa1ccb668f297dba1a2587c1d45e900e0e1a3020ba5062eee10cb5761b9ebed3d6a6d0672b8cf10a10ad077cb63340cf968c0f51a2c04565806f62d25b8e8db508a6099e4a810b9d053c74d86a66cec8875fddde71ff2ec76a5123d2803790b430886eec3d83b57e1dd3db0386ad98029a74eef4ad1fd92abbf0e77a4fb6aa8ac22bc1126c21b4315717640458ab3492e4d0679644ed8d371a52efccc24715b1d5a908ab0ba4bd7c94ae6032833ea11ccf7efa2c004ba4fe30ee2a1138e6c6868bb86721ae3d088a38a0e8c49e63b24a275a614fa83877dba3a46d7bc2abe565ac528244b10bee5e192a90248b978777b897ca8af3bc37313b80edf8696418bf91832ddad7aa5055822c6eacdf814f3f9f9f701fc49ee5b94b70642832cb9b883f994f2a0050fd767cb1d6a7618dd35b7f49a74726bc5303fdda29631a435b091c33f32810c0cdf439f1d1c71e520e206f798ef2713ae4502fd133463345999c6d44b54c9753a4b943d2c1c56e8cd6e248e01d70bb887a2bb224da2517a133f668b32dddd59f4438ffe030bc036072a3eba9ab97fe5a621d31ff371be3ff8f3cda7b6446c797ae9ff1a3c0dd77c0202b91eb36f6048e8b42033d1e9ec14d211a7d430a44e1c9956731f50156ea6d7b70d1140bd850de7c9e4e5409e4519fcfad5aa90cf606e1bd68e2c8f49a2db2e94a3cd7ed0a1e52790bca95181efaab69a04bc593b91ce64548bd3343ebb108e882c57cf4be98d06fcdce6aba45422417e8d4c8a8cd9fd09dc95ac0e273bbf35c6a92830b3daf1e96bd7448a6a9691c5377bf6d35ab30331450c9926b19468c9792ef4b554f52349020eb109f6ea8c8540457cf80334706f511ab5419a891c01b27f28bac0e8878f851d49142885f64ffbe325bffc7080865f3c8d1ae5391de18526bce8b43d3422a994182d73656537c21ed39cf4a578c435568bd8dcd4d1d0d4b8aa0c8cab19efc2c352b5157125c28f771c5eb7b36f7ac89eb8ae6fd25f3b9b8feb4ce8c9861a7ca6a4a6121de7eb91ffc4aceb45df6e58e185c55316ff4898059a018caed30ba1dff6e5e5d2bc7489a09d323a2cee41a363990e621e5ae9236bf1e9d04c747c1cf713ba3a6c59d23ccee2e929b1fcefdb7445e8f12382c344a3bc2bb8fd904dc6bf3d66c6924d4c746884e0300298393c069081b51d59ed0efa673ae13ac788742757d91846fffe312d22b0b064a9269be70c4f592c0c6c0de52d8ced45ef73c43a5beb5b2ea119b026b36a1effb37f5a12692677ca2c5541421b230b03791102770890cdc7c70a662f1e82aaadd35591c2b36d7013a371e49e4de7d03423f2dae50a3b90e9179cef4ccac2c355af7b1e8bda115ff7f73a32137f52a4f59eb8c6910e1a1193ef2084d82c9d356319c934e1fdc96888d0be44f164cb03cc48e12cf2cb060b0a25a9e54b91e06353967ab779355891c3dc4d5630fd838d8540f72d9e2e0a637784e68821893b91e26c441476473e3c5608e067d75ecc04043e73b800a7dec75044d97c73f0e933ce1b83430b4a851104e11e1a6b2069e4a1c1a262103ca332bca7b0e252e9c66c4dfdf2ae04954229363bea1f88c183b70ecc34a2b2302d3ba0bb9a111c06ec5a2dca278802217fb0855a3ec8070b1176240771efdec29d0b4880d4371392a170cdf200faa9804599f96c90c7dc90260072255633517ff0fb5095a771c7c1dc51dc867956f830a24485d39f37050231e5fcb01e0449c1a2c49b1e4c41481a94a700c177649de02bde910e27fa12161399dfc84bcac462f9142f5b20ac5b29a50cfa442a04ba756843f75e50becbe095baebe4b075a1e2dedb473f07352a268f32e322013f4ac1caec9e9796843fbf014747798d3180c661dd459b583abc0c72ffa2403f583034288187c83ee4262581cfb45a1e6eff1c18333dc76a1b123518cfc9219879c90dc6413baf3b142cd965ecc06d11508dbb941cdb956c18dc67141a85ae572d5e9e7e78289497fb28733aa74b38115f72453178729f537c645adc677481290f448b568a5e7734bc7e30ed2fdb19473908e08ef89f0e4f7324e7f1c9dbb86166edac1bf0225e2ce3801a1ff61ed21c025793a38f267ec88b38328e908ff60cdcb51542c8d424ef440efdbd7eab13d3dd9c8ee930882250051dc5fdeeb70b2ac9eb2ef950bd21cbd697f544aa562c5bdc5e1dba3a89c20da006ab3577c6090257b5ab837564a2eb0faf6063c6d3c8dc238054fabd9e346ee26029deb5232d786cbcb997eb9f237288aab003a534f990912bad043603937329654630adb6c17aa52727e42b9e776e716128f077f3333990953b6d51d344adff581943dceb93a3455d0b577294bc41bec01d431489a681f505c165eef26738811023a4f2a564dd7112fdff8d491e168d15e8f3501af80b5d1e19542ba325cbacb755d2b8af9717eca17680e11b6e5bbbc04bd11a8ab82dda34442adbcf5f4268d1d0adccebda03c2d016e87eb329efce80ba8b5c80addcc018a646550acf33944ce4a8c0a3a99038ccccaa050f73980bb598976e5920d47792da8ba2a7cd5e94a0c0a583de184e8f67ebe25b5d8c4c9c1daa61cc4dd4a2529296f5dcf99226e21b98ab904fe9253dc4fe610150284f374145d57f6d9334f104b7a92c2783915f6d230b1f5a9a1bed31536ed668cea09d414b6979e37ad5864ea0e712b725889622f2d616fe61611e03ea358e44e46af0981ccf3b25a462220d52f354dedcecd5d3f03a2169f892820944e343bb2c9455ae24665a05401e39b6f7867f7a178a544c27a1c51888c447f7710021309b1bfd87fcac3a9ce0babc7d84fde08063f1a8fec58de1666fe375287476adf88e8d03a83309f524de2674618c0ed83842607ac8ddd1bdc5326ee942db97e00be6c4d95042884d9363ab6d3128274de8e879d587779b72e62bb96127844eaa1f1eabfd65292cdf32cd2ab58d76b040ed1b39d738a4f154fdd0ff34a2dc6a1cc05a078814b075386993addf53cb2284628838aead645c15605ada73443d9b002004d930980570187ca6d7f22dcdac215541e626cb6888e985c4e0487d8e2286a221a3dba8987177bbb57b52163203ce118a1ba0a546844fa000b36aace6391cc654bbc9427b45ab9a484d59b48a9a16ceb9f3a1f81479393486371a2e6703064575dce91a832a90fdd323954a60820eca2fa9b24497b94651915cdc36438809cec30630136b7514f7169a381f181c4f93c2dd854806273e69b96b74da636c1af04d7539f3d9206487aba6d63eb9d8ae9f70d8499cba1750f5d4e032e3b8593eb412a36e382715c64f066c874f6970150148bf9182abd3ad5ae846b1ed687ccda5cd9b96800ab09087052ed6927c1c444a5fbf287ace9521cf27cd561892629188c58425f26258a2ef57351e5cb1955a56500e640e0e2c6df48a5dcd24d071482bef17d1899fd9f5630b3c036568aec8d0f6169bab3b695ee320130083f9f82d6ef2201de83da92fff4a66424813cdb2031f3da18ae33a7dae28d83102a9a3f3ee06c46f1d44f83b7824e2201297183c742391ef22a5b2de7472c4a7d4c6f47b809f68723112eea94036ea46fa763d8dcde494fc82f2e0606f80e583749591a2012c1d5a04f4b26d545a89ef91098c5cc8c3ca2dc80be25d28405725a46c9d1aca405028b1073ef6d2f94ce8a7368451c9490afecad731e617e682989c002f0d1d848f63340ee072ceb5767fb370596f4c182cf6a836d63757370a8320ee74cddd8ad0cc46927e8f9e64fa6c1c4587df416f96d9798aae74089c3ec5f0d5075c18c3763dd516627cfbc9b2e8df870996cc7a99d3e5631b9c90b64589ed12f1cae303f8d52ca09da7c26865a5a6f66fff17bd45245a315f5df46e7aea209535e5e30643bbf9388dd477456aa2a00ad4951520f4d115801f0be5e62670a9ae6fbece34b9f6cf672ee60ee0ead44152a26a3fae43e9f4588daa9010d97c7e7b0bf3c0551b55029443d6c9c00135f0d7830a266522780fa1cb1b79ae7ea0e8b14f0b3592de045824b857558e5cff771bfc9ed31ffa6b159fdee0964a6b7a534b6b0b159793b1d0c9a9c9c24bce3e66eb8cfc4bce036ecec3fa88b40706299a6698f4bcda375b51ad53854a0fa537ef266b37e9f2a718818cb075aa6d7703b5cbd5458bb027c58d54ef05f187c1799177ba2f7ad38c5de65bb4ab96a161f6e46dba55fa8d2908fdecb5a622ec76821400a23fd771735cee9dbae0df327f24846f426b84857d027f7b4ce3b6d9994f29466127cf1f4eb852f1509791d416975cc5576f68b2eb0813cc6cf025f77fca11b365f6688aee19c7e856055fa5202cd01dd05447bd22088f42d0d06e808588bd35394526e023e74e1eaea32b282610c7bf4fa4fde324a374b4cde461b7709ce3560abd8592aee09afd0d3fb4efb25bd9ed5a9e2a1fbf9fdba4bef0af6d5f74db0c0dad119684f9471bb57e5bc0df284472af5bffdb98623d08c45fb29d7b905ec28e353409cd8fbfc3c5e4fede3a8f2af098c073ffe6156539dd7c65fcf1e12c3410a7b69a5a24bb20af60b6c707b49b1d4bbb89c50eaa91c077ebf3c5b5db1ddfeb78d4e993807f1a34f62e6d9a8f2e9a5307703304f2787700504a8f61b467cafe2e9c80e10e9e351242c40da681931aa1736100cb7196ee7802ed58efcdb7dd41c76a54f01c2d94b38880324dd9686a0e087aa8efc647adfb53b21cbe24ea0a625ee140d4762277ba9f4affdadb938622f6a288f3206ed7b1c76ca34e9a1bd000a2dd53a424b96558974dd1eb75991e051dcccbfa83e81a22bc40a3bbd472c7f623d0d60b2bac20c48e9aa52720def41c406505c0cd064784d4ad74339ecb926ae4736ac33d5bb5c0b0bf833f0eb4feed4409b5793b879ac4f9da3938101215fc84739c4a78e81adb26dcd612959e08f19fa68928b55d36a715156f08e084733ce8c686fef5281e8d9f97fd146aa81a5a45aee9a72c426c479f4820db4dfb9a3122447dd75a4c7f69679991247902e80793237fb42853bad956f94c7ae5f2edb4638fd7b510c6b962026e3567eb26543b35f9789aaa5002ae5f311cb1a5798d42ce1f1e9d7a4d77da7705bcced0fb37af384891a470d2a63b2886cafd7d0930148e77e1200c207264232e931cc4935afe50e61bc5f8d38c09d1d85ef1994803de4517fd0601e40dfbf91030bc49e8fb818036d03ea29bd0135d20b90ca8d39077286dc7f90851a79b598cd5d5dc711f95496864ba1418173c3f0eb74a8cc7e130896e7b99b03f705f4ff8b4fdeb26c31f59806cdcc4f09d3084044554d1b052c0691e9ba0c6c1b4bfef820ce8d3f4e81411cf9d89e47575a8247302fde9bd864c628585df5918f07b270d202a938c58d1c4f70591d6887ed0c4b07916c66c71019e86dfd08831d08593892a41db5499d17345dde2f39aacf439213882ae02dbef297dc7fbb94ccda52a80ad4d5ddccf2dc73997545b6c39b1f29a105e5e10cccc06d5ff79954632ac034cc395db74b5271e0f1184209474cb4155c88a8d409082df84124cb5ce45af10656ef70b8cd49a67c5ce923ad14983ac3ab530a710638c66434738e03299f25be424011d4e2dfa94a71b63270f9bc0db460325e402953f1517ab84eb0ab628451eb0a474d4bd8d755298d670e14699611b4ae1454a0485728536c6ee46aae4bdc678e7c2f59e23fed3a6c86f87874045d571135e438fc35bbe4a12504ac96ed4309d64ddc2d021b80eca9b2c676bf9de37164d61ed2ff9ba205fb23d285d59ad5d0632b8e0c45942c17430e20559ffa7d8d69a9a5a46ab4506d0ac0147fb1e53431788e8dbbb44e9df60394c6e47c624a28ed0c1da21f97549357b16d4c998457170f0e82628b4f1733696143322f196f7d3f978825f3a0b14478e1c94de9cd0d961e82478223a705421a3a5d47ed49a0e8a57025e6c765bbd8d2cb193acd09492e6042cde10aaa1b6024e226123908891b69ac0f24205934c1bcac72d011103744640462dac81c4ab7404de9170d629d1edd2a95801a962c4d7934f10c127abcd8c21c91217ecf666a99f4f8b9bc421d4a8832618a7a1b503557f67add8e4edb6f8155d174ae0848c8800d4e411d847ab6b084f4b49daffeb5f9c7af769a887245ea40b55bb2e3be2c643771686fc5f6ca27f4a5af6b7a091f7890d36c0ac8761e8bc047864dd09cc59abf21c638f29ee78088d75de7979b759d3800363d57fd7a01a939974a912d71d2834f8c9ca82b942d0fca5d849fa3e9cf23c12611a355fa22ff3397a44a9e86b1ca6d5281d6a67f4a7bd706a7939c17cfa753910e99b730ea713ae976c5701ecabcf3323d01c0a6fc1e85e60fc82fcd4d9b4779eb4464e9f33914289d3dc5521d1f331025d7f3d1ab5f88c2ee2ec72b274583d01fa3dadbf73d4fcbd3beb3e38fa7b962748761c07d104a4a62ac9fb2442fb691d83ada38153f65164de908146378ac870ed904ab8aa0f6f6608293dea14f68a10288766f70accaaa3d66ad4ec19ff266b4427a240bca7d17b27e74f9bbd272930f9ac40aa9ddf0ad088fe16fc3f41196c10d4563c53991a27453b4aea9d1eeb37d52fe06ac2328e6382daf5c60610638b0032159d5caec79adf47e861cb439d60c8231cf0ea05edc48382dc62a836c49c24b34a285cc5783613501101f630d5b545d679918955cec7aa40cafccf208044306029cbf03bbeb2c9b7dfe2aa3102a9dde11859c45a5daea4f2c6cfc506b9e63319cb90c4c90c58de303071196b7d363f9bc82e6c4c5882bc661b472fce6c836c3cb6952192a3fe74aa3f0e516aa24655c579e052b9b45dc74da903df117e411ce32b2c94cfcfb11249990ba9e4312fd84f8e114ba6c184ac91ea88b692d09c01401802ced0c6b2718d83c95ca4f38fd4f00c08f8b8e30f019c2799df2b953995fd32a314d6ae03395ddbc732c60c7d66f428297e5d802dec21a26d4e9881016d34ab9b28ed0012e3302c10a9902f2d70d0e6b4ff4ff8beaace306931d093f5cad0cd0a1657a5331484e71fda0bf5d5287b0424f25187a7b0514ecdf6699ec5f85147475e4993eadf79ffb8016c9e1ef16362041c063dd373590d14caf8fb191410e62c23bc4b6cb04f3105d4c70858b2a41cfa0128b3c5899822cb79d047b1687834dd053a242d1687d2854f8e9273f8ced39e82d10d9c0d10399ff4d7aaa75d8f08a38c108a80731d31b792108ac21e09396d5dcf1f9606bff035113687fcbc695ba3d19110d3e9fe9307600126e0e0000e166ce053633df90058e0337d882c84ce184cfc8b9b1c6385556258e1de4031c653609f20949136ec5d639b1da4d100bb5c48d4f42b17be11bd3559aa1146c16cd2e160d42354eb84c150c1839708c646b064e06e04d085e3261e23c9fd105ac363000bc60441416a299970701609e3b541b6e5045b107b3619c4f496a725b6112e0cd5c4d6ea1fd55f9585981df823acf7939d974c98097d9608abefff5d29fe1c88519b20c652780b632683313624f1f36ef5575cdf6dab54cf6afa1b1301d5c0f8c0bc2196142b04c115748494c20d2c702d84c80d51c4560d0aa5f7b1bb10deba8833a06540f547582209eb3643c356a914661d811667cf2aef048b29ddb45a09a8eb2e6e12968b9bcf40d8bae67d89df437d41a0d063d912dafd639fb7c393fbb0bbcaf1720c09821c9316f36a36d0f0a663bb546dd98e51dd4ed65cbf240064737662d252742b994fcef1bac11060e9b1c290b8570915c623ffd5506cbf02b1e8671323ee0d7b0764d906442a240c7e6bbc147dde0c391e2147dc9cc9a916006d545daa04109458fd3e39803a85ef6ecb8b98982f7d3cd52cb34b0aafea94c562ce26e04b53006a7ad2f0c1937eb7d065eef0fcad34ff6ae80dce7c0e72cf471d69565fe5eebe158a1e95d8d6160abc4782328e5e9ca8438aaf43843dd70690b44f02e5bc894cecdcf8c4dcaffd72dc6287285aba75e03adccd9ccfea2782c302f64be619a09af731410f72614b85407f9bc9f1ac5728f8a38c8fa30bc1f33bc05476c1d4355e235f9b198760f0174120c32e400ea5a9bae1044194e79f42bc9dc6282d2e8e503f4afff1d222d027d2e80386c9521a4737d9f19d5857926cd8f45f99b84e3d5ac10883664f674bfd5d3bed864f302e2e503f3b038ae0974c2707c42fef15a8ac56aaef21b2cbcfa5658b2aef3ba27c27df53e9742cefbc741f42f3c644318201bae7540184792af7e95e87f6b27a94631543330ffb8cc2a062a366a7ade77c166490ae2e0206073933d7ec2a9dbf4e15be9e5a5a9ccab5d2c6cccfdc984f89970ed49577a9669247fde50920b31aaea972d25a7849b893cf16a0ef41b7c8977a2aa18e7588bb5242327db9008d12f5a2a593e14616c0a634fe6731741c04ace10c3754acdc06faa11d3a61381fcfdca365e41eaa37f516550589e91fb1fa34a7f591fc42270d1f85d3e69f297d5a76795436ecf6e9a8968ec686b575e703e3ea8fe10164f7bf587dc96c6f50a4a71efc3c9e070b0e98ef6a8c512edc3071ebe8d16fd89781ae45e010cf24eee2958dc61fa5c30699af775235f552f849676978bfbee5c752242596185527380f1cd40e18c5bef5e599aa335ee04d6c77ddaec1fdc1217c5c127374b3b69c139fa19b83c7998edb182dda062e757e5be1a2b6cb6d36b44968cf1dca232a3658fd299216342724067762365cff57e14bc4d6470e5cc75ec7a09fbaa49be4dd76f3f9626dd92b3755a2d11eae9089a8733630ab481a9e3d461943bbf1bcb5dc750292c1b5ec0f6113bfc9baa8d258a6c03bd29536f62691fa4a200ca6e24767e9dd9b1ad5aca15a251c57f7d615d0ebc2a8943d256095a28835d16ad3d6e60f26411beadd4ce208558abdd529f4c7ef29ac36f8e0726e5050ab329d6b041ee84db56cd83b9b95191aeaeb2a3592edf681721d5c45f001295e8a89e9446e78ddbe23f5f15bfbfdf1df1bf63dbeeb431c906279563457126b2a7481c6b7ba0fb4f8d8e30ebeb88e82f2b350d47f1bd243cb4bbe416b786ef6d193922cd16c5548f5b3b2d5acb3245019201f383d3047974cbc8f3160fa7e107f0cd0177ea67a79ffffd0e25e42138a03e0924702976104cca260c79f78c17a53800b2ca0c6603f2841649a411ceb6adabd228ea97622a2c3ad195cf9e8c029d218e3f3731bd741c018807db42b59a44885b40772b0488cfb0f3712803bec5f1afee58f6237aa9904c4abfdd09d24c22889d924754512b6e435b4ec22753ac1fbcbc8692001bbf386268436ba12afc57710584f4420e4dbc04a2716c5adf49a14a1dbe201107b737c88a07b91b0ceabdcdd53676d5104c3203a71eb93ba402e26a8764bb4bf069d957c795c62751614a91dd2003fc8f5b0af86174049e2867867c0ac114f1e40ade705abcdfc8e4e1458719d1e199c2f184527313cb0da23ec01afa53a2006f37ccaa3d50acfd8e8416aa705d49e058b2b82da0d7bcb3e0420c824d8136306e6b0301e501f00987c21c1b9173ab946e931638c9d56a794cc0b3351da7a604ef8d4b9bc3e8c2a1b004cbd60f58f72b0163e43b16a9b00e6eef7b3c3fd2b886ace554ee41ed11e31fb3816d1ab1a07165855057baaf5aa13f38d629f88b5d0901eca002e4d80357814a437357e0468924dae70ac5293bc734790a18c7910041f99a8efd39f673198574bed10744bfbc0ea5471f8c1efffb4556db3fb618d91d041eeb057f1d436456205315eb75469365dcfe1fdc22009c15d8515306c4fa61db008c9ffd70aebe56504c6c38c8414f00ff9a0084ae8f928824609b259beda0bad48d33b408adc1a10a51175919fbcbbe057ce4041c7be2c0bcaf9d772ae05b48523a962cb06070210d94a0b2822f3fb030e21a08226702a2870a9cc637f8c2b26660cb699dec04f55ae4b8814a27825f01dd3e8134d082f07371799664842184afea0442f00927eb038ad4018e760c4f0b746e19d21ef70a8400d8fc89230e151207de4e0c7d5b32fee2f775c3063238dc205b52c208a860d296bbcc2d67d03eab7e588b25f271c87e37a6bea86e05026acee08d694655c62f6c043209a868c632602271383ea3e6952b08c0ce28ec010f2d62ea4de050004451e460cd4c4121cc09caf28ecffb41d74ff0bd3ee48ea25d71748504112df0bc35a1d6e9d9382c069e63c60d88289e837638596f57fa22da3e7ce906fb85202efef24356e0ad964f6b0be5b2452678af4e6fea45d28b4f8e138c77d1f4321ede96b3bd0f21223a1520d7b74b1ac60c4d1cdde05d054e47f8e01d2e44d0e8ce86ae1bcc030af4d554eafca9cb325fb2af676aa3d125db7b4c5571daf742bc7f80be3b8ca1474324332a4db35cbf6ccc7bfef3a1a7c51a38bd02f494230ff4baf7a6693f9aedb5bd1ea6f80d9ce8a17b35f51b079bff1e76291297ac81bd95aa39a708940fd4ec86f2afbb97b4dcb4baecb008dfeabf9ca52f1b4dda758fe388ac68f6589f2b6dad34554db2b74860ec5526bed67b95dbdf3e8ff87a953c324a24efa7870cf3d73025020270207bf7c4be9bc8a09e3130473def0563ea4353304c5d4c3383458a0100fddca114f74e378fa06145f5faec7c772e9fb9c1e78cef7cfff22e844b02ae7457b07691fa49cd106e03b1b42956ffb91929e3d0f00f2e2dd6ed5db63974e18ae3cd459d9268d4e20bc9edfa95124920783bb1d06c5d35c8191e6d455ff0a3a90d309c7c25c9af449d5ba745fa10609621a9817393973bf4b4ea75958faa39eb0715c35a2a3de6d6976a82a841ef45afdc1f24b06a6003e0b3a7da9e5c1b87f054aa09efbad578037dbdfdd7d2133abe925be59f9e7fd683be7362ca46e37923f00bc8768110ad85c387c76ecebd8875e97ec47421e6093631d09ba734fecceb8af3614fe20eb778d06cf6bf4ec1bc1f02dd391ce80fa96cd7877c0f501bdccfe8487ebc87788acc1445bd3204323f871f3fe0a28f130fdce254200b3f1a36d71a1b6147143f4a430517688f82f7f4e40fbe7bd29f864964b3017ffed1bf2f69766afc7aa372dea1c021aacb624dc06b12227e0cf1600802573ffa4e113e27fc16569bce7616da9da64681e4acf896d45eab051c55cb3de88c2cb8e9d6282e5dee3fd25c88caf75eaaed1ffe75a7682d4b3558aad1a1520d7dea7ec1c1b879e099c0e5a185628abdc28035ff34867a855011efba09fe7ec1e080aa2ffd5d9f194a16552e7c57a9605076d50b79975130283755b4f25df301368536e93dd48669b2aa2e80a990952cf42a7ac1366c946a5bdd02671a495eda21a8dd1bd54c67042028a4ffdd5ec2e3a2d943d0cc8cf2cda0d608090898c939a828457a5ca01774cb12a1963c265f8777caa62b39042c8176de6d63ca2903c0b6982f45375a44f85fd942077aaa7564584259b29ace640b53609d67e0306e757699d01b30ddc2cbb6323427db79cd07c9dd2fa0ec56820e00d1012a2953ebaa1e751ddd44e378c2ce97cc6281162e64a8be58d6c59ff8bb73e862a6be3165103c5c1a845825b93f1921ab432ab021d3e095307f8b836ae77ad62e8d090134a48f2b3edb08f891c2c8ac3ce18492006d6e84cb725f1164b5827fd6caace9b0150f1462d25939f0e075439df17b71f53bb5036e0ff77547a3ac59f3da65ebfb673b8662e88a2722511fbcca8e191896f5804562b1b7e383952858b71ade6407b42ff952f9d7cc09411e84b1f2ab9a3c634cfd258366146ef8a883208307b6091aa6de65c7f5607b780093bb429429694e34ee6fb8bf64b1ab45274a1bd1b5a11615b20a4b3c175cdec3a376f4c04bdd7213b9ebfd14192fa8cdcbd7a3ea0d67c706d650558684fbc0284ad0d5566a75b2b315ade236f36c82709b561e59ffa63f6808137d157fa0ae4754ad479947fd47510a4972bbc5523b73c47b8f505d24eb84e77856cade1702aef08b9d115b99f1246d271f1a2abcfc6c0af38b51a7779c500d74353352c98e825e3c710b8c5ec26940e8ed90de734da7abe28fd5b132874c107198c9decee9626a57083a30d822218f9b781c3395761b903be931b19132748c4372b1ccdc487d56629fd775baa481a7aed10005d5b1f63a023854295e80dbeee46a99df02216fb6a22142622d363292953843f5c5eaca138205bd5a4654c37bac8865ba2584b4b79ecbcf456b6132120425d71650c2324532b982a988e5361c95e114dbf0a8897a312296fbdbd10762e466e0c8f45623356ee903c45420ba12d390ab8780f76c14b08c64839bdf1f9caea41756170bc82022834ffff51cb1fda1e00942a8dce1ecabfb7bfb4785795e5c05f4f1d1a9a144e404683d85653b758200b984a151aa9339324d48b5c3dd1588bab30e335e9cc0bddc6f27f226ec17aa8e9b92cf9ce5999b46ed016f40124f8ae8aef2b85f93ba8cccefb7b1e7742967abc134e5774fff2477b997b570d74571037b490ccdd4d1110ff28401731b2ffb680d28f80a0606ad8aee68c7d3c60ae79a015d5a1aae5df92c74f45211a1fc8070411e78df07ed0e9c27f4e98f9833df9d906d7f2f150391639cef8a3994f0e968096395d9f00e1a2f4f358bbe7ed22d657ea8fd9da9c5edf21440480b606840ded9be0b0d5a4db98dd9d650381c2f1147fffda3d51e13c124e4dae72d5b59c9f4456148e026157f4516f95dc1261488878fabfe755a65c644f11b78018bbbc429fe1a79d861051fe5aa38fd7fec7e653e1728cf6ce847b8643c770853f19741fca90de277315a9a23ea1843d6c44aba2b46d67de4fa5ec1b037fe37d59586305b1835ec0414f4f1c867778154831f8311806b2b0d29923e96a77d19284fbf5518976084b19f54ffde40508fc8a4ea41f27d6817defe93125e9d3035ad687f8f460b5b9dd0616885a4eb540f02792f93b2cf547646315633c8427b9b80401b807da220a671fe0aa22da59bac88d90a7e8a513c9f809e80c84338faeb66b52ea111be9daea01168c49af7fd67691055f1ab15c384d0104715430f32ba7ce080f5bfa05509a95d0328738b1dfd9dc6771515c19a3f4db04f9a24911081e722198753a510133a3c05ce5e30ba983ea7585905940c8202938bc0c6835127306b394f6fcf1e5836b1d9d6eba112badd6f08db63c7a706932098e312f4f200695bad3751011e8b5dfe0053fae8ff2d5c354f62ddae0bfe2158813dfbd4029c2f5fceb959824e2c7ab18f513e7f293a023c162119761e2cc1520075b2f7be552faaa70c1bd6318f3bd6cb6fa64224671ed6acdcb2e996661373b44f065350d0a2e56fea12bc6cfe326ccc1b64513f117fa47e0ca8c583eb25bac76dd47801cafa753f195dd99408ea4dc582564b228fc505ad6cadb22bd22fb5ad412163039aaafebbf07b00c8798034aa81159635892c737653b082b9d2a723dcd4b5260f0d43c41df65bef89c507b2fc3764eaae98e0bf6000cb36f503c8a584d84b7d8c42eff3d05d8331a3126846b390f198d249139448f86f07e50dba15ca7ea3a86622f4408d191cf0dc4f854fd1b52c50c5128c41ee071e37f714eddd1450a64ff8fc6988dbc3b848c4a8684d02b12d87e0d0d3185344e053c89f974a4694c47d54d76cc352d74f86502ff871974f3bbfc27cfc3aa303161ee9a84880953b275a7a759cb7ec9155ede82be28c3f182dd9cdbdf315969d52b6eeefa3a27336c255951e4ebf8556a04b4322eb431a3afc0ccd49508551c24d43a7e1f3895aa0d8e9d72c15da0f8103dfa1f9fb1bedf88d1bc1d7d3f34bcda9f41e26d44b864cc0c2895525f362cd7257d11aae8680d33f56cc98adc548b47bba82430f7d01bd0a50ad9bc9d586eee2ef3620f95f3f37d7e8cac4ccdc17f7e91486dd5018ad3f2257dc475594eb5e8dce5c5408195d052b4d92fae966fafd851e458143ba19bc4e5d9cd3ccd3fc89894b62d96e0de96b8000b760de82d7733c8bfa25f4f5ae0373cf9f8eb2fea7903be204931727066744d0f933e2b24a5e48c262367379d49000b65c968560233a851a4208bf2f89a2d7e11a37478f588c26a1072a446c2d7f46e6700aed9cb9a358505b60d88963dced832ef99dd989eb43cb4a5a69b00b49e02e00a618c63349140041a62acdea08c18c214f9dce10243d69850e5eacefeeca4e4f87396b6aac652b748d72064faa23c275d668c018a685b44e12637cc7f90c2ad8f3236c70ade26fc4310d4435d7514bc708e1e54757325d7d8aca953c0b6a7b2eac1e91152480bd3d3943dd95f636483302483fa22c4e691279907af25fcc833e1688731da7a85a8bcf2c7090a236b755cbb7085f446214e6b36c02d1b9a228769ec69b85f69f8a525e90a7f9b94aeb73d7834b2cf0f84fcc801078361d228138ac043d59e54c240c269d16584950f60a08ff46ccbb1b128cce20305d8a334d2a4a65b3e6d6e646a90c0d4c4abd7f5516f0c0f5fbd7caf1435063799d972904554fff0f5c7aa9ea8127fae8be4b3c112bd8a34ffb2547310d00466f706e10f1cc4b050260d320d1bda3f8124149a0a96243261a6cf78196aab0f7b08b536c16eb65ae7c7f97ada7c938aedd41a0d52b06224b348f505606e53179a1dd9bc540fecff4032f7d7102ec0fe345bcd7229611654587ed28f09bbb5dc032aa1526347af6eae3d9aeac9835213132ea3e3e71b86100aab66b817c7cf7b921560b354514aab3ebed138d8180fc004557a97be9015ac00ed49c3437f9019daef2e95cdc99133519b845ddaec5a02cc0d6e994ad5b33d50d393f0fe505808140852e8a6af9ebfd1db2d8f074ad29180305f87083f9e3fb77ededa663ae9531ce44074bea3e3bdf23d635a0102a5df6415caf4e1d780e3f2f2c0c1ff3494a1102458aa9c58979b554c3a71471f4d0d80c408468633d02c7f645716aa5d1959f1aaa51fbd377584f0daa34f2c6415bae4c2ed42a748b78f5ebc08a905a8a6ea5d1d7d791f236dd456cf52c8d30bd136435afa94df4689094ba1663622d6f737c1ac5202b315143175a7b26ba1dcdbb276246d10659255848206d10a3d87664fb557821a1515b368efe5b2263500d8895464677d7864e4d51b0d3a26821d214f94fb027aad69b1b35159600de727f7846f7a2ff3993d9386b56fa38a8cda76d909d45686216d8535e544bb8d3e6e9597d12ea42db5481b8b925e322359f1717dd83a2d6cebe47a94ad52e3ad9d322c69487e8b444020eb8f3a96e1daddef17b5ae478711141b3120c994371518ac72f9dd302e1395c5ee75b641eb05ec81b84e74d0829c202616050fe320d6f31d53b0fc9969912cc9c205a17fbb0c502e1245f91bfa91d0b0a1b0b042a2049240c6f0356f88fdf56efd83f4b32155d66943f92a8f5d1383a159f3ee1482232f1da96e83c77deec7e7947fa771f9596029c118dde3f9f76b3fe12ff52c5adca917cd59a9b95577e3e132750644803baa609ed0cc25122a20c14531d5cae26ff8c9c0b2a0772401563d5218725924e8b27a081814dc80bbc88a414d2546cd768fc937e857224e3b7d00fb22d82c788bd8998b8f79877c225ee130bcd6fd231fe95bdb48c883332a4284ae83483b2f83299de1780da1abcf41c8884d21667371b046eb4e98109b23eaf2715fbc2d206ff444fa664609340c28131627d622484d4f2824d9eb0534f63706043f1f03f66969121c524c269ee5c3f1970a86f61cec1e69d561c6add3182a252d333924c06ed692e22988e9adc9253e19f8ccc2ac2416c1f0fc947435083d20bb44f3572388405a1335403245a03249bf73b5419100278f563b92674a81462ee0251e942f60671bcf733733064733606070cd9b69ce6f35b785c480c341841251c33e2379a75c6e4fda1d91964c53fb1a1b68bddccee1f266e2c5394385d9c4baf082a281d304863410c3ef16cdcd45f20a6479df3f1bac6024e42a10316e1e58f14fdf56c274fb99fc20f0c1129d9a8670534cb271cadfc0fef4a7ee4eb1fe46e50ff4730050d689249336a46572156d09922a23b5fc30d2f1469d9dd9d33080455665b330d9a9f71bc7a7eb21bb191aa3f3eefd0537754dcde2d0ec4a33fdc75cbefedac2190d502db251bdcdc9b73dcbf9e76a54fe4cbfb707691c92bb2681fd5ef0c22899335eff3d41b0c0463f25dbd0faadfc31258de222b7d70570bc73bf828aee036cb2ac783ad29076daeabfe694ae8812aaa522db506ce3da6debb5b78ddcf357cb72ac488b47b517edb7acc80f8cce616d4c4c25b125e6a21ee741d2ade25fe7837e475a32669b30400c560c3f0cc412a78052f5f82d1d1eb1bdf5eda234ab55f26c7ec586f93c95154039e4eb248f0d8e3b72e1cf5ba208d01a4305c929d8b6b420a9a64542a39442cbb2990856e9e8b61669bf196eb528f9cb8ceb1a7d17021dd34eb15b9541e290609c44e436b99faa512b6cf606f6225e6ffab94db941818f03106f200d426ded1512a1d6b8d814337e8c636ed341e813e5507de6fb8e7508c9e2f8290de405734b3af784298853d9b07cbf098f32f153c70db2a14464c4854aadc45641c744f322d0a4d02bf608d7469fc88e0585acd1b3691f2887cc02840f8217016e9cc87bc205c113a689cbc641b8ce797c32153de0ec7ad75a0e89f251fbd415fcebf7e88a39166ed834e141c34dc861e06c3447484f8f51fbe67cf46a80a6e73a4301504aa7872ab32139d9536a9c1f8e0c584121b2e200f30ea6739813f008904035976f26bbad8af4e090258cdc7aefdf712b207ad71de30c07d127bdc8dd9cf713ef009834628a1c57d510209122f0e2f41199e01a6543e58d6416e8c49f768ffe8223ec834de81bfab71db97c01922025bcb7e1cd3fea60e752d25b4879cbbbe5a6dfa00e32adbc043da5c62a6c1e810f7072d7b42877a81e354b6b4bf13e65ecc804e507b41eacfce632feab55774216b9bdd40445eaf151c457a924e5449daa02640dcff2699c13bf9a96901c0c84c82e1224976475619999179a253f823a06ca6b8fe11f9445d4c995a7e82bede49db3422cbe2bf9c7e81154f07767b193a41908f079432d409f8ccfc964a49d9fda188de07244aa9627807332a6b99bce9e95f700553fbabb053a09f11ab3c1d073c6ae4f4b2da197e6a1754d0381bd9a5d4d4fe45b3d555d4012089aeb442fcf05b978c65f8aeddc6d82a879cc2cd24a7d5aa45742f635be976874fcfb62aa4ede08b7a27b75a3b1768bca69e8fcb1683fb083e069a48e369840199025b737408d365c45645e40f6bf8a3f39d09c758743ba5dc4916431758d7b468d451686402d9a81a6a602c18911732069ab8b2f22d0988bf0ba9a33f728cf3112abff76aea06e2ec4b744bed12d1e50b099583c89aa5941c38c4203a54ff10903cec4f18aa84884402f82cdb473283f91bc5e60f7bd319db11ffc74c4bd3e31b6149be4d12309420e967814f4fb3f5daab019a58ecf89244bbdc20b131c2468e7dfa2a33f7436eff65a8ca751bd3dad736ff04ad0e9fd8ca6dcce3d46c9ac185d7cf8cfb397c4888f043f2cc48260a190bf1e5a6d75ecf5b3f5a31e5b67a34fde1297a8ea7a3a9644d79b98ba856e2d9a9f3e9f62a1f144b022f995242a243cea676e0c1e20553c7af32bc847d0a4376811d688a8effd19f15deef6473dad972884460fab3a1454f4bc82305e80261fb92752baf4971dd19b49de6082e32602183902ad2975d63885a2a40010114a8c999fcc934724e7ec68117872c18c60e60d9e3a15911537a47b5520ea6a1915d0f2b73583ec805bbff55a38422c85d2c1f70951cb793b76412a8390f1478c98e24ee1861bbe9a48907143972a0a85f48e6ce0bb729f4647429869ff33003f649638e700988613419a95e5f48f4e553e8b811e79001c0bbc73c2d291beef22c48a3ba271c604d55a5874fc1b98ebc42f029c04f3d6b6a7223bd2a50bcbb470c6c7541f50c69de850413ba77a7fcd6cf539371277d38b4ef6374aac44e46658e3aca566394ff7e2733e6b6656cb025b71bd694d236a0516e7b3dc12dcde00696df3eca092fbbe3123b9747c93eb179d925bf536ae37c009f6e021281fb2303a168d2115686ee366004201ed70e2405af6d00ee2b0c1065118684297e24119130ae6df79671a71e5e101264114c407e157f3a7e30bfe2a89b4ae619ca3e10de20c843316111df289d508400d1d550fcaf2501da33bb43adce25c7154c4a843c582c29b0dbb9a863c35a94651acb822edd59b1f8bd09ed5778880374cf56999150e204ad46f7f4ee903d6f548dbe00177afbaacb8255664bfcf4c9d12148bbe14fd65070bfde24b1f1434facc9fae1593faccf316e5d3f75693fe51620a249db1f6e191ef0eef924980e94fecbc6a32f9f66f3cf510d66d8b0e531876799eda5d40eca97e9858e2637d258805de1e43b69f0ada8d46a29eccf0da07e517624705d085cc35da3a92d6d489d04af97e2d748793a19fead36dc40124763b8469520b36e9c6d8fd9186d24e6acc52bb791c25e8be971989ba84408437b0a2f0577fd27ecfdb38af4e67f9144e27e6f9aff7f9e282f0b5188014976934a655a1e2307b2edc3d81e74a8fcfe617279e6bb0a3e8526856270efbf2a781b0dd96e3a1d471bc4519c60109e5d216250dbf9905e7537eaf9acf8f71afa86535e08e511179f3ba0024e2305921d855d9295dd3df55ea384f678a55da3779a00d31d262434f2f4b682a33d0c319472dbf56e9abebfedcffb06233e689f9d0f014f0e885bb767a2b8536fb5d85a92b924e01c2cfca852212bd0bddb1ae3528b3c4175a02c590e484bbc8467e500cf6051a0bcbb315f0902cca3f4942a33e2a026a522234fbe3ca064d539d617da5ff1f1a59f15ea18a386159ea1d4668c93d58f45a4185b57ac191adea4ccd4f4378516276c84ecc59395461e0b740e6fb4325384a04a50f48fcd15dd1f69e0244851dba66db4120e14a3b3089281b13433f57abe60137e0c9fc6c52043f9bd76b90086aeb4dbe7b850b750b6bb53ba8e1e71a052b7f08da5b816b91d9122a9abbf2da561d5f3e6209f26915f60c1190af367478b2fe33735775bace397878f302c3742ace2dcee927901cb09d1123cb7372f419ba7970809ea25c6979607d37d402e64485843c47cbf5c952185f33be943d0701a9d7a0a1b3a665bf9570d3d4c7d372f5bc9443714dec33ecc847a1da94dd54bfb9ca4be2c1f195122d0cc042de68739e9fcfe2d045770800829006da2e1041b09f8fb11f577f10297a6bf55584f48dc777ce34a415d1d01f0e264499a0880634f5b1aee639208017038c4765b6a0da523c217d4a6b3bc59fb99818dc5d73f576583fe6fd6300af76371115c07fe007808732198eab5e66190cfcebb22340e64d1bcb555bb2ad0305a2e6f97535d1b7bc2149895044d2332714a09289a698849876ef651b181bd3a42b7b20c7a4c72fb65a8b933458822c85b141234d1f591489a4a7ab9e3c0b4312257ac29eec498ced58894ed8b4e11fabfd5339dfd9e952de5e7de42234654438f3a2830ae8a3029db266f50420b654a228e1c4bbf38fe863bc6d98402760eda3f949441387086947b15b05aecd7a24c33bf2d24e57e5db58bf89c1820ba54cd73919c28fa28246ce977178f818903f31c585e8490f09c483fcb962dd9c4c7203472f7664b1e6892e3b12b7ee06cc9bb18e0b53ad2ece96bbb9eab356cb3dfcf8ac35aed3b0dad5ade14a34c80c53218ccffb769bae236dd8d1199c7ec7511a493615f3e6b4d68f9aea112b5f97e437714dc555f03b084070c744a9b8316e3f2c1d5615de61a7f219559cc7fe17d58206ed80ea9b57340be35960c2c50b25d6ac1c35f579fb2b506c91a8acf130b7db23626b4c2a3ef959e7d691d5c9eba7de5ba65bf4f0cac19165365ea57e921e6ac30fb1fa381667f2d526a38667cdc94155b1f994fdc6662b2a4f3e11b1a4a4e256e819e3afa069824df70a003e161ea426e868b7f49fff155ef02531b13d39907bc000dbc72806374a8ee4e6838baeb39f4aeab34645e838cbef0189c5f97c6f68305b59f2dc7f4e126141ee36b9eee6da581df7a7f840b92b9c171c6e11747f8f2ec31799debd884c75856865c5ef7f75cfd9eb31ec7af0df66a1857eaea47f80973c025c4e456a0845bb004e87370bd76dc530b8de7dd85c862bccd7e730aa94ee87f5c825e93faf2b1d9a4e1dda35079a6b6a763748735cfa19057d94b664c46438d3c5899520d9c6d3e17734840724376ec7809e4270fe686803ea53b143789f9f21fe6aada26aaa3e136c23167a7027a04a9daafc6e7b62a1b07d2bc08d12781e5f83f6cda491f5aa31c23dba13f6273a97b6f780955dcc43d8dc6d23279c337d3562bd250d61b168969b1ecd1f2f90add949a96345c541bcdf1ed967a3b381dcbdfbc336d8dd35f14aed206300f5eb50e65be2a77c1c38b791e6e0839452ba0805231b5df19166df0b99e2e5f8ed5857429e433864ddb549f1725e9110ab1e0737a62ab476d153bf3174d3d2505f1567d2b5b0ea20dfa332893dec5881655ebe236b199de5ab4f412f92d192b0e352a4c211b0f23c2a17a8300c947fb2404fd65db5fd68173b43c99077bfe45bed7c7736b3943017a314369fe560322ec9210c1ee710fb3f8bc597b6819a64858b49f503174eb37065ee96a684ab58dcb7ce4562c80d2c20c78ec256431fa245c30981911ffeb78d659312172e73d17f4397c0a83167f1ee526cd5d12d2d48ffde9d94803956597a386eac8d66c4389797f7f93c53f5d9f4c7c71dc2693aba15d5b95d0ad4816e4304f1c6439b59cadca720fa365ca897bd73a4e133653d93332a110ca1d68b58dc98d26561b362c11dbcec605de270e6889f95b4efa159b8b1edf8982c2661eb11c77a2d1a0380a85fe761d053c8a894028374fe60094cd18a869d87f9903b81824596ef0f920871bb9e1a60c7ee860beb741a1d36b189dadc644bd0a0f95cde7c45e51023580f81e4245cbfc28b23968da0937696ba007e8cf7e404173e6882d027af461ce72092301dc90e71123dc5c70d24299de0cf7b26d3b38a36c6adf0a165f0653bea90f8ceb27e02a43cd541ca43371cf6308bc1b9d8bcc0b2f7889aabeaf138a610a6833a8245fdffdd656418d0327f5c322a3425b6a5c2e1fe1a8189e90e3f02ebf9a77f348b03a6235eef5443e8942df8d3f63ea33afc37d2784167613abea4fb1465e8aaab51f71c51d35cd726a5d5117784c314695aebccf967bd185747154ce9557b457969f490deca1a805117365c47f75a3e4cab87bd1b00064ce7c2d975f9768a545bf77691152c4aa4bb6bfee0eddd98e790d7ba913738e99726cf2f09b8495e3531cded6d96d5119f5da971579f4dda5102c27e563d174d0f12a098139f7c469a135f4b790f350c4036fb67e6224cdcccfb2e9ff1a41f939cc68dc4c0b24070c92a7b8c42a2ab216107c94e621a2c74d3059e3409403ca2c737acbb0d29fefc69c9ff99e06a12ad9888928e4a2a353855d4e171f0b3816973812edbc6f7cceb6ec2a63628dcb2409d5fc385c40e9e01dd1ba6b10935222e416665957baa754260ded401f2c0e11b69cd174799a2c5090358413fdc14ddc0bac60219001fa97322f5b1bbad5a7e1caa8220fec2b222bcf1aefd143e52ed8b353152322891fd153c8d8b6bc8d7e45e49dc89f0df7de23110fcd8b99b28f4edd1497b5d679c769f0fa8bd3b8cb92a035d6788d1dc8ff7f72454812fa6219192a4818b7420e2361e0caf4493aa2bc1b751847d818a08e3f59b29940cbba99a4e6fce60252c70bed88724dee30da6ebd9c4795076d17fc94e8f61a62d300867ef4a76cfe433c1087e66a4a1bbb81d1ad209b579c442db285c11a1e89c66604012f0f6e7e142b5942a5b40909b38805d1b3c762327012a89444cfd78000201753625c86f108f5d180218827ccc1e6bdd402fa96e80a32bfb2b720b85ddb168384d38b36bdb31044fc9119dda0482bf2a575878d8989e3f4a1dc93b693c754cc5b27e065bdca5deb7c254f83b21fcbe680f0e817bd9a800c7c150603f59acbf7aa5c76a3c7f0053cc7889868dc4aa5d54fa216067671fefc11fa04511ab513d65f2db00605248b974250806022f163d1e3bf385a80ed5d2c60a8f2814662a1300adf85ca39dfbba1771ab2b3e534fee7e51129b90cf92fce697219cb7e20a06371a09d1f64e21f54c2537a3c5a0f3ed5398429db3d1591f8c7a66a4a2502bf4d0702a24db41e9df2deeda38fc0101d5eb3654f75c474afab54e8296b60087598967c1bb8a0db4056163b4403ce6dfb68e721f6db933e606d9effba251e7d056513117031906aa79307857c029ccd87c719b32a80afe7c8e2ecf5e2f60a729f28e230cd75a377a641bacbc2221f397c9fb62b9632806e7e33db7a5e5e3ef7d96ba2b3980dca335ff4a50cf36b51c8c90ab8208ae8359d18a71e7bb190ea7fb40594252ffc1c2b1d6b86840725eeaef5da16ec0cc337888ec628e9bf8f1f5c5b381bf2a267c11bf6c634f1be8115b8d6be52ca1618e5906942fbb6a682e4171d849af3c973d414a26ea3d87c23999bb80619bed6c4d5a2906b80fbe8a41af1127b2034ca403e109b7453c73ab467fc7e472f2f6aed293c96f3f1b32fd713b298e060781d8b564541be9c5c79d1475835006174b448490bd65fd0fb1ae60d0ae4633d5cd17a4d0d44d5492394478b9bc9c9650cf4698cf795f64fd40f035d1b72d30762f245c1c8be85f76981758001a1b160a56a812d5df257a297b7e12d084440a8f111bdfbd081a1e320a2e3f0a9f099d032304f77f58d13991b8c6b8ff822977b20c66910606412b7c5d5e8978b2d5b75f7295524db7679b4331c5827f0f81692cb710e20c7f3e20e754b34118defe53fbea9c3a0a65134387bf3cc9810a6ac7b3b3b15f1c8e88e1d068cbcba076324d732c9f6ecfdedf19214f41f85a78039f63da2447e1a9e35a0d8cbd2819cb6ac6b46ad11fe87aeb2641e9814386dde74ee5667180c26371843b569c1103c13b119397a269318f1502761a5ae0c3fc89299a914d3faae9fc95c38e1a5b7ce9083a4fa5bffb963b2770b900e90d587096a9d33981b179291c9af15036b1f9d385c6c85980cf9580c7b898534d88c16ecf5ea245ca0587959c2fc791376a38abb575a8b9b05cc155ce4572d474d35e323942ed289403210f45396309878a57b54065877f3aaa59758924373b47ee929b0830ab75c6fd1fd855e3a4321afbce4b84d15e2f71f01ec0c1610b53b7b3460988a6d9534da8c385188d57c88973db4d5eb8e2007b00f53ca35ecf0ad19cf7bdad3b2aa68cd03fe58737a6cfd2c8b370a70e9d4b433953ad1040def61a9caf1367f02698c694b840df28e078a0a4f581886121f860fbef28995036edc9e4b9cb8ea3f83811f9c8ec5a28252cb80d7e3cced9c82389e410c36002ecf25842cc523a58f7d046c2092c63006d8205537e3aeda22ecf2f4c479226cadd47aadd4985ebcc11e1129845d53fa3023800b358ce38257275ab3c8095ad61f6aefd20ce441560804247c8a7d73591cb1412b22952a29bd18485a0058b9742f6feccfb86de074f1d19f498a16b65ed6cba8210787ca0b3a9201cf8de5541bd599a62d519a33c32c0f7fbf329578322c6ecbde3b92d1be8241e2146d691d29e970832189746f968ac3bd79ce43a5c5558b3a42a743f3127a329c90389fcfc22d60777210f3ab6be73ef8cdfc5ee4b8a90f026b4d2b462446ab5dcf9aa05e37f1e24b47aecb77b63541228f78115283a615d40c8ac4a9d51dcfb567945cb12bd9ce47d1a0e05781d9d351ce863065d12f7096ba11d5133a0f119f930d5f15ed14fbc152a24f5e9b10ec10a1165e3b747c7ff5de5c21603d24676c3f9ffed4048ebd002e5f5427f5e40912f5799587348ca28512a2e4ab012dc66d7ab9221a55a8aa12c977ede77e96f7c0a01aa21c0c9c937e13d8e4466adccc35b4525ff01ac17669a98e31cecf9df6bc825b68b86a27c922104f28c0cbf1a6fd312577ba7f27aa1e1ed80621cfae46ec385706a295bde402cdcb8c0c6247b6f81b1d4cf222908218cf8fa275b4ce29dce03e956052bd1b3a4ae1061095015079d2fbb5c8cbc3be96041e4b60a5a019c8d71a2402301e86d0f5c67190bbd013ea90d94dc4e297211be06015864ba75001be8cfbe78bdc2f73a12279798fd56f9ee918e2f3e65b8cf84208160cd3c113446f2a6894c5136dc033353e9a87b38fd160392cf724a5b591ce8818824415d6bc50b40643d2f61541ecdff9e62e46085683614d9849c3aa6b02ead05f8b7c001b409d1da3ca7954f1acb8281bf3f53f62e7e2ea324247941a97063a2588578ede81f46ce3f59e7e44ce42ee6dad846c8216d000ba5c0a711828bad9fd60ed399fd703c8e9f2f1cf7380807fa462187080b71658cd17c653b152c22cc1bda870e98d68996194894d0867fc7f57a9abcd496cd36b19fe0dd1c54300a55d5451d5b67ea969735de4fd6fc3f06ad07a307ece0074b0d80bcbfafb30a8656a9965ab4c2d8f911430b11472ee5ef1b3a2c053e48db3e82f1d4139aff10d56d5a8d46de6f3e960a806d7202375a272ee99114935c15bbf286a875a28eff62b3f3bec83b765d03e5c8d01eaf1853a380226b842dd18c453ee0720c9a70b1b53a681200b232bea0d2d4db5a1ffe3c65ab15200c8d6bd963422bca72f4541397575b8d61c291b2e4ea95c3784e75c663ecbc073720efd1e8556112c3a9ab2871c408d1df32ed653cd58411034800b897666ab7dc16846defaa9026eae1811785025a33f9d3b33b6b0181335194a82b99a6bc4dfbe0fa3bc6788df9cf8e9c7d1f8098d14c5f365ce355561bb4e0820aa3cd2d786cc44688d226a715456ddbf4e483a555b1683af231b32662d7a1061ad6ef1bd4a8e9024522d9534eb88ae5863afc33461eb6f0696a74762e4b8fb742536ea0a1e01d1ebdf94165c2547d8342d67bd8ca87191f7f071ea412153557fbf0580a723b420121e101b24cedfe97b6d897b9be8de8779c482538a8f4cb047b5fcca13aff715880e1211e7119d2457d3484440eaf0830b6f845363f5e63fe4abd50b301bd0bf746eec90ea03a36ae540b6cc5c125732c9b7daa7878a72ffb84113aa157cae408c9e9928600db33d91108e465e146488c95636715048b86a9155a5dc4328a990c6cadd4d01bd30427de3b4deaf3ae5f659689b5240209b61523210d0576b4362804a554ba98c9f31b39bdc19e56e8cf1611006df99f7edc9ca8e94c54c40e235710a42bc200249d377c3b183f596c60fc03ddbdae6bb4af1480eae36c59c0d11b3d6e5a8e5ae7506ccbb7f884eb6b1e72d84f9695e96d5b481a5d9a4991ea5976db9a926527121e96ad9afd23210350286a5b32cecb68870ff1b9d56a2acf6d8a71c610234ffae297d84125b182a4809f7c6ca9b00821d46ec0a2d6a59a1a805b9aaf57b72544f40d54cfaf36ab7d55d295bef11044015d675abcb831a4046d00d29e109405c01527528f0f5bc821b0722875e8664976d7deaee2ba2b58d6b70f8320895593a5673944e3fbd7e4040b5811df184e069928ff23e309d149381e5b25440902429a8ce5ea394eec643549fc0512a8477ba48df6d564af9ada1fea8155507ce1e45d30be966853cc2b0806c5a76ec92d7e710ba1c0b9f855a59cc094699788e2a747a0971d2ed9a321fc63a22ff5dd6c29bbe21901344665628c55001f998806dab99d9efa692bdd340de9e19c814f4577ae7745bf441344d92320a4dcf8494061470d25e1e26612336677018e4e439c870022214ab11d9e71a9b1491ac1d0b376f0e30218c7e48d497af6c62cf4e7722e56030c11d26b1ca51434e4fe8003cba95407ce1c6438a6b2c99cd33bfde34b2682b3d4c2f8e60dd11e6881b8414182ec2bc9bbd06e579158f0ffcafef3610ecf0d59d4ada78161b2d5fc99548d3a236436b2e38d47f21fe34dd9d240ac85a9ec54d1c4890ced77124d27ae3555cf47cd5dfb1f0a4b83984cfe51d53a56f2a6210620ac6f9c9923950f70bc4c5738d2e4e9767a37a91714d969ad66b7bcf52c9012f15dc602ca95798c59159cba69cd29b6a4232cabeafc8c87be24dd1f02842204cc1332b8afa3f9e09e6cd160704e1ca7d1332b890a2151a94387b675564c52bf662bd6119952be8150c8c4a245ccb22e6db18a05fa121b2b577a94c71b08d7d021a5e64d54438cefc5d2ac4867c10868d68d6d0a4cb48a43084a06f19fdcde7472a84d23abe8bcdd37980eeba6b9ec6a356949159187d4b2b9d8a874312b3b9983514787b102add531e6a640e8b8fd240484b80f3d1ccc95b649b6c2750f4a6397d33c59367685e5cd2530310aa9bbec18ecab748f7545c5ed8c746b34b0102780954347e5650c6c1bfc23e66d7d9f438c32d4903e8326b927d26088b1317c2e3c1aa6db9f1673bab5fd7fbc9d45425b9e413f24b886105edbd1d23ae511b45384614a5ee7710e7b784205456d861425a6382390a0f73fe10770c4f3cf23f8206b410e9639784e632d79fe439db98e46193784ff8ba338d434ced3382bbd790a85ea122dbb1dfc9d474a566b455f2be3fd6d2f9bc6d3d6432eb437accbb4db6dd83271f07828e0857b9fdc64eb88cdf2faccb7dc00be0a17353eb1b0bb6c0d7695e6037abdfca41b88004fd3ad06f7f07ee9c8a1b602c7d306d4e9cb8f75fbbc24c9da340f406ef9b1647524a88c356a14e78072535bcabca2006086b852c903032639bc6a285982625f0b36d3440798101c8d4c531be35db8e9311c43509c378999a52c6415d666c69ca58f6ad912ccf5d91a80a9dbd3c92988222965ae047f8982a412fa9f5aab07407419dd5d0d4fe961da0b013b9fd9f08eea991e4fca2edcca85d4f43bbdf6c3fdbd478cdc2e7f9f9f177569d536f94b7f36dcae2b9a7e59164259bb533f59cc22f97604e83cc41da5c4ec16e15cc6940177352f08fb5345e4da6ac2e2f695bbe694587aed814e117d3737154e07f59bbae3080afc2ff6beac6988862d3700b57b5bce295d8d46816cda8808c4855c1da25c99a205d0a9cc344b69165b1fa50c2d32803cffb399f0b0e94806dedc32864bf1b10178c014815f553a6be84a2953e57de36fbdd9989fec704ec9c0895c4472f90b501d35e95092af866019481205e60e4cddfb82880e5448b9afe51e9d2dd4099be398fb7fda61ddae8610542c683fb0d3750e97e37c055e629b47e4d4e1f268a5700f133d81aec8acd014280bada48a6371d198b3a5920a1b5b2b88ebdb9d4e0f1b972045c21d34e1b542a1923c0a2102d3321ecafee4494e0960c4d798602ae3af5ed1c681fb8e85d4a26fb8119acf774214e61517eabc537fbec3ddf5613264e5ea3f8413ba8d24f8482b94c0109c662e50f42da86da41a2ac34e38bb7991bd6b0038180802e4f9b709feab5b9c444d09473d458e5ac30a6f9bb0cc4b00c24d32f79910ab8e9b36826b73102aa83a331f7d1c0ca8433602ebf935c217dfd29aca8f8e9ef1e3e9f5acf9bc7fa003cb1fe4548b54ace0a4b619b2c3f207c7428ab1803ca56613098d859c885a93dbb01d7a222607e27e71a15a34a7d1d1efe0655b818f044ddd4ec4c91ab49ab18d58a7970021f0c92e99c3a95705a797a99a8b536baa6f6d31d2a74c4c3db08217a00bf8026555a6f869285d79d8a7f8db9a67db608dc994a3d21e07bb7b333c2f423a5b31471a39506bc823b6310441d9f6448c9c7c8aae1bc391fff94419da3e5a6b4c5edd87df9c3fa25b8d4a7da86c44c2406099bbe6e80789c942450d325a5c147f1961ca74b7e688be7ff322616cf4706a9149c9fa114dc80d704df620c8a9b4ce483085fdf63d3154c533b645729c3783c6f4a95537a230c492068e61091298b3cc70e90431e26b516ac8ae634910e1c17f9ee734165f88f366a0552890a246f7abefbbe6438d93b8896be71db18e964ae0f49e9adfbc1e1b987f212148ee5e2ea746909d8f857a17580237b41957ddb2c0036b0db4741cebe30e42b5983f4ffb452ffa9797b00d55fd33f52b3100a274c909c30c60c0907a2ae864b145ca41031eed91fe779141baf7d871b6bf351d82c415f00c7c848f937b98eef20768782670cc962bc4f51bea2c23e3539c6377464acd12eeb6ebbc623713a3b4a22cdaf4c312dc9b3c90d4c1603d54f738e6f889b445d8001709ab1db64c24f057a4350e9de7772d559638ce3456a3852f8bd5f94d6cef422c1f2c40c6892cddff12edbd983d4475fa59bae4dc9b9b1ab9d6b6c3fed4046559700c194b81bd7ddfbb16e7ca2b620a2541c1a7e946a6b6c86036862804e4596db4e4fa6619def02764163512278680fbf40bd6f44485f771cac100d379773d58d2a4d2f21f2eafbd306f26ad52ef2a8abc0ba39fbb9b985f4d9415ce937181466a0a24070956e562bb9d00549fff52aeaa4f4c6cab434d017588775cd0737b551d7b7979ee32c12361a7512a60083907a5e6b97f061a2920a210a89b35750fcbe355fd58d7211e986633101849b9ebf53912cab3d50a61069fd3f58aa08d9f9c5ed69e3ba31563316d25d7ee6c62b7b629cd0fcac976e9bb0dcef4f69eda5992f81ecc1d90af0b5189bb99072377c2239560b8a9df3ac9f9b36f86fd2dc9e7c7b2f2ba82b671d29f11f15777b30544fadb41b1620f2787975a02f0e64d208d2bd3f9e54a00dcff19ba6fd77963227c654c7e358042a7ab72c24c1db6842ab300091f3cd7b37611b94d732b2bca6703fa096d23540b5b1a80f80cf3a2af3b8530e85e388093464a98c3e30fccd48a5fec468d683dea74504f645446f0ce029575e879d855acc68cba782af074fd4ea41169c71cc488d861d31ff20918374900346222eb083da3251ba1ae7cac6d26ca1fdb3d0ad3595f4acb0679855f6535f8d0d954e898eaed73f65b62367857daa729c5d315f223cc7502bc90fd1ef70f73df55cdc04875b5f8e387f2c1efa73f090e4320f31d8fb62833c5a052b2405ae5886314b98d6330309c621862634a6879816a73d67302b6a1a57f10c2f3e627b5f1790dc3f8b9f4025aeb03fdc30744dfdc8c979985b3a6096abd32cde191087125e03b9878f431e0be8e353a1804bbeebaa6723c9832fd53ae5ae10cecf0356a1c8d0ab70e720a280e69bbdfae01b3c7cea347ae04cafbac9e9b3cd707c8c099db3526c06803063b427eb46e4116df13d33c5e5b7df524dd1cc5bba20036f02bae5968a1d00223444030a4c06795c308c4a4e07ebde33e77b6cb6e745b30d681b967e9329debee0e531201ed67bba102646712e7e606ab87e4441a758046cea5487196e5741601cfe3abe995ee48e7f0323a400aabed1d0da27f5fa5fe013c84b437bef2de5de524a99648107790758079c16a78b2a85b0f3c8e5cf38ca2f7f06da5b083b77dbb87da305b88dcbc8a8ac0a5917f7e5df3e201db41657dbebffbe22a128d15db509dd44af71dfe2083df76a5b6de30664e7f56bedb5cd3dfe4dd4e2aa4389a8d7dfbdd6bf3312d14f645656b9fdff1117f7ab1cb0a4b24e54517295d359c1c5aa11f7b54bfdc27dad155685dcc57d0ed8c9ca70dffda4a437e6ef5125395ddf5330ff97c7215403221ceead444037f7f641b8f17762a9f735da9bce155815a2cfbd0c532028a80c51c392132f5078808b65220e15158cac0eb54aeb1b870c93710a0e51462459776b3dccca00be6bb50fde71eeaa268a289568ca2043e9044c7630aa72c3ab7a400a6a0b0bb3872311966a40410a3166ec127084c8709ac4c0e9616b72ffde264b708a6e1370cf599631683a24db15b20fa626094e042e2099b1d6daad5a6badb5731583765d7d5a6badfbd0a696035f83b6f427cec974f1fbad368b4319e816ad2fede27764cb0798810e43ab83e723c4ee2da7d4bed16f92506a69a594d25aeba6744b736da873debb8f6cee63d4086142d209a6086302154bdb96ac80e3c25695d4eee33d6729a4a0046f06b950a7547fc8fa4cb3b9c0dee5d68bb6e2eec6070469427f34a7fc6e361d757e90afd91e382be34a0a5338144ba8084848418924610ab25d6a319ef7939294d2df58bb8f783badd537a5d4d234de0c0ab95615f10cb5487e145a47657150b9f8a8ac2898fc69d3a10a0524f404184e609182e504242758151698105984329e0866a0c061db4184378345c90e3fc867cfd995dad67bceaed076f9addbe4cd2077771daba78d49c5785f46e57cb9daa8ffc97914ea518f42bda638b647a13e9c17d8478d2c3aa0bbf2c7fb9cf113cb0af6b8f77239f851a811bf833be8bfcec9f970dac8c911eb8362f7d543594a5a1232c7b536c77dfe8d02ad9cb9f74422211cc0078feda3e7a1bca172d2bcd74151169871460fbcf780fbcd1bcb567671bb973a3cfe39cf23aa763e14e83df765ab14c29c88f23c07776c5fb6bccf62d9da1985aae1799e8ee7fdac42a9f7428a73c0ef537af4b1cccfe5926c096d9abbce3fbbe7ef44cf9efd3f686d6fed2ebb73b873efbed6aecb9ca69e145df7572cf5f61e7ff77df5ca233beba0ddb443ecee9ede21768805a1fe160b945f1ed9de885fecaed3e9c65201a58f7d7f47cc93e59f12bd8ca78dd6f6cfdb7bd4c7b2b5bbf7442c96be87f6edf0f85d52dcf7eaec1bdebf5fe78bed63a980dd3d1ecb17bb0c3bdf41b737e269c3bfe5e9d04a74d93a7bceb064b1f9f1db2be2cf6287e9d74477ffc1d0eebec478d30f81288e0f8676ee511ed95dfe8ea81b4bdf2b6f042102dc1fd9734f8ae37ee7fd043b2f7fedf2571022b0d2c037f7aded818fdfc6dbe650acc59b41d7ea11989a78282929d19413b4ee50a95695c4de9d1aee96f2b2713470b809e71d1472973aa1f376da789f0a4a8de15ad55dea91ecd35a6bad8558d1a5cea1b5d69a47cd51a291e8d1aed11aad512154d786425dd34f18292d618b794c25944e3da1b98d1b794c24362567d2e1b123839214159817dcafafb5d6dcf64c2237ea0a88d8b4b6422f09b1bf94a209d5e05dc0021e67d3771e4f56a1eef36fba89498f2e94e8efc1e71e7feec6559d5f470ede1357f37356b31b577304d2fd4804cc1c8178d77bfcf9c16f9c235e79231ea70e71c5adca32a2f46ef14e4812d3d528436b0d7df1f07ff93fd1878e203527a774070999a6e1121065f99626d910c9a3d5c444294d559175a94ee5a42555268fd28406793c86643e688e011d7f4c0bea73a38e334c581d4ce872d264474d827ca1d168349a75ef8046a3d160f8da2062f48deb94db6a8e011d7fbc0478db797344db82bfd0b8cd5ad5082dcaf2a12f5e130a4aa275afa3e6ea893e74705059ee44e3acdd39a20c1502a29ee45bba7c411ae2369ad39ce65b10ee3dc80c528530a69c2b15d3e5c442640d12547a6cca7854a19a0f893e5e95cea882873a1fb0d3940db496506daacb4d95b99baacac6a9d1e5d090fa55001d0cd19a001c03cbd1cd2fde412229cbbfa85abaf9cd2938960e0d6dcc6d37af5cf0b056edd85a6bb5b539ebcf596da57a1363551fa86a65532b1b7777fbc6f278a0aa950ab55aadbb6d7776956c7a7baaa93d675962b14dc986440f499cc95e9c6c4f9b52957befbd4b565051955c41432ec10d97b3e72ccb122c66294e94ba74c512ccea50abb4ae5966594698a5304496a5166dbd211363062b495274dd73666506194cac1c69d174cf99953056b47c332b55665644d8df9eb32a5434616940228592cb4847820c461449153ac45c46e69922a6334ad018d1118aaa78b9280e3218417379db5745fa324a62e48a4384acc8e5e1af8a5e53f2a6e69a66a0bc70e49aa1612a7279de57c5a12e5d602eeffb2a1123b336c399970bcc2a83fbfef78f6c22463e22a4bc5c608c7c415489b9c05491087d45b1c2e4226284f322cb4d60ae490990f355b1e55290400a25971114938ba404407d55f41f8c2863e49a52e0f08393cbd349d1265f684c2e3752692da2d29f9545691554da747111f13756b481e332429b2081ebfaa82ce79264cb152e231e1043dbd048e4c2c0260531b88818018f704d4a00235b9926ae23faf572557105a4522a684f4cae3af2c0d92908168ea8d72cad793ffc0961387fb8ede2546a47ab5cf5f1f418d90955433c61cf900ff5f1ab52a97878787a7a7a7c7cc8300c7f7e7e58b0f0e6d461dcddddbda7c72728fcf9f1f171afeed5abbb7f2b5fcdfaf3e3eeeed5ddddab7b75afeeeeeeeeee5eddbdba7b75777777afeed5bdba577777afeed5bdba57f7ea5edd3f16fad361dc5bad5640aa162e5c00bd78f17a6f8aa238864463059ffbd00d5518a4220ad23060c4881123045285402a55a8dd9d060d1a34aead9486cf1f202090a885aa45a802c2d385d3161f510b17ef22c8d2f0c58b202272877beeeeeea5915dc7175f75afeed5ff5f14c571ac00dce1a001e80763c6a0313cc6ada0023003bb71108c1ad467850123460c13741d65c890419224eb63b166cca001127d90bf0d90c823d23a8cc78821838824594d44feac1b435313a9767cee2e43860c70877b24a88106f60542f0489205ee98f139050dc07aaf5e4a396c67ad7a33543342151d67801af82e8f78343e1d46d7a861e3d361b407000058f059e0b5565b1ed1416b7c356cd898b5d6fadd7bef68810ab444d7b1fc2cb0a0d50a400004f0b93b6500166f660e7fafa7503771b890677f8229b29543984f341fde5459f47ba2f0d74b6cc0e7a0748608c1ba298bfe8092f43bf0b40e4a27b5a3fa3014150f17180c96278b3a9329e44dee4293afd0e42838acc98f9a7a1e1084e6c3000e9aa8177ede6b308339f3260e03e283a99bacdc8180f766479f362e3562594c1e3e9f0f48843f04ba3836ae088802a9a031ac49112c870687866cfe0949f029d96afd7879ebfce416b4d431720702721890e7e2d36174e9b04d5f7c1fc5e10e03896aeac54e8a851a6db1c65edb1eea032a17a4d8710383f765091e304ca0dc69fbb73d136cefadf758079dc55830fbc2011833177c8863a685daaeb5ed3997466b6f81661b102bc10f3398d1326506850d67542d8011807d6dad6a5b6badfdbcf4b0b7b75b0b5af63685fdba98f12245972b33b60d2a427f1097372c8badb5d642e00a9b1fc8a0a04413319765b32d0d4cb605b7b5d65a1e2ab6657078c2d21d2c1026b3ade58264db143e0a13ac5b4f5b1888bb1ca7a5cc931f2d4c2b2d67aa84492cb4b489f93cfdac80e449cf9657b80294253c1a2835f9ac80258a2a4bad6705189cec24f18c8192d2f2a4224267470450b42a5401565973b6c2d3955814a31e578e909e662724151183cdb68c800ac7719c15d9374978928d9a8c683a0a941e28247179d223cbd1079cf4b0525466498f2ab42094f44821294d093db6182561420f2cb4190b5158e9b102d3cc032454e9a1a5287c41abc228ed527f8c1b748bb6ae93302a5d62c0f4cf9e332a60643ce99e3d6754ccb06054d4e87f2209a8c3708a538f2935fbafa9b899f23df941a414a51e5298286b4609fa1b018906f79cc59eecf2ff63574cc83ad4aab047512c07bde365c60934169344e33232aed0dc120d11f477264691d64acc2881b652b068d076cf59ec0d0d2ab11390446164333054d4b0e2031812642976d0b7948ef5edb5e38a7a083cfa57a4b4eead1ce9d7bab2550b92962d974b7ed15a90c8bea5f6eface307dfdefeaa7c071bceecb2b563fe1ded9ca356fd4e52b2cb9615cb1a45cf2abc2c6b183da8bf71c97fb9e0af2217d8afa8fbaa66289a7ab922dbbe46cbf1533ceaa840dba8af0dda69fb07e5f83a6b503a526ecf1e7c28da75fc1e47d125de5bbc567d774cb75b3b3e25e78341344d8ee20ff4898b34321ab52719ada98b15b4bb01dccb297b3bd60d1b59a35aa62a55d9b441ff0160740794a4a2149a8cae5f6270532af49e6002aecb7e1551dbc50aba3ed2a6235cd7e746d7b1ac62362d67354252b3983a2255a34b0be26a9a5fface1914a4edbbd47b622d82d1f769e6fe0eb97b08e5fe7e1db978701f73f17e13571b16b9747fc5cde954d11757746cdda0acfa198abea3f883067b80e01e95687f6a995021216be7914db4da7e22816117fefc9c08c32e0e7f16930cb9b8bbda461fdccf0d44bb70b9fd2a836e6fa305501b8b738a46b4bf6f969d214b0b33a8acb2cd3daed3fb184a5c6d5a4727f53535a6bec8ac20155d525965794f521cde7bdeac792c3fe86077bf4d173dd8368f8ea56ff781c49f925ee78d1d45a1b4fe3a16015344f54f3425b7c75f71fd0d2afd6a77c6983e904a47226030ad78abb58ea8ed270af5aa71857fe7552b20faf10864e755757b4fa5bf52fa459efa984c4ab45e478b7efd22740c2264d2666d7374e5fddc65def26800bab3d7a2c4609bfbec5b6d0aea8d1ae0711b29121f4850afc1efc421746fafc5557efdd3851257795cfda88ffa0df4d3478df48ba86812c87d8e48912829345ddcf66da8df60e77f679c95f58d1ba08ae8df54e26ad33c4f44f528a1e9caafebeac77dd42fc03ecfeba0358f3c63113dae7ed457fd4a35aeb6a7f4f5d3d7aa5f6d23eaf5afc447893b44c00051bd1e8910019380e94252040c109ed73e5f84877bfde1ab463faa2cee9fe7555ffa9112e1e845af373ea31efd6f8c3b5f448f2efbd28d94e019551c87f281a24828c93d4adc3eab258a46a2421388990a6b36f7f3a94c61082ecf8ccaecb27ed9dc6fff65af29c198ae3421e18c36f72ba751927beead134fec922e39b14b8a4604b235bba46b36d7c42e699bcd716d7e3a5192e3e85115a2322aa301ac292481356ad4a041b3b4942307498ae2cf8f8e4ecebb3651b2be57b10826b7a8b188116ac4930bcc7d9e31b210f5f55fe7f5a833fea847b2b2eadb2094bcf2c54214bc2ad3022bd22b94b4f6099daf104ab5a314d54a631b17c58a2e6952514c6f493c288eaac63e07f382fcf6194071786f3ffb0a98187c6fed27828077fe3eca1431d91215eab9625fe9cbb67eb4ad97d93607fc666390b66d6d4b8bbc310a25adee9cf407d3c6486563280ed302fb58a462a60dfb3489929ead688f8d3ea6d1db584e37442440c59a2362bf0405169c5ca8c713d079fb7a5ec0bd5d0179d44864b5414fd24b8f458cd027925ea8df19573bf8f572a5c6490980f5d7d4eb4f8d9f7ad4a7c62761db79c5b6938a6d8b448f4d1bd60a5dd2188d6d238dd531a6472221bc5e2e8d1a894840e9ebebe5d2634fd2ebf5727914ec803783c017a818b9ea1f71856f74fd20eb33463086852663a490418c511263f6449ec9e10972891c9a72600aeb9a5aaf67d4bd2426c98e729abb2426c9ae6e1a773767eec720d22fbe24263bbf2426c9eeba777edd2f8949aeb31f750ca85be0bac6fdf246d0b3f6c67dfe1af7c6102b070d1af7ea7b6fd4a8d1ba31c4dafe9406ae5af537b4bb6edbb75ddfdbdc1bf723508c8d7b8da449920b8a111463cb01b7a018338ef03c4a291d29c514079d94529fb5d66ae9acb5d64a29a553b4d6d25a6bad94529aad78d85acedacd5a6b6dadd6564c71688a81b51587b556bfd65a7b496b2dbec1db2e75ce568a73cf58abae14d56590a8d2c7b456d4fb9cf306aa06fd26a5d6526b29ad1445524aa9a6d4da9be7eb18dce1b8c3718763afbd1b9c6b3738ee70e824b79fdbfb06b67de72ab7d56b647b1b277beddde05cbbc1b1d7ed7577270be78b3783ca5a349d668a2f77b76ddb360e5f8e26f99005ee84dcc959f4e79eef50388efa255d12b2d0a95dbad3c59f76af7b3e750caee52cb89f1d03ee7a1b77fa28491fc438dbcf74733a42a716751aa24eee64371da12c74faeea7efbb7dcffc29fbbee918383939dd29a84a958b76d80ce99625f56b991b1a74dcd480044de726072414103ee47ce3061876fe9c6f78da59cc6c671c9eec8c030876fe560e5e86f4dc81e382366739ae862336670396cdd9a0c4e666376ca9810a8ebc51f30cf7c3be4928ec7b3f6c61df59d2153430792fc4c84c21d3b219b57e20cdae3448997de0f5b29d3d53c133d46070c0aa66681234c311a784b540481c60010509e90ad212dbb2b132c8b6d5019b5073c0a187292430d44ba749939cbfaf634cc0129a5031acf03169ca89e18b134f098cc106269d2a6452f4cd4ac830981743104b7009455d0c4d4eb825b21c83131dd8f2f8c304d92c8627273f9c182529e18712a6274b7e3429aa5204f362c20f136a33193eb0e4078c89862f84474d1586155186d8fa67519678c2e950ab42a638af598d8928706a90d2c40629566238d1a83d67525c8891468a0d240b5278505531d3ba33cfb7879a73ce7b875c107cd3af7f64eba074ee0952588ba3dbddb62be309adb3e70c863755681f353386d03c3ccc4881fe6ea061a4c13036c070820a309040bed1e0130d3ae584084767bc1cf900e648461e25bda0b4cb6f591454c7b9f33e300744695027b5a38259dae313feb05801b570e1e2c58b6305412f60c49041b266d0a86103002ffcf9eb59a4d37c727ad51cf6e44a5e3accab05ad00086000376edc20000ea19616d779a13e2f7f1d06bd5287404f7a2890e8e6b80e41206d732caaea8fe2708da4718a32a13f4a5677b7d65a6badb5d65a6bb3b515d7ca430577ef56ebc5b5561e2ab4bfb5d6da5ab3b5d65a8bc9b7d65e5aa9b5d65a7b456badb5d65a6b3d6bedb7b364684fafcb5e9775a8ebfa4368fffb41d0aef5a2eabdf782f7de7befe675f7d68c3bee5e226cadde7be79c2048543f08c869bdb7de5a6bad76c35d9e5d9e9823efbd97db26b74d8a290e71daa8b372b7dedfeebd3af587ca9c73cea04aaf7fd0aedb7b76ab7bb31f04590fdf6badddbe8fdb3008125dfbd9fba6cd41699dd40eea9b14476a47c5d393da51599e7b6f8f4f55f1f4f46c9e9d3d3fb5afcfadd5faf60189eefb7cb527fc6ab5f75a8b83c577efd517e8de163e55bc66399cbb0704614363f2e14ad5d6cefb7418c0c109b0b64934101f600eaa2a1929c551ca41699d8fc90829d0493d06a6902b391425264a2328cd5a5532528aa394da5165150f16cab55aad2664b2e867803a95f089762578c6d9743472a7692384c27b7cd8b051a9787eec1552850c096148085a488e1a9bb0874d0f143d60abd5f3c3a27ab9f3bacc429c17bc601ab88ac87fd808a9d56a405688650394f2daaa039ebbbb7b0577d8bb836ece49575d5544f6c694bd70baaefcd448b5f6de6de3a6e83ababbb5eeeeeeeebe7118679c73477d568cf3e79e73eebacef3be1a443be89c9808ccc9b19bee54965277cff33ecfc999f6fb9ca26a543b83b46a72dbe501411d46a374ddf56da523ce41a1505a6b9d21a21c3b7cdf0853a9d40e49546307dd2110dd2a1e90082889ae63a9bdd65a6badb5d65a6badb5d65a6bad7574f7fc5d1e8968c085abd6eed5bdbabb57f7eaaea3a3934aa5767676542a1511518f4f1895554e1e4a1d746010310407b90811471fde7f4e854dfa449f6a94299bc640a21ac3a2c1112f81c928c42177538de85315aa5b6a14c7a0569916d047a2f5cba68fc2941acb5386e90b3124697dfea49422a184e6508ae4068922d66ceab02f94525a4413344529a54eec1d4a2b0e4478e0043e74608511580e6cfaaaaeebba9f212165776fbbaeeb2a120f605ea0bd5cf9f1e440a2bce9edc3ed081cf6f6b38aed618448a20ad9cb8531fe376208a618bbac6184525396a61a0864455071c66e7f99d43cc984465f950f46cc0050c43d7387b8730aa625fbbebd976969df2150d8778811ec4b44d2bedf3af364df1fd27106cdbecf030d07f645d3b46f1a12f64dd3b46f115e5eba0a5da6cb2484d3ae2f5ebabc340dc6defd68632c44d3bb80440a2e4079c900b36d70e802b6865440a4400041800922ceb68fdbdcb80104961940f0b06d8e7d69afd76bcacd21892b650389364bc6daa06d6bb0586d34e55463ade5b66ff9d93f6ccb83990e98e101cbb64ff200c5b6df1a62b2edeb506261dbe721dbf67d70a01467db0fd203d3b6df8107b69df960c5b61fc20f4c2f2db0a23073b634c3b63320901cddb9b570a40993315a509064042c2cd828a4783c4ba66bdb33d8be3b3051e605ca832e75ebc60e4580a006aa103ec6586bad7fcc9a272d2c86e2e9c598374d80546113b226949a6072118e8953b452d3444d143557d46c69f1b22725b050856e62405816bbf23302a3950ce94aa8e689858ca9c86789a61f198d4acf12b05076022a3c163bb1e4041a27d4f8844e4e5025e1d44326cbbdf75e36489435c59459fac1060d0f48c491648e8a52aa906644eb423ba2acb9552119d9131dda0e19214cd03fa0e89222a326ca1346984ccd494e125d34193831d06210348180a6759862429b241d567852c44b87189ca461d241c624044cf88726b8a933a20967c491114959ebd044d1e606ebf006ca9dd8e2354a3fd630717a87244eac2a8c628a1f51584159739758ef1025ca089c76e80213c10e47454d453bc8a614a114bef036806025a9e0c3942f58b4dd7336850c8c351aef399b3233010833539a76a99f86294db08af4ef399b52c5192a55c2300446e0cffc16e4a19c4b3ff3d72f73e89f96b2c0df11756c624bac6298432bea5c50ce337bea7c39d3f89773884e447d8ef8bd8b3954ec6ce888e564431ff42270a744d47ba2fe1cd17b1d31e7679b0d72306d80ef2dd9d068a858ce1e76a9cb09c410bbd47ba6d99a875c3b3aa2426a9ee7c9947833a8cc479b7b8ffb12dc9c36f7a56a03c1dd9a6cee37cf0a4a72db6b73af83b2d8a5dd774baa4275ac5b52cd3f9f6e4dd306f7155ca14b2e4efe42716cb5fbdc7369e615b93493c5855c9c79c1b46eaa0836c7856073f768ab2579e8ddc7cf81fd5c57f46f5265713f5dd8c73d72e12d147719b11125b91f63dac77293825ff7a80ae1e7c27ba4b967818db82ed8c7d78fce09bd7db9955b93d1e67eab71b5cd4dee5dd034978f9ce2ce6883ef91b5770a4121d0f2a042add6745971ba3c178f0e9a73ce39e755b5bf01f9f4ed58040c10f2e9577b7f0392fc4b7e9109ae7ed8afbf01f9f48afef68fb8aab8ca9ff30884becd39e7d7599c5b67189651127c2ae2a2ca0261c4788d9594d0df971c6c73471c8c039f2ba238ba073f3f75af0d76b00d96dd1a8a83e7c1cf4e1487eac1cf6d288e9d073fd7288ed4839fc1cf4854686baa2c30a8829e9f3497d96036aa4239ccb1581592f1e0734719e87111e61e7c0c23638ce573628ee12d542897a12cf08fd0a08131967329682c679959a682b1cc91e3c68d5d724fe44ff0c7aae7ef1f71ad7a80d4bf2311303d4f64044289904fff882bc7385865818fc7b1c45a1c4b1dfe58862fc6f2398e5659e0932ec6926c31963fae46163fe1586a9fb1958f36f89e7f3c222e8395f01266c26828093ef9d365c5551e2fe043c6a7385815c2f8e9712dc44e1ad3b00c912345fcaa2cf0afc81d7134b0e48eb8a3a31cd59554361a734c2cf118d3250e017e55a1eda8b2c0bf9ffa9e3127bf6e4ca1679b0cb41da58e8e36f8d3458ad3c523e63594045f2566274a82bf23e636b94649f075c43c0525c1e76855686be26038d35e18a68b3608eb621b7cd8069f3baa421989b2c0cf6a9ad0fc52584667311b3cda60ce98c71fd4254397d1dc13f75431e69e9e36f89efd5a059817cc0d3e90fcd2d9bfecdeeb44164c3ae7c11e3963d206df836ec494fcde52f27b1dd43352ceb10450d71d73acb2c0ef9cd0b3ccb1a6a6a6a60d7eab0a3960b2c007a3d8e0e7b8a0e94ec494ec5e8b9692dda3c41c31487e308319043b2316748506fc493e08165901195b3f8b606e95df77fe551ea7cbe6d873a2714adbe8a82c1087f7f33d2cd6dfc0f74c4524603cbfe713ecc6361c5416f818fc2c967a83ba07fd3d8ce679b40dbe3375e07b97fc20cd20cd20cd60fef2c17798831a0cc1f73e1104c1ff3eaf29e1cda0922bbaad2ae4faf7a2269f2d5ed0dd779e534ed90f77d1a78f7a1df451e07ba223117d54163864dbdfe8304a823f4170aa9ad0df7bcfbd40eeb5c1df68756eb40dfe26ab186fb20dbe0b9a2e72a3ca025d1ca1ed57071feb2ab43df8215985eea3c632c49b2be262568d7eeb256cfa7acd765a43700cfc0625c329f404260620e00d42d8c107f3821bf49dd20c5c844d472edbdb0ea60dbf42039feca8c48c9a6962e9d08c00004010002316002020100a87c442911c87f24c14dd0314800d65823e665c401f88435192c34008628c21c4004280310418620c62caaa0ce82a27d81a1ac6c4d01061b5d9edcef91f453135597b57896b298b5c3fed1607cb8201197f8f5f0b436057a8d99f0bf9df4b0982c9d8a91fb0b66e869a4f43fa1ddd75ec69db3caf7c264881990dc6a65fcde8b6eb2fd58728b77d373a70d78b6ab8db30c1258e45eaa8e9bf1378e20dd0f39bce6dc57d9ee720f1fd0bf4ec72b18683abeb89f8ecbbde6857bfd033626cd43430c9911a89c73558d1ade1cc59ecade19f68984a28de704b00c86e1033c924462667a000997ad9e6656fff6e3aa9e4c3221c21951df47598f989bfb3a747f91fdfadc6fb8a6910d74f90fdc7cb2040a317413834047e19b9d105e45e54c75b7a7c8de76a4a3cb8b2b970206acbcca66c79930d6e481c8615bfce2dec4810865abf6c19f6a0c1c2a98c2f23c3bacbca2dcf7f44cf630c6ec94b5026011c8f8f599dfcac8e5c73b2d5dd7c505ab97fb19c9ee6409494fa22491750fde0fd9467f535c948f53a394e0a3fb5a51439a474a43ffe0da2f0fb6225beeae4d2bf425289b3d7d24d1411cd89049d38e1905cb5c1f58c5a7282a2aebb4b01a7f3cfcf1e91334257e1c78ba5ef2af4f6591ce6e3da3c315dd24bef1d6226f74ba6169fd7c5f7107c2e78bd0bcdf326af270dc269fa5692034440a399b139884ea4386b2e6f1818c921af102f9a4d4776d6e1dacb78121feb114e2da06156ece0b1294d364a6f9a945644790f06d10b189394897602912627164d109876a7d104b94a02d963b04c98adfe403829338635d40289c7bf7ec49b1885578e65310819420c4340b0223899f025f244a068b79d492266c9fcff60100068c9cf3b66322c9a0e59f6c641e7810e1f95b821c769ddc56ee1acf5984945cb94453feeafde9aceda418d5068c6ddd8431a9b2431330886538fb0b5f29e2cfb904739c92981f50ba5a8a09900e1cbf7ce45b53a3412d2199c79f3d73279ce7bf8c76555c28536b3115e28e1cf42d426884fdfa5657aa83f4506f8f2c20a335085c96c98e8c841e481cdc5f8e789bce4f5fed95e47bb40a8f0d98c2bb2805ee504f04ea7f02d6119965c5fda975c2f4f89a25a6b61b4201ea3aaa0a9a9f0ebb8dcf3e12b9c10b0b18ed82b3cbd7e99bd98d85ffcc22551f2a8ddb51e579d1c2839cee2b99994c7ff68af798c5550d55ebe53f2fd9c7cd7fafe95ef8b372883c2fd75f738a45ae9d2835979ec2e8472f86e2e9dc91b3027eb487aaf74b275c9bb45fd7fb93b914fc20c0163e2556e6820d3e87f737c6b4167f9950cd460687eaeefb159345275223f0d26ebc0348d4feccdb3c955e74251d4956a1198835f2549e842e1725db120c2984255cb18ae10a0ab8cacc0ecdedfc96e82e6954497942952d6861f9b4d9927fbbd06b62bae756af2d71b39b2891f5676abde8076e883d075b9e6df8bcb3856d6e558bfae510b77bbe76aba13ebbe1c8ea3c63af59f36c84d316917d238164dedec159226a108c8ba947c9c62a0c03014eb9c8d509ffa5bc05e41607c6c1dd535b1a1aefd55f6db9f96d7a3b4266d2eb39f69bc491c6705170635417c76f54703283816e6819c50faf07ce909da0355d728e3cee04d1a8a8918516c52d4e508ab22291c139e499378066fa2098da96ecea3b20d632bee696da3f856c7114bc00c7c795d6c50aea4a5f77c8e8668e51dd5269c2ef02b1c48dd6419f6cbbce964b8875173e27282c87f2e351353154d91a39bf7f803d6d3d51d912d98309669d2010b52e5839308a2069b02be7d3627223b48889c6cb50d6cfe56f7bba647e6faeee75aa747dae0c3b80d82a1b20897a22569a1aee7de6dec850fe285470d4650fd7ed5bff265cf56e3e0b7aa584af25fbca77bc576d8f8e320a50a595325a07b4a7cc9fe66bbf2e1dfc3683c6a8f769f2645cf49f1961276f04f5a9bc0cb9380747d1075107a28e7a5b6d071242cab1cb1e0ea9e3f70a6e4bbeba8fce40b75d97b52356f987ab24fa187d438aa92fc23803271ffe4d658dd7f9d4b4fdc4ccb8e39ef21b13ac9f116ea22bf08059a1029821fbea8ce91694f634fe3e20670d28b1805ac5bad5c52de942e18e92dd1950384b594cf9ee9db5fda8399a017dc3204bd8ca37c8f9393edebcd9fd887d35a00d06b25c7481116d85c65c45232f6495ad569fb53fd2efa112bea7534b0cb2d399b4caee03b8d601c6618d213f97632f991829a2d23a0e988a01184ada7b7d6a20ab748f24b75b7acc32c90d74eae950073858dab1ae2a47d95f83d02dd2e42ba19c89c073e8394857ff25a567ae5be5d255ebee0367125100a3d1895a5c4794c0679555eaf33c0f7fc85480836be9d8a913631f4c1fcd008e5d0d9df2ebc2d77959d0417b9eb31165b5bea1430194084028e88c6f2955dc9b9bf641c3a6a718d1c0ae442a5378d747440564a055d57a5e6b40dbd02471728212291f7bcaf2a6ccce8b9c4c47b03330427cdf29f9263bdc8cf8021c8cce00b28bdbf0af40bd2d25e6b0476e5eb662ee0ef846570cfd469589934a62d5cb8eb8ead95dd475c37147410608873b81452f58247c2bf07c80f241789faf967072df51961e9fc4b31927fa71ce272e5c8be516e761f8754f05d74b9bc8074ff31ac4eaf56e8985bdbd9640b994fef6feba9e81defd161185527321d3ea19f3cf19275ea7135a42da5955200b2a990c4d2e6625f8cb513ba220f1d67fd3fe90b28eda00bdba9fa161133374b3d69d9663794b9d60e423bb4f92222e3c011f1367d71055c4f9e49f007cd48ef0c6b59517c8d34f9cc2895bae29adca51953b4d75035c7e7b437a5619d2e369b310823f197b2411afae924ac4111d40eeab670594b05482e41417a181c89128e3595a6d09c0db69a6f9d5693a3fa789ae4c1265e64768fb4ba30d5f41b7e896c5857a57722bb24c2dc61bd42f36f78b5d972703c07644f72c2b0486cdbf9042177938bd4194fc2564851ce6342ad4d864df71fcc4d3c6755480c9a1ae1eff08b461d3404ab9a28d109975dd48eb47b23a73dc73228d20e0d6981777c23b0be501a1084d4f5cca8b54773e8c85fd612003940f499e88862c0e7e0b4bcf235a960fc5e925701b59e568474ba462a9ba301524dfe5bfea76bf83959878a77611885cb2265578cefea64cc9a2456ff9436bb136ffc38633df479f5325a5bd0587342436c0a3ac35da5f019d2f536810e36076dc04219cfb3ba8fd1fb3987d7c4782e7440bdb1b8eff106a67e52948bba04147c8cf4191cee1aec8d2df88cefc3ff8cbf5b353e4566453b35be6d199fbe7d073f90b35a7833bebb682133595a7c04631904f560f184f8b8240c30dd5a0e449e2db9cb5127c5a7ac0f1a70f57da79237af81cd81088e977d097e61138949481a1137b4de92edf28df8bb787c9102ce7befce19e382e5b761ad2d03d3b71b0887c05029fc4f7497ee6aef1cfb1ec7172feb4fa09eff82041ef92c32ec5de519defc68c2736e9c3edeee16bd2f794eebb42bf65dea99b110037f29017aafdcd7d91d036569bf32e00d8b7da8925ad6fe4eb49316fed23372c42983cae806b82a4f7cc58597a8f01fa84db9aefd1e48f975b892764ad8b1c3a7a9fcc9a27aece21e5883c40bbe52821d8c02b40981d6b65fefd7922ab78966632ebbfa852d3d724b27093a6b47824b0a6b0d88d2fadff1f5901abc0959139faf3fe39d1873be45e5fcb7ed134990172dedcad8e998158b2bbb5327e5a404d95953e1f51ecac2561767e18e45e2d0aae56a8c471d7aa7140f8086112591d9cce551e38f0ebd2fcee6ca0cf604986e4d1e98527302a132d103a27077a31d525c88986c67cf4a82ebf0bec430cc8f1ffe8f92a88c69672cc364ea2b5ae017ba9d74188f0c75acf22dea894fc5a1cdbcfd916491f296fa19c755201e98a3a190cc5ea987afaf44006da82152eac9ca23e503729f814944a78a604891d0a8d0729dcfafb6d58e98603c8bc8eed584fe24c37ad1b7c09658f982fb7cc585f4f8e7e637b3be7c20dc89702920252588efbbbb8bc3537f892a44958ce2719a15f1a5ee67e5b42068d8efbc16bebe6dc98000aeeb5cc297310fb464df60bbd49aa5265cc59c4134a009487c1c97aa94f53c318170df2f91110acbc96d6a289bf75376ea0ec600c3e52c5cab8c0743e878335034ec53ea79c790e617ed5ca5c6066885f1ec090f3255dee96b256f02e8f7d36fb9c1a476cdbfc23e3d68d2a31ab99fd207e6c60e5a74b6cadfcee3072e9a551c4118f3e28faf0be5bdebae8015ca5a829732ad95e599a9348ed881a992c356381a5b5f0df5aab843e029e0f24ef7e5b5861a0291b8d5a124feabd0d0c8591c19ccf10578d813cd4809e55b18f625f879221052f514ea5629ae02e7e80ff21f9e0f680cfd0aff1d1b821be69b73d9a34fc08cf43260813c0ff7a646541136e4394c5c584ee40feb07ec8f1def6592c9b624f73e47dd7542ead4b43b22b47c40ec1b8243ce508114229a823c2d9a8c8679a0c7133a05be73b83765153decb64c12d25895ede4ff315904d593edac9a0bfe41d5d958ca9c999c775e619e2af9d6acb34d2c7f521e11ebef4dcae296c28e304d14410664e6cc9ae8edd0bc5eb6d2c68114745428a0c29a69a5f0546b2d3d2caff559c304e074f7e5c6727ec59edd35216adbd139e32bb199d5837ed303550a2b5cb739ba2f977aeb92a304c20481c9b834e5a2bfc25bb7d9e547f640821ba1a82c5526abb8014898b0b5d051d9cb5f28a445e19b3a2dd950c01204f1c7669ae2131694420c44ed42cbfae04978fa5c57548acc773861715ab9c7f047d72420fa8adde633d1156d5497a5329dd6da7c2b340897cc030917c67c974e145fe28b04d0fa082f6270fcd7443a1dc948c9c297f66b40da218fa736c8e30d469189d5fc6675bfc5f9b5aeb027c3dfbe5df94c11f74226ba670c628ede39fc2e76321503e2a61a02915b098f6ed2584d053360e58a7a6a9c24cf8b7704ed642d943cbc97d07a992a1f5ecaebc20401e7a0203729716ba6b31ce7ebe75c618f998644fd9a890b581586d596240c7626a19eaf11c116e401875c37f5c7d880b10f92237f941e269710c77c07a5acc91de1e4728c5c4d89f07a235634e62f219f18efb3616102006a12c9f03cfadba5514d5a52ee8ef3e3bbde7c2c413a2146ce87e934659d4504f71b763f423ae176e6af2a606a6170f834f82346d632448cda60f28f874541cce70ef4136548a07498c113394e64a250902b398edb23d3a2aa588830149328f483ac398405be16f72fa02b0c214c2d2564c4f7338899174d7277ab2e2e81cb2aabb7624ba23bcdccb7adf73836718044eb753229d835c634dd9e04f1ed9c1623006d9359f2159fcffe3e4f5acbfe2e0f4a815a080ad0735a752a6b2d7b378c6ebbb439450d7d475eaba29ee3a90314475edc8248f814f88aa0701cfda12108a323942e5ffc5446ab97457776a384d1e7073b85d9e8717a91b9ddc1298f4221817d378a7f2f0ab05d3dfafdcacf15462448839c82a2020495d44204bc49a8fbff7f19000d8393c670b92f5e28f051a6fd336f00651d094e25b23325c330a803c32d013db104b79129954d725bc94b807d5cac913c61b84696ce5574615d9d5f6b5ee87ef037c3ae921a7477cfffb77e9e76986055f79479bf16e1c572bc58ae94842233149a8845a279ba6c67a8e6fd357c1cc47baa6257fbf55bea7c025873bdc1ab3374a3ec4df449837d3e841ce9a90055f78f5c05e9f5217e1437c09c05135d35e3da575ea1c2ff3f90c11cd0816a01ef9790c159b19dad415457589181ae8b42bc4c9235981e0d61ef29e3ddd9747826c5f97aecb0063e132857176e11030b302c1d3ffde49776f3082c3d30f3ebb62ffa322861e5a88b238c78d965621ce69d1dbfb1a5462893067f99452f2cebb646401be4550fca1e268fac4efdc31b1f9876d55bb6f6dbfdeefa8428cd0bf3a71e3ec12ca2d341395aff4e314650fe31dae67184ed130bf2417a7a1cd577cd71b3c796e75a0fd6ce479565d2f24cc1ce97aa276b2b9381644c5bbba4168c7f81147e60d5f0874ad8409424d7db856373147d036c6b2eba5c3a042d146cfacb30ec4663701fd2a437f2f8d155f3b8cac7947f0b226d3e25891cc7a5f2f7815e9ee68245c23ab9ad76683f27532dfef21efeb63b4288e29d8365011a4335e2385aebb80d558e598400ab5be7e43cd49d1be351aff492523477b50c6fcfedc8092456d50e2aa89245a932feef1c48415f849ec4259165bcb2e7e2b3ad2782b9998ad1a8f4eb0e1f2a959a2634d643e5ab4b0bf48136b810d205173c7799a3ce7b4319821926feb0c756d78f8486ffc99c46ee5756e4fdbbf15c894814c98a1d5346e95875166b22bfc010c74936f74dee4a177b3fe1402f60540d13c36700a8b44f7fab7e4106601b1f9e70a03c7b262ad83a9f9516a01e7c7f9b4ea9b7871970f8c35e35f08387770dc88e32bd6ec2ba924b91af8830fd36a33d0837be85db1113766167f79bbe241969656f848299f88218757d2509141648a25db98bab04bfe79e5543ffaa492f823fdf4adff6369b37bb93f6a607d73e534dea327536dbd957edbf298da5d7d9afedd156f54f96fe52eea7e407536053cf7129c1e982b8eab2538206b3e0ee40f81582cd970efd5644fad9cf72a46be60c6f537f10a6f688ff4a152227c333d81bda8244522ed828692b3362e1e1489da72b95e14c402478922307856c981ce5cca51772a8712f661d441d5126f9d7cc9b5d821395c7cccc1774633c06a7874c1227bbe284b8a0695582c685817749f7219364335e07068e9264512fb0ea7156be482e076aab1ff85b059a16a99732343d0c3f01b56b381724dbde96093f716025f098329b9dd7449f02d00a1cbaf7582f49e89a2c6347477834cc210947bc3bf93adf364c0e52b95a1c2fad7d8d46345a87b2d83e0b0c2ac7e6817bf6befb6f4552c4356f3ce9bab94e91c835c372da1abdb896fd86e1587567f86506b25ff1cc8137402724630d1a6e6795f0b0b5fd4c94b9a31a81a40913a5d5251b0a82f77c0dbf6f4375eed7e34d7499c9d65fc75fcdd5f839678c6918b015882cb57c4c74d030fd5e06c60661c23b3cb3e30aaee8bb3c66948f776a705da9db7dc6c89728a0716a7058c7ae127dee1c63d95b75324ab28f68ade867531222cbfbbbc7426fa6b245e29c0ce08c05cac466e8d6067f216604e72bc4d0319c93c69282a8325ee3973d5a56ff521dc0a62e33379ed36108e069c38247f2b42b3a1edae6fc9bb45f2648a3557e4280ff6b9209ab9c60821233e81312b5f372fe9de589d205a773c8043d33c16f8ef38462ed9c434624c213aacda13eaf674c20d75d712b4e992fbb2b3188ec9776a319147de8dae1b8140c1f7dad7d16869105dad409a0dd0ca53244bcccb90a397302fd208d6fc4decf8a9c147a7fa33800f5de467238164ea008916557b9a8f8a19c00bddf4aea3cb139f5f0743a83641b764917b8fa07d047d83609ebcbcc9ccdd4f3a4677b7e7acc37467afab81def59a09b8960694d199d0b62debb85613acab89ee5859b41e6f6eb891fce67b290289c0b33419841a70978da9e711a023a9da6941d5122660c84844ebb2b087c8eef95eb0e0ce5a8a1b9494f16e57d05c1e3a0fe13730f9c9fd6e026d1e4e5b68870ad0f832f5b8d42a691bc36db8f459f36c84bcbade7d19d89fecda504c6e64ca2ab9c710456130918899ea439ffdfd4728e2593fc80354cac56a517c6d0010ac764dd6818380ced161d6a89fee0ed9afe545dd749b6006ca89220c69f8f39daf7bde019eac750a129e1612de1c5b8b6da9e438898d667645789e12cfffb0dc5a1759c98fc5b02b387f833aa559310f062db5adc550a04de4ad05e7ebe46c7544d822dafdfb8782273c8a95c0a707babe9438f76fe5ab46335979328f0bf3cae9b4757b7f3ae364efcfd06a83122f0e81bd71a0cc22ed62e9614124125af12cefaef02cd343550b8860c38fb8ad0cd2d5f7029c1180c00007cec855a43d54abddb4ca207660846fcbf12cc9834c97b2318e2706fbb8e75c9efd4a124f2608bce7b5172e7dee2b8dd24d9cf1717a505051eb659b7ac3b0ec6990e77e3411ec89a86d7c106b21373d4f5ec6aa034e24f29f9b40a1575932636256fe1c2007a5a95db515d54f2351db807713e3ab3e81f29a723348b6e0a1cbd158d36a676f3c5da9f85ba3094f7bd70dbc2f824f4640bf6f4b210923f64c061ff99447daa9fab2d10cc8eac73d8cb2a398670bda1eee8234f747aa1650d2657da29ff8ddbb3ab44ca2bf9097bcfa1138391a68482f49b86cf19f345f81916a819ca3b504ecf853840ae4d56599edd4cfe61095c9bd03b69c75921c6f4009d04da38357c226212467928af5885c8aae60b3ee956e7f96046165fcc7196b2667d276980c4f8c51c143685fac6243829f00e7c90e63a59fd993261a1ded0b202e58c4c076be600272a364d229c1c9139b3426271fa5d889eb6522142d589a0420323c1e53559b27e161d9fe67d28ca80855b3debc04c6c906072584ff6c051f6c4e997c74838fb9d157e725d55ef61ab258aac8a18d1042a5cce74833b4e1151d3ecb39d9aa6db8a0dea13bda5854d8196b41c2b61cb0ab2c1d673d280615e84e2311557d97e542a09b359beabbfc494d45b810e82761c3c049487cfc648b7a9bc27c42e68fe89f2e23f3e28e4958e3083356b54fc04d1e5d70fbf034e0db313192aa688bf60defaa81a0889793e68daad33179b924af7ede6fb6f97d6a51b018e3af64dd9aff4879816b728e800f3acd2f2ef99feb4fa6f9860be97b95e9b9efed7ca5d0d3594ff73ae50f64bff6036a92be0c1097e08c24d1525bb1f1047e60946366840bc0c559c0c47716a2c79dfd095fbb35f54a57d0f2822777bc5a6e6783981ce89a55999814a31b83a05ef1f54f99910a78fdb2202088a369fecf5209531f46acb16648c03232b24b9e0245266e3d9d08268a59c394f58acc75b5a47fb5088b5f8b3d5a9602b4d6040a999a29864a860a4cdf84b6c3d26499c6ed6e2f71a74d1a7370a1057a65d25fa7bd34120cce69e954d4f7ed3b510d6c3f6e99fd4d24509bc9ab57929b742609acf035507574b0f619a431dba47ab9b9a61cb8b09f79c694c392ea1bc3271042e2c835aaf211dc2798c58ebbe15fc4b66be58859b631949c78cb0c22b42aba140d28a50dc5c55a8fa56705028dc1c044576a9546c45ecae16666dd4a4c8d28937d66d89d54a5fe386d07c18e924ddf712dd59f4563434b14bc406def999c7e9358ffdba647b9420f4e3191ca553327665ee7f68b0f6525774899f1fc840a49f5de96a5775990a1f9c7caca4e6c55e4af053c36b95a597f5c6a262c44ba364a551f21dc6d275ec05cf7a7b95d24ee44a02627283ed6e3e28202bb5c70d86bc83a7d8f32e2d15be65d53926eb1ac2031b0ff2c860f0ed23dce8ef2dbdcc3d588fade57de8514b067c257800fd300fd1e51f3ad58691b4d0bcbb549600970cdddda5acbbecdcc277e8f01bdf693869a4234b9a6cd216812172d705c8b69a73dc97c31ba0635f8d0811ba7b118b9aa855dc0584a02d2e5941ec1b749750255b3a9bd418ac5117c0c2f4e3b52f4092dc915ecf99af53d2aaddb94ba2d41b3bacf405d47bf7886fd3b5b0c5cc0582700453fb0a906e214cd35247840a12f344939f78e7ca5adc366d90088c01223f66be5110460cbe00f1e84a9128436c299f045500ecfee680d69fe14b27cd3418b273001b43cb65b4daac709bd4b4aea000e3ca8aaf95e8f60fae327734d716aa8d80930aec558bcc424bd2030cbfbd3707de433ba43c6122c014d63e052c08d0118f14980b576591afbc204ba19b80dc5d047fa7f7a3ff92e8df5da75460d85d2282bc0840f9b9f78f043fffcd317206558920c8f5295d520d88779f75436c0171eab692af86fa0ddc7744fe435522cca42fbfd081fea78fd7f6b984222219da97851bcec481d7845ce2f55f1c33bf6ed7517d949e698221c74a21d3728b3748a1f7ef0a3bb1cc917e656b37b2f55bf3061983a21d25889e4ec34a8784f6af12ae3d909eec0d58f730c2ce008d5d5f72eb9427eebd4423c0c8b61a725bc0f9a121b5b3a73c551f71bc72fa0cc90d2a21713052277e2eba3b5c253a8eda809aa671964a7426afdcad8e76005b37d918ab051f27dd13faf625cc0686923c385d7fc1408aec4fb0edd4a9db18c3d1b1023c818597fe145a0be2fc2d1e77be16de7bdd51e1ec88d2b0d859f3a9a1b85d51cbeb90c3961dfdba70952ba444ffaec9c233604976c382678fdadae4a0bae5b971fd85f5c6c6f38a1d56db282ebe3ef698c7e44b47212b48159a00c102e4165aca1e4ba0f781068753f56835cf8eeb97c578a92d0c020c2df5d9d29c532814b7e66c02d5d5ed9201c07843c4d8df519ccebcf0fa05b091b7b60a1884bebc103bae7a71251632517c153f982862cd855b904f70108efc6951b7cac2855c2bdf3a0658552a3f4336f473ca5bd12088fa3becbc8f05c1189d01012faafe0471c262f1af7382ed398134c41ea6a16958a786677efeddc8b77240862b30d07af6ade53dad1adb8d01fac102ae29db97e1d6357926311ee8a712f5b7f23d4353790a988357e13dcc119c5d9eae3d6696cf81ae24748401fa270dc3d6c5280c4403eec3d35125e8a8d3154f84a04d6751e00788a62af714ce12244a61ae16e8779fb231a014c71005fb58df9fb37cd0a916f49fae00ba77641461967b2b493babbf9a9d544d66dea97c3015f2cf04cc93c8dc239557d8503a3824878c788da8f7196781894b402fd8e2d89d30dee8a13eea62bc8bc843193e519dd70627b56a10a09372ca7a609d589c31e755900b0e11033a624200deacd23d58d5a261dcd289b5fec7714cc15907b986fffc9f48ffdc041b6707fe507a3b91d9cd15d74176e6963e2caaf777e0555c0f22c0747b275eadcc03560548278bb0d6ffdf9ed91624299f8f070246267dfc85eb161c05f6cbd9445c2fe0e17e2bf3425050ef35ba3cc0d74a55fe8d73b52feac3fe2106c18b0a8bbf02b207170cc541b61b829e8b958897ec7caab7dc79a17a886b5c2d0b27f69c29f458c6c64e24fc87ed1eeddee1b0ad699ae2fb6f79c664fbd7fa7bc9fe62077c1b5a20289650fb9fb9427790cb786720234980d00a435a097d1b7709e40f0e33b6180f2c56fd3eee3c7ab131772f57afc39e9d735edef9b9ee0d1ab9519fff1e7f6b24f685f8ed0ac01f199585590588babfcf077a6dd39c7b37ed85da08c9a4d42ec6112877cd25e793a398b763075bd44cd6aeb8a6d040612a5c7197da9221a1fad69d837c84b8d08fffecd18799f13383375fad267803dccdc461f53c2f255b9bfa28227f182224abdb06c128548006d3ccb0442d88ee898f13fa88fff7edbced3f26c8ccd568afdb6e481c9af5b21e4e11f79abe4da39c4ccb8961891f5492e5fbf0fb81f8df23f9fb3709714e4fe55b218cd035e6cd0371a5344e4ceba07f3404e20f6a0dfcfd232202345a7bc185cda2331b4e6f38f18db2b1ee94514234d9a4a412911a383a3149400793b7d64e4d511ceec5899d5b7d483185f47ffab1f1ccc61f70165c0f68e842e60eef4014fcb0bcaf6870bb9258804b4a2179b8f372818e90ea2cc5dd208031583ae89fe3e012c818ccc361b26e7a6a67643d47d7a06788f04383c665a7d71e424d22f206a7ddbe0e06169dd0e9c900ac2edb6fd67da36ace681ad3dedaa02b6f7306816e22891715ef946ca29b12a6a2f6a43b1684697740a0ca56261073dc15941cf8cefcd7760ed44d676eb9f6590d52a7a6ee80814fc43593a492d39cc9e6e80e9f85fba34751ab426703d33dc85ec262c751ca69326e6d4104192fd9280a1982f32af1bc0f43201a773374788be3f39d12ec031e11b7008733521a20f15f38e87b0be1ce2394f568bac2b3aef576a8b641267ac81f41506af7bdadb5a75ff18dd1c801b84128f733284e4ab2b7db5601eeb191b98ce5a23107cd0c7a01500f194df7c2627fc52a2b73fc2d955d66e14e6560e88447587054b8ba6dcea50fd3ecfd184388f7c6a960569cd2a4da0be53568e41a72a6e7645e1e6c435ff92804e925857e9f06622170510743db2afde1a329dd0953f27f5344afcde33181b6f010adc49c8710961b92dfa7cd2f55f2b0781808638e209c4c5cf5aaf94166fb1824bdb3c956444aa32379b79743b438972941aa811a527378863190c31bad24304fa9e4059e8afc284b980587683a8f14628b0711709aba9cc5af570870e22e45d3e84beddbcfc382d864324d9eb0409558bd437ca2219ade60228ba6b4b4ad13bc4de71568e492f8ee78f4b8ed1e2228bfb20e17920d8924446efa5e1a4f5c343ea31e9c0db8870fad37e4a1dc86f1618aac9a91a103d4f17bf8baf70a1ee61672b3faca04535b49093a155e38361cf78521dac54b70575ea75915667da5acf9e7ed2a453b015125aad9a8679137e696c4c947beaef0e6e84fab616ef04d8b0549e835aac8ebd9c76dd01dcf1024f2fe320a171f318be32efbda1c7582048bff3ec7196799b5b76d8f97df001e7cf9ab8bd33bd45bcfb73e6ec840bff3abb3979e4d2012ee291e3bca107030f2ad2043f0601ab1ff1be9c7654bca225b14730f6792d315542c73aead0c78bbe4433758363a00dbb3418007195bc1707b0bbb00861909a70cf1d5f3e51ae57bd428e793b071c1bb1240a7e3999ca9e9f57eec805de1c6d99dccf0a345e8c1e26780f32be0f849a3390619a89222be8219617044219a5e20f6c99c2024ace24db1f5b1c10a597fe6583de0d2657ad9024e0c14a2757f0ccda2329a077313a60aa4707da658e6abaed10fe94b078c625cac3c0e75b1be553179c424866f6e8a86e924f6c365abb22f4bd80d0b52d774ad757c231fe5e461f46312f310a37534873ba75162f6febf89aeb0ee1304bcd2f63284d9ed04417df1f9b5785b39b317c565096a7318abb0da0e264e77f2a28b41093569cea1a7b6001e557a9f540a3f26b094a110d0b94687924fd1dda3ab59a3c0138beb014a2ce2a39e3a88d69d98ac567cf394e31205bbfd3a7b406a928adcc6a604f19a30c1b06dc464d514f68029c2c7296a10ad178e9caa7e3202f2b58e3488a643e67b505017df4d11b81647b126653963c701e645cc112bf9015789b4ad71e6eb450e30a957ca0bc1cc935682503a6d51c79a933fc8c11ba37a1ee0941236093081394ad414e3bac384846cb7a72cf9b92e4e102b6fcaa288de03ca052c1aba40282f219f41b43b95d643d2cf0c2c5cfdb704b3208408f3fa3b50332e06d16ef4444bda02edb258ccf0cb8577824c6537389b8c4d0db14aec3299b84ba2165ff4bc4a6b98efe2ea6a38bc84b31f6b3194816b6bf93fa543a2711504cfc41b1c165641e77a718f310967289107bc231444d3c921540aee9b7ac8e3e9aca9b72ec18c266812a69834141bb9c70b4c99d911961e7ad05e7f28085839026f4f780f808fa168b9e236638584209aaecdb0eda555debbf4253825fb0bc3cc63502dc854cbf4341b9fcde7f2faf67070ea4b4b017ff10a8f45052e50d416b58a5303fc6c74556936682952093f31f119ae24bc07e7ddf3a6c2d591a85d96a0fee331cf5ab0ee6e9a30abbd45814d9790cca5fda88f586fa09568205a7ce078d04c46356dc96865a1a15c3cc279794593521988d615582e0c3940a9dc9f37927f0fcdd1aaae0960d36df722dfcfb02ee7ae0485231ae9af86801ce4a7e35f3d3c6b668218668eea9419d2581f1e52a3bab9aa42680e4b8ce2f06a98d7cd92b42ab46410e58d2443064c60e81200b38016aac53148acfc9ba6a9272c8fec5d3e852d0af0eacd018582a007be737edf271901d1ae642f903e319af0ac8eaab754e55de24a56b338ef2fea6a20af7e808ada3c78eff9ab3193f11a4a138078c66531b0053b691b7c939357bf3bd7373e9bb316934e6f00d13a0e488ff57a4a3f68da70e302ac956ab45028908d716a3b98bde9e9444f4a13241afdc42a457cfaf7e4f00be92e45b5864d6785102881d525fcef67c7d7348eb8a0da8e415f6f22cfa9e398cff8780d12af751a08ea5ffb8dfe8e747f2d6b883347f8fc3c7a29b2d9b66a59f3d3a8cb3649456bc206d5e301c822b7be430de4ae6a7ff214d937cef4718477e252fe6bab45aac5d1872ba20a0aa53f171158852e3e1959f2ad3bc7ba5b63cb3622a8f996dc66b5b71cac32dff3c363e1c8ec309d586e5e6e8144894325ea63836d56882ba1553a1c13e153ada13b49d0adc49accecaa10370d5bb43357c8bfb8045d6c4d105b339f76746d297d07ad8f177dad9f23596a6ec75b74f6fd692bc95a29fb3139bb200c550b553a8e371f7cd1b06b33f5e308729eee3f417dd08d3129c9bfab52d32af84e57d117aac2d839422a813d7f68471cd87d322d69c729aeab06aa3f56512d8e11d093bb84ac765670b94740e85e656168a3a6717d3aa9d2d818bcab86abc2f3869f0c563b12b048054d8369f16a08ead9d75ca920c769cc6a7315bbb4094ec817622497ff1865c86224f04f6646f899a13e8da9717133ee6c6bf830c3ab4fa90f328f55d8faf5f445620a9ec07261c74b05d34e11b4ba631f5cc14d5b1d5def056d978ac35a7500be44962c22d25fb878d333f6c13c3c9c5c811c220b83fe976fb2deaa8d980535cbdb565aadcc907ace4f6121fd6075cacbe7ced5bcac5c7cdf1b6f1230ad67771ff749bb994dd91c1f19eec37d33355ecf6ca51b49ca0baa9906de2c4f2dab6704f74d9e1965f9aac5ac4659a5792b1cf6ca60e818e8d23de1a43e95e88c41a995c90ecc0ec22a676548ca6ee6230a153217bda9dabc71c1ccdf6aa967b62d7a8753cb0dc78c835b3df663ce7d700be6d90b55f4037eee03ea2a1a910f2e511c1da3342727af92d5fd7ad83b86fb6d5119d2eb6c9358ac765528273b18d831c370f8c3e626966a13679469b41d4ae427b930049649565a17186a368ea679e37c0fd74c5f55eac3641ca1f2a0c125c37cdd22f12dd3639b7674cca3d0edcef440ee648690a69e96c9018d3c41f19ee6e6ef68dd1fa71c7fa7fd6a99cd04e8e66172e282bcf9245f4a6a8c0d5840b26c57bb6e243b95e9395c79be7bbb75c47571bb83f8fec0de3e0b37c33db2188838ab67a4d388893d58b061aba9f0721ec167f248cb02873e77d59752682733eb3436343aff1a76ae51487accb15b2c6220a157b3538bf8048c8b1834e9ed50a6415ef771dfeca20241815efb838331e59f63fbd2efc3dcf618755b0e8b6d4d0d6441c5dbfd969bc3adf3e1b8bfbf5fd4e212fefb093474ae278e7e9e46eee3231394815e0e962ddc696417a9503d8399310c459f1b1eda2485717544a50418b3174954d5b3c0c30284bd8ec86055be15949821fa22fdf7bf1db0f24ab324ff74b4c9e92b623f8f7ccf1759a534ed63a88a8595530909456e571d3bd3d7c6505b28203ed41b341a5ac67fc4fd82c184ad88bf903cc33adc8d0339008c1f2b8121d908195d2569e4a81f601a0d123f1b215905a717c9d23cb568716b0b7761acb2603fc95d4eb45543e14b4c295836cb48ec97b15f2411d5e1e285dd057e3636def3bc225ad757c23362aeceae3f7e819172297f8073457bf8c4996e52790c0ae5e361b6d563b74efb22c153de1cf75cc075856c86c40ce71bdc9a3211d19654fb457f06c64bd34d5e0cf30ce0b224a22328d74505ef2590199fa8e894d3b285f81384428932b30a644411ee61626db9ca111369c172c6fb3cfd09372b6c884a8e8cdf6c3c8b6c80800a9c42cfd4600bc0817079b61f72312d5cb32ab465a5dd8df1e6f13e8455eea31992ea86041188507e92cc6e7ac90383a25c88741d19fee86760804340439e724b4736e90879cc26e3117b1b96bc78a91e9ee562a0c48012c6f38f0950f8f2797ae61acd7cadbfbda4dd651b4fcf2ba87fab9b751353ad772027847e67d033c4aaaccc05daf4f8a736776bd890c8e123ef3905d055d072426127a717829ea04fd0b62204f25e12d48c57b13a8e7463309c83cc3c6d620413e95dae059ce24692a98291891b102fe314f1352e0fd91499aa48f2e8b444edcdaf736277db2d09f80c98adae794d534de325101097ba05d739bd7cd2899583b67977487a7a6f3e0f13e25682760dfba50295dd0fc82ad3095a76292056a66a8c6cfc802019a73819e0407164aa60b27603e3653a45c93407c691a982c9da0d40902a3d0f683523fa6cffe235e56b3f2f92d4c7f77860437d64e30a09f490a5d7c70f39db4b23c8c5a7f37ad5935b9913e2e755e8dc18477a03ffdb931bea03535e136922b1e514bb0bc97bcc948a79c063e274a61cdd7cf4dc9bc1431de207f08d7cf75c938148886c47e9e988c100232ada72d6928c3530ffd85eb6eb0aeadf4a9d7be23039a88d5eb8aa6707c88b0f5a9067ce15cc62905875cf42859b1ba74c2ddb36dad5c8622c229ddd1e4174f239031037e9a7a4e0a3947d359292c092387804039cad6321936c9a8cdc17d993634817d95c1a5e4bea3bb728e60da7da5ad46f1f1f5986542c84eb55328971250cbd649408bfe9896314693c84d021bc7da0666b257ca0b1cfa811478e5948dfd1703746c30cf117f848b40f01682bbf7f3fc7b5c5c16919a57aeea9e124a45418e148ed721ba7c582ca6d14d560a9c7c71ef32f8e4f9c9b7bc9fb99d9174c45bb238a879c75bbc79fc76704ceba7258af664177211ebfd66e11c8dfd2953b87e01181a17658ed3576d572ae5b42b7df366755637108d4b431dafba62aacddb46efa2bf82d4c48559c9347305823cd5b8324c56c923b13bca18b6e1ba1ad6347ae221035b9a83a5f27b9bc1ba2c65b8aca4d36d644d22381c4baaa8d0c462439652df63e96449b96923e70c15e43254aa89baa549e784beaab4e9061671b648d24efe7b3b5ea49d21ef40eb5e5059612b820e0b89730db20ac5825057eefb25b86cd2792eb3275ccce960f8b10b0bec0020dcc93610bcba5a0acb5e135c67f3978570fdd739a0fa6343b5a43a14d26e4ebbae0ad93553dc7a6695cade9f034fa3b15ebc838b587f78217897327d8124da372bad0143fc0f063d3a02f4548343df13c5240ad4f0e0a6ccf2875f2b7a18a4c90c333aade36b3596af339b287e90f6ec749941718019b0b6dc4ae7ac0b968741d6783ecafec30b8a87e042a6ea9f53630606a1a3c4d6a9bee5aaf1d4a979f41f68c40efb236b0fcb172ba10fde5289ddff7ed50b763c35e5075ecf6449414a15bd606d616024ddbab3454f940a64b958acd85684b40cda3b6c5245b38422d7a3a54a08083271120e65fcacf4b782415c2d21ff034011a120db5783589acc177dc67694864bed5713e4516db58264657a600d2bea9fecce391912439e7b2fc3c3161094e8639b46214edfac6aa178b49550007611456b0a17bee73c1b55cdb1ef155fb7b5d9ed605964c543d286719ca4438f2071480450bc0c1cd44bad920313ecd3f70f6f8d78f2a1c5b7bf2c4b23bffcc05828a5489ea0a4b66e8190eec9120e7381d824cefdf9792495bf7fe1a6ebeef23c6af070b56b5e54cde44b7e507115a8e4f8d1d4197fd2ab7b8795bf701831f1bf0df21b3beddd2f470765a529a23aa8c4a755cb3d898d39031065f6e2bdc9c60894f12d1b79e4f13d23e79502775abfd15d54a42c59c48306ef79fec4ba76b191f4f43733fca00514d6267367a057c42e5578bffff741ffcd0c79201cbc0ce3045baa9f4241fd14809d5f32bc178bc709b6c7949394efce4b93448408059bd4204ed5cd48608d7287e692e5e8488765302fa504ccd65269f541c01086142f4a715b6a1f2fb1bbac3366d91a6900b3750e681f015256201e1173a2e6dce48bb83c114cef2bfff8c564b3836dbb0b59148274c24a041edbbfc4ce336e9eb51391ab30afa1e62d47dc11db58aca8e872fa126d13eba3729681e5cbc4b10e5faf32a7e84b8362d155a7a16f27af5750e5d223e4a004870409f02d4255973875c533f4a78b0795e813334319a7a3a4f36880a86d46f99b76b1814803f3279343814d8f980e5513038ea0f0461d9ad718afa5dfe8554e34711a00621ea67414797fc1898a3979297a8bf04e502e119fcc73353deacb4c6e910ef8ae2826767fdac30b9f4fb26fe93cc1512a0c1c0f055335f1d1026ab425e80edbe137c42759755ccde3a703fa7f8fcffe0eb0f9219bb9d2d571f9219b23711231eb48d6421e3e6cd57e0b1fb99473c415df767618026d58a73cf1f90054c681c31988ba9d4a013e1a1cf8eb9015a32c62d117db16f3e3c7424b52d8f75bd54b99d9ffe024772c10125658e814bd67e8db2075b7f663cbeca7b8b96573eec67ecc241c2c5c7f3c1e837c2685b5e733cb21201712ed0d9be67702c5f56371444fb792f10d8df6cf3b28b55b950f98123055f97c039883102a48760b25efe6c70c94ec7a020b252fe3ef5e5ceed9877973f1d7d17ae1e301dcd44822c72128ebcf149f57163901081c0e5a47a23556d88ebf51fec757f38b87fe6f68cc34bd29bb36fdc77c19e01feb061c8437bba8ff1c1c9cb8fe77d6b0bf9dd933064f2804c4635e6c7f36afa1aad138523e0e8c15d5bdeabcd68ece9fe4c06a4c98df6918dfd657e030838eca2e080443832b0703d44e454e09d0acf0d0df27e43e3f9df0a003f05d7b49102d63849de00803d2527b285d1f4f9f87d2000647663846af838b513824ab2576ec9d8e7100f1745eb3ed4fb4796142245105818d008429ce040c2376fada931a490f225c7297c58f0076a00f88a2f8c422eab67fa75cbfc4ca40be363d91df1f92d49b7bef20114aab86d3fb4c91852e833d73e64c489e44fd2bbe810f1d534e20bd7cf9fb276d0a896f5f3ae506a84d5f2831ed0c9de277079d310440f9cc564430e97a541df9188fe49b9abd8a203561e77022b6dd1ff96ed6aa883aee6f3ee00e132fdbc8c4c4ed23b5e7a1f052a803b2f052106d87bcd8940e7e687cbef6e19a72005b30762d84e37c17fe121a8846dd9325ee8df5e60bd5f30c0d3225306ff6227f06d84218debe3ee16fee70f24a9d0097a4913ac309f619832ded87858522f90ae60a84e01f592455ca38ae13ffcedcf3f14b3868fed29e7c405264d143e6704477c6824aeb2cee72d7cb8a22f56c9922b4552777833fe331cd917635534929f51768dd055ba8ab053eaff83c8265dcae994cf52b99478b471e97ebafad6f13ee52f677925e28c1742cd36baf0e61d85658e080cf5c26b86363a5002a4d535da0407a29ac5b5f8b4d730632f76e0c8bdaaa4873602bf275606813f53a82552aa44eac4bfd1a17d4639dfd3b8189643f464803781640a224b54bb3f0cecc8acca6c7196be52cd47bba012201af5a42e40d19e6f014617aa3f6306d8673ffcf12ecfc3146676103c33d41775b420e199f9a5ccd9e69f56532ad56d2f09da9dea36f59206d65e1277618b9f978836342374875ff278439766893276c298271612655093bf8df19fa8f5bea42e94337b39792813be84f84f6d91a7c22f52a86eb9605871f3d1a975ea41f42895826568bc51c4233e059792c3e9daabd2439c0e343543bef3afec0dada24458e63e369c6e42d14784b4d3d0f1a801d9c1f4d8d9d0f1ef112d1e96f5a188f9be74c55f00b8cc31466bbc07405d72e5f0907bf6f05a29c7c60e0f497f7ab2342f9d14dd82bfd0a7ac63767b27d7cada2f29aa7c7afd3e4a0745bc14863c4e973e39679bb1d016d8fabe7fcc47665810663c078ba646cd126b2824b5f2448f194d765f22bf6116073980a984ee51966eca29ddebdeb045b9d09198af0f4b427180960e4429870cbff7054e9ac0118d663e7d352393243698f55b613d8e00965438fafb4e9f2756e80caf90243a48d2b064ad4d9c443c77e05024cf84a68bbbf69ef0241d7f04fa566cb719ec6c99005a9ee89032eaa23d93f105b0c1f4ef96d83d85998efe6918bec970e158f387015843fcbe7d9fea1e748ed5ca08c06868f6649363d86100074b6865f508af811c94247fd87462fca1b5c35763e21410c705c4b1e07cb081d632943e7c1e3d4f2edc22d698e85ac112592740e03c469a7fa8875be4d92dfdd008549ff60408f9866ec4c6e9128ba031c18285686c5ef6fe31f3ae5dba8b746b7d0ad7b2f7c629fa0312155759620ef37f4bc6c349c6b46087f91fa31a4fe51583f1138c7908a344b937d0c900f058aac9b2dcc12c4e90c093b5630f462fd1a8ba4cd5251a9d91f5982a24bb3f9e8b42a6f1d01401625535d7afd9938fd84ad2701a86c81ad463c49b869012f2c9655a7a22d8a786a2c1748dd295fc289ca0ab2fc0f80e16956048c51de8d5e1720a6b4c2ed0a824b55c7f1bdae0cf278d490f0abdb350c848b88992a9e456793c853bec5d0b3623d693f65e9d12d0af4fc2519e90b5e978d3c9ef0a28649c16f6609169ddcc044bc877e50e9102a04c0570080bc99533890e0c2fee72fc7bd9f1d2ab90d079dec7fb12b43a4a0579c7cd26be8cc968659dec653c3132d026391bd139d9f9efb71baeda2200c77539cacafd6dbc9d3ff142a6940b87212ed03900e936498010f77580b62b52e6e22a2b74dbdf09535fc7e1b2c33d454b61f7fbbe9254b46b58924427e0e36464f0985de1b153adb9ecaf06b007a8dc6c5ab86e7d76c266a428b10ca65faaa1127bfbbb8566f29a697fc3178a3a9938d202ae7480f78dc79fd8eea2d261a56c17d609baf4fd5195443db72a9902abd4f488354b660eb722ceee9cd58ad881485ede5314cf48955a82d44392d8b511dcd5d7a6726b4bcae7825808c704e8a880c892f607543711c43a360c747b486899a22a731940e582c34c6746e476873e5d6ff2463a1ad7a4d266b7bc4a63843701e25d4ba789ec91073fc655f2e08ecbb91f32aeb746a37e1eefa5347ec160912223fcedeee3175728a220d08b2c314cf54569da8cf3f39032f5eb6b275c849b066f0e0ca120c41da2907fdaef2ef627e0d12834cd2756422aff55bd4bb116f5f0dca276489db87550b1e38ed98bdafecd72869bfab0eaf7d114eef5e337bd7c10b9f2c69d6d764bc7906e421edb6ed9c9ff06f58e34bc004901784489a71e00ec1d90b72819d3b55f47922a8ccd7d4c9d4f7f36fa2a196cae1998c99a9d123743b57110d0e3fb0c744bf894287a627230b00a5cce0c0db8add0b690d1862101b07e219f1e4ea5eaf4ce0cc89f65f88f078c974926819cd36804291b8cf62b69056c365ddf717f0864b46f3c7ed7e614af9f295c9fc7fe0eb3e2101412cd38fd9ebf95a7750c7ad60683a26f876e69e33a03ee7887e4e2e6e54dd5d048244aba73607e410c7ec899cd2b7b50d51f75672e90c143c63f575e644b1d6913df6930e21529778b35ec01743d95c099c76f14ed0ee6ba80b127a25697f5fc885cf6f260d299884730fdbe5e7f4b51c270b9699b5dc5f4f8eedbc5fc85d33cd343d455c3daf0300a6dced162ca522b157b3d90c82354a9778ba9445d89ded62704278e52beef9f95e7ddd25aba3afc7e47f3ee878de91ee837acab605771f4f5b72c1afa40b76bb8635310d56a2bb07a99820bf686a34e5c8ccc230c8b4a9b91e46a096dd85e16ecc40e97e19f0959846e621a4b72e5dd38104012da037c10d8c2993a94960c2d60b3bc8d8a09de0ea1e7d2b40b26c2f1446e7bfe41aaaa1878e1da14fee86d9fe51b35c719e1de5215d78868229575f5fa3b849c6fea86a2a04f9acfe106ef670e0459d044fb510424b82d3fa3ed586a052cba5356949103e945cb4da21c0ccb16b6542c2c382ef5a2935a1e9fda1451ad5907f10486f4060afca2aa4aa0aff201a49b403454d7cb4d29802884986baa3ab96280878763b51e197ca4c659d704327dd8a966c10e969ce02af81411f5968e58490de59e730ef83dab89a3a123fec9cfc8af360e27a961345dbcba8f78e322cee41079ea15628b1c382e9f765e17aade169694353d0956d87b0387bf9494f6c3881d58ca9b9558f0bbb18082d4539b747675368823213d496807fcce060c3efaa8b5b5f6abd204ff9edff6c6a1c0584908cf483165a98865e785fd0a96556c27d708bda67b057929d6d4d6913e9d64e90838b92a86addd526a9fc0135d4c162f216c22c0779bf7b0e0086e014258185b8c2ca2ddbc29af6c7bb2f3f39fc5e77680c6e0604467256477245307b8367ea480bec0c23b86a35c937717f30d3dee87a46283e75f5f3f78c74071ea2764f53f66b893f123412be41b12f8efcbfab0f48dbbbdfbba5f899d0a27ba656ab3d4f16532372806f5197b9ee60dc89b69711f88997c8c745e08fc4d9eccef93a7cc31576ea9cd0670e2e017ac84a8b2aff23b3b240e54e27046cd469a25cd2128838e8bb54a218ab7a09e0aa409576d8de1d68c30228151b86ae33a215674cd059defcde2abd498c71fb1a9a2a1319776391cbe97163fa15232e47d7c6508756b25eb81563c12b17a3477eed186bba073571c1e766e54bc79ec58a23b7a7f8d611ff75b081659b59a4486ff4518f571a8123c6545d17b200b75618a7948a703b46f375837b33f3c810b53758ccb5a76b2859709bc23da25cac032c4153fe1f3e1643018b41e733f285adfd6c392ae62d444f98100b6d22b8c3240d21ace2d1f71870eaad62cc139208548d7739920055a703de7bb49818faac984b8874c9dad3e61bd2414f95ea476e43a4f2236a65511b430345e4eefca155816af2bc16229f41cef905677a5e786d64a790bd1b2ed61698b4f92a040fcd5a896a2de9349739d5b6ad2595e028ab0ea570b21ca1c754ce95a6dab084b4a70bf18f2c1953d698770708b1c42def9becdcca4c06f3357205460bfcd901a1a0979c4dab3bebed5bdf4bfb2b7dbc968adfa08879f3cbfc96a285521c4d8fc553eb31dd84afdf382b4f5a86dcb40db0adf644affa4387b996601d657647be5d384801ef5d841da65c3fc065cec94b333d5ad8a03b89db3ec1c4c21232bfa899cc079ff59df4fd78d7216386b88e462fa40c8ba7b4a30e61f0f0fc8cc4b73cca3fa4eda9a84de6444ec58e6e338ec1747fb15ec40afe8643d325e3fd6073017f0855ab0d994ff728fe242112aff087133da467a437f29c96d0f5a9c7d5f3e158a7c9384d10f4900f015dfb1096500586da61f10183b49188971c27e24aa620a564c2128c828653981fc5e9f034e52d9cfe6c6e222d50c0cde28d2206d19f723a42f88524e7b98b9832c45bc4194ee070df3d9081639f5b91a6811e9cd52025d2d259720de8c4529f311111d911e2c0a6bf4d5294e2841b819e5d11b043d4b0844f5d6d4708901197b5804f4756917588b02d97a18616f43e1f88658cfd8a863a01ec49bc3c8c833290876039da4b4f7d48d68bfa6140a6e4c22f6503d5b15a03bde980d07cf2cf6a4c6cff391abbd24d494e7201feac8c3c4668fcf9565a18622682be0884ad162589b06f95c288ed290569cc527e5425e31d2a5bec58b53cd7ef507840a4162e3be4184a3f496f75049f55a3491cf8b1a1ca017699fa910125cf93eeba2bddde17604152f9d368bd8cc334130f94bbc70f88b8ef9178332b48f8a4c38f60c21778c08efaafc4850e0b819e68b84106e6b1fb2c99cecf181dad6aadc3db7ee7a84efe5d0c9d42dfcd1696a2b7d0eab3ebb258768ed961395576d5e84c958ca2112623875b7d998185b5844901b6a1609297410753f91e9a94361cb1617ed19b9f8e272710a1ed0981102a04540872ce7765c9db290be76af87d0efb2b0aa928e85a54be50626b6181b9ba7526a7d45e4ae8aa828d91dd917ad32c319e5a0850abff1c4c8f2d690a0fa46d98c299e91131279ad55fefd96a6031b728589c8df4c3e097568eddf76c40a6cad256d3447942e4b0218958d2363b3c38ce5b6020df4ccfa1ce2fae3892b343aa84251d1ac480f4d218dc481b3d23c49036408269c428eb5c399b8696bc079662b0c143bc562840b41829860738d3ecaa3092aa46b772cbebcf497101da277be91c042174423a6a7538368083044c27453a7469c5b2ca7c35001de8b6ad60d2abc0b3015a1a2aea46b71af2f0509e61201830f9aad4919e75c0e9aebefbf58ebb7605e942c6b210cf9129b434b999f4074a66249975cab3c23e9dfb30db820d7e629bd8d14adfb0c32bb144d2d2fa066ba613c699c469b90ace8e6d38040815ea69e575b1bc4de766023fe3b2b842a1a7f77532539d992f880725cf03767168c21ce1f4698e0cf5888c255a297731c9756795c5cdea96f97e6f28a741e310dd0e841eeef5debeabc71081ae5cc119b4484b5ac841bf61cb3da27b553f1db4968ef64379c46149ae56159243910f9c30b14c9cd1e7196a4d5bf18950333e8c7622a82d9293bb4e615aed431b40d7cb30132fcc8b4fbc68b361599fdadd228fd701ca3f44a8538487150b9a543190867d0c3b91bceedfb0156f4b020500a02125aa8a7766662a41a32fcdd3bb21a58d94fb69470f37f7e43585b0bb5450c0c9da0b88e40df21e3a12e1b11f17e223ef98829854290c5eb9fe265f860c3ec79ebf78a6921dd743daebbaf151922babb4d43fb80164937be03651dad2692c996fb81bd8ef6cc7b697d9dc47b3282a07dccc95014ebd3749bc4fbdeef468de8a5e9653a27464b4fdade2cf8daa1acf0df840266549205902376a65f295018b5aab2637a5362076aff10cedfd0d788b503bd09413b83b476e06510b422d825c62c3870dfa33aa514e4e791f3b64e293f3451900e98c5807c9f844cd57607d89eebbe71a70b85197b206ee6c68f243809488281bd00ee03758334d8d1736c74e7de7c46c1227f22c4273ab16a1f4423211550f8e96b7265af7359478ac550937c86cd2ef4f3cc2cd6618451f9c5e8c01a879223118ff7e499711cdd1f0731b70fdd7b5739cd9897498f716738b038863a50b25f5c6150c27c5536615ebf828578a29d23adcf516f836e14772f93ee6bc694f69a845f91b3bd81333c97c907c8c885822518e06fd035ac09e05a7a3706d62aad3466c923578a5746c79041029f45f99f04d4fbd003bb6805de9a8576002867b3d4d4a6af0197734baadd2ea3e903aa492167acc9259f227f84116e45d529279031316f794cd529db374cb3e59917da750ead0f3b8e9b68de0436595ec1a214ff3d37dafcf1789c65998c049722c5a4d95db7065f97b69a7e5dd57ad1e6ab7f8af21d4d5f5dc042aee323187c7e4f1d82ce11472fbdb1e2f978027dbd16940dbdff59a1eca22147fc79cf57a163a8e877d8723423e27e6c6eb13fd7f68a5ecd9902e38d0b9e6866cb01c3bf9ab89bf3160ad2dff4e9bace58f929a8676e509a2bdaf6ff9fc27a8b78971447af602f70e669ac636e62d715b541017ae194964ae5c2dd7157a8572f2417c9b89af50bb9370e87986bdf96b52e2ad0469dd3db86bea32df576404da3f3c1f5a085f7be512b24e0045e11393be74f98d2660cd5c94589360f152af1cd8a46e4320d1e885204200994515879d601eaaac3fe8c518bbad5b893b481fa43261a3cf89e14b9e3b8a928055b8b53ceb6b5e86bbb20c76ae3d9cdd002078952ecd4b89acfc3465320d6c75a7cf815dde49c3cab279fe8624f8253f98cee7519998059f6d514bac27adba9fa7b99ea998a6ee41b3631b906c532d7be0fac9f2ed29c31c99e9f30edd132f8df2650e7aaa162d21e2c1e4e2e9ea81c6c085394f7b9d706adbf5d3f99a0147e9ac7e4070624bc6add5b9f21eea422ecc48ae550b27ff94322d174b4e4ece165ba3c02b75775a746ebd046b870be555d1b636acf23b9ba4a5d7fcc7c297fb6c17356de085ba43c28208d1aef0af1488cbf65337715646a7f23381f54024f8bb9bfe387c422362ce92af0e2e2de5ac1a84f75137cd7c66ea4052981ce51a173afa843b8e7dae610622f50c51756e567645d189fdf3a5fe0fafe1d2c56b11d531d62679f10c61df024814dfd95524789ccb24469ce1280760a9bd26fc0754a477b498fee0a81c53ff1cb26ecb5f33ddc867d0fd12a41c787a5c5919e0f25cbf221d67be6a12a4b05adf4dd869befa1ea453723a4dde1e3796f2aeb3418d33208b9e8923bbd6a925ae94504a98c48b7d6127a84cc4141a5161136bc54d0dc5069af5416d383a8d12783016990d706720707f85e8b4aa178a726328e9d5b003468caf3b8cce020dae4c7196cea937cd1170f3a4049db8906257e6808ea8e0cf16051474aa6d5db0df643121c991cbfa7c0291f5591b572f3e8fe36df0a72cb4b6d9e69db7f32a1b5fa21f30a99e7742c2ccb4a1c850e19cea7e11c394c178d5055d15c0b490cd15980b97082a1026438dfdf962f14b49edc5654f3ab2a972dbbd0125e324691b711cc37b9de1445ae644f02e4a618a70248b3232afbb332b58508b7068734f523b6cf7a49211e2be59f1cf3bb2ca8b77f891d76fd3fdd820ff9193a7a5bf067e39d55814119920b19a3a8fdf5434fcd901b8565257e60e404ee02617f3d3565bfcd90f0f16a3538d20c002a7ca78580f645b75514ea4bb5076de34a1a8e05657a89830f40db422bf921d38aa287489a3250d922ec8e620ab0414d48cafa09e28f54a24ba82ba30d37174ef53ab0249f3759cf608797cc14c1ff88be8ab0580b8639eb8fccbe8d4eb298cdd5c6bb4d6473024af389685110280d6b3a39d3395132b120482b223f1040bef91ba9a51f9fa287a49ba5a13fd591f6bd88d3743d68a7b9300a984e0790aba471a66ffec552c23ca2fa118d6a5b987910706cbdd69a486b7bef9da44c291e06fc063b06313186d8f7c3c418d42cc5c499c730f39eb731a9598a7df8b11e91490d8d09bc813071e63dec391613675ecff33c94f0610fbbd94c883d6ce6f5d82eb4338fc77e3e3fdf6d5e4e9d836a937312a15ce0d7796b42a75befe89cec797cd338f43c7e3d3c1ff2bc4b64eaacef1199d42cf57c90c8d43dbdc5d455624f55cf5550f3fc27edecdbce46a82f7fe622942acefc9bc6d7ff0c0afd7e2ace5c54caf23df84da38b09161969e8c811f5f36a967c9e3ea55f7f897ecd51a7599b4f4599a37f934e3427f4d3c9c70728eba7be8f8f9028b77ca65396a8a5c71f91871db25ece66f5f4fc4d6e053d8f413e7f9b4eb29757b336e98ea74e198c424bf5652293ecd52c057d159964a20cba62da216ae95148ac0a9fc7f06fb24d28cebc6f1a6515cff3c823ce3c51290b4379393983d223fd1ee9f37cd0579149cd6c845a79ceaf6d96ea07894c136ad6dae480ac07e5e5666dbe8fe5599b50558c21776380bd3883127bd8c744263533a5fa24f0584528de5698383ef85085c33139c9317c2cf561e26c89be12cfc77efe9134421ee7135c94d8628b34c418b05411479f8a33a5faf4617f9b57777a5ea278ac17b35ed4accdf7aceefc9b0775a78735e57856334bd2e64472f3363c6b722a7cc970b3a6356b73fe043cdea69607d4e452480bca44dff305439995eefa7759647fdf161b0c7b4e203ae1ad714ddabf67ffd30fdcb3b945ee98bbb9bbe7c82f94f6b21074a3adf0e94d2ef2e9799ed771e9ba07ddbdfb29eeaed5f99c1eceb1621bd778e6bc5c18267415f8caddf89c2012286bfce0d75b1d7716ecba06251465a2972ebf2392329162cfe3b96e0c24047ad51b912bff25b6d0f7babeeff55d650c14bfd7ab07cc049e87895866507a847d8f30d1a98e3cefe46316d86b01c76623dfc76c8f12b6e4238fa885069e11f672f45eeffa91d9c8f779ef1788e27e1df0142d6575326d3623231aedffc5535cd77b52dcaf0396a222287a9ec8ffe01b0aca2370ceb682f2211441d99c70b6d4bd7f27fe84e2d177bd7bcfe36ce9fbee3fb1136750bed777e26ca96302bffb9ec7506acddad6ace1a6d3cc9a35cb238fa0e5f1850b5fce3557de239226614888898a614191b34979141027e02c9146e825411b13fe98f902ce0e428408792e4fc1c9f2469d2ac8d00a3206196974e9fe66f7c7f31596a5e5237b240a595a0d4ac1acae7a66387966e5b1362734ab2bf975df045dcaf3bcc5988d62939a6058c3d8e7dff74d30ac611873a344886be8c8a51131024274a402b18542624262f2f2e87a8c6c7f934878a40271a344488c687e3c5fa50dbdfcbe3b12abc2fa0eecb67d31900868439a529425b3d6555859ddf7816f5b625465456330269ecb23ea9ec2604cbaeffbe6ac9d6d09cdf57d0814ab955e61dda615c6e4fbe073a254ad8dbec21ac6c21a86b1ef0b6b18c65c9fb4739c9ddd765fbbbb7bceecce53745dd775b3737767263289a6cfe95ee7eeecd2dd6b6766767707634176a89c6efb6e1d13cda75fdfc9cc36afbbdfbbe77ddff7cd2c14fa73b9ac9cd057adeefef96466ee84be3dcf33a27d9e47f43d337722d3917ddeed664fe8e3c3fc7d9e6ce3fbf9ece705d5cfab554be8d03c2be4791e1da5363e77f7aeebdc3bae1a92a9999e85c215d84c347b78320fbdea6ee544ea935851bcd9f9fc1f9d4fcff33c22666666cff33c8fdff3c0392de3d073cff33c9b91e7defff03acf3dac28d2ea63a2d9c373cf9bdecf6fafba572d71b7799eb7867d8ee7795ef53c4fc9ddf0bcce465d22129d7be7eedea921474ee75e97eb72eee5d49023a7f39c1a72e47877c7ebbcceddddbdf3ee4cfa72a7b6ad7aec2a3933cac509b5867b97f32ea7061b723af7ba5c97732fa7061b723acfa9c1861cefeef8634de66e29bdf33ed0f5a2151663201893ee8576b88e3aa52831295a563c55575655c5b2b4d0482c8915e4f323036b4ce94a46c124546df3925268e41ab906284868c7acc7078ba1c059ce63636267cd28a50965a5d34be2244e268331f15e36c4c34b7229f06f422e3f90ee8582e72098211e5d7dd14a5f3c916a1f4d79db73b7aa575de5790ece32b14e43d2e4c7608d85c5a4f301c164566214b22614d4cccd35726b4b6e493bb8de9a503cb69f62ed641b06eba86e2385a6d7f41a1e3c66104827336a424da85966464da8324ab3165454b7fcc5a5eda6bae53797f65475cb5e5cda54d52d63e0d2aea2bae52e2eedaaea96ad2e6d2baa5be6e2d2bea2bae52d2e6d2caa5bd6e2d2cea2bae52c2e6d2daa5bc6e2d2dea2bae52b2e6d2eacb8b4adaa5baebab4bba86eb98a4b1b03d52d535dda5e54b73c7569bfa96ed9cda5fd4575cb545cda6054b77c814b3b8cea96a7b8b4afaa5b96bab4c5a86e33b8b4c7a86e31b8b4c9a86e2fb8b4e154b74a2eed32aadb2497b619d56dd2a57d46756bc1a58d55ddda2eed0c54b74897361ad5edd1a5ad81ea16c9a59d46757be4d2de40755bc1a5cd81ea96824b5b8dead6c8a59d55ddc62eed35aa5bd8a5cd46c7e90ed4366ad835f937effa34d28fa113dbd857fe38f2a3714d5862608241022f5c89800b10b0f28016aa5071000b2ba830e5968294063060010a4800020e8082010a70020106608200a204000a00964a20610425114200e183271e74e08483264c36d060bc4b32c0e0022549922cb0211d2139520105468cbabbbbf316a28798b28d36ba8d968ab9bfd79d06f6647ef77d84950662281379defcbee979a02cc2b48e91684bb848ca2ff2dde9b74adfb93fb826ff06263523c9bc2542cf13c3252c9b4598d6305b9df0f6dd298e725ab594cdb2a54b9fddec9cbae1b9d7759f473f57d7d53bdfc117ece592b4a63d5c84f2e7cd8a48dd6354bee47c5a292c84d5a758aca7613c62b33b9f863c3ddf13fa589f9e1ff1c7072802a09f20a2202021219a50d08e1db51d42329904b21d43434386643c7810e131349b15cd78f4e831418f990f1f457cf480000223087cfcf861e40704ff14fc0f6b2bb02f8a47441b410448221089888e88220002040908519020b620408408b14048101a2d8926a4564b52a349208112096a43865c304402cfc31e17ea2194ef44ba6ebe5b90c890a20c8a883c4d2075e70445628a4c51640228a30b1815f912ca77235d47c59d6f04340a75a0c0df29303255010554472aa802c991aa232456201d5d6143c2c2025b164916689124690b2549b8b8408915061774910106185892811777c99bf17ea1c108c6061a84c16483ab264cc4e0a0c9184e3820a30327703ce8a08c271e98f1c1933340f8002b041032204208682889a0811194d22061840d94400207964a5003004b595000b04600a0b0112500710410458e0902e8c0004c6883000370e304026815e084370c5000385030c09c03a0400701078823010890430109f0c00214b0c58005ccd10006d491d2003a529062e796c207a6dcea50610adf5941051c0b2bc8e0001666a0e2001aaa50c9d242951a1ed082162b0fd802012b4e2e40c08608b870c395087079e14a1709bce0050609e0c004430e31306189e1cb9d4f9bb0e8806bda41061c981964e0818619c264a1414c0d59a0b4d4d0c3162d639cb690b1c1a9cc0d36f8c0e506335db8fce0a54b140e5e80c80187209e7210e2cbd3103a7c39b3830e4480d9010d0f6072617828424c1823a0c41cd103549a313da82133660265c820e1439935667c48e207331488faa1024044b1090208258408c2024308b1c49921da107186093444349143e34411b9278c28028a238c88224de50976e823604de6953db8b0af144770cb59344111232314547004c91192cd82a4244a2ec020832577d46003264d3870d281074f3e00210411944620a18425004009401401983000029c500003a07000042440010b604003a4a4709ba2c20a2c38804a95161e6005022e44e0ca0b12808129062c4d8d6b197a86a6a1b3740dada5b7b453dbd0373497eed25e1a87cea19ffa4bebd03b3498e6a1c3b49886ea1e7a4c93e932ed439be91f3aaa81e8205a881ea2cf34118da6735d441bd147749a58db400748e5f3db7e5df03ba5ff7bf01381aed755a8dc29ddbbc0fefe16865ba1fc39e9eb6fe1ed34d7fbee4b6cc1656f54ae07de1c67b3d96c369b4d4a29a59457fe7dd3a960a482d17c158c5430f2e20ca83be4f294196c5ccae35364bfdedfacb838658a3f089dc856faca96b31bec9fe24dca7ee3ae053333b333b3333333773a5871d74d766766eebae7ae73f7ced9d9b99b13092396396d8eff9c57d8b123478e159e2a030e381f04255127250fa919d58f35c5c645b174834428aba494a449299e73cea9a933da14c599a2443690352997b8a8c2284a92f644d82f9334d9a0d1cb967064514654196ba8c018e2f6b84cf585d4a53c332597f7f425fffb66dfd366cd639a3f452699d734be8d041ea9c87493f9df6cddc86307fe1229edecfb28b3e67dd3e8b2b34f6c81041e95442627de38e37f5d89b3251c55c3429b20fff54bf3ebf357f92f91a9aac55907dff77f4fc599d2eb674bae7fbdabc78631f95cbf444a291ceed266ee1903c5f073d92bb3e671d67cba220c2eb25c0f4a1e71185b9491483d4e0e430b2fcd9def69bdfc4a61dfd4afc6c5a57e98c8c4d474a52a8a94ad2b27597c05fb259e87f53ccf7be21253d087df34de786be61f7b1ea59db9381b413eeca1f407f7d6746e3fe801d9858aa83454fcd0c595623b95e924ae1cc2ef6f2bb6e02e1839a693cc4ef2b054124d029f3c37d9fd6e61d79bb027733a7d2394ba231df71ae7b3ace5f4cefbc029394059baba653d20112e92df31618532d1cda9cbf61dea896aa8eb346957234e727d4f100c4399ec1f8b6fe7248df62f9365792e97cb45eb64e2ca2a9db0b0b0b01a8b7e7d02c13094b9aa8bbe948934f4f2a97c1e6b2f264549ffc65b208c8a4d66ee36f5584fd3794a79c8befc59a59bef6f9c45c5f924aba4d4d02f99435686269c35d9535de22cf62c4536e2a409e34002512130e8a78707f6469c247b17dd41bbbf4929100c4399ec9f8a4de49589b3cdcb29ec2c4993bf43149508c127e992469c249b8986aae20a893723d7f5af9732834420d187b3c450bcc9c498787b176751795d4fad1117b9c4998b612eb1c9bc3723f7c65946f6d64d5cf9df7472dd1b67dd8c48ad6bc4d7ec90426d384141444e6611071c4f23953858a08ed448679d39b8913a9809e046da51255181a7917a5a4f1880a30b2a46262c3ed6185922c122d4441d4ae046ea22838e0dcc31d217951719a81a29f522aa0aa79156aa3590801a29ac8a306f9c461a0be3ccd59b3284c88d3484b2c269a43d555a5983971bda8cd4a74a2b6f0033d566a43f55daf6a2c50a6aa440555a2639068e54165b231316a6630c183a46352278995822c1c27696787a625433457653278bab910a0505350b0d6141868badb1a9b1b49c5274de2a7ffe94190265a2e99dc8d414b03343d588e5cb1a418944ca0b8437f082b3366b73fe1499769041c3184ea38bb44e62ea08ac91ce9601ea8ca7715a1e5b76179f349ff2146fde688db4e1ecf0343a9189026a6434b49863cdc85ea8c1658db4ab9de53034f084d548bdda5926b6620b17592396971458d3ca664b1dcf160b6c8d1dd8d9231ba6781afdfb0ec8a00b041f9c48af07c16f4e127611f9a078035d6e6f4d13c6840a757710743d88f4025dfcb2409dc4254e9d51c575f166b3048d3450c1e1264c8479b1dcef13dfd288e6cb3a21e850d0f1191599e01675443ca1cb433e095c354a8309e1711ba4c72d901fb7444111ec1087ecec7dfcf80181f511410f2033213c6a6e8786b89515b9dd51c4ad9091ea3688e5d1e74a96cc9a5ba0fb592512897c7aa4bafd71eb73ddf6b80def74cb93d434a9b36c01172555b754092f91ed16bc605a2592e6d6ddce25d52de0a20d24136fe2512d0e40f75707b5031813ff396b375b22bcd96c4be4b741a844d6a407b127e107defd810f08402108892053e2e1a30704ffc3462012050122a4469380c890a22213185160a40224478e6c481624495282c10519dc25e3061a30e1a089130f3a7802c207212889304209242c410140000410c504020ce0040314a08b1214aaad8b94038049c182ae262500cc6d0164a634a08c0af24b8187156a2b619ab48069720aacc9df166d6156a86efb884b59a86edb884b1d50dd76119752a96e3b776995eab6d15cda4275db445cfa80eab6cf5c6aa5baed212e854075db425cea4275db415c1a81eab681b8f44a75db5197be50ddf60f974aa0ba6d3397c250ddb60f973255b75de6d218aadb26732996eab6c75cda54dd760f9736aeba6da84b5b86eab6c55cda3354b71de6d2a6a1ba6d1e2eed2cd56d83b9b46ba86e7b874b5b4b75db3a5cda5baadbfe72693b55b7fd7469db50dd760e97f60dd56de3706973a96edbcba5dda5baed2e97b697eab6b95cda3854b77dc3a59d4375db365cda4fd56d3b5dda5faadbde7269eb50ddb6964b7b87eab66bb8b4c154b79de5d2e6a1ba6d1a2eed30d56dcf70698ba96e5b864b1baaba6ddca5dd4375cb772eed31d52dd7716993a96ef903977699ea96ed5cda3e54b74cc7a56da6bae53a97f60fd52dcf71694755b7bc75690351ddb2072eed20aa5b96e3d216a2bae5382eed21aa5ba673699fa96e79cea54d4475cb705cda68aa5b7ee3d2ce55b7ac75691751ddb21b97b611d52db771691f51dd72072eed34d52dcbb9b4d554b71ce7d29e4075cb6c5cda4854b7bcc6a5bda6bae5ac4b3b89ea96d5b8b42950dd32072eed0a54b7bc814b9b4d75cb695cda4a54b7ac814bdb02d52da371692f51dd72062eed36d52d635dda4c54b77cc6a5dd4475cb665cda4e54b75cc6a5fd4475cb702e6d28aa5b26e3d28ea2bae5312e6d29aa5b16e3d296aa6ef9ead29ea2bae5302eed0b54b70cc6a51d51964a18810a19618a4c0385301207ccc1085c94c025acac586a49c90acb8c0fe8a787ddfaf77137fbfddc7d7252c7493ccfef7152ecf93f4e82d5245a39097c7e588c87cebff99ddd9dde9ddf9dae3befbcd13b937c7ebe9c9c14fefc0ae3a4eee7f78909bfefc702f9d820a11db2211eb31e3e20f8c145f6878be48716c8060971917c98ddc145f2ab95d9211e5c24df6567b6870f2e82808b7e70513847e8a325046f70ef7c6e3e746df5d33365403f3d56640f40a1191158055cffd9e38621c82008be3b7844d63e6ce3e9b072b281067707ef6024215c936f346b42180909c968225da6f20682408420d8b91e04c5255cc27ea3b9c112ab1c6067c8807e7abeef6fb0ef6f3c975fc9f79c844e9077f0e922f2db0235e9a16fc810280c7f5ad18822b04cb907082491be9f3d74a875228f1c4ff4ef867bcfa7dff7cdceeb3a6f4eb71fd0f7c942300462d9114a248df6830c30743a5dd7d3764f2783a083a1ac7560083e4f36e19ca1ec3c91e6855606e8b5e7303dd91e967a267c61044366c92c999925b364e6efe9f1f1f9f9010262212166e6f7616666c92c9965b62b0b7213e4e60aefce095f96b649abc08856011f3d61da74226936224fe4d195bec2e16433f7a4311294ae4d6f9c6d2d1c15a564b9137410fcc0cfddfd7ae715d40ae378c3f7e5ba9e573d8f525707e5d2939dfce4d7e5acec48a4be0265cf798393389136f4ec2b27b0cec2ae14abb4b4aa566b85a083134b6ba2f181adf9665e4d2bf06fd3ca65acca772b086a69b95c1de875567ee12227a943c3d045dac0a38cc2418eb9f3a5d60eac500d23358e6bf3d34469219bbaa184e2ee88229c5ef0d1f7f367183c31506792c1488d9bb54e72a58b4c99a881099b6df9ba6ed7755d57ab7ce224d95ab62f5b29054b279836ff4a27e9ac2e327f5291c4c69dbce5e562d157f24a369cc6126d17e1f7b7bee24ec6401cce09c411e1fe65681cae03c12f8cf5f490c964412ebfd86f8f6060da1ce2a32b9236e9874a12063e4ac2b4f936499b4fc5bebaca5aea22f33bf1939fccc9237927903b5f5ec934241288c5664a4581bbb2ea4e09e7ce29dddcf99fd7754f9ba58c755d8fd2446aad247cd44eb491b469158b6959595ddd97e5d13949def93fd2ba6e8f9ab07b29a5c4698562ccb0e6b66d20b8288876828fca740f4c9bdf4e5dc79d6dc4937438a14c82651a4e5ac349f226739cd473e7576935919eb8487e895ae2f670e7d49a35d87d59394eaab0b55aab2b623271678e8be6b3010317c544ea37e600e74f2d31c29bcce5e60cf3ebb84eebce032ef4c8c3febeaf6777cb29a79c5e85334ff7e979288fe6f7ed5726692e0508ca99c691c031c8e5a90e7820ce9bb0839801e68d335371d87ce1431d5f48b065482e6808301b000347ca0d38fe812b26cd89ef14bc9812238e1c3b5e9cb1f3459d26a82b78b89ac30cac2b92703a6850307903079c115b561c78c2ebd47e38a18a34e83c3103163f40f91cb20f813a1abaaeebba2ba400e38501e99b31a8cc5c5dd7652a33767290800b194d703953070b0d0c31856f8121c7240386902cd8042142881c5f1c088c1a0f12c82185a03d5138b2b429734689ab37ee8120564c2068524b1a566feea06163ab8c91d11b2a36535084e0652a3650c83ca1cb546cd0b02923ed0ce94172d18137f241f9619b3754312e6f5c5bc891f2b6bc19837e3d2e538959e2d2cb5462d208d1c1853b45a46933831923d6c861f485c75185c5962f6e8ce12285865167bbec4ebb42cb04be2d5f842133a9c4784165460dd7130d5da6d222cc7d5da6d202897b7ba32e0ca3967d6c847e792a8e5518278b2f0edbcae16a035dd6b0628d71d28983c5957d5371a6be08c3e9727dd3f5bee9d20f7ceec1e3846f078931263227c021ecdb5dec56e13bb7fb5825e2c19e4c97074d2d806fe5bb9ee73df1264d37d9c47bffe941d3ad12abdd7f6114c19dc738893b30dbdd93bedb2332a4e63fad4c3e99dfc4e7ac83babcce05ba3b0cfcacf5afeb5ca0f884ef5cff2a3ca5bb4feb60cba9efbf8f69a44d63bf27ce963cef9bc6177fb5209bf0065e998f2542975113e1105c1c2da5a858d162bab245e3d0927ac24a85b3a54fc9c927ce48e0d1d634468d4c3d766ed6feb6992e428544514d59dd7e6e28cf93603a2bd75abdb50667e6de9a4e63cdb937994576612c293d89bcac89227a0a2594952c2325a3726fa454d59a36b989c4e3d8df663a89ac52a68bf4f3982ed233694796813374fb659544aa61f5d41c3797cdcdb1e6c471a9141d32edb3c7749996c2658143ceed3702967ca8040c1d28a95388460000002011400083160000200c0a070422911c04e248177d0714000f65783a64523215082391381848510c84530c432110c22086102619320ca2191b5000fe119b2f79ec20e98583a718e11691fa39be5f84663d1e7d5568c5c2e90c46c61fc91f537bfc407132d6e8571cb37ea078d81a85a8fe08d532b182b5998d69013d9c71789771c5122b9c17527f21b526f5b4e6b465ac4ddf395a537c3198b9296b86b57126b6c05b3df56f9fd74c84817f9900714e928259802658f5a4ff6f9541ad2b84f154e13037523a695e9f2754a5deec90a13f335a901a3be75bd0f54a724f810c69536d4d04212658b9171312f131e35024d4432e96ee8ad01704a843c4b94703ab55e0588fa1e8a38756627c2b9605bb7c5a310961b7cc6c6e78494891b7c06226f1df093469c07ccec0b16e634ce30f42f44c659e1ddcde70e4e3946f8b495d6ca37752f85236b222e838da1db0239b2390acde82ba8f431446d84acb558c7d2889d50fa64209075818a604537fd45d2ebd4fb748845fe54049309847ada679d79a6003ddfe933419ef93dae1aa41667381343252e3fb92ce76e1491e6b7bacc8b7393f999f6259c70412ffe03d4f3515225a586a51d6feedcf6c36189bee722c5839181998b5a1102b6e411e603f7f0e7af2cdada2827c62e90268448bf44887012665473272323d353ad65e207044bff3b76984da6a5584a625ec990dad613f477212189e01be8c37d06c80662b6d2ae4e82c990bd296c850da2f6ebd3ee6b3229660410843389ce78222091d201fcbce0ae0a4e77d2e330c15cbf9babef1e4c62485c2818e097c08ae0b90b40c5a02d552c262f557b55b37ad1f6289b77dcc81ec3d8c4dabfe0870de6b9757b0a7cf1ad492d8001ea9ce71494afe960758a8ec0a4d180334b21db9e049b1be4dac63e01bac7ad1c96e2154ca89d4d63d0775dad7ea88266ddd9921ff03235860c870cf35dc2553f666e9f5a1a359c544021f178fb55e4fb2af9a357fb5049bfd32d771df33cf334f0fed50f2e765404037058271620e6312754c01d375a736cb9e2d193ea3fb8e1776a82a5f2f9bc248828593022cb73661060810d2b1ed123fe001af3340a59303ba729ce3c9ca6ae97c74964e9d87fbcdeedaf805999ba5a38d6a3507d9a0d81f747e7a67b1cb4309e6bd7709f4b39176940d24ef7bb9c43cc8c240fcab1be31190c652caecbd84b3ef5f8c071c7f4338422e878481383f05dc987019d0232ac0856628df9a39c756dfa50b696367b4da19a9c14636c2d64af06ef86828383b3810c10601af19e6e9e9a52e632edd6502a897339fe73e4ff7f794598e517d9a8980d3bbf67959b26054563f2c0a90fd2e6caba6eeac97811db36c4df002c306c30c50868753b90adf445a62d87c71b5df90056e6506f3fa22b9642237c7920c04f2ad66a1dd70e87efc0e440b2b37748938e61686ebcf81f62ec716c6a449420785d09109fcab8211d1932523a47fb19207ff60e606f1f0ab329a71b13a23da45c5472f9a860dc1320e71daa154cae3e1ed0c26f0cb99c080e91e746fa3f99c4781d35c713a4b8270a16b712e3424e1e4ac969caf2951c96bac26d3189f5db3db9b95d41d4b0d9c465954e5a5a369035256e239b3153a57238a030dd19885b3501f60d45fa02b4cf21e2c4079808dbb9515fd47f4894987c633d1ad70c06862f42decc13071533f2c63a1c685854cfd38493f6bc9e5349b871eedb7e815f5e1520a3f31f6edce2280bc18f5922a8a170b152a5624664c55f1bd2a8c718590d154b03d03371f2f34837e1a926e954fb68cdeddc55ad52aea3d67062c13655f44d7978a1483347967030a30163b86a010ec9a4da939b3b4cf66d5ccd293de65caf3be3f1e2343a37fe7fdf05125815cac8b55ce2a431633010d481c5df059d2be3de0718ec276d215b6321612428f87c7c4617cf6b56222519974dfbee97734e52b1a3890401833e45f2f9fe06046cf8b81ddf2be892545fb65119d7887e95a257dc4ea2640d781c59ffa767c1e10be6fe4da8f33798c84612ffcfe3548d501d4bf576bbdefdbf43fe1f77dd4c718c8f755b895c6365ed1dbad8381a20db3ce8325f4787dae2bd8298b08eeeb50c4b25ad4e44e2d97f771f81680b6ff3c468f26eb627d0fba81bf34dff129a0facfccf1b71d8bf35acc5a75775c4a98692999208fb8f27e6ac4337016a171b90f9466e4f8e9ff72e6f9e3907eb2f29c5b817f8c01a5e11bfd238ffda9aaf282e86f70e87f6e79fb036d285b5403785e019e3e9f019ece2a6c0e59f8bb2016025be3ee1e29940560bead25b3b381d3919cdd91103a32f03a6a3546e8bcd9115f960cbfe89ac14a099df3f3e7f5f3a005f4808a3d26d2c1cdb5f0fe3a6122687a55a1293b2d72b80ba563dd0b9bbd481d32572cff070b09c5adb6e4aa242db23c49688e19cf0c895b090a37c160276410ed5723ad5afa835b9f3d1eb8a3df8691871fe4795b9968dbbb4decd698d8726e398ddb84634e415354c3b88d2d3459d23bf03de538685e1807535aaf341b2fb70919a90adfd36d82e1a88a089da0996d824e814511a080054bb6b2d4ac78215e90654433bbb1b1fe4ee9c2064ade943968e0d4cb630296560a2c4fca83eab3560828230277eac1d61224ac55eee676f298149e6772b53266bf0d6b47359bd004df6a2c534fccafec66bb18a65988d6ac84f566e2a40121162c11310c0cdfdd645f69d3c7701947a82116e12a8a94ffb363bf0e9b30fef63c3acdd4615e567fc8c6939fd8e3950f0fcb341080306317c8d97d6a716da352a5de4ff3794eaac4a0343a42fd5db26c349bcd80bd0124260d57952ddad545980fea828cc28f0265267960d8f89ae3bcde8151e1812e8c5510a2c4fd21aecd6e6dd6234482f83a5c1c0cd7ba0f6d7a506b8a4ce3a609b4628c9e8a2383943995df99fef146ce42a104db1061e4677dcda006328ee7bc2771d29ece5f8971c56300fdf1d3242868c845974b551ff2151a8cb280f0cc2075b8dc2d8a5806c370d115a20a9c546f3e9d998b7c90fe3306f558c6454d207ae51b0a514e579f05553c259a83217f068c3997434c6a307195a4ec17f5dcf04f137ce10297df7114f4bfbea3baf0b8f25042b84670a94d90bce6dafec1318c481d7d25e4b155181af2aab2b8ffb1f4551544d5fa5e16c8141d30cb5fb5a9dd00fab5f9bb37bc658009b62bdc7b0bfaf34b4a4e8fe239ead0968fab92a73050ab64d521b0e0a4656ec944bb35138a1a3e33f369f70980feb26c69a124c407824ebc035965d114ef6258d81175d3ba17756db5d69e5a0b427f039c7640f8541b8e12123d012837a8ad7e6955bc2bc56bc5826a4538bdbeb66114e104cdb134b3e0e15d7f58978b85f5c5d556515b54b7f52c67b338e3ae502e93c3257c0babe0b1ddd8e2bf86fe995403c4d8de3a876b92a8821afe47f812138cf3252ec9be34b3e0a6e07f48f6d1b881316c97b81dab215dacac75b80e40552c586dd1f2ef997adccf62932415d645de4ae27ee25beddad8a34e707de5948c2d559fd4b974a0644da4b64b54da45f6288aac52d9704c38cd364e5a46a25252cba012a1c8c4fae605165e241df31d11be60f7f0d71e3c981f2298dde1ae60495fb5b2165a7580d8f3b32f35a587e4034709081a38ce88be45f34b31dd85232b2b3059b10b22f9c2c50468882d0ae025615cc473d4a14a61076380000701b35e8285fa602dea80850502bef6ae6535645d258bae7d3e269941d8126a813bd24bac76d018d041a6b7258413a49f6b8a6c3611da3c7547a575a2825942539373cf188d96a5e0852e9db4acf01b52c2c29ced8f9f71bd360416c4fdfa9d4b3c152d0fb220e2b12f1bc376a197a4093fd558d2adc3e839fd6050f637276a83d7758df9a3b784cf2492ed6b2eea56d769ecf287db1c2e9a0f453c65c746985501fd6ce3498275c2bd57d2aa3ed12517af954593c6169b794c812c29515866aa7465b4569bdf04a80da439d6e9f6ba7a21edaf63a7130b0474ad4a822d850f8d1553549038f049aec7ad9245b456e8abf8c9eed2c716794a016348594e0fd48f3bbfcfe84bf82f65e1503cecb947d8cc1654278ffa7237b67d4aa97853d04bc9a736629e693d794f6e43711d02220c05c2dcd5fafa8640181540f2f49453ce9419b8dee6cfa512466834ee9e7bb937d3102a504ea62803561aa2084f4ce4ce52010c1a6ccca3b54f5015773f530ea1537f5060370584242c1d845e41a20c709e905e87bde3b6935e3b32c46a951ae533ec7a01a1e2873ab8550d5801471ed35795dced4aaa8c1c312a83251d4a84f276d6a94b9aae182e0b7ea72b184eddd286488663154d12e1697b806e8b8a90ea150c392164183e32afc05733ed8292420c87173ce394774ecd0b032405bdb7abe30529106f868e3a146836661cf7615241912fd2b9f5bfa5c1852cb3aaee8b87023f1555d2832d6db526a460982282923be9d598603f25cf6acb03a71b0d9387f1d655a223b3c47261a677d1808fff37cfb8ff39e7734d73ac7a073a90060cf4447920822c10f4e11f196de8277df9d4e20e761508747cf8d63e62461a431135d9fc4f2770f54674b64a7b003c8bab13812d367173c5d96d8395aab241f10efa96109f9d3cf674dc3474556c461dac57f00cdce6678bc99d842ceea7fb5dc89a35e9a0570ff5d9c91d7b64213f6dc672c1656100a7b3223516add60839287c3e6946b40535ae799aaf0df1a5d88dd611a8e4f5684458be52b6e15917d98e8cd1eccc47ba0cba9f1972d348dfb07c5fe0646e88685b68868bfbf3dc36b261c560ff497e5ddc71ba757a1d060dd0d3e9d8b3c9bbfbf354c460cb8c33d1571b47f97c1de8a959e53f3410a8836d7de7b6d3ddc6aee4d57c1df53fac4ba00de3bbd88ebdd9909f36636db9ec74b7f90abb9935e7296dc51834185b663a0cedf69ad03d0540a951c639bff9f224b25f876a4cb6a7964acab129a6853a2b54eb72897925b5844f589a897fafa40238d3708ab591f467fa2b952bc0e56f31b07efa71851e80e2b47ba2ad49a63265faf344b014756878b8f5fc3c91c0a09954687a2d7896c4eef4bd18951a43253d6cd1dc3ddc8a0c764afa8b65a00087fe415b70926a3a995b113ade89b8a59aaa42fbab28302538926a648caaafff58e306434092d04c233c7ebe0e789a0f1d6d9eae46dd8a6325f383d36e48ef4a6131a4fa204ccefaab8b87d7bb1c91ea08ae86229f364a39b68a58729e0ff2d352393412a547b192f4a14cb6668ae8f380e12d19325182a15add83bab2099e09f3e2dd5275f26c8bda0baa5a28b81721463728ef7cf2c90101d540b12c276948413f0474905d5e7206a8215c8d8e559865a082bbadf0d2bb8500fcba31f8d5139548bff03045de92c42236892de493e813caccb82980b031dfde3ae48ef6bc49c5c6332468f4b2105689c55132e645c9c88ea186d809a54b5742d11c32ae7fc6a0d3038ed887ab7be650817ca585c100af89aa50e25fdd3b7765e7cdde70ff32ed3c32330504e7cf6f983f369018d24fe3f7013cef17c845e5d8dacfa9ba812a72b5d9a0fbe3d9a5d47bcbf67b43d2065587e33508108bd4c53a00046b63ba709e6ecaa32afe5b6c87e03aec259f716a45406bfe56adc8f5e329f1a4b80701c4e9f6ac2a881d6dc126891634a0b77ddc18f214b347d6c692916b5dfbc8414e7c64399628e65162618b1e88169e013c8815d6ba21f9790854513cbe568f00ae14ec9bfa2442ceca8c6ad5c8824ace7f569bdcf5f2abe63745ab65149bc3fbe6c0b5785c116ef208ef017d02f5aff1636cecaa05e09e5bb522e0dbe0d1ed924dcbd121262bc9ad2a6a8748165ba15c542cbea0d5fe81d826c4d536879d1f5fe8584108012736c235bdf7927a704d5bb62d9d947a81058701ae58433890a51cf8e217fc312e5b169ded94efe6b6481aae83cd820cba68c406d27f90f8891cf5c13594748a56c67e26d5fde3d11f37910e98890daa9d4306f84d6c0e983429a0a2f5819c677b2e65b0c472a7778341b25f7f42443d38e0736f00be78c809c5e7c8ef3de915ad25268be88e178ae67bb5da915005260b9c24daaa28b84b517ca3a55d0cba7e0b3c546451c25141474f0aecfc3d10c0b37087c20f050ef77ea44268241aefcbbed82ed3d95871d01df3ac4ac58122415b720ca3115305c31c708ee8df707fa8422a20e721eb102814b232586ed9b606b307e9beb259e7f1aee62038e737fe2b99697c188aa42a7620bbe2fb6c04501a51b233698b5e5f515f191774ec0e9c028ccf5562ee5c4a4fa0f64fa85c186e9867beadd179e8124f63f7b85212459b236622de6454180ac475b59109057721a0826b8d51adaf8866fc028aff5616480418e4ed45694462b7eb5e963666ca921b3e5418354a3bd6ea488282b83b5826c9bdd8e2848176d789afd4f7e62b9fd17e2375203e3fd26366b70fca43bacac9b7d0560c57d5fb5fa7314162be7fb4ec70386974d8e037d22f7fa70e2a7df0b885d1e9a9233508e3012c0d230dae0d2baceaa3be857f4576969d40e57daf6240f54d9f37bdbab66bed2300e6574b711c8b703c6856961c039da44a855cddd5c8e8a7bffc5deddf1c7523400ecca6bfb749aa214f524530011cc91e4422f1e8584c75967d9ef5b8f416ef16af03942ada14299c3d4d70a59bcb40b460a92ad0b239513e7d48f4ebd974a4e5479d1eeb4178d41ee417c1d011242c4e17eacd8249955a4aa9f7540bc5b8e430fa8d8974bd850cd48171e1f5024996bebff4f338f0a024d13262ab3fe56807bfa9d4d5979a5f0b42ab3b04d4f19c474e80cf1fb2e8d9e612723babb57e66d6a326a0db6a6e18b48351d6fa394a71ca174aab190c94ae5d589f04c8f2d9a12b18f53150f2899e89ccb27ff91ddce3345afded292707144e392b581e4849ed43bfc051babf6463d175bc994434edaf9c6789fb9850028b8d74f431a033c8e6763268b4027f1c9108f6f218414ae1e1b1b762841fb748891b8adb421803f11c2bdb12c6a0d42e5d5de84396f6cf6d407092c3bf5c406b1bc1cecd31dd990da0f075eca171d3f0ada99d16145efc1e7da565301de1ed10a915d77e2ac5efe45430a240046b0145f58b75ccb4d98960109ad9263cfbdb598f08694c1011ac47e0fe1290b203faef9bab3fe1f9f82f6461ae1304f4bdaf6e6f949130f250fa0123b910dee0e62d0d3e0dc91a2cbae33d0efcf4c053d16d1233bc2091764ca7b85f29e8e14d2747d8564829f99310d28009c8836e39e3af0ce757ce1bf85c7238f822f29137dbcc37d3f0342207516ba79c607eb919e97a013257772e171eb84da9212a364dd1bf54d7d4e106c0c0be94eed395d5aa682a1f8b8b3421c6b59bfbf11b8bc8536d05bd80da44fd002548db3d605608e0bd664241cf19744d676dc5125effb00810ef332c47d0cb9484fd46715e40f9eb6817686a3084a3a40070ef204b109a11b326294815931e4c86c9ffaf6d0a314788d92c93b3bfca1a1e1733270a194cd274a336678e8833e0bc51b77e792cf4c0e9dc5cf643f2f5d03a93b9dc4025ef80f9443763b77f8ca09c5c5d2380f3f05d29c8c0df2d5a6e49ca33613865edc8527eaed125f0af8cd92d5c0556385ee6ee189796afb64d8190dc57d4f4b8b5f93dfe4d7c07231967daf15e21827c85cac6a977058356ecce0d1cd92f60b0700bac30df9b560702215826dd6d86f07270bdccfd475a912f6e0ec02a5935dfefb1138efd8a745008b223b1886fc8015d5718d48b860db05955b1074d92bafe5823d51344650853d86f4ff773c46aab645fae90714743de5361d050e21d11f9c4a5509ef60eabb162a204d1fb1243bfabd86b7ea3a1da566e56ae370b83acbed768aed29b3b18a45d1a87e23fcf0c550dbd663f581f34b0fd8c8c6e296c0ead83186c62840143cfb76bf7d3f2a70b7cf35b61311760ca9bd6311befacd0281ea17d8a0a6aedccf4d9255c4f529685987d37918fb2e5504d174c4ab5ed72a75d7e4a2d60677214b58f653f960525addcd7a4eb9603793e4305f5cbf7bf91e8b57e498a70834cfde331112142f6e9e51101fae817b11ae2584a8f8aa06213fb39b55027257e7c801c87fee64ba66701d1cef47e7840ece7d085265252be04958ecefe3dce6e3c29c0147d778536f985b43f85f163575d5e2b783386b5e0aea373ad7d5e83c5202e9562ef0a4de9107aa50f164eacba26e784292ea16198d270a14ed60506af0566f77b3cbb48a4f75689ec8861823376d0f7873af70016988a536dec84c6533fa55ef49e277c01b5982332113856c41fb8cba8b2817ea879a3d2f34aafcf73255dcac858771cd324fe60458d3e60f29dccdac5c9a0753a2f7a60238f6bd422853cb438690c929191a91a85529bf1992bf750c630517988ef7400ab85d6d35119be8259496369008aaa0a8af84db509b1c38abdc6d813bfba97c3f62237dc27d85030c719cce17a9fe97eea3e0a3824c159450c19dab45b7a648ebafc68328c6ac55149e703da4d0bad530f4ba28ce731296b5182553000642a9bac31709a7e39f3580e94fecafee734d05d04e2982173771a08f99ddd85506839e176ab380135759333bd4fdbb9ecccfcb4a15d5aca3ded4ee24cc6d3362e39ff3a6d688796644ebb9238a3e0b48dcbceb04d1bda2d2366d3964d99b4f4e4ed42e0554a73bb584008c9ec0a8610080903287e10718edbe0c30e7e045be3af47a372784dfba8f18ac8f5ab2491b254386530108451d5902513953d47bdc9bbed179cc8e0f0b93fef2a6ff01183a8691e15096efba1fd99295c12ef159145b4420cad3f90943d4b5aa76a425496a63f05041667c8d9a59e89d50b24ccd53d85d4c6fbc03fe4575584f00aef5b9ca2362312ef0a3858e5a32d9d23bc5971596cdf3864e1c5fcd43d2fdff24eb2b3b63fe861cc75f2240de001c5ca56e1321ca4e467f3c2197100ab2592dcdb213e71cb2a0d177842ff98c70ad309d398573abf47994866c8007b79d2fbc1dd24da88b7d24ce411dc6a516bf169c8103d6429ebe638fd4cd24905bae87a8c3d743dc87d753ec43e5d1e635f1d1f739faec7b04ff783b85fc763eca1e321f7aafb31f6e9f210fb75fb98fb743d847dba1fe2de3a1e631f1d8fb9a7ae8fb14797c7d8afeb63eea1e321ecd1f918f7eab21f232b13018c0ff4d0eb1bd5488fd614f64951d4babec83d3f222295dd63765f9518d14ec01105716d40ace3659687e27f6b6aea84a772aac0727bec47100faecf492ea65d926ed5d5d34ed6a6db3c7d1035a16591f08f09cedf0abad4fcaf5086b546781f4dbd3278d4fcde8f531596b3b992efc5a7113a7369a14bdb26a8f1080b1e13a7e803776389eb96edb7a8d40177bc3be0f28bf2a64bb690ae190fba85b10021f482bfbb1c7d1e955c6cd6ee17caeb2e8b397c6e45c76cf8528db8f74cbde4d551cb164c9c1eba3d480f7058fc597998bdcc36f5b5f1f04a179d2a70f1edae5dcf3dd3bea8c0868f66d3e7be57de0ac46f77715ef7cd07d4c5a3c988b398862534689d06408389a270b6369a66a5378745b9f2aa86fcb9c6db3b3eed580dc6552f875da3ed11740af332fffbe3fe7098a4f219cb45c93561d1fea806100b8dcc67ba65413e3b905b9899ffa7e3eb495d46ce782831787da586aa8c3b5cc5bf58b77ddff7d039818060df2198e9858cd961199b31124b978dcf4f11589bf4a3888301a14e342ca4bbd5e6b89611b5c353753fc7ae0c413d416477c31805bc64138fb9247c8502644c0224b99c80fdea37828e1253b892658b9591fe010fed1f42e8a28b7ac14301b45b7d7e246cf046cd92c6084827360b411402a22547a78ea63a80962b2fc60c69c3391d7c0b2429b534e48206b9f2581d805544da231ba2a4d9f702a23627c70210f08e0af8dc9214ae9063c5afa96249cd14f94fcbe6325049f256da42e24c9220fb7be600219ee5c06c7ee57f59463b657e9bd50cb1cfdab457ce9eb3cc12995a887d5980bc65c1ac51f99810909ce80ae88e04da59d6476b8a644bab221aa502734575203b12c05e791f6d33322dac0ac1140b981b5581ec8800fbe5f5e00dc869655544575a682e7405304702ed2deb83352045ab7513ad7201b9d04ba01d0b69b7bc8ed600292dd795e80a85e48056817e50a8bdf23a9a2624b6b86a82291422075a07da4321f6cacac19a23b3e5ba88a6a490dc2835d0e3802d6459650171444955fa3a7d1615d18eac89c0f4447185740ade65d4e17a8c4c2ff015ca101846e8a6819660be959e65af6038f59e7f1b01ac2ce956fdd8ea06471127a6a88b80edcdda7bbef0027f71ebb0925024c4dbaad36cba3b58df4f1dcb06cb06c2b15063d00a0e8403cb450380767aebe024c6ab4735241ed1e28059b191ef07077961cc4c64c9f79389b8103023b0e6fbcb212b08981735f3fd71909503990bac5c7e58c49623662246fe7f7964a50033b131f71f0e9942cca8a895eb0f87bc34664860c8f5cb455c123122b273fde5212b88988bdaf97e701095838c05a67cfe78648b11732256fe9f3cb2420823b135f71f839452c8acd8caf78781b830642432e4f5cf445e0a1811b373fde79095440c8b9af97e18888aa3cc45562ebf49b42937212762e4ff9747560a30131b73ffe19029c48c8a5ab9fe70c84b638604865cbf5cc4251123223bd75f1eb28288f9132767dd46b245882d72780e6efd77aad265eedb4b84e8dda55723b69d2280c62ef2fab0e90942f5fa2557039b7e24a8c62d71f158ec2d0274dcd2ab8f854e2040eb2ebc7258e83501b5de920b876d6f13a2e12cb89e58761221747ec9cbc34e4f11aae9975c2c58f42b813ab384abc566bf08d131165e5c2c7a81107a73e9d560a1af09a8774bae1c167b35b1349fe414cff74d81cc5ed8c9b94cd2f00a3a4efecc826e73f4b7dcd74007c30dc37521bb78fc429fa85e2f633221b82062d220578d080a74e9963b505627145cd330a94ac03ae50f81a5bb055c3cc7f0b5c399ad7d31c3518ea81363acd0ed1e6b43403afc3caae17c382a1f4ea7127f8c7c2a6de48e7f54e9e96341feebf8a60ea3573054867aa4f0838be382b17bbaa49258a856e19b386677426b161529f0c858dd04997d417a6d471caf11dc094e0e72d8f18dc4f5e67a3449ecfbf89cc04939b7397ae1565534992f018fa675bacaa3d3d2102954e6eeee8a6f6b21f9a4738aee0c796ce9bf98727e73ffc772758621db8d34bc8db42bbda0567ace869a44dc142b048c7ee9a8bb6834880fc80796165d6822176e2acc124224701275b491160458d4917ba6ff1137ef0cab2a2bc6d013b84e7e1e29c3163c961da8921edd5784d84a0f9efa0432db155379f1266fbadb50cb7e0e083f878aed2f9b14cc17c782585087ad4d33457711e1729f6084f1887d42fd4644d6a7ec756a363654d63c8ecd94676ce0e4bd1067169fa6fab643d27d224cdef44a908f2715c3ebbe35adc470a532f021433382ccf21f2968b384f46825ef81aaa80c2d3339b5efbf6b44ccb07a596eb14670e09adfe230569e9b5a8d16b1b8357d966c8ca6dfdf7baaab977e4397553c99d4c5650c0c6f1ba113e1723ec0853073d33113d9e97cae7e9b6e11d093bf854f3f6391702bb13ed261bd4bb968eb0a9b736cfc69b186dd11f2d60d349226e25112ec0d278f5bdcebfedc39492420eb69c221d67ae5c5bc2ec8de209df94549534b745f9531d56441e1926e3709cf2e87dd61356f6a6027539e119df563d19f63a28ba7af9e9ec79e3ecda97d00bb694adaa66a9f55ec31705632f84ca7c7d07dc6075e7754df867dafb4e8b095372e3c6db295cab859bda9df10201f79f6120107b8adc98be26969ffeb21611dfd7e7794a884ba5c9fc7fa3c75734a6104cfb910fe75c748e1fb1e59569a5267d9e96f054bb3cbab20b38882825bde1df1f4f62d6f61710444c18716487014c517a058f70fefe79e0be48034f5524de6989b7d50b7b65457bde92ff58026f60c18a9cd8c3a11390f64eaca5db8f721d5e137403cd7adb664bf5833941d4e73023ec694802400d29c5603c065d92ba5176ec2eceeca28c5c4265f84007eac27c14eb80a1d4acf4bca5e6cb240b16510cec5a468e64b4f7b7cbca46b7656195eb83658d7b25f7f179e6122c3fcf88ded36270086c9e67c47caf7c66c96e84799d51f25747716da79bb600eef7539d6c8fc9bcf4ea7c8d40c7a06b40d20390144f8f8f54b65f1306b07341e2b935b88592092318267b14509eebb573b5bb439a0cff63a25feae606a0096c4761b67a0cc28dc5d77043b944e5aa279045c217a39c17f93e0b6eb2481ca89c2e5c2b9794690fe9a8e821ed412200eb7684de10e8fc44de51706fdfd8a75b326ba1b32c53e083709700719baf45b5f30a653bfb002002cf8b21310db1773bc429442bee053a8a2dcf438768002021315fe01d43c7b50f26d4ada50a2e5d066ba12e27712f076df2f15685955586f194189b8c005c5a22e0eaf5a114677c1052c12b9c9d752666d3b99418d5ed2972214a32eb4991308974b6e11085db7873389a8598a6bb5d72bd447fee13dceeaa09a8a96a51326abb4d9e9c608217e9e9b8e2c6915c2445f4df18f6b43452faa6b3d77596e34caa3cf92ae04fbfc0227c91e9c087611612e5541b93943b1d3cac13819d66aa9b0130b8a7c55b2bced8f63134cb6076c2443a918f5dbd52f16b1958410250f56b4a0639b2e7ff484ebbf8f41d76058c20f76d70a1469d3165c51da10067bcf77c485ced737cb06d4f94d8eac408d2235c2ad2cdd3f4526fb7cf44d6a0090ed7bfca0319af4b2144ba13806a764a4e1fe907bb8193a4b4e1cfb13e0c5b85013683ddc08bac6748345c19d04a1d515e11d55e39d4b6f0b79e6077f73823bd2a506e076b73a60728409d622d6b775db016fbfb6fbe56ea58e550a825323d8b552dce3e70657add147d15b1fd00159a2967242fefa37d8022d17a0112e373a3201fc1dd32cca4edd3dd9dd8eb20037d12545596dc1818bbf09b676abcc273ca9b8a08d7761a8515add6582f9c7249f8a3145a461ec83d8ea4aec5adbb7035d8e66e8e619e20b379d7ae071d4838234b7362f981f389dc18afb8299f4ccd6b4c5f308149f2fcd89e888f64d6bd44adad62f3c7a5eac3ace4a7521248976edcb06a44083741105aff0f7c2a6fa71f3dea9287372a81f17e29991e3bfde777668a19ec16648c4fb21c21853aca3c045f3ede91acf102aaabcfff8c6a8ad62c1e612f9e741c102f8ebf1c02ad66fc5af60902877f279094e312954adc70c1ef7d12b855c2333d14c68732ab73a8c93e00c94565b1e755ccf4113c88e8d47c432f6006c326d8359a3093b6ebeb9512414233e72f400a1934174c2d82b1cbb4df7cc3315e84a12b50de71b60605ba6ff780859cd2fe063eb05afcb6e383537566752976787a58db6be0eb88d1884912e51950740c1bdc948cdc01bc7da5a585398132b9dbccc96ecad097c85656481f4042b28b8cbb2013d07064bc1199c3f74968034c15a14781f8086f0df43389122ab3ece51ae1b83874d59c99802d05b34d4fd06e9ad58cbf487a4e78d23d6ed0b4427e1ff0f47c2247cb90eca4688ccc5629b10dc830dc255a72dd65c0f40a508b24deb0e5008183dc1061ca6d06408b30208cc00e0bb5978aa5ef7adb862434e8f2d5fe71aa0a57063f4333408ec128244d7c05d253c78de38249c12bec676f1ed2aa71e350178565646338fbe1e155fe52679d282a68e05be44483e463701174012fcce3af8e56e49b4697ffa7df43feb06ee718a7d6e1615169634f3b109599fd70fe49dc9d55215585d637297b086ca92faebf359e77dea47703bbecb70a607f44332795f64e5c267f33db1e44420ec4703d661692d9b6e88d0e4f4ab55d9c0631885b1f44f36a2be002ffc01c89524ce544486aa82e15ac9884a7ef6ce2e5640d3bd334eac12905426b2b771418a99d924c55859504643e828a40d69de91d013a2d62c61fd5a958ee518aba792ffe79813305fab11fe67f9cd2250efaeddd98264b3064ab3db1873b13a0d124fb6a171b504f9b624cac3c6082ade878d6632a4f07950cd4ba95366c1bd42bf96ffd2d1b56b5483c140c0187a37edb84df314d538f61d0b4433a04b174b8390f5b641b25aa159e81835f8832ab19d3e679fc4914938ab897158bc2e2b309112ce6f69a542c36bf861df3c06ebdc296a5189fc2dc2eb5748110a7e749a4622e8018020d22aeac517a42912d56471d784d669a7c4386cf52d072fe5113700806d77b59cb2089786d9439212740acc868e6f7a02fe30c8f6ebe06bf52fa5328503819945d970a0f970e4355dbd9464a31f90a689e7cb3c7c3435ab31656d667077cc1060f53a6aae3a6d2a767cd050bd367b101b0041ad2fe2bb23f76944051e6df5a0c49a329167b4728f7ac7ab2f0386e825b3381c4ac68048d0410f03c65b004cd2b8395cc66472fd3513a05913f8e3ad944c8d45fc5ab1d12fc6b94c6ee8234cb56ea083fd6e7142793418a253a032874bc0a2efbd0753e6345a4b53d95403c4d40a9ac3c034d539e661ed2ff06688789e6ac1b8e76a4a3af3b940cde3c5fcc5f41526002e6975a8123c3647b0e1475a5390bbfa7c6899056bccb9ea0695e38366003dc56a62c658b1b4d990cd95554f7b33c472aed4437dc505eda3487b4e57dc61f3d7e9dfac66715275fbb41a68f965a002f3c221442f3864d53cf10a1bf5318344b1decb3999b39e1c06a8738a439429f5d83f966573582f050754ca212366ddb19e5784ab41ce278c187a92bb2a24b2bef50dfd582f2624fa81ec1206bc7fa2cebe5f5b1b66b1a3c424dc7228d77ed211341f90d6a7932a5c5f0b1330eec3d2f1ebdc1d78e9f3f69c31f0b268bc545794a4a76b19df37ef42d987c74a19752915a78aa64fc5c016a683ae0ca14d1a4e6582ba079f862e9df0d5635283e04e7c839350b4c1c60978050ae78f13b08fdf37e155b36fc2cf5c7e5d72eaecf2c7573e56ada22f4fa491fcf05ec4f820ceb571cb817193dbc07ca75871439caf688fc846a10be9cc03620a1525b233e2282f2598dc616370626d4b537b7e9d43de831acecff895768e953f037fe2432ac3b1bfdfd55d2d476ee491ec11d6b6c7b8e9e5276b0cc7df4758636ec743d2abeeb797a8b46682a7a4c9a1d8e9393c1185e84ed1686f9e385ce4c0ed0ff7cb518917906229432f61dd48563cf3e3b94cad4487eb08b1758e3660e914b6f351479e57199c33b1bfe4b56248e89240455c245eaae2554a54de941bb958890d4af51c8986951950fcd12254d4ba78b5ee41c400327ec2eb37701c591987d91333942a980f327a70055954f220f4b826cc79c099550309816de8e3c814557d16a1a73f0f8e8139a2135f69f187f25cd5bdc971a912ed28d0af229bd207792310f7bc3a9732f70c187d0cc55ce5545ca726d53fc00a9f3aec619bfba0ec4b4852776d589cd315b43e07c13311258379308c11b975a438e38cc50a814edb18ecec3f8f33119ec7c0bcce20074ea60cc3750b05683cea2f486ee98eb26d17515525c3ed37cbb10a12411dd1566cf6eb8f3681a0e351b388dd8303ac60f8931fb3ca71f561e695113b976156e7e9dc11311c9c118734e5b92341199d10957bf496ad371c10fa947cb48fe2d6436828a5581dca2c7668a4afe42c3c1bb23fcb258ae3ec47ebb44ef07a73a6f4f24cb9289648162ea6b51bd85329006461e921428c310cde6125d914906222753416601d453c686078cc6a5237b78736811659927c43a2b245e5374f0a0788eb5d63541644471a390e4e62b27812b3e2b84f2f05b049b358b0546aa1c30d68854227a2343ee3293d8deaa9019047d9f5d50b172545d6e8000cbc29a6d1cde162c1f4b91429e394314a4a3f54fd87744ee82304bcdd1d55e0310c830602c74fc92e094a8b27fdea2b286b8b09ddd78a89012d66c01c6a84545727629ebd5cd96c950c8548ed4fa73981c7039064185e4f28f72fb60e115b93835df6938d8040194dc447dd971a76f5d1a85ccbfb05b2d60a38a8fb47f22d95821d1b53d82c79402b872db5984a55352a181ab3a5e38e45e8091fb015db034184637304b5eac0b1e9c0386be3cad265ca6c67d25209915c6bb0dddc0893cdddad9027c4178527ba705b8abe9a95a4138a84a7487e95651db272e7b8dc344966e58530362580f8061088174a6ebafb67646227f9a89ad0f82ab479fd8afcf063536e726451460561b635465adc3835a05cf612ea1e97e9f91fbf8be34d6b264de119ea7d1dafa4b3a76062c1643e22ec7ea17bb374a24a893d0018b73c1a0d42aa9721b42912532ec8fca885b59014935543f0149716dc72f86014e2bb74badceaa228d5537bf72b538a27e52bed4351a8a06ea638797869c25b065924344010b811eec5cd9f60455e81943189e473542b34d9cb2ffb05c5097dc2cca887be639ed42a2f8634f496d784d2020eca218bf249298b9a82f3e69355ca54a44b0ad3f4cae5eab74e4900ff7c8261c38be5d2d3e7a44b79512daf3acd599e7779522c0087711be71959aaa4ef741648cf059c60800bdb9b96001bca50c8d385a9b02cd46485c3958f5bc850d3491d61927b524424114be8660ee055559ee32c4a6e488c37272409b062c52059cfec17f6356ecdc22eed08112cd85772c0e469c1683b673d84eff8c8a82db8c3faec2e5c6b62726044c6a8deb90dae917dde4d0b93311a13ee12bd904f179c601b49e826b0070dde35039fbfce2d4976b4018a876641e1f348c50ced53db4216c4895dbc05ffbbb54cc74222b11514a533676ed04b2b50315eb5fb75c139c84e5301a0aa9f21666ced5b425a0008fbfe21a3b60957fb1779e379e8d00ee2a85a8dbc45422ab71def9b92a992bc638932ee17a85f0f425f3ffdebeb9fd607501f3d35233f9e40f1c6fea8c56491babf5a2a0f8c08d2f54a98eaeff37dd8fc574a9a60212bb74ee8c4650a9e8b7e14d2040becdddf171b23c5ba7189aebb14ea0d7bdc5496e316b2c74a9dbc235d0706bd92a2e0ea0bdf48c5e33ea053a9e283e4c67f94722cc757b392ae2123124f03c88a52a8dab26b38cbac4347e1f70a5ff08e892f397159e57cbdd15d86cdb918dae75630d32c6724bf9c2af8563dae3b8325dd138534984194867173e7d9e8b906ac9e97667cefea387ae4a997dda43f1afb857c220128fdd3f12bbaaeb7fe0d1bad7ceb4b532f1456ccd5d7af3b8d66a74706cfebe986c183f3ee77fc7e3730f487a51cd8770d4ce115ef8ab2156baa07912d3ea1822d6ba0c5be7777bbff161ec792de29f2940e10ca0ca162708978450a12f97c1c5f5a631841d3e05991e4dedb4eec91a8aa641ced9f0d01ca8c4f991b117541c19fb4e8c141d9c30f37c0378f03f10a26adb849eeb21a16e2c6576954490c41ab7a6363461e7396a5439d208e1b099c1f9850383a402f010eb8a8fa566df863af97f8301e8359466eafb6df57c36a0e3ff90f9b214b6d4ff2879eda8058e7b6b3ed7c88c636c7e588b0a72948fe10a84f23923d37f1c172e21f138f3d23e34391fe85bf90bd22742bcb86a7d20767d161acc8db632593d707745b7e38be07e745ddcd3ebcc1563b587f208930f5bfa0e641ca8939052eb71e6fd74be343818e68a311b680a058c6bc4af003fe3a21d5082b94a5b6bf4edcd6eef6de7ca35cdbc4bd7f88da7bd1efcac9c223e6ebaf08d17d883cff270c85c95b6060c6e66cc99f99e2f92ae58f5a9e798a8e6f3eeafb0c6a06f9f86c7c009d53c6585e1e8e9de8edcd4efd4128a7f046077e353afe90489ff612808368f4e0200f6334b8f38309d2a4df7541515a9d65e727e8acbfb85b02942e15c2578bfbccf676820298cb6901b4b7e03b8322fc15ab49a73f19d7361d84ae9a9947637aea0dd511ad61c1040d2297b8b0a9ea71c7803adecd828267e2ea43451b878d47578444b3faf416521de3205af9788d1bf64bc580e0b9c16c165608bc544e9b16e6a13225b4d8ea58f2425772497ccb8f8a8871de6d405fd746fc0b15af0dd4bf8042b8b07bc8c5e1a656456bd1646676fe0c560a6e067c5413a9459a88919765fd2dd0f58b50a48e958381130d881243bb127fceabea4fa5a112aa0fc5ed6bbfd638da931efb6a48e439191f010489cd32b0b31e6b4553b5a41471a85a90ed1355afa5b41c2dbd525bf1ff945dbfc3b205467a2b602fb65ebf4c09d254b2eb2ea694e2f3b5b239550db193ea969d62659e1a015f258e648189217544a0af6422b02f0e5db2a1af4aaef5c0287f9cca3ae03ddb46d3f402c52f36a4ad3675affcfa6bfff69b7689f181aca4acbb683e19facc9fee7ceb50e32536e9db29495a41c93e668ad33101dbf7cca098506f49db7c2e4f928212cef2ac534c7bf489166ba002ba738997489e770c76dc963667b8e5524e33039abb17a6bb6980a6948faa82fd97bf5cb25cd1004f3525ccefd5cb046f2396e3557c24e83e7e1c664042d3762bbfc3fb39778568fc67363696be9c8a01b580b35cb6be6b46ab4ac055f09de5ceaad881403b310c8d3d7104356d3cb109e1e141c1f75de703225d15801fa85784251b785996ca76f9884c5d5602ecb3568499fa1ce9b54dc7ec49f8482d41755fc20d89175a1b06d362265af4b2c3d07ea82c5657c6da0b660b37a66ebe6536dcd66297cc53f8d3fa6bff62fdadcbb21be3c3e63654eaa9a0e2b8977b80bdbcfe23cdf57ab952d2cb61635e5e445ebe31c9efbd1b6ae534ce7ed0e5043b13d9b6d1a09e8e81080624ba036aa12e8657205816a703f9819b4295991c26757055b82fe96e34a8125773129a97aae8feecfc88f8292ae25cdefbe214013b2845fc8cebfcfdeea32ed63c334d3907afbb01306e789d8d7d99b24cbe15ff2bbfeddd6d6fb9a54c29c922086e0865082017ca6ae58c133178d272c44b5dedfbda311848f6b6b9e8125d97df43cf35227288f41bf8da1645475da04ffddbd8e28c81c7d54298955b468e2e548531dcee55c49121da6ac86cf9a967385538ddf13744fac97bfe86742f3f9def71ba0325b889def3a61722f15e14f6d7bd28947e9aad71b26eab3e1d3827f4f20bc2395f28fa49863cd024647660284412ea54daea36385220eaba3d65a06f9bb5a512eb8e0c2473d49caee99205c9648e92393eb329cf992e3f7533500ef3d93fc99d2e3a1dd4665ab7ac0134c61d1b48ca88707bf410c5ff52a97dac3505e99c6dcbc2ca58266d247f75570d1b48025d1cdf8080569427e59658e0b4180d9b3c67cf6666e7d9546b588b81c3375ad82fa6f1d7f193180c44a5c59101004c654db7f5b7b8591c1e1ef73baa9b099259fd97959033a328ac0fec77646f2628b9dc023e71109c059e71e743105272cf6d5828ec868db01899359b713f577ef2d04f959f9c59fc04abafc819ff8ebb7f60c7ee59875325671c0b3b36ecfaab9fbfe3286baab46f335a0c3ef9cb8eb25a8d9e4dae61ad86ac51000e173e15a6f1279bb66d75ab9ad69a0264cd08a4e8353e388a59de023ef97b9be13280f5ccdc09ee197425e48cff085c49cfef1cff10d8391d14f65ce7409390d073cf21098544fce47f822acb09f17777fbc08259cc624d5517c0bf87a0ea1a1ffd4f9a7d66c82c1f66b399aac539552db6142ca9e73d3df349a93837666656a3a767f5faeb703269d1574eae4f958e97f5c6d2413d9be9c857b779dce3038f0f9d5729c4853aaf65daa9e3083c422b5ba7aaaa565df3829c4e2693d99a52266b56e17d1e15b57893999987300a08d6b13f1208f6c9019fbc99d5203fe1f2fdf82e87cc52c352a7325953256d4ed77fae1ce5393e5bf7f975706135cdfd4315d871aaa6ca513d5389c4b0e3545def3b551368aaa66aaaa6aa67ce6da31bb349811030856f0b4b58774ee6fa6fe0f51bd747d77face17a3f7b8ab9953842278f23ece839d5bac123f449b0e9d0e8f4ecc63147b58f4f0c34f5079a5af4dda110d193422426ed4dfd2ea24762d24c1d9a82c43ce987c0c03c2914220a212ea19531df27c91975ab5b2d92625e5efbef9afadaf3e81aefb570f469d15bda57d1ad1730941ee2f8c94d3a2f6fd2417b17d29b86700ee9d954811717d2cbfc8d1f42fad18f42215ec2286a68caa17ef8fda4d01444e66fc8fc0dd390d29bbef4a6973931a149fb984f920313923ee685903e26441285f7484cdb9be8c39042d37c93f6a497425e3ee6634224a62d3c22c4e5edeb2073621ee687c43c4c28441442460ff342d810498fd7109851a803f3129a667844079963d2483fc4e54921920f63432c29dc2149ce2881483ff6c851bf7f7c1c2f735ec2284c397491ae2b187a1238fa17b0e56340978701edf367d291791d648ecc0fd1f1a5979f497b2dd4b9f144748451f49b1e3469323a748451d452e961625e8852e803cccbbc8ea7610c1da9949f489fe36352314ffa0ac07c8ed0a443223de7bcbc3ce7bc3c4ecb8032c7049ab4cff149726e80262d34e9c8bce97384486ebc298c22f4329f244706bc11de78ebf242dc08b9c7becc9b9e86314ca1f493cce871b8fce82b601f47f89cd3d2f29cd3f2387d03e49c12c8399a4f8b3e5068ab35764a04b64fa7564f7c7c644099d34f80a200d150ca74b956ab2d86543d9897ce051c81cfa305b4626f5d3d11d82993cbf694b15351ba46037275cd4be698f875790a50bf1cc6b039e9cb6f6e73e3d015767e3868e12849a861b87f90a7d9da7713460b6b9bfbed473b3f7485cd71a917b344429d16a77dd5f4cf470d400a3712a5e327be3182056d237cc32d1d1fdc299bc0ac27ecc3eaf7a4117e59f97cc8a7d35ba485f245ca481716d3cc9f22c847c822a4b803d37012f2347f9271e78f3d1ee0ce25eeec959c9993888ef7ccea03e4e0ceaa317d7af9f3a1b3d385d2756ee897433f9d6c61097bea17bdefee169658ed76ef3d0c98384211d6c8d9e9de7b9c762f7485e52b67a3be97ec83c7117647e97f5c61fb8e7f04071df7fb0d748267cbf9befff3c01e72fee6be7121ffe6fcdb3770bbf2f3b0449234fd149469112cc2f38aebea4427794c665b62666ecf9ba51668a9856935d6261565a0a1659a69bb49e9943c7b761bc8540bafe4824ffe2bbed144e7825132f0c99fae98891517ec6ae6e94dea69301918f5834f387ef2e7e71f16c73217923022d2a6df9f0212c55b704e7cbfc3944bc4515f58e78f8460bf173dbb80ac0bc552fae431d72808b67e5faddf8878bbd435dbc4a1022bafbf1372060792c69f03d9899c59b1941d587eb1ab45b0087694414138ea4706f19c6c92a1243a57ddfec0b130af3b719557349860a7044836ed47190433b9baf910c1ecc82eaf0bbb4edc6a8f90a6813a57ce0401ca1901c10601393b87a378b99968326bd7684f1f8a7f09641037461c91156ce415ec46a97ad4b585dd4b0bb6c4795de53927c79e0ca2f785447d4690489035da1741daf0bfdc201cf585bcf213dfd090a3ac3e1b561ca579c5e148a3c8c031af021c05ff6eca633013a4e8cf51f846879de0c5758e12057649200e9233fe0b0082c32e094479355d937aa6eb32744d498afe23e82251a23cf977d334a78b5f1cb4c353a4b08cb3308ccbb8feb39999081fcfeb28a5f46f13e19b10c81f8c92fce272fd475748189d1f84a36cc84d644b18fa7f3fd7bfaf8bdfe56217bbd8c5ae3943eda3075a3061c5cc54f479feb651f6fd4bd226f4fe37a4cdf6fe38a44d871a8938d8e1f99c96fa2210880d087be97b20101d18430f3f75600c9a46e4b26844e48d9bdaf61cd76d60e8a927fe10c029009e486f0b08d3b6c5dace748a42e16f5b0edb739b6df916b0879f5a207ecd012116fc8af9916bc2d5d1adbe725fad56950833f4959cf1245656fae48f659b59ec0e55729d2e99939d15faaa45f00b3b83e6939e554c2833ca9472a9346ba5ea993b9fa584e5ba159d1d3b76ece0e131c82f06bfcbb5a5a48445c2bebcab40def0c034cea335e5206f8b47e70bd8c20bec894b811debeb4ea9b0ae196b8a47977074165c7f1b8ead44e32ee4c95f148ec2d191f08523b331ba8baf3c9fc1556a25c562b158ab98d02530c0207a278fc11a8bd5181bb24530df43dc2b4080c2ef7243d85a46d78c9a6ac7f5ea4a0339e33f5facd335db0e5d511fcaa230a02ea067d02b940daaa24be40c738b4271d8dce9e9a427939e4b945cd8b65381da448df18d16ce17d3f8aa36c1286f62f60c8b15292f17eb89931d9e949c093a03c69b985de79bc9869c50b88217d765333bd2d89c6958b4202d0b4c8bf1c8ae6b53d0903447980212aebf26a5fb32b42854a05c9fd2555c2368506c21699e7869ab96e6735de3b9ae29b9fed40945ec481b2b8d6a257d2a83ca681579d343481a9736fc4e1c713d4b2fc1727dd450705f9e3f1fa2679791fd3c1a8b89c0c6f202f6cc0ae87ac26a273c2939e33d5de32a12f4895f66c1b2934509ffe84abc65ee0d83bdfa497743e928a95e754ff3901e86926667812618e562f009039589baf2267cc531ee420c867115f6829df00a8fa031a9318e8ba9982891418679475f854db0a34f1e7fcf022ba36bb66f959f8266cfa56ec50b7b498b211556b6d84b666490e1f341bf82a14bc123950b6d95ebc2b179683e1f737acc614e65fa18de041a8e9cc5570e7398c31cf672a2b19ea8a2ac9c256de83777c48b515e78043bb24f87a3c68489b4610e7d481cb7558d4ea64e288b52262d1ee0009f8fc91d61433ffa6ade2e015e48861df9d53b3cf3b7679f57cd91fa24e02c65748ac935808d4dad5cb7558e7d6e6cb0e1f3d1cf60e83678849f04dba55ac8307eb53845cef85356829ca1f1974a47da009133fea1efbbafe1aef13e0583c1d8a04f5a6451271e48574c542d32fbb89c30100d704f550d527ca3853a4c235f2e2951666c763161976f6695e9c574c2f5b7a3d077b2fbae3f1fdef77cbde62be8e52beaba14e8d21e47558daa6e89f278c979781a7e871c666147a157ddc27aa5c4dd2d5fc7fda4c6449e0ffbb0cf086c292dbaa2c819568b4e562219aba6344aa74ffc332c32cd8a56c67509a3726da0311a1b698c871dd887ad982f15d3b0158caa66f049759965be1f5f35c363aa9894ec302fac1599a73ca3adaf1665420f43c684529ea6640f71abb9ea15afa68cb957afd7063a00368103e0ed517d98c6a7aebc89ce039eccf181d1993741653e7c43af5032a81a3b88d1181176ac31719e214ffe57aecbe810b329a3311aa3311ae338cef27a7158633e6ef8814303aa1a5418d000a64ce88aaae6773978dc11b6bf7b80c7da75bec2af51aa0deb5fd018743fa9938f862b592e8d491b1ac81b5781f32b083fe0178d4925211b96569127ff3358577c281badeb7fe3a331baa24ca84aced02554e52df77196af441d27ac5ee97c10f87c841e02524640488e1ca51b5336653077984781bbc9f5ff42dcf6a4d23a6b5795c7311a9bb18eb1b792334e5a6445e91a1f2c77a4b11834a0eb515cca48633cd2980d3406d3112224021190c01026d2a9dee12cda0426c051e056c023810e9b389ccae7c3615b583956f61013581ad53c74e77a96399b5168e3fa0d6341d2f8944dd99465e954ef4cd994f5aa9d28d19e684e3426da121aa3313a2de09140a7d2a73e8d5ec6f5d74020b64b4326c8197f0f43d290402718748247cc810981d880b0b75f03df8f8fa76400039f8fee31f0ddb0018ffd4a8df5180e8b8df575bdc586e52b55aa8c5f2e98273dcfd7ab6b48307436d3d9dc5635caa0e4b6aad10d6c60871d38e03d619a1a30aa82c1277fd235382f83f5761ff018e4eb32391306182f47f912a220850f460002860e63c18e1bcff5a70cf3ce2fef48205865ac52ae7fcdb8ed5c4f8244d529d2865bd5e77edafbe58f866167bd4348328fd33e4c41609ef442609e04c3313f6d57c381a53b20cf40e9a798977921312f43fa98f015f348603e26e4d776dd95c819ff900d4b5fe6c0901e66474ad79048a153d0838298af098b79524cccf277463ce92812cc8f3a607e748143be9937240260ba2ff423bfbecfebfb422b612a1cc4b3dee1d8e78542a1d00dc58032679e2167a0c034565a9cf5c412d4a27720df18b0731a7422e649608967cb89d1d1d35f82b5e854a695299b58ee94855d8c5336d615533932cb9036f35b546034467ec2f577987b8cbbce578b77d553e627ff6db3b6549a29e01ac92214182599f674cd9436678c1a112b285122ac15661ad7a76cca2efdd219b6ca3629d3989cf1a755349e23ddad29e9896d9bb53446631cce57d7cc0f78fd810f7c3ebaff0008b63bd61583b5ae384bd7b077936a4cfab6d2a8988ff99898d077b2602983ca94981f3b8acc8f0de5c7aea2144a5f72653cc6619ef4386dfa422a2c87ac228135e6ef1cbf58d5ea9ab1c6ba46dac4aa8c8eded47aae2fb9ae299133313b8a77a43123357684e51fc7cf47f70cf62df2dd50c4ab31695345a2b88b578d711581c7af66d2356c480e43b6206da412d71b09cdab73951d6458fa9d5281b45102f341ec63054b96eb3b17e554a6a07ca5a451358a85c25ec51226d743273cb4f892332e73c6ba3a5dfe422a2c83da75f7771813541aa5b1ac8cdaeafa87bae60b8d05b70b79a3a940d2bca4cdca511e863cf97b95cdcfb0ee57aeaf1c267ac1fac52f7ef14b5ba2f5809a1239c32f7eb1e6d335a394dd2a5efe74898c56719d3eb94e9d30b91e8edb168ed6964ae1f8e27c98077bf849bcb1f58cbaaf28a3933ca476e58b573edf78299924470ea1bf0d39f1c8b2a7743e4ee3a07dd3d9abee884d5dfed20a6c897e0ed96db914a56346b340234643166854817aa92ca44003caf4b59ef4e7d26f4f874f87d0735fa2ff8e12620b815be8eb735ad564619c115bc28b007a72cf7a8d6ec198c67f0d46f50b5a72bb7e5ec0285e83595bc88d615a14e1ba37aed4a28858aa0a3da0d73369d3fd836eadd1335e836f7a0bd93ef056b7357dfe7068118ce7dc601c8644f54f0bd6b29dc8db31c5f2fd5ad2a1fcc896b562abd5f37d3bb2f48c3e9d1d20477d2f7afe0ffcbee58788de8abee5452fc4e8bf9739dd8e2c9665bfc586a6feef47a3377528e4fb914988e85d5ef42eef1f1250f67d53421381f502dbf2f6e76b2d282dba11972ca4729065f4489b225ad883f66ec4ebb612d2a6732cb7815aa444b032685821a2d05e88ef5de67743cb14e47b9717f2bd4b28fa51c8834c14dbe2359d2eda5e04f6cc82b3949cf1300d5b05599386b4b902ab5544af5a37c802a87bbaa7c56ab15a2c16abd25ab5aa51510bfd91f7e295a4b063b77ab688b69e9033cc05df74e7a0597d65d69f8f1fb88dbba3063c76ec96dc9e55351cdefc6ef88122081895180cc6400ea4c308931968c643b8fe332698cf476b74bb188fc1d1ed969cf1d98807505c219bceeb7ab7ba55c33d55cd62188bc1e2a1e26ddbea562577b3b85b0d04ee1e69b3d5c7e91fee14ec15109ab48089eb9d7cb990009231c38e3d6b713ff6ec093264d81b6652ecd82d192b36f41df5b6ba79ceef1f04fa869e65ac60719ac17ab7cab354dd52a92d354b5134ecd8d3f3b41edbefd0c28e3debe16184e597a965a5164b5857de915d2ef04fcb823bb0520b1400ddb17be413aa80a15185185c91c62c0cfd58ed8edde380f9a67e4a04cb2e25ac2bfc1ce0e0c777013812a8d25ab5aad11d3cbc17c460144bc127e7b055cd7a0106183e1f4e7c36f5fdbd9bda0146f5ef868f8bf09c3de72401bd4070923b9fc386d2270ffdd8be5ad8add08f1d9f79ce2a8a9e288aa11fcbac198fc18f492ff9e9663e853fa1fd58d3ed5b9aa2ea9e16bbc759b552cd6d9b556301847e66251dd81e703c27ed66f0669861003bccf218740bc851a29fc29ff0264e5d57a4fdd0431f6286c4aa67c794596805c85d9a6f6bd1bb87fc6c8b53c219084083c720b37eece4ae8659dda2fe85507ce11276ec1fc80e82acf1810c2e1612d53d7e6af10dab68705d1ae1fa11d28b9022b73c099574150d96b8feceac59f7a8ba877e17a56f6e5bd54416acc106cf0862b00bd6b317fc4967712cdee34e3c359b990bc04a7413492345b7a07b4a4cd8b17b1a4b8fae449485f60fcf6aba35a78d0d37ddd35724d713fab17ec7eeb99f0fde69d19f49cfd89028265d17bd121689eb3e32024b9b204775cb51cd6275688a95e1c28eacba11b116728a79b6b023a7e61dbbe7867eec773f1aae4c19836312d5e5b05946a4ecb64f11be100a1b2eb1b31f79f2174217b36ecdba35eb59cf7a26fa3c26020b3ac943898f3bc2f695339bc82bde829f90db4adab470b7d9b6711777fb99e26e1c645bdc9e4118d16b71631e39b33dc8861d79b56d630d77abd94a77fbcd13a174cdc8473090a3a436f211d2a6eff63ad28657726607470591286e224fdb6fa328fe7669a8847b98270b368a104a88f9de6f510899efbd10f4bfefb1bdd8b1778fd4d46db0de6e52272d6e4fc1f9dd90e476340c7b2ba8a5aefcb8c7e96a5ff44346159ca9db51cf3f5310d1b7bc10d1b788420f85881ec9e845db57b053d7c74fdb083485defbd07f4f2916ebdf813ca5c50dc84f9beb075310fadf5760bef740d8a62267b66ddbde51dfaac4f2d3c6ab16472e0bc4767ff8812ffdf938ed59c9630b8b74ac4ba2abf527581fecbb759ad77d921c1387eefe48bad1e6749bc71d6119a4b7d3bc0ddcee9cb7e3a8e6b579e336916cb1d6e77a61a0b0f46768da3c3485deb4bdbff726eefd33857ed382ccef7e0385ccef42d3f6dd8f9e0385f87ba12948f7a3376da1902eecbcff5ef4a370fb91fd9d963762cbe9de9a3621ecbf84435c46ef12f6f0537d99d37d3ff0252267ea87be203307c74ff5394e081cc562fb6b0f03dcfadf016e7d2f01b77e07ce204ff5bb8fd19033f537905e6945ced4dfea16f2acc7ad0f7e613bc525994db36cb381b828a52efaaa556693728a5344e80c01c2375ae801a619c2a81e7a60141bc127244b64194b649650a74d168d603c70fe188e3c2ecc8f31301004b8b29329996acd41b59eb5ced704a3ebac569133dd61bd22692a19ddcdfa457d7d95bd9a70f7578b3ea5c50a83e88458adfca38913153ed58c1a0b408d5533d86755cd60546da2731f39d344cef8aa09f729a4374ffdd1573d66a852a4c8eaac56a932bea957244d25430d2e083003d50463be6898af09868a7d26188c622b9a99d90ae62a92b9260a3ca078e01b2ddc767adbe19bfe5965dbb92f03f0d03656f8f020a170fdbb1b7e3118b4c6180c465533eacb06ea041a03e3c5d5a736a9aceb7c53a7a837a0c20b16cdae691e3ab52f477ea1c346702af472f118a42f35764375428dd526eaca058f5f3870119caa818a01f38bf9622bd8278619fa01105080d73140cbc1ab2b087835562adda82bd3fc15f00df73b3ca61ce8f37328ef568382aecc342cece8ab1b8fe3475f1d69aab3b3c353430e0ce9635ee6774a6fc4d672e5e72af015308defdcb1797aa7ee58122467585602f92567f097e12401466e8294338c3c46e9d3a173605ee60b327360428ee1c0696cbea84aa632d5ef4c5e0eda646575683ab0cf310e9236f20acb292642cef8f3b4c3fce46d04c16032495f3eda89e89dee22a88d2b4197675c85c388d9b1c48ef5a519616b65b1c871b26d8992a674a76738b1a88650ad27a7d888909f18c128ae019f425a830a86a4f1e7d3c8fb42cc4f5aa34104fde08cb0e394c9ac2cc112648481b094167de4d55abf90370a7d558a0bdef49955cc2753c9ec993c2e782eb80045cef877313b5b09247a882a9a54859a8a6735a4c08ebe72e2acda64861cca7c144a94ebaf49b9fe74caf59f54ae3f3fe153fc78f3256dba90289ec267be7cec8ab26c8b673eee4dc63aab41be92994f9f41981f6f9002cd8af9005680966fced9f2cd1781324703e54d329bdc80697ef46a0d3b726c7a9f17e220bb90354d24123128354dd3b417cfda29f31fc9b1561704cbdd915bab21f00d18d5293fadf8462c2dc17a5184920fec0e3855468b31366c6ae58a327250e81322fa165260b9cf851f3b3207f14de8fd17206d385104248ab330031802b286539eead405e2a798eb0bb30089b95cb1de36b07b408514bd7bb87fd08276c92460b1ead70e4f6a95e2d20784526d631ad157fac28c574cd3346db52af57da1a93c69979306ea29edd350ba74c36b1c8dc3d3869033fefefd4835c159f886959044a4ba2e08b6cec08e9d1a6bf87e882834f177434c1ceae488582b53bfe84dfcdef726ee5ec8f748a2e0f09174ff0185dcead5f51a1a8003878ef21c85833cf9ab80ac56da6ab5ea55af7ab581dceda4b6da9ac80a3aefade94f10887ae5bb12dfaa26411a825b928d598cae6bee2b5be48ceceeb691524ae914083ca4db946529a73085124be1eeeed259d0f168517a73523a5dca3927d542295896c297668b7336a1352de81514d4461b1bc7416183823627f51707390703776f7ab3062efca6014cf3af1467111dd53b6c481aef56725bbc020705695b6d1804050505f115244a6706a5256c12b7bb881a9d3a8ac2fae63d68b70abe9fa2605f73e3e88b9d080a92335e586e5f6d35c00112ae48238b531a965e99451a4069b45cceec20290976f4eef5cbec2c6ab123971718528c4ce9060e530e1d4e9d3a75da820b0f863b78bc000018c453000410c38c0c343d661800010840001a0a50538029e058a0aee1ff12cbd2a88ae5a17d3eaae631ee75faf4a6f7c3b4ed98eba5522db164395e776e9bd329f55992a5f652e9fea88a9d2df29c1e02b960f657540186cb3ba70004ab8424765cb67405c8c25cb1c11ded43a1c42500645141b1f5ca2cae2821801558d29559ccb2fcc006f998cd9e30843bafcc62b6e2e1413453b18d84f4765c39df0339e5a720fc14b33cc5101205c44fd2467bd92c2e1cbb899c2133909f805c40409bc370470682e18ed2757b8a3b843c39c75c3e72c683662d863c48693ea8da7cbe99a10d4c23830d4b48a147e6d3cd559b3229ccf09946c65260d40fed7b34ad292067e6ff8fc7a1b3bfc5b0a39c89dc125b2fdc125b2d51e41b8e314d174186c859bca6c05e8e6225e4c9bfc9f5af1a9dda8e97956bedda2c999bf358c6329ea99c30162bcdd34c38884e4da353abb5d6aac1180ce697f6fe42b6d0c21f39e392335d7bda5639308084f00d6f2199e594e6275866999fa858af07d6c42f734cdc3304ebfe1c964a1206b20556dc1948eee11bae82892ae48c5bd2466b438a5790a2f3c05d84ecc1c24d8265adbac00e445af487f5c81997c9aa4c26639946a7cffb5081b35ccc1823fdedfa04d1f0c0eda79e3592e3f617a1e198e3f673620c321cfb678edb438e5b84fb51738c392e573b346cff37c71c957eec815b84fe46bb508f8ee98eb6695be5667353db26a5734ef6baca6bfc748e5230bff3b6e91d0d694d84bc9fa0474790e4b24ff6c071bb73765c881d8aba6d200e9cba53dbd8351f5b05c769396e2b21ca7130fd83eb01e616e1e877dd2247e9e4be4e2e1c616e11e66ab8bd12db3f964a258fe3429a86c4a6bd168e20c96d0e9caef1164a19c3fc19a2da6fcf6d1bf5aa51285da8fbdf363727d7bdfde0c3f5bed2ed470edf9b55b0fdf6e78fde9bda08641bb7e547ee47a1f6ddc0859e0da9e8bb341cf9ebe7c3d6c295e511f9beef3322de5ac11ec4aebdee46b1866a35f1f620de16517d7912fdf72ddf712e2010f5ba70b3d62de4e2f238ce85b4d9e32a47f2c7df1ada14d87e17d3a86954f3b4f9385dc11be4cb4b696302222fcc0f09b8f56b38822417e6c7fa9b3743a12d7c6f9cb743a18f646248a4972191b649c281e36ffca8fd86e36571bcf613c717d1b690391ce1d6a27ded06285bb45f02678bf66540af45fb31a06dd13e098469d13e0c7843c660ff05c42167ecbb8039a468df04be3cd93b0a4b72c6fe078ee2b5e13712b945be1789be2e044405e5d4b45a1fc7ab374bfd31999365287466cdb7ca3c9d8990962307ad959b93a3b4bb2bad5b87638e4b7bc871399ee198e3ceafcf6d2d8eea9e8e39c2ea8190b675b4f98bd4ef2ced7e4ab939394a9fd66deba17427b7fde55c58027d4f9b97f26079b845ea87359cda15d80bb9c06e4f9dfbfe0a8a5db3859b87251c7fed37edfdb5d7e86b4184ec7f2ccb6d93deee4e6bad557bd7b4f75a04a2c2890bc356e3d1a8d0f7e9c51e4fe4008a99b1c68a7ed7021a36d40ae3fa8f1d3709a280ca7db9520b1410e176ecd51848d44b1bd2a54eff8926977ee743c8438bf4c31e5827240ac74fb46b76685e42d67440320c24cad61f590797527f7b433c7440d6701252a4cf3cb4fbcd93903552d2e71ea72cc5fab47eedf1a894751e929b03aaecd60f8542a15028140a8542a1901129bb45ea6f1f0ad5e7ed4bb11a7a9ee7799ef71d0e1ad0b045425fc48894dd51f4f53d6feb72e87efb31f4a328d4ea9cd5d6fa3538aabf3e10593726ecb6ddf0fd180a4729bba1914d41ec8f7e88fd91e887b4bc281c43ef3d8e87a311ed3d2058bedbcb9003badf3c50fb7e6cf543a1ef2ae87de759db6243ef51d0dfe285a31129bb5d38562e046ecff9d85aae7492c7e851b8270301511a82bdeaaeba542cb93917171c857211046b11fa11f56496140c73314dbfc0056b6182143016b77beaca0b1825d987909d0617c172cf546a5404172957a5b56a55a3dcd4298bc1cac10c542e18524f868911330315b74bd9e1e8c92ad30c54936a52a47a443c29508af5973255b03258ac8c1a56260d2bb386959fcb75f1662335048f8c10ecd8ad1d50b07c7e72c0aa3c907881b504005708828b284be041446bc62d2fe9a0c2cadbf24f856d9d524604c1375aad35a238a8540dd65639eb140d0800000008a3140000200c088703e2e18040a20782a83b14800b7c8c407462381608932887619451c6186310218400010118819991ea00be231d829503017ee523f858000fe934e6bd14408e7c2f48bddc293628b7668cfbd82a34b80ff58d0de48ee977fe6222a58955f1a59754c38eebc5b7c60c4662a7a14ad62fdf93d6308ca6c658645293f2e1525337a4beddceaa9adb35a60284b6dc3cd149aea25d23877de2a0634845399c9e97f975dc4b0745e765e4350209449ec12970fff12ca61edf472a8dec65c563683a5663114f67f615ea3374324360f9b59f8b8f8e925bc3c78bb47c48c70514cb1321624a297052b30ab69e8bf9920771daf3e3d61741ddc2d6721ec3530dd113343ddcdc17c4418420bbc44d145f2d1019cdca430c21da2ec409670f01d9bc8f5e1862ff62a1efcde85604ccf36a60e41eefb1c51bd54c743670b1d093a14bab127131ae1be6bfe2fbe49c2faeb40d62dc18686b29292347407b046573618310d36dbf6379cc9a889a3e08101d814272c68e62035168fbea7086647677a35f461cc0120f0aaa2516b38a08a94216a1af50e83a53f990341392ac1eeb63824c8f11cb85e3a110dd3b03e72638ff878994f9a18c49c83dbd9980321eb172f4b02603f81b42c38679fd8607d6870fe780c11b9d4f18fc5b767d9abc51d2150cb39f0e3c8242a02517254c14a400a37b0614c03a2fb04499aff770a17c9e0235fc2a6df0f0525d6e2803c56774c8da1946e0a8bfe47899b04b441d37e92841480725543b64421e69531b6530a15ff2762652e0cb33294d41af9495978a48187c25bdc248fb47135de44f3435d4c227b7496bfb3b49f8b91b530f7f94fa60080fa9f42be88c1634fda7345939f0c5e0d08937a205b297654355e34881e49438c8c72b0fed9ab6c76bbb98742a8f925367419bd2a3db73d43cf59b8e80988191b498d19aa73082fbe54b07f2f9e0e32917a77b9631facc2bb9a24b39108ec8df7f477e994587acb973b682fa487aecc635b9b891811f41140f41867221a81c41df8aa24bb1c86e2ba8055bfdcc9293845829ffba1d3fecc63b2256d91b01e1abb6f6483807e418ec476c25f1b2571906044e2e19d8f54dba583a52724682627cb320d59133c676da2aa6b3a56eb81983768bdcb47e34a4f07f9fc972a2cf1906dd924583587851246d84704f0e746604fb1ad19ea2f9db8a4bf2dd6689d9b0ad1320be5b037fe0e4bbc09d7a659b83cdf2c05c770c60ffe2f8509e77cdef3e6564aad574f657199f75f8f4d06bb80f0f7d322f778591aff05e6fa7cae2503f6b22a3a0e979cd644c86443a9e245a7946d7f13731c82a53c04771922436f8a7fb7e591329f7d456afe2dc3d73b92412e8c614fbde13a0a3ad0a82a6b2733b21132876a7effe14027633bb54b183c822787f1eff91a9628b3c1faf5ab44a14c909145a0ed886ea90d8e2d7e89577a32bd6c2b3dcd543749d839a898fc15cd9b29f1c933a0a48f5b354977e01ff0d5d021e76991e152b354721490af95754fba1039494ce715b34f82e2bf47ca3fafbf11f202f481d7dd457547616e1d373975cdc5a018ba09234bc86dc91d1da138f0752cdc0e5bf83d1397266496b774657556d41b2362e86ead366b7783911866bb7c7f441133891c063be15ca86d977bf421fdb963426a2d7bc8179ce6a77eaa924c3ccef1ca2dce60a6d93915a0ed72ef1efddd737d6e1ff3527a644940c341db1af6fa9ab93676ec97a9f2038332ab555336888815155d9dba9a1b8e03f3dc4e68fa81003aea8afb2fdb9608e48f8454736078d0debbc0be16a9e7101c7e6559e1e2f6b9793794426c564a0ac2612d2842ab453974678475b2391e0b8d9aaa26e1cd46f96de73e3a94bc92ae5d0f17d7e18528db4d444f2837f33d93a3f75e2e7796ed4b8031ee25d3c8f84bdc11a8298c821630fec94e76e46821cb1f975c3351414a4f9c48379ae87d2088ebe8b9116f9bc358cf770e9d8b61a4248a16d2909acad65c2b08a6c0dc60d53d09c59e838c4f0f1c716aab44e1dfd114f2294f033cb843edc6006664473d262991c1be3f261817066fab854d64570e23cdb817da47be1f9dac4394f8aa231d200dfacfd960e0f5cdcf96ae9efb0b07f703ca50b2d3b8b3d3cc0cdf194e84c676f3eca501150378d63684fcf4613bb32837a106310c9ef63e227e5c33872ca6ac421cb9e7c7d864352192a90a146e0314e5bd9f84ef527678a609848592d523c7d2c612ed23509920303599cd4a0ed794b0a5064d734c39e33bcebf80baa1be98636d1787e1b678d532253523036f182587722876f240745b41c385634f575ba32efc70fc162a1ef248ae23f403f5aa0a443145a76b9bdeaea3f92279f9c57af7c0d39d169f7a40b7159b1f0fb7108cee3fde500e851f6e57f394bcf7520c3da842c7755b8fa9b7d9b31eb787d7a2f5867d0b87433608c164c8ae5edf1c00de6db67c984ee2c6ff6c1a894f2230f96bc71ff709924c2d2ad0fd001e2c9ccdc735697168cb56063b188b193380ff0411ec4b4d77385895e7253b2371e3e4bb17667bdc9f226647bf9d0a59b1ea296860d9fe57fd70f696998460a8220749f2b08d28599825099efc44266468207b8df5eb5591dd4a00ec167e27ae536ae8e337931542554fac246b6f5120e0fc359051c998840edc109fdd81e56ce4ebbf9eacdfab1947f453bc034b3d2c27dc0928bd417a07cf5c79e225639a2f792a028de6885f75fccf8f71f70cc0b46c5f1d539a30d5e812f34afd09510aaff8fa4dddc0822549cab8e74126dea9a89aaeefb4f0307a083617586a0e2c508ddcd706162e22daf2ab62b13ce3d99d1fcd678e058821f5ac770d208c5078fefed67aabb894323331d3fbeb9b07ecba64aeea4e47918f6afd2256903897277bb1dc319c9cbc8e2ff6b700a2f99a7a9e10e4664525d6a821b690f3b41afca6d4b504aef3129a3e020a499760829b0086651e0fc8262e80ad8d7e8bd501481a2599666bc9a91f7e84f5184216793e54f10c1ac7e7214092632e603a6fa6c75c77fca21451731fcea82a5eacf26a0e2a06731e8f74cbdb7a77ba73a716ccdbd220cee1d266657f9aa96d9d1b9ab6b9a63ca7e56faacd8a19900d2edaec3fd32bab0fdc2280a4613f46d66ee7fe8523be24189fc9a1569d4d3db08e9d673e4eec35e862586773f2126ed00cfa240926df00a4e6471533fce91396e116474c3c0106f85291e72f4dc2f28163a115bac0c8f82dac4d72b96ef6141bc6c84a0271cc8ff009c38bb6affb12ae4df714c8d16bd880ff41bdb235e4f4f3679ed375198ca3c417d03bca9d68a9dcc8721a004479092519428c9942a80cf2d1fea4bf1359a0abcce9bc687105667e614333327cdc79f46d82a75f2b106c65700bc7c2fe13e1b3db829f677692fe3bb93ec59d188c96b38ab8cb27be0098ae03ba17e35966e73ff587cb7872717d8423254ed537054f98497aec21238c0916e15568d0a4bb2d348f4bebff37cd0254a121ba9dc099f059d1e26e829567f7ba94892b2e6bec56ed354a7c435f4dda696e12322fe789508ff8d59abae962ddfdc1f6d07a0a792b6c2498e492f0fc2a9c0e071981da593743964466e5aa8519997f355ff202c5b53f53c0df38af0b94cbf1f5f1c0a490c4e1e82d56c69188a612045c218c8ffbaa50dec70d26672b44ffe742e19a7f6fdd2663c58256965e953d367e1b1feec4362c335c1f6f5be3f2e2678df5accab67144e5f1d8c7316aa5637ef939915997b923018a2dafbf67e4c00f1ee5b0052d57454b90c204eea5b349de4f8658b1529760d362f51cbbac81f275aa24258a1f6119395a41b059d16923bb865a86be5d9b31bab77e9b0319eed1e88ee80cad6f9eccec4e0450aab8881e3b932d2cec9bab0c695fb0ddba3a238df61978543e1bd7e8555dbc85163f63d42f58e2762dcf7d05fc1e48984130eb09c6df7aa6f8547901942b079802dd79efc0763aa40d4a1290a9061effaa39b6b0db3d38ba62b4ed2e24677e4b5995d588bdaa417a52dba137a069e954c0bf9d893e5887a87478da10a3e5b2d238343c8975932ef3149c139ce0e52d704ed288279dd4108868e0c5fd4d408541643466f25bc4307535ed3bd21224e36b2f0a9c44563e81765c487668acf99733ca5eeafbd6ec2f03f4a4ab0da67e70c0404928832843367f7dd8dfa3e798cc300f067e646e4cb039be3f0b9ee84e2253a2689b4b233434dc537e5285d1385b58062363ae6ec809231eff1f258cf4839f6488b753c6b796ba50da9ee60ea084c936764dca09493d382871997036073667513e39f7cf84daa19e440b18cec99959a85441435bfd08fdf62e51b895b0c17d91247681ca79ede288f1590a41a93f991ad39882360c5ae27cdba6aaf167260429c5793cd5ccd9b1adef5016e64d981bbc6e58f84290e594de303dd5c45f7fe94c4ec6308e1ebc34decc3c5bc0fb7047d05e465b311ead1066bef6f532394d299dbb8d7171522e1b5cb450bfc9b4f0b8cf4afe402cf115c2c3d56f9f7dcb4428341ef1760e68a058af0e807bf42a18adef36a6c8dd3f63957416e08f4fba4f40351eaf5a009c1f3d50d7f9ed3e95fbae627b30ee21495855a4e512850cd9360bdd803bdf8449ddb9ec73db4f2c283f2ca602cb662438f6a1e227c3c135bcda7d698db6360d4bce879e81e189dcbdbd124c04d95ca03d5dc274f0f5011c68a5791f7a6c817d95d2bef506274f3b788c44a69464f2cb0000f3d7139b9ade9ef87659cf299f977d66c2ff1009b7178054ca3fa7db27ab62178c73cb38639ad5ac4b4f9c90c09897a5626750823fa3cc05a2537fd78f294db47a1f3a4a3f0df45231440ddcda4061a57807b421ec3c018ed87cd8fe48770cffb8825350f0ad8861e7a64cfcad27a32ea9370d26e0182db8f2db2c6582717a315adb60f48c00d2a966becb3e52383ca89c8012698d30286eae20cc8eea74f4011bb78e021da309366e49d047d1418eb580ae9b41bf260f1017d2f3f22b04061a0890d9da9683f7cf16a23336ad5010d61563857c890b1e267f11647ab93899b5f62151757564adcbc5202809efb6230cd1d19947b9fe6c38af442929805a357f3c9a3603cfbb996721101a13c5b2617512b416e7dec41999db32e8088502681933ed74ca29c75fe1f1352b4b94ff99731e1e4e79938ecda428228f29f160430a8aafdd90692c71b0bb976d944d1653f3c5dc8e567154bd8b756253e0e7580c0e6fed26b12003a7a8a13ac5ad76f45f67413fa26671ed93c0385c6df61b39bd6faaa109a0829e07143604f4801e5720458188e52959a5d7d6baca94f853d4c7065bb0bc0a789b60892ef01a8d6a6651be0a5803cbb28b5f3c3891b107ee99424b8d6069313d2ebbbc35e62c5805f0c60992c5e090ba1c32fd17e4d684ba47b851809d60113180c84adafaa24b7ec3bf08299051f3980117945db3ce2fd775d91574b1e4b3cb607a488eadcb387fc028e347cedd67e5163b6a34ab912383e3726ee01b85dd017e3ff17a06040f0357e474958ff9aa76a7c45c289b5ff245589b4f06287be8aaec2ca4b1c9c8f18a68c1954cb195a915f8b4cd8edde3fec5a9ec30fbb5496450144f8e2479b68e1e1ddc156405f45e28d0581eb1779d25c272096a1e856c190bc399c0e0c0d4d1bab1388b8fcc69e1997b1397ee0080afac909e7c045b2b181e6c6591e4ad92ca8fb6ca2e1602ba3d5881090048e7cf52791a5acf6ef22e49d18cca55ed2041ce1844555aeedc6334ab92b724d2f8962b5c264750a3c11f111af0fa4befeca44aa2c441726bdd51de875962ee9d673d212577e0494b893dc9f1f2076ea2b3af4555c2499f13d27d358884d030984c59edb302313af9b802246fc50a9664702315ed2c0bed99864461bae88b3e40d254a131a20a7e6ec4cb8081b6282a7be3a6e64c3ad1a0134be3d5dbed2c6932403b629b448fb656fc6562b68bd51bb069e162e09cd03dab434501b48d40481cdfcb9daabd62da73ce2a8a3bf20fc615f166713a7fc8be1ff082195134ae95c96247a4ba918f19fac2c26c4d5523ac3691aa5a5c132a133ad894579c03b3da7628cb296ffbc0f1c02b26d2fc7944e67915601c2d5184b79ab07f6119d595eea475d0968bb2046fa9fd0275a0c9cea1a1ca459abd0e309f723f2d4fcda9fcbf35c0287cd846c27b58bfb2df41090a7cdda70a508c473e33e733ede6fe8faaf627228bffe8085ed2f40a6936244ab85aef85c5bca5d02e0240350d4b564f65d6f27d3b9097fdd8260179d0eb1fbf33905a9dd9b693abe278436450cd299399907fe14032b97261eeffdbb20785798ad16774c7499abc7565588beeab1d9b62c6090d89c31a437633e82b1c861d126619c0d2537069f7f30edc2a2b3f89c9c0ef202035d7fda3d44ed9e65cb9b50b349766205ea63e24fc764c26ee49702dc836b427413b6eab33a95f77e0eba02375da3544ec7ec50997d46808267f42baa67ab44ed7d28aed67c781f8849c86f18abe4ef4c52612525d225ad2aa9335e7a7b6da33585bf1d6f4b592e853ea40d67ac1eb14af71ddc3b482026049b7146318c5b95051158bdcbe53a46329280273f235588625a9e726ff33c793ee859d082bd3151694f0f89028945fed400869570905306138e668650bff26a7f1b02a89654f0db86780258bce146720f3a14a80fc0cc503cfd3265ed272291cba4651cbdc0e4c3d54233b67a994ec94071e17b10fc9ad959c6863de13a3509103ece71197154f810b28aac71b810b982c5f69555cb0561bb3ea3b50ab5ddde324b7c4c19d2fb1972fa2a3b74e4a10354df1de5d49e35a79a8291ebf9b2fee7f6c67fa79058ee19ce729c060131d2af4fd861b510db865088016e153b3050a407f3d8754ac56616cd460a130f98c50babc8e9d575658cbed9d1d221d8b00651cc208058afa0b8310e85cc80a7ddb6eefaffc2e24f7a9bb7d886f1ab89ea8cd108b770f5b4ae139cacdff4a5753a2cb2a986ecd8d1a5672ae03184621fe4e08e210fdd2d3253ed57408c8d9f8a1727bf22ff4810deba87d44c8ebb40a3d7b1bb00640567909b401aeef254306c34412933e4a83fa0f614c494617c85cbd19061f9e8f1cc778453a8f749ef7c45c2ed2a362a8a9333c781fde05dcffc47a7280b957c8e75dec96b78878f9f2eafd40486538f0dc8a9e39944c785d1369c52172e5804b865177c45ed0e44dccb5bf2f1a5a6ab004e73e5fea79926bd0cea3a79e3a93618e933c9c954fa3a37395753930b1fafda91895b12c721f475c1efdab64105c874c130a5755b2daa8d82f4e39a095d9fa488902783718ef9ad1bb24e65ad1c63d9567cce61e87f6b5065a3e2929eb8d70e20aafa251934ecdde984a68e2f1955c91ea19b4e72a7d97c4eb0f85d5cf6bedbd8b12760395cc5b1f55fd90b9dc34f130ea2afad88ab917a9e66a61aaeffbb2946c79c6b23b161440637c1e1b19d7a79d489f52d347dad34711c3dcbc2a220054907b8b7999ef97cd1fc1b298a2a2864974c11208252cfa11882f9bb40d678d9d05956cf293512ee9c19b81fd6469576f48e8d1ef735fa2adda5059227711937d7cd7fd1b4f82fbc2b83b198759043ed751758359cd6da4fe4951974b66fa6156f5200164607de9b8bfb0fcbb561625f65e683200c49793afb5cd2f7ae0a9d5b3e1aea03873e66b1d68336534a935e6574c593d589b44017bd5c1442d51c1377a1cf7a020e84c220038eb037822945d2066bf8bf94383348cafd4ec43f15f965eba005dfa38c5f94d79dd0173418361c2652c4ca4014c6d0cda75a790a6c3b6c77e75d0c172c28e944d97def7b9bf023278373652ac0cd40851f94470874ec892d80e4205840504dd402b76863e2303983f840333d48bfc470146927868b43caa0f9fbe56af036f96627567684cf90fb8bc94437a27313e898aaf71de55d93720a664894be15e04e6b0692eaf21fa7ed279f8ecee91c9fd8f467f3010c54e58b8c3ee3cf83588622120379851b4cd506724796c55f7c137466dd480ac5c23d954a913415362dc572455ae454a2fa6092439b9c6a5632d84ad71aa96dc8574cd10d4143870757fda0eaa43bb958222f8d4f7e664e680ee7214a31420da84cb1fb486fae544dce469705c01619d9c14777395b892979c8276f4e656f3ff1923bf5f7da8e5dc75a76dd63ed1027b3b2512661992528744afbcb8384dc81d9c1addc6d13ca3b4ac70f33e04fb5c09c61d7fd5514c3109453fba7f7376747d22356e95a6c70e4c2d30e43d4f8e52825e83f163451592793fb1c708164ec42cd069b40ef372faab79aeb5d08e02aba14c8f89772c5c113ad6d4c635d718db58157d815a4a540b357f6686b29886a4aea35b1704d1423e1ea514af9eff46b03f7e40c146befa2e4a082743a62e493c56a59388c72c3b78637e7bbff6969afd01bf16b2fd8b8e0759dd5281b2d690b5445a2600ba3bb5515997af3c8d70a40faa3c1b6087b63274a8b953c88c22225637f931d6317e1d340abfe51dce3c8dd119ce2ef6c68a957288fa3e424ecdeaf16857b9189727cf047ea352b7e922e6735381b1a3e739024cfd994f4354e97e3df4227f08e61f1badbf04485f10658a42871c8f3df3f360f35c3990876a23d97f40af8fcad37b82ed38419ad85d44b05b3792b69bbf0a538118210d56c9d4333fa67820e086f4e56ad466e606c84f4cc9462702153efcefb9738e769f499ead904a0a266ec8e39eae699386d3d1a7e69c01345e61371e855938d850fdc6fedb294ae41bac012c82413f3f2f3488bda9195b8929b8c64d9e603e042d3824816f551d775c02bd3e042014622d81ea1455d3d8680b9020ef09b143345d436b4465d050acaa6bdd20dce09b342c0965c976d7e9c25811478f4935897280789c09e052abb292513246b7292ef97b1517837dbf7584d7826355e0c4c82ef4bd88cbedad2485170a98539c7c72774d0b2540bc64188c89bd4c05e44be5e66bfae8ca6d1844ac0aa6cfafcdb2c7ea527bb247fb12b663922516994b220407241a8c15df45c6b444b39f42ed0caa334e947ecea01e0e01b4999fc8495211cd2f03f5342e3dfdb1f314381cdca1b12f74ff7cb04bc79ed7b1a33347745c388eb07c760114d84ca408d1c2c6eeeb920ddd7f4b89be9c9c319e6a46769846ad9ba88646391849671d1224732c42b43c5fb2675a15156caf76d5d332685408b87f92245e70db3c999e57f8ee08e4cb6b836568e291631cb5a634c018363113cd8b3cfb6bdb6e542bc74b74e574cbcda6fea681b1b81da88f32a6412873c804b49388c3e136da31179c529214288ebf626180cbb29ada0c0e92697a3809ee35d5750a928a5a01ea0ac8e4f3cb615b002bca4909acedda0f333409b6a8ed4e3245f99bef2a4fc43a73bef2fb7e0e3d0e7347db10beec567b7f6d1a0d95bd9ca1bec8a6c67c091fb30685e80d6f9df290de3f41b8bbb3a4fec9566cea4745b2db16939930ce3bd515f85d5b054d88475c0bda4162217b89aeb02acd805495fae0b5827ca969cdcf76bd89480d51970bf68df2eea4a53d0febb1f76761c0d640586cc66e62c8f85144b571096a6c834dd44bbf2ecbc343ceee97e0570b77cbd8194313241a8d6f814684919f26ca4f9db682b84a6d591f1627ef86871d79e8a3d83d84f62c0c77325bb241a1b45e6580df458b80d45efcf3ea95b322aa279a250de6927100e51c66d354e259170e4afd76033d9977cdc7e34b2846d1e69aa3ba28c12c3b476e67b7b868fa73353a5cb2b228ed5ef9e33f85869e1186e487e5fe96b16a9b5a272eeee82fbc525cd913baa6052c51345e9e75a87570bb2189594c749b3f56d395d3c95392e2f4efd9452f95615b5ba1cb1786d18f655fb0ce96ea9525d003f817b37329f8c345bf4eddef37d1cf1bd719028622cfc2b325821db19f7ba70ca00ae07fb9e6ff0df889c884134f86796a680473bc76ad9a9b8b79ee7ce6b782d652615554de6fed04bc06d4c5f3bce8afa8171c83943ccd4938b296410aa9cd437b9fe052d0d9d2c078716da054ad7b430e9c20ed62cc48f3b0b12857949afb94e68e2d7779447ce82038a4fa8b21276704162160b5ae71a9a9836070e28496a2df502a8c404f5a13b6880185794ac46de8f1e1450c069cef43213e6d7d067b47545ed0a2bfaff20f11d798c13cb7c0ea1a29814602aca5d6a7d1116f97fb6bb8928afe32fc443b7e03448dcf4874939d2fb9dc54ec2be43f11509cc9bfc49e80cf4552cee392d321a1e4529a54b904ac7a88097663d64d949d90f9df9482f866cd027d9394816b029639427f392b781a70971c310ae20e3591e59c76dec0c768c1d3184d78891321861333118b865d15b14f08487316573211b30fe70bd8cf11715acd5278cf10a3bea32221d5269fefbda5878ebf5e45ddf2f580b1e0a1f19e3bceae34a3ca7159d3fbb125fee3b28405df5d8e0ae8d9c9cb25ea15711ee33faed09b2d18771b047053ee9db5f54c8451d475a20e5a17b14d88d1c8301264099a13358d0639bf96f600c542a812424348cb027f0497f9fe7b13e5732f99a389429bc1f11394787aa361f293dab8b93b52a80a819256b34183c7a99e35c1017c6de0eeb1da011d07809e703394b3ec37148ca8fea80e8692c19ee376cb7c6ed665c16753b526e43c32ef1f1bd40ace941cb2aaa1e66452427fea2ba8a75fab091612876a1e38d134474b6dc3ad6b9fd9b3f94809387112a9dd0ef897e794285fb66e4ac3eb393087579e873d8b645ca62407f0ab9d8095e7d9cbf75008d63c3906ddd2f69b194ad87279b308c445cc5a508f8a2c0227180d483053a945664be07935c3d10bb00bf16f8b34444fb3566a0bf04d0df57bae4bdaedb39497190f3556c51d19fd7353f41971cd0b7ecf145af628b364c841c6d99ded3d8eeaf3d12bedc1288585c112809dad72c3a1141ef622ca7cdcec4ad074305f961212c497d5e3da196ea4fa4b87e00ba79012d0914e225461a172ebf1c89cb49baccb27c31383893d463c2ebecbc5d4e4a2c3b54d0e38ac21b90c5e8a7dfd820d074d70f2dbc5bb1c3cf01c3d8153fcfe8e7768d801dc8904f5f2a77d0b8c91dea52209aec560413126372ae7adc81f0b6ae0d884be4298a59524ff80cadf0ed8b13804f9ee9a5376cc241d093b5d04bb43d01633c1c95082b975a7bd22f27878793d7bb7ab12a665cfc727bb4b30bceff3740683a3d17ab1a07db3e5aafece1ade5cbd1c6506e4cadcbf36a82c44c8714899b699bf1a88637b66f2c9a529b2c3875bf03b4d20e451bb4411dabaca8a03dc5cf031028da62da85bc971de37975f9f4d787cf24de9f236f7fa543f59c33b8da97a6c43227be219de69717263d78605fd8a262ef3bd56c1264c03834d1dff8cfd00fa49b9a18e46bb76633bb89227e609978cd67678572dcfab9405093fc8f92fa9fe8ea643ea110080bd1bb65c107c6d1516b5d8ec820abfb7948d8a91bccedd2219c6f6cb123ae369adf1cdaf44512d425acb64e3229acd232da5259af0681efec2a944ffefeb1cf80774e41f96e488f2f4e3d9e5252364cbefc358e8a3603e4eab29bb9362d37c3a0d0da615934e2d83c51fcd725a16454ca4389ea59efefb34277ab9ec678acd1c8bb2bd39db618a5816098fa94d3fd1207fd74b547ba1db55ae113048074599f588083968a4b4b85c0c84248c2454c3560c5910b455b2a3c58afd21f71767247e475082ab953b7e45f5524e9582465da05eebfbd6997e4f992b2f4f5a4db881c06c0a4d7d67bc1f89ea57781af84669ae280a0b005cb07592305274b13c07cd274cf89909791e90d7ba5677cbc703b3ac5b6a2bbc0458d9430b0f4e52040706b70b28e0a3d51edd8c334f4659e53ce3ea64c5818b52cf7a23b852634ae66282fed75974027c7a2fde03d72f33a0405017752da647fcc6586db16759d20ccd138705eb10887dc2b1bb9f8dde688f2e05b4cb06bf1f171dbcbd80c00711a3b064f5078878e0587d35072b00350cc416c71bffc577ba38cfe9e967af0126ee13f70d83e033a285c5d09334bca6c27ba71d4e8e328cd8a3824c3210daa9f7d9a57878468ee0bca12ef01190798b1a3ffc4228f214d449333111590d5e9085957caec71d2b76522de183abce98e283929bb0c3c32741a628e284cf4c42337dd292702d28e9a063b65193aa756b9b1eda37e107a249721fa66e0f566e236b042477cc24498bb8e115ecfca982a118f28991640a8502217a6d9af9a44964c5451f78eacb86ed401f0986db768170609ef253a239de19d036e78063c364888868eb4f10c2f1111fc102250502d7c51fe605aa6002b85e909bc48b02bcf7759d2e4d8c4208ae03b9c850bfd5234c496ca985aaeb6efd7a03c2916e8861f80379887ace64a8e5f198dbcc28c6d44d9c4ce7e529bc0ca89ee8dd376702f2b4a8b5c505884c3894d7888b7a620bd351be945398c0a85df348a5c5e1e617938bd8799b804ffcfcfb56b9466af445b4c140b745c255f69a6c79349a4c32a1cfd44aca486ff290fa160413f9fd7e16088e5304ff2428d209c36ddd3b0f651d636f423e24e3320371b8b03a12f390054ccade002aae2422e84fda2f007b3e7f929ceb911de67daabeded28e3995939401ab1ccfed6cb211e1fb8e9c187518c63524fef89127360f20a76c851bd7c98df9980922f9671015a120dd825ce4a4e670314d0d5923d733f64cbb2ae0954e47e2e5c9cdcb05f3f7a0d2b91111e5c1d641c654b3f12f543fbef36f58b32b80270d27e48eeb3d44c9987368972cd5938890eee583a4528daa243cb9d2ade01318176a1ec83400551d746c05257377d79dafe0e0d5e1dd9e0445983c947223a40ab65dcef9f373b469a651da47e74725e73d077ec8e6965439cee220736b156d504384abb47256734552ea69b7e3c8c6aefdfd39f684beedfd5addfe6a32c8563234d1c9b611a3e59682de7efc3f9cc9523a56e9d1beae5aacf5140103989c2fc295ad7a234caef29f09fb5e6c8108683702b27debefec2078b7bd8f30d63b3dbbe5a09ecc83aeafad2f8a7bca3eb97811049ac0b7062fe9bcfa42ee160a889461a9d8480ec0e816c7beda7fd032f960be7adeab3b9c8caec1322938b0c99fb87ac18e95726d3589a2bd914ee15614c8e2878ced75a91ccc2b267971f6206033d6c40842659c1870f6f464d0c4966ca4eafb8389cc5c979f83f208c3a8d5ea6a5ecf2902a3fdbdbe7d55b5e16bcd1e2d120c58a4eed2ff44c5a23af32a21eafe5274ae675bd202b1781b7b977bc458f31341cfd3a5fcdcfe4d16fcb1bbc71d36bb536bc0b41860ddba15cd57ac95a4ee5a80c2ac4628206b616561048263bf22c5ff297ba0589ea6596df46e90b8098d1606bbc0d8cb5c1374f38568380e479d9d93ec01835b218b3ed3b6017d4f146672bb16272407914d98677cbb03d85075738d915435a98ee133ada2bb344c77f03229762d1a06247fe9559683c493fbb80a63901fd4f30c9ced26ef9665f39a0cb7827a2303ac124d2cab1ad8efc8815588641a00990e4063113cc3ce6e48e056335808fff5bb61c61091b26e2409cc591941127f89df7548fd6e0a8fdbe87e72265b03bff47b848ce41d279cb386505bcb86e1512a82682e08f039accaaa3f1d821f8b8ea833e666c2bedb0bb1f41ce8b1f41b25704321c5dc7982e73cbee39979d9c8b07a7c1ad72f2455de5e6079621ff4397c95624d4c4e44f563a56fb85ce99d6693187bfcdbc7ef206e312f597f6ff9b4fd2ff8f234c3c873d66afc7ed2093f3fa336ca02f5d93830634cf50cec09e5dd3433f6de986aeab6a67dc425b6d72fd445846b305e31347b76ec7c9e7d26b0228e50bc1cadbb018cc32c211f40973cdf5a195e6c31f891c26d75cca0749b4b01b4d02309819702979098e58cca06f06ab16a7ed5b09ad5a57cb942e6bcc010428c38bce727881b00cfeeee52de91335e62b9b31c3c7e1ecf528d05502ba54c4e92d902da8bf41db79a83351565998c25dbedd2dd05c068fb2d08325c480095ba9da00320370fe0cdbbd9672169adeb1c5c013deb0c6f7824e796a75f927ba961c7da8c82de7a2a60552eb63cc2e7273ae33caa3e8c15def0aa967dab2c1235d47204ef60e8ac14ec39c36dc66272a09d368693d64e1a7d8548b501b9d64436f578cc0a91d7fa1ebcc0711a8a396a00f098dfc105b8fc4450ffc77fc1bcbfba6881b0130f04f78fa6199ca3e2ade1e464b2d54c3d9d36f3eb5f6c814e08db0ccc8a76b8fe0aeb85e22cc28c6b76c48ebc9e4c936c8cbfde80d50067932a70488eda1cc5418fdf0c1161aaa3389b47e3568e02efc6a8476a7a1c95edaf4cebc1693e46c57d739fe01def2e988f36c9a2d86089a8205a8f66a017ecd373c5fcf5c9a7677e5bb2564ef4187fd61ef230d5bedb92d1f3376296d6a1f738f8037c5425e256e2d54a054a277593a3698de56f2b439364c9a8ff736e74e993a741df893fd7fe589cb2097de9d4892cd0fcc27df14b77d7d6e2a4e32781d442a1e5c3ff5a487038e8bfe5c3aa06a8b44c53c4e3fea58e58c9d6d59e43f9c455e633b397bf3547d611b6e0fe3263b90fd73e503155d8135139d6ea9bceb09dfcbee6b68d518ef4833102308f05bdd9bf5e872d11da13a5cea7090c6215cac0eab1a07306d73731c952939fe242c1b2898b4ddc289d37ffe24a95ae4157389dc9d49c8b8f6244b2b55150a2b45e149a04005988b148c8820015d5117284e9fb3b9a927bfdbf90611f5345564eac866f1a22c833b2af3582bdbd6aaab9d683f4e45240ff7aa142329b53ecbe889501b987441c07c799f30585fe236fa0e10660b4c4bb9bc5daef4d047f0d4b9e69de3d5198d97343f303090520fd3832df14cf52cb583e3a6ae78a3e313981ecd011150c5f52546ab710b1d5064abd2d6e1a250b1e325e8dfd948e9b4635af7f821fb0f95e8ae8cdfdedee00b85f71430b41905ce800b2fd544a4f19afa98c9ba5019845343640308146e5ba8ddbe8db12775e516ccfaeef95d2ca8eb6b4b7c1fac90cb34a6f0fe87649fc06acc9d45139c4b2d0f4447a9589fca8468efd3465d36e01ae6c6a556df1417ce4da61366f3b4359e082c43f7832b46c64f52da8cd249d3c30163eb948199532daba5c35013f8ff5c0f246adba9cf79a03f0ef80038feb71211ee59bab93d725d11efff58b77a708bca225c6b40eb87bd468051d9028d183025c67c6b9b8e7c1907b8ed204326cc6ec953a385a8b13b532a145d74d78808b17360117c68bef7be55f3d8f9f1910d46be16b86c3f8d810235cee45e0945e111541407b8f3898c8ba21d73806765582bb4928b726323381f05acceeb205f0f65fe50fc70b016cdee488d459c4f6215b24f8e0f5e5dd2c0bab0a1b2adadddafbf6e10a9ff2ecb0478da5d73ee4691874bbf53af8dad139cfb2a97cc9f3b5be8edaf5602ae0f6b76fb911353420ad352d76693d5b827e4604f7d6ccc57556f35a9dd8a62b4676f062a3f9e7f764ca50b4bf98e15c5fed57208ec61e6aa52410365a32a65e31a3e93dede1b81ac0b921612618fcaf5137f0316924cb2ba91afbc3f088b3412f0ee0ea5c0f05c771a6c00f637c2ff4dae3abb303c073a4860960403e720c204e7f1fad39709caa27d337b3d8f572b435cdfee518661a1fab8e64c7aac1586b56135d92d04ee9f4d63bd516c0b8373841cfbc5d25c7e808eab1e4561e0a7a93ef7c731dabbdf6405a5f8b1bdd92a736942ca38c1eb349ee051910a27e10fa7b91ca5c36b8823aabef1560e81e719523f575bd954e8e7339e6bf5a9e8898e34a5044c255519bda83282a5114250c62d9ee4ddffa2f527888c3ac010c8901da01f4464842662a07192298eb8ceac5f06aacd9ec8c095d83b6fc4750ef763061710191f7f4c928bf0213922e17cfd6536e7b33fff955b81169749f1a24fb6bf3cfdf9b97156092585e526e78c3784ac03e78a6972122ec1f555c61ad2cff36ba604390421589e045a8d8b84c40507adf6880059aa38f437afa672929e0c3aab215180009ec6aac1beee8a69193f681eba3c8191780fbf1e20b8c2f1f62a7a115b98eb1e41499b8844d362555183c6e3eb8d203c692595ddf8712006d00f7be4fdc6b42c90b9d761a1a391f2804a517fbd31befa9c2c66238b965d8aaa705457bf331492926aa7fa412072f2a55d43b667c3572a716943480992cfbab67cba785ec4599643d24691e11e90ef8a5fe71226254ed1578e01342370d100a0b908d55a319bd39f03b5e4f8ef37e08f08132b861af9d7cfa2472173224eb4b1c38bccc3248538457c8053a530a8ae51adb598f00526cbfccc4c04a98af0306c82a11749d0aabbd1b6b792271985770eca1f4997a4ab0c6d8d5338515f50a40d8c8459d845a8b275a4ef9042e753d4f5db404563f4b071639572520742d9187634997d189dbfa0de42ef91b8763fbbb5b317d6ae28543b9d112125506f9e16be829ca297f0c1b3df5090a63763051609219350dfcf5fa94678abcd5be8ca8339671a17cd1c03ff374ae99f51bd1603a2fd9322b3e8e4cec10fa41f4cc756bc46f3a1ec5d0f301ed8ec67339f317579e66fb081581c28588ae07f415013c91c18fba3b17708d838dd92f789e061d1356d24ac02c0dd0d8d3ba44f89d5c8068e967ceb94889e3e9e87064450dbffd478324cf4270e13f38285755964f537853fc403042b298e68b8368f58259cd3b0ce9bb468b55bbcde7aa59f90b3b4629364c128dd11106a82541c474d47441defa9a7f9938c439b8a2997211d64c9642a607c27432e8c6ad1ddf089a30ec1f4509bc0c26b922cb9fd16c68cfa4cb6db18fe540352eaead0413a5469fbe9a65cda02c7ee85ee788a1fce28727b3a4e2eb0d320e1b568b040b8c4bb85aec536a096d12d0c2ad7adb391190e3a986aad53d1cc8640f6f42e1375e6a17673b6af547e90ec9d18f0c7e447f8109d111f3446b3c72ffbd4e54d4e95daf9d7b1e57a0a8dd50c5395789b2d50e58a0598d51e682997d6239a6235c1e3c1d0415cd0226df024a5d5a4c27010ab4488784b62d83eb2d66ff073183d34b028a101ff8c223eac5731199bdbb907c02cb845d3abd65008a26b44b5ac8195b12931140e5271483179949ecdbc3599abef333c0305bd4f7005d9bf6366428191c05f3399ce9027f82b5f5812e71a14ed8a77c4d0cd7a8b9084e4b95c997e3b4041a64a454ff9410ceba45f069ec5ac8f434dc2172e147c05c3c255a094cd68609be864c7187b95aa7c5ea772e295581975fdc8624c10bbb847b41986a1a18f6385930a229bda2ed9acf82e043662dbfb357c6bdc288f4f2f1dd9f77ca03ff6f0466b223fdd86e13ab702b56faa9a1d2124363f76b2e59dd73d62910cf27ca79b55bcd0c179e0a1eaaee09b3109ccfa525ba8aeb28c5c329b5aa16eb2b00aed95bf1f3d53ea74f7af1c0e152137d7690c7e59a8a7442e6505a20e556a8c6d0361215711ab69d2ecf02ca479a40cf4479b4d47c8e082d13e4a78a1fb250f3a00e2a7b03470a6e790f266847048d73c388ebbe0576a69a21867b50a56d3240b84eeb1050ce30f51df03facf240fc2737b75f20f8c1f0bfc00d985ab818a44c9c3b43c214af8684d774b88823520d9986bdc0030b48be3261e90744ad06447b62123fca6aea5c073ab635d7de7d15104668d5471c72af988a05676de3d14e2ff8b2dc947626ce4987912034bb469ec6d324f24d2e0ee99d784db08fe7cecf5b1a85aa0558531a752b115c0f2902828ced5ae075662a5715beb97eccaeccc5e2204c011cfcd0ff0a73b3f79512a78d65da5f2c448e41ef030c2b793a33b53233d8ca4caaed376b14f1439904c84c6a534eae9459c69948654fbe1094bcd09848c0bacbcc65289c9ad8b5acb7253c9e63d59047c13374ef98a28495f3dda9c917b0174726125df9f9242e258eb46ea183b467b38bd29773a06ae66edc46e5d3ebb7c30b5c5ba2d6980648505cb5769e37af64ecffc9c9fba4c3e1236ca722b7740ceb09f8f65017f45029694eb6ee9366a50715d36e507b1462eee151948956beb9ab25824611e58059e24bb288efb57bc0ae052fc7184c732962b75c3f599088e0b2145dee4b46cd260e50296eb3c4334b7da4757d30da6ee8a3dc04250ca2ec6158c4b39c02e002a30545b021a93569152362e60a4fce5e2b0adc835fb1fbc91e79c5d32e3bc4491944abefef218349fefe0615bb64ec6086ed398ab56074b98787878650a5d84ad8688c1a4815f5be54c546513d381a6e744de59086391621abc8062f4523f4a3ffe4e9355cbc8ba6455ca6044d64691c215b44bef2ef281d8a2074969fc411ce749b3f8de30346694764432f467d152053262610252f89f67f47204e3152dffa2b00d39b122377c1906cc63ad40f38e4bc1dc49a3606060517cb5453d0434545ea8e02870b859d2797991933fa02d40022fb9f4ec487b812a6b2c551656b6162d6ab1759e212cf2d42872a43e0e3ec372d24f37d0f7a0e6daf41a95c80bbff0e0bebe6488af7971bdee534414e3251f37d3ac799860e7f5fd4b5a093b152940a8b4bfd6a2994eeefb0eba935904531738fa657f40969208c7f7f2994a5ea02f7a8ec9d0dbc9c4cc21d7bdef28a753fb66440fd19f869495b0254b3fb5a76310280719d06aa424d6c656db36ca44013692b63648166333396415c843628f94abe19067717ddea9b9b8f4ec0f5173844da8ddb29f5b633229295c741d50b64d846a6919f798061e383158e3dcb2dc83e171a1a0db89c16000c92fb1e903789fbd75ee15ffdf1e465c35fff4364afbfaf0d82d4a5c1b64c31369c2a72b2f484b7b5c773234f066760f09a3b486519ec1804b14f5ff005528f40c6c140be2b27d7a87ad1cefee41d59701c540c5fe38cb3f408a28ae60fb1f489c352821e5f3eec3b983c7e84f070902cf36a5f0329dea20ce823e7d52cdd341faf293e4d3fca3b6b4f59a01d18d4fb3f0c8ecfbf3645782d48937c02f98d68dc1790220554f573186f890bc1748a4597574a351d09bbe7cbbb13eea38a36032b7520b900d31dfa05b628ef1c880c30eede5673e8ef87be4c7e1eb3e55b866e0a79dadd40aabd51e921593fb3eabad598bcd788bf04b2c9ab31c7e75d4e10c224a11e9cd4ba65f08e3cc21b01dc220f0f6f5ed04294f40b74bd946c45b5dcd841042204f87a0c9717aed7ab3d2a7e0cebbb3119690915dc9aca0012a7ef3130b7536e2e6ee58f192d5b0ac29fc0b596748b9540714395b971c789a720136ea2fa8be33155595b6d6d6772a90b580180f8ce6841253249b30222e85683a470d91e0ab8f4b70bc75d09e98134fd31aea8ec283a8f09de0a7ee68f366764003e7f76ae6ad0b9d7cd06599c9ef408b1dc97c52c82833c5317b09a78b3d65a375e56e4778f4b56e9b52f5b88efcba089e1a16d9a76bde1d1a2b4cbd5482a26a1aad7a7c7943776cf268f8881cfb7c09d80e120685fea9d962354b9b78278d0108992b8e7119a2e11882358e84b6a0ca60e7f372ea5a4288b6d51fb7484338e9e5e9c055cb4eb64f71eb6fa4a97ac46df0ef065bfd2fbc7191135d0314496d5d6f6d0d4ea9d15c51499160462b4659f6f67cb5066df22d47c1472e245c4bd146381a3aa6a735dc80d6eb96ee6631cd488ef3e049664085221c92cc74329a41edc4081a040d95204220ac4b61e7d42aa30e2a0e28860eaada6da05f6e0679e86e822c548782637334fabbe432b30e7dc3e7d8b0a66d3f8fb007fb6a188bf7533a02dd17b61429582066fa45ea122d13a8f2f60397ee7b184d750926b9fc28f8c4a6d27e452aa3b6a52f239f446ba9eb5507c1f3ab679afd9efd684f1845eed7a9da8cfdbba0bb96f3a2cac22efcad9e5dbf43cd628cc9c14b5ca529d572770bdd9507b3de01fc7ce78bd3b4d4fb9ca8ef177eb6d58ddd0812329467668e28bf966fb571cf5643c7faea3059afbc912ced37bbf97b964c2784b009f2928709a1825dcaf8ec062162887248ccd996342aec5b84102d5a6808ab3063cc6f35572a62e1d60575b5670401b140f2f797353a151969c240abcb8a20228c53ecca4065eb49f49c471e4955b3b000f70e0a4be99a45fd90938b70e66deacf1b407386deaf54dc40afcda5a6fc1ef220c125caa2b68bf22ebd66b1a2b164d048e9b91f043cd90b1eed4d2ef84b7a24dd553fc67acccf4008e53f6b294901957b12f5931b6ac2b113317a808dd0198c10ac51bce247a1bc793b8d6e96222823580bcf9d5e2f028cf6116b88784fa1477a281d1478ca9f35bcc636c3f7c5a281d85d147496f99f93eb35db15e8cc61b4d7efc7d2b79128fb569443dc1a64dc61fff640d6f6c766e0d12215657bfc6f62c4750b4505f78ac4e10a64a32fe686b7615b69c5c0ae92c0a22700f846c218276444d40cbb17ee03299ae07c477e25768117b896aad7714be3377a34a3d45c14dcb5d9f716d6a977369d902cbfce970841ffc4f5af51501f3884f3e731b1a35867322e79ccf47e886d3b51a4a280833144b7a5474a04d89b0964cd6901b48ca40eb973c2bf394752dc574425f982846bd873182f02d91024493f710e6c14c9b46c4a1c08594c76df400da0f2e01a8857bfb5a898582d00ae02214b6e6ef2c3454378e5c21489225b05db4ca9352e449d2f2eee4e5bdda8cc85296e8ac50286eae97a543630f61db0d222c49d757cc95473c494f063da26680a3b9513ee5830a644aad338cc6feaa84f8582bed672ae3304b0598e44f3afd3aeafce0814fcebc18e38b4355e87072329138114b99cbc3556467c8619dc2c96843a39cf5423fe3c5f823b705ca74b8e12b46a32ad5bb82ab18456b044bd7f221badef67c1bfa5b8b8486a96f32ee7604e8136b2d3b1463569c224fd5081938db3842338759a1e62d8975b51cb035efb896ecd57b988c9f98db4b8094598883b5774d6e1dee0ee66a529f4138a8b523d5f4f2ba775c60f15d6eae4df50db6d3903c251f32f6e456bfd982dc5d33e1a89a497b79b9dd5ab2b5bd0f546a63e5fd02c775124834fc825ca88a773ed052fb968884c1f2d86d71db0831c29fb86fc8f1f562babc4a55819fdc726bb5025497ff15f070fa2081c07cc1041d733fc712687c483b40499f62515e8b23b4cd6136430e1f8114dcf68937942d3cd88069bf34d093fd49edb71ae438b505b0db13f7fa85fc3c3f90a70a73c1eca04b893d396d5ce548a608b56aac3f461f3e1883c6b6145afade6c7d47486aff547ffa1e309464cd51cc8abe60b32dee670bf29f10c5d6c2f7b35403ccd563006149c698f491129f04805ab7acbd67fab2c9f2bbfe74ad21faa31db21bd92974f4ce58600d52c957c734a6dcfc42d1043c1063cac03d47808d022b21bcae03ecc905fab4299a5ea87db60ca89c75f3d0d2d6df536c884a9d4de24943b1010180183ff4cdecfdd7ff9241fd9a3f61ec02c3eec0649c7ef1927276019a35cc76d40bbae92d2ad134498f5c8291206df0f47f7da3b3add1c53e52f71b8b45a48358c0baf7b80207945d21a5a14b029f29c6c70cd2d0a7bdd7701ae00ec42ed4f9ca8876dbd0f5a4a714e3329225f7429d8bef3b3c99d5804b695a2a18d2241c265321802d0ca5994d49fd05fbc192997f3035d7f355d2f03040518916a39b8fff20832233f1f635301b10479b9428b107196bd671ad3c79b062bfbda63a66242763aea0c90d8f195331cf40e55788661425ecec129d767ab7dc62db50a9c6a84f3822163ddd55f8aaa6f9070b4fe71b9257f7fe4279840e06a57414ff1fd6f14506cb6ae73d3e82b04d2cf3a883b16f3c95d1a56046b6b242048c0990072ed398f2284a98ede53d156c3e4e8476ff4f68ec85cde5dca377c65af6f9b4c88d5821179841d8db743c4295d300d020c28541c4b5389179e5c20b3989843763fcc75f9d857f5e82e163c75dfe661eb532b4e5578532764f12ea9714994ca643d5d1029edc9baaf7000df0d511dac74852796846c1140f720ba2c0b81615470467bc1fbf81af2b421fa74f0a7b3097fa2f612a1c85999e5e965986774b936cebb08a1615d8f89af748212b572a633b99807a0de10d7eed93b476685f9203cf8e17dee0cb5b3badecc917645e8fe4fa6bfb31a59689760ed39378155441df9f182902d7939b78b6dc8a6cd193829dd2599a328ae56e931a69485f2420d909b73d9da4d84d927462421efa566b53b5228a0107d2fced97c73bee3e59fa9b712dd61f4a8197531e9224f95e5559bad5b270a3965b34fdf71ee4cb7b007dddc6e84e02a8610ddba6924b7a9ea6912e07d494c8523827b1894ba6bdb50e17f14c40cf92aa43dbd6a21fd9b01eb48157b87166b72d1c59ca7d6eb591d4bdb5a9bf96433a7e90c7bd05156dbb39fe5f73f3cea106fa4e084904765faae98450b1e28076b2fe166cc501fc13e348389cc00953170fb69d8d17ba463bd930a6b2fac7a720f214b0c0800f6303326f030a2d08d7d76b8d8702a916a057510362d2b2dd0484e9762b938f4b76836b77c4f7883706d9d1414ecff5c94e9cb8cf875985b3e26fa47e7ca633fcd83da2d6d97df527ce3ebc575aa9aef2890aa645f1ee506b4524da4930761a56a4536f3090473a97474df0f1ad17b5b879d385ba251ac5f9a5c64b7d929518719896832d579e0052db554ab831fff57550b9c2cbb03ab063933e05b1c0293f43044f9dbb623825ac23e12b1fc4e674f0098f1acfab472b1594b522515a416917106e990b8ce03c3c34091291c4e8a813ae3685f0ef1a98500f94308a1d756dae7cb2a5f6e658950c04e4c85a4be542a479914dc310bf9975cf4f322f5b52379eb11037ed306ebfa3655f630ce5fcc01e9f9c98166db4f5fe5e623db08419def7fa089d080841320d70aefef0bf2a4509736d4381f81925b79554291f956d85ec7962c30373e2240361958d5fedfd12f05eff5a5d06742e4678de72102b8c0549b00b2d21fe392bad65083ecc6012a327240bbb1ea6a11677274c30acb7e1e5af74a3b836b195388c735e1b1f97a0a6962a55d1d9f5c813896a7c0b1d1625af4bad9f9cd41c8c43d778dd373bf0f9e755d83c2a86a444a63c997ad0447c40f54632fd0670905deb60dba56a574d4649c51f7a314de94b19759f6c873753408ae49f71aa7b714329dc36bc202a6e976d140ff3529492b5c87246bd7fdf145c56e8408bde28f1f9d5e9feda39d0c5b952a073dff8627b7ea805f9d57b96121966ae616751879a27bc0cd9e7671d894aeb0148c3f6c6b94d53e09bc35b44753a9e4d775fb5eec89dd14b66cce6f65938355d55c0c5a3ed5d2d6ba02bf1defc8d29f330871c5200e9ff49c4ab5f0ff17159cc96ecfbca8520672cf8c917a4b5f3c632e862609f695b774ef219ff7bda407a08f15b0f75b81247dba1dc1dec5cb247d8dfebc17f17520556ada65aa06d490ad59fb8e6c49a7d9f7736fa4af0afe222686b141fa0158aea41ad1e8706f3fc1692bc32e2157c2a4e200350c06651d176e9f0fa304d0cc72a4931a550b6587f31c7c54ed61ce0e010efa047a58c50b2a57fe8b70e481b387e37da9976ac9850bf24c901ffce85072cf5865dac2a908f8b882047795b8bb78919c2607f8dc3c5a718887f70d08520dec343fb623efcb9f448c1550f07c1ba075b910c8f916b65450faaec150dc6c2d3cfa2b56776082c831e885cdcb5b69235d4497e65fab67bf4ff8839ed58004fdae901f09aa887e147bdfbf5d65c65b1999084ba0f27058139ea1e60c0d37cd68948194b7c0e8be9f585c64bc6c5100ce9b7a840c1b06714eb6f7f4e2cacf54326a33a49d6480b3742d5e3554f47ff171e187c51eb6aa0976ed496cdd87ed71aa3598c8c8691360e181bb2247e3dc863f5cad877b7a5eb97135b53dd7176d7dd3006d41744b58b861f889eb4468750ee73278ef2b21ca1d5950073c1e926da58250b7aa7d6478c4c784f0bed51d158f0ebe6fe26330b56c7d07eb6809de72265587175e8cb17e3ce22a05ba4500e543f3fce8e5685017928857d4793454fc5166c922f2cc327b8faa5825437b6d4addab460aee7116537f3cc418d47155f41b21132578edc403ea7f62d4e58567f007ff4c61ab1cb90d0cfaa09a8277a97e45a8b6e78b162a717221af6fb13d515431aaa4a5f1ae85296165a6ac3363d61740b3ab36e1edc34e8e73a022d724bdcd3a980ffab6e1de5e3e8e1ffa931f263d5af061e1e56a83d503791bfaf1c2711d1ee8280d94c371afa5baff85b174653d6c875dc2a9e609f4484b3cb16bf99bf630eea650ee31b31e9bac271579a3eca32efc7bf5eb044ea4589c080ec20ee8aadb75cb7bceb7cc6890a3647ab4a28236f08a7d7fd1d82f40b82a5c231143f6261e17307c6095423d57bbca23bcbc7c95d3a72d2c35b76f67af91ebff4e8dba568c277e13978078944ba48ed8b8e1758a17236f7c5dc809936a37e564f8c8d9d732019dc49802a5ac3cc039dc8c795acf1523141dce6eb6e9aa19fb4f72852806595ad795a88ecb557e5e7d3ec0b85d692981b1d577d4c1668b822efd5c3fadd2f4e82fa47e701e65100a11d776c292f27656edecd981e89245be302bfa8e5e81f18bdd1cba40a5ec19656b02e502fc18be709cc1699f1afc16ca9353829eadd98066242e4f88382bb879148e9da0dbe338fb7d083b6fbe66f3c816a3b20ff266889bec9a43932dc638867f85e655f1384c923de503f4c1af2afc68a0a1fbc2297da0d7ea3587929a9d2838c8e09a535aa4b76d6a054e267e3d9132bbcc60447606306dd0ced5c386721e0403edb3d8dc2b64d09f9a239d512b04e0d6b3814196502292a118021364eced07821573f4cf3da7d6ba94ac75a1087c6e7a78dcfe2f8b87741cf156de9d449a58e552df95b6cef5716428b37354f98f62023887376ab4824a55d27b8852181028d24bb517f0fcf60695aa465575b7d16adc11954f650496f52d24cdc67593ac11ca4291676f48fd23a342dd4b685da3332ab5f4991be8a6c6e89c9df6ca31467d49139b90cce8c4d4c06ea3b35ce854f143f1c03bb7596f7462b25f9c8262d49ad7dec663ab49a4ae0fb0991d0a35b6dccb1f2e7dd5be6ab4a33f302331fc2b2798cdde9b71596d2319478362bf842471b7ff025bed5e28ee6c509cf363e15bc4a846241b62a212c39e895aa38478592d1516ee7b5b95c1be73ec1f5a1285a9a09879cfb76eada1f960f23f59eafc89555a69a21babf1e5978eec61b5c6a626d0c22865e14d30c6b72ccfb049de909c0e5893bad84ba0b090495ebb2e3f0ab28a5ca4d2ca62b40030a16c9dffa9136916adea9c9d1ceae7038e2ad8d831a2734b87f0d7b2f6f4358785e9723027b308fb39385b8e4763e6e67fc9b5c8ab22928039684407cc98a5e7d37803e6f4c1acfed4bb1c37e5cc1963d3bf4c743c06633c68fc7dec89c9e05e61073b5a5d4e9398ddc8c7e0bddd5f35923c931b3d44372de55da5b9506a1ce657a24645f93b58010077e1688aa9cd85404b7c4c61971ca5ed1277364535c2de422f01e082a74240677c8e6289f66e51350561b86de8f2aa2441913567d00c7aefb18b124e53ebf057edda8d7f188b653e06ca313c8ad9d723b8fb32f1a27bedf5cbfb6e1186c367a54d0fd303d7bf0f4898164f49652ac71e0b38f0052b202d93218592a76a96372ce426b1b60e2959267c59fb08dacb26e24d24627e63b7bcf632f6d3d75a08efa32707fb9a2bced37b22e81c2d9d97434ce3a6a7b0ba434fcfd6017949e25e117db16365681a49d5cc4fd7363f520380e6a37518eed6759fa062b811c833dda6528674c0ab7c2be5159a62e8c2abede6270149c965c03a01456f581803ae4773547491a52c8b295504bf3492ffe5bb220a43e1cb6001fbc75948f49ce875b3e055c1fa2d496914d7f49e98e7a1678b2351aba528ff14a2252a6c753bffb0a044f76e813b84b0538fa1060144887e98976c9eebc5f89bd03a3b4e04b68088230889623d728f9096b1b29f97066974153456f15f5fe6aa040ad4df7ea73fe6d737acb29cd20e0b87b158d053836670a23b31c6fb27fc13df39f906dc7a7289fba17c1e3b5d0de847b97058c6799d409d78a93f441be76eb8d0719639cbe3bedabba07ed830c55704aa1054e6c08b7b26be44bf6345fd8bac2094d383bb1863e625c538050df18cdc375352f06e6665313d6789299d5692393fd1d468ffa8889a866fe5a6f9aab8288b89f68889896e146a7427c5037697a0eda944a3f4cdd0d98c308fa41d397ca52fbc7d653a02f5b2ea7f08e00831546719dd025a379c9fb87fa5f1eada8d64c1c3c8e43ff861a331588160a9cbf19027d095e9d4d98f8aa358f23ddd8b598aa89ae982d8431b853aaeb7ef0d859cc9d62c7e864e4f771efb18144d34e1df75abc740e2f84820a387f1042f21e3985d25cf354388f0b6632d949cb9caac901631b9318d1e92e7f5caa3842bb485ad398d724f584b910b4b985c743128953147c49ed9329acf4cecbc7ce2673590d9263be40a25f640c19d3df2d27d04cf07aa2b37eeb8a6cc08ca470b6385b85ba5be7a19702608ea502acddcd01486ffbfdfcd5de33045c3656954c9ba1be85f4f2060fc9faf57042bebbb7633e167f2eb34e47e293714a51a3341ad46252a6f6cd9ef476d3de991030cee72ddffcbc953b68367e287373fcdd7cbf2ac1b5a22d8847abe6ff29a7577d09f8bb8c0becb6fbb2220726fdf21c3deafb67ff65fe7ce1f4e3867c8a6a818eadc00965592df155e94bac654d4c3acc5cc779abc3dfac21c553dab8f72515a061bfde0dc546817b04189831efcb38affc2f17725eb6c234247ef9672a1e89c83791321e0852e196ae8094afa8fa1f5152949fa0f8bf1f9f465ca3a994d33f7b8c825872c8dd086d3b38f8d1f7874ed0166668a903049ff5ae76fd66343d5d093fbeea8e8036d7688ea6cac5bef200826943caed7f0811df30412c11fbca212782af7d19c8e913e45b6b7b46888e1997ba42e8592dc03d6e03f03078b42aedffd213bfcb1f592e4dc31725c33e68b7bf48057fea93548e7bc4f65debc9936efbbdd883e40e025bf7441c6e357b4669b008202f61ded44344c7d0c8b5e9c9068beb6537a4c2b2c3a7b49ecaed3414fc8d9a9609b38f623b0d14f1533e27264ffacff7aeea69e0339abde8a9b82e4564bcb9d90a9251a35217dce79afea1f3f4a311340915afa33a7d4a5bcf6d2feb0c2437cfe7d4a5f0d938dc078240d38a3d9b7fed5124ccac30d29f4b09ee7c0ce7ca317a3e1cc91623755f112d9fb510669083a3f20341fa0ea2ba4857485f7ebc7069755aa734ca69c14360906d9164d84c5fa62029db6416cf029f0e7862ad9d025cfb6d1f3f11bdda828310ce5ac863dadcb615b3b931a364b0d34d101d9134bababbbb8273b3e7f2b9d411f2702aa5b6208df89b9317e2d9541c42e2ee339f4988a49fd10310c3b3562775608f19145b66cb5c34046054878edecde7831cf2d8ba59f37467a525620f6c728434484dbbb32cca6cca8fafcda45078c2d3f2b3f27bba120fecf4dc575610d4229a0acc7f9498cca52a28105ceb7817f358f7db4582d9aaccddd98f5638ec5b1e45cbc7a86ba177178e9ac0d22d536df35e5d983e29fd24e654a1e196ccb60ea7c46efc720b94b39d0f86a0fe526a68b8cfb085d8c81f5f8dca0aa9efadc06858e3460e8e18cfd92f6dd29037017899a9b7cbe2a88f623383910d92d5c36579180ebdc884f95ca153a81e0e27eebfdb6315bf4bd2e9fe78202ef21cd02d1de1283fa8b8f07b76dd4012f09cf85d141978e4c8b0ca5c1ed57f5097f1e580b71a9ebd63bc907d847a4f233f60ec5aebfb7d059840d6d6ce2ecda2975ae2459ad4765098b69d0040c23db036a54ec11ff7cd04a06d50dfa6fe90dba93b3a37e47d1dc3feaa34047122dc329d769ecd06878ae8c1e912f0b9773e1fd2138ae19c70d3dcae23265eb67005e042d415aac143326cdb63c3e510632e5ad72a23fc46b8806810b02c1ae90c6054cd09b4521e61b7a606ce456d8ecea77d1064835b88ae6eeba25c81c943a21b365d98dcf830b070d590f74d5c20098ad43b984220bba0196bdfb4bc9beeaa1088ba4d307ce6675b8f1f3b96d9b940b82cab9f7f592673bcd4b6b4291ad1269c710d9b8b0abc45ec7ac7a85a2560a8f3494432a8bcec00a73fd92046ec9f35b22df7c065b2843bd1a0c3284c47b7c1d47437a70136e0f2040bfcb761f6f148eca114cc925b37fac8ec002a44d804f0c470fa8498ade2733158ab1b913c037c87cfb4b92292f790281b4609bdadee8b8c8fc23ebc390870bc157e6cf8e17303148b2a60591679d76f096378cba85a15f07bf357a085b825fae9737804adf64e537426242861c1eafc08e3eac8380ea3898d9d0a553f53374270a4be45a7fdc088d3f8923ff4f1779cdd4c2ee98926b855985b13d1fbcbfa42eacbfefcbdefcc464e88f56aefafd74314526e408b3b23a434c741706dbd60926e0db4f99b7d8b4b8b5e2e2e2757c9bef8567be60500096dd4ddd53b0be674f347f5d224f569a8cfeccaf9e80dc5125931b737873682cfc43a62704b91964ed8096c527255283c3fd0a6b8f6bcb963380a91e2b085e8dacb81e6577e54288f8be899b6b1f0c2a21672ff176dd1574d6bdc98ecc1848b122994e7a6edfc8d29d62d160c796fd3e5fe91772476be0d676724f5a27007862e0764ef792c8e8598a79ff4157d57e80756e57e29a5cd64ddc68ebd43255e6f8c3cdf60f548f9f98c719bd7d8cace6fb37df13903322e538eb19a8ce3e30a37e7341e7e8c511a27ef302814aff8ff40aa7f1e8d96f10cb4991815fa9ab3bcf7cfda0c03adb5e878ecd99f3f40d31812b34e8034a5bdb6dddee350734a9f6ca88648f63aea30722ba410a8cac1f7a58ba319b1c3b04887e23a85c2ac01397ccab62f1102c705ee10bb878d86b66198d5550233ff8f2ed7f3e22d48bd800cbb600a97fd1bc4adc162050330f7b1942b6f0b499d8e423d03e8faba442407dd8ee0872cdcbfc15e2733dbdbcfcf033160c6c9f6384915ddbc2284d5aa34f025ec1658b221e9efb94240b3ab170e633ad0eae39ee9ea6cc1898fc0ddc662319c322ef2d0cd714b8131420ea6c8cbb1c94a49cc877d88913a9a084567aec4fdc0138b601a8a7db0d8190964b867da451ad845da51ff99fa41493aa927000cd5132b0ab93bad88a3e68cb42430c0e3d5ec93bd91f43b260f983936f65cd1a9b4fb1a87ec8ba3b1ca85166cb9e1a4ed2a5d15690d94e798e1ed24f433dcae76d72e5537f6563046e38ce709ee8b2f600e89583feb492df6f42aa17bc3cebb99cf97832c72602813a5067296e434506ae063fd5fb0b731de2e367a8d130615c9ae86f884fc60e89ec8c8191740f861cbe56dcdf5c21256106951f651ecf6f1f34b57ff1171b763c1ad4ce936068752926c5a4845f048c35a9ed64adce76fb7a34ecb35d5a12c05987cb24718576a6f9b78268f6090c4e1a85c954b504bef34f75227f67b8b06801201887692945936b93851fae203fd54d1e11f7c24334c5c50e5f79cbc3d0dfe7a8be447031e4d00526ee6903409191faaf0e4cabe006547bb64060fb846a21a120d926fe4744941e8034d318e46087b91129173cd765d36917878797c0d15837fbda0fe2206d8ec71b265ddbe4bce9bbbe9387475ec2f2ef1302f060c48db264394fa80ef270c2c09da2d995d8a00a4a500051ecfd06d6ee59f16cf4c4308562021ba1091b63506e4d2496cb0f8274e49b96b4d120e815bd508efe975fc988638e3717a514a238e8b95ec5d6ccf0abeaaac310015763c49e49f7b1886b81f46e1534fbc28ca796b233e820b4fc0e6724bd8614055de6ec312dcd1760164ba2690ff3b61785755c603f7e8b15ccc2d7a5585124895786f69755437f6aa29c3eac0666c1699376c0b467a1e1ebf175c07b3ba2a2cd3f78910b9bb6a564d84c1e2782c73a5422c980b68d1e6dc016f0ef1156bcb41782453e4ca77b2ad3bc2bcc1596fd3ed4b0d6f16243c14186b4ca764df572edf2c3115afb12f730666ca0071549d07a2ca3ad2c20a1fbcccfdd1a6641edb2d294d819b292a5e40404a11af20b65ef638eefe150b9bf5bcc73317a6d60e410417724113a629a94f231d15aff1caf940f023b08456a3b4e3f780d08d93bdc58144dd3a3afc3df3f96adaa68d6a4a0005c1ccdf1163fceed06346eb3e622bf2d62388561442d825d597af6ae31726c156a8121ac9dd171d390e74603481852042909bf596f7187b76cf6666da36c899ba466965e802722751f6997754bc2f372a032a09985ad2428bbaaf6d81ee99c9bef3019c6582974926721629d4321dd289fa89383fca693a5d4bdc60555225f11566392ee259c6546bef9ac1e26b8241ad9846efcda391e160b99160b5157407f2e396e97bd163c9baf30a45c887cd6a4e1735a675374dcfd364c1ad5eb078d511a9bf493f7d54089d36915d74a6bbeda99c742e9f034b5e5340b4f5e791b6c987368b5ca6c019733258df6f9f3159d2a298a5c1f76e51f152558c1e5da90deaa4de403f20ee8f1ca479ad61394fc810817d97f9cf52eff06c2e037e3cf147f783ca5a20399350d48e50ed349958706f4bebe6b9db4175efa1016997b88b4b7f7628c6a42c62377d4ebe88c84f9401474afd9b490b760096ecd5d7fe9038a3eaebe08913395cad829669cfe05120df11339d3ffa978aed2e7ef211f05e2d26e420b6ca07f01322c7052a441b5aa24f1223c9a8def535b541e61b3f160ad3494afcd2f092de90a929eb9bab47a078009c9a53da0a2bd6527e73d000cc5431020f15bdd85ad5f5d2129ced3eb02d40804faa7fe885720d8dda80369d5edb5b5d45d553aaab4b34857914e13829bf64654c9261966fd63dbb691494363e6017c3f98d1b15e5edeece10d8040b4c6778c09a43c6e7e1fa7aaf3628aceed347072e5ff219fbbdb6eb9a59432a514520879087c087eea9ce0a6a445c1f6adb646981096deed8f665b8584a6f16f42d5c27611b2c216bab86d04f903a6f17fa24a61a908314125b1e812854419e1524b1eeca0422f5819fe42dc702e41d29420694f6cc892c6b54ad2685325699226699246a9a449daf4991e52b22e30d8d662adb6baaaab6d1549b5aeeacae3f16c9e0d06f33cdf30b7f1ab43678b1677f7f73c1b0cb66d2fe29a2f01a102583a37ae7a3e9dd753708dff9848645c86f4b819bddcb8eaf974de83de4396fd96224c11c66d2d13c6a50dd63a302947f877115edf3dabd94aa705defbafc0fb929c9070c9f361618844d28dccdec85a4bc39ab061c3fca3542ad7612b58d36030180834ea713344448406079124e9d30e188c460d56b163e4c8dd1d6bbfdad5adae358abcdbad180a75b1eb6a5723778cddeeeddddeddd55ae337e21a04704d02c09273e7849db9e1c80bda3a49b86646847b708c3f68731eaef3628c317a3778191ff24e172758c162dc7edfaaac51322b4c74efdf5418879947f7856cc5ed77cf732d2e75ad2e4d66e0df4bbe3086cbc46d3c149740385c86351249b09d4885091eb3f6cecfc8097b03267e2d73900ad3f803a15e71e71a7f990c060624ea71e3a00f0789d0502409d79c741c1cc956359e2063c7c8919bdb4837ec155dd1155d5b6f5bdc6273fb8e91232a2837d880ea27351d39778cd0aeebbaaeebba91c7e3f178b66ddb647c61258dab854bdbfb47572475ef1f5f481cece872c51797e8f58f57b024e152bf0cf3d35cb3b9a060d9c4262cae7b32f8ddbe245ce31f4534ae112e11e13ddce0bad7180cd40ea9ee1fe216a554a107a771b1ca42e5613c798d0ab1f5459683f20b232b028169fca7a822ba8ca476767676768cec189131992756d7bf8b1c63c766ee1be6888a27af79cd6bb4298d3436b7cf9b799363e40608f8602f1836e98d83a20c6d05d7f8b37f77715d8beb322e39b3d74d298d1ea5f1f361c2865e9bdfb2ce433d2c8049fe8ab02b768aeb2fb6c277380be622ca261e06cc654e664544b93e856bfc5532559b1ec60e5f1897d81f423737b55a4be9cd4d942b1959a462c7c8919b6541aaa591d28d6e3146598c41e246dbb62348dc464712f7bfa1530da54f5bfd12e0b569c252080a2023043861fb23c035fe096892049ac63f2626f46cdbb66dfd59c28a98b01bd3606ef464700ae4806b74b0b0bdd1205ce347e2916d6b7074c453b64ac739d8bde5e06f44b02d16f58a25c44a2592a12896bd8f8a0de5f57fc5183932f39c71048140111465deea6696f10517da8936ce628db19014f6891a0fae472aaec71d5c7fa70293dce5059760de7594f418a62937de755037a61b0f836ab2f12124ec0daedb8087507c018b715d72b1509c146b4770c913ad78ace2510947177089bebf6bd13b3c0b2ecd8f43c47c1881b81e6770ddb770ddade05db8ac068daf26f385f5da982fb432be6abfb0ba565416492d93c9643299cc23eaaed55c2a95ab93459290982c268bc9e88b40a2be5d8ba423227655aaa642f5e3944512334f8f3fd0c1ee641de8eab8fe11159b38a97a17772e1e995cef40d1e7274e0ff42010687aa0771b50bc71e32b358dff87f6c37ae383e223c2f5f7a468015ce3ae831376def0e6d33961411fde88e6ed3e9f8e54804471072deb9aace6aa2e14eabaeafdb82e8b3c445a27eae1b2b26871e102e3bafb8c2d01d11ea66c4597c4e80ed7fdc3d053d09c5630cdcca24e2eba2f9c604c19d7b84c76c5f0316ebb7a4b1458b82d8bb55a3b57aa4872ad56b550c85a97aa5659fc0212c51d8822155ce36f43f4825445528c4552e401d3f8fbcb5452ace213abf88395cb7cc7bd10f90b2e85eb1f12312b20f4285c7f1b9ec5f597ae488a3d341598c61f89d00d6c105f1012030b4078e0b0b0f2146b2e03c92c16f1e43b91343defef5dfc0ac699a0f7772bb8e4bdbf37a171b870a973a7fde876f7bc9bb255e34857f3983299ecce952c9298a5cb5ee9f23dd0099826922ec034fdcc85aac4f60c9a50b5e84700d3e8d802f37e25ca0f3d97305f38616044de4724eb47048e6630c10e07db656e13c618ae749ec700f7f1c3217ec4c1f3ce3870384cc1c1f3f1dbc46e33677ab673eff992f873df4cbf2989e7e337d3df8d0ea6d932a1feacc5c19f7b8f28b6e787c4f73cfdbcfe84e8bc203ee7118db866bec80b2b67cd01ba27830eb74e2bec0cf1bbc7803ff73fba796edbf462bf0621edcdccd03df743bae7be2453b64f12bffbb66f48f749fcbbfeda0bae9913fc4449b0f2c38f1f2b9d5e417e118b11c6cccc393723b0935fd8f62f5ac618ed17b20893524ae78c94ce58a3284e4ae383925239279d944e49279d91718a413f4797e3e278afdeff289fd2b05e19075d7a3f64cbfa23ca3869d41fffe3231cf4c3f81f3f5aa07f04618eebf30bdf47a5df0d78e76de639a2dfccfce49df7521d599df6ec4bb7377d79530e02731888d1859629877e620b2b35d394fe20b46ea279bcac38d8d14fc77d3892405ef711b5c79670a471fc8340f3f8b07c68cbba4262e66a24a46be63595758544fdbad63ce23b124dea4d6e5bddaa9452d64e344d94f2bb691e1df7e99eb0db13f673fd76338a10c491f02f1d9d73e33e54e4ad22b8a6ce561e0e085f5a1f99f191c6e9562f41eb86c25a1de9a6c56677773a2708052466abd5aa6e72dbea5665e5a4a7fb745252295b5cd33070358e5361239ae8ee09dbdd2f36ec58fdd04f9d0bf11c36b0f2b7207caba856d8a6545ee148ec0bf300c24a2965f7f1401f2ae26e75a249bd45d3cc20d038fd650b246efcb8ea2f34004dac5fd07b272f3fb8c2085b708107ea09d1017da148131f86289c564ed8b0b31076166ef4d30d0f3c73c2e9e674bae9d837ce830eeab43ac9205c43e794f5930506b0894dde924dfc1fef47e5368e9bdcdc56ab6debb62ae20b12b1e9745af9670389dce48992f4922378809972682780f083cbe4bd114d0a1203a05bb17d4d206f2b92dc89477151e1aaa8135127073f2cb6d5dddd5d02a6f130da861162300e8ee6e18fa3c7781fdedf112c4edb5fc138f1f4a526340ec7a5fa56d5ee9faead8a8d53a479f8cf4e32ff22dea73fedf1ccd37d8ee3681c1dccc37b1cf7f3f7e339734d7b4baea91f19e3c74353b85d889c71ce3a6b8c31566baddbb6515a2bf56a6dea0109bdc0355d802fd4c5ce90f7fe5c158d22c9fb66601a7fcf62b409a92159d56ab55a0d0912d0ac999bc66ddbe846bbd25ab7ba511a29cb91ec1163947d7a60437f5db7c1e23630b7992f63acb53d5c953b2cf921e435aeb919f5b8d9116385abc562514ae98e118ee3385624b1228d1cc755aed46bdc7024bf27d890778cd8e047dc66bee7312e51fdb07212259627386d8a5415ab2b602f07a5d36f918dcb5e047c87209114a35061d5eae08dcc11b13833839b629e394cc0e502b00da54c30e81fe2b83ce399bf654cb04d2bc1b4e536cd9af14516ad3355eb9c73ce39e79cb3d2a9044bb94b348dcf280cfafb84c2ce26521e45b3454534b138a8258c1dfdd01a2362138d11b949cbf5a781f58471e6a6985f718ca84d51c4b7e3e68d11b1a9ebbacf526b4c5f4f65c399e2b2d5ba6d33156933254f0e42e192d499a82e369c29192a6eb7613bd89952c2b2f763a6666a862849dc828242cd9443ecc1cb04c31022e86d94810fa8dbac3a6d0dd1ad6198a908fa60449115599755ad0dd56835b06e54b061b35256dcc609931a9592af48aaf1345054505166111316036b2ad11525b27c60c39992f9195fe39b9941e667fc4c7f43647ec6cc901a1ffa1a1f7adaece74b13c988362eac77e04c7f8dee97097d8d2f898d0f7d1200fc8cd7c144433403fa175e07538c6806d4d9108db62b581a2ff322206c979a9981c6c73c0664bec6338e1a49313ff318a8f1386afcccd7781c684c1c1f80c7c0cc0be0c3c1c6cf7c003e1ca60cb1f1338f03001e47674394d3e30a36e66bfcccf63a98a82bd208d1a1b0176d459a0304615de69dc6e733f38ee3dd0380884d315f030038be991a2aba7d3313a8bfbd0e37462aa22219dfa21bfccaf8220d9a074c970bf3531416b9309f13ad28aa60bef8648698bff14362fe068d2fe6852f89ccc739d113cd10b1292462538c884d34ba4fc83fb1a794dbd0bcc45a9c39e82e51110cae710114d7b7309b8856e825b8cf0d67ab8b619022184cd3c91b9b0a60cc142db2680e7adcf9c2ced4e748e31aff2e78c76bf1e435afc51d445664c52cd189e8839899a2551c74eaa22d185e60e387d479e0128dc6cac2c1808511f31d644b58a99adb8491b5a2d1bca4669309e54b644d9f59668a6be60ff38a0c08312ab028b10897933ba33839588b446898a9ae61a68a843cf3d08ce7be980f41b7f14d554ccb66b4ac4616685441e6eb2d66cc142d66be68b3fbccd46471892fbde16c4d29b11559363eb40c7470816052a4451a6f9cc8126388248e60d03fb23c92a612ce510c26aebfcf17d7b87baa65911669916524922218318c9d1d76967891365373ced93595a231900ed663f0325fccddd2255b9265ebd6db16b7d8deea768264b15eac178b158233078d98e233b789dff216d7fa28e30b1bca4e4996cb6da4152e6ddf754ad45056556ca122595226a7b82eafc89914b11536942c09939fef1e1472ff96b0de9a7ddff711c99688e5921549202859208b6e3af849bb9ab41acb8a8291d4bd7f1a003bb0d3e2d545f4642092011aa41467c9c0dac2b66ab74593ec275b9dcf7c06f35d0085b2e059afc2071cc00de34b1ee086f1d53188e8729b3006115bad2a31462b3706c125d6d7856507a713969382d6ba6ddbb67d88648049f1ca4bf67ac5dc30be5876854932ca8bf5928105856be2a64567b9fe339e39a145b9e2379e20595c23a3482b5255a0a638edc82bb8c6d5b1497f506cc8b4193edf3d0628066ca481c27e8e965198c65f52b92e81705d5eb9ee4956a758ed4a75aa53f3c5f9278339e7fc7c3e1f4ae9a4f3f3f97c4694d239e7ece1a4fe7c54bbd13c1ff77d2fc4396984b1c562b0d7bffe35e7dce6165f91f4953a6ccb84f29a5aaecf2ad79fce6df3bef9482a116167fdaad5da5048b6c00b03171b53602b08cbde6784f55fd5d5b6dabef0b7554c4d1516b799b05904977670295eff199bb348ea3e04044ba5ce28dcdc895ed8ed89839b152cf7d2003eb0fe7c398a2e5c6edb4c96db843175533eb5d84272d30da7943bc60d638b4b7de327aa41c77d463f380f88df6d9b1f3d2022bf51e55dc04d311505dbf160d914bf7faedc26fecbd5625d8f22d912c9c086b235a16cb096dbc46f7be2a02792810db727dbebfa6f5ad8fe30a656a9a852a55253958ae08d2c95c72a5315695cc776cb411f4f1c74c6e2e9e964aa3a6f015c5322029ddee7864bf1e30f31753363aa4a3855b2255b32bb4d617bf27d7860a76a968a3207a74a0a9646beb6205d30933298491acca4103369c42c0bd7bfb1c0a54d62c134fe52b8fef10bab85eb63cc18bcfcfb0726c9163321f55442515a45b43ad58598daa61853750256019b80d4af2879c0530906e36a3261c4759eaf364210aea726cc55515485a52fd9ba79d5dc6e5dffbe3245ac8a9815b1d754bda66aaaa6aad61d23071dec566db1cd0710a5b3810c0b103b302b56acc41839b2a7eb4fcf6284c42e0a892410308dff16d73fecb8e6b8c8c58e8989d93eb5562bf7484ecdea2aad9344d55250fe95db6a48786b8ee3aac771713aa83f346a95c2073a34a4c43123140242103233ac9db36eb14306084a2b5be48832bc1ff17bda281f3737a36ddb1a4581202f24923acfbbbf17b93d90cc2259d56a35e7768c7adcf8e01a121b4ab89b993bb2562a5b69531a69ec6ddbb656b54a35a9bb8d0eb791e2a43afb32bdcc39e7a451b66d71dba20cafb25d6bed6d1b218049120c491bdd91478475976b676727ba7622084a54241941a15ed2a6132e3d719b3058d2e4f6a11f782e993b4c9611ff18bf21dc9784fbeee3fe685c03055b7feb264dd6228df770e9c541ff3837340f5f010b1a87abbf936af73e2f22918424d2344e4ef390bdbc20c9f97c5efdf9ee4f0dc9e713424237d16914922f36d08a741922631ef5c8018670442dbad3f5e8d445b1d026940965512b5445a15c7f19455068c1429fd0551435a1729b761b896a169394db44978c0f36152342511445538d436920002736a42852acd5da900f360036a0a9bafa01846f7f32e5a05314d7f8e7bdbfd1c38b75d74db90dcb1bb24c004278661e4fcd43f3ec7864d7ff46b760352600950d23ab968a39d8b558cc16c164c5c513a594527631bbdd2dd7bbbb259df3935d8ff1c186a35b639ea73dd03be84b9283a9c240a02f097d2501fde73b8f6846f4cdf4c3b4e787783c0ff32561df9e7b1d4c3a7414f5c2151b6759708d47591436ac308a1a82dbc022699afc6bcc6d7a78b15dbb2145f96f35b7691a4579fd627ab0ed9a32a47bef4bd2d5ee874d53c21ed7e455589483ce84a6fa0b47280d8091c635f091c68d60c31fc51b273676fdab169817fb1702312708174ba5891bd22154081136e458ade2fa6701a3281445511445515477b2f1454a29fd7a924b112ee1b8fe466c9032923a402bc235fe9f13a4eccd49acc52d77cdbc702b553ce65dfcc51b07c1d2206ce855dc4626817a13ccfc2458c42e65e35227650ae34423e020ecd410d7047d4dd0b7e33c34f85e60e9875e60528ed320e0728ebf935a44a790d0a42291c697e8aecfbd39952291e44b348d37e1f3854e8450fd4287c29bb88d045fa2b71697a870a9fa1242446887a2bd096fb9b7ea87a2703b91edf5b75a9ed06f7b68ba66eb7113631e16e8c8c7a787a5dc9467e592dba8597fbe7ce504c4650f24b3fe39ce0c84d58a15e3220506e5b5a56585ae56d5bd7635eec9c27237fcee6bad5f3f97bc15144d226244af1a87da20d48496218108a126ec12fe7586374d7bd5ae48530433c7107694ebcf79b7f2e0fa3f5769d9f951fa021bb26c031cd000976ef00126f5e949f3f0f76853f801130c869a4707e1c67558f33084ebb18fad0ed6533fa127da4f6606b80083880cb65aad56abd5a8c7ca52e166a64c9bfb65737222a94a493b0f1555aef9175a96d69430c963c34858d029cf97e3a371b88f86e6e1fffcf2726898afd7e49253abf461060cfad3d0384780602b705919610818d7662c2b66f3dde3e68185a5b55a6ddbba3098f993827262f22122ab44b8f35c91b922658e48294084694d7a1b69dc00d37dc05988d0b972743dcb105ea24966334f3794b957ec8d1bb24cd6cfe7f3f9eae0e73381e8f92244081b322dbc61de22fd34f847e446caf20d43a0474185d828fecc32c4ebe9d787610a1b72145f50b9a22996fbeebbb9f9978076846bfc6d11d6238a5ef480f483b040c041e7bbcd7c60e10d13029e28fc206ed3d94c520cb20797e4fb1549001def36954b5bc771de8fcd0697526069304e7f29a856864b9e77cf15eb5d7bdd779fcfc7fbe1f916c944e172f6084bbb1f1a8d46a31df1bc1d0e8e1cece1e08d833e722010c4c16e193e88a1dc85a5091396955635143a1a7532274f9cf8cb10979c8e4236c0208dfafb236bbdf4a768042d9271ce398af3e5acdf10a7e98cb07ec31010d6e6c086f6ba7c8fef38fe7cc51084d840dfbdb0262463e380c1216c689c3e1de1022f5a57282143316d368aa25028140ab56d3b463d3a06338399ebe82736d4d0a76ec234fe12a3f333ccbbfba5773d3cb0a31b3f9e15e1d9ac8894348cb98a2d66d7218bb1d825bea2966d4606ef3ed7f87ff7c726f3b76ddbb61a5aafadc6c5a5261ac7f3fd963eccc370c586b1359f348ee7065c62e2243fbd22c953339aacc99d4892f1fe13758a246fd249999c359ba44bb6e42aca28cc87f2142a854aa15cb3fae578626722107ffe8caf03150eb5d08a40709a4e49711b260e63058b5a82e212f3c7631173f6d5da50282e81b9f10718d10cb771116f5785f58f556e186bddb6214245d87645527479a660ebfdcfe1a4c8c4458c1397300c5c8a33f8dcf0bdcb7db515bae185b0d8b88469fc7fa84e228a6bfc0a1bc6566c6d1f7a1f4ed38f058b6bf12d22601c04308f7e5983112e4d0528a0fbc25a3d5f6cc556addba8c7cd5cd950d2ac6256ad184abf9eb4165655a9ba9b9b1bbad55a6b7d52f7616dff4251b061e84a5291a8c4f5ef2118076c33df85e052ff4da4691ccf97a479f88d77439e7d5f6e92d4f79bfa85355f6e68641e709266c25f51dc2074872269014ce37fa35255954aa552a938ae0a19e260ff27369bcd763ce54fbc7054173f7913dae46a4339399184a4f6e068462c51a06c9f56713991e43f1a816008d6b39e71e9b3a104dbb7ebba6e3ebd61ab26f390e17ddfcfcb077d731e902888839e811d52a20474615b3509543525e9314cec31f48f4056a6e9ff421b2af7db047a04bd6d40e87b6c60d8ddb8efeeba0f924e61b1b5630efabb604abf0ecc2b895d389950e0f6e57432a14226985702533b9d4e261d1d2502a05513cc2b71c02c6482791d253cc6eb64820168827918dc98604c30cfbd6c900f0b9d05d3cc9eb0ef1a62d6a63b3d836189ddbe9909f408e6f7ac023341d0c2ed1cffe00e6f5a8f59382a41dfee5974d9f006c4041bf6cc51b3d96c369b51ea33cf65b67065d65167a08f500eeaddb6b1043b9271123b5713e7873bc21cd7a9cff67ecc2f6caddb3665a432da3ccd79d2d15698344b67d55552934fa78e0c82b147a3ffda5f47c4dddddd3d09e80bd8ca94c388c90f5ca629fd21c36230fcc0facf990830974b9fb95cea3a7dca9175ea0607a9128be408f5e00006232ceda0b0f23671d053ad1c2acb7c3a9d4ea73ef58952a7755268ec34b7711dfec5dfbdb8bbec642097fc9238ee03eb573b8f16d6efec9e5e3708d391449fa6c37d7d5a2455ee0b400dac90d98d1e5e2ccfaea36ed846c0208495c16934da9c3b68758c8e99274f99acc7b03559173f317a778a42987843d75b75606a24b5153bd7df690d6cd8ad4869b2586beb38a9487f09781e611fa0f72f82821b9c04fa680d0025712793ebfcc53b2b1e366458ea156b1efe138b834da4b0adfa9ed8119020ec1d9c808df81371555c9062080c3ad83c8a500029322292a06a8566383ed40aa5a4a452527630d26ad156abd5ad6e756bdb463d5a33a4b85caf1d2f586656645ec0b65497ab7dfb500f48e7e1b863c3762921c68a3cd142609c2456669f54deccfbf629926efa497ca2a2a208dd4a4214ed84b80385b5723da9f2ba5cd5e572b95c9c886fbb869cbc2e4e2754cb0a1396ceaa4aeac9e90482b20623c95a2e798de35dfffac562c1e5367d1b50b047e889f66f4fe9f6fdfddbf71ba91ff24e7df0648db06d65db28a5b6e3a55e05180a77fb3ed5ea606f2fbd1f5b1525c1bd0d6ea4af9123f2044b7d4ed70dc0a300bc8502c0c6a9410938416aa05d6c100c02c1a07f0d8d83a479f8105d584a382276da69d970c4c869e774629d4ea7d3e964b1405f1cc7c5da49b264151df9454a296badb5e6c076dfc1a049978508dafdcc5924e6863cabaf5a3b092781b5849429490e323b28d3e773e8b8b032791f6b50dc660726758d4b325c9a21272c7f58721d06256c18dbd5318fc13f0dfaa6312fd61cac5dc023c39b6133d948e30f33051b46d749a2b8a71c05b2dd30db33dff3f15c1679a28f8863acd185edbeb0b7442ef10a26512298c67fca941ef4e086edb272c376f911b1567bc2054ec80df4d6e73e569cc6fbebedaf5d4caccb89d30d252ad6ac58ff6e2bc27d72885061b91bf2dca813d7db65a579b812d207066740ad4820e42a0a39c42bd624ab5dd48adfd4502b4822a96b55d45c35b9721bae166bb1166b1e0fc735421c1c4284be6c00c1ccb4b1d056df4412dfb0870a0b739d524aa9bb870a6b61c842686665b15a739cac5c00b3b826a6c541eeebd4ed20ae7f6c988f9c9b90671fe010b007901841e33414be02140ac54385a5fc04834180328486122b024bc6daa4d06655615d094d11aa227473c38ab1582cd6b6c154890921726242c310a6e9f08cbd906ebd05724c08970470fd89c0000a283d19a6582a73847dfba9967e433ad50d79f6b2b6880d2152b013f0dcfe402e794c888cc562b198903977787c60e6a992826a321b89b551eff40cc1728c49705f9f37fa7f798aafe2adc899354a7b38cde69b1662778cb17687c57a37ac4dba35c6186387c5c65e71e9bbee8574b0729b9614a61ff6eae670e5d50e3329a3bf6f6d6795811d503db0d36bf0067430dbbd6ff5e7ffe5571539ab55575fb93c7592a97f90684929e58e9119dcc43dc3803e1eee330203f27ca4b0ec604ebfbe70f46166f678a011e89b3ba31d5be5975365fde837bf1d233b3827288b52564bd6a54aac61cda55d946bb53614fad128ca6ab5f61f0447a366f58b4b7c7d764faf1fc6a5fe1fa6b84634a10b76b3a351a86d121c3721063d58c294433c220c93e7bf54ca32d7d8c993274f8ff60cfdbef4abe7a39f1107dddfeebcad4bd8203b18b1b65a6b592c101073ceba4dea24c65837fff13f291d758a76c157b0204290e80ca100eff0175917589066120d1a39187683cfc042b854e3bae806e192288bdb42827cda415110075d6efd520bcc11fc4990ab7ad2c38b95715e95759356791031ec7c820dc1eba22dd81094f2fb85041171b121087acb240c0683c182ccb9c3888e8fd8100e7f99c383cc9e93ba8e3adbe9597be9d8a45cf274308d3febe8f1ea973c5dff26820d2e729a5ffdc9d51722ac97cb59a8e8b2e58637314358ce4205961bde64a982c9cd424595cb598ef0e2e2f6eb543f1e1ffad55bc4729dbda4384bac4a9f9acb7c65893de186bdf369653942ec86ed456e348c56a415aef7f0aa3199ae80df5aad0d85fe417074634508eb1e1020ec20783ae108c5d3cb4ece628425d8bc765eafd7ebf5dab6978582e338e68e7c24e9288119638c2e9c4c32af2383fb9890679f1f8c2304300ed83cdc13b14946c4261939ce711cc7fd370e17f27e8608f41f519bac884d9c2889cfc788941c9111c910292972fa3cd853dc225cffb000d73f2236f5dd3e326e9a216a1367454a723899bcef603ec7258ce805bb9e08072433433eefbdf7f97038d271a2d6e260f7f2e19182c5e16a975feed362a5b07cbd9f7201192118a3c56f00e8fd6788580121cef5070bc0a53700f3601b4a039530e8df34d063b8eeee340ee42ddce71f49344ce38f802b15641a0eee73d41d5686877624e420d708f1b8c5c2e3f1b0876745aac7e3f1783ea311087ebe1a699cd890675abc97306157b9eede00506fb9a410278512383f9847949b273a52ba05e5ca78fb31f67432b90c5ba4eb72201084020e1ab1e1c80d48224d7f441ee794d45ab972ed20b88542455253a9b5d6eac9c0846b9c874a7bd23a6e93059372228d8eeb6134185c0069d9c0862db3f12f12699630e908e7c3c3033bcaf3f5ab50a47893277de7dcaa7c00036193152690538324f4175360d05fcdc3fb0a4c03c5f5530db8d0b296a3aab85a2d6ba18e5454ff509d340a2593c96432d9a8879021a998d5687453c4061f3db8cb4b0b6d853a236c7f884bdea871280fd73fe4031b8eee0cb9949147233a1a8d46a3d1b66db5be55881c63c766668e0fc3e50771649a077fbfa00a1760a61c1820c41096609acf1f120117f1174435780cccd26336123bc03cfcdd3fac2132820d6faed74abf9b1b29c1ea25c618afb07d638c3146902cd65a6bad52f610620e7d90213970dcf80c001036e4fd33925e003f2f2b43d7fd8642a15088527a84945246dbfaadeb6eb08a31c65879d63ff817e19201a4c93ecc69f3d8a8a24b72e474bad1830ad606f0869f3f810e97fb7e90c83ad8fd9d534f06f91fbbddf2f35d07deeed37d1495a0c7f8bce78a9d119f8b9df48000e10bfaf682f0057d33de8f803ee8bdaf0718a0a75f1f06e663be1a693ceed99bc17ecc10fb319ff7cd44a0043dc609e483bee71aef0b6d002fa7c3fd70a28f8c7ad9ebb82f6c6c62fd7360439ef12039e06bf57ca1e8f6733f2bc27156c435d25899108ee71a219e837630a71466446415a6d66a63ce39a70e9348245598da9da304db452f3a78716fcf8b2ad61491b8754422e97dbc17aee99085ebefa8482272d2c18103a777a8748db805356534020000000033150000200c0a860322e17848260a728d0f14800c70884082583e1a48b32088611ca4903106210300060404400433ac14053a62ebe1d49c3cda53fe7eae98e3e643a94226bf60417b85a91e3e638bffe5089ea7e763537b32c3cd1b038c3ed906023efb9e63f336e064a8a5567cffe1dbc0c094a9331d30f3b69c1fa8766c68dcdec5dbf79c271ee0c74b7ffa91677432bea0e7760a7cc1827b9935fc8bf91b5072e98c86924426c89c405bcd9ec76dbe2abf8b798d697cbdc896c50bd10f35b60d3eeee2e3a9e6833d8c3fdc9099ab586de87e245e3dd53f5a77d96d350a46ee4a20ec638faca8d75b58722f4c9d5bf121a79728d4dbff51812d25abf793941e0c2a3e16ace0527081eb1399986398e835311a9d843c292e10d193638c719ef7cfd7f6db872fb15176b6af55a7c41259774b743e7789008dcd33fdb5aeee4c0821d8133f78bf84d086cce763fd2975d25249e98900256f18c87ec3ec65ba3796c66396f3d40cb3e4c9235fbd9c386e20e511a6494adf8484fec0ac303cc9fd5ce9de955f3b8c3d890d84fc9f49851918390692050b930aa0579e9ad95d4f67abac99a2156e8e93dccbeff8726d19dbe1f738d5e6ed7c81f89e00e560d99582d3511fd4eae2c29a878ccd80c50717ab9af4827868365075a7a67f4f981646cae036e7d550443e5b091d11a8dbb68935740a15ed5daab9b4b9c564bedf47b8c41e2b26eb9f4fab1ebcac8d42f858421a2789cb6abeafa8603cd6dc3ae1df90ee6c4ac2ee834dd2688f12d65da7c2fb57c0b5720d571f17e5b89b64333557a8403c519ee0153a81296d23cb115ec9cc8faeced45f44fabdbb023c37503c1bda0f244dd058a0871e98bbd4e82b4b63b49ad2739856ea36ca901473eb0b125b68361cadce9e8fb4c36654b25830f9d53498c9060d377291e29da82124d2f2f2b081a8f4863e004b0616448ec2caf20752c8ec48e23aab98bd5aa73d6e853ece07c8aa7c44562ff00118bb279768075bf267ac182846526963eace6824eb4f159bc13b95b78e24f09182615f1f6c8da9cedecf86c906d856644d1778143198c830b1d57beb955188d86a275864d4f5fa80a4c72e30164540087cc42ff9727c32262ea5fc500adfbdc04c1a29e92a9f226a78adad762c357c9c06f4635c001d00ce22e486d8041ce8b8063ddf6937531de8bfaac2e6631107123727a9dd72003ae25618b18b7d7895e442553a98a6bd0b626aaf43ebaf5713efdde821caccb1fc37d15039cdd5081133f990cdbd4572ba8199fae0fd85cb60836e247223e06cd98b1a2fa39f743322774f5ba60978abce004c9f467bb2bcd1b1d1f16bc0c0b1bfe4380594a60e8c292610bca3f87e4b333474b25b02f8c15541743895ff0d42318117dd915595f6cdc4236582b62383e04de142221d853474510a33e68c682aba033e40b58ebc76bbc6290c30773b02cf76fcd1178b3a2ee7f8e8f0fc7390ab6e209eaf8cda14e00a8d0df50fb9901c367b8d55d2671cb402de4f72c2e7e2287ffdb07044884a22902a1b17269804e76d75db1de3187ae9943411682352fa2fa673fb0b818702808f2048681c5785b1afda62f312e6b7b34085bd8e3327cd5a3516ea969af67a2efb5fa660a86b841d56073fecdc848b0ab4820cf1ba6a0518546fddc3aa69f5022edec05965ae0c3dc47e8a6e85fc73ac1b4e09da48ed3d53a56bc3688a032d9ee5c8a52c55e44998a3c187ff1d5957efcbe0dd644cfb9adfe76930432b7015d19ac16f64b416e9a0b4d35431c27fa157c4d6546b94469717208a9ef5e444089ea933e9cc3c016aa0d2d87cbb90bd340d5d291e694d283f019bfc4f428959d736f5b64ec3e26bd24ca3c5f47394d134b17a824e181534f7c4c7016c5975e862d77f7c17203f98155dc4bc3dde2706189ca8a0f30cdb6ff0b7ac52c187aacf776aca195613a013c29d387817a57895e7df921f24384c56e9bc980f61534848c2bf683912224128644ca8599eb24a5afe099b80a72846470d30e9667628615161a4e182ca3ecc012a5f463a792f424412cb371d5289c610963613d2c89bf147ab590b596ad168d09f959ef77dcd554a927e8557c038723f857f9a6fdf08cc80a557b738a86981da9b15998db868ef528d12ae19edf7e79192f43b0030a00dd35ae6422ac970c657fc6f05d16c62d285b56ae153ba5ebf61882126f6416070e6d6caafc044f1410cafcbec35560083280eb41ae14101b312a1f906d0803adeca8d56e3b55c8b7d59082f6f0967ca9c3013ee94d68236909f3592ba01c801f3066d9e8f53db3ee9917d641c7afabdcef143a35918ff1e0b3ee042966632845372cb10a3d3d7165f0781b38fc300e1a6855ff14a54d9e0b85da30c8e917554f58acb08ce62b3b4d0f0cc431fe514351b0dcfa6ee4a38048fa8e95ccc4ac6a6bf7187a708329cbbfb45a20400f766dc5fbe3f8bd6c01c11363d1d5403eb63a2e564ca500c7c5ab9e57b63b842a4ca541f88c27623994bd1223e82a53e4ffd467bf353255ec48c98d1304d65a0bc0bdafa55e8ddf9fa060cf497c2745543ad5855612761105814e8c8dfe28965282f1121502ac17f9e23fd242b5509870abb0cfc3cea02d28700aae84c53c47ee4bf9f48611b8c30af99f36881d16a91b2626e9fa5b2fe34126128a51b518ff14868931744b9cefdf7d89295be661474a494f2305e25a17568dc11611278160b9ea49ac7aa0c4f9d1ae59aebd032ee838a6fe11299727b97cb6f0f3891bbd69cb48535d2f68b8e28d7a7e27b8cf0b8d8e20ffa142478c06d8a0bc22ac0d7ba8b3afc491980c809b0eceec3fd899fc402108979470025ebe8a8a222f98194fb3e9316e16ef9effd147df3da0eba96289ae5ca9e362b9fedc74695958a965df4dad6b72fff8d05d4a4be04e5dcb923077c745526bae38d9d6774e4e7e63afd4922fb947b0572807c84f889a32f90fb771a682a9bdf5155a31e0986d469b0e69181d19624d0ae384985681c49ffcb2e141ea8440f30b0d206d9815d7ce97d04067f1001a7f9aa4cea198e2bc91f6310bbd35cb4658d06a1e560182dce36c20e3ce539d4aae733fbc4a2edb6a1cbf13e18d3c52332047fd2f87bb177025aaecd21a3d78b3f859ad953338f7d710c9944a348921805137c51cee1ebf3b7abd2bd2eff48bc6c2f9f3bdd0b32de2ab613313936e325cbe3749ffa7a6377a01532bf8303a282701f102eb21647f7cd7fe6703cf7f5a0f1fbb0a0ecce432b02d948eb1bb99f850eca8db8a04a2a42df4ae6d8640c26f51a6926fbbd67a5c6f408c0d2229665949e909f22b189f71e1b69207e6e162b7c085dc9b1f84c419878845a611d6c7e1500a61d132064056bb5329615241cd71aa62ee67df9700cf1ef5725fad2508f59f41c94cb7ab6362247b7d9b9914c94602e602c81e1e69b2746dfbcfa05eecd43af1277f83745bf2a2bcc8105fbea6e82656bc44a25427fafd91311c34049fbd38599b17536390b654c662572d6180f070a5e1a5eefbaee7b186f2a51d0f94eb86423f7049c09867a6ba0e8e831c963a0895b087d0a72382505fd857e22f100695c6b2db6d500b563e0d93810de15bc602be8ef3e3e953f6f5071a3cca42145469abf001a1d644e6470733ab6f9153268eb85fcedc950150ede8e01fb480db574c85a9a2f49e5df326c5bedc8ad6e28ef29d8fcf5f0188892809f680888a9dc93c51f3f66d98713b45e19ce33b05c4f11d7bb1fb015bdaf0629ffc8633b1a0fa3aa3800fa003a7f72e4a60588ec8743207b6f21cf9f96b0fe20cb33d3e5361989f0cfa05ee8ac497760cb21e4e253876ea6c690fc812946908b79e8a354dd2665f3415cfa23420fee8b62d1743447aab966c99df93050992cda4ba6125f713a111191a8220b177f575b71426f3c25aea18ea52b6e24be13f90de47ccec53fc53d328697ffa8601d6f804022c3cb68e97f047cc83abf819d3218d36ec4d9951ade708a04561c7eba07edc995ed404c7c1b7f174246a2509c5aaf325d3ab9e467377de354d7d1802a79804da159f2fbe2af88adc376df0046f15a7c6bd109475608da1dabd3043aaa2d9fd190f62e131fabe00eb1487c39431aff841da124546423ce668fe09ee5500fd5abaacf527d06e5dcc128e77c0f530ccaed2e9c9d4b0461c8cf5af3bbefff458dc6aa2eefefc4f69e522bff8463d0343815021966c9f5aee60e025d943f37baeca3e0421500081742a80c14d9ee95d07f88d88e62e88640803219da46a860f1d8aa905b12356aa842f2d8e4bd536e625d3488720018d8663186c8cad165e9f3e44f6b6ca5da5329850b9332056d0ecc5c1cc7eb691c14d2aa9344cae648988be775ed2a5a3767493f9ceebfb7e54268d5c133addbd8c83e93504f758d452df4d7387168061150bf8d1c2e562078c4ceb31eb453672d4e18c45a5c7d50b3d76f27351715f300aa0507b2293d88489abbce4edea2a30b1295927f983f4e9fce7b547a49b1f2dfa38a0bb207e1a3a12d89eac800d15deb523fcc44cedc16afbc04e160e26662b2da8f05d238d6b81fb8abbc5c40e5c7ab175242d54e20fa0f839d54b32ec3ba87ef63e407746998e6536fa169dfd074af8a30a42996c6bc6f155698dfcc3ae14b76c556f0c2e790ac3ed65101d3f746a62e45d12556afc0841c038a7f9226ac88bff4c9e13eaa2ced3719a7a6cdf72771d3d1420cf79af1945e6299988d9630305f06c1643bc4a42d090e97a460602988dd09d55a0f3338ddb4f6898290dc1bb69701dd35b1457e1a0170b56380b06e82a7e86aa2ad2dae523308089e49c7b499da12a5826b44556984b4ce04b44412848a638881b2275c58d00ccce7200eeb5771b9895f0ed4d841fda00b28ca8cfc8f9a3249ecdfc74398fcabc720b8d458b3557ed9fc641f213abf5a7856f49394d2cb8b1a3196f55fba01a9691fe8d5c96701cb0d89f20245c76fc4ca49b6467f2a233142aad5ea75b0b4599ee4a86bf48f546db9f59396f73a836c176ea6544b4a640d6c5cb5135c29dc7ca942ef01f1e9c93e1520c961a1333f1625e04057ffdd71386530417f1a5d7a7b6c91ca2c870f23eb8cce3d44f66492c44a8f353f836afba51fc3d64c372c2e8b473a721254bcf0bfceff3867966954f999a814c4a5d0b0b154b6a3aa5f83770e14de14c8e9c390d8410ff361d5f0838dfe306959254f62e48adc6112ae26527bb0062c12ba5efc99a6fa8fd341bca9730ca51a730e8086ddc4bfda344f91bda9685e9867f16964af46d2da38c4fa40aa814aa99d13749bf4ee6d155d113be71763d80333ccbe9ad85090b228552db7b5bc3851cca82e80705b740df45251a2b3321d7c5ddb0fc8e064c802ad6907067bfb6d3ea8bef3726c87bdbef9f5cca7f30d644a1f5919df5ca9f58d02dc53007b2c001b05008efd46bff4948e11197283f84526e333ea5949f592a79c9a487ee2deaf5aa02d976a25db0710717faf8b196f6df9939ea8f409defac02ec7e97fad3500286567a1fb4178d785b85619f23ef685ce551434af46516459e00fb1c5a1b88c1ed57300fec042a57f70f91aed1093ecc1f4af0d1e34f494adc503a0a40739769cc95808f2f1e88520ae5b3f5940995ba243a12576a917d1beee263657207e7ad94cd2b06b3068c011c60fb1ff9dfd2c07386d0aa3c7f461d382f9ebb705aa3db04f03b651ff004c518e63f7c65baeb517b0845d83523f57feb52cd31a0dc803ebcc521b34e3c58081033b77650efa62e9187d2291b314604b21df488e64f4990c8990a6f36a9367bc838d6719d2ac2d1a2d7d667b56511b37271a94e6ca3caac8e3a1d013a16037807ebcdfe18bfb9e0f6521cb87e4fe087283a3875e9e60b550a0ddaf9eb2b672bc3d3a708b915eb573e4eb36cd2b7774b21f8a3504794586673342f0e8b13d13e46855f2aae9493fb020bdb826bdef0cb42a5aba683fbe60c48146a1a77da9187a73b833550c8bb91060d970ac3d2ec5757b6353eac66c81f4a8e488fe6eb69e88086ca3633b5b3655a07c8dd2ce7cbbb26589b4b8f21d43d723f80f0c7cd8535217eae639934e30caa982bfab4a6c3b38fdf4e163110414168ae9a527a6605160885dc7199f8cc3d9afdf7a369f26742ea7df8486badc6bd765a2fccd717800d46c36962271b06b00dbcfe576984c58b5e7deebc5b0d24608a87799298632ef373118d225bed47cd647e298643587c0a430b01ca837c52e0788df4030c6b52b278bf8b5847f8498a9496aac1b54815cfb50c33e5f0581ddda77c7d95c38ef4d9ff0268aecdd02d7b164bdd7267b66e91f0cb5533ba3cd65840ae0303838d390668185f0f8b6235ce490134eaa7e76ee062c82999e666f30ad251cde26fa4aa5746ea2acbbc55ea1dd785d357099c6b0ee1686d9d4942aa96081d29bab3545acbbd2430500971ed86b1f0b2482530ed2ebf14f7bd4ad3ff94eca7831fb70062ce6731ed80454ba3bdf7475c4b919f13c491ccdf5669416d170e0c5793df76518e18c3772e91b5ef1c6095fd9d4bfe0d973b3666042df85b6e02f1973eceead3aa3502498508f7c082d764a1926122900a2c20f4366592563d1b55f972488987214d63853a6036339fc387248f9da2ef7214e7991c23a16b04431468d795e3c7b2da99257f678d231d8b89cc96a26a68c4b8d82a451fd8ad2c79546402bfa365c58f8bc31137fafdd03be7cef9c594a4b7f1ab17fbe233a665c1f071e5a3073c1cfea76b9b1252409bfb16ace86d006880d08f0a035f26e689f27a77e7430d41b77785b56d31c307d2c0b4b1a33d8ad0dcb042a2330947513140aae52d98ca629b717ece28ebc04fcd85c549ec66f63ed8dd952f1aeffc971f5469330250336e774f057afb15b224c07716bc2b2b33d1c490a67ac82f84147c54181cccd0fa76f5d3cac128c8dd781158ffb581ad87c8e0abda6771283d6995cc2e27a78bc28f9dcb6ab478a95f01047a55321a3ba8b0eda1db2d9e8eeef611a0e0501a83cd3b17567dd1888a1f0aa7b72c48fcdde05c13e5c909eed65b6fdaf0ffe305445d988476f4d518566aa03dde53ea7c45cd1d63cc700457b20e7999b1b04eec640c4e03c0250477b4c24b34513fbd7a58eae9ca9daa3edc905d52069886e01351a9d344404b52337532253dc27b3685bd71cc26f8626e9b1b1f8f0d51fcc83cabc8592099d3241cb7af063e03d71f688938becf2efc9160494a2ac78d5074e928382d6412719339a6e7b07234699d5147c1b8a985495bed6f49926844b2300a0c9f7aab8445de6796253f54205e446f2cd0113a8c3ebb7d3e57361f03cd71a21c2319efa7da6c0f3c417ae916fbacef331ca8196f8d104e594b55be7f9a9a11cea92feb65579f2faeb5d2d2895044ca2979969e3b65d790a0d0765aeeaf254e1370465677b6a2366602d1328664f23693de008ff39441030f1150733fadfeb6c38cff28519b02a145a4c6364d05a756b1e78670fbddedc947e13b6ae175fefd15a10564252275495f6b34c996bcfbb743644cbf89a69b252966f9f4047f5f21011a90f85bab824dd2974a5125f8bc6ad6b4a80090105e6f8342649fcd4da0d86ecd4984e72783f6d1e657e3da8b9cb43194c59fc429709e4e5023e8bb5c50e864d27a11a6d3742b5acadc4f23ae21456b9020ee694b7de5663a7a86670f34d5217f72564eae30bc40f510c2c60b081230adc5c2aacafd1e66f9568b5703f6446c504756ee69733fad92e9aa689823d0a23b97d46b99612182c95bafc9819ff1067cb41922db2dbd3ad5bfe33b48767a688aa33f393abf4933263989a7a564345d7c83d0548487b0e09649f112201c3ed470109dec0296820e1505b76575d44d4befff5dfd01c1fc49b010593470761108bde3a441c7c4fdaafb190b564170de9ab9df051e7bb0adcd9908e391dbad9f0ebf0477675ecdf311799e69b02ee8318bbd2a025c3c0edaac4a3592b79f62a4837314c11201a84f31e9e6d958e57866040a5f2bb06982cce859b06162e7a040a5b33f2be5623200756880a328c9a19ae924fb7c896f5496a60340abc3125eec47590fb92dd03ca44299e8716bea1d047d06acc421483b6e41fc46a4a745e158aca19797068758b833d51a5179d53d42138f6381735cbbb0c2b54426754a62afaa6fa98d0cb4625e0b03ca0a3d371cd2e6dd1ad6da6d2bdb297f5f13f597d5b7077ad97108d0e98ef8ad3321cb4370e8bf374dc67e3e00030171ee1601d2f95086414f8c70209dc1716a50219e35992df51d221d1fdc9a18a45e8b29ac81e5ca93d30d428216c45440b8cd926add5519b0054bb86a61b307c22323015401c2eb728da840ef047d4c2abf2996a60944aae607e0e052139c6d12df55f4e28505a16c0e7ac134af49afc298e4f7493270bfa502282bf2bd621c1aa9383e0a92d5858523b198cc46a14067c1e00d8de0b32ed1c531761786b9e130d373eb9fccc6eeb2eda5d8f75785b906bc1c05906e0097234487c6f9c1bce308eafa1615256edc6b6b87a2e70d81c86576634bba14ef1b2daf37a283a5f4cb18ae78e59b20362d53afe4710bcf560be1b0bd378dc7d95accdcd00d0c880634eeca564b4796895b914c7c506238ae3c395a68137a2195e000451995588909204a746379ac80f54b7755f9c430f7dac534b39ad28c28b99e8015638307e72450b8cb6d56c68b791582e86757a7cb90d014c783178b14b8bfe53fb1abf2a9111591961bd0131261c82275d1628a06024cd36e44e0e4fd5204894b6e17086a8c8ba36d0aeab3737556bc35642082d351adfa2270bc1faf0ba09d661ab6cf443caca1997093d787a97413cae68482221254ca6ad099580274db1646fc195b4b105cfed9fba2d3ccabff992b2e05a6aebb5d6ebc7ae40ddde56c86763007485ae72adbc1375be6925b889e3e02259bf24b1aa12b76f9ad65306490fd405938953c69b71f50d702e82e4c451a537200e3cc349f342daf3a4c3c7ccb0f15e616383124ba5de8ac5d60764c040ff56c415220aea6222300c018f7ca2ea3dc57d508c677b779fdfb326d77d64b7cc573d08f2d8d48e79cecd62f080f04b96580534a807432fe4e4f0433472e7030652a9a146b463f83bc36b46c41ffb48b1325c11a3538a845639d98c970e1445658a013a8d9a72ba36aedb034b621b89d757e460e32d771eb95782d6fc4a726c9accb93774b04d23430bc9e39b9c2181878fe1ceb632adbbf2df4fb39fc55a73fe78cf0a50a40791b2f94d84e756b7c29e6f7f8f0f4c44a87775423b21980066231bd3cb306d03a86ebfc70a38aeecec325b7f9e8c4ac1494ee261f2a283ad8a2970450d2cd5254dbefa371d98ba1b27bfa0284bfb1e4d069735b724225ad6d93912466c1a74f07cd9843d4c411fc05540c4777283635a8ba7322b10910fba436c84809e0c1cb773f53860351cace24da7f26f1aa05ae4c0a1a1cfca141c8d28c05a16ac2cb83404860d2702cccb54f37aa462a600e70546cd3e426f7ced4f257c8a47f406b1a0696ba3af516dd6e02e3d55df110ee1ad2539d3d21f7fdda85ead65d32d69791e0ab6b3be2b2a3f158a38b2d8a25ca8ceae350e29a573a3e1c28310a92bfd4cccaa5ca4602788c2936196e7b694c31001029e7e9131abc4e57be60c8d60d90dcb73f084ff7735d9b6b40e357f528f934cc700c8d873c4018b91b15293e280528bc1abbc95f361e5aa23a6df9f4c3ff3a5c6be857892e384b0889b5c844f5f0c63a698c04bff8571655ba39c0d6d98bb355d39b4eb75e43715665d93f7d840aac0539d367298834087915c53105f9282082bf20629b93072355d70ab1772d0069349560a1901db72e359a81848280cbeaaa41a6255212fc3c9d2c035f185fb6c3f0a95daac8bbc6b7baeb449199ca6e671f1116ff046fc4d2fe77d654cba12b157f2f217f4c9d24de8e021b95fc4bfea89bda26b27e2dff3956947cd72fe88c34e647c24f80a64f157eb3537a44f4560089e81ce39f4814c7c921a1fa0c5e85666555bdb78b272bde309a023bb70ec66ca3005144d6de55edea7ec7394d1b2eed9087eff331eb5bc5f92efde73c5b1449dfb5f8d822094a12624a6de71d2efba4699d16baf0571eeab785a885669f7a64c1b5cd34d43a2e27d12e5a726b993525c633796837224003c7b3602469c8e509304f21905f743b88340ed228bb1ec4ad54bbeb07e8216d08e31b295c69dc52ef205e8ebf038bed90be5f70c60bcda7782207f3db2650d2154d7d774ee622bc31ae36232a1563d1067b4eb0e46aac2fb174a39c0b066de4ad7ff88b6625f7d7db6823b3c919769223f5971942dd7d3c9ccfa912f38f6096ada99838387799883324dbd265f55e9f8b5d34c3cdc699464517ac47a02a9939a82aa997f3a37696f4fbd9555e4189f787f9f71d3d290864aac4a1cb046a33875f5b32e9096f6969c4948b49ef670d0c8add2dd394568bd98985bc772598d18c3eed9327befa440387134671d89c27a04348f8210988b37304870ae817268ea4b03bdab17d757423f12a239eab66631fbc9b8ab917fb0d2dc0b89ea17114f80e01006014f1608233a4fb1f11c9d1b76e4ec072518d98848fb2373b034060fe6b635dd7af23d1188d3873b11a5eefbc847ab0c8308b41eee2d3bcc5fb3cfffef4a800eeb7a48b65d6991fb630aa53788d49e3abe8faa94d78b2c4f9bbb2769d9501e8c5303be66cb20046b5d82acbe1496501670b2e05439b70e57ac25fb3f018d4d37cb45bf7c42f62aa5e1e221c6b9b28407e6e79f9cf6d1b358d8461b9ffa992b0d40fbb18c5de6aab2aa76e3808fac00e780342c661ad75270afe59adf06d299d15461ef5e081cd4e32aff98ba8f7e200124bb361f1b0116e66e6595819e6eafba9af907db765da91d61dda9a00156948626da81e037589f371aab04fdbe959b4f1fe0d267b311dc59689b8ced5e175a1906f194b21712770f3b31c4ef87a826ae9515df000e88dac119c99004266fc18303621595fb33b04b0b8f6306103924ff5d26540b087aa20df6d04adbb9c265cfb6d23e1de3c43204f48533b6d6a5e7df1827e8f8152c75f7bad60c0c4d3deb6b3de5392798ba377cf6824017190849b04e2e14d774ad42480797d80817db91e36a5f26841809fc418d6695c867d0464db2d4390f4260b4f1bbdde97802cae777bb192f8b093b3ebba2a865458d262b1a1ee4d826bed829bfb52369e4a13307990bffc22269b41963c938b646935bf28b90ab1cb32fb8f5ea03229c8ec4610733b4b09eb5997aecad42b68e119bff37536d9c6fc0e240892d488c8f4e26757f640d94d07fd7a4b49c7042f7aef009a8495af1b9aabaa32b69bfb611c4bb6f53958ee3812cf95c091b0f55220cd3c6d8539e3682de051b36c348243bb6f5636ccb51d3bb4ffdf525e36ca342d7c45c5e112ac40b02af57da520e2ff87c06fd575aa8bd9a2128b9902fe131d9c39a9c7fbf496b50aed58cea85b516ecd60996ff961d073b42bb01ea4b14b6b8608ed3901a7f29302bd825fb74f5054bd1660ecc654e76eaffa4cd88fa28caf6256ec32ceb2c60bc50c1544c0fad34d0a51effc2ed614630e777d126164013d729701fffa0cebd3f6ae55644dc6a0798e2a068fae33840a03f6468b5b22b60db75c6faf8c97e0f069fcbf8fdb60c9cc5879357f0188b68376da4d6a0cdeca36e729a63e0ce5f59de37e3017deb575791c57e9620fb2de96a7d8f21be0a7301939ec61819b8ae52abc17fac40ad022fb06304f71a40d20d7784408a4cfb10636de17817ff6fd862d7ac0bb7c2b640b5751078c4885f4fc28b803ce31b20267443e1251f6f248e0607299a9d912e73a18dda523911c36f840c7fd7064a245478505d30994d090c2291627f44552ce0bbc8a37008de2a018a38a4c55ec085301b5dd89d51ef0981523d72f9697d8158d3d030a48ca9aae08ac397545e443675925f75cf51cda139d3cf07e73397fb8dc520884214c292b43d343258314c471f4068682a463877f12c8581681ae56116198752e6735176004d5934c99e2f84f9072ee8d9d153a340fe80906c95d55a8c9cecc6d502255823e95d971ded6d2d6a1cea2f0496c37e609195b0d0e500bc15228be89f59c4d5599a4c5bc19e816d4f6067aff643c5edafa203832bb50cb18980704e560ad70f57546496b34f2d535c779a2a51596e8c7855c05c0e7cd9a8b916782abc2430131ffc773837f1e26bb650ebb846377daa39324f298956c08ff5cf0fbdf2a3307ce0213b375696e2be34ded5835a6a2d73825f79c92e74add5ab94393066d3821526a7c720e83f80a44abf231a3222f0dc7a4538d2f9141f7d5ca2be517827943108279dbb22385046c5ee71ddf7ea41e16b077016028f0a18f000f821cd83ed20afb1638be0296efb9865c816e6292d4416e165d8f4974c87aa9a1f9a49620494694ffb742a5ebe486c5777243355ccc4d942ee5aa512d80452427b384eb5f8dbfb2a53814d19ac1bb310b8e47e243e615d68d9bb18f33b6c59e6ea4cc0bc61a2a24672f23d9b1430d8953f7be0dba7805714cd2984c13f8a7f55932ecfb1ba008738515170f305854a408f2f25b2780a915575309959024221d8d7d3501a3538b604d2472a73282228eb7ab5f1f23ace15cd85aa28581173b847650731cad92b488f04a9cf1bddb329051e1c411aaca34467f5d1c6105c5cfc059d78f33ea865c8f3787b531c0603bb1c6374ef72adc8106690f86de4806481b7a199ffff55106eadf8e6a2349d3025e9c4d6bacad5b6c569f519f2e85771d601c0014c28c570c17659b9ea605010700789703e086fa669d91ca726bab52998ec221c9bc9bdb144304adff22d3738dd7328b4c19f76c16be3f76833fab29afaa5b0c0b15135d7c3a80421a150c48fa4f65792d9ecd9e14e7cbf07530f0122f58a1159e1bebabdc375861850d77449bbeb5ecdcad20f2b6b6619bb9e57520d187f8eee371a0f01c784d1491746686ce3415520922ff10b49cc4aeabb185a9f41c449129b99c51658acd5ee7418027c7feb40ba420ebbfa2e759895257576d9adae4119ca5949d7ca113a18a1e7aaa4a0f5574b0128fda51ac7f91453424e4550ec0b7f3643fc4f7ac1900803085384deb4e016f21bccc1007cebd66d52ecd217adfcc785d857f162877d41016f197b4e2f916689bc7d4b205625c4d348165d917dbb981816180bc59afeca4184c842973693b6568f36e477a801a8b4e6d6c81710167cffd5929afb761c2101acbf754f7a08583f33dde4978af1af3510046f0db6756684ebe3e1401d81c00e20f3185a863e216efba675453b202d28c62e3491d6eb63c7d37aa10035c85a7517795a330b154c7cb2f6083c136e9fbb25c36e6fb192d8fd5d55c7d99f40dcc6518a419cbe4e4bc143aa301705733a263402ace984fb039ceae69907c022f7219788edf9c91d30281ea6026149c39e2c208d6337e76289c62d7febb1dd5101338d03c489b6adc2a94708a556c6b3558e49ac548eb29cb428c0b584cdbb554f3473f478d86a071e67cb2857ac6331c1a99c89537623d4ade22a5b8bb3f58c066cdd5a78245b54d1b46c5508e9aa5ca480d6c0197c30ef4ec078d86c7c72b9b43f53201f6bcd62b5ab8d7b67b01078e005fb58ddf52c091c11fc63a04db4d4f5ba97598ba81cf668ca917e6bf1b021779ac9acc81b2ff42fb06b2cefd491adb7d616bac7d314212a8966e5c79f95bedf02304968fbb040c76d423533b96402fe7407535ae831ae5cc893342eaa58a7052658c43ccdf677c5b9a0a729ce66716d1e0e7138fd5713c02d3d3aaeaaf2430543c12610caceb5e667c3eefb43e9c4dde55220ecaf3df2d3e55fc660fdfd6d8f4a399394c1fad386e3a30945c406ddb95cce3faa541aa011b4fceddf4f4fa5eb8491ad0598cb7f35dab27f8f5d0179ee5c3ff96d970c92dbd67826be8b92a1012207fd80ccf0df4f7663abe11e21717fb20a130a4b7cc3f2a4ad0909836f3255c844d43e9eec2e91a5683962f89c0b8c5c7818e376b0dad205bdcce83f18f207c2e7fc4ea3f106ce02ae803ceefd8611c8b708b335f8c2fd637200cb678123037135e0f237e6f9f18b04e30947bc0f0ca60895407b3e65dbb7720ab08f8fa46332dfd0e26a4dece89b67229f4a44ce1228e404b179442133e9a6cf90af982dc87c8495ed2f55e69c3641533bb5c644f8dcb161f8373a87f8d90984853151252daf616c94e0576a0df0d7d8c8ac2c6df90eb70af87ba0ce497d35a1b0b9941beaa5f6f17d3866a3bf3eba92347c56e7195483a6b66dc4d504da7b010674f4197340b44bb4cdd871c20502fd1caea9b820fcd47c828afa01021027e538b49239b9a990ef86730c82a4a9372e956c04a92b2ad355c1b423d487c3cbc112a990e72f755b721057880a4690a8d1f92099757ba174d7d887f1d9db36756d051ededd04c9b5b5020f15eeda6e02d5bea38696fc277177cca7dafadfb0184ed207ad0f840e0d27bfa626e48ade790a78f6f0cd5a127e283a93ada77af832706bc1a66d87f73f7f70b26859093f05ca7baa3432cafd5a42981ace66c4bcec1404856bef94b15c0af397bada8e30fed61f41681050cf98c9bafc5f6b94d8e9adc8551a31608eb43ff9754dc6d389eb6c7eec194c6234735aaeb5ed3740c5712e0a2c4d550bbbd3820d8353c12a80089856d01d1f559bb8c3d85d141e571c7351aa84001f802004f06e06e0ee4f9418253194a80cae1f68f3ea80186a35893eac6c32be4dfe0731b4322014d8b666a58081ba67c209afea223cf0750746672e268094cb4b1464c4b47a6b3c00476b645250105717a8ed7b42809e35ba86b264e7fac5d704493bde4bcbea7f7852649929b2eb3f1633f1f9fbf8d7a208dbc9b3ab3dfb9b2449c00afcd03ba03660811f9b6bda0d92003dcef87b32e1856e5d1cabe9568beb8ca647d0d32f9e2b040f0c30ae8ba71c02914658d6b47e31d620e7e85d4c6e32acaa125dce72acaf3c3af1770bf1ec9a2c88cf20ab6be60c9a44b0a092432f54e4d31abd701d5515cad6f30f9a62f20a5b983f39fbc3aa7997b9e60fae514f7ca3a18312f396ea699b7362b25e2132c34c1657adbeed960afbb0aac991742f204aaccd7f80432ad058ac4062b68d5d16e8ff8953dc32adc059dc572baa1b7495a0cab58ea3eb14e0103ad282a56b91ed5d43d6e0f18f175c98e02eda980b864ce32368c60381a9912a4494a376d874de059734e878e1fe264c61451161c4d303c98339c530d2a5329245ee2f081964883cfe76601fecbd0e9cb22033e74cadb3dc5857a37c64b5e01c1d6f809f810563b38d000c7557613475297eafb8e2587192fb9cb048869ac64a48737d4971363c80a054806df784cc5ef30e083e9ac6eb8c1701b08495a832340270a4b604a9f2deedeadbf50a16f12aa03dfb2d8009028f3e9baf68732fc24a95b65feb3fd0c25e7b52b021a50c85c02830f7dd0729bfcb440fa7a471cd5ed864487bd068a0a766b5e15305934d2ab6caa449ebee6c51869a064dcdd418306763257289ca32e0a7be9a93e866f388cbd85fadb7d68f55e346b6f7fd5837b3a71ac6e245b39c1f1bf48b0937dcc0045d32a2c8f9ce0f4a29deccffb65d35af6994a24b743137efda26c028afc0482ca9034ef0005a8ba5ec7c450f486b84c65deab89d00acbcaffd3191026b01017b56bad2969c35f79fab7d7e6e555015ea9872557a551cf7636fa579cbaa67e1867991c93db45ec52ed559819a3701d6daf9c104dff2647451dcbdd51ce1f837a86dce8a7e6708abce62b85fd8d8130c8834c7446c571139b3df6a375b97a55556f7199c74787303703de9ceafc1880179a61a075f80761836b98ae5502c1dc9d860a244328d1a33229c3fd7b61fea92b8bac334cd36b971071f38aa87670dd178417e4ef29bab34e4a22685f57e1cb12c899605bef84494cb058b1458fd683b86517dfc6e142c603e48c6cdfe3813a65b8c8fec9fe86a0a80a20deeca116178e5ae71cbf4226aadb7098aa9a87265437d62583528de7ffb1d26c887e855de6befacfa6ad6ba55b26dd1dd27c238589144a2080d8a1dfb3979aa1c8e0e1691ad3911ba66b1ff705856e0d451f8720d2b05964fc6a11509ca76b3060107cadcb45fa3785c418348321caf7470dd5e52f756d89ef4642e241e06cc8626af2d0db5da0d76ba074c59a0f8496f45a1d55b61dc9abe52e250a34f6ec79cf353998e712c62e9e7782c24b68676813113269263bd5a36801f88c39dde6f7dfef6ea4c3972c4958fb0fe63b268dff39f7b3df2481ea4acf1bb5db76fba5c4981b7eab77db2c23de2b11e2ead7db601cd1b6f5e86b5e6c37418763555bc8458eff7e802b18335c72ce12fff68ee581e3e230af6a56bc0c9dc99921ebcbd1674bee1b902237f9ab70edb6b655e82cd7cba88b8e2849e2f15dd206721ad142984f65ef13f3f143e5d95c25e37c4672f3efd4393c0f26b5280dc330e74a704fd6421505f65a923a4b21c40668102f7b15dcd0f82938fad13c4747c591a3ee84725634186877219dbdebbbcb83b98118a4640fe93305b571e7e912112e63e1aab96867e438c612ff5b32b828b09dddc147db76cffe1dc8b6542d388948222c1956118a203456e37ba6178300fbf711de38c758531b004a227f1fdab53a826adf3e6329dbc6cc72f3ae57e566c502fce9aaaf7d8bd42dcbbcded253e6478429bf846db7c0185e590d391a1da5eadfcadfec21aabbbe9c8e97706c6eced6f8b1960f41e6086baa9c2f4385195b124df5825dab2e57afce2ce59628ef6df2f5c644de08071db5a1ffb3d061c3ba083c4c874bd9d040c87dce6d7039550afd7ba98da24f7fd96a07f44040639452d1e8407272a137c28233a650706cd554d37c5f44466dde0777c801ca7af1b3e29d38e2b32321cdf8f26d269a8c1dac3f5e95f0393d2fd0c1911bfcce84da0112d556df7b06748c14c66b1b2910840991a75184b669b0a83bbd65a4267d8cd65400e4adc7ec4c706a478ed4d5a7923e8bf68941a6f397b914aa2137daaf543c73f8415329a5277aa90f6a632d1bb41ba4a9da1bf741b9e640dea902c906f510af80843fb59f17dc8dee8f27c4b255c0338ae75918311eab7100518f536fc3f1927bb56e3b186a9526748c61f7cd768fa1ad1e4376c6dd8ae91391a8a7671ec8b446b47d8d92a27ae5aca8d20368870a731aa411a2ae493fd133ddfb2a2ddcee5a8b6ac3665a6fee0c64124f07339b24d4ae9ddcf0f46ec28c636ac7fa209fa4c8cfac5c4c0171df05e4d8c69b8516816b0df2b05862471f65a7612da344c479270a78cbbb54d6f496a2333024b818b0d21910658e6aad69ab2c816a8d9af76b2576b1447f613039a5757d131b2bfc37351642e3ae6a2b8b262d4160225f937190bc7bd8b774edd8eb2d12108597a1e5cb05213fdb0cf0e1458f16c74c71105ad80a14ed1680dad776e6213c2f7a65cfdca20cb7517802c7d30d25ced7df83a0dbce5212f427989db4edad7622bd731d9d74b62a318858e0586547395d772f5f7d0ab376bd6839b9c15be9c111181d0d5c3cd460a0d1273bbb818d07bb0ed2a9dba9cb59b0e839bc040bed2809aca1b069a83dfe6da1b1b800bce62747dd66a3d270991335d0a80f1fc555a6c52b458369db029071183296ed98990f888ae7240bf8ba8609111b01f64dd4c7f9df805b81a58abb8ef49be421820093b54efa715f42dafa6e71a205c7252caf80b72d2ae51c0c017d40218932150304b2bd548e4bb009aa5a968d182f9d4702f4141208695d230ae6f4c8f7cc28698d3b0672acf0df2c36e57500429b460cc89eec2846e14ccbb2c2d569121dbd8fac0f8f291c02e152e0f51889b02e88b8469ef16e37354f3f63fd038696aeb8ae5574334845ee23ab2ec48cd066aabfee7ba85ce307242abf794d2128608510bc60a4774da2c9074ca44623460ed6da1c958aa10a88deba50b1df4d783618d2a30186845edb4d44a24cf3aee719f0db34cba41ad30430db935c2f30d276f11e35b47b6315e82534d07f8b1696cfba72581e5fa3cba2e7befa8c8350d68315c7f758d87da0ca0bae484b910babc2f0153255da145dee6546d8431fe487998facc1e8a18b4b2017c0f8a993b08a4f853d645cfdd7f12294f6e65057717656971d2bdaf8846350e6a7b4db5565bb91a6f13eb8421f1bcdaba61f5da99fef77b9b8b6ab5dfdb0a1fcbd2624146e6c54efe99002a7d1ccef3a6a049aa79b3ac7a082c9a9f6702df0e8f1fd1c3e0907d13ca2d5c9943be4b5465b0a5922f69f1514bea4cee8368afdb09264d20a92b4b0d54a917b8554efaf87062f2e7c1441b8a6a44eea9fb61fa5d28b2dd1fd20e88281f1ff52d248786693efa499ebd7464b3738aced1f499222af7caebdc4e21c5893461621afffc7edb52f2f928747875a10593dc114836349b219122553d8acb0eccbb105349265538ff196a8cfe7293dfa81ba58ca7b7979ee3300d90c5393ac4a82c6276c57f930346deb7721b993b1bae09e47cd6272867e5b236920ac3efb9b9d6b41e29ec033a586071e5e53cb11e1a8293e1b49673624225597c49a63755c2f53f95e7c5d0857afb31ad163362c4f8535e2b2df837d64c0b6adaf017851baa167877f63733b6715993e3105d6b1a8f6202388fd5d6ce021ed6bd30f7c2145977e786dbbe968e032ee5929e65bb5f1f17ecbba8a587c29d69fe430780d30434db3f205e13022675a75dbb582c0ec8bbf00b37e157934cd16864af98080ce4789b24c40dde2d45692e51726f42e5f5c406deaf27b33c2e268dc7ae03f8d466f5a6043b814fe0a23cc85b7f6e69b6750e3b9352b34e78471f628ddee680be61a2cb8b371d324451181e54b00468eb1edd149feb6bd8b65621b6908b9d90dd1d76da5d212bb6afd6094a2398b2c54f41650082aa65511cbc89f8dd6b8ba81a187e288d75f120fc6581583d6502bb0b22bfa99cd1ee45bf31a3b9039587081cf28a91496152901d9fc965c954cc71dba5c4dafee67f7c0b2b62a1d7616999b27bcea58bcbbd55d7956ba41793854e4d2c59b51d6a9114142edcbe9a6e95cd0a1733b4c2ef80dfbe56c5e92d516edc68646f89ef271cd3667e0c3278c8f968e8020bbe7f51b65fe82cba0ba3104c48716d03d3975ff1046e6644b528c89f10624c1f99b4cc2936f268cb7defa05d49f15d5a82946c98a634d9004a315d50364ad980915a758b35ce3e91962e96199ede91d2cddd212f2f3b1efa465e0ed0626a5fea7652b4fd2aaec1183db649ee9504d5cc415a0f92aeac9a4afc1288341d8cf74a711984beb31a82fb605cbe77599fac0c08ab17929c6367ac54c9c547385518ce3a20ac03f700d97b38167f15e8011d92526a9d6391a57e297222d6a00a9be584d59ee91449bea91c52a80087942c66ef1ac5bbe7f38a3060b444552cc58a6b92fe4dc679542f84061ab27264ede6aafbfcbfc1d506da2b43970db032dafc7056419c003e7614b154482077ca1ddc54a0a446a8116a70f0c6c241ce2b43567bd8416fd29471544ae581740ce95a52983ec0963aa073755b73f0e5c12d796b05c6a7542d9e4e4d092fb8ee0dc89a8f5465a43e0a92c927bb32d16bd6055d2e2abe860eb4bbd652ef090dfbc52ec9fb3e86a069ea63fe3390ffba165177cb3768e7bc5e45d0b86b0aac27d6660426af993da77b80794d1532c1e20d8c52e89fdc370f8f1b087ab689df216285ad668f1a39985fdb8dae8e226e54ad723ac96f8fc4a731e0c4116c93fb97c2857ee985a8f60bfadbd82bf2b1d97387c0cfaba64beac91e72d525beff87935115581ab9afd1812f1e61b46d99ab2e9016caa2b6b11a6959d1212e80aadc5c1210bfca8c00d8ed767012d4cf8101a2b4654e10cac7342fee7fd869e8fc83ab7ae0caf55189511f266375b4aa66227e9f3b1f0fd37cf9ec2cd64d27e4c4eab78490d0cbd03ff96237872959b6b64e2893c80512470794b1855e63b1077d40bf835c93b11675d24ad6396a5327321c19543d11bec71cbb802aabfc55b9d09c7d83fca03ee3af414fda88a4eeaccbb3f4717e9aa7d07a0d19f38bfb3ef445c3f6a5d0c1d9d89a2caf7812fc36d1b1e10bc5a3a5435af6e2087c34e770c1d73fabc8f0fa908aa4e487a8d2f11887952008622ab8b8d9db8d981829c54e1a5de3eb3766d0f5955ab74aa2486067cc75f11b74d400329c23c07210bb1bbc39204f83c445de9875f9dcf94e0415271b5925eb32813035122555500f78f0c4a108d7e0a1b119c278920e1f3ba344c1fe250e56a577710377b4d08dbd7dc3491f4c3414bb9fc995fcbd34d7b408f36f3b10d8af513d3a85a62428f949586babd70c15b0630afe1db956d05ec8fd7ee4d4250df08ddf254e1c27ca192accafad9bf4c9301b62b7fab614d0667351334c92a6bf8d2db855b9d2031d371059e1569b4e3df408b4e5613642a6ed26e65c7aec3c2a86cb2533e29e80c08a3d03d0e653c927032393602a02797119d62c678870a7a13b6420bda1889804c73e930f65179fe8023f3fb1661e64d9e5479e247f7c123e9666c19e89b634c322d4d1b3fcc45c61c4b4574822967601096a83d870ac9ef98e82cec79c008de2e0b9c7afc95e86b226dfa81932ba175268f0d7e390951e40af0b057beeeb756f7983496ddf18228bef4b3a8549f943e88946ba49381da02aa1bd67d0d737194cf1dfbb93e36b5ed54887dbf908a353b65092afdda2d4021b575f5ea1d4f9cdd6e968a96ac3dd6a442343d90b1a28ba19d955bcfe338f40f59e53812f5be502e2a082e501c3a356200b47e7f0575bea4bdae77a9e385ea9c2b71817fb942971cd659b2c3b0f80b0342c7c3bba06adc8d75caa5d44209a8bfc55000053d43868041d71065ca61939017c00d16f9398704c052eba7e4869d72369334f29fc432587c5608ddb47bf5e6c08c3ce58bb1257b12973a89b3640110accf4f26a7fd0ae57ac5db02b7709a27f811117bb33669f1139e2043ac9d1286f288cae9696a75522e8f4177ff0b7456f6170892c7865a92ad49af6a85d11ad4bf6cee3d3e80c7388eb3666ebf4997812fe14e795f7515979f5f1ba3de1592c79288408862985bfe5c33a360dc963397a809ebdb21c20d9b681dec0590f8d98ed8d959ef9fc95de6adf19ba01d9049128f3f9f0e087f4c68d201c36b8aea07a3c7577aab068f79f64cd709551beb2165a277d2a2dbf89e653e512f0f4d905c6a5ec25242e220cf838f5382262481b6b298e32438221effd31cf0a49688e574e65e8c081fbf6a680fef754cd9135a16a4f932d23b9524dcb4831b9fc01e52c454a451759a82e776faea3a6f81cfb8373859bef442ed3dea358bafea3501b6b72a0b2886ab83a210b5378bd9563a7908d4d33622dc3e026d79e58f16a22bb7d46adc2a982cede2fcf5ce5ce7c5ab567c4b60d646a762345473296141bf0833e797eefe35713af32669b1cf9617f75c785300e2a092645bb62f23887c13089796a40954bae6426e1898b3c683bf623cc6ccc138445c5c52d1166cec802b0b6b51d52163e6d0c55989cda9f8d68270f9924a52e197cd38c0bba515d2a69a2002162dafbb5258a61581e9cda65163040cf55f7aff0117d6c2587ff5b3fa9660d53cd50b8880ee0ed3153ae91c591e6cc5d16243c1e06d45e827b377e988ad5d617af7f3f2686acf249ec0e5790615d0079dec7f9339106d3fc44463cac394108fc86c83fba3b85fa4c78aadf5b4d9ac8eb78d0c7291239e65a784091e77eaae8ba450a0a17daebee902daf1d1e460c992f8e5624583c8c651a130c14c188c585cdde36c00442dc29f50e5d3e0e88f6729336366920956986592ffc25e42d4fdd76fa9ef2b8475ec56471226869ee8be0e6bb0624db6c7808647cc9bd88740bfa19d9cd43c061ba4139afb3e22d814d546f9e82d338119bb9875a19294c034e881ab51a072e2888fe8f7a911a0f5dcd830bf4d6edec76b0ae66524519092b7ebda85eb38b432247f3874bfc0023f6c0876dd1476d29ea17c71804a813e80544fd0a3755dc2e37e8d951c8cf95d65192fc80bd21441814bfc249bc4d8802e361d36b86184da2697f3856c3dc4f0b86f1fcb8a33ba285d0a3acef6a23b7074aed1ed15a967d94339086ecdf896a7318f7fb32b864631b18d5761e2fdb6c79a1fb5c043d70de28d5f0e69dd533afac3b3d2d9cfbcdebb309b46388d649d0ed32d30d3dfc918eda088fa39f84c5694b88b6655ece8980b654ec3d47c0a0ec7b7797c0614011f7cf57f9c46cd8d2e90d9125404f92b9dbb36bd3d9f6a3abe9edd2a62e0e595a2fee7a2f67a344090120056b3023ae6a6991d7d20ffb0f38d673498f4cc5cf022c7565a716845f054fbf1573a6a733f54c324376112fc507c329feb837c31f671bd5ef970c28519b66b9fe09e83a5110bc14c031cdfbd4e39ff38a552342f369220e9cdd623f4143b3a4caa2b2a87c499f3fee9744d5d2cc85171825d988dc5bac0b26d8e7f43c25378d8827053bd88b5f06efa3854b530963439d108d92d87088a853e0a63b9743b495fa2d4ef797dd9f2cc3e6c1641d40f112b2d020c13da59676a122afbf6b6d50355ba901ed3384d743202178c557deca94dd20aa16223155b93e7a59eaa0416b6dc4ac8cacb3d39d1c28bb6b73638628266a42753149e3c30ab7f33b01daecd2bbf566f8aaf5a35892c3c9de83f20b90284115ad4f46b9913d9985f1e76757abd9e9c288cf1e45364a6d2623c0a04d031ed2017393f9f34923d5e79a5714ad7cb7404e3fa0a90ac881ee08f39967e6a5da7866d6b2075c293eebe7f4fcfc80c22599d12bc15f574955d7f49e75234d2dfc0536849a19d6f419eafd336789679cb96fb57ac9a6698539694b197593403daae72c162baad2a1988df8343b146402ae6e2a5f30e544cf5511fcf075ae834741acf68cd298ab3a07e7d8b53f040c677542af1221e261de84cd62d6c0956db0d92ef7830ccdc7928124ed20488644300a99f7f0cbf41513a84178b710afd5b47102ca794a3b267a01623308ae01d60f124d88c8af3695bbe8637250fe46f53ebf359ab6e501aaea643d3d597babc437b243ad1bed7c096d2b6e2f0308708c0dead206f7d6092974f0c23d10966b4dc501a03dfe0e842dd0d2418d35dfa0b7237ec43698110e8329a6b2777711535eb53dc2677383c901724df564a01939bdd9456b93aca7c0eb6117341533a9335a10e2d587c11d9916d7c4890c1537a439fbee851b0824b1f523bbff93b580126677f6ce02a9a5034fc9341b8fdc790f9ca2a24820ecffdd7f6036e4cb06bc0301a0f264d46e84621649ad65c7f24917f6dfead5d9385362a829d54c14829e16d559547f288d5a59081f4c9c6f511af5e94804f10d1fa4690ace573b82f10726297953db7c7d38523658c9d4546548acea36cc8d11238a88d8501462131b9d0010cbe488048b0f83d203107bad3d777c587608b6173c8c1d58dc88dcd2069ecd981f87cd9498196c40940ddb85601c467ddda3cd0ff014c7f779cdea0f039f28d37821ce4db6669195cf5f28741341cfdd84718b4d573dc633c42030ff1e7b8d11837ef70b52fa62d2b3c6a8df64e0cbddd91e5ca87173c32a531eb6a6215f9ac61d48bfda398750ff039ce903561f62ee75e1410b8c884f889bfd4ead3621b1dca902637cf3b4d54800eb58155c8869b7a5e1ac823c3ac40a191c894253979d0a7140ecde4f6c412ae707fdf858ca6c6de95db80205d1946460242d8887b9219c6eb1d67d94589dae015e2cf4f4709b5c0317b9513044a389110d22f70840aca7c8c6fbc81846e2a486b27304e1504033e9805bc41194f1b04a56cddaac691f9d612a6c95ab3d2b7dc4f25df31f5aa9349fae70ce2226f121556872a1c1c6333ce81a47de02dc5adf68e2973399991810d14dbdc2fbc6c022921383df5c03ad371fbd00b9bbb7e1ca6c63bb9205aef492813f654c2fe128c1e98c3c2843400218efcd34adce90f64614e752556d77eca1c9232ed2d7cadb928e330c1ed0f88ed9834ad62de3bd00eadc32e5b79aae44e1331b15ac08356eedf0e8e6132e820b28990b837246cb03f54e073feeda6a8ec4f9ecdbdae07921c6f0b06edb399f77895b33c843d434a86d538e0e8e37700c88110fd54dc2fc82c686866cd1c29ed427050751b2966acb056818da3e79b84c4e37b1779da3a101f812af231f792285c789f087941431225bfaa2512af3f4121791b987a83c5c46d2cea321ae16813056e4ffb6c9c6c346d79f5e9b9a7fa70368af5a7ce4f6b3feafa57d1e61684634e07221012e4a03787c7f8f3da6d3107bcf03eeaa47fa8d6b09b2e7549644bad5974b374dcc1da11221f81c22f9343cd58e43f229a3b188163606d4a0fe1826f6eb66d1b6d8a3ecb8e3c434d419af53681c842521ef7f29a3660489543b66d80a1370600e2ad5316f7d3278d376ed39d377a076e01b9e95d65da55c2cadd3a2b6a3ff7d40a3cb14545d08550b967e48da7a903fa632b90b5667ce5048ecf42957ab53b8b712f0dd68004351e2c5d89bbfc2261bbf76795d1388b034514ad4a335a4dfbc17b9f736232a470d5d6b9957d9f7abafc020523c74bdafa9b82f2d48554d56db16a04d0b26938d2d3d13e34cc3a1dcd9a0f138f7471973e050bbe26f6813ae1fed03ef91daea1cab3490a37d432f1af4dfa4304d1131ff4f30cacf53153142f348f7b51a2fc83e58279f0e8603393321d86b97ffa7cf21c8a1d198bb9a8ee3b51c65e4a4e0ab69b62f084410f38d9039920556751fe3391bf6560e2acb3151700311331b02fa54e44bfd4ba103afe621cf8fd1fa83d8c30184a15f6ad48fa9c29059867fbd5caf143a40282e1d819bf3c371bf31f62df1d80d9fbbc5cfb4a18db255c51b6d55a912bb0465a175c291ab13f838789ba549214caca259192ea38bffe43c84a87e4cd5d6498ae94ee3a0f99656d3a825b7172e9bb28150ccedecb77aca715c441b45772fe2ce4e7635ee4c8258274f3eaa94c482d097232a66b7611c8efbd80e5fa315218ab857ff5ae03ff33976c4ff92f1c4cebe43e993f7a5a9b8b2a132e1fda7dec957b3b9e98d36911829909a284ed6ef1240e02171b323e922212c78cf84ccd2453b092e928c9a04c22f7825300f5345c3dce8e5967856b084b851921a6257a68d933b101b9cd135fb12a159c190dd0a212960e72c8a0fe64bbe958025dd845d296a509a760702692575aad8f0bcdcd9eeb7c83c5557b134d999cf01ad47c7c1cd1ac7406af0a44d5fbdb9eda198aef9c41e77061506445e4813239e41dee6cadccaa221fc66f6ad87cb421821fbc9172d04793c0c2e044ec1be5d81e006e2016ce7339022b7d52b37b36e1e648a02f4400902e9d8ca229565d9d3ac04c1dd6a8d4368f302b789915aef24b76d1859947b9a551be34ef9b4386ecd5cba2712d3f4a9afb50bd2f2575a8d98e75b065c8e29328d89a1fc74f2aa6174ddb527eeed80e5fe83475d9a78fa525901e582a05e88f8ffb558438e32d68a341de6e95e6944d10ff97abf89cc5c4d4abb6a55898b09a9c46810b54548556ba7296aa31a80e5c4e2cec5dc58b0c8edfef3e41541b79bb844ca925901533c7c0ab303f9ca371879cb68ed6fb9b900476dfc175be1803f14848ebd88d6af69bfe28be8d0da3896e07d8b8d89f40f348b5ebf010698e92d44d4c2976962d9b576fe6f9bff1ba382a4a3da6a85c7f087df812800e3939c26b4c44de3a2bb4d0d5e5e6c5c09d76524318c2116037afd338985499b26049e7eab7853d6a773c3ebea01f73d010159351c9a303e11c1d5bb9970f18c4ff54d4b690a7107664d73770cf628b8606d13ebd6e4fe80549fd336e5c6808033a8e60d693f5dd3a339fb38a39654188a539d984cbee76662ed9d5d28a5f1a9a28120487f888d825f7c3541851a6220a2eb06c7380cb6733978ea5e41f0ce2abed93029a376c66fa7eeefa056d6f2fa00f025be678e5b4a25ecbbbef826495f01101205991592a7fb231f5e0726c00a4b618206d9a14843f4c30473b4e091d92f41f05ecc1269559fe17fa2bd7b881791513551312b058be92b229f7454c153776162f92579b10ca2b35ef37dc95e504b6d92a235462325f312a95953174a3c4bf8a9599ed4f44f49dafb0fbc49a3b2330b5db7588a792ec00d68677addcebf77680831cc74302c40f2b0268144b5f1970ab4839cf8d466ab3ab00547a45e1eb272519ce0a613866863b69f3e5d9f7b79eb9313dc04a09462c354c2845fad52e5406cc412b77a32c071caac2c8032840a4dac3fd01a6b4133c16e438ecf28812432b51ba02a939892bc84c087b54be99705e77a344021f2b70964c2dd445120323c85b4a2a16334c682fcb0869ac0503cced5b51fbc5abba6b528ab0bf139685f9b09f313c0e9db38cb3955d49eb5d72885d694d8ddd9e42531c6f11297e1f15ee92be370a967092a09eac04759681d0eae33e5043cdf7d2d4129f9c9fbb227270c4ad69db3b3e119002a3726783f273bc1abbcd469278ada4c7ff8acbc5a839aa52a33a8223b3a03bc80eda648a6584a220e504d435a9c014f2ec135faf78cbc7f488b532a868baa0f4536bd4323e7e27c1951e72c65d2b8563c311b676f7c2f7c6eeefa176085a6f08b62e03a23c131739e87bb9dac05c0448093a22dcb491b8389fe4c6c962ff8843ad0e76baa23a6ad6aaf29048e3174d3f71b5e1109852cce9b92a8c0b34a065d5b761f9baa13b335e95e8927483c95c7ae2baf97f59dd50d397c6714e0a5f1e3cfe4544057ede6ee94bd4e21b483ea50917d1aee469701008d0baade1b55e707eab23e929cce9f730ec14831c9be24dc7e071839714ea095568881caa5dadd42a370032a289fc0094731ba4946d479788ecfaaee55b4cd3466a5b4b2446b04a30d5a5503e8523d958afbd1ed1fc70f487a579d11d0816073daeaeddcf85cdb54b54f177e1bf3ddec7075ca8ef32f9735cf82658a145586bcda4a06ae26d61b84a59a0baec5ac897dc5663c81b3b54976e73c92d07f26fc98c78c733eabafb9559e843598ad76cbea68336c70786cdd7e7e0d680e7d9e73caaabc8b4f8d35dc60a19dff7373017d59b4106c569a2839c7b7b274308711c58e658dfc9ca9756cdff2c1716010df4eca49e2d1c2c7fa17ea110a979d7d046788ff86f57ee164fa2491787dd64a5e13a745eb60638c20017e013e04f3572fcd247b0d71abbea295dc36134b4c238b963a08b00e1a690a8374bb12be81ab11026a27aaeb533d117223a35d4ae0467a7dbbd0d5830d6917290a5870e6dc46f9d9398b5173b4dfecaef3bf7ec096ac4253961925d592ed75d9579b574a129bd1025492c08aa835ae96cbb461271215ad48cf5b208816e7cd0c847473feecf1d419cb6bba5d77fa4225a20288f5274846ae94f5987bdced38d756bd8c396fb245a9e2b4c1326cce16a120d8c30d3b086a2e8e6245a358ffdee715c32baff63fa01a32659414a0e24f2db0e2afaec89365f7c45e22b662f31240e7969723e008876479cada45ae650743cb4411d80eb74ade9202275304e624eb0fb12fa93f1586f671633907e4b713d4c05b5f2b1e70454916e45d71ac2cd380764527240489901ee3a00cea6088faa2c5c9a799ba93adc666e41ca3ab4839a53321a33195854748128a1596b4982f223b41f48d514b6a484eb9bfa92d5af769bed16671f625442b5b0f289d39e8449cbd8bf01226429c5f4407af4a07e75618135067374d6e9a2b37e064ed786e6c7dc20cd45bb73b95b794250689e8a557168277439e4bba35c842c1ede78a1f7944d74f4ff9ae151b6051612891b53a3d9f224672f8744d6d09f81d5308fb7ff59654588084f44158214eca054eedab94d6930b15f1452c727c5868f14e02c9d9945ac048439c393af89ab61a422ba67e4d8c07326c463a2c5712ed5caf34420296a3749ebc694fbff47f69753195e2489d98261da76dab7831f5e660aa1e21961104954140165593b5b0b6ec9525a337ac3d0722d0252cb0b1c9b958a0edff3471ef0d509ecb6ad68d5e602a17b00ea99fc3057236d18676cbb8bc5b6988a8d213d1d31634f94a952510fb1ee1b66ea3117cf65463fa42b44b96d303829acfb26b791b30780b87c1c7cbca3afcb97f18adf3176b79f003933d671d5cd48d57f049d8759b5475a5805373d53b003bd004790bb1a6403ac35a6fde7e9aa250ac2dcbb0f9872181ca2f6f1c11070a53a37cb88e2c4ceef08b0dcead10ed26f90dc3594b7d2b1089c4caad77fb6c505c9a141dabac72e3ca17234e5567c08f86138fd871a104dcc34b0bbae329470851487f16acba4b6da1f4f19e239a6d68dd7c7819706a2aa67e77a778db72d911a77801c443b404e6e7a1e91664bbfce3ea8562cc8ffce7700244308983df19e007043dd66d86cebf257cba8227f0993ae355c4c6a1e0dbd5552350495524e5bf594fa653073ca5a38b4ad6e20d5e1f4a4b44d54b95582d74da101c06625259ffc2020662b1738dde95a6c67301f8d887c32793101f74408a92a83c199492de55223dfca478067406586faf309c2ac2a15c450fb60265b27fb83df96ced792663d61f8eebfbf827e939bd0031499083c8e5274b13e6ace4991cf85dbd35621fe46219477bc996b6dd7163c85cd84c8f05ae9c7604e19a8b2cda641afe1a20db79a5b2d2c937c9f31686694670805cbd7f4251f99568fc1b9afbc6ca0fa401221a5e3bab4365240d75b84f64be0195a44da6d2e0ffb1cfa8416ddd994e15307f86225c777d8257ae54bde5e6c5ff8b89ef5e225f4b6c7f4c3bb50851cda62154d0081d34b91344ac33d72b0faeb2605e8bcb69a9135cafade1a1c540ac9e10193e95de6d58591a202918c7044bbf0bc59186736553289cb3397189df8a1b09dcab9378de24dd0434d46b2261e76f2173112026dde1483186ef7268294e8b91e649b36e83f3c92570b334e662233c272b9284a6c9a0da34bb670ab02f519863a3b50f874eb95648b740397870bcdce75b0d66e7d9ab763b04ef06cf5434c1b02c54c51e83a41d7cb7a9d1d3c516cff1fd371737211128e46a2a865fe1ed3aac76d689d9cf22711e879b8d76f1b889f61aa1df3b03dae0c35d49ce3027182ed32ff993444c8f63e4f4a8065c91ab8b0944e24e74cd1f1a244a05068b5a43b1497df6ebcc3a59956cd10b903fa5620b949dae2a184dd589154ac609f1aa9ef3fd0c3e687556e6c943a1975df244d8786240c59a2e490d50aa785bb0eb33545ad00aef11fc1db7b1a674874b5f89dda6e307a6eaac1cbe471c6dc279745dba6e8a982ad94f5e26ee00542ac96cded164bbc9b3539e4343606f84d19a41f76a3ad681a343eb32f7a67978a496b15f4914625ae662a173d536ee6f8a7ba9347d1131d7b96a3caa44533f5038ea67e9993a874bae31966bfbc794a1f35b9efc249a6076e639bee04c1ae6234f66cc53342b9c5983ec68e165797054adb636ae71c6ab6973b63997012747b504ee426a46230710528245f271b0b4b120e46e731cb9f978abf4be721391d0915c46a302ddf7b61c23fe34e0b7988840c8b06b22ae586443a2623be6a79cc08940bf004ef733a5b7a559fc8128986c71170c5e461334b812d87dc9ef3c46bc0bd667a144bc0dd7c26522db94ff204d942a10408f3107b8d3478f51018d0d7afad1116121361260f785beb5185b3a4a91fccd66e982ca89b438865f0d60913eeeb9df77b36e3e79738ea1517c888b6dd26d8fdecd1153dc527187c9a98d984552102752e695bc2c5ae18fe5ce73dc0f050e0d2db7c69fcc8c30b878821a3d9df2a4214869075ad75c1f6e5e0ba980e07f91e53ce10a5e8c18fba754a722f28a369765561456974c0dca108bb1f420f833acf526301b018564d33e1aede0335b000757192324e7df4f80e4c11e5b8d806e2040425b926c26f9be6b1e0ef5dfc70bd993339bd39033abca0237dbe6b2073d7327f314883b052f3831052c2626ed92561d50b1be74c5c423ea7df697540c6ed4e6f2737870ad91d6c80554a17ad37ab9727dbdb821662bfd23a2f8c3e8687e299c8a7125a75db559d9b4518a2b1003186fcd9838303260186ab2b0e44b35de5c8354a68ea69d174b7c33c10f3879709c5fee8c1dc22f6b0c07527011b0cbf6c4352b391f162e8c7424be55e855b27d3ff613db56e85525af830dd0233d1312a63e52be6f8946afc365f9e6903b23bee323535c3321f4124a404496318f6a1f94230d8452456d53ed43f322d04928d02ac565e5d5481699de8b7efa0ff5d9718a89e64d14f05716e3819b67ff4bc5548cd8d8ea9a94c313cd733c803f15c1bd8ae962af3a4d5c491f590f2b3b0ba26c925e48b1b9bcf8e7ca281091b0f5d9ad2f45b655a2e5ae9739edb51658c7a7cc788fda2c833646811f9fcb2792b89de42156a3c34c07a808879bf0abbd9b08343b8e08adee2a3795d5f24196e719dee7b220dca49eac557a741b2bf9512b46e665ae7061f0c9f4421f8ef5d9ffd215ccbd837fa15ac05195b6ee4699740a5d835912aa1d8ca58246b465e9462ba9de0f9fd555288f8e61863697560a366fb0d694f1a03c78ec79130a2bed12f4deb851f40d4eb27fd5c69b885484e8243138a5279c4547084ca1d9f1c6b56ca0d4e4e3c0b616485bd8fe6e5b8c237f8676ece24102ae885238456e04d8cf72437ec20acc47b707dd0d1c8338ddccce6a67f1e135eacb57e4515deac7cb2705b1352a16bbc9de10c2a059f1ac7a2156ae841cb41f6f0ed1af73cd166daba0c5d5fa8382a8fc877b746478b2564aae4ce2a998b097bc527b44b00ee4bbfa0a14c7b807adb4d31f070594c494fe1404537c644290abf3174fae852800e6502c6c04a0d00cdecb84e291c225fa40ddffd53f28bbf2bde3015acc0a03b4e6636ade4f4ed88909ec9f51b2c1028dc74fe40664a989f69b774ed56388b6cf2d7a3f5b657dc816b08766bc3c21724284400931a7a1d8fce0fb947cb0935985338ae90ba30e407b2bd5f5e85751f6be259a01b3fe64bfac0f2f96e92898496c51af5580638e45890a8b17957278a474adaf90835c9bbca65c39d6a5677425eebcbbf7d32c5eecc1c495440f1abaf550ab0347431c9b24ddf0f50ce24a2af3d964039cf782a7bb6ab822b85d6587576596251728953d92f6843d14c207528386942df47109f9d0a405a392e427f1f34b93d83490048db38bb81766e35cdccf2fb903cef71e4c9228559dd1aac0de7fd7658409169c468d382eeb512f87d7bd8e50015f74f207b1044e89644b87db3e0097a7ce2436cf90de5f043157bc942683d0f66c535623ff162b3ce598af3943f27d1114f9e67a466c5a08bea0d97c2cda582aee94e3f8a7f7c4802b7df4fa798b0da5b8378b03cb1927acd376467d1e78b860096683a713e7d3cd8416fda734ece30b6dd376644f1ce3f4dc0f77eba11b913ffcd52335d61891c368ce76e63454937007b63a96ac57e330687ddd2ae8a9fcaa6014849e70e9c0f981447e28d08eebac4c3b5838af4a321a535911f6c539b109705c06b051edcf5c4203a3c628891e741ec7285163099f07f471b47588adf898aba0b7189b80f525841d9f3e1093824b4372fbe738316a43342829b7b1e7374978610bba6d961b3ba4a74d5a504284693bc9d1ebb69854370d0b3a8771bc71dc02077982b8bb03338f600a3d2ebd98a57bf3d46ee7225c618f7ca9071c6a958740202610ea0282252df5d2c62a6651640c83fa8e37691165b359a786ec6858480678b62b8ab6f3431fe743cb023d9b1b8223acd86b169a164e2a62e3626b780cefe4a4088432df4a244be431ae422f1d586faa7bd221433e88866ce7aa7b42a71cb263301a120691d4b5ffcdfb6b8e3209762b3edff6286823aebc5451670792aaff5247b313378042c427aa37816efa2a06a0424f27254ac85ce13a04cc641403b8da44ae5ffabd82e7dcc195fc8ca23cb085482957119db777e14c787d28f82d83b243518e0dbe28ffa5eef745405e37b4973bb43c563124854b494ef58af000c2c182fd76f58a8743bd360478432d4b0ee97ce1c581aa660ff111e65f390cdee20420d8fa4d8d692076167e6b2ef772695fde4c3fd420b94ec04761bb129598ee707397da0518c054ce178a646ce0e9db289fc301f2b432acf1e1cc9ef5eb56448f598c72586fbf008c08e07fb047bb70de34e85f5dfa9706bdace478a0be5a82420becef94a4f1d94263de507bd21dac40648c4a681dbf628f8707d2d2c6dd401904ee01680ebe823304928cb0239193076fcb5efca1cdeb12fb1e109e8491e5207322adf4a044037f4a02818fe52abd813bb931c2509640afb1aa2cbbf218b08f98472092cf7fe0842444e10df19061e34523bfb51038ef29cf29ed18fac3f290d2f211e228a219e0ac384d3ee01cb628b6e210a684ec13b15ee16ff5c9e7c51e1d83657f30d74951381a2cf0f8e4bc458290ba4b340d7e659294937c05ab75c5ae3aa4a38f4a4a0fe0dd2497877a83702c83724a24de7152d459239ef472afe5ea9ef091b66012237412cca772d7b391d89aa8512c21ae54d3dd91f2bcb46691dd9d955ab171df60d861860137a5a5dfdd6fb4780b2ded6017d0b663bd29a4010cab8fd38eec114a37d01aff11406605486fb8ebd155f275015e2e3d893ad5220178f97addcfe06dad81826225888604530d0a16571cceb45635fb0c69f60ede6ff091074722a3d749cc99eaf08c522433b2a75c12532437de9053666968d66dbbfa4d54683e1635404e12b975997243d9012bf523ae51be174232da9065ed45c4886165434e87a136943bd4ccfb9c5e62aa2bb41ec270296fb4a2eb76f1091f79a2af8729b675caea7774babeef5a5b1dcbd7b88c31d83847edadc280a4e02942a1419c4f8511fff75e4ea13d3d40737ed88ba62cde622fab19725a98e018b5d51281eae636a7b547ea4941c26a8b7b5916fb648e274040c6ae13c35fdddd2f49874ddacda922a0ea56eaccaf1a2170c18847246c8feece050b4a1511da13d151f30e331880db300d0d58cef821c90d02df1e817e68b9bad148242f4e016169eedcf00ed03054e24275de95f013ff591ceafbc27a9beadda8c0f1adc4106a00e37b9f8c1d2334f097b9ef6602cfcb10f8c1347717a563c92f2ccc44654df86a0a25d84d364da7ec0d2008f677fdce7e454211244da0baedf597cd5a641a82bab1e959d71eee131a0b05682917d427db195b42fe63b504cd1fa98b8011218f17891852788d1763abc05c7980a2d88f1102a42db1d38804c9d72d7bf4f909c1ff1dd4cb3d2aa543927385e0c89624aaa6c6399ac10f597c568b01612dcc3a981d10989c4042141611b5fa3c7fa67b70b2e8e8016c41abe720ab3eef1ce57c2723de3a48133ab87f047f894f2cec8b5e4c5ad1d0dca54a8315f4e82f1f2d0d7c0c5f60f5c51ebd70bc013ff448b596848de3c2bbe96a42941b86466d643978dfc82667b19df25b491ac04907d66cb73498242f88c54871da6d1c592eb8b9d6d7b3f83276cc4df65ea541ce82d7b84cf2bd3e6a62a8631e3c9c48488183639399ff3e7a980ca4a0fa9f41b6ab69923725f33603a0d8bde144a4a5cd10b8ac21344be6d7045e58b680fa5fe684f3199361f5dec7ac2a331327f4fca6615ec591818d8a8af43a8c92436c72d8a884f11b9e414298e191192cfb6641ba2cf6f1a5f494e5207e14137e16e6d1f78a2c38e6d1cb44e36a214e0587323816448068b04e22538c1f68e712c42311904426712dc5c50e89908ca93707a60126b6e79cc09ece4992cab2288e226656ee3c83530971ea6196aab06bf41e4ba08ab1423559e22b009c4238335a44f64e6f68281d98762c296c8f5c0813904243140db2a3db11d191432cefd034dfd335ece71dde2303a9446d9339ac36e59e4a31af53722e19a8ea36dc0c34dee2912a276f1658ad823e42aab286e1273a11d804aaaa3c4be2dc36e8e5fc2378c64d1edd9f26d245bb1c0f7d9e3413eabdd20e0b171275eb208c94307a5320a9604b32af250a38ed1c1c7d103ab0051d1c0e781888440b77d1af5d99c32ae8ff3418677b3dc7ce24327f9a934f8c02843181090123229bf2c463f75bfba55dbad63689cba2df98a203017c8a03da1e8a6e5920da15a903d9abd27f458ea7769d7c31cc9d5666cf13012eb38c2e4c5d4817d6c9680af1660be02e4427ed4b600ce01cd600702076e890f040c95d081e7d3bf0c3b271a7f28ca15b4b4222a8027941dc6def2df79652a69402d206c606e40623d80c9ff1a2a4c1f519df2f6962824dedc334da87b3ff89c940c791b8153e3e05916bc120a89953cbb7a09e6680a26779fb32c0202da8a7960641508752097452ac766fa01f10380871b9efa47077e53d6fcd65480a19af7d3cf849c67becf96466762dd44df0c5869e665c0e5c79ef3ab9a28cf244ca64109d4376219bc005394a981692149200512e71fd59240d98013d246cf4b22ed64a47739f59f8f33ea3588b5d064856404f829dadc9f290460c262b7c6245d7d171978e8e8e8eebb8cee7222a78d286bbf58a80cd521bb2da9881804bfb47a24e6e57e089629ea8999aadc93387cc23e66a2a993ab3c8f567c9cc2559ab944f230a2b6a2f47d0abdbf990f1392f36a26f7911588410a719dff2334432b04922e1924abed8885a5ef4a3a9e049c97ae6e34d230adbaf434a6004bdba3db4a6bd4c617d786eeb5e0629e78712b0f839c958dd9ea823660be965c6b7bc10a7fa5dc04f31bc88f46223fad18fc09cd18b5e889337832451a4171b979ff12ea0444998ef67bccb8f482f3632fce8650047e0cbf72ee0cb07eee0a7183e0650aa402dbeaf3526634cac48592c2f56fba25d79d65c8245a14260ec86fe7850b7012d131b70bf1f31d109a0d440766a29419354e28f188f9458f16f0bed6f037de8d5953ab012a808c5668809569ca86e990c2543c9503294f6a22b64133551ddd665c4609c73b240f30769fae91af3266886283373ce39c70bf8a987840192127203c60f128689cd2143eee0c8144c4345e7a84cb3bd8c0178200b143b234d4088f18425cc9546bba7e3eaf63d5bf50dc8f340b16dd53d9de366b58a75cf0dc8f34031ae9331f2c701f246de0887e7755cdd34dab205700956b67a70fdb9d3a4681c5ce7182acc47f86ebbb7043e5066451ec23210827f304d739288fbd2f4da4cb60a010f9c59955158bf75369bcd4a386a6c6a7b3ee0669e3c7bc6672882952fbeb681de337366d4629dacaaaaca65bcc2ea936aa44875d59eca9acdcc524ad7bb58ba4206b7608204ec94f36385253b7dffa3a508d7a90ba7e0aa629a15d3f068095724565c7f1a46ac58955c7f8fe723c2ca97cbc31e3f1a94b0defb007def49972b7485ae303564479d02099575fd4d9508c35415c7f8fb083d28641a661a174161e57bccdc1dc182eefc5e76083de879f0fefbd16b94a88d22286c836255d2a2bf4d3925d80357afd65ac11e82a017879b99b99b6a929341d0db83f6cdddddac854421c0fecaea09b2bb3b6ba129d65b591eb6a42bc4a7461df1fe03736a4769d2885362eb0adc99a982179a68e11a4f7ceac0235e71a4492e61187f9974a9aaaabe5c66565555555555555555516aa393cd5062eceeeeeeeed6f89b58e107a8536c14628930c1211858d0dbcf612134b9623ce5fc5881bdd7e81c7c7f4e3188abf56f93f492c3143bf6583640840cecce2311f1ea71fa00baa28b2802907f38a658973bc198115eb8fee5e072ebd7e47ae86dec5b30476af1841e273be37802fdcafb57635f23d9db24d6a20848ee46e26b493d4a18d44702033b03c2bb01283361cc4c99b97182157bdcb6d0460947e720293839729c1c512c2547ac82a8e48e4c713299176b99ccca703c742c2cd3f8f4004e8f3aa43be372de6b37a42947ee69a0426a82650f6d3cacf2c2c94cb0672f9cf721ef43a00fd07f209f38506c1f4e0621c994370ab115e4389415e528479709e226ef1df40a446f973f0938b39d1ed6670f3be5212705ecc2b287b54acb98a60ad370d30939cae44892a3cf26784029bc4e89b1e29b92a6940c076d6a9694dfbd1ca24dede27ed324101c348c1ca261bc03454985acc17599c4f5e7da88ebae2a71a049204cb3bd8c42d6402661c428a1f0d6624c09639b9aa6699a8d871330409bb86bdc03304cb3b111b817390b1a5101e3c8710d23a76a46cf8a56b5f95a47032b8eae7f7f5add3628e69cf5980e12616549ca4d1bd5c0e66c369b513aa56bdba9b5568ed5e7176ddc7e1a2014d45bc11078c8347c2587ce128707d3c80bfad58e0ef49fa67d0fea3ad07fdd7b3c80fe034b2e60f0f1bdb7f2a1d7b4eaf2848f2336a15f791f1ece4bf7f7d2200f3cac84409046c312b603bd9f76d48034baf1e7750ffa1c30c7035d442f6cdb01345260020e6ffa060bbc600710217ba53886022c2b712dc9706432994c86c3712b51586245a68a8c47c8d76b072baca7dfe1322f9d16e395e08b7cbef385f8a10b560c7b8734596b297592cbf526055d602d8bc5816dd1c76d5bc48ae10da5290786f187b2e347cef9e3bd58dddddddddd1a7b5d8963dc9320b650ec77c55207c57a572c953a87a39690da154ba551693492a65269541a953a28d6eb4ab26989c545370fdac443c2748eaac48b0a2f287891058d2456641f70fc72a84f3f1de697c3b6b5ab3492168a5de176668b039bf5846b48eba458012feb3ad0c7f7476c3e90c197e9e3653e9f3a1a9858ffce8ea4cdc20633f3a862c16ed788d1bf09863bd6a5f88ffb78e83eac924fc9673e33b39dfea261fcfb5be7755ee7e7013257ecd8f6bd063e17ae30852f2440ab44f791b9a2ab1e7045dfe19058d9627948c308cb2c27e22a1f1f1f1f1f1ff7711f4a6d4891125aef689675234ddc7332c90bdd3a311cfaded8a0092638874ddff0da840a7ce246e65070a64acfbc9182cc27e8262716ab321addc4b46dfb949873f2e439e79c737a366a74873d86f4a9128bb2a3720c6c62e53fc7bc5eafd78e275c2e53b581654c8561fc64fa77b519b6b05f415ec781c03ae76c1b7c474cc704c3f7d0e50b567ee7c0ea70b95c2e970e4aeb1394520aaba1d2b44d6a018ef1f7c14d1501579ab6efda4198ca6d928d4d0678e81bfe1b49233961abc84710d989059886effb0e1ce3308e03d65ca06fd8d0bef1b0524d2bdda0d6ca951bd6b18e553b44126fc4f51ddf683d0449221c7e2ccb1c33478d7eece29a63099f1d2178bda28462b59b03e9a43ca5e372d2446b088b476584fbf9f9690ee4714c41946a744a3baf683d10685d292edab4c30b870215ae37ca9398be8567e1499c0aeb38a85e83ebaf5d61454fc16029580af6ad48b1e00fc8c30e8aedeed63fb27fec0f140bbaa2ad3d52a0fee43df7d607568914de37cc0391c026e714c7b1384f718caf9048adb8221e4ed95cf4aa6157a86c20e7ac54ba87e826ae607d0304a5e01c70e863150ce33f04ac75450f58a9fe7151e9d98fa760ab15acbbbb57a9558a4ba552a91df8467f123ce5291c351c637323831593b55ab162517660465e4f5a3c158562d91512984a07ba4e225defe2fa92eb9d27a58c59e9ee4ce34b347163b8ac8512ad4b3f20fa5623ddd717a7914bf295af6444f9bada532b5c7f2986bb46a3781595693a762cbc07bee21831d8e453ace46bc54dae285f2bf95ac9d7eab5922ff95ac917378bccd554ada68a065484693628388764b24295a039c6ef064a9ed56baa54413c0832e64260182f5e1edb9cc0e4ab5153c5327649596513922402c748270ee3bc85273df5b56af1fe0b710faa3e05c738e854348c7fa0e85c5c7f0f149b0572c370a0e85b340c2849c334283a0fdab7a002e63275c534dd73a01b218b24207f5fc95713962fa6e1a0742191c079b5d65a3f0f9c71988ee11bfd376a709027c31352c662b5645294c47660515cee84a65229562ae52c16d3905a002f4bbf45f9e4fab33c6c79d87ec475e2933e85c19a34119dd5800684218924830c2c2e1343abc2ca9f48b0288be52c96a649901315a534caf86476840f95580c0683c118c635d2d4fd5fea1fd9339773f68c695cab2995c23006a3c104eb75a100ae07be50d0f75ee841341861c546813c1fdc7befa37bd088abb31aefb9f701faee6342b0f48680e831ee4b9022bce7be08d0772fc4f51c071cc3c2728e7020112f35dd5f804f1df8e5d0af69df0d25d0c097991a5e72a0e2cbe6328515676ca0c252c043972aacc8aff9e9606f835270df2869b2a222b1a8158c4a4d0b6d947c73fb82f6b17b44b1941cb10aa2da49496696b254b2121c8114c0915c9f0234763e722b608f122c7dad6218e92cf447a91ab97188d82ad625aed1d5ae518ee338fae823a5212d1c368e23eb937294b526b19386173616a341c48aeca2410496b5d6da0edc073dfa4ec3a0513b49ac5fb1562c7058859dcee1afd1b7f01d18a094b8cb437f6f314d7771794a8c0a1c240a2bfe0522040926c6518bc562b1586cdb3e2ade88840ee69c7394b2f267a9d65a6b85511a6cdbc61bd76aed68c4b6246bb576349261e871746adbb66ddbf64d60bd84c3a07e7ad997b5f6e572d2398cd008b8b3c72f9670fd6b04b1628ffd55607b50cc61db8090638cb1c9a003db9f433f10728c31a64b5932bd2c2c10d26bb05d1c9345dff0b79e85f5cb333995ed970b4c59970b4c76066b2da53c76e0ece0d14ce03009ad262499210a2bfdc331eebed2beab2e97cbe572b96a0d39c6c56264345d78508494ac9e73cab64cf30dd1a6b053a206334918c169bf7de55eba7b0725a4d324edfad73f728cff04c230cc800d06d75f93c1047bfc306261457e694e58314442b550c21da3fc5ae7f49e98218e391acdf9df7279083db0e28ae17c71e4ee4d50a9ab80cbaf99948a85bd21f4c51c51ea9a0b344dd364386de29ee7f34fa93d3f0b81c9cbe7f423e485cf890bfb86bf0c0654775a9a0d8648c2ad9f6ea00d35e4b421e461406986366861e89a0c67db4036d05ad358638fc5ae7c2c76f43e3e432c7b48a5f4402bf60b5970a5863e10e775dad4b8bad53ac339f7858e8eead553c4e52d77e22bdf9931f7c9e33293089b5a8700d7df554ce357c63b274df64587d1a0c28aae13facb2eaacb50a699b22575244a0a6153cb078d04c380c050b6c9681e92249d837bef76fffcb5a3d572994f6b47abf59ab5c637411554bf1aad1608342b28b45102b9cb16bc6913d464f496d3371c023e30d23afdf07666a7ae93d137ea26b51a7434806fbce66888c6d1c98139559b54677c05418d3a3339335f135e33a3191d978e8e8e8e8e4e8dcc93f2ffebd7afd555841498b0a95500609af6de821558e50425d07859b15dcde3aa6258da4bbade76e530720414a0508da7feee9fbd916c38a6c64350b4a06c5718566cd503c18aed5251c0c3510f8ef177cd5c3eee72b98b028d637bed29f8737e3e33357da8cc586c4ad122d31fa4053327d1a3eaa9e5f9991f80aa27d18bdc1589fa7b5e791d4e830afb329faf769a3e3486478931c72933442d2d2f12b55811c9cef8185e943f35325e87c7a0b049fec8dbf2f2478c1faf63a377f99834b98c4a2ea492e85b5e9425ede98f07913f31d0ad21c00d3d2af46a446143ef695cb062c76231f9c3f2f67fa68c22653226757cfa0649362161a18fa7482c6154521cc6bf3562ad5432406fa9b10473c728188bc5c299b2ce82c5c62ed7eba78a131f1e1915d893e972b19cbd8df64cd65a0150afb02f3bf8a9e7edd7130b4a62e9e778ffe4e9974b1cf5cbc562695f353027477bef2b98537fe33ee783e11f0eccd940ff32419b8fbe8fe7fec5c67b1f9587dcbe971db4dfde4603e76bcf270ada54f0067c9960cd69eeb4abd52c579425ede258bb38d62e8eb58b631c6b17c75c1c3332d359348c7f17302e9a6ce18a6d00ca288cd194d18d6bb2d060cea94d6dd643f6e8a17953aea1693dfc9b2013baa78734795f396f7605fb89c350c07b7c13ecd8f53138c6ff658c9bf3a20f35c6e8e798264c2ae8f9cb53430d38703ccf66cfdc0d4f9a617e369bcdb6ad34da18a8b657cfd66a6d206f03792330ece5d3886f9de252ba09d7bf91f413b197741324987a4818d8900b6040032c532a75c9927a48538d8419d239980e81d5004b804bd3488ee6aa860a27c45aadd3adadd699d010b899678ca5bcb8675afbb57e53fa4cf3b9e8c0b287b7bf1b660d13895aad9da174d5755dd76a750e6460f7ce816a6534912c79691ed8ad195d8189bb89d40b266559135a2a9f222b554b65237beac669df01fe0007543d5a35fba902a98fc044e0d0f6105d9715d184b1a5ea3a15f75dabd59ab1999921f96db53c2fb451c251637313c4689b9b3948376b5ead6fc009c3d22bfe126c0f4bb84c831081dc9e2bcfea8bec73e3a107403058917d4413338f1e48600296916f5d70fdf3e1d90d0d7a3ac56efe36ee15146be07a6afd826cdbb681402216bb12fa40228fa5b3dc4a0d7d54d3346ddbb66d037d9625b4c20256cbad84be0ee46d9bd771356439c161ccb0d878c4939e9f202d6b2923d1ed92d87abb24768eba2476e4a387fe4305c463c58685ba4ad7c934130e5552878763b8ea412a18561c09c089ed7fd1a18ddcee1b49c10a366c94a6aebed8bc67bab9f4a582363f9a1246aca3c1081721d896151b562a51d8f852c31246f6c460ad82e5400a969955b0a947bb8ab83d706db3cd25af381a81110557e708a5a9abe09a15a18511d63ec2465bedd980c49088f580cd601b0c06b37198a60283e1a8117501d3509a864ab57886b08e5829d1d15028ed09b11a56a56106564a6dc60cd804d130c47ad81c48c52e20d4a4848dda3698f7844c715c5754adda0c2ce781225c6f2de8a80247e17ec181a4d62899ca63e9152b2af460b3fe8b1ce143e4ec2ae75447b35c34daf6038b6528160ac51282041344505c2c168bc5623729ce2d9526a289182580c39134351305d0a1936f7eaac05f7d05418d3a23e76c1a680bd8cb07f0e33e5ec563b399599624ab540a435913aad4101c1e33b8420aa8d38f298428a7232f3a740fae33abd5dad1c86b50bba669db65e7256150723d8c22bae8557cbabb3f3d42830e9bf564fb555f355eff9476b7bf49e7eb8ad7742758b00e7b85a26f996911bd7cc5341bd3d02b7a57182d87c2f22e5e1c9fba24f67b1dee45bcfe1436318df73c3b5e8d5c77306c42dff22ca496b724d13749868ccf01fd8c0fbd88b4f220d207e67c2ffa1cd0b71cb111bd8cf7d102ce781f22d0876d7087d0b7fc4de85bc09c2336325ef4392c3f03bc59791198635f460b89e54524fb73b290f83449dd8b3caea8bd0fa3db4998d10d1de83f1c159702865771186fc2add611fd0ae23e6da45ff7670ac3f8bbcecfebe7355ffef297bf5e2f4a619dbb38d15ad358e3e6315f5b2101d1b70438b4461a898063fc7de0186718ff2a5be0c1857dd19e6f09da6df01d2e235fbb3c62d7885d231d34287169db569dd8b68d37d6d1d9b0e083c372c79126eebad23182c5e101019a3044804d38339d23878930298e157c28ce26936dda56ab27f3dcc7758a389594942e9b996bb52ad5ab54b2566bb5eb54ba4a9afce42ad548baaa8e8690a1320abfbeccff022e13c40557ecd4a80512d515bb894de4281ca5e7f3930d1031ab0047b9f3c9ede7516b10ef4917061e848007490bd5e3aeb7bbcc640b516eb77f9352cd063318e18b91e3d8fac6711cc78dd2b46975d3ea07e27134053b63c269391648b6eeebcc04b94edb368eab756686eb487cbbd5753536375cf0388ee338862c47ecfc9186a156ad473dd28bd78139317401060f4e393fbe24a723f47ff40f923875dfbdf47b1ffa482ffd20b05bd50097ca94202b021359ebf3beeb041d232684cc9692d075d09282f3465b5f0c2bc060b7baeb5958b1a90ed977a3608f1d845220429060626cb55aad568be588bd92e79c94523ac1be9225537a75b80f0971ccd1684ed115ec775166bb5361a43f2158ff2aa511b3d9b8da09a2d2318262aac39fd020866d596c8918aa7e384cb1f28a55d37a36c36d7626ae5b212e45d899f67db9569487de1c116c8361d858adf2ab42c7cc35a3b3d96c369b699a3785d6fec31a0fd8a8e1ea2971bfe64282b1251afe5326d8fffbb55afbbf6d4e34959479aa092925ca9c5276777f1886f839e5fc98a1c4ec14fa1f9c173af6a52c0c36f528933e4c334720a314639453aeff94b210cf9c734e1114566c253bdf0e7d0abd46aa170c825ed10442dceebdf73ee47954649f293b1db8a78f4347677f3754903ddc260f13366945c85432954c25234919c704e118bf230f1ddcb1752453c9502e336532994ccaa48c521baa101566e6f97acd58c591a639271039ba48008462a76d28dc0588331ab94cd7754d0242ad10002c7c0d52781d417bdaae57932d3a47659acd077c75bb374fdfa82feff55ce7813ccff32a28b4d1d203d555c0652db450e57a2b9452daff9765e877f7d73c48e2ce27acbf46a2b4524d2b694a24a574d229c7719423cb15e5a85d51b21cb1e28f9e102bf68b254b96a2ab821b720c33b934338a5b02bfe24b77d0093b8ee3467f0325d0af51272f0d3e3703db301211ecf793d2eec8d6db4637ca1a1de0824eeeb4bb9bfea6c3f348d20abbc35d94d00ed4d11be86803450f78a0f73c1f40d77b1d1e690711ebbdc82e1bc01d38a858fade83409482a8b7c47a339ebd1e0741c3191b58fa6209071c331dd75f6d14b62389f5fa50e36a4fa5699e4a330c050c0683c16a361b25263caec1cd5c7b7e9cf8548945a9d5ebea0652a954dae488a86cf7ebadf5946cd2d4e329b1f28acf63b52bfef5ba4f88edeaa655971efbc2bd1097a3a105db2abe53f5e2513529b26a7d31b0948617581f47fa7d74efbd8f9707da8040ae46ecd82caa28d2b19b580f7f504f471949f97e19f1d281deca75001af66262d943ee786c93b4ef6feb1cfcc8a5dfa3722c2d4a6538ac97794a29a5da87615d00b022c452f0653e0b71bbd520cf65b48edb36baa2a0495ff30f87bebda2a4ed7a3f60130e2887f7aec97432dcff0c21081d8676fbab8f53a7a0a26f386b27d55af56a4541d106e0a35960c20bb60409cfc85a511e826046ca04a081384cacd8abd65e003f92c1b2700e467abb4e8ef19c458939e7a4829d1e7ee1c4b6cdd9715b2761484ab05c00f017b06d9536cbb65adb9b42a552ad60fec4c8ab88ab87a544a51a8d46230e65a92459a552188e46fc725273c3329834f95879d0b6f9b0ef3912b7c2bef73cac3c2805969910197299896aa70897993c3185eb29573292a61a2961ae699ad63f05abc4271a66e02a8e718d44a43ead907a95d349f002aa75fa315b4a749195ab52aaed4557a9422f690a7244abbff2dbb63dfd2ca001b9f31bec41fbf99ed7a058128d443fdfba4cd7812f3bd8173d0f2bcf028a56de87e86d079244dc8f6ef7deacd665e66b2ba4976949deaf3c0fa067017dd807fd0ae8c3c7ca7bcf27fe74a0afad9034ed7b04a3db81228fcb813e8c6ec3be9df4388ccb4d6e727b52c3c3fb95d0adedf3335bad34a67195f3381157f5ca55bd72550d35e0c06163a55ab9cb086aa55aa95e2a954ae594da50712e0315b8d979641e7329fef21ead824fac8fcbd09722f4a28c22633b5ab7c33457700ea984964e4bc75b5d41d933f668d1df0183892540d6006d8709f4df7b9236f5f03a4792040ad71fe481a27331db62e65968ee45928a846936ff828b2d3c0b6fe9788b894ec78a2988709cb7bcd571a18d128e9f6f8ac7de0aa594524a295da976f480d087422f0ac0d42d8771d7102e9f28a5945a92e85948f657487d0a919eb8fe4e5c06f837164d84437f569f4224543d8942274bb2608e039c70c13aadbc10276f85c4e30709b342e2615f447a11813f802ffd166c70073f853e04f62861502e43b94c119c63fa4801430ade5728740ea6f95e7419cf2079ac7c917d388ee338707379a8846392f40d241cd3e2987602dff07f3510f8868bdd9a7139c61b089b7634bfa783d7921a74d139ba7796b72f826edf986003692101a2eba8648eb87456b2110100000001131500001808060583e190583c2e9c27d51e14000c61804084643c1a09a4410cc350c820638c610600620000000c0c91a8005bc4b75540a5a7c72b1deac50145f8dc4928dd364c074088a269ee6dfe486a1253dfae4a91eb83683c7be2abe30ad51f973c7c268c732e8ff712c14d2b6159b51439e66fbb7210af987b982a00826bcbf7e28eb27efa429df022e047c4271939921063dd42feddf0f2719645a76fc4a962a073a7af70baa8efb54d55a27d93719c641f8c33029f90d5e58d35719c62d7755351f582d2e269d3d628a6a751ed31a34f8240329c070e9648c5802a045db863d9cf40445b0c278df7d0fdf26edd62eb90caf0a8a0a1d69d84882dfb62ffad8faa54d3307de15dd3a4cade71fc11a4c9fe03c21e02fac399b2997cc8a7581f30db8a441934184df0eba87bfd4c734b0a3500e2cabe6d7e2587073cea4c712c021a900e439abe8a36df876321bd5e1dd189f53fdb99d07b13dd5be66c12e3107a6e1fb13d2e9aaaa80a707b64848797408927c17d4278565c732853861a9b5652083c50acb269abb8d77f5325231858e3bc3604e0cc020993c41205628091b626e07eb718fa43c37cb4e2d484f935d82269a29d0c2698a6213032ec325c24380ceeee5160a8f8307a02a297966fc2f63df573a80452eec54da143bfcdeeaee51ec6f06c1c04c9b27c675ce3ce96b1300961209fab8aae6c6968d96a2ef24ed067633fb5349caf52c366b82f5361003c7d5dc4f209b46aec54ceae2bc361ea71d77f6779559c9b30548c205520b84d0c6c00b953d273ccface5a88ff49f5320dc4cf82f7e966b2d97d82ea59e90de3eb2284f69e12fd4aa8b8f3b63a290a242f139a2bb71e7fdaa2dde223820750fbce770f88ce7ffa4963bbdccd1923811cb1a3310872841489428b57cd37e8bca7095840ec583dc08bcfe2a9259390a06bf59c1972f6db1ef75c123888d24db519f82c9223ec9311750ca9039825b68a01cd4888bd54a844d70eb0c32e7366dc4dc32fa794fe4ffcb78259a2e16d2c1397a38ba90c9bf56a2f4060c24048bfb2304d1612f8b233594f77fc494f62a29c14623721828a1e4b5264090272e3efb2145450631bba11192cc5da390cab66c8041590d50a9bb19390378271977000321aa13ec11b9c6b5ef849f56990610c2891094cc57ed207da0b58ff2630cd3030a2343b72fbaf7863d5c02ef0fcce269c574fb398d5f37922212a71037e680942eaeabafaca5f8d22e00672ccf690b3efb177ede1c8e0241ea5f6cce84181921802f9e88ac7db43133771e6ffe87c5078b9880fab08ef7f586d6c305f763c9753bdc537bc200d22a1b3552a06463d1c1ecbc7b1418fef7470020eca9b44e47dd34f56179f4d3979cfc99ae87c3d50a586b4e45b9f38162737f84268c7f031aeef6deac37571ab87ffec269b6622c4b8ebe1bd9bcca9ecfc587f01d74ae4205fa715b832ff7a18ccd52caa79ffe0bf8ba31fa94da79c386099d32273c4b492d095d0dfad08c5dda8ec265996925e12f3dc56165fde30179e24c75ebb34ad4d869ea5e7d04d122e6278686dd34d01192f098e5f11178f25da24ebc534943b0c54da911116a0225c41a7bc2b1ddc80969369c512d042a0983f92fc084a8ba29eeb386d4fb08afa396330a5009b14ab80775aea993744276dcfe0824b6c90a3c105ed2385304f95525c3c7316f5e508b3e4d48265a737cc2b2427e2d809572cf6190871215ddb9b526e68599f27ca8c7e9acf43f30f9180aee798c64a33295cd17b24a643b62ea6ae59d78f71369b67de7a77c0fe88eeba008439a19e32ef7b88b2095c2187ed8a5bba01de81222a89bf6167a78184d1e358988615b41bf88485c56ab78871c2f25a76443eb9efef0bcbaf66041011f07b8b71797d5d82c0d326c4edb90a633f4dae21cfe522bce3b749ae2a19ea31fe08ea8cce9ca9d4c17157ee00b22931e61f68d277b4ef9a623f4cd7ec43c2958fa53a43b50fd4a3fc47bfe7a05a39e5be281b6f58110248ac2606f7c1e7848cb2b5a0e3ed9875d048c9e8e69af173ea71ff6910b7d4dcf9fadbf1661c5db3115cb9cb1aed85079706255abdc40c69d0d2810a2a1ab821f4dc5d2a141f3c4a74ffb7dc1fa3337a8e53db2777235f1bd902d7165f482f30ef08313bc4d8e827f8abf1df92fb57482ad4f85ccb67ff6254990853988f8e34c1ff7c7c2cc1a8553ceb6f714dced694df8c12b789fc51c671a3d56e358cc4ed05c3c08be80b4322d7fbb964c450acd41f24390420649b61876ca9e84b4dae0b44ec88662bcbd6627f1f9cb43fb4406e7a4231e6686635002426c5c5cbdc10157e2e0d03896c2b2a85d91c7ea3a2388270c6de417c82f0296b6ee7ad0a33c5175fe4bcf5c0488d85c42924c9f66caaa4b7cb5c3c486ce6c1e4f736f27c42c10810ea7e52b86ca09826317c1f4e791ffa7f970d753bde5b1d8890e2110aae3e960ae47bb62b05c7dce68304d4478e444d0b0021a16dc5c09e560d108b55f9a83fe4ca4263c25bfd1ba7a8cac88314099149e8b8a3302525ad21e6e171c4857023c0d22425e956613687fb84caf0e1085f04a5dcebd05aa14149bc9000f40ae7d0a979eac196b3ea77cc1e900472d196d9dc10322a43a4b63824b1a73cfcef9544ad41a9f541500cdd601ccdcb55239616fe2cf20805af10064c9a2c103eab785fb5afcaa0288287402fb8881a11ce68d4acd689fc170f0dd4df4605deb214bf82b78db0c5dab8a95077a0c43e074b61820de2d5b9a3d319331124705fb27d4dcb192e0403cc241667f512595d84bd36f1fffa60f0455f9ac022e3402552c38da214794ab8f1f925952569403a2769c7dcc4290de6d76a71702b2d5b8c26662793abadf79337f21efd7293cb1d57f27c43e1643278f7014493a6eadebec9eadca9c8c8337e69fa88b81601253b10fad222b24f717d93155181b51cca11ba3a945fb686b5e81fd95feb5aae1d18036c9d58c781f997df3afd81d4c32b8fd02498cbaaf172d0ee81b76bd4d4e8d6f2edfdabf747de81b5f0a0ee42841b7f50dd2e6ca5f682e69211372f3bd5ed3101ad0c3415cf89f4fa0b43f7049528721df18c30e6bee31e41b7dbd12ab574ed3128f7bf85a1e0603cc1611690fa946e93cd34c0464626e8f1875a506b2264317cb3a1d54fbadbd5b0b2117bb166dacf872c6f910da40765a58474ec680cd3a095990db68c06cce2be4490b31c7c2e5c63a1a61a95f9a026ffed3669fd2ff1cd5b14cd2c97b97c05d12208576bbb3aaab596342dd5653ba3892f73943305d99ed76676ab49765defd56c6444a20d36059d73a3467917a7462baaf8c7f5864bcc84a436553d58061691eb013920b0ff338f4af469f67cd8fc77dc77b15a3cc7abf038185113104ba82059b8b47076ee02b7c3a8a02af80e0917107c765d0a9a602cb280d28b8a1f1e37e678b1d6d1dffd1347b2433322eb500ac5c669a3f595cefd6e21c7fb2cec274ae8e60854636543912d940011857b03cb3c9e40071656d41a7ffa2b2c27ba5e45e73e406acb1722fd0611a45d56117ab5793c0b41d7b8e0cf58a768ba160db3ae9670ad342aac065a4e48b12a8648c3b2b0c52498e3aeff2091cb30f630f9f360f533113f92ddc8d77f4c7184a3950b9960661865dc046af7fd502e451a2c1ef5d02ee69f95a2810789004dd525e386936d41afd48aefc038aad78adb8fd03b3b28580a296c543deb10f79af3a1cb06670df1cd083434d9e1baaa7105aa1b1d3c737064b21ba962fbdea89fbb4af8d4d1ca8045700b6411cda7b616d1b7a5269eea20001de27a1d306c87eeef553e34df3cd071d078ea81ef12f9b34ada1905a9bef9e589e60554abb265bb1315d20c238760207929536f6e2ba2769cba4c786d75f9f3475e1b14ea60bb0ff131d7599c205b8dbfd92acb7325ccc4b069d0563f30ab50c54812c66f0a7b0eb7555c3105677d10c2d6fa795b06c9a2b6e73c23be86dbb3ba01fa702162eaeb905cba71a18041a138899b7efff68b82245aa071c00a3f2af2cf5a4f01fafb2a3b6ae0202394cf177e37d1c739a6998d2d3ec9e60706782ff3ff22d4de1f2133e0b22c6c04eb6085217a07002426f234f7207ca3bb5afcb0cfd66c61c8770aa7ccc0608c53c43e753cb300cbe0d3a1c2319b96fb9c510afea4040e9285c320bab877d9d0af1580c3498e6a16fb58566dbe542cd0fa7d945d8194518520194d2f26278ba18b76b31a4a74088c9c3c35060c85d9007a4707b6ee0bed053c580d2efcf5911213e2300b0b510058f2d040c9ddc2ce13f8e26063b40f862245ad3920a320fa5012ae625435b878fe65e367265e5e35bee89b3f612ba3fa1465a463c141c99e6da0250c96a9c2ff6ea8871bcdb4ae49448a0f033d9ed696bf24981cf9efe568edfda34912c0e3ffa3df8fffda3b699910c4d5c183d98d05bd8129e1f3d1e0fe7fc0d5c813375dcd1b51e0f4f8ef2ce5234a35051cc4c0c93ee922e7645c121ac28e2c7728129633860f383211aeb1c8311f3b9633dc0e506ec87ab243dc6acc4f05ed9f2da9423f54791deb92ee12e698f3857b4b0f7afb5e33307655cee87cf1c5757db9c25cce8c40aa0a45331790844606dc4d40a696465c4bf79c7d10dcab3f7b8425cf62826480cf5e108686b6f85d1f626e12a8107026d1d48d56b2407b22b34d4807efe83e83782bc1468dd24ca44f1aaa2856140929b19a24885d06161de4802df2f01c2f932fe726068cbf3dad0baf78b193bd59c0923acc2590b53ae4f915fc0a22d3657d085139dd3d52c45feb687c9a03392cf530bf92095889bc942763396398baf7a754b0e2539a16db4cc63adcf983900d2f5cf077192ea85ce7c3f94b202f32648055b102a69b500501ba4cf025ba411714cfe7fda756d812543b31f7920efd20968bc0911faa7c23a24ad8ee628eeee220337093e246d31362a87d746bdadba15ea2625f769d93753775056fa3cbdd5bc36b1d4198e8984b67a31d8727720d3f1351916ec303c58efe0a0e0bf235296b3c8fda15d69f8270263d26de3ce55556cb61a3c2829c4241695c6874c0e6700334c567df1a25fd379579364e3d563ce9375ea227f13f027e6332152580f80a6dcdce6bc34e17331ad311e9d76f43a1dd64e1bf8402b6066e5f9bc2ff61cc4e27e1469e01f9c3cba70112ee9a4fbaf73a47445e9913963d3c6846c16d3307af07dfb56d9d416324d3e933bc4644b67a61d4a1e4e9dc3d66a002207672bc3b66b8cdcb0e09af3d3b97ca754019f934b61541cbe73d7c109f3f9535cb97a5f75d5c086fc2b4ea0cbcea4bd2284b609d4a5718201e069000ddcec080ca97f8f7fee1dbd005cc8527250d8a4bc1179ac1d74aac425093c162aff0f22605059b7994783f3232f4dd96c5bb1fb3fd522d2da87bc2a28d94e5ae2ddb2a6c6004f2a51dd7c0c3e79640de48147ef7a0f70c3272916d86e3939c8d83e88328bd11127d56b487089a977093b4e2b676cad27f44dc3f4d28139a4d6d81f5a91342974a1ae1b8634396d64c634708e28e48d39df050ec23d228cdb2d45ba753eae3f81b3282838d9c1ebea26a5d07deaf56f2fdef6d014b4d7621d7c2797c1532378df8d1815ef922536e52b1305979a9a64bd3544a486f45c39afe07a8d4201407dc16d2407cc31b247babe960952c56986ee75c5c1ba142facd46615372e9e88a360f0165fbe01e8ea572300c86cbf3e8fd1499d999163cd8f620e21433a520b80fd80e1557214b7a644f09f897091a362adaba63c5071896bebd27e1c9268f425239540699e6807a2c23136c082aafc4c29502628f4bfdf396e852a62522538c0780d812d3fcff8991528c5ecc10800607df981879205463d8e9e2bcd776c3db3ea0bbfae2b431f324fa3d1636b51e554f219273ece699f8f8088bd9757390e4dcf481fd97414486aecc9ced42c6db6c2d68303366e8d51194e7c443f5a8d62783c4b7b21db259b9b52b777db591cca8e2bab424ef7abea063244517d78ac424b1466e8cabacdae2d665388a6f811e69abd342e1e9c55516ffb7cc5924670db1c26c9932a7316a88a4b515fd8e2ffbc378d2465811f892c80d743e5c6206b1c0c5b6cc23f0bc020d695d64582781fec69c308f352e89d3fc132cf19f0951766d7875d656172dfded480a15532c3c5c741836c39f99ab258fc55774a35042a3dc04b5c1d89fda1f0da289f1296ed453795248dd9542bb27281048bcd6aa8f192f41bb39c9ed3fcfcb65a3c28b4f09ea79940efb07de19203573248c986c7746c95a1574a9eec60078c0f6dbe5a4b7611390671d38d5f3181facbe02a09e7a3f2bf1c276aa60be6d1fba3124c272ca8c67701db7168dfe3f3f5f7faca407b43cfdca7d442b390eb595404e51726c8733da3fe70154f7a671bfa23d03c286b8aba37efb31977c4d3a4d6405463e2b504a896cfd76ba0f754f3e801bfe5a18633422fd50ba93c24302eda7f820584210c54ee037d37db88b24cc733511d1711e5530b02101f3fe70cc297d1f2977103e3cc33f7f50c7b830bfd2e485d5645ef17fa1e98d5dd430a7c3fcab0f546238c6c6003c2c48af161ceab17973e96f58e256782d8deee55172021c039455d71af78f6f4a9b93e73d8e665b94e993710bb710854c20aebdd021e6964d1909a875df30fc3755fa9b61e29443157998c09c1fa8546cf1202d8c01520d3e9a63a1c538de76203d87ad2f673fe4d0ad34d90d04fe7d4ccb14759e339d1e886cbe9c59c05f73c478dc5d6d05d822fd26e9b137b42a97756507811d77b89daebf9b18e7c6bcf0e0f08e1c4152bbe7860af7515db364fc86436f315ec87215318ad64aa9751d0ed9286bb5a9f1ba25b42cfdc8d62a58655d82f200e27269f6a43c8eed9a7483e24ab1c62b45ca509e9cb0b0f84819cd8c7556ed2b8ec83faaac3949d56c3a2a197c9f53970f2c6c37af0272c8839b2017316b4d38ace2151e9f3f47893dee1c6d61c31d24449c1bbe9a5d667f0f70ac9159b9171bf2e221524b470c24ea40bc16341e0fc172b6d3172488ff911e9b5875a0b2626029b8419d76aca1031f60e323054b5eb0e0c350642731f88030ad1faf64a6a4076f1f40e9ced772881da70437f16168fa5c3fe4c36cb3245a04e56b0d195c6b8760308052cf8931f62fe34ff28210bb876abad9210731bcb1e40f6e126a82ef504461f14788de7870662695b53b2b0f75b58e7c20f6ffdc1fe66e78fcbf6390f5c76d10b89b7fff00d43cc1e0ca9167c82522cb7a5e1c3b0b052235d5e96fc6c87f58310fd0873bd2e551b8a5e198cb2a27600f5057b94a474fda69a6dd8f10837b6ba578f561e94f135aa25d0b508a58bf7afd30cfdbdfa5544192f6c7adc0055db1bbc95499e227fd40b1ce63c443fa438ea2b72d7ba454c6f6120de5f48a79049f4a4426c5253981a4e0d382a4a965d3d0fb769ef5a5d6d3d6bdd28577a3499ae518c3f3dcfafb4ae3b2011dfda41d042bdc10b643f1b231d11659e6d8cc8ac395bcd948932e81da32772c416ad932f0db8c4ee1aec87cb988c880a31167c56777e7851648a451d7690089a01ae253995c2511792f4adf9ed67b40e60bc179c7b8f2bd51afc0c80f7631628cd6a126fa6c5d1c66d568db8d32f7f6e39da066f5c62b9c49389d1c9ce6a70c6a41293dafeb7d726cf24bf0d9240b0231d628690802d50238fb5d01a1958150c601a9821fcce1c9aa2593fc3f5ec53ec007c1cf1c7bf2fee13bf71a34e04a4ded34dee000cdcc9f7b51166e4a9a640c97ebd2d4b287525fc226a5e1959851825639227f7948b6e1cf3cdbea9398373f86c8cae16429426c462331cbbef3a338a57375479af28b6078e1aea62909b4dc30fc2bbc50843dec6f7f28942283bce03c0ea1e7992ee34982e713d0c6b11ffd5e5ca23296f27e97ad7714c8fa414dd3bae9c205fab2d077e6349da1e40f620ac725f145565b231df862fc9405c58b2d507a2b699935287b860ec7da0150b3bb93092925c232729ccc1251a0737b6a272b82a21a65cdee1f15974490e8eb826a6a9b57f0940895824659a0fd44fe73be6101b7ca86aa235dd3984a65b72cdb6d801499f1f4e14fbeb22827f4eaaa5327e2af6d472faf5ebd9c9d672d221c251d89fe619951cded6cec9788680e5ea9b617f5e667670adc80ebc230f4bb9b1aa1e26a73fa787c3699b244dffc88053cae09c51622d0954d96ca725a7f1f196d7e7c5af4da93afb134c6336dc4d471fe287701d580986d5e2f9e7ab6413380dba9aaa6b469e4f86bc82affec94353ef1344244873851ef8ff96aa537a6cda05c3f9dc0faadd387c0dfe1c07fc775a0f32dde5c4bdbec0c6e454a172de29f596ca138aa27f77549b24a193707289be392fb54524394f158abfccbf0920ae379963689ef1a3819932ece9c585de74d697bf0b5757cce238c27f5aa7c015500fc27b751339eac4655158f8029a87ee2dd524fb7cf48371d437d7507a664e835ea064c0183bf90ed0d504f11952aeb149d4405f6bb08594b491c4b891adc52251c800a6631b2f458bd3e2fffb06ad5b7ab5739deeaf65f9ca59d8fe8a1d5ce5c62a378036d148ccea23351231951cbb37e52f153997274782f33a1bff0120698706e508348484cd6ade31e378ee644040cf4b1e94a062cd992696a782938ac494395e597356e521cdee2aefe7b2915e2168411d38dd3ac38c72f4aec8651b5faa50c5d0ea5417b75fc5a042a38c5b3306c7ca817f50fadf12602d0cc1b904e747fe73ee15ca4f31d0ab20911ee0fbd66c972844050aadb1ed586fb23b6beeaeb80353f31dbe00c9f654189568ab15beb542b692d4c60d17a8c01b1a4df9440e41abcd118d3c27b627b78192eb976effc09f3f911fd4d55c4e0296fdd250d4dde80ded3991d61704414efc5de9a403852b6c4c42c6f015c8e75a6fdf09bc45070e70d189d42eda5b20aa431a3dd036e894e60523c20091c0696aa367fde0fe6e8011381c49b84a0a050c3c200f997b00248f1ee8dbb7c26d7c0a0928128f3aa103d40d548bb5bddd4b873a1a799e942d4e4d4fe9080804574149f75ec757915984aa798b75522da221fff0e37269123ced35d12b78ca6782db74e630458df7f4942fa7b910ebacb38ab4f45b572cb43322d9305d409c737b6929d0cd6fc84469134c3f432dc1f420e092a2b1e613a948f502a5165f781d7cb447d97c82a9ef0a94e57d827e051ead65655265a167b8eb4ba198fa2372913af11f12b88db454dee48893c4be33b40347ad01a57e2b2345dd483e1871a34e6c57d36af4b4dd15651891bfb71450902334c1fcf33e97c749e07cde87cd8adfe233f02b5ca7cf9746660fce1b5c0f09653979fec34b3bdbb86e56df7663cceb82c65b75bc31233df99d32ab520f4ac9154a587ea42f7fadc824479061fc095476e384c73722962c88c394968af26b1189abf3724bc4bfa296f4245741f68ecb71ec5325171fcaf44cb1fff56ba063234ad465741a2a11e6e5ea1818898bdb7d37517f6f16da61623641a4e26bd1d5588ab6a9c6129d5ca3a4259b943d295427dfa0b9baf551f6a42618c906118511675b49378daedf12abd77de1975a091e6a8ce98a170a3205b749710175d879859b457ec988bd89f2100eed0a9c02e95a0ce7bea68d5a18dc223b08d0fcbdc4eea3438e9cb310f72464992be3f5fd45ec6b3cbcc3a282cb0455f91d6e350169ea9045aae6e0b8c5dc190511845507f105caecd0092e6430c512dcdad69d622a6af8373511dc027a65af7e8f3d3f52f6ed97416f8717938837aad82dc000b797f739bdbfefc1e0c740a49b2e53df5339af86aba1bf116d34094ee24db37a0cb16eb06f6edbd451c7a5122d1688702a56c18352d5ea017838d9e8df9f33d8e51e9c80e7cd84253991de7f31b5990e44c210199b5066ec89b61d12af912333a253f76615b7eb899b29c8df7b591740f7c9162b20038a6342796fe99a66ec397519e602f352dad809548bb3a70403101c069c3cce76b40a202af446ef9145de16e0d33c646e9e36258f9a4ffdde85c3155603e59c6a07296a3b93e0003e552f96a6734f6e42742cf55cc1d425ca2de539ec2c4c9c7e123a77e40791534e0697e3d4482716dbb5963a051ee7e6e381d443ef62e132092387350e3cff79c22e028cd4dbd70b150cceaa2cf7449caa20c679576d15135eb22bed8a36f893c6c1848d96b89fada8850c29640b639fe6368975bfcf863c5d27b59422b181ab0ac18e04c57a1bdb2a98a9653d16f6e651853137c7a1b546088cf06530e1f0e17c0fa75bcb8f2f4ab6c29fc3dd81e5dea0a4492573e6c0a29f8c090ee7cb10eee41c28c7f79dfac954d95b8e7ba9302d7fa57f2698b0a6c496c77b483767e423778200c2fbb5615ea53b751e20daaf9964bd37bc787f61374cc20dd36482840401de24097d9610e2cebc2273eb99c866134a25ad804b24152d751a8596914c2c01c5dd76bd388121d2171205614445f8166625ff79ed11b425bb4f50de399f3fbc3011c78da96439c0a85db5ead0dbc568fad5763c127c3be6b2a8c2f0ca463acb8969fe8480f289acac064b8ce5c024abe6abe476391f258a051486d35ca14ed4988a94ad0285dfb60772d62a2611a811fd30002cc86a296752d1dbb7bb4c6a88cdd9a5c90e99d02d6cbfc97c64dbcba749028e38fa7fc33030f7f6e21ae7e680600cf4b2a765fcc4b740ab6229776e1561eaf196adb281bedc3f8d10efd1df336468af4f04438d94987b88993cbdf7f143143dc6636a7efdcc838e65213a1dd11adaaa4fe2810ea161a1d2cc154c3c5c28d8898a7b8b70344edcb82ba762bc9c8b35cde31d785c59a2122860b117b3078339018d3c176b6f07c8a5b2b41fbda86c63a48f74cafc3d0d99819debd46a724e17656abfb21d20355cd0572a5f85612ec6f2b58486c5ba80811a90b9c69ccc90cd754844a53e49a3f507ccd51bd2d8952de78ca474df3543913422312024012c670e880450cb97a18033a464d5356d57d2f51f2bfcda01e5368d8db7c89916c773d4341f75c51a447d420089b5e6d23bd5eff3f439e7471418c0d8b68deedaded2d4ba2dda0d0c995856196d7766ac0a1473f5cfd9ef411ae7ddffe3fb2e76633c5d17d9eef36a420a236e6ab7159dc573b5156b91d4ac6687978703fe02861ab9b808f7aac5c5bbdb137e334e4a046f370c4ff51088ace0bac5c232a250bdd29ac26e8d7db9fa65f93a12203996726986cd1c0a1458aa42b16e43ac1321a4a31a3964e5e0486aace2bdf936f2bda43402b080b083eb1200b0894a64820c37b312138815e59abd319d121456276f22973290f406c61c489fa920d75dc084cfd5c1aaf7660f238f0329f8132369431b5e48eb67c612a3e336cec069afd52994048045ac69c7065c410d6278171e99e096a04e2bd23213532b970656c5a06b75906b73b52f51c277d5d63e70c7a2e8ecaf58c89ddaeaad68e40855ddf76e04d3f1edbc88e4093d84b3cd0fe67e88b983fe444bbcd88ccef64c2930e1b7aaa5be2cc90122816d0cbf48f0fae630ff71d621fda69d62042e006ad96a3bbd0588a19e65d1c8712ac8b587909209bbf5f009fbcd2e0935628a4e999635aa1deac96b4abf7272905cca58102d8aa9a8f80943d735ac52faf6b816aa35e868306b9d1fa2b56249fd644a23f49c73f10cd6f089bffaa859ebd3a1694b8b21cb5b0ae9f9adc17c23db222d00e04afeb2b980db309fa90700bea810a590d23f809b03eff7d30962bda5fc942057595f79e2879df7eba0df55ca77453e84a7115b1dc9427e9c6dcf20c99dec66e6cc34962dfb81a60498a40b84aa7b3cc04b973cd63b323ed1a16121d826c572db5bf4e7cd8a8dc5748741eda69685280d8c3c416cc87a983181a29a292b201e32f40ac3aa6d035c82543e2955ba01d0f1cd355b3035a18b72bcaa9a3ef6544de4a3729542ab11aa9590ff1d2f1895159cd3d46c008c608b0c940abc6f6492ae342c7e8c19072e896e93516d749b646de09cc355385753472a71310e4e5627d4bcd9455855c3ce353cc0efeefc5f318af3f731d0d26f7dfa9f308f0fa942c003c2fa7217dde59b746926d44e33031d1ecff9574d0b9e7f2e13537b0d167e903fd5dbe050e1da40618e7382a709666bff057b39b5373a96c1d6aff5a10546234cf6eb08289cbe8606cf75eb41eeca09184bbbbe27c334ee08f0401505812346546cb03b08fb6affb5378624228be5211f1904987d4c6376b8191e58634fe8294ae97ac9091dbc67018bbe695492668c02dfc01819d98a25e3b26be8c11891250211e19d80764f69c64d241c49a7f5b5700efb0e0218dbeae2ab0d41c6959ec1f7eddafb5c19221e0e57944a892b6580b8de6eda32c49fae0e944e8d7adf8bd841afa058aa9c69b1a44988ca0aa1f2e2a4804334c581c96274917e99f3098ec0cc8ec29ab4709aac9ea68a7e9a5cace3f1725010ff310979cb3cbfd06e3de92119c9f3831fae082e3879909c50c02c71dfb2e83233e1b6f43c0cadb38f62cee202963846ceaf67fb202cb1e9674b19abb2ee12a57e757bd8707f150350b4a52988de0ff06ecbd83561cac68454511ee69cad9b752aaa851cd63787db35e6ea54122436a4c3564c2058640dafdb67a0202c522dc68d26233e1f0702e8150cf955c7fc1557cc1e90fe3bc52a5750d85ca24f9f83a0576994a75a300aec370eb29877b107c8e656df2e7b990534b0c1292b3c4f5037f08d9a7e265fabb8c0296534b59b1b364f8e6203173d67ac9805d1011661750192f3907cd633db59a5673125d4f7c323590187c9b0e0f043a307e83fb9b22722334912263c16177a9bb3dcdedfed2bcdf53c5d4c32c8522d75f76d74208fac5f80259d1300bb2730c86b93a029db926ea0f0ce83f45347923ba25ff2727ae66971c9c2941ad5dc4025428db6afc1e7b72d7befcc6db16976584a2c66ae1ad676f6245334bf4712074155800dc048cd340c0dfdce0585128d1d53e0fae1d68229c6d70542413b1f4e5a4b9815be942c537ebf901d98158dc970a00ef63e77de541172f7da53678bda02c0aecf2c121a40bb9a91bde2e84442177e397264971689c0240adbdeb4e83aa18eb0b9cf6b3986c8a39865e6949231710c6e720cd05a5d7dcba28e2ed5f934069ee0b70bbdde8c7fce7d2ea26825bd40b5b3a04d3b62c8708898637e613a22e4e351377c95e0851ea25d85b9276850852e80b2c69ace512cd2463493a0eb80b91f2532cbfd3f910c1b2357a468dba2e7dac6414ce295f36b5bce993ea757d450f78950c1040ae921e1838a881fa99ddb27b8de1647364cb323cc77febac0c4d9d400313027c21d205b02820e85a0942e9e4047d65b9597dc26e14b95e3ea1586740e113e218772e28c21cb8042bb68fad17caa1536f8c7f54d816a02ece6e7b0bd09d1c487f32e55d7c200e0d036b473f45ace576a1de4007b639f3fe2d7e8d09e63e6c04a0bb682cb80ca0918b71ff16ae4e94bd4b2aa600975ba444477c55798d321a177506d5861c9617458a73d153b67857c541f84aafe698cced7e09c6600205f03ee14646461c63527d70843c690844eb7178b591122f849cf1a5fec5bc0f07275d5d102a440e5912f2f75b8ac5c11e65328970e9fd410e55cbf4ee0850daa696581b943869be35e431a472951d7c145b5d6df832c61e5f1fb95428764f2f1f1b13582570f19f7f16b1c275a33a04b370ff5e202e7755483fa0db6bc41b142a431a7ce87cd09d41154022c7e36581e3cdcfb81cc9c20aa2f6a6e362c766f4aead5719f2c3fa95ef972bd1fe7a500acce6c360455cc1535692a8d34f030aabd67b52ce855d67e3457b2089e8d12f3468c05403f414f580b41a0e302c1b8894a695b939564858c5c7b7b7425e1f8eab981acd58be6d2094cc98668ceba21168e6b6bc94ff870de594f041490ac3cc09e5fc80c818c95563361e5fc42656ec689cf182e15e4ce3fd8dee70c89afde7c072f5dce544652145e04c2c9f1e31c02299390224b68c4b44c3a1bb5342cb491900d0983b70594c132176b9dcaa69271799715a01ccfd72b9e058d9c991578fa81aa50f541a55db386a327fa6af400548ce9c2fbf495b9ba520b5df6b4fcb5369862a0d4f43464379c3fad7a6802f75782c5bcc9863c5354536f0a4c090951511415cb22d7d20e8152576efd513260582c2a1d8754d61515fd25b0d4e9ed72380b9e2683e62a3a92c5efe570b8995fcc9d620c46f2c1578f3c7bb5ca4805b3fb7984e87ebd06062b5b5a68fa3ab60a0dae27c1b5851e337d0f9e01a1f2d2eee0d74e092ab282bd7ebfe2bdbf0d419a8e61c615fb8a819ec759d4c0d114f17306584c9ef46b16f7761a1f0ad8973714897e3ff8632595af32aa4839d6d7901e103a5f1ea6a5153602d2d416128e927fd978eec93a40d438e5bf6bfd535dd8a13673f5b880a9f5ca5734526a4118b2b53fe96f8779ee452b36d3b98b08cddc18e2ff0fa1366003ea20ad894b4bfc2d6d3c73f7d4ecf32850085c4b6a0d97e17ec7e2fbe12269bb3de3d8c8d0d135de8e9f97a5ba007f91399e1b30457beb9e1ed01d2c9dd9903ca645f022d28b1c29c4c80bf65b404c16531f3ef30cf5297c8cc81e1907bbf29c463fbd4b997fce4a5c21aea9c78e732ec67c3f551734bcd1388ddf7a5a7bb1c10878359805e605fb4fa3bb28aac12ebf267927fa7e776656d325f74ba29da5c2bf3e97413234efabcdb51d309c362bae4cb685612ba04ee50e7e9fac94df9445490117765325d9d9cc2536f9576f79e0b607495e3e5a75285a0adee504b1a78e65edca0862f2852d1fafa4888d098df513802120c26800c93d2d729a0722ac8b1d43bbdc9c61b8b0d61551c6b3ba6d9c76844f1da1399d459cad53f660a5b08f37bd7b5c60f60a85597d7f9bca4a7928ffe83e1faed5b40776eb974403f42477c91c664ab12a5b8625cd0605719f1cc82b9d18a2f100b279901ab9f66eca211f23d9c51155bb0d67bf434fb9a5e52bdb13c148a4b4f5068124bbb1fe18d3efb69612cc1b021d7e7859da69f8af25084406f289bee11e19bce8fcda2beaa94e02b552dc890cbd8068f04d6d759d916c1048bdef77a5fc3f3c1df687a8592b0246f0b476c820d7a01546fe5a2f0fe1d450eb551a492b65eba8840174351856bc4b4b1af8062263f8685cb81ecfcf3adc661ca2f34dc6089d3d173092c634266b9df1a8650e8912545984129363f4db3caebdc130a758209dc38777ad5e759d4039ac2e68a3149b1fedf082deb1b436284a6c557a6ea1b0a3ac7a10206e4d477efeccbfc3004ae888dc3eaff9ee62b8248c68e66b66701c0da40151b2c41fb02b3a19dd0c40b30199bb13db63e566fd6af3e3812f469ed23341e3c0972cc6e1ac0868ce0043cee79f89922b14d4b9d40a7b4c5ce184f5850d4a91de0e6b0497598062604245d60b27a37a27eb37faf6e801a04e89b12b080f74925705cc9107f29085bae3a8c5d368ac4e1a76bffd6169eb1f314100acfc4923303f72a1425e9643ffba82c3da63b90ed822eb405711f21ae1915f8816051ea837bd335bc10c6a3aee24e0fa4c1323cbfee3062e1b00516f60e50ea2758f725fef3a27ad16fbf95d7958cdc20b5e81a44aec8935f4d17116ab8f59a24c74a55746075848b991017651d3d1a6b9e6185e591c0bc206856988f2f522d132cf7428c2b45b678aaa38ca5d2e99a80303b95381b8d51450ee7d40c4f18cf6428a07aebaf22533ff86f8c43c192df3e659320c71826e4060e945c449d008113db777a856e9c89050adc8a96765a74fb13ff8853936d1d60d98d764d2d07c0dab6f795719c8283d048424295effe0711e12214489a970351b55644541beffaefc7da8c1a86fdde7deb735ca5fe3123ee03034be9bccd4ff3df460a9e8f31be0dfb49116a5c3dde10d9c955479994d3fbbdf7051aa94da6ab5c7391dcfd2903cdb350e40a2bde5710bf582eeef32128e3dbedd2b20204bccfdf0b57754169b31d72825dcfe0493f17206e6b06103c3091bcf693e0868183eb34f80f00763e06f04e2e60abf92f5f8103e288ab431266ec594e0d80c68dba450c6ecbdf7a0a84d4dcbb9bc5b21866364e0cbb6426993cfe5dc03a5e61a6d62805d8c2d8cc44f04b4e07fe2b97f0f608bf43d47b1678e0ba9f5095e5340d492a795a0d9de9c398a79a9caf105763ad6f0c56d492989fa088729fc1d62b2a31070f3a3e7c7926761884194ef95076370ec4ed8bb2abe0fffd4d1e8fdc7bbff8a912e552d42e44e91bc82fdf8878098f5eaf1bdd0d8e3f4684e13a778d0c5fc33ed7f048ca2f9a82e9460767949f6c0ee7fef146590ecc664b28e28c21e309a0b004974d44447fd1ff477ed9403e08f27864e4d354aafe3bd8152a89610276fb71342a507220493e94da9266d2e0ee186e0a492695111dd17793594b90709daecf592826fa480300293785fab87ad630c430163dd2871eeb1ca218d6d84d52d58e8ddd4a6d510444b90db32b562d4a5e76a84c281630cd80a3c41468757100ddde4d28bbcf1282038984171203c268c697741218ba0bcabdcd5f82fd377a1eac1e09f9524ff0d72a8d43e5265767cb79ee7b25946abde2ce090bd38f907ab823cbb3ae3255e9c1bd27c8f4173cfcd6ce1136e908828d1199cd6553dcbaa45cdbbddfae2ae13270a5e15b11dcfd28c9f040b76e1d189879138b807a116b00abbd1850f56db94a9b5cdfa1c294ac24811992e510c463a31d8dba010494d8b77700b186677e94fae402bb9f69ce396320c1412f65641314b73280580872468d9671b0c546ddb8b585ff76210e0e028a98132a098d1127b4bfd7b3488a129bf3111144fe1ad63e085f9e63c6b6022b35a557a34cbe8f8d82c58d6f015ad29d46d4eb607fcb4bc102e3cc726d153d3c0657d4171dfc39f3ad31962629769fb432aa010e43a5c30b4e71c0d8ba124355c5b2a111280037569a63eac424322c432758f7a007f0962720ecf5915ccfe1c01eaeaa75969ec2fe588e808da49ce8135f7d4c51d58ccf37a9b8538576afe6218bc020731ffbbbfd76160d546f570d58bb2a9131090dd2c31967d66a471c1ff64f939e38dc7b4df8150bf4be18b58721ad96a177f577c3b5eae7a398e30dc8297b476aca74ca8234b076a5afdf339381200b8279e92e98bdb4cf4c0aa48af634ed738210497e6d000eb99cbf1ae704071a6c431c9abc8997c23749070d53fe4fd53838acb06aaaa79aa46af9c564305718235a758add978bc65d315bc0d01e5a2511b4bb03c3715973a4666b9cea3cd9b783e86ac224443eb948c2baec8c99d18300410cc1713b92202ca86f88a2f877128a81abd3b08057c7c25ea57f2a1f8d9bb974ac05c7ab0aa100df66600ffbd3330ce099be1dcc11f846ba69ec08b0c96b6649ad5fd96e11634260ddfccbffc49665abc458943444d0b339c4c445e16e114a3b6ac5c498107ffca4cf541b30c684729c16adaa0dc2460b8aea248bf7e19843503b9fec3bf7f1c6284944f6fc63016e6890f07fe630427eb0cb471f191e238a43f194941738a0d19930e8da9a553d3c681673a832e013911cf85407f0c8dea55c91b47c01179f94cafa55f19480ca240c7b5b1f372b6e405bae9495d32e36b698dc2ecc60353d114f89ccf724414c2d28fe8cc36308a5e6d4116da7f59aed50d38ba103fad4bbdd6f183ebef044732439fe0963305ea5485022c199eb4241ebc6f89dfce44bcae8f927d67ee80467dd20e4ce5955bd51f62dddcb617d399c43ce46621f6e5dd0a94c8c1ad655b532a43bd8cfbfb783eeba32618e662ab00c0b80952688a45423558401a1bf07c8f67ccba199db609c3c1d31db6879fb919e477ed6e940020665062a84ccada77f96af76587e404ef4bf562ffeafc45a14dc8b18a4129dbdde07655c940c27cdbfac313d04e80b032574a434c66bf19f9017e715220ee28f478f748c72190e61638fd5e763b5fef74318115a0d85c9264f6708e002a50fb158144a4cfc23658848771d8fcebc522942405345dfe00f9b6e8c19f387b13470d6ee6907606300300008382d4686ef3de62517c93837f2f60fa9347d211f5b166fe4d7e531c05c57210b88b29e56c1405cd6c9b153e3f1bb5c5703329037b2b254be23223e68746dab50bc1f25ea87020f2d95c818f21b63f386f41c0fb0dd8254155b81dee18cad39426ca90e4a535cb9e76a27a089b2c3ae6cf104e69ab3a99e937f4ae18940efae603b8817676e4d600c46d4e427dccbfc714dd5589f65b645eb9ff71c70f9c0374304ab21d76e386d628081d98f0b3ba608ede102340af0e9b9ef8226a8a72730635b0921a25b3a0578a813cd9db37dfae130e929edbd48b44d6453c29f2bea4c53dc358e8ec9a1fab04808ac103f8cb8fd59aa9992f8c241449b5efd1852f7bf1c9eaa003074815f9bba43fbef249973c30fcd29e5d0de69a4f6b6ec6cc12950ebc85d7ace1d0ce29d7095a3ca01db64cf02123814f82fc5bcd5dec1eb06582886a34249bfca144872ce269aaf50ea4b7e71ece906d7ebafa2a2bf81646f4b66a203e1c8b44f7dc48f2b90f5e7f728b3ae5fb89b6b4bfe90c0d3fac21ce81545abfc3f0d7bbe37e209b71310c9f53bbf4236ca4acc586a8a70341397f12344cc97bcd25f6c3ceee685eb9a35c0fad93837ca52af8ea92d03221df9eed6ef563b23941f87a4fcdca2dbba87a8c2b9cbd05a106cbbca20e16f93d3c26415e84d970bc54ffe830c866ab4549a12c23ff84645817219f91fdc101048256ece05958c0ef86eb38eca2b55a28e397904a0f8e0b9f94d39d49c689367610fed215e50d17a051fc26f14b07743aba98a218a1434f2ef82cbc168d72bd658550cb8c2acd8f3fb02ef70a9367ab085d91a51e8e5c4f2424ab56f0f25382d4c11903956da35da763f89e4da7bf91a9cdfc7a54ba3cda529b7c04ad2bd23410288075ef88b13a9cc07e5c8ff4cdd79d476666c049753515aea4c54fbd2bbf28020b6935e6b3b93a106d310cf407818ef8aa6b31df4d6d190dc20094924513cf488e1610899310ae60cf9946737833a074822250cde9288cbacaba54b3e124c00da42ec163955197ddb58e9375c0907220c93d4113ee729e8d98f6a34ef8c6713609972666b4f84e10c7f575742e1f6f8d641d99524446eba86e9b3e9ccc180ff0ec3cb40c8f2d8ef8e520e180c289d3c4ef298d6f86e257cfecadd1e4638fd72f611339af20bcc7b98b2d71b55a6c2c468412487946907f476278c10500512a6bd8386e0ef261d9b9d3f4010487bc45823b1f36ea63f71a06bb1a772f14a09d9b8c40d250b6b8ebe87d0c7292b8890da226011b96addfd8a75a04621a851d368a0a1879e22436408d11e66e984338db2662e48130ed18ab9670d824c80b189bbb74fe25b90258a03ac816cd00a59566ed2e7149ddd341bed866deb076fa4d0ddc32c0952815a633eada34deda23925d3949eefd2c296d2b2b801d502933445214d79399fc2df95b7d6a311add180500d0eb98a965cdad919edcfb2b456c832560370607c0ea605330a6a25c63a700760279cc1f650df6bc8b7f85776dc6ecdce55b4bc62eeca940663ec638412300d0fea94ca33064cb8f4e022a5fc8ff9b1a71f15227ebfd770a511be21f2aed815b9d27814f88f35cd51a197ee29273714c2ac5ad8f75a898bb44cd8571aab8f69cb2af4db6676db7b2f530e362b1c3bd0fb67caa48cf5df5607e56f78083e7137aa97bd7da13e3824bd3c5e292c81d26f7d1078a29935e05f7503659aa56271c5262391d6c8e2a08fff86c22f6bab3f3c1efebbfe42cebd00433f555e517ed822fdbd34de4240b79b5c2dff9aac290c3857463180f21591ac197e218244125fde54dfe404420bae25f8d781e28c9ea74a8c2f746d428ceb70243577eb03f8321ff9abd41e8e6e7a8ba6b4a3bdee9168d2ccfa499303d53df271a15245f58e0ba28b94377ad046138e287479d9ff58721587043af7c89b03210cc1e8249f3dc5f45cec07861b08d7f8ae5bc094b0a52c2dff86cb1bcce9644477114ee9b257e4446ec63de1b488b155a8bd9955425f6be3699c5d2be7ee10a4ad426d2a693d7e0ffc0e38d2623cab55f28f05c5557c1da59896b38c07f89f141a4336c304cee5c289e9a05581b0ef813fa1cab7026978b74413d261b75f6114aa1d5124e8cea8cc4dacf091128675bad07d6f2b6b55ba182d57cb5eaecec5a39c6479873e0e37587fccb6d07bb0544437e2aa8f2e89a548e87d3329626438df0f8f8becba8e0c41f45a1e4fb5a5444eadab86d8156b6319654cd1feebb1289a09664828afeb4adf9a5839d01c85a238ee5be9a24569d7146f5c5a9678908c3c4941c72b6666e42a3e6f4db434198645b9686ecafe52a100316169ef4fed55e036ccdc099afcf298d73bea505df608abc60225504566aae2b83d2a221de7657579748efccb0a7c70ba4a6d0837cab4d58e46f9a48a07756b53a1a5ea0f7635516b907bb64881c5731e8fff60097fe4335b275e56df9d80d97a7739606a23efe4fa7fb2dcde63ea992acb3491851c1f94bcb67a42ef5f7844d293388b315223cf205409cb9680103af2b223dcded5b2fadbd0d339c75bf63b19042aa77869f1ce4d54932f4e9b7a864a1ad8fe889077cc0c885ab105d6b4ea87eadc70793f21aea5e5418915951d3e7f59783e9a48007b1dd3ef033c6140a6b41ec4c64f715c6343507d05b88110e024350867ce3f6ed080e90e6d7f9a480556a5a70c5d17ae4ffef9011f769c763cb0941087965d3ce4b4cecfa81dc37f6359e4dd370bbebdb1cc895dd53a3b36fec10320f4b16bb39f2204210ab0ed1d5fc01cd489023914de7aca27c8891dd700cf07bd28c9278511f3fc2b9da833aef11cfc46f4b3cfa147b2977b99930c98f359eab14c71b750b8efeef8265c868c0d15d5175e1f77dd22ec47efb08c44930ce860d12497ea7b9ee5b6354be91e6d0cd85ac4110cf0d84e60855c5cf5c3a60bf42c1034209c8508d516a49e360014f62be407adb5165fe7e6d215f9a21ca0f1957a92d3bf40bc6687b37e8b90347d3e854fc3d06146acc42bc6e512bbcc7b90fa3fb627d5e5a70a3048dad8ac721f5f3e11112cbdcee5c1afd6d1a31d0a81c91ac1d6a50eeefb966737e3ba4728275dd0a300573c37a22470f859ff5db3a31ba6fdf2e5522b143ed2283e5e8d29ae429c6b6b81b843118e67509bdb87739b1527c63491df3cd2916c69c5f7e09c7a7e6cca493eea08c77144706ab7e5f3e1aa8525d3cbcf2ee1111ceef895f16818b06901c588cb4f71680c411c0a6e7a811ee6e71b0f5096304e6b2469cb131c4ab174fbfdb82d0d9755612428bdb0f78f85fb7dc4565580a61ab107fd4cc0b9ac381a859a8dac21d182c00efc11d842834289f2c3f487b761447d55fc6732427612a66dcfc6af7321812de5277a3c681f7200016c0574a107f1c133dd68d1ce966ca0a5d99fe1ea1a77e61b0aecccfa1273a46036a1563639926abd89d323eac27de61ce714c9b840849937535767dbc79925dece58c574bf4244707e91aa2fb9f26c01bad21602bdc064682a30e3f5e5a48c978d2c0faccef90cb904fd1e7ff7b13b67adcd221e6fa6ba7aa63d05190598dcc8e0a1b60cf58ba335aeda1d983b6ebedf045d4e77d11c2270ebb158c2655ef7fce3cc45f39c0a83f3d0e4104d0c88b71cc45dc235aabfcc9cfa71c115a9161fa08a8f946c2d205c023740cf063e0041d98a91684f080c5fdb00cf4defd83cf92a813f93ea01e1e91e33d8fc60769c8274b254dca39fbc8c2f2aaea89d0225eecad34d1886bde1d9066959e853474a71ac6fbb44714026c3b5af802c9b977aeb83338d5479c9d7ec01c86bd357d04c80e4eeed0564ede4b2444a99d9121b8a823934c270906b48ba2d1b35e0057ec2608338c70db90b3c77e5548a002d88fca8b68d07136a647de9d2b94a0dd8f2323c60ff2185237ba07efd8d49b4bd809bcd0b22a864ab8a279cef7cbdb22e0ecacd8cddeb6fc8fe24d94f2788d91eae144874a53abca9e1c019351654663f5ecf40e1b256d13a3012fff19af4c119a5d0a3d256c87e05dff323f3d89af38b8b6975abc69d3a5cfd70a78bda5488d353a97bb20805262c3884eec00c098c9472a9c17661c273e05d409c1d7dd569a25d0f8e85859751609e6805a8079169517dbb8cf82671034c8ccdaf73f8107d191a524d23bf0853acc1ebc400305425371bddbc230e35675770b0c5c27e0d59e294db4032b0fac21c83bb971c4a57242b40c14d088f9b0b3f6fd422a951da18b66a38752273df0419a74579e1d18a82bb8d21628ae1dfc1744deef83163062281099fba4840177acb4f68e4bc97c249f4a98c1e778eb63309528785e6ab1bbf9f48f1f155b0fa44a76e764e2b678c4e3282e9d386ee6d5e844981fc0d068ce827e092c66d7016f28260cae6966b6f343a2721489a3113fc9cb5a7b18044a2b3c883c4a076c01cdc6853036d4eec04ad9737bd327a350df1fbda99a86da8c321057903c15f54c82eb040daf29d310666013fc8c473b3242f8643d1fea179e1a3539d4bbbeec7579da5f2d0b4a0057639d682b1cfb7fca089c02b9ff52a68242802b71292926a7c2206572a1903acb4f705704de9f6be532ceac4a4b30014691d0a94c12096d7d4659a86061fb91228c073ea4b0ac2364351573000dd3ae49964dbbd915341d3c548430916af268b05883b15a0cc851bb7e755a0325a0b12fbe7b2c725484e50ee3087ff80c2ab5730b91b5e5e2bea2d2823a40a68745e75d0810cc1a5e9bb5b35c1c13af83ed9a1dd43984659e4acb80bac93f55b2efd6721635a0b3713048288f2a61c5082af585a61c621046c37133df6877d84e79f9d1fac2490de14c798440701c6d41a00ea2996e1288467cf08a7825e88ae82358fe05293ae71f91ab4ec498c3a157c01910a31cc017e27af57f926f1b4a11273342924f7a0d08a48b707560adb1ce9ae81fe0cbca97900b771bb5619a88b133157fb585ebcf264f26e0246a29126c18cb7692456abdfb48c1a102aa54ebfd5ba3ad107faac1bbe07c8ac8c4fd217e29bf142ae7d386cb0ab4e560a9db9bbc25effbcbfb8f35df51ab5e76fb81790176a6b84331800015895b09e8d8caaf5a2fcdfc518a2bf8a31ab50ca30fd6a28ab11f96ffd8a1be471c34613e29ec673cbe802e17373373d5b069f4d8a8531066ce2687f0c32e0056c1c5fdebdf3d163b32da327c74d45f97d1cb31e73596d0f18a3a0622914df5d2716788b3831cdc3bc4459bb9943150ef090bfdd1742ac89b472388cf746ce7c6e6115e31964d4d22849c1e50edc4c42fe91cc4d7f2ef3f0f66898183514e2b07b72b73ef9a63313466878209f0b51901ed12442924d60512bd10562b500965b18e8487c73564df6a393e1ee64099c13b880d3004e31cd1d3bf0043a9d2bc52f6875d7bb22efb00e01bd10256817ee8c7d02935cdfb4925868c2e556ff0a0252c6f80b41e3ae221a845b2e0d07188a6b1f22f533a02f2f13bf2fb06b317577764c40f057808b3138e21f7b430cb79f800f16f899372296268643d6c8aa884beef196ac118b2355181c687341856628a9fbda1222b327c99420912bf62b838599195d1763c1f7be40aec21581be1a12b2d7e267811a8dc4e0cc3242973a15308c24732306a05da8320926255fec276050ae29782f8eb2aa510f1d55d30682edef929e6ccb5d0342f4411187407021b5b11b61a4922ef93e94ad1abda2fe548c4a81b79c3ff4dca1db0984345ee5a064d070d09c51b7a89a423e97e198c75d99d0363112df24cc35c051bd577e3e6f1c4dd0b8d66a5ced9ab0648c8dc6ee700d445c3c3e5f0368168ffcb0a13e21202bfd678a94891c7be25fbc7f8b9645fe645c8e2de4060a0e496f2e1627673064fcef19ce19e313c1fb3384740fe77dd6155804e46fac78b905d906ea28b7771ddfe2cfaba204eb48d65c1c33e2c93830940ae44e5abf50e0c50e67c7b6500dc981f7831cc7d421a9cad4119787c1bee501df5b3e419006a21ab5cfaec44c6b28bf082ac31865784d732ad407974a6b9cb3fc788702400bb233c8250dd636e4cfa559e73dfdff531f7d151aba75b13c93d007702759d4a0d5c7053d3c1f84a46dfeab5f1782e766fcf738eaeb2bce8edee1a08431aa0a2c5743ed412a0d68208398d503d0104080c86bc3cb4901448a1224f9d4a57e49cc5b06a2357ca97f13fc3067a72dbb559af7f347983854a28597e0640619c0faefe4e306aca40c6c836b92aa3935154a7d2387994c9eb371374eaa0f64c651ca76eed4d2683286ec2a6c648e9e4b1f69aa91fac2c083824a1381c583f369b8069c1df44e799f0ea0af8fafadcc2c0804db33a7fcb5e324d55f4271a63d70ba7ad9ddbc59e440316f1ea85ec74e62d1d5a9200d965145f1480d93791ab88bdc71cffb508d587e6636dbd7c447f5416f087196abf41e65823eb988cb9341a9c84549ce4856d4d35545fb1936c8003f2a637751f408daef9dca9bc63121197e57c1133520e770a5c19795d9019c9668a1546f7a318b4ce74a0c49e0f6363ea597e60c77db1c54e20c758c58ca1e30c27a22e4304be0207c5d18d246253521d122c008222248bdae69217f5bed647a6b6df7b43ed3740c8f2cb7d8239e0b0f2fd82253956b92ef29f123b034040c8c841b013f1cd23f3bb0b2e595d5862b560d52fc24342b656393e7fd5ec1220d0b97ace46011d61c31b2cbdc87626858552362dead4bb43feef46d346d8133c72f9f09786356bef5354ae1d1720be9e0e382cccb9215f514d52783d63749d5bcaf56d9287cd287230339627380ea20a0850b5cdc3c4cb413dbfb493f34c827670404e281f5d34b80e1cc129f2e0c99c6f3bcd475a38c10e261d22460f6c68e59c50c328ebc3e069d1bc1860c69d3bc3f7682eae08874ce7f8a7022e130158eb8488c5af85590e35a53dd5fdf54d716e3e7713a331151b25386de025b0eba5362f4b8297d66ad5e6f152e9808c7e921145ec445cc0712379bc324936161c6f5cc4cd0c353c12683aa6a424a502f3d78c42cad6a5f4b7db57d0c364e32b3e8d8cad4d01c6472a33f002257cfd7585af45a41084f932c2dfe8acb5b8822b79861729ee055ba89b96fb025d7a6de8bd9e6297dd89fd206e4fcc209c29ea187d8e496333d2789a8a6dc5e20ad045363805188fd2d43aaaf9aa99d066429916e81e1227977e065a6ae7f3b85ca5069a59dba92d96ab00fd17203a110f5b9892251fa731b8a8494c2818734315e364e8366f66f69170672ed93283a445207fc617244e11ce338ebd22d5d7bbc5b4138e2b9075055f91b5a28741a71c22d857e07865504197f63c5c58d9b4556aa6b742fd0109fef09250c4c4cbd037a2cf9a96dd8f731ac821c863a5130bf5e0f86f498f5d22554bf1552200f753ec72111150b412f80e2f55aacb764a9234525c2410545c445efff46ce2f2fba6d58c3fec874e694563b8350820d4af9fbbe6ba6d14c983b3b6716b8f26cf73457fc576ed54206e8eba3ba6b25b9cecf347d9a06edd2a3dae8ce97035fc246050206568ec815dede61d503cff6e9b7753604cc70bcf80404082eaed037f629d3fbbec371e7a5a077ca0d9b50c9d38ba11bb4ae6f28032af931d3e183e4421c39c91acf01a99d6236755b914b8655d29fb86c0e543569ffa995eefe52d2342bd951147cb1a4bcb4cd4d69d9d080aa7bd07b57e15a0e0ffd8f96dddb49381bd0ed88defc964af041207569e67777a449d6934dbbd359bbc45a4b8d218a8e9e6729fcb26169f74204145ab9e2e67f9ac7569aa9e70471d0c367925e9932c564828a2ea5c91f7aab2415285edd2d224671d34e54961a5b1200a25754824a3fb596b699cf108b538040bf2f00d43e8e158f36ce6a77079cf78978d94735332300a5c663e441ca07c7415f7582d52cc565fe08186c0939a0a53e596b62a424cc454683919280a218f8e21d8fca2e0a0b609bb035413167006be16a1cb9eae2f76893444efe8fa26e0bde54e54cf682a6f91a0d2faf7c4f31d9567972467452d2524982c6706b242d35989285902e7503c14fabe5aa442fef51a200eb495490e6a397dbfc4d594ab7fae388774a3a775a3da383e650521fb5936b40b0beb12c1b87bc088328785b80981f3510da3114697643df3d280fb6608b84ef69a7a5904d489334ef150a74ceae6ee6d704ee2e9baa7f4281fcb16f4bf315774f49ebca369678186912d871e9cd21f8dcf28eed11284eec6be42140e551dd3ec59e9344758468dde9e9ad00caef1f03e4827aec27e46586a51de2d26fc2e17c470ea67463130669f0c6d34224acc32458e2747edb44ed167f33fc2984ae8d0e75019673cce3130e6d4f549055ae941a0c6c464519221f73c728bf376bf0ee4e680da75e910f0f618b0a3c599f425f67acf2c31f6fdb95b38206ff8eab309ef755f3c2c31965131f290fa771851352125e7a795c08159c6dce4894d046aa1b6a2af7c19d8e8431c5ebf29997fcc22b7841d2039e5ea8c01195103574b2800ea16fd15388780eb1bfb2bb052062be6adf61884f043f871f9179681b40f2edfb9ae04d4f09fc1df56daf2a5c651f6c1655666822177b1f6c27b3a2bc5d5bb46476eac65e2be24e6095e162c138b6d46112c54c97c18cddd5069b836aadd18c1112eafc6ba8a044e07fec0b57f67cdcdd9cfa478890ddf61ac7b04c5abb47bb8525cdac4776185a100e74d6e95b92c1e8872adb07a329c9a7442f5ce4895dd1cfede15d6898a54b063d8d87274fe88b3149274b56f75f5f6bf5a6ca9999611bfb2e114742f96c52600b11a4a4491b09f046f5dd3a3e546f39d177f561aa35d884e49af9c0e2edf900849213edba22e252d5767ce0404f8bcc4f6c8d974214efd982463a94e769bc95ca00bdc88a5f62f5257e62f2f5f9f5d2faf8af85073b11a176cadae0c413e57cfdd23803405502679c8ce929c3fa88cdc3e0db896d470480b1cb1b4b47be284049fb79e04ed2d7396e927fd383716a55d6a9a1443fad25ae320a87ae51c06ff44a683636ef354d841ce451b79e8b5e896684c22ac6e54bbe6f73a01c8613cc80d52437bace8c0ff8b7576550ad2043223bf59fa7a000983d56c3a9eaa428b73e498832606a1cb8f3f7fc3825548527004fa224cd934c06a8d82ab3809418f8adaa2892395419acd0338386b6d1f99754625ce03630924dc74575cb37ea3b4151951fa9b6c1d3ae4bf689489fed48e86078b3827d37421ae1d146c4e9569bb0274419b72661c39b43376b9814ca79f2fa7b28ed9e04a3d498529413af3b51373c16d0ce9450d6b74137e12043045b530713d5106fabc45b8767b30c11ec4c51aa224aa6baaebd9950afe32c82ea597765fc020b567f681c7247275e748ef440dcc1c3463105d35b2ff94c83e1a73d2ce7d1fa0246bd20bbe58a97835d2830278bb9406767c80d8db5d849a0fa73d95e06636009b583e7a712e7627a64cfe5866efe2176890413af4637fa870f6340b501b0393a8c5e4a71c4e71f22a594f6eff9c74f26393e865cde5313fcb432bc99f000f1c1bc69b32aaa99c3fe0f1113f83ecd5a65915bc41db9799eb6b1b03d0fad3c6be77919ab232e9b6947d02ca6e3350f57b46f4d0068c0e319de44b7823b5cafc24385408f55a95057109fe411d2d77421af97c490198168677da6e59d8c469ec699bf93040b851a50d8f57923a2b36b25c5024b84f7225fd6a9c4c76f389dca07c64fc1987d8ced0694e106d58ebe39a001e471f1552b831b5f050da9dc19fb44171265e8f0e1696166579104b09bbd4ae41efde8ce8cf49f4f0a987f9120962c6123d25008387c6383b252b2d89435dc002017675f5efc51b486213c6608dc104da9ba66fb31cc044229fe9ac5b3db9efd0de219e511893b408d3798bb57215137641565d5ffd5095841b7a3ba6c4502fcea53910159284c2ff910612f2fdd2802d1441b242a0fab9d74a3fce6137ba649677933de48ab67d9ac2ccd62a31844db8db2016fe6753f2708e37237d8a9beb85c71e2d981327c33823a8d4207df0b0121a4c38badcc72f8f650ad8a3f1c964d61410a553c7fd5350e43063a438adfff5fc929b5e6e1d0215fb11cda94fd962a69645bfb0397f2dfd9c1a2267a56cf6c14234184b7365e5b880c9ee6fbd5f5e300502344afef7ff8add0203ca4c7e021f0901f5b79f2c99745496a16dffaf962d4bfe02aa3b4fbde7af311ada018384652168fea2579738ba3daec9a2a614fc1bc6bf633c5e4b5e9c4c29437885f927690cc55887aff87bfb58dfbe18f087a0d0789d4a454be9c37e733f6a39b1a14ddf0acc8c2d4c1db96b3a42511a01b3ab80dc2c50265493cbeab9534c8fd878902b7846b72cfb4434a63b67bc3ceba0ff42bb8c552572a560c04e9c8b76cbda952e23031faa0c39db5b158833d958451a9a6e320729f0b40c69dac259afac3ecfc852d05dc22c71425d543951a6f592337fa9c4d51b0306ecda6d23c7a6086fb5d96564dc0f717cac82b7679fda27bedb4d79c118f66cf64873e374023a55e29389186a0e1b8f64e5baa7f0894646129fe468ea9e0b39ca15be2343988e5a85344edd1fab58be7d8add79eb726a5d0544c836029d6374dbc85f93636cdd0fe783485e42d708c13344d208a9eb57a0a2bf368f9f1978efb3fd57501e5818debc2d3281d176a029379b0ef90b29adeeda8249654530ab16f489d43a6f8b04fca74d48d42d44752e2206c64e5185b3f8ef31667ab347b0f4908c8e963fe49d5cb246904ec6bdd574d016e0034701acc1c75c86c1c0435b9306473cf8a413794fb46840fa559adf12dcd7c3e09f86b7b17c912afeb705c395eddf457d125bc23d745674d3b7be5ba4ce3b9464b93095ca11bdc6a65e3e3470f2c834caf76ebb588c3fc654af43cfde26a0e0ff79626de3cd93f5f8f3ba5fc462c6a9928914ab0f2ba7ccd50d14d4e567e2e9b4e1e50f60c8151d11f962ec175609bab069b70f93b158618d416064138b4eae630bade7ca82bbbff0f54180b654ccde3528a950eb43bcd0cd30d5bc488fd5123ddb8b5dec0f395d73e67ea618a501f0a3d9a31d04332b92e9af355373c1913dd5a98013cf8f2637beff06462c9c5ccbec42e3237bdd7347374d9b3b16940be20ff8ca6119368087edb207bdcd6b00cd245d9447f9e92e19548e9fcf72ea2a8a6e1ecfce44af6864a9758b5eccae389e33e742f235ca10007ac79942f4cfd1e200320af1c6c6938516268d3823bb20f53b8a0282c6ef2d9a5dc3f0c9926685c3ec93d6a488a6de61b2b57271f7b05a997c20a87b080fdd8c8980c4e600c37845a4db55de43b023e646074816fb68e431660bdb72db70c661cee0888a9db65fe98b8a1d574b7950b5e9e51b24146007a2afb9a1a7c8352a10edd1fed170c93753848c0f70e708c21b552fbd9255ce646b2ea69e1fed9110a015633d987ca8fb6b99fec50402a5dc40d8a349a4800272867b9ae501fe9a94fcec2e7e86a209210d9bc2aab338720f9e28612ba1ecddd5cefb91c7dd5902f24937365028a879e11c17a227b2b38cbc6d2f79794ad52eab5d1a98ef13aaa3e22eab5db0c753e1ed856349e3a07ce6389722be9fb40077ecae38bc3b619617e61450d276646e5879c70c1d9ee90206960e470fe5da9fe925e8adb5672f3a7526665807f031d960c749d6cbc73a0ee040252e87ffaf36703e1bd29e1d540a824631f2418e7ffc8f7dd35c9327d0fe1373d08755ba1f290771fb65ea28bfa1d1ce24551743ea2f5f9e4f2aae934d870afae0dd1273bef0c2d6e85de10d9b59b601d7e3d0b1933de33ae204da73b335c366dc9b5188eeb059f7a5c6eb2228d41903c0859b0ead3d901868274fa4068e3c813d00033741ea73c915dc548822d584e368c0afb9c8fff7d4650ae2c2051dc60ee3eacb4e37afa2d870529cadb159d725835f05dcd2e56e16c2a14855a8ab285d8cc94af527e35605952cb29ed045ce53ce96d3012d2b87cb5dc9c5940688b2ca999417e43f13d350ccf33f13a8c24599a177032b13fb0a9828bef842959b645bb4be9843ce7bacfd24360f686807e329afd84902dcc15c93da620e0216276e80ad7e32891c526510d878fb53da51ef66f25e457e971c56158744e306d30ded17ba8cc9703f94bc2c663ca5456c99988df30cba8a9cee778d8051fa62589b34b0a5bfed80a7ac5e9f8b8067c0676581436a7046a87e95aa70532b8a9e64899a4a1d79d4598b914dad5df0276cbf5a0c0e6bf643dd2fb9ef53ea8e356417cbe71d05b1b81a51ae3ddb78995c1490f492ead7046b19c5fa0ce2c89175d9010c59bf2695a926bcf96ae79d347fbbb8319e9c2adb28752a6b9c1fcadad91d8d95b286b34326808aa6903dbd806fe6e7f1c1e899571b0bb4e22296cfd7412783b127b1c84d762e11ce0ff1d7a75752e198641454cba8d94192cda93586a6988c8ef74d97f6d083ff746376eaeb14ec5a1d6dda6ef4f261a08acc46032a2bf9b970e7aa8f6a6c4d7857d1fd2290477ee669400ed6f746a3e5572b849367d029632653ab6106db9fd2e639531b9e1098218a4f53473e2bb3d165b365b04f09cfcff0ca8b42ad7dbc66e93900b90020d984e76b5260a7658d0826120c8087850a135f7fe5fd8a30b2ae7b8c3d9fbcff4558b267097dc8eba576a2270ce990030638b75b7d462729830b5b57fa46638c6c00d6dcab8381c768a80131ec9b059b39beb66a77973a406c9339b06c4f376df92b49ef6904e741bbd04417a93b388909ab029f1c5db8a19525995b328138b99748ea71456e7a413241b33241d030ac8dd5d5c4014163707b1cd0e9bc5f1b25d7fbb7c3b7ccc80b57226f47da9cd3aa090ce9aed9b97c99f4b96b177130434f5f044b6c3c5a68bca0835bcf0b8df831fbb12c7e6b41f4dc96f61b6636cd2d67b4a5f804ff482464b9b244922654a29bf05cb05c2056384cf83cb4f3f94103246cb4b5d903f84b0582e44878a344992244992d5e52095e2a2bc4ea7d3898ad39f2845013eddcb896047740d85c169fcd3f79c565229a8405788ce7890f1adf0991b0aa3846bf6fa243ae6f35be130df666a2714bf82f37a2b5e892b89a6b1bfe4a7782856f0a753105bc4901fac7be61d03d608a5ba1d90130cc33014eade7befbda11008a6bcde542873280cc3300cebb1d1aaa27d62fed80fe5f90976a821b06359bcf7db7e58adddba17bb376443947da7c5d5c2d9b4f628ccab04d134bbd429ddb44dbb30f6bc1cba3f346ddab4bb5bebeead9b366ddaddadddebcffa4157b0caa7e3b615acf2e938ae082c8a6cbeb24c4921c6b53056f988e83be8de2c5f9cdb40a390f320366299ea609867bfee7eddb59b356a35ab59cd6a5ab679b125d42efd9ae6477d352d13d9bc5cd1ddb4e9fdffd13b2a84ebbaaed369752ff5bc9c413014baaeebba7c34cdd7ce78c576180dc57ea2e9780ff1835df5a0c0758566d14f7ea233a09675cd3753500582601af4e4613860f04070030e5f5ae6b305b5744c0aafe0eb743a9dacfd0f183a6763a6f9add6b71690795afe7c26d3667295b7bee5262c302569134d9224fdd60a0fa6be050c3de33770d3fb81ca0b5306372440cbf400402652355e04920973349fb80933796e3299a6699ae6bd366e4c9527582854ea89142567cf2a8a8a8b8a429521efba2e123c9dd05ff6b9f8d59b0f9a977edbe72821b0d85c847f9120830d5b506e830a19a03df4aa4fa1de7c93d237e99b660875a25028140a55832bad8ef58c45e061be011df33561cbd0ee017bae39d3ec206ce489f59145318e23a9ec1142ea9c929488d47154a94a6e855dacb550f073b57a900b374de747b221af72d2b1dddc60bfe1bad15123d040086b0e1e3a22d034ae23819e711e1ae673d7f9a85a6185518676e27ed241142fc2ebb9ab1fe4018badd27185d7573f198ce3388ee3385e570d1528a59229c584086aa754723a7215e148bc896badf59215c287b8f9f1136ec4061a7a16fb8b8ed263033e680002dbaf827e72afdb0e158197c475a7c6832ac0a107ff93826b16c130003d2a5da552a9542a595b23f4b182529369753b462514ef755d2c2ca15333b78897e4f2b067b25c696a0b1fa92762a75eb2104ff53cb8036aea181e5c3f2f91c254c634454d2693a99e40d44a92e5ebb85bb783cb5ade9ecea853601d554ca1d28131de386d0a3c23145b955facc0627ec9c2c6831d8e500a8ba1971a60f1caf0b2048b311c80d6af069a3d50433b096faeb986fb7da09d84f55bc15361d1eb3e22f067838733c24c099c4307f4491e29a295003f797af0d64b02f2b390b72b14c81b9ddc2852a6e83c89e61ee7799224499224795d35700f1126241126b2bbb3ceb22ccbb24c0a07ba6bc2e17b9b841d8e8cd20f94153e9ee79ea184a29b5ab2acd600411908d10297f375bde0fe8944958a2442d14d36b250c846e0ea50c500080eb836290cdb68e0b1ca5bfe1da41b6e735296d910bd6063ae80715374a4d48ea4b247888e3dd924b66489849fa91d59cdda56f762ec7939836028f494d2cb0bd15014fdf0421e0b13d833cbcb711f98a8cc510e904d896ad4ef420e908da734e2c9329090115594463c58f644e3687f9891bd241adb22ec108b72c933f2f7a8568764c707ed4841375c21e602cae1d9b1b292735cf2ca97b3925b720e4b9651bd3acda1147ace096b761790e6dcf6e9724e0ecf0e973ce3cb99915d3e1a38f728c725d3c026954fcce1f4c901d57da20dc78386f64da0472ea01ecd9872b30cd4061ad6cf0606b8645f0be8078519594dd33444f40c2dcb233a0b7a648950a435605d526849672e130df3f9f244d1636b60c78eb9453aa67e57eb6299acd0eebc35d99114beafd81213987e383f6c18586cf3e6871d473a8ee368473b86f5a654bec5c465cce639cebe8481c57e02e4ad00e28811977dd019f046017bc6403cb8669b5da39a96fd014a53f46819759a8d701f298fb68f86e71e61370f9af77bd22cc0f75e1f3548911162f5125493f07307de65027f7ed087b756268c18b65ae5ce977b2f4bafe15c86e3388ee39274ccbb736e39950e24e2bcc6008d002e947580451f3fa1180a49ce43d770dce751b4cce71b8ea4c1ef3e97a26368a87d5e96b4c440f9ef5d89810b748d972e43c3e5cf16f09a2d7fa6a169b416ab9de01a2e53eab8da0758995d1e1db76929cfeeffae95e3f8abf1d3e273d9e6bb5ce3931d3bfed4f87c3a6ed340b563ee83dc83e30a40d45a315b310b002af08cfdec9b409b66c8b699e301d05371aeab063686b5f6b2172e71e05e620232c4b2e7a74df3ee8e5d1d0e232b39fe23f41bc21a7a156ab889562d485ce9f02af40097452c671983720b9feeea64c0fc13b90e33500ee1e59f58f3f6ddf061be40349c7b98cf5912d8e66be19b45d1a783a11fccb24face177307c7819592982455178896c3c0c58233472411882e3382ee7cf4745c64ade01c8387ac99d5d724bd6998157be729917508f3619a097fc0179cba7e3e35df8cc92678058be1ef28887cbbef21a11b4cc679719f9472f2b36bfad805eb20c900a885f5436caadf4c8c572d957befa924701957000298c4acee7c301c85683114f96731875c7ccf8d1c67d7806b843140f057a84734b969157f2966f68596d2e777b6e9101bab7e5e215efa2c16081d24858ad3c2e832151ce9d8d037444e066078fea590574298c73ff29dd2772b350bc95c9dff01f7a8696a4a6e9c65155852af543dc756ef62cd2abddb19a200ca267e8fbd03768e5618817f40dc2a6f1ec45150d856ed131d46c9a6adaf19a9ac4445b49764c74a6a292784d6de2325d9324e19fa087a84dde888ac52f51533fd22fefada82615559b5cd7a97bc61d46afd174681ace344410f4d4343f6ed1338fe4838ca7919ec60424000441a01452f8f154d69e9aaaa8d1666c4ad3383d62ecf3c7d17fc450972601a24b744c915a83f0082a3c8d271dc6204174580149891105a110405c25741883a800054200319ec6244e2f40d2848d0ba66a661cb78e11bd70aaa083b516afb0788deb0842a967169ec01f09bc0061d82a40c2046038c2e03ab88ceb8c54e772d2785d1876eb652d86a9986853ead4bbbbbb3f4702ee8cfbc3cf0a8f7cd499d6ca82ec89946cb56a619c1287cef4c595d65aff71b390c2a2af2ed18d274ca9b121464bd42d9e26e76a85d918f4f0d0d3c3c3e8299c9e6ef5e0ac56ab9567c3f3562b4ddbc14385094f7cd05a6b075e7c20c1627e5a6b39841449962b2749cc23aa26282524e9799ee7d119aab3e309c93478244a0025144956b4472b27d414249df1d6553eab758d17293c57d4ac2a3ad3de2354499e4ca5195a928eab91349246d2485a95ab7255ae4ad34fd2248f409e48484574828c438a189d8c41c62145ac56abd5494de7399a3facc6d509c6d8349d3a72498ebc7704d8cb28f8ca66a8cea7d0353448cbd0d01d466ff452892c91259205631ae487d2905211a5911c499324c91a42a10c8436e083070d3d42a150284492a927caeb254ae45484f49ef968d7f38f86f9ac7d62163e97826635ab594dd334da1d734ba552e95dc1cab2fbddeef77341462095600123e89a175c4648d35c3d3c55ae3b991052a74898f530cc285f28c153e04678145fc251a46b4631cd9b8186f92c468f934422e517722691346d070f179c4a77777777f7aede1ca5949abc887690144c24f426ed0799fbf943b52ccbb22caf4b8b5ad6b2a2ce28b5274935ab939aaa4c52a9542a954aa572ee546aa351c4094a52a32fe2c4893412c1100b5f60b1969563a10a7cb5b004ae496a595b50026399c6f5ff6421dcd6a2ad90b5907a98cf57fb52a48ff42454288a42b196f553dfe2add042287eaafc944d7d2af5292f371312944ca3ebab9ed37c7d34bad68f8528b85d86e690f3c341f4aba74e0b267c7d622d3b500e38a168f30d34cc58ca06a7b0c32e7767013c8ee3986b78d5fa7d8afb5ec9c37cd674c01555cb0759080277e832b496652dcbb2ac652d65144192e5ca4912f388aa094a094692368796e18ed13e5c75d0198c1f80379017587452c454872ac791cb95640c2d43438004e1cffca030ef425ce603ed76bb9f9bf513310c046088816878ae76c7e7a6af78d4ef668a1b5409b502149193c934dda04c138599a6699aa679af8d1b53860e7ad5cda3c70668e8a6d775b917b829752170a61fcd1887563ce86ab55aad78d47aba529038d01ff61867173dd0a104477eb0d65a104c65daf54e5afeb30603b30985301b1d5c8a7a201f6d1c8805284001c228078866c5c84d9e032a48ce491afe7d3e2f290c0cf7f9690c8c41e1ccf12948be388d72806c4c588dfa5d28401023ee5df80ce47918c268bb3ee0365d7cb5b68f9447da47c39a2c86ad5adb9349a8ce94a73bdb7d9bdfb66ddbb6bcdda6792c14efed981c83f79d7b3d3f315a5a71ba7165813fbb3cc1a2ef34cd969f07fc7da287bfe5a7b67d94480fa639e557f48d2cfa86155e6891c2c165ada67d626bd9d5b0ec6adfaf5ce673876a012d4183bfa24318117eeba4a413cfb2e8e797bfdabe5c6ddfafb45c7e6bcbb9d5e556abdad0228b89f9b39853df2a0974bed9b42ca3f289f77e3e11e3eee33e31b75a1c07d6e898908d9b8ed9c1e3069cfaf9171f4c96144b88c565064b904abd7492f4141c71c4e98b68bef32e84e5092c9fd862884159348bfd84e513fb0cc72c7379cb2fb0e4cf083080ca27e3dcd2322d9fd8397896ccf26d94a394858585858525b3b8cc7099f1c98d9f252443c60aa8c683a18eb121dac183860771721e7427722ae2a907959832be33a2d66aabbd31cd9badffa7c09e6f9ae69fc98366c7d4d4fc0c5a86e6b57833090ad5738305169dc6cdd987606b6cd3b80adc0c30962fe87234b016b4125e1f17e13379f0632c0ec2cfbf45cba85208038b7ebeea513553b106d488f5390163d96607c9086db6f92b963021e2a6693eabd7da0e862bd3c00f7e06b984b7090f839d5074d62545665d4e9e60dfd9d1988096b33c03368388c27c773842e7953dcfbccec3bc83713ae974d2e9a4d349a7934e279d7daef499fdc8e4fb890f8f74b9bb63185823e482c4464130c46303363616134ada41d0dddd6ff0d07bf0114f8e0ffdeb4f131004c15a59250a957a2245c9d9b38aa2e2426b8cbff4b07bf66299a6699af61d0e2c7f56bbcc65b15bee99cacd8cf33a508f34500db31e7da77d8ecaa080b32f14ca26ca35bfd84d09bb37a29f348d4816ba41a15028146adb68183b1b60ac29d87996251d3a9aa63b555452809df4b913bf441be91bb507e572a6046dd206ba59f4f30ec1dd24539c43601d55879f5ace3e1a375aa641811e693466c83ce20c00b0e234d241013aa91110da8339caf2075a4223d9870348619ef6608eb6ac65d1f3c126fac66e6c422ba1c15f3dc10b25241fc10ea7ea4b4c4cd5a952a2a4c7799ed8799ee779de7b73aed06064b15aa723534c52be54e4c717c2627dba4fd77517cbaac0a2b35827d64a7edc7d3eb9eb3e9f87f38ae7173d29fd0cf9e17c92c56ab1582d91889e8e3a4f2ae564b59a8613f2319a46628d45cf9375b2ce2d735974161774c5e4c1a74d5c8687929e22ab7f1485d1314d2a53ca4404f53bf74fceea4c2c16cbc462b1582cd655a38edbaf149165fe2b58e5d371db0a56f9749c870516a909cb70b295560aadeeee6b93650765d9b5f9db319fafc96432994c9a76a2678f55e7b8accd19e833c34d7df4394ef843d163a1891e9f50f44e9c448e1a7ae17b87492c7a5ae81e9d494015de0cbc2d3cd149abf644777787e82a1402c19cdbfd5d6afb1158f4540a0473f6ae86e323cd5d9645279908d08d1e0b23984b8c48b14003dc581c0109967b7a1eb4f91b7d39b67d46ba8fcb9a566d1c908d38fbbcc44ce025f0767531d40bac7181dfc5d00fd21c4225a1b761437ed5cf895d951281205523b50245adb56bfb05fab073dc0812518c2320da1045463c9e37202ea8afcb36713a91669323aa22292eca9e7b3a7912272fc36ac7dd8ed3a8f0b8ae0b39bf3714047a825fd0a61c271dfcf9860ff866b7375eb361b96e9f13d1beee98eb52b400bb0ccb1758ee220446084ae8c6bca75396ed18adb5d65a8b596b2db6c1808a2a701676a1e88516eb70703178775805d8731294babf2021438c9aa6691a160aa5f22766cffb7080bb58a5a0ca543e1df7659fdb5d16168c3d2f6710bcda6adb6a846c885eb0710087459bedde2d9ceeacd2dddd38c0526ec011515102a8e85bd7759f8822c5eade7bfb2f765d1fe63baff3c511a81110059cb45062b4b9d0e2240fd2329f7750e94174538ecbbc7773c4eedbb13bf7ee6022d1cd329ed389349b1c5115497151f69c4e2b1b9b3055390cc36c9aa63ba92801b2d1d3eb7ef423d2ab4c0394427802cbf7c222a27f813c9d5ab4568e0a6b4dd3344dd334e94c2804821e78c19013312851a31c200ad0190269c4e3194808053838473c5a1645d67e8f553162cff1bd9c7212e6eeee5cb6a00fb492d0039eb9bc7508702f412215f61224ca5026ec25488ce193e4c665ff9c84611223b9ce45b6c8d8ba2772022c3ee9650042dba3cb2d1fa8613cbb69e24fc5cd2fecbedc315e2a7925f16710be8b5baed7855d6e5a1b1bebeeeeee9fb5d7755dd7957178c92e19072fcfc88fe401a4977170c92f198719d9cba4fd8e86e7195eb24b9ec1cb335e5cf2f5edf0f20cda61cfe4cd57758c0a26f6e5931be80554432b096dcf0f7cb8c16867809e04b9cc7814280c7bc44bf2c12dba00038a49924f3ec992f1019062b3eeee16ffaabb6d60791865fd70d68f1387fe1d8e6a2ba537887af402ce0f51005a851911dd116a02d0ea23882602167fa2a1070fd66a87b53f4c2c168bc5fa11b2f1e1a1564abfd5067cd46bafa555cb5b7655bb691976dd5aedadf6baf7da22863ce6ee8e5dcc8235b831fcffde7b5d49ca2ccbb2b274f7a157d7d5ba4a4f728328fa0676c3d6ec701922f80f2e0dbab0fb6291893c51c64cc6fa86a57d03cb9e9b66a84a7778749e0daf2357b0539e1efef59fcb4d5c64714583249779d702085307a1cfb235e2943c4cd96a95996e19966559869ab68347c912c4da16ceea455d6932f03ea30116166d1b823ddb8cd3575f7d5191c5b0ac0a1004310631884bd6087a4813d137b24c81f01f7c6479211c08acc9215d7f307a66253fdda125238b0f454616df0832726733686615460d819587fc900a9272313e2b327056c99fccb29245af594dcea8edf0b18a505dcb5a11f075752c2cdb2643fb8cc8f872808cbabc8525217564393b060c7c84f0b38a11c2cf1f28210dd2cfa23b40216475cc93a592e82caf94eabe3f39f102d327e7833db0582c168b755d66ad6fadbd027fbc2458ec966a478808870b85df753c5b48bbce092c86b4f0e66f5c81b3f01ec1622806ec490cfebd3871d3c239b55aad560b27f582eeae5d5b2ab5f4a2f4ba905094124a92a5cd46391b6b6da8c03603010720ec254a9284b69b408f74ae6cb3d8ad1728e0ed72047001fe2e5aa1a396ece0e43d95dba8a0ff1f23def702fc9f93f02181c56ed510a769bc898ea9a23405141d128ca309383bea055224a2244992f43728176bca759ef8576e93fa5c8434112962e3505e08bd2e6e09ced12a702a7c65d246d3b4ce33d6c960f34b16d8412fa105d56e0277dd6bad09ca186a6e7930ec0e077e50088c57f0bc9bb13cc3fdb04c02338e89c88ba05bad6e75abd53af2048f154eeb3ccfd3da8eb91e93acadd553700267d75ed9fd687099bf9db59496e06cb55aad160e86854129a553f0f6ff5448ad6507b81a241403096fadfd2ec77605c6e1f770db82f3dcaec09e4bdcc39bf252447e9ee779f6b8ae2c0aa6b5d65a6badb5ed3838f7629c7316e1d0999c5bb9955bb9955bb975b3ad954195123aa71c209c12e48887cb40424490608d78b454a9d4342dd664537466465e59242bdb4412d65e3bd6644ba59bada6597b5df6ba2856b14aaf6bdbecc57da41691eb889ea12814132a1e1b2459b52c8acedc286090c242c5c406255bea9824d7674976b4452c59b2da05f9fdecc97e3ee29456ddaa944e415114652d2a8bac9106515e439f582788405116c5836dc1684b2b9bea9826e7ca96e8ca96ac2dd9922dd9922dd91286995e13ce28986952cfaf188f42dd7b31db11d1fd4cf4f32c014785f0ff47f03c8ce1192108660e1411423734b4dc619685e84cf9284eb4ccbce9cb0b9ffda8042e3b5ce9900000000001c31500001808088442a17048342295b7b23b14000d537442825e3e1b4a849124c9d114a30c328621028801408000c150892c2ddcd8ab999bd4543d9452a28be475855e6441efdf869a9c7eed04c8b24feead4d784bb5e98b0eb34645472f2c0aa6a5cb1e45c9525fbb94b0cb7f78143c9c394150da8d0fb07dd83214b014cbdbc732b77ac29132d9d931009fcd1a086ecfd58179be1a213dc83a3c45775a5bed482df17138484987626691f016730dcdd1047c6aaf9a6b54421a328c5e79562cbae5dd98150d7d18fea05a2d2282bd9da99e3b53515262afbb684310daadc2e33b5bc08160d9c1027a6e280a71d70e120f70c43c48f97d58de2761878cdd226f85980c9a9e93cd6f7ac287d8fa75c023f002e713c5b76ffd48b5ffd85062a200daaaf1e0711042b3a8126bc2c5640cf13778af074eaa29f81fd4e241a49165a50e9f539d751e5c906d684d935cf592db0d35020ed627c712ad834431c04ac64a13f7a72aebb6809974d250527c7c41f7f427597b5cab489f7c2631f34e88cf3ae4f42baf87af76ece5576c5bf0b49233e4484a6fcbc4d814942b2a9c99f313cd00d97a5482901a68aba5dcd569425b9da6c8512bda8a04f531448d64143806f621a0b7ea653ebb21fd1296bf63551cb58412662de6bc64232558eab703222b9166339d28217a538498d8c55f451fd8d1af76ed151e5d86b2ff932ffe56c4c59b4e0610aa12840804debdf5af4577aec948eb8fa239b7f47fc6f92f4f9f5753b41d7a0a1d653f6c4625c01e609eb15af69727ed58b9a14b2cf2b0ef42e4cfc1312b79cc6a86359867f18b96bee4cca441a7ba31888c4c3ca6784cd22a999a54e6700f6238f43715d7c03c7c9345e2eda3e80047325f9ecb2c8e083dc48427a206966e39142e75cc1cc5a3263331e9ca51639ae5996b82cc04e58f19ea44f3245cccf0ed4bf25b31ba3103ad980316cb264fe8bcc08934ceac860f64eefba4d5969aa5c18c1d7948e8424d9199def6ab0d9a1152cc7d11fc038e70364293e7f79e80d20d7d2f823710d1549f325459250e8157debd11b931e82c9662e4fd850176a1b6be3b6c7d3bdd29ac108fd47ef07dbf639aa9fb076792061d28b6dd82aa88d417f2c7bfd63498e3a1a9818731ccbb2b967def42f8bdac8fdd6c19703c113ab6ce9e2e2236205166a3a77f4f01d4239b635650920adc89154b0d635a23c631fe10436de96ce08af2ca3065bfa1789a704e79d6c8cde9cd1a1923d65fa01e373bf629356c1448bb36329877ad885d023c5b4dcac57a10659764699d3e2f7c2c3b128553b6913ac4f039d903de3a342227c0212916db8893c904313ff3b5a92336f6b21242afb37af5c72bfe908db124646186a3fb597694b6930f12dff4a139fbeebd09a57638f42aa3d5c33258f8565a29fa0915866561aa8e2da28c3761ad2884407cef8e13bbf490ecd17d5fb1be6bb9366b8fcf79418540bb930dae190e431c90dc9d81a97ff9f5328331a95eef4b243f6efc1e13c8af4d453ac47c1cad32f35a98afbdb4f5303d54434b73a45991f9ea0be154d7fe48a9634fc5f5104fba3e8209aa723ade8f9ca96ca9c2bb4515f1128312160e910cb9249ee7b93b105d91834d27c25afd9ec80ca7d056b381a4309e6d30d258f92cf6059eefefa98d76324424840a08b839113193246ca59d296b6819e6765a3480dd053fa0bfa54a876a85bb1bbe512c7c105c9499d51b7c7bffb46680060d06dfc18a9dd4b735a6366b41a07c84124ea9415ba8dbfc6fd31553f857b0423e67d215f3b52a113bf8348f4c407199978f5ceb1a625dfbbd65e570d5485b6eaf5a58597b2103a2b9092ac094289327835c09a18acdb282964aad6e6e65be42ab39e767c9349b9fc89f96a4599b857c6ccb6f70d3b8f260017f8b9bc50ffaaaf705bec76082e2d52cf42ce46832193772ed946c0d51f87d935ec9517a2a1dd450760926bd0cf24167426b79cab2e456a44583a3a89de85bada3cb5f48f567504b26db24e66c26e00e01529e90cb19c29375688e776d1f7397e4739dbe1ca563ad4db46de9be29db682dc5068e5d8b1f1c944094d5fbfc52d08e9886a38b2983efb848a4ba6dd9e4ab1a3c6b5bbb713cc4bb07a21f13f378c9546447b40b9d5c3764295d85368a96637e3cab8bdab6fbe0f8bec7eda54bc205bc2d75953d535cf8a21d3b812a47eb8c76f51bfea362c45f6c87640d79d2bdbe392530fd0d22233d802a058b8040a129f1b7f229457e0ed2aee6a385afdf7cb0fe457e1d320bb0a90068c6b9f39cddd8cb0c7740ce68d61e094cd3358ed99fd5293a661d13250147381caf90ac58f5bb87ef6cbb67aab2e7156537593eab8f1db81a2d33f5d87d3d1046075ac7858f336c9e4f54b03065585d57298ea3d6a206b5db8de10eaf2ea6159684182c1bd447c407aa4024149f67dc69612c511fe0413d63cafbe347419a88e88922a901567780a0174a07ed577dd760886414ad95eab06d2f1b01adc55728ba22793c45c155e69bd6a40fdec8068107cb9e9e97518ffaec2545a5c3074c384f3bf7e14c80a691c69f272ff146ad861432a21a8cda43a420e1f26bd1357e29e1bd50630cda39d6c6d58ae035c4ad6120430a39c3d2d08b882463e094073fab43fb2905a94cf4a50e6eca31c4b0493857b59174a84afff3f07af2e9371857c2c53c523a93d180eb59cfc3e1b56f35a5ae0e377840be332dcf11f26041cd29ca979a20e84c27b2ed612a9d4bc3f00f5c4fdd6e8fd801bfa3c0ebf2f7e28c4f406a5f472558a880c2568dc266fe86f51c9b9e22e62df55e1b0ed003f5ab3533641e6f0d7842bd1b0776a6cdd7d63b21185e4f96ef84ccff8611ee89aca01daff09e941101c89de345514507acf9635494e88e928b08ee93bd14951b00416b84d0eb61b10c1d2f73087724c45d8cdb374b85bb35c2dc3fecd85a230860b1093419bfd64dbf293284571adfb27bf951c70ac945e9a5f6b54e7917e025f16b1204595ea3fedae44f196ee001af608028511be53e9f613e9c844dda4741ef94333cdbef91f0f16ba810baa132ae82c70597166c28dcfe3f0d9f3bc01f78f62ea20a0d6358d00e15e32783a88277903fd06f672cfbd3acecf5d7f29bab88ea85b3498e12827d984e489f360583fbdaaecfa6a6714aa7d21c8f23334f281ba678af6733400fe9d11e5e25d0d1ad1497716a3587a749076624f81178015bf47cf0106e6818b828ba65a2a287ab12398b605e17f4e97576db31c709d88e8a5585b6a1bb512e628e73731fa8d754677cf6ccd9e9950874337f880ad364826df075a46ee444ec74cea06b678132e273041644d8966a8f6368390a121b8528966239f2ec58e798a9fa2b0a8b5e04d9488fce37bce285e27b803237a5c45d382ffafbb3a0be967a2b126917e219cb090a06de48a5adfd84f623b681742e860197dabc1f004109972f51651c495bb32c0c4a9b93eb99a331cfe8cfdcd4df2eb753ba9c34715886ea2f07814b2a7e633e0bf1fd49e6585e0e07e68dfd1051ac0d928acc8a21dfc91331f8472792227d6d9102c800f8f1fa35dacf9cf3a57122b5ccfb86489c049f2c6d27908adbdc1d3aaa36f1d4157d311ff0003f7c2baa78e4f4864161eef5ca101c97eee9caebae37532cbc6b034f320ee1780e3c626cab2df674bc978ddeb801cf5232133ff18bd52044ccd9ff0912177a1cb610924c1ba15a2d31a35b428223c89d55a2227e2dbcc2afe5c4f835d8e658dd92403a8730d87b9f851d70f8a6668efb1aa091b3ae5b9cb75b99173432730ce5e2e6ed66c972bd3085200b8bcfc257aa77cc964f483df88c311b89b2d19606fa6dc3b7953ecfdca90afcb81924870dfba42edeabda6b7898530583e060c6f27164cb67b094a2e3f60838c84afd8e95a5af628822719b919b1f0eca7a68046ae16bc17940aba218da2401ef4ae03d0ccd018e2808d15a6b5a0718b24e6d4014e303f3826ced880eff2f206ebfbaabfaf8588c3c2e623f2f51bf1cea41e7e4c7a13974921a5f42b346b01759346773c6e704e313999fc3e198dabd2bfc8935e5307991f1253156dc2a2c9c516f3b0d8e8871bac88740b3f477356c2160cdfa29863121b5d6dcb917415dd70bf50691224a16809f09b104b0ffaa0e0652c28484274c198be2d0e36a948f873f36c03133caf074bab48916619934d17319b20367ace9ced6e2505ceee5aacaba9241ac2fa4d5a228ce9a7192fa2b7b2de3581f858b11ee84746dc640b9d31403ccbc34194c1d59d2360cd937da21bf0e4267503cdb2e8872c9554f209116b6c60c7d0b15f0e2c2f854088734cf646a874709f241d82ca9c0e6c1ec9d90be5d7cfba4761d64520719a94ead7fed8f17e085c0dd17115862d334067be0b9b8e4242daf953f16b8c9cf1108a940b2788d7138ce0e281b20a4353b231744df56a1cb8c97af32eece387a3340096528af1d160b6bc4d2a1354adc74e520ccf3cac1841c0662c122b12ae26ace04351ca9657dffd66dbc59bc0434e5e4ea63b1a0a49fc7bc3034b8c780fe46a50c7f8c90dd1794df8c3233b61750f470c31005a899268ac887146ad8c11e16df532592c70cab5459eb768ac26a80808ad4d0e3711c2acfdb21a80982b5ee83354b2846450aa51cf16c798327094c5ffa98a28266571992ee3929c06432fe51fa14ef291452e77c8c32e18de83a195d1cedcb151281454849890fb730ab7642ea139bcd3c9a329a11fc002aef36f923ecb0c664112d8a557dfda6eb513d98fb5b6e2254d1364a788197055c422ca8fc1fc917d038c170dd88d7a7bd2d9a1909e1dff0695f854635fc37cf9f03a066a6f8674b48ec80253b1b33eee4cdbc32e8cf5c7663b06942f0103495ef4181f79c00b83240b5d7fb635d31a19db5ad1533d78ad5eb7f5031e779ffbfd13b46aa49088b8004b6d7ae3a15b8644931ba55e535998fc83cc252b1d1ae62eb713e6f94c492a2d9fd6103a2b9d77e78fa7588ca6c6b4c91b26a3ec974954fcdbd4d362ec644b2bd68a7bd4ac95343f73d898f26ee3c1c358fa5bc574b24ba47094304be79cb4da72e345b8f33678a04129f6652e3971ff22047e77706ed21b38e44113a1f8a347582dbc64bc4dcdab25067118a38c438ed952a323a9f83a3c375d5c5d75544f0432c7d416fa6d0919403ca69756272566d0fe9052e013dbc2b5287add033ac768518eeeadcb7faf3ec4b9abeb7538e2b836f5530a2fe436a1314ba486c41bbebebc2fe6ac95e0f6d7e9ad1065c1793100f255eafbc923186230c96ba9f2afe0f8555935a54a542bc54c457755002bd6c969ae0006003a0f2b73782b98681d45941b3c5d1f512126f253e1a1a03f249e0a9b59d8c169ba8ce5caf66cb16c74fec5e74c46097c3baf86302acaf298b6434926e9d337b30e5247364e981e2250fc4d6f2a4e5af3a59ebc51bd6382e86f35831ad7ca7ba4b6a95c1cfe746325ab5569f2561fe53156622ebf91d08a3c377fb6425bd6b710613937c0436c4ec796cc1c23c032cb9fd4d8ceaf6122abc18169e34e098331872e81a7caddb273a6af965b56efa1bed756313a8f8ee8647f24909599c4a2196542a0f96685b840e6daf1848b4b1fd9393409d76229d402340a325f6b1ce00297cc4680b79fbdad7c8bbd341cae7c95b92c1024ec2eb6f4976f669099f0a4177828589554a1e6dc8d9f47b9164aff79661a1e11fdd1e310d2933b224bb08ff5a86250b04c7f6653a0ab6ac9b1e93b9fea3ed7b3ac9c76112fc240ca3b78319fc140efdf24770d3f4306d6a33b1a49bba76db61c478be52fa4cf948fc51a6a587b9a27288a5ce44d857c3f047db005c13235bb79019d1fc894db43914083c9c90a2cbe950491fc22a5fee2805ca40da169e83fc1e6b82f5e459abb1dd35e1d6600c3b4d00f973c77325b9405b270ff2f55ebf047aacd26df7c2139698ec5bebb31c9037c183f299d70e35e8340072f4256dfa56d93a14b9c851f40e9b94b89790533a4a808beae3c3b83d08d105656073183c34f863b8f48768bebe057e55cb5be67a04f0e2d7ea5a99725de18ccb1477a74fc87ccba2915a16a593d49cc52687254b5ada3b15f61f9cb721bf1cf4bad678c2cc2b343893f9f0cdfb6566b0a14623f601185b809f957855b7a6f7a6a6544aa9456d3e85da043b47521f0b60e7505b0c335a73b5fe20d9301c668a083dcc442d65dedcd77386ef481be2c844d4ae8cda706627c9598a6854c3c77a6e04647e320dd7e887a1b8466f188c363fc31f9fc94170fe55b210d6b80741c1fc746c954dd99d836399066470e0fd938428a7e1780e1f4be40426d1f529d7626584d6934be930bb74393fe6801716ea34919cd327c5849e2da71ee143b1b134cfbf12ba1cb60ad5a49a9d80e72c4de474537bb21d27e0612532e872e90de18a426b08ead20b64bb0086325754a02e74b95cb9a45dba963f10b630d5a15a6e4c09581100aa30b33093c4098fa8214362fb676e06ea4cff1310b4d694b05b760519e5570fb1e522ff088cd2aa1cb3731797f6df1503a4bc0a7857f9086747f6de797278dc203b5f463c6b8133217dde9d1fa50e6cb98ae2b6902e727512fa332c0735b6dcefb3e6144c3756e2c69835c79b29fc05a15140271d86d973cbbd5005eaee72d9724048ca0b05bf6839488fa1e5002d715a943d2820a65f1a58e2352b55fb99f9b5f4e492a12e4c3b0ba06648350b0f240e0ac455abecb750644ec9064f7ce55073192d2ece691d217c3507705d6c7c61524719e9ca3222f1b8475912e4ee159ceb3b798abe032330945a539458135c6ab713e67c7184f0260702683d2a8e4c0b13ec223d61b9bd01823603a25724e2d174eb60681b8bde11de521de241c8dff470f782bf55abf5128ee0d3902cb002c74c5cae10e76271d6d81fd0475ecf48e551628ac9485143393589e85878c1374c6f24e2247285330b599a56670381222b54e57d3c1f38eb00e8dbe5e7046f808477978bf0ada4a7bb7be62b1c9aae02bb7934731c3be8c964805f3287b46a2ee216e66d914b916b130d063616a6eb1fa44d004bdabbe70860859c9675f89fd5978a6a7d601a8a0c6c1fae93419903aa3839e24ed43ecd44a80df420364e7ebe333a295f95328ae6f9a3fcd908345ad595069bd72be9058b281f33e45bf519c5f1714fd39b82e473a66ae4098982c6b7cbbfb7b30d949e94624affc426e449b924d16cb8827a640bc76e18b170fa8cb6548eba488d5fcd4de90ebf8437427ecb2906e65b061d303a29e66dc3fb44902e69ab1836a86207e35841309926d3dcf3a3e6b5908c1f1521f4fb9c34bcdc203d796a8b0be8a385b92addd0a36b247beeb210c0eb8ebc2f48770fd299055864fcc5fed0f37d0b0c8992b6c29d2c3af021779503ed1aa89ed2c3095c43e2860e50491439c463b290a7138035f51df94be4576dfed226544362c6aba12f8b37837952208bb260300e83525abbcba683344af6f7c164c19a472ebe85f68c54264fa30114a9f12395e88ef9966dd6f0ab5d1d3bfea1e8f8e36196769a885f841a27980dd7f47c0ccadf58434f82ca748728cb91a525f466290c34993462d9a71e7a47f0e5bc3b2303be19588d96e1b89b8ce24a114da4334ece8466647034c093855ac8539f141cfd72e89a0de523cd72f2cc65bc1ce9997dcb7291c4424ac7c4e6c2e1a5982d36c6279fc3cf437dd149b5971db863aa414fcd673c84b159828a1e9fc66412c90a70036c181425d6697b84515b11df40700f9d857f1026f711c71ee06e94e86c476ce1cc6cd1b81cb8c69ff05a416933e06d6d3cd18761cee8060dac5cbdde9d9f067b4d622f39e8d3f4c221f343f8c1ecaf2c6d5b9287ce4dd85e43a99148aadbcba7ad20fcbca2908c27a0742b821aa2fa84ba5df63795b17ab7a7598b87fb9b728bac19576f6d9a940b44b964924d82e765fa33ad33924e0db10393f5c19985a36f3c0faa42717072f9e2d9a34e048004167c9dd0ed4f679cc12158d91466e08be8b694994c934acd82e7d452a9cc2b4408b084341a74443863cb6713d2d378627414eeac776fbb3cd793d76ab71abfefdfca86eda324276ce779afd6bb02eb8a6a8f232c0452273f73bad5729877460ebdf9a663ccc4f27e7f559375d252d37a0519b1fff036b77863480d83f0a2b529506d045422c94951b18b221a269be1d92846dc607d0da70589fcbe88f9785c6e60c62f37a940f889d7875266054b07a596cab30915957ea5f201e44ac5595279d2cc7e1565572481f395b24b2bb880e722e3d738a166a4f213a06ba7dd128e1580a5893eab2614b3fbf6d6db963071358cd20bb2547aadf04537d228fc6d1a72baaf4ec38c905c04a770f0b236433df01584455731321862ca75d60ee4b5fd7f5ac9ed4380ac7d113bfda8f65de0d5918109bcf810a3b401340879b4f4e9571b2f5c1c9fb1a71fec99ee75747f6b6efe9a9460c001ecdd2a5ea819ac85a6e5dd8886937de53d55d81ee982bb5d682b3c0e921d8ddc94d7122499950407d112d03d8c9310e6cb3f58e7d31a90e47a6d7a729fbc425871ab26da749446f36b70c8838088a34c3dbf02748996daf5b45adcf85e57a88049c85b9e71f2147ca5dc3a8d2e6a1f84b68bea956fa149ecbd97b822d50913f41f6902d4c58823d9e3ffd03a911d04ea493690bd0d4065028fddd9c3088edd487b5887f8588cbf8b70d88a160cb90aa62536b6334c5dc5001b3e4485dfd4b8de407b1537299bcf6c36492d7b03762e3122d59d2fa0a610e8d4067a0bcb816417b52b34ef41807b65fbe7545ad51f11a82b4a395f498bd87b90915b19d7e1ecba217de937d3f68f85b0e23ca03438a623811b25505499286c3d6bce49b3bb4fc701d5f45d9cbe229b17ec6b901214eea6c5bc2b28e8695a111e9c8e25360b9dd536b29193ee9683ec8101c3d02276b85d7bedc25022a0ee8a5feebe209e568e868b66ab75febe44352b82e8b6155517c89c4442f265a25c38466a0fcba093ca29c4779b4a8886083a39ce39c6d6c836476ca3d497a43c7fc9621e5eee377f5b295b9aa40c0592f460388ba129eb18683a861b447f8085f42df80a4e58f068ed7df32af9078671d953e839581a456cca4d034aa923db9bd42b0723c46198a237c0d18cd6a6481e08edab6fa438b4743cb2474486e4b8035ad6fcc4530c03068da1c04d62c472b823f2f11d7a5e6007c84b2a93c9699e6696c29ae5d0a4937b17d0f4344053f481183087c2bab8fdc5b87fa62873cd53811d911e598ef194408376707b8946030aaf60689f790ee014b8de67dee1ddccfd149e469a9644a4a996b2f8a7a8d8ac4437209bc8df37ecf054c9fdcf2eb5c7cf87d2e477a9a540286e6d821e898f2ff22d5bf63bdf879db340f9dab9ddcf984a0d53f1bdae520ce76cbd19d5efcdf1ed36dfc1259aa6c202494bc4e02f60db1e8cac542162ad04efc1b77b4db45a73604514e3519e65224ac4ca4fcc4156680bc0a7804ba455c8f673070a4c3fe00b02d5ebf47598a635a18099c18da48e3c5fea6421b6711e9d211b437ac2ad60ada11d3968a7e729f6850e60a06c13cf2db1d17cbb4ffe5905a41f877bf454a24909238447cb2ff6f60497aec953c884b35fa82c4496896c765fecfce8a2e94f8d4fa7160d356a1538a0d87e80538f9f0185c38342c8e0b5a7553432ef4185b06dbde434887f6b2f9dee6d0e8bc59a458d226ed979d86a342b2b331133b72d4e89dcc152da2a6952ca3f67ca0b678d34b77dfca91654d5b45e3177b9f2888c8760ad99e99bf2a21a05dec8a82e574b1cb8aec136f8d9c58e1d921cbed385fd5ca04cd98b20440415916e30f2d38c211a2d4bb96e82191f7085c495f65fc599f30628ed0426078bb8fc2a81eb485213d6b9b8e3ef5ebe8d3dd2d2575a11597e50e26f7d32a4c50a9fc762a95f55d07b189f4039af12e4f3afb5d1bf642568007b70d96e9e93433b306dbf826f8340fe257444366729197fca98c1422e64943d46fa4f36db1e819c3096b59e9b24ac4e0e4940943f6599fabeefebdc6d14eb9165edb05f330ac5b6c0e27d17fa953c96a2fe53908b8d88c4cf76a18a8fac1641b9172768f611a7d3c904c7d0dd0a0f7f732dcf78983d1f7debefca4b0695f1c3955a3ea6bd61561a01286eaa89249ce4329ddcdb6bc11835d5b09b4072d14d2b1f75addb4a908b39fc3ba179c6c23d89688d4a374e8fc4a44bd846400cd0e57fe0ac52c3da024e82b94c3cdb084685e028256418cd8d57430617c9c14884bf877dd871c74ae9498bcbfb9173d38722f84602a18575e2f4185f5bdf44c44ae305019471ea44061d52c08564f9259d3d73d328a636ebe6132ccb7e8d6b6841c1aa00c93f177c4a270b52e3579bacb8c37d4badaaabfaa7758afdc594a2d09cdc02f66d89f03c780e3a001b8847aaa2831d97c6cffdefaff8eecde42659dce336870af544a12a0b1d58af12e5886ea048a275e4a25cc01c98d8b66f2fa2b5c774aa18528259788466897d323c54ad109d97f05763c8e2e1c2ccb7bf7fc0ce200cf596c6b8e28ba55666e0eab2e3fdfb6d5ad00dbd1ea6773fc56cea76d806a02ee0d8cdb632039137e56347d69e18b46715a5f4809f6200ebeeb8dff6f6838acfeb260657f58ecc0e0c5e6a6884bba4126c68485ba31f37343ac04e89e863c893f129f3e92a14acb844976f6175c01472b24c56d270116c7a72306c070b99b18822656006d40253b1a7428fc5b990b717c568bd05cc7b11f1c13b9c2907ce224bd32ec89aa9c891999dd2526d9addf0734c78465186b42accea6778b5f06dbc507b27886c515172a13e991a915e7c7f95d181b30d20a864f7b7c3d8614c6dda8e1475be097025355c8a49d671d9b46723200eb0a9658b803676ffd2d575841afd2336ebfc1f53ef211d4043bcb34fe88c06aa0c446706b8b3b2283671eb71f9568e39e1215ff4372088d534564fd3322fd25554710dc387b47f44bc4fab8e1b6c707e9980d9ff268ea72f0e2afb2f0bb70090cd1b0c94b3d61031d64e7d23110b6a846b26cec0b5560eba1d02c03e54320f19aecf6aac4aac952585b8601800acb40271a67fe84c7c317b3214d8ab36dc2728a8135a780278e48de7475467bb39e9b7343ec8f8d08ae9532e8800400c6089778d57e0a12a7cacd0a7dc5b5f880e7854073ff2109118c9dc04445ac2f0e7ff78b4a559008c6ee1b841a88c8d906b00ee5acf97466ec723495241af1ecaa9fe5b87a7427bb9356bf05137f80345092d3fc339b623a826e6d2f28213c83dd1b95032fe22bc7f2687b2c88380ac3ec67ef491b7dc6013b1ed877371b72028ab0a506ee4d36250438335c1f7f3cef52435f7f7ec7495431a1644f5863e4d1a4250d60aa2b5536cfaf7de04c51abd4de05ab98d7869d64df4242174ccda7d22ac7322379e4d19305697244e5124495030241406573cd425cc4ee9bd4d57c033f277ffaae35fa1096f3d96e4eaf36dd302bbcb80f64ea5c4a7978dcb8de06b1824c9cef5a7c72d741f6609c0c572b2a65953f24f71cd42bfc7d29a019767b7b5885e1485bc31a8ae30148020032cf944c79cfd3b100d76ff7c99ad6641b161f3432ca1d70c5db91064f52f86f977e71b5b5f489167e5ec635081fe9584180a5dd21855e35b95e53ad4f8d6b372ab4a9a39a6abfa482a2d19256d5f63b837c4f3188bad3785f4514d00a699850f37fcf53708d2a0c06915286d87adf1f5bf8d355063c7d605860e73e537f15f4806dc6bc08d1093b77a6025beb459624ed64e539ed31e95e8ca5ca440c5c5193455a43012b7885cad713249cb47ce2846c0283a0c490e6880c57beaeecdd5103e99851790f4011afa5f325e235877a5a0bfa96d38f535810f688019782d41a36f358e4265466a0468721eecd40f8193352c4f6aa20432475984255634430b6663022534997844970e1ef01bc69f4f63f2814369d2f745cd6dbb6089657f6eec3d081850cd36827b73b8fc911951ad2b761d81dad57462aedb1102365ad864f5b7559b924e0c086b05d425cf2bf8d36d99cb56add289899f8a7153e27c6a266128fbb88abbb8ee24c4f8f455645177cb1c9fafb5a549f95cfe69970c3f9771e732adce75ec10b16acb8b716e0f22481889b92b846525942a72ba408a11c751b59aa81c7504123e09a4162c554dfc0911ba01c62e4434b542c78fbc35f576520ec9d2115e4e4d5a7369eb72326276a29a5856c96ce2a86635025b70f0ae0d84f92d5a8442bd4d8c797b71d9f2df80ec33f2f8f36823d38a39bf37b77ad434e09704aab409291dfda2a8c01775820ab32aa38020b453c68d7bf2b9dca0401d51182d56bae2b78bfb411ec771ef526ed0323de1777690c47c9a20566f74d9e86f03b33a4613e80c45f1e0c341fa127e0e68d36d3a2846c5b4e944a2da195f784658acea1e9134be42dbea2206f4555bf142020aa876930b272764283095da7fe21ff07c27489516d9dd280c12ab7184c3c55df743977c669580e6703b0f98bf269730b4036ffd27bb32df59e6c8f996f539c046f880a99f85b2602917da7059fe718875de643ffce75a9400f7363574b9cbd3223d7d718cb7d2ec3f3e9fd3582dc4180ea186ede1831c07fe295a4c5288239fbae18ed1d11deab78d40c1217e6bb663fb742dbfd620eb36950a025045a1296f1c8cc58d69f6e99aa882101be12048017edc87860a4a9e53dfab05ab783ae6ea58b237d364f0e1e9c5871484c94ec394d6cbf4da1d54ddf72c08ddfb9f4f0ff393c5ad830534777bb1d4f4cde55f3659a8837f459e5d729ae3184105352b3e69d8fe44131ce64e0cec3808685f7ac4fcd40684646025b97caefd5464f295e8f4e682a74202773e3b15dc9c2cd7eee918a94fc7c454ec9b789291769d0d32ead9070df8284ecf81f0cf91b56f2e79871bb100839c550e1cae97a346dd6c62afdf4edd7e810bd910e48f717c159f6765a5bd2f3d601cf63427009544f0fa6abed4c316d45f10e50afdd501d33afa9e105a6e4a01e5d6c9dcdf274e015b7f9f008a259974f18b9e4d54aab50c16ad33831590e00c96f9d6af6e3c016f14a652b44a327787dbe7d3ac986e84da957cb4aff994b940a922e81ba2eac0b8f4be962e3ba8d554dff58e8b897ec40d0c8f4a680cb11a228bf3105e32617b096bf67b319b64cac1d4c2f74445371329a21c6d403f65529de61419a800c9b0247fcd3cd8f96dbbdcafd730657e651519359cc2bb5b08d98f819158ca983629e2239cc82d13e46631162cfbe46262b1c7dada16f2c36d9b0c520ba9059e92a496226f9736c94c39d266c98530f51b2ae9cc0f3a633011e2d730a6a22563b272306dc398d5e52ac580310883bb690595d3957542b4ed3a711dd780585f5982df60fa42d3aece90db5fadd375f12e988d65ee80b90083614da7298a8bc39ff1d9d918a65f16ddfe890ee74db420f8964ac240c120e59d942b0eff6f9271b56b23cba943efd4d439fc41da041294b8f263f3ecfc14298cce3b6b22fc6365067143721287443c2b33ecc42ac551b30a3f5f00a75410a94698a6f9a87829e8de2d9c6b2564e118ada4c223d8f44b0cbf3f934942d02a4d4d0511a99c530618cf69b5c9359f826644addd9270d0675e06fc5d1ec8bc2e480762caf181a47ab0dc13a95462d5cfb93c3150bce59c9231b7ed5034e647ef9d4b21b960d0d6a898bc528551ce0fc3a027ffbc48bb6de3167f2a0a4e9b34498774b8f03c032f46591f1e5b8e8900d1bc41b404b1b756afce3c3136dfda82d4cc1a5cd7b319a2b314c33790d4b9ac47c22083743da1428b803910891909bc200536fc5ce2b45df325e0facb0864356634716975bbaa784408a06219900948d87d54e7208ae128e29792e4ea8fd560097330308a7e8ef08225629fd84ca6f0a92e96c2178e5184aba21dcf7669ea49a2722e93145ea34e90816ca4306fbc759641a882854e50d0a4173a06cb1d40bbb4af82e278933a5030d220bbb944100b3824a05507ad6dae71861c8d38cd42d68c62c7ec37be50611861950bf32661865fba32df5c787b951a86f8d775ca81cf594534503a4d36b76e495251a8855801b0d734579b0f8bf4725a16508503bd16f46afbbfd8a31fad25ee017ce43b32651569f5b26d3bd9e4db1354910b8cf51e40040709681115b9a05d8c582ada63aa48b8ad53f186f716852eddd94ca08a40aaceefb10a4515d9930277a5f10a19cadad1eb3e9f5930cbcb2879ce29c67a6e08fffc77968e454646ceac21dfa2704590027552259af8edfbf5c4bba74853c5abea7f7b512872d2c338a0bed9bd85de5a6b982f51d10903e204afdd090ac23e4391db273bfa630750ce748308c5428873b92858a481e771b90ace55c5de1c6ecd36def60e73e43491c9f4c787889a619d90a82298e3b0969a88a03ac0cedc5344acbfae1d3d1b2313084a2da8f87ba1d6f82dc2de50c66cb43c0291c5c8cc0e7ec135534e3e26ed040915622d22c39f7dcc0a26f137b6daf65acb96aa95ff2b50a6c7ffe7f2ab0954ed6e923f1809f872857f44e54c264ed7732d28376d9d4a1a055007fa10fb923b28d259eafc47a89e9756ed4357708067e0f6de7c969dd6200818def3bf5c421dab786992512a3939f467494737d48d0c0d9c79a78b7af3c2252418475ee41573d6fc47c9a1801ec50ca36862b1fe984b313fc083b0d0f9be92476e760416115bbd4bd87da197e1cd83874cda87c0797136c5e0a578e55d7e71d8aab97ffaf2286a834db791270e217c0d9fd5f706db36e8f24d481e821f17ef33dd99797e3674fa560b28a1f38af89d1312020f17f146a36c1d23dad75c23992c613a9124d3e5545cfbd3051b91e55db54b1b40bd43a3f8fd9493dd1e3d5f6e48ed5a29bf429a614671f9bdf410479363bf5e4a7ffeb86d3e013628fcf8baba271c94e1bf6e1f16da3d772b39d75304b746201a950052c08f9e7d04828e86847d186aa1335601ad0204e836bd51adb0345e22508484b6cd3cdc9c0950916e318c33b0e74e7f0de57678c8e712b1c49ba01e7238a15686ecc70963a38636c5631ec7df0b59b85d593f2817ecd8a432bf6cc6caf9a9729d7ec93af35ea781b68ae94b550133c35b7da0c8e0f8f4b0b2ea4eea7dba7d39568f2acbf999916ef77f3d9de4ed73c38969bd3675a10c04c9c8902210671f88711973785f216f81b6be9043ca3144faa6767232edb5f6bfa7963ca3d1c9f3d5d81b28ae4d5cef5e22275433f5b4ad917a7f211bd0cb08b0ca7663a2153329423139edd4443597871300b00019ab45225fcc210cff1104660cf48339124969758af975e8a5e7cda4d37e4603270c6985d5facb53334a215b520a61802926db529552da0df2c0a1d2463628b36857e1e1d94813e212d98c984013b66246fff8bb094a59fe4bab6ae159bdc900ad4268d2cc7465d74fa49cbe73cf8261180d1c5b71f0ed938ae74eeba71cdc19a4a5dc0028ec16553fba0bedba34897f73a25aac2a8b7c79fe2a7e287395812d434c3bdbab6b0a81441d6495ff76dfbb55319af7c93cb4b6e9257b095d2c008844dacb0825f6022fde172b29b9cafb5e2b7e622c789d22eb73eb78461043a173e2b933e28fa50ab224578da1dbd6fba8d219dcb0fc7d15a2c1acefdc32a07dae30f0177b15265ec84bf153da268cc814640642cf9224341849e9cdae095a95328ca43743242924f9c1c61e924c9b38e117ad34c2d78e90c5fef223119947ffae2460a98ffda68add68482ef2a462689a320894dc1ca21505b993ede407aa849cda1d073298c966dcbfd935170382c4d38eb7a72f533645919c6a71a48628a91a831da57ec9c9b76225a00a47dd99ff7b93046e2f6240cddbba497e997091b103c0e144aa447641422b238adde70758c94311ed098c55ea92b407569cb365a8563e40474d7fa959dab10cf06a34392bab28821b43e00614fed85356c3a1aaa55bcf61260f3bb43e93d97394845a885e55a0be27988c2c48ae0737fd175fb38598c3463c62018ea43c5272098b73249ef7c4befe1448e86384c893accd48514f76da6e391d2a91e7cf669eeed8971b22cf808d0e20213299cff0f6f5860071ef14a431ca3c91dcbd96acf168933efaceee2b411c3a05215e3f23725e70a63ca02945f908726579ca97a85d468d75945e883d9106a53dd93265564431b5747abe0e48d0cf96782189dd99e06ae73e4fd26982f595c6ecb4dbf3507304c1a5511a345be9049f10fe5390c22dd52d2aacef0235a87d1c5cd7a8da2825b12d2e9bc54b91b6dd1857f9c7963d8b58f6c18e05c073a3224ca1d117885691f9458d3f292e54dc6fbe542aa982b9746f1788e8e0a318e7b0e24c458a7d2389646df1fa468e8a4f0c275e5949e5535237c57a8ba7699316ceb1d407f199ac0893679398916ef827ee280f3b6e8c027bc8029ae130d35de8f4ea640802cbb44baf51c8c9ad262720902dd02424ff5abf0ae738b83fc0df9eb46d4e17ea60b3b887a273aab899a30a0827ca0f79e0b8bc65829edfe9119cc504a7c2a691d102e74efc41000a039c7ffd13d6267262f82b4c85cd8986f274dee642e56e0009e97e887ca37137e0213be7c5c6d16f771ecd40f15f69e95ecfa5234cfee5a426aae4cacba826f1fd7bff17633cd826251768fb6b06f0dd44f50c599497a60015dee1f1ba9c3c16d5e3b7cdbed23ad6d7bac65004cf4f9613977563f6aca206135003781de7617aa53590432a14a841c10fa17d3b7009dd31b0150058d569b0d735430ca428c4a00e98d7fcda98d9afad85c07966b9d0374b0c38453bbc5c36c331a3337a0bbf23730783a31f860fd166eeec27db2eb80834f5b84625e345207427fc3d84633141c661b4489054fe0969d61408c6162f6f513c8160516b1aea89ed26516407d5605e6ecf9d77044c1da1e63ba72d49aa19b49f1e4844c70e0e212ff9467ea48b4a5d0760521d03af1081b83fb61ff05c664034c70e4a230ef8236b00ae89c80e62a7051c911d088c2abd88ec8033522a68fb87db47dbb1d03ea16cf1c1c9273d99ee14c80ed27da91d1d341b0c78d00e1e36723b956a09d4ee7f2eaf586707aa9b7e44ba615d7763ddc85fb3dea804fda11ffe45b0703aae2efd40b27e5fd01f7096f3dd9575d2325397c3e0e09bfc5308ac41b21ed448b10a32823f2614ec147dd003f97857224a761bfd7c87fc01271dbefd6b8b63831746132883b6c432b878881d462f8c64b907fe23e234ae8611747aac8dbed0dad83849d648f99cd6b6ea21ce5483bfa8c6a4504021b72de308264694001579647cb42e6a66a07277a56b8ee3a4c50e6cd4a65961d14a67a7b84fe0c7313e5fa2c4c43d738380d8ae5e23938b361a3eeb4d770fe657e9a198e0c4a623c17e337d63e9961024365682ca5634ae910e8e0d2d495300ef162c670b78368f3027a87398e2d36f36948c8313008f4e062b85f7bdc560c9a66e1c91ca9de004f7b1cb38ac571f9a81c587204b47bda16e60b7d644c7fee79f66f2eab53edcd93b3bcfeae6db6dbbb63c21704fb048c8723129d2c40539d862e6f37db337df49d1412695eaf3ed515111b2a4aa0fec3dd429d127b449eb60a25a9100f1466ddf30d9c76611a89e3672b56fffc605839890635541cfa5362da2ea532a34eba9b10c9629fa14ef1bc0be4dada92d00906f811ae3028297e1fd156f84fe4f0303bc77217be53897629b1142cd96c9601f57e6725472c0b3716c2110e1b20824cdce5ec02640fe2bedafaad274dad4bf6aa8d662b45ac47cacd2d74ba53d8bfcf92957966010e825a1826df349107d95feec3cff04a335d96639134e7dba1606dde82c2ae0dd9b81d1651c37ff369601587a6c53fd5cc8c38adadfa2ba43c934c129e653658372f6ddb0df3e33544caf26268642839cec97119ea1bf82efad609ffc42b8dd46d474136a7986eae95973f7acc1acb8195a70d0b3596d931c24857839abe40c452e390bcf33e975a4e4255d487f5f173e56bc05db85fb98a7c8069d4bae97a950ad51fa6cdd788a20e4d2afa788191c2303977955ba1939e5c8594670690a369f956d10cd266b1409f0ef1b30592a01fa2a6b777e25149d24de86446102c11584e61e26f9d37b09f735eeaf68607831bbb16075211d896c5d27cdba5f99482edd822f44042ae570fa249e19f50563e056e41cb4f019935c04ce1f478ad260569b721afe497856c61af92923c2edf16028cf65477fce56724b13ffc3d30991e89b29f101831d715e00ef4a304bcfa1a5277a94de16cddf5eae3c5fd9ff562312777e04f4ed4ea5243179fe5f44fa3e64f26df2d1df59d4f73bf14b82f2cde5bb9c5109faf9eef0893137224a2066a6f9aea3358f664f383d1b8b8cf3856f80334d3958283f2d7bd74902b28f1c888b74948448ca1be95efef9a2ad0a18c57fc9759c6fb907c41c89d2672f12fc26b90b376562288e5549e95aa1ca447a245d842f19fcd5197fc771bef00cdb6c4a56854b006f6448cddc84be01382325e85930ceb74c852ffe206421747bc45ec856dee7e67c952a78c255b9e6a0f2bf3248e97935a65cabbeda869884bc533a94c7dfad72406aa24c37c80c23419fee28364b0837c7d2156c3a50b051e2d1d49cef66b9d7152509b126bbc7d7801b27c515311175deaa8384a0ec2e3e715aaaac39ed95d7de7ad128ff1273bee0d7cb3fcee70f3819dbdaa1339b41e995efaeeb5a6a65e7015e6ba1becf7bdff67973b606b5c936b6245d26d0af47fc746b53a9b4fb3b30ee36fe48970febe382db3d734ece3f7d43d0c59299686871cec6cf7e09401317d21087284f384c9d9ce30092db0c5fff2bed078e0343c35b3011fd138de43dd35567822f7a422476a558a381c231bed794f72028b43eafe664ae5394d4db411e5203c5da6a32658a444b4fc0ef4f3c6f661e73f1cb09048b1557c8fe6fd5885c8b7122b8ab494329495597b1e504df7e399e50ab21de2ff8e0f6b6c60d792816273432c029b431e8b17a111a7a3c1e759903671cb8e1ab0d924d294be502495e23b5efc941ca720e1e0bbf2f56352ef8262f0a62f6ed87d4629e4fe09893b676221de295afca56bc3247eef64e262c9377cceafec2ca688dd873c4be3db2abdc246a28c7cba2de78675d7361d76c840a651eecc0a2db9ab4e2244c53ed942c3e2a3ac6910a710b39cddca4ea2e5c52b2bc660c2568a3c7583060b5a38bf4c7f3ea190ba8dbfd33db917b13c1bb45217b54019f53085be7e1de821d500a90c64173c43782a49adf5f5181c80dbb7f41c1f9c9e13468140635d8cad9f0486bd0a10b1ff21bbdae377caa8d3e764b8b2439b38708578e672382de17b17a20b7a7fbae90e62080a143073a48098fe0e26727a84741ecfc5bb473b59c71fdb84f3caf7b6d1ccd13c68f599f8fe40efe26871f641fd3c2ebda29736131809387d833420d65c2360541096242876668f6344351d469ddd307640f6d2f58d48586c90cfc69340d7758d3ce9a0d9f2775e0835a6781b6806871a5ab7151f2190168919d21b95ef3ae8bdfb28251d50d6650cb079bc6e248c427e34a7366e71cf048584a90a9f7d185180076d3325f4f2c30c1be23a48385e12c2b3af8196e47a183e085eb1f02237317a52f875576d062d2f0e9b0603350312819bb6bf690ceb789319728b7c045ceac783437796f38b75bda27b9a3d581a34c3c5d6f41b982b017a991b3147c019656e02af822f1b05843ba721b4ab531ac04c383c3385c0a76160c84901a579db7bf4604a20da31ba8ef88886a123fdcf95048fc3a184234c59a17da25ede799bfaf9fa3b7e6687dcd87048d5e92ff39a229655c0d55aac3bcc07fa3684e2a357854d0e4bb529d7af08128fab5def92e858b01e2dfe27552b3cfd03c25cff80c77490782cb0d657141537ce0da170c5315a42671bad21b472d2c6ecebf0deca0596b2af5111695b398143c4c717b1bebb7ecdffdb65c62a81d5af918cf6f0d58cbd655da5c82effdb54960ff63364658aace409f922f2671e66358953347a58481570d3c0edd9a9e0a52174873424555dc29ddf8b9a5594e1751004d016ca8570be745feb7ec6b0a47cb2cc79e63af02d06865af1ffe14576f562e536f7437cb70010b23ffe718649584d0507b4f63714be9047529e808aafb75de0441980c5035a980530c3e38886fe823c110e0b10f54a8cccbae674f1126abe51666a91d97814a6970a32d39ad5f76d83a3319d1865bd14fa2fae908edff19564b2f6d5632d9de0e7c0cd10471a0196629a5d61e99b09c4664f07a8320faba24164460e1fb4e263a4d2c8cc356f51a08189f95e2589db59ce086e884e30f5dd3e3902ef04d632fc530cc4479f8f166a3b8d6634c17c6684b2b1ee3a6c5b7eea07082fa4c2199d5279de8567161f48145f7de3af54e94f47ebf4c066ad4a57abcac1730896e23cad79aa8b5f8b57e4aca258fe438d6495649024f0e308b376744b5c662a6d1ee95c2bfdfff2197d486988608a2aba9efd1733342e704ec0c42c80ca78f4b6a4572106ead8947bcc8c01735d465d4c4b77ed1974cff9f3e28a04137b0144b52a7efbcd81192855b8c5f3b9ad29f195de871c4926d4a1aea5c176ebb4abc54bcbaf0563bdcc26bba15a58f9adf699d4e5d53c500d6f55aaff687dc70b78e0e3f762e240385cdd1521f13bf5c2d2ebcce37baf8b5118e905d15180be43f3d831c7c91ba7674ead6eaef5a18be999e09bd51f94156683f40afe48e87b1d1a77b02247268374ceef84bd1cbb8313c75d3819655e66fc6f8d522b94a518cf068e4c9f76d72ecd5910c8354f7d71bb194007723d16be13bf81c37961c4f67cfe9b853ca7d350806a6f1c758c47573fac9da52d46d6675e361f698d66588260358a4d918f59edc01488f30e8ac6ed7b5af8951aaa062771fd4dea41deca000c2eade0a404df79f19a2427905b1cde9fc72b0ada361dd9147a168e2fa1aa46304affa74a4e6cb3a68d3b9db3d248189a350a6735677869ca4783bd1ac7663545ce792b1dca963bca87db8578b52405837412154a84929027719f8f84c62a2933f452f0ea85131a970369b24df26211f74cbcf37f263ac32a22e128d80981151c435e4a24bda86b105873e3d8e660a8cc23e1698e8a146a5005f2054e9db911faa763fcbbc0205c86ddd23e47e91a76895bdf3f63192ce712ecf12c6d714cba02ad6b7c3810066ceac7da83c1ca30b34386e61e18797161fc774163e909b6252152b557bf6bc036f319dd7537f88b4358a241754aade0d866bf689c6749151dd9ebef34216107da9ae61a0ddeca5d3eba7ae18e4c9653d14865a645973e5546bb530b430039486bf4b420373355953677a5f0500acc856c816a874d831f3ed858e9c1e7d3e2a00f06b475f363a053e6b846945b6b53e8f0de71eb37a10d49252acf174a529d4298db602619e057e041044331ad64caadc7b737898a93849bcd1ddd96e9f81bc6d207dca8e6f1f187a0437101c8d9175d6ad37057a3fe6a7346500126370d06b8c1ec92aa651ea3b7cd17ec37912f542132d448c3024f53e8d6d47ea321e98914b0a7c394be700125276e155e7256f3c532c0fd5d1a10ac14401137a776be45ab08127d4b805f9c83506636f4bd64732f9875ff0bf5a69e0fd42935c062cbbb816107e9ec3ec6bd5688e4a9b414ce28fb04b31f0be7b5fc48b80d194ed333673a6b9a938a8260dbda2567726e1664fdf581fa5c24a0ac561909f5f2f03d204133b2a38548f56e1d750368dbd4dc1cf3dbe246384de6122e22e2e022202fc7b060ab5494bdd6a67f48383b21941f18d376fb4099998f7139dd6aa271573919b47466c16d0cad8580d00cbfbe55fcbe13a0efef82ecf1bec2592070cd366d9f721db3d9b48315ec9205aa262e35f2868c4e33b21a0f1dd016ca436160d6690116086294f17ac4c9e3f402dc04c940d509be40d54e10893d8e96a77eccd6ffe7a6b268d4a1a22a34d1d939fd67f525cb21b8d4c60a09a22470112fedfcaf8deed7e963597e885c8b099843db0a8bcb386dda43c9f9961da5e68b1582d985830e6bb5fc285a07c92c27c4a4c537983cf5d6e10c1dec23d657b5d612ef17650efa05711ef6c6f124bf152e856edd78b2a0f1f0a43c88f71e1f15f40102d9bde3a49a798fc85eb214e8a0f72cbd3b256d3b5180a832cd500359f97edc354cdb90466611ac6418ecc6e40c4f40df5eb56f5052e000346f7505e3fce4d5d18ee27e30114a96b17d02b1dac569fb06f4cb33b85f8352438b93d9d4116ac376a3d92d93ea66c69c5440945f74a43ee38cbc3a5c2dc35f2632d6ec78ef6479916d47f768b0fdf3715e402be01bb07b85e62bb1d8b466868a89e6a83a8f1bfaa8a2775453530e4122c9f16df2ef24f77b7a986ec2975ddda3678406e06d0ba7c525d6610e773c83d623979fe9cd23b00d67f9ce87f1cf2dabe327792bd225d9b3ee225fe9de7427f6c3651910958ffd13cde55eb05d90af8af11211745872a595e0ad787b6837055ff53b5881ffba8833a332ae57b13721815a47540549e03a9d38b6b89aa6a0071439a390c3414399cc49d0a27e33259696df06ad09fecf329718eae36f58219823d8f816a0764efde7542999aed9ece46319b876ec8219c4bc8a79750b6e87d02623e293a63b038c50656abf90098de7d1667f0569a36a5cb19f7f2baa481ea8f53570bb0d744bf5ec6716f07c0a00ee32d835a428ef30e3071c9b278fc19c4089c1fd1abda374c939dab7ef38083315b8ad9676fc40765a22c727640fb24635b86da0e5b9ee8faeaa1811c5152154195d1808354ad9da2a5ef80348786b0c482c7aa44d81f39781ac2e9e87616504ab229033de9bbd63ae5ae105b9f282ac1e82a3251e03b2955191efed4060db9455710c618f92f4f57f512e5eb8aed02d74d71bcb11c0ec2e9c4e299a8ca72c600954829c32ea7f67f7bd1e1e1f9a9844d9614cd8f5b78da4bc455638707a299050ccdab95ad4d2a91bd9f81288f09225871a358af4ae09ddee54edb7801bd51ee01350924555abd312590b106755314e86b4c6e4daf14fef4d4cc60e7f21827bb45d5612456206a2bc5059010ab52ba3ab568fe9a8e1ff303c4ad6a6ab38de4887af7107b9cd2408d83e154295cf57da5500f0beea88d89854d180389837902504e11ecb1f53270de9d5811ac4224c074463702917194b2c822b8149a4293c1f02c82a688b49a1eb55594927b0d051c06893720d4749e07a417403918c18c2becad3010a9badc01605b18c1e5e37b22c4f508af01ab719701eabc0a7489c5bdd0ad4e9e55a01bdfd295ad85128f288092325df61963020247e7816491842902553f9e4b05668a367639fc7cef98cc6afd0164891bdea9c9c25d386c084dfd13651c7ea16022db893f873e5072e599b55d627f513aa95bd8b1bea372c32893199219199753993a484ef18e8af7930c893b91c5206f254dbd28514a14ff14064365eb1449bfd23165e47cc87593a6a84134292963c87a0bbc8f3e0c12a3ea649bf499b87ec60845c44adea44009bdb273fc21d236ab7e01e0ddc267def96c6474096a8afe1a49f55a2da00a0f30a3fee65047db0d4da64ab1153a01b7c9f7943c0b8e30df564b149b7685a617388d5e2fe1564ab3733c899ab642e91da185d424122d84bf410f2e9e79064e70497869ef50fb9b9d8f463114c403296c93cdcef97415ce4c0a0c5a635d388547f47043c623f3abff46f9c14ab9b5f1c3357198c456d2019a69f448e52f532d8ed2dc474a96522ca1656f0a770f6626abb5384c437ee3857b212b3b88aad59e6b565d8d47b6b79543ad7e0716d3cab2907f512b9069d5c7de3785e068ce7b312d1cdab4765348d658fa6f539a5d8a7306517c8d37113868461bdad4b1f32a7919ad25eec686b1f8f84cea836856a94a07456a51ab56fc96a91be026c15dc5d9c07521a50582f99244e8750f9b6d575e83373dfc4e2605ab5812c920b9a643133ebb282cea0334f71e5437d98da1eddc0f50f36ee7cc78966c6aed662be312c8b1ea311f8ecbd349051c66e0229b54b499a77402f6a3a3f4149e629eb977c58ef0c0ff87f14c8a904ba219f297141efddad5617102021c61a5053ebae4d9c9e40123ca730744ca68a357114b0661da17092f2491aae12c8b9c8aa72d3327723554fa5f75c7a5be608e7a5674ff534e9e82607355abfed7b234eda1a6d6ab9ad3c35814553acb070dc85d0ceb806dcb0f69f71ceb8ce3878d708cc6b848e8319275b90b83a1a72790c117f7ccca65503c5ca178121a89eb47cbf575a7b7388b6143e8d551cf57c085590ad40f9ef56f84945457140875b882166d4a9d22e92a5779fc880595cdb47265f5ed1241bed76eeda69e00eb5d61b272bf792ca393bf73cf5b3a51646851fd5af9f3937981de9abfc75a7aee959e9e2eaebabb5dcd0316e0a7b13ec0a24e67e94cac4375acfccc01cc7496e80738c5d8f5fdc40381d05b8bda1e770541f9630ba1907cce783756c9803efd43f977c8903f59cddec74cd972bea9de860aa5c18db6e4b47e591b265d91ec7d476950bb3fdfd64c8022746c7366a060b451c0b502c9a9e3df5d74035bccfc9d16058f2ef1ec2f5c15c3f966850826cd7aef05814aa51a7918285702938a996dd0c643647b384d4f9bbfb85f70209d4bc1cb9a7b4100c31d220203b88d597acc1d6060e7b986c1b111f9d22a211640b5b8c0e4bb40482e09242ad3c80d6435d2445decffc44d03d8053028147f33ceab88dcab9b78592cf697893df46af231629a8c8b6436090e95ef88b66167b75c7b6fe7ee52edfe56fe2c97e78aaf11c57d9dde7d9c52627399313d27427df2885488164dbbd231b56761df171e81648c14f158de379333e83f9b7e1501e67ae5bdc9330f4d46fe320cb49d2b055d2a18ebbc4dcb008b9e41f1a72d54027cba7687e4419b658e4049b5726eb240e060cf599b58bc06966d5941c8ba904ff337ae8fbfe85fa838065b99e0a049a0b38d3bc5086095ee0d5a298d364eeddb1cd93fc380ae6764d2e8503201393b6284ad90ab557367dae61bd08b604d2bcf81979fd4a643b68764d4c387022799fc7be21083eedc7134f643312578f9fb2d2e6fb1598eca608ff3cf9a49ffd41ed1e525077a9b4f889692d4310515a4cf2c3b2379fd533fb12cd74ed248703ebd74bd544da6b8bfb83f97f39236b916760f5df44423479df36bc6c53c5d4f03c054ac0a85d9076444e85026ef7c4888c483b2c46c010350563f5381bc58b5af76f2698b0f52702a805f1a3cb9a7c5e423186f93532c686d54d9f39c0122c357dc5b01cf2b88b2eb8bc7e18576a30d46308c46075267bf043ad3aa4606f8ecdb15f860c69d4133c03e42c1775ee5ce4c748640e859a20502e97df08009733f5f1e93a8a8a959bacf20cd4b46ce47768cfb268248e00873d8e7e0ec1380948c087419910717c2a385fbe93b5e206d8106729c2b7e6e74896a472c953f9a2f80c82f2f285d1725c272ea735aa949244fc9f7005d2a43d23faac8e788dbe840436f21df1a5c0e11e16159ffeeeb6ecd8850d21ea0187c41cf27aec5783b2c379a04cb65a6c209337ecfcc5433d6df6ee600156b2043b2536eaf00aceb28ccd3a6b86ac0f73e8c9f407950221f1f77768339590766c967c617fc651e0ba92e2dd006d0818e1cd3df05a29d5cb5a5e5da07c69a3617f4bd035874718f4e7b437e20ed2904258405d2d3b1addd8f0c3e92507970281cff5b1618db7ad3a6405a868fa739c2969ea9864d8f1081d203b6628d50f6ceb2c26422fd5920974cb92dc8153227eb4e482bf12802fc626fba616b9cb30cf3f087ebec37f8033ab4bce2cd1e0c5e5886cbdf6fbff038784f9179ac2800608f0175a8ff5745361ba60d3835273e0d1eee2d1437e2f7f8fff57be2baf823d78bf19be36b1a485b84031c8075097e19a71e8ae80882dd92b507b67152011f2d3c737bda76db5d1d316f01efaf9e0ed0fa11738f5e8808e08adeca6ef235aad17838e0bd7cbf4b15c53ec793286025f6fff9fe46ec95637e1e095e5dbb8f8a115ca14baa15e373c5816d386c00acc88dd1a3e472e4692b1960d082ad5e8a062a953ae6d220529e4ca67f17df9bc69cc2903db8e41ee9d7ae41d63d56bc0dfe076790456f18bb160b7fce44df852d73d6067791eae69d6e9b06c0d8739433d988f0fc769f31e2c298b9ad0db0dfc0aff0a22f6b3c008d09c906b081c7131a886c5941c2d9e79c7db5303ec6e504c7b7e3cf24d28bfec4aa12247af47f3d5171d5d9dac0fec0387de709201f235138832da48a3432ed961ea72a140420a48a820c8676a9449b62e2cad85f63f64e779ae341898cb722d6b5242a1cbc49b43c43022c895d864a772881ac87423db7a94fff7df2d763957156e1b0dc2431f6545b9603ae4ab2838e0a5d7e42780ed6121e4548ff02959e8ac6581eb102060fc1bf5738c7321d4e5477cc73ca51388e1603e00fd6580b4064561c004a4796ce7c0f0d561e416a1a48ba26a5542456b4f46a38490c4cc5dc4f826fef82c8eeab2aee892b78dc7de1363c86b019d5d4817f067eb7a8722575ded07e5397b727081a1c00e90388dc9ba09c21fd16b23ff72a1fdaddb012e174579ea4290d4709511fe0572de5df6f1beba6054d3e8dddd03342e817ca82384b9c487db5ca88a4e1b73fdf500dcde5d47a419419212a025ed51b6e64cb18310994d6c36a95b7b8e1cdcfa4a27d917ac8f10247e40a03f56497894bc86cb3345863c7ff0dacc3f602c0275e46e4c58d126a59e171544622cdf587776645ce89df8007a086d9f1b786b7c50040e4bceab847c2d9fac05bea40a6848f6ffebee3fd053e30aa6803d0e9136b22639d1bca08785ae8fda927a81959ea3b13c84b7081afe73e1215c97bfee0458580d59453c853ccdab3447149d260a9f93f960aca9e5949f7a2ca6f7aeec4c8769a2f4738d79c632bcc2ea737ef4482cffd457f0658d4ca8874a7140ff6e76d9dc05616ca4ce82bd05b7402c3e18103f7e21b1db179a121a823043777d7ac698a8fda28be778df475629d4e64572b4ca5061656040ba3ba79cc3299c14aabe35baa2b457232c12d150a4e9e0d3b7a4fefea3e3815fe300a090e6c6be04e8fa511f7d9a65112d3a48e35fb97a03077500ca39f10dc249dfaa7ce67edd05501135aaf30cd2935eb06de6f204149e60d41099dc619a236b7d20634209c318138f3216a69a1c9770e4c3bc0999b33f23c2be1ac6cc98e1f48b4da194ca9106f257c8449dcbbdc525b7023261ba145fc4e616b1f0094559b7942176c7824f3969be8c09eba3fe4310ff76dd5ef39b018990db75217b3ed7577815e1b96ec5c271f1bb8d6ff53d859ea0c980335217e24324d4ac1fd5581b6483f199990c6dff014f0a6c7b46ffd0fdc1d035ab33d595930dad53607e5c8dc585e72aa45b8863fd60f0e78af06e43efed81b58131b6b27d4b4fbc55899a3031d56ec65d89c91a1060a27c1604690d718705a8e83f9457e7f9e6c1d0803e0fe7bf2e049fe9f8fd2464b9b249152ca94646205860574053e45755a29469c4cbd47d662f9567a29a59ebd3806eb9ab8f7d251278aab64431b924e4c8200b14aac6889946559966559965f7ef9e5975f3ecd975f7ef9e5975fbe17923165d08636b440acf811c9164869436bc3907c7d98ef67aa4aaee4743a9d4e65792a4fe5a93c95211b96655896362c4b1af35f9665f9e597effd7f59e2adafa0c39e96b021b640ca29eee28fe1644bb2a17b5d156c68c37074981a86a10d6d68c397ab34030c2349154e766fce7064dba8497cfed7a84d4eca904206bb94fdc514a394d20bf3b277af4ab9d9a3fb18dbae05c7fcf0eb303fdcd590922dfe8b81b2cf2ac8bf0b5444d3ba4e52fb730139ae6c67cf9bab0a5384385d40eb77398b4c8395bc3ce62ddc2bf71572104eee63c67ac4324692363732aca6871e3ae9c4c483003129119d084729c7711cb771383f080cfcd1673961ba143e7fd6e74a702cda86ff681adc4731c5077a0ffc89b6d1393c98a61a8073f5351318cd976841b37eb40d9f4247ac29dfe3f09e3db9f3e03931beb66aa7b40d1ad2144c01838ebc851df3cefd4ee5b40d6fa552ad9c0b7b8bd56a1b1ca6651bf66d4a350d7fae33a59c06d399985eb7ba065ef87c4c42071eb23c4aa93dd8ac1437e23ab57d380ab930f470348249c3075238bda0280f3dec74b84dcbb0e63167f4f0416524b9234a29a594524a299d18e7fcf9fc87a80ae35528346766df20d8a30898a04b8f7895af4d05640a2a910f2158562511f742784c2d51e76f673b140d8ad22d681628ea0485eae137fcf862b22ccb2e1a97c025a2a64b494b391d3a0c8d6a5d0b0fdadfd741cb287d8c7eefc0ec077af8eb073ebdd8fe3862c6d829a0e60a35572857425128cce650da36f8b9cea6c53eadb97ef02b07ca3f1c4551144551144551144551d4bda19110c2d0462770220bd4a3380c3a800043eebf66cb081530c678bb19085317f1da22d9f5393add17395618c3b21a343964e4755dd73499ae1d95da1db9acd4e693bfbd817df695ce15ad533401840483115a26922a884832917600294b20330048e43967dc073d938934994c26934966fb81c1efee340a72fab6ab967620d6e1f9f953dad54c8d408de3691545892a0999836a728de3d829cf7d918c580a96923d675b1ea198353899550c804963219988ef68d2c4a2f965d883e1513c67005a64fa9d8f233e4670a6625471f538f2e0466c15c2e73bda862b691ade4614610a57620425aa2528811c57230e57e24a708cd738625ca372857d52366181a1cab1a9944629f574321319ec548c4aff1472a5c9b142ad344dd33c1d4f87b1bdae9661d85e375585154d581bb76d1cdeb86de3f0c66d1b87b7546adb36ef25b4790c1bd0cb5ace89dc14881e33f84fc76b0ba2584a3d218212726a624a8245b1568cf1171983583948a886babc94fcda637d755d0dc5ba0ed234b80e420a1264073a08d28320a828c49448ae3872ce5967d5461a83c5b410af61ced42fb848824344c6a1f8ca552a8ce17f956a6555f831ecf157ac93a12efecd64013e84593d0a93cf875059b6e322759d7380ab542a95b52fab8d8806843585aa6e0200838cdd8729ba6e7713864c43eee59c73d79b5a2f16dcebe14904217e3deddbddb79b826677d3a7d76317c83eb6f4de7befe6850c7eea67738cfd3f14b04891bd2f640a81ac83c5f5ae8b8b32e79cd66698bd40c8321b17727d50a60273e5f3b7995d4c61b6ecf0813cec1235c3de5a6b6d6685ec9915727337441081ee8af495d1227fdb9b9a1f993e58e307ac0f9c2b1afcf3214992bcf7254496aebeae797154a4722453cdf615b95ab739b8901ebc5f6b9cf93db6eceb532f5d7fbb52069adb83738b1c201ea20e39508b38ec8b21438e5c6ee370d4dc9426ea9b3d67164eaaf03172a2078d691199fbcbe15199938cfa9426fea02e8c658a1013dc6863e17ae374fc9024cbb4d30fee614964ee734b877bfe3fb8e7bf83a344d091e70fc2f3f9c10683e32ce0f9dcd22a2a313a1ff957de07d7e5ce07d739c863f437b017f58ac6ac742ddac7985ad72201ee57de022a1fa30bc2a3d279ae9ba20e688a3ca029ea96c3b4683fc4a7817f611886611a913a52977aea247d726fac25ed63b8df3aedb5ecbb3522d5544b602d655ced50cb6853ac9a6a69eb8a700fd6d2a804aa715243b4711c4712d7d295611a892d79d552b6d512564b9ab6615ab66d9bb665dbb665188661d80f2e9381190c06ff055367b2a9c5bc625ad1812ec568b46dde8bbbd490c18126071ca81986d4596bd79e329ece26e3b99bcdba2b32fdb985429a0efed3fbb7d66b1c845a6bad1d155e593aacdd42097b10dc6a6152eb6412932426afebbaae2773c64a53355902164d7247c3224f56aba80da60daec89ce93cd7f512260d876927d3f3f7c6a2499324258720ddbb45b65ae410414c1b4d360de736ad5bd98f16d96a915ce7d2c1c241692b8c1285c17a7466bcd036b6566bed5cabd4225b64a9d52a795a2d4f89eb783cad96c78343c7699082b5d68a22c639ff6b1fcd8de7a8a190e7e5bef7da720b916ba9c1307cc21fc6e00cb75dc015007cce8217e459679d2dfdf88710cdcf067bfc36d8e3164c9ba29aadc51275c1c0206a218af7be84481d23cc39efbc5bb4966d91e99555f22c45d0319e0c2928c20b9fff006cd04ff17ded9b86c5b6cec7111f5b37b51d884ba6a0b4b71c8ef97518688b4ceb4f6e87ecc33bf4e31bb82ba239e77d5eeeb82ead7c42a7d3e9e42cc34a0c337c5e064b0c510534452b20950c6a510cdd8f23d8b3fc87a5e333c8685106b98805f42023842812e3697c72376a10f9597e05d4b2a2f247f011ed8710cd009aa24f86719b8e180ed01a484706c845193445db16e33d5088ca5017acab9d9597e163f818cf926198eb1ccc029a2dd218191eb33c986374169dc7d1438dcd8d0f397ed001844aee782787ec743a9dd99d4ea7d3c99f19c3097905bf4a1325a81edab74c974c6d24c98925460c31805838cee3a1c901877b3dd4d8b877d33fe800c2bdc65ea094524a29a594d29e349c93e5edd7183f6bf8a60058e0a35a648a669195b7b1a295ce8710a2958f010ab1688a3416509f20758396c89770210c51a4f9114c9c208a348a8223986089b47a04a9092d91664f80d30529d2ee14486a8012691793205c4125d230145054220d2b021454a148cb84ec201469dac602ea2751842b91b66d2ca036c2881ba4441ab7b180ba0826544889b44eee84c0f3d5074d26478c1889b4012cc94f54bee24c1bfea4690871184c9c3397e7b36a25cb790b3b4f2912d3e355b5c49c73deaeb6e68c2fe68c279f4a075622a52ac4fd49dba8389346eec0ca8433e173fb2bee7d6c2892413b6ab9401a4259d1987ca3380c062fcb8e62bc2562ce99edc5061a3a7634b16ca29e8ec807da519de29eab3a1d8b4bf2d90e94d9280d69d8aa3046476d10af87e1741ad2b08661188634b4f6e596d955be18eff5ec23ce0ec018a6d9bdb53692318f5266cccffe721ce013350b8f34d4c54e549da84a1b06398274a152428ebb5add253587aa0b25b0c6d5683bc085942f56f77a09d5f03c29c158ad30cef93fc7531ca190e7652e2efe8f13f185141c0018e48f98c15ead1ca673accd716f6e3169f48a0aab1386e9c8844194e3c175e05f57dd884bbe21efb460ce894321defa8155b9bac77814dfb0cd5952664c06006cafac7316f690738693e9e2bff2e273af24968f72fe7c7c544953e54bc8988b9e9488d9b91467b9776d6dc14d0dc3bf303d475123a68bbf14244aa22a70161593b88f95847125a9703e82290916c5b6d65a6e7bae7beae2e07f18ee66c2b611ad1e6dc3a604d75eab65afaa3db40e773219c6183f6e1b3a7a4c7c84c76ed6a6a6ea7a11258aa2288aa11a353659d45ae76ceca2fa824c5f730fab9cd18b0cce32026ee4ebef20d41b5399c1df8ee3388ee3bda1910bafebbae6e4ba0f253f9e4fae992e2e8009865c43155ef3e347f6d8ffd87ad42175116f6ac81a92478fa68bff689251c85f230ab98507017cfdb4fb40ff7f5dcb0eff5d8fb300041924d1852d82a08552047687795c2928a145175f37a7838c530627596373e303d7d1e2aa21a37531e7acb356ecf3c7d35a7b85464411a191a99a53b5aa610d925347505a1508ebaa54dddd1da2636fdb5328582ba20926910f219a481729d191d9796780e763541ea4513e5a2ad1122d8d4a9a66b79e344e04db69d756b56dabda76752ad59c69fab967ebfcf643883815ee8710691d100da94b8b4dfecee78e862d990a25659fb3ced3d11235512a326847d9db133dd9d169644fa3d3a87b85655bd6cd3963b37fb9fb58afb88d82f4823aaaac7553d5c861ee4a4557aa95ea5af14156a9ee6d194f5cd7cde5836a03d7755d97733756e4f999f63df86fa0ff5cef85643006d1e4e01e8e1e40a522c66661f39bd5d92ecf59af8b5bde139ff4095bdec4a1987ecac950fc496a625fd2232715e0d3beb62dd01b8dba4755f491f8d51a02716f840251263455494dc345da8d843811235ae7237519cd1944883ef2d168f4987a4569d6a2c874a6711e001dd3316811fc64568b22bb1645ee0ffc7c9f6cfbcc19ef743a38e2e7b3849a2a52f173823cb96e063d3a4b02eb01d7389c0e2bc8af850cce164e0cdac59190728044132d22c960e8a3223645418aa2288a62ad9d1f4c1ba0c8f33b5a9669dfb1d66e3ee6143e602214f9b85e080f293a72fb7998f6825a76b83f5da5bad7407ee0c7ba2258868b4075f48218d4757e1d9b0ef795abe97c77b8ac6e71f30495fdd168341addfb121a6952e8a9892909a694528a67eba3df4adc05479c2871a2c497cf3452d20ca5826b65328daa9c24a4911494131393c9f484c94d3e254ac95af156cbd432511c984c485a2d8bb24b9a4c319551587346e5e20a5df8dc3e685354455d4ca696a9656ab54c36c8df72fdfcaed4d5c42f158d59b99e78a916148fe9b28a19e3a4542a954a792e219fd4674fd4c55b4e52d99469cea04adc85e2c0549a4a53d932b5544b98584c399b4ca4c96432994cd70a0c20b40199265babaad86d0cc3b02539866f080df15f7f6d13a9a1e44dc6f2f939038446413748327d709236ee79dfc74fe96dadc66168cc0f3537b4cb51ae7a24ee28cbb2eceeeed6b29a39a736b5d63410f7f5c7075bde344dd3b4a7716d730fc7d542d74a69571965c6d7b5d61e31abfdda3d8d5f208caf8b3a6a9cac3975ec1089f0002214d262e9506121d77b6f8dc145fe0ffcd0f4efc075bf7ecdbd5173df723740e045188d829e73ceee401503584b336248dc7b2fbeeebdf7d2eb567bafb5b8de8de534978c4c4a46ec88030799d2c8ac7d77c39165a426c284c9e19ef5c04c5d92df3bfa7e942cfe74b807f381e9f9f370ef0324989e5b92ed20006814a0a8708232423a31b15fe9052253831ee10371f83a1a2c0f6f71407f1ef26bd9c1437efd1ef2ebaebf1607f4d7b2c34f2490beeeecf7a41225914824128944ba3744ea187177afdd3d8634a626c9c964c4f675f12b810411a2dd6ab28cb465abbf6f4eeedf98e47104e2030022b93ef83a34c78300be3a031872f6f87176cf823a3e903e109faf3e8d8fa036b9974b2cd94734943503b5d4faf9d86cdf22feb40de4b5d85246c6fbe914db2abb2bc5ad467bb04b353fb00efbabcbba161bfc6204feebd1bd5a06711797d265d865d865d865d865f8a2a2827d9354f6aac3322ccbb22c4b6b5fca184ca04e27146b0a9395125594d493d309632ce5ca329f33442efc099281c4b4f8449c928373c8669c7d76b91b7d5a32636a6af83e594c27a793a8042b419d5a6082214af04dd2de93b34e18630cabb141dd40ca1778d049e183dab60ddbb0ee31fe1504e6fe911a615b66b047d5da17952f68d6da69a7c51d1b5bd3384204492484d664145d3f2f900f7e4377b70f5b6be0e694c1900acc21b7f75e5c20bd7365ec7018eb27db699526398d27c8f56ac2ca8a528a1208655fc103d58884095648018491342a41c2c1109c71f45e42b4c385765d2fb9862d642b10d7556bbd735a4f167d5517c624d684d32a8a1255123207d5e48ea3aa474a2bbd51a96846158db9c944594959b8062bf2a42e2fc83a8fba786800a50cf6489be8aa010e64e0a655f6e80a3d5aad3b8ee3385e17e785cbc0b6dee762528fc6e76f593e9a61b08690b135c3bec4f45cc084a906a37164afeb3611482881d3a3ab464db9412142134f89d290bbd3a0b134e744067b341aed20f7e0e19edf7882f60ba533d4d3c57638c820a6251d6cdbb6b57a6cdbb66d1dcffbcf4a6470b63e70b2b2fd56aba58282eadce4a03a76c86053065b01711c4d0e387aa8b1b9f121c70f9ece6a73ec8566ad9d765ed8cf8bf1cda670041654221f42d0a0c24a1465ef43081a56c84288487b2166167250240aa2ad6fb801c4e1f3cfe14f83835540a72b420efd10419d3316490f69daadea97698c942fb81ba150180a3b0c851348c04d08019309d38a9950088970088e1234eebd84b073a75a6f46a50b46f7de1fe4fc00ca144d9053349102711f3781918c93e8fa16283214597c6837eeeea57a363e3428a24b850fc0f8c0a9f201111d50aedbc950971c2e0d122a3e70b27a3055d5da9266e4de712ccb1d9f4ff9293f234ea271eebdf7ca9052d3552c91b14506272902df8164361473890c2394423bc4aa6d35ddddf1a9187bf083e149624badb5f45a0ce49f18a3753cdcc5ffab3b505fcdb99da3c0052600030d683b546050426b5c8d754790107c11da71af2b546305061a8b00df9c12c5dd2f56b7680c0de70c28c6d7a45a0330258bd9635bafebba2e989c6f5c1de8e3bb78f8879f86ee91420a84843b0a7d5c3a4ce91e48451cdbcf4e87fbad0b627b6e7bae1b421404f79d0c743e3f7d95ce67117e5af2803a201ca2f6da6f8f3d2b88edb5b7400bee2ca0fdd65151744ca7d0162d411a8496301948e4e11fedeffa9a6538e6873b1ede3474984ab32830bfe811cd4a352bc5eae80f2125d13d275e16de161e0ed6c562b1582c16c65e8e62db5a6a69cb58eb323273a63ffc7692572783657e166152978f0a191742768cfd6554f6de1a342a5582d184c5643585848275a94bd376bd2297ba346738b144c74fcba7a2fd8794431f7dca074d29b207f1cdaec8a539d3ff83bbb80fee6203039386ff8c06e60c77ee060ff7ae07bb74030cec9b9c0e334aa67d373bae86325513bb8175ad2a92c12ea14aa55229c707f740aa9b39c33327d5cea6811f43a1b2ac860c0e3452604c92d9c74c4336b96d5c6419b68d66ce601f27e348c6e29250f03fee36f206c69d4291ddc6fd9cdd3daebfd129cd094992645ed180a13a94a8c5c0d62776d0597d630c04cfd71d0e3232d78533a4858316929648e2e93c264a85802924896173860c0ea4e6311821b5bb975d204e51d92d2832385b94bed8da897da23f421f6d3320cd39ebac5a5649b4da11574b29a55893b06fd29ce990c6ef0fac94c126913e0f88b130580eb30a50c06c8a0893c6942839547cce240559cbb08d524924128944b2f6a563857befbc531445ea7934a48d628be258924183eca1b73c9bf8b1ff74596eac2124113853b7033ff5ebae676022b78c26641f0d046708a70cb7484b5ad2520cdd7075a077832dcb39430cb2add124947a61e2a16c5032039939ef17e2de776df45b88d77879b1c1061a3eb16c510740684d42b69f13294d454585eb171d667ef4c39cc8ff953cc0f96cb0fff8b3b7d1402dfdddca402dddd574495dfc4b20ccdba1bbea5e0b71af4527f93ff1092a7ca08fa6286f5996652903671cc7d32a8a1255123207d5641ce99c18ff77163295a9e99691e971cefcf56559d6cf910cf6785d374a669fcaa8840e7a64c968040008405000831500001800068442a16040302094cdc37a14000d577a3e805e401c48644916c46008a2903186190288018000019299425500c62fcfd202af25a3cede476926b63f2a54c0fc9206d818fc040159cf0a4f6aefc8f07baa91e60ed749d43d2b2075d1742bcd3bb80fa7945c6b03d62f00a582e2d7e383ff660afdfb19659cc6015385c8a50af437bcd7837524f7e494b6b04eac50e64b6d2bfa44c81c8133c6b6b6e73ced05c1a6c24ed55d67c4e793a6a61b9a093193f59769678b8a9688a82c81b1de893b294755e589a146de29e630e9526c64172833fec060d2124d7fa25cf39990a45895a744f0967917d7c338afacc4d92b9f99e2f1427963fb6d07bbcace34e5bdacc0d7fa2d28aa0865e6ecdeb93f5f49cd395c52ea42f19c6a8e88b23f8427b54f294d5953230599815eff254d31dc54b89aec8180f0f077c75465fd946cec4cf1462a94c23935156412b78c13bc3b0c5dceab9ada90ac8ab60db5aefa194ea07c7797fe8537843952fd2c2fcfa626275d37e31da9e4ccf61327d44dabe18340429de298001def786b431a7ce3484191777928e9314ced8158c3ea8e836b83a3ac51886c4e8d470523b04b12c0e8dc2eeecde12a9cc60f7b6ebff74d1b80f0527e7c2677df060728f3455f7a41ec225d68c8fd8579603fa9d283b0b1c37a7e5568a19c1b3586e2830b848d02e99c4dddaca53fc844f1a25075c611859fc4e19206d7511e6e3c024201e88d02ca61076ff5210056b04017c2c22a71ee401e494bd4f7f985fefd0eaa0e459f1b64a5c723c562ea632c58793adabfded5ef5542a57da5716045e64bec5801589356b54dd105eae52abd44ac0d454f65a70cfcdc7553f8844295d833232334965459bcb2bb59dc359b221cc1e6541b33ba8bdd2994ec0e1b34f1aef368c5baf83d472599f6467bc44adc24ae290a6d0b8a8ac76bf3d42a0fc9f668b18bc958f481124b044e07f00618073b1925e58c15c162a9b830bd28f0e61c41b2629abac63472c652f087b0676e3b2d85f830ffd62ac5e22d87846f368b9a357c2249ce95e10fb9080af3a98176a217ff4d3d00a6e4bbc6cb81877c78e0356ccfb560260c3eac23fa2d9664fd88a11e08fdedd7cb6ec92ccd8b522a1d41008cc3c232bb64545679de519890597285d53b2671ffdbcb0dea41897c74d49caf1341b38640217cdb816821984cd168191389176c94cfeb9e17e068949687149e541f57a3f69ca67466f159c142a3af050b1549b8978345f8029501125fd07cc395a18741557f54496a191439f69def98e4ebd420bd64219404b58f3c91af362336cffa5c9eb0e42640bec22829b772c9eede40fd4903d2869df7778f7ecedd5774f4b100e746776a2fdde95ca10a3feb74a75ed01bccbd4516c332926999679fa8951155178a92b9c692a0ff1e3bb012689f538ffd11e70539114f90a13b0da116f6e3c87c8860ea453c91c4518a98921d5c6162d4972b74900ae45fcd8753cc61c5370c9d9e11ded5ad755fe3d0732817759f78221afb637ec7c57e3993e30fd67becbf4ae2db726811d0ddede8ec02734a435f2b192e4c8217c0ec681c956644890b25453d4b6bb720b1302676c43213334d24ad2b627409b327d1bec1ee3f06fcf0cf2811089ca9eb4326473277dc27d2523161c5617e954bca0df6c2a9654d1c3c4ac6719087917a382dc680231256c87b822c5a9e22c8c20d0035e542af3649632d6a533983ce8842a5e13b6482689a6641792c004890c1f19842737664dcdc43b4dc79fe6452dad542b3c48a7ddbf4fd26ec1b3b4be258c89f982de888620b28d7aefb09c469da0dda58e9282b58092d48f7e06fb59753701b436c87777b513159b43ce6aa785751a5b7a13fda5703f485c159a418ef0c1cb98b569e23eb32ad5bd151c5a4f75409c3e966de62d24c66c405825a5caf780d4868827bbb2efe6ef83e3543fada09ee8acd91b92a9b0a426799ed9a2b40bab7a9abc3c5db38cf3fe312c2424237a6ed5cd106d07b71e64f297a4626c30f4afbe78e2325258c0087b639dc9683ef76edc4dde00a1a83b73fcea0e18651830b82d60940785535eb302f5506faa736f3df332b86579a9a462e8880934aefc18270b8aa317628cbd245e60eadf848b366b064eceb7d199d023bf3f16100a7b4247af47512ca48b29a84359047501d7014628363ab2d0c0942d7f851b8cfc5375d3a63dad817a3b8aea4733952284530a9b16fab7827aa40cb3a437d74f5925b64dc5c9ea6ebba9a5c47bc79474b1cbaac42e3d8e96c8eac84225f706d16e113eed66a1b265f84ee753c6521a063f4b09ca749bb03cf7caa3a76c7f69e8b1f1fcfb99dcedbb67f5d3ad68d2390b39759ea25be59ba325308a46b3023a49c08630957c34f3ae33cb446b0b5c9b5e9526978bf55862dcc9afef56b072f2ead393efa23f8971793aca9c6213419acf51fad0cb2b6c17993ac0a27d94827d9e38e3a8725b527595ca7014cc4496c734fe964eddba4eec1b5bc25595712997d68405793ecb08b2cbaaf88c396dc02201d1baad3daa4ceaf59775714fa3cbd06802c95878d0f3431633bf964fe213e9cf56e1cbea79aff2313b56781c3b8dbf7e13aaa0849bfba71b82643208a9f129918a03d868e86987b589aa90fcfec0b7c3c4e4bb58691cb1b64593e2d5d923d4ad3175c5cc0f5173beb928e59f8bb6840abb9f8eafc30ed483b8fdd9d245afff0c2ae7cefa90b6d35de87e1e1ee29d862cd3885499143fb27d970e85722c5c52cbb3ebf78ad6d0e58a9cc5f238cb86d06ddc69ee718b9286c15cb7108c70aba2b3e70a0a23568e62a0943c745060c106e314443933b9a5e18adcfb1ad909891b11eb136071dc8c5507267982866d613a0ad7388ab6c2bc329d5ed826cfc012a76f19c9a3988218e8294067e7c5dbed236ceeb30619a490a95906d44546fb7fd59222729df91a7a510eebbdd71ac0972b124012f970c39b4159582f8f0850e028d6fb234f1244a00d7d4bc171e21ce81ca925d2e2afbd23d4a56eb6a5023d7193fb1546da43da033abc5fb0f2f2bc3f54290fa6d20c8bf6047381407fad8c58c7a92af6d5b0514ae1e97e74670e750a1d0d976830681fcc6e292f87305515ba69d0a87b0f01dd8436d5a65b0d7dddd1c1a4353ac00f33e38446398d9f5744f1deaeeb4e2e164462740eb0d351137a8b3e5d63c9362a538e21928fed3076071d05c96d070ceac4705076eed0a1f266c64a22826c8cd4769cee50b3a53303fc4a5bbd4e210c0d2645487d0fcbd224d63d8a5eb6be5ea590c1893c18a1f5a3c2c68d287343f743bd6d904ea00c0804893ecf34395237e5372b59889ba0c9d1fa2e302d13c2e94a021aa63df3faa3e80bf1eba8f27120e25845ae8358c62998a84a232c5b43d11a4f275bf5aea2ec8c1bab9842ea2614ec2ee030530a52ba9101eab409698f939c7e1206d1aa3199288e860906bf12451fb6a486c9a6c6bbf9bd75fc25246bb6136bbcf4719ebd4212226e7b97ec531c84c31f66904e631171ed4d55c2d0380c682eb5f7b8a837f3b0ccc8dca8ee36381b7f5cc9267ddc4051203afcde2315f9d4f4612538025cee70a924a19ca6620035decde70fbf2ccede068b1ddb27b9edb09e66d03ed5aece4938ac49a8edee848260b224bfd6e7962d97ee60f805d230c8d1dd279911c49c01ba0f7a87b04075784ed76de05730e121c45a9043a483b9a235152f0964b3a2878527cb45ebce69eac7dbb2ad2021d8d3e9178189e9f73a0b671b28a2384421a7aaad69ed778b30e8e077e63a7173e756f25240fbe40ad1e69156ef56843d75efa0742a11efea63ae4d076295fbe6fa481a0417f63b7b1e5eed1b5628f40e3c78686b2a31587c1be4a591bfa28626150f583d0bde714e8ab8c753552a4f6ab55f77010a542cba9d000980deb78a603befa003f74ba29b677d53a72da7eff92310c0428693d5ee7fbe99b32f5d4b6fe01c78a5563afef4336d82e1e2214e2db9389226a958270abe5b7264cf60315f5925058e87bef5637502b2313265f7a7d268c1bb5ada9008fd6c01da67c69980ee4761022d52020a4e2eea3a905fc41783f12068291f85803b834847ab62adc49313ed6f52c87ae37eb8f5204fdb9699cbcd6887dba04511866690ac16199ab8cc8c7b0827dd10608367e664cd177b3758d2d1d3a2bb8e7ae8e62175da57d71b007a244438b459b49ebd2aa139882d373b52acb6852b1bf4fe480a6e351d3a6847ecc255e889ab30701322702ac1f31cbfaa16fdf60526665a88cd1ba9414600720906d97969b2ec9cc8dbea16bb005cdd17ffaa4b2eca181e279fabd82e3734013b7c862760ce382162675d95269bc90612924f1fab6e883ac02697a89a58d4e89f567a4373ebd3bd0bbd279d5d20bde63ff24dc10cb2fe235f7a5c3735f09c3529571ca80b6c9dd9b078fa726bf31555992765c38c5c6480a32da180196656575e9bc1044b9758bce811411fcf7d2f79b2632ba4bb46e4f9c1a949e19f07684770459bc3546a743fc4daed6874e254238b7ec78120514ad4e40858343c960ac18ba84ba168b793da5daa2748615689ed5e1f8d6b3a669b61bb2c0755e472892b60e97f16e0e08557544c43e12ebeff854cdf4a867343e8c32c570b8549879b5f3e323d5efce881b44d5298a23f8d1ad7e026c2df0c58fdbad2a1d1f57286163fd8faf0414fbd9f38be581b686316684b50c2a5fa6a54e2b41462f21a7d503f3aac04aac67c142060662c32aa1273d0dc0f8fb4499947ea15c2d629b125694007e3aabaaad95c1c475def01edc2243c1529e16f86c6bee1cbbcb233b38414f8f800c2ed772f81ea9a89a67405e8db86dce67ac1e266818a31a1dae67ae4f0053697d01f3190302d1de03d40a878cd722ffaaa693647f36581f2b4427c7070a17948e194be98e3eee1b62663f5c03944cd88462c01711e99d943610a93f2351147d0e02f2633a9074efd3295c7f0fb9b3c6c1994c911950e2c2e8170e98f0c85656dcbf80bcda19af36be7951e1d634b7c154a5861e3df779586ad4ac54c34c42e00323b66e1c3f6ce7ae6688a4b7aea13d433bbc017a036ec43ceb13e264e04141a93ddb719f9b3c41cce03eccdadfb9e8ba4ab95dfbd3712054ee8c846cab5f936aab94c202c0a2949f90c9bbb9937ddca230fbdfb10a35fd5b6082487f18eadaa173926a8324017ccf81574eaad92f8ddc70d4585b75e34e82c643d70dae5a300e26ab120bebb5a83c12917324849b9b8f1c5bdf154af4ac99435cb445eb02128417149b91c116081bda59aaf18140ef2bc55207ae028835fa038ca06210bbfb11ac43c50a7657c687a24508b74ed3a693f1a174261006d245692616a5fa3618ef21138a308bc0b946efdd1d8fda64d15e9256b637ad0ef58f0bd7e7a0982f7ef138555a04b57c695924fbc242f4c9acb15afa8416d5284f21424517312199d119dde35c73bd8625e7a54d7d266eeedbadf38b837c9fee88fda35690429069b43fea9282e84d47117a465160878ac87ac67df5bbfa8345a3f86fee172bfff0c0a2184e9914a09b02dc3d5e5f9aca70c70c4db170e68cfa030975008de8a28e2e9b124792d0606fcce338d5e11dc1121afcb516011f4167a9f56430654665c78ce470fa902e53ae49807a477eb9e6fb4e03beea5c03eaa5423744e0ac17d1abc548ce8989854726e764ac4b1c109c490064d77c6ab5d3f9302370a9143a13824f53e13288f9d5b1da91cef3031d3074afd2dbb3cf4a6d7e9c13bae16a0e108297d07e69e248354e6d527f76d9a617475cb14085ac564085c80276dc57d4b6a41031a3feab429dc45e61b3d19abc7582622174e7f81d6337aa97ca672a64d5562679c80d9dccfcb8abb6397cd7f8527c7fbeb5af40ae8e64b4a278b23dfcd0bbb6030ae95940ed9d30f21281fb21abed9ee79ef14c78dceda006d7b6100514d9da46ca952809d4d42f8ae722c9dcbec8f9365eaf22820ac3b907dd7ccff00b164bb74fc3fb8e26ce70e49d8fbd5289cbc4e3198515d2e80ec75eb1f728a6cee7a557ac117f0cd8f189c55134ffe12cd95da9976b9dc750c1b48cbb805f6e6a0e8e928d4cb407eb2da462aa9deb1c0ea86a0a5fbdf9528b5af05241d8519c745ebf80d00271df256a2969e7c6a0be9c8ee8b8bba52ae4f9b12b2c1f7e1f6c6cce1241beec981d945d15d3557a61078c11cf3f229e81f670ab09f7aad3bbc055fb5d2d3d16912d8ab7013d1db96cfcea00e5d10f5c5201aea9dd38d4823f563839eb0e60c862e46d7f228f75acde2270c01685e8994854fe83ee3fbfe42350f4bec63245bdddc83de2640a841670c1e67965d181850347ce1118bc8e69f630e7036f374b6f382e7a6f5d065098bc505d3bf725c4128a4ef7e2a06facb1878d8d2db970ba5d807040a4a4e7d5ebfaf993092dc170719ebfe63cea2f025a64dba471b110192343732696ddef6f38e5fa7e8eac7115758fb971745998cf4e0736073a719a537b3e16d89e999ee0f0f244052adff6fdf2cb6a84b37b71ced93c9915bcaa99f23b01f674cea1600da4f57ed62664d4f120e8da4a1843612b108688b5938086ef15b7e460a15bbc0978e60c7a087725c212cdbb0a062b5d93e5d12792641c4f3bbcc128c8e6ed5ccc0851778ba5861aa36a35bc8fbc4caf891a2fbba244a7d76994c7e7fd1f2cf449613c71b72f16ba0029fb0191e93b45261e64a74832ae3c1ffd1982e5ecb7157024bafd75c1e5812ddc30591b09fd2e72e95fcf45b060c5a2ce914234cb65d1acf974629f98f274ed0903602a73a6391f4f6bf702b8cbd9a18a4ac92b075260403941e4fd136500848f702375294b3b47ef6e78e0499b33e4deb9cb658714d2219cbb93f422d78fa87f17553eba3224871548bf275b646056b6514009fb8a6032fc0e9409750f177df7f303987bdd75cccbab3789b56c794ec7f85e6fb232c4de00a4cd6e03d8ea308b25dc3dee58fa5762708185c82bf88278b96d8e305c9453c5b830103dcee14fd87d1f76da413cb7a8cd30f2e878fa02eae90bac34967f7120a7379d0e0f528db0382a694bfeec64f772e546346ff0170137718ec7d2a33bb4acf155a03f18eac426612784d1ef466f4a258081482589ad54204e5997f3e92c2c1b08190ac78fd80aff472a37ba9731cf50f6c9f56afe1109ad1af84d10bf5ce63ff9c609860912d3b6c06ee193d855de9ef3b41049b757f5ab942cdc0df858e16139267780795e100e04503cf320a1fe06e3a451719a441e40cb9d0e78dad4095f62094a27b7b437232895bc338f5ba0b4938f4cdc406942722940c851e5cfdfc4b93691bd718fd4eaddb71d050fb1c37cce18dc3617782509c7175bd3625fd4b0d9e89606eb7fb2b00a0ed54839804260142eb72a4477fa1c331734e13798affb63729d209edbf4a50c4ca900a98bb4a0f358291ab785632966ba14b57b92ad73e04fcb49e963d07fa266dd8a050cb1d6a95eaa7c18a8c225502e5cf8d4bcccf453f5998aa177e5a5a53130ebfce7c7d7e76ee4c6668412a52a044d6fc987dcc133df96880185254081c738184688e7dfa69fade9376b03d5929143d3e480eb9d3d79c42865384e3f24eca2294d5a8dd6947442cd3ee8c2400cf18127261846273c3362956c65b11a2ad147e658006d611c68fd564d557e73e988ed819ad84c9b5454455adb63458d31bb7d121ecbde3e5e98476d89485bf05aea48ac27112c4c6e1dc68435c69467617ef027f76b7f65ea0cbb34b5c44ca0d8096aeae18832b541f515cb59bb64f6a08f7d8b69314824368fd2f081db48c7ef890c3b7799afa9d2c18201ab1b2202f2e42dc8f672e13f0c9a2be755776c3772181490b98ebe09baaca3d6f4b3a64e93c74803dd2084e34369709fb7f5949c739727af81c1707ff4cdb8dadfc8c00814ae121090c48a9af2bc882e73eb046c1e26b042c46dc3143ad20a78110a0704374546e3884b8640a5b06b57ae60e9600b574d4838281571522703b89a54526930c68a26c3c23f53946535c1d93cb7dc2e21bc772fdb4ab067aa14da4b2719be6416b92c707a27622c55e204b34085a743f77148d6a88972d6c7c3d7b4ba2600a1f59f2b959927ced9917331e3b8e5cf5fb76bf1b2cb3108bee4ff0d0a7d01a882d87e30140c61af2d3212c971f7ce678084e8eef547e0bc65ca54431a0534cff6a4826982847f2b6a8662669b9e48064c6a7e7ae0cc52ae6655518ba5765a7d2c57f4a7c3850dbdb2e5b8f657820f72dc215e34dd75d49c42454afa42b118ba87e195d562e56c346d57d4c2da3f5b094fd492da6b96b41737a2e632500f8d0f67f53414132b3bbf1f17b861de0dbad2b0f1299f402e538ac08b200b901d054222e08f4828b6e9062094cb11f3578de02b4a15db227bd9a111416762043725c2d7a533a518608a0a00ed3608a1cef5b5b0cada9dd7f4474236a6561362710bf3350565a1d7dcef49c17775e161c3a720fdadeb4f45696ab530aad3fd4e190227d0cab95f0ef8553b98f6875b514696f119c0a18c7083336aa63912d980b351313ecc3f66c27a4e61462ee23a0a7303be455d81a6b5140fafcc1a71acb192b30a80b3dbb29d737b67a2238e9c7d8fe553028ebc9726f6391c81a88f33a83dc9e372dae6cfd756ac34336d1cec50485f887843086f90d11b187d6fe9e68062a59a86e14f646e01156911e652cf54cce7ec15d98bcb68b0402c29ef3f8b2ae24669698192896d29aea2beaed1fff9c63aab186ebd93b4557db92ba813bdc8edddcbec07112583da2333f62e6bb075bc12c88f7c87366230b7ee84a3351ffe7952f8c03a0affd90a56745a28dd8f3cabd53a33b7d0e5029fc6af0a8f8168b1fd4364b1c5e2ab7fc0a94a2b4bcc7904e3bb3f58d139c2dc8c5e92a334222ec27a10a1cee06c18a2bdefbde33645c916d10ecef3e38994ec8a8494c2aabe934a70cda2b8f31e1649082252aebc457394d42cbfd0983ff21625ae9a7004aea66aa6c3b6b70817d1bffa9e810ea5599461276c1afc51694fb9357da37fd556fd96de313731a7e92ae2446715380694f9f8f3a652b8f5cda16db33442f575c54c7f42ef0da60bbc456c8a5323a8c410d10b8a134a49954a0c1a6d7cbebe91463756f0a526aa7b369438a8a6f9c8110a68433f6c35097176ffa01fb59786f85ef4b2694518afa5813668c5222faffad826b97ec92c8b0739989096e970b366a2b2fefde632f3e199cf492833be9a54411d9d78c536022ce502ac1deeabbd86a49682275b51ba0e797c18fe92708457230ce35cac66d4701dd7ffcdeb316fe4522f27a3e099653808749ad7d87c7518fa02fee83c10e1067057961f5a4725127e11a210ff99d0f9065629632f4fc0e82ca94107886a2f40a37731c45cf22c160962f3d59129142d642263f280a9571fd1127679786cfc51b9ff4b9f25f0d3da7b2294c84f3024376cc0932069de82f3141823447403e555fa6e04d6511272cf0d2228c98a2749be9ccd2c0292d060e4f2012278271f012e2c32b652360bf6e4701fe2b84adce4a3078dcafc8cb8474215346293afe42439bf02cc3d0da7d92979283d6c8f1370c68f286441858c61fb266776eac6f8dcf2c36755a16f7801bdbc9bac2d4492e6ebf76efe88df38f0d3b87e8c4a3999236902fe525b2db33e36e666635380697508fb3aea391b8978c6aa6585a3b3201beed8a582167e97a90ac9dbc75a6513902f9cc9c80216ab400eaf1e7433c306129641faec246b2826fb711a2b0f5a0d7da4bf0a249e5c8ebdec98ba026563e0b81ee30ba846e42d01be1374026089fa1e58a7f3900c1d04e11d15307900b4c784a3fda73cc3bd8ea1eb81bb8b232613c6fe6344627336b5431e11041e9b5dcfca3955c858101333642a04fb71b99172035d8070b5315a918c639043305f214c083388abd9900203e62c52c9c8c3382c276492b971746b4427865414beca5f1e2610ed5a27e9aa4b453083c5652ef161cc04f1d49c3b6ff5f40444af49ebc71e15de033589e68c5b982e1beab107b85793864a084eb38182c94d0ca268cb4d59c54d93be162f6fa4025baf51eca72dade4d00684ccb84fdee4d5d6ee3e7c85cdf13bc1ec699dfa0018a6e3bced9868757a34e860bb38475586ccb3177fda5b6fdc859b5600580b531706db503f2bf55a7416289fed1c5e877baa0f37871be7c09c0d0ef7b7ceb1301920bd49902f834f0ffabaa2611f08230fb4d2e7fe6e075c0a1765e753b899b6bb6a5c5a779754369f7e4ed93ba8bb5bd4cf7194b9580d8ddb60ce85da4e0bfb63faccb5d4778750f2eedb84bf80c3616f1f8542bea8fdbc22eb978e4a030819d8d705c0c4aaf12ba3f1abe4ea7316bb8a3f94d0e694fce11cf6c935530474e099db288d3fe924b72e99f64aefc04314e637a57454e930b43060d314e992b58148d3fea48cf94663b07f71ab7f1255924d261158c472bf0dcc1630a642afbd931ab7d19d340c9344aa8f4daa86be9bb031a1ba52f730573028c50dd95fe8e7447d39832e3cc2d5aa740196573a802b4b9b70313b173f0d503bad24f0fbd689e06a401fb7771ef79d80d88dc4bd36bd243a4dc198646261176094746ad89fa779a74dbfd518c23d95335cc6b5299e0d6467788452cd09106a3bf62e537faca88892f228851266b1c6343248853388634e9a733005a145c6d46ce45f1921a120864c79a9e5c4ff0a26603e681341c3bb25bbc16fcce7fd242a904b81271a8b07c4c75da1fc39925d9baaaa8575286035fe5d24571ae28c0af6abb6c96171e6bfd9c4ab18527bb2d018c6efac2764855d70044cc800b9ede4b6b7ec38ef28738a21933bcaa25d105dedc687e8b8189650eec4f5d1db1dd32ff0ecaaabcd16caf445bf47d88261c2f869394c525e10a758e30999ea6eae752dc4917c6d1deec71265482f4626aff6845f47be3527caceb76c1153b235776be5e6d2a4a4ae63c21000d750c548477260fe0891eb1b9e9f1bff3bd9de9ad952f1b4fc3cb47d12d2dd4da2e585fa095755e56e2c9b6aa40573bfbbc6f1a0a34871e358454c5c9faa2908d4b50bb10f403f1e381b35c9ebb39d00aaf58374b4917498563dd488e341b7c0aea43de8f160de4cb5c2a2883998f6668a48e9e21d96d04f8f07311f34efa84c37040e92591d9feb84c23f32a7d7ec04f35872b51a8438a9753ce82171e73e0000f2d7beb6f236f2ccac0f548b1b06b4a254df3508dbf3e37aad35604118eb601c0f4a2296ed132268a9b805be5a5851cb76970ee3385bcffde98f07bd7a968552abe772ed5dcbd45946dcc5f6cd302ed6b8570a31f7d356e323f12f77e24141f7fe9c76d60dbaac3683013e17af1318282172ea45f93eda84fb338432f4cc5d488ccfaac24c6eb676c8be18f9a2ddad0c7c02bd7f118d7d49c669fad6fae566fca49f9d9adb279d4b1e6333ff0610e1e253d632e83437d359ecec21d3de502f2fe217923327761663d93a7c80ca6c2ce5993b71088ad7652650605e8d982beb012c9159c317839d61d64a9be669f10e85d98d582c2c36af66b5bdcadf66ea4de334682d559be1555348dfe01e8ca584793797476d32eba446061bc900a050bbd3676c3974934f974a10d3dafe8630fec658cb812e00bf7b81586474b0e25f3309bb501781506e676da8086f2924888cbb7ffbbbb9df2333ae069acbc92f069e56e916ba901b6e9d82067a262d6700f085dc5a802dbe0e99697723cccc2e77526c26f15b34efd455aa20374acc9b6114c6a64e36809af56de7f30e72e3c1b27f92f1ffa46ab97bd003f0ea39ee3e75263c072aa9b567572814d68a0671fe698c82ec6003f6dee5943232214b76d49e39151f3069e13c8a378baf3b4d1d08e0fcd63e09b8c333703669bb65250270f60642dcc63b0eb78bec38955cac4db336834fdda5a419e9352c371372cec4f59928b60ee5fa87080c7458df60f82162acd52c531970f0e3a8394d1fd6ad860397afc96c67c400ca3f73f869f892af4b1a046ee5ab321e1f65b650a76e2ab4ef6ad459b23600150b76b800d6d88d3b36ef11dbb710024165580b33fbc125f073b4e6462a4d2b1a58f4ac0315a3b5709f7624e63a3dd1ba468739dc167c3887b3a9ddbc456e12853aaf91cfc9e2a2259cacf0c5794b6ed9f3962840f5418c6b772cf0724b6d6e3c3da2ac764d404d5e308313cd56ee1f455c5f79738b885cd5da77ad4a3e8e2b33995aef7c456d0a7f5792f19c840e0fd9b67810e2f52d7f503870003c3cf4d7508d50e2b6c55e5b93849ec62b24bc929aa822e139c9b57a6df12b4818a15622cf075f32e9eb4952e6bb9510c109e4608ff6931025cd02bd96d534175587fc4d3e281ec845f68dc2aa060e94465c30bac79c2f8bab7cd2b096e3b2ba66c8f9b18ea34813e2e172bd55d47ef937194cef571262da86eb9ab5b32d1a317e196499a093181a8ffea317296b928df806c3f6b39d64ad6fb8fc8a78b16b1da3275f280f95b13021ea4740c91bc8dad97ad2fbb7b6b2787745aa5a46106c7edae4f062cf1254e8435b535f477dc9f150e5488c20d88f0473199146c83405bc8b5507814d8811dd049c4430bdffba1dc7727fd5a25d5cc547b4b36d52a4b4cb2354dce4100ccad5b59eeb53617671a9c6bedb62c6ebd901743dd9061a73286ca38b830deffa0166562a5b61fccbba29db1056722ee328bc16b77912e85b3d807c41b0d4956d295c6698e070645823240216abce102d212b3ce13a24ba87c5ddd6ccbcf6238743943edcecfd462828d7c2ebd3be4af91e738c60686a334beae5266079160d4aaea3cf278132342a6909cec0090eac12b2eb41bace7b602b7f4e23a7eef8ced7ce0996b3b9891bb51ad6f799985d6850c0225452228c159eec8056261ef2ad00d85bd12ae2b9c5e2642bb2606f092c714ee3c9cf04ae1e4af59d0e0213975ff9c2de2281884701a08c2806e3e876851db50256794bbe278afce23a36d95b7474a3dc80a0f7b6f64ba8d3e82f15fe2ed04432f147e9180561ee837ffec21a1a13bf5e304d39bfbcc6b0c4aaa71b315a4a0f7cd437b090cc280ec7d860fc7e6ee35315ebb906138ec05457f98b36530933e1d391c424943dec3701995ac57221b8a2fcc90063d8a8d12402561abfe833083b300c6bef70d9d53d1b79eef8ad7a91b95e7f1d810071fc6dc24d44f18e345bd4882f30d4d186a09aa3cc945293c33cdd60448b93432aaf85651dade7d436ba4f0e77a8bd038eddbabe862f7818be49d9816b3316eb08e47aaa6ad756fcda54138663c37475ef6184ac38125863b4066babe67e3759b7523fd551df3073cdaa10e6aac83c075303947d746e0bbe7c0d73ff5844b7d15a45e834601395651ff71c1fc2da244af3131809795685e3d11297b38f7b5ccb9b5663cc2aeea5664959d8d65bf9daddb1b2ddcdd87b2895b241a80e22b326d3563c80e56a769087346bbb94a9fd96c989f158fbe28377ea9f6b55684b4fbabbd525399ee01a086e08ff5483f33ecd062dc32b54220315d99098901ca28b3403e0f89a07a893d67278a02bd865366f915f1dda5a7b2c71227c5314a1bc59e8dfb2aafcf87e07433d25c54e9b95a526067e95fc40b6a6fe4bdfeb78f4606c5ac4ce2d1becc716dc2c987fb919d7cdc25d395f2b73d54c4cb6fae1351b928086be2694bbb87e244bfbfe15362386db0016d7ab02864a3ca2aacce0e98015cbefeb3a773e7ffeacc296df5c579b3d0f07052a1b9f48aee5f12ebda73f79b95031bd48f223c4b638f8d4eee87fb1ff9cfb295f2977cf8f269f9f93109573ff27ce7e99b8bb31d79d9ed602181829515d135df8744601e8179f043a86fd6cc057b06fef9af34ba30168e46e553c5b3e0fcdf9921db8a1a419f6c7babd0edad46408b9b270e863a32cf896b3a1ac7f25992fdde4579bf7e95ee1cb161aeaff8af849c32ef86009005829537f0a521ca54ca16886c3fba55476b23068d7a24bff95c6660dacea458e1006fcc4197d6b8bbd20b77f6b14bdfa37c5c495a6c5b0374f8bab7622b56039fdab072030eda1ce03d6a1330e0d33e0046d4a70667c273a5c61af4f0b193b896c5fb99a68b26f0a7b442001ab48c9e5fc04464eeb386d3947db3e14a2c38c7a25186e75330c5a816c210b3c65b43a6001f358bc0c6d8406832c32ad5a3002fc53b65f49ba405240e31b314f5aa1fa4dc9ee366fa7e0c4b4c5680672a4b709da37d2c9448ac1b8a317428590dc47429ae841e25d718682497dadc2044cdc57540a0c67e6c0f96033ad7ed71b48bc524a129c0a4e0735a397cf0e51e5765f56279c2a72d0dd1d59247f8abc64ca5d0519a691b710c8d5516f27b1cc6930e99243c1971c65d27414a9c34579269eb13b7ebc4e999ee7cdc39dcd3ba037af9eda42ddbcd9d882d2e816f85b6e7bea388e452b75b7841dd859f25219d08f479dde74cec802e86c81624cc7229137d0979b3ee3342fb7f949b9ae9fe467489b7b756bed08764e7221da1abec729c513bd42693470f36f84c8205b9e793515d70b8618bd0398b6ec7599ecd8c4995e481770d848d81b6a1d1384646521155f28833fba10cef0e603ab91a2ee3e0e0116b3a7685f474d41122cc947b33199d6262ad28b06047715a68ef2c94b329b38ce8d9e428e894c0197cc66e05ef783eaece0221a2437bf60084749f82e3fccf9e779e129c49f8ec1624134fae6852f8bebb967691ab790385e6c3aa4a35b88b9fb1c6d167b26a43e6d10c3ead4a79a5eaf8fb68903bfc5165311637b894a16a9c34eb438170f69ca781d6885d6a09bbf8bf0dab88a38a34f75d0f427d33b3d2a796156bc3929b30efbc1c1e06e226cebf61795a52104f5f95d470534bd0bc28bd19457a79a6649c7625820554d2ab695f80ca0c708f0cfd0e7d59515b81bfd9b0758fbd2b9d7bc1b59e764466416c7ac1cfa87320eafb891d73ac579d984a3a6d41d82e671ffff9221b7c57edcf2187bb0b2db16e0e445722a1dca40c78154dbcf7536cb2527d87f4d3b56604b63640b6b3978bd10af4ce70d0608c4c66c8d6260c8c802e26c4df85291528d6a0c5097424aa31a680df5ec9a41070144f342d30c5d8a4148283cfe5fd74a4a5be354baba5e725078d7d563fb4939b36681cba48a2ff5ba201ae93347547f696b5820b66e436c0c2731d895458878eda0d6d6e440d60ae1dd3a5d34e824b9cfa96dcd69e8329237df73cc6d8be2becee34b5d7edc53c9503a44138031a9be24f935b8e3defe95a8abb6540499a91385d55545dee69321fc1272d08e8ff0b8fe0a17e653f0254aa1f7fa368c6d4d5de4f29dfa8ff4eb976df7b8f554a571b34aa371be258ac0bd21078f044ab5154c9aa10ed56668627ce492905a42feedbcf234443820730740fb1ca9993869643364dbf4b4c6ff4d92bee72e44fcbeccb7f731f85d23195fe8015f0b5816b8290fb37505751b8868c5365152005b97705289c65e7315b5b69f76193c4f6dbb69fec77be5f305bf426917c0249982589d14a620c0685b22191c4a6365d17b8f92b7d3ca72c35f42ce9af50f627b0029ade625b103523a367a696014f20ded1862fe0e33ccb19092868b48ebb4ad43969951db46390c6940d8c8e80d74367915a7780044ac04df259b3db4c7dbeba6e6f56dc61edc215b69b283639aa5981de21d14b8a62599a0e59538442442b2235b090a1c5989d4687fcc93b9eb0c22ce8207fb6a0389ba0870738319a125d6326332756fe54bfd33f9973c0c35e91490469c317536ad3d848c878711dc1e52952ee89b32f939596fadcbd27145407313520dbb32e13c5229e35f9b857cd3d4604a8bbe63cd4cfe9838774a5d3a72063812d1b66e9fbf1591ea449964d4fd451e92f5daa1a5fd6e6e48a21a4814247ee3ed4e763511169e06f9c197fe00ab271b234f27f993c0c8011d1401b3c3db4754ce7a3ec6be141f91efc5a2de8ddbba7804f99987ac9cc8da33607dc479de58405843f2ae6ebc8533efd97282c32de3de3e835afa38d634d1aa87d76a9ebb0801e421959959cc34efa54b3cca73927fcf653b56de764a20564f4feea9a7a7c2603fb351a5934b33c0a278a49cf94a7822a52b35c4d70899e6d1a0e7b384553f4416387808b62bf3766ce0eedf077fa364347b031024044b52034989abc811bf4126830bd443ef833e5d92f47b1ba082a6232e46cd3ed6dde994c71ab76827e18550cee24921d0f157683a711cd58ef8afd3b27bca1f97afdec1eeb2ee009d895ea9dcbbc99c3657c7328ddb9258c1a347edf58200739425ab30d9c1ae1ff84bf291e616f5b16198da5f74ef9c1aaf881ab447b549e7ab1486cbefe1ca097e79ef0b868d7a501c64c178a7756f7547286aa0dedad17b2bf1c4d8608588dea2b765f6074d57e54dc95030e1a8601650ac749c9395b254de1a79833ee153339322bf006b01e7a43a4b15441857e808593d775c7a85587b56937194434a089469f741a236258cb04a3d5dfea4079e9fcb2711a1dfe68c3bc22d150fb499aaf431056d65079029454728b086999864e2e7fd1f968f72d37ce77601a727dd182715de56e0b79fbc661e66c289b0ad74037a5e536958fc95eaf2c36ea6504917550966164b5ebe2e668ce265135ac4b5c949f9fccaddee62f3d9e5905a91abf536bcc029b51dbd38f8e01cdd3c414975086a2124e23ccbb19818680b2d896a750999a9b34b826e45b95977c74981cd0fa9a64bda3fcd5ca96042f914493b8c8172f4e6558594275025dbc90509b84c5580809d22cbc017b9f923bd1c71b845a928bd91ebe31a50af6e3a9f38d24086e0f3d00688c88cf8b4ad315b6202ee7cb25674e5823ee5f269c7c281bcb207ac90438a56ec79a5cdbce21861819b798c98d326da3c06644a4870ed890150fa72fc24067cc40d02d856ce689b315b63d1c92a1619fbd9022bee6a94e88c25209fe68446a40b229005f70a4e45b509bc78fcabf4b0995ccd9e46d9058ee6e0acf26349a9dc36324903bfc6001880736541f402c5d40350d0e331b822d84ecf43295e28bc7ccebd75d1542a6b137bee79bf053816251ce6ce89883b1918e6f879f1a51324801c53b3edfeb759a41730f928ce53d70eb3a01608931723075b9030503eb38297d38f3b20a444952e7c4054a0971ba4cf911ccc8f3abfa0dff23fd170e0cf0ddab25f5ce308178be9678a6f7aaee4b1ff45bbffd4e1fd85d9fe92cbebb576bf28851cff3924e5ebff27c1fd184c37d64fe767f71f492abaaf7fc96ac23cb479ad16c5bdb4019f4c84fdf17fd2ef546278f4c27f937a7c1bf053772a53be0e7886572a930ba9a9cc55be9190a61a23b51486c4e6c901dc17a6eb4e7626fa93a7051e22163f8d34ff8cb2f151fe5879fc8ec3f09fffd50510b52cdad8cad8f5d317614a36048f3391ce800c49bd09bb91e90f47091f96fd59c1a5a7dbf47ef884e14bcc756f59815555016fa8cc0dc3fdf9de32af5f64ee14ff57aee0a26cefd51abfea360c8984add837d82b450da5cb2cea355f7fe47b63fb196b8bfec78be2756ce9eec2cae5802662eca7f98303388efdb864fd1640f6273d59cf1f32e1d81fcdfeb30e66b9cd800f5a33eb1f75849a41fe574d2c5f0d4e0cfbbbd35186c238f2a7f064367c10594038be2e51cc942037a7c5f2b1a6866239920277ba1a65e7a60c4a23912883c430c41a09d8e62aa53795cea2cfb61624c836fb879a9574ac9ef68bb7fd00b5f3ca7f94766e9c9a7e225502fab91b99e0b8ed631c1ceb84ddb012b8e93836fbf77fbd56001b1f8b18b61a43872df64e6a94bb90c13e32541878b96ffcad4a8ea41924ef319c0a65e6e068b7317be246a2d1744682bed142da46dfcf04fc1656829a733f092bec40124f0027eea838a5409fb4e1fc1b8e1fc245973fa6c53fb9a9661b0c0f9e1ff7536a92488d09ffa3c1b5a1f0a4cc7fe88ab0960d04b17b4d8f79bde38f0b2b24f1ddf30a3fd75b853651b4e73da1c840a1f0fee536fbabb213292ae7b1ed40dad312024338167b60528609f15457ae7b6941e8ab01302c72f6db288ce77f7161eb35dcf89bc84d70d36ee6cdd0ebf1c6ed077bf5caba58ea497aff1d9b3bc4cc9802a7b4ccf2f125482d6e0d1c6c4cd9b43fcae01f82c0562a8fc3be3fc2e534e939fcf2aeaf4aff5b0cb7aaea70a5c1480de76e00c2b35b3a205851ff1265490d126f0df0614250de7dd3bee49b0b39e673f2e4a62cbd0dea0e1f0421cc345f63afc80dbc51d1901135342f71aa1d2d5862118c869d050726d2ee09fef3e1384078a916accdbdc7fac1bc9e4042fa26fb9fd80551a75c67bc8b28c6db6072d070f54dbbfcf7bc826fd172fb42ca356cb4c4a9761107e0214e95cc482d756a342d66b4b6618d976349b25b263d2ec79b53c7d439cb1bbf8a68d0bb0ad484246f111a908c2be6adf43360fc50e093c311deeaec42e0c5818aafccc6c815d3244d88999daa4257510c34b43eeebb2a608b674e2b3bc4c38ccf2659fd923c0ebda89cb2d767b7eb9dc727a4899a605b7f5722bb8882b280ae252dfe0a925dd002ea5db098bdc349b2d9a57d21210eb44c4f9961c7f89d136fb095f9e0cf685788fed0e2202cf27780eeac7aedecbd2568d63627683ace9f5b6c69d8391a6a2b61d18c967a9c050ebc93950dbff161e59cd6f843de895d14f302161659c0018c49e36cb7bd39bb81a8eeabbc06540c5933db06a557a904a3f9c56d9d0786e4118e1b4b53eca846ae0212f930d863b8738af580067a3ee5b6c6ef1a64c1c27215324817ae8ae9edd62d0af33f031de707c06431e6e2de6f00edd47958444e5e7e7de3da0349f4670a6aa1f38ed2300ac7aa606d111080b2695042cc0f434870bd3bf710cb5255fb557f71ac6957f04684ad49ac63ae19987e04fd0981f4930dfafa236c39610edfc18040e8fbd7afe72dfe70a04597c197fb41d998c8449dfef34e650562586b6d09a1cf9610559311b157de674834e0b051f0faa74b196d14074e5d78b5ac6fb2286efa45a9a1d1189ed4573fec3323dd514af5d0f5de4e4affc147d2af527a69fce7b5376a90a4d86678ca62e56f5cb250fe3796fe02ae90c4865677b9874c40ace6bc288daf987dea8c841964cc37dabac6b5885e52234433dd6b00525f3dfea2beebc068bff9f6fba915231bc603b87d7a227dece951b89e2c2057babef5c57559efa59b2f7677e183d53e718e978ca1df9137daf4359a884937b40596392364a48fa217255d2180817fea2d3eee3352533e66b8c561aac40a0fb33c0044280c7059dc11cd783222a481a0cc3cd85d985b13ac204b790a357a845fd1502e9ff07ad4c1b51885e1a2a374748c00af687576219f57b49e8521f7e46055b6d1ca53111570d217560686c50c58615b78c04c1e217d69cf5d5966d6a6fc20a0b5ad2d5c541750704a9a072628af962b36de467b563026438d740341e4d359759609402c1935ad60eaf6f99d06b02f8781af16b0cd381e06f58745309c882490170d5f546aa9221348173440a0a8f755f335ddfb2680a1b6975d95fee57ae0990c4714abcf53f2bf79bd03e8a8e8b09489dc474a958b10e62c9992caa0a5e96853d7f2c490eff718273224720119c453ae3cf13bb8b151dae622a1406ec6d2b00913c26423f6d3ad5f16ea1f1940363aaae7c989742b8530d7ebfc07c6cbee8dfb1d7911c91f1b18e570dec5182dde7846af6ffd200231b46629dc0b791cc674c0568c9e4ea6b8f86fe1ec81ef7ce5dfd505b78d36764fbd5d17cc2e357f95e38e885c71d3c954f17dd6209ca7d83975b9fbeed37dfc13966708922ff9e13ceed8baf7881c543790cb87f5d2f80643703c35c84351cec52905dffe9d645f08bede8eac7b3377119700dcbecfe8850b0d07b1a710a90190b66965916bace8ed1d34855886ae2e0b0ec88e0f50f61730a87d8a062fcd810156c26a3611373454756110286255659fe2515023ac282c454b70f80fe0629c2a5353b1802d35e33e619e4e4c06ff81420ba10bb08d31ba1ba20e252ee90ae239cbeed027d4af5f06cb7482ccf30bc254b19ebcacad8b580c44206ed0ececa6682e8f7ad65e77a43c663103b3dc23cfdcda54e48de000473fe9d6cecec9bf20dcd05e7b32b3ac8b71d522029809e537f61b796e185dc8d63e700f03c07e997dda89672a6e4f6eaf9058f0fb884ad6a6d3b8bef13f6fb76da64c2c90233b4455769bb06b6f3b7a91dfd753c986ff6157f6a7e653d96fd135e32b39aadab067499268eb8650fec8dc0261412928500763aaab80224dd009827262106a2c900a6273845a42bd3d07c5cf9f21138a82eb3b7b2be3b7c264ce752edaa35d423eb374da021dc2b617f189aaec1a368202bf297fbb6011ff5c0157e65cda8750b7e3adf2536720b8cb94419bca4a4ac02de87fc9cc95c1f64df8f02ea3a05e8fc19f85d63cfda3c9257231064f9979553bf202ed6582d1dcf75d8ab168ecaa3860cbc08f1f188a72fc24754cbd51fb5fac4f5bda78107b25d034480fa370074ce14c2f98e31a6340372b6a85b2dd7d1c10b65872871a8c89bb5b6d6291f1ee8c83118fcf4c6876d2cf246139e3c829f00c64132065c272dd26f5ac72cece4568586c93564d70375fc7d510ddb0e60f86a1f0adf11ff636dc17f622a87faa8226278d1085022421aaee77122a234588227dc00dbbe8c6066c38cee9bda5a9e93c8593b917ca05a114fc24e03e5775ec6095f9036aadd1e386bb00e4272617657d12658342e3426bed1b2278c556ded8d22ac052084371a8558bc01933caa1188ad95843ae00892f1479aca1f8e0c1f94654fe6a8e966fb04ad38c19ae7398c83f0b65d8d28d656215ab5ec576a3b6082e9125ca956472edcd6a12502db97b0bba51b225cbfc2e490961f3f3fed7880fa08b59aa3175cb616083a360c89279efbf5166a5c151d8d7993aa16adc259eaaef8c919bb1881ac1a2c0896bcaa74d5b72bb06706a6ecabaf198a65b58b72209d2378b9bea7a78b779289c2ec7254d75c3544a945e6d79cb32f42bb8a4f0b00434151163a3a4d593cbbc6426d369380a067151dc55c3c44113fe7b1e8da9612806087161ec3c75d206ea41feefa987d925e7de71b401fa6bbade669e311a6bbcef828393d468030486ad9d006611fa4aabdbb8abe9ba4aaeb9847216303cda8076fbd851cb33055574b202204a0c3c2c487b8dcd06b37c294674a88fc99b45c6469ab17d8585d2d106407b08eda019babdd79f360aab765d93f407d2f46c367854779e40c571bb6e430fe9930201960b2bfc186d20616637db5bc8be8be444fb7c952140884398ca66b1e00319a88c4f6b36451b2c899e79d3f4a8c5d5eafd4156c28b2ea970200342957ad083f86c10fb690b3b74464bbdfee0b301fbe685ddd420971c29401349a7a5e61a7472f3e2e6d676ef9ebbe86ee1cb50fde19a33ea892cc59b4222cad73022a4a917b128a7bd6b60622979bb85b2556ad77a382574afbcf15e4265c35577811ee9148a5989c3870370ef610b364f21914145a5122ddeb6f87950dd87e3c3ab69dc869e68481ae43b4fd0b36868e06a391770e823e6ba442b8b8453d80d2c7088bd6aa039971a91da348582c4e51fa7eaaa558a09d72d0cb232b291826038f626a44e4d0463751c787c021f6f0ad2dae04ca9cd1aa4d2466026c8622499ef7346d7fccbe7490f2d9df3442051dac959c7d784330b081fd3736317d2cb69ccfaa5f43e554e348a07c7a095eb4101278aae78b6991289f71e3a2357e21c2fc2e0e793a206011848c528fb79c64f53f219d9a6c5b81a1650908ffaa9dc7e9d7ac07c647aa10b15a8fd9c31be18ce4aca22f00499341521cf195c24a79cd18a2afb1a332650ab7802d8d1fe5baf5f42471019d50091b5ff0592954c789db462c19ac469a16ee40393b308b58e6f35104913ac56f5bf340e7a4d0dbae945c56cb7e0e1d9566febe127029447e204513d76f5fdc4a470be7917d9e972f0e2afd2430052b0a9f4a2dd4669f739f8edf1917bcea9c4ed0079fc420fc25f83879e54cfe90781411004cc33676ef018a598ccd600acbe051a3e1093ea39ee4d1fef0718bf9540164a5be16a1d48fb887b09f902676f91636cc4d9ee83a4ce38032c88dfa74628a7b1251732d4ccee2cd4ccb1ccdd36111bcee342406ea787c79ce397508e5fb2503af2b040a824356724e30a502179bb8c6a336c3371d4fc658935d4181f5c1a793c8f89eb10cf53ba5385894efc739781f1cc15a12f910dd0cb5fe0430a9ad4ad9e4569ac71aef71079484e34082b2df19e760f2a821145f0f7048d6f4146aa939984803a547d77255fac21e1c2ac6665f153400335f81a60630f396225f3e2c7465cfd14e6760b20e9d6edaf1bc6705f4cc8cf575e2c511dcfb48c8297d7e946c67bdbdffa73a0207c97c0ab0e262cb5a7c0b733a4bd8f326439cd44a81de72989a25a3d10853f3fbfdd1650b725039e2c57eac1650f9b10cc4473dc3a04ae8f4c4dfbb087ea1c1308b2e0fd02625aa1b514395d709b3aecae19c0c31c5d4c0283126e959efa474bac198aec8a1a6c53052b04857520e8bdbce80c7f34beab607e660bca8bb97ff423e8d1dc7537b1ffb2fe2a115d15c5bdd556cb94fe4475856c94c9e296ec990a240a0d331fd0536d0722cebe172aac5afaf47f25e366b9718e26bf79987f9202a9c829c20745c35d6c2e852c984ab202a6d556dcbc04de62c74ea6ab2212241bafdce3345136362a721c1c5c2019b6718b0a7e736e5acc00333d014d7ad6a56d040afde47d361f19c93947b37c97e24d47655da3327a9f594e46c9f43c58991718232130a546c03cc0c81ac3b12e2361222ec87ae6b40b951a126ee409e849cf57c5d66555399ebe38e0b95e8b2020b79363c5fbdf73e0ddf2d0f86165ed56acad1c83ffb6a1192ce5941e28d3270113c5100fa50a1ce683430c4301531c6e7b1de339a102f4b0e6b6db4f23b16b3fae68f4426b151783312d294e22f330dcedba593582e6af622e1fa298f97de5d2f9be1593b7786e33ecdc2a6ea96b036d7e882fd37e39681fef2b7c0ca90f464bff5e2a1933be6d8547859e972b94957afd0d0695ed206fe0b75fab9f04cc21ec242fb26c8c09024949feba9f5daf4012e25b6deeb4f25ef105f223b0c7d71fbfca6d43f5663f25c5ccf86c15670b0434c33bd77cf31563bbc19106981a6557d2e29088d67c468b07edd397aa99b1a6424687092c6ae79045722b4215280d7ac5f07cb1180fa0c91404b1ffd0c0dfcf0b41d1dfbdb1dc6e2575ad61f401f1f71827f541f9cac859a20b6e699450c1813eea20903b01bb8c29c003e2a4fee05091ca637f9e1fad0ed0227512a2a36bd7e63aa3f66b388bb52611d2e246c9cb591142f0373b06edf2c9fd78b7978f6783a1a7f090bf18bc5fad13bccfdc237bbf7cf2512431cc7c5430f313f0fd7d4884598b9cb2f8632099dfa4836a4a99296f0b8a347754e78c3e08ca9216a0423366b4e3067e76da5298f5aca1827f1d4bad6f03fa2db26c1c2349f32c0ba210bbcf835823c8c420880a0c8ab1b4824158d21b1cccb65b6167bd85c21dda6e3e50bdbccd0888b10144041e8674e3036d6a178b31d18ec327ee9a943954383479c93fc21a8fb543065b21654a118c13cc0f2c34c7889a633f4904931d5ae0d2405b973605be9cd9fadd91536d990f5974d7970239065addd0db7ac20f966b58c0cdc9f814d94e6ad31580f27424b7189e1d3b7976e03d52238ff767396e1c4f92966747657b04611fd9d63fe2e0811485ce948c095fa66ae95417786cb8a647c6074dde996e4f96d5200fa995f495de60bbd321bfd2ae379cda28fc82506666f15e7672fcf57298bd7c4b90f62d4ff8c2c8ba5ff6d285714264c6a6821324f29d794f9c40a74abb0bad2045c327a35cac25c12a6f60f64e035be2b0b92fa2ba60f9e53e183229f99d0b399168174bee85f673526349e43440e568724b72eb475515c35feecc644627f181723da4c27aea1a9d9eac2474b71bd45675dc744506bfab468cf8a0dca0b2b01c517c37ccef474b744b3baf5cc8658444a1c87c849f550b08a2612abaacbfee646a1bfb6a41759b1a0b44c0de52b38311ac897d613c277d5efcd100404515274294d4e93d9c1da388c9def1ba4b5fb66c18451f11eff5a5e4b25f62257a84f12aca9a08de493aadde18408532f7646c6bc75071a17773b2963e8863a8ad04f10f1cbb87670858c9773ef6195aadc4062e7f996f310e0f8a4d4aec1a157e4fba89a2646b5ecd292a75c1106a549dd58f3835089d5b00e074a822f49b00a10753b5bfff2b12d574d4e31daa9faf8a9c348e65b610a61d3f22769962ab6d4a853f17c532a59a29e36f93ad5c9db2ecae4ecb64e59a838b57e599d04719246a41ccf12cd3995bc7766229488946189c972b15ce6cc5924798dd989f05db20731dd2d2cd1f20a94c1063bb75b9e4832d7e84d6a1a4de7573e5673f217bab3320e470ad88db461ef4a5d85e6f00d782b8db003be0c420cccb532a782b8608a898f451c96fb7b92f3129d7a458547f8a928e373db2134f70a36f491a42164b162ccf162496645d1d8d9c4158ff59da588825aede81c172a59ac534ddf4a873793b168c1aecc059d8b5dabe17a2b865b1d32d0dd6ff646195ce9e5f07f684ff2394ce5f6e7f449b19574bf66df92abb02ace7520c1a4444789ee12abb63d8116024c62c7abb87e640e79ef8f18d96462fb8dd631a940e300c2ca9ade500ba0aae6152183d96bb6403537878613454272ed7867e19cf02678358373aecf4b66571b0f83f850b5a7c652e674ec8269e56348e02be54ccb12db18cd69b23767e7269993eeb8d8c5279645ca11ccccd3d2fd1da18d5afcb718d96750f6bc590184d1854c8ed27ae2798207ab7c2a3ae29907e1eff4235f7ebe8040cb990a7051891757bbc9593d08699c76d50ea016a7aaad68870448f34eefd45660204fa03d463e1c6409860edebcc3a27cb4b508ae45cf2d2acfec0d91f51c4fbee0c1bfa261c1c47d152ea3a7aff3a8f065a3e6b192892faf85a03efc5b5d6336941fdf590607ef8acec096c22f39fba11a00f6672fb08ddd888788025d740039f11758817a72c055ec1889daaadd3e73baf9df1625a0891e750604b302069619fc0036ed6c53ee25bacf4ca31e1fadd68c77ae542f70dab8826b00ed10106a541f99b5f54a5fbabebd0d991ffaa927fa78d560add28fe08fbd66145349c2e2591ee4aeb1dc256fd4ab7352f44310240b89fd3bfdb9d289fc9d437379c36058bf256ac3a63483b4e20bef9252c8484766c03d68ae5acc436ba65662a80b8ccb08de04fdb449cae5ce49c4319e60f5ce8fb4972ab858ad2e602af963f92054348b38249379e1ffabb2df5b9c220cb702f92a7062177efe5bcab725c5e53943dfba69f310f71d429d497d96ce300ff1bb5f551f305fb3492fca17de04273f2bac0194baeb838bb000d2556aebe7d40c31255ac6536af19b34a8a3a040718dc58f167d3d16551c87a017f87647a27562ef4830a5c0430b673508dd90c55260248731939ebc4bdd7abbfe6e5e081fe0009e79371a580ddf7e0717cb251225f42b267a87b675ddad92aaa3753463bfd230b8ca0421a22e61f57bc33b0d0fa1a2524643ea262cf51251f016a331f29ddb3d8749606f57fb6584b77cf6b90f742b7c7b4bd0c0161db3b173bc0ba93a8f554e5f09caef6ac7e5139112a92528ea04e0be8099047c3e640f55db57bd8969c0b7b5a99feefff4d60fb8fad07eb142c21c519129a669825994fcd0acec5602c9d0bd3b59470f8b43d8c0b730eb4b5dfdef619b2c01729bf2d596bc9125db0774e08a742d1a6cbc53f436bc485b1ba429198ba7cb428f9bb216e6efd17b20676218157afef3f81ed0734e471f72d90250c17a8dd75614ed910756f64d01f59be57060ab23c484d8f196327c48346d1929ad0cea5c8ef4e918a2584c625ad33ceff46f8952f4d7607562ffeeeee633de6d7a2fa421ef27a980da427cff4d45f5962c1a70d6b89b8ad58852aab47af230092d01cd60c98321b4aeddf0027da7273549306515d914fbc4ce5cd25c43ccb5ed24379381801557f19e1d7d2e14ee0e256d89ab0a422915727d16cde6dd128d9d03d11b51d5c69d7135534ca6aeed8e5b05881fadbeadafa458af811c91757f29e1cff2a0c5e12ee76c5f5ad9e80c4d7798fea8bee7b8a3dca2e66e4ae3980c203378dcd52172416d9855a0d0038099755298a1ae0f0b86acc3392c5eaf17c71bde76a3e32f3963e470ae263c00438661afe371977a22442d9b837b99251b354b08784bfbc3e21cfc5134a2c68a94bfc1b8027819581db89aa3763b775864d27c978fdc8746b1ea82581d063026a2ca293561a8b1c0decf1076a18aa4fcbe73837f959b7ca8216874b03103fe1a43ec22340878ea697d5d770bb87ecabf7d56bace48c11aff6a9b08309cb341b7539e3c707fde42f72c2166a044824360e24638c31065ad1b82692c0521a8871ed51ac032dcce313f6510e0a4160ce97a3deaa8e68eb900d87b7c2726ef86962376a7e36186fb9175607a6aacd97b7c406c367e85bf8f9a6355da3e167b538bec7bd541f191b1d2a0176dc2c4832138bd293b54c93dededf8410af2f72e0e37c4e033f3f8980617a9e2f4341798a5ab15a798d1660d4028dee75adee4d0af74649470a228571e9d94b594981a70a84916add37da6026c110e9bfda93072314f35a18f956a49698bb5fd8d12cb481e51ae5dbef7b5e53222c570be1f9955b4fc03d825b4061e195b5ffb07b9eeb3a750f40e848b817fdf24694282d0e17fd0fb860811cbc7565c19fb4592545b8fd971aaaa25aa548479b6a5babdaab57ac836201179f129a1333f085388eb3986fefd370dc56b88265e303ed6c0c0c53fdf443d9502d28835768ec33cb110f7d9dd9ad3f01c39d6585f8cf0b5d2aafd279c421edad415dc82ca599c8ac29caf5d6435e8cc7f9a52ed4cf5ec25372c543812469789f28fd8d8d96072da1262fc9b892ca149acd93dc58f18fdcfdf45728238554a64821c356a1ec56461efa9926f4a5e509b7715c341f4eb15237c60eb4c7e2b41340a0450b4d1c0173e7dec2bf4c0a7248206192cc37916e860be965b151587766cdd785fa005d0b20b2357bf7717ef8e240c619e8aa01c627b16de979b3d56cb8191983cdddac1893911d6b4dddee1591f6a01126d65a492b11b074415e2c07590dc3f015c1182b92323d2ef456a1abe24fdcbc12cc4f17b7734be358be389844cb19537b0527a42cc7a685c4bef8e4fa2991f0bebb45d38ba4f3f44cb51b3cead60c764fb02760c51dee1e32a3bb671f67585322e23e86173630c0413383e7335d77df0a96d0ad6a6161aa14a4d4815a3d7f6cb74c4337da781761fa92a77e06934966e735eee1f9340410151d5de5f0247908ac95e60d67ecd78b9e0794afda2c47ccf319ba1389eddd974eb73c23cc763b884de374cefb9060dfa5ffd031ecee0b1119600ce1d2d069986bb1f099c0b036b1962c54a5ab693fe44675bd83850079da9fab5f3a073af9cbc2154779793ab22e7225a272f7dc599386f61ea1a2c148142d37150ace6786b2ce67caf2fb90ff46b99a616fe544088d99d43549b818211980f5e374d0aadc7b38ba7162aea7e785548cbda4ae85fffdbf7afbbba4384c634bdf9f97d2f1f5bd540689501364980a0e47b9e9985154f91ff9407dc2437901b9ae648fa30021fc638ec2af60846bf3123027f2bd07e508da449c3a90701a4536e0781313defe21cb11482933251afbc50b9164c01b75f9327e7ba850d458f7c5c27632f825c9c4585885ba9f735b8ba78c2b56740d6abd9460069c5d02aa71814c5e4010424e079c48509fbd9d87254ec77dc26891177202d14e2a3955c91b46bf28b6354fcc4f99f5ea70983f81cd73b51d11e662407fa4dede007b5248697cfc3e000c01690dfe98597fe37b1b3714289b67ac7dc86f7b77db724b99929429b704f104a80477766dec707148a66463078f7b677776efbd3317a14c9d973017b219ac07180960383c2d2ae2869033e9d3592ae188e082f2c4154c18d8d431ddba278a3c9564aa2ae6d9e83bf2672af98ee9c7dab1c88b8a6e2fbae8e26c36b3b163cb8cc717fa640bed2189fab0042d82c160303aa444049986408623f758866c48b52dd2e7ad20b1e5615e248aa2488b6614082ba5052a5a24d222f145175d74d1451769a92864a2289b55516294a468ca501351f48fc5542a95ab602caed2297acc251689f090cc61be63c23277e46c478e335a98f9656eb1b9154571c8771e3063642fcb2de2d0c762624c8c4df1b38781cdd0bfc83136c4846886bea585c5863445c533994c9c899fcca0cc4cac391363be3391c4c498288aa29128c3c58484a702134a2975efee9eb5d65a6b952d1a5488ff7f57ffcc1f49a3aa2bc4fa081b0c8e87367c17a539b42d2f586bdbb61582a769e34c6c7928d03b27744ff70a3f9a2a5dc75f69cbdaa75572b6eb68784ab8ee5adf29ae11030c8c90439c40ee20ab136c249bcc1df9d32f2cb37665eb8f9b95c8de9de04116a315118ac9623c82c631a8729c4c867f51fc9ab5be93a5a4f87f7f1434e0ac22ee50074ef0f6f696e0cd04df91220a5246ad76fb82c2ad0341e4d7a13e35a48c897bba19a48f7529a791283d294ad143a186784513bc6bed6b04293d51fcc21c9b0a10701cc7b54c06050f1ebe834307997ee5e13bd68e76a45f47aa82ab64085f69c4b2d609a5b4d24a7311f1f0f0601ef9f1d42aab9492561a004a29a53cdd0326b96bd624ed56332cb72ddf0e494e8e0b735a9f0b3826332546b20ad1134e26eb3b6b36c8646fefd4913b3566f774cd09b87ae2ca2cfdaae47e7e5923fa407804d594a83c0de2314390134ab2224231598c870cea710cca525a60c4590b8ad6de60b797b5d676b693373b4d34786a08811ea0943efd1e57e62a3105d2fd218b92d2ffff27219aa064d908c857a8f29ee5d7e56971dc45e9b04baa586d569bcd66b3d96c369a1a96bc49cfa3fc87abc96469562b666a561af8f1bcf6214d9d37ddf6530d431d3434185ce9a8c1290f955aa1c306a3b00dcb632b3e8cc23658e8c3365f0aa750df89357540d64b524cc3bd3c13cde42c57ebdb8fde99424cb57dffcb9891dbade5184b7932cb32c7fc566b6db5542adab79a41a81431f43385101942fce1808f98ec47ce600508db8f47a94f4a653fa5f724b11050f020f4336f3a1f23ab905c1784c8f94ad22709e9c3e15aa4c6aa8ff69161a45cda409eb8d29ab5dd51a5846f79625aa4a2294dd38fb5f7ce94e40d759bb7a1a10a0299705da247f754fc61be4bcb98d84bc798a69fd7bcbd8cb1623725a48c892b8845ca903f73e63b3f676f4428d631a69096f144f20ce336d80de6dc57a5acfa7f251ffa2e3ea890ffdbed769bb79c6ab3b1c1c3906e45c62f5fab1175cd03305d92268d0680182e872b8f2d1fc4b1a4945f721314aad631a495ae8d58c669abd97258695d6925b55cb45aad56abd57a28c116a19f0c988e1065fe020c068b113d293214246b2294e46130a2144e5c180e3da59340c81e2409ba2f1216ac1949605316a1425c6bffe9ffd75abf4787bd300c433725d9bbeab24dee2a56fd731c676bc51c0854bbd51213ee31752c36b1660fdf913f9d65e1e8b85c978d2f9c0dcae1ce22011934f0be1328e528e59842a5505c9696c6a5a571694ca152282e4b634d0a75023f8f86e6d6d0d0ccb05e1ac8174b9c425dd673014f2770e9f34ee0d2d2d228e5f875266b39eec32fb3349868e84629471a6818979696c6a551ca51ca71695c1a53a8148acbd2d29842a5505c96c6142a85e2b234a650291497a531854aa1b82c8d5746fb2914ed5228da9530b3657574e9c7142a855a1a1dd3cc9f203dddfde481cd719eed4cde079e64ee0c4d0dea53a9e04085940d7e1256b9111752a9542a582c52ccdb54e23a2bab54e645dc572b10a8c32b160b9b3b02949cb9f3e2967164b1484d2c361b0b162c6e5aa06646d3e8c511a9765373a52865b1c614681fc78202064ee81e19cc005be31cede346264c1daf7b4c4832d913a427376496127b0002cdac58b0bebf333317df197c71cd6a0e8140175c32651f8805fc64bc930dfc6c349b5846486ee44648b22732d993f9a4a8a8850b9a24e24c9e96a32f5e80408fa977b893f867d8b13a09674448e845786791196bd10204aad8c50743465e94aa03b9924996ed6028896c68064dd88b172fc2ee9e53b646463264b676db368b5c33cc0dc607e01750e9839c6fcb38e6c4f87c25b1cc70da6208c08454abc9c4298aa128abd0aa003819df0c10acab6b309e369231dec4681f37cae91e0efb282b5a2289aa11677d947d93f1998cb229b359abd59a0102673b292e91424c868666cc980142081505a51e4ab2888c8100020821d0a0b63361894b450821844063004ff3311c36680cc05fc9279938138b339c414e3880afbb71efeaecec804016ef002db15baea44fdfd626423718ebe880401dd6010f309be7abe1c9dd417e98e3cc3b753adcb58f8fb64da69b4df6954c9c297d1ec98bff0f7b0ef8f89db4f371a0e2effc8162ffda7bed94d96030189030317c12872d2e32cc185923e50e294676982c5b0ac9a59a8d1c6292a584c1602afc4813c84c79132365bf66912ca51c132583f8a70e8f0e08b3ece15e62c29a3a17e46e8988dc81e92a625ef85a9029b661bc84f1c0b54982041b488211207b2d7bdb9eb20ccaae8a8d62d692373f4431029bfcf8bd10f48061c10d6eb8410c90c01811213f9e3539263ffea993b22c14a16acdd60c144262160a304240226c322aacf049999d5a8879451481320c31898e636d20d2d13c2676ca6ce2346de2f494dac4e9fcba2a13b0400465488d881f375800a302e48614252b416644b622455b84dc18628c727362c80d22642a03c06e298068cd56b843162a4a64b1f242873b933b4a96248cc8d2c48b24ae4cd08b1bdc0bc307395c9a1b865c9413183029306e23123e8c4938398c4e44318a995d530b3831dc0fc90c1fb8dd8a830b19ee69042c48fac105946b734749a221d55e80b99f3b4a12521049361843dc2f7794a42c2ec4242fb0580e6aeac8562a779425885a4bb557acc8d1e10f733f2b28ae3ff247822b747c0a8e2dfe562958560a8f6b4dc04b695ae155c5dc63ffa16354faf3f3d8621d8f2d4b87e31af3a79f748c8a6b4caeca99d18aca95b439dd87f45e36b7784c3127574aa10a787dc34287fb0aadb53fdc0659426e63990f4ecab55163864bdc76f7fb7d79369e9826a960f1be6e60b8af36aae1ee3e6407dbf4e1f50d0b22aec4ed3f6e83aca1dbb8c674406a8c104c62c07de34d164bc96d2ca7c441e99dd6ce1e8e54a526b9a2f55818cf6a59ed58c2ccb087013e4bdca3d216441b524a2965ad392d5aa7848b2eca66559424299a32d464766777766777164700c8159f26122560820387d8d0223ffeec0e47bcd793c58911a70991217f8b204fe495ad159f6007140c65b9c188177e2af5916badb5d65aeb10b533042830a29fbfa3180c29c84e72e068f2eb60554858d48f2d41669828aaf9e1e57a31a2f92166888c6d26489153134a170621b00922991b867cb4930e413c116c126412bf22221d0e2f364f052d58946ac871da8816645758883511c3d145e9080814457028156d40c85864051c369821c1d104ed28880788e0606244e11544a31caad150b8d9b25feb2c094c98e4aa88b1706d6cb872051e82d89e1283a231bb700205930683c18e44d1a202203da50363a6307eb860ee285aa49440cb0b5c50b95cee285a6090e40a102d4ad0aee78ea265091b19b4b828cd224c6389e90ac79de30548dcb152e852d0414787254ee1c76311e6be46c8d415e4ad706cf10a67e998d8aed0f1987ad8b9cab231438a9d5563b278b82f55b70a5f61a68e5f2d17a5dcd13da92c318feee964171e93b58345140fa878078bc96dec49a1e93d4154eb110cf4e8b0ffb77c3c010d645abf8ae90a02f36b4fffc71d6b8489232d0f0eadf4516b25eaf6705f570292a7016d93ba8bdce188dc12db2c16af6fa4cf8ce57eefb4923f3468cc90051357e220b7cbafcf7d5af54f25e276585610d79faf62ae9a2a06575cfd5692c94a12cdb062d34a369961ed1d8e533b9b7cb2c4e7cf4be6563672a1262e4dee23159a6489714c706b721fa9600308ee4cee2315845ad0eecd7da4028c862bd325044209181db81f0c421e6e97fbc8470b4224d73b7231c68710889323372a37dc0f071832a8618ae8c3f5721ff958c104b7e63ef211a44596cbe53ef201a3b9a37499511bb8a74295c245a3e570d16834174d872b878b46a3f9d72e1a2d878b46a3b9683abe3f4551dfd5c7db88aa48d009aeb9e3a2d1683453cb90728a0aca08e1c4613b2146feb8bd132de68477174d872b872b29c9f522486fe58438e10c1180c879e9fd2d9ba391ed235bc68cc9d45d8a31ae5f07a8073febb2b56b1b756e34da08a12bfc2b8e42152b57925cef2e9a0e570e575be92b9df4d2c33027c4e9300c3f10fc4cb7939886735c2fbdd58a31001f12bfa40b395c345a0e178d4673d174b872b868341a0d575351f46decc499aa2f9c2a3c2fec7a2fbd7be68781a9232fd05d5e49ac42a680219f913c94deddfdffff9d524a29a50fb8930419ffd34b7777772ce54bf9ff4effab1e67b2de75d6c479edeeeff56b777fa7a07cc09ffeffc6656c58ab52d15a2b5d514a4998f7b3dc8dbbbb4b4ce9af7000e4631266f84e030306954c316d3c95f3a8fceaee6e4b6baabf97af1cbeb057ba12bfbefca29452fa2ffbcaa189dc8ca59452eaf8e5b793c194524b29a59452ea81df077a4aaf97e9a8dbeeb7335fb9ee6a739a46536b28eabdc8eefbfa724f2f956259777ac78425ab9b6567284daba69452fa1ed8e0e1779f3add3dfd294f6d7116f5594ae9d3cad98e524aebed0429d64c1d8952eae269e5a03ecbd9f99da9ffd27f7de03bd269becc75758cc63cad0f3cc9dc194a29d8dd327786a60675bf199aff477dcdff7f8f4acd7c3b5d4decdda2391da331a594524a2b4d0d8a529a9ad286e2d777b516d72a3357ffdf4bf1fd972b255dbd5ab955df2b85faff5e2bebaee28a1072ba7b6581edb66ece4963ce3973e6f4e9d3e7df64efeeeeeec972ef6a55f2bc39a7e339fd954657ed7dce9f52cee67467bde69c73ce39a75fd895787e375783faffff9cf596ad7f73fa4c25c5e0e7513c29ab398eda315f393a865677ec4febe931f8d9dbe3563a660cc5e17dead0ee6581ec983eeddaf5ed9873bc9646e7bd3e98d8c3691f1242fbf4c6f3f0347926fbada6d723db9b2debbb9084d0860b870e04341696bda9b9412c83db08374f4b75c3803bab9532bae72439cb8194b21e012d63e21bd646a57d35dbf8367aeac84a95bca3bf338060a6cad10c3c7490bf07c9165b9e34501d2141fa37412808216a803283211b55e194ffffc3c712875454ca01c8145b8aa4859b2e2f9e40e7883f715552c5c95083235fc28f5a95e106a2039951f10521b38420e28b1c3822dbbe8d1995109bdd2391680890a932c00c63d783aa735a29ad1fc470054a7e61a32b5492ae8c30aa5c8162063e821a1331519d1626ae787de3b5c91b00305c4e04dcea9e1499c33cddd399c31cc7610018b935b738dc608bbb2cf0e3e66648cb6a750cae95396bf220777409c575b524667f28e7ab1a8ec3e15fcd9793c3129ce1309e3a26dce18a2f873d0f3768b2d85911086fa898a24409b2ad768616638badef38b6f8face636bb15d7140826bb1c53756646c599cfde1b0b5788696e369cd90c3ab2188ec0e5ead153f9ee1062270936db7188fb0a0851147a0c0d8e182b98f8ef03074bfdc4747720899dccf7d7464098e0bae29f7d1111a5ccce0080b2e5e7069c2e080e0aab0d4702dd20b1a2ee782704603c2881035301244d1f5dc47465210c28c14d9c8600829bd69fa899a4f2557396a6f3a4085bee3d66788ec58eeffff2d27f32f4ff3ffffffffddddbfcf1de3ffffffdf29a51eedee6e53edb8eeb6ffffefee9cbbc9762050e37777f76aa25e77ffd7dd0efa9ffeffdf72efee52e6ffbfbbbbfddd5be604de53072e78b2bbbbbbbbbbbbdb57afeeb2ad34cf39a7bbcf5ebd26276d87e7ffeac5a2cf9a33ecd56b26b7cb4e8b27bbddddfd3bb7dddddd4d83a3ddeeee4ebbdf7ac773ff5957a652777759a9bbbbf707fa40ebfca4ac5fdf7274e94afcd57274cc3fada6af6368fda1e72a1dc3d9aecafaf5e95793f7b4f6f7e0385a9fe3f073958e01673f06fe7e4f9ececffda9d71e88b912d3ff80f5ffb49a68db5775d8a1b5d60567bbdad3e451295d5c29c1126696ac3779ef792b5f1779deac6eb735752bef0329066fc870ac578ffc2c1a13ca4df8815352d938373ff89dba6350fa2c21acb5f7fe0dc2ca580b42ae500d342ee46082792f147102222345e6c8080c34b2966bf04f1d1f138d23188e589c2c13832c834f2090cc494b11f80211ca046c054e6ae311413c61e445952317828e8488744cf95b1c872d132d20c95c01321d8283231686402ff2a53c62416c2d31edac90447eccc18d122b1ce5aaca15ab00816a132b5635638523f9710a10e8392b5768e48a6d40a07a332b12829d5c71aad66a73ad15dffa43ae18d72752a6a88043814095e21a2247e4a7921fd38040bf82232235cc1459f98b45e6a60643029938a8d4ec5de88e1136400013170000180c0886c3218118071339cebb0f14000f4c783e5c5c2ea48bc3a1481003290a63200662180620c410630c610632a8ea08003bb571b1b5d28e6ab92538f456f188d0a93bc2db4e411b91dfb7b4856b84d6e480282c8c87b213b860ecba34d473f9b735bbfbba91cb070e27583551eb3da938d2af070a9dea78e31d45ae15bdbc4b472bb0e231cad3c4b1669cb35b5de25156c939cf179ee56638e10ccfecb6a85f71ca3d35163060397332c2b945e7be8b899d412132c255c1f44537de41fec2f8aa318cf66b4d8990abdc9bc6ec35b76dde7ac80fdc36a53e9d587abf2f40e01e3970815631636c60ab0a1156e1d4f90f3bb46b4f06b1e5cd616eefa50cb268764122a6fa3d35d117befc737f84ea8b99db2c2b047e2f1df8604004e03229edcb7c70a7f661750314339dbff564fcc0e9c9a593a0df6f6882278635bc7ad7f242e6bc7fc8b650b4fe4b03d8bbb5a6a20cca8cfb705ff2d320d58f8250d4e15356a630b331527f21d78f0b09784f078618b31f88b506c5d3532bf9d045cc023420f54490e05374bdc7cf38d30032a38b8a40b693497c3e92b0198836f00edbe47f10128bc16a040f0755a466aa3360164d6d76a313e9451b5ccb67e7a33897cb09c30f710a3d73ad16548444cb207e87903444ee736dbef78077cd2f9adc6e9d6d3ed530e1a06a5634c4d4df1e24d7ea482951f4d033d2e2eddd15e64dead564dc9fccfab1fb39bc2e9c18b095510f64e817100070a66a3277ae0f11241a121fcb9dae7e9388d17891a97f1a180b4c38bd1126224e5b5f1888cf60c1c7c3f6c8dc5c77bfd82b912b1b53194cbb4a049af1ed9f20d4ed3bb4663028c538739f5839151ced87d66d92105c67058f6ac9c9f2c29ff0bdaeac1800e4455c7948ca6556a6abf7ff36e1a195a095bbf4daa9614e0733785d569c6900a7456c9b1520e50b3485d0470d4c4ffc09715281b1227c1a850f98634100e43b1cb279f1ef91e2a0a7458833734d31443a01cf2af6409bd49ccda11d47271ae8d5ea0e2b1df7e0d5b9e358ec19838921dcbd474a9b967b9494b4fd6918deead420956a79bbc567fa0f0f1736d0a0c00ab61bdd61fb4dae7c1c1dd7fc32c05bbc67e5e4884425336fc30b1fd3be75c93ed4f1222ea65960f253ca2846d2c0f4646142dee13f2dd0a4587877bb31b64186d40d1983b70503c40c61f9fc363133d3bacfb6e5a6d6eb386ba0c6d09ed88f1daf1222abdd5a1fc352bcd6c50ff4dc9dd38c2c09b5f1fe02413f3673684382a1dd44ecf827e7fcc956669913b75a0ae217bc764f8efb0a243667170f49b01417109bedef089cab7c89c627a9f7a7cd11493dd61ad6b4764e46300cc5ac8eb08f3b448148dd44a212f5437073bf71efff21b519d91fcd1729f4fc7684d2824d965102782691e37a21667a4a99c44e0950d4c7229be3e1faf9d1031a03029070b454a7e32543bef3069c915bac9b164dfd320658d64a7573dd8875b1376feb28e972e1266fee6bc5c75bfb808279c340a64e7c16e01d88954b8a5e38e6411fe04adcb71e5b9b20d20001d2c3b3489378b067930d9d171d2166ce7b6059692dbc6b14fdcd90ab2ada6cbdb63c0177a94aa6ddeb62a3139d49bfbca036d955ca5d145d29616274e6074620e8250891af575cbf83f6842ecb19b97a1e8c4e684d010dde9685cccdc21916e103fb0570e37e309a892693f9af27a698227b21cf152c854629ca0b34cc9a11a5b82278cab805ef2a2fb4aef2a3de8f203ab31848683d4360d87393e574bdef9e349ee0a42f7c1e13f0e620d005ee4364af3269467a237b46bda1e525da1d3df2495a4cea457a4b00acf0597da411502a56c7e9d3cc79a3f1d3096b2ee52a95aee1640880977a17450e8546d5ea6f0808b0c8f95c8815b50ab034236e2203c299503373d5acb9164df38d7cf932b14842ef4986b174a9b2e4b87f125d2a8ffd96d5517aecdc05c5158177925257489e9d90bc2cd2d0a534717bd0b2e8f6d4444f5464642f883294c9468ce820cab0c1d66012be6bdc7e47b871e7ee9bd53748e9f69a75a1895a1ead7aaf550686e75ceb529576945f464141c587a4cb3204891a18e16122232c485208b71c93edd5ec52daa140c96dd4f0b30dc7845f615e34a5a515e7cdbca068b8b1767bd0a3711205ba86ee9d3466d51622022cf2c34f7df617bf6e2ff800f9fe85e708b8417cc4e8943b75ab91e2f0117867ceb8c38142b34a2ceb90167101b8de441a66b77fd6e96f6f826d3006654bd33989cc79ed8582d574a764fff390e2e84a2aaecc89409f8dd638dbc1c3b482b9ef0f0c0505acaa620c53473345c9b22df075a3130245c7c962263f3b1911645aacfab545a1874317034359eccc515a07d2021d340647c1d7cb00555ec111cf988773c93042087c75c7cc67b354c5d2b3325c5cc260cf05280f81744499cb1bbf2556b89e3866e94f2fdcd9a112cffc07d51feccca81b3640ba1224109303432c4625229f5e8d87c620118894ce78261509d979c5a7b1285229d5c3cef4e688978c40558fd2b6350d0c15359c7e58d328a8326c6eca1802fce969b51ac6fea73488359b3c192ff8a0ad9862a79f2cc9463e5957bc0008058878fb6f122f4dbf4b649f0e61cfa6520f9df547ed9cece0d4cdfc2ca233347ec2337e9d0bb503c31b3c221c7080c828dd2b5439409105a0ebf1bfce1bc31032d85921c6443319c7b75c39cbd2729c9d510ddd074fcf1c422cd6adf9c80a7c3f4b091a699d1e5bec8e3bfab5201c630048b68043c31f3b66df91beae21889687aed006a7d745e82c50943cdbdeac4f3ebbb56b81c14922723d8f1e163ab24604b350a78632168fc3e30a1ff41d0073f52684cb9f089405181267286c475c016b33f306eda46ac0481d4fb529ed3c348860a38e373818b30e32f97b8bb124a366264276c5c5dd345b9b1fbede1ee200dcac8e02e2fee8dc9537f0c9e715fd6f6554e15ff007cbcee52bc1cc1e088283179a5adc121dc5e240b593518c8defdc1a41a12ab8ca33f19833ad77a39d0d80c2326f993ad123838a40757650782cd4660d175a2ec5195c84a008d94d24a6c4280f6eaee278807d2de44401291366e1ffb067dce109852b637b10695bd1226258241652a93be4959341cb5131f29e57c7ff4410f8551044860963b1130428e467c38a12858bd45a9504565d01f6ba0c901a2b90acd357d4e0094e1bc5e08ee580141088ea9d41bfd321ee81d8b055e759ea417e1ef71aa9f3eaa2a9d730b028a003cb997062e0cce01c6842ca64c94693d1a10ef3d0ced97afc87991acf5cff9fc785f2a13632caa1976a054221de0d73272465f2ca3eaf7f2be160f23a6a61b8a24b5b6b2283ea1ea3bf40f20c75d8de09753543dc80730bb09e6df2515280c52e3a4d0861da1ccf871f04f18a15cd8b00ed03fa5da1a527bf617a5d1e071bcb33e5f66559a8dd31801770a34e0acd35cc1882befbfb885f41bac8ce8e1bb42b18c16b5d28ac308b12d90a1c20f01c405c8e8f5c38a8e3b09bb614d92bfa3cd07b5f55ee47073e6601423ae0d1cf349a2eb1a6c3bd4043e8b156d764571a3b1eba08519a686bc0ba4ec4af54ade220078b463b1cfa3f5051854869fce246e841b834ec0683c589fb5bec071f3642bb2188dae89042bb2cae7c6f76cd42d1054316ae824c36750f883dd77d3341c56f9837ffa0c86500de8a297ab5621bc4162ac042093f2349de0dbabe6e93cc8a00978113b173012111db42b857dd4c08cadcf8a9f89443f87e9a8e5f46a5a5ea26e805729e8b8fd2053a2b84de1f9794b99dbc9c076c166d61b321b68ac2ebae1343e839d7436e9b27873c26988b3b56eed102b978223ede406d382c081d5458164edaab6c6da1407a417eb380dee5721459c8e641d68a30319bd2ab71c5487b42b07d183e763c98001f6d913e2f1a2aa269bbf0a462c8c10ac7825ded2b39c74d3e2f344cb2922bc3f61506bff128bbbab4e6a70257b0a5b5cfe7ca82c838bcdd291a078233da2638400e2b1a1a1e592ca09c584dcda74a0fd30ed18dc5121838921208750de5ebb6442a13e42127b3881d997329bc0223bcdfa30a29430c59b430f5731578275dc7a8b4e50e2e77d0838cf293122a9a680197c2e03cb49b35d70cccfc043cb5876e4176adb22f4905d8bdc2cef495263851d456c36deeb236c50b70cf770b28380ec20c2f6b9f9e4108759127261bf535613e42e2a3767e140bbbfc703f919427277591623c6141a4f50583eefd812f158edd7841af0d01316d1bc3e8631a501883b7328caf40244e9f005bf83c6a7512a4f51ee94de344602d96f05140fd769a3c351ed80d9bd7030dce6b9c0e913988a4f0b2db1dcc8feed0dd28253b91e57d19330738ee82f9cc5890617a47ad0bd3d229c701749d200d2eb8063867707fb3466ae9a72e7bec7df4e2baf69bf5f6ea0de4072a8c3280e008afe7617cf388fded2bc9411892cf9532963c4d4f0a888f743a98f44ecb7fffba61941e9989f0002fc604a06a4c00562c8462ae47d433040f0d1c90891ce0491294c4c5c985bca61919ba3127adfa366db86b6ef2433397c862ede49572217db7d3b21596af8996ab62446dd79eee7239097707ae3610d7deb6435c9bff8e6d565eb652904d46badbc1f11e393ad78ecd64033bb1594921301dc629dc529fa815df6601c1eb1fcd4439dd9a409a59c8bfba4990940a014137357311a94a5e73ee3ff18abe813048a9e0d483926f9bd68e3d9e5490b35140b0bb5e31ceba77ff8396bca321f38b385b902f493c3a5b5bf7e9afcbce86fa1e7cc3e297270f6a68952e9fabd6377b0e946901ad59895ef6c34dc4aa56a899aedf3fdc0d14d616fb08a5ecc89d2b333764c38a07faa87460d064f557512717902b37029eba00d92a44844a117b8374d5153c65346b3e22ebdfd1c20bcdf030ae244c1770ddeea3a86995bebcc219107f897339b5269ec8e7cd46c53fc1b0521b36f3d7c94db3e2675aa9ff644b4d8ec76b75d84f9f4ac72cde4a7c4e034b2107a7081866484a7da76656423629593a7ca81c1e9481d0d41967e3cfe00b44451936e111ce5ac1c2c64bffe56c05688cbd1a1be7a025db666b0d717ceac7e7f6dd15347ba1ea26f8d3221e68764904a5d1ac3792eec48b8d652a0e3fa21a4eb727717638d83d3043b3e7f4f32560439dd21b809d36ec81af37ed0b55d1b89a757f790d1a397846d84d5b46696a3bd6a6d12bd68ff7851486b0db7e628759e7e297239afcd881d99ed3234e4a13de9a74441b28c7691985d35ef3d06ea8b388adcfb0ceec20831ada9eb4e3f7e7e6dd1af5c29b9897ae57dba4e4714a3a5d391bb49c7672602934d403e6b483f49099910ee98983ab6eb252ba4267ebe742cd6cbde53de7b04cce4cfbb227c4dfe72f2d82c7d65eae4a7e3ec8cbe9afd0f97d1a2f56a1130635012ae1222b38d216204882ca83aa80131625dff0abc2feef24b82846646a9ea9ab2af577da7a720dec7a8afc11a70798c9fcd702503e217f133a1557a6aeaad46f3f2ddab59050e300515f8e4c9bb06a4644579f066dff9407293003862807bcb2caa5d618349f7bd81299fdfde77c1043f7777e7fd5a0ced70cadf7cbce5588020a266fc1fd8f6c8ecd0f50d3d056eced0e95c5e6c00d8b4dd0448adacaf0e69b5c067607177c61748bb37c4ad6f02eb6560146352417555edb29d5d1616a799ee5c9fd5c092ef97f9bc92646a9ccae6bd7324de5812d5d5ab96a29b07aaf86a66ff0b7e4dee6fc8d26a1e92e2e317991507a1bd22201fd552177d1e7addfcac2d686330095d4be520e15e94cd0903b5b9b1bbfa03edd82dc1871c9d12e8ca1d6a4e6fd337b3a64db7fe896f2bd2a525899bbd62b6770816c21cf0389502280194cd9f91c39083da68306f8567e6f70adfe37e4097225d8a57b8edafce75be8cc88dfb0c4118ddc5df05c32d406edc8777dd0e459e4995d5d1a5020e38fcc2c3047f134023aed8328e7293dd1658bee72a3771b40f9036cf06c8c644b5e5334dea15f3e991bcf92ed441ed4081e5a1c1fb9396dc9da999c6caa5bf237ad0468633090dadcd9b6eb53a635d3d92e8f572c9cedf4076e34601040c3797fd1b619c115b48003698612dd274322a0ba5e6385c499cdcd04d7d96aa41bcc5d958f03677d18e9e0c858c7925b4147a189c9909f24b62f1855db48278587d726238e474971208fd2f23102564415f1621c31b5ae7020799b151f659769a7e71f01baaed61567404a65dd1c2ea7adc9550aeffff50292fc1161f0b104cb161ea6a704aad0f460b244c109e476304daa3c2b3898c466e574bb6b08a5308e64c2e194f3d050d85b81ab4219fb074758b2bfb847047d8dd29fa7ae882752bc52570e8990732144fc371222dc249cc9acbcd1613cdba85355700d5243166ae8c798de09341d44041bfc8e87e8087af7ba75aa7d51eafb78e09bd1740d58138b81f0242c2cb2db38fa7da17951fa94523bdada8a641129395ab908721a50721ef5482c106fae070630c3e84126f2e7026e2cac916608e2c9cde19bd584076d4e58ebaf2fa0e416a30058348322c48336798a719bb8ef477d4d5a8106d437baca5adcfb22ef9486b7ed95a21fd11f5de0d52d56ef777b0f86faff9bd024772878758fe0ecd4b4906ba855af06d2cf03f74d715a82999b355edca070660d5f4ffc81d5cab272f1f3f803edb52dc222d207bef40e0751e28314f23996faf2f444c7ae53d6193bb016f9082fe5f724f903710fef65f740aebd792c13a7a45a1fa03e6da6f334c5258f63d4cc66950ff56003518be09529c1f3f94197486ea0ff39e67c4cac99904fc4d0baab43c29e2546c9c013d50cae6f2c49f67a528cf309083eb0c44671c5fe34541f08090dfbbc6544a650d1c8db8d30eca03e0eae5f27e2cacd570311733551af20eb8701fb885d50e9838f154f1daae4482deaa00119cfd741fa0953135681e38c633b0c35c4b419830863958d5e895f8f14616d5c7565d4fb6404727bc2606092825728b4865a8bbc548f55954552e1aa0a4f03930a48f64f8bbf242355c8c5f7a288b81d4ca01020f1799afa9cbd4f76d8d52651e10956d0aa1720528924bcad9e97446a1dcc2ecccd12493477e6994fcd031647126bcba93b6d09f80ef115d1b8f5652778a10ac4639454dfabc77057ac09f3d69b3e2998a63d0b5baa4fd16cb1d8cfb8f9f8fc496ed431aa7a5748605d6f74eb7c71aa5eac94662cd0a61da51a3eb3240a173821c467965f81154a185713cc1d94427a9284467750a918345fd158c7e1b30910b84dc5b65d2f8053252b006cc75785abd70b4263d62a80233ab8922ae393216a9d8a5674c12ba44111b3ccddf2a7b02e22f6ca7e5ac943ad686f0a4cd76c5a826fe61fc45a676c129b8383021bc4396917a802602b4271f407ab4978046c92c1ff39692fc6bee0e29c46987074c60de4820260195e75091838e8dec05f427593b0dac948a1d76c265f0489d4f68ceea602d18ce71506212d51f864e60c1ce9f77eda39433b7bbf1a0b2f280cd9d524392b4ff0222fe8a62acdd6b36c2c2a64c07b946ad2567cc2ced2d0d9e1e3d6a56184c10712988631762b92f137d3fced2037807fe6d21ec72827813d073f828a6c3b5bc573dd7a60d265e64dfb7476ccc961827600a891e58027bb7b8f044dc299d4bb0435902ef03b31fe65845581c192be887c17cbaf7e9ca897c250127141ba4c5f76c0a3c422999b39b7b52a923d0870078d55058891277ca6b3504582dafc146418a77c22c0dd1cf99741164506a70169f1855135760b3494832766a53947b33bb40d7f656b8caa6138c4e628ed40169c036c4e3e2386ee31e0ae55852b5a769c78a6e2a9f7dbb6141765b01f0502955608a78a7098ae16e75b19590c20a47359c6a144545628fc390d1c8596b8eef54ebf11ec2ed84b0fcf34e784a65d72f7a592c16cc4b693e6e14f0868417130192a5cf2323aae6c51d7cfe710678f8c87cd61d4e2be5c98af5311809b45be29f67eb5e30d6c9c6a02c5b5b6f09395b5510454600f6b673a21b2a9c50f177cafc02a20c63e23d06e898f8fab8fe0a693c8d3eeb800d390efedeed045a871110b5cdd158c8f0a0b82adc973fbf98096d0ac01dd8183ac7572251fbdf2f2c71a8306ecccba9b430027e74ad1f5eb755152d5bfbfd4ca1aed3e11d7b10e117f51da6662015f828b08a8c16f1168356d79a7a3d2611fe5bc0f0876129fa873f06c5dd54c6776ce2060883d41e41ca63c08141eae13a884e2059ff6a73ad63e01c127a0da3a4587e5b54ba8ba97465b216b997b6dc1bba316196c69d9352b10bdd0f94684bb6e95670ccaf6d5002d915ec185c8a6def1dd1194dbb7cf828ab5f0ac445b23a6a9e87e407069a6a5a8b06eb13084c5ba02448556f6680497be2e7fcac45743a4c492b69bf053a5aea7e28ca77b1cbb7a63c40ff29d5230f1470f22c315df38e99a6556ae4e8b6e8c39c366a8a96fb8bb7a8a264ba12d7e3d0bd689b4d5c3e2267adf52fed6ac145b50640ea6ce06b3e63245a4fe4cdba64454b0e182a5e640d50f39cfbe1e064a5e0d24fb759b7741350c8eedf6a4f64bfa7007bcec918f2b203c8dd2c7f4285f9a37b3e49a604ae703eaff2c1c59de380583e815a07585faa489448890bca0092ca8ac6fd9f9b93a9ce647b864f8213447ae2d045cef5568e5fe7c0229e33a56bff2d42bb75d1fd3acd9cf60ea464fa8aeacbcac788d094219caa9bdaa04904b148401fe2f010c0b5698a438ce50c7861b0096fca4aa3f871702dfae180c8249f25abacb8655612d1449efb10dc98bc1665c2fe33ee7f92500c2ddc59418083a4343a996525c044ee8edc0be298bd13f28be8561fa401dd0ad150530afd1108238889e9f5fa64f25a71bdb08e8d108e82888683d8ff8baf82b4f1709109e927612d95719e402c2ba0b1b9fcd1fc5bd9fd9cdb18600a626bbce6dcd9653a84b08eec2191420524f6678dcba412d422656b898eeedea9d2f2748d57c88b655cc3c31ed054a4dace0a71d7b4fd51bae7752b81311d65d42b7ede5e3c477845f7a06bffbca4b41e4305ae1e4c428ab7341e03af9a0b6ce8ee438b39ce09edf48025528c338b8c807b737080003a0884764b972dbac5fd8a1d196b480f6d5f1d9356a206a25ef7dd5504b46973418090c0265c30072b3ee3344a6f18d3d19cab8df175db8b3890d88d12cf097ea8e2f1543c1a0c06946f556b3c23781df1af9ac3fe2dbf781e09c614a10697c1096444f7e0da43015471de4d2bad04eae4493ca8a56f6cb8a4cb78c10fa4b138451702ef10394d19d2319a05412bb045efbe89a76e72b7dd9a4c0de56367ed8b2fd1605c9e5a4b7b5291a8cc992d2e2810246c8f747b0de7f7992395cf66dc180ea9e23cc14f5a55945c28e822cf1b58e4c7240b78b22e4e0973cd5726ce60fe5d20eba9fb791a036c29aeb8dfb13e22ea42418b06fbd28566c9746891fa8b388e8791c48c000b868181b06d06376ab1824ab776fc08c60a26c537d49d1f65d2d4f6830af29ef5b1672d0ef57c33f3fe19588efa84b41b71ebcdc6c474af7db3907f989235fe93297d39a50c528769dca711ea614a4f773951a3d3f6e1040ee7df39d6e922ccde0e0a8ca9aa25e29e03e8601a4956f4eb518588b727f21bae53405946812e8c89a32e8abbf7452a6697e78132994a47d69f6901f05dca76e278121f5dc4ab2f9228fa2865b47e93564094e39546ba2dcfa9920b84e2c40ce523f8f5dcfd91b25729b4fdae63ea89c7b241e1c596999fcfc03575cbd39fb0695d55d8e5152a58a1597eb72981311e4b9dfa368b8ca33c78977b54d9d3fb7525162866409962bd7ea34e201131f8f6c21e9a518e98fa4668f1dbe156cca4771886439bb43d4567ddfb34cb9dc318f3731c24f82df80305d1be941461806a6670859c3fbd984d197d53f253b32045021f40091dda44c0175408d80b235f82b40d708bf5420b6e1612c43bbc614c394a8056e818b82a0fb0645e94fc71411c6067aba2cb208c3b420e1cad140f112112a978947b8202353855158fca35bb8a2c201fa1ad233f64e8891cf0cdcf6a73b12da81546f188eab38bc602850d241a538514f839d01ca624d5a7b05496136a5a4a80fbf105a3303ded484e83f2e380605eb4162826e800fba03e24ee8082a33e003748787b10d426d980a89f21af41144c7d85cdb6af8d15a60a5819d31658226128875c0aed86a401109c64a37565b310a4306ba89614ad0d895cc682a52dbc0663ee9845e66e46c5ee09542140406e70a9c80b40bc92905961153b742fd27cc0358f9d22044411da85bc8dc264052b8d13e3be0bb9fff5be8dac7006c4165f30f349692ce70d6723161e0a716c2bdd6c29cfeac2468d3c8e81bec420dbc1f82849155b98a6500cb1db90c2a13fb8237578424368653da4bcde9f95bbae556858a874b739ae2ad6631a0955ca9247614b59e083784a6d777e5f555f9b1a0490f623a95dc651ff8f81ad093c29d208a832dd5def508bc040bb41d2775fc1bb80365efcc7c14911094c1865c6b9ebb71b58c02552149d21db10dc93f09043d9186996c0af98f3ad3a06713c45bc3abab2590e318aee9e0db39a807566fab1a0e17d8a0062d935d08d59882462120599e6eb945e714a131af453fa70f40584ae71d703197676c62b09afc5bec59786a72db2657265298f89d7f3d5d029461480913ed70caf81d9f31cae1495513ba0b39451a8591dfa0ceadb989d6036971f24c2f75d36ace8b8f298413dc459b9a7c74ec1f372833b21cf0f88500d4d61d930d0de98472abf6859082f9beab109e7b9aa24f80f67e2d273ff3660ac2ac265a037409cf83bb723840be1dd83a47b5946a12a07a3128448876cd0d456554e4a2c6d211009aceb63e86c0137ce21cbccc84c23d59131265004aade2336d2fedc26a1c2a9cc5aa272fe0cf5f33d27a067eba590e0b719e646292547faab164bd0c24d5a85bbeeae1d4ad72155f40cd2c72248bb0a10bcc284379c751a96a5acb54b027bdadb032b2ca0a54aca352b6b0921913fa0e7a0618c5602ebd6ccae333feb8fdc343f2d42f3aba3f411d69d26c3c5004bf99b3bf535877cb58f101d43d93daa296a94bbc8820a61569671d65e61979eac9f2a86a2ddaeb501fab41dd543c742da0e4527f04000a1c48feb4d10bccc079c4b36344416ea0948dd4bd7f0fdbd682094910ff468616913c01b2b07850e81eb873128683e889d785589c3c0dafbe61b2661f1415db244c18cfac9e30e86774b2b19e0508f3d687b4ca86613442315c42f1244817a7eb07aebadb654b3433e886534dd3839a6da9a58dda0e68b37dce520c9ee66b8fcb8ce6361c8bdf81d950821ff46cc7b40f97ea5d537e578859378357420f722a3420676ad204a8d619948677d93085936cf314a8f5bf93fe690c4740af777268f6ec35f0427b45f3cfa6da2374ca57a03ccf6df6b1103388cddb623884acdc4de051515d958878757e1125044ef03c9ae121ed95b353b00efcad93c6dde70bf12a077e7e227f01e2b5ebc00d0e0bb59617434cd40503490e9ac80f32265cfd297efbe64712357d5027a021f05a6c62081a890012bdad8a8b123b1a38ff706c0caaa50290c5bab95294c4c5c1731831ce932ee11a59f9ef712a080343d08c527263286cbc0975a4965e23d37c4b519cc4a05873e0376443fbfdb0e5d645fab842e6dfd6950389cd7cafb99818b709ba10eab352f3946bdfbf381cfa0e644df698a535ebbb98124d42d12d2b86dc702eb21902186cc0ff88688cbe46f1be0263ca1762029b85414ded2ff7010dfad105920f77cab0d7f4cda2c2630fda6d469ec3b904dd1ac5de0e623cf6a8cb46c709016d0dac2e5bebf3c0e458bd6203578dd9ee112a57b4915149866d828525105ba01ceb9f21da4d9c828f28eb8e0f5f877eb362c0bda0c6d117727344c2367b10ee3951aac6dd98d2759328f0ca6a99e7ac7d20f361fca569d8982d4044a028dce1d28bcf18d548cbccd50900298914726dd6b47200331cb7cc8b519061206db8b39a0ff8b8e6a261f9134f23cdd44adfa5e30b2c7fff9da6e6c6bcf86996a6535df054f3b58029f557e624d0d08bb1a8ddc1f195a23f4ec51c69cd7ff81d4257ee3d52abd59dbf4e6233611d14d001dd26cd1b077ee3c5f24f278b34e8fe201b830da21be340fcfa2d17f39924e569830e31f6d2239f7a09e3b9ee2990148656787baab78b42418742346653ee38dae9da3e2d363052ff6d704c7f667852f25862069e583fadbb1690df6e4be619764b1ae7202e9b583683ddec99b2fa1395e3271676b68df627c67d35d2774a0cbd91f65a021fa06aa6aa0059e3d9407e79ec8fa2941acaa6cf0ff4af99226a0dec8ebf9f3cdd36b099eebc434b9b235a519367d2d220fc2b96e065d4a348fea9e6cd2523c3a56c2721aaf8f51bc270345bae13c2da86b3838b8e2c7d708473d1f2729958c95aa65143a597c9d2e4b74eb82a6ed184147de7dc465c2e2a8a84ff5a0003d1e1fdf17d1458932a442ad009a2719a2bb145b3baf82c39858b5ec5ce8674599fff3828a48a3ceab0df7e9d86ff04d6fa7cb6e6bc031c2aca0b7310d090d812372faad95c09b55f28162f13835933c3b46a998600e87588ede951b513b77a1f43af5ffb1eef6801f4b93aa00e362310a02ba26d2dced2754a19156d7d7c246cbc939a6aca78ace58983c035390836758af77463f8f9fc349cfcc27964549dac7778a658361d84137ffa7d46c4518caa2dfbe738f7629abf505936121feb49babd1ac44ce3275a63fcba3c4c6b0d3fa99a5baa69dea226ee5b6d4fc5a92af6a5e52e97b135e54dc89b7cbb690a93b5dfe92df775bcceec12e88bd322e63e7200d213a0a9f3c4e92d950d63051b65ed3f42132fba7ccb62d39d5800f28158c2eed121b3a17fa7d1326584537390aeaae48f5d97cf95d3f584fa274acfbbae2a5ecfaf0ba6748afcfbedefc419b62b55e2efb49dbaa3770d25ff99d2aa31c67ddc7d5ecd8f38f42369502c3874e2d7beddc255163b98374571302598979ad8b2ff1524934dcd8f226717a37680e0765d434671d730f546be0b3f91a17a78e6f5c7c707297950a768ab7014d39b0986a31bb9feb2d403567f2fe252cfb926c1d6a20eb6f6e74ff8c11c7a06dc871c0fbae4c48bf59aab81557ea515661f236a4e8dbb07b38f824ffd068b786666002b7fb9b477d457a063a059f9a0e0110363897dac74d98adc34b3fd1d8621b28f992117bf00979eb09749c50e2774db1efe390935726f1a32da030e7f717da7b12b3c375490a6741e9edb81045d994a0a677bb87afaa66c4446bf55d68a1a11fa55249e671dcf97cd664ae851cef4552332eb1c76c1af25d796f495ea10cd49ca740a95dcc4e6e3b49e899053d542bc8ffb8441d335228fa17ed3b5251a8fc3590ce1fbdbe02135e831b30becb6438614d5fda70e74e730777331f43efc9a63348eb8fd7662b0d97386d002d8e06cac2354ce136bb82b5619ccc9f85df17f630c783912085bd1e259c69657249d72835db4005ce694427d4f0d5be59491c8ec4cf0112acdbb5b8b60da1ebafdb377af7097684703747f45f63db5f62e0c5403b497349d457a7dfc1f1d72dca54b40d38fc69f86c24f35f13891fee5b466f6821b4a0472dee3333a8c5d5ca5d1f0c18ade7cdb6932d9c75eb56ba342911760349dbcaf84a98dff196a9ddafc2b8ad6d784da52b1461198fcb2e318f71580bd217b628e8a119fba34b2219504d3b0ae0d39a41da448a85f2474f35462498f85321aee754e204d2e05bbc1e0d4fb167c85dd27583ac18f7ef81268bd220bf2b682b4e41fab524c55a78048aafc06934f9c83d641afa8c4e42ce80445762e5b3db843cc681229fe5b7330b50797105598237cac3a4235c397d81e0b6d22f4cb7c9c9cb341c1e7b494b9cd48a263b27f1cf1945ef0b44f4fc8c95ebe4fa0e6dbcf663b1693d8cb636dc7d2c4ad5ebef3c35b861db3d257c945e2ae536ac32de15ecd5369b8f1a254ebc5f18df7026cd7913706734a377ccf79617bf81b85f0e36776db11cb956dba76101a033d42e33945ad70fb7f1c6be0b9af58444c7f1943c54b3d485ea095feec24b208885e10dc260a5050c889fb21b68280aa67910063523b8ef200ffb1397296039e22b5c9b46cf1f48a88ee9c9200de0e1b98b8733922c72f7dc615f62d6456927a7517836f64c98143990bba0905980acd21df42b0658eef1e6b7c8602fb0ae65f72397c0b0b47f8474aa43fb564808c64df422d25e81d93bea0e04b646d644ff8161af25227172ae54ae674649a25f30a5ff42a92536c7c678a1df5e1ecc35700fda37599f7ac4afe7c2b81062b6a375f4e224d3764e1cb93812caf44508c5b4a2eb3eff02245f3803acc018e2ea94da9d17afe89b067f96b5195943c3392b8816f3b08e9e1c0b7ae7669409062000491bd344303f805133b314417c91969a181a339cda45a721404d16ded7dec0ae7e9bd9cd9ea9810c70a2ca6267fd602c27e4119779102d1e3cf98c9188111f8f60b4a85c14be980a14f31873567713f6821f3c5d52c5c9779472fbc0cf6374a3a5bc44d91f8ca6c85e8d6d7afb5f9bf7d65302460a01790d50f644db96bb809cac62b42952e0d7917983166df5396d739d6e871045e369403c518413586d8b27c3d7f350b24a83100c70dc3859455671e3679a5c9c092e15cba6012882170131a7022fcc1b739cd1051c018e1975274a46664e82583d57798dcd4183ee2fa6080dcc7de6f987979db612c6ff4cf1d89b76100b7abafa91a42d19151886ce07ffc557a49f9dc49229a889725da0bd10d60dd5246977b581e74772a33774b06ab9dc8e941efe114ed7581c616b66f0ba3a4dc05ddf48aa372d0cbb53c1c44596bcb14637f5e1121a48b14b569e6b86bca70bd01fb4a3a7277eeec368450d309fbe98ca9ff87b2c237d25d5a4149de38a5be1bb17e7fe666a068ff156d7b5b0cad2fef355fc14fce04e2496317f82db4727209120c0f3a2fd8be93e0775cc3c6a6761b8b75e37f99d4be23496e5708b92267735723443da89f0f96ce450360b9d31d1dfddf48055efd76941f07f9413e86d71c5d1df2e1324cd89dfe84028ff0c4be4a36d4b16eeb364f7efc2af51be8d1d7eb080bbe41c20a01be827d098f00f0bd1c52add0b82dee12b137753a71baaf8c30fd14ab81f33402690723e747e894b0f4a3c1d3b3050ef9f669c3d1da11215de238dcdc12381d8c65b2eddca014bea6d73dedbf0c232ec5e8ef5e4e786cb44a6d4631a99322d9ffe0c3495c196a35df44d56506448f1f1739d5dca53b8fccb3819e7f17f7673368c8b8048ecd4e3a6daa1072d75ed32d8bebc5c49199163c3394d926e24feb7106fdcfcea91599fefdef18d524e90337a92d9ad1586545c1dae1c3df6e040dce7f55a9f488e0011a36fe9460f8528fbb3e177127de898afd0beb6927c13fbb4dfaa78617b8ea234574b7b905bdb0f974feacf00be10cc7e0cc0f803c0e1e94031d30b37793cf72027675505ce053c3d537ce5b74c9b55aa9a2fade9faefd027c2c60d293d13ef0d7728680a70fc8f419041c9470b558396ecd2ceb572221bb3a7598339d2abcf1001bb59edba032a8446954d5f42db8fb5485f68c626c60feeb555da8df251182fd708ea05b834582f7de84012ceb40dd15bddf0ad3f86fe4de17585fdc5d952832b995316fc396fbaea639da06c606029311fd9251797a4ae870d137d329d2ee8f33bc59fa6c755f87be7e3382409c479c7889bbbb6760040889297f1a065469ebd96313e988aea3372293bd89b626fdd375a25b1fceecae324cdcd6a708b53ec4574252eda425044b255c78d5e21a3a1c29daeb68605c8be2fa4dee0a026d1f80ee24e01bda2eded0d8e1c07c0a3ee8e80784dc22b846d7be67f81b618a4ce7e186beb9f5fa6c8daefe87c2fdf58691319f0850ba2df97e62411f732b3e714a85c46dda776c869ac65caf1a74d493f55c7b1d8ee15f0be33e784fe7174b34ed3beeafd92026445b84eb7979050c0b2d16733cab7a2e6202459d61f9053746283a111294e593fa454c66749a7254c150bf507565f0ebea91417d6d20187ec42f5fb7ccd77290f46294acbfc67564d0242569c3090c90deb09200ec87d86ae83b83830960f96b5248e930bf753a9d5c19f59a135b686b42df5bcbe7ee9a9ef299b086c72ac82829e8ebbf5620d777324a5f94cb1cd57afcdb4fa9e3c3c4a1432aa1a3d826d92a3671cec4262104b8d070bb7e7a515061383133f906604a072141fc965bf98de1ff341069658013977dff786461b3b13621f27d4b181adde0072d163b1572d8ea08d1529612230a3b9d39a8c5d83ffafc6e30576b7796377da74a31a985dae10b0c7b4dc9954b3f7ec3313325c60ac4920dc5d19d93e5798189f7575938d7a4f2635e635e7220fbedf9f0356e053f4bb4866636ddab29d13b2eb767d71fa6115cc85b492ef5d7452ec4399c670f00f9b453dfac690d1ec00080b69305949de57acc2a1326e66a0078191fa68ea255b76a1357456f5a125187b6831afda8209ca31716a06d782e4ad4f3988e0dd2202ac6e31c3916edb999be0a17fd97a38e4e61c7348bdae9dc7102de2b788bbcaa36ba7221d3bf5a2d1afef867a39a1f64d59218628dcf9f8048c942aea766a318d86c902d31226baf33cfeef09248a0a4998054edb0e98c3a98b87d8f3abeac04db5751e6d1cb6ec93d4d36d65553700c782fbe25bf44f9dbfea94ac227ce586521aba6ade6c4a9c9be4b4f467ecd151f997c7c6255b09abb95098f0fc4f56233168f9a833ecfe0c7faa7aabfbd7573ca46f6c23d2d11dc0c51c61b56557cfbc10501d05e19c860f34f810ed214e774dc3c2be45e1a44f2c7e9538a622f821861c4c92fdf5281a7d0377a7e139d3cb9584b921176745999a5aa9a9e465fddec800fbc3e54c417d6402f525e9e157cfcdb6e864f83c8e0e96dbe54efb2ee0434485c96aa3d5fa1420cf0ad78ecd2943af11bf5f72269e5a64c879bd9c4f005dd5465c47dc4d13c66428042f9dfcfff61865a926a5032501cc4853f90b9a23ac75a23a943e7d85c51330a2585b43077489602a60a3fa82bd0edf6f46811adb5ae829102279f4b526ea99bf0796b48bc5902521da50b95aa89dd91b9288d9e00dfd2b804f6cdc9b08394273464b033bb8b2013b6cb7e47f8807f129bde855d2b7242c147e66dba936f735c1123a79efc6c44c03a0470310fc0497e2e17f857cc340802095bb10fb857ff1057868c6239c054f1d058889b50cee6fdcca11aae53a6f454c2d15ab7a16039702a2e43539b60ca68e562c13d4ecb565d95fae5c29e3e669565dfaea37b7c5b4b84de44580c17af18278e7a6d9b564c406857746e4d42560c0a1102d2eb069cdc01d2ef5330a2bed9bff252b9e887d284d57c89b52b6633969511723e48d627ecdf26279c93d6cf263bbe7370aad25d10db31d38beecc824152bfe11d6a713da9df7eab0f1b4c452e8d0e8a320efaa6d49947fd82dc34b389601d0f8aca9db686f6c8ac376f4445b705330038ab7efe3b3a9714812b6b242b2d6a22eb23bfe6e3a613f33d4ce3f3be2ee00e02662aad313031e3f790969fa4276d7329de32355f77a9ca9b50061391b232cf0782c946aa684b99c7170e367e1a72a3284ac33751d8c95f28a4081fee4c86a0d97a094c73947a6e435ccc81e3ec3d0e3498ea5ae586069f8193e63aeed1282f523b0bd5f0c93c7fe16aeb05f70100d4789a3fb2b036ed7f7f602abb478e5d63bd3381b612b61ef7fc1bfb3589f88bdf40dcbcc6bc95aafba8ed05296e40080ba6b79d4664630a21b62934cebac5fc73a197b0eab7b3976cc8d5674d457b5109bd70c71089cdbed50f0490628bd5c47bb45effb36341cb17bb19b7ba3986e3966b0207411479311b02189672e33c4148e5bb06593f37659d196eaf9b2142ca8dd5bba539dc77131e50dc29426f873db21e1f0e15c8e35fa2eefea8f6dc979c0e09cd9d7272c8ae678b90411c97e9d6b004e12a37adf668e349f52189c83554c356ba386542ab7ab81651ca45e6afb8c3453819b252625e595c5e317f2aeca3133081de114e370c3322baa1fed60877745f37d21382fa7a0f95641d60570eaf0f9930cd14ae03840c1caa08d3a07b4cf0a9a0d34770daa393c75bd769a76580d35d2e68fddc7404d4b65c3dda54a7b7e3854c1fa3fb9aa0b7eb14f9979e8aa4d0f7c1a16b1d6cff434f2c52382ba795289d40f41c3c2f31b977e86fae8310e71fb58672871e92bad782850aa25dbed375eebc901dd0bd9e9273c087d2de24a51434bdcc92629f2ae19aa69d0bce0fbdae98c03ee5e261950b4afd1c7215b119783a68bad0fd1dee23391f8dcb35394899a352832d2754275e99746677801ee5c2cf3329fe89f3123193ce52efd31056cb003d2743615bf3e7515b3013cef3f388569f47bbd5a0b6862bd3530ec34707f4fa14360337feae21899e100355deb735777fad88089ba3b921627a53d82ab89795e033ed0ef0e879598a3163d4229eb86145e8dba7db6b9e4e05069928cebc7cc49d2b02f4b2793d0a6da10c5945366e2aede6a1639678e3ed7f2a4ec1da5b8c26a78124960b70b461f82419ab75ec0703ac0286f14549c844e937920c94347d20e4582e45057d2b3e5baa3a51c9a8fc05407ebc2c5d420ff8823ee160f294323bd0005052d25ac938e05f4ec0eed7d3b4c9e087c834c02433fbb084f2531c414bac8920b45ea75f52a61698ce6e747bd2e018a7d3c1cd1b42f96f3dece717295958d84b46d8d75a4952ea4a176ee9a87696e369ecc53d7465e4d3c759aed2957f5303b0db25675c0d4b889ccfc4f9150b5d96eb16b49f61794aa2a97dfb397fd3b6662766a209ac593ed08b00d88eb0131bc97179612d911ef00fa49e646b6152a13ef69f3fdb0d424df27496b44cbc3dedc3ccc7e25c96d8aa710bdf2bc45e85182e202c5b0d23dd65820dd6a65506376152d389a5e282c8b898527b317256bdaa3be98da98527c510afd369920d9b616058b2377b9e361af7dd85bca3d35331dc9d373fce60bff7e704678fa1e3737c79383e8c19f8760554dbf43148110a6068817e9c8ac81272e3fd2acf50bc1e0320018bdecf258d48852e9ecad58cc3faee4befc393bbfa76b18d66c07621dadcc78002ef3a9180594bc2267664547444fbbceaf8a019d7c22080086c4748b5a020d90209b3acc2686b1fe14c1b02518bf891fec37823f95658a15cd83ccc8a023c71c5e8fb9021d9a3cce4aa48622bf9793f56e2b3f7f9a6dd006e803ff2d534bddefcc3b24b60f90a508330be8390941d8cac27a1ff39bdddb2dca07253eb8ff74d3db78837cc02e3d3faa41e01012153f2b15df2423cb48332bd4c7625ed2436a61d91f1a0389906dd6a74a40193f4d051fa7a61472c5e709eef74f16f307e42fae827197d22412ad6fb9c9a40d09a9c7d49495b5ce2836ae9f82ff8dd452bd35b004e658a2818f1c32e2bbc345f80ebc54afd32aeb33708444fd8fc6c9b43f312991b83a8c4c7d1189bbc83fa131559bbcb69c8415099c3b2a14737596183e465433ee60189fc42c772a1175990792aaf00c7cad7ce604002ecadc5f73c650910570eaa49a7d602c6ab799a87a256888310fa8ae2ece0fbe026a68c2ac6fb40bdeec8a921ed3dcd3d9af03528a866778a11da18b06f8f85a2596f2170f572a6f939559cadd9e7ed33c225c55e4ba9aef80072a550f0d85931ac021f68703657f73e8925f81cd779fdf4184bb4cccdf667bf03cf4eeea24c54f08f4d158faa2574ddbb9edee98cea195ab7460685eec506f6df97d14d5e5989c054ef428c2b462661433cc2c7be41a546b09c3636792310c300da66e4776a4848b3a3ea6ffa0ac9896aa4d0f77bcfc15eff57bbe5ea37a0981d1d07131ef6c333740e199a41d0e12ff0c49f0f7117b31c49ac91ee9d33b161bba92904026b665d63083c07905ee95d9ffb49b1df8e4f7ee3b92280ed13a3084bd324e3dc161787a73ad8b36eea42d90f332cd30c8d10c34e65790d59095946f013a3f630b81ada24c075bc00b0837c9c54bb275ccf57a2ec947789d0b1c6595bb7f79399f1e9dc68d804a486c19be164a450b431381d82331abccbcd0cafa1934206e502f76478904c492be848b6559c3e8b2fe65f947c2edd8f245bd6115afd0974873134f6730b0cc74f359f06047671ffc500f65432ead4bea9f1fa6ebd86604267d1e5b5766e3d77d3c526e3cc2ec9c21425b53390a0398d49fb9a3775f4b65039932a8a6d3673ac46ebe94706b4c004eadf4bc158309a55493ab94997836fa9ad91e698ae1372c7228d282db805765cd2d761d81debd680de4b9ec23d23381fc5fe2f6017c49a453a361590a574c1ef05349642d9d65a687eb19753a73634726d6c6b9f10b21bd984ecbdb70c2d0cfd0bff0b3f6032f4832df2bc5c22ca9f792d6543552d8bb86a6d1131a33c3f7f1dc5196c49f4381bac5505fb655f2f419221d6ca13758d257b20ab394964a711b8081c64bf7894e7446113c6a978ec29d3b19ba2299ae26d748d797a244489f2a42d5a94679c731e80c7bd899e2623571368557cd441d7a0af8936284ed163294cbb32201e8e38a45cafdd20e92c8955927b49ee222d6f56eafdcc7643b416d073f373d3c40dd0cd913893dafe9ba01b2b1a47e92d858f5047a12877ad75b38158c2c0b65c2b55f5a9db49557d2604fb5a0f6bf58c72dfe3c9df25942b568fd1a071c05c8fd5a0716cafd86b3d35d2ab520684a650d028aa0b88c419fb7ada13674eafa74bc41994d7d37aca449c49793dfd8933a8d7d3261a87ca1571a519712d1b34f683c69ee819eef518111a23922b17afc75a72f5f22c0b8c091de2d124b25167d80e723da6b31d4da07924ceacbcebb118cf27933ca317752d4b94eba7075a264d9dc6c1f2fa0b49ae3256fd9514672ea1ebe7899fd3a07b814dc1374790eb6554d4ca95e8df0594ebb54efdef01f38b85dcc3fcf73017f2a7606eaafb7d0e2b75531d4e0d913acc6f52877977188c63a4c33730bf18e71ee6387fcac2dcd4bdb7e56f4bd5a5c373f9c815cceb67d2ced52357a9771db6a96ba54a3eae5aee928a4172d5826512963128855baaea4d295526a91c72404a8734e381e9ba2c48aaea3defaf35283b3aca3cefef3a6b934a317e7e7e689665493229294b4a4a2a259592ea4fa9a816957e4a3fa59f52515151517dc62357f54866414212a6eb8e783cefef2c4f2614399b954f9d23d95913922bad2555f55b140d9d904d91ab1572ddae1081c6a3b53423882bd792ab3aa426916bab12b1db85b91615e5fa7ad4bd5e13925bc0745dd7e10f3a212857a05c3b5bbf6942b9a6d8934c922b6d8bc6d1597ac1d28de56a465da36e3d5255b52cb4aa5ec80b3a50f793ebe6936b85c2e681a7a1d322570d0bcec684371e93a954043724106f411b904c6a1cdbd1651a7a26d1383a5756b5495a4faeda0c727df58255c97832dbc18c27d7dbbb3511bdfad3dd80a2578f72b723770b8a5e7dcadd9044af1e7537a1e8d5abdc2d49d7a8cf82e44a631255f543b2242c916e893311942b52bd8bab194955fdcbd58e90e4aafe48557d96594b91727de947ae341ea9aaef4ea59f5c9f25c915e72355f54036a8133a51fdcae596e091ab0d8b56d5d77a14fc65f5167fd603b98e20d713fe626496bb1d758daa823fee27d7734cc499ce75abe79a88ab529254d5734b74492c92a423b9bee3366ec95614bd7a0d73ade8d59f56ae2f25d52ecbb623d376546f326d474f6c59fc9492eaa611895ed57aa257bf618d277af52f4ab09fc693ebb72ce20ca713673eee4ce2ccc9ebb7a3380373fd8614678eb6d8a2d892e2ccb77121d7d7ed9f6694eb2bd251d5c9b51ee57a4d6e2cb7ef8d24b9aaaf3ff0465ed15b1d912d25e55aa170dfd29d6ccb1fd3552e54d235e051f0291726814217228141f0db91cc326bbbeef73c996d3bcaf5a5a25a2acaf5a62b03af1387dcb8c18a7d7048a6b883121fe80452de8e51146c7300948323792b8294b7225b446f834582608ebe59165b5f6c31814724c9f0ddf83b62a7a79094b767f77bfe6011a0e86dd656275a52b5bdef44125d230bf6834ef01849e2d99ce0c9db9dd0525a0e84f2f6147cdcc95f4e90a64dc1246f5f53a19337888b00e56d4950be4e04925c4123494418c915f6ed441cc915fd56d2a610ca17fe9a8a9dbc61970823a9da0e8d24c915114852b57d7b864eece40d7f3d4552de9cd0c91bc5441c49d57623db59833b20cadbbf9ec2c8897fd9edbf0e3601245792b51d36112457a9d3210b07b250be1d1a218a33a56f4694e4ed42793bcab552b5fd74b72b5bfcd9bc39d1ca1beea2b71d1a6999f0971519e5ed2da191a3bcbd6336ec033a81a49de8641a59a963293c91a604c237c2f0235ee396586e0a0bf3b557ed55833c1fe409ca54889ef6348e93d3d31750251a473d7d71faac7519c995fca4cf6ed02b2d336233204091b32430394b22ce9c4e9ff1c41994d36744e24ccae9b39e6c8938833a55397dc644e358393db5226657d82cba2df2b6c3247a43323d6dc995d6d276d094073d633a3dfd41af5a4e4f85e4cac5e929915cbd9c9ec2a0bba90d323d9624579c516986e5dd3791ec0ed5f18aded1cf34f4449a498da33b3d768421c5192c08f3912bec545382cf5fd0a5c08ea0b3cb039916b58830a1be0ef31ed8535848ead853c72e04e6d79b958a4f3d8775dd54bcb98e3d1ec39ec238a963c781f9f51cd6062455dbafa0bc3d75b7c36cd7eabace0ec643afa41b34be974c5199a6be09c10ef3ed02362c8f5a7336da6ba2f6c286e5baf066458457645b1692730086533cb06387390cbeb9708ac7f5eba9a7369b882fb072202758019295fa0f9d5b7295d5d20e4cd7791e1090d692ab52c9f3feaeab317c7c7c7cb22c3b3a7a719b554292484825a4125245aa48b59515c995d6aa3ed5a7b68a327d6947ae342359020a92463b3b3b9ef7779db519cb679291dc82e46a23922aca41d12afa188513b82932e5ac68b2ed6c44db5644e4026f45529564bbe58ce4aa32d18670465a129c1167945ddcd45fae4b0b22b532bd6674fa2d081555f4305d672dfea0138c720065fa43a3db82327da32e391bed84324464a5b62c1a47e76c2bda582ed7ddada86b703c5245b72a5a451fe401fda7f3c994ebc95453421aae458745a65b1632e5764a2520d8526dcd23997240dc4fa6af8913a971d04f2d7a55ea6b080624579155da7822fe361764faea851717b24a3b59666d0793e33f98fc957632fde9724c448f1ee5723fd1a34fb95c1397038a1e3dea7247a247af72b9a0e8d1af5c0e49d7a02f01c9d5b64554d16f4cb22136898ec8b9254a4672b51d19657a17772b922afa97bb191dc9959665d61e65faea2357db4ed6551f94ea93e94b48f42d5bd419dd5b2921657a969b237af4db8e5c955a454fe92dfe32fad3b6f375cf317277b3ae4157e80b10673ad31f20ae2a9254d1d3d3e4e8aea0d47b6735ee8aca1d61ce287af4970afb71464831227596957a95654f6c3ed4e5f45b12d1a33cd1a3af78db891efd8b12ecb693e9b738637a8933dc29bd8d33df29fe3828ba38f37156c8f4b4fedb8a32bd762457f49a915cb55c6a49996a4699debe3790e48a266a3e72055b156fe415bdd511d98a94a912ee5bba93f5c974e542251712a1842012940b83e0912cb3b6eba4cc3e9a5c5b729565d6766d65fad295b9f9891e7c0a17e1728d883a436edc913756a44adfb8ff88adaf93bec8f3f2bd04630b26f9a0501e42b22e5c845461f9cb4af7061f58a49964753a9e7afaab8958ffeef3ae234a72fd1556d46f22d57fad848672d77e51aa1d20a7eeb4aa9ea7a7d24a3fe34fd7a89f9c022a4ed56f18d65bba76bfeb80c9254e074cae18967047a1954f7450628e1f151376cb3d04b404871f9be51e025242f412af99bd64d90b98652fb21798bd642fb2972c7b41b199bd64d98b245a96d1f6608430d2a04993096343d4b3ac258c11c6a15894936453f41941932200810a2ec880154190239011723c2ace7017908e8ece0e58f15d5c75cb1700e42f86c68daf93c896596cd14cb27c7f7088849192892920921bf9834994c025566401990c49a20a199790b1892311494cf2452553c85f5ce26345969734e0c97288065ac8f2afc110b2bc375483a3dc4336384296af714196078207135ac8f20c18628249962f020a59de88a11b504107073ad8c10119788a14a1c7a788157e868a8021cba11f21211ea2560e9214191d21dd20cbc3241da441e7063b3b60034f1347e85122cb1e3282f4e30411509010114f963dc483a32c1f731667be4d28cbc34d28b7d014497913da8c907dc82dce40a20cb7242cb825c9cdd9c4d2155d37e4a36404435f68f101419e9fddec253ea8424a9e73ce39b119f97a2791628a198c60ce20075d0a2613544d6610021990010c43621c010c2be8cce0838ef95aa8eb1c1d637a65a52ac295f686456e4455bc8c19ac4cd7d10091e55b75bd74bb7c1591e5bdabaf0d64e95d38124b298cb6157d4cb4020c482527d80069044d8fb410c1bdc71ccfa41fcc16d0132da0f1424b0648f9eba22cdf56c877f2323159ee2119b4e8921944accd3da4441bb2b027b987944022a2b50f52061209d1d09841a38b4ee491349a9877727970b62f8aca646ed0d050e5c8724ead7236d8c466092bcd8e863703001e958d9a7925bb2eecba2edaa815b9494faa68eebc24362f99f3a4f39a5794534e39a5fc94726612ce89ea1848e3ec0bc503adbbe17c9c53e2ec0abfb0e7f40ff62cf19ac8c3cd715baa240f7c81b9b806b9d3ed9cd23f8c2c78c9d95ca39b86e250a877af22ea5a09439e9724f6933c197ef630e529a89f6e6f0afe687c3437b9beca7a6f78cbfd6ec03f68e496dfdbfbdde4fb12f7433ce18f473edddecf3ee5f56a29d7e68616f615e67a697fc8a0ee77bacb4b9c8dc51d3d18bcfde28e5e0bee6ba50d5fd421d75d43dd0f66ad391d50e59eeef77cd2ecfda1b37dca9b4b4067abf2cbdaffd0d9e21b5a5815fcd5bb1c723aea5b6eba2aaf9129d74f56b939b906c1320a63d086cb6b644dcca2075d60df6398eb05e622c22c05c71f8b4ff01dccaced3aea792cff6458fed1d44896bbf2973b391b95a7ee4bf4506f7977bf9cdcb1e49614fce5e494dbfb5d057f30ab1cd67acf9391913c9247aae0abca57aea9ace0c0aafcebb2ca6ba2e491f0baaed35df0e9d8e926fc7557777b597e6a39ca9b1302f5165c6fe7eea2ce72bf6794157bb8726d4565e5d637a75281e8ac828360d93e7241c03469d26447ccf51980b9e21fa8af5c9827b74326a75ce5167f5008eafc24f920f51c411e2323a12322a496fdd745349efd2793abfd47a3723fd34f6f6e07e4c9292af88b4056b9bd9f4cb6aff77b9675d7fa1aa972835c59e5cdd9a86020327f30a3700564720afe78d87b7abd28879c0dca4fb723109d2d0e7205d181264d9ab4b209ff90c9278f3b72b5b5aa1ec6e4faac56fc6df8078ddec997e7933c196f71d5aca933800891e8d1ba71487b6534cb7ace47c80361e7c060c4e090d135e2bf1b06e0411bf1dff38c4cf4228acbcd43015d63e60d442f7f32594787862a80479228a3a5101262e180d8409c8155682f62a199340a3212ba060c401b2cbc7681c007a00d5647d7f824ccf276864d58e8c3920f9093e1a14fcb962c0c90abb74adecba48c5d7d5d04d334e4e7f5a169c09ad2101861bc380c6491bbade8259d64cbba3b2667de0b68b210ca0c4050a28b88a8c602b2bc5ac5e50b0359decba65c49d69793eb9039148221b9331c02c18ea9e5fdc6e98ed3f22a6a8e38c8954ddaf12100ca2fb987424064fefa25244bce46bab80c41cadf3cda8288792ec10940c81f3542434b6e2e2e93682a89392ac87e14c800f2a674c43cbf247a51069616d1d60c269e3f716662d1aaf8a905518e474747aaa0e17137d6c5c54546a665c4564427c3eeeaa0582d5707c5822b6fce86e535918888517ec94d44e40845355607ca53de33af93cde97dc234283b02731345afe584cab18b7abd29775ead6be5a812cab5e6b0cb9b8118c4f591a2c750ee970198a27807cae5738d440902e445c6404ea6c03cccb31586951c3434f9c80704a6264d9a346175d7b0ad7427d09d4dcc9fe8c5cec7757aac00353f727af2751ff39d2f98fb28a78460e5698b0201d1a0d40e7a79fa2eebb2dc51a0959be5a02a0e3081d8586ee7959b659cda51b37ccd12a78290f312a778dcb4a8d103666b12b576928841d18b4838ed2d1807e3f0d8812c97df3c858720e103cb05a7e40e64a5a4cb7ba4eee272212d4fe11e2e7ff90b16d2dd05a724e6f17297964b2002f448f42e6db95c5a44e5b4fd2146b9a7907b68c808326d512671c6f4f82c1b621f4fab50c9f1b4886a91291639d22ce47829ff6c892691bce48f59447d81fd2890c4418c381267604bc21389bff664689e9d1e3b6dc9a352504e6f59a76d207996154c5530968251f00957fc7979074dde2890c9740a34b31c2dae004dc6f00784e9a6933b91442f4a1ce40a424893264d88323689b6162acbbfe4eddf34ca4e232f4faa745c789e6e9f1fc53f68b2f60dcfa049247a1113faf901029a447312116597372bf566c9899d3ebbe6d2b9d6d27579777a7bb43c75ec0ed1f2141662e4c6e5dd85a4de22790c4961173c892065f9d91afa409119904a1b9d5402b59800922bdaa29802490a84bd33a53ea4c4a91d9ffd494c7fa80f6542ca679c8da42d7903135b391e43527739057249e13b7bee1c123df9895dacc17e3979ee64996d55bce46c36708420fc60c8a5933ab21f6d115d7174b493450f529cf926912de2cc377f72fce6901c5b4b620f2ed48a38f3fdf801eaf191ab26b2f3f95da7adc6e1e3c215b82e9168651a85c6d157e44f6e91618b1a5d17863c9047082ef77161d85272e991a872b9cd921253a02a02daa2401f355a82f6f4c096cb5b6e4abe59ddb1a3388321c955cbe3b124aa4377e4cac5e3290fedd1828d3ab9b38b7be94855fccb9d4952159fba13e9ce23a98a7771646dee2110a4217ff3c8e5c2ee9690000422f3088910bacbeea624a62dba641ec5792404ce884b0c3a310afbc122b8127df041073b20db927be80936e427bcf044d14f4b99a3dcba054e2a3430d04498c92e92ba89acf8c88a184708aba50a3b02464c0a24403188e1f3a29c72ce396717a44ca459a994d1186126e79c534a1b39e9906c49447461d817ae2fcc78c10848ac0470de0c6d2e51d367ad22a3226dbbce5a795df68ca7dcd825239c48d7135910b66801d0132420b2001e9f2764d08382a427828082999d4048aa3a7b228a152c00053a3a80b0a311bb30ec8261041b2377145d10438c28788105387cc1b2e48ec20b4e14c10b51743d48b62e22cecc13cef4dba355d76d77215f4ac0130e6e1a84105a6b6d2dc1ec1f4d861d764cabb2cfd4d8ededd10768b3961ac34abf6e91d28fcd2eb863348d3078cdeb9a9e91adb9878e78e10a360bf9fbbc47433b524516062928967be8881443ae9f9c88a717b439693721bd539e2c90a3ac79e2c800cdd81ba50239c6d32cdf37058165898174938c6118e711391ecec72c6e51d7755d5729f7d01126e4cf7ee640eb188de330d5fe936b13b91ee59a45f42a508c9f628b5c638c3046182352aeef187f72358254df874690ea8d8f317e12212455f1f144141101e39d58bb76ec12512455da8910922b228e2e2b9e42286b4652a5d5973eb803a1acfdeb298ab276680429b3b68d2065edb0891fb98a2c4d3bdc01529ce922ed2db5778cc61323cd82968a5a9cf893ff41e24b9992513e428886ec59ae509f90c77e599e2f8a6ce7963458e9237bb06338a2feb514ada20ca5100a6aa01f9f9d3c44d1bd018a1ef4bab33cbe6b39cae52b7749a9682a97e5a8fb37be69c2450a1d54d20a5286c75f4be1c828432914ada0e46c22c65e2335ecf60d2df02704e8f04280ae8bb3b988446fb6cb8d30d83eca31c60bd2a3e84d9a459eb185099bfde0cc277a970a3b1f77ae9e222bdfff681010504f0aeb89de757474a4f21ea9428332dd30d91367a60cf2bcf4996ad0e9c1ce90680434d48321ac83f29c8941974b0cf58027cf5324712615e7e74ff23cecb1fd8ede8ec9d9348ef826287a2a1772f488521a74a9fc3aba7cf2853f382b6f79c461b9cba3cbcf4fea86e5a9b33ce5027de42a75d31df5eea86bdd6de95a30cf9d4279c68e3a73fe221267523e3f8be40a7578048d60512b7fb2a7855a9ff168ca783f0a75a1905431498a1e44f28e1e3475324fe2ef47ec9c1cdf728593c3ea4caa7eba46aa8fbd71aa69d668d30873f744c513eea993933ae79c73ce39e7fc49e5b84f8e3b0fb99a5a51a61742a4492f26d19bd717a06a7e9221cfa9863c4faedd0c64393e1edd0b8bae31df42996af205b2a767c87eb2470ae105b5679d73ca25c823a0b460e1b116b6445bc1c2a2b460338de62963906da52c3be1cf5a3c4dcfb2ad44b169858db95e42d1ab6e1983691b0796a79c22cfd951fc93373092a704c2fcaccf354543d14e9ae998e9167f59663793e9d7e46caa50d64a375561adb5e22f55aa37dd1f4c2653ad37d592092b99ea4b2b7d714a3b764dbb66d26ebda95ec3b2c725639e0c8f7a8f05746f298058de52106917666ddb36ce86fe6272ed3a6a1c58f40a8b42abe667e93acaf39ad46e56deb9a0def21edd5db09095776fc1428cd40b5139eacdeade2a1d16d2bdfbca85a0aef2d4102a47fd46e5a8577cb3f2ee3571720a6816cb4d5514eadd6f50ef5edf756fc1382bef8ea302391b1e2c3db98347b10465f815a10caf72a5206299577951854df140bdf3d7513092e208a7490194e18b6047c1285f4771267efecae2428a3312338a1e93c696604558ab8fe2104552055f449cf95a8a534ebba6dd5a51b8130c046257748d79b1d6c5965cac08b3e2c87e368af81167e8e7a4415235b9cfedf3725e6e1167b417f5b4788876bea983b5bac6445dc594325d339d6ebad8d7a9d33b0b01caa6db37a7c3def49a78ba3f98ac35996e4d266b712a6551ac111a8a351d25eaa06b74b6a65b5392d5546852ca8961589bd2de9673ce0bc34e4bd74412e38c104278b718e3c4d98cd7fdb2aef15d2288914a794929e3c4536230cee092f18a18931ec4b0c65f863b5ed7bc660e8573083b646a88fb983300ff86cf9be1af8d72f6989b828f390ebfe1a68688390e4f41dc23e638a47adc7018bfe1301e00690a6cf6d835b128893d429484a0241c654882dc43486802094b202189dc4354d8dc43541cc9dcfd66e64e4caf91abd4498c13ec611cef2a5c93c9958753a51838558a8153252144259309008f817164ee6537ca30e336fe9d9c7236275807b762e3a6e065bc5933ae77d57bc4b80c2c44c663c8c0292e47c815d0ac4b55ec72581e4ec19b19f70eefd5780c8ce3bdc671627c460a4b46885291540ade06be078dabaec238399a164fbc9be262dc1477ee9e7715c6b171ef3800788cd410337ad4788af3aeba9018329ec3eaa1bacc65b0100f5fe0467519188706d748553c1052156be29b65037f4d44031b816f1600b0e44ede31b2779b756760bf4e7a76f29314134e0d112386eade853c06eee10951e14b95097bb8a5ea6ba19cce1a0e17c6ad81fb74b38cba7d1d9e0809477213292122520224747294fbc18c029fe12c86848a868ec024cbe41e12fac94976b60d951ae21e873be01e079c72003e8c18f77e13e31ec63172729c141703a7e05537321ee331304e8ac3a92166dc7b8f19f7b0102337315ee342bccfc03d6a7cc6676021315e03374b069671151662e466c655172203f7505dc6656021dc55b8a5ea44939c0da732e3a64c4fa5a49868524c29de4a0af7d44a8a938153284f7137329e4aa1c898b1825354b65b141977064ea53cb57273192bf886c655dc4da5527a7087d67ec6950144e66a514ad69eb0acfc2b87f98ba7522e9ef297a79e4ab9dc9452397794afd0a4c0bcc09f8c8b14cff47217930aa7b8d368c12915942fc8cc283fa9bc26e664538a3bb9a9d4cab9a71cfee4326e6a050f41e3aaa738dc8306567dc6653c95827bccb88ccfc0324e375d2032a79c64dc96aad355b7a5ca05d2dda4499336649413cae995b34139eafe885ebc4518c1c28cf21f7e1cc2c37b82bf994d87af917106e5b18838739a97717892c15dd51c96891ec763c82f55dc077540c8e16f66eed7e1551d3e3b39bceac2132c13bdb73d577ac3ebba258883cc2026c7711cc75d1cfe70327778af77b7ccf418387572d5735826ece1d4094e0de13dc66fbcc7f8c963601c2327f826c65518e75275f21827b89fc3aa91f0367781d0264d9a30d921b3c680dbac184064def007b38681c8128e000eb759306eb35ea260bf1f5d84b3ec628fb9cdbae136ab46cedb196313b78ecd81892fa630e79cf2f04a29a5d426ca35ed1aca0f9a0c3921b0b6fde446cee6243e27cbfb3d4720c39fee372f67da6e6f4e8637c8b55ddeef46deb8fb6d599b3ae8350c51d77e733e85c59cf3f31e0d19334fe25544eb984d1e420861a4810d3112610bc96b0def4723c39b60609b551f5f83c87c437e850afb6d25d4077f75ce68640cbf9382fd3cf983468e9797c9e2cc87cab285a609e0ef11ec0735001c28cfc3b8d6dc8e79183fb9dfc9eb8366a4e4ec7508c04bb502086c9251ac853fd6da1b376e683a04e03afc6ace2687d7c4d5ccea5773363ba04ea893a6556b6db53f9c6c3c8a1eacb54a1e0cbb2857351b7c92c706361ee5ebd5daa4dcde949b6e54123d28f385630bc79947f8d3456022ef50842345189277f80e45207245cee11fcca72da0489273780e876289c66dd8c9c9c93cb9e289cc41871c0e374e07cc395cb3a75faf185669d0e9302c2c29296f8e8525e584c36e6861535236cbd9d4aae3f4eeb5c26a391da7eff0eb62ab9f6ee7541030ef70987738ccfd72f20edf2108017c00ef68accc8e9765f6b7efe674c01ff071bc26621be5ea75b8343434a75c3dca8534a7f9e9d809e6155fd7f59caf9ee535b2068919f52d25057f39574acad53511b25cabbf2e7b5d36d6b68c8a5a45464618923b77468216b2c88d043128b1b9912006241ba783dec5bbeb020a26906c81040a22499664fa08c5904c1fa13852842d327d11aec8f4ddcda27797d45de28c193366cc10d23366cc98819343e35c0aa72cea299f05a203e6ec33628d15eb5cb81cc31f8d8cc538eb23516b0591282a6971f6cab37036b57e72db9bd38e72533a520ee328efcc72181e5eca711e5e13b59ae73c95c2373cbc06e3f4701e8ee3c3733c87d5dc3d0f37957a7eb372dc54aaa62663213d9c87d7f4e003e4ee9b7b711eee759d2e9657cc741a4f0162937d06d681e3dd4df7b3f93fdd171708ccdd258763ec4edffd59fddf1d1e77f45e70b76121e8bbcb7b81c03c00fc65c771940b738c9d0a6773e10f275f3511757a146743af410e08cc28f745cab19b8284c728d357242208657a8c08addc435464d1e51ea2624946a293e9afe48911830346a9e6c3cdf11e2e0f7f716146b930972ecc382ef67b61fc8147cf72dc87332007f6e10c38bd876bcd5d3cecadf7874cb6f774c19a68aff77ae5250f0e31317e612ef0310c8ea84b4e08fa8a534160c791a88b44b65a8b5142501f6e0e0bbb9d5fdcce289defeddcc3cd61e1fbe5644a2477eeac71a86b184cb9306f712665666b32bd394a4da68bcba2579f72ba66ef1724460e01f51988d9877dc7fdd0ddbe539e723acca89bb2efe13039ce728be4c029fb1ce7e1ddfd307cc2457ac0298b83d8e1399c073cc4880e01780e3cc4c8ea489c656426b56387e7f01d9ec335952d09b98790f864c8518cba30290b36559f9383d8e102b800b00fd4777867980a2287d757ec03f51cde7905f3950e872ff8fb75b82e8e934f98b529b76731d9145b537016bddabde2d3399b0be6a77b17f7833987a37678773f9cbc3abd1f4ed621871b24e61c0e7383c4bc03e4ecb31baf34cfe24c3dcd2f997ce3f6ded0c2d2fcb3f906cd339a67375ee284a04d9a3469922bcea277e3cde9a0c137be9ad921e8c6953a5c49240057e670e51237b01ffcc99a83c9723dfd84bf99ed3526ecb79ddeb9721c67632d863f1ccc9b433dce543c3b16da75428d03e61bb096aea32c375a8efd861baedf7043938b93afb7d88b0178fa0933a5e4a4d4c81b302cbbc7c0313860f853adc51fcc57095bfffff3bedb5e3140aa2aabd61b99a80651db80af7129b4c4ca4f39fe753cfcb331efb8f2e1313e6058d403e601b754c9e0962ad5ed8cba9d61b4beae0ffbd34fa7afac5cc3b69595affe74235ff57e1727db5f29c71abb3f6e4ec10180364eaf91315f077361c6a48fcab1a75c4d6e30299ccd75dd0f02d95ee56ad7c500cc11c8f55f677a733627db25194bb92b1a2a25db265945454545e55f17d9fb5d08e4facb9aae1f379b7000a00decf67e3a646c0543a213ee681a07ecf0977df52cf8eb360065fc05fe3299bbc0aadfc62127848d9fbc466600dc0f66188745366ee3326c5c067fd606fe7ed0c83030004ee3aad74800dcce32ee97a9eed7e51af79347c8f0312ef793f8078d3c4fe3defd6e320da824d2b81dbdfe2582fd32ad844f5698c8f525ab72ad7f78390bcb3597bb84fde2d191f77cdd775c1d2673c1b227c76500c49e823fef84bfabe0afc38a60dc895e123d70080b49f9e61ec2c292ecb3454fa6711817dac9306e3fb3761921a9878c4044fa40266c66b6ec277af51867637116bd8a82ae016fed0a13ec2751903f591467ba43974395c34b14340ed36947bc9049103077f466ae4fb95ffde99033611df51626058939eaa864e070cfb396d6fbf066e5780fefc1c37dc0427c380fef010b3172e3c379b8901e9ee3cd8ab906b7a813756024459d24265107e64f0dd1c373bc470f38a6870f98077c1ef9e3917fc8641818dcd13b86f119340e23c62d0ccfbbaad398110307d59bb39171a1920b7188f1c3e06f66986b37f9f47a0de35a6eba761e9ec3aa16a786c8711e7e93e33c601c23f638f526c779380ff039700ecbe2efd69eee07f329e5d5d6cd743f1ab962d36ba4e9dea7dccf7b7342c0b88df9e9de70c983533b6246ddc56b24ccc5e1302e048231f7cbf2fd2f0e312e447299d4e8a35e666badf5516f2d13ad96256a1129c100ccf65048e8542f54ffc12024156340668b81402660abc2d60b201344ad97291c11b95ca2be1e5efa1ce5b0208c98b75cc1bce52ae532390ebb28d79f52ea4d3030f04793eb636230c69e973fc92393e30e19e1486e79e9da4ffe82c48cba8b766df3af18ee9cae35077d603e1df6f02ff3e19fcdf65f97ed3f2fdb5fae78b87dcd6da972711f6e0fb7e50f997cfaaf9d2ef409900bf330fd29817a0c63c8266c0893b040c35aa3721ffa68783519da099aca18ae0bbbae09afa0417a45a88529b33c23af2967f462cf75daae9479e9631e3bdc6e68c1a68248f1318fca9d3b7f13a776a0327654c660cf77ce1d67e4f624547ca09480b00d4dc02e7a51ecca4209dc381ad6de9c19c811a3f89a1cc8f130cec8432886ace5a5564d9c6530c3160d34b863769baeac35ccdd3967420dccc8b25d2a6345d60c4347168c11b200e7ea406bb96261d89583102c56ce3c0246b80520682c0d6e57abe6c90296456fb3b0db2c0d76bc6eb396d0b03967841ed0e68457072694110e41c37266a075a9bd920660074e3ae7829009373c10d4829d12a020091f104187e107f69a584e11248c0cdda00966e831ba90a022092d4ef043a63bc3fe6e83fc793468aa60227f2dd44986aaa8414f14296862086a1882c492b28972cf1b720f1535c9a6dc434548f688d6cf826d2e421e493e45d306441b0eb48ef920cfbcaeebca2ad69bede6dcf29cef0b4462b7c8764d6e14723371a66aa5f78138409c89327af2317a387048f476a8dd400b11fbc9e46db2c06e7126420ce9135ac764119569e24a9b8176552a22ee207f4d09c87798d422077104b4b09f0c16f693c9376b15bde806f2754920aa906692269d60e155b4b0f0f29b110b85748dbe0a182c3c8c314231681d53935b080c36fe9a71c618048b54d0b41c67e39c0c71ebe8d93d67f7ecd9b3bbc140318c6254ca8961584b2963f2bca83c867b46b694f328e8c7a6402b652f3dbbc41fbc32eca39f5d83bd59edf89af304cbf097fda2b77ebb27d72ef7c9c38c524c7f7439fba4d8857f1421ef8f2ec74b25d06fdae18d2da4e672230c9b768880f8fe341c44faa83888ec472d621ffdc1893fd87de1ce4060ee5f37b5e37afdf5fad227f6d19fcfae5d3aebb33b2b9e1b86951e5f7a8d2c5d98afc38abfaa5dfaed66af77be5ed330bd2d61fc75af8f2032777fe384886fec235ed334fc553c4bf7a15dc336cf4db5cd73977ec22032633e801c41af8f2012bb3ee23b5f1708fce4769c726ae21df1fdb8a3cb5fd32a4a7d9230768650681d330fa1fc25040b1f2f2bb0e53819e9a8699a8f2247c8b08cfcba20b88edd4706642ec281eb9d254e0eeb7ae471e1ebf04e213a595e0f8194980611ade116b90f9108a069dd0ddf57b0dd4162ec9eda15ec372584337a57b0d9a78410c2ac218470369411e9ef8285ffbaecf22f71e66edddd36ebeedeb2190498bd0f2184597e9ba8f932e7dc8106000fc06bb0514353386ae5aeb1d316bd993b0a3028c95df41aff784772fccb148260458e570840a0d1488185ef4d87488370caee6e8bd45300e5eeee9e32c6c4c4995496b7eeee9630b08d7d3492fc804681902a78db51d84290b2a09021514da7e4869c0228ab846b5860371067e2e171e20c4df4e05519e64f468706f302da8364d9342746310cf380c019bdc982655858f089d82b04bb5261ad15ab87174837418a335be0479ef9b94d21996cc43c4bf70d7f7f07b18d9d8795e422205b44f4e6514e60e790a833758032c59148e4c178628c31c6083725b496f029f2018fbd11208d0862fb4e6cc27e91076a10a7b2c76738882d0e49d1cad8e3cc1cce66346275a2146c7cfd23ce00cc1f95d8b586339e60df6e77967f453a1669640552d0c91057405a0106223a39620dbb189cf9ab3ee283cc8c1dc38c88de3c8d94c2d94a944c1d29298c3a3b3c30893c672a08cc47846fae07cb3b8c8343852776585d77712012419041100bc7e5382fc7717178533b4acfb961b99188bb474e47841c8f96d36b5a844f5d23f4dbdd3175c4eb031ede5410a5c797bea3744af107e3212704c70096a7ae414e01cd6a79730e60790ae31c216f4094c44abd7b0660ce5e23e32d3dbba597ba8b0341c03385100bc7053ea2a7904410cb883cce0b3ee286198258296ca474177c04968424969193b7b038c1ce97ae4c5db973c89d3bd12b510cd57d6641e6cc6cef446f422b8c66963ac212267e8aec767b7a9a379623715647ab112caba8e8cd883f1a18362961bf78abc3be747abf0c578046f6615fbabd56baf1f4f06218a5cfb0434b33faef26c30ad0c8df4df69e9f3a53c7b4448c31e66843b1c89361fbed1661f9d54165b97de441a1c1da4f163c87b07c0e89de1cd29de52831c218233c1dde53778dc0b35c24ce32f2d2b19c8bdc351695b7ca63562a7dbb5f4ede340cfb0f9db1e76478bfe7084c085f57de5c94a5d39b93e30d72e5151c0068e3462ebd466eb7a3b7650ee51c8eaf9147dd6fe6146c44f47ed0c8273c77a6ce144a7189fad947457cde99608b4e9e7df6dcc4990b70df80c8d8558185cfb2d935127e592ec272ecfea0c93106b119fb01224d9cc93e9fa3c6932b16dcd1cb6663a16b58a16dcc6b175ad135e6e98557942e3cca227a33ab6d141fe329463f9a4c6113585932eca92c8bf391888b900e3e4b92f2bc4f134079c6a03ce77c4667a6ac429e3a7205ad88aaf979052c8ade8482fda68ecffc9ccd3242cb9167b24e0dd041823e72c5806cde8839fff2010bff02027b09a5441b628071f61704cb7d7923ebb79d114e4a31ce112cfa2b23555146aa6472c7c3932a1739b0f0510a1ce4f8971cb0e2e5fce7498f860b27d44352d0c9b1832c2329f0e4af5b9c8e148a90bfb6425eb38b332f71669281c6cb08905808641e73ef319fa20530b862152e256cc8c109ae147477cbeeee8edd0db1abb1eb6aec9a92045a1c4084534a29a794524a6d421bb023b431e5001ada90d25a137607703d8bd08694529a8080958680ad602963ec18e337f3978ad0463fe6993b69638ccc119ff2d76519638e97dd0def17f3c92c718cf29ab223c45840712e8c5ed725e31111bb9a482831d8a4dbc69cb31fe1bcb00bfb81c5ae26f3921884524278c2a08497d863298310c3221614e3a6e85637567adfa3efcee240b32ba6db867ca984bf23f2d5860cc32efafe840328c5b671c1d8309e408c4e194f5e34db221c006cfa2a4b598c56c26a29533cc4c2d7c489d9b8a49098e7b5d2f6755b561bc7d4b87b6a95fc76a53c59205fb133407326af439a4ba73b2c964b1848d79047e44b7275bb22044e402206186c29f7901236d0ec25bbae086722f4e0fb82d73c84104208af39bd6c622f110ee03ac5b2972c6799e72c4d6cd2db7962d9cb8bab94c15206e11bb3557cce2cc9946491bf4e52a4a40a29e418e42b265f57138e902f254ae4ebc615745032f97bc9c1841e26846109567ceacc107476094755c8df96278aa80479526188880b4b90420c2588c00a4ab8c2da2fcbb009f644470be2764a5089f800cfb682522b49ab415675404b4f604e10ca86884c70cdcc4e91822ea080080f04996147676768880d44e814a5d44026cac490813de58e420c6c802164b5dc518ce104558ce1055f18c30c66f4d82c771463c88137033ba99079ee28c850c5fcc001e2cc67a3a60040a0ae6ba27e20c08413af28818040a402851f9ca025a60d489aa8ec0401c8058c249028a11205105c40052888a09288a0029311b2eeeef6011172f7690ac0a33f1b675266314fb0f0fd3883e7212b7a727fbe5c97854b50824496f88978868e872965747663d735a38c538832c2d2a905c20b4613b412b029ffcd39273c651f6d62fd9a98d1eb98bcaed933a31836e7ecee19e48ad3057d2161e1e375cd6be294c472c4a2db66d9b265777777675052011e368700982057a4b4616268c30b754ddc3171e144cd84104a3803f003fb6519a8a58456286163bc1a9b33b330c88e3dc3b28cca2ca318c33d6397638c1d73288c4dc4d87dec7336f6c67e659916331a23ce6a60d1314a6cbbc61170ce6641291cc9f663c71c081f20e535e735fb03ddfd5386724af99a78c1fce564f9a923c2a3c81d68b2bb5bcaeeeeee8e51cad9b108730c5d843093ff72329c17964062d89c58b7a9d492177627463367dfae715d18c5e6293ebc2e2f6bd8d735fb7a4d2c7537be2044b141264f39a544a26577c309610732ece779fd82311a6918bb21ceb07c733912c227c42cce5ca62ccec0381ba722ee5cca16e83296d1f79db3c212cce095e1a71c81d4eaadf5b262b54a292fe7155a8c51da07d5a880200806913065f6f929bb67e4bff92cc64b39e79c73ceb89aac289b905168cef63c19191a1aa12c7dc83e64d3eeabbb9f050dbeb24bce970cd3c83db3d2bf790c675225e72f0c772b20a96ca8a46d15753d8d3a4533224000008314002030140c8804a3d1682ccc13416e3e14000e839a4878569cc9b32c88614a19630c30400400404000406466da00c0c436764055e2e393cb7f7b1169696b44dc3d53a14306f060f1015863a2d2a8e5843b9ffb1ffa8beda4fb023d1822bb81e7dbad20d60725548b59eca7d708e201e3b7810fef2df099633a29698dbd2f1ced3defba012367169b549e4d97c89dfa747367248fa832b38c5672abb70631dd62fb7c83312f247bb3001a6313212385d857242d53ca162676cccbb2253d5c5e75268c23f738cc0ed896aff9c3a49f7b4211fea5dd17ebcc4092a25af46e284d62ad80824a16614b6585ce602bf3971b0c8d51f74d0ae44034f3aea9ecf41d62ad7d8108397e3277bb31d0a1a1c8771c6c2e2776520bd4328b64ca5ef0d7aa8ffc40782d4e095f26371739baa153e4cfb722c8e14744f4cdca76cbb26eb4068f060f03935dc6e692010fe6aeb8bd090278383d7c603c47eaefa506271e63afa1838ed54ae28f32a6b9064e12c10b0b4488bc7adead5c92720472bc2420b75c37d19f2ffee648bed2445cf523c8b70c463f768135a76089b243a87348f645980f14e377a9cea930fc076a81638f0d0d90086e1d54713a8721f80ec81b7ef4c39bda043b160e3243110d5424069a3480936c7a4d954cd512e0642e15543b42337a6cbdbd8b8d281116e2510fd134f9c9f57848ecf632a45b81b40d5be89fc85854397205ee5d25fad1ae49424ceda8258c431a9d380e047c84615b261da9fd3d61af153243bf0aaeacf3c6b18225e8c690dcbe4416ffce18c230257e1d8cb20e0c3e1fe18fcd421986c2399fa401b3eca1ef13d0293c667ddf4869e7858bd67ef8bded878b01c03731c2ff9be456fff7694ddff5f69e73e8a185cc91e73b8fe6a044ca8a7ca55efd7ea8f8961e72e36431c4611d3fada9655befdc3426b1c299cb0487195a16511d77fe3b83ac56a9502e95d8a00ccdb971c23c1dd65823ac470d3418a6fba0523b28c099062a9e98893b143d5a8e1e9b0f2478495e81b84ac1722cf92302aa9269b36de546705a4bd228249def296ff7fad39edfa4317049cde183030ff9e1a30389330ed683ca5bf18a1f1c5d1c51b32529375d164e4ff80208ecc4ac8f2b3bb7c4420fe3904d931f937293c371d5466e0aa0e34b00d6acfb4e0c58f7f476a28e13f9884e0cdee4b5eb55c5564bcfcb3edad5367a6a5797caf0956978b41005a2bba3275d275c5d210e7f919a800383ae15989db5bb8c2f32433a440bc8ecf88a61e53b75b7ed8a01c2e4df9c4062b1c2b6fc48f701d70a8a87e34aa2014adbc13e7245a93bb0c2a6f41108779272ec56c733252b48e88ecfc0c57e7b989db3a5ccef273b165d5394961537143bd85c1bef0447647df647a9ddb3eccdf51f021bd69a5c4814db14a2df1b3986e44abc4dd8c2e9efc10bad6e0edc875bbff776c47ef0070354dec4310362cd23194b7037e939e3c6dcfda97aec58aeadeccc3afdc97f380e9a284383367c04da44e6f14a2f6f6046bf80c9598f35331f1a0e5113ba8603c2f863028743239e6fb643fff67e8948545b549626777658a5a50cc544b4e1639e4888859c598a9279f210a81dcaa528ceb010399ad36a9dcb4dbe86e2dfb9b1ba3c9169a3ca4ff1c4d288165573338e200b8e6140be97975c0a4a01d11b881ba1b0688513c657481b947a58c256a855e657015c8437fe54329753b0f14ca5dc2466031d3003d348ac380c969c5bb6fb579cc23582874b87202068b76b906831396ed9352a33632cb81dccef939a2960823a5a8714fcf13ef3f4521445ad7fa4d496d3dc4efb1c7837a2f9ce1089147a7b683113975e80d235164f2c02992f69340182eee9aea434f4c5b3f53f804bc98b2c1e60c6d0c98471c064e711a9cfcb5111fe990de1f10265f67d28cd21a74a4a6384d49e53e43b191a7e781f1d301129f523d964b37957d8e146ac0e673fcd126e69718fd540016b8314dee79fbe432bc35daa3f3078388aad066ec691bd0709840ed1d930e67738a2b355b4cfda2a494a9cfd26c6463e17aafcb5815b5b48ac8445e24d5f1cfda0b9ef3777c0f24a2f58cab0e4e63fade0cb9d2cb593caa285d84c4125bd80a1c6aa230d2c06e34a2c78fd74dac4733a1287122223f3ce7351cdb88d925b0edf02711ad3fa38f264d2f039299df95d04dcc0b17dcd11f1149f079538689e70eee6257ba8ba85532a9afb879e765bf9c64522a7f5aec4cbd9d8bfb1689f41dea405ad318c868aa5391754a78f8d35b47db53e8ecf87fec02ea293550fef24dc6e0cfc11e93b502d626c3afb2e1600409277155fd28a69bfbf18f2a09f67cdf609f61f8a3c890673951edb4f23d78920cdb9e08b157b1c168f45ad2f8cd73931a232c2b62d5c4cc108516dce83a4bb3260006e8848136064b63b69c5e8fcb9e3afbd61d9fc4623bd2487b5ab41c337246d643af851320faeae845109fdc4600f216fe429290eddaf6aeb6a8648754188c8b3ecd379e96ded7132c80288aec874cd785721725ca353b4903ba297d656f76292b5fa5f4f9be561c4de639a6aa0054f89af41cb7b338048ac33e7d8ba371515f93c4badaaa7d9222ab3d6dd2906da792a2769d17164a3ee090c88c545a3694bceecf4ab1e484acc4d232c63fcee98c29cb492f40352662cc0147d07b2580a19392e91a0dbe45ca52a0252f0d1fe4c976749a875e91a9824a4ad32df1acb490a2958b2b571c5061bb57b32665108f383c4a7d463fe69469322de535b6436ec0d31348b52283e2ac868bebdecc72e517c662ea1420e2ea0be47f8df233a9dac41e771df965e986cd3557f8ccfc73defadacb0c835c53fdd2f0fd4b802e585a67f29d292d08f49912853080132ccac600496561df98a1fdb943f1a53bbf403c907e392a3125bcba002b1581853775d46ad8a84d89b9d0c527f885ff7a1bed4a09bbae225512e8eaf4bac07d6eaf3db807dfa8edbe84036738d189bbb8e0b83da99150ec914475556ce094223ddfa764366ea61d81ccf668c9a81564ad7c843654ef76b2ed5bd8407d61621d86023f2d32b1d5b38784d4179f1db676e9f6f96f97882a70cd33e84d51da044ce43897a29e8010191dd76e3020d94cd3bd912696a4b2cbf873a099050f5050e1ed2443331784cd4da9d0dd116b5681b20de4ce509416829d191ad8f178d8b6a168a1a68f7a0a9d509b59d6cd9d732b57451ba7d59caa22d1c723552f4246b63fd06a5267695481dfad90f4a1646f68917cb45692155f4558c59cb9e6f1612902f88593d046c10915ce832aa5a74a0cf22c141fd5a75c91e608260d02db438b5ae0c2d453b39ad74716da369e58239773c59fabd7cd27ba89d90a52ffa0c3b4624ae55a9d99aa145307dc6f5675e79e3558f1f8cb09086e9e61f976c3f7f9de8331061585d02b49e0961dc181c4d8e048c0529375f4f8395255be020b88ef7e248a757fbddd76ee806ed79c8533fb2f1f8b00f0b42b8e62e22726a128239fbe2fe73ffad1d0478714e044a519fdaf4e4706b801dc16332d2184bc824e9041b6a6734ce7585cf52fdeaaa9498aa3fa449102819f0005b45b9f6667f4fdc400bdeb3c8d4f96a8144484ac9b8192bc7c2446cc3f370c42559c58fd2304f901cbc2cccd89bd5c4db98ccf407e05db64ab4c2176dc67ff2738b667f9706aaf55578e3980512f3c94e1c83fdfc096ca7b00ada08c4b9a7f2981dd8c3f3f323baa278672bc14f3d8d8db265e2b4c21815a78f7d19a500fc55762e262930f4b3128e2a7327b23dfb944c190fd25da2a7a6c2f28809f1e7390bb4a77614e8d9dd4d11a9c4412a6b06f6829beb8d73898743c877264b60ec764d5fda11561eb330f0fa6fc9e29f3c61444838a18f3b7b598987bf87f83cf8a0c707d900fdb815b7b5302e2e0a0eeb5c011cfe26a6475f8608b5ece79af3ea01222d31b9b3853f20b4569878eb9b01778bb180136b8917c6130551a2ff655fc189b32c56875b63e8277465b8259b2dc8a724bfdcb9645eecdb04da3c7ff11aabfe6a5f52026c4cf1978ef96c9f31a56c230882b9a2feeca8b39a187e28e1294e2f79aae36e05dd3865255b32214d4ec03e3f435724c100194febe1452bdf276310313add860c512a98e9de30fcd174571a482cbf73c78e5c08b4f9c49db2bd84bae61967dc7b7e7e807d9ebcf9af37e5c1af22459ebc14c8b2108d54b7fc92f0e415b25d293d9c98c921088d0726366e4762d5c16b62a72d3e058764ec60809aa67c49937576038955367aac259476368557b563c9b418ccb7055523bb40ce828e8ce4ae77d8b8e9acaf4cca4f081361b3b084f4f935099791bfd4b1751eec0aa0d1a604816ccb54923ba5798186c0690090ad100ccab762315eb4d0ee2fa776a9d09933c891169770441c83ec869ed7745f9ddb91d665069cce12176d12dbcca8b1048224a5119d499cd0082e0b24f74a6d6cc4640ef820b6ab6836cd6d374099f74421d39336dd554ac6c59a6328d5c4ebfddf7e5a1aa4db59b44912b25d2b1637e8a185ac0fbbb455a2cc51c3d06059d4d3bc9b72b419c3527e85cfd0ea43c1963f023d279efb094fa4a962a75a8812a24bf50fd36234b848566b364e135b53b81e8ac88f11e3898cccc3ce692e2bad8a36dde9b7d61a6aee17a0ad9ee49284a283424f4d9130023ed4858813e0347fb41d54f96f7986dfbf09875b67ab3ed2a2c0b4e485391f37334b7280ca971cc8638e572d60e46b5fe4ba501c40d5b777741f316c6fd5c56704405ea6bc294616ff95d3ebfbed3fdcb256d56946b6c04e058d732bdcbbf14c4b2b5465581d70a26202dc5d50b49f321dbb18bdfa69a0c0de8227873ee9b67647f8a3a7bf381295f219414cf7021d7b633b7f95b04f43857ce6998beab7ed66e02ce435f378ca9afc2d9c97267c9289dc9ab53f92b4fe7b2b506ce9f49b073d8bde4c82496ea32c16c043674bd820f5acb061b56b0edb407cfc93423bd161eebcc7be83b4127e1fd4609651aef10343abe834f1f164a3c949275cbd854ab9e294e231946bf9d5bf170a3ee72466d9a6562d7656d0525d3209ec8d839fa8834a3af685010b97a062ff3ae0c1a80a111424f8828620829497fef4953551fe52ee21f884a3fd53dee6fa17644471f1c59f7f7e01f228baeba95416b67d24cc975394a85685ec21e576c7446fbe1a4a701dea87f7381a10a929fe6a751e80f5d7a679b56dcd61db6553bd133de15a585c1cfc32babc38e429df36b9cb1b3e93c10911a0ac7665edbb2de33cfa3b101312a0c8b11e14b129197ff0eb9f121a617c44a805c17b44943f5cbcf4c288a5aa32ee2a5080ff8b943be8d6e0a68ab835a78f68c279e43712e350c34c502ccadbb24419da5c3bf22bad29696e0f3854772d50f7968b9ca069a2728d3905fa00329552a684e4e9e5f82d55121c1604db0ee006f1e0de516ab1d70bc0ea68472480b58334b11f2ad7a5ccb5a4032ba260cb212eaef0be6ddf4c95e11c346772740989e9faadc8f8c11876f861e512b894d47a39921ab4c6460c6e8bebc09354fc804cf9b59bdad3cb94ce9085005eac5cf49533945096844f8a1113b7b4af69ee9e75f7d0099d6e75bbdd39c240ebbd0213f73a4df4cbee7052df8dc2cc845f49324955886d000e51c89c8bd9a78c043fd1e48941fa253a1d8fdf06e00d28860ba268e24d049d96a813972283575024338d4c151e95c2a4510653434596a7d1d305581dfb7be6c5115b9a93152c70f647244f735a9298faddff763b70bbfa4d5901bea34bef9f4219f634fa35a93533000336dd0d99116e4d3a23880084464052a2ec469d321ece80db6bab330ab185b32a876637306004978b49bb01081d3a58ec5c3b2f9b3b5db17a1a4d2e9c9236631b8624cd2862fa1c324217ef737e0e46cd79c08f67b5f39db24580b8716bfb096bc790fe1b02ac658c8a3c33cf064d2ccb50905963826c6850e3a4117db24ee9b2bcd77171dab1bb4aaf63ca9847a0e70cdfdf3baeebb801c617eb6e7dee919eeebb194b238fc8fb64705eaf05ca0e8fc4f56510939759682aa7d5277b968c89e172ec3aeb851a16f05bc67c4a6715cd10a8db04dc5deb3457c757a65c8a3c667c5e3ba3c81ceb5036cc17bae2a9324c055af9e7234664c3a306f5450f3162a074bc5048b6fd7663501951c48015fc300a7a97dd7ceb05a8045ad549414d086536d6ba4fb877585d07e99f7106a36e009c422fb77d27df07a1e93137268b1bc8a23be72272f76ffb3a32bac5248c7ff8fc7ad3d146e27950700e3c2c59aa1f5c4a0d083d68187521a83e36b473cd5005f19b35e6edc23ccf897a4c3fb93f859dfcd1f0f26cc45a40859488c8c4a11ed9aad3d6cac4c0c156f8332cd24c01b1bde76017e945da6bd1e13db405e8aa020319a69b4826017a36a4afa4a00d51b3705cd4f3022e88353c8a5d0dd1fff6dfe234ac3abf1e9156411e34def8a982a74b9911909931e5338cedc5342f31e5e251b9739911c2954798b5140edcdbc72c2db3d9802807395a921921326292c6bbef0c4572a97bb5d7cdd5049a2294788648820a1fc9bf143429fb3379fd88c2ca5be114100b2711ae057f598cf75df8ef1c38865325177b55648a1d15b7a5252d04c2edfb49079b9a3b4046b1e1f87ce526545a018a071aeee55747c87919e11237180b5ec6e02e41a2e5f77de1d43155514b9942ebbbcb2ed59a8105e89fafcd38858f8ae336a7afce90c14e8eaa874e40b64c03afca87c1cdedb4261c9ee486e88f09527e4c24feb0924caaa40831326cb457a85856eb55d282780f5b939baf9264cedbcc435326cbbc3b661ee33848592d6acd4d983c34e06a7b5bbdb4af61f1caf746cca12648d9da68786846bbd6e645615496d98242df15a44904dbc2e4721600338967605d57fa23233cd0d4af629f0ec9e6ab90fd4a7412817a33467402b8be0161a62288bb95c135cd23402a11918fd250f3d6d0904f1569af6a54cbe389600542e2fe0b3707eba68556a9c522e8fdfbb31b6c46212d765977a0286045fafb3918d38c38267b0e3a972d811264401cdf3586844982dd59af7e0058031992c77450b0250bd7e5764320797d48a0f3f36030e45c1a21e8be1cf44814d080a7b15e267236f4740a927d028e4ed0c97bfecee88272bd827341c703b1d4c857cca9fbd3d10a96b3e6f9fb1421e1eeec91d1cb30cca8506c120dc7a6c616e370138a02f2ded6940db5c1e92941c416c2f2af7d50ae5381fa970c8a0a44edc0fabaafba29f38c902a34f9f523050d944688ac0dc001df3ae96a435fa621cd81a601965f2985b798ad3243c70765eb68f7f67e1584400b5d9e2729315e714ac75b0a0b32c194a75a3e9c0f0ee1acbdaedd40214324409db721cbff86ca6887b90b800e7860b9ada5e3cc499096a954134f4e765f6a58b33abc4dfa1e0221b46fdcd08adfe3953393f16a8a2a117cc52bf1d0f428209ba78b10481c55803e95cd3ec79099771ed3507e7b5f6551fad4b49f0f7a32c71a052187ee35c347df512ebc40bb7ae94a2389137dc510b694233b731af2a00bd1507e07c441d9c2f219b02e923ed5e5b7a1bd0b544e226474274ec8f35a85130468320076bc9ce0c26dd033b154356d9bb33d46c556988a5ec27b97de18f4915751cd18aba8dee4a73eb172742819871c40f9495d1121f4becdffbdb6617d1abc3152254489d6236309efce375519506ab2fb31c2448b9b32d91efeaf93b80251915292cfbd573dd5ad75726989e843cf9e3934eab3bbd7adc7087be75d787647cd70a5b69ca3337b327dfc7ad5569e23663ca5ee844b7480054b31fe72b96387aca843c0b59334d599441dd664685b56bb9f4db46997cd731d2da3b0089ff13c11b09f98e135666da5306a248611337788444c59ac0aa5c614035ff50f8272cf339acba389309006f8c477867590bf080bddf6bfa523027ab143c68498b11728afac88f684ccd8e04806c91d9312fe8411b6a4937d8102715a0bc838a49f1d0e0b9e27c047a1cbda06fefe380392fa1fbfedc21a0651d93b20c81a1111a7ed0c44cbcb8247cb703e575646a5241a663350e5ee0191bb9167a60cb71321cd7211ea634b735db159bb5b420b343d4073a333091046c703e45a5e43fb48b98fbfba7761d74205c68f733cc25bd16db1a4d633a9cc89d0cbcf3058f4b58e231da5fe8de53672689e03de0ea79afc6538a62d36d729c33c58ae66712493a44c9799d4016ca6e249e2dded55ee119f8d43ad1be5920f725668c8f96465db3a1c9ae67468b8fa006363d6d2fee85ed9eef9a113896a08d35bcf3783d121589757eee278b94e71e4e94e41bac9c0e7bb844740e3abcb211ad2060947948086da401d702af6dbdbbb18bee1375ac4b2f088909309677193aa468fc3125f3d6170a1afd7fb1e551afbbfde8f61d0b71d00ac1fe932c08d62382c1f97af39417f7cea6981b125f2f3b5f7369060f67adf4dfabd5ef3da7d1eaffc64e3178c0df4958c132b673b7e24dcff797937f72567034de94f3318ba0f66c241df9003c2f09f9f71b71f7974fcfb6e2459dafe8f2135090fc1cc1468b449dfc2e5b67d926c0af2be9a478da0005d53f3fe8fa8356d2470d01185b0e45067293aa82fae2ce8445117af79631f84375d7b4f9f4e7855a9457d3181c3d0a6c13dab0a1a41c8aac32bf5c41864cb436c97e7c739d749b124941dd404c9af42f60f4da322fa05dc5aac283f9fa612567b80a664763787b9f1761fa06ba811147deac1207d0c6ef943c33d29e9a031bc144b10b32e30cef6b7d5c7ee4a1762825590e05f6b3afd1212f95458bbc1a545cd66ba2adc3b9a55a2aa3557770c610c6d051239da83d277001dbbf035b77c7ce6da840f04d43030a3f065eb5f74a0f70b78f2bdc7bc9785c5b1369c9115d790cbbd8cad3d8a23d1efc6db23b8faeeec31c15489bee096ec7381f8729e6886be1837658b80a135c4ee7ffa6d06c6fa304994bf8ba9c4e7edc8f50836ce1e1c3d386a1c6edfb9ee95e5b1bb52dafa58f7be9ce5b0e882195125a53c4e33e795eefb3352da1c20872bec804997f96659f111ad109435b95773eb88bea1c124c4516ee559e7c581d9b1143ec5ee10f32c519e3227882d82c4e74250d429c23b044616e0a70cdfb63b616562f6231c37bb53a4e4206c21ff4d9d1c431a65963fdd765f068daaa97d009778b37dbe92fb13bb0e3ebab4821769b56b7347a32d1f0631672ecdadb753719d6a17949fade60ec0f1d942bf35eb5fdef580bbda9218bdde66519d8fadae2c4f36ff389b5e8bc79e67846aa4faaad24854e95a55e654cc4c4da3720feec8dca98950ba6598e9ccacaa09a404d52945f55248e082d366abd210594afaf5101cbda6b4ea4abf6e3efbece722946f79c2de62772118514a3e5dbff9af6a22b5ddd7b152eec6709d74341278a99637a94e297d4cfb40c8e954064fc3810ab0ea6e5a2b8d77f6fafbadcbf05385f82396fd07a0441b857f752c696a469414dc1f63fc4589df6a018d5cb6a54bdb3c59219ffb3570172b02fdd65bddba677814fb1dd29ae243a7f060bcb25b7725286bb3745eb7dd95be95bd7c47c1aac839153f2e480b0b1ff021e8f4a4cf3b34e5978093a48691fc83150f2e875c0cecbb37f6381de797e0ef00dc8b883d25f9dc24a2a034e05e45682bdd7fb3c865a501f63e52af077a3ba2501270af23b49502945e25c95c44e2a72753a338155f4aa2825b35ff71efd112e499212e3b7a81d00693c768c07a13e736bff9b4e35dc5accc57a344bdbb776326d4fc8e870aba582bb7f1ce91ec7993d40247a9e4491a9ef95a5853dd4551a860f1c5b5611e2d6adbc2aa5c6d152e5a546a87f6a3e5a5e0221b658e30cfdcd11b9d873690b833c345182c771b0a14f357ec53a0d611649b93af066f6ee3a460943cdb21f3724219771f1977b5f3846750f6424112a885cb10b10f48ae93756d470ee3ed54ed81ce8cae6c3877db6641b0643689973367125ef406f696eb58c67dac6f1c1767d4eaa82d0f4d35236c998b3c0bbec8301ef4871eac90dd66739b2c73936dd6eec169479eff6d8632302955ef3e01e20193c49115aa31773c625a1eab0d69c01fdbddcf56b6f6a5a799d34d86438af30624bee30d6aa1498514d436a73927182302d2d4fa426634492cdff758548690817c85aedda916b44073f881d0e1ce6aab2e7aeda4cbf24cc324c61569186dad962ec6de7bd095ff7b0c8d52f1b64c9686f70c73a2e3c6e0e29c283811b93e76f4579878bf19a1a65cac5cbf1dfbd53ce1621dcac6b4bf1b5143a90661d14ea2287de3c97d1066a4d405e76c20e8d9ce6a622ac9ee42d232c60d2ea1f672a24414458366e6e12abcdd3918c01d7e70aaf05ae487994a5b84fb97becabc2e6a564d1c0638516c384cb27f148d497f208142b358366adeaaaf229e360b28779999395849d5fafcd6d89d96b05267ada1ed91f9bfac8985d396d38f464407d46017dcb4867d7c5d1ec491d235a0d1c65233840b9f8bdbd237097aae9ada2618c883db74dffb7b2f6d0adb19c76492332b6e8c80d8f3aa6c582595464cc0ec84dbab790bfb1de86cb54bbe24e0ad25b19ea57cac39504a65daa8791c17b2d3479dd373ea8e9f0b7ae0388da35b29953c9d46cf08c263d24e66a91195dc748a67ab3ddc1d1b565acd2a55109b199c553d040501ec9c3c101470bcf49e45f9b40088dd22a0a40c2895f8e28d58871779e145562689e9ba8893931faad1ef3b13fbe728d11f0dbdab3d898ca387ad000b817166914a4dc89800c81c1b526ffc2901db2bfc615a645ea6d5d49ef7728900978c9ea1a02ff31545255ee28100e4ec7053a2631e946ea050df4559fdda37ba1b8ede8ceb446ee7e24ef41a7d0c7caf81b65b67262f7842ac705fe36f803b11032c3f4eabd191de8bbddecc64624c49bb0c098b73095799d5c69b0394f3f884c4be4bad81d892de9e56b8562be2ef4d20ef440d028a9b13a11cd8fa6fb9661524895dd322739c762bc7c13ab66d7912566709ab1f522c406dc6e3b54549b86a7b29fea9bc4604bde5d1b4330817d0d7647be40bfd5030cc6601df002854190fd186ec7ceb90976503dc76238653577055a4ad1034198b32d30302fc9bb6560af0705596ca06b4b6107c5003c225bccd708edb6cfb9e84defdef90357990be679485cb9f112cbbad0c9513d166aa8184818749807b2665e0a08234a81b8d6ae0558c251e684d3c0897d532f09a40b1eedf699639a1558f6c75e3761350465114369edecdb9e04f2304527a68183230327280e61d2ca7b0619efa07e98b133fa0b860a735a04619e2002e9e9a71732bc51bde299beb151d4d5b692711a827bbddd1e43592bfe844ef243065056e7f40ab7760504c6adb9d899c2a601eb2e44dbd635c1b051a8ffbfb1dd8e9029c9d15a4fea5b74c905cd615ad9f8643a9af046d59dd90121092022c0aabacf2a7dbcc82250f451e008646d51f0c655a6c86796016fd266ff7b2ce86f13fe6154a4665c7333fb049d6f7f9198ebfc7936e74340d74c3e745e6149eb31988b046f38e522b2422a6bd154ae0fe6e8beb7aa4c816bd11436a59956eb134426ec501ea9e631aca105dc86c151c0aefac881e0009149a496091be97713360f935337768040b217d9c407828882dd48091779884a834ca2999c031ff13681a1884d3a90819ede08e4b07c8fc5747144c598106e02d7064fe5207613864ab49276eb27a370b197348bd556d9881174626102866a2f862d6d6c10004f2ca0887d7f17f17343074c401d78ac8fd3f47ab55cb19f757a18b1e33b324dc8894f0307fae4575108d37cd7a08aa9c157df624a116a225a1aebb0fa04b38009a9cecdb28d20df2629a4346f2b48fca22bb65573bd28652d02e4709b663d7923586ff586f131a1c22f29f5303db8e5828011dab99bfeab744e816123123b66bc96e9e64e758f805bb202dfda12395c902566c5f506626f0e2cb038a9c7676817dc72e1bf196942893112c3468e306cfe1c0ed432497c921c6b7db911b823b7ccf7becaf0d1dfa618accec50ac6fcbf48b2e01bd3d781a7a8d21bdbe54c612ed8c8e433ca4b7f8a5328d1dd9098ccc63801d171a0f131db61dc46878a0923693c19f599dbb418fc85c7c73b7e04e3ccf5786dce0e06e084541d9e1db27adddf10bade7a791511e0481bcbf048da90aa09bc7bd019eeb51748644e9e6a923a363e1bf5c6b537775d5e722058fe6402a725e61fcc89a6ff6e379864cafcac0c03a844438d9aab5a97792ef193d7d5023d706b9a5b96c865dd4873b350e3322886d3a512bab9f27c9fa91b3b870e26647d52626ba26b332c17a22be9d4add760e3375d028fd10e7a6df2c416119c56830b8e0fc91c6235d39a162a047cb692e56eddf63d7138b0e26aa3fc693d7562be780c432dd54fdd8e18a57146bda6c30623b846f82737d685b2f42141c03edea0f4924e55a45bbbb07031c9778047e62463849dc3a2d25f5666a2b502a1d3593ff07f04bed875ce1105562492876d59193e79ca4135c387d005cc5e758faf234c8128f740c35ddb4903e5cbd69150b9e5e145019b7448c24b12aa0b60c1c0461fd0db1ba51eb4a1b81aced55864b569fe754dc6418de827663bffe0b38aab8cb0b75ffba296d84bb6ee7a90d16ff3ec936fe547877db655672288790f5b9290ed4f9285947453ee85522da2e033a6f5ccd52c828803d458c84647e09ca9e66c564d46b10c85666900bc5149bc5c6468341b3f737987aa0b7dddd2804e9f5d1f0318d910a14dfc9202141cfd79a95aed2fbe82631584c8a44af2584d8ffb2a56c90fc9f1d97abd66fb02bbe384d339fb7baa271dd04cd23f7ffb3faaa7bc34843f0f4353f21d3748eb401ce0e795831e0db60024f47884def0f5cc6054fd99906ef4c46fb872ddf55d3043f4e6c23b3ad847f8232f7555efc0fe5232d7dc7c5b3dac37be54f9781e030b2d2cc1f41ca1674b116127a7ddc50febe8eb00f1d14298791c65381eba2fb49474523e2ec5307a31bc65171ce3f098087a030a79a70cb844e88d1213cfa58e54904d86d2e0222075148152a7d1a2efa27041789aa4f8eabfcb5a43f2f905a6b01200967a61d15f0ab11cc558d388fd40ed4206662ee52f41930ecf87a0680223f8ee0b1f197e21e1666d19f6f4d5b871c07a92ffb8b8e90e30c16213d6a8d2a4367b5d207fbcebec1842e5dd7ce59a9a3f330ace7e29428cb660a06b3436b169ec66b92bff32f7368608a25a3ed7f16ec4ae410111a226c0194e7aa3a98e7fcd3ac238f6f34f5fa5711c37b6081281de0ab688ee667448fdad44ee54da9cf6a0f6f94cd0ed114f019b5bf2f9e6ed21622f3699a4437136159275c437736875adc7654931be3bb41d2e753407be0984e923fca1b4b086a5912c24b104e922c7ab58fbc374a12d75be1bd2a48285b00365bc3692314369eb1d71b85c42f09d747ee0054c0d9eece12f11596e86443013058c81843033b96f2031e8c2c39611d1f6fb4d384109fed2869b4f5313da0b9800b41ec60907fc9894170486fcdeb5835960ee31fe63a0d21e3f8b1539e938f775b1fed43ca09b0d6f9056384ce40d992008ab0bc44696cdcb99d24ab7bc031b40ef7ad59c7bc93c47bbdfcee8f5643c0504dfa659d794fe8f8643a0bc2f3ac9bb99091af3b0ae2784e23489f71d22f42a09e46b4de865befa2a346de0bc882642231c1f76cbf6094c23002be31429d12222de7450de501cd8b2af9202750593bd38b6c35e2cd25fe67819bd523ca87e5c24b7e517b746a23016f645ec988393a89f1122b62fe4862bceb1dea888c825bd5960d4029831af2b9aa1075c647291690fba5f82dfaf332ed2540c9bf3471b8744ff79882d22b5ae7d318d70192b5a12c4460d46cf1af641a69975b7ff07d41ef2a65ff1e51f36512af357c16c414055e5b3eef7b5268ad64650e4fd8c19f4f2fa3a842d1f2cdf0a49005f5d0350141f9490a5e2061dd03eda013346af81468b1855f1b9718020d0d158973a41b38b270e3ced6b50c4ff42bb78b4a3361b121ccfef2472d4c32592a2a3f4155dc202a8e784b661a1a28d224714853636900f10c37a31deb824732a3d56aa2daf861ed337d7cc89e54b648a78666279d903d475f7d0520561a4a2e1c25ca1a11ee59543d90ee4b80aefbd9d776187edf7bc35b3df1efd2f0c8410d5bca2536c75bbf08f976efdbebdef84cbcdc40916b9d38f1a13f32ce1f679fddd7c24f5f926c1a2c0a5ff8f7a657eb0bd21f1a10a0adf82e62abd75d057b0960a08773cdd04168a40ea6b9c8cb1bce3f101b1fd1fffa4dfcb610905f8279b9d2ab87f2db75757ec60c93937bba28a78d31753e536f04f9e84d6c7411adf693c26ded1a4451d08afb978bbbb2bc12591f520a5d2bbf1aefba606285c479ebe5acecba1f008bc9b4afb3503739bf1e6bec461d55dfda827fb3195dab61702d48c505b88925e2cea0fd0bc86eb7bafd2f480562bfed37fbc92b2e2e3904b07100f8da40494087a968425c85bc743e011b137129fedd7752a66f1dcbbd975e6fb14a047ebad528168f98d9491fa140717bc20dbeeda0c0aa4ee2d0498b2e017e21fcc06c5a8dd63dd56c7d93ac939832189bb20e7816c0e0b3d93e7dd3cd51c79cc27397565fc31cdb4ee34ef9365c2812c341c70e4d312e0076d0154dcee2682a1ae2aa9db1881acd181954a2e1743661cb7a3f524a855c6f05e4629578c357612aad2b23bf8b8e9151b14a008d4cdf7d5574d330706ef158b04f941249c139d7b2aea6e5bfa16d649055a7706e8b883c0d64a3331d08a65e56dac6fd367ea257f84ae464b3fcef0555aacfddd6f6fe9dc5a2c28f2b0382069deaf7cae4c14dde2a4af2540967ab8fed78d2d377d70084c2266d135b61410e1463f212360c290d1c7c4792f721b046033d6ed7acf193405b973818af0ec7e026c80d0a28b9fdc09ea73bfb0d0fecdc347d193d107513115d520c0739fd34ae7525dd795d0a4d6a8bd189042303c3944dc2c3bb913d40a918722ad5f08777bfa45e90125afe3160a9cbc067918fff1847147324879610249f643f88086295a6d7ef1b1ba0c6999f4fe41f2ff9b88d54445b94f58b066d56d405b452b3b38c8f51fea981f4a21da56426c1cbd71cba4dcf362e432481e4bcd317b5bd0a67db67b0a559dcc252b459ea1847dced7c8c8b153e3abe034f70e2b29f708783ac9c8dc073240f49a7609662801c9f89c48d55772ba037be420b47a473329e21e033021ae4da13729695a7bb563f6c277ced9bc63e305ea3bc2b1f9c0e7227099230376bbad2091e51edec9e4dbf0d2854f8e75aaadf13a5adb51a95bc19cf53f1d13716084390d1e765f3d4b296079700bfd7647086e9e01d4e35223a303b9a10fc061962bb29667f31261e7d6d00ab8b044ca292b91ddedfbe6877d260096660e23c6b8c41e883763b636c71d787c019b3eee8665c401178af18a6403dcba1c8b6fdbeec3a0fee6b2878bc03f30a82ea62d2cd60c46524e445205c68cce4a49f145e2aea07be1e7e38e41ccf7e091cddc2ab0f708b9d210c8bfc2af7b4d9b7ab67205b53667c8e7f9274e2eaa3c5e4cff1b6d637b802f189ab81b1528f912e0f398a6e9b6dcfbf2cf968822d95a69b1bb98f6bec02f8b2d4dd4e5ea78dd4648d4e9835c2c5d6ea6e57a633033fef44eb478a056a485c7523348583bfc549673c39768c9e0c51649f6792be29a213655f54ee2df44610776c42223ebf6eccc4fa6eb7f122fab17d82b85941859e556ec947e5af5ed293382485a5dd2e8fe0e34edb3e6a0804e92f23a4bf9892102abd65310a2d65b1fb7147d5a29155deae468fb8e470296c6a9c55f04b2dc6d42ebf8d65e6ceafbb42cdb526f7b1af844b7185234b687b6fdb64a530c5d63b60b062db1a9b2445b6d424f563f784e0ec58c70ccfc6c6ce04c70d2d7e48767ce7ac6989679918446171e50791dcaa69a6125c580e5dcc4ca1bce6eda84dc3539c95f658c37075b16f6843b3f5d306cbeacd5ee5c14906f58afd327cde5c43d5cbf7530ac0d510b206ff85da670d5ccd1b4c40365287bd48fd8ea07d33241f7da4b68fb0b4a4c55f481f24118143c7a52c5ffb219cfdab489303321cd0dda1232eea3ad777538a4a7ceefe8b01e7e10681d51bbb283ced841ed0d0df633d40fb174de1397c9b193c0e638828e82e2c9a20f59329ca4287d61feddd39fc3e476e4d5800cf9fe1f2ec61ee4313409af24d344c38c57c74b3ca3c0d0dab0ae4e8b91c5266605797626ebad7ab3238d4a7411d554c2c233d6c96f0cd15fff9c548cb6d38a7633359294a4c2d203297e0fe8dbb2392385632e915ae5d230d9eaa57ae5ab014b4519fa4aaa953795f9385ff9459b05b7f88f98cce6821e3532f27edf388d9d98fceb5b92c3276c4cd6567375a29dabb52a75da242b7a314809d267959b263df1e6f373b9c77d8861c95569c9e9797aa02b7883e0027637e1051995b20aa1e33db0bfa6a0c679088da269cde95258d64874164f02fe2a716bc6805bb2b5ae2e85f77508f28d09e5a493fc7c4b4ff811d0b205399221532c678a4fa0b95df74ac3c736a31654306a9dcb4e9465a15adc26c57442449a8c83319efdecf6d74592421c0860b68aaf60728912ece1ab8daa507e32caa501484fed83c9a5689c2de2ec0af973caf23d907b403a64b6b1f3c21c2e12fb07afb69b89aa6b81718dd20eb03e086e3cd27a9b64ae9b033b693fc4e1592387b1aaf070269e2f716cc694246582a7a7056c2ff46985a397770620a0b33c81ca8c908f889cefde81ab7830d7162800443649c13961076ba66bf3ceb306c45cd5848f8b6bcb6495f7013fd3e84baad8350d369f53c105739851238a9f0c3bf7e03cde451595445093100e63c376aff60a216a5d51c43ec80bf1fce647237a7f22bf19c83199388be072c3982af0259066fe01afaff41a285f6d990bf365f5546d455ef7ee8aa0c71fc95033068d0fb35e36278a00701525151b92729f20716ebca2a2a91aa73b8b3d7cf1095874df4b74ecbbc19523818c603877fec492228aaabcb49cb0c5f8256267b76647002d288a0d23591f620067e987c241bcd08ce276a073e21b3acf61331edbec6bd129c2e7ea3df45be5808109dde59d1baa6456912390779193ef5177f0ff4e977552ad603758a1373dd6b5094a626b55de0f336a058e390abd183a49cca8a965afc770cd8279be614b1ab9d1e3cf458490030411c2e1f2f04edaf1ff1a33d3e08961988b2ce49c2b0636a076cec93e6b2c90520cdedac14c4a87443ac5cdb189a42223ee196d21483486df2a64820c35ebd7b680b1b7f6a3d5dbb5756a7ae7d79a82ff82ff440b4ca9afcc1ed44dab63bffc93a3ec0d87697d2290374b472749fc19a42ea1db1dabfc41cd085fe6d52a1a557e8ba08e9d01c7db50515c2a32e3c5891a7ddae9223edb98edaedaa01915330d2b84e2e27a2b90ab1098c149a81b60b522e094923e158d9fee0f9b099a281d6ab3e9445ef4f4fd062624736cc9e620d256743c62c0d387c348c2fb60453d74f1a8a6164cc68b81077cb0d29d4d2846ae974afff20a6eb3df9e464f66c47b4bea18301c2eac14adcd9bc56eb6a12c7b6bf45c0acffab7a7b1fe70d69f6ae5093143d7dd900ca021ae1c97700ffd1b16617b151e8fcb7ed40af480580c9cb4a4c92c69b383cb88edf652feebd84bcf52b30df8dc58f485d0e74a8b2fc908bb69f2271a9412c9077331f537765b857be7d6ff48e0601b03755eaf7e5d2434077215131d55aa3a361ced31dd9f7d055e6cd589a5a48a2d43b0121f94b6a8bcb8353526b8c16ad83f7501a2df9bb237f25fd05bd8449b295775fbb8b6abe34f0ea66038f385c2451ea912db50134c77dd904a41ab0c54c1173434742c690ed384ad073ddc05144870c08ce31fdfb5b86ac5fee2f9e49a276613da33d682a862fa272236ec96c36015446e356d7adf5fdc197d757b7d7258eac95d90481973785b6471ac802f726e17dc824a5860dfd60f98f253aa6bc8376c71bbf88e14fcff5f1b0f473a03229308410fec36fd004946a8b6808219e95c75fc36e9dea6cc242d4d2f076e3c17bb0e7602a8012afe9450778acec6ecbe39d865b8e73ae85f9f3c448e0d7d5ca2bba4fadaed67cba29b985e22a2442daad690380f751dadc931ac9285a6054fd05e01a8ed801b0db90749644673383fb808a8adbdda51000bb08c522fc5768e8566b4280b0408c36d9a6d12ef4bc0a31095babf53c64793a3b794eac375708c3769ca46786bcc2d90d8b9e2f8dfe18453cd24f1a495fab30be5130fa2e4a5b1d22256f43176c4a58028ff12eaeec070eb35abc8cbb5beea7a0d3e390864b13ccf18d74debecb0f12e4db6b1b2608da8bcf3c4a6c85eef8db244b691d5732cf20f0d8aab1f5b8aedbf4777b4f4801a491f40ac7f5735ad60c42689138cb2d648ca60fd67548933c869d5f640aa29fc533c928911e99d375fd8a7ebe28309376500c48970cd28c020c7f3a338fe889e1766b276c6d175e090263edb794ac371a1a22b9adda656a7d3e3712cf20dd1413999b0ac37f0ffd3227c40d136a78c74592ad3d91e89becd842ab08a3d0b16d0ea230c5994ac0bc829477fc6e8891778006c50533b83a68542d30c36aa914408f9190ee72b73379b1ee6cdb9d8db26cb50233137ab3cbaacd0736f5ac3995804fe4949bc0dad0e1108f96f39c07e1b63081908ebbf9576235bfddb489460bfb94217d09d176947e8d50dbf4dca0fff975254a3b275b297b6930b2d04d9115e6865208cf1097a0cd1c41df0f8ab22434398a976fe8872c37d4171202fdd969cd66959ff487001c41dc62dbabef24b40a14e733f824bd4bc401cf0961aee662ce2c08e7e71dde9da5e7fcb0464ecd7c7b652403d712bb9200dc6c4b9dc80308b91101febeeb9d0ee049af0670d0205307a09d386e1016ff3e0247e563addfe562bf8f1b589a605cc713459c887111c1a6bded18b0a33eb3db58dec051796312261028fd66af69e9dfb822439c9a0274162d61a1a4c72cf0b2208404114838055e53ff0fdb86856d3c6e4a28365056c9d28b19ae8fcdbad74e631bfe053aad406170f9692583f09452b3f298dc7cf2158b4429cedcf38619e620dd60723a056213103904f39505fe4b956ac0e7e4b4ce3735d2db55e2400d27d5201fd2bc24cf45d2bd8878a0838383450e6d2826565c180a2cfc76cce131ca61a05bbb11fbdcb8bbd67e40d668ad90fd0a11ac827a5d07cfa0555d2b59b8f2f2b4f90e833ff119da56c87108581c2d9b5a7a03c70c39e1005f18f72845be647e7cb63c6bdd8d58bced3ed7df99f355492a32ebe824ffe90b33e8bf8eea189c64038f7870ef9bd1aa9fee79a43dfb806d11e55e24610dd3e0a635a2d039fc55e449d1b62f22c1662f619891f0ab0ea2ff76653c17985fd1b25881be516a66449f1832ee84562b63bf104f8d327b1e56bab6d6e14ee347a64b5bdcb23cf1695d7c85ea181e875c570ab971cdf2c80a15fb49758caf03da703063f19abdb6ce6e4356fd1c131efc9b2cf1d3dedf968608391ced7b96ee6d071c6a8e38ae76cafc922c3dd99b7eb5c25da32c8892435d119e8533fc3bf65ebd02015b7013b82996ec98d18793a6c43625f0ff004762ab4df9c04e83b4768ba6df27a8b407f5149907819b5344f0e4a76c433ef88880293866ff353ff4f4b5f35789694ea52008808e815026fa225f0cd3636f65846fed129935f410b495045dedc4e2a706cdc292203e0e4188a27690bea5f86f22b7b5a857cee935cafb9dd50ab51b32c6ab96a104271a12afacbd795976718602098b257abaed5d77d1eef63650f2db3e2f815b83a359f31a74486571335467c4640d5257d731515bb0ded783e589fb8559e3d7f17f455115860d679930fd5984411c39c2df51e320acfb8c2b8249f0f80501558bc9d97e4edc268de6f98b60905f169ead6517061521999f68d1e38c251ebca319e222f8e48f084eacc6ce695f847925a83d2db6b75ac221d6e092e7338a703e66f5ec3a3a4930e1ec40a7805b167b1256af10622fcf44b11dd191f4f145ec0ac5fec7c99845beed924138ae664b56fe112ca3ff0a2212a043651ad8b579f32710de1f11856dfc12c6be0f817fc81a0e9e22ebc75806103faf3bc26ab1854291ba3ac0649a8aadb04452e18456af98617a3fd3538f315e060fd875bef2fd0f58f8c2bc0d134e14f71637cf880c955edad8e8b611258b9d0895cf2304e46380b28b1c598043e82dd57e44ad06bc617f78ad02b205c9a0d292e0ccb903a834989365267713e54690647ea678a0e30f8545f43420a700b965204ef96330855644eee547351a9153ddae1175c5c3a49615261002a89a3c7d28061d6870254bbfcf8c4d3f232c3b458bbdf452b4ee34f22b3848515cbb8621ae96977019660a2a566dd107b2e69d8f88b8b63a3a3db4c384833cb5dec81772ed77ad64ba5d2f574c2cd0f344976ed58deba32a0351fd15b151547da3ee939f239e01c7243e77da80e84b77ec27a9ade9e837aece030d51662288da86781348da2cf9e4d91fb1cf94d77fa9d82927fd506624718b8cb4168d64f1be9d785abca0fae1fe967e8bece4a59e416fe8fcb06e2a6bf560d9025878d4ec80b8a1a000f3460ca67d4d0f389276b674c70b4222c1c22c5bfbde3e51faf73e1d4e1457d17d911bd02e2e9d96571a739353a1a39d22247711eb86f688155a2726035b1db07adfebdec94af9755e8a48273d18c61d81e6f03afe27cf96adcada8b8e2422475af05eacefd52e17a3dda8c655a41d9b8300f1e6f94d59a743ec7ba5245af0517981f8d9e6f8baf8b86a0c82fbff35b84fc993861b390bdae49f3c4c73768f4106be0437f20d00e5b95a3cf611821aafbafa8c73e48c18a7012c4d20f6cbe87e71f5c6fab6e44556e79f75b643915068073f7a1d94604f7226ad60633ed37bd4ab16e4415a7b80d7903b448f9031760807a180d55a7f1854579b5272b11957aaf1612d02750c1b0ed4a59f6a0260da01bdc294e39416814845ccda753801cba9e02968325471d550cc2b2ef8c1fa0fc8700126355ab49b98e12c2b160ad383e59a00692599dc3a35e8b9b4467ad4f78591a0b8fae470077f55f1cc14fa24a682c61762b12d9f60de3c89110c2408f3a83c4a2a9c87d0f3f111a71d5a3c35fcd49da62b08414d6d34a28878cb2b1a625dae9d88dbb12b6385b1ce48e25584b561963cc230d98d1c2b6e31505279a1f0bf4dd9f44a5b50108abce5b4903110cb5afcd61c89c91f2ac4be423f7de443819a9b38cef2b501ad2a42123a943856ca3e66f327b4030242bd9639c4901443a99f54c2c08d768bd4bf755ebeca478b2aeae56c936ac081d81c97b3081a4a48d49978870f31f7f774a63f0102348cd06621a8465bc756d11f7edafaee277d33e7f2c35ac543f025e1db35138430a298f796cb30bf208294ef18e18ed8286233e477bd1e21438a8088e5b88fc664bdfefad96158f5f8db305babc207f361aa787711edcd0040de5d2b60d38652981e2f1346fcb1af0412113ce3b1340489010c7f373f1b4218e146858c017ebd9305f3524de2fcaeccdaa814c7dc88b7202fcdf6aa417745bc1ed88fe3e2bbbffdfeae08fc3196641dbc86d76238e96d608cc0444359d7de34765505d899cc0d538bf10ebcdeb8d945abe5a1ce710b676394190b80a76b7f6aa56ba74f2a7c86a6157ce8890a5dfbe0a62ae28ae7347f0bd44d3b67461b7d85a6739215ee65c9f304f1d64d34baed53a40b135f1080c599e30fd504f462631e69f743e766f7a8514ded78feabb6ca781789b2eb83ebe056286d212175c9e08c0a5f1991b14825c501f3e7b8b59e242a11768ce43902711722b383c7e61e150d4eb1dc3d7b99dd14b1565b2f98f16e59e3f6d3506d1ae3c1e4f6b310da49462744bf307a6b1d361ab45835316aa96ba83fe8e2e5d5e2d0b836b396ea62de9b5f83e1f76cc1fb7adc9c30983ee0fed148a50489cbd1435bb0d69d2d8ee98754690fdec4ad02506ebc30f255bb96c281e1dad74ed2a0ac3a477f597d961b2c7bd0627a42fc11d00d04e5b59c950ca94d00b64bc0945fb3dc5e58241b4c3c13b846b61424c6c86c751c55749fbc48a2545b6375a0b09f5ad8927359de381f0c4b1294a50d8cc73ba487915775640afe2c3cc45a7f060cb1ae44b2931ac78a2e5539b3ba79184d775a5fae5df3d433e32c312df49171bb3c7ff1206a515562446b7a5a0b399d22247ef327a5487927fccb78aee4d40fa26f7c0757bccb233d8e2af61d588da5a51f5a00f32ca59af193844e11787f86d1fc968412c1af469bc8cccbfd775580dd646523d2fb63ba43e608908e372216bac21f61bf3a359da80ccc9fb3689d5fb058f793d2bbde05e6f1339677b48a606868818db2ed04fea2c00fc730f03586e116eba4642c404df12f6a09879ecd5d620c7af27ddd8e5b72a3790c48239ab4155e147d0c26085b1068686cf17b89e8f62156670e25f1d1b0234a2c8c2dd532606c3f2b8940066deadf66f56a922b8c9cf60da212194af4316cd4536a0de98198bfc9388cc1b7c5cbdeda337e1ba003451175a7b390a11d9e4040894ad34eadf3117b6b3861d7ce0de1be0a2404a40f7de0cad44410e0f892868a392ec0090cfa482ffe87de1dba5402550fa24fb045ed2efbc9461e05b52ae48b35f65185464cf6b462e063f4f2c6063dffeb4848b50b52a0a14105f89f0aad3dac2c35466ca7af93e8723ffdf0271dfc73506cdfdd8320a28137221b7fcd608d32ac486c2909c1ac125b05ae3f11281019adde1c1329cb3df59849f502c88f1bf3b9dbe218cad2299ee2b5cdb14715068dd52809b001e98740673798fcbb4569e6b557a9f6daf4cd90b4b55f6e151cc2fd4b9646e97136b108fde318263696076ee49074466211ef7c49c13ae075e9d83845ead3cc005304d81fb57cdfe489f2594235f94478622c8bf8383ff6074a8daa64024811500fc192f8c1a925a5db63022e2821d52dc8c53e41c5134433693b7f67e0429f6d0a93422a684846bfe3e797c3dab31cbd0e6dbe76f6788711d335c3bdc070c8ad0cb4ab20c22102d82989e9baed187e4e21df4b23ad043d91383869311668ebba68721586a097fa504823f94abf8bee2c60787b4e58ed29486b2290f005ea5b32ab0df41b648a4b8a214457f8d006b68db587d26a474d55acfe7bc8bb9a795e2c101aa468001dccf1271148b7818fd7728108d3e94ab8d42ce7ea4e38e1e8429e985c08a5b5e2112f514f35ba2f6efcbc52b672a1a6a1d8bb31a3353bbd89267469059d30207dde74a498105c20be18992bc0304053b1c45aecb1e82d652fd88a57e6d875f16ef84f3e627ec84d1179fa9cf46e212ecb9728c4938b398fa82b1517b33f1940bb31e13ee69adc6d9b1ac1c4de8c40caeb788364dc5bdef3805cb6e2ceef695627b21b443c45613a8bb2f2ed5a0f2efbfc2aa0d428179a308318c8c7e5daabbcc30e7849bfcc11c8db56ea1583f0de3d322cba806c9249c5542b2c9877ede25c23475ab5727d3543e7cb7a2037b9356ac95ce4e7ba518385a5bb1a752e9abc41d73b3f36286c2f0599d3e9f23895ad4c5c4b442811bb430c470e634d0751a5c5eb0b4eba30f482f35b67acb748c7a7df179c043f661a2a1531e30fa6a7ce50f9c2162097b0b3d168abbec56b36d2889ab4a8af033097c9aa15182f3f3baf6508bb669e14dc0fc26055f4699522b318cb6fed27025105446dd3c9e62d3d796934d0fca083ef3e54123992cd5be88cb6aa4acb41ef6c20e394e220d368258ebcac7cde12b2a3231441db814d82110e9a078cd9c3afa8e7cb06bbecff84ddaba5e21d0a20f66938dbe31edf9d6f0b28b8bb04167eca3b2ffe703a230e8ddbd4bcbe576903cb061a907e2d01feb3220be336bf9640965e64a2d71ab29ed35b0bbd0db739a0c3a686f44b2d3c4538836f9bd32c4921b8867036bf9e0df731b46458c0b24100ab9daba55026c39d8ea78eab8951f7248d77b119191f91efa5d7b698de7e209089d1b452f538762a8b6936a7da646460e43d49d78424a3848790bb9c2b9bf641b1dfcdd9ab695301333bec634e610165af8860cff21e80f301cc0053cb26351fb4160aca6e99fc9c1717d635396a8052fa97029219c6570642c519ca55d40883f1a49b1b94e42d44da967bd7983474dae43a0d9c106ab6957f7bd7e98f919cd87e29f46b4394ae40724246f783dde38bf5b286fdaeca31981fc047567e7f6e353833e548ba1ee51ecb2cc6f575737568f203bbd412c8642d36a14c10347b3843afe9079b67adf8bfbd888633c531ce524aa8c423e4c106a287a733c2dc49892493e29a394ca38c289136c34fc730d6400d66e56f5befb615a3cf83910b1a35f704e7aa114a843f0bcd23fad81276504b8f31298046e11e91332dca33890239c82d924439f258e00e3f4e621b6ea4a645fa1c451a0383913cf7b9f070861a92b4b935b6a513803dd7e65ccb44ae88611f7af66ec68ce1afc575f9241afb39943550afd4e8cb67ca4e6c5cecb86de427b290579a207cc7d8252b27062dffab384ba338d4cd6fc2573f02d4a0421a51ed218b19f2f0fe3d81eb819f81b4c6b735a31edbc59ebe5eba4b081cd151ea943052f72e036b76b07a70455ea383f291b6904f1f9eab6c793b645c73635108abf378a9571fa6a505de763ec58f9195c4264ba895c09c3be0bee4644b65b029d48f3c48c16859f04251fd2bc24d5d83fede56fb05d8ea311abfb620e56b13fba59fef5f22c98189d3a94cbed22b0bf73396d9ed71a986d07bb75e56f6eb4955ac218b1243c205ddc5b6085da66fec5fbc41953b27b7b151e7317c3e6f9fa9006f0f2f89cb53092486577244abfa99065e705a39e4ae5e2f9182626c9c67fb0c3c17870e6a3a9514a789e90dfdf977865d22ce65218a910214de41a7894766e9d41b80aed92573c0c12e1da0fd1592a78bcf60f9f4fb0c5fa1cdfbc792f78f9714ce6cdd692c76e05684158b827c2fa26f03a9dcace322b61a76180d8e91063a8807731bf46b5a4f05db19373b88d85e5ca06893277c57bf2c1d781aadafe2256b599f2103d19a9ebc7c75c42aba47e25e31883f138f7e5b214a3bd32ac84c41ea1c60dd43cf2aa7402729e7f6cd68bbd47e26426a839c8aac6faef6552267d0d7894bf5e95618a6d7bdffe5c5ede31515e1b4ed325f5f1550f8886138ca405ef357bdc79bc0115462404d615762d78913c8f9292bc007c9f9abde4ff78a6cb6f0d2640ad4407602afd3825f50af05fa65db34e8c8ebe1460f776ec01e512e4b64f62b81b3367c1572b7489416368859071baafbce97d325940304d6f58029294e354f9e03f4d03a42e4cd0e30b88c61d9399fa25f076f9bf896f4733a986d1d9a63d17e26705f1a681a36bf5b9452383fa018bc48d01a3f9d8344204c989ad191c1b6ab65993d57ffca0e9e931f34883821825e8378a74e824e48ac94607602f5f2659d35d41682c275c861afa189b898101bec6e1470a82a16ace3c95a03e878d8b76c6dad9578668fea2d0952778403f082a683c5df519c0a2580eb9dde228e0284cf84c2936d3777395d49a13dc50d38bacacdf6cf2003d309196c92c17983a179777451abc874331d342e02c115ea9147a565968cd52749918105793517e9ca35af49a18626843b6f1a63fb087e3b4133f55cf2ff79ad6b113f40e04420aa1cdc447c6b3700b6041acbc2a17fa81712445a07f070db66f4edb4408a8e499fa7cd821908bab367cdbe5f235e3023c588d872f3f415ffe0c461d2faec735a339303c63e74f57088cb89775dee600eaf9a53e6c9608dc9a8467d8bb5b4eebd69c2ed440927c45337184dbc906963f627a13f13185898faf4443dd558ba30fdcdc4a6144333206c945d7db4d5d1460596c31b2c00e435d6326e95e239394fd8e9bf411cdadcf6f9899195b0898ae37cc25720ad74042788885fd66da309c6b13a0d73bc0678f0afc6b0a71fd7414e880da125f5158d23fb53567db4a42141a121e7b2326c50d3ab3ba34bcd2032ebd41237c056575d26c85c426a8ffb575054ea3173f1c6833fe8582a10a269bbaca3d082ca1a52e5553c871f0f82522deb356c416e88538dad8e1b4b912c176413573507db238d1a725533db557c2285d9304e2b7d19ff4c4d24fdb48cbd9b6f19d9194ce5c3424431c0fdd583ab25305ae6cf6b25811edf0f01f7224df67873b293930dade3e3672055224dd0f9ed455cfdaf041268c8f49a7beb51de0aff27365b215a241d71f41174257eeaafa0a68beab5a9ddab171a9db0721ac17aaa90242b6943c394feda96730f8f85312d12c27a7a88e8049486f162b3ce5b02111db0fe360ca416d27796f25d9d2e78ed1e5ebbc0a20b4083120865fe96daf9125256bcc530a291541fe87c72eab57e15c9643d2a61e4db5cebaca8f52f9a85a0c78a40ecafd1526a927f18039af82075cf1bc0bba98f7a00402c1b803f4c574f6d40db76cee91506d0da21402298b2bef6187aee3b85f7e153386580b82914a4b3c69cecdbf1a108c70174d933c9db0f54bdf8f546b514fafbf0512de6d20d780a002bda80a5726055bf8a28603219d862fd92910297e4ed0227a3437e42ae3f78e8e6e22345dcd87ea404d0e29f6818bc82dd1da4109e030465210fca758bf462a474af16fab51128c5cdd66808b9e038eed7cc7ef9956a00bb082b1c4685bd314aa0ba2492cbd7d10bea7099d18d1d6a377bbf1ea4db13656f600611c12a84cbbed41d571aeb9934036ed40536fcf813125b2ebd953e25bbd2a45cb7a24ee5f6622e8ab37465619bd178175df9b7c95ac64367378c3cd0140069f1a771f53c1aee04e1f055038c566b91b95c7ca22655c860f06904f68b7e18261c129e2dc1066f3675361e66e7d2ab46b72a4c2a377378da04199aff21e80ab70b35db6770582ba46656d2137222a01a683821b72e589c8aa7f49217a74c628dd2017a48bdf83b2f3462487b2606f2f47b1eadd7abaa23616f5adebf77a39d3d323d14938ee88ceaeae23e1b4c9b3e3ce1f472632f5b5f39d76c946a67980a135458b3ccb1c9509dad37b522981d514b41b4c131642b50067fcc118d524317d9e9bb442ce396a86e5b428b6a40b0e6e9e857065be7c103a77ddd2532889c1354383e1f9c160afe5590bf756109964587b7c9f3d92666eb9783551511848096b8d8fd30f17c9642c58cee5c0d6de55c522906e7c45d0939454bb44fbb47348db92fb76d51b2e1d7b32abbbea0d179d49ee3913f8165dd8456d2ba61a5000ca6596b3b9f3723523e0a7d82bfd26912cdcd8870dafa7bdf5b480290a8a735d157012a965415686d529745418d21602241402b778291d72f7fc183896d77ee248d3fb8b61648c9c9a8e19492e106ae93b0cfc4306970a53bdf9059f151b3001eea9ef41e1eb894adaaf54a13bc734fa0e6f6112685be9f080717db0122d99faa1fe97e05fc1c57e6cc110280d9ab0dbd8ca86243f9fbcd6dc56ad9e77625902dcbfd7b5a172cda669cae253cbb95c3c97c18ec3584f70c4491f6d34a2c55bca6d25d7c99801f94e7661c0a03320031466d9e9adba4e1981b7261dd844d109016712db57388c1d35d1b999928634f84d5ca611cac5e53d2e2039b2d2c38a0d3ca438a9502964a21ed1ea5ed8c044b3e022a4aa02858f29d365559f77471675ef0755b25c5bf04c2f9f3b1f39d920e1b498a3f92020833c2755565baf15e96b2b266a118d40b39653c63033d31ba1db30d33b5f2e9237b2156a46bbb2c74619f06619a53ea93bd56ff1fe58669fe7511c7a990ecc1a5ed2bc9e6d45f87e5196d40abc44772e37ff7f101988e8a9ebb9ad25949d694f124c02153cc6970290d864c9fc72aeff8e522298c4cd489ce25e2789c1921c1422806bb805c617c8981f9af5fe0e4e9b95e6e60fa69beb19c5fca08a0bea187eed957d3893e77b59a91fc97d7290f6c23b4e17520f9430e3a898c7259cf76caa23d06ccf16d36e0c7900dcf3f1c0c6916d425e20f7bba9b45a1d09dd87e58bb85a621ba8d74406a767ab59f97334fb7bcfe113e966f57ea87b90f6ee7bf4ab3660f2436c764f87e32bd6a4a5f978fc6e9ca350260a4c1d4d1dcbcc7c059ff8de670c2a5faf08f5cea1d9ad4f931ad45fceaab8ca38719bf8ae152e302a7e03c453bb3338d02c226c1776fbe8e696def33cc9a8c9000c6b680a5d20055aeb5f01948480f2206be62f668247ddf0c7424ca4916f324aeea057e551b0629edc70cbb75059d714fa3f5d707c4602e27bae0361062da7c6e503e62ff3c30b0d6ea39534d6c23d2f89dc49d6488392305f499865b2e861f20775641888c38bac092a1338f422c4e159c1e1473ff24bd214cd639a5fbf5823bbf3537745755101814a4cde63c0db6511d4390fce353cfa12700aeef529f39da47d3d78b77d7ffc08871e5061d166580596586a33022568e0a95cf7736f611b39b315357615d4a3f6f86211409cb0cc64ffb35760123cffa28be2fd724596afd020fd0606d159ef9d5720fefe5273afe00cc8b7ed5865a5d488980d67ef2092d5bf331f2fe24071b68d51254e57c1881dbef8b4bcddfb4553081196965fffc1b05b39562a191b41e6ad1551f9afb575c21efbe8616e516d9092005a6084a09459996d80416e9ceed1877dec58fb910045aa73407dda5e58104366d3712aa903d766c352b84e78676f30cbfd99547d47ca08c2c8728e5f2ae3df8a8312186375af03aa99dba2af73c21f87ad675dd8798a7c997a65a3ae958d01b217cc4b98bb08a3abbcb77237092036d9bc43092367d4d0aaa905cc39e28e11a72a5045123d211ca54d98e669240dff4883fa2de45daf38c752977fa811c1b55609c8309b3550a367079c1f6fc8ed12bbe2dc4c9553470a59d54d323420762f84022764669e2bc16e67d0bfbf723c270ade8cbae829256ae252aae31387d096eeb18989067ff40ecf6d9be0342960df95780ef81a84951b907fcdbd61bb4d88492c590411462e5e1639a5fa251c03212ab94e4778ebcd48ad5df206b2fb4fc4033833b9034bd6ead0673578d551691020256fac43fb95aa6ae497134ca08e7d3518057f3261387cbddbc1bbffe402c25564a7f2af46154124ac783318364a6f22f293e3a82528bf3c284babc3d064f142758ea9d291de32f62ce0008ade2de0a150a61c86b011430a144588b7ddee8ac29f9fc767a89615593c7cef9a3e02db71efb96210ffc816057bdfab5fd9edfed807ffb3cd84a99ed564ecd5acbf159a7662b9e83f401c9489b65f88d28c97b3be308c3119a80eb23877cf281eb11a6be5e765cefa15ac4bcc8dacf47d8e483877609393d19f95c9a51932adea06a1ae4cb3c0ca4867dcd8dc487acf0cc0296852ec641da9d7c90adc81ef02981c82e33da89718949f34877cbe0787bd80b8a88a08b0d6c8e100a911a79aabc524e257890db7cb927f4305ab2683ec9d378019880afa570ae15f6a52e8e170567c9a91092fbc294a7bf71f5278c5f99b46b78713fcf3b9810f421acdb2b014059e9cc5627e96660bb8ceb2dd172f412e4f6c614bbded915c71ce29d7d6eea3da7cd166caf4ce413a5f66002edf6b1d496bde3823c26117940b9bf5f4949d6ec4df19310a57bc43a6ca03b020a28f1bebdeb251d164edd02169a57247af1122ca37431883469aa901e50aee0b69745fa977c45a9477c1f45acba96be8bf42a68e4a48ac6ea275dc87d5647e2e4bb616ba6f4586aa3a8fb3deaa7b8f082cc90e104a1611654166541f14826d253082135ead870a659606b596b8adf85934b0b079d1e7a348209359f8d168b15d0ef918bb3dafe822f91a9131008bf9bc199f3a422af1388d5291f23219001a3f3d5c5e109fc8d4ad62a06c63db3ec3d1f1c59bd61ca52b8e4f8c4840570a2e9e26665b3d8541ce32b34a5b12d9c425857fa1bec75d78721537a4ef452ddc253b79c087a28c5bf338978dc89be8f76cd64a252ef0e7d46944b1d89efa1889ca5c81d67d660f50610fbe71ad304d29f313fdfd898071a4e0562cc3b3a65c72a4341a59bfd92fe192d481032e53482eff4d94565562c8f75638a1ba60594a8156699ae300377c18afbfb4f302ea6b3963704a0410b565f7c971d249255883432743e5001523f663eed6660591eaf966200fc61e1b87911d4c7522d50ec5ec51c15578cf50f497b113e1da18444d50dc7e723f0363c352ed6ca3406f959e0f064827e72f9c80f923804a3555826927e11609a0eb522669404843073b098ae0603a2041c38a2f4214289217cef2c63ae05beaa00c4b77f7620750c01f1003331775706336f448ad32d03974ea0ed155c22e2c0b50284ac48930d58be043c9628b8faf8661d782ea9437cf7dc50820019faa5d8c33e845ac65b064d007cdf0aef93ccf25126d3827b7c60497cbfaccd4530e981976044008879c184324b2281f396bea0b15df5aef0a80ab4a74398e3110515c98a8de0caba4a43eb33607c9a1bd0776134292c039741b8e986de6cb8a66aac1ae7bb9472fd97d61c6d8b84070b1ff7096d4fdc52e66d17ea857ec064a1b77821eaba5c7cf25dfe8dd9188c7a70f327a4386953898c50515c5e07b0c8d6e20e0c40e749b414ca0dc4d78f302122a0d60d41c0d7fca4c5f851b06e55b183f78e629b3fd93c1677a707fec77251ee896bc52d6e13432c95e54ac7eedab5a6d4f4bf009343e6a283a80128dc03b60d32c47e69ffbe15c3cf1539a46079ebd954b9b07ca34564f3051b25957b3fd9c146a43835ec85a59bdbece417978db00e2d86ecd767eb9db491a3f74740fa159f871790ca84094bfe32f747885cd831b693d2e63e75e963254a313fd01e86d5d29436397bf7d69a7825a8084a5d17d7fa6f130b642e0e61f86b346e15fcbdc868f4409d046959b16662eb78ad806784ba3bd0f467acc909bef00a7acd467eadebe651ced6543c8d98538731381fd0a23cf0ee97170830e471309294933439d001954d21c72ce443210703b0805e073ab3bba107a02a78b87e77fc3c2384222163c35a4c46582b2448b3c808c48c862f1a2b4ccec42968704a2a951d320da2c846537b0fa1b5d854da779109d7dff4dc555ae775322a750112438bc976a3628945153369e17c1264478f227c0020a773c6437c7999e6764911aa5d8527567ff4cbdf0143e23842f5ad3c06050cd6c4de29450a9f2e0c9d248992872fcc9ed18dff47fc32527d0b6fe889e000d54e71ce5cee2188dbf37fc98f2e3d34873d09982e4011564bac12adb29f0e5790b47d2812800808aeb89821365525dcbf8bab4ff7737a89f634a93bc80acda41e358e611a024f02399520cdcd11d4486c0908e134c2f3c7c18f93a5bda9d6c5e54118598f93131d9bf4631b0cc424b11f043741ed6204a8c51808115b9e4b4dee2d50a31608f1414c3fc4de83a5c7f1687cea2eba3b92782603bfa73a54bc78fdcc752537c60b0f5f1fbfb4e05140588636e9918dad3f48d75ce931a02f39d4fe0b39a7d0eaa3e224ef825cb7d94d25380e1fb87fc36c760f3f6e6b62e2d52d4d1682f8e6684f49df629902262fd646ba2a011e70d14f3ac26fd3767c10709ee101153500b44e422a69cf21b5381c6f4b15f724317092d9fd7af23e6e97258ee7220020fde3f01c465d26bd888663966848540372608437fcb966a8d309a7d75858d131e72f173b6b85f1a27c80a88328a1577b68d201f1a77bd0dc113b061d544978f06dfb4fe840c2d8981148d625c85dd069744b55b65908bc32a4211568bbb20af3525d106d0ac97a0ec3f7f27349087e89dc5ed7d33f3bbfb5711904b085b6cb3e3346e0c901a641874a6de568564285939a19e317c7b4aef66b744d7ab0163911b1e911d7bd2f5ccefd9fb12a298240ef6a837f32714e3709c6e08ca9216c981c9b1a582984dcf0059310c09c55cda92ef77a329a8e86fcf0f744767a6c4616726bab748c5a6fc32f1a05e45a1d84bcd20c2f0275849aa97ad4d3f0451d0ce73a04a45016f760d36643afb9eba6d021ecb6e53b912cbef11d1f590bac099b3e98e8fecf332bc009c7ce15185796d3d2f8acdcbff84a3b11cc89eec9296df8fed6a231a8f41651a673f14540c1190baf6382354de66ea56ab686d7361281573ff327442f7a085429844b800364a662ebf996a6a3b21769b9cfcbaf41806d7cadc138b57b44a2abdc60a731d9c54a6c8323fcf279c2fa13c5c8e8b97896cf3c54be90ed130ec753757511ae6917b772dafe94f521461d63cedba8242e1105f28a261632a30db863d7e29e7835f3b71168cc8405ae199b538d9e3a5d89d16fcb1a87dada4f29fbb52b717efa0930a636924b530aa4958d20941a66ef0564d55ac8f667807bee96e083e2d6bdeff0d73807fb2e08a6df6154fdc05ebf09dd2603a9a387f5d6f0d146676f5274409f10e064f8d88f54a6cfffe2e69b994d5d69757d57c921983d16a7d0806cbef21ebad0b1f139466ad8b0c0cb8a54e53c3080c616b026149cf39c3acc29e759d0fd6b54dd428ccd182614944a9b71adcdfbd3e8a95944f79b1337050feed6ce72dcd4fc6b36bac51a05083d62406d0d4e83041632a9e84d3d0ac4ff393059f7224bf0ad5a4cfcffba0ae9660ae3f350354522aa3394da4ce1383faa6723b8063c93262a593a590d76d00f38c149e69925b9ce9ab15c3b5e88f94a3980050ba82eea1d90ff8530787e72564125ad5d6c9a2347d4d1b1bf6fb712fb96bcf2c20116c762b69449118546829be48b8d980e1e04712d4c90136832f2fc03bb9ace910446bde292d37e0ba2e228b80ab527406c460004db2e108a23374d737147b1cbc22532b8cac84b1bfa9a55ad8cf29fb11e04b18f9834763279aacc000b88650c893678a5d1f4ffd844f7ae79e04a33a1db7b89e7a4cded60883703d4c430ba44c7990f59b5e0e1537afb6caadc2d649b18b19c4b236314192e3ab8c5c62f007341ce35a86d809756859fa14afa6184296f65a4c262b14b9bfef9795860986eee85d52d25ce6ad99cd48be425b91d3b6a18236b5a6b327532a26d2c2d8387a338169fb882cf145b7e8434bcb7d829f22f5e954caa9e86a1d7bf1240feced94f80e00eef078feb402f008ff05ccb20eb1a74bd61089d92b519064ff5c2176cf5be544a2bfa705ba62bafda1c7b1551cb4460bf0cc233e2570b2068cad2dbcf92b112521ee8eec2d9703da3b49fc33fab217ffbe97573271b867b7e6649fcfeab4d359ac0e8b3d37b2eb1bdf4fc38e5a8e7734bc6e282b34fd6d292528a9d92740468988f9a2be8e47a838d538a0cb08b914e8dacb50c9e996e91cd9e63aa1a8df0533a60289ac23c577340e6bb490676a1f1c23fc8ca6f918049f05425f7d0d240f1562f8e05ab891bff85ce6fe67b7f265e1903d2e44d3c9a030cf1b61d62ac6cb6ea29283319b65cd3d1ed99ee9b00fa0bcef7f83076494857a1aabb73d8c8dab2bc97452e2a10a1d158fec1bd1babf8a836dbc0924759b67a1168ddea7ad06b73e05390e8c92b79703e6def143f28365b62e3c1e0416281da2b8f3c9b3adcc2398b0a7a4a75d9617a8fa5a27a5e9123056b1adf2fee36342c98329970c01b0e9271b4012653d4355ee0552f0809c288fc352b8b71bb2b2adfb224fe3c3fee3f6c9c236781fa8f452c1a3da1aa50fd8072aec2af1fc479ead2505224c0e7cdb00cf9712e282d964a6202fb6732c77bc425620b1a36a5f60bdcab24b99a5a453c5882783a1fd8aeaab85865842dd03ee87b3b4b7713b105ea4618ac3563234e2c2d2a249f7bc63e75ab93663b324ebca3775ce4bd3c11e40754d45fa24be8fecf73240d0208a446324c7f5e86c2a00a735a9c8e21dacfa4acc9673f33bdab6cd97cae66492989a4cd1f0a2431e58098515a1068a1bb703539672337e9b7b25ec0747737630c6e2b259967efd4b77d44d2a92621a25865b3900657cab83bd399557bbe6711c0fb0acadeb930f7a42feefb9e21efded7a2329fb1a00fb0288e463824882efc21cbcf93f671aa687e990f23191ce25f9a4d0d5f7eee8292bcd4fc031a1b9cbb0774cd2b8a792bb8d81fb55485873b875183e5f91ebfe9f1de1d8da605fa615e3b372b6d5dda3e70451c252c39d4605fed1b0f3bcd993005a2c398c6d0efb2ffb421cc4424563dfb530c7aef1292627ee24226c6bdfd1ace879a94f379bce15e2bc50071bda35c0beed005b269478aa892aa8f605c5556668fa685f7212ba0523881d261005b245b6301cbb132a45b636bc5c8d50876738073908aef2e92dbe881853e0b9785630679571d900bce84b8fb6459efe8c6d6227a2f5be9b8a0e274e6a57b143f3e52a8a3a1b36916575a00c0a35e238e3ff4bedd51fc1a80c7fb01e11d04d4898ca07fa9d641695cbfc0b7bb94595b05fe9fec182ab259ed771f4027a3a892bef38142c92eff302e5b4a3ac9c1a95d339ccc253766ebb29b6595d32d1190e23cebc5338cf91b4f4dd9c92bcc677d392690519712713cac72f3df2161f4e22e4c153ec8dee26b4918bf30b507460a93cc3d6cad0c40eadf6808ba37e80d93e31904f568edde2130984a123d19d58b59f02915942a54364e8540560fa6d9aa95025d3adcad447d05a09b665f02d8c2e319ed1f66f8b2e2b11392574e8d83405cff9a54b74fd2da9ed6e0497a530078d8d7832c59e37c813f6f4da8da2cabe89ed55647de9f912af9cad9100f4f1321cb856191e57051196aa9b54c0061c834be101a4d700ec16528155c16388913860ded7c62500b6098be0589ae1ceae8de53123dae708cbd9c8a88e96472e009737a1ec6464fcc28fe1983d8abc58f5fdd9353065bdc79ff750beee71b2cd45fdd3251b223a95403726ce4635c5fd20764ebee9e2b7e725afa457513bddae2d8468a907ebe1cddf204c831422bcb07c36d046cc0512e1861adfe67af02477513763bc8bb4de4f2ae3e04a201c9af37c4b8bed3e8200297795510a0f166b4bf301b8020615120a8ea242ee1da6ab60bc6373cb32da51fb3ce051013a7e20c519cf821b7d1c36ca1a0f9854d52f799b0454b5da006bf3b83c0853995887f6443a7e545b4c29495ee0ba8bb50f403ca75b35f61bfc0948c9e033afba4edbeb576322d49035e4cb60d113b40370cc87a35328e1f26f8dc18cd70cfbbe0abf6a81f1b0ec1760e683c2bdfcfa11bd40c2eca814f4b4195819a985203b41fbc98377f1a11fe498e21485ef200af052d2d8b7c40c87c8784e0799bbbdeab5de79328f8803f8d942a24e83e190ea1a0912fc950220ec17daf0d5928d2e8e2de07f2e076f778b1ff6a955f2d1602aa5cc1dfd30c3b70d0a7f74d2826c6792a6e211535bf946a4f50c684ffa188600acf10f00886014d881610f3c6b3df2c8a5a265dd2c45a7ff7482790ad62542af58fb8b339b82fdc171b28269acb4108e886842f7121ae47cc9fc472f4536506478e182201dc3e246496513c05920a3cdf6a8c3b47d794420671e592b93cc258b73e9a5d6901607523af77118d2b7e2294e2d6ab989cc80754359177bff28ea4341483e3b57779c4b80f5a041ff07ce5fcd9bb9a2ae1b624f6aa544e11c5c55f7dd8aaf1321fd9aa92e7640038545b24f0240d7257de4f2f4b45a08cb26eac54fbe52e2a0267256c1be98f6e7a6eaa3eaa5794d21e19b3c6341bab8871dcbaa5ad1b32916f152f7d5baea0c0a9feb50745a268ddd70a3c1f73e4bd7ff1362d55147c2a3926f92a4815db9c9eb1610fd357702d3e74cfa28889a6f3f38caa7962f5670a3fecaf725d0048036cc1b83b6f84aaaf44d5e9cf6ca300d393b03c517c4c08aef9924e26e7d02e9386fcef14c4ae018c91982f1944a7e5127acb6681bf4401d975fc666314642efbb7793b047d45b3dace5cbae92cf46b1e97428653ec23c06f208f1a4ef13cf8aab69e3bbd257e4e27dba671f5e128a4b47c1e876d192209c353f721ce48a55374b98a697036105457ce1e8ff732d5d74b10d1e68235647a86ad785262ecbeac5342b20025229dc4e2735552f37da064f36a573e0913993afbb98938dd4c7473cf71b0384a630c3d4ec85d486b69525095815f4797375f97a045fdefb535c683e5ad08cfe3c47a0b75792b35b6ad427f71e0106e2229a3d83aa9f22ae155bd75f84151f5e24a0c2352dab48a3d577cf0bdeda6d7cb8712e48a1d3e25b677ea3742f15953e39ab5621f869ea1896f6536c472d79b2369e70f98ceff2065cec129d1b389417b2deb536bfb04dcd088d04189d9765182e11e4354930af9a80361d397c6cf92f6d7427f2bff27f8ba94accfaa5e84384e45954d02ddec3a6504ef07a1c45f4268660d2645b258d2734d885acde6e204a8412f7d7fe25a0c3ae57bcaa747e6c8aa9225a71fecde5da8cb98ae17192df28ae7a6db30ba2bb1fa92bc051a1995e8d1c3d98c474af51a6ef010fc9ec1956400d1b6908a5c5dcd5c408d9b3e20807497ca37247f17e1cfdcb7d8e44556b32a898eb0d2782bfbb90d68e41147ba2170536329f531930daaecd543012cc48752177aaec84110dad6809c969a0ab6968e323e658b64538ff43c5af121e4f66399092acf200a25d932ff59a43dcd2da420c5ee72d4f52ec56e1904afdd7a87efe2431635a7ebd7d81ec5cb836eff8d4b22634ebf11d1438f91bee07fb1c30031d8da24c10b1f6b66d48c638d48e67af9887f7e59985698c2c1bfe48ef0281115a153d84c6e21bcff24231662374f7202c00ac6bff57307432222bcde5d86c73597deb83cdfd73bf2f3961308d2f4eb625b46d16b88ab83264a2861f3291a4929636885286d79bf9f45395462169ff6bd53e898f54509b38cf24570c6dd89601a5028cbe528a85639bf8ff0a53995cdbf18bb8fb9d9c9abf96699cdb4e7b067f82b8e34fbb7f095c98ffca8afd10c29b1b217a6d91e8417c1f5fba6725bbe3895db33948763429a69526454262779520cf05ffd78a07fa8a8997174a8d7b2fc34d6b39c6cafed1a6032546b2f1de727b0385932851a133acbf694ec0fc4ec9565958f480b698c81431924924c8075e56a52d00327c80ec2e2193cabcc2810b7bbf0b46e5bf39b1ddf653195a5aefa3ee4081a218e582349bec13c5b46ad910a7d036f0d2c608d9ac8feab9b1ce781818404bfba61f81394d4367bc8bc61157d70ec7d878e63982905418ab0692e4c5f7b3ab76fd588231ed030e1003665fce7c9c2440c421d28381098730341048d10df3dabf079eaa304eace1e3fe012408a8f29a844371f2546350d8787b435ccd2d014aca1c78bc6cbad243829972abe2acdcea7b9a88b50a90f0c9204ca3a3f9b54305e15251d2f4e10ee00105140cae798342b731c20e6cc34bb3318e4204cdc37d1219c52bc53e32e6bf45c1d6ef7bbe8e72ef9c5aedf5f175380a7e0a7b76ee2daf2633ffb1fb5d61a2184904df6de7b932de50e8b09f50879093a76447829c34abad4df71a1b6bdfbee7c3ce553cb49a485c4d6ac23a5dd58e7d27295dfa695d2c26b52db287b55a0c24b4ba11dd5d3c70a1cc147820fcf43e0a5edcdf3f8a95d5611f2d05a92c2c753e9b1d4e381b492f4159e02b9b582f156cddd0ade4a83cd3945c05f9af5a7813e699f4b2d3ed3a8c33c092df3e47aa6b33aad33061fe11a186637994da6042b5ad57ec587a7dae4c7f311ae41a789aad475d94d66b2b48d89dcf86815c12f55871612d5e5f391119c3a0fe91647e95ca93918ef7602eebeb614c00ba73b8fa8575a3ec233e28e9eb1dae384946766867c0821bcbbce92df380d65b80bb84e8b340839c2853cf2e31ae03a2dd2904492a4ddaa77fd94ff68521ef41ed2a4bc89963d1569b187b4ec65d8652d8d8692acb29b0cfcd46828c9aa8ddb0c15a9cd4458c729d650e41ee03a2dd600c59d9556dae2df774cd354a7572c4a7e529cbeea58ad187dadf49556a230abd66318f68a5ea66f6573bc5bbfe396aa53f7fcd6b7ec2c036f33e8cc269c52b1d38b321057a71bcbb4c7caf4ad761bc1cccdac4cdfead22c3be9938267503b49bb89b2963ca8b53a6b8297e9da5bdb6dd2b6acab8defecc79cc8f7d24a3974dc7877658e77a5fc7437b66f46c7d5c48dd79f0faeb1a9e05171b7fee38a7dd837c5ddfa327d9fcdf16e05dee1906dd3759795e9cb402e7cce909c1ed7c791325c2347a8b8f559d7d5707349d769a1869a4bc475b6aa26b95f5a084228d3577e93e9cb3a7c63bfbec9f46415518a76abe227c8ca742389ff1c141fd2b2a712edc4ac4cdfea17fc68eea97832bb595606deca6e3355738d845a276d899f145c637ad73bd98dff7ae2a0be67a18c7d302f2d3ce941b6bdbcbc88ee494d569a0e8fa8eec6777b52f00c2992e12c2cc2dde9687821bd6071fb431851069a961868a8b91de35e2e5b4f0a9e01df96b48e67c4ce69125b13908267f061ded4076ea82dc136431863a4674ae5bb80e82b5031c59cbe7022909997dff42d93d12a2290c9ee00dc0cd856c6ab38d3fa2e0878ed0f190c048ce816f8800b8580b77e93b9eb2e743193e670e640518afde1e6707f78404c11ea6762a445448e4a55ee0cf9012f2f0a50c0010e308001c48861125304f8aa995c3145a0947efa1543692da0ee021953044ae9d4640ca515614a51b9d6cfdc3a2b40a51501b7e4e670b2a29fa1b9d2222287b3b9d4258199cbf67240f44dec6572e9756bfdd3b6573f5dde1a44ad94b224105d19d17d175e5a674d60fa344331c4c1b932af00557eb25d4d32c249dba619c82a98a6699ace9adc80fbc31f62f8128a6086c69a8991f605326603ee0f31fc1c55f0f1509d132287fb0333f363d4f813ac9ad42298a1b9cc7f98b9932581bc3cf19df5f6304d30e7348114dce95369c9b315cfa14e878756470d2d24c12666fa4c8c8c26b1bb5bd6701241f79ecbf4cce384702217c4cb229b9ac47d4575ce654ed932f574df6ff52ad05da7e6e9f5e445b7b0186eaebb035527a33a2250b00ffec13554d00f3815fa1938157a47b121bb56e85486acef3c2fa106d9a5e71f4e08be94b570e9c656b8945a0c844b7dcc39abae4d75cb38c508e1d48c641068157a4a9b5ec80f1f4da2d7c09079c005419dceb52e4ccfd1b44960bfee62fa7152f641698f26d14fed0c593cd38be890517aec331f41cb8e20fc657514015a0d5cc7ac8ecbea704f65ce79aa59f3d59a16a5d6034d7a344ec79a34dd12a71dd1cb534a27ba750ffa48e50fe7b4b07bdcd114fb08d5c9d8200edf11f56a1e47e34c6a524da2f4f5d9da55280d1910da40b67fcb6cd3d849e99be6d1679405f254e8619a690cfd43424fb18bf89029b9747b492e3d1cf2bc30d22dd4693dbd4e4d4e29f54c5804de5c3ae9bbb9f4959a4f85fe69d953a1879ae8a9d09b6893ce3a7f69f31f8fe65e367f63de8b59866c13510ca3306ad679012fe478417331152cb954dd668a1c7ba5300c135d27041704355f313bb56da6c8a5aa3bab88eb54a5aeb345c465ad4d245ae9167a4ba3a7d194de3d7af7281de2d2b7109c10f323ea1555a94af5e9b3e7f24e2f7a2ef0f47f2ef1f44e06b5258863d5dc210f4a2ec24316830484281a31b5a007d388424c13f2a185ea64e4dc290bb91eefd655ef66f77d7242c0fb4ec47bef04702e0e36e03e06dc17c47dcfe43e00dc67d94d64a4b216c6bdf36f9959f408e77c56e0684632f89c17979f2bc3e5e7905cbf1e5f9048111ed2e35ed60ed1c2903dcb86121d7041bc1787bcf7624e6deb89ba392e989979729fd84ddccccc3c396747fdda3dd78e63cd9d5cbb5caf2ebbb3a364a2995a109a967d4b4269c85ae7e160d527cc364e9c7a629ce959b4dbeca94367c85c8c8b111dc62b08974018df45f4e36b8c81a3edb586302699a6254d4c44da857d94c18b57d32d930a215674a4792e289f40d773c13e659f4ea6e863127dbe9160684ca0d893394db2ccc2e920ede3d1324ccbd8c7ebee49e9e3b6acb6a69ad8497ac7b6ce4e770f19dff10238d954dd69c954d9b7d397b6a72aa38c30d65c33b3331dc08b9664d32184104238279c304208219c7114a58c324231c421977a9d46a0915330890b823adc91a7e4cea55eaca9e91ecf051a792ad4df42ec64390423b46f48b432a091e7f26c0bb13513031d136d87ce90bd9f5f0cf509c873a999a7ac8881c5a50e874cf7e1034845f5782a14f55aeb057074700c768e611733a9ed15b9d4331f59e50ae31ecf856b322ac9dd5e1697fa16e3526f0875f7843c15eaa3e995ad4ebde7ac5665555cc08943aa1d911db2fe4549447ee4f832ed443b32329ae071340e9c5ef7112adfc9d82e0136c943f63ee7cbdee31ed5c99833cb442492dd92b8eeeebe1cf05b13d79500c68ddf5c892fa59452da1cf1d0be491619b2bbdd5996adb339e0379e9fe0f9f33a37d9e688b68a79685d57ce4d2a6a0c1940844e15709de8ba3bcb9672d46f73babb140bde75ccc4bc4f1a0ade567dfe5944c8cbbff7ce6326e6591ef1d2da1cf5f3b4d2dcad135a27de794c3c9dc77be76179ccc41db0a110133f644c34f15c98a0a1a1a18979114e27b92625e14cfa906dcee630ce750faa33f143b6b91dd73aba855d66bfbfc88405f8466bd287ccc5af34c93d3a19d3734316930bd1104e854f9dc91eb2cdd9189102c3f770ced7a811b1125ff39f4d7d1238566eb4a51c2578b78a79772716c8eb9889919f1a0a24b449b0c0051c439d92f23c6662a4e5414f599bc31d6aeeba24aa90a7d23a213fe57c2a6e9e873c0fcbc3b91dd30cec6906dcb35b9a5b022679c8a66f434c5f70823a0f4a5178eaee53c678ea4eccf39053ca3f1727ea79cc57ea939eb24e886278501e4578a74e9d1664a8b9eea4151ad75d67053920e16e435cf739499fece49e6bd2fbbddbb19c991a3a4146172f8b5b6badd5a2281af593e607e3ffcb06e09515c02d0c158089dd4401b01f0b00eb9e8ac766189db46bd7ae15acb0768b6aadd5568b1ebe52971b393639363936d5fa5551d70d9753b1b0bc1f2c34301ae5480d75cb54bd4a2dbb5375486aad20842548d1ee18a7b690d8fa130d2bfebaec46e34ea7d1f2cacaca8a5cc12c16cac2ac4c7737cbb402a5bc68c83f4b5978c67c979597646141b2524f69b362e9ee66e996eea9592696ee696261a1c162b195841f4e55732be6ee943912102b9f78c6f4e97d0a22247ab5a294ba8bd2aa02d1ead4c5c235e8998256f56a2e33d50d1942082194333a569e15a5ab57b8465f3e2a5e0d0bd790f45bf56a6efc3b535b5919aa31335fd75ba33159d0b2406dc3a151978f26a14f771faef07b7cb565e1191264a93de1834ea8ddd8e2d6361ad5b23c9c5b2baaaa2aeab22c2bcfd2eaa726312d7b1af5e0a4b3e724bd748ba7b2ac58c896524ae9a5692c5ca3521c1a0e8d998f43a301594633099128cb58b06fd395daadbee1d0ee75eb553b66a75544f5d2559d890987c6c2352a16295fcda558409c5285ac8644f594361dd36af66aa5babb4bcf61dd1485d123161234ce4718db96a8bfc768ab943951e6449913658d3ddd58cf3955c6c6be31927a49cd292fd337cef909e7fc46cd39678cf36c23f5b6a32cc6c738e3678c51d6cc2a227bb4a5f83e96d58f8da00fe89ad70a8e6a8abf2a05e37d9abb549c2ea5c5f864acaf4eb5eb53939f346a8a320b75cbe4b9982d8651ec5b758f700de9b196aae766ca96e267e5395f238561169bf198465f2fe1bcccaaf5ebba6c7579dd3a52bd562ad218638c54468871c80f92758a846e57d2d2ba3e4a2995e9a795b049e97ba04b3ab1293d10fb7cda0f175981507ea319258910c9a244dd234261212917a136acc88d9752f2b425b8527d2845dbce0a60f26c41d28eb0538f171149e9067aa4de4da9ea7c85c9b7950bb2156832993604a2eac330cdddf71e469908fc68d63d1a251bc61378720506af8c5eb172e14bb5ad5c4a29ccf215977abc62e5621486612b37567cc5153ca3c8159f06d9fc6024cea2cfce9a7557df9cf3692549cdf9de8cefdd94e731921bef1a47d5f3ea9ed11161dc79cc3d5807c6c94868b41b23b91047157b8a71be15aaa3cc8bc798df8fe71e71ce0dd25b9667de5aa93a1ef4899e950944d98f87d2fe71eb93ef69dddcc33ad43ad3e8439ac9611e47abedf989ca62769b17cbdb52dffae4272c8eeace3ead4c7b3a97d0273ff1e8e1ff04d79887559502c5a58fd6ca86a3baf3950a14179e9fe02778063cbd9b2c222823b9b1bbbb9b9f704ea5415ed47e40b8755ce6f279153f6ba55af5eaacc167d7ab95ce39674b47cd4967654a62d562947e7b8fdf7bd396e8e51f333b66c7dc4a4ab33bc64e7237c8dd3af190b6e74d6f9a3d379eb302d2f617fde8a7cdae6e9e33b38aa87f9885446631bb351fe33a59cc6e1311f101b9954af9de7befc9091ff7ab43ed7d4e69f110be5bfabaaff4ade273d17359992bcf4e2cbb2d5755256de97d1e5a7093794d6de93d9ecf5c8138cdd9afaab375c49c56e6e153aa533844d36d954de66d0dc5859fd4a51193dda69b792a1211752e8557c62be77465b453934ec2c046ea145267855aa5966559f3b22c4ad9baae43eb0cfa654153c8e51a78e3704056dddabebbbbbb9b7ebea76a62a25b9e8c333afa6eeaf3247237cf77dc86efd04dd38ae6e79942e014d28e7294f51ebfc7eff17bfc1ebfc7efb1c5a798628a233333b3075a5df3de0512cd996522d14f72d3c6d6ac7704d7e8b365091172e555ea7ba07cd7bc3f51762be75c1ecdf6b242bac86e22510d8c5ff6358f40d5428206008f26867d34a314bb89ee0d18769ba2074fec76131bb2b05f1ef72fcac8ad1446dadd3d3d0cfb2c0ff3207f7336cfe5a4b81c0bb3a869514a29a59452b6045fa78d4c4c9c764d9b9999395e1a2b3373f3cacacb8b356796894494d23b1a798675782db9754d4ecd9b2e5a727be7e565658544fa45229b1a7ac578ad57913a7422086fe6cc3291e82791acc9a2afec4be419f1f24eca29e4f24b615b040f4f1a89f3c96c2871a35b26750fd5e6a9d4e2c3acf79e2c83f51e175c43deb2ae495e58cfca64175ce3559627f3489c0b99f93db6cf96ac1765195e5bf2467221bd90d68bf4d1923837de4d69a5945276216f2417f23d89433fa7e71df3c82e7806851e2a2f4b4a0fac26cdad966559f559d985e42274bdd2ea5bfbc81b97853b6597142e0b7c4ce329a595d6bb801b0e87830387c3d970f0bb56b4acec42be3e8ae14ed3651738ac57d5d25092c9bccbd6e329ae5bdc8d8a419b2e08cb6e382afb8feca25ad985bca1288973a9eca24172e8864b2abcb9151b028563a577ef9d53cf7f5a7d6b111412bd6b464723e9c3bc2e59eec15ffd0b5eb7b80696754f697d5ef0e6c250bbb08ff7deb39856dfeb7e481a7a51c2bee3c2aef53d245ce39d21172d6d0936f6076fe235e1cd8defeeee8df6d5dfe8db625c7e2a2d9e73378cf3b9b1270fbf7ddc9cd9f4ae5b7fdf3e13b0e0af3f6d93168376649d1fbf02f35c9800d96d9e844cde7b87dc5262c72c24aafbdefc09d9130bb26d652008e5256937b62c24aa176f621f5bd0c776651141e1925b2fcbd2dcbdaecbc2de2bccf368d4533494c3d044cf34ebafdee83757bac5b554a77ee34dd2c248278293c4aacbebba2e6c9bbfbe4d7b7d5155d7b7fa78c77cbd32b3a8522b032fd49933cbaeeb882a0f7f695beb5c6923136f92dbf578fa94cbcac06bbdfad63a5993ae8b50b46915515fb2ecc6d242a2bab555ae4fd1c774fd621f978cef2fb846a56c7fc133e2a9bb14de5c9652cafe62023500d45063bdfb55dd4d0821105d920de19ca0fcfc24c3b76d094aadebb3db34a3e3c94f0f08a6df711ff5b7756f93a16ca94f29d8ef51946b50b24fa98d3c039ee7a7f7fedee127aef13e515611d79fa557e419f3c15fd78476bb7cafb1e3a59abbad3ded96364f69f5308fe12b16ea96e90ece73ad9555bdb37cbc0c12d2469e5155761423d7909ff8f23719bed4e9ab0c4f94ddf84579fa39e37defef4d37421b79c6342bb676706e75e51b0890cb80ebb4c8c9b93936b7eadc8a91ba05bbdb2a4dc675cfe5130f92daa354422a0ff3a44cb756aadf21733c87b4b2a5ea155ea67abdce5bdf7a2bd6b9f0cc966fe5d2062d8d3301a4096ed7a195ff3c5db9efce3585bdedb6322d66335b6a8b9d5af6b273a26c891e7bc4a2b3327dafd3697339aeaad44ed465c1486f455885444d621d76d25c0ecc8370e6e2ea08960d25982eb163b343a90d256c2c6103f3a866d95882f9d948b6a1848d259ecddbb96c6309e78212ddce8d9bcb8930220d455bc2c689c634547d137a31e13c2efa862fb4f1369488dbb38194cd8d87d82314dd0b8675228b6233fb44300eba732e29f7c08f34f75234e754606822cd3914cd0a7dece6965c07e5e3355919081d7fbad1561978a91c78e797e01ad596a8c74b59c953b6c46c97e01953daedd95cf825780684f444e3188e71391386a6a16827f555279a8b715611f2c6a47ab495c4c99cf37f11908f274368517b79efbd373fedb3598287801ab486eaaa63c69899e185e4b2484c2df2138ca45b26f64bc3ecf604126624d331f313dd62c53be62835772d4dfebd62f05570c9a4e4ed7412bed142cb489a04374612cf4822c0976f51f17d6e59b6da9c4b76cbdae2c7ca2c1a4a32ca6e38aa63f4932ca2343f59eaeebab3d8af63f05ab6749d9a0065f9090b09689f885cd578a8c51810c2d09d8f318220744bf1f4f58134cfabb35be8734b93af22c300e89e3f90e6aa568920ed5611743275a94d6d0bddea52543c11dc7f8c849f58b13abbc29c3a538b43f8c89c9375260fe123131fe1217c44c431d6737933097c8475f888cc3c5acde465f62eb3bfc73a7c8412429f01c9fd755a980188b49b8e2b0fb5f8c8333339de9544c8770e6691982c22a2140fe63d8b88eb9876317f72374277e11f64cd4d47d4fbaa3c41111101e1291bd23806f4f164d6eb890fb2d23cbcef482e8cebb458c3cdad5db17504bc59b74c0babd0ab96c6a734ca5611f49174a389365b251e4513b54afc8926d2d88a003d4c8bb4ec28dae7279ae7261ae8ceb2d131a1c3f46348737385c6cb1059bf5d43cdc23c07e3676e9cecadae2d783ee29bc23361159655c73ec7b0eb309d61d9371010184d4e5044175f8472628279387461d7f99e0c7b523c299e1493a7e5116c460708f41110100602496c4405c8032b9383b42dbb333a3010f611100c34b2c204948140d64d4037b138039d848e594f9126d831cc9a3c115e9e5899be26761bc10cc86eecf9c763b70cb33e0a320a320a320a22bb90145a369bd1615d24fa645d348964f33bae1545a2ec5366d15b728a2815616589b42dbb333a325196890ef344d63d5ae9ba1361229147748fe5c13cbf2eca3c22eb338540d84766a27fa6d004fa605384817272d2cacb8591797e896c36a14c2793c9149a40d3679a98dd5ea8cb5f5ae419565529bbc5d339e374c2940b90f0e1f20caed3420d3ad4e62a7b7daa7f3e9f4ff5cfa7faa7b4e3560e7e3e55755dd54c223f339b62ad5ebdcfe792f968db435ac9fa609f4ff5d0e7a1ea0a1da631cbfac7e43af60df4ab7a8acc747d3e17945f36f4ead7517e7255199cbe5d329a5899be21bb8d6086b37b32bb615745f908cfa86c35817aa9dde0a71df5e564a54d77b7a7adf3118fa7c23cd83dec394c57fca9be65af144eef1a7af560a7af0f81348b89d48f87eb2dcb6e15d3f3113ec247f888a7631fde7bef82d2c4e4105af09649e419cfc48462138a4d2836a1d8e430cf84faa595aa4965627299fca2aeea323953186326c7ce33a35ec913c72106f1873d9c31c6159f5cafb71bf35d87401fbe2c1b6334615a85b6b9fdde7befa395765cceb24358c157d91c0539f53ecfaa2aabaaec302fa3fec9ac2ca3fec9fea1b8faf0abe715f594f8f9565596c2aa8fdd463093d9cdb215351f84675074daedd94cc2a036ec6eabab99032dcbb2ce3956b52cceb12ccbb2a6c9aaa6eb569d769379599c1b13c9344cb3a65b293ae3115166b504c37b5d4d3a51e9289da6191dce4d229452eaa615ce393ae77fcad2fe7f68c54e3c16c824bb2cfaf19c602093cca2a28732d18927f33cfbc75e009fd089282546004e3240010820630056d3ec05100000c418a5c010a19c9884401f4f865d96a2368ab2b72506ebbdf8de93efddb27bce49ca7e9772644589f608c89b2494721bbda845f958db6e4cad572debfddad7dbc29e61575b95a5941565184b25d5c9d8e4902b1fe37c71aa26a7e88c804c37cd382335aa99736e9bb555dddab4d3dd2625575e29491a4717e9faf74edb6e2949a6c72f489051107931c403849275a0942d25948f75ec1631ebd5f58fe6c91c13b12a2b33ddf8884c131fe1237c848fbc19aca118aa93e15c7c6e92da96f5ad264739237411c6cb4ff67db20bba6137ecd449e6a4ddac539b8fed198a1180ca6e2f77fb6d945025cf360fe65c7913bbb1911bb21b34c23817e7cad0419f76e301dcde39171937b9732e0438ca9d7329c061dc3917037c74e75c4807c09d7351b900b4ab68cea99034e7540ca039a752008d009a0c6d009a732a34d66af6767b11800d80dd56006063d88d34b229763b8c13bb65d26e32f186401fdbf3a2a124c36ca52c2b13af7ca5a56a29ea4dd196f897d9271b79c6cbd1baea4839c2a9463574e74eafee46b4cd0df5a699eaa9d4e254e465afaa5274aa8f8054232b4a9647a6e88c80700d792aee163aac3c9e438b3d20bb356466666666b615f4bc5b551c5951bd149d1190917cd53b8f82c05797e90ac05752db463537ba2ce6cc3291e834f2a50504692a7a0fad5292409af7f2b2b24222fd2251c35190471fa722293a232055b5c518d55c8ad1ed2585741439650c1b2ab194524a91ddfef8c44a28e508e7d6f7deabec28c808c8950c318ddea35d5bd36c538a4e671195d018d12d0e833459641195641acf4b69a594a320ef103eca51104ea9ee24f940e33215792ad3776e74725e8cace0dce91b691a05c1d145ba089592e4522c95f0744c8329f25c5e5e565648a4ffbd225de4cac72925c98d1f05e11ad528488a0e458d6a2e9552ca51109e11290c16c01ce86f301a2826d7096b17a5924a2aa9a4dba36207a554bee9bb3e293e94e2c6243f7b554aacda9275aa4abb4d333ae4e994a7534e17a573cae953b7eb74bea24c594a299595a5205ba51d51af9f522291234fa594a1cffaeba297b624dfb4d69005514aa90455b55237b75a1425a594525217a6657f9ae89386726872aacd7fb4ea308fe935a5e0cb96e4add75f405c29b8c6f478b2e702efb1f3b624e9abb4e4ab86843feb67c5d54528da89963d15eb269a7b139b8f508a7aebd53ba7fa641151d15bab9452fe52534e33577f70d059d41fb7625d31106fad9ab5a664a1d40a83a2a09cd45b2214eba2fa030546b5aab3a5faaea1ae3e205cfbe4dbfbf17edc780ba556ebd937bde94daf288e5267c16a5d1e26fbfcb81b8c1f0c639232ba07868d012346c5f5c634b2392976cb60882c4bd3b585ee81a45d920042870ff7e528e124348ce4e86c6ec7ed0c41e3c25c8065a016641e22783e63f880a8f02613a010e80f211316989c7caa38f9c4a3bc8080bc6051018145cb20d231ace8101513a572cf855ff88557483b4c8456906d2f1df8cb4530be906d6e679b2d5b63314563d144da8aa6e2c19051c6944f18b2cded4c266c5e919d9d9d1d6793813354d72d20e09a5b724dca0c10226d0c847b308d119c1b9b259d835f6c08c1a02c6c707ffa30a9f74932a4c87381e767c43c827a99b21092f38a02dc8c68ffe04351649b33f2e8f38820c988b647b819efcfbe7c608d2fa09b1797ac5578850ad214fce31a3152396803ca03a47f16869bf1a89d3da2c27d771f9d6c7337324dd818c1373733f4630a79136fe0cd0d0d7838a10d372419cc822ce00c8921babce3bdd7ec5ebcb6995b430b49b08999e11ddcac04fe64c1c60b1b0e831736d188112336e69c73259b73ce25d9e68c349c18e6b9c8bf1bf15ce2d4a47849e106373827cab83b50b58103b5d908597643348d72908958d0e366a11930b89febb4888318ee969dfd40732bd6f39660222db9a19384910a2fe0bcd8438f1d3b9c010b254062e00fee0a10ee26a2e26ea41fdcedc5077773373db89b335273210fe690e40863f0811a7e78010c7007406c01270a353c74e2202466c60a19c4600d72c8c210a2c08798feca737140164cb8828a9c1a306108317d98e7e2b2480213867421061af0200e31fd249e4b0370e690072c6001061e57c4f46f3c9713242982199450410728d82066016668c10ac0608517841c89699a1ab0a1f298020b7d40430de620071b0401ea8006150f78140951c105e6023f5c4970411772a0c8208410562c8021b0e10311f480033a08d98153bc81055317d04007287ee7c590374494ebb61892852147ac232c794ec417fc0b23a0ebb470c313575eb7c50ad0206326c0096e3e8639b8a49e89a20f3588c3139840042531dd5ce4478c3ba02e60430b824c3d842e4c95138e0061f0890f52204245c1092c48a18ae9c4c4872d64b006eaf270dfdb62055fd09b2d64a0c5c5aedb420660b8db23139c1093c7f3aafb1e1a7e6be59efddea3a6a99d13629a64047ed93a17c47b6bbf273778af708a3312018f609a3bc5799eb2ceca591096005238e9a4df22c7c8fc4967e4182b8cd2cd2b34855ae184fc4acfb175ee21a122a5749a788a16ceaef4add14fd3ca95f255ad1ee35df50d58d6bd95a90bd051d43c45e79dfb085ce8357c6fea8f60e6be379d9a93faa43ea94f869714457d4ed4e690f023988980a31cab505db760539fa51342462744844e08f89c10af9d101d3fd996e2dfe361bad497af5193324608a7e93d7e87dd7756e92398b96da72907b67b4208fbd49b7a85da564d337bf6ebdbbbccfede2fbd57e122c699e57869638c18dba955e62d6db38aa8eeb42e68bdad0850b768ada89de27c73be4f32c6279af46467154c4f359e29cd2eb5f2360601949055eeef2eda8f217bef34de9ce83451ca73b6caf44b736fabcf2c3b3569a23e69f212c6e8966a3759750b94d24ef2512b8114627ea377b3275875eca2ce3cb4e009363195b7d0831076626a7b010b3cecc4d477e30220f888a953d59a13030e919c980a6f728cc4d4a8e343484c95556bfc041688a013536790214284c4545ab5c641a6f0e326a65278d0c3196a626abd830c68626af5052700e126a65a794092b3862038e811533124b44127a6665a0cb1424e4cf564810b3631f553b5e6bcb8998226a6829c165f6c01276687b3f9820b45980d40d440c36f48034d4d4c3da95ae32d7440849d988a52b5e69a015b75793603f85554b5e6eca0879d9a980a0346ca1e84a0849a981dcc6f8b2f906c2949dc9ce634cda9e15ffca4c1c7865372dbd81c86c1e54c935d420c48e6bc1b433127cb4f0069244f0a32f01132b4ce9c93aad7ab463dad3e0b4cd32f7b91ea75bd08774bd7e3294b630ed9e888ebf157a5286de6520d87e8f69f83da4ce5ce030d29e504a6cf1731cddca76def0ddfe3cc7d7de13403998d70b4e69c73e64a6d939fe4ccb52c3ca30ff366203318a4e4ee9b514d93e4a8a649324e31c2a74c2949ea08488ace2808cf98a0803e608f2689609a373759f328c84dd60f2699baba4b5578aa82b7e0f3d139345e562aad52578081c015229c627c36a1132f25949406a9c944f808e49c0b2f2584d7263dd122eb4ffc28a1d40236dc82d4c403d224f98034493eca4ea2fae9956c244f8aa86dccfda2ad502756b0818140b7307c40ac90f5b739ab2cc2806ff0049b98429a24af9c429ac472c736032b1c3bdd542ada6d42f17c5e2bebceb998fcc2ee5c16f26837dee46c5e5e565648a45f14e5e434e6076f6c9a34591a4a3297a3c3c2c833a669c7754d3ad922eb6f2ee746073c70f5c443ce0322815427c34db74e2554f284099d9311c94af5b07aa5dea2d349ba05b6924afe5d1a914664196495a253ed280864db5e344e1b699cf6c2088744312e83d4469450a7510d16190e918c6f1612c5b80cd228c8888a0b3b67cb7a07270bdd8b6e89d148b2ce190599e01458c029eefb2848fdd64fe1ad73ee1b05494972273bc99caa868a846d86847aca66b148b68944a225504de8609ac82df154f6f7eecea8e9188b2615b1e1226cd32d946d4e90c09f70daf8848cde15814bc838068cd38a1bb94811510c1263d1840e915bc233a285d0081769e281de3645951136f2399f8b3cebe122f7c5ccf29b78d9465a42e592385d5a6861861e1701b7623b3144acd396641bcab22454d5215b4a9b4dc88cb879cf72f78aac240fef46ba150908ea43740b5579c0e9e0a828bee421314417c5385320090dd850e2c69350272718418124346043891b57647c8da040121ab0a1c40dd2151941011b3148403c7743e2233ce4c8345d73e2234ca4d6a16e40dc6a52cc7242f0090f323e5b19275d46b790ba25f2890fea542dc39f40218b775d9dd4647cdf7b7ffc77f73ec4fbdf65bc9055ada4ec66f654445374d2da39391bda11093f587eb0fc60f9c192fd6081d60f4a29b590702b8ee507d532c7f2c3ad38961f593896898bba0d7199c66516c9c233e02bbe86442864a2e2d5f4147ca75d99f6fdb83c979fd8677dcfe9a39068cec3d0685c886252cd53ebf3232d0586283bc9e2c287404d1dfc74ab2dd17a59c712cf52ab65b12cb52508a939f9d637ba0846ca484bd1606432226522f8dca3bb65dcd8dda218f7bdf7bae70f961f2c3f587eb0fc6031695018429f8f941f2ba5b41b12d30eac91b2a1e828a69dd0a5168a62da9976604d4781c1f816822de4ce77145d73e79d9bd36e32b0c77bc6ce06c87e3c8f6e240cb66082348f0fbd6452d2504c3b4d82dbb46355d5b403806f5900be4d013cda8dfa27c6e33633d03d02b0ae55b2bf56e918768e2c8c771a31bb85328c3ee512dd3997d14f465a8ae61c0ccd39156a1d907b75cdbd6e62b7ac85dc4b64e35d6d431fd1bdac876828c940f29ecb5eff7c3cf71105d7b82edb51cc40bb73a3e019f1991619098470aebcf08c2078b9a568b32a605437a25ba205e214689715d22f12c5ea155622238ad0b8c7c76e248f15b1c52eadded2a8b3cdebaaaad8f5155fe306c4b672e38b6dd3366dd336f3d166a20e35e93d74abb6b91baa19b96feedc28764e1a060ce0a4653370c22e5dce74cc22e1727c480d2ae16e748b3ccc8e69999439ef6e9a7ea35ba08f2b35c7d3b4d24b4cb6d25f58ad3393929d243ad38d0eae11a8a42d98643dc9d4880000800001011315000030100c07c482e18838d2242da40f1480108ca24c6e5219e86192e49032c6184200c00000008000a0499800d2c3730312345a41b15206bdacb84e2b1f029f9d8e502d16e8f9e56813233a05d7e6f9c8d82ba6c3e017cb304a39141e336b391e3ddd9753d16ac99b135d53d35460e5c9d4d87b49be59f3cf10ed73be8c02e28ca0009158e6ca59f306c35e552500df3a93e1c977f46aa097b1a5348ae8a5e15e4a59949d7195efba0dd2cb502fe35239603ae269b8975ed2594ab2ef2d008884cd6708c21b7349537e83bd1c95ea4ef8508c587121c40d06bda614777b35ece57c29eb573b2c5e39897c65f0be5698e40cf11f4ba729757a4f707b57fe60cc5f42ac8155b70a1bcd4f752e784364612e9dbd2ee8d85b85346723e0c502221ecd1a6545dcc4740916040c75e432cc1fa2a228b470daca363049e2b5eda6fe879a78d3d6a3fbc1f772b2e3fb96440008a3a1e05798919dfc836ec0dbf9099c5f7304ccd142332e09710fbe421ee299b6b2a787e60fe0b62745cd66ded1553cf41f83ead56b0085dc86fccbe95a63f976d03e45f5507290051ea7a0d17d6f510b7137c179850bcf9744392e16f609f4f113e7141b2825be0f6e829c0e67d2815a1fb71f4c3ee57ef1363fec21af9ee9d7a7b6d93a30e144dd6a10435c921a13e479a70816f50b96d948fbfdd0cad29629c63fbb808a064ddbdf5f285252857c0103cf4bee02e3e05b67950c87a2b964cfebb9679fe16ae67807ac185d35eb265023b73ee520530cd95a3631b4bd85714d8561ca9b12607418649635443037306056badf3fc80a1d5e3c3c0078ba0e3d15fd830d039a2fcbbf9e7d065dc1c5f025fbb4045110b3b2e64faf077a995daa30a2f52860410493caf1dcf1a4b24c0f2fd148402567e9d823f9f392cb5db961b7ba388d1db9ca20289d2acb5e88b4ca0a881d2991ae4af7049156f359edf613591c5a28c7bec4d3b288a4d61a952cf63fda3c8104abe4893c2ceb4fc686df7cb84eca5422aee0c8fa1c05d50f2fceab26e40f8b6377951a2cb9e685760ffde5b4acf2fe30e3b506f89273b1dfdb8b2cfc03ce8bca3411943aba21d2045e22d41b7e5aa0722241431ca49a19f0a838fe78f4e4f0bf36fcacb4c416a46d7dfde6f65da9eeda75dceb65207ac21f8f8bfa61fe3b77e0a7493eb84190b88db1c52d340a04e0e25ac283a97f2c1b7da38d628323c35b96800c3baebe1380615ee94a0fc25f33e4b11332a53a01cb821c571da192c18775f599c7ac55dc161352378efe0869e2e10cffa43352cd755a583d4dd4188519a1f0495b647eed4bc186d94c371dfa188a1eb1a270d48c9c74efb4d43c21abfd81c3e981af146730ad2f8cb3bed16782e0f6608bc291bf6e8a58f35953fff4a149032b1aabd3688b405e0e5c3fa4bf3eb96b82f11fdef11189bc4e1331c4feef2b1ef1f467307b6fe095ae9b2755e5699579148032accc1eace3054df08fb56ec6b645ca7969382e45667f8d4372954896f0597ea72e32fe50145e8e18510798543664576976520555ccf44354df9154622a337427fa80c5e87a45350a18341ab2c459d9ddc171639e7d8f68c5d270a4a383210ea2eba3920708022f04a45507e3ce61341110aa9f30bb3707b0574b77bd27ac4c45ebd2c8b61a3a38b619e9b341faf3aaf399c3a9f212d899f9b7e2f534d9add4469b2d4397f9fcc2df99f8d799f5fac7bf69492a66143b80fb9616bd314cba11f48afed0edba3232e68e8d2654e0df4516a7563708b9ebbd10d82c95c8c6fb9498461f3d2788b6c8ad74f0991dece840e90c8ca46a2b7ff8d68d1d677ba7b2a66d72254b86cb1a32085e0569978c770c00683ea85c39c16c55ee0a3515d9205d1235f2e3ba75020a4da072d90618259b4d7a1e890e422efb3347f49c5e8e8239361b2477cd28fdb5f587d166b5266edd8212a32799f223b098268f14b9b7200cb1a85daa48ac8a3a71b9a75bcdd01f440feb3e3782f5ce66033418ff16bac6d312e97451f4747c996334c916b367156090431c4b8c2b2fb195f87f1e6cdc62e9c61122cecea36d525a24918ca0b7343756ba003ed99fafb9fe3cbefef7e92577c8e7a8c85d4733942deaee85588154a31fab1ab585f3c21115af0370eccc6c6fc6e146ebfb2b1f9a5e4737741cf39488af3744a43f4195bd5f81751919f4f66e505253d3e04db13c7ff0d63c163f94ba0517bbb3e38763a508fb4cadb14016e28a617072b3ae29b6a9bccb0f60f24370d93b3a0b6ee37ccbc89e3cafe9d451b4c7e7605930f74fbadc11f612e7d37f5a0db49dce6412738859d31f05f8209661263e7e54025df2030ecee406a058066334f82125c5802d9205d7354af750c9c109bfa4dc64e19751268b37bf527bc1906bfe9bc3d06bf3d8b14bf785554b3e46d68062de684c403f4adb71fe0bd69a27507af91a600a1ec38dccfae80295a8f3bfe4f707e300e7885c37ece307b775ce7f38d6a42e998411f4422ab2b2bd8c39ea86368432b41ef1e6c3b0aa2a6442b76a8afea7ff39ec1c3cb3f81e24c3ee05b46c55850828a1a8e269139d5fcf2d0ee87eadb8cbcaa3e9c67218c33bd048a66c2a3cb318ad97befab4cc05f10ee07f709c54c9f8782aafe31c65ebc5e590c4437cedec9a31d59afcec40f02ccd9f9e29125d747ed2cb669d3d5ba2414e19583b4854aaed797e51e6a543ab7fc63cb80897c30c4961ff42ca5df99d769fb69106937cf32018654bb3d24a3a8e2671fa130fa8d3ea44ee706f2f85d1319f93a5f075510c5d11793b9c88596802c48532cccc51abc5db62f942d97a18f20428fb7dd7fc9b44f85f3e026beae560246c11571579f0cb7da5dece975c8bd20fba98bd6cea45ab3e31a6abf5fd59a4f3fbae4108c0720d99b3e6b399e42d3ac95e2e52eff661b8905a17c150ba6e1f359edc0b2b2ef6e98ce0b7808329e33797f4562ba2cb85f3424afa7a1be43903d96a54700df5880870ccd76b970c9b06ae137ad74e4156fd40b53aae42cf87e9b7b1365d510b5a0b735d151e08ec28ab9c30378235f119eb2437b09e932d4e016ab76908d933da0aec442c204e436a8d48ff3521fa9a2bf3021ca717e0b019ec7fad46b3207bffa64cee6d83064633ebd431ab94adf8b14717dc2086faaf831bb0e6f25c05c811e53d1087f85c97bf250422572a9efe9fae2a2af9b709db20eec9f498f344422a77bb7f2fd0595e966f64609892e1880d675f0a0ba59006d55eb64600bdae4e503ae2a45ca9a196806256def0213130050c4d15e5a8823b9b5bc4509cef0f39c2e74cdfb5a953038f3bff437e456f8119ac619d45af52abfd48fbfc27497c1c2d73feb9c7edba0173ab8e2b47cd9665cda557b0dfacebbc4ea7032f2bf57f69694c91d64d787dfd0991956ee2be1a645e5f0b4b30148b3b01bc406b8551784a18c2efb3dc86af943fa3b40f78e046a6cecf5e0355a4ebfb90fa7b14244316d28f43c3b3ba143123281a61f822a484f79769dcbd1bd6c76e1ac8f4b5d8e2d439528d707f395c1ecc1a7715a6b431a0f6fd89b8aa0285801ac875a99989f3e2e7630c6914e8b082b4233a3636039949dc5c6cb574d3c5847f4590c821897d207a116f7e4f5c14846aa8ff8f186e47bd9a49c3d44c43515eb3f34ddbfa99bf462ed95f17a3496673bd525367fa8b70bf479ebe4b8847c4af3c799001a4304c0eb826607120e46d61bc52f1275161e2cc84fe91f48bb50ee3dee807373d20fa3a6c5a5a4b755fcf8f031d34c5af19905c962426b0f99072ac3fcaffd9b390335e242adac4fe9f60981daa5f3921fb0c8761169d05782e3343b4ab290f4289ea918a6f512f9b020e804f3a64b13b4c64db384924f591d52b773d28ac046a4b81df9d24d3ca3a23cb7499db77b4e339174cbfc628b6b46ece7eda2b026adaae926031f03d30dd64b475fa820c29cebf8f4c75bb0d94a73b0c96f8c40d15247f7491a7be9162c0aade681b0f2675951e43f1e496cca6e8ce236e1eeb535a58fc1075a399e692e36eea60eed8880765871c2b78697a233f7aa17a975df36dcb7c5bab1e905893c32a41dad72b8f09152d65e20bab56584448164c2478015161904c0191d7cced300c98b438b6f5c857c2207e066a3f5fc04fa07aff49c18e77e59248211845faaccea9263f58edfefaa4b511f98d2ac3e502315d39d0d6d67f72ccbca371c88e5f429ca63ec39548e20325d98951df6eae992fa9adc8037347c20c2aecf5dfd0f06a1f653a3fa82e318e7760eef521a67816050212cc15852623b4964d531e1318c4d4411d2468aedea76da63ed7f4a1f7dcd03028917654f9edc46e39a778ebae52991488043cfff9b716e5ae7b0a0218a41fc1d4329bc5f32af06dd2ce15ec3f1c3e6f541139986693891042874d6ad25bf9525e41d2273b1006e84514238141b1074775a74b98abdc71490c0c96f0ce7e8a938a355502dc7c29f206ee7461b7c847a87e4b5b77d77d88d1e0828f312522104725f0f311b662f0c176668966fd7c82d3ac63cb53f82902e16d3a3195e8fe41337e651e58b285ba66ad358dc4ed77a68e2ce34a06b09875abb4be97c3ef01b0e5536b8648f90d6d0081614c4328a988772313eb8ff5612170f15480b0fd826f0723aa141902173dde1128721aa7cf33453a5cfbc6ed941cd8f0b5c5d7410d76ee17a8d5112bd03bef2bb716f59bf27374d171192df29fdc65ae70874c5df4adeb7fa87f625f569c996b84e04faee6c017b8e0d927b7bf127c728aa2a6060755cb5d0e930e4350dc0fc21408da19b7130075f2a5419b86dcebdd1b1382d89b4a943bd46f0640e8d17ab7d0168b277a3d3aaa453fc3aa47c7e31a92de240718a50670e3fb01824386c019a4a04684f448b01de0901da054a557027f3851d17924a53b73edc52ad55493505e017320f83e6e57eb514b2a72cdf470d0e8ee173875194acd0ed91d65770e2172c6312a4cd7d15b20a3eab600bd928077d55aaf086caca49a0dd68a6d5b2d564aa0c38dbdb28815c3402e313f96ac8f40316b28e9d428aad7c7e44807800269b2435f3da5ec446610ae5eb39051838018fb9a7e8ec0a74bb6381171fd99de482c8e8bb5e3edb65d8f157c01e2f2965cd70c164611664a6d01d43afd9b4a1b5469c90645ea739e132281ceb3308508b039ed6e2db4f9e7e97bbd36599010c313f36c18a1bd3518489fe8ae1e28b16e980a8f786260ea37dc95c889fb59f58230482a16d738cd50b009efe79ab0b5fc2212502000e8bad67b3fcdd3926a04ada27444ae69dbc4debeff9758405f25504dc685f2a69b28b6e0050b62e6667c0c114a18acd5e0d830f3493f35ea05fccb27b7cd8332be79d87868df6fe6d6ac5f255f3a69107ccb28143ec2f2213025fb087a74412df4024e4c2fe0901f01148d6d84d96a9d4b03fd8654db33bdb15d77236ebd135b254aa86f529fc3372977d447afaf24377da1134b8a58d8a6a52c69b20f7c2c2182fba4414587e05a7cd21cd26518a018a9f7ea2a78d789abdad06c7b9bcb2a845863b23bf39b82d4ffaf616022fc3ed961f46d62ff0a575f32a915ed9f7705c22b5227ced444d5d1758c7c52f870cb6e65dee78d959aa5aa40d87bb33abf9cbdf601aad9a8336b7e922e0c3dfad325ce257842ccba8f26dab31312ea462abb91594b73595ca7bca129d9958710a7ac3f3fd121a9cb53f69c913c4876fb3f2fe92f17addbe494252198f2fdf09767feb85353ebccb79f67ad39910fc6fbe93e0c7cb73ee5500a3c81db5f208430f95512820f053ac814f2cab2cf7aae262a89db970da58d807d0d5cf5a287cabc1d4b7f3769383c4cddb6a314b69ecc2be9f35d7d305a4bd94b887dfdd121abaa897616f7e5a537461ef11b979c2823349b10db0361c86c0d0ac92ea4489b2ccc7c1522dd42463179151fbeb184ad8263283acebecc87c8f58833c1ede47913be93583a57755fd16edc611a4075f16d029b7af9e4af2550022b33236145d0ecd5aaddd409a5097626aad88a2133bf1f3ef6272979fda28c3ceb9a14919e78d9028b79e444bf07b3f615309a7d4cac2406ca7daacb996461a54f06f959d153cc60c25e46d3d09170276baf4b39d37d9a18f092b8c1ab2f99cfe9dae28c4534d36eb1b953354bc992ef2c4b501ad0512c372a0b89d3382340043118f43e502913c247a3cf02982b2ad82d1876d31edcd3fa09f9905b283119a088f0ac4ba4a5b5fe53bfefc108693d132be8234385feebff6ed6bda88ef6d1a4a4710fb9a6798240d3e33f1b385c20a221ecd9edae3e407547d2181987e22587dc4e1f6a8a4d7cef68dcaf04b0b697fd8a1068bbe667c56d10e9141ee5974215e6a08c1083546377ba5f7228f6b5121e54b7862f9af677897cd92ba8de74677a39f26b6cc83a8479e39f644258a1f808fb8ba7aad54dc90eeaaf9cc9213cd13a6c09f443e83c19bc43e82c197443e83c191c331d30524d2bf03efb7937f4afea8216701096a422c5f4cd105e286bf982bfaf0a454f1755f80653392c82371c7ba7c163418f4616ca4fd7c8d2b5e0b8d5cfab4e83d27389b3651acb6766d21b20530166f91033bf316e71779152568b555c6c4a4d0a6aa75c9cd7dcc8bde3a8c09949d0b95c2849b6c13ed9a560c7f62e04456389952d5576bd5e11ed6609a8bd8f3ebf647e19f1fce29182be444f0dec69fcfa249a98338194d9f2d5f14aa70d6df21b9d74d01a3b1e11c71368282949ac53352cd82d2a43dd679e8694edd19b5b1b77940b9c9d0bb321b8ecc01a3768b365d814848465def84783350d6ec3a1073279a687e3a01a63e6933206489a3bfb9ec7326d28dbe684ada197e82d452a984ae58ca7eae62510f0508735743adafe18d4077144cfd6957f9176ff49d8023fcdfc2b28b14cede2c456cb06787f55ec16d74c3876cac91c13bab853545e13781d1fa4130e689905739b0becc649365f083e5cbe9bd255bc3ed4b005b17ff569d6ba7dada8bd8cc6ba005737e80f5df14c4a282d7757648a3b07e40af85c732cf4b3ab5b4a2ee2d5c46c74e06bddb50dfcf5be7de0133dc7e565b21d68e8e23560f59ceed05acec5735a5950adc4ecdfabb0b2b1d1f468aed0427ce672d3e0a5c6ae31c1329ca09e4e4e6b0c50ca2135a1f30e2d5083b96c20d74d77755e20cdb04b62fdc48ae20cd2bf7307ed21f7bdf0e9602ded2cc6899dcf376b526c40e159f60ad2109f435256876f564766bb38ac6dc0446e6fc5cf83e2ff6918286461c95aa67e7dabf5ba979ddfd96338af4e739c29a2be051a7b05991e25049c9c7d155318e9c421fcec1fac42d5f971767e8f6cc39fcbe5b00620136ef80f94511af7751eb228d2e00202856f4d2913df71c2abf2b02cbabaebbe4108a4da91f1ac506d9728768ff47c0a59a735c1afab2b85b4d6ffeb4d96eb6de8349c4848a83ee7a0eeda83c12f9a0e51a6d5955295552e0688c3e5655502162c54bcb685f156777a7f095f5b0ed9a5aba2e4966ec49b6ed9329c7dd1bd33adefc20ebb6d174d9ed86126aca5162e8861ea7f21b43a53cd64c12935286c1a6d3a0edf584b278e91165c2af27c4d5cfe13247cef088fa19ebb9b50ed04c225fddc2bafa4012e7a1e30530cd95ca51d52f6513d54ee1838a5069839b2625d84043f5649f3a6af7d8def5442e3f22a6f41c1d776ce8f259e206ae69d5327c537abd30eb971eb7479f3e754f42b0b31c2710e382856b6af1c9987e3caa24a75a08d8af8799ad2882e0931abde72cd90bf8f0d4eddb138e6ca4c0de55121e1da4eae8bb616ae1ad94dc3a00de1989e995d837d5d67b839b6a0bc13384460bd7c06b0a3eaa00030fa81d8d27b4d2b617068aa1291a557f7750b6e626459caec50c25ba8b9c96f5beecc4a30bd570029621e0bb26f52245b513efc320b1ede1ac634f2ae668b9a175cde54ebbe1209ddffe70ff52d1f9cb14290b645f21799ade1c7a018eebf2e330562a6726949a215d2a532bbf5b918b52ab648cce52337f403998e82438cd2040e92dc85f363d977e8dcf2c958b263bee7dd0fe6b3cd9728c423e79388aa498be4665a7256f2bb8323143ff55532a6e9073900daea1255a6ec75a12a3a072d8bb05b311dfccb10859eda8be5868e18340c3c3846dc6cce73215a131b95602d3c3a1a7fcdd0d9129a8a396657908bff311b4d940e36e603b18eae4733e61430aff35ce431e5000456d0da32073dbd3a5c5b9a402b9a515d99291b1843edc6cc49c9e8e6cd232b8242361dd65002f0941254297863bd19a8cf32e2035559444a3dd0838f8c6585f31eb0f67fd5cc9c0f7bdc0f264bda06ffa52e00c5345309fbc5065e26b3fe190a74f106886807dca3963c0d84da793e31d630c16cdbf49b53495f996dd68e5e5d11f2027b73ae3dc8cdd33b191786b9dd4b84e4471eb4fcae60b347a0ad9ff1ba45e3e02ba8301a445724e772d51e7d5459d2538650ae414561f0a6d1f8e5eaf03ef2bad5ab0d8566e45287f81fcd9fc78c065a27a8a05000224e2dea7515e42cd053402c2fa2ed74762cc48e02b2568f24ac15ab297f4a0f5fbb2580507678b903f571e2315b450626fe4f28795c2143739e7c61a54cdc714a9a82d5f4187ff3154612a522ca7bbcd2649d92723d1141d2ab115ea2f4492ac6210e05100d510c05a18507f983ffa91c8bbd63e898d70e87d5f7f97f90c44a345339f319d56df9611eb4e7e915c893a88af4295817fe6db3c8d33f4c327dacc2c08b8ec1b9cbd3c8ed079cf6388aa815d269dc144770194a4ac0f91a2eeb59ae1caad7033151ce527bc4ac6a28834283ed82dd8fcbcc656121bbcb6e1b5aeb92188ca9adb400ca769b23485c65545ed95a55bb81561d316c1eb9f6cc5315e935defe7ae12dc33fa1495d6c01cd52bfd77c78fc681b67613be429ce6d53116922bc3d0234328b792563a74b2a614fb2421f43f3c5ffa356963199ef295415cbdf9a2811d60e93c105ed23dd070420e4b6e667409a3184532b622d27ac28498080e418835a30b5479b8fb0de499b1fc80e3f42e4b54d0bf3fd1851ec1fe92ead42d50c6ab54e781de310568ed41399df2d478643c5aa11249371596d77a86b31124ac6c944dd0a9145cd2b67ec0cfdf8e3b69a7bfdcc66512ddb7e657df1683888331d44e65ab17fddafaf5cfbb3e0362cce8bc66dfbcab67eba0536e88d5a0abdb266d2347b029cfc273afb498cf8954bd3f210acbedaf9a21ebdf72103b52a7b7102504d52dd0d20cf7944082bca52a74ff4b55907c48927b79e1e5f38d70b9de48bf0181ec28ac1b95139a3cd13dc57faaacd3c18eb45598612ae8e36153699b1b528ac8c5665b3e397c84137afbf5b479dc22d6ae84770f1b67c6c3262596178cb709cc26667f1d2caac1608772b6930cd23c3e800e084f019ddbb0d8de12532b1bc08406077a32bc451f22406e59fb055cf7dc0a16861d9dc680db19d93ac1ac0d1c74272821a1a6a53cad80c6c8e223f45b2f1e09ea79bc24b2ba4b56c8a7ea8213e341884445b15c6bfcf7e8166684a94f80b0ebdb59dae8faa5222aa6812877da3c7f4790375901c5ed0d1b152f42422e581ee58ca20c0ffddcb60c7b3795d5c989e6c647983237cddfca4fbd6f5c94fe79ac2633d15c057018713766cc4e3dc64048fe79894a30b3c0221f92d95477994c32ab724a9e1c66fa68e73433a2a89e1d08c712d56da68ff1512b5e5a36dd0c2414dbb39750751c7ab3b59902dfdde7664544d0fdb8d02d002d4917b4c39a0ff0301b600f2e0d501ec6111a7be9a7ec61bfbd5af0b8a9e9e28ac8c3d508d174a21b74f80f24708a9ce52aa104caa2e4fa38827c09908589d607e2928d8716b9675f6ff2efa00eeb1ce04719a4e7d834dd37edc8709f84aecdfe374250a82d3b488273a633f390a38a0c51f1e7d5d515b04d8095d866b8ef06e96691cd690a6825ae92dcfdda05cd5354d49f2a01c6ea20e0f28dd50b4c118fb1cf2957e00608d7ea0839ba8c27fef304fb29f36ca40695c98defae48e8ae7467566a2233fab24ad5df834bae363d6bce1b4639b3abe7e984a9a687dcc63a7987ac4d0506a516f41b2cd84bbe7c66d942f650e936242f191d79ba72cab6c7e970524c73d63ee116fad4d8b1f8658c3c48871bbbaec31d1e4059be678d3f4c8b40ef9d0e3e17776068636b7a39dbec899ced2a8773a28e91210b236b4f2be6c5368a730c016eb8cf649561b694aa72bf5f58ae1c63c798027938e3dab525819e7f44c609e1c5fe0a32f6069ce9982c973e5224f921f25388760d4ca0d8ce0386266351ca0e3c4222db7e72ea55638ad22c74a57d53ce6ba3a5f5e1a46005932785d0851ea1a6f1e537d145eaa609d0ca03530981532fd0a04a6b4309b85281156c6e700e536682fb7daa26e40533394de5fb501527aa918671ce1e3a1227b975064bf41ee59a31bcdbc37082012d470e5ebf79d9586f6a2274acf5bf8b24ce15e78395740a66f87024500d82048beeb7e720f354cbcf8941be0cc2c609d510e0ad3157120ca590ebaa437218e8dc088c4a431911caf014226ed4ee2ae1836329a9c0905cf736a96e72532009a9e612dea2f3a501a9c2064d6e1ed02464a7382bea98ad208a8193a2360aa30fadb820fc416fcff7f4782dca64ce1ea177968d1c20d0276ee573dd98f8e2528b313efb26a0c05f7df49b4229ce9dca97766166abbc567ca1ff8c73a75c54dcc3f231ca8eb99049fedb3a21a9d00ca4dc0fac401105520cf6c3364a5e223e46f7343e8e27f8f8016607185a54d8e957af3a3732c018026fb0522dbd01913cef0f0c9cd6e0a53fb63e2417420d97210f664d205e212860b38e7c8d58a8bda31226d781812307822ed8546bdb7870333d9c2383a7d624b704f3ebac798e364dd5cfed960d770fd34088628fef15362ed32bfe6dbf53d62052300c9ab35b1d73fd6aa19888d212f6aa1012eef5624e31c69c387c6be3154fe218c5903586961eeaa296f170b295e23317a1503d4adfcf20348f2317c32469765476976644e05e2a9b63be363aeda872c73205c90b7a8d312dd8fa2439ad0c4c04c77239dfbbb5fa6f311327c6d241dbbd428bc707c72e41f77ad2907c2351fd02f47029f6eba31e737d182f8f46fee73aa2f7db0e6d130745d3dcc2576e6c8bd9c37eff853a285e36d41ec7dc7f7964311f80cebcb5266db0a48a9bf2e7632aecdc86e6a04124e890d03647c9b029fb3cf4a5fcffd2e4fd379a34bcc5e2cc52f392ea8a7620acd8f4c548ed52e5d9bbf4ab4794117c60c38a30022fe932861f62a3cc432d0cea07f8c5e2524a84d37a232e870a81cc3522f29df0b94de6c83547a34f7182069419fcc6e8c5b0718dba8d8e1eecba729f4b29b58d198854d8db401cfea8905fb30883efab5863cd17430d61c3807cf4e03cd75f5cc39705436e1139a0509401349ef7e2a3560a585d8e0fde8733fc16dd98163e834cf2b4e34f151dad2f09e25fcff4439469ae678e730f2c6402762e4d184804b07c88d7df017e47957423064aef54ce6a30d2577917cca79c116997f237c82e34c1c91f152cb25e6cd8dc38fed27922ef985ac604065c5ae5bd2dbbb0513ae2b6c80f001d71ab882d24e294f55abd4b4c4fd31c8960b97cf4226d1531baa897cc01584aabbb7a85c5ec55703922db3fdd7f5dae9480501d8b66ddf6c81bb8e81be69387c1c85d61221685b82db91822d8e8532b2c77bb4bad02760bca09a9ea341e8699b22c8d1986bf1813ee90c1029613c5bc3462e42a47102a7ebe14f4100c7815f4aa70290e683a88d9f4c1040ce706487ffdfa86210373d926256ccb3cc506d44b9bfaf77d0a81ec20f87c744691588ee7ef1c42f3bd44c755d4661369b6c45f7c2cf8812bca8e69aa0d7d3f817efe793e07bf904109af190f093bfffeaba66b6f0f3b51867ed1af86f649b7eec5fd7861e1acb9660b422d91bdad796afeb441837f9d7182c8b670c4b546031591220706ca905003592156d8afa63f8c1dc1fac676ba257bd55bd3212643bb95a1162f9143c9c436be3701ab6d8bc496e0fbb9dc94828c5885e8d045aeb3884cd08a1ef822224387d02a8960d33eb872668ff53e340d99ba243ebf8db5726dfa52f3b9521d1451daeaa2cef858c1a27b5c092c2f22ef74091d58ec1ffa1968b316d5b597cf909e5a5c9b1d9eab2aaae491ad87cbeabbabe42eeaef7a48a3762abe8f13d63867405ae6ff25d6f6ef0dd729be01391a8b52f16cdacd4a5d24e9f798a5db48eae76435583b5d7e44bee70b68eaa78631fb0cdf66e21e510721bf7918d8b85c944c4746b6b4688cda0da913201f1302df90623809ec4d2e64fd50d71124f4a04e420da47b44a02ed2e1d723c0f9c7a960426cbe15662b15219f88ef463b5359f0394580d1ad666442bcc4c69d3fad2939eae3bd6ec245157118d53c6c845caac80d328412355e1c33bb8f66e4b7dfb549773cb76a260f36505222af4d58cb58b3ac47295096a29d644526b538c819a148cf6464c5d3d54e2209e5b4b688f62a8fb3a0b3922e1d3b945a5eedb3369d76d731f536107d0402f9d5f60d11ae067bd0a6df04ed9f8f8d216cd290a7b23f43e2d6524f4d17e94f7bedf803c2497fa202ddfd76b2271e9ef3b791ba4a014330465f6d9be68b70560a3c4a5c7a19d45f8c2782d61a5cdfd738eb4fa202ef37737d0c1ec1ac51afbe203940c978aba6dec1832918074ac7289bec5573ab937697f42af6cf25065fc532d983f61034dcdc45b5543cf1345f472090e212ba49f2e82ffc54f442d2629f02eef88b44c48bf18702dd69fbc61621fea404615f888584f6f007afda443d564245029d55c8ec31f9409fad5a27c2c54e8666bf571209f3f532becabe16b7ca0fb34f48ef9c36ab7d5ca3a1ef4eb5abdbf41da1351d1b39b7d3f16807995a501eda3bf6b110a5bec5739615df0078663d3e53a73efec50a1519f015b315be005f9ae2474be305a4bb1d2eef89c83c4280d5b7aa87eff4c36459684c37a1bd4a66659a5da343dd9cd9dda9507b917c3b11ed9c4411d299675e9b27134570a92b9b68ebac8c3fa0f8f166dc68193da21f3780464fd6267b634982ca6da1513fe03021b4c6eb1237235489af974a600430d3706a45c3b0e34199961d209e5442c85ac62802b516d9734571ac34c5d6afc40db8a7b88f156c329647d4d0ea01ad1d4b166cdbc04fb8de59e43e4f25577c05b6fbd28cefcaef16dab5645fe08ac572f7068ad2359fc9c3bb51886b640782344e7921bad8f9922001cf0a751fe15f7caa441948b870e2ce9fd6d6c803ff7892bffabb0c0f48854287916cbb0ae71890aceb0d73cf0b1a03af6ce96363e9ec46959a53869cb954d2fc5d068a90359cfd7fdc86c278b1d574657e0ca4685661fa81d58744301358e60c3a228626c4ac8894569892d2a8f1c71cde57a94b2a8a88cb9a2c06822539e1aad35d062096f816e3384b92b5f5433af902a2bfd7f97b02c7733c8f8f8af1e5f8d773326fc20d775ce34e9047c4915a725607157aff08f514d1184ce6928abb6b0d39fb6b569d1391fb68c83ecab34a7ebb76438db59d2ff6e567a48af888f60c27b70c4067b20f75bc7a07b5cbd1655a768de92ee5b2f5476bdd815a9736158c7c757bf4618d1d18d2efc32fda7f3d083354ccef84b7a4d1c457e9c5e9ff01fc0c2e7cab690277c5cc5e72d4ba03bae321472a89d2c54ae3cda7195a51b6168718c23ee689d4cef1d765eae266bf5426386a6af8486e9ed0b79bd66577de2b69bcf3a6b7ba12fd672773a6b6986d7fed0724e3862ce34c2ff2239903a2498ebe2e652b172a2e8a6314608ab7d5ea9137fa3223efbe7c04a4f23f0cd36a6f0529952c25951c458a83ba0b9de9f4b178258286bca029108e013362bf2312ecaa5b12a5a5c3b6a1f1f774d3dd4e54072f2f65d0e5945ff792e05173e191624b230da7a20aaaa514f8e0df5c213766980d38ed5102568916a877417dd5041c950fc5c88ee898473a10a0a342f74fa4e832326caa2a8f16a200dabd671098a36a9091d5f269ea23c23cd26702cfeab76dd86335730682f54d146dfde1c2ae92cd4863e4301edc51afa9e8d7190e5fbca1580053e714542b7248f632153d4954d3aa1c597c923b4252aa841bc45d124dc4c8d54bb48e89f3d48c649d31a1a988146fa72f4cbd7253f081336d624b14fc2b62b9cde8696bf36d480e1cf62a30424946c06aa51d93d0953aaaf543db15766020e4888014e6aed9eb87084cf80596cd3aeaaa910baf60ff55cb086c1a1a5ee91961627cc45889c3990fa842c6c08c76dcb2b9115bf0dd4c34f420ef6d927f971df43817d7dfaa2a75679756ab9a990e22f7054d1915b6ace483c12ae09b4e09964cb7724e92c928c988ceb063b1c6da6ca599e3aa1c064a6f00d6c3f39d6837e5b787400ed8fae4067552115681e3ed505d4c7fec01b0cc4aac186c962d010ba1a5c972e7c57430a228a4487497f6759152bcac87570e4644657bb559898d272fb04c9cd314c8700ad6c4917290cb58d34ba989896b5164291dba324f5af2339d7d9b825f4748446e6680f0db4e3a7d83f3175ee6a077c94801272c3452e7c679b2bc9e28a87560ed884f349675409aec70d5c53fec3f980bba6dc8e681d8d05b8c42ccae51a204e7b7cd8cf38542abe43fcd733cbc172a0473edf11aebf7be106b8c0f9e5bf52f14ff80f83a164a677c2cfb016ac30ae28eb4fc8a7a5b2401a50ac882b223f300e0b22f5fed4d99b887805e581814265d7e730bf1e57f41d3865bf842b4ae80632e004b1b8a21bde42c776efe64be3ac68d5aa043283e1390b21aa759d57d89e817e7454f95ed76bebbad9426114963eb162c92344b3041bf729bdbdcacac07e714d16b775d8839cb432bd9541e6873ee7236e47ee27bf6dd0c43cd01b40703b0b03bb243323aa34df40d213a0c3fa8caf5fbb6d3d04bec6e5ed2f1b2754e5be961b5e98c658fdea053561f7358a5367fc9f73f93161d0d49807b20100dca7da6ddde64a9e0f15e92b523f687a8745b8f11ece51d37dbd11a094008ef235c8326c7bf09e262759ce755f7936878ed6b82fa57367a53e243977509a0602b59b14786bd0f2ee2b1c909cd7bbf1f597e9fd55a8d5b6d87f07b8602006c55dc2cb3c3c03c1ada255117dadf5064f43401ece3c8c2eb69684aaf34914793ee5dbe7bf3a5875349daa6264b55f9e3f88ccfe567b68d3220942e3e3d54327fbc9329de1bde43c4f4fda2f5045f51ae7528ea79fc7be871c32a01da368e929bf31b7756ca174b7203fffbdc1cc0fa5eb7ff71f066530c5b8154af1960e66db0514d0cc0734bc5e7ff0d94ce19b97f4441d793e9eb496f480c4b29d7d2e4efb862c5ef4d877e6795e77cc39e6314a42e160ef51d3f16e90f75a9648c8a4e075135e78eb9de38db2fbff5cd203ffd4ae2304fd3777117fd4154d618579dfd2955f5c88beae3f5c26b892f6bdfd811c974c7210b4dc83260a5d4fea4566b5d6371b1fc2fea5a15c801a16fdf275e17014ba65113c77d79f7ca0ec170507da136006220a24dbc23949bd644c8a1b7d26fd40c507a0a328d8454b55596046ed3305bfc22f81ed4bc3cd88fe8b0c00265f7d2d83979bba4f33be987f2af6e6d2201521a06d3e805e1c7147237b309d9eb7ac0e9e93f0b833146d6798f3c8f2c5ec742b43a2413de78b9051f8aa22b1608f247d979663670a3e2ca54d187ece2734413d79c79e2e74bdaa9dfedfe2b9304bf46d5d3830a99edb9f9ef76a086cb06816d66abbe79bf711dff5bdb72d2fd4dd7dc2de0fe88b06dcbfb614d035904d50cc3ad799f7ddde0a75c7e081c8165c29b608d816840a5ff49e66aeb68a6f482b429358a8e00ead8364a78a441074e3183a9da92466674210cf93093c7ae62a175530abe7337c0cc21b152b199ff471071dbc1fe31d150089ff7338206fd9954711fcdfaeffdb37242fa757fbb192430e1559069b8f39dcab3945eb75b5cafc46c88fdb865366efe5fa516581bdf7301817f3f55e68c47ffa60a4ab336aa57e114bfb83d2e6fbddc5a1d9dc9e67f16a2fa66886cfb6a1b5ebb5ae8abd8a1e6824b08b3a98f9e9f8f2c2c20c8264f0a24334cf3380909082e4e9df7df4f387f4137cad1aefa28da7f78bcfe3964005225450bc522464aa646cb295ec863c1b06645cf590de5b344612639d96b73c30250e6ad691eb214b07b5859f253456c9fe757bc9f38b6d63a7d5341774dffc1d004359e88987ece05989ee9ec2b33dbd732a7ad35a267fecec16de07d321e16241ded0ba67d34b99a11bcafc577cb49ecc3d241f57eff58ee8059e57afeecfd766232a2c18cb06cf22d5323ad3708c77bc7fcabaeaf94a2ab3ef4f58d7626dd513931f69379479b74f081543ffbed38db4a61bca6c9c1cab30e5e6c07833f8067b3c92e216c23318d7cf677662da646b1ec91e9459d7367c03a59ca7f5abf317cdb67840fbb78ae39ea6920d49aa93449568a431686c9c8028ce29489ef977c7b90fd78fc58f09b7403f4c0430075998a41b7aa7d18dd335c3ca0a47d74f109bb9524aab6a57cc019ccc563bdbdc5d00ca097cda449f2e92f0459271939484aee83fd1d51f6a7519fc03945818aaf835a1ab726faed994b6b223a8341ef1d09085513872a2055b86d2eb9d20e986cc9a2724309838967271942bc78c68c6fcdb55953fd9da2e6c97d3791d3c1942967fd82424b6f3fd4a8db88c4f948f385fcc69934d1a195091a518b0c7e8fb35c60ced3fcc545edc9929c1a8ca165f59cfffd509ca85ee81d6aa3ecfda056b69b9defd0a19795dfd83c0498831763c29c8316912a55e45495cfd1d2118a2b6efc117b744ee1f29711a1c754660e570429c1829a5f8869344634a9d6b05b6c9644b61aa8e94985ea5ef59ef5a67b54f60a4c450b850a1b22391df37626ba5c8e2c0f2e9ffa14055513b47266fcbc83b94153b366864662646f9068d32575fdd73bf699ae90a7b20723672180492b2ea3525861ca0e6014cf98a8c9040a470aece911cf3192737d042ec851c9665b7f8bcd830040162c594b354234f1a1604ddf8c3105712f4703310f4bf5efed90af739c85e4bb8f55328f4cef5f9fc3bd2c11b6a751bdc96705af171f228c473e2bed244103ebc02682c633b3188389cf8b8896f0c847b2e3e460dc3aa4ca80f52812517fd9375231178dc73c78a44ba8873b038d030154c9faaecebd50f1da10f1eb7f69729554025e88dfe9c1cbd137ca9e1ea4aba164863ae4b34dce136292abb78c072ad94a57dc5beed0dd8d6401f7b527722b7175316165f47b75c0b072f16ccd768e90c8c45278e3a91000192abcffb4d21e8189852f8a3f949282b618cb34ad5253d7342799692c3daa5ad070bc006508fe1af4b74ae7bdeb856141f93e514cabc3c834c5ca1b8755e0b7d5ce1c6ad6b07df4c2a0e8982fe91c5c2a67643c3c3dd2549a573bc14c6b22f87631dc01d4d3a049bb68de86f5a04339d5a7c9b8ccd68c7013629a2f850cecfeecdda5f4ea36449785c0c2a5bf95b437e873a5ee7fe6eba6436eca1ae50c1b03f47d49325d2dc40efe8592cddb7b3e1b45e3026aed7aaf5e48da524b0ecc9ae23bdaf29edb767f5a26a331bbff04ddb492f8a64e2a83836d9ce136910aa436c85b94161d95e43e1658323f45ee2229960d7e7dbcdb3c42ca206850f93c62a62295a7c638900b107c37de0811c8840042eb0a412ebf9ddcd37589a3303a31c373e45d79aea6dc1f39aa54dea88066f3fc6dd96fdbfb359b780441d6cc55042f562ca2bfae51fc854271595351e4b8f33ff5676e362a6125ae6948933698b9b1a720353fa6efb4b1b62b3edf9da12240a9a401b47de63744d0c05f070c83f0246c98b92ba58facce6bdc873f1e23817988da1232887cb26115f65fdf03f32a551faae98d1beb2bcff64390b57d82b671887820715b043cf87728889ea8040e0cbc17f8e1f03d6c2eac5d8c1de7ef8d98e645d383be6c835f03bb300d58f102d629c47a85f8777999ce2c34da9fbeefd3659993b93fca6d8013323d4b37ef0934e7a53bed3251e78b36af1fc4d48fe3b09f6747c1cb5d60336e998aa2fc593a0a9041593b79d419f394d8eaeb4cc6981c2c7d80660134120b7091decfa710bc3ba99cb92b8f0a090e39aedfbdfd6ddef42ac24c5027b50a1c4a8c2720a8e51ef90518832ed4de913b6375c3306b80a7c065e1844f8faecb5ea5f55ff1e08977741f0e9e2fde7be3c0108e5afa93a3c70ad72163d4ea97027c3ff2aea2f120837f41f616b48473b79df8cdd0eb8892adc2f1b17f37980e326e1ddd21085693bde25b9a3ddedc0c0551af368b0b88f98893468708738500c1b15f5ace015796b38cba948e7f533bf719c3ae414f95c39a3841e5430f93524c3e38f2996fbe93ade8dee9fefe89b8e1e2576d135137f33e5d669ee9a56b9c61e05e97e61da3db18ae7bdf239fa96c8b4e114b97f02f430d02f87ff370f51fb3890ce9ffc9a58242d990a93e9584d2e98e2d9a473ea70b91fe43818552c6c2cc5c1a2b02ea354b27cf2f990ab36b0da8b51076e7c3b1639c7e81852c255b705b96df329941dc49ab8fd475f2549125ed27d70f306edd9a4eff8b79109506d9ba3709bea94fd03fa4b7924dfe3399aa09bc7342eae1109cbb5bbe4002ce11e2b38fad73f2abc6c86a7c521a1ee8cea929789d39ef76624e300ccbf422325fe99b69efb38c3fd420881c02eb3e4d43d3ccd6814721fbfdcd914a02f2de106320e7ada640e9668a54b5e32480f1a9da13411272aafcf202d6f3c96578c111cd4acc894319b27b94ae244d70ac21740f12432251071ffde208ba19e4986e01066f70ea7f67d8c8cc78c34f0378884362379fd124ce5d979152fc22b2cf4aa630d5bec3020204574dc975f7a1df3fbd243b0e934c6e535231434304ac4a291f5a2f4e725b0648b40ba8d9843cbd87c4f471980dcd35b18bf4e09c9e21852be7de289303183e7575f38444f058ecc4cc6a491cbb3bc0c2b875da9c8c05d66ddb09e4c2d8b5cf6efb1dd3dfef0768ebed33a9c4bc832b2239d0f10062cda84041b8882da16a23d1237cd4f70288638cd85330e97aaa90800b84f05eda706f012cc442f3dc3beaf8088b1f3145ab54c3f9a9558b23bd2f1ea4883bb64e8ee5f38cd25f566ff570988b7fedcf25c69701b1f48c7d96c2518754996f0fee2ea6f9971363fcf3cb7378add722801b5fb731e0a197e04203720dff274eb5ae75a8b91be936d5c2e0d1d78acc6c16dca9174097a85ec3108a03f1034f1aa45e0022651d678d0cd18a04a84d43d068ee2566f452325662d274c2e67900a85ecc0476f1f052f9fb2913a393f3f2c0a6118939084c9b9936134881acf77de67f3cc4b41210d4c984aaa7c0b5384c8ed97d9e61be5ad2df0e889c072f84ce64a5b875e32a7ee0403d843dd2f56613555f9919d3078cbad61f059770655b5644dfe3c05bbaab56b21c2a20bd15dc85fa56e6517bd343097f6bef4bf8e710adeeef020051eeeba7c10983d8e82d474774b052cc0484504a1a831a1a9888c3f7128251b95363f4b4c07d5a7e89ee555e1bf2b1c4e9c9b030715e849369e8c3956d9c654779ac06f07c1ec3879fc76a401a7f8aead108521dea63a252fc0a453387c2b2e8a5d9c267c538ace8fb66dfa9b21c6dcfce0f9cad69f2f56c2e15c35ada0937078fa86d689bf132f599c3ca34e4256875ce3745cc8bc7434581f9e66683e999369f5c75930980e367d99dc0340184c6771f4d0e5a3f9c7fa2d382716d2298c3e6029208c50b3e73b32dd4a0af8dc71ae9e85fa03c8ec81c56cf369eeb60faf7f9c04f8a053511dc943332018372729fd368840b925e73777a7f177018ae09ca095ac19eb84cc52153fb4a61384cc11059f62a2728a827c5ce390b280cce72ced14df6eb3bf2edd9892b42a384dab92a9e47f84ada74a2588d73a0e0d645885a81a08881bc6da7b89c48a16f042e18b07c12069ac97be7bd49740a5e61e918843d7ccdb41b41073670b798a61e7ec5126b60bf443b768dec77cea7f0ee89f921f4e20807f58037d0a9bacda27c10c385889bac0a0838a6ee245f69be64b956a368963d4ee034bae244579a5023b7f34a1c9eb63416204ea38a52d240a0d89d6e9ecfe38661209181a88109daf73e3e0d1107921c9f235e0c34f12db0134e090bb0b7c3f579e3e05caf931f31d3ce2b905ecb39131737f3facf5b26f4fe7e6b8c04c84a014864cac690f9189fcfd25db6f85b133717b483263b4d36e59f34b3a3d9103922c5cea5349db183e881c3ba97d0e5091b27a9eea32c62ff8bde0d526c633fd08dfe2a581d9f764ea896c77acdf4e0790d942b9aa8993ddb8e923e2d6f5c4109361aa00579472ddada7bb82390c0b8eb651ff5b1bd2604fdfe5acb6c4767ed8e057c01f34ae6772e8e8b23fab3cfc661474624fe477f4960cfeb884fa353983725b2b2b4e94af1a2c939f8f24ab8ee7ff36adcaf33b46eaeb29427c57c49ee3431a027b635433ee893538a2054db5641063858146a0f7f1ef039f70c0ecff47e31ffc53da337a302aec793ec52a2c510cbd57f2521645fb87a4fde6ec2af2958d13d462747a9a5a86fa9fa0b99fef00f829820fb3a38f2a23c4e38a468be9683ea87429b9abadc38c4427cbeff6b5ce6db791fb633f888a2ce1501360d121a3529f1e5e54070a837c9ca44efdf4667d6a7f2f235001345ca372c6eae26857ebe89973aff7b17edaa29d05af280ef3bc3bcd378f90d5cf7ada6955dc5ea75070af06b4b788d39f9b05668e3413db5eb15073d03f8afbf43871f6e0214f9b525d374b9f72c3bdeb6266c88c5c92779b2a27397014158985ccaa84069a064347df1c1846fac4d170318f5be1e5eb56cc61985bf5243c3885e1cc1449ab69f31c9c261a61dd8a2f0559c7bcf9fad0cb5ec185b3c944aeba2a9dd3b8cd83d838d3ac641599a72c89034ed58d113348ab64adb0d26554cfdd40986c7bf7e1f8ba2db85d7d5d3cdc49ac9e2f989ed13136a3f01a7ae460786347a082a347a9454141c718000c458a4c5febaa45a8ee4bc9eac82f31602a47c8c13d94be9e9cd3e741c71a6e2a90687082ff2ab3fbb5cfb09d95ed954fc2b02dbfe41ea90b06cac6a69dc218ce6c6193eabe4af365c0271ebde7746d47474c385541a698baff630a7e91322a6e425241a5686d6f0a21333fe0a7e87e1dcae29fac9545859b7e91c23ad042df0ffec48b03290a4f78f080e59e3011f13c1325dcb1af514eb19cf4159741b4befe620486377f5f8b0994d19991199a327825d5ffa319f852522c45e21683e32bae8e1304b46964562f0944755d64b45b8e8ada1ae86993fc95d9aa179bef28e74c30e2983628a85a36f4796315f514894bc0fa33971c25bc3fdef25ce4c605d72fce41993991b62f35b74d2c6c9d91c3463a35b3f384a57b6468f05c63e06b711cb34ce621d7e989ed212026c83a9d71d8fe6a211d7205f428ec50ae048e60651cb8b776b75a160b2ec371b34fca664348b5151b1b52a8fcb352f4dd2c264aa13c0f973c62a28d39e031ca875c1f0413cb27369a495baf622b519082946dd111f3256a5a4b316768a9d8da1b9e6606e279436b4db6781f676e2e51f0035944b83b796c4f4150cbd94664161873cfe8a8be4d6443f0aa7e048a08ce2f736e23df47550245c63c91bf0c590b3832c9acab6dae5ee7bf3f210175e4c9461f75c4ab93d425fc63387697c7d70ee5d9a38abbe986a676bed2fe4d2750fec5607418ebd0cd82e36d93108b9a8e55c6ae0fe34dc6bda482e31799fc2b095e332279b7dae068c8af9b01069cc3d1bcd928c1e3c815fe98e4077aefb810e66aa40c471f68375e60f8a496ba81edb5692b8575d7abc8c0b5a955dbe6ed69c7781343a891a59671f48287f466ec02e3bfdf9527290d00b46bdd9f84dbf3b703e55b97b9f72bde3b1dae0f419fa40c0468fb727e090600ed503a0162b711f23daab2d8027c75801e12355686594cb943441b088dce43c582f53177a4f119e97c02e9a3e6155204199c80832fef00b046fafc8b1bf4f56088ee6077f6e73981ad5a235adc22d79ca70b5bb6c3918956e8d27aa39cfa1876680302a58584627516896c36a2666db867f12074276f985ba1267a696603ab7aa25b2920b204c621ae740d77a143b0a95a6d264aacc87014a0610c1e92a695daedc44192b7c893b4c16aacbbb9e12c9c3f3e722d617f70f4a5af6df88003aa8f037645b70c9ad9802e788223153dbc3b115ef5ebc6eb1f2536bc8c9fc190fd565150ef4b2f46b79c4ff7aa256d8943c626631a738fa1eb6e7327a281b7a4bbb53fc340ffd8ab47298b52547a04aaee5d1106c81a9e9fb1b8ffc16d8a40d0bc83650e42485daeccdf108f363a983322a55c7a5133483af8609d56860e1ef5c78fb75e05636a90a3641366dfa081d836a88e440416cef8875504a3c5147ce98cbe05528a1e8e229b611e3bb1a28341e47a196931ae1fbdbba9a92ceca542caf4db7ad4a7dc99a58ab8aa0d24bc1429162bccdeb9cb30530b9e5cd3ad4d1618cb29fe3ac0726c71ffd4b19cbf61e1c8e3d0867340e6ba57340502207589c7dc237a0409bf1b1fc6520b372e4e6965bd5def18342a8e8c907d2abffe08e5860ba01de9a2edfed52cf2432975a3eff07bf3679b34e872138993a4e5310a2b1fe5bd48bdcc481a8bb4143389bd73a7e7816290b33b9b609301ea29380fe44ce498a671954ee342e55ddaddf8d2cb15c4a3ed82ba5c5165854dac4f7fc82f82c37cd77022438a517fd01628f515e4c872ae25d8af1d07310ae848c36651d834fe4d1fb8f68fdcd2acc2bb2fdc9bead38713e9668b520900f531cdfd272ccbbb1b72e6e677aa66bd538c4f82eb3a1d5d349febd312dc577458ff8d397c3d4883e21c8f6a30cc0918db0f457900cd49523b067973d599de240ec2cc72c0b7ecf83c1ec4e1daeae01773b839fbfa7a38d521d75e18d08799eb645917a6aa95e548e0d19e0fc516b1f962da368b9dc33585f4c6243334d721d31ec8e8066cc924152fb52297aa3099da70d27aea79f482559c47d273d07673e0be0178d90800bd57e979095688ce5380d063713cbdbcc91afd044aa169e49483034fa0b83effb162071b9575b18da5aa792310210cacd5f06b5431d8392813aaeff41b4b52c0d1b8a0fea18b84e0ef542aee8dcb2272744eb6d44849e0a1724120f39c9ce3f5e92ce72f78cd9ded2eb0368dd331e215a619e55ba41ba651779ad324bd55b3eec020df8b3c5bb073854edc765d2cb7b8626506cdec7ceaa61338df51e137d69575a91e9d3f9a3a1bec997cea751080ca195b19483ebe8ed1ecfc2c7af3ac0b33693f105a12def795cfe64f0adaf2c2a8cc04194d6c88351fe077f0d10c8af5cbe57d912203632bdbce89cba418dfe6a2876b287bc5fa8dbb21ea4bc167b19c92973eca612694972d2f9321ab8b50269c3ebef7b3310204942ab828a2087744b445c042eb69383b451454232fca11d333426ee9d234af604529e7899048f6a2490ed8a78faae832d20ce3f3b31cd1c08eb816c8654822aa6a7e53bae05db4ce2894667a07189ece585c684b0d66ea7f3ee24a565012eb132af3686c9cd777fc9deae96c5347e292e239509e9a0943ee7e2a677d0bede172fa9ecc2f940e8038c7e04583c1887021f6a8910b9bd40bc557f6c729dc6df6b170075f0e2df1a62225ee0ea55fcad3aefe7c68a56f73408fc718f0d5a28aa3ddb8252e50fbd6986788d8015e365dedb90db8873317fc472f6530aac84d869974a0cc69be77959a3529836ab2cd5ad13be0b7b99949ccdbc320cc3b07df37f784c5e57f4a1fe5f38e3758cde0743e07c7e419d97a26c9f2d0503eb98aba575e723ab0c22f97c7320c950f9f2ca8af1208814f391d1beb29463fa68e3e5fbfd035b745477ec012c51cc8269d638689392a2ba7e658bc84c5bc8d0a0841843e79795bd323fbf68259b0fdda0c5c952736c6f6ac133f5ae4d5472c9e3893ff81473ba53904506a34640e3f6b5cf32719923eb8200607aacbb821d97fc6bc2abe0956773d1f6c6713ae40cc0a806360e838d5bd9d515ad6424a6ff3fd04618fe36bbe06ff8ea461fa7589abaff81ee8d4507eb1abbaeb771043d2989acebad62c6be310eba069e57f0269b5dae895e26498b4b14bae39fab4625f31739611f7b6024a43316c223fb158db6b13b87b21166b7d5e6f7606c61a433d7d64a173c696a39ee79f5b3b777e20ee67e9f01fd73e2ce587bf0f6d52c77e4ec22b69481134d07dbd952217d3e835dd220d595cfa7c24768dd74c8a79e63391d055e1aa9e8ef04d0291fc594d8cbaa596df39fceb4bed1ea6a474f4550e15e377f9c21a6eb31a97c682377ecaa5882fee86de1b854dca73b0d9233923796d3364fc3c743ff62d94201cf0f30c5cd2370a7454d73489db1c13c76c5c0ba8d18984cc1515e005d13aa83f23a670fb62650a576f4c9c298357085dd8d321660a689aaef300024440ed6206be6c034dd1b63f2bfd5e192e592d1793308e2d64096bc57736e9b4433424fcea1120f7b0f90cc1c30d507e6215a81913301e22eb1cea93af7fee4621a287a00c2588208ec024d5196ad82f34a8317457292fda110b7fa235a7525e758365f56738fd180d5e1b48a784203e0554f30521a834e837f2bbf44a7db6b275be2e2d185d9dc7ed7cde7478ea523ec1b987edacea7de36a9728a8642729adaa35c093f22ea7aee4427e7afaefdbc27487d06fb836ca7c7944ee6d0041cfbfc9634ab6e2ade2c13a823cf00d603569a3d0c8d8b251688860e9e225e300ee3b5699d0fc69afd6e6a95292304ac4152311359429853824dc5703824eb29309d5781aba626ed73337dbe189629248a99f28482f4157be885429c89292ba5ab7b718c098b47bbd57928c8c0a1261602ea3e866d3113354b00127b2d4d123dba66bbc728f7902440a3d89e9c4a0ce39b2aadf6df50dca410d5ef42e3c19fba01650c2b1c3be5c5ada3aacaa78f06c4f06d6b4bb887818dba24ef35e48df618b74d70cd5f32216b07d68eb88c621a148fca0df027b9234fd6a8dfdd5c31ad44d622353730af211761c518058c379ee52fe5e48250e885ae5d70175d768f9b13e40e4067bd75632c2ac3dded4587d9575b98bca7506061b4d270827ac7fd859cb75b3ace21890ba35a4d0a69e4738beb255d10192f64bc13ca2042393fba8398b28c59ef80245036699da8fb95228e29f682fa549c4637b8f2276250e923faa944e10f947d7a4a821be89943fb9f1911fdc38b430a3f3077a4bbf5a8617e34f2299886a9441466cb2af85765f404c837861ddacaca14c32e31e5e5bdcaefa9186fd774bba26863fb2b8becf7253ab7ba019541520c81b154fc9fadbda940f61cf42169cb27ce500d9f0edb6f3dd8217e0af71b662e4426d2b3b0d7f37e935a94d17b8a4186ed544d6879ed22edabf946bc3a5f4535807f7670c007d52bd4f466468d947138ed1a847187adeec84108a1ece2b01e184840a2a24b07fe60cb0de83dc76a4cf99e3f4fa5b4a34b340fe9312bf305363bb8d97ed4973302585c773d97e4374d0e0d5fbf208d716a77d44d31497f5e80893fc2d044d48f91821264968f2d9f6f19a2a3be8421f4acaf6459b455e5d76610e443ca66f9fcc659b4304133d5414a8bb875452984ebbc5e703328864bea78478a5ed3897a296b0037d90d151db3f79c19c64f14adfe8539a5463e2548236085075002cfdc6a543719dcab52f9c71a67015aa0a0a295e5838965a109e79b2975158e2567c0cfd11e51fa9d3ae9aadf2e1f9bfc76a0d4968ae2ca1a2f90fcea9689578b8db443086fc17ac09e411eb0c25aca4ea9c58fbe81ae7c62763d02026f1c53048fae4135e7c500b5ebee3e599e29a735abcf5e8ae43be4878be066f9c37f96c727c65f7d525ddd273555e0abdaad4a84dde0f3c27ef099b872421b1dd1dc53879270dadb699adf06ad37c533aba059963a73dba4ac9517ee9e526a47282ce14e9de4fb2195fcdbafd5cd7be8fc008c198ea2f2c02536cbfa0c7d9a80280ac6fdf6743a28956477bdd874a1ab2ddc4a6b850ae75d5eb9a10a5a891562f62eb6d888b38cfe879a45efce135cf1397d004fc36a1a1809255c85c55140a610900de2a9898dc490df6e01c189d5dcf7338e2851455dbab25d94fe88def9aabef3334ee4a1abef1bd011b49c733da7bf1cb1111038889c82039cacea16ce858002eeed57ab682055cb78a514846c29a8387c863e49e31bc7c0aa4e1b83e52e1e40de5e3eb52fa43d61756022e82b26091c2c5171746ff28b227f119080392384a55764e35f50f1518384cdde3844d90da4b99eb4541679e6d063eebd9b7d75885fa6d73442a94be876ff3c8c45e67a70e9947993a756a445589a46a3e06bad3434a89519f71d3ea63c8694c583420b4d498e899637102b137e0e30f6c2ee779584a9655a31bbfb3838ed5f0184bcc8e1d74282245bf4eeca888ed708ecd35316a74bb29979996000f90a6e2e68d6bc75d689453be5b9d13ee6d87bc647f52fe98c51c1950dbbc015e70be8b4f80bcc82da7d5bd0dd9d29f4726a793bd6735c9e56c7f3b233e490325c5ee23a66441abc2bf3968f4e712f115a154576090055a53250d52f0d16536cedeb34e389112a25b1a0cdd0cde0eb101a37dec22546157704f411a7ed00175555aead7f0da932532adf7e8512a3dd3f5bd911eec74a1a2acf8fc699c43cbe7c54490b88d3ec4f4cfd08759d58c27b380d05f28b6d215afdc7f79a745dbe3553145e300bb017dd2f0a1d8bca99b2a939229c68cae867de88d8a17dc0d26ffd6440fb183516b384fcfdbacf27e2d0e6fd05822c886f872764ede37ac1005405f918b590dcc916089fbdbfe3266f7d0e8c453fb74308a736ca14e46e04e1645ada74309ea90cdf9e45fcc2b6b80fc9e420dc8cb90ab4f4f83655345fee478cd8b020f500be19eaa8ee3e741fadc57a06f81caa08204d5f98356eb94bfe786eec711683f89a047d04a6ea3217566f3f25efc9ffc39362e0dd5e37260139f13dceb699153cb33a16bb246ae99c2cc2ff6223b309ce204384bb946a06ff82c4ecdcf5b5ab0e8cef268af51d8d4b9a1a46565785c5095071e48fafa3875e2c297a01b04b6b8e2bb56a7bde31d60462e1e4e28e4dd360b762b694e17c96ea150339b417c6dc70166bca55cc2639a71105366537b56d93a81d1594215b46fd0d13c12b099813f5a176849660b686f5464995050581da69763eae0871cee2dd0576b62808af46b46120d2d572d049c2cc2cb90cad13860adc57c5160bf3c1265ec28f1741a5631ade49c354cb05fb8bcc2bda74e625c93a4e5a35d52f48de12b4475bd4da1e824207d520942d1a684f4ed9e6ae958faac307fcc16013c9075bdc351d7daab10d728efef4dd1675b94c4d0aab920afcf7b86461da940a8856be0485a6dc2ea3d33e246d8003948d2f0a5d439b7a8727988d3471fe89616c69bdd9ed831eb19ed9a95be1d2d77d6970ced3557cc0ccb5a0def14171f84d2e50cbd4d6c0f36782089041fbe7af430408d1579ee94a443aed3cc782a96dd1e8e6c6460b261026f4d41bba4f93a14a18af6ffe9830f2cfc341330626685c17b10c592e7e4df47027db918bfbfebbbfacccce51177392acc77ff964e1698a88166408d18600a6220f3b47a97665e2d451a2e6a65eefb6826fe9fe88d7585177357aa4cc9e27d7791bd1763f26ca6db1fb0d1b837a3304ab5965ecb647f706d71f6272977bcb7ceb8e2354db9ed5bdde25f2ae531848fd707f8f0ba7adf7f099c7a454dee7f856ea1c31b06d606ff97d6e2454cd366cd78bf04c6f22c8b29e440f9017c2d9181253d76ad9017c0c7c9f233f404c97081e913b8310e937c9eafa57d53b85d07447a81866fb17855c8ab2b8781466df59733d46a4cdb4472d77cc675bd2f074d9dc80d7b06319f14f4462c48cb6ae27dc625ea8f2e8ec99e9fa95c14a7b0619f82a1da97092b2b1527b77aaa7bf222358e4012f0cf65def2a460ce3213e77513c405587614418f68e66e824874803311df3f999ab7f404f03c9230cbb6773e8c8c4cac9ca0c603ad1c453633f902161170f784ba0f3313e311d89620cfd1fe5c73967012b81de8d1860266bc739139895e911f38ad04664f7ced96d4394630d4d28d23e40a6acfad442ef3044ea219504bf1380b1202b044e85027ba2f150b832165e90958ca59940ccd552aa7a092cc85eedf8077c908482494faa30d0da37ba05d8f12d69a0e0d1da25a451dd4720f0d25ac863a3d486be908a1d5a17e35a8aa0c89a0903af060da1efa8ec99c4065d726a22766620feebe9e9e74d7fb2ff917a279ab3e45ba2f5906f8d55d518fc00370f112a370edbf3f8713fae71fb2b2621786c335d14a49f338e75b99d6e514616244ec244a66d9e25dc3794f4b5bb52f92e42a309af3aab04ee66987a8391e746df02c286aa1005ffc7c1371e1cafc120eff56494869567b6502166b0293b089f3066603b042679cf92fffae2ac317fe16bf4b87841b23a1767558bc9ccc70c8d050ec6ddded14b9683fd28a783198366175befd99302c25a4e886b5f2de63122df975a704e1bfbf5b97696f6905cdd1b2b2035814663c59039b23ad60a8788b6816a7891e68bf12f2e299deb4b401d151425bbb06b4005d4f8c6d187399adf053163131fbd744851aa9deb41bf1b199cb9db9051d13350d9f75daf35e52bcd8a79fe505fafc960f09e54f554b3c2c55f68eeee845e9c28506c543c7fdbd95211e55300f1aa41c2f45dfbf337b2167c36313e879aa8ba21ea14e38da60284fb37e68eb58fd5b57a2ac205976344fb91e0bcf15d446bb32d85f4f70dbb6df52428289a5172134c28f8cadebe84984a4a6c82017f34a780611531e3e1a491fea603c54cac59924069ef6cfb800d201266c45eccd40e93adca972449c8a6672337b2c254f6e1e1adb857845b4d3322c6376406da41e0c174a002a13a5f0738123ea8218a1ca670d87f1658d0ac263764c48e23c3065d564619e05a68632052688c552a6f15e77cfc9f0652cd214f9c0044463460fd4b562031d842da2e40f58584614e896d0cedec8a69f4021aab53b942b259b39e8c42ee3782165f4dc43acb1332fc580fa39df89b55ea0f931238a557c5b6d88ec93291ef0eb721ac7d7bb33c74443644606699822e74b32c1de3fdf2530cf44cf1d12cdf76460be0cf8877a50c075648b7303eb9d911e959511b026ef40f213b1cf5f56fb3bdc44b66a49e4d627ce35e7e52704db1a19fac9350fdaf64c6acfde2c9b0633e8ae8b6be2ff36b27ac43cac1cb2515220e5a87738972f325824fd22e6b7f8885c69e3042b7191d61b004364d89ed3ea65a1ae340944628a3e6f90c4aa194bb33730354d7711b16ac8cd7fc94c1b6589b151e3e0d00d25346654c9103b9de31166288828e105e0ee7b56dd7121542ec7c0345e9014309f9e5f86dcf932b7892805d856171551ab8840559f3e7f0d24c63d900163cce1f65a5873bdcc6546902a1957a68bc1407b7e52e90cf120ca87e05d41831339834858ef80db09fac0f7ac672761bc048554d8f1f9d4a03020d27b1af27e3b2b2f981af778ed674edc8165745239177d9613f72d6e5d65875ad5085121fa920b6769a6824485b8a73352f602ebb4ba36c452beb37c52f0f6a8d9fef3778f531d8ca712100bd19d36bf71ba14dcf2c63060af9c3d73e58dd67da15029d97a6cbbc746a84e8600913b1320f4c86dcbf278dde947f9052c4a865af96426c0ab3629ea190de96cddf59552e86e151a200c5ce043535fe10adaee7a537a9cb3ebfeefcd0befee356719664f43f6fce44de17865fa44b430739caea90d49129b841e6b9840faaf7bf319c6b0ae631c28764c719196f3454a2619ff5db129616ccd6ea684491e83e2ecaf26d1a0c00203de21af9cef4509b79961b0c3836889ffaaa7eba7fe8588d691276461612240477e115add24f036438bbfa9d2aea987000cde69bb6a72fb2dadf4aa66ebe3892cd88c34840f1e11f6d92a55d90306dc44d5f2fa5810f5f76c848d2d07ad793b66137a1a9026e338aa0c29d87e1efa095773fc58f4324c506bcad8c3401bffab14a606f67dc5bcebe14af28a5d94c2cc2a112a4ff156a2a901394fed2889d0b1f2474792900dc0ce619cb9fc681d01065484a8e21402476a1cbb48ba9e7ff611d969f839b9259aad21b3cf08f6522a2a72fe6e246a9c41bb2c6a20f28341712ac448b9ff69a352bf0331921932c4be73e90d063f543644a96f76724abb6fc1c6ccb2a141a03b506fcb8ac4cf929ed51c084ef662903b9704100540b55a488356dc7f082e1f0975556658e008b1b15f165c7619778e5c9d8b582468fbedf8b4473b4b4f175aa128ac47f7ed8b81530b81123820f09c5524d56b03ac7826fa3df532f89b110c4fd818382b1dc74dfb5421dc351e03231a3ae21aa500a68583a769dc4c29e6254e13616fbb41c9315f02c13eaece9710cef714357ae4ae8ea14a27adb6332847ac19a906a97fe12f91d367911a4c470d6c7ab1e4778b3d915a93454a7e4e59bedee0e5e5a14dfff5a3cb6208798fe22ae54a128867ac81f9a4f452808111459881a02789f266b838cc1fe7be3ce8569d2d8385fa19449c9884342249dc2d6985199a74dfeded4ba3ad0724308d5bded47e74ddf8cb78c1d4ef631c1fb143cc168d1df724e697bb7da3b8bd3afd3dceaa3cb29d1c2aeaaea024ae9b9f529628010f8afc4130f8e3287688b8d9f2a823af8a18cdb4819bff816ed0fa95048cb5c6ea13fe848b468883c41a09f17e37cd1d018437d1df080f507c0c9822cec49734568e78bdd684bef6b8df10cde6d803aa8e45593e8bec39a1e80127a239975046f1b19c6c6702a0e8070eae103cfbd5515faca38f097102d22b09525c7a8aff0054f77eebf5fce9ba00a226cff44384d256d2cbf646760b0a421ecc25aa96d1647649435ce747df8fe1933272f44139f2d2f37cc721e013015885add011d0208d0795c3607bba3fe1127e3a603539c272d62708d52d2f37d880e3726b0ac55b7406c1aca5038d450eaffcb31c88b71e91104bfe90190e03c50dc485131f160bcf75811115f3f57a3c12c12fbcad9a25685004f81b3e4e422a6101282afc7c3028b86197dd7dea1a61b5cf1575e243dca003a50930cbc517e7d8538606233c32ba37f7d8928aeeb5d7320c237255054409c105c2f75c3c60a36d7cb7a1632b3353cfdab53d43d73773e9c36c5efc847e0a819f2a340fc9e49907797d535e31c8a651ba480af015c98600346e04a2a97aaddcc2aa47af70d8f828b86f11e1cd4712dc4c46edaada701d62dd9924d7bccdef6d304be2a85656e12e175b76a6f353d8f4c6b0a941f2351f619c5e5740e5d9aa1dccf99a2b68c5d50f3ba8d167840e3faaf3b1dc4d0ff979d8e2b090ec916845229d18725b06f597b2f2359fd06593c9057f1e8a8cc475c0e4a3fe745919b721ccca99b888e444423c3a1494559e9345a2a901ed7096db378b5127447a31c518acb7fccb20ea53b613575a3b078b9b5eaf24b045f4ab110d996eeda9ec15d1b7bf89f4d548347849549ef480ef0ce0bf7ccab20411531220a20d3d77d876e105e02cebf2f129e906106912fa1be7f020c99e30d35fcba4a8b5ad342a228531d257ee47b67bc45398437b6826913abd58048399c490e033b07a917075800359e6f8949060e180a35b7d82574152a1e9e7998391f6ddb5b157003f17ad50c40d580ae3fd5a320b30da6a9d5213ecae2fe8215f7d0a26acb85f0c772d5603438a9d0edc7cf420c67bdac978d75ae132a08e518c77315800db2fa4c52871450a2f44eb94d70eb3eba47c7a28e910077db28a2ce404a29a9130c61bdd24cfa06ab6df2d688d7bf5a5a8859098313a4f057165e8dfb73fb9abe802cc8690a9141a6e5dffb2f508720ba006915f471ec2acdce5aaac040438f094b0ac947786e4360e5135cd229fe811520863d45128bccb41413d17d32f97689a1189964a009e0caedd78e264a2341b0481e5ac9414665501bc386e657c79b371a736205b770a67ef25174176893a0c9f2709e14363252a92ec2e10918a8bdda5dd8db433e0241a3cf1062f064b534a8c2668dd5b7dd4b2c1878a9f9c510b55d2c990f819d43e438d8e03c75ad39ddab498ca308bada6d51537826ca755870444b49bda83c51b4edf6283850d3465a46b7a5918f296c0b5040934dd76874f376a83ac3bb74860ba07ac7a00b267edf2638dbb85cf293a281fe73d058af830aef9d87c0170fa790c7bec95eee2595f64471993171aba06dc0180a89a2e29ddb6674d1054186e82d20d4f8164e164b078ed49772b78080cd5b955ae49f7bcdae20e45320a4be59874e7f591a37b46303563e99274777271316c9006d44737d20811ad68ff462d7cab94318d33e936fa4cb07d1a5c3d490c72e7e411e18f0647400decf95bc540b27702313aaa60c21c28c608930ebf40d629b7608a4fdd962fe78017f0fb0bd367ff2b258af13bc4d1e0b7d625c90d0213cfeaa9331ca72a1bc70c910deb150c0f5574d10324b6c5b09156eca40582bec81027ca271e4b21d1af2a4585b337042a3ca8b8cc7da9d808051db1ee8aa1c938894e54e2d9e2563d4a59d467e2a8a357293884617148c947c58a7464ccc57470228e7deef8e70146530069aa49fa23975f9055aa6b735a27ce4bdb0783afa24a4adfa493e1543ce6a567aff2226ff3b6541860ca091171a33b7ca9f4df459e8903ff845a9fa4d87243810d57d7ffada6bb8f24d247cbb36db877df7bda28ead8721b3ddfe147185140a8e9980631625bcfd8e55893d103a32e0adbc6877e28a3aac50bc0e86c65378cd44e0d240f4b78566d92f83eb0992e984cbc4f286aaacca4d6dfae22dfbc0f543425fbc903478dbab3343fdff9a94336f154c06127f43db2c87d3b0aa52e3b20639132b56bf01d871ef98df05966afe3cb60c99d0b4c785c53480e5c6d5bd3ed563525cab0e6ef924a158f2eff7a27323fe8def7763762a41d9b92c8f5eece23cb596efb5683a44bd490cabeed49e87ffac64e245a3e310e50547fa21a2922e67ea2dcbb9df44828a9af319771338945b16816bc0cc70a2d8ebc166d4ede75ed2028af56841e943da9e4d0d5dbc7520bae4c0de828773fb53cfa4ac80d920e1ef9db2216fbe7dfd15777833e24c35e9f8a8d13bf9e11e8427e5d2c791662e4c930a6f22f54e68538cb310bf2f85de14b76f7d2072a4af3fb17c5f4d2184ab904ffaf6353615e06ecf0c4c83efa8fbf4bcc6e40328abaf1403c7885a5dab800f2083f4e7a0d415bcb8101e82050a2f3329c82a3ea96e98888330d5e9dad3926189c03e8cea2f5283ef6a17c8dd2e40c8b328a12981b867596143b095abb6203141e85c623d48a67ca8107ddb7b3278d4aa0d9e2618f7d9edcfebc594d5c8045e8e49668895f1b1f54feb519a6d3d2cccc4e8238bdc354c92db4078995de9c5ce02d49f608516c7c4b4537a2986db55369f12b72b4a5114139829fcbf10f08b777b494fbe7b5fc6c8c11f4758cec291b9905d25ed3863514fc80470f0f82f129bfe88c28fcec4333686526f389f26cea3a525fe887cea7cd5d4a70d44d076711067639138539097b5af5161f67cde416e75ce085285474e485d1d59e2d003bd3c778a78b023c7b1387097dfdbcfd6625f16e435748c521ea2a4f452de0dcc18bde63533bb837fd213265c38f953ff8fdee911abbbfdaef2df8f5de6bd9f76f56fd72f1f2398b9aecac4e32e2f79541ffdef3105fd5b296705b925af93376a68972bd882ee81e3f4ffbf60c1aeca6743664f0ee5133fb38472a6c39f6317835e28ace65e7474e99465bc0c235819ab61ae6445fffd0159f2bf11da187ed9c9cc87fe10b67319fa6bfdf630602860985ad4784a18c9a36ff3f52a61ae28b21c9feb6a81e05085eee3d8c6cb9cf893636e5d1fc44664b55f1005f35b4eb9ec3ab93c4a6c01926c85899d17a437b7c7bd745fa2d36775c80597d43845884747f52720bbd4d9ab2129b120fb5d222908af7c17c68453bef0544646bc9b52492b6d7851837ddb1982d42c6a8a0a735b8a6ad9ee4e346bec0143619dfc1194860ad9861a8d5a477558952781b84481085e6be1765160240e26137f9fc3ab7fa7f745cbd8e9f97561953a9c02f9fc2d6a3c264e2096479106d956a69088c54d11655246cd93e12d4b810c33d930d9524926309874200a913b1c2755119f3461b39fdafd86d726887a8b0218c84cd78b716a55e10ff5764990d0870a58df4a8ac99ce847e9d9b5a61d1eace785f2280f460481a9501aff644c28660c685ed057d65f7465504273d4eaa5b3864439f23b1cad7489e5dff0cd567244eb0e49c23141047e6336dd1046bef4ff17e949168a21df1453a62a2101b179e2fa1acbd5474548fab837d4704d8328ac181b4b37f4e491b9d6c6821578fab7807c8d78642eb09535ddf778dbf7e3c1a8174a7fd3c86f90547da5004f371dc794a1f6cbae88effca0a6dc590d4c3f4b31c245e40e4d401aeb20609b41528bb8af929ba5bc4003972594b65571548e264a9af1f6eeb5c8722654d6d81b4a61aca5af2c42d5db597b8126a1276dc860342662f8228c1a0cd2f13c0709c6b8ec68e46f894ef28c775ee049d4daa253d8c78c2c2f77adaa869f68bf5c2b83826c181ae59b73954f131f4c3651d7ed0bb09d51f5893565574741a1c9e64394510d9c233b5128a06a76382daca3e18b778f8be8b2febfe94ed782f5baf445c41acb8a73a65ea98d8820823beac0a50afd1252a8641fcb8800c2e0694e185069929b9f8366450920591506b440b95ff6d931b818f6c8539c125a3032e493343c194fd5a506e7de9717fe8b43888d657d63bdfcee3bd19c3d021238c803899fa60d24e33b5c8263c434c44336a63a5b11713e9d748be5db1de927c7603757e127e912abd8fd42d3f9b6ddcb4ad2327462d1964cca8bbaa28ebc296a5de7746bdf516e82bf3065dc0c165457d3845258a6d819350495b2d3e3c303961eb8ff2460d9794845e5494f731c49940134a64fd5dd0a92958eb91ca13814d0c4ed78ec0004a581231438b66aa117291228abe2f585ccbf634e63516db435671f803ac3157005de9908284cf932fe373d18a8f0e0277312d5c806b4ab5f665153399fa7e1278f3d5a8eea451ed8c5d7d85fd4da9d70af5762b763caf15a1c0e7dae15779c285ceda55081391b4e35e224862593d219ec2d1835022a713958b709ccebbef3bd89e9d6111c6fe3a2e5c8d04e9485227396fe34fbacc317cb738451562f292a12ae42b106f7cccc1369990b95e5a84dad998e1c953177a40076759502d224128c2b9ea43c984ed1dfc09b6693dc2b4f1b8e399cb3dc1ceac3dbfad642a883023cea85913966618ae1648a35d4f4b292833e63449e42401fde88ccfb66100d462c9cee7ff211da2c09d26e453b499919936ee3493627ad430b2d9667016a6e94a65cc4c46a7d90235cf4a585bd552abcc841925b3b8b30b46e3cb7649c738f38228efd0747e7ebac029992b97c52654b26fd0cccae9e896c936025c1ccb9c4005ad5213ad1afcb13e97a60681bc50c0519bcf70a1c1dd77590a2b86788459a47f16a97909f78dc73770ae5de15a8364bcbc3e35b84a64e5e26d857daf71dbdfb5a55985579027e7a793593e393a33e22aa6959641305eb1227f43937b592886c52abd4eb294dfd22475b98e3a5b431ac678a2bb10f9967a02625250431087e5d13e4333f6c00add16799dd0f2943a1a68b032e7804d4ba4cdca1c2df58d927e122987c62091c633e33f5b133dda892b25ce8bfbfbb7520e77b4b6205a5cb0e79342f928f65420329477b4eb85c1d876368726573bd84d7d16c08351f793f935eb6fcc615d6d4472e7f6285f9d1727c6ef889301f3839c179a7da1159cf1ad7d3d4d576215e8152ec6fba631113dc69c03dcb95e9f8a06ea97f596dc9ce39658803a35eeda7880deda2eca4d81560dc0b80f943842fa54e81d78b2eb8285ee472368c3908e6c29a822421d6e434c8dac773a62e8237d5a99d2dff0fb1a805a08bc23bf9c737d53199a02e7f9ab6728433720fdefef05a79659f9798e8436fc634998a6bf77a0921268dec01a29e0ad821600066705cda322b0942b6a5fde571a61e0678d80ebd0908230757f6142129ac318509f59d04041249c505055e7d62698c7006d610f7572f6cb1a025124cff694152481f4a062a7435eb76759c4c8c60b0906bb8230e1addc5c45fa645e1b0543e1711ffbbec4468b207ac1ab1a20b54ff0e79a9412044469ed2fb5e1179d92e24be3dca770398f794f6f5ede03d79a7108771b1d886fa7a997b41cfddf52237610ebe1c029a8f89bb211554a488387298a3c21fb0d40c96909558920b2d0339e9ba249019ae390ffeb99b7cdab6d33a392de967400f7f024888ecbf1115b3033c948c52663a79e840bcb3cfbb1d20d631fd4ff50db0f3a745bf8a98c6edf330c72eae6f61b93ac97b93d6f55359ddaf76d3eda3906ae0e6ce2465a0818cb55ef155389602bc8e4757075e18b7269a264b321c00ef58f274f599a77127041754133c5891c58ca66c9e671e7f465a4bf7c027dd1f34182d9fb153816570f6f40e602cb2250039e5999424c738698471cde073df46a8173569c6db0076323ee137b5164db77c1a7acc560d77c9177401520fafa9413bac9e23154b274c6023399fcb301cf901099bfe0dda78f8fc8db75765d3a857e4bfc5cb79295cb13a7a8c654250f9d6cdbc7a326bc416eca002cbceb30f4529d9dc36d7ca90788174e5ff26af9a45764c0a9655f8d8dc8458c4b2af0a92d75f33a511554ad465cb3ecdfbd45eb2a5e91a2b74f51cd9983bf92bc2d32b3d4e034bab0479304e559e908305fa7add04b986404372cb39033d9a164695ddcabc884f72108b6d30239d4f8434e53527a7aa8a0c6536b86029bffb9228473df97462d41de9d5838d02f364d2240f2ff5046d4c611860c8bffecc35ba2550a2c447d14185bc09ae28bd59f2cd1969ae4d8ae010feab22a71f5dc4d3cdc5d424ef1393799ed5ba1f23e33cc93cece99bea6fb93f950b377abee6c80505f0cfd23acbaf5117fd54e5634e5bfa0a1f1208eee8dcd096706ac748005d7c64ae940dc7d72fcb849f142ca3fe936e72c84d4d521aea97dcbba3a74a099a8a8cffe0f45b52b09391413883eb161da7c38efe86989974d059c9f842a7b144f0e168349fc571ec2553d549f0de50ff478fe5ea0a24a0f106700e8f56ce22daeed89574087f7fa6b634673eb54980022c68aed03209c423a4cb53a81baec9e4b44f91bdae3006fcf61c9d661e4d9b342cb5a614a10a2d8fefd473c3ad56da07179083be44aca65bd15d999173aa933c1daa50258991f1cab2efa04cbaaa273c5584b6113f6016b8bdf79b8ce3e62fd28d540fcee28bdc640725b7ca1448d8fcb7571e30e91f5a6ba0b23fdded28858400f370178722f5fb70538ccf202e6beb26d81293227c3b651650ba9d8f2f3d61acade1fea2463b38971bae63298a6cb2ef4e84d9c8f21b2680d495cf2dae338cfcebcb3150ca238e2fa34173d5c6409bacf91eae00dc8c4a906e06f86a41ba571961e79634d0ddf4887f9f96d291e3da9aeac649266762213a347c88db941d3519249f661915bc24e82fcb0faabb0bb5ac30f83ca7f1e826e9828e7cc1bf480e710d66cdaf124e1bf8a214556ee67518131e4e7bd54527d50c3e1535cfc449e8f5a45768845093c60e9b9628c233c92bfc01747eb181a64d165776d0b22ee13418fbb1a172986b3a20a24dbe1165a637ad3e990f32a50ca8818493c062e1eb4c40a562ace5e5657cbe31424ec248815330ea675cc0a291ccdca40fcb5831a5b7fe5f2af6a52bad90ec2113d19569fdcc3ab1f045427462610610cc211b83e8cc62a3274dda7c07faecfe5a94a41686afca89513f265207470495e741b9aeae82690ea82c3c57d854766f0b825037652e87902eb676a0cff77cce229bbf0f5f9fed23aad95b55c13677ded6cf1be087137880e06bd0cf71b8ed39ea289a2329a98f48bc4c44d4c54061da9f21dbcb5776ae3bb7a4b7c0baae9ec94ece7ec8a7a8b80a19afea7da117b641964c94b9667717175754ac80d26002f3736e1482db3fc840a5da348cffe13308c22a43f4a11dcb9d0800af0b7c20d4640ec0b6cc2342af6490cf3017a6e518a198f1f2290b11ec50db7512961467c6fa9ec01e841a5b84fd1ae0abfa5e6992eff181899e142a1662e529f7a4b96c2aa121b97540aba5ef9cdd14346ad3c166c7bd18946a2fd8a2e6feea7e5238a2821dc0059b6078120b2f8cc6dfb65de819224593a4dd40cfa811a210f8073836ae4e1da694e72e342718e7dd355256323c11ba386bacc59ca502e401e3257f9c2e060ae047dcc4ab0325a6088708a6433a7fc00e2f0beb063b7578b381001a8a711a98a05dd2b591ecb05b464a073482db1fa11b49d2c68cf71825668a3ad87e2521673128cb306a815548d5e1ecbe66c0f74e426c55e1ecb0e12b48ad5e5b1a44b4280f10aea59d290e27059595e92088cd523fa2db62275624fff44773993fcd116cbde923274c58b0ab45e32c1546c034a158e6a5b30145ea503da7996d0e7ad34be5c6d5bb8cb595a4b27a6ce13003b19ce0bd01fe258834645ce122ae22511c5161d628bdcf0f801e33eb7bbde4ff37afda35e0ad72ffe8f269670e1bee17d834a7ffce22d3bc8de64cbbda54c29a5cd0683068306346421ef166a21168a21c68f6b70842ab696953f9439628c514a295329a47260be0ec373c498715877c55239b8fa67a91cecbf37dbdc5870dc8cb9c17f3e5755d908abfdb6b586bca687e8526ef69c150041507e45a66d69c2f9f29fdc04bed56ab558469eb59b80f06b3748e56b3fd3600ac71c2a9a48434b46488db33538f252fbe79459651a354e3885a8475388adc110f08f5262526ea80f3b15b4dfde533b50bf7d9cdae19f6ac76c55ffd9aafefd981fff07aa92406307744a6e668750ca47fde6eebb6973325bb1ea550c72a8e1004328575709872f0870021cb2f0828315e011c5ea2ae1d0831516ae6931238cc023acfc35e933c3a44fac4a9f137b29a7c4a4f499c9a86931238cc02b50c6cb74333191b24c37f33134326464cfbf71291c8ce2994c866518f3fa67c2180e4de09fd96233d8a20eafd96e2fc60c931ebb3119dbdd1d6bd9b1bbbdbb1ba63bef86114497c8ed5162324ab92b252633e93353aec6a1c2f82ea5e4dc93acb56024c7c86566d8cc30cf231789612d774a96f24377f795bbcdf5203d62188661f10a0cc364c4aec062160cc33029b12f24e65fb29c207ac430b98aa9ebc36b5ac76b5631468c433df699e3ac0ef73634d98732aa8e4dfe7ecaeb788b07c4f3693048bbc03630b02768ed058bfc30293b66e6f828c6e9be7df8f00192c2e19f0f12d02984da3239ce2eb2931455c8b0218a183728e151d49095e090430d798897542e7ac9e0f0030350a81728dde0440d99b5a2640316a6a2c5450e95f9aad790c3912d9d53dd38eb423ba523575653829c4471648601c81083f8d188961aa7a89186276a0cdaa1a3e1ec2a73d5abc94436654a1c894804612938e245415eb34a35b4acdcf080238c948ce0e086071021a186b0ea55cd902a90f88113c8c30fb6a042b3ba493ff052032b66c8c28a1c9470c08245d27ca6a25543a6a255b51857a8ff8186f3851c5c23d001ae1c1274d21a657cda3851a671c08fdfa790a08cfa55fd313f7bd487ab1b9c4a617ef6d9e4388e7bcfe9e7487051ae598eece79c9aa6693eab99fdcc661735962dd89666052e622b9b73c3c9d628616b74cd987b394aedd63e7b02c2809a00cdd7be0423ba757e3f4d7ae6633f7f9a842a28a002592cf505ffe2a5fac854f91a90f50522a06e7ed38b64585307f303bbc5c2d23ef38f88e6a9fce5664212babdf64a4680e021b2ad2781f918ca53f9d7333f95fcf9cbc23e55f62c60d9639f4a3ef6cb6221fbf9f3fbc1c01030f0e243abc5ca3e955c96f6bdd7f8d70196401419b1921c3c3d0a68c8454c1e1134ec22958bee927f0c8df3d3386110f971510a09aac373baaee1efd81917a7d59d3776c7766e5eed0760c188d3eddedcedd47d77fb6777f7c605c70884b8bbb179cec8cc3122f18845cfc89c25c6e8ede8e87883ab78459432fa17af702eb6a5a9dba0968d3916a4ec97de8e8ed7fc40c71b5cc5ff188966dcd238f1080c74a87119290284225b106088224e45b21079d2220376a40b1aaca06c60ca860021635ad18e6e51036ab5c56be3820a27451732c0be90a2b58ae08ea4b80a2353617e4f5061607e12a930325ca9300f238503a62b402af51bced4144d4d7d4c7d11414da5beb86a4aa9e5434dfdae004449b16d3475b3a9dbc7eda79724eaf67448ddbedbbc58aadb838db3495f0c31c56e4e0c153930e20b220b787531c4c58bae4ccd9ea8d9cf8c0b126c7183c5f12e1543610b2432cb22801a54fff954c593106d26af0ac39c161796640386618cdd4036d920a59472092c5a920829a1c54b1750aca0b224d960050d590d65862c0d01ac494fad17528d3f6fc4f5828b11473380410e929c205a22882d3ecf2cae1ae4e83a0d72185feaac68e8344eccbe307e3a4cb5205269542f6848690c61894ee188af56ab55e428c9155c38f1e2cac11169440a58b0558c5695115de934ceb2fc38c3801ad2744982290b23598a6accc2881a93862003e84ee6933927457129129465d1b8e0500336b9f81024776c119471118286a804e362c411d762cb0e2dba48cae504397486147772692207def1d4e4353bb9b84068c7109c3a224e4a337c914a32801758d440802050545da519ba0ce0896e759566d0a244b5ba4a3334e598814969062230f0efcf565d6f6babaea7daefbad5c398e83ed7908584d846ccaf8c671e388732f3f22fe77c42f40b79a96e128c2fe4275c2ca5eaf093da8c04887d3c8277016d905543a061ece91ccec2d33898098e3cb1836e6d79c2b21fa76c19f6d987e9c4784171c207c21996a19ef63bbd2bd49199b7f21ac6da6582262ac60082d228652c21b1262645998c22d98c61034f4e46f2d46234d1260a45050d5d028409456d44201ddd8004885ed24c9c4a07b93fb953739566299aaf30169723b99117c98d932104c4d0122a538e9870a9182ba80161f63c0df2c8fefb917d3c7aaef037ab071acefa537ff007e9d8c91a769e4a61fe8e6facc5afa381b6a22520e2487b47b96b8ff25ef02f3a2f1470a0d03930dfbfc5878109b30f39f662e488607edbfcd9f3ce87931f11a5edccac82f13ffcb587e1af3dc752f3b28f44f40b7a16cc47157fdd4cd7f46b2ba09d1419541745426713db3eaf4725102f3e225ea1eb6d6d6173767936085a4c4822b3f79af92808ec6f0efe52330cc3b22fdcefc06f7ce9c110eb8ed8bf2c22d4af07a3baa7ea9fb5bf393d100790e62b4b284ab08c208a96111c69ea8fd9dfb93144897bf8de434b69410d57df030dbb97122d980d70659a8ef10f7908480fb53044c92eaf0024862f08d78486eb14841fc837d72de58361c762a93868832cd4eaba2a46432ca12449c9fd8341dad81bfe06ec092ff8172f3bba55e5d780bde1da5e07ba4a4f05fff2e5cb97da5f03168c2ab8cb4f676f646dd6a921585986d2b0d3b0659f61588c40acd1b1280463be61e9004850595789071c146009edba4a3cdca0c30250578987293c18d5f07b8ba416282dedb3e4ea4a1e1ed7d5de4041c36deae17352da750f32ce32b8cfcfcf21013a122833aaf23659a0c7d688cf41a8dd3132a7a47b60d82cb4649bf99918638c3ed1fda36ec4b634616caaf2b7ce3e9084adc19f01fa518bd2b2d758d3b44c7bceb0d8621b581c32c5e2f45377b1a07c192f624af582f2851a477bf911091071282e699cf9dd854ccbf862c47c5e44bf229814d71f7b0b68b8efad103d1d373c14741a94361290e0cc1cf584f9974dcd999cc9005b835f3ec83632b7c1179996b49961a856a2cdec51dacc50dacc309436330cb54ccb0ff3a9df1ec59307745d4f4f4f4f4f4f4f99cc27e38bf18594c27ca1cdc77ddb3741d000314954326d8d661b315a557e7cb23596ee8dcb9f93d2ae7bf94bd953f9924a83724a95ccbb9bcdcfbc179c911a1c82ae8dec896d645f346a2eb6067ff1c9de7c91696fe44b25745a098c14c6bd029aa0303fe3e5c72316274eb1a07c39e38b465acc17dad0afd5cd2902a500e8ba19f12bcc179960d080c6f7efcf8be44fda715e94050909090909098983268a6addfc4c3ec641937e554013348c46ad2a3f1a21b9965e4c325b63a590b115e4394258f2a390e74496fca8240e7d2c8a4d362858edc7dffd5412908f3d0b8e7d404d7a16c85f7eaafd88b25f96fc653dfb11ed9837a605195e0bfa91f7145846c0c2753ab51ba10723f480668b369a9d95820842d0d09f78dcc96b4ad8010dbb656f1844a914e60ed8c5485cb4af255751d1f217c3021abad04e7b933b6571269dd76aa99b406f2667a55ec8012b21d085804cda096d8dd49c9476dd3fd8af55bffa1583d7c407e235d1998aa6a061bfac6c4e347a6d0dfe4e0ba5c2688a1a7f33f21c9a45a3ae24ac3cc799bc26fe9c9476dd3f08ae564c948a100410cfe9d7cbc90926323de938adb2803abfea406aa30353e3529eb3d8a1a01da0159b6c036324f6263e17f109ce4a7ddc44ddc87368c450a1fe5da917a35517fbe411f5efcf3e9992c0b252bf291eee1909fb908b7060518a28d3aa5cb492de6bf7b51d0150824d6e1ba7fa63f4f7e132fe7cb84ac21094fbc17ddcc17d5c887dfc2020e5730a07c7d40a42eac6defc622a05d48ed3e9612e770648ccc004b57db8da334002864bf5a76385d7c2b362df2f5f074f8fd4549ae6c3473e906725bc447e10b94d44aa0c2212878e704b57ba90daccbc165a537056290456aafc1b8db33ea8f25dd8c1098b949ec2812282c68a83bd0c8e2ca502f6330d764464fe3958056eb509c12a69c815133190f27584a046d91648849478108251015d25212521185816ba94e2b81e68ff2a0931a9d8af6f18c6428da3d2fe3114e771d8dca67c2e224a3db37e98c5bd4c0ac7fc28edbec33e1b50b56a50feb2745637b06f6713e1075a7fa0d8739dcf2d240d2a69500a3528352bf46bc8462b603a66a6d0551212aad86fbcc92859888d3c466a50be00aa501551ea97c53dcae3e9d1625640e3b1044d793dbc463ee7f98842c36daa12ca12757e412410277655f9233f062168b84ffb1d7d7808fb9529283666a6d99b1eb21f7d4e4add6d36a45bb338dcddbf0f40b32bd95a93d2676262626abd8c3c92e80ae1fcffff9f3c74f0c0e44f791a696664907037604c1d9fe051860e51c60892cd33e3d91926a349a49991e1646a68e641c9b0e2bb86a780523495d93c681f3a7662f849229c4f413334b403ddb523c4483bce42317ef3056f194e345c97cf8ecbe572b9a490cf8da741973b0d6e41c3e9e2a01df6666f76d9fc4960760d13dbb922ac18f4536d1f4f8ca7458f025ac40202e35371d1def4c3c0dc97d0033ae39391c105c5d88a3e1e0eaa28d58e50fbed53ede0df9e5f48155277baeba672506fcbc2c9c582f196a5793cd4200a6c834bb027f433d1fcb888461ba59a4341d3aa54f854fecc04658287ca5f68233343433a8310ff1ef9df4f93fd7afce3f7f32384a948912d32d4a7715a1c0edebdc0daa06117b64610ae2964ebc2deecd3b08d0bec09fbfb855bc57ef1eb582816317fa78f7eaacf1f3e5ba54a75ff1117ebae52e5a8ca67cfbe3b85a3b32cee0fecb97e347aae8ee8578e4a49daf863588f9f3f9583ab0f4c642a476318b61206dd1b8975330deeeeeeee763b4de7d90467409b06411bcf46468ee338ce9b7783a1eae158f2632592df0f0858d46b38994a01e3662a076bac699a37350fb5715b2a7aa738b5198cf0054fe3540ecea3810395ca81550e47f584ea0ed55d9f7dbdbe70a91c5db9bd49e5f0caed8d4771c0487130606052746fd893c1d1e58895c66c31a8989898182d26262666c6c46431588c8c311ed3311c7302bfd8c50cf38446514336e2a1d496cd07206088edb3623cab8028a80ba7a7713819266e18b8f2689c6f4229527f884151a4d07c94204f9e2821c5b69cb9a0092b4840b97b70c5513061b27df49db3bbce93a06d65a3fb1d25879928534cf041fb2c15666ef6d2ddfbab06757a1ba73b7e389d7a7737b66f9ba0057dbd48e04e9d3a75ead4977a0f9c301a5a52d42038a345431eda6716d72747c4a7c9556471c2a589cb15c58997902926f880fa2c158ef254c3a74bd35788c0fbeb4a297d77e3faee7e3dfb294a09ba27ed966977386b0e58e065e7c60c1d68b82e1d3a74789d0e28ee2e82b8bbbbbb5f5ca675ce3f7ae156d9dd940b3c4629dbc4141d9d0f596989a61a3e85120528dddddd1d73772cce06fdf7c6bb2c8d954d83fbdd206f3bb3733132d6e43b8e8a7083b92ba0fc73ce39a70735f161cb8a4667db2fbf95ced3689075a46c273c8912585c477614bfbb45a8abe4a40b126c9c34dc3e3831738f06f97d70f5f1e17e0f1f3e5cd0d91b7eae09ede183db6edab4695d274772024ce5e70f003f9290cd86ab2c450a17b91e9888a0438cbcb8ffc18ccf04e59e8d836dd7e5f4fef7864a7bd8a250d73d46667e0c8b8286ab1d5f89c2737431f624185058517612d0fdd5f2036b4728818222e2729520690853e4c95a8aa281aeb866239818824a08945c69024915282aa65eb3cfde8c42775775f7bb068ba06b43164e51fc553d9fe09947d2753c14c5a1788009e7c46c2b3498f1a8c094ca39ee72a171f6092447955f5d7a340eadccad2a4f20d95243bea18519ba204de1691c6e41e5dff53929f51b0d12cd5fc5b430c405f2573ea2418dd3534087a2108edc5d623724e6ee2ee5638ca57030f67247e28e64ce22a8ff7f838d6152a83f867dc488e66373ce1857f06a7a31755d47b4f1f4c47f148ac79c3c3c5e3d0db644794124cfc67ddb1666af6def1e797ad9164476332fc398962512ebe1c2d7e8f9e8ae9bf3eb71a3c5850665d6c37d9cab2138d3a0fc3ace5eca28d4ff553bfcb3cf9a48fbe8691a57d983cb85246bec065b92b610996982020a8dd31f3d09e5e6666460cd38a9410d6d666823115aad5641336040436e794f31826469fe6ab55aedcdaaae68cc0f5ce980ab07e90c82814fc4cb781a3df6b71f44dd403f05ada599a56e0a0dbb89441123468e8a1c0d79e9ffd93ec8d6e0efe7c0720ed0a2096d203e9e0f8fc70edbe8a0da3bd9cf148e2835ef9be1ec695a8a086ac842362d90866090bd41816d70604fe86f4d0b7a544316a2c12d579cdc9091274f505f3c61c26408131f5e0d2ecc0be8d72a3289155ee136c5cdc1beae03f766569bc69993c77baffb81c61ccf1e11f68bbd7fcd7a57b520e857285f549957a50d7612342b92e4430d509200a19ad0cb01326308fd979ca67d7326314b46d33c63b5d7467be34e360b1115435328b122d4519efc3d07e6b51ffddb6bbfed0dba7dabd0bf793d1af427a18924a954654b65a52a5dbad544b0df9e7a305ebb521e6aa2e69ca86ddb8c3a339a935277327ad0c948a7ce18427b66764e7376dd3f08ae56298f480af56d3427a55df7afad3414aa7bd25ce9289bb398b795ea0d45f57602c6f64e1fc6c873603c877eeb35309e4e08528db37d1e6e40088c944783609bf6dded2814aa67f6bd4429c95dbaa687e74c963b35e8de46d597696ffadd488605fdfcdb8c21df0f9056cb63f3c72bbc6373de6b74f0146b65bef6bc537fec07c16ac45e9b3888b26d93d8172e5663cc767fdf3518d03ae3ea5cd1073dcca3c10e2af47ba401bf0f08743ffcb8bfa908ec77e353edf874b8c2d54cbc22290906f47f65a48c0da5734257980ebf12164b4cc2201ec64fa741ef874c413ea603f641bc4af9d9476fbf5583197ff9f2e54b10af1c0601f9e964afe3ada47f343a67fe8d95ce8e4605c90674ca84fcdd395b4d7ff71678b6c68ceacf23dc205477ff95e76c1f2b995f94b287c7c7f341a1fd27a8d28b51a5b74b489654e96d7553bb37b2ab52feacf263ca5ddce3c6dcf3afb6c68ccaafb33524144e705f0cdbb75e83fa580814a2a4fee8f7d98c6be6bf59669dab95484a438ab817b2f5fc0889939b3b78da41465da52737d84149294992ec411ba4f7424370b9719669869716297a9c148df3ae55f73abc867f7d76097c2574a7174e15fc4b5d50872ff9cb2964ca2ed5ddddf1812f35fc69d5fdb80abae8d75daa1493d1578809932f5f5adedddddd9ff6a8466d956e8f8d5a4dfbeeb92f1eca300c23e6a373c2501a44560d45436b9e0685a0daf7a980a23e5c0939aa3fb42851da2729d79e5909b51feb2fd4deb5cdb25578f36db291826a2d8b14b8fa2f14285d52529ea4a43cd12289249ee8d2c4d2d258c9501aee2bba0e349c759bb33f206ba571c23929dd710ffc81f21361cfafcad1771f6c20e857a41454f9cde840c30eabc293199c407a6bcca8b1f6f311b5e75c7d50fa7df40616e7c7829da385ac038a66a95dcd7c41ff8e42b9621f0cf10b7d364b8dcf9a4fa4b1eb86b74383d0b75016640213c8c8ec1eb19732945232b38c531e716c7759d37380809a00654f81eca380fcaa3032fae7de70f4ecb47f2b30b30068a0fb11e8df0f05546a0876529c862a55765f3c7834b83c1adce769707757fe569b06b7f31aafd9d75e40bfbf705d2b2846825acccc2c9955df2da9115d59798286da5f1bb1f7d0f323a4e74748ef7e1c04ef0a614dec8b086145212cd5673df1e5f7133beff66e2a5c5b2af69bc4e54a9dbfbf836b2583515ad92589b667ec53fdf601a558d83ef5a90f889b5fa722807d60830cce5feda7170aa9a1f6a86deaeaeeee0b3f2108e67ae033f13fb29fff237b08ec6826557e33a9f2db3ef3683a677b17ba66f6d042c37dcd6c57270e7a784a32425d2514e4b0bcdb02ca7533b804e599066bd720af409edf1f00196cc006a7084f34039bd3c2fad81a5cf931b038d10651962455dea6ba5d2a7f0536c747f658d095e734c19e2bf77eac2d53d897419ca05ca5122ace2dd719f639b22f7bc9adfcdc23f27cba867b40df091535d4910197989d6de1bdd2c44e287f051a87824ddc6ecb8f908406a8fda1571b209f90f168b0f7b5e2b1e113da3d1a0dead8b08d04ec09fd08c09e5731a0a83a27a5fc3d8f5461bfac9edec67e56ece3b9c1b59253583e494a8202a5698b1042114350ba326504fdbbbbaca40b13264aaa50a7d6feb033298defef1f90d7ac8a05195f7ecbda9517d2ee419d9d16dd8dceb4caa2a32d550175a13852523718fe905570a4a4822e5e18cc0b6630fe19ce9959b675f78f20723d7cecb7e6b01f52ca7010ab7f22848c0f50647c80a2a4944489a5244bfe5c8f80c2748416a10a0ae84f038b653f9ffd1f3e8ba5fafbecfba781c552fb53450985727f74f78695800285092682824aa8abb484117fc1822d4f9858efa44b19dd37c77f7fd0cdd05ed6c90cea1ae999f891e5ade195df8ddce539f15f04959f1da9b22fb514d82f5569d506d45daa61a8ee60e4853f7b8b880921a0404dfa81e2cf0f9bc90b1afafcecbde51ee439254041819af4641f1f0828eb57f64c2a16a26abf9f263dd8c7e79ef8fdfc3451f1c49fc0b2a2b601c58f5bcf3df60179150bfef1e30784bdefa77e59d293f1531fd0fc38bf75a075e2b2f1901050931eedb1073202d23e36f21a1ed201ea63232ed8a7da4f4581f9d8f764af7d3f4df6b517c2eaaf67fea878522f84c57d13150f50931ed473a5c04f10b3b09f8159d803a154fbc1d0335ffb7e7a780dff0496e5e3e535ec427db84da9cfc96bb2af5b5e93cd07dabe16c21ea5f1872d5419fb80fce7a7daeccb1e7b1632ec81e4671f0b1890d7ecd74b610fa528e0ef4abc865b3ee42da45ef29ceea55e1687def21a969605fd1a7aab72f7521f35c84b6dd420ff26a49b0a4d71bd76f77f47c78d0697bf30e2a8fa57c6f622e8462d647e75ec34b87fa5f2f7f2224992224746a4808179b98290b06dc9d6d86609d6060a3bbf79a8a75e5f714531c462c2a47154dc6fddfe432ec291eacd9be12f64241aff16c2c9698855a4c4941a32d2875cc4b193cd0940d3efa618bf947ecca7da3ee637a0dba8440ff6243154340300000aa314000020100c064422b150389e29d2247b14000a73864c805234944643591224318c428818420c308400600c101a1ada2600cfb45151019851a3f4ca9642e3cbc9e9e230c410bddca5922151eb6290133dc8040710390dfa9d5f2ee64468b80d7bbbf9b50c6a5f8db9db7bc1bcdb5b99a8ab1417d21efb8839dd7eef530afce31d0b0d48aba7fd31c98afc201dd4ad87cf68586ae87da0b73c0051f036bce1c9f10bb9d9ca75d0a548cfc2f04b21e143bb30cbabc158702965b9984b789d054b0fe099a6802f25f61e62abbcaea765bbdc3e6cbcc26b135de1d173f2bf57e8fc0fd30dfe28c902297d465cd2c6071cc1711a58b54da2af8dcd35eff98c8983a40eba74339db4b835795e46ed6de5bf7680a20161c46f356cd46ec6ace0e307005ee094ef35939c959e05e7502ed346a00d948eb664b84ea0fd76602d2c4df7c0c0e85432409a2e4ff07d3efac79862deae4ae6633d0f8980bedf9e1d6d4f3f185b9bec6e75b0a20ac2292149b94301ff592d1152560019f0896466a9bc403f9a3a599624420d5414ebcbb4642be8297680a4394efa251dbb909bfb856a465519e302c1d58c73c26f1cd072508acb8e4f6c0de5976b654bc022c0bf059154ec50115f6a43a7fe5d5c351cc885a1c8a568492fc6688f56678ff62d926b0cd78a9db13e0437b320f81ae1533d5d9f233600ba074ebb18800db48727f031e718641a090c8c3051827e9e649f29567b897d1d03dbb5b1bda355624c9becb19a793a07af76844b36b903f3eed7d13bc65189837eb28f5a6ff06a5ae00af533206d9a0b10e636db6ea0df0585da3706480490c8821838ff5b65cac4af862f75ab69fb17df951268f7ace8eca935978cab224883c18b96d60c48f77c51cbd60b0d162becee62040fe7e8f281813cd569aee986fa4a2abdcd66322ea8891ed04b10f5ace5e7bdad198ddafd2634dac9fcbd12c25e01de377a1280ad60b6d78717668c492518025b6363f5666e28537bc547ce8f0b61814684452c48ad1b77e60a60f81578d9710730185fcd868486b2977a5b38bab058db322207ff6c23a064ce0472356f9fb72e5d0769ce2d49b3690636f2adb9e9937e03fbeef0207049a13003c28153d0762e5eb25e380af784f8966daf63e9870d52d2787c02fac858b4c47d6105379ae144801d963e187d1cddfca6730e8bdc4a816e28752552286f7d182c57429cfdc376ef3d673a9e8e43080a275dba9512541f6c2c8bdb9bc08e030479e366609660917d9dc516f60c1694faba7be3b49eed38f89322139d6bbf14204b46ed3c8c0477000be3d3e3a8529c1983019e5b87b7362baee849a4e7bc9e5798c2979f3909288113db26d78ad91258c30585b7fd390efa1a9ae0ca5af938e648e1a06021a961ea51a796e5c29a0eda16a72c8b298aad7af5badfb3dba5fd94f5da8ee00ea751e8675a6180e552e03d9f01cd087876a7bcaa3266fc40f652ff305dc5cb87b22b7f6f32c7d0a69ed5d56baf6924935c72733ec42e7a5342072a5e0c7977645060151b4ebf484ef608807c17baae0cce493f20bd65f20f3d00cd5857f5584a05863760897937d0b04f8ea5d85cf263fe26341e0d0896fcd69d58dbb6ff00514d4cb0ad7cdeaa005bde134efe0862e7ae15dfab73e63d81905206e6363ba1a3475e15bc721bc90e92568481d88b641266488435d6d3dde216eed4fdf199359e04c5ae0dbdf5c3a50de72f2f452cba382b003b584abaab53659757b51f6ccebe345a9ee57ac148f806c0f7a4b386e32a765651a45d14b7405708a8d8c7114db2b1e13cca7e9ed21d465256c6c1a6d0143bbdb3600287fac95b9d13b4cc504ab3e4e5d97d5fd028ae8a92557592f2012afe7990db1b9ee1bca96ede4d3c4bbdec8c979ce08569112729833c93d108d91b7e0212ff9f2e484f03d297edf234fbb4f1833a5628b9a1add0ce938bf5b50b1399b8341c17ef00520c6a10bcdb878826bac303a286e00be168961dcc83115f5d1eff3521c85569ea590cb4da5eff5c2ccff80546487f15e8d6dedf2a81c60aa8e103466dc75bf52c691f731dbbeb9044ffe12da857b36d23007136e7b24f1d3db28db579c0470ca200692ac2a3bbb79abf16aa53d97c3d4b2dcff097bf68563d9dd21be546e641da3c60689db8417fa5fc6aead8d4484fea1c55ab3a2b24b851752fcd4e74ce7473d036ef03a3df503c2947de0983e82648e7c9685b37f8d246ec786157689df0362f91b6887737efb7f4acea4707cae198add6e9e176aae46c956fd416bdf44e8d4ef0c1415bd8e305e8c3b52603186061dac74f078d86f0cefad81b6524dc9e89cde71bcf73ad0db24c805349958a17922452adefce8cdddaa0eb1f553b006e6d8825a41760241c8f45e6805cf5aebb45000187fc99f2c91879aedafd7a8eaeab9db19ae57a65fb9ace508b8253a279d96fdd3dfdcc9ddfa85e0a7fb57112417d9e741625f337ce2a6569362e8f87e159952ccf799783c830d5199443e66c30e0fc95acb1b431a30ae451a56c94e98039899d9f2ea24598cc7b3f6d363a5b6248425be3c213165574a24b8c867a3dc4b43643410a826568a0e1891131b22f42006136368ad666e5d429a28e46473f157e98374559d29223b4a5314c4072e1a8b0080c1c1073a484f09504e8f30264ab5c5c1edc05d8e2f9fbcd4b86be5b6774dbf3bf3c10c51705cde0ccfe45f19c056288801e5768da31fb4d112fb4a0a827164d33526bad5c4a38d966731f36d792f3636c50267a092c5f52150b665896f1941f2d2a6aea15033805ea2465fafdfa94d2450d4acfb45e6c91bc48c2807b50da164cca7d848e8211656ab39d15febff3678c214ae11201b4beb9ae8b5f075b4e17a0b08f1ecef2dc53624743e20eaf33da156f216532f64a4dfda7e2f98be5241c96426e9a4dfc500dce2f202e276d59ed82002528555407d9a8b216d7db0e4c79621cc76579e6cc7ac2e8ddbdf2c1aaf8f263f0002b0410a50cdda8eaee3fe413c1d5ff6c8b55666e6de9947c21b952d650f112abb16af715c9b3525a59415724ac4fd808fdef64de05d91f43a10a36da60d2e847e3694f82686c174a00c84de0207f9ba02d20b1e73dd60fd645372245041c0be35f37a46cc77b83240f882950a6912e1be9de86b355c99d87d880cbeea3426c06ec937927c65f4edf5ed52dc224a3d280714ecb67490569c92db668bb78c7ce42e41e16f28089611362e5ae459d1f105d11947dee239fcc91350de0e42946e0f202308a80445749decebaa4a7c13f634acf318b3b0a74327ef2f7890a40162969c29ed09152804966498ada0e7cc6c2d3c6345647c6f8ec41093c7e30b0022ae3111e80efeae238fd5eb426c3c2cf35f81fcf370fb93155754fe8763d7d32880ce330beefeacc904feb219491e1845e96a950e2523a9e6f5229aa90132d071b8b1e9cba1e29d56fdaca15c0b940731a31176ffcf6f9c3ace50877b5f3e2eead764a2b447442af776799df1092c83f7bf9b561783321e18ff9c82fa043b7067a06a1a7cbeb3a534bf9424a8cd89ebfd375ddab26c0a932800dd0000d53d4ccafc649d8ebcce4a54fad78fbed1f4616ca5b16100def77b8107b14ab05fcfac65f23d81abd3e9a2ce180e3c10bf60352d6b1b460857c52c3210baee9715b94b39ecd3c97e95f5e3dd2d7babb1e6c4eec7fed7c6fa55d5c8b5d615314ef9e36c6c34427e5a7e8fafbc99343030489ba20af84c7c3fae2cff247a234afa1f9592ded8203578b1f80eff5c3a07be6d3c0a94131fb4c10571c8379d0d422eb743a993848abd36accd3ed0629e262279b3950f20061cf3d99d8afb7c0918cf1a045cbb793b1608205c52ab32cb6a89361319f3bbd6b1fb330595552ae19954b7fca5433bc9e413ef398e41ac00e08c220f8b58eea709e4b90f8829b4287a2c770dedff1278a6ec97d1900e3c3e84e35db91841e11c3693f44e867f51c2188968a1a79d24aa8e41ea4ec0b9e6c9080922e1b75d8d3f00dd21cb468cc23204180659cfe07812fa7b171800367e00fae60590120ffba117dbaff94556f5f7dbdf5fd126e3cb1fef00c8893c4f60138f6863fc4278f089e247add297f86dcf4aa5fb0156196639901e183074eda75efe1c40b5a87f289f2e6a21c6213a8083369a1f835b74d6c8741eff024e7f1852c0ed554881327236c7b04e18d495a8e99cb22289207518d8d36e1b0f98522f4b376f0b30cdb1bb326f77ebd949bf354d052a1e52dabb3d0846d2f12ec85e44b32afdc50b8ef43e4661ee4f8189009517b98033f8fba3e58155fa77605fef0a0a04d5ae9224236d0c5228c695a48a67eb09d0c5a998bd281da0115c50d438f19331b42e51210506418e64064d11d10700c5bf87aed1e270e2b65052e98ddb6caa101c20b1a9f57c880435a6cf0a3ffea5c83694f2bdf6caf32e183a2b344ed92d9a54fbbc2d605a78edbc3bfa02b57da010fd9725b69f3863d0a28fa9f17ae66acfe5240d072e48bef1426f375c6d6875d0ce3c044eec0daf61dcab54aa04cd44f97fba40d4d88f30ccd08f26d24db57050c640687a9a199ccb1c2fe058325a4b9b43b3860c6abb49a6f85d4f6751f25fde8eef2241c31844c4bc36a2fa1f3e5867929f37684c1d14abf247ff53b1edc8507c0ff76464b68db3c12c3b5889fcfec2a47d0a35185a9615f8c8be63c95a7aff06f8f3318e5a2141746d8d41363251c386f09f4daa7a391f2e4c1a82583f4cbc73e9d5de5271786e73b2e277bb63d4a27633c902c7810eca6c2db134463b2ef38b21749c708e2d949157d97a645b792db50f23e0969064bba67dcb2ceb7d5aae4dbd60aa3097d979d361341146a7d343b42e51dbbebaa4eadbc21ce0f2815097cb0508c9e3ed6d8fb692d466bfc1edf1b2758e11f00acb397041d9f910aa401c476738145d8e2da15821c87acc3d95589fd293beca5bb9fcd1eea4cbec0db7de335e5fb5249b89a1688d9c40dd884bda315a1e55bfcc5bfb9370a17476765596d25dda2f889b8bc66868d209fc8638e415f0cada750ea232c6a793aa64e38c0d49a7412fe9948f31f867a0e98c026ba719eecd328b73c1f954f0b63085971cc1823be1ef9148c41c38777fa21b1ed374064aac6778a8a9e3dab4755928af5025237ff98b89661366bd702e2f70be7d67a4608828d86154271f9349b02751e0030a4711abdb4799d80bc9b34527baa6a58de8a1bff5e7fcbb71c505560c6e70bcf5c58642045794b7e85fccf924caf469f2e83dbd4dce90588ead3606c12afd46c1b6587ac8aecef66136370eaac6fbf9142d344448c9fa7f29a44320d8f3655847c06feef6d65a2a26bdc25cb2081eb7f98bb3f9feab89c6bee2c457c315e5d4b0c80c5e94b94ce295a31990d6a18b9b4fa550c82f1490a437ce3c90f3ddaca51e892411f7a4eb3648cd332b2f81a68b7a60a87879997d748887d5dd3a6a6578a98e8bc3eeb78b9e40544a4dfbbe2903ec7bc879f51b00e33e5b59defa5adc3aa39e668aadfc08b42daa5209dab4b9dba5d98c6a3f12e00a3c5b455d1506e110050a15f135e06790fb49cc5259a9eb616e600c4d27a279d6ec272f4b73b5a4f8165c2350523a1e0aa69ffff57e54147216c7ee780a0072513c430a29a1ec5ca7cf4ebb8e4595162380a0f77861316828ca47374cd61e3393b36e40f7c9e5111ac710ccf0bd6b0815061820adee301768752f0a759cb219343416fabc042357fd143dda81774a865dba41904349eda712121c5ded7ef5fcd131952c3d016775cb5b61593625aba23d041568d31cc7d4132f8fd644770a4a5fc9deb28d073f225a8ee7534701ba3d240c7a6605cbfd8155b27fc6799a4e1239e29cf110db55ce5e20b38c33b3adb49987dac8a5b877f7d067cbf721d7073879347270a528733971e87ef318d3386de7a77a87a212648f2fd6778de2988508bd9f4f2d99ef9a3f461229aa1d3aeb46581ec0f2ffe2ceb08878dd9c599a5b25fd13a57e698c332135f3707b5b9d64a0da08eae159bc9d605b683f6802bcfef7fab771cad584cae63fdbf9521db0e066432d8e7f17685e5f6fe92abe10fdde2151d0d2321c2a8148d10ff4138f63fb9b1c862c5f2afa6221472d14db06f9770028c9007c426e18658c24f6f4cbd06fbd1a5182c920c6cc4a2a2631fa4ddabd21e786ccb3215e87f24dc2c3159c693afca6416cb3168fb200029b728a23549635c8a8943b69ea89a113f57a448ac548c1dfbc747c8b75d888451a660a47e1c9278fbc5dbab6e843918d788c66ec634af895925d9522e9a060463197d5d80613841827bcc22d714c8643ddd12991da40d766314511d420743b59187815e1b06e63caacb6f673a944b781ae9d95cce0a684611d70e86428c421fa6134e00efab882b4e6e468b2018f6d59be986a198225ad01d20ae21a42d6006f5cd7ae06f67edd443201c274e99e0099bbc47c022ba01c6d8969872a5183c58397558e008ada6ee4ea3e4b80d9285eb390111fbd2a773d5e487d08a0748b5848240edae13de2db1dd3f0a7d01d8a962217a92003db2046e706ad27962f9f8fbc141aef1caaad7de40778b8b757bca3c4d340befdd50aed43b7ac2e1613bb337982884b47f75ba9ea8fdcb1968f939b2ab90f8ddedbbe17d3f345dca2921695425eba9d61b48702f17395e32d24263299ea9a35de8c49fd6796ce5342f1a2d26300d90dc789e3679f4109e7d0fa9963be5cb048786ff5559c5596e040290f1eb6ad1f4df3f93a01dbcf4855514919c2cfb174cc4f03a1aa3dc2a1dede485b16ac3a9749b9420761dd2d70ceaf04f92daff4be6f03834cc0eff37b38177a171804000d5573e2b4ce5998fb296070879f9f4571e61c2ed78598559c5b217c3c7866ce52e5998655c679f43ecfbcd055ac48a81af2ba7d151d295eeb933caa58c9f508c9f86ed675ba20b5cc9e7352208f939a103ca2f164e658dd01fcca7524e5e963ec9ae2517be1f69ba3ccbe8df1f65e565197ff9552f1428b0ee1445f01177f28af03ca6f3434c120f04154990a0f91f1090e9a323f0bae498b91dca3f14eeca78f210d96303343ef4235037418a9f7fdd72cccfe850a6e4c47ca23e4eeb9b6cee1f83647781cce9277b4f41dc6b7793e2b27c4c8c186688da8d4ee1f81f44245a10577440406ed496edfb96ca23c636f75489334a81ea58c438270d60dc8b61b5fa2f0dc756fe9593e623bfcb8ccd5516c906f9672c51cd31cc452606ac662ac1fe2d65c18798f40895d1016b98bad17745099d8645c16016153c11673d1cc2d05d083b08061e66a5a25ca6c07bf782200aa82f36d6e282f4d6bb036938e819df0247060f27a08e80b0fb048c7aea39ef25874af96b7cca606731d2315ef4ffae78280016124b092bf0e00c6d94c98ffc9a9ee11b02bfa403c2a9896375a501464eefe342d519541cb33d807b0ac8036d5f6ccf85f09cd8f79ade7570b7bce9641edd35654c38c3cec3b67022c40152d6d00e8d340025f95bd64b0132ab2925c7929cc692b5fec458a2fdc3817b24876958cfcac8cd4d3afcf79de99652ae9719763e3c4ca6947711581a25062f8bab47dfc0b20c171db507f67df9a22f28ac5ef24552535891fe7fbea4ef15242b2c4ef31df3494dcd720c088bd2cca466198a44f03c2f4dcd9a782b3434cb044e5aaa0f7512ef3dd32d9537e098b55dd33c18de4f24f73f7af927bba3c7915e1fc93a38a5b9f9e993c1c9a17b257b86bf3077a547bdc40fc9474384b0b50d912523add5ecb54ab1fc1e48b7154a381f5203b700616d3a34796103d0c49eaa2864ec9e4b7203bb0a3a100bba733d18e1155619b09619ce756db14e6384d23c2b4764a401285b2cf1bfb32888a1077d0de9f047a9cad26f6fdda22c65b947c521f247367586ba46ce3a5c1ca6091c677e56a5e0b9af919179a6cf9366722dc779ad4458d5adf40f6f0671a3f78d54a2631a6abaa489f9603a82f4b31bf70738f1e64bace59ceae66640f46b6e3f25dc051177594cddb84063883d6c6e3ab2aa3e2f5bddb894a16b25c95970a630e6f2fe42b5c1f11901273876310693ca6e19ed77602a3842f1b2069c01bcb13b16ed80d0342bf3de0b05662dcd10da1f5092a38c5894639a0db6a0e62058b00c53abd56811f7cecf3927fbaac1804853a478ee0d1b52121110e52ab8f2ba79771c864de8a3526f5430ba4ee8decdb81d60bdea0181b56d91a6ffbbb4bf0442f1926b3e8e1e15e71005e8f1a20e27e35ea76cc162497604dfd9d0ed3b542ba8ead33dcd7b830e3ce1b12fd1f0353f1f45eea6d62a423502b9b364ffa6b92dc05bfd3ab1e0deaa79a2e42ecffc9d57647f1f3e01ff4a3a7499f8d863ec7a0593a0ea4d94efdfc5fbc38c996df201f174f0b32ff6e8af22446eb9c2002d4a7b30ac8c6c5d77052bba7ca078af639a39f8a470870e57bacdf51018363cff4233633e5490930d98ae9471750e915d0a456e7f60a3f6c646933333c5eec11aa345457c8517bbd13acadd4bc2773c03ec7652e80425c68e8ab97a357ee35312ed0474bdb5a1539cb22d8e10a87c70eefd8b9f26994e8b3a14fadf39450459e4ad2136e58c5bdd44a861db1d8892c10e1523774f8321dca50264f6fb5fc4578418546975e68332dc42d884e5965be52bc45272020dde40fa65a0f67add422dd70d25d3ed54a14c5618b7ad30beaeaf6d54b9cc6631ba176a3e7ae4c6d4f70a8e92f2b40493da63b7c37a98861d90f29a462a30f5e2c4a7ab9699b0d104b1aa8279e87831e51ee3bfafc1d3358cb9ea7e3694269954ec3e8705d42703d664722059c47c1bf837894ed15e247e23dd745e3d8397cb728204482bdb5c614de4692adef7110af2958dde7144e0d5fa696fed12354895d931765794e2e4c16eb89b5b2265444bd152914f62cea7c9330dcb37f9a0e32aa0a9ff651562924fee39bc24eef1e5189f7bf3efb3fa6ff27c0009890f2db33c298a22c0cf419e576a058afefc0a8fb3f9078cf344c58f8b063a683f833d2d2fbb057f0982b88a87d2b3155cc32223d1893dda8a207f46777e33bae1f58b6973325fb613fe57e8001bff08f219cdb20a9314de5bbea6225b1e781aaf796cef47c3e826c414ca71f0f85da08b7fdce7fcd7613823103f6a17f4a38a87e144163fd8aa5bfb6230ee684210bd70f3867ee4154505b74f00c62aba397d81e42bb492dbbe697de556dd1d33df7de63ad51e02ef888121854a850f93d9a222629591e19ffe309bdb1b9b9fd3eb25dc1df0e8491c19cd6ff40f2b2da332896c368eaaf581c83e8bffbe8416c86c6a6c34de30966865e927895d4b46b931f826cc11b94828737435dafa4f9558dd97b6fb0dbdeb7b71397c106de0b04edc022cf3dafde623e9f5481a6b7502697268fd49fbaa4fdce873991b553d36616614ad0a0ed37805522e4af6a952c397cb54294f4480fd3a06655c15d88f1f8c2612bc696f874a128f4ff25468d726ece0a80468e08a2bb16f9ec681365690b7461db8ffb4445adddfe973b19bad28fef28d812069e3c9a07c36698ac665c9a4f77c57fb7670f9279a8b6fb030739b53fadd4622d9323fe0254f06bb74c23982b0e2525db8b8d0a370cbb062570a2aa64c15210ba0bf5a19052dcb86ec6cd2936e927a013a45fb6914a3318c59041ef1d5ac6aa08b39c0d5bfb5cbc28ac0e7da844b0ccf03db957900f1952c60ccb413f53d801c15bec5d7678fc9f496bbf36cd20cd8068dba111307d93428b1ce410b3e2cdfdf5493d43a0b3a6881b0ee411a0b16e3a4e65350cdbbec972903ef25d18c8470b3b594044766593d887b211aebff1cfe9e2f022a56ec4fd6573a04938c9cc554569f3e8e8d391a8defc44bb3098a63dcc9595f43d73da9e0f41554fc9d7ddc23f1ca6bdfc60cd411ba7d8bc2c4ad05beb66e6eac530df71d59287b7ab247e52d74d27a61ce98a4aed40ade9eaafdc282a8e967e24043f299b5ef2705b5ca5c1b34707c75976d2b0a18b785b3388f8b6ca340a7d3d8a5f7ca1e40e7a370d48066eb8a0a36ec4fd854aabe6bdc8f7431bf22cf31fdc79192a4003ef0bf40c2c3f4bea46609293d7b0e3eb8065a5d3cef62c4dcf053f12c1a857e65bd9458605bbaf99a93b9d987accd003841a32cb60351735979967cfade3c2a8a69aea7e9a0788b2e779272ce4e359d2e5359086f79aa56417b722f375b3477af4d2fa2df811a15ca84e2a2bb0445a452e70df980a8746d658a442415ade9985ee34159d30fbe95817cd58b9b27c87a077b011e5e8b68602533556d20e4d9d057a46a697d79882911c99949fb1d659f521dc3263bcf4453dab3ff94c7d8b12bd81d17e22a144ddfa6b250d6efd11fc906b511fb5541083bff7c71929575ecd4d830a8eebf03a136ece40351c6b89c2ad14dd6413031c7ec8c5407604f4c3821b032b280914ee9be88a57ca953ba77e3c83884e912425ad2d8a15a73b8307a72e696634bfc6b74c8da6498bc9153e8aa15383694d045f325b0765c1ab48ee88558366e18d46010a3bfd5d7595f9944e2e0f39b2e02a2190069f3396bc04d66ea8ee1aa719047a175048150cc4ed6b8745d147f1b6dd266e896c5ea1e93b357087837864f3e88fdc98d4ece64b14a19736483e664f945cb0e38eaf0cb50af122620833064ff7fb307b6f06a494eb6056b4ab3ab84dd8698672deb25c390b0171d19ee6623e4f47b0a9e64b8e36fb3b1abc2210fdba6f3c94138f66720a85be5e025eeacfcb80d75eaa9918bbebd914f998b9c3ef581b465445cec8541b96ec4d9299156730a0bca221fb38421ad31a90bdee4b4a215fe8171f531900830a7209a245b3a6b8d02854410b06289263718fc6d08a492ee5b6a506985ed8138b7e00000c6f4b7ded35e6fcea4f614b8916161a588355a063061c242c0a5cac779cf927a3f88ded44efd55f142eb8be316649e3b7fdb029a23d638abb0f68358877423c6caed110e57a76a051c287f51b87000acad0344270991de63991ab3ade4738ecdf6bbf48eac914086397b7032f61f483d83f535ec19a4e4a5eb3bf6dcd1efc4ac50de291e9a8d2d3f99f6b812b855ef641ad9ac86a9ac6336929de869c9ca0b6e2bbccd1204dbcdbc8dfa470f4742d783a92513f4a0cfd22731ef3b0afd14fbf05b5329be949ee4e3fd5d76c59b5804127cc1ef2c7ddeba106b33858f2f62305dc5b75951c395f3bf0f1cd4d54251db70a39fc6b631065f70d83454f6b5517ea49055b491cb0ca561b16d0ad241057f02007426e8148d9d4669cc47f38e1948816e8457152d3b59a74b315772f5a11ae842ecd978b150c20f336f6226bf0367513ac98201ba2465b9e4745740f3e46a920a75cc052b69d0d41a42c46695c76210d748f57e889dd77c0706ccb7d629a946a12c7c21d560b273618333411114ac4e933bae43c063e86672463710b8b57b155f96b2a8fe0b7614e0f6cc8bc9d76a268328a94b5afdfd134d42c35e6e35c506cf7fecd518e311063c929cee93f701844925b80503fd58513058e7308ba981b98fe5e23ded26918d81137923b5dda76430e5eb6e7cbf53161a160770f28601873fc0c73dd7b843142ca56ea388d8673a8ce160f4908c60ef0b49d2489f225c84f3c208c0a8e719e201801c19205156fbcf6926a8d60cf69d1bd28f65f80893a0535b4023c25d713ce9be17e4c839505320c18c2835237fdb6cc5e124c766e659a17637c6dad1bac49ad6919c8b7a1318c687513f876386da7e77a99c001be4499642d9922ebd3a302cbaee4cf7f5a9bd322e5c5ac58ab0fd250d4ce4987bb213ab5c3e75f07fe0d540b6ebaa8b0f92bc6d84770def2ce2c1c905e4e545118fd15383b8c7ace311869db2e6d3ea488509ac157328547c8cd2e50c88fccc0340c00948899be0306ee576999e31c0607ce173cea4853fb765f444a0d753e47f63d7ea6be648c1bfcc60308abe7d862c68a1a90a87bec1b8b15ebe16128d346b6dfb03a1a7ba90477a3d81a3bf434a4790a5137e1c7ed64304d75e026467be2423121e955a87dfe82fedce59aa263baa83a1da5f83c2dcb51292e5fad1cf24a93472c58160964f139e3242db3f75ffe4eda4825c1bde6ebd7aa2b503bbaad18072ef1aee2932cb7c7d854b2f4499ecac1a921e40c8ffb8e6fff2450d2e3430eb9018f8c69f2dc707513cf522d5734d2acb91f150a9ea542e5daed3449809f8e76723154e3d0c76ee4a656d07df3bb452bfd872a270db53e5439706fb6ff2dd88c435bb113ca57f2ffe70d9fdcfdc41340ad89803bbaec959dfe9a32d8138f176793b6040cd4c54aea0a34525f18efc6523faa6b3ec7e4b39eb491441f6ca53e69613d3418002d92f9ece907b50f1a8e3fe1c77368d05887293770bc692b02640d9e6f36d47c00217f4d199fadf75066f2fac4bd3a836de016d9b669cbb31c41d05a5c8a00b9d69dd8d1c94ac1092b891b7aaaf62c2e7d22766841a3bebdf390fa669791f24559dedff72c32fcc9c97bb9b8b13445a2d4f76c6971daa55c2284c41851629b88fb113f138a6a5ff521b34a845eed5b2904e8f5ee2a2fffa07007ad637fa7519ca5a315b536c909aced28a720d6f2ad62ea4c7b48399d27a32a20bce288e8ea3228ce97967043e7e1e06295159ab85d7d805e01e86f312834bbdffa02a5ff2601a7b2459ea9f030eff49df6d3962580917863f2c89e32c66d68b940c424f894113aeebb6d3e857c71540ae5c9a0dd96c491c43295946ad467902f196103d54a33523529cbabb8b82c04a875546749a7a975320d0c201ee704dcb30e91890573fe6348a0d0f37f79badebef3cd1306fc4dbb7db1744aa045eceb66fa66bb2e171efacb4e1f0758fc77b6aadc819b1a7d94d389600384b3eb4946870b5f9680c28e6734d8111ab4dcab7e56511d375aec6001fa7dcea48c3236dbbb80836ab9b6b5b75f4ec4eebe3d2e8d64c5ead9e6c0e6d98c89f911de90ff118bb6e2039f0dbb55bc820fbf9b436071ae80ae64edf5d8cb85fdb7b7a0e21f02d24df941da8c5010b5a27db34681e704391d3b4d771d7ccb471a303a258955cf4cca0146459c903d21854a91373d06101419ee3b52976496710448db0ed5f4b557f670ed2cf5a763b790e18ee88a8e2aa86e03a07d564a394ca4d8c98bcb2f2c48b9903d9344c4432a9dca485a15277155bd38a0aa3dd34f3b5ec24580b8cea5c45b2469ab8ba6cfefe3e7e639082aec95ca0ecd616ab9774b83a099c0632b4fdc2a23210486dd1b6889c4825e3285e0672f88f93a3481ed4e0545b85fc4ca31a2c584a2b4b0fc27dafdc337ef604c5dccebf69acfd0763c6d1b26c665c0616dee4dee5d2d7b80cd332cf84586382824072d7c3a9a71cf02f3406b7f11cc2004822701108c4636aebf80d2f07e55b38331b4e496fc05eb00ae9370d33720da213a1dde48dc65c94eab29da2268a1eee83c5d6801fbf24152fec6033c4fc55bd14b56223d5c48da0e78559c6f6bb31bd2fed400bfd6da3311e96839a62a3527a9e1f616124751451bf8e77561693f0da1f0bd30dc005aadeec77d4070778f469ea4ba277d38c871a2b8312f4100bdc2dd7831bff46ad639741fb1eeb69bd2130c3a20afc0ea4867f04c80d112909834799749b25333a238a434fe1b9a947110d260bcbd01e80b69dfd408101180aa984e0a5f99f2f6e240e3e8584ab53766170d13100b7c95960d0cd37ba3c85cf48055414c57ff8f94df64caa0d624d36eac6d059f11ef76cff51755531ae9dc235fe0b3a55e3a852e73481159dfb1471ef6ea06f97cdbf859045ff4dd7a89c2014b4d4925b96179739f0724fcb445bc3296afdb13024f9eb54be0712671d94acbddd88a4d27850b90c1ac76a4c255a2aa611ca2d16c58a383f73f86c62b61ef2be35864f9bcaf1a08b9b89a7605b93e6edd011875e7ca6587ef6cd2305199a5c4030c6341e559ff7d4cc87a87085e69dc70c230ca46327e718189dcdee089f7f72581b61e314c3b376c3072d8e75de464420a86f7d64d8245916e3d71fa7b1021f1bb1815baf4f4ceb1eb87a8dc136f17a8130228f559f4637a94295e9d951f1e3ac48c1a9cdd9c26bd407b6ca7efb78206285b7b76eaf6518d2decee7d6895b3d28e6e2873a896b0bdb5b18aa822e68aa9d91f1f5a70c489d5e3b7dd6815adb6482a3e8861cfc9610c1569f967607e0b4685861e6cab162d4ccc78072c72fc86e6325a66168442461eb5dabe01979f07bfaf80db00b0109274506f0476588f7c150d3c6a377c2aff2e08a7b316efc89ddde2b8bbdf26ed0876f478fe224aaf9ff74e094834deb59bcd3bea1e64c7bffd439d85ba11a930c5d53843701821a772254eca30a85cd43052341e72d19c7ce1a6fb91ca23fd3001ab526c1b9c0ed518857d18bbc1e2caa0b0b3a35e6a7853ef283f2ae9a2572d1ceca57b3169313aab34cd6a8d920c2625458cac4d57df04e48bfc16f8b974e178909535e26b965ae2ca251c881a995c6fb6206cee30a49dfb6e91ef822780527430c5ade57c0572e37f43d6a86d6a62211061c1b27cfd824320963cea0bfdc0e6123ef780afe27f4c0b5a3472cae09a9d1bfd5855ca6c6b0e214f2a2dd0104b027b518356adfb12dbfda7772f26ce6f9c98f0e9d8ef19fa4ff34677a3d233c41c0fa424bfa7e34261c39156d7b7328100db7fd1f3242db62172e3371b2735ca7d4e67d2f3b98e04a0fd74b9fc3f555e3ccbd3d024bb482758fd9acf44c8d88bf73105031e162bdb9ef7c2f9c08ae7f771c32bd945a0550e17c00f8c66835e9d7970f9850eafd31107efc955143254bcc9a67ba36013176080185504228c705d0a4e6c7c8f2c1792c79163206efa5deab2ab97b2986c4d8248e0b1368ef585981b11045edb837d014d0110c64d9eb5d79cde7192ad70d86c1460063f80abbd69c944ec1ac1758e2bd239bc49429300054f4bb0368d0cb30213b36d14c4386892fe4d10f19d30e075074f12a507714a853bbadf4f633ee90dfe85fd7939185afc14ce7621cc87e178ac6b9622e61fc09d29554bb5186f23447d8a35e9d36f64b2a0390381677a4dabc577b6dfcd942772eec29d51e1ff351e4cb792a234694da0a8fa4804d0892f4ef24cfadcb93c767484cb52b912a7d4eb8417e19406fa99414cdb410836a53841fe26ef0fc68933af2a73170c7fed35108f01157df71dd1156ccaf6f7540613e8af39196dda838705d60f1ed1a1bd8ad7178cdfe6e9ddbafc2e61650aaec18fcec6193ef860ba31bc6af99761f5dcde3972e45ac3efb17952a1d7be1792b06dc11928f60b54d856538f41ddb6d57e24f53c19983b8518c97e321720cbacda5702f6cb194e83d0a5a3d2342d262767bb78535b96134196546d064c927bb91b50e7e708f01948ba39158744333d441b68cbcb14416788596ce5a2307638d3b4ee925298dfac9d96475727a8e26a7b950ca53242e88bef7dc34be4461c703953cc3c2fece071e8d8bd5a74ede4b271735394725c28f45a3671dfc136fbbd19b37e834663a272b8421fbfc15fbcef3d18a2cc8bb4e9d9e8cad0f9cc75450148377a698bb949a43fa5067270e6c3f55a5f79da08b0c4a9d0fe4445f82b37497837ead2dece261244d4535e765c6f9ba336fdecbd48c75966d0ebd207289dd62fbd4f0e531fa24bfc4791d8ed5174367a2e16a43840c6aad41e3b93e09691f9923283d1115a2b451efe8a527e8f89b1401dde5161eb0a87f676894ba5145f7bc8b2a2a08e69b940d3b9ddafa9659ff272874f1562a2619bd04f93fde11c9648779c592dea291be88ae8da90c6cba989d911d0ab05250118e56e56395f634c2e4d45bd78d936dbd70100252e56966554caa6e51021d605bd76edffd8f61fcd03606f2318f9a408d03a2605117e2591a326f317dff9872b30104391a0a4ad3f54cfa769f4974168cba0f82ba17041a8135633708a68b665e357e0ad0cd7b1caf45e84ab0f1736336d08a7808109210fc5ad9814c1796bd82db351a3e704b5f123fe9f8f9d30d5685d16f01ea9ad21ca79c214d0bccc97cd9556e2e8d94295bee1131524afcd01346b482f17e3be3be5a9d02c7d4dd9b9fbfd6edd86f65da6905c3f45425b8582ae89049297d46363b7f0a544f80f14905fb306bc7fee6ac69ae40e1b24d9811f9661cec821e7df5ef5110f88d312ac5339a50a1c4e35251d94e0befb8967fa2b906744b832845ece13c1f8e630e1c281789b60ef09e9c5fcdef4e543f042d3c058b5a066772b240b5d1506d45adb3e8293d7b247ef76b1372fcb1ae35a16e24884204d62882f78d7bff054301027a5c6bf0cede50987f0b8e7b1fd76fcf72a8055e76bc52bc814310eb2c9f999e1aedb3766178cb05e81ca38dec64576cc80228e00f1422688ae1e7f56c4f6718692f1d7309f99cb2215b997012bed94b2f08d6a9f4b47e77452d50f50557b6e63d4f524439d35f7a054332857595f77b1ef290b42d4c31c1d2d039c69592b8c671bf57275e5bd72fdb56129954bd5fffedbd64402d9cf0e23060656f70b0d0a31e8ffaed2aa6c2a1482662c8f46b11d682a1835811d0095f85b0f3d30bac7b6784c122056d0058ef4406852339d8141a44eb7a01e4b50240b0b16812758d7e2ebf621afd8508bf8f427f4deb467574e1e9faea1cfb38ad45f19932eea0d97cfe312ba4295a37efdd62152ac47677754d0fd71ca9f355274246f366a1e88cbca7970d74e65d9b7ab5c1aa2ee4e950486a4f71ef0f992546bd326236bd1ef11a692f364788936bbd5a039b8e11df8b082a80737d102d88a92b79a8a151c155a0ca04d2baecdc0102699bb10e6b6d978714ad7d07e092e3a5768598071c76e04e187e285c6fc08caf8563864e91b4cfc884e532966049efb50323ebc8984d8a1170f37ff312af579f545ead37cc8eef25a52796100ada781da94c7b4d5a0a8bfdc1d6f7d0abebf4af09c70a1f9e22c4953a05fcfde3b16e7394e2a0ceca462ced744272ebf47d16d6063e87f730a673312fe1f10412eeb2a74f33410c6302bb4c1b86c2ff362fec2ba113538167cef340bf120a35ca65f74bb98ccc92f6e60efaaadd78fd5ac41432769266e7d7591269e4da2aec889a4a51cfb69ed5f66dab4d38ce39d8b380f7d09d17aaf2d36f0b264225e733581848528bd415474cf066a03503ce3e72bdacd10bb94a5ca304b455c1f0317c3fdb9ef75d7857cc4797b1b37cb05a32d05b4a75bf579ff595025390933fba4df0c9c4e6f3e9aaa423a7233167d7d7037cdab0f6a872c4cca02e35669bb252485dc0fc066d5d13b038ea9a4eab91cc5cd1db21615d00ff7f742a6b8a0df1712a3a74eeb38da4dea4a9104f95f3fe8291e355ac9f4355d4426fa57ac340ec7860043d027ce62a152d54d94e5ed143590344f664f1c0dbdc7b9ad532c277d76fabd94ff85b7f5feecfc3ff9fbcc84c294b073903ed3d098c2cdff1b0f2ee17f474af20bbca5084ddfe2301f0b7152df30e6ed3d7f2bd1c49bb320050c40f7c4edf9666fa385b458bf0a6a55b34d45f03fef9f72c2dfba39890428084c51a675c2507dbd760ac82d69119bda9fb7a35d321ee3bf12c589ec01ed324e05f8fb22f0271660972e363313a355f469b380e495de7dd510fc099ad40ebb169dc1a2112be88a0693cf86dbf47e9b81d2444dd845ebcd236e35e9833c08ea364c4c96456e44108264cba85fed342afc882eec2a273e9b83c65c97a8bba1f51ee2f4aef6f07a96c79933ca373ad08592b8f3f8fae7dc427b363bd8974e287cfc41c8aa62f666bb993d92da350fc1a0284524364fec375f98b9a757959ec0934a34bcedac2181d2ce390f79635b6fe0d3a2f65e471f65e1a99a702a5572b3ed3bb71cccea078c5aa020ca489d505e833c853dd155989f9dbd5619f621c579412d7641ebee51c2c548886a8a9ad18a9b4d4eb102e780effe4f8c28ee094a08a60fe4b35be16eb9c17dddfa30c24e98124429bdc06a4ed71919ffecdec856cba0ab2130739d9ae3267be008f4189a38a1d5370496e39c27d8ba01e975b55e9cc7518654c1867193d074a24b9416df456504780acafaf9a033ead10aa18bbc673d4a562199d32c08f0932fec0c81b941d8dbca840abe1babc68698ca2456953b45abbf823aa18a85a35e66eb7307c9cd4f29c29c4ca55f7f9588a885b963415adb22d0f033fb980398171b15afd2c52a8aab933c2805e32837eef01dba94012a18901e9ab0bdcca10eec7da348bd220fdafd8f35ff27f4a41deb57d04cf8b7fc12b4492f401b96724808ac44f263c40bdd650e945862a2ee90f556bfdfc540c4d43e49581460b48ec146ee9676156feda2a23390a03ec5afc990409e48bde24d00e1167e04f8a2c204211599d378c41cf0dcab8364ea75885520ab190cff3b324c7deaf16b367c17862338a1a4955f87da0a1b5da4ec1cb545d060458dd60fce281012f13e719dbc4dac4ca7f052881123ad6d4136d22e2a4225d85bb8333e9d1d124eb6b15870f57f63664961bb2ec86cc7967940f8c92d04191f2f9ea3ec4f963038bb4c36245e089815874517aa53cb20c050d6cbe57380a65d74b3ed83778602f3e53190c7b21819cb1f2168aaf869a5c57a997d9d7cb2b4df98b2cdba73d1d7742bcecb5beb234b00b6a79415ac596378e61a5953c801c87f467669b355b31bd347aadd7f4f1984ddf9d1de95bab69f2393dc4526db9d8feb911a9dfa805b462cdd825a8eb15864bed323e516fa2581880094463fca5e5f43467cbf4102874c0f228bcd3009dd05c77860c34767e7b038b35034bd705967ea781efa816e9aa97c7e93b4bc933ae9d418ceae0cf2cf42e9ca82b4a8b87045d11dd2c244d9eac00ad4897b9b83cbffaf46f91ee00092e0e6cc7cb533ff384374e05609e9192176f7665f938007fefa9750ecc4ec186d6fbb355a974dda7d9d97d1e9d7ae666b5020951f2d56f9885b9cdffb2f1b59d39743aca9ca6cf1988369e5aa6a0f065b1b7b9a1a81e4f0b77c88a87d709f94af9f15ee23b1c25a28cfc944f4b3dbab1f336883d86380bceef246126f0228e6ea8aa1be83992b91d59e2188a3c0ff1ea6a68b0b12480bd52720e6f2e681cae1ad9b98fcd03eb5d2eeac5888d50a71aee9f0387e09b1467ac24b69ff0364f3ee7e531d0f846cf37a4e36d98e1a7b608feaadce873cb6ef24852cb88043934c3bcf28c6488edee100de2c38af1f8b795f207a57fc5be0f8f81bb0326b8c3f738bed181126f103f20dc8c0f4a7a796fe89858e1da184e7676a2a38f639eddfb9b628e8cca1eb313b10946cd78b4c102aeef53e503119cd142093924846a51e32adf82e51019d4b27598c28b9fad71e743d7d2356dc984a544a7e32a79f4664a7a367933fcc25e818369f672734d7083dc68c3ce667afc8f179fc3876d49948142ce28a279a32a48241ee59ef957c14ddb92d327130f14c315945690c21021db1b571411cd8ae6ae61b042c046020301e89080f673ee0eaf721152d4386ec1de5f08ff8893f0b6bf3a665050703231bbbb9e516f057b56040e1e68e139d38c7c00321b648ee97ea8cd62c2263cef0f4da533140f399631fe3b8b081252414d8a7af9ee09989d1845f4c719c0f031a18f96ec086c1b0383b143e7336a910ca8183b112cca18f2f0e35da8d8382f89ef22a47070cf204c7a981b4288f697d4be99e7e6658652ea6ce4d0f91b2827e0ef18fa6531fb66ed5991218f0fae7fc27967abef1868c4aba354227d4cd408b1fa01921f6650983ea133ebe354c19e694450ae93f9b2ca7297bb4e736d8fc3b50868eaef9bd86ee2ef58ccee62917978b3d4d00dda33a215dfba9328c029b31f5f4e8df522ad3cc211e7e0a988c803ffbc812ea5f0173a2a4f907742e408498191b5a6c767cb377870df4755281d5a3ff99ecac9841baf88b09b45ed6ce5c976e4ef7c7f56c162b9098620b922a20c464d1cbe20d34e637f8624deb2269184e9bcd345b88c40dfa5918a5e4798206cb82739e0b1c4e9cb4106039e3b1017129288eb15a09aa50cc0375c12e31fe941cb6918121a2a1fa188ee45caa997066e256c02c333b3edaaac90b8199ce11c06dc62f18a3a51042957146f2cdeb179c8171f73f41a2694d6e65a79c41a2333e3686486137b1906099395164c3d845c06a30d2d3ea12eff1a6da440f7dd34ff895753bccf3130122cfd22873a4daeeb90732cb568ef8771f321d1c6086049765efe875f10bb7839d43b7dc974a4e3ea6b063d171a9128e4e983633dd20f04e2cd64584654b45e938d7ea7f08e527e2bf0f0a5b462db45d89adb7fc61c553ace880d2ca49fd869164402117ac28dc87c5868bab71fc203b84f8210798e72de57ea7de9585ee7f910f4e9ad053bde203cf43640496f3c73443fe4a8915d2196053ac175500e940f8e8dd487e84b5bd9e107b8ab4ad30ef8d69632cdf039e433032622b0c835fefafa45d4f0c41b2233e474107d20b50485da81339fbd20911d59cc7d9dd4bc2e0b47f3b1b3ad42374c0ad08d40626c076a1ab66491377d3627a17b984ac04d13aeab1bd4e43040060903d391c4fef60a458438d7c2fe63ae70b38fb8840c2329f2fa43aa03e562df0e1629abe281f63cc7c2d8a5ac7b075a3c2f162aeb086d9e54b0c5ef6134a5b4db468dc77fc1103a92570c44e648ed4131cc630c4f03e4b2f9b7511420cb4c00a0689964a070dfde5cf9a82357cd17fd6f574e4a26db6d8d8a78142fd45b5575a21447925d7a6256415202a0ccec04bac5a174a843b202522cbaf25dd125310a691bf6c04f83ae6c306b34e697b8337761eb6eff4347c62e9d035e1aa30be9b033ba311bf85cb1494fb70c6f23fe780f72db9c6c8b4f8c8093c516fb6ccb982db2eef75239e5579868dd55643c2ebe773d84b4c3b53feecd86b8ae70ed562cd9e7b4339fda497f2dd35cc8c30014654864a1f5c7568bb31f1c20ed38c947d63a1b34706d01dca23543b96eecd5038aec014d893afdb0179a73e5d7ce0287213013b71d6a2b2d3d3329482c9903bfb397f17d6b511895c0311840c6a055b445ea38c1154184fc64d8596a1db590d56b5b888fb9ddff83c083977a57560821c66414ec6de11f15a98a0b3d853df49d1c8a56026226e7e1e9bb3c3b716dfb864773c06cbd39c5009d5abaf5509955651b9f246d50f64f422887de398f92aa79f5428d4be9006bca81856210b7781ae9bc5ac9cf2de3a22b4197e57a2fe3619569283b606b446cf0422d314058f148917e9dad43265a326e590a0451a8a35dd9443269d858a0666f34150da644c64d8e4745211d013009fe4f04dfecfd7447e695f12454ef5b7c9f3350ff8ed0761ad73cb4871e2d22e6e749ceedd5321ce6012cb316ebaa05c9dbf9d5234a75315ed5df280c1ca916e121cdc236871361097716fa3b35a660fd5729bedbd139ce34db8ab29c177fc656fcff5af6d21c7c4995cbe4e99c75a567332bd842c339971b63faca21959943f7d2ce2e374286bb8d06a5582151850c71fcb441b50bbd256f34a23fa2283909e6f34559605293d39015f52c82df23ed39f8c37e6c15c94554f3a5e7d4c593e806aea1ebbcdd266584dd9148645c521261dd70a108b051d936a9207bb082cad684bcf30d9f36355c13169c3c38a279e2e257dce65d7f8c60ea11daedbbec5ef894adf4044260f15a2a9e4a725805d866121f171f384e666ac0169d0e0d5afa616e58f48c693263559aabfe6ab7dcd9632b481c40770b702346b6148b74b6324cad32b362398b7dafbdbc8633364186bdc20d0881ad338f73b1644a663a7f3c290a1f93f7e0adfeaa1ffb3efcc3c20006b3a4b31cd1f5c3c01db6d2ad1f418e6e78a400cc1e7b2cca06f8a391b1dd1d9bd35de692a06888d1dfab601c37808991d79a6cc5e87bf936bfc090865ec7afd4dd335e745523f8803abb9623d54a3a666d00d9b0353910b30f8afcb836ff7c5ec5247425984460b62f64caa234755d85970bed17782fc7c089a19a544a880b1eebc14c363ee50a9114844eb83cbc19fa662c39d58ef1bd82f1ac7ec5b29feaa1ccd3fcf197647048c8c9791f38d39c58d454cd7b694fe84eb67c3357336af946ea23b48f154c5512c21b227e63bcfeaaa04ae87e6027a380a5b706a22cf853a51cee12fe72e7ca7ec50de09676fd36926998d16d33f539dc1fb59e43a824411497b2ceecd1aef0615a597ccf57709ed27e7ae885e49a9ae992c9b5aa05165db4b1315bb997b4906519bddf1c8976ecfa6af7c9b7895272c2c0972203eb4d96247568634aaa85fa93e304e375341dad0e19d94eba0e1774f4095f3fd82a187049ed8a52ab1ad9422dbf3d9b6a70ff0dc527b19dce49b7ec49a0b0ccdee5d529a4062de19ee1bbd980e6adf3122d599344ee2371b4d7f15435a527b09d4ea3a1f8ac5d5b2df9a01390486a5df421a7405f806e4de1de8f46cc9e137a1b0b7c83ac53e4205ce2e7206cc2bdf39b2164c3734dadcbf2b669fc5b15bdcf4ddba31871963fe457857ddbb8ff884d7b0399ef0c604df356decc4cd68717bc29fb78fc66a11b367c1efdbca11f4bae259a1da674f5be6580650ed7b5455aa49f3ff325f9b370acd31bd359165fdbeaa13f31a64340ae631efcad27eda700bb479aada4654d2a88d7884547ed4fdb5849e2e5974fd7a768d6a740140efb3b52115675ed730c5f8110a6ddb6b884370eff78261cf9ff8ea048b47e2765d21126777f38f549c67b10e06cd870b4475aa9394c50913435a7bbde803d26413ba908cec5550b3f0e1d341e7bc88c15eb715925e66b3840b8d2dfa65045699c60b7c4265fcfe59e7868e6958174d90177620f6e7429744d95f4310320332dde683bc5576d9917c72a4441db07827895c9a80f83b5c9bcdddf1529cad3d7b6d385da6ced6ca54c41058c45fd624ca45acea8d4e8d397a9a743ec6b38943659431c2204d7d180684136a4a3d61b446400d9de162ad632f6cd01b48ee56e0e3b8043fc1a37f38115268ec2e3712c7a263e27853b6bcf28006b4b2f05d8e945242de1f9ac69dc96b5ecb6a9b6cce2cb9e61f4647f9065079f8eeb26b1842f4f26ff31aec8c9d601e2f3d42346b2e39ea550ac438a2292fc4b852f1ae43c67b25e30f101f8baf9b6004e0604e3dd88f067c0fc3f2f39d960f1f20ed173674409b6597102a9f6e14363f963d7ba694e21e574b397d501c89d7cdc1f17017300a9605e662759916c8c513243f786bdc6c5204578db07ca09f7a6e7ddb5418a5838a4d07a3f3b98d9a127cb75865edced017030ccf23633533455c63ca68b6dc2b046797c117199f4e8bcb78bf24c970e1d95f25bb29436478e14b1d6f353603f5a1c09302dc040ec7051fd5d2b67b6cd6a2ce3404f7c8cbb61b1a4a4fb27722638ac0de2f52f20839cd40b3de6c194cfda7bd0927c9e03beaf13d8912f0ebd7323831102f814646ae0df83f72b5f92ca8b0a7d021975bba41e1a6d6aed817e976c5c7f1409fff3f569fd91be85c415e1233be46844f648ade9e60a445b7c6d9819d9272ba4547a88299e87fc0ca23f61050bfabc553cab042091b71d813459328534bdea20cf55de19499be78a1febd20ca5220d90ba80724205c2c79ed0648b31e4cb357f3083fa2d40baba705261d07e24f0bd49c01862379b867cab68c5e68189aca39acaa01de8a864375e2b4d643b1dd73f92a55d94232e36b7525cd921f989896137314a742c402658590ca1f0a0e8233745b5e90ce628aa07c5a839a1ea6cf5dcc0191129022a06384e9852b5d289094baad6e35af4bc7a79237e26b34095e11491c9dae86468baaeb2026433efa38bb1f884db536bfb337e3fb9c42bbf53653fd3fabb9a3a1192f0ddfb08c1dc13283062428c3a3acd7921d1bfe0b6f3680bbd727feea3a06b1d00fbfd81df8670de150805510ccee254adaddb9eeec820375158e95d349fcdb57d77e37e4bf8f220cc7789e8532f33a24195e72af28cb4dfcdc7f96ae906317410faa957a77335271f67a11f06c64853ae19f0eeffd284e11a3f514a12ba681028b22825913816cdf8eb59b488ef06bcb118444a79a668ec6d396171eb9ef03adc121b9cd3436c92c4334e4d811fa59e2882eee1264f48c86a20db05fe693b61a38a0f410cef60bee734978f2bf38972c021f04e14b240ac9fde60399aa406a1245f39b5ebdf993fb3bb137f2c3941327e7b91b54d9ed6b1c59c95eb0d8dbb473b1442af4afa8689b7c958dc4579ecb2cf36253635fac682f28801f37312373147c8e7779b52d9f63bbb70ba58372cbe43411137285a3e783f150ed9b68ad4ecca9123cdb5c1e59551637ef484616d76ddc85151c5a912a0bbfe7e7c98e98b0293f86c5b4709fd1db455d6afd9aaf89c8b1fee09ccfea4daa25df9816a3a06ce1f80a24bfd1eb9f039112771467575622a342819afa2460a1b0d6f80d6ec42ca711d76cefea5fedef7727f85ccc09486a58b542f52e823ac2f34922bcb3135cae97bf69cda06853935ad638d55046894c0dad57c8f44807ea144a074e2fea4a0b8c8cc2dd3ebaf9f4fb863c1de4cb0dd7203adfab18cda6e65c0da14d9b9d0e1b0e6b7e483c19aae9ebdc9caced6937cebd1e2e512479c062a11acf774e7bb8a379535eb9505f4b5e16e2ada1ce92a2e8927067448e958180bc76652dcd4975ed48bb37d425d0811d8f41b25fa6ea00c45dddd20db14b7cf3c0d10b38a6ffcfa429bde32419a6bb69fbcb70fe5f800ed529241314479b428dd0549fafdf3a55ddce521ec03939955a18cb8cda2f3550b4f3d9b53d8e8a9c22150335973beb48fd8d26a367290cabee0094be097d3410a44fb28c9a21b164bad8ebfc8045bd09eec244d3ca8e49cf60f0d8a9e8d05811d07b2cffd7533e5e0e8f2c4631802dd9cdcee2736a142c7198337cb6e919eaf5b8337efc1449a1266d3369992436fb8ab8225c76594dd5e5ebdb0b5962bd9ef4f9e3718ecc1cdeb9f53b494b5497188efd2aadd2dd8fb192bc5bf14b1de22dc4341ad54312fb0eed0f902fce205d55055fefa45cdff123d7e2bade04c8853db7f4195c8c10804eb691d704b96dc6cb93259a23ae23e1c5f1a76ae9fa9de69996040c94646b9c9579f414b935d84d6bc0f70ff037780dbf987c23faa0535b898b4642d5145ae1b472ed5b9f57ed04ad3bdbceb2665a8a3fe6bd527d34913685aa5640361f52e00364de62be7c500b1adcaa92310b880306f5c376d7ae82f1b050342f28a2b54e28e491142604a43c98ab360ff070e6f457e51a37de4537e097b0e3ea2ac501a63f8cf97f3d6db198f673c10e29d21c688c2898b4488903c6f5ac3c4737b1d5004d137a86bb15c0fcd090ba5e42219d026f8d8f2848d31d0f90dac423cb75ba7ecd3700d6065c7b81d237c277d8062459df83d1eab50ba865b15c117983f8e9c40212d6de1e76b189b0ee108691d763d20229c0051247c30dddbdb3a14e4d69e09f914384fd5550b55de90a502db8f81df3d0bf33e8148e0a265373f93b1af792de3b69363f11912d170b07665bc849aba574dafeaed60a471255597ea544f6d5172aa43d3333d9f1bb68f9d8eedad3b741a2bd21f0ce6ab06e6c947b91a090b85c764231a898e7c315a9e1768b9eca83827b92aae43a771b7d1d66c8e844d36540d67adb50e19a89c9184ce7c7ecf955e7caf1f454d75044957d54d498daeeb0dcaa3ab0a5e6f103f6027b0c8e21e30b7279f06c19cc071b98746b8d2c0e149461372b430b0d3286262156154ed6916f0c8a4820dc8562399e971d4a12e89690dab4c8d28b30be170f9c8a534cd91d4277492fa45996083b9333e9078508c3ccb690c3fd423d7fe2bf594c8489e44fc929831bc199a219406885ed5f00b362aa15cc7604a17a38e85dc3dfd092c68038516f208fc452fb4fa88c9176d2af8557dd242c7682faa8da1e730168afdfcce77760de5780615726b025590d92da3051193d013699a5203571153e4be4ef16717c6367b61fba8014a301210526258d14ed6b73d60a365c5338bc60486c92a44ce103f55260c9896a069dcbfeb0e3834e41e494cc4870481e025d732e05faf0936884af1e69632bc16adc7ab70a7f955f7f82adccdf3e84e4249cb3559a1ea8dab4f732a3ead954279720de66a152dd7e6ac718381f84c28b42e64625a040b6de681fa543084d732d64a774653e33588cc9c8e92788725bc928bf4a90d122c4fda2046def513f6aed96d24c363b31deeaa2cd0bbc2167315301aea8a320778ac59a6a9a866a0638f6b2c8192d2d19a5d957bf8cf51036553ac474634416e14bd66324d9e6db380c7b057ba5813acc65b48ce8abcbff74ad0e233938cfcab854af10e0873a8710a5924ba4bcd07b5f1b90a7f8608608215062251e48cdd1f3eddcbb7280ee1bbb873418a12688109dd342dca4a07739e5999988e8883d13f5fc8d3b1dd2203092a449ba500cb1e25a554558d81b0393fc46d260452ee645fdd18eb3c7b0c68dbb87034c38feb61d85b762e9f87362208afec3748291114f64d3fe7a06b7608a0668c95249a704ce32d88c3886c76b025e4e3b11aafc2c40fabe81eeaa142cbfc79506bf292848d26c5e3b8c034e9af8980fe60761cba49c8d53c8c5f3d691a35178c40929a58c0d32889ad674f36d3234154e44f33907290c90e67076c4e8ca7e7dd1250c9e2e1eb010840bd276c6ee25e3da6eae7d8e1910a4ac7a42841ac1545b3d5c144488e3277f99fc201f129bd3aae73bd3c85c32a327f013e3b6ce40ade0819eb831147800b5745047a04b2cb07ea5e461e95d7dae88c499aed0488a1e563fab5da784f92fb4751da51349df5320a9b459125efa30e5ebe0efa55e7152583ab3dfee44685da5a8cc04cb91cb817cf3ee65e22762dbb152cf4dac949c56d4857616d74877b790f203f576995f85e31eb8c94910113758fb0bf671002fff176e06c4a3e2a58bcadb903932b727ab31e3482a386a774d6af6b778b552e07c6d9660d3b416e1c9134f9441b6e1b43a30ccf40a611b868808bb9c5447720081ed9a43df1485b5182e9071175e05cd65352936e2eb53ed2267e83b7e964d6eabeeec399c6432f30104b1e09bfee48ca514cb383164318ef063c8b881b35921c0135a310ca4f22284b9d0aeae16b1c9ee16094eb5e5e827442296f7c533e65235b1e511b31dda7646a4fd2cc1d1b67aa716c32bd896979f4d1fac53426d34d45a756ca25c1b518fda847a511fc3c9ecb3a9b42e2eadacc210eb8599e19f4f7b71e05d21e576a73ab0fdb4b829ea4a3853ccbcaa897b277cb0bca209963dd77517e430be48aaf020228cbc70d6c42e926bc850bd313ba0f424619ec67d990cb5442a7a206aabad19580e64ae12a219e0ca366872c991e41b408d628ef858c208d6796989061538f736ea05dd600ef095cb8007e170176a6e3edb78b7df27f077cfaf778e219d27712610e2151542d873817cf377b25d266fd765de3b05891f83808c194bad871ff5d45514c4fa0db8cc5d87d5c6402e4c2c163e2709475783a8056e3182549847f899c41c6ff5f3d442a536488b12f76e84bfcd1ec95405c08a73c7183189d17a1f125915473b3a9998480d7256d0298de110854bdc8a498258f9a03fba0c802ca2ba1e8e3501c871c4a4a950314006d923ab12a39c5178bc049f02283dd1035382e900d8adc81581cfbe1b4dfb75853c010a30c3257eae070c0979180961061806d0918cf31643ec2d77a29679caaef3ea9854ea33fd34426e8abde615240e4cc01a3e8d4800837f290f31def003304643b8642447915c5a66adc13263b727f2a8b82eb516d0a4f4d16cc305648d9cdaba7e3a5a1d3e9a96e5bad58275cd3bf81d1f91f8c35bc1813e8a94531dfb3aef671bc5624212697c1d25ead2da6e586968fcbf5b9d7fb3337a9cee9400a5732ab2e353e42438e4a44e818b7c48891aad87199c76e57b38213e1739a0d46ee5987504810d96eb5a42a589e717cf832e2a675aa776471aa3e4d6625c12affdb37eeb0711babcceef5a72e38f4848e6b650e7eb20dbe6197bd18eb2def3e16d0ceeb0bdca7cefe194f6884e907172b2f1eac18ac62970b06f62d56a2e169cf025c13ca1c27dbbd109cb2f0862b9394a981ea112c46d054a8ac48587dd59b92c145da9d122203ee412871598332254ce4300794d219981f13db341aef8c36167da1fb862583088ed70d89e03c38e4e65fb44ebd12046ba4e5df539b5c887801f22c47e611b477d61c55b2ddd94557ec9fc865137c2b0fa1959d6e2ad0cfe4213932858a6cc4941509f2f20193f7a940768f59e400f4231657471762e4217a1253a47bb7ba45e0859c082553995305ea5937d36047e1040e5be54c21c578315b553fb39729932c57ec87abb44a538dd2e9a14229a96f0c866eba8a520e0c84ed6cfa1e57e8f81a8db4a70b4bdaa495dfa1c976e8b5c92f2e59ed30d0bcf266809abc7857e795951c06d80deec776d4067d1553fbb6a4c696e46ef9b54b54bde4e9bf90bb68a952c89d99cc7cfcb00b050be0c9a476993c2c49e4ae5a54505ac4c3104e6fb995cdaa591658c4ce02b5921d121247224a14591598328279848a05daefc1a5290e0741df15efeca455b86bd5e615f233006b94086960c088b3c70bd6f972c89f3bc2e79fb93368a3e7579bb074880d0b51cba2a4658cf83c6a6fdd6d3fd54974297738b2d58be18c9e120174661d656a45fc2ef974def5c93a7b2db5e24481fb46771bdb1b8c1e1d858b84bbd984ac13cab19dd8846c14c643a83721093c112dacbaf87a74ab723909423cbff584dc07fbad16f981dd60d81178bd76d23566bbf766e77518cdd350a0199ebb6bc32dfc695e379843f0dd301d3d046e35a51fd7ed9ba4fe1e9b9bb5689899ebff137120af01a2d1dc68e08bf4e179c9418883ab82112a553cd506e339a1b48782c8891071796755dc713eea0c20a3855a69a2cd55aeb93ef5d442b139f8e69dd1f2dc78a4ceee2b0c9cf0df3ae1e9bb61ec21ffaa567fcb8d50dc174e343eb0ca3718312bf16888edd414a3ddb1d644087ed395d10767031ed792e0cd591a55028828a257d4abc7656ca2fe3ea0dc6ac609176ceb92fd629c28ee0179723ab8d174c821cda48a91a6c317e178626e53f16fb20b2742b8dd73800df074756eadda83af3009abcc19eff36b80488a527ae8bf5719a12c3b0bd4e5a98b5dfc04ccc31c23df32888ec1fc197d26f2e484c5519114dee59529425b481906e0cf3239858a986c0d9e93cbf28773a4e1f133444ca12b9b934ba6515239551f4dca89652285c2993d0f4360547b30f20e64548a4ebf2ca44dc0b481faeb59ea5a6111907b4ed2d1fe84148189c8b1a8d84a4cee667e06b1e2d2ff677de30e70923ef554f175e9011a373be6e99d8a8299c13db923e3f2f234d8477eacd8ac47a9c56b97478431c0f7993065177a1a1522cfb1c88c1e47b9a0d8b4eea8b9a75d35432957c5fc4f9a5b15d6b68d0af483c50d3e97809f782f9bdfc025c575ad7759546656dd39357d050de280852c6a4d3f57e037866f9838ae595f637d4bb5fed90a38fee1c4fc12baf6db24e83f6b11756a203904db334b6df046451ff267de3bf2138b61bdc5269b538a7e9895e59411a84bf147ae9e98cfd0cd221f1c40e9fa7383372a6f3ca5f88d43eba34290879dc7a9f69cfe3cc2459b819b61c627243bf58aa5c6a7267ac78cf042f46853d9b06c963057574fc01a8db60a3dd886596705cd280d543274e3b8f8e96623c1ac5c866ee533667f6497c312ad9ea725ab6f53478579fd871235a96181e1193637de84e80dbfc105e7572cee58b0188d7f1f6ef51f24e8dbcacc0232833af1324339273ae3eec10f8b0cde8cdeffc02c572ec49010786319d463ef2513f320146103ba3b82745c308eba9a0ea58266fa7dc08955740b5c605360b57c5f66d484a679b773ec9080c49501be08c87ebe00cd3cc310ec0ae14caa4340134e3a5eef9b4c21c36e17e94fcf1adce9e476981795bd72a191e668c416d2f6eeb6e59652a62465070740072e07dc0bd93a300572e1e675c7820a9d5bdeadd42b485ba43cdd82ee47b89784eea7d7fdf723755048f7938988119095cc0947cf74aae15538971cd6420eeb7b2794d3ef9813d40cf8a98b8a5cd9d6a71df5baec791c28a43eed7674617361f251c3c6b2a4c54e9a2e29b0ea59df48848afc5bb693c3dac9c9a9072ff221284f5072e64bba27a8b09d5af4204a1d095585448c88a8fed5bd9cadbe71f8ea42229a9733ecb416815aac34208f1f0e7b5195eaf10761fd2afc593deb71c8fa9fd56af5abb0c8ccaf1e874054cf0a8918adc21f11bf2a0c419553b0e3708ad11ef49d195d56a864e2985505c6de6254ff2510cb8d64b26e92a6d3ac67b319d5e32d79227dface5c25c03b64c6913bf325173c36d2152cc1489f1476c8dc60e44e3f09f43da99346bb81bda5b33ff6960fec2d43e099bd1f7b4be88386df8e077c3b663e410acc7ad58f8dd43497a8dd36afbf11bc67f2d175a9cf7e273bcff3b13d15f2fd043b157a4beef7b8e46e2cb9bf91c03413ad7f0a5d216f2473cea80b55d4471794b885d39e9528da09a8ad74522389c136a42e22f0d8484923914ab57aeba386af1f8d444b809b16cea816bbdffe8065142bc98f51921f230f4722d60799628c688236fb2912b29e1516f99945b4223faa67fdcccf204da4a22b27122ba4edc17cfcab1f659415385ce118831d366b4d4b93c98fbcc621817d24d67e8eb65a92a4cff6d652912d14965a1acea4a31a064f30004699fd14c8811df772468374cf853f463ff73b4ecefc7e2a1cef0d2507ba30806176286228b8be9cd95a6e6ad16e16c98643e05196152061f6adb5d6edbfdc9398f80226db2ddb9f5062748fcd91fdb9c5beb4d6328151629dca23fb938afd293f13e48e0d9f6a9db485912dd26802bfe8b3dec88c035fd47bfa9ef7ac9008ebbdf0e7fbd41399f93ef53faa13f2ffb4de7b16f8d33debbb1939e03a0626a2f91fef67be051a01e99ef53cbc87267cf92b9933a3d77d2034effdf673d6fe3fadeffe8608f09ee681784ff3fe3f461e02e9bef53fded3fc8fd116026985f77f68ae7d1a5ab017befc573ff3ddccea675e4030eba9ea59e05773813a2ad44c1fc7182c6746f4f3fee52110eebdd4e3f881c493c91cd61cf8833a288158f6a7e028ba9864b2679e91ad2b145d4b29e58b0a9e7276695a7eb100a7e020a5e38c1aea9e9f4535e35c629ab59e13706ebeca27305db8508a23539dce01ff7ff53466cc7819353443381c314e38ba38d77fe128aac21a8e327f854953fa1196118eef5f138e622b1c63b5a69921d6100e41f07e27794f083812e64f461613bb7c275d1b7a0e0040d9b4ec34da2686aebb4d7bb282c060b87bf794ee02edee6edadd9476373872b9a7cf321f8539d9d32d09dc6b4b8f05842be12f5dadeeb59dbadcab7d4aeb04ed579a6f485d2d23309db2c55a3f2d787e8e9672a72f0d3fe70d2cc051eb53aef56f0872ad4a3674c9f5ab7cc09b7190476f8a1a92100293e96fb8a3a8354a84926ca5b243aade106292e9d35aab8e1836685143982a5c706156eb0fb9d62f5799ab0cb629d36f117ed46a24268628a2706109125866b516e54ac549ae55860f4383951c198431e7c40cb6e97aa93db208da9466004388f67042b24c5a70a62a044393a002212a65c9dcbe6cf9c2a54eab4403112f6c36c460c06022d7748928c05e9659b278829100e36499258ba83c62f10ba727b4e5cab20516368f3fa5dca19fa3ed14789c436456b42642f6bceabb4f2993974c2514322d273c00859fe0cca950f6e7a3e33ef5db4b0fb73c9ceaa1bba2543887b660224666aa4fed806b1e9f0b7fa8c2c0e31ca20d557082a13a4818ad4d010593964c8fc8f4471911f4e7049a4095fe03ba47cab7639280c9c2e9697ecef5ad222929bb97dd6f985ce9095276e9495b949e38653adbc79c20384cf2a0603b09da4cbe6829207bf27c7f14b05fcfc969d1715accd101d71f71b2879fd3a23f8665ff6d0478944e3eba7e8c752e830a5dfea20c4a64f7d9b783c69716dbdb3e2549292500b2a5d6565ba96f1b121dc8a38b07198490ed1e065078c4221358059afb45e802dcefcfe19fe2c8602f13ebedaf17bba1a45f0fbad1c740fba04438ff948729d057e1e62977eaf75824fbb70d652cf4b721d1fd5a6b7783a0088f3733d92a53a6d054c184a60a2636d890840d33ccf032a74ed1841f1434814e2174ce06cc16a5218ec5feb5041e63fff37380346cfe30014ea5607c4808f12121440baa0d50903aca1160a2d86dabb556bbd99e5270dd68583efd1b06aee1ab7fe60e63e10d392738f64474cace19043583dc45e089007beae7e7108147093f80cc94edbad40c21070f1083ad0dc4116d282be18710f865503850ae6c99504a41c19f9b5639a7a4f425f09dcb90c8becde958770cbb2b05430d47fc426db7dd22d28be12f29ed6ed99422e1eea8975d38b4c56e2a84a951151b266fc3e4cbbe9b531a7ad98244f75e4b3d69690949949f63ca29a7cc3165eb88f6e30d30bfeed4dda5b4a981c17db395baa49c51c3454e96b5c67a6d6d12d34b73299de15adea52cdcd195a7faa8089f8a8ae0addcdd6ba5b5d64a3b9c929293f24ab939b53555069d73f21081ce10bd6d72a6f794524a29a5f2f502e1480d1b192175e10eb9dcefdee1f8fa1173987c569eb456bbdd71ec57e5afcb0e4af9d4e5e3ebe6da79524a29274d4a19a502a594524aa9d7b5524a29a5d4fb542b3cf37d18a86cac5c18a75836683c0ef42eee928b044770aca19452997f83b97524a594524aed6d5fdde01aacd68c67696ad4dc8a5975061c5399db6a64cc70d9bc6478121c0130c3456dbaecdfdd6c90fd3b9c9ef18d5073879edc49659f8181cb868deb06a76ce4fe8e86c7b97eec1feb3bed9ae9775d336db029a5e1b5b9a95123fb77e1cdcdab064e8b8667737f0700ef29695455cd8df355708ac320a536b36bbe8865595d4d4777772ef07c1fa59452ca9aeab5e54949b3497a299da192b2384aa9ace102bb7baae54eb5dbbdabcdd6ceabb4aabcefb374deacc32465e7eea56ef348c9b1ee8c47371a8f4a4a6dabd64cf95eb3943b94565aa90c8f4aea140a6af4dae5dde6a975ba3ba6b9bb53bfb91100986aab9d9133f65f5f0f6c29ad332d1a302d9a992ff6267405405691810c6a6bdd2a4e8d960694564ab3b0f645ed97d60be31b3b43ab9db9c27ea9f7d28e56cb62b1baae7bd985b24c9932b6be36d059bd6e7476543796565ba68cad3602b8e1d9dc8875ee9303ae034a847670894c892385060e0f2c0d1c0f78aaad0183f3e2e9a1d5bfbe2c0daf96563b64da4a3ff09922c5cde3a4fb767476a7d56e38346eb27c7a656d819c84d940a062cefaf007c3968c97326a70c277587f4d173c7ffc57e80a4000bcdb354d19c8c820834f47fd1a35d4abb5bfd91a2d0dea8b34d4bab442e3d3e12ab0228c7e58b45e18c33cd8a7637b1b1b78392d977f5eac4c242c66789b6c6021a12934a9e0a821f349cd04d54766470644f4be173f1daa176ddcc0deeabf1e7d801956d779988ad7f94af50d994fbcce0415c8c88070a9af0d7456af1b9d1dd58d8ea7a323bb4957d66e98306f34ce104e269b4835cfa46945c804a1c9ad5e304436b9e4709c5b643602b8e1d9dc8875310e3a0eb80e3af0a4f4b9dd003c297d068063436a1107081a383cb034703ce0a9b6c609e7c5e3d1ec49ad25266ff5822ac5faea9161b9483497c93e1df46545b2cb200cd9b639adb488a58f9037fa93903bf5034f4a1f1f4f4a9f248c5d5eeb8dcfeb66a63978824e0eb23f75f798f489c91d9f14088cf1c54e0bb3c316960d3c44d1f4d0d492e202950f543a1fb2b84b5d23aa218fddd460f21cfb293799281fb8c8aca7cd63e38a79f5d28725eeeb28f900743be83a294de48e0c2e410f2f5c2994789062edd74f8725c00e284852da61092d1b614daf88be3a8871b5e8a04408997e875d2efa2e7a058728a40ce145174e66f4c5e6a9405cb152e562af66091e4516f20eb976453a28c1c9f4ef3645173599fe96938229327d6a37a5211e32a55fb9e4e420e50465327ddafda083031513e4e042a6ef395ea26891691352e0f18a3a50d8cc8cc04d578c1607585b68a0c4e0021e9a8122f4c28a5e160f4c544d2c612a4e3e033411b5520af29c545328e94690f44141810ba9273c2859b8c07550bab80618c1500a4a18306c475c1420254c51624862002786a2387161a916258a0b062041541422036ca145a1c2845e2e3480134b51987819c53aa6650b316cb8803924b028304cc1ad2cb33cf9a08114d826cb2c4f48884cf08c2cb33c49914197272d32e0e2290ae604af90d4a8823f2736ca60d6908d21f08a080d76c038a9c613be4c3570c03335510b5e6599258c164c61c0a8410553174416b08d013606e69e9c00df285b4809e307518c510d1bb097651631546a1c65119374a41613b3c5d6a493392924302bcbaf5f18065f292fe82370bf510a5aa048ee14787bfbeaf7d519039d4722790b715a746f5197c5c4609fc205c4eda2058a5db885f108c214b1618c714be90616dcf09447fcb48ad32d98eb965d0577376f5c33601fa9c733a6e011a7bebf9b9e6cd93e02cdfea927f8738e965212a9beb08884a285312420a168610c5af6fb94e3387ffad465a8c54240e58e87f52be55e8242dc3bd9bd48909ffa1da9972ab8a42f4122cf41dfee0d456858e7f841ca0f600b12794f411cdde37d7f2ce6b21c6070151ef2289b7498b8269d2b0afcae16a58bc0c3257379cffd9bb70db4a0dc3102cb5047eec8afdd5feacd5d0098b08ef4997912f1a72111fa5eb885dfe22482df3f4ad5a9c019e5b04afb81c4b1c3eae3f7503aacfaff784f7f23a13ff5fd2922aaf0e77bff2dcc6971ae5ef58f55ac577d10d6ab56ff839f151209b2fa51fd785618fb9574588ed81830f4ab53a039a32c14a1e14f051c169ee0b0eafd98d7f6347c6d21fd1ffc1ed2573da534a4d1b0c8cf1c72587dffafbe9c85627b30df7b2b68ad8f630c7d7f2449e87b9fc4ffa3ef3d10ffef5b416daaf0b5d5d716fec0db130caec0090536cd61f53f1003e5fa9c18dc42f5c330b8fe6c9a4935e9db42da6227bfaefb0de43e9532d21f391b7881c7f76a10264196c9aa26ecca4d690946269d4128773f16c99386eb8144e40b8018439b11790551153902055ea2663fdeab422233cb0e3800cd8a7020050c41b38e6a0ffae5ac88e442862a42332fec0f9433d79125a425ba24c70b4573b2d2626f791d61e9deed515e774322ef91bcc76ba4f99130f34c016d246107da88c2374273e98ffb7602c3a979c6761abdc8a19a67742427538b1a3b6a49f38ccd25b7182ec678ffd5c3bff63bd27b91910374a11f8563913cbfa1644079826d942f28c3d85244cb0e1a41e28129cae40df49745cd33bfc7b778911fcdcde57228177605d1bc47ce80f2903bbd8ed80277c2118b39798f87c31ffc1f0e5ff3677e5e53f541583346e16bb258af7a396b16e84e0ee5b0ef3d509643d603112322ac377afdf03e0172a6fa9aa74335e0091f58019e54cdab7e944d56d0680eebd5ff1801c1ef7dff83c339e4b0a079e4b0315ff89a0b50bdf740beaf098918f9f41088eabdf766dec88c45f35f8df747e6ccfb1f9a703239ac5f334ce187745802e4cc0487f51fa9be6936318da999504d5fcdffb0bcc7615007d15ef3ab79ef83d4d484aff935ac67853fdf7be16b862f04785ff341bcaf097f8c80cc7ccdffbcfc695eb23e48cd4cf872ef83cc3ccd4cf8f29ff19ee669bcd6d3d474d00254bea505ba934371636067f2a5169de64beee454d4475d94db432ceed44950ee1464f5a3fad53b943b2d60155220a73477f2aa38aacd3ea7e9984b290f94339af43ad2d1dc21e9eebed7e5925e28a5f5012dbabbb7839fc3eea639e48efc98ecc529bb3bec965da48d7877347436c7032692e6a18f3f2665b247ce205ad417292515593e0a0e90208490a70e52ac40210cd5202ba58aec3fa5587fea604576b17e92303ec78657c8b54c762bddddaa6e7882dca92fbde46a240716f8a3c97348183dfac0989412e63547f697b0fa29b0eb8fecdd7fc20b914db7392f40261ab4e840c68b306ea8412cb14d7aa82f7ece5b31b48527bc58b550c33cc1b48277d1c2e54038c3ad6d58704ba48262b663b3d25a690d16f097a59210458c21c4942d4244b13428e91c2658760068be27440bb93eed5ae9bf8e00aa1845193d512962d43f2f9411982deef9a51941e132bb7928eddcfd7a97a500168a64f76e760a159986f386e3ddc2117b8bbea82c042656b64d22348f540a6229cf20bce45492d29424733ad7c4a9e3155869bd7a3dfabfb3877bf94f2499c8ad9e5ee656cfd4864cee06b2c78625348c3e0d65c3c601f08c2040691ecf80a4995640fa804e7d053a54c20f1e4bc6abc210155aa443c20a04812f7d2f2e2a32a514082fd1a73fc42954ade17e377b7bf9e9f0d1dbc66ba7bd96c6451e71be933623df9f136886938684472fdfd4bd9f0af1a4f590835d54ffd6d0fefd8e227de41d7bc98ffdc40b7b68389f72bd3fadf4fd09a57f7669f55d5cb66071519953eea7eed3200273299003abc0f38fa84dbe3f6fdfa121fcf23bf768173cb7344f33d5ecbd6f5fcb3ce21291efbd6f8ff09422af0b04d8887b276dfbbbddedd29f9f2c5f51facc21b973ffbe0264cf8442c29898b46891573ef8401441d0c686c5baf77edf7b7f9cb4fcd7730d6dddb5928142fa90c3a40fcd15cc60aeb5d65a9fbb628bc0435ee7dd6ae55ef0e762c04745cde3ea82bd86a5fe0643f64c2aa496fa4c4c5ab4c82b1f7c208a20686353b93c674deed4af453e44471f9252e4ceec41faf4d76642dea85f8b724861ca9d8aa536538bf53beca5166b95d4932e0bba1916a64c64cf0974932dcd5b30ff205b876cbb06dc2b8041a579f05bd6db6f26b2c785ba903ddee430e943b34dd9af35acfe3e0b9c605ab48f417b05db22b037794f7dfb2e54e59d496a81d2619b17712f7031e0f9d43c76c4f387a25412b875381572c73a90f441923e7e8473c081b43890088236360e2404044e026f368923913bf6534dc224789c4dfdf3de24202bdb5a3b9de40e7d9aef4f02a3e030fbf762ecae1702ead30f529f86488064b3995772672d18c038bfc8f6bbc8f683d0a709914c27c9817d25773613a2e0d65a2640b98588b2fdb63fc14481ddc99b869ab2c5d247faf474923bf6b3ece91a562061f69998b468b922af7cf0812882a08d0d8b85b36dfb6058fb36083c7ad9be0365fbd6eb99a00cae0041d320041843d90801f6c068f0846590400c836fa8d0f0430f5e7e1012bbf80106b10abc7a0116633420c236592a159d40dc01d764a954c484175434d001b3b2542ae2c1865091932745405ac4d8a0c953161867a944d4e5044426d8801605080606930649b863a286126c97304289e8034c4440d8d082b72c958878f0614eff6eab57473a6520696459abc9f293e5947f6c88dcc98084f573b1828c269cbdae7379333a9547af87adfd6ca5ab16f67ce5ad4a2ddeb6b0042379ba2a75f9f43a4ecea7a33e6bce1c8b43591c48a704e1d69aa0a6bc413d29592d55cd274ef73c2a6578d2a3299b1befbe365b43c16d9b7f73f391306987636978423a413940381d9d8e3f59432303261e0600e87e8f36a98c192e9b9b215907fe8694fef0b1e6914a4a185dc9743f980274c515474041c152a58244169dd36ab7f196b0b91893f993c78e7443cafbde5bc311d370e6de5bfc28b7f42479073ef83043972fb490c169e61ea81285cdeeee2d031e316e9c7657f3c8dcb31ebabd79db7bd2f3878c6a356d4e9f41d3c1f1a64079f8903d43648f321b903d3c96e691251a1275df605765e50196d0b0f9dc93710ee540bda039e49ec354c90c85f69caf21dcca5269862a799c41b98416271641997beefdab7fb7c999021d227de66637ba6ddb6fdbc681258077ca56cda0a01fd913812119909e0464cf1087bd181ce571b3d611ab40f9b5825c0a747a9dfb5bc2b7433e51f70d689ed8090d6891c70c2b37067fc63a416207d528d149efe92b5f94116a7532bbbabb7177775f8735a6d9eb6eefeeeef65abcdeddded6058da4694de48efc9512980cd49353d214ea204a29add45b4a995c1aa294e21a9e2f7f8eb329cba7b44b8bf273b4982b78caaf1183e5d734a5d49b524a699d492df250f711e0299b5221f49fe60ddb2f3b245264e645ba6750e041b55aedffbf56abd5fe3f6989e9ffbf56abfdff77d004a189f4b964ba073b3d413575116e72393d54510bcdf0ffff1f4c172e5b269625115ca97708171dfd44ba40ddd3b55aade8a8a8a88b7e740d045dab0975a935d56ab55aad56abd56a35ef367501438518649eaaa8d56ab55aad56ab79d01fe9ec745aed76b9546767f267e77d95e33e15bd57b5f26da3ab5837f32cd9e4a2d39680986a28788f283a2c0578662061f55960fd16f22ad21b8a0cc8f6eb513beccda709824b9ffbf26717f9134b6c224d2bcdc3bdfc7ea6a79b9ac9752fc62ed72ff912153ca548ce225a4fa12cb97f9c4eb2fcc9448230b1cc1bfd92e674c57c856aaadb50ba85baa4165241518193502fa95a28053912d63d0b94123697963a550bf92a945d03f2fd2976affa7a7c4e85aacf87ab64119e3fd4505aeceea1bb86b6e22aea1bb078ae610bf50bf5ea1b81fa1337fc277b0f9617f39e6d06b56a0cb0102247510b5ce00596284f3851c4154e30e960adb5b6061f5cf08219c22ca104083ac9cb096e1212a37ac55092263b34f1a4cb17274e6c5189b842cabdc208870eb2b8e00b185b64698111f5083741e1c15eb1c24cd312375ce1845382c3115a94184af08014461d02045c962748ee155090d45a6b2d81184b0606ebe58a1a85972c668a349822040690c00287114c11a6c9163329a7796e9e33c9262182cad01412601ebae01b9e10a203415d82a3329f3d8284e65176c90e45a61ae571d226d05c32859c8825398fd34af6a67214953d004d8628ba72e5ca0c2cccfc633c4e9072e4055f4039e1441033ff19348b3cc9aa8e58248bad73e0459dd8d6850a76d8c24b91951cb4f8a18bebc08154134a6e70128515485e70e1160890baf9f385807bbbe97dbf79de77537f5f08d86c98fa1b522e05e688b5d31008f75dd87e432e1c63a1c3a6ffcf7dfbc3fd06e4be0d8170bf85f8b3c02c937f8ce8ffd84ffd6cdfbd7cff5e08b09f7a20f653a10864fb2e7c8771e06bfe1b995d30f5f7132067ae90080e2be46cfbf153b65333fbdbd726a6a47207676fc0097247be6784e7c75a942f71748fe01987dc915fbfc0217da8b4b96a0007e562381a70824c862079b6cfb593390c662061dcadc9a59e2c3dc81c09997ad0352a1a19302053ee3db04648a63f45ee7b0399faf07cbfbf03784e4d231407acc608ee4d11ab570f87f4a13e3bd596872cb616ac68e10bfd23a4ca6502b422b43801921034e952a72c81258621ea0b2e822f5c1430d45a6b350192db4215a0285a40e22f7c0ece52e9a8288f1fbb5e0800093cbefccf1175ac064af866a97434248e81b72c958e6a68131c31b9329924b11c1df132c6568778ca5d964a4378916189b5d6da301f688d59d244eb8b314b527004e83b526bad958b0e1c145c8078a116610598302ab8428c099284f1c3064518474a66b8ae219686b052a543681e2ee575aa6f05f290b0fb52ca5b94650f7658d3af3209bb72c9089228e3910273ae091cf852bc2fbf1ed4f3f6f8debb59db63ae902b5117b61ad65a74a28eccfed441c47aa7bf43e6eda6783856ddbece3cae3ccf48f6c231a65219c92a8cf11bc9d88570bf92592564f51cf72bafae563be4abbe7d16a8d330fb1514b9d48b62f35831d63cdc5bee6e7fef9c77daaeed169dfd252592f288979440ca7666bd68cac9f62f879f7e3a2c70819953bf43666ebbb5077f5c0a9c92b2147024a9a3af7a5538aa8c641a8e3ade774672178eb1affbcf48fe7aacc6ef57a191bc0a475927a4be4a0a517d55a55eb5a382ae56ef90d95efbd95a6b3710079939ef9d3766a1aab413cf5a67752ab3ba2783a58023b516a50bc7f2c764217c9db34e20e89c73fad359ebac4dab774e3178fc1ca7230e32b5d6d818f27ee02bc834ba3fdf2bad94d254caa6524f53a930265bede05d1f62caa72395024900755cf04542cd1ee851fb77bbee479c5a4cfdbdf47e4e419939b0befcb617f5177d9a690c31269c20bc796013d162313451327b05f929229d88528299117d2f7c059929303935cde67b2ee6a6eb240a542a0bdccaf2bbf995febf101aa02fa8b46df844b5643403000010042315000028100c87442271402498e8c22a7b14000b778e3e76582c1a089324c7511404418c318618400820001162184254441c0020516c50695ce0c5a843790fb716cc6ea340f88e8727bec67ff44a591a7d4a03d016515bb9882701a944cf604088a9ac816dfba7aebf6559b1951f11acd87833f99578132e8b05894b0396bc3c14fda0d81c251ae4becb11b87d6df5b3b22abcb0321eda997ccf0553aecfa2271e5203fbbb049c307d2ddd77ffdd1754039b8b1d2194851b34e639d94b16d7181c34417a142328fc0043766f574ed165c011b932275e5aa5044c2139dae091585a8e578a2d1336986dcef41d8a343a2909289e66c6fb733806b46ba64a4123c90e2d87cd49fc6e03bb9a0322f89e04b7144b6a4664b0e235b373cc9dbcc5d5e9d5fdb4c8ed074022da12347a37f75f2637d809ff5aaa49c1207e4dc0ff36febd6f733da9323a466254a02390fdc1ab8de241832d2227f81dc8894000d566a6afda11e08d31ac4a34960b097c22b653a056ec1c9659af2a2c92d5f952168d6d5986da6a861728ed695f7c6c98bcca5dc585b20d0518e6d7556507c6ccb4a8aa2567ab12d6b5f718f3e062835b4ed068940d137ff25650e8471bd865f927fa139ddf988d7bf7af073e58f322439d70bf6c7bf81e675361d6008a9efe03c9fedd952df8c548de5ae9927cff1c0ede0e57e01cc7b5c0299081af12e7d99a239d46af049179cd0789f2245cb38d45bf2015786f07f8e1a444d1c025635f92dbaa09a50a1de1fdeaf02ea3ad1850150a7045f6e050e9a2af0c47432503e80f843ffd477f8bce44b7a47a9ec4daced89ff4d5e0b1d45d5fc0c678f68eacf4b061d677700538d4a1cf10b87e09d69bf802f9217aecc4edf23d0ace0ceaaac108501a41eb2d5ddc178b53990499e3bd7b804cf315ddc373618138c396eb1b57f2049a2483ff4f90f0322dd830fd256ff00c58a483c6fca47dc0dd3bae6e76f484a42bfe1f7a8d396f725d1c9a65527795a846f3d77abd5516e4b1c1648c87d555170d21316494f28c21cc8b32eba08697c5b3e01b72b4be9a80c61682a1861b4266e0a026a09947b0c78eed58bd7b674232d34f3ee7f09b099ec69e57efc7cd1c6a0907d1d0b3299df18240ac9ed79905926e52d70c2b08e8326c3dfcc503b8f21d7e589eb1260be009d1fce33223a67972f8dac80c55953eb04bfdd6b53088b008f77a3471df79b12c164b189474cf3efd82833d3db6ed2baccfd79e45b364011f2ad740de9a8407745e2a8d7de6218527b48a82ad9e5979aadf0ad542059db4e4eb07f06d3b5a0f0311f719d8e85d7f1080d21523f8b68c310950d4300d1814d8d7b32127e42d4b5553eac0874ad205acbfd234958e4a54458423f57cba213eea0326806bfd7a5852a4fbbad310b100e89ac16834a27c0019f7406e91b94b91acd056b1e086f0326a5e1fb51a64bbeaa56bafaed72b597588ecc825c9f9f1d18b473276ebdea571e43284a417ffad8d58c788d207cdded3c885732d27e8ba41f2c5a5d03f1765913064f5e9dbd73d28da823dbdf12dc756b1106e6793d369c239adfb7dd33f786536e4189798ec9f9ca3cc61a638e4a5ca04083d6548ef7bb3e1bab03d208abb117d6662c2e16ed826661bd35e3aae3426342d13bb8e7c569d7d719931ec6d173374fb16bcecf9b05870168fe1508c6eda0ae5867260ed3cbeaa26c4815302c657c4915a5af1328cdcde240804e073031cba83425810c6b8013a379c53b30b17e3527a427b35937213b184d4132e9948f5438601f71afc250e2c874f691eafb9a1a88252063870888bc49ccd9e60c85ba3651afc0b875d9e0f69f5910d01be54d96a5bc92d9dbe7c20450de810987c49b1ec4a39c71e988f3981762514d65a19446c0ed02a648abf4b43e7910c4ee677dba24382c020ccfe9f8c90c81c12fb4c9a675a66fe31cda40caf4f4698734d3d8d1041ab89e910b5c700634d049f665aeb12d6fe1c771995dd38518b11a01f0fa0a3ebd4437b8733e2628aff07aca3fa00d3b69c9fbcd178bae094903a04265bbc15b8b67701065c920096c17a21aa166da90b79884decf2d5da173f54201b7919997b71bb9ab38089fdfebe4cf5c88ad97802c2ca559d966c49b1caa2065e3bd18cc909659a7da867a78ab4566445521db5e200f5698186f40151831a1404ba072a1cdf1ba4319e93458fa4db89c9679a2b4bbd895ba381cf36372b3dc9bb3500e6dd863033007b6874ab93f0e2d795e650d0d768e9eb827b654bc44a5cd6b404ad7d646d3ca8917eb37868c8174fff3eb4372d147d9d1ff188e24667fa9ee0968a7366c10e2c4cbef1adc26085982e28947bbb1c907cbf8363e5a3c4a5daddcf20f8e96edd4c5060ce4b4d5102f4dbe2cfdc6b523f559a4b7b4df7592db24bb0fe1038d4813adcaa8b42055120979520b5ca66ce2dfb229f4be442cd08941a1591ad89622a79db854716fe4a94c5faf8e129cf94de03152c15338c9c68a0790bde558927e4fb6f28b26fd68c55a61a1a8906bd6302deedc17b2b996b196186e54a13cd3d7c2c407a9e284ec8dc8add9b6fd1049892eaa0451f25be43898ef8d567697653862cc6bc1628fa5672906520457d2bc90a4df8c8ba0d041aad13904562a33b797b1bf06b63a33bb5d9602cab57fa57b4c9c729ec3691d09be1f09415dad8cf364190135a7898d270a88c1c200671b8e1c767971b892a02dca774301a69c277232c08c7739d1a0ea04b05e44f67a84c08d1900afa40c3b704c9b44bbef45b57eec3fe374b339c86451ce7300f0d58c0c92cfc22850e9c33d3806099e4dea1b02589b7d8a2713799084703ca7f4e5ac476d70fdc7a6979f264d810747871fca9237808392b5bb4ca721f4c99a7a82070c65e7794c4f35bc4bd1aa66fe42e76a6de91544f5e6a63d7d77c9e190d0f5c00c91a5bfe856dd3770c5e8b44cc5a6b145eb348b578e9e2c74c6b9abf4e4c67c473c8e91b57f595d54aa527c8cf69020f5c195dc4f3cb351a8ff6c5aa761bc46aa706c7a7d915363791a760476223edf30a6ce021b82aa8a7c3aa20603d009d37640a3d91e0e26b0c0eac4d5165ac679aa2884cec34761f38dbdd1dd2ec38dc3a31b4fb88812546426777cd3f8d24dd4840b301bd1f0f7945cdc8efbe6fbaf30fcb3f6033def57b0520255f1126e4605fb55d2544a1d3df0f77c64eef6a49dc46d89373c6989547b44d06925186815edd9cfb0197be2352f67b36f602d480af3c620ed5a206320a36b1529ce2d9b5038061bf04e5de27b0eda846f6623c4a221fd76652cd83572c9f2391b4600e92d9e3729e0c005af50d79013f851d7c98144ec7d9733a329861b046ae76e4d228129d8d46fcfeeea1580586971509264008285f2bf81c6dff8e9b3efba76dfd97bfa8fa2400e1b00a77a75e2278d42852a52d3037e8897d437e1afad513ba13b24821b0f604640821f003706f93e6f1936c0802995ec54d54ed25b18e5a9e4877c72b7e3f9b2e61f8041f3cdbdb019f7f89a0e1af3fbbf76140ce2bd03b89e16b36e0d52b1704d020a129bb7339b01ac4398c614162c0be8b55792b8cba87f93bb602ef2ae0f499b9191132c82edaf336c43a81e142947d037e217cbd06c9256fb496e75d4cb0a48cedd2cd3ce05691a418996e99ef4734b59626ff43a8677616ff544f830985dc9ff8e4668a5ccda95efe497fce8b890172f6ed0d254119ae31f69affe67fe8251055c84bcc7c1cefdfc6a007dc9049518ae28c4e559c733f96acb5e5ce305cff268f0e234be81045a5e9ec4530f2ebd060c813ccc892bf88d395782fcbbea4ecf2f9e95f91d090f47b2c9d1ca48058de4fdcf60ce8ade0916b2cfe757f6feb9d2bbd9f2f66990ff89d46eb16f31827665765909e4c5d2dc21d2706cc273b9e69fca00db21273ed7c06effd3a3de8ebe8506f5c5626f40927f7b3061172ea62c176c449fcbd5c9f41e30c4f8818017b9ef54531cf731415c7e875f283e601d9d3234f4fec46018ab325e6e9fb0dd8bd58f741a7f7832f8bdb81eff1b8e30bb2d955ac7c7643ea6d1a99779963d6a4d811629cc1975b20f9af9eac32c67eb9b67ee3e18d48ba7c6d8cf7ffdfeee7fba579caf0ccaa756587dd14e6aa8e49e8d79062aefa82e723228a2aac0da806056f60346e1502c376fbc55e1c7223245e9af5b09312f819981dacfbc1105996e88f27b6d2a260f2b6ab12d9b3386f80d78b0951874f8cf63133dc63bb2ebbcd76fc2e8805d8592c5685ca52edbd920df6a4b53718ed2d791f9005e00d1ef679edcd3ebf1719f808bc9c700ec06ace9efbc31d32fe3d033560c3a88cef583e0e2ca13b574c13fb9b297b9d74effd5a4640313abc947bcb48235e330b49da7720cf9fcccd159616af63ab41f5bd0478a8a602d9bc9345c76a1f2c768d0070bc2df3b9762e401c97e8bb2f1d1c6d19058897f729c16fc1cd33a788d110cef8c3e94e29b425e7496b49b33c89321d1774aed8e672925c966d7903d63a11b64323fd284267a03a1d5fa3925e58e785e51b5fd4f6fdad90eb72e7815b31d30f266b8c2f81da0d6983ab8c9e22618eadae426de7031deda7ca17804ef99e46544911d6a769f6ac7b23e441bc96319da1c8323726704782029267afd29635a373c2f97ff12136392efd0528bd44f635503d2b6a7aa3dcb7de011973d9547c7339209d46e40af776701ef86499c7628ba36146d1e12433b552fe630ac42bbe30669a6e871ef3d47e93023ee883ff5300c865707eec9ee9a041f818cd4188046798c741828b83c06ba351ac3a22b6c5d6e905d38ae6e37c40f0eab05871c69e0fcd2a190eb4311cc53219b458caef1b33cf7a71a8e80fba422cc9bf201ee431f7a0b6e77f411ca25b0cb22cdec8c9042bf84d26ef4de0256ec37278ac8722e894c203f5feb8ca2797a02f9d3845afbc130b3d9ec75f427cdd27f7d0449b07dd2a95f73c79ce9038fb03d2af94f4745114b63693ed184d4443adc1c26e7d5f5dc574b4ad1ef626146834229e5c9ed63185b7ee020c950d1ca715589b0a95cb61a2637b8bda10120a0335f616e821e4c2882b6a2bd8fa25bfa88924d14d7e2292e54eb4f7233ace3da17fa6f32ecdf3bf3a4794438f3b9f832e73723d0d393d5596eb48501bfcd3280e11c7b70a73b08927a43c4f28ae722771f07780a46e16bab5c3c937e23d8b55397514cb3a5cce89459f55b72b4088b91caab967ce8fb007747b3f38a3377c599d1fbfda1d4a7ae65002face8fdaca2b083553e87de3f5896e02805156e0d71ee5944fcc450d3938016013a8d0d709d97fa9948ef9207cf2e620eb3f2bedb7529e3901a75719cc88517f5102c4614d6253d2bd9d7f3ef1a96687742503a0360a71dc85094c76be8cda0590c17f0f5856b6c8e9c04d9816f8fa53b6c8394a6c2933e093dfc453042707d7f22f6aa79bc4e0d6fe2cdb17eef233ebdeb8570bbf30a952a69d90df002c1a0480005cb8a6a12713e7de42142ff5848b3fb7ca268e3e2a3b68876b6393064d894b7326f428824b0042c324be77ff4145704f90978a4a6277695ee7ac18805e2ed13220491286fd1039bf11f3b1610a83beb79f175b014af4ffe380c305f36abd6dad40e8e3339f4190bad78145bff7d0ab424c4e3724da2d3e20512c82eeb3075a5f26fed40f12974b1644aafa62b1e97558fc072255533367b0746defebbaf63cd091f31e5b1f0a80e5c22c4c34e1ad68eb0da9b6e09e9df2e4b0e440f8f10812b210395bc14e20474f06d54d54ad78ff27565432b1ac53dd1d64e3d7e9ab7f50875a3c330e759e76a60d351619070e8a1437715316f07157f0333351c899deb20820ea568cb675b71c669325247b479216461feabc23f2797848c42651ab3f4976d9de701e71d5df03180524ae6cb38a5c900f8924638d9271e1d4aee733217a2220062ef1ac5a6bbc00a09b7bcef4cb41146fd734fe0021f35c9287fadb76cde9ecbf00a4f8639982f1e20e837f12e970b55d1111850b10aa575306cc6c88937e41ec789a0f81da85df593a2c7ee0d7185a37efc0d726ef8a8daaa73a9f1b88eda2da7a2624a503349499692a1686c99990affad100801a63f31e2500cb32ed3edd8d85746625b5d94d4225971d6647cdbe4bd87d8bee7d788c2e16218e7a432810dcd67bc6a09266c149f3522282231aa885afc0dc742dbf2a0a645f40e10621d709016b6dc03aa38218ff47f8449dac78300e3abdc899cc161122be8f82e7d86395571c8db99ddfd8a92c2efcd3c0e9c084420cc8b2aa7d4846800d08002259b3ac371590cf50b80d50abae4dadc004c6fc387daf60d0f73e51a4ebbdebd725381e06c13d8afaad461d4bdac6dfb347e95fed32dd84707d07f32ca6244b938a2df10461f6158af9b71d3a2c966fd4e73dea272950b851a9846c02d36ca02d0b4c621e92e8d7300e05c198977b57e34aa1f504a2cca1f7609b776c55828c90c35a604d1f1af9e1e7815fd1dc88852026cd817d3d94a7aae6155ff30f6903703fc50f9ff9eb9f32fe84aae19c40179be7c0e0d927fcceee2312394cab7500696461cd20543469c71922e4c15f3a2840d5407946cb9c13cca6667b9bba34ef5563a2fd26b26adcee9eaad40c506deb068c87c28a25ce4ac7193fb1981c7e03c56ccd12bf8caafe96371002092766ba901f13c105ddae20b71281e304e62c78ef5ff360869eb60b84e979baf76ded16721e89990fef9757c959cf270fdc9007a92d0fe5860f744b21dede2b55f9f9facbed30acb4f27b4623add5a6a1bd44b4074fd531a3e8219129ecc41d81e185dc3b0467fb2b3193cbf141a8f30baccbd51f155ca065344ce171ace7d4a80585a11a6c073a9e3282a338de6fd909d7a9acd2c9685806d11f6b7a7ec843690d08de4758ca4c016eba7dc9475158dd36f00990189bd5b07a54f0256b2b59f1f41a361447127df7aa29af93fbbb1ca757b2b221c4ff0b4c464656ba560250a491a1fddf9344815e6eb19820539aeb0d3aa57ad05424a6be231b5624c67f5a3d28fc18442dd08512d909714dc8874a1c284a204882677679e184128b933d9e96c3a196bc83fc902ed313405ad257de8eaf0c1ef734314afa34d22b70319cdc419cebc698275ad5a7e0f7d0e3ea6634d65714e8183f24c12e8eed247cc5246843acba50287daac8dca394037d93e7e675890364fade86dcf4d2f603b7ed025608e698181ade8fe74a183a460f203a03301d1b6d687b5cd9af18f1266299f29bbebd3d393958679948eff17f5d158dd1457c15827d625e69fb0d0994f255bec67998c9ed902f3154110514b6f2984cf68f5cb7832b6808238148056ba51412fb86afb38d65140d111bf3e1a0d6b5febb1cf09dee7ff98f06b893b65b05a6871d696b1708848668c8621a0858a0aea2316c3a20e1e9346114f45f2c7ab3ba6529b167b7dcbb1f478316de045103127f2830ecd763d92e2372dc5be9d1daba5e0b5dbec18763e8256adcd88c32cf1623a6f54e8bcb99bad1a52ad75c164fdde84c114f6257022b386c038193941422130a3cfdfeb95c416c53b25ebe54777de417e7a79865af4a207329b3f84f366d0a115a3919490b917ef14941df2c6f27cd04f0aa8106e628e92c76b386abf9a35580c325f239808d13633d2359c95f935634f16862110ff3dbb64eda2e78e8b3408b41df9a142ab8e066798910c42a2e2c86f778a7c4c39fe0536876d398fb901f780516ee6f9ef91c001a22391747873aa6dd654e70d66690494da4be7e5a218b911f7c3464ffff6ab5c1ab906105991e9bedc147b01a25e2358f3e3eaa9f7e2cc1d9d06870495f6b00a236a19e17dc205baece553bc2f94b87286bdb113a4d907e11fb802b7fe0dc3748c6ec3ad468e4b3ed691b59714e2f8d78834f7b986706e245d9c5cb9c345d7bb1f6a1137232585459feff0a33b540bbe0f55bb801b70a63ee3423c03db16171fb19821ea326dfe2b6b058de401699069f75d3126a97a81111305bd5b336deb48c95a6c1d60130176523c1a6c1cc658912e047c54912e0da4c0514ba39f9e0cc0d06cb97dd6c367b37d6e4b20fd160b7ab8bf4bdd8fd4ac9920bc6742307d7814a0d64e44f9b0270d492111e8cfc43606b7f509b96ef2e9b1afea5e5b380a4cdbac54b35c5d77e0a53649601884b4e9dcc7265c8e498f944bd43268364c9c57306939b6bc52edec8054d740555c151c6b366a55014353356c4151efe858e441b2afb29f095e4aeeca0a9ac9c91e50b5616c75e515fb0bf67b528f3938124b02d10729b82cb0f88bec6836f3e3548a278eaaf5e6adce52bb018bc2404dcdbe27e4ffbebb16e9d44cbeeddea399ee4662674a28a2ed795576d3dcabc62044de2618792823058d130f0f1509dba8dc43108731970f7dbd05fa5ff17b93b6ce01e5141f63c36b34d11cc29939564804b9cf98fa249f296e16bbef9e1a791500b98c12d3713c4ac9a4cc2366519616a1fa155cd1146e33d8d35c111406c1e900cdfeda1e2bda1602fdb3479910d1254cce02482bdac45fe2188feecdf23492ab460f7765237debf4fa8f7440004671866716f439c3939aee079ae57790828fa349bb9d263a6fc53de1aa65f7c8343b6522fc59a19dea1efc54fd92e99485c3eaa8bc392d5adbe0d2c742cc64d08d007bdd0274dfbd7e4e16846dd8fbc017e984929526eda1fe97c23e71d8381d45648df37bf40dc991d9685a022fcb2430f73a8d1b755e18dc7a335c21f25552c127023ff180623d74fb8dd498c7c0c53986b17384ca4e0b1a67b96217030aa7a24d6c39dc41cb2103491bc06e0cbf638e0967e4d8961d5c5371bf354eb9a6a7529db08ae3ecd5bf60a94215cac55d92a107263f92e2aa3d2f82b120e70737471560f7b132f5d6195b82ceb476ec9a9f86afcdde12742402a5c9b227e582e934e83f8e04cc31d5072c195b491a00ec454738a82b297e36e1b334539516d6f86356708a0ad91bd17a3e2861af30ca145f18c7c98b646ad40de9478993f9918c256e14240ec424ff2200c2c2e9ee77c057e3cedbc9139e2b92032df6e4764cfc00cb65aae672077e2949452beb6341536ef657f79690eaac86aa564a4ee53279d7d0d1bed754bcbc7a245f24a7af98b0d3441b6a1916bf360ebf583cb3968289a564e3bee2055ff209388f81058b852e52243c1c49d7bb9553d1a7b649ce25e6c4a9c335932c39808f37026ea32410be16d7f918bf9d65221268f8996c39bfde4374b02342db7ac237feef2f9ce33994f69f633361f40cd70a9e6550e6c6c6336706df0950c26c55525a9f44bab34ea4a8afd1217c9e9d685badc00a47e5b009e6b15417f3c4591815b1640ce00f2774ba163c94449cef009505adc4ba48b53c434ba1431f1f818916286484b6ccdeb4f76c9259676a760d0f2e9c4695fecdbcd208b0e41e48ed125ea7a28d9be7c7e9565f5e8c921f2bce7c1fd8b03702d2831c039864015159fe7ea8654fde8f5fbce48815bf8955762b616da05f0e0887f2bb9f543611ccf635d94c3a751da38183f71316d079c35b84d7a9e628466c05a04bdc88d3979fdc3fd67c08e578541e6bade0e3161a7a8670a9d294c18f38dff319506a02f9bc8ad80b10d6cce1fb2e68f083b03d83cc02ed8640bd439808fa0e902e9b1bdb51c9db84a41534b246d4e5c3ca981264787129b1164a8d6456cd57d4187f155311b785b7f2de477dd1147a6c114e445221a6ce380101048e1501d44f5898136505f6841ce181730c0e68bda980f9804f9de6be6d8c04235a470124c35c3275b6b78017d61b3f6b8d436161f09091c9ae7db61feec1a27c02bbef8b141dfb41c8e1e268841a225e1fdc6237f6fc61461c18aa4161d06920ffe6744c87bb25d75a6308343d378e13eb44ee71f45f81b16a12a7f16725efc8cb585028e814547180d708f0a2c98d102e0f5a93cd99ac08949ecfc28accfe672a8b9dc0b44fcbad8fd1d461c76a4785d57f3b04ad5f994c4f8a3f60829484df216ec4399961eedee8111bb59b041e9232cb2e5993bd508f2dd3f02caedecf428c0189316ba87f4fa5819c0a9da6a8396dcf9874c9bc4455af3a5263274cea2697faf1f10784c703a3a8079ccc0f26eba12497080e1e73aeb9a8f995a63cfe5a93ead4e1d7ed9921c0c21b78af7ed9d9d516ac8d00726dc9d5c7922829ff2654d64ac3df22dc3e9f27f78b38f3f6a80d63e1ddfeeb59c42768c893168edc5435f85e6a135ca7173c5eb892df138fe7099c7c8829fe128ef49ab75ebc582d3fec530e87b688670d2b3f51a59b437289555cd744da04f10a7100f8080d8d13915d5029a6771b63da6340bc2c2b6cf1e7d87b1df146480e60fd6c53be531c02d7f31a4c50820e5319c61b4931c81b57f89064d8a1f5351c85386f431286892426ea9e95c099215f68c23817d38315e580e39936975036569855f2024f0f339652b4c51e85c75740ae3d9fcb882437dcec022489ba24b4e6993678fd4c85cb6493e69b4ac192fd4f77afdd3e09c51749d9b93a75d90c5ece0c6bd9eaa3a37a5612c74ba301f3eff94add6417bd2204c220f90dfd1c927d606604f87ca2dea73556214ddea13d917414d96099315a11c3d302b2b70961aa432ee50dc165bbba49cf4481907e71258e3e5a0734d19c2f008206abe48c8ba99067c19fadaae9a0899c96abd2ba3ab69162c91a215d5da5881e614f751c69a74c5d8ec0541a44321a03bf1bd64ee391915cd5038d361e37ab2792ac553080363067d2aca54e2722fc544d63423332d8bf308cc08e3d08c33f425ce8e346d51f7e7d94c3cc1d1b7fa20bc3b0b457824f69f89454ab1e1384c73e0e64fd7801919cef697dd5c24df6385aa71062d7e3a04952949e71af3804bd0b3d400cd8cf99a4d4460f3a05c4942d15477e4143f86439ee2be46286be9d90229b1d7881036e0aa80270a7e47164d5b8fea73ffa9d7657d06ce2b364414c833e08604ae54943f49c1c4f7c89eb0bad3a643f4a7bbbd4814726d02b7bc29f6731ed9445a2060d0938178914b0beae8f7e099838356247ea574a3f9f003858424155aa5ec5f318ac715bd218531d2626e1eae94eb99eeb1660916db862ed52f8fbcb132f1c00e3c1376729e78b6e1d22f022392c56cb67974b5fb413a1cb33ca5dad651c35be0ac24951fbaf1c6a8b0e93eb9714428b36af1a3c5f6995523ff86ca18ada0e1b6cff976b0cbbdae3162c284d0474c76219d3142c86f1a0f83351ba91fc487d863d747aaf3b0da2d72cec230043712e2913822629c2d91c62a5338c173fd7228d4769866bf789bc1e85307bc81fa78b8c26c24921b415ef83f2ab4875353c289c387a9e613003ceca0ce96edc0430c8f7db88a81a27067ae909a2b2cd2c8b292459df237653802698c8097552afb80ca8ce6c782812248c4bba259aed93063395176eba28cc17072f5e74c42e4ea9081ee104b169026353b9b976470f4c72e6fddd8f1e26616b5677e805d3d2ac71d51f895544efb86f0793ff0c8b699ea4626fbb2336039e2fc34ca11fbf1aea1ff22461fd60bf7d86bcd6f8f9ae30059de7583b8205e240f05a6f43b61d1a23545c3d531801e60f5c0794345072ea829a7f5084003caa1404b90c9ed84876b9c9a7499308a3e0241a4715f779a33cd5f302756b22df7357fb1a26bab7bf02d2aeea1db427a784fe1a9d878806a85b1fa13fb44416ebdd04746a4245ac7b06423ad9b21e193d5bb0ebd73db5fe8db522014398110173f5bb90341e0d9d38ac4f0990d553be5215d6da88cc15fb5145562bd38466a40dc320aa757493a26e4bd8cc12c5200f4c601ec99795c9210c916bb8941452bfca5eab46cfa7f1f174f4044db3d25c38b3349c5675e004c4d29afccbaf0a363e9610678b55dff82947071db794d94d186aedb80f6ae695c2194f3c7cae24d635670899d996977f0b4e3d4ef54190f2e9eabecb611d8e7836551dbc35009415cbba97e50b5a864094a55b5f20bb49b14d7e15f64cdb496bd219f8267b205aa024299fc8a5df6a7f3f7e94a12bb25854e64b6e281aa943854dc3c31a41a6450c3e1207acf8af9259f792fa99350769d58b6df6dbd1522e7596b6677b66a3f76be6f030e06acecf5f68d8a40c61d20a2bb01c22d8006f467573cc7b600f9bf859aadcf1efbaf2efe1590c3541f316428d05647046be359a52d6328fecd3f17c93f8f6847b2314e552497b4d47dfa1d5c91966a806d1636ae7df95be10bb5dc3d0c5b3aa1157770cd26017947593dd2910fbee83996c68d2ae693c959c7a732c8f3d5e9298123b66665c4cc6e24cf14022179dca80aa8292124653762758480566c448f9079840b2df1d0b4a2b50f644264db1291326804cd95baa7bd0cfa24e649777d366b895cafd16a72b0208812025fa3752c8e5bc6818f5c8ebda7fdd367d7bcf3558e3c7e05de9430f1e5672e579c507279ade88a4b255c3e593f410732bdacd11f0d6ee8464b71c0023ef79ea2a7a406dd5e1be32e97c074e302f73b5d99661b0c36efeb5c721d8297b8b55f92ffe75640d633ccd3a44a2c29f639a6361e462db691cf6b32444d6fbb6c88dc21d63b8db2cda2f012b7b46bbe2b253f4a63e8d0f5c2f637fad48f901167ef8aed89c56b4e7bd5f515c605b809ff0f41fa93b1eb003b85f02ae24deb775aff054ae1a8ef034aded829c7a1f7ccb718d58ff522da9f9393dbd1f130bb1dd5cdb6ee61ac3822826f9f61a678d6f207f3edf9538b5b9014848674b5071b07307b715b5b6f93c871ff56b835457f63366df690e1f806609675b9b6db0af3571b0f04420ca7bff53cb0fb062a0c609fc715ebcad55fcd9497e4e898693c690d7153509f252df84b7e9df9533c8d022876f93c39e017aaf07be45c268241c50b613a830b550dd77e7c9180ee43bdaa5916cd223c1566e96044b96e8053556bcdbe2a5c3b903eea33055eb817d7c36aae87d795f168247f29f19c195617b2e40cd6179ec4bbc577f86cd74d46fc1dea01a4f31f0cf07a7851a87fe99d472bf65330d376ea7cd3a0fe1071380d19a87dfe20e16e7399a10ef84f1560b71b9edc08d4e2d22a6f78a89e383b1edec4140b84191317eeb39055ebe31782fec44a591466b45583cec81642b3a89a6c32881be04c743bd5483b7d9757e0cbc73c3eea1d1155f35bef6af62a5a15b48e58589cf7502dc6432c0da755dbb264a596e9ff0ea95e14e0b0562b6367b27442107acd36a6b2ada732efbb8346349a3cc2b148f8e5894cddd2dd0fa54087e800e8bcb067228008870660f41e9614fc98335cbaf334c659e3fc72b442c32f9f9e23840b860f11fa5bdc2bc99beeecb108f33be8fc359cfb807a56b5b661fa08b1cfd2ad182b3eb3d0f32cf68dc0ba23bff5e92fbcb72b133c1497be6a76886014ef1fdd09dcdd1d58094a7ca599d2c0fd99e351d9695d3dc6462e97fce7b823665d7c94140b893c51efc054de99e2fbd28fdd14b80515a91c98254199a9724b94c61a4d07c3cb19c8ebabe8e3d1fe79488b4652dfe9c0a491f67094c6b0602ac27d3e8e59a6bae0f71147c1d6da1bdbf6cb046d5f7d33d07d225e9cde1d3c43c479ec8428192e2563bee28ff769ab87badaea5a9cb2478527cca698fae8bbf59ac49fb63b13e712d1bc2591f2ab0abdaa9d3e11d44521deb07cd5705d453de9339dc37128e7ed3a52a8e8f8ae270ff56b3e9aa27b596aad85546b9f393cd7e05cf82d89f46848855f0d46a375b08c35fa8b37023f32bb39202bc853543d2b26be7e00dc077438d16d4adb5ca5d8cc13ebc0edabedeaab6fb9229a9cb80e6a4ca9af6df21142f93cd4773821d122a916530cc504f05a0e789dc6b5c4d45c0c3c587944c4178e3685b2c496e4f523d2499a8ae5741501b48646f4bc853c7d6ea48356c717311763a3b16e1e8119f78e78835681cfcc4a1d88308e58659ced19cdcaf467c6383ed4f522cd1292b6d920e4a3e8890df8d0a0beb3729cb54ff02580747172ed7e1a816d35123e32e336c26cb58e608705a78c6346d54f105dd93b8849f1a13da4733d8a46a65b54d2af3399994de7fdd3a6f05446c6a8ddc0e9908c15dc15cacfe11061ac56d0501bd066dd121a212dbc5dc880b641977c325fdfc57cc020f8ca1555e45de688208b5d3533425e706c52bed8dc4a47d5dd7c4bdab9348f25d3cc78f00d0dd9dcc4eba2c731b3f2a7f822858930eb8f8a5b24c8ae664f3b736fa65a9d7cb44e9d7f287492555acfd877544a9b8e83dfd1c2c53a7eb1f48fdb7adf90333a803b247492f5212cb69552b7c4e185fe58c45a12eeb2975309a4cebbad6fdcbe1b25d57aefe23ac25cf7464591e8abb495b4a31bb4ba73d06e290a70055cbda55a452ad41dd15add8b1ce9f8b1158bbf5a5d183bd5280393ccd0e3a42d1779d5113d01cb1539dad3aa061b9e30093babc42a2127fe57d97063f7798610b747f02c495b55f1f4a03a38c706466cdaf789eebf82cca093071bea8499e16fa2c9b57c964415419fa240267b33a50031f358b83de58360e209e0bfb43fc69979a47a799f8a5ba91165129a9f4f59e2d30d61fd6787640ae1cead847455f7d5bc35eb7f29ca7441ad3964ea300e5ef3ebba2bac00ad87d1886c8cc178bf4a13493b29332a54fd01f60405551afc63ec6f9fdff2b4a75c95d80d8d12210192192c6ba568b2d93069b8a8956c10b867d5e442e179426a2b88b213bba8cc037687aa499e0aece384522a29388718309f40d5f82942e2a0f337296f3328f75551eb46ae97225411cba07b7e4892d3331b9a570f154ee6cd99293d4e7d8525187915f47af3c6b395a6270a009b0b258fe00cb0dad6f668d1b71a335a551ec3530321c3ab8a04085574af56c120880866cb76a6587993ef5350a9642e810d2f4f32cd927575943b6834ea1785424080958a12a918d49d2a6be5c1a84d987a5fb3aaf54d8c51aa439b29aeb7d901c9d0107f42bfd1833a87713aaa22570a4f5f96103419c0771f667c33e5c4d444c38406c5562f7b2c145ef83ed86bf8913f5f070f3e0d047c5fd9106249524af2ce9f384926023a33fdf978a4c6df9a03533b524166c5c83334bf89bf4937ff87a76981b750070583c7919014c5835284a422a9cb317cc2fd629746718f3726df44b1c9198542d805a578327e6f1fd548ec43063922866ab83303c1ff9275319f10729824c1fc63a4a9d9b0c89040c238079202a7d0ffb2b69250761152e280c439a2248a70ea2cf3a5a03139a7509e1e3f7f59b602039686a616b4f5f9205e13084a0715fc243b0b8f36239c78bcb3f5fa7867569fc3fdf250e8246dfd16d9601105480788348641dbda7250e6dd392e8bace96ae0ec21c21ed4497eea5da02408dc13a9481db8e0f166cf55f9f01bdea053be32c85d1fa2fe860ee6aa0b9eebb58d8812cff4b624c8a3c1dd43d41e7dae38e94ff6df8298a7fc060102b0fc378415461f26c79721b8faa27f2bffc059f603501228034c3430c865ca3def301c5462462777aaaecb279418d7a7b7d688cc9574cc3ca50094ce3e4393100c51ba8d026eb1888cc988c6d6cf36d056b4e181798d92b630d43b25cf3f02195aa0d4883828a997c9616601e8cceecbff89af12881a496fce5ca5c218c76cd7511add428eba094f587f0fd928198e559c1583e3cff159b1f98af8d2851ee7191ed3bcbabf45348451c26d16910c2932fed577670a07b4308c53ea14ccda7d415d5498c8ace6a6460bad15ac5dcf1967edd2f14610e57630a089742f8a9b13e549ede8ec60dcc72d5360c1dcb25d3ebca23e3467b8830cb08ebfdc9d125fa27c2e588d08f20d2a06632d94224626f56c524e9368bac3a26b52c9a6855d47d72bf04ec0044f52131a431468949f7251b3589fc7549a89b931e9df73ca42375802ee658763eeb567e0698187ebe5f7c15843b7ef6c2f9b375341ef43cf77afd621ba74be86555d996fe834d8c11c1c753d0789ba37669a0996e68b59e35ddf31edf2b04c571cf2433c5727a0b32b1d159f77c60e29e556bb81bdfa3b6b3346f88a0d55a532364d5fecba74da018e715d91c9128acae6558a70269fc85e1d904b20b0aef87fb5b1566f1123053c9dbe3db0d4933a239d53170007ee0119558b90cf63d961f5c15c6992ef22c95b022134317106c7d0c56a1036ac8e167b40d2a44a4c62cdadd5d3c77b74a513b7401f98c682b8b020215be21cd5111ef0ae699946c75f7bd5577ae152761ad8ba03fc9f2f097e265a379b954e2fca6575a2e60391e2e84fd160447724e743ba2571b85126a1c76fe514a1c3de9a781b2da681954eb25662ac8715f2e5069ee3752e78185ef019f9cdd6da7c7dc8a6723ec04ff8f543f441d2661d9a808dd914b8cf7530e1d6de2d74c6a9de31cc5036250388ee29851f0e85112066f01a04fe1ee405592049484b0793105390ab6a5dc7168210d89269914eca8fbfea500c077f28a69ce1a366a38fad2093d0cb276f721ffe4c59c1ba6126fa8ee071af515b3de22bc96b8dcf6a4ade847a01d7e4135292718a2d2e0add0384d7a79d3e56cfd25428d885e55ffae1f5556d0ee2f5587346c220e8003bf1a7916dc3ca915a4c8e71b2bc18b5edb5e934e8625bd7c7c29189d842c36baca0a7c06b4c1ba8f1a3579306f4caf559f016df8a5c5ae96a902a7c040f91e3c5fb9848df1702bf09e365c663061b02fd199f3d162d24164533affc4c8f6f8bd8bdf3b6c901e2178e43a27301a1c5902a1eaf1fb4f85e50cb5a2c85b4c02d20a4a5bb47b118072765f140e32963e40f5557b9222666ce8641b8bba8e5c592efdf3d38c801c5aaeba71183ee74c05d48de3da28b21bf531eac910008b6a7c3baba9e60560f036c32c16e6bfd7deb9d16896f5369d2579dea22aa2186a3869be52b3f636f34ce97c0de6d9601f8bc195c59288260b8228852f382aee59d7b668a1b763ddc3134f99113ba7afaeeefaf674025afb1db183dce84c915782a9228ca94834e4cc1f265b5fedb66504aa60d1cd82d548a830e8dea02aca52ec47fed7cd5d296e693c9f7ecfc67a80cb721aa7419db0ecdc2c08d6ec1401428972bdccc593043a5aac9bf824b70ca074d7f63aacf9ba6e72c9a248f78753b509971f0aaf4e79f386e7c1811e8699d46217d87a31d101becc98f8f8c757e510dcf7826f05b43f969dc401b814051845c36cff8c00fd1331ef31efb95bfa324c0cca391282cf75258585e3346cd91b21fe5da95ab2caf814a6784396b7469f4acbcb3603d5af5bcfb15c84f3bf4c6754d404feae786d2f176fe7783effc714a0f7910cba6f00bf6bc4ca2da359ed3734c5f28aa257b84e3a0776281ce2d7902a65c96457c311c89041abf41fc61ecf7647f0712bed908daa0021b0700b1b0c68bd720bef443c118f0baf120fe7c5fa213b5d23c017cff4692b9dd95df857295628c29cdad5c08cba7ce5ba53514834d74a4b660f9f1fbccca1e83e10e89ec0578c0959668fb436e51189053dc331d42a1607c679e4090c6017192103872cfb02f300d47343d7ea10117fea905532dd6b61630d189c501325e06b64d53602cad5aa31e1fbc82c2c8682fa16dc798b0c73216b402a849b564a524e27e620e0855102f3b91be8434700030511c12e84a21d75dd77f95f2f05e1de40da2578d4875106cc53a0864cd573bdac96f65b36950b00ab1cb04bb2e6edeb1f6c4ba90ee0cf09bc347dd955379566a24c742f3ce626edf6fb38ef185be73ece603ca1d3466676a39d692c74f04436c495d269b866c09281724f914bfcf6d0d5be93159e8abc18a72206403c0b50e5f271d7d66a047584f1039157516e222941ec506620025c6981f4a6585424d87751a7eb762dce3a2ea1207608e229a3c72abdfeb845a2660b28ad6393c294f5bdd3ba0527022580a06847c71c7d98f16454a88799e90a43006d69ddcbafdc6a5eca74524bff1002a2ca910682fcf5d991e3173ef3fe1fdbbdde64fe6b2a70ceacfccfde4aae81e146ae88426669d7a754ef29fb52315b25c48e2d84d65d9bc3109bf3c852af1a87189891c9e3260989ce8920dec1fcc835c959d088fa1b5483dec5a5fb397cd273d77942f7389a656c00fec3284ca0fddedb3ad337e8476c8b0eddcef591187079ba00a7a14e1405ffce9f50580e6aae652b7f12c1fc4d514c239bf2a95559663cc0fc85be6c58b023bac754404064b778205e52d894c70d6b31377ad734e5081d885060aca23e4dccd3403c9d535b8557279fed82c690aa12818c3edbb64742ad8f092b6046be6148ccaf2e0363c7d85330eee4afa7237802a6820221863e51e3fd6f9f5c3989db6ebebad5017e3490e1b5a9cba19b40dc91b49f9e914a4ee728e6a3213f33e1c94be3eaa61781603e32ba41ad8a4c5afd5cb9a9f3579e4eccb60e4f8a7739b1432a81a63fc3b5258f0e791d65c7cc97c94bbece688256fe0bfcca66596bc717d75338d89925da126fc4b43e28b0c02d6999af0c197f7ad3d15c4738aba0092ae1952f60891813147fc45c8589f74aecbba9a0827c72bf14fc06253b1971f276c75eb4c1536cc634eb220177d8035cca3cab29519b34cb2d55100f7498bc1f79da8533100e7cb7e9a87b35b5099955e5b47300d6a5fbc880aa020b7096a66bfd34ec19ac5467fdb43552b501ff4a4de5548a9385aa40b3edbcddd0c6fe3702c6137d40cc2dc28974692ea53048e60ba53e777e7ef3f4ab010c57b996431925994d498b6d43c15960162296a1ccd0a4499506644b98dec60f25e7e3ddf029ff067f1b499d4537ee4627537e3b3adc02a3f46cb0588341f116ab5d428cfcd860aa4c1e0d8ab668e0e01471d9597e7e0c7674e242dff7bf02a161d79303d0267d6ffe279dae4e8d0a9be9d9e6c057c8678b79b8afa40e9ea24387e2a217f6c2c4a1e94933567a43242d3a9856c3eb5a175d445abc9a8d09a7b5c586b22d8d0f535a88b6c64b46b4c2f7206ae474fe843962056d41c58a70fa86dbc83c875a6ec6f9cd9a879084ed19a3999aff795a996c9c084b75b33e88f5f374fa92e1e03193c6e45a02ffac065d5ae1bd7d62232c4dca862914b2aeb724f14f498f11c528cdb68b16bc46482c5e5b3d13634d3ec8e67c3e552b4664fbc3cfc123082dab5e560c1aff0aebcb9f4b6ec8dcf5b1b231baa59c32b5578ccc54b109e43e7160ea7207805dd334c63cf758e04362c69d093c1b68c6ac2de3bd707cfcd8da8066765ea5bdea941139c0db092dfffea54808cef8a232694177ab17d7f5c97289389347b83cb33dc72ef157b298766295c4111f8ff25f8f1b38ba00af6074eaee86b17e192db2932c3165eddaa1ee46d355232ee844456497ba8e103a5f6e611e7fe72f900798b31747a88eb6800d09bbf09d34bad628dc7a1b00e1f81ad874619b5f1a8852aa0aef05fbb5b5990e672ab6332d7a0fa638c14a0a0ee024549459dad99a125d15af537c977802974e4944d1000633b8fb48cff0945154ee69047c5c1050e0aac8a581f89c3447aa458d114fb42239cfbd132abdffb87b08e1e15f3354e4944f12f4e85037394897ca3d0a512183ffca8cbf1092e98d3abcff28f118a3a72a0c4f6ee83db674a0647e513684846c39db1c6d4f061eb3b6a147be006d31a3eae30bd86082595a0f136d6993e0cc071c81c9b9112528383107648ce3aaddb6e7be075ba1f4012c601a514368008dd85b2ea506d8719b805112227ff171b2a6c4aeb290d42437939b2442592cdfe67a48ea5227388026a0b21283a8f870281d2fe97973738efb8c499aaf3ded77fb2819a82efade8eba95330e93e2405fb3b11cb012bbbe3a2197220eb55d9de7d63af674dbc1064eb63ea6facc21cb214a1fdf9d8e96f05c7946005ff67294a825168fc654335ba99bb381fcc4216b76da9ecf19989e7097df11dd7e6a4915717d7f8b2d924c35506e02318ee603c74061c9fcc813ca58c1af09d21c4ba754f020bc4609b728edcf2d7c2e4a3248dd5405719c9e98df80bbb63c40c8e8bb99c233688a6dda36e873189e228c9574d37099dbe2dbb8f318b86af32aae615e67e46d290b256fd26a398d4fca71691f8ca390b9c7af221f079b2e02e1072bea6b05be4bcf656704ff2a980bef27498ebccfe51e289a86fb44a365419be647ebd08c180e2d256aadab69c3080a499f9030dd9b96f0779117d964c0c8d0936c88f132cfeee15365592e46daa6f1f5c884264af51f876c169010a69dae6fbc3382f048de9bf61338b73f4ab5f02d2ca32f84f91c45a7ef25a34a9d4daf98d36a26a1592cfc366999369f75d1fda2262695fff49a071a14afa7bbbe72385e9be60a9b21df9e09f435e88c09b615185c564375cb2d1433dda29d0a9cd5eca59376dbe4ee6b4f467e2296a18aa4f81ac02213296990b283a1d36c579e8dfb8f846e4330d139c598fd94cfa777f3081f2bfca1cda029e63da1b946f213f9da70cc6e29afe479444673a73ba945a1802fd87f7f299165abd1499c79da2684f26c901677ea1e453cf7a39e774b128905eee31cc996dbcc17c040c9359a9f1bac8dd063436c783b225e722f0312bf93730a1f5cfe16de425ed030b6065e85f8fdd6d7734ec1981d52205fffe8daf6d5224b8137bc05981ea3109d4fca1be0c810e99307cfb8a4e131f1ab357b785ae0425e8868d71724b79f5a71cfe61b8cfdd9327de5a1431b1bc77fdc804339982b96e026d4ce8ad1f53cc98001121e2f5225b4fb84a22469deab4fd2780d8e76b3703d467a465634aa08f79f01e34000bcf3f011b009c28b42c75a45111ca824441c147a89a7cdde1b242cac3e6443f8c2dbe8a20151f4962d6f62364420d65fff06d5f034d2da4b2746818b286b09ea7bed83fc13b256318e407c81384a7063382ca1f8bf6bc4e48517fe08c6297b06d0cbd6019414690a29fd30cd9c4342854e4b67f347c861beedf4b02b3d424cc0ac0a4b78410952cf11b4f2166f90f053cbf38fd104af7161ee364fefd5211423bbab5449cc5e7f035f2a2b1bbb313e110d0ee4d1b6b58d44c1c3de90c0fd0beb54ad1dc064b6b787c8ecce420137a3ed3417039de9765813397e3cd8bdb4d0ff0a2b7d9a6ca0031f1950fd96f9012f35f4973175c656443217d9fc8dabcbb458d9f112ec68a7987a40ca812b17fc5bd67f6ca70d89df2fd88d6cbe9f44f2f924f355860541ae6f936a94a6fc65b636c0516b10c5029573b44d9a529926d1ee84abaf68daacb8d4af046de4cb1747fb7a8b9d23052118b534a81f5bf6078bac85b7666cdd3e3f5294c198a43cf0e9edc3279d288bbafca0caa7ba44c50d432ffa00869c0d3e1a859c73bef7c9142888e02a6188e5aef0f762b117949480a72aef49fd642f2a7a2bcccd4a20a0080b249aa1608392fdcb81c6249d32d21e83c2d04c65f5542243e052756976bf7243471efcbace1a614f4ea7ed4de64a7ccf7a5900212a51db03b34be2f41b563ddd989fd079a732d01569dbf802727ab65aec2fcac2df7a1b7cfd05749930a77644d4d55c136fe5aa47a2dee5d9004a373c0136c9e2385c666fd415dc1a4e4d3f0061d313db364f6294f60f2f0ee974bb3662bf87ed336634c4531f8b3d051a9fce19790eb924b833ebe8c045ad312ee7825df5b41d45516708d2f9ed2831f2ca1739cdccffa17a318e66b94011c975c1721d8b63593596450b23d0e9b6cfcbb9ea1c1285abb561b1cfbb96417c8b4f922f6ab7f2b0ebd44b9c3ea59178d252b384387f44bbe22e05b00ca713dc5e67367f98fb8cea641c7e803d7493737022d1ac1da6b41f48839342eabb19687401211ce6f77d45871e878f9d2c18b1b837d4611640dba82e3fcb827ea1bbc432541546c2bda81a01879bb2fcff748711895c0ce8d47338c150faec01c85fd8f7b8707f37707474b9fc378479f3f5b93e593102acce44de8a84fb2c62c350716904f1ebe596ff6874ce5cc800a16f6af99dde74494af689128fa7b4b0ea2d2b986504cdc4f64ee8a74462ac3b51824b59751aa3191bbb0e70af3a16a865604df4356e8b3157aa03b55f192b45f72d95fc552c96e033a827c6f5937da44d6b16c78c5d43824603f495ce7d309ac420e9061dab768eafa78c0f7bc4cf3e57ef391bfdbf7e74aaeabaa204e8152e0061a6c2a71be0e8aefa0fc6aade9e994bb5439b7ba8de53456d61a84d1d59c867e50ea969df1cebd011d801e9a3139ed3bc2d0524f99c2234461e22545a408d539650b381defaa4f9a35106aaf89c038acce3623a0e9d0eeb3d3471246e03720a60b6c748f3368de922502319eb716bd260d74cd03a364c97f76bcb97c70018dfe9e26f6982703eb3e3ce831deb0a2dacc92b5d8576e358da850fb49e084a202fdf5d97f57dd4d145e48068c85a0fa46ab1836e560827aa32802c61544700fb0e0a97206ee0dfbd650645e99c0a686fc7b5f2d9fd95f693ff61dd34ff208cd4f3c26eed4a6267dbf574c3f60e4aa680e741271bdc522d21eaa5ac46fdce781b6c9a380766e3ec3372c75f2c57014a42735d20ebdc007bc41dcbcc3a808ddbdd24f99af727c1e42b24d9d9505b3973164e6068ffdd8d172f8b1204a558a34d38a743e5cb88797a53bc4de440feec468754caf0e2c341679e6840dcb677103d87f343db996a5996a20e60af6355c6392af3b5c90ab0fb180c9a20960d0e07efe1df80c242382306ef787514f4c186bdb9a514f76cb587dd556e793f36878c9c6e725db870ec5e7c5d06244cc3e4e6d1c64bde84779756cddd44f66391dd7077618d51985ca45cb8010681b75d7112f4e423a217bbc403c3e4432225afb5356ab59f57109fb3ebdcc919dba08b8e073123a789de7669b91a50dbdbc5b866b23c783198f4ba3b555c71f2ec457c5974f358a687724f111532c3d9ecf3c947ba5c1476720ff0fcea5ee031a0f282ef3e319450b873705f0a12d108442166e4494f4d82f2d89c3288c7b7ac5177037682bf9d0e0f404071f1f2dd1848fc8df11f88a1f5be4b4fc098161b2233d17c92acb861e5e78b83d2add5c58dfe591193fcf52413ef7bc7fc3102feb4c87d8f0ccdf64177626125f357470ee2766f9f806e8ca4bd79e2311136ccc045bad10ab2d691de1cc6399433e4b1f77b2eb47f3f39d32a98f2c96a93b0303c96fd8a2d8ac0a37825f07da91a8d6a7a270f0d43717402620713050032c60267abfd294e53a0a9150eb65445455e32e6ed4dbbae7858be4d8841e28ae254101843e3f887e369e7668ac383c38924ea08a1eea05051ac7d754e87324c60e805bc12766e826a6c7082d65a9e4be20c7ebb4511f6a642345129e55156d5d57442846aa5c83387d82cbcd64664cb737a171cb1b6ce97b174e37166aaded1ef2358695be1c94ccdb91495f6692e1114ea21bc920e6380fd997057788767c30cfbef53b81a9f2289eca0a4804b048f7150b844cc93e49c3911e6257f9a3ac2ffbd669a9064ff28556c04215c11ee35d110c7b555b0ddddf12078f2ec44d6d95e3d0686098a6848f3c6a78b55845443ba6b16ddfeef16f8c6015328ed8d5c29ec088ecca5dea9d0cd9b68fa4200671e553c2955620e651ed6fb4efef1846a70878639ca22622116f5fb8b7617b348dd54b60fe3901027444abc18337eec0936a3a880b01a74cd97ee2be11d8972df3af7b2f2d384b22fc6ff64264f698a3d0ead5aa4e63d913c93f13bedb0ba8a5f7af021e9db01fc76c5e4fe944ab6b7964baeebdb6dd3c056e20b70615296a5061126ee1c6f4947ce69e08c4b99c4aeaed95e91b99aa0336c2ead865cd1e507aba16d2f88fc695a47ddf946fe0c19f511602d98ffe48b51ffd11f3353dcd139bedd76969ce674cc201d681c2ebb7fc5ca21e4bc53864de4ef0f96c8c1513cd01e26982aea0bec34c8791f0677bad5ef617643321e164506ee4c16cea4a2070bf64b42002c34866ab19360ddc3557576f9add31d6feff031b49eb6e490922dca732e7a368e6b131910092f574b5573760ec0cf788a16a40dcb9bcb91e2e1bf455e3e310563d0fc8997bf67b76f03b109e46e862bc27da116018ca32887896bda74a05ea5c111daee99100b11665670fa740cddd8278ca3f9eaa110385b60f11db22479dc7c6cc85421681278dd2200b23cbfa8fe618d275137688d335111b2823082cb66c37f76104be175c918f9fdcd86cfc34cd8065524ddc8e741195f7a9785b8b791d996dde34e1ca109887ee18def52ecfb4df4b759483442f4d0112a357e35321dd1f325e5e30eb3c97d8230d1e84c5ac2295f54f12608c78391ae7a69f18c647cafbc20832684d3c06f12e8443291b8072c4c99e1b8cfc1720bd5e19beaff324ae5f874cd0af84ce694d00dd84bf894122bf6a53199d2ea8057db37e14e9a057be7a7dc82a36a8b50bb9243e9c61b9843cb8867c50ef273781088640da142595f80997db8b485275eae9ca772ded5f9fd685ff701d2ed3314de89634274ffb921639ae2b3b2e6dffd395ba0d77c0722060bd2d94ca323b937e2b8056ac6dcf2afa5ff00be75fb102e9e7c947066001e80c7a519c3503a727a3df72ca339f8db338a7a6e2b974d68b5e4a2f75cd283571ed90916b2c619b09e3c9f4102e74fc9b479171f05676c35ee8b2dcac462c8c223fa371d7b3c1fdbe4b04d20ec20075bf13ce68a7d1fe73526ea8cd829f32d79e0b1eea0ec7b7afa64fc135f9c8cc002d2e2a90351102231ec9a2962fb96d8d8f4891e2246ff9130197047cad5adca757c74437d21bc9c805a837a4b62636b5149e15c02e26ebf6ac42383ca48cb489a5c08820deb1bf9a309e0111b3d42ea3cdf09b581346ea12c69a80cf47e7b427fe0d5c641ed1e23538b315cd23d267931875064a8f537965db436f47269a60d6b6e1464596d9179e3c82ef4851c9738c34062613ded7d4cb8298c60c9f7a96f4b109bd75dbb9e2620a1a9e8954c3c68034ff67fedb3adcea225a5d96a5e03dbc81934f53406b997f454af585772711a7b3cc9d3535f66c80d2c12cc3e3d1813975321fde4493058b784769d137e9ce2a4a66e511f2c327e93334e40cc3ea1fc6f03c2aea330ceb0dc3e340073948f5d991cbdb0d2715dde35a11949f013bbf3d4d90749db3b2849907eb584edf1cbd951787be304de7989053b9e384ef79278f749267ab4c62c946a260eb9e66fa223992b44e564ec5c45edc0355c3c0370a9524e4429ed68c90cc30d123bf40994bd14c25a754510940cf2ee9c14630de2b52efb7a3b8c7781f2ec77c2193a8a0941e37eca682b2fd337c74a4bb12a0f1477758d9759f3f07424488568e1ae9c706c6b08ba1b8b868a9146b9667f85e41b9b7664fb433d04d67ff32bcc966df0d2044ffe35ef26bc05ce25917c1b492822e9d4de0064696d9df6bd8b280e1dfb7610715349ed3ff83da7639edb2896ddb0ac3be364a6369e6449a0ec7fa322a11b6d66a83c205378391d746bf7e12bbb1c7aa988fe8c08be9a0d793ef08a8f3ba56f32589d7a2303d665c4568616a7e477a2975a1fecc8ae785e61ef148056da1dcbaae4f2bdb96d37bbe87894292dde0eec39c426a342190168a17698ca7ad319cee9ee5f26a0c174174588ba1b61959206ed371367f505759d6481572db2376a285d09ad2f92378797e10881072dc8463f4a2e00d85778c46642a0895f65b071b1803995420fbc871ad2a698552a936d99e7ee844b306c450cb021fd073532159225a4ce29179c47c506b9429cb28dca138eed324291084cbabc2c4b3c724d1977d463dcfdd439435132eff08401140358b5e7abcf00decccd5df4012ac2530a2990c6455664eb910c589bd951c91a8c30c7eeddd785993d835d1000f87fa1f046c26fe7a034b14f9beb1b1e89d68662092e629c149e423a359f7659696f27457496e3e2289c1d84321e371dcad42e5ea4cef279ab217b78dd2df8eaa6586f08b01a9ac77b200206e079f6ce107464f3568d586e9375f2d383a182132f99ac15ed8c2d15b75cc4e6c8c3148e6e3699c12251a83eea859cbb988ae03ddadcbce8b49d04372a319a446eefa6ee2da51c6342519363918aa982b18bc5700a4a5f8a5c1a77f38bdfc67fdb089df5380de99ad73fd168234569e22957c80e214b52f698cc912a0fa817c4ac45cf732251e4ac8180c015ddf2728f271a0bb63b40ede29c23487e5dd5d7f3b75836693ac86ce070aa6def697a6b13062f5e6d6a0822a5143a14b28e60ad60ac30236ef71e96dcde405d68b3071941b79c0f70cae1905a78e7ef9c73c4c167743621afff5112adb2488ab866e35af5acbd6bb9e0fd2f8174feb0e3e3dca055ba9429705d6c668d8ed95c88561c50b84e8fe5c69720d7eed3a83237963f461e8627a0b1ff92d492842afb7a14eb436686ce71e43df5b7fbe7c4948ceefd6dda4509c836d46d619f25690c2b8cc1f2a69a20456607c8da030ae6f9508d85508c22eebec9e79505504022674a2a9ffa6bb1e54724326aa91d41883c53648b077be2e9e5864a4d5b5bf29bbb283db8a055020c877aa42b08a957206b8b547f34b5b692aeed043340a3332b229d6824575088f84851072afbb648f8a0ed3ab031f3134107ca614880eab57acb8ff718a2d23b6b9b0354f76a1479fe00fc676a4659bbb04e03bebc62c60ad9f56b6d4b5e4c959e430947d35a1f75638c200c71644cb4485941e2756dc00f9df4ea80f8ab3f86af5a7eba9481116bca9b7d24b74cebae16955fd8ec94c1d3a2d957684188824674aada8da4f87a5d10683a8860a50e1e9834c6ff5544dae0ab7be4280a713ace8cf28b405f8ec4269767a74649e12ef07b5a19050d218c530d6c29ea1b18d2133cdab7f0fa43661851b6da737116e2dbc401d208b2453be61488c657218efc64e520466f1c21892710ebad3dd9b8983ae22c92ab7004ecfbd06933ad5d31d3094b0131825b10499aac357cf4908b374efd10c81b5fc939b6c5a3107a2ad3b5f4084ba4cdc24d13e43c57e2ede44e60a3bab70e71f709c4c0a8dabd805adc12c73a9b01e9ba9f4aebbfb478677b6f6460bb135297dce487435f47bea5e4532e3182be4d2ddd3f2f06440a93005e4abd6009eaf521242c6fdcd1f2eebb3d5dc853f591835907b414ad9b16287f084e8fb174dc7a696d70fcf195d4a511c921dc79e00659595b6550edff40083121a4c8ab8ab7875be3813e1c34e63877092b656882cc976f995a34cf117cc30e30844707e4164eb91bdff4c166e262b7dc128b2e87620fe26d3fca32a9bad41a95d1df3ce2881bf832416b5d6692a128c3dfa0a1b31136f669b4dee3ecd1a7363749172cffe0787cb6870a47e700f2259d43515406836181ad5776a8ff3cf5915910f586ad919cf875cfc3d35a3f814e21c9f4d921956a29a1ecbcb9033b54a817eb0725fb3931db55a027a8fadd840dd7e0e0c691a67e4c1adb61efacca5ce07ea28bfcfe5112d0e6c896e5687b236ae1f0c949f0c89ed968f07fe49297b2680fe27428de7411d3932f50a040d2dd4bd89d172d49ff3fbf993857eb30d0e19763662ee4f6add3b37e273ce65ca70b2693b88e508b53031ac7e5290d114abdd77432c2f8a7636ea85a2061337365374d623ce9812dea8d0fafc9a660aed715a386562846ed94a55f3868e0a151c186032f68fe65d038118f1b94e1f8fc3ce80f73af3ea65834d8556f459e9929086e65b19a44cd97081fd46071e82c841a3dcf2d76c1b23160f54ab5cd0912bfa3a23af989069580d7429db033ccbd7d5a3f4fb66f258b8dfabf16d52cbf992472b16cb8a193456e3c322d5f736984f7daa2fd5b0e63d3c191796954326321b8e94766e9644a5f79b3ca394bc1801a8abbf45241e6a3b0c36dc59030af7e385591bff1a54e6b622f888d034051a56c7c3d650ae8431f405021ab8949c8e750bfcfed5b3f1bf58e174e9e43f295198121a2fafe3eef746207baa44d77e3eac97d47a901e7cdb769e9144942a282cb7682351a744af2b8ed865d21ac51a3c331b63ea5ea00b35610ca36a66e9cfaa87dcb546d6e31684bf197221d16141a81455d64e628a8677efd0479cc0c47ba845def81d7e79d86864652099ac6b02ea28c40d10aa366aec23a5326b71b5e1fd66d78b813324008538efe2fff289e51c461a7cb218a7c80f1ba0869f89334564c112251219d7d849c944d57614ae5bb442268e24c9bc069058122b911e703b2c24c3e24dd65bc8bb350eaa8aadfeada52920d779f13e11b2687d73ab80044965e09268789a6c090969da3b0cada66c38bbf1995368660b35a83d7ae06a7b843ce2bece287ff51a02abdc6b378b9d65199ef0a72d20219495fad061224d241165a544a7d7df22322786e74f59199b682bb014a98c170b1a39e52e9ee645184637613838b0bcee7f792d0e03dbf492390f1790c107a2174864ccc009e34995b029074e0da5cb58f52282adbb03e9028c0abd48f6d5b7c9f01f98617ada1e93d0d45a79a2718f3630cb1e3c51846d0612e8c08560b304fcf4031ae96694572056b24a07159d088bb37ab4d38d0835da51aa8c37e30f5a145d4c1f95f6dc3df41cdc7f89ef4d0f2b5a280998d1ba9eb8412d2862063671b8617458bcf0b2f0f1ec2812f7f2761e7dfffffffd4ff6265bee2d654a29052f0909092809ce0c050a43f9ccf8d1c44ddcf4352971539307e1f0516742ac3d6a5dbd42724b7dd9fc3ca8fbeffba17383d983d887bd51c70d75eab85023759a5d17e2f0e1e0416ce54aa7ea1eb041aa30871d390e0a6a3f053d67ffcb2891992425e1cde6264dd3b4ef4971d4cb90a3529b73e0d8337e4ca0107165e241dd771772538bad43863772541f018ddcc568d62eb1ee9ba29a9aa2a2fa888df6a14bd045f40aa494b2e3af518110203cedf9c9987f64f899305e3d5445e1ad90c4a9cb4c9849163281ea611f1c877084a96cd42d1ac2b6d57449ed1f4965186973936ef5373b912d6cd01622691af600e23853bc71998a780884890e15deb8501cf3e9130927c0a30f5b1c72802465044a9850b932f11828bc02ff0e063434a82b266aefa0f6e43250672797b75f9ed05f000b51a52376c09efe3eea3e2266ef9fb94e0cade383c7beb277cfe33a23d5afff0e9d11eb80e13430e71b9c4c46dc83ad48791ba31fbd1a43e37c37bffbe272409de73136a845b01d436aa2d322d890b29d76f2203e8a2151a49c6e9edff9ca2a223b5fa444f3f0ecfc0fcfb42cdbf99dffd1debefa1d2794f27213c2d884decb6f0731d3afbff7dc04695e32135be11edc85d9c94a759b1f3d1e48ddc426ec02d6bc0b1b74816df917e9d80eac2316557301998f79ffde52cb473794783c3ff2110f0fe223a46ec23d3ac63aba466a529d06dc15c720d6b907af8e7ef758cb239a221ec4aabdfd349ff0003ecf67df4478be48892cdc164130ccbec33103b59f871ef96ad7d91d3ee1e19fc7860f79c2a29eff4109d80b75e67b106aa05977f5fbde2a88664f5bf43e87178a586887e71b7cc2f319f8c4033c3c3c3cdfe11310f0b0f33c08ad6a084f58d4423b3fb3aa31e11111c3802fe048a3e24153537c446b67db59366512de8a65124edf18b1947e32cc69f2be7e75de56bf9cfd59fb6ff8851e73ee5e1385c73e4516575cb982a972303844fce910c77149781c87f38437e79cf3bd07daf371e1ae8be33ec5fdc671e17844acdc770dc4f5d4719d3490882b8d3571dded5f778f66d6b1dddd9e725f9d205efbbd7638f650a9171cd7dd3dbbbbbbbb7fdcdd6def6e6fdeedac05dc7577777757ce4ca3a9996974cb34ba750bd26dbec875edb7c1d1aca7af48a5964d2a25d5b2d9867b1bee6db8b7118458d6d3e7ba66f681d9febb1b11397f58477f90ae1b4d6faba3b5acb8f087cae933ebfef17136862e071151e1bbe1e9f413e9d40bb4ce6696cd9612bc5977c90a2c5450c192153c5802133db0024a18fe194b4e9bee54ae7a93744529cfa49487aee88af2d015a53c5c4ab2945366736bee91fdbae473727e952bf0ef6b85ea95ab0ca0ca5f06d23ced3dee91bd7c0d7cb1454dd334eda56a487b5074e2699ffd1845f65ef5e4d4fe47b714c3355442fa7f34ab8064ffeb929f8121eb604de1f5e414eb90bf2579628b5286485e5213545ac1e447e218c21be9d10976000589082780810410d60b2a595260858923a27cb1031e1891a9209531690172d5a985b15a9af2059309aad49e9e7c8a55952750a8f2fbc91be9aaab02085594f09c78fc19674870ce3a42c2ca61d3ce5e526609ca30c489976551781e645dfefead0d2a00b7fc671886ef0faa3f0c3cfe1838d411c22f9f439db9350b695a74cf62e08d2c030c91610daf9f25a5f8f9fea19c3635ad8a25740828a92a8c84d08670da88c888d0429b9060c6b42a6210845400115332294700e19222796b992081028a60f2a2601a9c24555125081a58f0e4ada50ff3c34fffb444e07575ab40614104d4173e8881020ea0b4a0700694127c48c38725218c98c263eed147abf6be1d3e2cd981ded7a2949ffd942c9d78f2b746e1c9ae6357bbe31eeb7d2357395d7ede1a9ef410c90c8e81100294cabf2f50d58f2ac74ea344a5cb4a6e97fe3a92fde55355551f1d5413eb9c90a830421dc594142a3fd4714775679f3ae006f81085038fdd3d03def2e7a4e598f66662726a402ca5c494b4af557b47b5796ef1cbbbcccbbbee2f594a29a53fbbcbe94bf8944b48ba03954ae876ce5156ded1d7dd7d6ed7ed673ea1a2f639e62d89f1111bcd1a780cfeb64422146a9af1f33da97ec8321f7461fc90c4053318021b48b0f1c4162ef025234820fc208d0dc30a2c54502e62204608360c2a94e820a2a80111413461c51629e613543cc992bc20aac9921c3861841c2009c9d45da2e2045418415997ec52aeaffb0fbbfbe24ccdc9a5e694bed3478b3b7d939967ef99d30f86393316acf4f9b548a9d47c6a99bb53f7fc05e27f546366667a84d37eaa25993d70f70b7082f0f8a1b668f16056ee53cdd9c185bd9d7c7f22d520c1db976afcd775b787fc662224b23e89e92c8085e6b3d00c8f3aa1f93a4160aa3f4cf5704c529f7c6086d45beeee04b1723034cbb22ca39452174cf1831a34955fa3495514c10a57f9b31774107e90aafc13092a5e9040c2a0326f7142e5a5f27753073989caef7d4a4cb85229156b4e9516540e42f570fc9a9fd54032955fb008c112a1a5e2a3eed2882bdd3d0c222485a12eb8a89d85d3c3f1abac22bcad445cc765ea2534163ff6c5aafc3204d9170aebe2e7df262a767fc0b053f97128eaa0258201c870aa2cb20462142af38e0dc76d5a29a1b0af7d16703b65f33c4de129970bed7aa8bd7358973fcbe51ea52c5072994b1135449f2975dc2ce28e9fce615ffcfe1c730846731e86d7bf54c695daaa20371e185eff4ef94d4260d2fd532e9722597cab8ff53378fde1f91cfe04d90d77f8ec87496818d2a2ff0c59a101dc86212d36ebb4dca1c8779ae8209ea45dca12abeeee51aaf7cb65225c8a649101240da4e3d3891cd471b354c7411d774bf528ef70e4a3f61f4a0668a0558281d2376178232321212121f506161529215f7e92da034c93d0f65c4759ddacdab1fa4383194461243662a7f6b3d20a414a93d52285c6af528e924003a1f954cb32b044a7a713b51f072ee30ac99be1c849d38974007054e0c9dfd1416c44b776ecf0f161b1562b24a4f1e767859eb61a74eb679b81f7c3492218346852e43b25fa026fa49e5786a7d33a5078d5078f1778238fca38108166566c70813ea7d2c7f3993eddd215e73b28579cd3bb5d91f967bef2ce8afcddb6f85795c70fc2aabf6ae367086e7f59e342559875f9b30e73577f1fa52ed5ff874850007649698b231dc5a4388247f5e75e985e411d3b987ded7bdf60e99ccbcc54db750791cc5dba31f306aeb7e6fc49837431b8658d3f9472f85e1adeaf385948f0ba21b4940fb2094b5a1c5551a3cb4c428225253021e1042dda50c284c2092d2fe20c1b6981b0c61426a2a8c0093880811a501c21460649218f5220b865ddb4c06b4e22c47df645a9df3e891038c45beea4d39fd2e94f7d12a1a26d2bcab62442dbe72d29bc19c6accbe927e4de7b19702a9e0c9d0af94984f8a5b7fc930811f156f69fb7fc95b0818b94b0617e1616cd90c32cd4b1613eff0c89784b4847fefc2a3cf0c75b2ea512549f413f713390992a85711b55d5811ca8937d12213ff256160e31ea286fd14f228484236909a698d00a29c14f7f8557c286feedc322ef3bea578876a863c3f6fd423a94071dbd6a8067ba4c7e5405a5a19199c42d7370e3c3dfeff6d53bdfbcaf9d97cf6a480fd3d81e9cc43ae4b7e49dffd13796ec8093665b37b887f6f2c66fa0b7e28d1b52c78d1bcf205df1c68760b7e28d1b3f321255bebcf1db3be8db167e6f7f23e4a31691963012d3be364a299ddbb67d2f1fe6c79eeafe3d4339ccaf6b1f78836907eccaf2c6cbf0e5f7dc06eebce83fc6e0f871ab39defd7380384016c89f55bef1db86cfe0b8559877d5d08d23620dbf55423c90c5194c2085cf71770394350c290cd34ef4f1ce55439de893fd8f097ec4e1286dce29a17ce328979a1af33b6683c6872aa79cd27307bddef847b51d1e77779e2b4c13e4df9161864fa337f0805ba90c2e77c04feb181cc51df0d33cb06b71d5373031adfe7b8e763facba626228bf116e9449fc49c2d37e14abfc61256cd00c0bc5c43d6e60daca5032dcc03d90b00ef9854c4c5ac73c2a21abffe8af7e5534d0ff543330c8460cf5c37a16615ee4466b78e34679686407362ada818d36b063207701398a8bfc22483c1b703ceb7f3497c4f484373253124331ed0b2ccaf13afcac67a1acbb01f67c08fe6f20f845907096653e335343f33b7f464ce13371fc80be1fbfc7ce8fac9aa5006edbf2df0177758caeeaffb5aa8b200aa0d370870682edd0ff5441e8f3f71f1ba578c0aeeef473be2807eb7960d1ff582c168bc562e560e5c0f139fc7b5efe4bf0250f0c4ec334790dfcf9687a402f07b8dee209e98a86b09d704c5257211bb528657e5d270cc7233caa7f4ec838e11716edd0a227026f6428661e3e70f0f8f1791f8e276fe48e7117495ff33c1c373734a43720f736e098a3662111d780e357679e061cbfce844c0c9594c450ab4ff4f91199f5e327036f6c26947a1eeba9b34050a6004419b2775d53aa5c2c90b048da97d6473eb4230dcf816c6c5f3eb89192edc147ac43be6cb922d3d03de297c2470e76c7806ce5a4265a9453fee769cf494ef695bdfc1e9c6f633ceba16a39384cbf37fb1f9e038e3d95be0d387e5b48c4ffa37b3ea9276435a7a38537a5140f62b54f95b3d2df2143d1fd354d63f99016ddb5a11d2dca9faeb9bb873f5be53bd287513d3a3b365fd26cd3380c9a492dd39848c76ab93e4052feffac06daf9d5cbf769209d97dbd39ac7f9554991b23ae44f1b2d6782f46dc0ec73b80de4ca03eee2e01c58ad9353d37eaa86e6c3681c27b32c530dd1988c66dc38e0c8aa33f593079cbfaa4dc3c18356dd62a9fd9864f5dca986769e53694f44fe88e3959f06e7573c5b0f76621d47583943783684445a944f1f07dc791e50e7370e6c8b9df8e504ae727670f1564fe5ea6f562b1cd8487e28b32ccbb44cd3f833d6f8358d5552ee93012a0fa932540efd351b671bed1d84edaa866cfe47f37378e4a7fa3bc80fa312e2cf6f03f2fb88dfc762411d83693f323bf1b8789a114e72a285fc1ba83dceaf6a933a60afc0de01fba88d5a94eb922fd286ff9ee7a438e1f4c0e3183bedcb5f7e0fd8f32cb474c594aa42dda5154851b9addb01717e05faeb8039dc64555936e7bbf365b2cf62f3733ef54c56cee7fce855999c9f116bced36cdbca79991cd8cd4b1b4e4543f60cdafcaa82646ff33f5a03190a47d1ba09a640afad0292fab9deca09c7ef8bc0d7e12f4a22d4f33cf43c0b656fd3036adf420c0ad13e87172a7fcf73cf3bf8c4033d3d3d3dcf604f58d442e0ab6af8079f9be0290d84f3f2198a9ced2585e12a6f8f6d75f07b9f037e20b860e76d215d1787e31766fc37515a942f6d42761272528bf23d203cd6d802aa1afe8bacec9798bf87bfba813776ac911a481ef991d956edc11ab0e61d1c8754c93d0d38f654999f01c7afca844e440c038e34f5a563dc3b0c3f5a943fa481fc191c7f5b4d49234be5a74758425653d230a332cb9a00af0784e28d0dc55b3f2dca90478b92478bf25755030bf5bc149ddea1c435cf6c0cdef632b353daa66c383938211b754e185bc22276c596ca2a205d659ea1b8ff088e58aafeebb4c9cc99f7fe6ba06d669b993321cf99f7dfb6779006fc544333cfcd8032328fa45dc667405957b56d1f64fef632a091e9acc463a175550ca9f79e9b41e6b7e75a45c3cccb84d1a5cafccc732fdd107dc9fe12a4f9560991f9992a622ca937ea56f1a24b65150d313133e10e560dd53cf7a3e5fcf6b481669e478b3099df3126a93ed4f0c69f6a0426138e49aacc1bf991097db4e8dcecb6f7a09c9cf047ba8536e07c4ef8ab67214ed6e0502465e35024bbe4c436b0e655aa20335ff332a08f167f1a0816c3ccd73ce772e7737e2392b3b393f3ae1ada5435c83af3379f6364e66b5e82453aaf63130a89f91c5ea8db1725115a3d0fab3066cbf92de765c0271ec8c9c9c9f94d27272cc2791d24b26e7f13fe34d04c081b3949a68aa126dcf1f38337728c97c87cccc77ccdc7bc3fd79c7fdf8facbaad827e7ce647afdbd374d00f6ff96fbf3d6f0f1c5887ff168e5e8bdfe69f8dcd8dccff6822fe3fef8f03ccfbab54bfc950c65f158587f3dc736c5f32ef3ddf0703859301b932946d731970ebb675db16ea6c32bf2dfe688ec96ca11099f0090f398ff31b119c2f52629bf99c9c997094f96d7b9c4d666395ccacab4200ceefb66270de655ee61b88261cb91ad95edac8af79ce8dd8fcf7d90e329ffd28f3313fee506dfe03479bcf6a6ade553170ddb6c604a9091db0dec27919d026ac0147991f699d792eeb6a40f93423d79b99979151e2c1e3e74746e6c7af9bcc8f5fdddcabf9ec6dfe47df803a78bd9101475a657edbfe876b5566bf81317f038e3bd49ab789a9b951d1c01ced6ac0ad34b44e70e498ffe13bd418702b0ee88123ad5be8fd0f9fdac3c4469a3a3214aa316d3101de7a8f92670403f2bf6eb8aa21bbad9543926d1a94825b16b7a5a0042f539c52cffb3ef932e54ab7e422b74899e5eb0ca5d3944e9eb73a54cd19c6d055a98e1fe52d8b7d71d9987642e08d1b535454038d2f53b234d0f86969a07163aa3d76571a287b62f251c4a819aa8de8981326df82249bd857ffec0bcac95bfd947adef7fd8fe2d6a5c5dea2dadbd868a1eef3e8a0d4c684d5d1bf5bd4133ce9d44057f665d4405b7770f124d4cf23f7a02dca5bfd0fe54bf2984c244a3deffbfe9d6a3fb7eafe76344de8b180fc37c5bf0522bc19760eb620024f470759b587c1544d8ba241be16eac0d57584f0ccc0a548b8ca306aebe2a481b693500dd42e7582c8ea2fab87230f955f9e6c946852a71195e792cad3a8b2ec52f925970ef22950b349f5291ea4925d5cc988698937d59e46ccde5289246d4a9bbcd5b4c9839e9a9a68136daa4dab872a25dad48637f2539353652b4e3c51a88164f7d41bdf206da5b3ca999fa90a4a5b825b964ef6fe498424a89365ae436937dfb351aaf245529d3fb27cd449b5ec4a9d2f756ae8976f43bf0c8b9490df9f8536f06f61913f7fd315ae5ff66c9dce699cfefaa66dbaa6bb1ba669beb3de3ad55c77adea97e6df51dba7368bbf797e67e6fb69ea841dc30947efe36791b986959861f8a90bc78f0bc74f85a398312b51de58d2ea8423ddcf09470f271cbffd2f1cff261cc51fa7ba5f53d4e2fe0ca8e44f1ee4635b1b138e9e172ac184235585e3b79f0a9fb670e45179763c6969387e9b71d4e7ad56d5aed439e182b4a7bf7150aa53bfaa2156b7b4dfc06596377abd55b603c1f9737dc415c5d868a4557214b7bb9ee4634be14029a17400bc99a4c2913a73a92c95a5b2549651aa75777777a7f8658adbb66da3f9946af324ea4d5a8c62448b1eb6931671a078631f31a9de40485bab9e38e19653e0ad15509c547f19c340befa56d92d10e13943e1b6527fee2e4005b8adce5e554f5587ee45d60eaac2b4aa838caa7b8191b158e5cd6181e7deb5db17112b551db2ca441cabb0172ad3e0c52a7f17846395b35851c6165354e134c594de6f75ccf046f1f3544e58562d761005a255baf2c21bc5da3cc43a859452765048291bc8a594325c4e825b393993383314fe2c41b946b8ed9b167838bb5c6a531651a0c8236994e549090a6fe24ca2b858699212a597f491962c549438899b7489aaeb5477fcf0f30e6d28a9d4556a719f1966c7cfba606ab4f0ba37088e1a2dbcfda902c2e14ae9d239ced65b9bfcb232548bfb315ab89549b2499728272b4d4871245fb2450b1315279dd4b12e579ca634c1463012d491cd42061c33bc6d91856386275b5cf6177e0e351970db6266f9d2259581d7ff5503b9bae4cf26540cbefa28a954964aa354aa4fbc9c107851bc7eaef25bdc178cab873eab7e1fe22a5fd65042cda50b78231542142a8b5027242317eafabd1ed491a6f60f03e1858bc39d91b4e2806cdd17d755128208f56b71eb2a095184fac1248cdba83bd7c5ebe290430242d65d1a820f940d982d57aa4a8bd1db252d4cf503de2e699952f7bdee929626aa6a5ffb75a3b1e2cfebeeee761590cca0d2dedd2e8174b8b29a823e5a641e73abd7f3b36d7bf6766f21799bb144b62a866c6edbf6ccdffdd9d63c5ae4267125b81567055e566f3450fbd75715e466e4d4151ce3e12d5ecac3a5cf9e3b3f6564c183673b319596c9679095131b4827487728d6fd4f12fa78a27d87eff3f2c9d127a43d4df7ff4085b4e7ae8f635d387a14d6c7d18f90f6fce00e407b6625d57d1819fde009bdb000e077615be0ffe11352c2c31f2db6774b2e8389b68294272ddf500401aaf2b3b80ca4ca3e95df3d26f73625b8e94ae4b2ac331f16173ed78c460b3d18161b48cc5695b3a7bf0dd45ef7aa81565e85611fd5a3f9eaaeb81f9f25d6293baa1ae232eeb79c4c35a48519a53506ae0dd31da65823c193a31f5cc8074d48891c64edd8910f42b481bcbbb329a56caedcdd9bfdc0c1ecacb265f6655916ee77077252ba37e7a3d56ce53ebbfdb33a5d35b47582dad66db3f74bcf54190fa6bed4ee70eb56372584558b528aedc3ca2ab7fb6ae7491b48ae9cd23d7bd726336bd9d7dd8d18522ed7ace1f1eec32491695cf71b854152a5675a8aeb568479f985d9a1c2bcbc84635195a9aeadeac027319c16e92fa970d795fa1457e9ceaa2031af814f62429a6eb1af74b986cfcc27959abb160f975da35a66c8eb9a5f7d672699596acb9ae653669a9639e179443eb9be2b3aa774399dd022ad780c6e2baa72c8b60ef0949ad4b215674a07b0579c9916b2104443f2533b32cd69449f9fde8d61607efb1770577c81d9647d79f9ed25942d6ee1762ad949e66457aebb751aa4924abd279ec6592853d02e8986fce7d3ba99f4a9fbacee4e93bdee2fdbff684d0b97abd3cee2f187da04b9669dacfb72594e4a29cd6896895e5716b981e68622abefa3ccda4b3976ab59e972911b5a24227be0655cb783ebc6d5cb8f3430d91371a7f09f488b383fc8324ddb3939aacd15a57ce75f51be0682caef330bbb06da90dae0fab90685c00ced48c36306c2758802631ca1e145e5f746fab4051043489a4ab324e5fbf134ead9ccb299c2104f4d68e1d2930b6a963dcdb3b2503c42cc5b9b83079e5677e989c9138fd65d7a427a3afaa8189c94babf340426754324fdb520ad90a1c4b63e4aeb05174d57f895eacb65471db70b0b7564231e3275e4265a38195577271b388d01060bc05063290c2a6218218695165c811a03690c294b6364c182022c3ec0f224d9f8f934279c0f599665d90a7c985d202105d2873356f0124402b6d8c1144b68d962898dd9832ed030a2ebe20c1904f1a403277cc185ca954bc8c1199feb8b20787c51459defb3088d104fa4c106133c28411421c9832526b2a480f036005ec0a0cef9d40ba73abd061a797ecbc593359c40a50d17b0e08a286e708457c45bfced130b31d3acbaf081a6caa75e0aaafcaf8b23bcb6041e1f7fd44a0e9614a1c9833cf6ac5071842c82948c045e84a869e58a10bd058a9c568850e7e4a57286940851bab54d50283c2a5f166fa55b5b47b16a44b8d2add57858e16202056c69e298f8f85341044870e2815753b70a12889043081e4ddd2a4868238527783275ab4015b1c5f3ea56814a4205243c9bba55a09c48c251168e36d0ee4eb99276f777f713ac245169beb06e953482f018143985952080becaf9ca853ab27cc61d3ebc1b9b65b700403a2f6123b46052fd084b5a3061c907598c60698b2b517680c552d3117c588ad20265898b2e4b3f687a028436765f9303759f7a0e7e021bae00c2084f0841064ba060022107180801064f28d9c11231777767ef27ea589cb999b9a04e46537706132a68a209f729f5f424babb3bbf3b3716777777ee31a4bbbbbb73636131199e497777777632ba63edeeeece3f72952419eeeeeecefd947999bbbbbbb337167777e777e726a3c570f666777777eedaa18e907ef9cd8e458ed14c6e021f00d14c58b25041d1b4250b2c47685a38220b348ed020d1a8e623e6880a0c8ef8b842fd8a91f6d405c9080b946c09131b40c1b263c6c90766ecb822fbcacfcf2b9c94317184169c2406d44684932ba0b04829a744c2fc058371449447c42f852e66e0c591ef8a966dcb931b68c142fd4877a4f3b94182cb123e57322fbb728575e8a93924e12da8c00041451954904c0ef2138cb043243d91f4184c2f4da62481090b1289448ef15333832e68f073659d382e74c0649db0f0a05e83c838b1a2081e481c898fb16259414412ab7d67e993f3baf63cc2922112e62b417e0ae9e124dc9ff3eeb7ba5b802b929e01d7aa96cf2a24bebe2a863252eaee5ab7ad92322cafc1254a149410c75d837c4512672409ed165cb4408a10e74a9a100613a11e609a8a580444f08249a8879ea4c98458194a423d30b310dc72309d8163513582a4ab94a10e5d8d64e1760db8658dac04a9f6732fa04fb73e160aab56fd4b0f1cabe3a543d104de56191aa13c84b755d0b852fd99634cb1260fe21897e618972eefda208e3df9a0b168f4732c8d6d6d9555352cd30f269efc6d823128a02835d2902a20502b14e1414dfe4c6e4a8e9a5889518ca981baef29b6f34fe6a7b1ca1c638a516951c95127c18561e49852ed576a208d4a03f9c7a05a037d939c83f3e52526a67350a3a99c0de7600ac7c14ec74155652d1ccbd262c782b0313354313cf1782f61b8dd8aea493918199b1c1e8e3d79100cdc77dd4f57a7027df625bf5f876b7b541874e8f87170fbf96129030c04e851a7eca01fdeea1ce4aafb9299191a9a2536526c6c6e6e8e7096c0c1c9c901627583d5cece8e6a28e5cf4d9e4c535982fcf3532ee5d6e01f0487b891831b2c160b05265010455185292a743e5a3ca8475d1d66703e9d8f6a48ea00e2ef9896da9307937442878e9f9f12b0a003163a1654433eb1206c10c7a0bcf5145bc22e618376ebb65a50d2826a8895ac88f0465612eb3a72ec8963593a8895c4941cc5b6d46625310f3aaafd635d4d04c1108e5d1d57c92b22a80b520d718c4b8b354835432a1c532f3ff32d16c531a8a6e71897ee59462584fbee635441e677e1287f863a466648b7452da8a35ab64ab1a8065a185aecef5440becad0a6053eba3ae3820bfbdaea23e48dab34e4070c36c4a0fa09a87d71c7a55e542f324625c47fbe2743ca4a90b8f6d105c7ffb5b8a4c509f3b79e9c224afdbae0bc1b5329ed53dfa9972afa23f7dbcf7129493f05d2163b74da1d1a91df8554be67dbcb37229fa6a47cfa0deaa0d5961ae552dd214db19462b917eea6b6712a9898999aa6a01ab685648716611f0e4131fce392bf2135ec36e5e8403118a98eeb86e873373743eccbbb2fc78a1ac87f6a6fa4a808c9b64313c996524a0f2606a42faa0e4c894016c80631605b41439c01433c08b62d1cc6f0a0dc45a08c064e4c7184ca7fa31ae2a5eeb9a8a3e361705ab75a655a7c299e9d8eeb7878523ed85628a26763aafece5907ad76b4c52692d57f2553d9fdbf00814603c1182e641686345fccafbb71e3c68b239dd4e958ac1c2b48da71e0d869121bec720c29c263ff1cacc5c3e15540629cbc71a1c623ab9a3d677cb4c41f899452ce0e0520bc8eb1f68cc4466ccc190d278aaaa199f14b0ec7917f59cb40e92ffd0325bb8208542891dee7d1789e2b498c2580a18329a260d22cc836c0860b90886289255af083904b41a35377498b1234b8c815aca0026ffcea2fec0bf6420cef3f807dc10600c3fbbbb02f980b42efef635f301f3fdebfc7be603d06f0feaf7dc15e3ede5f00fb8209e005f4f283f6050b5a97ff8ef707da170c685dfe2cbc7f00f6050bc0bafc7fde7fc7be603bd6e5bfc20bd682cffbb3b02f180b29bc3f00f6050380389ff5f375ec0ba6635dfee1fbafb02fd80aebf2fff777ed0be65a973fcffbfbec0be6b32effd5fbabb02f980aebf2cf79ff14f6054b615dfedffbb7f6056bedbab49779ed51d8170c8575f97bef9f635fb01cebf27f797f1cfb82e158977ff7feac7dc158ebfa1bfb82dd58973f7dff705fb0705dfed9fb83fb8281ebf2eff7ff7dc19e58e02bf016086770fc0c7af9821101e97dc18864f8ecf705230af2f4f705232ac0a77e7bc188847cf7dcfb67fb821119e05f7e5f30a219de7b98178c88e6b77dc1880ef0365ff3fea97dc18868f8ef6f5e30a2219ff338efdfed0b4654c3af5e07468480e7f99df77fd9178c2801ffdff3fe30fb8211d9f0e183efefed0b4694e3597fe3fd63f6052352c08b8fc2fbcbec0b46b4804fe15bef3fb32f181103dee755787f9a7dc18888fc0aef7aff9a7dc1881af03fafe3fd6df6052372c0b3f00078ff9b7dc1881ef03bbe0518118f07fa00bc3fcebe604437fceb05f0fe39fb821141e07d7c8ff7d7d9178c28023f8077e1fd57fb821149e07ffc0befbfb32f18519117fafafe3cfb82114de0617802bc7fcfbe604414f8187ee861bf2e7ea20a843f2800f6ac8b7f0220cfbaf88b803bebe29700180150675dfc100073d6c57f0388b32efe078037ebe2770068b32efe068035ebe22702d2ac8b9f01e0ccbaf81700caac8b5f0160ccbaf86d0061d6c59f00f0655dfc080055ebe2af011c0272ebe2a7014cad8bff00e0b62efe19406d5dfc0600b375f10b010bf041405f17bf0c60af8b1f08180319957fe885ca0f0396ca4f8001547ea1312a7f75a1f2ffb852f95f18801895df851e95df471895bfc7abf2bfc0a8fc021040e5e701eeba824220a7ca1f0070d7b5036c21a8f2b3e045e5070050e5ffe9a2f2eb00775d2b80bb2e17b8ebf2015500f953f841e56fb150f9452e2a3f0ae0ae2b07b8ebc2118e9eeb06b8eb0ac15d1708be0f2a7f0fc8cfa345e5df59e9e46051f971c0ef8aca7f9342e5b7b1a2f2d7b4765d33618cb7c4ff52a5f2ab72ecba381c1b48a9a8fcda8dca9f4dc1df2b0558d1c8525b485ba95c79090d2a756bff04fae5e98c2c6a4edda53396a8bce1d45709e9a91c7e8ff3e4350b96089113cb51f2a4d440dafbacfe924a3765e164e5e94af54d4b6d0f37264a3def7b9ffe3e4aa78731f4fcaf4b675ca9feefc4240a977538aa746a71eba26d51393b24f8be2d6a6e514d9452598ea234715c9840d13d1941c9625f502f46e0d07fa1b28b67f4ed6f514c4c4c4c4c01a3daa884cdf66441ec10d1080000004100c3140020200c0a084422a16028186ac2b07d14000b869e4876589889c32088519431061942080284106008010081a99a1a00f9eefc6488e4606f630c03b257007b312e2ae872714fe80b28e7b11eb8ee7de9fb51e6850079288c27a492b5b8cc4e0ca9bb0b089bf9989a40cb37aedd12c2b831f3c51c25944241c49080b87349c3b3196f2756f4dcb3d198e2ca92cdcb0142ec2e6d9a85122e32f3b3add43d7208e66a346218d43b459036438066a326c6bac0197ec1a6520c2e8177586b9871c38a360c139b33ce1da8864f61120b5b3b2150ee3873c08193f2949d54853ac1095ba41d9b08498ec8f5c1727ac9b53ed393922d0d19372d94e093d1a003614dee8426b5a2ee7a151f27bbd18d3abe4f18e3775fdab7e6c3dc5ab68d610427098dfca8290f6bf87df1fcb8c19f3f5470c50ab5092b3b91e26fa60887da360a52be693d1415a9a9b4578876e59f20d6152da1bb68e17b847b02ea233e1e05fc33147ad4899561697016d0ae5421736d6450c4788038dbb671df1df7d0d45864706ea12ba9637766a89a6a626c072680ae5847f7d2207541c40690e54b32c3cc0b56d824ac5852bb0c6dd0736b673ff1903c942b182afb33ac6d1d7227518eaec7d4f2b6ab2111680e17dbaf76c521d58dc44ed52898414bc34162d8b886d5c17be1d1913a10845f76c69d02de0d37eaddc0b966968e51ece602eb2b6e5dbff1c89d1626cbc02416219ac459e80dff86eeb4ea4f6f02732c4450bb31ad8aa620a833d6f18fc5c2978f15c36c5d7352b7a285ca6c824933d01bdbc28bafe835b5eaee12535a7d930c75ec6812c69bad96de61e4e0e1b876a6dc03d3df6eca4951ba959ebc6004dbdd9e79529d42d151f3ea4da13185da3ededf38b31a153b2c55a8484415f44dea024194b51e77933876df34bd09b06f0266e5f9cd888db8d8d18c699f7cef8b5f12a243ab83e1628118d96012cbac3b7782f5f40b66e9ce58a3252305acb9042a757bac77fcb6d75c5614eebb15e9259538483d592525bd64ca04f641689c43cfe2d1bf972a108a94a555e3fce690c34696ead7ea767bcd61f08ca4236318919832d3828f02b1e4949dfab3a0afaff772198230a1883356505b8c45802ffe1a54d5ec1362a726f07c98c54585572940f8c54fec247823cea4e16379e0e25f19ac40c7eb86c431d1572332914274e5def29ca91e70cbfd20513ca6683ad34c2ab64ada6bc3620a8784f67158cb47fc9a3f3aecc7bfdb47d4d2c5644e5225f7222dedf70bae09f1c2fc1432f051c83cc8c3dfad0f45f5387988768efa311aa88f10c9fc5a2ef726851c20bc7d2e4405d39d057c3428967017d04d98284bb7992f1d85ca071555b65112092d52c7015e4ea5d53aa95f27498b5e20bc9be0d88f82633bbe908aab84ec82d91f496ca4ab16bcd68b6f144e1c46158a7a2d738b6dd9b98489e7b655454444b1f9b044b46977e7d1ad5f477e067e20dd9a6c3d0e364303e33c65a092e9b6f6ebec860a55f0f9de0ce7ad3c54d0f9513bbc754574b23e9f157b04780733720161060a77eef886c4380d5a3f33540ecc2dec2015f41035ce00ceef0460bacb043f6b0f7c27b84403513f0761dab0b0231a8b42dc3889983274a0b525c4c89a73b6982dc600d5be0f354aee607f9493b4f784887eae02474533726db297d12ea77290102158c9c490613faf2a861e75309ed024bbe9b9c708a79da204ef695ff0df6a74b70c0871ffd65672ac0b7eb68920e9c031a3f461bb613aba7b1a2fa8b41d178002e5b3ccedca14dd865f281f9078b7f50a41029688ea389303ba059932d58a0663e663b275a8e13b1df3f734a14bb94ba7c33799d75beaca92a1d490897e38b2cfd64b4c0b788475362577926fe0921033a0949b9a22bd037894db162657dae80072abbe851d93bfc3e781b07cbc2a50271165bfc27848db09f3f223c43227d07a412d214578360a672b0f9b31c2d5ff4f74c88baa2ab6f141d1521525f4e08e9bbfa201c89bbcdaf1495af381bf3069f145271671a8191b17422b12292a9a961087402df67bf614660199c9896d6f5775e7c567213d4c8ae730cc42221055ff0f402c902a7e6c875859e566414a9f56b5a1745a81122f0dc94265205b401c0ad2b8ecbe818f4854ef8ab42a0b53f3cdb4a84f755b75126cad72242d20e5c6337928e939902c24af9872da09704a499f320f751f0b2fbd832427214c3cc2118de8bc28425fe1f2d1690077c6a9a7f59ca0a2bf3172667840627f53dfea699579ff59f7928f3cb946c22cea9afbb0032008baa6b79ff8a00c3667ccbd3fa1c8282c180202ae064f858e3156109c5c79c3778102f78806289ae434d4485e0b5f18a4f82bfb40c60191411f01d0631d3dc190ece090fae56035c442ead1f6edd0803ee7b953be3077b134dd02c65c163b7cfabe11b0569d68b37dcfe6a7e7699d667d91793561df202c12728d18db0161e2bcf0e4dbdc8ad74542cd6c3f0811f2b1a6f2a40ff9c8073ef5299ff98cf1b1b468aa088745c1f8ce38da69915dd4134eefef453338ede853eef7f2941f48609b454c5cfbef3eaf2ea21db2db879d8f3c39f2b4c2cde497c9b1e84f28535a747aad42ebb249aa31c0cf213141734a510a31e002355e1d03902fae08c22ec0859df5a90c8073145ce05762c85fc8c7cde1aa96b76eb26fd81eb773abd2523acb6dfce923ec8f9bd96a727931cdb5b724e6266825226795d48c2d19d543270c2ce777f91524644dc32433de2f6153f60fcde82e95c01c379056fd61f34ca02cffa4a67381aa0f09d244b816e08168f404fc930f2c1f9783abf0d22a948bdeb9b8376189f24e6331993837c1b232eb140589a9429fa5e4928e627a964c2c97ef85d6d8a3dba18156d06b42cf0345ee8e120062bbe6888c552adce5bd291efc0618875532201727df082ca2faa11dd46262c9a2070ee5de89189b5fa9a6d55bdad20d79369f620b1da05ccb366b3a37aa27dedf55a4b4c6b25305015b29b0323438fa08b07666eff1ce21d398e0e83e00cd09196ccd0ee46de74a5f1cb01059ac03a3c9a4a082b65a3c8988b9eda1108ec9403b9c5de265d8efcf13a387866484fac43407a92385077f9b643d9a386149b17b7a477750ba42c7c2a9a77b3e4b79e61569902ed7d4173cdfe2b54a31dd6b819805667954e5a4751965ac08bf9e713af8aa85f2d6f8ac1b78bb0b1ba07cd5bf08527f1a4d294733337e618f0bb259f96bd7f2ee4a32a4e4295146287d4664c82e39a09588b403a7178b9aa8b91e14ba3511707660c2c703e306c29d199f0d9d953e5ff625c53fc03eda24dc81d0b30d537178b1908e52b171b9009f3652a26dafc8a5cad5ef8a7f1be640c8037b92294ce3a51dee1eba66188313b5ce45d8c357697b607e24e153a4d91788afda8fcbc09c134498c4e589928bec3770c5a6ecdfdb99bc73cdf0a911f4ecb0e75e5338b93557f2f16b1987ac160e414943238aafd7bfd936b1792adb65449ad6ac6bac2bd8fd289a0211dd0b4ad78ce3258a55d24560973b48cf55e5051b32432411b9d0d4b0e67e4ed9f9937aff6861c7d2d0352edd2cdd760ccabce0e70198442eca8df812948d8481bc978264c5aa9f26f5c01f5aac7d9cc563c09d4e1b70bce7c3b1dd66e1c26163b609890aa0f800ee322bc22f85875be551597deb6eb7094ec0a4eeb1945136be237bd1e009d463c8ee43488352c73cdd96773a7dac0f8bea55cba42d0794b64a73d2e980816cfbaed781c6aa06b51f659dae1c81ed85d36e153b57da9885a525ee9120489b23c276bea0e895b697f72c31ed88575475858a9e42afa2afa1d7d053e8d442676106e7c8ce4888eeb66149ed97d5def5d62b09c077714ab6dfacad816f1a1af166b6b2014583d5bd93f5339524035472d1d75871a9e68b6a83a67598284b1a2d176ccd7d9f5456268e12e4a6adc6789792ca3b01f29e3dc857202a600d6d55de5c07273a86788e8ee1edf582cb9fbde07caa546e9005ac7f1c9138edd6cde523194bb7fe258c6fd7f4a6ea0d409ddf62d6a53ff0b4c264b89dd520ebb6dcb16594959b863d38ae118b060b7cf4c1e62949e437cfc78e46c6ef89393f728459fa8ae08e4acbe62651f2c31c513b49db553df2185d9f12e23d6127341507a0a3dfe6e74889c072681955c5cbd2e4b0ea8e0430ba57e159d157ed6a156f3ea0d2edbbb2ad3181b4dcdbaddc3870acb39f0cd9c57024c4870ce01f883ca9b2315b980860476fd4dd4cdc5284bec02e60e95ac48fbd56ba5e71c16b9364bab8ff688f16d61625e28b77db4f7f17612f7b76e3798f95c03e7fdb8a4cad466a36e2f0a3f52cd2251e51432f6a69521846afa678f18f0b8e9b6923b4bb1188d79885401e82fa8090cd7d53d2a6144687172a51e6572f10cd5d1b0a13c8b9e2be11aed72f650471983eba2f1535dc0b3d3841947673bebc42caf1a57723072c3f0a6fb4e8ca8ead49941efa7f452b5128c1d8a4bd58be2174a2349c0b0d4811bc5c51b1898b932ec9d5e91f43e6be20130cdd2a1a675084e75c10e0ab6b8c2c1f6320bed5bc0ca2ae320472303e345f7d0639511a2df7f264cbe7d245bcbc99f16ba304817cb57daeaf0d8508a4e8f431f7b351465902aa68fa5c8c919d158e6a1410bcc3cc8ff50773182a89cf92e5dad4bd7ef09b1b875d11bd1dcff16f2c3b5907dc0918b1a86e341d3cbd05ee0ada485d2cae7e3a288b879e77c1db3cd04210bb2c88f55864b6304f44879f79473446bfcbdb62ecf68020e12d51b7e2398502e8f3f006454534f913778b33cfb280ff6af11da4a0190a74ff92c27e8a93da8379e5c5c9a0b9c617aa27d7590946cef564bffffd3df3daf8689e29810456a6243810e2f2421cf3c708223711eb42710fbdad89b38cf2ebdc78dc3d188c833fe8b4626611c9c373a4b3c08bbc0b51e2bc13a76047a8f26bee0332f72d3889a34d1969809c242d351106c2b97b1b23cc816693fbff5e52b49ddbe2abc5793a96f369318ac083a04b383fa7148e83e71a0fe972a368d910649f01642683b900a7d4abb50013c72ac97b46d3611471f6a87d3ed7b7204c68729557d883469f62a96e911e89752c798a0f2853ea596b982b4ae8dd6d92cf90ad3cfc796d05965b52a91f2e65a1167079043ea34ee9ddd40a19d7e7dfdef008e5a3b0745bbe6439af15b14b07872aed34d68bad17eb2c3f4fe27223222870c5f48a7f0066cbf9136387d53d7f6ce32b8afe50e4fad9a09c812ab3f0770e1e01fd465b212956ad9325abdcf3278a81633b8fdef3841db66daa5d6eb4a26a418697355f3cc448bbe0bb88c6d37b2c31f0aeae963eb307395c065d7321c459dc167b20b9882f47e5c56b8b84f6a8659c993b13b5a5c0bf7cf641ebdfe3a0fafbbc07bc76636ea1c0579078bb17eec81cf79402e009d68279e7506d4d13dc0556c98dfb0ccf3c855cf16ac55626658f47055e77125a488234a347f862ac785f936de0c2b22459cf21cb8b26d8da80ab62aebbaeffa78a291f15562c46bcf813fd75ec18f7a7db6044e4f70b4eb3707de20844f64408e98b385adb9a131800987ecda89bcecaa57ce810c9e6aad1575fc8296bab0e82e36b5ce272f8a36288a8b36603e951befa3c4578390ba6dedc086f83a2fd7181c8c3b3f17384b050b49a0ee78f606c83280616e6a0e7d01e8ed0d48312662f6a6933e2ff3bf89b2d7fb194ee0f1c374ab69334e65ef795d9d483ae21a57115084892259cfaa7a995e7ce72d8869667ca217b9399047bee81d797f5f5b4d657372620d8ea2f08460e0c1f57c4c78c182b517e6ac1fca82f6f5e736feae807622129a7f861df57a09c9b5bcd35a866bf7ba81f2080f8db1456d397d0ece0d0cd29825a5c344ca7ae9436abd65e2f0c290580ffde2ac9a7339faef3a930fa94703e1df9b91f4708eb086f1e44fdef492ea567e4f360596f88547a110caf8be7614b347c9f11a1b503794bb29ba52cf13e4094075ab0609e0922a7a35540baf0fc774517278eb801e7616336123d375bc427dca20a6673344a030dad468aae6260323a6ba75065b192d897c8049da355bae2c6e72a98c6d6a637c26009113c63a9390fd61cec4f583d2141a9654b2f176a6c257885d3a3d0044db333eb8f8a5f60f680fdce7e72e702ac750ce9b221047c73de797910218ca882e3a21c5b78fdbf7254d489a291d089a001e171d6e1fbc2897b33f193e3463ccbfef423bbc8cc2156d37e6fc46577a1132a1bc2147c55d8b320fb3c9093a92af11a55370945e2168345c6173c748868676e4f359c901e68214bb43c883d94cd7e793cabf9469c024c5ce585c0085f6734adc0eac894f07303377bf4a1fb731db83346691a8e100eaae3ff91a7862f5805ec70a94f268f6c4b4b942e933ecbf6406988f7acb525320cbb83f08dd27df781403de7095d192e9833f77f49ef347b4e57d79862a600c790a5ee528edb0dcd28be1b930e9aa98a96e3527adf0afbcaf029dd8d9909650ffff17a6ed426861c53d0901ca0f56fd995fc04008a7eb5a290c5e235a4de9f5cc5a7767743eaebc0b36cec8ccb52c71ef68625c43253dfd6cd2a12170cca252f22b3ea541cb23bddf0db0065a3fbf3527d97e009a4d3f4693b3e5afeca161e0b8dca863691ac7364ff9bdbef4820134278bdb1287da0da96987f3028e000bd93145b071a3582fb980d2d11b57b5a7f063523dc6dc418188916a990f023b01ec20d1ab4d1a31d2be8c791276c027eebaebfc6e8c83154e4e34618e63e71bf851bd8091b8e20f6aacbf9031069ea5ba6e9fb3574ab74a3c760d72340d2004040516f60e1c4fe96b7b61dc0a665013c2259e69954130e24a600814d502fccf0656d7d697d818afd17ab08558deeb858a242f58851649c3644b12144d55cd3cbec6a5cf4a6098aefcdbcd0bb6df5252a9a5e5faf3e9695d7cebf410327438924e63f3670631bdd0e432434e0959037c2f8a085447e563b3bc141bfc14b3d95f66cc0cf1ac0f1d7658426e743742504d482462496d63d8c567a5ca3473b1ae488ac243d002540ca44d1c8817fd53b41231731fbd8f1c36eca96727aa78b6c256dc5182638b420e7787dbc626d2b03493a9d27ff280718d0f376bbf4ac6e576101885938487dd9a3be01d7b92e1559aa9e2c2d53f02f91cbfa691a0009618a2297326c819b2c350636d6938ef01404a53c90b4a13d05a548a402c87d8b4d4aeed19f0405989cadf35f52538fb08f31e29f30a1f2e17a226628b4c57a0fcece4bb0caca823db2ea24306f25a54cb41831502f5d94c510dfb8c1313ba0063b85282bc10edbb50401ddc3b3a3b78799412f20d3af55ff7e300c06d37a8469944c8c79f4187411f24dae4ea3736ce0ce41049476a877264d28e853add467063c518011a45e89baa3d09ebb847059b9a0c68148071253fd7a0844b3bc830f27b3a937e3ac187cd8e8f3d9b3fc559d6105d540dacfd4c69bdce8180a8ef7211e3c2731cf0a6ea0d29f3df8ce2089551465a32a1fec8e9724caf9f6b5960f9729b56d51c26c0e3e64321589bd428e8c7338b346e4ac22bb360fcb9b89cca0be226ba870e6c32146304991433a06a222bba8e49882772dfe9541997fcfaff30468faadef75533e3845f7cdc7970e458855567e68bbd289b5349ab79cd19567fc0baa93894b2d18c4188e445c66d1d323de802cb1a11a251acd0d9498e2dd446fc4d0c3032b15ff8c1301fa474c37f7a646c9fb12883b3450af86a2132bc6de7f53fb7c01f90797ee618ec40642e1386c2a33ae710d2ed616c613b77393be485e45a6bd065a55ba4b0bd7febacafa65477fafc0855ce0cb18124666cae0b747b40243f07f6c4b6b26e82563d7528a60fb4158d90d82cb7f13fa94e5c4c7a7f3fbf7ee5ca1bb43f4aefae641446f00a160fe3a54671802e122e632010f52208b5529271b60780c328eaa1e11dcba426f9abef358139aadad7193bcbaf2216e9a8679489d75c6ccb5766e7355a5144c388eeb661d3a1b138623075a7ee66800709e73bbcb430d2ba48830cd610c225f002e4bba40621c78b67dbce47bb925eb7d9258d548bb593c4a2cb1d0d1034e53d4e79f0979b90b795defe507eaf8a6c6f9347a97a86c1fa5035042ac1abacba4341db92d34b8b80cd509ad49feb9c9bd6e4bab7db0086288d704be611d42ec8b1db752cb0b84fcb012f0080f68cbd8b8fd6c008c7aaab58195b592725b2bdf0faa4fec9a63ba0bd26d171deac847696d21ebfab550aca17c8c3b5f64982c980c5cb230a0ffa18bd6e545e63dbe763b1f8a748a9676e61565b1e0870538a29d9a8bfa545879e246441969c4b8935e6614b1bdbf9b2d45e388413cfaea829489c4b3c8b83462c5c2e2b6d4d35aa9e8bc5baa7436fa5e8a9e88637e4022c06f06bc5106e48834a5f8b07400c5ebbec14d4817d86add15679174dc482a01463ad6387765bc3b23d5dac8b73578e6f88714638316d26c6c540c8eee972d4a22acd04483ed7d9ece3c62fb5b26596004fda624c29ea860bb85ebc04677dd4b37b3e9befb1c8bad691081b349b2695c2d6ccf483824651b259604bebfebaef1b5710226c97dcf9e0803fcec037709fd757545eef2ebc770698043b3bb7fb08b120876bb88e4cd6d678dafb77e31f606a0dec0b4d71635500deb1650f55eca1baac3961b0a85cb556d80466369c2952ab1d9af45676986df38c404ab0304d4950f0e7469758ea3f4ecae494704e07f55ee1c0e5f74ed884d1a6020efddf5ada633448315490f69801958faffd25bc17e1d8e00702d1909e01b6e929a79626b5e0f25d62b3b4a41ecaace592cd78b07d17c818d903e64d50ff820dcf75528e8773b7f77c27c2feb6fd4937be255664ed8e79cdc1e4d50de3ad3a456356b6b550d66c17bf9a01a5cc836b08dd355efd7914c136a56b960754f56021268a710b282c072a260f5cc2c506b3d223465acaf051036f1962ca6ecd3b769c40233ea1388c8e27e6fbff44390389a7af60c7c71f252f993f11aab8b1e74818753eff1c0966c55a6fcf94ac237d3469258e753b08f6de14361d6ce50e58d27634ed840f71496cea0793b80a128414c4fbbfdde27ffa2897c94d63edf2fff9d097c4cb3bddeb5ef7bccff66824fd3b6d7fbe6ff34d9f83038ed2b5c8e81d8156c1687d78e18db2f055087f08043287082b4b061b5bd211d224a1d4eedd93f867b88c25fff88dccf87bdc74cc690d08fa162d9b86ab650cd8a6ae295561a750aa2a13070de131b7a55913ec3dff0f4188bf5748954bea5c7e4eacf81bc17a6a184ac632bea9420901a2d108d1a380d6cbfae33d0342e108d1928512e9f7469a0695c20180df06dcf58d73d1080327a3e3291d77ed418a963466fabe6b6d758370e63f6fe3dec28ad829ebac279bd8c6d838de95e91f98e020ecee5719a5246ba97b8f7572dae58fd0b6993affcdd6a43e983f20c54533237d132dc12ee4f8c8838502e70080346b24d697d433ef0b55d11624f2e4cb651eab2c1fc2896ef6ec0010ba55a8e820383439e3c5f42c6a43dd4362c87a3cf1e50bd6bcb3e9194e6f164c1642129d684a497584ad71ba09d1dd0635a7260ee724078c2b558bc5be82d2b4fbd86175717a993dd06fd12db08085ed7467c0bcd872cdadab301ff6e27b9101f2a6ff4d8ba8704312e2095f504569e87822476261e61e809778629cdb5dc55219a2946ea6374db0376b3f2904cc5dd2b501d07ab80087841334518dfc75f985c4a834ba4dd6e863e4345e3ed98341e5fa42f858b3294dcb80bad954b8aa8c0fe13c8c8f704239f5d4df293ea6c28240240bc637b9070d9bc2d7d02e32cd697d1d8521d3d2e34ce46e7003c6ab88e7a6af2a4021ee0c984c62ed3b927ffbfb26e8c8d6971193d2f1c5c01adc17e631cb359c73015735a3eae6f2c519198ad9f42f9a1217a964898cf6f7ce9c45534497db944d5e99358580c2d5b566e2cec050a3c48bbb12c1a8ca3e9235e13d1444a80031b9d362b287a3143e6c86eeff4eebf207160a949dfbf273bb6a794cc701077326da9c3e2fbc208d63348daea79e08a8a040b10d38cc7da4ac492b6e893325bc6b0d534dcc3a4a6188c6eb580b53f505fdd798893acffd0b1deab8767c53cda342641ebf82986382ab531eeb875fac03c1e8f4418d170eeaa7df3247727f04c7c3dfb3598a3c33e50ded0bfeeda9c0b0f87a0bf7d06ef03c474ba2048c0a2353b7c85b00ea7937bfc48d442c7b85f3e65037fcf2415c7d38d447983fde0323c115b45020b2800c2f1b14df26f9f93c250f0fee7c367c9fa13984509933d653f685a040d3ebbd8f0f91e6ab89c003b579254c758270fb2e7984c0d1c0239059a8e74dfadef16ef2864d250b6d7e33c902b11491d77ca0ae35b33e0a07762ad06037a1244103429a3c19c023c42c5c0412101076d6988d04ae24d6129fc7301cb7021fda98d6733f9d6d968d4ef1c53554ed63959b5c0dbbc4f4430a9a1a288bfd6a98319824ea338414b42bebb84a2a42dedba3793234f828336d83c8c991d386084a60598a4ef671d412e5343367a5daadbf539272c0c67f323e6aab78d833b378144ccce6264d4ba0f5f65343588ad626f51f6360087e93cc8e82986456f20173ab4fe75cfe6ee70c9178ed24c21561a7415c89cecbf6e3d16a51063a45aef0cf534dc7ce32ac8de356cc528a5aa0fb6ee16542842adb549669c141a50ab2d4008777827f1cb8a4c106e3e4a37eac07689cc40192e910d71d5d96b911abb75033b6b91a8ad824ce7c6331af35805076d3fb0aeb20da0432deaa61779b3fa0d5eada633e1ab410bc4e8ac05a809e4e4b90f3005ac1fc6e0d34e9154ebb6bfa300022b28ed9be72e3105bc3640b19595810076f3aab0314817979526feb6eebf7868aca4ad49b1649a85f228a1a7804f22551fe9da5510095888600f7d6abc7303420398f1682242ce20f2b7dfbffd5eb6c215bafa7ba553f71866b0eb4e47d257a1810166960450917f769c6582443126c0241185f7372e1cdb77ee061c6c2ac43a0344b65f6387e83084712137e20fc2a233360ddb18b0cfd10acbcd5ad41080985d9b5cab7965c2ab27a3f0529d9dd6d07c1d6bf0d0be707b5d69c683b6eeb6e6a9e8515e42a6efb75a62fab2361f235fde9080fc7d07895a7d65edf77d9f1fe52ce12c9de1a0570ad7716088da90b9fe7a8527999325dc9bfaa9078197efa95af252bd39ef924e96d1ca8e0fe7cff1d12270d396d09d1b160e9560181973cc70561c65eb6cf8ffe3ad236ee7061c4f02cc90089e10906300fe4c173cca890436a8ae6899cb0eeaa6c99a0fab25716e367ea814df9f2ab2ca5abadf652d70cc591416564165e9860da457d370fbe2f69d219ba14f6d74e2c4cb42892c0853e1dbb028cb49694f4f633eebfc65a14fd4374d5a34733e288e3ab314e922e33104f9bce6d137edf119de8ad5fe4bfcc22b3dbe2d78f515b9f098a768d2c032bb5041026579e46e422dd6640d27d802f2b92bc125b9df5b2bb9fa36a640290f1d4caf2fac484cc06236dd14edc48ae1c028f7a0513cb8c233a0b153021607eaca4c94290d816884008f728fa85c6a321b595b8b4fee7701465e0a887b34ce401ba93d93866cd9ba41c12c99b1ad32d7919df403ee2cd9e4d40491a4c55eefaf5108299b0ccba5c24e16eff6cd88c545eb8166c8374c73588d8104aef40806354900dbbf26c25ab866665f424995a0fd5cc1b83ea7ed4b9c24426875f90e66e8763a71a01e15c60261a70648579dc3bc55228bc8c31903350a34c1fb31e389824bdde9e6fd45e876931f54e988aadb434e8553a6fec51d1d14df848f5b39525bbffeb45ff408293a6fe090607702248804fb3b8400f2ea2ca89af7211fcae41bf2676c57a254770fc9db0cfdb4551fd382cc2628738f898aca16996fbcc76380bfa17438fc9bbe21123c81d5e0fef80c11398cf91c94905d6a8a2a1ded801294f22383fff60e2556c549845dd1848b96539a212f87941a02d6e15c16f6ebf063f91ba840c4372152220b171057ca18ac6e01805915087965ec6c621196193a9a1e285012e516de7da1591166c516f4d79e720d1fb2578fcf25941304843cec79647009dfafdcd3be4d29b1b2ea47c4b8181d8a641f01297f052c66c56f9dc7a0ae8a8547dd9efbf6ad1c9f356986143b7ed6504582b7010423805e114f7142bf1351a616184518425104a67591dc9a5b04de73d42d29021b34230f542f38442d10a1fd4b8b9c080290bb801f7a4689d90cd6d851aebd95457c39e807a13caf6ded826a85a815ddbec8100fc9228e3ee8588126510eaf84bad0015083c719c3956636e2d921b4f2df56f48b029de0b48ac5b236228fe083e031fe0f925b77bd26c6a6053d0077be8c6658728823a3c2b925ad82a628f658fa5f44b3ef8b478b8d51c85152c9e2587ac7f9a7be4b101a953900cbe45b246b0e34cb082187843a752793cb002710253fb2f7aa45e8da27edcb3ddb313f7b094c9f54055d2b61f501351a5ab2aadb0b61cc80ef314ff4ab783a0ae16ec626265eadf8c5d71f89a7b2a936209dd8a3e5779903480b159f6ab0451580983fd2e747f12d1c00943483d09296da2690cd5bc545f152fbcc022861901a4da55b5c74bf25bb09feea946bb3bf94c7b7a96c5e554963638e40e088649163bd41b05988490131af951b80ac287f9aec86ca7a8faaaf0e88ee6ae090972ab5d8b3134aed521416ff4fd5d64871494439f1009b53c67e7f9181dbd68d047b05e64c0b5a89446eb7471a00abc6c6db9c0cd4f8c10b8d1fe93c5da0d2f0c9a59ce4f5bacb0a6840fa2a669ce1425d6b7f3577b6fdbdf667a98581e89de94c9cac8139eda2ef1d769421fc202d869e6edfbe74a0629158034f404437731a2144f4e03293122851c5941a96f9da3a57e59aaada82cf5ae4e13266b0db0b2704ce674a03cc99b0829bd35d88577db9ffa83b2d461955140624d8cee3723c44ec857eafce821e9d5585f6c414d0f90103cd1aac26e5b0b08c69d0068e5b59df96efb50fe27f9ea3aaf8ef4366f8c884d573b9e7dc819239dafc88a3911cd3294966490a404cfea21b803953fa5398b60796bbd86a070dbf95b2b32b75d43485c8c0b8e7cc67cc4d6ba6cc4ff5989701e00d5968a4856bcac825b8b51e9533d53f583990c3ebdbc70f0404a5c05fc604ab73b245eefc092023d94a49998255c4c165313985e2651f1d8cd9789b77cd9ec772ff2e047c46aa9819c5ab461cb052f1e04f5e8764b08dc882b70d2c90241527f53de8520f1b451df0a5de2b1ac3fc70d58d9de121a887ed0be9f4181363c6ed3d346a863cc3faec6b19e476d29e134b7485430c7bbec1949173d0dc1500c2c8df96c029a073ffbc2e9654b9785beb439d203f0b96abd44a63c551a581acc088cf3d18d8e010e72c18a0a8a238a865b74fd47940afeb524338775c8ad9f0bc41c15d5fc473d6331dc94462db9443c20eada804fa2dc046ab03c1d8a7472e72574f924b621099fcd72407464f46100c1aaf40ef39ae23eb92ce237d2ec071ae441c2c7f43e8cf9dc4cdfa1b16336c57a53d474cffb718e75941df08670953fbeb05dddf38fcc6432f2dff5ac80f736077746014b75f77f1188a117bc5c42a69d59b33f76761e6bd636ac5467b723eaddae97917767a9b4764afd5ae239f9d65e9a5ce982e6357564dc40fae15b92764b568aca946ec6685957f99140b098d067c8149877d329fed61511c444c684c67664ff362fa85b76b99fa97cd155e3e94e0ebcf8b22805e28cbdd8b3c8da2d47106e2cda75a6fae759f71e6b6c33dbae16a6f3c947123311dfa6717883eb2e38f24867825917cc6aab32c8a65881e22c628a0d51d2f6aa66897b70e4afa400389ad59c31c530f7153e5a69a6fe296117d8d9d60775020f4234d1be5f5fb11ad42fd944ada0be201e1c1f9d32d42c4c9c496f5d3e10afc540f02913590433038fff6f909f74ef61af49719e4e273a0cfed51127490d9c8831cf2e251ae621c9b3cc53e8192f969047d187e9225b439058ef584b3ec937d948644022d099179b862fcacdbf88cb4c385baec8d5470ae8a43e8b2d04f843cb829c759f153ff2f2962ce4b48d0ca6f8c3187436daaac1f8e763b3acb93c5d36b28ed82571a1dc23e18d28a75fbbd163c5ae51e9ab74736fede2b11c86ea792d4b8efef8d806ed0723ba9b9471a527a34ab574044a22ae4e5e23d36a213f221ae8f0f7c3993b5c3999c2b7b859e0386e4065195d9a1816ad0905950254be89e7b597604c60b385e1d40832e8b90d5782d6bd600de17050476f1492aaad58c33d5c34686b9fc297aef57d8d7a29affcd6801e8539980ab61dfc8cd6474e18ac0123c6794459210dc763d8d939564974fdc4e1e48de9e6a72059717bfdf76939bc52c2ce104d519e4be91691e8377f29ef45c7d4ffe8edd2d9673cb86eb25619bbc9e40df2d2dd678427283b4e69b4c43ac9c8905b8ba26113031ff381999cd07eef14b27e39cdb7c614bb39a7e1e2a7e831f4af16da5dd432424bf35aad0a28da8de7039738c735a39c71f0d3a02a754b3501563311d701d5224692fb40fd43466b81d53748a6f55ae58cd1165ae2016523d31a5a7242e8ed5a11f162ae0ebd0f94940eee4a909620d4154121c507b01d38a9093cbfc3d1324407c5eb5927f4b349ace513f906fa2af708d88f9dff4ce2e78e4fbd4a9d7ec3e2510e63eaa1227e220f6b9ecf112188bd95d213b5e19e6d9fdf6be208c62a989a31dce9c06cfe66e2db644597fca6763d424ee4896f7d69772295ce5413d14525c0b9929e7f8c39b3c742f8d2bc5796080dce4187661fbe67215c35a0a610fb03da8374b0ee051102b9a3b72ec66798750f3c7bd6c874da0eeb750085235baf4a645800c479a2347ac2db015a27e236485496027de882f1a8b8a32150bcc60280231d6522e1d20651d591f0d4cc1c0ce23bb0194a414f4186120d5bc6de6d8140c1e2a8b297624fe124c18056b02c346988291be19e089615a37e2a1b21b1ca01e6c1694345f49196f3dde3785cfd9876f8bd8b5566bf0dc185cd26a5b1faaa41d8e248cf76aa3ab737bd5d1d83182d7456b747cbead6c4494aaaf93848c4d7a80695d5ef46ce766a58fc5ab40b44f5ed5d46d83a280a243f1a93bd0938acb7c0a5b2d1e26770c65bfd2e2a149c30e4fed0ae649fc5fbe306a77fb243e2b01a399639eaa23922e23ea13990daa8922522c6c3e94ac880d8707606971fccdd7aff6bc30fec947b141df86c838c9c2392b68d30ac6d8d3bb8e0a86b319d0d2502ba6832efcc8a5600cd1586d31bcba10ee66080482abd4fb46ea4d3678279c018a7399db99f81233c39e5f0723cd8897ccf467c89260c326b9c41e77136af26a3b972cc61874bf8c4fa6cf02c11df424022a0f00776400c49c1b0ff8c630361b9e3067239549e250dad5be14361bc20242bc0c7886190eddaf7e3e3a9d63ce6734457035d02ca859beb04cd0841361c4b39a754a9d33376287d90647e625951b1eac1c5e64b520509b9dac34e5fd657322142fa119fe1e7eaa9dac1a44e9daf93cfee98e6398c1f4366f82c4899f97c3d4f1b8b627244bcf13c62399a1387a8bdb82c350258edcb5509b7db00ef35082bc9c1cebfcba4d5aa3ef6c79442b4ba809915b16bd346708c16a02a95b220dea6abd819ef265d0e83cef9ecfca8e5b6a6467c9b167d18524e2bbd37ae05fb700aeb5a7f74f5d9bda2f1d686b3a54a254926db7b17427f85de99e442425fd723a0d5f8359d604533f0bbf99a0762767fd89941ddd922539469150aa4753110acd9bd52c35672de04d291c1dd17a0c4d01086ac0afd947be46c289eb8480c0f9ce12f16b9ee07b50d135fb404270aab9db50cb3a73bbbc1df7f4b29db6b432dcb6e3c1b1d261f091e5eb83ad5fa6703565567b9143a2d0fcb79a1835801ca29764144a769da946381d69d642d555e26c064ece57ecd1d6c11f049a8215fe35545e453868bfb9158f693c03f737840bda1e72d66fbbababfc4a3b0280634c47279a531fed08030e50186f01b0d37590781ac41eedf8c50ed160954606e26334f8bb34f8afb3e953390b213b55e6c436f557667f7e87d0e53f09d0a51a2efe167df0b362f9ecf0b4587864b594739c805c61428054fc40127b4a89661979808036c7a8794548fcf7ba76c9d6dea1990fcc159177ce34ce0fa9ec2b6598cfbb2c8325a2c49321714be67ace643539e70efa3998992c2f235c4de5c6e6be17dc873345f5ab9f75d6891de7dea3e3c7d89dd2d13d6d03ddf6ee1fd6763cb8a4014623ca256af7e7f9dcaecaa487e82cf163191d5d54322ac0673119351ca4380ea6d909dcdc5e6953246b0bcdae7a38a2ccb5ecba7521fc310656e6359d155cd6d976689036dad860a40a57f2be2c6e1928871182acb859667c3edc337d3a1a25ffb5f5f0a732c4a2f0adeded62abe871816273726c26d89226a3fe4c612ba633c329a9f491ca92faacfbe3d5a5d399a7e0dbc03af97d0408725fe74458e2a2db05d05c96b03e33d064ee358ba03e3df399962640acb927cfdeffe993716d7e125269755ab1bf500ae9624dd1c881bd8b74bf0b0c49959460af1adc7da2206d70dd8dfdac74415e3fd1011d9dae8b89ff0e04ff1344b303aad860aa2d248b78749548d467b78509e4f92c3e61a2480a36486c41fc9c3491cf10418d6fc9e82c97ea2e9819bd63c79b0614c82a4c1ff79746ceb4b65fea2baa6f7686a7b9389dd0ec2647332a50de73ac11c459aea451b949579cef6fe9af50ff37c36fc8f61ab484d6907c34fb994e3f918e54264e03064761fa1e317abd33d2b74684a310fa876e97630a6667f9e23ea211e4b1c6a494d5a74aee9a7bba607d657d74f96b735e3b0f5e21f920bbffe78866833c079e538ed7f96624d4bf98b94a9c9b549321ebf313e745555bb4d53b4e7cb48fee313b72e64b6acd240d2277fa2e695b538e76ce6cfbcddd61ffec232977ccbabe80df1c229ce2189856afee5741cd37dc6b6df01599f1ae6fb789f14e9ea2b3871271f96141b69cf7aa061dcc0d2b46eb76eb46e71980f9f0d53484a1fe71f77dbc9a09476abec4e3ca089196f10850298cb63502ae7878718503bb50bac7ce8586a7ce6e712e63f225a52cb6e6012da9a3aa43b4c30ad819c30d789abcb9177a902bc9a40c9c079ce4db71c2dc429f3196468a3a7f13570ac5b7e7d63cb1992d602c7822ce274d23c17de3656245b06c6e4733f265ed5f361a9431d276f45f8dfa79953be544377398733a8b3e8f2904452a6b159cc54082a3907786326d1eb864b0bc618e5ba79ea8df84e37a42ca9aaad10ee11249b1a759817247da7351d3c9c7b640560c619f23b30ff08e68b9b18ae2d94053f5e9309478f00a222c973ec9279ab53c7bdc16eecb89f53e3aa9cb38444403ef52d35c69be5a828417190d2fc315e30299b6991193736754659db25f16763ed5e8c37b0c0ceff456c5570a2a578741565b825d17486694306f2b6848069c7f3c19ffae0ae87664bb0bc9c22c23257741b33c5b8186fa8a03125861777e034ddb2e8b2424f73cc9725b118dcfb0065ab64f47061b7d7f45323b8d55aaf60e193cce5c104249b33513480b0f65bb87803a4131f839917c738295de37cfa3b1548e584f4198c055ecf23b2c837321d9c861d1ec439d0a0b9b85d8a268251248f1c329dccbc1c2f0272abc01f145e784d1ac05c1461688a5e2ca8a14ae494fbd187f847c62446fa52a20c97be9415c4beaa69a7b8d17f145fceb25db941fa401173bf618cfa47366b00249b193202eab9f1e80339d2159859333634f1c1a667265a6653b75356c8cb6f4400a6b884e60ec1dafb7aaa79f09320b3053e8d3559b0d68e7cc4d785febb8c0cf02eb979aac9e07f12566fcc2a9cc8423c1bb77f95c4af60bb1fe945222d1207a64a44c07bf8561c7122faf80dc0c12d1dcd9d99beb7b2cf80dce237424df2f13e63f1671430d224003877c21cc7b4e25e5501c36fa1d11056b5b866a958f3b9e20b43f03d5047c11547958962f6e96447e5d1308d720fa774ff67314f8cf7c973667e5c673fa08f6960d3f243d74f777ad2036253e2d492ade5322851df4407180a00189368b1b858417c7207a728befefdae6bed13226e41506232d28e1bfceeefa4949b4dadb720cc85424259ad5e858d8f267c43c23c660fdfe4566745b6addb15be8c03240ccf353dcf0fabf7b7947200e9a52dd02fec5a454a152f00b10e24aa89810ea6707ee057d41c883a5893dc933a704e1134cfdb2768f3131234582a2a0975357c0e1a5c058a6114e0ae5b06ae7c1267ec5da3da86ff8ac861ccc7791ae7cd5fba1fea287c2137456a51030fd60f62db750358c84bb708195afb69116ed09ae13f963bccda18a2dbc52403ee6fd48f4f3403619a30305de273ee783dcf033d69b5d2daa58fb56832d3f28ac9d869357489602e0e52dc5804bc5dc684abd43efd682e71759858a0020c05c8b5188538817cba910d721867af7dde666e9a1b6209a54fb5d8c3f4656a96c96ae3830ef2a2c41b8da17e75e60a8745f52bc5934eeff30685508be9ac185c7d9c4979de4b33cfb266993eb579f21eaa0018725473368a422f0272d8f090c677c8099493c16915ab8f45e7f88370afa20bc714c21b6d028e51b34b3607ebbb00005c5d4157a7b1f4908a9487937e2de75b33805eeb6817b144d4c3b9626f313cacff3f7971a439a41df490b29e3ecb84461b3b8fed352aef0880cb1b16cafd3d618d5e63fa4f09e7cab2f5399cdabadaee92e29e144bb0b62416b958cc0ac4dfa72dfcac18c97cbb75069d0f38149f81a7be1a98a713ab792b4eaacdbc7d6ecfd19d6f722386246f1e08d84b97fe53724865afd5408f2248b721480a9663fa88a55418b77df3e1cf26d4f9cc126fbc937992084745797e5a44d326d393dae0e221bb09a19c4aa23cf8a30a8d30b54e4fdf678cb82df834773af527fdeaa13df84fa2308fd1196985623f755911e1e0853ea2d9abd7d469d82a8cb63836592715f5d8520755b9832430a508fb476aca2d9da8c1bb0c30fa3981b39e7834a456e13de0f19d37c91a241125f35a34077f4b328638098294d7303092435529df441595886c160ff2f558b5e0c342b514f32736a6079c11eaec23e986f50ec9ac4d70f91a71c5587758089e17ca857412b5832f899641d6aedf3209cba44b3082a7ed574b3a5dfbf5369ba002554fdcdffc5b1f1507e686fe3d06d6271f780781a386f472400fdd5abbe9ba9747e9322c562b18333406326806f2dd31636407a19246d2f219e56435c7a72bd52d4ce3aef8002f82e000e5ba7694a17744ace5c107913036adfe8237d697354714d5519b848ef6e39b13a9886f333fc674d44ca5f130d57c2798dd83a30c5f3336d995cfeb58a4af7570a735fea9d66e846693ea2c75a102b63a0ae7b953c51cadb05fcabdf84d4520b692a74b840d10b099f327fd283559775c30cdb82fa93f2762f95ab04584ed72f7d829971a3cb955de0a06046020828b9d32d6de7130ba5068031eb9f055ecb91fee8c58eed8e9f9cb88356450c620ae8eda543c53666c267d839389c32b7caf1e9dff661cd3b842b6e8b64af819de752954e643369b669861a0d29bc5d8242a9caa64d9ffe1bea94687fb2169b6d3a82219d95652f8e78f95ac2b41a57b2c9a5512e7322b526640345b9200ee8f54ea0a91278ab849e943ebb390e30b95f1c6ab3f0c3a9eb7df16b3d6d570e1f99c7e10ec65fb2edf08a97cfe6acfdf911ce92127d0eb97d7050100cbc2a853b91281f56da2a38b8ea14f4f9fc3439ede7d2c1dfec4cc9e5320c4ef15aa351b02bdca1831248d2042bff9656cd2dcd4e619fd11a66586a88d9fc3447f437094efaba76577e1eab4e17c4a56860c9f989925b8d6e052f294a2c8e4a0d4bb535b82aaebd60e5f55279a1d314ad4b18d9b10387bc3694eba406d60f2085258c387575991f709dfba278ec8db674d2e9ea12fc2bd0c30d742977bfbee9920c24e452f4d45169ad7996779d2b2a956a3caa932b94452f3894a59da1c15c3bcf77ec6353ca6e3381eb121492cabe9156b8586d8b5582b33bc53b03c8c6821626f0185873fbc58bbdfb31072a9e4f410fe1dd8d542f735eadd3981eaf29d6ac3dd1323010c1b94e258d27c215c088933f81352bac5decdbafeb3abb243100f18c609e140d776682e93b57efc5db13c1df64417fe89c6b7704b08fb51eeaaf66830c823e02938845456ec538a1c0ebf8f91e6fb31596c57c49140c6370d7c153563b69f935d05ac705b60ffac7108d45f33ba436f4beb6f436563d80341fcfc3cac5e266cf8d564b9a1d1c897947115b09968a23d4e30b707d0f195c3060d6b71d4b367bfb7656258a767185f9f7f69d5bd825384d7132cb5b87fbfe28bffae397fe5c4ef46051e00dd07a9b72cd06f573dacc0b426b42981049a287dd7bd0b1d41988cc027699e6ab550e8fcb445759a09890773e4fc8541236f1bdeedcc7932b8944e19235aa3c65956fbcc78ec85c6b0fafc92d61fab74eb0b2d2ddce333f4289e91791a55070cb537a64b55f53cd558af5dcce24ae30b68b73e6429842836f4b35f9eec1f0df48beeb6fdf5ff79508e830683c8007c230a51dcbce0ce2c22291080850ae5a9a1349436b0ee3ecfea689bd1b3c16993fafe59a850a2c034f02a9a71f66873529b74ca318c8834986125f6795bff498e8a91e726fff3ef0aa3ef3b5fd028d23ccbf3e07a125f8537812b694e828edfd95878b0606db73339a4979422b5d64f42a01a68e1b8a16641203f01443b33fbac2a062a9f0b65270cb3e106c84f502b823cbaec2dc26ed0a8b698afb39c685ecea60e51c35794af2092ef085c1df74ccc3cfe188c4466b7a37e0f1d310e1b438e10b2c7eac248594d3a8adf9c1a68796375ce682d452d32f7091dbdad105a5581c8364013d542e0b3a0e528987c3c8d1d8fdb4693f216eb4ffd5db84e4be09f79b95f1d6bf87b529fbeb3d1327ee4560823ff7fa6c4b1202ba1e4e4794ec472e7bac40a610c451814a77b2d950146816617410804133272a09902bdc75aca7378202e100fe28eedcf19d1d13477206a85dec5cb72b95bea22189ce04608a39f02a6b45dfd3e68b8532076d7e7f6a90de6fbb242999b890612b057398dc0271a9d0be9c0ef2c88129d0fdfaecaa383f197bd731ba37ea5738ced3abe820f9e804b1b588368dbed309e1a62c0a5a3c71af07cc2039b7f183fe40351d6d82a0785e085ad31593ef885a53358b990c4cc2c650462a05a3e963a63ff2b5decd164022e07046205611a485f3704ad821078410afa13af1205963767094a23233c585473f1002b79e8c1f22d12f241c48078826c36a492b0438a81c414541668aabc1c3066eb36da4a77bf7234336dddc5402a03bf0fe3503de1faacbbedc358ce719212712d5dae67c294508a4cf7092648e738be41a9c4a48eda2e4f386c187c851255390630029e21a92caad7d54c9cf048138355a38408d1397e6eb4e35e259c95e271b815d682e7f2afe741052d1b70c0876809277e067c62211a0f3a2921b2312e4bcf4f7aa44d0b57ec3e1c8fdad372e13a94aa9366747c52554bf7427f72c586b8191145480a7cbafa2c47862783a41a0fb2ccb1f0036d0e9faf5e5631a35668beab75c36fd5d6e827f05a6a1e5c714f3c493870194d35993218a55790d5af757520d484c97bd9640bd7422bb1d42cb2bbace5c6f5fda69840d95b4d4e097e657fcded22a9d297fd92507d8c0ec7cd9371b704133acd1db8bc6c3e1ab79f15e985a12349ed035bd02ea14fb539fe51233565156a9bd6df98626cedfbc934b9257554472cb75e6b35c986a20b89d41e4039d0f2615345015fd335187337d48d43a2b32094087f2466356aa54b0099104f0a95f92591cae96be5761dacc8c28909ea7b0296ee4dd651d9557177c6fbac2b549f9904303f652d8e808b85051b3078e68c524dcb64f4b61805f697948f24076667af59af26696e0e4cb59581497f9828fe61a48e547c60f782a4fc67e84fc9ac9784d42beb0881345060d544fe5fc3cb0d2846e732b3240caffcc60ea17429cfc9277e6515170afc4b9ac4b4f9c93f81b8e556af197bd5bb16186b690bc9f1cba7d2a683ea5deab15d10ba4d09c66fbea91a03c2242a4e1a0733c2e029695a8fd10b18ae74ef255c72a1a9ee9fd161d3677f3bc4a2ce9afb6233e0b9f9aa8b98325776be92498eebf9b2e4e443bca3af48c4c57ec280a68872e52edc9e76caffbc2d55b7033161617880cebb31241eeac80211643066b0cc04e1724e48dbb60d545db168a55e778f902971ec6de93db1eb56600532ac31f28fe4133ee902d2a88c2947907d036080d4da0d35ec811ea84148a98645f7ff9716672dae14c0c0561d895b5c17f13ca97eb0120987c4c9dc08d0e10bc86194f71e7ff8b5df8b82253d08de99f58bde72b765908d4ba5cf493dc15783cc46e435e4bd14a02de3bb1425861674c903a3e884e8d703294f68ed54e7e3ea95038b2cde679e91d7b25c33007a41acaf75ea2f0dd5f75ff8393fdfde6bbe300d66a95618ee036f975ac47e8c9f4313544e0f3d2a4287b82b1473368304d21d1914f5a92e2a94a3382fb9d29008950c7f70a5106f36c910d7d2acda31404a656d92f8186f27fea35910a43e22fb2377254bef888ad473091d7b51150ac99efba4b1b6c0c7889c86c27c9cead524481344710a20e5beb252cc380479677690bd9d633141a8dcb3b9285304f43d390b62e37018900e3b512871303f60e8d9cd1fb6c054b902417934e01f3660b0cc8886ceeba4d13cd7b6bc7951553ae520068b110bc1cce3711aeb01750c8c1f31de6ac7280320135f0fb5197c40f603a46648e7325d45e3df7e21cfda9d63226d3d5cc2d810556150fcd89d32747e1913d0237eafa57ab7646a15e1d0f7e928f19cdabaf10c089226bf661f41da524553c29379b763949f8585561392d604de93a0194701a7ad4824779aab316b452c3d61245832b28f67b7116d684d5ee495a49b4676028abb3cd18a0d1a44946aa0764ab0d592f2967c8090f4c7b3ea737598e7771a59d6fb5527eede2bbccab8231304b8eece0870966648aae87450c12a457ca021e76a7c0419551e9a6da4b6cb07fa53cebfab1d381b577207849e829fc85b26aef139d3f02ff6a55276b53ff6826125340c15fddf869757b7585e72b217e43af610c81729876da5020a81615e65d4b392e02228d2d498c301d7be3350919efc6283a51ec580136abd7373844676835c2acb575b4de25ea6cbc245bb31f394a93e39cb53c4cb9312431f58c4b12cf24b3e399844f02935c1b5cf0747ef74256084628427e5dacd6a29898b1d746b20bdb7e6cbd32a00d5f02139d3b679195c22f74a63dae4774b4def34752af12978848b33aa92f47632375db472f05732adfbb2ae4520e6246081dc2c79275daee00d32d8bb1176d7ddff20700b2ce913e8df552e4c8610ccaf03bcc734c04bc3888264fddad1985a9cf389d56df72e3335e08b265773dbb273742697d6ca04f3c1e7411aebe8d56b428ef06e3324026ef17147e11dc17f68635cde19596806996c501f4b60b3f6fcdf898db9086ccbcc8e37d87040534d3ce3276956c96184a04f2026327c26ac9b083ad4fa37749affce8c434f65de20c83ef62926750628674c9c2d8d32fc49e3bb0461f0486580582636ca890fa84f202e097926584d4e496a7f3da8f5a34dfa565df0236b5a8aaa117f773d2dc3899d9382e9bf5cf07c429431fc1392ff3460ea28353cf8b53ae210911b72b0f70ae623fcfda213b4d5ec7de833dd1b9c8af60948a422b7a9c1ced2a3151c3a4ecf657f91ecf41bf5a825611c50f691d1ad49444dc6a8496de21b2568062f6dc6f7dc9662eb9d3302d316282b22483012d595be867f40f69feb582b32339b2b97590a509722cee918e409e8daa367d5851d12e23264d3f6d073d81c25bca2c2f94993046b571b3a636ca93cee0db21a27a90836831d103b22f772a06ff5b61e4e074f00c0cfb8fb15f108585960cff591eb750f91365622f60a6788b460b253117e2a5055228272f06c436e470d56ba961789dbd2ace13c127e3ab9e600728ed4acc51c0e04ac2dce67312a7cc8903b5c0eb3da16b12a7f1dc40d53f6242491a940f0990cf035cf4113c12edeaa9533426627fa8207a78af9751f09e4c9c6fd8bb039bff2382a027b890b7ca3dae0ed1755c202088e88e1ce8384a66e6a9a4b46261e0de6f02b91e7e38a373aad6ec7dc9b08cd31b0bca184963eccfdc0f5f321c9a6760b933a73cc00cd59c16ef1b35506899fe830f02f00a2894948c36f934d6a7ce046558d0fc992182c9ef8322cf96d38da0e2cb9561306a7b8c48ac0f55a54d48aca0ef32a8dfbde94b278398bc645e2379dd76ac6981b2a4dcbb994c313d167d6468a27a8aec462031529d543d7be62aac314d036c2dc252b905344a8e278ee4ee181f3097f7edc64d289b148e67b3e3593ba59eb2adb7ed728d464a9cd06de9a410c2d2fc9bf31dd4897ebc9ead42ae4a8d201cbb50d207a7429acfa30347616bca4f497715a2523bbe2f1cc1ec0631885ee1352fd4e5885a511f85674a423598de4df2edc6b9e956f1ed77426b83825d444e461ec128b58d25176648b07f82e6269aade0b346da6781fde324a19c029e543fdc5382f8557046ec60be8e9fce64b36d20f9180f1ffb07bd2878f522628e6fe996ebd7cae35a713813d0b2a6e49b4b0d2fada5f9a6645342ac876b2aca5ea064fd13d9652b007a365370ff13f0bbbe7dd010fd7b294d091ef0de48e30193faf6d0c55e54d3b9adc1310ca826e64261755e4d5034e56a1f0d1aa13fe38c5af61105430fb29a09188e6bd23ab2b66819a5c20155f7d1fa011de8d6240e76808a8888ceac6e5bd67035e9711a26a117d67c5c14762f3f94c1e0ded36adadf680b3a5b28325331c9ffde477699d0f03dfb813176b8c4e7627959d1d877e474a738993f46137ba1a297b2534e75e4f908256ceb87792aff2a1f11ffe99ec0eff0e2aa0e4262715de4b79833d3eaf5226abc5846bd6cf061bfd9583b216cf248abaf1fd6070913a3f707ebb26ab86cb0b130871b2f616e8f5141378eaee594765b8cf649f80af3fab7be17aeeb753aa64ef8ec49cd32fc20dd5d1dd96c27d3cdbb581712da6fce8f987986f0deb441f3ee751339d439cfefff96560b11842f861d5354ef28079106adf7eb8088560e4a6b22074a849a588d5c75042203972e32b2da77bac9585fb23d7db17d42a57bc60b7d08054d953e64554de39a04b9c24d7945ef22d7cad9ddb5856b03aa400feed5aabf09d1bd60d06f089516383c311f7f78ac4c6acdb68c419e039a6150bec2e09046c2e4995f5bce65dd36209c3f4e2cc8054292f9f009e5f2288da554c560d8d024430d22d8b762a16d92cc457243442adf25d219a0fc89b78a293fdefeccc77ebd918629a947aceb601de8258fd35ac83b5fe228a0f328dad21b3e75efaade1081ec3b5a89e615f2489d72261356f43faeb0c0e02484623fae1d293cf55210752c0ddc40265ec0bf90613c142577b0c4e5faac573d715287bd2c21454401ceecb45722f1c144530374ea460de1e6fe0c3c3f2b3c9af1ecae2f2cbc3f5ecc10060a080a26bbeb23e1aed0ddea806df98cfb8fca05e9257e1139cbe7210e427d6af98b5ff0494bd4edffa01ce8d848efc1e34498b8a9e38e8c0d05e87fdecd368ef87d9845d2b0b34706cbd67c2331d22548aa410e88f2a7596d4e2df9d59d165b2bf4fa973386d8a6a7d15144cccc38a436554e0399a74a854e375d2faae9c987d99cb436e7da3ba0bba64821e390098571d8c2fa65af64681e570432bcee2003ca1c0b2d0115e87648948fd30b6ec6edc1c05bceb6d4154fca13da8b4df9208be4c1f33933efb2556f88715ff7facd00044defd8cb25a65c1abbeeb4656146fab35f054ce56ec50f1ba0c512c52aa02e5688c5069e12fd97d802895d459cdfa4c06c71552f75c1fef78f1b2e5911ec819a004ce2ad3c5548b49d52e09e9dba0907351bc4ba62911eff30bd56a95358cd3fa08f3e9201e85069855657eaf5834d5d52544248e08d387d6f75c58d4c02e2c021044fdacbe06eb5750c4fa1d18631af2d6ed49d79098774d4f6ce2a6e44d3d27709e05331b8de89c57a76cc1c0336b35a70cc4ccf58047d204dc7a456bfba0041eaa8e35992ece03f2982f97fd0c5d640d1c550c4f8b8ca071dab771364b32407a967741b9ad744972005361f29fad3d36a36e7b7f476bb8b6a6db48e02beba9fd4b2415b71216ce091ec825515da0251456ca44f4a2cfbc00edd6507a2f44890ddfa91d96c234117f87014388b171ada3bbec39e8109a96cb0a1bbd6c9e2bb6223b64762e0be0961b7e1805e82993ff9dd181f5d13e5ecf52d67a763cea9859603f27b97bc54ce62abdd9d1e18a107147be07c8b53ef2488c208ba23abd504c51ee8796b24ccda856bcdc24cd2a82524bf032d74b502d14588dd3b4ff3b7e1bba334393ed6984406714eb0a67192dbee2ba5783f6b2c20ccc7f4d0f6651059c548f99e65287da537d0a7ef056d5be81031ccc2229290053dd106232a517d40013143ed9c40a191dd8dcacdbcd7a85c0ff0342ec32f8e2f65a8ad45ae53bbe4500cb9ff783cee8bef7060d1c202354082fc8ef0e8fda0b09f29144210ab08386d30d85872c603442a19fcea13d58b25af1abc43e1440c2c1671eaee9c9b8fdfcfe78e896a0d3a08917826db2d815eaf9751caee6230466f25fe00ec3f7aa2ea033698f68b830e702c170068b51c3b80270ed68831eafe352a5bc4d5f0608808bf2300d4517f620976234e846852c629bf5c1f0e836280f836d336e0e674de2d8e953ca58ec744fa4ce0f6166a6519d8881c37d3ad4babda1f1f06dc81746a0ba281d85ff7815c1d35d417fc7a2997f8120d1df0ef67dd5ad22ad6668aec5ad5a2d240254a74faf79d9f90ddec61d0f426bb1828868af387edf13a3e87e0409092e6564f80370591b80a078a2479aa6024a7223417141dfe94ac6b0ba87632773762db9676db70b390f0a2df2f63501c39195c3ac463f1aaeb0e3c05111217505fe3d4077b33904d06fdf348aabef16d160fe25bec9db8b6501ae4cd50fc9d038904b932fba76e68eac89a9e675226da5bf35e16426a86ba2398cdb5df39730c6697d5b0062cc12f256bd78e83b295be22181ec09280621f256113204f8e34c49acb713f850b5649eeb55d9580aca660db1ce28743ca196f7b26afc5b7b6b6a67ed140e4713c37b3197010166d05a53d27f5ad16b86491753d3e2f391a48359c400a1fb9a7403735323392e539aaca2e5cebb545a5a32017433245e5de1248a1d6955e09edd3114a9a459482f356280aa2bb98fae329aa57f85ab969fc55c11798291d3cf59b994dc555557b5795c9b32ca1b9442d99c19210aeec001daa39d94893d6a1e29a831f34a613c8da73600255999931d4c7390e64ad457f9b602e980400572e39cd1b88e5c9f052fd930db7b13e5016718384b370cf7edd755ea75a73c736436a19d03215fca359f663c7af0f16818115b9033cecb7072ea4560f21de6d44a0ec1abd6d9f3af81404a78d6bb666b5912105d921fc65febcc9f272db2d30002b616d477eb18e714866b6e41890aee1f3b4061dfe4b3d23905a67a98a882833acf6261ae426d0b4344795e1748248bc3e89ad48af708e13caba08cdee3d3b94dbdface923b309005973861868e7c9295c34521e0906ad854e2a1419a4e5b10278da3345fe65ee728c81a61dd348e2e3604680285d2402085b52009a98e1d4e61f75e17eef53aaf407bff9bed5b07b891a7030d7eec01a3c3289d749a6adec6f78b930b702fbd3a8bb0c081cb2e11cefa88c0c0c3e7bf036aaa59cfe626990dfd3e85ecff37a0786e25060b91933a266451ca90d03c73b174c7f00641e2999a7034344cee08fa7d44043dc7db3676a00a5dd586c4e6fddaaef302db3694e511aaf3302ab443525587ead793bf474c7d3b6e4a3192351e83275319fa6cd042f962cf82aadfded240c0aa2dd9da2a41d60c8ae75441575a7d1e638a797aa512c20ec2acd380c3e77d321660ef8eacfa32a4514a28fb601e8f87f357eb7d82cd167c45c4969a3af2a0b6055d046d866c5d9ad8920eb90e64d7a2d4734713c0915f12444d9782e768aadebabe43373fc139fbd605c4dcc64ed934c2e1130b2ee25bba24fcdb1d1f5559a55543bec4c60fa68ca079ddc53e9dd7e4f447052dcefc61ac20f8c0da7de6dffd62328740522d9e5e7e8cfb119f86009c0a0c72dec3a757ca64c64495d9d16a1550a3d74bfac7aab8221bfb6163d45b0a53009fba81f259d09fcff4a9ee3f30b4303afd87a23789bf1a63ecaae08578d09190ba5f68de5efe8c920a922c40373c139ad601506b2f4bc60e2129b78826b17ac5d954de1ac14f899a603bcfa2ca18742b3d84a66e23da145b4a3c42ff5c773a62226c8d6a8caeb50a208908222b2f601ef3c37599a48fd505bab6b90436c5cb2189641dc542603de80c1bee938e68a0c28b48e6857c5daa05ce25c4c84b589b3137b280c1df8adb5f4417a497947f05217dc888f684f6873793d12caf19dbed38cc967e408b900d239694a982ec62b7abccaefeed39feeab13aff90550e70e6e5b97c2f664e8b09d49df95b3729ac67ae5086f03261c99ce9893e7b7c1972822bb50a06395a4319405ec18a632b53ba958dc67ede821bb0ab7d7b36afc78a9722eb3cf92ce6619701f55333e845b22969d4a896b8fc739ff61ab78b66242f3041400e8045eabfbc66ef5c4082b2c0990b31073c1332480aad092714d30d082c5ca6a74b9128c413c873726cd010ca3ad4dccd442fb8551afc2fae2ddb82a5622677245814dd6c37342af69c895da809f94c4db3ebe4232ee2198ff218b17b8fb9cfb36747aafddcc32a02d91ce5baac5fec92107d7f2a891baa738867a0987a92f8c2407c8f54d74a05e5310a2da05643be465f374e90f8324638d86e7aad04f8e78e58532eb0b1baa849183849482f33c2660a9de2f5e50b245b417ba2ea706a8ce83c55543fecfaa8816301d98f187670088c9d9566cacc2941a490b470867085ee2161786d1177d07f673e2c0a8651d868a4ba2b976e0f2f8dd06f2281c2291b6a92844ac498f929687965d36d896adb294ce9c643d0435c05979d56d159ada4a441f6101b69b8bdbc114102f63bac7ab7ae99d62f3237d4fc4fefa46e8e44b0f64c847cde1ccd209f135b8c391c4a441bd2c2813888f62b903e46e6d6ea1f835818cbef94196323d420f5a89ba871b54982115415735db870d14297cf51d844e6c059e8fe85d6edeb533217611da6b19487b3aa91d41a006447654a4eac0a0f286982eaa3f11d1ef4c4309ab532bdd7202a4fe76543a2c481d450055fceb279621e445a4324b4a30cb73bdc817d6e160dcd84ce914226b992d106e291267e4824eee77c0a9d465bcc8caddcacc50b1b2642c8448e5a8a0f826ee8300e2e0890ba7c84500c22a0380a81c759852983bc24683ab87be4814ef38e2ed55a3e00400c3b505dd96238d178af20e18b40a622bf6974d7a3a95c01157dff989f8d2e2c9e75312064cbb8963ef7f34ecc400730d482c3e4a9aea1ba95ac8dfd0a264c43694b03542e04e9034f85736c5a14a6741115453c244c33d87d9829f102fd9f2db3546e166b3fdb1ac0d4e7f49e0e7cdc3e97e9b0a9d2b968ebc04edb20bfba1da7b56e33d8a51345864119e91be84653069dd51201cf312abf895271e2a37311dfb3a9d16df93f669992c6dc4b208a62254e84a5b65377632bf89ff7083db0ff0c332e9064eaa61b39512645774d0e229333b48aa845b4f4eb8f93b8e69b360db8639100db2b50ea1d4b0c76b8b33c4b620cb528162665dbb716581c7759d6c9d2cd8d631b594a474a9e21bc65c048c9f6716cd4b9aaae4472329f49a7b9bf8c5b7dabb606cf6e47930f0df472f604a2a62e03118d07ddc31746b9aed06d79387de62e286343f00bb5fd56bb02373d25c62eb8b9cd082689b50bcd40aae1b9782406b789695b073c271d4ee38b94de5c7d178b8ffeff91d46eea483089603f37804bf83962e656543c2324689a71d63423f88fc2dca29e319045b90c6050f19b74da2b9845723bbf2924208d8b65874a13aab6805db757a398497e9a31ce5f011d3076e49c6a271ae7dd147418b061602753ca51e6a2f7f4e366c2ff4b992719d174bf3c21dc1b286800a6724b51b1097d1912d3ca829da55ea9850e2fc19aab11bd571fb743fed9285790b98402741110da9e3899321f0647c243d5cc2c7605286a4475108446b0d46c2aa985409b083117aaec0fad22d080401735358cd4554decc2d09f7640b8ca7a719db6b129f72d298b6381d24f5ec83e4e9e861300fb2d6c00f34fd4d78b619383106c3aa5093274bebdfc230b8255bf44c49c6952e1414594f66af4361cf24e1734a3e36553297e296496af7d8db57c5af27243b959d51f20c7eb4654a0ea2593852adb900b5c90c17d03aa8bd8e6b9954ed47653882157d694853eea42c8cf59535e1a81f7c0567c45441d5a9b4fdd7bcc2ffee32106a88bf1921d66970313ee34652c7b2db0c41fdeae14d3c0b93832d497bb572bc004cbccbbbe704d10f2c7018fbc2aa4b7ff151e6eebd78d87c010c88c11c2e799e9273ecebcd3b2b4d7eb3be350bc15a8ee8d648cc49115a99c775ad63349caca8e261316b716a94b99dcc9b00a72dcb9cd8448e470cf5d90e9c5a6883390eaa4bdd84baa353b949145c98e6ec8b979ae700ebb7c70b0a2f9a86cd3d94bc6188fe4719afcd7394afae850b8b8a57c1ed6ae883a520084b317667408b69296bf68a92c51e0b00910f11c1a066361ab7cc40d01d83cdcfbad8efa6400c7b2b7df9cb84b6049808a74d860d18fa8f347274977067aac57790114c6ab32bc74e8039b06ef158cc5ab529e8b57b9147575f9885846d0b365ebd0e54b3d60171f0bfe7ffc2445f5c610b8d00cbce00e62b43e14dc55d2a32d63f795746c0d9c84a09d19b0720b6522e490447cb42b4619c3fc12d125e0484836180364b16ef6dea3403c74d9f92d93028102134dbfecc0f17c46a4d6317c5cfa69ff395aa84bef9d4ca8e4a81fe5b9e387e8663083d098f69e666294620842b804e0b10d7e5f0fdb2c30e7b19c5d7ce9b0bd54f5f5c4ef25fd8e83d22dd6201cf5008a0957ef1610e8868eff4b34382ffd76a5ad167718a0970451fba13a108728a21e040e8ddb9159e2a4d66990f6de00e9f4a49da729bc994f32fd21ad4379c602b13edc5ece29cd88c161fb53e7b5bf221fbfd1544b408cdfb9b69738c0f444c38af947f04eef7224ae440e85ce81132ed20fd6a08b7e7c7a22d5d731145bc1af3cba061f15d7ae04e76aa435bbf6d5a20cd051450cb00d054926f86e832992c800f82a449879454bf11d111e40907e146d6827956a0d0b635c054e844b43b8b8155af0a47b7c1b92f9c177f588afa6a15e7f2c318bea4268bbc11af70604ec9be9f6165d827ad68d513cfdeb919beebfa6f2b92d8692d4abd2c7e1c109e6c2513504cbbb6cadb2dd4f45504b77f8fa26581d6a7f5d18250f8289d705dc1d8bc2d46c8c8b56a3b6ec5ba60bfe37b8cfdab5e744706f4fadc4814ebcca050dce2a784d8fcc6c994d4854f977394212aaa46f915d9825b331f921ec956e6e5a679d0b0e4a4a8e3b80ba385d93016fcb835f415f4ae21fa8685c8df21ccd8c4ad0d20dc4cebad442240f415d6839c597d10f8117d059e0533017288bd86258f90a03710924b1558970333af678dce4bf782cddc68e6c849612a8cf84d7ee199579676266a383fa729b69c1bb3fcc99e42506f55293193accfea1ca602199655b13b20cea5849144a48da0d2bb7bee180f0043134c7496603a65a4863ee49c0d77b724096229b309eb6101274202e2accda05f0c0c3b034b533330c6ce3b3149bec663e2ff0ae78e2264739ed3f79bd4c06cfc0f54cd25f655cc5b857b92be8cc846f037fc398a656843de93789529a672d354ef5053ca722c71a628119cdbc89539adbc011256f4ef4013751bf94622331b18755b188e32079e0059158dbf2fca29fa918ef5096a93e27ea1f9e7b9586fb0db0e4ce103fa71927c5a30523a4a1b557ba611d5181049f87b5d3578efeb15ed62555c45c95e739dc48d5d89b1a19346eee9649720ce20be71ddf7cecec471e3790bac553678cbb848f0a2321d57063ac65bad11fd0fea13d44d12222989482291bd690760084e07b0073203ca3f72503621eac4f840c0f5af379d7e867b4ecfa8437fe6d613874d1a6b4a8fcc7d55fa56383125ac63d7755dd5d0dbaf32f63663d6630880bbbaf247949a2e65159675adb56fb19bdc7f89497bcdbe0848763d92f65fe7eeb57addae6bd332eb177fd8d192f0b2d30b76e207f8e362e211f4b1711fe311d1043bc62b95abdcd33c8d5ee2499df89c0d2c184a1d4b7b9e3427b566fea491a86661463b9256423ac16234e0e803a683e455310fd8ea0f9fa828ca96ef590587e2cfeaa3ac2af0a5a2c71b04499ae9ef859d197961cbaf681bb1b634e92f866dfa6d03024aa53e6f67db7fbfb36ddb7e1b4a42b76cda4c992933fdd7ed4c675baddb5002bcaa3c0b226902acb7d62f86bdb5d6643299be6867d96fdbdffc75fb9a724f19f8b587d956b4b79c846e0dfb2cdffc25d998cdd56399fe35bf7e90846e5d61e79c55aa6acecf3dbab027dc39d20eb955b9cf97347b8e30aebbb95ba77c2eb54a1527f6c42e480487e4c79f28a0860c410d77ce96b342183d9797c72eacdcf4da2aab8fd10cb3f7fbea150e81e14f5da137037d78402f7e0761849ed72a6bf4aa2a75304429c03ef62d60df02b744ca92eb97d4c72e0da50e108c5eaa8a1504160a61bb7d485eeae89fba62c04203e0eda6674c8c7388e34044fa56ab351d0825c4e8b3aa755befd65b5e1c41ce96357abfb3054bfd59f875cb4737d473c884518694f51656cbb2f4071f47f4e1d5e73edcc1e8b6657dc51b96bb68647d9dd6b2acafea4c22ddc15495e538ea43fdc1b774a53f77aaa30a28a594d2ea734faf023a84d0e974a702858218ba6c8f12dfa330c07a9475fa8c925616ad547644e05aa5cec49565d518e3ccb9d785510cc32686490cc3302c6298631886c11a42955583449905b99910d72a8d6adad4344d46d7340de20bc3988d0eaf0b5bb7d6420cb3f6664259a669dbb64d6bc7971046cf9ac3abf9b6c19979c41056964bd5d0b48728c8cf77fc32b05d6f189b4c27df3dddcc8c27b42324ee1cc8d309e108899b833cf06b788c31c618b5bbfc19a7749f4b61be54b992f4c481982fb0d003949a77ce2b39135ed9baeb6c6c6c6c6ca2436d03521fbb00906a3acddc1e39a7ac8935355e032fed83e1749a99e1381a9a1a35f0c6aa689de130e73457484d07d40175b1734a4353a3464d4dd779363637372854aa67e762685574deb821a35f35375046ef6cf00dbe12dab0216dd88836bcda6442379910148ee3407983729c6843dec09104ae40638ef9835be24ef5ecd41c570e1682bd317f292171dbab23f3349d6d07e6c163c29b96f15c1e8b5d3c3cd5e27158a37d45fb5e871107077338703e5b08b5ce9183f32204f403c210421041870ecfe33a211d6ec70e1e3bdbcece8eb6b3b3b3b363632313ba2184c3a3f615ddb89109898f234772dfc91ca1fc5c177de7e0703964f41c392aed33a7391c73721cd29cf5a633a11c5cce0de17a16e48a0075441dae83e66428cc9d5474284f803d4f4767c70e1e3b3f42266421beaa954a553435654c792a958226ed2bd2e1e9e87411da910989bb0708f2b83c78ec509e29a3f3c0aa7d4b3c2f13e256277ed43a3b78ecf08c20a343c8b343661d3dc6e831422b7ff59bd0dda7363f222866f3d8b163270bc22313a23c3221d72828f8bb0e631129401581e107459e11a4ec4164fbc748d4790a0001c884049009c11eefa9da5734c2087284e8b0070a85d4beb1a3fe260081262c8c6e44c6996c77aa69a414d248e98c926a0de91473ce49941373a0110906d260a7f657043f3772a31d03f6f73d938804c03be0f8dfc78813638c313a86151cb2ed1923b7a5ac01f5e855abf5b562dc75305715dd441ec444d8092184dc7dea5cdcaa28a5d4a97b2c62d239e5c3af43f2ab6ac99773da9b1e2f289d93cea9ef3c83fec420c5bf0ef94f2d44df7277cb5d4f49abdc3306ab307f5214a8be728931a4958ba4f690fc1bebc72b095b0f777c5a7754e2db7591d4aef4d07c9737d88ffe0d135809dd951ea25aeaa1f83ec3d2972caa940942861bb24c1720e1e1080e2e3e40022f5d0081cb126362161d30e388279acc3082262418244a62e0a5035998a84103369c71045a31a241135f9e30592209337e904598264884e942c90c2906121b4c0cd185164590994195320a10850c252652c2fcd0c5bc922305195e6c20081f34d1032ae6480f61c0600b285ad0031371cb912d3e948004484835f8820c19908104298d1ea028230c2c494954073598628a256a80d2039931908821a3054300010aa32f35d030530922c8a032c4132c64446024c34042811684e940131fc22c9d012684230f0021cb18198ac082e6e0c8ac02c9092ef0618b3159a0e10130a81847ca78312687107421811552fcc0831eca50d9e28b333e3066098914400105151a7ca1e18b1c03893efa000f9618d3449829611cb1028e61860e70b86203165ba8902408c3064e0091a481249070010e8a8408c1126472c032441857c0e8c10f860801164258018510f308119668a207625809410c6e9853e091a9a50b078a30438a1f68918418f40bdc12850cb4b84203661c81c313934a1a5794a0063878f24412497c8104822993c609c68ce0cb1667502fb48031c313204ce0050751ba4092c2165d6cd081862852ac50a5235420819409242d30b1850a25c48069a265a6801a01c41622e822461844d01b1c992f80687c6982e54910aabc400772043f88c00654b4c00a0e5ba2e002890c63c268228827be58c2872d907c51c20834c290c18a3368a05e60900ca2f040822bccc0e1055a403252762042074fdca0c50cb309241698e2041fd042891b82b0c2060f38e38805629630a3075e680d669214e3e3012caa4841175e84d1046a11812aaca46183204ee00507ba1c296201106980b105193a5cc981d200491454ac30da010f1ae08013740631d00004305ca0000733a2805e661648341bcc20450b30a688810697b802314a3ce921cc0d42d02d47240f31474c3027619eecf81188f8e323c6075a01e365c7773137a72c29fa7895e84379b0e3fb951c36ddc1beb82cd378043525c09f2bf952f46ef08071fc3ff635f237778d370229c6ecbbebe6ed7e6642366f6176f3a7fca5f6e96f7296bf157676cadd679928b5b38f9910f69f27d5f8a4d3d1c912f87327950cb8c6bf67416abec607c90aa3973d8ef74eff383e6234faab1be37e467f7e0c247f6ee898e1682875362db5335dbddf6c31a569ab54e9d93769ff883ab08a8d118b91d43f5ec7bf7127db7abfb9827dc79bea3f586503f9d20fff127d28fd1ca3edc41dc7cc97c2c09f4cf912944792ab9faa554b4a86061a68f0bfe63412e44646bea05de92fc85a7de7db9e31188707f8835a64f6e3ba6ac5b8ebfe33b8af8d999023f1439628367d0c6a195221f14396a44d6b75749d4201eae931e14dcb2e10166310474675fb58f153138cdb6b6fb529b0efeddefb4375fb6aefb5d785b3bd32cc6a1b5c98fe946127d3fd7ab7ec3bc3aa1635acfd479fa86947f7de7befbc990d2e2c135a68616484e52f67635fa3cf497f7557bc4df0f49346e3199a65cf68d9333371e6bf222af42b38a33fece929fb36e5df387f38f6fd4dcb593ee2a9093108adb5461b5fa6ea4e297dabb2acaa48ce105007faa81fdfc6c6042f6c19718e990026ed6cdbf3e3c76967d3b2aba4b28ab01859b08108b344f73697aac9356832cc33d955057d4929a592beeb9ee8590f923a99f0965d8b51ea4facfcf564dde34fa24f7d8933b5397fecd9137f8252330aa5d78362a27814291d8a01dad388cbf9fe010155203cf9032552f4a9a46c229bc826724acf8532f341d18b3ef2639229e22cd0d81f8dae2a897434f6855536d4017fd5dbc7feabd087c7587f75d39aceb47fb00aa55a89dcf46da5844aecf38954cece0edeb4eca6a20d49db6e94e6bb1b98053f2adf649bef724d9e791aeeb5c76ff115b072fac32793ddeae55996657fbb1aefbad310d6e81aba66dbe38f3f61968d31bc7dbf313621faf8e3a4fdf2a7fef7fb7b47bad8b24a4846f5dd49f4e2e32ff873244792187c17945c3fd951cc4cbc5d2cccb9aeeba29452eb9a3e57a67f5dd7755d9665ed58182db263557fe5b96bbe95b66103feeac4138a65f1a665106e1d1d1cdda6bf0eebecf8f6b2177677f6ad738b9d1ccfc39b96edd8186d88db9b18d9d37f75c67f382fc1f4f4b993def8b7b75b166d599f3fddb608ecb2bfa2287b28fb216b6b96bdbdaeebd22eec56edf433f1badfeb2f7bfb417263d7ea0f76b96e3fbcdf4bf06d6b6233b7aca75996595d62972e996bf9da7a5d6da30b2875826203287655a16f3f888691b5828454a7acb08e38dbb9ed9d9bde5d7f443b1bff7d7b73c5827d63ec462f76545bfbd6d3fc153931e9aa318d556ce6b4693965d921eb33df9b56e464574af5e6dc78fde7f4f70259a0ce71883afe34436aed3b86d94c04bb14814a50c9a1d276a0ee081503fcb90f108d1d7d9cd817e4cf49fe489de851fa0d903f3fe857ab8ad3afc3315be2aad6f710faf5c3080c922a92dadf0ad8dbafd25fead2f067ef5bce74fe746cacba2e4d94b3aba34d8ba436d54377a691fd4d5dde4e4eb5d65afb35887dec83e2951fab3999f6d95c4e4311100c981e860c98be060762d24b1c88e9fd6bc41bccde94ddca4bdb2d7fddf79f8e1dbf7e50bc377b98ddec7abfd7fb755defba48ce1eb2bad3385a89dc4355d71b3d9ca26558014365432b631ced6e432b6178b095c43d4497ecfa18e68e25895b49c5f2f5156696c6dfc470ccc61ec3bece8de9fa7e6928a7d4fa44a20fce1ce74ddbb7b6be045e637c35a6ab43d7f9f0671ff5634d4718c40509680fa5cecf20cb940122e5f31fe265caec21a222ae89fca1c0102475744aeac82aedf7dc00266d7a85942c530648a6c542f7a3eba856bde97b0f8aa345c5b3735b6b71b6cdc9d976d3b2ebcd222cfa0e8a78d82da59f43297efa3350fe70dc9f3ecb50d9cf3c0472d24b5099462960db6666def4309cf457f73dfdf60d98d1a687d9111666fea497c02a75502b68da3b10fca8161c087e07f2612d053564c8fed5307a1d04a2d5e8ddaff9734ef4b0d4a9aaf937ce237eecf40001459d1f55918df10808298c5fa94fe833dfbfca775da55f5c8ae042c49e1f87442e457079b267915b5d8e31613552814dbfd60af557814df557813d047fd2eb3fa81dd312de2118e052041727732b997beaa8efe5520a5ba38f125a2bfdfdf6b1d0a36f037d6af4a27dfa1df4713d7d6bb3fc18f6f13be8436eecafdc411e0c67571fa1577dfc0cea602c75aabf740779aaf9357fdcd657578da315ee64dbcddbf53fe2d88ef9176dd75f7d0a03ecfb8ab8225225e697ce96f988efcf8fe60f196387434d9dcda638e83bfc0972273b48fed4a50fe370ff00d4891f97e24ffd751b092edd80392a9f1f595d58e8357f3dbbdebb2fb72fcebe39fbeeecfbd04b80dee36a9b4d99c6efd94353c61868918d3108ea55911d2058d29e32fbaa7ddc5cfb8b3dcdfe16524a7736f6f52bac6aec87b2afb6b705c06f59c11e33219f655b5dcf8454ffed6c0f8abe594b29a5d4dddd35f6d907492dfba699fe7636fdc135fee0bfd4c24cc80ef601d285ca9a73caf8517a9c310a71c2aededddd7166c09f57e9cf4a0404747a82458716649f69ee37cbde5ffe9c2a9abfd4dea6eb6ffbfb35b9060dd6327ab5ab65d909706b7a9e4cb46e5a6541e4cf24d5964f935465ca94a95ba45160d93ef6f7d29b7d633e5950fd97fa56d8b362d8c2ae6a55f267554d19b140daf32bfbf153a832feb9422985a8953f98a4e5e200891fb69450e290b976717dd79f7cfa36cbd7d72cf74df6dadb2b13d22eedf2781dd92ef616dbaedd58f6f5c8d5b2b77f3d561fbb6c9642d1ce78e2ae1f5f72ee9c007ec88203cd2ba41894b6f555cf18eceed42184103e9c4257fe9ec89db8932ddd0996f9d39a724a6e4ba93f4b3ef6d8755ddcf5f2edcc84a4f4cafa2a3e5137777443767029824b94524cd70594caf17672e4ad79a6653ae5ae6fe5c5393b2998049403276e3bef964d6ffaed31beb2148ab6696b307ad7d34a7bc8331fee9f3fafbd30fa571577974f63956d0352749154b1d839abdcaeb1733bcb72db78b1b86fb2bfd7da6bb32cfbbe70df6cafefb7d033a1cb923f6fb8bb7c1a7386e05029e38b0a0afd59299d4bd38bd8bcdc9f385d010da3870202fee6290575fc973adc45cb6e129654df924f89e694ef50cca5c3132cf214e914f1e33b3440922d9fce4dbf4ef74ca88a2f33d1dd3152ea35fac49f918a20ec0ef4240584b7c3e3e04c20993157aa16dc450a47f33635ffe10db91f1f38ffbd0f8eff727c6efce7f9d8f0f15f6a93f01fd01f798f12508ff2d13dd41f4dbe79541ec0771967d764283e5fc3fee4018c1166cb975c0daacb10cbb6e1aedb6578c5e608cc4a972115b562dc75ff51ee141809fcd5db6558a52607e049c841de4716f2353200fe270b3d4e4efd08f9c6f7641c2f808cfa2e432936978f709fa4ee1e6f6fe4111e47ee795416001553173813a2b12e244319244779030000a1548690077a3d2cea863702576f97e10b6480058d11f8abf8d264eebb0ca3e09ee66d6cb8e7381bee761926e54cc1dfecb20178e872016cba7499009c179adbe51fdb5e81bfd9e5126ac5b8ebbedf9e974a755efc6420df651fbb264339c0d3fc7c8d03bc09f9e7ed4f36e10f90a3bc0700d2d5f0b0cb24d828e3f4f183628c31c6184f32292949fb1a244934c4f8f8a2143d2f38d9bb401e2d5227cb95ec4e64a70279b4d871e6e3cc7f914b0277f9a72607d4edb20f764597078071d1e59eec5f5023f3fc4fde8f93771e95773c8eacf35d16c0b6c947509fa46e1e6f3d5e2e43086b32843b43b8c3c3eab143271f8190c7480c465be7adbc34b9a6e66dd4bc083535b3cb01202133f1307100f149f857c2dfec3200382df876394526ea24e16feadbe51e9c14396e974718d3651e202575e37cd0c3e8e1b88142039b508f7a9ae7dea9a03ebebd910971a8f7501c0ad3649bdce51d9312feaa08b7cb3c1e6254f6c9f931972eefb071464dd6f135b2f73f39e8bbacb3ad4d969b8b3128bba703d3641b32b5cbdeded2c58bd29718268e893a34206335c1dfdc71bbac236b419745e87208d6043cb7cb395b086a72085f23e7fc4f2ec0e3e41c7f23e74765111e47d60f9409f05dceb1b90cc5000678287f88483fc238e9e8e82893f025641f06c84c7ebe8607f2a3a5c02772f44bd857a91c513942bdcd8b40f3dc5b2f13b2d129146d08211a9506f60821e7e402641873e89c6d641f99840ca30e01725cea94701712f28ff7914b781bf9df06d5657d9552b7cbb9ea80bf0a80dbe5d7b2a58b8f4c4206ca2ec665b07bc40f923f36cca872254bd4898f83237b97e879c9aed4651c1b33a2cb37503de8b95db6b19fcb507c7c7e7072cf002e0e6a7b54ced91598f3b95d46d964a75293bd4af42a19e06f76f96673198acfd7644fca5e253b8d8fa692e2fec6e6f43701f93354e213aff245208e4c2327b3e35f2f351379141fcb1f66d4a926f2285be2e003d296a7a29ea8821166694ce1a5ac17766528d80f134e889ec49e524a31aa6d76c01fc678d04257b042ca0b51ca082c44ba1c5ea864a4e3f0822c2ebc07c2d09903936de32f5982e1143938b1bfbba39414fa70337607b168f8ef5f1915c4fef5715f9f659402f0df4761ba05540bdbbfb5305b80c6850cae040474420a2c044d89492bafcca4262fa015b000da3ffe05f9037f85e853450cfa1416b629d1c7faf8275c81572ad00f79cd8b5ed575bd705d4a1ba752de77152e2d0002712a4878bbf4bd9db0c5f8180f49f71ee194c14058941fa720825dd522123dba2fe56cc72943e528c658f557d5917429ef9d4424104fa6e094c147181416bacf0b6b8555b6a29765458b69d9a11069d179a5624aa6a495722c4e2eb6edb2b0edb3b7d9b6592793c5f055ad4bd334ad5a18ce9f3fc9b4ecca6a666536bb19ceb62ccbb2ccd2d6c3fa4b6dfe24ea443762c7d732a49457bbced2d5aa1615d2487fbe67fec6dbf64b6dfb0169f64d883f2e7cd5277bfb37b20d1c8ee66d7297617d5b6766ac9a1a341cde5c309994f0df87d643ed743a65990e7aabeb8e968e8e96e2d1921b19c9997884d5ac565c6b05dab5d628d68575524a5f51ae2cde188f8ee2516675448a4947b5bad157603086611886bab1e930a3a5a4384d3e20a57af294d5ac2e549fbfddc88d6a8862845305555512f6769c28874a49a717c9d94352d7eb2e830d4af287685403e6a287f990b2f43fbc292a7fc6915ffefcd31e3b7a07218ebf6f3f3b8e6cca1e7573e2a287bab9c16167ef4a5fc6ec48b323102612c986a92a33cd859d6938af945856e997bc6a8555b6cd43f2e7e7568c16b6ac9a1f7af531a62f3d87c86da94091c4c0202c9fbe3771a320729bc0ba419c49b5a6c41f0752ab13d1a3bec5b7446f8a11d047dc4044ef6807ccd5ffb0172f4b4a5e941cc90b4e5735aa732324952b55934ac2a95143662643af8c3f85d4a0017620f3514ea267e56ac99c713a207a2c78730758e6d0e03ed4d0e2f5442fce1d60a951190b90e7caf6db4490092bd4f0823fa8e4829c3ef4ff5fce6a42f4cac0df4bcf8464cc9965e01863ec2612d6d34bb8fe8b615bab3fab8a12bc8f5c9f046fe83dbef70b277afed8855595aed14b5082f711ff24d84bf0aff9bd878c1173c1f2adafe40c5275c35bd66412d630849694f2f3f0756537d72a3a05014e10f0e747f15f70ab7a3742637f750355f94a4a5dba402db54a596b4de29bbe53aa5128e8214b13d144d1f34ff544cfdf03d890f9c5b0e5c3f0dd0f03177861cbff70f67cf9f2cadff5f5ad4f6eebb3b6f51f8e6519b176d543d6e70f495165af28f0bd37a386dcecefc38dfd25effd7bfd7529662de6ee1935c4fffa4b0f5dae3fd410b92f6d7da67fd56b7d51b9ed3f5fc2e8535541719b3b16e176d17477f23bc659ab1860df9dd481690499b3aa26c8137fa681e15bb9304cd74b88dcf5bfd63a2bc9559dc6950a70bc4c948e3ccf842b4c9ec0401bfb947b54b06f1b23d0864040de8ed6dfff07bb482995e613f80baf9eb63f99b2c72eec8d0a78e5aa441e992d5fd62c5a556cd2c2209b69ff75d8572630aa7ed1ae4bb0ab2a1c24ed6f2b2f1d117affabfaea11df56ad2c9d84eeaad2a82173573f77a551d517ed4a9aa8c636f08728eac80f6242068e91df754bd004a5c90ae2334e8d023ff6d2eaa21731fb1447c88d5e9cd782f46de5740aa13f67177db069adfe7ca3866c77ecad22387b08c782546afa714e3b5fc87ca1ca55747ab155f96bfcd7c33b495984bac936b9abc10eb05d7f5516d837edb1e7d7d05f0f0e68467fdda95613ceaec5a84bd9337f3ec6b5fde7799ef6f171601830b6d01e422bb6a6e1145ad52c4dd31e4aed09007f807e401ffe9aa601c91f1f03ea646000fde748408e84bbee3d0f2905b4c3401e1b4db0fc48136d0c7270c54a1949fb53fa7120622cd5d002fff5d6d78f5fef95b1871b7ec4aca1f9f3ba3c436168be1cf26c5af353520790528efef75fb73b1b9a69b4faad0aecbbb24c58cbaec5ae8a54c99f52dbb6ec2d8b94dbb66d407b4b6d58fef89498923f15f0b2457eb0e2aed3df7b29280668cb16ec5db4681460ff20e9ef4a385e680cf9c79f6f59b7aa1c8582fc0f921615e3c4b6d9d08a182d56651109817b88481691f38a9842d42e54a99e05384c7221b53dd79e9f702935addf58ef4f5d000442c48958fda0777d1b46ee6e79162fc1dd5d471b46d4a9ae286d1245208417e12cf01e368cac1feac3a969fa23c2b099da1ff6433a1b811afb548a2815c4faaaf5148498fe52d0339dec9fb2b7699e55d70ad999a960952953a68b3a7115020ba10f9a65fb4b27b6bf10b7f24892fa4aac4f808c53d704500d210fa5b9fa2b53c15f3e66c4dad588b52f25c3cb7677f9f43d53c13315dc359e720bf74be812eb2d4705630f3314b09dc1c03d0462ca309c9e72798994164e6ffad44e459f4ca36af4a2ebfa033b03957ddf042e34157d3ea20b81cc3c0ca6e79e9e7ee6e90ba9fe7a9aaf8710d2ba29b5374c600ceb88ecac8361060629dbc3c0bd897bd3b3c069d36f9a85199d653bef299bae7d135054a72e30f884a01c3a60cc5941c41bebadeabcbc04140ab24c19e9310cd6706e8771155777568fd07b58d1670583af84e0b437e2eed6ebbaae58aff40266072f607630da514341b1b0952923b7f5371b71abadb9bd4e6d0c2c25be5bbf7080ba14e7e1bf1dbeac2afb002a239611e99e87c059b763d1726256915f6613e93edfca34c3cc1a328dc8c801998618dba4628819620b104cb144c548ee6049054488913421bc4288a5a32962f4306f589268c0a008b7d86cbde3b0fde368e5555565966559395525bbeaa165d5b7189df5a77f16a4c26e5996cc558e87392bc3ac5b68699f910f87cb84dc5dad2bf3b72324a4da15f2f8756596f541715a9635bfaa2a2d334f4fc86642e49e9047e6af0add29f3778570853c75b31bb66ddb766ddbb6d56db3b66aa373935bdc7ceba12589515828a79c724a09a3124cacc0ee417624ac2884b0a3049a907ad47a208f9d99901f417c4ba9a1f65279d4244621843da7860a42bfb9e1a441023838314e4a679269c54208218410429f16662d66ede4ec09fd5a95fb5b1e71a2ac2aab56abaa2a286755559555ab5511e14c5ab9af555b4eab5955ab56abf20a7ea5616559b55e17d600cfbb989d93e60f0bd19cd13c2177ed866ddbd44429eba729ee13ce39a7432ef037b53cb21ea594525ed100cf23d275d67e73cf39bdf2ceb2e6acaa5a41f841b128494cbab88b5d183666f81ca574469c1c58777777d71046f85083b156021b67430821c4d9fe9002a6d40fd8e3eda43e1d5c08db2bae77c7d66cb14ce796dbdbe9e142a8a263621633995278f372204ffd4c77367794dbca9b6977c7dfdeacbdf6f7b34c639a96f9be7342c8536145afc82b433420a89431b32102a4e2a8f44e164435656648002000000043140000200c08868442c168381c1266557714800b7e8a4280661fc99328885118a48c31c6184200000000006064a6888300b99eb317ce9b79d38f19cad04833b1c8ff42131b0f32e2999964f16c2d046d4001a38a3cc705b6129d10be10723156099b1938d7682629f39bb180e7a4e2d2c460f8679ab9eb21c7e75aa9be2a3f29444e4abc12c4357b7388ca019d6f0536016a4a6b6a2df0c952d5a04727c83bca2fd4a46791d9583fc0bef459748f5aad60cbd08f630ecd1bf3e78deb71fdb9b88a488d7e56a2c31190e808912d857b89b2e5e7d88d13b607671d0055a047d0112ab65471546478f726fce304b44115771db376c2d161300de24a17c947635f8a08372eea69f7d63874235ba36f11928c8102e2d83a0b319f9605352a11ff330506663565ea2b0683e34b8480c03cc22410f2d752dffdc6a6e75f2e6f0acaa709710f641e7ac302d9b58d64e7809009f169525f499a0da11d9b2ebb1ed9804287635a3f1c7fd481be2d9f546e5f1eeed2beb031d6df1e17c534c4397884520f796cefa41de72489ac8634ded1b5de383636fb96fdb0e943039b398741ec865e9bab63873014c78a6c603fdca883ac74969aa6a9d9aa8ecc840eb18fc64f4cf1ca82469952bc2518cb80b12c5570c69b39e85843c18d4e585596eed365c90fe498579799d9993620c0be9121b5367b38a0d0c3cb5acd341f42a2ac1ce50681c68b60529cea113c53de7244aa31abf5f90f9768e2fee773a0046135783a1854059838adfb0205d481ef69461b784496b4b469c571a4a847451729075da4a38519b77ef07792f65f062481d79fcd0e6703ef3f37c09551b96d0539db6e832cb1a875d2e681078783515811b70ad77d8d1c89cf7c22937c662279d179915282dfd73d2a3b7cf2202943bbb2cb2d4b13f2c5c28ea5780ec033a514246f39863c5c889e1318a98d2dafadfde32a76da48dc84f0bcc13cb4493feb4847a22f2efba30b6cc1a396d9d191460f314011d98a13f76eeff5602776c6f393fe2e448e64b5a75daf31342dc435ad5cf831aedcc2d644f924464787187b896bf375023fc83fde3f1ac645db2f41ffbf09793fb3cfa13b42e9ab0613846b824c7fe87e11b8dc976e4e808d1f310cb5cc9e4bd099dc2dd6c06940dc3c1135fe64fd42cc3d414af1711f4c34796904ed35fbdcfa61bfc93bfea10bfce4db3894c6441230c26656b60c249f3234bae73084f2544aa2db933c51a9e337513258dfa1885f5b5bed36ffbbc3a1a7e7016dcbddc085219ad490efb794af754e34e9ad505975b89d5fac7fdd887ab1b4f8c858c755db5901678ca35840a0b4119e2298d90d5393773172fbb46fd5a961e0d688d0cec930db2eca6a999b6e071aff0beb04c9ee8c0c95f3c12e13583607e022c73030038396d0e61a889e38bd82727b4a2abf82ac1534fa47d426f855a50ec8e4e2f12a1be4f82ce814035c0d9b00139bb218e4da797e182b160f42a3f7fb8c20078fba474de297aace375ceb374e32a84ac996179336d731afd90a967f98c835f1e143de1950dd86014171af2ede66eca02362cc1ea2846a090c39b9403360a76b24283074e9a305f78815c42882e18fe63cd3603647af0bcc5c1e49ce8af8cf5e9cc29fb524a9fb6e77d5ad42601d58caeee6a8a032ce33af7fc2af9e3ff3aad83fbeec2a253ae6dd00c5a444f39b125891f9f81be0596e0ca29a5e9e95435912a4390fc43289bf5c651cc768ebcac84453f2e569ce56dca979aa0c914f794878f3a62793b09037c8f0658015a81bdbd83e4578e37efbf969f1f71b770750dbb8f8b92c23d1226c15ce474955c8a92c7717c31513180820ecd1ec0d0ab7877d414dea391fa0aa6571ddb028d2725bdbc5958877aa48b31983b30f7d51d3057e84db622004103688962f0acebc063ea55b806c152345e113a8fd096f542a52b1960f9c9f573a08aa1d73c2656778a9c46df9ef4924c5241a6f9575a5929fa0c09dce942cc689dd8712d33a3dcecd4a6294e70a0b718872d5d055c893ace9e74008cc0ed83dfb78020b32bec9ef40f9312bc424b0cd0772891755d5a5f789cfa31d18a39210624270c0857d85b2d561c35eced9e04186a19499dca5c2f7cee1b4ce2c1aa34269bb43f8872143c18010635beec9acb090f30051de30754d3f0e70bda9b70e090dbf10d02a1c7f87089890d07b41056c2e479760010c25e9d9e62725b9cbd0dce946b56b63a577e44c0c64f7206fdc814cea47594d266ab37e84946bf68585542299070162c63e4509677e0c98a657df302f302e024c32be17f36483ff47a3c53f933550398108b945cc74b75abaa3cbf72c5a99a5a29c1d57a49246667e07d462b05820099f7d848fa7df70dfb4ef5a8f4a5a3dfdeeb72041b424d620f94a2146e79f187527959348c3c09d41d7521253eb3ccdf8b1a7fd8197393eb51e5d3ac04c6f35a66a584b3a33edb06bb365e1d271761049e28a04105ba24588ee70c2501b55507d45dd9361ed1cd26738746092115dbc9e52783e22590543ef18ab2b15ba1038bb0da8329f99b7f505a6ca8c99f0774f1ed00ad4a120a05295abd4408bab8c22a04956d845c58de8fcb3e42d46657d10a96026dee86c581157c78ce6fe1af30ccc2c4209d449ca8ed025b0b2a8828a945d3b900da61609c544e00af4a0bc2fd827862831347c7983c0b6acb92d0a1a6badf0ed93b7fb35a9a54c916bac9767a5bb71088229472fef590d51dcf300154363d3e6f33a833838d8c0c5e05866571441ba315915654d5a51374998c18d2ded47066e692b6ec8aa9c6226f6b9effe8709b402ef81f9251552bc7f7d59498d17df6c8642fa5422ef28569e8909afc4484b1ee3c83149ffc643cf156f24e1662a998da5dbd83b63a15ff86e8dae5e0785f67c39c0daf205cbcbdab1ee3ad37ae23eb915083bcb36e4ab4add7f87fbd8b71946f600a6d4c15037a06bbf43c00b6813888eaf5af34d9372e6578394ba6a64ea6bef3f9acabd656a7ca6f238775e2895965412c414f7b75fc8790ab7a9f3ed5a2c8945523b866653396dcec6bced98392eedef3e3cd05bdec2b655577d1c431e6061be6cf25fa43591aed0a4db74db8fce72deeb19d48a7d12eaf49db8f5f983d23369a8fa23a3231fd638dc21f2d2fe3ec882b6a660085d3bf9810e5573212b061a03d0dd510882336cf1c57474cb50d0b29e912cf67edf09817affeba1cf1349cd885e5bebea78c52b8fabdb94173621213cd04802a45466a12d5c880f21365115fa914a46d68ecbe500aa537b818d82ba52567b1693ff9af1e5e152645a3131b42bbe5bb9eddba2dbcb67c62df50caa35210c1946c439ba983a79b2a947d5481b646ac4e25ffbdf552f94a9a67d78d9012e7c2489e89d861a81362ce9832a7315f1c17cc2154816d4c606d58538f178d0f528d3f70c47c9654b586895304d42c6d77a800d2e0734b0b9d184b89328d207056a4aa52828b35f72b626fe6e626331b166665148dcea3e85f6721e9206d083212db6773841ca4fb34a442cae70e4927d3053187a3761842005ece952c91b224f74399dc3895b2e7cc32079b63a73d256b987a4001df2606a269115481a06b8286947efe218a7940cf896e017c2c8eca100f805dbe6afb6962205fbc03ed798c61f8a42f0d423bf2145f8c70f32b3bae9e46735e195a4327cfd05712b6221138d9cf86a76bae042b4a5960e389bede0d80d5341f6c129fc9318b13308a012c77fc6ad0f0d95853e0a77b262c970e43631ba11465658b176e0e6450d70a08754bbd8b4c17396aa1f0d25ec1fa5d591a37d72fd9eca151a8d4b8801c78ac45912942d8c9e5a4b46f030f055fde15f343d7d1e6fe103e639254b342ecba69e5048ab0d605b88f7d32df24ef3869de4cb0268a0e4fc9b544e651846934d8703235f9d604e4cdfd15e6b4552e15f44265cf4229449ca9430523fd125da087ebbacfce07959863acb9a9f946c4f5b91106167cb1f4aed74661a4d46c48d5da407058b6b02e64568fe1cc45433f51a7a74615a83ca4ff58e4f070796cb4ce4c553b88a59205c1517d1c72e607e47e09a4d009ad335a5bb846d843e41ea5c4f0cb9f1c36de3dad3c67b9fc335a37e9a711222997e0d20c6e13b19f04a08718b8bb8c77da05e25088d777661c588beebaa10ebbf6e49807b008215a6aee5031f8613a160f99f6310140c9b29794b6b8ce91b8d4cff4ade7a970193267470b55853777f6fdd953d74ecff7d8a001f26bca7761041c83f35ce36697f865811ac6b5aca776b035307a75432d9a3b72a17fafe854d44aba7fd5243203faea55ad071e492b27403a554d725bcf3aaa2905dcb9d1bef3cf64685d80fce310de97b92be5bc2c3d55a51d4973fd60e5e9c3600f6cae970c2f58830511fd5ceb40ba50a0e270286e6067a65d21e7638e193afe5b3a99b59f42a9546245ae2dc99773b291b7d85fc537bdc3ccb8c9ce9825ad4f8a18c2eccf386f476d9702e623ba28083251ef712fa083ed3598b4c90182d71612d14465182f8b0fd49048a27a5e091747e47682cd8e6197fe0366e25378ff345d531e34ea3b761aa39b545cab93d7d040c2a1e254e1d9e3a68906140dc237eca1aeba020c6ab7ead9295ba8509164571b5ffb10c1fb6e3aa333140f870f647125b07c5d17a18e801c8aa3d6044ebe3718f2c41daed1568968ca540a3d78733678246244a2b841949669e0d2751322adda0e6a0ab14785bbccd4151e89dc61111695fbd254b1d5a33b146c9742f811e2e30ae5e63b370ac12a2f51aea517b17c23adfd0b20c0bf6911b6dfc23b3709d2f3a59c59f1b6dbf40920d05432013844032d413c07543425033e30ee4551583b62f611913e2e1de56c4cf1f6ed9f5aef7bfe7bbdfff3d3b25de134d3e1bf7e79df8433831ca605975b997e295c38692004043d0c0fda08e9269abd52aad7ae03256eb66053fee125cfabb04131bc56aa00ebe350d23629807ab4e9e93e5afcb42fcb72f5c0c545e2935ef17d650ddca3b1719cc9118fcfd62dfac7664cdfb0dfcf43403b69de86320102014c4fb0391134d6463c8045df4c8f118411a308a2fb4fcb97190a35d6a83c199195ee22dfa342ba317817db3e4c6595828065c4c700e9e3d37fcfc888d7ad85a30108e776347a70386a9281160f82c1faaecabd40d6c02a81b2345c122dc7644cadbc3cf8a23114216312da2e0a3a3274f0d942a73e553cef868a0dab8a4cb1795d54ad5aeea151b603f65af270c2ce2dfba6725056b667f2e2745661415e6a5a26b69037f9527bf9e5da708e688c5be8a6cece7e94ae555095ea2dad49c0f30628ff1c6edc840b1c5a4b3f857b8a573e35de19717efc83796a945825b099d376eac2cb9dcf76a4c687396ad4fe18d260db60a976ab88ef6080b33941433723fb3d94971571a61804115efae603ae575b619e005e72f04722f589b30c64777dcf4d79763fe9c06500464a26aedefd2075f0c007e64fd8b3200c634b369cd22a3dc916e7f2ee4791474a33106c0c8fca1b4d70fa81e42ff989e520362f7b0ea08bc9a0ac1f420e0ed93f06b2a99efaee2893e45452fdb27d1c8df09ae6daf40936a894bd4880d933b9648ddc14084b5a4b3c3845c1b9381b831329ef1445e1b13bf46df20888263197b88666f2c0443e08aa31ef3289e3764efa6ffd91ccfb9abb5e4ceac0a71ff0637368a71e6d315c7260f435dead76b200ee3db34d02c05a8c01a231d4833b4800748787a364938383374a19c602ebffe0e4c8d497e682708440d0c114aa444f93c83e03b2a3ddb3d387319482fc62a35bb93242726aea90882c53aebf01fe8191d93280eb6ca556aa07b20e088b78a880c24bafede68da0b3f9da7e5e91b32f19b32d839d2036f1373512b90e7d20384f1300c8d23835382d70ab7a1b49b048f96c62fce14e5caba34df71d8591580454c4931acc9e1019b6f512848ba03fac040fa15eb0fb372369828e71b5e5763dc6921b32904060ae63f7c2032d783465e2c1ce4d013c10d5c4ca996033c374527560dc0981bf81a2688abda9421d62e30c371a99871544cd46fbb822c2c444b5f10a8096e92f74d12bc56c28eec9fa581c4e97062e0de451bd807edfd68f5b234f880a90b46841b08fc2cc4924204e8eeb1015df476961d99cc607b1cb1271f2c55f1c3d22208306cf99f3bbbdf8c29cf04e3c57cb047346c9273645e07e726be99d0f7206bca2a0ba926f227573628e295b2dbb725d939932429412b5d9868c73579eb1f24e3da71b8a47baa6cff968723b0c330ec95019cf7c1268c658d2975af238653650ca84f80dc5c86969ce1fce802c5f84e63158ce087ac4053b31d11374fccdbcfc6b0a4693293693908683faf26ee7f000ba79551fdcb208adb2be718f16ea802b1845462cadc7be00c8905a4cbdf8b07f2a8b900e0b9807660e760b25fbaf890b772cca97150b3b518616ccb0d02361d86341226fc53a51787348aa1d75771a7de437cca05c38f75608b73c71f1112d81e90b457a3b61544d7a928310091871ba15ce31815084dd0e590bd1d192b1a29bc187623768576eac95178635e9ebe757c92518a368fe143d9bb6920278dc3360568a352359977ccd59112072c3a91857464c4952a885f30ad5cae12a4100173cb1600b552a63dc0883f5422e26cd4ac6bd044d1e63b1ca9c6d1471c60bc40040771e74fb11a4b23f2d4988069c9bd276e7773b437f4f69fdeefae14a5a9924e28a063d97b42fd843e81ae8c34557a988ec6a210719cf8ffdb5a9f9e918d6694e64fccdb915fcf98b62af8b40b79c6cbc2bdaaa6e4fb1c8a957c0efc09c61ba19093b07a1516a96e858fba65a05e7cf16a70f117cb6487704a865c6bf6edd606cb5eb6344be7f72a2ccfe9fae24ec93b1b3e5810146af75e0653b343b2a8897fd398041c9eb9f2f537a96e27446ce9efe98e1bd72439299cfc46d5da6bd520aecb4bbae01212b0c7672a600ea6295f4db58a5b9c363d3ff7349a0317f5986a70e9bc52f0edfadc2a0f61c0596d3c50413f50f56c4e4c51849ef403496a1f22c474d0812a73b586921840095c093351bfd29d4bdaaa840c94052670729b7b40064a28609ce26f3362c4b603d6360be79ec1b67847b69510db70378f9230d845b106f5550753ec23b87c8591766494d8fb0552a5e4a437bc62cea06ad937d72aed82a790a9d03a589ced49829fa30dab9a35e96e6ad6fa1e9f8f9f021fc7ccb921ea6ee43700092038f24a1e337dba370d90b869ff2ae9d307f92d1fb7be35ba88efb60f0bc7b32986fae188ae5c990c747092ac447850645950ff40acf7b93f4210d0090f33b9e946821a1d5c3fa50f20d2e443d8933ba42b6be7d9b8a6d3858467c05e47570f207ffe1546bb581f7fb3e790d838adca144639bbed26a8e629ba878ced081b9aca610b25a2f001626ebc0eef8823620ef356405cb6ef2dda04dcbb9968dfde6a8d82d50ae1b6d153c28bd3d4580ee532e802c3ff26f42013bad61da5c54ab3ba5a6d42ca5e482813c52473c6913d35f3e376ec87015947394d749c4b9c952ec50a00d13668b62656a7cf703b2a5ed0e628dc196e80e35b317e8d1b07edf7cc1f17f6403319c6115b3a28e01c6757dd4509495c6d60b61d83fe3e921ba351caf987b34bbdc5eddadc457f8dc06de9f2a19f8971bb7517f2459166947dcc8d516e5897956f5bb0b02d13686d46bc44459cd770a20d7e7c120ef074212e6a1b6d41d62af94aeb428f4f4659960c88ecabe4ce91cc9744b8caa596e6808e89c9264577dedc235dfc2e66b8ded6c14aac8e166e0a37ecfd4b9b83b12a52256a4322b04359070c7b87ec62bbf0153ae810ff32777f3e6fc552af48fe6da06b8ab2ec8370322dcd5bfced48497122a8462e0d97c7d401267cc8fa43df07e3c35bd7d7db9afe734bae24029ee0f27a69cc7dd5b41dd0904c73dd33898f49ce93ce0985b73cc1db77e66f112ac2db6c3cca9b49dbeb188fbf2c66ce01665839d984c87793e28c1199f2ce136b82f3af6e4de777e1e3600ee06ef1b56497220ecd0edf2ee279fc5bf24ec9dc835c4d55adbac878ad7485e442dc44776bb797ad79ff97aa0b763a72cd57f766af9ff69ebff227ef453661417b9d33c53f39f7265c8db667a99ce3c5bfb46e3df1fc0bf70b88cf2fdb59950ecd9eb1b7d0043be8a7d956d175e6116aed345f9ed2ca638f9d27bf2558aa7d8a7b840ad65f87d4d5c0481553dc9f81ba16ed72fbd7d053a7b5897a6f46d1f94e27a675be71ebd5635f1962c2a6027a97c44fc68488a1c14dc378bd1cc3d1b43757f4ae40b2c9bb5853f0a0efacb75b13c8aa0400672655c46f05012db3faeebfd2e088db1b738af8d0bf1fffeb628fc38a694b0fcb91cc544b7c32a4f60e024dc5540bbfb81024ef48f48392ad571e17da4eb69201c47cf5c37b87df77f9865eabdcb2d44c70049b4faf25d220b5eb80ec617041750e855f4e6687628bcfbb9e2d741d68b61ec99b0101206dbaac3821073092c0e1e00ddc3457048335214471d16b07da708ccd965efd74ae15c5135d172c4d36f9b1be0c47b9698abfe904614cf3a451c92d1ae2b0d5a405c8a86f2ffc54b82689b2b0bc7f06df474055da040295a87235304419a86fc42f93c77d8fa26f1913df8d8885a5ccda66f2a322958d80fea2a472f6a333c8ab66761b5e7d776527224c0185361e0ff97d92a8c89641190a0d6e2bf100e1d733154fefc33f8e0ceb84d06c8d5388c831a512508c03593a29fc360c5332222cb47d3f76395a54c96585b46014d66816d6302c31f03888f08e6fef60e99c347083d686c53e66014b2d7f3aa6a40faa7066cb0941e9fb0fc1e45105d6661cff1df877487dd0405b62e07650c704de170a2c4350780848148270b78d4ee5dbaccfb8a3239655fc7b1f97c5e4d79314af7156063ed6abd8eb567db3d7bc64e0efecb94ee6bd6a8da21c8824d6a8199b6c3d3043b27e8322ccf665126cf77e56be0f99b3e618cc69211fe20144a515b5f5aa9f992b7ffb08b36fd95e7aa3948024d7d424042529062b4fa592370b355b3a743591f9916b06aab3c03760093de1c6d4aa12e17cbded4f4ad9969d1eac0f06d09fb04e4c3868323b3d3b247a144d795a936f5a9fd5ef8e0cdce71d4b68891fe4ae046f192f3a241aaaf4ac6a8c16624679701a439ccd1c58a24fb31045d3f84914bc5aed122851dc2ec9874e6d3c0f0ce4a1a4bc05a9b532161dffb13fe440406735406507259db663d2d1fbed42a564a445eacf62a7cbebe791c0723e718177c0eba5d032e1e7a681ef1293f6f9f9f9fb9e74bbbc24eb88a70576d1a05913d5606564a3214d94198050946bb09dfb8657a872c2274d67b2b9446c74a6e9932d4f91a1ae45108d83d450ee37037e0413f360a6660bfb34fbc11f74b4cb2c08007cb5f64975889cdd83df3af1c046ca98eeee5a6d61757272902b513c50c2660b4522956a22c7ca859b1f2f19563ebcca604d34ca5a279fe6fb14338d2cebaa445b95c36a16132f750a5b38c7119fa504d877dc61f228076e56deb965cd3c1e0a119211cd4046c72f6467375d0697a549ca60617b29d60f5f5492e6d24b1dfc7659ac44e24f69f49758507994ee1cd5c3203c2ef441026c3e04a4e54c4bd04c8b18c8f294d41142f20a1372e27e07c48897ec7b87c69ad28608c46eab274141075611c9d38140c4c60f4567a4a086028eb46c3780ae886344e649a076edea083c1ea3f66a7a9bdb8e21aaeba4947bd3cbc2dde0764b28a744d622873cda3f88b65a454288633935a1addb590eda51d04548d0cfe976f2a8bab00c66f16775034b13ed9e9aa509a3ab4524c5fb58b305782cd345a4eaebd31076fb6fdace4916229def1c7cb8029da8eb939d63f4fad2000152b351edf71249f0b2d17eaa263f137a89ad82c5a94a9a388441bbf20f4f2a40694d54b500853cfb16d1982e2c94ba1e7b2e1c0365cf71c696b4d6c848b5e3240fb7a071f52a9ed934c56d2479fe0abd077eacd60bd4dbf16d451b6842411c9fdd3374184ea5215e4ad16c20da9cff2a005f3036617eeb33b50718bbccb46648ba5e2f6a16914d6f9ad2867f612b81d50cb9e5d63acc04891900a0e311ca91a9ea5d606838622777f96688a41a97c7fd095d36030ea05c5840a13ac48c9a6d45dcea7a1af0349f8981ac4b6e17c616e70d19f8a5e73454c56f498c8f2a29552c826c0066228a5008ad43bcf2395da2980ab23dcd8b49732ffbc7773004e1466c8b189040c3e7f24bcdf8ab8452df50699f1457bd4988ce941aa1e09d2cc8d30c99d8cf6ac6554f2a53f00842131021c14ba6ad314ddd5c912f12d7173401e16f04c0580875b3e2f45851d03f7296ca22ac7a4aefe6e919b5ae5c49f7c0d394b66b17f73ef2ca732b269e4c17324fad68ce4e6d4cf5339f92c9ec33a61afc76a573073b9c661845a32b661e746474c17fa574f194da3a76733f944acb529b7d0255ca7ae62d01b9b7405cf08e6d87f48277b60830e4fc70f893a247a080211ebe3f12e8e3465f4970f8a25adfd4531368b847d81a59200693ab4bf6bba2157ea81f2fa4a4aa8b672282035ff49e7511867a59b150da0cdd51e9ff4f52d4efbfd24b85086ef4994d6f0a495c1493bf36e8bc3a1a818f0cfdee61b623c248884acbfdf6c4500c074121a98e1682b0f7a1b366a72e5ba45101bbe0175e32e8b134bc5ed847435a7d06ce14023d7dcd0bbf2cb716b88404a286476b8419e888ee6beb0df853519b89ca7ff6c0366936caf064b1c5f1b2f8c09457d92ae1a8146142001bc60dfc05023f9127aba0f7a2b8dfe5e64ee53f29c31e3893a12ada442423e190911514cd040f2493563837a0aba541012651739564d2cc57b86a75661aac4a202eda38991f8e126cf74099a4222ed67d22b3e8a06baaac3b294d91379deb6fb5ec77c179b72631cd62460902ac8f34eaff4c40c199c0f6e75e68638724bded50f8aecd1183f4ff6d92455a4009857d2b41778faea65b53a570489eecf07d94b68013c3b296a59ccc39d12e420e02668a4873157e32de3645bfcd77663916e80bb8c92e8751f4a03a071f14efa11c01e42332838c8bd8d3c3aa7f10fa705f2ed537867f7101c6f82257a0b7552da28b4473ce54408706adc18fbbf7049ca5b4b511f4d1bd43438c1d6abb963db66cc378caad73431b1e2de595eda73bcaf5e2ff872fa6313c2bf673621b85ea366e315ca38520d318445b05b7c4585f40dd4beda6ff7847d50b6cc0b575d391bf8f083c291d288568bbccc0da89a001d5038900a4ca35f3978b4dfd4e064cc0945b2532c3e799ce52e36b46ee4cbd6f36b1a1a6da315d56a3b8948e87b6340333117ba62e80db55389517e5eff846210f3b77159c687b3f68bceb817181695fd23fe7470d719566c0c62e5cd69141c97c4f538d56420bda604900f48525179c7d4cd514206515d5722697c14c80674af95f37f6acffa2317ac376533e787db1009c49bb48f6acee3ac2370b2780630aa56862cec984b12d12abff05637521266f7da6ff97781e1c869f7298e320c31c30b296c064aeb5356b6d7d5ffec855bf7eea41e4bf1936b5de845e2975dd16a0dbfd44d16e8354ec76341f35ed6d7f795e4739b9eb7c8a8864d390286deedb7ebf8f97fa790713599f9890a2034044a77dbed0aa84b375589e644650632983e7a4e5d035c56ff29f2ce79181ef32bb67c888c23d85c9c360328cf5126c309aaa97f041f04bdd4c3be3efce04eba510afd55f7e9c2293300cbcf32d1d082dfbb7953b114517b3d2d16fb6f3a4b31295a96fef3606c1468a376fe36ec12cd93771112feb1a81588e040faced0ce8f08f3b3931211b40556d5e4b279831a0c0db6d3f612cb82e252c0487c6a115aa42983a06b41c2e8d05c44b5943163e80b2cfe5e70fbba3aa8633cdf268bc435cc422be60673819c46d75897712e1bf83ae2fe7c282eabc82e3a3588ff984eb3ee7afd448e789758a6f6776c4a27eb335c529d164741b4cd6a2a4c669fb64b7be6a7b83684be167ce376914fbeccbcc322577810241b0aae424b64580d86e1632be5d05d3a6b7ddecf61649b06b3ca4e44b0126fd6d1b87882acc47b88a7f95748efa7480c80c0a2f11a03d19d5e34b9d4aa29e482164df30021f1e272f7972b44aadaf7b9297af02f96a4eaf303a743829c675c4b71d777f6f0f4632914e31edef7a3226fc32af38816dd7e971f5c4a32170517f4941d766176a1014161807efc3a8a23e5e42a1d6eb8b1714835eed4de404eeca1349e3f077e09da66f80edfa9d8b82b15acc897ca200ec1d68948387bfbdc01e44b4e13a768dbb7e3102b7aaf6369f5f280a2d45e14bc35638d994c44c7c71b0006ab9097a1db7ce419150debfe9798ba93364c2dcfb0f909f71855f232360e6be38bc597790cda887e00278f5792fb53cc61773040782e728d0be49021ff68ba4b69f574b9d7d4c6184564d08850dc2788a90d14755538412278b9fc8b340a11ca64ab3edb14d95701c206ae1ee36d5d621e744d4394a99eb0002bc498d67e81faed6dc91c69f8f5089a68c8b01551edd80acf0437a7e2b6936387179b38e1fcc1d4ea94515abe0d3fded7b171ba5a72fea1fe1a1201947383f525cd41639ef787c5bcd22bff0982949d6ea7c152a990b94ba60c4fa0ea054102d1ebcae1d0da2af52b94424e229e2a69ccef43a11ce71ce7f8b19e05eeb1e03438afff407d122c93d430d8d15a5dc3c8d269fe104ffa09d0c7d6a1640ffd7aadcba9e5897d966f91efd580ee2a3757ea53955a5509b3d65bae22210e0c0bf9520f11295a53a4f573197218f5cb167b5cbc71b8ea2a3c3e3103dea21c81f6c0237150b7b75db481321fa39c116886ef957f7b328ae5f74bbddca7b6f5344fd09b1db1c69f820d503f49e6a7cb8810ea6ad87ff290511670a4738b6a0203b9eecdb116d4d80646f747c8d97ba311c0764581fdf98ac28c76210d4593d81b3d23be4a37ddbddee67e5470bacff2761551f00ef5d47aca184cbcecf6296137e0e37df8b38bb9187a6345336572a1cc34096771ae6a8698a68e5d18ae3d965cb178f347f606939b260e2acede0644bbee10189ef89abe5b0d7fca0d4020ee4dc8338d09b4d6e17ed2aa611683e6f5420f1da711cc5f5c9c86423e28253eda9832dd8db6233bd6e4e7c468f6cd7c1c03f8d53889e816f44629373553a1cdab5fdc48851c0d0b5c778784fa7e5c85a3e7e72d91815f6ae51423bb985ba596b1dd28ea32d9a9d344300bf54e75bd76bcc1d3762b57bd9ea6b90afc445764abd8fa4f3adeac6a892ccc91b7ae8b56ef599b0df8bb4e384470be7ca10f866194b55b90d12451406af1d447b14c8762f36b13e5ed937500a9463462e16ee2be628df16d1ff7855a8274010fc8e577c65a7e8cd6f2b66a4a2c848b5b6ef99ca7967ad81ebbb46ac16fffb7b235b008b1f89e20e396955e050c60086021833c3fce2d4e36b95a4d8faa574d76c6fd147a1e04702d3da269938fbc92185de559ee53dd0db0fb96eac950f01b39454317cf454fbc75ebd1d7190f5bdb394bf997386fe177aabb6f60b4d9a5b99d464f8c8b10fadd7fd029483e1562dd22da7874694d392103f92dd7ddbd4d89e804e6133495d48b0a240c97b9978362fcdb18ceeb8f39c64d4bfe4c58e6f7c7b69d73ca406102966e160743cd8be15649d6408e4aef42162eac6fffc0de44425cf5debe7de5534a3154e50e3a6a789d965242217c8b464480a4ac41050e96404913441f9b13611192773423fafdbaf6885252aa3b019bf35f325d3a5ee30295fa524e52ceb100b5df4bdc087e6ee439b2b2186581e0c7049abbd701e89fe00bda5c7c2795d30026b24a464b5e654bdb507e9c9c482a6654e759c998c8206b71f0a9627f59b0344f532792b55cb50f4f796c2b345b33940be328a27e1bccd1298d3ea569848652542a314acd926cc951e4229d1ddc4b2f728c58248f6258b153986ab68417fb008fd6d3a58e3a8d47249d5b055cac118afd30adba1a9e04107d04b5534c5cb77565ae04958ad52a709af8af0cff5ee8b715ce97c08f821627b17537d6008684f31de2a6c09c05655ed87d315a36394669f10dfb07d09f1008fd87721af0aaec924d82c8d246d20ef3f158aea12687d5c5f4d6cc1e4ecfab57b42e610cf870a9433781468f94fe50171b35da3fa1cf93e5a3ab3e8faebddfb1d566becaa841e0980581981fa32c9f8e6db4dd169d66052cb9d4925373727953a7fb8f19b14d0bbe703c93588e3ff35054398b27302b087d03fae6d92bcd29596840ce942a8edab1fcf4be58f3e3db8b52d91b243a8620390ae971908242bf7fbd5360faeb19c70baca3e96f3a8497acc08cf6df4992888edc6c67244a065169c118df4dcc6be1d44068c39e26693fb64f8df61f56c6c079e551f4ba93fe60842be5f61e015a21e43884f6caee37f962d3540c8d0bba43112d012f6d567f98d9fccdf602d123a249270ce2f59eae36b2a1f6bfcfbca9d9f7f44b13f70612366125fd13472b1a63708cd7477e3eea693d7e4eab06f3b971e2e519b2220189c406680668f9e4be10558ff819b58bc65fdc7587de86440f1623c3bc5baee3efed42207283b67a2e2f0f4f705de34ffdcff4fc5903d9aa358c3af4ded11743fbe071db7bd98bb9ce40f9d18300be2af0bc2341437e4b240728bfc42d66fea9e9ca77024c2bb671dd142047885293192ff78f48fa582763f3428cab6d6fe9770e90833b1480c914f88d0d77ed73aa3cabd85e101a9d2e8f109ef134893b4c840c15c06298c97d590831b28be4cf20710f89d3bbb012ac1459616d50b2b4c221b7f363283ddaecc9690e66a419c52fd5e84c228681300844ba7925655bf98d010006feb986361864ac1cbedc63a4139ad3a031f8181a0981e6a182dc7ab6cff79496b8aa4aaecb2b209f2ade21197413dbe7c7fb9a5eff83807657af64416300935704677b44408a6415d523fd027dcdee8a19bf969c0cebb8a776348c7bb627022a84d873bda054c5a23c1ecf3224ab2719fbb9740e6fda9626cf229d4ee323809d66c30e1fc9a3bb683c0494ef7f95fb2b060258a7218056d589be600bd8fe28a151581751915c782674e6ffb623294023b30bfe1d5f9ed3ea15c0d94fe2527148a4b1fc5458522480363b82d0aa9334a239eaebffa5eee83c0175c09fadf69180ebbda91cfb6d3c67effd3a7db74d6f53fac42699e8ed4024b1e85e3ec528ce53c772d9e483b249c64757696a3a389ab7562028a671ec60ec8329305ec4a2456d9eed294f0146aece6a59b79713c3db73ea49bcf0722ff33eb034147ffc5a175387e5032a4451d9947ed8652fc01f44e7f967de58c7c94f525712e92286d57397a9b19f63419089c836b68ae9e0004c2eda55dd4c82c4859d3ece3e6ade01a78b614ac3fa4ce12fefb52a0f552221b57a08184f021f71456ce7cb3371d6d94cb69e04f2ade483c7091360807caeff8e204a482e4181d2011410c78ebbeff126703844f5ad32e4aeca8b4eb4e3f305b8106bda8bc873773b81e7941168834604a42571596ea245852fab2bb293819247f52353bd9ab2450510f710eb540cfee3b50952eac8f0ccd3ded4089d4e2603691e8658832932946beb7c4a02d4db161cb8752858a2ef236c78f807a5cebc38b05435b6b3c5616304469a9c9fd245612bdd066308232e09a94485708497472ef2498e0e875d6707a68e35ab7f4a22633cd4b8f69f1128ffdca5c0624375e6e8003c0191e48d08f0cb659b0d08f7ab0950aa1e20c897c561a85630a3e2b18f8d37bb1f2a756ff8c927198d2d24584099fbbdaabfaac20274306087bc629269af20666dd7f3524c1bdfb8a1e49541d1df30b53459ce64e6cff92050837fd4fd93cab47c80e3b431b941267eb4cec6720534528d3628a6360f53144610fc4aed0fe1f931a7ecc9a39c2cb7ca603651d14c1c2aca9bf0c61069486d708ae2882f931f39ce619a5e70cc87e6368cf38ed97131c1145c8492ddef5676bd56a0cf659716638c7e7e8df1f1ea5047a45f8e5bb1b6045085504f13f518d410c7892e1a36909c9034fbaf63db1e0896337418c11c686d71d142bf4b445ceea2daaa91fc95fa69610f6a1950f02ac5db4582b9e472fc78f5eb896880b4a74e762342b0145927d6b5f9a6e43c6683226958b02e0fbbec4f0f3c96353da961fcfd1c39f9c368c2b0cc4797cca794037f9bc06da7db7bac34d203a9c06788649db16d1bff2077ecc6220bca84de9b2c183159f0ee2b5280ba525bfff3df986157965284d1890b3287ddbc664736a5ca7eb25e2afee50a940fb7e3a882586f6677914fa48ed865154753c7c524d63b785d374d4f53bac436b9c1760f55723be6d3f9d46604cdf32bf76160427b338587d8c0f46e06d367530f2ab4bf0b2e69b446f177bc75da7185d47f5dcf6ddee7350275e65fe0d8470bc5623a3ad6a0d0902bc21b06dd8e2a3abf905cd410de32925bc716a3d86267c4c04115a21a0d20dbaa7d3e96df04d8c7598f0e8fa15d2b742657e1b6fcd4fe14d2fc39f9aa6219ded46e4d3fd876e02e4aa7e41f148cb7181b6168123b44401b1188c7ebe7271d44ecf7c42c8b6eb529970e9557c202b4a553411367ddc656f85a06cb8214a4b20f7e934128af6dba4fcb8c23dd2a5bb15385262aee30602d13390e5f7af58d0bdb01366b3675cdde503164b9a75975b1d96addf1bf02bf2be712c259b7f3c498e77cf011663fe9e65bc98eed0a458490d2e621fe1cd87008a190b09c2f50e68e5167de078331734608aa5a3efe994d0b832370f99bf3e3a67a894707f98a2faec0dd8cd48a24d98f059a23774f793bd33bb9f79972a0059bb340a6489185a0db6c74e76dfc0e08e0621e56143b0e0f1a6c2298c1d76a3114510528f2e08d901b68d07772067170a05718fde10d2476743bc06d8e34a317ab2f4bfa927a1507e1ea18ac89de1e33d2c3f6da054d2c3ffe0291dda8851f1019d864128ced7bada9239d9f0afcd9b753e2c442eca4bd223bdcc490b42dee88b6f63edf4c94a4505bf8da54c080402746b217832c831e3d587e0256be3a82a3a9c2906f22298e1e395d5cb4131c5ba91657135918464e1e18386f3cac20591d5f9b8d1f0e89904fd163f3fcb631a1cd77bc082e6599a3f2a4f4c8e26954c7600a3c504fc05f7d167290b1774faaf86c05394b553805b7c4d70865c09230a43aafecfb1272fd4284419521b5319b03a598629a005e26c8fae3a9eeea510c3d761b0f53cfe1b4061d42431f48c474b70475b4abff4b99820163596fb38a4d8af197ba6832b606c1c939608cf1db118e3cea5670e4d1e0fb2471cdae58a626f4038a684686c68ae64a9f2bd74759f0f6f91ae0add030864421f0afc8def56f58f3131ebb02e99bd18327ceb0c3e7ca22c08296a5318ba31a0868a3e80bd4ef9ef8f5a63f2951efc71bce656b1f891b766f0adf619698aff31d5d11f02ce4e93022296c7abba33da3e83aecd24d791d24642ce0f88fb6e8c8286ffb887d22452b4fc262d5a6e915c72cd94c424cc98771194ba74587d8984f42dd0f5d2cc1a7e4b32d7ab0893de63ba3a20e54b266352b800dc2de80fbdf33598a68cb54ab9f38722c78ae0b425ea6ff031fd51e7fd0b27dab233ebe41e1b892f606ceabe49ba1cbc6edc9d2fb98f7b57e78297d3d6e086af4466bedacb4ca09776a12ec2b6949265a49bd4b6ddce37d9bb20aaf44b7e1284f9c4f5276ff8aacb3453fc2d2ba51c9a8e0c50e4fdc278ea5d839140e527de30726e8089e3c0c5c9f7d4b3887ecc5618b53da00b078a5e2bcb0c50884356eeb5594a1c595c8f74fcce6632446fab8dbd306c6d2aa59f99a01a35126919bcae6cd7a825a2cda0b3c2e542f41155747998f13e54c38f75d9e281205c4796e2ed31a08154c99653bafaf9d7502c69dbd0d3c3a740f3ddcd3b3e5f2786ce0c42f10d8e951c2d6e255c02157a94093cf4c4619b6651c44f3046cd0a2bd31ce0bd40f8cb2151dab3e13ff7acf1d2c3b82198abfbb861422461d3066cafbfe77cc489a52d3981f62a5c766ff6c2e29f3349c48c1c0ce748e2255e85d70c4161cad2c8acd3528889158ca05248b5e30d933badb774cc585c0a187a4b5424c9b15a322e0c5a6320ddd445eab8881fc7e34a7d751bf20a0a4f8fa6c05b55ac0e7eba2cfea298f5b3939383c24ead1a2c83bb5346958a5fce57c0e299b4fa4fa5601c06b96cba86185dd0bc115e215c7c7c9fc1d78d242f5cba099283619e7cf8aff8ee5f82ddcbbca6d33b869528fa4e3e598ca5d14b6251ae4ee0dfdab080d825abd6d08b91c28cc34cad010a3e6867c87c0cd14d00f2546d75e027765f88ac9e3dbde2858920ec6c6575ea103006f19955c3ac3cb5a8d41d33b8475bf17f05853d1b8c09a1b25784a66307fe7289388ab84bc438a46262f1aece39e3ca57bb1b2abf550d063334a1546812ea9d6c8a205e0e45b6ed2f6e115819a419c9578b1f2d1afe888c4b79b666937ec56d2b3144bd26adf100e41172bdaa1eedb2df12ad8006a66659574c006c344702e24b54fe09a3fd7d2a8a87ee3c55161467c80e26554455d2a0f0e4a4910c4bdecca2485707658734a6fd64483de1a901caa2984be1d108d66a9b5d04f6c2326b6cf8511ffd7a049959e3383604068a3e375f950850e356f5d2573e5f77a4ba2c04d04ea1b20eda36b88ea044f3706abbd37840b5f3787ef14094179129b54c283393fac80db3fd317a2b8f553f6459a7419d8b158589f04c5c70260255a1425be6a2de5dacd88961fd5ce8f49f278627b4831eeb3a6d735688b7a34124f935e03c60714db3ae3fcfdad8c8dc6ba4ac3cb206fbb7c4cd9310bfc0786f1b5e815b5360c49ee7e8d26f933823f05675343d7b9d53e90602b2aac9034a94c55c2e3210a6a9b6344e6d2ee78dc77e31fe38ce639f862654488cf0bb548eb32fc4be9631583c6440edc3327843cd80c96d27cca7cce4094cb60e3a701f845b02e9c91fd189dd9ea29c9e1e832d15084b59fb9bb9b213a02b70cd81c4328748b9a8893cd0bb1f845845ee25b9c1854f0166512cad266bf874687e298b8f42785c2ea1014d25e3b6565a9116941be22b8cf919327970bd956cdcd3dfd226c80e573079116ca1b223adca1264ab3962ddb96d0693942c0f33dedb2c9a662b9e3d0577c2f5b220892cf439be7c91b9fc89c270bd4f781bda9c1b97a8d5e650afa681abd0bbbd8c4c49641c0912a1a558a51243ff8a13fd7a1e7d0090ecca7eca19726b082de1cbf65a747b14a77889e1e998d769fa068f6341dfc433f85a41a086898f67007a9579ba3c29b5a899892221078faae6fcea923cf067a7d4484c1e2a412fd7ca898cb1eac519c3e00cbd8721b4ebb99dee40857a36c4db9ca470f830d13b5893788356ad0da70be61b0cee21197c6e74555fb5dfda90320a4284ab13daf28b1517552f50773421aaea469b2233b54bb2e7a78f526cfcc56c4d6040394b9ff8fdd1640d3b2bdfa797e66c22d2b29a155a2c25037ca0961dde875ee5f547602c66c609e8f6e53138cf986d80c78d0a5e8117ac743617797dd99970920daf81ab550f6e0a4f1caa0fbe1ac3f52733169a8c3a4bb8d684958a8c5029be4e821443a8f4145a04993ac4e20948642a2f35b6bd54d477f704a8ff3c7417b142b2b2e8591a6c0cec5c2de996ccb2c042d81e137e5b8c945e4cc536e678b11cd5c32be80e86d2f1ef04c897dca657ab54e4e4defdc425742c0dfef9383cb34c07b4ee36c5bdbeea8b84b0d25f58a3593b61d9a80eac846d89e50d9518ceec09ebfe11ac102547149125790c512d72ad759a36de382e7843f62758ea980317358daccb191184678632b5b5f1bac28e31ba1ac35605bdecbd9940ef1c7f252e73736057c43dc3cf543f284a4252f5fec770561443c5da8fe24e2481dca7fd0d38770cdc8299ea63a3066033626d8ad96b5393541a5b873aab6b5d53f8e911d02469ffa36e8c19df658716d0bf2500f3513811b7613eeb49488d02bb490858e2e017b17c4156be46083bb2b4e77d57cd1a716d0c249cc14ee368cc830c5698020ba66d958a59d0c1ace285f2072852158d12a856e9f31df085ba114c9a721e81447a0e2b93744e94117352b29283f8b541a1bd92f38221b339b3cefe0855819315b104d4cce7cc5881ba176991c26f5b3fb845416b30e7a3b2bdc139da18904c0161c42ee339cddaed9e88933bbf4c101925da4cad0ff333ee312e0decd57242d5f2e6655aa4b6afbe36b2ac9914e7bec2dbe0f8cc107113b5576f3c8a4ff7431110e59b26352e81c378118fbd9f70c1da569b62c64c8c1fe47da2deb45d10192a234d5089fc44406ad98596d915e87864573f2ff3f8484888c8fc5bda034b998a2b71450891b70c2257aff1a81f5ccc61aed5099702ec7b60f12985228763492c18322534ab3afb11105febb0a3f394a5b1e19540ec401127a76b16707273e39e80888231e373ec35a71c711626beb21d2bb07e413926454ad3d4cd8dc9381fff122a73fdb05d54b0b02e4aacf22321e2ff19a7e6bb9015f6293708a953d7c16b245a55e17c946cf08476fe4a0899f6dab3c14f7a6b284cea90375b9f3b7724dc781498297a398cbe274eebb29077002e863af2cba0c82e31562ac05ad5dc2c86331251ed000a1c830984e3c777df04c6dc178ad935e7683a4ad43294d01f6edb9e1797fa15c9191f88f6185992dec6ba7ff961be0c6400014790b17d50850406bcb019b0d0eb628f40a3b4366d68d7fb4d0cc4aa706bfc3c8f4030915b645af3957f1b469189de2cecb86f5236e225db0696937b7d3d56216548358530a994fa7fdf2276ecbebdcc42493b4fcd2fa5c71d9d6f166aa0b225b37e8031f67da4fc222164e2ba52deba42aba3566fe4a6105214376d70d1751f284ca131d213cd879792fd7ff1f2e9b1aa9f896b8924ef7a8e45dcd8fd97b9e92032850cbc3ff76d330dc40b45d46bb3543bf69f051c598e303b568a0e1e53a2c5542e8ca32f4336011e36a9805ea5fd446744a4e5ea5f55bb7a773899e43ea13aaedc9b53bc1a1073c3fa01dd01da8830228726146c8de594f386a4c920009602807bce027bbab820c95335269406bf82e703fac04638089b38fb446ea734d1d178fde7f1c4e940d2b52a8e68145445cc38fd55968e9fa3d8feb9b676d080750fa7b698947971088f12aad99ddcf41f1a5c93ab9ac679edfe8850c6ea425ac39c72ce2b5b814bb3adfb2f347c3e54fe9adccb4411f083338eddfde64ba2012d45a5058ff1255163dd71e4fa4ae4da46faaba14297ea9efd2e6baa41a1edb9508e3b67e3bc6e8975c0a29a2fb28fba630cad14773154d2b1abba50cbf7ea2108d8e0a8ed1593cb19c506dcb42ee260114e6bdf8821146933cf7f5e3272b13dafda40c0169a98218d85eb79672bb1b2e1cd0853262e944ebd01d21602514a05e342248d4b08e22eb43f8546b4dc430cf8a8450393ecb1a31b9d0dd8a2a61da570f94897f5beba7a462c9f5367805e043f235782e7e537c70eb4531f3d3c3f1a39be4fdaed6ead26b1ed761e539c2230655b14eebc7541b234a14370994e7d6c78af4d41ba467807ce2218c4d82fc1087a332d35d99b1176ef3696835e2e40cb4c6b7ebe60edb414cc331e8be095a3ded7e6bf81c5d0dcedda76d04c2aa7a1a0688db4fa6aa98f3c45d61210d974c0046fcb5d11904094c6700b73d1c48cc20eef16ca8dbbc0181e88d532d18ce1a2f74b5296c1c58ce32491535290fe453d41413d9449d0cc5fcaa79b88d8423569cccfbafa3f502d44b056b689911612580699422ccc95022fd7ab5919c285284bfecbff3a04e50b7614d971b542b4c4dff36a5de5de251090899c8a99b8b2ae10121dc2224a5ae9e0e84338545802c060681e0ce5893465ee1d2f38a883f85865d4b43c2a240dde6857ed476803c60dbf578d8dd1fd2f32172a9d02b3f94c95ae5f2cdf2b065452615b8d343d91a915516b930730a58147428dfa48064e4a99fe91d550bdd03812a92333e4b3a78649d1f69b484fcca9a5371c6367682e864194a4ca01d90eb79ceb9a35e34e9a58adbc528268d2e1580fc197a89b6b03086588fd187cc073898f437bbc669cb128e7b7cdfff8544cf517f2b833cbe1233a9aedc78a7b9f446d3530d1decceb6e3c1e8f5d6419cf025d289d75badd0f3e18987558f50b526cafbaf35a6d913aea65b16db180a0cde4c0ba6b463730267efd591483f515730fdd8c224950870f1049d3a0e0f1a4c4d2efc3f125466c1b318c394d0f134d919c289fb21801438fb85ee4eac5a561d20cddb2c176733a4b502370d1ea802202948f44349da347a7280f466c96c5242756b710a85d2e88f0251ef70031e3e85342a0a960badb1a693dedda6da76e02d2c0993235a9367a14e38902903663f6b3bc7033108de9c170f68418c40a3f3b9c90254f7f4372858715776d3905f7e2cc4db9dc83f2c9242f4d73e5cca68273ea8a5e9048cfb50091944f7987974b0d8d9a89fa6c8e81456c4f5e1240e889f36a26f526b753ff549a7b483c486407d719eee4855cf8e3d6da9b7c4ad92e2cac7612ef1ff41f5264711a8a797d6d424921319638aa1652a77a4c109a607182f9a0b22b2baff5bde75c20b990697cd60068bedf5463181ca2f820fba50599e12eaa09f97cea7f7768f1526c4dd39c5158167182c1fe9b82373865a29ae35e1d76956ba68ee21428da18121c268ddc9d38a2364d695a13250d139ac47f5619fb8afcf04fda0ad034cd5971e1d06038e18f431e4c07c3953f792d40e26c10cdfa523be712bbb263cf089c353df708a60d1e13ca5ad964ddc07e979b15b208471676a0bccce2238b2d5e6710a175d31eec4688a80c66056ff03fcd10fc64244551556fc2059a7c5058d4124fd39a2cb493a13309f1e49d3200f6e0436e0fca00fab147443b7fde8158bff75a24eb7c0acfb4cd047acbab1735b61a0eb6d61fd81f9e606a0cbf94a624f30649adacf1c742fafc048c25c8e1c08543b105bd7f576b6d38834673d34abd69007f76c311c4e902bf80464f914ae5acea4a4fdf20c1a67a41e6153e3c87ce422ad85c3595663c108bbac3323f791d275387bcfab21666c357078436d9a9f9ed2208320f0cb6322c32125ea1673579a3eaa76a8f0e726f9bf2c0bea7abf25aeef491fe22e674ecbce9a7831453771a1614cb67187d419a1ecbf8ffb5c108b085d29867d7e9846d4eb6653d4d99dffe46352533ea85e793fd39386e312ce8fb5cfda468903db37e52c3f3281d889acd0f2df8afcd19bc29b4d8aec5f156941432d679804700c38d9c07941fd17d5b43139f192effe95247eba61647b77caac4fa21584ef124b0bc24dbcac1adb18bbbe264dabaf339650dfd09f5c0f647915c15be98b165a8007e345274b0ae5a72883141e56722762750415c47d6f36ce0b0da24ceb7274ca6e1be5ce260322b2a698ba737bf7050fbebabf344a09f1cbdce722e7f566af567dccb030fcd51c9f667ce744f900b22ff84fbd3e87dac73e0035517eb2668a14726758cbd9710c944b9d51502442be1db150591dbe1b078e3d7dc020030b32feeb27c281600c928b3a36fb04ed2d077d4dab56e97e9c2707a14553b158d1692640ee8345c3fb2fea77d25e4a10ff8c2cf10df133e888d6e9f23f14d0aaa2bb7731cd578bc5bf541232736022dd9d0c7530b13059465556cdd0e5a4fcbf2bb92021e21ce9f4da86dc3a93f3eaca39dec791e11a7e6ca4f37594f36b1619221e90b7df84ddc509b920a610b61095788677c95f09b4867f988e7e3b10405f19ec6f21aa120c26cba98f05faa090a50d14288d5c6542fedc914db46f093dd0df6c61bdcfc5dea94ec72e647af7951e8e884701024296417074d71daf1f269bb7f6f077f76a3bd82f272c6849719cf20ef8b0bf9bbf56f7ce92ca2c17f0a217082387668bcfb0cf272312155d8a28a45d3fc9edd1e733fdfe1168a96657cce44b4f891be357f75dc98f826b4d4590925efaac589ded1edde8a25c4021928b612cbfd7c1bf46a7ac8f8813818112eee90017cd8a2d46436b6ff39e169a8869f69773f653ecc2032f57f0725745c9b986ee452a7f064284628f32599d3a88c429bd34edd4a489b5334f1f4dcfea84badfdf5311ed0cfb48d2bf4a9535b3c4a5b648e8085896783845bcd0f53bb3eee625082e383883a4d0ea9b3912c088eebde91def124e4cc76f808787156a62fd041cccd82c79c2d1ba2401661d22787125542e19655782b635b1a6f99cf2503d7f5fda5ea6e2a73b6bd5d291fd5e916418aa11fa78c2c68cb6036805224e562df0ac5ea32d3972450f5bddde9f68b7087328f846fb8b6f81b2ffc41fa3e1ff6e3e52a2ba4fc56eb8904b832a4dd51a31799e4aed56acde29f080e65f6388dafc04e0929ef018234fe9183c41b0d41694070ee547e18e77758a574b98518e1b5fc037d11a307ffc2af06c03dd84d68cafd7129c1dc855b0d829fc163d15bfa82fb13a929e3223e52b0a8b46e29e0a57618064d62d5bb301258ff190132728490fbb4aa5a45e741eead75352ad2bce7dcce531e20b9e5cd2c3390f1d2517139fff5d6b422e383ce9457571c8a1732fc160e02aa97816230adae105acac57345bb50959dc3f1635194c467068c4c05d4aee649594727181f8d8492e73862f5cd477f5e1823532ea548c301e85d372a72c968620546494e902e25d7922ba2029b7177cb8296b58e8dfcd16662e0d878c5b33c769df460822a6dc6e9a63612c2d439aee9bcb48bd332f99d1df7314a01f25e35cde0d85e89150e2039b0ad69fda9642c5095cc09f7d8ed98bd01caaac4bdbdb18e4c8deb0ad1b632198053546057cacdeee19b503e455d424c111852c65b1cd87dc726b9c5e6218ce5340d446150fd3e2317479120f978ee9bf60420033c040dc3d667e92885c6ddf33763c4dd15164c7803158194ce2194e88e94be3548225ddc1bf29625cc0eaf498d97ea822cf1dd7e31e67ba4d51a94dc327d85c4cfbf17340b8d99b73248d3c3cdc82442b338271a0fa01897b4489cc5a2defc5b8bcc00842c9cb252a60a03588e1294016b3da55fe866df3509cc94dccf0a410ce99e40afa883971bef29944c619217975196ac4a78166343d899bc0e76f87842695320178999c0ebd8a241f39fefafc583f2255973ce4c27776c36dd1bd7d78d1105686950561e9dca5d172ef96908c6ad7ec6f563fe314eead35309ccc54f983a0b06aa39f68ff588a982c3e1b23b03d4a7732a8324e2ee50ff43f98b3158373e9b470794c9b9bc1704340c05ed490fd03050e0a1f91a2d4831d4db04ae16db5b0117f6e5d81a463f52230ff8bf52e6797281089b4015a381fcdf019783c291b55965fee711f1437089a28d338722e070e8e3e71909b1196ace829e45fad7365dd8270f4d4e6a327725a1635b33b8c4a6734da08322cd7397cc6749342883c7ff136f0932286e41a5120fb12c2d1d18b23500c608bda0029e41bc0d6a038452a12d34641c28c6d42193ba12bebea464eb1fc5a00ba6df921f415ada821f2c65d87a254dda1f4a0fb3c9448e3e8fc4fa2e8f39ad8d20a91f7bb65f7c4ef62417feb761913fd68860b20978da8e4133a3ceb95e2388ad1a5bd10388a795d9ae8ed3cb979763f42b912a411cbaf22842607bd27417fb7a200e9f61133d1b4402183267f99bc0369f42580d9e90819d762e5d5b5ad0182cc1acd4f243f5fa1de20bcb921d5845667c453ad3649be0316e54d17adc98bea1bd4fc46a303c2ad18d1a81f721512be64fbadd6ae367c52d6c91f663cbc41ff79aef786ca552ba820e01280617a83059db9430f2cf0e9550dbc890f0af1309cead5e0f209b0a329b5f315a0c8df0408780d40f1c876dd6d7d973ad67818a0734da102ac34aea624e3bd6a54815e895cfe6f32a9f460d07b186c59cbe7b0ffa7b91aad31ba977733b4f2fc3bdb758fcb9c31eda20f69c346368bf1713100dad1932d0cbea5cd8ec141e68b5dd5e8c5d1205eb830dc4a78fabc0b80ab1698ce5fbc7e96abbb2a629b74db6488ff4559050c79b54da564e5305e21b10f799cf54b5e347b432ac60021ba63e53d0ada9e384dc2cac71afc203e2f180773c5a510030765143a7319a2bcd86513238227c10ab23ad11fa8f5628e27a555d548309017f61a685ba17ea7f5add012607a1e9ba3ce8f4999a6f12526628f7ae8ce0e709867e7c550d026ba9728a9d71a31cc110cc202d9dd4a61f091cb7852730fcb24b491e079198a4913b26c2b0f32deba055cbaba8b184b8f4f5a90900e8942e464ae1e00b52ac29f3e40b7704d9d074ed8dc368e08d46954a53ce64e0f3d99daeecf4af88acbfccb9a7441958494d18cd0e3f3f2bb704815b1c595937852a7a26c058665297d195f41b1cf3fa17a3e7f66213741450116e043025b6a56c1fb837c641b00cfc259b17f47b367be1fb6949582a0a0f8631f2bb1fa63f3b6f089b3fae8acf289451ec8bc2f7c832b5c29e415efe905d3aa3527b5761b4473b480e00e244d990100e3e88632491ee5d7bbdfcd69adc481a15098e42cf36694f5efe0882b005c5a41903f0f7f905296c1d3787e00282f164c2aaab966a788770263f095adfb748fc416d63043e45fe3aebf159352364de4c8e6275039b1d6a2e124280a03f5e6d26529d855f7aec10f8ce40afa49054606028dc465c9af853b3145dd61d875a0958a521a18a55f6d286e60d160b83f2cdc27b4235e650714ad9c7d00e0c347e8071a1927b880596e745354b28e82ffacfffc3a59a006e236586cbc74f1fe1d3cbece560e7ed8fd1fbf98f0313d3c1c0d9d34115e38d07e831a8f87f01b695cfa39ec8c3ca4908de6abad71b9e3b9c9c24e05358589708d413934ab1bac654ed9e05c96f3ee89b8070c022d6d61a6f7c55592e8c7565323c1bb0fc3e30781b485090d0757decc9ac2b3d09a1894aa213b74d18b588cf2f935528e8053a94f940fed7a03042a109eb702513fd9b57d467c1a27f27d9586d09c337e34fff214cd8cdc6449dc220d581ae25e2990d8e407a66026338b64a1842d57c292d466e305ecbb87580584136b28b3b8b95f573e723498e2a4b4313e0fc3754fc9b31bdbe7e1237f30aba8b6c72b66f3cd4bcbe2e01ed602c88bf20da00410fee28c37def869f5d06563cb232bd8d245f06db57deed11d1f59849f6b2ec8cb075e4b9c76c48b8ed47a1818884d306f2734aa8389c5cd28fa0dde97c13cec3c18cfad30cccb11ed9e213c79b95124ff1297b308cde9ab4ca097a0fad72e480b95d06784e572752ec1d31e0297326dad5725a8c0de32ccb4ba5d6f2af6f0e0aad26e0df52a25eb01b054c8e62e68081bb606633821f73d84c2d02df1dbbe98a22d453113448873f34e875d329e502a743acdd0e6f3f492f5874dab25d3c9a13b9237ffcaafbed1e071443c3b9eaee983586663964538018b2e12053a147584fb5fdbf5e51bea540c912f75cc332acb2884c1a3cd192b5026abe19e01724d8c9429b6450c14f4dbe59556c33d4e324345c14af86cf84c17bc26d1971752687f1f535065cdca38b7ca3a9b9ff6986b4f4335f0d1bdcaf6bba96482bb56384b727f5ea82b01ab96f86ce556012c03f06e4fac0e551a417dd43e9488869d55929df97404c29da98ac4c1d4952b0b25294a3ad89faad5438026399620ce36103497885a595b3bd4a0d526a04480dddcb233f07464ccd0b1e362a01365516deecde5ee9e28b329e2179504515b1930b6107a437a3d65a866f206708c879d88682c74b673f2f1dd99d24908c68604ffd81056a82a2b315b8abdc828a735b3e5a6e3dfe66e1917a628017a0f2856fe3002574b3c5d46d5f5b48a971a9063f1ae592d4f456f7411544c103d578b3d82be3a40c90fabead4c326b2e359ef86d07bcb7fa31ffa43b6bc96dc750aa05c4e7b3b4a4448457d806f9c8df1a5b0f53833425ae384e48bf565ebb45d119414456350dc8bf033dcc31663b2e0fdf523d8406bafa2010b2002b82823f15af74a3ad7c4c3e87fa4e03b63ba54465d5af2ee74988cbb32ee29365ff2083e21d85da37efc84024e6b51b541f8103a3f4eab92172f527d12360c74691acd3e77f1106849b51ff7f19a932900b531201875b719acc448b6b792741020ab055262581e78213a028d13c3d901123aa2c8ab098271374096ce0cd3ce108ee55f5e145c1f2a60157851359b7e5308ef576f08e3e9227233e868220ccfdf6ede96b6344f3541a141d1044791d8f08c30773a50df496cd3c4ef93e014870475091b88e36373e984e89994c5641430ba57bd7553582bc42ab21b88e179354b733c2e6ccac454b283ef2f350e02b0cd887fcb8bd9e8cdc70ccbe8024598fc03ead9a6019e866a41590211ea6a1efedbb79433e6020d83d6f690624e7ed401d6e90384a1e85d993cea9f83c199e16e462be0f383150186cb789dbe08f7999985d028b0b328708fb49bac9309f99266a1977b450abf80e5e59990f0690ebe02916cb5b2817982e40bb403afa0cba3a58f81ec7827f61484fdcf27d1169e597f2252c2188ddb984c8f4f0e8514f1fd90aff02e878401efe45b92dfb6ad4a317454776067fba0ed215a01e9238790280f33bc782272746de898dcdd85436a30814318ea9410618f35cc4877c7cad245548eee207d8225919594a024b8afba397e6d689bf4c15e87501e923e810d234799b415bc869a13fe4b1fa4deb99234ac7f561894783a90e447654df6faf1d98a9877f61a148aa1b2ba37d2624babcc86cd63497393681e70c67bcc959407ccc0600dc1f20f8770953a4f87a539206941ca30f24cb7e9d6062f8ebac08d840688818dc5ca9f98054a25af61310817e05765198eeb76e7c86487bca056f582fd1a39671f70657f1f59ba5622000e5443f6a3c442eb4315b6787451321f0e5efca5d19bdf9d0897b2e71d4c2a7095c2a13bd7591e1f55e7614d9deda69170ed7d1111f2c6216d41e8ed050bee96c7cfdac1318086c2915b955bec2821441269a2ed0cb5b114a82a416a5e61e2a3aac6a16af8a4854c242cde70c27ac553f4e939288e5e85c7a1ff319e5ad040750a69ecd917cc87418fea409359f757f46a28160fa2b457e99fdfdfa8ea7070adfac3eb5edd733c55e929ac8e364470da2ce336b8db6f55f3276d9da172c8a1f397bf59640307c2679060b7a183a6c1f4f1a5ecd19bc74c82cb7883ac9baad7a4c11cae903186766c33632bd780681f34bc6991c0233310b9e8ef70f6a946bd1186ad330b1668bebdd19cf5861fb06f4078f5fdc8df7d4893f208ed8be7a0050a5fd52f76337581ad61b9a967105e5cea8336efb52cdb3127e0d3152d4d1df52a94cf12a23076da06c5f63c9e725a5c23fdbced24fc4c8af680a56196dac59d8f2095124c0ee825b54ffc3348b5587e075a6fd6aad72b19a40aee821deb17be888fb4c0b3eedc467dc76481e01a506422bc1f2d977a98160923d3a75300934132e2184b80ba5d93b93fa5fac96db9eb4e43057bed1654203c2f3f2baf095fd335e970772e52e69418d363974d82829674ba0a3c064fa407695fcf82f1e691b6297df41d5312b8e67adbf4b2404e9b66e747cd3aa25abb9243111ee1f82fe560dbb4bbce29d9689f5f6da7e59f96dc046a7f7d85633b7210e684a767bfadc5ccd200f5649d0985229296971d5acad8fffdc2c1afe5b8627a8437e16f754dcda395d1f49ab7189988e3bd9fd26618659a50ac43b1bae60fc304e0c02ae1868cf7834bdaee9a5c7fd0cc6096eea06d17e85ec997dab35352eb77af98236451ca72018a5aab42ae11b02fe99ed0bab03222c82022e1bc0b2b0267f7572e3f8c4f6038e82642b023feafb046d7fd3c116be8fd7da587881c0ffc5ee8796613ddf4f82df91398587a629961ffd65da28963ed2eb25fe33962bb4b86b97849f434d415a21b473e6f0c9257ece9bd99ccc9cbc2c04da63a093a1cd6840617407ee5ad5a84dd4296d09ed2c52ac376e4de4303a4e3bc4aed34aa1aab836386f7a6658c3494b9f2dced9d9a96186b89c889d20a985266bb8aeed13de92a03e903cf33d54597003e49fb427172f988ce4aefff334f84f213eb76a2ca398b8c1135fa2d70f74f5e52a695f9d974c3dd69f8c473f5094dad7fc92e092cb212374182639e71cf99ad2fd4c9b97dacfe3e367ac6c5438de6169c7cd4dec5551bfb1e06be18cfa45b146b08bd3f47a6f105e54496f5301cc358aebb03d6ea5f7919a293cc41fa6c4305a75379eeee9080c2b402cfdeded5802e1c7d0e3dae484279350e1fadb6206e12b8c2be0041666ccfa3a927bc0267371cc849cacefc73b5ad89295708113ec15cf83efc0e76ee14d252ce68b2072b25b8814c2533c9b387e344dbaa3aed98fa6e886061ba3afaa42c566ea84e61d7ee0a9e1c3dc5eb74a5d818ac0356675715d48b9492ea2f7f18be5317227d7e0bd8163eb2cd7cf018379712ba3e23d47afb61c270c7c4cd25321da3669df270526dcff1a489fe1db9f313f8abf0e3dec5e7ebf5b01693499b011fa95eee93e3b471506597d4f4ff37e0d2bc9f1dcfe9b259baacc350f4d6ed4bf7b891f6d5500441bb3aa3a9fe7c8ba49faece7be1489c463664797ce8e279a4464162c22706888345828fe810774e4f6b38885810a1146e84e845bdac08a3eb5f35fb9916c73b0b1ef4e3e4f4948d3272169977558048fd795f11c6519b8b63e2d05d354d24fed29f7be3fe302cda5e24c7a52ea7d27b4f7988375d48b66d4dd0fa5da3efc27b562ac95d895ce51ffa7715566af1cd69f7336572ba4a7c5280a15f3373635aae65f5201b6b676c86dc124d85f164dc759da2d5fa4efbbbc5dd1c4f91413854a15876e3f748d2715f98068581421b25f8ae29ad78c69c4416895bdca8c46e03aca97be88e2ae480449c2640df963708162b07b6200297c07c29f051de67d817d3988db1a879ac6167ddadc78b4e61b831095e4e309a7082784d6afff3ea16ae3f35cfd7ff1288067ebfcb284b4f07c61e6feb52750c79a069a27398f4759838e6117290eb37fdce596cce00814bdf6fbdcce3af8466de3fb9da66b9d2cf9e09bc2983c0071f28ce3c67513f8ce561ac395ae40a1d7740c6545c667f94ab2301b361dd6925445b4c558d02a2b7503d6b185a5026001f7ea6fcde53a88e6f9be701b5280b5fa57a98abbc6578cbbbbfe9183ffa71d3de546698ff0a3dd8c85e19de7ec210443d5e84c0f774fb699c25ee8c20210c51c28f001037289f6d27f312f5f45fea8006b110f44c100fed60034a299e4b9b35f216d905418041a35b26140e196fd72a06c77f61cfce704164598b54f27b9959723aba4b8b2205a086141877b223f1cdbaf25f172565ff9b7cf077cad9163b10a1b68809ffa3fde074fe796546bdd39fd8138c5c8ebb503d69595cd968995113450f87beac4c6fe2b66251029a43adc74ccb5d5fe21993315e3434ed13f2d8e81ed7dd60e76fe52f347feb704f036470c720c28b965fc656808c2393c0b6a1b517977d54d17dadf34befb4fa25ea6c7cc0a12a4cc00aae7a174b443e483ea686f414049286f627a3aca5589e935fee235c3518f8f0c7c20d2319e8667dabdac051178d4a4cbfe77ae67cd2ab45aee7dd200ea90920efd30717f8bc367b01ffaa0a6bd3c0d8b8bf8550dd81ecd80ab3d1f4a248d2a527386223317471fca78cf6b619ce7e36f052882ec1dcd68b212727de8dbf39bc9c3ed0226a910bbc6ffe5097b28a229220a6382841d653707fbe69e03f6bbf280dc353d887b88a093c403629fbebf695022c720887bfa65772233ba09f782c8942a90cb1dfbd635c59390297111e4e646334a669c5e573f2d1ea120eceadc5da012b584acd9237927a702ea2f97d172d4f8fd0f900e6a1874800bed1ddfd771d644e99ee54c58c5e32b500b8787e6fde59eca28b8c114d14f834c74a1f8546fc3d3bc745ab9b423b369353d0b73a2b9c9c1ba3839b3b91a557fa7173fd8ee029698c59f0de3bfdd4fe0f70714aad4d743b6657f1ef7fe7f1e794d2b33d068a89a04d8051c858f90b14f647c5b1214e25ee8b68ebc1ca8edb6e386dd18ce9b45fae10e1111317114aebf4051acc57b53fb8812c68a8d00deb63f130f05896aaa38bbcf6f563aae9004a3eacf21874f74a4120aee8380ad2e459c28a3ada04ca520391d832e2d83e5de943b110679f3a096407dd3de0ddf802b0d98d3c1d82ddfbff4e7f3dfaf23a888e3b917ec717028395ce7372a8f05382201ba57d1fdc37126be791e2f22feb0777d4663a940561a2c457d575fa57cd5596c99abb3825d187dcc7e42cce4e7f2f644b6636cefb479f7f9012b7f0622ce5fc033c7fe846f076808a11918afb3e4355d223abb820c741f03375303b94613a16f926beb660ff832f46db3677982ac8967af136b65a8f5dad46a506485e3cf24ba69791961704161eed3413c376975940a190d2152e5b7f3cc7660bd0667adb8063a90d72da36ea8017647547bf07ae857f5b36d55e23656493ce74b4059a409a71b054a8bed97acc3966334cac1f98706240204f14b433e3d261a7b1d08a6d73309bc9fae4a3451991395ba6e565816f8ce4e752c7072d440a41fe121295398de1760865b178e10515e3cc3608139eb711df2fc9543a2287791342a4ee66ce97d4967d1b200f549fdb3a43479195541896cb4bf5aee709e8116c93d40eb0ff3b53519bdd9818bdecf69e7ce0013a4115715ea341b746a4c0260d3faedca6b0b89a3a55c7080054764d4e7147c5193e900b7400735e040c24e0f2023266b9f33d4d9d4a037b742a07ed223ff4640d512125c2484caf71e5439097ec2555377a3780e788f266d9058c33919017843aec43710e98f204fceafa42f6c1d528df1e2ce2084958060c6f078a4db164b9a4eb7c3e2ca2bb35ffdf49b0df7740f21f0c80e5ec59367c2d524524d09f32529eb4d3af8dc0f08e65b2277c3f2fabe23f3d347d8e3598a110d1c66860b2147e1e851a07270c4b052460c32cf4a3381f13e79b39ffe91962da126af94c9bff80196f0fef0a525eb4ec9f2b63b76bc2ebd3df1e4a3467fc88c12cdb62a24ff01b80eed8303564a02463c0989b52f669f70c37045823d470b8602fad1a40681e7b23effc6241df80b61be2369bf1580f260a414b5c87343b26e8c16fd510187904bf8fad3f5b1bc2ce905416e0aee0f64988f6269250882cf9dc832ef83b4cd21e035f7ed89c11d351c9a2971f5517c515e88eefa5aa76d65b90c5752550bf9bdfd0e3ba634129c3c5af487f11fcd480d82db932caf0fe43defa070a5a4a099ee2ee033054002cf5a4cfa7c97ee8eb9afddfc0dcbefe3a8f56a9ca3027669060524e79a6ba13c3eedc87cfb051346b8146fda85329ff6765a983fe848ddeae3048aa8911f322580e5a7dc36fec224d47f15a324e43fd4a091c691c28f79828c20461c14040ad48a9059305816a503f2460d49daa6f7c11696a91b2ace1fdec5ca0e4acd0c517debf1827a4a560c79d233ee01188823eb83f6e31fbf2120e805054e012fb5b9acd453c1ec3e0a9eb42c2fd3a71e37b3d6882464efbdb7dc524a99520a8b082f0924096ce3f2fc31b04de9b7ff0e70f90bc03f7adcd45c8699a91835a697edb1593aed47abe99f085a208c03472317d2f3921b3f15395a4d0f84505115284d8458b2fabc6b6a22c4922ba4be4b57eada51f1539ef76f32d5486986d5ee5372499fe3ee84d25a4da67fcf4947619af8a4e824eb2d7f01763cc4ee6b14dcf859eb8b17b232277cd1aca2254a04ab387b6444a21b185525ba0437c227c2aa91c58151d3464ebb1fb7286732a83097871899263e864512f5e3160fdd4d24953f3b69647b0bb0ca83a898c528dcf848fa870f3407c6f1c18403e3c88f5f01b6a11fff48ffd070ae8f6f3da83a0263e81f9aa5ed237299fdb88b25da48025e82bdf8f448fc4863afe0466ec59f338051a7d5712fc608551bd3f931c79d71fef5eccd39d342340c596760589504a2f8474ffb4892823dda477c2c7ef4e6523c299aeaab3577592d05553d3c444444695c807dc48f11034c13ffe32624619a780315d5488f7bf157c840cd3ec5d33f7ada477c9389bbe809e34699e44d5a4e44a41425da673dd9fb50b9381ecdcacfdeb9202de9921d4c7630e91d4baabfe60e37e8313a6c814629c76fe6523691e88d3e3f66dac7052ea55fdac1a4b27b2ef6f3b9f4b5b8757d7dc7924a5feba6ef14c228e3c31bede773c568dd7a2f97872e9699a664bf24977eff88c1ab24fbd5113733837b300693bbf7fd70778037da1c6d54fe90cc5c223272965984069fe5535aab49723ef25316f56b2b577e3de54a2b526c2f91d85bfaeda5cbcbef2aa5ce887bf2b7aea7bb0148922c27b9674e015c6c270de49efc1b3ba8d98fbe81b6e81e67d93dadc76db8bff131ff5118d5cb5fcc46476d2fb76deb71373bbfeb46b40d712eb9d0d8f60ff9b1c42a0a51f1a9cdbb17fbfd42ec42edc267c906e055fff0cbb4bb18e5d1f81ee338d3f4e7a1d806e2a061fe75d94fba8feb2fdb3ea4c52105a5683cc331fe57af9cec45469960a49561e662988d8e9a636818e39c6aa8cf3d03483ffafed6ba5652259d6a7079ee8bc0fce874aac1e5b9e781f9d1f3fdb970dbece95443e9b9e789f9d1dbc02b2641949efb20627ed45c90530da577f922257bdabe74d28ac4d8d3668b909494eabf3c4ffd17eb43457b1f97afaf591e5289b91d262ed6870a8fcb97de06ae2fcf3e80eb5dbef4fe3563b8d8afdf6342e8676fe4eb217e3d3414961196e632073d7a7818bd26f486a2853806b7213dbfce0c6e537a664e7d8c35f22fcf8d9cd47594daf5144a6bcdf8923aee8ede2c7bbea2b615292addec575df8650d5cc76dfae9477b3ad5b03df745467f03b3ff6e341eb7f93ed3b4ec6f80c0cfe93182047e0faeba2114037c1d0dcb201c753f2bee7f5640dc8395079eed39bbd9c02b1db8d91c1f007c5e610e2d1595dda3d67fba64cca5131a1fa065646ea497c43ecbaeebea2e4a4d1ea40db1c3184e5b2adbf90c2fef27949ea394de5c8a7d89d48d3a4dcbb22cf34dcbbe3b2dc3fec22c4b8f269899f9e740154a6b894f4eea4e3e7a9ed1e85fac0fcc8fdee7e549ffb3d26a77f25277727f97fa31d607e6ebfbbc7ce98847edec8e1849a24e08388126a1ee94591e785e1ee679b081573cd8007f87ec6de095cbc3581f9f9f950e1456cd66f918cb2b98b7a71d983f65d6065ec1d8d4cac57ed7e7c08924759dbe5f3ebdd4f1aa76bc2219711b6ae3cf41c85e45738a452a85d16b4af786c20a2b806c5a86d16b426f247c8c1bcbc8fc78771037373032333d6a6a328c5e53baf715db651b8625faa70ca334cba80ccc81af33037c1ef833f0bd0ca3b445c506ee132efccdaf0c5719d79ff20cd72c62422b92a71d86fea477f9d38ecb9f6ce855e94f455625fb3fab51fd6a3fd2e8e75e2fb3a4d27f7e8c310c7b86b0ca0863f04415517c981bcb9847be6919f642ae2d5bb6b461c31f39055c9ba9d248230e3448c37d1b3532a1c243df1d601bac2bf935eea8fd66bebfac0bed3c11f981757ba0173e7f462e7cc242725e14cb8000c9b0ebc7bdc1000244757343afd93df03c39af99197a4d197fdc3b8ad643d492c633d767d9c32c756fe0f82ccb328f8bd13c26bdc6dce8bb23d92fb5731996f83f6ff4fdf57af8a774a07f8fc949cdcda8f99db94392bab687a8eb2b0ba1761bb98ca2d55c9e7fa9a82a7bff1e32c398dd8b3fc81fe87dc557493f320e7c5a03d9356b3e1b172e818728855dfd7ab8d1e7c69883da3f82bf74bbaedf5ca575596cfb23a8d35205f81a1ae754524ba7bb41a51fd4b1419dc1a57db40f7f26ea217aa21d9d19dca6a18ef6e1ef704bce0d4ced238afafdf7a0dec0f3c1ee3bbb8da8c76d3ec67283c9543dc6de41c9ad37c4a836fe31b84d9b4c2c1d45d4af9a58cbb2c630da5996cdcf4e32649f39d76599f6cc651af69d61df18f69de6f33dc1e73bbf7f7e6f9bd6652719605b1ffc9da35b859164c4fe90c48e59ea4efba56e64c991be1b6354fc517c2ec6974c13396b993ac22b47fa909b3b8e2285a8332ea4d18778e10ffe23bd8928d643ac228a262e57a106d1355dae420da1abcd8d8aad52b799736a327e47db9f034f50a532cb94aa3019a6653d1cd146a4116544ac1e5f613a355373a660ed69222d510888a2101951374ac9d0b40c4208bfd4d58e348241240bf58854fffa7e97eeeb9503b9209ac6715cf771cc6d9ab669ad75d3ab75f7f5fb302f0e3ecc8bc3f51a65ce24e3dee6d20b8eefdb2503b45f920b3fd2adc298e6d53db7e95cbfd60da7c5410608c60584fa994c8f6ddd551847f56bf412aa9af6d95fdda7b19643ea6acfdca5659ed1ab65dd977d0ea98b437c987539a4e86557517825a8661442088f5c11218cdf18c42086c53859e8daaea750c5fdf526a822fd75913a18ca6d5e8d4bd858857d9c5807e328f818db4a837a59ca2b2c5169d462a4f3e1351f9e88f80cfca2664f214c72a1fde80ce0d7f06f866118b6759122b9f39b5252f4895009245ae7cba9e5f2155f984194032713b58c101a11bdce06f5bbb9fe566aaabbbb7b8ada2374990bf2566ebc1ed7ad855951112c9229698b8c7c84ddd9a0fa5ba9fe1f17b5cdf7503c0f591f640955a1d5242474bf2b0b64614ea06ab2202a42b9110b9a72b5645e7c980b6efcefcab2a36981a80883fa4d4dcb8d3f599355194ed68dbf234ac52edfc089d01d627141f108dd3ca8a87ff9d2c0f7cfcb57d608e2c6e7314585cf438a28acd13dd078e693555a1746eea018d48f85fa892a04b1b4206c412bb55fc0838aca97af44410dac9abcd48dac0285a75c58c19310b0d0a47a603e6149994058e124f017ac2c454d60c284279a587914ae7f8de222faa9824514222d2d289cb8c2440635c0019117b3ce14c040e3410aaa002209fa53646967a235c512ba5e6e4d31c6c52eb7a638632ee1964401cae2074638e2889567e00b368a5a555650831c1011456bb68c52469413aabcdc12a2410b51849c702688105b422a9861e880460f5cc084222ba2285d6e0941112a020241ce979fb4d5e553f9358b269c70a5fc198f16492a09130d88e81a0c31c485156674b9e58328acc009723425891478a1c5d009b24c2088b450e2074b10f14bb2c052ad27435c78b9f5a404330373085f620330890c942e7c53ca035888f0f324063e40620c278e085263b25670834b0bf9de9c5860190209126230c61a5b8c20011663e0e00acb77b0022e8eb82e27818b9607d0b8dae5561223b8dfa7a4179ab713ef543a33ec2469263ced7ce3369f0a08dc72bf4dc53852de7a250a625c1cb77a97ddee76e8eb4c033f36a1ea406666da1d8bb40bcb5ef38dcab95ddedc15aa5cb4bf6ee6d2c138ea9ad736394d9bdc77525cdea53341d4f59adc3431aa76dd8cb445bddc26fbeb395bddbbae87a155e15ff1da6a37ca344d7bcda52bfda8ab245bca3ea3ddd7d787783f1dae666d0ff1cacf58e8d22b73c9a28114d757dcc69d57d362db42125d8fb7ba0d13d10008d7b3d0d80b26975b5d10e072ab0b2b3709f44117f785cb2d1f44b9def6d98cc63b2e8344bd5e931886595ae5c605e1614acba8f32737445eec35312a256dc184228e0b82618df110ef11243dbc9ef934db360c8b379bd0b351f6b48cfad55c49ad8431044693fbe3728b4807d7c3b2e36661e53211164358586935717d5c6ef580755b5f64b9dcd222a8a585932bc513222b54b4b0b4a6bc6cda18350ca1fbc2e556184eee4d182c30b4c46ced3736a216308a89a2a0445554a22b626eefb8deed002304444caec96db8d583347a6006902cba3352ccffa2890b80cbad2f987891c665e172cb0b2d2ebb4d0a5b771fc52e04eec428655a7344cd6e6efa0271ef86c76fb0ed65b820482ec65650d3e5bacb7a7c3b172b3d89e3b867ae318c7bf7b8cff127759fcc2565309051b1efdf3ccfbd1e34602f93511257847d003e54faa998d83d17b82018666a1c9c69078b5aff8b97f437489f034725faa4cced4d7f4f8f7b2cc3dd998c2646cd300c4686de7284edb84d66638744cd7ec4da9e3ef6c17041280b580f0cc7eef180e168c85e875b3d381a6e68b820a4ef3b391ab2b7372f7041483fbb9fcb6e43b296723464efbd4cfb7976db60ed788897c3be2dcf65a6c1be2fb533f0509da7dcc51e062c286b815ddc90ecdb520cc37e623c37dc10eceed434e99e59f36c2b526e3c2da37e3757cec76c534a7fa326f784f808a1769895916f4fa0d27bfd8672f98bcb2d2eaab804b8182764be96795e4d0dd6a3e6eb713d0ec37e731b18c69118751bd2f76f15a62717a473903ad2b7979e392f35fd18a71f439b922aa97e8d89e962ca9db9dce24204976296ed377aca29a0ca50a737eb4edcf315261405d1ec4906ece963f6c4bd3d8d9eef0e90f41a471fa3db69f4a3cf3a24a30eb39799863ed67d29ecfb3199d49d76c6bd1bdde584c56245b1cababe64308acda069d42dd63031f9268ce2abc3c51346f1fd523a5c848069f83601aed61aa715f5b071d7a02f5fab8fd9cfbb18d0165d5c9acbad212d9752a1e532110d9e98d6b032a58563ebcea75926c72c05d2217194fcee8eb8279f0764c02b81189151b8f275a070bfed7e3c04733f1ef28e6ef176908cfe00714fbee7d5d4f4a87949d57817e769cdcdefb6c0b443e2cdecc634da0f89b7ed28fbfeaf9b1bb245f7346d76231b8d44b20f8485961c0dfdec5e4c74d3cc8ccc0ccccf984c2d34c60d696cbbb98921062031b8e4c47013b99b1e82ddccc29045fd642ec66dabca588692b80db786547025bf9becf4e830ce842ac85e3ee16adaa6a3c3c3f333f04851fb4bcfdc901dcbe39eff484ec7bdc805e9b1359765432c3704dee6283b8e4c4c83bd10ee694cf7c120e9993d86eddcfee6b9d9f76b5927432404e532d11466dc7a99688a1f5cc62244e6621b0c523037a4da9feb0f611827db8128f9dd3b5095044ec93458866118cfed1bae7342faa74c872475373bd37ddb4bf7f5f880dcbc97bac9d1d951f18ab254cc758ee55e8d769ecb9739a2cadcdeb62d320efb371df9b8a75ddb75514a63865858a8c609d13ef3ba947bf27574a0b6b4a9aede6064324ee9fd4d2c73b7d7816930f5291629a5305968a42f94d2178a75497cba6b56a963a6d9dc2ba27e48646ea5dab6e1c802d4dcce713a9c2c09e2b849c905e976db4cdc93929503ca57596e531b4afb111d90b88d3b71c20549c283ea0f01b7d97a6e30e2dbb68dbe63b8197e6ee7b8cde8e5c7e03693dbb01386b59452a68a6c6580e8ca211fb7e116104eae7c20a8dcaf9bf8fac9958fb5c7dbf2c1930bc3e5561845578b1c91fad4a9b3cbb847b226a6c9b2ecb70e89877141bacb76ae7fc605f1a75dbc3cd63b24a9cb29ba318e0fc72a258c922fdb7e321c1723ff8b49e29efcce89ea2f547207cc40cb1642b707781b0890107045a5b2986480b52a314e015a6700310d544df93e3ff0184e3061ade2fbfc302d15f83e3fec006a22c54a5a2afe3f9c90e0240aab68a9f4ff00bba7bbbbbb9bbb25d3305b33d0284263e85ebe8246153097afa041e52a216a30714a64326e702ff6304dfc1e8269e23ccd06729b18372b56b86a004f3aca57a144acb287b8f163931bb5dc6fbb3d04e3cc797ba02ad54050c580f9f282ed610c685517d1a8f8b451f1dbfbacd5a1c3e57e0de4d240d9fd98a853b8299864ab6a1c151f4866858b8a1df5e9b86dc3eed9fbd11437451d5a82151d2c97c8029b3aba74a3971df71adc5c84b809aabc99fe3143fb881fdd541fa6bc51c9ce10493656fbf9e5712fba1ced304d7c6e8afaf2a4d7a24296444a4d6a924f44462f474f6464a52881f997973d2fef43455a986abfd14bfb1d692ec8e8e50b67bf929c82c0ca932f6af6f1055a7c0d08e3909e5f875f98e8729c139e7bf0fb53504260c6174330e2076aace07f12f5a331a298c62f147d9900d195f642dcaca85ccfa50845bdfea3345c598681a88078afeb3dcb32bfae8797e501cbb21f825c9d0f7e31c62c8c9b7b1d8ca3b668cd6235fb88b3df79c8fc877b79f7d34b86a88078331ffc660c337b2f8e645cc805d9a1b708f99f7b7549ee9675356754aa79ffdcee7a80dde44b72e35f5434fefe1d30a8dcddcfedab76221dff41a02a56b447f85b33619c7e93cc9bd6e034cc30fda076c15ad498f821c0aa9fe123ee39048c98a0f6efc06afc68e0be76b5fbe8b23a308d3c03890b8d647152effc9ca2a09f73e67c966b8822212834212673ce391909ac1b4ca9c22ac210583810ac882d253cb04407a49452b2e042621221a594920821823099fc1586c604a65d6e2da16289152d084153de4c1ce932839bab73d30e4302872e5fc07ae372134b5b7ad069b55a2d1aa79cf26504f5e316141d776fb54c5e8eec3c881a39a1b5d3cbe436476f416186f0d84eafe8b4af1ea259d0dec1851f04a1402743bd03cd6196805e58e1f5c743bd1399a81f0f0545c9d181aaeb31acbbb9baa11eeaa1dee954efd01d541fdf990981cc936ec26468c883a8388b70b0019310a310810842601d6c00160d018628832068af92f04fd22ff767ff588beb115a71dd9db280b852807e35ad8ca3dcb4c5c4ee43d0428b085fe8dbbf75ed4c4878c1002ffc0a9a8fb84b957697339c5e466e1eb033840bffc3b94270e749c5d2156239c2de11a8a233720eaa2a1ed94280050b96200e64a1290e409287c1e9bb3c2149bbcb091c1a01bd2f6aa47fb4b310ee13d158a224d1f2b11653d5125f9878423e8d523fd6f24510be0c909a9afbb1961b8ce4403a618cd02e1731b61e333230148608cce5089cc9d832f68b0e3e2ebaf0a111f814c6ce2463cbd84d90b15fa8162ddc6be92088824f9b35a596244ed4efdf29a5f0af7f9d2450e55df65dac549ac4b3e2a5303d519bd0a2458b162d579a802549171742f8b109beac8d10cd43a935fd27a07f6054a856415a523fd306eaaa53b7673810b921f4d239a590decd795d7f79b2ebe109cd34a99fc908f5335d77db6148d4bf9f77eb90c4620713a66a6194f34a82d218e5bc28966914ab584629866159a66d1bc78d46a49ac154227db58d1bf568b4916238eeb71189546ba9e4e2f2f2020313c3059111a2a670828a0387c9044d2693a9965c60626e4478c36fdce81ba5928bcbcb0b0c4ccc8d1b26538e6b4aea5293ea1b3f986e4c381c070c4c4ccc8d1b3870984c3972e8d0b163c78d1c384c394c39749cfa9443c74966470a9d5d6106002cb4e0a15ce0d1a3c7d643cbb01e3d7a6c3aea0623733ac9ec484185ffaeb3768515666600c0020b2db4e07928940b2ef020f1e0c163c483070f1e3c78c8ec4861c78e1454f8ef3a0bbdad9d2315989dd9f9bfeb989979054e86a8020b2d78330480112c1851ddb6d082e7a1502ef0e841e303a3970f1f53fa88d07db40f1f3e5868c1f35028175c7081078f1eb2478f165af03c940b3c7ad0a0e49cd2615c30419927d5ad0b1c91be3d54b0525fa83406a18931c2b8bd20534475eb23150001c0d030d0d0d0f464a1a57604d0c44e53ea2f4e48b576f0f3217c1d05b043085d4c103d14fbb8ac89c7b46cda0183eaf282faf1d0ced0d0d05016cda1472882ee7403bc4cd47ccf347acdb75150cc8d989f4153b02a49146c23ffc6cbcf1903f3da283ee98b8c1e3e09be125237e1dec9dfad40d4dcf120889a2e7467cfefc4a8cdf1475f04fe8b55427af823ab840a0fc9d257c2308524ea07d52fbd074155a985588539d1f532ae1a9182b05041198c93cd3bdfb340d5280860f0c0abf028a8b7c0b55c9aa983e3a70e5425390255386c433171769b1ed3311c754d0451f349dd7cd9589ce5ded472a73371d4f4a02677ce6c0605050505ddb933fa2015044d79a9228a4934499fe32f5d5771b1429a329af34b9dd441fd3cc8a3b8cd97bae236e1361f5fa9122528ca6a742364481097c6c3863e83e80d44314ae97f24034c21dcdda1b7684d027ff83b3d146015cc04d826fe02d440b1d70fb91a78355f8b3fa80b80377e0ff6a633b88ce8232957bcaccfcfea212a7632399dcc41a5514606be3ff48f1f87a40fe66b0f5f4a7dcd1d0c5631f7847f869771e79c139226e9739c3422b19063c1e17090a036fc18db4ec76243afea9b60ec72523b5055faf93f02aa71554f9973d6e0ab6a5b0b44cdce594f1c08aabcca8c325f9b92cd06a276e55560155877ea8477be5b619c22a88a6fe241c5825671a7c6927d24a53404898afcacba055156b8379f347fd4cd975bd4cfabdc6e5d2b6ef3e1b8f3e7d756dcf9f3bd8a3fb7c324cec7c13393bad684c65544612da6ba4d216610f793b935f08a096ac70b2adff95feac6eb239f986f7c24442e50a2b8aa003db46125b2da38c2fdda89e6644b116df3c16c3d4666701b6825063789fbc9780aaa4ca6f89aa9b6a03244c123d0623fffea4e6e79307bf213cff51462d863cf2b2a4fa8ee04edce1188e215c686ecc4a81db12098213333d727b5bb9b075a4e0c53d2bf28d54107a8468d393ab80d5f2918041c85d99c2c4e90a81bbdd839295e161887e03e10830b4c8c1f18218740c500aa720835a057d890c17db97c850d297774f90a1b4a18418b9bec4c321d17449d7115fd7adcf8f165fe8377dac89e64f624c3bb83cf9d94ba0d845235339e4926422168d7f5542c503108e96bbfbd4eab590db400d6d095fa555335fdbbfdbc5a83c6f0bb9b7b98377b0cc3be31cf7e0713f54b5d8ca35c71ef9cf5f961c57d8e675d0f06395035ceea78a0726bda689d00d1d9d318994da5eb73520b600da5628c99fde0cbd3c8fa6ccdd1906d4fe9e8475d6454fc4c067a3f9f9b3d5b0fc69a5e4a0b0002e6e3a108bbcc651513ae818529a7f90f4fcb8fefe3a7d89dda9e6a80ef5f24beb44aac1225fed002271afa29e9a1dd81a8f927cdbb93664f0c900f5fd3ac0f0bfe0ff195a4a43d69f37f569b3d6996071ef9dbfc9fd5cf8acad0cb27fd977a5e916c0e0e2719fae989483ffdb6bc92160d2a27227de9f7a5f644447bfad4e2405fb35709159ef8a45772d2de2a89f674fd4923d9d365315b44c6f7374285f6c045d7536b011f08e143fbc3e8e147ab0344cd6738e1977d0ff05e492ef673aa21f28c3ebeb4cd8a3edba44ada096cdb411035e7d7a021834ef4e5a9066a79e49f78e25fb6ab448e75bf168aa27e9095e33a6a8450b975f97e70680a54f52a0668c40e75997250bfd8795abc15523cb1c216bb16b819cc6cb16301005bec00a095a07e94852d7633a4245ad862b74266a57e3176b6f542282b958b6e527f1b680713ea4763a782f6248a4381414d1cd58437c1638bdd8e2b3b29177d618b9d8c1604510d3482fa511f5bec4edd35907b4fdc6b92da62a7e3ceae9d74fdc45103a0976d2088f214aeb4295cc805811c6f95d413d032cd4308614321681042c834d43d18a78410324fe9ef90f414fecd5950598a00ca08a10cf54233cac0fae95fe1cd1ea39cd7d636878e085c18c39ecc21810bbf3df93206870bbf2b506fec16a31c07cc3108eb978fd3598860ea7672351216da404848a1ad0d40b91f674983d7b8bddd0b060927eef5f17a0ac4bd9c105da6a93a228b4d76a3e988a10bb3d48f7d84509531704be2800563a8e1a40652ca78c14a422c57c600ca957fd9082f74d03ae204373ebd62239ab1e2c65cf9b4827165cb881604910f85145bdbcc98429314dcf8728bfad53bd345577e943b50a144171f8001129240c51cc2054f5c90c78d50dc184d26d642064a6c37625f98da848171c42cc2093542965611683c2122cb101ae04887208a39d064060d40d1c0086628424803538c98818829401b116f6ad305e594725e39c38b39e7dc1202477114da84a3e496275cba90e58c222967248104356bc2fd6a3555982b50d0407d31a668c1779b1391f9e3c49dcf949a482e9f753a5c10051fcb16edf7e180848beecbe700affc972e076c2bba97d5b1a4c2fbc5e81c14a8327dcc8dff2a0e1badd501deecbf1b1c94dea875c441b9f061daa81f5774e18f9ec3c238d9c3e7cc701b9787cf157164b88d6ba5aebe465f7244b08ff2230dd84986f8d8c7c7e1e5a37cb12719220ef24b2f5febad748a5648e9e5cba7f44b5db51f0feea8e3a12f673fbad9af9ab6a2cb5a6f10420cc67252380a72562e8c10890a3f76f386b6606e6ae5b2501ac08c4a9d97e7cb37995e4e8c820f63fc669c1e1a4aa9fe1fe58860d76d756bea72300d7cd9e948d598dca66e9c05f5fbd44d0e8cd2f6d0d9f5cf201bb186eec052fd53eeeeee5f5cf525821604868a6a6aa3eae0bfa30815a3d79411fa476b819fab83ef284265efafd4abc0a1bf01f601ff45d32e3f64557740882ea264546a5ffe6b630cbb54aa948c6114fc43328e0a17c20ba1055282cbead800023f16b2018a0f3fe38244185a2be300c13832e3867037da8c4a85973929d9cbeb7229d910fa57f7f98df2e3d55bb48b034c03bfb92119957a5d08c4ae99465a23eec5ef537c5e65ef2ca86e84280650b2930cf179e2cf3dfd4cc3256a0ee318b98171e2c36f00ff507259f500154ca6528961b43e4c036b9ca480b88d3b61038cbae3dfe3957d270b1620687606b59f4f1e1f5a254a564aa215b2e255bf9351bb655ba8e3a2b2ad1d79a87340c6500ef291c02d5baeb4523cb2582b0821dcb265e55bb6a8608d1ca4c86732ae14fe359aebe118e0459ac96032c84083e4af792785452507ffa692833fbb3d11e9ebfdec3fdd16883aa51b5f23c9e719165c22e5b4532d7d7ada81d60451713eb5a79db9a24fbf57d4e12ba1d683a808df2f7b0351d1e1f717f16739e40eab10c10c6a53aa5258f2990196158b48cb8af6546415fbfbfbe10f90f084751b0a1ad30cd1a8db44d88d80160eb2dc1c97afb0c1ba4360528426975b4320e372d1e5161945b74504a13b4b77be469acf3ebda2fed12dad19a36234da229ee7d8bb5dd2abeca36522c40156d803f52a7b13f6d967ba41035a1d889ad9d78c7105d9a11f29fc48bf5f89126a7b6ac66022fafe45a875ab258916fad7f350eb4385c7bfffb2ed478022d03750901decb52c5a9f5e65dc8e0645a3cfd9688b5809a201f58abe6619a278a0f56122c40156f4817a757db44b7a757d0d1995fef5f1a137478fa31ddabbd5a27fd9d30eed55967daf32eb43c5065e3151424549665b6360581a0b44c167ff22bd62615da06e50e7e5161144440c0115911842689d9999bd2111124a184550462a0b8d67be54b4ff10de80c6348a20d283a296106d04418408c804d41aa24882c0d426178470c6d0d51982e8679ec0c48d975b432cb9dfa7588fb385c6333836f84acbae8ecbb0e6d0f12e36bbbd020f6a578710be91d9f5ecb8c745468ada4a670f59da9caf75bde209ffe32e43adf3558a21b7a062d6870a8ff6d9fb68f2a177d2c8e415e43c2baa7dbfd8d14752b677ae48af36ebf3c34ab3b36b2b3d05a2e0639d110aa9b568d4af592c0a37eb2c6d0438b6ff622ebffc41ff9b5c4b9bbd69cde2308dffe7c275fe991856997ef20fb9b10d86b33d7f896db4cc740056298027c09effd55120870ab00f7f78861ad79f70dbfb180ad75f07721bd75b88eb708b3567d4ecd93d6e48f6dab5c50ede03f00eaeeb34ca3921432e4ec80e8d456e988d8614512087f6e17f791a5a9e70fd75988debef5bb2e06b00ce0895c2fdeca400d115f7b2930254571ceb466b290bea47a9e6970d349ee12e8fa3e27677e20a4854f8adc58f85e4e7b88c3d76c73d2e6af721349ef99ca734b3ec295da59b7094f7138ef215a8807d785b7fbe5c109bebdbe569e116f5f3a12952aef721efc26da277e143dd41f01f541e5a87fee14a149181eebef19477468eb837c56de077fcfe243a40d5cc525d4d5a0a520a1da6aab8d7c409fecf8516f62a767de7f714f7fa79056d80ab695700aa2d5d8ad1ec2b01b48a95c8821b072251e5f54fa42083fab5909523f0fd752000a7e00d3318eef90d91bed31eb9611a2f2280d70057974d419415586ab75a0868470e2a7f771087304608234c615829478e48c1a85f4f99d21e1dbe13582ea0ae7b8eeb5fe93a659c78994bdcd59c91011e3a08104ce3ef4c7383f3fd9b4c371815f5bbbb3bfe8fec2b0dcd538c43baf33dc639ed29cae799ab537c25d7f34b5b43ff7c1e2554a2e5e1bf5e49ffbc94cc1d58d47e36991c06acb5e646a0ea9a454a8c1cb9809309b08a7610377e43a14d8b2660e4889306e2099742f1208a7d84d11c88da99445ad48f8b8cb8cd478f40c06dba280235bfe61bc7385ff64def97fd67d967178bf1bd0dc6a4c5987dcc3629a1d45ed3ec69085fcdd69c51a9f63f37a319ed7a80f7b25d8415282cb8b204145ba4b18a9e8a25d0e0ddfa87774b54e9efb90725848e43fc1901bca07aeec1a4b5a834ae9574410c15d1000000004314000020100a86c421b1602c1e95cbc2de0114000a8ba0447e529849a32008529842c610600c2080803100032033325b05e52e2b6ac05478bf273b1cc876a547901d81ba146452eef7bc0a948c6ed2dea62f4c9784d52eed030910924114fa59e0de1db03f7be46813a14b0483308478a33da0abdd88f57483bd0929767d894aa9d6a74997a1d1efdf20adb8322ff4b9f2db32957ea26f2727881446ec4a0366981f409e52a16cba27c834a97f45c2e8d2266ddfe35961756aafff73427e714d0be498bc34156c2712b34a8c19376272f61ccbb04846d1069abe353cffe41950d37bd97b971f906e73df893077e4af3278a08a15d69543ee8a40d25557e39c1447ae6f27d6fa8623ded381c5db8e2645ed5362123465a0da19a66d735cd0901b33250d21d3a40c601e869e3a9b7e400274800cf624a71aa248b5af2ddda3104e8262d332a7d4534f1d695510b5435dfecd623eec499ca60a19a64b673854e7e8b8351a8c1ecb49f3ec57b9cfcf6218b3142dc652067ee3018ee3f99e4bef8a6bd3149fd698085d9b26191563177429c4204ba735ae485996d66c534f02d8bea5a5f99ebf4df263bed1a7ea3db6ddcb779e5035886f68f9ad01e7e6a411591c11a285e9d386e9b95fe3849b3b131296689b60c7047fd6af67300e35afd4afdc8b1af0cc57d479aaa5bcc3b0495a6ebb1a4dee9fbba915a5e6b9a7b7a83e7ca942115f6a4c7636452c6cce9b2d3121d0873852869c1acea97138132297234ad9f71a7036ed93dce4a3d06876107bdddea8a2497b7a103032e60360b535ed44e2f434a3f286c8db3040a7724415e7855431fd9d2550691537de0b6b9ac00cf6060fe1dd1914837aff900d09e14cbf9b34ce17ad80f44f4cc1c7bfecd97eb4ccf1e734863287b6749e0a597b3217d375eea163f35075453223b0ad0463ef194305c5e3ae9d5d9bfb431a0b34fc277c567413af7ad0516f20f7526b81b41c14f0285746e9244f91818bdf417f1fd299aaa46dce45ec861de8b6ecc22c1f9bb604676a3429910131b10bb7fb15b3b27f806bcdd4dc9918ab7e5c3bcc653d35625686ca8a00dae6407f0e3abf8444079703b0dc69b0aebec2138ed0e46f218f23ed8fe9337ccc1d3174d3f22d25120b373ebfb55aaa7927d9e457a984be67937bd4400b158c16a7582a3285187737220644d1674744542e2491a3f12e3e2c208b0c3e6a31cdfdb8db67d6d421b0db3c3561a3deaa9b3300382a6375739025324c9f99e14007fa8255f8a775a6cd7b7ba1ed1430fd3e025ffae9914960d95dc21732ccbe6af91a7d969d7422103823d92b876a54989366723d29155b1b652a4d0713868417e99da2230a84b62b9ffa95df646bf920c6f5a5a209df264518f62e82fd04b877ec21c699d05a8fedd529a529a45777e211905de71d67dad532308ec29c450f9704cfef7205cc1d5b01fabdb8235f93c6b229c1b0ecd0316a9a37c2b468962deb93a00266bd779b973fb622e9455b014f10e126fde906222f62ed50787f916944f8b3c326ebb16f97c21ef00c3a881b683c196137cd1dd2181a9b61d00cade4d67256ab6c36811520dc8e8f598eac6b96bfa24807d0a526715456ad9dd5392e45d278fa52db44729ca53d27f8c746fdd628e7680b1ac639c0b4479ea1832dddffe9310f7cafcde26b1ce83ac0aba9fefde4f20bdd4a697e5ad76b4658292129e9aa3cf3a3c5e64808cd35d65d1ff1831043dfbf4e30da8f62e853e320e2ebaf6b4074bdadb306c5de1c6450baf472f59d1f579acf9cb60c940737e52f9529d9d54980e634f15b3732c56dc00204014a399d294e26a748e514f939650c25c9e4833f71bd3294c0602053a530d90abefd540f85f5e707211f476e865f51315c05ff6706376ac6b493cfbca1cd613bb8704c1c043003fc100f0b0c98d6ebde89a300ac9ff3cb4d9e179da4e307e3564725bfa5470c94d164049b5828ff385689b94f3f892c0f8a4de70594968634ea62f95b603392e10f934458131cc2829fb71b91d5ab7cc5ba8fdb090bcd6462d6d6b7112c209022d00c703b61ca3f70769d25dd7ec93c91aa1a3aa08b1fc2618dcf4bfcc7614dd6968549b6da939444e589d17260dca0d9defb984bd573771752bbdcbdeb245fa129d25aabf4e755a6d2ffe4a6070ead45120df5eb2ecee3b1116a37fd61d0bde355392efaed4f917cc8029f80aca4a44bab6f04824e7de31648a6a7f1bf33665d0fc8547a8d0cd6d5ce61e82974da717122f615cef89cda9cd04100a3f4fa2fc88e870b2d10d7a4ae535e15adaf48b6941a754789547fa6a793c16fcf49acc54866c396dd637c756f06d458caaf97b08192b89a957564ace6fff982c7a17a2e31c0e4393eaa2db87b007be308595948fcc286d4990731ef073d9cb7a3785dc08ee79f883b352a2f98d000978449cf4f78fd59966e09315f00af3fc7f9b408bbfd74fc03ae091c8e1ac83859f9efa1d1f78f1e10db9accba489de6a94231165a627439d5e81455eb98cfd404f86fe605e103c650133029087cbd2aef653dce4d82f41264787f5bc94d02006231e10dfb31802e9d52fb3d96277c0add8219480b9ec5f6b7a21d674f51b46d88d0354a802d758d6d430bdc4d9050da6b49b7f4b30ac0b552c13985651d3339aa0c309281050b4ac8cfea394236cc0da609e7e904b2159195e69959fe560b265d7682d212ed3065409136028c1381a0a6296e0b1c7c812f55d46b07b0fea6a3c346c3c451c03b24b260f8fef63aa97325c0246fd5ef1209533ef63d32cfcb5606d063cbed33632929786041d91eb23c1b6ce4f8538f013c0cd045ac3a21e793ff965ec171bd0a0537e42b9642b34c221238dac2a9bb72073ef45cf5f82a757abd14608ea9922b491ad78ba3bb9fc7405c6077e5c40193d65d9d5ff7e750ee19d2e361655c4e5dcdc4e5f09357e400891556e5ecb61c11823acb329a7ae7e864e5c6c8c23d75f74b9b5ab47844fd31d2aa0307b2dbee95a1d4c53cd2df7de0159c3283ed866c9232018ee686577dd0adadac3724742074a9a5f37a41fab171400a989cfd0c878270414f62927f78d20875e2028ce5acd0a66db899a666ba5aa361de96b9da3f26c5baa4cecb276a4e5b79978f005b3c25b44f3cb2edaca9b53fb08fc9e1084eab2d037ceca42956fec090a18a154383bed00d6a7d03ccde11317107e09c2351e99af5eea103af304365ec0f94b65a30f7d228c5bf72a36afd4c24c1efc99419f3a0c0fe5db33dd2358f60f78a87c52402324e1dac9722d94b5dc62f7543d5a09e03dc74cb3ddabe0228c297a720f54a740a663202a68d0e04d1367602598a391c92ff047c8d34f4446f95dc3bb60e0e5b86a49ce615ff5df72bb576a04f11d13b706aac6f23f4c81e540d31e5c0cbed861ed42e1b2a948a2111fe46a8535d1f168f9de235ca0c1807d8cc2dd870b0705520a0fc511a2fe5d442972e5d88a1ff98e7965ac31ca22d3ad1ab59b99b12703389017d6c1ccfe2df1abfa3da2c94e9e106b9bd9b64d112e9a703b389c8b3c9170ffd7ba022d5255ba58b040b37b066a79a934e97b8c14e99cc02c64d5de8051d715edc14114f4821039c4a7103750fab7d994471a32b9934018ed0c73b1637cbdbfa5ac8276e26179f5a8826018534ef1b93aaf04b00ea485c9d93accbeb24b6854fa6703077bfafdc655c67cc7565c94a39ce94922c5d64e5ba933bbdc1727df3818fda7d1cca32445f04abcf1be64a10f3d70a47026c02201fa35cec8c5692b88ea2ebcb178a51568d406e51b3a418c592a246fad460028ac0519be451405a592bdd065125d37818f459c1679adefb20963759cda88b6f52afdc628c9f1bdda3471d5b02344d492da4a48a83befa10ef1b8944782701b3c16360662ebbdaa6564130847ac848941251c5b41301c42a43d12e0c6f3defb328aaf8287fc051846758c866c5420f50325ce3a81b8c038780f6f81f11b83250b1ed750f0d2732f332abab1f854f780c34ec4af3db42b048ad3377828785e680d43e4da611d579e97de4b8b2c7c7f8e4caefea89e321b5fe868c617e68dc3ad178bacdac9c04a71a9f4f6e0005d1755752d04bb84a239b5ff2cd50865596899ca1cf3e5fe2238ad0d87e60f9316e150c8d873b08f7a08cc86576b2c79d5acc09b8adb4009c6528a54041815ad12a0042d1a7f5e3dda474554576152339e9ad1faf866494b9dadb37ced17c7c4d0ed7c8ada71d3202246697637015ce7c71d3bd6f1325abf119eaf2fef79e2c15c9fa7325a1a638fbd5028136684fc7e76092aa31c3f3550255fc22c9f1c89817236b743bfb573262098a462b22a4596de23e096f79956d744e9026aecdd18fe05af37ca753c6136534ab3de739f19054798923a4bedc1984f5e92e18a090ade04e7c8e8d41171e32d38e41514000bf47d3b57a52a07405eda8a9ae0bb5f2f329467fb89c10164176e2df92ac84c578c384f9bed282e0b07e3e5a46505e2d0f8e6ef0afda3d054e7e86908b1be913253285f1115b4e9931aa0f7c11823936d2cac2d05857e0b4bc0f558f2523a93f02dca963fb21a3eac080c54cda404946b72f727cc8fc6990d624b86f2d92ebc50d73eff95e6f3d6b9774a943e9c42f027669b6198a80fac4bcbb12b6df2fa333fa408dcbaef4016c3860b18274903728e23079b04d026968e5a3a07282190b052f0fcfc130e0d433a8eb485c318bf20e6d88ab6205521991bbe3adb22fe3c67eb88676c5fad8b9b3eb4a4fd405bdeade12df9fe364edc2114449d2620257ae4c46d534e2a41e5d09707e814995c550c7038d47c7249db13c408c308f782bf85f55d97f897e37a61e813f28e73824720d7517e9a17cd5320f8edb31ed5980ce787d3bc4c490b1067caf6c20448176731f37f323cc8a1457c04f7f26043a3f9d1b8d814e3c62b7c454b36d158da504addd16639b109602714fa48cbd1059970e4b7a2d4cd4e07ea37dfdf7373e1a18fc6a9d5b31497adb38cdf641a8837ae31fa397d79a303731f4ead154cc48aca45f0bffcd6d64684dd7dbaf5c7b87b6a04aaf049400d48c1a297bdf2621252653aa9e6638a24d39c06fbcf75ec2f87d72b15614142d422db44cfeeb8a15c73890a5b597664220c451cb0524579543bff334149951337c918623c4bd5a4b4f2327843ce48cfd78e24b716f18e2dea98361256ef990714f5db2f767dc5343ffb88ebe8f7861d07c9bbcdb843b66bc696174c421a27bb6e98b1b114fb515bec98854ac57d9040de57e57130816135731b67295d3923796e16aad075132da0947464dd314b4b2646d4e2ff48089b4fea90f0461d6b4cbfd8100e64523a02773ce0a3fab64e6955b2dc76ed4eb52e7231cb328c714bbe4ffc9caec215593343167eca95d80de326b113b0f1352add7b31d634103b7100469be7264a03e31a9e18a44eb0432510b6ebc53bad80323f531a8108c7accc67da2c573fce609ee4bd78cce03f17c6e24a31f37f2594b5d1ba0d4b573b1a4341635da765363f2840c748850c44c5abe535e0cafafa284cdd83facf9ea77ab4312dcfbe7079430c314c669efc11fa9730d3cafdb5a700bf5c1dc58443b76d2f1ef700f9935a57321f382e44e6b92c3b700ccfa43e6b9c4caafe48b19672062a126e26f3092168983a888cceb61799fdcf46b207075d13fdce31ac66a0905b6bc7fe3626d19a51b461b3a6887be4c55ef37f25a431daaedbdba49a0b14e9bc0ce4314c2dde4a2f7e8db506d6655a851ee2f2c350a78a5046b25dbfc326643802370ece24a4e445d7deb158c71a9bcbb02e6e180c00fb9eb3859c676d9bfb5e6415e93505454adaed082eb2810f364a619b37a69daad3431e0cc185d5fd28294b4e01ceda81d819f527f032771e97324cc8c94387aa6b67617ad4cfacf611eca215866d6355b14a6013cb2dea0c203d685bb19a6ee84517863e21c9c4ffbe2eada1ddad0430cffc8b49a6f118ebc3cf73bf846dca495129d3be32f45be73d2b126c500fd6e4fac65e5f9c7ae21d922970e43fdb2c32d507c99e60b2d67a5efcc1125302bf2dfc6217123bfe65005d9055ca9659c0fe162ac480015502b28d1fa26fa605e6a1c409096c70e0027cd9d214c8d5c82b8ebcb06cc80c05ad7e8e18cef5974e9f07032f889f8656e7a8187146f73911058b549f4d673f378e07be6dcf456db0f6168fd51fb2916d6081703634833e860f601737315c6ba830b14a3212b7e30d3b7d9b7dd7ab340037b2a9a60715e175acd103c6067dce1cb6e65127357410a9f6bc4dab2980c3bb30a911623d193d1be1162243e4447ad47455db0e3eb7e036f1ee08bc825ff2f43deed1791a4c9484525e3f4feeb73cf85a3cab289544c588f452e74beb10ab51bde1d8cdf162cbe246fd0a4958dd75713cccb3ccf6027f71144df0263c46fd652f25a15c81fec0739798a593dcdddd56db573a0aebc1556b45da8f826de5bcbf40b3760f6c8ae86c870c034fd72dc27333da0c62e04b858c1b0bc2e04896a7edb1bc9716f065f681e97724d7b653f8952b3ef9dab0fcdc95709064aa66041c19153ddb10183132ef5700c1068ec7669da674b56f3ee2111fb33e8d02ee44f8fa49353011cb0ecb865e52d0116480b9673d063f9ca93d33cc429259b33dc6ceac1d09e8bd113e80957871b327b9da37c9f1a6eb120b46ff416836669400c587eb62c3762c35744c22494c6c97c0447ca67b6e60c7ce7aa7444046e40849b290bc8b37208c395251a26e843977eb2fdf64a9cff1aad991be60d5a17d1f74248813c77863f37420065dc4886ff338bd69cdb46346e2d5107b014f6ab990e7169760cb9b9aa42a19f6428ec8fc640a8978b1564427dfae96f63602dd2646a14efb2b1bc191d364bb0c8ef68f1301de8e2edd98260a2a98034a2606dc383fb36e208524e19d799c18a913ab05ddce6e89915e29a26df4be867b87379d22af9b0b60d54a7799782ca38a1f390f5da577087def7a70b9b501729ca66a08e7770fa695aa0e0789415d7e6dc901ca6b75d769a52a86bb42d8de711fb3b83c4931f07638bccbc113c2b5de1126a45d0ef455427a22d79c5c22876cec885b88e80e40f6dbe3cb852057e0610bee421d5b02ff0f956a1a027ce331d581190187118a7988e594c16429df372c76704bd81951bcbc5e109fc56336b71d4caf960808092b06d3bf7301b4839954998e16f5924996e79cb5290c81b96a34d6e067f894cadafcbd2e2ca672cf96352e6064ab72cd3c7be6aaf35a9896b76ce81ec1afa927a30c2562bf7982aae4fde3ee893ae1b137d9c01cf19621788ceda0073592ea93b9dbcd4eb9f8697bd6716c6b3a9cb9efe994f1924f8306c34680f71042b0804f4dc0f600721efdea709803520bb08bea44d79e0aae25534d2e58ab3e564ed356b5a7c4bae03fb3e8683e0e507b37f7e9f1008ff0384017ea1ff45f2d46b725c10ac1b457e8c0dbc245d3b630eeaa26e7177c92c0d3b02c7a364e3ff33bb0db938f51de7e8e5933ad8ccb64613246d12899651599b2d35dda96e99a179bb4b15acb1a3508d1f7b556408691c5f169123abd9c66237443d96f9d4d148bf5c199cf283b5db45b0e7183e884c43865c7f3567d2f7e937198ef87a6266697e3daee88a68e2d374eb7b4636acc77b9e7a8f3cdb7a42fb43b1d531b2011d8aab383c5ed3def1059c01245a681efb10486e94570191376bdc20d663188519153dd063dfab66bbd551373c0672a3d9487d029634cabc5876413b4c7785c51e8ae70dc11f050e3b1a64d02a683094124bf966628b8e068390a815f7f79358729a59ce44697a9c11d2a43a5cf1572287d3835614391608cbefedccfcc730b17c489ce03c5d9954f2f7a24a11236a346feb1d3f3250204fa0eed4e8e8f45bc8e8ba9b10aaf9cb8c086137a75589328d3657bcd94055b85de9971b430508f1f824e7b8fd1e969caa6acc22aa23fb2e904044239d1779a0bccc0f6525acb21e760b624ffe5a645388865b7c5db599d6096723f1e0a11a8109a734ee1f21e84058bce70a939426bee69a1026111c802593595c890746b195c18cd42acf43986405f82389520df7eb85605b81c3d5b4516d771f22a6a89824fb651eba0a60b624c77ccd50a1249b6ad692183f25917c96e447cb60122b5b911c774692130347d8deddf74d9281a088b929047ffd07fddc94f6f59c8a249a7d0137f038bc0c7ddb825a6351636ab377a54217c148ea145fc2545f779b4181b8403fa22a92389685c9a8542682534c26bf4a1847962bfddd31fabba703c94619ad48e4c982d5e71643cef7e1259a7109d9e5a7d281a0ebc4347120a4244192985f3a5ca245e0f8ae86700ed946f00a02599e9bdd1d5dd3be72c471b0f3074a15461616ad8f8048508354b727220b2959104d84b80185b159302858c7a139d2b17d0a32c9594f4207ade5b51ca0e30eb67400cc8e85b50a5f28feef9b5be983bb277e40c09fa40520957205cbd7f3e993383b518d2eac89c107aa03299086ab82c94002590122d423526b08d0e6fdae4b4b2c7ed2b83da3428fcd8984030eb5bfa2a093a9203b6954ea25cc151d73c182564d38022c89a43f236e86995f82b923dc5ebdb0402826c969d42fca782b95fe01ed3379a50239446922317e67563af0033351bf28416815746cebc787ba436d66ba23a3d0308a2346ee384b94bb48ba865127928b9d220b48c97efdca93fae75e1433df6f9802bcd50becb760302e69177285054448c61ee5f76b9d570f9066220534a11d86deeb35f7ee18b9d82a82e06721c1dff1fe039cf0faad120edb8aeddaae051777bf11aa57be06d74615d0772cd1783234d8fdadb3743057f6422e6ad6125ed2292834d149219f63e05f7cf258f0c8f5e5148409116ab83d7f731cf02a553f81887b3903eb4fab00aeebeca8cd29366ecb3c91580ea15adb83aa02e02e07184df61ef5f15985e1ac6db9d976616ac85673fb471c2eab814a2d23c4f83549c1ce7c0d8dfbe67789fa2b0b09ddbb01ec673e709059306244e9db32a7cd0f4f9f2cd4658c68a1fdd8e6010e1d026bce66ab451045e18f861c0735c3a60d6f2bb220111e90223f5125166e66282b72db65c4f4f581eaaa7f2b79fa23e0a33bfb27f561346031fb0812a2a1c6155af99e406a80c33baaff903441c69d96f0a3904c56c7e35ea3901fe440d13c6716121fa25d7e49448a88157eba6f650d50e02ad89d0e8dc02ab765488e049a1e701afb1061de9b4390aa12428020f21bd9a0a23f1b73bcd8de32a4d076c4ed6d0edc60c52fbc1cb0c4cc7850f1d10755faed489edc0b925868d0981540afbe5ba5487bc28d6ded347b0d0a0137d4732e5cf996161685018e3c7d2b900330f2667e9a82bbd074b4ce64bb85e6f2f3e28785e6b0e2b99a4785c3bf403eca880bc787965b5079e63f5025e4c4b3cb3ca4d7dd53b4c30c0322b1370ce06fcf188aa477bdda064c09cd63ccca775558d45c9d127542f3b63134b80f82a53b75091b826dd0d446feaf9e97eafbdff7b7eabd33366b5310103c652141d439f577892f5d0eb154eecc3ade4eb8ad9c26eb314d8ea205e12d17077b752e21292bd69a9e3a5ba9b2a786956328eac0d35432dcab5b009d03d35f7a34fe39c42fa9e49d5763a734e8b0d3f9539d1bd0c3288bd6c26c4d6a7c202286ccf43145d921d206c7206234557603067703d32c5257bef912083ccfbaac09bb3846417adb8dd823a047e19bb001633d6591ff6565caea25db2121e10ab7a7b25ce6b28b05b821eb41b5d1ad819fe036fa53e8817df628b4b7681810fa8dd9f2eeb6197ee37f12dabe44791c2063ce607c874e79683fd441243d54144fd2a40475b772154dbb2be2330df1fff6db04ffb82c39c8bce50ccead992c56c6f09dc36197221b7fef7ea2bea65b7206237d32430578188a4ca203f7b1ba94b012d16bcb1485d754912f53749c8d760aecc70fd7f8a35b346a6efb530aca2d1a7bfafc3404170650429b07f09f72140ae078798680edd8071c8995d0cb9796a691907975b6ba8168d7e9973c287397dfa401e8ad47bade0eeab76fa47a102e707c147d960aa870f99f494c4143e571ccbdf32f79cf6134187b3170f5618ce44d5be290d31d9a19738319a00e66d451987c1acb5bf2445fb02b5771193aecdcb086e1281e6c15c8c13c7f8b9bddf936d8ba73c41335b3807f493a4e581e8b7f8b066c6405e0562d0f9737014be10faf2b3f36dd0fac95ff0022083e00425cb96bef9b6477dca946fe1466c367662aac4e83c2708b9ebc283e4de28d92316d62d335ea005fd531c96dc34d10e36af8e8b3d8b6a7912a30377c8ef8d0b7fa914b95ade0855ef8457a313ad35bac31ad48059abda2ffb4895598d1ebc2bf805627f193a4b2bc61f2280fb75aa655b0e427a14a0e79abd5ff2344c24478885eedfffd461216c5627c268089e8b5a16a0e5a41027543e0fa31480c212ec46cda9dd4797edad82c9fbf385825214d9331362824742a45970dfe90b3a21d9ea124ec478f86011689493cfec428f9e78477c42340c207ccfd9088861f9897815185850117b8fa94e9ca4b8684746816948d47485dd40708bba1c218e0da74801c05b190e0412c20eded8216d4871d6a4b869e65a1ae2462f1ff983a71b757c8c42a8dd375130c6a32121d22bda395dceadca9d2bbbf0677efb43fa0ded84bb0f40aba920317f0436b35f96c0d76ac9be17bb87886d4c745cfd416c4e1b10aa370ee30174a760ea796a2d265af60eae5f6a6245bf28a8c3be279d4a1781e7e1893158f133ebf1d0d7460817f465765a0e545c8d61b0d2547cc023e62ad2ee741798cdaa218bd753b6595c6076c8d1f0da89589ec7b07f3671d098a0479b70040fbf3437cbbb81b60433434f2795328a0e15f58aa42a61654e29332cfe10d6f25efc1b5d40c0520b94344d9e463a501b9393e6cec1fef15a55807dc181bd0b75faf9d0324c9c216ff41813fd617de51c93583ec0123982bf6c1749db84152357d2ea545087d48b89a033f4f54a8372d4ab56ce3b1ac450d81f69053ef372d379a1c7a0540ac7a24114711e08f85ead3870bd92d92db746df7cd7ba24bc6ac19eb8e1a08caffdeca19502f4c90337077113f211db1055bc0dde8fbd8d5be02aed29fc06962e91aac9dd3d8858374f40dc06db90f9a9e588e6f6fd90e20b9063e57183cb544e726ec5a94a02839308859f2cc3a609639647c80e81b0ce86376bb8b1085080f705295010f1d3e3912634f6b603d158acba7abf7a9f815e919988e16aa9020e927da69bd4adeea9afc705d5735e81e5adbf47643e88f9629a46b3971299bedbf2e05d1c0ec7d4e7c3219b256500df450d9042c2b2db7fbcaca9fbf282514d673b03514b03c706504d281c1fb9af02419aae94e1dae558341596867ad56a2e0d3a98169499b61e9c095be1ca01b09be153adc014935b2ad29b40ccb45f52c2aa089d9288f5409038acb2094078045267b96bc6596e498ff730a34f6a4fa6e2c3828de795c649a2866029e009382fec8a0d92e90da0cda6c2b84992ff75dc7be6b8e5f39be7c1ad5c4fd4ac5704bde5a2a0ed77edb3cc47779770ff44ac4f7be53e739bd0799a420f46217d1e6a9fcf876111c796708b2a508b37088aaa3975ca087a05ae7c259fcec9d83c3153d1f8f5eea9f17a8d6412950c45c04e91c8738793e8e21832d7a2a2b52755452dd17b53b1520d6868a7119f94b56ec3bf9ec27754e1f8ec30ee56eca4bdc4009a60f05d3618ebec78d266e4da293bbec28b05999fa9cbc8abf8c2a95634d1996c1745d8654e0580d41d48765eef94679790861f22f428872c64180283038dc0c741573e28686907140df7d5e299e6c5b8a2ef05ac1ffd1950ec858bf4780ccca2bdf06b42c20c51df415976a08fdb1dedb0fd7d021e3e1c4b2dcc5d5b5d77a24e671260a4237b29981e0a5c43558a1b56e636f97ff5169966a2ef2282a1ee075b6e00431a936e130e9a820a8d9ae34191240e7102eb753c8263f83afcfe1624b4065d3cdebf60c476c1b852155f6f8fcc3b2bafc64d9382d1cbd11d6e59c36f1272adc52ce743aa3c3d9c96b99be2f071e2704b32291bd74a80c2ca9ea9a26e25475cd658f864678efffe1ff456d9276370dc58315333e6096c1f3ffc30a1c4159a64c8591a4b997c24c3a32ce7c1bdfe84eaec3a07d0b7b6fbb8e374864cb49bfc3ade10d9babc5fafe88279ba5ddb805325d88d7bda0e4f229cf694e2a2187f894df0fe1465bfa997d8e10c66a35ccac58dedf0d3a0010b24e6a2c12867e83edcad13cf7663ce119bb8d48c0c977e3319fce73a6d3fb6e9b10a7fd9893e3d5d236c9cb03b0b0ed3680404075212903fbb664189ab5b9d3c5489349dd64a81dba502dffb2f9ffe2e171b36f98eabe52934f7d58d4a893da075e9cfb61d7f80d53668f764bbdbb2a4b0822df2df6beb583810601d36bc0c180e3683e3cbee52cb4ded0e4587c4e86d9237709c9e2c93c93e133136c4e3188bf25b8665368e489bd87c0e407faf38ed0f8f526d25d073aee60fd01071defb843bc933eaa99d4c1a5bff5a650ec5143ecd050a360f9e75cddb7f711fbba8ffbd2df4e509f0d2614d0ef85602262ee633725de6d102101ef6d8080c0fcbea500ea1fee0f981b53ffd6f77b735b2a5522b212d201f8a7fbd79081ce748a4f95779db0aef77eedc3afe6fba12fe1a6414c2f0ee5133726827bd1dd2b788f759cddf5b215878a2cb3aad2d420a6574352d733d389c02d19788e4bb9958bde5cb86dfcad33c24d195d56728b15614ea4a4553e290606bc8f0103ff3eef8b7dabbf1cdeb0db1273f37de761261bd966393628017534dccc4f3204588bfbc37760dc4a263944fc2b35a787acc4f6c6e912c458a08ce238ca2e104b6f8d7e7d43dc03bf26e3ace8ea7170e18a55256cd694ab3f32a3d838351ec47add1443284f26f0f13656efdeb73ebb211cb726b28c5f1c6519193212b8dbc8042873525d136da06596a3f4cfed931766ee502e6a1968b5ad5fae3e23f41aa4eb59ddbdaf57b75606eac5d2d8cc14adc5be57b75738d49248e2e611e8e5d88034b2aff775df9f7d36f132dc2cd385089ac4e61c331d30d995144211ff08eb37b85f8aba618c6d3102971f07c0dda6646de24e3f92fa323a029611f0b4f9a5ccd6e14c1429e4e10948eeae99f1b40e460a17e29cad00a11d88f1a4fe7055e0131a7b2091cebd4d2cf1e0c08264fe128cfa2f95a2910b37d4ac2c86ea313e45e02bd49226c3ca7c0e8e3573b788deb087f94337b62c134b12a3291de3d35848974f950ec4d77822c8d0fbacfb3f814104fae5c3c46bdf0907617a07deb27bafb0cf0e82f2a103c0daa6897e9e017d988b103842dd3c258b4b5e7a99f77ce3da4985507132212e56558983ea53e23c52ddb4fb149daf24cdee054df3e3f3863e40450f2ff1d892306f934d98e029fa929fbc799010883e7f5968d932bae83613f008043a3abee220b67315e0bb8c6a89941de6d4b90e1664d88cbbf4c9a78eabe9ee1809fcd8b87cd61e8caa50b663e6b308921fe509f29bde6deeabf577cff906883ec57eddca4d29d5bc4ac010ac5ce2a14ece0fb834e5e18b7b26ac6bd475119cf9f0421625071817b146c5a0e751b952fac022192f78050370ea198513055291067c37b37dc3b51042815482f41dd007a5b7c80fff57c941e1d7b1f01f0b196257e536541f3a29a0723e718a19a188aabe6a20d630a466cac365d1f95be9484035d100b48b29e141afa16df78d8e3f0f1c9e534489be19d2ee6389811e86207e601fe5afa1824d730b7665c0ba55958d68f7af6a7eab9ed802cbc32163a2418fef7276d2eed8210729d0829d63b1e5a5222f55dd2b392a8da58b34754f3606cc2b723a5a3ece8f6389baf21c95d91d4743e1666643ccc15969fd5141714b30a9a73d647f7dd4b4b6ea2380b9b2a33cfe202e2ce94f615782dfe4f4a61c0868be12d6debaf37318f32d3c59812542576e32ce58b4aab773f2d24ea282acd03fed7e727bb407b2da611f8c1f59ab4ac8cb515a1cd96a39871e5bf0cd7237665f2b6e7e3a805839e3bbf2aac6808e8b0434eea516d7255366e13754cd3cb6439b6dd03a5d1f62aec269ac1cf670d4bfcbc297d4418e823e8d63f2a256a142a31c327f3e6ee40368f465e07255484f8068a1a46f5401c78a17b8a4c0c39402de3cbd71cb294f91c5a3d578fbed0207a363d35a8bf7fd5ebee5f7f3f5737adcd4c7d390e04a24b87137b36a74dd91ec62d197570a677cb4bf4b626056b51ac28632302761c27f6e8f4814ba7f88ff15fabd97bb8a4f9872cacc1e970f12b118f235aabd43fa8d5a5fcd509abb03d46c26b300ad24488c9291aa43a086e0180947840f0f5775e8c9b4e5af33e4897857875e22c35de80b0d249c0faa929f73ab4541fffa518549d436de8caf339b1f3a6456b35eb199cb6a05c82fa9e96b09a68f930e635e204ee9038e280094a783348feba6158fd8b41e3f8c66ea32e071e066c0b9d048caeccf5be097367484985fc5a843d5b0f001cec072df05745d47957a68a1121b0a7f94ff3d732f865b7789400641ff9a545f2cff2a750601a287c880f6b7276af38c4c7582acf5f41461df3fd5d2295e864e97a1533bd725654f94291737eeab7629ba4750f9118e8b131fd0682a253097fd78fe4267b9662cd7acbeebc82318abb7886688a835b18fc85b4ec91ab0207a4afa13f6ad192c09808fa5ceda217b17b0c8d5e7e20ccc7088ded78678f66300a2a982ea80f0534290694b16ca53e9b8e389480e7c2e2d733dc9e8a2378b7a3157a0bae8233885003ad7ebbd501fc96aa7cfdc08fe688c06dd261dac840704b43542b9e5389422846d5d27d1950c6a40e800f03b07900fd9c6564cd312480bc880d428ba8fe4812475a41c3f301b4af8f19ac43d371687a11cda8fdc87d4868f43c9991e94a9b53c51fd67b82d559c24451933eb22e9dede2e0946d9c61cebf2bfe09d94eee6f6e410c595d2e770fab529b2a2f9607b9b2280e320d7d6b5ca1cd6dcf9850aa300c5714f4dfcb6320332552bb45667b9aaad4118faad946cb3bfa1c3daee34b99756c7eb681d116e418fc697e0eda2d872b2962701caba7942467c097f3bba2068860181e3f935d04292c19816a4d46bb1dbeccede72b1b00474b1de160731dbb3029763efaf1dc8c5c6a2ea43e15118896686563705eb7c364d9d800912f1447dbe0a5ffd5fd92234eeb1e312132d7f1c7cbc80065252137e7f842598d870d800fe2c540285449058c09ed2f1b7f5171e8ad80d18000caf9840ed78eef4e07914efc0b3829fac53f21ce40d17bbd8c2ffc9ace978909d5dc244d55fe03bdc9e1844696b4f4d801ae426b0ce8a86ad3e16ad7fb324039be10bd8162d933b09ae8d9520be283a500dc9353fe8898b70bde56d449fc091389ae4cbe96d2c5ca8aa791f838d40dcce19b56ef6c92d88d79b0c9e7ae87de26d47cb540487b80ec513c7793d904038891068f0cf1698819376fa8b352ce56adbec09293568bdb4e099a58c716ae617b69c78c731a7038166a43fca05c2eaef19caa4c425053f957a47826175372a125220fdd58ab6f0d02b12da8b56926e04ec4a8edfe9fed0321aadbdd501a94019c2bb96fdea3ba2645d1197841b2add2fc5d5c837b3334714ebfdd60ea9b6d021fe7fd468cf58edd8c6ed1a90222c9874f3de69a26b74d322bbc8942abdd1fa622934526a0746b69b2483032bc7a789fb33bc94d826ae63769222b50a0a67be7f804ed819eb556fb35bd260cb2857a50cef32164bc6c414a78a1504bf3bd08059109cf57e9611f55934e1179387266f1ec47477506e326f7dab99261d568f659379fbee0cf38ae81c0cf988e250466cd033a70de8dcfbefa0c58c2ef707448cf1aef0b7be9415b08e1df101fcbe2b49d1ba5e1c22db2983ed9aae7a6502af143a479cad1c3221750fe03ebc24057c111f856b36dcc3b01f127bc283c69a54ea4b423767d06d7a535f4c51d648eaaf655343005c03e4713733f39715467c129436c627b526306527dc8d965421ee4921310441ebcf07dcee60d92a87e0f3c11f1d436681e176f8e81a56f78b20e07608a114653800c8241194c14217bedb270d0a85e61eb993a29576cf77cdaa2c79a4c9b5026e1172d0a30a9c57bd3236a770190846d0be519b60d058300a18481c07cc0d342691179e2ca010aead2ea8dabbb103a2a14b3fc8aea0f9944f75cc2bc48cecbcd0d254f89fd473f2f19ba1c40cd0e2d26a1b6d98fc92e3d585aab68d2b38ff988f7a16c9064f9bc968137ed1c73452c009a228b8ac0d224352ca052617734cd24c610cb1492250854adcf81469a4701f8d0d02efc3e944dff76e86efeedb3d8c8e9124b42fe6c81a946841abc3490341ba4ddebc39dde83466e3918398f393d66f256664bd89cb8b57eb423e425035bcbb8486a8715445c80fcd6c8dbd335465fd4530abb41f6105525ab9d83cfe30324fece74f59123eee3fef4f25fb25a9189124b55256fabe9365ef435e16b48bb78e1b75fa6a0ee1e4c383514446b4d72e95c2e36555a3cad7001210e4fe7a67c36cd58cde12a657b1402ead77dcc32db34c1dd34a3628c9b518e5c2695c437d9d3e60792960a7ed673411feaa3cd07209dde7e6c0156283eef6dda6bb9aad4c57dd05c1792c6b607825ab0b47e4a9e0c267748328477920056abde478941f12b0a8a38fe9bb5f695b84b82e80f24a74f13e96b1a8e0b255022170b2eafe70d3ce53f65c9afc4fb8348b90598a9617b66591634b525657e3e24b08710c06c5969bfc6ce0d24ade764b4c2d857ef0db3e50b5196f1c5f1a827ceb5c7cfbce58ab28d37ab1dc9ef3a0c8ecf252b806c684fd4f75b18279bf812e51e4350b4069c9e2ae22dd8f181cad06258a8c4543d88f35bcb540c95e13347d560a0ab67a5a1c135ccc23c490c7fa3c3e934020ab6b3652fb1c6b5c819c514b582ad5a66c4194ee2a5f4edde7d824fb48e8371ca629667b84219cc30b0fff6759aac367349d83ef960ab6865acb24a6a7cc7eca1e8452012f01dd20d2b467ad84f033c7881c262fbdf4e60c071c112a9962102c8002d3aee0bf2378ca56f129da8bbc9c049330010936b92dc63e9f5a21b998630eb5882e2b8bd01517c0daaea8af9aa2b2e54dfb53ad7852c95c3b32a3a46cfb7cae9fd5340afb0171f63ae53744da451796bc30f3aba41f2bb99bcca0099fad28ab7a813f54677c639b4bc962b5a9b77b24e0852a29e4a0b720a926bae75202b9f2a44d811e7c823b77d8219fab97015e10d8e549aec085aa7927df32b558c02fc4db9c5c7b81f5f3d9a29663c3cdaac81f0f1c167405ef2e8e2abba60a463ddf80d1337c55d133038429ab6217a81908f5b9b9b23517b964a37fe090ef1efec62ea9bf2a14fc7e18ef3165dedd63d3f1bb2c64b925b6bbf51fadd04de5836e74ceb5cd57e2cc8a741b6d6e24145e159d1a852235366816b938492f085d98bedfa8a955d58c7cd73d9ae548a7e5ce6d0c8b7291548280e3263408800231299049872da1e42116a7bfb7afe14097301e2d0926116728eef9db938d209d888921b8aaa12993f6570e728081aeb50bda2c6ffb5fe9e9d98e33bc7df9d952a43e140d6a3b06e76220b769733d9b81bc6c2c7b74e81c33fa1b31e0dede5df22b7c189e1848f704dc7051eb83334187b2112a43bc0628f802356719b0186d1caf84238b2af6cf719e61a27f640b3ecb06d33611f929f6145050c43ef8718cb099ee9ce4ce008bdfaeac68b59ebf36024393e5cf679ed5271f8d53539eb40c316ede5009962324cb1fd7a8beefc1ed507d97174cd7e7cc5a839f133dc90d0fb171a8200c3fb463651dd033dab012b34415ed13a3afcfc25ddaa462d1420c6a86410be35776359938bf3ba99cd195eaaef4709754cd6000c7f0e0ab62f49a92117f2ba9c603ea143f24ddf45069b263db0fdfa2f09697ecdcb2da448f9d53e0c029e068e539b8c2dfaf76d382f9ea8d95304f57c5f0171224e85262052032ea7c2f1a0083bc4822480eb55e2e09b504a5a8bff361e3378c38fe8c20151cc774a8af9558cfcde866f858e6459fd3608b1e13db70ad36cce5298769b27a8bbece2c99dc99f91089a506e36e42d6baa71d396735f3cb2abb0b3d6250b83c0528c2b412ba00be3c95ce0c83e50ef944f05dcb454bbf7c60ab8ecc2ae452d0beb89693385bda25e7f87e19a0cade78ebc2ccc1416076092a42edc1b52ad8f1bfeea5ffc8f91a245cfba7bc607184bdfee0033361d118fe7371b061395de9c712bcc649b4a95dbec8a29ed3339f83d5418484730916d243f7a3237476e588dc8a5f6c72e2d4aa88b29159d5fef08291ef07444d588221a63af7992fac83ca35409e09c5bc23a72ad641221eaaa098c90cf6383dd0219c6fa8a6efc24cead13fe4999642619c61f7858b484264fa17ae8da2ad05e86f1a50b537f1fc47ed14579342f1db00b1b85d2cb9cb5b53590a0878cd7423866961ad5b776c6828fb819d78f5a2ff62792a53e8ce6d2ecc94a3a1cfa635b8bf9a5a3fb9e61888299662ae648047151c46e303264c517b5447c3a095479edcb64c65b76cc7c5d98b6d191797d9e9605b5b4606f379761fc669ecb196aa76c9165a8e80e2c021b36605729d9243c2b6f0419fb9ee1a905cf82adee5bf298d297156f1e6d943dc70a3fc40c0e07c59e9a9866f6b3119c7549b05bfc0c1bd3e652f4751b8d56cdd52060976b4db19fa827e895efb7698ecabbd038c7456e29de81e1e061f65075f4c8244b8d4c004943b266402deab8b2921e55c63c5fd751782cd0201c78070b68a5ae6a2b21d9fcfe3ec6ed896f4f3386c4365fefde1b6de95fd9c185238b206ef6e10ea6dbeb4e1f9ec4686d6c5270dbcdef08ed73bd33053e2f84df5ce6bbe5d4678c07df817f9e0f9a9baf629483209dfda20577bc5192c09c89238e8c98437e22a4b46692f003028a86f8fbd06f251d6ffc0e9207daa01de51c1563843888744a73d8b61a505fc576d26265e4c5b5d232b2308676189173f20d26127276e98d40b9a3ede0050b2d7c381b7c157555bb16cb36a9498630a6aea33564bb99bee57596f0a92de5f3da3f8801801161375ac220c96acd41537540fc6397098681da7fc45d7b1cb940959f229320d70ac517ead4c59077e46e9abc8acb386008f2474e8adfc9162cce5f7375c3e001e84f69498b4c7b67b64e62996e033e9ab04d7b439a04e8ede2510431b08729ece639c07ec3c9e5d7dc4ec82668e08e1b7f5d6489bd065e8fba125f505301951b0a985c7f66cff44cb49c7b62346c0301fad1a61c6db6f5b09ef16ecabb60a83a24398412f43843ca833f1f363f847155d5daa59c17728f8146b315d12ca8c238eed165b0fb01141b9bd65f0e6730a32b3b6aef82aeb4be9f14b993e7041b327fe9f6142ac7fd3654c271005873a6a41d4a404bfbe05b77c3d94abb1003e7a9daa5f6e52bbdba3eadc26aaa4fa43be755a6634d5cbbc6bcc87668b4245d8076af5defee1593d2e71e0ae0b269b18ce6051a6758669b4eb5a6276d280d7f870245c3b185c92166d43d6f026050714332d109fac8ffd3ea3ca511740b6d1d0e4d47ca048761a79946aaf5177c8335c3a622133500d192f412973a8160e464548a00e3261c8ab390fb2474ea18dd716bd9b062261e7e8f6e99249642e9aa618289a3fd9f6d1b925067b0580ef27587ef3fb9f23db0bd91b3c166d1be5e962b9320261cdb07a8f9df29fc698078a46b9c200ad9cfa94077537d8b1aba332f3c361bd0f8fed83762cec4838dfb2d861f48f1dc41636994641975336f0a5e667964951c40aa070a4812bf1fdbb01d8d0b73b37c3886837a06522a61d8f396e4509eeb0d238bfacf05ee7afec6b713a22be268a8944d7f0f0a44e3480e922bc0dadf40caa44542c5a2759e4331a975ab10f9094f1b56bf23cbf8d15386ff9d0c5c47c6e0cae4ffe5046c4da0ed542ebd3f8869fc872b1b71a866add2f050d7eab7d293eca5a86b072db2db7283091c218eb969220fd5a3f3d18253a23c0852fba7520593d00fbb7b03a783bbfacfea0b50c41d4549a5c3582f0299f0d5bfd0f8724f06fee71eaacc90724271b09b482a3ac4ab3f14b5c4b356a0b87bb83c55bb812130ac77fff131eed246bbb5575c355c241787232eb2c85c09079d01d20f1dc3f04f58df90becabe2ae8e4dcbb450b3e207d64be969a4fd136a2f8a051240f38a6f65210a14e3ce9eb575fb0774b838a8341fe90959a29a80a892f5e86a819bcc41120162f2248a13da26ef90785f240bd398039063e7d748fd124885cd07ba3536280ba47baff810a84a828e146b5219bfd08dd867ac61d67a3a85ae181f3be78e15d4eb74737bf7f79c168cd836de2c8bfb2e21d4fcb049bfcc594607c425de3ac4bb1459e07dc1e28f1e02e547007825fa1328034e7c2e18d03be8b713b4f21a24cd132e14ed7cce02537fcfe7696eb7b95b8ae96c0b6a8e003c9948517fb617be3ea21f5678336078c62c68b460a88541e235890a90b7282ebeac89c44b700d3f1513da84e1c9b8fa5819f5e6dc916acca2723db81fb1557659c8ccbe2b9e35b997347c4b5dd168d0ef4113a60c1e1216114d7d60585ad20f651c76abaf908d6da96c20017f26f6702ae133d2cc70bc65c43bf1e65f697d2e916007cc0c68de275055c71b0b4a35dfd21fb7d64633a04db23f49d20bc194fa4f72c4a5e08be701d0f325a0b1340e3ace5c979606571cfe042cffa80ca1eb922d362d1b968f8750f1b95d200c45e9ddf396b9f5563d7b47a1f1fe7db27d31bb6f415b9de9f32950ff0b443e35bb8d974d1fbe2029657c4334db586e8c440021b1119a8c87a3430e6e8d05978a21ffbb56347c176834fb1eec0da0a6807743e814e575f3a1173d6e11409af62e7ed7d160ea76499b3849dd551ba9ebab0a1e4f7e89da279e443c112569c9ef0848e9b88ff4b72ffef58c9eebbf0073fa80e4f38892c6fddf1e9ed68a44ec631e06fbc12cc8106d7ce3c89172f5fc37e65608550fbee699a7e788cb95e0fae5c8a36ac4f47a6207fd557fdaf3cf465ddf79da157652a67562f66683668990200c75138a4a210e439aa8a249e804423807e69890ac91c33febcbb36a947dd85a2106f22361e9d93bdf7a259a9c8f9b4e0818e432ebf4ff1290b31e282237ccf5cb17f42b8cc57b9e2d73db46c8d8175dcac384283f3d841c802af8ab43051b9c9ffac2148e8da667ca6a2b10ba1ff67ee4062bfc5fa66ae5d7a542dc197bd3bac25055683d719bbec07689aaa7099c3c00558745e7c1cfa938e05ceba3d5ee9cf2e0b7048750045011b9133d8d63ab2fcc076c41fa26bce3caabfcb5949af058951d9ac53c22c838d68a4221c28b3180d38f1c8b46f3cd4d9f3eac2b941dc718d40916f8311380703518ed721c1f042d7d18268f6e2239166001ad003339fbea7c3fe9c9a203a50e3433c8fe633879918975a8cc8a5774411bd959f69d573b791a2360bdb8c1ec23f7d5e0427b7e6780199c81530dc8d21c12f14a024f03e220a49b4204c88012c48397306736c16394d090c096fc0d75f5f1347e128077abdc0002016900d5fd6ff70e360b6ffcc1f2bf3c03b6d5e1496cc58e8ad0d6e162847c1eb282b5909620187e6800a636038096b4d4cd9f097b8d2903e531bf674e9fb159e34b1b1050521639f9d50f083fa5128c1ea11cff78f022f2b9c6729a1732e93258cab6f88502f6d7a222ca9ff41cb81703726a7f250193ce455ca70f3f918388562e19ae35e374d6aebd4763d226caa9bb28915577eafecde1ba5f28b47caeff0fa4ac42608066a6ecf646ea4231001d0386743d6462769f81f71967deba1117282c90958d42bc335215a451f3fda094e5c6a927a10c10312188944cf17a4706c05ed4e344c37d3f3e0b50e6f3abf3138f3b5e2401c84e10de31f09754988793241dbc580e656837604d156e3d5abd96dbd549e9d31c04b4ec33de7418904545294910ed2f60c70d3aaf74325f38e853b9a4beb41ee442ba49525fee31c2d4e4f2acd9913a7d584a13e6d793818b80202ed4571a626e7bc3e4e9fdcd3f44521049f63bad551b6c733f37545abb4dc07668a2f51e99f35d4338de4638588c64d0a17619c96f6920884441bf7d563c744681108fa96223862ae7bdc4dbf46eb1c4adaa31a40ec489ebed66921e3d51de1a31dc6d9aab594dcf94eedee1ec71ca4196edc8a7674ec33129bd56a616339bb502113e16c4e888b5ea4aba1c5520051dba1b438f691deacf598c3c9151c28df5961fb637feea239dd576582a441787b5f364c864fca9c4aa1679fef747ae3f488fc032921a30be9ec244585a2564eb7065fc6150d1b621b4026013e5abf5d057b53190f08bf10723373487a637355cecb13dfb6e29313157f248c62d3dc2308daf3f254f06f3f70238e671e7d9440d80e6a8da5ab67db447d51c519121fa412fa23d011afff5f8c5c0be0fab2b2da751d0d16adaa32cad97e7b753a7ba0111ce340114b38145c66af14b10f34d600a5782e15790f72b7abe4b214d040f843e9472679fdc9df40679c7da2177a6b9cbd087dac25433e9e7e549df68eda3221fea3f30d081914b7c9a204986c846db6ee2941661e60874f8b752afa147b6440c53965c1aa75d3dd457c9afd7dc687829e85c384efce6a6b0988789320372eddad3e957c20b7c03ea4e777219d0b636442f2477f060dd6385946d2df75bb47cf4f7afaeecf1e57b74ed179e9aa025a29e435fe1e7f7b7c3e8aad1e88c6d608bbb49bf0c07457f9820d8c9efde01ef6e8e72a8441f6f4b98e02c9a527ba270bb0f922788b11bfdf93fae5645dac0a6352bf39ef36729244a0245acf3ebedaa4089bef9a624ab56d18f03e602f773d8e6280ba3635f55db76e6a00fe2bf95e564bd954f1403dd3be0cd9ec81f185298d9ca704de1ea97f7694a973b1f5899bd67f12efe210fdc0a670cd4689cd43ed8e56383b01b84074205f22f47b8933f5954018ac8d98e61c6573372978f177938c1e446a80284e0c28d23103adfaff4b27d24329d1d685ad791b957dbfb0fdce9d208adeb927d9874aef367c0004c13ae0a86fc3fa18497c5fd8ef2d92efc176feb21ddddb02510e9888070ae8a517ddbc7742bee4b3a1a03a03caffdea17fb3c886de783adbd0750e388d207babb1e7de82bd80ce0bed9e949fae977dabbfe6614a4e881d0a51289672a5ead9a75dd32238660e11b59c81e48df6a45407582d593f8b3f34f79fe26aa040c5ce6849287c31384326c94edbb2744472929b3412b4b084b996b987afcb18c24b8ae4e0550b5c0eda1110111a011ea270c18852e70fbc38b29722e43fb0db79bd7736b1f0fc519e3ce6da2acb6f297bf610f9a7bdc60bc318ca762af290043af03e799962b1018f497b132476b87515dbebb7eb14cb1c28e587c58cbce3d33d66e9c90ed6b768795c83542308246582d4333971824364a40ec7c3990e73bc0b9f5d4ae59145dec0809fadc3e0160475703aeca8a9088530bb7c95d4aff4b6ed6c9d421240b7b815248c823f03397c85e2a41c29a94eb3eae0232027c3d82c9265dc480afa612fd78159eff068e81da3acd5a3aecc341431e9d86a610f2bb8e056707cb3b9f044aea4178e78bc7f0d062b51a77b41e6932f133d11075b4861ec78b9c2ed8994e2921597a07a488468ce35fa15ee4a5115a5197f62a66329a7aed8962d0e4fca1e4e3e7f92286fb70cc29f96fe414f43975e9f1379323f2cdabd545c5e6b6391afd1f2b30ea39c5affb62ee9f89ef15277f7dfa2ad35921f0b3d2b7b87df6922f8dc7c4524ed7bf955a554bd5ab27b7e761078daed1dc56bff2b50643c44ee6a54dfb705469fcc90078438e330365e89996268fa47d9792841c7b09f9df2ec14074af02cb26b708c85943cd94364167e06231e4ba6c31e46213787ccb67e6fe47cc5e3b6bdae95d6741660898ca966bfdc8b434b673385ee702dc321c93e04563cf5cf29e95e190411f7f1519b0a3ce6339759d0753b56028f702dd83be8c7db0893ab19dd503967060607b7425838271edb2cd2dc74dc530760483186771ffd0c9bec7bb086c4f420bed030b5424c1f781f3f9582156f54cd54a8eea0371bf2a39fbb647a9be623314c706390d2f1cf636b883c543c08ac7ba16aa77ad9a4667b93cbd15769a0cea2c7e92d600cce287f8b73c11497ae1f04b54a482e9a7573cf022c468a2a8b5b02a714cff14ac4c7bbf127c0fcfd47f23f367226806faaf7682b73b178e13429154edb5e544c88fe63f4305ff4baf5e7b95d0e53c4885053be32053ae00db3f4f052d4a30b05486a1dba7c1ae2aab3bf215078dc9a632442f09d8b08e9b7f5106eb72db85f72d7c493624505abd09c32e6cd49689cd0273510ff736057bb1d80471be62c7fc1507c11ffe57ee1147eb8d3e2cc2dfb8a3b6006d9fdf8bf67270535d3ea01fbc055c7c3640ffdce3a9cf878b0b057517d7fa73ce7f752a731676979663b6321f93e6c32cb615ef3c3b2892e16a83b1dc250f8902d28ff9163647a1b2531d843be84368ddd9ae942aa1e1a912462172cca85e91c173703a90ac2d0a65266a1828639a47313bc239eabe79412b44e96743a004299cc418c7a733d5c1d5940475d0898c96274632f5811cdcefdf03ec5b1b852b89a350eee2b0eba0ba749e872bcee48b8991781ed4cc5cdc4c5c7942ff8429f3cb818307b3e9e359cd3dadcc8dbcb3e0de0dc05b0bb814d065ceda06f2d07149510131107202279bdc7e3bd2677d51211f5865da3c003ba4948e5160db9404d879ef3d19f6b420c1613f10a2dd068d0424b18b7dcc36535aebf215aba4ec8246fb4c833dce17c83a06d4a1d00960bb1c51af5d1c57e5fe237a8d665197b2e42c956f8a1b262871ca2e791d1a93918ac3450284f2b143197a36e303a9bf624a6d445384457a4cd8bcbecc86e83af04a31bc05723f1e6cc7728360f2905e2586fe02952dc6489e33859abb869df56b6d6a46ed518a7bc9d221e58ee918444c0168e74ba8ea96415cd1390a56837a2b41a6a975af0c830bfca70a4f25c644a5c83016fc3b945f40b67c40d4328fba9aa4e6b26acfb27bc3c9ebcd8efa820d133cfb260101ea5b83ae634200bec90366eab172bb63cf01838b26d2004aee5829c1fcc1269863bf9ac7836d1f74f9e9ec631a9513d2012752c610043966be300618ed5aaae82b05e2ab33c1e0297fec247169bce5b62ffac3614721f4818abd7943eb6e6064fe2eddaf09c2f31572e26ae93bb008b7ae3518346548de1db3676bacd119f3c2d2c9d35e3794bc0d95d788b785627f69534b48c82b073d39ec9621821f0221cfb998d3fd1329e0586fa1909553da42c83af706365a35db5cffaad4e830bb5c71f77ce9920a4c7029a406f6bc022fd1a3faf7784e6987b8927d4e5e382254cb38320fc2b8f2a7491b29a5f33eca1e84fc073936b6b53356a808934396c738ba00034b8786e6a8868512d614efe9b33c8682ae3e56efc4832a1b4cdc178b0f865e3eba0e0721ce80209ee56f076ea7b622c39981a0243e5cd7e378d8832637f13891a76ab7fe9d8a5774e5bae530dc96bb60eb81ca340177e51a562a3657b6d51286eaf050dffa9845889ec3a9bed78fae9c0dca40e52b31e04cd752aa97c6779f0990afc40e679fa3d10ede239c3203bf17464b0366172fcc7647c72ac7097f79a822194c0e52fec351e9664096ab892be59ef697786a5d9e1d91c768037d13b4a289767bd53f241c499a13f13baed4b7a09f44ae5b8f2d4c74f1e259f9fcfb987e33772e1f0424e9d0470486df547e10617e8a08f1fdc641a83eb4aedbff2a2f9f4aadf9f6d1503719b862958c42b4f1218674b47929b136aaf84cff9de88cd10954e2ef9481715f941bc38a8db262635971e359a3b1acdd6856d148d63696151bcdaa1bc39a8d67e546b0cac659531b07b152091549c06cac0213b78e07aef383d4f5e0753e409d1e5cd783d6f9c07572d0ba1fbc0e7cc05be707af7bf8ecd479e30e1231514c69cada36dbfd902c8f19a6143d8bea66a014fd10a01debdd63e222e0f2b33930c137ba7be763440d00c7b83d756c89cc02a6060450c696804245d50ea43794cf10b03db03f21ea3c7a1b122e464cadf9ff1400fdbcdf1c9e4f7f043b4da142a898c56af9454af79d857782f7636e58b4c8b5d41536c325002a5be2f4f864bfccf4ca293b152b5c1a72cc1344e64996d167ff1cd4d774134e2176a01ea4f9f678f3e5ccbd83e3343782d9f1c87dd193828aca06ad874e712f65d9629f929ca9d8fbb16ef6b4d4cf2b12e84e91ba20281cc94adfe7db2bbd21509e6e482e004853fbe03f5a0e422173e95133eaca3d979fa0336f0477d478651a9a9011f48aa8b053e1a00a1cb6cade011b03225cee9cc0227675e31d3e16e0fa102c2568f577d0a1613cc4643c24d0417a7e0b52f1d038a361f0722c10271326eebcde6236144bd1f5518c3dea46eedc3869e90942de91ca9bc2ed7ff2428c6e79380a3b1abfeae5dc9a358887f90cb10188e164e4148d8b97594a01b85f6815e2a483f19577335e4d91bf6f784e9e0ed0d97dad7bcd7f0ea53afb6be6717f8e79b6e86161245a8be2811a339dfcac83db3d8e4720baff9e0d5936b79c75713ce2c68b5ef389cfe8fd9d683dc112fbfeb2fa5415c74e265e4e9fee8663093eb4b48bd4f681800a6d6191d17351c52af7ffc8d2164001a9c4b20d9be4781ccfcb0a8ef2a54725d3b9e00568bc4a818cd38dd5005abf2ddc3ea2b79378aa0f48424ad9e0e7a09c8064d2a20c111afa95d31ba7113aaccc9067e6c8549d640dd307d79c53727e6e1ba9b854a3204901ec76db04eee47e17540c092c4eb500ad60e228b8a0c6d5c8c0ee9b59526b2128e1c33253624181c06b51f0a09fad74efdba1c2121eea0239cc40e9130028bedc45ec61d975ec288fd0e7d2eb5af9274a0d6f91227d9b1ccab9d0154845b639b524ee5b5a91b2eaedfba29c82af83a616b9d31e6d8efc9dfa4d2513e8ded4da8b17248e2e08687ba6189f3a96ad847a26de7f3659b89c77fb972868e1fb547e080edb9c42edbbec1f422f7d46eab45d1ab5713b516ad95a22875906ee8c2857806c4ee33823fefcd92df0cb8658ebd950de6b58be2a1cf4fe9aa827f06622307924c1bb4b1b3b59e7b8d5ddfec7322387820884cfd860174813567db4bd4027a91a49a7801504782f37806426f0e19f74a4f59c2004627c1df0ee635b2fa6b20f3a82dd761ebad1272e58cb1d82f7965c320a0306b3d767ece37077d5cbba293078d32f357f0eb0b78ed9cdcd921b5346ed56fbc43e18918e8c772875ac17d3b30658a5c90c0621b22d2e4ae82b8648c146bcf836673f5eea9ba5896051416d84268f7c3a5f941d615f3107d763a26d914c1b6f04e59fd8b3e88967ad9f94fd6f455cb8b571ef473f5ac9a1e44b760e4f1ff15ad2ade6ee0104a53a98af813119d4aac13624fd6a466195bf6505f6941f25d7730bcc2e08bd0c15343665c2fb2fbcefedeb848994b6b0a927819873583a27c61a18cf353aadd06d2426825469a1b4c5fa3851e66d82aaa6783efd75b88738e2ca7156e209f8d75355572d5a65dcc617122f112493ed94f263033ca4e908ae38046c3d8f2bcce0ff1ae2c1b26db62f4db4e2dfe08cfde841dfd2ded1a96b6c15ba2ddd00ec5f49d6ae60496bb8b36806469ed07a2391d2d692bf3bafe08aaa1b2a5053eddd499dcdc9b323ce93997db5123135a8bde95285883f3e3d0c5ed3856a52fb79ef0cdd06914d52fc9c1c00bfc16a9e9248b2ee7f85ef7a1c1775a02010043139f828239b722178474a97876c431302dd573bc85f357eedbe6d71a89c2d07ff155c62688582d18cd591e351b3faa04b17b142f16ade4a93bc6c1f01c2b542baa8d92b59e11a88d34d0b6f47a7665e9174f7bce57e62ef90f7c939f2a87266f01b400788defaffd5aa46631ef391bcfa234ed8179a9a4b8a74d19bfbfb87a122abee14fc047e65700e2a3c0c3c3185edcbe20c65318278bc1b90824ab1de9a32e1f805afb2c00c0a156c5fed2dc98601534e145903518c896071d02e65a9f7e9453e39d1914a78d11dc2ff8390272e0225a32f75dfc239f938ca5ca29e568e99ab18a301071436ed07f672e29ea893fc1aa78368964a99b69a4820fa1ecd2b5e983c4b1685dce83d01b301c3a146ba672a763dcaacc96bc7be4ee0d54dbb35bb0c4a23f5be655dbc8e61ab15f9cce82e750673df208d78ac87fe3a149802951309f5e7b247ac1eae45d263e8905bba61a51ab771d43c2d23ffc00bb0a4fe9a8b39083b31dae12da68c408b61a00328687fa847cd8afb8eacd465fbf054acb2c674c2c244ad50b4af5afc9c1f666eb836545297a7060bfa7da8bb58300e76e4d31e5224361e4b5e45ea3b3ea172586f46a95901462ee15ba77bbcaa867aebb39b656523f6ddeaa9b6a9dcff8c12433380fa617996c1774ee499986c7e6b141e37f1d095f2e102cbdc4b06b655d5e03de7990250d3f478879ed20eaf74d9285be921c1f3472befd87120eb9a968e5c6c3bb772ec329a369376795351a89aa0d294f16eebf4bb50418448a7156dc385a5d5f5f8abdbea74f13ab2681ad49d4a7bb4d4d406c0f2fc3f0993b498cefd1991b1f75c0858f96c2e49d0124aa0e2ddbfd5b0c5995db19e23d02a222db6978cd045304c266c623fb5e4311df671f70dbacc03fa16ba58007701f6374fce0ca1d8772065779e287442a30ee2bad4e088c503d85d2a4e71d2d666d170dfeaa851a38188c399344ff04fc168f902e9bb9237e159d3b48b5c5f609f147826967f05526b550f06d5c2cdb200126a350ea6b54c84bb185876399602e85fe9a3124f04dedc454e2e8ffdac819505c6d02ac0949ef4f739ddc36733a6f7520846d4dd4aa8e618977a2343b2253d0f7e617bddc79f12b0404b6f6ff839bfcb00498ba49afc0b3f7c4001da0e7556418c0bb4d5cb3771c88573a6a4c8457abfeccec09215eea152c4831c1a024f20897660fad3ea0d2f678e9dd897f73fe8ce398babf8a87a00650a148de1e07dd59ca1433c15ce95cc18e65dd55a26f0776e51225ab8584abf80752f44ee6e67c046a10e37360e196fede0fe1038b5d2b6d1243d8a5a6a5b8168deeb0204172cd553ff26861d5187dd0b1848a192f7efc1e73453ba348e7f788f76449696fba56f674bf9d59818f58251325a4c5f10aa2812544c2fc806a14b370b9c446abf86adc2c98dd83ece888a78a8ec7407895cdd33d8cea1b9cae92e7f9f4e8fec81232064bb7ad8c37f6258d6dfd0a760ea4f4a695d734fb841b760789b1ad3c8e7934e5b36cca5bdd53dfacd927a3abad36fd7122722d0c614e4d282b711281a1ea444275157265c6b7714f6276876c5d34c8ff3ee5243f800eef3a1132cd46067b39caffaea48409536fdf4b09acc148472185d6ce77904be2a7f25e24f500527b43beb0f0efa1a4bfacc3c92bfc63a8b3f9c06759b2fca2297b65984dfd4ea47c62db8a08395a99193686375e2f4ab81257828e1069e95f4fa12261ae6dfe55896bf1c4321eb401a6945c98e234b312fddbc527e367ec353fa9facefaa4af46d8d4eb4376dcb97dbb74f90efdac7656938555eb49b47ee78eb81456501ca11d6df07c29f19acf71c75d9868d3f6a346642aee4d1701963eefec5ca446370d11d7eccdc098220779e359418cde4527a0115c1b0b9d3d22800da87bca60184d04a21ca40015b01a21813115420377200e3284805600befbbf0506e61c9408ecac7873d75c36d222463fed21a0935694e159c31293b6adac5e28375b6e8d8f8ddfdd317467251da698b8429c019584e7e1e3c7b62b590d1ef9396658ea5ce3080697b2d0352da18366454938e8e7b6967489d75e70628bf7e02cc134956693a4e575156549ba2d2607108bb34fe78186fe4723941d11132a799e7b6a6e3042dbd6070a80ee9d048e7ddaf19ac4bccc37984909e4801772b7cd1491def5bbbe905e4424bd8e6bcc19a2d7e7cedb793d75efdf173b0b17ab4944fd7cd7143fb786b67a3d1cf298177c0cb21e82c95fd0ed039786c5240eb9f2b504e5262eac612f9872fd781eab41004a72efe87ea9dd45fdc17085569afdae8fa4fdd1cecb5e8685b544831171aa89cdfe478ecf517f6e464fde1987cb019fa3ef4e83f6a9909f7f879723085aea16a374a2f0ffac47c5980711b12f8676fea2e752ae33252f626df3419e7e358a62054335d447329bc43860836af0f8b7154d2c5dc0ad05d2af46092d0959208cdb6d5bd24554fb94bc2a9dc4960d70bdd673d6e03d6d1d4964735fec078158822b7cfe44e99ff5e8249854b11a047c18971a49cce6b061069b02de621dd49060e9d8ef2749dfbc1a3d10ae775dc7d9ace45612950fff995d29b26de8c6bf1196d4bb52e9126201d4878155131f45d15c2d58207387d6fa28ab1772a5aed866f4bb46bc1f16894589191d423379b27b37a5ab4f389a49a72f728264c92eefd8c04afa85e8d1ceca4eebd7a1664b48ba68ae99cf01ad4b861cc533b83bf6d4d679bf2e113030fb2be0079832fcd115a8324bd1a7fc6a9b45723520371f3595b21c95c067040465a69e7387bf96265d710d6cdb4e089e35add86284b09d46b6f3a5d4f7c7aca86ec6913dac00b17b00975ad8b34c07a2deba83bd630542f280b22d768e9fcf7c2a4a3a8b86da35da3e3836a0b71281fb687de0e6abb54440b77b599e58f4eb163433985b5543dc9dd784d428a667a5787a3f0a11c931011e137c607dc3fa9f04ad95f6279ba63957cd576add37bb27cc52c0c127e25f7ad67ea4db2bd43fa52ac3582e159d726c0d432201ddee8e36eee44a53ffac797a95d683a9b42488f2c1896a773af7aed21c287b678045a99f16f724b3dcbbed1ea075726b40069a31a8bccf178e12cdb11dc3aa47f494ec00015095e3b0c586b79c46f0ad7ce1dc362f518d8ec4c628270417c565b6d3f996acb7fbfad6f81739681280f7d3f2e1ab93fad83c74e070dc4e099c6db654e9a0f7811ffbac96e853990481c833092b788cc624b0ecc0557a529b771573f664216a4462a5af7e7fe9f904ea42112e75f4fbab958a2439ae0b624141d1eb5a4e5cee7a38934fc4b9483c8a1adbe2bb51933a21adc42b1b0245cb42825fb5c6000574f7d0f86d41695d05aec3f6308fbd3b3de47f17e23446f2914cb83c462437a373c8522be3422a96532bb254e3fb80dc74cfa5f5af7075db85e8868f85b72e5a9c114e8bcc5af3d9680992312a0cb90fef5e29c0faa58fc72c973c56e8e24169171399cee3359587e7ca71a225d0932131587174f4419ad995d433e24abb8b156eeb38de9515448ca6a820aa9beca337b53cd666b6503e5b01b08925dae3d91ed9f878895ec6097885c2e795acb11913d93a80ad2ac4e2fa2a3ca209197db4887a4909664feddd79f97454ed22c4a02d9ce0181c4d466acb828b794773819e80f5837f6fb957202ab6410559a32a97643e40b927981b7578af8f4799f19178353ed0a3b41067743e46ce76ce3befb6bee86c7d80f32595c76e7ea33d4ec87d8b19c6fa9b1a2d3f69858db9490e2400ccb71d7fa66cff7c1e669711abb1bdfde74c14fac9d04a126caf9bb5ab5a84110d5ae16007d46b15fcdb46c0046a1cd50048dbed5effe502b5858fce71fb8d55686e6e59ae21f0477f687e6f33a4df7e6805a1e4176049bc7f297544ea8aa4915b5a6730e8b2c3b2d200ff0b2054baa6d26cb46cf87ddae15aaab8cadd5c1e2d835adc7c0fcd81e42a6a72698d10d889395e4d6deba38a0bc77ff9442122033e0e247bfdb225b825f2e1891dc57b958e57a83b61083da7d1829d235e5983cdb253ae86b32bc4998c9b00a664e471163d80cd352c8066528ff34c1943a4a07322bc8176b3a9946988d3f494fe5769b9d97ebb447a4b5f60ca80073ddb6e324b8fc73c959131f1b5823bfbb85c27c4ec7a25e4f846b7e09afc532386c77ef36a5ec108e9a999c0c768acb0b653be3451f989dc2b83e0dff812bb86fcdd4050be19bf70b5ea9553e10b134f642f1b6ef2fc1ef0aae8dd16782ccec29588fb37b4fcc3c58b6ca06a948dcd88198ded0ad67cb45410e06554f9856e51880f1911bf3abe0c4c27663c7f8480754587990f23e01ff25920c8996177d652a2b8900a8c28d61f7ec89caadbd843d1ba7792b878a7dd119ecf37845ec9267988c0f3947af2ff6e118355ec3aa62bf6878c5bb41015b3491e361c151fe2bd4ba6162b93872f4cbacc439507509a418f1fd05a9aa2e77d5fecab00554874638061349471f2a0b9bf4058dadf5fdc8aa236e62641a90c2e5ee315c377f9f52d83811a97721bda5ea6a326d397cdbd17be3a81e4371ab8e59dcadbbc9ac94da1c50e4b605d5431338ce34ed9aefe851cf15f8e029c702dc1ba355f5deb0068dba91eb2c3054b42ded2ac2ba15bdb3baa42259ee1cc50844f020022deb1c72c3e7233dae06c19eb4dc0623c3b9fc9b05e6c4cb1f4cdaa33bfd033fd46904275c5bb873064f6cee7a422bc8569699f39adb506341c6f0fee328f35d9a966e9320e4ecdecb3045ad2ae246714b103951e55031a610bd89be241bd8921cec4307d29da4e6e03e89087a28d7af8a104ec1c0795db59ed657c459ad73a005ef9759977a213b95ccf20959895148d969c988825a75e0da30e990573797d1a16adbc6372ba1ea149dd10078a1cf817db91b32639e9b7e4e0d3d8080818c1dc44ac37572aa5308ebbbb9f804c85136290c71bbb7c17123bd4e8c8922294f4a664ab15241ca9dbc1fb0e06850c7d4208f48d1b3b3baef8bc2a6cab91ee60a7a8e9424968532d1af40e6c872afcc7ab75fc08e27784f13dd4f729502566a138a723137cf026ccf275cbd4c05bd7f5ec14e24ea7fc0679e5acaa1d7b01a783325b9b1c5cdc12e30045a1c4a8b5328376b252d93c700544bf93c224d4a29661e074e05b80c997ccc3f869b27287a575ff300205abf720dda05442179b15cc62f4c1e7099b664b095743ac5d4e0953298ff7526950c1a3c728750fb93a77abf94e66defd6d214ae0bdb7a0fd7085bfb850bee5407f3c9dc1a973cd8a0607edc5e042939c59c49009bd84e2e5886427c7b26864e1e51e53b7a2be111be3855370a067c22f7a65af10c5c7307db899944755676ea0fc52a3511c8c302bf6c2342369bef5b265b11fec35b850233673cd70a0491b91a777a3ef3d634db61532158695964da4958c9b01b66e08b0dd44eb26c744535939c9865445c80b2f6421efe5d75299afd016c8bc60aa1783bfc0a356ac17477b2a85da4622d3cbbd496feb5089a7ec7b81db171f97277dc24ba4fd3fc464d603c1f49ec032d08e742accd7542671d5914c163af8208d1aabc614d264623ee8ca4c44359921a77f9ba92928d607815b8c6461f983db426d1d0616b5b333ae1dcd392977ecf181d5258d5550d4b6b39bfdf14596b19bb7b6d77283fcae385eb5f1e2363cd66987f2774e6a3daa64230328d1a8ad6a8accc6095110dc05d4a52b4cf7d8d6aa7e849c51ba043e0a823f57295f66caf769555c3e24df49850f388912941b94648012d5be687e576d3f5bb586c56654f748b48b26195e3f52f18088fb0beb82affb87d7aa0df53fa49ca086edaa16b4614b543d3d6652bfec05f654725d0b9124c03d278efbee4e6ecb80875e511a58ecdfd7128ce61744963c6e8f07ac783cea320dadf749066dbcc391fac9a9bdb651ff26838c9747b7cd4876ee717610ee7865ffb4f6cc853bbb5f6db5fc7f0b89d4af15500f2db06518de6939a10af925da60cd3b235f128bd1aab5e070d1b7643cc7e2b885be673d3c39ef359c6bac70c3104a3b5f03f3121537c78fd7a8aa6a4cb3fa77af704301b0c87f2fa5224f87711772e62e2e9747e52b7edbed1d8849e03facdde91592090ba9216032cb8aa12f76621d24bcbe42f40247e91562ede15ca60e0e73cf36e34eeec7d0b972db486382914e00c06b540a96a4a1a15754213f8e9aeafbf78ae17fca807ffbad584ce691ddd116db74608a126b4b2bb7b07280925093009878f64c89faf29e986c70870a36f874d293c75f83515bdc9e1e9000e7f0570780e85c363130e2fc4e4f0480270f82543874761c9e189821c8270d8a1cf811c5e861f87aff201d209494a9492f0806489c861cf7d1c7af097971e58f038fcfce8613db487df083f720880f31c3ea6913f5fbe3efc76aee3d0bbcee12597095485c69c1c5ecbd20978890471e4a60568a5c75a784e6e5280bec94d50b4d2634f7e23372140b372d301b4d2632a7c959b0aa0b9dc64a4951e23facd4d457a2be0262ce040501e04010f3ac08914e0468c7e24173d96b50d4977412731403bb9f4177b082edc85ddc48501e80fc8052231fd0de0db85105cb811222e0429e0420b88b5a0939c72dc8fc845d82100fdedc75010daa1a9e0a024a7adbf1d990206a03f146e08f73bbef7d69f0917da424f7242417fae8b1082fe727e11427892d30efded0bc004fd091d85c75c3872895c2341416e420b408f692a5c0b26c472dc480b381ed482b621c9e94f720aa23f13de447fb10fe024402181059d9400edd4d2dffd072c18394c53b92c10799213131602a03f120ec402d01116989c080b1f0480c883b0a0bf20076ae1421040c2139de484a33fd881682af73bf2c308d049582204c87fe0273f9ee404a43f58d6c2d07f682a9789fe7edc25b0030101e92fe84234150e48c8939c86f4d7fa124d05ffd0df8d7fa0a9e01f1f007d48c8979c04168280e80ff600682af7c87d0284c89320fa0b3a094f8290f0242798fe700ee4c9cd8d3c810179921311fd053913fd91f0007cc80186aca0930ca09db0feeec7e33e3495bbc29127399db082fe863c4853e1562012e463051efae37e64051e4a8e9cc80afa23f2a027ff29c010157492938dfe4ef85ed74712fd057d08921f1f9a0a7ee9cfc77b682af8f524a720fd21c99ef4d0542ea7bf1e370992070505fd04fde027e9a1bfd579f4e0f1242724fa0b7a929f23f990158c9c707d5c252a10d19f910f5181c89027399da03f9b2b51c187fe587fa9e0e3f524a723fa23f213f437e44a5e82510929e8240268274d7ff73a9acadd4d295c9e148ca460444b21058d3ba2a96429e8efc88da8f022994ab69b8a4a20d29fbe0e4de5f2e828c11629c2a3a960ad3f9eebd054b036a2bf9aa97075ae692ad7d61bd1543823468a1829f224271d3afabbb73a749ee454f567e4b5c8ed4bd054b2dd9402bd3c97a3c21dd11f7d099a0a77a4842739a9f4a73a47c4a33fee5a53c1db8624271efd24a7a9bf239ffa2be1f442b3c90004800293000c2d394149122426e4111ecb247c67a00b65213f9247780999841bc9402f92859c481ee14332095af2205ff0411948c3ff64215a02215ff041f2085a2ac917bc0879e72164efb00c0290ec13634efe23effc83ecdd47ee79650f64ccc97be49df3c8de79320074ce5566cc894e72ba863bb47422efe826a2eb648f4877398ebcdd9553cfc9d97172f7d46fe48c95b7e39cfa2a6757e50ecba6a5969466cab4dc225fd066bb021ab648b36bce48a0c4022936cd330b0d5034d833db1d408006061269861b2b0390fa059b1095741312923f14683bec0bbab7d6c3251f30952f68067fd08c9671eade9b5f73f6744d3f037fd04c14970b6321f9032bb53686242fc8577dde00a65f50845d1152cceaba6378010afc052dc52520848290148989813c3546f8bdb0a504a955fe1092af0d52f943deec801ffb87760ff5845b9b457bcacc9eca1e94e5a0fa2053b21d81b9a98439828881bfd4a68f6bb6fc96af6ce71e10d1ec18e26e9ac0b65dca6ddb9036ddb659bfd42715e035e814358928d6180a5f767d9124b33f986606f8bb512aa828a5690618459f484a67a500d296b3d62921851b9452eb5c00471d0586cfb2419ecf428966d494d97996edc44bc9a4e4de235fe9710f7a1ceeec42d2831ee7b81e3d1ef34e8f2cdbe9d123fb0ef723eea8c175e7316731c6b8f3e57ce736eeec1c764a72ce390046edcf4a972d25e731ea2931c61863ddf9dc3bfaab42b9d89d9093f3c5ed8212235f59b7f32c6f3b99ae54c897dca94b70c8ce65a6eb17f9c2227f2a14cf79e83800bcebe438941cbeea3bba3e411f8946be7ee0b2736c789e9d9d9d9d9d9d9dd73452e0fdd5331b03816f7cdffa3bfeb9f60ede3bf9dfdd3bfad3baaa4103f757d15435f2e78cfc992ad04e908a334e789eebb8771c5dec7b2ef00936fa3b9975a83594af9defe84a867cc119a94a3fc121fa3baf3c348ffe5c00d03af487f74a477f7773ab1c38f42757aae3ad755dca75eb82bf8894f445fe7c53693fc91ff8e40509b67c85923fdff2144afee0b83c15237f762e4fa326508fcb53a50924cbdcdcb8f987f78d9b9bdfc854e97a87fe5cad7f5309c7639764e7b0a34ad04747850257285d01b0843f59e6c975f91e19caec3b5996c1b98efe2ece3dfd619ce728f345de8bb1cbf5bf5e30d817b423ec7068fde5dcba32dc391fc5c91fbd915a608837b2120c9407d7a78f93576518625797b8fdd5a5a56f9a994a53c96acda30bfea81255923f4b6ab63c851b045ac86c18bb376960798bc42375aec02f36ba2ecd6a96d5d71398db10ca026178d8c00221d8df6f012b78883ca899b7c0123d7c07353c3019ac70c8384f63e424a464245314d04d0c7e8b2d180c16020c0683c160416021c0603098bc71ef4ddd7befbd5ca4f04e209b9818f8a548908bb1fcf9512395d3470a31126675030c03fd91baf7a6eebdf75eeea6eebdf7b2388ec25b44ead01d1e3e71fe7407f2f011a2037de25d96c3ab584b1583e7689c18771004304c1783d3255f31062790168313281583f7d4c01d776f90345832cd73624ccdb4ffba344e182c082c04181c026130d85dd96b56579140aceaa81d63d448afb592dea803d35257e75e1daf6b5ecccad5f8aaf47256522ee3a0bea01fb0204499e783abf38ce7d2ecf5c5f3ea51af65b97368b7f66a2909fba0c72bfbd537bbbeb4529adddfab8704cdd4eba5c15ef5be226dddea7a65daccd125a95aa5b5d2d77dbd347d656e9787069db2fe7eb384d43f1d7c09947d2645ed6618a31986d938a29860a8992a023c85fd9285a065032d6e9cc0dfcd1004db4b93e1533ed205914bc041261ba1b9c88aa399881accd13c643765d764d37da6f458762d27ddf058a6951ebbba6a164733097bc7061996c803f4d1b265cb1c76803ef2ad2a7006caf121ff08c1e1c6bc82ec1c8ee61176d0c01fcd22d01c420e8e66187ef2389a4168858066203b8dcf179b067f540747f30f56991d1ccd1f442e5aced1ece30648981182b504d30d24da28022bd910a1b45a228a85c40d2a2598f0129d1250ab2d565880e34246b505aa025b5354b7050515d09ad0705bc4e021c504d4b685064b978bb6a58d1e889a404a82071fca1041c46529892474205ac24c1250e84094849a24ae50524a02044a444c984922cc0f444d5c72d0783f2c1de3964b2703d2019e293c2df98a3aa49981926ad75a5b2e18b561176994f100e9062f2c69d6e841039cddd0a305b8ab800722c02c0cf808036b4df4e0a00a2270b621183442e0031a5bd88059649090907e40a38c1eec5cdab08b36c6ecd71768a4410234d668c9c10e5128a17936c4062184104208218410420821841042324284f1a201df15f830836dc07c8044163d5650851558db100a8926a640c28331f0dd100a09227ac4e06c43282480f0629352ca29a58c5766526ee84329fda69c73d25aa994724a29ad94126fb9240a4a4965a694ca2ba794320535958b0a0c2184104269ed9c41a6a5f605a370a296ffece79cf39bb79452fa694b27a573ce7fd99cb37ed63ae7dc2a47d3e06c2aa7def47b0d01ebfee69c733e421ff8cd39a79caf5965a5130758d21aebbdf495c4a5524e6ae9cd6c5d73e9a51ad2bbe6d24b335bcfd09bd9bac6d6ec525bb333d5da9aada1d7d635975e7ae60cbd6b2ebd34b3f50cbd99ad6b6e5cc9ae94935abc532f6c5a6d16630d5b461abbd45a6d4699905e88823489b42012e88b16a01fb01e286f8f84e5095165d2c2c46491b0543da318242c99ec329753a9a65f28e87649ee6f778284d9ddeea2180245a6881441905ad3bd1913d2182631910b8d484abc540ba1cfbc4d9a8b71b4914b47648184d2768408761602117e80f6c7ed6a64d7236e30220c1641881140ec29718c1016f4579669b892704a08e12319f8c988a495b5d6c2664f7cace2880d5ce18123a8382266add2b6d65a6b6d50d06b7f2da62c76e02f36e5590113103d6bf0878384d9456c20db6a8004f3bb94527a420c3050031a41d040882e4a88c97410812f6c43c51a5bdbf2fc8837ec2f4811f8655b11ec0f8eb19ab59dfd6177aca5d6c62d2dd8918a6db394d9568b6d7fb1eb2f180cb67d90d01222226eb0ed9f403962db4779c1896d2f0306ae40d262859219db42282268c0a4650c9722b874b9b22d84ca0017f6622f62b6b50fb2d65a6b7b64e1c0bd9d076012a679f1210363260566071ba6cbf562881a0629c352460e148bc572831fe6089e6a96a40bc826504f7488206213305db238819b8c008d7429fd8075288049d8186a18e1011835baa8a106163d30c0aa0dc1a8d1850f23f0b6211835c078f980ef8660d4308303141217c8d9100c1b5b94b0fd6bc5bb6950a2440d0b54755cc76198109109a2cc182595627e3b929b3eca5e77b6b4cdf96d9bd56e75d65a6bfdab6e72665de04fa7428922034483f47a990934ed0f50505faa8e1ff0847042a8a5c45077f698c2b4f6846b179a98ce16a4b2473e35c67ce268ac8184a54b9d99cd7296d15c245f2ca4745c8a4af0009882aa81e16397c472306315a0062ffa090cb56f179217f38040ad0c7fec06c47d7e3e19c357e5bed12f587be27927962e775838252ff504f89387eae2cb9697f67572437461c134a286e002239a2070902a03268d96c833bb21b9ab974a5d0e490b16c102a1845862df6cb92447d8f2d5a60ea14fe5eeb32cd24aebebddb093ef5e9044c70f986e4be40fd556432d44f312263045a26b75419f3a035c796409f3f5d556faeb512d1443227bea5363e827b00b0ef194c46d2b19ae0e6838d82e1b0eb5d65a71b85d34e104c5e102693629a5843e74d2092f6574c176e50f84b0a0bf30b51267905286b1d18927d0937618a38f59529b669a3e92f747f38b503bc618e352f67b6917a3d0de69aa539a462c942e118d80116ac9eca187b14d6608a554c9cec84a4ce84911282fd4280c0485444949f7d99388b6db0c6348287dead2141b78098cc92529ef0b6212c6e6375967ada78f59c4e244c6a6ebb387c296b4826c7d1ae36a4d2a8416586ed8794eece78de636bfcd6f9e91fc7699394f49dc5edc72c3edc566f6e2535e6ccb5ebcd985a49682cd9eec3113ddec37cb32884642b1411e2fc2a889ea679452a289346c90e766cf3aedc5ec475ed41e91ea29c098eadd8faa38191d6557fdc8a9283beabaa77495edddab7057dd4a0cc660ec1ec6b6d82591303c8049dc148624f644a90bc978b3176b517ee23951a9b25ffb5de9a3ecde8bda48fbf58c52d9bbd76d4bcdab5c5a50c6068e422505c1a691524a29ad144a144a29a5dacca44501a5748b0d0689b4a1b673d8c01149860914af1ad883da73729f7ad17d4a577182af72e474a43a3db2bf87faa83ef5953e9a3b5be0d5aba84e6f57ba4ad59e13d5e9bd4d17a9b4b73dd1d7a752afaa2b94fad41ebc7609460536f8f0840e19f822765f349f7dce5bed44b347fb4b0d342640230c24ccd02296a9741558ff647595a96151474e45a9cf1fd17b2d2a0663993e4a69cf89fdbd077591fd7d51fdfc07d33ebb14622c3b9c324e95071f4351f6d4bbc77db3077fe4b4fd28f5ec474e9d2ea2bf3fca9efacc1ed41ea73df84d17ddd31f51ed412d03d2ecd17428616ff336ed39493dbb51ea993efa117dea463706b5a6ec4252bbd7525a5cb249d4ad84edda29942812804bb5ca5a291492b5d6babdd65a5fb3a7ea084d69eb6b985a19207f3eefcbf142945a6bbd078740b873c021f465e8195bd40287c82a1026f100a39658041c120f213c03a11816ece18bc0a86311d0876660533172cc025f458cb1650c228b0cb6bc05e08b7e0661c6968f715f1d97a00fb5400db67c84102ecdb02712d22c6385495a3a2595b27e61938254920542e3820637a8ae6c61c5997a450d2ee98cae0733d6e4d0829a43adb5561dd81c542c11a8ba484d81820c18d134031bd460072796a05e10517be0806a731b4205f1441a323d5833704825692b488091c314293893431850c82eaeb85c9c01020b986146cd813e08a61fa99b1d90b698316306f26c6e674ebeb07cb9cac03c32e6e00d4d087da2a69312d9aefc81504a2965bdf9fbb561841d84a2505c00cdea05a00b019248427290fcf9a07c8970f856ecf0ae7d781da1c3bf051e067c012f177e2800f6c307d97148857cc18770f822120eff04e8f02c08393c94270c48c0a144913f5f130b4f1d3621e0d961910f3924f2a0c320ff6152618b017e806f874d2b3c75f83515e0d9e157a484c34732f2e723a2e5927cc11b397cfc42fe7c418a1c3611bd0bb596912f49867cd517409f7a9d31f017a3caecfa28a64c7d952f1813c9c817c021364731d58ab59672d9edb35b2dc3feee69d7823e57c7a5ae49a6a5dcdb674357dbfbec6aef664deceff1afc6f28573fce14f71ed7b3b2538f8114b37021438fb1797602a95c588c56adae52e6ec121705b6db16427e4ec387f566f5b8ae3ba221ccfa1ab38d9c3fd45d9df1457a577ca989a65bc21fb794d4a8fe178130e3d0395426892147243adec35529ccd53cde56c9e69e4eb862ad51b7942654185c6d96dfbc23d51a8ce091597d516ccc4d26283e50610ada51b1e709472925c9ccd1509c71a39d4786974bcec50a1a3cb0e343f93cd6832005086e70b1e647a8cf120ea25a6078b0f9ca05c7e9cf1e16c9e6640d802630a418b085846e06ca64004e16c9e68e4ebe69b10cee6f9245f37f7d10dd93cfb06c4470f10204080bc80dcb46ed8701b10203e3e5aea0201726f06044896d90a8290114408418810988f8f8f90880195a6bfc9ad9c314b6391af8a5bae1db18c9005c3583d527d646aed2cbb37cb62a09bde52aca97c6dd9cd0f80adadfa223457f4aac8d55b6a7596c5b8713b460d73b4e02f46c52f7ef922ab8f59764fafbecf3ebba1d68e5f84dd907d2a73406b47fd4dd9574f69ed98e9f845fe7cb41b8a5fa895765a0a63a6fa64d0da3bfad1fd4531bbe6c8f214d8bef1cb06a486fa4a757d9623d1ce6e65c36e5edd85eae805880f187f3a9773bd76762ecb2df9aacf9104cefe71bb56224bb2f80241807b491623803e707f5b48de7b513856ca6eefe918357beaeb925e8a51f255731481618cd5323beacfb5a18f1af88b513baa4645b5a40757789c00771b42618145191f5c808a170a7085c203344d6460c134051630b0580a8389ef2d9376564a2b9d954a2979a49411afe6953f334a29a994f272369093569b5ff313c279b3219c509b32c29b26ab66361a279a29291a0821a438986dc468a6d2362a3d234633319aa9b48d4acfa06a6cb322118295ceee55ede479e6b287e59bbb2648d7b8eebd37621a35b95811850ffb834f104a0c0a9ca298d00006d03cc1c36cf292e68a2536231ac8ac5146cd0d67ac985cae5043857ca0a905300853060c282618b7032ce056adbde280167b75ba5a750087bdbaaa4bb282647457acb1bb735d920e7eb9820a1d5a3069a82b7088612079e1e588e8851559d02ff78c0d60993744696ddc0d285dc08b316b54c181304e40c5ec54625b0e6cb16d6a5b6badad628b12363de5a1034f154d6c7b2d151cb0d65a6be9611535dcab04864958e67401cac53e09319d18a14622c91eb8bf8ba948c1929323a27898c1131531d062861717164a2a8049188f1164a0870bae1b82d98193193b90c247961d6851811d98000736c2e031c271411457ccac59139b37aab7d393a79e8c41d86403c71c5faf18c5e002671b424df18507434031850a5c504a0821a432d628bb9871ce99d93a29a5349b76d639699c73427875d8a69c137eca0c3999e79748e78c311ecacb28c116e99c93b26084c1e61ad8a54c770a241ec86092228d1cc6589a820725319ea600a20230589ac2081ec6809ac2e90632a26a193268c0e4400d17186f08260d1df6771cd2c08018692cb1031ce016b918bb18a59c52c6edf5028128683e79f5f888446e76fd5dd3734a2112ba604bf92412612182208fef16391a6f2bc7c2b3a8c50d053b5c601965193c378c0c782106195e908f686478216628398a9297d4a7f6b844b36060cfc33875f001dfc727e910e2834f332249b821d004726965308d724e55d376d919c9d8762dc7a739b9cc1c9880f69cb71b45b24408004a48d769bba9018ea773de9c118b04c9d79c954a092f9c3d70aad9828a405953546ed5d4da2a9b3ec218dbb2d67cec7a043cb6ba2b48fe84005fa99ccaf52134c15be60440583369a780ba014e6d0825451a3e8690e28c1460ec6e432829a2aeb665f4a9d4a65d0c8120dc5259769ba57eb56b7a48ee2f6e6fbb67543f9f42d5d333aaa74fa16a7b6f964d2b76862967187b6b96e1cc663c5b4afc7876566bfd3785a7d6ecf6fe8b99f6ae1e82f2f54de1d936d6db5cab151ba70e5bab556374b58838216a2bf5dda44024983e9e9eba4610a3f6b25bed65ba08ce9e59290f4f4f4f7018f18293524a338dab9e93ca182319b576a952d62e55c71438be4a69350c744f3d05b6e52c82ca1b1b3480e1c3e0cb426b0d1c2fa7f0ec18e3149e3dcf13a714f16903f7e9be60eb42afb843b463522960e9b63293598aabf976d92929678c12069b3adff13cf2e7b6648fdc1c2e93590d60f9ec35f37cc600d77f98eeda35a148481084792a9b64b2ce6d730432ae7602d011bee6ab6c4277f6fbabb1968f99eeb867e4991ab73c805fd76d60082505d37e9a3fd58b33a4e8210db659e3c504c6673e4872c1fefec396367cd0805b5b6489c2cb52144cecef4544e103de36848a62a9e5a262a622a5f44a09e79c73ce39e79494d26cd23969add44e29e5945266f2be6094c6ef7b5dae9b35b0f8e037ac5f26ad36bba9596bb5f225ef8f311827ca7fb5555fabdef5663745b53937eebe6ed00666760c7233b17bea267bfaddd45aebeb9ba7347f5987ba5258bbd93247a98b7694d65af5a5db96c6255f50c5adba24f05072f3aa99bfc9755be0c8d9dca8ad16a594bb9956df0843d549c9cd0be16d3229dc340a21b4787230c0f1355575f637e7cce8cdaebdf207de4a7180a5a473ceade3b0d8b009f8e5527265e008e19cab2cd35524105b21b1c6524b2b0dd4d635965a5a69a0b6ae5943abad960c5bd7d0325450c69a32ce9401633c53c6cc4119136a4d171c37395a5bb5569bdda036752e417e7435501d2c9ab05bb2cbe2a214a4c496ff766cf81d48246e8920c821104875a82ec378263b60e7248ba57494d9338613714b4c8a4848a94deb5210c5002865b48debd2f2a203c5a170bb255b077b71af1712a15be7352d15bb244d97e7aa150eee82b9588129dcb0eebee068c375e1c2850b17c8850b9c40383155c76d5a54266f78c117632c9960192424a41b9aa0816b820ce73406eb092b5450a4e0c6179dc4b277ec8fdbdd6119bb3bedba64815b74832203504f8800a7f9b89db556e044164e423441868b04504d54e00699a14698113c098162424c1c038a1928818118bc98c0024b21934503496819620359d81c3c80a4451f3a5cd26c7afabb04984d8f97b0c1a63c6c8051118c0ecfa64d6b36e5e2b4e95d9fafcd8383314a9c804c53139b5eea005320586bd3245ab029941248b82e165603aee86a0527581981034427040e2f749cb1c9a085458e0dba2daea441a3b855dc242db57ad84a80a30dac04ce96ae0d17746d58e1eac1b526cbb6869a6e0d3170769093a56bc30cbb83141b2758ad41828b46b5668bba0136d4746bba986bd4c099a00d1b562658b3858574c5f502098acc0b1acae03594e8aee83ac688d41536c4d8aea051c65e69b9726c08e5010f4499800607f07737f528de7e105949795c3b50603829fe04d20e537fb2e93fd134edddbfdea5bdab6f2c51460aae6042aae2a4e9abedb38bb9632e45027be8efdd6c96dd669aa76a29b019b5411e3c81644c27661ef9caced26480716c18e80e627d09910bbf68d3de3dbd8c3ff29c5c6de419d9cf6b8fba48fb165347927b11eb9cb6124be922ed5d113ed71dffeaa26df39cb05e5fc47ad5de2dd2eeddec292df0b32cfbcd32d6b58d9b167792ce98bd5eead888754d7b29dd2435fe91bdda357d548fb5778db6b3b41f79f746dab973fa086bef6a8f88c5fa1136d2479c3ed2ced2de2d621deb2a4ef7ea22acd266cabbe7aef69cb058f89c3eda8eb7eddebd11fe9177b551f7edddb7145665a20c65f664d7f20bac55f62ecb328c6941b3278b58e42b68674b4c536a4d1be4d956abcbaf3a1b1b9bd56ae5c47b77239823078e635d25e7385ec57b8e5b8949d8c91f79efa0777ca39cab728e737ed4ddf3debdc87b77a98b72367c2bab17b5be7afceac6b1ae62f31bafd2bd752b31a9d25558c7afb2baca4a0c77c72fea8e2f8f6daed255726ef32adebb5b896115cc717c231c57e9a39ce3c8398e1f79cf7118935d124f7b91ebaa17e9b46ef491cd6f9ca5bbb7eea55e74f3d58bd4c70c7a4ca27dbcbf70cb6faae7dc087f471fe91c3f47e7f847aeab72b2c77de730e6658fd39e13d7736ea4f31d9de7e8fc4875d7a63d4d3b715d7503b8b4a7dd4aec06d0d19e86bde39cbbeed2559c8a5cf77415a873e81ced11b9726238349e3d32277b9a2b7b5afcedbf6718cb89da8b5bd7dd888573d63b9cb37e74e369ddcdbb1be1b08ef38e759c1fad7e93d24e6ebeba016eb497ba95d50d80a359bfd15560dd9c1e91cdbda2ee30c624de7eebb2973a8cd9642f05e4053d4ef51f8c2a923d267adb2cc53ebf90a1e46c639927ba70810e9e32ed31a1b7b75a4ad534c6b2a2c9951a2222e882572f3902cf675789dcf544f2e7a2205ff0dbf69b67fe22929ddbf6cd6e5151db3fd8e6dab66ddb52598bb0bd7d9e955d13c8357bb61c047decef9c73cb36752d9522ca02a77e9ba4e007a1b8772b9b1cb77803db1cdf86e3baaee362ace3ce3a8c6d94734d208eebba23d6b9c318c775b7d968c7499b89595fddc8c6e62b7de4347fb4ba11be8d66d91cff88f5d5eaac17adcefad445f8361792b24b01c66e646f7e35e75cadceba951b87bf916d329c3dacbca93a4e952f9e3d54c70af0fcf743bfbbc10e4ac2a8e9a106259a6a30c40e492d08030653982fa830636fb03a2d68c286304a6394400aab0318558c1b5610b5c54c0d586ae982dbc2cb952bb6060f2805f50086085090051a25184115b11578e0863ddf72cdc3a0b2508305582e13e6e2ea722faf27a8a079844e912ff839e18c9762ec72fd305850107d3a3229a5b4a5c33329a57386286f1adbd4b822f88b4bccc7bcbe809171e67e3a1066308319dc6aadb5d65a2d7de40e63174e7f3344cd3066b395da0f8aa182c6d7b64f368535b395521d635652334869b3c43441676064bcbef818971809effa8179a0b4caa0517391b09274c5a5a02818547c8a5d2297c804d1ecbab4e76193d8658628a6e8c40c63b643910f0a92d0ce9066cf25f055efc248544c970be309e565cf29e1ebc99e8f51863d9fb62836388120661d679bbc719f6bab609324ffa26cd5b56c73b86995feb46df3450922e7375025e0b259cff1b2332a809be7bce626dc59df62d62eb3bd0e13e09bef759800cfdb6b9afeac703902d31b8ad175d80920eaefe611a05b4a8edbfbcb62e521eddc6db04d86d0475bb1ec6a65594aa46c36e3d8617baa57aced3677dc37eedd6137b45dcbdf0c43da59f66a18ae24d25c71336c284aa4ac0eb7262db4812f6c7889a4bfbb39dd1d4b24782eaf20d02449ebddeaac434fc926a5dedac36f96bd4dc45ad4f4070314a41d03dd10b0f138373404ac705e8e81f5ce1b92c27dbbd552585f5d062dcb2d7f43aac3a8bf21d576a1b9cdb0a36c2599d35f448236f90171d35f44da753b772119f12a07cd9774c1107713d5211c426f1f5065775f6596a6b79a9eb03a2b4b4969ebc5572fde3ea5691886b6738f7a6875d6ed3f2b48dbea08d06df5d097b6d552bac32ee7782a47a4cdda3547a42de5e639cfa199b09e73d90ddd3ca789eada6dd6f2eab06372f39c9c9c7d31d055f68658bf39eb25c002c505991d332e764d6e76d4526c0452dde0ec7532de2a8abfc16e88fbea36774d589f8da68756d71a2b472fb3dee5cff5c9b0216641c882c75c406d94bbc9ac1bfd459b3f6ce1d6c9707299953f2b4838ee23d2bc4b37c9defde6f84ede6e471638daa7e2eebac76fac5fad752931d0ddfa77e3f0f6563f40eeb8d29fdcab6f5cfea06c2e8a2a43f1b27795cd0b5bbbbd97a1fc91e190b5ba71564b47206e9ae48b9bdbdc856412ee5df6ecf190ea9e3debabcf72df70b657e5d50ae7eeaa5577782e7fac7faa73d7b2c7d24338f7549e6a75f89caf3cd5f16d6eb377e3ddedb79c7c739cdc3ac769f9e639593bce8da6a9f4d0cd3d8ebbea387b9c1e627d75789ce377d95bbd3bf7cd266fc7695d95bfb8556f1d276fbfb1d19f95bd1d6e57e5ef861eb2b9b7fa567a8875acfad665acca71af6e337e97ed6d32ebb01b6261fd5959e91828d7e5edb06312c28e31c429735b1d01b9777400efba430b1c0fe7032c7c96e385663774b3e373e40e33738bdcfe5a0f889b76f837e32ea5303563fc1253293d3f9d9de97ba189e92facdcec4ed8ae6961da94ac54a7dd085d707c2a03c2f12c098eacc304584a03b27fc39de9b8458b7c55fa6d76f84d38eeb5c7b93d0ece715c38872e1d97628775131c1db1e4fc669c692109378e2cc9903d395a96e1e814570bfa581d976aa10ea894ec98a972182a44230000000000d315002018100c09450271300e26b2aae90e14000e7796406c5099cc634990c4300a82206300208600630820840064942aa20301946a70aa9e4126bc9c963159da9b7e6a1782514880c1f32f774369bf5052c700b13e82cb399fa52af277b9f0d0be18b0876bd0d1747de2522fd6b50b1a412694da9fa8f606ba690ea60c289d1859eb9757a711b4702dc182bd32a8bf5bd29f58d408607197163c02efa19be661da82597e4de90c15f52dd204b4dccbb155ba44abc55220889d5250d440e75fd6a27da565ef7305e2b31502c2c19f7d8e10781857526cbfaac8d603e3898e6577258a12a014bf51ec1e95ca69561c00a6ddc5a608be431d301860c8ed0e9787ed2406c2825bd58d10a623630f58985a1801676ad2893ee0aa3da594a1d5b4b63cb02e826708cd6cc5497ba94dd1aca2a9dd5dc3dd3ebba452329d7322174473a262d4a6f510c7818802106581ca7f830da512e18d059778e5591a15aaba37ea057c195b8ab37314dfd9e7993adb4ab593cd51fa3c57d402a5b839d5d9a2492cc4404247883acc4420601484f3c3e32509d7f4d2c15b8db31be32fde779d62291ad12f7c08e24d5c84601ac8688684c6819f30fa0b6392a7619a5db3e8078d97710c0c3f517aaa1510eb405bf551329e288bc2ff9056fa88ca11fb7dab724506fdd8fe764cae53dbd5447e6898da82e3324e87191b76b3fc09ad65d42f271fab29f0b484c439502f24c003fc647a0bcd03911591df42bd02406a38b8924d50c05a00f190840ea00711de30518268952e84556ec3413eb6469482923289a966e568a7dc7aeeefdf159baad3f4b8d5e50bde370db8674310eb77e59379d9a708bb8de482ce0fe90e0eb41e9f366bb18a15b55730320d591aada7180f4708bae3668f6e0678307549de09f455d8c0b6cf12b9aef5a36b5c8e461425e7cbc1261836083b22a328e9ccd70fe11aa5da44b57014ebf535d503372bfe085f122aa26f168e462e048cae005da3d801e6ecdc0fb443e894dad8b204ee41b52f491bf611459364f61598c5ed6f56646f12232159dd37a3f51e093754c77ff125fb62f8a9a21c86073ed636a8306528554bca2a25db1278d603e83e8a7aec0f352028a6792710d15705a2807129821033548d75ec52cbbf0c7ccb466dd056e40db05d28c9066c9710da27c56ab21b26747771cb5fd6ec1d4afd5824a310322a27c516079ad0322ba7f33d30d6d017b8c47e0c635085a7904eef619e2333c063995a1f648d4af8bfed8937aadd61cfc061820cffc6434bb763c34a9469f74464d9b11014c0d82b88f6c3275a18af3fa2b053eccbc81f34f059cf05e5fa10286f6b4eb64fc53d442c55ac87a72a648b153e835fb66d132b61ad1adae757066402db534052afde3751c88106a10cadcb854350d5eb2d0a31d335feffcc303e46f324740124ff816f40f4aa2d945578ddda388c22a4bde6fe2942a09285cfac04877883264e0de93c8f505097a28be9c0ff28e7fc4ac6c8de4b23292e46474a3888090fa88f0a431e962ca8fc4829dff3d90755f0dba504acb0ff256e4643d7c8a090b63591f5d81e4bfd6b04b0041be29c93317113bbcfbb55815384933a3291b2f08297ed41297e88123c8732329ddbfc545915e56a7550fe97e658ced39b28d4099f935b00b0f5e443b20b0a2cc3f68fb1b4e3f42936b35be4836c0877ab9486a0f5081f347c59d72f0940ec17b1deb8107098a7392149972c8dacab1655b33e7f99a6adb094374b432c0d9298a45e82d9906443b380d9063ac1eb9a80363b5814adcbca7232f47d5cc19b98fc726f8ae8f807244683456a59b43994f645e2bd28e0f4ad7926517f646f4998c70d332c7a56300922c1a5e3797e0729fb9d1f8aff46695713a835c773c1451123e07a2a04b04f943c88c9ee0b687c1d6aeef18e71e4a3338505bf8109201099ad46bbf7c2837dda08a6ab02508bf0b3d5a1ae930a8ef2c208a23340ed3ad630e46967a73cb17c45e148f5c1c9a9cf4065f9ec912ac3f9ee6d11830f61eef026e2365957a02f820415dafe6ae30738c188b31204a109a36d95b03b2215fab322183f36f5934ba754137d1b0ef3c4afa1758f90fde37819d434b84c74011bf0d6f5883406a5742946e507d3c0e5bf204f09860bd9d2f2ce7326f982be1383b0d8438443745c9eaf514c7ac95db1d521151327b6c8d50063ba8a5882fc7372186824a45a886b788000e2db420d801a31c8fa1d76bdf45f84ed82478a347920e89bf93e1e4fd84ae03e90aa43c32a7c4da37295fdb19269b69b50c0fd179dfbb1f403f33e297b3bb1df44318c470ba591bb154592d76eef54e5b91e54aca063f1505188fefac26c045bd6d53a82b32c5d48e6995950a0a5161dc71ae261c9772400637b1f6bd1d3a8c2c58516d98523e787c62e072c7f421e206552147ba2a8286f120fc768f8504ddd869b109e1929ce61a2c7315011398dc6b07cf5f9f80647806d0d56fc90fcbd6967940dee1e67a841c703847d26dfc27babc58244805092230346d4b6c81c9086625d49577f793a64fda86db5fabc144d5e47de5513b60eeccdee0159c5786939a7224193acaebdcdb915b3ee50f2c71720b3158c7fd940343018f99931b24577086451b9856323104308868701b0ea87945cba099cb6e789a1fe861cb7a2336cef279581d2e13c1b889714b908074e2028857e04be0ba074e77f7edfc0d4022476e3c2034199af049d206ea9da807521d3741495685d4d77ce4dea8ce1854cfa64592bbce8da72af921d075f4fae73a15beb57742f29773af7c84e298ce4496ff7ad524ad47b96d3d46057b4db5d3de2bdcfae76123ad6456fe4feff92161ec38e7d4374d572448732fdc518acc302e148ce9b98951f47d092036369e59f3264b60ee3423cdba8bd1c846425ba95cbeea80b7e3e296b0badd524bb8dc4152fc7fa8e9f95434e31c54367177a2960930e2259e490e9b3b03298787912c0303d3b43585bd119e2686ab6899081d3b6bfcf5bd4d23a10d760879f7d574848956e50e411c831b2e5e698828b9b6171471dbbcc1f9f03ab5c84d9d5f7760b13797d82e5755b71f7017e7d60118f914b1926e8ef11c59286b602ac20fadb597cd132fc4c40c4594d3b7acc7e809402db5066463e7e5d48c3d8bc678e8519807a0a8bce6ad127a1e24d452d4ca096b373390265b2954b8ccc8d4b974fc361668f8b47580d2ea00c879dc83e700c917592d8b91167bb59981a29817f69b9ee6a806ff97ca8c821180d09468c0106221275b05042773577f7eb8fef0f3fbf7d489b68645de51e027a1a9b1c9ccf2616f613e654ecc0082bc844467a52a42f2c00c1ca99479ca6fc297dc4a235140cf204ed843676cac14cc7e770000d6fdc65404f03142fc37d715e945950341ad1c755fbbbff3702e311cb9450f23aef20dcedde5e52e6f02e4bd2fbe7fb0c6882ae633e0626e0f7838fee265aba10dae4817d1eb425c0d77b1a57846c2cb6c15bc93833a7cc250ee94debe58db9a8c0a6d83a014bac2555eef64801b548eb46f7ba60d2993688d86fa79cabecaa0721be4eb4f521df625cf7fc864c188afe2baafcd7e99e3e17730515795485e99253cb35f7b10075c878086ea5b58931bc33e6260535fcf1cea904697353ee7a66969854768685b721c4c67e97f950891cf6631f34016c8004ffa45ff2561350747fec1604d219e3b26e26de4a9882b55494af9706cad4a88f73e1f9574cfcb85db1e183137ccafa14d378701222c9be12f621639bc9480cdea0707a13208a91ac6b208c9d1f20981a3163c3da617b87db3a526af0625acd3331a73e0960c6110ac8fa81d80baa82b6b5480ca1353523f79f5f314ea0a8334e2814371860230a1ed33d3a98efb10b4a7211e67aa1c5b9908ba1ca54c57c877c42d66992ca5e601bb3ad8c34c9d8305b6554552cf3de2f6f15a1ade069c88f83355cf4a839a04d83c11e395888b6275f71518ff1a0679768233cf483ddb2bb3d3e326e6166defd884c6c16b208ed2eeb989469529213ce01b111826905af023fb7fd6146df1277b309dc28e102203045ce2080d9cd81677fe30b1731997effb4fe988f0b34f96c8920c117e18485cd32eaaa74c1c1f84dbeea2cf5096d66ab9cfde0e65cb24c82554e9165476e9d4b935570cb0adf7fe7c15951bc03c26e12295d8da204220424782647902575b6560e172535669f034f5664e8d9389e9d93f58bfeb290c4b5d39084f924bdb2456a75b7dd6a568be9531f0a69d372011842c20567a6f1ac496c63ae4f9b48bb1d285a412afc67284a350d5bfe18eb049f43268b998e6980155e8f231c49a5bd4e8d4e8aea4b2ea83d252c156b08c973bffc8b4498bc9ab33bd5b0fdd317e160b031bc5753b6950367b082867da2d11a5d34c5355dd642993c54b5feacc3b1448cc72f0ad4df1159ea12335115e7bafb71bb0d76af44c9bedc2070fcbbfb36fcb88ab86684929e1053925736ad2e88011c731b293e0fa73c202fab60adec36dae4cbf0252d6c46ea76cbf8d2f59122ba5c01057595955ae2d9acc87ab821c423460fb5be1034fe9aeec9561adc80d95387d82b0e924c6ac4af58f31a20541537ea762b2fab57422b684ca95204188d823d2f7c9c900e0ea445863343847e43520938bab887d81538e8eb936bc4aae9b8a9be5f18cec1ad2cf1a767b2030d5f1d85bdcfce8bb6d21806c9e7f0b9f77b86be6f633d91ae2ece2e826ac4c4e31b875e25e476b2ea89894b6805d349bb99d41c1108cc84af010bea728dc5768a9dc8a3d8f977299e0f76b3ee3bb3d4bd727c569fbd855e410b93475dab51d61e42a40753011e3190d58b53e4f93b6d64fc384e2ecd106f16fc0afcdfdd70849d953a037b60e9eafaed1768446fb468287d11f4bb42601f8e3e41841ac58826c6189acda3fd0a70c9d7792f368791418c8e39e4e26eefadf0ed092784ceef4799be02370b075032c3179aa66558db0a2397d5c8a87b50ac49661d149e32ebeda5b0edc419afa4c63ba19e6f71eb3a1c2d2b41d5a4e357eca4c3974bf00fd300ff66722210ebaa53fc0bbb239ebc3b778e603c36337ef5deba04ca0c9d388a917907ef7539abd1cc32f9cc4f364449ba01e3b7c11dec29590ead6254c078bc35cc368b88a9074775575c4b084718b99af999e57599e1c1f619a740200aa6caadff73508544da8729054cda08f6c93ec723ef55e7a737fc8d35c10d6d8ba7930755c01014d1a930faf638b151c41a4d93a982645264c9c6d1a5dbe08b4d937bb900e7acb4ed2560731fe6bda0f946cadcbf4708fc2e0a2b097b0f259ccbe97460416baf5dae28bce08d04050bfa6cf886cd577e3604a42af3c3507587519e0ba5dc98eae3c1e5e95925a5de75320d65ddf7163de8d203d00d12cb197c4e1093638564893641620fb3bffdefc88119cb59b7a4790a5cfaa07e76f1d71716da62bd8c250e082ea9470df3d462259532917fc00b5fa8d9f9eefc917de2483cdf2fb5f6a34c0fcc3bd0030b7fb0661486fb452f6c508376b55ea3c0512aabe3cd9e37676dad4f1119aa4cb12574eaddb5ab31a4f0caa7bb268493cbdaa9e4ccca2e1082565e55b6a3d7cb33282a6a14e64b95a7c4272c5a6e43b1213e360e6ed236d447082d97eafa04a58645371a6990a78a2fda40f12ad972e1e5d6aa9f0cd3498b93096ac25c89594dd6f262e006f185e3a0e811ce53433dc0cdfb887b3856044e7f3e105b0f046f564fb1c9cd8820c381dc287e714e0cf0e14289fa7db38944ac40bb9a15b285aff73643e5a83edceb5cbeda46b44503638bd7fdced406e75a00f1012f6742c4395dbb78d0b62073c117525475de6c41a9979a659ff6062790c6cc20101e43f59026320631cc26855fdd56bcfb43692f9e85e9390b9e10d720b74c523304a049f6d5a25595cbf820c52a41c5b683af0081a627181712e3597b6d002a123d6a054bfa532eaddaa1f9c2fa43297d1db09da529ba77277c7fec848d095121bcc1fe0ea2e4d71b7804deeb1194f65b3a3defbdeb6ba0e8d7be27454f985105883d9f19c009c25ed2f558c8552055d4fc4e7beb8673e5feb4354df06238859f31b5cd5ca2c1985e9d7dfd6ea5e4cc78b32783196a30052813c453d402707c9dd481d22d66a6c454cd65959d106b3961c1fc04838219b66f3a9c0d745895f849a2d68e8bdd2768ab90099289e4ff875b174dd9d0aab3844220aed91f4ce8c8165a5cdf54cdd40ff0b4b683708b18ea4264ec849a159cf2500a62320c49be9d0908afc423d8ccf40f6f16927c7c9e029ca1e27ca8963c20a8f15eb27cdc542102bde92dc18bcab89fd3cd6689f698741cb659681d73beef9a44d50cabcd6ebdb92a88f164b6352d93b05d236848089ee5f1a07598f33279907e2c1b9fc55bb73df30945321dad79a141916b23296d07b9d2170dddccf9fa146534333a9985739dd620fb3bb3c88c2822f7bf4133e9fcb6cb960605fcc06cde5e5b94186d045bdccd248845f7298358e49832b4a3ce2c0238747aca0e883ae39af6f4963ab029ef3d78814e6dff8220f5ba41be765470fecd3209ba44955d00eccd9fb924ac175967c264c6a445f8fc5ef76651d571fc7e4ff731f0a564b408835144d87b883d7aa3f24750404c06d78c9f065b57a7033331883a26fa1d3de69aa65f453f6f732b623b23a21d6cf1fd9a35ed77b144ccc2cb0ae39a7b744f8eb4856303818e5c82d2018bf22a3cf078e359753930dea267f48e4e9d3db1d5002706c724e494e462ad471f7eccc734b7dd02a384c4f402b662ff33c35be93c06889897d57657e7977dfce32a0f6d02ce3adc77b34af9f22263ab404f88d1d9573e6a1f1b1607927e81fd79a4d1e8ddbf3a2c8e1a92f8141a22c1a22554bc88ba5be93af5a54ab608d1fd8cf2ef04404b5f8aff50c82779c3e0e68150535ce4e04bd97aa28541bee0e214e14a204708356bc113cda25fee0b1e50b9c00645de6cf6ae8c113225a3274a6303b34aa7408b8e5c5e35c5cc80f10f24cdaa6c87f18b4774405a24e301b712408287d9e48a72daa726d863790fb46dc3543c8b7cb7a3a1394b70afe84867141a6594d2d36040908abb65d6eb539929de81e27217e49450411fba87c8a175df0beb3f81c64559f45532bae17626adfa837056872bc8f51628726817274d0c17b6703aa0c559397133aed1a7b12bb1d6029112b79cf5a26529069098aef22f0331c814159d4bae0cc91db603c5bb0e3481b479f951c67a06f075937d19a4e2d4cfc7fc2e604f59c383a88f1775fbae93a4c22c529a4581a0ceec498a782cd6bc6991f4c08133f7b14a866a7b13861ca448d738ab9a8e60a2015dbc2db192097bb1d87df887f85cfc2d462cb6e1c54ca4090374bb12e91d331c83da119cc96c8dabdd859546c3c69be55a6dd44924ddc6941e219f2880595749d9d70f9ead23a6838d09a5c48cfb800aefa1d9978105d344d13eab180a0e578018e103145a6445950fa86728cdc7c59ad19a7030f34b25f27f7fa0651f8b667db49140dc1596c6fb969a6fd2b99d1ca04e3b380172b77aa7262d428127937d0d8dee9f40ca82450a11a197bf7c4b5db0b1bac56f7f28d629cde4694893d7ae88b29a01bd2a2944a8afd05fbc0adcd3c32e58047433ac6449c48f360bd922d28f180b8c88629b92a95bb77501769136a98b3f2a12eeee8e53804cd74351a6b2264c2444a240b25c9330580d820ef0cbd726f6e1749f6faf279e37d1663976312a4b485f1e18abe117d2ecdf162a1d116b18975bc27b6e806df24cbc0618574485beab41b8dabcfa9e0530fe93d20147ea837038e13072a496be7c23ef60a074574888d979b6a690a4cdd5b0f67921f20177ad9b782d86ac95c2b4acd297fd34d84e81be44750f0503ae406e6d909e9d0f46a9009484942124af377600b4a3037f68948f14240406a38f501aa1257f4936d3a0736288938b80a2110c6a2ad1ffe29445959a62ccfa95f719235e4cd94c83a863f75098cfee97cf6c7d5b0dad3759b35db5ed8128cd0322919e2fab0b7d08ecc302708b99df12232d1c824344fe3670a0c21534f89e0bdd2f7740dc2ec794de2728657cf141e0456cbe33026be29b8e544f6a4e655bc6506cf9e9540d80a3b92bfc78729e5523c8f06d4a942d72fb1bf7cd3451a01c0904e7980ee45956d23cfae32144941d336cd397e37d509e6124216742ec8548210b9a6c9cece18671639c6d704f63bb6565f7690f7678721a690af9fc2987d0548595fe30dcecabf6192d83c78f8189260caee6bb691d9e5e1c388a1a4089baf06861450ecbcfb02efdf7cbf0a8ceca30eb394388959f2f2375cf46470a9dd6671e306563d697e89c7b427bfa7897273292062565a4b0a3f85fe76a015cf2ed6e4e33417aec98284e41dd54f517c69324cd5892b9cf478a8440b876e37d2b6bdf2d6833e91cc56071aff6dd82038f0a88bc643f7a64bc074518f8e200719813b7a096f4e435f632ac612c6657a4ebe74d23aadc5b5134dd160a474a1a0034352b1daedddd909917d152ceb277cfa11a74973f15c2803abc6d71fe45cf1a879adc8824246f99a8c3e00db7b29c5e1b3058b7878fa9db805fe1da0f2aab3e50f687b5aac7023f69c779d41110cdfb54beaad3db9b7e139be161eb7b381debd203c96d3b71dd5820e229067e67bc44cee6f5416fe2043f70a46a9ce7a88a4a41b037f501374cb9968144aa8606ab69096c65d3b6a6d6d9f9bc845e2d09011a0961b64011f35a737b46b4679b68599013b56e54b5e277eaa384b819ffdebce902a64b8d002c094124e91e1f17d4b2bc8c20d23510b6c9817e45ba782a94284683fd4dc18e474dbb636981adead3251354106dfa25c1ce77da0c0130ad39f1977d592629e1125846d6a56336a105d8fb58d1ceb641ae013772cd3ecaf0b48acbc4bd3ff9b70565709f5989960397a587c4812e8cee5d01e27ece34f5b16bc86c37312ef8cd7307aef4e6b6b66ba1fed527dfd5ae2d9c77f3ae61c6378422f422e9ccc85541904d391dc4fb7a0153baf24700fb94965adf575e1596c694a3d3a381c6d2db755f630c490017e9a4ddbc505b0bae695a6d42dd2a492782cb59c5d5bf5922af9b5f3202120c53b3eea8c9f2021b0dcc3c943e1ddd0e843a7d9a8ee0ced8dc87a668f6deebfa164db991e1b032fb462585933b6751032b651960d0f2d026eaf07565ffa40cb8b25ed15fe84544923eb111e29d2efaffdbabfee193a5dcbcc5c30def70bd7f585d37cadf6f2a8897b0a5ace33f8302b64b284f547a59ea976592065a23280b35f2780231becc3198b035913e200e6e78f38a3ef9ae646877463bb09c326537937639be1320652dbbc72cf2b645055171bc9601cbdcbaa70eadbf64c54fde5f78c42f17d03351f529b2f3cc55b1bb9466889013c6074210694ba8c3944ddfa8722929e668ab163625f7cf03d3a61078fa779879d71879d9f35c1829c26e55e83adf9cb255ff4bb0ff7fa237899ddbeb2b71fdf93fe59de5f6418793464477730cf1faeff90781cd1ae17df7ed625dc71e3b4256547fd8586a819b0f26dc7f49adbe7f09fd7260259d5845b50c43071a2b2346410b054df91ca0b1ef371adba99a9d556be71a95ad49980d26185a8c6ae7808a999a34bb9375b423557648d6ec92d1a68e8aeb3152257fd3573477ef62471562ccc4474707c056d612e2f1863fffd44a0685990876b1a7c1a6c68fe27a1127102413b432700ea794187c6f042718ff6f6c0d5a532514fb5c83aeae80856d64f0d5c169e4119e56cff1e2f878855bf37df1a5fc22cc0c6c98a933efb111054a7538bc1c32976eaff63040876a34e60eb67e442f4c126442abdbc764ca1673e9a8a45a766e22c30224a2c41bc9a86b10d6d741837914eadb275ada129bf185b0b9c46eb35afaa91f133d5428d1fc0d251ea7e8e1e9db869301e22078810d9a459a8974cb822724f588298a4b0b25c69fd6888c80b94df1086714c726af358b756f1fc6360b469571aea4556fd16e8a2760a2507d92a05d2eb7f6e35e9ff1ded52175d7b163de694a05307c59ab70071fc791c81f0b8ff979e164e3d0e1bd3fae6d44677e4024273174119f23bc87abf1189d5a34ce780d754415e295301f711fdb51c80dac45cbb17f40095220a97a113c50aea2e979e044aa17161b2ae9289f408cea251fc0a36f84174c70dac9d7d4aadc4c3ed722e7c2ab3362e7e02751cc9f7145af7821fa032d5b3aba9e7fc3fa4e358501eda77ef6138ae9ff0e0d1cf1bfa184a350a85b5fe1e75293357819acdd3afd62137e1ff59077f0301d32eb45ce0f98d46a4d46727b091a64f9a798093b97b92eab9c249b00ad3674e842c58fa9329f1378297a8a75f1f41961ba3d9ebaa2ac65354d92af723edb1140eeb2b557117f04bff12fee3807f9545998edb4cd24ae37ffddda6a15eb750be48630bf0147a7c0f44ed909d96bddf04658fe009600344284124d14327f0724732cd16d26d9c4271653faf0646318bd4c9198fd8a7014341a0a8bb02aca93268aac4b99da82c7e5587e2cd51c45214cb7229cf9b43ebd20c72bfb14f73978d1a42c23e4a19434d1052cfadd44c3b45122ae519d2023d8bc19f61fed2f1cae851b94ee122aed9c09884b283524967ef9e0f80fda203827de0562e198c2e1f0936a1d0dc14c53e4a64e2de08e5cd384e32caf568e4954579c32a2fc83b6aad4adaa702b0e8d64ff2ba18973ee6d49a35f3c805393b71a1c8a3429d2ffdb623b166801409c82f00374482053462dd309db084a50b73f5ee42de462c1d4faa12d3fac9ea167dcc4b7803e2315bcca7a75154f5fe1802291b5eeb94647c580b297d485e8cf17d47b74dc55632fe149cc098dafa4a8c577c0d713db689610726ef5bb95f3e4ab1efff63516dd143f51ae6766f91c826df31f9a80e75b5ee97bea1e04d80677ea5ab55e08fe736d5854746185d1adc0ad50f3867058a3439d6d38618d85018251831d37007818056263c73993f21dad787ed20e203e3673dff800071902fdcc2089529557c86a5b842e7ef5c7f84022a1149122a8e17a059c551802f07cc64bfb93b69b116c247f26b3e6bc5107af75af33383b87313d3ebe6753b6d0682f12a4ad0b79b4e23a8760a8bf7d03fb4cc6e65c954fd578471313115ce7e2d22898892ea363828f9f1a5980509bc78ce5d32e048a0d86533c881edb05847e2e922fb12283e738f69c17d79229f848e923062247ab94b8d17ed6012d258695917cbcdd64a1913aace0b10a61c6c947798f42ec81d84f2860390d48ae9bcc269998f2c79e66ec5bea917a849283c3c705851b90043dc9c784b8e5762c99b03c4696258c194991e64ea43ae20dc996f1f86f9db8e670e56054d126a679c15eb911fc048700299b6ef609c9719e946381850d8129079a058ceb6252aa2322b2dc797415e196663f7cd40c5edb42583769dd8a547a4a6cf0a1d7f169796506bb6cc9eb1e6ab4badeb91d4335e6a9f680cdc4060b9e2fbdb87fe950ccb19928ca1742f99aacbb63c2c0a64d7698f654745d34155ccfa44c18c3a841390b6d38c97c0c2cd0510582ecc6bc3ad17b26ca34a2fb215b1a6e112ed79508be134e09ecf97a84ea5fd9eea2c610e4aa977da1cd9011207630af99bc0ebfb13da475e51a8b92bd80ca80017c1adb0b53d66d009cf97e8598139cc072c423441a75501f6f45ad4c1afdc6b7c18ede322a5adecca571632a7ca23e259d0b3aa7f207305f23a17e45d4a20deb6e684c9dde60ed83b6e121ce158c704ac6ee642644befb05667a4b5fc4c01dd37e4d476adfb6ebaa138e93648f17f90c963a989aae13e793d29e1488050d44ad528692c5ef8444193c4687d984ea17efedf49cd452264ff6916c5c33a5306323adc1f4859fc30df9c4a09dff6eae7240a7ed4d6885fcb63ee56e975eb90915dbe883501308aa36b0eaefed2da7d5f5e7f10e37c544df6f643a7611c9947346b59d71cc60c46a1d5d6c41d450dbb410cbf79f17e202c43788d89b275d838352962450fad12b2348404315c347c994688b95544b9f85be0cb3ebea3d961b99681a5b9bd13c329aa23d0a6d2ca80551323394e1fc76ef27787d0eb4b89606161cdb68e5f538a41c7528bd9b525624db2efa1dd18be7f14e7e858cab45b2306e726789d1776b30347fe90d023495b923b74d674739702c9b9901cbcb9a4e7d3fbde930d6ef6c1775597df630baaf859171dc51d1d93fad28e8a452ff234801a9ed7d2cc2b1684453bb99527263b9a8c11d654ebaa5c4d098099e49332a29dfb6113293932dc50d822bd19af295c1fa8265262dfa0a5973e750e33ecd037f8c76538ad373ad9f63b9e40e4dff4b85e67f79660e27ed1e45d10d710ff4b92e0e0a887bc72c915e5fdbe6a5f2648ac95f163a8edd061bad1be3234b27308c611b91043d99acb0f64ef6f6ad04d56bbc4e779248775e1bc128fe0118e33d3d6a44c0efa759e7d857a77b3436f6f03d455a29286c20e6d9162cab9a5a0d24d1c31fc7db8964be9f4fb6d7435a0f2d165ba0f50010af81e0527a6170e738ac0d3f3c1ffc77b2cd750627a768cd1200c86597fc8d7671a41f1fe151ea7437534874612c25cbe5a29bf7192c5670b807de735659a91990d10938e0cb80e9a3ec6d5f53401f3670b9f3c2cc31ed0e6cc1311a0eea6989f92c9e8ce89ae97073a0e84194827076ed2e7e2e540485e5c5cd3c68ba16aab9b28346116a97cf101c815257cb0012250658c798a256a8e8144d3eb0da89423721cb0a25132217968207a076f8e0d40dc0607358a35963e6e74ca502187012cd8405efe5070064d9f403f79cf56f66a0c0225a7c7a1fa97d0ef6563bb6a42564e43539fc60006a717829a05547a3b5af02c43cfbd40f196ccd5c9e4f037042c46439e805dbeffaa7ad0b5d3018f2f3dc72ec8521e1c5fb974b90503d5c76b1a80404695f710028f69e1f87d34deda7b7b28058a70ad53a5f74eb429c44a52f046500f5fc9eba780256c6961d14835dfaab413b21fde04a2800dfe89096bbc878648bb6880680d9047479687fceb201b1d57c0fba429aa967c96bef8e6f0ad7136670656078e3ee3fbb4d65cb0af1d4f2a0a1b85fe8e0862267f748390d8d45eb1d16e0fa3631fbe0933de0894d3c9697e5ada267cb8c050bf80906ee0cf5fafe4517cb801a1951b17088ec2512afa0f4be5ace3ae61c343c627f78e354bb97cd0629d1a6e924571008a63440fad7d5f69cb6378a2f3f456d2ec4f617f5e8ba8504dd783b650c988e5b49585048602970a1b5642a00089405d71ca4f7b40f617bbf36185784fe04ec4d9b5a83ec0fc96e1c76868e8ffae0e7532d6b7402e5864dc77eadbe9275e403bbea8ada205f2550c50880e9b0342851c5c53007b7d8520847fa489c60b3541aa4c35a7856af6496392486cb14bf83a271a3b693f742bf102c2635b03c601cd23efa33646f5fd9b46fa5e44204727ccbb221934309c111ff8758719693b6ba2e89d12a3ab6dbc7b243f4bf6c8cbbb5d257d00e40f3e1fa92a9b8e9e46493256de71d1c9163922ab0b2a7d0a9f5cc9a8ae335d000918099ca029229a66de2aa651815c8428bfa1b94782f799057549f606fb8ad3d58cf1a37065da603094eea4653bc593ba402fbd7b20a3366c246981378dd291aa7b44442e78bfc2084ffdf38ee82b8d612c7f020babeac14ce5e3ba1fb707dc997652417564188382db5c25be645a6d41b16e817488c1a5120fd866885048788d075226599c81c1df626c59c5add1e5fc1962baf239237f6430c0fcbf97ca33111ab99144d798e18e664d5f8e852eb48d85396620b0f19cbfd12e0d867aec51d53cb01a81382e184fe12b8e90f7ba568dde8721f7abfc30ff322cf097b48b8eb1ebfd004fa5aa9600184ba35118a8cad1620626be68c7bb9592970a2785759d6e86b80a0d39ba9ae6823d4c810a286bc913df00452d023c6e0eb2f04453fd514b413683505c662c005bfdd9be192be185add7af4524807145492000d2ba8426b0bfd8f5de09834e0ea1f36cc59e79388f0c9b695fc273858ae101d3bfd2464bb53854f9a82418dc01f374dbdfbc5655b938c3da8dd6a0a0ef05a0bdf496a7d24a919181709c5ea58313e4f5aa1c1cdfcc38d146b4d784bc92e95c47a132b7023b613795646873fdbce42803e05d0cd01a9456a3144b18020e76e185d232c6edac45aa67b135c050526101ef051fbfca73b21c0a02cc5a9516dd0e04ea10ba00e3fa2e674c251450122b9f63a24cd1f262344f9350647719dd8fa4c24eccbaef7c4768a043cae22a5bf7a659198f14ad7cb7533818cac48bb6fab60a25723aae79572e14d3ce7bb56f96c38ccd7074a86c5743ee6ce79036e66c7dcf7e119d5cb789e85bb10dcab84b35900e4d40d66c1c34c4f2f821968b3923fd314bdeb55be17aca04245380daf4d6624866311133b40e4993951ab5630f9940e8ae0502f8d62678ed5278773c3faf6fdf7721aa02af6bd7a7f8cf2f71adf29087aa8b6b77715765826b9f7cfa73934d1c16c50e8f9101de132a073a7b3fdb97ba216314bef4a53bb8dd17bb72050d5e4a7951a176a57d6a9b4eb16bf4aa854210b7496ab3a461c29ca6bed739e81f59e071b53447c40be7ac99bb6d166de1f99e073f392a193bdaa7eba1ef3f7f5bbe1c9e92e063a8a816e8ea3f3ff2e70ae67c4dedde7c332d6b01fa00b3e8c7b168791d82b8be9a15d6118cee92786ac1743914515100db77d168a298b3034ea20e4a4d5d024a470048598a83695829585f56d995eaf536ee0dd0089fb9c28184563dc83f1436837b83d7cc3729c3e9e10f26fab415d501835712bc23eda7a28c49a63a5d07d202aa2f6fdb1da32dc38bd24110eb63c8102e11d5ed1280b7ec3f741b7da61e7eddd1b044a973750132aee7b96c2d839e0d3d41d506bc4e2cac0d001170cfab6e2aa593b166b19b9140a4f472f02a51d0b38e31165294dc7a274ea029eec584048b2332d551be042a60c59cc91149f59b46162de580a2dd2e2e7d225f60a215f7b96b89968390c1496c075d1015fd598842b8612607d07212a77294fcb0a3ff122eff95e06a5a208bfd4a6a91622d34518b43f5c869335d82009033422fa0383c9a47c21a2f00a80dd96aa3545e1928fc0e47935e2305da3e61e48ca4d281fbb197c0b91c43cc1696ad70d51197847dad972cf9238f10ae36552ea649d176f99f987554654a6b24eeb74515a2e909a2718594f4a3b47b385aadc00ea8c6ea49e2c2f67233f66e14014592f92cb2851836052b393df3df7690ff668a34144b2fe60917fd8ac04d177c3669d12cff0be796aec9e94ccac0f0e024803def499f42c8edcca491055984141164ad60c442a9e344b62ed9d559095745ba141602fad45d1b0562d185e6bb8421ea34106620010d574da18ccfae55d55f6a596afff2b474b8ad57793be1d888fe579e4eb6047f7356e68ee30c442403bba346bcb94d66620b8d24ab0382e818fc5de51b503c6f8485959aef311a511dccb861f546a9f074dc0ed2028b02a44805efd094051c4a1e01d221b7bbac524a60cac58667888073bf6c4803e037cacbd4736bb1759db6fe3ba8d48298e8adee76750e04a2a4cc2c3bb2292f3122468a9b4774e4e91d088fb524ebfd01d29e094cde2009fc7ad0ea755dc1048f53dedb49bcb3d8a128f61c8f6ee56d8ec2355bc5af5e0672a9a0f41349dc3a8abea87c0d9deb8693180736adcdd35e07e75c98941db24d6fcf50bfa9a76055313fd48a8b6f5e2ff3312b9c492c18ce7a02f6cae990e7705512441fd1ce32c511127be3b5c6041b76d3bdb196affeb68a85dd6ae813cfc7ffdd8488ca98227914a2bfd21edd4e80e091bc8c3258540f3a9055edc0321476f940732778d9108ed69971c0c79a603cc46a5530199d638aacf3edd41c8d3714e6d4489f31aaabf52fa0a0d64743e87ee2e5d8f9b1b56365dda61731c39f528f477c2d232d62c25ea7bca7eff94333e9cc22a9c127b9d5e46fda01fe421fe41d7fa742c5a847bd351e9681830a2cfc31ef7dfda2d8583bfa70189164754ed51e284893471c2c27277a98016d5a330930fa3247ceddd90474f273a09f331eb38d09bd3152b1c44f5fba780c90be1682f4315836691597fb9f43e8b3b35240b80bee139034eaf42672b3b19014954e1f04ce9552d915dff0a9492a37f6f5337d4df34fe112d808bdf25ebc349c6f39ecc1cb74af5d09c193d11b3c798f07254258fa60231e6994b609c0a8688d707ed82bbc1f2c86ccfe380785bab3864d9de6eecb703f2119491ac75ece3b445323b0eb24c81b6890d5212664a8eebf31639d2250ba96994685805762541f83e458e8f92d603c08ca8c17ed410f4807b06a68ca6243052dba3d3e6a6e4d7ac1160f9e83a6c4a9653e718fd8a0d5d0200db4f5695ed3ce9fe5d74f104b509e917a9f537191e36ed33b7b42100bf2eea794d8289967b95e7e37d79be102ad82ae718160f1afa88c76ae2b6a617f7e93bc7db88e16793d2ae0b400860a8fc6eda1952c21931799da4d7475828ac4e29daef9c261d69c4e04aecb4340959db8705445ec0069dec6130f1d18a097102dab338325abc82800d14818e9924d351f44f95773d04f1646dbffc889a88bcd28467d6eff739443b8823d9a4cfee693a96fdc15da5a0c17566ff6dd7400ef44e48088d52ffca79ce68e2e23d73458f65bf9467956c74bd504377b207b4e605bbda5c8e6750d7b5d7dff79339e0895e314a3b7bf063d8aed54dbac394b6efdaf477768559e5241082529a7a1208e2fd5ba55d85e992ce90d1c5e44a2b47a97ee4887b4ca778d408fe721d0f9354bd53cf17c969f398a76e721a2f92e0b68e4fb10891dcf060102b484532149295345eefbd4759c0db7d8cf3dc88c6fc34e8193e93abba1622f89840f2d21f72ba4ca447e0b6f73222a32e56b5193ae4f2d2771120d0228072d7218ce77ee8197fc01397ed084346eac458ede2413e411fbc95b91e78da772091de8592d8b4356d43e81d1aa8651e463c219a120e154abab04ae4568b463f104b57296a9bfb39100d49c28cf1c719806fe0d79ee3d5e32619b9fb41c510fe83ec729f218884e7bd181eea68f415fa688c4bb37316878ac5c4c0d666a5d8135a3d6c827a00e2b9433325ceb6d7c0696d8feb1c950fb150ed54a0dda88a563a84df2b5e37ea0b678cab460c84411f1538d0dd65a3ec5de4cf0c3ca8b487da1445c6e1a02b97982a719828c486b34c59ec0c16f140e44813e2c38e1da7f54dcc7c878281ab1492167021ffe40a9b5c56715f051bf525becc89bd97e543a5120f916aed56d69019d8c1d703d1a19ae38f1791c4da632f9d0632d000ebae327488bf46391d0abc3c1e98131e95823c083dae1a30860761062cd2d3cb9c60463cbcb67953f019077f52419e385f410119636220194d6d1c48cdbb04d933a1e591ee8f3df70d33a92841a57383610298606a421e121aec7e35f1222585328614fadf7674414e635b36459bac721c5660a70d9293f4fd64d9ab6265e7a4de49c205353c8f20327dfc910d531231da5a562a7e28ae1192545238bfad2e616d6892532dbc09034984b5905956b2f2794bbeedfc3678764ec104f8507feffc79df90c00d614914a394954f45575f67fe4d6a2f823bd476d5ce8d252b55175f698d725bada9424d08b47413abf077ec37c03dcb1da1303b8d97e9f1a1c227a51101f6d75921294074fd37370a0d65541fb9fa4977de1d542b98d94742d08211b97a483ff63e4976a36f51ddd197a4e865ec7c3bece84d88fb3659873e5a2cb75a18288204276614bf7b516fefc2300761dbf29007e1dd84db6a51d5d02dd5ffe26010f2ccf569eef88e331d4d6e9b1b6d3be695215ab6540b5335691721decb8bc1301c0e4eb547d008dfb916c49debf5e3d5c77a5b97cf756459c5bcde33af75a1c9f5b432ee3a79e77d36bc140971d256e6a211d5fa17644a06cbd00e7ab791aeb804920d8410b5b79b4e90cb72a6b29aca3cef5576e268f812d75bc973f604e2b6b4e5327194ce84fb7b284d560589a67898df7d06f950f1300de90fd34dc1d247abc374610ae63c971482350d986ce88f111571a4165661b91800b72be620aad9fefc82310034053811a08f0f0aed56a4d8f9206f11514d1f0ac16db842515c04fc15be54b047ff9dcad7be144dc765ff566908f0044293ae69f991733cbe38652dc66fcc9413af71f4924e36add19a907756c38b3b5b801a561953994a3befbc082d13941a26ccd8e97b8fbe9d9edc7f1e827b0ae786657d97e9fd106d8f91e6897ce945c81932437b85ad516698b6b1447bd8a5298cb87cbe8616e69017a2314f53ba2af6c6ecc00fcf9a0da605a1dd220f483d14593ff533bb2e92cee4746b4a833330c9cd3bc648982e176d41d0e965ec1318ea6809e1c64c63417f87983714d25950d26e0e56fdbb8550baade8448fa485cb2d804b12016be1d9af962fc6f715af9cd141fc0b58f3912f599ee1f8de42053688b0274cfdc207c413296a3a768088e1f4e0bf6e45a89133932e978dbf5e3a42f71781a845be8b484f40114be285a63da57b6e9815bfb9062dd707f8e1716f7b527550b807bf6285b2707e9df9de14134906eaa0e7409bf4043f20671926abfc3d6b417d748df1cc1be5c688fcc91349a224196e9421aaeea1bc67845c58843e1a92e37fc903ef76e52f1b59018f1e4b1178ca28170a622bad08664350191cf3f90500244dd754327407f8ed1b3629dc3e8c7a2ef525899a8ce551972cb290ce4b0006b9d45e44375039b0c194d1ae29381500e3e8c51d3fa971bf48daf18c8e8b9d0763b8f141961e80aa2f1ca4ea20fafba97909c669fa4fa9db27cece60092e3ac5c7349985d925baf86db82c86295973d183cea0611eb57b4f9170d6a4a8f066230dbbe15ef97d14f22308496b62b16423af6c4e3d32d97539c01735a25548d0ddb49119a6989196bc81f372e2ede51187d0f1d1c7789f744de4a87de6d0112206268057e7fb982681acdeb6511bf2da70ec3a391f1542228c3a23b9025c158b83e00f03da1bb9e3ec6c2fe0989c519a9e9e21b4f0d8e07d2367d1d5aedafb38e87be4f808da21ff13417f8be908181af67ab3b51c8c121e8174e13af04fe85b93d4651f541f6db745b3718883726a25cf9a0d9283d1e1e8d598d121859e8b509da3e64aa798ad086e29028806c2de4438d3775a060943d0af375bab9284e3c3a131d981c8cc3785faeae24622e5f87b06fc83119f3f728400fc7d790e70572dfe909d11013218ec3e5ba52884bed9803797f11496292f73d795ee0206ed23225b5af1a9e8c722cb081cbc97a68a450d455e382fd2755aff05da5bab498c90abba44b3f03fe9257a4cbbee1291429d986f105d3d8aa736bdc624444740e3c744576994fbdbc7a1bf949ba99eb113cb3d90ecada91d6b758fbf07fe79c56ced06962a3d51181d8e47ff0a66bea5d4fa7f3d4d9a7681b99b497e57af3c437def6360a44f3ab9c8b7d3ba0f21d73eb093336846324a120e47f6537f532a8e7a328f53a9a8be269318f31ceaa25304a0c4db6e0ceda2d2dbc504cd174ba6f864510e628557f13643afd593c2e382339ce3c6fa24c00221250cf1a9e441a35af53ea6de4d014e37a7cf5484ac45156453a4c5565e74369115fd505e2ff7fe00a296712612d3e9714a6a38dc4bef3c308f0a62f5095588d2957f362fe5f440d820d94040e628174b28450f781771ab234b03a282a0b93b325fb5ab4456a32e071ed83b539efc35413e5734fb527a73e4e094407fb8e1dfe2cb5d7e6e1e4335291f5fce22afd1661185211a5fb0f6497f331f7ae3b061313dc274763de4ed1e7b9feee9adc1d319fb2874ac162d99cb4ca56852017cdff765021a388689ce8ac35b771b5acedc6f12e8d377a3fb10c126e1277a503af594d6976c6d010158ab9d8023ad011742b7e035bf9c29d113912b514ac01eef3d90a27845a4094ccf63c8cce7c3abd5aea13aa45a18bdc4aeb6fb72e489b2285cb736f556e712a6e1088f977bd27c953385a4f1db7cb1a9dfe14774f3b4dde20bae242d388c90b4ea63fd515330ada29a4b0968873d14cd98d0cc88d20a67f0444dd8e9486b0a957cad909885705deb791089b40e5e8dc4f21d830e9aac746c093d2f1923db53ee725f22da3ed90e0121ccd0a42e169586e0fca06c3e4e9fe7dea2c232496c625f6011b3b6bf8e21d2bb61aaf28f7fa3afc03b7e15e5bc40f858d723d1367e65871d8b25b8b917d8009084e69032c0a618e7b5b218928b01027b4cc8786dd2f4d6a846cd8e22f585d985e0b8300710396beeaad4cf252a27576f1459bbc3e4eb7c5f48cc44c1d5c1adc721c538c18a432293a310e9eaea5a1d0736bef4bb64c6dec56fcc2a0d75ea141722fd2f8f9ef1e15b70ba4d678fdb167356613e4e264181fef776b60a605a39629ba7c8675c456fcb211203b34ea72deba70129d399bc749c0411c7722cffaf43359c113454c07c5dbfe846660e374bdf84f022919ac66f9b73c3defb6db5849235548dbd857cea9e3005a31a13ec81c7a08e14398ee23f54beff73d8a8eaa0d7a658ea1142b219033070b373bfab879f5e5ee9a9dbdee9060d68f5e04742a65365c23685db4d5ada6785ccf70c96c185eaa177e407236e27bb4d29b011a76be2f323601103524a6678d4f88df32e5855b3fbe59f0b24ec285e85576fc6538a7e9cc75534e70816766729c05198fddb6e9d6c248d4cbb5492f10f8df502029027823f808dbe6b42df07953648d789633607a5f1d9eb5e97c54abb1c8f74f1fc14279eedf9fa96a2090083751e866af3c9ee394fbc464bb5a5922e65722ab1523502c0e88077f7700861148da0410fabbaf00ada3c88eec1de7667cf5be321f77838ba587be1414e9ff540ee4b1829d38d06930369fe3b3cb206157aacdbc26e446bc21a14d49034627bb476c9b620091431ea590dda99280725277e81532655e7f1e910470eb26b33373e3ea08d0c76b4424ecb0d79d78734df5934b548574b5944310697556b7bac23071ad838482192c44f7cf829c1a449d4d4661ca3a6862350ba1078e63619c5198378cc83fc1df7168f3d4b775a923c1287dccb4b8dd6280c5294ba3e59dc04adb14f461c1e5b7de9ea9aecd4550b85b681b66c6b637d8a607142f2d69ed1c25d6ae0c5525b48a63a13ad597303b451be63ba66eedc75ccccd40765fd9fa853a81e63e83292c2de8c6d6f415d8e6bb7d6c4bc070eb9a456a2cb5a6f59c532aab890a2f9dba868a1125817231eaff7e59268376eb51a2dc920d182d176897d5847b49c217f0322e22c0aa6ce63d67110bd3944a982125e1216505da7bd61cf036a548197034a7cc58612ea387a6a9dc9fbe14908908f40c06775da13e384ac2ad72e404435b387fe52237082e8e708d87f29def0e6f44f89e9eb85f3560d5661139281699a8f970408881320c1402bff78c3569c9484e13398030a464d8d764f6f70d137c341d5fd111397a9cc0deb4fff2a253a6148fe01ee9676ff1ad3db52c073537e72b869c4d247ee53461987b433d50c176b1b26bf032b8384944290d5fd8f6c7e9c1e90b91b1c15bda0eaec5dd6aa08b9ec32852c60ad47c4a7c846d6954e88da3586bcc26f1271e90888266f54b54e3bfe31c02a88a50aecdca378cd81c02189c8d46436ebb4cc9695347d33536bacb80d6666b0cef3f3a3a16a4b631d197ba1212aa92164d61aaf3bfafa3bf7e691dc2363f121db81e95c49c8f84ba8e26f54ebb543bc7fcc78bfd3e83d550b30b7e55f90b1864aea21c4babebeb3a09013c37a02b193d15697104f8bc56fe431f655eff81170712f0f269812600215c68cc1ea446548186c3c86c43504cbec79a8a53aece6c9d683f5de3c5fd588ae52096e3d9c1efab578fa0aa902257546d73420bf6b84954d7b8d9997e9f204279dca44742bdbe9e2ec542e69ea41927bea276dc091dc6409ce3dbcc514944961ca08e5be73918f6431acbedbbe3ee1443345a5142eac0cd457fa51671150092f3f9137f83866abe5e8a28079b36e8cf042c122d723f56d7808ecf26b1e2c3ac53a12c3247b13a13d5c0790baaa883e8e696b799b23ba58ead308890676b35c466106442e3201418618589c805b7fc2fc2a6b023d8471dcd6c083c3627c90964ef5313771e414cf48cf4afe6b6508a99202bc95eae717769e52ed24452c267b6a789329e351aade4756027b14949a248aedc23aebc17afea089924e913c6830cceff3a670460ba0fd0d1cc6b96e0cb33ea64ceb5b3ade67303547b7330c0d9ceeb6b2f71e6d60cdb2081982483567da1a1a7f0a413b4c6e720035550eaf902f34594addac78826b09b7196952eb72d924782296790d6734e0e3c29b414224564ecf307fbc8466b70ff638238977e6b2b1bd0b60dd0f1949adbaedce51d778e3a92296f96cbc047007c457de78093d0c6072d4e61430cc89167fc073d2dec81923acec6e5a896ece6307b7c4a62c542b4995f3e16a81c6fcc70b58c596c3e1879fed05c1e3639ae61504c48ecaea18e2f255ff09ce0568f1ad9ed298022b27980fa9da7b5d2a84e4be56dbc6f3813bb443892bfb698da902a9d7b3f85a86b9e6f34172e6be038704ad825e8636cf341e9a5b1c34d53b7636be62e2cc3168fdcf99c6d84544fc280918134a5a6b35fc3b817da4dd28711ccba44864cc4f74445278c841be91d4f8f8a6594a5104199e97575938dfe086bfa4616b742409f06f7df7da00ca54b986fbd20bed4bc881050d7b5c62a9633a778d15ff959b2832aa5c5565a55c5d456193ff6befde5f7be58d0054210fd9579ca64ab835cb1784ef6028df3a65c236c56ab5e960e1d8b1c7f2503f5cc5ac4b25e58ad55b452917a9f62d98e94d1fd58e2eb2a52f9269c14826cfd2e983657f83c1536b25cffe5e5c753aaf6a6ceea7c94302949ed7fdaee5feacc2f5d4a5659dd673d9fb45b948e8c36a65b1aa1f2b90c94a973938bc185a87d8563f9a9b3beceef98d3b4013b31f4fd9c92c6cb61bc65617aaecd4f5ad3dbd49bf93520a77cd506e41c28226aa6dc16f4c25bacd16bc889161b9ec39446b1e88e9d65b1dec8d575c568511d748d1b2e3e977b3fc91f2fc1190f0473601628284d2814dd681197b9a5244f379ef286722203997978cf5f830391ad1acaca6b910b28a3a894f807d02b30bdbc8a2bfa649b1a0b10956c055311facdd3d5ade6339834c085543ca553d7008103f43b58cc96a4211f35f0de33033d0e73d8c490e83334d4e7c8b0a8dd8bf686f4cd26e79d191a6675ed85f72c52b4a86ebda19cd6ed01692fa9263ae453462e331961b2a862ddb0b79a3341cc727fa27d4202a0bc98cbbe698848b58a0ea195ebcbcd2b58df7e628b69615ccde5aaa14f8eb9869769614c64e55355e7a64fccd48cedfa2076831a0c0420d736ad59ed31a65e55e15255729e2eacf88ec62f0cb69476ecec16a814f705452c5ad6902f72f8e3559a838fafb4acf35d5edb502b9e5f2c5c1517b616a1eb76c0f9d6240b76bdc5ebb0ea3b40145eae000fa9d42412fd3788d9e7817185b930a6eef4895c61c37fe7cc2e3f8c71e5575cd44662e08381f314e3251e35a24a680b97af984818d3eb8871967ff84c0409c89a1c70511bc3455379204aa6f2764eef76652335ad9a6d1632d782d3b955081dc72f9e2e0a8bdc2a42c95954d9f7935d0598f104aac431b09d73b58cac47e2a36e4e207fab3773390cd83e50fd37c0bcfb587ac99b6f30375e980a840260b203b79a7afa81823cf02287609d4fcfa146e6ac642b4158149f7a4cca0557a69d3ff5f733edf3e06849a0845742e0e11432d494b6bd65f3689b2a67d531ab7c5e82fb70ad2340e30f2c49a264496d0759224323f63a1895a43fa3c44c83073d943648fd414b3f74f10e46f770adb19f5470b7da00abf5ced32b191e3decac62d7a97463ca5244b40f915955df7fb20f662431676038156331062ee94e1347c5ca0f181a65049218917bc12405e33cd2fe3ee1f468e0726b0c409af029bf214d61dc0c0cdc26955765397c77a12336224854e61959e735b0be7b501e4271f0f19b2d6b570a8cfed4093062293ec7c2c18e22c7cadfb4e2fd62f5179b32e60a376cab08319b6a348108d2a9deabbfc07b9526d75a32ad05085d9c3a9b9f47e6e0410948793a11c96bfad2820c6dbf540a60620bc0efc338dc9522be3e8574c422f7b0f4bc128fcd30dc95fda6a4a73a5c2887620e5192a54339703d79b369117d603c9e1a83d924215c7aa3eb12bed2a3bab202ec14067168507376f9fa2dc4cd435fe57559a934d0d50e8574f5b811ea881ab579de04b4d46a55539326847741eb13dc4b400b5cdc55367a0dbd46060ace696a280f638c7956480fda1a8bd91dc95607f2bc00c2247b9a5eaf478eca8d1d8bbb312f9de172b5b6f120ff914c48eb7de349a497f4461a02f9b5e5eb43457bd2240ba523ca04570121b817d007aeeba11c17e756ddf3364bff5da359806c705dbdf8cee8b7e48dd417426de632d27fd140a53aeaa62293c5dccc2d8b6f4a9ac9b0734216294d209a58a788fa18c75ebfdee7a92268f108b12f4c1b83c9d5be8fa4e346877075b41d51a7c6c5e9c548b83740995a87fca255ee89ace12a6493c9486eb7dccc9b02d67f53b846bb05afc56b1a064d86080258d6175492516995a19a634c37707721209125d6362cdd600adfea90bad40b0eb3ec7f8d9eae2488f5a86915a29d3447e7597478530a2f086ceb2d4f582cd676467804fe98e280c381dcb2932889578ce6a8990d57710ad256a6c8398ee412649c14fc7fdd1a0f3a848d482d2e3b770e00d5eed8f26a12f044b8e57578687d4c93bfb88df125fc104de081b3a5b801e26e2edfb4da4e3257fd7d10515a2ef164ff78c627ba5380f704c3bc0c093d9b32cf024487e491f4d2beb110b8aeb8ce70db4006c240088f4d1a60e414a84d851501418b21eb542bee74d24fd3a6b7524b19491ca8724ecee9ba9b96b3f26d3e4776ffb93148c45d493a789d83de6bebee24461de41e017039d1980b67eca5c8489f14a4a75d2221bdb6d479b6bc9814e5b312d2aa485700190a83e0df213fafff6e158e50cb05c570f29026ad700ab9508df1c1c10abd505952ca0e6c0c6bfe4ae3b9ad7d5110f11325650a41cc050c28e90ce815c0ef7fb4b1e327a5cc2fe24afdf46e4db41517350372b77381c69b8d00a98b4ccc451814feefa516035f0b2a91bde09db77ca8057d4266b2951775955dd0541cb34d74a4db39d35de5f9291f4655c29a7babac2dacdad8cd37033b9cae554f05da38a321b205ec0a0805b85edf40966b9acf362ebb585c58bf467e4d5025f30dc1d52b997acaf80358711f41c4ee3da5c7df582e4d87029229471c5d06ac69cb5f138558ad1a91b2c7e5d5308856642827eb4799233ac8a58048dd2ee3a254a5a13250328a35ad75c505aa6ed342d53d0ae11b2e91b32f97aab90b0318a6be6742987eae3fda663b44812b8c924db6c539a63e74c1573b22b4b52dbb1d3a6e64c2a07248e0af5c7f446470615049e9f6cfadb2989eca66c4bd6877cdc4286f4d0eee92ec82a8185af5203ee8368730dbb0b9257afeca0a34aeed888672ca74b2900ff8204b8cad939d07df4c215732b11d4fddbf0fcecd2f41bc3028674bad903e4b0e2e362dbfbcbe471771de895363937a63cb5086678e3594e7c09cc29ae4bfaf2137e086d5d4c28a20aff0e24777f5c5aa0587a5a3e82fc009a9a873a1f3a06fba77772716228f1c7c5abd2a1cf38de455ae8bc2c5b08e9fd2e469c4ed41bc27de3caa3f82ce00d8f2d76089ba66d4f00d13d0c5c57287aa6b9fa1f758f8b76caa09b662964fbd719488ef500197caff04c751384a5a83e4efc7693bc727862c04231f9d1ae15b38e70cc710ce6d4521bf6e4e7b89c0b369aeb0c8c6afe240b22d814b5738936b6495f33b5f5cd4327e264a1acc9f7d2080038694af34dfeb4a69146e8b872917c6b5ad755e4338b3a191fd6ab53f465e571d18f5e8cc22f4633afb92d01a8408247e5449f3a41eb66c959bbb9ef416a0a790503f534801a95e41dcf8284b2ed1d91f2a4bf174d264a33ac7d82b188006c8510b0e394fe60b84f352c8f67029bdd15829bb65413f0a650ce13170c7a52d61ce26cb36679ddcecbfd34ca9a934bc5a801ffaa8b62f78a2add22ce17015ad01b06cfbd1c5c1c00ee4b7129ed0a142394784535ebb24f2028d9632d3743c63429e5aa5ec2f0bf5939632a569cc31faec9c0f962678c64615f7eede107911a7feec98884f94160e2527541c728e620f708b647d54d96e3c4e55e85807783df46bf7b5a9640f71c8ca1b7f35d369817625ec824efb6f64aaeb98a11f3d5197ceaf3f2129720536761d3ef4e0aa6e9cd6201b5393e55ea6570849cef792461ea65303edd41e202d279555107e2a8a81e135be8acef324c421d19335d1f63f7b612623b46a182a1d38e9f8e0475800e010f586ced1dc8dbb49751023ba70c27e4a7ac8c6cfc045b65fe54d7842b9eec6768d024670413c49eda57d780e3a745bf8fed7490fe974737a943cdbb9a1c7e309fc31148f1bd0aab135e5adf6e856af3383e1d7113491751638caf37f2ea9de3917e680c77fd255c9d938a0080bc9560bd889ffdecaaaffe432e4bc8adf53b0f99015756ce4c5e435094c5742e751526a59c212e9042967502a53cac0e541ac50fef424e7b19bdfde79c4d13d53e467892c91edf86c69446a5940ce242a0bdb726ca17945544e3979aa8591a090c27534cffe9a5aae4bf50b39ce66468bdfc7601e2ef9d47ea32f63e3a91c22fa4960332565401c977c98674d675c281ad91842aade990dd7325360aba6a77b60ccdf76beea285c15a22233906ce039b524d730af213b3c5139355e8572ec045d2392c05f0a1c82440b3df97b515d912de448646f8bc63a710dd7c05481eae14e120a25ce9490f7000d134fa9cf3e5904c7203137e5c6b7999d073950166a931c06454014f521c3c5280395cdf99786d0877f51f800b92774853c0f30059c8423771ea84e06bfba1610257ca8c989aa7ecd5e73def82b538d2986bacb6b0f458c067934cd29ad551ea422742c3e12924a7fbcf1165beaaa039cb5fbf55eb05c819ce95f3f4ec4b11d2c3655b856b53e106607f9e9c8cdefdcc88cece54b01c5f81b46146cd146b7826c7d9ad93fb9c186cb46bb7e258e5e83a905348171b0b0824e7aa121c53384844e542dbca334b7310436c0fe4d1365a2301e78f79c900881e124fe20130319dbce3d4c68b73290677495eedba5caa53f8ce246a400412f5f1f4be8b6f13f6e575c1571d5ff6c274a8064591eec224ca81146ad772d3603b10db186d22c6839597d89e6436fb28c4d8677010e7458a65651f928567d4dad0d28e1c4728f056dcdf30cea688d929f439986247e173c685394b07bf4b8d26e916874af3e0a6e650a39e2a3fe4a9816016d5aef84e3cd99ce6c1e93d7d56238f53ad78dc0f4c386a5c21d45b243902607a9cf730e4183518bd2d968ac6ca82b2fb67dfb2f46bde8478e5c10a5cd7167221e392f8478ffac9787408f35c64a3009e904186b1179b94d571a5e01d790dc684eec09828914f9c05facc660b003d1b98b9301d722f6198d040377f0b3e8000081676ccc0add35383e5b2adf3d3f8a461109e678e97697666712eac072090eb94511f3f97e905186cd32eaae0e6f196db5c1123437884480b2ccdbb95694bdc0feaf1f2391f020c9457b02d1939c83b386759c47316d2cdbe8c765f2e25df05995f8a3eae348d3c5609642417015d966aa991974ea66ff3176d08d00849d88808a21028a8b6ea396daa24d99330cd1d3f390db7ca46c0b810be0420ac4957a0e14a453225320501ca580345cb71b3b567b3e487cd764cf441fec6246e89b129004182b374ea4df53bdd15f4227dc88ae00c8216c184d75ea8aa4a5d604b0c4403ef80f42c7152bff9ba9a22d30e34487bf0033c21a29d6407154ad64e87e13cd4e2c0c6d0ee0d59421f678252840cc19723ab54803f1adb66eb9aa56465c079887d09101e62b23c4e513242dba9994dbeb241ca16fa8888617b6cac2258d507a4bbd8e428dbb8ec71d341c186257cc8c0a657392a239c9e2c15771d14e83fb8818757704d07e07887e106b37587cdf9dc269a007a31d5fab398ccd4a09549ab9718156a892aa82a26327347da40a065ea5838f4b208fcaa462f29f718d981253f769071ded21064702f49697ecee70965466d56f6c4e961b2317f379f657ef561a6d86b05535726e79cb89a6582ba228f29402913164dea1580003ea5c17e0bcf44a26d21dc189f1b5996351ebe9a9ba9eb08675d167208e1f7b8f3c655b05a67468ea6c7d8612ba0b7a7cdbcecf735568394b404c0266f480d1800e7935342272d346405ed2077d4b16529302cf50be2c4150a112bf770068914ed6ee2cb73b4ce68c80e087a31dcf01f53857ee3f04bd3d6385bb3fe4eb47aa682a9633f299ab45504fa30bba25a20181ea50365fee81889443650d29450a0329046b6fd2852c97fd42a8d6288a74733fb5daa8a5734901a1e8b0e40602a430d721efe6d486786ce9b85da93d80e8c0c12dcc87f72017ed466d1482344eebdb72444ee2da54c3205c10a8a091d0a1308cba1b64dc68eb0d714614bf5ab5e0e1d6dc5835a870e09fab0e97dfbee9de3d9e45bc132489ce3d1114e7ff1e30f3cbf6f228fe7348ed55471ba8b0716decca75e101c250c8f1d613e9bf0c40b7a03968518443cc142842221b0603efca00584161fb21bb020818926c876c08204ce5dd4576ee07e61b1015bdcaf971280d00de1d323134b99ea51a7582855d0475d3ae9bb4164e7c60e2ca3b1ffe43ff91f6a471dbd02d4e69c6d4347af002f4ee3a04038effb86fee6e104b1aa94ac815299e4df8c73f6c0b1c8e284216459c214ed20adcaa6ac3d928512848a7670430dbe60507c5005113ae860adb5292f70b6b8b1e8a2c5931f9e40825112580f561c618c0c0c1d72d87a10460cb8f8c1890d37f01026c9078717d20b0b24271886611804a6c082071f2051f485123e8412b0c801ab825a11461068b0c10b0f7c40a58b2864c000dbc1042fa31f54f811f358688ac30eb04ad3a04783fb25c50e1842083d5ea14165ce89655224ad70bfa4d062d24aa552c954a158cf81af570babfc8395d3b66d474cc22affe2b61991b84b8da37fdb5880f13b8f368ebe24b0fd7a15d5c0fd7a49c14f8750bd94e2c704710047588afb2545ab251936e84816fc19c5129cec203d4631466683fb15450fa2c892443d5284a31d18010544c85206abb528acb8200b31b4a0610c1aa81811a910e560ce395357c06c81c208cbb75108a9748840c83be79499084dc909657b944c53652050d1875e06fbe1959e11986300810ae718700f7c7989a832c5aec1fbf4522b36abf2e71bd65885e3c5994e5e3a70b53419a3d795a42cda0ca0d71357b61cbd9ef882014df11b29425ec54aabd5e1e4e81568e9538161bb10c202a0e8af4087e025136ec0b1044b107e94525a81a7115172f4e6681cf355c0b3051c6dfa663ee7cd1c3f5234d5e444707cf8cd99b95e20220752be1062092928c0747882f900290618866158bca1e70d3ef002888d0745d002872776488253ce2909a0c3133e5a580cf7cb892710c087971361d897135f946007a9c54910aa1356387105899a52428c51ba7faa7eea2e31eca252cf11720501fdc4169824a4a32d46af22a221215710d08fb7c024211d6d317a15110d09b982807e602b499118601812a407cf0ecef72a4be30d6d9f2224c51525080ad0931f9fd90a03e64b9217a42e475cb66831caf2ba526485a8cad0142129ae284150809ec81fe9235b6130982f495e90ba1ce1c8c5bf2d5a8cb2bcae442b18be4cac2273641cf20f36a055a9e7c0c7b056a95494cec7eed481339940972b68d950f6370ea861d80fb3a8ab2ab61fd288f147e3801826bf3bdfc02adbdd0dbd3b5651a9e774e350bbe9dd6982edf78657409fc6d1bf42c2c6306cfc9294f26569963efb5aea52e931997d2c956ec81c52874ddff44b69a5c280b319638c47266e2fc678638c5147e3d0c2c647c2aeaad8f89d819f4ffdc78170673103b2f1e96331fbf9547ed693bd7c2a367751cc30ece6ecbd2ccbaebb32170aea9bbe34c677ea39100ad1385429f78edd1da571c0d9ded0c334170cdfc6dbf48dce417333f5af1b626424f12fb61552cae8d0217c6c4305dc10e78831c6d88ee35f3f0d42ad34424f7637f41a6608a3c396d808c4f16626d82f74e853d8c611e3f412c4be144208a97cf83146195d89fa594a8fa671cc19259533f60d7a851b8d634e2d621fb10845504d1822125eb1ddf276479af50eeef9a7083d6b658c56222cd5186354417728638cd325ab0eee170f5e30f5dec6bdf8589884e49f8dbb5f292a65b15008ba7ed502d4bef49ce784fb2ecbb86f4c9534617141b8cb7de699ae474d5ce9b94c268bf52007f227d8ff86e14ffcc77390fc5d2e21fef96b5e10ff6acd1238ceb3d38b306dfd80f1561218a4a4a38c3e7dfaddf46b2398443d4aa9c948c42068235cbab9c91026d246f46256544158f82ecd4d3ba8f33bde28638c32668865308319cc604e49cc331235ffba6191c7de4e187032e45f3b0cfe3915eb5ce94df533cee6f937717f1edba1e4246e9e38afbc99480a639e39a9fd6da3b54b395b7677cbee38e38c33ce39e7752fa2e085ad31d41b05a41217f221d991e5ae78033f85969d0f1f7ef11c5984096a7002777f1912496929ff2004821204e4aef81e04545d5268201272f9073f432ad65d10097ae9241c5da631e2cca49765524a8a69190644624d85b9749ffdc5a750662944c77cc8d938e263790458becbc611fd739904991cdc638cd15d42f7ee6e82c148c559d3e590869e02f78b065ce0eaf0ab77a418ca17f2414b4a8fbdbc13a09fbd054a8ffd122a42b02ffd92ece92fb9b01597d09ba16083758c29814ce800c40f2bfb2aa9bfcf9f71f698b3747bbc9218af0a089b2110108540f1e6aa58603304a28fbdd35f82fd5c42450866fa55fc25f4aee21582bde9b1a75fba4bec53ec210a4eb858a5a75fa262601727de380b99a784092c3a4a428845456585092c42c207894505aa9480503c080bbd4277bd826b627705e75dc1f916c03e7b0bcca793e54a661006146fbc95c4bfde02817a2b02b56861f8ace935cb814c4bb877b7673de7a440d8ecda60118a63d21b8391a3fe61de0e367f37b20da689db8e4c857d8ac2ed471388336c8ada36d555f686e794aae9bbe3f4ad058861961dd91eb44ac76d9af7e8b0e96df60d91fdd1f7cc945d29318d086dc77322c690a27cdf7c4079537e63a39277020725580d840314d6b6040e9660d5504103ab9e208955e32809563f4ed4a971031a585dc50962c0eae7893a359a3062d598020c568d2935b0fa87449d1a47f8b0da280545ac0a861358fd45a2ce0c0e5ab07a0c560f01074fb03a0b568b210410c2b0fae11735b01a0e5181c2ea87425147c715376cc16aec0a56aad503ab1f16451d069c008a17ac6e2e4760351080c06ae824071eb4b01640469820b0ba817004563f4c8a3adb1090b8c2ea8760a2ce0c2bac2d095b7c60f5fb4fd4b1f185d50cd0a18b1eb0bab9d081d5efaea8d34f6869620b963ff1a18b18b01a5e8185cb15ac7e278a3afdc4d018276075562507ac7e7f459d28041a7c7e585dc760758d82d5375a2005ab511083d5ef495127e78814b0fa1d4cd4a1b185d5363becf06383430c58fd3128ea94acb04e5a94f8c26acd05464060592b395ec1dd6ad5d062e598c5b672d412434551abd58223b072ec82f918b5688062c4cad1cb5cc2a8d5e202042bc72f9468a8d54a4207568e61729c50d46ab5845859fa401a865a3e43706165f964fad06ab55e50042b4b2825318c5a2d22a22cc1a8d5ba8207569652a018b2610b2c7260653905cb51ab25e50656965588306ab55e70042b4b2b520747add6105958595e916206ac2cb35001d46a4d798295a5162cad566b4a1656965c8060d46a4d9982956517295cadd6942fac2cbd4cd751ab65840f2bcb2f9507ad56ab0824585986d14ad06ab582d0032b4f9f1db45aad2d64b0f27c5284abd532e28795271428865a2d2a62b0f28c82671449ad16951e58794ac15d049ce794c8c59b121105064ab8683084137e68a1e086ee07a024474a10a3862dc2d0a2450e48c440e1435f09e20b134e98f8011440604f4a0003ac8a124cb181e27e31618299c54998cc09866118564312a4644e5c590d4a603e3870e2653a6902f37182c5886c87ac86221f319ca8c009941a56e083854fd0124e843003ccc70a4c8ae924cb747202d30ab61ab0d87c98e0848b520d5e309f273024d42838273d30dd60d2a20626947cbe2825ed207d8c40392182fa184d2758780d48d2490d309f32680d5a322735d48004adc2e704196a092c2b4a90c29ad86ea0e2414703550fba329ea072123334c1dd2ae6e177f7f5bbdb737aedebed89790ecb486c68da69d358d0fe64e266688257a8efb1df3dc779317772777eccf7982e1288ebd7fa266f65bfbe49061f9a0fee63bed63aa36e35dee4c1168ed15a78f3f29199af41e36df733347e06024d5adb68fccccc8c47bf763450dbf7a3be7e4dea6b6f3dba4a205dcc57cd43691705adcd3694d679784b79a798467d95b79bdc6bfdf579220daf717b9ef5aa97a1501268bcd2ea6bf5b91b33540e50a8dafde9f439a6c66bd7decb4570e5acbdb948cd6ff6e65ae365319f9d22865dd73dcab35ce5b6cf9eabd63eea3b7b5731319d8dd96e8fc6797b24367077572894e5663c279379ab1e8d536f3fe6372f23b181eb5d71cfd53ae3ae667cf6b5a3a152a95434ee6a46ad3277d575d90ad5dce937191999e7644ed9766580486c60ae7bae7b8ecb2bd423b1814fbfea90d8c0a9bbdab2df32eeb355f7dc67dff6ae50b747e355ea7b621ef5a998477d4ccca3503116d5bfdd1e8d4fd77e67bf7b6bbfb3dde59ef372132b57546ee308198d1da9ccb22c9b32fbeac9995189bdc96b20d208c43e661018f612a9c2cd07166594df110806047b1963bcd9b1863bf8c1f027180c1f8b3ee6bb40b38b4120d35b09e978e3f21e8138462410c326d4593ff328eef0b67137f372924c71a642666e8d6c7bcdeb18176e5ea279a8d7bc7e2de6ceb83083aa713bde7030cb31cca83f83665e04647ec6631e90eeb7afd0ab368a4a6fa3c8bfd69b9b208998fee6ad7cdc9e52c43d343e2b7de5beeb7adbb29ff1ead665decc0cdd4cf79907b9ef381fdd73d9e28c898ffa5a7df8dbc60477db37ae5f2fc775377fb769db6b703b3dfc158faec77ee32311c2c6a1799ce7318ec5dc9c3df7ad3dfc2c4391163e126b66840946b1e038f38c408ccab8ef47fd8c77243e5cf9b83ddc779fdd1eedb7245d9793601822f5aebb5948d73d4fecbc0c5bd87ef5320cb82d77336cd9af50832dcbd16c07ea65f5f2aba2f5ecb79b5da8f1db6f31a713fccdd3eefc2ce2d376a7e6f59c2e1288b35fa14ede0af5d99fbe5e197c74cf3deafad85efb2cbb4822ceec0fce6e0ffc7ed8c21a677b647eab3f63860b333366cc98e1d1cf4af565bed2e7b22cbb19e2da5e861477ab9ff92cfb1a5e6f40b6ea75f576bf09e12ae4bcecfb67b84a5da8b1c9788d6be94b5fc38b1b10fadc975ca871b99bc84526b2373d8f73dcc3af35e67b8e445ce37af5b2ee6b78b546eddf8a6054cc968f64db8eed396f957d97f1787685d4d8be765ec6715cee3ea37efbeaad3ad40af5fd32df7d7dce5b718ffa98cfbcd5e9ed675f65bcd4b6552ff5325e4dd5cc6ddb738ff2561beafb538f7a6b7ffb1ae3699ff2627ee5cda43c2d06e5c9004f57c8ea57ddd7f899cfdd1552e36736eb75bff2666a78339f79dd5b2feb4d488deee6263337dbb4ed884724e2ec22716c4309ac09eb0fdf48f64fb1d62a4ae501bff4b5e4d54e48295b21f6d27853aa9f6d6ff2b8bb7d7ccdeb29bde94f57156f4cda735ef6dc9b5cb0b7238d3726edf6946af7b5a4752e9cdebee6753727c10f79e2f6d27ee769dfdd476d092e7d73df516e2e9c6ec79b8ebb99892dddedf4ad952a8f92e7037ec97a27ea82fd936f3c4a3f6b12acbdbda5ef2fd19c04e7fe23910eb14bb0212a0ff826887de6955694529e689ade95b4a7268ee39ef332cd4df0f63909debc9ef9be65bf651a7dd373f44d4f4ddac5b6db33bf6eb908cef47b66dbb671a026d99163c0d8e72218bb997e73df83bde9b96bfa8e01675fbf1d7bb941a0095e75a6ef4ccf5d53fbb7dd9b99e04bc4e9a58e3d5d7afb21967510f7b6a374794cf8990ff8f3963c8857a65ff9b8d8c49e2d51b7a75f79401ff4e13b2e79f9c31a863f4d25ada4693717f91efaf135edb5dbfed5eea10fff74fa7af2b20a9fe8d7a89dbc06d2bdfd9277bab9082e55d5e312f7f2e310acbdfdb8eda8df7d8c77ba9989bd72f3e19876d7f431a51ae371f5e2ca83be0ff854f3aac903a27dcd9007fd69fa7ab3e91e91a5928f1e4cf001302feeeedeb50df6ad7910620d279401e2f852cacffec88633d99836bf62a68961d83542b33b03f63360d894917a0c43a876dd104a2803c4fef1b13fb2612cd29f5e9c7f8d6417890772130c679833cc4772c2f3ce00ffc826af1107c3c33ef6d682ec913e9f05bf0d53c8472046e23fbd8629e0e8829a24a043f718a59c13c3324a4bfe512ad15dde4ca394d11d4aad524a391ffacc4d5a18c2f70f2787841f3ddec0813b50d4b699c1ca145c7ab9054a165cfad209c3557c8927865b32188768290e15c5a1a11987e250f63568b08da314fffca97fdefed92d72cb9d9fe116eca85780d86112eec691a11658bef4f6a48704627a73fba77dd98d4798946a8306b604553318d2b6adca703fd5fc53f506e5b7bb175092a8e76834bd83fb5337b04fffc3c154985723088bfdbc99f6883a4d55f87495746f6f4b2e36f76b02bd820c1070fc20bd02f522bea3e0c5f2f817ff42b1f187449d8e415e9166c8e67e0d7905c1b4927743a757a08ec10d48f6f2d634c1e62638cb6872608564d96d9b1e4651d5793bb17ae9e75f5954c1a67f6c2ad53719cd943e9f4aa58770276e3b7947339db49d9d5ec171e95f88c173e2973e01ad43e34de96d13a552a9542addf0aff4d0abf793a7496347ed665aa2115e161e9bea4308e3f6a3093679d2bf4ea202de6067bc7400d4ada3d34d5c83de0f65613f3d21de6261df5e66821b89c4f3573ea00b4f29c4b2b039e52502c3bc8e37f1a64b8c3addf166d88addb171b401ac804f98e8934e13db13bf33ee221d8630c49006dcb73d24b2850ef767f92b1f262caf912b300ece5d06ceb1210bacabc0426323b00cdc6d44766f2c50ed8533cd6b074838ab6854dac9f60a2aff3aa8de48c10e58c01becc45db443165f149c35ec7d053eb1f0e59d510bc0c58d1e0a40040bbfb5da36392bc231c26d488e4038027928f0f78490d2c60f16c3cfaa9fa803595a8df30a8e39d8c679fecca9ddf9439b5a193f3e1d22a9a492528993c373b2a7b452fa254c353b7fe68fcd8ab227f03d258000d0c5ca79058531ec7c41f8f13dc720a04f02277e4ae5396129009ed8f70fca1f2fc31ffb36b039fec420cff1279e13dfdf15853c67e58de1ff60f8ffe339fefd8a51081a57d81efdaf69349988afa5accd9b70b08fbb0847881a7c7181e784dde9d18f43a343759fc63fcf47bc4a1370de91b2fd5f08ebb9ecbcaf9ac2e66f35bf1ff03b6e780eec07ecdf63becb2fdaf9f58d63c9e2c252088c8b0984e5f4a1819d3f77b600f0c4629fa7f7640a24dc2f2c493970bfb02881bd28ea74f424cff9e239d8536aad4af59f7dc99330c6f82c60eed1344deef14449e341b192c6e873ba43b1be333d27fbb983f30c4df24ee6e520b2133774d8c05624e227bd817d9c77200b6cb6d8230f2a9511a4607f9a96299bf96d039bba0afe190faa3f4d0a435e09c8a6704bf1830d70c0f862849f1d0ca8c7173b3a3a0b3684fd69686cbadbc6a87245a11f8233b0425f7441cc2dc1660c0828eac496c4801a03c2802612306582c568c0fed15f13a971fc7847d88c8393031ba24540571c1afa420bf64f55e9621176e872211f7215fcbd1b6c76210b7d0862e853a80c611f5a41c83f77a1298dc387a20f6c8e4371c8e3908d2245684a169b5bb168b6aa0f95f9363c0bfbb611b28408c75fc5c07a44fc359fc8c23b44deffc1c691cd2aa3e789371907e70b2bd83f878a46472aeae0dcffc1661cac81f7c17616341ae8151a0f11e239f05a213635a968a9c0b8b0bf121ff9363c8b3e0621ccd8c4300cc3ee4ce22a804912755a9c183653087443f5b1e16790abe02fbde97215fceb0d6c76232ec95aac1575e43bd21637b253c8735e5f68f11c0f8a32c5b93061734cc2fed5b760376a1cfe52e6f862e1169cdd08c98de4c55aa61cd86c53d95114b66dca46f3a1225f95020c6cee97e5c2661ba5b0d94ee1fd1042082184dd72f6ecd9b33b1e51c28521e7c3a3961747d83f55e408f491574629a38c52ce38b18a6118065d49112fc93dd247b08beb7ceba936212da24256db7047b0340cce30c8837e9e508d0642894285f5f716ce306805bfbf0a2f84b03fcf8e0bfbebf0e207b3dc85c0b9afc8ff6c3a1fd82d7ed34714c926a97a00e65b7ed33c55fcf80d069c80f19c24f8e0238e6790b874c2400d40b5f2c40a352712c28d31d0b2e2397d8597271870f2c573561870f225eaa0be7bd95ff2eb8eac8b78a20e8e6f363552d8fc7f6505406c38be6f3e6a188c79c4255fb00e143086cd4712c2fd42e27271bf9088f0c72094e765e9674b64e1e72560b07fe45183b1db8d63debc2409e725449888bfe21005fc3a0d540034c2a25aa9c23305431d104ac110429d4fde07f0d44461db891b965094420bb500c718a54c410a9e7640eba85a075509f46bb0bc9988bf70bcb4461296c61b252c8da276d47126e8c0e5d55852f033c452d2647c91c31616930d08b6a0f809623181b003b56d4cc0840c8e581546814511ab8d78a084114b053c60e16209c065049722569d5ddc40882156c54cb04412ab662ae04110ab9b8002c688d542b0c16788d55f04e1082256ad5af4025084a41b92584a04d022a208442c260bf029c20fabb1f0c20623d6f7050a767f1bcfa1c1fed45ff3af2d27e5552202564361042d7e586de3d1871a1920060f492c252a5811c6118b09aab5c5ae8448251e049630462c29fd8bb025bc42861118575094167c253d7a83a10130a4b67130411404ce1667095f46081d32a1b60d2d515a8247248636efe0ece8f01f9e03b1a79023855ec133d3f7a75cb31ff4e4767568b7fdc6e10374f88d27a015d0381e4feb04ec65af5083fbb71da58f1efd92e7a3e1675007bc3ba723acebf09b1d1a50b025d4b6f96edadd6472ffee99be898ffb73e933897dfaac61449f4253e9a6fc939a855f189ba9dfc08fa988fdaa1a47b4c266faddf0bfb468e2a07ae907ce340e7fb8b1007deb4ef9d7051715f42256b547d7a9ef697ed3ad00c8650c9c1d6741f8b0858ba3219c298e9716a17295ab5ce54fe3153aec57173dc0ac975b64caed11f6039d8a4aa148e44352e89314abe1353affef31f9ec43ef51a9f9f69c9a87b0a606974a5a95534c2e29bd42728a7f9e9442fec1cb43c2d2974a39353fdf8f681d2ba7749caa79a5506e304afc9b9fc4bf195f06c1d02b34d6817d53f3b34b566a3ea7bce6b367993e64e3fd08147eba3fbd816d024f14bae0599ace05db76d8a8f16abeb71d36fed53cf4e4973c16e04b1ef03188829721aeb936fed53c8fd7ac6116b96e1c352fb51a221f721b5658d3ffc0ade636163ff38691c34ed6c6dbf879e239415127c99c70e298b88753382821b5ee39350fe1eae1b683e6874005603fdf850c748ee9e767ea7db618d5f0516ff2627ff0318f0686ff1d84355e8e2f74d212c6e3317daac18d855a9a621a1212f2a12a442f37f22d6ec5b3b8162f9a9e93b10fe2dfec998209492121cc83b8cbc67536de8643b75274c58750f81a358ff6d8781920aef9209e93e10fcfa1475a2ad745f1cf6a5306c9201b33b0335ef5331f5f0a094921cfc15ec7fbf9b268d69f2fab78ce7605cfcf320b9e443d1febac149242728a1c62c1b124a2c9a7f1d987e8cb8b04d2fbc33f1b354f1040838d5d3cc7c6cf8f4851e73f3718e9aa79d37ccf931b0268b05906f504f16ffe11887ff4c8284151c75d320837f42af3a14729b1e9e62518fb98e4dffcf7e217bf99d9e7f6dcf322cfadf837df5d13859b29ae71570d2f435ce3b18d4786b11bc348b1f0572ff333de87b46cf19c159793ed52cfe334980fcda743f2ae543ff3b9c1dc0008d702f73e047f760b1c1288e5cdda97bed478c653d1f088f89fe1c9789e0c30c6cb5ae643783eca93211b1267d723eafc9ddca659f8df377de502c8416d1b79b2fe4de95311b083cd90c8debe90c8b30f3de939ab7879c4b76f79649915a845eb582f30169180319965df798d136fb0a7168b5f33af6b11cdf4321eec9bf8292fc683561a75ca2ea47a94274f39822296619909c7bfcce413630cb55608f405679969ca2eaf2c528be422bb74af2b451c07adbd48208fcc73b02dba544aab86f2a015ffb0c77995ca62367c605ffbcc7f8eecc582fc27eac43cf60ec573a2788efcec841d798e7dd4634018f3208c611fe36547d4b60c83b3f7417a82b39f5070f676fbd104a3ecad5de7446cf0ca08c4a74722717c231d8ea887017591407cba19da7bc3bf18fcc39e03fe61db860027367b286ce5348c728d655f3b9e7c3c27d3bab6297af705ffb0a71a3d71dfafa32f3843220989b85849ca6d74c57330ac888655cc84758fa5fc9b10a848d4517552c47060c0bef4190da1b044cbaaba5ff58924f19cf8f57dd4afb5d65ac3e0fa4d1fd6d7d115dc5e70bfb2d8822bdce6170c3f460d7a595ec1b008e248875863187d2638bb32a9be464fa9571657f0fc1dcf51f1788e7ffd1e5555359aaa5458efe15fedbe81945ef93d6afdb6a9b707b6b0252e300327607051c51554848912813862055bbad8628b11c48041104198c11170f02148881daaf051c1ccc28814203184215cc9d283186300318f3065052a3f4f4822cc183208c2420339ace04b0f2508eaa10a96021370f92268e04cb1e822bfd287d269d432460361c4a8ff0c4db089b5d05af0bcab161cf3386e8241cf72e79c73ce3c21c5f36146fbaee4cb246cc00afb3766d89c73ce09348e7e991c6c8c3d5b7ad1b7146d79d11d5e5b68c1bd4516dc2d01937c93d79b04a8cc62fc58a2b494655966929b04fc8bab52896634e062bcaa69b6b5afdad7faa79b93606fab45f0f6f6330ff59d1773aa7faa9f699bb6755e6e125304f7ccd3e87e86c6cf68cf3df184c6411a35be964a5f4fbf51ede6ed2bd7695fbdda6d0fe16b5f3da871a7aafd8909e65ef37c701f03bfbb99d6705caddc37d6b6ef3cce9ba1098ef99819dcc77c6f5a0ff68dbf4298bfc678272fd73fc9a03a542a95abed6eae1f83656ca5b1b57e198665d8845de68427e88261199c451815a51a9e0076d12ab53f60aa2382e1cdf32b0438ede38cf7d4335ed75d8d4b693210aa64361a3366d7fd065f06de4c24055393d31ece7ad2b4874422707af8dc77deeaf4f5bb6fd7b4d5e9725fbf3e470477b0a630f7f0747bb6ef52b8bb4452d86e3eb6db2a1a57ce9832a914d4b40e7615c22e057f42a85d6ec26e42082184282f13c1288a632c8e89f91a73baa70ff51c17f3a76fcad9989f93a6f0bc99480ae56522a98efb69396ea6344efbe9715caac3f3bbc97199480a6b29f88021449a0834006207a926020d80a08208462919341428385ec16928500e10a1409161a0a86cae58e9bfa3809e9ed669321675b279a1fbc8c5a44cf6a22e774f73ce79bbdae1a8a2a15aea53aa97b1aa4f69dfbda6c99c7ed6ee352436b47a35cd7efdd879b9ebba17509db742fde93b4a354d5ba16efdd39fbe3ef7272fbff0029e9afdaebb446cb8db23a379446cba4bc4065b1999542ad5a53aad6adfa53aadaafe55afa2e9ba17e87ff5476783e21ef539aa5da5ba99ce90b9a993bdf99ffb19af370834e11ede6c83b99b89d8745df7bd751de7e5176c1e431a0c6930f7f6db7354d86bff50a9d4ccc39f79eba1663e26e6a2ec678feaba1770ccc7c8e7bc8c7acdcb2f74977be8698ff24e27cfb166d3a3bd041a732fd8f080386b3df51df7d6a3b14d2662038548f53b48e0e265b383042ea4a0368bcddff78551a1e70fdba1cc44d052e7b7077fe51f83707c611eaf3202e1f6a3f8ddb862db8e9ed3ddfdca7ee9d0fd219c4d6210c4ad1939b09926486e2d10a778800a683ca1d7dc10ac651b9a54d6f0b77f98b3f67f088637737f02623f88d6533ffec7496562a6bdfd55894ff1e5576dc710863fc385e1cb7cad70d6c80186cd45f0f63602333ef5aab740e055f5d4b75f3dece373430061ee51588668e1f9337cc4fcecde7e05f290831ce42007b99f0fb98ff172fd917aed2377737cb1f36bb511e87ebef6a7b7945abbeaae8fd3d56e1e82e773f5b31e10a7b2fd989be38be5b8db937aedf6a09e7b1eafdef4b2bd443e9c7a5444cd78f8dccd7d258d7b2462998733785cc6cb4cf0b4d23f0e73af453c2390ebdbe7aef4efe625986adbb6c3f44022de2260aff48ffb7ab59be11f893be66b0fad7fde839dd087faa64fc2404c587beb0dc1311fb71da6b6c11c4d4e61eedb5f7a2af5b6dbe9db3d4f4c7d90edff69a0642490d46dffe03545c0f4d56463eccb08944c9ec5e202171ce1fa36428ff3f210cc7d47934c8cea3feaccd030c9a86e7eccd1b83350375b999b8ab9f99f279e3c2410c73ce765d467eff6ebd7efcd879579ce47cabe7c6e0886da43eff4315ef78e4f5e465d5a737dedf49c37c487c675cf133b2f33c1f56a57fe87b7979b29fee4264343b0840800220b128edf48208e3e64acb0f99f86d34082111536f70bfe8e0e1e8feeaf84a3ce6df1bbd83d957aebedae92be4d58a62bb59b8fc4a01804c4bb441e2250e4e19b51a21882168c833b8a214809426afb90d5316f9f55a800ee4d5b3ee2700b6c6bc184bbede7c642f7f4e186e439d86f2fb71d5b103679361fe79c318495dc4ff9dbb7a4bf3ddc1e6e011500f1b67ddc766c4d7694baae7b16fcbbcdabcf794db0c435cb3cacfa54a27e663fc76664b1b4c78f209e331fbe0e16f59aeb344b7a2688e49f8ccf04671e12962859706309a20b5e006e2c41bc009f1e7af9886f9edc76188165e0550bf1edb310dfcaac04fb5b8ffbdebc0976ef8863eec22d600a5a15ac3d4fdcfc88ac127fde8629cc9743fe491f700b9882fcb943b5b63d932acdbe09ae52cb3e635a9db89a30186b0eff6edc10e0868773d338fc336f003005ff8a6d3c20748f374e4f320cfb52c55a0bf1b3cf10630fe3bde11f8df76b1c3c7c45b388aa695aadb53ed5a6cc5efb1a3dcd93d9c758327daeaf51fab546198e64b8745be832307d235d06ceb2c46e8271082c3070cdd77cb09f0f1f43081bba3c1b9e05855e76c8c6095df1ca30f11109b1d80c5d2f03c42e1d58f80cf879d67cfff7b781d3e61bba54345909be242fd8ff864dd9e4584db12f9b2d24427e5e6343a737a85654060e4e5216ec9f2388902e3aea9bde2524e47209b9a440178ecbe58a49415651d8243c858868ad120c12f2cf8d8ca04ba607d005849424c465250616167e4c7a127bbc7afc78c0d10fd5878af419b5ee1e416c606310608f30ba60ff1f3f640f56f3cfa6781e8025684a1ff578ede8c83255a881cd36017de34f593b8dc38da4c80115acc900230e6ca62d486db05118f44d5f4d1e863ffefdfc98624d0aac114fbc811f05fb13ec57608741a28e6d1fb48ee91d628161d185ad7ec1332c77c9a8a5fa968c116cc1a4cc08368ca01bd9435711ccb3e159548a24b1f1163902049b3ba9086c9e8f3aa55799262cd18bdd8e37f9db51a4099a8b0a55183e54a3a13bfe2561338d5b6cf7079f8c2a5d38fb910c024202fdd01f22ea47447e9421911ff99117ecef5ddc0b9574c88f2891fcf123223ff2a3272da5ab8bfa72c7e3c934efb8c7d71be1fc38f39ab08ab2ec2ae97893a3500c6ac28a40334dd878e54b62c1ae2c8a3744576cde21a23f06e5080494654747961865099b77e27b32409c77faeb5ec2365204f2276ca6389a3472c7be4082a14a10215d62908b0ceb4f630516c628d115831ac74c1376826142e3f0998414dd592e5bf18a85374b2224dc464141f3c8bf56bffc7bfcc05a3d5430ae42eb09d6679eb0f0711cccab83b0bfc4c971251e6d61f3e73f5bd5874abf0dcf2abde69f7b3c5e0f256ca6168a221838c00323f6b1deff82fc117cb0bf0dcf03b4dc20826a7ea38ab7fba879e2d314451f30700073ec042d4ac148c2fe5acb070a1582dda14f6c862df8b303ed1476b6eabbe969bd41a2e9be406fbb7fd65f94f0cc9dbb53bbb0721a1164084c0f2ed8ff07929730ad658b43a65716a424239b61bcc2e608b47df7a7ec43d14571910a84bd077424ec434343eefae2d50822253dc6c1acb77e1cc883a0f52050bc29e2b697ab8bcd146b911221e43f2fa129561c0a7c1b9e657a77a1a12c363798f8039bfd487beeb3bfb68fe1155f18c78289ade8f0667be1960c07c38144b6e1e6258937fe9a67823ff1860b9b29a65a12f58c09306058e9a29ce8f57062b7c4de1277b06048699982fec91dd8577545f6937039d0904b4a15a3e8452b6c7697bba45004fac1591205cd90416404042a8c3081135b8001f4431345c020c51006fb13f9b2a323862265d84cb514582f5d5aa6a0f4b420db02a42d43bcbcb0ff0b54a3e932c43f57c11f853036d3a8c5669ad48497216fad228c9708ec9b35b10d88ee8e2a15fc197498e7f3b90afe363c8b4a868fe3dfd71e4e7ffefae2dcc0f95c857e1eafd16b6bbd0461ff94a4f24694c1c1e6ef7e9fc5e68ff6f7e9b8b9af7cd8480b0fca947ec1fe5a9047f129290bbff2d830f66a1cfe364a80b9a20e46146fdca3640f92ed4921a2a82387a4101196539e0c2baf0c722914848786a43c9248f4775446b082fd69bea688a894ad7585452d11d10c0000003314000020140c094422c168241a536565fb14000c93a248745418894990c32084900100100208000400000088c8dccc00bcbdf5763e6c71bb01c0cee3087158de58af47fafc668d52d7008b50c11dcace6ef541765362c9b20775709e16b79d1e2c8ca5fd113d585aec72495f95a9b562d016689e6e6d922c58f60627a7e5170064bc88a92bc017595dde71fa0805c6b5fb5338e888b1ce8242a363b4dae59a0cfe25656837fa81860b6dd383116b0544ae81af24ce2a5bafdc4a8308f5741483cc46319365a504c6be0e5bc65d12d3daf2fef471af7f1d91f59de9ef45639e56ab34d38417e9494cb508995c9744f9449a6a3909af86e09a94982a0690e6e8c28aecbde3bebd5e8a9f54cf5e83dd01c61ec3403c0ffb3dd6b1f750f5ee416ece557ddd8b94243f0ce583c9acad7be96f3249ed9cde7e4707c222cec723d0bb89738586c6904206add0bd82ba833c64027d57388382da5eaa8340c150adb0b3b07b53699df40684ea7d299eabbe986c4f7d87a0c0d05cfd008b8dfd5d85f6025c6f6ceec522e21b48633100332bdbe1040cc59c693db8a589a7dde0c756affd0bc1a6fe293560497f05b6f2870427302c2298b9c9dff58a90c1563f85691b21146af1fb6b6c2f7efd8b5ffc321fdbef62171035ff6cc51c694ac1af619b8a96c0c9d84000fe920376504d4acee79413ba024f8d9cb34fbcf2da288c33154ff8e28d18176376dd4c91a0c3e4969c85ceb9476e2574164263d7395f3a9fa3a7686eb5a79cada1dfdb82fffc84143d670e47677262eaff857ff6532badcede4f8f3ebc0f8aff899d6f4b46d11c8a819d0b96ed2a7898ad8b98a6f6965b06fe988f04efaa17a1520d020ef7cd69d983ee35621a4ae0d0f997742e366d3ec9c6179193ee51c54aff24ad0479472076105a47773dce41488748fb927472b8eeb82ceb988796236ab76dd152ce500cf95dd060a41556d6e03ff9323729cca2f563c671790b4c4335e868ba18d520c042d23214509e5718dd5e9a6a6b566be52c1852624b4e9a721df7cf094106ed7919aff9f1f071f06bc886188a6388081bdeec4f752ebeb2879cff93dc511e1fc92ad38a3511a483f971918cde9ce732846bc7cfb927631b868a5571f61539dbb0e13f71470ae4ccf9b0769b06edf3311b561bd0f337c9982c39a77680f6fd7483b10eaf55a387369f519d6ea5769b0fa9a5b6b0216d737e565404e89a7a8625a9343e25f753b87941f2bf64f38148b0bc512cafd94440e136ff4c6901e8a8ee9e1fa149503e02fb40d9b1476046497a64fea740bf5c9bffb48fa9756d1e32328ccf4af0be8cae66c24fe16e3ec865fc28df801d155298633e82815a526e2f779ffa687a0ee25a9f2f15c9ac3c0e7a65266f3467a6292e638f1ed0745895ba109b088a6074749acffad589c30ac95b854add954e3dd31e384c874ce359f09ea159a19feac963e71c660c392a1873510eea586f5504ccbb0b53fa2c2ab43d3cfd85a7aaeeab5785cf7715c6534b1c23af9b221e53910aa53b475a07810bb32476b1e6001b5817cb5c9b17c719a6736118962623323c10e32a486a24fb9cffd444c3397fca3a52cd70f0bea66e38cd39b1fbb6e22acfabcc9ba169e7e45f97be0e9711d0337f89ef6340cd72ab1e21e29e81a0feeda5064feb16dfb9bd6b128c405ac79fa01c6955df03f79cfea36f52f7079ee5b4d1ec9d50f149f1c0f6dd86658f832d60e088d234e3860a66423b3b9be9cb60d3d3a2451371c8bcbd32bde333731b619d906a8d341f009362dab908ec4980607b93d3a94001305c7d0bd5822e959bbcd2868fb30ff3b0dc88b74dc559a8f328fec37a1f39bbd06da88a34b31fb030b1e5cffe26e22af74b5d1e45a689c9811d4c58aec15de935c61d40463f52c8bf9b1330fd0eac0cdcb096a69c0766b745a810a546f79e91cb3ec8fcdebc8985e2f7bd05cfd34ffa1b62dd312df30fdaebddefe3d495d032dfc4496398498b61ae80bc23e89e1fcb58aee1052ad20d5aa1cbd0993abb70fb21fbb735605ade496bc58b80d1963aa41803105e44a49e782b48602fb8a26697874799b3c4a71bcd2988d8187643241c341a6166f07156f5b41303f713d488e69abe29b2d413bf940df1e7f56c14a7959dacb56df8c9b85dfd5e4b14cee8cf8a565d3f9fa11d3bb9457a3a782f12227d3ecec7b87b706c9135bad7415c47a48bfc682326992b439349301585ac1023033cbaf14586a7dffa9d1d17bdb975cd7517aa8c37410cde23c208f28783a0972997a22f00fea8051609f5ad58e1d8b47ad8ddfe1c64ecd3a6a119e75e2a1004b24f15a613991bf90411eb3461a34ee642dd4a4b9ed255e4ba05c64ae9c8368c2ab8c2c39bc6c44bca8aade4cce4235c2391de27b35db5d113c27251a382e4336fe64804d8ad5b13f161233d29a034d2257cea0ea8664e0d4b7662530a2d4a582a7de90239e8486faf5dd8453ef9911ba36eaad8631a5131319769c4b0473e57bb92252b6490c1cac908bb23adc8a9e830e91abf3a24576d7ebe5d8213d8ff942ac1c5c074a675942782b2ddaa1cd08605b3952af210c2069706d119893499d79bcdf9018dc59d72754d0e398e86f3015909e70d13423543ef2782ac923817c9059df621323f33ea39e7e5b3c4c1f614dceb990ca6ba30722ad6a7e183dff41f72ada08cb49426c0531f8c37a211b13d1b5ce424212267ab333a4fd7c9819b40bcda9d15516624df0a46ad4ce98e0e5eb5c8dc636fd0d2d58ca86c3ed73cae0d307d72b8d703922c848148d41040c6e4e7a1f48192f34f5ae45f79215c30df061682a07c3fe089f2b3b6754cf4635c5d0cc6c70501098278e8de5d29c8f0c6381827bdca12bbf22d84217eff3f7404ba81ae8afdcb818c64d9bcdcadd521281e284adbf2cfd9904696189b8e11a370e9c2fcdec7b08287d4a26ebb8f0b349121f2b9165ab0602a2a0ddd4bea3ba918c5381042bbba75d0cab849d66b1851677f2d80ec9ddb9d3ea4980451ca5baf77e003fb8a0177d2a4505e439f5cafd53ff5dd2073e1150bbf84366d79bde689588fee9ac52ce67a67446aadf09bc379cb106d1ad60b902956fbcdf3537e9309dab6be41b135e86034955f0a108bc753fdb584fbe2dc2fda70029cada1a8dea300b986ab35f26a03ca6853829c84cc864ca023c3d26752c1a2db0ad89a52ad9e3932a3f0a7bcfb5faf5c7b9a7772b708dbc86b2f6a66e626975a836d85f676f12e4c2227ed68311f29d812191224f0517526739bbd30cbef7de5570093dded20bfa76daef648e194abccde1b8f0eeb6ca48371f62f25929023190d0981f7dfa946767405ac0cd840bef0dff841cbfec4fbee1913b65405b751e6c103957595c9b2024437de8685632aefd073825c6127d4bfabebb3fd24dd3337302a9e1d7e48a7968fefa6b015bea15f37216868eccb46c37faa5b790bcad875b6c98d3dbd9fea891a711a50f77f2399c0d73ec528490bc1894b0eef3582d5f9f12c76f80e10ace64b4fb189b09d1f3f8b397d0ba0dcdad2f84c257ebe2313d3b7e9b9d2e98cb80ab06d8a60738f2ac258a52747b5fd0050167f5efb745223e512fa089e6d2736205c34939f8592a478e94feeddae146de36c96159713b85f8fe6eb75611f3a376884071362c27414d16bce21c536c7b2d54e2cb5d5c94401091f497d088d88202bf9409590b3f67545145e7816932e7e7afe262efa42c874d4c136b94fd46ec2d565ca8acea621ed3843e4d59bc831c4d1d554254fcd417ea6d0224f7ae40fc02d4fa587f37bfc605eccd990f8d1f80e95a4f83a15f4d466f92c87b1372befcf7ff9bc278038f2aa00aef575bd28045660ed57fa98d93bada5a14562397179cbbb048769f7bf888d8a8b7fc6266551d8c396b35b82a09c8514edb8854076b8b7c82f29a46b59812da87560ea2b934f9cb062b0390be579df51869a11aed68f06cf40fefb0d1e9db875a9edf413178213c8e717a8967780e5fad8be9c27926ad3438e85bf8d98780fbdb35937a0b571d0b755f010c483c97a16fa67730cff49e55b38bd615560da418c0c73fde0cd06657b1b78e2b86ac45efe18ac892bfe01d0b2829039f75b80f66c172ed913235c221dd7b2ce73546a9adbad89667b466603168f529acb08d2b6b12bef714af6437797072a58277e402ea2c9eb7a64c4ae5912053ecde75f5497957eb98f90a0fde91251e4dcc12f089d4366610ceb8178baa6ed38ee129f83955949ff37ea7dae43456ae2d875a9b26a37ee2f69a4c97c3fd42873c5ab026d9dac78af3337a77ea0b9a094913b0ebb9c840379795e061cd88b5ad0229745c40b9cd824967801d4b75f53b1c6574b57fddee2d9209c9646391f643c939b48fc4c2ed03753fae15da873183239cc943d60dd7e61e1446f4b66b653b7cff29f481b0398b82c434e919aab04f2fd21f67c097f71fc3651b5cf772545614f3860ef8251b76da66e435a68c1a913b511d39a9c2f141d4c9f44dade3c4f0cfd2c4bba2443248d45bf242836440dace00c92c106c5597cede299364e5248cda8a02c2179dfe03365ece914bb80090b7e3367a80c1b9452014cf7dc5a1171cc0af17df33f618557aeb01ef14a7d529cf190da28fddd1270d7f1bc4773cfe665b1f1d9020c4a35e32c55df1d934b98c8879ac9d51943632a67b0b84c77791f813833f906deabee4894c1793655dda5fdb72ff2778aa0828ffad5c6b2d03eab0210fc18f385bd319d0b8a4df5d6af1907a2030e497c9e104110740d0423410c5c126983222aee559ca093b5063894843abf799b46d943b2018431edf113a6e6b9d47c30aab91e2760ef7be455040c9596e9637a9d061dc1bef7ac06a4bf5c0c05155a5bc5532863557ba890bdd350f3b81406a042de67f8b320cfd50d4a1f6e1c478ec2ddfe7e48f3e1f222d4ca2313b09b39b4cc57256056d8e760bee7cab961b9f4ca7fcb20148cd7dc0e39a87e6169ccfab6b10537702ecd7e60c503edaf671d5796319db236c3b9432b92b621053912bf51fae699800d6045c8a5c0569c5664c6959a02cd172dc55e5e0a7c29fcdb450d10658528b28e9089972695584dee4c89f1602616bc765c57aae40662de3f6a216e8218678c9c6f90718cbfb40625b2f377c262e862a71af1afb431405ffee10f17d790b14c686fde64bca4455eb755a360353a81c1c3f3700b7c40bcea213a0a2aadad071fe16a37ad5e8aa28295b4aeaac4891f26fb2d1f2266da1f73181df6ad8cd76c2d89512e12cd2a498dfb0539f8146072f58376138006177472f901a3a5e885b5da22c7798e0f82b50fc3ecf53486e84f3a499d3444211220ddf027e64418f8b1d603227ab351e564a486f436cfc6e5f4ca98c73670dacea3042df1a217343a6d97d8693a6b115b9a7810af3b4432b8b167804a005135895d1bbace946ba8bbc199f6b0dc2c333ecd2e054db3b4bccb5f0a380a776c05ca1dfd8bcf55984a9785581c2bfd0ea79666325038ad436b5a627dac2281defd6a352ea7bf1848033b59653e9986eeb4b173e72edaf015fe94d1406bed8af9b3185a865c34005460c67463b42ba4143181da0d584219a0fb6d0c2b44d3a469deb7e3e45793663c837ab29514723eed0c88d734ed1f3960a04536a3b023b5021db3f3b05acc76b3194242a157414c37920258153cc9aa3cb1a9209224ae19f7d222015d37313c76662a8ded9bbfcdd564ded0c693d2b7f637ec601481ab1bd8a30158782528c3ee190db110d5244089ea11d5b34e3fb196a644ec97acf61598ff7e57dbae3ad27c79be08f85e0b4add6e2d845863c3a4e5e361ce03f3f60b32d08d97d19efb6f12aa4b6608aa2bde3353591ea0e855429ce829d50d9fe7dd02c5d4544472976b9db3b89073241d899236e1c7f021f2a31f5b1bb927d6097b502c62ddbe9f438f32302f572774ecdcf25a5e62bb7f72762781c8e28f32d056642de8fe2168be17e77b96c26bb44da5088487794177ec8fd1e3fb03ae797705db034ddaa6c421e090be3f82f805f5a721520e54922cfc4584c2da675761f0b26c800dce8528ef89ab60d8e6c962edc2e8ad3b455051ac350795f55b49e896243569540e38620f58ae9318c82513425a4e5a0b00c880c8df865bdcdabec578c65d31a7766013a319adcd5e08a7c393744c04124ec55506e093e8a10bc81a1022c500660c2b6e11372a852aaa75eec54f694ca101136df74a591fd3076d63eb7068186711e3fa62100f8cd583044dff7717b4382a46fa60ec84ebd6de0e5ab58759a5dae8e6dd82be47991b72e33195b76a49ce5549be04329ce0a5edb4f4a441f481a1fb6630befcddb44646bc7ceeb49e0c1051c04fbf9d1afa108e789b22ab61284549c6d3b43fc8abdb64d5ebdfd64b1e2239513ffbe136deea5e41cb046ce5bcceb09d949d60cb224562673e000e60f28f86ff0f64a4eddcce9b1a63e58a0d2dcb24f89df13202f5baadf62964d6fa3a6bd092b9b516efc9c2361d403355beb7a7e7606420f968f991fb279cf536cdab4310324fe01685f5cddf0882e10a6b5d463668a61ab986bf8d5cd9876a11f6f290dea7f2980ce94485ab250dabe997c02a473e399000cfabe4ae5eff783f6d281e4f57e909983c250cd3d1722b0d6c915d326b4ad57744ab6fa07adb81b578de5fb8d3cbaefaa4525a8e84a9900f2ee7a4ac2f2fea36137964de3ef2a524a30aea830f4680ac29618ec860567873e82c30bb54cda869e676758b27a360bfc99bba472b505dacae2a0c2b882413f8d737fc882b57f46e09950adec77578cbfc8c4a9facc280644d60b5d49be8ffe70479d610f50422f813aff62050c56b53f3dd860dc6ee6d37aca382b673f09aacb9a195ff80ccf6fabf93b6ea79212a0dc37c4c7db5cf95978d429977b152adf80ca9956d07cf80fd6fd3a69762ae4fd39d382e540e837d5d019ce8299178db971b7dbadf2b5a39fa342607eee133fd1ed9dd0414efe3c47acadee50ba6205f69ef82524c4cbe793cd73bfc6628b010a9344d626f85076b62e41afa88b7811101f6bc02d89171c79145ed83bc87328ae8886ef772a31fa4270d487c97f6290dce582056b4e3da0b60e666a9f9251ae843591c79cf7f4d519e76baf6b7171a90326ded473e6465785bb40eaec41834773adfc90ce8ae00d76ab1da3f16433b84799c26d1b9090e65ec19bc7dfae3d403b1a8f3592b02196102399580887b4117cf8a324fcc894e4fa4adbb83737a66e5157d0e130254fc2c0d673bc48b34fe46af4cfcd35f8ea6f8981a7afc32f3c592dd2a37cb879a89a1b5a54d112b29fa26e5ebc95d10bf443a691e3c1d91040fb3e5477f8a8559acd64c14d970bb17bf02b04184d426a068df17a5e31605307bdd85f1542510732b20ff8dcf39427563d9bd147c5b2e0acb2e8535a6ffeb11680671d62126e5baf9784993ad0f50d23f98aa56755585898e16d2623230f584acee1b1ce551b3e32e007a2124c4afca4970c48f646725aa770f7976c07d0a817a2d7d778fc4f5261374483b5cb6eb46ce340b68cbed4ad9ad300a86d5ae59da4132340a3514911f7deb56f4fcbfc88c940eae3893ddac04d0990c616f49d262b7bee75c2269121d9ff9bed49af63703d6238d4263f59163e2fdac8cf598e628f80b320517ed7ef93036e27b3526daa65530063bb707cb7eb5a018f28151b090f053804486272ac002764ca34e2a907dd091755b4823136a525b5c78e90f95b171cf8890524394912bcf068447ad868f0d4444d1474e4e3bcae03968dbd503c16797d0542aafcbdfc5201e26465560c2bdcebd33d0eff34536670327786342855c6c64e7d224eb00091a5fe8f9631c4f575754657f51eb2a4ef2ee780a0c2ffb13400af9484d3b86642649d00740a26fd0c0ad8d9c26f56f9b83bafa44f4fb7e0f6d93bd4b7d034f95636ca01566e60fd584c4fd6676afb592e8cc86f6c016c4b6d2c31ebb4d99a524dd063a7791de64f796854799a825ebeb537c10c91b49023c77594625d2d36d3e06365977e2863f29f8f1e6254879550f54a9b90a64650716bebadc514d06e3384380c9003b5290e42989d4f28ccb4bd733a0e5dd88e08edaf1aabb02aa09ad3ac24835b8043bc7c580bb9d061971e4fb334402ac542b17b54a641c47696988f3ebcf9c33980bb39c73d005b3b78619454ff35e1ca763443958441a1153820de26035086c0c012e787e797f40351738703850a22f1cc198a37ecb3a07e2b310ae52b57db03ee03fb27602d6530dcc1955b696b2ff0c27646af7e21fdcc14589c2f3caadfc98b273c9574b1931991549cff4dc5b657a51419e2b19135b07caba82a574c092313188015574fe605c3e05a275603f3ad7a0d50955e3e29df7ad8d91a28372297fa11a1f45249fd7e173d467f7fed9aa699621c1509875e2096c5ba815507c32d33a99ed598f5a14830fb4d4ab9fc1392cf97043e6ee82adc5a8eaa91af973b7dd19381a2fa81957b4f921144d9ddd6bd61cc4f18f432e932fff65d4088f85827cb1c5e2d34ba0267f52593aba9deb026229772971d928ca843aa407f027137dff4b13eba0ebedd147e23ba8a02c03eb65a3dada1bb84205450a7a9aa25d92ef5854d54e783fc2fc445dbd04c1fcb8bc73c85579c12bb271be3861fe027c39fe5b1cf080a4d79cc7c5eac2e896aca823fd8eb09e6b0704c1a086b54d9f463540e0191e000b74442d695a1799a4e3b955ebabca6b3034eca7b6c2b3bec138022a5b25c8011c6f5646c81c3fa844b9891597dc6539987ee13ba184238f0f2857348218eb79455fbdd88e500d5ddd53a9ba62c881504abe4cae3303daf32d9113bdd196a02077b24c919c2015beaab82960ec3b37578aa4de809a62bf3e8f7c63ba898bf0c9f3cc26914bfa0479830785cd17578ddf70306b4422bdc151047fa90386e8089386adb24b6b8d15572c7fbf01026c5af2de7eb0c2b5adb36c25b9c74dcdbb061b131e38e0bef894f90c3a36634e02851212fbbbf8ec840a06893e00d5fecd986cb7f6fd24c5c473fe7f5f66fe582c52e2b7f23935f19bd81dd0961016839db5f21991a58d70f893517121404f8a5622d4c0a074c7bf6f150d3b373626a8c88f27d5308e6d48b7594147a1f16f88f3c8f173a5c8e4390190d6cbeadac1e8cacf980becd5e9ba39d3568cd8381cecfab96dfa91b10b7b58bc89da82659afc7990b1e3e97f1e035e92449c97c5f6e7acbde920b652f89f785207e0b56b92c8db69d80064817bf5ba94a32227b959755ed526e7e05e98581bf23b9e3a8368b03508ca034e49005c802cd4490aeffa99db98a33bb15fbe8f836ef5752b14eb7078a1c43225c3d2fff9882b6895c4a3bab235804e6cde26d6bc41bcc6bf0cd72d33aabf709645b3829428d9a39512b1e76570af0822f8d8f878203b8346174a61638943c54c4a2ead1b40e4320211ab1d5bf0a0868346d582dac8986ca3b9011243c7f85a851a72cefc6d019c5a82893f5b84222c512f5763c73473ff3577a9274791eb64f101b1953e0c22ee1e30b918d815690c3836e779d011d1bce269ba653c4985b5b3e2ef479d2418b081064647d19b84aae48b889e39c943316fbfcf47ae349b2417dcc27eefc2be900cdf9719d693be3695664a53771fef8654bdcea05cc41004c378fa12a2cddf50ac98f43265e3e29328cd8a2222dedd8cb2d0a96563ba029b66468c7b8a56c1f02579af90519a0d2ba936922a254094ca8edd7ca4a7ea9f0807a074ad8dd9aa03098d469fb5df074d6da54fb92968572c024cb923006c87a22fd81fa59c499a3d0e773b4e68bebcc2455756380fe895e78b014f0545d3af69778e40ae15ddd4532fd3460808262ac9d30ea1994c422785cca3e629e9867ecc1855d80d799056ec73326366399e8656c693811c8ce653f1c07756c8ef1097d3b9496f1196e0a00eb7924743b2b8152a4953d11e42ad9abfd39499f4be64a833dd70daddab9d88e0ea1d46c50d83da768497574e74563eca8bf5777be0ac635f584cd65b9e9cdd064778df888e18d70f92a1574a904ba7209a61ec8e9ea2db350b2b764bbb78114e8c4c40d7828399a097ceda9615b2177c239981cc333a1eb5e75b14eee591eb486f87e41559658fbc9f3a83840a72f79dc19c5f30544c80212c36cd7c633c6064d527e3144907f87083da6c76fef47c49d85837bf7872fb5a3fb68ffce50649e11ff3632f2130de24fcd2029101c40a517b8de2ad59ac9dc7b5e85b514e8b47b281ee310278a5a4085633c5b16905ddea50fdbc8009913260e8065655320de44257f6bb86c6424acbbac0fe8839895d4fafaac62234d33125b83082864c40749d805fe81e70d4e9f6b67c1574e82d0ae40a8a829adfbe0100cf122ab347a56239a0052a5d1c1734141c1667fe9ec8817d2f6d7c62db5e3b1726eb44de3624e9d25907093d8080c56b1dc81858b6a36d8fe49cbba0291713b03cf99ee70030e00f3468eb2a1231d51f18abea950ded7b3a59c28714476b38e7f8507281315cced5d2130ed5ce20b5bd82004b3836f98b6e3172d971ffc5ea0b3276e6ca3bf135c9dcf49da0fa7336b68ba399708c3bb89836b104fb69aab8a9e3c431f21453c013b37a21d25ed2f3aade04b71e1133b36184bafe1d4c119642d280d81677c5867ac4d418fe2358ba8296ee4a8f0f2cfc9e9226823c2a5b36b1558fa19723d5f3723dd3cb2a57ac4cf185e93a7d91bfc4d4833c9b0cef64222965c9577134b3f5399c2e4bf5a6d3b74143bb20a04530371fa8612cc16e2c26a279553eb061504b860e838d85807a4fe685fb0b8ebf112014bee891d7786eed29ae0d6ce127cda968be7faab74a86761d727fbbbfe483630b54e1771a59fcfe7076762c00f27475f9d9bccd1456e65f64c9f7952f3b8cf5a61025961ab9c2f28f9322e95daf5de131c27f026755bee9f136230595c0e9302a85d1115ac6a8e785b4db2cc06b5e55282b81c720ccd3a5c6a4aa2a7c3661de3428b94782e36cf5e2dfee4b58777f38496da8ffa05483c22cc126d4c74af752cb35d22981a3147767685e515267b275320877ab5d69c66c8240274233c04771b59d4a9faf952ffd8b48a894e59087d8609285a738dd3faa752c2ac53a195dccddc5e6d4c9089507e2a2bcce59310c2b7ca15aad2e35b2f302936a0207ccf1da58143e4705601ee50c6919e430d6b039d4d79805614ee1db5ac9f2e5e0efc603a3e9b17fe90b60f11a781038b1eba91949c04bd49e4ac62d60a8fd374d287c649abe7780482399d05147a29113b7fe23bd5d3f9723fcd6adf25d5771480b111e7cdc95e4f538d7d6b66a95fd994256aa795bdd59e7f3d59990a36392c19baa6b0f77280f13c45eb0d73dcf1da213e1883256b7c71a00e8480605c0241f5e8065ce15200203d55bd87f360ad5b640e84aa34120614a0a5e456fa942aadab667848337d5cc9a117f10897d8cff7164300889796776de4267586dc7f647be27308af6aaa74147b31955c9ed5d6d528db089511ee643ee422b8c68ba8daa06c463f175a3639439fea3f1eb5a9422d6da4430b93bc9ccfd78c1868216c745cc6a865a148185d86a13040cf8f4029e99fe8511e69d52d974ced5d8737f24b5410529c3de2ef4221231098eb8fd4f42dc92ab85b027a300803647f805bd1838a707658a76f8955a7300dcd55539970dac5466345b9b697397dfe73dd8a9c33b6ddc8d7c5a0e74c6964fd7bfca9ecf1da92efeae0fc0c31f741c1ee1771a9ffd78f0c66d009e1e0e7260029fad0b242cb3417ddb607f20b204b8b8396f25249e18c6839e4e9a76d36fecbc7f813ade0ac4b68074a615c0c2d47d98bf12b1824e6187dd0a27bef1b0ba43e257624aed64c7ebb66f6539246cde441526fbcea52afa6485049cdb7901db46aabb57e6862af9bc93c51a4e104384a9e8f41c5b49dd40afbdda07daf023f8fd1dcc294bbbb709b8a42dfb07e46a6696e7f6cfcbff343aa54e471e2ebbac8a2144f74fc6b1e3cc56a71f2f34c48193d6c9fdb8d6b1f21c27ca74adfb502a81c0916ac79861090cf068b4a77cc07dafd7b65d18973d3b414b658d644a66ceeb914b1502de40dbc9e8d4fec7f214c11a8bc0c940fb79fb2b3bec90e0bd644d0b0510df7e7c26e759e8e867d657bdddc42a5b3b713154b64b94e0037a05f18e46afc587760a6c5bdd26b50d74253749ca59afbaf37c90bb74a37bf2ea46b37a9b0418f7bdead514d04fef50eb1159b99dc9e881869e071350f4fc21ab915a0da775ae5f61d4c7e2c5b3bd94cd5d39678b511b3c546af275cbf2691c692c7d48fc04ea632842c16549e632d3f907199d84f9caaad40bca46b809f3482870ffbea262fdac543f19d0fb176238dd863018a7a4ba2bb6b025eda03a8561e7b3f54ef0520ab683e32ed13c4e085d00845bc5580747a032c29db7e65d5e65dee425cbfa1a170810b5d1b0bd6b21ec90bb9f60c6b2e1653dc07df8b1205419eb7d174dc09d8b0a76111db1d50996385d3605e85885d6464ce1a0136eab729452b5adebae5addf0e1aa1a8bd61388ebf57a69f3a1d59fd4a2305cef3d814beb1dfc705656c003a0fba8b0ccb9e49e5bc2ce10dd2c0289b9509bc925e431229628ac0f26e2a95f59aaa7e188a2ab64b8a4114a8212c8caa7032220c07f5ea13a4e4870f92f2f35083443bacc68ee1c94b5f9f0ce784b5b2b6673be54a913ef889be364e1e2313464cab6738f7a797714894c3f48a1374212714a6ba2cef2251871ddae1abc1196146178b10a6eeb7fcd5a0186cd9475960133ec2e0e235c485ccf1672f6b4d0c9869979a50bd02574c77e36da2eb2c85cce22f3a4cd6eb00d1191c93b3c1c0af0be5965158e153c873ff8086b5a05a2d06a3a59502a2b98b7f7f12025e72fe85cc511f0396fef2053f9875a4752e429c7a11097edb0c56e0b0cf8eeca88ca796bb13311fc6cdf3a198697e5aabe7192e1f666cc86c08cfdaec702a506d8c46e03ee9f3513c9a8002f3786cb5b4fa83234bf2671c32b65745007c99c02a8827da804deaabc33ff29c39a25aaf6e7ce24510e7c0522cf222ecf793d018e19b4aa639e930b9df07879c1b9891306daf1eacd6b0eb9164df44d6e2e033de5e70173815c310626679f2b20bba7f03d0f1830856d970acc922a04f4147456881ca0116703379429663df745db1ec3c4bf370390bb00a9d8df5317f1f0dbf458629d0ae7ebd3c1632f39fde2ac412eda147d296acbf6f681de73b78c47ccd037090f51511208eb6d026e499af8196f6467ab17c0ede3c3f50b8e01f1d626d49e354c5e4d8ff9ee839061b92079a77b9d6f0372be558dc652c0f9e6a788591f64aa93abb59cbc014459c423250e06835f90e624b73f5f5d1a1e84c37852edb3c26c1f4abd664981f2d5852c9e377039bf01de7918687c8d901eb3a1ce1984db8f968303ee066b6a912c51c046fa7832df2b660ab588bba9737dd33d6c15a9bb5da75246c86520a8a0490148e078ce25f25b674cc05fdd1514ba073408252f414eb4d3c791845aec4424770b5a4513cac9d416a67be8e075b3dc5925ce50d663e6108c4eba29144adc2b08eba0becd5a3d2f5e36683e7aac4f453150863669c5072472bd230db33d06bcbe0e6a8eb602257c4f10cfe03e3f712c238125c587ab72bcab0cba1f21a2ece27431d3eb12c760eeddca04f41bc741179c3f211619fccd0ae50379275b4cbfc4ba6570262bdd8ba3face03a6e65d2fc6bd9c1fc4856a279de8a0c5e2ea58304b7af1b95a5ba6c2098a0debe67315acf99def2bd16adf079cc9532fe3f50be04123445ad2cf72e855eb36ee3ee3af5c958ea6d4436f397bf106727bb8dacaa0a0a044cb31f456a3eaa7e2c96800a3c59b6c3255ee8326545d956c9119c2a0a697168e06acf621abc8f4b81b49d40f25a1df010bbb1c423a37e0425cad5b2012ca5d4e3a0a33809535c4a730221ab583f478be52edc5f740adda94558c02a05cb0b56c88690e729bc549b94abcd6c2c8625ad95f31bcb22ecbcc88543bec00ab75aa7dcb1060bbb0577317bd8cdfd0a967d2654543e5eaa9f33aae3e901b2e4f1a97f7d374918f86ed083bd42c5a4b996d2739b7b038bdb46bcacaaf79bec7578ab39d64ad25f73f65773e859e2256f225d23fd74ee4ba350e12455b79330890dca9b7f42e8415bcf418991255b2df27434257b5f79831b7f2c19d930405c1f06b5e75a503e0e68398b1b0023e21f1923b34a75a1abc787cab4f2f71cef7d63915d4f5603d508159e12379ec711291f68b16399504dd2b1467a97397febe16770c0f2be84e1070bd7fe1e8e1e5ed91ad8d01ff3d9fda9380a4549d1ff03ee8e7199adaa900de48ba7e9716dbc5037dfe3dfcdf8287e58d41ba0b0974b342d201c991870db7aa1025987f13d1684998c2349877e37d0870fe0afc7187a0cfd23a91313c4e80ca78d029708da936e8b639735f06ff3dfa3dd4c0b88b836295347dbfe18db44f88f48083edcb1bf8d9b6e631c8c024199ec4a742276a570b12d7f6d9817a641605708c51d556f44f2410425b799f17c9d0e5598c89c75c86465e1a7c2fa137e45e7eead8a23058afcbe34ae1faa1797524ea3d5ba8cff0d807ddd6186280c5d452a60ba9286dafaac4d5462a6ce0accb9c7f2d9a2dff6f8e655d24213e46593ae40cbf59ba930203b0494d8590ceb027611ec73cb4af2da26f59d3158d51b921011e0513376b951babe06657128980117b3236b8a07e1b6339518c4bcf802621f2935e2541ea1510604ed5568a79db93029ed547322b7f491d814cc97b3dd98e957eb595f210c2a6291a01afd7f7322ca4547445fdbf605986ebdf5c1737dac4e0a070ba15efcddb901c24df73ee1cabaec7fdabbea8dbb7a21962ba70d042eb7eaec4d3d6a4b2c4ee8f17a12b7ae6057ae1d8b7f5bc74eec9501c25193300e1281f59df0099b98d44595c0d89e54c7ca284127fd416fabe89988564e4b669135d83f9ffc5c8b83049e2f0e8bbc813640e5953558df94f3dc627e1da26a1230f6caa37681bf48c951748b61e686c7fe2737a89680c6356ea4ab9fe392bcebd71d6439764467b3cb33758bdb38ed7e5dcd9d5ec9826a81febe0f5160696ee01bd105f2d693e5b3607a3e60b370fc8213065ff51080ab42c1bde746d3be6cf51ff385a9cc6b6a5ebc25eb4f82265ad6debfa04af85cc0433e6bd81dc9b841c618844604312de794c2c35a40b00d44f295f7fefc42d6245c853c9700dfccabaa12921e732e3bdc3316ae6d2288b5229ab0994ae45208d8106fc81be67429b16e51b58c2526a201e87b0c3b592b449df3c577d71f8a04539b6db1bfadd365e8e1ea1f857b310a7269f8ffca013ce0040e0a94402d721d2dcb7144842230d8ae5ba6096a0ef0e385442a06cc3a4fc189cb29ba7c73f01bcbe4578f2220fa8e9d8eaf2b3b30e44bd345fec1f4465cd4174d36df8286691f7de610c68036a1da277f4f4b6f0dbb9af096e28143741fab33a66dd42986d0325d7db58f8f3f1aec37532b5fe98f4f6712b05132edbd7a267ed2b61497fc5326fba562c135c5e3a7cf6e69b5c79d986fdeb5b5cc4dbd2f2feb682ffaf7b3db72c769a910c38a84479df4e6f44c7d965313fdfec9d38de5ab865afc6bc2f54d61391c793408189158dfa73e1326506638c269a3432c8a6a5e419617a7703406c8ecfd28090fa106b6fcd4aa0b694e2fbd71660f3c158f1ed1a71dc6308ce5e20e554460b5b6aa556be16f32fe045605dad9f9284a2bed96c0b38a5d4e3dbfe65669253d5ab09fe7c3f11499a8fa8946ecd4a614920d903daf0240cc858b286627860772bca0fe65cebf983a77b7e30b505f9f441a956f3a6dcdcff57d8185c2a02b625ac154d9a0c6bff61ae24d1e922c4356b5ee11a261034ab120f1c0d57b0e0ff17637de91264cbe05dd5bfba08aa52305499d3d066024c992e30ead93c16a2518dfa4021fb0642e0840e5b278d7b83130246640a831e05218332971a8f3a54f534eb50d421b7d53b055a6bd8278b46dfe7e5815cdeb6246eed85dd5fd7f696b988dd8e956345175dd20d87a952fa740653813bf5d6b61fe75d4f22cf02fe8b6f174f40e3fc6f5bffb2c390a69b8d8d4c2b57eceb9ff0cd485ef96ba1806728d714ecfaea3ae799b20a9005f3097fc71a5a35e225b8ac8732a79ccf701472f36e3f4e4eea47516d88cc64c1392908e19247c44fb364024b8d423a8f38c2fe59c47dfa238253a0e6d79301d9d776320b921936acfba6ca962596d9dd78b0988a802556666f5433b3e2f6eac21af4ca7913798aca6e28fc208aa14527d3073a77d12624deb7b20815ab75e1813c3519c1c5c37520a20b6aca1725d068671cb5ad166759d49905853a92e6c2490f2178feb5e8e7a215abdd4c4bffc239804416b105be934b40e1541934858416ea4cdddfa126f4038608496fe05cc14880c9686f4cd2e5c720282f44893c8b1ce3d65a78dc57b40bdaaaa452873b9cfdf65f872bc031a2c0c70191469a362e29b856639a90f17f131393ffeaed7f7d5e60bc82c8f7b1aee3fb798511eabb05e370cace1a987e43fda50243f5f5e7af26244a28e05f3d530fe25feb942c82d3d846b453a21f02f8003ea688a09eb4cda85f5db4a84b92a203adb2b91fcc1e838beeda0a61bd634bb4d7f3e07d81e70ae5ec0bc7e19f46d14da93fc3aac6270c213b42de1dfcc2214826946a151df09f67e61465d77b315040663a254059eaa2b2f7442217898c44e916ff6930ae8632d576d72680681391edc4aa1070d28a6984ffdb07fae5fe720a84e655682fccca14b3b95fe45e3534d2dfc9a8c33af0f85e9efbeea970d7ea6cd67615b3cbd1ae6fa268b3e944a1b257542308ecb256693482c425a72058f851080a90de600d140c17db9529c5f1ebf25624bcc4fdf21632702463ad80557089eb12bb31f885231372294c6b89be502b9fb67970b0e1342eec0f2e79a978e3ece3da45e3710375d4574a7d37b7ea180012f55c028287d3f196292b8f15cc730148c01b945c159079253081b0469f3f173969d10373e8fa3e1551be375b6c7130257dd0f0c64454faae33038fc283da2728131ddf19092ed4cca205bf64b58efde89a5f2588c80813b57c5eb62935436ab33317d14326d6d1a8ea568fdc2b6ed08ac46ac01a7c6947b5bf12d025d82b0d09a51d51b3706b8029b2db32204e8c14c5419f7277aa22177da15b9548ef6a1a1021c546c0a7ac5a55624793ba578bd21dc25a704f71dfbb7e00f0bd3a4d66f2052810cc522e9e6e2fd1fba9b82b60aee239697fb49c3c5d70552d7f5d19bea8bfabc0fc2b0ddfc9af698f200d9eb9c36c24743ffa456f09b23e9e5ed53aad1d7fd0580d173ec03e6ffc819e846c1b48e11ab6f5affda2359252d08c1d6e85e3b1ad4d54b987a3972609006169090f3a2ecd76e36025e1a63e586f9e7e3ac7056dee318a69c14d43dc3e8d10b3979b0a1ea2d5d295e1158f018553ae51bfdc0dce2773b4b5b40dccc626ecfa4315d5ceff2a04fc9bc5cd17155260dbbfb6aa4d8464d7290cc72a8b6dcbedff33af42ced74343d79ea8b0ccceb708278046f390b0e4de0174542c8b348f49e974dcc7fd668a3b60584681de095c4bb2459a0b4137e64a3c14254d453902b02a538db34632bfe1b01ce2aa901669e666a5a729a3418750525523195c1a880397813006ad911821c16b8a8036775d650b3b4171eeab7e2bb31cab766b05f649c881a2ed1e7c75c14f31b14128c4612a88809e212672cebd6e33722be749fc0802a2327d7cc5754ca6c4ae7da09a891c1f0fe58f9b454bd4e0a51af5e2ecef75f772b497423c4c649c12251070f9ca16cfa0738aa38968f569de57e21d23ca41e19f2575b819719b570ba228fd43bdef991de75303c8ba5293209598e9ff6b0f526ef6fa43060f12746dc9a07c8b2a5280506d2d499218a00488ce0e065ad8863dfed034bec077bfafc3161ae11163f774c9ec139cb2d5645518a76590f1bce60bbdac2d1cc4fda17e58ddb79968b07490841ff343cedf3ac1a30457350b99fdbc68e947c76eb6f11f1a6e0a5b5ae848e1bffe2dd4089d334657116ca14ffe2eb60d124ad10c009a9bb07005b3bf789ff688a22326909cefa80538a43198460683a9e4471b1a53bbd8866e349b387703335543677d9a4a54b02491062ca06643ad4a2f7f24b43dff7cf10b6143ad67cb1b94057efa53d0176317d56de1f6e281d8f69de842ac03dabc5548a7c980f8c8a21f6b3078814fee14aefba6045c5be1901993d17487fc98217755fbb946c6c411329b65a23f1beaa30c486814c43435bb9c359369b3f51b18f1a93f5dda68888037f42830fad7cd6950c6a76caa3eb0aa1553ec0154a844d5500273429f0f5099cf157084e086401591b370bbb965798bbedcf959deb54a07e91ed9a85943f7859369dedd6d94f3d6544386eebbd81189de3d5d7e804ecb2743fc9e2b1f55d390ecbf8923ef407b8891b187cad86a4859bbad945a5bd79d6a2c3dc5ea744dfea26af63501160e57cdb707b757c2b6f7aacad8fa76e36279837d4b118865a31125bf98e4772b2bbb74a397d1cd0ff53edfd81498facdab178efc1abe1d446f8d81074f6ed8e0e4011c36de9f15ea607049fe08e1a3d5e9edc7474ad2407c1d239ff4b0bc3678446e9122070ce4adeac8f87b5959189db3099b04c8eae02728c87591119273e30f7a02d5666093552ff32f35887b66daf533ab82c2988d4c1c6a5beea8a25a66d08982e842b471793713aa60b48e69c0a2a0e8e3d844e287e928afad1e0182897b98cd191355c60525231682082aec10d9edbf30243d076d89a3a9b10b9852f603b6d3bf65bddfc6dc7a9759f11715cca376b9d9d5e6fcc3b32da6b6e8e06ee65eca0102b82ff39f059c6891caab193a428729acd9d4bfc23b7c215c7358ca0f72994cad2a3636f244bd555399c8fc738cd689127fc465ae80fb0c14a23cffe05e490e4b2a671236d64b7f8bff6dc556664665174097238b61ba621725dfd58a6fa1c5b75df1d7ae3a10174c558603399f5c3ae3c0f5bf15a307d5c0cda1fee964db22b8f89768254c97efc8142d324820a856b1a00ab6aa4cea9bf90a75c5fd2f4e896c198a65b3b502722d5f5f333c066deeaf55da4814831026585a4bc27922c3f4b2b3c3ebad9fee018d5c454a7f491b09093edc15283e58d3f1f864030d00374f745f38ddfe4c28dca9943c9f5b9c3c368f8cfefd411a186a12862e4cc28f79b594c44b30f5b3dc42bfda773ee74147176acf1a2b1589a03243cda07a9508a02c9fa7d1b3b9b79c96b5c9f2d3f5ca7f88896a7f1f652eb43708d58a65b5b9db09456f17639dda832f0996f46c15450a8c404dad293f53a5b51b5201d72fd818bb8cd000d4da8c724eedbe4c84fa14f136676a679252d945d2c7e3f3349f7a94f9beea5afb334d341079e415b691620f70f04e0dd33a974800f6a81b68fec66b060fb7b80f2a6be16d0c484d78c2c88a129cb2caa3d7324d82d42a70328bf8262d72271a79dbdca2c1827292b4bf4c19cac9dd90b150d1179c4299c13a65226bf94e2a25cb5e0a44f2739f49c932539721a425806efac7923b20e85931eb52ebe109ff57261b7e026e0ed79eb54fba47504a94089da386ee0975c206cd5335497ded0352ee7b17aabefd6c059277c127397212d0a2619d6a3ac15409bf529a1bd7708ac51218b7ba7a4385c83bef7c2e2c74c0d823e220c930507d243a2b9a5c2bb944ce17ea5a0bac841d96af901769472de5ceb2dcea0230b7cdb19d76207f06815eaf8a6e9cb98ff004b23bd05db6ccebb87bcce47052c18104589be143e2523e7c3231874ab8ebe862ef4aa39405b87d512e4a113588aa315e72a3ca3ff692951a38e7532738c4f7de9f903bcf4b49ec12115caa2bbc765a706b32d9c3a7fda899315ac053a1b9f0882519ae659be9325bcbdcb0a5e8f781b88442d7cf0515fd7350887d2243e28e51352adac496b8d3f80a11d5de5f029b6c847d5929c985d20ba849ec6b29ff50af08b7f8936806281d2142383a45c1193efeb21f025b3581d5c3e2c2a025cb32e862812469bbe785cc8f4c9f5f33e0bf19827380f193fa5b4149fd2cd0c3da989d7130f4090fdb16813f460681c915ddec339f0ba91b75e50556fec47a8f3ff98d20132a371b23747458c783c76b3cbb991d5b0d65124b21711f927a5ad4ec04092a709b53c5057dc5d2996b50ec75a134e08ea11372f79ff577607c973cc4f8ae6a659d700b81d517a2e4c8ae86ae58e270cfd8730007945a36e2a961a9ad62e910a2f49302f72e37b5da2b96509ed24e34108def9699c60d764a048f4b15bd8c6dc5929832bbc255281f0d1b565b3437d8d5f79c38daa64e202f1e2486b6a225d84b6fc8cd10c6a1cfda57615c56b56493c513528e1db1f698336880c37dc6f28f003734293ec044363692ae938aacb95063b6e836df1a150ae99e4b620500ffc6f02d3c2c5ab76eca4e4f5f49be4b62b9d8429f7b119dbbe8f5eaf32378c6c974d08dfc816a3adebea48ef26c87f6a5a31ef4baa4ad2c5711bd842fc4f35b5b6bef928bc034772e29d7d12fd3b3b61cc39288ce02fa2aac3f5384d7795d09e161981bdd706ac5975169f5564fcde8730c8e9e333aac2f6b34c82a265896aa1f0cffa2c3922661c287dc70d38775a6102b0362580cf630bead344a7151c89a78b38b32e14e69e7e9225651d4d5e029c1e3ca3dcf644d57ba33a68bfeaa1a0774624230c57310576a5763e20461afe83d0aa7633b40abe05035e1227f91f23e20b2d2fb35738ee96a9b25cc1f2b4d5fe9e0b721c03c2658362cced80d794c73fe4987831793fe54fd9e8ceec69cc7f42d1ce401f9ff3ffc46a6b4a3def35dde3169f11afe2f511bd129bd3b98cb50a044d759978ccf24a5b2c36735f4a8d1ee949d3a7d2b2e6fa6e04c773cd9a9095188c777ba2faf365212be1a127917c8890e51b967bffb5d795ea2530742b83ebcaef03f0583b5a4b25dacaa5b0ec576bc8c8d130cb625a00fbdb9280863228a6175d163175dc7bfdad5c8e0f0d3bbda73b69623b29362c63783b428f3d7922a5199dde691332522cc5aef067f2f08f5a50081e761c4f2913036f95efd24994adbcbcfcc7b67f2e954c773e824d3d95f8aca04cf5e52bc671387778f56ceb445f671dd88f421e7c434b4977ab40ee5f5608f9b7ea08b23f2d870dc6266c3605194e09a9d6b9501d488008e1395ded04f2c8d0d744e23bfa87052621fedd6fbaaaca5ae1b386dcd2261468c7f76b2c289ea6e72743e3fb43f8019dbdf0b4504878d7b69528f44718ae4cc75d8038a4c2327acdeb3c6a6c59245c59c82f2120b46149093f23a21709d6c33e8c9df6fd25c110e7e4e248f7e96b1f914a6a53c431b0aee78db8ee06874dd6367473594005f6165249a87e8d5393cc1e1ea229a97170b82a5690513ded5b63712d0ce5d205d0024198f17a228d8f22bcec62a047a6fcbda1963d5d21832ed074457f62a30bd8f21aa1aad4284515117ff9607538eb51d51cc531d0fcf88ca9900bd28a07ef11551a114a2e691d76822e62a6c6274c8a57a33513f4f08d815fa6d6c4e69cb8c0fab17a8f8ddfe12c19a92aa13223213437ab2c1a8d60f4445a4017f2939598dd3a66ec29b695e89d79a5856594134095eca45f19823022a4c667b5791d28359c317c523090091f4f931db7c78421370ea8a7db22efe62cf1d571faad191899379ede566f02ca7d0c66215a9af4e8d8eaee682e4107545233283eb5fce62ce0149a554abaaf182ce649a4127df77911efb59e15b5ae5a98f481bb0ef1c99a95952def60ed376b73996903c459aaccae7f91011a30d6f7012136000dab754e0069367732339d2facc095341c57c88862ad0834fe7ac044dadd64adac246ff99569a120d11ade2f87666c8f801278075d0789038a7f70966c77d3e2eca2ddfb93524ba5b7b7fcfe082e6f3d6886b3b42137225509d7239e61520e98c0231562b6c1a48bfaa595b97eae369d9fb867b9d2b518b5ad89d95010aa1603afb5131a64f2c77524ef052659fb2d28398a995494ced22d049a22e76ea742391895acdd52ad57392c0b5295ea0f3188329614e748262de426f468b782a8be5fb653de30e49a986083863d93a004bce83179b28551d2d637f9ac90c28eeb637138b582491f55ebae4caf8fb76fc4614bfa141f62bb84b3e2ebef9aa8cd8b3479500e4b8c90ee94c49e8568d48f70a97f810b3f92a302e475bd530d5287bed51551d208922e93ae7dff3826546786d01ed0187a239ec9b8ad563622c4d849a7e5f74b7e1f2d1d13eecc1f5eab5ec6931d34ae1cf0649b4cf3086e85c48e6af65d3a2c97ce4b90c21c1048dbd9f76d47f3d77303a7d4d4228ed7329207655185cbd133a401dd27d6dd8936dae049bc6fb4a43a661700d94cd8ff9fd2a72a5a1037d5388662543abbf6855c2bcce3ff48a120208fa26491d12899d0c65882f715ee6e93f28bed2679a5aa7559b3ff172a82a96b1282f642d40fe5ed612e91680c93aad62000baf15bde39069cea3150173aef8acdd9c97784c6adcf93b5dfff574021eea110f85e88b446ac170a5293dd211ab2b12ed2aaec3114f309c2b4c13e883d91327ff45e8421ea22af943ddb9c214dc95a94a7624dc40faa378d1ec3c755e25c30329de3398ebe0327ee892ceff16b5adee16afc3806f8b6bd25739f23907521c06e90320bee22d3f754551f31555653323a9220070efa5242b183fb3365a7d8fdfbf8a95a3138cc71673b2432bb2b54ff07c12656c1bc10066a6a0120fd437e91ded602f30e13510dc192d657d4e6244189caeba892edda033d490732f8fd562c1f85dbe9310cd7df7b12cec8c21cdd4c9995de536055cabe1913b4d1d991e154b977a19512cdd7427b68c9d53bb59be4fca63a8b4d4fe9ea2714ca07cebbe733bf3550b1a9f62105ed08e7e1b329fb0a9c4cb7ff26a9470a29cbfb283543aef326aae261555569bc0efac348f9677f67df59455ac5755720b9d611d1b571db4b1086d59b8ca443fd1eda4197616bc8657e180ac710c3d8f21ba56cbecdbdfc4336755fc215f552fd1693e37cced71a8f242d3328456d0ad64f2ced4c33dcb08fe1dfb71fac591a99c151c8fc9017b175a21a04aea05ce380dcf14aab1ecf42e4487a57329b73f176c9b87ec17108fecd0480ede99343ca5caad7826f11d8903cf63b10a1b5c2b917ce04001eee1a7fe8aea78e1c32d0cee443b16e6d785111ea8813960469687e1eddc4d8b979be86295df22e6918045cf3e533e9f93a100ac582e3bab559e8bec866405f9d55369ed8768448232a148336d1253f6d3debffa81da1069eb1de59a8d955f8e00ea8168ea8658c16fd9ed04892e7af2f523cefff25de209e56e9c3b4c448e7e32afb5718778d306a9bcf61e6677aadfd604f15d433a567671d63f43019e94204a550f4b356273e5133f0aa468623bd8944236aab1c5e19c449aeee9c620bd057218a9c6cab1e1bf80d0e8a6ebe48a0eb35204d2e951bb9c752c71acc3259d5567c8f82babdeff387965e59caf39c86c53ef1f400480067edb79b70f388417f09ee989d306f35f8297908629f13459d2094d4c45f252fe6eb9212cb8d77960191d71191c03fdd7aad5d5aa7afa696e89c99d120179732aa8a3e6ac933abfae0606fa0e078f761c1f09f6b0cc64f0f8e167545bb22232e773fd268bf9695837f8ff3fc90e0741b4fa71a50cff68d9e460e851089ca1cf66640038782fd8c80738ec6dc1855f74d0cccabb4d84e5f2af83a98a44f2075759b5c23745d0e33b730cebb73ab9ed8c8c6e0bdeb4996982b13694d66d6bd341074e5b7e56a1466e559807ba23c1a2e2373b6540f3facc01aba690805205a8f11bfc74d4b2701688baabf495d5b87938f0baa45fbac3736f024880386b85009edba18285e07a8b46d7355d246a1bf846934a464a611e3a230512fff853999127e3ef347f88b0c563627502fc3de9b11a4a80e01e14fa6f1673c824eefc0b7d3f1aa1a8549365a63a95ce8bf166b8c8ad282a1d9a5b06f0132059517569a5a8a2f7b534f822c7315536b0a88a930a3137892a6a06c86d2522e013a4eac9832fa08ac24e462417e40a005208eb86b691d58f4c8bc82b1b75fc6d07b48f56854db1fe061ea5617dc1cdf3417179056abf928eef12563a60360d563c9daf969cb9737a525cd4d974592f16594a4375480c2eb41978b41e03fb48a88c0f2daecb9376ef900d4d47a7dbf7d1e3f7e917f889a3b2b4710e595d386b1e1a0680de56021a9487e3f9c613075d839febf48c5c3744ec44fe6552b11a6fd6fc485290145bd831a9497570557013b7067486df6ac1862cdca39fef15d500b7e28c2595923735bcf4e3ab95509bd45e52a4bc3b9ffc38d25d3e27854a3ca8e4e26a4866eed9c6e3604fba0db65a6c91029707e0067163fb52f72abf86deda90c7de18237fd17660ba92fc427806ed598d4a3359729750abf70645f8d3cdc5634d60eb338bc2281bd3710fbccab1247cd3eb7963c9c46820da15cf66e1fd5cefd9ff7f562f4a72aaa15abda7be8dfda8435662052e36ce1d1f4fef6c1111650267f682402ef3f9b97737c5af86db8c0c246ac0fc99c43995eb8cb5b4353079463af2a90b865c2041a111916ce0830b195f0a00874912f3b771f39631836bd29057b5ad4dc71ab517c396cad82e9d31d86a691e82afabab71340dfdab0b2677ae437ce823723902a3015b00edd6639b1d36f669c67934554ed04e7beacdb6372fcccea641ca77779ebebdd308ef4c764ab61f71b27caf467fdcadb2d36bee49f103c4df75571b4257c2cea459c15a2bd52f2c2bd7df629ae20c9ab853f25890be9dae831087d2161291cccc48d6bfd4143d19bad2922a20ac0316f29b277dac55017002327b050ba836131257ab0db76aad90d2a17183901b325c6cab9861d6b593aa447dd3ca07ad9e4658af4c51d782308b1f83e892b04c749ecfae959d68f12150cf309f90e8247eaa7234b68025fa40728422af00ce7d27ae78c646ddb0083eff09f72450b0cafac998427f618cd1715ff8c18ce3205cc47652c2c993568a40345f5eb72779b0004fd86eb2c1764525afe3d44d1e5ef3add97eb3ace00e9347afae4cd5f9511c33f807362e6f855098d3aa38bd3d26128dbd8df44bd80fd95ebc643f328a62e1a2f2421642a0c95a7eac4e85ebc41002fa5cfd2f7b40de7aac2c749dc7b65400625748c463add68c337ea116fe396e13ee1ab6ca65108bc8427dc1ee73983942f75538651245b4b6ec931413bb4c13a6bd514943fd65dbd27171183f6aa2127c8449369d30b6f87c8142f7be3fbcf1ead38ddf0c6157b0df3f852c1172663c8de5823db9b351429a6b176687428203e0315bfde28dec6596b15ed03be9c8b32fa4616dc8424a332aa680f9b53a1502df5ddb13bee822c83ae4fcc2f332e1d3a57fd18a5892b6d5b8639bb5f1813560d19240c3b4810faa1d8bcabeae02916d83b83474920a261d51bb09a8b871359647d173a61cecc81336c525edb5dc713e8d37046da3dc58687a93811c13bc4c0ebaedc5868ae89f85b59da4b02d751af3e7986556a75119e41753d10ba14a1116a127c62bb841a668642b763eaf92c03386436e1be91e3d10e6f83106a446e96a338e2ea698738bf6b8b34349e56c0dbd9d4994f4633ba2962f0cafa978140ad480dd24c8a521004c373f3797faeb90e1e9ca4b3e4b5c470a39ceff250c59248714e0d0d6e8381986fce4c510c3008725e6c1e6b04fa9dfcf572e5f1a120721472dc512c74d2ae082a4942fcb8dca9d6b3ee1764a0f3c4c212a8e30260f3d098575b04fae84a67f97e5fe89ea784a0279fe26f784848a2e4212b492bd6e24bd4cd5c14d0cc2a3616202fadbc1622e94bdd5d7c85f9b88c9a0e8ad273f0591c67ed80e626bb1e9135e137a0a49ecf77ff6cd310973c6500ff9bad5b5787b15e49179987fa88f077fd12e32e9c04cc217b587fbec7ec833d02a131e705868ae52eaa64d83aab95dd497c681b0d2c7e502d371e776cb27038fa7b094286daafbadb96429dea74f4d8e8f8e3df30e72dc6120509f172383d77d235c20373565d566be43eb966f8121fe23cfa3824e1ced9bb1091631be68f79f7a0670446479b462817c02eb3c32172d057557dc3172b12dd868099eebafcea2e6e738d213108a4faa0a7c30d39f5e9552e42477fc0704b9314b14cfdfdbfd6854d00c2d4b08cf33c52caea7c264433c68c41c6e80c51911c70e93f9c6d338f50d19965bbbb6e6a37167c0d70f2669d5d2feb413f7245f90ab387378d8bee0dc4a55fcb5760f235d0687b51b9e2a66cdb9a2b76520dadc3b58b101da6092bdefd940ede63791edcdd93bc71e82ba79c579e631e0760cba65f32417d977cd339660b4ad3698cba07bf385b0235fe192739f05167bc26f23b33cb891db00ab20dda7aae49d8eafb080829705033f324db6eeed74e327b98c736e8d91ee9c98ada6c686ac3e406771b287264a3d0e53095b835c723a4dade439d8e51682676ff8860b94499e1407dc1d523e8640b7751b2a0fd376cc50730cdb65551bd5f64dec28f7179538337346787c68e0ee50a240d563758714b06ec483e2486885d943d65ed65292434b69ad965271caf1f97d62380345602075bc3a41419cfebd0d88a681c4342b9b7753a3634fc19d561ec449598621227c032591e40ca818edcc5dfb3aa86450c4416c3c92a20f48bfd1421a9b208f56ca8cf8e58278c6077a3f10ba37413eb63783aaa089b217e755692f1b0d1e6d7bc4455153b60d3e8bb420968181183967b4b942fe0db86b1de6a7c1ac63dc9c4227a5ce1001950b6bd1c65cc8ad1e67e3d664a06bdbcf820e3b473cb09567a9d6f93bd24be0880165ecfbe206fcc5fa3e2affa5b682337a7c16c377e9c65500a7119f621220c68d46d55e57975a25b5ed45184d7c6e1cd95432db77667cf31cc57f6f2600494c1fe51be80184222db904ed2861a59e36fb1ace1ae02c068250ef9f4e51c2c235fb2f94b3b00f7f1802f962578723dcede3ef035980bccb6d38133c7936b2e3eb49ce18c9f2c0965ea153260e59136ab627c154b84c9403d45cc58e882e1cddc92add480a26ce86b8372848c3ac8aaa2e1c320220b52a8eb08eae20ae68aac994e2a88824a5eb791afe1447d443447c15852a16dac0f102f6ec6cc8832924983f4070cd22551e7ebee86646707b7e3b1d7fb08b816a7f77a68d9e28ff43d3264efe0497057676198446de33f15baecce729ccf3cbc77415a5d64af8d3a0dfac6d0c042ec1c811d7ec4e973049dc6a0eb9e0b04c91ad08fc7e0f4c08df76e33986c8da0c7a97eabb891fd996ff3f02e104b5dbdf9b7831c0326b8c48956b200d8b95ea7fb30d3f345a0447c39a3a5db56a664420fbd6e4896346f4888653f7d72e813cf8174b385feb65679c62f0f95d0fd3f88c4601165cc71f47d43f3b801a4ead5876e08903b2f523ac97733e25e5f5e74b757406165397a0045ee3b3eccf8513ad704a08c732c921aa775c7e36c469d20e97947026abd1778493790367dae22b9d70e04cc6ccb76b5faca3e5d6844e0bc50b7c2c910c53cc872cc5c010874379875a83cf0abfc92e2c4a57b644fa0ab02ee43c7e8cc328c8ef1cdc04bba8a29c0ead76d08963d5d089c0dc31aa4968d634f6dc803b7f7ca42fe10656ecbfef6f7f98faf7024a6d5f5049d3d16a97e23f366243806c58119d2b20188d0a9a209512ecde8fb4d6a1cc3b3c12ea4980a19eb3ccb7b53ad52209856cb5ccf01b73e420ec7e213f24e47bd9198f4509f9548ed496250749f49dad347e4043f6dce0b9735826d4a554fc043be7dd4d722b61aa64c7ecb3fca0eb59da5ebec9664b5c3dc4755d2647e4d41aaa9aebe9753804e38b5028096f964fed9a5c86c68c642a3b04a10df45d79cdc1a4641170e84b07d7067b07efd713c11c3c08bf401cb185ac46b4d4f50d69c5426f180f5ea342d7cc38e214d112c21a5b2affcaab8970480308cd3289c468b83a4f5b8ed822e2c2bdadb072aa5e8e3c1f399ca90d1de3f4cd9a00d474ac42e05295c43e51cf4320db89f78a8a59caef7f688d9a20f42d20065d9a19d3db3c9555da4680ae3562a7e2b612b7d7df47d5e447ba40e166d365a9a4b61cadbf99f16c3b75793b43751df1fd5547fe2fe44ee3824e85a54f30d0fc0f0fbaabd656a87a197625e1724737c18017109960a68f042148cf554615361fb46909140dd9ba5e0165279211b909a8d0eae444f9a099751d0cd60cdfe4b604c26a05850d247587165af630eb2688a9bb0e86a23aa7370408c4fe739bae2f1a63ac329eb81d076a8ea3f57c124d6355b44f35a2875cf390cf4f62a8bd1ddc14b825e4bb18a4fa236a949cd991efbd6651885f9b0caccf96c8e504be8cd53c722ab890870816744bd8ba37b25b59503a92ea64c13b296168c9d25de7104601d726e6559e9eb466d1777e1f79945b63eabf6ea511694d22d915b4313b9fdcb34ad9c2d9d5d450fcccbf82d1431bd991f9dfc05cd95d4fd877473e8b5614859ece1d79584ee30ded1e5676c76802679b69fd6cf9c9413feeeec8fff29c5b3dda9972977bd7c096cfc83ea76475790f04a3de4657ae229b7c225facc13ff83a6599aac01e25a8697d487febba0df9bff4339440462996045cff0245aa5c7d69b0dd04af41d0e238f92be64bfb4624970907077d2029330b864ea99315d9811858d58ab58a404bbf5485161a1612bc47e6a0676e451fbd209cfd024146d456f39139b50cb5f7df726be05da8b039bb246307542316fdea80fd80ceea02d0657ce2028021626f9abe05757bc020cd009a7a764ae70e82a917ad45cd24a193cb048c8d14be9445b63b7dd5f2fc084f309d1bd9b19d093e7b09b5cc927894594ab1a683e0d2ff510e7c414e30036c95d308c5a04f88036606ade109e013e48019612b30097e425c282368b5db048b031f201798095b0193f00be24099410b7002fe0638206686e358edf16bec1afd8d4ee3b7f16df4373a8dddc66ee3dfd869f48dbe46af1adb0dfa763665178128689b75b0dde187220af485a14dd085f09939e78cc17a9c3c7e66ce21635a4e93d39722d798395b0f13e3e7c89d330d967372fa9cb863c6d1324f18be47ee9861b60c13e677e2983246cb71d2fc19b8c6ccd162e6e4287d2e73206724d8f2a17f52f89a38c6ccd1629e18fee1e0486e091b4853770f65f1f97be0ce8c9375aa895121d573ef05cd9e76df3799ba14a82dfc5053b97d38009eacdad67af1c8c53e1d92883a5ffad380b420cbf229f56115f427e85bc3d2c87f7d1f293b507bbccd5667587ee23b807c1d60933f0325954c18f2ef11e725acc54a522370da8206f7435630a88d778e2b60aab653263f866d7211eb82031e018b8231098cdc8962bf5d82a539c7ddaf7494f2972a24d1c04eb3012101a0f3043e2959d42764987ee2f5d1e11afa4ed1dece78d48118c83b8991810f822692371e713fe66b6b2912e3e04a4fadeb90544590d3e7d18581f773e1e56f61641acc0573de12d6af6a0d78a59f4037767d25e33606496cdad5a83d215993fc27270dd6d22ba7927499afe0f3aa77af935be6fa7680965757c7cbc57c42cee0ae6c7833cc01d6b72eaaae5180aa3e852b02c973a85c017094b6d0f05bea797a0247fefa4cc50a5dce370471b0d9f6d465b6d002af78089e6aaba1dd5c56279b79c0357350f8a2eee06d2084140c4b1d805eb670f6d3bc43f1e54d8b391898aae102146e2d10e5e1cea833c895fff409fab2b184d9802e5cee2483544d2aa06975b483180a0f4fcb2928ee5ab0c1c775182f8ba15a882bed8c18f54483fac31f9bfd65d67c7d3c56f49194a88e742f9a92bb641c829a6608558975106ddbb6dda87a00ac68a70d12e5e061d2ee0674031d836b29db95eaf35676312dae63eab2f5d48c9ab1840e06969e395d80ab766927e3475ee314249585a80749d980c51c2e0f1d74d63b9e6835859f47ede59bb2c80814173d427d94d84741945cbb4f7d1a2a3bfd01fa4b512d40e1e36dd8c0c05554378ca76f78865f39458902a6f3071bb5fdbe7cd5f86e2511f5b77b2efe85eff72f5f65ac020091516dd31002f052b539222fa2f84127c6ad675d7b505b1621173952d648942805f969d7e4a3f8bd9cdf338b5f42f5f39a80e50345f3dc5504707b78ba7328379374be3c4344101f7b24e78ca78f6002977546776606c0e46db31b84757f95e10959184830e28ce5ab43250401e3b022f7340dda7db83bbd4d4e81456f6f82d1240da1cb35d077cc185d141b1c7ceb204581407cc6a52c813b618a04c7f806caa8df3d09b04a1d1ba57154867fe95318446e12aafb1b0d77cdc0236af234f7724a65454ab80c144f3775c92cc15006975936124b1c7cd40cd6c4ee3013195452dfa1341cb50228cfae44bd87812e15a0e514f5ca09eaef397dbcb84a35220d686f59b634be8810cc306c03170223c95054b99b8f33c1b14b20233cb3df430a868cb390c1ec34d3644fbd6bdfaa555697d7cb5cb24726dee2f5ada3c4da530ee15a693ed31aea1bf3a5e937adcfb1d68ee301ef9e76773c0674bad3d45a00adafc360214523581fecdf8464a40481cb22d541abd2e40b8ef0c6b2c64f2bd60ac3953522a148321ec7ff786a5cbdda9b39a791e09dce1fdcefbc08bd787c5aaaa79ca9602cd6e0f78b959163bef69198ac99e8cd7f9a8db0ac46df5df67960829408e2fd72ab370d786214cd223374d57cddaa898df01f7dcab64767004b9bc907a697cf090f10447c46b41506910d18ba78b4920dc3924dceff96b412bf23688150348a0d51cffd1d413b1aca1f142394e3afcb46b02a8682fdc88b15ac753d5b6f2163a9e5b1c492861478723700fab7aef7f9c3c7859627491a888feef4943067df6c42040a9ba8c93acdbde4bd03921b6278d39fa69e13dad6ecba0b7464c6b70ec34b93451b05f4c4600098e8b600b4681109a2c7340ebed12e62b301a936349fb7dbf7b0e4e7cd1ba284a71797a4dec830c731b37cb65adfc25e8fe8b27221b9debbc7084b026a1d9f53b0cdfa5fbc066d5d2d97311b27c8ae715085a4cd42080cee17c742163be37a9c00495978f9124df0075a2d8d77b3b575c96568a64a454c20c733bdaa3108d771c809e8d4a577d566f6e071f2e68ce086978f01af5eb88f80c990bd1154f98c607d6944bf6ca5984a8f4298491569d55942a62734836f07b17303723ceb98880cd87f3e0a82094cab9e73f7973ce217cff33510839860ea318856af9d58f16ad101c2a22d722e456f77c5b5dd27c5e22230a384496832e095297502587130b11645065f821443d336a74f310b4add2b779c18afe515f513abb14f1aef373d4f20532f612bce97183c56da0fddb77ece6313092d8fa1fc45bed5fdef6f0dd3b68b82024aaf760625dcff38a9a49c8c74d0cf8e1e574ec4e614a12a01905863b599398275b16f12e3d997c5049550196a893bb4a2ec5f5b6eff2bdea83b986d1e7a94145cc256979b5f956bcd9767498fa281c368248c8ec512a762d76aaef4ce388c4527d7c7b7fef2f5ca4de219be1f66189ec17409a0818efec1e3b1dd77c15a05bc63d0031041439487a46bd48e9d05a352734f3ba923d262d0af8a69ba2b4c1cc58055932742516311aba55c8b8196837e8622e1fabcf89002496e234019ed7deb3440a910209f8917e2ed152aaa86ed67618de87795fbfa3cc32e15aef20c1c46cd599cff0b030dabd2daef1df37ad186a75e2ce2addce2ae876876c3c0b823b30515f434243ce88baa3b9b881a669ab4a75630a774227a03a866ecd04f178be1122d2e99b5e00d5e0d343620347ad070e341209ea49d52914ce47111c809b802aacb43b88bfbb9a568211df44629f642ab2401623ba348d3ca60ed8e619005e1b9290b08e04b2ac7213aa7440a30e903847d6eecd39f7580eda3ee9dce0ec220c05fb2c51dd34725acc8ce054226c6426e942f7f53f24efc8b1a4605fb4e6b86cb4d6adcaa8bf53a68512b1814773b77557d8f6f32caa4370198910a00f99e9382967342e214d981edbdd03d83b91513fdf576152a2e7e70500c63f1ee95962dfded85fa3047526fd03398c5abcf3a3e1f02dcee7dd60d1d2d0884dd6631de69037414374dc5667e1180790e46449e10b637610e78e4600cfcfd6c7e1207300b0e1dd01c7a1923269269c866f8b6f075f3d5ac56904516f9ee9c5937a2002e66a0ff7c9c4c18bc9950c9a489974581e781d2779628435592d6f4b6b998259e910b67df459514a19f9445601155232449e94d723a6f9b92ab9d6727430c99c6282bc79944a43f2aa476efabe79332e9baff489d26676a4ef05f07964008361b60cfb10114efd31a910886d93d72ec3605762f722b55f26eb894e91db206d060ee4ea8e9238b974c465cea1c14a6fb06543629ee1165a34ce39bb1ebb617e96d80219105c3d35e3ddd246e2357a1c893e4f660cea2398632d567c28883d2e87d99baba04af7bf04542c24b2058ce767cc05232b48ab9d61eece5868b05533401dc2ec8bd7682de66ab711e89848bad04f56732803b2c3fd76080f299e7224e05b0a9fad8fc250c608b29be452ed8d8b6306556acf9f389a1270836a04413609a1d1ab6e1dd5d9d235057235390fad6faf8e21fa6fa9fabaff3fe86ae8e16ede74fbbaa32f73ad664d9c495ac98eea0e279ae266b9be1be102fd47c6f91570a91938ee7d3b926e76de3f3d7ac2ace80d4cb2d12a735769c2113368f28b44ffce9649faedaa6b98b7469a2fd3b5664608d66d6154ce85e4330bbcf3abf9d513c40d3d9da9801482019abb7fcef4e749ca616b0978a982d3e9c866bc8dd77f93f510422bbf4a5d47502761436d0d6e951822e396c3ea2f8e02b8c2b14d00bbae9f9cfb97c9d4492166458be0e3ca2e31a663b048bafc4cab50015039f67ffc3ebab48fd1ad68fc7106b4f36dd6f69e6b75264fc11c5b0c791de3ce77f9ba2b7239feaf4a87f7afc0bcced94842ff29d82192cf7daf49dcb93c2028b56ca886177fd293591807e18384558ddb900316b04ec3d623754250071fb8b6af2a4d87d18fedfdffb807cd408cc80f380030762e57a05d375b465a73f5fc798c68f54b4e809a4cc519618f7af1a468e126ba73d1a07906addcbfac2296f0d03de079189249d13c7377b9968a38ee2e0a2f6413a0c8df57327b2ba920f7595cafdb8fa79b58ec9a0ab4f5604d95ad1f697ff02e33c5f76e615fe588546fe0645ba6d1af5b6ca803184912f778b0db503c8e9b44ab8f98089b7294ef0ac01a60a4f1187a34fe4262909f561fde3d3ed1e6a5283c15b9964b198444d106e50998092d80a547d492e12f41a0c023fdc80ee111c946dc629081a3cda10a3b1e85c424ad107a4f6dcb99cd9811e6b225721990f41add00ea8b35b85387e09132f7e05e3a0ac49c9362a2a56b63d10f7c511b4c20250e9cde6d876114608e5d05eef5fef43caa203487dc81f6b805f97c80a0569e57b159e399600614a1ecb84c013930e49a7f4a9294f5c0b51e682cdb43676ce46f299b344ff2a8ec0b8dea68df35a3b10d5a5498a561a9f73f8679c7ad9fbf213321144d11ce971fdb9001581a8274a7003bb88259a7079903779b33dfc77dd5e2c4e247448261498158c4a6e2cb858a29cffdf2449b5f66584447d9a0381680f490accacac57bcf6c0ea0815357ef47bf7427a59d0de069675a8c7772d4ca9b6f6489b1efec5849c33ab7fc125cb50656e7ebecf740ba9ce542581c42a27bb853517d283e0160e445096a44577905f4b75056f4f86ee24e71e7a26fcd67309053f7a27cfd25d5e25cb7e152b2593e52db08390b96e867ab683cb6bdd64de1fe0f3d226486d5260e21c98a3c1fdecc460aa4589c9c7e2fe5c773c307d0a126c0ac11a4eb59e36214f7d9cf588b5da94fb963e3ac5e1690bfb9e794fa207e2a37bb1c92da3e0c611e02c73c9800c8abb6e3e96e9914e449d4ef6a1f531cb9c04b8bb0254dc6b99b33726e85d243dbe6afe96bddbb7364db8f90ddc2c55c56b7a8787cec8f99c2fb3016637cce6cd02d4c8edc45dab90e92470757516ab626ba967a81fd753c543e85630b70d7af61653abef4c880877201e7686d60c24773bc15c9a8fd5b4a2539bf3570ed1794069d31a044eaf39408a99bed0502aa6de4356809527399936eb60edc68b1be4921f5bbbd45c5ed17540886310de573e9024bcd290724386bf95602391dbf5c2a3b3c232d9d38a67422c80dd2a46f04b2baacd4b89c52986a90a5bb3a5e41740331f57e28f4acf81b60769c1e4302319c427dd1856f10704a8457ea09a50d7dad41ea1b7cbfbd428f3c7c2baffe07804800bcf8aa33785bcc42c8c910bb16217f466c237eeb8c9650d606240dd641329c2c4a641807a5cf4af5c070b261e4e3b3db17af687e2f946db01cef665837df1cb5ae982815b0e072ccc5eb6ea4820a553460be7344177a439c41d4eff30d84d8c69b360cf1dcc3c571b4b37b685ad6af7e7f30a7cdd4bedbac884bbd3b2d6eb4dc4d68779abbd387eba064596bba0c1765171b336d0e6c69a4be90223d0bb6f287b517b76b7e1ab65690b6e9cb6c2a71722ee3be095a78b3d4187e205c97e13b2f9212549ad7ff06a72ff5d0c7755feefe737097489ec6006b38a3414c8a8993f9f10bf5a5a0e9904edfc24584a06992e6de2c6738370cce0ee463df57aa4b77a59aff5ac9ed54fe137e5c6bd996ee8e6546481d5baf16fce1b74e3bdb6f7ee656f0aba21de6b1c017d88b85367f14f656a5b0bffda996dd180b98fd1d16854290a47847a04939bc1f9f6081828da7351104faf120eb2338274de14f873d7a7a969857b31957f1e323c82184b50b6abafc0b446889e2712db8579f42dbc752eec51e7a1bec5de6a4b7a27f678dceb3128d7b4a897e49e75b84352cf4e3d9374aba3be893def7c9f20fddde25ec7a05edd92f6b385b3eee562c585c2df2cdf2f41fed0d2abe7c2e2fab2e297e15907cbc50aa3f4e3d27d89c21f593eae1bf0acf657c5ab7a710dc14a736771e5cdaf90c060a5ce37dc5c518c66dec5952957538a577ba599cfd2cc4bb43206654af16aacd442b19a798b2bb9a8a614efed95373ea3999768259a942dc5bbb1727b60cd0e45375756db89b7f24a54fcbff2e8e8028d9f433f905edb8b0363fd5fffb90dddfe47466656dcf036f171a95ec15a377913e0e325eb8a5517b4dfeb54330b3c9e039aa3aa855b3052aa9a74ea14ae59b46ba470d6de23dbb6e9045638eb56919daa4b16edde55cadd2669b5a2c429a0621a8c1a841fc2100400529608e28a8c5c5e1ea20781e27fafd7eef55e12c6bc4fe8b1caae37b3b6d612a2d636217b6fb977e508e4071a08d3d5c93063a6f70428304134ade323c3ab90c4aa726154ac90fd26ee31c4ca02051e68c07c56a746a353a650a1d2f05a5792fd581347f922b613726c3134193e653493647590c28e0c9f2309f6abadec35b9d845c49f179b712d57aab7c34b21c37755ccc8d0c464f8ed03351226c3a334f003a10e0a199e7b7951c67824938ee24ffc9155bef803039213421d9ce10f6764bf0afbf1726e86af49d8afaa60049d3c8112447342863745f9a123c3bbd6b0e172d4ab65b75981ab11486402f6e8d33bd12cdbddc0cad938f1c795e1332e70b82a71e28fd59125fe1870f3848f8ee943391ec77aa932f48c92c68606f6822a00e00a226fb644596baae596ba6a3031352bc4d4acd082878367430cac5981082d980183e3f52b9b34934930482f47469c0d334a22986143110e36dc60030c830866942237ff2cacd79ea4b36389b2560abb59aa5a666a1f1d4c6c73de4c2980f6726d75777777b79431a3b3bb71f79d73c6ee8ec51e514a306c7c3f4208514db8bdadd37bbc72856d430c55518ecfa109413e44987ca802473ccaf111c9a7a5813a56ee5e72779883dcbdeb7e1022773f80e1451047b0c1858f4f8cdb5879dba8e46dbb0d236f3ea4206f3e4d70d00417b090b5c336d0c828ed912965824fa64cb041a66d84e00a0ebc9a1fb478811442190abde4243cc9f2564a964b4862038d2ce5490a1940b104a38d04d5b26125890c18174c36583093e784a922724ae8000e9450865d2204f2473a401cea92e59328813ab03f293e225ff211952f0db046fe88c421007cc93560f2d540470d24a5b5ad96ecb70e2c0cac819747f2104e2c7b0b14d8efc740a687e99b4803b38940e4696d8f1e666eb6daa3ad4ce7040206eac82f85dcba17038170b1c7bc95f1e6c09a590f03759460a58c929bff4e799e7042ab65ed5773e2ec0e869929868135b1f4ec26f172b64d579c2c5cd77ca90d3b7f8900b9d820cc566126550b2c0e7d13719ba065467b1ece0c4f3ce95c99613fd86bb5b6d592b03c3529365894bf24b0cab550a7b521ad8a7603ad8a5625571f6d48aba255f1628311484720df92675865fed9dbfb0f8349b0b6d24674a8854025a5437428d3a436a24374482bc106fb86cea6d995f436195ac928d7734a49a59452ca7e46339a6536349b369d4d2af31349da858c8c12d205ec66dfd992ba9c4497a3f6baeb4f292b97cd3eec322a3749adc8ee6e39a7fcbc72ba9e645795bcd3f2ba1c7541a6eb8926a59cb3bd98156d104688124883d50f5e6dfd633769ab924e94ecd47c581626d8278cd504ec8985524694230e803a992764e8c29ae8cd63aa0242424242a236dc0013e20398902a3f5202a56f909090909022ac616541ac0c3c071c5720f08aa214a1a13caf0cb1c168a9a252b56aba9d97e23830fd8a515830b231a2318ab245571e08a2427a91e50d07884068769a8e3d22cd545f1401348f4304d234d3b2c841ab2a14d4920cbc52d5aa66da81fda051a402eacca01ca240b1816ed92dbb65b76c198dae1a72c2e0c0e0ab6f61366258179113904498bf885d432ebd7bde943833d6dd46466b7777d3ae5bb7d464679dcdaa6514d367df289d31993541d5ea49fcd25efc8095313c0476ce395bad5ae984dddcc056fbe6dd314653de24d7a5bc4d4e526ec21a9ae54de3b2ca69336f25aecabc99b8d2c699386eeb60e84f53a96ea7086be067d10661a6076b20aedac4301bcdfe699aa6497a654728259bb3c8890decc0892644232072e287a8893432972191134300b52a154750031809694058102106900d8608030968074700418a15238080046486116140e9170c324018525a5fb4b42108815404243c1a08f10321bc60d2da322412628bb90358cb22592003322db0801416f0895181dd32240a228d9717b6cb902888a3208c0e932bb89665190f8ca094d23190fa05c748aa3efd823ffd82325158ae06444dc0c0082b55e4cb5b020bae341124234413506cab89272e2840c05a9adc618433a0384a810704bbca70e88c19c83439a307af32ce08e3c76e190e9d518433d238425249ea17f4f9017232001b3ca12f1f9200449524606087164970923d20609905329a65b406233489126589295714418a200769e4e00a1499cd3ab0840c5c800552e4600b1870f144e3828b2856605944404de45386444045e4cf7e8600a21184d08f1292f82162090330f1092288688827800c1044510928853434308411c4f8c072190ea521254a1a53764c61b30c87d2c8c11a6914e5c0b41ae3bd980426d7971a6918e1215c202221a55f5068a84a919101c038a23979280d339a20c1450f9080381a52034a9e191201c144fe9e63d6c46b85268d6a9a0788d08630541a011199107caa0a8aa05682b42b8698568a321a1021855045be604dd2c18f5012183f0308824f52108418c0109eb4981cb1321c4ae3072220903a0ed4564ece6896d11d32b02c19121921450b234600c508275e51ac29432223909899c2d60c898c18228527b6942191113e438a48aa7d038980e0402bc321352c906986444c9068b083ec8b21542a08c328096900d10408d90b8260d37082524a29f5a10645449c0f42d0807641240411911055e45586444240c9c0061be76bd958637422e67863582d43a21f1f3c614b39466984221aa29f2d887e84b2cd90e82708099d26c020065460618231b6b002762101318020c6154a88220650aae813642ea0598d28d410418449b135c321353a60a5e529cc73d639e9a42d3bc3f0cacf4b7b764398bb7be291a452ca86f204310a6a6db568b4c9df12aa840f980fa9048da22961e3db4a53ebc369b610c6186b8510c28cd65a82b07ea74bf7eb2157ac69a734f6a8a7771a654a9f4d082b841aaeb7f488187494db89510c3a3a12231e5589c10e9c32d4ca964a14231e9dec91ccc596b5167f47e8e930ebd262eded6157e4845b495aadc3c0e9dd5bdd3bddfe74b256d5a24552743b9db6abeec7ad4edf4eaadbedf6ba52e75028570a0f3992ed37fc1dc991db56aa956d657bc2495a79d5b276b3276b397bb2f69e77ef301175d90531ad5e254e8bea36a53aac3dfad445fd645d4e27a96ab5be242d7b249fbe42a14ef4747a94cac4fae9aeceba4356ad2399a6ae32e1242d4a8f504a69abd59242c3913c7f4f4669e960043a20410ebe324116ade4562283f84bc2e5c943f6194f5e6997b48361e2af475e6d6e938bf2ae3e6f5744c5624d6b97b4ecbcfac7d2e4ed3798b5c7169c211e8b1aebdca7655d6a3bcad5e567afaaf638777ade263a224a86441e18ca5ff421f2405046d939ef1019a1c6d98b8a1135c48b11e3af872c53af29db4b7872fd112526539b5a08cc8d75e0c94b622e9d967209efe4d4c07ef028d325ddf23e7d9276a1e5f44794f0b0b9b0d8b05fcd137a347449cb9123fb459fe8d2e28297b476a54a9c167c8408fb4558e4431bdb108158ad830748d954aaa1d6fa392b2cbd5edbd0b0255c85fd603089e3daaee5489caae10a68e7b473d8b51d265fa68a2319b0c6749369337da7ab15ebe1b767fae5c242261cd668d8e9d4116ce9dbe72b761160bedeb56123f334ccbb4c97788a02c0189379790ee7c897e93058637a16b604435ac24876d305c2f4cd74984dcf3293e9f14a1384d2643242c34ddba0098394601f83ba54a511cf8305b5621739d6b05f0705b9b8b05f0cb22252d12e98b029c3a69214b2aec0d22089bf28f412d4d2500029cd32186986a70eb209547b2624871cd8c6a15a39270b8311c2881f10a3917cc9d2e3c53da55f42edc94fb902cba47fe44b66b87ddab30200f892bf425a6e47ca1848338c0e2437f87a81a82fc5580f733d84b53edeac529afd19968f6cd8cf47218993235ff252ca8db2b0618d5a48ee0c657767a8a83d9823cb1af68b463627e7cb89460be897bcec97b4f25808b6d7b0972b38326219adc07eb625676715daa8264cc861613f2e77e9f51c9c0a308a9beab36f56577d536587dc94df917d6b3abdda7b755557a6306c0f6733645f222f12ed11ef40b3499b37562b1c93a53a0baa57f2f3f26ed9252b6a3ad4bcc34b86404fd62e85c0ace1782a6fbab2934175d3a5c49bfdbc29752d85e54d513b2755df698be52546a2ba09236149bc446615ebf5dd11c95818ce4b16d4ea73b58a744523356997a7ab29f134e16fd219e349c618a3f693fcbc277b59f1d7d3b7f6a68baacd5a5d1586556ada94f3b0a39485e55feaf5a6b7ee57b36bda670c167f3d64fb1bf47e4a3255f19055acc3c669b1f0973afdeaadab24abf08704667b61961d0c31b3e01d2646328964d7aeb14e57afa7f67e3c6493bce9daab6eaa3e753b6b37c99bba260cafd99fbe25d2528c7a4569d9e3915ccfdd8f875c67d681874c7db2e9d9e9ac37ddd2dd81568c645ec21bbd7a02deb8c2de6897da391b141081c01ca3ea281c2916d1135572e9b36bd7ab7a765195e5d46ae138b1a42a4b6618c95ccd194a574d24594eaedf2267fa2667e3c0588643eb829c2e0fbb19703cdedc48bd85e5b55e53ffc63c0ed527fe9278a8cf241eea15856754c9d093f56c62d7eaf328d5b3ba425d7595eaa2eab74e6567611b346c868ba48ec2486ebc9378291c6f1029ddc02d38585866df49eb1b3559e6e3a90b72fa9ca8393f3360513f1d09d667f774d6955d90d33728a5f4b2d56a7559575d5befa26ae16869c1d1ab54b7565917ee7476d865a5a350cf6cc8cdc27acbadc471b93cb1d81676d9e17ea96f6f8c2423523a0a6543669d85bf245e5f55cd70afaeaa73bd5f0a73a89f90d4a3bad6faac7ad945827ae7c6daed4e579851f7f452a705d990b598a9e6d26737a41a3520a7744bae971cf339ee4b75f984e4093d221460828a10a0e4d96f400e8e6ef96122216501e3d1e4c05e2cab8f40caf4ce6d6b1df6e7e4e453a491461a69a43021763ba158261c2d2d2d1c374c2cac154a754ae570ddd01423aba5595690f59de6b8b3dc387545b673f61c8d34d24823a5f6f4598e7e3b6d1cc77d5ea69c7d8a3e753f2179bbbd9f907cb2dc763f7a14b641c352ccb19c4b92f3519c2427af72b8961ba7e75a386e64f6593764ce701a2267e04ef614638ca79310976ef58d863de72454876c9556d1234eddbea45870b4b80ea5cb057fb505e3b8815930cbf330ea2f3939a7d3e92424c7c60606a3a1f13c1898974c5329fcb5562f2f17b645a55eae4afd2585ba769b3a9d8464d555f84b92934f9fa7f77e51084d5d6b734e54488ea739a79c534ea6393d14010911410e0c511221c892e505dd7769d71064e8eeb6e96e9a39a5940d9bedcdf92d838778d2b477f9c93cbbbba59415ce43082184b0b1b0f15edf05c3cc9d638edddd52ca150864a073ce49e3e1acad496e42fc13b249a79c3383c910692279341952da40e69473ce09e19c734ee9cd39672c452a71b43e73ce6932ad86a8539bb46693d60c0b4d8cacd6ac0d3a6bd6c69c342b514a33adce4cab6d64339b5ae50e4b39d59229a514cf9969b50d9ad579941dd92c7bbc966571ceda4696c11e95873933b9711c47b336b6d66446a306bd5a67d775259ab55192198d1995117758946a7d9c8f5aa4996cc3e462b29964367358b0df4c6c3dda68464a6fd9c41efdee121482d58ce4c4d2cc5a3768d846ce49b312a534d36a96d1c09bec309842f7ade3b49c1cac65da2cc5eea62cd8a314a7a6695d106d562a618fd852cbb21964ce88c879ad0b5232c98de3b8ada78cad96a6698f734ec945d3a4997c74c969c51efdd3a90b92fd3425ee6460e3b78823ac291d962e21ac29cd5caa9ac45940df007bf47510d0b2a1c8b4997c5c6c6cd94b86bff0a646afff132b371b6736246a398116db0c7e3a289c0c32ece0940c5b9e6d6db0f863b9521619ce3bb3ec5ad66d935ca9656545412c323c4b140875e0155d90599a32058532540520c3774d562e4c6929b2df64e9b490210bbc01c4494b0cfb554d27c7aaed08800c9fc30cfbd5b354605d707060c07593e1cb0a554c3fc86153e558c0bef999f4c7cca6f853653287f98e0bbb212fece1af872cf3ff5b22730a7fcc85d94b017f3d5906c73ce6dec3dc2f49fe51b81f0ff99f401267c624fe4e6f18dfcb7b7859f2c539173604fb4da01374c49e03c809bdaec7ae4b629a14a357fad52f2161a7933b86cdfe4da01a76dec265574300ee32e2c2f333e6891828238a50cc85f7c476e4cb85b9850b63aedb933f28d4ba9f975db838e251cb40c3e2a359dd8fa5fab4271348ca513cfab8fcc5a389c50368fe68527f60ca03a894dffa7ad2480b6d5c402816007c172e8cb57061acb5b0df4b3e19a2d003d703a45f58e2b90f757ae4fcfd55582925cc95dc3832347d03899c90c102bcf92a111368e43e0b5e041998729321511257f2869af09008102726c199675c02c4c185fd9a8ceda6331d8d3c3d2aed75dffee263589c21911256e4cdf428c4952ec5e50a4bcf4deed18af6e6e9277b6dd0b0d9b9974ea58e08fd40760de17014eac9aaabcd60061f108aad2e8cd13c69c8d917d608eaf4c82854de2e045ff35ba954aa37a7944da1de57ec65b5e74a61183aa7acf4913f4aacf0a489932c63f3a97b031068dc1b7ca4dc1b7cac189aae2954faa6bbe89b2c0bdbbdd59948891f64d8d1c0e43a3460fbaa734176dee83edf4e248eca9a7092770343c982f1178b2e5ff47da13cb5d0679b08590e91e74e9f2ebbec27eec6a1f66646e9bbcacc0a2091124ff2cc3f4646454748502091123e793e5e699c199b2c325857a816167633c8dc425c5724fa904df1ca7663924b8cafbf7081f5fa2f096c521c457fd13730cf2efa665af1a34a1256a41c8a55da9b309b88058c859cf626dea13312fbeef371a86fece72395be41f53773f7ee1bdc9a333d93c25dee54a8bd69cf5dfb93b53826a16ebcd2836c833a3023319dfbec21cfdce1ac339dbe9ba1c3a58b8496cec52f2ea1444edd3324f45c9472b3675be4b8c89db647a510d93e01a3bc3d0bb97604b0c66422c385c0a13cdfa44a864419a0424786444d8272ed23230b54f90132f1404a224a1bcdd9423e453f5434d013560bf58d8b0623edcd33c9f3f0eb22f2bc227f2ff98b4938cc17cd204b398b57fac5a47f7a3b71d95dd4549a79bead50c9f33d7718f9f8f8f8008990b022e94a9e9ffcc9f3891a59e44f2a416f2a6531a96fbe5894e75d70664279beae31b90d76a54b848abec97a32ddaced8264575dd9d1771d91e893296e9f7825265161a54fdf449f3c332b4d46dfc8f94eea9b2c0679beaf40ae0b925123f8ea8250a23272868d1660cac1a8e298c48124141fa9c2da2e86ce3ac4256425b0665e754b5ff0f0d0de844935f47970a804c10ba1580f2a2d84388a4d91447b16a970451f1f2aa21441e30fa7ba5b1a484231d8d1eff4eac258a4a26f287629311b294f3aa393f6e6a3933cfb531e8711c2f089b5d1eb0914d4c9269d51e66b03e3d1c06ce8945067c6676159edc5576d6641676c1b9d53ceee081f4307598cd0dded75c37477b794db0fdb766d64f2e7f23e578d5b6c9b9492b694b3bb2184d0e64e09d31ebc9452ce9b29e74d7777378b1933c6d8ddd9c60454d671db35a5a8a9b4954cdbc695b614544d2ab1a026163060177a3108c8b22cbb1a8dc0d4b030418ffef480d2cd4c3b64f938e98dd09b593725b69c34d36ac984ca3a6ecbc1d40d39caa6a8a9b4954cdbc699784add90996ab5d412947153c1958eb449b5504a0f4367162fa66cd8700ef98a93cd9291c5524bd544e965e9565abb212518fb5431a52c5a76ba32bdeede012b7f704a6b0651b4a1812a489841132bb17eeddb1f7253a2223090e5a7a4d99338ad28899092270df294b80cfbb5e2df3712a7c2e25b0bdb928d484404144ae1b4b48b4c7f8fa8064e64d99c19064174812c88886022cf4b0ba02053fa6eb528a5525079decce844885ad9204c865289f8eef21b0a36b0dde3eb86c21636c3b300e35ea49cf234f9949d7e3a9d8ef4cd119a4ef58f85b3faaabbb0bdee50da0c7f47724881adffea4dff2250ed2b9111488c3f59266529e31131b4296fb0a87d72761c72b0a1872f720f4639fb7a28ca59b7d3ed451fedddd50195d39d7d3be12fbba6e57acd27d767328ad4eb5d40e3a4b425fd2acd9c83c499b1aaa484848d4eee18361e0730723f8d1f2107e1d45bd256354dabb5d67af85a6bad75d62adf46d0d7d80a81e54037c9357badf3d5f45a7acd6a56b39a55ad6ab5e5663a91984b47b0116eb29eca6aba34d55a6bbdec82546bbaa9be845bedd563262c4ccdc0d0b22c05b046fb27a59c00f6d00e1b6a34b0466b6523ed69dfe917d92f067511500766ed3d05d499d71ead0415c52fa25133c9da95aca19135326ab536d33ad6ab430c30974e24e61aa98841b046bbe90856e62f0611a9efbeae7e4fee21f55ddf93af4bf3b2a6bd6a1046c3483a886eb141984f46d3112c15ed49813a11eab4f27c4ba9711e28fe54b15f0775901179be89140ee4393b0479ce22604d0e1bd8f8af6310ac99e768e267add606750e1227fecc39e747137fe6eb5733fc593a23eea22b869d8f41918ac681735a213527991af66bb5e47560fb72f6105bced6b8cb69ca696fe65285cdcc46515f7e1ac9043ca49f33c2181ff19c734a29613361d30eafd596f059649b854b1a47083d7d292332893fdae96dc0a16ff2b6c939bb66edb65cd23b3932c86e4913994c91357cdf896ac27e1d254e429327a641c80de050922349bcf6280d0f4f90c6dfe4c951f69671a5ef745f54910cc7d64aaf9f37498b7cf759fa2cdadd8dbf1b3fd21569ccca6296eab3342e229f1599d73eb34f9361f5794de2c8c5db1fcf0ca8f75177a1f010ab952c863cb95fe5b56f99bc0adba061255ed2a60c23493deb159e384bb57cd62d9bd5b1e3e4ba23ab2fb9ee2c4efcc943daada4edbb7fa4af927e92f6e83b3db26b181b84b19fcc9f39334a5bbe4f46c8cb1b833cfdb431c3fe30899bd5b2b5c4818e4a803929aab0cedbb9ca93ce3927b637d0c70b0ff207f3923f1a14963841a66f7d8c4c899cb8608923583c60bf7a030d00654a89342094e9bb05b1408a52831f2b404145d91414f06056cbe5d01c019b1865589eb9c670c6457c10d30ea14f64d84e296c1499cfcf1623955fcd0da5644d395bc258fb2666b03dd93489ad5619bbb9da77e609a314aadf44ba34e37433510961686c5e28945f6cf4924a29a59cb2b1946163d19523c8404198ad8b36f1944a7db04e75ab92f1b46948d8fa79170cac2aa652e7696197a7700bf3a45218d2ae95da5c6e4fac757b622e41628ed7aab575f26a0efc822d8cb52e8c9d7e52dd0f16a9345cbd7e49a011068a98f205ebdc65bdf6cde95bbc2c1dfe7aa06caad4bd9cfa25cedf72ed0ac75d375c5c386e6017015a7f790d2ecf812bd07297bfe00a4c61a4e52eaf80ebade85dbe4e77b956be4e6fb956be526fddd6679363927853a943a39cc22cf5f67eb08b7c8268a4bed35beaaa3ebb20aa14fe6656d9afaeebf4d5a395eb3adde52f6f5dd7e9ade7b8ebba4e775dc75baeeb8461e051866df9098f462f9f393e1fab481c1d9fad4f97d5a7eab3e5fafc093a72bcdc54aae59eee725bf7e3c9dde95d27bb225a0ad7f6ba4fd995ce42dde20f1a65fb6d83028bf2e9f5d6bbbc27d6faea56e2b854980626aeb7bc75971b69a9cee3c2aeee3d312117ccd3825ddd7c4f2c5691afd9c29607106c8ebffc84f3205ff3e799c288cb5d7739cf144674bc75285f2d98670a232d3f01f3b4dc8579a630e27a0be639825d304f118f10f99ad7818f84fec12328a70bb6f2650f632d2acde578e9c9b09c041659d4d5eae95b6cb53cde8a0c893cf0247f3143220f00e59e58cbbcebb6bcf59e584fcce5f6c4b6e65cb727d6727b622e37c65a2db885e3c9f4ee96be23bfea1a1291588dc3044c78518426a18c13e209e50d7cae0fd7cac9adcbe43241800f251f175a38fc76f9eac462219cbc850b633344171197eb30bb9eb9767cde35249b3962279c4a29a594524a4c23941c3972e4b8cc711d394eaf0e281ddf6989c216f69b4a344eec32c05c73d8ed38cd611784c7bb570c8cca9c1e7917c9eb3043188cb91f4da6df646ee797fb45a21f2b59c7656afa08befab5f6511fa150a878b4d91c7641fe83dbd1d65a9965be513222520c71f3ae87e311ac690e8af63428da13584393b2271914f40ad499aad3a45bbb6dcf41c57eda2aa35264bd7bf8f3be09e4e32426665d0c32cb7ca35b0e1bd8d3e351f7f2ed41b01f4d0282afbecd5b2d2929a4d0accbdc06aa3505e1514ac66ea35c03f50d8c3906b79356a2bd0934a5f44d6645df08411deffd7905d489691c9f4050a7f51f38fe1d078e7f15c7eb61c4a142f2d527cfe5e3f92ddc85ebf2811f50c3ce33761969c1c7835c88cb078e22860be02f3cbf2796b1eb0139a74236f807feeab1900bc3bdab09c19a7eccd5ae006279e1c2f018ee0b177235ef76197605e8580c587bf2c2d5a0c070352903fbd52a4faa6d5581817d92dbc763de9f494420548dd803ea7c22017500a09d51b54308849e71d8d5b07397911682e0d9c27b62d06524ff85bf8079048067c62ecf054304c0231b30108a452658098af5c4a6d88f266dde8579fb71e58f1f3e6e43711bca1be7eb5a46284ffda4f336b95f16eee30279ccd5b97763a645126702c9d71911084da26a4420da13ed45b4e88cdcdf00c0c2ed27b0a60fe43614edf575f2044aea6b4f2290ee40ecd11a4d6a0ec09a06c085f9476fa081daeb9e026b1abbbce7bb30f3a556f40afd88a9942dec179de0098499b03782e0a4f0afa270085bf986cbbf13f28df714a813f3be975957e15f0bb30efb887cf559ef22fac6c7bdb7cdfb50e7c76ffed9dc7d857f35775d8f7f9d4f873918763504e02e1862f69ecfb34d198a65ecc289793e0f2c0c1c14c5f25d784fcce64ed81eec5bbe3cf18735520014eba19e119be2df7e7d7c8b3f5a602294f3a54b10e113f37161daa38771fda211c80d5e017f35f7c03117079c041c913834fda277a187c1f4f42908d15f8c8f54c05fabf56da3dc0b7f31bfa4e7c93cc7635744c765300c33af8bc47bcc3bb286e63cbcdb93775c9867ee47e3c275d510801674f02314e371e13db06640149b42e29c980b329f919efcc743963bbc1999dfefb4f43c997fe6c2bce3fe97b95f4fbebfdf2287a5bc0f791e314efa464a1e972a7c02f9b8845d7eda5ce2efc72790c4d389777aefe27bcf3b4cde07cde58ecbd3ae08bd5764e63318c21a19f997779a5f7632d47ce6927aafe3d79534a7f7bcd72576edb8bccce975edf8cce90e3c44e6339f91c1de77a4773f99a3e0d57ca729cdf13d8ca4e6331809ecde775ae62e91d9bbbc5f0d7ec1fe7747e4de7bec86c03054012f91d9c7e93dfcd97c3ff01299799cfae0c1437a7f7d7b5def255f3352e6de5f3ce469cca5f45e9799c11f95afe9c48977893f27f2530999c71c097aa737862686493e8f6fb18361067f3d7966077ee11d352c9acfdc14bcef348ffbd5fc7afdf5172eec155ecf778a9ac3ae480d7efd15f37b1849cc656264be2369ee4e7ba7a7f9eb13eaecf8ebf599d35cd8deccebf51e3dfef1f83ff31d77c29a99d7ef7d7b5d24b07b19995318fe7ac8b0abf099fbd93c739afb29c934df71d83731f7b1037f4bf28ebf5e7321ac79e18f27bf7ce04f490c0ff975d8373f6e73087562eec3067f3ce41daff98f0bdbabc19f92fcf2813f9ebc037f3c4ef39ac75c086b6af0b724bff0c7e333df9134f757a1e630c37ae01d62e621e36124f432f732f432ff8efbc55c739afbf19067bccf5c9ad7dc1d34f7df71fb93b907bb22afcfdcbb1f0fd9fbeb4258e37de6e55d99ef38cc01a13ba8846d979435eb149140000000000315000020100a064462a140249808bbae7614000e7a9c466a509c0bc44112c328ca20640c308410020800c6001821a21901b02068fa44c7b603af965f4e0776b3e92439e6ee0df3f9043d0ba2916888a315e6157695466e2f4a16c7177c6a1cc7c508d3950d20e02481be892c2660f0d4ef419fdcc044cbfececdc2511a38f95a2efe2825a2e2ed5e42266650b9a123184c883a5985dc84eabce2305f065d9498a27bbbecd7322f50b349a2c92f2e591998dae67ebbb9fb5a98498346369818f17daa06af4ea3b9e2e3ee3d1b2f9240adec3664146f8bd6d5695ba63485d14ca530f552cb4c6fd15505d5be01c3bc03cfcb9d94e9788e729dc460dc8fa174560f28a237f4f44b96f716055e376589a1544f00059fc8abca1c349aba8ee5ec02610197c6f217aa1735a350b00aaec080e719a366ddabdaa7ae547d03a1419b5dbbe2c0c01dbe09070e1549ecf5f3cb5f819cfee76ae6d199e2757e38286671c5e0f6022810b3f32a86e6c3fc6a4e144ec95162ec4bb6bf2f85ef10518b454c17488d565d4221ff5f65c52cacfc016970e7b3140ee3e25f4865c7613e5397cdeb5c4db0ada37f9bc309934cb518f52ed3ff0146e557ae35776bd7e3deb7018a63c34eb7b4c1ac7d263d249e85fd60c762948d33bbcb8ce8946607312a51685b29f83b344c73b300da18ef91478956b4fd6377205d8c0a72dfca52cdc7fe47a5e80415542a9c802c4f94d679a9716dc78ae3f102c1653b2adea0e183061f3752ed685a1d88e2bc25dcdd565f0f2a1b81964c5adaeb4266dbe492c2269614ed8c80b0fae7642654bd006fefae302f749c2c96a2d873b9cc1786fc2e482c6326fd0c2e36e18618dda6b4aca0597a408a3f78815d2e9a00590a481de70484cb6f8d6ba4d0023d6b0c22a14d5876ee92f73c7b703c28d6ba62c4a9beeee1ec4a3e06d529de1c31e19c342dd879ccda2ff965a389b5c5ca79d834cb5fff56df25967095f1ff54a70d53b8983f5dd55de7cadaa146e8916d286eb4833f1d1a123bf24e39e5db4936262573fc28fad5369a46bc26dec02a0621ee02b1e13f7fa5896ecf3609c4c8ca5fa93695324f7e2d138916cf3f7d57d070ff0a99d71ada9e80ac8b9cb366cc399dbf0b8ceab1f55c7befb385a686eec46363e992967f1ea8416963410bf4f09ca5601112f1a0fa208e779d3f869a4493b048be70835bd1f6d6480cdbf0f75a27dfd58dc3f32ada23a9bd1c5b1ef15fa442cf400bedf203dd468bcb41c911a33837d941f71a418a2a96d3daa7e235fd29e71ed169f77381e86b1b2a5cd05fdc0896966360e1c621ea85e291ed2810410e98916b25f054327e2d7a5ed50d5fe8f5f3b5a93e52d6a6ae004b79c272918756ecef181daeb60355d748f975a7bad227bcb252f700e5cfaf9ecdb6d44b17144c155e2ef25f6eefd6fa166ab5fbec9900b17b4a9ac4313f0ef70c1dbe26a7469db161806c76f41e4f67550a101151da22ceac33fdfacc80fa9db3da808ad6479a05af102851e1e1b840c572ebee8f06eae4a6fc0eb4ac65f7c93144d98fec669b216d1b140b1f974ab36eac82fd0e1247b03bf2492b7237e6b7412b64f566d79c3ea4ddcd0e41013ee3a51d10af92f9a2771245d1d39a4c4a1b8858dbd38402f77427f1fdaeec5575d6dd6c5fca8f9a092f760863dd6e2b4af47e5f8e6050396de8972cd01d24d2bf9aaf82f4b1f9b9df5f217a21f4a22244b7f4f0745a3d5250e36ba70c5a592832e39960c15793debebce9dbd577826d60755aad603239865e8e4cf58851cc09ba5c8e6a2581ae77dfc27739f94d0cefd8e5c96bc4e10c3b253c4881138c0f4baf9f441e5c16a46130a90125618ecbb923494a6c27ad25e0d7c683706c3b9fb186370fb4c813e580ab3da85243d3b67d57b34da8f1b473b214806209dd225ecb78840b1c15cc7bffc78cdaaa108af8ed90e48e9fd1c7e6184bd1d328bc8153ca03bdbf3c2de2c50b60b5d06ec36080295bdf9c1c01fba71ffc84c06f67102f109780e0aa37f88b7c4e105db150b328c4fb0daaad6fad8368cc0b38a96dec46ec059bafcf0c56faf5d683a41226754a5ea094c6e05dd889b59bbd0a39aa8f870af1af6c19575a83f98d013097e00f1dd8c1745a6588e4b5c8fe8cda2b5d1e092a7923c62308bb7b14e8c8de31908216d89caf7a5524a030100143944549212aef0b9a0dfbbd7503f78003bde88f82d2ab8b55c555989d7efbf6b3ffbeeb98e8cb23644c0b5b9bcd2baddf06f1798d07b484cc6a6ea7f38099f162393b6867c91dc844cf7ab68e200dda96fa22b835aee20a135a349fc91522e4939b0a2248fff9b982f7720d0c4a1ba5a004eafa3c4f48776cb6027ad64715d879259c02660d2cc768cc4157d78e33a7c460b270cc8e5f372459b094dd6f9e36dd99101d1a3ad0370806813ea517d860dbd89995a2ef3aabafcaebcb0b56e293ced9d4aeddeec9d84856c52779acc58e39ee0d223debf7141e440ea059713e20bccb8e292bc0f0b51252ec61ca8663581f1280f81c245ec1cc7977418b4216fc57e90596fd21f8ea9c76d72921bf110a570a7d5aeeca8678de1425c72a6fefc3c7d526a557aec32801de6effab5d326c49950e16a56915182b2bb77ae37a2995ba27d7a5e7dc4cebf07804ca590e512e6f0497cbc8bddf992f8351c5013befde93cf3ce1ec75530b77946a453ba6882e26f13fd280c6d2c2a9b941e8ece17a4150f12d0a7861796e4461283eb77ceccfff0c3e4119dedc9785e19bb5b232c3f750967d825c20509f3090bd2b8d5592d298de1bcaba35a86998f366f484e85c9f790aa64e1161b65e314c551255ee1cc975f91cce014173bf4f6393e67b681cfee8571c3c82fbe7287665489203ede7879c856ac0ff738e169c68c47ae597811fc1a97fdc31c8ecbe470604d8c944003875809ad1f07001d117d4bf41857a92e9257b229b5f0e3318f1ec45b2ac903f3a4572d2136d37d2e0ba93bed7949f719b95d5fc55e45163092bd1fb729e78a9157299862fccb460d12b24c9edd78512a725aaafaeac63dec0cb282bd0e7fac500d7a9d9357058482800c6f5de20d0034bcb860b7e178165ecd39d90e1765bb39e3f82694a29ea30589469ee19a8a3af6d06a1edcdb574b8238b7d1b60a1ee3b0b02a37db4bd5f068d36cb2bc070cba265c377010af6c8669addee3d1d16a836e666a2a1e439307d036be7ed505033db9dbb60918a99360b296529bf9fff472ff87dd20283145cb3452e47d540833cb244291429029c3e71855ee3cbce409e16553e1baed26a875957b7270910a90ec1e021015e6b8836af1e01bcf6db986d23800d2c0b5edd2f9467b2e5fee8fbfc4393f15b763e8d2d9b0d1c5d9d9223cbdf06ca6dfaba08dfae1b9bda5d35eef451dd920383a6190570c17d03d64a1362ade067b8e975165d3825983264cca8a17eaba7dddc8a3b0b63bf1cd0cfd4e28f2b275a0ca6650d1f999e4bd822715c4148cbec4da862377a0679ecc77bb38b5ca3082c4446d5af019ad54a74526c3de0b0d1d637f3ea900bd0f1e776e66b57f97a80a3eff39d57c45393bd6a2ced250544e518cf086299b6b5f296d4130c9d9e80b9ec55a27cbc8680cf4eb0b55512489908ad637368b41bcf4ca700ee22c84696a5b73ab419913e9b3b64d6ec5603fa793f7fb4d24cbc5ada6f707b029f8e87f8f574cd1590b0880296d27446cbd45a38f532e4abb51621b40aefe864adc4ca0be1ec84581f2b0aaaa036e86ca1876bba5a30556e50981cc483375721f0149ca674291607e98ef668094e200ac068fa3bad6e6d046d1ea357ff37f743fbd16408c3e68d238162ad19d0b4273742a2c842520815cc3954bb9b2e0eee5ec8ff8a2eb86463dff758d25182754e764cbac00124b950667135b4e9a5f1b66eccf693dc8e3cc8ba6709a8ff8c5668af6bd15ad455dfb8b1cdcd295c4ca9937db9b1215cc07faf0660a4d0e9ab56fc481a6482e17492508167ca0041b812cd62b4eb015061120f2f775a1fca969af74d37da81a5bae7b8a318a6260329924495bc617cab2942392823ef4bfecb5de493a6162049225133400d3c10c815a0824a8b937de7fe7e836878d9a13bc5cce7a15dd2228e04148aae1b38382cfd7fe21a922fbf24e79515d01fedc6d677c81caf82c76976e8a55cdc40054dec6a240019cb5180393b207e357076f05cde25057d3fffb7181a273dc280cbc4260b7cc1356b76e249b759197ef93f1e3638f7488af8b46adf11bcc19887e8fddf322673de1c7bec2eedf12f31b9ad0a3003836452c572b3202562c8e82cde5071ce422396c10ebb4a6168742ac850a430f8affab0182c4130484d95e7d199a629c8c8cd73062c2661c4d143bd218c32e2cfe99f07923d6adced813c0afb41d836ef6d6e6acc09d375a63a1607f0340feaee97def24ed69acacfecde6dedae6eaed93b15677143ae87da89da0792582d9437a7c23be25ffdcaab5985ae65e26fdc262683c3c0fb892519ac27549a32d5271919e4676313224ac2755b6132e280956ccf6c6d6a6ebf2072cde802a3b5b0008372c4b6edd349408e74106f5b222d6136f5d6add4cab9350698107c1e026f8ffba6c4e75f070fa84cdc2ddb1bfcefe156932bddcda48abb79370f3b92079d35c9c4fa50d24a344705b227ff5a50d2e83c26088526e5f36745e30d1200a407a0147b889d7f05cf5825fcfb53c4c60e3388327a471fe42f91dc4441349dbe205e76e9c993feffc43864c591212b019558eac49692fb46949e6242d63bc8cd57b5e14d0170c27c569abd1cf025bf65a62b86f5a6340b4f03e265fe14ea937e51715f4314ab830239220c504c15615a643da015781eee3cea4a943c7f57b05a409245b93a1985ba856c0b50ec4779fb3e55425d4fa3defd0c5bddac73977a50543c5913b672ee039fc54e3dea3524caf3b7373dd9a4aabb657a603293b800b6e87d7b38dfabb00eeff5f6b28af5907621c89e5d82b6401403aef75a7de21a7538cd39dbb3c69b81cd452e8bb1e510025d04e7358296edad4e258137a2146c36a58731a9a9bfffaa915b4cf3cd06329012428317524330812978d47acfdc3359eb2400a5898668ce2807413fc3ee2570359c260d4c487debdd38bf9f07174f7206f49e23dbf6fd5a8490f8bb90ebb97af23ba7373ca85e8fe1ef1c3ea5b5df1b44952b850b67d9f68b83fbfc760b40357cd3eec28f634b25ab5c358176170cbb1c00bf74d4bd3f7f3e1ddee2417c77bcb0267d55db500e398c3cf6a05407ef0d03c9b04243cae235c4d319a76cb26e007aa40bf2122abe7e70c1a1d3c308a2c523a7fb4d1a351664e9106cc2674255f7df56e8256ce2dfb03e8079dd127b5c9c36502a9562f163dc76840d594785978433128b9b3eb835c2992650d156763670139fcc2768e9c2cc7056c7cdc09d8928f7b26ce7904024cd3a34c558df59368c563f7c917235182931c448d523f278dda44f0a3712a8a5ddb87809d2d72478bf5d1b466ae39f83c648959e191173d04e6f42705d6a281a6bfaba156598f58034359d6746261e1439e3865130eb07452cf218b39cc5334857ceb5bbd33c2e35e7090149de3bfa9c5340b9b646e817ed0c5bcd0f36a3cf1650b2490ffe9ac2ff05cacd54208e80ad08e9dec17358093dc7eed1192173e62b593286525b865a646b09266633336c06bcc7ca48504094d935cbb6290e1436ef075fa26db2902265f3d46b92937d2c7e1474cc8a4b5771279cc7a595153baf967ea9a8b50c84b2eb41e30e1dc1d4d99da4a5a8ad4c1caaebcd144ccb6b3846a04079b56e73e02e8b15d7d334d8f40a4aceade42e3333d32de4a2310b14f1a3b91cc6007b42f46b25a11f4839db46db88075cf5c01dae1af370580c601577ba06bdca77e43b2059a15269ab2c48f70d7d137361cdfaa5fa53c8e6292006722b63822fa0ca3b6be7dadf4b1d3600c70a77fbd24867202940b735c04813dbee21bb1b94d696eff9d7b9bf0168938580eabd7b2ad8911de8949511bd92071db1d0dd4c80f95b09e78f637f06d112a11ec9fae82a42150d667b55a4c03d8d61cca31ec1899993ae5a66bde19ed685e210fbdadfabde97ff5e01991a81661a227ba94641bf9da21095867aa1579a468f3fc187dc2c7023a7074548ac090f68cd1926d27aaad2ba709d2ecaf13d015787526ca3e474016e9aae3c5950c2d581805a1202cf5ff54e16a601d422065c4baba8ded193d825f3e9ed84230d7643e5b94505a732336ca7756ad47549f7cd06185dbf17312c0a285cc36d290c8e545dd9f63d9a7ee87b7f3d761b781cdb58abae61ddc99c5202d804353ba22c53cfb79066e2b4efa603fa5edce43bb5f4beafd3f0469f996ba161a78c893b2c28f5da804f84c0fd615941028714f3133c6692220fef1436ce0fce5d98a6aa38c16cc18a850f2680326aee68ba3f8d9d53b6cbd69167b98b7bd35e992d88e27813980f32218a3a66a0ecbcbc959db9661bb004c98596bfb91a61358d87a099b3aed0c4551a5cb42041149f101aa2e471818f793feef44f1174d7555b69179b7571af375ab28ac8f8f1a33e31c41335a8a18d8cfee8373c7845d5cdd3975e7350c6224ccebd87c3e70a81ddcc184c8ad6114159be29ce518285bdeb5808fde507751b31807335727d4fe4cd95cdfc384f6a4d002ec60278dd209405be92d4259a7e38229f3d50980c40e8e9141d44e2592c092d11d63f0360fc83232b6c074b7fafa265ea498d337ae6ff9349807e688668b5399d690292931c86fa51e33ca3202a9a77d68a9cb326945c967376a97c3d3938ddeb0952a492eb3b16652d2d473af01a4b0027858bcf837a424f989081634bbf7a11b2aac11f86eb03d31526ffac2b82dce9352e8b5ba1f26aa6d477b641a59ae62d7af011feb251191a627382a4c3e2ae2d8c05c1279439ec1793e4081db183ff46569500a9157f3970f642a37dc79bef4ad142533d43a0a24d75ebf7fca1d8ea65b1438dac006a74e6d7a8b238c64e000e846e63f7bd08eeb1f1d3e4ed812c710205c106fbf6a7e2d29d03ee7cbfbbbf11415fa5430fcddc99a95bb947153dfb373db0c0723c299d7cb3673fc7db6e4ce7a3d0076f7044b6c39667bf435dc5cc346d94bcbb05960045cb5ecd32d12a29dc4f984ab8c199ab97e51f77203b22bec7795071792321d3603af129574be29994e96186e7df2f5c57c9cf9d4cafa34e47054261e2bcc6069f094e5c1e1b1e2cde17e780ba90d113b305d8d9bb5913f7f3e489c0ba1d9868f3b816b835bf242ae9be4818099c2c8e6dc6b17e31235949ca12b9242b1a05323da125b2816710255280f0cfa6b768463bc4017b82305bb6815095324ccf5750c2d829552d0c38527c00874bbb6d6a6a4ddc8a65392d3bfb1eb6418be3c7ef0b464bb3d42bd982cefba7cbef003e4cb4c0e1ee01cdda7a0298854012a826b4c9b61cd1d4d0351a0415a8d810ac275c3e365563d8004ee64455b13e044e1c8951f8b3d8f37b0fd057da759ecfbac0a724e6abe63d01c662df91d42b8d1ca2bebc0b9b601fbc0194f49a7ad2fe5fe1a9136b0abc104bf2a2056b15ac89bfa2ad91e0ea53b116dadc4b4467d3b6bfbeca01e9172618f2654c615f4c61523f7e0a7ef9ac5b0430cec126d63c7af3ef0e0173d0c333437380175f68f096ede2f42b81672d3fe4156ee26a6c8dd4f241eae5b2f2a2f7cd8e544cde8813cf45fb96031970ce7632bd0820f2bc77deb00477a490eb47e2701d83456b2a08c4cf3158c455c8e217301ad11101fca9c80a1ae0da80ce68928b70d70a6caf878bdad5736d36a9793d0c8f605101aa4818caeb61806dc6ccae7bfef71affe90c02583e6873a1e1ea05319952a6561f8631750ba03c55f83698dce94ae5a8c7cadc299fac95d54e738e22b0e88ce40a330619c44a720c1ba2a844a07d6655dbe62e5f40fac450ec50d9f22404c3b624275373288065db41ee54921d27e4be57e1c0d8bb01471d7149a452a8d0d5a731ad11eaa1c37652203c88d2bb84f393f3f59898e48518f0ef98d9d6e260fcc29323efacd3cd5c17d137939c20b20040f1328dd98e33579f201d04339ff4017c95d243df3aef5e566f25224311cc6cb073d92f584e4ed94d0265b53a1e0208a6a4c996a3b8fea42134c42a6bbe74b4545c31142fc9ff13a08b0c0535486eb5928b4681b92482015c14ee9ff7d10791b55cb8725a024ae8c38d0f449205d38a198195d0fe63bd72f6f408b626126c93dc5353752e1a3c5c591e9efcd33efc937b4f128f48f9228457932695c3cd7f27004fab7508d0b2d3d539684e268f4f5906eb96337fb7aed9a89eb2c1537f7471053b9bc4a0bfcbe488d6f75fea79d30dd1793b00480d8e8c3da3e32acb1269a1c0f7930e89043dff22ce348f45a3d783b239c8e22967a544f998fc0b06275fdd92a0ea1a06e1f21bcdb68dd1f2f782f8db3df79af89214f2aaf20ac040f0f7cd3389357ed953df2767015479284956ff6900536feb77524a738ff418aea4c04cab2bd0d7ee14d356ebbc9ec07f2a7dd0a7e1ab7a65bc64d1bb2b0863835b08420669ea0407c0e3d37ce99d460e0030475e0ba4a470a07903d82981787ed991c565652e4c4ed6c156852f305a749e46c241e848fcde89608f68b823d08013dfb81342acf7ddba874dcc144c10d8257d24a3bcc8d2a936d7978bf89f6efc4db972e6321b9060a8a59c671348d3d9a2fb6b1a6a7da31c5a58bf38e01245e297108d0db23e060e19538eaeafdaaf8cee62f1acde682e041baad57d55b2f4f10773af3bc098c0772e45f4e15f8f4aa3a7714cc5c6b3ab22fb5bbaa1b16c11549421803cbd486009e5bc2734f3059c39479b6e7a9cee54d42fa56b67af70513dc55fd823c2b29111ac96247c4b531478a82d7102555114ad92185ac0b1cced4b084bcb20c5cb8f27bf85738d7d99ec2da8f5596cb5f5c5d250148adbc8634a17e68fef426b25f9797a85c2c6d9e175e96258af56656bff9e820110769060218a383f7f02a9cb56a5200b0b11893dac17a4d84dd2de42143ed0807201cc328d97c1645d4a0c6ea9ab7b8f7e9c09d3f13b00ea9c812a860af5fa1641b171e032aff3c8755b956a38a8f074a79a3d3571a1dcf10450f997c4c3a46c4485dfc52ab59b1216b5428e5322824b24012066cf2c857f044744156537ee79ed3fc9a2f94a8b18b1e160bab61e43df306368d30594f25867e4b220ea4640e11a64fad3fc3f94b537fd1dfc1ce268d1ebc5be5762a03de9a10058c137f913ace1121e1d11de1c1139693aff947e1fe9d734244b0459dfa2efc19e1fdae752d6c53c2fbb945cee06b882210c5db15c132d2f1f619bab4a734b56319aaaa1ff10bc0821d6634c969b0b29fa06432f2422d1dfd82edb74e482df624d0721a88b846d28d9296402eab19d0ad6f0a9ddedba7221dad00c84c33f1d16715f9842b007c8dd7231cf75310cabd2ce5d3dfda69daa7ffa79ca9f23c1a64eb1b66c070b6f1c7b9a909d4cb830c9582c6169dc100b188f1fbae69450c80a319533d4f2efa2e1803250bfbc6ffa61216af8dda157763ea727ef9b7a5173e8a884f39ed250ffb64153316130021725882b3752a37169df64b16c88ffa1b9cf1b784302eff90f006f02beb80e0bbd760fca8a2cd6a235d85d9dae54e384484f1220ae12851b8514e69d9e6bf1a475a8adca139b8fd7443aeec2dfab20ff7c1aee889e572817ded39fdc9229e66cddd4fa8cff1c7bdc5e58c43f37f9b6b7ccc79831451c53b3e7a17a810a42d1b8322da0bd8df1aa3e12ef8f305b1dbf8fc5a4c54c7b360b3de04785e97f1e9202b361949b3c8aefb3f1da0ed894a45e254bf20d0fe716ad321ef782fd02bdbebf3f25b27b1a66fcaae62683f6c8cb951222c03274dfa0736ed5a7e117b321e3fe16a034c40517aa4386d4e1898d8b725f298a99cc4eac79074bc99c1ec31a638c52eeb8ac6f48ccfb245831c612e81b733585c6c0cca2be6507d9f05e52e7087c64e004a065c845a7d724e786a5f7528abd56808b91b04f644d54aa09e1e5970ccbc44a36616af429b902b2c0e3e461a6cd492c69f0367446c9da40c3b09370b00f9c72048bae54d2b5300c5753c87389a2b65f81d636b5f9836751b7e34be400bc29df146bf35e8f78be6ef5219699d05886a315acd0c828cc63de998ca30c6fcdb285d05322c03e7d7f7a2a33ac2d7f53d2f063ab9a360511e721d47aec7e6ecd3ca3ffe28ed76bc7ddb89e0a64d3be9073b22654564acd9f9505e75c2728533a9671a127113cc9256b1557a26b7173d614d5144f6f66157bc2ec287672b10b1d3122f1f028a79bf6d4e74c7d426d2dca4e086de05401b0a9c8ad7f15202132b1761682ec6ab4a0264ad49e5c74146d40d4b92880173d9576243f578c641d63289eeeb22eb9e12eea32552d3ec026e0e7d89035155b8d5e47d7b607eb2fb2bfca7fea2c81ec52f1182cb1421d057a9079a420ea274a93c7b6fafdd1d7dcc886e4c8c3479515faf485a07082d5780992f524a3a674ff01523ac6010c453978eeb06417df9c72f486cfe88e826ff24ba2738c39d218b395ee884b772e14aef9bd615bee5cc1e9e8cfa7b4b627367733e755c44ed69e2321107fb97c68b39bbd40e54bced37b16655271c9a6d575e433156ab80f9e2be2d1d1f55ad959d87dd4bbb1c33140088f927382363dfa681710517d850a1171589da3b5df4b691404f733070420c7e9bce730cedbec68f032d352d868a815ed7ac43ee5b26e0189f62aa67989423d8c0bb8240398fef46469b9d016e3642f6a976ebacc63a2b6de541638d8ee5ee16777f78a7b62ceee5e71277752faf85a78c82f9b38a0274678964e9f056eb843e9bd5b06c50c8236c71f19ee0dccb07b2cd311affb8b83b2e4bd9a8ca6385a1494726986aaa51c2959e791f1720263a2e3e1fa9b26be238ebcd524fef1443a5874a3bf36369b7dca90f3969d70b869a6bd68374844880e5fb849704073d16c0877b35539f94e6cde74b351828d87eb4ad8dcda85cb84bdf3d8623fc6f8ec2a623c2df5025c666ef5c637f70b8ea471bad36d909d54676cfe99e56e500bd3c984463082ce1c58dc80aa7e577ec9ca3f9c73a0582f13ab5a86cef34fadd02fe0935667515a2a17bd6146f0fe878faf163c0172ff17f46eecc87192d23e961fb4b711da473a2166ffb21d49cb647a42bcb09d98cad42f628f4bddd12d09fc8731322b9f45c7700ca2897761e81f98d03823e4344bae104143574ca3ecef34f0d048f48ccf5889e0898df0527f9032b0a4d62c094f23fe38c78c9d0b9ff6a31c6a73cbb06f09e16d81b28e34703d869cd234532a4f90a91968560edf633de1ef7edfdc02157632debf0c71108713b30e143dac30d5ba72a55027b9721b7b04818d46dbacd8c6c9b2c53e3ab73f1fb2232118f5e5e35353bcf59647de5e41347aa9da68356a6df0ea20a50f236bbafc3eeb0421e2832e6de699a4287569517aab185b5dfa1502a5ddf6324e3428c90e97cc5c69f8ada10252c49671b277a116af7666f6c649570e6933f58211d8d7f50b91fbcdc36b0b84c89f42a7e47d68d612920c454053d4b48bea5f94481fe43476c61add25f8ea9ac634726360dba16e49f002fa64b70db2232dff0e020c940c441e659b9eaca5478fbe3345b2afc961b1418f01f84c61c24fff478abc556c8cf9182e7421ed7d5ba3e353770f60fe5d15c4839f671ed2c816194d64a588a24871a59e6a168a314b9d714db9310d6f99573a27c7a17be654ac317ecc9fd6ef11cf828a71c7b4e7f62d3398a759ea00f9dd82d94928da72cb5fab68da11c2d21e64d23f96d1834e0d0ce8f97aea3f24cd735903bbeae0cbc36d66e3ea96e7f028d066361c306aa914119015c316ab6617c0f932d881a518cc22c708cdb77157b96dea6e8935dd7243c271045fbee044ff88336d1059092067ea51342678ba9ad0c285de1165632fda90d50e1b7209c69d3c275404d3ba2177f6eb63e3fd5daa5fee0a7bc8ee9a1a54ba4d7eb2358a2048f8019c16bcd7578bbfa48488b59b8b87a188a0ced4358bcec8d86599d973899c02c7e16357dcee2d54f7aeeedef295685a3d2aa2bdd8dbb4eb3293755c3f02b6f76166dfa9d94157874a8c48243785c590303d3334a8841abb29f287841b2fa7cbc9ba280de3883246a91ddedd47c67b9d013424a055c0c24f28cfe6cdcaeccd56c5c744b812cc1ca50a6c27086ca610830def270da47468cbfb4c4d1f4260f208d36324787cdbf27a666009f760f4d36de1d9466dfec6052f80508e3fd17981a349b40e06ef05400250df235f6a9134a257937875bc11bd55f52dea1ccd927c51489ed4e8948ca5b86a8315e913a95b2492e725a029d69a13cb646b044006c93233672c9a586a803109b6d0d208cbf8def49d2897b03ed20428c4e2b803fa0cfa37d435d31aecb57737e76e4bb76679e4c7435d2302f77eac817650f735f069730d6cf2cc64c052ece401cd22eb4e24f2f122a6f0c16f56b95802d516a68a017cfe9d50d00fe396885d529006ccb32864d1a8b3041c149d3514a1800609f430caa7e3775e45f629b8a8b882a0a627b9a791ddd3376b499bc5edd20c2f73b3963876665434767afa375e1535699096bd9b63b31e43a846fccc5a99446c9259ae26cd2ab6d046f1b40f136ad4c9085c20323959c775c575ff12a1a7f3ea338f991e65822458955ac38f9bba68e34235e02db4c68dc73a70ccf4cff33636b02f34ac21025084bf42048332e1641a4c6b4b11caf4187808d510174c997261fdd06a5a8a0443051edcfa0428f94c32db3e7f89fb140aaaec66e2d2af45d4f7ef224d488cc488d17a16615d986c76682840f6b08bc44e66c60e8b0c75e4710ac75354783c554631a8a3e0607e090157e3a476069d920ce3440351e175b960f5d542362d11cc6f10be071d1b4ea4de1bc554402dd9c9af70e7265479ddee7aa94c8b97b7c424e666ce5235b519c478fb450819519c2f1f1054351eaf23edb19092dc5b96b674fe8ea22622481730f3d955183b5b66d663753c19d3182b72cb8344e63ddf0d4da719d3cbe513d6f13ca766982648ac48b669072157753abd61ff11ccaca0922c0fb28790db39378143cde2983d44479fb9c2fe50e87e83f11078ddd623f0694bd13708784bbfbfc46b46c17e0b5fe87c210bf3034dc3d1a9439ce9004019c138be7d6f8ec5bc010b4aa7cf01ecadcde2477964810d87c52c609a25bcc1001c0af10525f9654138dbdaf25b24a58a22219bcb2640f26cc4a507aa8024ab04de3a07f34032307e1b8d11224080724be7bf4cf855ae2a95e5a6701ad534149e920bff64a31043b415cbfa7470091a8105091065a4af8c7dcbe489b2de24858aaa9245d8b287309ef17f86b1e570cdcfb01c7ec82a2f61767d1c94e47c5f0bb2c7510063b63298a1e8457e5ac23ba3726d85015ae83ecbd60b1415e9c94dc46db990325e022cca6de063ea9215a053389d7b49b35276a49325269d0f94e96862821ca4c655977251fcd7fdf6ba895842c782c2c89addc2f89a5026bdbb9f48e23d942273621210fc2d5046a07c156120e6b04926d83b593e53469c61cb736e6259f95fc6576fc78722a05a1382c7be2df1fbc8bbf7f7237d0c775af540046d53ec7247c0de200c70150f7e7fd5127390442da085bf0367af3ee2f89142a561ea05d4775c4463d821d92ddcf3bdbe60cc776d97339e25dee77bbed116f4da22da6a26e30bdfaf36318b528263d71a2d07f1db31e18421c5d6a1f02c6ae4c4ee723489cee042e21ceae0c3b1da6acf9b2d3298ddb0335d2f75bd9593006f22131c6fcd30f57411c00f4fb95a6665edf27979c6e911387be4322767c88844b431f1120b5fcc595fcff81e5c92a3d1f8a1a64df3e82722c2530a8f817d481d231590ffacd4420607df6ca37fad9196846ea5b51d62f8a9637c40ad852b5eabc1aaabe12a23988161c0ff30bb25851089d8641f11efd7e8e7146030800a750badb7e4ae18910b9c796c1039d656c806fb38b0a3477da4940519c800c473b1dabf5a9957cf236f97eee1fdc002700779783379cb577c7cb3b030cb9e56dc00fb7e7c265317d7e96533ba78c01b4544c1e7171b9758e2a465950559ac6e902574571b553e8c0033933e7750bab6e785ad66290e0699cecd744f86cc1e3a28a2a3f6e6abb557cec7613cc49c33ff13a82b127e2865005563a44dce0a2a114d523f24f03f4b372ca2c40f5203f5858a9d3bc6e97b31600ec94898bd3bbc39d76dc8ee513aa7588ee3b152790822963a341285685c9c8cd095020c74ff3d1378ff9ec6e52d80947f723b438012d01590c2d897dfd5cb778ee793dc5ca7d7e6e44da4c0d9eb2f5a5d6f376081cff9ba45175677dab13255a6c0498c3320b53353ad4c9a4e18972ab2831e9ad7b4608657b1add140a8aafa3d4c923c04558e1e30e362219faa771a5592e01c68b0aa4149cd86873a8805a38f69008f1747e6ca3b6589d5ca524ae4ab40971520ad293c96e01ef592188f2ab977f674cd1917d30e911411ef7aeddd3732fe53c5bc49b72d90cbc09f3fd116e11438e061500c0abf121c5926f3b7f209d86f4dea6553c5692fa0b903074b9829b1ba0d1261ce7a7bd9fbcfe69521f1208d3d313130b21bb95cd5965dbbf549d39079973a237cc393218359cac66a14eb71c414d47587a4d8ee6930bdad911a3a2e72beba01293ae3781f4fa442d440edaf36a75999aa8373e565d65488e42e2f536031caf9e90a05aff63a621e4ae4e7c41aa42561db13cc50b353bc08d8b1625d33e46e6325b3da2058a3b98eee5e0566518a793842f2665cac31f32e523728a45c62cd19012c11b8877d84a0a6cdb0e74dafb450ae330a2317d174bfccfeae03fd09aa4e264f31df64d28dda21578ca331bf8738682faea06b7d45fc36bad03f6d4f0dbfd44d0697933e5577d1ba6e4537a7e6b7a89979c155b01bd8dce5867e287c4c988ea7d748e11df60c9c40e5bca48794f546268fa8ed0b068b197badd674c75db7d335694736869e557a6591859039cf1bcdb35a2f3239c29c3818457d8308a623260e56370861c5d1f91b7cffb07648ed4b3b5d51c3115e4771725986c33512f16735a5d6da85a040cacb2e84147c8af0e3c800fc722864991ad49259432d75e0d729a9587a681ef98d242b4db20bd95df1f3c079d4c98c05958f809cb3fe9d9e487c68edece4c489b6f2aa1887976fade781c44236e1d71de5cf59a64bc8f483384264f9ca1697dc5454c027811d29ae28618af915b62b2ac280d8ae5705d879103afbf99e0a86bf24f0ce7798d61d7efe5ec7a26cc95d0264e6f24300c491c89cbb52b144d873072fa34270c1aa25d3d7b4e2c4520587d9ed06126198fbe09fc6d32ff3f786ad29a4094c015270f10fb78cd4c4646cf851f903162e8608b69dad535a02040e933b1676c64d675c64f21e70428cbe12c06be37838bca025caf060850b217c46efc0f86c9a8478af759f71cd7ec9d5b51060d755c63e8e1761fc1feeff4ef3c93108d3666900ca89856d0c8170045850dda988b1122a50e50e5dd94cf39a98d28dc133457a230414d86c91824a4423498512fa1e9dbfb1dd1467eafcee2078e5189c4c2c131d3c7046eb1777f574ca7660a84a895625edcd856bf7adc61de7ee5806d28a399d54310f33a8acfd6ffcaa719fe13949f2b2af71f2df36043920a3abe327558193ae4022f196e26dd89ba84ca78d7b29e2487bed123cf01f32e9b392731cd0af8bc7735e481f670ee52c263362a687f60ec00dcf561020bc3fccb073de519e08e5c297242d6bf7f9ee3fa434ed0269f7ebab64e0a69c72e693e48204ff1f3e55ad59cacaa88f7cbd226fe11796217ea9eeceb06205cce67c2d12bb05ca7c6eb97d46c743d4a6bb44e4b891c5c32144610dff67612ead7ba95aa5e9f25d5b32b3c55197abb85bae026bf2a252ac28b79eecfe6096b08acf263def8fae9e39902b6412e5bcb3d5026d78d6c28a9823fea6f3b4c30f3b862c6c9ff50686b06c23d6f72f17a17d3735a0a602976856b086f53ca34353ae9e4bb0cc4e4a153e318ff55ca27c7219f7ac44db6f2906d88f3fdc382f4711c6f40dcf25a4e4dac4d6f6b1c809546c234352bd805bd5cb222e031824e07913aadccfb5737630d89c8c78d0ff87abdca010fa12ef0bfb431563d8f582bcb6c3a809af0d6f8a492e84056787288dc7f5f1c3e50b0ed1847cb7ae2ac003027e04d88047720076c46f32e11259137838f68bfc98c55637617a6f6b4aa570c0f182852f01be59242f525a1eb54f922e38695a99fef75a40c7f1140b6eb9665d30c3dff2a948882ac700c83669d8c2cfea21fda7fc2582829ebe923a810a8627b7cc7962eacb7048f2051344e2e656e357d4992d095e60ea0a52e7b4039e535bece44a7b42830b45468752db46c0ffc7250a71b8c26581bc3c89f9f4ede2b8843847a8e64e504352f117f026a5561340765a7901a71221d02bb3fd4c64a65c6200c408ae71e751b924bb92d5482375264e8ef531ae536cffc09c4d2fb3bdd1a2d727e8aa5c428ff47f69eeeb22b65820d1980ea8acf7b479e20747cb207c2a3b64ee49982346200461b8280b5bd1b0f9510214348582ad93b821f6dd69451c41356c323e9b75ea2b891071e1a858beb05e6e7658101b743013801297888ff787758c0adf91a5c3b4767ae292d5673c75a418c07ad272457b4f4740e103e9778bd15bd741511526fe62e28f6e0965b50a829bda3d69a6d4bdf318987a8fa66c76cd228d2e540dd5c4741a095d5b02a417fb22d7ca4292f544243fea08c42567772c28485b514a16c2077a4b9c81df7a8c57497e01cc5291fe29e05f2a640a03262920b5f2870432dde660c4d99f10a2f94b640c972c8c5cf27d4053062884958f496638de088513398203964fe12b51a08ead32600d9e02e4f50b177073b25152e53c876add683812f6ba6cc44c87807939f048baa221d200735c02acdd02c58579a01e5e3bef97306c4b79bc58eab3cb12b513791822ef92b84211318c1566f553f41fd7108b495d87a9eac0817ab9a34f3fd7f1bf94f14ee0a834d0c01340af85086367af9b47d4f0b0c8a784edf9dd8de2e697a7363d4d925402b43ad4ab08a619f6317e666d504fe06a2622f3fe449cb6cff387e49f59c641d7aae56cd5025246d896958951e4ff1ac80e83f35623d37ed0c517612be47d96f1005b21e4b680a6d6dd17492587add0c634f0e67488e10c5edb30abf2eb7015daa3fbccb94d8d81973174ff86760dd78b47bfc3a957977c3a7b39ce558e2353183a50bf9eb72802f3de1c06a7504608a4cdc8f8bedc3291ec6152cbb71603d0155684e29a02d218925086c97ea100e5c280458e9193dd949230f5d4d081fac95e9d0d62a7916e8e271dca11e41485e6aead38da1b65766d3d312fdceeb28c6fcda1142a1f7025697f436fe71919bc47b9e6e91723b6096b4833510431143cabbbc579500ac15ac664ab0d6cf3b43ca2a8d4c086d50b9ac8ba65b5caa0300aa23851c8498471038afa8eed0f78acc1dcae0ff851e530ae22cd32580d46c1d26a3e09deb6ec72aef402e643f63fe0e6054d0c8f413c9ba1a209ce7cac41a742244785f58f30f40edc3180cd98d8136d3e816aa4209d2d46d9edaf44736f1bee359fc8ad2f47913fa4c76dbe689f5d301211b528c0e8b364a790039c228828df3eb460a982b5093f1f47e5f4090a0dd1150440a2f26b63562c8da0f0dfaf186c8b7f7868189726584eb56f0171e1d4556d209b84360e97fd7240caf04885e2746215c7a09b157a0cc8cee307bd4db08ddb649dc1e8f27e3b81bf5d42865ff07fe31f1f4230c11f4d1bd065f1f9f2de940594cebae04412500906897d400312b36f1145f372e23175646693bfede6891f9816c10c2cc122219fa35877b28bf9b92db5f4d77f1639cd12fe69dab78f3b7c52b83b6131daddbf204978b220001590833aa0744408b3b1092c12aec207b35fb49b5dd9eab616093d709a4280510b3bd5b3eb02d8b5d5d75507a33d981cee236ded3df00a67e0bb2b56512b5e0e8b848a81e34547675a0bc88bde54bacd38062981339358f5d3b43e02a5e6bc004813b2c5baedf4449b2f7ccaa56c40889d1656f214b62d79feb3638b936315857dc31aeb6f794a93765dbe470179e4f1e17f0f452540dff1538b0409bdfa08197c51a92418c91c86c097f50b3b4981e293893b82a8fcd4c1e3a693c057f480f40ebbb8e0e5bbe72e07cf8d70d1966672560c79b9362312ac623b859f7c7e6fc3c680808d344c82946e1390c3f94d234451a936954db84095cad206d7a781a89c8fd6b49dba45fc32e09d5705717a8d1970f22cf3b772f4e87bcf483af6f25200a0b6e6bb250ccff9fc4c03aba70351910fd5b42dd293e999c0d3d514a698b27fbd689a62411a68d10255931f0bf940fbb62746ff45ee238a300d02fb11e2b85266122237119c0c4b37f18021f015eb2d6fbd6e58cd3de1ad5d8156c4692fd01132389a3df001b6200eee64b43dadb077fc8b0f8ce3d714cc26a764f8b7a3d8c30ea04bef18ab8f6189687f6e589ecab22b9f0349a61ff695555571a8de1eb2cc07ee3729e393f83c4171030d7321b65e80ea3cf71a1c7740b20400ba51c1ad3b3f122a4f83cbb1021d1846ca5c059477d89feb81d55dc74d389b4b7abd76d56cae32a9506c1198a8fe2a168ce27069d16a0105b19b226404ca39010dfa5da48e4b2ee27545c288d96ed643118a6b0916e459889d14a2105bd6ee75c65dc7c80b072f989e913ee159625eb230b2b30bdcf153092654f06693475ff7bb51d5906b3f9ecb74cc67f46047f98baa8b44a7542ddf776988762a6d66abfbc8dbeff6c3314eaeb9ac9d12971d92a689d58e8fdb0f93b395008433ffc876fa4ab638b874e8d404867da41955b068b59f990f265bfce2466899584d147bb16b96aa568e3ff2efec3ca73a8d27db14142d66577e4a0cf79dd634281bdbc9199e303b374ec928beae8d162c9c81ee96ddd9e5ac6ab35f88c46fb0753e7658b17253ad62039c6f843b0dad17eccc6b7bdbe6e7b8e5ed9a717b9c8c673e9370d21903f4921b85e982afe49396e5bc35686eb499d99cedb4becbfab9442baa14d200f6e8e7977a6db458b86eb143a55772a9a13856371a73b8fc846622ddd61d4cb4fb18d4546665692c91697b9719f22e9ee7d08119f76dd3267b8b9337e836e31894a07bc41ff9a84739a00f8cf91b51504ce4d6e538a214566a8ff2dfd355433fb7f1732c358a4409d01441dca68362e22a257ba5d723f7cd04669c09b47633c8b25ba49d3bb5f0b8f233ebb875999759aa1908368a33d068415394fa2e3481119c00506dd26e8b4e1c6d3177e4c3e75e14193bf933887d798a02219676bd71429bc1d10e176765f71cadd8e0fa549daab12193139a263aba7f24beb642d7b1002c13a759c30840978c4ba097c62331d61b281db82f2eeb29b92c0992cab28bfa9be1d52ce87ec06ef848f6943b6d2b7f872d19753adfbbbe95037d2d4d057872be6993884a612d24eac7fd854ea7e87548e05b595085942e0281cde4194c39d88d7f681dab247acf6d412885346f4707ccf2c5dcdf7bedf5dd002c03001d3bb957538db3ea7fbc793ba2cd37482bbbc3466aaf1344226fa3799515777dc0225e1fbb54e22d921ec33654102ac1e8a2db9b7bb4f745c317ea8bb296889d75e19692f86b76ee90bb266d89a2c09fb02520a1c581378226878b1474f50660f78c3eeaf3c260a4b3f30c2c23f38a1ae8a8ac7ca9222b417a796a021582890c817ef97ab63fbe5466255a0c3f1b684f8fccc7e6eb3c47a435fb8d1cc817421557bd5331a2862134aa7dabdf5e72c4f595aaba36d8447ba617722ec28f9484738ce3f3ff8c3543c9df0120c37f9d303181550530b6d36cd7ba0b719dc0eb47ad0c4c9f2cbfc1aef7b96e80d215f4426b34f6b2ffe33bc2a1ec878d5cedbca4060e06db58c129fb3798ca7ee07dee4914667374f93813afff3f772b6249bd649b781d2941f049832e3771fda8c5fe1d0c891c991778f63a7747cb88b3e05b8b884828885440f2cb7ab021d330bc4d4d0d96ba6e470bf7318e6e6157e1b3addeb2eab7e6b2299743fad1cfcf03fdf026213ce48fac7f91693d229e64f103702f3b1b62a033a274b08c765d8c3791dd4db8f04022f28f57ef4cd9bc71616db93d1d07d7207d3cfa3c83a873491095ba3acbe5da1a05d3cc29f254508ceb6476a4a1bdceeff6ae9f9f31450afa97f31667e1004329f9a9666ede9e806caa4ca1efb2351f7a1af6b378ba37507111c56c2e4d76b5aedcf944b9b6bb6fb793aeb088513836d475a20fc2ef361a1280e1079d5747027a5efc5d623941d4fd8d3579cb17dc42f13e538f5645608cf85ee5895c195325e247e5824e31ec5f83fd6d5b868036bcbf4f2c24be4100cde8178eaa66a034a357c9a1920bc1d70b238ed62e33cf0e37f42a49b8b2f9752433711d94a3e6ecf9dcea9b7c8d4ddb7a7c0058ae04bc61bfbf7a05bc7acfc1823d559be4dd06428cae8cd392eacb5896f2a6a4100b3012a8ae9f4f400cd1c88a02c68eb916021891d882afe5e623dc27fe1abd55d1c30f901e40019d65bc9325c10ada4dfddf4b59dc19731f2a0aae1385ac302c8f5f65aa73bf7a4582881b3d27b467d367ca2530b40864f471b57d13809a7dbeee4461033bc99819a926a084b735c70369f9e61dd347c97a931fa5167c3d51700d7bd79ad25681cf152ab9668dc982309076927a1f85e08e6f551a3f89c10add3602bb2f5e19a12321a0f204f7bb8d8ac5a3be4eabaf01cee1f687a29b7fe33e84436926005e4dc311ab8a44e581cef9c88638d5fb11274d8b1ef1e6e9064f409ac0b992d31e99d324031e09a1cd0f82855921d2d2881f0f5a4207748d10a403a04d465c008856251c91b0f3af112b6afba0332f621c6d25934a273234057948019dad33947af6d962e226857644bba37e0c03151cd3aa38293aa3ace9e2a9bdd16469e5634a4fd987f30612ae45d6a067edf7e51e56f10016a4752208096722e8fe6dcac78f34a975250869359c87ca8eca7d230b01e659769afe11d4fcbe4c964269734cd7da3598e3fa6f42303b19c450f983d1b973ecf774aff043864df2a2d2b31d160b93824443e4907bd8ca5c3f07c7912101510bf13b734a58d86887ea95b5b9baa27c38c487151b07d3381db4c56fe65b49abc9cc76db119fca588c8ece62a6aa28b00273867918bffb8796bcf3f0884287d8d7830e4c3662ac45c1823f34c4e5bfe6a98184e378fa9153b1665be9e9f0fbc610e34754aaf5c86a9f03798e2487d0c61a53413e0dc849a0080dce9ec665a04b63152adf34737d9d3e6d4c582d593dfe9c2d3ace9634555f339d3ee17ae4e295224c6f1643bd99d04eb33ac7416d041e9a496491c241ea668a806534e925a33f199a5cbf83392f4deffa37239ca8e07c7f171358415640ea4683fb80cfde05c6f30f3e8f9d2f15b8c3a0e0ca3b73f95bda3705858bcb4947686fc51c2fae87bee5726642c8b1a8154990961250b6cee87f9eb1a32b3657cb5ceff8048d9bfdd0a502ca5271c0c56e3b237a782496ba620809eb15efa34e0afe524a1a0b56db7c58d066974df6c1dd24a23b4a0a48bff6a10592abc339644316129611fe9c49c0f751c44f88195fff0353d69731c0a444e4aa0f7ee350e1fa18f12e575978e25beee04c2b042a0ea206ef51fbf0cc707e40a904a83e2a19c8904d3d8bea774934f138f720bd88871ced7fe8a1811a68d8df742a608849aab7fd90b95eb8a6970698430a3c720e05a2fd3b174cb6345ccbff282d7da3356d364ea437f859c959b9d10639221c6133c94dd357d45080d9645b1e804ac5fc24e751e624dd233eafd9a4bd46e45faac314e108ec77b940bca00be9caad4671107c91883dc79078ed9d242214bd5696484d8786fa7bb4b303263b8953a8686a2c1f07f9e306d844a4afe6c245fac954f8d667a09b1026cd8d67af985fbc8f969efa19f0b666fca97553a335a06e12df3b583f21b8583a9a79e8372e1a40c805c70fa402573beb132faae42ca492d80fc1257952c24ff524025716a9dd5461c89af73c9bbcec7ecdb7b990a85489c9d9960c7bccd8e745b880cc709e7687027cbf02702dab7260000df6df86ca3314693964a7147de667560bb3db2656ccb2cd1b7493220a9488305617330ccad252db6eb094b581c2a69c06cd773c3c611a33ce5bfbda061378a657d1f6c31a2bd1f6092ee403156ffbb3334cd49a67e47a5ac9b8b751f2592ebe512c3da705d99840d246dae7bce7d8154c2f35f9483d0e0939d1b4ced99b6a47afbd99a0d3c4a6036e7af1de05b4f4d2d1b32cb7c45d198aad21d3fc484e2c0307b89b8628dee366ba2e96b0c2a1f580a38255c17192792aba80badf7e978e30e42b346e8cc93474df35e2d0319199c0200e2f0c168ad9803aa83408ffd9c458802d226956c41913c17490fc5003e00e67925de798b0cda8d5eed7431f7535d0f1d0f052c5841f13396998c49a48a37dd00a4dd03a4d29561f9b311f436fa1e5740ec6ba106f1631d0e652278cac462e6afe78082a2d18d0109b926633c410153e54eb9beb1a1ad23cf6f1e577ac8f1b3546bea0c6d0d01cdb98cde4e356ca1cc2d0d0f897978c6d1a91fd319ed55d914169d32c343406945bf228ba2ffe2c0c7ca5f888b19f9742435946d01731c45944dc6d2c665625f0d6587ad2de39716aa244fccb73369412bcad4ccec3237ad9d0aa63009eee0c9b5a0f334eb31d73e415be6455d70917d28b6dd632fa973a1a9c63df2d45ecf2c4a921ffbd9b4235e441a568c8abe79927889bcf7e7ee417b2f23d3c98eaf770ea9cae755ab006cfada004af056026f401e18ab5b5937f0034641c92b417f59c627ccf2539f2bf5c4064767ab39ed6c01dcdf8594085856064481b430f8b2cc1c3d167ec61f22cbba8890d5f17baf63bcbdc505776797c635953dfb126624cc7a9ddffd784963de0f4f3b300ae5728085a8e8f2c2ef9618598eeb1b8daf5b31e6eb67eaa46f1521e250257b1e3188758b78a21f7f1144943b7e4aee978bfb1dc28a95ea03b6321a0a0e6b5f53cd1d10527d4176cf93525085e97427478cb5ee6abb5d6238462df3a2041b9aec01d99f20219a8f00b011409ca22a7909f239b1da1580b6edf8571af32ef8874c0a3780302b83c41f7ed57e30c5828bcf4bd04b8d9b293a31cbdf05516803134eecbc0df37d697382baaf8c66f9cb06621c23da3ccd14edd41428af45302d0a0ca353fd16f30f8f318c0fce43a6bdfee2c330f8ff0a80b91f3273e37b6484baf67a1a3945a394b944f3d165931ea5a4858c560800af2eedd82e9c3812977377c6c6339bad9351e3877cb5864743b44befb2a1a98708016247f8174b1413d82efe76f887630f660ac3039f4101458cd25b5c6a05e7bf31024c78d36f3ca53b5c91f672a964956960c775e80228c81e73171e5ca34c2cfb15d98f78b270a93b82191a0ac5482d882884c4eab21ee7f5a3f3158fcf6df191a93ce10bab165b5cd33eef7b3675ff2b2262ac5f2fc9dfaa9f5846e0a1c8eb23ffda8e459860426f1ee2c49a5f4d14b62d80d48d6400e6a42fef3c77862148924258fdb7fa4b0be1dffcf25cc8ba475c7d3a78f051c20350c4be802608ca1d4fe8afa390bf45e03bb94846786e42e03ccc7b7f842cca21545df417eba1e7469273ae38c4e2117f81f772d1acb24c4acf0c707a600654b21d96f9ee1cd82577d650c46b2fc243fc8a92e9c4377f25c8256b981afc17281a2d977578fd6e7e5ad2d5567b2464996508dea7544578541efe250c95fddbf958a373b80c57eb1d04fcb13b14cfb3b5b8bdf76e83713d1a4c320406af41fff1ee23e906deaa6c0041f1d9adf5fb1bb56237f607866b7345c6557bc119774f7dddd676aa0b1efa4a5ba8f10d7b6741c67311991d239e2318db93651af27e587d3f5604f5a0675d24b71e931b531c7245fe27d248d5d5ae8a9a64d62b341b039f90ad395a99f7f014a3daf22f6b0d24c1a38686a055dcf94b2d8572cf6d382444cd31ea8af32c1bb011a98b8b69df8cd243511310e481bfdffdf59dc9b4013ce32b72a29497f527c0be9b2393813e7b55cc0f5a1d3f9037950fbc53af8b0c185c411f5f880e0deaa51287a719fa18b82bfec6be3c1af461ac16f981cfe7ca86c60f8110e3ea286fc0da5dcf294058f73e5e3620cf552eb074a766e2b054493d12341c299afac1e4037213af44a8eb785b2b4bcf3e081c49b9685d3e7e0c0b7d9f4bb50861e4e97a83216cd1ead54f12f894667136d05abcaf09dbab318ff0904bc0368e3805ac82d7df57b57a76ad048f8a5c11c11c23048cb810c0515f63d8e3b960a64fc4d09f8a491301f0eab4cfcc505a86df794c892c095af8148d84b48d4635bf7405dd5ae9f35fba1de27fc18209f8b924e0d9be53e172ae94aca3bb26dc07f650c12614c479b1163dda2d08625c1881272be2a36c6e63b6412a67d85d9452e5a126c1f46d699b1b1753ea684cdc30a4777a4e520ff0610cfc484ff24f4d8c29219f33f660737182befa6e623963890ff53ca1e3689e9a0509311ac7ad383a17f95a6343234573f5c62970c9c3fb948fea27f995fffc4e162b55896ed0526e9fe392c077da2082c68fef00e433168ae3ae8fb6109908f0ac9b6fcd409d415e45375f0e6213e16533d626911fb7b5360c10e328c04423a2ab6c106f94ae59e35bea3903e235a29f4f10cd642ba33b09bc7a88e4903ab6157cac5b255e161c43f252ea6d26453128a18f4cfdf3b68e8e7cd61abba085f7c9d99c20f4b9116992f3eb36331f599f6845c4a8825e3d8002c33164799075bc1f9f3373ff49771179ab89fc7aa56cc60af0d474390761114c38d71870fed3fb9462bd63c736966c78b45263d7d26e17981df2e1baaba9dda2b24d2bd5591fcf2892f9984d128084fca1475467d2d1da996848948c52f8e7de949b9e03cc46612ed03d1dae1c704d17998771ce7af4ef8fd1188882497323dae7136732c6332217bea2bf2481ea3b4e8a7cc0596333c5a5a274e3f9e29096d3ab72b3fa231d8415b2d65cf362ea20bf43e3141e62f7c3312b119d79d365a93cde83494ef914afe180b7178864592e3fb6dfbeec3fac5b8bf680ab6edf411335a55e939597d0692c0bee89402384896516361ed06470a3e325e9d9a4279a1cfaf0652067a2d413f0471c4220c5cf8e8291fe8369f6ec633c9f7d9477b07aa6a7a10bd1da89565df506f25718716080a2a123678bd00e1557fce34d72793f8d1885c05f00ced1cea309cc8f0c69e8003675230a0ff32a552f8270cf5dc81afbb35ecb5274655790030677e8dc96474a0ba38e6661b573a873f4b0d7d4f75b9cca52cf29005222cc8211e194710899691afbc33017e9d5948c1cad5592f100ef1c603d7aab6c80e486f191795922c5cd3ba61dfe254adb74e76fff95c03f6d2e423a9c62298941ba8c1a2a42a1411005f574783983b6fb1ec8758318f35588fa83e8dcb112ad1558838ee0693c06bcbb1a7ee5461f882c292bc6f415ef068a64a889c1802f195fb3db1526494436142a5d3d894e606c4c33917721928001a5ec743fd55714cd366a71774b0df8211fde296c86eb9d174b1347cd51ed7fa73309e72edd4a93e3502cf31b1bd00a71e6a0af65dc8e4030e7d4a6ee469a0c67a9bcb2e24873e972ddbea021d65ee11a67cd296a4e365a0fd13e1ded90f219181ad20ea7d3ee2b6e773fee709439beefd52efff063776bf24e57f1cb9e92edb54a8bf904b46f70efacf844c4415ecc7413bf5711a41c1e12f9044b6a882f900facd184f0abae86dfd84de4169f4c508162492d16d858ffee2df7f7c4bc6ab0b45874c700c4636e706a283db902128d610e55e1dd3c41c694bdc988cdbb592f6445495c64cff52e3e37235c3de85204f6775c82a0810f39b0968993e4b7b70480754cccf08ce92770d60062cd4fd68c0dda4472beaf89fb2cbae928892146d4643b8fa367f503ccbc0301a83c190aca98f5c878fac41bd25c9149e0cb4c2b6247dcf99f859d307ed7bd2af9f3988b1079b751967558c42ef424eeeee43e5439a00d16aa98f9a727c699f033a7cb2618be02738c043186eea545597050865c060f5b0a154e494974daaaf0daa8ac4c2cfe1dda1074565c41412902e8546d9e752d4c352a0b4b5d1f4ac3292b687153ad9fcc93b2c3035532c26a96b2a7e69c5f982dbfdc2655cb3cd1f889a2a08bbf883980fc41b454b33572fdbd6d9ef24cb080a03f8bb92a150f9a53c12f4b393051e0516cf233cb9583abe0d26385acbd5bc1e9c67755112a739b9534f4c00c9a55c6aef0621824767528b11e3c066c9adb185efe676fb347c2a860ebe8e58af54d300a9b304d17b38fa486ba36bb7b61cdd655091451bf55d2143e1ec897cb62a5d4a3456361824e428517f5af97a77f69e580dfe6b82842fb89d0c49bd3c5f36922ea7c890c55de4faf2e7c02282c159d5734bdc4c45a84f292c721f2f36461b9d5ef6c107663cf30426d4d1f3160d3f86e101dc7f1642b7b092e4c2ad91c7b8408935282197bf974c975d201e95b79634e5c33864eb958273f781cbc5c6a11fccbd9a306e048c6d425338965f9728ef992a969720747325af16b48884f3a87aaf8d438ac9bd906e97b82954ace7e2ba378db35179f949bb09854e7705fa1fda630ce122e880b457c68670172050ea4ca02f2be8743135c77d9e740bd26194ea78885476dd6ee42125b30eea87102aeb10c3841073436a7d1402c69206f4f0802f9baa8bd67f39d0c27de9db3a5089818a9d2fddb2c348a2232dbf7daab43bf5cfe4d2f73f32a2ed5c5fd29e4a36a25c8eb6d07421c6de2a7f485b98f9baa861050cd7ccbb541368f73668e93208312138a528bb2336d5671a429747517c712e452e0cac4c18d9503b5718c8d97d6eb3a5b8e5923ba106039bf4dde3ebf2a01879ee3b668d9e11ca3851ae06bbcc74802c2393c0c7996907342b4ef20606cb52a39cf591b3fe72d67a34fb3f681de528ed701f8a40e0359db72be58f12c337f8bc7135ad3f3f15385a6148d2255d7a1e5af3c5387d494c6b97772bc9a7b974c46576719db46d5e8ba12e6c15fb3e4294428170d9e4866331eca07f2872ae69ecccc33456ac6855200a7173fb0aa4fa828066652ac1827ea4810e6d5daa94aa30e134cb9c841e6d85d961c2d86c38019dac91487936f77e5658c3234b54addab6502b8870e91df3b38a00b3e41417f55f49b9a956ebc265b5722dd684eeadb3ba279d0222b78964d9e685846ea4a80ef7d10f575c8ce21b51a29e81aed1a935341ad84cf4ea799281e035fb2f6e81ea599950509d224b8ff372d1997cef86eae563bb0a3d887a737045930675553f9200e48c9dac89d21c1a154bbbf54cb9703f29899032d6516e9dcaaca79c75d2dc761816fbb6875ab6c22fb79b9917451dd6bf9ebf0ca8ed822489056a3befa110cd8f5c95225395cbf0157bead8683939f48131e5e76ee3528a20d8323ae619288b8d77dea8b9964b1c1b92d2c49c459614ad50190616339b8281391a5b6bee82f2f8d098c9bbbcd9947caf711366e345c126f6a2ed9852eea45175ce0c0c07d089e70b7d8ccd3fba09f3363311ed55904aeeddef3f00dc3352bd18e24c433ec658dbd02f3a013893885adf7b96b4e1d899bcc91e0806790dd4978d8c59db5d075f40e5744f576255bd7816d8217cfb58c35e4a16f4b1018d528fb3468b5f4163969c78f45a0d2d5a32a281fe9c492870eea5cbc3d9d5f151380e46a383add0c5cf1ea679fd026ee68f42dbbc53d973cdcd530cb381aaa910daee7822e423564a6a757f0160745033b92854ed4133523be8d1683d369b6d980fc590606b3ec3a3b201e6fa64b4b1e02d3d2097291e0f54b4a059519a7b1a4347a3cb4e288f8125d357dae726638b95d4136e5222ed1cfd3fd5c62329f48a0fea5a1553a2b14cba21f55dc969ad86f1d28246d2945c719acf5c590412df332ee4e4fa1dbf6075c46b058f1cd5d873c67333c12c91662062fe879d6aa0e4fe330ef1ed06c92e7a9ed6f74d8d704b79fd37f9343e76962267ce9b014d51604557c3cf429fb43690f4891801fbb738a2901c86a64fbbb08825c7986052786786dd5d414c32861f2ae4ec2e0d0438c6a146764632a3dd25e4e3791f9b3d1e35ae7f4ad6cd17f0e004475781549a9c971954390f3ef0b7d310ded069e6896381ee5dd8183f9d69adce7f222380d918fd70435e5088df77150ac73d820c7d353b6a9f9a8d2a53793063242643a17385555e0eb9163b147fc9be7e208fbed7406b155c4336afc7dcc7a671a89b7daebb6a8574a95e3af8d73b7ddd688df10d0c284a0afe6a64299ffca6beda704ccbd12b24b6176eb888cecf2219c7b0c04b495b334cbb7fb2d82648229856f71419bc62f494d51b6dc9937caa690394fe598f3182d11fbb527e12b1a8017aa004f64ad4dd2f9721d88a2493abf35f94b65d025107d0d1c7a466a08e90ceac13eabee18337a8869507fd1146680ae5c66162aaf64134957c7592c485eec09a15fb31a56a6ce48c54daaf542b2878383b7687b2e63a7f411eb805d1700bfc56d072c9a651448073615590c70eaacdfe158067512cc79e6e3261eb13d55663b82b65b18282c8508dadc1b36776b196890e47cda38bd521ef61fbcaf201f432218df35bbdca3be2280dca04fbc575b5fec3bfb4e45505e6f4e03dc5a2295f2cfdd929619c3a80a92d3ca9e799cdcc6ddc2bba412c43c568aefe0909857824b6176f8728627b898e99b1683b124c65c82bed4cf8e5f177a87ad263861a0ace1efa56d171ec5c5bab5a7d70b4b1e609e70733ff1a72eaf1516202ebbb2517c1ca2a5c44f9f7e72282127882a977cf64faec89a235592698c73bf6dd6afda36cc6f5a9f31220518e956b3d3ced3f35157854c4925006d22bb394e17ac656609acf1f69b0e8c6ca12d63b580cf1336f733e34ce992a713ecfa3184e506c80be221e500c383112008750e509a883d70e0875aefb2029db9689a9048ab9bd9a1cf5955682669f85e164ece85ea1b3296e62edd4f22654fb26bb4758339b66f242faf49621dbb644b5a6399ca344099d9fa48e17aa0bbabc6f9f2c6dd1147dbfc777f53df961eed49ddf126583585efe9023743f14232b1347a183dd76f3a45c5b0afd89896bb12d5b702ebd31061572c10f894cde200b654171027b610c193ebd95e82565b5538bc00f69769f409b40709f969a6530d984e5ea22e0f689912b09cd6f796954e15e4e91fe09a862fb2d35fc38a34dbaa978511e74d0a26011b121c07ff2dc1172a23ddbb6303233f2ab7cd7749f54ff2723673d173225d4daaa092af0b4158830397061b2b27565e1ea7fe2dd456994f32f5690afee26390ebdf8dd67776d0b53b4634bf81e2fe734580efaf1b8e96d5dfcfc51862e12273270167a2c6a8ddf808f4f8459eeb8c0dabf70f39e347a38a943e073375aa435a8b6e69bfbee85320a82952e32d3839f56d31d647bde2c52422dbbcafd0d416c3a80956b1a946dee704085f4266b0a1ba43d043a1a1f9a5a305c15351d9981a263f758ddd46dd4f5f7dad545b35ba6d52a844a7eed36a439734fa8ac5fbd02c2071e99211c00030b60ca0084d1bdee9c330ad85c00f4001e6d6bf884261f632ffa08f661bf35db96969a889051237bcbbd777309f508dd0914239d623b3a15fde41487a563d4bbbd77637b775defcdfd60c3180cb3d664fa51287aaf77d7bb1b932fd5c5342a7805bf53d74d267b8750f94ad783e8dbe5556f5dfeba3eba4eba56377bbb6b92e3e17615c3ac35997e14eabadbbbed9aecee8750c117c7a651c138f6e7d5d464abf295df2136bc826d64fb9b7cddb03b664366d1adf683e817ee21fa867b8c7ebdbb3e62f2752ac2a347f96b4423a625d2e57997bd2eefca7e5d5e67afeb9862ec2a81bdbe1bdac8df65c3dc1f3f44895e5f595c9c9edecaf920f27e9663ef2af8266fdfede554e075f1a86511fd3159723ea8264ab668717c1aec42e52d4ec3f2abb46039b6d1295a5bb438cdca555ae0d8a92156b00a8ee91475717f69bcbe2a6f7ae9d977baa961ee53b2c6a12a571c62217682435b42e17bbded1db9fbb7b82c5fb9f82ad7050f5c02eacaf18750b98b3777cab1727c1a95bb68f17b9a16bf2dee7f4fc3f216bf1f82a5850b7c67963ec48902c22e30feca695c541bfe62081bf00b5c6f8bdfe4ebe2aa1cdf95c764adebb29925d75d931c0faa99f9af7c0896ab5c72a71cffca698e574ec382af72161617c72c180fe102636c3b557f9323e7ddff342dce72f1f1102d70c4bfc9337366aea8dcc7c8d0468ec72ca52da9948f3e28d38629603fd891ac795506512a67c823396229bbf664a98a41a9a44962cbc21695d4c28ecc17f9932f4b620a841ab139383ed6c7e463c24c39312726873227f644ccc7fed4253144512cc2d86861478290c8217246fd15521b65117246edc6b2882145f4758f75aa5fdc920c6c1879ec44a15e8c883076a6e2651147f0a1961563376e1cfab8f9aa17e56a5cafdd5267bef4adeb32311cf3b22ade262a66091c8da1666248bba34b84b13145fb6e188e9457cf7c8142cea0aff5f2c93ee8a504ebbf7e4171f9cc970b27d72b93e0e8d4fcb513031b4a9e70e664b90509836921ca5c4a228c4a7ece0de4190653155450c8533221cfcf9c1a8f1d003aa6145812c8224c66b060c8510b39c6cb0d48184c6a000231d65c3df345093e3972b561244f8ee461e1c99949266a5e20b64c0c3420a6e6656a0a24301561e506e6bc0c19b173c74513368463a7a475b16da2304f368526b5276b9e4dbdac96b4256d29a93c6273e1505a7d35166874935c6f0f36543860202f8bc82132070859e3001ea6c6b14d727cfc04b2a625130bb547faf03411190a9b086661d60ed9b2e843968caa92216710892a18a88e3d2e35b0d1464b53c5228df1891d72bc8b0c1adabc0559289040598749fbd8a64c122986c35196d8ba381eb24ce32dae064663a634d6fbe2a519d85035b34d548c14065bc91c311f1a04ad5eabd7b5ee9e3cf29440a84b72d7b48f8d3eefe8e3a3ca99528b3e78a6009d74e80f5d45ff4c54979460c3b884dcc74153f3c3290575c91591bb7265b3edb4ab8b4c14bdbcb1f796bb2524d8f0cafdf71160e3b2648dfa0dcf5ba169337368d533a07553c919d9eb89e4199fc81816976057765d5886165556bd33131533511876591aa31ad81e206a7834cc77611bf3a5071c9fbdb65374eef0d2325ce24be8b3ba648c7b56ef4dce64c81821cdbae488645765a2228d5844cd5bf333da8ae1409bf8bbd7b78b75915c37ec93899cf1913e1fd2d19641d2bff9e678c832094f2cd4209d7b64428166e9922e0ac644d1e310e110f349c0cca523368c3d618c0d1c34350ee01181eddee9b7399a30a589053943e659ba71a6b267573681bc11e95abce88626f0d68899043e720ba8f524d2eb4938147dfb453a463a7612965c0244b71fdd2f8b6eb2f5b66fdc76356d6241a289f96419a536537b943342df5ef1862d19437be56af4e9294743db4626194389ac6a9a96b56b59bd1addb6ed35e837bc4d99b357ad46f6d0bdc9968c512d2a35496f1039ecd3c890af44af9b8659c7cea4f36e44f6b0d570e863b368bc62be2293f9729d0b5dd355f944d5dce1512ed1c0d277cc0e13e67b3daa5e1527cc37fb56c819327296e87eefac6bd866e4bbe876e02b26422126f23562f6f0c9be07760f63b7b7b8b83d729c8f2d6f585a2163843eaff8a25e28f039e964cddb7028a42787dbad87b887369abb6fe7ba6fddf6cad5e87e3d05e85444ce88b9eacc977a9f1cba538a89a2efeef49928ea44a697c3514262c319c47ddbeef6942be6cbfcf614a0f912f3f6530ae40c99b7538ec66671b766d775af31dfede4add3e6c6d5d8cedd9bec63eee40c9f522063580fdd30e58a6c552443e89e2c57b161ac6a6dde2bc6236f8fa21bfec81b0ebd87a4b6d96fdebf7ffde1d0a6e6efdeb4d8fbbc57aec6e71d7be7c8193163de2947c3ebc9db75514dd4f579fbd25b6fec920dacf53a7f5d47c06579de64e27d2ef1de24f0bc7b21fbd0354dfbbeef35e6bfdf84cf1ef7cea6660e6fa11a44262e59f34298bc4acea00fa9f6cc85661a96216364af9d3bcb4e391a19932caf092b31f19c3d7b942167c89cb964a793f220afbd86ccb2d06916bac936335f58868c41e911edba76dd38b1df647ac3f3c8b15e77562c63a63b4ed98436fb864172bce24d8eb74920634c26b9df37979439a031a714b0886152461f9b2c420e298249e3585da04523489c884a62f013951811224f6c3373054cd9c694b3bc917202bf028d2db438b5162da24c8b162c2c518685656525caacaca8a844191595d329ca9c4e2929512625050525caa0a0984c51c6643a3989322727a55294299548a42843228d4623108c32202812451991e8fb3e6ba38cb59e17653cafeba24cd7715c94e1b8d8b26db165ca685a9645992c0b85a24c28145b30ecbaa2cc75595694b1ac5aa34cad94764799ee39a3cc9cb145467e5ede486909c104e8a70a287c789e48b2736489247472884821b26c92c515414b9848f12325ce1563d12bd30341a91461bce28a44b04215991e063d55ec9e2aadd78609c33353071a4329c5e982166d3062c3f853310cc37eb09f8e91e590962c8324905c226be426251356e630027b2387c8481f1313132fb52ceb9a40b5704c471965bcf53246a5ca22d7371614f3d496b1ce6add1c88b0b5946d620e43582343b055080707e41a56a1cf4c17d4bf19b1218dd9b558b3cc14c7999219111acd71c26c745249295529820d9f290a063da57f31bd2813e7fc8dd6f5a17b93fbdad48c61193246fdbc8dcd46422ddaaa181e949cd5a2966559169667a439d0faf29a942b298388af51e6ccce224f6b725f0338808359b81034777a16d19158c43c859846e44924cf2b7c4cd49c3c988e649922c655cb25661892a3e4287e01e3666640cec9040b47004a120435ec4451038b0832d87440822c18c18a220b491822054cb010638cb2bb37dad87c99b13b7637a594524a29a5ddb17b069452ee1b1762f63eceabf21095efbd76ef09bd61e9cd25a0fb09c342144d0e4bf701239f4e3b03418ce9ef3d39dd996949890a9ec8f5f4c5fbe4f4c43be9520b96d2574a573979a9f4caed78711baed9303f14141b504c9fe92b5d7b71633295dc0fd4647aca0d6f72cab5de5ea484348b5cc0604fdebdc9f4dad47cc232640cd3eb0d5db2e9338d82e3448d6ebadf090e61e412e9c689d2bed1b649ee878d3b0fa1dc9de3be594b0303857af1827a1e087ae1e87b41a905bd172f6e9c29cf761a0d8c9b0dd7d36b2fb237fcf0c5855c476fefa581a59f3032f7d0bd4b031bfa80912b07235399b78d865b16343747727cc46e2002d01460dc4004202964f93b6d3802f7892c49c581a3ac54ca48230b8a3e8d52cef328776f87ca4f1aa651c19ee6554e3bc7d1c44e698f9daa9a17d6ea795ead9ee9a4e4795da779ef3c4eeb3c1c59f4d1e93c3a55ff712a1de90453b92715fbbaee386155a72a27e26ab5a2d368441ad9fa5315590e3c5591e5b8d355c141a3824fafb59eab1d47e34315444db3548612c98aa84a011d38ca4e4e4a99157db5d6d1886447b556558761d69a4c3f0ab579efea3f6b2b0571d0cdf36a15e1eddfbfaf6a15f7b0b5b31da992ba3a1a8d46231044a12794aabe4ff483eaeea24f54323d364c4a8aa9d4892cc5a12a77e0c88a4ea3118ac99e946c6647d64a1c22eb6d9b8a7b941908c2714284aef34cf5dc8e2ef33653ceb28c4412812392c7ed40b9a97298060577f7c07b60e76d998769baaeeb700ff01e281a91482452d7913acfb3342654d7ed309d8b3b4c184b795e77af13bd870dcda277dde8ddb59ee79148249287c3eff624d2f72e7e64459fe9a298ce75dbbbebdd5e2e9ecb36d3fd4183824d9c0da5d96a48f32b83165f70a2ce3ee3911b87d9bdec72cf3e10147d190e4dd144b1567bab918636b0db87ad4ba4087e66a6b86061b32cfb68a3413c5b20e5a1e5ded9ad9769efd7d6b2eeee5dcbb2cc46f4ce3dd3a8956937d432dc63c4d1daa36c949548e006825a9659dc0dea43c85d7b86411f344cbdacaf69a73c3051df1eb8043c466c27e7dd4d33ddb3a311062d16711d531c87439baa51aa510f8737dcbd1ab0dcdb02325b9f5a9665f778e4d6de97bbb5a91aa59a46b9db542eb4a959c371cba1909ce1117978641b7a808b92c06176a2b089e298f0de81af4cd64e940ef41e078410e00d6d3278d1fbd8d75bee3a3777cab2efa60b66a2ce70e33851deb6813eb64d2385b4921dcd9ef49d466f9525adb7eed4022d32053cef74020fe2eddfd6ed7d8fe0d69d4de313f813f8ed21963f7c02b18d981dcc0e3ec3e8f7ed8636fd6dddf67e05718fd2777d6cb97bdfd062afd495e8bb114923913a52e77d218fd49fd733f4ee3b9d2fa47fdf356aff99b87bdb4874b2ef814dddb9ab8936d2a5fdafffbdf187c1cdebbc5028cb361105414a3abde1e85696652f1da4b7fb4e0f7e7be9f6f840cabdbba29344371c611f5bceb230fbbe7df47636d93b78698f0ed35b1f9ba5f7e8bd631f9b8fad1d00848d9cc1143370828f2d5b40ce608a194c71b0061a8eac659444414ae9290ebb6f9556db3d76c39470889146f560b5f5ab187b2aaf44aa28f444d64fbb77dabee3f495ef60b94a47e9874f1b5ec1342cf766da0bb7c74feb28d52a3e59ed9eecbf6b9dc9844f169f729cbe729a130d8eace1d0541fee2152a9448f128552cff3a84769ad59adb56ef68638b2cd40100c5119040f5ef35431df907f1cf81e7d2822d2e9f41da313dea182a3cf36adb7793872d7fd2405e524238128267b52b299c892ac5da17693f1047e74d2cabfbb912ecde923f0e65059b122ec43a5cdaddb56aeca4f9765bb3954f0cabd4bc382e35576e52aa759c12a98e684bdae533e545127c7f88855953c8f69a284e0bb2190fc715c08a4e32cb5f6d64e6d3be9edb449a7a5531c8a9a36f77a8ee34eda28e57ef8fe59ee5e73f6961702c99cd55eefa9c3373e0d73d7b8ca719d394cb9136cd35c49b471b7d72c672d77bb71da6739daf566efbacce4c9effb6e770bea903d72a14dc665dfd7894c9dac36fc914db7131c3de68ee3344dcbce5dd2b37b83c4ddeb6e3df07e3814619bf6ba73b7bb373ecc7157f36c9ac33e7e009196069ab76d17512cdbcff4774578b30dfe7b87b189a216f7681bd2bc61edde64f086417253d228c3dad4186b1c961e47f4deadb2ebd63fd87ff8068edea3f5bebd36959c0fdb1b9fb4f749c3372cb5d76ebbcff2287e05efe11ee06ddf90721ee79d50d268e4f5d6dbd6ed7d1bf5d6bd511c1beb9e7dc320b96d08247b07ad873b7a20f9fb4c77789b280f7cf70f6313e5dd7bf779d47adf4bd2edfb3cba7db4f33c9499eebede28d5b6cdebee9dd2eddaefdeb0946a1dd5a6b6d13e958f2d1f6fc1d0df39cd0b92696d8be93bea7d97dcbd5b9a17cf8536f3f346948eecebedb52e37ca9d62fa10f743fda9fb0e949fbe43e529b7f76e48b377eaf0a7f23d05d7efd486dde3f70bb4debd4b6b101fbc537cea4ecfbdd60e9f72a0fc741a144ca375df431a3dd605c9b5b35564b75b7ac38aed462da5d45afa7978d451da7d1ea51ca59bd5a4773311a5d7d2c7a3ece88f6ed6729f9974fbe928df7142d9918225f8a8a23693bb386b6d7c7d77b96bf6ca0e7ff5745b7fba33fd598cbbaddeb54fedc41dfcc87e23909bd67ea6439b69efccc2bba1c69dba7fb728f7f41a84e68463a3fcbbddcd91627f7a6bd7c65d8dcbc20c64edf494d39c3c1a149c929e6ac319b50182591a24bf344fa8d0dcd5b0de369bfd13b5bd5eee9e69db7ec8b09da82d7236d43b098d28c64a1ae91193747bf60377efd96bd753876f70d8c391defb7743eef16ddf1eaeb7eaf71f3ed4537cb2a7f7bedd933df72db3f806777a79db876da8e5e11ea25bdca3f4ed3393bb9496b651a4a40a8259b6d18df39e7d0343d9768fc361dd6a287ba8de1f39d4358cc966fa0478b8628ab9eef7ae7613b5a139f49885425b88f36e2fc7891eefdd74b14e79f8a10dcd75bbc6dd1ee063ce32108719c8e049f76e77431eb9b321eea6100e79944e4a38fc91edbf874c3814dd8b273547ebb32c3bf8ed8a0ede1ba21fd9e3eed9789b6d34ecb7cbfdb8c16d59e761198e1385c30cd02c7328c3a1cf4c798ae3478e8f41fcb0a4f949f1d988c9d7f7e9d58ae5f0d73729babeebf2baaeeb3c7c5dc44d527a123d985db71dd2932befdde34a7ea661bccbdb6818ee339d92ef6e4da77870d8466c628a4159ca08b4e19015b6626ca27866628c266dd22ba48dd094c5b044aed6afc6a185758e8480340c860384a988cde9db68982cd8503ee1c08ee1207285fd66c523ca707986308f4ecd03e9d4bc69e60cb1c252175fb0615cd2d6112bc051a2042272e844d949a58ff53a64be504bbb8f9462be08315fb29989f326a6200b64976098b526535c92b5deb488fd0822c2480f84423f3a4513c0c5a01f5186cbf4fa44a04e94b1995e2a1165ae402f8970090399864c1fca3164fa5076215329852ca74842a65c963ac814479fa892a9f5b09deb126fd7bc3f36943a2af43b3586c416fafb043b846297f499424829268afee8a09a3cc844a9a53a211d580b63132575e4a9951b1882944c21043948304308da27b70fd044054d54c7051b4aa06eaeaf4929e62402a353856c222ccd3e5186abc2a2204e27664e9461c9fdd93d6530719d3913d55fc9819539081334dcb507d98e8368b72692b9c444293175e6763bae260f620b1aacfcb952d83851f28a896a30581994fb73620924a1882d7d202020a03ec6e44661dbe78a4109a494b25b36edee96e10e748724ace1880db15c84d6311912369446480dc417cb23c8f4024654c7c49656c2368e4e755eb9810d634c0d13638d189638606b20a22e53080af632c1cae7d0215afc0e98e4013be325cfcf2000888619000160dca0ba005a1ee3383cf5500eb18e43df7ed8f8882d3387088a066c3873a65c71df68e623ca649f8fc9e1f34088ac8cb758394bca63343d4610dfc5637f8fb1616450a772b827ed3a3cae64dc93864f3964dce53a72b80e39dc25870bb1e1321e5777795c692ff029870e77b90e1deee2722101b80e38764ac33a5caec375c0425077390521b3f65006659f993208c705c6f8c696f95f20266a3e07193776aa050e51319d9a2c388c91658c1cca1d142a0587bf099770682d883fdce1cc0396c31b96475c1061b55010190efb584e177ca8b0c72bb30a071c7050a954383cae04704f9dbaa7c6a71ca994000420800bb1e1a947ec12464b4b0b0cd461a00ee34202f096c7d50df7249fc3e32ac63d497cca11e332aee386e7808500e0375c0616c2040d007ec385d8f0183711a0c88c1d260a35800be3dae8d4fc0d57755b6e8c8bc3cd38dcb852ddb88a71e3ea861b572d37ae60dcb8ba6ecc03a83153b7621bc6c400f4c53200ae0dd7c5ec7a9eb042cf135638810f0c0cc330152a8cb1d1c34f8c12248001a358ec819d748a9e3040a6101335491e2801b69a31cadcc0fb94353632c6943f0d037e8a5aca020cf96a7b289bc8f31207d200db470fe5111964c5555d49ae069781c4969969c17af77bbd46c05bd7c8106c057ebb427440b5fa3ec4aec003a1848a9f6c3373c5272c6f39c87ca9a71cdbed4fa1930e4ff4791e0d730a611ddbedb7db875227f450e684b045c2d67b8b74f7547fb3e2eea9e209d07057756abec34150d8e3805cc38a09eb71b53d443d5b6df71e9aac0d441932e4c981f842447cc9260b268e9c6123a708220c378fc8b3a661e28a1447b2c995a27bade65db9d3a91bc496f9eb4a2457267165114964a2e63c42c3b29133e4084a105b72e8ca5c8361d95a12a2c2528cc3c6445d2558f9309b1a0c8a80c4bbf6ffdc4a7c7a1082247890a80286945236866158cba6dd2d553ba07aa28ffccf44c5989999989923e4e4d0443125f24420030e7549b17a58d775cd5f3c72c7eb560f375986cfd20b368c3e34354772297354a2842a72d330810d314a4513ba20a2020c83a80917801f20428250980daaa0e9a805d49452411c74c5c1c1c189a886b1d13895cab91a89c086126789d9b4e79c162a478e20c6e80b4b1c598279491c216cf8bf982d67cf77b71665a62414d8a9c4199ee059459410d8b9842a46a0b3924300421474565189108ec0596975a3777ae0034728b2babaf0269f6e5839cc40c630264a7e07497a70566d25923f471ffa5b3fc1d573f55cb5d65a6bad97cfce5cd26dcd25b5a7f6486176b7a58a735e2eb55eb556975a65b89c40c7d2c9eda3840db71c755a4746ebb44ef5913b5902c9242a974deec82676e4ce04ca5d8449c4c2049a4016106850ae3ad3886d64a241330434880659249e39ada83ca8a1cd933ba886a7791ed03ccd9306ec48c67ea8851db17ab6755380381ab3bbad6e7aeb5eef3bdb6a2b05a84522c8735a279d2b03d9c2575b97b6e0a433516dd5478aa38c98b47a685c92650e8fb8e4013c2a0f2be877af29b6cc6b328a177cc0a7872592f8acb4ee001337f05969549ec00849f05969b5e7a70547569ae5042c663064a55d5bcf112772561ab60149c1b3d2b0adde134d0d129940e4089e55454d948cd911988004243b44622044d5348a61d8abc51e0a65c65e6f45f7fb41cb189edb7ca83ececc8a6d990b43f7369bd1639ac5a4155c8dcad19076648a17ddce46928c5d912790b442c6980f61184603bbee0eac0402ca92abd41a4111180cc13ccc091802e8ad6953656b94a996dd588f6d974e1486d5502814ba7693af4b2e4e1476b2b01d65d942a0efeef3406fccd7e79b9614b8ac895dd69453f6b02c7cbaf1d97362cbb22c8c7aa19a2fd3b22ccb0aaf8ba27f81cd2dcfda018cca4a9bd2534a69a5b23695b9be6fadb5ce1ad82badb4d25a2b0cb6a2d9a78956276dd95d67d3ee39273eddb8c2be2c8cfe4d9b36a530daaa1f9bca6682165f8419a5216d02413fe9a7920833833a456fd109058c760eae4b42b752a0dc9f411d6d6d7a051b5e38b92b9389eaba24b6583d9dea3fd7ead3934319106c587d8e4c597daa143f5950cb8699ab89335f84c064bd62a6ba02a9c0ca26dd6448ee954ca9414d727f0275aa59a2b0a16c02947b7234b02c9bcc972c70e68bfcb5a467db208727d8303619126402992ff4dd6f988a598093a392152891c3d81395a8e08a091357f3a1b5d1b9c646c300d17303096c189bf467a6cc01d23038ab6e15489181f851d3ef1f551d42a8923a3c35353535353535353a74e8d0515353535353a343870e1d3a746056a5d9d1a91361d69af00e150cc334b15335db6aad12c73376fb7d1688873db1a7fba14caa4e81a3930eb2a87ce5a39b43e58461262ae4a715dee4ec212b6485db0f1277214da35aadb5fe264b59bf6119f2862ed946c3ad6eb20cccd95e10dd90268baec53967ca34cddb9626839fe91f7b4513a5eb94bf5e1a581819c60c7dc0f0ace63616154a555cb0bc45fccc2469dcb56ea41c05ff403ab7e3bb218cfc7ddff77ddc51fe5d7b7139eb9e4e180573971edf1e46b724fadda2f42b2cd7acedbb28f8a44bce87ef28f8548f727bd23dd57b2761584a4a4acaa9621a1bfe22936e714893519e82439a9c72edc5a9742747a3f4931394d3afd3c9c9bbaee34eb89393128e13757f724d076fc82393aec9cd0613a613755fdcb89ae97b4f9484618c4e718fd12deeb18272c38a7dc0f856e88a0b96d7d3b75061c1a1cc2a57b1d7ac8ab5a00f16ab60dc8df644a09d74eb0257fd66e572c36962fc86cbdf80c3553808ea383c48001ee3372b19f7242fe337ab1c6e8e1bae3acd0d575d5ea5ba0c1c0400573d880dbfe13738a860c4b8a1c58a2013c523771afb25777cb082dc30bfcafb53672a315fb61e1934711ae6f44e61d189325c6619819d79f495539ffebd61b0563daade84ca4c54131657465ae982275dd19da09b8e8e4e5ca5c4d549461d2327df3c651fddf4947bcab08ed14d271d2b4ff9ca532eef094bee58197c7a2ec98e53ee12ecfc699fb9a46156c209c40903e28a470d5e7918814ee4e36af430fed8187de561ecc98d250f228c5e4294913bb12507730711e6e43d97903170d00521c288c087272b57f248cec8c953ae3c5d693252fae476581c9c21fd8e28ebbf574c599749b4f46918d23b943ff2d3a7979033641216b7b85e4c5967b12e77a24c8bd8622d911b473c7ba6ce9da73b713ad54fb9132574d3a87f72a576a513dd133b05fad0c82e87b49cd149e4feaa74e38a74e36a74e30abc713564a56558eec49639244485c5b0c4012277e2e6dd78d343a2c51761c5e9e6e9d69945bc775c983af3257a98c5a2a9105b0a39c345c628437c41437ce9236223117b07b17d109f105f342a2219e48cedfd1e4394e1de2d3a0bd974c58b1332cb3b0849e40c1a19830511a6a65344fa501841471379896cc402b1a53fd7106180c89cb9634413816147c4549f67e6d24397f4eb82b7aee8d45ef2ecec441996fce5fe7c22a50925a223cad01314f9294293d026624b9feea89cbb31b69cde82e5ca23220c7d424c519f4e5135649ac54579764dff9158fcfc5891a3613ab9d65ba649148c541a5deb596777727a826e42a49b944824d98488dc44a0373a6dc22b92441699454091682659913c512672356acfd979a73b0d439774aa0a3983662163ace02574872ed9c9134f21f7357be71222aaffdd898488f291319634cc8c22a65a362137cd625291db9b40b1a53f9f0841c1ca1e9eef4b1a66a3594cda853c0aa91ae47da88fcc51395d79651228a66b335140666a1ee7e7e4fa285d1ca4db0316728604628cd9303444be9025164d64938804223662cb3c7861c8ef456c51cdd4fc8c99491a8bd8d2e76e8f21b6f4b7db6490319e1051fdec3615b1a910532e72464bf1e284dcef20c4543f0ba62b4c468022a67710db07b19188a8be75fb8888eaeb6822d3c8199d8405d146ce6822b7409f202ff1851cce1d34c4598688eab7b8f3e72ec9730d3175c41772bf81763ad56f2372bf89c8fd9d9db91345c5595983145738d13384123d4284a1064e044d9c23e008e28c789f997693b10a9ad812438018bb042b63da0c7194fbbac194410b42ad71b307c7449d64287778b7a7e9fe5dfef3b0e9662531695d550c8aa14924cf1c2c3bc2863268c917ecc4456822163f57f830a9a24891bec4b00c8aab89716856c8162a08c68499420499a4496654c3c8d5270e0cbc3083b4287a767777239948827690c8254c41f2874221c2483004fd04fdfc502c836415f45dfb72466155da54042919423e620a9f385f26206f83e509d8d82c7d105bfa920731c684913b9bcd0f28acf56a447bc368414131413287f1c746187d76421782854e8f248b7c801c95d820284b4e869e5d8876ec4242cf4e7368c7ae23f4ec9403bb761dd835eda1ec210d9be46dc8277cc427b2fc4c1256eed022cb5f3631280605d13c658eb56a1017689d97143a4a7c8ee8d66182c57efdc77cc18c881a4684636a9a0830b84294a1292da74882fc81d40167a36144efc7663981053fba10d149affff9a9a9694084912d883f3a37b042981042bae8f5a7799ad1c1d3902e7ae44e13e718611007618206bce83a48d8060e0262535c31a1cdcc17d90f0390e56d8833a40a628c3e6647a4cbbf8832f4f2a4fec804da9823d8981d6acc6067cd34733eb4c8da8fb78ef4a4adb15d5e9e6e5ce217b1e593f235629638a4f9b352fbd4dedd6f363314ea50167746091595914e8e99b19161a973d56031ac424dd4d502454d1d6f762fc8c15982176ee0830023da6d4c549f6a60c3c86363c665c26857990608f20307a771e41ef33e54f9d2b02f582c54354723b47d08eeb153563643eb4395352c6b59166ca4481c59e28989ead9d34f744ff360938707db393251488e44196ed607f1aa77337faad66bc534df411ce47b909b5588cad67462727227b6f4e3ec79e4889c3187e496526b91687411e95e0983251c4464799774633ad5a3eb7913d38c46d6da8b623ef06a7434127d640a32ba27bf5b9ff634e02df80f07b1074728d14d28d3e88ab0195baae4ce13d4fd79c57ce18932a1dcda73b6b68d63aa8285827c820aa89f9bb9e9c3710f4598c9d3a966429e3b1665b673aaccf950a5f05899b54f80f61139033322cf6f9916ac7422893cbf5db3b6d8309e05bfef8237766f573e617907af63748b858c22f81dbc8eef2016c2040de9e085c44e7d3ac07fffb01012963dab4943ca9f3cb17c42c3fa137d9f083cf7378c45babd90d2c15ba4dbd3906e71e920b60d630fdea29cc803ef8db090efa35b17f54b570eb10dde3b0de85d748969be8fb010fb9177d169bc8b70ec14f8efa11c228b8097c6298777f034de413c318dfd085f744ff3403ad51360405c7d380810f866f5b8b28f40a25be3ddcad8192957ce876885acb5d44f9e7a2ae4fe09a70f0ff3dcb7abcd356459a3bb7c8dce872a6fdbb34b6dd2d0b013b7cb5f903b3b4d828096fcf8c81596f1e4892d54d81096488e4c54b73c92fb579ddb4ce9014e963b71623151f44d66d0bc42c76727f3c1ce8e8f4f94b93f55e820622193cc9c91800a9c30740089945276cba6ddddd285ba58392af14952440335c6ce52e4f0b109108ddd48f639e79c73ce89a53c92e7e90d6dae4c7ffdba82f0ba2f735e71e412ec2573a80ce51902a28930f597e71ab2c441c835e41313d8d02501d488f6992977e6cbd430dd7246392a392f68864fb28fd91158ab4a9e248c01a358b52eed9a9846bb8957d42bb6a0601cb1e13699ac0d7d62d544c584aaec32511d635355a1abc246faaa186ddb3ab3b6b7296742b034dfd0acd6758955666aab1709565e073c2d833e7264a29640f54347e20c2901349b0911466aa18166048a2d68b0611fc97d7aa49768249d2476f7916f8fab1cddb7ebe8be5d88761d9c90ed9926638c38fc68ecec9eb28796cb30131ba23cd61128a04082fe749289a2bd83a491ec20a1d612b1859a7e620b7d88084b7f58320dbd4c2189f4ed2c456ca1a71963622d1c62cd23c40ca080a20a3aa0a2044248a1831c244890f8f4b060618dd0914148924c89058e13990faccca14492f9c0ce1c4a2448649034431259be590645999bfb210f6820f73128260a934155d8cbe201425225164d8276ac8462a2ba39196465108c9e257a7a7a7a321250613be7f280165f348f5944eacc588247a684953953c26648d896417c8999a8b89a1151fdda84c7090b6c189b0c6131c1ac41c87a6b96481aeb03e9543f74b1192d83886aab890ad5c407045fa062b0d288c61796437070e490a6e8a8740d97b4450b158d0000000083140000180c0a874342b170409ce7e2201f14000d8ba64a7e4c18c9a32087510819440c21860000000019001860b48d00628607bdae1afb40ffb997e327352707c5c08a31b0c6489061496de9c085263ce1ee420974353c2c846dddaa2970138d3b18366d0fbc58a44e6f2755c2a99ec67717316a3968f5c2933e9d4a07a9509afff7b4d8c0072a1b06556ee68ca99b16357f77514715f4bfcec3981594865e7c74ef684e03124c027f2215aada7ad1c71c1d48158e77b03cd4fc2c5f47fca94f3a3b503a6f068dc3a14d770f61384a75e19e8350a5c96202734b3c1e23cd2563cc4858b4615c368ac40e99074db6073aff19db91e526668a4a7072c223bd6307be64872dc40879a83d1ef899ce4bfb6b618c07a9be7434516b19268ca6a26f768c7541a9fac9b1395b16992975874c8297f399d93b2bc8546075c5dbb88b3094877b48c6ce5ebea85d175ac86f32b60086657cb4dcd95ba2c0381608813623b9345baa6bd30eb626d18c83cbb42ec939fb0fc90d535306a2a58205c5c511a1a5091ca9ab75e852704458f038fae6d9bf35f610fee3ae4ea846008f79f9c09d96d92200c10fc09486385854f58036b3fb8e650b2e1ed5cad70fd2614b82dbc82c225195a5c037425d75298ec532e34b3dac180fb07c17d37d9907ced04282a239fb43ab9e2727a1fd3026154f51b048ac9ef95c035f76782d4c597402d3a9d849ae540749b0f83a42a8d6d90c57e6d61464600f2a1f95574651e19dcef569e2995595c1efee95463d23adb4e1dc57d1a2de6e3917041a4677841e0d66b28ba49bc5175b51c8543c4b9f85fe166ed878dea8ab12655e8557bcb1afac9ea702558d69e64262721add1b9c861405efbf33ff7f7cd13d4cfeb2c795ac646914ad58a8ccfb8d15c581c7a7d40f073cfec2318cba85327ba13f730c068da5b202a9aaedfd3268591242487227c417d30b10c7591f0ac52c27b329bf3c217d124c148ae91b67433e3bc51c1ed8744d2289ab68817f53249a9e0e9ac40a71fd9266f51df2eee8a13697d6c8a226179a17e5640b9319b345329d930945838988a6d2b1c567ec232cec72f6ac0c7b9341589517e5ae624c635207b53de01dee30683cff6824f9fd8cb44cc91d345bed990a0c8dc0dd2545aece2162ac058f5990a5906c64d92d397ac8303eb0409117a34282b1c09f12035caa5027c31cfcc875681038f0e955ea9954fa93db312722d52529df8292fbf3ffac215e7c5615f635306ccabe11c97c65045162946e63c9057a3898b4e1b6782efa7c93a4ec95be695438ab73b740398d623f8d223a59820819336974526c021c28877a57bd7797f39353d1fff4350d8e1cae2164946f6acf09304d23b83869f0d0486503b7be90e812aecb82af5ca668363cd0a569900275cfce2ab009d49336b63c6a101e24932b70b82e239e16bfb2795cf7261e54dc7b53e49cced8d0e171a252640fcef3768e04be57755ddf9627e0143dcf5d536e1254271380209a0b18a602e8cf3f9c4d1efe12272acd42c954ed60825dca15a191e53d4a3a7385ba96ff26766a173d0794cc660df0fe26156fc029fab5ccb1262bedae6bde670e071cc74896c7f38fac9d5682547ace9172d764fed0504e834d9629ca27d0201d5c920e42c882c6485eba7d9224c6940e44220959dabb4a007a6eafea42f4b7b83cbf27bfc8705919388248198133531ffadcdcbcd56f9abc1822647196122029a7be49bc44c70f27d12df41546118e97af53939bf11e1cebe8340b1782d65e3b5875633abf152357bd10787073e6740d6ec3974ff5b4e4dc5fbab4a08ad7b963ee6b44f8f9e2fab6e2a120ac1ee66b8079d50d8bbeecea329d7f8f4cb4abcc816d77d9a3a407f0c786f636303394212d41cb90aab2a242cbebac4f6fb8b68f862c64f8995485fb4d4c566c09c6c928cbf6f4f4f775522e4f0671122003f418dfadeb5849d0ce0d775d16b852b500ceea511325f59d408aac02c3eaa75fdcd034ee001821539ffc9d73c79918ed6f981176616bd19494521bdc5834977e4466b16a8493e390eb3dfc251ff5ba78d2f1fed70467b114f50dfeb7969c8561238d03f4af733406d58a33b385cdba9062060e76790c65d4e4a652e92536c0249c3bbdf6cf9bcfbeb8a2317257cf0f0bf72bf651548189d29d586198623adbdb8329db4b4102da44fc92c9101fa8d40734f338817a3873031052d18e4e31cc6ed614822e4ee1fd7330e85259e1f6bbc3a49d534e4ab8b95882641385884e24e4cbf0f57a1551935c6c313910e4d24d957e54cd17522035c54238519f022732cf265b1fdbba166561e8f75912cc26d2389e762fc476b5eba6aaefb2cc6d87fb7742ab16807a8024882462a0b81f5f003e853a8af3760165530ca366954ac30601bb138321b6091c7ab1771b82736818a41113157ac72e446cdc877a21884d9e049dcf0a1c8ec227d75518a4baf78bfe9c60c8a51da32312b2bd00247b101c3a0c83b8677373f1b5cb4c527dce9df333139d690971ba4d263c570d21d7e14e22f70ad95a12d3710215d0c4ee0bcd3f475c2aa0550934d93feafd5052ab294d3885f34e0a33c1aff9f8442fcde8b8288da1144f2dc59cbd148f0ec47199e49684cc99fbf0f9d34cb47e86d0b42124188a2a9608fc1412ea4dfdcaad5a170a62428036542499f762a7368ea7b4ef9929636000e27ab13cde83b4c686b35f60d700d9b92b8c81684a068730b64dc00007587f7651d9316341b6707be0d78ca87c031bc46f60ff0a6a3feb8fb1b15d0a8b3024a0c20cfa818ffd458f5f386dd84e71df6b8561f86bfa9162384394a0fa5191dad7fa402ceb7eaee813d7e708a166012100651a13c761f4fa95355414d5b00e99116730ed2ec688d398b195c8691212905c3604e4b3fc171aa9098f86281bb1f2fcc8b79bbeb33a876a292bd1615ffe92f6875e6bd4e92e30350bdb859a6a6c30ed5ba14932b74540e29e3393735ca1805634b22e24f749396948c614e402f7221eb8341db72904d642b8e90f9d81b4c19e7dc8c6b9044f37755eaf65836cb796b7d5b2f7a57e79e95bbf458694503e43010ecb7e15194e2e5e8abc69d410e9bdeb47da050e3cfebed7bb28d6a0eda9dab852211e3f1c8aa61d4a6b87ecc2c0cc3d96200e086cb06a057355030262593a3aa3aaf06dcc05c091e4115da068d87211e456e48e8670f61537cffda54792aaa078bdb8a58f804948875811c155f0c5c6bf021162b14b78f017ac709d942374e81675084201990bc04711fa700eae9bb4139622beb02511e0368077aeed34718113eebaf255c0cd9e84897a0216f8f0d80439c42ce08b641eb4609550c1a8dc839c583a72b46c259c364135cbf95519ba2e6c07bb1126cdebd02c29e35c413bc09f3d2ab7192bd49dd8322d8c41e4246273c63db828c8c4a871590115d0c47233742654423fc0565dd3f745f59ad2800020eba82f74049a69fbd2ae31f4719b8744594a360c7053e621819262fee971119ecaee6417c5a2e40c984f8ba0511ce55283841a48ffdadde6034f69a0481da5356d607128adf1239e86c8060ae2ff24a230a3cf6869e0c7a1dacf47a5d7b523542d17349c9f5512b4ed8d9273d23684e6c953651c35a4aec9ad9d232fb29ec33f26213e242950131f49b21870cf5d13e943f498db245856e3e20631ef9e438027d07f35b8f216871591b2498410ff4cc5c2a49fd6cf44928ecd20a970d1716e4e060066dbe4465c5475512cac110eaa9ccaee36bcce727f7571b18b702ffcbf630e2d839008d363dbbb8c04113077513cfb21d616b9e8a57975615e217b90194a05f6e20959125dcc43562ac889ef8f15d87f61d35acbce8484a008f1b3addbec08bafcf7c5252c390ea0102a4d8ad9286f1c33b23890c77628a1f1ede59e88e41df705e8c7800435e2a33026f0c481eb8fe318112365407d4361c47c2fbc6afc9ff2a0eab5312528a3a090eb894341c9e9a56c496620f617703bd1dcd92d05b2eafc65caabc42ce362a121fd3c8fbe59c3263abf8dd979c0a25ae772e1bfb55a153566093f4dede7ecceeb347b07d90ffa5c990a1226ef0398666faea318efe3bed2be149e85db476684276718c4247e04f7970af61adecefbf5d971c639af891e5e079eb98e8e3eb27a8d1f540e37fb815a27205661236db8425d2c749e1a7fb1edad2cd9a9ce07ef05633e0be4c8018196f2f738e1f1db99e8a3f21655858e9309016c50007f9f38593bb32b81286d4ee4771a64bbc1b59e259b467a845383acd5d51a285d68e63e56ad0c58c1819459aca1af798c53cf05bf7bbea429fb01759ecf44521394c9f69b1c1edab754c8f752eb80942976c1f2ab15ba9f956d347d036d9fe6b1674dadfdf48c127bb2807d227612c209d8c370eb07c5297abfb93d671c7969004f132f4c01a378483b45fbe74bc9dede740bbc3a04be43c19e2f6baa03f662661e28360803f26592a234abb6c8852f19dbb10db288c75f89ced5494a66da9eb9e44d4bfde85244d64702f1182a9b867d22b0cd8690b37ecc1ea0532650ffcc3a03556e49cb0ebd03d5851b10fa199244231dff624088c4a9f6d8f622b14646a672e4c6fba9ea28bb27e6734922b49398f15d37ec9b6ef7f94916abb4df63e5c802d0ef67289a5d157aab46832c5f281bebba26b1228b94baf9bf8f8240c3088c86c6f089da104c7106f78a3d13aa6918e5811cc74508c82ee0b472d6e55421d64ac22350535c4465ee37cb3d1d062ad3bd5e960ccfd89905b62684f9f063c239536c8f370964f866e7715ad2dde7f03729dfb203a4849d3320dfe406bada3363f2fe0e9de3d9a1928d06932471682d598db0abd0e28bff96ffa41b966c8b162e335c1ffafe241bedbdcaceee3f695c9bfe9381f35cac15b0c38a578a0da2760fbf93f4eabd2d1ed969caa04709f57dc274de1b35120e30cc68825660a31e2a5134f749b2c25fa4273d21a51c7a8aec8987a9780c1a4c69440a37745bf360220bd646fd06a710c3f9be977b793ed9a10a89372cfcbca3175001505b4f2dfdb4dd6c00fdfc623299202b95e1492935e4eef5241c75919cbe1d62ef7d49dcd22beda0da22c26db1a98c3250207254f40e2ca469788abe28e60541c6219357e7cb6058521b352756f68d28c883f70310fd94e01178ca2925848cc952f7b55fe3f5b2e00592b4a3e900032d46d1bbd40262c05a78ba3619e26aed24bc4b681ff97e0515e909e26fbb8270a4c9d97829df39d22f1036834d31a7b06a9a398f6018cca5a04df981a30ebc332ee016527a101bf9f537ac8ef50cb5bdf83d409fb1a96d72608da24b9993687560c33879029266a53b397bd6621856e510280ae19da8dffb1da775a51857979aee9c1b2812f7287873e8fe6b8fb3f95dbb15d233a1f3ed0a818ff671c3a395ffd9f954674800c3af45f2d94b8443dcc1f3104766083b71082aeef22f40e8abab5208af1f79ebcc19fb841b0f3c0ebe5ca636a3edf111e79404ff15cdb62c9cfc5a3d39d76d5f8297a13fc11d8da2e40ddacdb6f4ec0b52bcf098d0b544405b1b243131c23c0d48e9fc7e7c5febaa2a2875a8e5c421045d6dadf5b904944468f5a0fa93a71b5c41dad3fe19fda92723bb4dbd86e4f7f3429e66fb326470daa4d047c3eb23c1abdeb3bf91a16b67e0fe3ec023f76fc99c329c2ba05d75812b313250beb0c05a049c04dd74f9222db0bc29c14a2fddf76a60012114f913498285289cf7f96149c1e66bf8ec66201a80ba7b479d478ac6fe18aeaeb96a692bf36547c78ba32e97f59eb52fdcdc7f1cbfcd9677cb896fac05b718cdd3492336468dac9da2705b63d08ebb5cc8a9e52983358621c5c3ab6701d80e7480fb10966b6a3624d1a1bf94f7862477df7abf62af5965f984398216d8f4930e3c6160c0b9c9bbb771c5306be56a5a10b07973207dbb42afdca03c045241077a37151104085067f980f54cc70544b16187d45231a718018fc8131182f3c429908e426e7b8ead2439d1d4761b4d516dc89a71b6cee25ca89b3083944b19268bb4b51a2c3d49c020d9b1d1592297553a38cca8644e2231983fc526bd7f9a15cb36888f5f08a6a8e21df4c959b1f6b6784d8dbf7479821c47c27917dd804b79cfc96986ec1df61b8bd04d7637ab6b39469a773acce47ac4d34f072c54466c978692a1e3c85a7975f90fbba33cc38b257638f1069cca3b47ca15406fba6af7e8ed127de7f3074331f232fdb18f12a86e29e7f86ed692c710c6ff74e07ee390268572264eb0df42e504801827821136c2c20b0cd670e9e92b5fb730ecf6c6abe183253916875f1c37c40f035befa1050afda8adb3fc28458aa113810f2922229e229f08c299d4a8a8ce0ce1866d1a0dcfe7bb27f21acf24c43033d295ea8fa097b94c83f3816eb19045e6b1b39ea906d251993d53d55480db661563b1ec6b0cb36d03e55a016cdb89292da34e08e179e2eb28818d95ef5b881cd035c3dc559934b9d8bafe4bde69b865cbd9be2ad4ca7fb234274d95a2b7325618d106e54d8b4ee736132f0e0854727c15be2794b37c0414a54d89e60cda5c7189f213a28025646c555f2640759771a6526538db1c2a41c3f95e6e231ec59e6e0c64a3f9d590bd703af29e7546f8c8a4d680e8bc4a756e0557bd719aa0a885a4583d9b1653f6e01fc9e65b225c12333a3709809e431f8327cca9efb862253c64ca2683773eb78eaacfc1c07bbf07cb0b987f0bd82f2ae3ed7ccd3493b4151d7748fb640b8998478008edc8ea9c746ffa54703f235349a9f040e6866b1989113d064d0bafea15a88625ea7d09c923a8940b74944c73bbbdc9ca18a3ce4ca53102436e24a8bdbcbf7e2b9ebe3a9db201bacfee7a7fc532af8e73a1d7f5917f63b52bf9c36cb5da2c5f4c8d90218b390ad98579f7f661c2afc818015eefd707c9386629e8f4147ceef50e4a4a9de77a574980947bd891146c0f88fb6636c1025c3491ad48395fc1104b4f819124354c26347a5b03230cf0c0eaf54269bfbfbc7cf3ec23480a6a9909c1727db3c0392993c332c9efef8565624840dfcdfef80681c5d803009cd2abc50e0d33a5c120ef1b93ae73756945ecc203967f25598a90c845c2049f5fb8da3bb952187946c820faaa7bdd84f24bcf7d6b3cb630d2302aaf334651025875b762363f3f0d896c0fa43bca885c1725411b32619c47927d7cbecd6e16e4bc216bb0c6e8fd85fcbd9786abfa4fad9f0803ec11dada9a971a319c17e5f4266ac96251ee9a30beb28ce8d6cdfbf7c25596a48ed12112499580306b3425989eb52ac3439b96916467af337b6d6831de854af6e81884c80e187b9975ae6ad9cb9779fb310ffe17c9b2efdca7a835cc3532883e36c444b9909910108a65e033abbb7b050431f5ce27049108b97f4a3d8b5efe5e2fdaaf340429b8e4c548468f1bb312b12832c1b8dae39db1a438b51bb4ef492594664aee7e694c278b8a7b77de51fabd9fd938265dd22289ad49c5d340939f9e950a7e0d14473c7448eb6bc8ad2131223cdce37689592259ed4e6dd0e32e8a6344f88928ff66a1f5136c9c4b980693be0de0af7fa5cb0919523d55078a17eb8efe8f6b5b1965d35f5ef237eb8e46e2336cd437c57c892393259f241705b7bee0dca7b7a80cd073ae8aaea5d8a6dc89dde5109be94a2ed11b35b4d8cda6f8d61c055492e92517aaaec534ed527c0fefb482c8a2288de0330d6ea5094ad2838aede9c554859fa0c22623d18fd120166e49ecf970e63a1357ef70a7c830a8ca64cba4009d94639b29e6c95e3925f522bcac6b91b4d5ca75ed383f4dfb5063cb3bef9ca551454c044d29f618100a7ec1a78ab22582ee6472829230fbfde79cd54fbe284d893b5e5e2742b60f43b91528f91a1da137d64a4a9585be30b30e8f77eb1be799d8097a8990c6ec04c9d6f7f1f99e8678cc4f21a26f519aa3c3a16091fb9470f882a95957ef828c85c6415925cdb80b4b08b706e25dfacac1748d2cf3d77f96db445be5aacfd4ca5e755606e2601f042c45787794452f91d7a91bb4f4c5e42861d542b75ec6a10017436a247dcaaffaf751cc0389bbe19a40991c3c33ae166f0617d0ee1a013c49d5c4159f1f068f1456acbf894413f8330b027bc5845d93c84a5666e2943cd39b0f4dca5e87e38cbc53aa5e904d6ef70bb21f0b6b2b3244161b990d808a66826d04eb60f50da8d071d7f47e4a5c3fd2d9f7b4e1e0d8c0bf8a96dd7bf4b0d6c10ec1fd0aa18f524d97939b2749e4be3edd4a603afbd8be4312467102d1c5ef0f2107d514bfe681f5d5b64702bf0d4aa00643d1e13b30512ab8e61400949639ce12063e6dd402666e3ec437a24aae3e864835d4fc6fb38542625f60d5ab26bda6d1806e4b70f85b872819906c3a43f5a23c084962958adfdc1eb9a84ac70d4ad690306ac0c33ca56a76d1bad5bae88feec72256ce13578550e1a6b895ad964073ea0c2d571aced296e48c92e1fb358af3f810656a0da77c47a681eabe1c36069b4c7b0299b0faef33ff828eb1761f7f75e2a9cfe921d882ee73d262050d383335e924e0588ec6265f1b380b7b3957ef0c9ba0af2a51729a0d16484c9263d7c2a8e960707416ace6a1f31450c8263c3531beb2ceade9f05737180cc2723685368ae1302cd054111ad9e82bfb0ac0054c1946e81c720c8f83345d749d29562debdc520d595dff094aecaa3b71307017e3665c50f5044747a4116feeb6af777ac391d60735d04739d6b6ffe42fe970f01aedf0d308d965cd4bcae1083d6b1fe7428e8fe6e6003702571e299da69044f30f6547cc7565be556c517e9680ad87fbd339b48bdd93afa66873ca2f0e3355801d64d7659a050b7704fe37acbb43a237bafa8f573facad95cd93aeb74e850ef383f866db75ea8672014e23d2a1f217e984d2e8c1ccf85dfcd9837ad3143d3931867fd56d91f118d5888c04267a79cc408890d8739bafd490bc3021d5738497ebb59952fb28d18627692b51125a36858dd7f121ba7b8e2533fc70e9959c0188057b24c853c5597cbc484fcc741d16d30d35a69abf55471ec44cef6a7c28e04134105488fa66a503d20253310828d89aedd00137d1c24a6b311603104696dfce1ab0d69729d676acc8613b8c2e718e601debcec66092ee4727784e94355b6701d42de6e0605d90a983c6635a74dd9027bc5f57b969863efc1f6328fbbb1c8f2f1237382bdf13c01b1a68729fddb928b73a23e579b9d60d9f6f6a13348218f7bd20819e9a9293ff7c2489e4a69f98f77b12eea61bb57df466960334794f41589fc96261f28e7cb3c807808f18adf09c4fd3e6dde7500590712400b0d3cdf905a24b1c5bcf9a63134aeb1827d71debd7cc6dab84187c7e3c379847de41a796567144b00893d94b39df68efb733746b0f8028fa83640f158ed8b40bda609cb7f53436cd55f9499a6daf7662cdcad645e721b0813e3f4bdcb57fbeee427ae10ad2e9ac1e852588cc261603e8b984f5e4e94f3e97f9d8b4f6bb44d2b0ff7e9f93468a8612a24f0da13b94874f1a0096eb23763961a99116ce80cd3cd1ffcefc8ca8b33100cdca2f4b0091a4c05800a4a60914debae28d5707451154a325447d4cdfd6eebb9488225ec69cdc962f93b7e1f952ef5c6ae8f8326df8e362d6c1ea2efffd7579681734c23b637824883943b588a757eef5de1181081c07495c7c3ac01092c0baf4d648a54adf7ae347ff9c6359c213b49a81a795f64a14400b5a8fbdca5333b80122195d96709f8c9e3042c2063c8587e3ce9fcd1c3ad0527144b2d99c95cb1966b8c2648c9c11c1a30ad7c5bc46dd168aca9d4527466b2862991613f10fc96b8b225b8bf5a0c941e0a7a47ac3b19fa0f02b888ae93c6afb0729b52c1a3bb038a8daa25b73695d8ca4a960331a65267c0c13ccfa0f234134ae8696bc7846e37c744859718411a7658fecebf6c371d1ba114dc6d7e19e92e2bc772bbcf076fefa78ed5bf916c7431490e0c49801d2eeacf3d8e9f6bbbd2673c7836c4db574eaa25fa0626b82085ebeb8721e98dada40716b49bc47c77ceb46a40c6ed8e575e75c3f96c3fc186e328050d687e57fea9586bdefb1f29b32fba775fd5f7b2bff3f5f109d243b5f45516ea67de051c45719003d14b7940c4f5ae016faeaa00e7d9cf900309370f48f76973a6d41817cee190c4377c18e37639673936e865ca289b498a64f1457e8ad064151e87ddbea0268786fb15624ff1b5027fcedb9a1c7919856653a2fcd76f5286965c6b5c284691e94edbc98fd19cb81d92b1f11f9202464084cd3a645814e0d0568396e02ca72e64709c0fe891678b2a280d78ec435b5e65cf80faf27b67b31ac960098bc9f1539f5ddd76829adc8c84f529230d97a601ff8831dadf23a927826b169b4fa450678ae6876a81b05168381c21ead4a65a8475b657e12fed4eb040ce8e4358621e61cbfd79762478f948c4c98d90867d244b10dd3dd917c844f111f683a39922a2ed0c5665b1f40c17c21c4f5e34d4ec1202892fd68cdc390336fc7635f067121eeff5894d696bb22615b9ba04b08aaae208e70056b5cfb3c7f037176951f0b42719a1871ae12635ec3faa2740cdf1022a6c7597d0d09f243a19b2320c4e4d94d6e1bde4af0e310ec4796b178af415b80ba4780f045db65a3b8484f65556d88be28624466a675d6ed55844afc335a9b6a35c14d5218257b5bdb2bb28f481e5055307a4792da78ebfa5e5bb1b8aa77c55aeb27c515ce57739dd690ed3b77348150467808aeb64dc2252d8a8cd83222b599a173363ae9f40c1a48ec84e93c0009f2acad20fbf9295d03cc597e28ccb27022f861b8e0d895b28d597ead89ad120a269cfcf71567f984c1e29071bd6e4a46f00bf793a16770f899f2c5080287495df92945972b825897c96237b2d42beffe64621e9068ea3d83d6b75930d57d40ea0fc88dd22e5d0571ad8c181ebc05d0ffd8c7aaea29eadd323c57b405ca2c5045ff53aa3b12870b42ad5af5c69376ed4c128213ae66dbd9ec3d4444fad85a7075513bd444e0f8a7250dc0a3741f2b4af687ca3251e9acea3a5687e4748f00a06e4e490e088e40ce20b732a3e7ebeafec9a6711db1fcec9333660171aab313aabd25696dbdc6443e820a5f1ae43a3a9f411b321e0ce47ed072aed688ba36cc6052fc231427974a93e83d91d25ae8d9322abfbec95a875df97fcf56bc679ac7f062584d3f244d6e28bfaaecdd78c8674e88b55c2c97730906c3ed27d1cb97a1566b9f0f00ebdaec9019df914719de311e1a98010012b04db445539417487928dda744d5a72daa1986921f625d0282e94fb8e671d8bcb76380a8d475df23d3d60e7bc83e2361d860be83427592e15864c454eafee81397d5a601ec49a5fb5e79ba18722bdfd556f7978c0afc7bd15d0b01b2c0bea72af2383dafdb9f2729b2d976b71f28e8ba86b8c6e0bac7febf26b6e2550aa2810f2efb39781981ce990dcae355caef5f80f62824cf324f901c8011b404c708997a1ae19864e3f11e050959ec6b15aca471b17fd62a46ea7625f4e1b01cbe02b3a511a185960a715306f3b6658e3fb2fec74a1da54ae2a8132977bcf40759617e3094457ad059f36a2446e1f4745ca015cec3ad6f1af4c7c899d258af398b07c484b9040e249e0ec07d0b7cc1a4a405824a1e440cec3814da7af763aa0c42854161e3979b5d74de5a4449886ff8c36e22859072351901993ca87422fe7ffe4193164b185346dd82b89a2f7ccf60237c3bc52241cf475fa2c073fd639869f07a89406f75bf97f1d6a2726f1f6e065e382f700c4b13977b02e26542bf9608668310175ed9f69ef3004ab41a3994319a9fceb397b3eded87c3942df923a102e913e92d9ade92d6e39c7a95b3de92eb6192d5d3e2a9e81df30a9b807a51a4ee4e89afef2438bd146341b5b0c21b863b821b0a6ef4c36d104ea08f9d294e93059400bc9e9fa5387ec31f9f1b81a21e331fa0b96054e9c554f867dc40223af5cba662018930334467196fa3ff599e0dab1522c9beacf43387b0cc8b34f033627e81439d3f8fa5ee3729c3f57ff6e937d0952150ef75c728b3e6d67bd9843482cf48f9aa1d004c24977b56aa024f7518ec7d283e83ba8f7e511f9c0f50baf1368d791f805f52e6b8822f98d9bf91bee8832ec104233cc007700b6cd8d8852a9efcc9ff5bc8fef8d4041207a7795af176472fb0da6aa9a85f8502b70b41c2fb70ea286a1424e1c84537da386753c3c2a96666c521defd8be2082768e90f1109e677c4c836819cc88d2ae85cf5431a0872ecba05c421d0c3ab473c0e2c0f7c63a9fc55839a70b0b93e8e51c25432ef1032f8eac30e43e3aeb7df872e8a87feb378637f7104746ba3b77e1a6b3304ce5aa88ba42d8532bf064a911589b8eb8436b988a4bcf4174a2066e714d105a6cc8ece993df3a6d91551d858603c853e88fe1ad59cb64540e0ff0cf53da6258c87e5862b8d8edd13fc1e4713fd16f2aba3bbd356af62574d3c0139fc55559a6066d908551a34bbc576e5cb688cbff6acd00cf49cbe1adb5c4397335d40b0071e247c903eacc0e4a68ca3afb62e4c4a650d775fd5e4a4dab5ee27883ddaab2a1c0b5dd7d1998270e37ffed63f220eb1c6d9915a78c5cd2b3954c3bf0bfae030874e8fa17b5bb59c141046e4c724c2e20daef4c82e1306484923990bf7185a4bb8c2ec5595accc2e99605b865ba8dd15e0891b57da8264a71ab3fc267e1a32d2054d1817993d6b596656c65eb635a8cd0c2bd255b381dd6ca1d73753f20b59178cd1bda4d6a612fa15fa6f62419f5ca5947a5f666d32d790e8d60cd064373654ad147a47f401cc37d800311dbbd2ae7dcfddd1f2f8d95de384d6efdefc55e3dfff87a111195be01a1b59166d8016ac40695e8b50e30e34f1ab02338901ae5f101f011bea97f90386dffbef87a6de33b64b6a6db42cdb134d1505cea1aba117bff7927b6d32c4d5720c8182f699c1d3cd073a68e6ceeac122c96d1eff180ece1303b0ed8c254501da78a1c2f1abebf8e9124fe81bfb08c84469c200b715eef0d7ffc7871ef4ead83eaeb961d9602769c104f6d37284f4a7a5a9cf13596bb34fde37151e8df0f21970a3be913f52fcf9fffed0943e4fc59275d2291a87cbf3e71738b359d5b7230754b68a4ce85709ebcdc98f4e6f52e66a02f01daaf1d512a416a375982d138696206c89f31568c8fad663078d804bd53f657de6c7287effff7de84aa063fbb8e646cb808da45d12384e6b84d0adef87febcf70c9891195d08d4e6bf58d1a21ae983a01ff8a0c4b1a59970aa892722f4976139dd51304fd15b291e05178d4a63ac3655845f23bcce9e1ed9eec8026ec74da1fc3cc12fdf59df342160e1829fe90286226758fdf284e4d6e286b1201e2161c1a7534bfee7885062c1960d390de077d0ac3d5895d8b43af33c4287b34e5fa7841ba73722972d566db4d9fd28276ae28c3bef3236fcb4c6c7466057fcbb73ab9b2ff250df546101e259bef6b91b9bf6fe354b45660c5b05c36bf9e78c7a9c7269cd1ffc7648b285d729240b1c183bfe36c9dd348def7b44bceaff1f1b43ba1e94878722fd1744e24097a2bd67a760ecb8f31b1d506085028f63562b716581ccce300ed36f9b87d78498bce9cc63ad17c58cc6da32bda4a63132ea3cec0de5534c770c4facdccfb73f97245a2700929129aa3e774656b18ce47f44f5ab4a89ca086265493e1f45abdce1cb727cbdfe9dcf90da5484788a7b330c3d97d01c7bb8d4e051b6a87159079599b4f04b15e7f08ca4bc7cb2128d018c2ef5ddf4517ae5f35f05bfee049eff138a9bb8a717997c62ceb77d91cb9b1f517c0a5aee084af9f7e22b586e210597d33a5d392217f97e95cbf30337dba09ff5555631668a87ece94740ed49f475b82647209a400fba51baa9e12efa475ad5a456359be11f2f9e7180a61dc9943d5ca236b5f9b4cbf7fae8be445cb1d189c310ae8275e6b24696d1ba5a9b2c12ea22bb10d80438bc79af361c4066492b3d4ee12ae3b1d03dad55f78803a395e30573ccaa678c54cc6aa92cceb5d90d5a90d4063f7794f3d111c8a413178bbe4bdec5937e668268f2b3dc8fd5065313795b6a25167e3aec9de7c9bd1d1b10b817bc2c34c375fc71f479d3051bf6b562e013f9b0f99c2f67a2278e4cb75a7d2be0c2075fc8b5f33857a54ff423922f4acd89f8ce79e3ba4fdceed7719ab91fc361b647b79dd671175dac08b2e50f0968619bac5659c591e35c8f6d3e9a935b8ab8d5d82e0f57bdf9fef91b9fd6c0ed88a20571c619c9e5eaeb955db03e94eb2b1a435876d38f8f767c4d0366ffe8963e8154ab042fc94f4001a5397150b12e169d154930ca42980c41945b31025369fc40b839c275517cf4268e84104cbb83a5e85478d22181537209e56d0d37fe4af4d162ae371aaf6f63991f8233d77dd2a9d2d66c9391cf7f2af6f972b82237780303ff0acd31be0bd5eb1f37711868d430f000d4a338330d917ddbc8f059abdb0581005e13fb645f1af29088ddb3457d2259898c1fc54c83b94bf2faeec7ccb34f40df3511c88549703c0b361a7b0c3261dadd19f960e093f01abc235983152a9e14ca00e06d9cd19220170d90b8fad7e87b68a6b64611dce2ecf05d54ad871dd4684cb57330e5c0a8b0350e1610fbc593125a338fe97949ab427fb3ae97f33d08e12e4d3b74302481691c06fdd3ef1ea631b3cd7ad9ea1dc2689081e4f609959359d9c5c4ce268ec23f967cb2ef53af6f9d04029f05f7fda2d4a22646cc26cdafe5c0892012fa0a6753407fd671c1a59d73890f6481300c04121e5ab354448bb34d9870c1dc3d69c75abe124010becf4b456b444d0921ce64463ab12e933c971a930ccfa1678c86b9f7ed2eec857214f463b43989008a5c165b0c24cdfeb7e1860990b7135fb8319a7ccfd9a1104ff3ad6b5d1aa3ad51a734781d1e33dfa5cdd0bdbee9b26999a3f776fac94cd39ae19914e0aaa0744eb422e9b98d84ad5fc7b47da148c88926c916331a1d51c616352cd4111ba3f3d77949347b844432210af0d53efeb1ffa78d30b3820a08ec9913c171207c56a86a4e3ff364a4e8774fcb1c30c85fcab65f4ee540469e1e85b4f03a167940cb6099cbcd566e6197dd6f7110bf087ba164479c5fc203fd6c4661836b4dec8808a7597d8ccd43dc086d3c0fc87885546b21b1c58df4b1409267f82b4c196d93924b4bc5885fcd17d97b2b751791fe64fa21c1044c9b905737769917efcaf510a2b257a2ed5d197f536a009bebfd82c5a3c754fa5f365daf2a075875d92c9df63dad3f2c8dd977803a46c7363eb45dffeeedd59bd2fab8dea7a949aec59f3fcdb58610d1ea35957e249b3ffcf39491e4cf50b9fcb75815cfa3a6d7b6eebfc61e8b226b054f1347bff07d3c681fda6709ebb1f54b5a2eaa087c7a48ae7bacdd8a6012b8e866ad8e25b5d271d11a369462582a686938b79162ca33b8581242c40f8e623d274b6b030808daddf556787808c28148457a3f4195d5bdd4c60d8ab9d2add4fb49c1d5ee8630813ddd8d4ccac2bcb5d479db2f8d0756acac1e4b84db705d3fc54d0f78c16d343ea84d8e4f6dbfe64bd6ef6982dc2814e19dea1e8b442784e86bccf37118343ae302e95a26005381d43b4846e5873e1c815c18f456270f1c5ea4fb9601d01bffe4718745561397f3e132f14df213d42bae99ed8b13c4bea4fdbe026ab82c345d19429aaae2c00f78dafa8a9799486c4faf4f487c91b1f4f0de324b9f580989d0631f4f74cf6d63f9dd727a94a94db30029a06770f11be353ff5e71d3635d47592fdfb1bed6d1cb940eebca73c01c0ae17d7b9b803c7f9ed8b1e333aa6deb53a72b804843a31d10fb74375d378843ede6186da44ee83be5e8de78db31e58a1180f0f7957d4e21e9c7e5ef046f6e2bb291205ca674ea22915aa36ce9faf73a856b63e010be1d8e9f5924f6668d2f144c3f3f35614dc808101c0793877b3f53a7f8b96c29d2c7281f45450c27a3b6f62ff30bc51d3667f1aaccd18179dabeff0746117c47247a905a4b564c4bd5d1e5cfd2b9667fe657498026ac77dfc22df02398351d82f4982f7741a6fc9fc790bd3116b210c020d7941f7404a4733e950453dd04a8e9bbe48f3c2eb3ed7def4be2f172fc69eb551431ab0e9b530c4eefa7b5d5e42f7f26c7973f3eee7dd5c65becfe7387d9b2ee3f6fa907d19c2df3ec01401debfecb1d266eec8283ab30401d17d462a667df3e920ffe9af30e2259ed8967a65921988c96850e4786da3f083449086d50c02b9593441bb72ba7c1b66455dfeecaa1c79f021e3ec84c1b580d928a2e3901fcc05c2f43eca28fbc12c3c8a33a0ab94ea3e53f52e270c024494b9010c2fc945c5e11b3641c7112420be99459fdc2cc9fdb7336bd5e13882d6c2954819a04a05861710c4eef55992a978acbef0e040a0408c836dbdbe4061a366c36fc6068e8bfd9c7ec889af00e72a81109b0482ccf257b660ee7b5d20f85fa8abc3333ad29e1a70f8d2221f6989fed0a4951e3d663374e388ce0dff6fe171507e53cf8a8fd3c85db761ed251fb599d7d7515a657a0e2400f20497fe7f51fbd8ff9b1b8c67c145738ca58c57b2cf38506a243370a3c71fa9d5637a337855a8b78cf610613c94c419447526ffe84d9226c102d2f29daba57a780f1831d10264baac003bf984b3f47f18e37c37cd455781d7b3a6de687af9dcf18ba8086f1228f9c49dd73818793ff97715ba3dc67693fde399a8c6f0ba498ba420cf2420135214cfdd89011f710fdd80af0f288f4061b62dd24175967fc9045203f01e7ef298092ee97d64b8db07fbc335acde38ea0e201f83185a3efa0d87aece6dc907365070ee5dc5e0e7c5034e9fea5a804ef7eece687d6f088e7adee2275b2b888bcf0d510b3ca98d550fcfb7811306d8c9bacdbe6109c66355965bcf819a678996009a8e8a10b41176a1e368bbef54ff0a128b2e8c2d4a52b31243704101d71beac519ac3f10aead0e9ec6a5e4550fad8c54151f35e5e4b5bba53fcb20abc57c413a2607db266d6b5be8511dd1ee54bf7c83313acfa2c606305f0c701fafd50b58188ffbe11c71f50875c5e9201f520aa7f0b6a9dada402fbf6dc3548b15317fed91cbea69c8fa3a1f426e206c5bbb3f7506002824bbc2b834f28bbbc6689fedb14d6e14ecafa532d10b8f328a1cecb28402affe93b5664f5302a76bd085d124d656b6ccab8b85d1ba21c287d68a1284777de096be4ca8f89e98e4ed65404f5293f966d179300708f97c7c2ab961d5e1202afb389445add2318ae66b554b8d91332a782e4e49e6a473fb79e8466b34e16d8f3e4864a31e53b98840a65cb50c01c8030c7952966618d8a530cfbeb29764056f2c168efc00774512338169e582347617ba91bebdf60ac71b8371066e1b3f5573d3c5df5641f25b57fcd4b572f8a5a9c9b5fcc57ba58778653173d5fdf71aa87e8a8e7e8a4baa0002246c176efbc41d425ba12db094b8e64c706865931268c3330fad6effc93ae9e662d2b99ce5b51c1fa265005f25f15346528ba902fd30a0e3066b24608d677a850a14373b904e1c38412ac7fe332266b245e166301a26db75baff4a675aacfea15b9c64cb9add13b2ceb5b588cb3e7732a8546d53321002a384013319da24ae808d02b761033a6ee1fc57d430c54b4a0f8689c344e9ee9e5a383a832c54ae5e65dab3a16fc56f200970aef5687587c055e35a41d4af49dca9425950c7b03fa04eb8a665c34c1802e257ef3df5b002ee01a709584ce6e7c230ddade4691b50622da9d0f65cb4bcaba29b902a2da60eae7a467a9e71ce51632795416d279872e267b26bbe98396b9ea6760459fe23eb1af443f024f4b47fa5a0b4ec2766ba9e5ac8701ab033b536335917aa21024947d324c04057eb812753f8b77fcb0b0d490cbdd5c487ff0c1b916fb1620e4d6353873bc7775b755626f827352dd3dfcca7057d05e3db356f8519708239e63ed51035742498451878dc444eccd3ab5775b6450cda8742de5f380548f5e769fdab294ab3b4356fd91fbf0a8bc10c2e7ad3e2e55c92156d046af0571d9da3b28d460074e35478c0df8d7902e12012f8c61454b8399232538206deefedf20b0ef564a4eb504bc379ff4fbf80e38d5fd03f37a8cfc3b853f7546c879aaa6d21c99c9962555dddca49cb2715cc86f28fc868ee023dfc724bc89f075d7af1d81ee88bf4fada419e2e6c016fcc5073369c186f4427417e150e2879c86112c13f61bcb9a97368832af2a9e06f182ad3556bd93b434f03e8bc507dd231d7d56de8a0de4474318f9b7a613ddb4357ee4b37be0333d0f2c8d747ec185dfe7f69b9084d5b6b53910b01a74caedcddf79bffd3f035cb1d900a38739d7f45b29e9f7418fa07353f7a3ff07025d3fbdba2621b6c7fa0569e0a74292fa6d792af2b25f804176651387b73b181ef324aae94168a8f4f81d318f06f9ffd617de22821bd28d1b9017c6e7bdf0ececfbf18fdfd7bcffaf2bbadb522714c3d67e7aa12bd20299c20507abc7e2867ca6b39d0c253ab94ad8ccc02688fc7a887f802fe5f5a8c284a3be640362fa8af2c55a9ab38e923c4246603c39521a751d04930b043ad947b5022617dd64ffd8982c3dc153abce2937d5c544994cd69a2d54302e69e97a99e9f42ee2003dee33c67545c6b4e57b6284fff0344c7dea9bad1f014b335d7d39e2a240b6183e17dc9a0db7c17ba5cdfce15eb1f0733d059251e390353211db06071e909907af6ffec9e52e02eba2def6e40e340cdd5cf4dbdb1aa9c3c257cb22930936376b16ef0507963a54570e8f02c7e0525e83430889f33ecfb024ac44cca9937a9bb746a8ccf279e5d7f748d8558a774aa7e3f2803f86b0715d49345fb36724d06de20f8b0606a18451c662bd35601dce441b86ef1907022ae11a7c87341f62d063e5dfd6be5dd1225101f5412d340003248945c9fb9df7fa265e84679e600a1abb15c0dfbf45c050331f43675d8ba296ff00530754fe991a46137c216298df5706588bfa80fb779ba20a3f1f94c14f5832e6ee1afe190a1708c773796878f9cfd4b5a6f6ef531a31ce784f5d531c9ae1cee7c6b611d17f328e69953199c48cf902f5a206865b5ebff55090c66710084e8f0372f80c51419a77d12b388c00cc840428809a6b83050a2a660e3e5367badc8572db4ee333beeedb0139d72e103c9d9a03ed7bbdca9d9a06f9a17d4eb714cee7ed62df778772e8a81896454a1cc8f58c34e3fb74a60b19691bbfa238691c624279862ce434b8ac9e786734e2fcdd8acfc085860cf992eef37b5def6085accdf6f284a824984cfa1600ea710114949735afee0951e9fde04f09761c5a8a6e9064ab8f69bbeebecc5ceb070af858e9837c0747e4511542e21cdfc99556051ac94fa44ec01cb2e14b5641e5e33a229dc858246dce5339f74ec46d44a5e1dc8b45cb5b51e075c6b40d395126c5883bb87a4c62cb13c2b7d9a25a47afd0de4122c6d15cb4578539fa51a43f1cf6721c770f8fa4e41db134df9fd1af59a4b74cadc5c3420aadf2e02024780ddd1e320a1af632b002aac3635e75000feaf41b5758cf62eba81783bb7a3dae55af842964ef46d1747390d3f1c80e774e42f53fc760e7da7f097fc231942e06a91deaa8b8d18316e7d610a1aa27a73a58cd04381e30222c033c3205769b4be4de8545371a9fc4b2626b4b165fa08a7d0262ef024a1724e06077f1b54c6c4ee5dd9dc6f3e6a22b17df717326f689cd014fbf1e14800f14e1d4b04225288265d303ad3a167951599b43ae2d0b0a111cbebefd654926a4774eec0d3b97288167f6bb27e0b96a0f82faa8b2e13d0d4b8301b16aada9478cb8539d3da5797ecd60206f6295dac05a809b266ee39965116d346fc8aa4e2ea0964d18336412a14d1fefc763590d972acfd70e39900602942324b7ffb2142a1abc461dd50717125d06b953373f88bb64c6f72c4d5ba14a740d68733a11c9a22f386979ceeed2653c5d5b13b61b8fb3c67560a0a187b443e4fe5aadf7dd9dac47390c4437569894e779df2b5a7f57d6a4ba449ffd8ac9438e11fd3c88bd327378b4dc410fd3c2358455f1a9bec3241dae84b3ad5b6e847c370b47fe2d82feb88387887595ed99bbc1ce21dea7b189e98eee403315b48fdc1dbbeae94f72c9cce4d8a6bcf420c57bcc13265fdc9ab4954f52aedab343de063298f3ae6d2cdfb5ddba3ea6e27dfbc07cd807f2f2b817e6cea1f9be30dc53ed779730973c903925c69df96ee75ad45c9e01bebddd27b72995eaf50f70be3c6eaa85dfaa490ec1aed418531475c64231c2eb5fbb9b070da55a27191703502c632eff3fb6e8fdea0fbd0866568105b609b9806587af021b0e1f2f827354369587d78b5e7a7db95e2146887f4d4ce1022c670f269a2abead3b17199fbaf0ceed703e06d6e56891d6ab642782e7708917fc252d0aa663a8c3e63d8b5deb91f1b548e56c0aa7192e82826544aba0bc698e10cb4e9d1c0eaf9bbdea40ceef248bf896f25b40c7fa8b762d7572d197f4ee686e018bbd064f29f2dbffa32ed90726d183e05cfd02a84767336a1fd7c23663815093f2ead7620cb68f0760f0c2d736c9fa79322dd66f5068770fd7eaff4da3a96c887ab32f2d00454efd616e464a4f3518491fc0c24d2dbe0acca9aed5fdec2becd6910dde915dd61956685374929a46573a2dc0beeb81cf90f799a1adcd1324f547d5650978a904bc17270f8dfe584f6d3e8c67a959eb2ab7b952bab14511e45922a05547d128d1ec686145c53c3ddeea28a36bf219e18a95800f6231f833e113c0c832e21bd480e6a906eae3cfa060f8831e3856be25caacc7f8e24f9d68a18cc001d85722fa9463ac83eb58945d778c474bc1713df6a0b7a00877ca660ce3842b31f2540288ecd9f263d6d4c5d7739a968edb4aa81f7b945079434c09050ccfb2052492239d0e509590c731dc1ed1596780c94cbce128954f3ed4b5f2f16a280d1c5a0ed3ef01dd9ca46cd0d9930076c4a36f95d15e4734f7473e9918935b63e9529d79562f4fd9876127d95bb6c3400fa97d2b756856e6ca37a645c25da54aa1d785afad3c78f5763e4af2e1b038c02e3e62dfa549d230206f6db499367703bd96645701b1b82f2dfbb5fedad839168767f3ccde49841f8d901be51c2369ffc9b86a7b57b58ea47aae7996c80ac16beb51ec75a8fbe390a5f1d566c03947540a3ab66f7c0578ab3c5cb3d1b599cdc572d6e425665c1122b3cb52f82073c679d6f8ba987c3c56c3aa3d9258e10eb988f595fadaf7a8fb714486cc032fb53824922ec3d72f13613dec139e19c86297f5b1b80066a3e7c6b3d875ce6bdd0a5e4f59512ae4fb5a830971e8ac84609f2ebb0a9ac0c1dfd7d7e475497d42c11a7040ecb5b9e4f9087bcdf643fadce4ed61e6c17aca59ea785bd7f4f6f9dc3eb675bda3269cbffc212d62b0920a4a947815ba31b22a7068032609208024fd1420b86fe928dcfc91cdd7b306f70a6c75397da793aa2eb240ac0499aaba8ed640acf6ca08ba70110fda1c68839b2796014ca340555d0ea0bff51a555d06b2453cecfd1c3fe040457be16ebc0d967fcd158437846a4e39a6d094294b018a2e3ac7d59e105ce86035f05c938f245ee1d730298cdbd17c55a4213fa39ea93efaaf8e0212750ebd525b97b5eb24dbaee862cc7de73d9020c85bc78cacd74e6aaf500ccbb657a9aeeee9819824ef74f6558fb850570dba49f38a33510ed58d40eb5ed181a347e8815abf4e7caf5707b9257554d49f0f950c76b047664e448687285da13d0eb8850b5fde767208872ab4b66770be68a5594929f497c64a65d2658f007b70c17ad8e28ca3949400a8b369b57b595bd78a5a56e12c58fe22ab7b8a15bfbc2cb33c4fc03beb67cf206bd86809d09b7d98e669c3eb26ba485f8d5f794d0046fc360aa4bfab02a50c129b7b390b56414eea376526659e01b9f1b923965c25b1404c98bec2b654576aa70b30214b4c6070acb71ff7e8dbf145b0a912a829940c6ce1cb2e8da003060d50118ba6de5946039661342c445000c91f4bdaa7a343befae0498e685e8c4be658eea3c939322d3177452eb0c1c2b39a13045d07f336216b33435c79aba815469009191840ffb406c6fb583434b3643d5d921c4fc5408de8e16430590a4e6681532cc1690ca642ddfc79104b053e859b4645a9f9a1fe4b27c7f07532d8588b512e0124326405eecd1c5377b22619c6e24f29877f6f36a0fcac1a7a3f8eca614067a55ef421497d32506443d5b1052f479eb164e908172796206286dd576049671a6ace25607663c262a0a2c06cc0246b4a1853f26e53f00c5ee4e3cbf2940560545c9ffdfb9c60f2daa22d0b2ac7977676e6ce159411b00147a51a42c7f7b4c9e12cb0109921cac212b9ccabb2e7b10e17f570e5f2b2d95a9e511a246461eea60dd847b70645d1eb8536d01bc0816dfbe3317f74785ced5409624fbd81c53c49520c4f0ac13555498f97c38c1eeea51ebd20fb2c126875911478662a22565d50071a8b85e560781b0b50a49783212c8c65af3650693129bafbab400a3790e71d65a16d62a00cb3e64cc0b36f22d665a666f73e28f8accc080cb490b026795f3ac4a49995f6f364a2c1c8e4d2ab97c1d2faf12f4ab92d8e6b6e35e2512f70c67ea236a05c55a67ce2e34ba708f324a21ef50609567d910cf2d0079b6418a758138f755a1e848d198b9c1a763f244436aa7305272fc5a708df17fd982ea707dc08045b9a2cbbf2a8a9f3adad3bae3046e42ac0654b468366e2e88943cf33a363f29e51c17639f5cfecd9916623415abfc58928428978cfcdffac16600477796885a2e3eb75ede17cbd1ee4d2923df5be3e94dea46e47261ff987827c74050368e6acd15aeaf17b8301f60905ecbf50274d27dcfd40cec0fd189d9ec6fbc1c61d593ca8701bc32de304eea365189ad683195fb42b7c62562c4391b33eb960df11aacd957d11955671a741af1fcd1834ba832506110a51453387c7d68f049905550120684db6093fc2e376ab92726e944272f92c54ed9f108de09ee8b8fc8762bb926c04e459b476d88fb4b9304216b5c2f738def7589368849250026f14ba30e2073c2b83d8f75f702318a42ee8aff18fc6306c6f73e39f63671bdba214045c8c508936db83d8ef349b7b12aff222a08295618f1222b40a2f30b3626fb3930ec2c8445f902d689761d7b71bf445006e3c723c1a40a505198e6285807898478a5d12763015ba061bea3341ab38faf0fabc9b8b1232c2ac4c5a37da047491907e910ff3d82c66e3320283ade7b87a0fe48aca078952b4af222fa48e8a59577c7a9d49e0a814ea0b82d1db792e2a218c9a7dd92f69d556eac2a221d430443c53dfb9e766a07821edd4bb66400be005511986dc2ded50aa9fa4f5a4c466ed5ab0ae34a944f40a6c06d4862b848df4b893deabd4920a8f9a0dd99345a665923bf33790b35ce07b64b2e8f3f6d2aba5f7e75345fec4630164643cdc5f41fdbfeef62384947023cc67375febb2cc75e5c83c21424a5fe24a15b511f98f2981306628ee197baf77d7830c298e7df2744913a80056b9d040aff9af96896fdaa437b7bb82f64e0ce61aa06c4b6634c180a6b0da2d68caf2c1e633ee9cf704655f48721eaf962494d7cae0e7fe1cb00eb4d298c873bc6d761568e661fee4fa06638fc86d5cf25a607ed1d53a40109cfad398325e70afdc48ce9bbb6f093890f3967cc2f6d9b111b4689651dc209879d00e4e68538da47a9f067d0a11f1a0ea353cb93a4d182e363d673b0d3a59ef13ec08ab6c4898f8496a8a09f49e9cb728284b1844df4629c47d09c90991d20069dedf75f889fba4704c0963e27c7f9a4f6a7d06162dc6ac10cbc07f4fd0e0200f34cf26bc24d782d0a026e0e30e8445e9390da239ff4f00486f162c45b86da762ba40fa36896bd03b190a50a06b04d0a05ecc379014f4937e6c98429938084b45ef3bd2efe03d19879148692429ee40dc9bb4e476511231e930bf054e5474032daef52d61adde609f9a84973890783277862188dbb69a8613969f551f6228e2714fdc8df000ec92a988d1882d865c8ccf1ec69b034d2cef704ec510890e845d099d999060f0e96b5a10e88d556060745c338533eb7d0b838116e555818cf009735b56cbaa26ee2cf2b4a897de93cbdb0d1453af75f5abd20e2619e15369dee064b555ea1113a1e28c6e516ef465823c781c9b073600539376c9db1a829b1b0e4db98fea241a1d81857194f9f0e6e52a1b9b09c49f46c8b08623865ac8042a58eb62ad996032f86fa38c95a5951aaa39674f83f82cdae16468429e2c3282c63ddac98c38666b076d704dcb4bb72bbdaed64356a8c56000b5453fc80ad84021dea26fbc9483b0bbb0f993408918de67402877a453224672508dd36972c00a78adb2004df56ecef3139d80ea9c2c4099563738dc152b7eede3413f938d53a7c22acd1db359070d4795498c8cb2330a5ae21e56ec9d6d66dc590ff49c0441879826cdf5d3f5d89c8e8710f178e135bc1ac8cb24de7fd23d350c2eda84b01a6214a746efcf0dae462686219c3805ff66494ec4d69cfeb7e608d87619f9f3d5ccbf019e5d228c6a1d9a15d902b60dd4a6d72ebd7a0c0eb6710e5ab013f0613faa090afa930898c25cc09528b44030d2af60de0c8834a51370f849cfded966dbaa61bf04d1b1d4fe485f1122b3e6252513bddbbc5a3b5264c759a49941f7d7e214586ca401cc9d4d4dd1bfaaf32350fc6486bb0d22d0e8851afa22dcd0dba8f3d0aa60190169b0980216f34252187a5bad08b7b19e2a2f8e59233f69cdbe925a2e8bbfa635071575e30fffd6d2ad0dfc864ee86d9bb14513cc8202aaad7f82120d525b00626a906b7d852c41a6df1c17bf1073e109232e30f3db957d7e7566b7de694d0c673754298e8765a9d97976f44cb0773e0566740a1e0489d405219a1ba19fc5cc9f8abef8b7423862d2779b8e9abb19b109dec7b5079a253609afbbaba7879a03da8612221f0c8a2c4c3d204cc7ab3a8f1965374388a9f7a94c1f2cf022b3fb5cff8c0a35d7ff0a51d2f902a295714621d819aa7fa60c1ed787dea6ccb3961541a26769039be9e9ee6b7c0f1469ed8030f9d64f63094c4ef0502b20f1fa9c513e65e66829b1e7936b18e9eb9f7c180c80b025c96d49c22feae016a16650a5b099f2c0ce4db4bf5b86c97467cd37345403060825cd333909978d93e57a066ad6899acd09c5c517565202812342cda186889f80391cd41a0799d657388749462d9ce25138161b505fee4938ba17770d04c3b689eecd51159dc0c02d0962a71e14e0426f04eef5ff5b22d4d1cb5546b1a58b26c0e2c2b5bfdbe0992c51cbd7767600fbad66e6c5dd182681fe9c613d68f31fc00bb00b39c681e411b2396d125b852c2e0adc29c04541354499feedd46329229b3ffd412ebc37646075fae66368eba2e237ba646c61e11b8e14e1fcf9abb1495bb614d1d45390ae4414ccc2c991e6b0a1c6fc20eb4e2c3dce8db1998ceeb607321183e4ddab3ec49bd2e94de4965f43aa0de3b6eb7632f8ec3d8ccfeaa33a502f7a015d0b79681ca9caaba5ad6d182d27101bf14b5b0786cca9a3ba025ef9e5ff4df7598d0462ed92cd46a0bab22793148cca27b85b1ccb470b05261f0c3a39c4406de6fef7f6386116ff5b0b5b56b752fc8ef18194ec4d799fcd0c0c6f61ba22ebb374a01783dee533df1095cb6e91d775bf1442c8ad7ac447768f39eafd2058ab8e91e2604e0bf82e4405b386e92bff7bbc11f1dcfeccb2ba3470ee5fb8b9ef2f550dec104b991c325075686ae4284589c69a07ff948f9d9e4ca544022a7f6279ccb2ed352ad4bae9e6e65e9bf34e6de1941209e93bd3507acfdab285d961d473b7b16d9d76fa0a5adb04373962582c83098551d512a6baddfd23ea4181e401647b799c38dbb9604072d617edb9601cd7bfb6dc912f96f8f19de6a01e4f61806d986737a7983be290e3b417a45dbabc81dd355f38084812ee0e2fb66159fa67aa63efbbf157044ea181062b9a85d29d26de6301ede0d1c096a9ac16141850e834bc6905c6ce17e12c0096a28514463fc576be070ac037da5e30454d24369620b5dbd3b4ed6829d3060bd02b5a9fbe10b18e49eb926d1f2f6cbc714cf49c2a0046c6da30713d62f44dd7d71e53bd98a6a86e1f446c325bcbe1290d598476aa1a3426b62a7e248559bfa573d03268f9d2a8fda8138efdf7b7276ec25a51ed1567265414af8631dca963e38631e93df9e6a5be373a672760ac6cc6d5f30662dd7de7dc7a75784fc02bb9fb157bc3b76d9027bdd5e195905163149d795bbe36538c00287f67406191a462c6db3b3d62740d181fb6a7697ddce33c50bbe574df0c624085791db6a8fc3a482f651fb613f562eae59d23f3dab0dd2cca8ceae4cdfead67e6bd9b05a2c82b832e1bfd75c1cad825e7d2550b274276740889b18e5819529a98047cf3e73cced1232151afda058c6cfe5bdbcd6a230c3ec0a63b332769a30124806b1ab73b7945091b308a3c8c7c452a6ee2526d2d1d043518282cd1dece5b1a778d978ae3a1eab2b96352fb124704ff7a6e20f3da9a1a6123f4e984d33e9517949eb17da46825c5a83e4215446dae239a015512a7851632a3a202f0ea8f8b186f4356c6bfbdd05a87e0e67e78f713334998990ddedf2000d18093088372202e96f22ce482ac68c11d239c5076952be9548dfe642824905b24ceb58d49995ae0214b655630b44473c0db8b6f4f15160d936cb6179b43ddf380acaef00875f0de106c747a3c88d419fba00135e3dd579a71346f4ce33cc2d7a9dc1f1c88d2d585d74a98a63945003fc6f9ff471db7ca582098327f2217c099843bf4ad2f82e12542fd4b2900289a3aadf1e81c745db4f50aea27a196057d68c0e7d450f4ff72a460a512ca60a06bfbb4ebfd1d844c5a9c1c5180177d86d40efd6ffdb47310cd96230babd22ba62e5fcb879e39193a1abfafbab906f6b0c14fe2ed9f2f15e76bd84d2e078da2b61cb13023e941725d6ca63446eb370ef99098d3ebc9c6b2e5605a3aa24434ba202be22bfc9c10ab01f110e0c7dec7ee0673a087703cda24e68b33df6592c62b3aa55013a49306423b269b8db14ea2ed051c839ac3c5b57bd01d717cc04aadee10dd53e30040728e723185b9bc9c9b813fca7b9d8f0e06b665d1add0390f369e31f0034e6b7f93746407abddd81e54e05c8167e6ee2c21a5751680d6d64ce0925647a50ae00c9434c80bc34224273bd40722b3680fe26ed73a990f20bdfadd20612340a0f8dec82262bdd910175c53c457f4534a14351a4134c213965c8c5fbe9e776fc1abb9c9072af9616d2e934928cc6b4d46ce86f6bfeeeaf059e3c6c67b726f3ec960a74dea364ef2e607e276bc10512b6add49da43e47de84f6a1686cae93442a740109a251e70463d79bff20f6a8eb31238b7ad0fcb0f425a1bcbe6a83e1afd11173e365ab8b9191c1b70eb3fb979fe8a4f0c959993c44e68789a89f52b2bd3cf4d5a19bf85575a9c97f5f66adcdd24416185ee936c340738f68e3b4cf3b06ffa69a74a4d62b966b18387311fb786d6361b53a2d4ecb7e759011afa0b6ee86103548f48017d5d3028df992e21bd264b033edbb0a1f2f70f6323eca4a85fc15e5fac1d3d047fc5e762490255ebd8c0683976a6f2e6451325d6e08bbb8340a3cb871b26693c6796e268d926e5d54fa1a8ed277989e1fd86b892e152a106b2d6417e4566540040ab1ed099818015dcee788d7ffa55610d63a31507e66397adddcc35d9f9a82ba9ba4b5b1e0731d63fb1126b521fa7d758c329226f029ef6df9ea5ef787b25ffd3843cebec7b7111714e06501f4740d5ca2e61a24ea7f7b42d9ea8b1b38a8e001d706326a6015e3dd6f77604c0a19c532661406c4905a5cd5d616c483c7d1363dcbc505463f97652e08ba17326a1e807e3055e7b2d1ec775d54f668799a9523cdf8d2590f9bfeea4de6c2cf8c3a17334da1891d57a9c42d8185e425343f41a0ba3b98ab9a4d003e5d0a4f0109c5f04c6dc48a289cd85078539ef429297428f5061c65325528335a7b20c867f0ff38dc455b583bf1f4d83815e2a96eb34c966af1677e36d9bd6dc5ffa9314df9a1c9e5963ff85f431f4734ce22d3a8be55e9ac810016573ac31dee82810be687d8c0d45d439ff32c7eee9c34a55f6f20d116701b08a3d48aa2b99feb410a90abcfb328ee655b6f6f2de5335e95069c10613c8bdd392d08e77f629cf6d068b19e4146e5d443b48153285d633f9d1f1484c2dc75e62c4923b74f392afe401f215d81d285dc9aa5b8bfb6f43eb2633613a4539e1e5dccab933bd410f11c14853c60d2f73537a9821d230d7ea0c52f75ec64ce11d51cd4f19a5509c462d18f153d5a56a8f9d867827d80eceb02e7d439177a5d3071049762228f7d6d6af60f1ed7b4829a2d053e1e683cb2ba12ab57227f894ead70204618ebe08d246c52d11cb70f15e698302ef7b87ce31c5aad0a9090aad6589160caaceb93e8299f27e5715459967f98e577a20052c2bc96f5e5a4df606b05dbe961d90a68a71a55c31d8903d489d7078f8a9c683ed70d4352430478fc095c57ef58fb322560a1501ad71b809046a763b8c70a43cb983ff2a7a1415c6b07e991cbbc1a7445e0798aa6859735428ef049363016f4d988105cd61e3c9908c4676badc587b6bd1a26105057fdbd1e699b9a9b1f2b12c526c2ee279ca7dacaa990049e7ad63f0c53a9914b010164382c2f9687fef7b7af337077beb9d640a1c9d9cade1a972b1ab129efcb9fc912a52078d03f98cf330c7afb64cadfab2b93477788b52f3184fdb6bb6f712f86982c7c1eb2803a4362f3cd218e2a7ee80c7633ddb64b087092351ae700b38b5465a057e2e8efdddfd47a2e367af903a313d9a8df4dfe203169e7c0df1f881c4ad6ffe63d2a1610ab2b147b47bb9af58d3deac4771d64a9eb6e89e0b2cf20e683958653a18e49dcd69c16b36cd39034700d28eae67b15f05356c3f3d701ba69e71bbd9ce66395f1915f92039c0135790dcd492719bde78cfaecde9c6a93803e4116c52c67bae250e605796c7b9cb141cdbb8c85aea434e8388f9a84357182de00297eb62e88917fe1b4b5bab71fd839918ef48d6022a5cecbb706b92b56e1c9fbe49b9405a88a2584340c594bdc492908ba41e7479c479df6d3f6b75ff88a4b86ea6976868d66378587596ce93faa25c66a837795c27511c4cb3f6fde6091614f4cd567bab274c4993448de70f80044f008c76ce7174d4f85625fb4dea6f5b45a28eee96122f2d3fcc4ab02663687036bc3d9608e9ccbdb0773dade8cf4e911884d9f1572870cb913caa319ab42c45804a468414f3d40113d1bb04950bbe93bf4ad809274c76ca502b3c0e5db2172b3e172e239b5d163c60f578dd934100b62fbf009bc3d78bb31b4673d7213a46da687271ecac2d04d388653094dad14c81c147f4a49ee060f38b5173b4128b3474eb3a81bfb1f5846c48801ac068f0e3225a38184c1cba33f91a0a09c61160974ed849622312589a5c7da85de78be4768e1363ed1fe5218a26ba0905f6dd7fc7b111f050e9408adf80bd00f8deafae2bac60f19ea04b664fdfd9a24b9ffeadb0ce78db4a043b8f05a8f178adb10ba2232e30bd5d7cd80999be2cdb89ae1b18450a5a40a13373c2f232e3774c9166dca8eeff625504c8d4d5458d2da0db043a5cb190829cf5d090b3a5262a181075bd071610f1a3febc70b38e6858714508a25c712b8ca430709e191e5605c79c82abe3f5dcd156b99dfd7c53c0da886e979373e141cf8977ef2fd280df6c977a83838d08558e78546c3d0a93c57338aeac138c06c26172fc4fe4b38497eec5f834caa65fe4d5145b3048fec4f812c0f66b37ea11d201dd0029783b900fb21c0327cdd166a1e1c307c84364d012ee39fe04059112dde1ddf05454a7871238e45e69645dd649bdc921f060a2de7b68a8d7015350333450a3e5cd57187e5228cc9b7de5ca5d428bd61c9fe64abcf1b43bc9e335a76f3885a0ad906476904b1dac781fa9db05a3d1391be2daaad449cabdc483a79350e909847990eca042c538c4c83b74ea1063a47112b6af25ecc3add7cc6dfa563adbbadfc63e619d9b063a8098a819cdc45194f70bfbdf7901696fc0daf5e407784c158506d370564b1a814a4a7182e2509ea139beac8e44beaccfbde739b6cb3ecc6019af8ac6d115095cb56f039bc2202868f644755abc9c21e60cf48b503e853d535f417c9171757d716ebfc30520cb4648437f9ca3963b04c7da77e5fbf4f7b63ebe14bd113afee73dad00557547482ba7a5e0c46efd5af86e44ddc5332bb1403684b537f1ad9d7da06d00ce803b1934548378d1d06cec777b0befe9257e14d327260ff3201fafdc7444f5b15c546e9d55363a75302ef6b184358c678c7f6afea592d017abff6a67ae1df8083e75667a423cf2033b83678f2051d98edd63f56d3c5953351871486e5bca5df552b7f888f53e2d087ef1457f5813ee8529386587a762adf2c802f0767cda402916ed9586f5f8bceee5f433adfe7ac1119f656aa0ccc4cb046a18348c61471f7986b2e84af3353c60215037d5b1386b6cd95c5ab37d215353bdd321fda85c6637626f86294838f21b3bcd6bab439a592784fb83247345c51894b8fc83f39b5db053107132ee40090799cdc41900f577d627062243fb0f6ac2fdd021a828b48402c46ecf090dd94978fe11bde4d40d0f00405340280df5cde43f1ed2a025657a744e71c22d25e04bc201ddd20beb8eaf32b30f690d6fa9a1a02cc626704e8b32138ddbda631c9e9620d70a3a3e83280dfb1487bcb16c3ce81a810e1611dbe675f6555d123ae87217a27095f18b47b8cb58dd44e551eb4b54623c4282a69b88d54fc6e04e0a13d51dc9461a938f931f7fde18031bef785aa16e3278e13cf3dcb712a447432570cd78707ac98661033c6fb22fd44e6adce7fe30a1e308020506c5c73d25e0fe75814dd831155cd7dde542a6a4f1287dbf9e1ea79ea3dea1e78fc8d4f20c79ba0747d187d5a132024535fe86334aa388f762eca0e4ce498d978576f0307419e74c45998ae3e73ddefb84de251dd37bcfebb579b95ce8664d7056260f5bca0e3768d01b607c11321a2cf2187c2ee4f318c7ccf4dcde900b3c659727747d2ec528f3c9c13e76208f4020778d0a3c0c51e16dbe653346ec71d8cee71630ed69b4097b9f78cbabd0bb05581b8956dc853cb9a3707edc49227656060c34fe3f60b994f6e487e1b1047f105487e8898b30f3ea1cc2bc8e78922f2ae82126b7d7af5596316c4180f1e0a1bedcda9be7e82dec06e8db6f148030b832239c6b5c629afa53225ef50b4f02cd8bc965270fc814b67f08b76dccac8d8f2650da286d6845e603a608a455455ce66a02919faa01612c9f7535ba7fef713309fad078f2e157740116d02f17f14be0f4677fc5931c5d9de49b251811fa03ddcc4c5165136e41bae56a1d3ffe83bfc800a263dc6082fe2ecfe15ef5353567a824dc3703a6afa96dd0fa395758603a064dcfc72d8c4149eded1841efe314a01e9914faec47084272cade2bcefb0ef7b4d30ea927be12b1551d2849b4664d955a381db40757e530d7b4a3f6f86d33073ba4333bdb907fa2a3a63568b217bcd641b8fe6ced657055b31fb9f037f706094d1df567f60f39850a936d0ca4c81aa4532e987c72d30f9e59ba57406f7dcd1b84017affe2f9af56ceeb8fd45a3d9bbc98545a65bbe48712be9fe960c63a25ab6a6999b54bbac29e8f1f6315dc302541a060a3715bb2f91a949cb510e48ea8bcf4cc1c21768bd3d2a12d40d01489c910140c869bb9897594601820ed3f3feccd57b7b3c503c0d930f1182b1b1a38aefe13ac206cf910035ee7e4df6755cd09454b5b642d3a21a09040d6a0c42cb6721cd91a8c894b148c232ac224a41f697940ecb4aa220f73b001efddeacbb642d4f56053e67b2e4c560e7377047809fb85ac622c794c212b85f435ad6c4884e132f22c474768b73fa5ca69d763c78ae74192ab88f7c8f67f8df8ae381ced2769d67e23f6ed00d86473c003609de6d07a3d884a32f9ccf8684652eabb3dc08e0b99f7e7f5b31b83d2aadc707f536eb9e97f682e46509262337d1e5e17c627f8ded0148b15a6764708b00601e4615de3027212e4321abfa73c622d85317c88e5d9584614f4cf23680892c1c506c11a0908e7e6b793ca49191ac00038a7775e6ef0cd5f3e3c7314ebb16ea6698dba5e947cc808b48b300d7462956f6a3aa1a49db126d2a612b3022592ca24638010569d2e69d43843621e8ab09f4cbbb66b8734fe2d8df62807f20e17b21502f9052a101e44c87e5263a40021faf533bfbe313974b7661dc2598055c55c0c83a660f9b1417aeafd372fed6a202344e15e4c8e87fb0cca3869165ef0f36ad7170f57c09cfe824b671be17166e360dd040317fa65b6bbba50083b3d782d8eb48692d646700f431793e29d38ae8df0238a4f5625113e79b98e672130b4b8a0196464627e1edfda6146ec714f906083b54bfdecccd65223ccbd07fd86870cb4656acb541007021a7bea23027c6be2f6e0d4f91d98766f042fef26998b50f52a2674205d22396a59eaf2b8c71649a8e056189c0deebcc5f405355333ae3419941c74c0561774b0b14007ba9079ff9f40c4df4d788a094bac77f5fadb24aab7074d76622e47f28370887aea4c6d1dc87d523b365252d9efb86fc16fa321900616046ad41515e26c6f49d3dd3f560ccf85aefc5c48a04320019bb596fb3c901c766bfc86757f4725176382b301b2d360d9d120c16180ec60b07436b371757f4e824d53132653417600e15775de6f1c8e6f36dd5f1e21f71d0f50dd116a059edb803cb789d03c7b431c0b003ee483bcaaeb971b072382b703b2d761d9d331c1d67a3577078b18789a7b940f17a643429b7192d3c649b89951286f1275db9475d55cd21a6796a68ecb0fb5776e0e1eaf9f2a76d808360ef6b3c51f76bf1d768c7e56df7d677018c76ff10aaec414d58fd882dda02c230ea249ae1e0d1b886cf1cd93d5318c4d7607b577164c8b95a819e4487e99da2763e6a6d55f4159f66f186092d80f93a8dd4e9d8022f075cc01b56d424518bf05fed964fd8423cae7dc25478968f5ae8448bf6134b1d89731a69c3ba730d505987a8fae1ee1759272f6c5fd112b33a4ebca4e122ff67475f659e562a66c84ef9a6de37436a4acd1aeb5725f8c6831416cabcf8c4ca3aaf2814fb5a7a10e6f94a77789d01d2b71fc9396b3b6d38003be261f7290be1ca2c1018f432c0951a0f1f2a9e1208558e4563227be3c332da12d26bdac7ce02edc29c34da7344600448b04c3ba059dddacf0be424eab0ef2cab9e4800eddd247571cb18ff6d07d1d4a2c01363d6a8d19872d748e29cf9f26b3800310769dda6ac68319c5263ab0711c9a0fbba4d271334baa9739e4ce4450e38b4517d572498585b54613a9b5ff94ddc35c1ce11d13f3eb60332962022318de3141c39d000b03d84039d9c00ba60b676d997d99223eaeb3fda3f2ff36f3e0df25c58e4976536b8b3771073a59215f4aba9c2e3e1713bf6a659c9a06ae6220794de25fde6555341e00aadbc806e442260faac721d9bacdd710e887402d99dde28284ac60dc583f0d36f1fb2aa43b8dba23581ddcda33e26dc5ae09ad356da98940681fe519bcec25aef6132e54087f437ab9d941695d9c92adf9d8d904e15246bf2d9cca2146a29b7738f71bbbaf0f9a44aa8564d0e717afefe421fae29a841dd9b1b7f6baf1c1e688d7ed23f47c0c8ce5189acdef3422ce7c58b01177494da04dd31ce7ad3ccb9f78ee1e125d2db4414f5d15c7f09ef036ef9f2e94a0f80c402f0264d44fd9226a56f229ca6c576e2ff71cb86441f1c689b06b8ceb0dd7caf85b82e3a9efbafacf34591dd73d689fec463a1f11f2a85349c7294bc99a6585c1032db728cc08691d99dc5888f081601243ef02484e938af4ed38bbd6bf0d349e425800a2e424b602088d67c8a9970b158dadbbd2779026fe1e877e036eeef79ed82f258ad63cfe5006174ec9da4fe9d22c0883be069b9d20eb127308d31a363b7844aa5c3c3e405bfa6ac2dc5ffa74be0c58ed62f1650208b0cde362cb9708578904628c80315bb0d521f79dcbafd177527cc690c702d9a16a193d0b655f8c3ab92bb22843650dc74167205404713f6ea64fa2d0b70e8a0098af23d6ede4dbe26fd38a02189654ed8c523fb3d12bcd3665345a071c6159594a84044726ca62012d9547fd9c5be115c85b1b8984ce0713415773c0a51b8e135aa4dfa95568959eddaa3266dacbd5fd106935e285a894a9d7d0a9e5ac28d63aa3a4ab7470b29b6e1cadb0a3111127f96ded162420edb795c13991dcbd3971ada597a0a4f0e895dfe518776e1e9b61939fddb0123ba5d75501ba1983b5711894ad4a4ed17edcf5fa9a442930c2630f23d80068ef6645b7724e9c3b0f1318a0ae8bc3a7c02672281edc9b16d630fd4b1d84de422a783b7ffcd758d0b04c684b4854a09b2f0a39470f2555c858ad088f6d57cd03a9e676896510c15cbb44f2aed2ac5138c3522b54cba4dd5a53bd5a02f34d40d61f254723b05557b0e291faae9aa6fc056b6473601f8a882a261567979df3b08421dffb10ca6296e84fea9f1c5bd3ec6d011bbb6f4924a73f39c3ad1cdb011847ae3e9b9842aa654f4edcdff8a63e17280597e2b2aa5ffc7a0d8ba7b45c68dff41ecc6fd99c4a75f56b616d9109c7b86978b11ac9d8c1a4e4ae280b9712024aabab8b22f5f56304ea6ab78e6a3b029d5c0a2e61234463fe89506b44a4af04fa3874c86f6ebd0ca96fabb25ad8fa0311906e00fdc65c0785d9e9a3bd053c9952e5a34ffcc5d298a35cec3cb9562123a026e8f1e4e0c3839cb042afd09d35ecfb25c61f332521359aa7f6c9240f9cf03e82d731f9f5f487c6352aac0314d3021adc555f5d30522a9b0ef1e28426524ca8ab663d160dba674e3ce9ae2b07ef3299aa251505685066cfd96aac86c2e88aebf742986220d1c260a817bd5a040af1300a49a1878ac3a36ee8c6b98ea1cfd6ec45363dc2c3881006697befbdf79629a514f205ec05b305bcc1aa6f321d9658d2df45ec244bd23699d3b4134d6c46b5ed88924d4868a64454341372f992679b6c33da8e6c421b0c969c6626d995bf139e127a45f589e44e9cc9694e482152cb9d546772a6ea3f7da6cc6936b02bff15c3f3292bb72935e47b20a7acfa2f973306ed4f6304a095181512d28c9413261f274c4cfe32605e5c5a586241b11382fc6867fca9e850f00d7f15d19fd819234b125144900aaa3f92ea7f1a8d6a5852044be2b02bff203e1dc6ae8010fa41142f96e462497f1fee7994a652ff2c9a5ab650a5a9e536096911c36215344755152ad86847a5763763a2ad7e63d0d94d6b9f20744b4edcbd930e688082c61c6b7287059e1d9511d485c9c3d3625848335f3e561f17cc916e8ed067128428c8d177baf9afb95bdce11102ed6f74706c1e4f3c7ade77ba59c0d25800696767fc4b7e17d072a1b8ff8e1dde9722eed0817a17e6a7fc875ae1e32aef8cb747d5c3538b5bed10d4b7f1503c3d288ebb33de7fe20b5c5346158d0951a211954f51e93fb15869a7917d5040594dfae7657ff81e3e2bfc920c59dd0a47f73c4a6b18ee0afcf04330f47ed206cbc349b2bc479c26fd6f9af45edaf116bad5dedddd9ca404a9cafcd73e511f9c73776fef2697d58ac024454535a31e5a8c2c94b6265ca9d53c218618328218b80bde82c80b3943414008f1d9284dd6090e2d6029bd89113257152aa8cf76581097299c4d66a276c9486c1f765de735c88a60f275953b13a9b00d3336628193daeda4b6f3a6b03577cae4c632c54293debec6a063f7919055a8a0fdfb251b19ed380dbf7896aacc7369ce1d168e2a3f4fb7542a03d134bc03ad856e6dd211231fbe823f5ad2911ad42621a63a7b5ddcca335fb10de81ed5ad658afff58d41474fa401dd2f3d5a0c18f5ccdcf4fd295dd0929b18caacc9229f752b759310e8696fbeff6e44108340552b8c0c60dfe6994273543e4785a69df1dfc6f042cef33cefdbd7f721d8034e15f0555a54a15248814853535393d89432a2787ae2a94997265331bf339f128e5fa94923b1318d59bd99a8de43781f48d44bda08e56d14f6964975eeb899c29c4fb8ac03dda4a127d5b9981e4dce9f58932a1650fe79b58ca4d2f78b240ecdefdffa8790eacfea968f9e4769ec4b560a9e47e79093ba4944c84ea89b34645439e6349e47692af54fd24a52c7d6f4cf3f000fa9738917069dd58529764cf56b117b8ee4bc8c2edfd3f2348a501dd7e1f710ec979f2c3465748a1a9f54c00a1d17957548175dbe4c81638f60046d7997f7173ba6ba89fdfcf21268f99751022ebfbeea98eacbdbb467e4e56d34cdcb4f16d2ab493a6be46efc948b4aa37e98e73c75ddc19973ce39e79c5d4bd7c9a5630a99f3873961384ea3e226a7a2f2a8ef49993eb5e5e72e94da32ba5646fe11631c63ffc6719a14d88523c37666e280f3e7fc9df96541b7295a4165a6d71c0d7474c7409949e8349acd8ececef8cb0826b0d32c74eb7bcfa18367a88e584644cd41d5391acdc66667fcc313db6c0daa04c28279192fe3431aa2ea05044710550fa8f1345ef532f2a8f134543cfe47f8df9d5919550b68797e7e525fe8ba81ae726056fe960752b728288bbd33a597e2400a1be0cad230ff29fa80c9e543661a7f5ffe357e5ffd32aa1ef05fe379fcd7a8f13d22fcd7f8ff1f7b667c8dadf1e2f3f81fc71ed5ff8b63cf911cd5ffd813f335c67df1c85166c840cb55a25e8aa6c02e28aa964b5bf958f8de4029632c4fd1b2352c5ff2104e78e28c0354dd405751dfa807bba0a7ca309f82498daa1552fff22aa4c66d52060ccc7bfff240eaf730e2e732f234b9b38526b75a001c3bd37f125d402cc460e0892181681d4ebaffd00474541ae2be4610f71523ee4bf579b5db978e163398a63fe1cab8b8870c031428309cbeffb3719ae7fe24daf8f046da7b11d2041b279430734300374455f8ac07f21280a80a6f7c8e00fec6877fe3c6b36ea0f040588fc2e7b01e85f1c71199ff71c2b364c61cd6a3f0288c3fc86701b9210001bc0922eb4f106f3c29a2f0366666666cbc09e30ff26dfc8f137ee68198f0404af89cd597f0fe2500e04d187f98f000f81f25fcea81bcc22e4124c09b200ee04f10713c2916e00b20aa38a44d0735e941429b6c9b55ffada85ba51fcdba45f4bd1f756bcaa6d256444b9739cc5d4602ba7de92815168a0afb44856dd2fb243b653dc6e6e019ec0d7f0fdfbfa3fcb04b363be34db6a66b939d014717ab9e8630b963d89533d9b08e01f1a7a880d5324505cc886398f7d33086a1612a1eafc2f97124e75ba972def3bcb1c7cb540dc0717df58d2e5f398f8b391e4beea1fa566bb8bdcb33a5eca9dcc8403be33f846e23ce78d3e4d65b1647449894c44ec0036cc9107c9061a526fe61d09295941aa502d7fd2a003c9abc05ac96bc05ac763e36af79881562613db4ccc51b610d1bb94993fecb83eacff15619a5c29c356c14f79d08564f754c0bb5ec09a526bd95fa951c1c42a78f4749c29559d7d1a36b7be609db85fe685ee84f6ca157fe9c98819df1f7440cec0d1783765defa679c2eff9fef43de02f9367e4ebf7449d1ebdf2f0f4dff784a7ff9e47f8fde9797c7f3a7d189efa1576037a5c5512b45c261e4ea77b50605b0758d27fd4b133fe9b8b72bd35e91f9c1c96252519aaaeba494900a102cb8e81205210c3d49834e121d03b2684fbc42488ea34c94c63e11a93a6a1cf29292a2a2caf32aafa577e5f3d2e9f32aabae579a47ccbcaab7c4fcba7742ce3ae8cb4855d46d95c79069ffdb949b7fc9be55bbee507cb188295f108f840640da482a3a3de1f48ff27d3e4c3ac44600625163908d5e2096419d3f1855ef17b3bbe62cec72783944f797e1ce620670939978ce4a4b20e8bc5c2c961d96091a806a3bc4e932c43b8bea27aa139ae7d7aa9c7acf2ebec740ba6f2c340e303d68c80a23207618450e5e7e1b6f6a1f2f3ac80b29af4b1355bb97befbd6edc1718841a89fdabfb7e1b4dd37127f6e895b7d3640fd761b58d6e7d651f652eb8011353122d60814cc935a51a55e2d7f62f5f4197669064ca508ffc31095ab23c2f06ceccecceeeeeccccececec3f4d9ef8c1120c8911c90d03bcd9d9bddb1b8b203e3423f18a27962cf919829f109205121a9ccc40d4440a2366109a2205480cad00c80a211560b14b8ea2348164288a228de44807283baa7852e4480bb890a17a90c44fcd071b92340144d4440e4ba408c202206a53d8c086fe6a30248aa1708a2234253f0005315bf2e467c8cf2c92439325419014418208dd6700ad0ea9adf833ede73add15ad6eec93d965504f3407da64044db5251d74380ac2c2892896242931028a2555bc000914330025d54cb08276e2892f7a7bb777bbb7677bf7328e6ea5e65094a41cfcd0474562dbdbbbbddbbdddddbbbbdbbb35318a6c3e0b850c507a77b4df6cbfa3cd26de6c37dddac6f279e38dc51bb6d971c3db363b37f34df3f6f6762729a9010fa1fc2bc3738c7065ca19e379a9909190883890ff78fe333dd2c53db72ef315f820f8a7ef091fd503fe49f56d2dda6256dd8af1c7798977b12b7f1e5487e24cdc97ce44c55951b36e0d6d6b386762695a881d52ab25252d13063000498ae28c192b2bfec38d2de33e3128d7909a74ee3bd4f729323561df09c249b757211cc1ef1b4bf0bdd1d5a40df8a50b649b19edefe19272cf7395bf837ade7bef5ef7fffda2bb54f7dee55a66eeef7b15b67e63f879cfdf89464eefd234a7e79ebacb5bd79237c30a8ca458b2e1081b64d58b8a8a8a3847c7b89bba514ea44612edbe8845ff6920224affd91a21b666bb695f9b118d8b84d0b29b98748be35105ee69547d5fe2201d107eb79db8686b9886add815c91dbb2b716d0c42b92fb9569980231379230d328fe6b1c744b4d3149a327e9616626139da1a2eb6e5c5b638116cebabc1c8b652dedf89587f626b54dedfa1e01cf4fdbd880dc261dd8773e4185547a2820a040f41aa7f87d151fa07953573691167fc27a69e13617e135f9e672d24d4b3ad49a9eecf21a12b5f6e34baf2e55caa48ec846f30f9ca7ffaf00d26a698524c89c608ad168b3565b2297317b896dbb4c9362396d191c21431e651a2ea19899db0f44c8414a1991251d14c28e64b9ecd52336488f0cec4d433522966f2e11bfe2c4c3ea713cf58624eb3c9b4d81c9b11cb268bc5e60c605e5e3ad641ddf4662e2d2cbf4b61be9a49aaff36c36e22d84d86b186a5d964be82718ecd886ff8a302375f10a8d731d833099529e6e8ecd6a07e64e2d6a854ffee616bb83fd6e4b83bd3429a746ff6a91ca549e6906833b1c307f2f2e7ffbe0fc71fdd7f3fc0f776290a98042cf749d79f9f213fddf7ec13efc1b11b7f80db0528dbe709a862f04fcf237cd4d8e385370c68d7bebc574ee7ec8c3f4369928beaf705dd4616e99afdd5e0ce35aeb112d7d6a64316d25ce3a52e7f546ee4d919ff79946a22a209e7740bf8fe5493ffdc6ca265ea26f5a38afbd2b5286e544057766d0d379ebe24ba8d3adea6d3f188a3d3e9743a4de634e93839364dbab7747af8f42ec8118de3d435a2a0dd77834968d7349bad112b5d9f5f14fc83782f7f478d3a76e6fb8276638a8e59501f62b6851cc303cafd4682ef32ddeacacd61836ff89f3e9c4d22c0726d8db73cd82e7c7d5d8532154b20d57b70c7b9abea6f8373acf646006ad7502453314dcae040b9ed8659d8db5077aed2d5bc28540a503420f1135b33674a093e12c40e218bdfebe8967faab7a6605229a430bbcbc36c9ee3f2309ff3f2a95f9787791e2e0f33aa1ae665bc7c6a54f5ef4b064c932c1fb67c384fa78d85d296959516951696efb422d2d08517683d3d7f8f2728941304ec8c3f8b07d81b2fe28dab6b580f964e53fc0441c50e3f4d3c7952cbf4459d7329e5f5c8d3e4e4f131392e683fe9abb94caf81e5d91a5ae7b7b035f3a7d75d6f403dbf471e2ca83fe934fb9aaedaa3aa7ef5b301e5c61f996c3184a157feac49a2a29e3957f967673ac811184d5b88f301d1b32e6a1943018202e463c856bed334bfb8891d08317bbd66b32e6a23f07fb6860b4f10083f3c8da2e72b70f4888a7ad69dc0cee76ce65238773559d433577fd9b36de49f9db9b1a9cd3c31361c20f8295ed0a288ac269bec221a6d224d21f307e6d3640a3893b6046d9395dea4f96a095a4ed884398d7bde37c392aa3fe581bc548d5219fba76ca31a34977a3661d2644d9cb48954fd5fcc1535bbe8710a9593c6cfbf094d19bf50b7d8f368bb029f859a94cda029e38d43a5c0db269b42e537e9a22f35e94bdd2a276c0a75ab74da1c72a739ad5b4bb37103fda269a949f70eb717e107da2f739a09e3718eb359396130d50ae1cf9f4d544e581b9934186dc2605ddd7fc07ca1c6d904035afa12adbad3684bb425da52f5e762b4745a1735e9bf421335d9335a1bd168b4179a2bb139788cbde17f7a7f37da1a5e62094eb21d25cace84e015d33b5117b05998000a190cdaed395bc35da38c9c9e51356c666666661e4b162ac1064110fcd0719af410f652056511fc0ec3adb9707442b25b5d99b9bb9b6178a1f3b156734078cae9953f07e27c238b6cb21b6348b7626b38eecb0e22e18ca3d3834611fabdf7fdd327f4f91ec461f38bc27c4f0ce2bdbc07c520decb27c8bf7c82d01b042867eafcdd0294ab98ebe58de2aee602c6948ff499d0d92dc5e41ca8baefeebe49cb5630123ca33d763847dbe0c2f6dd871cc74d1f581141dcdcedc4af9ab3dddd45207377779fcd9b37770af72278639962eab123b2d0a47f221d83065a0c4124690a307c50aa0205330cc1233aeb3e0f5fdd75980ff90816b63531b1ad99df0b09e14043aa7b91128e6d0dd7fd37c28c72dd976a1b550ff01e7c1ede83e1e63d0f301c55db6824e5f7857af067b5cd27e6b0263b51b571ddf6df7be1d8037aafdac69c6f546d1f760f82dda8da461cd49f3ce63029e8fcd261dd7ffd4593ee305fc5be316837313549035af64c24a2a5c3981cd6ad6d8288a0ea5dfd55a40d87910e837143ecca1f89fe61a2a9899734a9c32322adaf4e25082ac776c67f08e5d1c685e49afdc1a07bb7fbe60d04b76d034fa80de4f0b481fc1bd8830a4f1f828f7a1ee0a3c653f827149fb6499511dd9ef3d8fbf80b41af1b59a4cb0b9237293e1cdf0a9acc24740393d0fe44564fd9944da139d4db9753c6c44253c6513e1aa8ff2e69d26bfe7dcfa32379edfb5ab7befdbe5df9f721287aad919a5ce2481f7f1c92d71ca95bb56e95dbac2ee99697be4b886aea085aa66ec881fa6fdd7ea8032c00aaccac270d9b51032d1bd6b186354109429747fe1f304292c5308ed6675d9e688114a5f881996b35a41d983908a9492d46c3a0297ada2f29cecfdd5d13d0fd263a8201121c363b6e6ca80eb45c9a0dcb85c4ee76b1a9269ba363aac95e124ed0316c32202de1e8baaeeb7e7b9bf6bc6e75dec6799bd76ddce62c1da5efaf29d5f28774ccccbfd3e4bfd840cb55e57fb18167078784176c8d8778f8197a9be69a060e9479773218364d34238872111045b3a259110c4533a2d98ca8685624039a01cd8488848a8c8478a1fc30d18edab9e4ff264926654eef6f83ecd6d25411ba35cd02e367cf903fb5710cf364a2fbada496293545f4f62be02a2a189ffcd80e0aca485d32d2f6cc48ed791cdb5533d2116db78924c4b4a36ef5126666e6187b50d8083f0943b48f5033d0466ada8f44747fc4f184d6401032d2cea800a41af1cfd95e3efe08c1cbe5aba01951cf84d0b2675dd4b396b5d00d347eb2240041f702e1ab66f469aa49268e8a08b978d3e4eeee6e775721039224e1323bb7cf8deb3ce5846e935bf3e6ed9f8668ca555325fc50c20f5984ac9202ea620141f73dcf474f8905eaa72eebbd238e4e4d87524a757056cca9f5eb34b7e1e8f8ceea0f4b70bbbb2c8fba76b89c481d22bf9c39915f8baaf9208a154504a55c43d4c7225e607676677766676676afb150e369a106456c8a2390581f77a03d7a23120b78109e7f1e8f3c9367a7c9de259eff7f1e5fda6181679b3b7ec33bcdd3c26c6b1ff3a9d41441d496b400fa82871f9808a5bea281f2c840b3f907887fd88128a514e88789fc004d77ff719afe2939c63ecc3eb13f516f7e6143bb45a98bb5a36f5650a9dcbbf8a09df84d6e2e624c933d95e09fbf7371b04df7418fdece30b703fdcbf4b82bc474ebd4a44d7b0cc29529d746b7b6da58afd5bf2fd336627ab5a586a0278f8996d45bd24fb57ca1ceb9a47f7b759630d4fe90674bddfee6474656b7df19cd48a86eaebad18c92d4cd6773fb6fefd2adf9dbafcc06841b76288285926b89134860f1ecb25ed014093950fe0ee830ab22072fe020ddddcccdcceb62c540ad20e2c4122d6214394110314c6f6b31e0d06464440f458ab868a2429644142a9210516406126858f213daa0ee7b1c6b1e19c6318eb14f73cd6db1e6d9692a22abc74393a7d960fe556c51cb57826e1ee7799dd771dbb6b9bb4fe6cda793208313473b14b5348184431bbe4e23d34bee73415dac19ebc00721eeeeccce1b4f66f776253da91d014612417c3cd07dcf4ba57e64f0191181388c37784ec42b8a9408519188a00454f480c81731081a628914454a569820091a9e7c5794a0464407198d99d99d7dbabb7378c40a21b57caf1ee1dbf438cfebbc8edbb66d7edfdc260f68b948373898210521995008a1b933cfc9734e9f93594da8b8412d7fb21b75ed535a9951f76f7cd53a9aec6e1860e4d3344ecbe9d636418488466d76f473bd719a6daf69a71a8711ef594f4bc5a880f21351e0088d2882848e5e30272087a39a0d6666f73ccf73f6e9ee9b3405915aa650e09344b4248908a8d5b56c752d5b2dce61b444cc0aa35aa66642c4c2f6be267a33bdfbbbd605922ddc87de6cc9bd8f364d36c77936dd363b96f3fe9bee5f990e4258eb13ed0889d6b414122d6c9e328aa0f375b68679bb14de4057b98b421c3d7030b033fe2a4ae8f6b3396cf20183073eb035cba1c0564bcaf3a8bcd3dffa79bf4223639b9a7cc8f89e948c6779fe1e984f7d8f8c7f7916984f7d0ecc987a1e735ed8c5260533aef7b7fc0b035bd39e4baa49199514d1d5e46260c579a2a48d20d06d3261b174f7245c19274c4b5b33756bbde7b8aedbbca02ed70a3a4d2ccc1d1a45481a8dc6756291d3eccbc325a7f17cc543cb223cedd74bbdb494fa9c7bdd9d4be19e6b7323f3cfcec0bc1bb613ed0889464b21d174b6c6bddfddb1fb1c705c5fa57ce5cb5b4a1982da8ddce6b51729c272eef771db6cd431f55b3be543433ea55c4019b56c847bda743f25e81c3d1fb706749f3584ced5a4ab28a1deced20f1ffefe3b3c5b73aafe18d89aad66becbd4251c4d93d35b4d3f2a45a69629d269fee43852622d656a7f7f9abca8142e3745b9746846020000d3140000180c06058321915840281c2d7a7c14000d77984c724c98c9c37112e3484a29638821860000000000002a008188010df3fbc11416fc68e1939ba6d428385c039439c70c73e9987a22a438b8b27cbd3f0ec0bd2f1f22d8870c8ec036211322c125d046ffcdb2a9682ecfd7848c359941eb62eef31ea41a0e9b0f22ecc16e6be837be6b49fb4794791d7be959424e05a7ff7975cca5eda5be88d77d379a1029b74aa3b986c2e6ab6052ce5e6d389b260c3852d523674af5e37e735140f685ce331425c46ef50458db95b84efe0fd76345e183d6e036bb9725c2e9d85b0a114569fb336e135508a236340e3d4f9a84c68fbdd4043d39cba62339f6697f30b53d7440adc6af45b42a9ef1c7c420053c20e4b87035ae92f842a1980ca84a01b4146da423fa5704e52259f53c57affb6d941f5ca24f58482a051301dc8b753579501af73808271fa9c0c101de25bb1341a86e21ba0df56907a5f7602ee0ca6f61158c2e1171b889bbbf67d5decbf929e574fd363e6f34f6f033bbe9d0ce6ae59df543f4b5ad0f5cd38eacd8882e9400b8ca96134338d0060ed4598b28d11be4b90407759965c2031640a104adb086765c590900c830007eaff2eb4dc9aeebf581a4dffdbfdee4d2df10eac3c7f2dfd6ff44e8faa60729746b02a1964910da4b533abb72d90939ff7afb5f288cbc1bb2c8744846e69967ea7750d08d87093ea4586b234ac576fb484b9a2b67f5000dc66bcd8d11629fbb0868d2861964711ed1c7088495f57397de8d4a309629a1d60c37c9111f46aaa07df2f60a789a99d601eb52b2b87eaf928d464531cc7e616f056b63c4425681df837349e18ffe0665ae9fb253da2fd7f7ebedaa7053793adc4433f30d94d30558bcc756befd89445c16753f59b64fa10fed62d23c4cbcca728eb1fe505c37a1dc8238ce0b6547026e71310fe90e73d0c730e421eabe4ca58f290e85bfc7207c840ef50cf28491560f6d9d8b4b19799f6d4d1cbf5296b7227dec1b4af47bf42b52f3b52852821db91d977c65dbb011e82fbb65e80d1b6b753668d8185d961abc42463504faf64a8ccd40d40da7a643e0f953cf77738fae3b8450dc3f88744e504222eaa0c036d13661f55d427d44c2b46cbcc2279e9fe2d9e92494a6bbe651ccd1d5af1906c83f25a542fbbecf013867219334dfd11a35417be63b4c2bf85e68ec4c21eacfdefc7902456f12be23e185e177779c3f78d9a0803cfbd8dcace043d3c748d10c6cd1fd9f34b0f55003326deb343fe8dae37e632b0803713f5b0473dcfe09f2d421053924c2eb654f1ef0897079a1909bec637fee543fec201de7c8caa3ee2046af66d309015204a001c4ea0f83865337b95722c98f149e227d09154853184e3657e2f15662ebeb3477047081690e22c62b91c12207ffbd4acc6a948974686815598c5a787fb28dac43ee0d361bbb0d3e945ac7c283e46ad40634900dc1350ff6f9fac9c285b9595ad7f4c281831772578369256fdbae12c7c01f0a86554d72faead2dc2d584589a82fd6b3739ae140685a093398e39ab6da2d3aef3bdac73a219bce352b3ed8a71a64741e8d500f011dd18ea3b2a211737d0d6b9ceeec07f7f58ab2a8bfd17484cf7484d9f9edc399df64d1d8329e6ee816adedde4596aa0ed2ce129ee8ddda18fd757f64023078535d1c109bf4eace3aba2b11e824656104e1bbbc0ad1c7206f3255041dcac6317b8cb230a81ad5ad58c196afa96b160506dc04acec05d3350029c34483d9b8990c9d32aa309adc08f7b491adcf3e47a45ad764f6024d1ec84dc78dfcf17038cd800167c74bd155f586ee9a626db3ec90c8a89cc5695601388dc52059cdb936c6c1281642042e301eb352ddb5d6a7af88ca9bd6fe37567816108c9eab58f80525ddec093759e7e9ed9d5f14954a5b9ff4cb93ecea763485445cd3ea8ca0ae5853ba7ef3fef741627d1fde17d3ebdcbf5f48bf031a2b2f27bd360a4b536e4efe4ddae0ee84e3b314c11f98ae123c429195f4fdede52e9e6a7f7c0d867ae158eeda51c704bb242a5548f92743812c2124edcae58c59b47cb3a6eb33ae1aa8f309153c7bfdc3c07f5f9129f16ecca4e9616d0ae86298611aaf0939b63e3e16cc5811e971dd290654c5506ea473c3c5a1a21434f1e1dd52a42951a9923228637f30edce85fb6e42209967c60848484b5e8db2a00c53ab31863cece850edf8ca16109583c72b0dab416c0f9bda585e091ca3829c40c0b9432203828fc4b3c3db08ff4093bc61e8b02a22a75de6e7e51fe8a4ebc88b6217ca056c90ba61ac70fa9372bfad57a9b937d3aa3fd8088df73cc272f4bd6b96fc5aaa1dbe4a61720dc781225d90b673814afa0ce53140d51ef58cdf15e7533dac44f8d8388fa25a89b94f235a44935f5e75ab342908252fe5c5c0ce5700a8009cb8061de7a99d0d6a56df6dc767e725ef29978d1e62294b940a6fe088b1b9f29f3097127957996fdc654a89a6e4bfbcd04593f2ff565b9e794030d3d9d95ed54703e7a0e4762f4a05f10174f4889fe5126645622b1e311eeef268d62ed3555e1c38529359b29cde014619720c59c0d2e8f7a17493a140ac916335708ba24eaaddf103ad728f2e7a10b77b5c9a737b76026b17acfe11e6f6779852dbf661d56762db623cbf2b8050ffa974d53dad7aaf9566b919b7774c0efb0212a41dfa65e84c27bd2b5ca9ae31ad02a34c7a00db57b5b2d81f113350a10b5fbe747f8a51f74e02ab8b3a6238e47a8b877c9c63a66219e6b0e440e35ef9379467501042493ab185098993d052fb57bc502c01e9f2110c4618c72c4678947ffaf2022cc0d86cc64634f68bbefd1b0fe8a74ebadeb1013db6b19a730a24f0409a85c80e5759d0f8d225288e0430afb41c6f48c8d9bcc1d6acee1673388346128589a3eb01e6be135d13eb4c042ad5b1fee5fc7b8407ad6a8ac11778a785105e0da8ccf8b86155908791f15f11c93e4dfc2c9617e4a0b246a3cdaf7d7c8e31800d588090eaaabc54490705670e4f726ad1caacb41120cf8062cbf3e00433aa2855a9cd4c72aaaaf9d85c6f015d7001194346ce6a9411a74fc147068890c2715d6fcd06e5d36cdb157f6e206062edce0499fc2468dad53d73bce1016f06a5d534cb4b9b227ef86445db74de10271c7a88a014c344e6911240ee78b4c1621e1b1f7a87964034a6f5bb9a7b5e8d3a7931a1f53a748209e099b771964db42dfa653315290639f3503b06347def8d35d579c9e0a6fbb0978615f728d1eeae36e6da27f945cc05c8b29d2b0752b68c04880232cef1f964e66818f6d1a2a044030d23928af467772af42de8023e2f503a37bc2972e2ebbe8cbabb05f130a2f4db22b6f4452ba201ea3c16d9737a898de8413056b292d1e93e18b393624f1b6af4982db65dc955ed442d92fb2d4c44295480a50f4a6763c960f656ab6d82737c5b4764317905846e2967fd1fa144fe84e828ef904740b2d3509a15568fce0733469a0af51749040467c9d8abdf3ec86678da489a86c51ccdf20d9bba9c9a10a21493acefb89f4124a01ae9b5c36f2a21321b192b18c7910a134bbcb8a326d3a6ce1a65a4385aaf1b0b5c1a8130b925c92fdf42c98eacce4d8930dfd0459bac17a15c2275d1f0e214bb9edd2b674fb403bc41598286f41ec868fb086ebf84341617d1c820cd5301caa7eaa34906278e4d4f4011aff783948690480b6e30b670a58955b24a82f8e8a940a2cb59dd548066e16a04ba71cb36f78394bfcd1453f49415dc2855ecfdb4f42d28849ed44c9f51e23a53a25ee003a6d933cd0781ef91d7573db0f63ccf87cf0ca26be8495eb1e06d104eea5a87fab87cd974c1e626a745ef4685de7261b4496e5a49462d3b67568ca821d8f2f65c9577f6fd594ef10a414bcfaab2d96d556a4f8fffd00cf0b3fe662de8b9d178203e0c6d861d95f4c70e8913a75b38f60ab00e3abb0aaf7b5de3629a8d448297982a21e20a019fac1db5e10a33ba3c00cc301a40af6cc506deaccb982b981ffb64f97a769b91b4de11acc8ffd8694e505899da04900f87f65d37986004c1222a81ef06be634aef422a700bf783c7b3500b2398d3ebd6024a70cd67e3ed21f1c2c27668981d293d9b0ad3c9b59fa1a2b38b32c5e18ce0e5b9ea9491807c5caf9b7405f5e6a292ab53334be0f03f8d750f7f82848de80519b34628e62fbdd9585c772fc459802705df60fe0831dc399ac70db73c6a3e542e320c9fcbb80dc4a3e29a010ba6b5bf5f4cebccc06ac54f1c33407e39a45507e2cfb3bd5dc2e451e36d7e1b5950cc3640840e89bc45c0174c59fe31a22486cf2b62dec2fe1a9ac65ee04c82ae9c3946d6ddac1a8053ec01a19a5cfab8afca6f3e0a7f9bfdc03c568a5f12d166e503659993e619d3b145f11c7254b8497961848cc62c47714d43cf7f7b84e643dd1c2c363b73383393b10324bad722fc410b60c6d38b44f009ac425500636acf2d180b34acb4c72b3b0a31f2be2eada2c785284742f21b81938ca1a52f7e3dd27412434d316947f9c2457ba69b9c5ac75cbb81ced1dbd0fa701a19c76d3942fa5e381f2c0c4f274cec4458c4987c3d8903458e47314903657956fa29af88a22cc49cc08ac33a9678c5f318d4074689d12b56c90daa79cb65e40025946822e6103e18c8e9b8910e7a59fd788c31fc30d51a9734eab0f25e9090e6a26eecbe8da317d7d8a8b99a60e2dbf698ef4bb406d391976a69f7478027dd3d5db0ca93dfcddee1b8892a4ec8eb8ec66fcfee2593efa570ee13fa7d26b7d7b6145899bb7c0ee822460ab2aeda0b219cbe3d678f6e3de043e3d76d1fd1d233497dabeaf31cc47d48591677c3033e7b0fde8047fcfd1d12415b3dee8d6f06199ec55c04904dee665234f66f59ebf7ba840220fe5cbab3637f4ffa9e8a9c59da675b25001136619807f7a5acd1319ff2ce814bcbb01c3928bbc425c40caa166eafb78278b116879c98756018b8bc8a94922f4a8eeddcc4c5c9c3de741deb995dcd50da11232bf78e242916cba97ea1fcb7ef479d55209e6c48913ad83c8f110446f7a1166c44bf180f1bbaaa104158ebbad2ea2a4236f44e658f9821884ff8f90cfee783d4a161c9a0d12386d3e27fd98a27448748ec9612ba99a66258300558863ae49d1ee342b9be15b06d4a4de1246d8adebb57ab312861647ed5ba9034fd49c4971346cddb1b47501dca38fd799ac04c0cf62a765cf339815d62915197eba18fc5959dba18e731419e55eb081613cb29db10f4769ed8c796a4fa5a2f6df7708aa7ad6b831efadc91b9013ad3c4c976ac2c3894096a117aba338b63d1c61ab4e049606b054989c751eb89c4bbff00a5aff4339b6c29e421e330014253f3358bc34926ada2b54bd483e12a9800e9297e91bff59abe75cf90fa2c53779cb012fd061b25e5ce26370ea529c42a5ee0bb88961a61f4d3dfeefee4c03480c6870bcc18e42830b5bf7028eaca7d9f6c3d50378d47c0f7b354ddd9a260e9ea118e411b644082e9a92994e94e665bca2d0670db440672c70b0027a8ac42ac6437f400cd99dccb6c74089f382f314dc486a2f5e3af36915360642600f87070e3bba4c06c6840b2179d1a1003ee9842fecaabe753511835db8df110998418100b370cb6921e37949d9771b3111c2dc74a86fe276d68619a752f332201ecbf315138f377eef90c7761d78c6e268ac2b2b9a9299ca316065f6a1b75defab79ff4aaa3d1722a406b0fae6686b6ba8ec1a621f58644ac702182bf5d9b274b863c83c7c608df188a437eb6823b2049135837851a590c1c728e5b745788eb0f4b5808975b31701f4941b40ec6e8133b80ee02a69e14e7bfc1149fdfd10a62a70fed82cce1e707e32ac6a11f59ba9ded4e25796024b1c729b0e9eb1a9e5f28f7e29ea2a7f2c37474df742470a44ddf75e180414b8a10290cfde0d97237c67395e942d233be269c6410554e2dcc5ab313af66b608c2aeb861556b383ae4629ce308778595f389b974b24f5dfb9cb8ac9019650f80ed15f94c67dd048a858f6ebbfd8551b393bbfd275495eaf6c4229bdc9bc885bad1a13312a8c6e73cfb003b18d924c02af490f4e6c86d3b8cc83b8f493a2f89a22380a5d9dea0e47a68d950b697ea7bb8f1a69878b6d870f14b99c446cc67265c2c78152dd674e4ad9ecb4c76b62b77c0d0f58655eb1b1a0144150a7926bad4a49c39b6ef8af0218c05affa96b0e31ef895f9ea0a684c072c18cdb7c3a6d833230eb9a91712fee75167242de473aa6e5b35e6782982ab068a5984aa944c7e9c85bb1ed8cbb05e5d140e0736fe3b4b774fb72428d714a7dadcc9b5779e9b7ac2c22d0e35471af7d2a3ec91bc49b5b58ed3c85e415407df96cf8fb67634b8b35901f299d421f8d7da1041f09580b2babb23fb6e8c93b16c1ce6eae2c60720211843db7a5f857c7b1ac0a4a00515a138934bbf88774314ade4b2beb1ed3f14dbbe19792a026dadd2c3451881689e228cd57800950e378647520f372b9d0b866d953312e1b8e9248cc12951b4cfe3e32940a3911efe1773dc191f220a154fef99848803cb8784cb8d8c882c8bc7c15244251553d13167e196446b554053df7da43d06042e5ccb7a30b12a6e9eaa6a988c2f6748dadea5c6ee8313657fe6ec84d5567cce4b24ccd760fe080f4f465aad8147d8fe1a317803c6d2aaa841f9e8e85983d9dd9ef13ea14b104248559a6d4a790ec178f57fbbeb14601983daeaef49c06b9658d34599b596b548d7d161482392eec030769aea81d1efc9b2a77fe0ce54394775e9b9fd4aafbd44db4cc79e8787c862e9e8b3c794e9200521abc41c4952d3ce651949685340ffa65219bfdb6d9801c26f58c9eb53353e5d4d085720e2d445b17d90551572648b6941544171101571e88216b74b077384a465568801c5e6ff331bdb352f2e97a7539c446949560dd1516b092caa3603a946218a40b8d9fe36957196de6869564abf3f47aa19e833ef78e90ab46e1590a113ee67c415576651f3e91e76481e1919c35b12d2a56755b8e5a44127162cc2abddb09e09052529cc6cdeb3e58cc03cd0e14fab33d0afa33a93e170c6a0466fa51114a1f5f3768c1f23e4815cec692d8662a0bcf136a623067a60f1c9093d6ef8cec51d4b3f65217b9a9b6f8a5ae6598004943279803464c95fcad3a3a000fc1f9fbf002f3d26873b4406d6224c261085d7fc4c1ba9088031e5b0e5f1f483d7a1a4ea80da9083a8d0bd1e397ac591886f855b2de7c375f0703b5398e0b0a3440b19f3cbfae6e09f492f4328067205035224b6a4fbc44233d78f18cb43a13970949f5834c4f40b1495b8e548efa78bfaf29955bdf5746e76aca1ac9f4b15e4184ecff0a35fede4c1649c6993bb28136b88929f0dc17260419ded7ec09d8d1e19b4037e48f57d2de6ca730eef147de7ee11336b3594356a2ab2657f2b1e720e69663d3fe36882c5ce9daa39a988880b0d1ad0fb73f660e15f6ee0e6cabbf6624669964f2d1207bbca2bedc8997697472588cfb9bf14f993adec19cd118909c29f31416200e89b843e74d79d0df5c610ac3c5f6ee48889c7e1fb01490cb1a8256ff117f837ed28fc761e81049eea7c45f6d130b29c52a065645100c4b000bd424249490a1d37b6443dd88601288d22f39b894501d4c1c8b64495c2df3d39d7efb21540f646cd9be060fc7c389e023752955d64bf53cf5da8b7fdf5552194bde3925aec7d23d1b46b814bd0ab50e82da3e777a1d6b278b71ae994a0ab2e140e14b0659979a56c2d45149dc04231f9b1dfd1942fc36a95864d4fd4da580e187c3b36e400fa8a70f193fdf727ffad0d95248bc1e2ce03ec11d5d8d3e1e32f331c047cbcf737da00bc35460330e473ef7166fbfc75dbcf387be533b00e9615d86a430fba7663af55690463d2f715a8715333b1ef6aa2e0170c4d2e3e912df3221e861128efbc10eea160079bd593ec5bf4af22eb648c8b45bee1b2da6796d50512177a8bf1025f1a4da4a144a5d8341bc40d4d8837327f36fed53eb1eb9e690396539f699cf9ac483cecec692175d619371b974e3ae5de58e2dc082219fcf1f033664986f15bfdf4941b48686c549143012e42f6123f4d8be8c19c510f3b506a1b68c1eb5437dba20277c11d94bd94a671b2f0f071c5b43c3c1dc2005007022e4c1b5c45564005f5bbf67b8a09eb06ef25b36b38945730f1d903fa8445295c71b9a706212cd02cc04c9a7ba73025dfcab49faa1776fd64cc2419501071d20d37c7b001b5b0a4b21955be26d966eb4a731ad37a0b9c8cddc0cc27def34a06a00cc8edc2d982c4eed931cc894cbb3f4cff3933b7151b71dd7f132a195ab05f9e420f3f6ee5a6bd2a189231887b9bd3b46f3a7b8864df31537c50ca7500f13692fd427ddb1453dbf872dc35c36a7f88bb69668d8672b9b48c3bef9e99e9a5e1e4d2101ae32bd079e144039325070725f0609aef3c552d03e5fa17041e3825db33711064a59eb06c2f9b7e36834bc715a4dde151641832f11efee1219db1419c1000fd6f221b4ba9f31cbad579905c6ea7dbf142c9da8950065a69819f99dc4862146bd2ce2d48c2a83d5ce0b1ba64d7baac7fc7a87254fd1ee0f762e0aa608338393eec2424f6fc169a6a6969a6a682f07a08006a03c797c0ec2392813cf6e75fda81bf6c3c93e844b61747a1c5d72b3bcc9982ef8af861e5299fca2ad589ec227ca8b1030b63a8e83e4bfdecc7abcd1864be4b3b45267731dcbe4e8d9752413689f2e1c50f009f438fb9ceb0e39296c816c124d44caa09bd2161db0f8911a094c1b487471575d552dbe1272e996a0660d2daa49fb03dff9b1a1252ecaa70971217b4533a0e7e59d8f07871c69f8ffc3399cd04af434440a8c30ffade43a287eb3117bc9116626f4c1005652d28f5468ffbf4f4a3f17290e27b06c1a0f4a37760122d4c283be28dcb9febd4b063c1a5343f134177e152e99bfda3fa150d25c15b6a18c69b8a46c953026731f4721853fceac236415266e898b105db0a3121b4d682d90f44770eee7901096b539863155e3e28b8fe235e5d474b4b0529f0611f060379a3e4f37d8e0eb397c932a01ed678d9018242fe6eb4b5d36be482b957c0d11e7ac0cd842261417289430d12c5d081227d207facc291cf02d22a75087ec0d9cbce192600c32fc62c5ed331a8fc5e463dba0d4a0711a1545b4a50acb31a43f95991d9b746d162c099f7fd8f1221224a8c416fb84a589056def4002369655ae7708909b982b4e7487b6d6089d9d0a9f7bf113468af5364ff749831a6c407cf782f2f99927150d93ca91b8587ef3223f2693fcba2074ac93fcdcadb7e89ac564c4d16a83975526e813790aef8851ac4b6adff1e1c41f84ba7bdfafa026da3e291802810610d78c34329825710724dd9a5367515d5813ed59b5c8959392ed4c462537da136ee3695e2495a737e5ebb0508d569ef820045d693719f549346aa410975f73a1b2f062bdb21da90df7b420a55a0b5948b63133ca6bc586d5c4117d8f1877f3f13832b0ed040127ab0873a05d20fc06e857fd5d43400a8e6623e5253e0a8ef4d85087bde4fd177122448e6abcf6803832ea731aa1c95fdc574f60328eee7304754f625f27681a37264897c84df8cdcdff3eccaeb95051646187ef0b716d64b5043189928a3710ff9e2763b91841ce42b10489414382b319a65ceb0a66571213ca6d2f546df4fe7d8668188389035e5b435d5d719a83d8c44079dfabd196f61fc60c26fddf345c7214cb6cdc80050a7a1212e9cf3032be97285421af45af006c4a28bf674e487d13653d141eaec1b729fc085faf2d6c6f3dd8769078bbe3aa86fb8b4258f6a344a2cec17070dfc698413a5db57ab9ed6ae9850e6b8e460b6d6c08853d287950af928d296fe4bdc7ef793f811a326f74a23eee7166fdb83971e174f140f2d243c9f98a33cb8edbc18faf98fe2637196898be92a3c94aed3de3b5a30d233ff5b4a22600a5b91174c649e5e956cf66becf2f9b741132ee7f8391ca5ef43d3f45f96f64a00b50b9e087589e5c15c2f42d57e0a95e1ccfb449a032b6f75a196323eaddcc37f24cb75a1060dffbec358a00b0fcecea97d04d09a50a578f14c92c71ae60ea816c4b8daa0e43a61c9ecb19c2b557b2637445416e91b1ac0d7c6bedf4cf8298e1d57dbbeb25a25e3321c2af5803a4d1a14e3921e4dd681ce948d9ac27b82a43f6f23119b0de4ba31108cf02ec93c53038a7163f2539588f0262eaee92e66ed030cf299fa03dd74a3f0571c66b5e76f95ba204de9d3b955354f53bc7509dad7f8e7169de94c534c0cbc3bca43e853482439b71d5c67a01bd0d036d65d76e5fe1854d0e923985cc151abed7e4ae614cb4f20aa988924f0b0bd7ec597629640001583632bd388419db9e27947ba6e93427fc3082de2dd24a2546cf3b7024f9eed1b42df7a56b588c888e88816a1dcd43df2b0793856693bcf97abd227ed703653ece68694f8d2c07495ec490c722d02331b960d59c9c0fc7ae2b55c4da7ed976918924dbef442795c2cc922f031c995bdc79173a291c0e13663f964288efb98d839289a615f8ca700101a22c7ba52934bec4f54fac46b11f955469bc14bed644e40184d12a353945c624a4c04f2f8ec36ba18b1024f4b01698c1e5f644983216ce2e3af9a85e2206a5c683c013c1e9c06ef95eff5ae4180e3c9c923eaeb40e7b57180702cefb6d368de9b81f2f6cea761bc7a4f55fa60eacb19229e5026e68e0892a7dff4183db1ba46ce0bcc19e0fb994accd11e87915984c1a508e2c3487fcf240e721b0cf35b1439a51757118b1c1481c0014e1c9d78d7303623d9ee86291ff9589d9a82c7e2d28b14955a7e526b096a8b96df6bd71e7dfdbe6a0968bd9d283bc7b347c39854eff2d18407e77e6d4365230ae1fe5e3e35ed5f49ee1e0b8465004dc2d0a3a559615b511ecb0c0d88487908ad1b29f9969cbe2935093c030dfebaca2454ee37322224fde714d23e4262e08f87f0e063a0bc9fb98fc9e2aae10812e2202da38311851983a9d9c901408159b6ddd435b87e4d29698496557762d9e16bef2b71260ea3d507a08db74187c1e8270dcda32bd8a543a47b1705df594dc0d1cff4e0e3146a0fc3b0778d912e4c8e34b56ce63e880558e2fe6263a4f5a80e963e5b0ddabd64c5bb73a6cdfac1bc260447d243aeada03a64d8dd5f094a2636bf62421f8c25d20b4b26d913508c9591420143211f55929e5b539882047029dd47d7fecd7809fc434b1c3fb8a4a940e9115d2081023f38bc76faf1dcfa48b46b1e20e6915c18ebf4328ca5f54858fc2f4816df280569903e81e6f68db31796c12d24223487aa2a178297a14e4ea390fe483bb8660f24e962dcf05687402390042d30a2c35818fa9de9316b3c79627400688daccb7d23d96e6fff42d7f63dc560675861cc35f5260f6236c922dad351e69ccc0756f8ad67b6e323a13faed0408200a0fbc0dc3780b22e7677cd3b232237aac64f862f33f64084ae221348546aa298c789a0d17c1caf968d3a262a08ee9bdd7653747dcdedddc3dfbd79be29c625f919f786811dd8ea280a060b0631b69941e50b0085f1dedd87ba954b097f5cf7304b2cf63160351c78fa4ad3396b4e58de021136df7c042e51440bf92556677cd630be115e14070e272fa0ae6a6f9fec24bca37cda4a40ddc37c9c204abfa6af193787b6718eaff64c0ebe362171dceaaddc6cdb3b88d92613cecb7c832ab7ce503aee2a0a64ded8c213b91ef35556b84f542792f9b5321002197392f180ee9a78cd21cd98333c305eb1f53372745721af6deb742a1aa622e7dc1964e377dbb9d319ce33e94d11a5d211964bfccaa969003e1d533537ea2e52d46f98bf8917f57247a4fea49c91f6711361f04bcbb320dcc97ccc81c84eccf956111de122cf40fed5e855841a1b4546af842fcf0a4d1645c3fb9505e887ccaa61389e4825dbb23ba77335633c895b41279e46279e02b18e96f566753825a7fe98c2c62fb9527edde51e43ae85263a1ba44096ee8135f2030c7efaf90720e8a7cf44a58953d3d6903d7390dd5dabd20ee242877b680de21fb206a75bf68834759d4a87d31dc2719b513ef806da4519b6271411bfe9afc5b8f7b1b6a0cb0ff02895ef908066ef2609a9730be939a87ce91cbb273e7c442f724593e24b06a9ed26d1ab4a009e67cb218ca5798f35c26ca5b71a2b065c7f52070d5f5f6ce7389f3654b49e888bc081df70334bf20e2c7b2276c07dfc2dd78e0fe6f260f2205c60f882f14180ff33400f5b9badac39d18f13ae12cb7654028ba0d88209d560d02dece6ca161f31249b7282b149b84d973de88b45044d236c23861fd6bc3884989f8a667dd7226a65b7419a1be57b9f50d40c73006ea32745945deaa4a7cd2cbbcddfe101ca46c72129a295db4c91f21895fd63dd4bb9cdaabecd500475633b252144fc64b93ccd6815bbfa47e73833511ac69fd38559a9a08fe6bf67e6197f2fffe35d51b0dca43059e8e54d206b7ff5fed050e71462428f233764f9cd883067ff7477984140b0acdcabc8ed97b6c001a6bb506571f5989e750145cd7182f8217b1e786469987e171b8dfb6007d39873a0c2088b278a1957d20d06073cf723b09c01008c52f118d5886749f0625dd612e5681f8ffe209a0124e6d0df07a9aca39029feb3de5840e3e61754e319a7628628ecc7312b19ab64cba12aa665c25348be574315432a751907eee057ccb1655b97718795713db1a76ae05bbeccb77395c9318e8fb539bc6790098a7190775ebfeec78ad4fca06954db4a18275fda419023f85bfd0a2fa705d335333ade35d661170fc0898a8f0759d3b2bd097a2080119db3d99493a626e3d0c5e23e797225824442b5935c28e2223020639764006eef088eb8f57a9d129859025f2e1d5cf16ecb8a6bc6141866192d813765ec3c215f2cad6c0de3cf190d94da301551af1847db2e0796aad32049ac830c25a10fe15fabe4fc74af39f9285fc19d6be8a76e55b190609581c8d070d0f708b8230bd269ea6c1f2d7e9681009511c0c9d4394a90b6548963b35399633bbb9b054a6b36c44f28cbb1860b1fcf65998b991c981b528b27147e7000001ef66a784360cf301a1bea9274bd7ca70baa9ecd245bf84fc825de6e579c629aee8f7ec0e867b30f742cdc2e45a9edc8c890b5075a8bb4e7c2df6c7e4a4159c989d941d59451bb67695a00dc60474134656936efaeaff697739af4a57f9272a9a6aeb2507b4b536be4643ec2189681bde910fe99dca4479c6681937a77e4f1432261a064efdde8819d950b5b00a4bcb1a4eb5b1a696ea43aca006ae917eb0ff5922430eb1f772f6b97ca30a578b4ec97585ba188025fa12ada180e563e3387a2e2d875e6db425555f31b79dccb1475a24fed96d52316aaf6a5cd932f85cb107c7f9824835859b396ce9302b104951c9c736befb0704ce8f5947894f4bd204065d4faa47f212dedf21149a602d261954d7cc52a896e81d7b578b381f9c0bd7694ccc7bbe1806537cd04354c002680976308d4f637af2d4d988beee962611a905c8319e3d360445f3741e7977eda96c0678465a7e08f367be9924f75bfbdafcf078b89221532a0bf145e6b5b9454498d4b35c2f993a346b39bfe603c57969a17083a49d724b09b6e2b92adf050c6e40aa8f90d38b5bfe7a3385764c05096cec3adb24e6e29fed5199b2966911bb70dfd6801ba3288ad9728f00ebc8aa431ee24fcde9f0457780e88a57a7872734befd65c282c5385c13cf1b7e20dea9f92ea843e015f2553c27a8697e0de286f011d7de2e5f185bba452553ee65bf820a04309bd75dcfd79eae5366060f09a9836a0723c4e9beaafd9f59ef4c2adcafaf8d0446cb7f84699a7efff1d32411d807e771e2e36b7156613f36246b20cc94ab67013d613b6b38cc7799445b435f1f8c0ad3e8d2a051575de7f44e10f00f320e664aeff8d9fc5f4ef2eac3b64a6db536b18d5e135d0dcd30746fd57fbbfd3c5c26fd1062e76e9594cb2dcc424cc8145025b79eff727ddfc86b24e3bce3161eb5a3471a1a60a12333c7c49496ea072ebee09659f78f7ad3dcc29bb7dabd0b1a4233f623519f32b6604c9feb7391181b2f6d304024dab983b833dccafd795e83f1817a87d00618d12008456ff77f440214ba82fc88143e5ff403c07a82a3fb39ddc2544d7d45ed2ffc267034b3a60291be53480a266152a8a30c16789d4f90217a19f7e5b4fcbbed992bc6b064bf494888e01b2b91d04d362478b4f6a653f4b7bba916655cbbebc8bac47745b9a16e34e4c709b8b05ecc39eadb585c847a02bcc593d964e63c423e414ead3113cada04b6fd6db9fec86f951f3a7b51193ffd63c8f81dc27106535c05c26c0efa52303114f43ac43a16f757a8a9e67f4d85f1f5cddbc190dc4f58f6f6333e0fc847ae275addf512e75c638f9bbef4461a0be4eece9566d315e9aca6265c8ae288fdfcf474c3e4fe3023cf1d4fb04e1222aabb1920cc118b7f489da1a1994649d644482b00764d04064ba7d23f054957b666d8012eae22e413485bc2a8d3ee75fe0a8bb80582f50ddd3f5d6751d44c63f76344f6d220aa133df4de18d86b5df2fe00c6a2fdd0233b1849b5a644dd3fd3b3d137f1a4597402c6faa9fac072a9f9c55106cfa8f4a36d3be334585445be77da1e8c072b20d8da1d464ec5053cd0b562f183cc8d8f1a1363aac40acef5dac70985f2842507870bf9dda91dced1a5c2c9a74eb0be67ae46e4ef49945208933da97b2a177ba5525859524f4c394295cf25fde937e1b4c02441d128a4345dfc4eab765004af4a97e88343dfd3e93521815972bacb25ca5b7226feaa98986cb2a1d4fa6634f04232adfc92cebb9f66b7bd5373c4a37c323f3301fd88708c52a01201ea30e6194ff2109128b318dae1af48bf420f4afa8e5b18236acd8717fbb265a7a72bfe1c631906c4b794e707b5ba2f94d243709a599da6e28724bf5a080b193315f9bb3f268f3f0a2980aadeceb715d091d83b5f048556b8c65adebea09ce19c6a526c00f44f18cd92a7ddb288bde3c681a513c099031b9af5da8f94388e36d31097f1dd3c128b3a88d941841c5afeb9cd295752766d3e85353ca02e3fada6f6c785d63f715bbc51cf480b4861a3feb90280b8b1276aab953290e8c30654f6fd3d11a6dcd1891dc51e6a51e5859a49c7e1fff56aa0fe80d9eb4d97cfca6490ba583f388702a3a41b23787d1b4d6aa81546e6eff9cb013a3fcbcb115e3fe7e068d8b21c420dd364283e4f1a36bc68e9139476546dfda5982cf3aa138181aaeb04f7d2b2c49276e90ed4d438214bd8d471df05f61c706ceae72ce548023919a98f7fa3169d0d467f982154a0f714c1f7eb547a58e98b57f43c4c5f33f6ca381024499232988d1b58b565ab2435e6cb9c35f25b1209669968f2f587d4a73aad5eb9144dcce87c224f6e78cc906b9525cb50775048c6a36263452d3af46260b8f72a15036431a1e6830fec2f8393935609f21f119798baa429fc4a201fd789dbc597fdc1691e8114849d2451cf3772b9178fe2712b59c9745ea4b8cb103924d3b7db400e3d9ca1bfb912daa478008c21c2345ef9aface9f66000388bfadf97ac2bdd523d7da6f48f88e828bd30a5bf9e5751a096e5de821a194d30a48c430c34da648eaa5bcd34ea197d40854be0e927de87d5e3a93a8519f5b56e7a978271d207eca08f90e0197bff84adb1db5c48c1c899540f2066795462ab2d86a01870eebea1f67cf280b03ba5ba14b0ef7cc42ee7083aa1f373386abc9160c50262d10f93556daca5a180bd1213564d6bac95973d0c9519c0d8559b38a2b334645a554f2d563069e7b72e3d3bb89a89ddda15b31c30c07c373e39b5e674863b72ee4e1fdb42265febc6c16a9df5f2f8819fbf7666f967007effc78408f46fe50f619187349984d3f59c7ed3b46f27bce1d9711ce83e43a6f657d05a47f3f5f4ca39da20a6a947ebeae524270ef6fa44fa952dce8f527eb6bc8818319e73b05a063723d673108e2820b30285d8f83eb689e2eccf6ebcf2ddd63e313bcfb3e9497f814993a251420e79efef3004cd65f44cd16ee16c4777dd44abd9eb8301342b3d53ff223973471d2ec5d2720810eb5e1da51ff9d583e1deac8d97aa84da47e315f31942480d10b8e3f7aecbd0b160a11084df7b12259a0d99ddb6e606f1439551202b33c5bec2016815ed70de6b3009901855c7a3e90c515a8662bdf4827a25ee665ca178c151ab0ef90f464e42b9cfc00fe404e2629087cdb0b57210d77238fbba36f7d53bf5d302580c34805f77a32ffaaa06df248fcd36edf08567e5c99286b0ada10138bb8db9ec1e7917e5303eac1d3b7165aac99d97095718f43d1e1e21f961c8604360639657878cb8d2a988cc6fe14b4838471e93d9b5f4000d6bd16096b047656f0a3c4a6fca24b4ecfc37a1e300fa3b13b437eb3db4112ae99913a82eb91ef2dcebc719aaefc0e0301a1d18c76b31ac25d20d8837c504df01834f6a7a03d248e4bef6deb4e409a82fdbf2cc780073f4c748503d9a881cec4005eac9951d46c53b792d3c6c732168496899510370765aaa9ab611210a35bd7bfe0938d4a685ed62e1ac8a09346973a91a5a8c939eb7243388edb0db00673a351b1733beaef82776b4b567812666fe0114ee0617e2340b0d4f0d4358080c7a09a961f25cfdcc539ef61621ad90036cc9e15c009aeb6b252452f3e5d3f5d3094ef5c23e793a22fe130e3898f7632c0278ad9cf005ac99510a95d48a03afdc33be9ff03cc32c40af01b2832ea1f51d181c14b186f5d155afefd093e31f94be008be92bc005260751aa4d83009b20357749053ec65d5aaf98f595c1f185807ea0ef0ae10572e683284cf7f4dbd88b4def0249eb4f946a578153bd4866ff0ffd085d7d6768c37c65732b9355e67356034f17a9079541f243c462b031951cae285944be967cb6148781a62b03fa70a7d4dbc7a1a755c2b4d1e90b0d47d97c14c3272af5edfa3a6fa87f3c0a80879883c2a4cdc96c406b3e461c7281a6c26a1ea040428e70d2e74caa11eebbac4639f8decc0edcb82ce7b68c590a9b4dda57744bde3d7cdc9e3c274ec5b73db9642d10e3f47cccb4614017154daaa39991191e2f27352180cb10faf3cceab4dcfb10e5234038d9f61c4e06106ec690af1cec6921170f0c623e4e04337a7be0ef7a0ee1230da2734211c5da6a171237cee84686c0ce14fc6553531cacd0e40bf13ab6a12841f6833d39481fde961702522b84588f44be52b874fe68e5fbb461d716d7a9b49a923f02208ab0f55920afcede5c53f3ee39e083c9e01b997d9a6e0f04021e8678dc8f6a3648f8783e7734c6cdc9b8d7dd10a4e79435b3fe402733bdbd137db27667a7266538da8747be52d6c9909721f9b9a13bbeae1c7b014423a14f18913403b0b1bbd2ec02f1485641a9286ebafd37018471432318e0871b29d78683042d07c6bac0a083c06014dee087606b92c2e9219759d7bf85673c7a398e72682dcf681f29cb410db429081be670285e91eb4ac28c70d5a9504eb9a502f6382b9f6f77c250808b97d1f5071b1015d55002acf2f9f3a09681050c58f9b70deefb7addfc0d6b02057f60426e21f6236cedf3d9f54eba4a743f5e29d1139ea8ab6681c02bc728848c03e290bbca49dd959b82ad7defebb7c9d789696dc215215cd533684f108c8d24b2016cf0f39e0636645258475393cad28f784371b83c5c3f7772a4a9f7dab0d241694cafde3a086230b668b27391220a6af86bd93c4264d9a1b676c320626ab14187f51caf9a374a9961ad5064400834365d01f6f43405128d12353200506d6d3c3bcbc184c7defe8346c67f45a8fb83b1b679cec59a613f762c992583b577be8082fca01b3b230b4546301e02e3b389326ca9eac9abcec968451540b68171a2ec2b6751f8218e2063df99b88ef45c76447dc1c6a6bdc886ee0fb7322578d10acb78ed89f2a9a06890b80ad54e0b48a6dc8ac41c04c35019f06a57bda24a7866e461ccf5055832ca556880bd87a418065eabbd111f1f6006dce20aa55efcda4387ba4ab633d31679eed833ea693f82c6c6fb7a9b826fea1b3d3d1655b442cc4fc08eed39567688d5a10178a766d115b21a1f2e59dd32e0f7c6a0d7c4f06de1659aa683b09962e31a863dc582b2946a1de2fbf5c5aa4d01df379e4a7b78ada380cdd52ada2fd737e043546c941d1407e156953a217cf7f7711aee380bf4a8c6bdd176efec9010a614074131beec2b52718696f623212a6c01d18ed0e23c6b8bbd3fd63e531999baf804e971e1de1e4dbb439446c59f9da1b097a2a95ee30bd0b2c7bde9a7d03c46d8e6ab01d7a59e5267704fe8d4668b5c82fac7192d91a60d547b123dc8a8255ab43672412f15b0f1db95f0479fc2297017f4336fcbd9faf54bc5495a22de5b0d359c6c33e872ce0fac226d0a1fd042d0c6448282955b92cbefe9844d7cca12b4a19167461109b20552387d25d3a7ade56ebb79441d50497b923651be1551c5eac20100e88492a54bbcf211861b0e41204c5336671b5123d2317e45489b45abc79fe55e0b5b9972d21f0199994d979e8111caf33ae07d13a15853f88aee26a0460522a3f9615954e35aa7a3d9fd064951b960a9a15914e2965286d56639043f5d4dd29a364dd73414d0643a9550dbdc940e351ede8c5bb304db3158c28dec2445cb016896227985d7588fb225afad8707771f0d78967d2eb09f6efe21c8c648cc8867914ce68e5590d396c74bfdfd20fe8aa53cffa8b14b3091d466139a97c82b6caefc0ff078818a18f8ec208c20c84fb214b2fae3a80d2ca189b181d6f2a9850b5d79920069086930a0298df90a71ab052d52fb2507164f3c1b7518c71fbaf4ed5e12d4dfdbee5b0199f4b7d0cf1e237352891439c9aab3852ddd268f52ce00464f03b6443aa40e4fef5838f2f5e90f3dc05d52d8a9e2d7d279615e5eb321260fd19105023763005ca06728d2bbb5620abdd05a5cf0cc8c0c24d8ff86d5ad0223b3b243bb50ca3fab25bcdc39760c04d8f2a0bf814f19c68119b5c8f0fdaa0bf7aa75274cfc30b1672c1810a7c42a0f9ecb7c8db58786ee6a34b91f792ae2f7a19085890aa653f182c844819aeb445b387341bfeb21129731307fafed2e5177b3664611e9b805e242207a2090498f8d3b41f7b4f03083e21dcb35713fc0759fa9ff6f67319c2f6377a7a95895e2fa699737cc390bf02d15be435e2ef4eb4678aec76e69264b880090eb241c04488c94ee9beaaa43e3f6c40d6a8d637a048477170818d598394c88540d07c155a3b1baed30fd878edf0828862e5dfc9147aebb178beaedc8e3919e2b33e5493cfb79f8c39267c8a8ed757a6bbdf93bd0d2ba6e643d076fe1fbb5746bdbe755cb2a665cdfad65fc150f7058cb38e7190095fa859f9d51abddc858cd2c86330b585d53f80d717af937454e00f735ecadfc0ceff89c2a0d9e196ce794d25e8d72a55f18bdde7fc8ce1943b08721bf63789929805d48a3ab52276c320006f6ac1e0db86b8703ba61986ee8950a2ed1be38a44defb2db55393de7aa1486a1e02cfacec5d1465a63007fffc92c5af01fab2c09451eee72fdea689806842475a2a518a982df3aa8fe691cab893add33330d7e8a17e1f3223a4b0c414303b7415d90fcad51693565eb4b3fe26812a25befeef5f5b73fbdfe182f12bd73c77061949c82517d30cc1e22a49dc6198c4211af38838ea5b622e5515568b297eb1eeb2639e09582b1a3704b2d636335d85e916e08a53b1f8f175155a098cc6330e6307c7466f8e790833be31ad2bda23d935b7fa5f68d74062a8f1a18c9fb7b0f80392ad1f78cb5b40f61a7220fb79caf4b80eee42b0f6c17df375e0b4fc9a7100af0c9c976cd33a37189c9ae39d853e4e9603537be8c69a6eeff1522f4238208c0beb20ff3d474a47196b0433ff0bbe60ec47e1157bda2b753de0dd8e0784761f146a80949dd0bbf526b87879ad9320635b3bd9df280018b3dc1c65f7c848dde78b2eb20e7981a9ae7ff7a91f2a8f5ba347680408afa6b659749bb26c9c48f72665d097445d2e7e4dff201c0ff010f48fdc207837713eea25af7f158f73eb610e53462024c2c9fbe75ccea90d13bc7f1e448c4b36cfdea86c11641f7e1922d00f2f9e1d4864c91d40607c97ac8e2d8031ebc7f5ba288f5e1337dfcfeaf0d3d5a12019c0fe4c76371811ca54069801aba595d32d7538ae73707f3612655ff26c88d4cb4d8831d5afc0833563d4ac2039d2ea97363434801717a8769013c11ac0ed6ae54b034d4eb059503c60106a7e21fc60c36b599fc90ef24603d84dafd0c4176e38c3ce392f7fc0060960cda47953c2990e57ae671ee8378368294ee13b3d59439897668c106385d9decae81d59278fad64d85ca62f08228ceecdb38bdb70f3f1197f809e9a9a30a6234447e6526f07bb7d535471cc348e7012623b308df1ac4816a19b529cc81bbba2c438872b0da2afca4d3496a304c56ad403a6070ffbad1d02dca4c3b05d90f54f1d82f20c09a1e05c7bb094bbe282e06ff6fb91bd5801229eaba3c1d71676d787b026f485785e9c17993d7ee3a27d53736e86c814a754cd144ffe2283c1359bdbb09422b45c6d29f592cb46da5285927d6546b4554da13cc65ee1fa22740161bdfedea00c7466b2643f0c3005eaa968856b49ad193fdd639bfa7d1e3674c91c92b9aac2bb01bb26b0a6225e6da9ee4f4ba6dd4d75d593ce761abf0998d6bd6cd2d9d54eaced69b7423d572862cb204970875912faaf2350f53e0a0d36e159c77a6f91603a4902b2519dd0d93b259571604dbf3d55765335dc752fc94b23599b08b050c2ae0b5448f0f97dba61425e89fe7d4f692c3e57d0838fca8777e9b514e9af1ea5b4e804608a616539000d469c7d6b33ae34f0f729c360f204f912944639c82ad312c4f31eedbfee0e00b7834297eed56e23388fe37b328d8ae927b4a8efe216611d8d97d8b835619f899c9581c2e427ec6915b2d2a4ebcfeeb0bfe9f9801f4c0275d10144548d8bd19c13e712d93349e3141c881a715d76259441ce4a2195ef70f99707f86991fe35b0a36561d9b11ca3fc497f68c613581ac83b8cdcc3dd9d1bc6eeffe13dddf0f6b95c135c8fbae10b46ba6fd8b91b427cbf79dce92b382b5142dc4469d28a65ec97e83913bea81ead74abf065ad254eb6d3bebeef60a89f5a25cf5d00bbd09ad583fdd17e7a6f186f8179df9d91919469b5c82e9045c8dcc59aca244253ace5f99963aa04ec1b91588041aa28566a89a220a3da54eeaeb5f889d87c1d75796bd00f5437b901c80da7cceefa3254802e8e2d62050984cf78fad896c8178049d84be4b4e1d6a1960795bec6df3e9be32b2c7ae6d80041627b398600d9eb6b7d3a25f718d40b5f18f5384d27782b4de90bc418b5c232b1614e4520b9fa6b47f5700806530b95a170921e1a53c4fdcba30f9aad59ac8b28710208d5860a529e1a1ae1208dac52ccc0dacdba22230d71b63b32c857fb1ebfe939d5fad572ae0828836a8d9fbfee7b880cd3a4278e5a3463b394849e731c6ddc8387ab43e443f6beac6971730dc49f700632d089cdc7e00deb0593caf0ec76d05df7b192f73f23184d42b4f7ecff851adc419d75c077eeac863014de8a58f9fb325ca546421fbad6c62aeebb8959f51e7a1ac9d29b9910ada085c85d5f1837e1b57ae24b4b93f5df9997cc73e04aa36d6a07e0d550ae270cdb3be37da01e4af63c3931343b912cb7debbabadb7b522d5541c335ef0a5d0c712d1e5d38de53f40df5585322467df7845f810c68c76dda9da9fb5a6b7d92fd5b00e9019dd88f5579c552ff95aa491df6dd2fc09cad9ee085f17ec652a4a385ee91732c0557620bde0b8b1a71d7e6103a8e90330d7f82aa9872d9202a630e76ae9c1737d1b658b124a124bb19e866afe9ccc25ee99b0e00a8214fc37ddd4600b27354bb58b332c9d6d78e690052955f4abb8cd7492714c59e1f1a848db44fc601834a812196c18189c36c1fa60299c464e1b246264be61436c4dbe08be8f782c1d1383b32b84e6fdcb922d348c2f6273c4c8ee0524989d1646388fabdbdc56fda910ed365a5cc8694d6bb314956ae4b39dbf999381cd7623de15b9becbede367f99c1db1d373769d30db38909b162e4339603e18e8fbb08d1b416463454ba419d9045a350749ce859abc5cfa94364b688a6939f88b325fb96301319cdaf405354b9cd62f8dd2c2fb9b90189a60bf9ab11f3108cdb9bf0fb5d7537f67aeead166028662ca07aac57fb86704e0d83217d64bd7fad12acf24dfecd197e25ac6bf9cbf0cc65c9bf148ec0cf7fbfd71dd6ab89358a7510a4f014879568fbc7aa6c6391c19f026879578348a59c7d35786fccac19f0bcc5a7d790080459e0b63d50606a551bce726cbcae2c01949aa3d35d47a5aa461d5301fd0b5069c03439c8304e5e76258283ffaa5b01f32505105f740db868ff561dbb0a45b2e2b72362250481d05083b10862da728fb926bb0b8ddc35de8cf87d4169562febd1a524e9b533ad75b41e59e1c8a7c1cba1c9fdcd16eada51cd5d86769b9073a9c442e7acff625a7dd10ade5c05fd17f1079c86334459d97ef4ba29007a5b4a64d804ce84b38bdcd8e02c61d1f793d50c60c7578cdcef582ae4dcca74625ab6eba6a2828937c2f9e6ea9a56ada503ae4d900b0d4ff84f7b01c51972891285f8676c659227ae3861b0efac63ca07668524c3264ca2d3ae502a0a342f60856bdc840d4a0d81f4018378b36c0cf7805f6b52405f25a29b75417622f25eff1ea2337f286acbae0bfe368d0c4580b941eb97328ad4623d743de60613f6cc48f248905479e7467abc63b1d0d232c3cbdd3cc0b05a7d93b49cb47e1bc20b05e4be2c807df9dcc11ce60e8f85c6d8b2cad1e9fd424b5e65078c778c41de4972bb1136dd8acd57fcc7f5f62d57585eb39d43d807705ddf6e0b25bbbf0ad35e417c713aaa90d1e75a3e9e411b583137f019b9453bf00d948ec7e9b88bf6e193e0dd3f8286bec8f32ef8ac2c8588858bab1c984295b7a44a0f43be0a3bdd44cca25f2c6e344304047e94ca94bef7d4567f71e1cf7660284223276374c01ac173b543217e095f77ed05cacff9b88752b540c300f6ab1612f8c8009e470f8e2b7f3853244fe9342640b5705949f58279d57d5a41e2b73ed2942981cbd13e6aaedcb0cb56dfdac427700bfabe36698f757af72b8ad9ba5a25eed143c1bfddaf0f0a1ad613d3857212e6a152a0bd13934a7f9fd567a5230516468f2deb78fac82a90909ce81073f2ec935291c3f09b3279609283f490fc758819e09d92de5f3ebce545a35d564e576f67f45158da1d4646f8701bca2063db25cb02a087817da464a2a48102e3877e02aeea535fd4da1651a1dd565618dbda9c8de4c4fd77c41818a9050688b2578ccfd1b4e23c17de61ac40ed2d61167afcbae548a807fb67c010eb26b9fbb4ce3791f228e70e06b48e8bb44086dc5f4072352bdcd1456582f206bb3c078d09787ab047ef547eb688f07e30c7dc30870156bbe07a2cb6f716758274632606f0089d4ecf3d73658cb392a0528a80f19779e538d600323ecab97a0e33e9a25d1817902306e131759c315b6857453287c0c46930b4e80f0a7fe4ee082359b21a222a1bc1d4566762e9dec8f5dae24eec8ce4ae9b51d492a20e4f923aeec1b74660d4b5afa370eeadd288e63943c6c25806f60a77137546ffba9091b0ccaf2093b6ea353abbb5cd03c56ca06b08a4743232cebe01817d8f75aba377972b0c7386814b35c940b5b1243e75dbe67d8f434ec204223092f4630adc1ebce4f276c2604c0b9785106138d63df3fe69681d790d1a054d1c18d4ecc2583cf7fcb087d5311404e5a8a51613f932174ff93e1bce5580b80abc9a025281091d46340a90c63a0ae926344f531487a6dcd78a6df4dd7fb8b419a6c9d2ca3031aa8c28b5d30ccba9a0ec5922617dcc1ebb501475a21a6bc70491b98de1303dedf668a235f05dc25451a34654439ef38be72d3c3d1e6cea9a2f3a7aa3040a48afb45665745254483e64dd3bf0f21a021ef8af767beef6fd2dade7b6fb9b794324919670867084508159ce241391f8a15cce82573d99e4721215b0e69f32116492f872d7b82b1e7130e23ec1d8a6c5761cfa71cc4d0c721fdf555917376396f38e47c05a91b08220404bba6b0ebcd3b8edbf88d0d39a72b67a753994e4d3913131393eaad16325f2b538dd5e9baf47495313131c9549ccd76739bdf820809827f88030df156557df97d9021df97f3f6e921425c3349fa97ced70f5299aa4e22e71f80ccd720383f3ecda87134b665dbbfdb6c1accd7f82017e69036af43dedc5811799099f7b75a8c968b5aa53a3950c5189732ba36d197ffcca9413a362f833ada88be74727490ea2269527551261a5d8e32bac4b54a91677454ce5fd6589783973dc3ecf944a508ce133991d3245a8489ced3292f60a638207b782bcc2115e044b3ba847e7e5aadff71ccb97e2ee70d290781726f8abb6a1512fa11125acaf4b9a319cf01e3e75a2232d7653ae21163cc43a61b7f0d32027feaefbcc7737e9f66e1b0ff8d7bead2e777d8b1483211bfc683c4af9f45b2c75d78f44e63f14642f0e78526267470ba56561a0511b267ed7ee62df339dd6920d3b6e72cf2965bcd04ffc45fe441d8b1952157ad80f1ea3ce6398e35dc98c70935720005f53eb814972e5251f2a42f4f33c98125792b47d3189d51fd64cf9f547bcbee40d2d122cd9921cb38214f5697e9aa22fe39ef794e0aeef0f01dcd2ae2f99de7016f686e6e6e685c2c0ae1b5f3532ce2f91d6dc4d22c9adf791e2328453ccf7a239da74639cfa3270d196408c9d344a6bb88e76368232834cf13e38f5e34ba88f53bbfa3ffe8455af03b3f027ded3c110bf45797b3a6ce4011eb6368a30de60b5797ebdc6258f05acfea22f5cfbfe6d14444a014fdf3b09ec86b9e8fa17d54d78e26b2f33c12d8d19345b2a69e3734f3e666ded0cc247b3e157948223188fc5bc062c578124a510ccda32d2089b0b4479f8764d17c8c9faf1d9245a3591aec3ceb79f07c0ccdd2af2745f67cfa379466521a1a4a3b3a6f665ee88d66d2190d79c81d92488c67e9fcce4b20e7799ec863901a5717cdb348223abfa369e88d2e799e88cec77831e7795e7422a6b5e753932e36bdd5a0185fb67462b030d68941d2a51f37dd79fa19c88393a44b3436714c2239cffa1d92f544747ee7a994148ed40d95da8941ea3c0f99f3536469a0f33bcf43e777348f9c67fda4d134a665c89c8369ce5325a7b11c927271973f5905cce2d0db796fb5bc2585e3230da494c65458d08fd14af3294c1b3594066b01be699ea563ee3987687e262fd190b5c8f4a15f436dd414260dfa744697350434d3a7359cc77ffab331975c75d618095c69a8a555afb5d65a6bad4cc250e7ea129fc5037cf1411edf8f19c4efb94ed0ddedc67a02196f501a9235dc55b1cba02293d60aa91a214170e74136220265e6dd841944542f3083beba6acab905de4e65a02eac204e2f5e359ab56386e6c397919973cefcf44b9a65e67c99cf794e994c43ffc7ff0f22c4c8c78cd498a5b254760e09aa770c31761e78cf3d11d5ab5eb45b728e4934876ab63f03e690b7c73999f0a5cd73439e7bbe549ab5237c9f2a529470c4fc11e44021777b4fc1a713739cc8fd383a753a6e6f15f520f248407ec99c95166a6cde62e17f244b0310d4b3ba42fdbdf72c1edf7bffe9210ca82ec7f97ecfe25ef5472f4fb32e0f56f7e37bd5177dafc29d2e12fffb4f1b893a08a82f77b923a77dc31d44e5ac95dcd37d1fecc8f287d57d24abd33c42cdc21f3eab7bf18f5e1e8b8766618d9fd5e9a2f0456d04a57ba30ed447af8f7ef0b3af2e77f4e81de09da20ed6dcf66a16fd72d6e8d482fecfa169abd17e708cb9734f054a5d8f53e720f27c23a55b7da54c1f865cce5a8f1e07b0f9b2c70e4bb78dced555675ceefb9cc39f9a6c2197bf417f7fa10c727e19199907694d4d4d4dcd1fed1a7286863cdaf4ee194d004a23d778aba7ccf8f14f31973d1867f0bd05eaef896a06d2336d8fbbfcfd26230579bed338e7654ed81caa2d6ff5a81c9e400298600c24597cd5ff84b5a6227790020075eba706f9f8eabcfdfcfcd4e3fc580522c6d179297871dc79a794cd49085b4af0e18a06e8a65fedfb73d0cd2f69be317dfc9843d5fd68535dd20c4c1acd8714752a66d55bf365a871a0f11de87bef6b70e06e0f480edc8d835f4562ee31891f879c791539f3204896cf7a500764913a640e39a74b66becdd3901eccb77970d2d8d0d0d0dc7d43f33536244d8d98f34a8be2ab4abcc3f99ffe911cc99ed68c1a42b46cab8c29b91cc739c739de382e019c3f7b621ea3b573a649ce9ded4fbb4c707eaffb3c4f5928b6dd976ad26cc81cf222ef3feff1732c0dc0ff5e02a09680a785b86f85dce99f96c692e7972d1c5626cb0586a6249854d75a16955cfefc30115fd4f993a2b69ad0aa7c9a55b40d0ed9355c7df82a92a5c1eac3950e593c444a021977ad1f8c1bd4e58e0d64dc5de7e9ec2debdb3748c351fcbeea12c8b824cf5de6397acbdb75531aa30375da03efe9db0781fcd8ded34969800f9640c6fd7df71fdda08f93d2a8ef7df7def4967f4c545fe341aaef6ab8cbe61a845ff6c7834106152239bd14402e5d6c5ba06a332c539c644f9230c59468d294182f37292e5136da6ccf396d92284e2f66d25470d3443c6923793e408a0c14e61bc9656b0aa1e5219583214186ccdb10212121a1214184dc820819626b90213ecac965bb73e9600a5187c900ce9eee098b11d410eae004f9870f4c6f4a18e7dc026343cd98261f64c4f063e70e358c7bbed39eb40c310224ea8fa48f15b9be9d35deca795e1921c8f4e73741f10ceafe665fe5eaa2df91a3eb31d32c3cc9db3be77c3badb5f6ae6ec77d3727be42ba7d6fc79153dc2f338932c05bb5ce5ae7acf34e3be79c13055b6bcdd95b638db7eaf8f3738d07e15725725d9b75d639c588961662a081d6724d87f1e8f3bb3927ee640c81a95bd1b39221b79eb4e2c7176cc894882cc3c8c4fe018de145a64bb116688c2679e09772d9836589d326632b4c1a2cd86c537559186c069b59d8ccc26616366b22d3a99bb764907053ed404377bd12b99cb01ed56dfb615b9876a5f6a9f745f7056e194d7269c19e2419e791abd5a095e6a654535c4d9ce26ae11457fba6b8da9411d989da2e51b9be6572a0259b85b1a5551f32dd5cbc55bb484d9fca79315dd56a6e0a964beb54dbdcd4ae5c6d3b3950978a85ab511bb689d2a8514c1b1d501af5837e385057a5e610f7775251a718b7e428efc2843113a5b3aa5845f705fedc4cc06d39240e26666afad8bc55bf036ae37a3169d4af5bdc755740c5ae17cce865d7e7b22b4db25daaab3ebdddacd3cd3addacd3cd3add5ce64cd68969774c9cf8476fad2e52a95e0cc53ff2de282c0a8f9ea8c2c96892cb7a67170bd4add52099b7b65ddf3a554bab41aa9fd9da76a95b0dfabeb6987e698ce524298c731ec77f0ec95bb5e438981279979cffd1e66033651e3919b5b134673427cb7a756ddcb57132991179cee6d75dd65b9d554e565df53137e33e094e46c1e92d3b9bb1e8ab9e65ad18beea45ad7a2222c521cbfb30b47615eaf9b29a45f59c3ef44b9f85abaff9c51aab41add68f63ceadd6ff38ca623333dae803afef3fbae5ce2e964b232f945f6bcba49591d689b4b4a66d6dbd55579daa4cada58f8d49bb3e0ce39cc7f16b9d7948642a65f3960c156c5cadf39514f58605164eb3d96c369b952e639a61ecb719bdcde8cd6b22d74a4295aa37f0e7f392e99e52f5bb881b8cb01a3366cc181ef665b5d1075eb53e91db29de72a95d7acd69d5e518e73c8e4bbee42191ad936df296bf0c156c939d551a387b26b561bf14aa957e65a25f659b665b6bd3a60f54ad2e511a54575d0a3975ea692157cfa272d6d65a39ae723adb8c33ce18678ec6808ceb679cbdf5b80616ca00d32c0d6f418328eae1ada794524a73b05f605c6606549c39cb71d776ff5383ba9f5151c85f98c277b2eb2182a00782df78f366f8148681873bfa8f45b6df3d7d20de83e39ceb541ffe286c485211a50e7e08f93c1c9233dcf579b88b6cfd430e7c70e01fd698607ad8aebbbb5f5b0f9bb5e500ce1e5b6f489caf64f717e4549d4af5e148a9be8eeb4221e28d215ef27df1c3f70fe242fcc12eab9e95f3ea698c6e0cb627f1f126c47f08927f5ea998e03ce87ddd6c776fb5c01dfcea5578ab5ea5cb957ebd77e28dcee66b74996b3eab9e136fa85636dd156f745e87f3385ff3e5940b7a5651ceeb60b104ccec959375f6911cc9eff90d828f7d71c89f19def2bf194c1f7f2297e55b3b20eef27a83780b4847d627aa371e791dd7dae5c774f95b5bf30c360fda58a82120f84bb9fb297aff1b74a321b025d9932948494aa0f55fcadc7758b2f7436ebee36ede717e14ed88f3f6e7fc71fb569348320e79430e7120f0e983460e3491863890ccd33772a0fcf4296cf5d4ea00bfe671805fa3774de99f679ee68866c65b2b585efd78ab355d572586de83159c02998c86d050197d42a750249a4495c0ea59b0bdcf14e73c8e38eff842c89e0d91db904614667443da10551b4cf66c8e9c1a3284088aef518daf9cc0492bb520644a0477bb373724ce0be15485c1781b57c8be3343e2663dd7e49d913aab1625a34dac69a9537aef0d0a83cb65655df5bfbdb07bcaaa7f854f5f2b4bf3d5caeb05b32390ea8bfeca3aad766ddf03b5c5813820de739ef5dccee37c0f9d1f9fc82f4b039ccf791ee3b334119d1f3f47138152a4f3a376223b8fa367d5e58ebd7a276985cda119384f593c5fe218ef896f693528869ea00ec9b279d61fbd7648968d6669b0f3395fb4f3396f63a38b749ea58d705ee78dc6dff9a3978d4c4d6263e3394c55a66b4f4a28bd0c8dcd8ddd25072a2f9102aed24d427fd6d97c5961369a01f4d26a9095c9b0954d1ffa443e3ece4f7107d2f8384f6b8ca624b0d826270762fdd4a035879f0c84223082e390d7e62f17072aadcc81eca67fa11c683e1d9164d3bf3507cae9726f5436fd3b75c5781012387b3ac2894d9f43f2200b6637ca81e653d4d4a67f6d35887bd1a7b1b19c549303952ec6c9814abb64e640e58dd1689c9403b176143d7120aeb669cd61534e0b962807e28ad8f471988ca903eaf6bf4b3568d4383fab0b07e779d02f548680e16818cee3e80fb09e48901fb50f0dc57ece8f5f4e2928464a694eb39ae7894c7ffc9c37c27919231aab2efaf9f3e3905df2ac2e1afd95ca1c9279fa76c981689efe172b6f2c77311b2edea22fc5066fac065d8cc7f1ffc67c767963565f30a60fd78593f2168da64f38313b2b12c86772c76d2ca7ef359ce791f3e3f3c0799636ba800c45886961d444a014e16896e76896eb1d398ff3b24f72c891ac49d5451f87f4c0c8b6c9ca2a8c3e7943f6a79756832617382449a18b2d7ca952993e14ca8322df9a7e21e72faf8d78634647fed5e3c8bffaa36d146e9ab7214bba6d7e454e9fa2dbd4c8bc2569c8b267cb4cd7add9ba4f9f1317d0da20186dd583dd8ab55a55195285d5a4ca549ba6d42ab5484db2b2dea9de82adda5161d5b54556ddfc7d7b4356a4ebb72679ab223d0e79539d36fd90bc01c91268979706e4d1c7974683705d6115e6f75eec15565dd32515c67947362e002720931927f1cf880f6230dea6cc271bc46cfbf84bbaa452894856eae9862a9b520c367dea54836848e0cd061836c5917a9a12934da1af12718c2fd972308740a03bffe775b853ad38ac9736fe6d2d7e2297799aa791c9e48aecd103f45820f87d0ffa3528cda6bfc34707f48178902a03fa440da00f7e5ec5f11b8421db3e91ab2cd8f38efe434bb69a49d2bf66de897c86ac71ab500d2b43de0c6a58fde32d2a02ede77afda9d57fc22f403c5b38f006c1687b886badb5d65a23300218a72a3e052cc24c728f1d38640ce8272b70a9a21e357aece851eb9173ce3d7614d57614f5b876470f216fd11d0ed48383a639a3d4a9d42541869111b8121a62d304304c774ab3830e5ee825a4160cc8c129a54e67b0369b0d2ac80bb974a6ed4c381f26a4b0810a909cb0c194131352174b4b92a49e9cb04198272b389cb04117b35188bbe7930964bbcc2f83821fff01402dcfecf954430e4872def3a9862700ec90ed9e4f3530d16252c30930b89271f67caa610a0cb8c8377b3ed5f005064932b7e753939809983e882376d9da4118f1f3d4e4896df77c0a22cb2effc72211c2fe8d6e416f357e684de6533f326a432693c96432994c269b426cfb348a6ec102de2aeab8dc17725c48e61a2e871c076ad61742d1eebd31bac51cba975cedcff3eea55b68b227f7094b8a9555d9cf8fec3ec634aaa3356a1b41d4dfafb26bf316a63525b9fcd9f6e7caa2b2f7fe6595714f3c8846d9264ed31ad6b449466bd40ba5516b5fac179096a907f8c1efc41cf8efddab2c32f7fed88696cb9ffd8910d0811ffc4f977401f8131d007e1962f2febda4ebf89e7b1c9fd6e57d0ed08d3f141fff10f086d8032f28d6f01f070aeb1377d9fd5383ec4bacb24f844c65a0906f8da38597546dcfd21a9555998ccb94d04daac3b4265bf27e6e8cb36c2cab7d8c7f7e644b41e66d6aea66696dde682d28e7f5bd87dfc3182fc59498c61d5a1bdf5bf6b7a53177d55fc232d90f77d5bfe1f903ab3f4e5e1559a65ee89615d4cae742d16899e63d7eec698ae57b0c7edf7bdf73ef69968efd7d9a45e49f8f92ec3dfe3cefaff7f83d7ddfbb98c5d95a17f97e496b58bc55033ffa6546a35e6acd05067df095a0bbdc4235c87b8abb77a1246a63ca31c65dcedc385eed49d3870a2565406dcca7424d55897d1afad21cca40d51cc7dd0feac05df62decde3fda97e671a636a6aeb6193d6748dc7f1f57bf7a5939be520412fc05e5fe07f7ce9996dabea44f92cc913c4f644ee7b195efbd77ca5bd6ae7e7a6f6b49dd9747ae7e5457fd624e306bbdc7d883e9a0df7d9df2d6b4b86e6135551bfeb2da7c78ec8703d9c718df171f3f381590c59fa28efb62102bd47c78cb86399bb6e9be242bc7fdeeaf76240c2359de6dbc7b64c873a685a9fe7b5f6dd8615ee891d5b38fbfa55c4e29cf61db83d5a095d6512191579fdfbfac369b03ddb78e64ab5230aca914b539aca43fdcc81ca298f3481c7acb72da6132b070da5e68f394a907f7bfefc41cf7f183dfd1ac412b30c4a2f844fe7da28ec9e56e100c6d9e72a981a3fdbd0881fbf8a9a8e33ed6e2026e0e6ecbbca72120fef7a07843f51e693748c57b498f043db2a45946836086035446fc4b8a1f79ffc7c6ef91e5b83d2aaab4e7912009641499e417c9efc03359b7b8cb3ede3e6a107e85d5e642f6245d4596b97924b8b915595f1ea9da5ecbc95671b54d1ffb9f44c67fdf6e9b44436de88420b5648bcd1f35e847a806e159fe1ab3dfb5843cfbfd2f91ab6ddb47807d960eeef1c330fe74b5551bd374d9c738e7d1be85652772e96050cecb3e2659dfe3cb912cee3bf10698b3bdc061298b124eb25011c5cbcea72a4fb6ad71e40f3773577d1aae65dbe8f597dc559f09cb72d3e8447ad9f57b68c81ea334be8d1d40c5ecea91de86c1e61006495ec4811c68d33fda251583e9944c88c3ede202e866e5c07fff6a0a06a4625e3dd645f9655e461be53f7aad7e54d74c4bd39ee20d16d644afd5e7bf1ac80cf3020a609b95c36efc7663cdd241a996dabe02fb2939932b301e2b8703e87b9a95833bdaa0b8017dfdd08dddc8f4194012d89edebada6394467d0b033d00571ce6489e54679ee4451ce6b0fb778b035d2ae5405cbcd6a25d5a752ad77f3a356d2dda6ad1a9d61cb25ce89649a5f6dc6293e026f0290d0acbcd974c706ebeefb857fdd7618efbef31c73ddd9c7deebf27a737ad0762fc16bff79da8c33ed656b3ec7bbf7aef891c7b7777f0c3813e6ff5f777d4a05c5d36e5b4edfb7bda68511a57e61be040f9ef9713887dff670ef9ecfb42431910cda1f0a7456dcca8d1337cd34c99e11bffcccbc8e819a51fa6192b34cd4aa1cdc29739343708923e4a72f8f8c507c3f0c110fc50b374ec30fc4007a859e0afb417053e7e9508be07fe07824f37087a9e66853f37793189c36421fde3adabbbc8dc7f24ad79ab7b16dd5ed73dc61df54269d06f927151497775e8fa56037463fae007a0d8179f7b20d6f34992bd0faa4f926c27deb9e3c8f34466d9b7f9a317cedb7c11cedb682328f68d6c6eb4d1cd8fac1cb583a37df7557220d5dff1ef5359cddf2f2993cd25aa2e990761dae821c481c4eeef1b7d39b9d818ecfb3fe690b53113640e797f7f86f418a5e1e37ed09c5b5cc5c53dd277d7819c7843fc2b36e0886e8fa43ba434fa3e6a1c94f4874d5f6a5fbf403cb12f9d4d1fea4630893fe78f5e2c5c84f339361ae7b12e1a6ff4f8472f1cfd63247fd8903e4a32cf13f95fadeabadd83fa4afefbde5ba129b92626eae0bec43177900f381fdeb24fa5541e328194feec1e634d044a91c703130175d6ef1d49ceeab2d406f63ecffbc00982a27e0f5561a87aac33febe7b173ff171f7de73f841a29f5951f75e0b3f58ce19f73d66e9a2efb1ce2208740c8bfbf74816a73350e4fde769efe70b7b9df6e93345d007d0079606dd7bcff2c7f8e3f19e03651d8a1070cdcae16fdf6e513adaac1d451cf4b01ab841a78f156ff8f4b15fc5397dacb621fd942c9313cda93ab5e122581da6ab5aa71ea65c72b53974041735c5944b677272a6ebe4f489495cc935683aae39bcb7de516881426f81d3a77ed9ed59bfbc78bbb73440434f674b52f04aee3607e806627549b707f641a863c64c4ac3f334b65ef747bbd3737bdace21ea85d38ec95032379dd2b0356ec67536fc219716466f30ff0a951f67cd894bf7062edddb126c093687a610484df5a32a54857258ccdf22b92d525d04a424d8f4a9e1835f86f75683426d911cd4585bd86d5b98087259a796a6e6d017d446ddc22d714b4a21207912ae23389dd93974c31e11e492dee88dda006361b0593dfc341310cc5d7299bc75932c29792b367d02fe2ab29b2ddffe73d2f007a56a10385dfe17cc8266fbe3f8dd89053092bbf7bfa26692f42f03fcfd02a0904b260678efbda73a039656010510720e4a5597006c752a80ab0a352b87dde1db1dea92c89ef2d6fd21d2c5b8ebfee7449dbc75df0024ade2aefbb8bbe0ad063d2875bf8252fbeeaa2b92b7eed7f70fc37a54c369094b407f70b5e78d1a5bcc9e9e16a5e0ea468d2db2f59608d62099caa4b6cba0613a08e298f3fd0390867bcea8135c89392ed8ab8a63ce39e7cc60cf05503de7a474ce39ab1017b470410b75bb2e8d4400674f896f168c07e125eef2b762ac1824a4224536bd4a5769d3db2eeb33b164cf2b7b3ed9a43678410bb509492e31d4f6c7500e549b1c33d5251f6e870ab515e32d5f926d937bc923b67a1e554a4df8be7e5fbfafa0934cbf76b5ab5df7714fff821af205dfbbaeebba0f042b58a93bfeeebdeefbeec16f00bb7bf0ebc00ffcbefff48c06e85abf0026f4743c20d9b2c17724501b7c47e26583efcf3de824d39f18e76c671096c9d9b30024e4fb17d490edf7e0b76a1087f3c61bfc48b2bcdec23fb14c8fb7bee79eee398740a0377fd101fee0ddf4afce7101cde10f3e48410a6a0ed00dea06685b0013aaeee1ee131c0c91448230823852041058182145f45b1533850eb31d5e68f2058a2ec9085937ed0e6f29ed700205296870d2430f598410a22b2106ef488f274640a2c5114d767842c2b64f63bac0965841142f4628cd89050c2b4788e08624685c3a101851041fb1e285d203591246872637dca0e48a0ea966457ea56cff61c8fe5697970b058630010f5d58a01dd1a41ba126e5dd0bc05050124b0285a8147422d46c58cbec06a428aec49a88a10ba196053c62830d2928a288305844d0a183d576a0a1476a0f15a69890984a32c6c68da94de198e4a815ac0842052b5d48490aa31685b3b956259a8070fbe2851794623a7814b518564990a00b1451b4c46ee0c0f0e891da0bb7c9044249102980e1e94b1556c4d490d85a922a27a93e4e8044851121d030821b56501be132a9553bc50aaec4906a21c4f0851630d65afb46f28eb1ba8ce45aa9d0ca83521213145844b0a289175abe702e7b1b8a5411831329a418a946b4508195b4446103432e47ccc930048e0d159640291155c44a186e4acbecbb4942a0a549971f6eb089208c161cbe7b24532d33d0e4249e444b0ba1529416116c8ae06265e566518497ceb3057ab86551c11547c2e8418a1364594060c20aa618020bd74511537858977e6b0a9e84cd051b0951bc38c20ba78222b6747ea4c90b51b531379cb083162338304520e1719574627b62427241072e382e94ace005081f76f832858a6fe96e96215172052d0460a2102326054530e52bf32506206e43d41053b205e74b59e856d17a1223c4174d285061845b1125a86ef55202272a2588218c089c1663b02b390b3cf4c0c50909214449ca62e64802bef80085890995159c705fa400b9903282929426b65ca14316599210a960295964b952801e9414e1f4830a9880e15464a1225ac942823c44eb10173c48d990c20f27c8e2258b920c9309446df1c4c6b43404150e6a0c9e4018148288d5848872e3ae80020372d34a1434ae64950081ec3df244834c0aa72b2a4ca9370c111ea1800e26e410a50a132454b1026aca4a890424a258c941845b9410705da0986aee110a85e403b27e641a51220cd314314f5e64a9022ac67147fede7baf9523271494e0e10729da123a380aa0eebd7763d764cdf4f1b7b73c87ae8bf92c3f846003942861603b7054101106c621635cab942423584871e10a264b8480438117d78fd401200166892c4e8cc0040e5c88d862ccd3c7c6841a5875a18b2b218922060726aaf8a2045db829a0f08e14e96207134819610a14332998e8c00c332c524ed084165d4411757d3a81029715580821ea85239c1411b4d06a21e20a771d8718931980b08202a530382888a8c2390d242c9488e24452d416dc13449400ac4cb630c97828e10712e08965b6f260d10483ad8a2a49ccc09d40866dddda975f212289c68a063688a0092648c022b6026e0b11307ca9fb8c0bf188148d5b6a654631d101d97be413620431b0c005942c60a858640939772b26e894acb5d6e71347381183a2e9052a489c280178a402575491c2298523312e4d2c11876841c458487001092e464040e3981822d6b9d2bdf37ebe3fdeff5b45b02f09dfbdf7823268c073885e24c0ccb09e5c266b7d66a1457d7757cade840acfe7e47499afb676de5c75b02a7cc102172938114105596c4fe0287984791de167937a6489ceda24e53273bf4302b660e2842b5d84601ab344ad6b22e2e7a8290c122eac70c10a09342580105725c83e5f2da8d5cc5aa11a84adb5b26de8c49624820f25f060c5cb72496cfbd4c3b65fc381eeb6ba0432330bb9c4e37629e0cc1864e1436b86002c11802f0090426b842727d70bd99312002926188491710c00a892710b4764156a7882b9a2a4208a36050094c8aa3d9f68494cd17ab8e04bb6d9f38956c3055eb2b7e7138d89b62484ecaab0f29f3dd9b7df696387d2a0efd5bebfbfcf397d7be9e307d874cef93bd4460694067d9ae9fc0ce8d379c3891c4fdd8e727eabb575fe4cbc13a3d571f7bd960e28dee8eecd1cd97117efe018db6d0f95ecf70904573dac7a08c1cfc3d539bfe00ff7feb8add53bceddbde3bcd65abdd66a2bb5ee6ea79d6228aa9c521a1c9deeee95d269c15a6badd6ef9c40f54bbc7184506a963cdddd67786b3e56d27577da8eebf0c59ef5be0a86d37edf773f50bc4f2c8ded8fc505d8aa050c7113f3fbf9de5d3a5b5dc6a1f50f745eede52e77b9ce76b8ce983fc36e5a71f8f8b10801bb3d2ff41e04c3190ee4aeba4419e41aa48090da30402b4a559d70b57abb7aa562ae4106982e7f4fa62b69585d6b9986331ca8c39d9d1ef550208e16dfde67163fa420b3fdc71f9670cec7ec72e5cb9e60268df98c12c1fd73eab4e79411255d3869f20313a957d41662d3a84d8308c116df3a90d0161f146f882a104cdae18be2e73065faa26a47fc4fbcb152893a0376871ac89cdaa01e91f87e9635fbbb32667fdf13104aec0f0b94fd3d166f7ce3f6be136f7806c0f80a958da5367e4ebc8127d00092c00cf083086a96d96c5b5cf36dafa8b7432b0a12c25851e40606edd2d0848f4000c60e2e3c4d75310b02091fb6503a6bcab3f594a504b1fbc41bdd986d1f639ff2230863892b6ea0e104501c314495afc3d16996945cfeb7ae053b3ac4b6fd6c9f7c90b22dce6073dce7a80d04051412dca0786284941767c4e6fec781b8afc1bd02e6e68e0ccddd75dba78722db5a2156d7ca0c3be9e8e704166d267c2a98855114f1ba9802b338018371fac29410ab12c6065cdc02a103a8942df6553d0b11d90800000023150000201008054462a160349846caae7614000c7f9e487c54968ac324c861148490310410420801000000034668688a036d8f710c200871e6e8a3c2fd99197feb07902cce524cafefd92f2272bcd65401e29fd44a851cbb690287d35105a34ced90c269de87d3e0be909d91bb5fa76f679e3117fbba73089e70420eb9c6e748c104ff861eb8ca2b4f51f3746c329ef600837f76a8b554fa6763110b7f4471eb75b4a8843c73b2372d6fdd865942867896a2d7345abc89abcce20d52a29b6128bcd81db8c86442338ec234cbcb83b6365398fedf854fbe56c2e36f1919cbee65ed49651a84b2d2021950d80245c84933fb059f494e33b854c2b17733857119ffdc11fbc0a82805171c16ef79e67f9e508bcab21dab9bd39fdd34e89117ceffcda36a55d60a51af47ead2570ff91d6970548457d049f538d49fe984f55a7448217630bbba23ee863dafa605f916929aa8c9556831477fd2d67fdf87a78a645e24d71f83fecb1ac1c4fba479b8a441b2ac63d25686ba86877030a0368225201cc865e5de05230a1a5068326f85ae1db86da83a1b84dd5ac66478224ce7c46b31354232fc493dd4a20283fbc2611d6b827ceac5992aaec1f014a6f3c4fac1a279c44aa76864b811808fe53ba7669060cb5a591fdaa5d1593f25a4afaa50e077acc90d1386c54e71e81d4c865a2e3e2766196e6a177c2e6d2aaf5f70402d7349561510db41f2151cf10e2a10b00f33e25c212184dd76d121bf4b8884e12590f16df0aefe8a6304f006d9693b9f6a73f5e80ae38a013717e144eefb77d3705dc29d984dc27a6cec0ee48796056bfceaedec36043ca5fb045d17d472061b98c4feb21138944c98a089c2cbababfa11c4827a16999e6a3b28b1b1342e0f87680392c503abef768825b451e818765757622b81ab5e217f49e4da8db92182f14121fd66bca5ac8726f3ef89b8b271097bb930e24d9eef729921c195378342661f36e92d849eecd130e7c7bc327653beaeae6f6845db752730f65a72a78fc49768ce0112f6225a100578128f489e79d1885b9f7bb00c1b0dd565cc633936e97440197f426c243ffb76c482e9826d733c0e3646f34fc6c67222cd93bfca368aac23def846c5728ec3b129774bec3e2e4d0b649c49368dda9644eb1b9b48bab135c1de477c18ee7221620335ab8a53350ca6867735764d98a5c238056f043fb133de1817074fa4834fa1297161212ad602aa122bd622b67531cce05f4db869de9249d103eff160a216b67be296351a73c72c2a1db5fd7291fd89b5a4b3db780b28a22dd354c8dd48c5db792cd964413b400e6d521bedb08fa8aaa006b988ed2eeb0ce3d13c48f88270cdf57794d4a4a5b0d35288332432c4e6de9dd37e9323f52637ea01d43b4b4eb213b224211288d915d5192c82341d15eb23c94591665a3b6007d41f901e3af918fd4be2e548e833b5c30ab5d456b8b90d58a1b11e01e170cbf870024d1e774975734acc1670c4f50496653161b7488c5c799244005f420b4e0585e7466a0e51aa980f59e468eb3f42eb36006b50aa93726482f5569b95312cd5489087d340e3b00d339839f8f88fdad053e7ea13d8c9031b388dc5256f34874541a37b04672d2944331b4eb190e0f665ffe24aba897b774ab1170dd37eb49c12b83ebee61887d905bca2fee42a7b5dd869d9725bc5f8cb96d3af5258c4e73e9759806f80bfeb254a4e61cac8551876fba88335ab9cac5c2ff0bf3d0910cb132763f313fe432b2d06c76b958cc9398e9a4694c911cb32c6168567e2874c415115d534da8e3d836bf9cfd92f7521fbe09297a3dea4465612352a76cbabc51d4e78925eda8a98a86291416b539ad401209a7864744a5a3013003c65e2ff738b6528d7b8c404ff0637ab0bb45f3b8a9e4cc9f0593981256370007c416025a8ca3f3a329754d015ea4ddc946922488b0ad28efa94a8f453a8f6657241b86640952350a862d412d01c35369833306f0889643f44a46a468659483a5d84ef7f2aa297eb72a8aa46c5f7b63215e395ed2948e3d05cd5e3f2a5ef6bc28208d2186b29fe9fd4dc77494f40476dfa33314401025e02da5111fe31baf7155acc08219680ddb1a54409f1b2510cc6ee9d202e3322841bf706824106fd43652eb3dcb47c204d3ec8b905d7b1b2f2031b13439eb7f887acf40d21a70fb149fa59c93cdf295a897643186806b32be00f7cf31ce2bc369c9d4f35356bb187f51c289e4b6474b5e2062c6114f8d95e69919c89b352f09514d282bc828404c2ca8e753cc7b705dc7b2471c693b62e9420a6b981402c1aba62eaf8b115d1f53d468bfafb6d517d1d9d6a2501e571d509a3eff881e11de5b7463775a0002d7093b55b24ca71acdc3be4b8f474a6fa64bd24b98a5aaf99027aec3e0ef6c4b7833c81ab30b7edea79ecc071c2ea37038870a228971b9311b9ea2ee7910f26f5bcf29655e4b85e89031b5f81381fb279dcaf80242a0bee71db5506125d5285062f242725ef1e2c30c78abdd8e7ee8b2d374a91e0243d59c7acd54f46d2be44c0b7b27cdf1ac684158ab40c58e6df6a2a3ead8469e3163be4178ecfbb53a6a43b7a4c895246d9109f0e0e58dbb7e596217ab5804742e86c292728964f7de04dacde186db7f09d2522751f7e021596fb0d4f8da8e5df75449f170178618fb34b60a24c43586d6217183e275d9325f509aa248e94c9cdfd6c1c2bd2cd3ed692ff0da7f923146fdb2b39ed7a835aa9d174a672adec0d8936cd7222935e0cf19db4465f0c99efc30418fe5b15f8eaee5f9021154af5e8e330ee0c2983fb1c0d811c7a2bf6f744ba0d54b9108a13630dac02bab1af867a87a5d53402da985c12b9dc7c25b8eb96024a864b3988bd2f87b917d19737f8f2a2ef98a492d7085fcb34a90e9e09657061d901522b85e4c391ed0c8f083c7774841bf96ef6b9fbd4aa248e42c3b4cc206d5d55040124989029dfa364c8432141044e462f9f74329e06031bc263f792c4d88203bb0841ddbe2c6815fe09d01c2ddc6de133771c4a536bb97abc1efe29c7b970dbeb30e7e6d6a9d2a88b8419afa7c9d33be8092e32fbe897cb11a2308360a465724d24b35cb3e7cc167193fd0383b9144214092eb5964e8ab220d5c4385d6a15620befc840c3f1b6035aba29d32d11e33a14a7dff61bb931429c3931f29a994b444999a4e25a0114ae58f5e406f947acf1e494796846c1c816dda64d6729874de08856120e43a2db6120af832ead6bc970323e358fa5358b5cd50e8f194f9e7af149fa7be620ed466a74607c66621292f0adfe88aa038abad0086538bc9038ac66c41fd1fa31be8901988b219a5bbe18615a36fd6940097221161d7a7175c87f99b85e48026aa096c8454404da8972d8bacc0ca388c5d64e5ebfc9d16b448c68be0e1d2ba9ba2e3080a23f41623edfe3af551659af70b9524380efdce306a16f46e3c409195f195e23ed018fada946f30f6e871461c0b732d16f356898f072022ead6b864ec4b721944d809c6ad3728e469e0f375b4ebe0d1ac2e1f8b82f02de4f023fb37ca2466ebb0a0c5c7da2603bb893e566dfd6dfdc1d79dc6d2f4e07e197703dc5501e476e68ce57bf2a323faf2f08a6f86933d638ade6fd8b8b7e6e78b81f78169ed0ce8a3578a6676e518006674c169481a8b30be9c541519ae1972961e2bd8b729810dcafab18b580cccde253f6bf5386fc86c44e95071b47f632f19fda19f2e805858b8973bb426539dabc414195394169b693549c9c2074ac903f0245e0da0ccbe553bf07821208bd1f823940bb08e175d42a68ab2e4faa8588291332694c31cd210ee6e3a96ddd2c4d5ab7384009e6ad5a6c93ac577593037626fa88e7cf92672d52d324c5d6c96088c37a6f5fb5bf00b9543cbcced90e4f84904da2de0d46f60ed4415fcd54b8fdcd0435a8d16d5d4010808fafaf9cccbb657014bdfd651e5adc7c5cd75d514c1ef36529c6844ae9b31ccf9a7c4b6efb3670e11f2b53307f0923963de024dd8fb8180deead008069c27b10d299a84d9ed1be6d545e560681688a0bfa8a030a1995f838ad76724519024bb1c4a5f9af344f037cc6f48859c9c2f44d5925e65f0d48aadede6812dd5f3b77e6f90d6a38c5fd0cf474da55c7b768a6b0673454eb32731f17f4f8140ca2846a640c6d2855876d4fcf9b4df27197b167e251837b7e175aeb37d945980bcbcfbb3ab07b350ee533fa234d0c4bb22ba140fd9458b1fb63029c5ea1f7a37416250e553d6b7eac8f53d4c79154134c91088c5c19ea7276df25e519326bcbcff9c10e93a2a7ae5d11b3d7a2bfed0ab03cbd130a10391f87b1e8562519ef4f5a46be5e5f2ec1af93ecd8ec786104230bf4db371927ab3b0323c7cc853adbdd7bdc025f08887230ad5144f855e8afa121cf0a919b6ed61a1ca4d66ee6ad03a9374ae0b46a0d7932b700191a815394efcebea5e5ab7b783845ed7a72fd88c9d95d35b9fecb0080d36a5f92c8102a6182c0380aa1d61f3d956818174caf9f764c8d22b1e5039bf27844b9d4ae4d8a808e08a984a384dd3c99ab9a21b4bb9ca29dbd2fb3aefaf0ed3e4117221cb8d9a42c72da1640e6a6667c0195d92b54d14b802c961e4f31e7b3138245b85f9872d36624cc29f254d617616ffb5dc348850184f162305622343c7b1061ca69db8baaba312471aa376df5d78327dc650704987df93577c0fe832e32dd2a6cd5b60208433173a7d41bfc98947a727c0c726d2d70278b9899ca3be51271d4a9eea132d2b7be91693bf8d17347ca2a34ce481958b9c06589e7329b3192160634a420ae91a14d62b01c01622994da47ac829b4159762d5a45c456d9f5984fe08070a2136323d20831b3f76c492176975158148989f1064b25d2e6c660a7f8d6fc1c8d2cf8986483a2963671cf3b628752e265acbd42669eecbb6217dc5acc344920aeae4ebcaf63252835efca55c6054d883699aab499087d42c14f46c96f0f94825e0519e47aa38d7816db244346fd4cff28e9ddf5fcdd81cb525e7457deb5f4de4af15bf410d113111ac6b87b493ea43948c2422d4417143680a5f56073cec1b177d12aef3381a84079785f35c8b02da768ef2673233a18baf719a00f6b6049e897a377604f01b510a3d668df8e7ee6952464396a451223ca0429eaf7efa3d25e267ca04e83dcf350563cac94046e59a8e3679fab9f16b02813ae0d4e98d5f65743389611621f6051afdec4f697f6fae3367c7fdce313fa19100c6d596c33937d06ad2a875b2a60d3bcc90433ee06e44162ac5ac41c02f6f96e2ca1e12958893104793125b10c0d891577ff0c18c6c4a21573dc159cb788c67d8862f73604cb5d50e75421a14a4147d62f6e2f0f92bf7051aa4721a58e208e31c49037979472d1557514b7483534a21550498bcc95d88d3f813d5127d82d786263e77939904e550d76f72e8b74aab3607942d9eed3e7317b1e98e5ab5cc178e1f1d01757f49bc3c20cb1ebc546d66004f303a981333ab505889b7320a029cc63fc3a337750a876c65bac26e82976778493a943deec4bc73c1ebec2c284e57a6ac0cddfa6c3c5ea55ac1c7dfcce9b7a5780e535be8331b323dd7286d8ee090ba4c6c9cde7d2996a38a0698cfe9813f3cab6610d4f974c964e41d85233a6c8b8c05b673fe67eb04fc31f19fb36f503d51f3679933c137fa0d0290590c18b32620155b1023c388808b9de48df91d1dd87780b6c70426da691d7834550a2016158e3256354f58a082b0afa78671a0423808744059d32990555fa91b6b6cb80be5d9a7b599c764450ac90a4608ab145f80308809adf3dbec8c2d6146286d9c7dcbaf1b07da8e11384f603eba4859326d156ff6ece92da8c6e406bd3456bfb315eeb386146708298679951248c8caa31dc0370559407c32e899673b52922a7dce31d52d7d35f0291768461d326722c9074c9b64618b4f26f1c57f17bc0a5607e0294850ce26dad78e7820a866f2973e5852f79bf3cc5d1449a39beba67408b5a42c50c91d054e8ac9df40b01e2d20e212a97d4259f0a88808cd0defa879ef11c3e4ec28e9acdf3667fdb86658a08cd11da9e81610f86ec02125e81c733a6b3491dbc0eae111b6050e594da4dc28310bc0875902ec4e1b692ad73e984bbe83a601910115b6cb3a4ac1dcd080d3951b941d55914e9293b33aa02730b2c232694da072e341105a699575cf523ab14044927cc8c714c0e0c26da6352e627c76670cb35b876cdb04c24ab7584d07cf6041830cd0578f380df3d9e0e0586d2fa8a1c029703d6429317e6cf0dd08ebba8cf243c9646fb3b6969c97a233816686b72088732ab2044209b1550a309dce7722289f3bf8aef8a248de09c7fbacc20e7fb775ec535b86bc58565426e5df5ecefd09d29564aa41a5f707f28be0d1531fa17c41e87dbe76a74b2ce9627eb190a5a8a62f6bc2c3aedb778378f18b5593477a45942b6da568eb2615c47f417cd0a954b3bcd884f680e96754dd20e0b0577dbdf039682875c0c6ebe801c5821a776197d26f9f5337b4a9c217155afe6708ac80d55f9c155915b68ab8730983025824f3d712fb8d787780d6b4f7219c21b77e2e5e32579e63026066d29e68d3baf8e919e54e1800edbcd8e104662f69437f217d768c58e7d09e35a09c23748251392646e1b56183cedf2b3a44ed73d74c160e6f473f2f002757b0d4a79527641cf89fac7b4202e9c9b4ef1f1e0cfc59b3ce9d87cb3fddb6c8be1ee52c5b2166d063fa64b8913abe39e4e27288cece83d183dd9fc8a1c6150dc0045f2eef76a206b60c154580e39f4c5ae5bed93c282a60795905a2aab3c9fad55e4960a30db96ab82d4d91c3f93a36168d8b5c703c2abfc37c69a455de463d2c31596cdbd768bab16d060810665c9df288da236319d763910b5cbc549f4a88e04ba39041d55be33d1490bec296e9bd74f3d27f90f8c8a7e595693367a8ab7cd19bafa03a9e0b1b1072df9014ff2d91874516160e8035439da747e4e3a1de180509e3660d077dba87dfc185a2a0d8d4b01d84ffc279123ee7fd619cd553baeb6348341f391c6431515367cbfba8a4f9abfe1d345a283880dd26cc29bfb4e76e2fa68c3eca4518f1d4594a1e6d2edd7c8245ca90735dfac80dbacf99f58d063a23f884913f47516a157106a122cc9de5d63460ecf0f6565380e9610da6117378590ac4ae726d276a69beaf2fde81b8b0c2326492769a6fa57af0a62ab5105bdbe3a765b8dfdde93f5f89c762a933463b412497cb0c4659c673050e12bbea44ddc5e50432b690c37801dba76c973f096484723726ea2c1d3e1730f2ce7a06631a4b0f7083a5261888fc691d3843f29f631781f6d4e475490e8111fa08a3c28963a52ced91484441c16a5a5c1e1abd0a7254b621fc7c7c847e975947de3318d52667add960d7c1a659e347eddcf6d36a3c27edc22a853d734311198959d471b20d6ac00148c48828c90ea6cd7142b83a520e522fb7f72c89722581cc1de63af0b058ff6dcb86ee09f0237fffd486cdbd1e4ee818f93c1f8c99b44632beb11f998a9d16e79930cba43d691729bee0379dbaa8058d4dbcf45eb7500e267dd3e88bc696bbfb1e2feb6c60a022a1d7826ed24ea77de0038578e82809238578e88b5a93720dca22235a9134919e0ea949a0f3db03283b7b0bd468f07b143f56f1e0073ea7fdba5d85088ba210db497ab80fd7c68dbe409c4c1853467d0acbb5cc21a442d49cfd634d1118a251884becd7c27f63b002b979b1939cd9ca0937248ca431020190e2fe7b3d10090ec1a88b3117c7a1b0ea28bb3a391fd641c980ca5d0fe5c4c90ad02a7915a9758d28d42538e3fccbe117adc740e3acad8caeba6a4a6d3414a39850c749b4d2703bc6b244b5d830c4ac647e4b6ad74d7a785acc28ae0d5d85e1e90d589e64755ba6b9c6ede53ab4a89b84b358cb82a794bc2f8faed871e0a7685e059ecf1618de400afa4526b43ed5de41d767c6b827cca2792d3112f5b5481cf86527919ff69b064935feede3e023882b2361c2548e8a9c66ba34e0975bbdf2a5285366762239fc725335b4cbc2412d1018e336c0a484834e7598d1eb147cd7c46257178b34012aa80f1a9c306c6d63d4ab613357f47090fd42844e596a824340b3680489457bf366085cae375733d30b61e4c64f4ec6d833d1050e120badd0259df9f1154de9865955b733738c8996aaa1cbe091966e68cbf13be8d9ca71ce17606a9738bffebd651c38ba6b23410425d1bb2b8eb91db957a96409c33729636345d9281c1b320651f8427e4a8a97282cdc9d57a2d5ad34e1e579622e9e48504aced3c1bb9c804e5d513cceebf69c67b6e6efb63c9ab0bde412b46a6e884a53f98cc2d817215302c7f5e804bbb9d7434f551f287c419635faec5497de2bb7fd319265ea31cf8bcac5f4b9fc52d5c4c60dbac2cc244045cad802b64d0310881ae4b811c7d70e70a167a31275e12185fb901f18dd254b79863a352ed85dc4a1dada9c7143b189710bf845c32f5685f872cc34921775abfb74f17a32f85fa7ad99a78e0d9f502dc5b479043d531d84060c454ee33697f3fb87785225091e6460b48997a89bde974e202e57caf7a6e4d84b555de206508655deaecb02956bc52a50b9dbd102b73a2818ce8d5b24fe28e77c10969539505d5c5a9571139882a2d2b9697496914335120c26fc917c3ccbdb1d4fa08a19f9755be20e44409fd7fa6a699198aae6314c2be68c8d17bfcd156043347c335804541a288e0ed573177f9fe336755b2394de9bd9a6046ad8fffc825de8b6507774001fa222dc46ca404ee0f9ee05b032bd900183d587158c4aa81c80c5be47099911e36dd6e1ff21e6cb36c258895fd616744426461f9c441e03bc8920a1df2034eff5de2a8f6b554c61a437f4cdb552a04d6f62b2672f23dfec82c71e57c5c2ac5d60812d353b92df92b41877bc289423a6485b3cfdad755210248ab7324ecffae6f0a30feb9b26d0a4f1749dbe10464de05c20fe8e58861585010b50c6ef69d84438265a80005aa960ea610d425c433f4fe86dafa281860e27b5921346eef35596eed0a3372324e98a3620e684096b49b32cfddddd03423b14ec50847c12480153070b4923fedebe2e9e74e29af29fab189b643ddd67b3af9915286c13e69bd5c8da852425db90ef02af3eff628d3513b8b90203de9c28b7e4a2f191b4bfafc65efa0db26fe78dc60ed5a95403f28f231fb05006b75ceac5f1cdde6d47c73058480bb8dd6ea5d0527854994cb35d99842b634249b01e9360aad971fb54123f267a2cb78c90bc9394f516543d0fe79d5bcb447dee6d13ceee72470dff088ec66e67981b40b50bd90f499452480d9be1220c53d80eae6d5b1c362f4bb8e3efcfecedb5fd621671e7c32dadc987abc3a51eeeff0b86c8097eedc06210d4177a959c6aabaf0ade1332c7abc33e267d1f9cdf0af150d87347b4804e15fee3bde26bb4760f58f5efd6733b47b7e21b781886eda52f2d20914c871bc9aed6842218a47ecaad0932cdc6f4a71a44166db01f873b5db97ec511448ae13d7a18bea9c14ba4a004400d9b9f4bcd59110ed1b8ce6c0ecd195dab4ec9fa98da84adc0c37dcd0ed9cf2f82625d90449a78d34d09bc7346f579e981ba7d21c29aa748f7815a5aa8c7ff6a16fd90ea4169237690982119dffdc63bc501fa4095650e8f6590e6c34fdded79eabf797beaa983ad183515369235cae4312ca01e3920db2a510c17c298fee689afb94f7f54fd79f05a8a46f8ff3364549f1a21af4224c9bad5faee050c4fbcccb16b4c832243f7a493e8a71c5c26e910bfe08dd322572a85ea26b5efa2d630fcabc8b54bbe817a72fc6981e0ba285f55524ba2113cb19a009b68905cfbcd6757fb75f569f14f297cb6ead4c7402c301262282a644081a97b35e0305febc42eb70e132c9c7e2c6e9fe1db2865f796e8c4fab8062c16a3e8ac69a2749359f68c465a1a42dd3de2de2af20e473c0d85f6df729b69cda455f6784f4eeda12e1f8008b436405e5877b2d2b2cc2f71e1822049cd117660f86032783d892b040bc52111dee68b8fd05950adb710b8dacf4b0f3a732622a22d18eb841f0df15a70365c4242142dd0670cf257db043cd878cfb6898d3a49bc27fe859db052d47840f8e4f406bd7ecc599a93533928910dbcd45f871475cf839fc643b470fc8b1c89805e226b100ac2fa986710e1720266d7148d2a55bdc0e70f8a2f10c19320f05a9ece04d13631599749bd954b9813ab11960385a9e55e04090c57b4a4a0e4318aed26a947aeee94e360a72ed4eecee06e7b04b24722d5c4808e647bc6e8e1dbeb3d5c5b7a4d842c4b7c2e0ec7ebe99137fb0c238238fd34aee8b21c45a280055da9204c670a5df10901c8fb228b38ec71427dabc1e54c8b2601ca0d9e4fb6198b08385011c97e4f74d6f9811b17237cf0d2db135a188c99ad81736fbcfffccfefe6771f2a60e98ee426292b92543abb636b1a2a8244106f2ad7721fcd19ca66171c6ed70322bf162a705ce8eb7a95a2d0494881ce5907cc328df0a155cfc453c0057f405c10240ed393e898e8e507e2e443f663fb4508a9679e70984d6dc598c4f1d603b1e5e8605d70a05849059e6dd6caeedcd5dbb051008a81fac4513f74891331e5bb94150bd0dc55a87e6d5f33f7740e051604109564b8b49c4b9c518b726aa45f460c9aa33efc2335ac8573edeb3ddceab85a4a604684ea15248d4ac273359b83f45032c7ebfc7d0d568557d261ac2ff01a42f0f3467583837a6011a62634323b59b107957daf7f68fe3fe947fa2ce34e591dd3491890c1ee75aebb9ba3d90c1674814bf8a5af169e4bf4e00f1e732a78c9b0cbe05bd01b8a704d76777bee7c267219ecc7b5affe90b02bc7e09dac7f4d1975c9a8e7f6e66351112cd80bf16e44ba13011d2a0f5d011badb03789366574cc8f0e60d29ad93cc40f58360eaaf619adb6df42c4d4e0e6639b429647c9f0c4672a5ddcf7c7204dbb1b0d568e08ca0dee4b5acf3ad746a0a13f6f1600d64216c34e69a920616891de81e515d884b80f49f9f44aca97859e79b42d3535a1f2007e83cb70d247be91eb3aab89ca9b357b0ee84f90c9fe3717480185bfb7da6c27256c1fef6607b6346257df31eb705155d27bd341647421e26667dc98482ecaa88ec4c50c84451a0e4952df50c7c540a09bb490ed13b0fd6686bc1e92833c1806c67007f1fba87f10c49a20604eb996cf479a8543bb003111baf87b8c4316620f360d492dc26f11c4101cc274653dc155cb045f9f9bb90f17b9bdc54aa84ede78f9837e4816ba654c213634e5c55b8e5a0538baa4a51584f98b4b14186a70563ae4965a812a6d290fe2d6b02b933d012e6e06d53a259777a86a5c8d40c496c8b6851db7a7be7505d74193d4e86cfde7b5b4b147634eacbd96f0ecf8373e702119357b6dbe10e43a51887a52d75007cf1ac203c162f802f77465d6ff9257339231577b7e4388390b6a2bcd42aa2a7b27d1462df3dffd00f92cb372ee8592cde9fc4db6ab044308a4709dd38d85bf319b090fb53011ddea6dd4d703322f8014f7dfd7c9e42de6a98490dc36919be84b09625ff99593a81607a21ff99d80ac5de1086828d5e10a45d500f5de27d91c041e03dd348c7adeb04aa11efa968eebae1012c79cea5ea6de996c4f8b2038dd579f4afc71873c4fd9dc4302854a26e5557b5e2cbc0047f9249f269fdfae83388af2e5909f79b1b2df13b2f104f998009dd206140519f8267f284c80fb535634f289f81a81d49e7c2713afdbbc5ecee769c2e26919a5142cee8669879c467d6fee0089960d84af82489f0878742b61a9ecf9de3201609bf453f482a6171d10d7516f0e4c93aa75d629435b71a352f47e9f08e2d6c29838e9e23ffcae87cd3b70886581210558242de111134bb7584a2ab3f9d168f18af167059eb2a2af2a461f300c7518b33c56159d0d978a51a1a096389fda3df65dfaf59488c862d2be5762e1a34bbd652ef43a7355b50fa7e47f5b306c6c6816d98fe1b0f0c6f2e12f981850268419ed92ae5ef29308c7377ebb7d1c9ccc2cac28edd3cebed585c3fdffaad14f085b43e78ec5b1274e910dd72e11720a054e4c117d8c4954d120703aab5f42ec2c8fa5ae455e170f61d2e59e288d21721822ba9a6d0684f015e36a60cdf80ec50d28b6dde4d572dcc6263cdf015e40d50374952f8f8adcb41e8760b9de7213825123db2978483f9d22c1106894c9e850c4759a8dd4d592cf8e3d2b964b1ae1a2caf9571701cc2eac65e1130bea9d9dabec0c4498f4300c721491b3aaf95f3d54cd2a2342750c8beb5c35c289d96e61ecdf84d482237a4c233eb4c9bdc5005b92afd41fe9d2bbf6fafbf3b38cce7250f592fbc52c90adfedd5fc4a2ad5d734c3858a0a06d553a0de4f8586b8655321dd980652a9bfb8f1586fa61d3332d3948e1b5738ec7a2a73542aeb36efd8ce806025c2a29fc21db88138ab40109526f1514ff1ab0d35d9f323265f4bb09d59769acaa6288da21b3f188e0c62f4c7307ab466a6daa1518cf375a9b037de6743700142c23d9f1f11a01a78ed735104ce10a45f78ded08cee6d4e0cfe7d5d8af32e258b43c82de6ec22cb43aaf3ec33ba4b19752d4f8e5c04245623e415e17b55149b63f5db00889d8a451e158ba3e867290d6816f33ea5f10d54bbfdce0a6cef939db5c694646f85b0752aa6428a2d05d2ed8395e3d3d476692809a7b044f0d4fc417fa741c5f837c609934722c0bdf518599a7509ca75b4ab4539c6cafe484a9e9610bcaee2d9c17fb872aa0c23d26b1fce5c043a0d174a5c90c33e3e50fcbc34438f15fe7c3b95b637922d2cd4448680ba54a3407d3827765301f7a00b8b15280a3a4fe5327a6579c31b81c737b192c0d0eed19486bbdf532d4adc63d10e6e79683d98583213522c0c08c1528b88f51dc9529eaa5fc2e1959a28385a5d6a48d25e891d8b033f378695c0eb15523f02267cdd728b3fd08bb42fa3d8f07df9cb5aefceca2cdd1a106ec25185b75891e00761a78160dddbe03e88d7597fe0613a6e3486c341343f789d68f7f2e372aa7688e9bae12136183f6f7a92bdeb67e881bcc38582ae26fac4f8a9329fd751e93331d5fec6c7ac725098341daaf34b37480f8e9cec1f59c9095d9fd1354461578aaccf2843249973c4c9ddc9bafb33ce3e0c4d098bd079ea8861a7f15a019aebe7c62b0d4e242679592eafc949b84f04d7e16b89b3da40c83d607c218d2c374e74023a7f44bea6810e14126926710e5929b84f8cac7fd6b59fe2c936915d7f633cab9d400fe0cb3d2a1b57a5b34e332395453172e9e05708d5e5b5bcc39c1321df7f071236380fde011dd26270500aa519b2731c398a384696c81d06a3022a6293b5f7587d5ef3112ecd748bb950237f4937be3b3eb206aff085485e5dba38446925ef4ba835a196b0cc0126f1aabd4b4c07823a12ce845452d334582f6bfcb6a1ba5ba612b9175990eef9606d6f47ae4661e97868a89b4c845f1bf950d4f98fbb51b1ce3305a5f893dffb315c4cf95900a0c2567a19913708405c0042e8dff254dfa600fd76c4a242dbdfd08cc06eee27fc3a2f10048996755b18b67ff419c1fdc04abe3c3341c06922e83da7997058857f7e79cc50b16018f309056fb7118caf53fa5061bffd8bfb35e9dc2ee93e73f235c1ed48742350619c3e99fe321c4f96731288234f801a076e7ad65a7944519f70dd3d4314b5a5c41c1113889da04f23db4c87915e0124e066ff9ece35104f7217cd2ae131abd9d335ee583b9abd66b0b56719e0bfc242c98ce6cb8ec78bbea43f6a8667b20ec84037fc787dfadc9c0d986f8d9c4d4339d0ed4bf86fe7e543297569c08a71e928705bee168d2412a016dad6c2b5345a51df60f9288080b07b844ffd9cd41998a9c7c7ee05d5e44d0807880acab4b39cfaa44157fac4a0e6ed29938a01fc586b3cb3a32de53117aaf55b650441a8b4bc2eb179fcc87e1f50105c177cacd33b9751a96315a4869ea779257f4cc9a4865b53ae60249db0242ac9d83e408940675e22347357426d14a74b68d1a18c788e30f35c0706a322bda97f2069b571e51f28e3c3c59588d3dd24a8de4c11af3a939e9a0e60248eb9b323fd064f08e88cec5647d2b14dbf99473c914cf2952982388d60b3dec2124b78d2119210d326fae7e2e4988ee40ea7f138925e3e3ae884849fa290f00b62965ae31db3c2bf13d05daf792a7f51302da1e2719613bee873331678720367c6ef152b7b34243a406848074f16ec6a3eb40a7588a6c8e1f4de399b899eb807402fcda1fff38b1580eed0f5fc0b47da9d526e8a4031939f4801d10a3ae242a67e66c24116065849233e116d74c9d8033354908aae13a73cf137c6ec0b40fe464836345c17628dd7f586bdb45a4d6c2c86e67286234e548e46628595d2e87ea12ede97cf2c652e54d99da85b20c150309211c38717f31e3517a0ed4f294d9055bd3459e755e2bd1bbc1cc462fe1198a287a27165ba3c6c10a59050f91b319454f80e72f39cde84183979a6102333f9c403b698f9d3fc01c2449bc8a46d3a469210553b227898ae1036e5ce814b4cc73c8a0d6b412a79d769b31f7a1fc869570f75c4212e5dbb6be6b4dee99c5b4b656376f925d079fcb70605cb936096d0beb93639e115ee4810b271ca59cd55edd4737e761eaa7ed90a1118621542011abbd9e36ff4a9964eee76ce62b6b17f7b1dff4eec31a7bbcb40c497ec175a81dce25e7008932facbd713030ce1f75030ddf17db37a56af37c0ff28ca8c1503f526384ee4a2e04fb1081efa6fa34d1586de26c6d6f43a99b08e0737523f9d47a0bbe20070cb23cf04eff3294f8c88df955c18ea2cc91b263a9fc7026c568a586b4c1ae78e6482ee154d1d10c95170db63f19dae95cf08871d959dc56f2837cfa66a9c8b1d61f012480822d831165960c4ff9b9681c566472bc60f5fa520199de14d2bdedb624cca0a9c8d24ea21c26832b9353a48729affc205c5fe039b9b1ef52f305038bd97bd4b13cd7b3a247be40148927f1ed4c194408965c0927b7a2966d17a4a178459b16c01ebfc4fd4ae043cf7754bba6a131c080c9a2151799a1870da1b79da09a2583f4b3147255ee66c6e23ff9454b20ea6726b97f6614c139a376a49cb8fee32ed2441c0b4154da16ad885eeb4e2631b5da6c40d44ad475baeac7f2ace1e7901f2c465957f8fd929f2df134c6ec3b0b7e953e5d0ca45a1a2a43480033b8811c3bee2f2b29f62ba5f36f4c293299c9bedf24fde4b11b4ea9e83001976ddf4b760a07f4097bebe0a562f666018eecc31bf67823dce1c02d7ac28096283a0d6342a8f4a237a3e154bc52d98c381082ebe1fc17aa6169ebe3af05d0aa35b2cfb4efbc6a88c006471847dd72bc4c3057498b1c79f5da2db6386586ea2451bcd500983385aa7233440fb126f352068b06e1b50b477579e7e81743a56f2398a65494fbfe15409f4bdb829b69e255c26102675c3ddb8c3d1cf0805ce6f86251602252c366eb4d3397767b4a0a2279118b4a1e9b34336dd679666ae3ad9f98ce959b3ee4180904ee9a40c511b808025b18f71c55d43457b8340358e3e8aeaf2a28093633020d404780d89b28eb12bb999098cd4091f2cc2dca0bbc39e5bb07be190c7bb2889264dba7bd013601dca77ed76debcb6039a9be329c2257d709fa004095c8666ba67aafc0e63f8e0f0efea3718ce2a1c7255f20f8e4bbb7459f480e4b1c9e7e910f99625da242c64180a8a3c153519882b1b836ba19244e1170eb94c5b3145507bbef996412b10a206c648b716e1fe9c085d9f7e660f2e21b1d1fb58ecdf8721cc9bb318067f9468a91efaeed88462584c340ccfbd9608ff46fe2ce7f6b39030bb6ab2f1ed2dd3fb61794d48131a17e09b664753e3f60bfe441597d0125773e75ebcde6f526b98c0e0ec3d59a406d51da3d1222ddb3940f674c98bece6fc8d38531f23f876e598b65307b66eb3fc1d23f21eaf406b6467dedc07dfd10ff36d0849b4262dbb95a172c39251b2c00ba2d109e0e603eb0addaa1a21dcbec670d05917ebb2ac95195d35d0008fffbbc0047fdf8cec334e9df575d3ce6cda5ead503da56e489d90255e0e872d8b0db0635adec800fbcef58138c39c353144012007337fc63f38dfe4e82723ed0b09287ea2ba6bedbe05ddd4257429aa3304fd764c3b47282fe00bdcadf3f37b730193c343da06c80b9015536be084d3e341ce2b225e037c9a0bea2ddb76ef97d57cec9d7f2601802571fcf5b4143aa35183cab80c6b7ce62fa25c557e61009e72ce9fa5898c6578572fbbeb6591cdf5de9bbbd48ce89f1d0389d5ae22d465b6759d17258453577aeaafb9c6750bc155a29f144b6cb68d014c39ec1e4099d201726271e95ef2e811eb047bb971c1688fa4e8d6eae1350c50dd60c3e296c1a2287e4aa14e3de4c5de840284a9638806f3d0206871856b14f8bd48747bf1ebb7cd5283b09c94208b37a71c4cd38144230744ff9d15054e5484f795c05c7d25774bcc89fee821d095070db7592ddf18a84aded3b59169696daf60c997a2a8ad2847cf72bbdbd0232f73882f8ccabe44aa102d2fc61efda093fd21e7b57180796032d3c35e0d684fa1b1e0bf83fe84eab8d3f865ca56b54128c8328f77cc936bcb65cef8ae279fa62085d34877578e0c5837b374dd106fbfa35f955c40bb5a5f87ac93d3c1ede77b8ed85368e9c14ada827a898b2b900ccb1c7876884e3d3f5186c5f31ddb8d2e9393e277034bff8a0f91dccdb00047d80aa1feacccf1e1749e5940c5c99f85eb4ac1c0256abbfb4264d07a77bb1f7f3d7ec6a6883b937fdee1f027cbaeb4220d814e06b66373768c580f988796132ff93b7e9f2f318133d286e78773f058d0a8d5b46fab4ce94720adc581fde2927fe0ce443577c50a7aedf8fe75e21d98eeed93567833bf8c878b5b088131128a7456b593c21c7f0b8c80dd0012cd65dbbcf6d3f132e097081f1cb04c9385e1444cdd836e2f019702fc856a3ed1ad0188af8637f830320670f0b331342fead9091f1edc33c5128bd259d9a8ba89b95afd984e6411f7a2ff19440657edf17c8fd071a119901de9faccd44b07bc9197b5e8d72bf587a583cbcfc096054f0f78f0f43686ff9e7e95457fded4ae03359cf0db5a98844e7a5b563096f92f0c96c463209a51cde59c5ad4e3180628b7a2912e50362801433629dea3d3a9e41438301fecdb9150bed31b03751d5861f604f33697378c396867988f925953a2994b93cad955f42f8f07048a81f58d47f3ed931cdccd80ae8ad54d063ea0a0c5d3d19edfc72d777276cb652d90b307f2d1021edf9212542e54b1dd7c3d2efd90f81831d8801b68850b3471536298481492ed68d2940a140f8a2b4796cf91d151eef277e2eb0302c64212eba6fa5c0b38a1570f309a2f79e4372d09dff4ca193339f9686c635a7cac4929764d1fbd1a1d09b29416b72a7d3bd0e82567842730562cdc7e6ea759d79eed85b0ef9591761af20ca076700e276985e25fb12ec662f0db86c6e801504857202d6b50facb149a6f445449ce14f62480ae455eafac6bd218873b10daaf77fedf387a71751418bd7a9df80501829ebf41b4edbac835a7a602b1cb334798d0bc7f30eff00acd3502fbf153a91bd66e37a372ed3e58ba073d2a06dd1fb539cffa98ba07cfeee901e1ec28e121069922a5152793c5cf9fc180804a0e726a8a308167989cc9654b3805762c194a693acf66c3c0e34e7dbad11850f1bb6fe1d408364c4cb10f4f3760b686adabb460bf486844bd2e67eb69a10eeed5599f1bfe41e11a59110451a1e914d6dd4e0832ccada086a36bf620eecec50993f63be23304cf04a0cbe725a35afd39abc971d049b65e0c4fb18f285d382256260eccfe056b839b8fdd70b2237f7b060b32932aecfa2600452425b2009bcff687be3814dfee877b012bafcd10b851a9e44fc1fb135408e9b1f9f3a910bd0c1dd88c9496b45fad4dfdd97bc51e86265d863cbc328b8ed8c18e511264f894d4c668d178b5d86811e6bb726677296265f5e494fa9a15fe058628c06f87eaf3bf88e0c2036eac63e0c91ef34f50b0595bbf4072f7224f83664695867ae1bb678015bab4035e2e109b949ecc6d66a341839dc7a25d8fc84bd3c739b90a1be9e3ff519571d624952e46bafff3ef676debc25ecaf377055694cacbfe9acfa7703c946da7a0383ea2eea88cef330c855f7dd8a107b31e45619a4a9c5b15cdadfdd01a5891c289b70372b6837a44517e56066bfaa205928739260e66cd3fa8e6925b1b1fbce097d3e0aa33eab0b7c5ea4c07e422449081da0cf1771b30515bc63cdbe82635e3025713ffc0f56d6d1900748c3244887d7c68d9b94da5cea781599621673046ff43f8ecbb14e53ea300921b16e5b03deec222193f52b3b325dd9ad631c6e933cb147d83cbb355ced395242260f66e87059cb03568922dd0c4d4b20ddd90f3594e823af705800bc4b4ca4b46ed4544e9f3ec5dd01fcf74e28426042237b53c373fb7ab2ba08d7784b2ee8dd6548c3a4f1234ae6b8e3d630b52e61ab4f8b031c349f124caea74ad91ba16e1ce341bda6abde7b1ddddcbad2762666762cca329da72292876967ee170366736cda2a40e9e3c2b3cb523d29a7c4bdab1b8e130d5b493ec3d7dea6a920dbbeb0e51a5c55a6605b7125c8d0ce45db87ed89582803e338efff9c748e30ba310384fcfe1af1f7268c5fc5f7d3cc280d6c903cfeed9d66110a02af41a2661128815f02498b1d48b0d8a9eea79a54ddea152b552b4e566889b5003f6069e273e49ae9437ae5d59ec71ec0cb865a135693202bc8c646a88256a0cf712a8b1d3bd57345b148cd4e53171af122dcda5d0af1529621a0edb20baad2accbde7b45ed1f02433195802c0af09bd866e6fa8fdf9e2ad2690dac35c5dda13f24f37e2b91a3b890bb53814b40e32a963771978f5264235dc7f63fa82be5645ec084dfd211f57c5693ddd32ef309ce0526e2d226a56f498962c4b7bbc054e8170d8f8855dcff4ac4245d64811a2e580dd662575bb33a2d72973736a41bf55830ad71148335787eec433525e068093e8cfb2bafc5b9691c3ce2397dec4b1402682a8103b420be13475ad71ba9f8a9aed8d94f7f7f5061dcf987eb2d3c592253378fc14d0a3aa0d10ca2cbc59fc1345ede6b5e8b9aec74194974712a33a46c853cdb52d61c6343c784ac5e6f77be5263de5ee219e9bc670346edf8677756635c386ab22913393c9fc2ee7916b130897a55e2b9d322a1185212556c16b92e45ca748e0eb0b0422e27503822524a7c9d23db635a49ddca2b07864f2ab00c0cc0c97e212e2d35db65102fb97fbfbf135a6cd391078f7fb81c85cf2b7f7c9e36b0d39161ce7f4b5ec9a63392126d2f3b1085a10500c2cc860c8b24ab5c14b996467aa3c1a45cb8f48e3d9935ec51df6973fbbeae3ff0bd2bd1f2d42386d183c2ae4d966796a6773e4da07b73b34881c627ecac67a7bf3ed229ed927582558d1c75fbe91588ab00b2c160de0a456df45981f73dc98e95be68899d8d13c7acf6017f02802fcb034e168560d1404c5ff9eec40adcd935180b9c33ffce7eadbf6e571d741dfd3c0e92403a109ff6ec9b7f70535c6df2c1e60537fb3fafe6c99ca6c8b0acd6c2b6756b410630d3b060b3a58d0937a9b042b6daace8bc39a0f87d122c38209d96553ca6c745d74c6ea889b7dfc20508033b81c9aa083a0c93874038905878fbd84463427651a32acda86d16cd58f92e2bdd0d97a46866ebd28cf428accd2463049144cff229dbf3368a6867c3c2b90efd2dd5199fa10f547bbbe1e50e0951c1bfd086d5947976d04c2fe4977fe644319cfea2112622176327f4e129714d5dbe33cf8bcb95237a7e510e1668fb7f5c51023ae3db2b8ba49ddf9f0eca1efec402cda3c2d054fd682187693578281022468b3a6a604adfcb66b6a718344fd0d5a1ae03173449ab78a1cc829980e0f6dc5d91f8a07d1ea32c31b1ad785205e776638523f788c314347c11957bbbd80b374ef397e5d3a31a105daf920493b9bb3beb701ac738bf906b4fd03ccdbe4b09f923e5551f7241f43381cb5a27602af570624c14db3774dbf9747fd800249c192e67e9e63aac9cac9e48057fdf155a577fc35bf9134317099b64b85a8a5744ba9b440872ef8e6d7abf35acbd04861c2d59b53496632119b0007a89312cfb97cff6d4f57b2fe35793677261abe12b459edeb996f74c30650a0792119ef9700adf973c00b3537aaafe3c9b448707c100b30a24d3f012d414f36cc291b6af2ff94cdb0f76cb6ea6b129f466b702ccad0fd6d8b99835ed8e6cb3b481426c2bb11e57e117fe590091c4229924c34ef030c1144dacab32ef4fa807afda534eafe958be8892b6e843f0deeba39d2f711851a4aa693feea33fc437744459e105712a2160bc5eb054c35c35b424fe6c8760c4bc8f925ff452b7913b4e416749ee48386d36e3e6e9af29d9feafe3ccf9cf42194b2d24774e9da182a59e14268bc9eb81cc2dc0cfc232d5362afd0da1862d12b0264a62c0a9ccce61648967759e0359ca7f0ddc99e25682de13876c551f23cf4336f5d88716008f9526af3f14a99e1514872ca14569d290c9d9f36f655040820b1255fa0e97b3f80e17417959aac383f8ff197638a47624a058f704e1439c13fdab94e900836fa3911a13328a2347507b0cb4ff63c686a122e23b8ba0ee8cc21966aa22db1b92d58c913aa876ca8a9dd60853a4dcfb760ccc9e50e710b4339eb01bdd9344f2a30cefa95317b017965bf447100b703a07451085657c8df483679788121d675c2820ae1070248b46284036925edb7a06132ce2a943e111b585123be604d8ecc0b1777af93a540bc5f9e1e7cdc5799c65d7423fc518a862fdc698c5be32bac20fbb1d0f2dc41d011d034319fe02c2154e064cbd204fc0860cfed60aae55a8ba9230780f31f8a9631c9acf94ee20730242f7b695fca20b002086a4e04a300166947cb6d7812f754e5a83d5c76faff07a0072b7500c7b125e2e2ef15f6bf2498f4b7812d2e83149ea24a50185e09ffe5078f9da944a60a391365c0ffa96e6055388fdcda03eba851ac03e5d736651ade4aa54529d0612cbadc5c4351d28a5b7dcdd890d0dcaeb5ad2d8b048e1cc8a4466c149b577aaed0a357792bb1c26419fc750c681e2b5024b1392ddbbc84b1c501fa5c622ca68a4ecbd62740e5f96c65387846bee10b14382e177af167c12bbd6756a15e64826bcd3df531406022af9e179ac496343dbedd3cb7532cd23c6f3f429a5ed1d0400f42eafddcd34a6776372416486b5009cba5ff45a70c3104aa7eb6a54d66ac272bb83c786e4e3364e1e786f57083013d89721fa7e3a192e1b8633c40cdde621615498ff3c5f1793b704a27bc6f36580e6a2a5c1799ecf3b2fb1c6a516ac4a12c45c7ef87f19bfc750cfffea3b64c34e9d7fa75c8b7219b70d92c6cab6c13311850d18eefceae8df35d00642f3a7f79a8de06605c93fc23a9ac45a419e4308a89382675c9e79aa7833d9f52f895b26b0309938a7af24520e061c984d3276a136b5d094532d36d452537325942e7e7ff4c6ceaafa25ffebdcfe1b5b0cd7e1fa084ab3d18713cc8295edb83c64abe890725728f5459c01f9d46595013c41fa752db2c6aa6653bdcd0552384ccf118ab95d9d232c06df5ef249132161791026c1500853755faf0fa6caaf099d90526e7859863de08dcca5a5139b1ee568ae9d9e7f0093971d883122b9900b382b612094d05ab9746b332212b5aa40d02003f66694b1a77f52af97420a43f2df22bb29a4111a4912bac8fc8bbc4251de925f96561df87e38875e6b9fe69c1053c7c3019993f1ee0ee11504cc0f19810b0a43ab4fc2b76e71bd1fac498cec8a12509e856d6011e9881304cb23cbae4f84b5aab03de24141a7fea7da0389e61e37497a878addc42b5c8dfff061727bf557c580472b3351ace57e162b1d731f1125c5ffef82e93bf7bf6b7b4de5491f39cf1de74aae13d6e1f1fe30235a49080046aef913925db858efa3012f9244bac90106393713e26cb871b5f27171c1dea719074323faa6a5d900409e28500b0f057a3b576d045196118a634446f23802d29acb8dba93922543e846f905a3e5b1470d30c9d95b808c913a6ae0178d0cf0d8481e2cc478250ca4b44e333e3d6c0dfd7bb8cdbf49be5833198708041afa67e2bab4ab2779fe9c34c4b5f5f3be759a5eb9d9ba8d50671bb81231317858daf82ded9663245d1dfcb4164ce8d738f02ac146cc7d008c1fdb7f54c0f4803a0dbe5f8c3e605f2c547b89fe3339864ed1b2da0bd991ced317c40376c5f2a354e4627881315db417eb20a85c08d1bd5c4c6955273084307a62b9e42af0f55262e54c5981a6e110df34e8e1fa0b9904628adecf83e4fbb2866f491c85f8106aa1030aa96146296543ec21e02945003fba134e46f6e25f393dd0a6612aa102cc58965e9550e48c389c1c04a326470cebedb427e442f8b848df82a8c5d55eed0d1446c6e5d6e0ccdfa2d219019ad3d400fde350d690cf04d083844adefe5069c7066b0ea24854ef3f5853328d7bb2ea735b370eccec5f0f777a467c98f040c423fc4c3df237ee8b1e9cee6c859c860c0297667809ffc0b73af9815b01384834fcb56d3e782ce11ac9124345a3d34fc22ff7e0c3149d66f7bc8feb9743bb54405ea88902011cfd4ed07ee19c6b8e6de6dcb08e2e86427e5e675dff052652af14d7af936f032bbbe508c54fd4dfbd16921e7b1c1471e96f3a521b911e32a5888b5a4083be97a3ab843a1dacc19e60f161fd84c10fe94c9952829da6979a83ff25faea14df6f6d5c876f3103a8a93deb51a3834c9da185355a8b189585f7c71ca082295ba29e326e60b9d08ec5386deca533a03b903541380cdf616135e348c6b09a5cd4a074d7c2e3d794e44a0c4ab8537f3f53b12ad2c2a996c5ec0a1b6d9a25d920a782bbf2120262a71f816a66ffdfedf5e5dbb868d37517607e880d697720a0119077b19df4e9e8709a177bd25508b00c6c79d4b1c1ca7f607aa7b18e6f93e40c8ac9d8705bd9deec9626cd4aa06491416430f1e204177b62d1bd18e9fefa0da4d6e032f785851dcfdd3e2a2058b1811aaa976c30a067d9accbe4504abb7c2a39492889fe518dc529cfb481320dc05a785cdac3fd225936716b149620ce252d079b0f14a947bd7efa17868935b6483516f7653fe108e5c1da91db5a58a3aaf920ecfacdaec9cb997d80860e966fbc4c2cdc52f41fd3dcf8bfd70fc4a28f99128b1b5e0d412c91fd59a9deea1d9b5170e92e1ba12064847d72f9ed9a58874ce906252c6679851b735cdd5f3f17e35e0ec6117e83b9718e85bcfb608428bf03df02e2950a07e80844403f21b8b0e6ce349c5ca2c260aaa780949050d2d9fb90f890e0d33b4a8263b0f3ff72d06ad34072f3567c85a6e301d5b374d9a78a94e5a3523735e3f9c2db0734aa7c7c5787879687b485f1fce35966c742b896aee8f0361d98ff221b58f0746a0bb4ff1443b1f21eb26e1503187f52903a842a278d3cccffe3328876cada9acb5f109f1679265c39ff58a2f5f63162117f63556389d00b7ddb9cfe0346c62344ac0c9f130294d0178425920cf8c5623e09bd387d8f1d1bc4a7df18f8f38f20680ce2734505fccf2d7e13a0fa68ae5f7f949346bce6896c992cff605197740913534fc38ab50d679c92bdd04e6682ffb8581c3de476b13bed9e72cc7807e6ae6a0f0fca5cd9a03086c1a5971991a7e0a074c128727a4fb8e63240df8d4e87ba2077b50ca0c1de5ca6b4a09fd2d16022aad1342b0dfcdf706d13d433db3f592bb467f911fdd09969d1fcb4fbe4ac156a4227dfba8999642bbb7239cc6bc02d4e1cc5ffa47642171b9807bc2e62027befda4cf5d0b994e36b110580f40ae0b8505bac989738219bc3deefb4fb85037482687f8f196ceed78ef556907987d952b042016046c57e36e47e9d5e4c71d5fb7bd2327809e4663d75a402c9b6d89857d2d07375273e3142dbfbd1c4c658fc64e2436ad5d8a714d9a626b8c6a26c3ca5cd8b7bc75162e5ea63a1644e4365ed6b16107b8462e7186586b0b2c43f526f0656c544b70f5e59e809462b1e00853cae8bb57e39c55abe0e2435c53bf68c0dd39e3bcf6da3250f4ad1d47f1e9813b22b109c9f2ca6b06b362f8a0c11774cd90321561ea5633ae94828698a5b87a338a0a1ee58aa9e3759a68ebce835ab5bffab1cb564a704c220e8f451d02ca511d3a61071e59d7a638c6eba218fd186a9abe7144e21dc862d71cc14fbe19898710ec7321917cb26df10994c2d4980eae0c9c512cfaa4068fed52d0c57c9dc3fe75b51f333210839a37ff90e8508bbe215d7fd50cc4c90daa00e10194fb660fb38b9a85be80afd5232edba10ab7c98dd8e5ca4eaa6fb7828b529711682b92e5c980bc877a83b9d7f0b848181110e370bee7ad434463f90c954705032d56c0b86305b83f61642daec190941c8b687f46d70c0c0cb4d627c0af930844189d3df40c1ac2c5e0f19e598bea0d6405cbb41e35fd850f26bec7aae8873c883ba0fe69c268839681796ad11c090e891f6cedb944fb4118092d8b4397dce87fb4755b1ac74217d12597496c41370c4653df24c3df308c2c9605efcaa3e8fb55250eef97ba85bac046d6aa82d249fdd348bae7b1f6e658b8072a2aae3dc22920e419e656810cf8f80398bd4dbabc4df89b4b46328524ca43edc03677156dc05a215d2c504db8b28e876a1797ae0b57de85f634151a8bde6002948ae1f540fe4d5f53186fd979992d4bd95c2fffdaa0d02596e17b439ab392ae0f50e6bec0af1ca9668890801d5dd1c9fa69c06ef9fb04b9b0260e8279bf93f1fb0de27d60b0c0d07329f986aa78e6660a8d0785903e7bd71fa74fa1ad87a952a22ca0c215d035817ef038624b5445a326d84ca96a7486058add15801596906b1d78381b9092f504db0008628d32fbca6471359d445a248c0019ff3b8f6157b7b45c14c76ef673a927ac5e425c0a2cb71cabda5b2f323aa037543ffe1c84c1833ac61c016bbcda50b219bca8b2d210914e2e6aaf5483c943fd4e7c68228b26bf751cb466667a66529cc4c19812a8306a25776f5ceb24a601baca327cbaf470a61955474f3d9ae0aa8fedef83fd62d1d1e843a835c3b993a00f09faeadf015cb28d01b01fe5ca49864b667ba0cf0ceea8439d39cb2a9ce71f7b3a5a9b2774141bfd68baacae775d48ba40cfdeb1d1018f77ec090a44ca5f4672f8a1b6110421edbf5014dac6469e980d3e207f43e25353c1a7d4d010974e145a7cb8a5e05d7d79c7f4eeaacd410dab582adff9b9ef8ffadda3682651dbd8ee69d60464070908bec020c622cc00cee15aed981698ca7d413b83b78fc1a2e6c85bc11787374342b243093d26301476c94e671d7008c7f076dfc7d5ea5d765cc4e82b0fc85ffc8c6637909142f4f3fc943170002ee6e4ffb8a3807970aad74a2aa0faa7a3bdfe4c925757497aee9bd3c1ed174e3aa9ef62d6c8df963d0272545e3a19aca824d7ebb9e22d1818f67e98d5dc3e1b3c45fbbaf8a5e053db3fa77af51d975a22ab70321bfc40af2e3828542f4236adb4755552c9437ce84d39332588d33eae8fe5119eccca99d74962a3c9f09ee67eea15f24e58fdeb5536db348f335c67ddcf19dfe848cb1bf5f2be87f5641032816376f9dd3a983822463898a9ac4fa1c88294747715251561bed2933c08be3c006f05a7b3fce71c179e9fc1bb1de53392ba96d0080179c058c441b9e370714526fe5a01dd1ceb43fdbd75efa6324d799955a4cf9441f50c25e456c6436f95fb183851782552a4e3ba51d2597225b8f2b84dbd01ab4bebb18a49712ea84e86297c4be4e394a3d88646bc8da23d09373a2534fdec05540253bb9435ce0f9c1ee444de9557f880d3bc81ae16dee47085dec4022057b0d3cba15569f542939f58609cdacadf9d5055a9b123be4fdde7985a8a4eee0d37032769527587fa1e09cc7122c8323d1c7ca23a98dd63e970031d8545288d6224c9cc034e60731a9212a89fc6704e67f4ff3638366168abe7c5300fa1dc6b95f56e079cc9b97595750031d8d346244a5619031e924c5eaa8ab82200032737621f4e5684fa19c40de1ac18cc6d416ca42dc59e52b07e1a69f3dca3ce63ba55a4c1a3cc761148f83808c62b840b9252ec3fbb2060b9e7c75f6d94891722056d24771bdc8806d3c5124153b2e07ccbb16b02987c0e0ad70a29b48ddba6a3201ed15f4c055ce2b9e3feb5a7ff082215587c9d891c9c02c2eae1c5efd8625c6535ec57be13465f5d4b5acff1945f0a7a11b0e41546b1e8f3a3ae85fff4ec1c89c2dd44398d04e7689464ce2c631929b4652c9e7645684a0857d6c2cbbe2363496c88a5d05050fc76d488ba7f953959cccb5ce8e062c203df001a542cd5e3ec4fe97eb15619a9149022c9ad71b9de36a3a5d257a0d3ccca684666a703ded560df69a53ba546dbade63093831c306d04bf0ab91ef00f96e22edc0e9745ee374693ea38e7c9a66f766d395be678036af3520d65e817f05c7a9e4b5ee15decfdd2c40e6a75789c112a2cd02da7e7aaa27b57b50626a8f989630b47550d39efbdf17d08da5e066039e2be80c51a0221853d0d840fea4e5d0a818de468b455d1a99b98b69ba89c118ff0bdcb250fbfaef760e3eb3098b92512e0cbf2cd32052281727ce17a1d55824fe953029190dab087eb179c5981c1e785a2400bcacfb5fd63025983d3841b2123e21b529bcae8f9162068db42e1280053dae8de583b3b713f2f78738df67188b7a01f40f0918fa1e51ddf85757ce251e903bb996d87ae4c845a420e6333e0a3c292521190d1318d4840446166d1241f4a837b47179ea554e144991857037225cbcb44e782a9c08d58f166da0e7dddd3b05ff2f7cb5ca53e445a3e6c06a1e3e03c7912619a8418606b4b5a6d8df274ce2aad313d5ac4305024352e9219046a0ec8ff68f6d7ce075490b9bd41fc9940f8a73fadf6577a4742f08d0524f72500d944e950ec1a7b45d2bdd46df4bedae251dbfd53c97a4d3c746de80214c46675522ca77fcbb13500597ad629d5625f9c2ea4c38d1334225b00ec20b9127dc4a15d65f129f4f95668768b51b02341df16c8430c163878ea13ed24b683b993732cc35b987c78e2bd25f1fc6ee4e152bd489baebe997a247076d380c092acecb79c91213a29ab370776cd2c3b3871204cd156e2ce3ff091fd31b65cfb824f14f400f6d9bea7894b8e2744e60b68cc0c05084c024e1cf64867a132f7429c9411fd0c9e225f4828bd9f523bdf3f5a8c21b09dbb8f4770bde3a488928ebdf0ff3d2c300bf6f4eccc94c5b1e4551ef25d144cdf78fbab3fbf37f01160ecaf91b153e6a9322575c082c4eefcfcb41f606eacbc87e9ad7022a0916ca782ba8a2bf4f408d135a1605c85ba1e211a78747ffbd1ba7c36589bb7cd801166efcf55a8e2e9e24bc6cbcfbf1ede191a777fb4a6a67a39bb92498b8d06b287882c859d668a7757716572334aa19f4624fbd7dfb3cd42a34f6f12da60c850a21a2e9998bf4256b961200e57621dff71ff89860ab8f1388b889e6836fd22f120d9f42e08f4999454c0523038e056404d989a3dbee88cb59a57efd7c5bd3827bc85a0bc8c02953ed0d7dcaa108c115a48ebe00c19ac6a0b7daac1b1468d6d8252e93f061bc381523fbea0f248d4de1945c704fd01510d54aceebfe791ff1a44852613dbeddecf2e7fdff1e981cfad3c615f65ec413546c787497f2dadcb2022a6a2aa463bdef07ce1f303b06c8413357261f8d662a71d344737504ba69b8be1ea8252e6838ca8936dcd7041dfd6a646d1cc0b12436f3c89a7feaf8f57fcd34f860017798b1cfb22395d2f2836dd02988ac37178f82e5c543a98b8982e59a0729168c866526ce53173da0b9300e6c17ce85a74032f052a82142b290911a0edb93bade04baa6e061678107143823fbfdbf3c06c0dd0de671ab6567cc3ee3a657e7ecf6d10acfe4ecb89f9698307623643468e0ecb86d9d9b18db9ebd3272d674b43d42784367f2454b3759ca6bfdb30a9001d8b0ec48ed1378d40a33524c3a81de9f331833c8a3e9cee970f651dbfce88b26ed8f6a4240fa2907529e11a44d4b35483310d25e3c006f2d7d51432acc216d43a4de48db275262f925ac1669a7e6e9317fac48f5a8c60620767062e78bc4d93c3fbd5dd47a332512d2fda7447f76a7badc77f862f290ee4b89804280f591a6badc290d3e128c46b681e69d71c3ed2a3b6cc4c530e0479309d6287b692e08529de31d830126afb4a4652a8aeb3303d3547d00b93b661edc1b226d6d79a7761fd1e43264a090a0d8e9cca3454ff74b8257714e299ed3c6f5b6648b9c26bc78ff09121b9a485282e4c5c2b240129ac0819c8532b0b9cca4472c180836f7285d09386581375779cb546d4c8ac6043b3255ab1035c1d6e07647f453130bbe2aaf04e5b7bf49d0cd6474c25a75c67901a23870ae77c32e61af3ffea207f8749100649c3cc054c36691d4127bfc7ad18e1f018ed75f0609001dd83e41d6e2ac66964c248eccb2802c378c9252146c8674ac23b81fc13b0089add0152a1141a6472370df94ae57e54ab1c3a26c55f24a1dd0ccd360e794f0a63987a08d50ffb84a76d3689ac1211ea5d8917dedd6abf81476dd0ac1c7017aee6abc9ee1a41e6f5edd0b89b9d99983857dc32914abc1e20c29c2c2882c21923c947c980e43393500716ba97ec56ce33fd4d01240988636baaf5aca649f87fe606606fecb888b6a7c62dc99688774d648982ce1cbaaa3282467050963b31d95c29f55d427d4166b142f767a695ee5be116bfc9455f405c12a1c3e7dbb14564f94c73dc0a7af0aba2b89c172b2fe5f9c059c0a51fa792481ba1fb6f97faed51fa1e1857eafa818290875f34d876e2a17e5adcc7bebc7b01b941b9934c2e4942e9064d836f976444ebff2dd6c52f44fd9132f4a4a0ab659f052f5b80e6696d217568dea33b7a90302d011384961f7809b0238ed7e26a28492676e4368ee5548a3f5be6b7c3f4342ade03db48e82bb247dc95b52638fc38802881d0c2691f2801d0be3a66700ca06e22070d3987ff4d44db8f952832f0a74e43f77b7874bb68fa67435fffb3a5d98b034ae7766999e8c9563366a70d86110deff01dc7cfae0391be8ebad2fe2d816a06daa82ad16587e43c91dcad74b672541420d12c50f82f737de005ad7235436f404e87ae5b3ac732a92a34bfa3c20f267fd4c7ab07947232044ed716b52a0eee6bbd8388d7e4d15c904a4ac17bc8a7c57032a9e8e8a5c30ceba4a078e335cf0db5a938e3233d9e16a503d741755da53cb2b848c86c25199e1245000f93043ed0b8ca80200a4d1cd64118869789d5e90f2b393c38faf9492f91051c29563e737b8d7c5c704dd199696ecbdf796524a299394015b080f081b083dddfbbb0e92e54cac171d082fc24500c43d3192e533573280171d8acfb0e33fc3cc2ba48f17e79700bc38bdd079718ab17b78f8e26c5abd38b570f0e2740200fef30889f313080d369831481685caef5a062fcea0a9e47b518a91645422baa2362f4ad98e17e5171e456408b2077fcd8b52a900dbc50e7436cd8b9f6366bba86dff1cc9aa4168760028e60680a22e0104c59bad251901c5cc0050d40b004700c51b8d480450cc0a00455dc4a5116dfa2180e2cdce50208062fe0014b50730a84d3f01a078b3f3880050cc4440516f10146f68363a0028660380a22e402b1b6dfa4240f106cf7c4031f780a226c06bb6e9077961261628661e50d41d30316dfa406694857f80621e00286a01f88cfe8ecf70cb0728e60080a2d631c2ad4dbf87d175ba30baa20f0050cc1c80a25e81a2ca69d30fc7bc012866ad0128ea1c5054c1367d1c50bcb92ebaa26f3300c51b15088a2afd81a2a6ff44f90614b3e6018a7a0798b46d40f16657998cb26c0d28dea87480a24ae70045adaa59e300e9cd162d28de6c51dbe055ba1615499d99a40227a67136b3e19bd31a5112b09766d4d42475269426457369ad198cc2742a8f5dc49c9868100d923ad9680924d86ba63edf5c37cb3564a1ecca2d2525a9c314862ce6e4464652c777b7e1177e022761240c0b1386c32d5c05136121ec72fa7c6f4e6540c96257264c8a3109690ae5ebba3f5ca32b044605a9336363665032398620757cdfcf375791ac601a93449c2d933a54db5c446c2fbccc10238707fbf9ae9f6f4d4657f445215949636df91d8d4d8fa40eb67c20b2c73f67b1ba7b752315f46e7bce0eda5fb6a9267b2d49007b326dad71b66912d4a63ff67c516362e747142005d8b283098a40f6fc1f76fec654c294404ca94bd3d6db9f7ef69c2a5a7a983d147776a84a2582ccf6f7917252ea5cabe420875486568656665b9e200fb1b0b267ce13a6e7d3393fb9bb3d6d0e91aef9405461026da74892ec49bd6c31f7b03939e80955ab5beab9d30f7e53eae1299cc4113ba16def4bf92738cb47bf50d65aa5fa705321fa725aedc559dbb8ce4bc9e899ba6bd2e66cd0dcc081812a878e1a9b1d3c6efec3ff9bcba0cbc02b92b327160f2d0e4e475f3467ad73cc3407e3ae38a741e7f3714c74e57405e6a17d24586ca645cec5b99c27c8593e6f61b287522ec63ddd7baf067f2f0777f5f7de7bc3bf37c8fdbff7de7bf3f7b22ef8f7de7bbfbf97e7dafcbdf7de9abfb783cbe3efbdf7eef87b815cd5df7befc5e0effd7175fcbdf7de1c7fef002ecddf7befb5f1f70ae0e2f87befbd37fede9d5be3efbdf76e7faf8ffbf3cabd7706e0de7befd5b95368e6affe7befbd3deebdf75eeeeffd1000f7de8b736feaefbdf73e8de7c1cd206783202c9e0e80fc188000767c0440a7070034c0d10087831b2b1dc20d34c8c1c9c0c6df70cfdd703540efdbb8edb79b8d9bb1e96ab66db3d96ab64df3e0766cdb66b3d56c9b8c6ac360db3ea523874c25c340e6d1641be3138d8d0e870e372e8d8d17a7c376d452b9361db455c964797399ccc7a7f18e3e6ee08ca6b5f6cbe59a1dd49f6f0ee394cdeedd14f7b6e7eddf896526f9d898eca1201c398bd26a85d62b58b687f3de572ad2a702ad5eac55b6f7b548f684ef7d6d39ab6697e76d6086f4a9473c2f5234b677f35e7dbd9e5e605e514ecec3a30ae953953a78915ad1247b3e2467d5714c1ac791e98af37c11d2a73e0179911a01933d35ef3d1d725695c968d191f754c87974bcf7148af4b1403f5ea452b6b703ca59d6356b79ef609cc7e6bd772fa48f3d7ad1c1d81e064bceb2af57d3cbcbeb69741e1c4e481fab248017fd0925d993e3e52c3b8eb011c953bdf7ae83f4b14f3b2f3a0fdbb3f19e9579900f156d2fbff7538cf4b940d3879d63c2c89e1b31675d974be68272cdb6477385f4b94701f0debbafd7547a2dbd9ab647dffb7984f4b94ad387d5797126b1bd1aeffd2472d61dc76934bebc991e2fce29dbbb4fb23000785186b1bdcf80f4c140f3bddf80f36cefbd0ab207bb64ae984882b3b0cbfb22d2071f4d1ff6a5922702d9de6b04e7d1defb10644f7def43e749bd062f6eb0bd773c7ae08b747b9f237db0128eec71161ee9cabee7fd943ef869fab0ef79af81e78197b4ab8bb2b8eaca711d4d1fce0acf1165715cd5ae97d2f4e15e1d286dbbc964a36c94d5a7e9c30101a94fdb7e9551d6368e553656d9586540d3677bfa01e4a2acedf572bd5c2fd7d1f4d9940670f4a2accdf57abd94a6cf762400a56d35996c948db2a7e9b301edd8a76ddfca284b1b472b1bad6cb432a0e9a33df900725196f67ab95eae97eb68fa684a01387a5196e67abd5e4ad3473bd2511a292bcb64a36c948ddbfe7d9a3e1a508ffbb4ed5f1965e571bcb2f1cac62b039a3ef90900402ecacaaf97ebe57ab98ea64f56e2e0e84559d9e57abdb2ebb5ed63a5e9938fa6064a948573d65886c76d1fe7e39e9ea64f069a3e641508c8b7889ff653d9531995ad40fb3494d98083715634c801713200410ec659d9f6736e523c38ee75f3dc6bdbc7f9bc97714037321e321907b4ed6750d3e9189f6a461de3f8b4ed833b389beda554b3bd746c2fa56dffc360c3b1b9b6230c36178ecde56daeed68dbff1c9a4ab6016120c321f3641bd0b67f6323e7f1c9c6e88d797cdaf679dcc034da4ba96ba954faa5b4edefb8976aae23cda5b934d7d1b66f53c3ce649906d4b564b22cd380b6fd1a239dc7a73c8ee3530d9ed68b872fa5acc197d2b6cf693a2a9d4cbeb3ebc8b52d78b4eda738a11c3e95641968fa6c2328aa545a63d9ce40db2ae98f7b926de3f4e1be481ff66d4bc5c5b4903a761bb77d95f7816f1930a78aae9b6cbb93f3881369c79c47ecfcf3ef590478fb53698e7329ecaa8baeecbb5c1f3817fbb8274ee6a22c0e8cafec87d996737151dbbab898d4b13ba78a1637d9b6bfc99c07cc06c5c168ce5a4b0ec90d50b995742e666dd6aae7643e2ac3e2dc323f559a0c78019c1054903deeeeeebec3f3c0531f0a1e62145cd91c6c990505aecde974f373ceba757fcf885377b73216dbf5f3a79d6ea79b1e50fb1944e1c62b32c116e6b6e005a48cba165fb88eb65402e8aa3fc5acd0b10279c57568e31ab4f15331d2474a1fdbe3a761644ff7acc9346663c7130a8cea098bb429f519ff6cd2a09851e0e1d2fbe0c88f15ea7419e5219612fcd1cd8fee681af7daa6713bb6d7669ba79b1fcd5aab7e0cf7dfdc600acae7e119a9ef3b3c112a131393b7349d4c934903b9209d134dcc06e3c73b4c745535d734cdb70f3450771ee6fc6108c4cbc60fa429c706cd01e92efe1adffcaa5d8105bd2b2075f05340fac05545592af80abf93e8318fd9e0094e210cbc22149c4473c83d83f7834c1fb9e3e1bcdbf4a9efb258de728c93e1f08a5cb07a78856cb3baedfa2777cd5da76d910034dcf950f594898eae83c81eb977360d695efc1b3830f091db878ebec2ab1b9a46e5ab95d672784634217494d19ebe9812e9c8b4f1774f71784552600b73cb8062eabbd75ee519d1de035b981ef3108b1e63fad1ea400decb4ea7d0b75e74722b3f3dfe764073e7d8e7e3a609a82f0f45e58464270b8f3a1ca5b9476ef19a1413ce4412226991327041584ae38cd30464a2e0b8235d1592daae17970f347e319c9e01c0245ff4964348de88bf7690b48c82b72535c900e7ff8af52693de60ca1ef4feeb989bf7b7102b16b7846f2fbcf18394f77f49af144c8294febf09b4393a8a3e338e6ef5e14f2c022fe4da26f0ee550c18f82d4c1eeae4e88b350a02bac04b524674d58ca1fc0182d4e214b97a40e7efad151882f31fc9f23c7cc0c550167d7b450b73f1299edf9b387ce231ae1c04ef4d745b6f7231c38893238877c0a4d21222e7bceb30905ffa43c6a26a78c02a5ed15f1dec116e607f953ef9e7f4e7d2ccced81297846dd0764fbc40eb60bf18cbc4f9c401c89418c266c638c9fc39db79c07c85d1a7822e4f7bf224773d65a2ce4be14e877812d878aa64b7404e188068f70e0053cc4cfc2dc1b98c28f17f0a3e03cdb63cca47ddcf6e579c5c6184856d24baa9f4c94557de5df650d631ce2e778ecd3af7e8d4f888c64eefc3b94e54fbec20f66e3a8240fb33106daf88336fe1f4fb4e87f57a8fee4f8bd22146c61ee0c6ab0fb68cd15c6390b37701247ac42bbfefd797fbed350d09eef5836fd1c22cd45a9e09df781ddf9a5f529377c737cd022654d222faa26bd9484ecc9811e217dbce88acf5c70e127e8338a44dff31630ffda22f3535e91f917949bc23aafc8b49bca7bc11e1e52b08e30412959142657f591b4a45de98823750059813ee2ffbb3ae82fe93fc41dcc41e8191235729ea35a93f66d39cc75643ed2f695fbfaee84ece9be7e37df03c5e9b2bd790b98bf7945e63b9483f1996c86690c6afa741a8bf5a0459fedd8b3524f531f7df21585499a7a5f39d59aaae87f28f34b1ff19887f55998dbbd7828ba93c3809c4774a75dab4bf6c8fa4960c6a830bbfe0f18b4e84eeee43c1e73e9d291a8bb23d125aa44b160f19c4daf7858fdb9499f3cac150884e915c8c34a6352a7be10fa087d16a62355094495c89eedb9dfb6bf9fc881e274d9e6b0e8ef3207e361fd9c1e747d975587c252a9087675905e79ba3f63333663dda657a897c7b111298cc2fcc859da8d5ce25abffead4857a44ea558bc68fad023a48ffa957af926d592a64a49489dfaa2e7b0eb733f5c5aa43198f388f4efd72fb2c78f50ff90c8587fa7a1a23ddf7d3ea5e01cba9da8556eae503757bd4e596d8a7a719e1e3416be4ad39f4f9f648fa493562ed5f99e9e8775856a77f6d01271a0ed3b149c3c48ee0fd164687c04c9058f50f07ead427f86eab306ab874c3879438b9656cb3af3429cc77e0c170ce11b2104e7a9b1e007b66efb41926d2b0d61841c9f5c51f76ad4ae4f6badb2d65a6b7561df2c575cd9a21ce5df0fc179ee8fa0a239abaefc6ab0fe21839e2d0fa5644a638271cf33342e4a29e5e74c8f3e2769473f107ce30fa2895cad0e4a991227845215826c0d04d96208fb88fc4e0c617bd769da431b5fe34f903df9ad9e2f93f26eec39ffbe8d1a9fd42627f3a53e51a5f70557989f0a415436dc092dd0b267befdd944f638cf7dfb53c8ce2b2205c50f6c79c54e20e731c1431baa54f2e7bb6690ec9992fbb7765224e9c3bea863b59c55f3d321849e20d878686d9289f4f22d9d394fed0312a5f3d39f4f73e6e867bf15ac7d77495fcd7befbdf7de9ad4791ff8bef7de7b6ff579e9bda9fb61ce5a7b185e9bc569698ba1d427482badb4f2f39772b6b4f33895f1192dd55a6babadb5d61c1974adb5d65aadad44b22db35811dbf6a5d7df62be9fdcd509cb96599cae6cf7a6b8ed59634f1d53f64c4daeecdb6cc73c45db97dbbf8ad98413ee0ff10bde90e9743138b9e5146999c50a2b5b66b1e26873dea9bce8a6a524d1a7a391d9bc907c2041640f1a6364687fe44abcb1e9933da93742d40ccac3a54d43038af5b52bd0ae11db63173127262c5b6b50a4443bca630a4316734aa540d199f6eb8924249895dd75a0e8aeed5406942cf6b461528c4948dbc6e1d89819942c67242b98c6a42b9bb625dad93b5b4be36850dba62f725bbe4f9e3ee8ac51482964382b1572456b8c8c8a85d64d4f0149f6bd56a98e7bd22dbd9a4445b28238e0c5da43035eac4294f0623d8204aff1fed40cc942c1c88bf50506bc589b2ce0c51ac3086ea30ac99a2e115ea42950c08b748b222f521684e0ba08c99a30105ea422f8e0456a020f5ea45912f02285512892359d10f0229d81c88b94ca7e91f630c4535e4896b70ef0a28b618017dd8c02bc485f1022e38464f9cbe745d7d2f3a2574180173d05415ef42725193209c7599208be3a68d0f225b883657b9c33d775dc10cfa2de19c43f846e1e601ad504a7f6947e62b735aa499f6e339d765ad5d1e049aba6b1d91857156caad56854a1a6654da3d99a06567b6f9d9fa837e6b4ac69793e0627c6396bda764268a9bdd652fa73ce3927a53f2db5d75a6bafb5d7da69a7b5d65ad05f033bfb48926ed68871d2d68a55e8c40e23a163bad79b820e4ed061462bd44de3e10d2d7a6aa973bf86ff2a95be5ef40929489d8b8287f77384d0a2def763f7fe910d50d6101530207d24307ddcbf7f5fcf21b13dafd8f367128b9b4ff82183963d74df9f462fd0dfee6f1c0625dd91308d9fa6e0f7526b41fc9c6704532a653b9b42e5e94634d28b1bdb8dcfc627e5782754eeb49c2ad5dc4013385054a924b881db089a90672c386b8cb2868c4e20fd46e0e40f710a66106824db390f96c9f9358953923d9e13b22725f3a91428cefaf2b8fa929d95ef7747cd987bb31b38db665af5219ded189d51564542ca39d248cee37bfe48c791ce2315e6e1b45291ea8bce26fe5918a82807b3e7ff8041d797f36cf4079a1fe22c1af071e813f5329b3c9c4b37b468fa1cfd56a8938629da602df270cea4ce3c729f85b96945aa583aea3cb282a92c903d5eeabdedbd979f98023d707b2a6e4f917eeefb5cf7fdc61487c1b2ed3737fe56f02d526a86ec997b3e3d63b26a8caee6cb6098d5c99e55863d5bf8452057f34db0e767d9136a4f2e629dedf99c5791a4cefc9c299a22c5f6fcf91628569f8a64aeb9fde63c582e6da24e1e4eca654fba14c659f3e9cc79f03f8eca662676f3e992cfa7239d383634b469be3365cf033ac58abf89e59b41334916c6de2966e447328ac8cac67fb7ed3e12996d04ff055b98437cfb8ed0df6610c8c29c51b2a788b3985a8ae9a455fe10a775d3bf20dd860ce930eec08b69e8af02de2dd01e73ccaef4936e7ff6f0d07bbafdfc193567d307c8f4d1e56c247828e5061bbf9633b246eee654fd9c01458d6303c59f3efa3529545f8e5c6dafa596bd3d069f137b7b1c05d8dbdfe8607b379201c59c02b78de6dbecdbf87e6c3ccd097b1349d81b68e3e5a64988d8b06103c4913a1b48f372d31f9a772109119ab79686e6b7ef7aa0b5294d0992880d319c2158c1eefb8979cb9ff4ab88b3e55b210090b187b0fce9729e2b7fb65c30611b412a09d9b549ea95c8a46472a8294b6b194957a9d4cb4c29864bcf820553f5c019811f28394ad1b4ebcb3ae794b5f370669a5b3fda39f6c22581edfa72f7d8f291d81739b03d10891cb37fb0f162fdfca1f31cf13ef450a652a9542a053ee8396947322b985f4ad943aca037f39904afbef74368fdf29e013d1089f6168362b69e0732bf713232325aa60192c8e55effc63da765b4d6325afb8ef33eb1f380842593c88dbff1338f648ed95ab6f748e698a5ed3da62fa33f2d23da2da3c1f7d0f33c24f6c5d4b740f70f1dc0f172d71a8fe3fb99791792903003fe2469251909b9806078a145e4c7850f8c59420561948186983144665e49122233a0129abf910291d8f7b81f66de7e343640570e22336093999f81ed0cce7c3333df05e8ca312801a9e3afe399408b322684fd99b742ea27765063066ca222a264e66b804d32112599aee42b513203bab055d1e1081b6658220a23221f904e2f314a9a9eaa4843082292aee42ba9f14a667e03945503d47425df41155dc997719926ae8a2a3bdd25655df79a4384ae4fc2f431926082ac596bf7604a1d9f2931498d8273ed39b779f9dccd6bda8df69b767301674d28bac23f299eb3e6757c8e573d063c387edea0791b357ece6821431210c20824384f9680b3fee7a7e03c746aee19d1344d48065050380370c240700a7da0e6f1353cbea6be6351b4555b66c1026873b5dbf1dd3cfdb4ffeffbfafdf0f6ef979ffbee771ff7ded77dead3fa6bd4781a9ac7f1dd507d187c8e4ff53abe1cef9f8eaff9fc6d3eb96f7e884f580db6d9f1e1afde0778dffc64a2fff8e70ed0a6061473ed6abe9b9f3b87082d4ea809f5a263d9f8455762e3175d48083fb7e39b9b47cddb7cf58cf0f89aa73cbee639ef7688c0e36b7e88efa8f95a987bc7cfa8300e14f443fcc6774dfd0bc8953f940cc5d9b2e1f1edf8b2873b3ebb6d3e0fb4afc1afe39b613cc49fe3f39687f8559f0379881f83cf5d9f077988ffc6e79fdbf89cc88b3ed79fcb7cfec2ef7d0ef32b8e743fc792e44a1e8efff9927b53f61975721eed77fdaeee5aed27b5cd1e11623577affed683ec5aa767a4babbdb26772ffee4b328143c6d99a5cb95dde5685b6b6f68d1d803c1e737a3b0bfcf3c0cc63b34e7bc71536e5cbf9a9c358c71ee7276d0339d87136a688a21595a9ab131c638632fc8e59e833b7586fb09e53cf9797c267bccf846b0a0c433aa89640322d84c318de5f01d408420a58a27050900e931e29e160eb0852440f2e0dfbc6eeff0fb9187dc73dcbddeb4b7f7b6efb64f4db1b2e4bd8cf6a7d3fbd4adb53a6ded3ded53e3ce4fff62b132b494df6508b33508abe831d163a2c75a942ed5d73b897414a2ae1e3836236da21e1803c18c93e331ee7307e278453658d5caa55efcce73ca4459381ed3590d6d882a1ca03f81220d0a6f809906f428edab0ef41507e617f3c679de7beab74d6fdb96da5e664b6daa6ddb3ec8b66ddbb66ddb96e319d9ba6ddb64b6ef3aaf03c189e319d9c0143cc45ffe21ce851dfdb66f6e0dcc86e99c4c9db75c4dbed7a14a148baf9cbe90ee47a9481ff8c8431c16491dfcda4789fcc96555360633141415a39449f6d0ad514da323dd9af653fad071fac0afbd967a24323bf5be395013b2a54880edfd8e8cf7da93f1bcd17b21cec3bde7b3bd2780f43e88ece9def33ccf7bcff3641ef48c78329ee7698fcd3c967951bfcc8d5764060cad56eb094d4113aebce5091213bef6de7b2f045c509a26fbb95694ac3c011440464b3c26d65a6b9178f295041365021261804c482208ef689a1a3f4b96905ba8c0b0d87105587111490912ca4c7ea680090a98c8518a1f7cedbdf7de8b63df7befd52266b6efbd59b4e82c29a8416ef1824836114608cacb154ecc96f0e0f262975c5faacca62934686826f304370dc99e5cb9f7de19165f49a5a526ed01332f610d1dc0884571c117504d88dc1df67dfd65df961833049530c43873d7f5a2a8cac507345cd5015c185da24a45174552b80294305151f282285230a94c38456152b493f1c516638ccf10a22b99f3cf0d32dae414443023308808dab6018c600c125f9c44b0022074706d3238e9321be221bee00756748318980f485ca1a105dc1623c238d9a10902a8254304c929c080c26310e957d1146ce402261915d43003bef6de7baf962186f6bdf706cd24b0d8333a202bc5ce40492042a889097624c3740415453a218b714eb0b04e8860ad566b0a26da320a588bc849511dfaf071feef903bd18c4411bb4e6890c517638c3186a2096a639cc31014add2345adf5087cea801430e5880d1058a9f31c65011c20816dc07ec300baaa207275c15474f3efc4802821611484cbc10b1c5cf84c2618c1fba128ee0a18624ae1841144ae6932e320419618c711090af649091a021adea7e5250c49210c8926a80a24395a11b4034f9f071fea160cce42e859f31c61896e42b393239d905c062a110205c3c18e1258b2b58680f5d8af0ba94c0a5559a46eba5314362965c308c31660213b431eea1cb82461951b41f262e6ac03180382f78428b2d404c61e50b26921859c0822c323198145d2c90c08c20ca920ce30e328da32516482d58985c18635ce5c3c7f9b7415aba5cb10d6d0da0220811784d00e921a78d68cc9596eb0b580e38074b74022a122a87555e2b4843a9e80730309528f785289632e00a59138ca5d8bb64ee501c7c1e91341fe7c3c7f9af42450bed89b5d63e31f2957c6598465225fd183ee009d34e102fb90aa127202a85dd42d3b8269a2685144d4dbeb811205e55a986a46ee9873a8009d7bdf732f1d68e945454996d8cb355420b0d8c1d722ca850c2894b8a6108638c9d6839c00917187ff2f4848a1374618124c61833f9f0713ea84b0a0aa32219c688fa82a50c5c44654c92a6699af653e42b7964e50a16a5127e9670184230f1e104569c0cc1840c430a1745d65abb84155fc92b198b56522dbd91259ac29d2ad689fe9a74efbd5a64f0b2ef3dc2044a206901c58c9f228c83982c9a946c0892df5a6bc388f94acaa08a84316bb98a4009e12064c87eb44ad3688d6fd0966d015ddc34848238e8d1c245d40ba2a2c002862e36fc28a1699aa66521e42b4964f482212519c9624ceac249ebe2cb931fe20e5cc64c7162cc099e98627471e24a54f5408c28ad20664c5c60a96734a518d074efbdef23048d9a664ad012941d6ed0678c3176f94a0a11d9225b18fd1845c8b0a488a84b1337bc649210d22a4da3f505bf20995165a8082943d8862dc61a59b640810e5f77982e8b2f166ab55a31608c31aeb131c618bbb6dc2283d0c6d8b593c060a225017df83817e3ba25cb0d395ca37ad444ca3d62ce643269c8702706891d8b2fc618638c316e32c2cac6d80a4d0ba4705ca0c829622fbc6028081fc4bc8892b840c92243030e5aa4f03065054a44f105d7d02445152f0a0d5cb23c63a4186717bcc08698983fc6788c2669204dbc61fb846b63998d31187a581b20c62593f029484c1c146f26c5698b202fb27851a362c0d7de7bef55c0102d4dd3d2c3089a3811c2e8c7059806241f2a52102e269911cc7849914c235dc96cbb9c819dcc2636bc144394b404132a4ba4ac00cb11627cc1642597a525268c317e828bf6908d1248100514315aa0d81002a3218c5a60b92c1947284a10dba161009391d5b20570400206298a20818bc98aa19264c91353d3981fbe98155130b6502e387cf838ff4d59c420858849c489549c1882660b4b2d529c5ab70a30568c2a3b425b6a88a2822f4870c49319943c29f1a24a9221588b0a0e60ca408a61280bce0b4d0372460f49d0e0c44a165e10c153367e5db497e8a00417b2041e8ceebd178acc86dc321446b68778b652f45333a8035e18b5ec4d4fb49c8a1f2daec75c98e84ae6265aa569b4963286a6699a56e42b7964e50a16a5a5052cd19414dbf7feb8039a8062b2538788f94a0e21cb4340e921662a225a4f842bf42efb4ef184114245f98bcc842f505a0b0b70d0863e7c9c7f210fdf7bef76b7eb5234dd1054a150b282a12a6ff1c518638ca950a2b531c63570d1fd6895a6d11a1301652230553af8c08f1645428c71c4c513938c222b5eb884a317141c143f0049e1d2c36c630c055f7b535130ba925bbb4b2d8021480a1aed892c964cd932cbe2880722a2789112810a2334ae931ff287f7091a829aa2e81283c18910d5034baf2d34887105891db00c398c51daa116162be2e1c3c7f9bf181812a30727c0b852051522b7a6b12f0e44eec5185b0d4fa182102c696202511469e143154fb4cc10021aa6e4f0650fedc841e34806335e512ef8c20823820033b524b19480a21ec000d3da320b8356d1524516a9a0d1e28b855aad56138c31c63936c618e32d474c1be36cc7259a949d291cb64f348d8a29464f90f8ac598c313ec3e52b299489f403ce3052bd6be609eed2e2a29950303ea93c542e4442331d232ccab862044c284144858b0e2e4e8a1aa880e2046bb55a4fae6482b2e2c485c9101eea92a4699a06f395444a1a999c620f1843e62bb9c52049617297178a6820818c66354dd3b4fb9ab7354dd3b42366be922d971091d16d00122f8ac3fdd162888240f518638c957c2597624dbe9256ab2110415aa569b4c68106dc84039525fa6b0c3a1830851394fd7b31de72451a1bdf8dc128c4c4ec96518a14a37ad444886dcb134948f91d7c826af1c594a9d56a51c118638c319eb22d9bc288da188b2562d8b62d4cd0821ae38b26a204e1189ad208639bc64499819e41b110143f685439da4952e4840f3f28e170839527a6174c70d102a858abd58a6225153324410521021fa298587c31c61863df18638cb36c1ac18f56691a4d0375103e4041b1a40b204d9f08f239676cadcdf9adb5d6da28a85c42d4ace51222ca259061a47d9a267fce5a105cd62a1a560933d8943a672e64bc76cef96d9ca7dbf91f27eb336625a0d17a2c72b6399909d56ab5a4dc2bb534d1b4ef6c37b9768d2c6440fd7815a3195e4489bd60c51437c81dc408e386fb2f7b780e60c6921b6078418b491573563486071b3235b2e4a22b99996427185ba9513c11e48442b9f77eb7afdf7b6b2d4307269e68594195344a40e46229fb76a1b20315306b5dc539bfdbb8fe9481914559ed7b1cbff2c5108d0e3f186f50687124b3c86634bc38bf7d7db981bf2ffe8ca1f8e2858d9f461546c9c66fe3aca0c5a864cc184a7b9aa1a90100000000c3160000380c0a064442599ea6499cf5f614800e6596526c50369d86b224485214c510a3000000000000208800829073b4a3001789589a955a9b67b75d52f2f90cb9b4157bcdd2c16ce3e00a990a31292226c739bbbd9835c20adcc6afa81012339a8037382fdd96c7f08b065c974661e27fbe4b6b2600cee951ed6d1c1c96205aba8556c83643ff8511e10c604d7a368661f77c56de01b4617dc87331da1eb9a09829d58134a28a237f74e4ca32aae8855cf3505a7cdce072f6b98838a5d6a375f2bcd63d2c995f60756cdabdf3d30f62ae806482f2e47fd2d0745d633cb442a43114e0ec4bcc81e0860d4f474b978ff8c71f480d3364e88b3bd2e3bab5e32faf69da18b2cec5c44bcde9d49c5e95099b68a5a701e4e6baa1272bbd14fd1e7692dd4b3bc99b8fba5a9c3c8ef5ec7a01abc045be1013800ec5369816f763a72e882d5cfc32bef807ff526d70d39601b99b926465357582a9a4acccd80c1d7c0992d1a68bfeae692239d12300edae46f6ab7ca0bb8d1ee94cc9925dc131abc92cd9dec37e20944b2766e17a123882e69f99e7e34aec1c67275835854084012c2b051d2f3360ef867c98be957e8668364aea0a8a4054494ac64b56fa0f73e6a633ffda7d13a6344c142b028d257aab535d8cb8d4842da4457587a87159ef481344ed96963c519c67e13addafd15397b633a8a08a34b8f0ee2193806ba69aa9f7974550bfd34c867623b14251abaf7165845690ee8d4aafecc1a9c3d4cd2b5efef7c82a8e9adb37d6935613b7d97c1ec4c8d7f0a486aca200fb12e3a03bdf243d0cdf6b57e274dfbed096f8dcbef9150e58292f0d7dbc28fd5854239db3400c10d241b4e16e47881bf28b7815201a36e38c706f89ee09557e7df2edca497860a1da903fe008c478d59a620c59a6ba01f05cdb7e18c0433643bd3feed737260631c9e07d006b5bf63808adbc7b45b7ef99aa0e5083a693e85183120e430db11e3e40907ea4618c467097b8074336ebf41d69d1874b8927312a54df0b10ad126090f2f62175a70ecad31d6ac2c30535e3e16a8ddc8948978012eaa875417b41318f703dd749607b67573e026a9e06665b282c4fcf8e299fcdc5c45993af3108d9f7046db141719d84240fcfdd6f09f3a1a9efe7603bb0744354e9c20019d4e9f5e7bbd596b9fc885e9e5e36f9fc8a9ab77be6c6a20d05a692df0b1253154b6dd6d245696b2afc72accb89cbd07d6ff1f4797a35be9f69ef5debc61b848a4c37341738aa729631219d35f0b839edb3b0d2e07523ea99bcfbd208cffd0ea2b9bbc9596451aa0dfebd42dc888495b4cf1db9a9ad6a0e38963ac28bc74e88861bd68a7ff8fdefaa4de013b5af3d5f2a02ba7e43255b2022065fa05515dbfce106f54db4f28bb7552e39881a4cd65865cf8fb0c5a1b38338633248e159842ba32e12268214f8a898b87fe3865ee5ce60ae80503f560080caf3abfc94ba346cc56d73435b2a66abcc7a44d1a1cead361b9838097d26b0ae0d8c8740f7ee85655c34356d631faccad21663422484ed4a4f5fde07582a2273f61178228dcaaca68055963f8e756ca37296d4b64cd06f6f05d172a7ed8de872a3081d11de94aa91798c980f1db1d6cada53246d0cfb194a6f62a0241bc0ed903f193288c7ca587c734b5be99d57619649cbe0f4c3333ece7b34b58c9b4e27f03f5b974ab95e9c89218d10b1ec4939bd4884953396d851d368ddfb7f13f340bf227dfd1741bb8fe5af4f3ff34e5bc49884ab6eabc5254af84399184029c3e5f463426b2c7c695e5542f3876a1597d79e0ffdff15a0a423cfd07b6eaa2756eb4bff2e7696b6fcd01092b9f5ecfa9e43cfed87ea1b0cacaf0b63910f719b5b5424e60ef026281698ffadf90c95bd336d7509676ee0250887152e543fdf4d0ce9b2835c2d8cd087e5b6e6f6c4aec81e1da1d356a58ede46d3f906226a0e856db6ae22c73f686e3f4bef09d476619aa291edf112a6db9eee92f22dd96cbfd36e35c315f161f0b46d324b0f879bcf98adfd8eafdeedccf22c164249ad8ac917983e181f9f3915ee441bfb6ad680c9b45468589bec364fc56c0eba51a864602bb0cc7161784c4224ae9089b7914eac4ac63d78e58190f1d462b80638e0bda7f4b5021af785c2d7805526776e6f60c979ce3d52ab4432dd815e35e4a0c4f94a25c16741d6911b6896fa3168042e74e8193560fbee3803a5716ef90d40c94fa423e994f9704d4563e9c484af9c5410bd89c4d94e3b1fb3f4e0493d562533e9b8a33f7032a42b646c56afb7b85d5e98267d4435a07e53b1f84a77321bca95925b94017437c005b39a486aa3415ab3194553288c478e017678604c213751d05da19359bc4f7ace368bbda50b60392e5b505e63b35aa39bd0d7fb811f2ee6a0c232cabdff2f178969e5c7fb3bcc63d0a5266cc2af47b925b1fb997088f34e21bfdd7ad8fbe7c0d518a52d68a54158b8e57d8d853c9d7b41062a71f60a10af8796db6cf4f0a7be6c8f36d4c199cdead3c01f747b2f2b830947dd087a8940e63b820f4ce65f5fc8b2e2d0bee4bdf30ab550cee9425a937136018405d5ff245d7ad8c077f4018cddfffab03b49e78750b570a49f50eafc38f9a12d526bcc88b46059a4c234ad28fdc13291b2e6cc106f6b4f5aaab4a50201a6842fb2bb6ded57dd6ee06b5567a8dcec04917816a71c1be608e44bc836ebe3958ce016c3f9857777952765a778f18874585ff4b64e6cd430e319939db0887f30c11ff5528158b08e1792ea4cca196046a792fddd925973b6012e7c3441355007fdabc21890579fc706e18737901ff960b62c22d05b65ccaba1d3d969ead8a4af0cb4bf12d6815562c66d6f3e13f2c54fb4b6ed0eb74c3e5a099abf3f3833b79d176563c9530bb040f2cc04db2c351e6f998d0e69b4396c79639d498a5b2dd351e306d8dc62280b9547fb6615e3cd399f5be9f2f5b9f7dacaa9699a7068a0361a48466a4c4cb17476da2f5f47ea44a089e4849c94e3d0d3597d7afce5193179f4ebbbb45296fb3e1e059b747c7340866b8e310673e1d5fb4ba63b2911115dbfdc595574b2fe99dd5e26680acdceb8e799a95c836ab0d95ad4b2b908c03427d96985045c49a4cab93d0f609765b4210d4aac2933e935c8d6fdefbff8a8f753c03b457ea60173cf748bafc448e1b4ebc7fd64d839dd7e7d5143dd05c1b32ee9e004b6c6aa99d00dab977667a1bbfb1ea856257b7c49ebf00824f1aaaabdf5c31433ff30f503f9635521c3bb9f506bac5431512b0520d378e0aaf4d90fd8022f72d806b13fb283189e59191a3e6c091c75543cd6cc87466b42842c45c1c1e8d0e71e93536a3fdb4d1b8b998dba6df603ce243cf117eeb6817d587d830e262b6721cd541856c8a0347fd4edc940436c308b33634fb4e99ac15d2d5f6931fd3c1c26026f0389838850355c373e04e41188ef2f9f49cbb6951ebca41ed52f8cccb902750158c937424795facc0867281b3268eea29f3f3addad0ebbc8840512141a9eeead73452003ab6759f4d68225221076ec90ec1668c69650907f7fd8ee3af136ebf9e2b6681b7dc42191c1b33dc6e669eaff2a3ed0897a3808ae34541d35f771594ed5b8ee6fb618a21cd6c3ea4006d6783b9f9a58fe76802bc45df94f7f5b16767e193a3a9af43cd6f0968d75e8a554bc605cb00399ff6365158bfead1b489b280ebcd5ed20869684e1de3128354aea070461f9c7c2c9634ce70001e97bce1f355eaf9948ac0330701751cbe76efb18859098b1b79ed39d974a016a58dde34f0e0c39ddf12b8ecbf8d7b40d7697e1a5424b94d7f062f46e184889e7d4be6b0fcf064bceb37c45909a679d3826a0cb8936ac8b26a0e45bfb0a4d22689f3ba237fdc72e41bfa32b6346ee399f6e9bc1350b6a9ab827f80fd3cb6f2004a13f6e02d567ced5c13344306bbdc43686c24447436d390603ebdac40a0a44c8f375e1f30a5d02be287f97b02fddc2aaf64b879c398ed31eac6f900ba8ab2528816cc480fb4799602d3fdd8caad6684898ecd63211cbee13733b51ce5fa9c21b97bcbf4ab59b7497cc855e666d904deab169136a81c1b34a455218a8a74388eaa106eb05050d23cd599c0b1b91e4e39464527a120f8fe91a612f693794c90321131a6bd42e4f0c08955e7f4d9a2db9112b83faa8317cd39fac2cb94910b282f74cb23dce4143925841abad3ca5f1dfc098e2165c3b5abd46d07339e8b1737cd4f7ab84d3250058e04982db947a6231d2ba7698b342020aac7fae284ddbffd43d9e816a87a3de6dcd0aff6ce0db0546392b5fa8b0e9542b6e5fd3c944ef5eb794f6aaa5a3589b13d059de1d2c71b6ac6cf0c0c5d72449d795f81883f62dd32c97bd8ee704a7d4367a014b589bed95bab6110b91a8cf39d23635d994c7c8780437ffbd4de259f678358f3e0e2738adeb753e3396dafd4ae5a3b16f2f30894027dff081cbe640300bf93240ec925b0bf86a6efac326311762adb8167f316cc358293a39821bfe952bbd81b4b6c2dbb51619cd0d6106261c7323a41ca0dd5667c1d9c7f535caab2da27f06787de384f4ecfba96b6fdf15f4ab0e08eac7fa3f6cc713b323c7361bac2e96280bec301cea58518c880e4db1cb61e9ab1488abf04ca7c0920c8aa3bd7984ce1587e6660f2b0dfa42bb0c5fb48eb78037d22bf0aece3dbd3f15c8e073605e04e427f4de09dea961ea93b2a98966c2607436d0a554619bc09bd9de262669ced06e1a2b0cc061ee782dd2e0f851c74df6467246127020c73703a1ea980fef963cbb9048867fd464892f49cec9a1f5ff783117af870030c73c4c37f5d99daae862c3cfa322734c74e760c8ee0c65b4ea7fa5e74934166754317c63ea0746c6d434b585eebf15dc4697ed53f178dbdc65a672853f4a6ac12e7745b94e53429f3bfae444d989a450304f4a46ebc9c491fe7c812dd6c941342b3196dc54fdcc9385e2939da9fd842a90cdd0fbd9348eece2503f9592ab468a8f864f6d41ef735e0380ac00e84e11d9ffc7ccbb16b35a83b6cf030a042873c97dea4a22394a1ea657ccff9d20b2515e133c75741df6091256d2c206b8510ac14ede483a066513e26213128eb84d4a4ca4d9442134063bdfeb22e64e774ed8f77b9be1d70e9bf37454037eeb53ad91c6bc1df58342348ce0ded737d76df5a3976edf231b2df75e38dd26e397f2634bd9acec8cad7dfdb84fe4ba971430111f13619fcc9344e55fcd7619dcfb4b427a1da24e10cab86da1785560eb19d8fee95ea9766f3e71c0b87c4b20ecc1f8e572973cb560d5d3920b951670218f17694b82b30506aa73890abcc8b7300384bf6b258932d1f9205c1494e44d981b7922db02a1742397cd3d8f66379bca28a990a2f9e387fb25e16daebe96fed5a79e729851c9eec0ddb79f4b673b6a9f4ad2239a17ffb39897185b4c266d39778f7bfd4d4ca694d5c2c3b4fc2dad44465aea4297525e633446c6a57e5f781983ee1021776ae690926950c4d6095ab7620b78ebc894d4f8a1339f109a79956ea64caa05acfddfd52c5b2af1488ecdcb0b9982297219c97846e9c14f7c6b2d561059da05febffc313935ab09b6e3d3e6fa69bfd06cb36ca4add2df7e66cd8d5bcb0a6f1801d39b5eae9b1bb218558193518f763a38c698a702df231f8ead8bc9d6ac55cb495488b18386c6ae39b58433ab9114de31ff5d1f4b7a727aa9979e19c75de1cd68f2e01e3e4f3f3fcc104e5361fb654c0c2c0aae4c202f308f370da17e580750c075e1c2cbcaf86101a7eeb99380aca3a15c0465b9375b20b912bf46c4ce9dc691f34cb057d28f458be7b3b25956b8b0669d234d2497ed1122ec3cb84aca600a4111ddd0d13073647b901ffa5becb4a8e354435a900668c127176ca3d40301cfbef411a268e7218f7cf23e50af1e0e4e3a2b2c323d3a4312265cae7a124d426a09dfc6099eb50a25099703c7da69b261dea18cbb6455e2a6cb76d283ddaab5328afa244588b52ad98df7ae3200e1558b00b1cc95fe3fd5ea8b94ac8b1f98ddc17581c780394bd45db415e705914c22efecce660f107bdb2ddbe618168781ddd0142890026c3ce6f55278c0c5911d3e6f9a371e5332f3c5d8c707217a9529d742266b51f8cf069f3471b78feb66cd8cd56ad1e78a873e44ebacae533e0ebbdd7e118d785a67286133ca6023264953a19ad6a215bb961b7b1350058e43a35e6b22619893a16e45c8e0575ecc3cdc1aff031900e37c9b8fca8b1265fe15bddfdeaca365ace8cf540189f30de1ac62cf4688007a52a0ca3199eb76b6180ec718053d6cf00b116c20b9203de1ad8cec04d0b8dffd63b28aa32f945bf6703b0140272cf50939a40f93d342d2cf143c3c1d38d573d0599cf963fafafbe1672d7587ab6cd9b7ee641f038dd12cb9b93f94cf7541290d1ddb0258159ec7105697facfea65202212d3f4c6549731712c6186ddd32888562aae5a3aabc151fdb64ca73b84b9180b1a753e9da5ae2eec31adc1440d814ac76b75d0510f7251a633234d0801f812dfb00b73bf1608c6471e0a127edb5fd6cc403d0c30cccb13f2a7918a6428428e11f40c90b3bd7121d717a5032e5791c714fa966e258c99c8c529489ff5606aa729cbd7f865836680a8edcd0ff887766e65ebaf81f215009b466ca1295afd3626468d7bddff5af49e8840da54e7901691308b91e6619afbac60f621595c6c7ac3ec7ddb00343c1e4824653b9c6aab38b30c052236b0a39910517957c391bc801fbbd8505b9294fc0c9682c53537112b056537dbf7bf2e6d752d349a679f0a021585863183dc88d5f02faed84cfeeda1fcbbe2899e2cb38098b5d815d408bb03d3b8b5a6052841f0eac59b9914e783ad42ca8577caed35c3eb485be09b41c43d08fe53acc92b763a1fed1f8a453de2556860c3b521adf769252364068ed6b730789fa7a0a66fdc475a61e1a93c0b8046e4b6bd11aa54726faa50ad02f00936eea76063a81c2a22e2350dea6d6d595fa30916d7f1e80ab7fe82c21336cbc289e3c5e08a6c3f2e0d7426ab58e2b6b953040a60bd8376710b639f5408dcbcdd1ecdb3501a3564ff3beee3a5d7ba0a6758bca2dc81231e0233217306e6283071a93875903b49955ecead296480b233d18fd9e90dc1c06dc621c1e1e37c8f6d7bea717f5a48cebeec09a7cf974ad3eebdf259f9731ffcfc732855afe4217b9190d7473185ec11bcb2e56a42cf4d57932b7a5fbe4fe0900e4e1c0afd7ddd3886e4564d801d9bc023ac63181ee9f913a0c6e013c317474340e8fce98d6e10837a197e636f782fed760c1d21bcbc473f8a18e12243a15aa9bc69124080c97095f74544a121e55fcf38c4de25e97d7f9ac047f3927910d770dcf00d0df614e5d3aa0537dc4b695525805eb702025228c0cadd5f7bec55e0aa4b6b64d4ab14c6e41ed9771f07b6fa135764ab2585988bcee7ff00f6d4451ff7f4ea4578244f5a848d496cd4ee1dc9d14d4b6109be34551b7d781d92058c43b8f700ce53611df569fcc1f615479b6b31dac4a62899897dbd3d066095a32aefaea928bbd9ba5e367406dd8359b9ed0c1a8c15ace934c229b0e8f0281671b316184261549211124506650e851c03036b1ea8c858c428de10ae205115a1cee0a593d4e37ef14eef1582caf7a8a96c52291772ba28a51b5df6ed897527aa45ee514a6641fd3afeb29a390247d9ff9a3b79599ec9b38301b50ca505f8e5bc9db5a69397e36a663db6524bfe555e9ee726bffce6352a8877ee90a17ed44da4f4028892ddb5b9ac9073635a79c7a04a62ba8d3a196654e07ee37fecd2d94a4bcc10c81923b420d8bb30aa0cab28f384d2e4433214219d123c72474c1e8cf8203d9ae303a22a5d0c7c8a29cccd9e357e8711dce9d01628d59a5f1019a24c518c02865ae754996d5877c66ee5b9b8128314485706ce39a8a59f62198088ff674c9db5304feadefddc6a53ff5753855e1743124bae4215dc1df5786896e80330cd8205ba3921a1f7800b16049dd546ba937b7b1b06e0d92f4e496c2e5cfe16eb2de57032cdaa0c92840d04e98c5c58a05821d4d29b6370000bc76c7bc9a303d4aa336b6798b0dd11480d6916f8c164502c38a598bec0a4560a6b7ed4dd5fe37343951320dced3e2af9c44665ab358d9dd4466d9abc90e06b32119e3ef818a82536be931c2c1bee502b4ec8d0293c78c5e78707d370b2495f52c60149a5de65c05897e1db3aa52fae888a47bea337539644803957c5ecc7969473270fe7ca7022678b75e6447e7753b296166e8c0c0288aa5dcbae9718ef1003f5b850bc0e09af26ace3b98d6c94e5e6c3b31724f87091d8f9ff2d1892afdcf6f28f44b0a4fb1cb96e8f9671c840c20a00eabf249c1b2e4719f03d3ff9216616e617eee4eeea27dad466666b181fbd87a26a0b9acfb943b803a80ca30125506721cabfe288e8defa16688228a8e977273afbeb419d400ce206614c5052b257976b522809d31aad04771a44ef1235c3eb4022135ba7393fb944ee74f767f899b392605a93a7b2c1d6ed32ab85699afa22194e6c840b680acd09170a72357f7716cd9ea73dfe565a6146de9384834c24d60a176df7ff0e7b1fd2a2d7272a7bbb47936f108cf377e0c157eb8ae1a27278d613ae701dca901e1a567bbf152845744d0c5df4e370013e3dff3c91aa8d91088d25a3adff19d341e0423d33117bacd8ac2d3b7c5ec8576c8e6008227ca1bc9c645bfb190970d69b54b11993041dfefeaaba69ef6f988e71f4512502626b07201d364c84d56c90fc3f619a9b24b00f2d19eef175fb6c7c9db3703019e000c10c5ea0be93c3699fc5b94a283ba89e13f2b5a8afb2a3af5853d5070ffe843a1e9caabd8f2e3509da9316711ce0d398dcc19c3ad271ad5cc3475b5c11107f739ef604550391d957d662fe4a50e34726a73fbe47120a1e247b545e8def59e39976046985750e79abf9b4c5477cd508286e1011c309539850ba6c9520bc109b265b6302a0b170a0bc35f4f59ac0244cd82370abafd888d4ec14f56e502d53ef24c03f30793c010daaada864dc5aa204bd4a30b7b243ff1152533a945bc4836f89cf813e10fef31c07b5b7d0dacd43ccbea61880c8bc35c5f0b4f5e59630a8f1aeb822febd9320285119d72f15ffbaeb11a4324331e2ce0a9347af1e9101209f680e00fabaa0b4645a4e04ceab5e373e54acaff45d57717d929c69cc0c14fa55abbbe0312b563e829e464aaf3d6635ca1cd3b2d2de4a39e79efe3f55b847fd947c36a13447afd162213f1edc32e61caed08ba90a20b5e6da074b74a2621a0eb29e6541db16ab48534b772cad16738898bdfd3e842b890a6369c79b399f730a6507b4087d1c5eaffa76078fa59623b9aa813db321c290032deab6718561743f2eeb1853ed0315344a00ab4a423c8cecd8d146b1535ed25c0a9ac2d15cc31142e7ed4cb4151a6dee7a89c36cdc3dda901a771b5d23654f2fb767d2819d72d20c37795e493fbef4f9add9c6aaaf9103a6c179633a0c10d7b176b265ad039a53e0e4cc37057a2aec74f6061e2b3ec963f18aa9b0c74327c9c80a1afbf5cc133c2e890ade8ec574fe1001752d77b49e83ae1982e88f0516961b742e0d5044a4aa62db5eee43b47da06be9839efbb397d0bdd8a174e4ddd364e5a42d54b15dab354199c7058f06817686f30a907385fbb0ae55d72e44564ae994bf3d9782d1483d2180633772d4bc2d742488b5eaaa7fba0027c3460ad5b487be964988395d633cc33a7f3f94eeefb22db89413618f0502b875abe51a563b8950aefb3f57c4bd270ef316d762b7d7ab329e93a19a5dfd506ec63d200ae5a63d6e02d985611481c64ba2613238deddfe28f1eb0b2956934395b06c5a61c1bdfeb857736a3fefab01756d581a4cd30b136f763f6cdf2569456f4e04439f20fe90c1485031132a9552fcad82a3899445923a5edee0307867e96ec2e7541d3b5897b0de141b01703a312e0c2a543d9e50df7434fde4857117dd54a436845d57a7ead81f89e54ed113903277db3bdc2a233deda93ad255f118748b2ff4c9b8e190aeec224a0b0ec9c8e3aca01beca1f3435584fcb9dc421c808410f17601c3c4622cc40e481dcbedd3a18f464794159791bfd98168fcf5e7c9a2fe2a88cd955f853d0a690e4b53095c1b83fe1f72d918431b218f9f94f900bc624bf65f6e2a5180f95bc259802aabbf352c83a2a2cf586b3d4cd2a5282cbc2c96aa5f402954d7db7224c611a442429c39d33b0b6c4f57f077afb388b46d666d8a9290cddf9df890856b65c71ef967e03d316ba7301edeb98276a745b202e45e1eecc6ca12e68804626431a4de55328eae00dc317887a8d28a234beb74a7cd19650fd06da3ae4d006c0e850f09eec6d2d5549e46f0180a21e6ce0c8d997e327787723caf2e3e346b23a9d309f0f16e370ff47e43580429d0888ba247ea44a034f1a7a9588982170130c0c03edc250f928f669734fb7c1e90bebab943662ee26c2344357632e7aacd0cfa5d47c512edbdfb70cdd173aac1e70427c81f74970889e79110b91d80c3d6440f49bae8b98afa20a27ca8fefa46baa837ff42a94f952f15342798e8ff3f9fc773899dc29a00336144d71bf7854ba10142ee4060303af38e77a3e5d3b0832ee792228e2c707b03edb572382678c336979426834a5d1b55298f91296e9cdec3e6cefecd6c0f43d9308b54ab9c5719fc0d9062eca721844d4f6eb6fea7e959d720a06d48100c4620a3274615178b1b4a4d7b53d713f0139b51a02cb46c1488b587051cbcd70cad1674047382e9234828ff8b460d5e15e194498abe6e51143f6516eb11859e6c8e8e8dbee3c976ec28e408d81c9a5d390a75e5a5e2b06fbfee77d4d380ece5015ff9e0d4b9154d3160f627fe2bd847fd1298ec42eefb6d029e91cd7f2060f08270a56b6c04bcac807ea68e3537b2c4d0f1d4ae41d0370089554c840b586e59cadc9594dfe893119d0acaf1bfc1f65b83392ccdf8f1b847644c04b4d6ad7fd5d4ce17989dac28253b6e2f3232883f2d8ccc30581a68a5c21c1b10bb1c93a8936a55fbea790fb8aa7a1b8a5f31c1e39e040d2c5b457b67c3bf7e4870f4677f4a19c58fdd3a6c82da9929b4ad5484d34d8219db0c9a4cbf5a606232c40a137fb50e0c2c85488b1430c5b39f72674c12276e356a270cdfbec9fa705fa874653a611ed8e245c7ccf69f9d1454faa1c3dd1d6a94397de4c79d2f0902f3a5262a16274edffcc87b631b3948cd4cc2947542cae2104d86ac5f896115c782a00ee4a89177da304ea6ee50ba0aad21c80c5c34f787ca23e1003fba884d2625b3a4bee767a0f10b2747ca10dc81777d797312079718ec227476d6550449152b3daa840d252f6f5b10e82f25bc9c17ed61e1bce605eb46ce8758abe9f0dd3911b66b212f1390d0506d2c11857bab27525b5272a5a4b95a0357ae5f0d2c4a8350b2e8042d740519fbc09de395c010ee6b4e22755e694ad5df71b09b033b765aa3b22d527eecec48cfb47e39097770a09141e36e45d12736344e32ad630361134448d3b126e990337d485f2279f0fca461c0fad128f530fc15f8b923ce006407785fdfe2a06d5f4194e016d6f3c57cd5ac661ec10a9a6a8f7ed9f69272d136376b182fdf60abc24f497116f4d572a1b636f9eb7e026ef184484e35a07eab254173a83fbe147312c6106a646d9ece9d448e440045e1def6f90b74466721029b409240a849e98ae9646342e3ad809641e74ff6c92eab02023255043d1f0e0e67168acb766272c1506bc14e2164b4ca7101ca09d33b6c3361ce6c2592526bd50990ee93235853096f573040e6daa11fe543f02558e453fee17af8c669890778e7b2c887b3ca9af3f419347050bdd07088063703ba12c100239ed2e9d56c1ee7d00a832a0b5713e5c0f2f45fd227757aafdd15938fde3bc5b417880b7e49c89eeb35a46a68d94468f7249077eb55d1b9a39db78d86de774aed3e3e7b574e61363daffb5bbfcc7dbb1f049b3aa9e1949dd566bf9d31c9ebfecf8b1c2fd5a66253f62d554c874cddf04a4e879f30f0428b6b9990b36d68f1314dbf3d1070c1ea71745330e60fbeb19ce7f9583de72b7cbb76d79b00991072505162a4c4eb6d70444ca030b88416de653057f9f1c6e1df800e13ef3d456c6ce9eaf825faaad51d7031f2a06f51d23f475e89a6621ed9c2d6a9c38998ac10d1fe344e90a106e41843d1176883a62d35dd119102fc7428eb9baccfe4a786dc0364c5920501a97e176d7316969f876f6739c4cbfab26e1251cf021cb1dc48bfaebc7c2a1661e27221c05315d1f41220f8c2835dd4bcc4b78567bea10c8ba4090021bc650db5232ac295337e7d40d43896e5d733f5fd8d95117c707a8d5c662681f4f3bb9bfc143d06a2d248108fef9506536d4cd3980881ca12035c8604f2f32161027ce85c4f7507f0873e2e7ae53bef9467828504d4e3675b22909502c79bc87394d48cb7bb209d60dfa89234ebd8eb3772631f80f01c85790db234ef58808313f398ae307c6f045a3f1ebab74bc9d09339e7770b569e744937bc2763cc4d6636d3bc682573ef2b996d96fd699dc7cda1c7c6ee590c6366666636cca774c5156e6813420e5816906a7a41701fa0dca1419b42d543b2695d649ff95f67ba61807f370200635939b47dd3c23f4ce54b1e5c2e155e9496418e240f31c7b2462efbb7579e897432e4a1664e357374a246216ab4426f97827616306dff2c7153692b3a8f433e40dd47ac317836e98ab48158b0daf241273c5b1998eff9c2728ba6575a8de0d4e5506ed77fa35c0cb61e4f899c3888eb247b615ef28ad104820a97474f8d5b2c430c2560f68b9e27b8411fe0fb85496d3c5b508c7828d3c3a536412607372ee4ff630671c31047b250640e72ed825d012aacabc7950998774a54156f78dafe42781fb9a8ba666fb8c4ae8a4f6de0e43508321dbb0e0a243669528246c8cfe34f41c9e1f11295c46ed444e1825b8a076b0ef8376b6d321ae0bae95d83ee51f95336304fe008fd93f82b58b36cbd5f822dff0c78d63cf1eb2d40e9d4aa7f365aa688f6072bb28f2643fa25816012b3d328a1f24e638e4c016f43e31856744d88586d60ab2c6072f897fdc64f9f3bb1850bf400f4a378dc6bc46833dc661b1e586a0a869c826b54d5dfb1d7c2c9f2a21bdc44792ae0dbfd8ac8095f69b919d4f58060a2e88f988ab14871c0cd63f0efd42403d717cdac51f26443a69e1933386744ec296d79562831dddbcfa541b5726ce8eb1b64cf3ad9b35cce735e90ee36865fd75c97e4abe6aec350fd9e043835ab4b026857d392d8b09cb41d5a4cc9a35346f52abd1d989a9bb0d62829e6cc4ae43ad77095de51a73fdb46afae55203c86bc182cf1e1f22981e93851ee4e88e1b9a7ca2ca8d1faf6a9576bf6547b6e63d40f188b1c52c48ac51d7c215ba3fc77c5070790c41a409a2541979493c9320b851a44767df89a11912e661a85acc95f226283ae522b6ae63592fb868b6b74a622d80e80e96d4d9663eb78ed2a40b2d824a4acd9540db9a81c3831b0a06aaef679994d5f2e10c4d71264b7d41e3d9c67035789034f91c479d5410df09ac25deab0238987124a67770e26ce4a54e42352334fb10c9ccf3a7d0b00c34f3a2e75fcf1e00e52678fc3210c6228fb8e510a0318075a6c0082fa8b8ce72ed2a54fd8904734f199c77ec18d217a34d1a225c2e8c682a60599f0aa0af2649974306992da04e251559a4e7382f1fd3d8a4ac6370e46c20abaf6099d2d84016e9f2e7ad6b5cff332fbf7abeed81798cfefdf44f2cc700e6f62ccd5a2006922ff8461dd0d892630f3709aae03f3b36daa95e53b4b196ceb21ad33f81ef200c2e57b83664e44279bc9f4fdfeb5e6fc92f408298ebfc144551ef4304019ba7b583111f9c30ec66082de8bc081b90ac28d273857f285e61aa311ee13b676c4961a28d3f102b41be004d188ebbd6ff37feafa90bec272ceb2e1ec33a0099770c744416d9e5341c1ccfc1a588b6a8ed54fa65e78466ec6995df8db07a0d1e44a801adf1a85fb14b19d30befa0cd0bda14357ce81536c29445604f3775831e43504717de0ae52972832c8820d74611b1a2bf0ea6483dcad463f2d0adb56d6a45b6c4281e32c715e9709edbfa7089629dc2dcf70b4949632ef32dd77f1245a60c9c48b161fad4c855ede1c7ac0604324da651c362a776bde28fabcdbd30c5108855420f776d40b10c9d5dae25c1ba3889f8cdcd54dac626df8064bdf56e765519d82faf2bd9124f804a58b2fb0fec80bda3bc0ec798848dae2621a1339a1bc2802a628f5259cf00ac225f6ab086b6786278532562970502f380d49b3954da170581d96b3bd1f79d34afe3886eb49d9ff2287f1b5f94691089d46fa9587799e8718ee1789515ea918f42f8b11bbed6a10451ebda1d12be87cc40b3582971c9596df7827b1a32fd45f3d479967ddc34d3370f8e12b0c43f5e6d49a87b2596c20a6f47a4612bee31ac43da2f96ec073ac1da3bc56b731fd774d3f822008ed3969df670b48d7223108c3451d193ff1dfc9de0cb000642a8f188b5550118ea17c95b21f68507e705d6d125a078929fbccef65f63d16e3516e1cbc4e80aa894dd00c2afd388199ab9de1d1e7352d889c8b6d3a637453d0ed844ee3a01ba52fa16a147783217a2029cbe4680d14fa0b5ed0d8058073c18ae9851a603d9fd02b8912ccee92526e75f0c42d89309de25c7a3dcf6c3801de6550e908c58537a9e41864310661c61e94672375efcee09389dcdc5f66717c86c744ee8d50940f54c85f2fc9a8ee2824d22b362c507ca4e9b7dbaeb0c80864c58a0d3358310b769153eb580d47913127db0287d75c1e84dda7f1e8adb3c457e010231e23be631a2b806724ca20ed9581920fd857b32ecaeb0a1006774e1816b983bbb29cd0351c6d1373aa10e8923ae9fc94ed108a52fabc4b0c5f1544c59c2e0d4447e50e5db7d8d07024d204d597e0e7ff9a4fc7a9e5b64c171939c21005819fd344ed80529addbcd97083213b8783a0d35328a51954c085bd1fb7416e21e4ec2218a2423634bdcd969cf5a593beb36ce4cb7b0354c6c42d0e4a870332fb0a4b6da29f5fc4bb5ff2a6c355ba6825addfeb60bc0c287e97e6c951cb19be1371a419e5bac0b9bfc3f28690afc62aefd0e9f18b04dfad528291efee35fb8b4c66fb9bf954b9118f2ce1e1276385855ae0a2267e9f8e665b2c6cee23a5d7b4b9a7d82409c1b1cd2fb1af8f0ca03442e0761c8f4e3ea25e86a46174ee1ebbe84613534f50e4e1ad2b01645d9122082cf84d589198c717c228fff447c95be7b5e558a85e52a2e94c55eea20ead7fb64e565d32b50b7c110bf4b8505bc2f1e07d76b363472db05391359fb902cb97dc265c6cd52727704e6134297bcd346f58d4218dda55f9da213796d347867a569c5105a40ec6f7ccf046aee230a361082874e115264bc25a270cfde7a198052a3c24a6e8e4f40cdfa66e9cc0abc9410fe492b4cf60af66437d5897896d8e5592e86cfc651ba058f8c87d90d4cbf6270f1e46aeeb1c4a3e77847b169b27c339a87b91d66507156f3e87a6331ad5e4e22bc89e3b6416739dff4813680a8d0fd9bf9ff9b52191dd985754f271d4cd9f21becb512408d8531304d380c80da54ab02291326d27c6defc6aa9ed1cbdc51c05cce30238fe112e2f3cc326877d6aeb472b3bea01b9909edf4fc2c16a54d9bde746590652bc643185dc07f3f15347bb2acda8dfe1cae8b9c914d4a3c8e767840c7ffbf0381df9c55ede8ab9298aa18428d5feb46410f388b846ab63da3c946309471f5b0a982686a5988502d6e8f1dc74c08e7ff1bd320bb46e1780776109c2208eb1417dffb95a487597740497726180bbda7dbb831979ea8a51b44d70800022a8b4223fc942327e7c5754739388a7f76d4c397805dd72887c8738756ad7ee74939376a8d49d7cacf858bf0a08a5aa64ca0caf348114b309066c67a6868255465d31554efb2bc42748dd9e69484573a032d9a0c880a90e3e12ad2dcef8ad970d8b04e3eb8bd48e347a658a892c73dca1c9748488ad029173fb7d58b753ba7dc33005f4e8d3e5f3e9e442da81be733bebc9733b95d55ece240f71e891c79a64bb23a8647f0778a845858ff85ea61aa4a926cd5bbbb9d924e76017843c0e35186f0aeb8ae12098c4caf06b2a5331d83006b47d39f6bb84f5add049e7b46c744ead670ffd58a2ccf22a618671783c387bb810662f1957d4e079e2de68f1c421a6ee779019008a2e39814b144a6578d579e121819799201ae4679499b01213c686037a6c28c35d5a1b686f1a623c89191a19358ec71bef62f6a04c4fb52b9e1399aeca00d993a5677e3d460200cc2372ef0dfb911543e44165453f1f23f6b787dd77772c9734a80443aee6a300da711e271c9d70960df3ba5f5817321da3d434d6c1313365f709340946ebd6b458a061b6eb73eb91459b00ffdc300bcbca89a16068d22d3e910624543c395fa9b28416518f4acb060ecf8cda51427c5f15b8a63f1c0262cee1e8a7ed95d7264b5f43170ada9ef062fa6a3f3bbe428ac9b42bab5bb8c6fdff66310611422fb262f6c5e1c9bea9a2b3a45c6b1dd8ebaab36ccca1a99d75f82fe1d1bce7c1c283ccc3db84842ccaa34245d895d7f67fb379cfd6613226ee557a368cd37974f8eed68e8be29321ec737d5b524c1e73a25f64f1589679b2ad1318d6dd2d825d27e5abf1dfb89b8587bc288d5015315f5a54e28041e73749d522a92db017d75e1475979b91da947d821b21723f3db9295671fae647e0309611ebea3620bb31d7f0aebd4655df43ac39515daa521dc14f926a59a5177e338b6f2a38a4459581af11b73f58b9cb2e7b05797343ed813bab7d56b4576d41f3bde6e27f6b9ebcbad079f650b66dda0518f4b2c86db180d81e8b5eaac61da76509846def8b79d8b64b12a4e735c16af521cec3ad27b15cf44e82c46012ead88bf4485c5b60fa94110f14b54f12c21c3ad3e7114782a589e79025562d2ef3a5d33cdaf40ced8c094d900824663e18ae84263c07840536c220a17d5c43a6e85fd2cc1462bf1bc690f48c273bb01b4505a8da50464523e594c34734edd07e31e8f93163955c7ca056a1f223d174272c59725bf2a6ca33084abbb765355a19f9839c3d6d87f7ff2acc55a79552b52211d1acc5017429875516336307e3fa8fbbe2cee7a817f2c1aae411eeef09ac0a4ef27b4ffa4cf3a58ddf9037fe984af5d78ada657a779e3eb3ee20aa910e1ab38681b03e1ec76e7613af7ef9ba47350265158fb494687f2cc55bb6a2d67e0b53a334bf585243e13af5c2452c5e0d2eb6aea38cd91c4275ac567f8ac729d47c61335cdb6bbc4317e9679436e5bba709654e49d96763063ffc58282a4fe1883e4a7046913bd508344e2e4a2bf6f6b62f6ab44c099d8948988d3c214179389f9bf2d43a42c5ac20ebba0d56865a292f1ff3c93d178df2ad6b311021af1c2d871aeaf2ea40b1768c0ff3cab3da038b08a45e1019ff77699096470c044a48d727bcb7182193401574558fe58b61b56c7ba3585a3f4400c4720206ab3b0570e881637a0ecb05885458c5cfd7929e13ca5a14951b18982e92945e1579a6ae36884547c38e0ce353195ec03e374e07f65dbde85b2e8f174a0603a579e2132496c52466d1125b30e68afe2d96b546135cfedb8adf98e8e882f2d5245939ca446df3985394ef080dc324ab9eb061d30873734f572e922d3abb2faa370f4fa27d59b1362fe09a9494d1c1f015120e0219a610c8a582b160178c142eda3d49e2f3e44eda428ffdb546b7af3b13aae2e80822c3752c041a3932aa0bf2e94492939a6cf95961328a6450beae94aa1bf0b5effb754c98cc1cf492edca4859c5f4b26fea5cabc9692c8dab6308091691426542c378737db809e28da8cc34a699b75427a76e2f2e6493c1958a35fb75665b9efa57297b107468c987c12031b34fd6b47084b59f553e9833bd42c957faa1125fd1981e644fb2e738d30dad10c473949dcbfe1d3855c3f0a32a2268b990c7443ce0c3817ffe1d0df8c74428ef154f0f904af5799954bcf60d85137fccf076e31124610f80cfc1131cf42225945f469c878573509c0702920cbd0e1ee56ea69a7d490580e0aee677ef78eb05020282e97c7b2548856e825c67f0c35fb6185c23da3777acc48636401519f28c93c93b875e8c09ca28d092315189624c1deebe190746df0fe88082551b308c6303a3417ed682c9f1716b94adc72b80787f0697a4062e6f891b0701a7a1ea5e5bd66c9baeac354de33ac65e0d995e98d91fc98d79c350e29659dd93c7c1ffaa83c0dd5e70312da0137d228abc1f074fdc606caea84071a0d1cb0869d705137135746e68c05260471ee501b31bdb490e6cb7d6a170a87274cf30003d1262642c2372fde55b048486d2dcacaecf8cb280fcb530a3074bfba8b8e823ba41600428cf91605641879aa3d1622b5be95b98751166a27011f6debb0803dda2305dc5c3120986da95839ed014d2fbbbc92466991ab207ffea54f17231cb2fac40d9682c2e1df2f48417e11feb2679f20465b474202cba751a60afe8651da7f84d118e50b4d38f275aecdf2ef7fd89022e3968074dbd00d14e23ac3c533a82c1daeec2ad0a729c0ab65c5c0375c0234fa9d0cfe4b4a89c8e3e20cc522b18c73c56d62638b19cbc7f3a0b5d03b108fd1b912e0c5458b14f3cb6bc49cf6158a035a77cd38af323618f5c08311d77298ea4324f08266a6c561c0a0188136c4f47b16ea3603648432f45764eec0b090b0ba6fe03bdb057e1474e6cb369fa78a2ee484a13aa2bcb88452d7977b4fdca0d28829ed07f09d3177fba494240d11bf0433cbaded6ace02fd2d0e7303f0634a00ed92a6377e48b14a0483d560bb94ab55a4adaf5a34e2338483cb14440e9f8c9b172d52d59f31aee3f7b73f1f714048fe511e496c8550e06a700ade3b983a0b426d5cf4f6e29746893c8717946c88f5249005a271637ead003df1b2cd41f41b08dae54c00d2d4d8f42a3b100aed136485b260844d9927126265f7a1f05d301499df38d4a003d27a22e4ab0f382b559f66a87624ded8aa84e7e623730f26da39f20075f6231852f8f1004b1cc032395bfc362aa9c067209ff305ef02d2c6644579a9664781a903c2a45621d8ed70839ab84f3e9a952293fa5e82915dabf83f07de1ce2652e0b5506cfb20f1d0648ea33180f477c69e451a7b58856312409bea0f6e2274e65dae8420d8bdc060adceb745251edc9fa068ae23006276eb161b7098a5432fc9d81e81b0ca498b24820e5b53704462a4a655c0146540ca16009db6b9c5af9b52d23b05752d548596c60834878fe92e070fbb5daac7ada19a6564d4fcde160b013f20ab5786d29ccb5d05ec4d1a10368c2ca362b935d5b2aeb19ca10120f34403efbf78b6f425070ecfe16ddbb993dcb5519adc999da7da9a7e3dde8c2cb9dea2fe843377c538dac473e6c06f812ec5314376ce44fd69b872ce849ae27273c392a5965a03674da7580a229b73263d4b7f48bbff652cbb78b6e69cf9e0ccc71363af5a23a084c6effd28e59ed35dd98ecca00e2c3f866a7a692665d3f89899d95e8ec7657a7955f4498dc06e62a4687655cd99c119b468e804b0307ac311e44aa3b007e680098e1f1f24d4c8dd9fafb3676a45212d7986a5e2cd3310c09ec7daaccac3590c8a3677ce9c10e20801d47294dc0d74da03b6fc0bf6bc22e6cf576d1ce6d25c9cff9c2369dcbc99732c72dfbeb7fe361fbfe4b2c4a11f8fc51ff74f4e4cb51c428c0133020a005bc4e94807c27f921a0c8e82b8154fb2df29c56aa6ced8abfa6439b8d02d55d3fb5b4f381eba5f3f536573c7060849301f96ab71dd6cb24f59ee5103ecb2c426803595d3ede6979f9161a9d7c2aee61463857b7f79da891926eda4b73b87d794c9c6be9f449ff19141cf65b29d4e93b39c77cd4358dbc4cf6b4d517cc52b61382f91ab660195aa7a87a4a28926b3237ac437c7b9502c91e17bcde2a847e4d19b0a63f603da66dbd57ac7c11c74ae130db409f89e336cc4d86bfcb63bb3f062b255c54cd7d6356656dbb3c77997c32b36b126343eebcf0ebe2762ddcbfa9ed03166a55144d630970207dbacdc3d072172b9feac02046e8ee44ad24d3caccd7fe2491cf998c16061c78819e45952a040e1d1a17e56e8661cdd292a7b6ca1c27a475d1e0850512388f29063d46ac41f2347ad28ca1f4129104685b2703c07dbe18553a8225655971bf1fa2185b414fd12ab568ea40af9831639ac47532a31c62e9f2c967eeb4290c44de9ceb868fac83a15e4b8e5125163c4bcf6fb7ef57554d65dac7da1c15a03a4a981c476086c201919aadb3931f35609cafd067ca20fbf98879713a0c87fcb299d710060c274cd3da7f0a4f16611cfd6873c429e504474b25c65cee202c2779de260b7c42d202609125f4a9b6ffff35b97f2c3c21fc08ad1523dbdf64bbf65a937ae8729371c039b59a97c0ece0c438c2efecfcfaa941f96e0802a464ab1f4fa931d03ce0c07040e0c03f33da93132116f6ecc5dfb9fdf9a94272ce8802a9414324f47e6c14617fea724e5cad009c310fa03073f5e46c3d218a9c7cd5f5c94adf2d12f26023c70f70131e2c2be3e2a3b8f73a62123ff1f9871f9e7be742747d5f8825d5c29868c6a5745ba28a55d7aba30fe2caab02f4ab7638107e4cd629aca00a87abe0b5b81a86ee00b7f5d577a38b2581b1ef34ab46c47e3c62a4fef2cc02977549bb691b55619ea0d80c6ec46814a159aae992d400ad34ca3f5d719ce9b97f61a0186be9ac02700494bf4e0acb11ab2cb4d833b76060475e49600c3a30df85d221107f8c3ab0e485e02f41907a1be6cc627e90974060c54c7928dc3b5b07c040823864549c52d100acd95263c0f78405389802256be0f66d36d0a78a608d909b4236a5f59ff1382fbd2b7fd5c5bb6d1586dd9399ec46b415f6330b2bd9b83c2ee55dc045759223f394a1974dcca9bf72ce36ab44f7f0671f894c47b836c4819b6fc47916d9d20de9a727d8342682d80d22e2ec2ef56afef612ccd31eaff59cb6c451c5c2985c50e836b65d1e22e05513bcf83be4f9d9cb44fa54859d38007174068f305513f2e00d43cab5abfb108c16e39c41f6cd19d2ac91970867433db8e17be369ee72671a052ae556f8466aae55ae132d5b939b7c2848a6c884fc50cba5ff01a7e942355f825d8340bf000b202c2be17de0cf0bebac8b3f6dedd69c0640a7a8118a823210440739526302cb9c47706f50dce50934dcf4e5cff93c5e8b9043c6d9d9c18ee15e8431b4f12753f2557882fa29fb4e80d72d8db86877e4e17bb135f62fbb3fcf9539b0307ba32d1fdb14d561a464dde926e626261c9c63608303157b68143136213dbb413fb2d347e396f2f048c2be045b059c60dbb255171dce29e764bf8e961d12c4efd4d83365eeee69ca83635d7bdf185ca86af01fe972df4ee0ebf7fe2c84a98cd2d52994131ffbd34b39d8e5beb60312312743946e10dbfbd44c0aec1412fe265fd0a65743c52366c4989bdde2dba576777ce939bd132a6128cba211076a45c070b5f918942b7d5e0b9cfec2695eb6f8ef4cb36f98ffc4faef6a661a64787d9a9f2299d0f4003d375a29d527259a539292337adf7e389415ba9f73135e8416c589ce0477dd9bc5bfa92d3808fb5bcef1284ee3082c53efd4b3bfd2feeb99817637b8857f14a80dab7840ed30ab4584c087887806940d1e3d8e86201e9e97112cc9c7c110ccfda590bd825742733f437b9888f068a70fc23efbdb950930ab81f9992c341c47b73638827e0ee4cece8e3a1ad1a63414e0e854143102ea69dfd75ca1741496ef2bc9408541e1ad6f6e58b93217a3ca8541dbf4585e282b4b277e7154b9b805c43b33bee0468a650914f324feffca65063107d770b5f3e87fc931550b2918af665cc91d180b2f51910b969b2ac50562c16996c9fa0a160c22a19ad734759747afe6cce98b9ce9807b07601fd8fb05c51f4c55f46f2fc31bff122e712e51f4d5e25cf58b52d07e6a9e51cf1c1c28803d4a330bbc883a08d3840fbedbad55a1f0e4a89c25f42cdc8cf3a4a862eda5c0cdea2ba741e00c31f218c94231c64d475ff24038b85a09dbab0692342cb30685a5618a3eb5fa322e613b896aa3b90c9a57ac0ed2adcf4ac64a71132223222e5904a8cb391f1e1f297da3287cc276a054a4fa0cb2e24a1166f2be2ead1d03a55b84e49dd283c387f91c0528166dacc5231263173c48c6ddadc01f0e8aaa79dd00a2ac9c05ba432c2057ca72b9d51fda11038fbaf546d558ef17465bd32fa4dc1484e63006ae48b113c60c6a5f0a440af934349506a7b57e6c002493214713ef762b9b0c4dbe15f63115aaef3af893abb55591063996f41fce58167b74a303b0d5df7780d12d558ddd0ea7696b8bcb509b78e1bd8fa9302a5ba3943fa28d9ed46234e8c621174829e22c835e3fd34515e5e55d2b65a4075235543d58028c4c74a64f28491cb38ee45617fee85984c42776829f537c0be85b234974f6a3e8e7d613c0015b817eabf2ba2fdb93262bcd16f443c0ede0078550b362e849f31c4d7a3fe6420b6182177634c1dd57f3aa998a01651bfe33b0b81fc7a18c48515870c1a1ae88b4158b41b5244f89dd9b5159237a8123be905a36e8b40e5a1705ce35fb74df9f8919e1132b2fcef2458f05171082a0327818affad2fa20d0bce082b0305728a008c03a5990a1ba9b6bcea0792889c1d62c2890f4042330bc71a81c19be930b9faae1356287928f2351867c811ec40c82fcfe44ab8e5f94901edab3889cdeea6c9aeecfd5506af9e849d2f79cc765952fd6cbe79d9588ae3bb33b1ad6df653e0e9086d6a3dcb84fb6f2dc9a8637996684518f43e2672995b6424c0936a95b3e3dde2669353de7a2d140923018a452efc8e2f4c1fb4b6bfbea0150c701788c07600cc1442f73586dda14054a97bc2fa5b881d9014d8aa4971fa487da281e617020c0b68d4e2c48bb381965b63c5e0a695d5c58d0671cc496e430c787104e6fba2fe6f29d34f3b1fde236579f52ff44ce57f7149d8bdb7ed3f7870428ada5055c921dbb3b466adb907ec6a1b6a990aab0d7ac04e6e6ce6df49eabdaf7ca43b7b1913ca94e8dd1b9401205aa90c4b36822b391329320c9539478c1e071d77e8d011071c3becd8a10e1c39e668393038d7adef08be63c71d39748443c71d3be8808372e0d6a1fc3bccf22a388987f307072ede4a5f8e4df8e2cf5ee527baad4a855f9b59b98f7a82d4cf1756ae2955f16756096d056c75e1f012c1cb93e7d6e271044171d4e2a96d09c04545d23084a3152c267480532930ccaeac78766da09b0aa9159733ed4c092e7bc4e6c2054a1d9f2aca981eee39afa134596875af74774bb8d4ce720b594e5a73d6ac07eeca4a08adcd9a61915b49bb949519663cf0165b85b26ad61963ccd6d451d69d31cb8cbbb1325a2bb366bae456d22e656586190fbcc556a1ac9a75c618b33575947567cc32e36eac8cd6caac992eb95525a83c1a7048b33a1bcc50f975c53ea2710484f4fac26b5b69d5943d2a3f105ecc467fb9e913c3669e39254a7be09664257a951e954720ca3c00de277ae9b976411645e567df59ff5acd045eda356aacd8969bedb64b6c9a756dc5b4ee968bb56cd1ac552bd6fa719451d959f12542f0dae8d7a73b4ae59b87b22701e66a075f90242c2ec653ae791a9d224235fa72ca738273ee7261339b12c2afd6d63092bc9806aee44e00356d86b3772f6569b2e05219310f0872115a38928b10d4dae0e17ef053494dc4c5886c94901ec8213a50ba90a0b39afb1aca722edb1c801b9daa513fce47ab708f90c2aed1380f51b362a19c408cef9e9874b45376036ec1a2f11288c07ebde8948b57a4d2de8c5c019e18db290e1cd28e50123eb0fc2364b962b8745071da645ccc002b69a070c48e080d49601f1cefeff5fac933fcef1073cbb7b33b9a77ed0a83d1eaa8b6316622d4bde0cb1f03153adb0b1f4cf6cfc55d3e9c019112e55b38ac7e79ad21a95f930bcb651c0bbb24528c474aeb49eb00b2ec22fa981d676968a07b14c55cb54728d31e6d130142b838df9ffe0773354ea5816e40c96f5bddb6d0eacab6daa465ec3e911d378ef8d33a746ba2439badbf72dc0dfd020e980ef23496df2a0345b899921f73fe8cd7604be6751d89ece9a2f97bc57929c985633c7c3203f1b5fa81a056f8b952d0cfa80fb73c93a50e626335b61c70c40fc813ac5c1b1b8e68552e05e2e974b36c32add18447b2d2e177b1d14f05f3ed31042e8eb8dd186027b4ac3e2e4edf0d7bb2b2b84f45a5b51d9d1b0f0511bdecb33a88fca40ccf9bd601d2772319555e43858ae9794786a41c85f1ace26db60e4a73da45eca6d70ec50188855603818ee005100c3cef92308b8db5b70f11eb1e0f0ca0dc758992a783c8119affe8b87871040649c5902be2e2e4c860ff47b3f0e1e63bd38d9c40720139bcde1ded593f2bff48333edd8895abc437d7825be87b1976c0d15a813d4f22055293035db3e895038c1c31e143361404714eb59e27aa7813d3d2085f61e18c174c2260689f317ab3d912e5f0dda6e9bbd8985d3401a4059e4e5ba0f496c36008773ef3e4d3e2d298947882372b6b5cba7ee2a2709ee1b1a3672fb4a5a1bb124d4240fc64afbaaca3f0365acc1f8b03ffb723ddf4431bf3e7430b97092c2f7dc31876c101f114c3abc90f4d2f6252ce98149ecc5ed8acd140defa71dd636ca1455be13cb5e9cababc4def79878e231e33ff1d7fa33cdf18befab12f3191839a49dc568e4f3883ebf7b90d9b0b1d63a8b95c5b5b20afb6fa3b0cec9e160773cad9030412343bfb00b5ef33206a03e5d03b661e7ed88613431b2040eed0aefc126997e7336ee124fcdc86cd858e31d45caeadd590575bfd5d0b764fcb8839e56c0c02099a9dcdc16e83f6c40b35061946a6290149643b4578388b4ecfcba63701696bd947ea762ee6861bde1a765ad8f5272463887f756cb8b3e67dd97a5715df29a0e0c5d339a223f3d9d26af0b3b6bdfc5cf5be095eba5a84dd27928140ca026cfdb1065040138038fcc890b0cfb159dff8d76385cef2e880b7091d06da2810da3b9d68e30f4be18b8deb236b5d16918c458422d83c68bd878e9410a2fe3a4a76ca4709e7c62febdd8608748864c5e6a8c87f49e25193061881e7701527d99d4083633f1749b4a6d86baa84ce8bb79a48577b58b8f866b283c566230a3840b26a7150ace4181b8320e06c07bfc1b181cc8439aa33ab0cb9a890238d60a96bab9e0a1d157a152a09f17e4ea331a96ed1e031046450025809b16a26ec3c780a4eb9485aee49549e7a9d2bc505cd06de08b3dd0ee7d9a474bbead9791b69b68b64d0a23dde3b17d9a16bacf56ba96d857b561310c53ac43dae331958f2b2476ba0e0428ce6f3417c19b8258a512175b58fe9c75f79909bbd4ecbf0033479461f63dd19fe943dc395a0d6e5ca99c2b98f5ce3ce4f9f3a60da3ac0696bc84f04634c159501c5515f4eb2b7ec8b0e3091a739a0b8259673959de6cced29d11c561575e34588dba84c7a76665f90decdbc1f9d8d841705ff4a688e0d231ad4ecd6a1e8466744b1b4240a1c2f1fc4cb86e19a0ee2943d448a0a297ea7b27a9e96da16edb608246a4d5d3c1fc8a7a5349aa46163423eb0e15fa8554454d60846a3cf767a340fcd2f0b35b456c44282894150461685fea1cef0ea0f316fdadb6373b7e882e835b7025a236544b84e753d6a8c1abf1a60940dbf970be16f8cf7b5a67139d8adec08bf3dc1d4afdcce654b21a3ddc8fc4a9741cf9c29322507b176991554ef53a71ba7c6068f1acad74da87a6a47856174631ed7e5a20bb122055ba38eaf43fd69856bbe98d2fba8cb8cf45e649a08c3f767101cc014c46aceb07561c4c04a67a4017995aba4c3790325af2b52cddb4fd990aed8464981ccbe9c4483ffc9f121c579047ae82c9282ab67f83394ca0b2b013c5d010af4dc355770ce61750acc7240ae50e5c6eed0e64552951712ddec19e28f9907ee30612477136af386f61118d7dfb570de7c4c681f1a17cbcbe442e5640ae7627934cce10ecae6a60ee50db66f3adb8ae077af16e826774a726fff1d310353034603bbd91abffc38f9013491ad3faddd27ebb42e0a815ab25104e6c8c69b1841e3436badaf84d45b4a84c494f20daea8dca3b05a62389b43522d3ea7e8c72edbeceeee454bf596369d4f280845d13751c939e77c216eb5eb2cdcbae49c732e06d55b3a241a8fc8f7504cfeeb160114632a525e623a322358a5206f51e99218d7dbbfd8b1ddb54dab893df7fe5ddbb45a4b654282836539f671ec9680792cbb1a42d13ccf6ac2076868beef8e919022393c2e4f366b46f1e60d14026e7c26b97d77f7a757bda5c121d108e2e9f84074adebb60994b4ee09aead75dbaf623173aa4ac22c2feb68df5f4d0a526fa9101152fe208959b2b4c64d388e3c5e3d40215b8df242415b3846476f06a7d06050573713ac8cdad0ca2006f3ffbf4b918434501865e6bddb3e0b93c403bb9510dd21c32b47a7a9882964f4a5e4a4fa78d7366def7ffd9b2ddf61fcd4bebfceffff1fa0cd9cdb2521c396bd01f5c13313beaa6214a198f8ca5131817ccfeeee6e347c71eebcb603b1a123693161a59c734671aab7f409fd0f28d1dc1bfc0f29c3e4c7bc20cbf250396041e15ddbb4bd8bf07b767777a42c249c7b4fe7eebe33a1213a24472867e3c50b628e0b0a510dbe6727e672391c77777777f7202e45a2f1c4b907714076e7f9406f79efd96b3877770722aab7d4a839001db5f6271c9c3bace52ac6d58f7a25963688745f6cd88d301d399ad1e5ee633f50398ee4cca880445afa1191437d91c15ddbb4bd995eb6e9459a59bbd62517311185a42a2c9f2a1f8f9755c60739b2e433f4734b5c9beb2ae97a9a58aca666347d718506a31f2490e5a91f8447166fd9658097845112381fedd004dd4d582da26b83dd12d7e6ba0e5cd15a6b2049f5962a974de7f30408d42b70cf2f7067edc0427304deb54ddbfb573282918f530b41bc1c434b30a6b2dcfcfeffffcc1d1edc7fd631710861884168bdce05efdaa6edbdc232a237e3e07b76b5c5dddddddd3d884b959ac4e02f75bab2345592daad2e40b28ebc5ddbb49deeffbc5c89b95c4e664d9734904b66d48b5fce90231f4515d8e8124b2fbb62964400843a939503ab36b304cd8a22b49195861d58402c79027ee1220691e97892e1f3b58b8699392504b24d5b62ef481ccdf07bee6469ebdaa22accc2ad8fdd1a6e11b78c5bc72de456d24ddc8133eeee24c434ce4ee3c01a27d6383425232e30d6955b3ac4c6c7552583a9b5d33484daf9704319c673e7fdb825aecd759943ddcb4fadb5f6edd45b0ac734451e10bea09c73ce334cf5964a5165759dcc889dd0ec5ae5114f1844e697ecc17a1159660c3073d0d3c00a9c94183c3830ef4e366ab1b5c23291c31eafdfb3bbbb3ff5732765815dd05299624083d05411244c4d5490cce869986293dfffffff6f2cc5b890915b41d972c29b8246997fe55aa429168b3cc5a0a25091a88854646a560928eb55aa9135dbd1b0f1003272411f71244e73ec4312791ec1c4258466bc51878ff7eceeee4c285e9c7bf03dbbbbbbd18c13e7ce33519fef7a4bdb15b7c4b5b9ee9531c32c1c81abec98e80549015271e532637be0947307823005757757712084ef7d72090af311c3062a2689e297fdff3fb9ded20c9c7573ce43e0213bdffb493e7c21592e930b38764ca86b6b63e085e6d18326b2851d764b5c9beb12d9620f98bec1f596b659a78265310192e20aa9eae7ad1a3e65bc8899c96514a2ac788ebef0aa6924516e4c315b286dc9744f392ec664f411926819ffabbef5c4c9b80e69261ed30a0cd06ddf3f5725d55baa5c660e554dd7b9e00c9390fd40babbf3dc6cb5536f291ceb132b1aad81447cc35313fefff7b9998318d0dcfef37c81102dc6863a44468071d9d440aeb04a61ec040f32c566e728c4696cd8d5530ca291330386870ab6e20945e5e55f07f724393c1e8ec35ff332a5ab088bd34d915a109d56bf3361c38eac6b6bc788ebbfecdf2d716d25399bb43d77f7a41a1a9c3b2cab81758ddc32153b7ef944249d6822a1313d323306486fa6f4815f1519083da22dd39d791585553c4392a8de5263e6903cd60280ea322cd3911bea04800a0bf2518304588f1d3771115186020d4ec0e44a70d960690153ae40116080686c5ac134d9fc40be2c19309bd1110ad205f463e1844b8b1b0818f05a19723c423da990fd789249326aad354b53bda5ce27d4034b34e701f8aeed0666d2bdf5e9a6640a366af6fbffefad37c6a06cd8cd1ab6407ae4e55cf9cdb8d0ca11c118cd84181ab934c1a69c463f6c58cb2b513d97ee3f7548f4dd5f49885c386106754b9cedf45a1ea9c771a8b523f366b311ddc731eb04a3f9822175aa01cf02459c197ebb756678d7366d6fd7efd95b9d5b92ea2d55b61d5a96bb16c248840550aa0b860dd205fe2970fe42585860435081e637b10e25730f28c011fc6cd7a312523e7777f7b8c697c117ad91c5c1f596b62f3976b76b9bb61d8ed16affff4f51aab774d9747e90f2f4921b37d3188b6ac75a66c2ac20502df4cceccc3499c68c18728c6e896b735d1d31b3fcffbf2a12a45e51ad183b6ac4dc58a135c3bc514fa6920afc98a4bcb4442075df33d5e399a6ac66b4d65af7bcde13f49ea1f710bdc7e83d47ef417a4f32c984738f73ebeefe434abda554595d1d7e88ed3a54c15fc10a6cddf663fdcb56eb0bf4c05ddbb4bd31ac00b0ccf994002061c7468526a08f538c98b8c5c56d75feff0fa2516f699127731014e4debc0ea9e248584700f4065207e44a41e8e05ddbb4bde1155c7777e5abded2e0507fa0246a1d858348486444c72b16d1b2820aa8a753cc8a698d94a8962bbbb6697bc7231d4c0c77aa663a1440129dd24e13834ec693526f29555617089e18887717ab42761d08817db4708a95814b24b0495334e9da7819feb9b2a45c87a477fb1e4e617f20dd3cd98e3094268aac64ec70295dd860b1462c60a5bb3b8f8010402eaa601848979ca1e342e28576f4d3f56c555fee9085d46dfea924274bc6ffff1ed3d38e16ce3de21bbbdefbff07aecc2fa432e92b6c967da0146559d98b09d22763cf2f6719e6d3028d4c0c7725aa9bb66b9b5693edfd2b091203014530203a3e2b462ec213575f31173cd08d207aabb0686c5878666830069deeeeb323142b091316954284104aa68965450015cdf32034eeee4094989669c8e9be6bdf1febc50a6ea74eb3ac36dca25f1a172e71906a5e1362553f532b869e47096a6473584efcff2fd930e2fe338fe5a9649885d906a7d74bc3221ca524c1403183a973f4a29725fbffb96877241ca872ea0f1ff9c99261bd8481129f2d0f832e25a70b9c17e371a5015de6b480493acbd199cbe55ed4232e352221750c03ba25aecd75571ebc382ebe50311383d34c8704616c0af0a0e9d803babb2fed1071eebed4a38cdbf7d7f9ff7fb9ded256657bff40adb536a2516f6991274808841191bb3bef3dbbbb7b8ee77a4b5bf6cbab60a092fb3c1c5945474bae1b37b61e05ebdc1ded162c2a63d3966dd7a089caa45c6f01adbb7b902cf596767d0822b683e30c7e84e4eb5d5b12a77b5cdfa3891584172873f88efc766dd3f6ce3f150709a2a88c651485081d9c98c705ef02f72fc40264bde00b3c7ce01da11a3122d6236b66246548887ae755ed150b295750e7b925aecd7575bcff7f9f32c33107e36628e377241bf13462e7844c0ac2c5b8c2ed8861924437a42899c2ffff41f70e46a83becb2cb215459c3eeb8c6cc96505fce395329d55bba6c3a9fd00da8a239126954acec11ab2073a28627a6451126299e8f4ccc8acc8e64d218f62ca2a43453d48f180e50345b9ed25b2533072be5762e38c32939e79c6984d45b4a84c494a2cada40d38562b66b9bb6b78f75e69fce9945a2e085988ddde9ce5b89ab556ec5d7dbb54ddb7b85317410b582cc9325684515c49a31a26cd95d31c3da646d7a01bbb65d5e6979b1c97b4871746df091c8c26e896b737d85b58e987a4b77704c53f413238fbb5e79afd65ab740546fa9f1884c9eb4a074770756d07ece39db20a9b794294595d505828dd8575044f8ffaf9273ce5f39f596bec121d178f4f08564c36e9e96d4647c75199e81f550656042dac84e988830861eeba64caf67be20d9a35733f6a3455cabd81234bbb6697b8f2d6742d7bfde0ebe829bc54ce8738189e853b3719622f524d51d0a115b2a502d18797e85d602ebad0fa452503174f0f8111183215b412648cc1fb690d1e77f001b2e7d252f7cceaba98feeeebe96242aa6ded21ddc012cceee6e8d3578f0d414ba2619a880089d1c3b7ec2c246a4d0a79b212c95f79edddd1deb897307aaaf6b6389818ac26f23e9aea991109adc340d64793430674eef6c802e6bccffbf12972a091571ff4a9c056a9bb36fd7366d6f1a361e324052d21328b97b81ba7e5804edc080250cd103244aa80495a36a840a6631035385d09d64ace4ae6351d82e8851c70fa8488015b7c4b5b9ee53f0c52d716d6ed4f94e09470c39edfc8f58002f5b6eba444cc6bbb6697b67e07b76777727a025cebdc689c84aa70ed912aa3ac4a54ba1e7cf4ce5bde02a0b6c05a99899a13fc2c8604442a849e83e29022c5feecacbab97e37d6c6624651b881cddf3d1901808804a1e42b2b0cc6617785cbf614909b7bef7ecad9635b5866bd820789221634a450c142c1b303f6a52b462ffff19dec761e2d803b61d8ca2be357d6a0d7b5390f2a8342d9e46c5009d00331a041c41c3401cca12b9a6031480070d8890bc7888d02c1286c6c180480c0086024100180c0602018140202c8a817096ca62b805e646dbf3bd2e64f64fe4f004eb23ff0e699e3247d24b954b5088a40ecaa685d8258c2a3eb4ecfe165df5168aaf83b3641af522ff11f7da40c0a2cca02c46d2efcdcc6a1175a1daa9d70e2a750166303407f0b645b3fc83a82b1e4899184fb508d9357cefb368f6496662150a6f7e785018b411225e0e15f5890e2d8d344d1149d7f438da024305fadde9c440f6f31737f3568755bb2fe44eb98682b9ed86ba66f98ab4db8a862750ed262b4417862278d552ef5a1ee7874ac9ff53c3143d4951a8845e4981874b338fdb6644167f70da9070c9695afbc2886b3fb9fd8c545bb6e2da3c96ac914c53d3127a343d6fafc05c2dd69c4ce918a410957a367bd3c9241d7c1218cba1ccb4637b47115544336634360960ff0568b26be93a44a1a2d9f59b24d4d66695674084fdbd875a244b6e018a06c2095e2dd4ab8dc170c6fe37ba6bdbdbc1e8b152458909ab6d0d8a7ecbca1261aef5c8f04b1c3068a2b35b45a74db7b089e3dd78d2b48c6259262ccc5d1dad46da316394643eedd95a6410cd1e4fcd54fca74e1f6da5b8bb3250970d8d5cf4a4a195d4643556a2188b7bfd5559311193eef6871797c02bc2de8bc8834292b0731e924411129b9ca537a6ceae2e270dec2c56a22b96e463f562dd90156539a401cfda7ebb33e130c2b32ef4653f2413e0940f5b53b2c237b31677e5fd1a5ba367da9a8f52f3ba211cd0a86acd14aa4534574928ada454c5c91726a7a4cca5c9e68a194a25bdd2e1ff64f5ce58c21b4eaffe332f7dd0630929ead5abf96f88b98f4712bb4d25861070fa396f6c3f228aa769d1bef21e9e5f3ddee813d442ae61f6e8103da1331e1fa9e78d1736369bc1bb1251fb2176cbdf28b32e55ade7e471266c81a4dd06282c44d1aff58c1627dfb68302d90a51f44d311893b313a8307cd6fb1475788acc3457c7b4fd399f0880683d5c650b11cd9d249aff68132935cc4ed966f320996adad48ae4546194780947df9a4e180080b6741118468028346c2867ff5c8cb30a14e029c09e75d282ea573c268928293a1f81872649162790292cfc5167a458b89fc6f0fd6686b2a058e8390076939b60b212624324df61daca31cfee89ab31cb4570f2e01b2296c28f8a721385c653b853610477395dfa84c8bd4a47210579b988cb39a3a2c755a18cc1bd73f346275a053857a265e82b441021e8be4a6224d4d03359ca241f3d7c14887a47646ca2c411139ff02fd8d3318ad02125e321b6237dee5811d423c8a9a8510786e2f6c731a10a01da3d9a5796898a7ca49222908098242196da2adf276c85713d91c840af479be33dd44a5eb0e00c6ea0f66ace750ee1db88d1ed6f050d177a8d4d6c6088a44ce2b2a490949a94b4b2a527de01514f2f3a3301a46b8f2609de770961ca6bc649fd8aa25cdac6883f60893904bfeddb91fe918bdfef655f3c8e517069c457ef5e66a650f89c7757ddf4bd252eb8bdd1c0613bdade3b21caf239f0030c93d7aa99a04305b4a17589908f2b2fac28b7981b3ae9e762258d3f881e1aec599b58884cf5cc63bdb190a6736247b98d5cb9fcd2610614e180a3298dde507fbebd8deb27dc403ec12cd41cf3fd70695f9192f71dfbf654235c6e90353490315fd6c832bde9802d0853ba985356e9db8af0566a27858c95138b2db3035d7c7a3a75dba3b647d4c995ce53d2c5b4d91dc8ada94cac2660ae2d76e22ba90317ed99b94c64fd6e32287beb800bfca12f1d2a2218ad7d1db5ca5d3b15a28269a39f7cad6b19a4b66f7e239af7f89607a8a796e0afd971daf7767d82898e207218b3bc828092d7b227d770933e0ce683636435391513bab68dad1389cd709cfd3c40e78d02454f3c51050c97d335cb8a9278364abbbcaba81e076ad8261218b934eccc8c43dff240fab4aabaa8d5c6efdba7c461f7be65bb39cdccadab8549a5b52571d0cb81fd05257f52bc54670ca95a3f890155712e5ed711a3a30a6ddb7ff721dc7d60c33917ccde9916c22a0b4b04d0af2cf4d33c4e6e10b9e3dce44cc35e553ab56df458700e79afb0220d95858da2acac25d9fec14943b4a5a1fff0bfb26a4dd03fd1d0b18b36fe593955a56e6c0b044fb29e7ae1b62415fe0edb3738f03ddadf16285f195300a2f2ee76deb793c71105df2539ba939f5340d31ede2c94a4d025862142884f01f8b598b61adddd5408ab29283531086f384921340b5dd1eb61b37b68394340723ded2dae6516f96f14f7a417ab1907ef8f3d8d002807e59119c18490349de164614cf2a4d018c482df8a54fadff007d1eb27bd59e53fb0f0af55089995afded158869ab33264e0d9ec7bf7a2abfb5b2d0686615895ea0b03f339048da25177b97eb715f87d1b76e542097067eb609b821735cd01863e577b6046e6f6ab46eeb5e81bd192f6d9586dbe510289c53f0e68887b0360b42f22b057fc969c9d4526900a413ef5d1d03f08bfb46d36078b080c9db4de95ca92f557d53820708327b4cadc8b2892f67e0abd2f158e8c14f38877a6bc6b9c4f3276af85a56859bc1a55a67fd2bec14f8b438d341d5082006a530ba240c0df3f9c5422ddddca3933d61c22138dde9184ceb36a9cbf6fb9b5fef5e3ad28aea8c716ac000560b91113db931cc3057892624067762b3a6a9b0941a5a9c4c7ef8dc2f56ac24df912b4161ccd44b5ed0e896a88f89219c826ce8f87bde318ea29d098ca286bce340d0a3ea2b03a4dc786543814d0d7e91bbd61729a3651746d1fa26c0c61c4f34605d342b5c3ac9f66ac3c78bad3d11a88b7ddb2cd19c17b8dce701d600e729e31978ed283e17bd13975471a3cc41380f61b06e6398572c969e0a97c8f0e0e1ffc1e75de3e8ad321dc86edf9aa0ae222941c5ad99472e7b497fce170223b142e1d9d710f84883ec8f038cf50403ac8bfa5be213df902c8c309038fd2b8b108378a2f35bfbd9111e646f33569271d6b03fb15ccd2e646434a6bd7d12267fb3cff5d27d2e98334a71d985bcbabddebd1423a96cd10e7887b26343f81de4cabc0c072afa62e8c7cb05c6aeed6bd1769bf89179a93f1a6cc33976e5b49095f86a79dc57f7cd358d583071d03968b122cb8d09a05fc47adec2af778011f7c542ec21fc2ac4299c316b9b112b064e15331e6cac305901a167eefdd1defcaf6816b685ba73f8b3cbecee1f82bdd2b89654323073de92f0279361ddc40b851462f2bef68bf7c6c788e0b05565c089e13c681f12679ab7ef9ba9ff896d673ef47529ac26095eec0fe4123407ab6faa661484aebaa842b7201eb6dfb528c06d760a637fbae62f1d3bc14d90c54b814df5806b6921652d24ab8e6143eada637b0cd72fa85511925f5d25b5019bc983960a35c72aef5d1235a162eea205db3afa039ad0d2e604bf6e5f0aa5b5d8b602d54635bd41d408a472636a64855314e8282d9b4bbf573350423b6d42693728633f2df5fdea4fc962764d5f80192fe48a89bfd148273cb239add4821e82f9643abd542ec7632e379c8cea7bebd05e38fb9e072b7da4c52bf436734d31fba1ec750e77db919b597124bdfa5214b2c8ecdf37ee72fc8b79da76f87306ec947a989a549238b1e6af86b9577b39aa4d7f14d3acf0d74b922082611b0eba19c2f31d3580b2505c2c15283ef06b9132264df40f716c2c541561bac5bf141f24d724920545cceede2df3f42b39d5aa37946d4b5ed98f05e1fb0e30bee5a504e93f4498f0e111d342b496ad2f4e1f4ff849c982949182dbad8a5fa4c9801369e0f9fc5680685ac05030ad4c4241faf9eb0a0e17b0b25f92004ccf3f3f5ac6bc5d61ab9e36c80a5b3397a18d538253934ee2c38bc13353534c1106bfafb9dd6d6cb96c74f050444df427c6c0ae8fd0b2952172f5f981c4cd6d23cbd4789c704e0d6f09bc12e080065c7516174ac9d4a35b5035767f959234c16fa345d631204cd16fc04323b557bc95c8ccc5953e3abab2b23c449f7f2660da91a9d322a49ce4fb65751590f6623eef1398865d64680c9156e7910a949b7f4048c7010441292de7129ce5036b030541da3d701c3fce1c711ef2e44b0e8046bca4094dea630ed38264db66adaafdbbaa81a47cc44616ce544e1419ac089e82c1be936dd76769eece46f515713c1f02649320b810a4062230def881095b3f4937fe3b0e8c6dbe3ca7818253db8ed10c6d2eedaeeb1e1ab8f3be1ff8f77209a1a6e5c21860d572e8f0300173fc6c2a2347e456ba045efc66b1abf12b2d76760aba0ec363e19c1a04e44733a3ea7bc8ad605d8e9725d0d49fc0c25d494e0344c50b616f398b342edb8a1894229c75ecd67eb20a2d7687cae4c330d13435ee91c21c74aa81054cfd8170ac3306e3fb097327717c6a0cbdc5de3d921fd7ef7a56c2e839d01a4b883fe558446cf79df45115ae624eb16c1960813dd326f3cbf8594aa969d497a53f2030e1964eae09908068ffb047e7ffa76a9d4564c424e87b4d7e412f2dad17699342f204022683428ec9295c3ca27e0ddb0c2f6e11701e9cd20969fd2f6c41a10902704f0ac8769bd1a50d1832c23d4aa888bf3c5d6bcda86761f92709fcb0c60c26f20e1ab9a0955c7a446dc5a28e9e1e371b81126b7c9588b349d0642927c3c733ce462d4b41fa86eddb5f655e5ae883ce7799b4156466c33b15f4e0f4cdd142eae599fabe121b1cd09c5fbf759726074cb015a22dc7dd33f7e1dd1ee44b9b9999cf6b7afe8b95e1331cbc12822a14a6b76411b37f65ffbff06f45ad9a19d4f7b3dc8c2ab945aed68f4d6a80bdddb98040e8fabf9ea2e33939ed1f67f68f7711d1ba6b4090d8e1ad131584f88aa1b87b0d1794ae4e9eeffae272335d065c65885e150a41a0983350131a91f648dd8ed9750e39c62b72e532def6ea17e0bffd88aaa70451cede72200c471d3a5002c5e0f6fa24180d1471824492dbdde688bfa41fb4029e138da56dfeb755a3dc949d459cbbc5a31c0aaebfade577cb36c49acf1af887f6679dee08e0e36bfa2d64938bd418cebe8c1067b011ea30f7dbd88d884d65e340493895c03c835bc0ba48e1c402839721a7e20a414691b12ef4ca1ba04fa31f71ea6a357967a29919bc7e7fe5edb61ba0eb613ccec12d7779d4cf61a6380d3dec324e84529b72deeb6bf2ae9b31323744956ab86e8d7bc0a41493c245c0ca0ad88cfea98bf6a97582645ff908de9a238ceb9c1415b68eadeb2f8ab10624f72c6559612ba7fc1323dfeb15bea4a857a0f56ec1353ced24deb1172eefe871e56575a0bc12c3d50b90bca175f125b9a3da0bf442cd46a68e06a86a547ffe5c17cfa11b977d968e4a02fbd41d07ff9e4d54d866965af5736f497b826c02f6d9d3e1e9cbd13fab1286ad5c4965502d99cf32622d97a200ac8f446cd0a477a938c0c13c9703a4dd57eecbcbc742fa1944608a93744ce7051996ee05a4239cfda769080fa6744df4ff541f1d08e784b07876778caeb692bb9e788673d9b17bf49fc5dac918d70377f1c6c587b8ba233cc12320a303b55b31d2b34a115e96a5eafc5da000152a60c3294c4817b3077df6cf4f3b60db39983e05f13e7c66e084105d287db7ba5328224be105f81cf482361c6d34632366b58484e8d25ec257da93ff08871708b6dd9ea121730964a1b8ca3e88d1063efb192853e840942130a0669a653da7d2df75522648aa574bd3a29257594a111c53b7421b60b088916063356ccf5d84fd6432eebe295c3552803800da55593b9f378fc8ffe76e2371bd072d3193c91b24adf9ec7b751630309762c578eec13a747e353128e64daa4a423933ef1e90a451973330c923cdfee5d7712d0ac9426173e69ed3e21c4b2cbc82d5fda3292b2d9a5799380030ab75cc9f823bcabdfd466e66a4f8ef611c1c7583d93470ed5c1d64217769222423cf278a34bffe5e1c513f0fcd84146f85880f415c520cab65967690184994116c5216fc10793b5c5c415896083570e317e7d7ddeb3719e333f36ca82805985976c9e4b50f355f9f194840ac79abd232f7d7d32f9a6efbedbc36b661bb782ecabbfd9ce877b745b38e77ce3e681d601763490bacf0a99d8c177aee097ed7eec233aea98077c7ac2b979e385d474805fab5aa88bcd8a964ecd4957841b07361c04408e03909bdb6cf698070725ba56209131b194cb54404694c68f6c36a071ecc559dc54e5b87c7c05264aa022c965107a3ae4cb2f0c72bd56eb113534f7590d4dcfb928264ebfa28f548becd638e42fdd4cd72429abde6d3bf20c95220a5c6a481da5ea48ed54da96b1e8577c332d98bc5a6b6d73267ca80bce40cd021d4447ae98300aa46b93c2d31a2d2246d8c45411993f7cf9215b7624817d8779a3c2db8328d10db2204bbc787500fdbc5ebc9bcd71e28038a7da15324eedd453fb6a54e921c908e3694405019695788ecca1f3b46b20bf381b859732ddd868edd1a7237ade96a05a5cf12e093438f1ef7f3b33b5cc7436835a6db765cbb14643b682b0ff9dcd4e4ef8dbf1af4097881b93880b6456dad962ab116944774b732b3f1c45a0520ab8514a0e5700cf36510c31584b061f1836b699c521ffdce1e99c0e89b5dcfea76c532f34f0f92d811440128a9bdfe03e2f4d24683d7fe8f183977ea8f2d944bf544358e0918041d55223dc07b43685381e118c6a3ba1a4fa713d1c2a4a3a97839c994f20c3013ebdddb59b65bb0011f6051f586c24eaf547bac67197cbfb47e939d5e9b7dcde3c5d4ea20b1f7c060741c76b732e4bda7ff60d96ed4821e2b971b86bba909b6939b71f8608eb662e62914c0d4b538c147a086beee2d3dafe979001300568cf821027573870356c330c6e036badeb53df3d357b97f1c7f9d7d370119e77913b12bcd79dd62c0785216292327e3a6880af7b6d7807929cd3342eba9283427020456e980d1d643f21d73522a0cb0ad4482931688409d813baeeb6ee34f654c2cf8cbe81eab8dfbae134ec60deb1b3ead5e5eecfc8cf8c804c49a988d5bffa77054d3b6fbe58bd941948de3e200e1fc316a70edbb85925d469c5e2897465d7c3e5c010646c36e09f1593e3f51735adc6bee6f9fa557169481a8695dbadd30c6f2dee99e6513c54376ede961d573c1f3cb16db9bb8185c3b908e91bb54be0225aa6d51efe24857de5891a7709bc9eee680c756461af9753bbfb475daf342e6ebcc6fb748536c58fd5b7dae3d2d8abdae2af7f8a116bdad5cb00d4a8215a349bd917ff1d450cae6d3b975f40630270d820fb983a30d602c66dad4f0c1cad0ec3e58a3315bfe59bba5018e46b956bbba8c77281aa25360bc431f499e549e8f928ba59527ac5cdcf39c07d3042e86e39d6a6ae8ef0f70a08d1fde3f7ec1bf6f66b9dad0cf8ae90428b8444168a90c0580156220318e5bf2e8b5e70e2bc6ece9cb04fa0bbfae26f607b804aa5736168509658c609c86a0cce70ae5daa08b14feab81e48c9bb3c59c1b0f62ddf690d91db364ea3286cf219e29f0f005579897d1bd9a8a1c97a41b800825c44be6c736fe3bd7047d0a2bf3124d921b7254bfb9d5993a440c0954d4c6ec53301b96e1735344e1c425a189ef90c7293dca0312772189f5c57ca07e950dd954a766d5779a191922e19966174075facb6308c5726174b9510075770f7915f3b06f658ce2a5c7b60d86ebd88574fc55cc1c02b4ac3b4320748eeea6132dca3dc6f5ac179fe71d6bec523be767cd55a9f31f6289966041c98313d957871d67fd236e24cb549ee258f7fe3171f1b09ce4b8f4d696fea15fcd087c75248bc919c76e974643797a90056c9b6398a340bacebab26a790fac76ceec942170938318fd9cc51d85817315db8580f5e34d1a00c1af6d05a5cf14e76f8e84477425ec910a3dac1e6c098c1f147c127e95b3f4bad2e01dec8c35931dce2c7bee53598b6bde7973071a0a6955d9cb9972566a80cec26f0e8ed81e7951a4efbb575ad6610a864e58422045f40d1f252564ec548b5546695e831bfa1fde8b6c5f919eeb6c8ac9e52753d0a6c678609984a35f7b19cad9d965d343a2b71f34dc84040834cc3d4e2fcce21788c995a6662684ed2a4be0e04f9acac09651a0754727b7dbd694d2a53536b13af58c89bc8f73397012d16edaf370a3bdcf4a5328e898a1626611c9f10af07b53c1deda04033e1b81101daadf8ff6df92ee534e5c8c3bbaa20b44253fabea55b28d22d2391346911816d0d8cc248a2140fbfcd8f2d0fe273d0e462acfff6244408d2c5b54e63afe35b07bf388cf2d7daa7fdc18785549b578d659486d0fb1a18d1809526af40485a1a19d2139346285d54edaf80860de4d4ccaf17529a48a69c4e4122edc0994dcfb1a90fc88ee511332d5337d4508933a67a95d0dd4a23250ba18c0c1235783de853b9011daf25daaafb7eb80b34e1f5a26cc4d3512b453fa11825a5bebc5a20d0910fe038ad381bcfdd14243566610fb1b5a3352cfa6b5be72c1704e45c9c956ee598be33456ae20458f125abc95b84b80240f89842a3138b5ccc9822bf3179799844872390fb597743cd1aa9d2b44d4759fd48e2d162ed7fb41c0bc62122f892364cd6b72927fc6061d4cf26958e1ce168a1919b6d894173dc8d85412268b0d379eba4528ce085e30955786f12d57962fe451c2d523d51cb9baca878322bb7e297b4bd87e43b7f6801cc12245a7bb8d7cfd9ac8ccc44795035eed38ad0ed080afb4c3c5485a52ee4f5e620ba11240addda1868e97c3bdcd8a83282330f2597b97030d425ff6092396afb002591b82fd07892c47c68bf3f62e4081783b7a4d67486693a735b34f2eee3b1746714ed4125aceacc82d19c9b343f54535b23619509c2cd0420c1cb025183376bac82afa30eea09ab0c9ed51a644b4ae5e5a863a63f7654adb046b295240b0e88616e53cf40aa602bb033d908a5444e5a8b78adc19930591c01946db369fc3f6850c874119197ed6d45624fb2a56890498be906d1ce405ab12ff4ac82ce1e23002c7a366128a2f758c59bb3d6bd306e7f4e8efbbd3d244b555c0a5192564390d69a9711fafb7b379ee72889f1f74cf58806bdfde655bc93d6b07831bf75cb4c71033198f2e8058f188a297a486a32835104968cf14f3d755d9bdecec0f94035c8679618a245f22d81a0461ad0e024054dbe3016f1fcf2a879ac891fd5106608ffbdcf3d11f45ccee091dda2cb13f2aa44f20f1ab83914b7b0aa2ba97b88d928a4b8cc0290f25af77e643422aae968415e4bce91a313487745c8d3c0505cef3be10c1f036a336c9eecdb3d4a6cc3d0e51cc8d0cd274f0e7242fe5f3139b383ff3f9515d9c97c000128f1522679070c725ee9fc00736a3c7441915bbe9c3c6ea7bc38e42b1d2d6f17717519c1b81961ffe69773e700549e494b90ff1e010340a9d813f2eb6df8887acda12fb90000e0e505db744670a3208f4c0e7e920f7ce35834657f4ab8c0184cab5bf9f0232a88ac0051e5798174988806eeea46858b5ed68cae539ea970199c98b3207671c4c270452761cfdc2c7119139629d19215776be4531642195195a6abfe50b50abe36bd26ac4e9b72536890598f4ccea5b5bb026c21ba3eef3f14875f10b37d783f7120bfae425dce01f2323a04515a6c9ee957f91dea4811d11b7f188ff5a272d89bc507bc2eee213e03917cd2c8c8fc27cf6ac5fc6c89dd8b629ece1f6a754feb15d595bf71bd6efc3947040427ae02fdada3d6579900ad5d668c16804ea9309ec56a54bd70502cd7a8a787833923604a3565d7b5fe2ff4e17e53cae04ec8f7a783b4fff7744826a42f38d87fc31c88239e828ea71d96cec115c0fd62a611daf325c5b310ee38c6d306358bfd6c1c6943b37aa6463083c843acf5a56c29e8ad8a2b92972d33d2ad507858986e49f9d4b578d2d02bfd5bdf320bb32fca19adcfdf3a1ceb49f3b1066b5af0165e8d449fe64a3e206270fbea7495ee99a4abc3eed2d591e4067a1a5a47655eadf13388bdf7305abf9385585130b1136a61b853b6e18118905c18629862ef04ce1f9139aeec31c2b763aacea6371067789a8f58c305fef29f4f6f8cdc789e5d7755120169e858363d8ea82ed17da2381a03ba9a4d94a8ec3bad452f278bfffb40a2879205aef144deaaebe4bcf2ca13d15911cc682f0a0cbe43c023f742dad5ea478ab83d98aeb6ec983b57c0011f22db7a8cb99f2782e7d055ec5ee8288f62d240627c95521277bfe3d2e121ceb296baed42a2fbc8819e343492a39730a110977e7d1d8054f6fad40ed6dac8c31eddda1205b087243335b323b969591237811282f7e83e6639672f9ad9fc553c37d93d946de542851b058b3020664141533e76557d75ca8fc5b0ef810ec596079e81d1460f3091bbda7a7c1efa3aed6003185ca144617f87e3a7ede9f743e6e977786f6fe53b4fea9a3619261d783f946a13c3fb9114856aa06f18b8c3be73ed7e2866370ab06f7979a58c16f7af20ed76d2d6f91b0af738cc5264d6f2c6a786aafc1c01d598bb8fa84f6a7e454d0348b8bcd41f3ae1f7b028337faf664209272fc7ec0611a8172dcc269acd9f607e26acfa24ff16d8e772dfda4283a63a73a372ef55226ad58d19144a526ecbc8419646485e042106076b482d87c9648963d2368ade1b834caa97552b417043cb900136df1c242e3f7eb56d79ebaf56bdeb6e235090f971905b7f2e182548929e025697e2edb295f3bc57b63a16f9efa707c277dccbe9ba94087596c46f533dbbd05ce3a6d605f37f1d3358776ffa50d7228d4a5237d2ed02d77a834fcfa7fc44c1102aad4e244a6cf26724aeb15dda5981cf8177623f249b8b763ba4edad621d119c4b017ada842f58708e25a503f6a3b75858eda21f429f7aa0d80f9444ec0bf5ab0cac03d0c682caa3257748598db2963fb767ee8393d7c5f631bbd4b858b236b4ce4599c576b9d8801cc9dff2af3ee5d67be05e4a5137c263b04d64f6278490f802c202f70230305157835e2d6174702ea87ecc213b267e7070ca091e77e6726d5951d6937555e6f6ff3bb8c59c9f73ce990712a59949a5071e4b9b07a42e674ae95cb0544f6722bdc59c1e8e471d4986c04cced04526e48825672ccb4430a78fffff4d2ebe953f97b541d6065a98a7aaaaaaaaed8ffaaaae1106d0e6eb05aa295befc88f891210a6745865536c5cac54244229692549abb2964c2477944a301972b960cdb411c2216c8a563645f5c99868533389a46555d814b635eae3ff3f972e83e0eaad3f9a6be0040fae9a96872786232aa3a3af1ac618bfb428cd7c717a8084bad60e4d702bca7ab2ae555b582d0d8d20451897f91ad2605eee91f9d329019cf1d8da8367b0bbbb3dd1bec88a61e179c191fa6c01ecf88211c312e300ab0cc74156db5b51d693751dfbd1fdf021d5c2939c2140d61b74621372a222a25122e1c2ebe3c6a6a6f6bbbbbb3ba010629cfea829943526f115326b5dd30865d5b2a2ac27ebfab076002a143a70eb122f898b369b6dc5995c318b5043c9169d5bd489546fac0a9bc2b2972003ab13461ea28c32ae13c8140d63580153dddd352bca7ab2aeed29b5b505c4465c3f13292ea09e8fe3865b7777d48aa42e2b63570dae271ab8da08a9c4058e4b77b7cb6229182d3696dc9278dceec32e5ab66c5ddbffbb2af84ed830fd9284c0d29a5e52841d2fe5ad47940f175dd45392f4c678ac7954ddeef64c8110774337f6ffb18e28cd44263d60298b584b9c333fb843b537e79d4bce39b6d6387d0504f3ce30012e3039f4aa0ce994450301de8ab29eac2b0f2de608012128bbaaa1a94645950c4de4d443b49112e0032a9d4bb76b8ac061bb9935c3929715653d5957fc12d6c7ffff9e77e57fa744f0007d88ab592308930ddf970d163234323864f87f14c39c7d5a5c162e276fd99aa41bcc38b6c0a2cd669b494da72041e0180faebbdb4ddf0c052676400b30785cb07462109da8ce14c12dafeeee0f3d1dc61b5a93b6ff33862494663e19ad368861eb1f72b1f2e7bc588c802220b960ed9011290a6a1933b8009821769ab090d8de7f96ccb53bac509ab9d5e56187d82c67360d4d76dddd6d18aa4c69a6fabf10d562fbff4c4294660e3308a662ce6fc953b2f7df4ca621b6a47a6b8d6cb8f16289dd97354f0e8ec063d0e45804b3a2ac27ebca7b9b73ce594594661e91496587aca5ad8396eea58fffff3860702bbf0e1b2d8b8cd40c59fa4b793897125d379272c920998f1b215217f15694f5645d77217cb6a6b6e4a5bbbbc99029cd549f32fd60e175bfacf8733ea91411e3f0ff8150261ac4feff0dc00986d588592e07dd750c09142f70090d21239a351853b06c77f7900fa5993f6088a160568f6aae45edbe6aa4436966cff7fb6004bc0ac618639c5bbabbd95a46a1f63be3ee6e2b94666ee10e325d286ce35815368565310c4ecdb1350cb41a5be3bbb229dffd4a5ab56555d81496c5ed77523d9f6d5d328258b2c26b7ef9ffe70280e9f6ec51eeb8c4136d58fdffcbb91473d3b7b19aff1fcb86d24c5d4f032cdf8f76769e865c923bd2610fb2fb6079fbf8ff0f2534b4f2b7b8d6ddad944c69a69abee4803cb37703c09b84ec171b390caf16c22ba89996a797946606cf30ee8131c637cabe59368ead71748dd36b1c5fe3fc1a0728ecf7ee09fc727158f7d41dc7b52a97aed89d7b61cf20dd7ad2cbba36f310b6a42d8ec8e7f032e7bf55f3ff2be9509ad9f3fd3c5002fe3f6f841a5b6245239c747c332335f5cffdff3d6c28cdd4f57cbf103d805db37a8b31c61877dbf296addd880132ba8e242ec618d718a234b378442695226a2c557c9382e2c17b51067892b62bca7ab2aebc921b43160c94d01325b613b2947104caf7bb3141614609163bac5e7e3ace760ad8fbea1b3b4598c7aab0292c5b03005e77378ec40b39dc4995245b7c97430a9cab1897c5fdeeee6ea916dd4a37c61d6731631fbe106be707a29e9a33bb73e6f1ed8992b1f4f1ff9f461289e4089b9e8eb6d7474b12a511bba99c730e16519a794426952182cb1aa7deeec52da705590b8104494f46ab7bf06efd3d4f91c32c901b784f8b218e9a8c68a05c0cd13a8f3b7eb83a154c62329634f4248f43ad087684305ce398f0f471146488cd022bc8f811923b2292c3e3a846850efbff6f3184d24ce1f0438be2ff322b9ab44769113c76e1fcffc16a5cdc92654a33d5d95bb25c4f36e89eeabc227470569fab730855da6cb69a99596c912872dd66deff77e8c043aaeb5f1e6753532f898a2bc8c66206f9b1e85249810848d68b209e196228fa884ecc540437f0d2d5e9e888712372bb3efeff5f97338e8cad29221a6c36ba860d163d97a5006ecc38ba526c94f08731cf3d41f410b6d8a4694811efc61e308dfc5a862472445a384ee494d0f755c345219b825d5cd50659dc11917839a71042af8e28cd4482b84afe38ae942b3760e5baf45dced852300f0613cd6870bb181508993671ec97ec7e79250930206408955717d111e70c4eb930f2faffaa224a338f1eaa9039dc345ce668055f1ebeb12a6c0acb0a91f4b4d7dad16f7a8325af407a5891347700cdaaa189d1825a6e93156528cdc4b52f4e4f0816406d8117071a8c31c638aa09001a7528aba5458f251c48bd4444aa5154342d3ad478a4fd7f18d44be97be9eec6f86d0b3f2576ba96c8ae545c8105d970c09df8cd7a5cb09afbff9584509a5994f464eca0648577aaaaaaaa8a337bdf5a95daff1f86446966324cb9862d51c46cac984e45ac57b3402522449e488f9501820792373d2db723c1125a472d406ee648ea5815368565956adb4ef0eed4d0b8015b21c319b60454b26abfbbbb7b92509af96414d16385c2b6dd3e3b2990a2ce8ab29eac6b1490b21cd54562e66c81a7d301f3824caba8c914d21b3143c0d6d4955113d412a8e753c4cb811398b2b46010f278f05801416163fe08fe3fcf645194c143bedc691221bbe15b427edd30b6d2afd8c7477d39c77004df6e47621cb16952ca51559574238a2498d24cf56f4dafa4f9012a996b915d581536856571cbaab029ecdb4c5d6663d1c7ffff1cb61c22872e6723e4520c1d442f1518bc69acdc7835e49d36ec9e2716d030a88fffff01ef8fcecc06d23d4368853c3e4470158cbb0257e002a6c88b5b8fedc724e34176cd7a39d7c7ffffa82572e5ffdbffff3bf5509ae9fb013308a7a0caff12e0e4c379f14ceec9399b90455ecbaab029acfae69cb38f10a59943b5b81e51902c083e49959a8bae2ca6254b6e68d316f5e043974b956a50ef58153685655b75c74077edbcf08562c44c52d4901f27b058b28bc2b12a6c0acbe217dd993986b86555d814961debd66505807a594f742bca7ab29a6bdb8404d4ddad43820bb025b29b2043390da51563657adf740b57c3d5fad121ca41e4f6c562654a33d5decdff5fc48d62ce3fbefdbf4c12a5994a0f64966f115343195239bcd8755dbabbdd54f9b7c45bb0375bc21155228eea85ffffff15302436214a3387c52332790f6cca9fe4869f7ae07e1cd0bcf58aff8c4ca78fffffc5b0b295ffdd4035030fd44c6f85f24d81f5758e7530b662d724fd09d6201fe3a2635a35e6fc254df00e05feae0257316163238af8308a0103ceab86fa529aa97eb744db59dbc7ff7f61cea80957336e37398ad408eaa7e12e9733054d3e2762f070a167c609d70425263c7182f39bf5b8bcb5d4f8432cd5b5efb74f27889baf1baf28ebc9bace786b075e60415c42e38e19352a88cbb3bb73a8cb8b0b7c90a674c4909161a49b8080b93881bf9b09d9ef37c36abf33eeeefe4262115fc995ac8acc07b79393d14cfd55d653dec0dadb060e54b5fdff1fb72c0df81e9596924f3227118d12274a4b48079f8527231f1c28ca48109934f460d0c1579ffbff91921ae9a9918c8d64d5485b8dd4d548628d54968998ec54555555754bdda563099408c20092540449662868464cc786d5ddff3f0a08a5994220a28a5694af92165e50f514b946388854a72ea6c80cfa04b595ba6ef75b53fb9d7177b71630a599eadbeeee96b7cfee99931cc298d8e8b101b4aa6deb1a65a4ca5bce2df43ae59c734e12a599ca10394b9baee7fbff16530a08885b1c331bb639e79c5f21946616059020a8946ddee6c40079131a68040d0371248cd31863001400070c6a94b4787808311687c5c1a0480c0484026130180c040040a140182c0a82d068aeaad17800a9362d9b755087dcf8d252e1468564365bc5a23186ea284aae53a43daa469d13895e5178dc5b2b30f6db8c5b8ee015ec4a8f461befdb5090b46549080152a5f5c6c1719314fdc262e01b2f7cb846499bac1e5a5c8b5520cd8497002e3e611b62c8d6356057eee490bdaea48a573a2d93e28830caef4f278d631235b561e51fce0ed32674e53f5140a552cb7c0d081c6388e0e53a5effafcadb1e9bb7b2e6da0865e6d8d3fa48fdd8523785e51f71ea1a27128cee1c5d7b313a04ba1da2b55f9be41aedc5ca165f290751cf0590cd0d4fda27e79761ae35b87b26687011cf31c28ad9d213cb6344da3e3f74b2b3537c3c235bd069f7683ed20d21d4b088ed678cb5fa2a8b5490f1b7bf61818323e03ed030f223019ad1775bb4d2eb1c7b0de8168040063925f1d163b92332dc60a11e8fa243ab93c61fa4c3ddd46864a2d2308e8fc72fba05207a5cff261d3ce57fd7f153a8add024690786bf3c69bd9e960280f148bfdd347b2c4e8917a4c69317fbdbe47b65fe1897ca90aaf0343c7295c6066ae25db25a775eb1efc34f746a1c8bcbd876ca1a2cda61554b1aa040360a9311f60f9eb859eb6db7cc115a2a1a8a6941cbebcaf6f168f51d1ad31f04589a44a940b87979d0107e60ec29246a8ccc420d8fcb4e3ea5c9ad027bd880c3948e6b7159b4a8a37f4cd55e4911a95427b5d31f4d2e256e27bced380dd8d246cc8829b585e8f429a07f12dd81c90e4aef2cc53877cd856996c1478ea4231f03dbb9cde6d8de7297ec59b50b45e032049ebd53d24e41279dbfbc974d67b9938564044581bd86a8ef7d6cdc08a29870510420372809173a9fafcedcec4568d34eab4f895b5024e18176a716b453bba8d17c4aa98641e0ade052f9272f7b879a028af2df79c3a056ba73b0dcc0ab1dbfbb3ecf2a45e574a9214208b332241ef642a9b3a700c8f059c4882a07b51c53c0c4c948530ddf491f68f199e39d885c90506d27e3097393314eaf300cd5dc2bdd5d2f3e57a48999a80f83cbf9c362786b75587cc2c110b6df4957d4e698db840b57f3990871489c67d1bb9709db69cb1d53706044b9c4beb1b1adb6a704c44f90b01ba8672f30afb5ce0a199edd47bcb8e111e93344e453b4d2244f262a824b6193cf528a7639103ac8b8f2a9bf48c2343523120f50b2cd953e8adc09dcbbc14975bd51999a584ada2ca8feb61edad30207495f2dceb01a900e462e6039dc910c907c662c6d15f58cd2e8df6e596b650c42f533dd0d81d1c710855ed25db4128e89420b34545bcd5aa3e8d1c07cf9370547599d25c9fc3535068d70d49bf3178596ab1957e640d3c1c9ec30ab08cf814acf3bb05ca64ff945703c097977487372036f25de21d993cf004ee36e30c75fe07c8e237a9a01dbb35788df885ddc18fa956f48bfe02d9850367135038276ad7ac5cfdd5aeaa97facc69751b681d13c3fcf0f0452f8018b861cc8eb837bdb995d8d04681627cc7ad38588b3024e671d9ad1941ac2b52ff4a94ff9ec9119c77e26f4f16b2da84947bae7c04807e6d7896a4eb000169b8967d635e87bccbb421fdf28d726084833494e7f3dd1f6aa5243fb3ac63ed839d08a4bcdc162b3595e9d5ebf4459d602799f8bfbd5586690b47ed354eab043c80b429f3b4ca1c0e42edb66179c066fac66bde8536217815d0c77fee1a07f688191d7f3b54a0584cb0b21db23e415fe7ad635084a5e405de7c966d880a6746f69be38a57145cbc3f38f131b5b21e5bdf0498a9e2bb907d5aa73183bbd5e33089b4311958b675a65fc285b577710993c27a5231644a4e96ae90a487ee4b5f25548fd8b6d66f546e5ebf31b5b22d90630e8e9505e9479a09a0b589aeb9267946fb14fe12e4b5bf927e8662b339ca794bcebe7924a432b6b215078fc811dd261095648c9f765445061abd52791a68bd1ae7496b0272b2185476bc1989a6dba82cd1e6595ba632c3c2539e2cc4490fddc1983701b68a2790f7ff8a7b6f3504497669a34a6b5df26ac7103b1be2d273be43d06cfe8503da2511ac3b9093abf2e5a23c034eb8629158e83605bf704cea275cba29d285a1868e5d6848115adca25851956892766d702a8f8df05f238a80c2a402072804596d9116cd5da5f2fe3f834001ac6bb2609735f829a9729c75aace0d836b8065dbf5c8df4e036076bdbc0eec5596eb839aa9f283ab945c837d4e89aec18423f40e74d8cf50c31ff70db8457a528c77a3e540551713c65e776e3272224a12720a9b9e434ddd663f81bf7a6ee289d5fc5c00297698a96aba2e380fea4cb7fcbfc438db3b037c9112df4f081eff2c5bf86c1c8ca5e279d4970ce10ac5b53260a9c2fefd849d37021d84db480268086fe18e2e5b029c72a3852341224da2635397b4231b8fbd5efb58bf18e9920bae9463a4980e06a20cf274249c6005bbcfe7ce8dd18c24732cdb4ac1bf83d11b657c55bf551d7b85cc81352d7a7c53e89bb842e069f8b2c124a6af601f9b69bcef5e6740a0ce981c6c7714b376ac1ed6ca411b2609a0d4efddf50162a9a18c4213803c0ed22e26c04dce7b9032a2229a2c083b04df26a7f4c657e471792b80fb7e08dcdd5298ea718e1805344b41e197f95e6edea21c20a06b24c5f28922d3d784a3db550c565354db5df30631d0935751bcf76da87e0e9a011f121f4da51412404c9d533ea8f1b47c2a766ba97b8751079d3abc46d9e1afb396038767c27d5737cd76cfd19027c2517ace2a75d1f2d7da2387c4a568d9c5904311c9fef21808ab53b5830728ccff0c1a15829d66e8dc39a93d514b76b89e25e9d555cb043cb9d3a0e67a77c8ef637ae9e59c1be788c702cc609d62950454a5d9c578b09ceda25e6bf646822edea84c8e59816c400a69df0178f8bda50a373bfb87b442a100764cf27fa86762ddb6f1c5aa225543fb481039a155063cef8bb3ff2e592becc16f12b8f93d10ca7bd85a644beaf8ce26b7b20a3ca57a6780188e2f69f9d7eab8449aa11e447e81c35a2a2c7771cdb03c1d4f793c81db60088efa7525a972757d5d2eb1776feba731f8830be46903f5f1411464cfc60e10d96c79d2503bd6befe426afa55f50d91c298ecb41f2cc0f06dd7fb4a868b05a8a9ef77b074e8fba84274b104ce035f24e70b17b13a5dddc4b3ae4afb24ae106e84657b1ff6c49fa936154f1752f996f6ea52be089cb271a71c6f783c5997e91a0552fac57f96413a7df75af1f4f8a66abe2e4135ccbc3d8d8d6fca963f9f533e50307e01cc56d5bdf4feacc7a481144a30d3c7c55c6ab2a5c9ac38d13b0cb04ecc556f816d52db834b498546e7824764d532fc61e67023e4d178d2707147b0092326e06550ccb01985b2a541513973be2ddf77c2c98099f8990cf5b973b5286c9544566989fb38bc5247f2d5a1236d6beb0cb0f494e67b19d4dac0e73700df4781b55eaba76e47bf70066d446295b25417f50d0814e4b3530c7024fddb0944d737fced13c0d7468f2a1cae92ba079f3a212f1c0875d95c9f86399f8bc530c36c8a3da191668369fd1b569cb210f73b09820cf233bbe24d37d370caad446009b2e668b8ebf147e6fee3cac7025d0a2ead12c4e9e4d048658313f79ad5a630264c542096ffffcafc920a94d3ded10ef4a19a576f6e80444ec71712de5894aaf9d467b4a712365766e2347beb00c4b98017a2fd4955a3ab7285639dd392b77110c9c1c1b2d12b20a6db882c00473798ffe2d7b3ab67acdf9581ac9484a5000817f0b57a48b9ece8473f75d62ed4be91653391c90280e4be7b125992693464a0a244222174232851f3db684db69d822b1b8c75f36af99cd6babc646e9796a2a2e1551f9862c5df57abbcc8b6873a900b24ce7abea3c9078941bc4807f073d624a9514c8b2146fb8ed92221633ea1b5444cd927035792ae8e2c22a206cac941f7da4616aa0e32cb533bcfc704b421a5a676a2e93b0505c95b550d4c544e00267dbb6f72c08576e2350e1f469f3e98483749111862081de8772d4b56e7a49e96a234abe8eeea3e116b5c24746bdaef6b7d3493275a86ddaff53e254618fb4a19fb07b06e30ae735850e508df75c9f19de0ef29821309f3215956c124129bf0e4098b807229ce8086074b272accd3281655a57a501733a1da0399981da2ec635447792f9e548724a19f09c3fdbb2b78e747385b38d78a995c203d80254b4994420fdd4e01a32d812921ecc8b9c7d183ddad7d23d1f49477977a961f6ed52ee658fc49db2bead3b00d4445d8cb97b6af667936aac950647d04f7dec399d5bb2afa1d386b5c29a41583892242fac073acee0c7740470225faea9d56a87c6215b60c2c7ca9734afe6bf501ef2b16a25b8c64276475862d9a795aa5e5e334442f9a253d1aa5a930b88a83970585559b7ee75ca8a2fe119d85d7b8249c9ca40a079b1b873568e1b5551ca752b629be3e06ff7eb0faa4b490e11f169d15fc97c4dfa13f10fa031ae24e8080137e5b3690ed6ab8a7ecf87c7577fc2d8ad055c8214e103cc8920ec43fadb5e332b4e05261adaa0a2413b37b1ff24c2b81ef63b9bdad754cd1737d28904bb5670779d624f5bc1068cd0801e6001b8f0306d27a5957f15f60d1f3b443ec1b2d9802710178009518133e01a4fa23257e2302ff1ddff9750f6fc024a837c6af43b46a17aecf7bcc0a5d980bf7e91c36b631cab928e7652a3e5395289179e82399b02c978398c4e4796c2efa5a6cf4fd704ede56334970e33d447d6146bdbcdfeef36d319c0d40d3cadfe267a4bc688acbc30c9fc52689aaf22b132504da6328d3d259c692c8614c6f114c2136c651b2849ed57681cbce5d7ec868975d13f3b22e2bcb18c369a150158f43c588f848518a1c3503f525491444e881a8696f16b519af71ccdd9780c76e973e452fa293057a143030a0b4e6bd895823cf05a6dc82cb945740fa2e2e85dd61fcea1f0fa746c15674086a9839964f7382b9a0ffe83fa03816808fe035a21d6a7ca3675d7b1bb7c16eadd24b159d2d42ac8cfdc552389d1980bfda9ecf64ea08426a51851b7d24f05511f813af01fb2d15ec806d423e72ed88249a22b5c3428e0e51b9d1d6b3b180430513c2b90157403a533edc59300a243854217afd93138c291a9d91c861a66597f1c0a75708ce831bfe516e8a23f9eead92a0086d518d7da90b5e096652efffb956a443fa022a07dfd5aca652255b4bbfa4d6e7bb38778a0ecf9441718c9fd6e352c12b38112bb9575bfd5bafbfcc2bee0b8d36cbdaf4d8a279fb2f2c4688d7900602553a9d939afb7bded4c3c37a5d2fb7bd02ea964bd9a69a5c263234759936adf5f51f07cd2cdaeb75dd5ff76b4902adfc34dfa2ba87d1eb5bf0bb7d3aa9ea8308dc0156b5ee68c85c67cb39352aa13391e126a2308ca6a4d75e5110b0e9ae17cb0c50de55f41dc61f0cd5ca6e79715841d7bc10692cd9881858bc51c94e9229bf1992361e2e2bf02015ba3a9b1cb2f04f2ec93a44956c4af931d28f597f0c6ca20278ea4d5888d53971a147aea2cfa9aba84a0e82fed663cc3f65ee777e3e125eddc757ae872a6b7dc2560d07201882badc1fd2affe4b7775ef80d45ff2feb7cb12a9121ddd54807c1587bea92c8ec3bb40d154828071b6e417fbd5511957577aacf68db03863d4776bac2cafffeb84b7b9182131aabddc536355432bcf0f696a9aa126619489e1c2f837e56090a568740e2d036e1ad58ddc669f3eaeb25d781b0ca4a2309e5ffb034be64faccff6797b8a4fc845ba4f183033f1ab983220670ffc2231999ae83ffadb834c048dbb4d7fa5ccc3bd941084927cabb0c7ae5e10f7fdcd441232723b42a53c91c716cfd60bd1371d858f58b717353649911cd818916db570b2c071169ebb7653c9c590d438783eaab595ea2702f0ce7b618fb342fd2e44324ab75a30a4e6d5d41dd8d926328e96f99d871e4a368dbdd0b747bb8532b48be47f39a153c69b843532a8e93d7af5d643312d8e5b5a0c4a25f42219acb58f2a70185476e6e08384c5c6f49b77819579789c11ad80cc72568b6c9e9ca2dce874dabd6bba4de1b94886a49fec7cc5493534bee914bb968949d4e9831174104147cb09ecb22bc02aa12f5f38d0b5f26f0053046901c43f244c279943ba89b11e1f1c79b935d56d06f6bef29bb9aceaebb31884f5044cf3a013a91e663af156b02a26eb6f83c8cfabb0ba9646523a610d6fc98009973a394195fe11da460de87f866fe5573402d801d4f263ff9965a7280e28bbac1fb3a91532b116666f1014580a361bcd554368c3ca907f8255487d14c049ee329f36d1c965f0fb871c65390ce44bb2842a5678922df7aadef656b933226b0e5f49f1a87934e662483344f5b90192273be59a78d50521104002e0bb4d70665d566e48f7448cc15fea67bc89905dd64d560c9edc3772d7fd0fe79f04e1e16e153b4c198788d4958a48c5bf6907e77553abddebc4d936111042d296b4a8cc2094514829e441437056cd6627d8e24d3402595c750e0d6bdaccf61142b9eb40d792fc1263427c75c4804ff7a085386eb563553020d5a2d91f2b938c451d0e1e46a5e477f6fdf6230c4eafbddeb1e129692601de45a6c59a2902133e5f6a230745579e1ee33275b7949954beb877d6213b8f9e936bb6029697b62096eca3b90903a67767f7171721fad61db7398a629fc90e9e847224950390feb0d01424edb4a4e35b638e838aa595cb525d25225e5b021ec60f55019655ea536c72983be55d13bd4e96587c7ed40f3b4f505f0768d27f3233e3826a779b6da96ea128905ab30ae2dd9a9274f0d2b6d99e1d25bba5b08dce4414451e17b3fa4c6859ba8c4afc647dbedc69b6fc8b76d45f0224eba4fd5c40dba0b542bf719df330757f504b21a1aad7f1d401e2a5f83d2c6d2def10155855e5956be4409a483234b7c34f32756ffc36f090c50992fe760a384fe8b8b759e16c0e65b8ce2469aad7cc75aa3126b186eb7be7f494dac0d612e66872435f5ae50ab6971f21e6e8e22a4029135a61b7984c9f5f7c4e18a0410c3badf31e95fac7cfa3d154312c3a4682813c11462febbe9dab5402b434a4f05e49f052ff85a0d4f45bb7192106f45466f5b20c3c70847587331ac70b6d0f4bb56230fa6e66c50038cfb4624fac635162f6fb87cc1cec3726c00722b670a1efea68938df478446285c70505ffceb73aa508ecd914b96c4918556a269d38f495a8251b2b0be5adb91bfec46f849b060967363139e2d45f127b22b0f8439cea55ae51766bb3b92cfca3f4180bc8a1ef7f548bd86c4cc348b8544cb3db6ee5cffaf1929356d9288cb80407e847082711ea1d233184ae64a78a6a46ae59a4510ca760ab19d7d4e0ca3059ec005c0e864e5589b654150b015d2d314b519eafcc0b80c768df4e54836a95ea3ba878b035b0a05ac8826c5306e3659bd8126f5a261dae5b3851472b3cb8e118dab83603a556232584de2ab5d5cfac6d967120e469e0a249c2a84e7c2aa22391db067a3a275dc2a092b81d30f0fdcbc34faaf08bda5adca3346ec6454a80d366e899c2f7cadb8f4033ef6801f4ce7505fb9f5e9297932d4b73ff34b69074d344473edea2d1c4925f5c2b53c8436839466313c99b431c4a191275219dcca464842fd4a8352f4bc28ca6816e5564428a218d1396eb5f4fb77fd978bb38af36fb6e95a205e34cd60cfdc31d8d3139427e3707d7c278e09dc9b8a779e465269b1a008776d0858f259f86cb188578370b958c4bb8b76a169daeffc32533e6dd1c2de30d379e0bfb66a448b0f3060859cf1e061ee028af35bf19db4166d67846564c600ee1b32ed8ce2f2804d5646aad46f6f8777af521c4dd6a0bb77abcdb88c74ed3e9aa987517d220108b1a922ebe9bdd0598967c5d22003d678dfb92172a40a02c641e9346e75e2312cd96e7ac7691062d74ed5b2011007f14b9d6efae4e95b3ac039efac873f07191ce29a3a77aac664ae8246c66268b102f78c60330563d7a56c026214a8af6f2dab6207bcfa4f7edba740107d51fc2eb52afa264c2ddfe1827d1a1d7748cb4445b2f45b4bf953239bf7e60864399f74f5d24c1561c1176b8b564bfe086fe44fe158da0ec3a66ebcda0345bf73b5dc84a4698bc1c3b5bf5181f49cf3c818972d1d5b58802a4b7314b83be9d99ec89bb4015c386d96e87e99b3ced7eee25b9a7a22986e0843d77aef9caca912b5d0c88f55061380ea1529cdf84a222eff2e5864596c8cd9124646384f0cff8ce257aa2eee2dea3c3845c968e7b1c794963ab7e0908141a0d262886a99bb5fb9cc2ecd67c5574ffb4e7cef3e257c8aacc503f343ca9f02380b6e2726d982d1591818417dfa0ecca45fde0ed31a94b84e858415e52e22f5b9766a6daa8ec656da88c25c5c79700a6b9a107647fbfc45bd95232a2bdf60db1fb6bdf309bdb0ccccad1f11efb5dc1711d7cc3edc0caf0ca1909cc8283cf2a0042aec2a386cf7460c95d20944ab34213b702d777f31df7d266c3b12e63de1627802154a13eca949db37729d8d5ea9638b3e2706bcfdc5f20f2f822940ccc657083040464a9f93aba30924406880913b6f50cc7c28ca331d8d1d02f0e1def8a485517c0419fc11c39e23914fad2699e57923472d7145fd0f06afc16b379d8bb8a69cd0273254d470cf70030ce3fa02f1baa7213f2c34953515ac2e0ec98348f36fca03d91e8697cfd74614cfccd74c7e4798cb3d28bfc961136e2ac71ea2aebacb81bbb0829a0c07f672be14b67c03d8d9f31f0e2f079bc71de86b8b96a0d04e057fda6d35a7bdb49db820fd89817a7a9a28a5521eaa2d59858b96cb65a380b589ad04836f715661cd76a7eb5fa6c9f6d0a2d59634a960fc11538d24812af0cd6afc0d4b093f1801b6d001c24ce443e21590c524d303000805b45bdbb0e03447da1360ac21ad213a6027bc06c3e10587b4e26967b33564b96b530a58e6a5a8051ca179fbb5547e2e57a0d43287cd018565b6b75ee05ae07b8016948de2c4ca919a920991e025149b093a8c2fdc9ff78c7cc9406c25da988d7193b2bbe45cd379fed3a6ca7c322b05e4bbe017f1f2fdd3b2dff9d641e99a3d566d6134550196467e4326a20f8d1b090b8009c3633d80e776b2179d52854d461e1a8929d1e5096dc8743cdd1cfe7ca31ae14ad046ca127e58a3097e81c1dc164bf8e17d4829cd8de665a551a0b1de6c4d5e094ea71ee568863068de987841de2327e4b70d7b87d846fc1e05d1939bca72c93146344659f71332aa2166e1ff3417077cb28429b10d1c22da1a2ffe7c673ce6ce8fa1e66f22b1e28fbe63a915a507b095a8aab9bd16244cd369d39451dbb5db4145a583f48c2d2a7365424c4e77ad4999163905a029b4ae74f9a8720f310a7d4012a44aec6af7f92c71f53f5e9a9d92bb8329157068338468af607b2e87c5e3a455459dc62ecd7a00fc3a18cfc04b5e46c352bd863720b26a9369ca080274574e2766041555b8cc21515907e700c78a89e0f619a757e999b034773ba04951e3f7b70ddb668d91c8c0698815e16abb03d03ca9813aabf069f6ea2a1583eb4ef5288d43257e704ae86f80404db7822f605e894c5997382d6adf78ad1d86fec29589e76e887142ca7caf53df09fd1390b84365b0ba89bebaa1aca0fc5510538954961b137296bcfa152db73535223a74a4ebbf9d25eb30c726e450229ec2bf5a7d06026856c6281c0a62e534a486ea8f80fd2ccf5356a8e50f54fd2677db304d5220fbfcd29548d41b91a9971e84594ee5bef6577bec7127915eb0d24ac0c82b0d207e565d5b130d4bf2f1b50230b381934c79ef5cea9f8ec62c7344b17c91b3fbbf0f85753f70ebdd44d7629623304549ba870cb87bac1b9343d114855e38c4d9ada3d145541990aeb857eab77e2f70e400ff1a8bb12a429b3647749339e20a044d26bd685974d10be43e38266dc96e9c543670040e9443eda9b180d4a8a7d95f7d05ead92a4ac3d23723de7658e57e3d5693a945dac2d5962852c65fe9eb597ea9b06b3395f5d1132bc756c03a1b5da16d1e87705053bd9ce85a9d73412ee9aab365298c5cc7b0dc456282424934ed9746e084c088c62ce9467c5b0b06338b6dd9215b763372b481fafbabba955234d57fb24d9e93bd9e06afca8855a6b057f998b3e58f3d5902dd95cca4b81591a7bd3c8ce56b2915d27dcd51679cf75d43925efb841a040bf357f0d0cc86abcafe2456937483f7d5b58ddd0dac538fa3fe2342ae65669f760a288f9f523bbfd1e24dbc76b196aa82164a287a63a2970b881454a346aaa0dec14f963cc01bf266e801828d44584e73d3b94320fcaa79691c947539aca8a24bf6d9c9c693876e5f9cf7cc807051bb4d4c21b53bd5da722bf7f58a36b964d03786e4f165e04faf96b68a74e0f09a628f297cd9c348275c32d1dfe014aedbd76714faef4d670835d13d71bc14a868a5249d5c94c8f9a698afdca73b3a0b8808c4ed165f9a5552a9774963112457136bc5be16f2c70510dacccbe8a8dae3b917518f6af4a772d573e18b947a8af60a601823eb85ca0d83d429d7ef7cd186b6f555203a9729c067e422178ac293c12158962c6c03842f021ac12960562090d1930e50943f6b202ede345de177ed4ed3b36f485cc931ca5eb7bb0efe1be5bfa857a41381c089f7c31ab085328e55aaff6455cd76bdf74cec0ad2a706c47222ff2aaa7de08e034f88fdb6c9265b4a29a59452060f064f05e8056fe9929f4ff23cb951a8526506cdb77ea32d50a8e2350d30dfd61da62ebc4e6eb2a432f6d8715dc1376b49e5eba796c7d66bcfadaed88e55d08d22ac42d349a0c736271980c342bbea6cad5334dfac3fef37eb37fdeb60db21ea53fca9befb767e4c807dfa1011ccc62113037aede8fa0875db05e091c8f49f3a6680e9c8fa10127c18e2c310128c48301f307d82286c1c8eeed8b4b3761405c2911d7958a2322a53b59487274e4960c4a06f735d8ad7a4d2086b243e3b0ce86bff94e8db98f08baaa331e85f578200d37ba309f37712d276eb49d5f1f873c276eb2e36055d40a03abee3766fbe0c76bbddf62bc6a027d0d7fee3f9f5f99e6f9f3fde68c2f6ebb5ea288c18291ee79194c18cbf30e6d66ef7495777dd5eaf1e23e98e3f4a736b0783578192086b6b558cf17d0d773a9fafa3752c924915063d12104186e973249201a62318d62740ffdb19323d86b5adb5adddee2b1d79b0c9a0153fcd39270913ac3e4511767392f0ed38518281528abf5efdc784f9b583bd734ad5ed18e38e247debdfce6ece4942f50e6e9500d46f55013c6ab09b9384e954ed2f65e1ef18c3c4d7516b521b7c2c4c9f63d57427a4d2d26eb77bd2a25d8f419fc4fea3a4a9ff589faf02d3579faf441f8fa41d2140abbd38574defaedbbba1edbb51706e551d7a77ea2675e10d476ce709c9b945e2276047d269cfdcc20ea356af7834c13a75183a9f9f801d61d09fec30ee6e97f4934713b05787f143fdb6dbed7e97f809e01146dded766fc71feab8dd6ef7e16eb7fb3a9223d8ed76afa34ffed011c9d365afab4f479d12fd3b924c33fec77a0c134f3da9baf730ee6eb75b7bdd8f1d91ee6e8e2aa04e794cb79a3e7d822e9474da6eb77b3a6a65ea79a14a7053880b7bc2f0525532bc33be513c69664aa03ca7f9d9c0565a6b6dc38dbe23377e7829a648c1a1e6879427301a3b8ae8e0338315192bb12d3562778ee7799e1785a40a4d1db4a2744520ea4bc8961874e4889385871a58a21c3132b342738351394dd92f1b5821f6c2b2318354a13903121c7632c49adb9c1ee004e3032d8d0b61d6c4a0e5c0522bd346cc6c5d34ba72be72846c25192a6709c207f69ef09495f6d219e79c73c6b203c9e76c85735416d09c78a05f727659da861cb6160491fa9a210c980db46e121b1ee440eed54a6b1cd3ca87d5102bac2dc46bba57ceb060396d3569af1abb26298a95f31496016d870796730e2ffbb53515f62009db4c59e51ea2c072ce5987a5b185c5c985fc14e2c29e708642e4d01e58df7703dfd05c36ec9448648e95225934cc98c1e2a4c79490106c8cf1d8820108a719dcbc91e2c3cd072288e3a508950f1fd28cdca0851b865c2a62419a3cc881dca93cc881bccae9172cb2b04748539c1c3abc6e13d61286858700a283e389d5cd063872ce19890ecfcb996a4e7d4d894d8d4d954d9d4da5e170ca0ab03a6269c8c25b6bad776455a1b9c585e5c50eb01c84f7de7bef1d984ae692cab173e50712ec39799003b9df9cd6749855c656396e6c9db5d65aeb2f3055689299e10c7ca179d72d87f94e799ee7a1d1aa42334ad797d818129ab29c6f50ac0de5fb0a2cecd13da692d868c0f2c2ce9a929c73b6a17b6435e51db308980f6b15e2c29e30d4509e3170669f51f3a4425cd813864fb58a191f39e79c438be79c330e1daad0d48ae2bdc0d1e520543583f2eaaaf185c52c3b612a32990a4f210085302d64de10b1b1a6254d142968b059ce535e235a588295c52a889919179048299b52662fa4d0bc6c28d953daa3d20526c4ca856bb5ad9688588098f51045446dcdeac992303435ed2b2aecb1c156b69293af88e11757c6c23651c07ec0e94aeb41447b29e1f182f274b5d65aefc0aad004436676811d5a0ec218638cb1a8d010c5a13144091324374c512f4faed8b8e0799e6725aa0a4d1d3e866029c9ca19b0b2b51583b132ca0e0d8d0894a3acaf7cc656b87a54b3a979430e0f7220c7e15b67adb5d63e7455a1f9855ff820860b71614f185a5d1e39e73cc40acd9d33946faf365f61cf5c43c32c076160a69cb9b141a8a992b3508554716ab86b1ae7d9b5eb8aead2f1f365ea862e555e806839d124376429bb6d74641bb6daf03ccff3920ca942134b49d6169705926091e2f9408b0c8e140e4da684d1209f3d0ce41d75f5ef08f28497f0eff2b8bf8bb3847ff4e6e23cba5f0924eb493dbe9d82483dbe470bbe5081e98d88befd13fd8ea4ad033109e7b89194f1382fe15714a3fc48bc263cca8f4437218e4b23584186fbac215c17dde623d82b562775fc7a093149c9fe2091257cc745d1667331898fff884a1d8ce4895157bfc7af9b709b97487ee867a965f7624fdfc7c7250fdcdd472499dec74571e90317dd821d9081c293f8b8530a177d042b8a4f7ee45e41f71e9164fa1eff7c4423a2bbb8820c0f5c1c771eb8e8e2e89404b799dec5a524db93e0760a1fbf8e134d8ce4f5126f93d1236adf587cf2230fc62437e711939464bc55a2494936075d474c02475dd55510c484369f7f9de66c5e08a4aede4692c9366d22dee24cb28ee9cbc84cb8aad3e6b3bba7064d1ffa219c4fde7e7efed56fd4d5cf9f4f1029467decd99132fe3ce72ae2cff3a8ab3bfee9781ec9251df1c98f76becc32bd9e5d0b923ff1b3fcd2f2dffc6c37193b62f6273f5a41860374c61de83ba3d3ac21507ff2a31d9f479febaaf6efbbe988f3288b641593643d2e25a5907dfb1e5d909d8e3094ac2efca414b26bd7a30bb2ef11468cf0a92e7c3a82a54bffadade9eacf1a34f7d6596bd1a2e0a6aec1f036ab8a15412b705668c807ea4655450e22fa6e5a67d7d1c93fd6ab1d9a12e2b04854837c070d4109f5a894e2b5ef3197dd8eb80d4d09adcd37ef8f7e2ac8a3cd75f5ef6d86b7f91fcfed5655648cb348a9125dfbc93e9f29b7b5bbd8a31e993e8be4d267d72299fdf6e59b8bebd86d48b7ce4db36f6dc2fc3cdee8cf1984abcfb086b5d65a6b69a5420ce5a0eabed65df53beae8db71522f59ca1e849f6596acf7448d5d5749ecd5f55d7a6facd61393741583fe2dd9e6f644281156f0a044b0ad306b089e7b62f5db6f9deff883649dcaa84cbbd6b16fd4d5ab49fa4e3576edd397e8d2d31208aea75e7dfbd6d37c5bd259cf2fd0e9acdb3c46f53ccae0698ceab6915cca696f73bc455db535ebea6b9cd3da27282655a78e41a48ce9d674c4baaac017d709d66cc23c81aedda7a01e93aae7d18278d7a01f3cce5183f926d0f4eb79c612b02f62d4c7a3b52246ca4bd8b5679164fa3c7f89f41c8fba6ab361512242e8ddee0f21f6bb2f98cb9ec5189e5f18d975da2df644a5f95a544af176d4d5af172ca12255c7e30573d9b32761d7d5cfe3985d571ffb1ec9ea7be9eb579c382425ac0ebea83d385bebf883547dfcc17e6fd97707209ce59b45cf27b8f1c6f72798a95ff10af200f8595ea97aadb7f92dbd90bcf5ebdfce0ffd10da2add493afbd775f5c9eda0ef7182255ca4ebb691b479f5adab6ff36f484a9874c78b048a4950d8595b5b5bc339bb4dc4f7e623ec55242fb9f4365b92aeeaea83180841ffaa67723bde23597fbb57a3fc0824ef7f1ee547d701e0b8346b08365f9a3584ea37af3e82ad22769b98dd139564bc9d3f95c4a9777cd30066429bd5818b5e502e2d67470146418e0b6fd43eb2d38d58bfe3dd7fefbd3731d6628c2fa698e28bf1a423a65c18638c31c634bfd9b1d65a6b6dc5855f3dc6feea2754b7e35093903ecf4f33326d8ad0a8b1153c61da396647d2da91720de120fc7e9657a27e3a53a5b71cbe09e4d488d953aeb20a98a75c4fd1beb4a2e4c96dc5aca472f6d689e8eeff05e5fae9942ad13525a0a74e9d9e5ed15987b2c2595ad1a4a6547ebaf59b751c96b78ec2ab8a59768179eb406fb73c9559a4442d28012ae5afd73bd67a6bf5112a9d32595c914e9945601d95f653b7930575ccc23781b8688b4a43389d1a992c54a04e4f8dcca2e92c54a05366d1747a5576ed9e725d3de59a2c5498b3684af93b2a5d9172cda239d4249ccfc34565b2983f47af348474050a55b24c21aa02852a5c66d07cb2443f4b32459e0ca7e0286e4d787f966488fc0b7808cdfd2213e47159645ad0f2659f40b4524b2bad14db4aebc5bbde6a2badd4d24a2bfd284833ddd4a3975e5ae9a596567aa9a5955a5a69a5d8565a2fdef5aee096c5695500e66c0ac0dc5cc19caf259c787bcbb9bd53800230972911270073db04604e8f71a19edb24a802fded2498b3360198f35a94a08e718d4004604ed7b1253c2200735e084ebc70020463521f70d3e38131617a3ae0c5c381314e466c80c4cf72cc11129ea701132898f8598eb132918131347830b07381bdd366a72ab7b0402de1a54405c05c1633a5870245ec1056e5cd981a54da1ccd29b4ab5e7c5759c504ae18279f04c05cae62b04a44404c90db096272801e04c09cb63c7378906e6b6e1e3601cc691f311f17602ed7305478aa4ac881396db5741e400298cb340c92f0280c91efc3f4b079de083a57228491d2a9724003be2b068039af0518c209ab588039afc502324f199e2a05b0b02bb6f7c5236d2ac09ca76d08604e7bde170f84045c30595870002330a72f98ad112c30402d401198cb3e477c88866e7845000d064810cf95e709008c140fa90390d33784131e01c008e6740827bc126d0827243da7c9f4785ed61f8039ed0198cb148c44f103e6b415a2cab6b967f34e8039af0af1e46301e8f916e96b1325c09cae42e0f020096a16b807869e9e1c2484d8f1d8353b79841d03c32f60b71d9d9fe5172a3a399d45ca93c57305f2fc2cbf64f1dc2e7804ac9a5305b6d9b6ed67f98588cdfba263c4fe3263a785ae56cd56a9c837a3e52b15f866b47c47941961c190a0ae6739fc4892033956a094399a5368d35ba5955a5a6da5957e14a4996eead14b2fadf4524b2bbdb4e8467b80436442ec57af303ac28e9b2cf06ba2a220be7a5151d50f2222fdb3f4d2f57a0f81391d6a855a679de990171bb6b641606e5b2f572ee8bda900c0dca640800ca06616f20bdad620fbd9f8de3aad12a2ebd7165e6ab8c06f9de2aa21604ed32d04e6b4cf522c2dbff53a03055dd41643d54bf3be14b616f583efbbddbecf368bf22cca0e7af0ad9847da5180df9a2fff5c151de4b107abe63ec102b7efd58fbdb5ceb3487b9e45791669c7b328cfa2ec9e0952bf76b7534509d0451e730394313f67d0fde1d303e6b405261b1cb85c59d2a6c6d8d9d19c42db00604eff6774805b058180b9ed731655d0057d17d6d6eab9972fb5950073b69c776f60b75b09f8a9d7122d7c6ac8155ef580b94d02cce9902be41971afc29cb61f0f5fd501c19caea012f08ade28a8048401cccda2ecf7bb4734053df75e679db5cf599447bbb5beaab3287b9d457a1665a7b328fb9c457ab4d9abb61f90efa330dcc03c6e16dd3cfecf52ccc757c73f4bb12a1a0474819f7a16e70b600e8435e1ed672906f533df9eba531f6bbe17d605eda12092756c2faeb859441d378ba6634ad388cd781223e769ff2cc3da8461664d62c29cc48499691213d6252c882c61c2a4a6be84b130f5258c888d2f6147a4bedc301e5e07a91b0bb69f651891c79561476a7801ea92c5a52bec8bd70416c407e6a50bd7ee82a509acece2e6757e965dca3ce946f889cb962d624358b0167bbc25eda98aa5a23df8597249c24506babf608489ddb834e9b02a3a5c747e965c5830c285446e7b5c452969e28252a48b930b45ea7a9003b933c1ee41976aa77af47ba9950b632ebca1f1264d7339a27180727707b23f4b2e3c9e74231c85b9e8a0c1858a06d63f4b2e505a3603669415fba8dc5256966a4358f0254be811e40895314ca444d16052394790148c703831c61ed2788c4bb0a7c74d47f88e55762ec1d6bcf7b30423b36686accc6d8354e0b2f9da7bed9b28cbc6a986383acb9e565c7aefed962599bf77a82b9ca11df3cecfb22cdff6b32cbb7096212a5f52004291b5660c884a0a463e346839928291d84d6bbf9e97b35d6d133ded83745d7b76d6a09bf6fc529c8fe3dcc7e636dbbdf4ba09b789267c489be3fc2ee96a7d1f138e73138e53e1a3a5ebe95f2d5baec7b7b82392f7775c4724975ec74391a42a489453412848823ce15e0e020c10609ff6db7bb494bdcd7b2ee882e9716e13afcf6bc9d0b7dbdc8adfc5b93b6e0cd96755412fd5d9b75e7dee7105ed19037a9c56c80a51eab75e2919ec2d3390d82d631f080a7806ec50ef06fa8e488fbe0956a082db77443aadd075901e55318b7e459be79b45ed37f18a3de3d4592c5b763e2470bf7c73c6b85c732ac0569a53bbd3715352ae613d39c5f0b55bc7dbbf4058fdbb4048ddfaa66ef6d55c830098945d19fb414a199b1c46787864d4a2e6b8409416465504d60f31305c3655a9e6e3ef258a23809fa5da1335264ee4c08173f3195182e7629c091d520f4ee40e7676b5811fa4f8f974ac93f0504c5b7b6830a76a3787aac6d69c1d95cba91116e1b4d3ce9aae579a9a1a358d0cca2b2dcceb428b2a02088e0808341f3888aecc1e7eccc131258e952c376c1c695d706940e0f8a8d1dc1ac104fc00132210e44035688407a469a5657d0e923c693425f4668892064395b42a1fa8421ca449ed90e694b5c624ce12375c663a1041c31054c2d42096c3161c80e8c1ca13ae1bf8a0c4890d9868e98065c30d34fc10e222368478089220497c34b1dad2c1c3d4912828729ca4c8d975a53d6944e4240edacf6aadb571cede5a3b9f9c5b2b6e1dbd1de28c11051152a15cf587195fa3ccf8ea1fbe5dbf275010c484d6ad6b3047b30e9b3c59648c9da4d41bafabc0f4d7317d4ae10c29e3383db9b3d0b6beda6aadcef4aa6489b2d8fc6d60cedb1ef69c8e6412f6ebf9d314c479dcb388fabcde2c7a018b152b40568cea0aac2535d850264c0d607b6ac43d6dbd600398dd0b6bcedce00104b5e3e7aa488d16d09c9419b3c58685001421bcdcdc20062e3548dd70e81143181c9c68b88216150000881b6ac7d5e981c90e438c64e192b59f7a44e07186eccc17133332ec1aba0612235fa4685185d94e24ce3491e18c5a94263a84d84d3ce8931f3eb2667003856ac8a6915d20a64717acb118ca7cd93376806ca95135264a8d72ef78ec10e04401b91a0325e4c99a138979099b51840d5f566a1e600992034c4790afb1393c3e48116707355b7478128395b41461b45a984104d85a0c7276a000f2c4860f1d48ba1479128707cfd65690355a3b9098ed008747070d086288ac356072680305ad040a2e4dc2d8a80147899c227cde802468c264cc4a0e6590b81971f6a19064ca922142e03809d2c684561a1498304b98e4c06687b766876d6681c34406316084f8f2658699587821082a24438ad090a5e644106b80f08222c30d333748c3234cd3c30b674d5ac072068d0e9117998eb4af1b67dea8e932a22b4195cbd75a271065b26d88737c31c645583dbe3d38b28a7111548f2fc618bbc9222a52c4eebe41b3d68d9bc73f4b37664f863e2767a218c29c513833f33c22ced0c0d24d0d1c4432e833448c8800c10043d566ce72d04424c428c4b90715a0c9bd2900110539d33ad3218a5e7b56c313d190eb40672c9cdd78d28d6adc13fe555ab3ac01332b239a5aa39bf4106bc8f29a34529b524aa9d2b54e73e02caa3e8baa4fb1cdee6910ce928dd9d7323f59cc59e267c926cbe38a588326c566061b29363b9ed8f48022b2c126091b18b2d7b7066dc6962ad9ecd03f4b363d2e9ee19b75d61e280be41ac1b71cbe9f659b27f98631c6e2cc5d2bb6016b436593f859b621a36de46c9d1fa4fdd6ebdb919cc1dfa68fa8ca66d1c5b4379e9f659a1920ee6799a64a47fc59a639b293a6eb71651a227c843f4b35509ee7d93e355b267e966ab04010d4d9517306809fa51a2c5fcf8d8afeb334737adc8d8a297e966633f4dc48902041a2c7a73413c34d16277e96663e3cce6ff3fbdebe1d4915be7a6abe59777e6e21b3f138141f02ba6e10451901114519a1f1a25243d3c44a0d8d112b35ff59a2817af0675916a70c08b5322a6a6550d4ca70a05686835a190d6a65576a287e966536ca769ad2595429d2dded763b8cabcd741a00dfb7f43f7cbb31ea67a46b2bb53adaf588ddd65a9f1819ed8c88661035116146040d22a7b5126564b839738ce600cda13866bec8ccc0b9beefbdf7cd073fcb3354fefe2ccffcc0e5242fc1676a88ba6ece5c455937e31918a2dc9c6921cacd191e4e6e7269268d99312b381c1aae88331ceeececccec86eb41650ee4d8882dc847960324ab01e776ef55632b23d3579aeebd60ca64b995f172a30c9781722b73a5ac24f3fafcb3249bf2a41b8140d839555427edb573ce592b8cfabafab5c5bdf5ad935557bf7eb55043656988208208a3216ae08166082238d00c71e3bf9fe5103b3321ca6895917143e68ccc1632568e944132c496d9c5f7de2f7fc9b4fede92cc87bf40d59691d1e17702949ba8c4eb9a2a6a1022c50810000000c3170000200c0608244194244110835cf8011400095a924e543228a10444a130140c8581280a621808821004621004a3108603599e4379006f08f625a52f948835d9a91cf4e87e805b4d6f4df601d52d4ec33bcf3d2d3cb2ef4fa9b91f13e453a7286cfd7e8fdac537da566343a7491b1cc373e4b609d88932d64caf92684f9c1034e1d0a249b244afb01cd0faaf5e890543d89466a24a107bf452882f2f7655c21e63184f5a1211b5b7854fde71975188f7dfe82e7626de93b73ab7205a3e88db850b9ed2700fe052e8227d43892d0f9e0a890df5a8292dfcdd9c319a368becad0b221fe3d33c420a543c767b20ddfe5c764e092255a42feed35312f4bf3144d892cb770951a74d32648585e0da50e8ab43760ca2e7ce409b4da4706f4b8b06731862c939f45b5a5aa71b3e8867c6f5ac641db00cd6ce4e1b222e12dfebf302f45ee3e2d9ac0cc6ad6d7d4ab823b4b06a5fdef9ad22c8adee66d72d36d8ad786c782661414b245898c148db51440d011450f010ee8398131faff942ec5208f7e5ef2d1ccc21a262a20cb37a500841a3031097ad9f31483a2882124a7d18101198160045036a53e505710e7a3168e594c5dd7f64d1398afcdb0fdc23f5ba786357702997c9c5ddcd6d5dcffdded81ddeca7bbaf9bbbd6df7bfdf1bbb83b7724f377f376eeb7ef77b63eef0566e626a3f0e16c337fbaf1ffa39f18cee60f6c5246bbd2b22f01f85bfecebee5079eafebd571fff9aad298321a14848d670ec65890022d8cf7d4f62be8c0b3618661945fbb2a405c71d2c938fe25d95ba286e9d4f2331374906c68f5be2daa789e8528d054e053941107c14d5c678e05c3ead5a18ec07b3ec040230766313faa8069b20c3fa51d5e9eb27931cdba7796860e2f58cbbe8ecf560470f7310e3cdeb508a3d87de9a87aeca0e102834ab3e11b9296e0a3a9b298010e095f65f4ba53c2c2cf8600b69d2458dc159b07a96aba364a935308e1d0268d2e6f9bf189eb43cb4d61926a1c0073c3823cb6b7d376abd199cb3532de9876240c4c7401d9f8193c9b93ddd927f3807637c0cb07eb8e2639ddfd99c5bd22df986b29663626a20a30ec9ce7826e278953010f7f2300f5556f619473fcf97638470f9d64532c1b2e142876991e660667e5105037cfacab5670b835c418e009bd1fea3aadbc60b0118995b0b90fcd06d804e1eaa2005c4baa5e0ec0a7ea697e289f063f44252ae61e92d5069a44c3f3c0f2c192f8fa8edb0c10b20be72a73b5ef8c765af2c9941fc0a8db7d2d321d8214fe2c548ba653ec3cf7c25dba3ce1155eb54748f790bec9e87d2d11f41e83414c1a35d1a17c99e47ac3bac0a1b3757a0258a3aad9b1f0551b16ddf8498b0eb2715daee53d45e4772fb3dd9fee4368de2ea00fd45bf955bd2b4dca24bbd71975b113df686187fad43635358bf0fe976cb36f3cf86a3de28fc03cc35f141da38c9e6450a1f4081578f3f04301d68b7d5a2800960bb15471668714417bdb2039d7b2d2789f11ef588a2381ec7f122d7eebb1c3157087cf1e884ef48f14b7a032a4e607fb04706330273e0dabb033e68fa6725ebf0962426a4d04df6f55eab0db04f1c406dfefe849aba0c61ea22ffb307c79d75940ece2f8cea004b9826b80a72533d1a795a3244c3fcfe326120233d650a87a3b4900d8ee18e7b59a8f7ad35e513acafcdf96cc1a5283cea440c46a326f85bc4d489b05d0de1db35b54b76e4b31bc76b159c3f7ff51e1660e9e326cfea4e88e5e376b2152df47b7386967e694e57f1775a680f416f8bb63f83ae6219b423a1f5739e91e0f645fb4b84a2fba1039459fea0e70a618044bd52f66be2a7c6b2e2f8b9adf6b2a3fe8a3f2f3a7ed93d59eb3fa9eb137a246f69f18415f05ff76397fecb955367a248a54519099e6bbd81a0b21fa5270b0b85ff6e7d993e6f75b7f67aabd8f9ed41485f237278ade530d445bff18c75b85ff7fc2f9de27be68f78e665fae71601be0dccdb7ee91117f18fc46ee142690239f18177ad42eb53847210a930bff3a0d56d72547cd9fc03a30dbfb49dabf82fc7bed842ea0f92e9d0f90d41b320c2e329d4690cf682e8ae47866d63c0c00bcb07237ebe1a216ed20cd2e9570d3efa86ff968e2f962fa923e2b15671157ab70c8fdbaf07bb229e33c2d132922d035f529dfe292dfb8956c26c632bbc66fcb21e435e7409d3d27da04ed2aa043ab300a267fedcf7a96ee874bdf6ab1125ef22a33c8a36cdfaee5bc0305c94604e52f2a4da592a6a59c1f4ca2c583f0c87459c07c344b4a48de1dcfd77d0f08c464f42ae4e62002637cf83a0eccf104d644ec545c9582047d19b027738d00f2ed5510b93c27be604146f3d95e207f8dca8eed6cee43786779a57831fd86de207a99c683182ee11bb6e1acff97e521abdf913497f063ca2e616dddb6a2bfac013dc1a982ee576bf4565af01f4cefe7412c19ad9dd912fe737913110040c4de2d85ab80db42ccab34779d582a09e8522a70104c8d88d12751c020224d481dc7651f0bec65e745638835c539c06a90165d6def00526e1a641277fe3d17aad37c3229f345c2deee09107fd449ff066d17201a95e194cfcbed970359ecbd949f8a39a83fcebfc10b75d046603bbf8b83f6dade6e002116f63d82f71f9c3784abc1c3f2a252faeff43631d7818bfc486e057c117587713b4b5b11c6989e10ae4f565520ef49d5811721a5e2dc8033c90f31299b78b83721a3a39dff6dd94a441374635b66b3ca41fa23fc105447415c0e561e45bc2755ea714402b7e2e1bb4dd55a6866781b6dcf21d705221bc764621fdc95844111d660191010ae80eb3170c3a47aefae9de817d421010892f9af0af0086fe30884891fce2f9f14f4f8cea0edc00bc96bf4a3e32704131b6aba03055e1135c25687890a19cca4b8b63f20e7842048daa4534863b80e3d39a73c03d12f49e8f265262a256f36651730e8c4872ff782a1200097d92203e24e1156ff35937922443dfac89d4b6ec80164959c4373830417aaf5db1ad5df4b2561695b89b5dae157857c1f0b69635107800919e869959546152853edc074579282773d03043e8f9ae5d1e5f4163700e5441bb6fe4c9de9a663dd70d03e7b0915db4a6fa5c582c71db6b0a0948c4ab3b4bdf01d249a5cb5b38dec3da6f88c9e2186f95c3ad0425ffece3e04e380fcc166865285e67d20f4c6e4c4addf7c05fc1061f13841a2982ea494a8354d27827790d5589d111516ae393bd6087192577c8d57dc9969cb9825debdfdb12fe70b5134ea01b85b7f14ac0bdacc11adfefd5baa661de989898fc06e6592c00e29700e0b32ed9bde950fc166dda832ad7ba4331239a281e25a61337fd5feb61900e036470920aca9e2d67c2aad8cf4727f4e613dd3f02c7f802c19318df719d543fe6d3517ac181a6a347b4ebb2f9507367149ff3895d96740aedadbdbd2f44066c564a8036f731457f571b4c0fc706b476c29b772ab2aba1e8f62f9d43a716fc497b602f17cb4708555721f80ca5a46a001f17dde0fdb01c7b771e1f02d3cb73b833f601f8d5d108c43fc9479017dcf36f242a7c4ff8639b13585c9cff49e3d24fab2e47253d7f39cc9a57ad2546d5c53fc6f9516a2b14f1c31f832dee4196f1edc98d6402c949e3a1e7cee8bde2880bce3a582f209ff609c7f1e285ba2c60c6886b82d9b8558c6120451aeb7bc254adc7afa8ea87ac48a52f2b949173130f1eb32d208a10671872194d3cfd8d79fec66015f1ac3f384426c6396eb9eef3144e459c539b68e9fe79d8f0295c504e78d58a5e0377d513000b0d28dc92b67c765a906168544ce77bcdc3b5ccf06758c27c1641103c5b86f0f5483424d44cf614894a71e02801eb2fa757bbd5c0a661d40047c8752ad9087c3aa2e42ea5990d41a63efe3f40e15f44b2258e3adca0c7beb74b35873a70395eebb7b138ceef7e8c02f9e26536f1c4ee7234bf51034ce338f65c04fdc1c57f08f0a72a884b225e09833430f5d2565566edbf0f28f8d7a16097e31711f9859e7c9815c63f17e6f5e4c8b3ba1ffa74e64f602ce27fd408967b8d11264c8b0f283fbcaf3a4751981e45d2ede514808974f1599df6516e71ad2c8a0ede32a0350d1abdd6d238d537594124f384f6dc7bad8172ea026a376b5d4ce47b144f6b861249c80af9949c9807850ac769d6c7d57bb00960185c899fb79acd3dcd896756d5842fbd2937c437d3413d517f06d79df13d9d0a82a310577f76115cb7ce8f183a0488c29acfbb2270c5dbb873d63e651fc08a0a2ea50b9300430fa204f4d527c93bc13a78f8b181254304aec9919c15a1b27cf16e80fb954c301857b01e7f438effe7bd7d9f1d9b77dc9d3fe6ff58269d59e5df21f3bcf57f2cdbc4cc43bba61feaec9b6e5e360ecd3baec94f689ca7ff694bae5b1042127e91897e3842438bfb517b16cb48c9194b7d7785cd6354bc336d879323a2355751bf1d66eff21d7393752a040f2103d5635c1a8ae0c790ac8de30878b64ef22a5bbdc5fe652e9668de6c7744b258d21e2e7e9bcd199effd4858c2956fb182175e66c579809dc0b0abeac765d1b637818b6f4f32510c672220788b7a25b4e2bdd2e430f69d893881d4ca51d274e36514a3550436939f2a9c0bd90ec3c53fc63664a4c3038714a5ef0667cbf5b318b15ca28eca3c3c5cf206da3273f211cbf2e9f8dbf5bfc651ef5021145594a72cda05965ba0d5800692a8b220f104db55839af75c0ceaf16f4d499025f029762a8276271c3413a21ced80f784aed850c286ed166c69744c0bec04b0088202dd3f3cbe6a7ddab11bd9e4919eb08510d48d32638a3c97d01eccf7aff0d902d7935616a35392fd9ff1ff6c3d0b05bc362c63d466f0f087015abe01b838d07bb5ccda1a6c653a6d3d912098b8414c63a9bc263260a889259724a895f4623008181bee3bb060e97fb40e9a1c45f92876d28281e855352949800f2f41410638338aed668bff597401d05a4b7132d88b4494adea600b8e511fa8103bc62143fffc39ba50b69a32f1811fd4f05c3fb3f45e312e8aa374494acbdd5ebed202d3e5ad841e35568e081091c1ecca26a4fe73c74c644301117a3faf662b3dd8dd49055d05b1691958eef1a7cb3e95455d8712745743d274070b67654a0f242ba1e3c5b7d94333aeb1886256290e8b0383326fac8cf4d4f6855316046979c9c705c4f6d8f85dcae983952f8a5a37466220985a8db6c9c79c81308fb1603f8afa5f631d01bb6b1ed0910498fe369f44cfad48fa7338dbecfff4aacf5b4af05bb3ab2bd779c46c139175c39b00d03844580f37bbd0c3974170a42ff3a77781003b754ffcd2c21e82f7461f0522396e0d3592ed83ab75a7ddabd88ed66f08e5b48c6124963897a829c8792771e13b8ae9eb53f7d2d3b05188705678a5f4a432cb55a7674f5cf23d34994b377aea71cb9c5b570cdb2c60fd22103c79908875410eda70aa732f0b9f997feb18761c70c9362f0d8ba5294b580f534d3925f35b0e7d3d9a1250d7f05830894ef2e45d06db64355b1fe17b0a388429ad61e864c93b72a1ee94bb5c48c02941a578ef17a81c0c965375f333dce1d7af1b58a1899265dfd8d866268ed80ddb500f320475d38a06459d4879be976e8fda7f657846a8e822293dedb4e89a93c7a965e48e21b58df6d4f019480120112204db01082eadea8d3587279845f21e9fca1ff6ce0425adda94afe106f2b3089ef95e088eff329b3d69b1e68ae5c78836af78e2af38f7bf3cebbffc7ec138bb4cba69fb5fc3354438a394d721b5b2bb9218d98409ca79adeb7a8ad271abdc95e036a34de06410d5187e480ef7719a17902fdcc970e27903b58d10171efdb31067d98f72605021ca9ba6fe8316d59588e410edc4e21a5fd58687113823b94f49b6f8e67d06391640dde45ece52890794aeef96927d9c6f826c584b880d8b25c0702b888558a8896dbad6135035c70c2826bdce0f8aaee34beba7df23a229628c9487c9b814fbb34384bc14e0f9d1a8b25519c376017ea0522201a9002ec2f0ba8b1d98903c7ce8387106e0485fc0bf9a25dc0c3028ee35e65d53e7a0348892393e599761292b3b4589d231998ce01d7d70813711dd4e8543bdd9c631aa84d51462dd4c85dece81a6baf4791c0f0c74fd7673827b6e8068f4313b96f4006b7061fbd7e7bba679b3c6cf1a3984d9d318b2faeadc4db92dbda621a0f8fec3b8887fb4332ab5dc64aac7d0e9521cd3a3370c6867b3cc5846a21a1649a76734af2ba22a2852ce15b0ab53b6022d3f6f5c2c5d52f6d20299302e737ab6aa4f788b785c19fdf6284ab9bdec3d55ca22c9039e83a809b2cae9ca098013dbf489fc3b7e8a3fd68ad92a2a47ba26e478d49f9c55c0427a9e2bc970e8c80465b30ae794be41c338289ab6102a4d108059e03500fc88a65b5307f4efff528134fc3bf3ed0b3f6dda14faa9a5218e194d6d2beeef229795c8534313b9be8a29e56f3d0363cef8814a31a13b873615f56cc4c4ca0cca8013ea56a1632e52de396aa9dab565b15538221aeabdd3094e22f9554fb6d84e3e2d39fbefd30adc835b8d0b9b7b96ac0ae194b4e2612a466f1078bcc59e50bf17330882382284735daf404e55a5e710cb2f92e8e21269efacbfc9171798ed1a39ad5bc23a59613ede5be283750d64b3eb62cc2b49e8201a62a2a3f255668d4f75585913144c832becdccf64a1dba7494d94716e6f695e73185f0529457e7c967493cfa6fb365c50df115c33aef0eee52c07ffe50384a208a9662347cce083e28c48bd4ba36a9f185f4ef91a3bd85fbf97b6e1c96b10f9f59ce03468035899bd61f9753503a4d74105935ea8c2cbd206ddb97d5a1fc1f4d4558041f427a0c8219aef47a1e05109dceb79e3eccf4090d4520f599ed31346247c5aee8e09fcfdf8b4d84cbce98bf32def5a5c2c455c672ce30c1f31f2d3fdc21e5be3bd85555052d991f125e4509826ab9ff190ed51c7b959d636456f20111a569fa29075d44c58df552227a5bc2815abbca36f535b5ef380d14ac95826688542774e5b7bdd7f8675e6a0e4e9ba85368164a4f508023811c835df08c85fdf908c4f5f5c099ea6225c7a1380914a33c65c5287e392993afc3eab4a91baf82c4947ce631d09648998f534f2d2780d4f4e8b2464cdabdc7a5ad5995735a44b080ab6b82ddab0f4f36466665929a9ff34565ff06159becd0f696d7ac246c750e36d7a6f7854b9df58ca295dad97a2066af1ca4c706b341b8cd5d90c5c5dd3f85042707b66e47405c19ec9a37a7e9fce015207a761c932f11cc6aad441341f29dd6b6833d490d4c6437cd36947fabf40cf55ca5888498d774033adab5d92e766807896948cb1dd4029d3278370a24e8e28c8884117195254238d531f6ddf04d634f8239745707938c581eb95658582266563928be82482a1f22c86c4994a14c3c12a01d70d827703e090b0caabe13f34cee6e1bf6ce3747ed3835589e7b1a0e4d0207e3bf03baee1a4f88b1cd9b9f229ba0f47be70e85258a6759d36da31e27c1f017480295217f2a5f6af7b026246696c99a7f8bf49122ceaed3c258514ee00fba085f6a2bee10d69f2574c27be352b5389b1f7e1232c055d8fc5ecba40e36785dba784f59696f39dfaeb18fe6e738f2896b3849a3122cf36292fb9ffa108ee0a140b0a5ed177c06939de4ef1db3b6708707d16b094ef7ec3059a454d8d443da37d1ab0fee2bf4c0cd2c31cc6a667840df26d690dc65c78e14d956605ba29beef803b87e2bd550e101b0706cacf77832b56674d5bc429d5b9fea85a919e12f74d64ab2851f54821b9f84789b187dafcd813a4d73f9bb7a869ae048d031eacb48bee13485b910d88268bf068d753ed9b9bf62d2d2bbe9229186e82b3a24a3aa10cdf4045484ba4fd0b0c96119535c5835fbe17d821fa3e76e016d5a67755f9cf9d98010b50c95ebda505f2754c3ee2d7b38bc398dfa81e9d4b0dbb3a38c0cf2659db4ac786a216fabb604639ca7833e7d39011c742a12c7dee8cc14a3faf8b43d2a29a69c6bc3e1eeba9ac3469e4dc672b360dab155c84db6df08ef16b263f2ef264bc56092583431252e1bcd0c7e4b8b689535d6cfebadd0eb04998ab505d93e3c2713d4f74ba7fa12f429b674c3f3fd2b11369df79667840c09d0c53259edb1b9e99ed7c9da418b9ce3346b03b80d8d5ed68d36745c12339eb03bfa0f9f47581dcc67393091525429b08842a16d53c57211e606cfc9d2c64923932bb2560885f8ed8149b09ffc2837ac7f654ea49ea2a03fc457fa3c98e78cb4113763aa6cf85a6559f4842269fa96b19026a33e0a93f7fc4f42405eaae74f2f9b3de7582af42db3ad747c5ee6a72b28e36ad50bdb5ce98d2f9043b6e42de9954e15c8f4f1686ee2652273b9ec7527b800e4b2279dc242d4c653b7b8934d6bad3cb56fedfe5ff560f2328160d4ea4f30704026f3974125939392d595cc7624476ae9f66ee1997cf0d3e395e67d5baa43af7dbf596bb3380341314081a3d82a0726006ccc1f4c5ab85f70585d7eb402f2a13535776807fde8cec59141882cb347633c198cd31e6a60664c4e749f18443fcfcdbedd0e52c4d1276440c47d8ab9dfba245bce3681f83f552db95d4e9e95883d007032b2638ac1cc972e6a796f4852c5f3b69b7eaa6ebb26ca62b54bb181533fd6ee5ad331cdb711227589ad63b43254284f79fe478acdef813cd26b0f86b3b093325f2412e1bf68589c401080d833fb97113e741d0e427dda0af9721742e2ee89badd512d9effdb9cf8bbccc1db77103974f07ac4512ff07e39473de93d8593be55cdf78c88fc91575aca8e387dd5c46f152ea3fd14ca040e65e1d7e48e74716ec3a4df6ea7604b5dba0eabf15017cf221eb846a7e9545953906e3d868f366017f08f76d53dbdc550363c6ac6f57e19cb34d19acaa6e8adf510967a1ecfcfc00bfa2667a78103d3be1fccef7d0a61a66ebe4294c7563633f99eb50c38c4dee1f1fb9558e67478e072efbc257a454a716cb1fb7bb1b9905c2c55a9ce7e0f0110fd853793a1c8082feed4e65462e3b9e49eec511e0da82171b0baec5ec8073600c5abec0d3e1d482a71f73c42cfafa8f1dd0d63fa3ed483dc1b8a87698eee95e6b9323dbdb1f2bbde05f48d0d9c7e4e596e30da07422f458b3cdb8827b41d9c9d3089bd1cf1e9dcfa429c6dd5c2f8fe5f20d151761a73267e07ae3df15aa6ec951b0f65d69db94139fb9c76ac6ca00db0893dc880b8fe81e8e1f900787f90596aa97dd72acdcb00ef67438046e38096398c53349fb95a6919efac8d03d14c41b183205d5568cf86f7e36ad2ae17ebdd3abe6ddb00ab775047d3cc8807c21cff016025ff9f145ea3b2352e975cddd8bae00f08a0c123ec9cf2f31ac75f45d30ffcf9b5d65d7e7e338ff5f400e80b71ba45eda3f60be08d71c484c8268b26ebeef92ffb3baeccbbdbe0355bd5fab1220f29c08eaa40c96a8aeb53fcdf4ac84585dc372e95eb1e6060ab8c6730b4ec04d05690966679167e79862304a8fb717f4d05432a9ab6082f7d35e8646cdb3eb69a31bba7868870b7871ed44826d4fe400f1c5c43537f80173a49a1088f8abdbf171f16e41368d3dfbb8c7b1cce0a85232440e33dbae5f61fd7ddd17ebaafef2e5e06617880c0054e000b2030fbb5ba4a02073aca13eab276c1e874c376e2a2b76dc7d4923d6905b65245297165d41a55e95c30e623c0b2d8815b5c9986bcd801706f6b9261fa9b0d26c1ccdb8e283639600a9d283cc8e4bbc4b9564ef7df332bc4f9905797a26e05e5cd7af38fd655260c32ff83a26ef1746433d23dc61259ec835af1322767f06703f2e6548d858321e4ac4939f8c2c77bba97c91677b3607b231d0c774fcaeb9394dfdf96c9a899341421c90f4e14758c8a9a048d125aff20669fed7b0d474bd44caf74b52a336a671c493db06c7c92a8c1f379fbe2a8382bfac44d244fec0e84cc0d61a7c30798093b78c538158017f884aa3347092aa17ea04a0e3151fc40fa48543b01e00b85d8a71f6aefba7a299a49827fbe147f9ade3a0f052cec21a0b5de196599f4fda1f050e6633fbf267be3c63d997e12c00f9c84e322d22c6b94a226a31dff4823267a748cc81eb9ed25589685e524f629c5c69ea8a3fde99e60f68bc92739b5c6e76c6dc1adb3bbd7ecab4897e753241a8098f1c2b97dba8bdb0adca96f9ea5e608509c4d44dfcb60eb73f40254aa577c1bcec7eb270f9d07b4e5dde04b9cfb41df49120dac2efeddf431725c44bcabc887a98bc5d5bfb7e873e442bb1dee089a00b955970bcec608d2851637fa5d0c11c88b5727c11fd2becd04cbd493c666b77eef24949390b5df72ce0fadc30eccd3027f67a2363fecbd4285becabbba57688e361fc1822ab6d1046d67ec5c44ee785a1eeac309296fcfe39b33c275e7e502372bea67d7e4b8899a573198e0194ff835b19bdfc91ed42687dff87fcce6997338d735b1c019194e72f9efc8aa4a2141f45371790d2a6a4c9b5b9dfddd693f556cf28ebf758ea235268d712fd9a9f1ee0e7efbc51f7ea49e15ff3cc3a138c00ddd0559e6c9d93f649937516b4d630667a2fdc345c1bb1c1d218e5bb96f924271bfc2c359e6089f136c68330eec2d46c3afdea2ef18aaf9c35f5b12281635f88c8a6ba24b62dba60682027f826b3117365b3ac7b37da680a707a9f955f98fa417fdb1589e36a471c4b7c7f66f9cf73a619885618466a4db06782bacf095a564ee030959ea4a0c4e18f861e0a61c657c2afa8ee3a97b98e67bcca8bff16d76769cfddc16d37d163f74691e65e79b790ba4d3624cd1f3afa601b50cd72a52067077d31a24145638b9269ffae4ff9564d2388d3f63f917a95d3407850b3cc464737060a4bc6bd79937ce2498fc21a0f56963bcc95b34e578621b9246604a6eab683367bde78ecb4edb56aff1e3f11b71cc3d5bc75279babf9bf726c96ee469eb290793d075066e2142fe4d03c09f9e4da606077404f91b0382baebe1513a7c9e78fbdc9fe4d53bdd50ba2e3d8e2ad7039e61d23928247b29ed788c7fbbbf2748197c4dd173c5576c6443d630ba44f702429924ff2751666fe2d34e266eb3234fe3e9714c66aa2bfc16cd54bf02b827a6f16e41a67bb87c8a87ffe9233faaf8c79274eb1ba74fe85fbeb8c2619975e415e60df508bfe7e8c8f4cc26e34f0367aa1070505f414fd9897f828378f7993f380e0f7b2d672dd2fc5df4cf200607f63d5a4b0caf05a001a9932a53b00a8370802fa03830a95e4ce2578f304746103c5d9b605f0d6bc60793165b214dc65e507c31dee160af1f207ca541d98cc67776368152f50ca6fb84ab326283d00f220a93e7c931e615feff41bdc56f73a73be4ee12531407f25887fbe693b0a4b6aab279ee25858e139838c1ab0404cd9407597d45c833aefaa2771760946c16f1e3879a9b4f29dd8c95d8e713f11a3a49406bbd7d6d828e0bfc98bd62cf1d1bf8e36c4725fe19941dc9937e89e32f0cdba3b8946c7da54063260ed9df879c827c92a529796c412ab0c889999c50729d28f042e0112978e7811e2a5c42fffec9c68668d577d89fb78229fdd5d97238cb33259b33c700f8ca52a25209ec3e10c742618e219105eb9bb6735cf6f1fddbab1bfd0c8b05c6e9484ce5599a819779a6b81c386730a87e39ff12023df3427bb02522f80456bb8e2c7bb6d948b8810d8a80acba3a34ccfeff4e31bd1c2671b0e555abbe6b498deb0a0d7d07ea096fef8ce84e4986eb8b7004141642c85faae28193616c2fe7b76b9de48db86600b8981c5f6ef747df083382c2f47aef577cb4346547b7f43ac0f41a7372332f2f3234289daea64c95c769769895c7340c38596cc9f5e31bdb5526d9bb24917b5ce6d354c4186632a9d6299e0c8618b5a2fe36eec5e37f2801fd6d84e7ca462385a1ac58f486fc1c9d1832ebbbadc41a3fd0f82503eccf798388548c348210cf69f4dabcb6f3e70c332acdf7a5b54a022e98a81fadca535bc7440f4a6e2aa361d68e3cfc733f679cfb2120f07f63cdadf67f5bf748a71d19d664e555f76a3c7e4614c97555f232439625ab73c0d8feab4f534fa91354acc90dccfa886b3e71e3454a0ad183c0a486c219122dad0c7488d17c88f88f53d65ceab9834eaf8166a982539b69b8bb82bf0c3b30e9a2e61ca91202752277e207319a35691a4f347bf1d6fc56190a3e43cb59e447c41e2aaa2054d3fffcff323a42789e3f6218a4c4162bd1e574121b69294acb375c5da3b70b8b010bd8a629ae64054ed83d63802813bc8af9782146efca9565d7d32ef466cce972964afc1b6e1190708292ed48ba12085844f6b3a9d5a31b0b2641271378b5e5d216568db0e19c735932d15ddf7ec63e91a59de4e3797dcc628ddbbffa20af29563e64e78313e4f2656c6f57ac414730df464ff451e603e5bd2d2b8bc6ba62eb3b2e1e710f20faf2a0c232f4f4c2053dafdb14bd6e4fac6b212e81d23f510a4755e8dab7e8f543303a1494be1ed505b366f28eb0a93c476f02605e0ca082c928a91057112c11d02a602c7a37623cbd4d599da03434f1884fba8701b9323c642f0dcf983e39b5f682e014eca3ab06d1734aeb415a714dca15188dd88b473b5b06c985cc481f4e96ffc4d54f1774d75ffa9a7d58fa7bc53d5bc7fc4e465a9d8e105900ad074a46ad711521a3d4d5bf278acea9a887ef789d962b17d91dc082783d0314f67e4952dc5e0cd507af0d98cc92ad91f6e2d1b14b395c70f22494713ae3cb38ed9e6028e859bb379fddde512295d8220cd7d16968dc3d7db1e81f6575a7a63988839cca6ad3857f1198b2054c4a775f756d2c3fd60b922ab2b34fd3065cb487ca5b0f19a0ec198a45fb2b4b9efaf8dc82a260a741c148f22acb2aa7b6ef0909eed5f7292bdba20195ed81014e2582511b88c7c30b7a0334216555d712149fa73c7cf034a6363c3fe1e18687ccc22fa64160ea2165933a4a7a09033a2a4f71945ac18e70aa68be25493f37e19d9e2adbda64a9b3db6314c47461ef63c69d024d5d1c9bf5ed32a350ef6787b722bd483e131a1eee097f49508e45fc519b2c0780bc95f0caab77379de21097aeab9e2cfc5720f47fca642370e17de94b0830632b1ad3a28c98c76776a019d3708d0a6cf276777330385716cc014e04894fdf4c0b52cee7d30bda21a1dd22d4756847447b57975ad120205f30ccc1900a5a0b9b439e46428b168c2c05350ee1c4061e7c4de9284aaca9b0e2ad5f19fa3314c26ee93a79b560a91645ed7a6774a2af802865dcd6eb6ed4621f96ede841c6434803764c3bcd27cbf92ff7ea5b76d77a40246c274cd83f37d7f3ed70481a387e1fd6deb790004dea14dcb062f3f5975943ea801e12887f364a76a8a0ece689b2c1d589ad60fbf6b1e3105bcf3b111cbfa39f6b01af73e4cc51f2162631e79202452c805423938ff5c1685e26ba5e0882f6d9e1eff0c465a5f571f3a181a20dcaacb49078dfdb9bc9aaf63d25d46c9cc683c352e40d1e19a929bccdc4a6b5125d2a7929477d57756cbdc1e9a3e72655b5029aede61c395a29f566ee33bbe66b0217b04c2597e4cd0218b436abbbe6d024238d08e4cecda778d6e3e50a5754689d3bb4aa8668f1bfa0a101dcec9edbeb730c6dbc1e83264ab96b6ee75f7ca748c876f72e3bb75f053c879497bde09092dd653237a79515b4d856563482a13e969ccc9d37a8627d7227fa78a7f8a17939bf92574aede9e7658a80dae2439228f95b2e3e2d26e29583da6b80d3cdd3d23f025729a2322dcea557d531583ba9112dc0e061bda12a9dc07b420d6f7d9ed44e0f2eb8eef8fe34a96180e4ff7a3d51a365a2cdf8add5a7569f0e01a2398c1413d3bce43134b0a43db1dc09133ca6b7b4813b7bbe553aee80265f375a0e8b0219bc710255e9c52199b9de6547aa4da874aea2d5ed1552e3cd13102ec625861fd9c0d25c9b7908440c582613b4a061d8b19611b0b25eb7f1623a2584ee9f5a64fb9165047756f7edfdefb841b41844546f918e694e2c46b278f30801e370da56434239909ede42d6f5b61276759949c9f9ba347ac972de480de83d49d55b2c10749f41da528f1750508b263a956a5d3d3b35417881bd9044a97dbca8c956d3719e38f34a9767082a2eb3a299381ee90cb65485f76c76848bf3f47ba156fb9f9f2f0c869f163b54f84583fc84e74c1df5f0e5c708afaf324d24dc20ab3f8d0516f30df92f869c4a4100e318abba7540d5ca3bac2424cca56c053f0a5837810712f9462fa8ae765a5139f4005d0b460f318aabff72a651fd6b4700946804265eac12461b7703386c04fc3e4904925df87d8d7e35e9bebd192d8fc1c04854d81f9b94d4c3d2196e4498f85067fb1f97fe7030262991fbefd912909e6041cb7def18c7e8ae85aece40418ccee42cb1f1843327d60f1ff419316584a2a67771480e89334cbd1a6b33fafe419f10d55dc34c597003f2ba75043be6a725f83aed41ca91c8d709fe780145f4201a76a54bda0cbd22030350e89a3116da16a6a6ea7b763eee767206d057dd10216c39bc1a51dba4d007b23dbf2cd6325c8ad3aa82d6cae6a5287806447dc143273f500998b85400cc27b350c27c262e0e38f0a50033e5e4a9cc95587d6098fb1d2bcc104df3f1f5e8989c8dae1a48540532045e742987591e2bad48df05036d30f35ef3aee01c10d983dbeaa944ce5f3406a141ed59d5fcb1561397129c48323a965dcabd89127ee444b01ecc83aef5a2466dd2fdc4b59a5ca3abe50477f42bd881bb9abef562a7761f9eab54c04f6af33eeef4bf240d158b0488c4c19376ddad59c4c01e8d59bb022824539e7a00c67a8ebc97a3aa9f582c3ddfede0fb47db07f297a6340f0a8e9651bf416fd3ccabfc12f69ffa81ab0cb04817cb566ea7c6f2e98109d278cc6552492bbc8b6aa67b885e9f38694f970f7504cc35091f20a97ec324b8e912aa3af3d03701c0137826aac74c548dd55a05c75323a0f950efd6d29717d2ee7414725c5da0660791248fee222cd0b0c02dfb61597e0ee9e51b809586e01c1390c60564554f1b9d0f1ac86a74fa59834d0ccbf386a0cc6cf644fd97b4e795990e53a5cca75f7a86dff1ef33b8deb8be9290a7da49805921d3ad50db89d1a7b3ad1c7ae03e2ce9642f69993c144cd8e5965c9ada5c5360098dcb008d84bfb33e10002b73616e34cfc076ec826d8cc02879b42f6fc94e501723797dbb816627ad5c48f7e352794feda0cf77fc1c65e4dd6e4010efa061c09d56e7ed6e8e114676b50a14abbad14cf2a664127b9199d42c543e5ef4692b9ff5821da9f9b6891bc74880d07a0953d504ea3b5d27fcf6d062d1ecd58f495a60458a4061ee440dfb8713b34cb5065eb18392851c69c52420a5b0615ec80c0ad1e54b7297504273aae6a4c434b2b83ebca82f80870862959315e11292a5a8414c99d6b1bdbc7ae66ac0468a39ac90342357159307b1580b2883b6ff98b4e0caf6f8816f94e94a0390493629de113ee80cc56670866aba0ff70c43057b0921eaaa3a313cd090d8862b60ac688c16f2731051c1844eaa168335b6e884fbe093f1ddc0c707b714d750aa4909a91523a8cd9504a2e3a20c0bbffb753ba4212cbd3c3e0e0ebdb6a6be818a149e114c5fbaa9831ed830d80196a00c4be9ab8d4e2f3c75266288936cfd0b27fe1adfcf500f4d2cd736165e80b765598297a57f28ae75e54480c96d3b4a39206f9f900f993909c28697b553601a150d33fd921d36425483618e00d07b953ca7a5eec7b9e0cbf187b6be811a159d0d30bf94e3e03f209d3066dabc90a71c92ab37bf6c8b1726ebf32adaa58b8a9ec1ae9d3b0d5df3a504f2df55f72c2c4c7d24405b55f81430a339b74e40e4ba5adb599ac60b1934a785ddd2b7a8140896571ad23c2038464c8f087d4d3c81902a14b282695f3027ca72add8388c5d21b8257243d666eee44ddb1b36afb76c05361bbba9087ec58f3a9f5a993fe467d2b8fd674a66cf05264c09a52ed99342145c9615b3b6e2eb85a6e2ef96fbff37f0286fe2189325627e8d9338f4805d089edf52626d7be864964406fbd39ac2ba5e3125592a7a338d4a5808e9b3961de474701222ed554ad18bea31b4286b9d2f2f2a19e34f0f71ce6136cfa5849f05032660014660e830cd012dc1445ffe19ff4c158de1216496446ded1793c296578baebe6c4d2e07faf117cb0a52250589914f2da356311186b53bb6bc2a36f2e43cdbec8114489f9e1e128f0eaeed7b59a531fd45e91f423b7a26e3960ce64cdff14e48898f806366b1ffb6a00468e6095034103464d86b589ff2bca59d92467f245fdfc69b034df08ef5f56445abd9f50cbdc19e80f0e55c6dcfc6d19ac5bb598e5183ec69d7767f5666599eb295c1ac3bdf2e735d86f094955746592928aad4b2ef59cf815107248a3cb00d62141aaae8f9c3a84322e25f1802030ad5114f6b4c7f20ae4d70e1ca57b2725e2ea060e5a49fca82bad1a209e6508bbfe56607cd1ea35ceba58d02c45c036ab4ae8e5710400b398d01e149323e741ce94282775c1cbe664cd0cf78bc3d3e3038041707c220e96dc40f08e2196ce00e3a9c734bb965461c169cfc91795d7063c9ad56944c24484b530c129166c027bda4edbdf7de7b4b29a59401be0976083808ac9353cd9e9740ed11037f37087ed23e38a1f688859f341d4faa3d62e2ef1663e2276d76729eb1573e727cdea8f6b0f7d159a07a778e3c69201557b7b97570cbac0320ffb02ecb3f9975200bde9083fcf31d1dd6330e1c30182c76e3062c0683e1c071a29a33396e1c7ae7ac4c26dd9df31da24a61e10db7cf5d2e3dabcc759ac970bb7c5cb7d9e7f52a16b9ceba158b62076db893797ec5a29d83532c729da8c6ec9c7356ba52d9f890509c89e26c0bc5ccfa4c0c75e48c0306c3913fc5a29c43aa830bc3ccba8ed0d20c7e9073ce3860301c19fc3399e40330b3fe41fef9160efe65d669fe60b00ff67ddf7798e5e0cbac73907f7e7078457b9bb778f0d3e79c33ec200c070c8623e74c2679107a99750fb2d65cb805688bec9e58c4fd070cc5a22d003828166dffb1c71d2091615907aa1067d63bc0f63f99836fc9b1bacdb1caace7c85ae726a918ece02a1c6b0f1c27ed66c57cceb3a393734a4feb653528ca681f168002a954e32880cdadb3f278904e4ac18f7aad95ea8626996c1bda27cf99e78c757bbef29c2e9d3d653cf94746ea107aed613faeb85015ce5d69f5e34f0d975e6350e0ac330093a513b0b4e795bccc4f9a5417327016c648bffa9cb3e452250a971780a85d239aea140e58a26d57e2eec46927416c192597a62d658ce145470f863e51b1b0ac3d4b2c2eb858cec04e9b0cdc0e9d6a082f54e28b022c431688aa3d4b1cc850b5b09a325e61853eb488c81197d031b353f278d153e61003570e3678814eb0a8180d3e586cf070830b8e4d887360f297847e224727c50f7705076b3ad8ba0000f8464606101a1ee810e703271d51661b0c415c0d3bbe1c84b804a0fb22002ecc00c6f0d89e00c14c8f2a7c64f183734300170c6d6200c16414603bc30059d608c1298814224e866d032dcb01b61f10e079293242842d09214c8c5066081509e0b020c28502362f16805fc0806d8c226518d9c106475c20817bd28028231a1cb0617980c743095d906c4440e00813c444800909909980190aa0a9c01516d0e2026d30f045065ea0013cc606c8e0001a1de021ce094e1e8842c2705443920d870ff48002e60202292525588204d298149e4832130215a8508a60e362052f580869ee61043d8841821ed4e8818cae5645ca55f2a20564e28416319b3e5c4ac3e446dcb3d402a7052d3770e173d2c483a1e9b3c1c9d3f224e5c40828259e744f4091224a29650553c0a062c60b3eac11e504860f86186e90c1d352456a06303424514315ae89ce4a1457bc2a6cd8e2062fb0747b963f8081c30f64e4f0031a3a401967a9c5cd9ee50f37e8b2dc273b6c5378d8828593a187eb458b962a5f36a53ed8227eb8466ca94870b967800869a64fc6596e11b3c970d72d68367d1844b9e58a2e21cd5bd4d02e34080a04e542b7d01fa80f540bed81f24077a059a80e34078a03c5426fa036d02bd40aad81d24067a055a80c34060a038da22f502a740a9542a35028f40985a24ee81375a24db4097581b64099969828d5483002165610810a21484a0169891210a0f0812447a4074ee8000736a0810c60e00216a800052620810898000124253cc001460d20e18891220c588002882460c80842442842c001689b284808062800902102fcf0d103041e03104000847604cd747ce001908c7ae1e1059c75f686da4a3fc51e33d35d457a2a7a818728b168993f188b0ba071e88efa5b430a640b412eecb20a75c80debcc8bf16e3224aa219e43ddab8cdcce11118990606fc799c443ee3eda6e0f762156a1ee6e88bdd4aa30d64becee463bd61280d4623dafee7acc5927903a91e81eaaefe69c75d634f0efbd07f1ad9a5cdad75e9188266dfd148be6d0b5b4d2004e2a83fd5662a9b2377c70ac3db66bbf7dd364bd1e527f74496ae935d3e6c7062a6e6c90e2829b2b6b5ca141b7512d464bc616639581b5026705576220b45d79c10ee32fe3eaf1c5f89663b86779858b2cae5841668a1d5dae33ee4721acd4cba8ac041ab88ce5952c54289c999a2b51b212ae4071569e5869e25aa8426f996ddb368ee3b819a2700933581967e08267909a82c50c59f00c3e34c1a2099e5d51420d6f3a2c35c0296ba001951916698466694a19d29431b86056451a5f182a2aba1b64f8029732bc6046c5ce9e659518ca2a4d68b629a680e14dd40da228226c6226862e3786219a988941871007331886336270010733314811c312294881d099281bb80045d40c9e405146a9894243e687bc67f608c65946899965d4112fa0a1d2c5940b9a28f1829b5bbef0c54c89d59e651497282d4d4b44e5f0b4448e3dcb176cb0c73dcb17c0a863c488f91123342ba2a4c2460b9b4549c58a8dca1927a0d4134ebe259c7861ba22382fdb0f984a96a74bc586272a559ea85079a2d2c2940e0d318a31a24c61c113a9295c40494db1028a14b76739e54c9d22834ba3a9062e70cad2a40627dc7456baf8c270b20418181833c41a6160d0c50c42d4104319058fb809d700d54df9d24319a58c32ca9319988e8627700aa6e012b426103540e9f095529e3c990921658d2dee594ea161fb01bc1dbdb7c746ea78fa88fb778bf591fd7634bf4ca4de9b9b7ea9a3f489d9974a67523321b66a032f332f5e84bc04f113a5890da0b0186f9cbc3650d438e902a58aa72e50cc38e952425162df3d4b284868f9d12234fb014a540950ac64114b285252663ef4f024ea095409e504ca05a129a2c062b6eb02ca666bc289a7c56dc249699b70c255819b70e2546736a431830e0a0264bc18a18316694c896206275f6ca5131840cd6ed061fcf62c9d14b1c96719597b964e76d8e438db611cf72c9dc0d093c6c66620b15a40fdbcaec92c90fdb474d746210b641f8b19e14ec2a43560d2b8739ac802d96fd6dace02cd1891796d2fa5df178469f12908f668fede7655f6c5fd7473e380f6a5145cf66bcfd2ccd49c7a0b1313a918fd3c08f3415f8c731fdd237a3c841eeb9008fdd535b479fbbcdeb269265228cc4b5da1d8fcdcb121b7f4a1ae22117a1bda9c635b6246dbe79ea209760d18555aa06d5a69b440f3320b546f81de8c732bb92cd06c02aa8fc58c6c2761d236dd8049dbbee96981e683da4c9a1bdce68ad0951fa1d1f5cbf0b57736c30c66348c76cff22906fff9f62c9f58507d289fb4944e47ccaadcfb11cc0ed33483a17c9262877b964f4ccc62289fc438cd6098c9308bfa89aa325b69207419bef85219b93d4ba72f9bfc65253871f9716ab2f19e65d31a9bbcd0396e0665e4b8321568a229a149cd78abbce9a87432b8b965d3174db328a3d093262eb4000b16512e3811830b61a4b890830d38f62c5b88b3557b962dc8c00550dc38712a5b58d33473ead9b36ce189eded59b6204605b3a69f0a52f108f7edf786650b58c0883922617ba76bdd9ca25d1dadeb1ed3bad7365a775288a375277f4cd188a3795bb6dbbc853b087ebab644fbdee51aed4e8d7cba0947eb6a4e4b340e09f77a63f2aa71c47326db66d26efc4ae90de396b137af88771dee1b99549f54ad97c9ba79e0ec8d966ec62ca60a764ece324a28c7594c0fd2a9a9da23e73c94a3534aee47b0d336932fede9efdc3724d6078d7a3da6c91e982647f1ae1ead8647df05e05b4d0ec1c7b714883dd59eb524ec1bf73a1ed9f37c251609373ee5a2c6bf34d9b3d2a2ee71e9284bef82e915be4a14427fe469f122c523eef44bdbbb7af48ada8b21ceb2c1106fa1b5cff67787d9de7e3c6108c1675d27733959759ab7d71efb23aac7bb73a86bbd63fd870d7f58efc7eed9e48fa8f0944671e948a1ed79da1bb577f26e6f0c0a8414dab85672bae101c27888814f4feb022e3dbe97c9a4290b747b7ac6118ef72d098f1d9e4cc2638f5f9d14da5c7832a9b2b26bcee83b5148f8a35193747b07cf8945c21dbe262d816ebd3cb6f79516f51d8302093599c2d2f60e7e78db1d0b1b1f9cb4623c04dc9cbe63d01d9b5e72d17341c20b12511b5c81734271c76f1229081e6311c4182b31d97e1f625dfb8e7f2f88cfc4fbbde045503f714a4cbe5fcdf43d7ca89b9eb8dfc7eca1ba8334226bb311fc2f7bb9c9aac39f342e370942c3bf796efa096b16ee2d65f2d11a46100e73da16f62ca90ebfa1b3377416c71c42c281e3c61c42ba71c3c61c42b261c3670e21f9f8c4e610522c069b434830d86b0e21bd5eae39abef9943483d3d3c73088967ce26d2ce8ece1c42d299b339849493d39a4348ad166b0e21b190c639ab5fcd21a4d54a358790542a710e218962388790c2212410fce610d2f7797308c9f3ba39abdfcecd21248edbe610d2b6e1398484e76c22dd399b48d6d6398454e7acbeced91c42a273ceeab7d85b3d972c135a2f1990f6501adbb40c23a53c224b50963065182c5041584a1388d977cff2082c6ae89a103b6dca54bb9174ed490b9f73257c235a6dfb146b58d76ad3027137028fb7df1b8ddffe4d7d17f5953252bde36e24fe1ee37f376a1d1ffcb273f0463adf3e6a1ae57d3c4b8fbab675bac6b9f97eefc11b71bf1775cd5e4aa5299a9a94d178d55bbac6b4bdf51d4dbfe0e7e85a4bd754ba268affde690bd4a48cba83e7748dbbea2c4db35820fb51ff7ea56baa4fd76a9f91dab4405246dd435d0375add3a3127b0bc46481acb54016ce54944bc8cd97f11d1a8e66d9e074a3cb0da74734347c995a4bb4764d5c3f46a130b2ae9b6166ad79c86d7aa2d56eba6bb69bc8d24cf72b3709427b95814dc50e956fc38656a25fda75b777b0c87b2b9374b7be4cae003ba973925a391232b7f7bbc57b393956ef02e77e8864f4c76ea4e32f5d938addc675bc6643d79ad0383429fc987efdfb0d55dfe1519de7b121aa87573d3c19d3aebb32a94335c904c9e8f51b37fa5f52484636fefaf5d44a2f5d7bdd86a66a5e474b82ec7c09dd54c78680e739781eddfaf89dbc84b28680d7d1b24dc5a2159c39be79e99abd05b24216c88e87d16c82a6af9b60e33cda8c9306804f1accd69e4e833df65786caf1d89b6c5091083d08c66207ab88a4c9462cf69812130e0efb8d14f05eda266042f2d2359fbf0ecbb5251aec20169134bdcea483c360afacc4c4e730cde4f31bbfa19b9e72fcf598911c87fa39ec93168bdd464c3fe5386c72f0bbab26b98b26e8e72cd1a658046faa49d53d4d2f8e5a05bb49d93eaa43b62dfe836cc402d97b909158207ba06c010b642fcb2758207b00acb0c90e72931507fa29c7f57f72932034a51c9a05ad270dcc643da23ac96eee5527596ac58a15fa653fe5c75eb7b0db5328ebe3c6ed6fafcae48d5cbbf1a77c92aad9b406fb537eedf5a77c1baf0cf5bacf6119ea751bbf91a15e875dcc47cf50af4f0bf43c693e36b2a7c9240bd32a6c9bfe95a5ae5e1264537dd47d4e6daac6fa88b9b946d0a8b0ed7ab049109acf6d1c6bf0b0dff807e5a39570fce1b43e9e9a04a1e1b88f37e79c739ae08c09ae6cf086b73e9458a0d1fed833688791a46194582ce46e4f937649907dfd84e3361e821aa9eefa271c879df44e52355b3fe1f8eb9f263d9d6477d52a6cbbd35b7068166e5c93dde8d22a6ce44403b393898d87607da48fb6c07eb7bc8ef1abbe2ad3066f9e531b9659989985d7412a1a79e94e24616a4fb1c8c84b57a65d7596782a2c0a905ee602b3eb7dd4d8f53f3a8ed8f561ddc048bab6cb045e76bd8fcc882abb5ec79d4dc0596957e3f515895dbf6202d273f182a67a7963a5a0ae141a2c55c6568513d309f1c6abf20d21a682437c117e613262d75fcca22ab3fa0267acc222224ccb4d0e1167e858d929228c104f11687a8a28c38505cccb8912c08ad8f58f95e04c47c4aebf4f09c2a835f4e9421b3f6a0f72b442c75a6537ca6c602465bb04f366d7eba8a51729bbe6791e26b8d689ea14774e547b6253f43951c5b1e553b1e8a7a27ef7916635f9b32b185234c1449b4cfbf5a0a89174fdbcdee6f8eb53467afb23fbf0a7f6f8d1640e9d446853fd7ad594295f8b5a8d4d93aaacca6ed8c011e28073239f60238c18cce7f3d1590255460fcb6ebcfdfa2b1bf5f0b83a179a9e3c02cf199d9c1d6e47ebe49ccc1a5b5b4b68e4a1125778a563d31f51067ee10d35fd7c3aceb3455e38e293f786db8eb3cb41555e8461776b10942028425a64e0ac207532762fb08b19651735e6ec83336fc410420f82b5522b82dd5593b2a2dae96b6fc539a76d0a620d4ae9bd58b62ad65a8bf1ec02a68b178a8f29bddd5c24e8e244e57400679d73d24fd102f1660d1067765dc2da0fcc998228c38c2f3679d7ec3341a02008306a04a125082c4148d9358816bc33d000c128206e00820b07486a054a0a65a24db5246befbdae15949cc0711cd759dbdd7bb9241aa8b2eedafbce765df729165924dd08f75a6befbd5cc6c70aa8b28a801f171299cc474703fbfa24f1193b34ac74a3eb32a11f96c88e60c422b11a489254abb4494a558d098261245dbbf688e1df8df127ad8393e3f6bb37009cf4d931ee7773dc274d567bc4badfdd019de4b163deeff6bc4f9a071fa1f6887dbffbfb3e691c042a9fd5b1988f5c958d7cf46bf57195c4354e98365a693289cbba88f6bc975e3af2d49c12a441900983cc1764d69464d4b852261921c818b15d5546a689edaa32b1c85a7b2f1932656243ec26992ccdaff1043ece4beea990797baa91867c46363ed212da66e38b30db10d96a4fd28d978cd9b0bda4c3be22cc36334c2e3d41c6cbd681e76da6db6791abc95a4c84f94d54afb8648526175a60fad1bcd54b6a9bb47bda1e829c18a3904481c1519b4e61242b6c1c45a144d1a8bb46a98413a5182e3c6e18bbde314a3258ca27a22e1c7a4b409b435caaf0eb36a702d44981602057870c757e18bd93a110581098f1de2704a8d36a08f5aeeff8e0b66d0fa8174f42bda897b6973fef987ba7667b79d6183cc1b4b977f4c87e567104a23d750a74177520f8caf6ae6c9f936fde41b3b9c788dc73bf9af41e3b5704e3e9ba91bcf79c80b3b95b27dcecf017f69b7b6ef34a2ce285178570dfb99777deca4721197e7c4e0cf6b95d1deb5d8ec1fefa3c7830c788848f3d7c4cc7605a84a5adf31891d7592cfbed3ef73cefafac7357d6794f2655f7ce938f5a9fdc773419de7b8efef169e523eff9cb3fc2107bfa7b9753a0db1efcee0782ffe07c48df93b60dbaf9bc10b87692eb66701ba3c3fb262ea03b772b825095ddcfa9ee0a38c1dfed6e9be5acb55673f4cecae3c7be97ead4dfe625d4356773da30f15d749b4353cabbb5dafbf42ac5f976d786f0dde25ff6203123f93d29f528f5b890e46e3f02026a8feef63aacb5a30b88917e7a5d9559fb201fc6b9ef9c33341c131a384ab0eec2a9156a0f4ad636f7a9497b255c469a5465f5e1deb24e95e12b49f2a1da5565415323793457034ea2f10b51982f15e3cd23d41e56d7eef3642b1079199520203c3d39d11059fb638739d4138dcb5569d78dae5fb68530bef435c90f5a65ceea83881875aaac5231922ed797129cf48571962880736febb69dfb100410cd213a87e8a6dc11d7e31289454b689ceb537be053fced604eeede7ab77db1f16344ba6fd779eb5b26bb93db5597ecbed3d444191b0c37b1a8f52e8ff7597d3b9d7346ce59158b6c45c0779a5c0a7f445184a5cdba4fed71bbdf1355f137c75acff9d4f94efe740e5e47c75a8f11112fd2a0e60c00d9dd9e2727b3deca222c6dd6988f56faaa7c24ea4d35a867956ddbc34dc7865c2626bbbb734328d3eee8c6224c279898c02e2fa17190964c384c4c3ba9b5bfdba4707f4a7687b424c84edab687e43d692b6281dd1dc792209b8a0958d2c16972bbdd0e7ae208447b4e1de417525184edf76ee19cd15f1e73ce48d52e1a3267f44ce8924eed986e410f0c2d78ee734f8fbcf7265dfc39758380b43a317e155e0cedb55693dcf157994719e3eade571f3fc5d57857f7aeaeaa0b37f86111b8d77349b8d6a515b55c942870a3ea3ad5491d7cef05bf5d95799431aa1e923faaab4ab27baaf4e78c3eecf2b418d3d1b600ac32da755dd7755cd775f73ecf3b97bd9fcd6df73c6fdbb67b9bee964cf0cb28fce08cc24fd0970dcacf4f10f7feb85c5d5c231365302106136b9a687ae1327166cb6a8f0e0926c4f8a8341b54c558c97ba918708a609c251352f62ca7b26c01ec598e19635b4003dd2d0aaa629c5546492a72b4e6a02de3fded829a18f161468c6a8f4b373ebd32268c7d5f05382b7ee7bd83733749adb5f66a926a926e22916f7376ee3bc7b845b4cb0a1861eb98b36a64e69187919c6fc8a9a6ebc6f122b993562a543fef18368b9a33f6f72c91107382375b68cff20447ec316bf6077b964ba0d92092da63e277dd3924f748a2ba3c7af7529d1e46fa2934d74c09d41e36ebd833828cab7707df913e36fe23d341f270ed1177e1a827d7a9ee6a65ad95816098b9306fdbc3fbb56dde0eebc422efa0e775a0d779777361b661ae9b2a1106d9a031e3fdc765cfda5a67fd06054f2541bd7d77c73a3cdd778e431d34c448ca36d639c639cf39fe8f90475fb14887874727e11dde8a444810274e1c347bdcc93af9ee9cf0adf0ac1c7ecc2178fb2ed424f80d1cab6cb4e1528bda34c0ef344f3a3339dd6cfab3a9fdbe8368d22c99710eea2f1f71ef4e43f2d3a4d0feeaf45d587d8c3e58b2fa31cfbcaa3a1eea805f69f23b47bac60dea2fcf4d392d748500ed838c18c77b9ff60ad9b9432b1689a08eeb6777dbb98fe3b88dd3dd9220e3eebafcd34d9ab78d9261789ac9ef902e0972ebbd7462d5f689d0ed4de669f267671f8ed3a40edde8ddbe713f8fef62c662d1d6d11fe4ea0ebf1fd60785a25142412818e7c711bad1e5e2b237c5bbe81673901023495baff5ad30d41d9875aa6cc35e4ee2dadb7de80873733ae9120912274e9ced537bdc5d6b5556c7aeca683767219971aca2111c0d1262acf73e394c8ff8d1d212850c3f5c497eb424e19b8483b4586a6d9b5b83b4586a2bbddba7a236b80255d9674feba3155ec79539906afbed765b47df6da758f2d177269b66d2ee0da476bb1dbb23d0de6f47e03f91ac7583a326936ad781dbbdf86a122fd536ba6d14cb26f1bb7c64cf64df7c91d07724adb249bc7145c8baadcef656e049efdba6491a3541bd6992d3db266ef6dda59bce57c786d0df22f4f74828140572bf697b49d6712764fb15b7eeb2f4d86d611e3539d708e93eb1c803c13cf7ddc07cafddc11db72ee67095bd1359ba21494aaa6412ed3e7d74c9ac43a15020780d39d71cdd6fa7dfde515c414a528dbbe3cf7b2b4f0b744cce351b7f03bb7fc7502810aac9ef9eb7e1cd6df7debbb94d53287407be4ce892aa8e2640b3c34d4f8fb388b1b241d8b34cc24d125fb66ccf3209a89dc4d4067778fa338758dc75f66c82b067013a0ea60ff0cbf5f54862309705c1d0f33c2fcc47dfec3c2f0c5fa75f62f751f8af087da7c9babf8727aa96ce2cacb53f569aee8fb4567bfa2824bd935693176f4b35262b6cbc052c30e24c9318b65c41850aa2a8ae6c19d326aa0d531a4be8a06123031b19b059019b2deebd3b76c0dacc0e1a54e082cb1316fc0046091a3a2a90ec1a356bdc4cb1a9114cdc7174d924467aed9a3454054e382976c884146c9a9cac0973860b35d600b1c11cf6bc55765411da820b7c31be343466b0823245b0f1024a67868d102d3653556ce0650d2d35f0618935d6e852c0e6068e0d0f52ba30b808830ba61d3e44c1268d931d336cb1c69a1748b005182da0d831650b279aec88d2dab3dcc2cc1650ec90b203ca0e2818632c05638ca1f0ec596a41841634dc90051a38642186942cd66481e60325c66ffa510b5b4ab1e73f5862ac65137bfe0331e5127b7e0e81dc86b76ddbb66ddbb66d4bb3831b1a7cd12587285eca8889aa200c4e64740146142d5aa03853c375bcb8811733dabc81c1541764ca266219258506db0a153da8c1e50657b0a07b028b3328166840b14135c801c6932e6caca81203369d1358783143100d9ac61a039d2d60d8d1640cf72cd350b1c3290d133b9ed29c200d1120ad61677de0d1de90ce3bbbe9cd29ce1f0f4ca0fa273218b6d0aef5549f80c6483fc27d9d3540cc780e11374e614f7b2bb52a72697f01ebd7b4b36799064b53cf9e651a2718c400c60e4e51bcf083161460d1c5c3020cd4fd7f8ebb26ac7c3c3ce1eeb550601175b1a8d2744518ab2b6c3083156058b105973328b5e18a356964a046898506563c61c598d91560e4809ced79f2677b9003888ceea827b3d783f4117f58a07a12ef793a8118a13bea4998b38a4fde15c8790653b7da24655a9190c3364281502cdd28f3e06eb25b6d72b4fa225183825bbac5943161a140e814dd515fdba841ad78a18939ab27690ebb0a608d917e09bd00d61845a05e689b393465b95aeb69166b7b90581f9f40f6554d0f8ea9c3fee620538725daf653db32794d6698f16346694596b20a2382c8b0e2cb0b3228abb0a18a2a33801304a78a261ce26af18558bdb43359497a7f846f871c71f7fee17f07bf4ce3eced1fdd2038456b6b772ea9ba6ad326d58da92882e0e7ed4f18aa549fafaa9cc71849a14d647d048931f250a9c2f0f334488cff541f736aae3e7e3eac218c9ff3a178f04eaad811f1e08d446da4a2713658c322234da29126279553a8418dc6ab1d0cc02ff6f6895590666fc74b58c80d0fce1ac2ea24f74d2cb27d92f6e2b7af32d4694b35b216c24ad76aab33991584fa5941507de299c32e5bf1f75ec81d4caa966e5cee5ec5a2ee134f1d23b22f96520565ec1558ef9e3cca18aba55badf5081fbfca2024c4a6eb84c04e889b29352bbaea446e953b0be4ddb31b05d7de7412d70ec573a73a488b91fcc1719947191dd771b6be8a4482bcb12a166b6419b13ebe356aaa5b44c273b1d811d77b1ee3deddf66425969411eb3a571a5f93325a5d47d75a5fbd9673d57b726c7b4f6cd3b123e3ed1fc07a8f566a29f5dc7ed2d46cf19c5256ea79ec48cec79bd0eac95259c9953b0be4fde6696f6cb2a302873dbb14c447815ca8d2b5255a4d8aae9668abe9810ef62cd164d968ca6c3176c47ebc093d67bdb33e4656cc84d65937caf99863d43a4bd728ad4524dc44234daaaf94d493747347ec5937a1e7741cb7d7723e6a23d6afaeb5ced25624121ad9f9dc463cd7e9b119dfb36e643f3e288b31545a29f55ce52adaa4c0abb292ea3b42c4779abc9be3b873bb23855c737a625e42396f87ef9680d968751dae735920bc6d22110fae08e20a9ee7c87890314e1a8f96aadfd14a5246aaafae94f3d675b492eaab9332234d4e393ad6b5748c7352e9d8f758a78d8c629f3ec2fa65fdc61e30de7ebc655d95c757d15b8dbac611d9952ae9824b5e650d31c5384193b7674945952e69446931861a4c6a58d03101a6c9146fa6704393b2076b56aa9f349b4948816eac5370c0a435717772128456cf9da8727702c598fdf6bd1a71ffea3d13ea3d5da3b4dad43d8c1df13414a5c58e78ba064569debb1b7d6288892cd07d988dcc32e211a11ad4867b585f754d2a76a49ebb513da76b52f635ababeef4b440429b6bca75db0dd27a410c8149030f52bc55cede493b9513fd6afd1ac0e518be9320340feb23f49e56a29fb42526daa7d8804933526feb9644c5720488c65a29a6aa684489c93df7cdbaf0d138cd74bfbd8e31fa983edaa69598e0dfd18ed955335d3d2d90c5126693e3cd6ca9519aa7c4a4bbf70028136a5094d6bd09a57577a13b372f53a7a5ee39cd8426556b914d934b8e92e0578d023ebd127c7a229b56825f7f330a98da59af6a098d8302be1a85fa7a2676c9d6aa86d01938e0f77df105f7867b33a788b271e61451db942cdb942c47490cd034b5efed99333a4abe8d82019aa6b635f3e628c9ed42aa33b366571a7669146e17524c5b8893c14c83022e87a926db9ea51456ca28b49465c204855146b1840b416bca3371ce9c31069c3f9df5517f4fba40fa39c1b97acd3529a31004b591ea56d7a858f538f220e3fed34d55d7a48c787acef3daceeb29fdbd11fdbda7f7b491eb56d7ec5dda8805e24e754dc802713cba46a46bb740dc7534f855a86b52de6be1bf7bda687515f7d5e9a90e754d8905aaba46413d2d10ada7f5352923faaa6b4c54d72cf0da78d6d428c4faa44d375ceb9346395ab8cac9352923f02d5da334f0abb3b252f8ef635692320a35cd6295c2f368b3d29a7eb1403cc818b5524d8a8449c3df3e699f56c237d2544f53c0250553fb0fa0344c9b3fdc6ac9560e88a02dd86cb185150f82f7de7befbda0787aef1559e238de0b8aa2a8fa15c14f111441711445d678a20a8ee3388ee338b258add63bebe3f5d6c156265da35095b56eebf82f5d65e32852718e5a3cce2c16164f1031c6d8a51297c2f054960f2af1b67e652bacc9d541bccae291ead4b5319e26c05658a54ae2529565b8ecfb9db28c15d74ed8c3b3dae8576519a8bd7a584271c6bedf61a9be8a81ab71c43ac6d23150c7b491cf61f778cca408ae34fec6fa8db10eae56ac1b815ad4e409fb7e458ae756392a955e81fa28fcea44555cfd86e1d5a46ba7876747b55aa956ab202edebc11bf3a61b36e3beb03f6f1e098b15ea9ce3acea1e8da57f599335bfaa56577257189b48442cd06bf7a3de7ad1c4d8e2f55a71abf164bcce40946ad7c04a65de3a793b8c4d545708a4531f1ac079cf4a8542cb7953d4d32a56844000080048315002028140c88c40181482c4dc32c647e14800c8b904c6244154a22418ee32088814806a1184388318600630840082169ca46001418201158a2d577ecb846043cf14c7c438620918a066b84dc99b810adb5b4fd522608dab7562a9c3434482543684e2ae184f7720053e0fa25bbb099c630e86ea9e253d24e22cf2a5520fb06205a3b3072bea38bdff101383d761ce609962d3c15cb319fbe29163cfd34a72b841244ec1567e48281e58e78dedd6962e25c31f5d174f9984e107ef44b6d9597dfa579430bb81e119fd5e5ca611fb0048bdd20c01136cd6520cd1d5d2f7680965d00fc0dfb2ea7be4641e0fde1537a128c00e5a801cf37a823355a3366cfdfa1373c326ca6440f74b9ecdb7a7fa4cff0adf64c808f8d8e820dea28595f6a786a5019bdef8f85b0dd0b91d8ad6f2c15b117104d2e9800f1e4f54331d4ee7fb973f5dc16bb608988c721082dcb6c09157725a4d5ada288a1613cfad6864e2b49e5e6d61884974da39bb45d670f293140fde30e14e353c9c228dc34b30adb9e86469172b7108f28f01d92d6ad27832cf6c4bd1e7f026f380318cfa1f1ea6857111a25e84f96a5eb799640d1836f89bda1c3b99c4b4a188072fd52e4225eb2eebb54dba0bb4e5771a738717151e011e3e7822503212650da0f504e75b55b6d0672bf9b9e38684aa5d5780923fc4ffda11e942e141755b0d4c3db57c73aff42cbf65bddf4d9ea6226e14a93d5401862af123730a1d581ab5ab4540e6fa0a46e2ba4cf3e1ae5568aef82369a1505e04df7939b6f3a3a9ddcf5bba07370867aabb44bf228e799f37739456c8c5472c455441d03e73192f4679c9155d22dbad5bdc11690208bc3a31a55c2672d0bda8157957f8dbb8e178c961c6e803e4b1fa72f35a52243dfba9e6142873785c9643631bd9acd931d0598658d2f025e1ae4e36e6c6bf4afe3e3c0b4654e3361da24632834b6c04764095429bbad47d91ab1e92b163266d15577fb439948d46f0e6b55dce9a6c32dcde79c6521aed209409fe2bcdefc98b4cbdbc5b23c4cdadc6d1c3b8faa7ba3e387c6b1bf7f1193d675f9479417aca3d35c84145139815c5943950309f8b5767069cf5a6c828a2cfe29f5dec48261ed07d416bbb18931b4a70c3b98aa20924623461b92225dc71330e809ec9e420a6cc45abab3d36c9055b31d17ce2e59a1ec4f3fef3fdcddfbe8e784dc7a03318a3bc16f6965cebcabadc0f45734ba6c0a7828239a31d0d5e29b2c83ca48cbdf03a5c1c0dcc573cb47f073baec4a8579203466d78788264cdb333e353061ef40b0976bcf502fc2a95a18cd877b695c574000df50501315e9905e2a0e0d0eea9d868795231005a79b337d7e4c7bfc50de8315579b2ef2cf93c9feea000cf22632f0a3bdd2f7e7db25ac11a9f2b9520e7561eb6794217968bd43d754cae53266c9ba7a84cbe3e823e2d4ef9df0afa1d6b7fc9dc719a6933bc89a3ea6075dd69131473f3a15c54f8a59726ac4cd86ab6833b4c77f4a57ddd59ccacd97f8e81f52dae0a6feb685684bd4f798dd99eb0b492d364b22304da8f2fd6a6f342a3ee8cf2ce8959f6078cc9615537dae85888671579d11596cd552a32ee40982e86110cdbbd534333e5b4b0d4dd821d83fdb99434342e0d6a4220a5d991cd6ed3f1bd32355552cf0455d48161ec5eca4394e0d91c76b41c0f5e86cc5cc808471854f70a0e83bbc80176579824aaa1c05a121d56bbc80abfd80d42829f246270492f334d6590c0213fc29e1630bb7ae9d2169dca6a4e62624281c0c7cf88ab8d0c95b4e12ddcf76474c59a4cd54918fd262a38a67a4ff30278d39a4cc396b0773f2845a70d8dc884a7a4e94ab1f1699e044458b466b8f7926b6762e5cd44647bc7977d9600407386f4764cdd8d3fc5a6141246a6c1916c75713d117a8088d5c0911d5589e13a9f905d9691728878f3716430500992e97c55ecf3aaf48426eadf3816bd7015f66d469fc836fa6dbc8bc9763f9b34929ee790370359082575303bedae2ef7100ea15307c9124af2b487f4c36988dce6dc4c1110ff6cae36a44dfab582f39bae77ed0bffda5875a43d70b39df6e266c190b6664732cd8a59dd5cf99af8d22c50550ded7e01e467aec7174e46f0eb511706ec5bbc1dda545b963ae2be154d4867522a3ff6c4bde7ef707051ab4777b96d2e3796cc529c578f57007023492e112defce4105e17836e5dd9faf2392c9e152b364d24f29196bb3eadb04e26c8b120eb458e02d5570e745c8989a860cf2bae2d7c527fcd78fffa2a0ae4a0cbcc3e5dd689b4b15e4395bc90a990fe72c06c49d8d57a74a838c16f50a422c023201350c8e2eac4c65ee0b6ea579a611859be6da66b3a8ebe99366857c2cac83d6303b83a1beb989b578e79c505a0896315688a114a406c372fd4a36d40ad32c24d29f665b90db211a8b7311484049be543d6035c27f10754b1bfe2ef3dcc57923dab2819ce3c2de0002208ccce3a89c81f3ee8d88645bc2c3bfaefddb1c286269d6aaba2966de2f305a97b90c64bb67ac0e0933cc4cdd861d27f8c54df2e4d775d8d5ab7e48b825ac5aed491819c0a74168230109b7220a1ce231a59dc2c77149997806544932a2f59d4eeaeb15bbb1d4f064949f863e4ee640babec0d969ed3c9f51a1721015759d104e9b6f83bdd3f2f4eb557ccb8e9ed8afa8e211a48defde0c42e55bc44ad102611e5104370eca4a2b28bd1216debc0a0bb659fe6a8d4b40f2813c9aea27cf7c006386f049395bd50c77a0a83fcafbfc79600953a9626dd3ddf0e30ae36e7827ce8aa93aec9a146e04ce289177cb2acd8d38aa80d2e7da2f6e6bc688c46ff53b5494260bc9ac10ae3b9ad4bbdc0c2ee156e737dbead3861741e58f12b03402c1bda38ac4b9ae58b312b468cf7e03e7a3d0769724fd7a24c2b6c37026158e80ff0fcb65a31cb1031f471dce4f2719bd4cb56773b013326b18cc40e1b982a374ecf29673c75364da7445a747e6e2bdf2428c8e7137bc5ee923c60526eaac1d7d89e77c983a9469a43299272fa4662f8786957720f988e1e49071002f79c4536547ab25f50cd969ff643cb502b64b0a7e70668926d1b86d0193416498134febbeb97d571d89e79fcaf044908a129f82e9b0e8911ec39bb08061abe4e224f8487ff0394b22f2f46de9619bcb45042bdcd3396b0be0d337dfd1433444775e6c17be52892cd9d65428db381c5879bbcd9699cc0f16190c1ecdf217d3761136ca413878bb2a874f2c5a5ede8c4c1940aaa66946eac4789163c02dfa94e3245aad9b124897c8a9098e444e66e1aa93990f1437d62b2d95c62fbaca8615e7c0ee5ac76887f2eb9865b3dd9b837adf70c69bc516efa27db985e04bfc04944d5f1d81d6b270ad73cd80b2a72c4f96c243ad7c57014fced287e3055560118a88f8c4f07c266528686030f458f58dad06d2e6e5040b411213eaccd3a820bf7d82fb109b8af28b7b86361445f0488940b6f7ca48fb0fe93c716b6c3eb253888830e42681674514552f80242fb5d08dd0752374e9eae6ac68387d7296c62ad4340909c900f0c25bc84b6fa74884b502787256d75a6a74d780ea423fe456ae079f0da730ab43b0970d0e55514f9acadb6596ce46841effc8a0494421cd83d84da3d50952ddb4e9f3dd2cd4f7d13def6a6960b3948dfad36577137c589cf2240a95f60928676ac26b6f03c1a0771839728595c2bcd42e0c4b0d205f38d120e534a69e4dc4df04eab0a3160a6b81b598f620b8313bd4c660ca0480f4a5313b02c324a169b45a5bbf506fbd36b269bccfca182c9edd56139f1bb574c15569ffee7097b54de6cd526308db0392cdb9dcbe6586a013d4d54b0d800c1143cda812d4014206724322e23089c2cbb3dae362557b0d5fbe0db21a4b8cd069a6bb435313c514b6cf1bc984758b9054a5b741be6a67da1e3f13d3b2657633b216297ee20db866a7ca34ad6c9a011286b180d5e89d450854d21e6e26f06008ed6b486bd635e50f6a71a04467871806996965195405b05586ca45337cc5245210597b0770d6c9fb087f0841c896650604d4a5be54db55b682d726e0045e34888c6cb3f44ed21b78e5b7583dfabcf1ebc46138f6b59a30f816ce2ec9288f7d129591f422e1264018696828b2e5498d2738403ed8f280b071c74dafeb0ab27a5bc22b70c1214301cd0abe059225c572581aebe581c7b71ecf2cc7481f0acbdcd3e8ca0f02c829f2a9312c5bbf4573783cdee6df8aa512db5e418c11b9840b975859a9ac3bb962c74f92abac648af97556b435159b6f83a4cd98651fcf45633b749aaefe26ad15ba565d3b922c927538524f2e0686e6be7e3879f6a88436a4b798bdebaf461c335563335d7ec4d4ef810b524d4c883cddd8c1656fe480be8f63bb03569ddddfaf0c2880dc1d17530d9a832a5bef9ff729aa5cbe59c75372846a79ba3102d47cba719215c25652ee3ac264fc1b496ae668d85cb7083d4ddd77c79280f4f5cbc19c5c1dd39b1f92d3acb8d83d865927e873aea288941e04f51946ccce313c62eee9cfc26130ee18bfcc5a02f4ea19eb3c0f2f4f740366abb1fc199d0a603c90fef13c3b3ede3d4422b714dbc150ca278f50a2f7132e0b6c3d68dabcb3af5e2bd93ddd8c013ccb24e3e36cb483ab1f5fad65ae8a24a135aeb4516cc29349de02b2334fc42e92ad9e106674ce3d4d0287d80293b2897382b954f8f040e67279e4c1f116b6b5b777b51e2a0bc80438dd2406c2da00cb04dbaa2c3e9ea9cb7315022fe5bb03025fc85e4b853e5fd49f7ae0f83f83addf427630dafd03c5ab391734f255510e166c3adec2325989a51810d715e031979de12e289ec574c04536aa56cac1a1bbab7de94b918738cf103b427c5751e95da33b2a760d41148c411674cf6a0542954a44307ebd4bdc65a653af4f80165e60df92850dec7a84588959b929f99fc9432ea48df5a593fce625d3e2ef433243ece4230cc3d015d578b41f183e8d07f1e999aa09e8beaf6827df7cd003e430b11a8a89b2097b84662b77b1a24840537f1906c183a34f7e997689597db11e924fbe94c22e443fafcdf2050040fad4d9d8ff043643a136fadb908f8edede1bd265b7cfbd621eb9676d934917726cbad248a53a33caa5bcbede8cc2901136bcd575940e08b076b0ce328b516f9d496a21ec416c93249275e8db92d267eddf462c26b36caff620ec9ca86b48fdb624a6fa65c2b86b59345be98ef171ddc0927b6c6e009c42d0a32b06721b798e82b8ae916435f93fd95bcee28a8b64cbf8602d11f48bb5bf78f10e331701629ec2117bf278912667d8c628fbde47f0ded3068d6a5c4c575e7b581e920ab8341bd7c0d6c424059a490321185c70336757bdfe1df9c4ff85f4e2d9cc6dcd82c54375667da9f20bac3464386d65da88e5a709231f7115be4eec5059ae911adb158b5084448aa5fe3426e4a2e492af50ab050a146e7b89ded71c1d5938fe3fef170b84c08ddf2648905bd214df6dd94398980d2c6ef7ca0e8f35a3b4d2c78dd6b95fa31a7d9fabb35f215dfadcdef395905b1389aa782d163e49626fcbb8b8187ad981b3800d331f2a384198b98d4c6e095db5e76c31e0949667014c98efdc882d184551cc1af8f608d2c18aab8063491f0aa27206ddc796aa0891218c583e67588164e6ab23f3c18160c143ff67638d85bdfca873ea2b7a95262648cdb8a3b2cf64c0db22fb8c848d455c6f575d2e9af63795b44b10eb773e293757b14c4ed552517f9baacfe780932df19c18251201ef67d5a56666e4fe7e06c3d622fcf2c6331fbbe5790353ad622191c3145436e43e8b33b8c45c09db93f80b77b23ec31f25f6148c466f41e4d270b04d4c0beba7f6a7788f79c9e7f1b4ebcb06634dbdb20ca283ca8a8596b28053e56bdd4723943562e542c2c88d73689e50b096d78c74f04390e3eb10f54097a91ec29928a9207902b73b037408594195ae0b9658f3cac217002e5ab71e5b448a32175e4cd164e355f11ccd26441964dbc3db7eccfb27bbb65679789a10ec109edbc60a2e8793c842d7b0c0a2219ec550c8d9d04381a431a41e1d4e5c7f9f9f192be4b2f188a223bf0f9f109396eaf57f0c55a5e063b34114cc9df22d8ae2ddb49e0c360079a10bcbe59624c96bb6202a50bf6428c4363d516ca8b33e750614ca58e1bb72717060220fa2d2cf3a2d4e72de9173a9eab61f8f464a1e56edc1e1d0280a2f7594edd1abe3c6ee2c8ed41938d4feb4d50574d49b1168e2bff7d74d76e790016faba103c30900278d58d5ec2327bd980dca03c7211585f13d78f06df3a7f54beac95a8fed34d49db5a080ef5d4242236ef2208d0fa9e731b29c10e500972d776aa751db54ed50a79e232b253bc4652656eb74386e08c5931e016adbb3d46d735d1172eeec6ce23b15a4de1c318861f5621cefd514f798118b52766f7245f93bf269f7a6eb501837f38134c470a4fc591c54d0fcaad7984f767351bafcf222cc1916d1dec2bfa0f188b2e62586309ee71268d6e4a2c8307f44485419bd5e3e36a4737425d4ffa2078ec0366bc60bfa4e0d5179b05de949651af3ff17f593a07442d2f407353f0dea9154f620deb7927b5854cacb40566bf67c592d5896012f2b8395ee4cee2f65a723ed0c776a90c2c24ca457be678511ec67cd7a19410112c472848eac9d020e5bf6c0d88b2606806722dafcb323325703efb9b44ce6045132c3a5cf93b355d1d4762dd8cfb590df85e192b8d675d8e54b99446e915ab480321634036277003cbe49fe2606f5366955787a23ab085a59306b1f3d3aff61b5ea80a2095ba25936e8b6822253b0155ae797262ebbbf49253568035cf625925c07087d8494088f983d8a3b5ce31749d8e42ea0a01b4892c48a6224a17904ca21798c4ccb9f12b3cc39b9d8f756701c0d66aba6c60043a012b81f6bfe850ed993211a5df900b6a8f473d762b778680b7a049ff91ab97d520cf4924d5b01d6bb42c4466d4d2c63725f83ade648f3bea9aab74f96b58812ba31876f8d054f32d07ceb63c4127955087ed1b53b5f6b4d753d274d1790d3795b994a860002f97f369ce6042a8c434111caf0ec191b8b171e505abe32883419ca136ed99d77e3429cab897d5b2a46f77dddcf15e81e4c292fb8130e094a1b13158b4d315fac1cb56784f8bfa8cc4bac21793adfeaf43e3b72f3b458d8d81066fd9d636d490f4b733ef160cadc8d890592727e5fa6a393977b0c57696511bcb776ebd7f976f322db442186ce6fdded6097697a686535d09cd98c0f6a95f6885d761fc7f1f105d377f85584261f750591f1f94249139367836d1387dd25ec235c1d700ecfe4fadf44e911faf1df6bc5e12eae2961050a609f99fa4c3f66239bb980a2a492b5a64eae164324b5d7761b49aea69c8adb37d832e7032d0a041d402201e718e29fbfe3eb633185995e1e848da250b7686500c132733b40c41605077dece7c5fcebf10c1b83976f9dc912cba624a08b64cecf20df211a3490bed2f093edde5fc6d7e4957d9c74f09dc88626a6136bc3cc55f607909a1e9c5af38d4d9ef16ed6d92e2600ee55d130872db418fcbbe23a8d37bd4bac0e688f2d669858b4f5c258a5af97b26e5440b13f7ce517a9eedb4d6ad897403979644596a1d306f02a277e1c9c76d89e9353c1d7045ab90ab6197a0647a433a6c21218795b57bd0d2427962d057c1b013c2b03772ed100242fe40818444c4398293ffd87422fa2c9f83548d9169e9e7b578569a05c89b245e22f59f4c55758e942fee57c42db33d874993d496263fdd146162674343e5852e65136b23b98a46ab0c7a907e3c8d175e76fadf384bc60993bdd784fe919f585f6d105c39b299b80f39cf4a4b44decc421371c1db23d7cc5ba70043f73b4501a47e6e43e9cdd6853c182e69d57b657a370a80ce30ec4d81dba877bf005bade09d84f4e4ce24c3d47d180b4a1551b4eadab3ca8c0bcd111501c5ef9ffceb915adf368f364c0f4abc48e0e32325eda3e139b904a2b01293a88c8bf8da8b50b2bfe4da64433c66b50a7ba47d56882b81ebb733b8a4aa5d16ae8de623485ada78d5c87b2e001c783b8b84cb4b0c2b8964e985f04e0218c4e8da8fd5576ad25735fe70371e065101982f061e9197b75a5fb87bcf7871479166c1eb420f96985aec42e9f56c01acb10d7c091a188caaa7758449fef068c420ab9fa7299618899fc4d3a0dcd00b8d7537f400839e8e28408d55f9a99fe6d0050a5de6dcba337544c108e2ae71e883e3aee780b812f88edddf472073c377c5452969952a537e5de6372da1c2689c18fe038a472ab5e742ad61c7d4606b30d98d7ed0843d7ad4690f489d901fc333a893a3e8fa676dfb51494a94665d6e2f3f213b8c04f516704a216dd47c6cf2162085dacf9c06376fe60f9e688d35e4c97c8d20386185cc48f556f1cd395c7f3a492cc2e136959a4f05cb0be84708ac56e1aba75129f1456d874b74f1788db8c8d1da72183a5b24519d0264eb7a8ea863d96b2603c76e592c25a71cc0d22b7d0c142d047a867e43d4fe21b39216dd2a2d4ca4c8cba9f5a6bffb1799bcfbe3e6e5e41dab005d285e6a744d39e0425250b4528244b80eda2ddc4d2686dcdf4120d9027b14bb70ea2e03c32a68d71cb586feb421fb806ff089b90a9eb5a809b96ad9aa8ece3166c7f415c2dad714fa14c6f344155b567ebfeba835b82bbb06578a17959ec13189e44807354fd6eac811dc674ac748c28b3b6b20eec3a97d42d948f8456814512f440eb16344d79c5dacc02f9e5859afe90e051e8cfaca03218c6695845968181991f5869fd9d57fe7ecd53f5cb2c7141dd5ce9d9646954306d07efea29fce502535de8953d5ed681b1872241eb7589ca5c79f0d42ceb08794c56b7ddb869f75fcc8a28245ce3198d4875b4739b02ad28384c65b8c2740e43914d591d6eb064ce622f95feb007592082472c02a5b44ac2da580f5a4ca73485803f4e814ce40b65b71586231de1829694535980c02ba85566b9e54bfc34a81277f0bbef96e4df60546382f8a1dad379549136133da853646684e36bd9993cb6692668313700ebdedd683a65babf356e9436bcc67e513221dcafe34a092d113bb55223644eca266129da8990c3140bae2af9f6a452e28b04298d988139225646683112c64be939c2f7e5335eb328e265744103723aa3f30c0c4e1e1c929a6c96290177cd4e06d2b44a671c0d8b45357cd1134d57d81acd39bd0ebf62f3765038bf621448d10b0bdbd858b1073cab64a6c1bf7c39ed10ec1ad36c946bb09c71a66fae7a9fb5a013aa2f45e4c18d3a3e76d9ca9c381eac3325ec67f2eaadff6c01ce2fc0ee08744a34d447fd0a52467a8382d2ec745f8bc75cc57ccf53196000aa4f6da565cd8faa726d0f6dee6264bbe46400bdfd2f6ec23f1972ecdafe860d76f397bd135d3a74349767d5d7155dba54c409c15e6e3c71212225a1b0f2039acbaf8883a7d6bc0e09903a2b2bf518ea391e897e4426c454dc570946fa7440bb3ccb62e9225eed0ba25b35508f137627d46114fe3afb1091d85f7e5c1a2d50ea3d4993aeaeee101d7289e74b8b40716756b27ae4c9aa5e3519d059e303878a9cf7054222af30afdbe743ad99f53058152c24586b06133f836c6e16c0cadcb9ef41fe50ad84f7829f216e08a0aa25a3828c992d92da3c72a1eac348f9a136935711fc83913dbef91e4121f4037749868819045d4c97e9c8df1b348e8ad724dc0f160f7e9ac50b7546b45fe6d72cd91b88b1180b80e7e87b261c17cb4c3fd172dbf078f71cf1cd60114342dae02810f2c390f9cb920451438b811c93b68fb0d8dc2878833930d27172ab53ed1f6176e1bb20513727b5ae557f3adc57edf1b7800238aa988699de524570809d18397ba7ce4b30944a91490846d550d0ae8a762cdce2df65f7915191bd2f275245026867150a18a51223585513afafcf13481921fb3f61dddc35b4e5454d6d11762f2306e4c7e0838f05542207a1254bba376137115c24bb7a4399ee50e2f7dcc668fb888ae82f37e2d594ed567387abdaf284d7ae0e4e1c56fd75d8f5b24eadd3a94f107b02f02d37c226f679348cf3d3c0d7a42491d21d3f98171e75e0c208a47ab4b4a32050c3317757a4cca29fd0d8c7c672e94eb02c85696f8d985d79c8b21f624c31febab3153aabe275b1729248435e6438b9f7a8e2e424d5754d69b44fc73afa6b1d23a7f19617c1b5a7130a902a267740a5f7e5da973969ca48afa4516e34ce2e1ad9b4f52a6ef197030bf27663a34d673fa6ced37fc82a044af5f214149c2dcf99b10834866512b16db42ea2f93ed44376f88b0844583791ca04057545e66cbad13d2384b5ee738c47546b64c9c765ac6cbb626e52401da95f315a172a284a8e4eca5aaab700d9fbe90101144068ca09940837a1a5ab9f3f494801776bf5a9750230c98be899d98b6d8587f7e5e2915a161287170c9689b7da15d99a01a9a3eee6422deaefdc265cd65504531114fd30d800225efd3311195d0975b448cbf4928931ff70f1fc1fbfffb33f8937e2c85c0a241868329d434182959e123b420dc5004580d120fcd3c8eda29a1e98ce03e780881d06124c559fe0ba64058d82ebb831732dca707e54ee41bedf0d2f02823877d015d998818a3a63cc845f6afc6c82023ed95d8b90925c6f7a18edd906da0a5cab62c85ae9c04c209225948b3c4bcc5c53ebbd872fb3bf281e5b00721f9dd2344f0c0b5a344f0916d02e4ed0ce40a38e6af79f156083b35e1116223d5eadc98ccc9b3816b88257e6187a0372709f26f9f2256fe744b7a49885e41b434e0264b441aa1a98f0e4d915d89856d1af9179298a7654851da3b642d9cc4131c9a52efba9e1683e772a793e4d2edea95b20eeb2883d212fa42b4795be85e1580f71ee2eff8458e3ff2a1b49fcc1a0b4da76e04ea6ca9b90507104d5bcbcf62318b2877c62c24cc11eda5c1dc9c1179412a4054b4f2028193a67096d2095256e9589791fbe1edd128ecb805beb82a37639c6b0c243b351f93b0fb0a892061eeaace2fc0256c84ef0032e3a8951d27d1550897b07a21f5e2ed31e671fa198c76d5bf12ce1ff6f109c79cc48f8015af107f77f2b862a9a3a12e48e4a6b4bcb74968be534688958bf6cbf2f694aba2c0e50068d2dbc9978acaf21e33c2534a48ed044735b1a57fb75a2abc68a317f0beb8e799e3aecccc5198f7b39cfc1f330c66583ffa7c3f66122d40c11cdbe53818a97856daa051e6e2b03d221f9fec210822bc8705fe26e20c0433a41ea1082d8dcd79e0adb1b623c78257aeeb6c79abdfa428b436ed940c64f0e40ce61e20dcf14895f1c0cdd80a7ee22a09d3fa91316219b1ac3725c7559b18873be6c730d65e7e2ac1c2ea6c228920531adcd85e66daa411384fc76f780ed06837b331607c8d0d1e18d6363e6e5075eeba2212e985b6af168ecd439794c183d38cfa6b4142e04a877886f80a578642a1a55ee1573d5ffd61d682ab2cc57dbfeb234915dcd13bd5ed6a2d50a8022a2eabfb118135086a627c27016b1ca17475ec31fab5c73188e67bd9c1b540984bd4a70e46a62917a4500f0b5d058c995c74d5ff2676f897d837e0cfc11fa8a408061feac5a8724dce74cc41c91eab62a48e2be25195d66a7f841ab135a56a5bc9e64ce161c3c156cd9bf707055bde6cb889c28afab349a93832303c08b6f82b7a7572d80a0c0efb6edf9123a3c5a9513b53b13eebc8bfa5da07b2a5a72f7391849e472820bae446d0808d5bbe4613d4d8b1c98adc4e6206d90840ee460170a9cd28ebf41cc126b54227adda5dd26adba8fd874dde316852a6f27d7441a2a3221fe46ebd5f780f21d77abe0916a086e4968c964efa937f2f2486e014c3094dfaf186054babc7864af938623ed3c0be3602dd20e37b11bf5ec588c1e1e82707d9bdc922baab90d8ba78c96e0e45556a40425d639e3e4550ef3c0698e541046c47b85917b521ebc18c8398516c0da2ad35f0ffda6fb9abaf0b8b6f5ff2636e5b5086537a6f512a2137c5b209bb94f7b092102067e52426d02d87622c3a86d81fdcaa9b571fae25c5f50aa7f2111859b9bb6b200f838fe36486b935eaf20611f27381665a97afa43920ce3013b62f0160dbb52e64dc9bd31675567b4c8b97419fbcc1bbd80e342847de462911897583dc57898ddf4fe3f9d2fe6fb75187b1021ac35c2c10e4dcb3a5cc2b15911e1812b921d79b3e9d1f39d5833c9de929d0d06ac6c9b71bb0caea6ba90cec9f5b3977d83fcd0412289c800703161cf40b7c89740f7fe1f03e226d97a30474f851d402a88cbad3e16de4079f22e0baef28003cd11a8a82b8f489fd9df0c9c713fbc348202e48f3e24bfd4e8094701159a97f35d378c4f926f535a11c251c7372b0bc6b527fded5c4f3de11d6a120bc44f0525858d99ee036aa3062ef140ab6355000098c37c14e167713f485c28ed1be2e35df98fccc84805f15b74c262beb35c9c148656086a540e2b027f491b6e08ba409b070ca0386940c1cace44f2b4e4a81e4d38b404506adc094402b6beb329c24e578844e0b0913398bfa3741f64e371abf3bc30b737e6cdf1da88eae298f859b5d5937d55cd680bda55811f9f24731b4997168de5e896c1dc83032712044ce90294a54a5d703d40ba73523021214e29292fe4217c53ef313c286c0861ad0b424b76606c183cbfb0af1cfe945b0d117e58ad30bde837887171dd11801ec082574f8331e29ca3bd752582cf4e6559644af3808580304f0c2fd5343b69c9807705fc98db600d52c9cee43af5de176495ac7ee9abb7886f31a1ec5ee60321c4096bd7fa513d82d4de573ad6d91fb1dc8a57ca4d24509d20997618879a585f0a06ee639ca66ffa8a78e5fabed09cfc2042f5ab71e2960df79936d1346a4a1631ce44993bcc6fb53bd2efd5770853b081dfa6944879d822abc8984a6bf1d8b4b190663a001dfbb4dbd2379ec5c21a0b1309713c0a63ce8e0db91079c46525d0f27434e3be2dd52b9dbac8d9dbc5a0dd613d5a3fbf8bd279679fb019ba519fc7704766bc44d997eb98efce58bf02a2481eb83ddd23259b14a9a92bcfa0af23e4739264df77238e2c25a0a26c3fb564b2a6aafdbb514917559c7376c07c62c5aa3da013b9a001b7f19b0db3e6d8fc0727cfcd26be6929f721f204e40dbddfa2ffa45797b08f870f6a3f0ca2738507d98be8a80c2ffe0b8a07cfd80dabbb48b9b9ff9b7eb8ef113973307e8e2c5a50f261097d54c0e435f6418148c733281cc31bfc9ea53484ead400d4462927cc812c5bf2aa664bee315ee6b409900cc7e71baa640079768b7aeed6f41f22efed0792493dba054f7e43088ee9332c1887af7d4e2f5edb9511294f70c4a1f4ff82e3b5fdd0bb3666d92fb93b65590c7b068cfa378dfe5e653f8be329e0f86d60775bbe4fb665c6a20be4d0c995f0e1fa060d1328802fb0846baaaa88fc736bc6d73986c94f9234ec521ddf2fd9fc32aa851b38adcb575cca8b61bede1e81bc531ef2f194972bd712e8a3b807bda5175799b2f9693261316525a824f99b7b0bb95483213ad8c153ed0da04c353399c26f23cf90aaa2c0da4db07c1272c4db047acb8e39636a9dc0d61e5d2bb3b4ce8321146e823a6692c3609fc0b23ad060024c7c8301d99a05f7c418fd2202aeb1724ad969fa5ac66761b1a4e0dc0bdca8d5133f2a5275c4f6fa6362f428a18149a8e551fd49a90263665d82c88363c201e451f79906f5047ecdd1409f959c288f19a5170cd15a8265ae0e43a8c27f52811da31f5a4292ae01acb9e990e333976597e3324061ae3c258069a8bc1696b102014b27dc607222621c7c2c37d3a0185c555903dfcd69c12cf7768ca6643a59887417ff0cd9bf90fe8d3cdf14ae074e56cec4eabc51e723c51a35008124d0a4234a4240003701b3ea2d9639847f2b468a3324650f5a537071f70f05c3ade0bb071beb12c6cb7ca73b24bf4d5e6f4910a8225e62eafe155730cede643cd362247d42b5a200e23f7b6fe46492f911b01f910c05456c0b210c0e697e4a4ad432094599e1268a7088cc5ba9a6ef48b039e418ae512d86e96e86e7e50608d3bda774984e2faa22e2703fb2d1991c2a48c4da7b80dc28d722d83c3369a57ebd8a96c5d88deb65e40812872d43cdbc2707d0f70c67063f06c878010e11b7bb58f863cf545b81818dcb14d6171edb7e4a2f48ebc81d9d1113d95ac78be7aa964b53b0630e680307c42bc2c1fefe16b8e0e748685d9719368dd1b7ae53774e14bc11cb22e6152ceff88ec343e56462344d79785e60bad0a081ff058caac14814e8596b977c7f924260b502170e58d0a3c387b50861e4c615735f69da69301e48159cc60fb238ee2b378e51c2537781bc32d492a0f57aed8e12f296e20da5a001e9094b0e312dec8d0bcba0a60a0efe6044ad3a94553266559d86c1a3a8e4b451fc065aa24995ae40c5ee381ed82a580d983d865d83b9997bdd02c570f0a9879d1158ec059c0010569831e78eb3c482a77f2c1c8fcf227f0d7128a0ac460fa1c634e4e1d84cea337ae915392ca23d252d320cb611511d0c9febd06795c54e744f2c00227959a67029f340f8e8bc51837478f2c4aa5503a03774e03926419530cc3c372fb8249a7a2e5370b22cef2110712c2ba151dd6279a91a8386cbb435bd9392f22c508304b79fc0ee10b5497b4a28594bb697f01c4caedb62fc3f3be271b1c092dab0a4b8a3d9ef890bae7284a0a562b115c5ae8d86bb508878059ab170ac245ac291c1406a79c1edc7d233d20298fba9837868a42848540f6ebd8a06b04a1e91b07f4900a619d8012c4d1145edad88f1c07d49214dc5b75b516aff81fb237d2849890b5b3a711bc7c661e9635d44cb550ea8b883289ae737bcdc871e76dce438d2c415080d4eb8841c9ea3d72ba0d5a493f81574e256bffa7ae6848ac0e75aa0e0248ff4a28af4b49396290600a29a8dc88f4f47ebe2cfe06a2e2049a02f4e45dc4bb1801dc005347cb83ee469e32bd301f2144978a5a9018a0b6494e7cb6bc4918437cc82fdbde1139989cf7856ffa11e400901b009f45d150d020f5e74c15a2fd1a4f830e650225c31604b74afe17d662a568173590ba710edf45edd4769a5f42a709901a0d9132789c77a19069ae3f5e19aa37ab47cb5ee3a3047a6c36059a88e5012621737dd004639218ebb134c45bcf09da211e6dd592972bf3f44894be9ce4607014e4536dfb343898762436cc712947905cd7e8e725151bad273cc7e134bb53544855bd903e19b4e200cfbe4527594d5733937b02e171e311b2e2cdb5bc0989af370095f9cd03221dd0bbbc9933ac862f377dda023ebbe4d1e940875814da93074d84437018cad2abc1a484a26a187d5c126c2fdfe460b46c89976b02857e086aea2e61fba5eed0e4c4c0f1e843524aad3723f5d55180701bb94b87229e9753fee6d56b03e8966697e69b605b941574ecf0a733941d86861c16cf935a2a514f88ef40a870088789e811a43462a81e5c85208021086f6ca5fda9837051ecf0a337cb68ea3c136762b654279c8c54e590041f968e371b802038e36d8c5df3570141671b5cfeadbaffd7fb269b428db56d31174064b119508b971e7917ce6fb5497195a05ce5ac8975bdc266732b280638fe83968a65c7e88e62e97e0a9a17fa728824ca35084c55d88da80d7872caa71d091774707b2a8d3e93a2292db0151f5734402f0008db4a08b7c2d8be43859c006a62f0e7fb6537f6ac8fc43d09b88f122d7c5432d06460b30c2001e340fbb4a0f323c02b8b412a447181a116844e1e24e11887971cfba6638ed6d1bb13062e85f8623e6f6936415eea6fd264d9a8e3551712ef325d48560567a96c6ffe962d00f5a059d7bf33da7fe2279eeebde8433b00ad5f810d7fb625e6b8ff205ecc732434406f51a5b86a62a33b3c35a974df0d0e128f47e5d49c0d2b856718bc9a30451d148c5f51e77425fd6a1c37b0d797cd6cc029a8ddedc020518f7c2c102a34a8b416dd85aa7bfeeea400ec3eb47405c0d611547925780f6a67cf6ec26464b70d8927a20d1078cdc01909d1be1d52a77707355b61ade20b1177861d8ccd01c435d3910b85906c78e6879c02288d5921ee560d7906f18223ac797160110207082dbf5ba5faa0203332d3eac7534ef46162a25742a440b13a882838b091e2ef1eaa0ec1c6286eb15f18c57b346dcb058d93a085ca04d7b24460d148395efeb08993bb5a67c800752d4f2067a00798f67453a921e5672101ac6f0b3dcc15e6cbd97f3dbfefbe76e5e2768d2272ce7782ed67188bb5158f769771b323e4fe838e566a508e3f8bdc84d1475b449da6266a665c098e195ed38c7d54408c954e59b78ae1c0f2528d779380f6a502cac503f89c02f5d1f46a8c56867e5526b0e604dbc1a2195c980d61831ff9831b8647e20130c5f3c900259e15d1da5695fc89aa2a0b1f3302c3adb87f068f868465af72d2c264a28dea23538cbfdacae66038d525c9e6f5b4b1dcae87ae93635308431adb0f887b8494c650b89f5b33d25ce5f5604dcd87cfc4448ec077ce6a152d0c954489c8be4909575b0052fd516c5305cdd9e01d9294878a4e01ed1cc185dcd43f318c79cbce8d09f47e57e1da7fb1741035de0dd0e2f4fe8dcc184da5b4977dcb84f94bf6e3c5c1ad2850e413d075c1f59b376cb0bf9c3ba1aad0fb4870945c70a4f376f67e24b04a640d0c4504fe5f13a292a32e54df1e396046768c641766cd5bff9ba508fec52e54ae1b05a46090004a69995b01d1a754d3c2e4a5b686f413b5a92e2b62130f37f1dff12d29e9861366a1d379a08bcafe3b7665457c56508e642bce2c202256dcf200cd7488f3348fddfd88529b0ab754fa9dc12a5b545355fbfbc65dd206635f5f44009a1a6a4bfb99b0d5990f33dd67207c2f14b220012cebec72616ae02f60420ca6a6b861e9613d4413977f618590e4f5849a82a09657ecb51477076b1826a6861c21755b9e25111417c13aff1d109f3611102f514487b92700d19115c9d0a24593dea658c53a061cdebcc2f93445565d83a1eb2d8fb8e5a300a98af15b30618237416ea8f41902f6f9b496d31e8d6c24cd8f5e9e305e0141dba80c835802e8fd4a86ce0517b2b90ead1103ddf455a75e664aa3c13046eb53eae20d1f0ec2171e531245e571c25da123dc77e1b06063bc2d890c870fb227839556505a418331498820de7f84d0a7eadfc689f63095fcd0a9b96d80e20a96e04d79f109f59daa0dc831b4f31667a84feca68d2c9bcd0ec2df902f0232e05dfd8f80668f0d8863105171586d83e878d3ae9e87562e2e0be0df4a7540bb1f62d22b28975dcb143384c9ffb7051a7377fa848816a1a6165216f299ac9f0eb3c13379a9a65a37c29a08f7b5ca60e703388040ab2e0f9f9edecac72f4450fa95024c13532a6b7679e1444e62fff8a9f4382d198aa1c0e4e7d0913fd01dcaaeacec8411fe98e9772d2690ea211e8733b68e596295fe0bd30851e067f3d5df139d10e04f95182923dbc7a04d6c17de6a5f6c18103cb4ba19d0a5262f85faba3e9aca6cb5fd9b4d3ffb29ab2b45afad0a6d0124b57629d0c0b1e2b71e859fe745e9254dc898a282beed28c409adc68705812561e10ff93b62177f3a71620b2aebec38c32ef21cc0a76591b5d1a22674d002a87203ea46e237119404ed6ce8ae1b39b961bb33f6eea42d498ace063f1a1f2bc32a123d865c1ba36d0234e472b9a5f7bc7d37d40cd10b08742de7d0923ddad89ea3a503b838009acc9561881044293aae5cbbd4a19190f23c064e97eed720669da61f7c0752ac2a2cc7886e6ab17453299409a888059330ee742b6322734b89791f550bb40bb6cd9998ad51508bf2f47e660dc2b3f922d45e58b98c0528ede4515d416b711c7f4a12d30532aab07aed285075441aca0b029060f93b363fdd34e704ff392c5d88ebbdd9c93f34a06842252d65b63b1ec5ae10c3b6d76c35bd25596fe83515d2423c979cb01c199d2c657ffa60acd52b6dbbb5b5cd6a560155c79efee534d6b158c0a41811e292878cb2f01e257b200730a05c943054e2ae1726ab063ea6780f3838782f41d8cae302566cfe669a47ee88af71320e551e06332a6bd73038e08f24f55b0a01425b1a2ef917f3217846ef50b2dfc6ebfbd9f105c66568c5e5c7551da47065e5d659cd92415b739257a668c958701491ec3bb4266a9061dfbe8be448852f25d525debec0316cba2cb325d1fe09dadcbed33122953fed9b0fed4bf6319f810f45f6b1f58a5ab2dee38b6e777161af5b8ea60008d75154d7d7354ecbbb6b7d4de39877797def1affd8b7ed4d9f0fbf43e43c7bfb116b76ac33932fc27d4107fbc987e42fb8597d8c4bf8cb26af6418f2102cb245a9792760bbdecab359e43905034e646a4affada4f844e4536d3c411e9623ab415aee61f215922b004d98c18e71b74ec2cd92d58433287efbb9700ad523cf24aece6b30d8965c4aef6cc41972859d0be6473f5ed05f476295ee252b5ce5fceb6c932d3e2bf695ba16716b8507a87db37545359747b77426a969d7f10f2ca4d06fbc73bd752949be25b6a56f1829087827b2d715ae272b90d5bc20adf6475fe1aaec9cd74790df01e540f8fd3e93ed9441a09eaeb52c73dff7b71b3849b84ff893a0391c9f0085702b11f16aaba8ff0e3604fb50835b140af2a742b352179c89bfc0d4e013a98bc0d816f7d9c93f37b40b68d3b12835b3d1a759f50c8fece6258b5b11e2bf0bd0f9cf160c5d162b51755ee79d26218120c03909aee1633c3757a3eb91cb3a401a387ff8b24c18f6d31f6c92abff1b7969bbc4ad2d5b7d45c2cea1cfa1e622935c00539f6475204b12173b017887401e4be0ae67061c874d90b05495e68ea178adfcd1328ff7c08f01d61e4c389f1efbb1b462ff8e205bfe74f3c75362fdb5626e9f389455ae4caf39b00088fed13b1c3d43644eeb04f070d57e6b620a6c7cfebb6016c0897f4bd7568dc50cf105fbc9311391ed6bc3f9a93467a151c353986e030228e5d69068b9111dec876a91f4afc5c1c13ad3b90537f38bd235e915eb0b85168199f58b4c6474504bc562771f0833b41a632658b5bee28f371ca848f1636b73fd8dbe0fa1bcd66814b7843ed042d6743ebdd707dcdcd39bcb330774a616614f698a01f2617d236e2c8891d4860373f1917d3d4de40dbaa2076d69394033d8dfdb6caf37d1f53a030955cec1be6fc131cbe153cae0df6cc8646e5cf289d96c4e7f07bf035d10702e39742cb724f55299ab3509009c42934188929030682a837e984135e84bb8b08a481a948e63016477d93a377adc2a804cb3278fac40387bd6677205f9b3221b2ae8cbbd43ea3965b58c78be4f09c71ebaffc76e524aff609021074ef9b09a274b7ba62d601d05a50c31869fb23315d6c5d195aeee1171e15d20454eb4da390970222290fe81b0c9c795ab15a48da09ec80edf7067dcd1e488677b3546db280fac43a7d1cf00af8e7ba02538e711329d1623d925bff89b63829990a1e878c38994d687d46700e2417f18d087ea7ea9698c432e3101b16a0a4377a356ff10838e9c358a38ffae21e36865b199a215b9b2332bd7c920603e27367724c6c2df1292a9b13160282cfdda2518838b1098ab036b4f3b0b1a2fedb939fb6f3e4e5f01317da779f0e780538334fca2ea1bac5821f8385ec1f2ec32b19140c1c6b02860fdde082e5e446c2c08060bda3223c2e8b8d5d2648744dc435d2287c97e31d4ecc088fc2ce16bf4c05a8acd1d59071494793b80ee41d44432de6b8480b664df153a8020e6c140d182561b8dff0edae34acd750c18336bf9edbabff981c2ab4ef9b67d6e16ec5975b93575221e1a8054c811da5dda424dcb9a37c873d6d48c7b3c885fb395892390fb84e3a38fa142fe24f0ac0d434ab14c25072d2de9cad4943718fe1b228592bdd69964c789abf7390c69c1842ae08ec325b6422017d5d23bc69484b70815219f3077b966253dcf87cb17250b4ef00c621b25e1f5cd123c30f04a99516a831e3ade53cb3f224cefcb900d5207e26797d4a44844451c0d9b067485f68ac6fcc03db623dc99bd6395cde143f0d2a3744eeb1597435ff1ad5bd2b50e8a5ed591b170b35415414ce271129aabfe8fa9558876ee508f92e94eabcd2d755986d5793dbe36ef83b1117a7264cc4d741aa6135894c5f5cf751d4965fd55e3b2e17060d33f45a91583d9f08278e4adb71e0e26e864e4e079a064d3583d96871cb60053f611c0284eea9bbaae7202df7cb3bb10e8565aa827c05b307209c890f73dcb4ea156dce923d578fa16fd5db779597454264772701fe9e9f858758750102f7da75791a8a1294e206e0658efd6c26e8965652d4892b7514c70ac83f0b998d7024ea34d515ab93f85c82ee17e43d01f5b7c48cb13b687d1ca54b1640141200800a2f56911a8b8aa82853069ddaf66012cc3f6c1f8a340bff67ecccf45bfd6a9f031f6f37f87d6d963e7f2e93c557fd3f7b3b28f4a672c4ea00829584abfacc4efc1c1ff78574e5b02a8bf9ff229bf1e50bcdde0603e3cafdddb374f19a5dbf4cdc7ec994745951d71ef4f39946dedf04b483c4e8d9e39da357bfef40ee0914dafe5369b2ebef0cf18d4535cf0ee296ee3bfc6bbe9040f71cc361bb368b68e1f3b38a5d0f35b357d1f3e7a54792d9b1af5a84f94d3616e5b9c577d42072adf36e96d8ce1f56151f7845991e93cd2a5f023c42e254cad1be990a36396d0200a21116a7afbf10c308dcb202fe115a264f12084b31b32e1988e892556aa14b68c83d657cb9fd74d80fac6266c19b17aeccd579c48551421065f59f90b0eed6e51a8d381e3ebd2a5fac1e93edcc52ba959fe687dd0bf15a62d271691236bfa5614a2d4bd80a91900b91bce59b227ef8b8fbc2bb3c221b1c9d4afe1926d7fcb90a5193671d2c0bc7f0f9fade4a8c742d8e9a8252056f937a771d53cc75ae83824c404fdefb0dd6dd4ba93acb1c6fcce3b7e0770705d6cbf30c9cdd9a02a6158afa4d92043ac6a2b05dae97856fd0008e417d6345036829d8cd37be6322a20bca1cff5ae6fd08c525c8fda31a151fb1661080cdea5858a99f3a5e25552b194d025048441370bfd12bd187aea4f08d8d340512e61a7823723b48770c7fda02cd45101b1c276799180bc375a6910383f93b60e62789c3407e587322ff0258d8c25b5ca3800a59d5d61655c919dc3ec1d2cc3de6da90a483b23c0587dd8060f586c72d02525b67f6d3857eae8e80ff881660026376734314003ea52e3a3654ea6b3c8b0877266d6239421926fbd2d53dd87ab40df0c19e466c623534acfcdd593eb5f72af43b400712ab7799aadb6f6da7e95c97310aa43d9451097f077dd6cb601b6a4724410864696511d90ae181b71b23af5bb7a2d0129da6d43ab643a93748c6768b5f5e91128106a363e5e020342a7661e8455dacf528d5b876b59a711597cd26110a0175b8daecfc4c646b395342debe32b4028e4567b4d7533b4a5bd22a7eb48187b1257b53e49356e85058dbe0f02a1d4fd1508319bf588ad3e4bc77a04565a3e27353784a3469f0752ed1bc76b49bae667e9d6edc955c53ddca6e9bae21e56dd0f4128f38ce76875ebe3238044eb5668d0e0f730a5b9e1bc2ea5d57e226dba3dbbaedd866cb4ae57aec32633d5fd150cb1ddb84aa5b77d4a686f0a071abf1ea6b5b74e579274ed0fcf408150d359f55be735298df6b3274069c3edd1752e7d732bad8f8f808110b3d94fe1aae62789d6edc95522bdede3293020746ab6eef674ad7219d6738b9e31a7cfe01564f314eda535a6bd02c2d161d4a8d18df93344becfbec845f89fd4a3acb91f32e5f7ebdc5e2709f29f0cadf0bc7dfc0759f5707a7bf03fd90a0cd1dfdef8db5bcfff255bd090fee84dd827c9da6ed737f704e010c4e42ec41f7b77e82f320b1ea2d33fc92a70487f7ba7fff4be1f8028cdbe30b103431fbaa0ab7fbd88c6004fb9fd4af5e0c1be917cc85d73fc4163f909542ba244883507167566c96021699e0754e8d9fc0f5edac2c2d4046b4a192a2681e1430b18d11d2454965df7dbebe52e9c90fa5e0de7efa61f5576fc316a36a17166d84f674080348781eae1d840772156751734d28d90cfd8bdc2cc791f874dfd7afcb572f7bfa39b94fefa9aacbeb2e6d8b11fed667f56b07329463e306c5efff263ac4fe08deb47c963970032bb1160c1d422119b9ad2497ed8a37e040fb983ff5af2b1fb7f80bfabed6dc251e13015dd984f99769bbaed1ec83c941d7e880834c29eee585be12f8bab6978e8f5ee0ea216f27e9daff5079901da399756d327c192f6a48365d869880322e18c572370fe3a0f724bf22fb7abc157fefada15473cf55fca1653a56a0464150784821d6fba4b0b0cad77b38815423bde72eaa24b5eb38a67eb1549061309d134405e7b9fd1d8a27af697beebc6dd580c624a21d7acc42a5624a22ce972f16a4a7c9c401b73501a611e07f285f42747c157ae801b8a2de289924794f86779ce4725a62d91aead5948dccbdd752219682119b45bba2d0c6854b04919639c40074ab1907f894662c258b4c6268f2ce49acc42c9cd23a5c674591dcb8bf0476481d205f8dcc8e8d0f3c89f0c2b5fd1619be27f8accff0745c3f9525a6865b10885b359a4d42f1ad2ad821a1a64818cd3109047398d50be4a96e37b6f5e9262853ee67371d1f8fbfa4e4d0ea4c31cfe6bc85dee47fc7439fa2b78ce2803172d9c5b5962665b162b28422cb943cf1b5b9e78b5baef3a517a9a7dfb372b6a7b8685c5ef8fbfbb21e8000d7be14c738ccb66a631ef7584aaaf4860d0740bfe0fc21dde115d4374e8d5da5d58e96d319179a91168d93c79f1d621bae6d1b792bd9f90812053ca4ed4a90502cc2536cb80008f171fddf736df8a7a62986899c8fd7cad17028b0ba10d08dab614602261ae2b98f31d3f4c47d68de2abc3eb6a4656437eca6b205bea11d53e8142ae95be95b3da8cd18ef76ddec7cb851b7622d506a87aee324bf452277d932e4b7f795e41d434ef7058459d3d0eb7ec64ee8d4954ea0c4ac23a448a20d5794dc2c380d9bb3cd28561104a0bffa92c15436b516a7d459e265472d9540c1c849657fea4b25db5f08d377f8b0a1dda891a8f582d5a585bac33aef7026b17b73e43d8e1f540d990ebb9fdaa4399a9aa65fd4b406bef88025aa7a191dd2554c88ab39a93b696307d3656fa01474cdaa63fa00e04ef19ca1d428e60e2daaafec19b7b505fef43dc6731a6c9e3b8bde6953f40b7678c6970b825e62efd77987ac940706b21f5bc8b4e69a19b717a4bfc659f88d2579cf97781dbb9134bb82dd5ff48752606c331234c1a71bf3adb953b59c563fc752e0fb1e3982f83d01845e654fca040e700e69b193ec82fe3e38eb87f456078e88d5390f0b20cdee94b3ec55c343ff114e57efe2fc35ef73a7d62848ff7dd405f6c6b769349df166b12aea1338054674c358903cf8d8b48bf737394b7f744544d2feac34c37e72c9f5a59bc2d557d23962623bc62fd3efb72b6140e3abf81fb865ca7368ed6d46a7a85f224ee5e9f6884aa0d9164302b3f80510360bf169168ec1a5d8b38bcf19e32d84fcea3be09d1bfc7d701b3fb9ed1d0fd27d9286928501bfca543ef61ed20a1d4bb7631c925b9ac320c5830d2cd81df5f8e8e0bedbcee622a6ffd305a70de82a53b876859b3abebc5c901de69fea666d7e609a0891b7ff40f467483b87bd967f0c6cdab30795e5d715d559c9fb8fd95b789651ab5318a1b92cdd0db88b63736c3f12e009631799c125d08ea60d12ea03e806a438a3557d2fecc8dd28524555f863638676a8df56ec46e602e7122d517ab4cc876155f1fd07a70f50f71cf09a230f5c8864f3f8cd2325a822734953e5bd85aa3ed48d4772516d543e691a7c759ffbbc39d0375e4972e4a8173d7edb990a66e54fc1212888ba79c1581d7bd47e255084917dbf56f8d26058f929534ffb48ac8c558bfa9a8b9db2722c52df3085e1ac2a87660af55c4d5685d8c849c8634ea0cffda4d91ee1674e2018cab89a554a6f8a7d974ecb45ac30513631651af6660f9f45c7b1919bdcbe29fe62980ea659c28e58ef0f6440d2a59b764c30901cbf4afa9d72b9ed69d275310f02fecc99548619ba50de5d1252ceae54035be7437774dbeb7799901171b54ad0d6540098b1667e450fff2ab2e125998ee843e44e9a6ccbaf69aa42a7108ca0ad3db02fd1ce0ee376ed3d0300b7fcb421f11af3330032b4d9a7f22415f3cfc5761776e880566d7ebb32956d0d9a33534d1c3e326b9faa744da12c1a491fcaa50fb5271250a65ea70eb7a5d67523797dc0ac05b7593602a46634c29781b6325e36949c508e8af3e76d84f534a897ab4ec3c124abaa0e367cdf1d06b97780efac5528e477e88ba1b87ff8cc8901babf44e0b19d6f2f0e551212d12a7c995638ed8f86457e555586b1d89ded3506731853815a27fe195ab9dd25788de93121aeed1eb4bf8172ee91990d8b7c9b3dcc48e6ae6456b68386e3c91761d6bd0d43371515b4134bd8a59886633ffac12576c5f3a09d7424de3ffbbbf4793efc90446aaf3684ce09a50e73430aab60a5e6b588c99da6fbe825c7c9ff45449df600b20bf77ee9d039497d61e2d8ab6ed6ba045828635d4907ec5c7f580701c3049b80284817bcca4506eb12cc7b54e97e483c21ebacd67e2ca1b565e119d49c6e84813ffe9da21faddc3c4099a151cbc04f76f22472703e6ec03f218c3e5722c66d3e0988b2f49108a13f32f5445de7e68b10373eba433ddda86816c6bf2acdd40bc07d9cc31f4fb93e4f2c4c0c41d57daaa55b40b8eda685bfa3be43ef755d875e4150870f48268eeffa84e9f725ac38360fe6c036d711cd0d67cbf85522a2c3788fa84d802e485ecf7b241858c48097f055432227d19ad9b1d8a0046ff16f668c50a9961e4d69fe24f2eb0f0d8a215c99e104c785c4f26970e14a788c8ae84f3b3cf111f016610b17805e6bfc7509dbe5111a3f69f7281235e1e91ec8acd47d273ceb0b05740cde61c2c8120f0f110808bd24ac59d11dc3821aca83849cbf868118bf0d7d3f98633f4a407cc4ed7a43d3ead57bdaff4eb2e5bfeb932fba6af2f7bdd0bd4c60deb887a738fcc0dca647b4959d1ec76ed97f1e0b72edd5a63924e0119a1e0bda7706e1122d0d72c3b7e0802876cfca8714a0dd5fde04bdc655a8b8a984e063bb256ea33c6caf8017a2d3b6193a868f6494b506c944c51740c6e1fbc69be9c859daf3f16d2584e374f35fe6b8521828f6023f939c68842198291ec47d82d13c9355a837cb49d75475a6af0c81b1bdeb81c42ddd4b186ccea18a89ee699bf8c0bf77c626501dadd9e59c4ed5aa4d9b03b8e89121bc67dd8dd84ba0f3c18014b75d6a7d5f0eb0981546063bceb4fce36e1d4a869a4d35e10d9cf3b03a7ffce7e8dd94c45f83688e1fe1a4f8f0b3712865e2ebf17064cdc3dec5709d3a1a1b2b2426f4846fe4c19e7791b9b3116a2cdc3cd84cafc528a3bdd167a63b372f9721a46aef15663c7e7615d6023ec6f3a368c1c24661a128d0fb5f5ef0a34fb7a68339d0ad5a78bd3a1fcf0561291ada4fd1269709e02076abe9eb848fffce711b73ddd557f01aab18e773dd45825174035165107bdaba788abae04bd3b2bd56fe1b4107deff3e2e1eea6bfbbfac25e1a277c02d4eaa3f9788a02750468e6b09a77083a6a047e7945c50f979a82f21c49b4c537359682549599251ce618b011554411cfb1b244d6980da2a69a45ef3d1c0a708003e96e4de7e4796bc52ab1ea4a2104a50955d5cb989469db0f2d83230a7024c2be5108e72a04fdeefeddf883ebd6abbd52f757d6d7f25e51b7bf181ed348a838d86c4a54d208a2944326018cbb01fa3b254459404c31930e92f708f42ca7254892a7b48a3d650cda9c609b016bd8103ecf350f35795247f6966aa65ff1929d68ec07e65df15ee9a609331e46676e78508e2bf29a6564abb910a462d88c01fb43d329d436d964efbda59432c9e20370049f04f603021aca40426d32bede257ec8f48ab60a81aa4157d2ccdd0d10cb4ceff566b5e6b9905716cad83dc0e0d21ad9839858a2bf436640a637cf6a0d93600bf996dbeb02ac4405742d234975e5402412c50464504480cc5a6b2d50ad592f7119888307d50e90fb8e186480ec430681206dc82fa0404005f88024401e12db5af08548ab2e0d13bae60908b556fd839229dfe8960a7c7001103ac0554bca21df9c3dafcec16bb797dd25d28a446b06b6edec1b570f6cb01561db85a264ebe11066b6b62052a135b97a259feab4d43c2500fec1ed5e85cc64ece02c6b00aaf7822ea2d60a921c3292946f690ae2fa976908fae1af59e2065204c1592e69e41bf8a0830efa0c04414b53d15e10ec85e400f2e74852d8735fb200638c31c618037170dbf75a7b814a24510014cc8081174a5a59df2f94958562c837bfcdca0ae2b6c13718d61e5435e811fdb7a1efa5ea1544b523093b8ec837f06df50ac2da0dd5a94e6bf7f42fcdda06e1738c3fecded841f7f97dd8bf8afdd6b63e0bafab9318804c6f05d885dc640d35087f4023e4a96e8bea3178aadb92915dc8f549be799341ac576f1200f9067ead20da0b05d666da4276a8d6acd76acd0a69c9e550adb5bbde0cb97a45412cd19f33fdc412fd3fd95620529f8c2485c9183e02a86660cba50c456c2e324f7568bc7ee8a90e0dd5d3f184f74563165169108d8038d3695175104559a64e3d3104aad72b8ba73d05b22003183ef5b61d54b7ee8d2558fcf55bab5bc763071b209ff644d7eb4ff5c1edd55adbc482f6f5c8ca245f5a85aaf20c8492db6fa05fcf17935c2298e905bfc51f13f4afd3a00dd7e96f28d17fc90bbe4978d0cafdb70bbec1bf8086d000742f19e47311155ffb915f4692aa640c1f416b0637f0bb56514abdec81d8b5efaed59a50967cabd5a8b5f65e8bb3d50bf16624297b9d00e2aca621f25497060a83270106d5da750e252e3b10870aa2faad59c1187b4c53d1df1ec473ab1848ba3456df34aae3f27a3a5692b41949aa5a37d1dfe51ef770a5b49a91a4aec7f01152d891a4c09a81e79e974b39c8a3217646926a135da29f66fa095d095d39096146bfa4f0252a84995013dc2b70184c82de0a21279e8cb434835e07fefab58d2ff83cd208ff256f69895c8b73a5dd80fedeea1863bf98620dbe1066d62a5fb71a84e2e4b6d6f6f5be77f6445f3fb982f06eb55ab93a7decb5d6ea4454441a7433dc5a9b0999c94f240a29c9ed336befe7e16bab9610bf21e857129156db7d868b16d2ebce84fd6df6f5ccd6d75ab5745a37bc445aa5f840d2e5caaa2b57c78b935c560a4ecef4c89a5241770296d41a158ebc11d3e58c1126a9ac5c162e86317cabdd2ed06d696eadc5350bb0a4990293142142c40319c4636a6510ac54e30585cb26c34c149d39c296d284174a2d28a6aeea6f6e53a273e46f6559eae810512acb058cd1a93107690e4ec69c11c2847861533170d812dc0d9382b3615c706594b892c2bc4018be5e589a7d61cd10bfccc890993ab2b9954b1232ee4165c303abb284bd345d1b52eeac031b9351c360e0c8b026187276883aaef7f585f565c65a7bf157d8979a6fe6799ef7816036c090c2300c65229810b01e198c0aac0a98979f9f9f9f9cc1bcc0d0808dc1d0a04183860a464adb86e9086b12755f3f92bea270412878aafb52c266cd1bdb13dc8604dc068f0fdc26076e03f5c2060449f925c3502e99626392e304cb41222748154521ded49299941997b032615c1efeb4441b95af1f5f510f546b12f8baf1851bfb1b18f65427078d9c3272ba7cfbac922410c3e8cc419a830b81ad6a8a4d08d5234629cf17374ecc4c098788e001cdd5e71b449f398b111b3e18dd8782e0504dd0af510f35eaa1c120d9c7212888839d7d08b3f73c030db924914762cba5ab0a98fc817cab0501790909faf1d5f38826623fbdb42bab6bd745064806441b946c6b926f05b8304010e95714ba2e23d6b69567344457ab37f26c9c2ffade888271f6edb55b7c5104dec8fb69916948445addaf83f5da10883626b97d66abb61f41a5add65a35bcf55aed07272c6a42d040c66b3d14eb060b55cee6d14d74535a67ae0d9493ba99727572907b01038f4a8e474e890c3b27d8f9e5ac9c2470b016c02a1255c74a8f330e948f25a7184e30e70372755e9c7abaebca19811c97ad3337766e39c99cb95c97b37339b293f8e4a29c5f7250289e3a6387c971a08d0b4d6294f92145eca90d1593932cd725b40e387dd4545184349533399aa74d2873684ecac6022f098a4e812125e5f4713a97b301b90cb9761246bfb8fa044aa795a8988db38d20c06a03a60db927670b66fa85b3079133b73586926918b4502f37627a085503e6dc71622585c8751634e700725761de50aede42d784625f504c60319090ece82221e754e46c3a676ec9d94072d4ca996b1c58ce3267063406addd99cb79d13bfd8467860a678e3e69ab14ec60da0585734c379d39386067aeb3c6ce386797e91044a95c600024272dcc7d9d09c869c9d19d9cce33078637540addd3317427fdd24b754c8f8c335752acb042a72e1b3bba8a9e51e3e53c72fe6893739c233820c849bb7214a4f172774e30c7845ea15927f5b2878d99d30590294cce496cce49ab206970d234f77e3afe63932729cca163e3d52442209783a2d12ed66439c3a8983387651627eacc39e1ce38556270985c4c9933c2c3f19ee73645de9b31c2db21a7458d156c4128141b9053a4ae9433945983f554b726ca8f4adaaf9a286974e50d3b1998e2844c580dd6a8e60c53995b03e4c7a7ba354ed5b6ad55e55936954d69aadcbb01351580dd76c0c2097df29708e3ae569e7a74f0a07a49f5d587a27c78fe8d627d6dcf6f50ad559f40b68e1b9344d7f189ebd80357670f63a9b57660bdbaadb492ade5d75dbcddd7fb3a514731c1370dc4175f4cded103f286ffda296289fef60c3654d043105c41677fbdd28a0278b4c88ee8e08c5b688a02ee9ab01cd1d9d938c106d49c393638f8032fbabaec50129ba366840d97bcdd7a20fb74c794f5796a58d1c082038569c9c834e5851bcc0404000e2822c606cc489a1f7242aaa75b421213d3d10c0b4e06a61647f7de3434dc5c11588d45dd71fa121287ea6a6c097735c2d0b0b9710fda3181799fa73ba62a7ff35274022956a8844a035a98e244513903c74992375e5ebba5b1dd52938d4d8804a703e378e06c06e2ca1a720e8fe07078876b4ab271ed70562f80a73bdc1010bced374afd5a15d501df5ffdf12561bb9ba4fd2c6e5737698ea041a3956fb91a5df022b0d0a0dbc988b3938164cb1af2435503638cf1ed70ea94f1c0b5f8763392703632bb195ffee7e96ec694a4322cd8c6d8c6ec94bcc031afafcea12d50e37e754aeb0fdfda1fa291ad1be991d65a6dd0c7ba881abe25e9102e5bbb530af3e2d39d52162b0fa8c96262430718205be10a6d90feb651f29eee94aadceeeeeeeeeea63a6a8d3e25bbd80a6206d415a8167d6fbd679f375a073fafe14802246bed4d4713f5ad8bdfccd2ec539b01032e7a3cdd291d79513692189293c3d184b54e7a60146cf28215db7bdb12b5c57e6ba7b576f3a8607baddd293d398db0d65a6bed968c9fee9470947894722b28c9f091f7fcc5bb24342f7bba4b129382dbb2dc005b0ecaa15d1294b74f77494efe56ba90e46fa5ed04a932861f729ecca35c799524c3b694d4428d18d51b130757a9848c102fb61956d23ce14155fa50a19c03048a470a37cc183196cc3c315e64b1c3244e0a6aca2e664a295faeb0983daeecb460735ac1dc09f92ac17684a3ac8241030e0ca43978ca3a5f43d8985c593274e52e0b10296617931f53259714be4c305fd0b10389a9dc428a619a90472aac44a16b4c2b29cc912d245009d3c47603898985c8181fb67387c9e6a849c99152a9678f39858e0dca4d0b68ca27634aac2e495e306f58915162a351c47c8342c906aadc72be98612be1858b72471d17e0b84893a38c090396c9c295f20428659e25a610236282bc4082798e50da71812bb5b838ba72078a9c2f6bcca918cc2c30b61830d8784429a5aa4c24165018d2a3470c39b0d43099762d605c19b3f5009b6a23e74c29b6339b6831974c797281c891b284a812cc092f9c6cb83d364cab3d493ef4849993c5ab44b265961163eee11255a58472528a49a2470acc0fb127734dce84a1474639a6a785ad8598e9e4cd132e3364a47809b3624679526651c551c13403a424c3c336c20d332acf525925cf0b5b0e3b5de2984e6a82982959f8f294432c5e7278651aa932674c955ea24a273e969e909c34c696cc2263472ae0090266f2e82acf6cd99ec6bcf0a2bb6233e1aa6c5255de99ba1365d2f16112792a879c65f02cd5c073545ae1319bc06c4d5d65922daf315abc9463ae985e57260955361c53e59e28b3ca8789e7a954e1b4b9b8b364cbdd392aade03901ccacd2d5c25679648cb98217dc9512cd559baa335368a24c2c1f2d9e6c36ce387696ca37768ecc1c1e9b0e302a5d495b25d718938917b3ce95d2cd559b2a38536ba24c2b3ecaaea71a67589d251b8c3a475478cc12c04c2e5d2590ad126c8c69c38ba974a5c85549a76aca1495a812061f604fa69bd34c23b654fe103b3212ed9e5bdae798835aabe5bf0736509d12580d3a005aa7510fd5ab530fd53fccc822ec065f39f8aa0297b556bbc90e94be1d010d61bd7a394443d0c7b7471b4c82bcbae184e71774a72d4f773ad63c039eee743071acf7a7bb1d475ebc333be219fe661eed5e0ad0f71cf44b43d06f4f71ab8ba8200e7aef44fdea462060f1f445d4149e57cfbda741f6e2f0edba672f066b2de8b1cc2fedfb482b0b41ecd73dff2ee897e679d05f32c85aaa3abbbf5eabfcf5ddd31a11cf9a7641152b8a3eeb4645df5b3ca2c0d6abd75eff76704c7167753c71ab4822530fedf4b675f2b9f7f50718d93a55cad741bfde3444e8177c9987b4a29f6fda4d067afd79f8b6980e281db86ff7c6beddf37afa2c76fcf8023cdded40fa158d45b0bd968d24ba7be620f8854dde46bf79ee5303123978947a18fda3c93c9016f41f088e34702cd16e5d846d1c1a5ddc0dfa6d44e1b9d7f5236ab7fe91e2877b3cd1ee919e75fa91e8d0e4adc353a71f60e4a29f6feaa1dbc11c93173d0791a72f6281c23d7d1c632ff3d0739079d143bff9f82d8721a9211465b4998fa1e8216de6e38c06bdcc43da8d05d8e1cdfb79a3fb335f711f03cd380061fc83431f1f692038a290b948fed06e78863d4f45d1087e74a3eb9ee823f369998ba3e8230d4d84a4e83ea2288e331765b4cf7de8480b7a198dbacf38862842b0d6c21926c70e2e7bc1040791a7cfe28b3ace53fc2376a03416df23099ae74d54bdfd03699edfebb74a3159d46f9d08457bb56c70407d88a7bb11b27ee714e694f51d3aa8e8dbe79ed7cfbb794ec7122568d1a2858bf7c8d9075ceea7ee7d14d3a0ef403d50f77639afbf4ec35aefb8c147269bb94fb563079ecf3ae4b16d588915fd9f7f7091653ec296ff5c7683cc5ff1861ddc121b464a650ecedce83ea52e8e764421238dfa3b45ff8cbcb5254bb47b200d6560fd3c046927bc15fdb3b65cd7ed05afcfb07b8e6d4561dd5e9a11ee6641b463ef89e0f5d89b4c1396af1e3279bb3b897cf5d0c51fd0fb1b4d84dd32df50d44fdde87e7bd1cf37e9e2471a759346fd94bc591abd61577122bf08d543ff42af5eff0b7d457fe8962422896827784abb854e43af2e86e058f4f32250eae1665358f2d63145f5108734a3fefa3d82e486131f1149547d453f2eeabf41441a74fdc3f5757077425d10cc65592b55dcbf95290ef04d0e0de0c38d6cade592df62aa00a7d4baf776f616c4758f8487e997a2c5ea15df4a5e3a14f68d1eb0f803807e1d743c9e188bdaa81a358aea449804bd2c22ad208aaedf0bfe54500687d935cdb94d5d1f3edd35698979156a674349d3111b53029023a804cc983162e8146d800093160000180c08060422912446a250dba90f1480076ba22a604026a74a0261280a83180662188461000040300060100041188ac11854fc696ae111e10cd10d4adfbc73855d6c5157cacdaeffab8fd83d5225acdcdc68de077071832af5d2cdfabc90ca8e2f7f8c1279d154a6e33d9d7558cb590c132fc61a6a10901aa598ce0cc1f20c0ccb2640a9b6728ea42ba70bcbbf03fe651d285140cc105c091dacb07029941d5b2f1e33e25c8b669a1dfc76c6e6ac9840555cf00274f7292556e0b781ee185bca54e5e5559c2475ee2120161b6c648dd381deb760b75ee8d7b9eb6ad3e9a0d46f6f4b2f1cdee79429846ae5f5beaf9532df5bc74d7d3181fd89326ed2c0238d99ba2300cf3b1c8729d8563f61c925b8630918259a2b756a1069717ea2177a486599dad36d5b4743f8d45b02eccf814f269d4916990b8615c0150c1805a6a9cbe9367557b336dc615b3773ee07ec408d282987cdd98a249f89508a3bed15e2d030f1b7c65196289326d671f83f4c878ac08855dd3d15145450e449dcc2e46af93e83280e881ac1020d2cc3a45c96ef1714c1c9b4ce3e988238eb9141308294b8486afc40e39761217f810a6ce77e0eb6bd62d73bb0bb5e9de13343551cfaf0293203fc8ed9a3d3179c0fceae403a7d1ed6aba382889dd9c365da81c119667005bb2a6264678abb08f9c4f86c8377f47c0c10759f5adbf434f2d7a12ac4ba24b2c88528832468a6dd6d7603127a32bc643541a8053100f150193110540072110aa43d4d604e40eb86f280a677056eea8d98a8091b66bf68ac3d5bbfce958c2bc68ddf448399cb0e2736b27cbf2c090e7c3447f81609b8fa5236dd012b3b2e7092ec10984d1d100d3e4cbdfacc0a81f6b55b82fa622ad9fc8283971daacc47ac8a602cd2167804472ce35b200b78f860d56d07c8bf43cc5c36226dfe2e8603a3e007e1fd3ddf6f00b8a51595b6d6bcc8f6586c39f7f950014c6acf1d7ee5dfc5b21e7639f2faca85c490d85232a757185e1379443f4766b451b3978dcf28afe121259f5a428cfb9b8a05fd9dfa241b6f91df05bff4a848e24d692ed71484f1d701dbb6240c88174a71a434aad0b97aa508af2a69839902773466c15aa40a7e8ca38ec26fb4e9a88d3ef3543582d0950342440f1d07de537c36772a18692039a61e08724eca09b2e20a6bfe040f4c0cd8cc82f0e68cb5b8cb10f300857a0de483f3e358fafb7ca02f03c68657034be02aaf97dc25cbe11021142143908d6ff1517bbb0380726303058f17ea7a30baba110dafee98c9a78d0289b6ab0c425e5b131539ea8010898a0ccfda467de2acc708de1b9305bed8fee4859cc9b998872d7c3d40427dc96ccc69cd5f37ea15ab0c19ba41203c47439b7dca07b3e5849fd63221580870aa7f1b82d297dd3be103b97117fe3305da99aaa26500d855c84a1bd93cf9264cf7434dea47152942bc4684482632cc0d2a8da1d03804eb8638036b6985d590f6dc058877b64e9487a434499188dbbc8fcb1a8e226da167e70e075fbdf7c048916f998a9f18a96f32da0bf692e218396ae83dc27fa11a79077bb774e01c0526fdeb10ac2d3c9d01d230d23de224ab256f81d95cae2704eb805268241aef58b49834cfce937034b68750c284ef428d3655ebf60466ff44253edeae876ef04a7308533c65f3a2191604fe675815ef8ad254ae12403751020103a396798a16f409de6c177c113fb874b652893761f5aa8c06d4372431fc9c64b4394024a02263f7e842c4a28b36ec22ea43c877f561526c6d4223e0ccaaf6eab4b6d5a12e11af82674ebcdb3e5889b5ee4eb65b828ceb40c77c219825f6613dcff4ea2a0e0d16d1664db8be1aac149300db70f56bd3b7d8406a156b11107e30d626d10ad5fbb75bcca7bcd41f140fb3421d85e5103ea54945bd62d5749a7a8423c7360205160497a5d01cf00bbbb96cf0a03812aefa0931a6d6917321ea4591dd0b0511ecbff0c3d2f7b4c230ffb2ffff626608b94c6fa52a698bdb39feb55787a42fef2ae9f537adb7d501c308a5bdb9b989a9fe6b57f3b49ae23be0e1cf1cb01ddfce3921218a2dc91280dbd7b89357640a411a0b9f52a181364e7790298afa9213bde4ea2c53f71d4bec974d3e234dc72a791404ec71b1136e8c7613bdad0a6905f138d70b201d85633374c3f9731ef882e7a96f0939c5238d25c881cd8050ea74cf1a34c6df452c976e59fd4e40d99066c1ae90d6dd1352bc401759f0e42a8abf4adf5b6a0631ad27b030c6507a22a05c8971705026832d817aac4ea137775eebf5199d576358929b989bd6673f3287903d8509ff64299b3ef273ef634a9e55c20e7dc0bc7199779071513d208746ea4342d61b51297f052a79c589f141f6b48fb5885a62609d4de4aba808de60b2bea5497b2599df07bd949890766321d1fd058489cf8c6007b19694f57d25fcdb1a1742cb12875d87a381db63700d6f236b9dc7a2cee5c5e7f93ff13bb92852ce48c39ae53f3c1440ce67f81a7744082950d5d7e2a51a26fc946437189e0e40de978a6ab75ee422d02036efbb1b301b35b071867907547f39288f674ea7a324062ae8cd21a2f510d52a46d6ffa945b3991c109be2ec2d87bf52471c8f25b4c44964ac3864e66c12b6cc4b21198f8ddf31b33da55434e883a41ac67fa30a872049cd9cd3d8845b4de12aa4f25ff08c414f9e142d4bf7ccb32ec6b4830a392ee26f58b33cfd46426017271918797bed8d8f4bdd09569ce95cd8224c843345b67e4d1bc6643d39e1813bfeb1d249249c56e445d599ba67895a07c43fa7164723078dd1d15c1e9a6bdb2759666c06d0a48b9cd36ebcc7aaf844dbe2783cbcbaf3826f36b7bd63331829a9e5a261044adf67f1cc81a01331cc06ee82b106540203e234cc3e20d36c6c69d5ac98c930b87f633efcf8945b08d4a0c9a8a43c37d30eb00a4dbd7b61a925a5ac77bd835c4cd7e3ed79bf85bafc44ec9d609af201db1c6bccc60ce906ad489d9e9674a526cab6d1c23321b4c097054f62f65244e6c0c7b559d85a45a8030ea72505a4fdb5ad856177eaa9005d99a8b42a9885af4d202eb0bd029488a5019b0f31abac32b9376389d784f8d4375dec91a61cba3fda995a65e3d32adacb5ad33e1819f843c288e2cbc0dda463c69b0653cd530da60a1c59792b1ecf899bfa7a9d8fedbca79893e047a31e925855e70ef875be3d1eb4294eefcc6d1b38b092d854eb75036bc24bb5475a066ae517b0c869eb91caa8a24442f68899ac572c3a81c16eb4921d276afb4ce383f741f8086f2210e3f8797928702898b743a53c5b4d59d8141c420e221db033f651634ecf23cd438eddd1b3d9517f1ad3301b7d514b68d7876e6fb230092a1b5eab1f8c9dc942364eee66e1732b188514d13dfde661ae15713916075a818e245c694e46fbad8a73e80f074caee4ec23398a30f83b0e76731f2524a8e1893fbbb38cee05a0f159ff844817bb5e22801c6eb222bdd7b528476a6c1920008cdabace58c39f5035be4f5afcd1a294a801a6f74d42b20aea1440aad492cfe492a60c503f4eff54473b9a1bc1aededc5249cdc72c7d5bfa1440ab67ca8f334ea48c93f7616a9d4b9514357a2e55b32c4f63e698d22d754b10bfb81c97966f138222445ae6959923db6693ccd474dfa64767cdc863f20f2cd1682b1a82f03e652a9c5f080e660ff75e4549610143e1e35dfa75d5bdb1c1bd982873057734dbb6530894b52e6c9912c8b9d45182d89d03208974d0b8749a1099cd4a3c3317a5c8bed30eebaedb7dac9ef639f070642bd6013df7b5cd1e3b71685bb1297cc5853610912173779da50f3f5dc400d1b30f55208ef98e7e401fc5044421fefff76913d8779e11e93bfa38059a96cb755ef7878214bfa9d04526be9866ec0d1c136fdad2da0d20b60e4d5c5d73f35dd8684354afb2b4a44994f63846f3a0d3b5222caa64010772638ce33f271a7f03b649bd8ae339b5a2c18fa4f87398839c87ec7cb41dcda1fece1a80ef23c340593706486b07b1e18d80f827ceda83351477e8ed76d2375135df7626abda085130034f22880e054d6f84f18528d81c5adb32cd85275d11758c30ae049c7225ee1e0311d04db57dd4af396edd1bbd36be9ed649f12c82e1065393afe5f314c8db69cc2b4f8811ca4e63115843018ebdd64a6879c86fd4db45b5e452dd8c3b58490081cd7b08c9d02482c9c20c3d9bcdd15723bdef419732b5c417dbb7f9f63d471a287a80b8563e2c19692e596e5038abe631affae8b8866e02085530f33dc65ef6031ba69c318ee631cbc0515c723d82113835ef1d998c264b4909698caf265a53630897a2a495c5c716d5d8c542e4233ad5d542eb18eb04f0d00791e082b988b574bc3f128f9704ead41cd77a9426844103a98167d44d97ace544a52243e87c04e2d94d18cb89f0754e6b9a69b19b7c7bf4c58b3a37589bae0e8c8ac3e927a4b25190d030b0bed72c0ba734409e3b68097504ae3b1f8a48ae31e323b0fe4e2a4d7027d0ac60acda63a2df71d58b61c418ba08b0ae987e774c204c8ec405126d35e30b300a838fb01444b75f7fda0e2838a3c749c6b9c8cf04dd644420a278bd85a41d38b81376fd08300f263e09622509dde9e1d751835efe0d7d56559986d160d432021dd8df83f3e41e32a9058c54e60994f8cb24978289123316b9679a738c056994a4ca0b0aef3fac7013f7057a4bcedc14765d0ef7dc85013dec130b174e3c13e266cfe592d74496f1decd8598c8db4763abcf863c878c6cf804f4642f306171163a8ee22ac2d1ccbc1474467f20f508bdd7098f732c60c8f41b303fc6113cc74afdb0d8a8af79ca06ded9c1497f6b439bef2e08d0d36a6a03fc0888bd6574017c5c9bb845aa2723878d0857f7ad68bb4a9695c7ad8f94695d6d7e4a38e520ac37b7c7c1862419144a37df5c014bf1a6af88d38b2293b0b2792c52a82f7c90081a0dc47cb1760521b1e52259aa8452a46de052d92140987b1349d1c58dd979e0254d7f54ce0c772c3dc9f5364f3fdb2c89de47b390fe3c268438b06a516647b3aba8e9959401ed982bb16b901f496afa453e5d6b32bf9171c93f48cbb3a2acb7c85c2ff7989c71b011db0e9b6d61b62acf96b3f08619e84fb1a0a5cc13ce9aac1bbddef4f8ce4f285c1b975271cb9c422f93cd869019817d34a5eaa920faf2d4b24df84e4a0bfa8670911c6f3a2c42d6d46c198a021dbbde9d046e1451c5dd60c5b5bd27344f4a80799f54c7526c7f4b2746ce44bd45cbeeac757c26fb9660a3587353c0aad0633aee0e4964d08e25def8072bfe54b420b050d282a0c3aa32fb2b721e96d09bd1f9c48d66cf325de169cda579b32f3ce64c525c773d0f79d54b7e17e0b17eeb4ab505d72114f250d61c5645b96e7f612524deeeab54c6a0bd71bd661864fb08baa6e2a078df09d9c683f13249f2655f4d018a83bdf4562e57874e1e34323d47a49e67cfd546deda6ff61d1130b1da5694c65723f416081d3b6d89f2a7cfb4b0adcb46f6e4fff19877ae73cad32226ed0458479ac746d41a3c22b45f1dd622856eb457ea1e4a0d283276fa9d91131b4e4e02072a17cf9c2b6e5d4953de16e98e34052b61c3edc5efdd4a22989aef84692c3eec2171efac1a43d46c4804b506a4cc3f20efd2e1a8383cd313bdecb2840899b0c49d74f753eacd82fc3f1cb1ee71cc385a84837be940b9a5b16da816af903ffcece73d13f6e993c40d7e9b9190d917046adddec151a39315bc2f9550c75f0c34d67d42321da1b751fcdac4c4de99b907358a19584069f280ed63ae55f3dc88eb41bc1a75abc9490c5a8227dbd6a79ee2d481c4fdc1989d3c4f751a0ae5e14b9dbf4ed15de08ef136c73e517c36a28ff34f6b5e3a4c940e6f5c07990eaab578b23c8faf347eb4acc01177534c20596a8792fa5661ee71b96d0dc71419a2fa94b71f0641058859fc6273452ecaaae827f53be886484806f7d5764fe0c38ced1bec1763a8c6390b2563ab10bd05a245b66c9af386e6ca65d23ca45d8f8ace2fd12423e62e9152fee9079d7cae11ee0c46404c63f16ba36d3269c5da10f215e23e5cc3bb1bc26bedc6c97e77903caa6f4933c07629f00943aa6f11a79c443bef83a0bb521de7439a2e061cc09ba5d8739a15a5196e7d58c9a45f178c59dd9162efa08e0837dd0d3ffdb3f8dc0755158a8ebd9e7d482e1221b461966a1536855eb9333e1abc4a5ad08e5771fbab8389c10e5d13fe08f5502abc93735d579031df138cfd4655067855640cd072c7ac4b62673cfed9288feaa01a539c6e1c2d0783946eae08666b6efcf5ec235a83213855303ca62777f8a70dbd9588bd7b2c55f4702e113a1442f600ebf58410a41a685c4d8a99dcf81693aeb1d7fe9647aac1cc74fcd4f9d3a40047c4d99f562a720041283d6325943605fd7c171ddfa2bc8c82cb737beeb172c02b4ceb2bc618e63a86bd1c3d95cfc93ace95110ba10286d1830410fd85efc91f18a9cad9363b419e3a28f62fbedd09c648f5bd30095a7ac99ec7fe4e9ff83502a40a6581eedcf45a2d591bb0bfdba27f6b2f27486ce5a095102841bca9e1c09043f23b71d2aaec20dff05c63c222536acb6efa65c672062b23fb3f3f334108f6d36e2ec5fe0df601ebc87ec3a22a4944b6e0f34256bd081cb00673e0c6fbb9f47ef25965bb915629560cad0a42028f607b5905aff98c05a7e2ae8f57249a5040da96205f343ce035a89809c780b81e47aa17243d11083460aef2f99e399fca13aca616b53b2f895e37aa65efbf75a30277b8834fc5e873149fd7c666d2a6fa281806736265093ef5ee44c3ac18a198bd0e7b582d253388ec7a630ec1ee924b60721f0b48eec1851c2826900b68815eda050274b4f6dcc7c6dfd4f1e0cea735a12bddbab9a9b3d977907889782200a117f75b1b8778b816075e543571dbe5c96d11384bce3f971772bab81c616dbee9f29a78acfa3c5848b5837bad7bf192660a403dc88ac997c4e941c812dd4889402a349e02ee3f56ad1108e8c824c58f2b8ec01582ec8239e720f0ad16f10b5e14249364631e181e626ab5b04dfba6caec86a3f05f5027171c62acf2e1c2246e4487d39655f218faf1519a4b6aac9eeb5a2b1de13c2a8591c234a89fe2e04891ede138eb07296704451920370205497dab2db1055167210c6345eee3aef8045d2997921ef2a240d9fa8540b8c183c1ca36f44d5e8dee48a79a20a5a314609e767159808b47a4c2e2c0389ac983be28ada68a5be3db009d2e233058a0e9eb887203061f5b0b1a567ae91bebb30cd7a3dcc9b86782b838fc2509ad9ad6cec539bacb0970f19ee0d66d95ff0a3e7de0ac74037b8f9e73046e59f95bdb146499a176687bbc6f582dcfa49013d47a66a0a1a393c10cc2efe68854a1e66d3a25c4cf8854409dac16c0d82a44de006faf8133b9c1bf2ac77aacc6809707416b037a94ace98895a81e7f3b815a2d03bf08f6be3cb2a5799fe1d46fea7c61dbf9f74fd86549e24c686199fef9275c49ecae94726cc59238b2ada8beca99b882ed9c6b0b6ae9829ff8401b8d28edc4d3996bb17f209fb07f446e0821b00f1dd3b00c78c214c09d2aa3d769ee8539a846b1dce63d83e93ff6ef37b4e932b9fbdf5c5c807d02bb9f9e26ce61c44a3184f2754d61a5e791cf609813e68d498541ccac3e5a4fdc134e8fd5062fc075cecfead1b846be30f65e15a95619fdd85a11fff33eb5866150176209bb004aa0070190cb867409e6b01e969569b10aa9d9846f32209423de08368ec038fdd46d29217c11b408c08b003499c526c21eb776ae72370830e2c7ca05178141f819de12234413218bdf0fb2ede2e903ee0610af440143f41b30411f18e7609abf9dcdbec32dfaeed8cced654efe54bb2e828a2330709f3d3816703eab66bd880b27ac724df5108de342f9d13936c40a84de311a120854373bc648049c15579f3dbaf3e13ebcd62e703e6de78f8816e64f1a9712e2fd08ca699f1a6a17899f9a438b78ef5b01c1672e762ef253aa0e43f7332c07ade707610e2808fd8b66c29c115f81fa6a47418e0effaef627bf0e43fc33b5250594d6bb759fdde53eab960702882158e43f747456e07faedc73417faeedeadde2fcc039822196112f840888310501c1a7b97370d11fc372d02e458c7c2ca89bdd92d667b0bd25e8020d2ef19e789ff5ad0f4231021f041b44244255841e842322c5113f0df3cf9a2d0dcad266854014e4f35e0e63f629ddce20d4053a8ebb6875e9afbafbb2959bb2deb13039ee79d52811bb11e088c0207c76e05820f8d49aa54400fd7f5cdb2e9e3e076b2c849fa4db59c43bc4388269884d107f10e940203e0283fdd96c592128a09a7411a6407022d409c102e1134327b4f8437351237e1055bb5e7e6a49ebd6880b7d29979b870bdf02cbe7c78d22744238431c8b3081d08cf823d485c00f016c0486c8cf789d15621f89a344ac4e88a0cf4f7b4b4704a04f3e436f17a84f96ab4680475c422884f808210e61265c248dcfbf8b17741c250cc048264bd21dc6c2874edbf6574b3446a74892fd915f8731fa99de9e0262d16d5c68ffb88f352df89f8a70319da245d6c7f706a31118bacf263a16909f55e383501881f808b598fd17ec2673f70e2de957be63c06e932b281f8c5383c010e88a37ed6c8422e21191e35f3721fce4c2816a7fbeb462207420441719e14054a1502c84b70d32a2c5c347e39e42d4e9074dc999136707f904ba154658f14aff68b140cbb5ef2bfcd06e2915d1e2ff09b6abe2f919b7092116205e208e20cc204c1b01d4f7c96afb84a0c2f4415a3739ea936d6d1174b1baabd20bfbc7eb680c4a1f49bb5b5fe573afc98b70401446e023b622a0405040ec8350cf8816a34fcc5608840294d4900db0df68a55fbafc8c65cc02f5e43cc54d5b118213f14e0446d18706ce0a854fad43477c10aa362268fe73e0e221c48df093f823b841019416a6b9e1c4533e99561501200816970f953a6a9d3e15d92e923f2bd70da13804e0d715c924b9f94598223623a6b7be0844f976408d98a1dd828db8618c9cdfd743e1db1d73004ead119cc5fad16eac880ac4dd0807083128be19027a4b71258e1ff68759f0d66c994b3d18a323dcd18cd3fd406a67b04d11f9a6ba33f81fe1004676014597d65c88155df8aa582d9ccbe104f904bbc208e5106e448a44b460fad41c6a08f17db5444ceec813217e562e078218818f48d60f8a7db20ea351f273e5ba1157fc73551fef001839445f18f8bcd0e4a3d14d5431848ef013f80fd7883081ce1ad144b06810c67adc093bca4985829345bda9710f90e7d9e0b721cb61c9668aeb3366c6c2656071dfc0de396942d496aa8534524d91816a37785ea7b69e1632d42c2a8082f6c12f6023922e74ba70508c6c3d8dad4b0feabbd3855e720de3e7c033df67e219269e088187828f76292525dba9f2f02273c4fa3e72acaeb1f17d5d06ac51f942519e752883cc8c104a31ca6dbfb0d0ad27056cce2c74281dbabf04b164849eea78bb0d563a40e03af63efddf22790ec5ae19baeae5f51c9ac1cae8a9668937d4fa622593724a7cb20ba44965d7a2e73f0a75174dc61806c13c60756e02f90537bc085b4b5bc2f02329b9850037f6e4f22f9af210dde652fa91af1d5f35a972d2c2f29b3ff152ca6208cd670a662f7f9ce18c40877aab9f5a23f04a4b4e3013263b42d0be41490b6735c105bb62f59e221a3f7835e5a12cf6781d742643e8c94c4581d6ed1df6022d46aa8a570c44372f61d8473c759e464480cca582c34cab1a7341215aa45f418e9bd52486a3e347a6ead63944e7dac11010cc17783a52b2d843d0c56cc17d75cbf045e3773eb550c54a4d7b3ce7ca5f0c3b308e821abed32e312b104f1977cb2ae372e25ac8bb2bab65c1bfb128b14497321398e3de11c120044641a31b42fef43dff86cfe76fd9cebd7f39cb4d455496019d4b289f5da96e04d061397a11243a1cef3080f5e1a9c1e15da744691101c4795341f017cbe057837d3978085c2387a53d0c06528895e1e428fbea0f59518a7c0d26fb2674c41453445fe7e7beff06ea43a92e791cf61584c5adecaae6922ff22125a0eefe3fd6b102809473f59ee10b63178a7b177b358d86db064f65b867ca6e8bb5002a921841ead9742607aa4d03f7a8aa58b66c18066cb0e22572294f6ee02d45289e277b0ec81e36d3d36863c9566473e18689a202f15a576003f8ac00f240d1b0fdb6bcd0857a6e7b86eaa8d277b99e50c9399fd48a1cdaa10b63c57506a039eef84e4e922c267376159c66abaaffdbb186b6d4f691e88e01096f081af7add35a6ab5d5bd259549e9672d48f7b1c8be26f6de0cd6fd3eba1220dac1b416752d3a4b46ac7713fa55b3ee3f300c2df0f08eb5992ed682098e77128e86de69978032b80a0f26b909b04c3b546eef6e727836a12daea981ff85d0f6d3e385c7668be1d2e422a859ec0e921e7dd6bccf144ec4fa805fdd037905708732b7f5560810661d6678496bbdc706201ecd62c2b1a02302605dad60517727a20679aadc708d1023db52dac037851174654d5aee99c8c1c633e150c39a0cf06aa4dc5e2cb630f4d492616b288f3ba90cb38dbae6ccbdf9e038dee09d196ee63df81b309f644277cac67154cf54da4eb71fcd3c1f5789e73a8f9eb08d579e2b5bef842f0330ccaf29662ffa74787ce4fd8f1e29dc46133088960ec5cc62528f4dd0109f5720d5fb88d13a3415c1be9241085cc1bbb6c5e7e13cbcc1d5e340e91037b99f62001dc4288ca577e6586e5fc4861e3290fbc9baf51d78ef7f46720e16bd840c451e99ddcf172f27a7b35c6fef2e3c82535c9aa6f9d92384ecd130a49f6454c16060525529f68ee21c439ea9b1193f8a5cf7af1d0a09f4516a905b138283c05ec1c03aa4ed05d274155bbf5d5b7fad5b1ad1218788d6646543b7cf5d2b85cd366c53ce0e4fd7287047c7b2bf1d2a79e956f0af1d99ae73c92dbdee061f71e4f661646b1c8003c4cb356b5f25524ea1fe5ea07196b40c2221dd2abc4158059b9122be16b618c64d05a763106bc07882ddd768ce7bc0650ae0baacdb227a7514bffb8336b7783c048603927abc8201cc87214e24a7b0cffba678726b8d4dbbd590124811b1427c05fc205d37730a9914140ddf0cbf8573b05753da8b48dff27b6f7c7dd5679db2f1dddae9c7af267c218c62fb9a7729c846e2cf29a0f11140ecd658b5ac6e36b8ae8490ddb84a07214e22824bc1da031cb9fe4549ea348434b81c43c153a70ce1cbd08a8bab1c8cfe4b0c052d17ea21f209e1118525c287ae01a8dda41e658d16e8f4c015ba3fe94002a376311e901a18d15a199ab6e16c874089284775ce7594970a56836ca23ce68963c9fec58e96590a81b41b37f17486065e0e3f472549d33683d2ed59d6f968e08e311ed075b19590760400b20c23aca3d19ce1aeb9e844a68602b4515855e5f1b24ecb1c2d9aba5dae071fe50b4863e3e9723be9fcdcb60da082c68f3d7758b48333eff20fa89bc0aa46db3fe58df6a19f7d1d46ed781522c7cc0abfeed35736572dbb640fdb980e604b4ae5c6aedd02051adae4d77c9e3ee6985cd7542c50bed2aa89658e9f3522d7f47baa55e9f13ef23cd98b79447f27f1192ef038aa2bd2cbb70ff4c05e27dd8c645dea92bd070675cda7d7ad8bc0c2e3ce6193428815ebd698c6700de565f4ce8adb9812373582755924d124f5a8dcb9d81b2f44eb2acbb6d73c2b272bf310bb9e67acbdc84f8a6c260a85344ae80e3850c9a0cd28d2225f0bee6bb3023adf19fe1c224c16d3ad0c238b5472fe0cd50963c0140e592e9a25ae7489ac89c109492c885595f80b538264b2b04b96e74b57b9fade342c1c9b6b18c9149665edd7698c8e446493459060e618d80a9b8d6619a0b3c8b2d621f75c598de46004895960b53f09fa9d8cfc9d3d110f0deb8ec5521d51953ef6f5be2b1a2dcda9a5c4cd3452bf3f13190916eb4a5e1d104f1506bdc8e8cf770ae7a2bc2d6ac5b79eacbf4c6c05c45bae4f447058d4e1fc6e79769f0624ce03b8d267841ac2d2fa3bc7c7c62e1a634660ca15eacf306932c5462a6005e08aaacdb4fa24818b911629ce0294f3d5026c1f53a640ce2ecf177011235d0b119c3f957e07f1b41189a265c89d11c904ea69bf9c3ca62ad86a17d9ca1adac34d2da70756712e90dad7a4a66fd6bebd694088b53e77b6ac51a9396af2356a77c5e67a6c53abec0a6f8a0526332dd61942a4cad5bf7a9a8ab88aa5e5fdd9bec59f9a216d90162e1d64b5742ecff9ec71695a1ce4067978eecdfbe9e02a47137370bf21b931963b21002dcd74eb6d227419154b8ef69a70eaf013eeda25eae5e2137cb7e56337b32615bea5c5745d20da9013f264ee084af59f841b34cd65228fd7226611f2827be14ed1b0f5c84c56f3af448be1e6facb0a9dbfe1dd1c8160d56824dc12f5177861c7496cf06868e4106d7e14eedad125f216b6712818fe4ce48606fcb79d6f31a56606384d57d5429394dcde3da5c62259d35262b5c5e6f89c7694e03dff1adbd75687d829a46809679488135536786406d6dc269d4c0734f04f1478fbb1cf39facdc42bf87cb10450acd1c9fed1f5a10e56be65eeb4ccb19f4036de83023047dcc2de521a38ab690c6499c113c0751a8b09c73539c16030e9689d52bdbd0cd8c4e96cef17b8b7fbfed1f6bcbf61f9384aa415394febcc05b1522051710e4647fc027e8be625a2133c691e48a309e6f03fd81af657d13a0b243f5a37715b31d9af8d0e45d98122c0d1cc3f5371bcf25ab655a3ba0aafe770f0ea8d463f18220a522b0827f52685695ffb9d470335e896d8501313ed174b78bf98b3ca6bf0a0f1bd34a814c4153ecbc4db6f4835aa1302b2e41f15cf1d5604b730cc687f502f73dd15a84d490b298c8e9d77cf0d3e78199f63084760959e1ec7fe0158ce978e22e99714840f1f425ca2e3fadcd58b268eba294b89f5a6e799b71dc4a42b47376527678ec25558a98ce88fa1d0db7341bfcfcbb94e1c520e5c608e6417a4ac1dd03d6c6143097460de87e9c5a3d880b770a294140ccd9b4fd4ab73b3b4b31833a4350974b438378614420676ed08f09d0db01b8360f6d51a721ceb74d91bbadccd14c122612c3388942f7272515262179a08aa4d9ce0353c46ea7aa09c4609b7d4704106941a5687148bba795e1ffb2cf3e922ac1f3fcc448131cf3f9cb21b157d88fd6bdb633b8e216e4b3f3e6680d689f11124a8fdf78506f78682978c367354e234dc8dba6ac475e7ee6fb3c9878e62f9bf09bed28b7899423c99758fa49ef322b0851fbe24ebfd2314095c9ef755895b25439a5f7db90a99521d1e7947db30efe84c51b5983329e62f99dfac60327fea0790031d69e1f3fcbbd2b0598b9c6f2d318f26476e15627e26b0d6b27ad027a50a832a892f1abb7cf21d4bf9a6fcfa05a62fd4da6aad5ded61ddddf405196a55fc552a782d2ed840a065d0a925a1b626cc83f3d737e9d09f6af0f678609f301efd750755af7efdf383003f0d10df0322547aaa1cd2c0c19598951da7d35d1abeb3ad4b720f1dac250075b6fa5010573fb8efd79ce826e9c97cd73d78f088f4a13efca13751095d35d816235ac5ec0a324277032bc9034422e354cfb62c12218546c9896316b456f2473f0647aeb29aed0463389d1d98c92518d3081c49116005ed6d9011d45049c448fd8bcb6fdcdf94545df8683984a5c762654b391d629b68ab236b62d1c3a00e42eb673a2ffb48b0b36bba70dd2b1789431130b0bf3837e302d1dcc45c5bab0aac1204fa193ba45410380b4d3a958292b819245d14f8bac8c1917fc8f55059c341d6c367a511eee873a983b4bf2a3ed17923ad32d6439de64465637772e31287f89281863798f56d40659be0800f7dd3d79b10b9e5de726f296592329909670ac40937dba261d3d9a871dbdaf463fa7ed51fb5404a80e39c9a0a95e63eca79fa33cb856439389ee6e070a19c108643c7d400c7c775e792b0bf5973a1d152ed32d7856e48ef175e172d154887a8a492baaaeb615a6e1ced3beb51c93796f6c1b723576553ae0acd48f92dc65866abef77a16743997db9c71fe21be771f4bcd13734a8c72633aeca0bfff74fcb3876b1ab5cf66ef7d5a6a676f797d3b2ee3babf1d55ed85d3b2ae1b2749c032fb0fcec72bdcc433c39dd75e5b58069f4fa4e00ef9c57ca4efeb679dfcdb0e3244fd7fd94fa765dd8fda5434a1d97e4f518d49cbe2e7aafa79267be9cdfcddf5b379299aba352959f8bdecbdc79b7bbf7c7bb3fdfb230b54a2ec27200e923c92453503b842d6b59d66cec1fb54ab6b72114fb5b28af8b339425a994f2503a36896dfaf82505cc7621ce7b99658a6b997d0eb69549d9f7bd135bd68e58b3b3d0b6fc62cb9a14135b695f7de9f75866297dc251dc7648f7f73f5d44bfb189b8dddd9dda9724fa729571cfbd7beeee491c4f8b85116be6951bf1d453a5d0b55472e4fae34de2deea3185238a084ae26e085a0d8b02fd79eccfc3f982d18e76958523a02240ae6ee0b6dbc6e3658c87de997d12e028ad8aa82b1c7fb6cb8ff8228791fc9c1cdf2d6d8cf3321fc14ec04fea66521f078efbdee3f81aeaf891d2211e5248cf04627300ed6b0f6bdf56ac4039394d71266772da9a356ace9c3143864cd69831565bb64c59b102e5e43465324da64953523ac104139628519204638c43115ca445d2470810fc187e8b35d00fe06b6323aecd0487a00892c867cf2417381bdfb81096443e42a08b509268680ab94c336b9a914478d31d379bfe5ce342f8a9cfa6ef4c2e347bfa3e85be4349229c4d71a8f3de4b99d6f94ee31d7aa6af8b5d8ed7d1b2c739f04bb13e0f7d25cf9440ead7d7d1b3e994da5f28a54ff534ce773ae7b117a4c726df0f0e7e9806f1fbe3e8cf458cb5f7327bb77ab5de2d7313d7b6619023c0515a1d7121efe90dcceb91eb73817af4a94dffa547579edea6672e6e3a08c8082af777e40757bc53eb48c2c65ff147edc45f7bb0be17e4a005506e22225025bb3e09bb86586afcd3d34d7ef6fd9824badbb669201dc0ad7abf69ece9b1674cb26baddb05e201dc9eabffd551c9c63e5b126ddb5699cf86c02f7fc7f8af27a1c0b93d97f52bcd37942ee2f00ea15dbcafe0ed019c7b0e000fe0bb726f38d2edddafc3b3da1b5ec18fc318e5a143d208080bb83dbd1fb5c2dddb755dd775f75eaeebbadbddaeab33ee76dde5e4b66db7ebae54aab5c9acca29a59cdf757a84edae0b9dd039b76ddb6ed7dd6dab72e3b83be6a36e0451f472c582e50ea516fb47abb848ab1938366a6b9b61cbbbc3964584d9f793b90817e2efb3fab571feac9e3a838da9576aeb66b9ed726fa7784526c6e9c938c40fe7f85c39477ef0f1ec9bde6f3bdfd41fd6f9808800a5e8e6429ede42a4ba9dcc36738a0daff4f164432892f77263f19b1a96419e6fead92b16f3f14dbdf33c3f32d2f7d5c551a9aed9536fb0373dd6bdbd72527e309481cfc4beeb651bf77c5617b1a16ce328f97472d1a56fe4a31c7a6175d1f33104785dfcf14d1d04fbc13ff473f2910d83f4641b4e282b59407f11007065cb9c645db4e19c62c322cffbedc73905cc36248239f8a65602802418c8373509f9c88623d44c70912e01ba027a930c3030b5d28e137000bea9a1b8487f837c64c314c40db0074c216d0a7df4779aae80738f730aeef931978e8436f87c88b988ef219eef878f259f7ca423c88faf271ff5841ce8f80048073f40be00007d2b74908bf806e1e7f3f2277dbcc701f8bc2ccb49de6fa1ccfb9c2d807ce4e120200fbe39c585f2910df1feac96b522cad838d90bab6be3a16f4e11ca453ca33cc4f7379fe81be5232f9c4f2ed6a2a6eca9736c1ce483b2c7a27c64c3f9f461cff3ba7cefbd2e6eb337ea0d0e85e1f01c311d3ab2f9c18ed9ec0339a55c5b28a54f372a39256d3f43d9f6397be72967cb8ca4fbcfc52bb95bef379d7400e586dd0db65724833d36996d5b6d682483edf5f3e873ba0bc79fb16773494625db86b231ec579bc1a65af6499ffab216b5c496fbb5a996b5a82eb675b18636c97e8eac1151266733813fafdbbfbafe74004309e0323b8ab69d8816ec9752ddf5939c86b2ed9fb3e9d109e1092ed207c12631919ef084e65f7e1550ca70e3a8d4512ff67ece106807d0e5d2b15d86e30cd39ce459eff364cedbdfb8017e4cfadc07f5cbf5f77efaf3eccbbc05491f294baf69d57cdf7d1bbfc077bf7428632a298fddb8db79f8cbae1778e30607862327474c878e6cb6a3c7eb10003b1f978106df75218efbc73df9e8761f11b9bbf0473632dbb46953e41e4fdd7803f1e3348f8f1fdfa6710f8fb8810619ec0020d4dfd3c36d3db6d29e1e9fb2a7a703977db9c61e93a4fc15e692e75d15419c01222a0832fb67cb5a10426c22b0f68f2d6b4358ed4aa368a896a5965a6aa9a569aca5340dd572217a75110896ba58b508404080a3acb9bb13b9aa56abeac90ad3f660cb9a1058423c6d71cb5a106bb610521bef4f6be317de91b01b2fbfc78774bc0b71372fed4bbb727264fa755dd7510976ddf7e1f8d98121930fa46946f052ad3ddf8ef2ea7b6b434b6d38564aada59b7dca86e65f615b6ae948d5d8d1777e3a522dcefbd1c30f36a433431a8e124be7836c33e7b7e1927b414bd350ab471c618e5c84bea568a6106badd61e73502dfb99fee4be28165a5d2b1ca19ddde7e40871507a63c9a537cc09c710c78d90a6a94fd65a6badb539a4692690ede96fda7e1185e91b389a7bfbb582e04534cd8759a25813699a2c5114459aa6880e2da13cf563348d28ce5c9caf43bff32c916662a2199a86a6a15a340d0ed7314505c22116fb99b9bb3b9d651ce642561508876a95ea17f5b19444cf66cf9768b6dcb20675b5e57355f6ac01d1b4e78fdcf7c409b767524e09640338fedeef42236c4774a17dbf3a827ae4be43c23df732042592a64a6f3b241989fc2d23a13fff0b6b0d61341fc90956fbd3e624fa3017a23f5f3cef3bcffb8e8aca0a4b92d9f275bacb5926dd7b45bad0b3dd7f437c6fdbb99fb9c8e52e0ef7b9396673dbe6c2a03520f76e2b13ee6f112ebca18ccb479c6f6ba3ecdf2a5137d0e770b8dadcdf1a7dfb03d5b6bfe16cddd6c1703a9c9b0a62b910dd32270f99569b0bc7efd6fb7e7383c97dfb37b2915ce4de2bb77411fbfef417dbf901818892254d52609af214e5bb3ece61f317f5753776df9fc97dceb7ff26960bd56c64d67ea8dd5ce4825f918d7a9d925ad7b47a5599f2016acb5a0f637aa8b2ab13285c9b366dd66c7f2272bb162ceb102c1dcdf61f97760f4ddbff89cd7623f2e73be1b6943e54c909b7bbcd3db513c9f61e72db943f7fc2c07bb276ae7cabba03399807529a10a6cdc659898d4327af36fbca073dded21ff57b68199e5f0ffddfa34708f5df8308fde043207abc0cc77ad41e3ab1f04b1554b024152ba2bcb2658a45318abdcc0890463a51ae0bb1904a4ec223c849d8564145159f638d2063abf3bb15d8aff81c8b4cc2ec672f33093b7e470826ff949c646dc8491667e160430d5938d54083163748f7e123d320a50454a4f80c4ea6ea471650ba0ffbf6addea26ac76730339a9492006168b31f4880b39f99843034991f10022d480d58440218275537f0f8d9f3d060709e879ee160d72f6daecd0eed248dc81dbfe3f10e4d250c4d4a0250e1f13ba8ecf8998f2410e677682a3c2210829492003cc21b763c952aa49404a062444a49801da14ccaec67dfc6c63cf4ec77e85908267361b513fb1d58da198cfd6a3f87b222b2cd76fd93d1dbf997b62b63133871bd077a6cfb733a11c196d7f0678c35dda2d1b0c676db77854fb0abc853a0d16834fb3a44dc477d223323b28d8d671a8cb5386cec0a9fe4d8f604d73de882a33c3e831732cc204399186488e18ba6189ac080a10986305e80e105332ebce082184c2e30e5246cf318519cc9a3d88c8216d044692127657c06c7340bd3d34c775dd179aceb8e6969a413429946ce42b664d0622f298fb3e0b49c9471194bb1d7797b060296684b46b12f721dac6b2ccc4ecbd8a66085366da05161b6c1861950d8d84a23823342a005822d47432a85ad355c05521f58430d5f81d4133562e06aa43c100336ce022925363068810b3048c3054c69bc604d9417acb1f90527a8b1066317e4a46c711aaecf2ecd819b972e6e4d778d73dfe04c1758db829c84b385c1065c9fed962bbc925b7057c06ca43490df6e99915258dad98575dd32e7f08aa43c210b7252b63929631b831524e5c9af66056aa8b00215ac01c31bbdf04650548086ddca001b18f0362e205d44c3db54000d2d9a05fe7b54e0c31ea1a43fb472923d232759cc0205ce48d3820969cc8852821968982620e98ffc92fe40930217249082325ef8644b76fcec6558864309ec0867a1741f2e970be1327292252327590c0304c838d314813328888104148c21c303a4fbc82fddc71862cce00031cc48698099306818210c306a6000185fd8b0802fca4c5140192f46a0c48b2ea824a08b13dc808013d49c0e20290fe5a99900872492f2982087241370a18301b8d8a20a922dc890e0089912ec60a4045af050002db2782a9245560f44b2b0f001098b2b7e18225d0cafb0c24261399a5600414410578488b212d3d583ec635ac410f6b1cb155abdb4b17f33898b5c4ee262953792b8583f3f969e8b888bf57318640338b3c84778661181e509b847daa6cdb67f84cc45f0cc9a581c1185c511565c215584155255606131a20a24b0b2646b8ff86a7051918f6eb25c4d8184540d381f7531b3f1e3e029e4141629a6a28022892c502431e6092590b84f28e18418a025c0515e0de990479ccc540d60ddae2b464b2e62df158eaeb7a1554eca3fdfa56b189cbc25090fc2e423bb05cc124a80c94776892f390967259af8c2449516aaaa26bce4249ca996e8c00b972e3929632e4cfc74e9c2252765cc0490172ef9e8a60b5513df9d01a832c02472640ae926101da101260d47c9c5ae9ffde64e2154f9e8c64b133909e72a2e1d34f185899c9431123924529efa92a80322457d44fad4072285a414698844f2d42e57fecc3dde2b8998c049782e919332ee126609305bbc7860b5258c962aca23e6d69f5aac94f8e2123de8eb111b1209a201ac5b9c4a88c945ece770cc8f13ce2472120693434cc37c3a74acc8330637ae7141b3658dcbcf96352e519b880bd95dff48123a54d5d068b42b46b54af2c831170b242379e4bec20bc923f7d863d39f4ae423bac19619ebf81cfa4a8c0b9badc196352a339b4b95edd4a963225ba2327d22d3c79aa34bf9a955c66fb5efa598c662635aca5e17f3c774900f4486985319091124f21056b224b1fc9d452227d9c7e1a8c2c63fb34c2c5336768242f1053d05fd008ee2ae37378f6f6ebec8a16c8c839e408bb5d537373fd68d83c3e69ddf7600a9ee1c7ed207f64875db10c7fe9c19c97503f637b42ce7717cce37a166ef6c596b628a7d5d283fd012201d123de7cfd13623c1be1f2fd6370bcee320995bae8b593e9f8b29d68735ed410178e3c3cdd7b7f928e79b89251f49e558943dc2da27827c005f145db48fc371ee6f1e9191e0178dc8491c11172a221fe530c4633eabed7e72f71756176995d207052570d3ef2410fad3888cc4ed22425991fcf4732829a51308293eab7d06f181fa4aec5fb1cf3199b18ee9ab6ca372127e1b32718d4bdb85e9148fafe424fc396492dffe242227616bb3ae547c56fb967428f6ddc7f2db70d6a8a030cdfab0e6f6cc45ece330874cb6d436912d59bbfe91fa236c41b3b7ffbe9f511909fd2f1c71759ac5949b9d572af519e552aa45946a7b39bd7d150a5581ae50976075ae20065886a1dab2f6456b6f59fb5266d620a0a67489d6aacaec9b2d6b5557bb56455535a5a5caf6a3a5cd05cbd98ec82389ec960db1db7fe3426b61dbfbab412fa007023501facf5bb70fc93c8c19e1b7fafb5c8cb9582b8db65ddf4ecd8b43eefbdd9eb761e15e8a73595db57b8abda7f4066dfaa6b0c7ed95e81eb9f7be53a2bbf33617c445a0312067eff6e4f442ee6efaeb026a27773f99edab9da4b0bde7f413babdedfd7571ebac68b4cde94ba3ed4ddffd4387ae91bd4c8e65e3fe93443226f37dcf876d39cebacd761b4b1fefaaae9bfede76a48f2d22bb33dc913e1ee4a27fa7f78f2b70bcdd7cfaf631ddec36b739e76c739e73dd7bd76dd37a0145b5767d92e1d844d4a25a55d48698aa597112291a9aa6a6a5a996c416d5a25a5ab82ca022c0ed81b4803a404180e38c7dcee7bc0e3438723cfdb94676adec1817ea1e470eec6b3ef26ecd431cf6f6efbdcf7d3afefe876327e771fc38db76ecd9361cbde7381d1d9d7abf3ed2fd1a5a3dd6d9cb8c64ff5bda9e92ef998ea635176948cbb8e8e1c08123680d98f3f5bb2e27279cdbd373e7a836775716def8aa618fb7afd356eb6c998ddc8761acc70aa4051c41a70ac79f8df3e07f2106bf778c3d1fe5d70b17d1ac713cd6399f8b1f0dc7d9867d125a5354ec211374100c5f5f445f300b1ba2f3301b8ebfeda8e4f53daf8eb36d4358ce17b98e7d986b79ff3dd6361f61dc853223dc778fc47df74bfb8af9f3b7bfaf7adcc11ac77ff5084966d55c04847deda7759ac0b685ed1b8e4db6cd099bc070be623ba630e2502a0e6c00c759b3db47abaaa8a4a4a29e9eaa5029540a6ddada62a3a585a64c991a16d6555515959454d4d3531597e252bc696b8b8d96169a32d686b34c6de642937b202be0c855e1aa38f7e442e0d3e7a05ce8f5f4392b2e14c549f9d08ea7cf6d71aeca85acb8312e247bfadc1587e5423a4fffdeb7d6eb3acf52ab54b74c532c2b058d8bb4aaa96bea194f5b9a65b2525ca48fa3ed14eba46d156d9fb485d2d68ab65156ca4e511e5ab35476ca46d9a91b3b65a5ac0e930b8df639269db1fb6dcb458ec907d07ef73a9e4917724cb4b9e51c937352c66c66e88666d35243e96f6c249113a5592e44b35cb45496ca5a55b9500efbf629968b54774fb35cc866bde895d534cb458a44b13ab9eda5575859b6b3d862706ab9d06d92443852aaf8539453f995a37136be459ba8942af48946512a498463775c0ca0ff5d402d0c3261a009238c23aa6a5848dd1a16524e566a5838cd1c2cb91ba08b332b5844050d415f5a3b6b5830edda021a3a0c7c7a16f5c349a3dffd5e6e8519acac8dadadf2256baed7ebf57aab9dd03b34b73b739ab5da61ae2d9c5b538d9a352019e7a91fc6983d4edafdda69b1c3a8daf527533e723dc2c0dd645ba7ae5f7fb5cb0b5fa112359cf6a68a4fd87604f3169e3102c7993545d6aeb0b287a851a5bb3e91aa242ed465b1eb2b39c1ae3fcd58b9c806f4e6a472201880f42f1d22522489fcc798f8b3a590696602a93f694276f8ba726d210ec71f3353881751858cb1ebbbf628e953a79a1f57388aff0a69369c520ebd201a45996d8cb070415580726fdc1a1c7e21d32b1c63e36fe12882e1f853db4cb5a16a63d5e6ca6ad74b27d977e9fb8552896e1cd2bba51ac923ad607369924736491ee9ffb968b9ac8dc071aa715aad6d31edfa738d0fb9118dc9df3fc7d992726a8f5bd6b0ac6c9d1080b286e5b41d8b861a16d38e6d55b165cbda14517b0cb2eb1455f608ee3973af428bc27adc6546eccfa5cf6b54646d599b226b0aaa8d5fdf77b511388a9bca5a154f43d4883e08b4d61a7e77cbedefb63d2ce643f7b77fdfd95617b9589fe621419840b9e70701c185ec66b939e75f0bfe136c848282762af5f7e2a12cc0192c1d6389a574f7395f4ae91ece2c5f414fe058eb9cd405c614b425352cab3b9d56bb71f7ebe494f73a4d9973765d54b54229a59e67a9acb516e3acefe3388efbbe8d8bfc755dd7e56ccbb83e8c3176b9ae18af2fe79c5fafda9519afd70b046b5756b5ab346ee7f2170bae685bd6a4b032b4652d0aad8d5ff2af5700ac4541668f5f83c114ef9cd86e345f7e10bb85a00dafd89f3a0853d31bd970c779fc03c179fc7d0c326100e957bda38348008e4182b87b6d02120314e6fbc25550068dac7966bbd33403f40538cea63d2ad15d5fbefb9979668c33f30c972a141403388a206c2db0cf19fe7d5bb775e307acd8b4eb2cedbe7694d6bdc56d928edaabc5dabc7da79bdea8fb4d2fe0d5c58a28cd493a7bb56cfa553bf1dd81a57da5690046f73b2da3f7a3bcda307a611add67ba51b8f04b9ba512b828befaf241d8cc797e5c4cb3ebff0405b9d2f446f7cafd2d518c19b3062881ac00679248a604bc4f7feefbfe600dc7d8f62dc4740efdfc70027c62b713dac6cc9e396c10e6b37eb1988d7d92886e0973ebb9c8cc61d7a7da89ef273c7607969ee4d8f49d706ddab4b195e6a68f42a561559b072cbdcc24cc9fef5b14dc753da514767fdbb6bad5eae0cc1d080b1cc59f2049c4fdac2ff6d46a7d73f73bf7fcd86ff6b9cce51acaea2f6da4397fec21449440e68f3baa3f28615b76bfbe33f1eeca9a5f5e6e47fa6c9bbd57ec09b29bb538a410bfe36b6ff7b7c7d4e3aaacbb6a99fdedeb6452dff7116cd372dbdfd76df58ef4d9dccbbc50d43d4175c666dfcf07619228ffec7e736baf75ab5d2e5babb4dea7e7acc6661e7e0745d49834dbfb3eff700204bd2f3ba0d2c097f9e8fa3bf7d51f77daead82ff2a3ee6dd697bb1bf7f6feacefdfe9edafcecfe9fbdee7eebdef5d7bf937ddbdebfa786edbf6485ce8bf415142ba9ff3ff1803e6f979168b6dddcfffe12eb75dfc713636abb7ded8ec9f009df4d8575e7fa5c91abdbee7c7a9f72f50cb94bcfe755f19ca12a3d7cf4c41ad02f85830205dcc21942546e0cfec7f5d1a757afcc2d07bcf3dbf9e87c1ba3b5d77d01aef7bbff7f3ffb6cafdccfe557a2d0a2b1bdcb20605d5c6fe79cc4519c39b8fdf96b1ed5560100b402547ba1eba9c471658ee602969bd2ed211047dfb21e667d69d0f7fceeb2e0491793fb7bd3f2ee49bf7d4eb3acfebbcce9b92a86e32fc7333b1ef9f93b9f187d508feedc96b7f8939d7f7957c63efbbcf7fe622fe4b7b7ad37f8c01f1df4fd7a357de5e7e9288e32a955523db738fb47d0d47285fb5b908c65daef5b1add4529713baeb7363de4e5d36fca4cffc4ffa383ed38d9956b3d2799f60da9452f167df1e637b661b561ab0dca9d1f98db5c89da87242ca2e722149eba54377da5cebf4af19c9d29ebae62217fdc56c51316b36a55fca88d932375be9f41eae710321dd02cb2ac64a90f9b9e3ce29a55dd7751ded3a988b414f200a75dbcf5ad84be8c7c5faf25f624cccd39cbbfecc56ead405ba58ab0c226b566a86dca8fe111772d0b6b1c3ecf1ab5f3f4912dce5234abd7c4429ce479466bc6d5c7e24b97328733d931cc402902eed51696357e7d1a58d75936f7be11c832949fd0ffb7da2b6bdd6c07cb1a75fdac5bb74a1944efaa4c7eec0d2ddf32f165de817b098569a26e8f2055017d8bc617bfa056bd33bdee77ebc746f5eaf745456570da7d432f9db1f3901de97db6545c227a3127d3752e48677055fdadcc9c0f8e5e3a01ec0b1eeca84993d6bd8b2b685cdde9ed2bff47195d6c68fe3ea9edea7f46fa6d8d3f9257e9c70ecc01275590abed2ddafc79c4fc9ad808293d9667faf74f78dc7292022bd2ac80c084686f3f6f38391e184b58c04c80d7f0952011846963ff63a1f46e7eb62ffc2b139e76c6dce16678cb1cd365b6bede36cbd4c11e369cb9a99293355b6179a1a8b93858333b6cab8cbd4d660cb5a97a68d7f83dd54fb22b5ede32f4fdb7e96b2edbbd850d176065bd69ab0b263574e4ca8d9221a34b52f5b1bcb28282d5adbd2aaad9da9b2367efba5ca6ae3cf531bbfc501bb954f63fc1e95876cfb3106f595181157a28a30c28b741f477491e222290f16aa2c4d4817916042d21f534b24b145521e25b4685142bac8c316aa2a2e5c7838420a4b961f9000337dca9503ebd20587892e436cce63ea60e4a4d585f3765b2d613739385ebcb876c8593e243e15745e1a61397d4ae743269da598c69ec467b502965cef3e6424dff9259e5f7e25745c3ada6aba16595db0776b5d39195655758343e501972e60c48409e3250c145658803dac26d0e36f70aa5cd92a1f59d88d0d9580b09d93f3312ddd0358cc9f90d3ab0be775b434b25ac26e7270c488717db9e9dec5e423d71739d6d13257a8048412d32ac87eba4c014b46b247802c64922ded9856c092512cacee817d9a91dcf390b9f3c7ea7477e96c9996ee8195696984a54b31abac2ed74b23aced0e02421de18804172b4c1f49a295489ffa405bee85015d01ceb0deb6cc0f98dde0389820128459920990463a0a582241152e545c3eef522527611d7212b631adf3ee057bd5e75f72093dfeffc3303ab8870ee33ae4e0841039e0f0441038388d0142d21f3fb2d30d5040f90d4e258a0f7e4c8192fe90e2ff6325488944f645fe2309b0c7cf5c421835324f20043541628045b2843051be00a1b5d63d5ee6231d5991bf0e424a22f4837fa8306af43f54086a663eaa40987f0da5ff5f66134230a343203e94edd0e37b7c91b761a4a3230b7bfcebab305162efc29291ce13a11f40219d90244aec75429912ae8d91ec9ba064239d5f3292329d708afc61d404fe1e8e75bb7b38e7f436f0fc9f17631fbb41412e607777f79d9e0f8c92b57b827e071673ecee2478232c71f1e8494a915b6badb5d6a7136d322961c0c5265068d4cc336e45e4c80ee0085e38ceb6f759793e9a735a2f66f3519d765aaf42d1683423b46a5a66b4d00819414b8d04729565b1f5e1c393d9291380df0c7b5b5a4ea0d6534c8b494b4a9bfb094d2c52f4979edda6d068b422d068010d19d0a04234021a3b481fb94751ab055a6a481eb9399e3d33cff65c25a536d3b96d8865e7b6e196e88564382bcee30e05e55330169ee246bc9f4ebe23d2cf0a8fd70eb97ebc9e8fd9a69d52f9c80b4f00facc50f4846ba7f62b1fe3ad61e546bc87e2a27b3e6545a1fa6ccc7d77737ee5629a315b5e6d5933c3873464368f2d6b66d036fe3e97669a3fb5008d3389ed3fb148b712f233ac7462dc6fe68f227948e83fa19b89fd8c94df4ece1bbf90fb201d402617b0ff5bda198e998100fec044cff3685704841446d8534f2bd1ed791b80ed26b0ddbdd5f94bb3c6a6d9b26730d93362c860db4f73c6a6d1923cd2676357d46bcf252490d7e77aeb0a7d0ae509a758a1e16e755535c6cc1ebfed55533e45737733cdd2c52d33aa064a1771b76409a766842556b5ed242c51c3850e5506e8fdfc51a281ca5ce8563e46cb6ace3d4e2d2b9f34b5340a75e70f77958e4a3292b5f6b367ea0e2561560922c58726564f0f0dcdd0dc02bb7704c31915f533f18e985f31900235e5d12268115e28a5cfe7539268066d012671d15fa96a2ef4e69c33045d7a4a398ffb7bd46ef2cd4d697b3fe73767c6a15bb9e86fe4542e84b587dcc87dbb4f8d0ee5504d5c68eec7e1940b4df7749369bd30888b6eed2cd2b6ce992409c544927c01bd1fe5d591efb116c5fc9ee7fd8b5380dedf1dd145ff3078c10f9ccd84c6ebc16658a0f75d6ce66fa3820459017aef85452e42d9f6c799883dabe9ecf909fa71d1843ddf5a1be4a2f778fa0407aa021ce7cf2817921a251a6b59b027d6533799f9d4389c9eae7b84fdcf1e61890b49179241c41fe95947326c596accd4d04c810ad22148871a1a5a2d055653413904e11054058a4a5a535881f3524bc194329acaa8613f399dc97232d85032d46c4a51b0b5291952bbfed46345e228a594ba4615b6529d61a5563018e953ca29ba65ad0c2c9bb3e252ce329c8ef02d6b655839ad428e81e5639469e26063bad96f62ac2579e49a2bc616c814932279e4185434d8755a33341a2d8b917c9b8f26077bf470b4ef36dcdcbe2f06bccfc43ecd48740d9540a1fa371bf1df68341acde650060b4b8951b5b777a92d6b669a6c186cb4dca37fe6be0ea003c1796c102d37b2db7eec8d4017b228a8b2ed1ff1a16a44b7fd98b51f34b3f63d23b9e1f6b022efffb9906ff14f6efb757ff5eb827800ef57ed142b5502c980f49164fd5bad24b25512552a89a84b22aff5c8927c77a79bc317c6188763936f672028c0b933d52f7757f8737bf5c35f6c02f1ffbeef8b4d21b319def63b0912e19a91e8c63fbb8ee7f7481f7fa035c0ed69917b7abc3edb9fd317e62edfb66cdba6e461918f1ede6aa5a5096c4d1318f6a7be54270a58eecc2fe809b41873e198c2fe0b8e31eb2485cd8d2bece984dbdcb67163fd5a819e00e757f965297db52bce34f92e8dd66ddbb6edb73265f6b66dffdb6f1fb4d5f6c664fc1932b15f5fd8c9acf4058fbbde538ee3386e72f3bfc97d94523ddf6afad16f7ebb8eb0c96d8ee3b86ddb40342ca5da7e2b18d097f6f69b06b29a77537d7bc0f3fd3fd8fc7009969503e36ad39f4d341a7403385429f79de6ae7451d226b00f89d79cbeb052466b8b6d6cbfea4274e24e02d938fab8bae4b63d92dc8ad89f219322f3392ebc2bec51896e2e8d335bd6d638eded83b6aca531b5a50bc9dfe8f66c48916c3849369e646cab4d60347db1820d5650c8c64e2551ec2deea4101d1248fd106c01942e521ffa55fa50243982520a906e7a933e4b9b7e5b248fac926324962423cbc82fce482934c217670c7153341acd8b9114b265b6954a81c1a80206141851604c491ec964fecc74d2bf014ba01580b2e6c5d31ec52dfa9549840f41ad54216c101b101cd4fda1f3c1eb013f7d3ce41d5c2478550175b891c30d0e384eb01b7050c919418e29311b74d4a043834cca6c861d32f088a147d3c3a05f085d0000d34e940c5ad060b2b0c1a4896d78daf0c1c68fad9e357cd4e0200640d8fcc0a0833402f002a035412ef0a005422c18521364051fa880080d215a209c21803403302304344529d865189141803322a0e0688c2162209921124611300af085913247bc40d285014e90544b6282037081802d124046490914a0c502b26040d6085834e00a0758f1002c12aa80001511984202574ba49840142540418131263c5101272c802f6085811032308051004c4068224403441bf88003414e1872228482071d085202f240009e74f0819f14808080835d1f87c067d7c722e881f243051fbbfe0f5e816749dcb40d360b1ab49041941d2600b810bea061f8a61e31f09061c70c3329321a746ad061436c4a8e11e450c17103cc0907879b1c6ee800567991c06577701ebeb99f640fd9872f88cab77fc0501e107606918fb60f62012cb28209f105d1bedeb007a80d38deda15185bd6b8300387a53976fd118888ef471275df105d6c15c5ba58b3ebf704a92f42ad9dc069d75aaf50fbf381da502fa4587ae5036aa3678b3d3f364a04da026908c406387f02bd001cc51e203640a02d10280d50ea88202a178b67a9486c53d7fb6c46a2405be07c117ce886d579b65742895a6dcb176c59db62cdc69e91eaa661934fa422901ae0267d828c00fdcca7e59b5abebb059979b5658d8c947de9508fbbf291e8a2ddf26d81f948144ba02575d8b2a605995d14debac45767be5214a108680d90fb1da02cc051dc3bd37ead61b5e18e0b855a8cd9b2a6c5963db5d8b2966566772085dc90407a7e2491c51bef0f943e1f13f9888614480d900958133954312e3a5d66db66a9171e55559f8f6c28f6c400b4f38bf82504b353c36860258af1e144cf131c8cf9812200510449217415640a222a40a86200584556185d21021643b2886451002d8e94c00064926c8180cb8512132ca036c2091cd00509d38b08dc324bbe28010c13c2b080190c88318ed104051b387302192894a1948227685230230469a09cb182160d8d1654c0b48217d434b14086164871410d6ba6bc804a1a4e30c8814d9518eca0c6d31a3e6c41b111441b56661b2268512c18d18254942c4c532e28f1c216189868a28aa18b0c5533809162458313358cb1218a295723a0820ad60d573865e1a0450e6474e0a24a8d045dec508607309eccf430860f677e28030a0d106982d012420556d40cd10222d65c49238a4d116a18b175c46c438a86a5852c4c48bc30d594840c4a48d152c396294b5061c2a9891ca8aa70d9a1cb93171faaa0be0401c64a1822aca2c418e184d41359c64c41a144145ba460e28a6a8a2e5454550106cbca0a27ae1883451459575950a1055609ae2093b585165c90e9e1221fd1508c02e93bad76e36ee7e12fbb5ee08d1b1c188e9cd90e1e3d5e8700d8c940830d441e1f3f7a7c3800f2d3410080823c101a0af201911010043080108ab6110144381a8244a448018c1c416280a42407404002942860010c18a1010e78000910888004964ca0040a9850010b5c000319189934d1c00638708213143aa0e481271f480104211001141556589ab4c9c26c6146994cd385f9c2846136cd18a60c73862965d2306b9836cc29730493cabc613a4d1c660e538759659260ee3079984fb387e9c3fc61424d206610538869650e3189985766d42c621a21e71153cae5c40719fb0b03f2024a3046a9ad3e04969f9535b140b3e79c73ca39017caf5b05a4eca854ecd8e992562a9a110000001a5315000020140c088542a1509806622ee61e14800d86963e6644950844511aa3389261209a310600838c013023403052c200e2bc81b72899f9e7cd6fd94238a6202ef7316b79e56720c2df59f15d4193a3422d7f5c7fd6e72626066d5eae68c65d70846f165ad570db2644f984d2952ba1f4e0e586ca8c371cdb51c4f6f77d77b6616085a60f624cdc9e0dc6c06f929dc35e5b3213203a3748268a061ebb5924a00487d941f763060fc7d3ba613e0a555fbdfda1f2bd8633d15304cf0398122568d56bd3afce8293915f1dd891b9ec8eb639b13be4a9e945db66fa17a9b3bf4a9e6e04f2513a4d8b368716b6dc3fe600652f29387dbfab43bdd8c698d5a0cb29b303139af69d500d30ed69d5a0f3cc21a78306b43c8ee9eb42301d35d3f111b422b84340c03e52ce0800c03c5d1aadd1103ca840326ad89d14edae928aeddf06655fd8054580bf7fab038a354b85f296a9918481b5f28945d9dffa1c53f85bee953d5863a4601803ee49643661d45e041ff98531af7f5613b878f8d5d52e46a3e2865da20efa1102ca37fa4a4e02cec7d743f9475a8dcaf0e14e6fc3f4db09727d561b4b742c5a77bbacda1dc07866b5a3a32988de12b71c0b9b48dea7830b802e65f00dfc029ad1f82f5f7feda24371e01287d1db568c0255e39d00983baff1e7269ddd2618c5bb79c63a748679a82debd2c32c79b9ac3f2f755277b8fc2ef4a11311a5c8c0e2a0560a138217524d607c503ee1a4d9efd70ebc87c96655eb7040837e8ab04919e28a6c623b96b0b54313a53227d26357a8ceeaf5450a9699dc13a7d5bc691205c0716e5b6d42ba580d86ab6232670c5c8d1a06b48a0d346af5be24a4a5440b3e93278e1f1e152bdd4b198efd6f8cf45f53ad1aa69852b31c8d4881c38146c0b028b21603bf50a8f0cab564026c09f369ab3b91f7c962e517675f7c23f9f3c44b7c1574b2e6799a58d462330eab8c25dc34d6a9b2698575f9c379eccea95d9b3ee5c14cc0d3cdc078604a30c0aa075d0437c5c5d3c50504ef991ea190c5ee833435e538c176c606662cd193aacf527ee6cf4fe6a2852ff3d025d218a75801f149a5484cc8d703f25ccad9a19947fc4b56dc33cc3a60a2d88f42a0375cb49e7f4a18c5f6ddcde3c3b479fb9832bbde149554f6f0d7f1ebe1f3a32f3d722c90bfbfc9f25d95ee9fae61af2f68cf847731a078558e5f9858d84fc4628356dab389a8d38cb6d26c8fcf6311a43a8de227c46dba0d34b1daec67cb564618ecf124c93eff025b4955a2d3f738915c805796a65e3a6e0a7674ea6a89bcadd5a89b3be58fcd3d69de8f59370814fb7ea02cf7f1b00694e9b31fd00fa807b07d10fa4e9f0447df7e93033f2e0c3c36365993e1da49fba9173ab4eec947c54e6f0f2c184d855e9725ebf48be60afa19053011a43a0353fabd99c4b43676c5610a55ea1bfa35ba5325a750f315f2f13d06b113cc67a18a0d547a5e85b02a76b01b1da7c35eb0871af840d7b91f2c60b1ad80e5351a6a526f49115c3726f0ede861559fc900d65b5c675bd0e548220760d68d41e127c26a11e1aebe2c9628fb2735676dcd6e423d1303d46a2bcee761964ca712c3ef1b58f8b12b74fe0bbec74da5bfeef9416f90c34d028ddbef4c31a6a5a39e36fc311c9eebc68dc756398c1eac625070c1acfa8dfbd737ca4d600064b03ade029a51040f6d7efb1ad94241d662dca5776f80f9518c19b154c8b064b1b87fba8981f0bb3ba40fae8dd4c0c74c2c4c2baccd0d9c97446dc773b4d53cc9a60d72383a78acc78d4e598702bccc80dbe41e740e60ade3c209b57cfcee2a097d661a57f9788353118e2e6944f81a68a750c34635025b81047c3433d011b8ff9a7c262bc1f383761486ba3ad7cee4ffbac6ce04e405e454e9695045dcf28706c5fbfca4e6948cf41cb353b9d15b983130a1099f708e693f8e1add347df153023559984cee54ec537866262c8088731f39283370ef6c9cc245d2e4962e3f09646518c53bc3b61188f5c044e45e91260fcf9b7bcf6a06df0c35902601bbbcba0a0da24986afbd612de7f7720a8f1cd5145cc91358ddb0bef95df19f6dbeb02135836a1f405dcf40b85aa04e6ed39e907429dec5e1cdea3472e0b7ce4938b47012a8d0755857b8e80ad7632944e3f322ab2a7fb2d56160a9f3e1358f8584c399bdd8b103afc04ef958704f4eb73bc1ff53b89a2782dcf13efebe0ca17bf3957df2466148761880605472c82f24f3dc662575c9cd8dca3e65f68a713321d67177de77e55a965644712ee600d17a760e724c5da0da5d41a5a799449b9370c1281ff496e14dde1f5069be1799405be2554b72d4bbb836a9e5b06d31288b464ebc5a6c76569269ff06e52a409b6dca59a37fc7a102e8d9c359d447614a880409e8c5fbc5bc7c3c46536103e650ea0e0856a5aac4a5e1147c4fa46e9d7eec862e76ba00664cd79aa1944130162196065f914d019bcd1b55a00d5cc12efc0b9f1fbcbf48790ecd4c5ac4ebe89ad469c4bfa4479794b8114f1cd04db45ac61aece26bd43c51e1156b4c3c499faecec5349a97c9304edb9dfb44140dce5b07cbdfa8abe5fa7869ff93e38e09b24467a9970d43c77e62aa7d2020c6e613183d3ef7a630ef56446851267735e272af9fee95ec17c39b3e589b41ae20461847f342c8d22c4b3c28d6d1d723fc7d3c8d9ea04365266ca2f6dfaeeba9662e91c82d820bc157e222f70abe265dd3ffbb236990c8fe485e66860d81286a402ab310950e700f60b1f4ac262a068b292b3e3cd558bdf128a0643590b7aee983c538408edc7dccb1a8ca5791f9ba5212e63b6a3bf20e51151184497ada94a07f3c1c2b4d0aaa2422b4217cc9e1cf4eaa66a8bce14cd267bd6e60d13fc343f221852206855b9f71d7428936b37c0411d29eb12fe61da96cc93b5c4bfee701c875e6e79f4629f42ed817c38abfdeb87d6be6d82af3a342e3ed8b8b341776b23c5edc42fc1e9db4de12ce3d93c9159b9f6b6465e2de74a3d496662d0290d8d11efbc0a94a7da4b84d63838671389ae4aee356740782152d9565383ea3a22288649ef2b7045c9c5db760e1b05e9e112b6e4b50e0e9fdfa2cb9e79d7204a42778af7b0fbc061e16aa4ff9ec28dd39a248feb2d4dea6e84cb2b5c6d4ce19a453c1dc51d0f40c3065f7913f7323b0e79122243e8f6e481cc5058fbcaad7960870327cf722ff0947a260c8e5f71c7f72670f7883d64ec1a2fc45c88a2199f84e7c5f49be80d1c1392259b7580268f38fd5c41b06d0202454ba5e79d5f6a98bb412b6639ff57020da4b30d7a13a7b6d0d86685a55a629e10c940a8d2f58ba06403abafdb4e916c697dec40ad9b6c2c336a1676e88c0f00c65e445ba53614c3064658fe9c261781e36afccd7884808c97604bf4d485be8198d19e4ba3134092864bcaeb80f29cea2973c17e3d7873cdbfe54dd48ad7d311002e1c9365e0cd7bf4ed9d94620f6528c4c2cf3ffdc6dc82efc1a9a1cedcdfc230ca473f5fd7e83ff62834d36791115cba1e96a503af9d223eeca1c8ecc9f830348963098ab221fadf84c87c2a765a45570d4eb50d7f830a65ba0157e842a23a8f9abd0c9ff73348735d3ec253cac949a5eb40bb5ff23568191d0b6c02e2c8925eb976758de3e6fa4d73c2b409207bdcd096c1f76a6e06b30217eb22772b8ad39bcc819f97f91124240384ed84733141a4b54d5b96759bc4e90b3312c9b7771e8f535c8986d931a0a9b365b101920956af64c19f449ebcd678bcc7efc5c73a92197f5fa5629227d883c4dced4b2fbeb1ae49de71f6d6ff052c815d537f8b087b7c6b0cdd914b88cc104a10355ac69f383d3e3d0456c1cb22d3772a750b3c1626011eb3e36191baf8de2c459599cefecf5b8602917f15c8eb3596847b2f1f21b58b882995023a5e7cac2767dce1a8f9f16a5e8d95b8445b4e949ba84932f0b90cd15320fe3d237dfde415e6ab17d9f3a3afce9cca17b5ae3a2e706c0faecee3750f125cc104d5e95a16e6e481da1c7991cb9127801e43c3e0ee0280ed0e1f276d93da76783df2d360f54962d0ca421812e4269b2fe3b43f86debc6bbda8179326201011e62e6fc5fcf48ff268e83ec4cb3b1f44a6e0e4080bb7c3f1bea8720e7620f45f87cf35dd3e05e80f357f87f70db8b095b917b38df685a4bcf59a4686a3847828a571d019550116edcb2812f7e9a04f0d6267380542503804048a6ffd2b3b8f38c05db4ae9f462cc0da1a963ba847b0df8a0ec775f7817b3a7a24b86c7e57a0a4a10f19d9509415089557846623d1ddf938e1d72242743114aeb0e4c23afe0d7e56638747dea688a4431ae834284bda82f6cd161c620f6601ddff296d6c65c4a754e09f5ca02692d57705c944924b46aef351ee4bc2ca11bfae85e06c1dc4c59de534875e42a14c499a5a0a55f213adf2d4da62c1ecd55e0dafe9756133e86b3a038b7ecd0db0f32de7e3af94eeaa0d57653adf62e131b085c757585a7e0ba0be4a25a8a23df3de50d8dad786cef7142b2bdfb24d4b1fc205f8a450ed9e858d3f8240ae1972f3e906080f852a926bf25315031950d0f1f66720499f819ff31da02143f676a5318a04470038a9f3288bbadff3e00e028cbc9d1077b201bbe547be96ff810736205ce889171f2749ad182003d0ec8c8ee2a82b395a41b3e8c90b48a937605a0d90a3b8da2b575fc5496e2a24e04a4c2c8465d34c99ee11cd1c7d09b3b97dc65837547222c25eb5f8a9b87095da22870a2b1475c9306f7134a6a23e3893a42f69249cbdd3bbf97a0cd4bda7b64a8366971b6c215494d6fb841a4116a5b0f9c7c68cea28f1c973ff276566544fef557c389b13846d89d572094f8786e6d19858c4538697eed2dd0be18d2a14d581137ac04a21fef59a7697c6b37abdfd733a1c7f5c9aaa69d9e1e69b6b494ed95eb4f1a577683e94b9bb1631cb101284c2c4c9bc394824e1c3354a8215b9f512147e5bfdb71c723a7bdc60361c0aa41c676d00a2b7db66e93eaedc344839167dcbae76bb076d3d1beb383d8b5406233dcf0439af87f7e86bd73d42bd63bd86d47c2c4d8c7eb223f86c400b902ec0d55e3bb744eb2fbf8da782a49dced8137292f34623b034ed6c9da3588b1ec938cdd0b894407e1f811d5ad5d61b84ce5f9bdb8007cfb3520d857f9765f62d8ddda5f0973a9ac2cd0839190a434e5a62c7f67ac52560c47e2a2edbe56f6fa0660d8652691841966f69d88856ac048b93d1538e7ec7012eaf9d3af9cb716a1c13c9e20caf1c152ca5465a1be6df36b7b05d738c009f4dd60d720526d772375b058457cee3825413b021663aeb17fcc26b7759bd0f5e1f1022d7623bc47ef50c86ea07c5453cc44fb9452deb8c11ee8c535b52901513b031101c28efa7dd0fe62b3d53bb53f5a6a8a5ed680413c2e722bea9e935c6919fdbda6d5ed4bb8f9b891f8f045b60e77ad31dce4fd096011ec46a95f1ac1b7ffcf39184ddd4eb7bd82e49af45c054f568bc5f22626c512e397ddc93e023e7379d357ceb73a6447e5bea952f206553409643bc7bf357ff60860de76e6716306127e580df9e25ce2524115b0681bb4344bbeb76f49f7591f842b1ed698e26b480a78f6f0c5eee593ff2c5a8114018755abcfc5bbd4f11b3bbc5590882f8cb82c1e00f614db9e6b7773ffe1cb84d0141a6fcb85cd7101dddf7c211de38c05756ebd0b378884e28de1ee3101cf398010e5242a1f2d58fc81bdb937e76c28689e0c743ed976bf23de71ecdd6f501adf74ba9f0e4f00849b98371b3e330e49506bc13a37c5520a5bd2c3b78c3c9ff1ece4dbb7c87c99747627de3941dd0703a1c4c19c42503ecf6edabe189d8f2ba864fa9f052585fbd82fb9d863217421de006d2200b4377880232a199ba26e7c39627cb2fd2d1e16c81f96a5b044878a8319ec215b2182df011612e3bd1e3283997fd82725181bad0e48d7867d3ebf3312aaffc0fc9b8d48f61afbb590b197aec69df6c0968b90ccd11e45a560ec4aec522bd5440cd43d3542b37aac71db94355e8cea6583edeb465684b99178d30e2de6de19a45224584c49cd531e2d81d3bb95fa97569d2a56c39e38c9e89e46ce130e6de1867ffb2e5803f9e8805734a705a699666bcc0ade326b6236cbbb056289950494bdd34f0d2f6ec26f88ad67fcfe028954557a8a939c42484bfde452b644e63fd2dd404e6e172088810431525f89f7026e8f6bea92a27af34032955d78060083fd40e44bdd507c011c4e68eecf9a13ff40031b102d60a7abee8bba18a57076e3a3c9b070b03a6ca59684ef810ade6638bde1bb863bedc0a1da6a8278429f41e138a6766ae122f2ff014864d3d6ca0e17293d5baf400209a633227e9e824737576cace05089c6daba68a0600d23d4d72150bcedd66a158e2265a85445830b5532a67e9f000436edf6ec90a8d2a3554a1426c38ffc750418dcdcb5db40c1241bab2a1181c2b5cc243f4740e146aba55d3856f2a44a050928486596fa3f0a0ed9b058b242e1a54eab6b910102148cc87f67d0d0ed55ab050444d2bcae8e142a58c948f07100176f5bdb5a8522250e2aead060c1744689df83c0c0369b9d351c5ae2b0b212092344638cbedfe67840e0551151c5ee111b76faca9262327e697e398f59fe54fe1c3e196f39bf24df2cef2cff0c9f14bf9ca7bcf0c709e9d4a03d30f960487ac69389d2e20bb9cb21c9f288f2346fc273096c34f946438d861c0d3f183e26cc20b47168e390034347c38f84180a371c6e18e260f8d8f0815046a18d430d861c0d7f53031fbb670c01afa0760822d2aae28824b125904b23b04874faa18979ddf2aa5a31b7b392c7c9f1d406517e9ed0ca538ed43bd82b53a4e23010c6a8ed15ac7b5abdd874b0f7fa61cf9a1d267dc9025c633c5bc5855a93cb13c5bfbed84e9eb51c0ebb85fcb41ba980c40457629d764d09904b9800ddd7060ec6456c11384b3c06f4d4da0a22b942d551fabc8ebb6e6d0e7accd4168b6449114bfcab358eecdb0c0ab0a07af0e4c840a755152d8af1cc9fa9fafbe5040d191a1d04ee13664c132867aa8f079898dcfdc1db1e0f60d73aa542b75e860d72dc2df1020cd8652a6ec310cb6f143ee63b848df5ab40b254ffad4f3f2a33bfe7d88a3251630d93be51cb333c6ddf6edcc6a862c3e450da8ac972a306e23b74e8275117fad9d89b00420337c66ad01104f15bbb5824a4c4c608089730b4b0c9228075365139fcd0b9bedb38704fe1c7c28c4e80c7965d3751e6746038fc9409cbf32ee2f0d3fc911bc6da33ed04c7155800a800929c3763ea3f2deed7fc97fdd3f61686b14126a995f074949e622156fca4c88333ae414d1fc8e6b0ef9b1cafa978458d0174dadb79805743ad61ae0c068c5d1e7ed40f1767232ed7067f004ea14575b20ca0f04898a73b789e1301394b4b8b91989f38d36f6bf33725890bae0ee3994de5f87c60b6b68fc5df2999055468af7e00003c1a99682e2bf05b6ebb6815f67f27ad51976159dadeb8de690cf2635a9f8ea67d5ce848369a1941e00e2f815c8bc6199effc3c70b000b79917fb1a6107ee7e2489cf11d8446fa1ce223eb29698b484d47468e3f4a828ef49aa9277dbde84b6f0fe436f6d95bffaf40d592a58887b1c746a660df8a694d0ac050a9d01e1b38cc73e80b7ddf7aceac7c0139de9883ffbd9ed2ed57bcd4b91e6f24b0aaf15a6dc124b271a91371c862575ab3fe10f4b963211fbe877e7f6f09ee3d72fc000ee43370d138c5291ed3cbe4c73a4c3acb9c75145916cc3fa9ae775443c177584949c0c7940090525dda2343e937ddda34467563d703d0e30d8ee3a0b80e8af320380c80d360dc0dc671a0b8078a7b001c0cc06d70dc06e274509c07c539001c06e16e304e03e33a50dc03e2a807556c9ee940dead9fbb2b051206fc950220a85b2067c91b5b9d3e5fd100384de83a9aab7509608f2a5b54479e5d0aa28d47495720adc03482d00844151c5da0b402290aa6100c45306ac1a805a4149032055cf4c2921dab2f2af9fb202f833649ceaa9749ff7cc89351331927b5c5e4bf3df264a899d079cd12a97f8fb655fd42c9ff4ff9996849e4a8ba90f4e7b3fcac9a9338525924f9eb915f56ed449dab2c90fcf4c88b5d6b52c7b58b257e3ce0c5d42ed969d565a9bf0f7860684aeeacbea8ecef87bc0c342438575f26fdf7515e468d091cd42d93fff6cc2ba306d20a26e0a4b298fcb7679e0c35933aaf5920f5ef313fa686099deb2e4bfcf2c41b5b534287b5cb657f1fe1c3a42dd1a1ea52b9ff0f79b36892e4a87291fcdfa7bc4cb51371525948faef395f46cd491dd72c90f8f5cc1f5b63a2ce5597b5b487f05f3957a2e2bc3a19115fb4094434904e5513d1fcc07e9720f58bce8c56f56f9a127a3291a103a03fa19da6a481d1b667639227cb94274dcc3f9200ff85a5f316e5e2547fc8d7a281cadb2300401732b8b42a4f28d77d30731258b5b647e1639700835fd4683b59d99a50a28e5a8506e2133753073b8654689ed0dddd208f95f159f51caf8abda5cbad49180528bb7ff87bbe4df882a563ce084310b3b3e8b85d559ae817785064dfb9bee6ccc6f8b3965aac4f4fc5ffacc6b04bf82d5949d24ddc836a211d4a8bc5d6aefb50f9e3c058a910a429a5a5ebee014e60014fd62ca0e52b2657c051464d83270715eff06f62ed35be36f0d5b534eea3217368f4729b88ab505f70d821e26754b37f5e46241b30bceed67a401bacd48bbb2eab814d5601040e0123b3b23009e84f17a6931a59688cc6b20992d08210e88561646c34968f540c471894321444c60e53ff66f4caee184ed262684ae6ecb0bb45065b056c77c16eba925a13b11b582562521fbf94873d619eeab70531e24714acbe94356bb91915c0fac77c542a93b488724726e8d6fd9e1a49216a7631e54a89d4da83b200cc283bd4d5731180441606e3d7cc53ed285a6580eb22dc7c814bb801b0fd45c3a95f1d881aeb23c85cbb818bdc1267304b300fa1931a04c97265ced83100e11f14e58db4695f7c1c8f9154906181106d9835d501370c59e9948660ad847152050f176cf790761b770943a707586b658461134187b090bc5d141d0b490e01861f715c3c5723190abfe31685d8a2f60e098b82359419927ef08cfa9f1257c6a0c43e8a1e375b7a460c2084c5c8c69dc9315dc12a80a46e5eafe083b67a826015c0ad9d26a66fbdbc2035ae9cfb0c37387c1bf329c9827c7fe557029b6add91d76d70298e123ac84fcb22ea6ba6504c237bb872d875024238bcaffdc7da628e79d15d5b93620315093de0b9da45ab349c77125e0025d3e14192ffdddbd78c992ea16c449f177c70db36dc6ba90540e44ad7eaec08310b4eed30a3df91ac1d72625c5f80f1e1c68b700c63bc1472d7fac01e7b1e1e1d5cab15ea14c89bb6cd3f14a7e2074df41270dad480aecb3004e0041681fd598297d441085ee3104b634c22c2b123744afdcc0a9ad4e782f5e7edf042f138c6300dbcac674bcb1c1aea0445cdeaf7af129f5d9a11807c3999068be878336933957d889cd239c8a5d84869ac317676cb639f552d1ce6872a78a7c307e1282e4992e07d906961041e389fb11d767dc59235451f269a33781a175ca40d697112c4019f772636dbc157e396be82e5d36fd8ce34036fc3d0e34f1824c5206f6c0f7ad4623cff513d1ae8b997598678ee300ce48f30125de34e5ec7ffe71ad0a71b18c0b9a297fe0db5c3cd66c66b940823ca88e66194138a1c93d979f5c5497187c71a826d3789886a1e6723ba6f82a1c11cef5893ca3373868fe4625ca454c482d179f2d961b4724a0d372e84e8e47f24c2c08c32569dc1d69dc6e9f000d4939d93d25b814ed3b709af4f6e2132bbf7ee55157c27c9fe51f9bc8efea51a8bb93aec27ab903a9c277db20b8dde60a96d149e539fade01961f450c9521889251468649401d99e3538308dcd721a4092ff426d1dc4e52de7a05a902bb29c8e6cb4c6b400021a4be749c5117954894d4bc38d041c2e025eb89df769703c125d2fb449d3b9f825e20e0aa4cf309d77cbdef8d99d7d19fc8be3c44b942beab9e2dacc55fe5dcaa3cbe4402a6a29edeb54433a59c542172be559018c2b2cb972441f3078cf0f478c4b3ea98573cc54b9d39c7a69c7eb702ecd5ba52deaa59da39c4e6db914b92693c743facf9cab8481815aac13f7d9bd8cd655aaccf55654a1fa188a5167a1dd5c7da4214970dd09aeb67dbd7e6de7b9cc72a491c6d0e443f655c03fb3c9d27585162f326ca2bea75cba0676aaa1f7ec5661c3f898862cba4d0d6390992b98d475930ad4219128f516753fdec5e9f9a3261f88c3d61bf3ad11795fa5fd8d13bbed798f3ae5c2ab572aade07c861c7199fe5de3e246d6bcd7685c0c16cbee624bf8358efe934a0443d68be4c9be174e730b76c0261e92439a23dd8562f1e627c3a72067ace8fcc744fa54ffcf29a98ac765225a52e5c64f165bd3171e2256103ea07815e661cafc66621aafb037eb88be171d14db7b3fbf322f2ee8ccfc69e40cbb637fa6716fa5601922da82231017411fd955f291ff5b60f9e6aca056464fbc8ec546f582eca333af1de4f8be44004f5f49e37d8629824a304d5736822ee25d045c4a955c42cc154e267ae73830f73fb4400ca8662a96ccac90561a3651a6c19723c0b279ec9a6b10c600ffcf6f08802846d2358ec19624f369fe6c05a4f5c5fc70f1737911e444edfa7d1b5c79630f852ee8593381c1572ce2a01bfcca1b7d0d2760d39e25463f917a216217f95b7480f8c35d1ea78fcf2399c6e8cc74486bd29a4ec7aab838a5ddd2da8336f85dcdca54ef3e71ad737f8dfb02eb7cd92a0bd07e861bf766d8a1f898cfba50b5f9da3215224b78b2073b94e35e81557b6a420bec86ac754f18d25a279c6753a20e9cd8619cec2ed7c5e40f3fd0d91deb6d5b695c30839cef5afe5694ee893bd8c98ee16837e97ff8619d76678fae59038a343171f6e48881a14ded0777b8f31d467ddf88c7396e3c28b5a0c0b1130bdd357d4f57543cd76bce087367ddb90aac059bb9f7ad5b7b7864e05dcc0912e5e001fbb32cdae097e6f7dcb8f8ee8abc40d111d336e43d78600ceb1654c6ee7a2cf58a117111ee599ca301f8e51d390694c6852b1258436e3b04ae4dc61b19948414e7769919d74d76466c0c5f085272b4aa891b5615a2158fe04e2c0bcf9947c9d22d1e7c08a194ed6516285f5b22743f1b3219755b0ef53d2de56c62207b9865319c97aa71205eb1a538f1dcc31bf7d1acd549d4316199187d44f553d8f4ca4b7b37148ab57383e182e2fb1df4660fc5acf0218b81df436baee404be7a864e0456bec5e71e16de4a6da1e46a3db9692ae2cf9b3eef3be15c99c5d270273c2822b3fa32a769a55b1bcfe708df6e2bbf6b0da0711953c2b8f0c994d21a2b6a0141ecbdb3461eed3f8ec0d40fc4cd5f41728ed510676822062f80bd7b6b3303fc0b6abd4c540ebe99cbf3cae1c2406285d4d2d8e196fe5e32471e96ad92874b4617179ffb70175fc3faab89ba677b890e7b54eecd7b940de69829815257d0d18828705ecee8d1e1ada10e3c4ca96662d1987e879c69c5fa8ea3006da4a3acc9ad13b02cf6c9d79cb094ebcfd47826f4afcdbc443e6cba6e952acd52dcb85b5600fed6f06139ad32680b4552d272c4125d6f87e8d6a10aa2aeaaf64b83f21fbd4bf11b7135ae9e7160db71e7c092d171641ddd2210aeb44428167e1b1f474942481d646991391424788d606f8a12e0544bb11edf5e53413b556bda8acf173b43bf0a80d59a65678c5435a356746fe56990bdab94a57142e3d472623cfc1d71cbf21da931ac49dc89c53c667928155a945770d529bf1335a7869bc80588461ff00e7a4b7be008851b841f29c2050a0aed7bf5d9c8fafcc42baa72df290ad1460150a57c55699ea612977c7200c375657df1acdef3a79437858f541ac3d2dc8702ede5f19fab9913ab0983f77e0f4c5c0a7842fe316c6094f303cc84707f018ddb5fab080a201762fe80d824b7be5e314a8c0d071e20ea23f470162765fb29875637c24725d919eb3c9748025432e9d100f2ff89c44757c2511ec3fa40c4f2fd3f78bb6640696eddff515e7e79e9d2d2877505dc676e6d8f20375f7c126739b5d634082f53d68a29b1929f526bf701604dca66981f86a23af3e80e264f2750cb38731807453791178c54456fca431418662feddc54784cc29a18e5942485f609b4250c7a439553b91b4ac79397712b8c711e8dce38a6243495c741de7f4ee93b3022dd89e03c0ea25463854c1c6c11c6be59a07833e5b21d671838420c4a23d66dec9149024d0425e86718e431fa799261669ae56685a03c2b8c21199162637f1effbd81bc538df7cb13e204bd2470b042f2371b542231ff1db1437efbb1a7fa53dcb25a1213c96ce28db42568cca10984c75a1a8a2c27176572da05cf340fff011c729c307ce33bfdbde6d7b315393024c2282bc5ddfc46814a12d41760ec5f96df39c397d54adac182132eb666aa8766ae9f5f680128105f99410ec7f50859c95f61c5269a63cde2bdd02361958a9f1ba3bbdbe5197113579560252aff0a57119a638af448cdc2ea2fb021e9dd586c6b519b73d974044fc3d1619f30f3e28219e3efa78f54990ab470b95bb035c8e01a31421766157e5544f5c5d615890fa801adda41379d19b1c5a3cfcbe92128644abd19ba41779d93590e6492c8e906e7454805da0cd2679ebeaff1f941a278e3e34cd2cb76918dd238faf48b943d4eec898381394a41bc3328518124d8b3a954089d52c9088ae045065fd54058d9bb60516b11a76bc3c24389a0901f80747210278c631e381384edd8308b1c7f866b30852a49bcb74411cf8fb2c1901729206b55b077c3ea573a6ff8d26e6c808942ccb88c0222670bf09b3df601cd76e344a20d53feb9f016cd34d9394412dabbd1bd7fecc750dc6aef2a40230633318f76019b0e4c89b19a17d3c63b6c73b3ef5532f67942bb4a7f4a16c16a0d60c412b311501d0d51f3a5a0aa8d692539b87eceaded56ac132e2acf2d9738ec4944a45a44c40a1eabf38f1149be81277190664186be65eb12f61b027c3d3a0c842819830d212ab7635d58c976c14784e414aae792c9d283302a792809b2c02e5db13afd385c2e6625d9f79c07d58f6093bb4653d3d0c172a51c94a49c0756a9b7f8533499bbf3384a925b153d06169c6e02367f8d4ae0facbc659ffd89c3121efbee219445f61dd6eb9a057e199a973627288034013206dafa58b194eaf76090d801219e77cd01df6a1504d5f57ea58d351fdc20be8a9ad5ed4f2ec78320c47a754ee8b0ec4617a3935ac5139d188a575c3fc01f52334350e14d65891dfdd2615658da107bb3b5ef1bd177828c96c3d9df2cafd10487eca304507ef4ef022b157d4e2830eaab088f6e5151082f7f43b0dd0a983acd8d50862058db0c3fcc3fd5863bd0c7578144eb4e42431c2c4d6f435ce6f795adf34e919c367bae02b5241a32e451c8db62e7a1bf275780b85e7e0b8996a0127d920398a6ba60531d7ce9cab62e3d12c634f9cfb015cf027364827718d40891abec56e83e4ab53bcdd1da577583d8b2ead4380bba0ac41e8b817b310254a56957e14255320257693c97fa87266b1aaca70d561cb397bfda00dee61f0488c7e01295375aec4fa7483532aef4c6d1750f8b9e2f56d1816fb3aa83ec428485c02f2170249b52d0c24bd5854c8e3a46a92ba5410b709952e55c3673f58c7d166d5bc0bd547b7a17347ae9cae38e121b5c4f182a955e286be5ffec09f1032a063c7d48784518df844fbf46cda52aa51b1591514f4ebc0e1260219c372d5cde8c922ef99c5e5f5517c62d73d0591626287b34cd1ccaa6d35159c0ffa71b16e8eb13bc702168e33ba90185c946dde7a7a08c46f79d731d7a6a0ed0c1201094fba0f53faa6155be8053aa1b1a7dd85cf350332a20084b86c4482af597e17e3b3920fa2cce942185c9721636dec466f6f93660b7464c04f861d9aeea848617792ccaa7c5d520381bb1d360d38b2794b1ec9d9aaa44094ca90593e9ef3d7f15a0601477cb852a8294bcd4545727e98fa5d138ecf05f86c6f8723e886985c09fa9d6491975fa00577529f7b638f6d086ad051e3d952f215771cb4abab01758fa1711ee5313270165c8bcfdeb63c8317068d9268b0f7f6f22108a16bf38f02697b873309489b038c6c40280bd28bbaca0486a5adfc90dd0017ae736ab3bb0bdd306b8003c5d413fa2809168bc451b6b61c310af53f64b4d784bb43c532ee440d5409dd25c0303b4402ed7b67b94231a0a1d86afbf5398a48a2c024ce33238e93e27673f3526e87da6bcf9a16e991ad0d13c81611f4d5650d77366a17de0462de76d02b82db9357e23171c269c65dc92b215864b5b4e91ae6dc23e0bc3643a90103b06dc0c450083bf1767b2875fa0c7c3e264b91e3a5c0bd58727282493c2f87f4af126172e39992b8c331ed13e5048e8fb5eaa3efb74032fdd44e007273274d3662ab306bf8bebdff987815ab0156a6b8ffa0da1c7b79b86ab5192fd30972b1c2032eb2db38e0809c44c76538fe910e1cd1a4e785ac2f5150a0752d584b2bc658c8a01b524d339a4bb970bb5b6ed43e82e986a1fc048f9e2a491cd9bf2c78b08ef486aa02e3917217c8b671c7b45f8c8ba700299ed127c3d0fd3f12e8bffb7c03647876fa96552241c9eed3782df6315a43252a4508f12aa200cc080c7e3bb8ee8b8810b8def132f395fecee287250f6e7bef415ff10119afcbfa5ecd1be4add1ab234fa30b8fe5350c13d8c17908e6728bae1ba4fc1e1c072b8c94f42faf58efbbee59de4348271fac73e1dfe7d33ee46b7558f5c4d20bdfbcee3e82cb9f1618256d501a2e0e9a81b8d8f0d23c04dd23fb6990c884b9e026590f985b7b1d31d6cad366394c811a88fc52544147dafcf8a57bc068a26949fc0c26256b9fbc7c3f503fb9630a2dad9f2802495eb3b898cdc6aaf95c1c70bf3630edd9352a5faafdd8b59c78aa4de6a33af874ed960854ddad1495940564679810673f13cae7f9cf014664823e5d40ffd8fd6cf0f053f6d4fdb8a938c9b6784c72659ae660422036e822910a608c50087de16aa0243f3dc44511681a42bb82d9d608ec7b59f56da555af749f0f1a524d28e4610ecc15817b9f4da8370a5f98e3ae3911b0fb12bb3fb1ceb70323214537a74f0516aa8726468636e5302645dbec7b1fbec1f0006efc71af65f4c54d84bcf7b33f71ce6e233ee13c743f169279595000270ca94757f4f6a3f2f23f636b40bd317fecec2243fbad6db8ffb3dbf1a50eaba69872cfe5c2e5eeb6c29d2637faaf37aed8639fd19f8bdc51f9b1341b98ea34baeab3ea9689dbb02572dcfefea6efe9eba3ef0eee9d9c569472deca19cd46b1040753a4967e43b4e22243ace19a2c03e5d295f28df8435253d1f85abfd4dee5736a31ab10e155d39544596d82845fdf3aff5fd01e9a4ab2d3409ee730406192df3f389f53caed958614f1bc1e69361012689030b0ee149b52d508ffd7c21905d1a9050709f6efb9c53884318428c4858d1e84bfd64d10f1d6a601f320ef75dc27ef429e329a90884e58fba1f772fc0f5dd7c210f3bbdc3ef860851841fb59f2a26d249150129dffdcc5ba1fefeb7fa19dbc64b08bd8e4410b456ddc15b59862bfec4086dcafdf66f97f538585d9ade8b4735f8d084c9407525491555cc01a798c5cfd8d6915b653ba1421e095f9f36d36d9d54314e842dfd3e4845af1670a8b444320281f2100b47dcaa3cd6e88f414716585a23bcabe91afdc83aa9fbb4d781f7cf4a93931f424cf7ad41d3f7e9f84dc064ce7c3166bee18272e8d234196161b5f3700aa67c2cfc2473bf94adbbaca9a9a5e6d2768fa521fb6359db929bbe74eff0906b27e5654c0aa82ce381dfde34df49cb993be982fd633df33106db0ea409144e9d8af5099a657d6b3e7048111ef0a8856b7028255e375a118f55aa4c75ac2b912aa09fc79e8ffc4b9ec686b2b7c5022ba457fb392bab81e9ddf9cf7919a07dbda1f41c7ee671f4473deafdcfb01f24ec3d74992d38883508a31784d8eb87d995275d791bf8975626935584fb06f55c3fee2769115e970228f6b7f4d1c2c6428dcdee9083a82026346f49d6f21927895cdea9b7d85d8e79b9e044d38d72e1c925f6e1fbd26958299c4a6c2c84205cbd03a12a57526383052cc0c24c90f62dbacbcad10c44de686572c99702552f8b1b37a9bee72d7f2e7e20aecdd73046c95c818e66b66e6142a8523679822434c4221076fb208746202dc2f8abe131d096881c366668de033d150164ad7ba27ba1a3ae5eaaff0b994323413ab7198536fcbedf2ff627a7cac37bc1dbf624eae3f8b7e8a8805fd163144ff5b9356c87e379eec00102d365a4fe0fbfa650f0708340a39020d484505342dddb0f04658d637f106642c9b55fc6f26933468a51049b8ad3e59b05efe56c047357d553e9f428c4b44dac044f4485e5c85f42a394f96532c795071021e297290f51724e578b741882ca0d4832bbb48dc05ff6f542601d9414462538b97e3df592c901a4e754b7e4a4a76ffce0dfefcff0d54264302a83fff43d7ebade7d0930f5fb038945503c360d5bc206ff5ee18369bc7aed8c57d61fd12f04fe29c8a97c14bf6b434223105a7694cc71d795c315f3e37c936a20cfcc42600d05e0d0b936a2059ad9eab76815c6a938bba286fad6544e463b660065cf476d600ff0fe641cf3e1ecab36ab16e30892ee8ad8c18a724b8759a7b718ce9ae94728b2dbdad7198cc3ad36fbd4eeb8a1855e14d989888b6d65b34be223d6a152d3757723621886ba44e380f2ed0082086126310996e82ec742d3d52045bea78dbd8dc37c5303e583f18db09f8e075586237f08860662393fadc0fbc06afc49a57ad50a7fe9e7e9163566ff93850b988f75a1294bc1ac150be1f449883bc9590124193dd51410463aa68afe6c1b29f4509c660f86e75dffdaacb14f93188543f695f0ac49d11380853c84e2edf88bd83ed99dbf9012f91cc251a6856a240eabc5755dedbb71e30b8330b80b41a75f5c40bbf3929b7e99a8ed341cbe01bc1f824b8c0701e3c00795581cfc9bfd4b5f7de1ce533f9e60106009a1748f5e1798be5b3184dfd696dbd4249aa32e6846cb5817da8280d221e03288b93eadeafd768f4b0d2055ef2a32369e6f71eff7d8db951a88c2c2cec294842de1a62ac0a7c47ecc6f3c1f9eb1337935dab2fbb8e63637cfe067084ef586d862e2e9a48d1875f9b2df6bafa846d10810d6ed6422d8e3f3115f8168597f6f0d7d87bdbc378da3db6a32d2d162502a0f9eb7940ddcf532ab6182f57b7bb62317ec7cdb6ec463d6c802747482951a55bb7842247aa2e847af215ea81e93afb1095cd315b839e4073a97f717f9cf251374fc192ac190390c66451f1769f2b895a63fe6092ef8f8d9ec09a793857ac85684e0df92bfcb6a8fe11b69577b23b0addf2a6cb3806c7c643397de7228e16ff1b4fd8f3722d5450027b652fc7c48804c71ed239b773c48b72594db00576172d345b50f287abc1284ab52c28b818b1906429594439344c6a4e5585a229b40c51565751d52e659851fa1ab908d237e649808be24ea99b0405e8ababe1ca765307343531db4187335b096c689d668d582edac7047878825840ed90a00f8ef6681dd759f34462992265498c09e99f4f779829895e02041b45d43b525bcafaf8e600b33fa5b4f8012059284992b1bc86bbbea350597a498abdf3effd8d31ec6852fed4bde085665bef81ba98b8c9777450c473bc033efc8b7ce275868d1ab9517abbd105b9fc2504db4476eb44203a62aa451ff06f8c38697b7aebc416cf000bf6c17772f27122ab280afed2a2a81705c7fa12754d087973f7011c960e17fa3cf4df062a10882a923b035edc3893ba9d2a77126bf1ceab18b383c53e3f0f81b0caca25b0ec797312a8d57c1e02c096979b40d9e7e625a06c2b6884f990776698c2a391b8cdc50feb8773050fe4c8112f919d25b08647e972010000d2fe751f230b254e1c2e7162f552154ee96b0376523227ae1e8f3602405e3d0d0769043b35c76ec6ca95d9603ba2f8214038b98df3d3e65502f5f3105616d003b5d22e35a91563fd0b2630fbe67a14cc1c130240859c88d04e194c0db43c3cf40cb408ac76ee7e8a761c38c3e90272c712d1254a4f63d9dbb663c7e4b6b7c6ddcb6f9b45779f73fb6b748f1469e55c5970d2fe586e601b75ade37bebdae9fcde5cff7ed381d00f5c40d1876f5b79051c8c040aca7324ca7f83646348f48c1467e15e19cc18e4470b2d4c73acf9d72df9e3c8a329b951ae0da3d18da13229b40197f94e5aac6a58577c7be682b66955bec4f2a0c8aafa07ddf5efcdaec4f35b793f17395d791780630cbf6fc5fa5f1e5d15bc7baacac304111f49b7140c2733d98166e027945feae464ab690e085f60a729981d8975e68c2cdd5fec1ce27e964d8f7b5d8a0a9c23eea98c323b36287297a4bf55a67a6133a7ca4dfdb90d42c8b54f4a11f9e76e4ab3858a3828a9bddcdaa6552b7b64c2571f0464898c05b6f6a6b747b4b5d30c156e5abf1f956f6e26914dd8bb0f42cd8eee6e9c5f2529d9e66e27a61d76e4e0ed4a0196af29ba99ea099453bc7f5529d9676ed2d80f9f270feffd5505c99fbb99627f78de6cf8f75728257fd66d9ab2878c7c52597a1a983d52152027aabe702fde2ee6f65f1539d9e5b7a5da9dabb705fe7c1489e497bf9568f7bc9e1d78f3a12493bffc4652fb734d6fd84d8111480a44b634bf8ba2c214061c08c5c742db378beef95f8636321fff326c5b249c0efa1d682aff0dd9557bfac0739484e920afce4f1c01147ed3661e81413e6fc4c584a48ac7a54e9d3986cc87a81ae2edde442ea4632ce4d0a6929f5856a6af0f56eddbcd9ae7b8a668129fcd2d23a8c9bb8da47d4d16ae987099d1db582c719f7b87d02c20cce0ee19113ca715f61e08b0510174da6ff7d18f7956db67fb09d7987d98aad837189535117a8637c0f15d67009477cb2d6ae2c22e01c5d8d892456885dd30340a6e327eb032138d28c4fe50594e6c164419638a1d74c7afd4132b53b7772d8aeea61ce16159c97867544ace78921f2c1b4168eaf6506cd9514622a98eed4cd1ed3ad55269e2382448742a1c7689484db4c011bafad42dc73d5d64f93b03097a0f9ae5886745283616a789d2f1e5e0d88852acdfbb920581066f3d28336ba02dd0887a79f7ef3b2b055f9bada3d5541f4fc7cb708a3a03c112c19c3d868c525e4f9063946c7740195b7ca0a9e2ae213f4d720ea413b9a2e3abaae397ab33b06eaace27b8c3dadd840cbf5ba6d0dd6f19d8038ca627b818e3fb9d4951c7da80ed3bcfbc046910776a87389f367dfcf3b24c0c1312f8973796161ee298349cd2b5175cbab35f89372a179fcf2ddb5b798ec6f9f4f6f389c69bfe99a1394020321916afa277a4bbdf2486bd5f17d1babf8ebd59f4f2f8bb3bb7719ea973d7edf2d2871e6c796df39fa54cb17aa50e6a6872d4eb9eb8e8e8be4a6a24cb590ab1c5e517c7b18625a022387c174d22b6618d2f582ba289852224c9fc69cc3207ea084305cf5fc1301672e2a809331fa7ffefa04523a67b6fc43bd5b5319c4ec73d10cf44d74efacaf7939a755aa0a683a1aa5db6388174242194fb1bbc69602a23241a81cfe574e5245c556dbda03eac64ece5a5b19461225e3006aed1ea401a5137c07a50dde03604889e7880917cd7da84df57e9e70b41bb5e2007180489fa8bc11e4e8b5a407ed72ae35b15c0df7a0d3ae4a532504696e61ba6f9bda0166b2b8d7fa49ffdde1eb2414418e1df07ccba396dca45d2cbdcfd72ad66c54a74f9c87d2d8fa006e1ced177a8645d7b171f1ca95789a8413de4536d70eaa16e1c24cb1066947de1f78a3bc61c60d43a7a259c3fac2c88ab1acd05b89d7fca604c872103ecd34120ade48c48ec79b0f934c07e47b167163ea3fd0e07587cac6b01923b6ab899ad380eef4221f5b25d736e590f5e6754b8b526b3f3ef2b0f3607c3ff3e8ce465b32826e98132d27039d605b9dd9879f12692667763020c2f10976797c03cd1d17181f0966b1c28e3ecc02f21e5ed3c5e06af70544eb89ad87c4528a41142a17f64ebf53c37b596fe249c24c05a82089e627cfd0833878b6f0e42fa30154bad0e8e60d86d917d300a5813967eede2371672ac81cecfedb7fb665c905b4b82749747712bae563e28afd0464039f17d232d581f42e2d11d6fbb86ecd97e88c260eed466455c4509de45dff9b147036af7b13f2224bc02d95d399279ac5e4be329a76bbef6cacf5135988aad925d772a87af5a5edb0b4afd1250ad4a816ddfb33502d620cdf8af3cd12a99bac594440b70b449a5b9cc554a2d346a41f6a66573fddd506da59e98bcda9f3fdfa4f2e0f296c427f320db8f664ea695a14c39e5fc6ac557e06d6082f62a185e6b46028ca4cd3f5bfead0113ad9f5c5481ded52605c2253d698e7ea006260b63377ee59c46c62e8f88c628c09bbe306fe2d2976946b51eb0c6ff710e08db721f1385101ac163a52149970892eea45666c2b78e941ee62e6ec7d9a66828620fe053b607fb5bb1634a5f4a4890028f14b8b4a835ab988ccf2159ce73eea956810dcc221f6c88cc43f70a8724c6c748123940cc95ab39ad8d70e47aed04d303f3aa07d87e83528fa008618157ef03ed4dda648927cadc3ffcc0407f0b8d66c80a6b158115659586a5ecd8b68814d246a9fc4674578da4299a27eb1618fdb5bd4fa8d188b84116d4e4df7fad24b449f54d696d5344f4a3230b29e5db91af1d6f2c6249146b1f09444db30add7f8e88ff56644e834ca313fb7cb25e394af3bce32102f9b362d73aaa16b9cde0d4357fb3e5161b0b4ddb7c351321cd904626be85561da98739397c20339ec68289b5e58b83e0467a0ce8274e6832eb6e43650224bd2974c867ffcdad9440dca8c9fddbdc8b6fa701206ae776e55908691fd9a2842dd136482f139e1afbb4272de27e0b43df896a91e0fe088a4b2c2bf2d33f11c30c68a19e196cd3ca97331f742249d85e8a35c6fd685f28fa3e66afd842b8e28865035e071ccb12f62dc7100ef6952c032e1178f8db0958fd2b20fdce1b5ad8c04d65410646bef32a6b28709b5d8166e302a6e8e97742e158b353e26b0e9adbd6c951dcb4fb6155f24c93f5eccc17bf9f20b8751ada81f2a1d1d2489ecc3df5e8bb7c288fe91be75c22d6a4b3a33e58de84f6bc2b01a637ce05cab51484ed0a166bb966cc23ee56431200c1366c6f18f4fe64a382aeb357c7c9647c9340881f6ad95b6fb66b2d3e33e6aa1cb041ae66024b0e03ddb2fb17ee287be38977d06c267ec9025bdfcf9f347bb9f1490f2e492c788910cbf486a1b5e87588f4b4d6c31d4f140be49b8d0c689adb3adee6a82e89e9ef1b96b3f00a4b3a81853186140aa4cc599f61882a55ab5cb8a818acdb15a6e6037f85eb0e411a85ab26da0f994c16cacddbd562dd5d9c11b64e4d297bc70cf39c32c39e5b514f30442637573a2d5d5bc45367c74168e68a45e830e72b165b66288af8e19d8d2465a255c7d966a3ae693f92f20269ca9588f4ee5130c91f8cdb4820d022723fdf40b916b2d1c06cb2c9e5708e35c726644dfa2b90fe627befbb83b30a5a59cb119d0f6c663d7b69dd5b4bc410ce5050685387610f70f983f45e5d798b0bc6845c6998d7888412bf77967103b6e399099a41feb3166a887ae81b1b4e649341c9c9e51e6d6108f3bf4ad754598ff0821673ab777f87f861a09fa27096be0ee0b098377719fc331bce8bbfe02ce9e2b24f767a56402ab456f37e0eb21200575135286288306fc9c9dd4a10ef4572a7341cbeabf61d6d5ddaad70eaee1a23ddab6ef09b9c4d1d61807439282c37c798894d692030d16e8e98b9e18e1e7a28934b626b4d105e22494ce31642600badea37e18b34866762aba2c8f9dc4fe641e99c38045b426d6e40069e08378cdeb32d0abdaebe8851edf018661d4da4f875ba70ca42fe75b48cb0edb58e8952f04d181e5ab76413902e26fd1a0e93382b2e87da7a5aa1402b94ff02cc9d2a13d9b56b65feca5f5089f74598e42a5c91869c25c432482fa14f218b60dabb5f6bdfdec9cd2df10ad2d750570728fb69e244ef6734612630215ea29ae0c0c02fade927e1fd177ef2892b95fe9392f13550ffbe0900c9a6f7603e2cb13a77f70ff1fed2b4f4efc2396cd00dcc344809d8ac9f9b0517ef6b9839430a1ee307687a72e588fe8a926da4aff893636ca2f8ce337c7d8e30e7650868e440edf1e1f1a0efa9810626673372228e1350ab8179ea03b9dfa94e9995b3a3b6121418b48af77a4a2c087fdb014601fe736ae80cc26181978f466b221967a2ef7351d60c7878c2353c8141a334c6ce5521b8efc5a15a0b31e6152ebffb9dd5c3ccd35a5cc4d39979a1db6c296a171b5b4e62a97000ddc05b6f0a9e93590eaf8d09468900a2ba254a9dc21c8579009c427b485e96406b5ec6fe0d6ddfb165eb5714aa64036b1d99db667b8e66cc196c64827d6d967c693c8bbc9015506b93b05b317b76ec0eed64889fe618dff9aecbda004915fe88aaea7d9d06b4332245e660571f4fafe4da9e84d32261fb3216bf3c2355cfb18396844186eb8f55827beec0530812aed470a68ab3fb9442c9de9ef26b3a405bca2ad5e97e4c352b7c45cb32e890c498bf870804eb68013a0b3f8ee810182e4aad47d1d65213d3ce905b966726392ce69cf3955198bada521cdaebd77d538b3659c6ea428ad96d7918ee5b3d5d50289add16e33378f28653fda087a527ea08f33e8a1b7241a3e6770f32f0d9b78acab098f8eb1529553e88daa6d68619337a1651ec27659ee9683f36db19086fa73be70e04548dc10235a9a29c18c0fa48d6c04dc134dede492089619a1824587f1a9526397483f13dabae8f2b2172496b66b8c5d860d940216313742f3323a9e3c2b03e59dc7ececc679c49407b056d6c3806b9c8d0123913b6510314e2de36953533ae63e76a5d125dea4da9c5ea42e9f3a9e25599d958b216c2e035c13f04b2615aec119dcc812a6b17b1ee4a09340acb1846521fd056a366c9c7479b49c20731f886a56c21e0796d5ee19c0b6fa4547fd89d653c56e0cb5f3847c15b29eb1b9863982d94f20be6507823e57ac3e92ce3b10a5f7ec1398200a1fb9555ac267625f9868d5301a25969ae2a8d7bc0d19fe4fd8730eb7923e30c06e8fdacf345d2bfda7adfa2cc5652b8abd4ebef29f3190c3dc36f85248876fa2a3387d544ff00f64df30534458aa0381028baed1fda57a5b329ca2a628a30e834c6f7e09af9178fa2eaf2fc49348dceed4f9417717142be3019b94ea5a9feb4b2fd082e1b099ff8350ccbb653b8370b4f9ea4ee77cc01a4c9f4894453645944cd7019e4a09f647024e843e4a0a06f1b639470944c3e9b5f40e28d49858f86dbebc5db762bea9cddafd721e09b8762adbc1eb3f8c0a026636e1a943874b406804ade017f5fdf223a380321c5c98618afb3364209ac28d130f3471e4628473d2a3b23d6329bb11e2da10068d1d6b8374bb4f740919bd7f8cfd9a5de82236dd38332acc8e4440060f8be477c7209a745a4665b8bef4d7629a18813d7bd24ccbfdfed9021c0227a7fd1ac755f644eb46bc9d18c8ee4da56d2abe4595594fa68b54c09502fd59694cbae8dd33929c59fdf546020954bc63472b26f28b3e321f306c91256be8c02a7cdc99e6b43d350fba9d24df29fc8a93030f93776590a357e49ae0d03c40b5b300e499e90a2022cb8743b9e1af3834b633c8a8a6320f34c5d9ca0f298c6a17738ced89c2b4a1f826a3aaa76fa93303a979e3a04250ae7b211cebd2d262fb4533c9796e56d465e1a4f4db56c5027ecfec255186b21e31b5991f615164b2682d3706374d6caa680423159fea37b475770ec849f0534c9a516922989aeb2e61bd965ded4210a2b28e7fb21effe49ce119ac426e181f848bdf3fe6435bcdc9e114b5a6b0576adc87231ba064798107f311f292bde1e62a0f6f79c33bc7f61820d90d4ad01180a4a7096845f8ac9c2dd09c941843688cf3168216ea6e8bd5203853f6f6b5ab978950258a90b2bcb637ec844f9a96e8a0dc4ef08e4327e8e4a2ad493e1560b2da6645792b08d316c40bcc6c2f442727679eefd8fad4c19adb18e07f8bd74537f06eb9530330c0b992a91e0d95d6e62b3acb7be3bfa50ea9d83ad1d00a0b594a813ff505bd01f55381d386c0978bc589cb4028ecd7519d91144ca24b8b7e699c1975c5d5193d697d0240a382051943c2444c1a58f5867f364cb8927cb55b475c0b445f5a91821b175456bad3a4617d4e09ef4ef756179c06611eb4a49cdc0be82efeaaf5cdac56d3fa1507e285155d40212bb8af207b8bfde03c4956795f187a121cef8ae1750865d4e1914bca7710927441933b80c68ed16cd388ec6c8a132573a1e7abba1ad17e9f8e0b9a93c9d0db11e406c7983c42e394f500bf20bed9cfd781c9d57c2c3798e3b52722cbf8e603cd82c3b7692b38a02f42ad838a2ff4930ca63b8fe6cf37a56d9a48cf5878d23ef04178bab4855283bde449035a147bb4d108017f1aa777a72272aa7c389fc5a8db2170fd820991d8c5070834a17edf818828da398614b6aafc9629e4bab6c1547801a826dccdb79f78ccb38b8eec2872c5bc7d6a605831bb2c67a8c8052a65a19eb049a63606a0c1c2b67d82685dbfa6a9b3ab76dacf611c516c62fcfc8de84825393a6fb9d42ee38d3fc3c53da8557769b5a98c6528c5a50146c61927cea5089e9c4d596b529e74ddd3d9d13cee41bb744db68b753894486c6e897c2f5392e1baf303290dd72a530b8192e81c25c66cce8dbb0ba02196aa855bab08ab92774f8cd8262c3ef04f0658f30bf56142999d0f75997a18ace4045b5d1da4716ec64a3b3725e6f252df4134b2e405b41a06d669ed010ad204fb250861bddffe08f5486b81a01d95c5e5dbb01022cb05094c981e73a5c9cb5e9e5d03df8d04cb8129731b93d2f8f9916c2303010f75a17ba8f54f05a5508380159ff21084cda8d5a55e31ea9784b666c4306e82a2115d19f9ed4979b3774acdabfe5bfaff6874fb5c24e1053b42fd5892a9bffe38901c8ba110319cfeb39a03f769b67aca52710c7e88fd75738e1aaf37ed04b2b295326cadaaa6dd7346e530ef88484b1bfa769f50f672b3b82feb7d11e946dbe8c050cecb6ae9a441109ea7a17d7ca98694e4dd149d1e292123e28b31055f320b0f4dfee8d3e6e2520ff74245e1bfe344a8572c5b441172adc12a8f33b393bbeed8c0f7e2624b7bcb57b7da8a1f53fa5c8b599b7778bd85cb1e8b90cd987dea1170cc900c5e80db6450acc99fa4c8dabb159ceb1140a9d2aac75468400494e733781294e93b38273324cd3b9260fc7053b0ba0c69d78bcd230467a9a4ddc5d8c06fba4da7e2e4983aa63498dd69c04a5feae7a93f49c8d25bf83205a2cc0f6fd176d32656387f33c83ce4eb96275f532358b9e45bf6a2acfedf0147f17a310f8549884c3bfd80eb110ec8ce29cf41272220fe960ab2c59964e20a9d4d2186b84b560c40167412c0758c51503fd0ba4d3ba864308742c75b05dde782ef0432ac37deb30add13dac0e6021705649e82d2cb8e30b23143e04c643738ac69cdda9cd97f97cbfa1baae2b076fa78c3feccd32962bf2e9339893301b94f30fcb31e8564afb067320608bf2fc177624ceb4a1612a5ded969beec3a10e00b84db5e8180579da0abd40dcace57de8705d80ba468a365ccd0c8c29397fc10a58101a0b71c4524f8b8e3e85d6b720f08507542e71b29501daea58601a2f8c9ec9971cd07b38a18f3f2bc68ff3b6c7532d9de6c7f0a9a69637f427f2bc787eb8ff8e40bdcc9bf18ffad53c3610eba57a53abff388e44db0da5db1367e80b51fa709a30323c59ea2e0c4bb7c9b4ab335648d49149bd827e9d13fc7e7c977aa5d38dda79c7b9cb600b73a115989527e80f9759ce975fc544bf5e00454763bdbe29c47f2cc7d97aea1368fc44c3fc5d9115372a9e429188985911900c62fb737308e290cec5dd26a37a1d835414d620f17167cd15dfaf2c91c911c26ad604b79195bf96023745a32cff21320bd862cd4b8c382d92e8d1bd75e120e27905ee7e1748507fcd0fbd8cc395c46c641b4d881e1f4a7380a7abf9d64528ae1a0bd256c41baab928948172a5ea1c50862454782b70148575c771211488a495cc77e969c74b4fafe09711c6d7093ce993e1f190e4bc9645352354517c34bb8ca173ac6b7f6e91e56d16a6ed899020cdf524af9e93dd333b09b48764117ce6eceafa4a4a13702538e667f8b04e99ada0b28902161f7edd4f0aa5d13be4582d4553e409425091f71ea261820277a1f3b392e5e7bf6857c3aa5ea07f07211f31203572012654e67223fa74f351475492bfca950119b4a456c0d9a1938a56181b6ece68e5cb76509a6ff4ed0a797d5b55536190baab4be3855014104c153e9e00853e9596f5ba6e5fb6ba70a6c279c878609faa35744aeffcbcf3e230a0f945db6c042ca2fec4ba59e8eec387f50a2dbf815c10a229a659ec44b63b3f866979d17c97ab4d34f0e0819ae8f627bc5a03216b7c1738513aa8905c8763ea82fbf15d4a5b2e543a30a913880f81702e8531d9f9e3ba5a2aba1cc21a1c1201f9d6d3984d6be271c24567791a708e638ec311843eaa32c93b89971d0d0b948d414899597b7808d7360cd5cb10eac20be53dbfa3ccf25984240e3accb26ef25a55a3d4e40f3f83b978032d24a33a07c78b3973eb5ef8f7dd4c63c8c2fcad8cead4589323cdd8ad1a4ad21b9d087f2154d954c959ecf3377c2e3665e9adda15310fc12d38cc20ab519f7ae365737c9b6ff33512a8f82efc44c9b171fa73f85b7fb8241ad0a4d545ddd6c606cc18e70006e4e0d534815d66d7911161ba83add966935550e28e1d90841a64e494143b21c5ce32e664a6487df95e2aa585fc49fb8cbf32bc2be86957dab37855bd4c99c45799a1d39835a02f47f4bacb97aaaf3b6f19a82816636a2f80bea9e4942e18087dbe52b1ec92a8663ac03fecb21747e7b0d2da2cd2e9bb80cb5840c0c27960a577b4cda60e26dd948b112d9ae621d81e2a56f2ee0aae1a270146179a6593246baad2a5fe5e65d097c66e90eef4d04a0c2d4ca9f9863fcc54ac54d9065758b8609d958a5c65b7c49de60847170dce5e6edd6590943189853c27ea5d049dbb21ec523d46b5e0d14a8f528ad5c750ce35580c3a61b4a6e6ff68a6326b9677178fa67e84c60b15998e339492af73ad20005e7b0ae34c7e1146d7ecaf4874fa4ce97dc541b26aac593349b32020a8a6ba57cf188cf3607170fb101f86b4310221b0c20fa290d96e9c42111a286e64490c3dc45f2e8dbe1546fbede92aef6a51fa8480fa5b21785f59571792ad4ca1d7d6b3a5117b5d20c728eaf627ddaa20034a08e10b6538096375847427270c646ea1a797ba6880a0dd36e5d00490904022f9643f0b8fffb53ccc01546726428cfde3313d66d98468914c75230fbd51e112d38cc50a9c27e6da079282c651f9eacf760e907890bdd26f1838a9ff1915e20fd357008b0a166934f8bc4f83d8a71adb96ec23c6c25d95812a28d8924e8981bad767ae1e8e426b1af0dcb24f7306a4617d25598e3188525caef981c4dada077696cb01c2d3edaa301844ab3270a22addf3e2a8b59927e4faefe39db21ea7c8973ffe32ccfa1a245895c513d8f329c6a5d25126eb5e215f53554dfe601e5032be3b7725907d4c5b9c3f96995f5970c6a2e353535626c7e3d8a60b4f402efd6183a6cfee9139c5724dfe2bd5ea8e9c70c3f52b2e91a23bdea04675ff7651c06dcbc35215e797ab1df2a2ca2fd6f0c9aeed0700e01f5127578e807c3cc1d86d41f79f355b0124e0af71bef97f0b3bcbd17cb0078548b656eee0daa5cff37c2298827e7bd3b03050798b2517c069ca36c644bc8816d6c0875cab47d22fe0549d0bf73fbcfc44bd25020e502b0ddd32022b065a4b9f7daf4964d86fd3d8584856001e6c480cbb400da434149dc4b71f6b0d234497f840b36ff8c1da4638dad478c276773ce83ec65074ffc67ff750c22b449b4c40c040cb4966e9b7e56456f3cff643a34a5a135f976773473fdc3e159be69b6b87604e4b04385f9abe759f4b7a5859ce79531880c78018b2277113bec70c9fe48693c53402fd4c1f8cb8c4a371be0c46cb074312b2a2a1f601608bc56d09eb47c9010a0a0cb428865d521ff17b757f6c9cd25a9cfb45d0cd751bb4676869d73aced65105a1606d94c9a023731cf4c01e9dcfabb27a059678697ffd48937d81dad0bfa98590f977e248b39d17aa2d9fe2edabf51a2283f8583e17bdb0241992fd89a9007f0a923ba675320a45daf41b7f0b00490a107115362aa16f1570b9acefbf06645a41a3c868774c7249ae2b77375c94e93c4e9e2a29d8c43a60af2c1f9c3dfaa6a234810105db3795ede906dfc19b7e9eda26225e4eed4ce43e4a4e9d41a71556f507e78b55c018e6a6f6997e3051bda142d4b84c530181b93920c8e258c4991ecbcc8a06eea8408184b88def4fa906f59d2eb096532ea2ebac20f4cb79efae1814086516486f35de3459bdeeb8d9f12d5e95a08141638128c063bf2b52d044773427ef5dee7360270b4a507bc36d37c8881f5a86a99ef3abf51594c9bf079f391b59464c96fc2319644a5be30a1c009ef9c205b86ac903c21c11092daba944714d2d997e18b2e3502a7ab56661501c4c7cd210678f42b6ede3005168d7b55e3c5e70f8770243f9e1be5d489b090f30d9ec24a31eba25be0524fbe5b710306395bb930f7bd3ed4ffe0e2091be6495f4a40ed063bb2bd5ee21193836273069e9a82744d37c9d31b8d10d8b9eb4a87d6c8fa47b579519c85ff500f592df69984d18d40ecf2f3f593aae4493f84c69a6f3b6f09b4dd9bd81d3ea079234cf6ee08281cb5de7c6f1894c5d35757d598535a83aacf5352f447abbb3ed109c1d3601122ef7f6645bee1426f1039aebefb95737363a73999fde4655c093872dbfded9053c4752d364edc0a1be8668eb8278004bc7fada6e1f286512219da67f59ed3ee4562b4c3ebeed8c9522d32db4374feeb79dd4f9256ab82104d13267d0d5d05ad4de6c21ee569f310158116189063624cf8f687d8da80d2ecfcfb5390b568c8c941bd30b7d7d2ff1899dd792138601cda5046576ce52b82a112745d6948cde41b28493571dfa3b80b0ee792468bb009e9c9ea63c6397242dce0cc71666c25deb59e1b4f54ce16c610670b4343bdc5acf74c36b32b5a06f3e6cee020e8493935cadda985761b567cd3b394ea7a1a9c19cc3aa64fcf817e4a96a7e7fb7c81f6860d66213cf187bbc0dcacb429deaafdda5757a41d02918a122103cdaf0aa6558747fa4c32569e9ae490913828e10be0e6956c7e1317ce5e46823f9df6f275cd7eddde90f2ecc1ad638928e4f89d38beb5d7d661d44c3c099e22d5af1cacb7ceb3584d095a579600af5e8dcbe0aa166024377debdf49cc82cf03e2b786fea659f3e58c55917ee649b530caa93a3dc78abca9d0addce4d17bf5e74f34719e58249863107751ab8e964acc9749b568e811bc71c35d67faa6e904397d57165c09491ffb2bc69f7394c9923e83cd39337bdf7982a9ddb7adc1603da7e9b98b91a98d0f960386fe0dee54f01f3e8ff3af17690af8728ecbf7ad2baff247d9c9cedb8e07e04c7663cebdfe8f47d200e119d7a1b43d2e482ea8d9425f27fa32746a3a5cdf86fdb034d182e5ed8d6de535a8a16f27f361ffc852287fc1f8ea312737d35a0dac8ffbdd4b044ca5b84f3299c900854576290dbc266c830baa9966ae14b4f81260163ca8e9d0020bae9b32ee93aa93fbbb7a628e23782bc002c1846b87ccee32199a208c6bd33bc004c92a42a56640fafb6ddd2f34567dc5d606f117c5a6895aaa5708cc2909a761bf988c6e033be00accc897f2f0000c5a326d0771ec4008c649b4f25501305f7e2e6312c142ee4b47615bbd649479bc2be7d8572f091acf1358309109d61e47651738a4ff4d5d619cf0e6ae30989833ef2377ef907159caf89663eff8703c112fe00f10e93fcfe834addce21061a032aee988c762ecf6cc2a1014b9bdda2f7739425e0bc25a0818b2860b16ea7377aef064ad52e85f7ea3398ab808374d3e4efd9028028085ab7be4002b38a09266691bd868718505c36c11e13478ddc35c7b5691b09300c92ba25368fbe11030a2bb832adc916840d5069d92514c3cf50ca1b6083100c88e92b7cbf84051a7877033a754c4391a4d0316dc0f41308a79af4606db8bfa7aa8b00ee274b2b149cae04722bb7e0c701a088bff2d44399191901c69a394b2988f32b7e0e20fd1d0fa8c3772ea557c39c9e8fb553327e8bda430fa2aa3be8334156dbdddf01ad0ea21eb0b9a107e84070f1be01080394058be145811efe5a5d1502fd37c9082b369c40dccae3244282c08241d092872f204ae3aaf7b21fec48530f7b981f29875cf7d4a0ad91ed091ccd8266c4e45b93acfc37d34e81965d356ed50aa22bc7de55b26f49642248bdb744605deb38162d30fc2a0b24502df0c95d031fb3455f60964987088fc992d80bcc52f55c66421d3603ff9eb1555ee0e9ca4117ebff8adea3c2959c1ced5f0cb049613168fedb8aeead139b57284a1e5a62b56fa88ccdc54ca34fe7675d0bafb6831fac4f96869a717b688fe82f71f4117b846d1fd80eb692cff378cd599ddd4a3445fb898502a00abca687187461f2d34d8fc08ab5449785e395947e2ea5d6a817860afba6cfbf0723d100163adac3282cbbddde96d95cfeff4e85da9b4b6965bccdc1ce203c10f64a5fe6dc33a09439cbc5eafaf7d104c26d36912fbe55a7eb02aa0f260e9dd0f763d44753836033309bff29aab095be0fc70d944f494f170621abf4184f908d78fd945d44df28171fe43d50bbfc9cbfe7a7b45478e3d8c0712c9e6f8e509a5610cda471e6dbc1ebda75aa44ba0821262b16f9fe71f2a616940556b7d116ec7c0bbcb560be6c24add594b559094e0234e9302a6cfe8e38954615a3195ea7cdea8b36929506b793e50df0d3b98a89a6d823d0ea8123815e8f297250d33ac85ce0df2e55f01c338c547450abd85542382cec31a165241a29098ddae78129196488981e1b5e9995df3699f264e3470feab7656598ca52ae1ea71c6cd1c3f60491026bc4dc2bab9bd929fa5bfd00c1c893f6b7b4f04bcdcd808a619776144c89e308fd88681cfb309bd2dd7203da1ddeb6c08abdc051ad34e3adfc86539bee35d5a423ae301396736da34189d96982adbad2b031ece7ce3939af8dc4de689fd2aeb2e8a9ac6a2230034d6bacac920588eaad65a4ec5eb0e39891bec00a59b7cb957a0a49cfd0e2cb21fa43bf2ed23174592a41d33667ef5ddef028e86f899cbcd5b78873cfa6cf27c1a918cde36ff40d0ddf646c9f7d339e1a0ac0040ec096e5a64adad5b347fe40038bfcdf95d9a99d003dd8eb5185e0d9a7b31dd4decc53af4d2856649e62ea74e2f2ef373aee9d7cc15889473d724d809b2bbd45f7ae7a179ff682f29aafee556925d1c3080bb65f3db3388195d04958d674fef3b5528284231c95991735aa5d1b64f9300c8980c749d1cab0dee9f89b621299be67cc407da1e058d21e7df217ac41e2a559de995930b10342bcac95d02c7c2f981011cf505592aa2f9f4bcd1cb3028d1b2a82a79215daef167014ca99949638b3ca5903dc073ea50f486c695a35b43b98b6600a7cea88aa6ead88248013b7a48320c1bb1fbaadf40177dbfa50d2f32ac8f04bb85c2942e50f9a6a0e43d208616f32c26b98bed6a34ed8da24071facf8e199ca173602a6c233a40268bf9c9b4db9ac16d0580789f42cbd13331945f54bfb092aa2279e4f032e92cfabd04805a670ba89eb29349e015f36d34d3c9aff267f90f45db44489d070f39fc787de66dda5e6c32f371451af3c6a68417f6de7a4ff622ab3367dc64acf039266559628ea6a73935560b439d2750418678b18588647ec1c7a3cc4307c32f55ebf73480f7b5c43fb56d8c49871374d85a4f38c0fa2df8234b5056efb854814551030ed6a3a0c581bee38d40832fccd155c40f303b5048c0ca7d3e988fb81d36345d58afe29c46e29194d64174fa891b4bb45f88b4f249bce8df5e3f3b0b70b13abdff33aa42b5203fbcd31b4fd40c2ba096db2770a1216e814f9125e91b7f27c3c1b2fc7f3bc27de05dfcdcae5bd40eff33c98e7b52cf87a78e0e7b140cfc987e3adbe0bbcd5eafbc00ff49c7c9f17e4bbf16cbc17f8799e07f3887c37ae9507be40cff38aa8f703c90bca6a0396b7f26c3cf087676303c3f922e402af08e87d5feb73bd1756308463c5f33eeff33c2fc85d9ef30fc8786abca8e1d44b0f6778e185870c1174a96328b1458c303c8a47030ed5c3041eaa4ac09f7aa04287271e9c454b0f61d43855a229587ab8a00456ea9021273dfc107252751c5124868e91229e1baf89181e0a9accd6ecd084a6c0c7991c1704cd88f880c2100203d121241bf000b40606339a8301f5b1e33cf000e333560c4ec132bec17d5386b72c77ca8c6a7b8bbb23f98b9b1e87a1741eebaea5ac65e626832daa5c362e1b50c7ab89cb66452427ca84a69e974febe66504e6ecf0c09c960cd4ad74562298a6cc0d552d1d9fcb0604d22162a444d50d979c1204d261c2472484b505f45a50185015054b9258e6036f402c3f27a802c10948bc30c163c1440ca698f081415641398f470a6b832faa5a2d0c9ccc54b0e30885551513005005c4a64b4e8f9c1e2250a920f7d2376e5441d936556632842e9b950e4b0a54a105135ea08e16116fc71213ccf0c106c905ccbcde18808e0f873545743901affc17264ee0c1f2b9a172a3410b041089cbc90952f450e504235eb0201c32a3436259e18a2a9cb38643191d253b3c2e43564f5c409a9870e372d2ba79e958152d555911b9a1e246d58d6b4907ea65458812951b1b2b3950251429aa7c80404d6e5a301e3aad1b1ca59e1084a8ba7959b980c642156b15b2bc9696d60deb490b8994239cd3e6c8cb4aebc6054427c9c608d864e3a3c7100d825880e4b5c195270d4cb8798dae253a4b8a969c5c39a02b080bb66a6293031ea1535c36ae1b50887505349ae2b184ac7858160cd9bcc89513a5273f5e3716d8149980d48443f4f2018d80465a465421d10126b136605d6063041cb223c98ac80955a390b7aaad82c0156b849e10046005be4b28e1051a269630028815c89040a5a56a4fce4200e1030fb86c8172c2d2811598d1121f0d322842412c1cb0660d1198c181940c7a820071f110012e8610428b1207302215e4e0b4aea0220149103104103ff4c0830e5b7c8e088d38dc1005b5660e13d03e58430d05c8b028c30bb626e5c98d8b88186028b2c9409a2288803969e2a307cecdcb35440802a000e1cb2c4d1d2c5151da21002a0060270c26552ca1841164ba70296af2a30738012f4ac0454a942636ae56132924c0091f6a10000b231460871d5ec8d224e5c9911c4f8e551cdf1ba01aaac6770668c627062b0c4f031f06be0bd87c9163816f029f043e2abe26564b7c526018cf8887c18b086b880e90cfc75781d743e4c1daf9442f04757c38ac9bcfe6f5fa5c2ed6077a2bcf6bb9ce8e9590951555a00b5520961612eab9da302114a28ac5ca7169a00113c01e92803e728038a2b5421588c5c6829b213fca54d978013f585d80b35b392f2a2fa3d6cdeac98f1bd61655af28aa7a8060824dd14a073672c0a862ada962e504b94055ceeeb1a2cae6878ecff58425c49ae14c053840067002d842d50e9e1d3cab2051e8f5435c73026b8397910bca6ac70aca0b88aa155312a1d64d0b89aa154e2b0808849364e5813920d00d15d6062e2fad20ad9b56989c1e393d6c7ed858e0f2b242c109ad202b264e60655195411810476519100161393b5a22e8ad825648a0538e8fd6e7eae212e26a6255a0c36be1ac5860ce6ab5fa56aed56b657333045cb9b6b8767638dd88ae96cb5bb940225008f45e4056424cb0c1807544950e244c105ba09057d209201827f4c8a8af498119194833012f2090c569838a0106a5230ea214e1bb861a4247c21d2fd7970fb234ade9c20214c0e085211b0630e50630302081072800014b0c21346a50c3b95fd0b90d12a8e1c0171670c001c8941002081f7890a5a90a150c2ef061011553905942891248901119822f063ac0f080185cd59a149cc00d10bef8b040c4c003b0588002100045132f04400559085eba64695a9240175844a00a1bac6c4003127880031460851040b080421912a8c1c0170ab0820a27cc547db9a424eaa829830c3020e0802912f0441131b8f0a52c0410be7459527a02fb117524d0050474c02106335f86d0c11215a527301d3aa097418604bad0028b2912f0441345c8100033603a588af204564494e46747047d4d196478400b0860e1802912d0441132c4100017cc8c210303020742e31c7260b9b243470786c0d0020b1e7419f2c10516765449d8ac59a5e043014b8e2f8ed711a0115f11df109e922fc96ac81b7182561baca805187c433c21ae202b20de0fd08767810d8f9d9d5508eec8d111ea04c9018233e446b471bd5aaeafd562b1569e0771bf09739870779dc73769755f9b73425f62d2de9388bbe7ca31e32d0f73b884394d618ecf0ff5a1f44babafa5dad26de9935daeb9de485a2dc57103878d10870cf719b55dfb371dfda151a0509f2850e8b5b6a7e13c11e214e13ea3354cb5a57b4703338ee397252bad2043eeac273dac23ee8e818738465ce7f1e6dfbbe684f20641100457abefdbc0431b316cb8b876eb2f29ddf852fdb8a43f3e4fe613ebd046091b1bbcbcbf2bd5a1873654e5fdd0868a0d1135690d539a712dd7ade9db6ecd371b50e731df48dce3849eb4a6b639c1dd2f70b719e185e6b5596b7fe61f77f771f7235e194e92502934e79344e2c489919322274d9c307142e4648913254e92381972e2c4c8c8a8c8a88911132322a325464a8c92180d193929322a2a2a6a52c4a488a868499192a2244543454e9a1835296ad2a4099326444d963451d2244993a1264e9818312962d284091326444c963051c224099321264e888c888a889a10312122225a42a4842809d110919325464b8a963459c26409d192254b942c49b26468891325464a8a943451c244099192254a942849a2644889932446498a923449c2240951922549942449926428899321a3a1a2a126434c868886960c29194a323434441ba21a7a7717e2a10b059d47dc798536aef6199360dc5d03f0cb0d90cf8d0f0994043a02a54037403723504a2975f70cdcdd88bb17717790c7354461ae9c1908c29606b27077201eb69698cd28ec066836a3b05d7b93d29bd9cfbed96ca674d7def4995158f9a6ad355da34489c2a3ff46ee8aebe1095b3b2a0a74e408dd95962f45da5b732d2777582bc8dda9872d1da7daee9346a9b67fdbfb64d2e19cf151c83ac37d1cbb70779787ac07b072f01f0a050a4de243ff34eb149dfdc54d335ae9c574f6e393ab59774f18ec62bb735966b47cd3ac240cee3e86e01cac9355bafff8e4b735bbef974b9bfc30b42c0270f72e1e8273b8cd82977bf9b88c6b66c5d1a4a52c45ee2ee4ee50dcdd030fc1289d474de6ea641c0a2a2d0edafa62d2dd7b90b9923966b3471847913966a424c9eb7ae4ee3cdccd7a6eb296bfc9cf51296d6bb349b76dbb8f45933976b39df82a6ddcb47192ed5ad3d61cb5db399cd2cdb2317eda5f65dfdfdaea8bc9bbe4ee629871ce4a9ff1862a7fe7ce9c3192fea9fa584a6bd6a9bb9431951ac6d9e97e95ecee501eae80e83cd2acb933a65577dfe1ee3a3cc7dd71dcfdc693c1ddb778f899e177fb16e0ee14a6cb7b621aad61ba4f4cf196e93c0e8d49c62441faf78e26b4448911f94e68a513274a9a90e42709c26172a49db76d6dbbbcb5b57d16c2b65cc951a84a89a02d4ee871f82644a340a1fe7a1955e0e1477d5b33a7e96cd7d98e879e09dccd1a840f0fbd27dcbd87871e0f1e25bfbed6dd75dc5963bcadf358629c10f953a1e7c191bb5be0a107e4eef74d0a853e8d96e6896f9afe506d7d28d597d7d29fc2b8cfb25cd56977b771ff4a77ffe1a107ea3c6e5cade5ee9e679cf3d35b69f9379ffb33bdd8eebaefe3decce12cddb6e64d6dd7ee5d7119d768fd5aee3ec443a77277d1439fc1dd75d8ace59fc274b72d597395d479247f4aa6ab5988cc5a88c4a6598576b9669a35dd9db502ddfde3c0cc17073840151b1f6890f8410004c63875c08aa89a0488d0c831957c784060e4050fdd6b8cc3061134992a006163d4f35380245ef000b2c2b8cae924231c843983c20faf0a00a98a02c7d15455c57c05c48c0d38384467bcfc4b013473c6a3291085600d2b72de3e622c1ac63525c5a25e071e134355d5b3955a3d521db4284b0a4b6b4bb5a817fb5c3ddb8bd9ec10426506779c1d3478d4eb99c1a3cef3ad78563c5b8a45bd954dec6bf56c2f06ae89b1c01e2a7c7bb1af870a6f35021c213492080defcc19907ed4e3317306ec49420c07522b2ac6d174c159519c55cf151c5f8959992f332798e07df403bd038020084a7dd4061c33f55156cf5e51d62ae6d1970be49162f5881913b3e9916a51d69415eba32fa957cf981808ae562b70058220b85a510ff43ceaac2270dcf3bc559817b5b1a1ceca1ba4ada98fb6280be411f32326f5f578cce31163c58bbdc4ac3cda23664cccd52305d28fb65a3d62563129a71ea3ae7365471538424000234408e9f5c011a265b5e3078f7a3d3e780a30b3a26652203a62d13154ad332dea3a5bfc8845cb90a9aa02573cad9e174f1488b3c307779ccfd3f1c0a945573cad568be2b8ce961665f5ac7870583a4e2e123ebaead2a2df9702092dea1d7d2e176db97a52c869b5563c600a24b0a87704521c9c1565f18c2173c43a5af5a4907304d2291f4bc989453d58d1564f0aded18e35472cb0a70aef49c13bfa7aaaf04a687d200e38ab2216d107429042c1a1be2ad2e1f9705639383cded1475f3d551ff5e88bc78f7272a88deb06a4371ffdcc8034877a3c24d850efb3d1a17e94c343a6aaaa8a27050e8e56f486c7cc19ef684553e0e008a4ae23440b487568d4ea0887474787dee0d033ded10df5e88bc74c0ec5f17848b8a19e8d4749c0a15f4ec8a323848a8bb6a45c3d43ec3edaea1962b7a21cbccce0d033d48c0e4d811edd504fa7a76a453deaf59859d19b9e2a3329d0231dbad2f940878784125614a7a76a4575b4e844ade877c363c373c63bb2a1383c6652e0e00887e27c3c3a2b1d25215a40aa433f58d11cfae23163d385cc914e0f0994841b4a020eebc686278524c470106b51ef88888d508caf89b57aa486d87d74889d102d20fd68d48a7af4e5b4a274f5b5a4c0214a23fca84789ca086d70848000867aaf55d19831257c74454bf02848c72411e5a22d3ac6cc197a14654359b48417bda13e26057ac4ea1953428bdef49ca1472b6ad353c2ab87455d3d25b43ccaea29d3f26e06aa8858c3c31a1b5060460df0f33ed002de1c4f78ab700e1318f17e2001fe00636172629f8ff7792b2f470e325e46af550370d05cc941220e35719cc1c6cf8e385a8863863872880388389088e309c7f158211c73b88ee741ab1d8671a461f580ab1c560e1c5a787050c08343031e1c6378dee781ac9507470e7000010712703c010715703000fc5a28a0f15eafd70f9082a0d11b676220f8860dde8a7e3a5756d4a3204501e7fbc21ef405d27ae0f84794055116a0e7795e16edad413dd08d39dc308197e6042dc4da30b3a24f629ea6046956ab96c33cf6a238613eeaf2c0d8176bf57c445d84ad9694778515868c7f06d820063aac0d11ecf8d183c562adbc1ed08d1871d88f188b4533f0271e63d1230ef3d80cfee36de4f06a0388b00d247eb4f1046d638a232c26aaaaaa8e38720aea286d10c3017df5e908332616c67b85f9562c23282cb0e5020816b96898bd22dac24c122b6a26057ad4ea49e33908e6803daf2f8ad6ebd57ab53c25cf080a96b0c7aac56ab58c5a2ed7b76ad196165e822c3e1c56192008b672a07c3facb0022c232deab2f96c40b0f5d2c971b9e84b47e7f4540163383d2c0f02691530e6eab90163208e0dbde159c16c28ebd371b128c8638487d2d0c5fa7c86582c106c5110a42d90b66e22f0b2f93e9beffba80d0fd0f7b32a8aad98b810ebfbc0d647592da0cf670512f9a409d18055d8785ecf8fefb55af5f8b4284be7f54d59794e73404f07a43d3c90b27ae440f3796872c0f93e96fbc4b4cbf5ad58ac6fc5ea01f2201608aebef0c2c603592005592c10044116c862816a8a7c7c7c5e2478c3f5234c8f21b1154841230e6319b91c06d2211f209f17359263c40b968d0dcb8605d2cf676504054b0fb0e56ab568185713578b7a8cc562fde09902335005c3ca87e903af7c14a42c5087e5e2a161fcf359c57af8be8f8705027d31acbcd56a459b4cf97c5661c80611abd54b07908d19b13044b195b7c3615e0f140f5a515087c3be1ea75032107ab18fbedef83e8ac3e209c3a11378432c4a8d7846565febd3f97ac2901a617d4394d230fcc162b1a6f8a0184531f2fdb0582c168b0d13c4582c8ae5e5c35a7d3e465f8fd1e7c3a25f0fb562c4a250b084445f93152513db1ff5629f7b01c6c7a24ebf8f05ae562003beeff3becffb28a53f28b85a7dabefbb22c4f1becf410aead06a79200be4a2f5b558dff7b1be6fd5fabeaff57d9f1cacef6bb1582c568b25c4cae359ad56463cf40182e2b55a2d6aa4b55ab5807c82a8cfe7e3d35ac901ba3e10044110044190822008822005bfeffb7ad0ac84f0224aa91b3c0c7d4386b0f156dfe7f91a3455aed60742f1a0d52a8afb185d11be62af1e9607b97a7a8c7c3ee12aa4d4c8ea034d80b302f220162bc87d5854c7a31e0f088a7a7c3f2cca3a0ac3f8c45814e88302bebcd52a67f5d1950b9ee785a18a85d92b4fe7079c149c112312f33428382386f3e540f97e563a4a203562a4275c03430812690152d0e7c7eaeb6179d0e7638445874e80650804b1d547575f48519066e5a373586fe01039200c12c359812b6781617c629ed7628134e61ae2412bfa83e54d88b8cf7745e831d0d56a40f8d12ff662391112e147411e9607b128cecb49c00409a220011a17f298072420c27f5ce90b12033d88f5d9bc1810b25e37397ef4830436c45674489832622b8a839383b3001d1c34e1ab47478e8e520ece0e1d0a087758118a6115e18e4845c863678a1e3c1250410f292ca8208ad0870508087ff870203f5e41807c3150489055ec3544089121de054430b8c0631fc580877a3d3c457a3e234532300245e8c5400d32008f68f0f239f2c5c01f9f55ec85e4c7db0089c73ea00dbc20a06f0c3a40e8c5c098d0f8443824648030c9902b49e22d51f2112d71225c31216aa20993a22646454e8c604e9ec0a03c8902454a140ea4f8140e9a884d398a211d252129255151aa42c54a95252b1d2c79573ac07285094b13539626a72c9f16a7272d504f51505ba2b86c61225c75e1e241172f1e7ce0e5cb07207c01034208606421cc647546ab24ad24bfacbdad76b3b9bd85b152619630a570e6c5e19bc36272598cce5b9f7bead44d51e9765422ecbc114418330209634a20814c0955644ca83ac18432279829838299145000400a6700f0a9706605155858a105165c6821002ebc10802560782106186488410032cc20001a66a8810625c2d5006af06c1800016cf86e20000e37ac72c041871c7c071d0ab0c3c743017ae0c1871e7ef0c181f82108203c2182580d210411437811441851847784112b248ef0d89704125e6ca544125fcc975022897015f3623945433e6f091d252442228e083feac57c3e9f1f5f8f9107093124844e15108cb55820cb83be1e222342cf0b42e7eb212a22043d26dcf38cf08ef090f092f09af098f868921a38fe799fe7b570c6f3bcef5b83873572f82e80e31f0e0f26741a6081173b40b0461b62190b66f011f3346a8c50b3001e70887e48a3c61b3e9e6d0905c230ce819847b178e1feada07c3e1e1aa02120cf89ce84de43c641f0cbf11ea0c762008ee779dff7b97b40e0e0b80750f0267f8ad69f9bae5660b028baa3d1f2f77dfd39f7d98792368737adf4feebf6d3a8edda5cae32d1716b6bd299942828331cce358afbdba6da34edd6f42f7e1cd6f47525e9b574eb334c0d07d9e8c6970a1fd1719731ee7141d7d2b7e56a562ed8897e356bd7d2d75697ffb63149f712cdf46934ffd45ffa19d73eff6d97b7d27b526d29eeb78529456569a263f925d574a4589b95026dfd369b44c7dcecf7493ed5cfe88e46f743d1b17cad9fc439476dd73e8dfe6c4bf1d64774aee27c68ddb5a49a8efaf1a63a579980f2dff6dbf0a6fbcc99f547ffded1e8b52399716de3283a262de56a9ea4ddb9e668bd96ee7b6292acd434cf1afd5b147d5c6d223f07f5ba7e26ef128ed2e57f1395128e7a1c46dae6596b9f7471d3be2746aafd13ceffc1c54da6a5629a158965dd1d8b874dd870f7809cb9e97a7b7d52bc29fd1237a13005d3665398a64c99e264cad0142153804cf9e1394c7f32f0a1302a447f32a01428030acb610a9401f5a1140644c17cf912931c8c117290c58f4f95c1a8b62deac98baf0c06d3175b2dfb8c492d2b7feb33ef7bca7027ce5e8b334ffd7947deb7e5c7bd6cbfde16473bb5145969a6596b325b5050d0eb7acbc9ec364f9c4c26cb057d142ca25c41f1a67f96b4527a4f4a0d308002a8a1608a7c09a354b9e3bb7b29ba7b29d9a6fa1b46c1e2ee3f40bb97f259dd70d3d5ca0d06236dfefbb6fc657d2bdaeabf5f85fc1cd49317375d6c757e2e545152e4df90b4bb7ff1d06805771f8f9268ad96a5fc8cb73e738e7eaef4a7bcd527ffbd16484bd5cf694c5e4c526d6f9bfe131393fe9bae56e8f8e3f3b72d82d574dfcff944a2e3c6f8893e3131d1ad7f572cb4525d6f95eef24f298d74eba7d1bfedfcdaca68f9a59dbab6dec86be9d37de4e6bf45651c6eb4a3c6fdb61953fd53f5d26ba9ae375ac3389bb8aaadac56dfa465593fef5e4af6b65b6f24cee69c2cd737495892bbfff01006e44e6174574af1a6bb975aade1c4012ec3597cca82aeadfb9e1817f4a6547e5cc6e1642b94c18510c5c3221e34ce2f02b55dbb332fa6657d93e24df5b634baef5bd2d66aa7a6e5ef9abd69af8bbb971e36190a8baa0c2d094109bdf5b6a9d66fee6ea5fa71b9ea13dba9cf4ebb3e6de7fa379d475a509322dc71145f8a420e6348795cbd34474f283a8fe49231c9b82488fc1293413f65779db2f94b99aea65965baca6ee4ae34d99319d77010ce864cc6b853d89798a46fd2ea8dac9fef49cbcfbfef5398596ffe7dad69da9a7e5bcdc972f56fb2fd5a4b917f2365fbc45f6272bfa6b2b5cf3999695aa97d7fea65b76d7532a9ec01c0ddab42a14c0aee1e0965aaaacca040c68cbb37a68cbb7782bb47668c092578644a70f748f0c6d8b48a0cb183851934eeee1d8800cb147fd99866452a2de692df342bd2b5369bb3ecfb534a2f55b7be5bb6ae26762a5fead6276d62ec84c34d17db8d999ebcd59a666c6b6bfeb6b72eb66b37e662a72e4692ca15f74fb5bc9fbff64fa5696f4836cbf43647fdae1b6a6b8b25ffc6a7187bb76cd356d9e5df1b63c93826ad73364abfddf7c44736abdf5671f6b3537ef32cff4642d9b8fcd36ebf2531d46ee7ac49e5a74c8ca4f55f1c2e6f7d322b6933deda5ad14fdeafd5b2ecf24f6152dba4ddb67fad79b3e096707942e94dd6bc2f46dae553d7cf513557de9aab3c0df74cfa7f6f8bc361a41bf951a625ef89917664cd24c67d92dde6c94415b55febd8be8fdb55a728ad9fb4e55dcad99bcef8b6cf580e57b7a6d5a3ddb6a6dd5ba05aee1e0b74f756eedee7ee9ebbb7668e14b83b0ae658b366cd9a356bd6ac51e3811d41d07490f3317a18a2e6cb1743fec5131e8a5c98809167c1030748d8a20a0f9c8a2b2e807326e70c6f22072a649600c37d882132f8788115ba0e4faab0010d32ee0250535ac00010b0e22c2469428a32f3e0279c004a04d504463e8213aa31608059c0b59a2b72222079c271162c6dc1ba800baffd60860d178dfa4c86a812c86069c13fe04009ba0000f9866f21a10d1548f0a83be5a4e87abcb8c2af2ce14060e072e15598406181230120e1484e85788c0039073750a0044172e44f9caa8457c4c086177571802a3868428d139da06404272035e0496c38a091a901f27184391e20440a667c032a5e90522543e93f1a4041e29e1eae0102e698a3042430c28d5c1eab2f4178c07ba86c81238384294ea704e141024202fc020fc439c2a8220a1fd2c2075cfe00250fc281344e24b1011138102a1ec482a478c07d3c008c27b528a15b30fb3e1d01c2f00a74c24c816abe701e5d5c4089129070f8ce0f3e2230fb32858b2a8469962815e021990330a143180df88e146e74745140e83a70a0330001c305ae630510358cbcc0798e1a45e028536081aa1c243b6f728348154e0e618e38be38c36f1ca0c119392d3e54ddf400f5883aaa729b33150880074b44f8eb0d165a0841115ff82ba705193d607155b94810a2f000244bd0b8a83ee019c00808d0b85c5ef870ca41cd94960e26151376e4d0b49c3cd933709929ac0d14e172058f31ce3a018513725ab8d0b0848c24d1c1ce195560184dc810430b4f54813588a1c38e1826d0805a2e0f4e4a48e26068010d60304064ca8a0238802047192e61ac6c6040991fb43853b5a26db9917a5a4357421828e9e18703f44b811a2c3ab88d876f0237a87066800c0f9f106c48e0cc000c345f095ec430c48c32fcf3a2230401d040c1ec6392c20f09b373f6f5f8f184c36ecd14cf04407851c314aaf23090e3816c2799e24d41b48117b10d26bc1ebc0f5c5314a8f2ce7c30479014270c79627e7cd08d71a5cafbd265ca122c47428f8a042cf000a80683b784840278f174c6cce3712ae10c1a95175ed8442a031c68000f9e636004cc8d4995ab910304264dc8aa7c0c2a940082891639ff620b0c0930a2829167214512d59305d4a9d800174e78651879134d883184a7a5ca87180241075128e0e43a3479000910350b9fe1234de004151f67e18887324cdcc08497711d893064051e7c042b535ea43ce1e33a056084b89d271c572400739041430e5e63c283924106239fedc0438d15de08fd83333c2a2a6aa2f02d67beac41abf0c29d44708df0050b16f02b5130a9c1880788bc8a94595e8304261ce9450501243471c63910cdc05de08a0e7fd2a584264c200081177d41c68925e220c389ced470e4869b1c3c096e07270b8ce1858f2600f1443411c2375880181d3cd57082ff6c808010c4f18305ae01154e5c51039a13dc4836c1cc65c108efe9c00d0fa080c790d3a4d66e882e0bbfe00b03dc2813a5880f81010423527c013d481844620fe8d1c0812c4981104769ba0f2d1ae04511111db78096002f661b08f10a76c4c143842058701e13e08085a33796f84e1020284981dd719105256bbc1a21f0d0040490b141d7c37700600425eca0a1e33a7230233785180db88e021c608610379e3c670d288268c30584e76c500230644fe538ba015356d834f11b2ca6b2448004a5df14b111b1c11983dba83087084a700002fe82c30711e05c89c25f622680a68808d6b88b0d170604504214eeda6df1441ab013b8eb75844d50ad89b776805541f24410de82f58862c464c7591bc8c0136b5838c1596592fc80033480b384b0c0608ae6c241319676590c2b1c1c404e052b601839a8a50d204168910107431a84a4b921f45505766e5d2c5184af08a0040514e078e02b9a15b80700b1c65742277c51f2a307ffd630d930841948f8370136b42461060eff8650b23519f1c23f3217541942c60dfe79e172850f4e49f8c7448b36c41cac7f3dbce814c1f1807b2610634a097e28710f034a413ca00136f7a6085341014258e25e0f443d7007d8c1bd332b40a14651927b62b8ac50822139ee7d31c104213ca8dca3e20606d248d9b9b7e407d7132ad8dce3d14ca0f161cabdb08b0a42ca18c33d9782c50c104bdc49f00309317a38701f230d921305d871ff029b2957f0e29ec5049608634607ee54b08145911cda706fe20164f0e8c47d0825286e7c2b771d685ef8a2729fa1c72b19e1e1ce02540c60b2acdccbf85072021408e13e4297d3070514e1be73e4b0dd10c51d37f40517ae22dc6b54907152821ddc674558808b5c18f70f4a108ad0e4343194c6b9a66fe6ee4e1e1e4912439f949f4f9bad5d4c525ae94fd51bd594d58618fe73f34aa0e1f6e4062ddc2b4107a327554adcbc12361005ec880b508fcc4f1a2713f821c22303001b8ce038c0cd2333471646432870c6ab72c1a9484a4f149e09517e349931208d77828f344cc8a489c22bc34345c4030ad47866ac0d4bd07031f25090438d1230aca01e00cea049d6fcf0f25688319184522b9e0b47c218e240c9140f06285e42217441e3d1b0c3692c41270bef865a55900eb8303c1ed8b00244c5088c3c22e60863030ee8f0c273228d131239a060f0ac20c11b4882e03cf022a0059a1f27ba2a0f8c149ce1e3010f67786e6c21e288ce89303eb10a2b1ad470459a6fc9076832388006a2f840a02167c4026fdcbe17901051c4043aac7c1170322bc2834e5901f142448f122546ab290578c1a305ca6c8506541a9aa372063cc930c2e0052726583d51682e50e28334ac36c4f048a09aa2777fe562abf543d9f2ac17e3ecd32aedd66cb35d2b75f34fe12af96bfad61c7571d3eec6a4aed5f76fe4b6529fdb45eda87076fa421f23ee9ee3ee373e3d7c42164280c14f664139c2dd3d0f244001267e80f1450ceefe79c93d1df1458f352570f78f081f8a70e231451645eeee0dc101047a7a9082e2e6ee1e07327cc902830a3a50e1ee1e0356642ad0810544b8b9fb0780223d0af0810cb4d8c2dd3d032841731d2a84f0e1eeab3646108213483880002370f70f8b120765b070848513b8fbaa8217bc50630036504101776fdd40c31b9e05602998e2ee1e196dc430668e017ca0c8dd411c662e02dcb6080386fb97f8067d89afbbbb77640677271281f727376871b331ce3a8f34a12ff115ba96e6ee42a8c119552fafaaca84aa9b2af7aa2affaa7c55e52a54390b2cb8dfb88dbb9f715fa1ca6daaaa4ca8aa62a1cabdaa6a85aa133c03203cf0bcd048933baedda52f6bb6e5b66c5db1d86def9208d6acd75251da99b95c4cd79b53d4932ca71f57ef266b0e87916030f24b4c4e2959724a91e5cf51559c14894d5a36ebc65b4655715415475a7d6dde542f7b5d6952942c39a1e477a2aa387ca3703d61f23b69184c8ac4e69194f273399bb786e174d541c9e2218f936ff273c624ddf86a4a6b96526de9dfa2e893992c63d9acfc9bb6d7d668fed2b4e791cea3de16674ddc6fbd73f79e7c6bec846b776997f1aee5ad4c66cd9fcb62565cc64de5ae3ae9f7829b16e0ee57dc1decc06d75531b8662d179dcd7e2dc9271282809f9608ce813a3f38b14287a579dfe6b32bdab39adab89742377cd699ba47535c92efb66b3afabc2dd3ff010031a4c7bd2928c49462541b8766dd09b95d4d766b5beedeaa4f36856b382e0e105398ce37864bf09b4eb671bddb8898e9a9ab59a27695692deb6d551dbb5e53de9e74abf0bccfca9376555df9a374ce7f1a6ebeda6eb8dc81bee3be39ba618ef9bae370a7b9d7fea33752f7f2abffea7ff4c85d4160783e9fb5278c3605a9fbb6ea8276f35ebd1c556e7e702e5ee4b56623c5eb9b4df34ab130e43fdc54d51ee4ea99090d0ef9ab7a69d5a88bc3b1d94cb5517b4ffbea5e1b008414c868476dbe69a2bffa6ebed9e30d8ce38973bb5ec7135d7de5633de1a067b9cddf7cce1101e9cee4a7fd7ada9bb2be94479380448e7b1bcbfa9fe89bb87e0a1102afcb7fe2bcbbfad0c87734d467e4e0b913f25b4847c1a49d6222325342743b4a2225a7d2323263f4454abfdd00f91b526444e6aff4542e4d6671612c283bbc7dc5d5f6c37f9a692c6b926779fc241fed24ee1ec54774d72f72858e8f772f7206b0441836ac8c756a39a624a58b264397a52aaa214d5b4b47454a569e9290a8a4629452165c182654bd393d21295a5a826a5a4251a85a4d4c151162c31a5a724a5a8d89312d3962625232a515b94909a94b66851828a5282226d7edcd9e4c424b6f4381b8514b5e57136aa7c11ac59f3dfaff226cd6acbc5766dde504e4c47417ab8fb17da9465292a86b484c5c92906e40cf72f146989ca12929213545094074a514d59908044e08af23779e69fb2dbe26cce012102481021901cdcfdc78782f94295a2625c969e9c1e67a37ed7242af74dd97d53f6bfdfcce1307997cc4ada8b6f252f6e622a6d16772f2a2d2e6d16dbb5f9cb9c653f5496fc5359f297f59fbec44cb61af534dcd964ab51eedee4dd9dc9ef1dedeaf095ba5b6c35ca76edb515f7d9dd85ca2f31f965ddd5dd898c6a7b8bbb8fb6baff342b17775f72b1dd56e39cbb2b49e2eee5b5523f427717bab8c9ddc7a0c761285f4d0940081b511039c8c5b54309a50deec2401537ac983272af0cbe19c0121470971b3c2c8d300a59e36b44e0ac1a58285aa2c600f7a810d70ea008ff704f96e860e7e3ac1a560d58a20113677d6044d20898a8fb8f0b090b1b487197e2428b2d6a80e15e03c8c0e044cb8cbb801c81450f736471d0660133c81630e5ae14b280018438d6784b0b4e8d92c51a7757fd36151ef817022b63970a38714781115560a6a4c1a24dba18038a35ee728205c90394299c65860f330fcd8f7b44f83ca9e056e12b262d7878a1fac1bf29381d1c805be32dddc203a6ec74be9240129a55d593af9c9ca6907440046fe13083064e90c08b7f5128b102ce41c45d28d0204415961ddcb546083954913373ff2288395ef8a183b7841ce940070f4c38d0b559b3be0856ef13ef2a160ec29dedaea66ae62e9ccb4107a378eee2c12b59a2e7f21d1ecb024f87f7520216f96a06f002d7f77ab1c095e7b98f6ff4cf3d2d20079ebb9eb07c47cb73cff3582ca20fe773cff3569f15cf73799fe7ad5a227cdee7b93e0b9e3cafe5b9fcf374c6d5d76af9dc500ffc3e10060ff4bcefc66be3f36c9ce581dfea3d2fcce779df6bcb27e4f3be16f87921f83caff5795a3c1f9eb7fa5e0d60ad5c3a3c0b3caf82d7cad3c1fbbcd6e779df4ae67de08d0dd197e34979792c23ab289eebfbc024ab9beff37e7634f1460093e05059397d0efa178407ae7c40d6e7ad9c45833786e79f8761e89e7ba0f7796a78af0f89e77dacef5b79de124fc87b7939dff77d2d24efc8f7819f37b41a7180bc34f07476827c03f0581f100ff4569e7f4e1fbdf140ff6ccbe9cc7361e53c0df07c0a16dfca6b79a0cbd3f156367cdf0bb6f28c56def781f4cbb1f1f1589ee71a02936030e47d37add7e7f23c98f77d1b58b53c9b0f5c4d793ddfe979ab20cf06e7f35e2eef9bf25df05df01159f9cabd1d5eebfb3c1d305c811f90effbbc9607d63c1b231e0eebf3589e8e2221df920bc7e6f368af1cd7e769f15e9eb7fa3cd0b3e211f99c7c433e9ccff5b1bc95f7791fcee702bd156be50543421f3a3a3d425146868bb212b8285be3e26c878bb31f2ece885c9c39b9389bb938d3b9380b808bb302b83863c2c5191a1767127071c60117676a5c9ca5c0c56a818bb5888b55898b758a8bf58a8bb58b8b9574b16a176b0a2ed6195cac44b8589970b1a2213244489070274890ef0499e23b41b804a9b9fbcb46c78d8e17d0452f392e7af9e1a297222e7a0172d10bcc452f4a2e7a6972d18b072e7aa9b9e885ca452f29b8e8e50517bd10c0452f45b8e8050acfd90152e53b4074be03a405df013283ef00e9c1778024e1ee3a383b3b7038e3210e34788803101ee280000f7170000e5e7888431a0f7108818739b88739ec789843110f7320f230072477b7e0a54347c7cd6b473cc377c414f8ce8e10dfd929e23b3b3fbeb353e43b3b567c67678befecd07c67e7face4e09beb3d382efece0e03b3b41f8cece12beb32385efec2cc077762010eee84143141ed2e0000f69c88087348ce1210d6b7848031c1ed6c0f2b0061e1ed650c4c31a843cace1098e0d8f9d1c23dfc9a9e23b394fbe93637d2727fb4e8e19dfc91980efe420b1c3fdc78d0e1e54f4e10a226a4972510b948b5a4c17b59871518b005cd4a2838b5a9a70518b025cd4420117b594e1a296375c7ceae1e213918b4f4a2e3e7171f1e9baf884828b4f33b8f834848b4f50b8f874858b4f5db8f8f4858b4f1e70f129042e42b180fca88085325c64210e175bd871b105212eb630e4ee366278d3bac9e942878b5dfc70b18b222e7641e46217555ceca28b8b5d8071b10b9b8b5d8871b18b115cece28c8b5d04c0c52e04e062173ab8d8c5102e76f1848b5d38c0dd71c257053b3b3a628e4e8886150fd180f2100dd24334b68768a0e0211a347888c60e1ea2818487683ce1211a5a788806181ea231020fd158e3611a403c4c63030fd328f2308d240fd370f2308d99876988f1300d321ea6c1828769d8e0611a41789846123737e08e1cf10411b87842095c3c01052e9671b95886878b658ab85826c8c532442e963172b10c928b659e5c2c535d2c835d2ca373b14c1917cbb4e0621902b858c687968f17a8f0f0050778f8c2161ebe90c6c317d670771d3d6e76585e7c8765f31d96f61d16007c872583efb076f01d16112f177a78e802110f5df0f1d085200f5d60e212b7b8e2e216d5c52d762e6e11838b5bd8e0e21641b8b845133a3737ad1c7104352e8e00878b633e17c7ec70718c0f17c7f0b83806c8c5314d5c1c93e4e2982617c740b938c68b8b63662e8e315d1c73bae3d8bc76a060e03b5090f80e1426be0325e63b503af01d285b7c070a18df81f2ee9ec3c383c4a07818b3e261ac8b87b1301ec600e061ac079b1cb101402e3680898b0d90e26203aab8d8002d2e36008cbbbf5eee363a44295070510a175c9462002e4a510017a510c245299670518a2a5c94620b77efe13f2a403dac00918715e8c0c30ad05e2b8400063a81266a8490a8d2dc3059c08f9a963339f8f0717777cd016483bbfb8e009039e3ee3e840511d2ac59f317ffd7644a5e15544c910029bc6789300ad77934cd271100841705029e78e2092928b2701b28c078d0dbbc83b874a1a3edce92b22c3d2939c994aac49eb24441c9a2b06c8951514a82c1727a0645df56a5ee792369f974dcd12815a48d9bcad7fdfda7ba6bd2efba93cadfb8a98673c58271d4a6fa7d3152598f5c8c15039c711f67abf9960bd25e020c50827b5036cd7a84136fb048276208aa4eff547070e215549af646864d04e167d0d6b5f6fba4d9bc77accab479e8ee3226d494f8dec0040c486552f91b731162d49a56690d676ca7eaed69f4379eda3a67fe6dfcd9d17cca5c6b146baae968bb16db2ea6e34dd75b8c8eb7df980bc6517a772b1314b08912403ce58b60931841120a4802091f7f7ce88f0fd57d936f20092cf507210f930091c000121170172a4205891328d535a764c9d2eb5a0ddb5e23f1b27da6478441614fd69c3ec201eef4c17ca1528228982ff4733bbce9085ccd1141c4d027f74d992c4a141a8587de376532234e30a274f73781a8b6541bf1b9eb46fd9a6a7be2923eade633661bffbea675679a95ea53dfbfaff18da4f9c45ee8a8f5b96baeb95c4582a4eafe888e9a8a036d8b10f1fa4244263384156f8c33caef04429c719caeba7de456a41b1d6d9646f1aeb67c1cce527de44d5a8dca475493d8bc5f969696718d6a4bafc566ad7c3deafcfb7135d71cdddd8ad35b6c43667dadaf7d7dca64667d0dd31ac774e44869beadd1bf6d5cc6356d561a366db47c6d29dd991796c3597a71d67d6eb7ebc5b7f699ea2bdd6133f7a5ae369dc7cf35f7390783e9f0b087325f62a4f16963a67f23a9e829e69f70ed2ee174d5c1606f9e2566c2e9dac5490b1e8c0c50001378a9c10ca7ae9fb7b7410188dc33093b5c2067964a36ab754f5599469df1262b95aa6449794f9a3f77a464c9eea5663f3f148802f9f8d4f006944867d6b2acf4568ac9f29bb45963b849f754308eca5fe62cb52cfbcca2df66bceb52fef23e958d9b1e67a374180bc6510b88000e2f57c2d96a22c51e57c93f0af34fbba5376d0d0349e58abb91f78fe898df29a9ee9a34669cb33b7fee5a264a55cdaa318e665cabb4fc769c5d3ba3aa4c33fd3a6ce6cfe54e6a566cbf466d75df27f3e7e8ae4ed726e56878d35dae96b4b5bf4fc3367f8eeeaa3992d534fff6b7a829c6f2cbdfd18d6f4977369374fcb2e234dd35d1ec74972ed6f59fee16a47f7a2a4e4e9fcf5d719f9d76d54ae9706c9a066848a26599818c7a8ae12242d10c4453ae8305dcbd04016cffa41040e841210313ee3a94e18cbb8fe6b87ba90de8ac5cfa2b33fab7dd4b5d1dae7a26bb78762373fd6bf5a92dad371d2d77eea57e4aef5e4a1fd15d5e53dbadb79bdda4c5e18bc937f3974bbfab25b1d631dcb4fb2b7af75770d391f085ecb69b6f32a7e285efbe08f83315aa2fef520070eeeec2085c9861bc27ce9d52189bf46f51b18d9bf25f5d968d9b5e6fb94b1b37edfbb7e2f23bf5f0705a00c2dd6b184f27ca9701c0177a9f4f28d3ac485a7f2ecb98cbe1b3f37d2e2fa80a095fc28211cf8a15b058817a73783f4c48e107ef042e0498c0a4822f881745dc9dc943326276e009cfa8f11d15ceb91d6766d079a491e0eebbbf62cbb12b4e001881cee3eea58478f67ddcc50e819cced92d02a0072e4fed1545e1a6b5091d200510dc69d50be241e122582d82d594fe94dd41543b482827a3da55a6fbfb39056688c0e9aa23c778688647ccc657f7170663b2b979f2b4f1cdb3c43819add63ee746f2a97eeabe8c6a07bd2dd71b12aedd258d9d6edb76c93867a5ac24954529eae97fe3a9d2c432b36a5bca64af710ee7dc3d339695b9eaea2edb1cd5cb76b2d28410689bd54964b25c95e92a339fbc95a495517e69a768eefec3dde570f72cee1dc0ddbdf2e9cebc5d4ba38672756b9cddbb97ca670e67ab49fee75afeda8bf09f6bdae284486d7125249570848434db0b220c770aa871b754ee4454b80709c15d47c8033d9c2f4410a3830d170fc7784182061437e93c925fe26bf3f642afc7aec434fd4487cda751d9b57a6b6c45ffb53b87fb6b83e89fd7ee8b49fa64b773b820bacbfb3ecd616cd28bddc930c3dd87689428b403236cd179cc7fef99938c433058fefd391725ca58335fbb7b30fc7801f40260d2ea3e71ee9e0b345a4b0884225819afcd591d9a703747a5b82a76402cb14b9231fdfc53621c257d28addc36eb38e04e3ca4327f9f544b5419bc08f65a9c2e8cddb6a6d54de1ee473cd4cd4033aee9a0cf3937743aa478380543fe6b71e114964c472ae114f8fbcce130199e599c30c0f699dbe76ddbf0c41285d2b1fc6c4f3164a5b5b367cf41cb3769f53ea53b8c4dc5487321ec5b19f43947a376583fb35208676148c9994217a1612029108549a114262b479ba5e9253da4fc325752db9202510e804c327320cc298cb66b6fba9a9a0295f7c435aaf3088389a180bb0f7928c6875dcde69fa278973785c9caf277ae1bca842abf0c736ae8d717e7f4677a6d7e27aaca14f526ad72b15daaca346afa5275eff6b5414f5e0ca4f7f91b4f51dbddbba0fc4e6599abee88eaa7af51f356fb7c5e5b6d957ea678c360b32ff4fe943e546f186cf685d69668d55495a96add5fa02cbabf663dc53ccd585baaef53d9d6274e464fbc3be98977b4b644476db505ba4b74d457874baa3759f359526d6fa5c553555345e5bf96e969d44772b8dffb75ae9a4f3513ba757db33e0e88c99b395ca5aa7f8b2a4d7cd39f6f514723544ffebd9fd64375bdb6f6e514ce395c7d5d6f495f947136a3d54ed1fd1a898e6fd2acae5f2be9d36cddfbc891fc37f235dd996625ed2d57b30defdc2e93b4ee23f7c926bf56ab3463bac9cfe19b5f9f41789f95de5cc5ed5e0af61b5f4a85755174bcf8a4b66bdfb4371d9de959141df5dd42c75d29ce34cb5bc35f31b9ab398a3f1f79dd6f7b8ad15074ac7457d376ada6f5efec5a3a9b7d9efd0001f9cc806274fcfcb7b2fed677898e3a6c6a9bab4c3aff14a6dbeeaae9cecce1acde96026d5d73f4c97e4dcbaf541fd9f5b681663f3ef7cd71465f9ffb48173a0e01dd2c74dcf5c949abb634f7a4b65d8bc39b3e8de24df5e53df1d3ec0bd5968299cdbee89d79b3bc2dd75d7eb6fa934cb392b4bc95a4e3c5b9f3fc530c05ca1848cf28ae7ede3a77d25be9aeff64263f3f8eb45be36babb6a4362013e34d6b65d097e6597b7de65fa23b3357a52eb68bb354fd2e9f9d4cf3a4f2e79f5a9276d569e37b2b92d67fb7ed72234f268ca3a06a56b25d9cbb986645d2911bdf74ae5e28dbb55a5b268ba39de5ebb069c6366e32cd4a6586048ed27d9502c0c26cc954107931ed6ec9556762ec443ba1f643e5306eca65d16fe2271363a7fcf72e99183b955fe6aafba7ff4ca5bcf82848489b18e7645756c4dd7375e3ab7154b9a4dfc64c249d5faa6e285db352b984a3cad7b5cf4eba66a55a96adef92be18a966a5bb74d3d584cab896e5499b9d70bf9732a6a243305f5c6b96f54b0a547ea61bd3fc5219ebb014894da120770ff2108c096688decf61301f086c80c001770aabbb96753f8e0251204f05af052077cf04ad71eeca721aea6302fb86dcdd8b8720d8b8eb208d735748ef70b833a944dafd95fdfa9f742e577550faa9fcb2fc1975cd5220f2b9d0f281cafad98c6ddca4f56fcc0587f7ad3727d94fe92a1ba01ce10708f88008779fe2432fb696eed8aec4347acf1a0dbab8c9abc20f2c70f29e3526e3505090c5e9aafbf1d9ed1cee65da8e5264dde3a62af5e36a9396959fe57118aac4b88a69c6ca9fb24ffe53e885ee6836363637e5d32ff46f5136363637509cb68c5a9b39a08c6bdb6aac6b19db9c8b518fc9582bd578974b5c6652a432d6ed6f2abfbc273e82f2a7989acb923f97abb9cb8ce27ddb676c46cb5bebeb2850c654e858e9eb59fe5d8bcd3e9f24599fa65fe3dba6ba52a0197eda54df34fb5d7176eba75a7fb6e19cab74bf8de24d4d5b6bb4a4ed72d5fac98d2fcdbfab49ffb6819e3c4b9c73b6ea28ad5fdad22c4b8bdb9937db896ba7d5f47537b2daaebdd7da3e46475aded7146f2a85a93eb22dbd91f6a6a3f9776df757ac94bf2b69cddc9984e361d400a27451d89dc27c288551dbb54f140c857d110a1acb2fff2f7d1add55dcd66798b2d2181defeea5e8678cd3b66b6fae5dbec44d4bb67a0475a604a81c42dda066545bdd5f0a446d9546f1be96525a3e982f3487180aa0868271f75a70f758f054f0ce3ccde1ee545b2cda568fe8135ba595b6746f044f51dc7d2c5f0c1d6d954677556756baf589bb3f656d5b5baaff12d37490b6559ab6260cb6b53565da5669b2500b07dcb4b5b4f8a6a395da2e696dd8a4ee4ee5a11610f49f5adb1bf99beefababe99a34fee3fedf33965f5fdda97b4326182d5d21264ccb852a730ac1494131a77ff1a7d6262ca389c530e7ed3678e3e313189c185061e308ef93535eb7971497514e3306ed76ca92dd04c8ad109857c2e359cb1a69feb0e67b159ebef9a1f775e253a6e6c6afa38bb6979ad548c8e7a679cfbbc7b29aacb8cc9cf949626bee593ee72c5d99cb35d6bd66a1836654f310f7b5aa555006480bb931e4a796ac279c8e484dbd8d8dc8c363636373f423a484893b80cbab6e284b01d474de2524808a6737bdf7cc0e82f1487b73e916c6c6c6e64b251976f9a1569772b13c6519576ea5dce2c31ce44da412643132770318be2b5a23796c0506584a2260f9dc8b0883263de00983061019fa6ffbc946f9af68aee9bcadfb5cc6df9122369fd2479425dc9c07dec000519e7acb4e4610740b8fbb8a3d1a7d15b499a3f47f7ad36d729fa1723996645723262020535da4ab754c551bc69fe32637aed896995eaf095da965eaceb3fd191fc9cae26f925ddedfc399c73b467575cc6548c9868dd435633f7595b6ac4040a8a96bfebb559fde5a88fecaf426949e34be1a96be9ad24d575d712df74b5b275b6b5adbf34b10fcdfd4be128131d7127ce5255dca8b5ae340ad31ad02b7ca4175bfab7d9e7dbc6b5bb14dbf8d29aa53fa50f7d6d562a749c295972cea8b67436a3bbde36c59a96678ea263f9f773b97aa3e7d6b586e96c369b7dc61b8a8e5b63933e0d47f579db369bcd9b3e8d9aa6d5515bb31b5f8aa9feee8bc99abd7fdb74f6faa4afa3354b5f9f957c1a9dfd507d79978060b0289f19fddc6ed37adbfaf36f6bdec8fdb51ac6694b6dd74a9135efb77953fdb6fb536f5a930cda379b35a9d661d3ac54db5b141df17d129bf46d95b45bdb6b71f9f797a6190b578765b8fa5bb65fcb642fc3e5a9c5ec828725b744312110438628a614f3843940613f35a5bf335e1f10718ebb2ba1484a13e72a798af95246fe9490f65ef8bce4bc38e2fe49e02ab96f8b532a31d2d23be17e5b3a56303318ac66fe43d1516b4bf749a3d79eb65cb1e05dee44a8e498b3b129af54cd6fbb18cbb63636f8a62b6f2d1f77dad0efa269362a574ab1aeafabcddbb4b5963bf5b6f6da6de96c47abd1ad31d216b6eadd54fddb94121dcb92ea4a6b56a73f8916d1389395de1387c364d54d615aa51b5f3a96bb343fef4c2b423569f9fbcdba2b2d2fd171091de9a8848e48744c4261356c4d13933f4f5e7c295069653e375d6f96becd9b522448487bf1c617e39020a1556fa01d8dfe6d9bf55a9bb555b35631cd8a83aad5cea37de66e3bf749a65962a42f31924e2aa3f69bf6d625632afbccdd25a827d2e67094d2b5f586849b4cf3a4929431d25d2aa38ca84431d94eda27956fef273d796bdef7cf7aa352da1667b1ecfc53ff7497c6a0cf3224d76314121a4537a0982ee1ad31be91b4ac345a4fa09d69c713ef6805dae7111d3d8831c1604d929cf5f3b64d929c545b1caed666651d957c316eaba9d051ef74c6f93fd0549569e3a6376975e7f26fe4894dd36ea81d15cee52795d7e20e76a619d37d13aedc0ff5e6f9a4fba62e11e8c2c4c58d25d3ac485495699c5155a6d99b38e7cf69aaca649ab111cbd3165aa919baba6bb6d18bafc6fd97678ddedf516aa3f8a6fbdc6ee39deddff47d1cddddfae689f74f05fdc5d4acb67e50fedcb9f357f26be69969d2121285a2e2b4b4dfcbc64d3bd37cb25d5b2ad55d7395ab63e56f6db16cdca471bf97ccbcc5767f5b4dfb2ee58dedaa99d336a9c4b84a979bae56dea455bc9fcbc64db17a64bbf6fe94adb27153d393ce4f83884e1470c2c2c90ad1e9004e41884e40388d426140b4e695e194e41fbeaec99f326d4def6b6b5632eb59da29bba54cfa7878728011b35bf29e98fee2268c9f9c3c3756529e54053c046207aba52786942859a2ab4fe512d59757ebc72200bcdf5e5bde4ad3b67cf3dcf771b7caaec56176a65965bfebd49bffa4ddf8caf67d9ceca7acedb3dd52a4952acb2ad32736cfdc99497dcfb26233777e8949d3aca4aee5ef1b79e2299cade50b097959f0c690103b7811820086b20c93c8c8156478f192c6dd573a579c33775f5d40e5f396086854a102549859d02447142d2531a85a491c54dd88415e8460c498c921012bb45c85850e0cee5e1099fb1702980f0423d18a189d472128f2a764e5c3609f3129cb69a95c10ee06d5f29458850cf7efcb0b5e9a0fbc78f075f9b8b8fbce7a678854ae70779dc71c2e7285ea8b4d133b5524291263d9d6cc4195b996b76ea8df780a069371d992a4757512937444241744a4a224aaca34eeee9333faaff3efccfc5398de4a81b0366374acd5b08931d2182445d61cb575a5328e3b73df273fd37aa3d5762d05caddaac36128bc2feec4587e632ea369ebbe4f75b6625e5ff3ac690d54c33be3dbd36af8b18c6535ebcfaee17d1fb7c97a95c68a4dbcabfaa7ccd7b8bfed29bccdbdad492b7ddad20446f3d4a6b9edcfb63e7a8ae21cce4e4e331a4433a64f6633255dbb945b572c331a4461fb7158b775f97febb67fd3edcc52b7b3d570c6a4d604a81a233f7931b9b48fdc99121d69f7a4f71655ab54a540f43fd7be56f5eea528d0d7ea54141def163afef8e830962326282bb8a97cbc9f0beefc8b9b366ec24ddf943f97db62bb387779d24ea5d04b551a853d65797a52a2484b55b260d1e57d9ba938e1b5c0840c77a7957a669830e1f92032a1721f77884ca08ca5a5e9884473e0fcb9dc8fb8484445d21a221094b84844dd475bcee0640cdee2c5c50d606711387c8002359ec64712fb16a21206f82c6a466b67eee24e5d6ad3d6fd49356f2a25b39a6f052a3feed41723e576aff5934acf830ae28fa954774dc2b51b8583192949a2c3d72693ddf6cbf096555d92ffb6aaf5e364b693f6a59192244a5eaa9a34a94a922fd584891299927ad3b29baeb7276f95edd732bc652ffb8bb7a6e1f0d6e1f2b36cd7de94e96afbafc9f096ed9bcdbeae6299b632d9d5e19bae3799b6d9acaff799bbf677ddd56ed3ac3625667d9dc4acab28442053e7f149f9629ee81367b2e2e80cccd47dd99722b39b9cfe9d619f7341bb52b6db7667cc24139150e12fdb6d9b71988a93c9b6ae35b3de8cf74fe1cedfd5f6e697f7d45b5b538aac264de6791f8ffbb883e0e29137968c4a928c4341578733eda6ebcd027b3582149aaba44cf7d71b722f26664085bb5746ce4a337a8f84a399eb538ca340206ac085fb78b1d5a7495ebb6ded4247ba2bdd57d4c0080d78a074a741d5b559adc1cc5dd4c0c8dd450d76dc8dc62441e44f9966c569318359064614c8dd5b2e66e043cc20c7c81cee0e44a94a0ba39534e3702e178d84e114a6716f7ef95f9ad8caae8fcb8fcb1fa5fb262e195f9bee9e99aa32514d475cfea8f14b4caf05c2f94530cd9a7ba99adba273a76cc4fbc7a7bcf8c876edc65c46fd1b4f9176df6ab3365a77b4ba77ddd5f2e223a47147ea237f73a73e127480714675a54a546250b9f33449aaa740e5c5e443d1b1e24ead2bdd99db92f76f2533a6650efa916dfd3459c5b92d74fcd9da874add9a736790a634466177898ee5ffc65c70d3ee5626fd1b73d1612cd509476dc9475b9f4cb92d4f4875d7a4df98cbb54fe2902cc4214cb88f60be50994c1c8254fe99b740fd99a5b4d5a9441282852b45e97ded587e06434eee9b95866daef7494da1e89804a6ab619db18e3ead029d5a5bd95012ba35c6d1bd8792e0bda3d53069f7e36edbe659fe9959f5adb3fb393ca3b7da5d7310de417a2c2d3d62620c4ad9f2a76cfeb2b4d45a4b8fc44cb3e69f52a29d9afe8d02d5dc89b38ddec8aa44c7dc897395af3d0e9354dbfa64cdbafc396cea2fcf7b8e26c63d8fab3629dd436bf8cc659c4d8c73380cde26363176c251da76b1f9473bd33ca9ec6e0c1f4969fbb471d3c64db66b77386ffd49394bdeb8e97e2ecbc64d5a5355a6b7dd7a2bd24f552b1b37e9c7494a493b53eb37f33b55bb7375422abf2c632355652233fe146e5756243a8e74e3a6b734729ce9f09dcd92d00da89e0599f54baa93222b8db630eaba504d9d5edf6cd60ac651e597a749be49de2d4f92a73e731aa8fcfc3977d7f1c8f880eb74eeaee4ee11f1395ac3f4f5e7b28cd9ae2d317995caa5fcef4413738c728af889ffe29c9576db9af5d4a2c3587ed7a412e32a5787ef953769758b8e5155266d2bcf0ea82ad3c576d7a76d71164b69bea50283ed70ed2e5dc0bde8b1637f3ee428815781cf480aa23802538457c3cd8a1e2b7820031e2b42308128f643957fe57629bffc9c549a59ca2f6d75aa59497c69e1ee321955c56da1aa4cf9a576b72ad1f1a7ea0dcc175a67b66b67d91ac95ffba4fb53dbde13d37d62a09d59b37488e20db4a3d15d6ff868fc9a95f23be951876f9ace76eeccb42ab35d4b9ee5e719fd4a9f6649a819f95cca9ffd6c9631ae6adbadfdbe35d7d336bbeddc97b2202a85cec61f97aaf9e54ccbca1ff3dbcac70235a34033aa2fcdba7194ded7c2664034887e99594c858e3b7aed98f1e7988c3f3324549569e6437f664832a632f3a1fabeeeefd03834164d69bbefefcc7a2bb5edda7246e2fbd987eadca969add5309899ee2f0c96df566f6522ef4ed36b75a59d9896b6e66ee489d3959ae6ebea4d3fd151df9ad374ac396ffd379bbdd5fc407a06863e91d9ae7dd9971985697d32d18262f59e3f3e54e34e325bb2ccb8ee68b4ea6ebaceccf2d65b26f363a6a1a1da121d1f97bb7de4d6ac54f3436121fba3675787674d3e74d4d966697496df29e3a81998d9ec4b352dce6a3ae6af2d95745baa976efabef92595324f1ad561d3dcd12ad53847a544c719989a95806810ad59e9cb8ce6ff817dcd4f6756a29c9492645e9eb244412d59998d60661a067bb2d6f2ef2ff9e9583e59f3e7ba2d05ca785b0da5c337a09a9f966fd6fc3b303318cc342bd2eccbce2c7f77ebcedcba62a1637e5cc6545b186c46afc59f31131d731547b59dfdf88c3f3e7af75230d8cc9a399ceeb30dfa4c77401bdf5aed4b4ccb16e86737dbbd946c36f536efcfda666b3fd75c5a3033aafab76dd26ad0c537577137bd3381b22df76b8bc3e4c89cfb795abdb7ea7cae74a16399abed33c5b56b7be84ff9a70fbe14a8b65471b61587c3e4d32ea6e99e1e5abe6956bdff6efda4cd5b7fa6f9c4359d0f35eb8d3e79f3e77ea7ebd3cf47767fdb9a92cf65898e527467d22c494bbdbb313ae2ecc524d5fbd69c1d6fba6ea88d9b8eb2ad4e171fe9cf98093749ddf386244562f36ec14d547054cd4ab52ce58d416ddc74dbb94fdae95b7350b66b97be1e95f5c834ed0d496ba41da2c13ae38d6b29d57f061a7a4655d6569f0175869433a09861821faa2d50f93ef43735a30a3372a035bf19db8c29666c60061133723405d259e3dca5b4fc0d822940d082ebf0a59a4661b319bd5689cb1254931215a5a4b17e2e57631778088271acf9a16a7ed96b58195a5051861265cc8037fda1d7d26b6906e60f0fcb3075191c543347a3902187b364cc408600dc69143214400d05f3e5068b6fc903bed50616a020021068a08a3044f02b7a946002d896d21381980fc75c838b18b450830b2118101ae2bd207073772c4b3bb2da1e8bf6606e029dc7a0a08b4d0fc5f02f314e3ffed24e6d2757e3815b7805b4d8c0e3e113f0882ef00037c3cbc019303ace4330a090dd6ae66460fc70778f7a4b55996a98aa32d1c7d9a8f167b773b8af9bce481a4493cc7ce88c9cd16dbfd27476cae66d5a5aa5e1a7291dc58eea789652a4cd34ac292e631ae776e6d6d5047a9cdd77fc793d9b51bc4fbcf301ba38bf083b6d6f7f6aa1e3c5f4d637cb5b6bdef7b7a53a6c858eb9936e7c81a4347dda8d7cf25a283afee84cc378d3abebf8a3f5a1e553d91ad5554777399cf73531ee326b4a12d434a33b13a87c2dbb78f69a6afb43e5890bad741fb9b8f431cd1cdde55a5e4cbea65abf794ffaa479e6708e3e4ddbfb9f3f476b96ce1e87a16654ffec72cd3887fb283aeeaa14b6fdbeb6cea066411e64e91245c7d9d08ce27dab69d68bf5dfc8faf966a1e3cc4a549526252cb2a82625a62a4a50b2a62c4b51485b9ea2a09ab220c99e94929ab6e44e5ac3d706e52fefd9656b5babe1a02f6bdeb4d297aab88b2d6d22eb7daaad3573b94a475dbea9c3572ae85a4c06557a234f4df56b9ccb676e97abce3f55566d2b694dda99a3fa27e862fadabced97b2a60fd5f6b3aed476b1997fea75f84681b48949bc71ced16bbdc49870f937dd19dfb43eb125ad5971b492f74fb376aded5a9386ab369a712d2a4b138d82dac274920f458bd01109926cda7d4f3a54feced479d7abcbf2c5f01d61e9cc34add48c664c672f55f7ec46e29cd24780717fadea9bae99a4444d4e5ab3f4c95f4cd4e4bc9138f72ba92caa9800cf6c637363535fa5a7d5239d475aa5790165bc56e8d20c6e0850e1ae67d44966bb7647d6d296b792b227ef89efe729d997b25bdf24711859696526c639d9ef3771969517d3eac5b4f24f99aee6f2b6adeeff7f5773b2fd78bfb63299aef965f8ea376935ff35cfa96b65fbb5ace697e9fecacacff96b5fcaca8b699f6fba6e2dcbd5cf52a495d2329969da1b89c3a4ec5a9c6c67e672350246221024022fba7ba9f2a730131721e0228b17c1227151e5eeb66bafc597c2fe9e19c685c94554adbe49f16ee2a2888b0cdc9dee5e6a579aeb9b483a8f19870bb798dac26e11e5f4a6eb4db6efe79aa354e31c85edd7b98a93d9aebd67ce651caeac32fd26794fd97e9dc5e97ea58d33677c60c226c783591a16a248410a6e3e8454d0c3bae361a51ed6261e56260f2b081e56ec612de36125808795080feb023cac1c0022a651e4621a486980e0621a615c4c63bbfb4db86347f8c4ccc327c278f804091e3e0183874f14c0c3278cf0f0890478f8c21a1e3e8185874f7ce1e1136578f884093c3c802706c1710de1808b43d47071c81a1789ec7091088f8b4490b84824c945224f2e1209c14522d8452227b848c4051789e4e02211215c249200178960e1229108ecd0218e61c6c5316470718c1c5c1c430817d30071710c03b83846152e8e81c5cb25fee02efef072f1071e2efed0e3e20f485cfca1c8c51facb8f8c316177fa8fd70baf80309eeae73a4021dae1c20e2ceccc51d9c8b3b542eee14c0c51d225cdca9c2c51d0ab8b8f301177748e0e20e1c2ef270177954e0220f1e17798c2ef280b9c8e38a8b3c40709107ce451e23b8c86306177900e1220f285ce491051031bce262287331a472771d56887cc7ca91ef5861f21d2b5c7cc74ae93b56c4b8bb8ef08b91875fa878f8258bbbf31095d81e2a01000f95b8c143258cf05009273c54e20a0f959880874a6cc04325d2f0500939dcfd95138a3aca7051c7085cdce1b9b843c7c51d405cdca1818b3b9ab8bb053f441cb68b38ace0220e3bb88803142ee2e0007767f9d849dabe93b4f39d24169272707642f0c07742a8be1302ce7742d0f94e0825f84e082af84e0835f84e083cf84e0840f84e0848f84e0850dcd8bc5ca22836b9808b4dca70b189085c6c22878b45372e16edb85854c4c5a220178b602e165d71b10804178b6e2e16e55c2c1ac1c5a2165c2c22808b453fb858a4848b450a70b188022e167dc0c522365c34f27604a8c4a804f06da5999a744c213333321009000000931200304024180bc70312c16c9e86ab0314800372a67290521d8aa32849415219c20c0100000000000020981a02a5295a29c7c8d19282f33e01009b2b0265b1c6599152c7f19b177053c43555c0be14b1bf9c5fad8f1a6dea9b6020874aa802915f6811ad2cc131fe698eb2978a280d50a1ef92c4c70a46efe9525d1ac4e81034e235d5337003df3edf50b9ecdfb7fc52a17fd7f4086af45247e6af2f529cfe8e3149cd0db26bd903b6e488f4226b19e01250a3e9a2110a17132954b2729741f1642cb47251420a7add4522169a8e46a998080f4d3c70dbf53a0f36dd9ed1c8f0df14b9ebe397d2791b889d13924dd5ccfa3023e766edd6e5f676fa397900b4def30b070fc2477525eed178d3873c083c285cff25a8309d66ea8a70a0cda27360f908165e7a25aea728e39205ebc3182a9f66d740cdc774053a8fc169e04bd9730ba7ae045646414bb46fa4aab1c482844d99f544e36a4259f867339763fe05898bb9a20a5658329e25a8d15c75f9d01add8f8653879714a8aad6362c8b67c86edf38147d9b817573d83271a1192d830a836cba0e6fdba0230b64a236790111a66f81a5c10025160bf3b1c71baafe84642c27f24d5aa5b18e4a0d42349f848a78a8c5202d01ccbbc8193300d990b5dc5ef3a30dc7d2f8c94a8fb15806e435a5310fe768b2f226898bc334e1510b7a2d8832e01c3ad6a8577600183ffc3dc1aea0c6f80a6ea843ac6c2739af045a022eb631c4714cee200558fa4f71291f4ddc0f7c327e2b8899b4e1cdb3329b898df6cd3ab645eba7a92d95ec51ede37df57e99b801ed5c31978614f484bdf085cf2f9379c6af1120197c6cb5b66bd50fa4cc28e8bc48e48d19411a1545469298215a4902f6e8280b71a16f6ca743e3034371caff99d6ba142902ea7ea1086f52c049b0fc08ee310b25951c59f3b14f485ccc16fa5ff90fd808deeb3a1a08e59eac455db4ab051a1e400c887887a0bfb39a36a4e01da402a3c3a68f6a2efb681b4cecb44ec43a9ebffccc0595e83e47958328886dd0ee15aacd5d04cdba3deaab3a039a5dc52f0f519ce93515b2dc2513e911cc68f85a50fbc8fbb01215320a1f629f5a82146128b840010e72d8235bf803c66df8c2769a9b6728b807c3c830ccbce0954bc0b7a6c81a5479db1e6b6a1c1ab2054f8ddc9786ef7c608c6aac64feca2dc8299441eac3ec4006dee5b87a887605520c18f44abb9208536d5ff3a004775f8087e46a35398e9c9bd99239b8dd985493b305d7bc1dfc36a84680bb83712ea93ac6e77dc19b02843dc5658d2a48c3656cdee3e633a8652e69e51d218c104c913ba5229253f3b5f86fe348a62ac219d79ee8eb07221073e3671eb8a0e38b5b1be86b644f52ea8aede9db96da275c18687ce2625a8eaca81e37b2c1ea06d007a0eb32feb6e9ee7fa8ca87d8a12286ba76c42793dcb7842e72b0161d36b22fad1f79c893a5cfee9a3e5bdf39de13c96632e5a4dfa92d310fe400b42884e1bf8bc7d66877da31a00477e4e039009220603780f1dde98ee7f890819d0022e88a8ae4203cc01a21c1e19541288e058e01e7a016e78c7dd60600bce0e888096c3a436ff7ebe513b3f3a7cb90bd6b244751329e56b5e45f5930781b959a41239a2796118cb91708a260dafb8158d5bc416e3d8ad1a2e8eb9a4695851d5583db437d32eea0098e4efff1c8538d7b75b7b179ec8909901f5c4bd627463240404c14ac09ebb49dfa20f840094e90038886fcde1a7fcc3dc8ba721a727db6f1d62f32803a81673d13991381179cc736790a9155f57f35e2266486d9f7b1bca38de118a5c8eb9098f3c9de038e6af2da766307d48efae1b6e13f86d6ab2585d86dc3276cc781c28cf08a2ec7501620d10b0e2bd1bda1f7c9ec93531806e46d096a4521a3ef7fb00b084a6549150e5f2463db5db0e6da1a1c2d439b35f1c7b7062be2c5a844adeace61be787c05ebca890a291224cf00fbb807edcb6cb25f99ee38d82d40af621a3611f1685c7b2af18bb111591b4158ef6f0f334cb1e205439a8a88f00483b1421695df54f7fe4cb310dbc651faf56b3c6089113f87bc71e8011b0eb24cfeaa13e71c87319ac45469861e8ef0c66dd44eba1cdf368ef2a4a7fb53fee63260d5f839034786a9388b3d635b10174966d94df2d14686954b5e877c1bc53e2f96db90f8712979933cd632179ffc90b229d0bea9c5472297da974a00976f8de68b1da90c550fa7f98ae401e0ad26dfc0612ffaa83cd97de3432c98f8e8840dacedcd49d146b2de82944d8bb8ba11a581bc28573335f946148b0fd5aa7bb9162d183911dadefa20fdbb894b5578203e7524ca6d1869e0296931f0cce558f1431c54de2ce131f31149e4445db510120612a2a2ff227d3811547ca8f1aa21936d55b39b3ce73b42347802869f2287c36e9bd5e58c0b13e5473b2f08361a2e82357501170d42d7b3ad97a92ccde36342c70b4b442b8d351931650afa7340eb4088a34907604de457bc0bd4e77e585d152591cf9b366dc3dd46ba6b64777e2fdfc3e35e71e1b7cecf0730156c9c5c9ab70412c47c1be40be0da9e5dd53f4e357538d3d62570ada641da09a5f071f27c4aa9ee8f295d61b1aa2025a69a62468270ceb18b6def77924bdd6e97ee68d29cddea60a01cf9f529e77fa3498499ccc9dc7059a0bfa99d54fbb93fd9316266891df4f2429432624cb5530787f41792c433c1f2100fa8165b49d4ae8fe60fde9ed641140538a16078310cd2bfc96f5447273741d92b0eba157fccc94a8c22483acc34d222f5df2324e2dea7fb0367e354e8d76565969d2cb84dc91aee3a58d28ecdba617dddd6fe34f64deb4455192ec92b02fc8d757f90caed2538567840a37ab042fd0f91d221e0c22ee41025aa123f5a0a6703855edf03e4ec821f2a06ad04872b03046f0edc776d0035040448dcc7f29277cbb9ad186731b0ef6556859b9da7b5ae7651af672ec568a241597883a16f9a553d031a5426d87534edd71fc0d8c1de4be2c5630cb4781a1f40c770a345bc2e9d3cd23cc154028cf0f34efb0e84beebae9ff904d60ef88098bb49138caf8d30e15e2e8cbf035939a9ca1c1607bb793ad8efb3221f86c5a0e80f85e9ce1b5b36f69acfcb5890cee7b7043de4cd954f378b5c218a1a5cbd28585f7f7b527d303f92c92a5e2b7ca3496ff9cd2fa7fe91b3db65acdcc4df634e483fd35507818e0848bf0c69dde68d580e176f59aa23de20f1021f2eede580327e2963e955e0b57ee1b1bdf42cd74bba8149927cc17d43a69e12d0ae8ae63b550780397835ddaa317434d1eb604918909ad781231636d1140c1b3cfa67530fe761ef9b2a98ea0cde1c51f1769845824feccf0f9fd54f12871cfd878f0780fbf1d60f4ef606df7e0d4e16a36d6439c62a20a8a5cd7014a828e69af6a1800089a0671460b23438acd3796f99864be1b682eb30f5d50e90922a99a35049934a92238c8dce91cff51de3dfd934b783d8ccd7be13bd319551841047369a7d41671cedac2ff1c6da4b0f8922d4a394d442de5fa35c64cc6f8fcd322245a92cfe6a7b5b7fb10e81ef4e587994876fc98b3a2fb7c02863ec020dcd82ecde9650599cf0ff767e152b40d0b1cfd91b5007911aa8834467ab8048eae88771918c100245fb8d2c3a0e110169673a9b96c3fde2ffb930d2019db13224023e8a27f168e14d59166c8183710ead7229bc7a3e6b3f780111488e81897b0aae6c6116883ffc1192c84e6890a92148352b2c5d35466abe150e5bae7c34ccf91726ca13cb2e3f1e1c13af06f3aa7260736b08ef9fec83988549592c14a872ec98e69e05c11ef3e9564a6d90516ad899bd304b88d8b75a480ccd2e1c7a1cfaddc05a3f5ae3427d6079b01ffe349e67495e128abcf630ba66324532bacd578adb9cd99086dc15994f4d1f79a4bd85eb9359e6b0df118198f55d99d841e54f7fca270b7f48e927acf9a7daf36a5cac2ea0974045d4811f97e21a61c4d30177074876aad26316a95eaa476beaad9678bb64a326752da93c346c878c071edbd15f39636dc7da9621a8a6ca71973297c94afa2e5580bb27f5a3e5794f6be7c5dfa2a1f71477a0c5b8da0461d4183de6800c3724a4f023fbdfd1c70b9b7acee58d28cc2ec9c81996e13ad4e09c771ed94503fbe24b97f30215c7f1cc25315cc01b6d1e7bb2b28b8c6994561c4ba5fa605b35f358a0518b14159140c06934cfe69cbf0b86730630812fa559818e81608a55a02af318528ae8fe3d5c405708c9329bd78c36b00e3a2b45b31c4982513dedd629875a026822fc2e54697ac53b636ddb016519463e1924ba9640b02c1e42eace837c8b3892a7c0200d97d54de3ea9cf1c38d6e8b5695142abdafca574dce53086fdaaeb4bbf67690fe105df6319a1aa8d3acacf51c58a8b279faa6e7688613f822b87717733da453eb9ca6b52861d13699bc35d3a4de762515c1f0098218d6b8c82793f899343cde6823fa06f12a2bafe42185f8897a7b8e0f944cd0e510f3486504eb821cebbc22068640a3160d34c41f0976cbb3e710bf957012a322cf32e4e8ce574d1e4a2a404484d2f3ab355d144a703781422113c92238adfef0f48c356e9017455a77464921212c1cf496a528c66333d3c7804435da3d4b6fd8dc197fdb3b2f43908700d5f619ed33ae395a9925361f82a7df1bd1e8b154139c61cd3e8651d57b2cc48ea71a1411f29084c65ca89fff7661d0d2ad61a6de140e82448ca7997c066fec0eaa7b58387d7108a8c1bfafbf54e8bd1299e2608764891abf95f6eb6f7d5c31bff1a49c216b7ce8aa00773547cb592c57ed96ef8320ea846810ca8a5ef45e64ce028663af4fb2586f22cd354a86750cd9bee1b345ecb213e36ca183b3b538b928a54cddf5484742e46e920ccc42438580f71cb8cc9410e884c1e95191796b0ff55f07024941630d6ed2450a90c5cb8d3dbfc41b8e38126148635e3460901c2e9d2e3dcf22f3029c1b01a6fa8305524dbc8b9f52c3a87ceaeb3ed09a74221ecbd19fcc6e4c21d92855e936a902aa6a724cc817c08e5668ac0c7c10c2bc4278d8e57ab0c28094ba3ac20bb5692f4b7fb35c30aad25a952ebac0fd084cdc29a499d1b74dc2472f23089ea343465a12094566a9ae347fd5f38a78b31b62ad89ead323a884983d1bcd97328fc3d08081662bcbf0c021e7594624140058de19c854f9fc248aa069123e25218884fa4905dc1018f523c8e05c9ccc241f143a17b214d4a7099d7cd4b381c603b9cc94895d58970422c182626dc44197544388cd6b92a9b0612f683084a53b114c91094b2e5dedb992f2bf4de0005bf6deb0f0bdc143b9757f720778c692b474cb985d1022d2d5e7c9b639911bec47134fb36a3160449ff29823dec41d34c035476cc4406363d480de3e12220242b653421a15a5ed521f3d89e95da810e47e87a356a9c0837f19d72c092b2912b33a7bbfef0c6064828675706d9994ab3ae7dc235d9cafb852356300e951c8b662b1719382a78dd900856fb9d6b6a59557f38f7516ad15465e469e3ebb1707c97bfb060e48e71b39e2f5173985b27242d4c12a00d755a50f7336bd665bc125e8a756faede236086949c3d5ec911ea6a0b6dfd16e4fb2e4050686c38764a35275a0506f65a920254c173243675610d5b5e36404ec005e93e43a3482972e500fc9ffe124a0133690f08182e830069ca1310c66bf2dd407e8160034ec5098caab0c84a1297dd45d59d388093e9710b6fef01ec03556c745ebb103c72a805f058bad4de22772743a34a6d4c4d3b8a1116c7d4c06229ff4a5fb9d7e28dae5b4915c2268c0b9e8632a5bbe78913f569325ad9b329b6410a3cac52b8927548bdc64d00652796ede21570f3f2fc5c0cee8c0a2c2a3f90b051a11d7812b1d30b1bf8d544e381978b903f008cdbf3a0d13008bd1d80b0bc40cca00ee27d2fdf56a677b1bb9e757483e09a2df3de737c18286dd7813efba4d9525309b54d313ca9fc01779acf153291250225212a284eb69e5b521a11fa2ac38458338725ba40ba2dd9d7b7ecaf0efedeed42c03007458ca80f6185db70bf1d59b0faf906fd07a821fdddd4189a1cd360cf76fe4d8a46823d16726e2715b68c6c74f826cbec7b893f2ea5f48abf73109c2dd111cedb1816510885154d17d0935d62bde262898d45c5c1265a7bca14ac19d5d1a5ea251592828a246d17f9d90cb99ab4626480cc614a8812004f88ebe8849a830f6c29d754bfa2c732f14f3557f4060c2a4308b93536304d19626656af1622784cf0e78ee96bef16c465465c3d315e9055dfd49497c12f6228da394f56d95bc0f0644260eabdc14bf0a7ea4142b713113541880880405656484c378b00ea9bce23bfe17c4116ac23c0d8be3ae81138d83a533d45f311d0a10f8df7813dd3ddedae798d7243deb938ff57541e3625036effd654d855f8e459b31b83bcd6b08a6f3946899e113c9bd1f119318063906d6e2f0b3ae2bc9246e436073a20b3061e223cba9c51d8d7f7ef76e02e01e14faed7d77e2cc5652496d850520e96f86ccd3df7a03a946416e15e72de9fd83dc605516721de3767af2f9e518067105f4f87bb4572a7bf328fd390f1ed2b3ee51894deddc090188a1bcc2f363b2bb2f3d307254c0d8243056c789c395dcb1562c8b4addb53942970d4da5fae223045644c5fc6dd31697d648f048362059b5da8dfc6f652754ada18af960e30de9ca989bbd83c97d301256b37c92ee23bec863d9a7f9b07e28fc754c189a50bada349e56f95e326d75066dde76b855e16f792ed8c0178aa992cbbf7d0b14494d7e85ee60e9d05615d92c0e4b16f6ec8d9afdf610bf311c0a31f23db028c46296de47355db2653b57562f35a8e905c19c9e126a43a0e07adf810a5d66a12c9623ebec70a1d69a5ece3edab920b6360099a9df2f2088a1dbd2c8e0dcc9d0fd5e5949ad024d0d82c402a506ab8fc4d25290c5f20548818bf6683d12761d566adc07b26a6231f39ec1c5b1e26af7d594b17313101fa188fe718b009677dae0c8d53ae3e04142da5f6d3bd4076c54f3775c792d073cd84da7d7025903c5b908cac3456dc8c4a7c60a16af90159fd6ea02f0744b2bc702f1ea05646683a67648c7981da39354f4d258abc3aaee0abb458e39352c0e449282c57033d189305ab766fe04f79c35e02dba19dfb56fc3e6a75b3315c59430baa303324c0ccd8317482ca5842d23251bcbff6fe5b585b8762c5b906cbd60b16e0ca0ad2a837b7fdd86047b63e1b239fe2267bd4b61eddd6bd0da6d5c830a82d1035d1a30d9fe73cac1120e86f1fe8948764b9e73985de4b542c8ba75b98b275ca4878ca9025d8a3b40299849f41b7f01dd3f456c11fc919721b9c06c0dde4b6d9a22ad7abd2da1e99729bb383bef075e74ee91e41f76695a467f1596ca92900b6d3064edd5d3f33a7805935c02d98ea99efc922966d0d2d7bd6bf87fda18937316d5e5b7c73ef4978c34e4ad41b96fd4beba2507296ead3a778bfef7e1b4e1be6dc856dc3d9102304dfe139a2fa8680e5de887d3cac6418ab65f51f03b3e7c47f57320e3ea2a16d5b660270189cd275fea9d6386a7d5bf491c161bc28ec2f3663446fd0e4601e832257b976a08b631a4df8c9c4e6278c44038ab781e02ab1a43a91bfc2e6760464cea889a51f1970bbc07588a9908a7af5807963306ad4a57cbe6864d5d85e2598b9c8e5223e1ca1a1522f338edd78842ebc824699e8697f1f708252b028dfc4c7165e8a56b0f31da73ab71eb160569f8a5b71d8c101e79754d5d26230d80ab5d7a005ecdfdcca1c203b7f977dac8744901efc6069cb10674d2884baf10111157f76a10c3f623bff044a0d7f271b4d4e12c3590628806a43f3c2a88a87f84e9949369ccdbaf68a879740cf6f38e18b8e5247558f734a9ac52ae94d6fa159bcf5a3d193898c4349298146b8ade3cee785f7691027815cfa0690f7751e856517e1ad27ecb6fc44c4f48ccb1e6fe45b00b556f1f8170afddc15f31bca3cf78a23d8569ec74f8b2022f0700f7a62328a11945163121b79decdc5156ddb05e50e01c3aef189b61d1157386d4049098b5b94f04cb4046038145a6ac3fc1d144bf9877d9da82955461304ef72d9cc75ed5d81013f92461907c8979e11cb2f301250e0d1a05dbd317312b6711f344571ec461a3f7455af61413600390b751107c90c1f1affb88c68d3481071dec063cbca549223b6d34a1631fe1d2ee644cde5af17a06352bfe0fde4d7b2da0e3daf5ee2fa07f196c15974897bff81d61e435d489b27d2af986be6e297564e520c167617d323e94d1c6de1e859962a4b32074a63d8c8046fba8e3eccbde89c8c65a8fce93abf3db8bf2dd82a68f38744cd98c663a6c1e643b47b8ced4e8382f4f9232ef24801e6f0be047252cda4abb5d78bbba4145b2c6dd810fba1ebdd891ac38ecb7441ef62625bc5c86ef723965af821210f2ac7783e20ccff44fc0d60b258be4f3078499040b033918504eccfd647ca4f410a1c7eddc080e3421f4711933928d5af62f489dc09087393bdff1431ae54943b3d88a8882e9b1292ccbc506384b963915fc1610a1ecd41d36504b7cb8a14cd5ddbcedc487fc7600ee88249eea728ac6b8b78fe187aa05e8ed8216f9d107452a1cc0f05c0483074c5ff949301f9fe3c3942b6b80cd55ae5e1c7e18401809a5ce6b1ff47737540aadb96f106f7691e931709ebda44839852ef403659d1583f5390e5cf83280e0be6ee837d6b7566c919c95696ad77aeb91301587bab76b564f3088cea47caa69794445f5c2f74e4d1a034c3661a32ec8270d2d45b7d9bccc53204e57eb8874b932d0c4933535106e0e10593c95e13bbf42342209fe73ada9948470098aedf8ed810e607adfa7a7b37a25a17ec5dacf3fa4525d3f1a029b7ac08b64068cbcded6d7266ee2a550e8df04a1561e3ea2b1d9c0e7f1782f3838bb49441119ed74bfa7702e54a88354999ddd77af9325e073465fd0628a627dc0159a7391da9c322a6b15d802af107069d5ef217338767259adb17310c4cdaf9913d82d395dc7e827fcd0d27c493887247a9cc9a157c373f8ad9d76adfb503c03e44a795f499b823f7ff69b9c479c23a7f11f776436915cf735b96f07b2d63a52d7bc834f581db62c36a43346d7d4218025550935872da447019bc06232b34c1f710005988010cdec2eea65df145025e2c6b55c50b77a467b91901ad36056b203372be10fc682d64d7bc7fef30cfc0c0f9d20dbc14aef366739e3f9677c6e3c00c371de4e70dfff9369a230c530db6293c4fdfabd16895bfd64b44f703e9eefd42f8d3f165140316b529244787d0f11e321e4d840af54b0e2de66df5ed6eeff61b9825894d7dab9e9ef31b2a5b3afdbbd81e9ea7517154725317107ae2b530838061a34f0c546a1325cda3658f00fd7858e0c3a476796434d16f057d1188cc3dcc9b0341b253a19a2e8965e3f06f61ca9d3e8c9ee6c084ff5180536f3052eb38543076843e08fee0d3f62bb2d7bd3baeb27528bda8f39c927b2b2bfffcb9df83c96302ed5e82c4342e42cd9f734b8318aa69be2e9e201d47d78779840c179c8ec37a76d750e09dfe7093674021b4f55b708cff85a11388254d88830d4e6d1e8f63ac43b2610f483ad4bf95309d47e3583825bd10104a1ba3b5ce830e8c5808930ea274284c0941304ab6509c64e90f9aa8d8382d3c3bdc88ea2fd9486e38c93772665d353d2426e32e69778abbe50a342174d4b25ec58bfe550f375ca786e6c8f3d5661a1339ad41c227f8ca18f476bc457257d6ad6b6a9fcc564c6dd50aab81b2962ff5e3d034e1447059d00c4fa5d4936ca004aedec2fa03259317e05daa1e5d04b1f77063a4b314a937c0d04426cd991dc05f68dc37f99f151c6cd4ff7fab42ada690df2332724dc168ce9c5d33b6ef201b358b764b37c51f6ffaf884695caca94f556087d26e82fff1ba2434af893b9052aea6fd1d37f9cc490268535e6064f07b7682dc8f709b5bbab8867e940df8fadd940a8865de41ed839a63290e35dd577dc2a89ac58824e8cb9ce37a64243fe5447b68ec3a56a96202232e77196bee9f3a8db17f7b3854dcb871440b43855bce9d6e1fdc9485f59b2b33f8a8e1f8a8706aba83fc199317b8fbacb1f8b278c6a92558ab7b3c005a71c22fcdc766d71416c50e7e5905a02b4b90ed6350f22cd012007738df16b0d473c31b4676dc1f4a837d15993193be17cded6e673922f043bd2b455ce4807ee8e2ad64cbc45693182239433a83d44a8648120c66247c7a530c23b4c478600878b14c74686193f5382e921ed7c1d5b97d1bda96c1e2e852b5be654df1ff2a3ea0a84efee9c70b20909697a2e2a6ebb2200de5e82e8600b5b39261e9ac22b41706a02b0d371c344ed7e665bcf55dbfba1a78df805f1431059b499fcc9bbe0d835470bcc224458a89bdaefb2592531535f31b61ae10904f533be9890e0995d93590fbb4cf5dad08d3c16cf2a8c5b3eaf51b19e3b66331f55a2321e1158f985955991b794ce182d52a4fc450bf54ef735eb7c7d84f256979b6cb24b527877cc01ed758ae28850e3c514fbd9848f15135ae7feb59470336d008348c7b7f293611f6d3c439060297ae29016f12085057487388e8dc333b500d59a0cff3f2f3cf5ebcd07ab7414cabdf15406dd99e22c629cea505742622f121a21d679a40b559143edd23041c6866d33df1a1460b79853ee318666455ca8abe60c2a606b3a13f3be46d15d59ccbe4db48fbcd19ba517b3e9dec272fa6b200154117274da09a43d9ccc93730626ef9a6a40b89449660c03f8285bd03f16652884116e02b876ed62e183f5789a580763efd60fa8feb4aa6ada391b93c868089b4658c8a795fca8e4df7c20a5ec6bce3e63953df8a0a5b2593690d0f061fb9b0e8a13d4626ba5ce5f5a2a5a120c4e9f601762068c31295ac0c2206e6090f3758f163171fef3f1842c8e48ad21118ed83fca30a53388987fd77bfbf0461ca7be92fe4c77ddd27d3e381a87a8b3f93258be15e43481f43975df687af40b743d6c5e87766cec57a0d946a6f46fe9118f6cdeb68fd1650bc74948325fdb5de23901345b6d7958747c262e79249b88b3c423d5eabf767a3fcd6ed80060c1759707d24323f41fab51405edac29ed4ee103bf90c494c6ea381954a2f0c268f69aa394808472e0f5c64bccdecc7b87e8fc34a6cb81ea685140ace3d0eb36cb8f4692786da468aa84572b06655b1d5d488f4c35c958def19a31f7a3aea1fe5e3376f8238b79656b54a668808c7f043e42d4209f85a9d95029702e5ddc9f5ec38e4b09f8baa002809855e6c21c15883c8da6b95acbb99023ed0276975052022109844d216872252b3bf3a2dbac0b2d46deb08fad4fbde0d980c740147fa22b8dc88c8ac26ea98bae55e6215caad98a84646105982c15b879c83c5b6b6a8faaa4010f0e11244c8125e10aa0d6aa80d2b0c5bed83b554f813bf95ed2e54e6fd845ac6106b32c1921e06274280897936a262bf86f8634b023982f166a00a54fb4c3a23609327e2c02c00021fc19350fed96e111fa48912fb2a250b2f0f6c850fd698ef14c7ee9e764451e50ffdf2e17cf86cb1d4bfd56929639387e1459e3f3e4dcf0c2e0f7af233b0c90ce49f0a3d8b26b8ae43c4e2ddc4e59e3f9f27c759afc68c58956d3172c0967a3413a231430834807f85f734459b8552b33b45096803bcad2a1e04079fff6984667d0581ba0221032500c173da12f621606b564b00e5f5f57fa64cf0ed0c350b1357aacff45c35925f29476bb652b8fb606104c07245fa575a5fd00801a03361673721af9f2a0031cc4510a6488b910d985fe560062a65f8833277ecd0a9baeddec616cc5dd8334fd1778dcf4f9b66e71e69c96510f3cf0ee848dbd73d327092640187220e451df2da4c531a1022848296cedf69fcd50f6bab042bab36c68ddd0843d2612ee7c92fb517ac3b1ab37fb99c57d4bcc02d743d985380921cd4d8b0a78063026c720af59e05192c973d36463a5561db939423df2fc5999e89e863538df06fdd8a42fba346be291ba10b16b35a14ca539db0b4a0317a9527aafc05c147bf80f0f39387bd40b950c6b94e84216867f98e2c59e5b53e8c42141a0eb32016e1cb866a25451a0e33444f390a87babfbbc1784f54092a41685c0e78eb9d8413eacc21ffda28365bab4dc0023aa64e725a35d1698995aab2f268dc0f870650701717151bd860b4f962273e50936f6f2bc733a8091e4583d93da75b194b724b78146057eacac9c35f93df546c7e14bf3dde6464f85859d046faa98a437e8a136e10d0df5b95365939fe61038ca788038162c12053a60223e8cec033d986c11e02b17a56e5ed2d9cd7e6d0b45fefefe86a21af0a9f0e50e7afc8ecbafbf25c53f88264ceb4650790abc7b6a5f7cfdc8ed53643e16586f4125ca3cf7af1c88a9acc19e2951c78d72f2af47020c5b600cd8fac92f8a22cf9a270caadc88d1663a63e3289f304f3dffc26f1ee79af36ee4d6d5a5f05fe6d75ff65473d26386727715ca204da7eecc2453a25101afc33901258cea2a825223368c13db01a4135105899ee2a4a0c0b41b70cf2e4cc974c380401f49c30ea7b270aa0d922db7c72ef27ffd4c8bf2a10c9958422f7cbc2a7b94d0439d6a17157284c1e6725a338d38c1ffa4f6ceaba03c1774dbe7a3e0bc283c9b2e44c94ca1d16f1b7ed85cb1abb280b6bd82efb3020671b682e071e8cff8f38afdc68ed8470712fea2b908eab5433159f53314f28869f8751afb970bee9bb769db23d8f3b06a392e7bf5050adcf841c8ac1f702fe0ffc97e3a8ea77879e991765c2a20d19628dc69f6782c1d23ccd5f75ce1c8d81b4694894d56f30613ede0cb37262ffd109043945b9e6facc2238a5cc47f61ffa5ccd73988fa83629c6cac00cc7fb35728253405745c91a11380e476bd01618540e8443c3301ce825ee418ddcef67f09d70bc24f44b826f41db1c6698cfd4b8a046bea4450f0891565ec3b7efc550d01cbc8f300cd2dd9ce388b43a3fe94b0e85816c4b92f8c33f2b762a8521d962dece5df34e99a3ae686aea1f168d201c65c5cd273962ef517ad83c8cf6ea259c75b28299f3d9e9088847dd365240d0c0734b6bd91231d00b3f5776475ce9265a86ca4c13d747597ccc9dd0da897b14a3c5e46227d697a7ec1597d018b325e8fc93e14d1a2de9f4ff22066d7ebb8318713e3f4fd977f9ffe94b8c5305c8a2014fa309fa3f7e410d48b954e88fcff301ae85e1ba3e855458789d3b197495d069dcbe936324c46a5cf12507e2bebfcb98329c3f8f941bd2d8878f47438c00d5f879db370569e0f13740d3e1d798fde52a9a633af5e5168ffc88013dd2af1d397cc8408672a1ada8905a9d0d8f43412eff5a893fdb6a653d768199ff98020dda889ee9081c043db59e09e789622ac08b99c87d03407fa4bb6de3a7579a5d756014a710d591b516df34501135fa3195bcc6d6b198c86e44f54ba4738af34003c85ea49be5f70bc3f1c95296b6e049b8b88b7650b11665de106ceb6fb48ba77e81f07e0bd69a1a30f4d12278900db6d4cbed0a950addbfed611966407a91e0b7461c88332bb0225c2905ea7d2552b2727ac332268ad8038afd2d9685fa15bc6257f25c9423e3aa1c4b4b05db0f7b122599f2d4aa430957accbbe8a243246c78082da2211fa9073155e6e3e1bbd688058941d131c0e71cca84b24d41a9ce4476a2e290b02bb835e86f4cebe786a56429d71619dee7a0c04930af14e4bc4bf45dbb8b19e0a057e0d17469b566f450afb6dad06ad08cde0dd414c67b49bdd490925af05e1d5c08ca5d518957ce39354cfe2975a2803b6e949bd61ab16cd77db6a76c1579dac5e7a0c17165f7641277bf11b86ed5705bb66f1d53b2f40fbce1f4b3e55f8b0817c7125e4618c93b9469e8609fa82ae220e50c32b4322f0ace8bc800167498234a4633da9a717111f984f0719b405b6fe8a3ea66b181591baf4e6dd4187ee0555fddb0100484252ea8c8711728073c74dd47b45748edd9c0cb6ba028f79cab38d5c97d478745e73ee64b9a52c52ab1657a56c75cd312a9df548e5fb084b7db3585e0f0756415cae17b10c3060eb91889d66d84eb365c658c16d856991a33b2d26cae66a0e1367528297d40c93e77ec10425463c878289141df2661ad374005698e2544870302e0655a32f45007fa69cb3499964240c02658fe3735d4907a8cbfd17daa80e3afd991cad35748cd248ed4a973d3c74e716215833df7f4d130283269a48c9e662669c56e964be558c0d2d7bdddbd4734880e341acc2a2a2fc26f1bff619414ece0b6c247799d31bc6ad8cd7af42013d99eaf4cf7341580c909502559ebeed4463233df5606c6e8e169c3edf2a5410cbb0d13b5ebf02ddb823d7abf05e932e93dbee65c6aebef06db71442b8c1c4b0f1ac6098d32d017a05d3cdd837f7ffb335020fe2db8d0fd4073af82a3e40213b5421f9700fa50f747603a8e5fb7f82da1ed61cdf3d6a57bf1efe4e01f40ced79ba5426ce7ab5bd84aa783ff5623453afbf19028615c9610b09e4813b39bc662dde8d5f5f504cf7cf2b459a7f30c2ac81272904cd1ebe600ab92c0bd12b2883eb7164b4d505b2f27838b1d66398d9a10eeb9bd212736d555abae41bb441885f7a070a6ec6b68a951b37bc7f63eada6ffe696e460ced8d05e2bd6ec8a8186409e63b762c2f236e9e9c2b717e319d33a5c6c172d0e36b2baf0c677b868039bef68558d0ea381006215d09174a586def6f309a5d7d209f3ffe1685938043bcc2ca972ec7d4a1e418e706da36a1dbafdd4cbbc0b187b936af694f44962c38d8ffb3a8386dd50b44ac7dc2c7298cbf2219d94cd18019df32477eeb8f7b62dfe99f268b8616e7e7577db0c83b93f2771025280e86ecdd9ac94e940e99141778a53deeef584b99a7eda0e0def498a4bb9670fe149a97305e9c804055025720ca0821082141bec4589030148c093200d5ccd98e49409ce910b29b1821af64a1d473c42e17fa713576cb7a620ee32b3fdcfe3c3bb38c93090c77370547a5e330f368bcbe8d46e54918085506bd7c6ec03db423a7d60fe0db740796d211ecb51d640aaf1da43a753c7ba4d14666da9bdccee1b4379881776473bd994f762fcb4f211d0af5a3599f657408536ffafe15f666b3a4db544744e70f22fd63f446d3a375a77782aa2fae9c93db612bbf508e0212dc1025fd5b93f5865bdfb7fc48b7193399ce21dc97e1b3e839f39cb72d824337b1c926643e87ce835b1cff22dceb4a87874dc790df12b5bbd334f5a471b3ed70c9d44fc673156b727256c3ee0b961977936f068878fde313fb0e1e54bce3085ce62ab3cb4037bb3d57f51def027acc4c8ec5a2863d1fcededb5b38b2d94c322176edb135fbbc966f13fe2aa3e4eb4497df8684f75affc4765eb386969828de234775d3d9f203c60f81829f41cd66a1f1e523f53c702a5fe2e4d9ca33d022fd2bc09d2a517772cf978f3edb8b107ce37a00cd5fa31b67e7bc7a6a6fc1cf48fed7470b5d70d4c68fe4dde08dd4533bbfbc28bd725c3176663e1319e90792078e6b7b6672cf569f8bcd5603749053dec3b0c60ba9861f38cef523974018f38c52f7d7c3e2fae39151f5eedc5efc1b78916ff651cff13cce3da58b657774e2408ee20a74431bcd75ada8c51236e86e0c89f600d62fc1c0d81e6b983b8147fb5592c3401f0ec6a17be3969ba705785e6890c735dda9791b9d3115ebdbb77e13f13e5cd81345aed5d5d9a75ddf41ee8e17c48f9ef3c7e98c8ea57e24983283b868b6d44683ca8bc3bb347f4f6964887dbaf1aad183f2038ea4f6972ef2db371a470c8f40700ba284ed93dc75b7541c299694cdc976beab9b422a9b147a1cdeb716a26bead34ffaa7655379532637ce0aa6ef5905f9a5b229947a10c60d5efe6b51c0a5913cd40376036200da732f84f50952d1b507adbbfa489f58d49275e084cc165a62d3e3f7753a1dd2baab16536116cf8d9a73be3957498c471f407656b3e80a730a3ce280b2fe5f88242cf636e176dab073e0fae4e3e0eff8cc41b097b8225706a2ed68b355626c1c26f2f16bee2dd8f55080fdbfcab60d43d0a5683984e2d05eb88a773ec27d18bdaf3606f1d6eb9bfaf4ed973b40ce05236283cc90e50c98f0f94ce62675f0bef5d522441e03c0633a3e59aa681778b8c14672e7714886313a55ffd32dd99f9b9e7155d99a052b48b7441117581af151929d5c380ae0fa916596c716889514e6925ca2c2d739df4d4d8186d4daefef2d5add409eb980ff670166d286ffbcedff087ef85a2971f3bbf96ba315f9d50bac0c7fee089107e0cfa54abcfd8bd2092baff6ed93b621204b8edf9f3b39adde28cfca3bdcf40b2d06de1b1efe726a4d878171acd11f96c5f4d46ab9da20ec2040fc42a6d3d8ed1a7fe9821dedf2929e3c10f5590510699a2328a046d545c71d1b8aa9a953c046a8bca0115c3daf4707caaaede9c552515ecfb40e7690a7bcd10d70b435545248b87b957a74d8c4f1320c7bfdba0453ee320ad832b2a9b3d9da7215b584ad709469a89ad272663efb4b54c9b1ede8cd8977e7d9e3f581caac677e4189f441ea59f96bea003f8f7a5b22838e38786d7fab8b2a40c8ea29801637f7915ee6731535eee47acf4a88646274d3280edbb8e27706b9c92550e848662ec0daff0b1c5b274765223d376061f2328c817b6bc51c1115e809ed41e1edcd1e63c2c2a14dade148688a15b881ce83df9b4ab7d0149a3c8e2305f02fa4f8ef2e1b5492a8f97a0f82728a0c7bbf0cbfa8dcb4882658b950fda246fd78f77bbe7f1e626492f60ab6da1a02192bd4c814121cdf169af1247bcb0e4eb57280993ffad34213398eef69e547ce6a5fbb643fc658b618eabf3898b37d7a729168068c786049b9ababbbd4d964eb22adc3db0c78c514eb6360402687ac02ad42644b14d07da89f1d07f101f3a42ef5f5e02025ee41e4bbf0031ef283323d2257b3ef8eb6ab03c2eac79a2387126d5b9f4b1c8448131c169cbf3d9f814e5881991d6981750d7dbb5df57ed15da96d5b333a8a1f44d56b0b4325556c44f56111a95e193b7c713f21de7bf791cd82f09e0118b0d22132ff9a6ab406a27c82afc57678c33f41a36c019c952e4a8e0696a769bc0416667b7a839d7501864fa622639e645c764a1b9609f371cb2e18e59fbcbfe1f94b2c1dd66e62038a4188247a2d3d999fc99414b307c83d8121a80364597fb125187484e676fd793e69f9b9f93b480353b8335d7f7d8d3af8319e5ec694568e83799b2ffa553f2b8dd2ba1606a53bbab07a16b2d99431355320e81004f18c73d851504a19764c0fb9e00d860ef5ba38051f6cf9c5e3a9155b5e5fd240463cec544eeaa23f5a3b033faf9c05f2efe42f78ca17c3d63ee769c74b0dbcc8cbb10b199d4b250dcb15e1a6aa0f0772da15690f08097ef239b7492d8a1f698debb94e03cdb3c28369afa037149f8ef4bbba748bf3c47542a97cc6bee1f9182dc959e4a1d1f6cad492c8ea5a8509a0360984b01ccdd6650f8e56328c6b3ffc0c0da123e321fa79e017fd1ed75d84c446df576aa241e70dfaf73dcf8708e62bfb84ae18661daad9d9689e9c2aaf5c341b8a861072cd961f8024576615767c78f70bc63139d4f19f86e7adc0ec0c2339821441b74b9af4de5c1a93056140e5c3bac990e692a75b941b36a33404a069ef2a30db953041beed4c08f4da8046696574d7e16ec20792e87c885034ebc1c272bf7f70b5b3dbf0b6f4aec9b742f9ff569765300c9489192f01a9236798e51abeac7c5cc7aaccf4cfd142ccfdfb37ee800330ab03fe8e5df92b2c2ebf768a463040f8d234759f75f0f96b6555e161be384229cd65450961bcbc5f3ba0196283b1a2b7bc33312d396ef2466b515fdd3ae713bc7a5ea1a7b3f7e8657112109210fec788e5fd705799f8fa0b286c401ac754392d14e3eada14edf87ec881d5fc1e90e3b097a272a56001773c1b111003232aa31d44491e80d1424dc182998d79c3387b1d98e63fb804d8ac59a41b97261b2ca373ca784ab9186e9ac0cb6cffef6e283384d810e68afa646a1d528ded0da549e686940755a125b2f6488ef2a3a1d16628a31ba48a9d2788c519956598e695a983fc583011f136f64794719e7e11407e40d0c7362048550f8f2c49e4130373a2016ebda8eade3804f73c874fcd4d53a686ac7e2545c803340477034d120ef7b369b245221e924194d7f587bf62db6767f45df9613ff3363510db929f8aad83befc91e50335bb13dc8c3bc3baed84e10eed5e8d20c8d099ea33e7a79819e237ea46cb6c090cb120e8c62a59a9bafc45c69843d3f0e7cd6e22779455f2fef10f2a367abc72631e57084d20956a8217c0e0a97792105d830b85038883b0ed2d4886650afa0582e16b677c4267ba46bc3571ed7491a2ccb28621892af552c4425ad933d443e878ceaa7d1b70e16e0ddb0c78c10c63602d804ca208e3b348a987e44d36aae5f02c0a4a505ee5ca9d8c8bd5c631ba0ef37f4383aac774b6031d0fc0baac33b83e95b1854fdd261e0c45cf297ad9c931e4c04100a25b4289dc5959fd48ccbd4f426e0f8f7abbe16d32399f0a1c6325d3b6cda2ce53c73bb7f641542b0ef8bd7cbcb39c4c81831c987d7ab380b40e91813c83c561f098c6fcf7f0e97614476c763f0950a7448324e155e76d4c6f4b685583befd23575fb8684434e26c0edc04eedbfe6a8bf2e9a3b1307f29230005559742689b1aa1d6414d5b422e0c86332a9c5a857c6f1a8c5b911c85018d6b36ec6b5c83aa75f9a90c6c7c025e88d7081adceca07ab1613cb4611869e1d622f3b33d0a5d166fc4c3b4f7b42ec46b76c1e34305359f33268d43b3db5ff2800889d8f43b90380b22f19c07c7c38f199d859f41e470d4501c5e29c51dc9153b76964a2cf7c054d360e472260a363f5dc859ef68eddd7e9ec5c1cb127396a517be0b18370ee509558817a622035347efdb1935ea02cb9e654e1c1ecb08eafa78e2287c60c9f4a946be43f7d14adf8c303315983a86d7f4e4c1588a48064eb030cc05c2b66eaa3af6aa51b8f32e92f2e6b5fc55a2ead9fa72138e2c547cc18ab96ddf1794fd0bfe13306123fe54476b906d5423cd05f0dd72b90841afaa149861801cd2cfc0895d1bf2d0a2e65453a2d2ced8682e78e3d8a76255da354846227be9eb18a98b346ad7266fad0b70c9a1f7cca00484c79cbc383ae39c3c4aa1ea7992ae0e40b0c94718b95171c0c9c00273eaa24f81aa11a341e6210048bcaccb07b8c73b735ee8dbd0796706df803e2a5bb8b825991cc2dd4da543c110b6ddce16dcd2b81411f4f1a3b5f5dd1e5f7e8613e14dccdb32c215230d72a949b41e5eed74c1e2f330bc812cdb81211a5bc16b02d97a547bb3d5dc3b1521f1c308cc071144d6d6195c460c0080c54f561fdf70a5607f8a7090edab11e2dbdc60dad6d8ba940bd975438dbc1b6c0b595d1538ebae52b3860f569ea786b75cb7e4067754b2479d1ccbf9886dc2da6cfa6080d14efa3dd4a952d6fef83643ec807b383a9d65540e7f0252f8456e1095527ce440565152fdc2bae1bf1da201a4d2905021e33ac0692e300ce5acdc4cd316cf9296506fc0c3d1d9d1d0c9f936d9c0bbe7515309ce20411ef4ab77dfad988c38d96c74b1f0efab33fcabf7eba317c929b71704171e13a108714b4f45e09cc02406f961697747667d5db00adf870a115ad4366c96bd72bd009f6a1a194d205efe8adabda68d96a526d1d242856f28d6ba70b9fb7142f2a15ca1201ba572d6f61da6836c2071535accc9ffbbff37b71e5ee5e5edee8c941ec8482ce04f4c7fdafab9978fd0763d32888e9803641e18e78dafc0f89791dd08f4764140ddd6cf2855cd408c6dbbff117ecd2e82c05444805e57f703ac8222720321cbb4743efe56a76ccecfc226e4b287f75a0e58209f974a5da6a4e5d0eac8c74957dac09960ebb102b2b05ed6738adb4c24c14b24ecc2da436df960103bdc5d2d39e5d032fe162d205b7761cd49e966b83118f74bbf010cef8286790db3396ee24ade17720f2e2670b2083e1a069941b48de58a1872ab0116929b5bf1fcc9cbdfcb1d61f22a0634d91f48c056184f9c99a92d47c840469d32dca7c0bae9572c9d082604c761f0af30c1a45689de4dd4482653162324454e0d226407e1b8ec08d57ea9abe7eb3af3d5768c1967e091af01da1edb98c8ee581a6a2d3434328b5aecb9c66bff89b60d988ff9fbc9488e514e828fa6fcf1cf219f3d49ccf2805c09be8fe3a3da368f5319d4d16725733f812ee8b187621449b8a1ddacd7039d180022692aa95ada864c78994f97fc7220eb1ee1c762dc9e4910859c5d5fe7e133bab771740a063f9f71ac03d32c138b13d2872d569b58393b6cc5734201ca4a6820739df66ff041944444f4bee59ac051a33610f49f9ec1e857b61eea2ef39eb73425aaa73a69ff871be0d795f9efa9d52b7a8d40dd1cb2609f1e3b718c531f416de2d142ebede5edeedf53586e907aef3a54b333579c6b677ee1f6ea111aa616ac05d6892c9234c4f348de55abe4633f1ffd9fe1b55b59599d2f0eadef785cb0d419779b2e652d1ed6bdef3a9d57e2f4b98672ad88a3583a936e38580fc2dcd0ef8311f79ebf607c8d611d9554674dad6c27e3c05d8d318a1ce1f73188d9d327b10fce1a709c2368b6e1049d0bed51e2913dbc2de7d348a777449a5256cd1158d40ffbdbacd222073591f572df5f1ef1239254b17e50f0b7b0e88fef73a091f732733cc95d69d319241a70422e1a6525239ec872f0bf670b7337f5f785c8efad9636a666cc0046902eae8eb0e95d6e40f2c023d5a405726a77d1a7ad3ee9d6344ab4b48c2bd441c1b83410bcbf7da3a89fe3a26bdb079c9199a4067c9616c411af9cd97309b7122ef1e1fc7590268bda4837bff7b4e4d9a403b0cf78bd09ea63d5ae74df5954ea57d6d403cde7415d33cf365f921354f7243645b9de900480f177f983df3e6949b79668679552d1870c5968ab79bf751b1337c6eae8089ba8262367c4867d8ebcbfc3dc470f673408da133b67ea51bb93ea11abccd5f9e7735312454f604c2a692578ad619e8ba324dc5e849a3c8ab93c8466f0ff22caf67bb8b03f743af0e5830747c67be40c9c28ea584d52f69a869dc0dc14516a9a2b679244b5b70c1f186443f3257b6d146be78958decf68aa1b9d2de88bc908746c2d65a31a02dfa4ce02af64e38a295d83ecc49834e2f22df173c107213cd41f95e3e0453a6d536599b52f11941ef547b181e6136390ea8e8d4824321dac7fe143211af79e4903846767ee2f2d6a78433fb2af5ae29c8c930d2f16616d569d7824dbec08bb7108e9c621d364af3dd3f56bc7afaa03ecc232c0371532dc033fdda996a5d51069875623798dc94b4273ce6192e3a60bedbb3c0c7f2d487656edde1a2cad5a8ff7d34b27dfc579c8de7448bd2621ee1e47ac6d82d6b8eb90576477c209541c4f6067e7a0aafc1ac90035032d82dafe55f29264b4863ca5ccfd383357acc6b9a4c69792b4e14e30aa08f03854509e9040e5b1fc12e42ac6fa66c32324352195138f0880d86dd83d12b0852c75b5249b91bc339a5ed86f98330d064e11b449be44439a0529b556b76ad7c3405388971cbb083eeadd1b07b67c9fc660e852260e7c6bb686a2c095cd023a630a5bcf38a51cc86ac7920d3adbaf9c99a44b61033fb25e53366c67f1856aa6bd5c574c1b55f43f0df311df44b9e567a0350bc32fd8699f1ec5d2fc03600a2dce51917278f94f71e429fa273662ab5fc250af96b6854d99075458e225c1cc0d2fd5435e2126fca7aa6ba7ebf8e1a74f628ef71b98487bc11d4a3810492a14f09a113e6d191630d1eb4d44ba4d8380844e851e4881f208d21176c3d91509148958c6b5187b140afe3a8ce1380ad66c40f17b1b82a876c7d71cccf2b04b588de6fda296e84a71bad0c228d08ddd778a89bf075ed3326795343d7e0c046479c779d8013d217e559709f2d51a02024be9d861ef1bb072e973c62274c02aa6b6a49502537e90913c0e17c504fd6e6a586209b0452e6fb48ba97ff8cb162aade0f0d8542de017ad79d37979ca42e03c8ae5bc556faf2510661216cd6338e474481656d039c04d48eab98a09f7ab2c479718bbb6624236832defbe44cc122f5c36c1a178c1245b09c7bbe06ac468f18b085fd9fe21ab917d72c92f31e4a5be48bd2490750084eb4ed06a9a9d07ec6bcb4e34a903f013e7f429f25f71451659edd2f975ad0de32e05ae3364bc0979a6aafcba2692a36c79ea44041ec40d0059d5cf73466e174610de40335c9ad48167a0f7512d97a2b6cb335ee992d83b639c01d9a8fb01a9cb9598629172829f8989f70a365bddbb97d44aa990c5947a9ac07e671eefe3c8682017850d75f460afda209e387084ad8e11ed5cac5ec3eea25bd24443ae1869b030d6f41a6ff573048a19f45c463c7d122ec16bd7e32631af79e16359464da9380e800373dd4e5e844a5634bfa129e015663540ddfd8b4da3c5b9ca7f01131e151dba39a208cc56adad9cc70182205ceb56b9046ace1e2d6a244d548cfac5aece3a0077990b9915d3778b7f2441380f9509fcd89f0af3dfe54d44d05a31198716265637d75b4c5088be49930b08333469253f64dc15ccb9b995e15433d0087eb869b9e8d4dedd2bde5d1bb4eecea9b20c740f8c190f076dfafea718a3667afd9d8d9e2b2ce668ff6d346685a307d44584684982ba84cc3e8a213e005922b7777c50d8d3720fcc5649d9219d065e98d212ed5e989d10f96a7aa5921a5ca3f362c30c89d805e81f96922686377e4bcc5435b82ca75f3587758440af27687a408a0b6a15625424a9ed21c1ef58c7162d7a703bf5e36c061111adfee4c099351537b5d2a355cf4be00cb17832a03893de4543931d1f6d3ebcef84ef61f5f609ff7fb9f85d2793a787df83604b88b2ff7c6ad2f6598abd4d39596271b930005e23f602d042e4fdfbcef9174fe0c2b061f35472db3e97f10ab09a1f4dc3565231b48d39e51e002e5cd644a08e18db7a3be901b6f52571dfea13e6c7cfe179b52f8ef6423e3919905bd5e04daf08827c9bd13f6e1488cd0a04cfcd850443d5389c4ba251a3c06365e48cdc1ea5c2af27d262eab393745d1592c211b8dac952a930c3a2e24707537033da6f8d6c2e8fb07f3b0b0e6caf8aea09c8a105a8b99c2575ad3a1a4d7667e7344c1e6bc1d247c65f8e9770fe0c7a9951b645167c9cd1604abce2fcfb9b30dc15cd9993d5ece2203dc7a0a8666ce3dfe87c2ac8a6fa67a684e012f0a9bcc5ff25cb334f4fa9a76ad3514ff5114664557a32bc55643851f21ef644b0992d4f28e533b3ca43f8758cb88ea7f989d96dd91b965ef639ba60f14b892ab6de99bfba5c437dee44a6b5c485be7cc82d4a8ca08bb0de5c7987122a566589bc661e62792fdfc168c0fcbdacee2add9789f2e5ebc0ae92d0a235ecc7873ce1b40bcd74d1c3b6d5598aa0ed5b5f5007e293e395f2c2921a6e7f446a836155e4cafd075b2f3cfc60ea07893ef8eb40b9d59309b0601e3df57c2909340ce8089e9ed2325deffa7b310cab43ece4e4d0d27c773c10075228ab59e7f8182965abd0dcb6b652050b2d365e7b1d05f649b19ce7b62f47ff95a31d052ad4ee397b2cca64e292c6cb37dacb1aeb0f547ef44c998b4b1d7980441e01b7446e164fc643aee74afe9a6c5a8e69a0a34f1829750167641f1749da69f44809109a2a4c5d0312929049f38f9929fb7191ad69eb2da3e357757973d86362bc6c9511a347ce88106bce57e35c4dfe9a33d5d76a1a6d7cb74766356456b7a15a66b7d2e483647cb594768c48402704e09206985cd644a3e61a51bdf1c1f451a55910932e37e1d11e7e3d60929d96a29f93c7c382870d29deaf4666f9c6498becb1dca7a0f03a63998377b82d587aead568928700b483b71483f53a2543c1eed3b9b4bafaf2d45e10ff99da3943aa77ee5c2e1a84a6f0578654ed576171308d00096ab25c93f244ba3104662f94298bcb6a7fc1bbd7f3bcac219b574146ef64bcf0cd1feac4b74bc276102781f62ca04eb2c6d085012e0d06a2c2412ab1be32698f304d73b0a533c64920d8b33530b65e16fce14b4d615f1bfb236fab226c9c90b9448490db19bfe5d529963885ff0938a175e112d72c6c4327cef3f44702cdd7f83ba63ddea8565b001d6a23fe51eef3471f828f5568faed064ab3e2390c86882b5a6ee8782f0ec6c37d98acf968e5bd613da46c36bbcf9f9f0707590a19c28a509b27a8bfbdfb730032a64bbba14471ebea2c398a506612b785922597ee76139034cee2ff3e74a4d1eeb471cdd7f43068bad806a26c8d86412362793c7823e1e500fc4495399b0011a6988eb76f3be8047a849951eb253ab9bc2767db88e5cad1100b1fe331b68968198a2e033211de0c804009f95c409a4e13210a30cf12d725bae62b6f6ca513b8e7d9ca8eb0cb9b646bb178847fbd01b44b4fe1e6b15b0ab7c2962874d5fdf70a455f03d2e4454163e89b20e6a1d8033870e1e177cc6d856457c3624dd76227bf6b5d4f0c2243dadbc11235dd58a95a272903af1ee6671b636d8f47e90bc36d5c6e070d9067e7d81d504f566d040c3f193aee5b48195fc5910ecc4926d1ebb121c2d451d3e99a2386a69104bb5c15171ddad5caf8e09d7a051b33c1db342cc4ec3d81e59ff6f19983a44ca609a48d86a053d635ea49fd90b0bbe55be786c28205cbdaee73e6ed957f8e93b8f2a9209555609d3346a1b044750221bf76b3d1c10504ab8aabb28c0aeb89fbacce4dbc40afc27e0b3b9e86a3913cbcc9bdd343045e0ce24e72c6f52b6852683fd9a61533e5994c107ca65a0d81d4ee5322c0f7168e4c2b32eba8a7fd783eec9ef68d7f20950595988bf1f857bbd3e184aca1ee2e7f5f71fd30a1075e59ef1103345a8835806dfc6a8a246dd79b7551c26a9b16a7628dc65ea8c930abfc7a7cf04f1e475664e1373cfe6ca0396d4c750d114081f22d33d7981eb536e60ed2d3ea5fa72cd046c5fb110fddfb14466f0d8c35b2658eb69742505e7d1d4161f1d4c1b9af6e373fc984baa35f81392bbfb56c201ae31e2b6fe2a22680802d51d9ef98e7ea830eefa07667879bc93921d4bf4f117fe0d328d1586ad150b58685eaaf61e704a2fc6ec854d19ac27a9d236d88a1fc310c23255187c88a5e2f7684a02098fa671d68a592e2096466c5b87191efc3163193123feeaaac57b4fbb7d78352b01f5ddf29563946a45c325c74c70e2af45167619af91d10088f0699695decdea4b41d43be14e0b0fc95c150e122d56dcb90ced5efa1ec926bcb5eb55287c855f215bf7dd0159a7cfffab8b8f897ecef49764928f3792c5e100c8a3fa3044361276a2b987f57624a509b2a119c12a678d25791101d182af43f1d44749055116496fd0ad78598cfd78bb185d0a5b02fa302f3559a5f952b9ca6e639da088792470e04ce9d76c431d94f0633b7c9893c3a72e01289b8a7573e60cec814bd2e2a553eb1728a9f629cc9324f93f7e440a11a39a437bf3ed1ed4ae684904cdc4fa3546e583adcd55d48a8d0ad228e4eca534dfa0cfe35dddc7b817f9a01d9da79ea45cc49915a2935d6226b7e0b99417c55e29c070eca6c394f3994026fdffe5382a05ef7e348dfa260c790b9549d5ee560c7a83c1ef7f413a46885df2a15f14d81d83d4fa51c709d5ddd62a34b6a8d531e04c48920e525e98875cf188e8c800876a3186d081472c3e3c5180eb3f8b1dfe1b2bd18f271262211601f7b0a7ed0620c5dfdfcb981ba7a33b8b10af6995bf36b19889b9938a460659e2454ab63e1e0d423ad0d984d4d3c0874aba21623d301d12f606fc3296c427dd4aa1bd6dce71c0f8b31b1c2be774382b8db3f518ffa883b97da0dadd6dfee0a647ffaf1e89515ca8a6adeee729e35a3a3b43cbd4826397b4b7b627e41ca18eede322eada538090e4b924104dfa67909d1a4d4941ac4448d911e21c6068c8d094fe3359bc8a26ed66962bf618c6c93f9177d5f93527789b89ba3d68a9e1b467d3616e26af40dcf7e8e71e6fd1b88aa1340ebea1c0e8ed4400f13904be8e90cb4af01bb7be6b8843da9f878948cd705f2db20ff131ed1cfa4695d6352b78a2d9bee47e45f19a30cbfeeb561853cbb0ccd307a5c95c9824001f9e74ca4cbe5ea9448f3ad32cba1087603c3627ec1c11ce2d2a8692240d650735dd67199fcf92bfac80b38d535d4c40063632bb63ce185a3c5aa992b7a28b761f0683010d3a3c058a07d8dfcdf7e9714c9c639cfb4de8a8ee7bf4efff8c5fa8ded4d8d5bf9e00efe6edc2ffc87b886a071b6e65465192361c76ef37897f7eeb2d13fc3ed6fb02bf2b9b7dc973d4a5239cef90bf5e60fbf5d6fe601963d3d39fc92451d5fccfe61d8873bb2ec8e386d2752f7d3d64f3f01b90f6a8b13ec9fdef7842f7d7efeabb2e6dc7a2279860fb434b71fb32eb73f0f199ed35e4832a03f1409b38eb0b76df9f885aa7d741f777b24feac604b4dee56aa9412461c5784182c78129ade4459596463f7f53eac7e8eb8b15c2747a3dd1a848c6a5ce7974a54d6ded7b739ada83925ab0a1c7cf2cd2a1871513c77fc80f8ac1c8b12ee425bbbeaad522e8670902a42a597f04db17ce3394e0a1423ec869dc9de9f790da539d84cdf51c4b98c5803a02c6141d1b42c2fd896e4cdc9b9fc2854f5d67da69304f70bc3c4dbafa5354a0b896c60de031a0fceb8d8c1c2bebb7ebf1feb0d549dcaac1ed75ab7e4e6ca0842ba63a95fb17ec222dc836f37171981b6b15f6680a67bbead684b46ab654d35843f036b0a51c24a73d0ad34104b473c59cd7f6083cf94079024b5f18d50b9515dcfb63b4440189659718bd1e288348ecc7df7d80a9e8f65a8611049fd9d8ce67e831283a524fa8893c9d0d907c05fb8ea777c015a1b707008dc17fd1c2646fe9e85df960eb1e8c00e6dec6603ad15190860e1f1cf1f61a332f2df442b7a48e59fb5914ac307fcccd938ab4eceafd708bf0fde6d8017ac1d2e00cdc3c240978173f357d0f80847edc43d1498c1368e8251fe4ad885606a0078415e17dd3dd3ffd23d1385c5a02fea1688941bd1dd7e547001ae785bed2b7d02b117b4b665ce3306a37af2017009b41e56ea7b23e8a7c0f8ec0766a690f0d8dfb105156ef8042413af70ae21d1e75bbc6cf8df1017a144d736241c745774935344ee98b7c964c2e522ef4d2f71f7560704a505b14fb90e5b1fabe63b43a17c3c242dd49c46135d9cc8e1e118d401b64410fdb89d04b851870846e68db8351733ae52d52bac06fdb52efc13c8ff37187a755dabfc3018f6efc4fa1fd06aaf544f0f2333083af3dc6f335a957824187d3988ba57e605e43f68deca71724add64be8cfb852099ef7382343ffc9fd367e881f3b0aaf60f7c4d7d5c7b38f687be5027a7d675b827e4aab8856986bf77858fb039a83dc9cc4b10d281a01b026aa81cfd5ba674039c9cc6301ea3a7f32a4f28bfb059bc530d5ffaf4192a0a9eb0fd1137380a195ee1f3ffe308843862a6bf97e4a071f00791d7e7ad63f83ec2b22e3ddf41437fe96f1f32068481d3108e6b47849641eb7dc8e969a596fa374e1919517129d9d0d6d78056508c39e0df6b9eeb2982c2d98033193aeea3ebb47d7ca0663caddb4dcbd63fa8f6fe4e64427bf2ffdafdf4b89bc2625f4e9fb31e01e894321a337e3cd977aa1dc8fc5fe3fba2d504dff188d77e898ab92cf566377ff837fb106b8bf986c903053ec44f263acd65661423d30c33821061f32e594110ff80588f33315b06f6bb1893c25fc0a031cd0cbd3370f532c5572e06dd70ce7f3e9a1f0595c6ec10f5de3b2de433dd9c99e5cf1c6ee3762f30dca011f14be2629f56322724fb5f885e3785914a1bcbd017bef734471916ea0fc99cf8fe21fd6120ba7fcc1466ed1d7d570ae0e4e5dbc7602331de15d9ecdc821d65d79da065a8744cd47f37eec88287f9a1f3e8db1fcc7c88913afafdc66298b6f126fd1bee2fc3b0735baaaba2a324d607ba179b396e830e238d8d884e8d7bc018e1d731170dd447bca53a2b5a7bbaa8fbcee62bfaac97eaa96156d67395cd5140e54c2d2a2af27a7e2441758528d65e5ec7de319fcdab11ed2500de72962e81ea3d297683045dfcd54856393162bd9fd2a25313003e10d822011f3d30df0994a752c692c6233f60b2c8b3c0529bfcc878685bdfd1aeb6028971a0b16b86dfd40f6f34943cb19d1230e2ee8be4b346748377b4dab8de3a2456c22e06e0c0c72a54207fb0896fd02538d305e53d20b0ec8b7c8d506cfbace3c43a778f9e60b076ee3f6769cb070ddb077bce84044b24d69a8bdfd91738f48b62d1cdac43f20f7f56ec55794515eed0367dbac7d45f2afb47c04491455d41c5be1b78c410bdf11d4ad2a53b4c854f9b111babb072697727aa0ce0fb907f1d500f8436b57fae4511fb63eb9f84bb327cbdc939476cc577c11e6b4fb6c02f5c122e541003e2feef34b763424b3f66b9600dccd931e38d2475b0edb929f1288515b644abc3327e2136944d835814498caf7553c3f724d4c102922a772db3536562766d0b06b8453ad78d139d426ea5f882d11d24bdd728e1432ed77c77f3fd55b740c29187dd6f2a12d94a1a9f096f5cdbd00d754f24a23efa621338ff70c9aca84e4c8673e6c20c630d3e315c223fdf15ec03d1968a67b7593d1fd6676c5b586a40352270eef173badbc307a6be314255c84414134650469a960f0978dd09b711ad5d1dfedadfe916503e9c7d2b31b170012a7192916e54d541ac5ca57a4571c5aecdb0894084d0b95fd4673c630f7b5d55681174526928b8a9159fecd90c6d8e50c5f2285c1a10de52e5a3b8d273e075bdf8eeaceb75da6ad8120a90e252d9880271d9b61469d08c8fa02b4ce64a3379c1bbfe1fba817488ae9373e425e0f1974a9147efa9ead2d4105662eda3737edea4c9a63d17a8e567aac50897a0ecd72906c28dfc2e41a5df5dd61c17d7f6a60604fad402c8cab492d26f551585c095ce39c17e0fc598a3140f34aaeb6e486b2ac5d458a2f1cd1c54b503d34623dee4b93c9dc02fcfbab72f621cb0e90256ad537da4a4afdb38ef218922723d1551b8bd581027ac79c5578695975b096d0200d48b0ff1aa6548badd57fadbc6b60648a4f8f4ac2699d98343e7dd3d0632c2a0cf7a6b3813a0e21e8eccab9cda6a91dc71c3c42f3c2f564d9a998b107b93211ba01ef7abe7bd8e991bb15daa16745427db628ce4cb393599bde748c1731260609985c0e1b3dfa512a42f051a7510dc3e26a387ecdf236569ea6cb1b469a72336489fa5e9576c3d93fbb9d7e4b44216865aa75afd9c27c3fa497910acb90d1e36d3e958fad04fc2a4f33e25148ae183579c210fa8bed9aed5661943f52dbb362e25b036aee90ea2a23918228e466e645d878670c81e4b1f2669c1cacb0585afb9db3b64b7ca2f948a48d8204fbfdb2994e1e87a7d2a8a7bc49e553f844b86cefb2a414972f2a95b6b7dd650b18298de4122a258414464a5df39e162e4cd4d4de5aa5340605813ebe403367d7f157a93793f595f53aca8e2a4d7ee4074012a02cb5323a417d8eeb494eb0cc17033de1d08d65c4a4f9ddde4cbf28698699cc94163a910f1de124935da8a45019f05898fd4fc183863573b1f0d67572e29f8b97d6f2472f8b711b7cf173ad7f50d3a810e506a2ff93e270281a5ef40dde744ca6ca4b71e811c76d801457935455bdcd5f855c3d29d2a7d3b9d78b5c48b88df16362af28d795643c440b4be67229f7ba80c4bb4881cd168195b3a196bf2e5531c60d60dc6793fcf3866d691cf8cae131be330ac3c01830c027ccb88d1527d6425fc37f48b27e8afc958e277c4113910103c7d274b61f22b4a04f7b0c975385979fb794c20dc9b2ca5f8cd90be81aafc8448fe77ab128f429740b51918408ce4b4f3c05423e52a0347bf44ef670812a7a9c1a9bb93ad9854f1a5af49d0bff294229004042f513808a35fdc15d07533ec9f551ab32623d2dbfbe12029110a141e179e4d2efbbbd6e4212fa114b78ee6ad47d68534d195204d625040354bcfc573e98073d8bdf549a6d3485c382716bcfe1ac369898258ab299ab2c1d1ec2f3d79482903038841b46663e68ca4d6a4789f0ba25598530bdafd0783c2bbcff336a54a995f8f67d092546acc1150fcdc49b036bda364c1042cd0a88a19b9aa890001178a8b63edc7b773707e26ffca4bebd34c8cde6362a4afdf31eefb46bd79db3f49ade0b7a5a9e134ec2c5d0104d3848607b04818f2d42c7f07749c4d571d550c5023442af41bbb78512eab19f8f7674a126fd20467509033cd228564d178596f90d0c55260a20e153a6f5c6da7e1cdcdbc5a192200bfb1715311b7ce8f379c802673459710d3d2682b1ace45be05d605fed6552f7f77a6de7f6eeab7da6e7ce8f78a69d67a3fc478bfe9d7fece3df9f8a13e95e0cd73cbd49b3e371230cde67bf52a34799f978baf0b5ab12e5ffe67c2f027a8cb8d59e610e749a61eafd76f165cfc1649ef71f48647b7061e1669de833b89d8b5cf95e3384ec121a1b741b13b7430ed13e5307ac34fa1fdde82ed4f34bb3164292f40a57dba8d8a34fb76816e746a2c1b85197dc3081273fa7d23c6d296ab2218b76353f59388064cca506d4d95e486ebadab684e182f53a84d851e0facac28a7863c485c039e862ef22a152e74c17b250a9a10db26196755c0938a193ffbcc80e62d93468ea1998481dc5062a3d89ec69a2ebd7e3cc9d739145bad5a421896eb7db228eba4d5869885a212a382dec9184fb2bb12f43d4a175414e6eaf22f5c77ddb3488e539a920284462ead356142bcf3041cddcbeb22cf6a8f9c5261f769f22b5cee8f77b3a0580b44a8f0a72ffdf1251abc5ed4926ffc98851e150de81ff441238c21a8bae46611d933c3fbb94fc198502fc0d639b45bb04137fee531b1cf9ea4898b52fe3ac9f0ef9004831cd34490ba5b957070f302c8fcb117076f67ae27d846de2ebf9bd1ac7aba319d39f3b362c774a666225f58652f7dbede7de62e5d5d0858dbd5c90be6aa5dda51889584019af8a9ffdf3023f686ce85acf977a6e2cae426bfbc516c432f6cfe1bcaae638808e992e1bb47b3fcab9b00c75a8173c74f589730f38f4d6087b5440f413f12ca5612e095125ac4b300e424008c16f6fe99898d129bf596bd25b17060ffb061b3b00d5c43454bb21f063c7b03d062cbcd6be4ea4a86b8deab28c7bfeb87be73c8a8959890216864a6e34a58ee9ce02d5999ffc9decef9cfffefba7afaf3d92b490efd4a83c3483591a2748b66228cbdd1e7420fd46f1d26b0d8dbd83601e15a5d76087b89fddd684d211da4fc42a611ddac22dcb3cf6d6fea51dfc20f0cd3c3eba248ea9967fd84c248ef6b434b16e0b50447329e1ed4ad799b1a0e1e89dd434cecae57d6949670520c67c9398ec2220a77efa2e1893420bfd9afb9d50b2d8e5173236941285a45670292538054ee4fdc9effbad56206d54a78ff9d12916ea0f1a44f8169e209a0a3ee0fa71fa1153ab5ac683770a1d48e59da4ff59a8feb9c364f7a99d25aa57cbfa45b76a010cede3ec6219965f0550bc1443f8c6010a8298339343a0da30712d740cbc81d24fd937674451a1993bbd75aea251f0b43e76a4333aff96ac2b34df28ec2a9fcba1d5a87884ee61645558b3074812f4f53b0884ec568f5d8517cd01cade1e903f784c6e78869bce9e2093253f6407452ec3ecd9657efd68191d648124c2392f0597022c91ff866a946a2a1fe2cf37d61b99ec177a85efc6cda315b1b3050bd70186123a8d1695e5d927ac39733d84024910da51c09a72aaf082471980db826fa885d6aafad98aa269a65cab6b0b2f992940c0fc6fc1fb6ba1e1d79ca2bb15c0580bff044d6076722dcf7f8cf5fba79411e6319ac2686fa8d19298074af70b8bf8a241076ce88b093cdc27699b66127a3031be25b6d82f0543a5006033d02d5678a3870635f651351448ad649e90092cbbca12fa80262b42a9c2903454e30d56a1aa586d20f2494347a5738cbb5aecca0d6b226a7be8a595337738b70ffbf523f47c815732b0c02682e417c3bd3f3d5984cc04daee30c98b5d497234c12f598c79040054874b3ea8e01d91b50080a498c2025cb49d590dd207a6ccc7fe26321814095f28518458d7016925b4b39413882ea1fb1b2580e58fa7e69bd549e1b0a1648440ede71cca2d68f0fba44283f498c3c5acbbd885a187888050677f87728a62493270134ee7ab158ea1da10e82f243908e25e8908885a2746b8e448d3a02c9c3d0026f36780c81cf052efd83b1c9ffff815fad11688a308ce38e52c632eb9e4cb59c26e22e8a01d8cba3148c1f7af3ba17597cd95a3c9b78865fdff3ad553d340782be52f929d83f6f4d6044ae82731e51ddc9ef43bab1e4ae9a22bdae4e98c7bd3de47efaf2dfb54719408e404da99e5c834b5f3b8183fa553fefccb5f2bfaca3a4e5338a7810b4fc1d0f97a13403c7599a607bc7bd121beb627c83fd37283487df756ea7d38b8db9fe57ba7f9ebcc6cab3ecc7d70127a0f0e80a111b4309ee7a6118085cd548e8b797bfe2c4570a9ebdbfc2c44cc8852f45d3f7a7e6778b553f42a24c6c299b5fd959a111cb9c97b73c23a13dd7e22b980c645e40cdbe23d0ed56dbc18b95d03ae0e698162182c9f6ba881a4849681c3ada9fb6c906e7eecfa39e3d43780852e42121622f84235255fb3f7db690db4c3d1fb8d86e8b8c4eed38e05864fac37d926c0a29c4aaee6f316e37bdedaf8eb1fd3d957b9d0a24197a3539bc9bc76bb314bcff9e84db94bfded0c195e28254e6042e6a563e8bd12793134a919d48d012fce325c9ba590aaef758e70bd43757a60dc65a6a9138987008bc14718be687ba42bdb10654a00bc4a644f65e00a61238e892a70ee5d56d89b1345789a6f7e5000c82e5f9763385e62e1cb0864e1a2e89a7be3073adf92c4e8854a1b336c8c5414a768f3ab4a9eba29eeaefc1f221ef954297e597ba3615d3e3792a332ac892a0899fb259039fba984484cd435c1d8bb01d4bd3e63581dc277440eb28be7f1966315312ed1500e9b07a39ba5e6ad63ffc146f1fcda7aecb8645d2a29a7a4128243041bc1bef1618de2f9746160f47c28f6852e466e2558ec0f27607e9ace220347b6ee85d443c8865c1c782c88f2cee8556f6d37b7a1de249d3a3c52ad60b4c7fc83fb3124a6c7da2a1ecc2e9a1f0a17a5568287e0404c60c9b108cb381325606fc88043352579b5e3c11e84478e8ee72d9cd989e4ff6e3aa1234f7d01b6cdc03fc7c7e1ce17bee16878301c8d2de0f64de9bedddfa953e85570aabc00df6c5e8844c4ae92b82b2001c16a60d582f960e73514e19a1d7d97fb1cc82ebeb0acac2c1960c8144a571076c28caaed6cbf89f803a96254ee3588fb6c5169025046de87a04460be98b2f22da176a460464229239549091ff904654dac96fee466eccb16247b4bf75b0e4fd1feac1a67548d055705e75f82921562f76c5d3fe9823c76702c33a8b0dde91b4e9de9541882b6dccb1d4bc1fc8bca7d7bbf5e930f94caca5a7d455be1ff3a05b3d3b61d6c5736b9ee8451623d443bf977f558f425bd6b9ffd58cabef15e5f6e491df483d4e1fa493d5415ddfe79e1de7b0a0030038ce77a30822d93ff8605625319f6d363f81d237836a2ea2eef526c53c92d7a43100f0184195fe42e0636c0d9e7dcba9f69d905094cf23510a63003a30d874f7aa670566d89ecfadfd4622987d5e4ba5223828cbb662ec7c2fbc881f427c354261d028688879378dd18a404fd82784e63b983968f3a8af2d76ac10c66f8c8cf336bcb4292ae1bcf565d0ac5760f324bf0a78084515c034b32c8d71fa53f670d0cd479c43f16aabce9febb19edab9b60eed8de41cd5989a2ffbb5bbf1e32936f805e0e3845abeadb68c05ab6b8205c860b73f76572c45da3cccf247d89249ba60d3c72cde0ab3ff9ee1b025b3948adbc6e02179905eb6365be80ee3954ef3a69bdebee464ad0f1a49584dbc88c496ce703b18436259bee9eeba3596a13ffbdc222103788ab1d322749016a8879f4840f52841f018f87f09efa5dcecd0e34932d73fb92ba4b9580261d82c63eca0c56b0f1a6d25d01b70f661fc0f7d7ddc84f14d0addddb46d8cda3aaf5c2a47152135280cdc9bc45dcaccd52714e209f848bf84d1a43c37b8fdf9f834334f65e4ffd1f6f18bd3a0479d008bd179c9ef18c8baca0a2dd1e4d79d85858304dd3b61aa7d5a69b3910780cb355f8d01f33e13f4335a95a21a6729857bf1b2397d9e2ad270739354cf8c06a3137d6fb74b872ec25ed45534881cd5818e044ebf5b540210913abd90a0b29c28b69d1856f01e01eaea889ac3f67e058ea8d83d5395e3ca98a89da33eec58ef9ae016956c3c913a647632e87bc54810966d85442c0050325cc681b9122a2fc5b842860491fec9eb277a0dd3ad7958d2ecf91bca76bc0374bea8a1caef412040258e846f2ae86aae6edf2c96c7f6df29b9bbf1fe39249cc791304b4b8a886adcf68af96bf853c6df5a1c3d48289ea36041113896880178dcfaf39f10422de08a79af3272a95621dbe26231e5e5e9101d632291231d127fcc4481f6238640038bd525ba5dc0e18b57661569bee11712711cd0bc4a70394f55207e7464c7caad5888e680baffcb2810c7d1e95314e322b2dea7e024132565c788b293162ed4b586cf327b7c1f3684161d21186f0e3b87f160517884e9f2c9340a8f485ffef92129024c15cfc82226102e74560bd567fe2559b679526acf95f6b8a75a0601b81a91dc6382f59f912bb40211609d5d8cc201b177e7695dd2a8b5bc5fa7c19cd92c024107dce6a004d031f217d68d86fe4685c850abc2fd0f34a6b6bcc654317b0b125be78b863494ddbf6519b245f927a4673869f7213c1458717c6fda6b884862e74b6523de7af15634197513fa2b4706da848ab6bf8280de1fcf7dca7d7b127075bea38de190a33d7812ad6bb09f05dd500f75fa9a1d3eb76a31262f690d359d82e437d4b752c4c904872144c847f0c7cec02ceef18bbcffea4f7b79a966c6b566bd0d5278d48ba69a2e8e286422d8df8647a78755e743e06bbc19cec962033237b3134de52fbdb470be8c6f2de7fbcb0619fd679b4cffd9c6f830bf0f2fbe19fbedf0d8f9ce85187fe26d90b3af33de3c6b7d269c1a6b474088596834cedd819c29566442d925481809a5c6b4177062179634d4c22ae52cf659f28ce0b52d79ead2b242e74c2d7e4e30ed7839639a4222cd41b90e166592ca0659449e6a6634cdd459b24487757dd2c21c77f6df3ec3bf3ec2e2d387b0f9f7115e3e834e4b58fb3143e564a689acb3926e42062ad3d4a4e6b0a455dd81427059aeb8977ce9f16e3f1b51cca924d96924abacd385d081cc1435d724ac695957a0094ad3d1b2ab3ee3d0bace40117453a2998db18a357bca834bb394230b9b63192b760725a0399956ba348d452cd889640fe592d9a7794a91810db18c25bb831c109c4c539ac94ad6e92454a033859a6b0c2b5ad725d004d954b4540293ccf7109555739550b61be7fe4f4eba0919a84c53939ac3925675070a4137194d6c8e9558b333a880e69434a791acb24e17424750e3bc1aae00be0f86ccf0699a5264b349ac63c9ba5aa0074dcd764f39ded9f7248184fbc73575917b750977d619b7d44d6eaa8722a0a8ad3148bbdcdf5cafebdcab4bb8b3ceb8a56e7253dde1ca2a742c21ee650c6ecc1c6e9299b89629b83393887f64ade69b7fd83bc9e214912771c7ffc63f1eafe0086b5bc28f082e5ec50dff10d7e8bc8fdbfd092eb178074f585b126e05b0b615f7fe437ca3f33aeefb27e147a3e5a5f873c6c4fbb8ed5ff089897770cb7fe10743dee166c6ecd51490eddb390e4ace73c4fc1f6b8b8cf9827d49277cf18a93cdd959fde0010f925ded112bd606cf3044294d4948489649e5bb3486bd5ab11fa6877828302ed88ef0f3ea1012a9c89bf504ec3ac41e20554768931d9f4d430457f5ee328c897c4a044650281a3c60d1a0effd447f59460569a605c6fde96aa58ba462391e6281c55872fa8c1e5ea89deb10c3b21e8a41a09d52a1cb95c9bbc2b30fef0a0cde320669cee13566186bed2ec1385ac7208d6cdfebe27d0fee09dfa9e73bd6594a84a343f41b6369736d6285a7da9cb5c97bd1a77b56ddfeea0293658c6a7ab8a822d0d928237b5376fcb37fbec6101e2be50f2ce0dc4e9a390a62ec6793e8a3ddb5a52f6a04e5a24611d3526c86af495d23553a16564e4c47e0896825eff448b00616d7d47323d4cd928b92ca942754a2e3fa3e0025dc855e2cf29b51aba72f549d44c1f737bb9c2c5594a073c1673a9dc41777b3bea2e5b7e01f8cffc4dc2344dd65b5ee655a95a741f750c53c74b722e582528b03d3637f78f320a84c0eea6dfabc1b095776570d3546aa9f695138ee05aaac3ef8427320ea2d225e279fd407b68ba4e017de3b4957a877b1080c2f051a1c6ba5551048cb481dc419bc348900023f1a70d2ce4fdf84b8eb9df6510e60c22b079c4043120119812f49dc52926d915c2b2169114b646ed29e0609211eccead0076a416c002b2fa01770a7fa0615ac3aaa63748516f17f0adb09130a980a3838aeca18bc5dd77954a53e9a51be0a3df0aebace697fbfda8fe68b65ccbacddefdcf601da65de797beb5423e9a2f5d12d2757eed2c530fbcf9ffcc5b2bd4986cf0458d243576d4f8d11da57ddc9ae961c6872e70edd264ac65ecb3df0c2b8d308d6fbcb75b9155cc26b355893443a58b31a7f3a75599fb99e50db65579976c55da23a4afb6a85a6aab12098d1b62d2194d74b7b3a60b86c12a366798babdd9ea9756b42a9192eec3eeed06fbf0bdddcca042c50c0194e18332c69421a68c31baa3cfac4b57e1f06dfed9ec3ebf024ae424d10f61cf6bcd6a7f6cd17b20d2acf6a7525b43a544d622fdb5b224a5f740a42246943c8c2fd1bf50cefdbf507873ad94087bb652229c2dcad67befa3f7655f53de2532fb2db942352c3dfae3ca9268a88665139c9344951261fc9277c9d1e37fa1cfe10fbd4747f86d32fac2e13b9917dba39cfba29cfba2f09de0fc4993626c33d89add274a193fce0edd3fb22f2cef926cbd24fc7368ca6f72add03c4ac2e13bc9b92fc24119cb218cbf7e964393063d96b5a30c1acdbfd1bf59223bab2e9b4c5e5aa593ca3e4868565d299b31a234be343dc2bcd336627889a1274615e3bb6f6ee8cdf5fde6c669955aa14fe9055e3aea2cc16c86e52c4d01d304992e3bc4a0bd048004448aa040aed140fb73fbecca9f245bf993e623e54fba7d76c5f09dc4f06dd70a11a28ab7cfb7cfaed8c2dfbf13bf471eebf44be7579cc5940747acb2455569be4d6687f287def7dd3ebb3c9974abfc921e83eefaa4ee3d5ca64386b8cfac0bc98f85e0a77456892b4539ee1ebcb9f3786d4c4d95272b8d6294b31c83f2e806e247aad62659fb82b1f0537ce34cdaa4200d13001105036cd077809d04e80691d66b2608d513021d700446741b33ad0292259e4075d37ce145172a480117345b74d3a0a09b468b6e9a2c684e1047b5b30faa61d9048661f6fe1518be478ac49c556cce07fae8eba32fdc17b93efa8a2f843ffa8a1fcd9686b3f3a3af88ef7f1fcdbef4b42693f53f2b3f5ca9a4b53cca2c27bef199f7387bb1f7bf5dcad670ae4275f39344f8a32fffe86b562a44530023badba34cb24799cc479af4e768d21f2c148045cda4b14963b02b76982efa933dca247b9489962068a2e0497f729228d664927a3497feb119efe370a6b59fb53cdad9ec2988f112ae56a852a248672beb37f9615b338dc63e866996f28abd334cbdb791c6eced66675428769dad46422e2197f8c8eb0fe524518cb7af9428563b9b579542799486278d75391daed6c3980a7761ecda8c3f0fbdc7577ed867f6976492c7db673af384132350a17193fe5cfae596645bf4f466d86799edfdb2bdfdf43cfbc13e10663328619e857d36cb608fedfd037cf46fb5b7cf343f9616e67d1f8865f47eb0fc2118bb59fc38f9d10cfb8bfb0afb68fe6886658fd29e1863bee7655b675eb0712237be11e360ef8fc49cd50d4bdc8a0600432278234b1a5d9233b11fb8af3d941450a7a8544a7c94782a37292acf7e88c5ba4679f643eeee754a023c3729cc0727c0a287133a28f199bdd7c3014a7a9c086c7ad64b05783c691f61643c651713f9f06841135dfaa7ad364de41e22a0e32b25f169f38148691b1080b9e1e5026a1db580bcc7739336089e0d088874cb1b0e1675b4f083046614e008160d267cd122c7e647005f0837365b70c084b0e504590a5bba1b8709299070d1dd3925a460730c2055595c701b264ac0305aa5b78ba6b100cd966e1a22ba692ad0365e9eb0f1d28566886e1a217ed050a0bb939e38f9e40df67f73ec9344fff7f533dfc6cb0f9a0948404b10516880c8d29386668a8d07c8687ce377cbc9dbcdc603583acaa4cc03f3fe9267f1916e1b0ff858628d25ccb0b7bf36cb2a78b3ab70ae7f731a5fc2a73d4fcaf08d5dc2609b2e46d41061d3c547171edd3518982140960b588002361d38e24193a569f5d2257abf9f1fbe6dca58b6d5d2283163eae59957ad27417bffcaaadafa184bcf28ff0d53fe244182eeeed136493c914406922022891cba553e74f4721f7af272157695d2276f3e7fda6e9a1fba697ce8a6e981c70e56a620510211708056e5e5d1f11586f114a1cd87cdef217600432e3cd02aad34276f2e7f89cef8e89623061b24b2c81fd84356c59cd4d0028d378de7d96c4cba6964d348a16e9a8ea6a3194233e48e3b66ea98a923d2c0984b4bf3a4774b867e5a4d805a4b908a94b098a167ae68a1674a3053848117bef131b06383012246f4002f790a158d80626344ca8801747b9e04a2c99beb3d2a976455f23c095495b22dc22e4f0281718110ba3d49b34f4fb3c0025eb28d05ac74871ead66d95f1a14e463b3050536450c4002a12e79fe937830c5f773efb323f452eab1d9d2753b2be64be6d36832f648f7eb7ea8f32ca15099ff4ecf7e2aec2e7894e577954e11558e8e5feb39ee954a8cd155465c55c4553a523ac73b478a449dd0cbe5e4142982e9928d5d70becce7774790384dc63c2f85ca4f63b1304dfeac1ccb661da6c9d80a77f73fef6bb6ae4be938919cbcc5146c885341b912254e45c789b84d4a8755f865f4e6bfd9a7b9ccb79b95fd7579b514c85df06871b95a8bc32ae5833c5d94165152e7337bffa3defdafca5c65be5f6e89bbe091266319fcfad96796055bd1db6398df57cda75e71a6f826674c93d42b8d5641b779ba3068e9954b8f1dc3e2cc7b8fca8c3b9afce96832469331a79e57fad3d616ce49222966b2348bf58a81b8a6052e4d0071c40608158008a1fb87357e28e3872b9c855fe52c95b3f04c1126f460b32442f704faa186279512c5ae739bbd028e8b704e12e516ce0ee57e5617bec92b8f28ac064c0f45b8c70e363c84a184fba2595d93c6aa92cd606bd29f1a356c32571750c436991dca49a218b1e3ccfa5f52fce8ebfe0cf2585f56a98dde9c52c96b515e2d0dfa4993622871d7ca68fede869dfefc5969cc257e3a6325ced4c7424984729f955f9f954b998a27e6d743cfe3a4313086278da9306ad298e39b9c7bdcf288dd7966754c3df0b63c1e4df993946d912d9af68856e9a43fd916dd97df24dba21e3c39393aee13c3ed630a9a7b1f533cadbee7c99aac7da030df8af3dc3e671eac4489cfacebc4af16e623063ea414c9968c295048531540492fbb5628683e4ed65ef36ff25ff3ff8218bb80b0007145cfc66e21bec91a715f73046de8367b9c2fa34b5caa0a72f9b5b90b65337b7f26a3332afb0f4fcfd6bc3ace0e29d5bf62af90bd7f004ab4642f0dc2b6a85ef0677aa085a204ca0844402a105298a94112e166670e9b8f88951e423e5cf727bfc62ac015dc2cf2c4be9ea8c8128bb50a4960135e0943c77110c98300c346038a40e3072c68f01044b541b865279834e90493725fe494e86e3bc1242c617682303b416c27886d722f2cdd17d809da092675c4b15902c35953fed03c66a15736d9d780b08cfd9d5958aef6ce6095c232f8d1d8a4b10a56f0b35d9a15fcfc6110c39e36c12a39992bbdd50bc696a82761dfbf0d276b19276b394676565d33a3991146ba5608e36ae9fd59959e26f1cce893447866942b7efc1403fd3dc232cba099d1acd449f8836b5836c1ffa13cb2b1d6edd259b541322664ab980bbafb15b127976e577aad385f56d0339551efcb2df1886f3878cabf1ff83689cb5582367806eddfe82be2c7587e61ac52da0eb1cbc91b8bd5e1ce67f3bd866593297f3c0f149af2c706d9972daa4a3c623550f090000f20a850d12828b589ca2b7cdb6781800089b406fa570b622c73ef3b3a0e8379cc347604c9ec2f52473149c1d2f8c6088038df558332e078d5ba4d37a0d1dd9efde48dc562b16ef0a1bbc3b7b13e4b0f577973f7096e40e1da283ef8f4ec105170ba6f368946abfc759e1c0cd66082e2a5bb3114af4d5088f4943f4d4f74e81e8a0003e2928d659ccfa7343dd8bca691297de9e6a1bb7fd4ecd0d77e4a44aaa5d8a3b61b58270cf6d59a5e4768495ce01b33786b05212347072963420729734337120bdd311060862071e4749038987490388ae8207184d141e2f0410721a342072103d441c8c43a0899b08390e1d241c8c0e0841d4e50e60520657c20c34577f78905091c7577cfac208709a94b371bdd3c4052f57c7af38f0d5658ca03e887470f8f1d1e383c5ae0d1c3061e4348613ad28126073caa697c230e903146471adfd83e42cf65cd50778d931a665e5034c50d13d11bbdfa51d3240662d8877fe65f7bef1213518d690807d350adbbdb5947aaeebd9965ac1e7a354c3f21b92664e66bf8a1060538eb6f569641eb2a9f954aba8469f78c0b78504dbc709171d5c01586fdfbb96a7ecfa4a08689104f7525b9869ce50ad2a48d2667b8ca95a8f09b9a2ca0490e98fafd0f66330ccbfbb6080826466c214573400b031020f4c5fd25a3d09b42551a87358a889f1e59729b91c85064f57364c97d566e6429dab01cf258adcf8f859eb13caa2e5bf4483533f0aed29323a69878a2e8eec7e050d1ec322c18acc8c89584b4e4098cb56488204c3c4033409a0ed30c3998173cfa015b70b48ed841d2d5b06cd2b9aa4a243afe8519e59df4bafa1bcd4f73339b42c5ffc20cebaecd3229d4d1e80cacbdbceb7c663d3a69d30c5cba632a7c1bcac39cbca96cf689abd2ac4a33ef936c110c06833d71721f6662195d0a7ef56f13c66306043128306104129c2146890148e8ee26a10914ec8c5087aaf66303b2813811471620345657c1e5fc280304c48d12ccd8c93b2fdb4ece4e096d4aa2834e12268915d9da0bbbb71b976e8cdf839992f81026c9dda624af243da624deddb0ff599210baedac5258994ea3c3f49b9064e179d24b7723d9d2f829121dbcee4672d4b09acc7e4876ba1b89b70c74846f83c9904577c3bc744faaa5730d93618a0c418d6132a01acb4042f791326d3a32e648982360ba1b049a235b1a85361df1da6d9ef2a77b660440aebdd787139311312623b51e130c5a4c4654353324311999b2d35d93a4bb064977cd901a196a33b12504a2bbe6484e4ebebf54adecfb3a10ae9874dec03762d8fd0f859a15e8fc2fe4e972f2f682f7782a276f2fe0584179f4658ee356ba2f3362ac718236e56409ab94535a2ff43c82d276d718e9ae2952b30202681c60abcd1a0659acf91567f4d3167552d65da3d35d9353b3020d269c283d2606b18c68648a44daffb332ec1566938f930f85860634ab15cc8c11868a0a26a0d04256c159a6339f71c244a4892e6284fa1a3338009165a6015f835313bbbbeb98d4c033dffb98f45a33249850748861425d81da407763eaff85d883504add5d429b502d136ac7934b30130a05d3cd0f6ec62ce9ae5975d7a84c37b91a22606682689a134c336e74175d9051230210244c3144c9d7621a23189248a1ba6b8644dbb5b3b6810986ee5ead5635a91d7f9cab5ae1948f849348dd3528a1992a6880e8aeb999b688c55a329b19d51f2c6b4add9dff8ec3e057de7cfe632c7576925c480db1751454d44262626ac1089bc9c3e8d6c00c2a7423a04d0ed44d5cb7cfaef7645509bb8432b64df21d3c985800405b9af464ba323d2a6a12667a41138da9b1a4cdb4615ac1d5b44a6158d2ee2f59cfb348b95ef08756a9490535babb67f4e8a9a4d1bfbfa4bae04fbc602c85fa2b3f7c14b1ff1205f151b4c9646c7efebee5319625cecfb628d779a3fe5ece8f3178c516cdb7d9a5478a8149f395804ca09da5d32a55455a7c57e56e3949e431e7bf791ae1b8acc7209757e93d8d4fef7fab188823c6a0f739ad3732398b81385219c5f247ca6287531e9555d0ca3c59cb56455b1eebcdcce538532131100bf186c082863b31b299e66c8b5ec816853619cd493130e85a21a4a934b11dca180c9a8fb3b32e71e52451adff592d8a14799c55aa92bcae73f993e2ce31add2d9cc76dd0d0a70f7f1e9915906b9ca553e3d3d375a4bfa5c89bb7b029cc5727f8f7e372bb3b5eb3ac7995610574b1f29cc7452d00afd25eab5be7f27b44ae50761dba6005c117f263118fbd05501f0e96e619bcc0eb9db6432cb20a41e21f868757748e7675b27fda9c940fba4522793fef43099806820aec29bdd6b62e8ae81a1bb7fd4bcd05de342778d90a7f73e36fd48c3f4434c37f1e79c248aa177a9b4510ff765fb6149636e3d70da5b4d8637e36237a31eca6aff71e7f829feeb441eefcd4c1db9ab1ce5d1654d76c14fc96375cd9f7549c69e87e9129df2e6821936c4d4800e1edd261e6faab181a9264b4fe08c3156b0032d32d0404031470a4c8ce916c8d1ad563830d8473ffaaaff199966b484d79a663ad34c4e77e70fdd3523c2aca09ddd6861f3e7fcd8ed592cd887733636ab343592a96d30750ddd5d73c95af2a62680a97fd8dc5183ee2cbfbf77c880ba03053bcab4b3e67dcb627df8654a76987ae98232d0ae6677a9d2d5ec2e09213da961d94408e9c9acffe5c79e2761303a96ba8215fc8109dd1736a78c81ffb0fc55603110c3e694b1fa3110c33205adc84147cfc7a00b06ebaef1ee1a166a56e8eea0dab291e34877c7db67174c8e0c0bc118f63c09837d36db94a1dddd3d58404607dd99e2ccd01ca9834c945e7a2f16a320ac200e3a6ce2e002840d1c62e048011c5c781873ef32818e0247149b37c6d8bce145775fa11856c359c3d94d33821ace6e9a1674370f1a30fab3d9bede4801cba1ee4ed9b80fbc9ca8b3cad1dbea671247a08bb638d2dd5fa957a91b5984a159018f14ca0d2c1550c2c68d26ce7a18cc0d1368957ef1af6ca34c1b61b8bd6d08c10adf066b0347168bd5060bed4a3c7c1becb3d9bfef41998edf835c0fbebbdb59e13bc9580ed9d7910dcb97cd608b8d31366c4051f36a69fda1f09de424910f8eecbb2b499756ab95151c9c9b95457a85de98cc15276b38e270382124331c41e22c6751e956ce7a59d491c103a5c47469cad5fd259b3d59ff734ffeede6674f36e95c7a3e458e20d1d1c1e97156b543808e907c88bf52e851ea819fe5ac9f69ec7e92a8a893429318fc2bab367cdb12a2169290db5cde689cefd8560cda5a845d46b3bc59f7d9cd6db6f869357c2753fe446c33fea84ccac775df0d8cb9ca2679ccf5b1771fcdb8eb3ae74911711e1fe244fc7f0643b912ff6886793ca98f6694cc93329812cfef05b2e658d65a37a875772833fe25ba74418cbb01aa5b0d31dd1dd5006352234babd175c720d74b8d02d8000536f8bae36ae63db66e031ad22813d3e09246678bd2d8498384ee1abca03b66fc93c57a4d178bf59a140d2bd028020d20344ae8e28c069cd1a4fb263fe31be954761ec6fe2f36430733aa98d113432fa9a8e8f3ecbd32ae28434b370dc6a08107688044031766d0c50c8e98419319b4c860838c1b9021c4adb34a79a3cd64182143003220a33b7615c459cf6bc94028063fe81203a518dcc480070cbec0e00a0ca674c72e7f52b53486de4dd6ce57f1bd4bd5f762fc9bac281fe244fc314b7ae0edda99981b88d932eb1864c6d0c018750c14b410c38a5183182fbc00062ff0d21df14f1cc69818861017d8e18211b800e602a216906941981674a005575ad0d3dd75b250de640dd358579562e881a10318303062981c84f1120601617aba3bce2cadd24a6fb95222167cc10213b0a0b6c3821f2f1bab5bb522be76b602225680c30a4e5f50f1c5952fba2f72ba6705595dbd600c96a5175478a14377ec028e2e9ee822d645933854a0c4ec3a0f63f86bf95a9f5915ac5240c7ac52958214c890821e5c8ce1820a131755b828c0166d6c91832db66c81650b225bc8ce579d12c79dafba148e2b8f187f55e2b2ce572ce944fc73222e9335cf37e9995679f31bce411b82b3cffb9acb0367df85f86bd9ad2c625a05673eb3d251d04201102da2d0226b21d3c294451d597c208b2759d49c004c77c7d0fb259cc398bd559c8dff379bbf0fcf5c33eb5dbe16e75d8797c47ce5b7ba3f3b410a587081851558502c6cae40e38a1eae306245195680b1225b81002b7e7477e4e1f9190dbd5ca3516f5639b35765894c5001135013d06002ef8eddfc2bdf9385b55502232588a18a31aa6041153854714345185460a142670a1a4ce1c4143e4c219b42680aefee88333ff7a95ac6d94b7bcf6619b4c4abc4602dbfac93996669bd18abc39fcc94ba32066f61959daf705f3b5fddce579dd792628a9b36492184149f143048f123768f71dff92a1a01918008cc1d603e00468299218a39a24041146114a88eab0378c6f6f318fcef91f3ac32b647cec23f2d51f6308d753ff826bcd91bfbf953fe6cfd60896716090a16406105144140f103858f66c58ece269db4f3afe5fc18cb59c10bce1cd3a0fb572abd557a9b128669e8d303c3f46fb2ee80d893372afbe9c957adef7da55e75cd87fd05bf6b9f10d3fd04982770e82cc312f704932748e858cbb8af2d27c2b49ce812e67c4ee951aec526277e447c932b6d89c0daacf23c1164114127829d10a4417b8f2ed12b65f83d09e4c362bd90e8809e040a4100be60f1654b47fcc5a6892f9ac02156fb8519d6ddafb6891540c00508bc74c77ac1f94ca0c104154cd4989031410213ed65092f4a5e927c208d0f68f9c0079078604cab5498e68f41f198315805b4a177f706b4a12b794c752c5606bbbf494c5561eebdd92f61fbb5fb3356c37f672ebde2e497e481214b9cb1c40596b86189205dbce8027639d279f2afdb7a1fcb9faef3997d7545d1debff2d11aae76fe2cfe0533385bc90e88a0036007541c0883035dbafb63b055942914be3435511c686103606c4089fe9f3ea361ac2a2951a6af28d17a43031dd000150d0809230908242193084006ecc88017549b32a083acfd05bfdb79540ae5bef355adf3d52ba793208dc66e12d3e9f3bddbf92ae20b7e5d18f358e990213163ebd9d69021ce6252e42a426a418917fce487b9c4c1e5082e52b81c718981cb09486cd11d59ab18e66432f749ceea609dafa2c73a8a298cc2b1eb280da5f7352c6b9efc6b67d585c40e1226f4116558dd1161aad1115f3e893b02cb6c1d71a46593ca28988401336618d0a11d031d064830c2099311b9eeba6b8196c4dc1bb113bb9fbf7cc2b7791e8ddd3f9a5616be932348409f77d223fb271790e2025cbabb94ce055edf4dae7fabf6d2a5d8ed316ec51e8a05e6b0c0062cd000a13659a0c723b6b30815148183a9889dee8ea197a3e1db6220063d0f193264487c1bdeb2c496275b86b6f46c4161cb08dd6d439b88a882082b2622a274f4bc1bde0cc6727d1abb1410caa873d94f2bd3f1554cfd12c595c5b214f6e10ee55d2a49c7575d95b2cebba4eea795012539110742194d2b23e2c7ac550133ba3b7a1e755281fc49157040a6b7bf6f93444b7c5e8f65edc3f8de252cd62b8336acc13e9a8730a3bb7b882fdd1d6d6b88af3b0e41430f4142377e1a0dc42d21c07816220bceaca09d499c10b58f662148c0996fa280151d3f9a294081ee6e1f97ab6a58d62630c50488304de0278044026848c0879b1b9f59a44abbae8bf597b494400b125a685a9e680940104f04d10501640a0208103e00c20820baee9066b686e4eeac95cf6c87e4441ce9a379fe95ade21092d26a6725f45272e93c1fcd3e5d75edcc57b1be4d667c63abf3e0befadf2c931f3cf003961f8e7ca0c387287ce0a28afe61eb01f52066043d2ca0075b0f4d786883071af000040f3c3cf0d8e14b775d90afdcbb944e902ba6ba6c6b18fb68065d915669b7fa68f66b69961964b9c294e5872c48225046048488404f0472202006021a80c01104801ea0830708f180dc035ed0a14c77f833bd506241beeab08a85a77ca45890af62a7b37a75917a38d3eea3af2af35c255375afce67b6eb522f9451e7acd057b2ced67095f6368a3ffad12057d77d20ad6159eb7c15f363ef64a5511ca40314930e26981c40c6015238408b036c1dbb178bf5ea3c097aec5e9d7bec96c48c1fc4b8c562bd3a49143f9a5755d6b06ce283fa68f6ac8df9e0994f0f6c7ab6015da84c9132c5a3cdda56038e7832c5612a53eedb09622953fc6baf06c4d0dd3e2c960fcdb1f4574f4fcefe789e9455068460c262069621b034e978bd550c6dfe5aced622e9b46901255840071640846901411d73727254393a4ec493b2cea1f82aa6507eb3b7eb72c0410e3030e570450e52e4f0c59483027208731082d3e32efc04f957235f455fc5940ecac8573e3dee4284c1c2b7d909a63adcd70ee5b996c1d674bd600ad0a2805a6d29c08454f6280de513871e74475c0b073034f6c2414b96d3438943cd16e1e009a0a39b4902b848008d558ab39e5b9b31f5982901347cb5a62b7774479f57cfcca88811f04a92e98a90e98a4d38453833852818e47ac8f5e44ce88ea99cbca1a848c12b9a8b5da7137f62597339fe3ac7d9a3b43a3df4268df14c1aabd4840b010e02381bae8437565de7333be94a4ed312104b499608005e017a31af62f628cd56e9ade1eca53da67e671ec154764186c274f304bace57b1abf4365b5d5ccdacdb1a8e564bf3f568ae14b472ff684c0b6fe9ee9609d3206bcb8483442aafcdb92ff2f161b15e3d336b7b72f6a767d6b258af6b80394c564670adcc4c565c307d68983e2e114b5a938138d5a13c530fbd6ae3f43c8aa357be4f2bb321a6379c307f55be124cd60a4b3bcada376d2dff4d16258dcebaae5aff9877e473bf47f64f303662b1eeb5f72e995557c6f6a392536452296224f7b39aea923beea0a9b2a68a88dfb5428d89c796377c10be0dd642cd1d4a4b92d400d15df3c38f0661e8dafbd3d6f8d05dd3c30e5e6c65ba25ee8b90b051f1c4649bb1d5335376305549e3892a5d5a70531559054a15a52a475568f09945b9a9ca0f13f582461163a62dd8ea59fbd5d430d558505b8195dfb54238578a69f227633a24677d5a6b6684636052f8d1179e19e17fa1fa776674ff0393aafcae15c26096d70ad95975cd02b10afacc118fe9e98158e2686de12fddd486bb8c2f99abc4fd3aaa4229e912c694ca149594291e5b7707532ff2fbc38a98eef82d2b56b0745bc9e9c6d40a09dd31ffed6693be32f10b135ddf97eef881b64c77fc9bc5368b68bb3e591ababbadc7a46a46fd124ff5ab4e6e6522a6556a8b3730b1bb69e9be4589d35d372f4c4789ab96b63c2f318fc86687ad4b77bc56565b29980a63d6b6d45502cdfe7e458c803e3e3e9e47633e3d30d80dacf4a32fd4d0a8fd5073d57e9c685bd0be8ef16fde7558ba2a762c56a5d403679947e2c752f68a5e77adc7229176518765eb32066747713c6fa6c40cca2c8607e3c33732679065c6a60859c812203b8a5385bdcffee73659c436c7f94b3c761e3f36b33e640828cbc9592d0c56e9adf321d1b152ec8cd817f914c786ba5b4e40da709249240c9d19dd0aba06743bf16b39e71a8de29ae4a924949187d54eb759e2d0c3f8c30a23a33baa42ef334c0758d7ed081083001f2a6c5031e244c501548ca880709a42c51423a604650c4ec7d2f3d31e3683c1baff507edfa11c5bdc4fd0dfdacb5791a8eb62a68ee3ccfa077a0dfbeb24650a293e48497280340e20e60058ba63175699aa602765561b4351e966b5b14e8ad2077673b66e98e30631372871bac1deb0a372168eaba1d410ca08e9069b28248852eb686b38e950ea800206142f1eb5b9cd2ea3352cbd2416eb0514fd890e9ec8e0c9104f983c01a234859255a2e1c5c6ab03afdaeb3f2976b453d54fcd28956a531dea97ecad73cf935ffd512c963c253520099624c209490d24213a7bb6aac0d47cd9b5286fb9bc6fc318365c4002363cc08604d87073b281c7f48c705fa472564a07657474e5c87674c4480d232e464418796d8b7c151fcb5c88a1f799568afb7a8b70d10aa7a21188a42092111960684888a125a7a11f273f383961c3c91627312721505183d7ddf1710d37a337cfb6e2fb9fe36a7f2af5d5575bc39e9337efbce54358d7c80e61e5bfe1cef3680dbfadf2ab0d61d662f9d5d0f1da2c9333294b723dd11d5d374dd4e86e277d6a72812651dcfef82aa670b514e52f17916b88c56a6599cc52962a42659abb89f86d762d9838c164034c8c60626322c4440421129c847c6867392b7c9b1773b5d22848bbf26775f92aa630c558a23cf452ab47422d096249cf92108272109446d0114114089205bd10040290088066402afc74f19325623b31c5adee7ff26b47ee3185f2bfd253b44a510e942a4219396bbaaf52a8228c3262dde8cb5771465d55a58ca7d6c9fbdd953815be56deff501efd985a55b47e68b14e34a841030db918daecf8bb1139cbd62b71ab866596f7bd9f451a086080384e06f0a2bb23954e26c56c2dd24feaf2a1f2be8f8f8f0fae96e2f91f88e55d5229cc47f64f7c70765298cfbc4aca9c947451f275b712a39e32dded3d61bae74bf76cf57c7dffb6ea53cf4e0f093c5cdc2ccf96ce8fa94cc9c99b94293c94a784ee8ee04d4d965bac2d5897d259a507b91cdfbc268db1765077b6582674f70c635a3370d19156e90c5c66f8669801b553a63b565a74dad9d9a911edd0b0e3dd3149991693248916ac94e48bf3948486a64bd5d6561215e2949f93b78f666cbf8be408121264104386214e3230408624477870440547b074a78c3e85a967bb1667248d93112f46964e466a30c2a373f29693b7979111124ca7969344119f8ad08a149d8ac4a0b3860e17295fcd54ad3772aa38e53020474999138e08706e3845701cbf07ded96a76dfca5ac6740ac524e203a2778a1f495849715a6d49e95cd00ae1292deef5d57ab5b39bf29b74b56c631e58eb4995864a081592eae644040d22491099272241447a9c8684b1be9ad56dc67578d29faec3f6fb4963d77a0e7f2ebd4306302675c40e29a0532ac829d5a82650b7136aa85340734a2020a0506256f7f91483173160200629a7184e80410630100183100c51062f2c717a819e5e18800b57b8705d4841081742721d23b61983acbf85a00d6bb8d847f387b3074e6ab3ad97d230665b48727239dc8c938fe15abc8a13b90a2cb880852a58c8e1c402664185130b21ac208315905881ae50e4a44299930a635410e2a482014e2af038a510c529052c93c67c858d7c15696c0524023911ff4101d9225fbd8a948c806041ae1f589416d18ce2e0dc6f347f96651464f44279ea076584fb4a8432024afda08cdcf3242b0534692b56c1ce278d5589420e28c8503889713ac18b13ae9c4e507242905301d028c00f0590a7020831010c139030a1869309dda99f1f6cc46d264aa11ca3dc71a655fa9eb5316ff9cabbb04a15add2e92aa992b44a63b5140fa088d3001230801f804e10344e415e700ad24310a5ee1ea14f41989c4a80a3041594a0815309b8127c4e25fc3865713a8de0a4c3c97512c134839309042713ccc46363039b246c8a4e36454e2490712241cb4fac208dfdf8ca5771e20c5a8bcb11488a28d6f26330dbef6b407c15511e4595c293c67eae5d79b77258b514c368d2d2669593c66a93c662fe389aabfedad9941d15c0152701e4208029027861041d9c465062040498fa34c2d0083f4e22e440042544304284a153087474a7a238113f72224e44b39e1371229451ea0865e4310acac8a3e749998ee7c9d4518ac869d6431911a18c5247ae4a619413392b7c2711678ac336e34c6f3e3db3525b085d8a42c062429f42e86208a8b645a7008411becd6f96a8f74e70005800401a27004cd1dd51fa0400d909003e27206b9c8050e004c4e7042484c6ff85299d94a7308892d19b20975f2b3f96547f84a71f379c7ee89c4088e3044218107c388110e50402cac719271f5dba5993fae79dabba99d1745d8bc293c2c2b7b158387c2791669b6387c3b775298c0adf96947b1f47f9b1e7a1ea635b3bf9e858ebd4434c0bf34f3db8749f7ae45ee8538f26ddddaf530f7ae251a61b9f787ce9eea6a0bde1c4034b6ec9fec9a9a6cca9a689534ded54f34204fa19cdf2963fe9447381eeb6a14f34359c66e838cdbc4089cef2874439eb0f8919bcd663759d0f8956362476b74e9f664e38351ba7fec0a96ba7ee4ebdc21d34b8838a076de8aa2cfbfcd3edcb3d8a551f9db922ce14cb1a823f77cc3b64b0238eeeaee0fd3c7365f9df5f3bc0d881a5630ae51fe8f3df8e9d2057cb8e3ac2d4d1a58e1a4e1d293a78e8a8f9c11a3fc8c00ff00f52c8f5e947657f577f319da3ce3134470b73f890e38b1c5de488d2bdaab64a582d0b95b1658a9489a1cc0f3260c848323b644088e38b3872dd9d42a95878e56e957c1559dd6757b63aabc37d6af51705cbd622dd4c2f18bb51ef6b705c01c703e0288203d5ddb9cbfdacd2dfc06f746fbcdee87963006f88e0031df860091fbcdca8c30d319dc28e33e523b98eff04f92a56231df0f619676bebff0ab9c1a5adc56eb11b51badd50a10d32ba636afe12f5da48a20d0474c7d412f550f331283fcf37f5faf8b8aa86650f8ae881941eacc00619dd5fcbcec6b371840767f0c0071eecece08c1d04b183243a2843075a3a666b6f8d783aee2b6c66fcb10ef7b5bee761a69fe57de5e4e4e8d895ecda9f495ca3d1da4b9b5d95a53f6d4a9a7f2d873e3d19d3a1d0a7681665099b486428d2440724748fe99883ae65ca010362a5d6e69bbdb31cece4e0c71a62566b6869adf175afd1e4263fdfd648a12785753878a34b9cbfd461896be1e02885c201ea961913a67b4c97a431b93eeaee380635e6479c4937b0a23baf79a8e413d7fba0756a4600002028931000003020140bc62332b16c3ee9fd14800162b856b05ea1cae33cc821648c21c400000000000000461b004c9b09795e5624098c79f82d8f62a2db4da8cb9481beb753c7d4fbcc0011a86a0442ea728843db6e9ea28405b06369186c3adf84313ee780fc84c70dd954eafb8e16a8e4389d4f1da75f9f31a087b47002345f0c1bbfd168222388e873103723ea63ca1e609a996a569696e0a3f60d3a08c7bed33fe313dbb6cab83436bbd4b8b9952d20984220b5d43340e31c519134d91a32b30f0eb627444280abb4106955136c0a1596c7216f454370f684804fcbee4ac7a7682ffe0d3458cc1405c2943dbd27b3325f10f50d2f5aaea27f2d3b72e4adc5bd847ac9a6dfdc7c27041afe45d39257f942171e1a335331236d26797a7863c63e830af8a3e51cb410fa0ac629fd6f4a50cbcc48f437a1c08bf086cf3f58163f9cb40e3945e635adbc8350628b3b7b34966a4b2e01bf64ec1854e4a8e051e4bfe512fcdcced905a14b317ac6a40f5b2cbc00f80c190bd944b6bb630ba7f03118c8a4273cbd22cc8f9e6087bddb30ebf7a6b6ffc12d590f83dab58de83b9a3782789ca57a4e0fcc97bf2ede6fe54c018879a7dbac20827cd0cd8d97e935d4be20f409bf7581c2daa8bb29a1107bf20d7880db2d0f844e075c715f4eb36a621ebe4123aa00c065e400bfb096963198a776e239c4eb1efe551a5c82b0586a3f708b58a917a50281ed99a5f7e1e5e2939348ff6fafd01272f01f80fb315c175e3f219e0aa4f88ee3094e8e263e282c9b577ecb4e8a2d9d0496310523ed58a01061e5acee370cf5a07ae2c32029e430d5fa0cccd63b5089984902d232b511ff0eaf841fa90869876aab058e62c0870d821609caac5541eabbb8e42da37086971958920080bab497b46cb2b4b68a49260ff7573c5c9d89804227f2851aecad918ffea473d6ccc8985ab2d3797b4b868f9e5d6af03906a6027d77b8d71126b606831de9d2036ef4ddb4d2f4ffb2f3f5b5226bc027741a26d284f4251ada6b49e69f9378b523f9c12ad2b5288de3218940d0866c2fdf16cc6fae6b6c14deb20bab4735188e10b5dd66fba11123e901c016571bc6568980c9d96d06a1d3181a3a6ccff54eb129946118158fd231f2becaa3123e968f6fdf47ba0de7d2268d9c0903346c1cfa6df49ba212a7a520e919f5ba354cdf68a17b40caccfa6e7e030248d47b152b4c69a91594906cfc4e562844088dc11cdc1e242e82275d6da4b42adad609c6b36451a54abf7dcaf966c0c7a723f476e66e78f61e497f9bc700caf7dba1933ad0f604b6da4287284346d1e129fabafdbdd33bbb7a329a50bd8a029ca7472185f82b83e1e5e16d4a754b7fa3b3ce5f9761c0c7563518d204319a113d57f10c8c30d5714727695f60e7b8e66bf57a61b0b7b0f366c1791d5fbf3ee4fff9cb26c000aff73799c37f20ff7f192b7afd4edf03f814d57e358a5c53422421669ca69ce5550840b8475b1044c1d1c204a61b61af3ea82cf487e90bb4684b045599e9afb1bd551b2f6f1571255d2bea727d2cdeb2bc96045e717d3c866a2afe57b57e43f018e35c37a231220d8c2e3b74a2fd2bae8cc5cdf5f295a3ed21bbdb415af746739cd5a5afc47932751450118c85348455d352f8d61138536c4f6eac0829a3d7c85ae1dce77e8477a2180176e6dff21007ec543ba808a8ee9014fcc8953ae3228ded0297b856cb8babf4bccbcc238e5646c06c88061f154331648b9e51a5db10e04805b189393d592bf228a6764f3fc123a1f6bcb6a5c7a0b6aa078491fd3a65b78381de1687be99254ec0937347f9568980e70652b0fef6901549db3442fda73af177c3e07d5a6beee297f16a4fd0f942a58447b84fd2f8c1d8f721c126c6815bc626cb88eb2d44e92f2cb828aeb9b5f82120e6f7c5ac12ca49461ae18b69806e75305cddd443ebc832826b65fcbbc911366466fd5b8621399cadf3f5e59c04d0bb89475b7ef43ff753e0867516c47ccd4881d1921591c9c102a82371eec375745867e51916fa99dc2ef666257c8d665e05d9098a59788388babe2adf32103ec6661d728f40c7aecc52defd20a2a640107d9963c0abfe1497f9ddebf50b0fb74a79d9f683d5b094760ca50883e0b9f6ac473e7b17535ffd1b6f0e1991841f89a8253278c4953d68994ffff131bc4d17f5f3db01e74df94e0bbb7a21b7821b5d8ab51ce288be1a8cb9a6b6943d252bdc6535a151e6693030f5faecccc008e18042b78f14b4b082db325c21f58a5c85e18248ea611059641e32f41414727278122948112a198d05d14091e53d46074dc24c7ff3b3e3ef8246c8067eb55505655ce1ed4c27ba980a17d52fb6f41352802a9bb3c69248706bbc8f96cc7b5fa03c1686a9b24f82ca13a9491cb727baa70f123f5fe141e90cedd2b2e3c112cf6c86699d24c829ab3ccbe3a4d93b891e8bb57e3dab70076e508882aba4b2e0a0235922f89926e5a59bd352b65343e8c3e2682fab57b36cc73b18141ad8d69ac1149820456831eabc3dd96f787b163b4c5c1e7c5c4e4219c14760a74eddce611a3a7be33fd005236b1242c368becff57eaee707e31740bd999bc1afeabf6508294894d57b3eb7aac9f5222ab1b1b314731cbc2b38a914c266d9ac507850da4109eae721ae79ee1b2c222c20306ab08e04d7af322ee1382f3520da815a522ba98b7a3b82eb643363bfbbb2d4492da5980923f37153be92be0074007448856a67883d0044b52c135dd7ec6804f2777ab7eb848773adafab7f2eb8b143ea30cc60c772ed3d6c97f38ac89cecf23fc41b975080550e7b8bcfb0937643968804a77d67a1757e6d004bf122554b31cfae2e3e7afcd5226e5487be980deeb679523c47f5b715ff96c69ec96f56c5cf357b06ba36c2afc30df38c1f57417ec03ed40913d6c490e4cc3f70e21b20913007c888aa63a7b39bd732ca1be9c8482b151b7c2ccd78c1db6c7483d6c10f7bff1dfe091db70b4b1903695e0f56dcbadf44e072d3dd6262732ab6d0f75264f3d6c7e05533f3988b9599cbe3c4638ff120ee6d0859ad23d1295689043680d7892691fd6444ccea62717e8e11849571c493b2dab75e7b62c3cab656430ec5b6bdbc1363b7a5cefa14ce5a4abc73a2ba451b333158e906ce1129bd9bcdd1e5b524a8d35d43c6f44c633564bd7a2ec710cbe008e89f9baaf3cdd27b83306d68ff1f85598f4139212a1daf97a94d0c12b4d0a4fdfffdcb86cb3a4dfdc78911fdf2d661057422739c62781c2d0985d66a70e40df538c2566ad29af2c75c90aab6aa4f1b2244e2bd1ac5f9f17f857f34fa692e8a921e8bd5762c74db758e23555ffcd8c17acb49e3f6fa41d3ed2bc42b8b2711fc59d0f4b787416f30f18b05db9edd3faad4db22f21e400818c6ac4ff389c9acbf67242b0da4652ac9968d101d96e2a158fcfb819c7d6a0a44607448150a16d1b5a7c3d4f53b09f2174d7f1978512d3f7b1bbb8d3d73c28d627108b76cc98403cf6e52374c0b976c54001eb770d821c59586c4c3829a9bdc765c410316401df09282195f8e2cfa066a3bd912201e8423121ce11b38fcd9853d39991269b63d2d0920cbc629603b68639c8175808f3244162b5fbce135e046104264f16a336f290bf9b339e6ea99f0e9176d0c3eeb11635cab78bb81d04db82ccf3ceef2fc97ddefb3b8ea30bcff557f003c8a464687bac1ed9e4d251cdea62dfd71a2151bca4a6bad751afd2e36fb60d4f8f69409acb329b5e58a8418242130bcc76cd4ef2eca071dd23d1c053a50a6594057e1567f4ada5be2ada1f1e206c39f7b7f80402e3da83c594d87ccce9f0fb74a39f900aaf5d8c2db75db186d4a3f6a4394f80193d7139710b50a59bf463d213963afcdb5058605e454f6891ba1d6c6a3e1d1046b47b83537a0c44f3405e81cada1bd7dace161a3ed676abcfc717ce3ea444b70c194899ccc8d3854d2d2f9445f1b2d4b0aa98da0b0c6236c8a88d1e7b0d38222057aa45e223f3927064629209da4cea3e7c5c1478817588b20202c5d1bed513a0af75aab15e24d40c8ade1cc8c066517a460d5c65d137eedfca6f9a86449a200ec12cd4ca441a724e24eed47e21c414093faae6a9c8b6ad50eb7f297d646637a810cf7f92c0d3f2cf7e8a0df0b1aa7bf03aed13bae12fe6a58bf1947ece4c780918b3b7a926125b1610b56ffa57dca835baf1b3f54c82f33aebdb9f1f0c9840c83e52b87304bda75842a3f11007bef835e8c4fa3358910e54d7c1208fc2c750bdad7e36d0bb3d2a8d60a5defedfee6fa28ccf95d430085705e03f890b5b2aa871387b1e1ec19123a6fdb1e0d3c319625fb7829b804d957a0fbb5f7f926de6ac6ba994a8e857e5e8cadc3885a4a0f57d32e040511fc3dbd86a160765cc251aa9188b267a5225cf23b20ab391cf41a126d70b0867016a3122e3f971308aa40eb913be0750d3522f03e6f0642f503202a4706f973392abeb9f161b5da47a2d82eb47ba57f4110ff5fe78366feeec71fc9fb21195d8ff39228cf55f4c0c2fafc778e1ed5e6151ca79d71ef52ae0a87f77015615658d9f4915d35a5f28d2778bf43ced6c6415981bdaae586ad182137c92b1b65ddfc9cc0ca2fcce039fa81e6e5c9adaa7dee49f4996d696204042871355126cf3d6c6cbda28c33d91fc311d9b870b6ebacca7a96d6d9467e0d4b366a03c535c1b76c11418917969af3d5040807b4588f90868a4b018feeaf743cccf0e72001013396fd42102340639c8353a9381b9a2797224dc589ab87e6d184386814651341e8aa616742e8972eb20194156d2c6a0001100648761083c26fd712a3c8e0a9167a70c6b8da4bb00f1b7ec03d352d2a3ef1399cd5cf3e9d24f970f773aab39edd1e9f6f572203d272b2a361977c81856a0291e106a298399c6b6c342f1d661690a3fd7a4bb6d46c180a99ef183c254330c2e1b0398110e7f797ff868dc323c632afd0924f195c86965285315e907e92c7603535db1ef2a7ebbe6b31266df92478a7d9547246af57067757511f6340dc8e2819414add8970566ef7b231e9a860fece979579836775f3f27af5b0a106eed61c579fe37cf4e40fe09e5d1b3b9dc9dd8a344b3baf93b51b6622b4b94309f6710c0fa7a756a2aa4d90102e4cb31b21b89e5a24a8bc4a5b73bec3a56bdfb82101f80263a25b70f83068dd8d1e64cc98e63b3aeeccb78d6c9e3878b62a9c41abda9f32e3164453e971e6acdf7f29f55818e7dc5cf8d5c1a7bdb1896fba395692a30dee4dfdc509a42c658e854c1bdba8dddd1aec59e43b8a2c0a6d0fb8865c98d269cd04b978eefcf844f76d99251a999e2df85daa8352343c13bb49a0d681ea4c513829e8131c939ffa81a34aec3aae07773adadb9660b79ee1f063b8783e5f4a833a405deb1f5a7991f87d0bd131d8f8168600ddbd0058c3eb0017b83c1b05588996f8ca7ce0562878a9a8266d889a03b5cf0f2fb63be78735f9544ab4cec7ac59c16dda5e488b185b62bd528e9e19ffd4a52e5530df56d0f0a407be88f4de93a16986c0ed4e3e6433a8158e71c20d23c0bbfc107c79526e4557e05e681754b0571f8e8a5f7b03bdcad40d29fffbd6ea56a614b41cea3942dd73d6ba7c347ea9471caa6ebf7d7ab348fcb822b808929cf9dad5741f9556a6c73af196d4bf904222325a45a9e906976e93fe647b8d8adf09e5861e510a821b54adc469d9d3eac678229742a0312da848336eb6db59955c445d3f376f6874057af80d7dec8bcd18ccf140fd6b0831148c775656fd9fdea9c539f62adbfb21f3bd978e3926f4e64fdfc806779260b650e6d6cd0a0d534449e428a18620c98e070876f9a3daa5e5c5c8363ef1b6f4bacd049ae85b09707c1a5eccb2b26e5deb424523ca53d307b01855b4b62f416e2513eb84ef45f6a4f2f4d6d03edbbba4b05b8501561907c58cf8b106d6e2ef3b4bc1605a2ab601c36be51116a617b2c2eeed06e44027bc7b1f4cb023e3aa5de92803782256b75c5a0c7e9e30ae6c0812ece56e00b160d1a47562fbfd22db64204ea2657f713ff995cfec26972b056dd41671e17bd693c81feafbad81f73022d5f5e955f13a1de1a6b2f4fc4b975d0b1b21b4a213e4c67850abd75569df51a3c516d11577f2ba23d320b1b2e852469f02d762ef1684d514a8de7c7c7c4068f2475c52d2058964a6fe272491712c254e183cc293667605b9afe4c7151d6b79d3c4eb2f493e2fcb1afe4753e212fea6daf32293597b00035c4a0d08268ed71fb9deb84b5e5dceb2680026cedff5902994a259c44373cf7b15ec29e0893994772278f74280f5068023a0ee7b7b03393f0ef95f6acf13b6f4f3d3543124c867e5ea070a4fd85fdb7ff604b8ed13bc6a747abb9843f55de90478b66a21861d86efae0a8e53d0fb17fa9d055afe7a9e2c8c3f007c874fb6814077fc863f66f4141a48600ff7236b7602666a599ada2d22e6e0a968c803153acc98959906c20e6aab31be32554f4719a89b1a2680d12e9dbec0255699f3ea87214c999ce0c86fb1a23d7dd6d40c225bd449265bf500f6d6a01d7b50883eb7e4d32e0e657b50e39e36e1118274435dbbd6b30ce624509cd7894cb910836478f7fb2a15611ca792178d57f78984459496e97894409337b6fba4fa1d807f21a1d930801ec10608091e3fd4768f5f4b513906c11c750b15e72d4e6c55ecc9f87ce9f4100a29ed52fc29d936356e72a82f24966ea33213db9ed55439f90e7fc720c516a68e6a740d0c08cbc83f5aca4259cba5b2adce4b1fb689ebfab5ac149ca9957a6c801dd93b64fc75b4fa8f9ac3930145710c63f3e78ca0fbffd058d447b941815fc38ae182c1ae539b253bdf1d3b43829bce332157d12743c76e2a30826cb9de44a8415097f876b6822c22d094d35618045444bc515aa0ca28f3f14c24f0fdc1e2d3e0f6d1410f37c88e1057c35d679908e8f50e40c91e350472d468d34590ebd05a11b88587b8da6c8a2be46370a9bef2b2d42cb30e87904b5f6e345d335336bdac3f37781f8abfe29f85b9a4cb8d450a78cdda1be4a18b3f31908f2ce85805a0365dc7677793ab8d68b8c7af6438afbd211ff9109f0d322ed630f462f2f954b63d2408c0b2847113a23af6d434d0e746ff24299f9eeb1647e1fa4da63849f01379673dcec6875d470d284a24f9db9ff890a47b1fe0b55755fa998a6b32b88102dbc84f4c5796e7fd2334b6f15335baced92ff0a4cf253a6676c71983cf893eaabd3e93cfefadf56fa4da19a0bedcc46fc5b479fa7180307071475cc03d73cb686f2de4c9fcfb87d14d1cc2bf4d99fcca7b2a3127d1d0738579b8651cc32e3778afd3c698c10c4c1bf2d64a1e41fd4445b1cd10ad427cfd91d0630578aeab84ab548d00ad4a03d85d04edc9f4e8def432ad47ec3ef7ac9d20a44623a4aa3b87cc3a07b14fb0c07d313d02d1bfc704e01999139813ef1586549aabc0c1f984d3cf7df11b3e19ce06f67e36d4093110303fbf0f4668d8dce7a512ad94bc7f6008c98c3b6c036a0c7cb68d983a3c39a7e0d0d63c8a0ee75c70da24dd1d3484904804ad6e1755c92148c51b8d9e90cb9289cfad2180275a015408d83bc7ce12207bc48a61d1b9519839e081390850d51319855e220c5df296c52f1fdb01e25db2d048866b58de962590ce337cf77f986eac751857f1392a64f79707a3ad42bf6f1186414c7514cc5a5e2394c4ce8ecafa598f4f0740b7e06c982da5f7f6c866e4f12b07db104ff3f264504643826048cf83355b0323765b9330293837056746fa9cfbfc423e7eb7e96d0b784e53679e557bd2e24199f122a1c849de09a72aac58c58a18d1f44e116fa914c129b325f1c2e01cd00a4462d68f2d5765cd794064c37e278b90f351057fc2beec202c946bab29efd3693ca8fc6904c81a8ef85de40e3c3811ac6442ded8051ec73b2c8660982b8b271145b8d68477764362962880474e9765358c39295a8fbbfaa0c74cc2a2081888d02b6f094f9d72a9f722fb1ed0731a00ae9d41937ce9a420b850066b515d6a96e209c03433857fce0c51bed1a1cc8e8aadb4a60c999b8a894e7e351f5d470c74a5ce15c1617dacf4cd5ad97d738365df496fcabcf37998b52604ac87304a0ad52b5ed19b720b66216f59c48e8ecf3dbe906b1b271826f2c5872c55778ba6cd9a6ea1a42fab8ab515d39f6c23ad107179594ed09078d4ba3c8c7e104adc9881fce03190c68cad30b398d88c24f4f801f46252b6f12343084f844ada05d13e1b33c220904e913a2dc63d3dee26f15c4cf27533a8fa7f8e30d3da3950f93229f4da06748b773c07c24bd0d59a0ea502741418a980389a52ae296a3380219457e9493b46400291732e3fe7ca259a25e8c58aa173ef66abf170eeb4bf1f2e591b4952182315a9860a117c97c3ea7fce33a1a9334d1b7d7862fefdff14f68c0f1606f2c51524bfec7d4297fcb2d3100d1a7aedae7e05333a16448d3a7f0b4829c60b330e672dcb38c356804b8790c478ef74f61fb96e3c90cd01c19f0ae0a6094b7c3089c6e1cc396573d8c0783d561b072a6a3bac71d8a391e69be35442737055691335886d5bf2b264b7771892920140ab9c1ba4d7fd81c6f561e8e1344662c1769566d87101806eb9e1b9e5eb21a7b92c2e5ddc85d98b758e8935a37dc86dfb6ec9f460f80feb6ec7c2c5b929fd6faa2d7d692377f3b8e4bb33c898942c2c23e3a332f2796e88e22f720af86f8650e7710ecaccdc5879bad1d5e46c41ff96ede80bc0b69e64d3dd167915d149ae8e7c3bf8cb5fb0911f2794ce0bc68237a881231990eb324cbddb9b575f86cda0678d39c0aa054a20b2521396f363198fd1acffcff81585cc255115c2fbff2e3bc218afc2b4da254ae2d337c4e1faefb532ee53d8065161495441ac587cfd391717979bfc274924a39e4dcd2554ba1533a888da1cb52a47ea8e082b4a18b86e48cfe3acc54d2f4c64c82d22429eba88b1e9e663304b95df3e22509666551bf971dd3cb5fc054a82b2e0212600e549b476e726010037c6615cab92849bd247046008e9dca88207ddc5cc36310cc88bb284d0836a66cd231340f36f3ff6fce1e5bbd74991ae4364442d04635e6a9d8fcfa52a4425ae7c82df238a8be7e63217ffd6ac3104c988e2894caf1a12450da86b29599c4fb17097e4f9a8e7ee167d164d1990ad117a08469aedaabac3c919c301b5babc1fef4e135382f3849deb3aba67a045319c5b2d61d8427f138d741b8179bd8f6d36e73e4baabfef963e208ff419e65143c7a028394e227c53502eec93591522dae8726ecc0d5baa77e73f5ac39707c27fb1004ae16bf10fc565d31d4eb381c6cbeaea0e4f3b99ab9f6e30cb455a017996c0fa7ae1eace7dd644f8f6ed2e9d9171461403977559dea507539d8157e52a698a82dc88ff53678c3d0af010a441bb0107dfbdf19ba403412bb3a41bfb7468b6258a77f2d1e18434b97e09385bb74e90b03abf7a9a029d780ba2cf0efa68700a99343152dfa6adad5123f70c3d9eb4c85e3ecff798e54045b3dfe0fffe428bf31c611ab08257d3a75b296d2da6d7b94c2af28ebe1fa78b4cfa6a24762cee53b29b85cf10f3c42b401846fa1d800e6e549afc70e9884237f520da7a68a0bbf765872b57f26bf29425e10c0770dd192a94944d50d6a0654bd62e3513bcfa1648e339abcda083f8a522e3ddb0b4113c5b8e955f267a5065b64eed0c24c983cedf3a36e101afa0ddec9e7a2c607abab235b13f794d2bbe88e585dcd959693a93cb0118f8392a649b26f16ef1f93232a1fd95c522c740393310ec318d5a37dc382fc3ad1fe3fdb64cf44a71053f0ee0cb60b57efb47a683eec6ef4abff5fd41d8b055abc79364e2b960376d26ac7300ee4ea583dbfad6f2784362e8a9489aef24c4ab262b63df667dda2ae6d9c3eed0ff5a445d4f031369dabc861db4a74b0c5124f082e864e3531d483c780c009c2a66de1d0be999ad2deaa746500c2ba2e4c797ed4f57376ab2d2ba017af1ccdc1ff343105e4450dc9172bcaf03966a065669982f443f3cc572154758637eb7b02f3c396249699b59a461a74df339b3f967b1269d1f5551e78c8a40bcea004cd0fd216664868867f29524eb75efd9bb71e0f08f9f21314803697c583e9384ff5da01b7692606cb6c51356ab399e3a41130e782422102cfc5a095c3a918b06b1e2869730590f408d5d478367f5721e368d54e75d727594a778ac4a3e188a44305f0c611cf1c7ba90b0f284f52a245ac0e5667b3bfe9bfa32d077124189fca04c8fb7fecdb48c99d96d497c1fff7945c48c2a13a9c4ff0885a25d5fb843e7f80e21b7234335141d008ea78c3df60959c8467398beb19c997128ed67b122def88facf5c52f86f7aa1c65ef017ee387344a35c6e2bafccd256f35d5ca195cc43e88deec202bce1fcde419fd59d3abf12067be1d67d06c07adc87eff05ef3ebed3fa5b9621f0f3cba6b19d26009952968f7596f313dcdcc0ca6a423578720a1d430582ac2908a7bd295c8992353bb14a092dbddc4a2b9c6264a4dfcbf26f0e8fa4bd8d64e97081c7d1477365f97f79f950df70e9aae1224462120cb029c635b8cfb39b3653f13f795e3f2094937cfeffca4ccc8c09b8334ffaa1ae8750024ad9fb89fdded472fbc6ab425f20e96dd2aea6acb4f2d15830889a0531cf9dde2bcd713eacc09648bb7609be257521d59f2532de6198ee98cdecefde9a1751916c76494ad36020b95c6b8e0a83680eb4f44297ce15fdb21c6be70a170f6b3813f63e1eb0ddf1b239e80e95c1fc49f25672cfc0d90f849fdde9c3bf090e6da84e30e999500ee7337e691d71eff4df09e30c3bb222a55c1850df266e1f74c2af02c69bfc06c2f46d8656ab10151600a57737588e18e3ddaf228643cbcbe77487e3b9b5f14d4c8464e7e980d406efb926d465f5cd51d1c1dbbf12fc3d8cd4e43cc8eb91bb477679b47d7288e8f0c3ecbc2e52df50d882682b8911f6c5a05b27bae01988015d59ce3c3e032ba4ef2d7c2d9514318021404a9cb6475c559344fb1450b148bd8de464b2ac2e2b89cbb4b7ef079424a5b4705817ec876363262181dda41d8001a071411987a54eee566b1608ba445bad5ea61eb6fcfbb219dbc2c5ddb90a2c502cc9370288ac84e3e6d54ed60c690f1198778c213f9466fe8dfd7037a13744da90bca11633cb1e4c23776b90383b5ec52abac49558aa2e7c74d1bdbfc58e39531fb0c312705d4ead7e6f1ecf709a7173a8ab4e97cfe5ebf2644cd4738a9dba58f948316fc4dfa301186144c8a801ca7f889ef6091c811d108ddb5721b4684acad063fbf0a28c1a1b467ead6d45d7077af719219078843f3885a8e711b4be17123ace7a5f845b8fcb4da74ae5c847ca4dd3e49b65eb28877f1c89731902bef392605724ae61300a5f573f53a74f6bd9ee47ba098e2656df384a4a1a1d9b04df1d000a89ab229448d7bc6e62f9a399e30c89476593f5116b67484c6396dbfa9c863342f2af43cb0ed016852c07a1a6e94290fd82a901d2f25d0940a4df13a90a82d156a0c6db95e7ac70e622b829da16f6aa1e3d6d50d491d735a308de22af118d610934a3aa71189c5c3579f7d256755cc6379cabc02e5941225aa76a406fd3bfff7040381c01ba0fdd52395cc6567860ae2c47b31d62e9637471072611c886201253897644fbafbeee42b7512381d3bcb76d63bb465cf8b52c0bb3138ec68f3da34ef533edee7fd38877fe1dab45308da756d0a0d4dcf636d6cd5c09c61114a4cc28548121e9775b72e9fbc6ea6baf2ebf2e11a650ace454382e9ee75185a79e4e1e8cccb83f9987b3ffd946391d1724dee56532b60fc5e9c304dcffc8fd6d7c2638e87b8e0c89f227f28962fa9aba2ec7daeb5fc9a309d00ab4e8e0c4ab70bef20077f5d47794cef872f463a70f1c2cd99f50c6a176febcc741c9c1751e9892c73bf40be3b5471fb7d01fcb22397aae5dc2eb5f86bfb5d1ae5c85a384c362d1f9e58ad5840321e911bee8c449bd489bd07ff9c7442defde6ab9827fe8b20c4b105750d5be9fef585193b761faffb2a82ce4b535f8fe30c77b2ebf178dde7f0e484a4be4231e00a31dd514806505c1c46b3e6882fc31305073cf71b490a1e6eca614fee30c70485a97d470e1711e7759fedae6a25efc99d7a2c7dffe2fc7ffcd17be07d5ed178b2611152c82c69181fc751cf77d3b7fe8a93c7a9c04df6d7f1dd3d3e7fb2da6077bc637cf9b7c880e21e1cca517f0817da785997eb97c99bc08adccfa5610b78653c20a12888404470bfc896d4d03d2d9cf6396434126dd326a97733998f11e82ba88c456a1a897bf512aad9fca3500dcb2528128101a395e63b5bc6ffd24c069b8f5efc7de3710f5f458f48e279e9daf8011b60936c982df8733c5c275fec22222a7101f5c22c985ad1b890ba73a82fbaa697952833745d871d710b49bc5730a4189c90c14dbdcc1a0a49c3535a6cba9d394480ab004d2eed7acac25ec33c0eb3cecd59e98d8db11a1d84c15b03779ec0bcaabfbe04939211e48fc40244dd0499c3e075753e6159e0df8b6cdcdf595fde2531b193e435fe0fea9aec993d7f0b6d492c656904fc509f847516510e3109bef7f93e5c441845250ab4cb3b3d2cafb7c0d1ddc6990f33f94abd402c1755ea57a2996e1b774e5ab144b454742140c7243f8fbcaac21a2308baaf947f5971ded9372e0203b26b53c379d6fcc30e86de854320554b32e889bac7396b40561e7276de5c79d3c871f33878f984af500d76cc73229b39f2f6604cd5035d96dc396174d9d854461042fd80edd7ba511eb84f5bce64f05efebe22d3d274f4947b5a12a2002174ca5ccdcfbfff1064f612a49116e17d06b2332b310899e8e04c2ebc55e15523d411f7bc3249eab2d3d0ebb311dd86f9fcc5fe81ea109f8bdd57aa0030de1cfb89eb916cf9bd81e786e29bf627709d335a20bdfcb332a7bac7b5bed134b90e9e094ab1c2c654c7898a5ead3c352b313095b55ec91cde6aec6ba0d4643a65266cb22668dd9b8cd8bb333c8765ecad3a86bca291deeca48a15d7b24ebfa96954f699d41d1b624b03f55322c605b0d3f8f3066b79e53d2c43315e5cd14b0d8411a5378a7afc745c5974799a74e13c5500e2831a0e7353d91b0fb64a2e4f7cc3ca6d29f06f489827243b0299d19665a3fbc1e1a4e3922a7b83177e5cf012601c6c4bc460676ef8dbcc47ce367e4dcb2efd596f0ba98d688e198fdcf488c94f6fef27d368a419684b3aa1024f7c7c0d884aeae60f66996b998fa8b29dd71e46d7a2c6944426dcba89e9447d30afe444f69924c9cbf9986577d0b9929710928da1cff9a8b85cefdb79c1b21bb12c513119bf9694884c8eaefba81dd4893c93e0d97bb8b70ab0843e0d36a24d17747a7e7dc26db9bb61316f94113af354b21e46b608e7d6a57b3aaadb3402f68b8151cc740aa0eb10ee8da1e3003bfb004cf0132ed0289fe784d7c8af65ebce7d86ce1e12d438219b85389e7c67d727c9be6683a12871975e3785ed7ea43f3275a3b78b08c8ff8bd2cf4c2d3dbec2f39544dc726493ab3ef2536f313e6a77bb6d623d8e8e1b925035b53a66b429ef77111b22d3f9db082c9e24d5953a28d818bd47ff27b602ff680bf42f98461e12166a841f4e1227f83a97d0ba85f057257243dfec59ff6e8a16238188228f8d416be52ed655cb0f5b8d7ac508023a981cf238d385a66eb102647e6e2185bae0c4568c1efb09af0cea464cc2673eb0dd5263e473529a6b66e643a68bcbd5a15a0d817ca9a10997debfbc8bcd4bd0ccece7fed8b3d5ef0492dfe676c7bfbea2b52e792204566c96c39154b4a62f2c031cd0761a21ac84a8a9c694d53251b544315675a40e9b407f4807b8dcb229be9a54fdb4eb0a8e58ef6e83f64e60c5397ff2e62448ca933009c111b85c53b247456a8e0cfeb56f9f0f31db62cfddcb24cf35a54951a7724a5dea6139df116a6102239d2d6b2734e0b9762b3e0d11cbdb7ecdf3ddc8262504ac39e0fc33de223abea3e3bffff553220fd7c449afe7c0d88a5218d28b0ae7c597da4638503d427903917faaf9df0df0b526bbef8ca7427706644f8fa3e83610206378234a5605d68ab44f7549f7bc04bd3786f33948b60d25225c619da3fad7b76b38cde5bb24ffbc6f36eee4d57117037851707b10fd5b2345c786340c2156fb785fd1a24277bfb9a82f52be0c448104281cdf1dd4fc44bbc0ea535b918cebccba708f78637e2fc23e67849ca2da7055977fa38c8fbf2420b3d68df73d47f10afa87dcbe4315d8fca16bc62ec365f7ef8584d43be3cbadba65bbd11889596c90d8765e5f632487329acfd2192b74c4bbce73869f68da7e364b1d87cdeccc48547ee376fd39bfed6791e9df7fb7618b71342c74d4cf9706f9a4cc4c7daf4c59e5159eabde73f4db0c164ad5b5986d614bf8263ac728375e2a722c1db8ea82cb74aa312b5d85b66ea1fdf6bf2a6c8e781303f51d0d89995364e86d46b70f72baff39b3e822933376bfd511ff4fbd877ded65a629cb433e8cb1b313333bc0336ea17069d8c460c9dd54fbe16ecb198563e4aca3f98ed055befe295a58bfe05018819cb973a327ddc44c0536c1a8aa511ca8694549dcce404661bdbd14f915df2c6ebe782e73ef988a31b96a025608d021882b68244c841ef86217ed97146e3d59e84dbc61290cb312d4c20271f55f2a8931a4b1491113401ab6cbbb94e8a0151282eaf34eb423b31bff23745a752e1aed2fc57568ce222b8efec217ab499146b3741eb9433107137326b8c5d3411ce467fcb4fbad9971f5cab0334025f33a6d5eac4baf8caa953fd20418f3a08296ed1e9c69ad84f9f726c98465cfc0c440a3ea6b57fc0f8daf160dc557219709cab043432a9dc01de9f8bf4f551a1ab22d802d66793c887da0c079b3a2daf731cbaeb58cb648c6e350b3118bcb80b5be4fd7f2dee182271e7935c0466aad2d31a12f339c0d52029b5108d0702b9ee9aad2c1d0cda345408f90cdd2d60ae9bccceff4f921a8a323d3d5ac6af26e61db8cf7e8a0d8f00c907cedd730ed13a3182ed2871c1c9a2e49d40f5454eb101e9cd94b7a805120bd0e948d34bacac14f96d8e3dc6a262f3278cf3fdcb0e460618db29ce2df78e39e83d9c7773b3ee3f8e24f4e985e78ba3e307972d9fc277b0687365208ea4c1d38285bf1b1ebd6c386055c20145d8e6060d297c67962d049aca38828fcfe5bc8786c74cc5c2c21c0424839984708b7170cb78577aed3690e62041bd03748448f749d169cac92db3e24de44a5bf5f108c379599d42cd898235c907357437c2367167ca841ca4652ef32556f590475586821270b2cbded67fc0786905064d14c580608bce0255d33f21d2949fcf1de07eeb63ac3ecf8b9e8d632fe5baa3997d7c2233a9c37bce1c3052990fe6ec4f1a84fb037fb17a2f0ff6e90efdb52eaf07a0aedb8f3facda4d59fc12611e01a90f71f3657abdc33b0f7d3d02e772f32cc8037ee3935d6b32935830d69c034a1877bb73da3844a5e4a9f5fc6cc12692ba6eecae7c89bccdb462dc7c81edb0a65a97ab965777f8dd6bbed9d734cb36d2493d64012589bd6f529b157c893c866343f17bc87021110dac1ccdc560f257483b5a9f42ce5645f4b773fa84462d7227c85334834764c2e65d0c410a134b5c82e0aa4b35ebcd80987d907ed47ddc5fc9a3552e80bd0ab51a1201e2468e1455f3e7b1ca9b63c6f2dc1641065dfbfbda817b65e6553d74a42479739426c495dd81ad0ece43abd657f564f1ad5360222c8da00d1da0abd66c88176dd4e9ccfa113630606d7817883765665dd308e3ffb5a44a99f0561c2842e3b4e0cefb64b6b01a23f18e5f6b55c04d5b2202091eb47bf66bf19093e0392c6b0c16868cb8e698554ea7683c516a9e6ca7848de0018b77e31d86dd3f06256d0dac0312ab5bdcda836ab8e7cf663a01a2a1a2fd0cb7d5c5f1a8e462519e58642d1f4ceeb9afb76f59f7635fb051e116f555b1378a4432a0633ecf57368b993ffd94e14407e35f9711aa62fef293e824035ddfc3cd5354e21fc9576326ea79121ddd05e12a268064e07d0e023828f16fdb72920c5749765a32afe788067d0a16f8d9c09ae624b065d6cef8e79bcc44c02675fe54964a8236fce226ff020aa951bd64826571ea289017e424a53997be347341abfd865baab5c76fd44475f885329fc7457d2cd24c4bef6163c0b0d294905ca9f3569945a2bf191f72519a05948d7a60937c73628493b0afcb56e30bc0cb43a7eaf02f564dcc00b87a6f18d3372cf3d795b5082e9151e0854d067ecb439a02b17b352ec0374c31177da92308e8a294a00f2b70876d962f6fa30472e909aa6ca540627c651e453e8a3dc8be31fc86b78a76d527596768d9dc328ab584ba9c3a838b71683ccf914184f02c9b86595b556edc67e27d003ece8525105a4497b75b2afa1729b6b3cc5fbe8f03bc7adf08bea68d4f1d1f0691b3dccbc1dc52b44e9247326ac61ad3034e66d3cf1f648d0e1cb3cd89df5fa1dd0d1703be6c78093da98bad0c6f1a5632415cadb8ee7e146804d37f48136a81efe221432bde0a79a0b524c56db635c5dfe6220524a5e2028cd055397a26f85545c7eca12b78ca10faa0b2d88717032d7e816bfea16f0f16b589770f19b8fc850cd2e4e84f8717dfe2433fd774ff40c53f428eb7b89e4d52010334c1f9d27944070c42c64b59251c2eb6117f574bab20ae84404836b346d8391b25bf4236dad98b1b63a7e68ec0aef40502d4f8fc7261d7f63444b7622a10816899e8b8c770db00027133d1cf56716172b3f639b59a701b37af34dc76a7de4776ca77cf504ced0257c27a57e77f274532f4b217177aa9cf28b6bb5523adaea03985391a34f67f06edb10f13e83e7a38279b893db6647c8845a4c0259883577a83e4d3e1c945f044f3d1cf1e687369c3ad036ddddca3b6eeb42dab8ee7b9b81ac8f13750324d9583a1b8b5322b47cd4a040cc5d9f823fc517cc2e3e405aea619c281d02727987a7b134a15de2ff0b560c1d0791daf024011b7340df5da612c44e5eed676debec01f2080390883290a1ca257e103f15a8288c2db9e230f0b004600f6fcbc0b1755ceb073cafe3745497d4f8867d2d109856a73e8d16ed037e07fdeee6e3ab7c67e6a59713e8197ba7a7e8282b82e7a0f9d5ddb34e82c20a3bca7d42a3386c233825b5ecae0800d821066169f955c3effc34f0badefaca5cb16723644d00e4c3e3c564ee266c9c53a66a9d9c05dc2106b15f0e4876d42e342641401e4b8730f323aefc626b4af118df949e027c187f312f8240545fe6f7f14f15fb70388da8805f16d27abfae51cdc10071cbb678b26f6f4299ac1fc26a500c0e9c5827c737070880462100695918d3560ef30a3a41df5f9b433da349de06762e0507e14ddbbd0962c245dcc2b9a060bddd3f41aea6824e2a160b4ce4983e04ff88a1e524c56677d71305ebbea4387e46b28dcffbc20fc7db41a85ecb305fd5f204320f0182fe34bc02ff9100ffbac7e0a6a969b621b1486b9787d4e4ad0543bda6c5e43debc84408268df2b1828d14d0bcfcdab1bf52051ae32df0baef37ea77a0e311b0dc817146643c1b5eeffbe85ad49c757291895b08b65ddb39dae54bb1e0639fdc948440e1a792e7d12cc48bdc7f5bca4cc98f8ebf778de000e92bc5e805a709cd9cae0f9830be3597d545561d3ec418e3a8122188d250cccc578600b39b542ea3e1059c2b563486c897842a89b5196a1c35c480847eaea307fc1ea0ec38d904cb0224d2cb8f5d53afd32b917a4c7fea376030e6da56750540dcf6cb23d760149f7c16344a42f7607e097a3cd985e2b35fd77afe22e04fdc5b0f1c2e483e6b7a05b675c9f6035e29723934d60f050c43d381d328247a17829cf025431b8e7c803561508ebb35091b790b72fa5539927a21ad090e2df510662b897307a424cd380cd252f3f231326bcef276adb13f3d52e461ecfefa94ee89fb3473afbb62b0969f73ffb99d67b21444b644e207839278c6598a96dd5fbc1a185e1abb6395ff4927d14b707a2f1d3faf58f85ddf6ecf5329d8561f814c51de8f6c1f19e07f32bf9323ac5fe6f5bace5b476c4ae403085110bd2ad0585b891208a40b54c1fcb84d33d8cc3d9c47cdce5ae1595c4f80007cf4b1df3b644a123076d9adfe65f2d6a1b22393e3d01733a7185b04cc5f8b6204973269affa3494a963efb2dd603e7950d6056610685079e5d95db507a5c935cd306f02398d1564103613cfafc2a0ecdf7f793f0c21113d2686b6362a6e40f22773b24009ab6e91ab26964612cda4a36bd20cc1e9615cd0f73422a2148ab3116537ecb468e4371b3b65c2042d5b8778946d00c8ed0f8ec2bf70e329b66bf0fd1679d88e35d1c72361e3a30495efc0ee9712edfc27f0b6d6fe539bdecd1d2ed3f40e74792e74ef74ee3c8704ca4347af6e0f44d7a622aa6c1bba98f4d14643754156300cb84fff44f40b36835090dd16e719fc99e694b982988d8f24e9c3a8dcc088d95a7862d863c426fa48724bb0b90f8f78096b59735d7ffd708754e825fe405d3a71c2dc42eebd6099e514dccbdc75ac613c194ac9f0602280a83f98a7563bdb7ac498ea6d024d28719dc3ba132f4a5087a1b8d5d0dfb10d837a17150d1877b87eb3689fb6bd60d373111ddfc6a434983b6a5c0533ad4195359510df316f64046c7117e19b31685e6c60d4d875ce1f216280b00a546b19307ad0e381d007783e58b793a3b5b0e6406c992d0801d0df6c99ea6d6d771d9256ef949b507887a923bde54b2eef110861d33c1558870321d30100af179b872abb3c207d311b7759c32ad6d06daeef3280264afd14814569eb10fa9a18a25b78ef0e2aabfe2660403075a0c9a1c6fe3cf9fe82d8dad96ffd3f407cebc7ed4704c8092e5a8d95cc2e438cc606ac1e092d6ef72758c2d60017803caf0ad413bebd45eda701de08717c768da7b440b433d42d04dd0f6f973df16506a246b2032cf24e01e0826a0262da609b87914d4368bb16ab0c35c16c8cfe3a3cc738ef4e3a56a810c8c62cdc40bb121da1aae6c911907e75f42cd5e0a756212135650e0ead81f63ac5f8c521e522d86f0ce45eb8485f9281bb138b93430395f5e5e7db556b46dabfd0b15a9a47f75d65264f5123ab95422c8e5f8ccfbb55d481f8532eeacedc4f45d86da88a31ae93a0230cd3e45ea2e91624eaa5bc3b63d74f868be2fdedd83c1fdcd24f5c9407e7eecf780c5bc74701b792c788a146dd1107348c8f4cb69a39a93081361854d99546261afa9f2e7cdcea7e3e361063ec8f54e47bfb5cb382690b5036d20e8127437e17053ae7dd9ef5f302e3ac9dadcc197e37a346b3845d95e50e2f023fb184c9948a2bd441d02d840b3baa551082bc50f6c2f5591eb2f94ccf04e2e92aa970d2a53ec382f09c3841f15452f331062e3a5041d89eaca6464d24171c394f3f568962cb119a292da9d6d0ebc0836bc6a791260feedd878947863fc2f30bdb908a479091a50f70cb363740add9b19a26056101634e84226c971caab804dda3fc9475774245ba63453bfc01d3a1ea4771003fbac56a7d9ed25c1032ad0a72d71490e9c0f1eb888c04ca2446af89f6debaa211f1cba467c66f9b6a7f4f541587414fa11df2593eaec07e4ff0702e3716b723e172a41a4e58738019e2c0c22bb8b0daa92370a6c2533899dcda7685b4878e452d22c3a679a60e4e8be9e9c57eacd51d914a3dcd28aacc2b79b2e19173d3bfbc47347ad9f79b704e82b4bb1790546763ebcc0e79965aa6b207b86922d1b24279c28ba44742bee3892b74bad562fef0477547190f8af031c33a1a2a32dc4de3cbd3ba7fa0141ec0d55fe0ddc5f23d423488e13811d9b7b03356a9ee70d1e919252d542f4534ad0836817c53a76ed9dc4d45980654952305b73a36ee7964e7f94c7f33c22ae2e9d385505ea0b89fde8015c7a687840029af9eda6b109a4890a5a8ee18842210ba13602811fbaed726eea2d95cdd7834bc9df1db190db964d058b1f985ceb10b50f94fe1a7e65747bbd457b9dff8f80eb00539a7336d2b681619eedf33a09e5f4f96ed3fe12c6e93a7e259386865e6bb8d0778ede5c08c95939e6958adc40eafd8189cfffb2c65f99f4c9344bd7b29e470f32c874fd102b3dccd6bcca3e9c191ba38e3d2c91a0801a2032907f7c2c920085ba8a71b7b9939a0961dc9f796c0187cdbd07919eee538f47a6bddc982cd49c4ffd129e76933de8e158f44e1353c704c26e42891897f58cc8b2349a67859289c30e9852c974c4030d398932ff5a1cda80abaabba6ce93ef20d8d8392d4a0a3e6c7121c9cc24190ab68dcc762df4015eef827acccf11f554ceb6594ac7c2cb6e51ec7c03c3e163b942a750bfc7f4f1539c33073c142f0b1be9a69965399411dd3f6c4df9f5d9912eb3ed09e1191e3093dc1496e88ecd03654e65bc1ecb14ed14a60ebbc913cef32bff7f8f063f43a242e4b3402afca766719d56fd5325b3f1788d49b7fa3742d5e3d1b0c8c7a1ad9ef0100035f7d10a50ae442e4d2d9c6c8417fdd3cb3919b9a2bed815238e33b1161a69aec9ca393a875fd823e8ce9221a276e2b6c13b3427167c99f768b31db70f8f56648071e73e257eaed4756bcf9ef34286b3661a23f93f41eaff6e00b6070257eb736ef34aca3e06002ff8ebfe27ef0f3d65411087707822f40139e3223cc1cad7af9dd21afc0a6632e0e87e838fc3c112a695e1634eccf67dea9e761fcb4c24f0a7ffdf576b1b1c1d5c6734a50b3621a1768330275714d8ee24e91dc05f7e6871fbf2a20b1a7346fbcffb614e251bfd8fef687dfff77d06dea4d1e09ef2fcdbeebe6ffd56f4f9c84ea71e97255f819a5ce34d406435f24d75a1e1f2454410768a5a780d6cda24647a5f968b418c5739aea413e21c8739680824e1cb4f2e23feb5c30abe397e0e0da979fe79918f4bbecee5ebde3df7ec169ff1001f6c8a8410eb5c146d2caa3d36e11c7ae20a651ff4c49999c0c76e19991ef4ebb247fe84970837c9fef1752cf4da64df791adee734ccd1703b1967cda82c63dac5979609062423bac67a1707e7dee077330c40e557b5dfedcc785a3621cc2fd423cce8928a314575b0e3c4765335e778be730f55decab94424099b482cfc29def59c8a7dd88d6799a77390c2fa3af9a6d7d60461a26be36525d32ea93333af46bcd989f50878685f61081e87d42ef12d8c8572eac67ec459d29fdcb1c19ce4429163f726b4d86de1cea274d0084dad4b33d7b1c09541fc568b5444593207912ee3023feffecf97234473aba21b7232e6a497f003be931e5542ab8932dbb9450515ce2daee75b64cdc541981cc18baf1dfeedb6d3c76b00d84f9aaefab307b02334e24f24124432ba8ca8d32b6a51e62bcdaf707689bf170571f2e647c8e020d5a7b0584127a37f91bd0577dee6cd8ce19ca30d65a629937811977d423b849c5be4b3871194d13d1c0030bffd4091944b0a488022c9de8898e1aef743643b1f9913d2c2ab610e470fc6bb314b7a80763ed6ee8f8a585721fb1405f6d0c6472c96c81ed766bd466729178dbd1808002445f9ada8057f9a205ff6d03c2abc99b34f9afec32d14342f6343a02656981d58fb747a3b75b15ad7eb40ddf5d366e548869d231f900e85fd774feafe173e23977d2211692c3a9b67112808758cb1c5a6003548a0d1dfca9781e5a9086e9c53be47ae49a18d2bd0ca12306c2ace63d73dc5b9c2948992d06b900df6206930ba312049a632877b351eb4d9846f5de4c543aea061be8bc207885324a38547e7f3d59349d1ec99162d72b78372230daf2a88bd5458272240a729c737d4da7e4779e26221f1b77df5a7cec0b8b205dbff86f71732eab9423b990f2900a7f1d7420bcc7ad82fed3c1b131fac61c6aff72897c9d01cbc8e3e298a00e480e129cc344a236a38bc6e2616df6abae7c1523bc294e4853441792d57728aa18e234c195082b140f8b73d751f18e209a04b2d246df6e50e78c19e752146d54174a780414bfcd8ac2722d1b642df67df3126720a5e47f9f3749f790db51967dd570d05a50559f8f2308532720ed24d2da0840cdf964b575daf006cb5ed43f0f4d53c03f0962c7ee926d6dc18308d79b6956489fa64f7ecf84e123eeecd35f91fe55b3d8a62828db98ac44a27d72186deeee284d268d951fb1a5413bba5d9885737b54b19fb039a29b2e493bd6177cb8b8b6cffb5d130b4c9653f1ee3046de38b05db5966be41298d0179713bd666bdc720a4892d6656f8abf3d8b0f072d070649f09080f687f63c1ed34b7384c3e6b0bf6c18807109f1cf2fa67f3bef9d16c1ea1c6bff3498f6492b1267170f56c789b957ae4561f3d123af3600beafe857d945e7c8433463dc1e965b03ae85109ce253d331dfa4e3332bbf5d443c29d1936e7fd37c0bf41226c40c0d4c3f8d923f16cb9c57db5f1ebdcefcade110f8b40406d8e4f63d89b86ef0a199d8e21b224d3394322833c95ce1c12c20c08dfe018b4c1e5dd42c29f953a1f6734af19240f4632d01bd12c897ab7af67a58e7f75430a8a4fe6ecab10b4e8bf556e4c82f1bbfbf875fb5986293f516c9fecc4cec5bc439ebfd7e16eb935f111e5eb20012fd5b9f0f9033421c1b6836560065d44084758f3bebf9a3db94e3922fd9a1e4d3fe7f334225864bb23f7b9a8597bc0e627669a8e7a748c09b83ee7b26b630eebf43485b984f6fd7865ce6359f7623d119bf6e645d34e58e2fc7dfc1c1d4baf8c543a57ead500ce6d6a38657585975d34cdd9bfea4ea6b2b46243545f914f8f08d8c1e1e15d3b0a6884813e0947ce2a3a53c628b9fe87e509a0150f32cbe9d2e8b056c2429538326e4bfb499ddc79dbc203f1fff4492d53b02eeb9320ef3195dd7994e1f8c1f293c23a76a3fb13fce83dab58ec091e667b16cc1c557abfba1adbc852abdb1960476099d3b01adadcb883c1023492aae49404338730b75e4834e12c3a3573219da09d93a296ae4f6b7f5b127dcd245cf2549c714a24ba6f411373e1ed0be7516296514a96c6ee3c3ef0a4de11b38ba7c6cf9af282cc9c45b69803e45252a35c5c4cc950bdb4b1764a371a103a0454c7d43a1f60a2ae90b246f9023a2b642382bed9b0c17342974c85778f0fc67fe577bf8d1f944fb70d7fdbc589547c5ca4221772a83e7636c87e29b9445d1186e9b8ad585f1df53b2c92f788251689fe36e5564806ee450c36f1280720d6147c027f2fcc715ebc7ddacd1a016730b03f637d519b31a17d691d0470eae8bd36f944f21317e1bd2cf1c27a8b00bd7b9040b88341dcab28c085564b7a37b6f9e9faa9416db9d1e45f25056c49b8f81520f0382ba1ff8b564f2b0163221e13b54bbdb745c8081cbf9d5a6d94961862696e973f84a009a309e5b6c4dde339dca1df35a2d7d09273e32b59fab9863c03685cbd98454817f395cb899b8e4a0650990d3bd7585807853f12c26e50e1d04168efe08843b49fe8331c15fcb0b7df197be691182aab05bd0de73f9b45fe874654213e93079b68b5515a7db956066df092ecbdb50856aa976e3434e7e0c6f93b478202574555744ad7a67412f8205e6fb1fa7001d2b8a78fdfd01455303b797b450d895829a4d5a2ba62b02a1699a8853e8ceff64576084917b0b9bd90a708d8c22a469a245c03c9c4e8b4d608535eec1c0a8d28c9d379f8908c7a87c2e3792c55a697ceaa1530f66e05aacb55abd2c5e8c4ece658bcbc22a63e6378c3b129887dca929ae39155d9d2a9085ecd049f5514f8eaee2a50215fd10ad34070b4698b316dc88a65f651acaa023288aaf4644f456c886a896ad93a369334ca22e6370f1ed929d48417e0aad3612bc022e0ffb52a21d1fde6602e1f59da088ee4558519551150a59fb4baa4c05bc67222bea465a8cda8e81f897d27dd35dc091c96972e69f0e0744932bfa36816796d83262378ba47ceb4e25915c68eb21d1d56840d6fccc3c681035d0a16d31e1581e10ac338f0db408aa5b29fd4b0ad1b31536c5a0995a976bb165d6ac0f90be033ba95716b36a7482315a9cc79e97827b1abc28dd232c2156f70c4d1a3f1e7b18b5ed02ebffaf7073bb015020725a9e26fd291d43a3a18f2800f2e61b1f38e9b76f9f062a093674dc951eea14732d2d2ac93fe0ebbf79f5882a7aca7ba57e0290a1ff0a15c5a1cacaa78d6b2a56bae3b79a1becf7f837d8e4cf9ab559e08fd3c8ec1f34e48ff4376525defefbf46a85f2325c913adedc433e2e7a4078bc5e2fe9d90b6f62f781a914127744444b7ba0bd67e38b03f38eb4c50fd659e75f6c0983368ffece99a2392fc42e23e8f3b173d7922ef3c1eace06bb8748c0aa13adfc19b2953e196aab2b7834dfdd04f0520c03550bcf6df4ff5bdfb7ce30adbb27adfa0ccd0e1d6895745a21682d5fbe0ad39f5689ececa7d1bdfc8c1b1adb9286a6f0e9793671841244b76e723e94216e9a4ee6f8d2f42843eadcddf36912504bde40e683a38cbf970af2d4183f1c78f49d92b364324dc35b0c75c8cc00bcd02b8f818ec91e1f89df735f933e4ff81fd7c2cdbafdbdfefcb6d2467b0b5910aaa84a051780cbddf3d5fd52910d31be47256e5be39e40fbac5965af40d199210a9f1b7e81ce6c47a4f3b6427ded880db6007a2bedcf66b1ba6a7710dfcd079f5176a00fccc25b12c2a66c371a271710c3dc70cef254c0c341daccc062dc7800570be1c12b082bd63d4c1f2ad7cdb1f08b5a30e1b8973ff18040cbf1320f0d0e95b564fcc94dd68d97a455e464e69c1062b85679623266e3183c94e06eabfc4332d634e727a0038e59109ba347ddee28c068d74134992420d2caacc2c1bb8cf0d8f14dcf8c930d851b08ef59fd13e3ca3c2466cd912a0091d2bceae98f102f065a8566b5068b2b1f5da2edc7406d58ef240f780b45448d432d8c780d68f6d3e0b11a2c07fd6859804c8e28ad5342c666102510082c025e0238991920be52b21a3356aca954bd6eb9e39318701395325c5930271206a7b6871de2a5c37037e45e07ad5c55624f14416063c160012a0817da1134829299e3e370e3181a17c6054ffc56ccf902c36a8dae8d5c0fb6e9ce87d395963012805995e43275268ea440accf2617620e7f14573de637b27ecfd117cc1e1541bc859cbab0158238c5b54da9561aaba7d1875a62264b7650de83b32e588c3f0d0716acb3a1ce6e6cae2a080bbbd4b3e5b3609234c41de6681ccf7f04eef61c9030cb22f65473feb7fc878a5f7fd83475dd2f4b3ed77815ac595867cdcf9a9060b71672024ae3609cadd62d5cdc24483325c58e1ad90f42f012adeb74eacdc1e8021c7470fbf122618bad4c88876ecabe808d531a85de449a8271581be4de728f73f02db9adc53e64d674b90ad61994a4dc4eb16d5e3eb8c20e997247413a366156a7a4edafe662c3c3c0801fca05799c4f878301ab0e9c9fa6cd40b3e092b26788189c625e59e2f69273501502c1a886cb454180812a93f269addf13657a67e282b59e06c27a0618c129497386c99379edd48c6b8a8a8ffbe982ed66f201184e1fa0d8141168211ef4568c4c4720404c893fffe02f92879edc8ceca3bcc7fb6bd62cc4368fd2aeeee7f88f248694e8e7afc4a5c0f5bf8d61625ed227a1b1a0b9e9358b1dcf92f2138fd483127ea5046888b389201555cee98439f08df19745a69e9792057a21b1f4e75e447569916d1301505644f0a8510e970fcf3427cf22ce6f28471d88cd6bce768ca7f4d0d449316b9581221ba501aa5d9fb58fe4d84bb068c8f5f20af2e6fe3c0a3046199a5ec2ef5d6486d7355a76cb5f8790b3415c0931ca7b32ca065e36174280a6ce2ddc919951a7464ac6a4db61ff61de1f8e6c89eb23ded61bf7ddcd32a4fc7db45d0d9891684cb414f759961ad5a41ad3bb14186261fced6feda0479b05bd99a78d639a3066df1f5b08dbeb0c9c27da8f7744e3d98b9d4082b17b10a5f0593119e0cc63c3cb48e253636ee70b3a84faf0876ef0b387670e641b5fd2577fe838c920e2e9b9cb98ac698c832c9fd54f5fef3fff77c06c41cd73346751d4a7c70fe5a4e41a05cbf9bf8fa4edebe16a52c4c11b2416d30537691bfeac03b28c85341cff57c7fdb21bb1858d14aea4e4a0ebd69f1df88770e2dcfa40450ecf8fa3951eebbfec871d043295c0173ce751af4e0650e9842a69f58b0a1a0a1e795cace27e05a821cf4dcef531d31d0ad20f14c75352f6c4e1af3d57ad52fd4f57d52992e2ca6bedf1a7d7a8e97ce59fccfc40976264c844f881b0cfa9c107f351ef2968e53f8c84ec5db6e40adce48a63ea09bcdced80cdd57427f643e15e8f862bb02adf726031fa2653ddfa1a30f9af08abff25da4b45bb8a122dd3cde29cdbe1f6b0c24b32a94fe483420c91e8c130fffcef401fe8f9ef0451036638f6650a337da5bf11d1237f8216be2b09f3c233672c1f87a57ee27fbccd3e828b72172c5f2872dc487665538c96e1dd72ff394598f69e5896dd3b81946ccbd295032d9815f83a0d026cab3c541a1594bdd3e7818b743047817e7dc11d50b0d01751be0525324fe270d7e917acb042f3a35ccdcf27b9497a9a4218908c3ba264e88b02cd43e6b8938f3c13f03b02047125f5f327d0c6e2125eef895aa3d7e66013556a4c56cf3494f20a5a6160048de5ff8e33f605367c062c23f17ce7f489e6d517ece617e1883cac7730f69baba2760b71484a2723a5cf6aedbe68a33ddc3ef7791279d16b785e094d6ffbedb998f54fc060699a373c7dedaeccbaff5c696ee854b6556e04505bfa761079b2c0cad0ab2cafbac49774a7050348adce7584e07df446ee95aa46ef26a6138641b099895f97c90120d3320d098259e62c2ece5025322272071f92a95161fccba3b73d18f0485d3ac91bec90df3b30dbfa6410bf50aa7e74364bc4f71797cfbc681886eaa0621159c0707262a705c8f42cbbd165fe83d4aa3ef8686dc3a603e7c4d192587fa809a7c19cedf30cb51ee58875b508918bc411dc98ff163c9d7ab2f12403f440f876fc5d14ae2cb624a2e7f11c15fa9f49105af3694bc88fe1b3e49997df81e58fb997d339fbbad324e626a583282279cdb5daf409bbeccbc080f91cfb4c576722fed3bf574d918f7846d857245fc73457e6532a1cf991f0c9a41429c3cd33696396a092f4f5aa84ee710d89e9020cc064c670ec4186b427f2c1e6cb6555d4822578236e48d70ac0ac04ef7bdd330ace409a38843ea2e4ace31979df7eba9fc74c35c149c7c6a88b357b92c1b75d59d512013741b45969c54bfefb89f7cff1c35d858f0f1c2a2519b46e33937f56263f6b9ea349760dadf7993966e557cde75b29ee0cfba9d3d7bdc41aa9cf9541695808b3c7833d3aab16352fd8b749e9bd5c457edbac0f92d4cf71a8d96b47682a78edaa0d467ebb9f355e6ba5e65f9c7ae668e30b29dd089ded68f6b2df10fbbe472617596fdda6c8e00e664348a25d8ae7cc4af4c7fb7ff7368f3b1ec0cce33201ad74a300c39067802f3a7669db71d875bc3afe73d429f74eb2cde3d2029fc82e867610dcb487ad1df03d3a876e38c496bed99c4bfb9eb321ee7ca7c8590839b66fc3a4ec78c335bd79153afc23b0a0978f052beb25d714aa11caacf44d80d0b8b97741b5bb84406b7a69c8d57acb06f0f064f9aecc4de8a2c2b95f1d8b8b508e9da3ef637028582f5baa7411be43cd8b143887693c4d2b0c7ca28b3efbf27bcbdcf19be83a27921ad519a6e411e9970957d04374d9d60f8f65774d1a760a9c1eef61ae07a425e66ca735c7cd0ba663982e23600c8f8e287586cd41f1a886d5c21b03a283976332da9781a3ff87885cd539b0077701406a6d62e6515da9e2a048491f05c48a49f0d0436c6a10b36708bcfe0e2df91f851d7343b05ead9dcceefab81dc74432b84687ffcade5b3a5d3ba8744fa58d2fdcb4a366191ad292ae840b9a71855db2c1c3fc2a88ca0cffbcf11cc12ff71192b37b5ac15cb02a1b8247516c52ec9e7e1a0bb82aea480619518057c8ac85799c60c7ac8fd8ec3eb611a467c273ecd9108a56d6029db0420107618de084edc93d09c83b6232f5434949898ae9f826433eb3fbbed52b1509bb48cc084c5acad143b0d62c03953bb0f0a17b7e5f7e7c63e538cb505172b98fff2e166ceb5fcc525a9866879c87dae3be5ab65e3d8d5779da23cabe64ccdeec47d382475412552e0541d460bb19a65a2356f27acc394d441341416ffbf8572018183ac46cd1280022115bb1d51b776c61e005559738821c67226b73beccbdd4b6ef9eaa10df43f76d1d4489c59eac32c2c6977ef2157f84ff7a1d719fb681090cac6702e9d914d36aa8d6b5f0cd2e6378bd83d943bbe8376575b3cd78a7ff63b4424e9a1f7ae415048ecb6202aeb020011108fbebde7cf001c03300ca0135a56044f35a84502ad4e68082de90e6006ef0f5e8f0d1e6480d4025696375cc8dc4e9fed3799e21edd5138d627152ccf01e411dca1f8a83deac5df4ae736aedeb315b8cff43add3ebc97408300ef9c59d20277f77a5b023b79f6979c1f13f8090f613de7b6869cf0366d9cc7fa621b1ea57a83fd090ef76c3daea76c43dea3c9f91b78f84da99882f6db30dc984856055eabcc486a2a92919bf9c9e261a2ae020f53d35c8397e8b5d7fc5d3534f5aa68ce4de91dc068d8fee3898c95bf568b4eb06445d9e806805570ddf189d08211827e90559cb7d4d2dd2b9ed79a2363cc42c151f3077a339b3e2b5512316f6f3e4a30921af1d37ef4c42e1b9888b700fba6ebd4c7483dcbd6ff67fd50fecce49f1158149a069dbf459618efb2d401fa8109bc8a305dae68341ecd47146d2345df8f70dade6e8ac94f78d7b746e660a1f93fc6bd68b288970c507fec3cf080a66f8b7e45ce19bb579a6cd4ecbf97976770074c45be6f7b3c9fd32caff7bca21d921ea418cf19fecfd45d2ec28f8dd8bf4a4485e4e16f07635cc1ff84a0a0baf69aff2f6df0e27aaa28e13add852b72f69979171b47ecc62669807c24689ef34caa5e960c56b802be3338cd6f81b781918f17d670d4ed66358fcc0bf4341f8ac9327f340e67e400886ef9ba03330546d586b71068bcd17241d7270886caecdd8db48b953c1a2a9d4e25bc696684801413a57993883018f478c4838160f4b193bbb62b401cc4f9b35e8388aace39f82be8a9cd704033a7c68c657bb1034802ec60969e1c3f11963c37473c68a6eff74dbddb4e5bcd8bcd8b00f8b0f0e4b8feacbcf7b51bc5ab13675e636dfaacd39606b38f1c24cae8765a027b26dd44da7ef872c20bd0d36e8559f372c6dce3ad666c805258f92336b79cbd96340710e6b1916d0f391804c9846b8860eace1a39cde73784533ccddc1be5700c608f21165eff24fcafe7069f08fdaa371fa84508bc1ce1d89c1c2beeed807e77603e7716dd0cc9824d07c00c829d82eb0da413666f2e1b0eca2ec4ae1a36c2dc6672f0b2ef442e5c0b18ffaa917fdc17d56ceb3512cca352d659dbf9b8629d292cc1a7a630c2648b0998e1b3c3b9bc169a521a614138d89f579f4e7c2714a4f57f57e38730707ef9516f753130d44403af3a277cf413dc3f762aeadb0c918fb1896acfb4c189cec9c36b4671d8803c5be4f1cc12d1c6983e95d8f10509c1f9a61b491ae689195987e856931eb384db3e9688029d3229f7fdb2f27e092a098805cb6e1a2323a63ff6c58f370fbcaac70a1daaadde1c5bc1eb3feb0fd6e68e44fa0280a9897cda9798235bc188622bddeaa401af18f60e2e9525250db8c7e56dc54cf00f4abdbb9a71e00cec93505a1285fdbbc5187567f5acfd81e5cf8173532db56da124a29938b7a60aff8fed7f4bdff67148cb38c6ce416e21c36d0398a1455cd037f537fcae49fa79de829905732728bb1ce510cefc1cd6cb389fb3e35627ad3767e89c03bd2ab8da14c69f08d76e7ce7bb97d941dfdef5061746b857d9993f835f9e96075ef911d65d05cd3cd09419c0f2efb7d9f05ee94528b1cf6006f30158e70a751bac1fc0e9c60c03c231ca8f8f24e438d3a3f3e2b39e6795a452b6d870c0b1f114ed9f5c61c267c4a377600b301b50cd29d29c585b386f9f7439b375e6876d44814710c1e2961aea3e2d23f6bee6077d02b6494e67003cde03953666a98f259340f20c7bbda9d183eca7e0eccba974474b59f00fda4dc586bdf7c3f330fa1a60464adcf296a057e58d090a406253483762ffc07996fb9ac58672030d46deea411067d60afa0aa30082949d64497def516a58d0e490c5cbc86f11d3042938f47f85cbc7ab6fbdffb418fef709f5e52a25cd0ebc5552b1ee1fbc81d0dc764b3f87d06b02e49043395e7ce3fd95393038f3334b41dc55e0ccd7cbcd4d9af5932f58ca0c3ef9d34a670956596bfc06fa066b0a1f2514c4d2f8c228a17c7ae2001c967b200d1ad2dd1f96cffa2edd21836bb2d4a7effba7c99557e14e589f79d6679eca9682f1ad1d2a502b85e6a682f3baaf030bc79814dd5cfb23a65a6cf76895f992fbd935f4d3832a668799d9c55ff087c8f07cb4faa8446f164ba5868164d647f95818287bc120077f3704d6a92257ab6947f809c13bf407ea1f26423f41dd980630610e391b6463c8076c1474e267688f1dd068b5a8563bf7b465d39d8ec1ff8f3a04e0ffdb4fe1612ade3cf00abd10010af1570a6c690eefbfaf256ee7fe32d5c6489f9796bdc46ef0d5cbe7728e204a234d273060f61c74b0219cbe5f3d8da244412230276db2585e4839d71f58c2d0ae270c02b9503a1ddd54aded0afb711f866c0f46a4e62f184104eafa95e47872f3b482b3d35beb1f62fb1a83d35440b05395d0bed173b63fbe4b554f8ae7dedfaf9b0c4a415520446e8a6d93b55ec9c418a45dcc9ac5850b2105d813b8cb335e6419e2b2e1b70cb20c928cafe61c73da6d28ac82316c43db0cb004c52a833b51e6fac2a23c4f88ec53fbe0a4ad71e6e6729aad5070fdc393ed6058b627b4b42f407c07ecf72ab91c237b6345136849661a57ae89b3ca933c68dcb8c5bd0a549ed900c49c0086bd60c3ab7f5379ff5df1148f98e64ae1ad1c24cf0e6fae5ffd9eb852a718df05ee56dac13c675092d7c664d98641c83d45c39c913f9b063670b5dcc9c5adda2103bca43e3b2e36db3d3034056e672c5fd5325e193acd69dc82c58b366769484d6fc2efefa31733418de6b12c725d15277a975623540f84d409405441ceb02a3f84108e8b17712871e22c16243300b42462384f391027f3e03955f4cfd2dcdbad39464a647a90755dcca10644858434f2a12fba3855200a6d8ddee54fe960e70195f3b20f5107e4a1be91eb88f996167dc6e232031b7bcbc85aea537d5907c2c2ac0fb13037b96baae283f6e3577dec72e23adf2de724ac59f52d44ecddc1b6fdac900cfa7528c8371e88296364d2a02c585638f1c759d942493072717e875bd3accd7c5b431045a9ea9900b9db2f06d2a04bda0088f73817ed04b7be29370439d2da35708e2898bc5d1e8434a8b4b012d971087269393e6fd0945ba1ebcd4c687c8ca50eed0767707f7c8b979ec26d2eb1f05620381cc40c537248f49b2785f133cb86d397b8257640dffc276e9462cfd1dd4233f7a95cf01293460543bbb6342ae6c798b95c94838a0bf01ea961453bb899edb63f59e6aaccaadcd2bc98bc9fb30166786921e809f45be7fdded8f031efaf70caa77b06b6882868ae0474c4ffdf190f3b364e3ac4a6fd9f9c1bc2274bd7232c10e004bc03247390412130923c92e5c3dbbd13cd8f8d4598ea616448e8dd4f8195027c241ad34a23ce720e740fd702af0cab783832304b91f2c2ddd14d68cb07c56eb1384768cdf40fc68b55a8eda7e4c05d89475419402b1c3faa212fcb3dcaf9705f55717b88629d5bc7dc7ec3b4b82a734fb62eae974ea7ec1e939372d3fd6843bf3ae60c6fddcc7953a769f7c28766dd1a9039caa2b6fbc75d40b75d87c5c1fb91bbdb8db5000978ce03f7c78aea767600cdc74ea6ae8bb8899979a6b513ef5add17b2c4aef417bf8ef80527fb0cccfd8173f562c66603f99857a2d572bf5b38b7615900ff334b8c9f717c05a30a041b31bfacb9008957f44679bdf2e8de7a12c28739e0114fd40ba21db041860f5686f40a2817b86984f6095d4c63e513550fe0610ea3be5880c43d8c48740ec34b0389a0aa9507f10c7c5640a1e85c852ee202a3dc9546ea9531e90b25c14e3e0b16709497e9ecf5888ca573b30f9f4e5a4f4405fd47fd3a6cfe3410ce724d1baf3117a5b6c8ccee50aa282b8e1b745dfa940e07fc8a8af4d2107f3d808309822774a502a8c774d0ae71b19a077bd704211756babcda608e0d080da536d642428221ed66d949b4d390b44eecb73a7ddb30c66a11f7717f70800fbd11bda5db75cffd68c001d2e7b14fcbae6475142fb33ed76a699ae0c5c2ae54f8c2b25a125d1936e9d707b0c24dfbd62579750d0d1d3c0a92c991892c88caf91ee918e458fb27e1e2fe6b462c873d84e37983fb810cd76204e8d328759a1327456b38336c22e0f34b468807ca5fc79946337fafcb87b06ae8dd93f44bd37771d3eb805d997e9e66cffad20ec9390d31c7d9335c652fb9c195d8f7708b7d3c0f394f828e25f5a653f6ef00d3eac410f65523a9448c30b9f7c8a7752a078ea248be98036769a119fc97909180385d6dd0b431add4d463816ffbc5b72cc680d2b859491499cc680474ad490b7e030ad92e20bfa2003d6f2e5614304cbd4acff73a3565a6afc5e832f9c654a53c645eb08a7ef13a635d5402fc780ac0dc541f1c593a3ce01e5b09cf93d664215400d3fa3f153c44f47e50ef988c73b4abde12e46d3b7b9197b6353204f19d91a309444a528c9937361dbf0f4df64a1e2fbd65ee327cdf5a9bec94996d30700aa5ffb25d664569f736d85cedff8d52c01f623a3f08b6160a3c6ae77734a1a098aee24e2741c28e010788f0042dec0653dadf42e945ae4ad1a18a5b44eb0335deaab18c19d04060dc417ce632aec07f8c665d8c7f40cb0398b2ad685483051c50fd7c580003130af0f687528f83cad622f55ee82d20de0f0121d0eef55d3cf194d91c074fce47e1e9395e4741021b2218996ac9ff71d463a0f083d3a013d61fcc7eb3efb5f915caf4e7af380fa968448f2f00841d904ef2422db4794771c33aae3105f6c9c4176480313f6083cc9357d424af6e74cc213b8186314a3c6020ad52a825ce16027342e446f59e58c7055633d468e1c6d4d578af479390d9c4bb7707ff4cefe279125a3613fed160f8dcff7aa44776fcc6d3323e38842910ea3497af7e54884712f4fecdc356f63752ecb27aa6be4a6ec6380a2e659be18d4f68d4d9fed9e5a8789413c52171b4512be72c62b9d7ecffce452a63ce3dbb6ffe7274b4ac0732cd900e281cdcd95b5410f126a3f6b0bf1fac1661da073b5d55c5423b6f855ff562ce88c0e48b523dcae35f47ec573b5f4d9a168bd469eb9c5eacf9f6b05743ffc6d643082cccf52cc3a94a793a844fe40da8a768c240b82f78310b1a03c5bbf230ffe3646378690d4687e4f30fc06f5310a1027e570be311b8669987c9257ef5eb7e7bc6850fe0295315275e6a37aafa8b1e2e5f93dfb46dd10b43f4bbf9141b116275f01556668f6c3000d3456f49cec0d7fe65a1e113d5893f0848b0da91b3f1208608d0452362081c5dce062d3e7c303b0b40b26c67c1a64304e8e56e1b921f7557650bad38e59faa5c9d741ae8e1fbf504c067f8b1a4071169c77a23e610dee5d87e52e3f7f2cdc9889b928ef06e4e2a78a1785ce1c3d4fae09febdec70c1fde4bc45c1530a4d106cf83b7c7b31d8b7e1f318520ffab2d78b3a68d7f0d28721c373606be86b8ed81b8e7b0dc0b3d5e51bd3b9a10f1947e31ce1514c95753893f7dff997e8f1087993118763012ee3fbca81edb1d72c413a856f65eb13d7613b2be67efd60bee9022b52cbef3bfcfa3539c987d7232770f142df7bfc557b977dfc157cc3be421948cbd6c91fbff204404172481d9d8837f127e7b7da2eda72fb9fbc224b3f841aa948e2d07989b3035c12451be793239f192bd7718137cf4a065c5e3603687393704326dd4abce59986f9c74c59aa6524f6fb490549e8e9e9dec42894d4e5d7ec5de75a538d320461be86979a201264f33f251c39ed65ffa74c57f7bdc803f0fd7b09250c61be7796b41c5e17231fdd706fc5adab8eaca494cc5f06b205c105ea8fe923273db5bbdfea62c3babffe72e5f49b7939be7a9910c55c8e32dc7987ceaad2c40001d5d458728e6a3d676a4aef296d7c3c255ed79a7e56ac50e9c0a376210be388a8676e121a7bb7da61d5ef0c6fab88afed2bf02e33854c43e8f14f1b7811021777bef2cd3f5d965979040b6e09d9fe5501c1612892933f30d23d529de17ff9d4b4718b645eae623f96833ed5452c8ed039a50779bcc665439e7db8f59fbd1325b36fa741d0079c431c1c1db25530e8cba3a7ce0e1bf48776260e1425014cf9a86ae30fe6c5a3fd0144817d8066da3b393514d49d025afca0c356f3e2bebbf8599632b4df74e8e8c52350edc41cd72687ef939269764f6c45a45a3fdd73483778f22053a1d9fd82fcf549d308a201d8dd126347e405a63851cf5554dd6459b412b3e0abd19f9fc9bb42614f1ef7d328e919991d3dcbc8c62bf7971ac9c82ee101a7eaec4e40398b264f35109cab8514d18ec1add6e04f127ecbdb0e8f34014ea340b18302538d95b8a95566678bdfd03213b0138676c67693367012f0b520bb3113645b62502929a944445cb53b08d1915a850e0b18a2d05679cc5c2c80e6274b638d80bee3f1c67572ac9d4b3e6c6ea95606d4c3bb83ba1e4945c240f5a1913ec683e8e9cb4209e3108ebe46ad5b055274bba1752fc606c2bac2b0c6128ca910499571d3a0b6e4800e42404b5d8ce49128d14f5d402f7a25bf298bd6ded4f8cc1d17bbae658bb05a8b69d3fabc7ccf20e173e5b5fe41d3b55dee8ddccb15e65b40126fe786f84eb4b036acfb74311f884548179b0d88dd246306ee42df5ea5913dbac269594b8fe1a0d8f0c4a39cc402846ce941393a7e4e45b0e2173dc1495cbea2913c46467068f06516dafbc78653be054e8d772793582e80e5573e00339041e4788185530b4fa7747d1b236c25581ab3462a235f4c7ada43f5c032e1d63651a3c81bb45f3c7fdf2311e56fd6f2b2f5eba59daf453d338ef5024141ea92b94ae2e79043088880a099a45209e52dd812415c90debe3f1eaa2a3ee8accfd405b0c06c7057ac396f2f92381b338893405fd2b4be98a37a1a9d89c5df54c54e3e049c2b1f8a42af8a58f0cbc272d887e89816b21fbaf5898b8e2dddc58f0974c68f3248ed9f7bd30b566b478d5a96f127eee13c13b787673953e0713a76005fcaf35ea7d3da343da225496a3634f65c6b6a4c8b8a393c9425af20fed69df27f6650ceb5040cff7d997c3d7e722792722472af730b8f55791ee78de29414a1deeea4462be62f9022771d1c61d30739f6db1bd0ef78f0aba2db2131d4714f7f1760a5193b1ea823aba146ebbd1f88d43f90471e5278526966f79bf03a676a11839f4a83cd80eee6c2604723eeb1b4d4b95538c6fa0067993d4bf8d0e6168c414112698bd7b3a35ec98144f245a99aa827d9121c92745ed7ae8b61475f5dcf69bd21ca4bc90c0038b53d5d2bcac6dc8225a182c671c7a3efec9b7e1387aa14f56bc9561feb09a804797f08a42dee6dbb652e90878330701cab614b1efecc9a69204ab859be2826aacd9a27d579634ab888333cfd319354c7f58c6856b583c536a5f09638506b864146241246f2dcc884cb61fe0463229d78f6f0af3857d09c022a59f30b4beeaecccc0bff4f6663a0e48d7dedcc8ead42ec383557f91d86f1e08b3d42fe10605b22e0c7280c69c4ae670eb61c3cc5e7f6f15c5f9f03a07a71ae2511c7cbe54675926f6b96c06b1f8aed70724122b720203d6b18c6af7442ac29f87d33b666743521c1bf3fd36b5d0ff730aea22e5d664280116832f8e16fd4096f24ff6624571cce426066098d5af78d06da04050bca5b4a060faa42e0bd156557f1600ad9978f52c3cd8e4a879910e7c320b971e61b755a2b930f2f150d9f548377769808a5afee0b2051a677b8b0d4d0daec9bbb78cdbc4d623bee1e224585077c7eeb458aceb118ce1bd49e43c2c578c11e2d0c6421f57380d496442893077cc7f0db3b5421d784c86c45f4ade46ff8a008467e4681126f6e4c912778e0d10f9b7dc4bbc4bbe4a0674b1dd0fc83705171cbd12a9f6467806d10a03b7c887c943237333891c99b03d6eeb0466d608ddc9ba7792cfcdf5062f06be7b6191a6572e1a8195aa1bea7abb550969534f05b369a4e882898b79accf58443d5e62c476898c2dcb768f4f348d6cb36b9aacef65658d2aa0a1297b734990108d0ff144c06ea1e9d3a7a79ed58fa3e1745da88a00b4adf3da41ed247e8d61dd98dff68352a157fae7efa20c175eec2344b088a00e030cd14225b4a4c4fee992c1fa7c64647977ae93e2474945178c9765a649f8f8549305e0196068661061032a37093f304c80e11d44eff503e3473bb1ced4d820253f109972ea4794bd0c6bc76410c1be9429d62d360b8ca2b525bb573c591988ca65452ba3b249a03c7d5199ebcc3ba3f79f0306841bdccc34623b5ec71737d06ae1ad642ec13ee393f4741a33a667dab704be3c037fd20a512b11862a897a33813738693e7920a2484f0294ec43eeda8812b5af0b4418387c53144cdc4d9198b803e933dbf23f6355e9d372589108e12f4e6c3c32155469f5eb601fe8537a67dbff3b3a24155ec66c103aa2979cd852421ecd6da2468c03729fe06b587d949835d7a8d5020d62144657cbe1e8db1162206f1790d898a8946f5535820074afc2ae6d3e81c7c7de63ab55ce460c56c2fd3bd29a366f3979e4c6c1910e9d9c9e4931240f6e8873a63c786aea91d678cd96e2d2400d72dab983c2da1170bdeda68bd359dc2f098479a449a62ac3799fdfc188afd96f8cb74f4edc6ee227c0152dae700c436082842c265fdbe48dd5d43f0e99e8b0ac4c6ac640ff3b931a5db2705a3a1f4ba6efb1a15c649eac8230930abae799eb8e2044df54f474a6165c47953d5e9a0dbdec4aba69671f15c776902794dc8557fa7513a33751b3dfb4499342edea0e97d422d196afac73e35e03dc38e2652961dc4dd1d46273574ca5c4b3d00f5b4c71ff7471e4626673f8cb59a27b9c6c8292c5606f7333a539b7eeca3a7ef2a938220315bfa5bb8f5b882723fb7ccfc19250b8391d431f4d1778dadfa3666e65db2bc5deae4a2e02e28f07aad2764d272efe0a6fdc3c0a3cf3808810eeffb207bbac00b5c0870a07283065e6fbdffd7f7fae1ef12cc9fc1eabbef49f286c28cb2cbb3025e866de0448b8454b7e744648750ca065e95579cc1ba156891bd3cd3e8059d2b861f9485e25c186263956337ce10619aaf48aec5e20a5a43524d1971b6200718c18f9a73b25e56ef73f3fceeaea66a0d3c22260f4886714feea12b51e23995dbdde924e184f8d5544a613e2bf43b1ee263a594f70ca88b33059a1d04df485e573b6f37bce6f62adae609afba7c489b67436e5cf91b6936cfe350441439e074e6a7acb27fcf33bb7bece54650d601d3f3f7fe6637504e5ea989001eeede4d6bb988ae749aebab31a933df7bcf6fe32f450b11b76813c2785ca84399329697a06aba3a4d5c69ce27d3985ed318e51908dec231aa343afca754caa305b714f8df450381e2e9ef5d8a11329228775d7cfa59c5b065f36fb4cbad2c0e7282e0db0891ff35367f512b39308b5d25f4804487e40dd9a00cfd71d9c3bccd5597287ec39405dead1d50d6904ee96629b0a3756f30af27d9ecaa8c937f99a1d70391834e10317d8e00e6cc09b4a253eca8fe42e68e1098d4ec9eca411e015aff630791ba73035858b2d043246f0139fe535fb1baf51180d553939dcc56ff930e39c2bcf6abe5ed7cb15b4c6a13fc95b0475ce69896fe20c6ad987d89e5562804f1abb452f2d9dcd16b5b7a87eaf54411ee2c48058d08073a67b21a935840adaf02496c1ca2c3b618c2432f4ae1bbd3e66b47a516281cba014abd6f58cdb590238beb88544b8c21c8b6b6a0cbccb34715fc85cda581604ac8822a1ed680ba9a5f922850f12b2a6e51ec2da7d557e14d729322db62739129624d114ea15c16027bd9248cd706d73b984cc59be405b25677b91f60d61c3aab7867078390c6a0c7165e6cf1cae47f9a68046ec85d10748fe4cacc3662f60397e856042c68f391de94d02cf0b62b2af4eff67bcef61dbfe0b62f061dfaf74ac383ac6713b4c1657abcdf52467fc4bb1b8bfdc25af84f6286864e1f695fc433d2d40d7adf5fe5f55572304c16a20eac4aa45ed8dc1410f65464b82d20b8587e0eb023324cc7709cd39bfef1f9458ce0a4127fb441ae3a7dcadb5c41e834e2c50b49297bab7df75aa39a17ae37d7441915c82783ad0d8fed51ff672f2c54e77b0da6200ed46cd4bf377248a505066426a17eac38ccafbbd40603a88e4a48bc596858dca7631d4aaa02fcc964f60c46a72e9a9f649e90926a4738292fd77f27696de0eece6ea6e89075843613fd5c5d2a413795c6b646eb9ad866303c9e953a89712ec529a41abd9d105cb0e77fd951a7257d5f668546204b2f128799dc7e662c73138deda8e5dc53358a67044e256092a5445f23be2e7c9dfd856439861cdbfc25c1993d861cc0aa05a2bd1e1a924407d85fbdac9d6eab266aa4082a751bcab26887d146e5ed381fae7f7cea4eef8c619138d470dc2c6faff92a762bc1d3465f041e1ff32e1611b303b2f4e49ebdb04744f8c96793e69ae0d27a915a4d966e7977d33af21b425132ad6743f6657d4cd6a786a7b902bfa01c038af536dbb3b7f0e5dd210b29b3b0d3364d57446d131de5510c0fad9987c8d6e0e1d81ed2a46128d778eca411b49539eb548024a13e7477d9a6f1a6cc3dd879f3cb2c89211594b0645fab966b05188b2d34a5319682b1dfe544a9453dbf4d31f6cdf9c4c2ecc27ffadba746cc892f7d2d2080ed858629abce0ef50d4771b8570b79ac764a3ba03b81436a25f4ff4a9b8bc928855c63fed5cd8679a68268751cddfd823cb78f65d74373b2a70334760bee2748c3d55a0a683e00f7f263924afb67dad80156710d28527310609baccd156f6c99629ebd3006aa8559f63448908188386caf629a05306aad3a0fdfc5135858413a3b296a1f1a75a70e1873618bb23f639d267a00c0363046d1d0f6391499d4d8f03503c4debc0e6225a40f20747ce70d8f2b00ce6cb04bec57233433ac958a570759f7c43842bd0c8ba9fa4e798d373331108f074f319f2b5f2771cea7907e9b5a8d4469b9f8966c2b803b47f080fcb5318abe4a0050d08941f0fbf5e0a20ca4cf4731f7263a1a8b7ed32281303242694f007e9846c5fb368421272e06a4af0da5c452f21f9c10acff627e0f106cf1328030aee69789010b87f1db4796e57f69118c9e062d80ad3f06d65c1b6729f6eef17017521956af10e77d6cd26985e5c00ddd29db6cafbb6669199dafb158af4e34b1a46736994323c3e2c09ae7f9340f955494e2758e5e1098967b15cbd9f46058f3ada060ee40d7191ff0930371c6c8d9fa95d593f07f1c5fe1612cb11d00d4b26d343b5fc63548d41007f961f1434dcbb0b6b9562e67deb7b13e40cfe5f2df99c26f7ea7882cf71bbd092c9022231e52feca39781e50e03c8e08e760ff9d9a8efd41f82637e7f93dcf41ffaa7276dc09a91f4ff23c0fe2c1ed4bb5b387ec18809fe5908b283064db48a392f60e03b7eea7f0e12a6dcb5c38689a8b24fcb7eea8407b8aa97afd455cc1a4b9ddfd30dde064cf35ccfef77e8034d494847b305d9598337baf0a386c909e182e67f75bc9f57c58398201a794c4817d3837b580fd4101767ab197e8484e8875ced2d2373bfc30c1a1081cfb74b6dc68046d9cd784d688894cc8d5113b9d1196afa6cb8a28df82361c53300a003000a001600d000202a00800316e91e0f0a564ffd00da939301c9295966a752d0822ff881f14a664ac5b74fdd6f1ad272e38b9576c0b4bcb3f4fa1715eb7f21ef2e4bd77cc0ec89d302fde0a587e1ff9cd07a11de95afdb1b9acdfccce99f743c7765aaad2061437e72fec1559fbf84997b9d05a31708f52d674c775fc479f78485597be3d64d87668f771ea4f2ea9ed6e36783b110b3134f1e12c4bee6e186aaddd9509c5ae859f7affc40c4161df2678c50a5f8a3b43b5586aaea1200dff9e224c42442a9edd96b1302e7ea5f1284ba70357eb185c25963fa94823fc17609e66266a5707ae96df9025570d34c88cc0fcaf6abaef588bded6f29d3f22e03b579db8e1aa0bb317896db3c237893a0323d9c7d69d6f232e807bff04590be0a8fbd74486dd8be2468966ef68ae1117ae4003cff53facbea2eb97aed4c0dcf57b225ed0c3356be957983021d41c14bba73a785a8ca7ab3fe8f0c3889fb2c7a1c96cddced5e6040f2bedcfe41e98442a4683cd01550f49939b4048b432009d36ae8b4997b2bc783546bbc569d8ce64c761937b151fac08ca1a091da6e21a3ea69940c891facb6d6e7ecde2b55a439a56290e2efe33ad89a10dfc97197486f74e900861d0caa6e747a47f9224d33b9d6492d87f4bc42ffd9759247add2f39080e72e8e01ec567dbf83b708d59a351ebec73433a1a19b79e3f3662f3b8ba729afc4a57007e80f044bc4349a880616e71f1c16002b6eb08f9f01a61793b8c133e1f72f1195f30a8a7db4c037cdd850fa1f5014edbb0331863d1692d65bb4039d29d812373d4896f70420bc6a3744cb43d0aa2d5f4f643997ad4fd98cf81081545c2cbfbe90635dae8e75eef2b695d77578390b350fa1fa4b64f226b4be7c54b066db735e4bb027b6e95900091be946d33e0f37ce0337a01e0b001e202d80c8e6bd3d47d89f7585f0b668d5fcdb66a48c0ec63a76ed0766d576f695e1aa12725a40ba549b1bca02a5a9db5038407ecbfa2f05aa8e62f37bbec4ba2c60b43b29d5c3fed2c8ed617cd6165afb60f3696f04baa98647eed8efbb8687523deb0abbb48a85fc20047d12f629d84826dae0a30c7bbcb3bf3787d186d86d9c518718dfcb49cba91be9ce48fda10fec5a78d40037fc7078a23c59e32c8f5cdea85e02e24d989370ff2070ca087a1617fc81867bf855c986b26163deca5bc745f4a25295a061db6d4819d8047cd5545dc66e67ecc24af55fd00deca200ff46dd0c43aa9383ca05e924dff743af420182621c34a3b465cb7b5557d3cef8caa1a8827b1795c1267ada3f2f37a0ec884daccdeb1ee61d61edeb3677d2ebe59f502e99ce9ae652883786c1fe657887824aed13fa852c1e0ee5c4706d583a0d884b1b2ee26a030eb44e9863b5375823f4c12b980dae77dd7c448a0228aea726f4e5a8e11759554f8e6f72e7592acd47a1620dc3883d4af230ca975ca87f03ea13a7569f4aa63687eac28d6dcf83966a546487797c452aaf7d9915a649d01f690bafd8d8558566bad9abfed313e293023d1490207ce971d6d85742566568e1f08a802d70f4f2070acd5e2e8f691b955fce22b59fe94d49bf9817a29c58c74b91a16c5ee50ce7bcff5dd5be05bf250698626f290b9d943fe319d563bdfc712fab57ea2aac38ad57236ccfedc378eae9046b09527d02b5aa5ce8ea38b6b3586e6efa2d7224ab0de5b2f36cdc05d3bf5e6f9ad6921d1671918b8653033828fea71a9bdd92a4175f42c9f634055cc4a89d950e6809350dfe8940b137b3c832672ad4e5664f9ce7213e3848e3f7d5ee88be2e9fc17aed09feda48a3a3a1dc94c9ca45ba0e500f9dbe231e90bec78f91b89336eaf70a90adcca493b67b44c37f8fd6a7f21afd06d6164c8e8af28c726460fd46ef795727b8fb030e5230ba72312dd9abf5ffa5099c1d3d156e1ff43ee649857417fd628c53ee78aa6b8f7fe23fba21da1bebaeaf183803f4dbc7e4cbd9ac0f34a9fc1aa37ea20bf8e78348beadc665d08a469d3d40ed5c307a17140c418ece6fcfb70aa1e43feec6a49762c3504c7769f27679e5fc682a5ecc6749acc043fda3674956f67f1eedaf65b29da8bc899db6d48decb44278c3c0fe17de1b663f4dec396d5d5d1fd352ffdd5426bd2cfd4fe38a026c32314c11deaad8548ab284a6c20abc53952dff633f99fc6163e15778f72314984d5417106034b0a671ceca2ce9a2dba12559014c2039c19c399cc55a4849ab99c565b14b74c0564d2c49f04b910054af2c500516ec8f22538f1456e06a3f88e7ad5f3675bd422fefc03405d594171ed4f9684958f092a8d16c987c84c3862b900a46b92444dfcc108ea44e249a6568dd0b4567ea70f375a5d98c25f4b9ed900bd76eccaebd9c376ea35bd0e073ffb2b7260509f3fbdaa26dfc1c1ca92c0b6f581078d86ada9f1a539a5f36e7f587534fd73078eef37bcc1baa69253fc724d6336c7e261369ba076b2930f708615e32e6e52ee8258a0390d242506cb011c85766f1111b00eeb07a6900b9c6b44f1eed594386cc0301d223a919cec928f18de709dc7906cc6cd4ddda0bafc40e6147381413d96932e97f607c9067754bf0ba9578bb21363e445626ded6c67766f33deae67cdcdc97571deb4eaf1901deeb8f8460ce4e1830b6bd58035c1869862e5e3a55ae10387e61541eaea8d4d6b861e3d4d4b8aadaff30ced160074544f941648e41b72c165d3160538113e7d031de33d89c4aac68c714c13b32c5ac1a8673028090762cc6bb893853f5e2c5dfa13e9ecdd6cf990fad7067c60194b585a11a5e68a767bc8e2c0f0da692d2703e6bf1044a329210dc9335291db43d619a9f94589665dfaf5b59adfefe88004575dc67276a00cd40574ce6c608f0b963d53801030f0323bbef84767988951bf0075d5ae995f86c8affe0bc9898e33bf4496d10e00528e30d669095d0c0bb68ce281ff04fb968bdb2263e8458ed215dc3bb682c916b70531fc9c1e0c157859f4f8bfe18d65ca7676299d2a06a3aeb1e9829a67c904902eca0183f5aaacb1a14ee40898651aab968ef6b3a3a94a6b3e86b8ba60318c08721993225d72840d7e651f5f014d87d6d96e3817a3f9cf899a8a6f40fb39287c8802825df1d0e0cee488399646f1f78a266c8226a73111a6d60b32de2cbf0cba142a640496f90a72b44df20bb41a179ad6011af9bfad62f4f5681104eb31393280558fed38752617d6d43eb2b7a02eb3f50261f4262225a1c49b4bf6ff3f6ebd6abf3457862c8369ed1506f1715d2a901c633af4cf378f757747ed57ddf2c17c445b910d7f6cbf789cb609754c21beedc871aa5ded75ab19f994949d93d06b880788353d908854c21f7607480384b0878fe0d2d4bccfb9ac9d7f65d318f71d4524aa5adc74b49306d200cea4952d60af73b817151bb5119effb0c3bf9429b77c11b19087e89165a951b743e7a9688a7c5a04d813d414c90c78dbdc0425c0bfe9f1293052ae88df1b9a60bab5f7b883ba0c312e234eb026fc0c9869898d982a3ffb2a98da8be4b5308e94e1cf72938a5a7ca0034cf14833731a4959cd18aabb95177cf4c8a092609044b4dd5ccbd49760b6aac03edb8379d53283e0e47e5719b7596db74f6d946d72a6b2b523ad5162aab117132d50ba338c69be3ef4a1f62f50702c18eb62971b8fe5a746beec7b0582c8c127ae69b07f7e6a1f8f4621fb8c1b43b0d157d47f948c1a7b1250c776649ccc9b3310f26110ace85c76a04db80604b6dd06e40000bb691e6a3890966dc50301df01ef9b2c3d2fc4f419f7d72d711bfb9d68963a972859b7972e1a0db3e392b576ca6e23147a232284ecd9494a75bd0e3cdccf63bfc7bec32404e22524557ac751b2dc6aa33cc3929397ec75a8d290c2e0a82527eac5b62c43e3b384ce3eb5dfebeaf86b55fd457161df2004d4c8419a4b3d24becc1f17aa2e288db63b55a96b3416d4a3b2bfc9a6c6bb8c7c1f1cc0a94273161888d07a91227387c33d240b17fb27c8a8071ade1f11b4073e6e869efe27347e52aef54cd3d482767a30b846f6863c0ce6d6c026d835300db594635392134032552511f85b82150d57ab3ceb49c803916248c1ac1cff59c1cc29504c1017d1d7b6789eb8858c545c2f43f8493a4bfd3d9ee9894a5d6cf8e5400f8f97a69414e680891cb32823e8113adb154ad77f02ec3174456121aeb04d15046018c376791155f2e45f02f1c3aedcdb5dc9d3582a3487ccd9a4cc71ed0e22b41079b1b70cc380e5f47cd0d2e627368287346d0efc433084183be92fef49e0cfea93d7ad341f119dd2feec3d19156250b7a31133e812511f34dd00019c5205934c22527f2ada2e0f99d875e3a90a0ca489fa42ccbd732aa3c99939183dc541223fd1839c6601ef004f51495ce73e86b17cd991086d7969f2b2768c1bd5d72c79476d491067443438ec60548737cf538081a99ca9338353b5600db9d4a3ec8b9d0c568965908e8d44f3467aaf654f592c9b10ed32b65e690172fe34828afe285e994f115291396530f30c82e8c283dd8a8f1b60427a928fa3a756c19a676a519c079f0a573d58551edc3dcd2fe69c78cb735dab07decb3b2781e6c41feb573e1f823ee2c13dae7e46444e892cc49ab1808c3867bbd6ca0746be316138a0b7ce661a87d183b083b75b922006caa161c5c08dabc0da06a64a4edc96c4ebd47545f378e40831468b4dc659a272ea6ad15b7e891c090933ef3371d9166b7dd71b0c4281671ae24bce535ddfdb13e43d64d500b1c1ca476a362e189d34ac6ba856c5e6832fd003e6783fed439026c0d2eed3e0df85fed9bc6a4b7f1647f30047706e58dfeadae31d0ba81d5db7c2354bdb3dd4f4dc8c6448a9c3a054a93268f4e1010f08a3ae782dc87c35491ce8f784e11da89c83a80c5d90e3aa1a20cd9e38d46ecdefebd25838864d442d26409117168fc07a14d71813ca0313502a8f3f9935d3c0017e56a77edbd19147296020709ba08fc083f94c955b36539bb7bacb187167b80792442496a923d747b00fdbfa5bb9d3b7bd5e38b1e4ef45092071a792891072e8fa20f96ba8cfb69f2ed636eede3227367a7585b8bc7173c4cc0c3c42368cb8ead345bc02da557cf3d585e7ecf2b9deaec73d19e8414940321241588fa5026fa438788866a0734a6776672d5cc0cec3d5f2edb3b9d65747cc99cd0b1778e4d3303cdf2f27099c3c0325f1f9615e9cc7f1cf378e6ce5c46c7d7352f121daf79915c0900faff7a479cff577247d6d21d64eeb863c97fbd77d0ff9ae560fdbf1c2fff55ced4ffefe424fd57b2cb11b297ecd0c20eabff5bb5a3ea2b931dbf7f3b68a6ba3a12f0f55647ae0e10eac8311bc5af0fd37155e938a2038239103047d57fed73fcc431234e135f75712c90030f39ccc831460edbff5f5d1c69c471260eb2da0b871a38bafce01082838237aa78c3ea1fef468c7778c7bbba07b41456f91e73d58d2c37a6dcd0a00d30da30a18d0dd810830d16d8386283c87f25733f9dc6bc5c6692e293dcdae7eb3db4e7f25549dccfdcedd945eb14cfc0fd3b6919cc6d1228cf3d074d1d058e01e05c8143548d7047b27e6252ef9c797127896bc2916b94fff5b5c61035b27e4113d79d1a55bfa3064e0d9dbe4b6a9e9bea91a601e783b9591a61ea2b0daa3480bebefa0536bdd9fa7f73555fdb74d1a6375c7e466f92fe0d7d34e2fc1f41e38aa28146d2ffde261a05fcffe3805863cc773820d667d470c6ec8c0ecc78f335783985555377a798986165860a5f6fb51a53c5ba9c1913b8d17243839b10dc7c196dbe5a736f6e69cfd645310f962f6b5df456a332b294b15406080878a30e297d66afd2982f9b1b78e0c185b26420800c27c858818c236db4da00d1864a1b2063dc31069c31c08c011c2303366cd818c18604362f31ecf8af3e6c8444b7aed1a8e005c2f4595eb39f48b1d8cd325dbd63b1255deb144669b02471faea7031a64aa7348dc26ad944352eaf2e9b99eb74e5dbda915b2b064e0c08b4cad0eaa245b4c6cd9aab355cd64cad317280ac034c1da0270c337e08a5d5ec3c1b199dd0173eb9e9a22f17ce5d1d1d69595e1cacd67307c7cb474b751f3b0e97b94ba3741b0ac63b5dcf3e8cf92e5896b7e3feffc9ef8401465613ba4d30ac4a30a66660b8bed8fae2ea79367b5fd88efb1117ed5f50f3dc6ad0d06825d550a971a901e285d6bf17545e3cfdd71d2f78febf6661bdce9b84acaaac5e9652175b5f499ca37c971878bbb8aab60baa97eb76d105fdca05b9378f7281f55c503d142e92ec6c01e7ff2bd98d6c81f5ff556a8baaef37975b24556bb7e0f94f03e7d360a569e1abb532998c56b4ab15e9b053afdf1e59d68265794f4ee6232da6ba163c5968fda6ac3d77308b25b2b05f75a6dbe4bc2c807c182361610725710e8b3075ebe0a51a0baaaf583cfdff6f2c8ca071534ddca55434366041834343bfd64ef2bb737ad3717685d68cee4a7c8597285d7e7245ef8a0bac00e3bf623ca356dc6085b642c87f1d476b2d76aa028c2aa8fe2bdff49298ac42c97fede4ace451aaf8af5055a854a17a4685182a6cfaacba29d89822ccff578ec9dcf53ed2f593da296c53e85ce04c0a3752c4f0bf779a16852e8a09b0f2c052022b0b16396ba2e3d010144d401100282e140a3cd1e68584fa130738a184134027ac13449a98a309ad87a242c76ac926e8993767ae3843c3192c678aec1860ebffe9489b68ac7724744442ea2b65800898d062020526624cb8aec6b8c2ba0ae2aab6441f4b44b1b384124be09608a2841925a0fe5f08f338fea12f9779713566855e38a11ae47a9a29591ab34a3d738f98a7338f635fb7424196ee68fa1c7137a374bbecf962a9ab53827b26a9f1d8318f6325252a78592c762b9ada4637895c1240ae7e717ba769b1d8cd8c16334264202103124596a4658931797d348b7ba6be74ece9cc1b553b62ea88a32388bcf93b79df298059000facdeec58dd600582d50e01b42040553d3a72d52ceed72816bbbf608ef69e7b3717ad7c739d5dd4c6a8b975b9b9cfb74d23dc30228d115346d022ba28224c114f8a08abc5fd5aa1201f1b2f771b840b72535fe0b983e70e565cb38bee1eecd22eaf06f5264fdc7f9ad4b8a9e30ec4ba4c56193265a6ca2c512a83001158b59b3b445461222c113a4368e9f41056bf330497ff1f62082021dcfc5fed08c1e5ab105688255508325aff64ccfc3f991ac824fd7fbdc11c19d74e105a40bc1404d54e1049fe7f4c9cffdfe7183463c8e43154ff558f791ae31a03e4ff81c8faaf647680a07a2034c6393176ece0d4f5c962b11b8ec4411c259391b458ec46d27cd8a4c562371f36f300fe872d7e70e107a31f787e681047b94cdce4030b3e3cf940c364852153751800f4c0460f34f470f64079d88287137818daa18d1da8f87ffbca26ad1a197d3a20ee75cc77dd8773b02cc9cb31df1466764cf28e9976180213074c1260b28041327193ee0bdff982bf4ca043153ae8a0c3eddf87cd0cc47a27073239ec7278e500e49f6ed3d2a0e9c5031cf6c081091c64c041041cc627cf5cf0cce932effddae80d40dc3075c3941b10b0818d0d3fecd8e042b52f17f6ed8eebb9027db8675a8bb3e1470d65d400a686df7fb5380a47f14d69808306313450d150c00c59cce065062019f2e842063132ec6428ba3f5d2673ce52580d9e395cde7e06cf1cdd669510552d544955415545a90a12039a188c88414a0c406f7d96c2acb61416f3d11faa712e667bc71a0643171892bce0e685245ed02f8c6fed6e53174c178ab4a0a6053228b430a585a11682b0f086ef1d16645de2e8f244972532f89d2e5e9ecb175cb47009dae14264cb165bacfebb111f36f316ba821d2b58b1020b2b7cb002102d2c6861d2e2da518105159a54e84923052f290420052b296880821d2850f15f2d7f5d9792de0d0507042080140420446507950f54552a8969d5874d9c3b61eb8434279c7042d25f18660a809a9d009c09802e000fec98b08709624cb0b5f2d2ad29edf9eef26ed39789bb8b9ab8290b145978960c4a18a3041e4a482aa1032c6760c1fa7ccdab71b9db5876825004b30461c121536d4c113175c314d554d15712e782d707abbaeec3666ca6c4fab0992dc53025b0d8ee08a8052c03840262f0ff3158b5b6779c0f9b198731dfc9e898e49499e7c691f9fe2ec73829a0ffff73ebf4f2ffffeafd8c72fe2b812931b199f55f7914f36be4668e316d5f03663007c41a87cd608ea4e1cc6f64c6e1cccd7e4da86242583335b3c091254c35739991b29cd546963b4b922c40be92b8842d96c212aa4a48fa2f81ab81c5eaff93b0f0d059504d904bb0289cdafaaf98df5e96d77769309c9af2f23f65a78404734031802c84c0df3fd055cbf2fa3adddba4d966525a7c2b498579a9258ffb792475c05712632cb949e7738b93eabf9e40d59cc2d96cb3dba1994d093a9cb093397b972a77c7fc5696e7ae93df25e07038d6bb5bfef45f494c7b374eaff4ccac6d57b45c09fab9c9a56e9b94c4e5ee7aebb6e6b34a86bf2a0ed35bf378b7dc456d29fd4bf235092c5bca3b73bf7047b17f9df274e69b735c37d04c6bd59da69ae9f17d6c7c29f8824a389803758eaa1dd998e8cd5294ef5e3bc7529787250fe5d37f2d79f6521496e5dd7217d54394ad5abacd282015eb4d827944c209ffff34db8c9ae5ed32fc0bde623159bf49b25b51afdf244ceea3dc940be839d19ba147ebf1f0e4e0e9c0a3c20bdac5f9d1e2acb975596e3df21ca9716ef4a9758647a51d98ffdf25c16ce01db00e47109259ffbdeb8bb38f0fb526dff5ab336de4944e8ace486e8d9c0d39107249ee19d789f05eb943f787edc262b11ce0ce0871070871617022e08258d9c38a15cc0677f98399fcfa6ea71877fe7cb7d77b758cf57e6985b27ae3283466cdf276e0e554679f4ad22324e6714b7d6abf3c9ea53e40ec4387d47dea3649833a1fa94f235aa1ec0f85b2b7d7793a5b91b242046a0d9497af3f6cfab02e77a8a1ff7f4b75de65cc7f18a94a5905032a5c5099a24205c99433a6bc306584294323c019a1cc085446d879d2aa50169fdcf45d12fbf0ad665c4d9de92eeb4b2994d5653316b354f0e9828ac52c151d27b7a9bb519e76788222a58fffeaa27c5639d6c0ddb30fc9d7b30c93fb88c453b7c44c25e65b174ab1752914f0947a86e224c67f9d39dd9c8e38f544c9238a9830cacff24b61bb5cdb6d4ad238b6349334da8d707c7323bc8d71eb12de8cc29baca98da61a6aa7dceabddb556fbd7937172c390e9663c799efdba33d9316c7bb39730a6b29cc7fb1d88dbcb857e66bbe9ae8e8e42269a43e374fe291678ee3b2be92215398df6da6a9af4c400fdc5a0ad37f15818cc63f8ea5b69226119644789a8900544500f2a1cd46c35732e36c08d4d8d4ac086b586a4098ded3b684c412d5925e02fa587f0529bd51a2416904a520509480c2a5c248de84e39d6db216eb274b3cd13d59e0ff13f8f0c9014ece70428493149c3439095ea0595eba7b79c78a6b1e6dcf2151589fd919ee86b41e6859425aa6edfc7f0c86ebbb4cba4a7a4ad2811202560821b83e98e3bbc95613344daa824d9af03071f353d7c7c40bf63149fa0f320182e4e6bf6a1cb5790ec9cb77a4a719920e3dd23afaf2ff7db7cdbecb232323378cc2184d31d2998151cf1da43498d39bc463e79bc4313258f6d059389332a3397797785912b524b6040310b4fe6b4f4fcfad5663022129048101d91ab21e42991559051f50f1726d4ac30f46f80082a233455bc2a2a8a29ea2204a7aceb775bbb414d6d343af92590c8f308615cb12c66e1506f3000b0f82a107503c3820c913611215ea8be2dcc5598a73b90ee074504407be0e8088d80889b2422217be9eba7bea3ae6d61acd3808830314420ea6381832b4c5d0008644181a82440d922d2192a0ffeffdfe28ce71939e3b6871bb8d1b427da8b5f487da20ae772138a110d6bf9097ff1a0a3d09b978fc066585415e8292aed9cd73cf80f8d81a076f2800c180747e041e39e2083f82a4ee51771b977c9bd7d21ded0ceb0b35090cced7cef386e9de83fac25008614d962ecd926eb4898912ed469930d594e8528d22318102657644991835d1924899109293a7334fe711ebffe48d5575b43fd2ff89f3b5bf30df3e170deafb1366b77f7a0fc4bbf087672f853e702ae953f53ff3b17df5e1f97f655d20c7e42c7c51e5a8694467aa8486afa7af7c53a1dbebff9f977bcd850574bd50cd9e4b59d1d4f515d152c4a988920dead8a08c7003321b3411e183c812447e4490febfe3123635654a595efa439bec58e6a89a0db223ee6714dde39dfb0e29c09030e110010c5132a4811e37c21e227aaa843d409ef6b3b4514a8fa6d01f3ac555ab18766948efa057140c2d814285148130833032a0fa7f4a474b61c172f3607ad311030f3058e002362e18407881ed8208849021c4082150421c1823b4404d688196d002a6d0820bc20ab0082be8153c5500840224420ac6841448a180011e2e7882b3232a31ea437ff02e97b49b3956edaba9a976a4c9ba8a58ababb4c955b3e3d854962e9a7b367f2f17ed14c6b7edb69fe58f8ed6065118b5bb7cf99e1569dc03ea7c7de1046e3e9cc08bc98d7c38416f827e811f7c28419cff0f253042825e28c14cdc0ac5abffff99c8e57f168abf2ada44a09ba813c609432d8c9b8464be29b4ffe3a74eb7c390e73fa82fad4ae1ced6ffbf041fee68853b56e14ed5d71d9beb77be2902381170f155262b9ada463257eeecb9b343f0e667101ca0f326d429a383756ea18e90ce030fe8f1c098f0812af505d38d30cb64e469a781588fa10354a103b6d0010dc206daf87f007cd8401a0d50d1400b0d9cd04053d8001006900819a8aa2f1bc451b8586c577335bd6c94beba111285d50e8391e5a6d66673d55e40ac5db41b099a3d77769be33cdea541dd1788024cb840090b38850b1c102a7044a80009a102b250019d8741d9bdd3342ae4f599b6dac2203b0c32250c127ac0411092da010e81680981140199204c008e300123c204aa9ec952582e4c20294c80e7bfd69e3b7b8880508840901001044200688500480300600800a010003e0784718055b52f26fa439f5c354b71101dedab89c95a8bf1aed697cbc4bcfa72f5186c9be7a53897ef0fe3dd2ef3bd9bddd0003d20f8d080364203aa080d20131ad0141a208605c829404c580094f0c71e5f7b13fd9d662c76b33e6b4baac4c992fa863f7ae18f26e18f22e10f0ac28f23fc1dc2ef127e511f73fa78f34fbc3e7afaf83957735298639bb3644e057320e0e3007c70a99dfed0266b852c85551826b70e669f6024bfbd9e3b3bb54da40bc62d138c52db4403fcfae868fb2cce56128d8305d3671ca93849ffb5cab1f535cb81f5726cf9c7c921c611a7963b0e20e29012c70f38848083eaabe5b1d8367376b769af3c0783e5b9437a63cd1b27bce17a230337b2dc38c18d1f371668c34c1bbb366e6cf4c1061998f34c831948ea4b06e99847dfa8738977b4dab9c6baa7cb3b1aad5b6fbbcd5186b1e4a658ec46c7974c26bb15f1749eba4ae62e9bcc5d768ec98b7b38d90b4ed6c3a98263fbafc00ebcc1f935b4fed7a046746d8daaa5dfaf71fb7ff2aad1871a6dfe5f8dad46086af0fc63a9edfb5d60532c7623c96bd338401a574f92b358ec66e2591ab4341a78a3554d7dcd29bc4d9b0de228fabbc03755e11ba537ae8e0619684c8186feafb6fb2cedddc45b771c44c3e88c38ff729dd1cff0c08c2d1c745d9ece52179739ea3203cb0cfb66e8287193e5c6ea9fcc6e7a5fbbedbb7db91b9b797d3d2bb90152fb2ed19441a68c5e19495f06fd100173fc63c90878820010c868830c2c3254f851a23c9d6d647c1b356d826883dbdc360d8c71660c2fff97e6ce265a6f6cccb0d1c286890d0562a0214612627431686234a0e546cb072d520b8916903575ac19c01a14d620bd92d1aed63f60aa313149bad198c48c90826ccff66585e80732a1207d79eec5f7cb0ad122eaa2353c009230f608638e30be84f114c61130f800c30a306000e30a1844d27c31c517515f846a9a08d574351a7841871761bc7812666d3d45027bd9209b750951a35dcdba68474261f4655f41364b47fb52a231eba25d88c2846c76b7836c19ef603d93678d846cf48706d11f6ac44f3b443ff880be5cb1d86ee376e7b1d8cd5294d69ffa53492c952f90e29cee76bd33e6b9cb37a96f173c74a1d445045d24c005125cbc10858b0bb808c2c56fb1660b14b6b8a59993c68e304d99344f698ed21cd1420c2d7cd0c2a605d0ff636dad92ab666db6bc9472a099b9ab66e9685f4a945a1b106b6a97ac925da23fd4a6041b5279575f5fcfcc409d0e7613f3ca7387f9d541d96ea95033e39ec9bbdad2213558624ebbb6487a5792e29c35c546f4977b1d4b658143165ab258ca22812c7e6041c4ffaed669cca7fbf4a9ebb3d95c351af3a1584ce98cc592ffaf4634163c51d0c4f90fd160fda321533b1a9d06628dc656fb4e86e68a2bf20a2ef55783ed25aae48a5e7805fdaf405c2c760bada8590142156b54f1a58a5d154e55204085175464a18257ad893b8ee3f245a3ae198b59badb2e5a5f354bb3c1dc1dc912fb7254bfb7a87e4d199410122a781eeba6d8fa29acc229aa483c4592ff1bd5bb146ebe02b18eead794428aaaaf522491590a200fd35b875184717f19fbee5254bf375914bfbf611421d60b2b0b8bcc7f25b1a8b096604960a9d0a8ee0beaad6732ecbb4b396866992c16d365287e2114c127e0fcbf9ec07aa2ea770f183ef1abb42792c462b7279cb8aa79e704d557276c5f9d7055276e1356df9bf8fdd7739b4d2cf926787a3e03a7d228ddd2992b7e86ea4cd21903a0b906e08275ee66802524135b4c50e193d1e8c8040357695ca1f9aff6cabcfae08a678937d5363d29cbdc97a05aa20325e850828750092c4a24a94a5e58eff2528e8178079c254146125e92302109a1248a98c93223e6ab6dc2516b668a191090d8fa4ade1aad7d4b9133247a40a206246648547044968fe9887dc4ee88080ad04601c014e0cafed19ee9ad1b89d2f96cb2978bdff2cacaf21ef528dde5264e675911f6e59b6ca4a302231d473a8e244d26e36637bb912226dfbfdfd9713afb703afba4f879a4b38fa7b38fcb64b7225cfece5b910e3b9134dc35228ede4658fdcc88a96a499a35e2a9067194b9978c20229b6231d9ef37eb9792b422cc22ac14a153268d32539479a10c07968ea31d6db794c911264f9eb86ab632a92e6a7b103fb14f98d01ffac455b3380546d998e4ec5846c724671199711ce31fc744e8f4c221dcfcff0f613544d5104f43f07c0d85d8aa24e6098146882a2184709181f35fc9585532543adc69bf3d324f644232f4fa82082308aaaf63994d7de61ec4d3fff374ee4100d97a0c1add49eabdf518aaaf24dfbbdd1d637f0c2d331070f0145e02e2ea4320aa2a1053ff0fc4d3ff3b01e17a3170423157525ec4f4fe5f2726898ad991117120945496f7680a939af6dbdc3680cac60f667ea8e187da0faeaf967c59ebf281071f62f001c90707c2b8e1d4e9e5e472da2676da26760aa31406d6431ba49b73301896d23cb8e101061ea2f4b183103b60d9e1680720601000060d982b606c602cf8d2e6cbd5971cbe00bf98ff3e3e58aa0c5edfe5586ac3603152df1d4ddf5ab1b9a5b636cbb3c328df74dcd1be00d5ae83153a78d161b799e84b9b394891430e403958e0058d971fbc287929000702843854f5dbbb4cb72da56467bee948626b39c661c80d5aff7fc3ee06211be8b0c1cd5b5c2c26bb15c5629d7e407fbfd0869c0d3a358c514398b0862e350c551286f90f47031b210d6668508186dfff101a1a98018b19bacc50e5eb8839efd25e5ecdcb4d333020835628830ea10c31c850f4d5be8236d0d4550d5195a5ca15c318313011432e860d609803862560c0c1c0c0e5bb499020e5ed3d9ece96eece1cd69bc4658e5f1d0c162c83e50b48bc90e505272f087121cbdc3bbacf2b5bc9c6c18bebb962734bd9ede482052dc4d1c20d2d3461610e16c2b050020b1bb000a44b01c22e2774f1e1d2e675366229ac1bb13dd3b4a7a5e696c2fd2e792468660da3946faa2487b90b898b932d7a6c59638b9a2d02d8e264853a56c0aabbcc7167b2244fdf6a07ee1586b4bcd1c2464b172d4fb4e8a870c70f2a2c852a8090421cf54561fade9ee9a2f6f573d52cced158497176ec7987b9e5e97ca9de3b5a95c2e6b9353fcd8e83b707afc6e6760a96e535352eaf997f578724ebd8dc4eae221d7632b7d491ec6a1cbcb25b51efb25bd10e67f6dd1b22ca587267e7616ce24e5c96389eeb3e5cea868832df53434439c84d8d8590e4b31c22cafdf4613e44943970731edf4344d9d4badffe0d11e5ce7326ee57f01051c65a9bbc21a2dc4fb31c22ca389eeb9c24ef10513e8788f2ef72a010923c44944ddc71a5b9f91051c662ea8688f22e0fe1424872278788868832ae3c7d4344b93ca5323944944fdeeb4344b963de105136310f87f5ee976796b7e338fe01b7c6e1a86be27c9bd49793e5c63cde71e5d56133981bc27197eb2c15cc54a16c2c86cdcb75960ae5beb393fa52286ba990d804ce64b7a2a87e6f51fd9abf6dca8af4ce26c6923bc692bb4c96a4f3d89535aee47025caeff71be10764a3b165d84ab41b567a048d812fce9b66e6408ea536b6b493c1d2de0b84b2b15854bf375f50dfdc2fb0d3f166caf73571184be636aadfdbedb8c7cb308c77b01d895d514633e35c39a375ebdac4c489765b6272b444a32330cd6e4c966a4ab4b6d4ed91af26fa8a945c94c1b2272aceff88cb5fd6e9cc7fbb97f7776a5add3d58eadd336d6c46ab99e92f47f5cb6fcfccc09c33a770bf3a9d9b4499f9a8a7a82424504102d557acf78c840c721699cb07f57d41599b92a5e2a21c2be5a7cfaeef6d7deda1f9ef55fd7ab6b35f1dce057baedef3f8cdfa7f1e99caa3faffcab3ff95b7e4bf8a3c9dff17776f7ed7b41b773d580d9e0143f90a041494548d90783ae736ff515a7905527eb1ce5076bb475f2e2b927890498864183289fcff6a84448325cedcecd9c5dba58f1ebba575ea2ad99ec72051e7842e8c4ed44d904b53cddb67d602b1ee3adf7acdbb73622ace776adfa9c5dbe526b93f360c6b8275e1bec059e19e7042ac6461e58c951e30bfa8505a6969de71d439868456116a0050b52a7d8855e254d1a14a09558a50f182ca1154aa7cddbcebebb4badb8db8974ce8c94d6cc79e477d72ccad796e71ca11e21415c42948532818018dbeb8d4970c8eb080f894c49395a71f296048a9bf0e8542599cf93be98e4999f91e8fe0deed37ab525e4e7a38b989e2459420a230450921ca04e2ad0af12680db78b340bc01694aa2c98ad85484290b97386c6e1c2e868496a54f4aed3cfbf8dc6a4b3e3e4386d40d3429edb9ee97ab48879d38362fee4c404498238255b9f9a637dfdd4c04aaff1ad4358940e4ff83b960cebcb8d3992d8eed8bcd494d4e4dccd79e9981dbc49ad43bb779335aef14a6789b1788758d4b2d49b5966fba1467a9ccd20baa0a952a2e1fc7becd77afbdb4c4f3d55a7a2989315752bafaffaaf4fbda332731b72d51d2f9af50b2a090f99f9308a50765498502646b2d7d323d21f364ea959e24119fd0cab1b99da2f4e54eacb6e8848b139f131e7b2f2ea948eb342f2fd27e220db869409292acfe6b52af2cf72f842842582184104200a1091c4da2109bb8f05f5ffa02b18bdad7d4f5b96a96d298bd348a4da6262f2672884cb4b699b7be3491c91626529814bda2f4e5389d7d4858482820e99018f8af51faf2bc39c612a5af7991c423258eaa8eecefb652946874c68804233a93f3ff412fcea818ed2b88627ee998631607afa5b05b734a1d072f0ef39b9bd987aa32ee1e904a153ae35982f55f73bf4becff9913972cd9b6253a48342a82c0e6ff65b2de6f7924db42d68228db1fc08ade145151e4138b9a8afed25e9677ecf82a794154b24509d0ffd77d448c85896931e6daa2075b3c30c1830d92b4e1bb031fc40e70073a446210392112e54054428ea8894804fbdfbfda2d7596c26a59ee18a9effed5b2dc3f3af67bc4d3481c407971688c212e434890e481240b24412444840e206445ebf5e532854ea10cc4a03541565f839a7a262dcf6210d5ff2cc8f6ff391168ebdf5e8ead080444f55fe91581aa1c89e3089afffad219783919e4d7d467b9a5ee91a96d1e713a52b134ce2d8995cb57a0ad1689b03e6056db0ceaeb824dfdd77e05935784b96ab97754cf9c8ae31a2394ceb3d11c5db573acdbe2cfd6bf5c74f663f5ffe20f95167f6cfd3cf2e3f249f37a5d26a20f113e4fff7afb344f8b3e406ff3f9e7fbf562f28a4074a1a93c4d45579877f5a0acc65044174f2d6f9f1981639a7bf75f0d887591208a9c453610379043dca0890db46c30f424d67993a0f1af168b592a3e35d31f4a49736bead371cf14897041248b480436248c2154ffb5a7a7e3a82e45af7939e517d75766aab9781687040d59a027eb2b25b3a53ebb6dc92cf61cd1404b832fa20651a20656340842dff8ffba4dfac2e11c6e9bb8a89c897d9857a1b1dcfb578362d8bca645caa04d062f881910612007063a88184c61b0c3a047c4c0820bacf8ff0f5ebca0cb05572ea0a2103942d87411b2a3512c79dc3bf6c2bd3ca4f2285d8ea2243633def1bdbbd8d7bb49a3b51bb14214b0c08c68410dffb5dc9b52d102a50af0a8c0870a441029d8a35a8b710772dccf600efb76a74714740a9c8814bc441e3e78c21079b2441eaa0a65b1addcfbd7cbbd6d531737757d306aeecd34c19c09d64ca0459c60e8ffcbd24742594b652483b8b7f53612253842022ba20440a2688688a5b099cf2b9d16659769751cc7308e182a218655621882b8a3c70e13e24e0ce2ce0a3b48c41d0d2248f32486e1da35597d8ee890fa1a39d6510e411f22044188105481800251e78da86346d4b1a2ce4dd4f9213e50467cc0870754b0b81fae64d4b40a65a94f858ac52c15da7daaef42a13ebbd3dd7de810f10110c4079644076a70a0141db0406ca08fff11caf672ef1f2df7fe755c2779bfa54c762bba9c49cfcc527ab965b7a2cb316e6b8e4b1e6f9b38736f2e2b2a37c7e6353bc7220329880cc082b911e31dcd7626932d90c5f3e20256e20253ffe20274b6801515b8caa2025bea2d881931c896204d82182002c1128178f9aab798801e62024d245042024e0944202220c78c8840504420c97f0fd5992cd28f68de4bf580350e10e200de01444403f830e08c015844034ad100971a05882940271640452c80e7ff672ffe58f3238cf843ff18c57f437c23c4078acf81f83a5f3766d273fd9dc192674aea2b85f671c11c3ce6383107cb9c2a73329813f2a1c587152af061850f217b68fdbf6e6c528bd03eda6c2dbfb406617b04d1830c3d5808f540410f233d84e441471e0408f3a8210fa63c20c0830d1e5ff050c19cc23d3ddba419caeab214ce45a96327f5b9e34568b0dc5ab395668bcb5693ad5702da4840990470a9346629df774c71c70d77ac7007d21d3f42397bc8b9928345ce939c0be4e8d8f1851d63fe6bd05218ed99f5d06de619ad3db3a7278a6f60b6ed7293a0295360a4632c265ba2635291ec725c47b00ed7ffd77be940d32fc7744c753a9efee9006e4dc93a879939707324fd575a350eb135a8349545d458aa19000000500053100000181c24140dc8a462c1644cde3e14000051b47468dd9836d1649d54c81863600600004000400002775171a3e24156cadb10d96309df5fbd43f80c71507817fd707ca2c8e3c1d2c485cc87081f139026bb817ed313d887db583c563a9ebaeb5bb7465f92774d263f8d759f1c612a3d75c6e395969eef524e684d403039fded06032b9b8b89b1ad7039aab78d2d2f47692ba1e789b1137e6601a9be07bc8d895542b0971e36218b2f75be69489b84c1fe475eb70f9ee2f68d428f3f249d366b89df4583a6d4774b7559d03c9bdb48ed737f74ed6c625826a0006c7c21caf0724b86ad3fb229fceec5e1ccf3fd0df78cdd3e6eee6eedee483f8216b7e4a2dd6eeaaed2f7bd3bebe526a482e5357980d1cfe321c504e645716361f4761349d1818a174b657cc657a067877a3280ac2c82cf37b81a2e08cc74e7f20c4ebb55bda8aa05e39fb1f2864b60b424dfe96a000b7f94d84597095048a0101dd28cf37f4742b1717e4d0aa7bb14e47820446962155d27e3b9561af6014400add1ce74c2806852823a07782bd282522c52989188714c5fb60f17be64700c3be4e71ad3e92bd352738e569f5248fde93fbd02fbb45553ea760903b68a5da9b11740df2c9d7b33225a60b18279bc82351f7f7f974a1683920d4c981b42fe32395ec054b2949f6a39a3ace33cd21b2e356352a0a25343403a9037cc39166fedfce56c18b2b50eb9f6b8dbda5265a0e0bf4ce92e6aa0aa908736a8a57602b8a15f037d8171cc42a87db3094f7166539737a90b43a84344dc96d1377cfbc4f181761ebf30d4837fed1d28f2f0b81a23b59bce23fb8fa34a3fc048d61b171cf93ca840a4c69395ac7b4fad4e17b519e9ef94c1f3936f56fdff8441122b00ac6e16696e10909e7aa894156852d1bb91404fde7f1cd93c394617050641433236c309b9e31659f93dbaf9c143e3626036d248dc84a010620012796412a8d6661c6ca4a8a81a74b82e95e32a80f3d7694369ece556fe2148be8bcb1f3e2d4ee2f78501e83e229e331eae567489bfc48ead1b6133be1b837cc62c0e0de3a38a284ae8509a241e0ea0ca3d65b2d287c6e38d0ff6c5cfcba3375cd7cfc2f82f6f26596c900524ef851710d66d278b03d4903f76b00ddaa26ef7fcb507a889d598e915baa8ed2bd0fc63e68aa2cc46d96b8fb92625caa608a42be71154f9808222a06d62f4941b4907235e5e0cb6fc428297fa84e3914fcb8aa21f35eb1f4e3efa48d33287e4b1c525426070d5ecbf09e71ddd2051dd77b7b0d7dedda1b2cbcbb55ab504970ea21c04635b7f6d536c203f3d8a4cbead514548e382222dfd9723479271b35cf7887374495ba6a099ee386472ff770698215588c601b4dba6a8c981967ffbda66cc348bb3639bb1bc38bc1bdd7048808057c1bf480403ad0644329cec5701f92c861dc4ceae7755e8b3596465cfcc913fdfda9e5c348a921d980833cb0aabf0d0af2d233ac23c7f743e86033314c91709ba6756b364ed92bdc9d52003e54953eb85354500b251666f971fad0521fd20812e405fccd99035add92a61782ae43bb93141232aad6cfd8ad71087158ccae296b54447278c839e8536f0e86a55e2ad13e7c86df20524abe12cdccd9d45b144ef69afb4d6b1e0d50b99ddbd7187b808395f77aeb60e6f042c8fd21e501147273969bc6b4abc81744d90005d96c48a4b57bf18b91fe7c1d7a5258fa78b2879d15b9c6491b336d8b5b549573ee5c65930f4049f751bb77edb9bbf836980e34317c2314a1abcec9b11beb4209ee15943d8bff61b0b6ade42d640bd7ebe44ad29394855fc3fe6b19ac91b4d55002d62887bfe83fc7f431fbc3b5354f67e420a455fae7b4f572cd23e2d8a22cdce13765a36c309f4356977becf04e0cd9ebe9644c939131b62502b0676e4b20201db11e5fa8758c84a2f739c6ac1da4462c11ea5d729b9e3a3109166e6da97a247816fd0dfbc9a49a4f8996cb12f007c272c1dfe870ed836a4bf793e00ec2728fc7f1ed814d35f9384dbb6fcad0cb8eb2adfcc713b22b5b6f167a81ec187402bdb1ba080fe264942221a2b9067714f6e71dc0067b42e36b36ca7fb45cccb6739f75c0773b45bfcc395d0435eb8f8a125d7f1cdd1897982fb7c2b5a18919b3ee6e490276fcfee9631ed40c5ce0d822c663d996d823da258e0405d234af66b570e482f394e9590dc7a45afaff02e5bb4531429ab89aff67d69a376a83b935a9ca5b8157b7549994e1a09b8ab34bc5f63f2414f08829a599f4578deee8e044d68354970f442b13b9614a4ae3b7781411e13ad77fd496a91b72490e754019a7c78aa39782a073d3cb5efefa9d2f2d5905dc7873499e343c3e61aed628795593bbb4facec798bc7a4496508809e61e024b2b314f4fb35136312fad3a05e615c1b9eb706361f10d2bd919d7f1ba457288068e3db68386d44569b480dea4d8cb86702440cef39b136f09491398c313d2d8696d268582cadc5ad2df883eaec5d9a6c72eecac5fa2358745988923db4f7d585aec6ecafd99015b41fd0c2e393d81f081665b89158d5f39410969ae340685babfbfc2049d2fae3a3e76fcad774217616dc9d659dd46ea19b43661991db2d885eeca836e411f7ff34c034102160019910e831b48f54bc834c59e19962e952f6fc48077fcedcd2695dd280a687bfd30200ee1aa37c8448a0c56592691e48d459834139723e0c74c8bcb420a655a3e8d01f27b54ca76e627b8df283d809da70153c5483cb63e9801cad5057d7d1e846b512f6efcf1fd9b8a00210dd8cfb2af26287610d0e7096d6229e759c87545c77429723e4b9b1cdba6d30f74b67e46d8d73ecb0640e483c4007b1cca60ce7618a8bcfd7ca907090b16dd427fcfc114f471bfa1d20c8faa9c1d37bc9d01bc9e746a58ea6304698e5c5cc9faaefdb5e0af3de64831f63bf00451cc1bc4b68fcabfe1c62e029ccf3f9d82edafed7b461a2adfd28594cdc9a18e47af01d18fc6c6154cbf9acecec1d4d94be7769cc7f00cb771f49cf6463ce00169763628784172b58858c64f2ae55561ac55372d9c8ed559df71f0d0449495c81f12b77fb3391c31cc647d091c59c437387d3e0c54b4a1d3cc93572d6f4b942e41c83f67f8ef69f30fbf3216acf155facbed553df092374dd21211adf1331561c033774120c487b4f8880993e186ceb209eb0f2ac68b47612e1b83851d7c0ce61f90e669b2e3e364659c236871ff0ff5ac45d51904d0bb8515b40d6cd2c2f9acd54917957ac0dfbe845c9e9c915a753c5c33ce3b6ca9297faf3d6c8f896c91c1a58492b36934b3ad9d0e29898e4353a958e67a98c8c5a5d29c5db1f9f28ad797380010600a83d13176b3800a36f1776b94916e4d9b2b86448587a40c896b1e266c4648e2ba99176ba86e42b251df4d19ac48921d7283c834d02be558bf76b9c06ea22fcfaf6a8f7a3540c46f73a7c2d19dbde919c49c1f6f5ffb217fa421fe838393009cda0d358f2bc242293f1c0268eb2cc6a534196bdcee5c5358662e5ab1db400c9441768d050bd24ecbaa0f426f437e03b94ace7124ac2d667106dc9df2e05923249cd50e949efa52ce557ad99edef7ddc48831be8926dbb9d63299e09a350d4acdcf7034745fb19310180b52485c68306ef97d896f010ddc39012ed162bc5c07e5b47d2fa307974227a5cd3ea77b1351e0913563034fea7429e3f0aafe13a349e4ea1412db71df8200fd5fb4db617c34b842bc1b500a60c53153893bc8c74039ba79115da275b0d949c9f4c90521b9337972eaccc7b2c007aa5416e163665b51d3b22c405e61bbeb2465196277331caef2092e6f155e8b4ca1a8fcab8415f3d73096f305cc4502bbab3503cd0e90acebbffee6f1b5d5cb12c8c37cabe37e2ed497a4c9ca3475bb2a4c285638979b04adc41ea3405475cbfd9afb140bd7f899c855547b425dee813824694dde1038c6f22ab98cf4ae5923ec5709428eb27eaa863dbe40fc4ff665edfad82effbb1e811a7fd2141a9c46849f63063689aae5ade2d51bcdda66e335856cce2e66b1385d372393463290cc4e9a9490bf7843ab38953d2ff9437eb9d27690c0cc955b9ba451ca57a628ccf47709ae496133c905cd751494ee9a4e68ac14618c51c11cb1e6e0b83a90547542073e137bffb261c59008a99f9ed693070adebacb8409c5c4bdbd3d27303f4477e9e2f4a1cd4671b03836165a42d5a18b7855600ce7183eababb78327e1b097d06df400a421d9cb6afc5d5c9766e943f4f2c867d4761d840f4b4e09d6cbd0ee7ff11a93d81a7b4103dbcb8639c4e2bbc198667a3af4413f0fecf1b029d757b60d9da83476a2ae7d4a688b5c1b249e3b0ac94f3b97d71b690398ee64a46bda2edcb2981d1dcd227cf429693721c9efa560e9fea303ae39671d1df9132ad776aabecfb7866319c38cc495ae9568c30100ec09e02cc67353e868ae452f3f8683fb73a630ab79ae08dabb7ff4f65b683e4423ba5fa1abd67126c0346e2c80c77f0f634ac16539f8a994cce7ea77e7f3d21cf518b871277fe1923105b6bb9191e50595aa81c0604eb3c57bc109927363ee99f2c4db9ddb7af2e2bb166aeacd4857962b18ec4e1af6c5398b7e2c97edcd1853dd1798e86900a7406967194318f950c7115b2277443e2daa6d1c028af35fb91d7268857122687b0fe6a8b21c962fb1e371d6406b390313748dffac8394ef2be8e7a8a5409de1790880aba4d90b33f9974800c05cda41e21d43ac572d5d9dc1d3d301064d20acf204e6d44d8a6217b63a8c7b095cfb5088f12e2821d6d61719d13a82374022551b8f6bcc56c1b502b84eb9fa0deb0814eedc76308e83c3d2e493c2cbe96d7ebde5269f457f3511e7efe6aba1db8e6b1a106ef1f64c5845d9d7cf099e5872bfae2c9fb752a057057997ca3a0a203e12308d67fbfd4ae34dd9bd03c9fbc1184e3e866f7101569c4357808242985f5d793e9ad5cf91b69be5817c2f2d4c8f01757861d62f4bbef7bdd043f8f8fe9ee1fa8477ac2d2da62ee0cad16e9aa10ea8a989c909c7c5e48dfd7d5e500721d07a527f4f860ea3c92b3a65223646357bde4d1d111ac009c065ff50e68d6974e32227193e0c0dedad1e35853ef3761b1e029babd9cca9af399e0061643c11a65c42af84f2f8e3f38aa69d045a7f38cb18302b5817eabe5248a8b8a0d5d8a3950abe3ff237c4de42d16d2c614fafe6f3007bdca9ec0d2e57a4e82870a06bb56bbf3c1329535beb2c1773e0f347607daf93c2771108fe77073d8abf04379c17bdd89f3fa896df8890853ead950a3b40b1d200d72ae57b999796a75e3aea310c8a78e7533ce3b9467b9b0ccdef0c506464d5d1c46b7ea7a4d48d721375d5eb1b055dcd6c676562305f499efe83791985f600f54dc91ea511b0a93baa7e73b856ed206e6321a3df0386b1367e058292818868e4bea19f844bd6f6eaf38896e568190de16a487086bf3ac279e482b0913d5a2b4c7d0641bc48ce918d1ba6bb1c7e9c0d58a0eabad985efa2cfe7b04bb23f5d800d0de0ffa4c6c5e5061c192d4673fb5dcbee650739aecc817c64792f6ed9a97705d75d475b0a908aade2948910662f85acba260d617191717bd2f8326d92fb1728e93198d130b6006c25fda320d32351f912f832869a9a6c31af5d04a038160b194a0480f692195dd948f07b848ff42b3c378898d22b7e33d4f8e8f29a083ba70a5ba7be9372ae7acf753e42f5ab88762d25e0810f847d7b84e5f73506562bb5c6d815c74b84de897e8989dfef4330a7c0d06ab5a52fa8fad16913d2df9cea779553a35c2e7f41c6177108fb31fb5bf953128530674d1d37c8ad88b7c60a051af84d8437a9491fc8ca0311e78646af93fd18521f3e609387b117d4c0207c35d0efcdea697cc0ef4c96bc7cd639c9fd21619209f11cf635a92e17fe39df11cd2a0f68d87eacd8967e5a0feda6e7a7b18ebeebbd82e5521f98aa9f50763b981c3551b22762ae161805a1223a55c5e919f2f36b4272c0bc229fe64781a91a46e0e96256754f077e090855b316b7c336060cdea31c33433a5b1c6ca7fb66a2ed8ab44a8c6e5196458403d36aa07d1c7f0d678c632fa7993b20b0880d58e611b801154ded240fe3e649f264bfb04bf377d89d5da43e14b07e113b3770fbe69a3ffcc863133f826537c62f555f0ce22ffc9d84326de3592ff116399453711b6978da7dfa4105a5c2e7518740990d91f297002b88718ec311988a157922a7407476925ff325bffea886fe94e54bf5a993d0d9ea1c02c81682ee1d388a08579d75de603b53f57e78d5d53bf0c5927ad7891c9921482b5bf6892c995e6810109197b42db6f3e2e72f8563c989a865370f050b332209c40aa0b273d0c923bc440dd7c1708ddb98e1f1a3174e25ca082a96607728e86c97b666696c4e776430f37f5a754ea6f5a73648f2462bebaa5167c23ba80065d244cdb18b10fef334d91d1969d8ca27721b25e90b1233e8b44b8e4894912bb698b6d680d5f77dd81c4c02065586147f1be5384c36824d550cce5194cbf1c42d3925fd0cd2e816ea209b06bccbfa55dac3e78ca5a4be0e3f33181e3c6fd9300d9031608c73927e06285bb8075bd946a51785dd4a9c6d5d7e7e32768070797707b9a70d45d5d2c74f1f708d0a69eb50c231a501c6702639cca7f5c09da976f8d914230ab5c5ff788fc1e4a39481a49d737408d115a534c344c763d94b459879174a05bc8240b439153b372f0d516f89da1a804df2b65a78611da9d1ff922afccab36b82f6eb30e6f850998fd335afb9d29e4ece7709b55c7f7bf8f5413a0cd236e3edd0187dbcdc9455eb35bf0bc69d785d5141e92c219ca33d09d64b4c14ed0d58d9f02233dd4ced9a379993c0c8541c35c79c1af4e6c60982dd8cb55899a787534a69c72c00822bd0df36734805a65419fa87c7103fad03264e11307d06e614c1a34aa0884ad300dd5e5308000caa68e812df133ca6f14a8abaa185ede3ebb59041f6c566af611c46d5fb7d2b24b1e0b92d9ac577007b8489e6f41927a38fbd5b8690328c73b8a4e65b65c3c01c205eabdc3207c167e07b08b88a2b7b26b01fd1725cff7774df0920f5b76275d8fa2e0d9272494260db19a2304c01aa9ecbf699a21e7c3f6532cbd6ce926fbd4ba2c3a58be347304dc97f99783cd174f42e53eb8a9e2fee9dd2a995ba554d7d4e8ea475725390888cbb95119d9012b2eeb8e64f749d9783c902ea548d2bb17160b0e9c34ed05e1af04ae9403d7cba9588b0f8eb7684796e4b04883fb5580f6142338d970303248f4af43fcefeaa420f86b4630fcda9d6bef478c15dfc0fb2e733f16cc0848d3660bffc3443543c2c15ddd13e7202260bf07e6070f035cf4de1bc80a33ac174f870f8c009c64a57c4d4e34a3a62f5af16289c8a69550c1bbc7ece2631cac9a2001bc1400549b4138a051ce3c0f654e360fd2c0dabcd50b0b7da3bdc421fd7434b0f43cacc242138f5486a040a41b465cdee75ee1b27733260234e164e529d10e187ab413793dbc35b3a1979ecaf13528bc6e65e358dcb53c725de6e3d370aa58e04f6993298b87adb167187715659b2e85f755dd0648a3f3f5f009cffd81647764e134f8f32cbafdd79b00764ec24b02dc02c582b23fad89c39ac54d531bece98929bbfbce8c08f4adfc8ebc0911c5988cb0df10d8feb99ae89fa8f77e0fad3a8228d2299db807a695632a047f8457efb1e535990aeac15be2d6573cdaf0f0cea54c7f0baf0d5280815c605470119add45f0ba2cfe6af5c18ac63c75377891f458381eb3a14cb4ba9ccc397c67584e41a7ec17ee2207d48dfff7060125a1006fb1a2096e20e94d75eaaa0771c567ce85aec684b405ce200cf429c702508b899c74eecdc95ee08005d0fe2feee3b15b07728535421f9ca401a27336aec80bfceb6872f72865b07b0f343909ee7457d4a96142dceeb161a727d23811c0b71b986bcd54d17977f1996eb6b293b896232303aa8200c3f277cc5a79a465e127647e49134bd2b47b7c6fd1e2ff863dcd29d77363deb15f098ba98d8ab34060ee7b3ee1e16edb09f83953dc9d3202763e673a0653aae4a5006e39edc963045ecdbab1495c2e44b36dcab62694f35b99f3552ad5d3af077dc0f040ad48206eaaaaf22020dce7f33153a31f1f1e2cd3d6a85973b15f320ea047616cf67d2007e6d0e8b924fb660fef00f2451bcedaf860e5389474abdd8f06e40c1a7ffdeb8dc6c189c1fb6c74ee7a0c726b4f86cbc39ff1cdb88584be247c17e4bee8a1baeb82fff42c1fccc3efc857047e721870338954cc167473ecec22a7552e3b16b7d7da5b82189fdff58b3598b892907ffb496f919a471590b04106454b22e490a7027e449ae3379193e4670891575e9dc661796a7063ff789883ffe7b9962bb92f4ed55520a14acdfe49ee9c563fcb1cbc006097f2a2da17edc7a04e07d17137f3e483d29f0ea420143c9c43ed173f52a292aa941c611f9f579e0282501e701c5fdee386bfa7e5b12ed140bd15be54913c94d358d95405402762f0c4eab6e2f2c21baba3bb722887ef0ad7eb5157ff07b865ee2525f27263101adcfd8e7c9369304c8c2bfb4de777bff0387e31361c5e28a65ebd86ab835e89b22c67afab7c76e7be5fa8cc787e40348607c3e0b3cc394e1453d098fb14c9e8973f5501d72077cb3aa1f82178b461a087430fc62f06163ff6411eabd8d7dd19f4c6ba6c274b1b1b016edad5c87f6fd16ed7267d70777ef19b19d8d40ad560d5e1739eab8cae4792a7b5d89812ad88e7c475a8459d69262be10ec1f91c3af1d67fe77a6fbf7f0d4ebe6ef0bd166de7dee03761812e2b1858fb944d7cd5a22edb8fa5ff83fa1be9c5ba8b8e549cec2505023d195758713388d22c7035dcd4354d7aa33767aebb2a2c587b7f42f70e337791afd9547af8fc8c4bbb3f80d2f36a96502ed3e965fdcb566fea2c8a2253690b9f5a3801996887af6e44c10340469a7e4149407de51ee2e9eab83fc854b5612c13d0140c2e4d01afa309a353d007e0442283e07ad6fbbdf2507d9cf056ab1de56c919c71823a43ad718a1e45221dc2ebc43873db969d036e0950df683d9d3273c08647089cabac97e551e043fc87ee82edca133daf03c00070603a4fa91280d6c0366f347795597334fb68ea882d87ee1f87f4d1a9f638429c3a75637d78262143b10903ac4123bb1e8e36a56473c15d490fa19066d17e9c45aac5c838cf4c8ec5ca20208553c672184ab065802d4c959d3c778ab4c5d1bd93f5fab5fb0e5cd2d12dd21cffa1c1bee77e29327577669c64f005ef8fd06d8c3a44e0f032b233c7fa3fecc6bf2ecaaf87948e82476a97ff2ed5a830af9df585ce74a097dbf04e306ddda7926c2217eea6c8daaa92a33f95799daace88acc0b9aebf0f85cde627d9a58c003821ceaaa52245c59670bf8de4190600af0283af4d86cfe59a8c7f90edddfbd9ae15ea04021f3c0b8bf9259f6cb9a454847708755e370094511e715aea178c5d3ce2fb3afeb3456f891cebce1ea3e768a49afc4a7f6828d8036513cd1bbb5d07a327af24af4c7d8daa816a3c1a9be94ed60f70235f30ae3f3df0e469eddcce81bd2c8473c1f8d3ef02ee93f93367e53ecd005cf440cabf0203c08b06cc135c0e892ea47e571af10615223522013ea483b35137b6c8eb492810c6fc6d2c2dede45c22ffde58a3902cc5be61cd95100c3ac9ccebce100a3ec1c315640abbddae20eecdcb04ffbc9c58b7be1c60a40ddb15cd640b1b048b6cb462bb1c0b055d29a27d1391f7edb4264bcf52c7c9a9d4ee00e8da32a0b6876e82b11f41430552363e3eae4c4435218d95a09deb2f576df0efd71b34410df5a81ab656d6508814cb4702e1b0ede6fd08c03d1f1499ad31e046632a0472d03297d86444c3c6bfda8a4b8bdb748c683edd71551b1e28135637e064f00dd2720206e471813d9908fc5303d37916a54fe4509790419baebc00d33afa6aa037eee719b95f8eafe38f0f1267393fe60c9c8a1ed911a1443522edef65e21a9f4d90068a878b7855c1d50fad3ad9881bfc14ca70c4602352e27fc991ece95b7be797939d6f2e79e94c41bb70c2cc7e7433d907dd7ef882f38ceec0a43e648b072b19efafb84e60be3f5ee478c235385ee3fa59a1d6a324781a1b39fc67c9d1a956163e20c2fb873e057ca0703f6e1dde9acb60bcd2668b9b4a21763feac09557918ffc4f4eea0a4f0d9cc44eed83429381337c8712718eb9a927ce6cc0293bf836cae45d535a45159196d4839700f6cc604f3d2d8c584c0155e30cda8c8c01717fa0c7e5d9984c4c14856f7767a6f730538e669de833de781c1b271fb9a232d15700145b955f363550b57452a63da8dcbe91e46a1216106cbd184e90eee6ff0d15acc2b25ec79a74aa4f71d6b3a5005fedac263db7ec10f5071ab64bca6b59eeab8e7e035c4ee68396e79c28f6cd911798ed0d31fe99f342178078322a49077089b0b03864874150a5c689190c20ff20d2022679d1fa1c6d34f62e26ce115cdf2599e9cee7c1793f4dff71a43a40a56effee4d863233d7a6241d1fa82146042cca02ea20d2d1602c6eae8f158c5a0a60be1360dcfee05a38f682a99749ef3ddbacad74a9d813a1248a4bfca18e199e164b58a4e919862a287aa91a63fc07f7a07e88d0bee25109f496f98955ca29a89738fb5b3c33a96f1d3ad6b2ee027dc4170a339cc9b6e0713b9bd628f098fbbfbcb1a3ddb9436628998e592f72548ca331cc564ef923f4d834cccecf4595071a8c235c1fde16ed8e6823b9824be2b7f030fbe256b0dc35f76eabea2e804fee81c82dc47e262610a7697e2a5a4bce754320d4d44c30859d1f49b848b0403ac81841b9c660718ca144d3a039da6e17c39375be90011f2099cefaa288f46623856d77f6587688b8e38a1a8f5c204ae9c2d88ad622e89ba77061484eb283625d18aee09a88d852db82dec238b5803ba6a9309297e2a37ec457b29273b2be5218752f5f77c2b86e755a05613055ea2625b407018b5d088328b92e49408263f8bc8fe4ff2ac70b06d4006baf4c7afb1f389453574bd72162b12cc8e7f173226d165af8735e024371b730f4674135335e63f0b25d131294b529d9db250a9fb84b16c7373a116b8c16efc1ef63525e89a9063e09d8547781d7a24126d23bf138049d621daed9bf5b70e522760cf5816f23b2031adadd6ed6378beb1d596f3f7b6c509eb57dec36c6f78decc1c7b4c1eec68941ecf57ea3d5881ceb14220d00d948b310348ffdc03557fb6184df31324ff5504c56f22f8c056410f922c32bc1990d1ada45aea152e8a52aa58d591da4c0562c8aee15182408f5172f8f5a35675a6776610ea051930b52b39d65da2d341b02fbcd7e84508cfd7e0dcbb5b4054b936d41c91948b89c00194ac8f23d087ecdb4a508ac1265422edfdaae7bd768cc67fd19fe75b43f70a8bbd913895cd59899e4f020a1c64ef2ac2cbe46064637873425782790c2e5ced53bbe8e83ac5f56f15de66ece8a748f15402bb7d32bd70246106f97f728e36baeba596034846168b81921bf21f90e3ade509bf411c9576281979e61851f85e5afbb1b5f591b0269582223dd51b073ae5106852a4dfac27e953fbb4fb873668aa015113c7613d56bc3524fcf02918f95fbba16661198676028f3a0229a68883a6ac6c8d8c77e878f73fbf84f54c5ec37bfb9f25ff0aa00f76f9a96217d21a23a3d627edeff01e39000f644e6bfffd25fd1cee1f1bdc3de6bfd0010708781f28d2bb777de718c22a6c5e4c6189a11719d2f24a18b55fbd02b3db45270d4488aec686b5f6a7afdc2d132832f6b947cb6250a0e82204d31e3933ea780b8e90b4238a3ceb483b0ffe4760b0abb85931f74f3211c33cf486ad7c7b8cd71d6662479c8487b6b92bcbd9c81b3236b497911f132d8566d0a07de0f8e6cb0ee0c80bbce90dd211bd0439d123506de95391bdf26379c641d8e577281d7719aea805e5a0661576ab69740fdb5b5f578eb5ffa614b586c7bbacd7269afa70ee2abeb92ba1587c5c923aac7b87512589eb92d1bb1404c9d1bd2b4c20da610242f4043a7628c21827249f6ad51ec799c2018f0a6c3c0b501008f14ee6942152f843569b1ed03d866d0945e3b9fb819e4438006513695915a3035e1158dda51a6393f7be1ad08ef88673315be260c55a88e24a89dda96c308afa221e0a10d08d933006272088972d2dda687f27a873311c30a3de6573ae3561e996e4b0b688e29e6f40b32f9481d70fc4074c975633cfc71a966f0eae175e2e74e3026fe4c9bdb1ad0734061de6d7e0c52af17648faff0b7b861f19a8c50e6fee04514f4e4d2095157f5366ab71a00495ac353a5ac6f6b0b0c56baf80080356e16786042c5e72dd5e29da871dec73e6a1f68148c7117a84bf6cd00116143fc865b4d431edd449b5a2963e9748e6d88908f7909318cd48fbaa201dc64b87d518596ec00372cd3638f7fc326b623a1922db1a59d77be8148428281aeb063b60dc8e0a91df3f2a49d2304575a5f6d9c2cb8e7baaecbf6f5961d3117436a829a43620b859a8ac4de1f2e79f72e30d08122fd0ae2e3447e3ce626d4c71f1a7a7c126a4f55ad6fd380ba9e99cca42a759bae5304d26f4cf1b53d88cb112269b547d035d482990125688a3082401a8f1ce8e3d20ebc5517888c4d180d44c5108ab013f55106fe1c8eeb4f4ad70964ae6dcae3b82c73fcbddefe80dd4280ee3818bd4ae4bde751b8a3a0747d921577ad0f7936019f23378bf850a6501e50b19344e7734cbc462e0f492c2d36343c13785988e617429b96166108c12920791a3ef18b7f5e252631831a9bb7664391a998455aa7d742fa6b49fc52c7c759d63e36152ca18c6ab846148dcb351b07606333ad7668a6ed69f1d6acc9a6ecfaa8515845469a426d64dc7483d4d90b1e35e14386036d680bca3632e9403f385c2b463dca9799a3e57cc31fe40b633744bf5f7cb818c5af7a8f27c9a4d88b7daea99ba1db01bba02002d2bec3075796991db30ec1dc2f98929c0ef031b7446206c9454ad111226f19614f391eccad26ca805f039f6d3a4d712528a00408045172bf502b5a5c90488c4f2c6356061f01ef33fc04763c086600e37b65f581bd24a27c9b39a7f0c71b9cea5430e36e091998b19afed0741a66bc5c06089689e8111aefcece76e8a5c3ae27d1913ded3d60f17c989d019146871c37bfe619287d227453baae9b2020f46910b821c276fe3266707a64679050650a22e2133aa7a0f16b34345144c4ae87134dacd0e1ce3abfeab8a55a7f9827b2d0892a369d4051bb0c3cbac10d04f254f0215cd1cb4438293c497cd186130cf701ab42e94ff165ffc43310fcdcbbbe5fc60a25a8546e4274199d30d213708a60f739f2cad37ee467e370a3d517bd96ff405cfe44406081334a00ac618c80a681892d6c77852a57ea8ea3a0184b8891f25277d80e941b4fad9b3959fd37e1b4af560bc46c08c868fad56c0d58baf73e6466d959e7fa4047aac032ef09550d0c748b045fd1799dd0d5a3274c272c7291af54c621c1d35379f8b17b5ee8ccfbbe94e88ce136c3d990b9c0e2e7b2372080532e836a6340f730e95dfe9d74f031fde53835a19943bce7b52baeba50b8a2111fa65be1e97b5b46c3c0f2d16105a191dbb0ee22cd5820c28e8020bdc2cc92b0021f5bbb78e8d5b1045b43c482107b6cdf2d0dbf8f9ba124e761136e84a1ce9b0094d484fa620957fee615abcf8441348e477fcc64bb493316b0984de66e4d773e620a22dc8919621606c78ffe63b4d3db468688a05d2c5060305e203ce78059c49118577d4548c462680702fb76910eaff70e27419cdfc7b1054d1da93677efd4ca53a0becfea6f2d33880c4c117e2d057e322a7608f7ae3323eff09622bbe1430ae4eb25f090470ed5684ad9d119235ebcdb3e3c2724f2acb589d57fd55feb38c51f5ec2343e1305a75e0660c875c17f820675af3a382e2c3e47e5347f4172610855131b863296a1f0b887fea519a57bc76aa6df23d7fa723d3a749679d5f599c76d130c9f97a66f8d83313ee7c0083fdf5caa5fd459a72ccd671c3d54cdc681cc82f6982ae84989e663caa45ae7346adeb36bb7abcc2736b763c7d827a97c2f701992f11b90791307ea793daf7b20d0b57aee07b39270aa754d980a0b600946fda80db8432f6dd1b420ac08242ca0196960d12c2a00719c1ae439b94d43082d2624046a2fcdb36a93826956b827e1c2e6ee8aef4a5c5c2d5442252e5fded5a2fdb1715943b656dc2e84643bbd1d7e85a66d55d6678c1314461a71ec7635e8f94acf22c15f903c2fe3df71203d07a2bcb2b58f76d9b74f5ee302abd2a0cac12ae0abb0ad118db49ee5632a6e3afae38cb07831e5fd6243aba101d532e58c70c5f5c4e6f9a30a7600e75254ffd463fe55f947ca6d2af283cb5f3c4e7cf1afff93c707c4d037723a2be99b4bc39e9940d1247c9f48752772e23344cba813cc76b9ef7dadf50b7f51e76744be74741017857c9357cd9c09910f34ac55caa1b5a768163a6ebe8b289e315c090769dff8c983ec2d66e65a8b3131cc81812d8336c5170671867990015d7d66d12ef26c17c48d40d686fdd8bc8c7f07e87d4dbc7583a7c3fc85ad396adedb961bce0d497c2384fc8432ef0ae40df9b45d5b09e811a1aa5152035b26d4f396c54cfa6661389b37ec2a48711c33dc258a1f53ae0281633a98083bb118a7508d114fd003156cfab92284225f7c9f2c46db167386f730d7ed1e99508baa14c300e337013a6633fea98b4a52602153186d869acc0b44461b8face84c3be8e1856d51b71f38908bb1e57b09aabadc53ea2445fdca47913840bfcaf2d2854a0f6a18adcfdf0167d4ccf450e4a891e3485f6c58837e983a25d310392a5ba70acf99ad481f1f830a3b256ccbaee8b8227a35e8a58b28dc0d1da29a6be70ff16734d580b26606c1bdf3afbc7d2bc21ffa79928e43bbfe1ed7fb481db9d1b1cf91c7d7abda83ff750db30fc7e8bec7cbfb369cb36fa021e943b6b47ca28c9c56765e9f7c61e5d5110d898b05918bc778c726a64ccf68a60a4d446e104a32af659fe3eab134fbb3193f83b3a9fd39ccb32f2e2dea2da23cf9abbdc8f15f224983506056f87469511bff2f01b18f25c9228f152fc2310529a28c5553eb71a7a6801aab79a472f5800c4608be08b59f9e7152e3687309383a2d174abc57ba9e7267c834e657d741e73709fd77f76c0289d568a7adce14331bfad9a0f33a4ca976804efe23db5ced0182e968224ce30c924bcd082e995de57faf2573d4fda0ed66eb3ae0d759865d4b7741dedd3c69981fa17dcf3b8ba773bfbf75cd2445f642225fce3203d24f965ddda233fa41d66710b6f154cd4958f3a32a819881fc2ff1ca757d3386d408db447a20b68774b0820074fb97481611ec3bf3e264b01481fd107025c770081994c532457386e1dde683397b8f28d8307af81e240eacf4ca8a513044dee4ab9ae80e89c23cddde6ad871345553c3cf5a80cb29106d71839d1085a4b710fbdc5bc82c37f033d2be65661c56f33cb905bf0c12b4bd18c8e6f786430357e35daa5693099d67aa69c036416c37519b933c37ffd02a5f63e7090771abcd90f7d9e01bb9a3ec919a7cfa5fe38c82a80a59f5ad1ada62613959a2c1f3a7a0b7dcc2e5a2a32fee6e3a8f58eacd2d840b624199f7d1c640bacbf639708e3c9025d19c9bfc6794b5c0bd33aea9a67d79897e8c0e48f2854e44a3f0f6a9928c3ae400e01afc26acbd75282f76e5024b7af66fbd3097f6081cc020d62773e03438bf0d1dcff01f272f36ce1de013cd8e10b1279a375dcfc95bdb1fc8361cbe102636390cea7808793be4b2a13744110a504731e6dd06f0b740d556ef5891368763ffa4126389fcc256ab8cb9fe69d47493a615559a3cfbb059f7eb8e1d77859acee4b6c48a401b60123d4ef0296a888fb67afacb9cc5da7f09a912beca4a62b023f10b3b64bf9e28138d9c01f87c3757cc8b3cfbaac6358feb11cf8fcea8515b6268c34f672a3e83de3ddef7f2c5b5488546d26108b28e01074f50f180133bf2ab757ce0aaed3793727f2e987fca522f29e8ca35ae335544a7b9cde7b8013cb9992d660560b5e3a603f48907773a405e70f56685b9c36504e30181c096a0386a98a03aec70705033154efa3aace2c4f39474751e3d34b084f692fe2b0069179f0446d012fa9ad591608cd8da48a7d9c3eb06721fdd00e020618ca20c2e9ac66c59777c38088cd9ede70eec1ecf8ceda63d73edc792cd4414360d684bf57241b5d2dc6b81b5ee4956a6313fdbd41ad6d5e8edd070a21e755d6528af5865961676dd9f5940749f36c41fb8fd8e51b83e8af26d88358526b2ca4e1a2e1bdb7f9d361e6b33dc095d18ed43be423d466d004be044083623d22633eec7af79711f9bf9b8b18b47c4924fa9aed721ce2663a7dfa4bb13f9138e28f91c9d05cd88c698a00e34a80d0cbc402a7abd1f00457f2807404e5fea8e51103140ae88a2fa49d76bd4f72d6152104376652bbda6fa30dbab2c25a0e3417f61c985d7c90af74313fd1da67ce86adcee4955ff4fc502d24b0bc33cbce06ce002b7e5b367d9de5ad44f6b676cf27db35901071670afe22d54c9b1233db73836c4e6ac1cac0090b5c3a7f4b51db9de5d1734c631f49c18ded45dc2652548a659f19baa99a63d62f5cd9608b8661cf7fc88ec0ade0d13568d8cc4091e07c7d9d8544b4b521e616ef141187ddcb9f4caaec7be8d90cb66ec8ac2f78a19c48f7d18b55d596b7e6aeec7c3ec1222fad3d5bea16033b6ea9ff30f94e97d661e922ed23bfb9557fb1742bc36dca6b202f9218b2ff870b64c7aff4acd74513fcb8b1d456c917adcc57a88b77c978093edbdf8a5d4cbca0319f731c8b61253a77077ac2d575cc29c65290174ab6bf04860b8168737dfc9102903df24368f1f7b2e178eb3a0867537fbf301dd75b00070e5715cbbbef5f1164c30a0acaf78ea75b958af893f4cf76def228bc6ab77bb89d7977d26dd07a957c35321ebeeeb8e8b784947a4c5c44c85ee3dfbbcf8cd429f2cdc161c5203aab4b572b63f2e40759197311856661a94e1cc9cd9aca266af0489cfcbdd653a6ca84cd1b70175bd6ce932840f5f9dfba39dcca3b15b8bcc9d7a077cf38b4e43c2d0579de4186a7db488367c2afd8cc8732d7b3dde96a334335804c0bba48fd0465286160129e4481274a79aa46b54e93c2e6b557fb2d2e72de5f0f92079de6b46cbe80af70ddc05dbf944d01ef405129b75cfae13b113425d8444715edfb667ef8228415d7cecb2402405401583d9a714aaa3b8afb9b9b1626e82f85bd8d23c1677f9be212935ca19d341b726495f50d178a17db767ee7b105b922361938e6ef2420cf5572ab2cde3a05894170bb10df9a35a2e5a0d9d5a9078f169825924259a9ddd8cf0b7284105c4ac8453aea54056c6ace84413998ee6af9e2d8cdfe06af335164e90275613a357765f1c55c718d037941820d837b766264d093bbfdc7b703e589a9db324df79612fcef727e912acd2fbc12c395e98c7d8b1278d5a3facfb2093ba0a2cda2da673c300c8747da11574389142f859a588005ddb716964efe42cab10f97dd4a04a3260a5a6026825e1129294e92e7f63388d2923e31d8fc74a34a0a42857e41f811d3c74131ab73ceeba63417c3b902d4b962a476d8c8323527f6af03dd7aa2add82d9e5d21d4bd3b63f8d5508cc8ccd55c07a3e10328ba0c51bcbb616b001141ad6ccff3f3a52ae6bd1fd4ca26c6d2c219c7bf9e79d7a2e3c7ba9257c7b5092002ecd22082564eb9dc786681fe513752b2064882ce7dd52c19c32984ee445399c00e09bd31682a5f0d7b8268c65436ba0d4356018998a8dc55b108c85c25e42587866757b4341222cb7008dfdcfb23e80fe0352dff5687b3b98ef8289650de50556b045ccd304befccdb98564b82a096291f8cdf2760984f3a5290a44e3871e8295d4ce30812799d74142d12bd245dcf315f4b211384f454e489e87a80855f5edc06f7ba2ebd1219103f1ae4c0ece8e9e7b86b196566358b05cc4df733a2ca3e5cab2fdc14807f1aee61f8988252b09f975d591c82c0db757b294e16594f050824badd4fdc72bda7cc0988fd65817435a9cfc5ecf0ae843a427087ab7c2fe99ff5d4f65861a8bb8355a059593c638fecbf1e3a826ecba88b6706a738c63869960964139c3d0270f6aeb4a99ebbb0167d39f4efcbc8d84c7b0935899900a22b627eb81ea21dabe8162c59ef159e27d7df1fc13d46305e063c08e38fa1a2f902cf4a1ef9a9283f950a2b9ce2ef5120a77f313e7e5b123c64f0262a11e2327d243d216cc9c58fb1ef7b3994793308c3bd16c10a0d18170e663db168a09583b91ff6e79fed89629b3d856c6602382e5409d99925aa9c119ed429896044003f6012d99094afd538de684beba6b7652791a8a5cf3cc39ca1b042183b72a4339e9ae4c3196da559cc8660297bf7c499e329fb91e7279aa905c9257458f8b6d393ccb510c8b509b941841e1961a04bd5ae7bdad1b7132039f8d7582d268021d3a9ed91be2b17950d7a099dfe963834521e13ec9a009ef037c908b8fd40e854dd14a69eb2352ff34b3fc44cdd4cfdc9b2068c69794998649b60252d9ec53ce26c30323a7be0a1d9d2e8dcc9241d66c4f8baa1878a66b4c3d20318f7cb8119a5a146e839291b1a2dfb1c458b63f01f802ebb396276b7631605d285d631b0245bbb3efd7c7e23feb5c8456d6cee8430299591b9e3b0aafeae5fb0349ee5408ea192cbb605ca9957d1edeb73c077b7f08c421c4156c966017978faa5301bfeb8174e71731e1e08101ae94f23449880823d054c3666ee2df0d44641641d218e3c297868bb7b60d9d0a92abf6f012d403850546b74f4f85c1a0b4216084a12aee858dcb42edffbac874d5913fc4c19c893c06c9afb179fea443106df3a19bee90a03da1acb91ce8bb69102d4fa1c5c32eb3bbc478b459ab40428ea0d9c4381b988e037b5714621a2c733015a3240eb88844075a1807ab8be0318244ee29076f71d9f8a70c88e30f36c992efc24cf97666adf3d19bea51ee0ec446856ea36020bab08b7a543c33ac54794be993443b9eb60dfd9e55a112f64bd2ea993a220a46dca03c0d79634f6c63396940b1e7b052747d1651306971e7ef68338af2c5dddbcb20bab86bba3c2b43348c31284d9b759c7a62251a47203ade31ce59a9ad379f21f9b8279d35140f3b8c1b02a0fb61d1c10f7f5f8cf8fd72dbf29fe5c51026a557d536d2eeb105b53aea8897aa7a6f0670429f948c44e550a0d914072b73a8014b2010a6188e915f83981895bd3f5a0a3493aa1dc054fbd7f9d990dbefee196519f2ca6883d1118b85ebf42c4c50b655a7201d13516671a134d50284bff0ad0bdb09a642f3c149d889eb587d73424b1a32debfac2e237cc88176630518141aa95eff800710bbe87ba9ddfbb24defd95f2b706d96acb173bdb27c42d9b5a0198b2eabbc80641676ba36470a070db368460d3923af702771e00ec74270081b3a0bd4e043147b9479bab7ffc6c287bb0f5995a3a23f49393ee0dc99d4f9c6368b10a22322cc73741c13e90ad22260bb6e01ca9849e002338e225d1603c0f31949bc406f8a9f5594225739d39227158c362c3708b0ae5132b2224e2d300ee728f9917ced6901cac10590fedea57360968d08e0134e50abe98d3a12acfd393e93a526b6d6cdd88673aabfe7bdec8abd88bd6d07db79c2025e19661fbf91cfaf830f763b38f52a7448f1398926105ba8e4df2ec3e8b5089a60e6be3b649181c949eb2f9852670ad6ab8a9b538aa4b69d0ef48b3b10b06dbc8723b837ccb1d522a7ae506a04b72f50cfa0d8029744da6d3633f95df991d2dfe04842c66a06639cd166f741b5d9d5c794fe3761022f13ccda325af6e7a574a012bc766bd3f88ff20248afee366c2787572fd475d9f2a9ece7994603213016e52f93b13429d82d26d13b2bc0b584a4b19a5a664cc0bc5772d40f77cb956666e67125ffa396f8927a77bd2512bfec6ce210515b81ed8f56b4be8b677895b7e98d3ce2eb7959aceeb05926b0b2cf7c47c32c93cdd464445b4983eaab69ced1c1e33e60aebf28cec6e2f957aa635516e881ee6ef78862e828859161a6f2cedba7422d1f6b35d7c0e6be66e29510698b73d202c52dd1e043800fc32ef58e5b08312f240765450dec88fec10d5131b04d83cad60f921ebd85b7379cc879a4f33ff23ccedbc7787005f55e0cf19c39af55315289af8ae296630e85ffb7b3df5583ef8616a39011bf9e3008e02148d589c904ab567674b2c46ab30be054f9ae23201323e58848f789ee48d6d5f335941c3bc54de838887327dbd702d2bf7fa861dc1421bf8e76bbb4adf1207a43a155ed98cfbcee1cd6cebaf351d124c1bc1179d3eadcfde0f22a88c8989aa9db91cb79d54171d796877822c3bc2e1562a2443edb6ab0a2a7f37a14e76b028a6590d4eb80c30ccfb5bc114b4c4153b94e122fe565db7be43655d8b88053aec5eb9e2f65bdb00574971e4b59cfbe40db65204821fc04df783278f6e99e77e3888ea5591bbce12810546d759d278f5c90ff46e33d3f2ca3651533e0122c5e4135590d486ba75d4c6c8013cbeae0786b06c4aac200f828c8a6961c6cd8b538a82717996fe49da94f8233ad294faf9c3b24ac6737ac755b44be07c2fc96f6b688663dde191507238f2bceac46760f726fae51697c06e7ed9b5ce2cdb499388067a40388f59f7d87e37cb9053921ca5a26ac03b1f533367da375c4a86d21d7249a66257a9f6bfe3ddc0c35af0d945d7863fef070007f71c151e1e22bdfd4494841a7f405a87fe6214276c71bac5a6f959e7439650973e4072467a063cd51174a4373ee278e1eedc3b0525b8ba74abbe0a51bd950ee7a0b7f58503537e2931561179fd81f6620d96d05a1ce12a9588bd09bcbd0adba6a3b836432dd3950c29c582df42872e21225f9ac6c24cc0cc81add0518f518b0745207957b9fd8d4f974dbf1367bdfe6a757b33d6a5507c9ae0bd8483e4a5333d164d88fb4bba5d5280b4fed5edaeefcb198ff614ce7a3cb765dfd6a339b1d1a97f9a3e030d8c540eb64322bdfd77ab8493c5b1804716a94e973faa764e88fc59f9557ff259c8cdcebc62c6630904b0baab372ed174ebc92128aa5f1a316defeeb9768d8953f195a8a08e29185c56dd5624f975ba1c500d9e21e82ba46343579b241998baecd1d9d840bd48f094573a6064612bb4cf730c76199051b14fa2d882af6c2bccfc70d068deb1c24dc1fc5ced0c4f967b5b58408d1e0475d7cfec7395c6db77ef08abb88f7bf2aba975dd7f733362a3b61a1dd84d0af2fc069d688b404d460a4661767b7c6e6ba056ab141ffd59e5a39bb96a6adb350989016f8c6d3dbcabffc3958cd035aeedafbb0f1e6976eaf42cbd8110a3dd8b6871779a9b4d39618158b0876567b3306c12a1027ef9a5c4cf74f486c6e4d4f808d4656625e7c05754a54b499ff679b9a9e37d376f67b15f10001fe2faf3fd9f89a8df44ac7bfa64a7901fefc5a4232201eaf8d61ffd33814e7012e51a7bb1847a35849f522a00889dfb618038d4a35bca21f6a1292a921e0e7c6b87b77a73d00585df2a559aa3061a851f40c4d2d9af5e398a792f09bdd8b72dc03b4f648cd2b31ddc8610fda382040e8a8262ef3100d158888fbb2aca235482c1fd05fbf5f15ed8ee0e39bd643f3fd0963e77669a72f86f04157134a56a59404fccb4f288cdb5dd6182523db42524102b46662d011f34240237a017dfa56f857e732c8071e6ed4d9abf67ecee7a78162ad46393da94945bb9e91291b15d63871848a79122f652cd1311b5fb1d6e0d80b47c27e9179e7fc00651a8a4573a71ec348fa120d90f27041cfac1a3c3df5ca7fadd77b0c1f64f6bc62af40f5e47eabe93e4cbe0e25442b0043e3f55b12fbbef210827b03518f8401cf9f7d2090fe9c889bacf0c3b35bdcdda4e22a0c098a72fe06fada48220109f57d30a56a498c6b5060629f5c0859d98a5a6735067cca06c44e9351d531a26362006264bfc8c1bceec9111b157d582abcef55cb9be5241628dd22bbb92931ab09409f9d5e04f90250f9b4f16c648781d4abdc988716255429a068798c64497d20b22d0e380afe29ba2ffe570c9f2509c434964869e4800366b89bbd3950233276fb561e940276338d5f485953b494c32509e14b7b584002c889fc6cc68a92f262498fe9ec3288d2eea7a4288377af78e6f29a8e2f68501a57d0d9cdb246eaf32240cea76f93145b24a11289e0a143346f4f09285e8cb6d64799a83b307f8ff50d83c2de70e4d73ec80fa8bd6f689e53e57a6f6e5dad847d0c0a1168eaa546215fc45ecc8662427a15e44c356809791542719226eb21977f46c680c0bdef68f167b67af47b7d8bc8128e0d5b1a28e854d252f5b20b081af2a86369a710518a754092d1a3330205341c31279a96fe04a60c9abe2b97d9b953978cc7a12f30d2a0226f8898f7807285da2268f78cab075cba6b367af136caf5024d717a5cbd15f99e441c3afeed3c6f92130c2960ba487250a1538e7e1aeff17ae4451a27d51bc6943a6b1f56f41b27a56c8914944ea48cab098496f35ea1b8d44aa81615db4328fd9aafd9488c5fee59d2046401312dd60b3efebfd18f8f156168025c9a1bd64bd79303716913f754b8f8ed156f617c0bb6c28e83998f2414b0a0daee0f4ee3b0ca936616c638f3022ca543508ab108d4bbd342cc4845def43cb89beea0106eb8d748b6cf4c95c017e4c4a3ac25d17df8f525dcf903650e8eddf931554b6de7544e8f751ed159c2e2f5b82ac46240f60867cf508df4a693813f89ca9d0dc54851e27491527c16f83e5249e4b3f22c2433080ef379b5bcfdd5cbf44648dd9559b52a95dbf395aaec5ab17a56f213c2c69c2bed246f161dc1cda431d606573ddd67de005b2c9dd71c9d1a8f2da9bb7d928d76431b95f3e60fffc6c8a52f4c534489db616f207aef8ace4b05e8267287cc63357e603e2c377b98e2470a7f23c7fe7e86e80df2c4768e64ea4952c696606c15cb24cbc32a69866c524166c55d0f81e3e46fca39bd4e5651eb01c90935ab000ffad5d6735e85005b80a4cd24214a5d6018ea4b858a2e745c7552d999ee40dee0df7ef9a01208c0094b1794871dfb90ae984ab469038802991a76687e68800a764ff3123b3f0da7a4fca4540b379c45a779a0aa9f935336a453f87ec801d8b00063867ba8be5487ba668bc881a6a704154756625499248912dd347e642bfc949f7e801b158a3444cd9f06473cdc06faa139a002c3551667f22136dde5ea14fd273509d0ea69e2a14712b6f151357c87d1c558c75586f548c7b871d780a425c14ef869e8816993695663e226573700d9006aa4f4535aa75505f2a144e2ed4134376485125184b1f73a9da58a34b12226c12265a0ee0b4ccf682c1b3779af32bae8be28c10822e4a027948ac60f2b20d974c32afcafe314563a939ad2d1a6bdd1553685e0ea359defa12224e5eac09504edc2f99e3290860633f9e4b63bd8f6e6aadc1ef4cba681aaf996ec242c3519e4a2fd64d78c9c8de488e96853f347dbe9ccd45899765898c7b2e59ad29ce60d2b43c5309fb2a70446e1c0e13ce58bd58ec20a69911d7655e984ae43f120684b0f702d870c6a92c349d949e9b39fde5c9b62978d4eb8a43f6bd41a996b60af63218ae4403cf764210cbef154efdcf6df34debd58f1b66ea750079f0a97b9368e0a360d92df70e09365714acf2114971e912dd5777d1ccbf2d2a83d5c038001846bb18837b74ce525150e30bc0b079a353eb1b27183372b1ac0be01fa3e7cabd34f92318d3fb5d33e6e8936787da645c29c41fb3cc03a235a85dc4bcfd6b4e98073ba2d51f14a70b65f5e62bc007e76d90b3eb086e91b6bde38fdbe70fde2dde1272cc665b4677ff6f5b6da432aa221ae4217aa11be315f9391895f8c17ffacef7630d64537896d79abe4731db3dc2af978b478f43bc7bae44e06b61f9a528035598837062f7807e51108822fce16e1727566768a157565b7369c6151113804459f7495c46648bcebab6d8e187149d98a2e3360a0ecbfc0b607ac48160418bbcc1864f2845f26712844c931945e73fd3c819ddb145163bc851ce598770d4fc1f1f298d2f32d76709df7142bf3f29c5697e7c421b0baa043de67baa9e14043062b53ac425091b395a975447678fd136746f08f592427478487c1e99e52e9c75c2d362938bb478333b6a3a60d518f8f15222cc17690396145d0d6f8cff859aeb94371b12cbc0dbafe8e7be138020143c1444d0211f3aea1f861ef1922d8a15380ba9b46e141255c15e9e4aa08238d9039812855e3ac7bc5e9bb84dbd3016c94430de1a0d53ca5ce8c135db3fffe2a53e44e1b0f29b629bb4f52fea7c0ad3dcb59a72b569a43bfa804e05d07c20b7813f1902fd2b07983cf9a13fa8655fcfd937344779f21d62651b891d95ec9520a409d2eac6d3d22466c8be96b65512de6fa4f6bc0b50b6e6c48394bfbc4362d3334812f328ab4fb6b372949f6a89a23f8b2cf6a1e5447f9e9a8854f462853215981056e5ac11ad9384ee58cc6e3b0c79b72c800288a01ec4a0172092cba8cdf5df0787ebbb0830296ceb513f491f2cee7e01c5744800d66a7436f01106966c5d9ae21142efa1c065c260d18fafb302f3d46fd7a010c6c546318021e1ea4167b89c43689bb7ecd0e6f6cab5fc5fdc489233b6ca24a210eca212d9245d1edb0b80df7ea805d6564a3ba0271283dc611e72cabadc61848cbbedacfc66a08d84716c35080f97988d217223aca933289717ac1cb605a80f7e692225504f8951b3bb4bd4cb2a7f62459bf2b7c976f4044fcce3a76ac1f7d4bdc5bb0b6c5c2976fc84b5e1047cc41ba48093ccd7b6b23b43ab8812d90dad273891877f558444037165d04f67b00027340f337e0ffd68836ef72be029b6b089f8ac86f46f9403b40cc267b7edc5e2ddb7815bb72bc220bb09cf91bd00828a0c3cb79d11b50e9dfed861d15e38b6341957f32f5f19222a18435d9d02bd3eea26b622a74bea261164a8130d977000d9f7b855c4ac05f6d50d43b79336d5bf7ebccbc5fc42f9d5049035f92e095fef308da00045dd39c8ff63005e488b29f6f7683210412896e631445f1d7fced33075cd402215a67d31767fb5c0eb2edf1df30dfdf6b73c85b3ce93cab4ca9be8c67d9214073e821d9ba578f059ea87c4f921e4fb7e69110ecad840ea011e9bef87ad8de6eea178256ba846b0916e1eb12b724e04014598780899cad7fadd7505c3e55d10919c0e0613af22f526e42ffe9b8d149ed9b1b003587d2f46038100cb3f93b4f2eb618df9f49af6e4f322183db85644a1bd13f333c94d7b4a05745cbdd8ca45a613c6f6708a0d7d0ed603b03f98e95f33a923d2909804c77dbce2aad0e329603fb034ae7ecca9e006104d4650b3cd5f81886f219344af5d823712042a726a88b301d5784d40c67f20f8b1b2a8e4c93dfd5496581505204cb7fabc772f393bb4c25117b5f322247d585cad46044c9c3069904cf25f91af792840dc898a0e8c83df7039bb17c433d929a5919a80b974c4b1823d9140094c82cdc17a800ab6c1c85127f9b7b9accab494194f216763c21c1e1297dacf22fc8f2f8154a9acbb2f662d032964aad0c98de9320da00a936efcba3c934657f2903ba410718864d78d13fc9014307a74d05cdd6d77ca055831f94905073b51dcf0b571360728311f78ea3cdd3ac35dcbe453e28906f9dea726dc6181ef81eb513c993ac531138935a3c10856c9418accfb0314997413689337e22407cd0a4a188778d25a5b557e141cfd5cbd13f5d9dd114be4df070bb01dd30377ef16675defaa5b2634d0f9f97987d9a33c5167e3cf8226c9c2f57baa730d3ea8b3be75ff9c7f51929643a770c87150365b7a15bdad94f72a526ead36c52e26fff75dbb967b9eaa1161746e80c62192cec76a7d5538b65f15af7580513134284d11eaebc2c83f319e5475980b605d152f8b5c0a34be43317ef664f4f00841135a93f1336bc9240fe752d4564ab1d83bdb21aa396d925df00975963088900745b080a8f6ac7217839cc4c8bd65e9e862160a12dc36bed1a7e0191399839c92099c85cc943a8474c0e4f0bf8ea04a10b36bb9e65460cd9306dd48258cd62805136a6dd0b5cbb9def29852689e3b7aaccb20cc32d14225f67be7ec94a1b739ea941455cf04b96931dd1d410387b557f9949b18da4ca7531d311c75b5551b744f20181513e250034d67b528ef99023d58a2431a8e0ac195c3b38c6731b279eab0bda61e37c29974c14ef036c25629c674eededc587df5bbd40529f3ef9b98b7f889ff056904943b07b805479bb1c6714dc8fa83c87a16091107cc1f4a3cb187f15c074e071d96b3f5fac79af477aa3b8b1a8691cfce9784a0bd73eb8bbf9d8b881e44fcf9e8a86f1167a97bc54d2a7f7b447398e33be97fd2ece3f8ced7c835f2aa344dd2a74f6ec88b2b6e177875c74562ab6567182bfa4abeac052b6afd7ab7bdd2fc8d364a87afb6efefd2d7eb26227de664942bb6b6f0191c4df8ed93d3e5d5c1932b08f6234e3a62e1ed49ff07ab498e3417b7920e17eaddf159a7417e6ff63cb6f39eff3d4ff5ca89a21120611c5677c2c0efa2b78ed6b35a444797dcaae8539c45a2c83c843dcfc35f833f10091418bced14e3eb7fe4c7d2baec7ade52b2ea1ed93f351bfe02d4389f51ffede91f5c33f66eb2693d0e98bb59581da7cbe951747105682d232e78f46f578303436c2c6c078afcab31957af13d60c4c31155cc97894273366230e0bedf8963a423d48313e250b80270d828ffa5cc9b50a708a4be5bc2f0318b80d5fb5de2d8425d5de2dd5b90eb5127a8c2c9956388391387954b1047aa056b6c94b13bffced9075ab059ee96ab4614bdf341e80abd4efc7d122f28787ca53ccec0011deaaa704ccd2c4215347cc34da525b4f937188dbdd137df599754d246648e0bc791d0cb215ed9e88f70246779b7db2985270a09812a0c40d4fb92522b8b2155ef3f5fd4678bbd58767fb7d7a5a7b39bbbc7be735d04f61887bdda5492d3a9d645ad176b47cea41a26b120bf9e2c9f79c5d0faa7a164d383852d3fb7ae5171d8eb4f77898f1c82d1a244a3e2aedf7932d4588f267d9b10b85fa06dcc4473a8ddf1614b88445a2aca1cf7e9e9706bd5aeb9b000a0434fc835404421a97a938dd017a82c1826410b590f457b284bc1fe4de550c692b20a31223dcaf1447f70528630b14a19b85d4fb48458f75ca34b478435fb83e13edd79c1eec1de17313c867bf4e00b61b56f633540552c6f0b9ed45230eeccc4921719c470768c45e48c209bc6fb6bd4c38afad91fdbe308fdb24a5df4b28d739902a4a7094286fc968438657e99b1d7b621f0c7affddaf1c1938f18db5ab59907114e60c06a24b861be315bd0d15f5292d0d9ef43215a895f00bdb1caef75b614839221d5445966ac0d030d9a4233b4156d8f0e83aa7860ac7967e167b21bbd7586e4965ac9e6a4e650b0d9aedb944d9862afffc270b5c6792c84761ff155047291402ebfb7d26b906b65c371e07a4a065cca6f8bb7f1d4183244c0c606bdc9ce633cded1cceb6347b3670433a942f2c18a877c536f775072677e25ba9cb2dfe0642d7cf8a9db7d628a69e15d4c15a28e034c8a3f734046b3d09c57e364b0ec4b2ad7bc36dda91f0ae51b0545a1a8efc67d5c13936358bfa6971c14239db16d21aef2944785371bf53c47669fa39a30ab98865b0b007c6eb1095dbedc267a820ba9e56cc3b9ef24f7a440ea970e0899b57ea5258c43b2b4dc02ae620ff65f0c48cf0c044bd330fe86e80089976599c3d91aca0a6a08f5f33df86c8e10abd7ecc1850b3e7fb7946f2dc763d6fb362db58a970cb7dd82e9aa6b81f0fc2f873a204bd35b6d1fb8bd43cb79cce8c6590e1c19e14154b2becb426cb685a6b0b98ef9e56167374979120fc4493a4275863c52bc41033175cf4e94aaa6944722114712cf0f6a571e31cd8144866aa2602879659a307b675f0e5793877b32b6d6db7a2db3058b1471cd270fd99ac88cfc09996f18dda4f9bd73d08cc72d39d8e54bbef4509a2f758cff232611f0474e336a6c42ef9779de2adf6c8361048a39cff98ac3eb37ffddfff2e68d8f1387678d839be716c4af8abc66012702f504edd87b227dfcd4dad71c1edc236beb3308d4a237c990697e194a0aed0f8ec71c140e90540bada5e1c19629d76126b2c858ab6370914598e68c71b026865df9d6043d2c591a2fafa927212b69666cf539a47b3906747d9feac5fc6e91621454b08c0fdb43ed0c0899268e16597986216fc83c79e1b266cad42b32d324a0fbfcd501ec044b6fd181f3e7142969996d0b7f93e6149955e0b9309fbc3f0a4d70a1ae6389c6688453b4c4f2a85e85bd0130ffc169b8583d694d565e58330d125b22d0d961c8638919991084f6d9e96fad1cd844977596160083fe7814ea6d4456389982b24a0ce6a39e5df2c53d1f7162c93d8335bad25b36898ead329848e9b6c5325d4a8583bb3ff00495d4f138f7733816b208edbcabf7a5543fa1764a27995129494895d02de29a10f12db1a7c950de8d31c2a86149c1e0396430a1658ca220a6755333f9178b21af295fcc8f5474e081d83e47975d77374b325f68d8e276672783ef168238e7683c8222e1aa601108ac7edf415980faa1e9f9820dba28d4403e2d78767564a3231241206ca6d12496f30314253b19a1a477e314a16dc8c0223b60ed823606d2a0638b19178a8ae690bdae60c403c76e46d8c97b1e010a485c9f062f0cc4b88deaeacde714e84306296eb837140a8785f9dc6acbb879de001f2517801d7e0ece131774fbb692932c2c92be824928adf23c24a68e7240bad554aa12a44fd5d4efed2d96b4766b4902bb57ec0afe0fab1a2ee770b191efe87395fee284ed376ca7aab68ab2370a6b52e49e10614dea151465a99fd761f89f31392da2d55ee5675918b0c1b0493a83970e6238965149d3bf6d3c16bc093863db6e293f31678f18073938584531608ed6e5a1b11c3b00228af7def4593494e9b00bdb3390c955554a6874267852b4b3d5eb5547fea55f2f77f868bae4ff4b505913147da4f4aa125540f128f9cc9b2e557583e0b1511cd3d8d1a96ad4b6c5d90629780f618e6503356159ea1121702eacddf6094079bef8e1c792cf95fa2822cc46909bc4e6c2c70d4c0b1075e4d9aeaca18d2422c1bc5033c84a2f2911dd956b7298c26f1f17f41b0ec0ffdfa1b8dbde474d647b59ea60903f00c33b44a6d190f388bea577c344c753b47766327b9f7bcdb80bbdfd5b309ec5ae66917998478a3c116f60c389e00b380f9b6a80ba3ab5982be8ccef39e30c396517050890d877c72f816e541d00a7d7c070bdda59408ffd23875ec2a0ab036e8bb7090b7c7e08e83d32924d33d4ead57010e4024df5fe4b02961ca44117c246cb442071c95f664ce7692e4be280d625adc9d3ff73b846a6bd0c8f73ae0a417ee7204433b5f92bda4c93902e46d6349945a86ce54c2b3b96f3ece4d505d971c0d73bae610f82c6f3d7d61a3fb468b8386299edf0f5c7ff3bd3766bcbbc8ab09195a66ec34c41c251ebe3460aa3c1eda404d33d5e910ca065b363b8e5de961ba3ba00d74dd0d5943f23b1c55d2a45e657e9c63833c2ac3e5e80591770503957c9e2ecf8dea6bd3d2c56515a273ec8994cfea9b2a9cc8c27b764d3d230c9af37f03d5ab0a57fec6cf4913418d66a8a92629d34183be83f700749106a22c193416030072c9cd52656c8a15dce2bd12ad638d1d9148861aa9df8aa1b57996af1bd945e52379a333d1702b3ee9bd5e73e993ea99ca998aef496b4b1d2297d329490ad0773e89f5ab38c9f31ebd7d136ddc87f4183374e182d6dc633f9f28de847ba364213c905f323dc221ee8f060e99fb33be1d767afdaf25b084b012850a0c6117e2ad7dc803806a5d2a983bbd3b2a23c0199cca25af2b66dd9f4780f617aed3e8fd8f1b9d73ac8f59415db5e97f14c204f710ca44e42139bd39eb3971e13e9de3f48772220b2f1cc517abe155643f8ddce3bf437dbd5184655cd8f737047e88301ce1587d069dd7f0521e476f29a9856def84c282c3f87103e0a51b9dc997cda9e24890a496869e1c444626470ddef2dadc6a5a2bada4ad2b7d2cfaa7d539c6c738ccf82df2b1627c28e9d729e49ab071459e1fdc2cff09cf47bb8a77c2d7375ba2ca98aac738f18a55b372b122eb19dc42d29aa356fe18e0ece19ad052083ead5f1a17e996a40ecad1a68e683fc9b419b27d8c00439a1448d8482e1029e6302b525070b7b67b4e7aaf3cc6b9ce703c04671c52c98689b35ffeba2aefbb6d6279cd0b6dcc88b9180d2a8e0a663e451ce733850a38dbb278ba4fe9482b2508d9eb164fd37ef25964e9ea7fbc8446b6c4346d51eb234b08cc5fb4e57d49f737b9b6119e051c1b0ba3d6c19c40ce64b68ac04fc9f8a97ef972ebaa0ccf017399af2e0815d518b8ecb4d19ed7db30e99dd62064b136f2d96e66edfc55fa20f1cb3cb70a12868ee1e5c17722654c15e74da7641728005b2a138e9f4b930668ab5163d05081f18290e766d24a4e34609d8b235a4d420dd1b54bb58d90e3b8f4682250b8f4f66c1ddd8dad6cd992f6bf1854a8bfc3a72207c95dcdaccbd2a971d28ff0cf690387235f7203a1ed4febc7e8a69e4e73b58bb264b7c010139df84c80f6c8b63dd277d0ecbcfd8ff93d66d66583d29f778b81aed1c11092868a1bc72e4634889b65040830362cd2773fd2efc38bc9457c8c75bbd110a079130f38d0057fba390e750dbdcaa18ab8fcc679664cfb0345eeda3b2f90f2655e8592b95426e1a91c2a00b1e91e1478d618c3a75343489e00789b311aafd4989291a0f95b05b6c642e42eceb6be82e3df95e1b1725db9c3dc5d4206686cf390f1875bcca454b5118277196222cdf04c5d8cb1c8a23f35844aa329ac10753d02dda2ec5f2b0b6283c4c91125f2b64c9ccc000ed29f756128551caf52368aeca6465afa203550640fad21c79714a3d0704a13e2bec7a89efb63c57664343a3192d43caa2f04e49ee2022e85bce5bd810358fbf90487e8d4011227c199a9c483066e4fb317dd8af46c6187e55b70762e63bdae226802d20f3ba3f6055e197465aefa6537acd2c0dc78c0aebbdb2d4e8b34ff00070e12ffca9748dfc6764482b1f0937080e4111de8b9a400e03590c3ba718e1fb7568d627b16bb52603751f80af05a79024ffeadcb24879fa013d7210ec6b5985e63ece346c5e7ce9713fc40bba189d7cff44e5e228a01952785593b31469b3269f483e48a30f1d0ef2ca75aa2fc2b4ccf56fbc90df8ea4cde65494e49aa01748521a6763926ac71b1ec8c26f66b06fbda6e8b43e43bb8368588bc61ed09c5efa72b309ba37076c0a0a4e1b04ae2fa4c2140eebbf19953639284b67cfe6ee3e0ef1c82f154431071650620dfef846d2bad030e6624b8392fb2e55cb192c43d2f0e373f7c694d1416a3350ad63f58858e5935823efb44ec484c5db24c452080a90cb24c7bc9ec20851b4c1ab90edee04e973809be5f748cef2f0758cf95ba89a71ba13d27f13f2f1db1885a56447fb79e260c56b8131905e9cc36ca676df6b4f7ed31b0f3f7413da720b83161994d5137459b0a6d6f2193ef3717cd14bd349f8c9c873fd594e7c03543139071053086089904502e0e9a41eb1f7f5daea68212ea0d6e9e42e04c0542ec969c7631ba49f3e443aa5c7ea96c40c60bc2b175abbed4e0ceca35945210f22d773b15009d9f5a616dd92946eda518b4ea9094c22157bbe71d4b0371c88e8e9dc0a79fcd14e4ca484f1eb7521c0aee2053d83f0b47864252ee1e983dc92bf798490fb7f2149d5733ce3db3fea3c905ceda983833cc07f319e20eadc69351407965adeb381ba45d80d414d21fdef32d062233d4b1cea905ce29676e99cc79dcc002e70d380e60f388d51acad0c5f778107c23e500d47371c9ca5e596ecd715ec4baf0d630a23a4153af80891a4e3b4cea2342969a125457135710796961fbec09a93075171ca2a98ce2391f02d5c4bbecb530930aa5671ff732d6d330d42eb82dba70d7bd5799b888026d53c5ab0a5d63c97ebe2ee2ac6a7bf3d85c8bb84d481306364d7401f76ed5e040500b462780616bfeb441ede9055bab0e410d2f676e68d346a4e5297b8d3ed0d239ab155728d5e67fe5d722154bc1cdc60f8597d7799987c0b1021a4c82c9f826b7a7e0586079f36d3c184be6796956aeeeafc66a0e99fedf0b6fb5de52c42de6fdc1e6d5c013a43ed71be613ff6fcfbb2164dd16d0c0765cfa9319d7fd0382792d3eaa2f2fe778e949d62a8a1c9aecdfdee82baaffa125b5522a0ed833d06b20aaed71f446628125747db601d03c7314140f8aa2005e64314f522ac6d7c82e36f2a6265467827e2935ed017e610229c13b575f42b2866658a6c36374b975135a37e9fbe7db4404ebf810ef7066f7c62ac1cd8c8ff3c15805370bf3510f2fbc87b34b4c557b11db1804422f332fccbecf0c39fe8571a2be6f35692121d960c8d8bd8b76e2b2430c9990b4f818fdd60b00337ca18d4138dabc61a40d575ef55510215882ab47bcaa44d5a993a16da446173d044a06e9511c83c2dcb63fe08db0127759b4cdb011cf1cc5d68f902cad88424948ab067009ac9bc3ebb62151e3b9a5ef92c99243f1d795d8407f7de18354da2b8c9b7349cda0487e7118927ff53b2dcc54a776222d502092cf93c12ca50910cae16bc8d57bd42b489d87865961a7f769bd325c4a068c36be9505e65500e453d827ec9b6effe16901479d78b74a816df89abc9ca93ce477c3af117fb5975bb11deaf0fec06cef16b86b68ca3f13f7a60b1e4929178d35f5e645def10791c560c8f064e3b8c3bc01ca74bd809ea5b74dc685b42cd0bc8b3994778cd912511cd0850069afa8d23162dc8609c82743ffcec06969408df0452437deb35614db518556999ffdaa311586cf7173c55ab77db58570168c273b829072819ba2c2ae355484aa6fbcdf5a553bcaaf183d194fd8c6ce51df58e0e7746272561ab3478b01145c5dd6a3746551e69a5cb75361dbd184a6fe3b65f92bbba6988cacf130407e37c99c6d57fc276eff14c40b3045862e63e63b5b9fe81f583f58d03d02ea1ba19c849d429aa79787a56ce6e7a86322535aff634a644d359889f0475f8e6b79800471304cbab32f0f3ebb733b325f5edb8da95df78ef6ceed92377486a88ec81b0c37257ca3cd9fc769f23088c3ba4cd3605f77ba308e97f7fa6766dd4603c7738c356e1d7b36f56d41f3c25a083e72dfd969a852fc190891a74214349fb69562ffb68b36aa231915512658c3ac9f2a2c1377712b50cbcbfe52403268c1f401a565a0641c7bb1ced2b6fdc09e4cc722ad83a687eb995598f8af59720d3fa01bc85b4833563f096a0caa683741f4cbfde885dd89fb7b20aa2d7024987bd188e5416825301d86123bc0ef39135c7a3f0e28dad8de98f4e02f0240554d25b5ca6bd76bb6225fa5a08e49244dc9988be5d1965fbef9e94495a6747298d7f33b9b8a84e2a4c9ef6af4b802acac18526987619e09ce86e900b64e83d96ddc3ff6cd375ffdc58c9f346cfe26d2f7b48563c37796df6593b274ffa3dc35a4dba545f20ecd6b2e4874bc55bcda23ef12f39c2d1e7ae8015193df44460618157465b60f6c1c2c0fa5046920504bdc10ed654508c4bee9e00bbd325f6d5deed02c32a53f3296e5654372cee007721f13b916e43657b06bfa66ea1eec0e2afdbe0707a064bb731f5e5802ce3900d8c0603d1eb5ca11bebcafa0bdfd3ce04ddae776707f916b27e8224557f806e17745ae9df26b413d83013ca511e96027fec11412de148d4b673ceb0c5df0d8812c13890d2e3cb10ebd460f9c620e1a354d58a36fa9dd6218ed939b02cb23cc04b087a7d4aa6d601d36f2e6e1a03e5f497c969e1744af136b51761eb3423f91c20b35b9f02bfe6d0fc2af37f68aa7a494d86118295b40236dddfa892dae337cdd2bf1cb1e085deee0d256d723d979fc74fbb7bfd8eb2a2eb4364ce95c63f5b742fb20ff7992cef6f2a22defb1f747959718a0269faea870ab64408df838c4c3edc618e18af691445f64fc382f84bee2f8b46b6285669de8a5178fcbeae44cde5affd52197f54c8ccfcc8468a53f1d66024a870b6c71664584f600e83029839cb8284bb774019a173665ed29f11cebaa0b387c074d6803c6e52bf083a5d2a0715f7f2d370baff7b35de4f753582daf8c247962b1c85ffad6c891ad7d2d0e22daea491500a4cd5a0cb525ac656598dc4c18f8e27eb90f719f8c3f6e7844d2a477767f7b869ff1045f0e6e938810d02a1bc4103d7d8cc204e075e65c47881b10398a570b50961b4cfdac2769c6ac3d388ed4cfacfe9587e5c4e209549684760b9f7d78afd8fcf98ec27b63885ffd7bd74a329919a57a104109562df538d05bdc067cb4ba46ea178ecef47babf30677fbf34d273537726702d37a0f97fae6d17443908af94a4ea457002e89d9d1888a254bba1eb5b364f4c80da30050484b15bb7935a06056143f32895225ac77119d25b735c1cf6d71a71f101a0e0856443170c834b344c18ddf5323e305d9db55f7ec5939c711334dce1d64f44787aac03a04cdbb0156729b40b64ee7e3e570a709b175cc5b8a7078bdf6257a4d434fc332df57ae191e7239899fd77cd598ec0fdf31460d4b49885a68c97a1933aede95cca5614d5e34c4cbd26c372d82f883ae3cea8a31789941f696edfbd6c7525db9858e0f82de4d019ab06c2c7d89a52db13867b6723966029b6dccc12ecdba90843093845bfd5b4a5d859788fd1d7ce6e28cbe9a5b40a8c3d508cccf6361512f47878637af8a7810fe61f306dbcfbf6bb88ea8d7f16d2ed48133c8600660207f4525fa236fd107983127afc36dc24bec5649912fe65fec13941415b07ec3ad859b05a695e7b2791caef5e49d0f508bcfe4783cc251d8e1af023c236c10456058439025a342962b3ea7e763610cdfc6b3a7c96a2b945cf1ff2cd2c835485bff97672439f2a9fcedfced9f106c32b09b59bc60703f6470087f62dac55e77ef457b53cf326127491e571f026c2f8cde2d3604c52e2a45832eb7647a5e2039d0d1f509834ec6e9d60e26ec5f37a2f7053fa29f1de51e47d130e5169de4fd461cfb4b76b8f8595c36b7bfaa07a0896a5b6f466a79f6c5b4974750c7762a8c2213ec2176ecf59ab0cc2effa63b113caf753d405f610b415435aaa3da48d75b2da2bc2d1dc3dc3c7e9a63e3cd6375da722ac5a73c4dbb05ae8fb602e85c67ae8d393b3ef70b5f8eda7eb93ae4aa0f824d540de5e91a2fbc4d25bd07d721e5e629084f71a04f1d67416b7d7185c53fe39dd436ceef9d01241b4cca952c3cb46bc2e88744d153f34c0b591fece10640a87211a2785892114c0f1daa26e722b7ee39cdcfc1c147f76efa76a07a855f3c3f0955ff9cd65fce8e24e9c973db5887632ae7afec037a50e17b02a799f2bff9685c3f5ffeee702e99c4dfc984bb58f51d1658103acee5873bf82cc3bd516e2e85dfd7d3d755988223f79699b9eb3125c3fa95ff5fed1500ed2b8b0cad571dde47fd5feac1b9f776d5c9414ed8d26ec04b2651e1e1f0b10e48119f9d59ce38dac16b6b3a80fed6fee2cc7e474400abc0e5875da4d4c40277101ff9cb3d6564ca8f6f6b5d4dbc93eb17f586283a6c2c33f036bbeae61e8008c62e7fadd92d151a50697c1f23c697d7a8ef38a1809ce5a69f39ba69f017272e7d34e2ad7b7620fa1baaa9e764079928bfcecd5c4af617d4ce317d562ea232a23b1b19d479d083cd57e87f55053c4120317557b4ccda8e3b19f815b119226dfd7865bd680644aaf2cf33a47da526a0e81c78c20910fcb669efe452401c53b57356afd1229afccbb99c9331148439c93c643d72b861838efc8adf50006e88cc8be415b64739d55786d83f641c97b685426640deb180c454e1372228c6fc6167e80e01777b975a22e3c48356b74ccbb399994a6f8e4fc32bd91d26d8f0e78c91d4c853c6b6ff35c18ed93a3b40b71789d0b045299b29f37828daf43d3d1ae705526f0c3d3b42083e847471f66de3cb018c3f50489aed65ea2dda7f8e2511eb6aa12388fe80b68d897db7277644888d8f7e84e39ff78e03bed918b727ad35269335e68555d822eab7c7b38639a4139645f8205fe88349326c8faf2e7737b74c5d60de2aebffbccbcbacbb58df7a5a74cdcbb5ef90aaf1eaafd862cc67f42dab73855011c043c78873f3af0216ce1b2b8032b5c23b0a43e3160f9f0e6e3e8cdc07457c6fa3628dd72b5a150cf3fead105bbe9b841bd72c9d801227a29e7fcb7288f12289c730bc2a1ee4a6878e56c9f4bdf2516239026b88d4a5ab0817411aed08b12dfadbc76e53ddbea7b615b9d87a4cfba4f9ce62f594aff1a30721dc374c2299f390fd4e3e9108bdc4281f85f068b8a832fa9bd64e712544b2315b42d81c3e335f8e5759ca5865a1b45ea466a2a7efa82d7f4d421960fa6c53b11215839e20d65ac47468cfb7fb1f333b25292d54f7842290e8ab22c8363d3212f0b0a165237294ab91c6f58bfc88300d1b0baecea2cf5babc3b6ef0f973a234ec80a7a2f5b559b7d91c99f82e45252e7a7b63f69cb0d278269bc523e16ef2d7dec10883f6a2220b1c849dc731d13abb920d59fa4bfdbb18d8a9abe71e2a31f3ee7175388785d1e44a69fee27a820e5e7785031a05333878183a4168480256406890125fed87ca77a1fab4ef6583b4cbecf1e7b2ff2b604e3860bb679a11e2ddec7f993abf2905e2ffd5ecd9b5711f0ee042f22240af2101b5e29e62985b1a2ac0c51c7cef44b62476410e3976b326db1fae13c4ab4b5f5fa6bc1e20d14d2c83943386dfc4040db1bec83fdf3e7ba7e1840ba74738355ef57a8aaa2712822247c9e3aff77bf59425458445e4a0a92d6992a7d0c22b97564f83ee6ca33931389186f759faa395b644086654bb9affa5301604f708e5f0af2441b269131eaa518572a2272d148d9ad32f56b5ace8dc96093c4def4d2a91c33998e0b18e09cdb1c2a228946655f69277224fbffe10881dc344261664ab005d0224308ad77b84c5e9e7ee7b71cc40cabc7a201086572bdbdb6d7a5b60fd90e4daf25778c7da75408336e93e00dd5f206a2e2de0c24284fc1b0a5ffebb36b766c8298ce231601677bd04476a86efe144fe6e580f904020ac9d228061f22136a093a343c3cd442a54428f0e345849345af104f6c0a83cb44f1bf76dda9ec758a4623a120612097c74aead060778dd39629b6c4d772116b48bbc5c7ed4986eec15465201aff25ec63332beaefbf9235e2162e9cccd5c9ad9a08e58c96d4075488d5e383086d58f1c2dc002ca94e09339fb901c3fd6cb747486cb37fe1f20041f9c6b2ee6cc746d28590a2bcd01fe04a3a801f081d6d00769255e584d7af5b1a043e4d700b7560d405557c3af67f1838427c01648847cb29d05c1693d5ea04f4ec3bffcd8fa938cd6de857a49193dea3a1050ecf5a0bb94213abce7934f31bf644900e9465cf865868edffa4ef92bdba5a16dfd48a997cc6c4b2b6c4d616b4d074c74e166dec166c9a4af99ed5692aa419485cd3d7953e18698e139ec7307b74d8b84c9f94d86fac73a5dcf407a2b6456f4a7a111001d3a48b06a881e15f171e6f7eaf519c813a9264a86dc2a7d7d45c702101366752bf26ac066cd33a337c65c76c93f4e1ad7fc9bb0f0ca43a83ed7cf35fe9ad932d7bbfd79340c3fd8d61f84b8ad4c2cb605086ed891c5fc3da0381183866a179fc955b0cf88f1460e620b492de69d53a151c8d44e35c3d65bdc527f1855f13b07d45bc49a48362a2d64512432c362492671ab74b91337fa6590b739483e3a3484a2c8eeffca75b0461628471c25da07ccd4db3909b85e5a20df09d85ac01fb545625dc9cbdf78806796b5a38e21b812851dbbecd92e3b3594c4bd8f39e59085cc642e6b5a1b2356ae15f4a00ff9cba9e7c3ec742e760e0e4520d9c3c014b677cf5aac74c1c9500b94b02a98a106d0e8357ecc2f2ab363ae0d8c270c9a543bb257f33469b02384bf1a4843890ab329943bb10c43aae35e5d358f97d5a2a8f2ab8c36b86dba276f9fa4e607b04afdcc308850fff9c3eb46bde66026c08c89dedc7b6d96e91bf5fb3eb550182b39d5c89ac7554bb8fda0037d29f89f9392a2fb35c35b50efac2064f9f540fa8b78307c3d7db0a0d292c602f23f873220bbc5a34c2d3b6dad603b50b0934c05a6db27e46c0f85ce7493d98bb7ac56b24c54e94675d5517a5c73616e178eb40d5833d1f021fa8baa4e86160f1bdc8ce0d0ca102dc2e36008b27493dc0436c97d6e2976212f229713a2d33ccbd13921ce0decc6adc61bd398c989b4b37208cca4d57c9e524bb6976d7042811595439f4d58a85d98a36dad3f4112aa44b13601d13e0e97de50d4922c7d6f3508147c93e620cccd4225c750a539c3e482707112e5f8a79a89980a3ac6f993f7926f7ea959970a7c94d8a3b11e52658019ca09c0e7d8a0aadc2f322cff6707a65bae011430323053e7d3807a01f0cf0a1dfe6b2641a0d3e2e63111e29805f95cc3475ef3115dde93d3b0933b2de91a73d8df686730cebc7942549476e81a42bbad9c89a9e21a23c975f09d64bbf2f92fa5db19f2945f723c5e6d825ddbbd6ad9b96dfd7d510f3092755dfc7b759d08f8c37aeb1ab8bd445af960d8296961397e063418a41c36275670e65e10332b7a43088218bd836d5d3b702575a052f31d340034f64a2941f261e537a311c2c56ef4fd539915c0068d7f1ad0145acb88af7daf1c6ff510b76a405644c84babd7931bf1c09855705f2985beab880d6a30bc9e29317e3321726d4e10220cc70747369c4cf71a2fb6fe4c4c8efae276959a4ab33cb909a1ab307e6639cc65641a55badf6eb734199213c440289a37a1d936d0ec9c870e7514c6eb1cc3130ac659382816f6dbfdcda1bacb3b0799a61feae3be4ee21dded12048e4d5766a3ad9237d2aa62cc636a761a28868d1be495206466d1a99266bf9ba226ece9e8484b8c5676f1e1cc2986284aebb60c94b19c531931e2d0c23f09a471755ea49d3d53c4e3e4125db8cfdd7a8a09ee1ef9a7b61db2fe122cdcdfa9b750dedc7c2f329b574ff42d0934dd58db76f6796fe59b7312545fad99b929f4e29f4adf3f5c68827b446a87e28398789b9c98125baecbfbfac1b74a0103bae63d9ada8bd72c8291c22432db0935301e7cc2cecde1ca47b27e80f8677238210fc86a624c34042b68e1589c71df730c42a3bdfe2ac27d7ea7d15ab1249d07c6829bd2a9bfe710f3df6a8621675d990677b2f3381fe1bb132cf352a36b557b608bbb0bc13adaa04eb7c196393fc09f5339afbdc647e8b6de66672fcf9a9983191de3ba96133dc3bc2cdfe9c73e3e8c8cf133f7f46e7c3f2adb80197dc272ec44f95176580b6eabf159b6cf45776244f3e96c8c64825a6eadbd29e3851ee3ae106dfb0ea304b973cd4a136ccbd70dec7a5a6234fba70b1ed1b5a676b78fa5e187d8ffdcdbf9ef083fbf5f0d1e07028fc955efee2d7d1fb0f94716cada0a610c98e68ccf903a4ef5ac467173e8e0771d39e1a2267898171d47365f2c2000e6d93930dcade2c56d3cbfc620cdc2fa9fd29b960c2aca0295fb176b1618cc8352a9b6f160f80d29dc29a3c1a5138d39c97c36c9f0b59a1a5769ca450cbc24067dd65000898b6ff3dc41b19bb9a17548f2c9cfe2b30e4c8b2a1eda017e22f884e5697aa8d89640f0ce10dabb4132c001baf860f070dd2581e1b286c89fa8eb84563483ce08372c6cf1310218027c28e2272e927f46be9c444dbf78e27846f97bece201152380c2bf774aaf6d50f68721f1044f3e7837d864233c4db90b40163171986b574b2b258070b6e8b1284763613d792c257a12f5ccba391b6e26d06e7c5ca7009ec4837cc0f98192a9875cde1ac1d229e59a765260e8fa4807629311d01c4fb0316ba857fe05f70ebf8e0f260177d9168c8c03ba732f4214178f0f8df92a3f0fa73962e6ed2525dcb49768f51dbb442768824163d6fc7ffb059a75f8b8ca49eaab4a2aa3e03bf66f4ffd91307d75c6f1b09b53b29f2f2773c4641e4323296414a83d773913d15f593cc07c0c1bc4abe486aec3a48c1de23164f2e229ecc48a24cf20581ec64dccef38491511f79c852ac2d5457bf003e89662a48cb5dd23871aefe21f155fc9ed8bf6fffb6c9e613f7289c68b3dca1b0ba294ff6b0b1aebfbb05fdd8a65424cbcab8e11736776ca3b3aabd27044f949b970509b732cdfd4c9419e22f18c266172026148d51f6cba006608e035fa1b5cd74ad4666e463c87ffd0d58c173d02c2c9da43bd751415925df6cc2ba6f68d1894cd38e44d079e7f596d3a42d135b5f700188dbefb8e25dd83441e75d385006d239978968ee3c0d183bf9a60efee98fb78d1a50347d084674944f78781216ce77ea4f52a0cf0aa6c264507f34cfc54517b4e07a13197e3c6438a96d76571c93f0e040591e4d1ffed0f1c2807d2138269246da0cfde522f38edf7b370e03fe1885db5d3539aeb0444a6176f4f24f9e4c482aae8fc4546870b12f1b9e418abb48c41e71528a4474f881496cfa32d968522ac9acfbf35d9104338a2b2ac0387d6f44677dcd5d3453f318d89ae0abf4c88a724eb73a803aad84b3dbb4a355ddd1eca51e3968df81ac779318bfa61585f3c38c30c026a08e033c06955f1e4b743b38466b6b7200f8c9252de85ca491888fb5d8195e7d11d8596e4b583a4e63b576443f446b9f46d8f5bb4949c756164730496240a54f85e416a5552c9a93f38b5929706e2903f52604077f9623bc7927081cb9722a66b81389462b8acd7781e10a9880b41a3882060036acc5a371ee26f7e29f71857d73a257ae7b90c09ededd1044da2dbb2576052ab6a51dc44f00a6982a7b38a061c59ffd588498bdcba05853faa5d50cf4d03960c9344be7fdf68b44b56f65328e90972c10f748a35251b4812bc1b904d68e9d055a6d913860a1e2ab4a0e87cdf0bb89c67893cb8203784e2771183a3da68a319a39e15347b13af0b8d0392dbce9dcb6903a4f25b359131cd9e956c935b0494966438932bbbcaed34b374730e0050b5ca3f1dd82e8db25ae72db57d744c1741f18b7377e17c6e9186b6c71a9d46313fcfaa0026b0fb62b601f708e860742e567df188f44c1597ad8ea2fb0060346cebbcd0387c1f17b4711abb03f3ae1afaf0f59df867a4fb5767efd9f8cfa9259dd29f5bbc90cd7873cc076ec1f6c3540bfeb9d723929c4aeefc02f265d40f2ab9c35692d6cb3ad954cc7021cdc2cf875084e3dc548db3a0c774dbeb2315f3a9aa3221c33923fbad00b692bb984f67a437dcc4802d6e93a801442f5a13234f487b20bc40c5edb0d280878305bfb8370dbd5b170adfbc6392bc396106b3920bec0d3a63e2c2033c9fc41cd79282087d46019bd0bb17bee52c833d998db6b18100a551887b080b3ed9442ba30af4382a5406abd472328602b9f5bfdb65ddc37b6eb419bea9d9d3756f8d384820af18ffa02e32de964e0a5d3b1b75fd048da067390b5fdffed94e5a53099495c3d5d728d05cff30658ab80eb752a323b4b3d1f06677e858183d0a433c031451d9401a9afbb0bfa563ecf11cd2922513b52f79f0852a07daa5fe6941863a300160bbe07cfc5ea23d438fad2e4aef262ff687dfc376e850290e472ff55ee8107d4e8466d68c8654c9b9fa35e1fbf31e1865c5d1a5ed887d13b32076d49b83099286c420ab69043b9db3ed1810d97d372869ccb53d4e0458170aa6376c4126be983e4f3211e049619299c475e2852ac42daf960112153a46dab256f426463c17629a436362b00088a953ce04566d900a2a0cb62634d9bc22a05c84791fe1af05493bfe59243d8024ca91d7b0be5440343dca336a71fe205e02b3ca39b5efa0979f162addf292e995229142f627437e5882d92fb9d541538fdf9cf9f5ca790272e12f691da14ff1c3b5a63d44f8e3bc9c693e68c0cde53ebcb17b9e30fe44f4866f60e23632bdc692ee9cd6a8e4e0a00db836e89acc0e44aee9197261569cbb3766c6c619d376df36fec7a77f083e6343de3121c4276efed0c43340559604083e7e68d21b07f1349483918cb0e8c651e1711406e492e567cbc09e3b8ff7eb25de7f14cd2e20d1aa2ed42eccf34610a64d2fe3fe11b723a8e36e3a95036bbbd5b8ca76bf44764f657add728d678a598299e53ca7736ab1c1a39fca0b72c3f40fd74c6739f5caabfa60afa45f463d5ed7fe4b88fd92bb5a411de657efa3312b001f2edff252677969e19f3b19fc41eaf9951efff7144160968b7334f93241b6074ca902f16d780a143c294ecf0017b86d03a9bca96822c93b9dd085038196fb82e81bb4393c175cb2a559c92545c48d346b5974816a67c3017688439f9c1c6d1263b7481eb83ac581a88005559d73ccf6f68382cc7800ebfb65fb879b4b0334235195ad3e7c9c3c7f45bec6d044c54b3cd50e8512ca2bf71c10888baf14f4223d7455ee945b431e23b01ae22af99b58422731fe1cc4dbfe75a36da9b44c0cd1082e941580fcc7b6df20cea445ec9d5c8ff0f318f03c0506fd330d5db6cfbba23c73d296a4e49ed8b9226b631b7e5d8fed080cf5139a1f8842d25c35c5a5c23e34329fee4e64a12d79ad01697ae68596979b1016bd13d6cd40803c147a5224d2cbffda67902751a154ea2dc10ebc20df068d429472e697b60b67ac023d8ce6723f457c78e93ef87be2421df03fcb841f8d2ad5a5bb9e2ccf9d3032537fc9db16e1b27df119aa30713ea5962850213164d32c4175e934c56c5a00613488375c4e39ae1176606bf79e1733750fcd8aff65983642ac60d1271a9c4365af7d48b59bdc1e062eabf4904e181646d49eaca52d071d65c4bc436d00a7b94237404953b8b444933c3c5ee9b4d0e18de4813b507a58ae86a037aa94e81f4e02b6b5e01972ee68d6a711c85dca653a5799247a10471d5b0fb48dffd0f3caf5c90201262df96240f0848c02f5ce82dea42eacaf96882c58dcb4999c35753b733d6009ac80e9e49f00cbe633b7f4249e947bfd5618130e150d292b12e36c6b3b3fe8a0b8159cb5a2f160e3ea9ec271daf9d3ce3f82055384b3bfa081b78486cb6846601341db24867505cebc9a508214e07167bcf6877ea901102ff99b0a21f58c92aab856ee4e5bd579a34b026cdc24d09807177580afc0f5d5c210ed795707c6101d119c5e7ca334b4b3d7e7cf664464994f7d44e74ebd8d1d6e0166e75cf61df8a3bfeb605b059bccc8b0ca65a20a6cd62f9c9017e62a24bbc887753fab15f5198b1318c4318b57ff4f6ef39f8c622fd3491829350fd765e3268684d40510eaf26bf68b3e1da64e26e60dc1f402dac64f6e3517ef51b37073c321db4fb956c14adcd5f5620d565dd8e7ec916dd14ba60cb51ce861c55b7d31892a79a8e0c42ca9c32e0bb82a239ec55ed3113aaecf5172492f65d26c135b977a4f5c3bb396c4a2c33355918da88d80b852c39614db94a9a3b95d4d7b16c813911f5710c38fafb709966baa9ab6b2da64b8e83ca4dffb33ef12e818405686cbf32597c04e114191fea64c5b398c0901a17d0d540cdb23df83ce0d94cb8d2c1f3a24df6af2d18776114d16cd48a60bb674fafc53bab6ba2f75e1325f3148ac83c0c91d02d02bf81f86fc01ce554e7126966a937cc7597ce63f86ec46cecedc21f1e08d3f7992d834505bd0f1f9ef2c2e03b063feac80bb57da9b4650ba5eff7759df73f074a0a5f3b54630b080448845e4a90df898c4ff53d3c02b91e5dfa4d67acf9f589cfba25d377407b79b49cf7c787da3caa93f42cba758a78a42797f387394a118f87d9efccf2c19be11653275633fa849eba096c8886c845fe5f922a2001add80ac2d6bf306b55a8b15db4d0c1b1c39ff25ee7b8407aef21772e783e3bff3f20be2f7441c6e1bf1dc012cae1873f57389863dd240c335372605a187f05f0b51dd69678d452714e151eeb2dce75c330186ea3e0c9eaa87c632218457c4cd1cf2316e70717d81cf5df97421f6d3a41cff1916b89dc4f50668c665ff0e873254b417eef95a88066b2b0a1f1a2801890b5a78e117d3ae1450981454ae09c7c8b8fbfd1c8f92f839bbfa2f89d816ac57d12c97a677d8dbdacb261310099b5c319b00c49ca4d73afae45c12e30dc4d9dd39f0e5c5c7aecb3df1b9db88a07577d01ef7cd9ba172eb0bbe82cc97b1dfafc8d216984c7e24c3bd6ff2eaa7f3003b4abb3b689b43c81d423a1d80468ce64115487d9fb35098cfc4fc6547664a582bfe94a28a0b97c97a839b7f49f4fa3aba2e6adf8e38de90e46a7ae1f7a791cbdb318777264646f27cce7f8fe9c0c73bbef402bfc7234238dee234bb1d1119828359a5665cc715b511f9aad8d3810a925fff520615a6f687d68e20a7bf56cade375f389b700adc6ded373abc4a979fd23f54663213c91026245d1d814a9bd63888e3c78d0a44d00f6fcd0837f651e3cb3760ba021e151cf9825bb0608a637bc47663e06bf29bc3cdcfcd8b0f1ddef13f684cb859cd9af580b17b0c4485e82202d43e92cf8ad3116ac168562507a57c0d5789e525dc108160f8f20c43bae08fdae24594b8351f0705162d86ab3ae44d511eb11052db378c42b1df8c22ba92315d217cbfb95188d58bccdd13d62deeab55def673caae5271cbe55f020fb7993320054d2b4d27ac59334efd384de0c953f08be8d05d437a831aeaeadb1cfd02d49371000a8af56253f53877b1b353163a020c3f84a68218cbb6c23150b6bdd57da061720e3b70d26efa31884a5b627aec0fed84cbeae4ef97cde04cb5a207a96b6dd9c7fdc06e54ffaa9f737306dc943173f3d601c58fbf0f6d49152e4a6f346b67487bf6665c3104efa6ef4830ac353a9ae0bd9bc3f38e96cb60dccc99ef8efa64d43418736e5a54304b9f54eaef8c625cd8f5d46dca98fa81178cbd12eb52a2b74f4c9993bcaca369af876fdf36f64c717b0fb0364f27f3fefec4d9e44fa666209d5e21f24bb1bea1ff16d4d776fb35f9b731d8a5a981f0cd0a413ca41fb8f77479faa48d542889368251969951ba302a8883b511d3b5e37dbf8d713e579e4dc836781d3b9ef11f707b708e7839720fbb0b96d48b57fae034fdc875bc5f6682fee2cb87cda41c214b60ee573e9240071bd34b6817729c7ece5122ed824131b1c5b3f25a879c3aaed700addccbba69b3cc1c830462733d7e2f489d511766038c2a30b982c46676153b1d7d6e2470c8b7e90748f9fec97379f5b4e37f9bd3444f7e9565246f3ebf72e2cc45166ec2b4afa3b94fbe0edbae7e6cc19c5eb13112f864a28da29760e7831779b4af8c9f0e44d6dbec2368c3d1e3fb6a7cb31e3c3246fca7a6671080f355fa489f2b7a1e9c9e9e9ef17b909b3b7573e3c089bfcfe43395f5e35ceb1bd5e99a1c7226db1edb4ab89e450c49a29ce64707b2edf45123baa59ca9f9f95988bc289e3b8f549617321f055105a6d94f6faaf2164517b99c001dc9a1504aee55acfacfe4848951e81af6adb30bcf3d80aadaf1f17337df4aba7560f0787fcbf823c9bf9bb8b761f16df481d6a36e574c28a5786c7acc41ad994eb74584188f57290d01c1db457e4f49ffdf56acec90fb95737722fdc70cbf6e286b37c985f63e04bc1786f2793a929fe4ba9b7344cd1f47a61bfe4b8419884a42084c2bc6adac46ffe216ea2cc2b487ecec9c9caa2c4cb35b5dbc3b29882886452736b6536e9e973ef4d4bfce1ea3229f9442034d148be8b1d00099b505b74cec877a6d39082da9a35b96982d5d2d894388b42d4ecd0cc66bb03daddef08ecdfe4fe53eda98d70ae0a236c2038ff39e4bbef85a79fd1a75dc4aec1527484d31abc09829951d01876d9c777abce7dba14e04df89b3da386516e674b95bbedc8a48b6276476dcdce623da8fffa36dc95bb6d22b621f017d544404265c8a9d81912a6ace036db7d5ec43347005beb8097a434662f5f56aef1a6c717a9d98fd0d4ff958a8bb2ec7517b715901d4a9e22c66b150451682f3f1f52743eed71290ad60b41daccec84abfad4971c5a0a91b2d39ffdd199cc32f16161ba7978f447360257ed3fa6f01fb61836dcf9ddefebaebf003890d54173a07a2dd1d9d28d1a8fe2fbb1936e63687ed46a6cceb1610cc57006e48b58db5ca9d1e6df1259098702b692f7bc04b1e357f78920405a181f95f726e017849b17939a7ec6f494f288fd003ac2f430612e4fe0fa86cdf358228fc0c9cef4cd07759a54721c673c14fc220aa51a3ef9943422c88e7919580f75b6eb4dad912cda9781cc3f549caecb61c387d2cbacbc842c13a8a6a3cfa8f16c86cbecd3fe9a238992301a8c42024690b88ab85da58a1d5ac5861a7d7bfe786f3b8c190b202c3893c2a1f8d53606984964fa073d6731a241e7729d37bf301b063674fde91b5007dabe42e897041081184c4026feec0313873514d6a218d6df6c6f09356ac950f237a65bbf48c47d73a038f210a1f15ef1214f4accc5fdbc8bf1449c390697b12a9e3d9b99db6621a356ea273d833057ac2c58ae0ded3678c378eba5e4885b4266441fe9f82e3c119ad0118bb940082701f7dc057ebd9385a42cf58cda71d1266ddfae9a1d2e185274c7cc8c006ab160a9cc5d8a77e263bd8d98703daa52ec1080cf71251f70cd91efb74ed4c439721d8d7b0504ce0e221f2c98f6bd60e3d5a3f002f91c7588b5d20aa8d9f7f6fe25680c3563279d903ac8a27f69de517bd162a07ea64dc775940d1594c7fb642f8e258c62eb9ab176a8760c1511e67df24267084f1165c137aca9150b5adb093275d9a5978904f79eb638219568df090c4167dd67cabadc2a432625d5032648b2d7a225dd36a031612326cd45cad626255d2e6b66dcaa4204f8521b54ff25a91d29e5b64bf1400fe25f98fdf1476e1275ca024f811947886ba212108f63403151e4f5e6484c3230868390c45833821890c36432197a253c51918d26e4a3af8659abe29b807034a2fea248ee37db7dc064ba3295e4b626021fa79a16f124e16affa3be43d057ff8042da38d59258e50db158bc553707ddef8fd270a0d8fb431992203e768a174c81b58f96c9a61cf8eab72544ba42bfb0be70b8e93c0ef131315b12d0893d3982dfef0df5e80ec8c231546eb1c066a830840c7237097f307b3bafc85cc1a4fc92491f601af9eb9c3ed07d767b92c98ec53ee9f0f0e15698c05d985d06d3c022ff0a8a8f17a9b14747da009c1838901b2ca8aa4938baa8c8b037e6644150d4410a0b5491bc3300c268688858a0aa6e8608e02b668392d8b1f5b631b8d9a8da5db4aae6ceb002fa27907dfba4d782bbe000f8aecdeb4339254de1dbac0bbbdfc5687fd24eb0f9e0cf40b27b08d139806494279d00c8aad3a5c9c401e9c402128d5e4fc4099aaea6bf5f12112f31eb922faac502f1a3834831084a8a2b63011e232e19c70e288609ffa2071299274d2d438695438693a3871429c38cb89af13f62389ed54d01f585c1c0d93054ffa0b4f883ad6c409fbe184c120a1301038065c86c704d3b448304d2440a1003b610938d69a9ba1288f50441c8250c3f2801ffcb0871611c5a2a1c415419024141e705ce1c58b66103d7898cf0c555b68261045753279608200ca8ce763c5264eac70e3c40a2a7cde43c50e1ef645d5858d2124e1a48a227cf1a3d0cb5dd9802811e599a128204a745205c70d921bd5733fb40849b2c4668a0cb02469faf0c614202a64bf9811816250088045057a51e5630a4754a1e9640a3948523592240d1f2449e3461b7b58430f92468d3c744213094a4c07e573324514275340713285025078509101314e9e473a799474f242d2c91b4992c60e92a4a983544d219d48b106a90627526c7122451927527c21492c28410a20e210070d1d2449330749d2c8210e1faa23d2c0419224cd1b4033f422499a34dc20aaa82d4e30c0058d1b346d90240d1b2449b3068d1a502a7081345688b2271710802445218828d488020c2751f4800349664c9219731285e8240a21ede1c0c49e1c1440f9745072447182b206499242a00f553f1d29c78989154e50c43841f1e2048a1440bcc1861a7690431a6b38031a50cc2a34d5cf48e53d749e40f14ea0d88024492b404149018a67164922d1605f860a8528fa441c9e08c2133778c206663c918227a82772fc139a930a2441b20f060a16d5dce20b97203980f00372949828c2119a3224a20f6a98d0c1640e6e300943124ca660c203144c000092244d5fd8cf7f811213fa509dd9f2269c1309404992649c44401392f41272c14904825085a659759470128129aa1308b4a00a4d473e238ee8c867347d94884e1eb0054962142b8ed455f93839e18124893e584c4e12dbd9545888a88f2864ff379bca332b556931252b40312314847c3c68e6658a4fd5794bb180a80f16270db0815485a62433e68ad057f652568846d40903129172c2800e9c28208d0cfc60499258208303b420018628590423dc2841a3840c2709f080a4714333831304a821499f190d19d080a20331384180002449bac401ec19064822499a31942000094728421124c90c498a410c66408624955146a8c7013a1d901a450a94cf7bda09013c484a1620090e5042ba42152e70841f42c8010ea42152910e50e064001ac9ce0fb91b09400492d448363c78542702c81180499c04000e2a50785c8a0a7d4c47b4b91fdaf122430101557054bf48d501f9a83aa0e901c5740010872405917a9c00a0049274292af4c412de529f138d0f4e341e49a2b6902aeb99eff2a12133f43c78d4ce0cc9d65a6badb9bbbbbbbb33c618638c31d6ddddddddcdccccccccbc7af5ead5ab57af5ebd7a758c31c618638c104208218410bef7de7befbde79c73ce39e75c6badb5d65a6beeeeeeeeee8c31c618638c757777777733333333afb5d65a6badc531c618638c31420821841042f8de7befbdf79e73ce39e79c73adb5d65a6badb9bbbbbbbb33c618638c31d6ddddddddcdcccccccc8b237cae396be62266451a01269a2049d20cfd15b5633f44a8d01b99f9a105c5224379fe878609a1e78844d4e7a3c1424b45b1549a2b4892c60a92a4a98267cad0cf4c21148b2469a820499a29485290a459d92a2515a0cfca8746a2d1ed4c8a220a7528caf4f18caaa9fa0a0b4aec9024cd1992a4818224699e20e9a084e40449d2304192344b508224699220491a244892e60846308134b2e2b2d1426ad1051016b969e9a2a5456ea410225a74c1c5c60b1f5cf8f08c2cc5051cf58879d10a0ce5e901472bffe9c07c241049b44092248d0f7a603731a3ea3d33121c2a764892541201a9e40c48a8a00a4d3374535513a49141621f0c142c449ff9e9c4744231a3cf3f220d49b222029a91e4197926b505cca7230a51d407ca147a84171a2f44204992a429a221f2224992678aa68a23b4901a8105fba11e709445fd2226f0e69c113ad0c1082f6c872ab2f220a8cdb8a84494a772375a841792f474fe506929623f3e66423386f2c82402119224c95c217ff8f8214af2a32e3e2b2a2d459ece1f9588f2b8d8a7c2dd08a50a77a31df552d40d21516929b2b92124a1078d2a44b8808ba8b414516929f2e94ccf10974a543dcc10106856a3ea7308151068f2e0b1015552891f2428c40089219214620cb09988008c20499209241d847843920a1137f693746e10e2708424491e8f6322a11fc283c7065411c223499214253ae288e30d499274c43102a90b38dad0a1b079535d5143d515b523fa1836723c8223d83c723772d781636510660091248d073a20499e5048564d0f0ef0a7f2418940332120a24f871a8281900f04310641481a0ae808440fa27080243d118427cc084403a44a93058fd04bd20c208af007454892540249072056159a38a209ba62468485864715fa4e0b0a473441281e8f0f1e3c74fc21057e8843871f5c241d6fbc21bd91224992157cc8826bbd850034259274828d1b3d9024c944d2c1072aa4246b4dcdd3b2c20ae7064842e181d2c5152b6e0c91dc409124e93d9d3b61664c1b9a9024e943a40d38d440d1b186019ad0860aa436bc90daf000cc075aa1f242c48a0fc582d286244928f6d339c286212410c5c61924141e5c8409483ad84832a4191b3b24c91dca870d014892f459d983225078d88e1c244972d1b18726ec218924edd88310499276a07cdd438a24ed40d1b1870668828a1d6bb081c223f4301d6ac5435923079224893e6b8024141e6b7821556baccaae610022920e3dbc21e9d0c31f241d7a1084a4430f71d4cf4865e6e38362599124cd8e25499a2ba4c98a07cdbc4892a60a49d240f11e3ac3e854df22499a0d5492a4d140cc7bfe41332f92a4a16214b3a93a92a4d93185243d6846f4a099299a97925334b5d0a1468a068a0c4892a491423445530b22208a054451422449937257bef248fb91a11f7725f443923418780f9d2fa11f9fd04824aa1f0251212d5c8288dec5332b25e4ff25886752ca1344f42e2a2a2e10ba40e842c4b10b9166765242dcada8a8c0662bd0b1bb15cd05242e64a8501049d24481b222491a2844d547923416a88024aac20043365b7916214244414441840869b6e242e483345b09030ce96ee5598408110511051122c4dd8a0b910fe26e050cd94c4524a4998a2848339517d24ce58334530143ba5311e24e4514c49dca0b71a7f241dca984e101d2ac59b39595951517222b2e4462ecc747b3955017cd56429417cd563c2ecd563ccd56c2f00071b712860888bb9521ee565656565c88acb81089b11f1fee56425db85b09515eb85bf1b81009715cdcad784222aac5dd4a457d346bd6ac994ab366cd9a356bd6ac59457db85381f1741e883b9510457954dca9a888aa0fcd6771a7220a81e1e24ec55a1fee54e8c8c59d8a67c4853b15241d2ddca97c3a5cb8534932b970a7f281e9543320d00c89a059339715222e2a445cdca910a99f51e8c5dd8a8a16cd549aadb8ac107171b74204c810772acd5456dca9ac346ba6e26e45f421d0b7b85301b284396041b21398c3169224e99803cf410159a0a0f0f80284c24556111ff6635eb412a2427280220e3fe320461cd6e4c0a18283186cb3111c5400872924a99a28554c33ffe30d492469d38353cd2d50aa8992451a37e022312fea4892460211903e8c4e15f3a24eb3d187d1a9dc504992c4bc38c2e79ab3221f1fffa1649e45a5ba1f1ae2a1286abf873b2af4617ca8cf045513e4c28621fc70598321d63004697a149967594305663649dcadd84fd2510951a23aafa53aa1ca7efed38179eae97c0d5194e77244309f0f813af55b5ee4121a0199992c3fe3230d6974a1230d4310511e978fe9bcd84f0704f6d339928646431c68d041094494a7aac27e3a47485082890a2beca7736487fd748e5c6ac60382cfcad70972b1ff79501792a46180660167c8e20c28d2190a80c22326c60c1f141e9beac87cd006049a3c78446ead435519a620bda8806086783a33468aa84e0b7d117826cc0b7911757eb8d64766ad0f02d77a725c5c6b4ae6a599cc6c6126335b1693992db165660b6c99d9f25a66b6b896992dad65668bb7cc6c612d335bba6566cb6a99d9125966b64096992d8f65668b6399d9d25866b638cbcc16c632b3a55966b630cbcc9628335ba0cc6c7932b3c5c9cc9626335b5c66b63099d9d232b3856566cb929946c8e0802a34a181051d687840071a5248d218a820d98f1955a339a4fa8e8e31144092c4700669b262d8c124aa3a42445f8961b2341b8188e488e34554910f513294108a7aa97218a21092247dba683602c30a2429471c3e3e44e0a83e3464109294431081908e544a8865f91025aa428fc443b9f0e0f13c4413a5d90845542728e66d17782049d28b2824ae39eb242ce43f140b17c2f080debe67f36174aaea61266853553d9a8d389b23758ab6108298da114d198f6756fb9bcd666dd6a68baccdda7091ea5b3c1e1fb5ea70f1af22aa3a9ed07790c0cc1f1f335d3e2b228a85a5d9880a7d7079b11d205c641569c6a92687534d4e911e3da0cba2191177a3b754eda1630a389024984f67c6e3f141b550a224a5240a0a40a0128ad8c2195638a20a4d1faa138514e83843ea3863c8342945015285263aaa2469650a1d5090c3ed702a8903137540c1495568ba42f4e98c603a54f4e9c074a81531a34f35d7c44850b8d0c113b220bda07c3592410e9327c427a048f6613e2820aa330249059c8d740227882449ea694d8c44fee8a2e5c5e58b12a9e48c922a343da542a2aaadf8743ca18f59430f00804d384392a42fde43911ce984ac98f9238f279f9615309427871a9294230f9294030f928e2688710749929078286a088b085a26912042beaa7c803a2f9d992454a80865472e40aa17899ece1f445cb208514078f02080ed504578f0d86c389b0e686e4055d58335379bf6139a1155dc3df8916c1c3333f3e2089f6bce9ab5c86233b3983fb2783a29f5928252cd2c8aa05401820d920f81cab084244d2865a0a1a38c468624c870021949282af428a03ac880128a8e17c02149d214eabca0063a5e9044aa4414cba4e3053b2617a4f1a1444f47d7053990241d2ef8a2ba940be01896f850a11f238d313e44cd9719832349938e31c650c0141fdb411103109224e910430b301f2a242302205d7c18a11e3d7af4e8228b2c92503f363233546d92cc98169ca0058f0570b0e0062c60010ba658011e56f082158460053b2469a258a62355473402cd78aa98993228339f593b9f09f2783a321fd3114d199499cf7c99a8a876a06840031ad08087a240339ece8ce7fe87faa8600d1590a1024b89503ed6043c787c353943be2001ca4b0a38a1230569e8484111244a3483f29c2f5270002fe808630961804047180f987184247d159b8dcb0c0f663a90a410497ea023892889053e9d19125d41a1cc7c6638a2102523aa461765a68a992f3ef6333955f8c8a272a95ca8f8fc40a9c2435174478f1e2f59802811109922320298bef8e20b2aaa0051a21d4190b071a49a2324399024c98a1085c40b898241b29084466c48f3c7080cd38fd18f0020ea822846644575081d151a3c78d8f6565e2e4ee765bbf282992f5d94b8415af66e2fd6eae8fd67bf0d92e3747ce3a48fba4b091b9e2ffa6ebb3e1f7df72d1494ac414edb8eb60b19fab58e3afbe98000aa213216a35b2e3277ee3984ce756cb7b9da1546cb6ea64136aeac513bafc7dbde9a25689077756dbc286d4a2b7d7b06591fba7a6ba3fed331ea7ca5c40c3246e896ede6aa776dff6590933ebba6d768db65bf22192475d335b3e5a8bbf442ca7930f3659a60a6689a426f210b89406848469fad85cf698d77353a06c990c51bdfd106a97b3dc52097d7ee56effae6ebdf711e83f11b516d292f4152c0901fd37999a6f788e8a89aa669822c4636a39230c4e55c8bafdd5b33d36b9bf2b2b539eec7791ca2904ccfddc0743c9e779da4040cf241cadcf47adb85d0310a5d48c917647d4799473a632fbab8398fdfae1620912991e843a2aa138a2f255e90eddafa59bf1bc736db7bbec3f248a8d09069e2caf3968ad9346fa2a40b925dbfdd26b79f9cc7d64e6b7d7c3ad41039d97f420917e4b3f6b167df5e6f8c31dc826cef3d76ee55e8d6c6398a619e1117d304f150a205d92ab5f1d5d78feb43879cc7475a8602b52c48b7de366ef4beea4d9f711e77362130aa9ea67783122cc8cb6ea9f37a70c549a95322c8cd239d04255790cbdb9db596ddc62eab97f3787137e86ec4dcf3a2c40a72b6af715a5b5fb3ed68398f8fd409f2364dd3e46e344d9ed9097d354df6f347eaa4d3e4af648c922a48f8d5c6d870d6f88c4de63c7671a1e999b57247de33ed23098daa6316239b1e255490f1ba696f2fd666b3ed6f0ab2b276bfa953c6d7be8342a6490af2d2bb608d6f756bebc209af88484854b96331b231414914e4ebafb459c7d67dcbb4e40c16978d913176b4a785c2bc7632abed1032af469f10695bf79cb9eb6ea55de204595d43b69e9b9fb6d6604913a4dfc996564ae9b3bcde723762825cf37fbdb858ec5fffbe53b20439e17458bfbfceebbeee4809d22d8495ff45b7d6b976dc664912bee37af146e670f1bd8ff3d8dd03c574181224c7492b638f526699d796e4b708bd0c25a44f507204d99885d727bd9479d53a61e747094a8c2027bbed59c7f9f3fe6a91c8344d53cce8e3588c6c684911e45bfd1fd9b6ae0d3ad8bc8408d2b9e172d4dac7b859ee583204e9b8b1061f5dd5edba0d398f65fec342ef1212b930bf90a862254290d7d5fa2bae7819df662fe731bca251ac60485431561204c9ec91ef751c2be5c83c66f379cfe31910559b6da3949714da4629b1bf0408727d5df4daf92cbdb1236d404a7e20fb56a66d5fa41432ffd91e28f181bc77ce06a9e3c990d1c8af07f25f84cdb117df9a3c5db49ff71f253c90f72d7d1effc5d0260629d981ac4fd9eb59613773bbfc79cfa69b16253a90be7c460a99df3d2f6e939303c9d6d25ae9bd1cdd2e4a398f1d8744150ea4c3e8d0b6bab8365a5be3bc4dd5698e253790ce46c673c567c8ec98721ebb8ff9c6cd6fa9daf98cde074a6c2029f5eacbb1b3175a873124aacc90bf9cd6d6e0bbccbdd8d44351748b921a48e75864af51fad1b1edd53490aed93aed5ccb456b9b35b72e99818ccdadb9536e0ed7bc8bf3986d363023fad6b50c24bb9fd45b5cafad5d6c82d854120339db71f7e2777456e8cd79dc2a2530906fc61b9fc2f916b56b3dcee38ab2ce2d24aa5c4919d25a16abab75fd5defee9221636d7d2d6d2cd2551fbf921748c7b5f65ab0a3b3b3d92e71819cf6db3facf6ff17f7978c21ef7b5debfdcb1c63daae084ac490b741d89aba836f396cc779ec992198177d3a1b3b41cda1174551cc65b005eeb11dadbe2a33ea9cf318368f36a2980b250b2a8bcb276d934d7addfc7436ee461be798d18709598163754eafecf69d3777cee3e6a202e91c8cccdd9eb35ddb6a5d2990acadcfe82cf79b963dc679dc5c98a128e6b22cc2907efd51fbaca3fdda99398fed6f9c5d8b3392521629dbd6377ab3d4f9b073c83455143a16231b9824325af85c7de83ad6c9e064ec3e44d59691357e7bee2de60b23a32e3616231b2f9044760eeb7af4d535db659b91640fe16af5c5791bf2f5888548c6e5ffdc8496357bec17e77105b9923ca17b6cebf5ca666dcf79bc61dc42f2fdc256237deb9cd1c89cc7ad0a3024f7a3b76f8490b9b6064110bae7acdab5ea4706d9a4a4f78c7f67846ed95bb582623a8c7e248c77bdbf8c3efe5be9c7c8e6f7da2ef66f6aafb7158f7431b2b5b05d643c672fea7e566ae733874cd36331b2b9472473ccbafb4e069945c6603e9d2371a140b63867b7566db4b42ba48c29d71df93d59c3aebf5cfcd79a3916239b1270e4336ef7b95557a3d72d384d1ecfd769fa108c74b6eb55f6eb32e89e3ae7b1ed6c1e34237a1f1fd334f3998cc5c8465ee97445ff45ddd1e66c9df3b889626e065e72858f3257e383d3195bcd5609acacf7fa7adc7ad2ebd3b2247605e6335ba6e9612a10b5e1c76264632b7b6c113ad8cd1d8b8cdb6f33cbef4e4ae36ce6cb79fc222425f4a29569fa109069b2df0151759aaad08b40d3cea9178744a0122550099fd97b778deef176fd50f1eba78b8f8a831588e231253f7b93a17b945d74479d6331b2d97cc19c37165fec059941e8fe9c57637451e775b1a61fd98c3b8b914ddd480b1985cfc2a60e42fb28e7f14c4804ab1af2e1550d4d8f2c46367e02c79039e6e6cd7163e6a037c3d8683b1bf95deaed1c13c86fabbbfda573f27a7e4b20ff3d3285b63d7576d049201bd7faec3ae8aab769dd76a88d6331b2198111f9e89cacad5a997afbdb11c8f8b6314abb1d737a1715e285f479eb57766f74ccb4af08e4aaf7c5c87e9bb1d58ebdb4b318d904292299766cecececb3aebd4d53880a857e9adc6331b2e91071ad57ece7e05b8b6133638fddebf2e833c2c7cc3072de344d93f3900884821779a3d3396375adc6089d398fab23f3411bd1c344b7d9bce748e86336f4486dcd626403c4455ac8de7270dd672b6cdb388f31d022adc717fd3686cfb5785d0824e3d5b6a1bdb4bae7f035f3907f0f176d96d77cf53b86a00bdb5e7cddacae56af375b4bb5b02c06eb738b42beacc245570b1792553759b3fe8f596ad9e43c4ef5d80979104e7eeb7174cbc276efa113c4ed0202796773b4f536befdde6d9cc7202a64797d405e861fbb317e0621dfc979fc1fca5337cdbc22df3adae2bbfcdef557e63c863fb6801c39bef5da41f61673b39b6290783c9364bb38cee66efddddb9585d1a94621ea7a40319d6e31a30f13c55c9920b2ae8efcacdde9566bffeea53651888a7c11bac3d791c2f51cab52327b993a870dc6bf6d4db7a285b4d32b64ebae5b61b376711e6f18b3e3fa48581740e46448adbdbed8f3d3d99cc79b66278ab9ccfd903e9f8b753ddb45db5f1585a0077cc8666f3df36963a4efadc579bc79d003d21d3ebbfedad93e1d759cc7905f072483b3ced5ebde1827e4c9792c438560b87bc837eb73cbe6536eb0c5cb791c12551c90ebbbf56dcdb173cc59e43c16c5dc48240b79dd5cded0b98eedfa659cc71bfb991bca72bf116021a7d7472b37eb687b66c879cca946a04d88ba1be7230548ca4bca8b9094fa45ea23d9c8ccd06b2c46364278c87fb345c8a8e5efb72ce43c0e4d4eddac491473a7e9435415816814b3598dc5c846045142b771d186937a3be793f3e02845a52525e54548cae6d3791d1281d887a8ea2c46361c80b23e6be37d675ef9bab9388f43ff79922e08e7746fb5c5ed59ca795cd9ff6a237a98c6292f41aa99b27a94e2324d33e54548cac6238b914d890c5c6ba0d9d780f3142c02dd14159200531a5812b8c2025304aca04015bc6303ddb91f025d400313a082023ba498428a0c5c408a2b1aa5f0e6391b0a60c05d052e9084921159200a0ba040e15a5b80613307a502cce3e91c5914986202f3433b2430f347aa88c01410d850f10051688aa638690ce880e954513880ab993fb2d9d1002918d03154dd6cf8886b0daa5fbd056c7628000309d86ca440c026c98c81e20030946703f3e9c0c0509e081820c60205e0cd87ee2644892605083000dec44c118a00a80880fdaf36170040ca13950666478e8dfdcc8d9bf0460325bca93a9b8d28c96340f249b0195028f998f9d1b0197d304001a4365ee0081fd20e7490031cdcc00666d480063390410c605006192f70c11862b480052b50410ac298495285c000519f18cf1114743830f76da5f38bcd094c500212181981a4a30966483a9aa0891c602880d4861860f02179200090e8801d24a96a44079248928402820e3c213d128a7aa8da630d3d9a207a981e64f4b05af448e9e1000e48e2840376c082c4811c70400c69118a901c6892cce2115920e2d3a158baf359c902491643b2a0408924652161f1862461e1052ccec02208120a0f2c7220cd7c7c4892261188a82e92260c27583880872624942a78acc1c30c28222a08ca34adf048398421a4dae1804254cc8c87026d409468f347664c54c34934038507caa72324824ea2094ea2072449a26207ca099aa2a2024ea02002af979d8bcdb7f84d19c8da8b5a56d946763dd218c8c9f8dde7bd60b35f87815cad39cea6abf172ccbe0c19977bfc98838d23fc66c9901de17d089d6defb5f802c9eebbceefcdf68db2ea02492b6c18977d7863f71c4376743a2b74d4325ff7e51043daf6bede8790a3b3d16b27a475b4c2656f9bbcba3527248bf1cec88d2dbe90996e42b2c5fe99efbba76fba2664e4ebe65cb523a3f4ae9909c9973e65b7b7c18e7e3121ddaf576b74d3c2d96d5e423ef7cbfdb3eff64dee6809c9ff0c5a0717f51befad847c37ce76935aafedce2125249d8e272fea209b4cdf2464d759f9dfa22daefa4d1292f6578ed6c5b6bf6024e45d8db2e59a29bbafad07095923bceece603b65661f21a765cf7fbeb8e0a5d61192d5be7fd9e4c9b339d808f92cb3f1da767667d76b8c90d1bd3dda8f7b3d48d91721fd397be7cf672b5f5a1521dd178dabf93367b3c144c8cacdaeb5ec22651e9b2142b69bb536b798ad6ca33d8464af56dbcc3c9b568786906d1d5aefe6f6d9dbba101246d6737e74e8cd1242b2c81ddfe30b59838b3b0ef9b87937bef7d1d5ecc22163bc4ddfed7d2f3e1d846cc8b7c5b6e074ccad1784a4cf5ebfceceedeae61c0819db9b2c5e7ee88dad0a0849ed83af419e75c23be31f24f3361f7f74b4b9c86d711ea7bc04892c46362c30f1839cedb2f5b1ad7b591fa4c74b1bad9332b6be46ee37e46d9451f73042e66ed15a01f11d98f0c1dfd1775a1983f4d5673898b821979b96dee66e7d5f5d534f790992c20d0993362adf5ad9fe7dea1a3664c3ba76ddb9afbb6e03fb0c267b90ec8cd558638bd0aba56bc89f8b3e9cf3d2ba685d1c4df420dbc15f347a9bafad852f4a1335e465bcaaebc7deadea6e1cd20224c5ae4cd3102129ce358b918dc919267990af59e85875f6dea06d95f3382482292f4152aa0f202922aac8e763545a5266422faca182091e24eb55fbf1a4eed141fa9cc77e0749996de62284ce1ca59529133b48bed4ad66f0ef7ae89cd641ceaecc7e2f76fb68ad7490ef6e4f7e6ea616421add03267390fd60843edf84d6a77b4b0ed2a34fe66b9f3d52bf1e0709d74fdadc2f16ffd566e120e36cc6ccdab61a69ac363233b4ba0b9337488f90b6f8e6ab2f360b997f98a421edacbce663c691d947a90b17dd5b98b841faabd1328b3e277bf33658039336c858a3ffa38dcdd896b187619b206c901056c8bcdc5b5edb83eb6e8ed4190292029b47292f42a6a95a839cd5a37dcf986b0fdbbd1640305183e479eb6c0f2bb5d6d1c7d2209bbad66ebb471a29f5478364d5e37acc395a5985ebb9c919e4740cb6eb7a5adbbaf96e4ccc205fa5b6b93fc75665ee1e91212665903c394e37a9b3962f7ded8c98904132c71afb572dfcf518da24d48f69b29f84a2f0814cd0e0dbeeaf1fd20b6f739cc7ac81613206d7fcd88b8d357d7ecc793c93840a8136949da6bbb1764e5367865e9849224cc4305fb5b45f5bac3de77d6854519e3a4d53e83d9e182aa4b21e17c2240cef3a5e70ae581fb4d13d189c7d733ab53d2b6cfef00b72b1757d7673fd66a5de9cc72ee525488aa7a33245c1c40bdc5c65cdd256a985b1b92e3c1bdbd23abd4dca1ce33c4eb16f9412d3e154139214fa4629cb45265cf8371facb71db37651cb792c1ac540d1888b8f4adb026c6ef56aedb9c6dd1a64fce69bd746e79abb4e3d9e59fda3d22030d1826ccb1a3ac8fc9f7d6fb61504932c4867eae6643a2b6d079b731eb766662c46362e265890cdba069d4dcbcf98f6e43c66579071c2bb38be7bef627f3d04132bc8ffa737fae4e8f7f97cce635705f911367ae15f375d7beeaad935c82c46362a26549073567693f2c3087f7af4782610b8e13805792dc765dd84cdcd27749cc76b39becdb118d9a4984801f2eaf62284efc1d51a3bf77dcf2d8fb53637635b6d1205e96e8b7771bfb7cbac3e108b918d8cc91972b1e5a08dfe1e3e9bd78251b90f1328c8cb3f5b651446fec93a4ea59f201d65dc3d2f53187d42a6448e7a6113274886f6e73bfb8cb5fa2ae53c8636860aa938c1a40992bdcba67dac27b747df5db498a669f2314dffe9dc3b231bc184097245a78b31857cdfb4fe388f3715ce783cf34e936b7d642d41da67b638ce18e37cd52fe7b112a43b07aff3ba2abc0bc69504e93f9fddc5fc39caacd6161324c8f962534b5dacd4f9b109f31f47c4e408b24e678f56efbecdb6d08701999a1841b2b35add63336be6fa721e7f189de8854911e49dbf9a6bcfd1c5a87d8b08d2fdb2d141fa66753152e63ceec7ae932cde5c5eec5a1f7116231b6b3204e9bc5ccfe9edf2377bbd0c132148e766ff73778cf673b03d4803932048369fb2d71c8b6d326ceeabc919f2363c630204f99cbdaf6db40d726bb5b24cd334b118d9844c7e20abfbaf7eb698e3846f721e3b2f9a890fa45b3a9973ae9741d87f7b205b7cd545fb2e65d6c1f64c30e181b4363ed61ada7676f19dc90ee47aba28eb782174d7ec7520dd9dd3b665cf368ff152931c48777b2783eb31bb22e3e7e2c28184ce5f63ef39dbeab3fb8603931b48fad839d28ebe9ef5d9e00e4c6c20eb8c2ebef9dcc15ae77aedb118d9704ccc90efbeb7dada0bdfe4781b6e4c6a20df3167a753da9c451b8398d04052d88cddd2599ff31856cdae99c5c80688c90c6074ce9c6b7f10765cabad685babf1aee5fe276b9cc7d3544d5343622203596f7d75dd8ff73d378c233089819c7f9bf9c6c9e8746c2d0c644fc60ee374ad52669d97d1984318dd45778e5f5bcc316ef1cdf687acb137a7bb266448baeec3ba38b63ae37bd85108262f901c63cfc50dd6875cdda10f4c5c202f75edbbe7bafda86537fec0640c392b6dd4b67737da7cb69939649a360c6d6022863be7b55c73ed357c9039b8daba8d5aa72f42cee3fa5564b5a413322ee7b35576ca9abdbf9c57411e259c90ce36666e578d1f9aa3209e394dd32446c926243f1a1bf2b51f1dfb47ab91688aa6695abce15a7562dc394d48ea169deeba7b4e48d933212fbdb1cec66e7d7ed15ac79890942dac7fffc218ed9b7665c3979014d2e50e2ea74e7b5d670969af853e9d9dcdbe52d69590df78be77a947cbeebc8b12f2c50b639d8bd955198d91eb570d48ca8b9094990701e4c59bda11813a1ecffb24e4ac8bbd236b6c424948e7e9ecff47d79e832ffe480192e223e545488a5b33288984ecf6565df48ece7dba2889f4e1a20510394dd3344d90c5c8268c1248c855175cb0dd9ef436b695f338446d440fe30c1fe347445f0df9f973065fb7b51c33c7d5f4428ed4fab5f03942ae76b7e79ad3679bac9df398ba9fc842d983665e5e63414923248db0b1fb63f3dd1779aec0c808591b65f4d9e95eb7858e721ec7182aa4521fe4b2796c11b2d969335bb718b5cfa822646cebd9fbcf5bf516991b414922e465cf1d72bf35bf794f4448f7e5fc6f63361947f74a0eb19c47e8abf98aaef16bcc1e650cd665dfbdfd9ffe40506208f9d37943d86075866f7e2124acec99bfc5bad967e12384847f1dfeab1edb743ae390f317736eb96773f6b21eea8628d154490192b292f222240534b99826d08ce9324ae090fdeee9a3cb32c83aaee83fa41907258390fe587baf7d9dcc7fc21b75502208c93f21b4f7576366cee683665e88501208c95ffd7f51870d99afc8bca9420920249c175a667eaef7f99b4c09257f90b5698dceaeed175f73c779fc1eca42d4104af45999a699d0342d1794f841be65b66e9b8fceda16bb73913ec8ca13de1559a3b732636f2624fa4817672a94bc215d64fdf74e36d763cb66737c908de965efda581d8cad35b8dc90b5fa7dbfdab1c6ff1c4e9333926962deb03312e656286943ce0a61bfbf8bcd7e3d1925fa6c28d1a7232a61433eba383a6646e9622fd23dc8dad6dfadaf1f46d8ba398f535e82a480a89016d304a242769aaa69aa8f6495ac21efe2ff77bfac46dace2ede70e56644bd84123dc8051775d6ef5ecabed52f324dd334239aa6b678c3a2cf9b1135753f9d2794a8216d63f1babecc2a3f742b461894e441de6bd9a3ff36ceaef7effd16519dcb8c123cc8562be5fae0ba6e417fb889d3b4d9cc696290c5c80649c91d2483d646da5f27eb1669b483bc953ebfeb75ba66b7da3a48171964b8ae57ea3532ff50d304598c6cc028a1434718577bee45065d2f876f45c6b6319dccbd2ea8d292c24222d01c64335fac358badeb7cb0c941aec8d7c26e8bcde96f31ce6318070929bb93f6b4bc22abfd70906dd6196d8bebbdbeb0716331b20941c91ba4a55eb9b11aebbc0c574d43062f4252364d038d23649ddf3e46c7ae1fda081b21a3ede9bcfd2dbb191a46c8c8eea3b4c50b2df5e7e822a4ab8e99f7b20e6173c740a3084967b5fdeeb17395e1738990eeba375dadad5dbbd5102161eb0bafed09b9aec9da2124ff7dd3f5c7387dd96a08e922e5b7babeb5dc72ad0b21ad63466f7b3769a5712d186808e1bdea8bdcdc1ce1f78cc3c5d68dc1c8adb1e7eeb56efad6a5ee3dd8e87c4e82060ee9ea5bec5a7737725bf7721e6faa8dfd7cd556a019847c666dc1e7bebecd5717e7f1a713f3a4a011847c3376376bb0d90addb46ff10d348190ec56fabe3d63ffd673721e57b5534dda0c9da0018484b75ad7ec6c43e6f15dcee3c647429408182f012841f307f9f8adf8a2c3469dcfc63e0fa2366f0a1a3f48db9a2db7dceb56576bd5e480b81d15347d90adb2e50aadff7d96b5c9dd469a37e48b7ff9b6bbfe3d5d35721e579bc62548d0f081a7a57143bef59e1bb4f645ce63c647d0b4215bc3778fb6ab76cef63c76e5d3c5345547288ae3adb5d6dcdddddddd19638c31c618ebeeeeeeee66666666e6089f6bce9a81a4acf780462f294384a42c6631b211800e346c48f7f6359bf0d9e9cfd5f620e3e2bfcfd56b3d46ebb8d9e84dcd1a92ad2fee3a6f63ad3646f520ad3b9f163e74ecae7b5ba3c6f2e570b1079b7384f1c566462773eaaea3f63e7e1102aaf8bd409307d9d3be6e3cd9ecea98313c48c69eb7fb6bf6838e69c71f9a3bc837db337e34fea26ddd0eb23a735e8fe9a477cdf60e68ea20ed73cfc566975d66f05a3ac8fa3ddd5747e6bfdeb47390f15e66d4c27add5bfea61c66f3652cbaa6d4c541def637c2e53436beed3138c89eec208cb3ddf6e3c741346f906cfb7eecf726cfc830529f4d1af2b5b61c6cf3ad7aafabd70dd2adffd7cd93da36c87f7d216dcc3247a78b970d32b668a79bf4bed77adb5d8384cf2efbfcded76c9dedaa4146660bf6856cb548df6f1ae45d0c23a4ccbae69af58b06f996bb95ae696b3357d73d83a4b4f65b77c1cbf0f6bf19a4c7199b7de7e5e674fa6590917d652dda66f6b9854f06f96ebd75dac9dcc70ae1a3212b5cdffcf15d47eefe31c8f90e63abebc1d8dcdf8a4142eabca67f43ead1551b06d95c5c3fdfa37ea1756ac120affde6da8d6fbe38e9d72fc84b678c76c667de62acd50b923ecbb4f2ec1b99fd58bb206d6d8cb5eacb7573ec552ec88e76b1b5d3f67df55ddd82ac8f29336fac6a41b265c8bee9ab1f6d8c340b92cee83ef247cb20a38e6241ba1aa15738bf3d63cbd12b48f7e2bdf0557bd77b736a0549ed62a6edec73dbdecf2a4846db32ebd6b3d7cc7d5241be5358df7c5fac7ae3390519d775eea96b8fe9ec35a5209fadd459bcd4b5e7ed4641cef91d6775bde0d7f6ce9035fa85f0d508e1bbd7452848c8ce27bb6af92db72d3e41c6b710d6bf3dffb2d8a213a4bbcc42a6def4459e2b3641da47e97ad0ae09978b8f09d2eb8490f56d0f2e415a7f73ad1721bbf3de0695202fe409d9196de7983b4c82646eb3ce7ed6dfcfcb201264b3f9bcd9fa864790d1c5b68d56462773fd4690ce1bbbf894de7efab408d2dfb608e3c70799ad4904d9b53edbaaa5add9f8e21064b53ee76b8ed54b2f83429097b63b57d7b7af36c320c816ad5bfd2e7f9bec1d10e4ac90f9b6b37bcc20fb81bc6e3d325ff7e9a48bf94032e89ec1f72e75ffdc3d90f52bafb69c756c35ca8f07d2b5f6eeceb7dd4819bf1dc8f64ddf23adf6efbcebe9404ec636c6c9de0bfebd3990b679edba66a50d69b338900debb2eead577703d9ee3eb76d5a6add84cfd940fabb5f6ffd67f75b73ce8cda6fd1afd0b9d740c29fb0b27a978dec6f391ac8f6ae5f9fbf9beed29f81a4f02b6bcfb1177b95817c7ddfd7357945b89c8d8174cbd8ec4921bfad0e0319ebfc6f6fc50927645a86f4b71c4f47a3c775a92343fe5fbaa24f1a6d6d97db0b6465cdda36cbfc2d57ad0b248dee35bcfdcc3563c8bbd8aaae27ab1ce784ae1143fa5ddc225bff1af4e8bc13f231d6e072d6debcce35e7847cede8dbda70bd5ed57d1312b6ed78617f378cd35d1332b6d7ec3ad8d5bd789967423e6be3ebf6d31b42cb1c13f21d73f5fbcef64bc86967bd7e2f83cc685f4b4867e17ff7476baddf5a09f9f7bdbeeb21656e394a09b9dc3abd0e7a7bc70a27216f3fdb9a316ffd6d1f9384642e3a73cae6eafb5cb348c8fbb73e67f359e7e6650609e9bafb3aeb1f6b3767f608f9fffd6ddd9d73b62de608d9dc3f658d6ffb657b1b212d85ebed5a333257fa8c9090619dff6b45d61eed22e4735b8b5abecc5fe354846cfada6d77e337a77176222465eb229df0355eb3672342ae49ab6dd0a77de65cec43c8c5abf6834d27fbc9b00d215f5bd6d969d95b685f77212474b4ba181fe3f5e6432684b46db6dff65ab3f5b28f43d246dbbdccad166be387433eb62c9b0e5aff87d10e42366dad5d6bad20e473eef2f4e8de318d3110f2efdbaf8fa3f5c72820e48c6eba7f0b36cb6dc23fc876aeedaacd9b3af3e60759e16567ecf274f7f0f39efa48369173f441322f8f6ebab518f305b90b39de9073725f9eab3666bb32c879cc2c0d39f8209f3d5ede7aaee777bdc779bcfa0b39dc9097327bed2edb7541db57c8d1867c2eda6efc6fcd79a333a79a5b8462b828401672b021d7b42ed6d77cb97563b59cc7cc43c8b107e97f9fe1b4eded5ad5ddb5de629aaa14205ca4ea6ecc626463801de458433ea73f2bbbcfcf1b7cce79dcfc1239f4206bd7e598ffdb4ba9c3c979ccbac83475403e9ecea7936eeaeb3672a821e18dee28ad2bde08b945cee3694a22da7cfdfaa13e33a24e10d1344d53354d4080cc70e1a323aaaa8f21326d804cd334d5ce34b9223f3621ea6e3cf5ed34857ec30800478e3c48e71832ff1bdfbc8e617540c88107196df51a1f8b34b267ebba83b4de0e3aeaac33c86b393bc8e5ee9dbb8bd55e16610721471de4bc17d267635cfcdcbf4a07e9ef5683973d5cd6798b73905c9f33b57e7fdd385ddf20871c64ac77ddff07637d74d6fb221172c441fe6cb7cfaf9baf3ebf8ef378f3e01839e020ed8ab3e19dac2b85eb43a056418e3748d7adcddb207ddc2b726f380db95ae40bd7a4d429576b398fe1e3400e37c8656b75932f5dd4c55579866eba3f44a6c9c275841c6d9097c1fb1c63af45ebd41fe7c9e460836c7575f3e8b1b24a1b6d1bd1c33c167d9098630db2f1737e6b756bcec5feaa417eabefebfb6c0e7fd99b06b9a83b5ae3a37da1adeea241526e7459fe37fffff93d83e4659fa374da7663f4f56690cdb3f931b33ce7b3d396415a0bab63abebaa707a5b32485ef3dbd5699fa50ed9a221eb9ad3ad6d95f6dbbe750c92b5f77c7dde39fd2dad6290b12fdb7fb4b938b9d21a0669179bcb2984ccd6d6af8241ae3aadc73add3bceeaf50bd2b2ef091757e610ba55bd20ed74d4b2f7ac1bb37e6917a483b6ce7f2dde8e943ee582b45da3ffe2c5fafeb2740b32aec8625cf6368dd629d582b4afed6b5a5f73cbb549b320a38bfe91f2adac9f6b140bd27eeb369f3b579f2da35790fd2d7a5dde627c0b32b5825c6bba8eafddd51cfd45ab20179bb53e66deef61a4930a9299bf76e7699db519e714246d6daec6fe4ed6e6d32805b95e83d456d74ffd7bc628481a1d5b67bd315cb1bd78866cf76c2d7ece277db52314e43f87d7adc77139641c9f2027d308fbbd06676d6b4527488793c6f7decd9fa50f9b20e743f8d8f3e89c65944226483b6765cff432537e0f2e414ef7165f7b87b335e7500992bddeeb7132d88bdb7d49909399478e70fa74bdfa2141be175d656c99df11a4d3a68b3238e9f5690469d7a2ad394bfbc6d56811648c8c3a868d3173ab3a11243386cc9e5617d7a3ed86202fadd0b6a5d3df53674d0832bae560bb752dc3176310a48ddc6bc1bf0f3a770804d996a3cc2ba476adc6dc7f20fdf662d3ef5bee6f6b1fc8fbccd66b117275372ed6037919474bb93a7b7651ca03795f73436bdde78b0d7b07b21973e5c8e2ac1e2bd581846c9b356df3bde5eb9c0319efbd8e3fb275f7d98a0309e1f467b13d7803e993ffd6cbae8dbf9e17e7f1c7cc8faf20870de474c7663fb61665d3a319b2df556be7d7e991d25703f9dec5c9fc60b3fb5cbb349090adfa1de15fe89abd33901e9788a864f0aa959196a22086310010425949150063130030302c1e8d86a3f178a2287af203140004597e6a9a422e124983c1500ca3208a82180662100008208410628cf14a116e00c03ef6a440347b8ef61d6168e8db8d0df74bf5611d510b68ad2dc4e0fd4d8a705a5d0d20b5810f42cc59fd9f4544d406b19c83a5727210ac88d8bc7a22b7f22c74413e376766d43f788a5fac2fdf055a20aaed78093242b7db8e7c7791f20bbdc6d4e1d1c0af715c1647479b78593ce764b6f93fdf909a5f5470cd7fea27029f280eab52b3137df15661e3323b8ff7015830f651a7273da49283635c46bc941e5f330b5c53695aa4c414c740ac11cc8cf434f43e3b88e4e300cbcf1b5855b61443e3b7cf3ae2f58cbe3a857309bbf341814fac97189f5c7dbbd563afe96028de3f4d559c637775ef79a3917c321f061cf708f244ab71818e38d3693729bbadf51eb454afaaa0d78a3f4d96679d2e21ac2a4724c040e4260cf2b64988d8840a4f118c18df8bb57c4b12d0f1378c3da0aaf7845d991ff3d2e20506a60f0aa374a0c8efa7fca2a0a21527897b47f660b144cda256ae37b42d3e55cd014e24343b0245cc00d7683431844a4d024634b43b04256600d928b43186524d014344b43a0635a600ad515aa79145472ba4a62af3940685bac813d562c2a15454487a9b188e64b508086641d025fc6b50cfeb6ff590124f46d0febcbdae95a0e40be251b52f6d3c1b83e9328941be548d995566b1a4bacbc4fb17900195418442a82bab0fe602070a2030b4eed4f5c941dd598cb823f387efd4d4632b82e965e5d48ddeaf1cb73606e2728a1388010a05ff86fe03848fab7cf105a2e4006f4b5c860909ce6cb43b4223b61ad1179b067e396222d21b4b2fb206a21d49117b35622ed207421dbb22a26e4e92315b2af3bd8e0d743812230636e22eb60c9c3ab2a2440bdbb494cb10a390de587a252ed413d3d1fe6ff0381dd8e5b83a72928c8a309d4fe6985bc7bfc47c0397c5f1dbe276fa2f2b5ff4b24dac5c183104e9264bedc585d4cb8a6f1d058ca38db8ce6e276d68cbd03a55b24bd0f5d2245eb95cd7fc4bcf0aad93978332a153c81bf241fd5a11d38da48bbd066e1cb1474aeaa8c08413c8ccb8a5ff1bcd97b5b4daf0a490ab1173b17d4c28bf2cb7e87f9bb272156230d209961ab214a29b2415dbcb2d9a5f38cb4feb8525bd6b510ddf74b7417f656659966b0d9b666a9e068e85db46721620ba2f4a001204182f961cef46f5396b295ef6f0725dcc37df84e951d1da2c706a31db74f290f1fde9e2a18c31dc6c1ca3fd8cd71ea7da9dbbdcddb6d970c5a861734295122e6558f85afbaa0ced3d0e494ddca948262bb01a15bd7ba69d12db681ba49b30cbdb7d19f28eb2710b7b85237b6b0e9d4bde82f1c80913ca03e9e787d9c605cec4ea6c6783853117b6c7ce70855075dd0ab7eca674514177509eea1601f155d06c868f7e9ac0a2bb4a93744b2c62e552a7df82c7c80e9410c6568f1a106f5926ebb53c43f68cca8287dd6264cde7ba3e28ed5506c217ce7c270e662b2b5907a87fe8cbf965efb3b7b4b5c7729ea531371476482df40841102bfaf61c052b0f0f36752491b2b48b4d352190a2dbc7847ac805d697dac9d4db56be954d37ef10cef6614f290e60e834e594ac79483a807ecc9167e8b4e7fc17e64d6a7197a7ffb60324d2c0f265701d05829a8c5336af89c889c2e384a5052572bc0366cc4b3f92f636866a4a0ba2bb4e41041b08b68d12286dc13911d2ed884c9befd5c8c24c76df134c28b0aefdbe99986f8071a010630bccc416981aba14c7171b16bdf4baa4b93303585488bca03c33f1fe37cec4ed5e7db1cd7835346c12ffd18a84c2146237de06cdcdc199f64778ebcd2534020bf1c675f868d73e0457b31e2ec063f3fb0a85487dd6f5fb2fd7689e767eed5744420be03cd8cc05d25fc961271803032f9c728593fcde6332a54317018b5a471ad5e0677b35288e62d90ee373d126c2e93a29f3d340ac41f68f323ae6e2f80e81b27bcb68aada9e470c4716f437ef848369151d5cf6d460e9a38c27532573379c0dfc3517c428f52d4c94ae18c3bc42254650002d2c6493ba240c163a89265f3d0fe4f44eb282f3933c701949a201f1c217b46b28d8f5f9340f6559debd6f3a3d304a6926664c1c7740d4ebe62c7e155015dcba450bd716f65abdc3fadd02458bd856f5b550c362b9c58c56695bf06b51864573ab2e5ad8b6b8d7e28755e416315ae8b650d7ea1a16d32d68b4e06db55c8b362c9c5bd868f5b6c55f0b342ca25b55475d0368f1440ef9b59d3bde22f31d0369abd85d6897557dec455c4cc6a0f8559e314a7dd11cc36957b5cb6c70c7015b75b281a61ea2d4062f8d92666d82865b3a2cbf715deb6b0cd46144d7a1ffad2adf582b639a0c6ab5f23da3b046c3335cbed52e8d6b32de6aa03d6be437b4d3509f63115927af415d07ff1a85606d26c3d91b16695c36eb171bc86dc4df0d3dbdaa93b17e8d9977d0a5959b8c626fd8908db0ce78dfdc349ff6e9ba2abdbc22ab74781919c3ce111c1ef12dfe652bec7b42cb849dc1fa7340d3d0f77f6d6337f7df82cc9804a31b7468715d4016c709787d4f16e4065c7fdb19eaa279bfa83073765a6739bf1351e54f40e22a2ed8f7640d0d477fa262f489c1ae543fa3dad1213ec7a51a841cd666717e315c756d879077f10f392723212f29cee423df9a0c0bc3faca117b65bf562bac05247f0d95d89e1da5722ffc705194fa79afadfc08b0004faca5e3497d63ae05195f38e2262756700962671bde531c050685e24b445c7a0a995f26d17990d010f188d9b926e1d70e778b0f1e1411ccbe087cbf73c6a8274169ba76a8ed86ea20f28f3d7f27e94f2ec26f5cc5086c13f48d54e9f2bb5f0e7aac40cdc85e5c5522d0d07c8f135a0d67d9ef80b87cb7c8ef882c1dfd379e4b1ca17ff286936d98fb4fcbfd43a428c3b888510f14cef3f8a1228446e62f3ebfbf801a18e91203712601f84ebdd7b4c8d53ee143988c3ba2e7cf07f0de2e122141d06a40c9bdaea3c5d667edada6624e0edd34b520922ba36ff7db796b15cd379c03b7eb0cb5923ea410a9d47b0e2f223fa4fbd6e450354e7f2504f4df5cf4e1ac89b8eb197ca51d44e6c78fe0d066e627ec8ba4549b6f0a72cd04ebbfe59cf1eebb1e294fc75bedce8f02c3c5ce6769e661f1af1366453ffaea09e3e2c3dfb8dcfbb9def19990f05124a4d3bd87cfa9959ca9249122ee17e94c077ba424750a2a93e1fbaeada94959aab5b77616f436a9c6f788db4ad2b58c1a6d0e684530461ea6ec69e43b661f939b7b3cb02792844409aee219971f726fc7e7d87c8991a18a7adac404bb843238f551c28e41c55f69e772ac4eb54eb212708e75e70f176c0addba40d31567ed46f177816bc26d2282a95b7ef80148e8d3b689cc47ce52f679a1435fca2503b85ef17607876b7f92923f06f74b9908c0cc0730b031422c631b3e7a26ad511bbebfe0a908d334737b839333df9933a4b779edcf934dc68fd7432a623579ebe5cd3188f5d1aab6bf1ce093dd198020139c92dba6a4850e0596c1f317978aa487884803257c71fb8e8bbfcb2566fc88572d71082992bff27a380da0e51382d7f438f4070dd56fa239e89f7e4888d5c9ed7685e2290dcdf23aa6a8c02fde2cd94903d347eb7033a77246c998f064b19f922b17855903e69d553d04a0f6713a78de139d6d1ceb4694ce727a5c27ea06a3249a2e2a559a6da78357142b20e0089356cc31a9083ae55380fa68403147bd78ece1123224cc9f5dbfa4b07e09f8bd21a114249896b4df49dae845a4cb3cbecef597936000d8be25cd19e5b88bef4a557c398606e21965771450f4a5aa10597598a6fd4bf570190c308bc353d738c6f7891c4f12ca7b93a81fbc00505211291fffb4fef73b01f66eaac5a1bd1756c0818ea8abdee109d333e504cfa007d240142d0039dc1dfde81437d3e7add96ce88716644aaa3e275acd46d28fd9dc73f345f6b3f66ab40446a8c05e06ddd0845d07bc27f6b7cad87d00408eae632c5022bc42de5f160def1dc433bb5fe55d7b31e80aaa249219f58926c5970b2ab480bb6f8faf43ca4d833d97708dd997e0897c4ffe2efcb8f305b23c2015a9f70502c42bc0e5242403c3c90272dba7f8fd2538604a5e6221148eba4566f9af324ef844bee428366f32b279beabe6d0ab7e9c63c7dfc2e59e9281e8305eb71f0391a8aab77a1fb73db179e58d100ad2b09be65dddae58f344dca000f71200e27b63e0e7f9563481aa3dd2143e80d118a5aca32c02782db66e6439c9cd38c4fba064de9bc0de147522bc701f2e00f04212ef34ed702e017b623e53f9e48fd659da91e073b3ae75d2861cb3b41a77770d3627fb83aa75a2e88907482fd8586f69c8acce86d35a3b515a06c59c3e1e16c5944bf4e1718d6261d46e2fec908b569dbee34e195b6d751e188d3c60822849889cde55ad4c92d74111e0ac2b65a50683789c797e0d99f3b8b9398b744d2cc17c366868104f1b9f9db742325431149ff2546b7a51514829021be68331d8dd47b31d3799476ea3020419016cdb2c37e6a0a96dd19bc7e99a554f2a2ca1227f5db47b7e749526c9d65fee40e3fa2d6c0a6311e014c430a0f3220de787861ea4478eae17993b2ed31ec8fdd1ca7884d399f2e31fc8c9fe12c6c38890fd6b190bfa1341e63dd6f167abe9986a0853e59c434b30c1582a5385524445e2d7beb7176add1b8ff3d1a26b790c03697bc14646b4429f71eb20ae4db9a8e6b2d5d05e33fdb4e71caa9d35f692746242f49b6ccf5d44df5fd1011df591a66e3b9ce474156ac66113d8332499af1a49a1bea188fed2952c70de93d1b7779edbcb4c5e51b5c3816a728bf0308616e637cef76fa6d26ce824c5930dcb0dc1782df32613e8c29dd89461967585c0073f204507682b1cfee09e7cd246e0259b780afbf3fb8e297b2df2898245b5ff37a816a148cb1c185a76798c4209ea1a6cb3bb71fedf267553e622a49767e724aa594ffe57652577ca2036b1429cc9e7a528185d4302b9dd17fb3ef69c66a59620a9ead454d36ba99a88a8b94b1ed22a5677ae3fba95d37a179571f3d4ff693c1de9a1a36bba217ac15f026aca5ec0e28188eaf2cc243f89bb9591c44da6b52e9d773d17112fd1eb4b9df655206a6b87a074a0c55fdf375e319590e62e4969c93b0e254a792ee253a25c9dcff4d506ed53961066aaa304f2b18d2b4f965137a6f668f9e2914cb8b78f8590d852023ef7afb80f030d5659ea1613b2dbecb41277da0def3deba0051b1f5e71c93259b7dedb64d844c61cedd5a8c377739bcf408afc50546cab65551eb3d8615e6f1a500fa5bad569a772507b12a557abbfef016b4e869298811cee56683b7be466088f2050cfbed4cd922dc70c3753a04dc841acbf06d11da42057d57e45f57fc304edf0d22a84a55d982864e8c754e09c40aa1fc9e134d83b965702b8a48bd9075ee7108fe36a68d18034e3f5bf1997d1bf273880807d32fcd6487bac28315d286ed401814f921de50198bc57604ef4674175d442492f921d0630d261ab364cb8057aaae69a6b334aa85d99754146b59264b6cf27be1b87cf894fa7fec72f3d4be3dba0344647c0dcd4fad9a0dae4e20a12947f02c2f33c9ee5110e8a83a04871538980d9dca4b797ecdc32a735a53528123741455be5d05e27968fdfdd2cb8f81e75d2d902be426a9ce9e718a14bf41134e6e93b4e3e0ae32dd9e1bd8cc355dc6c58095a4e90d8b3169fb0ca5cf2c76dd4b0cc5bc19b54665370d63e51191cf0c7863d68ebbc1f22335fbd4247b004a43ff3e5e398e32b2b5b00fe91668af55a94639d340bb836564cc5503e18b7423fbeb9204554b9b41987bbcd82afff1362d666b76baaa3beebd58451579bfe256c66c5291132383fe50a489c041394b6fa2501b98b8d9ad5cfdc79a3a7172817c0be07f91154af6125638cdfa772e5390a4014952440686d30e572d1242c68d4fa8e1e4c381e1c461badaeb023523944d8d49a3599240faa8fa8462137b89c17a6117f2b3a9114421b9a1df747148b22681bbe9617c0a6647584499be813f8474cb31fbcdca8572c269770c1fb721ed75c3be1d0bf36bccc99d533b007ba7f80ea2035f5c5587f28bc08da9185a7e80d62ae65e90440ea2df604b2fd2b747feb45eee25eeedff5ff7228f4823e108e7506d4b0b8a864065686a2fda4f6b056480ad750c41e436abef1444eb1201061109633e14a99f081d0df7ae3ed84a69f8ff8095191e9f35e7a936588e3bb6e518b1c8dd5520ad87c6662381c920a44b1368709cc922e0763574ad0f8a5e106b4ddf2cf90331013318ce0698922771336c3e778b6f92f8298132ed5519b82bc9354544ccc3d8413997b4e641973c296e87ace7f9d26cd58a13461fe902626f4972106d7c1d4b87e6d33ef76f8a840572bd0762a0273f4beb07e74e48e481c7d61f3c6c54e758dab93d8525966583625153c95e472d537b64b9a4f532a830b43043700aaa39c8a7f6e54bb1f3185200f12e9e24e31a827493a4bd3e44849844d0a617b77304829c986729ec4b6c0b0fd58e2703e6fe4d990328d6c30f0ca073a0c8da671939601005ce50221ba2c4a5375e525649a12d86d8d6b67dd4dae83d82f2d4e519410a4b7285480feb5919d995fe3bd95752841aad49190b4d809316bc0b658fc75b9979931c9eae19279e5eb6fdfca417fb9a9f6ea5efe4ef1a370e67c68f2e98a3695be1d099f8731f5aa5f5a14b534368a4c2b4c7b5b6b542709b4becfcafbb8e73e6ce0f7e61edaeed2dd28736ebedf03e8132c3cb8f3c917e0f95cb08c28b3c93fa4e1a468a4e967e8058ed173fff9bc1135503c50e7f6aa0b6a80cf1941a4bb1216db06d8a5d9387c8451f889420a5b9d78485512e496d262e2c170c11a5fb88abfe294daae3615c6234a3b443ce6b7ccf98abe848e7cde138095329f9bf2f591a577b1954dd07d5a278048dc3eb1a1ede406bb26b7b8def94b67fa1e91a49291c11225936b163cb38a1b24b1b2504ae35ed4afd2010d96493f0a2e8c791a596fad4dba977ade6be75e6b32d8592363b0936cd8f9e47c9e3a6addcfa17fce0df7b885d27a64dfd6d8b2ab779470afb259403799c9e50a4e2024d09b92b43ce18776002c5799925932a561ea3512179400436170d10ef6866a5f2cc22b97c81cb495ee77379ab21e4c2c4b5d150fe7c5327dd6bb1ddb9275bd95cbfbd760462a798f00a2787c4366d2942fa8c2a779441dfa3a40387e1b5831698a9b4148380d157dc25f9186455146cd767dbbf4b858576b03dae235e5e9bd54039e34f55a7e26c60b1ce8a51b4de9c0d82fc766a2d032de07011ea0732a90431bac4dd5abed3e11979e3ec6614d12041bdfbcbe16e01f370e9e2c3887385e0ea7f50cbc3cf6e4f2dad01c243cb7bc6267e100b6c01ab62b2f0f0367287fee581ccde8adf52ef81fa2a5eadebbdbc21888550bbbf25b110b3e8f17720570f2421e5ccb58b3c687f050a20eb9f853c9d23bc9e2a2c6a79994de238040d1dc7a0f266158d08a800eacef02346932bc301f712801f068d24a095192310014fb0d1723eff46c802169607eb100908fc48247546758569e9f109b1ef79c5c4a72cce6c4e8c841a88e3b278426c307c76872285f3a058df5271786c874c2afb1b2f8b1fc73bf7d72d1b341c6585f8f9fade1b6e5d6bdf442ae84a7de9d14c82a49bcaa16aa217dd2a1b625812c352cc0ad60a958382702c2f6903eba36872d22582cffec8d4885a1f7ea2a8c21083effbad3cf83d14b75e746c67a69c74fbe61d8c6be7e5455a24f92c671b98117011c1ac04dd9afb756cf440606a1ac349509f3bdc171f51fd526b06fa21d41a74176fc92029899cbdd311876cb419644facd1d5765fe24e06926bf1c2a3c5549383c287d49bb9340eafd09642634eef18504e04ddf0ccf62b06bfc0455f1c52e2978cbc105507d43c5d391fd6e60e243005886e9082a488c80f09564067f9329d546ce0b47435484e53bfb8a3010bdd463c8d4901bee7d2d21222f618e5714580eacbffe81fbef2739c0713a880e8ba7111650da432a158a6ce78f77644c1ecba2cf34915f50ce62d35dbc9103e0aede0442700e43935d1732f89e9816ddf920a10589d0502ff690d3e25663d2e84f49ed758909865c01fc44c568d0b8bba6e1f9bf59e56a146f1dcdf4e4d458e841d5836a6a57c2ae2e04f2f195093c09372dadf73ec1ef940daa4603f770d4ac65338e0f90cd03d758184879c4d17767de5108b0d4f22db99048f10f46f0d321284058a158a716c077093ca5215b380644d1d922e1145a7dddf716015dfd7499a381be17f4ab2ab9fae43d5db30973aaafcc5223883940d0d3b0a7833903c90904293c4b7b88c7e6a8fd734f5428f0a29c4648c418a6e0357f2ee9c883daa3135e28eca0a75e06b18b51f8081c452114027f1b64006c59047e96c200505405f048f82d108130e593f0190a0241d0178123e0b64200c7a9f2fb40a87ccf1755ebcbb25c509d595394aaae116e3932d53b538c88877f023f9863bf09f61898f179bc670cc5efec43c1f319c1ec5d8d737aac1a2286c1787242140145936f5397db79dc31f8626dfedff3101993725363c3246bbb538d2c454bd0a25cdb69cffb77f6130391dc204e6d7f6fd2bd7aaf37f649bcb48f6ebfb5533becf4bef2e5e4530a49a088f88fb1581d153fb0200621229d9f3b6f87b6d007c589f3e4b8241b5549fdeca443fbf6fdb5f3ca34f45abf8368f47e269ce28e1091d392f91a755e647abcedaf931fc270206837028b605727b0649f35f92ff79edfef7f31971628892ded4412ffe68702577d8c0a008cca2b21dbbdf19ab264a423fe5105c80880bfefb1b04635a45ceed05040a342edb74ab7298c817ec3a69fce4c638ef407b43a4c022839aa1542ef34c57aaa6f86ed755e9199adc08ec0cca900b9f9055148b23d931eb70a9594e72389e1e639be4641befb49b6283b53a6fa2b48a27bbc90dce3cb532319a6fbfbb9c132e1ed0e52ebd1d627472098d895c0fa10cebf8d7f3a3911e0e5def01884ceed6d2bd854ab07808e83942d3b37b0feeaef0f36f0a87abde0620696634cbc1466917b0d2ed67e70bfe1e5e0512cfcb27c80e8d7537cda2a9d93649fa61e2fc705df0b6ad3f520bd8faa12140f6b52398b8b0c503f11a6eda3977e60cbcbb8fa55b389668d5f6e258d8123608a591d9cd52dc24a7f7f8debf662dfe958de04526bd4a9c8c37b8c44b1b7ca7bbfff6357afdf6edf047f13988dbc4cb4641bb9003658e1c763c8fd206b96821d1281d2fee0be074c3b4b56a48850e09a8eb9c165631d805d85f6d238b847a676428dc9b04b3b742021d7dd890972be0a32abbc913558a4f417fbfcfb396dd189dde5623fecf9af4dda686518cbd2db55081fe335ad8081db398c83421cdcf230ffd96721e7d8d921092d617cac5a8bf4dc3380c0d83ae6f739318fe3936f4f021b45bf1a112c9167a48a04079b8422ab4e70bece405eed0cc0626fc4bb1e5a1cfedd31c74abfc41b64ec5b6a9419a002fd88959e72a605ed59206ae936b79ec0a9743f981e128e05f709a83521d048c3fc2fd7f9607042476c06ef5fabc2332c583f68de3c4bd884da90996ff53012dbd7e66ed4ae4ca24c7e5461b4221fc0e77752678ee914ec852658452bac7c7320bcd5c11f32b09bdcfa49f714cacb9a2076eaed96c46b1b95806c65e154516a08832d769fea12ed0504340d1f8799d77757b00f3a568fc137fd06346bde0d06ae1f50856b6c0d79c1ae930ae85c9b9ce4d3d83f6bed66187a6a6703346b27bfd6bbcb0daa3801ea1cec5af7b85a76c7ba94a3752ff3d6083e5f4ced826068453092c869d74847ed76ff96f6b8258b2f752d1041431af1d6847f51ad9911ced22d2c5ba38aa970f63c7bc28aa647caacf1c0db3a612a35d5fa9b74efc917b9cd8d727ecda576f0fcd0f92fe45e0be1fbcdeebf68f460d09511647b6448a2a791fe7dd2a392defcb3d7e461f68b0b42ad999d645549e2411ffba6f120fbac7af9c24e72d818e96a8884a75b72eff36d0e081ae6b030de0c3037d1fa22e8a99b29616cc7924b0319346d756f3ce9eb78f62d9fb0a6319845546cff6f395d16e19e0f370241eabb1cc453170fdeafbe019f0130e28814890900510e15cc9d3bf124b28bd4eb0018a8aad49acc320cf08d58f7d32f486ca56ffaa2c83b4b394cd1d5d9e39cb303ea2de75b2a3f5c7e3261a345f2a3919d2ebb0e1a873e9a76d568bcf8eee7649b5973acf71bfea9e1be04c6e36f387bdbcfb65e5efe2d1a73c70e79c9569724cc3fcbd7ced46ca039b2140a46307f86a172b558eac56da7922d72378db41c14ba8b0d02f70c436c623a01828d724342d000c5d6dc9e70f40013035e4d1c9a5559ef39bea42c5cdc001c6e735390bb9370ec05e99fd364b6b5068be035e66edbaa08b38db961fa35dbd2ab754f1f8dae74c8d07cd36520447eb6d52e78981a22dde389761af78e72d7bfa7ede68cc5cd4f175b5b6817119e9db4c71f2d4298bcd5223f37d85d72e5dedd8466ce5c0679e85ec930bc8da509be3654062107f0dbd5789fed93a1e2ff2398ad48317786135b7b10d3b8081bf62b3bf4e26f338bc7a38b8727e083d4ab3839bf8427135efd0d82895bbc9f9e5397b117beabcbdd403fc0c9f13006e2a699819352b73a9627ece9df07df6b4e93e8f8a3bb9ed5baff4915f9987dacf7a409549f77ba0f8e58b5354921994b0bf061a32ed41e3dfcf4d534caac4356eccae07c98a338af9245ae7e1addd5dd6c0c534431875f09cb334c344966eb19066d6b150b8198f40a6d66afe827fcf8cef3f3c3b2f3a31c992e11fc1fab3f4d802e4071ff7f1dda64cf41867347a4d25d98fd5557a34bfd07b77efd1796d92ffbf28bcdfbd3b8ebb38ae393879a8c44d60b8de1095c7462fdf67077bacc80117e78ad2c4cc86081f24fcfd1238d23d1d018e194e50f287ed4bcbc51aa836cff51729b9a019fba6ebdf979dfb7656ac33a5ebf12f9524af9d68ee00f30a1500ab688edcf2e495afaf7504a8e340e5bd8f9de33878933691f04c72feced32bbe92e301d7550b2217708cffbbe632094beb5a9bf23cfdbf5aff71d9d8556ecfefbd326ded2d331e1ad67f25f966e604c89445dd307500817c9c5f9d5fab86d44fa7ed2737c43d3782e759da40ad9e8418cbcfe50cf36ce4de3e479a4ac5e23c690b73b69f0d27c5983ba79f37d4d97ac3dcca5ed0dfcf65f11b6a7862decce17ed67689dda38a4df71563a9329991b1db0887b720a30f28a74ece4e1a96ccac07988f462854e67f9380c242ef09a8efe95a6be4c02706f8a767765b67a7bad4e8bb479482e163264db6521dbaf70ed6bff9820e3c1f850a06e9b37b2eb3ef95956ca3677c0c84ff827c61cd492197eb3d2e0f8910ffc73639e9fcc2978aacdbf453d307f5788445cd48e339a0bcd69e4693bbb078598b3370fc9a99ebcf8217dcbaf1e6df993c58d2450c4747bc637db8d7d6fda1a3beccc7b28ebc28e5638da8f94a997f6aae02aee6953dac94fe83a89800394ca57f00c68c63dadfc0476428cd7ae79ae796f362a21dacbeafe232d827ea9b47b8d88cb18f9ce661148c2dcb03e7aa80730bbf4b12c9b38e77db696593c1b9916ef633bd1bf164a9e70353d5d73bf6d4ba405b24700917aaa4076f11d10e9ef21268077620b0c9fd4075feb4d4c298bd85913b18d854e909371b241a2b4cb7b6bade9fb7fbc8dcb67f631955134197d30f13b7a8fae12b7307426f55ffe6c477d4bf90aa352da52402608ad4e91f14d414b0fda2cb9567643c8f6994a6a0d1704e7bbb38e9007cb4c52748e7b575960bd572b1cd338a900d8dc2f4b95a2029c99579767e666e17b98f2ae3e438f99c46a420404039b0d0cc2f151fd5359a1ce6290169a330b9f5c73b7fc705ec9831fe2b949869b74ec5e83adc3101a7a6886a72fe89a748eee48d4f270dd00f1e982fbe3da37c00062b8bccfef710dc6a81764d66f5d05906166d02da05b041e7fd4e890bd5fb63d717f5e03f6f8271f8e32fa46e753c073c878a0fc3d2041f4b1233e3f077e6e22b99265becd40641939794df96b46c6c78810fc45949b6054245f53ca2f71e2dc14f97b32007710852f07930f0b2cd3403c85cfb83e58e073719f05728cedd3bb2b3d194f96d3f0bdaac5fc67f3d782be3c84c7fba177c6ca61f307addeaf3931510fc45d31b9504ecb6baa7c2dcfe637eebd6d6203863f3fe95d91a5010dae7950ff0b6c14675dd77f8e2bd9b37f727af36be013e00dfcec11044c80db44288f783507e69878ce26e0a9c6e29ad5c8e0a181ee9ed023824f12035680596bb80758461dd835597f05605ef5756d2edfb8f1ede05351ffbc2752e1f3b016ad321a362abcd28e251fcbb663d246d1402f1e10990faf604733ddf1ecae3a66649f9dd3a1b9ffbd132d4bb1af11ccc174106ba9df3dff558c03489ec2446776e2e52190492e3677fbc865a4dec930d5a14d185ab7bc0284581a4b32fae82e1b384cce3317d7e8409fcc7201be49c10488e2288694d029070641f57a0f8142b559a5622366c4f91f8f41f315b905711d18a3fb5b310107d5d000dcd57a641e325a7aaa283301becbaee001fd693f2626aed0ddd517d39048fc4abb656841ed2e669be012e160271bb44479cd8dfc83ab08427e845060c849ad94db01bd5156a8de42c76f236cf1920e56b8f7798c61d1fef9a4588147b4dad0d3ddff7afc5bb02f4157ca808cfff53b0dd86a760db56cac048ee18e3b6d0887aec975dcf9c1aed39ba265ed82ae74101403e46784fc96f9a7af9444a96897f7a73e2d8718abfd497ec01215e841d0055022443f0d9ad14a05e097e12380a508e89c760496aa059c03ef8104419aa3f0292f801b06e1fe6c81832d45054316c108901d5428463b35885f8a87009726a1b3da1078ea367016ac071a0c6b8ec45154b369f48538087e65089ea88a4147eed153813ae0dca8722812429f1ac56f4503e04bf3d041650468761b3a8b56ab3bec12c0960d73d3631b9cb6c664d30b1b886b0d58535a0d22d42a2890e4319a7e519017c1c320f662000729470184aac04f835c000b8bd09f0518a0141500be123e0562210c7c927e0640a012f420f00ab85408c571e0097a9900c0cba003402be2d02329c68025ea6107c2780a84746a541044e419b9839fe63a538285e0e1ce0686965d0606c5e0fa05f9317632c0ec2a7f75871c9b7ff2569e640323df9a8af16a107e6382da6bfc5e59533c6f14cf03a726b0ff07358e2aa814a3fc686a78aef0fcaed790115fa9eb3264d024e82abd80d0f7a74acc55c46a8cdb3c593cf95fd08a08b6a659bf02883543418f71d2607bae5fed5207952f5efb13ee73cf597cb8c58306bb16d4c7f59d25c23ced72a6bd04b0ed87e2534233e2aaacdb04e9c0c77ff20235d952b1d9e69c8c7abbcad49ee595ead1070fbc4a5938bc1bb1b4ed431185dcd7b92dfb8d04d4fd0ebfc0aff2f17faee4309abdd8b1e6f2a995208112a5ba83d4fda2cd1491bc9e199bdca4223522bb30dbe198844c06cac52414a4b7f46c8bb4e74c9d4fc90483b53c348ffcf9ed89ff800ac23b2957a711031638dd9b21751184fb3b7c3924499647a200cf88888c0ecda7d14584a7eb5dbc02d64bcfc80eaa3d826c087a67e6c4885939f74f23cd37808172a3ae9b79134c040da756d6da5372617d339c8b5fcece04e71cd32cf605d7cb84fd98a3f5e10c6c844207c88f2ed27e838d0d243af92d9e7aae63909863d5493ce0e0ef45560a0eb9ae0243cbc74da16d171fdd1bee46a23f07c300cb726c60dfc23e271b623f185f69049fcd4921e50defe37c4ca777bccc8174fe80797015468743fa65c57f7937ed54214694d69028287e7c7552e976a23bd5c7c46f7f52d1b3473333e136048abd60f26d47fb65d7ed16814a7eeec98e44274b5229637ab96295fe0a579da178f26631d94cb012e0e1fa37facc309a51be243292a89d42412680d2329ec4b901747be18fbbb520f9a4f6e6c181881ddc5bd3f773658c9a160475b9b520a8cc5fc6c470c55e364bfe6263a07d44c8a7ed0a045456992116ba411c0caca952377d9b58b27d6758c597cc89ee5835f0848af387dbc8de13765e2dadce1fc03fec394a6d5b747e8040e08010ecb31ca4db31c7d985e7d054823549a65cee90d3d9afd64ff61b97eb7c079fc453a55c4d97379bc2bea3b92c6496a265b012a3d3f35ab0fb09ba21d3422896065612374eaa805ccc417c6c2f8673b9c33c8b5717b042d9e0db14b067ca06c4ad2f42c00ca7ee9faf87c7b6ec446f8243a85f218f4fbf77410c8d741d3b0463046684fb8e1af3991fa593d1693ac60d1ad5ab4e6b0982a20abb6796f9acd62ddc323d76d24f03bd37cec28fbda6430494361afd74eb3dac0c9bb227509b51b0cd5e4a4303844c641e091976e1bdf707114ecfcf919b0688038a73a1cfa39c050a4a4fe8c1403fefde5d778714353f6dc412f727c54a2c4a9180944588883af17b55f6644e906f7c5de55da56464604c647175a2e28a290ce1b284090410b8f990650c1da1771adf39d831f2cede93121b745fcb95b8a20d428103ff73d7578cd646f65c869b86b44f0b2be734b387fcb464ba2c3fc7b59f8cb0eff2c6e92e05f6cf5dcb173df2c5084ee3ca5606abf07222b057178d722d3fbe2e4f20e277b6f9fb3b4de253680b48adb05a8bde23242d1270b66989e17f365d2d3cbf7c22a1b11b52d023f1ea4119fb2aeefebe5bf7b7d949e8abd0e57048dee8979fa8f9a923689f5399e2691e91dfda84737a3957358218f174566e5a07857b28656c36f624bf23d917ecdb60b68fa859d0b0fcaf511b0deb591e137131542ccb16436dbbf1a3aacd7008e7237e8d58f9d026932331668169d0ddf7ab3a9c8d73c1fd305e7513bc7f12f2182f9b58bfdc36a56c6a6679f3ad8875b493b2c5bec3aa380788a6d35c4ce754139e74f2627efcba8b838ac86085f3ef65b12df9145656a71bef88ecca6ee97a02b79e2691e0e9036be9d6a3b10441186b426bd7338ce79e806733087a7b8f37e22bfb3247ba09399f6d696c85bea41b139e723c22f918fab56ac96075961f4b787ac1d0a70bee3736d882b95246ed29075db870b758b8e07e7e87e6c4852e3d9b45ead8383c59a08ac2851b403557dbc3b2237556e211e8f50affd7bd30c6203da89b9f022e4b267c037bbd31ca665aed9eb1ac7dc62769928695cf1556b17d16ed57ecf342bd483fff7f4e80db65973fda918844498c6fd469154941a01bb734d950e6b590129eb34626b0e3b2f6079ab3c41d6ce99f330d782e76499e23e6fdc18535eb0889759789ddd6b3a7af0a74a96a5fb37bd9a3c6c040ab2441f66a4d0ed1f14d1dedfc59af3f68d8fc3c98596a859033d7bd3c167e4313fb2d12dfbcc7067cd2308f02e73bbed98e412059b129f5a73fc6f8785c1708654789d09d28b46c747021d65f592833b9a7768bf3e2df0a397128f9f090b3011f5b5eedf548c04acca020192319d4c3f77d95bfd5f82ef1b2f96b37471c2fa0dc4a5a7c1cfa211e378e223b495d0f9cab91d85a2614bd1d6614dee8a37d496ab68538f788d73d28c9cc3cb1eed4a5b76b0f335c9fca8e68f37ea8d85783e7a935248f05bea224f1522797347a3ce77445a3434d5dde8e119f4fda400cbb37d91e6fd6bdbb6cbd441afaa21c1149b5fd04fedc83bcd6caf7b9421462761a29187c126ec80c68bf04de83339e9ab521fa28750d5bd8add669ba83d2fdec9a1e0cd0823f263e7fa25a3ae301731c4aa98bdea0d95bd4eae0e0630b73f315007b71ef4ef344d7ad62ff171abaaef55dfc82abf1d53f136cb859108cfab933036d3372c1e5b90d022737efe30c27f2e1c833fa655ee2ad5fa0cbfcf895e60dab807e3fcd3861b0467e3fb6f007658af778aa47a66a5b18ef77897989756c0c99461874aea86bce804951f47f86cc8e33da5f59f5565806f18066aff4ceeeccadb52e530849838132516863e7c44c6138f4f428cefd85952202805b7df9cff2fc7a9d502ffa304763b8ff578efc5ab580dc98b4b99fe5f090ec1c58983e340075253120916f10e6e496142b1f4d6627434094f714a40fcf3f8524de0ad21584b982bc3d74b3982e3c0647d59b471906934a7820f8e65a48214948b4b3f818b5af0b451b610cfac76ca0c2d301aa7eb92000a894793a057eb8020883764718075e0e2761cb025eb0b91f5387eb73c44c800038c182604cc096949dd392206804592ece1bf5ce6f195d83aeff758142cb7a8d19bccc43b4366ab5834638b1db7c2ba783db2722bc811b1ca4c61d0b14deb70fc48d8ab896aa50a18581f37c38dd83254d6510694daa145052c84b78a53020b2117168deaf9663b72d18e6adc5306662f1005a338e0a04020598bdf848894e1687f356fad082532d63e4fda3f5c796c836a1f2441b498767a52cae94b92207349714b4a7d3693e8b6f0bf7fab665db7ddb0fdab158c4141a7781c8a4c672f57838789925b812de7aee0f053ca88232ad582e98af92bac36f3943110cf07f96c288d3df98d8c515a29777ecc10c7aa2b976b2490df83365a5ddbf1994991e9ce856958325024411c9f7f78d02b50bfd9e73038af47bb1480d10c2729cea20cf4df1979f3535f3ba04f0b63c352d62230ee4e14c7032078026815295d3f8014b0bd366f2bbf5f91101fb2388f7217b072e05b41f270739d5bef78bb8a304265a9f32b153feb9dbf16be6046a15c917445ee7eb310b12b2871e156e0fc5d0c9c26b6e571caf9618b819e42c33a82b583fbb44c150ff333737b1439479f12b51352983688060b7c29cc260962432318a955ef92b2e811b2e89f6e75f20e1708b2022414f8aa5995ea3a15b13c25c6d4fe327aa2654b2798670ea8752c07fb5a47c6592fc2bf5a9a52487afcf7a2051d3bd53ff175527a0015553335aca6c03c55c177d9f94c88dbcfd3efee47bee84d36bb7c282354c7c0f84716084b9e754c3ebee02f4d027df4b08131311d05504f5c426d961a24e72e3b8bc0d717f6c3dd9a3991bfa53f4f2c448a18701cef19701eef285d252a85edbdb9b502b196ea77c5a95567de216097a34ceae75aef3c1360a84a8244381cdf0edfa73b0fc76958cbc2394c54c11417bf479dd86577dd4a8cf4bb52848bb9997263cca7bc9dd599ccee7d0aec1adfad55aae38cd38c5eb40dec8ae2518f48851129d3d7e38bd9d767b43158c59e3bdceec2fb327857ca6780227ff3937d393272989a76592999db2ac03af9261a0ae45b6627872a0e528b6ce6921c0e974678d446887821ae38a8798bcba3706348f6f0ab780b6c77f50f2dfb0ffdd2874a30b7a3ea6cfe38b75105bb34a7fbf39b482e9fb1eeeb700bd1a9316f6acdcd3ebaf6349460924e130856920974aa020d9c6295f11a6b1ed8dedfade8653233aebe4e2c0399aeef49169b6da154cc2471694a613ef60bf7cfbfd636dd1da0b5bb05b259715543710af6126a5242a6f7d4d7e324887a08578eaea5c8938cab7c5e3e4317122e7d08aade36614c01a1f5d36a54ba51abb90a433add9247384874e7d2e001d86c6600b85b2343b0f690e310b825873f91141921d9ca266afe4c63a244023150a57accce96947b3bcd3ac39125d4ee472e75bc831becc3c7c651aba627976598f72bb75834ab83ec878012c8355abe7ad876a2ef96c630fad84c96f710c1ae2f59613d3c52d38f054667c0d2a9259ba576ca828c9fd632178973386afb6e845c8a2e1e3194de0af1a16a4b04349c9d154a4ca39a3f8d8c4f138249e41d77179a99da09f9e4a73b64041ac16073472fdfa89d1ce7c15946595f0db3f9972484f290a9a91fdffbc3c114eeea4106dd91b89bb5f5f99d01323385ed83d1b5693733a5140b06d832977036e2d03721c7002425845bdff156c3f519d064e1df2873fe72a8375a50c6d5bf290249fa7a4dea6b840f0a6fd5a8536ab3238eea474bf74ced4771158a8c7ba1f32401cb8481bb0fe5d259a16abffea8f8ff1d15b0542122741c32088b7a705e96178daf28379ee9bc3df3ebf3e624962493f6750cf6e82009636c24f15cfdaa8620dbd945875664ce01688cbf318c2500003e9246495a6cbe91a0e0a41399b4d9d6c26509ace35442075ccf188d29dae494da195ae388e3a6c8bb29a1dba574a1130845468714d97f91b879ee28d2768960740bd49d43c94b0c002e93a52e3d07e0a447b26193ebffe2a10ebe0cd518d0221a2b118b3f6c170b0f185780880d7a2f324ae4e207539dbeb167b6b840281de93191ddc5d9afd9ea50838d8c3c64671047c4fad289d5b887889ab42e50731d16cdc1e630742bb263bffa6a56cd445766b26d1f7640f296a4bbac8404307cca0176f0a51f0d8983a0a7560f38095ae39b4f588d0de5ab01f419454c424064854ca8845cd36464300e277180bc74933ff8b15e0d4d47344e9dac182d686a1b4390bbb68b10ad57394f83cd480c4c50510e3c9038f56bd4d061e82b60681ee5fcf9a1c722a4e39c7c01d7ace184b9f2c38c8bfd5bd99682ffe893650c6c0f83178d0064a1bc2ee449e3ef2e71490b473eb80ec1efb95d5c3ccbf35fc755932528351aa6c50b38c9ae5eb58e8fa009b47be9279b5c3fd1bad68b95960841197d52ae062e0c4c865701f26f59ffb419571fc3c056cc86dc3607e4caf2406fd5154c064118a3482dca517d7dc6f6aca2acbeca999a3e5ad0df9cde74b1e9e728791ac3180d9fbe230c5ce73ebc309d457532e1d519a20b491f3c8282cfe2ca6765117c4c92de8c08ff9fd11abd2e5e1b186dcec0372c8ef3d1542a7cfc6a5344b15a56d13b06fbbcf2a8f9d416c98ddfe04d96a5d9f90d8c8e9e575d51b772302c41b3b16b9bc2ee9f198863f8f5ba4cd7603135dbc961cebfb34b473ed4a367666adf2b07e44ab1fcb10d4217441fbabea1eb428bc7045f6f1ea9e8aff9db94971de26ff960e4226aa964e21b8804d5536cdb05ba46acf8689183624aa0ef0465c2b963d1ee809379cbfd6a98831af3dd9d6629e3de56a084277f132b67c17717f385cda1566f9dd9a1daa4def780d35d5827dc0f6ac2639ab78f7f08a480cf18bef0e69e34ffe825884ea2a015fa5c5ad34d1f623f0822e9a15450cc367dd8078bd75b71002969a06734dbc47bf613a4c744006e8aa37e01eae1070041b5a119fc7e0d822e04660b21a16af8c077c59ec97d98f75350bcdbeaa548c3006da4ef08918ab26f16e1f8bb5bb88638a899514e0009573196ffbf510dd0981f02c61c569aafba7f025602aaaf5cbe2c517cef47a219feb00eb44119ba8b2df1109b655f1eeba3c4e84b80acbb2c7b5df040c303f311758e22928fa2ade189f1eb232d4940ac1361d17645ddee7fd893a1095d866e0d461201c08d3562b32ec556b2bbc2fb01db08c671fd1ff9a64d33d082889a0ad53af4d7ac0f02e01fc703a1baa7145e2e9123f903c5a185fe094233cf3bf18521960b11932b412bde418e556843bab311212ae173b6f0c1e11e2192d86b89ebe484df2f7c56215151f96afee4eef2eb1f225af644dc578821727cb4f28e19046c0a0d1be5c1bfa490fccb8ae8478d2bc424df6d34d6dc9ed8387d41702720df1c382c0d44bc3fd423cd4be54281e88a6da4192870c43c1f2eb7fa711fa4442a66543ac4f81e90483fc3b1bc5351c11557552732ebb7ef0f4022ac4d17ad720b3975029daf394684242fe4a03e94421608fecd0e51a0ecaa5d6e82e08485352503305ac470adc8bc90cae9820008abd270a8368871721f487a6c518603b7a5adf1d1111b2fb34196d35286003d19dd652f979e137d79ac4932d541ab2a0fc05855515bf0a132f6d5b1a3d2a21bdba1e6732c65f9d5304853839942e90184c5993db5c4a4a22a86cd323a89becb26a4a6de39b09ee77b798957db5a105c1212514d3470fe412d64979222d99c49e6114b1b7bbcf2a2531f5cded156292d56923e7e673d475040bdfc54c4b6b95bbeff75c686dc8f57d2cb7d496686e896635a38627e83c8de3abc2787f0b4e801ffbfbcf33628e816baed975bf2c60987ea3c6babc4c723c0f9c8cb91514deee9e6947246f573e4c7d773901e0ef79f91796abd65e0d5e732a3dff06a842fb4d2c9d8b8a7629190936c3fcf779fcf36f1451d55ca24194ce40fb5472cfb10cd51c69dd9a17dbb6ea4e1ed34f668e20ddb50a1142bfe01bba81df274a3855aaac96101f9ca0ddff95549483c6ff9abf6960f94b206c1caca4d548acd3f97588806bbf432f73147ea954bbd07f457292bca089de8040a0e69277e07e69a1cc9af124c1ec0318372cb8045ec7a52953c346eb8728acb1e2117f7c85401f322834281cfb982f24af2431b447f5f27ee579185f6e7c6e34579bb27d13f53b894f9fd9933ed8d1318bb06ccda1221af2236f9ef573e7ad68d6309f988a028ff863c275386c425812a24e18a80b07df91b9a316228a92e2da39a3e9f48213cadf61dc25647a16a2141a996a69d755cc458c321aba0c748f666112400d473d4c11617e70765e82832b0869b98528a33ef8ace312d96f3f4d25be8f2c1c66d07dbebfede4169174be0153cbfd9568eaac4f9ba065095786a87e5004f07fc71c085e2f5740698113dcef8f07738ebfed0dd736530d3931dccadcb8b772f68be37aac959a3ce9dff6180941f5ce55665c13324a037185720764f3737eb0030294b91ce519c05f3467e76f8164cb9dbd287e034b6b012f93816d221ad8f9cd68a03c2e3a8ca14bd40d79bdb1842e52e7cd094c7f329efe79e02981def22f01d1117b0650b0e04a2be79ff942c6bc751309fe40f7b7fbade6585f772c617e3c890c2ec07f980b16027a68b46c926bc8f205613c4f1acd35dc9158a4a6b136f745cda0901ea9624cfec022b8f034c55ce20a3b1a3badaefdd5e2602731221807a2ef05c68dfca3c88caeaeced8f973ea59828fc9b50b997d27caab83936ffac0de02e87b25b9e1133f12a90341c7df3917d21fa7ad18eb89f7b300c75f10fd712e6fb217bf36d3be4d8cab614ea644c026ec29e7f087a1e93e19bfd371fc035c519289a1004ba914541b5ff63d786680f7da09139a6397226c705bc6279239afba6c7f53a6416556abe8680eefddefe3795158054039e9c1095d23da86022bc49b07d2abfd97a94574d6626026768b21148068095d7669e8031e242a81582f8597aaf31325312bc2b8c975020743d0e22c9c47846e3c42d65d4bc26580d1ce392f629568d11d74456aa8609ddb3af0d9f72be147300a636c97b0aff027282e449361fb3c3d5f8f26978d09eaddcfd37327684eedda2970b8bc13608a547a473fefa09a2b242cfe106b75ce3c64e6c34cf1c6ca183c19e4fb92e146f50496a4764c443b8e7b2365b7a912454b50b02015d7244a0f66c5bbaa9a50377524a0b11e4a349f3720c2f4ad2e64a749d66aa9ba0eda7194e4bcd391ebc9977855fe2c9cdf41bc630180861a18f308727dcbd0d39b976d37a1dfdf904f77a6a40ed06dcd94738374389027d9018b1943c5b713325dc8b5de53e34db7cf819a55ea2fa52b11b05b2f06f31084fc2a47a1f72d6098df6e66181ceb8d40135120f055587a63e793dc731c3439b83d9ad34d6c234c11b5e368983e17679ed558a5ea7a3ad33d5b9ebe300778ef8b8f578f6e30d7363c82cd9f03e1d780087c4c182d72e0319acec822f2de2a32f24733f57413f6d2e693c7de3ded77e48fed03176d03e7629c68662b9410a68580f98cd4c0ade2e5d113f8d72379e78ac6038525b81ef549b934f85e4714425e6990805ea4f011f7e62088d86b11de93ae990135dfda335ba4d6899d2cecc2c7cd8497f5226b3c59af0264c537de2ee237fdaa594788747be37d35c4755475ddb9647ceb63b2066cd4ffd3500d1c8e023e5a04220b3760b46439330f0f0f0f0f0f0f0f6f4337426a6b9f908494a454aa976679010b644a29c99492d81dbc0b9c99f87466e2d31546ea6e34e301030b400bd30a89305b754ea153cc4f17311a1a68000d2d6854c0c3c3c3c38bbc0c801a6220c214d28388d6a39e7fa625c4388441e5edce5e2284ea63058d011b39ba7880214c3a9430d9298b440b63214c21c78b914df249fe104218b465c813f91f15620cc21871e5d6434b9e902208e3440b19a91e545479a2102310264f59952647c8881a157284188030a5e777f438191b6b016084187f30968e74f988efb9a75363edc3c647c12d4b31fc6076b7083243e465059386161e1e5588d1077357fef6f47137b6f3c1246eb35ebf9237ddefa26b68c06fd898c0923d186de28afa0e1d3d18e5524823aa3a7f3a711ecc5f174b955267a972120f8697a4b4aca5548086062440e3430b1ced12c891000b2851438c3b986ad27c74ad38f91b362002e50a31ec60be0c7df524667bb5af83792dced6c4fa09da6e3a986e5baf64752ef5e139183ce54ccb19a24a29530e26a13e57b8a89e9519d6588b83f14a456cb56ec2292538184ef6d8c9d0918488a637e8337975ebd52f23c70d068ba6bba5847e25f3b4c1782a629b087a7ad5c26c3089f59c82129d3c8851d7b09af89d69d3fbfb5d0dc6d1f62762470e252d4d83316757528ea8f360123418f542e3924e6a44fcc81990e79e7592d8f0dd88194c9fafebc329b7951896c134e23e23f4d998fa6430bd560e39a29ab0a4d9180cf2246987284292490cc69ff9d4ad94264cbe6aac1962848131b7d9ae20536c3430587a3cfe09791771fc4272a23de2880929cfc70bac96e87069d927e98241e9d349674cdc0575e242a272ac3d2f919152bd05decf52e447e9f4c1d58229e72454dae4f24ad5c982c94212972fe88d05632491b3d1174a4551b942fa4b5e9f1a1d4f3c620593aafcdb1dea296787ab7067cb13dbd46a6255a86052e6de267ddd4444740c31a660c48e14d192429784670d0f69267f4761d7125365924b5ff250307b2425df15c2ab3e9e438c2798cebc4dc6e52427182d8f7c87f2fc155d9b9078710fa1cc4c2618c4b6044942924b583ca565c7d2217fca9460b6317defb0d57f214930f68dca496adb87f00e09c6f8ddf3aaba54b61ec1d319b135ce74881ac1b8af27c1be6e2c9f72885104e34a34a14f45869e1e11c1f41971d72dd4ef538660505921e5e715e517662198aeb346eaa80ad2471b0483e855f79c4a6dafac4030999950b9206db573f20786f7efec4e7af744f6d00d317c9068a6965ddd0373b6c41c939397fb82789007af3e53a24f9c24edc024277c56ba206f92b6bbd8a26f241d982aabd675f47091d29203c385e7eb087bed220eec0992b642527cd9788353fc38ba446f820ef2183630ba6e8b6e0fdb298aceb521460d887f49f4d7280f950662d0c02ce6e13cae598ae4a1b121c60c8c5e3aa5a81246eeb8be8618323069cefde76f856889c5ba9f53d8cfb8d2aec1c254276ef73aebe597e91546fdb4edca31b57923572073ca9369bf4a2b0ce7a3d4bdc8c6fdafac30dba7baec905ff2bd5d85c1e444e5e6fcc83cb9c69aab1960a8a2ce761792852429684f2accf196653997fa4d1f15261d523855d7317e3a790a5418cd4ea2744c5863ad057e820fbe1b3672743185b13b6591f11c23966534b4a0d1821c5ea0c07451011a5ab4e0867f71011a5ad0f02fb8680ed0d082c69db5c02340430b1a76c39d8bf3850d2df3a251f05186c3bd68418e2c85414f7a0f8b2426297b5218d56cd4a8f4f1f6b98fc26c71d2cf88c5eed35714c6d1e965fdc36fe49ba130cfbc8f288f7b8b2c0185617f3b4be7fe2cefdf270c2e4946d0155a3ea25d066078c2a04694e5ed59d7fabf138614f22c56a8958afbd4d80d745181b3829c30870d911e72e6dfb29cb809e39c271d99a142de4713c60a7af1223444ae8533611049c5fbe69e4bae0f26cc79c5de7b74ec10a2a55cc214b45c4d9f92aa22644b98d46595136182ead866f2004625d491cfcfbb1682caa2008312e6a0ad2797ee5b93e1f8c8f3008c49186553c4055d59e46449560086244c1529ec8e55d808ebdcb8617608302261aaca9ef8b521b2eef187106040c21ce36e73278b47109700c6234c6d415f0e42bba9578405301c61d8b7aa2ecfb7b0145463edc373d8e8e24e05301a81d0191e3e6444790306238c713f62d1e43efdeb35d63e10522f602cc2544ae26e9b84a4425a6aacdd5084a954764cf86c3fe67101231126b566592e665c46b4cb4c000311066d912da851fe41a4d3210c7f61db43caa5b9681bc29ce467e498a51121a51026e5392e9f449810061d134a790ae141982508f5385721447a2c087329ebef2f5526e60f8439ce6ea464656af2640161b69446be8d8a21e4db3f987d62593c6bedec52fac164f7df69fb631f4c29ee6e65adff50b3f1c16049eb6666df8331458ed029b2249f5735d672b4e0468eb5ced10bf0e223c905400c30f460bcb4f1faa542ce53aac6da471d0d0d34600b30f2600a6982a4b82e5539b45c808107f3e81e93b318dec1a4646858588a4e96658db5b230ec608e26579b3ee26bfe5263ed830d07a30047af200130ea60103bc23f8556eef5c3737c617e18061dcca924a54fd2f75ba489e760d64939b722bf65cfa68f3b030c399844d8a9a0ff63df851e0783a58689a7541bfe4170308e507955fa6358f5fd06931033ea6bb47583f1d37376b8d5aff8691b0c1adace3e87f8b922369882fa4cf652daf4e49035183f2d98fb051d6f92d460887339fc7327b17f8d8b56810d143806baa8000d2d380dc6d02222d553f5ee99ae00030da60cfbf57af7703eca3318b7be26ee8752224c806106f359678b37eea9457b30ca60f2cad24946844f8da700830c067517ed83b4fe1ba53406e35ad871cbd7e3b29be50186188c5a5b37bbb67d9d3d6130a7883fda42ae99f6d100030ca6b3b00b914275ea60d6ad01c6174c9d5444af9b8100c30b86f43b97bffd9e7459140230ba60ead4157ac7cc264b8ad987a11c6070c16cdd162752f27a1bf9ecc3181030b6609049317a1e52d809920f3336c0d08229291d26ce8fc9979033fb30b5d2038c2c98f7e2f3558a8afee75830a8591ecb3d59bd3aba82f9564b7b5cefaaf3b582e9b7a3e413d23e29bd55c1f4c9b28856bb5890142a9854e764a53fbedbea3b05534d2c7d77aae2e7775230a9a06137d2ac24465214cc7d1a298d2c75d9520e0a062d93b297d095ad84fb0473cdbfa5abb658eed209c6d5b4504ae57cf6779a601a6d61ff45878ffa2513cc6992b07fff8ba054b40453ec0bd3d6ad3fd7394a30789add480bffd22d4a8239e8a860fb5fc94bad483089bb9f117942749b0fc6114c49d99a488bb011cc11840a2a87d1cfdeba08e6ad6c6a3f4b53438f4430682f11b211f3b22f3f028c21984bece28afc8a259df6f0281f8607430826ed4eb1743e136f97140473bea978d6ecd68d05046377f5886b7a7779f903c3488ab964a13a9f181d0e183e30d75d075d6e32ebb33d30a8b9a43d7479903d2e0fcc155ad41d988358499ae02af945d68131e4c73ebb49c2c4e57360743b717eee57a16aa3a1c58729da62cb60e0c03c16225cccc98c8ad40dcc1f44880525f46f5ad60626fd172d47dc9aeceed6c0702a3e8a4eb942be80410393b4483fc2d7e4251d9d81712b23a7bbd58fd41d18323005a97dff932a8ec9321646d3f2380f23a2bb25b0305df06afbbbecc9f4fb0ac39f4732f3d167b1a72b4c62a284dc5e893551d28286161690000d6d81005a6148fadada644c92159b5ab10b08801546133af5abd492d72cf00f1b393ef05620805598c2da6ee7d3a810376320005518e4e7be3ecd5f68130502488529ed2911adb40987aba09d8b0f1514c31b282801a0c294ee84ae4bcb97eb2fd2028f403985c9c427ff298b98c26057396e9696ecd1430194c2204566e6ff7bc8be202408801426f7bc6769a476d2e6e8628b1a450b0f8f519852f8879265f7a62ca6111e672887243dfc2e5f7c7461833b8ad0610643d075973fa67a8f9a40e828c32a2a3ce928a24e7e51e82003ee5f5e9d25954612133ac65072bb2f31cdd0b30b5c4287188ebb9611cf7f346ec1f9280b833945131efaf24608dae90083214938713af79c599a5f3086e917ada2aae7c4e4dbb9282fe8f082e15bc4bd54e42e186f74ce695ebac73fc905d3e4169973514af6f45174d0b105939294f522df4d9cc65a3098ce2945c991d4a0230ba634f12a4b92e4ce6663c114f428a56c47f44b7e35d6cc6868a0010ea0a10109d0f0f09041c7158c3a493c699153b357a264830225d9e8a2eca4a500083e0cd0610573a4ac9491a89d26710f0f5e0a80e003004ee8a8824175ca972e6fc99354d5584bbbe139fc861766aa800e2a18640979f398e421775330a470175b17b4e335cd8f0e29184777ac185636f3f9d482060768584002343aa260f41c6f25c5fd8ad599066868416303342c20011a1d502874864861fccc3f5d634d058e010f0f15f80db41b39bcc043c7138cdf5f51527f36d52d7e31812e18e0e1e1e18163071d4e309c502297969610c4af8e269844926a0f777e6a7eac4da18309e6642a590ea1b53721a9c6dadbf8e0251872a86441f95a12d26a724a30ce4fda14f14f27d54c82797420c1f82bea25071d9f4ec5fad071044350cf11092a4a90df8d604ed1a5478f96fbe6a4a308a6ce26259d8aa076f5c2367410c1e861c4e517216774ea5e740cc11442cea3152d76ca2d4bd02104934a95e6d564769c488d0e1d4130c7121533b2e59ce3b36573e80082b12ac6071ff57ac2439258213a7e608a9d8492d9fe8a7caad436b8f0f020f6611b880e1f18d447f47fc7fa5356730f0ce2a942a57c499bafc63c3047c822c67284a9b2c80ecc29fbe46025569e832e9fd0a103b3e7fc1232faa774e7e4c01cf793f3466a383084dcc93a25f5ff0df50d4ca9ca7476dc49133a6c60b29bd1f7afb14b538aa1a306660f22f9626db8458e7ea18306e6206b92a3ed857ee5ac858e1918420a7bd723c553493a6460d0f5c1f452ca1efb7d2c0cefb13be446fc8bc8c2c214b73d6ae7707e2987bcc27841ee5225a929617b579872aa3d15272e88e7a915e6d6cf9654e58ffb5cb3c21037437755ffa2e4945598ca3ebf8f885615a6cd946839e59c0f6aa258c82215a6cbbcb99c13ef0a59a0c274ebf6e154485b3e39a730c7ca4a4a23ec1b3958604c61f0109552ca1f254afea5308ff04ff18d70fe1eaab19611c88214a6109479ce3e7d7a71bf1b4116a330e58e7b1a4134925b4a8d355118c54f951eb57f2af13d01b2c1c586c270e2262d9bb4acd0528db58f0485215e4acac38ea8c6da65208b4f18546d3dcd495acff2d558bb40169e3065ae97764f1225bcacb1c614c8a21346fd9113ec52a6fd7d1865c1095359fc4fe2d69ddb6235d69c8bd37622c86213a65431d789fb6ec9ae1aab09e3ed44f5b62dd3fba61a6ba90b1ba78b2f361326bf8a1f275d5f5ca45a70234762c26041a50b2b571344446aacb5e0460eb2812c2e61fa9c3e2e2ad5d60879324b98455987b058c23ec4a2812c2a6108b2697fa6b273ccd696035950c22074ec7394093126ea933086850bd1ba6a24ae92306beaa591266143422261bcb738a2539daac41224cca2428c70df0f6a52e711a6ab18177cf257bec91106d331e4771aedf183dd0893f214e18410c16cd28c30e78f9dae5f21f5b96511e65329fb9cc9274f9e2ac238ee3da3edb7f2fc441862a7a0ec65e554658908c366cdac8dc9da499243982cce9ccd07194bdd19c2b0ef49cbe6c2e4d9ad1006d51ac22d564ce6a93c3c70b8172dc8714701107c0400085910c214a367e4d6764b8a21b40fbb2c0661bc783d31fa395996bc200c49de09f123b1cc4e0d8461ce65948e8a1ff40f0863a898dc172d79c59559fcc1fc1366accc52fe3c310b3f185fd2986f044bd9c9f7c1105b2f2595a4d3635e0f0f3164c107b3af873455d11e4c41c8d1f1e446bf4bab078369dddffd8fe855963c184eb52b5be8feed13c18361bbf3fa95f60ee6ca932d9cecb0371766610753aa121242aace33f70c1059d4c1d439e88837fbe5a9743a1826c6554ab24d5cead11c8c6f66a3abd255e4242407d39aa5aca7a7d42774e2605c0f7796173a7030ae99d9870bf274c4ce1b4c25cbb446abf55da5dd602c714b4145a9ba384b1b0c1bdb299dc50a1bcced3b22ffd55e94145d83b14ee48aa74feab35435183f452a593339c8b7d360cc095ff91192a48b241a4cf9447eca10e6198c6ddaaf636c9abf04cd608aee22eb352c9a50aa2a64510693ba1b212f7e4f0653ccb17062d26ca6c8c76036212df34b2589c17429a87e1ded15742585c190d3d6efa4ead494243098b33e77309bcf174cd9c4a28a27715e757bc190824a3255933f7ba574c1ec7f9def42902729cb5c30968e6f2642b21ecf780b86a81f2584b0ea13e3d582694dad72fcceaa77370b463951a5f379b29b5ab1601cd5599613a24d4af30aa6b0fd59478d6905934cf68f3b4a8ee79454c1b835794d7627557d122a98ceab3f2b9a369192640aa63d75bd5f4ac63d77523056866817f92267b68bc2b1c32585d1eba0603cad94ee53e8cc0bb227183e4e9e30fa932cf79813b4b2cf394284ad09c66d131e572a74d29d09269d3e2c89c6e5d32fc1b027df4dbd64cd1229c12446477f3ea1524c4b12cca9a76a5fb3c2642498365436cbda826b5716904216473075e990f354e794e36a239846082d22d4cbcd7f8a60d056ada0e324f5e7b03040430b1a75011a1690000d2ebc40c1166a4430afa7d8b6cfebaa1492c30607ee3e90c5108c3732d64dc7ee3295f5f01082498e65af74a3ab1e4e48c82208c68cd1d9a16a9ea51d0f0f0058210b2018eed273ec4bf331e12106b2f881219ad6aca4e2f4f42a3e30778970abafb6d037d758c3f1e1c517391cc771163d3077deceb58dce4ab68d1c669b050f4ce13f7f6d9b8b0ed9da813925fd6b5e39393e6f4ac84207a6246e23f3126543c63930e851973c6f527d4bd60b59e0c064f5a542b05c69a7a31b18b4424f87da883a216f210b1b98438e962bfa05a5b4493c90450dccdf9d45c4cef7d1b28d42163430880ba7d353d6f0ad9c8442163330c44b5de92da84f56a2608b1b5e98690bb2908131c3b5d4d743462ad5be102316a620d1935cb45426b65b2ec480854925c7d3f5ab6b4ac5f415860b2a26bab64711f95d61cacd4a4a08f1126df256182396f8925e49cd66b816831506959c4e091d4ebfa4508d3557c1163752a22ebe78d2858d2db640018e5e0189b10ab3255dd3adf5d5afcb3eec0f315461ba3c2b39ba92f2a7570b1a34b4a0a1050d2d68380a9a021e1e681f568718a930867c864ef19239f10dff40157cd8c8f171e3867f78d12858bb618018a8307f2775dac6235e7ec8294ca2d627a414e6bd448708314c618813d383e4b7fc214f928518a530a88dacc84ff13c7c0e298ceb4195cf8fdd859895e9284cfea984509d5fc4a5105198a49afe05a11e4e3ccf408c50985c4b948c999d94da9a82c21c44d0cad974fc42d0ca270cda542cff7896212fa29e30c514d33457b7c61a0bbee80f1c5be017313a71c8a6ad478f87306d08313861cc104965dc566aacbd9f0b000e626cc23cbaaccf36de1aeb802e2ae0e161ae88a10963eca7c8eecec494adc69a2f51c19503c4c88429eb7b4ae1930af3c13a25c4c084b13be7e87ac1930c317b78e4c0b105171e1e1f312e61d4f6bb0a8babae33046258c2786f229ef77fc66a8f5109b39a9a4e5ac3ccbc932861d0e72762446848b10b8e2d100bdc930d0a4cc210627f98999af02177bab0b1c547310ec4908461562b89bc7d593ad901312261300db38ed725499bb68504562161160f13df7212de1344ec1e508256805a0e1c5b70413a10e311a6bc2345655bc8b11c2a00f010c311c61f75df31ae5de72a1f311a6150dfeb104ae9deab1703311861fe523e267f2b4a387b11a6ee7c29fdc65c6dcd0f1c5b281b6228c2a49f133f8450d7d93e0163b06b542449d1830c311823a41c429a2d7d1f2936920d1961309d460e22d7c608e1078e2dccc820030ce6141d11f4b94ef81a71f105938c116df11452e507820c2f18fbeed674f67f4f3b5d156474c138f21e724bbbc3f7cc05639cf7288b2d5241c6164c414372f658bb5acb6ac1706739548a3769cb436464c1b43d49697dcfb7a17a1958308f34ede929ea46907105e39c1c5da645a78ecd56307c96be28afff6cc184b6c8d1393290041955c827b5d5cdfc766190410563ffc80e1d65259ca59ce046a3c06f988c2998d74cf252302421dac3bccf838a7714cc637fb1f6c2733e8f0d20030a86744a6a990433e1697f8239ccc5eb15a1ca3b742798723aa19d3dcb92ce5213cc7d715c46a911b1549860b48ab5adeb4b1eb46709c6ff5db7121f3c5ebc4a30e7d332d5ac78398ff20b2f92606ecf2d39c6073569511948309e8e246549e49bbf1a20e308a68b37799b2ef2846f04e36f4bfe2c7f8b6711ccffb13e92ea49189d08269d3121f2c63f04839c5d8a13a24230574e9ffbd485ad6406c1a0276a9927395a8940306d9747f8c85eeafc812927a1b4cec49095d8fbc06413a4a48b1727e1ef81219dcd5dbe9ede34f1c0785abd25f72ca47c7660d8fdfe55af9589b60e0c96a735442cadbf951c18e4ab465c9771604a29172e37b981a9cf45b72d5be77236305a4842a5ebce6b60aabbb3b3f78ff697cba081e9f63c47ca5f19e272193330859a9d9e58c182da65c8c06842627c7e512c0a69a74aecd30916862065724e69a7571873d5b63582d29bbf2b8cabd7a9b5a6e4b8a91506a5ea2fc590eb131756989359a7ca4dd6d3d9559824971e95f2f9a4e0aac294525a8b7ab721ac4d8551542f5c695d2935418539dae81021a354c7083a85c9545a5c9e780c292253985b2624a16c4f98cea15298f2670f2aa69228f9218571ad757f3b72eedca3309f49cb232f8bc2581d3af2e321556c4361bed355ca666ebfce939fc87e9e13b3f309c3856cb933e2d7f9c713a620d16f27db9d30f78ccae1479eb4b339611c3f933549ba095376bf2d9de029775213a648e17c6e24ebca9809b3cc5c7a8753553a870973d4eaa4bb737d0973c8a7ce5a2679b4ad2d61ce96ee44c5889c9b75254c7b41b99d122bb25953829cd47d98e92761cafe9096462d4f58256110d671a2e935ad279648182bc951dbda175b59818441ebe8d121afe7cbfe11e638aa6639976fbec511e6942645e5c4c8e26a238c9246a5b1b29326228c305f2e616245a87b087911865827743eed5811e68bebd039f57d3e281106af78ed517911612e4bfd126631b6c73d8439480c15a1d22d4eb88630453c3121d9a38414dc42984c77c87b77a5d7c32584a9e4984ad162e5e4dc0ec2143a65975feb69782b08e3e7efa7916d0361cad0102994a97aef0161d0b19444134a5abdfe07e3cec4cb0e29723ec90f66391d7eef7592eeba0fc60b25d5bc2f760a5a3e187e2e74a5e0374a64750f6611a52d29554e4f52d583c1c7bc52ce97e6c16012827e7d13d5764a3c18fd4556843e1d3f72f20ee61aa5ef2ccd6907e3e98513b93baad696753097ee7ca53da659793a18b4a50a573274b2d8cfc1ac716159576ff1477230d7249b5119119205c5c168a64ca8fcb1a363070e86fbb871153335b6bec1f8c1e3578a923b65d50da65c7a674ae9ae0a4ad20643caf993bd7472b6246c30ad5bb8e59b4ee126640de69a942729495e0de65441271db75fe43f9206530e42a913d16bdb7244835135d3eb3ad8095bf10c8637a1bbbd3da2f58666309bb4d116452b8341a8532989d1cb7e2a2483c1cbd446b8f14aed8fc1bcd75eb27ff296c4c560d24baea5544da91c0a83b92be8d23679bd94c0601ad393e2cdb466eb2f9845ad83c872ab1cd50be62de91ff183e75eba601053ad8b13b96098a0a4e54bb9db82415bdcee24cf23f93a2d18f5ec27dd5e9705d3e44fada15d39873c164c5db2464e5e8d35f915cc296f217dac3d9492158cfa15b154073597ee2a98e6d449b5f8ad242e5430b7685321f2650a06bd9552ca69a1cf220573300f3337d9a260b0f568d95c8382d962786544b9f330da134c41c48f7d2a6d04cb9913ccde25545b5049c653d60443d637cfbf9ebd6f6782c97eb4ab5eec4e3f5982e9edcb7427794ac98812cc27d62992b44f72912418336d6fd5ec73f61009261521a84d2b1dc13c5acc549424a22719c16ca67eafcbdd4254044390977366d224c98c08061d41ac7efcda11a91a8239e6c4ea24a2e71195178221659985e510776196060c2008e6fdcea346628dfd2801c15c39e4fa32747ed07d095d42dfd828aff8c0246fe16ff969db43ac0703e8814196840927c663a97d82ce417630001eb8225a3d5ef54d773430801d68413582ba3ef5531778662e18800ecc152c7f0ab2b4f7d44bc630801c1854687c9e5c153d49968401e0c060276458fefd907be1220ce006a5a553594e8c906766060660039365b913ff3c9e44258930801a18238e4cb83c5e0106400383b6163f378b1eb4245be011c0f5a251d00511c2006660f814b157d953ae08fb00646098d892b3e85d64b13716e652da7c929cbd4ab1030b738510c2bbea5252b3fc6f91f6bae315e6385b11827b52f1f3b7f3b000d0a0c315469dd176399c9599b66ed8c0f131015c0f74b4c2a04ce850b245e67b973a58619e1ba533f12ec9885b598549479f12c24e52d05176a14315a66842c768951f9dac4d854962a90baf734ad829e585a10315e42c3eda6ada276d5c1a10011a5ad0708087470bfc0486868e5318d2861a5f516df9c46f0a73e84fe91e762d8579cf65ec53ae3c499f5ce8208561a4f5a5edbc7aa98238bcd822070e2fb6c8310ab3e9edeb1055e31f3f36d217386ca42f7088c2f09f3b848e3bcac1c51625c8c1c516250885d9b3529061a7ce521a14a65425b98298c849253f61dcb1742aa9cb4a2192c40a1d9e30ae6556d76aa5147fd40953ecee70499b862ccb55a18313a64b6da9fb713d04d111063c3cd00d12746cc22827a731c2df83ce1a0f0fe42cf0227468c2149a963aeced99484c18ae3ae4ab8aa2924aacb1f6710963f5966ecd975029a325cca6e47ad2a66b47e95f09730cd36952ca497815d558434a943aaec4728667cb4551cb4fea63b44e5de77f564e051d93309879b614725ee949649230e812b2fa431821b1bb8c844977c88927c47dd01ff6a00312c611b1b3b7a542627798071d5af8cd9fb7c311c6399d45ed9aaf97bd1d8d30fb8cc821653f31c290e298bc93ba16473bad60e8588421a94b48563271f72b1d8a30c7b3fc20b612ce4247228c1d45f4e6a868a3bbef408471648a3839d96bd9e24a42c7210c3f5252929b9763fa39011a5a78785c87210c9ee22eff25154eda1d8530bf5e24ed22272f84141a5a94a183100629a3a346ec5bcad5a20047afc0c30305387a056410c69c18395cfecaa7a60bc22841b74cfef4d1d080046804c2ace2b936449dd5870410a6ce1dd15ab1c6d5f407d3c8fe68932bc70fe61479da4542fa60ec0fdda253ed7c307827a184b49c4e5edb7b30c90cdd2e916b7e62ebc174f9913b87388d3cca3c184fdcc9951362c2e50c0fe6dc503a6cab750753a40bea2145b285f8d9c1ec21ea46f588141d3b753005a9262aa2891d7430d8c955cea542e660d4d3d972b59a6e179283219e69b7bf98f99cd7389827ce5d7b4a952d7fdc0107a3f587d0e9fbeb7883d15484f82f67ca725248e87083a9b3b9c90921049316521d6d306b4891756a724e1c42071b8c23f4b9a4fecdfd0ad0b10673be7b48296adea7c4c343081d6a30da450e56b9fbc34b5a63cd16e0e19106537ebdcf16561ae72b1acc957209f510cf3f6ed6582b868b2f7038063c024320c0118c1dce577eb535ac7335d60a4d312010c008e6740bd1907d93f6322305018a70acdf9b134135d66a060420827962c65c14496241da359653172671f1858d1a19f0f050f3d3c7120438a4d1129773dcf25e40002130d97ce7b2778a1408100453c4c9aaa7957b963c35d63e706c0104536ddaa6c80b277bb25c70f1c59fa96941801f3cfa6dd573f09c6404475a00f5cb81003e58b4859121ba3dc90e6d81a38b2d6e786a77400e82003d30083d96a654fdae530a1608c003e3a57e1aa154ce65fadf81c94d52c8d949d781396c7c85b86f93accd81d192d01b42c6a4981207c648f941282182b211961b18dd2dc6842023cdc93420800dcc791d22972c754ab849011e1e384a170850037396c7fbe42b42bf9b6860b0da89faa193e9f119025120c00c8cd9a7eb525b3dbe3402c8c09456d2c5504ad5079dcd8885416b4cb58734a977e219b028686f31939fa4bbaf4862c7965ac4b4f60a2965862b4c25f94cac3a74098b7e71c38b3b2d683c400b0f8f1b284001eb0f66b4c2f82571acebc4f6c7ae3061062bcc15752429d5a37b3f5aab30e5709e52b2bce7742f0933546130ad8ff73948b7a4c244c28c5498f75dc38490ab919e438419a830887deed02927a475afc6da47b261a3041f38ba066f91805232cc3885299b8821c26e5e3efd1a6be8c3b9485d740db4a1811c5b24bfa18587070540f0118055cc3085b94d564e720a95752a2508334a61906b5a5a1f7e56749881308314062df1bf54e5745d4af72fb8680e8cc2e0b1bb4327468a284cf1f65772b04fa1e357426150e2f4e99fb662300314a63065a1174f277ebe19f88451c6434fcd44519d843c61566f730d99e3cee884a93c5aa4582a48eaf2ac31274c17c73fd207314956ca26cc9dc27c5d8eb6260c3f6e41e57abd9f0433615a7f397539ed56ce1f264ca523a83c9d439f50e94b9854c6b8664d48726fb18429a730e51bebae61d14a183e68d16e71d348caabb1f6c10517fdb11f357278e18905332861b034361e33cf547d28d38c4918c485e420fcd4b6fe481286b5fa8c74fa481845a724f14cc84bd69119903049ed097f2ab47a9a6bc6238c59fa51aebb43f423331c611e376634228dab8f90bd749fb6c61ad96206238e9e949794d1293fc48b22331661d2f0d1c82ade9e29a24b6a62673b2dcab22bcc48844124c49b0da12d9de820c224fa41c49ed8ea0ef321cc21c4eab54a13dc3f8630c4cbfec82ba33aa46c214c6ace62d5fb87ff132184494e777bc8f191a3c8660cc210d4098f587922fea28d98210853e8f5f8c17357ce1c91dbc38c4098b48ce714237eefc68285198030e77fd3d5bc325d769236ccf883e95a457c7ffc770b397e30a62761f1473c7f64990d33fa60d6dd970f337a3e98c7a45c9e442d0d33f66010aea53c6bdbde776f01175e78d1650333f4606e13af1fff5973d619c18c3c9c2b54e9cb18dfc030030fa694ee1e543813415dbc83b1b453e70e9f676a5a543b98e5834417651e4f82e4f20e33ea609891d5ad20f3437b8c0ec64fe95325516225a56c0ea69ce3277a2521621e948339bcf7a99b7822c8ea92e461461c8cfefb123cd672bfc739cc80834967ad9cbbbbc68bf4e30c33de6090ba1ecdde34bb196e30674b2929a5f4a53af56a6983c1e44d8f7eaf173d6283a9e3e547ec3416ee396b30f5e82aaf48a5238d5a0d268b1fd352750d6d350d06b127d4d5040d0d660fb338a9973f652d04cc38c30c3318923e09162b46cee69d32185b82969482d0ea53693298743cb9fa57650ce6d8ad11f546e66973c56050ad7c51e24388d96130a5759ff0b62518f0ed9359f51232e30be6f7d98a97e45dde0fa9c20c2f98438ee36d2945081eda195d309a1097c9e15752fde68249bc24b595976405af6cc130b952c5113bd24ab45d70266668c160a2257e4e938967264f624616cc9b577e73dab31f52f81133b060b6d2f37e21be2e9a0d1b37881633ae7045b2deaaad9067d5580fd7c2bf7174b1013531ccb082d9459e4ade39fe96622412985105c3d749f556b1e449785430078b71b361af12664cc160da258267b887ba902fcc90825154e5d55139a419a5ce8882e146aa8d6da90e95d70c2898f7edf4c7c64497bf673cc174419fe9a0ea7f37c6194e308c9ae751e1cc53ec74130ceb26f6d7a2a9347799607ed32df12f397ef2dc8c2598cf530e167b7a12523843090613d367128c3aea3c7f98f03f2b418229ce4d90f5249a71046368b5af390bd2427f8c60ace4d9a9ae7253f45904f38f989f50a7afa31f36127f31830886e855317b524b3afd0fc1204baa8948faa6d46f2118a4e4594f25bb73c26843051f36d2e69811049376f80f6159cbb72540306957123331e5a5c4f603c38b49d28bab3835a98fd445670e66f8c028294e6be59ce6b39e7a60d059462387d1f4d12e0f0cd73a314bed8a4e4ed948773698b103f39f8e45d62b91b4bd3a3049d9bdd0c9ddea7fcd81415d794ed73d75ea331c184e828c982af777756f6010bb11271baa849cd00606f3db53e9f35ba5e86aac7dd8486506336a60320b0f6926b95a142d0d4c6aae22da08f5fce799310383eeb0ac9c77a13b5f3364601259fd2c87285af9cc5818bc732e1fd7de6ef5790d3260610e336184901ecb40c62b4cb51a6af53f25774bb36980d440862bcce1d923495e6798c4c8b6c2f4162dc952ff64a6a2066e68218315a6decbf5679ea6626833a0058d08d4c0d10ee0f3a25180850220f878808c5518649f24990f931a6bfa0719aa307eb07c75a227e1625f63ad0aa981a36b905418dcde372ec70e16b373c3c6ef7d51010f8f1a38da013570740d09f8163734208174c3e3865742062acca2557a45c9ca2c55d758ebc286c93885d9920841a514bf7462de43c830852958b63f616b6ed9ef52987bb393474ba9d4682e831406e9177ca2ac6d895491310a43eaa0b46acd9808f9230219a23028eb683149e8b32482a130dfc5ea4f3c29aab2ffa10419a030041b4941ded7d3e4e41326eded53ffd6371bb9278c92fd846a9b14d13ea9138611caca7276b9cb71e38449e4544984d39d4d94d3bd7388b264d5adba0c4d182fe4c497efece51241cd8439f6e9a03bdba6cb24e5c9c084b92ce7afec98923c65370d322e61f034d244551239794e3901f21a5ae04186250c415b2bcb8b76391369250c7a4f9ed74bb4d0e651c298a15ea2a85c8892a449983bdeaafd841589b949c2a4567e31c5437b1991309fc895b311ae23d117820c48184b5cc9d1e6e31e43081f619c147bbb43a538c21467fc83887b712bc55690d108a3e58ab6f9113a5e486284a93a8c2ab91c3ac8b86f0a321661c86ef292ac07f3b858086428c2242c94fc7c0a41fd53321261b820395775fa3071224106228c1727e976bcec9e2dafb156ba90710893c5d0115e2d5d3ccb8d761c1f072d20c310c64e592f7a59b6c46cb990510873c81d2689d029abc70961f8339dd5889eab84103918c81884b1439cd50fe5b1c2bd9a2a9021088396edcbba957e3ccc390619813029b1772d3934d34c650908a3e8ac9dd35a589c5f760b32fe604a26f258a7dbfc60aa0f594aff6ca873530b32fa60c8dad1d2980ef3c1202c8eea76d0f649d92bc8d883e1e36513225c8a5631f221430f26257f2e6c6e555b8c0332f260cef693e38308614cecd9041978305ed6f3964b09a2fdc30419773029bbbf102c9a0aca4576300839313546e6a9520b7274f1059aae40461d4c215a58cde94ff16cd3c12444ecadea8fde1964ccc1a0bba154a7c98a8fca0153461ccc167496cfeddde229d37c830c38987b25560ad59fb3dcf806e3b9beaec7d3718351444e7a4433de93966d30d74f7a75ef9c9316b1c041061bcca2f4baa9f5f7e764ca32c858833174e4cfe78e3ce2924e95c544728f3a4a83597d3f9dbd550e6a3b1a8c9a753a9728ff0cbdcf600a71d1d76fa10bc8308339766ad3dff1fdf288cb601a3917226ad4ba332383e1e4255987ec1faedf1a6b1f3612a640468d87a8449165d2581c0a8542a130280c8360f97514c3130000000c1e144763f1589e29bbb20114000442343056343c1a261e141c180d04e3703014068403623028100883c1a04028748ea6700c8b0f2515e935d557e99e1353aeb200d9fcc6754ce80c44c8119e6eca14f2955f0f1c331dc4d506d130fd1664da9c86913a19b432400ec18bb911457805b235891c4adeb651768091eab5d77e70eb293d81c4e3c2663444d433d431c64abd4c5e2ac4d1c74d6a65fd83cc3375796e80d929b7b27a360695d2efaa2e71a4927e3b85bf4b25079595fbd2e12d5ce34315756e9604bdbcf2447447949e31e3d015327d4ce6ff8eec4b19a3a68e3a2824240fec1a0c4a29cd8fb6e60e69df702c238fdf5a221c2e4854653009cfbb492309094f03eaf9fe9a44aef6c32dd154193276cd51fd88a713bc8bbb72614d62280f0dcb61821b33646137b0ed5c93179a1cec5d49039ba3d9b0fda804b3e9f375fff67495c531f413d2479b2969aceb4e308b44c3595f73e813bb039d68c5d33d15d074468af96cdc2007853353c5e872a516d2328d89ebea6ff34492bfb54746e9263e4f46a88595c6eb656166e4b4ff48995183760f133699db9bd3f91713e84bb02dc6f0280d3e028b4cb443aae45f1c59f868e386af9883444a0c3313163316ca65789f5ea051f15275dba98b19f039901ed51a26979dcee3cc0057f7ce0115f98c7c50561c4c70dd3a131d688e4dc0c8dbf95b9318d566b8c1a7f13a1820aad3e997bddc246f0501819d6dbe2124d360abe4e6f8b9d368b0e6c9650340fa75193d3f6165c390a5645f63a4630ccf23d0e988b646e2c50832237d45d706f09fd133460c2820c85317b4b310e3f2236f200911d6195baf9b946e95aec8046f4bc1f070cb369f9d994f47d747526de2682ce100b47127ff8a3d2a827fdb6947e9232edde09dc9d118655613024789b870a8c4c4a9023cfda92f006b7e5c2e9d26fce8d3531917d86936c09666eeb5d85105f5d96a1285d0cdc6088b5a9ce6545550fc62a55a82a94c5285fea0553886d36acb8394807da585bc695904589de91399533c582c41d09282f086e43284ed66f127a2d58407d58c66b9c3921c44d3fcd24a15ae568a5ebe6f550257005784a41641645c8feea284388cb28047a9670165e2f8fb11a03a18ace39a114bd716a330bd6276af016d6933466fd716a0dcb83e56b4553be0fa08563cc701c9322b365491d6fe6e067f4ba7832e5027bb66a42fbf982ccc6768fc7dc748ef8e74372037cd9c2e33969a5a8ba4340cca6fee1aac57efee90f26fbc8d5fed521609bde34a2c38edbe02fea1995c710f46cd8926f3ee054665cdef666013642696a1346dce3d9b935fa5dd876d522b5059bc58d8f7019d0b133494c18577e1ade092c4b7e1b12250b93e882fcc097e2ae27503b3d2c0545f0c15adc5328f93e951e631230d6579f7ee5aa358ed8e6ebb9f50ef288a543c45723ff414f08891a6362b4a7c585477a951373813a589b3513eb6ddff4d1dc76fbea8e4b03563d4f4bf6943bedc84aeb862c66254a72fc70a4f997d631840619285488e43d4e4b0618555bf6ff7993cd9c8e83d4507d42df8ac79ecbe7cf2cd1c8d51ef005c319d1b591c3dcaedeee1909129f14831e8e934bd575423155c6daca0b333ac0a6288eff7190aa94d26df0fdcf706f610be82a7eb81c69d36165f0f97435d6808c0b1cb38c91ef6680f65f2e8219ef2f12c2412c80db41909203cb5da2bf76d21f0a6edf92ef1be0a0dc7415ae763989a43686280f82c90a64bafda406d829e67911fbce7e50f8c9d9e4886e2a7f0ae954326d3c32b98d18ea3780d645642a76850a5eb633d09e8062181b846d2463553631a11bde2cb0b24489d5c41f0e67fb1e1e37159e99ea2a66f93fdec382ed9b60121641223d50946ac1edcf085e577a3607d2305510bc6127b38b6ce897021a09a554ad83c3d3d47a05f281ba95037af91c7229f145b624c8e36600b5adef16bcfb3e1760774c91ec83d9675d492ff9d7f424f813ec8f343d935f6c7aa7cfc9c9ad4a26408f147452bee24bf1154bc1421e669da917700f30d08c8414dc3d346bba0a73dd70d13a03c2c17d109ed09a71b79f24fdb04b7e9f589a33452ea6200b3c60c75e108fb01d33f2326022f2babacee5cdd3a9dcd48a8142f5dbb74c2bae7f146779f26c070527dadc04e5142a12d06ac44d69c68bf20ab902b3411ca990c250ab4a8295f5cc92e01c46c6f8313e0730889f94103c04a271a1a9668810665cbd920688daa2ab0ac9089d20193eca01b8c49e910e2df46f68e6512ad5385b1363d016da0c30f6def5453d35149ea2f30c5454043b06f36702f4f07841ed2c652a2d80f84f250e051e9685d1b9bf4710af4ddd3b1d58fa546fbb26549d5cff4339a3fe2537e46cd1a727a1f3f31cace2767cf447b6a6c2b2cd19c2803d01efa9f731c22a779f5235b4e70027e715161c209e23408ee15fe61c60367185730afa1e881b4be630e3034af70f8f12a7a902977fa47676b95d68889364acb145d639177c06c402624e91f0de01b5dc7d81f6f3157d71c7eaa54ed09b7247420260af12fa8d1892c80b00edbbf50cff96ad8f070f160ba02ccb6cdc5422419fe282f4a314fc05b20ce8d96f4f17d56882b6ebf66059e8d107135521051db202c099b7307e57c67fdc9b27b44e03c49d1c0c37b7104c0a645017d4c3bbf39d81951e3fd03ba90de3ac1ec34e460461009e16a010e9b2fbf61e8843fea3b97269bd44ddcfcef2e4f50978aede1bcb4735261489dcb44ede40c69ab926d5ab441563a5cb5cd513c0f480d8247ae1b730a9ca315ebd32be660f6efd4c762259ef8d71dd7b3c4ccd7b401479be91147834f9d154b8f4bfd1783a4174d4dbe951736a5e4549c86cf4313a4b6b52a8645720a692995df1f2ddafc3fca341e4ef76ba08b57f15a397087c02db6ab1caa3c75426fd433828c9d4dff765c57a22196fac2487ea9d1ec49c876ce273a353ff85278af8ab87510bd80ccd5a2a3672c6a1cb965cc4b0ace8757a1d06370430b48829dc1d645c47c30e91d02628f193ebaaaeedfc51f1bda6474eadade3e208cee33da6295565679256c4d188b64fd6f31b0614a508f9efe35e025317eac65e6f1b12ff2ace24a92b0a39e8e280de735f5e47a8ea2a9cd582c7c9f913def21a62c0d9708ac19216f7f90ec15018c5730f95e1f3b416bff1787514842ab6e6242c89073f69b261dd381c169785b0928962393eb211141e728da5a1147b02fdc6cf000f60b8adde605862ca16e03b1bf6e64e4f14ce7584274415967a248abb91498aad5e26818ae60c8a3985822946614154bf53ec087e4a9f42de02d4853b0b5020a2650ef0c9a8866091ddb41163b0f292e0dfa48426251b06f839e40f4b681606e5e4991057489c916c23a1a10f111120ab4a0eea4af2f70dddbfec18ac75d8058ad40d59ef704ab045c8e0506e67794163844b9efd8016364da9804ac9febbadd75aff27748af82661223f915301a21dcc370238d0a602024bd1fd430247b53534b3682fc35b9613ebe0830be262ba420825ba247918cb8c481a0ee359042660b38e7d2380f61c534dd182473045b738b3bd88d0f7cee823bf926103a40ecf63ea4bd14bba392e0725d3e2de186a6ed7d2dd18c0184147882d7d74f20a07f02b5e7c63e49b7d6586fc4b740f60a502dc94fd264c5691ed44e71655069eaa4c6a162a7f9d272abc898c52f2ad1c7f8a443fa2a18d66d8e95035cab372cb177a34618be41c976fec7648352223915586f21a5d968298ae628298b34cc729542169ad6216c156184b117d152582cd571ac306c5c6660a165f72e05a864c8b45e53dd71d6e8f2ead82e65944f5b7e0a3d16f1b608bb280a4b308d129e041a9c05d1260d01661bba70b3bd1596006a0c0e61dce18377031308211085a10ce0fff7f2d175e34669d839a773b2ee70f4e5ac544f1096d2479c1c3d1e8a42363e4511da8a34528dcd5a5986636ac1f1e55b5597d2fb1c7e13d4884e80a663246dba34bad54f32c81aa4d1a664651e8ab850c73c754189b59218b021d5a99db2c9cfd4eda91acac26cab18ad5c587f90ea981b8b1105e91d4b568ba85fbeee8fa609f2542566d22726436032c6f448b381725f00d7f6da63a51e2e5bb3f2080587eb64acd05a38f7b5faa830ccf81e6d83f6050098080b0a6e49c80709e02b988b6e97c2361c40a28eb5bc1f3cc321f7b2dcfd0d10510144060a3d04a1375e0e0825c87bfc2fc3cad2be668e87590abb907db333a2fc263766775c14e4c38ac3f41d5effc1410dcf315b1eece3416c9780ee738e6d0855aeffe1097e9329fc55d3e9ca9087c6df6b80a17e541c786e827099ce4b58a149e602dc906d8922365610ddb843ae98151c14a236db415ee677135d1b7075275f7097a2c7db3723e3d758d21e5462c54bf262b17e835eb50eb854909cddb05de2a3a42e75a28776ce6f754706652b4c3cefe11a91deb97fe26dd998a738d10a1fcfa3090139df3e1792db3d3792fa1a799d5734fe19a0ed81c15a46af7498e80f9e7f4dc8631d6f358b4fb7ce6d8ef496d391fab6b460156a256ac6030b05a1b43bfa89002589276a70e0e265de40dc84a84e5c84865a088ac1bee1ec06abf228b313a4ebfbbf9dcf8943699c0804b3d9fd09db19451f95616d378139a8d9e406c494208525791223aeb81cf08efd360eae8862706d403ae2817bbfe4490fc935d1bb4936a7d0c9544246d4d18beb20606739bb92e84044a44f27a1510d425446305293c1ae2b314d6feffc1b34a3638c94be379af27d12542d4045a5dfb6c7b559947740d5a75e6c831d8220cd4cf8a7e00e3812dd86d3fffdd6d5fe91cdcc08177ebd8e7c15948781be697d412d29e519ede69afd26dbe7d7d40ab6ba565eea80e62448f634b9b4d3ec6d36086214007381ce5e45158e66f6c7617970635580749fbe6003ee96cc3aa74de57ea131aca128ed9258365d8c49e1ee9bebad8baea4ea045a1dd445eb9aa551bff1f35282303aaab55f3159c040cf6999ccc99b2c93d965f3a952315162c2efaeb5d8ba5ab5142e77a35ed59e91f440cfe965d6baa7c729cf219e87ddcea44aab19f5662da7aea85642e55cae1cac810e8ce49c94c99ccc2dc588be5b20033a670bad09ea292df6aed65a28001fb8419a0bfa913f9782bba69d9c96ee5889373a889929155af5a87cd6e6dc1f13309c0064880edea01dda712c0691a2bcdc7a8dd32fd9d35ca9da2a44f5592f67f7b44a519e663a2d9c5b17032bd5cdbeaa0d0a7d36e035512c2eb62b309c35cd4b9408d1eb36395c47f42b69baa83781988d9f9af68c9693d0a2cfb19685467f9358aec464b1a400e3e90b4d15b63f6c618fb20b0e7bfca8b3d223538cbe49551a50f524d8696eaa54bdecccda325b59721ad7040b6dbd9576c28307109f07c6a45f02ada4021f4c27c70ad3fdd457f540fcd71ed041b90b96e85629e021e26b91fb357836a29e1b315ff8a662ceaa88cbc2dae08ab4cab5781264a89cf863656dd54a781afef003aff17b00b649223c61b3fc4ed93210d4083df7f2054ad5f8916a11250776bb474afcde8070a03ac3ab4642a82fd0d8b21d44f9e76c5f7b4306a0afba43828c8f86006948b2a39793e2c5fb67cd36e357a3ffaa4b5257f13d519c84c7e16c014db804ae7b16153b2ab5159a22ee0a8e47d2fcdc0c58a6f5bf55b235a4d367064e0e4dc820982ec17c53e9e09d18cea18a9b89ed4cc921459958cc60da9cd53225ff0e63ed03076fc4a041e6dc7b4e4a14fac99986f362dd8951d8eb7b95d7a28611de36663c2ebd3653358ca005f881c3d9897f0a16e74072eacd5f118d9b2a5efdb9b7b9e891cc2303021a86e3a6c8a1e43f0838d29c226cf57ff04dec33e016d6a36252c36ea35849c8b21a9763f5cfa7921011dab0f91be0ad109e2b39683674bf07674f3dad160c776503f680490ccbce93eb4cfde05655613ba1a611f364a06e2c4a0adef6d9cb79d12ba577a44d7295a8af828c9cc08b0db413592262d83227468524b572695a32bb50bb6910c25f221d1e459e6ed04a760fa838d2590b68433940f58691bdd1f17e25022389e0ea0ca70c8f94b603dae629bccf5a3dd7fafa071d093a0b6d97fc204a4c3ff45e90b1cab00874ad7d756880c480951be64e1ebaca40f0d107a5da0f1d81d059d59379131bb2288c1dcc769827490f4395a5248b4d40e0a1738c6c7df960866c8818bf2f167307a65a474d0e4e7f121d42dae258bd8e368c5f2520a4508a530495d3ac908ef6073ff0c535069c532deb887e8605b2867889a39b28730467b1df045fe000e47e3989990ba1981973806ad4085165a4bc1593924bf2b67ee49a022238f35b57b614c78c50f3931289fbcca9cdcb914d25302c994c1975870e2564a21ce049701d4afadc798e713ade09bd7411a40a316586aa3726e33c138cac22a532ee7c0a93fc9aff14bd712bc8ac13544d8efdfef2e7843779aae61ce72de0f97e7088bb782d4c6e9896a9a7632a536222338323a231b66e690cf92f64e8bbb466abee417ee2713178169814ce3f3cab878fa7828da4eb68c204976587778f0215358ff93d86c9f1da7f5c1a562049cc6a3435ba8543f69b84b781a58a98b04a45c9bf1f81e90db6a5a88153d7df27f23b4aa50662c433b04c5b20103079344120d91c52b119c1bb22f9100ef26cc7d746f92798e276ff8e7ca09fd068f4be423fbf992c0f99d10f7766b89b22c683c64dc34cc33b35d65871eb3f821d063403ed749c9bc0d400fea5c9ac30075cf431c69a462b1a7b219f68ba782255c7e683e988dea42343b4deada812efc2515b95025b3c5dfbd1e36217cb6fd280f0ea49fc22e81368b2a4fc6950ca1ead6c41c756b5cae6fda17481f4cd02a363b2c0518ccb650065be36a855cfb91c4d72196970e151c9429e25dae3d601bcbdea7a8468b85b3b2dfcd06e1a4d8f62a5f9ba3c8de9dd1429530265b37467ac74b3bd920d6b3e86f06b758f4fdc869a213da35b2971ae65609c05d6d6084e0d06e1c838a1dfeb6e0e9981011a7c0e427f88380cc0c2cacdd5752df7ad611a0f182d975ce17712a9cc292b30aa502c2ee3732fa41b2645d9eb76b7d081b0a24508b99c398440d7745229514a28e27a06002167b822982287535039c5f900163da00abc06453a610cbc71752de78db24f47e2841492a81119999032364964a5acf9aec81ee548df6ab3843717447163c286d6a084cfcd2b9f4dc1449111b2b9368afbc09e623171271f8f9ae165710844a9f0f6990e8a43c38f90d9b11560ca380ffe0eb9b59b411e0f65e1c8c6e28f2a1e8049692e181536899aa314129127804e3cbcbb56df21fd5be2c03c036039669bb355c3162a2d55eb00a0c9ee55fdc4e397f151504888d76721fb5d70e156d8a1258c6b39e6bf19bfb5130b57122f4b3b31b9d227e542b7e08768b3ddfc906067efb8909062ae0341bb0bad6e9bba5ed97ed8236beb408a1c2934f37a12f3feb891a1600f14ce07c2e6a83553293ff1441a2ad5e526a9a1cc1bfaa2e0a0097797cc20903967660c36109c602819029fc977350b10d505799971085720cb2c3d0cbb9cd37ba15ebeed57a3444fb18a2c4a10f80bd3b80240124d00086ba8f8576700a84c17239c5732c7a5725b5a22133aa23f7a9d9de905924c9c82c19adb4ea2c5575e61bc497c11f0c04cc23a32fa9a003cecc551d9ea042384a5f201ad5cadcae108a6f41aa960b032dbbaa1a7911ac62da0d819b7e744fa4ae22652b694bcd044772a323281d90205c5d5ee210a6b82251577c6ae0d70c99148ada5170d36751954b0bca23d7de4e8d8a033919278656d6e21f25c700041b0d6296e629f5bb8405c4ff6710ee00e56c1f7a4763b50ca5f061362ceef80e1904b2c69529064cc6b7c5c548fb3b64f259dacf03b36d9ddf5429993ae44100a51d35c2596505a5c040501a0f17751d00d8588369aac0cb8094d3e8b7ae995e87a88d9a3154c393762ef11323a406759431b868e3836867b8756c5dfefc509d8b75ba9614bb62e242becc15a7e25a13b623c0906d6a7a563835424a72c5f0f24e9e428b113c6901459832a3faa68543122575a27282e598e4acb2094be296b260f68461de88f59a22f600c0d4faaef59df66ba38876544c45fbcba2a6fca748dea1c57eb3c297f76d2004cd7a6d2b1001d19559189f29df22407539e9d4a396221bde703d8a83f2aa3c7650da8986bf9018711c782343210861750e0a1ba40ff641ee85635ca4642a6950fe3542eaa1fe3fd05ebee38f56d829c4bb9774623c4aaa54368ddc6509a01ed3334701e80e6774965fd7ab1199de4f99261b602e0d6549df6895d56053b1a492eefe45a0f23c9e58b5eb4af03fa7d6d9ef477a23754125a9b0a8e48f0253de535443e94eb0d9603118d0079697ab395ca1f16c9160c73600dc4bce18130aa31073abbcc1853ebabd656e8d6b432b42cd0597b70f674ca63111b6a2ce57ce8a60fe24597b02aa58fed7bdb80f5343cdfc92a225e8b02fb39eab7c6ef4e5f3cfbbef25b67bf053f58fa5fdab7c4ef565f2c7d4ffaa6a45f91a57df278f53447bbd75979c211925976126a1b251a27ffafa7fb836955d0e6f734b63f4ee894414c595b994e62f69f5dfc09093d7a5a1b410e66c398bc6f4b47a5a07eb14f0cef5a8ecc660f3f7dbc9226d7409b5368f9eff0b0b0c6bc9ccccb0347cf5c992bb49e631458b127ef44c6eb62acd7b203d62621f2d79834a481646816d50cf5669330dbb8d911dff6730373b8ff0674dbd82b6d95485bbf93ae338754ac7d72adc3897e2c41f29bc42a124704c92725e30bc7044937127d24963712a3a2929a8442e22bc9d625924d8c4625518955ca5383447c24aa2bed158073893be9c9b8b4a6a3edb07970af3dde535c082d078f661e3eeec771f892a7c89c475d1e2d3d08f5f8eb61d543aa87411eee95c77615349bc7c523a687821e1e3decf410cee3fd75b7ab207afcec71d68fed4d21c73a7b1fcb800ced88ae215601f185c8a9718ca8e8fea2304402136852e773ef5cc6dc38154709d11ba8328f2ef69ba23cd8f38e0917f7fa4d3b17ab02d6d936067d42722c0d223844530d6951c4dbaf4996653eef3684b379659624809d510afd6d024c57b6b4c4f39aa96ab5c3f991a6838adcf05bd98c7e00db019324c72d133e925ecab8105380e606e7c17f6d8a48295206ef1a84518b7de68e2e209810874e1c2343320a40f1b4dbc21222d3592f062ed80ae8c9a5e8611c534ddf0bacc3d815371278144dc15602ca81bca77d12b599654bb48d3530dfa0894c2a0cec0a33f31ce4b295491e9a716dd2b44043f846b544cc29aa5b420353b830475b0ad4b1d921f2742b8475a9a468637eb2c9724e430dd83247386e389c7a2d01701a0b6127b160a955e938c289cf05c7b3e2973a37ce871adbbdce2e1ab2114a9165a43179437663c6b010db77abcc2786499a1254e337a76a58360c54210ac9fc503d6965dd62437cf0db39b438b5e38219f22413635d0cd2dad7ebb4f8f680eafc362daa48e534991ee59ff25e409cc2097a5bccfdbdd82d9a0af8053c796d80d2482e28b3e47db119aaf2e005942160600ade7379ecacbb61b786d23e501ce9966c9715392a9d0c443b8730c07e787bab84968c58414d7026386141914f631fdbf871078f5baf3e6d6c3d48e16f255407b8e434084640a7c4716e1215323c2666ff88b68b6bd9b11a482cec660cb8586519420036266ed50e4a46870e6a49be064bb1b2fe617a189964cc13a7b6f55350c9e47a48efd563925ba6b746bf34499f85b1ff15f89f6e9148fd39c4839b8f5635a0226184a7aa63ee8d26ee9b4522bf0612846a6fa71cf5a4e7e95cb70bb944e8f0a2083ceed422ca8bf291d12eadd76a283194730311534b83d63c31940547b61049726078751186f9f6808515936e46c375436d78272173d80a20eb96efe242572ba38394724d3b7affe1894e34c4a46804be6ae3a2ab85007f400f4d00ab092d0c802ee0f9f1936b154aa37814cfa7de1542c0e2e67328d7387b3b7615ac044b68e4871e358000f4eeb2736a2abbb01501f26812c729f5cfc034b281e89c10709c5000e29e3ac5c4e65a59f09e34a7257560d1493dc1669ed86e005e7c7c2eed258ffa9cca61f7ae1d1cdb600b5bdb86d501c78206f056b279093c4bf34f53a9f7b40f8065ac623c7389285355f30ae06b429715bbe542a81fb1db278e316c68bd5818da16cd9d2ef063ba5026008fe0acc34c156710c480da7fb0e34668e5876c504a10e8fb7c4e6f07238189ee2d273f5b785ef929d876f94422645f08290457f9b1233676e2b6d51c6525448b06c9988d009923c72398bf297d971300f41a14f2951fdb95a06c4961c8ada0df257e68371bd16c010e43fdc3789c5f671e770f36a86440c45758b44034ff1b16454adf482ba65c0f93c26af075296796c02ff744904ef533db4090cd329522b2053bc24489a4ce527f9c783c227482d9110368caf03081ed464811e98626af381b718793196e6a3439e036c48d52360bec0432f046263ca3e706345dd7c283151ac431b06a5d9bb21278077bb5be32ad756c55b166646b427c3b6a6adad3b7dd5225b3b9af5a103a7d6e99d328fa5b986427b6484f5c5b1a92390a2607ae0a540a68a1f5e8d9193a4fd969267aa04b919c53642753ffe937704d27d1237467a8bb68d66c881de86f74dab694b0cc64ca809fe4a20bb3b4433a4e6be4a3e31d41d3fcac39078512da528f71927d7dc0494c51c15a490fdb2b8eb667cf9e5898d82f57384295f5aa0d761a55f0915048f53ada6d1515c04f93a579fa1d761a47a3639708c398914f465a8e4c3061367ad1368a6cb4da48f46070460b041ff58eb43d232355da918aeb9cc26f9cab1ccee9352a83eafc84869b782abf711c145aff6ce44d10b77ec1ef51193420ced8dfc141387e2c201fda4046f30455b2ab0ddb4036c4541a4a690e27247c29e9e5bd67e90c1627d3e7302ada05801bc118252935b9d6fd729c58552b9d376586f1308bfcccef22092febbd8ed82b390135e3154ea3e7e0fae5fb6a088f70c5c0f2f1d711ed60598fb8e9f5bd388644d7b493591821bcda8165c9ca8d95185676b71ace0a438e9585b482291764a89f3a65e8d499d080b48c851b94e44a410f8d8c0407daa6485209474185cb1711ee3a1cf3ea0dfc4645478157512e0433e4256a180698ce52c6e88255648aff76ee29943c60257aed1187b9a5be03a71ad645440c5c22a8a4542ea834f2f1362820b55135788acba4ba50956084a7ef114a81f87eea84e022f79cb9e30175a4804c444969a55df25710177434ea8a4fb48337c90f0dd289c71f3d0f00214229f0e9a381ce1d38bdef8056cd6e2a67e19850308a02b5743f285e818c56861ea407b2313bf1e9cdf8ab7193622de702fa0e09ee882b84e3c2c085d42f326fc4c423f783fb2a93b223540823bdf4f3762589803485210cdc40243ca219683c6a0a372b6b32ad5fcc174375c931adb1a6d58574d697e45a66fb79181559c8b71f816a90eaad50be42f3f741fdc7057f5e9544c8b8e4d94acda85b543398d3038c13f3de1697219b3407b3519b8d266024ecbcca858d4c3d2788c22b0ddb80641b18ed807b5081df10982f55b10c4cc49ea976c3cff91c73449f665d5134702930e091782ff2f88d7e5ad6582a0d0492edc0f9c3a52f57c00fb20f08e607104f2d0b7b96e92f97211632e90998c85d0fe722d2adb6524728f97f6802c85ea002f3b884379fdf4f23f8dadf5c0582ddce2ef23de637d3bcf6fe6e7b6b495e266b758f847d9d83dd179e38705a9ab87fd8fefb850bb4038a460987e2e52746597357b121ba6ece14ad9c14dcc082aa3d8a5320ec01bca340dc06cd1269c3bbf151efff71bd48901bed4d84905f411f3baf9166d0ab7cd4fb7fd40b20f4cac7bdf161bda88f355d7706d8eb7d4ceff8b8f11a2fb87a852037dce3d5c5bb25d156d5c92020d886c6b87290b213a4308548b3ba0cf6de6190751fc72032ad26acdd500fc1a720eaa4a762a274048c70c3b8cc2c3ed7b7deeeeffd78ffca3dbeb3f8ab9ab7579653a85aac3ff87e421a62fb19b6d99adf42cf146c9967a0e52381cd40bc2d0b75b7098c37d644725581eb609e8cc5c044ec81dd044c49389bf0a85165ff8a24e593f4fff04477fc0c5bb2de8c423ea20969dfc5d95a4db48b8a18bdcb601bc2a6d66c0700d63790155d884e9b4fbad5242e16906233635d2d8d453fef428faffac90deeb00840d88de0dc270024710739a621fc6ac206054925b84a81d3e89f10961b21482f37482a0fe20d0842ef222aca9c29825469a7d5bdf6bff881e13124dc0c6e5402750450015da19672fcc76340c634d8037cccb65a58caedaf501b047c3c69b794a256b5857c0881648784ef3e1517033a6d15e33111774898956608481597398027af7cab65075a5ea87f8909b13ee98a0cf1a40c443a51fa3c0bdb119a11815c7ca9f7ef86f13a1c6fb4c8038c7961c2320aeeb4e758895cab3cddece684873ad1cba72cd9aa3a40283c117722a0d197dfc71833ba3b1bb164a52f4fc7a1266b8a840937a33eb338f33360d475859b1d0dfff8dc3f88190fc4a014cb31b55748203c6ab6fca35a1b1e64c1a3eb2217454abf630c48495fadcf049f13e3306f652bfd225de84581b01bbee778bec44af16e551a87014bf6a1037c61f558233492dd320cabd87ea73802b3237cc792d2e520fd46380fc17e089cab21a84e3aec1e561646071b01731ed38f298e59c784c6a4625ac65c1123505716eb5e22e8ce81d03a7019465d22849273208a9b5b722f77bd37353bb48349da92b91bae90a610fa435d58f7b4ada1e16909c43de3fc97e094c3de8843355c5c101c60a0767679c823b7d6b3b7bde87ac1f262c0196de7c755e705b049e05096ca9a6cdeb141029844fe645fe976a0553c32ece269c82370576322f4ab1d4d8858dcdbef59f6e2ec0370d2ba9f39b8ae1e30766c2cae199eefd86c44759c75f43a2eeafc5fa4092f909c967278f689a8384cf8b0dd3e040fe03c2598811955d1301ebf2c6985e70ed978b83e131afa13c6af2b5cc25ec6e97733292be305b9a537430c2a6ccffcee4904c813933dfa228373af89f71aa0d6d216dc6d2ffff748a2bc0c527ddf106149a52584fbf05a780b8f179782300377280716d916ac67813ac52964e22bf1f453b7595487f3fc078e24c4f35cfac6c27ad708ccdab1bf84a8969bcb8243482399106f3b0a3d280e4187136b8e65811cf974eb94ab2fcd433aac47ddcb36e61d4c0f79f2cf17269290f1f3159e2f75b1f0dee813eca4021e89848e0b3660b46439330f0f0f0f0f0f0f0f8f3121b5b54f48424a524a4aabd75247e420a524a54c2989b7483b7a3a33f129a6c9de6498f8040402c20bc70b970ba529194ac5532a28a5c7a80127ee5f214f5a8e783a3168c07a4a297878ca32d5b663164c8ed729a9f85bf27ae3396c6c60c60ccf614305336698a143166c66448d9d4468e4d7c4823fedaff9a34760c189ee1077731c9d745baf489ecac9cdb74757703a42ca15730a19947dad605f82662425ace3dbc70acecdc3d44790ef216dabe0934a4a664a4e1e3cffaae0a444492b62a954b09a76a742aca8bd6799252a785bcb1d59a994498c769c82ffa484d211377595af29f83af9a33f9b8f1252540a6ee2a65df797284953527016f4925c3f4fdb3419052f2612c54f6645c1f9c5dc94dbaf34e8e0e1e80805abab13420e25bede9b147480824d136356ebd99fe0d4fd57f637992df6da800b68514ed0e1095ebb2477ddc43bc1c41c925f98162dbe419c603d922e696b494d66cf2698e469a1e67e5737d2046fcaf467d7f6c7c8e94c70494fd9e40ea57dbfc504aba5c77b83ada714d24b30392c55292d1ef27f8a25f8ad0c169e1daa76639560547559b885790a756180c60942072598bc41529c1c3ad4e23e6346c724b8b5f8e31b31eb3eac1a6934eaa6d021094ef848c8e1679d69523f414f60c60c2f3a22c1d786082a9a6b4ec999ea80049783526d2288fe11bc8ed021c7ecd31bb2c8118cd60ccbef49d4434f7e8398024e021d8de034e4dff43da943ca0d6211e86004abc12b8bcceb9e47c72c82efec5082418722d8efec7927294b29c9b023119cea94dd4bc6122ef22e0e175d74d1ef051765800e44f01526d3c4cfd095cf6c41c721ceebcba2eb4e372ae83004fb1ad2b43f3c2479ea1bdc51082e67e57491a5ec20042ba23db5ad8e490a5dc0050e1a9e6c98391737726cc720f00b49a530e5a2a26e0c3a04c1994ab595f54d67aadf11085627e5b2720b992de21d8060628d797b55a9945448c71f38a1ffa305b788ada1593fb06fb62629546366d3fbc05fafb7794bb0ccb4f9c07e8c6cb2d4b4072eaa78aa4efa3cbd5c3d30da79a2e7f4f595369a072ebf267597206944060f5c063d12ef2f596a767760b4268d3177ec16f5abc30e33e8a8c349765042c7545a0ac90e3af03958c88a215f4ea2b21d73b0191d72603fc9d131258fef9713904003a0868e38701b2c8d5c1711b225b57ff1451b1278c1c50d0a748e66c0052420811933686400ced00107f672f2159d624cea94ae60a1e30d8caba958a752c8775d631b3adcc0d7c7e049d3db36f023838aa0a116717b3674b081754b225685a05e035b6eaf6ea2a63ffda506d663cea1be7e3f8f9b062684a42fee6890adf92fde810636e7e5a4824cb3b45981a0e30cecae8ae6bd3bbfb69c02860e337071623ecd1c3aa59c45eb858e32707f994d591695e4c46fd0485e943b420719b84b4dc143b2181c5c181f748c814d31668b90f24bed88e4b0a182d221063ef2fa8dd96ec4be48018d30b041481229e4244fe434707071050c5c98fa4d41a62b499e44838e2fb09147d4d94775f111aa91c44a0076d0e10526ad28d15841684613e902a7b5b784a4bb50a1830b8cb6eefc93906369e8d802a74bea5f67ffeef4592d30aafbb3d783e7deb4d0d09105ce72d04188125a648c3916b811faaf6a21bd4d4cf10aec5a0c1dc945a9521347d46105365db2fe0ec2bf62765805f6ddf4e7385eeb163aa8c0bf7d324b093a22f7d33105c6826e0da9e33987d0b09156061d52e0af920ceaed453fa64a0abe880257a36e528aa8a3aff324b0fb4107143ccf5fb5fa136dd0501e743c81f7495a993a7a9ac72cdde81518740263df1aa4c5b1bca63d4d60438f6bf64d4a3d05bf461adea083099c1cfba469540c9a7a61435b80bca0000ae00e1d4b6092d6e49a827e97b549097cad779654913faba808858e24702ad47b2d54590ee16f193a90c0e71044c8bdb1f65dcd11b864392d57cc15b91742860e23f0b529c4226e4811f8c937713787e7583a18860e2270c94a9d48d14e770c818d1fb1b5e4256971371a2dd00f740881fb4fc24e488aa03cfb0e7404815359a966f235d79368aed001045e34770a399ba44e3a2b56e8f80117fc3d04b52153238d469d0e3a7cc076a4944a074fdd7b658db416acf5800d21966bcc53513dc303fe92f6ca1b32d4b10326e6a6354f162fa7d420a043077c090922ea256f8624e580af983daee4b30bcf260eb8bca8a34fc449bd7f75dc805352d3fe7609ad4ad7068c52e33b4144936be91b1d35e03c09fd697f6b9dc65423cd4632d322030ec841070d18b7affeffb190aacf8213622a88adbe27adb194051bb7bf2bc62ad3696e0660c4824d69539e64ab1efd9297b0e0bdd7df630c39e60418afe043bcb451ff25e84b2379d1e45cc16669ab6822061932556d048c56f0371e36415465d18c16197080b10183158c655325a3dadd5b560b2360ac821dbd92ae2fa7a42a7acf8dee95ed2e239b0418a96054a4c9c9f2078931a660a082d5089e9d95a2ed22458b2db4d8420bf30818a7e093d2eb2d49957809cd14bc4f4e5bfae40499925a0abe45e69d2521318690cad17e72dc80410a368b7efd90745809114f41c01805ebd9eb7df1828aba9fc11005fbaf41dfeb4b67356d8d2c2c1a80110a2ec4fbced39b2b928ae2e8030c50f0ad31c7d4aaaf9553e4e200e3137caea67753be9a2fe8a400c31368cf76b24dda453db5000010303a91a7eeed0ac13c5e3f80c109f6433669e5c1e2a9bce826b86c3a98ca21f754a4cf8dbe40299a6033fe691f9591f2f51f061899e0da6fbf52abe4fbab6082d3bfa395426b525a62b904ef19724c97a24e12ffa025f810b47defc40ede2eaa042754c794bc36e9a98a53820932fe645162b973266b128cf726a96ff142352b0926ae7ebeedbcf4b596014624f85361a75b7df6714220c1a5b6f4a4b324791e458f60f3ed72cc19b9bf73c411e40fc1bd73f746b0a63ba3a6902445513a23d82a4b5dea679d37ca4570327da9a4529922d8a094f64f113dc97037115c5e6f48c132e76bf8886025e62b7dd63985db7b08ae93e98ca9534ba5983704db9382f9067d39f7ad8560ef93daf2b28d105c3013135372b389b006c187875022b778cafb7bc31004bf195ff3dd0895589a0ec0080497f306cf3c75a1f29a040b3000c1e48ea5cc2649f78ca32bc0f803fb9927e9896b69681f0330fcc06898876c17327ddabc0f9ca5b6564ca67225e52f1060f0810b9195d734e7db905335d26ca4f4018c3df0b962e6deca9962d10327a964f2daf423b7926aa4a5d4346c24d30318796062aac98a936e6cadad9176c34990bc0bd4010c3cd89ff95fb91e52793968b4e0468e25598071076e82d4d518d592aeb809fa0476605c84ceb7119279bc8fe100461dd8d2a419aa555474a7d448ab1bc0a0032b1a24a49cb2eaea8e36ca66ccc0d145172968c0161478038c39f071640ae197b264f2a01a69280032c090c3b61be32565eabb730088028c3870494d44dfa4a275f90b07f6b3a62447ff7f48ad1a09da8b968041018801c61bb8f83d312ff807bd34ddb08132100b186ee0b754a760992f681c2260b481d32f4fb596db647c370301830de5a997875f673c598db4b741e3d0c05803a7329e92372a4e022fb830247f80a1063ec4cfb94d6ee6268ba681ab89f599a496d22112c3051868e0feb285b28d24e35ab600e30c9c45aad211a692d000c30c8cfd071962d41ad174712307a2d18243a30cfc7b99672b4d3949ca910c30c8c0e6fb243379cebf51df301b03373ad2bd2e26494a4462e044040f7d15d1a46a65038c30f09541c8b6104769a81b0c7c753279426e48a9319906185f60fb5fbc62975a47fc6b24046078e11113c9fef9ffaa3287d105b6d428d51f5754d0ae6aa4599a4ee003341200830b8c48d7a98269ee18ca13dcb8a10005cc98817c035d74d127c0c08c195be03d87de9e28e1e96987a105f64cc7d041ec3e30c0c802e721079545971a4d8f1d16d81c796326216f185740e74c1db28bfd56e02a66f68a0ec96973ea0930aac06bce6221a6f206993354e0aa468b0aadf89a9d62028c297039a4d32a414bff27111a0f6048814b49640ed31b31e8e93900230a9c087a2fc4d39ca43d9f000c28f06a6a44ccc94d5d502a3304184fe02ba69096f49350e67921c070022342321d794253232d078c2630f9c72af37ba8a568aca1000613187915d4af36a95cf5daf8c2d1043096c08e96fa9873df35d26ac050029faf3fad093d31a5bbd448c3190148f068059bf52d66ef901923bf1eace06aaf764210b9c72a188f9ff4c5948c9a6378e0a10ac62a77084a4491f719d3399a0137f04805e321b2c6b0efaed31954b06dea962789a6b47c993123070e270117094fc16a8af92586a89a6448c1c0c3144cce7275f3a8ae79548f52f06ada31a4d51f7539b413f8000d16789082bffd0e3dd31e09ccf01805eb39da26ed639d5b4913f8008d2f3c44c1a68bf729df42279d6a90808b1b34be7037c1bd4728f8ecde762a4ba06072e61a75f22d9fe0f45bea88dc9954d248199e073c3cc165ab77d027833c61f94eb0773a832cab0921c939c1b8baa88852f24df0b9e2080daa214df0216c7754b724ef0c658293a63ab2a6ce23f53e98e04ab9e48b1ad44ab29c4bb02151c342ecd38b765b82c9a92a497ecaa2c6dd4a30394d7e68087aeddba504374a2d26097253fc5d27c17f8dd08ea0a5e67f4a82cfa8adf7613196dc4d24b88ffeff9699ef7e3190e0e4782c31cf1174102a8f60d7f3a71ba5c4930c298e6035d46628651db61d6c04971db7da649fe7df8e119c4a4149b3d32f82ff68317ca32711324811dc774e7921672d119c8e57e90c4ac8eab210c1a9a484d4f862a534bc1a695e3c0dcfd13772e038880bf03804aba1f7c1c79432dd202f52d080b3f430041b736f8e9c4699f6da0bc1e656cb1d25d64ab6122118f91d6f7d62e6cfb11e04eb112be9d19f20b80c49738f504b1de225106c929982b0af511b6904045f7921f7e9789327e80f8c6adb68c7eab7d1cec30fbc67045922aa059dd85d7af4e13cf8e0b1079b31060f3d9c7aa33fa5e47469c005b420c7068f3c9c071e583d3df244526d2a6f907fe1450abe01382640812db4d8a2015a64200236328064021fa021804a78dc81bf0d6e5b25a2259196871dd8fdd7f50e0f41e3df1e7560cb5377ba9c3f5552e2c276e0410746e926d55deabf23a95c0088e03107ded42c4ecc7d3284ea75f09003ab6a31d364c7a0db937160d399507949b5396c64408b2de0c05bcee94ba998ea2f21273cdec0e7ce23fc94d64d88f90624e1e1063eb5e670ef5c9a438ab581bd931f6a4c4653a173b4d8821cc2830d5c9a103fe4f4e59f7d6468f0580397b32521c57cf2070f35b0d92e53f576877c927273f048039323270d41b99fb7bf72f04003939406952f23581eef68b4c1e30cbc66cb6baa63c94b9d3703139210adf71d595a82cac0ae07e193840613912032f09d95425afd6360f2e4cd21aae7564c550c9c309df933b7835fb084810bb5b2d21002031b9a2f495745be53ff17d8531aa4b5e6eceb96e405ee5255b6d6dcd0340f011e5de0367b68470f1616e20227d2eddc37d85b6074eedc9c72b25a60ec46d5e999ba9c72cd0223ccf46de5a6e9742616f80f5a91b3d66fced75c811f11f57e5267d660b202a355edfd29248f6d972a7059416d95ec8c1a3c4205ced6f2b628d3794c810f3993f6bc9162324f57800a1e52e04b480b3d5513a4f5ed110576f3e514c735a45d4e99c0030a7c861063984930eb9026e4c2e3099c9b4e22688eceaafa3b81dba429a7984a9dab83155080210ef36802579b635df44d954ce916e1c1043ea46c9aa563e6b10446dfaaa6bf4a15aca246f050023b4282dede5df90725d982471258f7fe2816af3ddd5624703bdea7bfedf4678c68c1e3089cd064fb7d2d2286c9088c29c91a52d4eb0a1e45e0236e6886c7495a821c041e4460834c49176c73fe7beb31044ea60a91738a71250979088111312747b5adcc23084c122aa44cf22a0f20f02121c7b6cf4954f7a6465a0b6ee45063c3e307ec7dda34fe39a66e30053ee0dd83728baf21dd644b8db413a4007909bce0225be0d1035e350615834e212c49be465a5981070f98aca24b34da6ae51e3be07a5dff4ab958238d460b6ee428393c74c0bd8f10aab27257ea9f032e5db598a794e57a4133669475c10307bcc6fe2e3d6ec07e5433559bf2353da7461a0d1b393c6cc0267d6f955c9e46b68b9c056e47c3a306dc45ec51a5ac2d66b088c08306fcf7a6a74ee9e324b8d135d02c181d3529ffa859324991460e1c292841eaca400c59b0f9a2b3a59be853da353162c1c64cd66964ff38a41003169c46ba9c41ad826e88f10a2627156a375fd4158cf4aaec20820ac973e7857371a3015a6cd182193368588c56c46005e36b1152455256a95b05d7d93dfb3e6570f71055704aa64bb13685f869cc18a960c7dc3666f7a9fd9ca382dd08c94e6737113b055f6661f984c590738818c314b9aa55e52c8dd1d42ca34ed618d26dd6766aa499c0868d1a34ba485cdce1214629b8bebc3751ea79498514c4f4be979a6246c19b50eaf52b8735d2708b1a39b8e0008e2eba5080165b68a1c5165a68b1851618d0220311d0824999ab808b220a46a407b5cba6a2878c8682cf2721265332573140c1e51845e5ec4d9a945e7d88f10946342519ef3bd4b37ec4137c0ac9b736d6a89887189de064ce75fa3ce48f5613ac430c4eb07e9e3668d9654e21493818b07088b1093ed3636a11a62e52e50a1b626882d7789692a9a568b14506228003c71a62648295e456ee27caf276d4628b2e1a139caa84549a218f5ec9b900a315625c828d25d4a5b78cf55a1e4b1435a9d363414670e4701c38ba125cb692b13b6f55de2053236d8d023128c176fab84954526692a449b0976152d46411de16920497432af9313b4511033122c1a9bb2074eea0b682ed376cd0b03b400c4870d952ac8b56e3997edc0208311ec1a70f96a643e893f96b4770324cae5725a9d9444412a3114c6c537aad47a6f4d931825127db2f4af61c82ed050d37bb8bb10836e91c159328fd0e221e85188a60cc738a693fe2798960f74d88a4d01ef48b880836289d79b2e8e468216bc1a1c145dd03ee109c8a95b7c4daab9166086e4da84ea9757c73eac210a310dca53549323b48fa3d1182f114abb64f445cd42fc41804af6bde71b437444e108c972a59225e786bcc6ba4adc0861781e0be53d49ecaa2e91ebf91022fc40004bb9e556ff1b56bf207fec2bbd2c8bdb30c215088e1074e864ed0aa8c90009f10a30f5caab547d172ad97748d5c420c3e303a6811e11b74759a7a0facb85568868c9ea3c50fc4d003a3217bd6504d4b22796083f0902e3f2fe95c1762e081dfd35c53c29412ca3bf09691ded36f8e88fad8e18b22aa73fdf31a69756077e455363b9d0e5c5fd4117aae29a84cb939f0f6197244cfb9dc532907ee2a6dc516370b1762c48133f5a733f7a4706064ceafb42b421be30ddcc4cefe65b981bbe0be9e326d884fbe0d7c1c25b24ab610830d7cee0a524f6de7b57e6aa4ad81fffbecff4ce376e5500397634bae1d93c68d1b9e062e9d4efa13cfa0611349f7ca83e40c7c9410724e976fd4c5941a696660e4e94b2252ccad0c6cbcbe9c734ceb924a9aa3b9487e821b9ea39c200619585189227d6d53236d0c671e59694a2d0656638ab9c9ae114134b00431c2c089b098f64352cc3e18f88f16169479ab4d8e91408c2f302aef87d2ccb92d6e9404de8517f80aa9dee3b9f1235d606c94e609e5f5e29ae4029b73cd6d4cd828114f13630bdca9f10c95a216626881510b651d923784329d2cb0164310125c628658e0748a3997a8a0235ffd735760b29fda6ef2743926252b701f2aed2c998a355e57818da59134354becd1392a303a07d3f9da3b2429b11a6953e0c2c7ff3a6a6ecaa954234d0a9c950c49a7ccbc4446a2c0059d114385b4af13d51a696b218801052ed365ccb81e47984fe0b2b86aba31218244538e14d810410c27703a66a769319926b0a33d3f54d289099c6b6b277dea2cc325303176895e9126253022b609ed96df22799e04fe235b4e503b2652be91c0a5da65151162e63ca523f0f6499d5263954da79206621881cd74559335bc7b3f5223cdb010a3086c5ace7fb7691a3452fb8d3b33821844e04352a9e933aa246b085c4a7731e9979043525c084cfed1e29152a74d9fad915670e4c061e30c023182c06afddff5be8554c2d222030ec84004baf8e2040f68c08c19ef89860abc701578c176851840e02387b5c6d5fed1efb161e302c609317ec0a59b6c95b4e9bc9bef183ee0eabff7278d27460f38d339692d57a8a044bb80183ce04d73e59113a4694b290d2b7a88b103463d3b2fc92b0fbb910ef84c32e68831b988a63a07fc6bce5477fb49e4d662e080338f9c5493f73760e409adced33f1862d880039c08cf9b25e97bec3e158cecf15477f5a5af4605233f6ad67442648af6145c060dd994b6ce501353b01edbc264d2e9631d96825196ddad4a65fff6470a3e68d2ae4e3712832419551b05ef31d7c79052ca8f1aab91562307172b30d645a30c5170a9628c1d42893a335d8db44caa38b6033242c1574a224529710d3223a0e03ca449572363b02ffd27f810ddd5d77c927d9072514c8b0c38c0bbf8220519b8fb820c4fb0994b748a87529f773a199d6092f0fba821d489dcd5066470821beda5bdffe287fd65a12063138cfab497fd2fc752d76568824d21fd6eb5780e299a8c4cb041bbc44bb2490626f8bf20e4e4d23b694bc4041997e0fc3b8752d7cfb5ce58a212ec45d7915ae973ccdfcba0047b27c4d742f2c89804fbaa27d126e73224c17f8dbd28cfcf2163908c48305a2a5f8f4e823e2a03125cacf348224ddc2ec87804bb7efb5927e9e4b9ef0826fa9b66cbbe89a7f346706bd9e79a274670275f456d1cb9d79d5d04f717ab4aa915c16529a5cc3b79ca5ad944f049e93a11dbdc537244f05555715b63a52c113d04679f4fd4784ac92b260dc19e7a89ff1d3b3db68560624413797dc44ea247083e272539550461f69a41702129e5a9b7b1bf4e10bc650a2a684fd5039c2023105c7894689faaad363f37640082af3e15f33af687468e147c91e60f6cd75a249d5367b3f2981fb8cc1941c4d5f4411fa5566b5bf36b5c1c4f34bef01b5fe05824c8e0039f2b55ae05197b40aded84f25e53da480603197a60edc5f4b49d87a4cf3379487b9bb4ef3c92aea3810c3c305a53e4a9ac216e6db0469a17ed5d2075818c3bf096d3856429febb586d59906107f66a74529d3585a424c70b1975e03ce4f3b428f21a8932e8c0765f2a33515a3f9ad648f3c2067216b8c998039719d6d9e46a726074f342551c292a6e70780964c48153aa72678e1fd93c66ec0a32e0c09a0875a5335310163926e374c173bdafe3062e89147c9372977c226d603de22515c911e375cf062ec8f83942eef1d0a0b30626f88e760941d6e566d5c09d4649232ff4594cc934f05d2aa297ba0822f468e05cd2441749f2abd233b0e9af36f456e4aa57ccc0e7a9feab495f414e481978d33962b628f53e3224039b33e62c495a8b0ce518380d316ed5e8ee754a254186183820230cec6d8b16111fa920030c9cd20d79a695a1fd5f20c8f80217f2b95712ea933686d0f0027b1a3e3182b264e0ae0b8c12215510dae45c60359dc817af94b6c0e8a79c1bb5d4f77aa405de5cdb74446ada484159e0765fc732af57e70e6181bbfb492b5a934e10cf15d8d4ea262ae5ea50aab502174be68d21a8282ae25a054e0615d5be4f757fe7cc9821830a7ca84fc1c44598104a3a05b65b35f6a4cea14d4c2970e3253b8ddb8b484a13053642b45819dc4424cb4081dfcc1e2a8ebb2425234fb82f67ee04eead83762a4fd9bc824d6024e75869344529e5392694efb3658d29a42c81db10aa4fb608b5053294c0d6ededa72f51d1b93256909104fe5350a1a35208a9420709ac67b869485abaceeb2338d691d35ea669047ed4ffc9319d9c31e32c0e328ac0b85f36ddd490085c7f705bdfdd5279913104fee45e0e35bdb0b31b0d3284c069845c55d922230849ad749dec723287175c68e0466f607b200308ecc4aca53f758a3925f90363704b63e6dd1753b620c30736c368c8e841c9e0017bf162d555b56ffea406942063079c576b281d7453788f3366b4e0460e2f3c083274c06a66cc3984a0e1a6fa1164e4a0dc7e3175ff8e051938b0b345634e704d31fb1b6c29659b670649deaeb962c84f8b20c49b820c1bf09ab531638990242765041935e0b209194b8aa56c70c181e43366e0281cc8a001972a7808edaca79de41fb3e084243522869cee8490f990056b23729b50e1971c7cc482512958127d13359b5748830f58a0ef6a820aaa57d493b49a3ccb1e4d57b062a7720a3ab5db0a45957a48c242061964057b3a66cc1fdfa4986a6f7cc102fec286aee224355228212fbeaf0a540a75de49fb099954e03f298f28212accb415c93ae8ebd66e9080d8ad59093e4ec1a78ed0a31282d0112153707b766934c54e29188f144b7e1c99b4a73429b80dcb2958688ba697320a46e9fc60f92a9ed3591fa2e094c79f9433fb72d3130a56744e4a59e6a0d1e20414dc79a83e0fda8f4f1462b28db1fcdbdaac91863a840f4fdcd9ad734d35937a1df8e8c47d7082ed3ed11925d5fbbbffb10956c5f24788e62e2ab626d89cda749490d68f4cf031a78da619417aeeeb295070021fa021801b7c6082d3a172723b9d3ab1342e6ed820c11962fab804671da3c6eca492363dd6489b3163c68c0e68b1851612c08002dc1b90fc4af161092e5432f5ddd6a07f3c3492175cbcb1c0d3096cdc6840f2828b06ac81e2a3124c3aaf9843997f8e9bfda0046ff6edfaabd19304f524f8714dfde9dd2aa693241811c5837787de3d8f44824d31b9566ae7cfbaf003126c2ea5520ee54978883e8271cf9e2cd3bf594efa8723b832fff8fed9f4d108f6745a8d765c8b91941f8ce07227b73f09f93a7ef063119c8668ca4c89a24ead0f45b031f55b5626a5f9d6fc48041b64c829e59cb4acaf43b6f0810846e555d08fa1ea53c80fc16e4e272187e8c3106cade68e8a26f4c4d41f85e0b3a6ca9244559b90dd072158cd49f976f613fa921f8360b527dae9d54b3db9fa1004db16d4494c8baffdf98f40f0e1c9e4269deb0720186dadbd1d524c117ddb2f3efec0a80d322c5e103d513f303e29650e1d7699bcac83c1471fb8b8d7975797e927311a7ea38fe10337d69de4988a8468e11e78afa0df752ff8a107ae23951069554bfde9e0f8c8036f1f2b92f094f4c64e1b5d9032047ce081efca6b394dcfc23bfd710756cb4d6f527f69418ee6c30e5c52634adde8e8764ca903636d52cf723a912fda0f3af0312499f39afe1f7360443a2d3f21ed36c6fc871cf8afa064b2ace79dcef2110776829d483929992a09990f38302a26a1e3e8c9b13ac67cbc8151dd793ae712a279ff0f37f021ae96c689e2973c79818f36b062ff914c722af92967cb123ed8c0fe27fd1b542acb7c9f123ed6c0c9d8a984d20c491c5cac171f6ae0fa4d997e0abdcf7da6c166d4f840032f9a9521585ac82dffe30cdcc99824050fd10f33701e296f44cdf6471958eda02455068f9653f0830caca4d8c9d5c61f037e8881099af268efcd787dfb4718d89c967b9f2fb5ed8a3ec0c0e7a0524a8a9b4654ce7d7c81cfec761df32e5161faf0026feafea22eb21f5d60cc3f075315838841eb35d2f04a0d3eb890a6a45649e70d8eae912ff8d802efef9a4ce87e2c65f91a6938b8282df8d00267e95e35d384605b1678ddcb11fa29c898597917366ed0c002ab959b4b42eeed3ca181830bc2828f2b7039989652d32935d2f0c30a8cb0cbfadb552724c96ba4e1e81af751052e26f389969ade23bd393ea8c0a99c838be7f4a9c24a16828f295c7d4881539ffe63786f28f01105c6b5cefb92e9943bfe8002e7aba669924ccbf59c65c2c713780972926f50c92685c8096ca520d4468914dc6296081f4de0ececedcc474a0e8f0be183097ce50b3afd268b153f3880f0b1044e54b99907e996dbb4317c288153f25ce28d6b08258307868f24f01394298d6216eb24db850f24704974344fc1f25d2afbe3089c9d7b56a8a7f248b9113849933798e5ca471118a51db47eae8e296e0c143e88c0462e2d42771aad1ffd6bc2c7103895835d8ed61b5397324008fcf7557592a3a2dc844a193e82c0e8ed52f23d8af85b3a0c1f40606f54cec1e266d3d3e7fd80310f26477f1041552f1f70935f538af8d9470f88de1953cd552cab32b6c7f01c644a2aad91f67ee3cee40a3e78c0beb678a50ea6c55fb4032e551295830c2a4a2cb50ed8108fa2b565c9d19e02143803906f60c68c1933707861e32307bc85a45719f69124c4940b1f386063d6179d379fcc6a9d0ca4143e6ec0fa96fe98a23fb796f861035ea4a80fe22afaa8011f16aae3ba994c42947af8a0011b928520ae51fc63d299055fe7aa5e2a043bb1943b78c88211a637c87a3b8b6beaf400128b42a98d31e8178f153c60c177e8535afe1921063d7905a752b44eca21befe8ece156c0e52fbce621c25540c034660b246ea4d3ddfd6eb8bc0d9e438923de514ab9e08dc55bceb9359a6213f04467b7fc8dcf974d0bb10124a973e08bcd65f32ede974aa4060358a12aad631acfc01db5bd9da366cb3877cc0dd081935633c1573ba075c44d752625a76223c60bbdb2bfba885d276c0be08116c475d94d4ea8037e91fff4348b12d25079c1275e341e898f7c5019baafdb5d2ed4e94dc80094a8688faa7367fd006fc8e8b0879a38abed48051a35bd9399632b402d080514987fc591b29f7b3e072c71169b34f845616acd768659094c782334d4987da0e0bde4d4f92a04fc96bd32bd8db20fb3a23858b5cc1c65115720e21d3d256306e4944922521c81c6205ff12ad5acf4c4d735661b2e0aaa9fe2655f09e7f6df487a454b0112fa994dd3c882c49a86054cc1e32675db749d22998ece1a7b326b72c319982f5b8a9a28fe54bafa352302a640549dbe2dd3a22053fcaf49b0a32e42073340aced33f9b3ca5ee4b098982b3e0b14a864877a38442c149d09031e974b92642a0e0c736c5fc63a15f1ff409eed426a6679ac674234f70597db5bcfb62451a3bc185a8ee649ba29e082758f3ee64ff7b3a68bd092ee55431b5e8e4f94d4db019fdd3645e6a4f4d2638f59758ba2ca9fd10136c0e6e962aa86a3ad9253879273f830a5132a7902538b7aae41f9a45f44ab09f16646fc85322bd9634b7ae3e097ee3faebe9cffb133f9260527e3a2d6a234acc23c175d61c2dcfb4165a487015761f43c8a052c4a047f0174476915b11a3ba8e6037e6760d2286598c368293f43ee249533a9711ac9e5a456c11a96d2e82ff4a4205957e2ad75404a74ac6dd74ffa85f2682913104d1a33aa2990c22f8daa04ee792f9c6c2738833ea7bc7102891b1f772af5308c6ef53e9484944ca09c1760c4aa8d1f126c02018a55df42475775abd092008cee4673a61fe23e4a809100836bf490d299dd4baa0260020b83eef0d162d2c784e13e00f9c68eec578e9cb33dd04f00313f226dfa46356fa7a1d40803e70fdaae555793d8a960f7ca89c83c8a7ddbfee1ed8533a2664ff27c9ab1e182b8d225cbf4d4c9a07269e59b020647b87140f8c502a478a3596fdd33bb0218ae6ffdcf4aba71df87fdb10849e75607469124ae7ad9c7d3af0a92798a64f4977690e5cd0294ab6502b21c6c8811bdb507a648d3e691cf851b5a3e3a83c2584032333d3a95fe40d6c599239ed836ee054ccf27b450c53b10d8c46cfa4bb535e8e1f1bf8f4c9a37e1b9244afd7c0ad5e4e509e1af8b48dbdbb9252779706ae425c2de88b896b8d06fef365c59c381e72aacfc086dc78d994a87863b51958fd98eb4fbf5344be0c9c9b4e4fe6fd92813753e331df6fde7d770c8ce7a9241649589eec8a81c99fe65a22dd30f07e2295debed62475c1c07632f97f7fba5d93fb052e6e9eaea02c45af5c2f70be25bf53ba0ddd97ed026fa6f7d2225ab0bb2c17b8b4e69daa73251d25bb0526b59b97855467fead16b8cccc9723699294b4cd02af31fa885930192fb558e073e8eb9ca87fbafd15b8ac29079dc5ad2e7b2b30229e43b036e9fdf12af0df1dec6490ac224a546033849482f9a46c6a4a3d8d9b72ab921478d39bb3461b09ea465160db4fd53ed8a649395060b37c54f0c8392ba69fc089144aec4bc809ecba68f8abfdff649bc09d90e857f1e4df4799c0a88adfbde5934a2f4be0fc42e5de88d257224a606cd773b0d34147962481cb4f2aa2aa4dc5844860f4a5dff020fcb67447e0f45a963693a4533d3502e7172632fb7cb3e6b4084cd0542d2aaffba54d89f07b4abca0623f0436598aa2eb34a488bb10b8917ac1f4aa53043d089c85a85dc27320b0eb5b17cd6bb3b9e807ecaaa94b0cdeff29f201df22a3c9c8d9267fa807bc5e92d57f5726f38707ac8a4e9a4209ff08393be0f488a42dd557c8bf75c004616aa7195295da39e0324b1635ca2cb969098003d6c6a4e8e9ec66eb12e0066c90b4b6b6173b882c016cc056b0983ea5a8854509500376cc2f5945ca909f24000df824c382e6537b92ca59709ddfa53b424a7e1759b076a6b641c60c522bb160537373b5a5dab44ac28271ef60b28469935992af602bfa75bafd75c82d5dc19767325532877c3995ade064a41cfa3cd5534a252bd82832e56e92af828ff77f27732d784852056726fdad3e948e3f4a05639bf756648aa71ea182cde5162258c58b203a059f47f209a5644a326f53b057693de7c71cdee952b041e5181e942865272505ab19644753fb9c951c05ab9e7b629be40e188028f813e969292d9a90985070f9b369b3112aba33a0605df3080de9ad82b27c820d9ea2acbcc4ed2a9ee044e3998a1576824ba52767df585fae71823b4def29a538b6fb26386df17b4f838d95ca6882179d420a3287ce142d93894e49ddf7cd31c18810fd4bfee8a5892ec14634f330f75882d5887942eeb7128c58048fda796b2f4b09d64b42889663b77b26c1f6ada5e7ae24f5488251da368904275cafa4e45ff59c16487035bad56b928f607f37a7183db9a4be388213b2bbc472d48c498de0f2ef5a2df3525c0923389d2ac4f4b499b4bd4570a579d3798cd1f46e8ae0bf2fc6cfd024a94f04134f5d4cc92cf305218249e199cba2e6113987e0437f4fcadbcf905531047f52624816c5358d5e08c69274fdcf55999a44083e95a70f593364af67105ce6da82e0b296ce9cd492403021e8da8b360920d820f9435dfa482195e40fac961a651994702d11e2076e74aa3269e5153b47fac08a12cacf54be897d113e70faea6a748708d1f13d70da293394e4e49a263d70d9572b4d06397ea53cf0ba397a7b42dea04a78e0255be5a6e5fd7fe80e4c7c5f4d13f3b8396407fef692a759882d1eaa03ab55c2237be8c046fdfb1c648890b4670e5cfaece6e37127e7550e5c921d446853d58b6b1cb8c929489cb89722513870912708cd19a2e4ade01bf894e9bfb265ee0626a8a50b12b462d0126c03972a752911dcad830ed9c0af7f3c0f31e5f59ce11ab8ec987b29c343c8ba1a38fdb739bf47358ba932810fd020c100d2c09659ba9037d9b77fd0c0a88c1c84ec4dc163e70c6cbacba944e47676d70c4cfcfc94acc192d4b60c5cbccdf849cc92a62819d8bdec185449ddbb730c6c4a691e1273c8184d31704a2b881034ab4ac78481ddea949426ffd5b380810b1e3d8bc48e6842f805be4b822a617b810d6d3a5a43e78917ec029bd449cd1e6348ae8f0b6cc564a62fb40546a50b6943c94f0b40184016d858395c6428a1165387055eaf45555cd7d45777052697c7ee4f1983bb6f0546a89b8a14325ee8be0a5c341d4beaca2df26e546034d9d88754973aee53e0b54308b9a69b774752e03c6ccbe2e6f5e09e28f0d1ca74a6fa483a7aa0603372004fe0b3ed56d079438a41ea047e937abcba4e96f45e9ac04f2e254547caa55e6502e7d9efd746dbac93b6047e4350358f9272168b2981c9196a2f51f34a12b124dc5929a8a44c6848602c990613f61ad2373b029fafd6f92a42cc296404f63b6e124ae494497e8ac05f87b2e021a6a63d4418c0106c86090620044647b030192369d6741058fb1e151d2d6e9206022f4a74fc983b6e08fb078c48e2f53d9a6f93d6075cfe911ab3d268d5b607bc9a0c5db595079caa0afe41f6968cef809131bcdd2c7e854cea80ed986e25541c5d269303de2f8ffe18623a6d161cb039fde37a5ad0a32637e0f7b73f834a9d43fa6cc0c9f7cd394ac6b425ab01b79b7a5574a6d8e901d08011ebb4ff2131df320b269d5abf0c9a6a4177a11690210b26554822a5309142751f0bb6ededaa72c5b0711d169cdf7755f06841a8ca79057ba2927e4ac2e30a3e7de6f2fe2b916de956b063aa6962bcb4b7cab082f532939e2d5d7bcaaf82932cc1335a44ffdb8a2a028daea8b4b065e2481c0e0643c150300c86e193d305f31308001838268e4582c188a0c9c27c140005462c344e3c2c121c2222101a87832261181c08048461302818080302c140201c0c0fcb0a3d0f70ba8f5f491016d864f976f89f9f6cc6117429941c168278c0b9b07d0815e404de0c45821520e670e1e5139aceb8b6510afd64fcfb39e4ce9fbb2013ec291e18f7c24987cb2b154ec964de2daae97b54239ed0ad7351dc81fbde55ba856e92bb72ffbb4b17c79a275646910142c82f341eaa41b97b68e27ac682828f1c8ed83a29f60620933f7c98077ff8c5c23660953125fc191e84fbe020fecf5a5be93f0b2486d02df86a78cc77b6b8e483bdd94b2bd69876c18ea9c3e9b8bfba31ecae906f3034c80c7a060782f402fb1d7c662be5a09df06fa80eac0bcd3aae9906d46562c1d45d0c3f2365681b6c0181b3efa37c48116e0115b0e65eb8022fc6863fe4a0d763f8b5de50f777e8caca8096f7719c8de7cc137b751e9237f21e3cb55f4d48255409658606a28d3c6a5412880d5143821c3e4bbafbc486aec342c3dd306a78126e0943c014bfdf2fd9ffbc768945ee80761076b837d7b9864e17dc05fc42e2a17a082bb419ea09417942cfc88b78ac3db41025e402cd790bef83b7f570dd4f45ca0b35c1c20e81b9503d647a987a15f4198c099742e804a3b7063ce5b68c772b397239073d8303417a81d3ce2f0b9484a6c08f50cbb5f9557c4a2e5edb70cee08a250e1e1d374c4933347bc6402890220472ac522af9fdd060527b71f1474ca16e3143847d0023a34a28333400f989c245b4c8f72f13320909325cc1b101a820efbc43c2d5a4655adf62fdfc53d0f5b676af1c79cfd00bfe584299a141e82fa41eaa87cc1aeccf08436b9da1e03067d8273c02a7287f9a631cf4bf3f725e3cf5cfc5a076eaac93f035292c16b610ca441f2f119c84a661ab00cc102774121209354308a15dc876c2bf01dc48c5f7512f285ee09a81af08882f2eb120a570cf3f5ac4e9a94703e96d4c5ff2c32cec270550797e4294904b8893a0065426c1af2b1c83ec1fb8460349ff27e0a12b72a9f95304a5fbc2d961f4a20629415cc068e654e214fdb546c9c891cee1cca69d54c4a07118669813ce09e1de0915941c0ce00fc382eba0d3a06cb01a44014e82766a5c03693bfcd7112c0272d12f62eea86ff4ba7b6fbec6b15e309a31356a6562f1ae8badfb56acadcd8aacd5cc747f2f97081490c712fa1c5f8a39ee7d7220591a2265feb4b715eb5f0e2ab1021add3c48f29fa4b4f451ff77bb3c8e8c08701c73b9772e720a97b8b690712a86b69c3875324825807cf72b681be8888ad73b25e053ec152aa72a4f20447dd9e25f5c4f910f5f588d9d391373a491bd0f4807514725378a640fb1cd88b9917cf0329a302a1ba8b535dba013232201cf5a5b87e2974edb1eed80a2aad8f699e8aee1773c94cf5df2063acc00c022b9b900db54fb2fb96be32f8bc82d66c77e572182a8c777095db6e84879f0c1c4b91fe6443a4ad5e4722d66fce13534d8d5ca091de539a65b59985e316c2a8cae81fbcd2ab326f80100310aafd155cb3bdcb99ad4ed9a9cc445b593f849c592be9de4ad3ae91a3ce030e74ad4a5e55c39d6b5faf9597614b79322e58f91828ee657da92d9531ffd93648bbd4a812cbfe527ba142e6b4ed819d968642400a1d9e8150b031e7653e983d2209e94f8ca91f889e33ff6f624ae3989c830eeed45ef2dba9d90422194841513e6a2d06942a515724111cab1d812ff6057560d02cf3f4a90b1ec6bb134bf5bd0732520061c29e3113b1bc40f61607a7a08c1848c4aa153b27c708c8aba0d9dbc9f713e81d7ca9032d8af3396d032ac59ec61a1340280c58d192117ea89176d1b7b498168928e093ce7eba743e3ddbadb95acadfb5ed98da9af6ef7a3db279ce88a6a7e6af10cc06cdd5f2c32456c144ece54c0cc9190028d80926471a24623796b91389ed04f6c6f566d0e16ddcc8959a5fe8e509032ed539b38d83c9c803d6dd24db586f08fa8f2eafdacc79eae9d06860c42e295e0d05bae40b3e46b314daf094539bf80180c0ba573ba1835ce2964cac961a85e3c580c5feda6dbcaa94b5ef2850c357302fc11dcffec3860800fca504e407b72fd923b7cb1da25148ac503fc90d4b3b4e0464ff9ea0dcfd58dce9ee043c6f49b1ee0c1e9239c8c5a547805f3d4504601493944bcf9307907d60182d40802b9a1feb9638a14165650c081d046848e0811270465a18a423908f9115236217d0a9cde4f16ff327c68710f33dcbbd31c94af58edd4686cefffe637aefe4b6a405483c7499cc2e07035820a26473d083cee6b561c953d1a211529e8d3ebe7407ad73fd316328e48918d144de870ac3221ba37b63165834b3125ffeef762e3434340140c7273b808311b62dd835191475fcf0f3aa7c5aef663dbf6b955b3308e298e85e3acea2ec7bd46a2f949d805b6492bfc63818d05a83342614185ae81061a9a299a2851a5aa490088d80cae64deaffe05217fe3b47893f8664bb16c2835e803e8e9d1f3927aa60a5aae012d3c5751a50295019481192141219ca4ae09b6469bcb85f67ade63210000e10b4c56ad48c66aec4a5b1575b37196a23dceccd8811d41571ec328ae81eca57a7dabe1a4f635b5a10905ac8b32611af6ec7c027d574a090559633b95de2ece656234bf422f2f9621e535a6e3940b322791f5f99c4ada6ea2e1ec3e5b4e715a660a646a68faf9cdb92f5b36dd7d602bcbe6cf4390bd87626a1618c7d9751a3f40a926835c23d525905d5d5be3b9ad2c6d8d02256d755f6692f0f710c054064598e0df5d47e94192eda34b7c1603c8732d6deeed3e9efc0ffbaea61e78ecfd22a2214e25ce8150096d90ca5296849ab31629926554339c101dd6f88160be726314416170260887ee0f4834dd18684c104d2fdaaa53e351d64c034a730af7c3d3c8ba64a11d6c121aaab4919fa90994ae1d7443dc4b69dbd7daa9054592532b53c10e36207650e0630c1776d9fde6ed60a97a248e40f4a42ee555341d860e64a3d13955da00c5b9eb23d732a908474ad49d80f2b0d767f813add37042b30ce9dcf8eaac7d7f82f1100cbc123cc06206fd97e78d155decae172ae58a8f6decc3103587d4d0cdbc87722ad7f9129bb66ba0954f4649f4e224c362b9e31fa9f88538d8c39d3051d2b68475da531fc0094ebd60c52ac2e38170526fc4464d64cd387fcbd677f3ad0cf71031c179b0a5a1ca049b86bdfc1b55b8d926340e1e42c7e6d3f9c345f84ea7e75d7f9247f8e116d9099db5cb9d53ad727af78fe0347643149152c5e665b8b402a76cb87ece60107201bcccfea7e5df35d6ae7f075186a3531c667295af6ed3fad1e528325c56f5dab99cbb470d78c502bc7c43a63199b0b8c573ba3a3b6669dd25074bfd4e3e6b6241135cf710f0a496b9d9fa1f89d68504cf65da483262183aa10a9080a7f95c62ab7743b55754495ca2361141f35b27d13b898d4fe498819789574301584fff2be37d434f7dbe30ab11d8f603c4648e2a1c5e36ba86d0252a504ce2408cba2e9338bf0791b6d2c2a9af4da5d051c906d5b2f9e86cfe91abdb7ff281adf8f188dc48c15624d70f908f33d9d7e288797b41b5846f0dafa65b4bc8d81653346ebfa19f3cfec6e3c743c8306255eb6b4addecd55feb25392f55597f20db544e1dfd3260d71cb5d44563c657a365e580bf8df1f9fb3383ff4d1923012272d67427be0d275922c2da74581c7ca846600397a2e58ac1548343cb0b75d07a946ce2ca9f2cd5fcb7d3c642b1bbc4a9890a6e16093d3cc641443b7464584612baf3bc824341458ad8e02dab48a833d4b6bc32822a4c17e03ed5c4b0fc9cbb95968acadf06880ac2c6b14492faefa1769b704c89b202b27f93d3d2c675951c6f32f07d9f96718d55285f25be7973aa2adc73aa46587cd51353b1f17b3f2f5279aa86210b5a9c3b18bd4fc878020f0d2ded14f6011188453b857bff2161ba5a6c73040350dcac56f6a0611917619bd9ad03a4ab53ac8e737b327a2fcb86e38ada87f115058f64b4fcf2798fc10e690bdb636c2adcf0355b28b9b09110c19455d8aa6d7121b28d76797d2cc6ea797dab4b87c376127b7167d51d2dea67d7f288b4d10442a9a817140106ae9e3ad25041c94e40cabd467172548b8e5190b67346aaae4ab45253f2aa68cf3104d3982c2912e086d4681bc55984be9b8d2e578e69a8b2364dc8c56a038cc7d7783b5f67493afa521eaea6092d4b764854f9946100ddf67b775b42cc84245dfd1152893d2d11a3a28c42d52a72fcb1a833654a8a548d5854e725d4c09611b9f77b0356fbf4cd4aabe061134a0247667b231855219b21d394e66a3a8fc3b8d330aa1c60fab3379f1a93d3c2323c2369da8493e746af60547391ad642ca084c4cf179cadf35ea3495c2694ddb220395bfda006f6e31119c7e02d0d66cada4dade822b09ee8601c3a8d0867b09b7a3155749df05fa874d12ce557e3c9f9fb2518c31a1e86686e597e20a72b5a3931883fe62671244b3ebba5f291dbefb72fc9190ccb37016c40dcb635ceb0587a3758a6a3cd6543d5182f1982f0347fc3da6c69bde2b636f27cea1a4fe6d8cca77e0a54e961bb4a01164223862dd4181010e353d3ac76c414c3d2ba574f8a12e4d0da148bc0fbdf07f41f85b2cb699ea24c5ce5e8e9d0ec10cdcefae347e3c4f2dc7e1d36e182f5c3539a68c01c94a2d2616c626236b221ff9280f9840b299b1dbddfce77080029fccecdc0ca27826ecf4a2d2463ba44f33e3ab6069f4420ef133784a51e4c966cc107274824dd62d0caccb80b936b01dc483035a216c6c4a3c88274d5a2179f93c3c06b42c1a44249a90c60de23bc4314f3b316a0b0fa55568f668a8aecf609d501e29e9277aec0df93c809f9638c40d766a7efa69528bc9d140d52a9b453c939f6571c095113098ac0f2001c41968da1529510d14f915c25bb1a416e5de4fc818d5769f94a55746ce3b499e2199309f965543ea3a25cc87d35d8aa2e8528e878f0ab7bf9eb488947eb87551dee5515f45554a98d2edca78186a4127ff35ffe5b4650e75d940c2efa3f9da2572afac1bd884f1a87a6ca08cef4d44a065392a0264612ff45c229b3818c49a86d28f9ed3acc37400a2aa010450f186d58b103029dfc21d456be086c0f4d8aa5c8f75d79209c419c3a92dba7af37604205779f82885c043791c7ab58cfc4e5686fe831e62cc821d84a7a992d3dfd1f5528c0be028cc66a8d13b58b59a959639e428cf79b244e13b0143ca6c568e55000a3eab6220ae32552799f898ba569dafbfca371507e0799f2a873f1b8590dbfe9104d72f481b53e1264eaf7d617691560a823feb120806f0c38bd6f81d6b029e9fb0fcf1be4c6e8cb494f8ee4894ae4de48857a17a64ef09a06160b6a3cfd373f8b0564f6e364625a9f698e29c4770d7e58aca888c1d351af4496ecfb33ce83a385656ab9cfd8250d6171474f91b92a3a9f57312493ee2f895f6d2b591cb849ca9c4ddd2ea73e29dfc7a3cd7e70a9b08a601ca7f4efc11b1ee83965cfd819e3323459c45ee23bfb0914bf361928412b0956a130d3faff8a0a135eb985c1f0c2847f1a0dc1160072b506076f8c347a7381852e0083ae2989217dc6b735946d5e6bc029041e780d9d72b757e506cd4e08f5c7640c65c997ff331ca9a0585f63b76c38715f5dd4f15c82ad2e68e1e450d5ce7ac2df7d175fc65a0158dcd02dff4565d8ac24ceeaf885458782c7cdbe78407d742c099b77d4c3a3405b58223959807db53f9a3e62d9c1ecbe31fad605cfdd4778bddf4a4a8cc8eb5b4bfcd18880d8407bc56ed43367b6e37332ec8b9b293d5abd92aed8ecb341c526874dfb6d3ada44b409caa6924df96d72daa46893cfa61d9b3cec66cc096d72e42e2721e7ccc2ab7d88e336333af546f03374c357f152153c391abc353c518c94055d0fa1c072bc965cefc95e1c6471578def335f1a371d63f62217f7423ffa45e36d678c97c265598ee00701a19636308103c6bfb6bd220e5d7b71bcf61d3e83f1efc7cf9169ba9330c7a6330cfdc88404af186a05be6e90e38524a116786b4585acbd4026c64ccd092acdcad81640469df7fb0c38d690a107e1cf7a305e41fbfda779e4aa5a3cf11d3f0cf208d4a2d4676e2d123be85a9b62dfc6205ee359cc382ab5dde4ed6dc08fd39666181df734747610c207b2d71158fe8e75b64ac8de9528ef107d27ee5bdb83d88d6c328de948c0d92b9949b6c8e4b0ddd6662041da419b333240515dd66718226336a9728fb205414515bacda8e0e8888833465108b42a7da409b4faa1f4da78b611c31ea4d42fcb76736363e651c0ec1a097d47561b581ac3f580378ac39db31f9eb50cc7978e8a95c76dc702a4f83032a80f3721119e1a5c15aaac7379272073e52cc7079aa5638e3b0eb673c46c6031a58323be060cc195b388f0e45b1afc45e0df96f3ea6e2a955d97a47343e62ad193f6391301541db3ba6301b3faa16466351582d9444e695a5d178ce842a6bd6f7a92507c2f9243a3425df218082be8ec18f288384d2e073ecb93a87212bcacc7f4bd470f2ff7a4aa70597f52b4812cdb4ce79298246b2a6ac9b1114db1674b65f44012c6e204b51476eeb1879683190667d6e73a00158102033024595516da4def680cc36dda08f18113e6abf6124ca84915a7a810125d4bd0b78316ce44c95200b0f154699820411dcaf534c49b214ba6a218b991be40ab3b5e383db51972fa382deb818902d46f16ccaa034dbd26601fc97e33b5b536570db41eb00dea66509be6dbf65fddccb36194bf20b4cc371e0a080969f0fd65060bf0e64ffa3928e36ef5fd246cbce35539384836efd3d5ca23c473ebcdae16a36878f3cb06abe378975d565e0390c76bb50065d890dbd386d07a044f6354f9272812b4eca08bd9365b47b1be49de0e3b3886f16cf314641ffc15fc508f46efa923a094f3a8bc375bef412450298879b4d748677208e45769e5c8992e75a1f5642b2aad4d544bf02a72efd5d55e7ddd022f8b0909962a9b16600607cb0edfdbaa4b005b6a2468d51549d2cc3ae5764747baf8fa223eefb5e73fb886bb6b4137f7368b34a78fd16ac34eea55d574c339c0b9b48e37acb522f50dd37859a2573afffdd9664864ad496e8bdba7da1b36bfc3b626c5d212210360872760727ef30da783335fe8b1737c41017bb00ee45401ca59b5cc4eb9e92153326314b4469b3556eab840fd0c21e8ed94afc1585c2b84d973d007ac707ccfa198b0e02a4f1858a10a54a08fe0c67c9d61c280cb6a0b72649f15b6ba258e1b2ca093042dea9c5f46394541633b2ec86a17014d230bc6ecd105b753cbb8cbee3c07b59bb7c800fc0ebaa46f9dc0335ab5c13546de116806b87a2bddac8e24c609c0b6f5f9304197a0db82d6a3a4eb675212685b89d1a64972b656dc9c6b691f30713708628147ecddefc1a18aa860a8adae579c01463f9a362976d069aba9ef7580866639c7481b9ca9933445abfcdc445678839157645b6f9949b0dbedf07447f3b78b9352915dcbdf74ad882ef2c6054bf4d017e90383a1817fe7c9c5bbeb3c7a1976b22874570436d64332500b67a511d6308ccf9f1230493dfd1cd4cd03d3a6cc3ac93e14145b7e368d87ad4fa8b21436071b910215b571658a16647bc7b64e2758ac084821ed9f00122b45dcf4bac0039020299be99987d90443e68be7def628975479732951dcc4ad6b315e19519b3f7eeb329adb56f0af64168e669a8d7ffbf51d49a5e20c3a1abd676e73ea6c0ea9ac04f7cbe3efbc8007f219f69d05deb114d617d0ad815833ba716b12c16467222189bd1102ff13ffc759ead670b5b18868dbdac8a85750fde785e1a23def49794d5621f06f9826b78c9de596cacbffa02fc41a3e0797a160bcac175a710e61ef9e5fa8e3df44590bb2951fe048140cc9afc38a1db64005f3c8bf8006694eb8abaaea883a72ec2b002eb82d5d394c87e29754bd29403913b45c39169d5817400a1f924614cf8573904e5185fb575d2f4ced6d452fb7e8d5086676cb48efac17063a740666f0a2993ae9a816535792355a989eeb3265a1c62dc22de1a4a7648672edff76dac6724b005471ab3f62cb282f46b975eb4097ec61a16b15491a77c17fbd01163f5f219c1df6b953e4ea807edfb3813c1fb4ce69caaebd4693e26089392e6ba5b6ce4c8173220bdb8762ca36d53170f2f1ebc4cea5e71a82a1ae95f4ad34e81d1b01d1484fc24f4e9df436d0c73a204297ec1f716922e77e7d8769c61e94cf51c2ec5da082290d98c83304fb0060bbf7b33abc96cab0ab3117b1bf6b125671e7bf5a6bdcc532752be94c66d0c0090750151f90bdeb24ee63c26f540fe6020cdfc12fe2f9eae2af22a68d70b15ebdc4fe626a6a0de792d8a375e826f45a848181888b855b1b73b213ee52e0843550179e1c0a68c6b44b0ff3c07f309406e75fbe33d904d2c3c940c24281a6a46aced01dff8ff905128a46165688380e0c2d209a4a109b1bda1614a5fe380d4101eaa14780a43602c14358b6567ad16b58007a1a23d433124b0d888c6ca6fb8fb083f403010f3911c06a034c033b82162f797a4f99b7ea3b6a41c00053b4df0b803d0d99132ca704882dd4d20ee014e0de7e708f83e974657f0f6da0f8c8adc6455792fd900e69ecb115f9185c626d200a45851c6ee269deef68858dc0f5309ca795fd5806d6aa533e1d03383b27e1d6b300040f2be852349237d1ac4fe182cd00eb1a5c73155c35ee949e4846f8d95a89c7d556d082427b686b4fa1e5ee01da1a961ea52b7588836b10e6101145db80d327d0584393592bff00c37d0c7b0f6cd9805d1ce70bbdaaaf68c1753477dc7a175ae0a0254471ea334755e8f07104d2733614802d63b2772c7727e22a3d2e369b53f5a8eaff865a68f90844a65f474e0ffed6b9848286a93331f8b57a515b1aac94f4b1c4b75e99a7c5140efdfb2e82d2a4a08adc40b01779ccdedda1a5fe6a4727829a79a5d082742a0771ec88b82813559635d0e758ffe82c80991473f724bc61ab29665c8480e53e4d4bb24ec1da62984b4b7799a3c10a2c9bab4d2be89bd0b971d8af469aacd0673280eab61fef85d8119337f68795f02571822c5606e764a197534e24140636ee8299a2c66b4bc1f019f9aef0a91619cf8258f972182f4f0032c9dbf71834c41905879c655c06dbdc0b4f166f62b38f711c86952943947da12603096f5c51573db48947358515143519087ba37c1c07d6be56312e29414379b8c4e1882f82fc2da19e509e400167febf68daf17409795f045f1b7aa9a09a5c25f7f21e0074921f1fd0d0f7a35dcb91a0e1640aac10c004c131175f4e93fe877897378ee476b417b1d9a682edb8b454f803d681e7df350bf4bd9e082b48ad60e02a032df1586a11a4b4916e19803ac6a0e9b2f2ec24a1e0010f0b3e12d8cc7a97ac360c586bfda0b4487e1ac1f08601acffbc4f291016d0f510368a51eba8b8574ec2184e4eec92c311c486bc1188fce8027434708a46c2c3f21e23c47e5d5a49e9c20448ffe258c98258ca7895d89905580a01ab089161b1942121c3d2d154492e6500c30032b7f1f0734b2f007f97e089e18f657d045c830b8c63b159a62c0e2360a3081bf0668c8ad6004dd9ace0c15601571b14e954c64e65133f07cc87497d7548b2f2f0e484e2e7f795cd067c464686548668f8d81723b8427d8cf85d74ade7e3a1a36cc6d140f73cfba4fa88c2c7df0548762eca7ec398889d69e9440bfd278419e7bf81350c4ccf000f2e83a5e069e2839a6fae4f8faf1f1f24be217e5438456928a1835a9f5d02df7973421512e1c3a6f7712b583ebaf2c02764d7faab2971645037b17731bb2f3d408e4cbe348a730714c7942f01ea9e8187ac4b320e20c24199ed9b549900952562fbd7434579645c1788b96ee5ed4be32339a3135f52b209c90ec0fff9e1e26a908642c74a17b79081190970517153859d2029c148c457c04fe99d3dd1bac1961faf4316f2f30c221a26846cd1b0486802e63df3143f6144555c09349abe3b8521638cc37993c48555ea5f60de1f857bc366d711f17f6890ca5a22fd6ac4f332cbb5ff5ca6a9c1e77c98c06741a34499f44d8c6ae074715a0909283a28637c1f24f5e7381a1293fa8e753d36ed198e9ea70023c3cb8be0e7f7046382b1d38d61b4bbade954f907d3c420066a9cc37c5906bc702693d6f7e9d0666e00cf7d66aaa58d5097c1b63b20e192e11214076b048b7b23c765f3b379a00b0471a5a260aab879057cd630cf0acdaac222a53a9866da61c97841ea67050addf463d95efb1b46b06a21c717624864a23aa2c512c1087530c60549623a53df694afa694df24e9ad93fb6a2b5637a187d9bea30ea90cc0a245215d26bf8cce53497bd00441e1970918924562eaf68b547b0ab63d760205927ba5451acd96d80c51edfc163247c3f0d7fe939c793bcc06601dfa1ddb2274448c3a6279076df8b779753685122d2794a99a2598b14bf2c65b7a1645aaa157a8d002eb3304a59e0412cf8d3f8a08c283ffd9ed82409897e26f5c4828772659ab347e22d7e921a3237b51b16ea134909f332474f7cbb3658a9f57bd4f5e45812aa9767b7683102993db0616192740fab0809566d6fbeec361733b792e475d96b7b26bd2553d603da20f78400fcdb5965fd0505473174ef239cf39407c7fabae2f6662ac2e3ed5f07184584dd223bc8fe908a4ef10182ed367f32da45153e78851d45175cd954598b5263540bc9bc4dd2c14ff169b1d5aab148f0d0777c1c321776f0d5dea17437a111371e9e61bad0aac889c7a93f09693fb519c96b452a7e486fe9184ea4e906a35619ac5fa45ec82e07870f7090cc049ecc624f37516cd9107920635d45641b65b45b4a1ef581807774a49f31104e6971d4c71413156b29b14f39d5fac2180d2e6de38004296f63410232d95ca1c11dc9f5b546423f6e9dd8c6e4bb659b254bcc2482ad9328d1c44384237390af69c2b65413d703c9c9e78d588adc82415b55933a0d42d1dc4e4232039f2d369bb16ab214b2869ef1ff231d7f26ce8456fde1e559af494db7d3df30e3beeca0eee54c180e6c9397894c6afe93330ba25821e23aa9edb29aa939cf10eca3754da50d4c6038edb04adbc05d7638680318f5183cbed609baaf7e3c5bed654fb335ecfd76482a56b98005c10206fd665ab4407a525087a29b3d42e122a45c9e3f743ba6ceedc466a3e86305a39808ca56c04cae69a7a205ee907b72092d4fd889c33e105ed56e485e14bea958ba6f6cecc7cee673cbc84bccddc662c2bdd334752d9d5b1f0ecd19f8e3a01e69f44a27a7ea05e16ca234905d243840303498212a83f9615a984abb1309bc07eb64ca98943818b9de52ecdba481e46d4b64f61d471af7e0c7b0e054be389a8b1b7635c284000085d81684e831b977f0a18532dec98d1883d7561293bb43949408d057815d420fa1462d3b16f417dbd0d94474016c3187d406a14dd9cf2700f21ce0c4dc7d44e8ba2d66c911484b059e29392c845c795f5e319ae10bd6fe2815d6f15787f08118fd6047ce993441f85648e3e44fb802848429e33eb08096d46aaab8807f48ef8e3f79d602445b4c5816b9942fd7075b7336709e2232cda8415e9e224dd891441bf4849f9678362744983b0e2348669bda4206986721d6649afb7817dcc1f6a50f7a411a2241984425887dcc03b83ae5f7ee320f4844dbab088553f4058b2771226037ce7faf4073f0f6800ef4a8d69f73256cb707b152bba4e470d57ce9c142d9f288fecb1e41418f2ddcd5e9e5e4d3bab4a23833d5086bcb4d97e6b549e465cf603643e3a523b48e83b7ea2f3a2d1fa015f2779fe271c6e7efd34ff100048dd842571ab924add6256e142d5ff9f192349151ad215129946e8ac62263165f27cb58d0fce9771b92fac71305d48e56a627c91dd530e6dd2d8dcd4c2a8ce0ffdb1038e75d33e7cfaf85ee3fe31e21ccd5789d53c9c9c8ac0e58456e81ef08203088b04656a2f6c1772158a21602fb5ea06f5b9a85e3d033d9e616343ce928487d5fbb87aacebc0323bcac3b41faadb610aec92a3777686a4f7e9b72d358e7c388055975afdf31f59935d8f17d9019d41cd52a4da3755bcd07dee3829ad370808726e96a7689b34f59f2b93b0331bb068990770b5b7a74abcf79241788ae04de0a3b1225763ceb094535841f5e22a9f26efe845601e7f0fedf8c0fc8c4dcaba9c98a06b8369132df4254de30e5e641ba45ea43acf7c410001e92a8dd1017c4ce3bc09fc4f7ac11769b14e0a86626c7106ec6c498ea7bf3e975eb3d090bea00ea65dd7c91808b0af15221fe364a398b81bc02c4fa6c5976088717eb70603163870be2e2010359a1b18472194128c47782a80255c8b1eb3486a8122aa1a2c80a870b1d24842ed7367245a1177a0a670dc2e9195354056fce67b608ac6dcd930e007c873bd9a3b950b9cb0b634b7fd8ebf2027fbb1e8eece5b82580edc5fe1fb5be43fad4d1c4c235f6b8376124423c2b2ada5cbc80b0a23a3f571d6e9f1dc132c3a6f0c5bf03c5be074ca18c266c9ebcc9da170bbcdbeab38ae09d061c7060f1886d5c6fd292ab2ff6b0724683b17211393e8832ba08d6544a4094ddabffd05cae4206813246f5c6c20d134680dd3a46285810fde053033d8eec23708a709b17218338e15af105d5dc938c2d4c52bcdd903815630bd56ab09e79a1c0b437343d4dab09e282a56f8ca297a8fb409e3eb22c561ca4b9a30e5aa490f90811c5b2718c290d9b4f040df8a3d01e70d4d8a0581103cb713db0d670efbe41ae8dacb2408d2d1987938651a2c2e850f523a642df26a789abe881ae21d07fb17059c355cb0a945840866309e783c5b7590829ebb1363f7d38d0cee8f05c27e102e268d47c7b52f737e0a8f942846c1f1abc1a9ef4a099dc2893f1ca01a0458735765c8c3f08970e59a4a33c3f41b870d23d9d772956e00aac8276152022d503d578927b9a4d0618425d3de94bb6ff87601e68630b21ac07eef95c708b0ce07d90a80dfca2ddf3c7213140cc1568a30596d1bb093fc64f85ee6e68ea7292d435e11ee041e13407c11015a6a07386ff668ef2bc26a7af9467f672845016982dbf37d55f368487ec60853ae288ec3037bbde789fd0102a4b25d120210fee66d60370fa2c5cb6b03832094ca8b7c5db58695a0f55a0e8b80dd9cf61ad05abe543af922d094e8f9bbace9a66b18e8185a2717ba8f94cb303dced1e55cd0ce2740a822a44a71fb5625ed980872cd5af1f126f16f7dee4d591b142ecfc77ee26d8f89b7aa5eb9794dc1b98c7e3e473a04ca094ddc8f92abf9147b9dfc9939a32441ddb816c551eff16163beb5ac283990d0eaede71a5b27803c4e21a3fa87c633242ec80af7d21883296180f29751338d08a21072cf812ad73e699ec72b1f5d7e2c62d44ebd65a7540a0da1c514da27e5ac6d3cf13ff5499c161d86876f49659e7c90043b2324730db637430337939a52f5fc28fa2b870066e3b1053fcc6b80dea7b5a179233b810443514a2bf0b5eeb1f533ce1c9c3397cee5b0f5a3cde27d6db953bdf87a7c99d10d165ebe381fe6829f61f2c60890eb7956218a0d0d6922033f802638d5076bb9489a376b6aa8ac67f25c4280922dcad9819583756eb7714b40b86bb8adfc21bc4268a4f8a1cdf12708080b7b98456eea30a55a6ff8bc5097b9e586382c47db412e438de5bfad90266ee2639c3dc2bedb3a94f9a84dc0b8859014486015382105ae040fc2514ec7802fe4044df0a3760b46439330f0f0f0f0f0f0f0f6f706ba4b6d6368024a49424a9adf153f8ee9029a594648a8433d417ce4cdc86362184d1863fda080d02010bf40ac50ac9f499f2b5ab325cc1873eb19c2fda07c868051b529a92123cf3a59820b00219ac6064bd6b472a993a476d6ce001362820011b5bd880c0183256c1a43e4f3166e855c1c61acb9f53da53c19f680d31c8fc7ef9265430f92ce750ff21afa77b0a268e8a96c73bd8b6ada6e07a77f46be99c25eb2d059362481a6aea99de1e29b8ed9027e4cadd28f8205a9265d09e29658b28f88faf7fb6d9d3a67c2818d3f8666a440c146ceee8b952b0de0b1df904bf25748af61bd2a8c83dc1260b59c53f923e25f24ef0f9647a8db69fa4fecd093ed8a866ad64294a3a6d825162c9dfe228799a9334c197902c5a24277bf55226b88a215a44537f25e208136ce4d0f71f73e275e55c82495a21e85e907973722cc1badec678997542094d25d81d913975ca5ea276a504b739a9c99e96d6f58293e034b7492b957eefc59304a373d25ff59e48f06d25421242de9f881448705192529f839074df23189521287d9e2a6d8a992378492a85a44935b6ed8d606c5c536806d5604284119c34d3bd2d79a47fde8be0840a26b2e45c5322a22a824f1354aede4ba52e272611ec560a22654919e97b47049b738a0abefb15aed14370c13268bbff6032a225c3109c14d1993d665536e11582cfcb9f9d2ea7641082dd2cfa73cca064705707c155f610e99a5434b5a80c41302a7b6be39f84a4ab77b1c3e8588055faa2053bee003202c1081193fbf9aa3db72203106cfa9317539ac4913193f107ee4a4726c30f4c12eb517a55374ef4f481534ae8535dcf49883ac4088380a12351061fbe14f5141a5e3a4765ece1f275d3b496255af0764d20430f2729fa2495b6cf6a870e301cf0001c376e84c1011d1798808dcb01cc41461e4e061e4c9b335a1aef0e8c48cbaf1284251d499b63c781c0160c68c08d1ba87164f941861d72edba183a090bfabd066edcd8a1038c3176dcb8b163c78d1b3a5c0c2efc04376e7419c3860d6263d84036c6172d6807e0d0e16274c18518a70b07dcb8f1858b21c6e9e24b11400c32eac06fd22554acab15931ecf72d482430162635879e15e14d70146ea800c3a3041dae44f8d7539863e073ed6c64b6152f3c80fc981f59c444c1b458e4c37f9e313e8a28b311c1007d6439d44cf312c37e370e0369ed2759d296508a63770c2bf4f3cb6ae471cc97003b7f77f9db263ee48a78c36b0c17c63f9a70f2d1dae2081a12090c1067662b59f8c41d449f30332d6c026e9d95c443019fd35db21430d69cdc135a5e995430b87abe0ec28c5011969606c4dfb6e64730c32d0c05d4a2633a325b520e30c8c9af2717dcde61b649881cb9644fd5f1acbc0c9143f5bfecccb5d2237c820039beede4698f24befa731f039a434e919ba83369535c81003e3be9e3f8fba06adc130f07d26ec5e82fbc43791db820c30f0d69fb55792f294542c878c2ff0232baff6f208870c2f30c1d4b485f4503131c8e802bb49a8532143e9a867c220830b5cf4283107a53c684b627761811b374c05376ee028db838c2df0d9f71b3d564add6b818f29f7c40a53da3cd959e0e249cd8c604158d0278b50be279e2bb0fa95449f2e09c9feb50297525636f7f414f42a70f1541236ba1a29c5a9c0aea9905e9ffadd53700a8cf29ca2c4b558c890023f4a5f27939f4d57d089b1838b09686087efe080b9848c28705e22483b9d37275a1e0aac5b9ff6e54cda4dc99fc0e62611c72d74c6de9dc087bc632da2e926b0aa96eb25242526302a3285ab9bcee8c9b40446869434428852c93fa4045652c81bed6d97174349e02b67b4b43e993de206097cbc48e3e621e608fc85749aa2c734b333023978deb6089c58a848a9b359f24e128111a6eec992a8f5dcfd10f820737da759c960260aa1282a276da7c552818c2070f6616363397dcc07029b2b22ed86d2b92ffb03c6f622e5ec5bdd26293ee0c23f72fc1b0f41a67bc0e7f8554a1649a23b3ce04df379695bab8f15db01373af39bdde4a4a242142043074b126f2d99262707bcfb6637355183034e729217e468ac0e326ec06b72b7aab497954d9561032ea5bc9a9625dd0695caa841b6412b92d438914103aec7473b0b4e7eedc8d49bf290052bde954f23668dec522c18bffc94549f62400a1eb0e0a44790f69af335ad690b1bc40236b6b0412a60630b1b840236b6b0412660630b1b4402362e30011b71f078059354c8316b4816cb84af06011eae60bcb5caf2ae294f9e6d05a739ad24d3525975b783218616f560c517e25b5269d15d8f55f02571729694d23278a8825dcf6717d306a5673b20031db871a30b47810e1d607c9123edb871e375a800061ea9e082ce6f2a29e615edcc0e555470ff1949b54474d4a46a233c4ec1abee6866f28e872918ebd4fbf020ed8292799482bf0a0dba5ba204bdfc48c1c90e161a3125798c82cfd5a33e652d3d9aa31ea2e0f562a5fa1c52a964268f507032db574590b955bdf40005a78334117358baefec797c820d795355ec8b1e4ac9c313ec8590d247f1d31325280f101e9d605490da994da8bcb93225ede0c109369552d1941d3d356463f0d804bb492413dbfc1f3bed9a6025d9e59dd8a273867960f0c804eb6bc18205d1a14665c5e081095427a9f157c97e31868e9580c725f88c3e22c326059d7181c723e47d06cb4be5183a2af15bd8bd9f679b128c8d92b9baae3777d4bf1843076ac063127c0e91458a5bbfee3872ec18230cc22848470c0d6c2567815f053c24c1061f69226ccd4830ae39a4a037dc8404a3e2263565ccce27fcfd82c7231889c1435bef7604b71f6a6b6ff75386c9a3119c4efdaefba57a30820beeda9b97443ce0b108268888a87f168c828722d8a4f4c42da12af7c41c143c12c17d9b56e9b859a445cb0311ac046d77cfdf158eca83c721b890f2cba2ffb628bffa0d1e86e0a4d999f87757082e87c6ce924e242f21941e8460d5d3a59844dac9fb111b049ba2c6cf79d46c33d382e094509bc54ac4f2897904824f4127a154f4e7c8db01c1d7a63a9d628e1172c5a6e0f1072e0909aef9727f52e843e0e107ae3704d11b5f1bf5ad471fd8a0db69d48be9b4343df8c04b0ed3bccb252478ec81cd173be8d254e5d0c2d17d881d142430bab00b00103cf4c0468e6629b45f27ab5efce09107fe93c633bb94c9e2c8f0c0597253bfd1be6c7d2167048f3bf0716d2d2d640e4916ca2d78d881cf509d2ce5300d95d781d321655656cb972548b4e04107ce7474bd749df4e50dcd811f75dde65532d6b8eb21073eaf280bb2627c8fe92e81471cb8bc9e665f59d4697239e001073ebf9a8e1435291e6fe06356341942f28d091e6ee05bbd72fe0db24c9989a305270c331978b481cb394951cfbfb1aa325f78183bc6d871e5c10626c9ec5e4a94aa1ceabec0630d8ca757f430b563297335f03149fb8e96df64d27f0b113cd2c0f765fecffb3d61d2c62c78a081c9299a9e2382d23a4967606db227a146d89ec528163cccc0b82921e26e2c9f1cdb32b0c92f467190819139e93791e4e7381a2a24f018039b338410d67b31256d1bf01003fbaa498a5d503a3b84b6048f3070a66c930c1d47b55f0a0cc7943456d25d2bc1e30b6cdde8cde9b1cb74ee385aa0430cbd828717380f6a7dbfcad3877ebbc085d7a9884c4a7bd0e102a3694925e854b7c0f9980e52e2297f53d10a1e5ac0721e0d2ae446aae091053e6fa8648d1d1ad444565df0c08297aa479859fc0abc84a851cb46895edb0a7c90dfde2e32fea9a90adc066d7df24ab8fa54e03e43f57966bacb4e81f7493172aacc4ba18ba5722995321f055e3325896aa5172bf350e04db3f376d929a1db7f02a73a7947c545855cdf09ac5eea076dcda263f49bc004cf5144aaccd39f3e133855f12c4b0aa1f2cc5f0223dcf472c9cddf48be12b8142237affe78d6f093c0968e65bdc89b62d23d1238b9e329c935f99ef547603f256bf650933ea57e47a5c949d34a70e71d84ca9643095633b86bc8ec24f88c9a63e79f940497512d491db7d4798491e083ccd9d245de8ed53e0d4864aade848aa351748f821d670336091a8f60848c5b413c54e7c71dc195345de7666a04bbaee7495e75868a0a23f8d0ab20624417c1e7a9ba20661d73f614c1e54d0db6b9f636b49208b6d2e9688f31b6f588d84c6434134a7608c6538a489fc97208779b8b2d0c0d43f0d9b549ff540aca275508464e4a8e11062118cf214cc5d1bdafef0f824fbdfb510b7241b01e96dd3546dc1f650a04772a846c49f99eb6090204ebc14699aa49273de9fc81f3bd1391ef3c7ee07774e3d5fb875cbbe9032f31644a9bc6bccc357ce0fd5753cb93660f5cec8951ebdf54e8bb7ae0aba2a8542a836ef6681e8cdc12cfd7e28907ce92103d31787ee91eefc0c6ad1c4c5abed8818b39cc3e3de705cfd78157cf9f8489c6b4392b3af04175ca7819a4258d990397530a49454c264a47090b68c8819598c13e84150736554a4ab27a8d5b88c3814daf9cfbe1f71bb84b4ae78edcddc089decda0cbbaf74edd0636a6ca2fbe9577da64d8c075ea5222d89fda5e740dacc8f6d49b37a806de7f84083958c839669706ae2496d03946fe247434f09b4262d299fb5eac7206ae84f6cf6f1e4a249399810f22e554d9eb32b06e19926d940e19f8902c54ee8da977947a0cac78796bce9253a5b41818bb3ae175a623e78ce068810e31c2c0ae47fbcd29497eb01218b8cd67a9fd93f70556f32af57684c5909917d83ae59d25a9c9415845031a5d602ba5a6cf6a4ae988991d6870818f9c45e8df36dd10475e70b181a5b105ce6dd369519a94e8a4a3052ee5e7fdd5dc1d92a259603746caa0eea7e244110b9c5b6acfea96bf026312526aaa7117216334acc048099ef267a5142f48a30a5ca5bfdbafbdf5b594a5025fb16c83690795ad73f431ba382ac017d098c2e291228aa8a454d19002b71f4443d787a8161b1b78c016c50b34a2c0a9260bd1b41e9e46690c1b1798800d30d08002175fad339b526a39b883c613381135bff42e7a8aa71750aa8481861358df4f0bdaf55354d57194c181461318b5cde7fe1a732c956101183ac240030d2670da83c48c6e4a62eab30c3496c029ef13eaa9367b4990d15002a7dff25f3bd2e4895d0e2d1c6178914a79b145175f58804612b8d4eb4a492b8a042e289534ea0633a55febe31928a510a071043676964ef622358268530a348cc0adc44b7a22e6b459544da051044e5409cf215b9e38a944042ec35a74d774567c1110680c81afca49bf52c4abcc7aa9020d217039ba74090b324fe62a08ec290926c1454408518106109890e495e89646aaa0fe80afd69c534b8714ffb328d0f001574aa594f4ea95e9b422a0d103ae57af92a6d2ec3b960cd0e001db63a3b761c98249d61c81c60e78136197ee62e80fa937020d1d70e2ff3175ac0b3adf99432b0c2ec45893018d1c70390691d73dd3f512061762e0d811061762744103078cf63bcdba1464a040e3068c77648d314516c976db804f4b216b9b29dfb7c91368d480dfb6d23ebf4d3d7dd1a0011fa459fc6c153263169cdd9bb6dadf376139b2e0a4de8e48f2cf34cc8805ffaa9236d7f2657fdb033360c1495131534fa65598f18a728b4dcad2d9aec00c57b063dfa52b2c58cc21cb21cc68059be49769d0bfe725439615ac66b4ccd851447432550366ac824f5971efb23e6dda76862ab8a483074bd974275d71093352c1be86fa4dffb9fd928f0a46435747e750b9b5834ec16d52da1d4d57bcccd514769e1434799fc508334ac189ec7699b71bc2724a0ade54667c932034e576678c82db6891fed385cc1005a73972758a9332a599a8302314dcc8bc4d2a5eefa5e08282179dd926a9ef6b62ceccf80497530eb641095d5a3ff7049784befc38d2e4a77d3ac15f5bf747b335c78e0381d3199ce0b46565fe379dbced83a3ca143163137c648dfc9eb7ffda6a08d8d8c2c603b6b0c136b6b0d1802d34c1675226f72f7dbe3023135cda10845ee650113bba0516666082b56c11746307675c820b41648af9f653841cb28019966062daaeafa0aeda2cb4b1c5186654824f7192501339c434da86125ccaf190439b9981044617ae6312dc067da6a794d6955f39b4ba0b303e083324c196de0d495e3a1b21ea48b0935da2e64c419060b2281d31974ee2a6848f6025fe577a9852e121770413728aa5f8e9d35a4b663482d31bd916538f8e3083117c74cdd74e593daa25bdf02202691b8043cc58045bd1ef2aa9529e79438ae03557f2aec816edb42711fc5a0ed9d6e2410463216ed01f2166d6c839041f84c4cc546117ed5a43703e7a3a53e45821b8d0e3993cab4fd54d08ae8450bde412f39f84e81003479587d1820d2462c62098a4b1d2d5f34316e90b82abd49bd9ed8329b33b10dcdf6e0ebab29724150182fd78edede611f278f40f8c69ab3f75b671d32a40c1e62366f881f5ebeb11d2ea2c5abe0ffc55c7cafece5515743eb0669182f0f4901df2b3072ee5a818214e1296ffd3039b4c3f72929a5ef3a57268a9000538fe781738ae3a2001731580e185eb08402066e481d39a1154d2a022a6d5f1c05d06559363d21a9edf81ffdb20313ceae474db0e7c2cf1d7b2cb1dbda375e03705b5187445f5e9920e6cbabe9873fbd69412e7c0279945e65dcf4cc927a6fd8b15580000aa98210776426e491294478ec98f0327b2e385507582039bd386beab3db7d1ef1b1855c942afc8f7b6f204ed08c60c37f0b59752c5bcb95deb6f0393763d5f48b55945c6b0819359b2fea8fd2079b26b60b744e8babaae8d9b570372848c41e8e86f1a1899befdf2539e986488063ef6e556aefc9693b2cfc08714da64c87631490d320397829222476b4f2caa6560944ad31084480c79f46460cdabb35e8292a5427e0c6cfb65a618835c0c8c25193a657dc8b1b31e0636a7dad0621a4212afc1c09eeddda8d2a72ff079eaa3a36e4a2add78814d7709a61642aebaa60bbc78784efda6e102a37394741fbf32627e0b6c0c4942c98c76fad1d50223536df69f8e27c966814fea937ecd251618cd88a9be976f17b91c5a27f8828b1d3ac648ae03479da0991414665c81314f2999e5106b7df3de0596cfb002a736a7ca88a12be860e238c1175ca0209d40877b5186821955e0728e223d847ecf2552ba382ec60e1c28e8620c946563030f6080316f30beb89f41056e83dc8865a332ef1de98b8303477f7170b0808baff2850e0bfcf109883106056edcd0e128d071bc281670f10503c0831953e04678091169d3bfa2648614b8b4416dc40b966d6b93436bfbc60d0e9828b0af395993ccd2983a0a057e948aa62a3fbf3c98f1044e099154b9b27437955608339cc0bbea086d79a46e507130a3095c08491e6b27fead884ce0a3ed77e7cededb9212011da7033798b1042645ae98aa4c6707515202e79f747d103a1dcc48023b69b494bedc9954f410099c52724379d01dcaab37338ec086c474ee1743974660527a8a418847af084c5caffe4a3146df9815e30b2e3870e346210263fd357a3b9dffc5b0cc60c61038916b4cc4d19f35e9940b6608818b7d2e4a853ecf82c0a6aafa87851ac78e436ac7026a3003086c28c949691fd359321983193fe03e65f41cfc7447440f0b66f880519372fabc37cd49e66c1166f4804fca247aba8a179ec61d3ce0c7bba36fe48ad88e2f8e0e318a0161c60e184f6a21aaae9f04e1660e66984eaa246da9949e03be72c774e1a9ce269970c097dd56ff58325def8a0d66dc80112965b1a0aeed554b1b705a7931e36a8ec9d3ad01e76a75f913626d0ce50c1ab09ade5a33a485560b0319b3b0c3b36f5398c9dac5f8e2a4d3012bb2d08245b5bb78569b2423167cde18740e1a3b5870a79dbf6486e0173b5d84e185eb0b64bc828fb12f0184e025c5531a565223bac6c60626604307c1ba9f106db59932725210e64caa83d0fb2924ff77081f81e053df5396a067396777820f40f0f9bde6e33105b533b1e1e30f9c773495f75250093efcc0c7902fa5595bd9c79429193efac0a7d7575aabe8aea0c307c6fddc4a3d85e41e58d3b4906741add6d71f7ae0cc3a06f724624a4247ff9107c6266ba747dcc9a22d1f78604c44fecf154d738595430b8cff820ba2838f3b70aaeee65e5ffa473d36b610400e3eecf0f9a638d16dc4b31c5a2b383a70b8bfd58193c147a452f1e4093dd1814d2e6abb746ce7c0244d49f7a27b7268e506bc0b14e8700a9c073ee4c0a8512f21670b922cf42ffc04a6031f71e084ce6bff4da1b36f0a072e2328ab0a96477aec38f0f1066efbdbdb424e11512c6ee0bddb46787814d16fdcb831838f36f0e6c9d2fac95117526e470e1d384e7050290a6480021420a6c30b2f8a083ed8c0e68a14a93dfff4d28f3570b5d9be59faa5e5fb871ab8d6f02c916b64d221fa9106bef7bf9392b42f42d51f6860474566919474d37bf6e30cbc89a6cc6a29a6f4261f666063652d4d32bd9652df4719d8a4845f977a503a499e434b4df041065efbcc1c03dfab66427e4e36494d7268a1e08ba303c78718d84b217ed9b985d8df8ae1230c6c6b2e4fc1c3b4cfd361f80003b7a75f728ed61633828f2f90f369ce21626e8ba60221c0d11f5ee02bac56c453e66f497268e1c8808d2dc84717b8d2bd110b1d65ee1d2e702a9ab698d47e47af6c0c1f5be0a2a5b8a9152adfb82106185decd80f2df07ea3465e8be6230b6ca51c44b4ba5ae4a4fdc0022337d3b78eaedaef908f2bf0315f658ba57b52d2b815f809b2cb3dfbf85105c63ce43abbbdd2967e50810b5a2c988f58f89e3ea6c075468f9e3a434a7c488193626a9d7a2df2c75013cf131f51f880427e31eea952761727c761e0e309fcf9dba64ebaf7e39999171f4e602d87aadcdf4a11d9a2253e9ac0240b21656b089120a41f4ce04784452dd31c423c1502bb858f3bc894d2561c9d600ce48de304607461f0001f4ae0aea35958974aba52b4c24712dc16d3bb112a6e60023992188701376e4ce103094cd011a39a7dae12253f026b2e4994c80db5592123f09573cab896828e6b8ac0a9acf02836e61584ba880f22f06d2a78d6d05be1ba6b213e86c08798be1f2a3c4d16c78381c330c013172ad0e11a2895237d7174a0cc0d7c08c13d7d6abef7dba1a4021d636c91868f20703ab3b957a94060af445bd03b293d7d82838f1f305a216d52952e59d5ee0336a88a646eb71d64090350838f1ef0f93de7dcc9d6d5d7c4032604f53cff705bf1b1032e8390a20e387d4164c93622fb8765630b1b5bd8e8557ce4809718ed3bb73c53c50f1c70c2f4a4511d947e52495ff8b8019354445f93b89fab83884de0c306ac4d7a2d21847077cd7ed480fd125943ca8bf9e18306dce8186a84ccd8c13212851ab3e0a2272931c82c416551d240b1430d59702986d6abfcf70c41e9c51874a8110b4e459219a9d75cf72758a8010b6e75344c52a6e41eabc62b38e1f527b45e7ccb9e0b430d5770414da9c896bf2b06b7157c6f1659fa9e55f28face0e257084144ea5bf09c55b03569432c846de88b2ab8eca0294216eba81b4a051b64c648769a6dfc34d77109811aa8e0f5b44e8ac1cff2f9478c8370eca9710a7645c6cd8ba233342e7c8c1494aa610a3e7752dee57c8b399dc610354ac1e6502a8927350bcac73cd42005df159a6388aea87aba35d41805efaeea2198f47cf92d68a8210a26ed56a5e5e46d691dd60805e3aaa6f9d53e3540c166f178d27cdcdda2c62798bce973507ad74ba9c7136c9e7cd283dcd4e804e71233aa44bfd0a12ca8c109fe6d3c27fdfb20121cc46a6c828de6f76d3aed4454870b1d61580d4db07dffb9419b094e78d69ea9742931b939b42a063530c17a301df3c70fea4daf4bb021d8764848a633420d4b70aaae9e63142537422cb750a312bca8f98ba81af364419460ac430ea62fae5aa831092ea4cb9433955fccadd790047b4ae8f04832640bc2ab1109c673ed8b9ca06cd4cf1c5acb831a90e0afbb228f0e41dd9258e311bcd9b8aa988b0993780d47f0d94386beccd22c4ad70836dc4f07651d32a10623b8fc20ef42d59563c721e6801a8be03ae7c5494a868fba48166a2882cdcf5a9b58969e16fc070347f72916811a89602c2421d2d4acaf236e705003115cd22023c70c22631138e3a2c621f8ce7b4994d68a212b65085e23778f2cddd933a61405d42884f9cd27082183493381d15b83108cdc284ae4f4bb210779d526a0c62018c929df7e754a923e6b420d41f029c2bef2b90969a5d608049b3428a549281b108c5ac664b5ead6f803dfa731dbe990fbc1ec5e55bb27cb1a7de0b49bee7ade4e1042abc10746b48894d56a4165f76aec81b37613ab7f6b7d8d1c126ae881ad4a6f13cfa6b684d40535f2c0a778a523a2f7d608ef0635f0c0ad69932631ff8d3af91dd8eecc3156d96d07264b0eddcfd014a1461dd8607282bc92acf59dcba1656450830e7cd6c5a45d42e4dc9e9f0397eb94ac499e9d9bcb814b9b344aee7890691407b6ec4dd7f78dfa2c7138b051d34949195266c4dfc095e63d91e2686d44ddc06778528d1ce936f8b731d69a8f121bd86a1d393a5f7bbd92d6c049c8912bc4a44d7c4b6ae0e32569a93508a1cb4a6960359d8bf6efe7cb104203b7eb163347d252223f67602c47d2bfdb1c3330e66e5b1dd275828897812b2522932599f44b78c8c0776513ca3f6dfc0d9931b0daf9b2978f0eb14e2306565f5d6f57fc523313064e2591d7bf3ed7791c0c9ccabbe165ba52b4e80b4cfc1b61315a1ce99ff50297d466536eb92ef0a7dff6c13ec7acd4728193d183f9a515a13f64b7c086eda5c62879d2a6b5c0698aa2524fa259e0b76b6d62c8799983140b6c348f9f4765afc07af6fc31c7d3a52cb4029fd3f44893a0a24d50abc004212ca7ec954ac94da9c0bec88d39a81016c934054e66bc1249e74b152229f0e51a4183c8a54382320aac9b8514445fb9a832a1c06da860aa2c9d89d69fc09932adaba43b81df51ba62cce1e9d3a8097c85901acd6702a3a642be3caafde359021b2fc6603906d9b7a612f811b58ff9fa25940476336af85eccff202430a24c9608d9edb45a47606d2f7e0eeaa1e349d008dca60da57ff222b0317f478e9913811f573f9554fa29350e813555710f293c21307a27679f9c267b8c5d1058d5ca9072b4030267ef218b12964564d00fd898e29d2afb33a7d0075c6ecf65b2ca2fa3567ac0e9602a7fe2a4f080c9a93b9a49ba3c95db0ef818546dd7064df144a603c6bec2444595d4ebb11cb095cb4a72f6ce66711c7041cf7c736faddda56bdc805327b2a434211bf09fa7314f87906fda1ab0935b47ecd574f4b4060d38f513aa2f79c5b59c053b6a21efddc9a8db952c180f12e26de6c7b87e2c1811d2858a9be9939081052f31497c2f9d4235f90a36bbb3baafe94a52bd2bd8a0932e765d48a14c6b059393ca9a83b5ac60fd4cbd0a3e4b08376d49a7cca2a30a4ec5dc935372489b924d055b7d49988e39890a36861092734eedac693c051ba92208d19ef89f33145c0b6898820fd26209095ae3dbc74bc1fee668427207d34c2229f8d2f9d1cc64879c9a8d8253dd88792354be4314fc6810c9724d3779c943c18b44fd4e32dea0e0a2c5db20ba235d2bfd092e7708393c8614c26ae409763be4ce58163a69e67482ab144bef7258999887138c6a93d9e4ae4896acd9049b2fa6ee3e32a5c78c26f85839ebb3c61e69419960a4efe51482f4d2d7c18449249172df622ec1c9143cb3a6a5aaac5a821149a925510a82e07d726b5ef2afa0f94070266408f393f963ed80e02f89a0a1f4d286eefc819164eba15ba134c8f88109f69e3a338d7eae7de0542da7de562b9129c6074eb4a63d95bb228910db03233fc7de4fdaf4938e1eb84c5a73c84fefa0993cb02ad613634af1c069c72da59eb9acbf3bb0f97c82767fc60eec06491b5ca3d8491201831675e03a8a75e79c2157254d07c63206a5936c9115f4670e8ca7ca206388460eecd686cc263c64d17be3c0dfa8e6e63af9164385031b24e80f2a47c9667dbe81b549b9574fd44c65ee88e106f3fb6fe7f70b2e8e18ae23dbc06ae44c3ea2d3a7feb46003934efb95fefabf3f79166bb1063e8b07f3ed77b38fab811321e58a1213d1917e1a98e42243772cb9a7bfa381b7092149db889a75e93370417bd0d4694f3b7333b041a8bc1a2f8bd44b3965e064875b2acd8999364306fe4ec99222bdbbbcdb31f0b931ad7c6442a08518388f563fc294302d790d037b22af629b0c4174a7e4d04a2dc0905a7c818d18a225a8e4926397fa85165ec01468d1852b560c92cb2aa5985482a50517d2a5ca936184716e145a6c814dba6b9fbe628f086e0e2d1c8866630313b0b1636c60021cb05068a185d3220ba605160a2981165760b466cfdaeaea6b8bb50019066861054ed6d85dee5e5125ea2ab0de952e89f85a323df4002da8c0569508caebf2efd42b052da6c0ebfb69a5dfd96b74a5c008b541837e8f16b4a951e0b3e429b5dd5947d4f5042da0c0572af51aba792d9ec009a54330a1326f94667368556d2079181ab891650c5a3881b1caeda7164f4de49bc07a0c399e3909372162029bdd2445123a889ae812b8512926124f25707d2bb12cc34f021b39078bb1d4048fda2381efcf9e11d7fa2370595a7a1f9407f5593702ffaf49e974bf6b99ee2270359aca6dbcb75a6b22f0515f84a5ec8b7eff10f8d29f6ba38a84c0c6b21d592f9a93dc04812b619bd12a757e3281c09d4c3a7b24d30f98a0397ff04a412b7dc9077c8e7977cf54520fb8f748f5fd67a1829e079c1e57b318f1be436807fc4911ef5c121d305a2cde955273c06e0a1952c896d702075cf95edc4b934ce710a4c50df8ab7c93b694bc95dcd1c2065cfef71ffd8a17f276b4a80193269824cd7ac9bb375ad0800bc1268594de654a6d66c107ab7849c8989105dfd1ba946c9360e9df587097b935686adefebdb0604f8b8b77d03b4f7b5fc1267da3b39ff0cb5b5a57b01a74db238af58ad0b682cda3fa94d0e21f27baac60b3630e65c164ac28d255b0e1a643f2dc54f1185505576ba7b3aa8d7e8d9a0a36e29dae1ed724ed525470273db55fe490ad969e82b118628c94276a0a2ead56cfbb735ff452701e528a112dc7cbf9430a2e4dd4d39eb37b8898a3e02a2fe535756ab52b290a2ea58f299d122ae2d6a1e0ae4774c49c82f60c0205234a63f013fc089946a8969e94a3e8095682870ca242ec049b3f2775a2e3a7d61c7282cb7173b668fa4e63ea36c1778908955384741e2f9a50d2c84efe37964cf06b9d930c15b1eaf498e0db43ec4b37e61fb376093e76c8912442fccaad5982d1c8ba5582df929a9ac3cc7d45a3042742c42cd974c658a29a04a34aa62043ed2da8b52451b220e3a75e5224b8d6984bcb74a9535a48f059e47be8603e828f29b2950cd13cfae8087e35457d75e27b758de0933c53afdd63ed298c60a4879c91b2bc736364116cac521a74288b7944882238cf9932af3289cade26825395af6a3bc9a415728a08ce377656804330c9e42695f431c5c43704972ea69cbadb7527e71482136b112253660eb23d842863b0b89a9b6206c146755d13a1a729a95410fce994d74d9d8e869d81e08465aee6d1699f441010ac59778cf92b850afdfec07936d7bffb2a15acf30393d44573d3362de84b1f3899f4f5f8ed5e755af8c0b5bad5e40bd97e54ca1e38bfd431b5edb607ade881310bd3e6eb77af21250f7cda969cf8e9747a53e181abde9cce844a4d975377e06ceb742b857765b4ecc09a4850d1939cd4b2aa0337d1dd4a3bdd4a448a0e6cb699aefd909a03a327eb5637269996037feda6e63147f41ac5817135dfcd6295f47be0c009fd953d6955eccbbe814f4a081f0b4ae77817379823c970cbd136f01d5b37828a101014800dbc29fd418479148976af81b111af0a3ae389f4d4c08a0c7da79a9a543594063ef44ee6cd29b134b268e0f55465ce9ef9b7ef33b03af2ba479decae1833b0d53157363dd2b72b65e02b68d357593793c4c8c07846091e54ea1bbf740c5c9fea335da23fec1403a7424ffe5c4a448b2ac3c046cd1959ccfe4566c0c068d0567b4104bfc07586e0f182bcc02599428a88f9eb02bf751593d2f47181b3a4fbb2ea2d85e5650b5c65fd20d3a4ef85a4a205bee295d41331bb4c2959e083e5f62fb10d8b55c102232c8ba5ce26bb0227b3d4fdd52651619a15384d1fcbdcb2d9e8d1aac08d4833952fc92a591915d8da167df7a4a64c3d054e7f571231674b0afcb6bd8898c98927ad28f0f9233306f118a3490b0a9c485c09b9d3e94bb39ec0f5c79c325dccf3f5ca09ec579b664b3781b5d12d66723386f299c0a9f79026ba2a33be043ef8a4281a456c728e12b898ff944e891f1e9249e0a3889853c7cb413f8804b637447e5fff09ea1d813b5da739067d79296604764c453ce6a0725e844ca000456037d4c9899e2775d7446093d0bdf5214534290e8191e32124a4f6244d6542e0b373d404d3d0be292e70b818a71e508020a0955a52ca2ac1c3ddd328fdace06b4105000277a2471c42017ec096674ada92bf55aed1d1a0003ee04e64d0a1f35e6efd0d0705e801e7a22663c7f1b8800b0dbca0003c602cc80c936f1253e5ba450e0ab0034e9dc64b29b5c69c357643074cbc8b41df69ddf353b7a00039e02e549b524205c001ff1d430711537c741670e1050e34c60e6501175ee0f03f0628c00db88b1eb3fb4325e8e5779c85c059060a6003ee227e964eeb9492fd05a8011be2e94f5a2359f20d0b4003aead455e8a36d3eb3139b494371815b8fd9805db29e6383abd6e0ed5f9172b00030718626091057cc8828b933d66f4ca3e69f7c1b1e3c610e368a0051fb1b0ac7478081e3432f880051f4b6b8a93ce7292267a059f49a72821c6f6c3157ca5c66c6923ed06f9b582bf2b1d2a241f3f58c1e6a86f4274b86afed22af88f3c42574cf1c775f4a10afefa46a78e54f57ffb2315ecb95d7896d2ec6b3f2a18ddac9b60ca7d349a4e510ea6378a6ade146ca90f1643bacd91d4a8146c5b881ba6695dba37a4e026b5f9dec56e14fc8d9dcedd561105973ce8cce9db534c950a056f6a5f5782bec9a0ef0728182147fe5f0eea4f70797793fc623e3fa1798289924d4bc7ddceecaa13dc7a2475f31141098f7c7082cd223429996983d2b13e36f1a109bca13a3e32513e30c1c5a08386c4bc5832dac5c725b864ba7372fb6b89d1cba159a47c5882bbfa90548a64f5163f8756a9faa8045721b46b497dfba0e91c5aa4b6bcc1f8e21af04109ce638c28d174ee9849e863129cde04914d968e24d88a112346bf10178f5d043e22c1eaa411adb316f7ff3e20c195da7595eca08f47f0df1b838cf963b6baa00f47b031e45831a51311a34a1bc1bafb687dbc5c9bc6c2850f46b0934c5dbca4758c41868be02ce4adb4bd493fdfae08fe43ab55501ac2e123119ce6fc1ad48990d32d09116c12c1f7538d8cd4f71e828dd949dbfd53cc0e1f86e0b4585a2abb0d19ca367c1482495ebebe6fbba5806fdc08c38b84868f8c7da8a4f024e25824100883a1703810808102e603e3130000000c201486429160248db465fc1480034b2824443e2c16281c2014161a8744c24028140608038160180c0884c2e15048201ccf84bcfc23fccdd9b69cdbc999e79ce7382bda9ccdcd90cd990f73c6dcce96250f9f0356c899d94abb0c693383abc7709596ce29ca2e9d82b9731b616f895c47837bfccc3d8f7b0ed169fade7f1c99303dde814deb1166c45d17335dcd2866080662750208a22d4acfbd8604ee0af7cbdeac53946a23355256678910c3f65c9854a1168be89b326a1661224fd6afd2d834fa7f6ba163cc780669085c05be215e24f785c3c2091c82a5b011009d7eb9d6810d19965687ffc01b79050cb0f5b5b7672bd90d49735ed176c4c29d8d26ff6656b1e3c5e68d6ccf32d42c88053d878cb4d89b356121c6e908fcc0d1bd5cad382fb3b985f4189e1093ff24a0eae6b9fdfaa5c7a996959b0743986eb3e77bfe4605f12f85557bcef57a853fb8c2e504163bad26ee3c3ea29dc52ec3ff66161d54e2d4b1c5b3e2b1d8a8fa2dc1c74d655e42884f4d4810f3bc45686720a124b49317bc4b00dd21d03b6a1336613f500a90229a4311fc3d17d50eb0280a5da010519193d28dfbc28a775be808433e704c21984d39b0c669aad8aca0c5806e182d8d867bbadb9df3cc3f86b525e4119e3efb0c8d706a78dfc143abb1fe5c0be0c2c521203af53ca677c9ba39e3cbeca9f2bebc27be75013e16e90f2092a199c3f5820ba5d5bac5913d05fd3d76a36e59f99f62c3158687dd53c9fd0d0f09e2b431be040f1436264a3ab96e5837d71dee86d59bb134156ffae571f766bcf11e8cf7cbbbf24c79a93c40de9537d1bdcf1cfd78a3c48f9d5008e8e819fc591a6ada7061db859d298fb06cbd5afe4de88881a8bb791ade8ab79f6fc28c1d2a15f31502527a13e44de07ae7b742f73c0bb357ef11f662e12d4432e6e2d6e105279c8059942ec486ca86b68539070ff4f306dbbcfb8c7f6469779a9ad6d6e460e0f52feb163274ec32f5c5b8c9815381b6604c5444de1d710bba30f9860b3a6929b90bcd9de75673efdc656e3f37b43bd4ad706fdde56e6f37843bc48d7add0ade9271a6ee3e7ea8e28d8a169d272a257e744752c67b9c5f0aee9cdb39a6a5d6a90aa9876c593a757dfb6dfecde67b6c4b5627db17d51a7852e9d76d7a612591324a962f1a4ac820d435cb2b47c6a3885148b1210d1152344927471f21a5202279479bd1ecae196a8e968b5c85bba4161d4103d0b8457b982332b1d19553c8189acb7927775e74317ac0cea48f51a9c552f9a8b1e799c1364f4e29153cc4d252a0a897a4a537a33a6f15869f05fb91614864694b76b2e641ce1cb30551477508c0e4932148e488940265835ca018c424be145e26d16c32e43082fb265165683c125a0171f9de345d0d14722bf73ccf4b5eef8122d7d0dd48bb5130da3845b20b0ce1fa21a9263f386e59ae48006dbd0dd2fb4101030163b313ad0e806b7b9135c8d4e8b36d2784b8b4ecba2f7e6fbcccb7bc7e71d0cb1288cf4d76b2f0555c40df0435082727b1ed7296cd0a83ba6c4df8d4210763e4ffece9e133f723b60a90efc57503079ee3f01ea559081746cad4dca70c00ea7d8b58dd8bb06ee401d05e25a05eb73dd78d934ea15db7a1745097603af90d5070b4aea9f052948fb361c7552134e496f19f0c34dbb77ce8c0d557251e7cf83215a122f935e5bc50db6d668269bcaab06e8297114998b432c92ce1d823e6783c1aae200af5ef57218fa4fdbf9dcbb015d3dec5fa2e8ab104889334ca906cf1b43a385ea9c07b2c09dfba501a262c88e36d4460cf7c4c3d4c0552c389c1010e92b78295a6b1e5d9495c0afa9a342b588eddf8989866462f8b5b2440cd9061b5c69132b7c48b483c7767d28780369438019d780586ca75b854e7d910baa21e6ba3e173eea11140c9b570b904fd41ecc560a217958c710becbd45d5389aded202876af974244a67c73952ad23619c2359f59b6dcfe3bf27b20851dad1a8b476a1b437443670d596c9630e547ff648a3749d2e00722ef2c6299394c8e993409bcd1d51553acfe093925723dea2900c7a55f6702e5f1f0a5cb3a2e8c8da7eaf2b330320861109db08ddc8add9f8b2359bb21ad000cf634ac2f598b19572b5d7708f19919632f6ae6ee4c0193252aba1ef3f8d4c441fb35297b165a611137747194e85e0883819076371a71099cc6adf9bdf4987f02ef182fae849bcddee61fa210009f1bcf999142eb03226982cee916ab5b02d44c8638000b246e30d3df95dbfa1be68858ae49e3fd0867190ea866edc860df2691ead54994a8555a2810f9e03a87b8adb40b4b81bb791cf86d3baad762f010aeed10190c80f89f19d2021873ef935831249264f3377a1dc9c5696c5c95484ec51fae7c0ed7a521bd037d182153ddbb0943d6f5440c1bd0ceca4fa4256974d0162b8b551462b1798ef7f3d5d5f44ca9d8686cc723e80a2c24496afa0f1d15133b6cc24fe824cfcb0e117cfea3abf962e31cb05af36e1be6dda2c07ec38d240c0761465fa1274c1a449b078c7b17401a5866d7a3468687e756852de462442d1c01cffab5d7f9e8b21c98e0b29fb214d56d44b4e6d41485d8d840ea2496a5dd51c30814c6f83c87ada6c8dd6980db039807197050b388d3433f6c9168949cd2d6bd8e0a70f29b7535694a600fee9b95fe98d8f1a6058cf8907852b3ef7ce25afaf81217188683c5459a21db11b15543cfeb47038fe752987062bd70956d0fa79660bfe538c4d10506ce825ca1ec9f18ecfa36c6a13045040410b2442bd5599307717502e0e697eb016517d8b090b497808392dc5c5a9bc8bb9d83fbe2f26606ab7520f9aa8cab5345cef62ea4796f45b325e6350c351528a86e3341311be125464182f4c566fbee8a3685c6452dec82941eb71c65412a74d56baadff13ab9b36d9c4a1ee4ab0575c83e8b488d15d9d3da1728f98bf1c840d621ffefdc294422da4424ba5e55013bcca5d07a3841724605c56df971d060567a16871405fcd63ae10619c3b8a9cc4f2eb4f6cd9182de961267e1278c9d59e06d00f5ad3a38fc3f19846635b4ac57707981c1b374094bb306cd73a02b7ed97a5d36822429e2c784579fda9809789b0e06ae0114ffd2da224e36f20126057b42f015905e4e28c09a4c915f4e2cd73f5ae06e0c45fe7bfd04b68f4267c235863e814c442c77631c9f9a34274e0b1655836d63fd7febf448e5ecb4e9aa1f103b0aa1bd456b3727e60278f5587faeec1aef32fe58362324bd29f6e4d061b7a9535998d0965b54089746c243fc63d7214d999f9a7f864c94e08ad517a298a654263eca4912262adee08a1b85a85cee0b35e8b67ab9c8e60216e5a5b67aef7004410477468d6126bbc8a81240518a7332a8a205f5dada0225f270174a03284325868ff7e2e03d05ba1b6f23533bf0a2aa8b73ed36dcc197ef27d2842010caa5538c60eaa81338aa1a559043f5658963ee5da999ab2847181ec470666b9ddc796797f29e3ab2270833f833991556e4b030611163d0ec60baac74a6faa4fcc27c226b87b2700c9e5a6cb4379bd3aa6b8cc9254b43eddb05e61c43ee4a91a1311891fe33f358e8da3178d6b49eed1ce3ca810fc8f4675039a7e6af4340a0e8440a270e14efdde9276a19776a26df890ee0ae41030858a7bbb4db02d4ea274cd846b7151ba7449874a4c410dfd89112d21f9cd127049c80c59dbdc98e8a95e8d72bfed25cac77c01ffb338001e970f2beb1e1a1c812977ff27238001b4d8522051521641e1e23e3480896fcfe198f6855ba8f995bf1a80e8bad7f4adc6143967bb1806abcf8e590abf26801b2244ed42b22fc25a3f248712961bece57c951a7e6485e4e4553c6ca8e61dabaf409bab6bb65cccf867e69054d0f621b84066e0f219d3f34702d9aa420139d0e421509e577c275e59fcf874c6acdbe1a741a6102e9f9464c478da4f748e9e83c6c3853edbcddaadc405b6e3a0bb8cc7f1d1dea915fc142bb8868bd9366b852fd7918f263e359a32d118704358481daf2149749c8e0003034d731cc498f0963b52301352e8d97f3528518e120c98f967812fdb5e7f95c63f57ee74bcf85af6a46e4d019e8a4b4318db5023c221cc603934afb720f8d4dd6fb2ed54700339a87fdf43379ddb237eb207ae32695bfdd68bfbc8fddec44cb117b95a286d37e49eb5ae809417641b0e7ca3fcfe6c6eb8ff424da114e99c39f4674c7c55376e0864a7e7ed14fec299d727f223e655b7caec63ab0535a3d14e349cd31247c70b4f472f4577851691f627052e77b25932965753d54abbb43e051ee0082c9026c74c450ae42203b2b47bd9c6880c284a0530edba2796dc8962b62155ad3dd2d6f16cd0e7036dbf80976396180a58f54e25c63a46b57cea14db74d96ab12d3c5e754a7e3991a0f3081a5105e3ded0cc4f3e83e9522a11daf3d8876f3b1eb4da137463fac6b2858bdfc14b1dcb80d0cc2dc3ea1e6c4c21fb0db448df5116580b6e05c40590d9240cf3521a4e8c2352f837302892e1a94c834369f51057c6bd385d786d3c97cfa524254632d407bf81cab69648308f9bb6c81c8b1daffecdcbc1a04c040d53b12e167491343c9244bd10ff11e5f2049c6a3621a5dae536c88de633c4556d5253f9d170516d98e0d5b654e72527d520aa0f16ad7903bfd25a5b1837c20d7ee24bb6e640e5cf09d0f98883787fb0cae54ca35f87d3bceb2f08857d634e990ff8b2a32daff820fda3a9423459f74c0e662ec0ec29b68fd39ea8c840160a94ce98cf1dcb405cceccea4317f8c473c0ea007198e6880ed9a8fcf4a83c2fab7ebc7be473aa0136eec07269d701849a0563825409352a3b0104a167275254581b92f503b8c2ea8518f386c8b19623ac78d146d58447b1d32fc08a355c93a9e77e16e99a9ccbff2593a2afcd7911958e9c2a3aee748522a3927e5e8712f11214ee84a11aa37e3a19365e9fe1746c86a377a96207a4615932a55eb100ef6ecde046f73e6a2a5b309e53ddcef26ebc6eff80d1e1935d93590974c0f1464dadf78ac48c95ed67f996f064bb784496ccc348cbf448aa313222be76bf78f292e4b81c1759dce6bb36adc3548e6825e20dee8fa622b0991e17915b8f8c1fdac02257dccc73dd8f002ad3061af9497b795b4d80b0084f9db04fe7247c9c0ffa97804feea0a05bd04ef0a0d7a6287c003033e40aab411300e60405b63ce460cbb452481bbc0e7c4461c193649fcab96ccb365aec8185e03c7c7cd79c094ca87856ea12ddc224fe4a8652c8a4b7126d55c0a6b7d70d25dd3b9e0711489252245dc9bda42d29c1290e7d5744b9225d4b01246aa98074f3b4c08a8adad3d23eae3cb180425d25d24e5ba7fa1392e3c45e92c7298e5a96ca3a719510ea523a78bab45a4c5d484f9ce679f5c7e26339f24ab053759973a37cce2bfecacb5361c0c0bd66a9fd3105b61f8e6837a87f8a2254a290ea10a9fe2d1aa7f228595dd2fe6f670b2389c9baea04359da53f024271ef25fb93fa9aa5be2662a96fff3e7696a2686435b2fa4a152f89a0e49a7cada4b30ad8c48fb003fc8158cfe90d24b314a0efee034d51f831a85f8c61e2a596954d4d70803ab3f3ec1b4d73a4bf8e55ceff43120e5c184d88d16054f6790590ff38ebf95f2201ec106305819f1beec2b43288995be639f3cd3cdcece819ead019ed9c7ef93eb51cd0d6f8f755420932c72769ce1c4fe8f14356b5973fc67714335cb4f3ec88ec299de90970f5dd73418865d253dada3aed3f96b829bf2284bc8cebe52c673a8f426b1888e493ff4cca36103a1009f3ee2c537e14fbb89d81313347fe8370035d514c86d40bcd159683b6053302ff9c96d679a30d68f33e669c78a1dccb4f0320df894e9a9b54f462d27ec08834efe3e400d221869df9cd1c220c1d7e235636327b4cdcc1d085beaf1c2113c30cd4e495f32e2bc5cc99d1bc637da41ebacdb543a2660bc70f20e9df33a1ad3566dd64a21b5475efa082e88b0bdf3721601ecee0025e3dc4deb67907de0c53efef0de38b020d7a42e71d4c8898673d0e1125f36859e1f51c1c6c185c7eca1c289c5848350a435c048cafb790486545e2c1e61d0c5e7071b13e7363184aba2434849911e806b327f01c4b58117ad9a256c56112184b25ff1187f54210d93ce74256f0f48ef5393044dfafb6751e71d80bd8822b506472b54caa1199a8817f15dcb0165e31a1a9039a00f9797536944cfbffb99801671d38f2e91e39a4efd422a11d0d8715c17562f42613a7461f90e6c42f9a74d86309d63c1c4e4877bf145f5a7a25256d64268cedbaf2c7661ff060d8207405e31db6c642717a6316b8cc16c27e944218dfabdcff02df4ebe2e7e7165934937e983215e16314a0a8d79cdcc05286cb6cb894de0d24c0069a9d852e14bcbffc6077234a2366823ffecb7f598eeb98c6ee7039c5a890baab20df2a49e4b2a59766d26480ac8330726356198778953cdd4b3eabb5400e8ba341271c30578f3f7a18a7a7d6dc6837beb0659bbf598dc4109112b7251148c97a06ec0dc844dc50f234e5258b828d78a6f364ac3261e2d2a60ba478c32af82743548901490f42eead5dd515a1f97d7a11704265f15fb8b7af19608aa9fe8509069d66c172e09f8291af272c3effcbcab6ab9fca97076e60628f93afe921a0b7e52b3748f14d5e1e3491281a5621d096f04a066f9fc2e306481aa260acdbf4067db93ca7a3f06be8e6a61deb5a681befc1d310b60a86c2c443a03c720aae06a260ab51bae780a53bd0222ba3cb4f927769fc084178f13aef04b12f5d1f74e43c118a49a0d519508a115f5541c00fc904813f3300135cb35c21c806ae3ee9111634d7f9d8f3c5750c4f84a06b5f3c9e8744444e590137f527afc65c7a02750d0a08028c3321594737515e0235dc67f10339f949467c24a7c3d7e21eb99a524dc37867bfc1af616ae3613652010d5cd4d05b67e0b2165a472b8e07e0f35a4914234747f81c67fb88684cd24e66ef858f545da8249b63525d37ffa641c20c5d6bc0c7b14e0d3155cb7ac6c0c5bd7955de2430101f3daed09c603d7350df9f4c09a813579cf234f9feb00117b9507bffa46135451bd260b384aef457b1bfc377943099bf87753ba518e0a2795d583b017f217ad58e18ab08ef3c2757d81348959b1630a97cac9c233790d84ddcbd94a404001295a34115cd63ee9b6201c79d720014110c08f40664d240566c2bda1b5c7bfd148953a56a4f16fe1fad1a26eae501b49962a50157555bcced0460fe98e3ec22c20ad98b7c8aeee7c284df811bd5e36d021a8defa91af1177e25391ebd5e27a6946184ecb8845aaa2951f2370586cd6cfa5fb55a6b6f9539fec900388af9224beda6a9c2c8bd12d56f7431dbf5a701948eee12f1a36fee9997f7d1067ef4accef88a92088a254f5a39287927a0af24c43f9d9d54bd043c404182d8a7eb26a7d6c5406cb700b7b47ee9d0f21d5bb3fea7dabd72a92a41f5b877318f729aac74abe9160a25d123c13af47541a53479e17158cbccdab65970878ced3a0ec1bea7c997ba7e9f174d73bede04133368b3719b3da7b199f52df3696e6f7498992c79b6561aef1ca30198f2338862f1df0a60a61c10156025dc54297d3ce78d848d3209c8f49b24dd8a036f7e6f96b1755a8a19802e735a6fcac53c7fb899137b083b22e5edfcf46521b0e6530995ee7ae497010c462ac6d2ebb119a04487be53dba42083180fea1454b32bf2dc165dd439301729c7d0761e49dea2717f64673411f439057300b270c39fb61596b13d0c10ac48a4cbaad4a80c7ce6e4f80ecfb72a8504071bff29f75dba42709c43424583f4f57a6390c7ff28c4dd06b91eda67e497bfe01a8edce4f93f612149436c2368c82bdf9b7426716ac4fa1cc5e176126f9a76516720f119d6637f51bf27e1efe978aee0b513920d151fa2928031df16644c42b622dd6158f8274e2512872bbfd6feec1e0f4215e0995db5c179d4924bd25543a7bf9906fca12504fa3a394d413a56ea2c47a4d492c343f1e5b2f90241e66455d70d44a01b214061f6f862689c75bf0d80b1ed54b150fd5be1ef1757fddffc72716ba1fcce9a4f62872cbda99d3a80cb7f213ca5356ee6f36c016c52259fffd302f28b13cb23173f0aa0030abd1412f6d972ff5590f8d67e45fc7bdbb6ef9850e9b93b40caffdf968b97165dcf725c360df884d2810084e85584c8374c4b5c6f59b75c4362bd01d2ce02d2e8c9d662b640b0c0bf76641ea4d0b637b0a97b2e958f0160bdb7b17062151805156602f7013067b7541e85119467240c9124e239861246fc75108cc77596c5d5e63b143916f5e221175ee1295ceda8ba483f321bc5c275aed647182b655a0311b7fc4da579f939bb29e154340e7468adc46f198c96c64ba9e3914954704dfd6f831cdc13da1f1281a46879db31a3a5a9afcd38ab73d52521ed4a191a7df8d845c3b2b4fbc06585c322c4e4e0a2c48fe3833cdc6818b6f02ea9cadb61619c8525302055b594db7de4db4ded5b1e8442bdbe73a756c65d56d089c9535301867813d5c8efce87f78eb745981415a0963208730d204e2b912d1627c7a48e4163aec994d160e678a7bdaa9ea4e8659b924114cc5d9270d57a6e4ed1f34dc5889846260fb2b16420688ecd0408268014ebe2600eab0f03a790612183687b195f9f6975fec47c3146a7f816d1d1b23dd610a23acbbdf419c90f19c8f7028fb0f3b1c9a64a25cc2a7944d9bfd29f76aa05473cdb075de04f6a1ef2b809d816080d885c6036c25f7d26f9e903e1c18d9a2c690f2648d8bb050da8c4a2e310a61b3e61f9cb08f7fc46528ee248d68174c490fe837129014ca4e0ec3506b9f0efbdc38cd5b39be743d4a8e65d5063c1a880685899f465b4784a730b496863443775f0351153267d71300d5c79013346b800889fba4d8ee1512c6f92b317e50d753d76e4c373c4cc2e10cca8ea7c2c120471e93a32de5842ed50479e062816ac6769f781a2a47fd5d25b9dd7fed580f473052298b8770e50d566d2f9c0c8ff2aa036f7d6a48a1a538a6258e581a5f0e2cbd3fa9a0249c8d6122b60b6aebc811d2797b44cb80ff27825e2120ceb43e045140449c346dceedfd4f5ccfbc488d65ed8e282522b6b2946f00fb59979609db58ea6d03c2569ef1a1340719cf51190368b557ba40fe7e21711349b696b1dc0012241c428b9420b7c8811f71c35648c9da23ae92f952f8226a848ed611deeb90a5e209ec843d614df82dbc0a3fc2acf05fb8164e0837c2c21ba24cad43042d08b6824f43da41fe44ce9a6bd66a111c7ecc8a84100c0990f481cfb0b0754382a2fee9bdae163e7f2c24072fa4ce9450782f33fa58c63eb75bf7e2c420476291243c88091a2908a405e1bb17f54f675d08ba7c96204fb097dc26bae259b41ae333a5ecb4be0415020b24eaa09301498f69898642326b1d2aec9755f26846ee8cd210e5bd491d26f8cd808656e44eca690ae33e9b6882188fb299327dc88753a326de4eec2913dc8e5095d7dcd963872dd4e95cd56468a83bf42586b56803cb0063cef42b41c5c863e691d22102acb329ea2d47f5018fff59caf96ef219e525abd6a29599aec65dc650b04c327c1f454af352beda3a5e22ba32eb0fd7e44d34fd07b5d2b311def030f7caeede5acc3eb5536a2242406faa2ef235099680394da385e27887c3265959f760f80980c611fd3a9566627a32ebbe6a165d2c00d84aba57944f1ea4cef4d592179623fad97d2880e600e635311233b973952902bb62b658a7c5184a9b21b370b8fe60d56c3236b2a00327f4dc556746a4323b0d17559d6fa49878230f58eb4d01f3faf44d67e5009c7f33dc416c018a58c551848797444988cfc275a8bd2da85a363b0a366d9a52086722cfec3b418f8d4702f636938caaa574259fea6148382440c79c528fc09190b0edbb0b9cd02c8dec1fa66797405aba8d05a58328e3ce3b37804b24300a9f3b875a2d8345be2d0c30d9e2e40a55a5e0760de9f02a66299116412797f21adc770ee9c3e5edea62d19306284a0b9fb72c65f5c1d14b54ec9cfc9a5080a5c162be5c52eb15930cb3faae303323fdc3ed1780bab1721fa05c9b72dc1e01a1c55dc7bf16a804b31c75b800dd1c6df8ca914b40022f0b26130c1dd8083c79fc546fd6f8eec49113e4ace29fe1dd1b40a255c412c0986827e8ead4a25d0b36f993f9e187574e9167adc002ccb28fdbf0f1643a3a91621fdfc0d7e7de909600e8d9cea1b92abde5460f34b0d313c219a13d62137338ce96cf2c3dd6c07601270d178d1d95e15d042d8ac35a5f2f0aa294aecf4c2a681fcd1e92da2e6b5081f766f281bfc4d27af3152b82f1b1427a608da3dd2751b35b1c6f9ec44bd57036974ca64ea777ca0e7c14fcdb91942455d7dd46300c973166ce03631fe6bc8cb7b801a0a171ea0c086b474bf6786d7462ce501a4161b1cfec274839c29599c99eb10b2542a10f52fbecb082e9ad4b52657afdd8670043a599568836db4903b1f1b225dac8686c7fcaec5c47dfdbacf94954e506fb6013f8decf2620b111829b3902b0b1b5035b2d77c3d79ec9661378b51b25d8016c670a4cd0e68813d9933f8d024e4d4eec4ea895fdd6966ab3817a726cbaadd321f029015a4f65bf482190de98a213feb9815a65caec141e687189d56712608243c6982c7eb0109894967865003a63b1f83f84f39fb1031a1662b3dcee42e2162940d082a25bd3b3513a31b20c2530e9477a4fe9c056a49f9f5ef36461daaa946f289485470061560fc4b7bc110253332e90621105bca9d8d9b386a7ad1a675b15206daaa00a2887a7558d5e9e3656bbe83bf25e8964558dbdd2344a0d7c7ac3ce03750bd054a46fc29e8bc11d97c13cc71aae8cf9fcd03d8c784aaf62f39f438689035ef0f96ff406b4e4b98473cce6b9579eb16ec327bca6bfbb6499da22364a10312ca2bdc5b08344e0e1838797d282b8043aefdf2611f84e3d717e06bbe1f6a6357fa453852d4a6407f4f52ddd4537c6982a9e2af7c5901d2c43b1092f0b3819ef564f37a6a04a8d4d81a1fa7a346187a5ce904d719ffc6efb74deea9ff6106e4907577022cb1868ee833b23c5cb608cd7b0fd308f207e4b9491538069991c3d1e00861801efa834f6a89de69b647fcb9e648c231ae80cc31147fadb619e1b480dcdcb43b58c1cdd65e0f8a25cb4e25cea6123bd5ca4b60eca7b0a56a92c4bce58463ce48223e21de84747f308097df3686e91605f7971e1e108eb7b974b29e48a93d903d65bfca9557523a3140b35d7e12bb234064d7dd704e9e6df68785545f580356d1cbc54e2e53ada37445e24b173370a360ade6dd16cb994b1cc358c9eae0e1a2b26361b66ca45216dd374f8f9d13f6b8f871bdbb9de2445afb3b04104b479fad1779449a092b9f154d4302cdc0b02ac5f660fde041baa3162988bdbcc002de380e0bedc3323d07050045dd3c6beb68208850196fa08e75719335321bef6d8bd7a22b784f4e1712167cee0e471223c23a5c776ddc9249a5bcf4a71090dd04e188f7949481e21f33ef96cbc8c2b96db23b55f6f6358347108e185d2e4f93f0f1120aaaad6664dc4079e945f02553df450e07a40e5f8ce3ddca2ac00b6fef4e44e6a805d241a167ec797b702a0880220491597fff8dc17b098f00bbd13516c9469b39182610e92e02899852cbff53c19325697d34d573cfc48c76771155b178a3cc8e56de086fdb830923674d9707a3186b617b29e04466c919a8e40dff20c41baf878c79bb1b11c7ad855de0864a0d5b7200fddc769a6364a32d9c922c56242609419430db85a12518c273ae4f42d96cde2c90d7ce90f47b92d44821743f443ebcda91c62048588b59c67db69e7d45355e3727cf0e42ee7a87382c09f94631427b910681c5330aa82c01cdc0767ae4c9cfae5467371bdd3aeb85b2ad2ce4f864a9db95ce6d71ce22d22188cbe5b4132374ff52f40495c1e914d66e03ab65cf49f79dae419102e5c47305c04e98ca4da7c03ad1adc0ef4e3f841068dc861f24aeddb62b85bb83884f1aae70fa0d6100b6a087a25ae0098821562d46db8bac21b4c62a9d093abeaa546543429f02ef404f1a7d32a343cf94f468bc9798a594756c889aaa314beeff83a9a44de5efcf9ec528b738c7ff559be327244aadb8b67bf8868ea7a808a9767929634c99556563cbf7a72813e570ddcca3b8b6ae791702e38d793270b2426dabf358627e4d78f777969adbe8af345a845d211b3a44fb070cd139ca575ddf27f2d25f23b67d48444357ff888f60008015f90336a8d59969c005e0cbff7686203a95207121f3ec5f691dae75e29a5fb83a82d9fce81cc6e1f641d445380deb97657089c741218ddaf56d3b1c91493041bd1aa3386feef390d8f724f0c070da501787f361ea0be9914f7b08de820ce6d645199cd232cfdfd5df43f76d37ded55b4c512dead744f18755f300aa1676cf627a08ebb10e0b5645493d57ff93381ee7efa7eabca6410d49666e884f89ddb65ce67eae91725133a9cba5ccae5f4ff8c3ddd9374e14e9be46e510c54cd9abe842384b8973010117474ea30c673f0e0d6928b5ab803de511a18d851dfe4253c6ba6182d9398c2443fd5d26954fc1c60ef59aac3f7b9ba9bab19796e229b00dd517d61ba51a1ec230af124a97fd046ab2e9282a8d14915145020663b0634bb8f8b29c0a0e86c23145514597781bea48cf03f300daa13dc0c5256806f8beb40b65fa3f8503a59af32ba7fe79939ecbc315fce4f73ae7d539fc51df5c81f208094012c89023e1bc4920a3150b46439fcffffffffffffffc2836884b6196b5f2699a4d43fb65e9f93292599524aea8a507fdb7bb1adce6bb700705002370ab70a2a0b6783f633402006064ec07808a3a48a09795f762b7b3b7018c25829438599f9948102331e8d11b081a310e628ae57faf7251fe28430560a3955f2e8312999580d1c8330b9a85c29994ed23e98046112426bde9d754e218a0261d07d7969a132cd4407104693e521f7d357d7e40fe60ba2e53fb473d4b2f8c1a0f7df345674b793d70763e86041041737efed2c1f0c6bd97abda71bd6e93d184497ae9efc5c8c9f9881430f063993f263296112dd1603471e100d0e3c1872492a49b12d6b2a5d068e3b20871d8c132f3b826ea7856a1403001638ea6078ad98aedfd9a225a7834955a4eb9a373122c77d1b9cbe2570ccc12c794be95eb864acd982430e5d8bfa49b2f3ccf19a0c573150b0813478607860666046197130fe7552e3a1974fdfaf070e389842ee11d52945d7f7eb0e1c6f306e8dbe9896f466a204898101316c6c1b38dc60125f5ab6e106071bcc7d7593ef53487a13101070acc1bc9ae57741d7a5d21b047567dc020e3598d5c45354f6e3ed48c958ab71e36f50461a36520d1b67a41a957fa38c1dbc7f192420f63ab091032788230de61093bb64954b86771a69f49a064ca08292010e3498b387c52461225f83be41195380e30c061dfe9e2fa4b012a5b4c1e9b7810dfa0635920d1db0251b3ac0097098c17055f24d25a70b950f9571018e3218ff928509db5056bb93c13022fe95bca4c492c818cca9bbb2b7544c072c050e3198a3e536e1f14708cb3a0c869c336554cd4704638ca154e00083e16db4d5271d9f274f38be60f894945f9dd6923eb91a26070e2f985267c7e40f2a2718630ce50347170cbbebefa72b574fff655c000b1c5c3057906a7f41733150000213ac81630b465739bf1b09deb5d7c0cf000187160c2178fac64d9e4923e1c8827135ecc297b84ce0c082c12ec7df8591db298538ae604a5a44856eed2748638c31ac803c930bf1c55392048e2a18c73e575e388d3d1351c1246fb174ef6939a6604a634244106627cb13a0148c56aa74a9724f0d237044c16416f1da547fc4fd13148c21737f36436f434d4f3007a1baa7f3236278de09c6ebeba4d7e72618f29d0863ea7cc276ce04d35dfce533a14e657909a67abdaf0f2b17613d4a302515d45e5e11d2c328098657fb71fb2072b86023c1d43eb69eb49e490e3a828d911c46e02882f9b292522907217663250229824ee710cce152cbbe49eef0d86e091c423059ecfb750ed7dd49b5040504c3a419174f9d4812820c0cc35f28ebd2ff22ed41c55cd0fac258226fe4dd30f5c2242b2cee93887861eaf91369e3ad257476615041d72bf2a90b53c8ad203ba48d94839d0bb27857c8d6a6ed1e63d973f4c5195b3109e38db791831a4b06a100bc26cc7b5d8d7c0b835249453ce7d71686fc513c4c8749a6c5ad85717f84cc979b1626d9ff9d90d35c3ca9b33059c8b8f0105516c6f8adc94959c82657120b73b00ea33fe8242c0c3ff163c40e93d542f015a60e5a34b55baebfe40a536e919ebf9f43c9cab5c260c9266dc6dd5cd28a025861d0cabe52d12ec53dab30a8d9114ac7fe51492e551844a6bd455c3f1559292d3fe975428521dfa7a97e855c21994e610a23fb22a27fa776c9146657b3acbb5ca6564ba530a5ad0cd1331e24a94f0a43f851ba4cedcd24ef4761f2944d5d52e5a5f14561cab75a290579288c7212dd84b8bd8bb183c2e435c2528c2c132be29f30bdec7dee2f1d2996ee09d37e6f558ca4d309e3a5668d50677db23c9c30c7a9d23fcf3e56a9b30993d25b39450a61f683ad09739a8a093a6974084225135ad70861c23023f1b2043ba9e1954b98c56554f6c92c215ac268651523e71257c20c5921e694982961708965563a65ad09f224cc7e966c7410af2a2749984f6915356b3d267a244c324ab456f81bbfc842c22897bccff28ac7fff61166d1eea0ac3ea7a4e408e3248addd908437e4f759e57216c4698e4979658f2751127a1bc3c8408524598724a1059eceee74b4d84513f449c7017f4e85111611eb9172e589ce8f9218730cebd5d9e53223f7c8821ea1b359284b4904298e25fd8ab6f5d8e242184d145dac5f828a195e7208c25f47da49935152a8230d998aa88b6a022693110a6549e4ad47f29b32002c21841cc6bffff07c3ae78ab5ebeaf1ff9a130eda7f4b3de0793e88fd65fdac34b870fc6d221f7e2a9f76048fe313c62877f5b4f0fa65879a62f25793089ffb7d3e21659abc38321d7e4ce509d456e7f07838ef2cc0e66754f2289b7eb60f4a03da4b42cdd4ae12328800ea6ec10dffad91ead3207b387d6940fa2a5c4dca2003998fbf24452c943e26048dedba17f3412a4a100381872342de35b6fb9737a83f9822ef5f0e979544e6e30ac6ec9a888ccf5d1b7c1a443bcefbd9899be3e1b0c56c235363cc6f885d660c8b9f9e94ca538b1e36a308d4895eb2fea6930aee47cf1a9ffd3480e1a4c419c9ff9aa256db13e83d9a468c93035aa92d56630ccc752132929cb099532982607bb6b13e299a3420663878430a6372a5a5dc660f4dc29463c93c560f211b978fd881bb286c1385b51422a3d35b925188ea12a1b694f5f3077aa7fb76a7fc88f17cc4976bd85a958296dec82b992fab64acf3f9ec305a35795aa0bf5313ebd05e38d57e70fb95c49755a305e3a799eb3ea3afd62331490057387e4b69792d2e94fc282b1ce3ac9f8f824e2e8af609219aaa532defcceb702e2773c96fe1155c17013f594d4ff3c21712a9884d6c4b4d27fe94b5330e80af627642b05937cedd25e9d4c27d95130bb752cb3ef0a14be74332f215ee509865b8f717146b632632798f774cc12fd9eac83b609667935ed977a32c12cd721844b30b5b2e112cce925d6de3a68195109061d52e6bcd905e1d725c170fed1c727f9aa9690604ad53152f8ed9e9c1752c01130533119c170a942d07d9db37d9e8a6048c27a72ca257be3f40a208241a28ca88a91ec29e7153004a3a96895537f4a59880284604a9e7a3ec5d472134ac13088a50b297e47c0305d4af2c182d07809fe2f0cd739d4e979331dfefac224b353a530b7f6c278313232744f5e9842faecc5d83995f3db85715de5e6f279498c5917866c15c53a56f8503a940b53ef4e8aee2192880be2c214645e97e81423a8945b983dd6fb920ed726c01606196d73f772511e24a016c66eddbae461e634946861f0cefb984bf75039cdc2a04c58ce963c5bca7564611cabfa4c11722c0c631fe729fc75953861619411ca530435aff6ed1506f9f3a554541235b62bcc2dc122a41ccd3071b5c290a3479a5821ebfb618549fddbdd53bb7fd87e1506cdb8930f2a5bfa5d15c64c4d0941881cfa74a93045319d5efc42a830e9b85fa9273f8541f25cc85cd85870dd14a618e1d44ebf530a8350572e661b290cd1f6dd763dcde8708fc2144edc4589708bfdb6284c7a31bba183426190dfa1e6d2749e561914e9c4cbded922fc09935dce8e16b395728a3d614efd6a3bf2e2f5aed2099356779ccbe179d3b39c3049d4ce92266fc298b7d6313ff5cb6435618aa73aaf6d24874f66c294a447b1d4b09c53b5983025b30ed94a5bf457f412c6dc52625da3a3a65ac2e09f442e91c4f38e48254c395da570e96927344209e37dd09fb2e53909e34bfe78a6e22909d32789332af983a57889846946470d4f199e119c10f7d596c118caf4f2ba875fa64a06c3ac794839e1ef434cc760fe9338b23ea204f5a71850296c961695c530983774de5fe5c748f20383d1d2b8cee7ff0be62496e339a8a4eb6e73720ae2b36c85e5d105a3c950932ea9d86c2b3db860b6d149c731d3cdf46f0bd97552ab164cb26eaa652b5ee66264aca14fc306b2db814716d0f1f3224c23076714d3838207164c65256e94e7b42d3cae602e65639ea2468ab896580d53567858c194f74a4df8ab2c964c457854c1f8d1ffe2ab04ddce122a98cbd2c80efba78318a76010b2ffca76c7837b09111e52307e89119b76f9fcab838228182325fd9b955216f0808239c78b9e9f72f48f952798e5ddb479d0179961999d8387130c3329cae51cd63bc96f341924f06842422fcf621c030f26982485cacf7b48a3a3843c96607a5753b31234069f87124c51547c3cddbc637cc85813030362dce06d604c108210f095f64882073c9060be8e707b3a3df668c97dc1e308c6fc7cdb17ebb81c3f848711ccd142463df582ae24d2078f2218dcf334846bac6f8ef2e0410453e7aa4a975b1a824988bb4e6ff11082e1a4ee894a21f4c54e308c7dbb9643b9db48f081618a37b12e59857e61cc53162d5b12b71c725f186b3ed552c3fc92e5bc0614111dbd30ca8b0e49bd041d7288f0c2fcb7b26f21780a964cbb30d90911b4a4a02da8ef7591e75cd92b9872a1e5647fb612c7854105b311b9c27912b2dfa21c31f2a70eb3a0c316e6be9c5417fc24574a1203033d18630c5344472dcc9d35dbce9456684f6961d02968a8b78549299a85c15625260b53ce49a7d873652c4c3ae5ca93ccbaed14c2c2e069da74ce64e5f4b98e5718bbdacf7792fe77571dae30699af4371d2a3ce7dad10a434cffb8dc69647f7d32f6d3384304638cd1c10ad3f864356bdb5c0997d3b10aa3e5fba0eea49e19e85085e174e8f939c041a331023c0f74a4c2e4329f7b2c8ad0b1827fe84085a923567708ba7c63c5e544c7290c9ee6f24254ccc841aa91c68d33c8404d7498c29436fa417c45aacf3db071c6096eec253a4a614ec173e62d78e578925d1f3a4861eebf35f959234ffe2cd101d1c1758cc2a4c39d48e695547aa92c0af328e195b2495d0c1da130689c27bbcff2f711ae403b40610a955eecf3488aa1b78e4f1824071d545937de0639b041b6c3132695b835264f85d1a771e34590fa848e4e985e23453f51fe196798e0d4071d9c305dcbaec7db0f196bc4522a2303d9b10983f6a94b8a569d674732d68e8d336e9cd6343e043b681390232de8d08439f27ff220642713863989f712a29fd26562c2f4c94eb2dae9507fed1206dd79424a8f9c3aaab584aa23e62b4db850072a61d2da2ed692844cd94dad83128618579683b02c49e19540c7244c62419c4823e37fca27634d67604619356ef40cce6a7448c224af575db2bb3fde8f84e1841ed562112b749f206188233ebea894479867c4430425b28e30c6964e12b2cc8d3027dd2108310bf3fa3123cc5d2b41ae04a1e5aa2dc2a4de2ddc27e6841cd48a30b6c77f92e93a237212619ee41255b54584b94aa5646f7934d0061d87304dfe9027e6892779ad210cca2f0511b9301b3b29842959ff7e8ed157f33c210c9ee3e792131984399a668c87b06d7d16411864e4a598b33810a653eff944673e7c5e0161bc4e3aac94bef422437f30be75886a79b0cde81bd4b071460d2074f8c13cd79ff695ef1d731f4c3a7ae3734c5d7c9b3af8601eb153224da7875adfd0b10793698951e93f7e435ec618a30c1d7a30bb8952ba9245ca65e7c110d79385c8e94e2733e1c1bcf93d16376542cc10093aee602eab8a15254f7630e959f96c48899192b6a30e86a0b23fc71ef5cb693a982559306de95547a890be3107d365f5f021e7b782fa7230460e21fff563bca6280ea6b9601eb7930eda3c380231306028820e3898fbb44d77fa88625f7983b9e3267c7b753adc604aeaed6404a196efe28e3618741cf16f3a5eacc975b0c15c322a5b6a0f95f8411d6b305d57c8cbc147fb5dd5a106534e1777429851c971471acc29b376245af09c67d4810693b89265f1e2d67106731e0f3ab23aad7b3a7598a1a30ce6da8e961ef9e62fcf64302755af9274cb8ba5948e319894a5d457d9573d9a5087184ca6e25e59f2bebeb07784c17cc97b84ea297580c1dc9e6309d9d1449e24ecf8823154dab0681ee75e6387170c42f99ce890e30a7474c12caa62c988ab7570c16ce15497752a756cc124776fbc453fc2850e2d18af5ef74d05fb3897ecc882d9c4086175fa43b0bc93b156b2091d583078593ce541e92041d664dc681bdc48cb29745cc120b664bd8fb0a07492c6df60247458c1249e9752be824856e111dbbe362263ed460dfc8c8b4207154c4ae7f34a7491ef9b2103c10180091d5330c9deee284b21edd26a850e2998d2547ab31ca794fe3f0a86ddf2c841c72c31f291a1030a26a12a28efef70161d4f30d6571e7d227a6adc103a9c60f6d4a347848409b9e464ac1513424713ccee275e4b54aa850992b176b68b0e26984daaff4eb0cba3bb97e139406f63cd3a96607cf5908210da11a14309e692ffd68cdd8f5a0d6446d748c3df868d331c2f0523400108c6188334a22309df46d01e420551714400f0840e249842fc3ec9ed18255f54888e2398c37b12570949449f91fdd0610493d23616ef2f8fd277923d7414c194ac2e2bfdd9b67ae8120f1d44308b4851b27c5e8e4a7a1d3a8660f8d7c8edd194086b4f87108c592167f4ffb94bce81812318c6eb30c2e2538255489bc0010c773e8ee46f512612387e61b60a539dd86eca26cbe10b47cb9caab16ad4c0cb40a30c397a61baadf06217c6a485928c355e9894f6961c612732d65e0767d84083ecc2fc966cb764f9cea582347270460f1a0417c8c097610305638c91460e6e705217e6fd78b23d89989eefb3326ca060066694a183336c90a0ae56c0910bb3e849cbeef87061f25c953c67d3e9d3ad5b98635bf0cbbfda41cdcd610ba3951ed39da487ad7a6a610acbf38e60e1bb744c0bf3b55e165521b48fa924143866610ec26b525239fd39aae39085d9de544bf4b2da4a411cb130e8b855faf6179f4212072ccc398654911b59626285e315a612ad4ed656b6256b0e5798f4a7f411b4b3f5c511472b0c2ff973be37915cf671b0c26c2f42eca8a45444876315e66d13fa626dd35694f4c68d37430f8d326c906abc0eca4011b8a0060018c1a10ad3b6c9a7f6df6eddd2e04885f127d68358c5b67d97a83064b94f9f2646d0959392e31486590f49e280c314e6f46b4a59104ff1692e85397ad22108f70bf9107c3446200606cc18811818e8c018637090c2f8f964a8a8898ec214d4d57a5f885887958619efa2c83cd6fe509834b6d4723c670f7080c2587ad243d076291f1232d6d0b6031c9f307b780c612d2f7a47ed9e30a5bb1c1fbd7119a70c393a618e3197191ec2bde3be64a0208b15383861ba60e931c7b38854934d18437877140fe1ff276263e0d084b9e4e64f503365c2144786b22bd3bfc08109c32753b1d2449aabc0710963aa9ff097fc1de32e352c6132a1449f0877f996304725cc5f9574ce7f2ea38451ee2eb6fb6dd44426b1fa9998ee08faf693b1468ac02109e388ddd115f73f1a9130868cb8723a1f27fdcd0109a3e9f0ccfa0d353a35ce40c6f10843d2ce12ea16ff53ce7a60e3735002fc0087238cf1d9b2875ce2137034c27c65daa5df7eeafcba4119c8c1089350a224690ba14347ed171c8b30c8336d51fab4894990220c23f52585ca3825a3cd011ac57024c274255b95c389c958eb341a751a9d81b3438439c4d39c471fc958db47b7349694868d336ed80dc26883594250dbe1c43ae80f1b4c6147aa4593abf1c1b20653f64b951f5613c7623518bee3dce28abc9d531a4c924288cad0a2c198163d480b5b9fc12022e6b283f97d4a9ac110abd2fd7bd3329854dda4d86d12321852f27ca3151e4f98c6600e537fcdcf5a621783b9de47fe23c9ffcd1806f37cfe9f705142550583d1de4b46f768651208e30ba6204743ab68ef05c3c758b270739bbf6417b0ce26d404317a4789b96012c9ea2b56ae101e4f1b67e409630be6533284058fe4215ed1824988faf82d162b744a66c11027cb4e106e95ddf5c0461a663c89411858305e5b95eafa3afbabac208c2b983a3c5e88e65ba1d5b182f1f397d896ac963ba5aa60aa160b299274cba57c2a1854851439cfc147dd3305d3dd7b1493659182f9530a2e63428d82e9f4d9aa7cdfc51625148cb17f925372fb1df5134cbdd96155c24b92f085e104539ae7f5b0939d208c2618648616e126526ecb1e0cc26002f6de273bff787f7210c612b09453c4f7ca49fd26c377108612cc7d236e7e157ebc2c1784910493fca9d3eb9d173f289d9186191d20411848205dd6da35753a0c8871c836d240037b10c611380c23a8f9e93a240f855104d34e6e65ec056da4814618443098dac9a93a23faff52e36d80c60dca40038d36e30c33126da481068631041286104c31a2f5f3920e1f3a45c69a06c6182318a6687d6e23929c6c42fc018c740827dc7d2f4670f0f10b435061ed93529523a4f6c317c6f413c9d94ea6ff5b7a61ceb7f0218f5e0f0b3f2fcc25ce639998eff0b6efc25421897cdd7186c3872e4cb32729ffc80e2f415d7261f6eb38aa53e592f4d4bee10317862826afec736b753bb730974549410715a4a6986c61aad634fdf079265abcd4c2ec914ad6a79fe546190b820f5a9874d8b8dc7df92c0c2a7fbf1fe7e9e2c8edf0210bf37cd995ca444b2cccb61e210513172c4c25b994b6607dbcc2b415f9b5a4e7ae305ef0fce941eb34444a2b8c559b16174197a710b3c21455a26bcf9fac45c9c72a8cb2f19fb2a8bea99c25636d35f0a10a735f9a9790162d7a0eb1c0472a0ceed9f49da77b8d7f64a4518619638c199851061d3e5061a7305d4e397448994fe2ae0f5318258e38f9a92b87b7df818f52900f529852860a9d6db370d9761406d7d1511db449a9f0210a539e3d95f2b89e1825e64728cc215c298b1eb2a030b75558fb089553840a0d1f9f30ad87917849a924af2f9e309b6993eda4e2de32a265f8e884794543355efb635cee8313660f3e3a8470f98f4d98ba93c8ac905532d66a70a3d3a8516ae34313261d43cbeba578e1b3f6e02313e63d19c1d2363624e8c607264cba7d9dc289741f3e87c6c7254c29e5cb49930ddecde81b669c618605c618037530c618a800c0c587250cf249a9eb590f495755ad84c96425bba4bf38418614111f9430ede8a5511d2d859c3a0e1f933024cf8ff6f24149bc21b14f99928cad8183bf71fc1109d3c70f22debb95c6dfb861010c70400c0c8821821168a007e4111f903048cf8e2621761e611295b91a164e7bb54a8b0f4718ed3bd88e078f7a154d679c6183046cbbf868842145f21073f11da45a186110f727721e7b89f7e5224c4129ad7f31354518feab5a23774c909927c2a4cbe646cfafb347bef081088367f7202c99de05391dc2982ad142fe4ba4da785cf830c47d14c2a4a56321f444de9e9d091f84307fae3c158bba222ae4c2f0310853b84bb5753da7eaf282308a7653e13c53d72a1bc147204c2d9e1fc94b8aecf00c3e00610a6d12dc428c94feb7910334ce061f7f30ab8974fa6247c6a9948cb51ef0a08c5484061f7e3045494269fe763efa60922c626fe9bc2284341fccaaa6246509eb88f51e0c1f64549c9fb01ecc67b222f2e7c98339c99e252d953db6bc2a7ce0c174f59323c7506175d28f3b98b3a5eb10dba10f3b1896e245855d4cc6da1563c1471d8c15cf4c7c96243a986c52994d1a1532d66e2033fa0c4f061f73302595836f9c0e231ac21ea45106213ee4600eae5931b392ce103a7571c074459cffb87e95ac81c68d3a7cc0e1f5eb9873667d51926f3042ce7c3593967a4fe30469860f376479e27ba54c64d369433733f14f48bc78c992870f3620ff392c8d1cf93da99e1a3ed690b2a85a97730a4a203ed4905645f0f865aa19c23490beb6f78325870f34a0426cbce362e9ec1952a1ef74e912bd916f0d1f6628ccb9fccb778847e7f0518666b7829ad193c2a8c4c08018221003052010030377f82043aa6a219e923de1630ce82efdb394b23664031b29e0c387183c73dd0b27bb33d20b43aa2f1792b42fbe3330986639463b2baa46cc8ad9ed21044be1e1e30b065162559e23e9875fcaf80037e1c30b0695b3e4eaca5df615ea8239a8357517a16142ee5c307ae9f270a65aa9287c6cc19cba2d651bedf92ab5160c9264f68d05350be63c31dfca7a7bed2c5830298df89aee6123c6750573be8e2ea2a276df3d2b186c4bad2aac5527cf84955f86f041059350495dcea65bef43ff988229e74a9db746968241626b8a897a11d1520b10a2828f28782962425a8da806cc28a36d6440a16008ba744cbace4eec0b0a1f4f309dae382295b897f0e104939abd0c611e84c930d9f86882c707136ef4e06b50238c8f2530e0430908f84842033e9090808f23dce00602f830c28d1b6f038e8f2214e08308370881013e86501f42b0810d1a0310f00886033c8051a30c342c40820f8f5ff8e2d1195e2307bd3880072f6c6083f60178ec82c3431765a06181303c72413c702186c72d08e0618b5a24c08316350ce0318b1a65a061811b3638e30c122cc043160ef088057bc0a2461a08f078c58d2fa30c342cb0810e0f57d428030d0bd8f81ad83863056178b402fd0c66d0288880072b6eace2c3431509f048450d0578a062001ea73080872908e0518a007890c21c2ce6e7be2c29d6ef0a048f51182f646ea5e4c87fe692b1766383e0210ac34910d533b146e68743612efd1a4943e5010a939795bea825f4f8842188fd91952304356a2a62a06003c80c0b8ca145f0f08441447a4bdd0a69545f88e0d10973ed6b856fcf17365f74c605dc8c1c68608c1f7870c29055b4e38ea4cf61271e9b30d7759ce84e5ae5c08d31c6f0d084a95b2e4fe4f4f1eb7c076760608c3170e0910983b25d55cb63c00313864b52dc7c3b889f1cbe81c7250c49dec48a914535f0b08431aec4884e1da37b66b5107854c298a5433db59f2861aa39cfa5b233099387081e1f4aa83cc2968479c4c4c68275f49461240ce7f9644e529ed35307097358f12e91bbec3995c7234c62f27eec43ac7a3db081027f1d98230cf9449a96f9eed108c35c3c7d532fad6e71469894322daa1ed4a5057711a6bb0fbeef29238ff03c1461b2e8e2315f65e75d7924c2b4399ea59727951ccd0311e6e46e2a4832a14584ef3d018f439836e5c3488d943394da109efaa8daee17c274263f8f28bbbd9315214c725ff9fa3482463f195c1e83f0f36c49ed3c046188752a0e84498a560c08737b96f18963c2e61a68dcfdc1a43b82be9b14fea94a7e30e8181f6fd4c99cfbc4a30f06655afcd26ae8755ba306fd364eda7af0c17cbd22136b2f1278ec616d78e881a453120b0f313e66f8028f3cb89664a747ad8ebefd0a3cf0606338c2e30ee6203ee94f6ea2ab82871d4c7bb22fb6f809cdcfa981460e6ad4e08c336cb0251003036280400c1480a0e8c0a30ee630d76e6b13a2e731d1c17c67d7af1e3c8f39182f088ba24c720a49a1871c4cf2f9ab3a2996a59c338f38a4071ccc39b669b57791c71bcc7daf6e2a2fe8f627b3031e6e387d8affb63b95b6e191a4d54b2663cd6dd4e881196d34f06003713f69d3b6ce93dca0460fcce81a37ba3230c618376cd448e346bb193b3803a1c71a4c6fb9748732a9c1aca743ce97143cbae9a4c13c729d2a4c84892829683097aa8f92e44b1471ee194caab2ddd26a78bfb966305f4dae907a30a9bcf72123e6e1bab0248456c933e1c19c435b78cf967758558488d2d9c15cb2ab73964f12ecc63a98828758212b6685db74308baabf8e16f2dce53998e279793cc92feeaf1cb8582faa447fe26090951622fad4a8b41d1c4c4905d7fbfe10cd4c6f400303dc601cbd922d0989b5c1f4e5397c2fc60643983753d3f521a4656b30ae88ce3ad594d3613598dbd4bec47f7acf621a18800693d07be94756673044cd0d8b55fb494290016678bf52366de16bff736528cf579678f82af7c9603c393975f544977cfa184c97ef3987cac8adff88c1c9f1b63dd49f9b1e065318e97a39444f0c910306639d99327f516ae4ec2fa47223e547574b222fbca572178c3e422871cb664a81015c684f655d4656ce16ecf3a0f447bab72d5d0ba5f39c6654d6d6fecdc2215bd6c8cd8b61420103b080a7d71c193978fa5ca1ec17747c39252b14d143845827a2df2a5c1e7794120b217fa860921842bd6be9cebdff144e7edfdad1df3a2978c1647b4a416707191085435ab38a58595a3b28607f3259d4e49ff8099f095f1d8fd59d7342365676a31d235e450c6802327475989409e414ecd4bcbdc29b4b40bde3ed56deecaa90014af0d2884a27843a71332009a59633292a0c4002f2e3c595fa08c6d1972c45eca4b4b2114cd1f524c50929025a548a949245b1141b110c42c9f814bd7df259870143b83d82bccecb16b34d0c10824107d321b32b899fddc140e959f809d3253960984388da294bf40b436eed911dd717e6f41c948efc25556a2f8c1bdaa176262fcc1f734df4574ef1bbdd85af615226e88aab31e922bdf1a32d5a59bab97023e9911b5e41f85db8f092ef9f0a8d6fe155ce3cb3493b3a5b90e7f5926ab530d8a828c172e4f7f8d0223b1d2a84d12c0c415285121e4c59a43a480b1553fa7b2c529193fe38963b395868a63eec45ae3a855f61d2397c8ed45859dd15caa46829e47a044b2bf8be5049c9911c2b5afb4e6a3f8818afc289a9754a45d57454e1f75bfee55829ea5498c2e6a4921d0b3d294385c14743a7687f293f7d0a934a21757eb1cb7e2f0a0b30853959c69c8dd2fb29924a610e93e396fd8c07ad3f298c7edf4994b83841c43f0ab3950896c34b4dc4f84551bec88c89cb6b288c236eb3a22561541228cc412b451555a14f1892cab7b9e81f12f5e3098350d9bbde743e7afa74c214cf6d5bf2af085d36278ea434bfbf4cc90d954d18cd752d9c12b226cca73b2cb4d794fe8b33618871134e998e1f36c5c47a1d2c7c76a77bb92f61d44d3d6521a7a4476a0bb0844949cb4105f949859754c2146a65f2c84b29615e8b2264a77021494a27815097f36479fa699230473bcf22e6bafd4a9130e48e94cc2f04a57d26244cd5e7c1eb7de7477f8471c259289323c62b4447742179b776c5d04a23383bcb95ad14762246182ea2c7bcd32dc29c6d5bde4f8a307f9555b8f6dd5b3711a63f53df9e3e879e1043c42bf1b4f34e0e6a3b4431a9bb5f24ba7c0c61f48a704a843f1d6654089334bb4f2a2949f22d5563c1b0004218c2e85c219b31263b6510c64ba373d2058f2823fdc2020491cea8c9d9de06820f12a2e7ea7ad30362dbeb2c1ee1dcc72e7f5075745ec9048ffac12054bc4f1f9e758f9b741525870f86a0e452b22e8fd41dc28505ec61ad2d9110947a3858d03e696363ed3c984f279d922aad9d85090fe80c2dfe3d498864f13b20d2da32353b989386fa89abd317aeaf83a9374f74a8d27fca79d405cdc188223b25650d9197433237794b88fba97148a86ca9f0fb210d07934edb177c674d5aed6f604288a6748aee92f2c80da653428a9c644adb5cde0663a968699122365c2aaa04b9db1accf9469efad8cb97ac06c39f48a583103969b83a42b85cf30a42868682cf758844abaad319ca67deb1b47274a66d06a3e5b0f859d75b6997c154414b87d2ebc89029b12be93194b52fa6a9899dcec2627075c6b32aa4340a83414e6acba9d34c4f70c1706c91ffa6fe73aaacbef004b112c7d2f45e30e55877e99d4a871d912e6031c4ca88a813ff5f2e9c75733f29fdf3d942e761f611625eda73b5902e2fbfb2c05fee4fdee69d16164c61299994246bd7737905254553a3c368d10d1d2b982d455d2e76a4492b56613797943ae8a95088262c23a94e5f9a42e2af33e3ea2c2d05d3e94b252feb8b422259758ab8bd408124bb4edba40fea53fe09a674314b926c277095527487f0f14b13cc2656742cd6ae5a9609860b1aaa4f65848fb3b6b0802598c3cb271de4eb93ec4a30bee59b28426c88d1a62474399ea1e90290505297bc69c9439ab5802318f3928909cfed93928c6038b3f8cff98b6048dda5eb927b2298820a226b5c12b1da1e8249567cc9e593ead287162004931e35af95b907031193456e74c0309d9874e661ffc220bc4e47febca4ea7c61b6cb23ed428d900b61f4c29cce4a5744cdca5e7861ee8bd61d17f35df8f15794c812d68549a84baa77252717a60b49b2246dc28529277b95ba5ecea2ea5b9892127fded52962a98a2dccab7a9a922b5c0bb34dec54c14dbc63480b438e53114b446761d2799de593c3b996b228ba08eb148e852a13643c448717c1c2bc13b322aec809dbf815a60f4946da89743922ea0a5365bb9c9cf89c2bc55a61aa5211b34e1d46c594156978bc8c972454d7ab30e54ed172c98dc892a20a9398e0ed274723473415c6f6cad3a67f54186b52fa58e74e61b214bd6db9f4fd2a628af7de53bcc25218e5ef629e5d69e47c92c2789f5ac9e36514e6ec14bd7f3d8ac294963f8c94a82a59140a7d4ba48fe381c268ee6945825be8b57c828b5f732967957cf27ae21e2d173d24ffaaf0e984499f595b291155173d4e1884458be623b409936e957a50353bdfad268c575244c676eadc936782ff2a516d499830a9bb49f95a5498fc12bf889b0ec94410a9f49628ae47bddd5abba412c6f932955a8424fdcc28717cd3218e9787f52661d05177bbe32e6f7e4920ae3e4685302281c93ecb275de6d49030fba85239151fdd6e91b1f60843bec8a62e7e658c46df00c370049eea6147dcad2935824932e691bdab2a8597a0a1e24f55901f7ee5a61d86c10893a7702742bc24f36a2fc2e83977989a7db310175b43188a30277d229e68b1a04350224cc9f3bc696d57080311064f41a9e77ab50baf1cc2f4621e57c3574318249d909594c8dd33b24218b3439dc97a09cbde12c2242c5bbb6ea73cb9a04198b2754f6586ba76d205611256a35f94481372cc0361943c4997d5a5957705100639a264d49af40fc6d1b553f14ca77799f8c1f8b9f6e1aa651f4c91e25f25f93a1f4c49a848496c3c5c2e59187b305ce94e6a4bdc30f470505be16ae4e9f82935100d61e4c1702ef36f957e9382e4cbb081827c4018783068939fcc4324a5aa5abf096e9c51469211c61d0cb1f77cf34cbb8248dbc1a0643c3b9c97e585b80e26ebed1cba5e9e1cdc58d4811f844107931239e9fc217ba553a13998fb23cb049192eef33a0c399874df53437785187799a9851107a3e6884923adee924e8283713635df82ee1094fc6f3024cb101e1e73371843a7f0ba038d5ea8f4af6652612012070402711c045248f51e00c3130010404078441a8bc462d13c0e85e1071480035228245836301a241e18161808842351201404844382502010088541814028240a27e196ec03ab41f74a5cbda0cf38737cb323ed8a86cfd25657810116da0600aa70ce296e6ae67566a0e70b7654604720f737c5c02e0d64724730741c6dd034c8fc50bdd6880191268b4923507cf74d766582231df89f0599770a1a4c373db28b2b0e747984ad00cdafcf08c0997b5e6e6eea267b805f181beeb6aac9539bb444f24f37fc683c9ca949faf447fea535fe479cd290c5f455fae73b6e1d5cb06d98220fc824eb376fd0c1de1d4480b743ac6e74a47b0f15e183e4091e968a808de6f2541c8e584911c341ca0bb86b6c938d8b7c3c2b5b544b4461523983f31de5922592fc69c79a4524c8671d92de4c7e09eead39f5d4bfe1a94df4278e6c79a33ca999541f6dbce7ee7fb86a418d54ffd00120f8140dbc9d22f28d7ebb9f1dee3f4a15f72cbe0681c61d3aeb4ed422a08aff1d39bc1c530481076fad49d9cbd92fe58118611efcd5e88ef6c36e3b08c946c14b908e148a6dc8692dcdeb6d7b5af9ff140f940d20c69f84ff2d9748fc199f7c0bb77bbf8d53bdbeaf305628eb7dd3512db1809933c2bbff7a5fda6e21c36a472e0fb4d899ab41175cdde6bf844c934671899fafe9b6045225aa6d60af4015ccc7662a87ee9f1d9487cfe553ed082d1c63fd550089f109f0a6476ec7a7ad90111def3be9d273a26aa725beb92225aeee3fdc6b4808250dcc5ef550e2b79094d8a6a18a5a039dc6d8357c3774190f03370365a7a63c1c251171ae9748682c174f23718da31b7e8203a5cb808e5808638d4d6dcc72b807b6a18fc51b26e5a233c7511c35f57d8d463b36ca40ebd4c484661f9c80837d1b25c6513ac446e73ed07f0e9d13fd339a4114886b7d702ba65328781a479cb5bf8ce1ec300d47a91f363af189a59d4e56ea9f7b9ab99c439bd905f5c0513afead53f1b3046c811da319a4ac96a56eb9ed2007c14672098d6710e0e0debf6e209b3fad0c9146ff7e6189d501df8bdd8147ca7b8f21e8e05ea2633757f4e6f3966859bab60e8bb5c1be05cd7c762000b61b38b6124703725a8808cc2d040efdcba015185262701af9e67ad7d2d63104284feb64d6eb5f0993002109aaaac1112dd71c4e6e0b4bb5e4d97cd9d90ceb3f0e98260ce2bae69033045f53409dfbba11a06654e95522090e53da0559ce056640975de35c9600f50932ddd94450109a84a94701587bffa21592dfb9e65012a0fa4773ae11140ebaafebe8fd28132685d21eca13d6a10f9ae18ec94421dc56084c35c66e1d1eca59547ad0fc0ec170bca1abe70d078bd5c17ed7772e7effbd63e4f37c520dc8aa5b2a5dd5887f3a93080e6504827ece1014f80622f0586fd0d16ab76c4055f7ffbba49c96edef3a47c98e35851308cc6ac01b602cc3fa2f23a0a7e6a87f02ed28826339280daa37f1d8ef6e28012ac3b7992e2db11c3c4767b0fcd54f9a2def88673a7a85ad3d44afb14ac087e5771d3c87903115f818a8250e062f6d1ad0383886173cf08a56cfa2c142da31174c58dd48e3914fd2ed80b49b531898070f7670907e822dc10d19421bb4d67440104da63b2a891a3ca4d51acf651f3c71e361870a357836b71da9ee74a069a5b6fe107877373c9b81ac41405283634e106755661a13990f1ee43a58befc278558c50436c5fdb45d569720729a7cd112c1206b628641d3171a19641d5baabba99dff00902c5ce031d27cee7732841421211974f0002e3e1cb08387e50c1a123dd7dc2192f560b40032b1c1f30413780ae0e0715db9d5c76af008ef83c6ed0f548e6a0d9ee7a387811d6507cb7d0d40fdab60c7a3f11fc20d9ec428864318099b40e6b307b4583b067722f1f9925eb156d5cb4058ea76f8be871624460c1a0669aa32683c0155cc4212c68ee001f6e97a29fa702c27bb2822a855719d837524ec18bb7678848d649a3dfd5dbbb5ff0073e0de55a38c9edb7b04a6dd12d71b34ae2dde6aac54b5f57a3d1efb775e284666143f458a510dce73dfbbec702304ab4c344334181886046dc4a1c66408a01c8b2d70ae21424e16835246a5346d0dae1894169478c33b565351cc4ca7e176a8e929d2614cc0e98350313f9e1584987a8375820e80054cd8f0c08b0e06505803113633f98021e3e088628c88c30ad93daa20163c484fc40afd50a4bb448f3255450e5a401a0ccca1b83c4e32bd7500e94f16a58d3b472e6c8135694c26de69913c7710d4a8ca034d91f673434fd44fb3e5691d2add2003467379045a5d75f175d3db4231ae012afab4ff57c033658c6d6d4f6eb167658f7e374eeff55ed2110f629d4a028503cb647c662edf598abc8f49fe9b384c8d9fd8c05cd5af0d549d3c550297945fd5da01e83faf6967c602eef7ba0baa36279709254d0da59235a64a31b69feddba8ab7b6a2354f20fc11ebab08f3e441faf066b10ad9b25ac15ca5516837a0861300f507c4e244389afa32cdffd7e37eb3294eedd42b1644ccbc68a20f37635b74cb661cf4e9445865d99cbe342c009b91a06000b63ff24116ce487b11a992c81c9b9db81761434f949930bdaba7bbd6c038d55283df3271dd4e7520c73008b4737df2c31aa1888f3b0e1878b1cf0b921327884844bee5a714bccac1ea25bd7f0f11e1b6e82f1d96dbe6a0d516fe8d1912c35f05a0e75b787ed813de8600d653d9c6b17aef7d2fbf5809de265ff366719869ee80311899639a26019e509f8151c866ae27e19840b2463f1408e11784a253050c4c141771070e9670e5cadc8b03d1c743106f610bc51c4fb0a2262485030b4fe61fa81a9fcdffce8654d55a8bf96213db4db24e8f92428c50414d5f7ea1db0927440a7dc31df51da2554386a7a54a906b88bc36eae965fdd8d8657d007f771fb405e35798557e9a6009aef56e2fa71caa281d2d71190a7f09990632fd0a90bd39bf0d50cf8b1a02981f8f9c18c838a061083a9012486c106702d84a56ae81d3e989721f1561cb282b4b0546640a1ea5e6d8e9deb6e2e06331afcc76b697620892e0bbe5e4b2457166de73ab573f8611a1f80b5d6c422c073900b3116aad24de0f3a0acbf719c3ea3988b7180b8014c160535c3871bc6433615e2ece25ae9c9197d090c624b1fd1c0c0dc0a8286d070801706b12c6e587ff95a8150da4896b50d079916720ae98d2bef454ed81c969944bc4257c8bc372dd3d215fca33f816fbabd579cd2d0d56a60667ef624413aa08a31415638ea0526713b34d30661cb537620f9b9eca9e0ca09059b34e11691a16421408d55bfd206f0b868ebbe6e9524a87530e7eac8c8f0a3b3d3c0bb8ec320c000eb4b950a0f5a89df0e3def45b0507e33fc65716c7f25654279a5fc2bbb791669c662cd9ee4aa39ce62389a39c175a76af872761a0d7d6ba436037aa1d7973c62eda5b1aba73eda03bc29ad35bc4e73cd0d34fcc2344ae4f6ea2aa4ceae9b1271ca760f1dbc0a03bcb7fcf552b02ae762167f9c545a6a41dc9e7235f35e42152451c14d5d2e2473e2bb1a7663b038cf79f831fbf1a41399f39950df4cfe176f8f416ac5a09f7c098a38c98b3c927aa2fbb697f0b31de0df2935f1839ad316e184e6e2be53b26e8ecdd88875969f73193c263885ec0f809f6c4fb0f29ccddc163e0a369d4e211774edd66eca0ba7a4c3ed95a8ae40cb70c1c693aca14c43ed386b24c54a524893515afc1fe2048b2c806b04fe8bb54d320e34542fd88ba4066eb37e8d2dc3d1c5c0eb4aac5923d9a76f3f610fbace67f550b5203e31a6e93cf4aa2ceb2eb429338628bb8c8d74bde9079364008916d853126280f980d9dd74e40d3449a65c58bb6e46b691fb52791582cfc56098ab40016e046131a182c29043533120706c40423b3d7a72b4dd417623d49548ae078f503e39425e044db78c0b65a8d5c12b5968e9c4897ce33b75f273e1bcb07ba86b3680161be25abeb05cb2cc30ea5db9a3ae57f41bbe72e94f6d8cc3bab49f93700c6dee1444990c542a8153af88f10494ee60e068957e8b5f945f483b67ebadbff389931e049939a0f6dca66cdbcc92fc7af95ad9d400f09e415a5ddfaa0ab5b495c9848644c8e68e964b84bcd453f28e142b58ac7795d12f3f0f47f20d966f0252cbe851e5e4eb152a546cb880600923287414314b9bc651bf76675cb6ad8129fb192e57bd2a386935e1352ee250d3c45bc5b02d53f201f0dd3199001ac0c98be3486af99261294008cbf83dfcf5f828e8867b750a5a3f6199b715c2a3b2e9fc78cf446b11896e53a15d74145014079aef2c1db5058166d1039dae12685e5f4e9caa0f973d08ff0af43e3310cc9a5e573052ff7610ee045b505e49106ee9dba0a4070bb03f007f417c967e3c455356bbfadf7ec49e47a40c2852004a5ddb1ca97bdc04e43cf650f8ea68bacfd829b8c90f0c9b24186e76fae090eb90a0007ce265734afa1281e43bc97fd59f05333e36138d5b1e2f802f83d18091015291935b2a23e02553970a05e55fa032c6cd4580f27fbe48db17a7788e5ee6f4074a331de655c63453a71d691f68a42db119e9c977de2b076dea2d63df8373c0090794bc017fd689b3bd7bb3205c9c8b9b60ae44a9c961ecb7a156be601ea334a873a870dd16fde4c2239bb8ee4788ca3b839c18cefa2c59b21f019f0b37e30471098be5daf339f81e07688ebea1e0a691e6851d422d3b74aac8d33de2ec6894006daddeb1b0448d6165971ee04a4bbbb7465093ab6950bcf93a930e7c00f8356e9d1bc67da851e07b79e39a579b39caddf4bda6333dd9c46c2a72cb2b034b261e209c301231cdf434b7e828f2c7f4464fd6cde242445cfc43c77ffb02800603bbe3c6fa133f04203b8a7d5bd1cec0efee9dd082435a3fc13bda89336040fa736b11e51fe7aa5ccfc20a6507c42bdf69fab2221e1e382593c02adcbf45b5add7a506c92e9ac02f51e620d37209ad315b59ef012ad22d3b3bb85ae67a4ed1f1a4a12c901c2549273db085365ce1f769958c7b0f1bc64a66dbe8318deabdb8986dda60f4b6e648a3dbc21263225b8b325402b39f406b7d23937360fa309a5bb68c60d1beeb67800a3100047ab552c63c5f777bcb8160ed2d0bcf74bd604723d4fa42f45b13874e9e95186c43835b3b80b5b1028acec69d0f71325de08d5327e9d8e0009896f340190fc7232b2991e1bfd1ff4133eed2cb74ee46c98f7b2a170847355a911f9cdd4c6c13ce38cddf9f9da12caac149f750c72238ede31641f0183f2e2bdc266a4f1823b01a888fa2f31316897220b27bc7461daa80cf6fb489f53d9f93ff6482e016258325523d4ea4d421566ed4384a3db5f5d987aa9401250734f5b7352f0d87befc1dbe41c92a4101e73a0b485923ccf99520071d634a14a8ae50d44d118de7a11743a78e936444da33f07ce3fe6a5bac02c14ee24de0752df349db8d0b2c2ac6960b8c58d3338be231e3a0a641c3a8326240b4593cde9804adafed63a74f0fd12b1a9ebefe57a445a814301addd9afe8522aa3f6f7206153ab1e480d043dbf52f9057adc890c5942f3801b6852d1c845a1a404a83efa945e3860990f826ed5a85574fe5182748320a55046810e52e0494a23c9f83c18c71a462c071b3903cd222be6baa84c8b9e44de1be90a54b6ddec20ee637e4e31337b734f05942b1e473d587bf3a711b1ef22391f2f6601cc8ebc6a92d684e72b04a1c99113ba37d665d60d8969bc13a5ab1da250b79960f23c0eaeb799deb659c22b917ec462a4f3b1a2b332cb16885033f106b17d04d12d2821d354305e6d04ad0b3bd3fe31488089727e858e118a5882e59628975ef7ad0529ad1d58b22b32c00a852d7d66b26225fae6fd03791dbe0992d2079d71b5e93972423cc1c3b2c4750307e12b136b6826583a11450877eae119c3ca6d26d6660276bb2a2d51445e14743b1465846471ebc9a8820750ecdd4133e7956a89034b7b6b427c53840f5ba764501366f91bf26cfcd4f040c261466dbbdf1880781125f3c2a946a936a61697763341490cd65e0ebd9c712e71224408a6210b7762fafd5faef3a9a5871585a6e94033c199d77f8e7989771fee7191990da6b5afacf4c3f5c67e0fa39af96315f44d15a02fd8e7cfdb37831e87e930a81e882b293284689ba1a5c3e7126bcdb4498c5b045ea120e4abbff1b4eb03c1799ab56e1155ae0fbad636244d1a9323b50fbe84836e19a98e52b5a490432a88d2a294441f4d16d1ced6ac6e02040fc82a20cd1b63b2a87f13c12441a199da27b8da08a9822f645096078ab13d8ba83e78a970596418829f786a24274aaac14ce13bb6909a26a42ef5ca50e4af0810e51f85df0ae9e7e0f9a9e749fca9f2a7cf816abced80cb722490ce22b75e441342ef96ca933641dd02cb38aacf8820ddfd85b9385822e5bba7ae972a817df8a66f96db2666e915ae768423ec88f188968a321d62a8178964af08cd2e104d774a34a87c4d4c00fbbb3834cd3a8b2626b0a015b393137efc39fd82249d0b67ad09043473ed2316bc27c142f254b0edde9f1805ccfb2986eacb51f67499b124b08405fe7f824b6b39651a4f9d9ba8753a5b525b5a8ac00b4ee70f035ccd38751480f8833a71680278183e8aec9d776283fdcf445b71abbbd05a6896653bc7d651ba5c680841eedc27a3266ccc67411c0b458d67f63d5a6daa13f31b62eb8b55f5c155ac30873801bcda2c0c6726a64f54527511838329578edde956aa40ca0d8db018d20f5a86401b8f2f8d5daaf7edad1667e97da463b3451f9e66606912225bf228fdc820ea3db5d6baad7c2905e8c22a22ab237645cdaef6db8b36dc9dab536a4edb4b8fdb24f3f250daa0e47cf7924e1a51b890ba5d81fba2d363dfefd2e90bc52a74320796f53465c2683f8be470ab5b2739c76cd445a8f2004b672115ecb8a44c7e29e2a04b4b5c07a8201c512357b4297056ba1eb08f706e259408c3fe2778bfe47e5cfdb356dbee57a0b07a26938e43e3c81147328375778c26e7706c74920e3f8c416dc7fe525b4b781f7de7cdcfd38649ffc9e71914596cc2a1a8a9b1ca11d31346982a5e898880c5dc092585f19bd2d171811440a85995b8fde6b4b1646a1e6557e3f4c95479690c1357fda02fc43e872074bdde2f020ab9647e6c9210bd745e1fd1f98974459fbd8cf1eacef6fa9b5513f524b8e203631c4d520f7da9f0f27153f976a125374edcabb5ee2b6024c33b19fc9fe2bdde45f3619a50912b174cb0603c186e61e065092a62f25757e7a7512276899aeea41c12eae45a019dcf612222d2c1b0d273a91d172cb93288c354e1c76293fcc7d6058169a804abd08557db0a5ab1086082fc9a4b7a74bc324dc109486c8778003825dc43babf0f8f04e2df4c69863747907ce3acf49944c82032de235bdb5a32fd364e9f13812a59aa92c1471b97eb3374791cce52a1e011231eda1f1b87bfd4b192eee36f40b89ac4525f0803108f2738964fd711cc844025c304b5d48020616223bdf31aa4dd557ca1931068551223374dbe4f2668037d05c35847884055e3ab5c152ce466865d15a01d3d5c7b02f2b1c42db858cb49d39143c96426b9a6551b5b90949f2eb87631757679534d21a64ddc36db3dd172c3ab28e378646efab7b6562cb76fe7e54c62ad2d6f12e008c49df8e2525a89169d3f409075e6221e99a93a6a7f463960fd47524e9954df4f609c1ecbf7f23e546e6c04dc4144778635ba1794c04b00af52d6d32684898e9317bf3b460627abab17cd42a055b28fd6e2d58f5a9c6df5b70d09596a29272329197c07b0433454bdfc25c02934e00115eddbd462e4577f2ceb67c44982d2c2b84eca7652f46466548bcd9fb28a60eb0f2941fbb67b2bbfc6d21be3379eb37bcf56c6b44ef24f50861b00de64cb3d6b0c060d955a50f5582222eb2802b3ed1de51bb8af2867d5989c45a92e7c7ddaca24fa404690d4bf5dfacfac2a22c0b7252f2e57fa74e02eea5a1b9797490ce100e487ba3bb892dc693cc60c27720aabc9e8057dd093b5d4d20505918635a1af736eada7b781254afbbd4a94c720f1f73e786d85e86d327affc96c1ceec4e1b28c70b113a61d5a5d15cf859471f435ec632113db6421abb55c6dc95fc8740acdfbaca88f5f52cb5bc31314b791509b1598e3773dae0ed3df6e13fbb43d6b91a1609c5d2447dfd4c8032d083b921e4939953e954a1c63393416d3a1d83708a9cabdd1e34b7bc53c4983f97742da9345aeccf5ee4f454e77c883d2314b1c6c65c2a926b63776e5f2b2c95f4d47ffba37c9b6f722cce51252cb2db3d87c35d2e5d25f778fbd950aa35a99f94556eeeabe5e60212c8484b072b5881cf2791bb6e618d4a407ac5841d1d84ea0053f32eb4d4e4abcf8dd8adb4b8f7d8e4122b4ded650b64245d8363663f109ed85315872e1c3c94dd2fbc6841a0845564b617d80697d51267c3827b588a48754838bd51abb13825cd36b931d0aac8503dc661e99c20946f185361dddab351e32385fa2ceee6c618b20a4d58cac8efd71478dc3821f1b44ad685a1a1d05bfe0087ca31db53adb69e9a4373a72901da6b761b2d5b04a470d90d312607eaf6189a71e1bcf7f79b9a58cd4a763a225197c3578f2f79646cc8b05c435740658e9065bc7343a21c4accf676c40cee25ac331941db3d2b6e27b3a44f56a9019e9e3586225ddd1bc29dd9b1a29a384e151a30ead38fb03236ca6dce87d46081ac9c54ae9254f70d22773c86f2b8a151e89f9c1fcd37dae479dac3207c03324a6dd0a65bbbbd76482a02eba30b02f58699f2cae7246e94b5e2a98c206435dc03cbd31989c0a83292745c8b5c57ff0749402fc3ca04d964d99a6fd5797b90ad30eb9b7b31f30b9fe80e2e666475596f17709dbbfdb54212d0d1a99f1209dbf446610e7b7f15370c45d8af465fd70c690045e0540c0500a3b81f489fa60bc869957c5f51f10e2e3d38beb1f8cce010c5b01ba2422421045219cd85c8ff667c460c49a519b94da79332ae6af53c562859499f8be6ed3353721e334b5087eb2ebe2c1114d9180e9779a9d3f1ca20477a6ec856c423be0232afd67689ba661cc549e08f75ffbf24fc42d234994e66470d1ccf7c48f86a1b48a81c4d61181019518afefcc281180f5d19c45e3f59d138f45c6ad546715851dfb6b8def5c350c277d8af1b072bba022bf1b1f7e1f8806df8ed2d6f6dcc4b30fb6e1700ffea230b5874bc89e9102d1629c0056438c1dd1e08c00abe1c62cd1e48c001535326ec0727683cf4b60bdfaaa56b9f5ba5064c2973f1fc833b148588970712a192edd4ee483f698fbf81a6fac471423c4b97aac52119ae5af7846a74f314cf587c0d12466d756890513df5c7fa9fc18330832df9f8473bd3ad7c1bfb6e6207ed183e4d015c58b5fdccdcdaa1fe1e0dc8b9792fe1c8c5933d91811967c0fbb31678d3fc578f5546f90389e232861b30088774d8a459628238c8554700599994923bbcdb7137eedcb76244b70f044c59f7e105a3822bfbd42e8e8b73b93f2c54cda5835738b51af0782c0b50fd6464177b85e414aa2b50071e2949954e43b0d24711abf56d6e800ae6cf8231d20a8e9ef672128b1fd670cede931905f65c8840b4c0c99d70ddef68663272021b8bb387ff5a0d7ae1b24ba65ab0783ea0235f20b54bff568acf1ec016e7f6e4cf6babb5914c03d55299e6c6b84bba1e92d4fe9254b982bc3f76b53c2bf3a007a6be2e3d18c0b3c860392acbbfdeba93aae1d1957a59f1a809784e3a791f428a3fdf6183f99039506c64624ed6e74f263aa7c19e14d1dccd51635f5e004343aef20ac11ec979936715701a820f9b34733b456e29e0aa83e6d540ccd037f7eb10c9620686ff5a27ac04f2a71271908e01c6f6a07bdb7cb16d84d8c1c7a18dbed310c2610bf5695c3e95dcdda912cacc075c44714d4bd0a775f65d6a69ffca7a7e3977890c910ed3f27fdb2cc51605f7446d9fe5a7e13c48c921b2f34d70f8e84469e6d9b43b4ab2854a1caccd81ff6c2e0d963dea992902e5144288d16d0f8188d01ebf147849a7b1e9b2ebb155f8f2902cf5a7f4cadd768c25fb907b97319fd7e897ae89caa42d9b6a8c066fa17e3b3235587a0376fbdc7bac3afc35c8e16cc58e5311ac5beae69e8ca7e94100268553f4f76a5619f0c8fdfa18eb468af8d51b8928dd351ae37813c47afe73c7cbcf928c808df3ed395a0e7e0cafe003dce4eb74dd71cee51574eaeda133b1fb0a3442e710a6f06ab29e7f33c0b31ca3e3b41094bd4c48548b8cc1b4259c8330dc00a3dcd8ae70d0a77a6669348ca8638c116ba91e9658d3e85866f77bcc16400438b68a58707ab1fd4b02e53814a3c7e2ad4542224d5c597e5842224290b9ea7a9edd8978a8c8fb94f85f837ed90d075b7c04992a167b3fd3e1795b97d87c3a225961b1f4de9d41b6b81f11ee6963a14bc8c048e9e54260adbef3537cbadc8df12b7441ae6e1aa85a58130094f186aaea06607c1cc3d4bfa06ce304b5c268977b03d6416a6748ad3a5d9d4ec8fe94ded80c0a2f0d0cd3d052a69b9129b217819fc2c4150192bc729b5edc0f7609d31a936365ad1f306a6aef0d892ec27834b9ab217dcab7b3a82fbb2dc5c530dd7018b2e102b7ea288afd850f1aca3927042afb6c69937ba552121f5b2c9a78fa21321eb629054e83eccbcfa490ab27717d435126ef5c9eead9964cc3376221e000aebeba8c9d3b3b041be98e9073aa21758b5ea04d0832f04fb99377872248c54a67ee7bcbe82db24a0edf465d8654d0c3ab5c41aca66a2533c05496426e11d3c658e0a773ef5358348c3526acf586ed06d857a2b2b19044522f2811db6f5e35f22f0a1edca8c8d49ddb32fd47443d2394f556ac9710c510ba4d793013f3063c7e78973dd0681cd02a6209a0b4202b596720e93945e4ed1ccdfd9490327a9a90d7fbc23832472af6e420b18759fced10ab618baf16c4043e94d84e296d3e5295fdeb09fc8ccba2dcb41274cc62b9af00f95a0885d51521fd9727c9a24ced543ab38e436ae80ccf88c4fd2ab1f58046132470bb8058387e098aba5b9361a140c824c8ef3ff12dbb787c9d6b612eae4c248319c3c865dc3c913a47efddb5d202d61f45f921ec60513803fc70afb50bd64777b10f7d649554eec1335b980054a1beb72416d78a871fc323589a6aa87e89d07b6f282de5190cc753336c96126820326dc6755b92e70ec41ffc6a47856c204223924c42f7d67ffd792fcc3889e63f5c600258311b93510ee600a2636b2579357ddb745b8b6489dc1ef29703ea877eb23994ce8afcd7f6e66999f30a06431f46e14e3e5c1417918ed5464d2b723fd0c527ae1e8b04e8e6f1dcf5e2b03cc89088f7271ff5317d2239d87f25df731f2c829d2c8c5e57912ebb159b72827ee8389b35f75f29a6ad16fe6e16c44b555aa9944dbc96ae0b1604d59038216957ef4fa29f452b5b27657431526f66125cbb6c2a03610ec9b82d0147c6cc12643f1143a270b9dd52d57335adf404945093315295d7f489ff562949a17b3bf97e1c08ea044dc581cf06d94c156f831e137e0c928ec018772da783813678286d8a0089c0a3317bc723dbe8bd8610a045cf614309e0257014c5b4cfc82688b70127d68400c8a3305523bf276903dd471a6a6da0faefaf07f25d0d97c713c8c1770ff55602d7dac5ab097b26206133af8bb155783333144b45dcbfc574900268996e2477bb7d7fc3b8171a2ea12d04a465af84e82a42f53a555cfecdb13fe2526bffaac5de2197c3a5dc993ebd7c9c77f14b3e86bca1579f2eb38a490b700e7db049e685b4bf6ec9e23fe9910db357521408aa0978d43b66ad8d80d075f49f89147287d0c3089df397e27dcf21f70f82567237c0dcfce6b85f3e3333d4eeef15f53931641e927830e7a0cc89c5d11008edbe92f904218d371beeb1b4f9136828205f1cb710e29b8115da70396a815cd9112791a240742ceaea4caa9a962e5cdc1a7f753c20cc7e89026850a342758d04ee47b4814b43ceb5236fffd5cf328758afbb723f92557fdf70bc16b3cea036f2deca4f4aa2bf7a1576b33d79b110a09f6b292885bccf7c6805e21b0748488ab60025184a53a47bbee1fda045ce314cef2e9067044434ef088a12b3d6c1ce270f2b378ce9436736e21b38c92ca8244ef3ac14a3af6e5803cc8d4c81b0f3fd412f3ef668e34ed585b62603322cef5bd8fa79aba174a447c8f9498ebb8a951545a2a735e0662bdf7fd29cac541eaffde43b21c429ffa4b86ec1725d6e832116c3f6d979641778a698848cb70ae22b03b26abee697fea238f8f6c61649aaf73a199e9d72fd3e4d4f126a17c802a286803884131fc5d0b14e93fe56586d542ab511f6d0e762e83560a930361e55fd88e1a18a7cd2120fb875bdd120b73f3161908119fa0d709f304f9d87efc53a2ecab3f9055bba5a70524a91cd3e0ee610e55bf8be531d90db4d7d949ad615151ae345f0fa522bc393c02a9796d7bcab3fedf26f1e1662e319c12173b200c6f72cfec3913c7cfc58c395d3b025a6ae4a8135baddcacb538e195a2b5750f1cf7e90227273684425e01d8fc67c35dfc8ea2f3d768c3122bff90187d207738ee4d758b776e53cd68da3118d063a1d0f5bf2a2234969fe0d76416fb69693b1e6badc9160d6b36dc69121fe2661911e704be85a8ee1c6d30bed5ddc3398bd76e0180674c9aac92531dc4ccc3c3c1ae721c152258507a92cb3dce4c6668fca56ccc6348c7cd6dac295010453725ee9694c2ed040525f66690b823115f13838bf5031d7827480bba37a936e151d4a45215c147655c01b5ea6b2cc3928c21dea76594baf491c37ab402416b2a244c99b8ca18046c01d22a03828946c15246708e70106ad758347c2f6728d04028a278c5bb2fa491b30737aa950fb2482bba68b5c12a7435a515a8e890088c4aa2a615a804cb91d968af8a7a82ca2c455794fc53b357193a46b1ea1adfd640a53250b7255d2eeaba76095e6744c7463c031de7330c3d83434d8553522cf484a1c3a19750c5a17a5147a51e88f5c49183cd373afa0afd1ceaa9a3b094f7afcb701488a8655431d4b9a827ee08ed15531e869fca16a84cb08cff9f241f4ba8f0285f65c42f346ba943ab6fab32d1a244b1652b6c987374c8dc11c732ec5ad6f2fea1b02efb1dd8cbd1cad184519ce80fd09c1b9db7ef07ea3f7432a999aa46d44d422bf2c4f9ade8553db582f75f8fb2e47566af15f565b95b560d37a9a72e109848ac3633f5bb2a63d00c882c1a059c18ca30c54ba7284465a04e75cb4a8c8e4695416dcc018d169f8a942168a6d486701d30f79b58279cb5db3c09a632c18b06369a218be336273cad4b0fa10a9dc27d41bc0386ace2214127f248d3a4f32149e19d6e89dc6af5f07cb95bec3cdcf3b884982df87ad8609e354a6c08ca6bc63dc526c36e88adc6c68645d2d8ea6192838fc62780eb764ee5cdf44f346d2f41cf5ed6b0dbf0b46dc734563e5d6730a2d92ce4bb2a5c2a8e601923ae4604a3e05c13e81eeb227c5a918498e897caae8a611a735141f7b59ca22138fce786949d91d9c831b418506c1ca03fc8bc5c0c63da50c17a462d56fd724401c0d66e5c85d05a5e48ca310c2b4ff902402288e3a3f6030b26694086c9ff4c1f0d4e066ec3024aa10591d94008b37af412a40d7a0c9814b8095871b0d817a845a4831f3af5dd66be611c05b01d7977c159a1431f0dc1763b774f348c8326176d47460d8c9014bfc7263c1f32a0284e12c6a2b17e64a36932d79cecbdb41a4feb715524f2011b14790be7073739ccc10e38335c4566fc709bb14a447a85e0444b9b597196626524361376c9ef1038ba6cae039b315e6ca9711067e837c3661bfe0a721bd86d2cb61191d80cb6c5d5b9bd91136c399ee3d8b691d98d65e51887534f6c6677d6be0b9dd88cc9a128cdae8d7af166dc144cc86ae5c8c6b46caa02beb045f90d3660b0a4900330333333333333333333e3ca4bfde37ddc1ff7de8d60c232aa45279a9292921209ac35aebce01de7e01dbc8377f0d67401ed0da10d1f0e4d52abe0757486b10e62c41ca48223a94327bb321b1fa7e055a63ce1d2e660724e0a6e8ebfe737785c565f141c4b1e13738c50991e149c94426d5585f6a5ee098ea47c5b29c7098edda4077297396c821f2ed492e698473a98e0b57d479bedef79094e698ae918f197b1129ccd6695ebd2243829e68b15296548706a3e6bf050691b2d3b821f3ebb9d49eab09fcc08cec713724cc9f4712c5911fcee30daf6690c11bc4f93de1ee45552ce109c24b143ffe8393cfe08c10f6311d467d6233d4170e3fb6e36eb2b3b0304d7b3cac52c5de59a1fb831ba6a5f7ce085cba91a626726b7f4c0b188eed1d5b47a5978e0a4c6e6cb13d9819344ea6732248f611db8793a3e87b4505a7e0ebcf6d0217ad8e3c0cb4813d66e3c546ee06d9eb7cb1f04b081ab5d9eaed2b33adab5f082d87f94ef83acb569e1b6c725211d3c0baf652e4731480eb46659781b24180b2779e6c95154d58c312cbc489a7f2bb2fb8faff09225c9a1f9871c6deb0abfe3284a9fc754af6d2b1cf97854438ea7565b56f81a3de4f918415388aec2bf64112d4398e081aa0ae762aeb8acfee1a334156e69baef49bf7df944859f3a948ea297a7f02579a648ee5c1fc4148e678f9e2d867cab580a2ad4c986258f144e942011fd330a5f2ddfa71ccdc7cc88c2df509a156982c71d0a377687f7e8e0592d28dccc21dada625dea3ee1f49d947ac8924b9927fc8ea12558b020695727fca05eb3a74cc509672285e718de849325a7d4d392ae929af0439248513535d57932e1a71c4ff89c35740e0f263cf7499b263269adb984f763ee36d1e953ca58c2dfd80c7fdf1e5fa412fe86ac1e49ce9a630f4a38496ab2cf5c26e1ada698aa6221ea2b92f067b3860f5f8984ebc9936d65b5ed0f020947cee6db72103d6df2083f847f2c094f911e71847329dc86d481a7a46984532187240b33c2bff0d03767c851c72ec20b29592ffb304554116e064dcfaf19bacf4f841f7bf0a0437920c217b90b73c1e3104e6a5bd6beb74a1343787fa163f3cb66e1e342f8d32621dd29ff84f0c2079d6254f6993708af3abe93f1141fa515841b358636cf5aaa3981f0ca3c27bff1f3ce00c233ff20d774567cf40fce775027a942747cfac1f50fa66346abd14efbe0fde5caefb1458b1c3ef871920d8f7b0f5eb7e414ba23f5e0556bd01a3bf3e066a4d57469ed2d9c787043dc6f23fcc776c13bf81f77297898e2b6038d29d9071d07621dfc4f39cec17d460acdd1c1f19caa3e883707377c10739a433e084f0e7eaa34ed992da5b7581c9c34ab9eb482e58b191c9ccf9c2db25f8c31526f707cb2ed322d37b83982abfbaa4fcc698333f39f4288f6d92d1b3cbf98c2aaf755e6881220630d6efb848fc963560d4e48993e30b7ec3fcd64a4c19b481552485ff5c6340c178421030d4ef504dfd8b0ac40c619fced38168fdfcdbd449366011966f0524e89acd441a357b803168601a30c4ea4cc1f59c425bd5eac066490212f937073e943c618bcb4b8decc95a1ff3b2eb6bef0a28b2d3220430c95e6b0fea3142eb68c8c30f8e21ea4b4a9089721cc850c3010c3fb2455fd525f30a656c6e688be1788e497e6735e0b97404617dc0e1b724c4939d892b8e057e7cae361477af1e82df8ad193faf355ae1046468c1ffb19c3286adc6908f0c0332b2e08cb77a78cd1d7e230b032fb20232b0e08590c1be821ba92e68f274d1257fc8b08293c26a32976da4bb8e2fbcd8c2aca08b14aca00ba4808c2a781f89a7d45e953cbe80046450c1df4e9273e8925ce00432a650351620430a2d230ace5cd0147a26c3c5568d1a99749001053fe23f3e5189b858be31c87882d31756db2d212d4b8e2623c87042d540818c26f89299cf238fc2cc4dc6c542c074f1850714d72083094e6dee4de7d31a1daf8c25f821cdfc440c2a5143274309e590638979a2e3586720230947e6f6c163acb83a073290e06dfe9c25a40d1f117304c773f81023a6c4b88a11a86ed5f4f4be087ea8b6cf1fcfa9559808debb89440ec9330427cf769c329a840bb1230427b84b47f3b17e529d20b895dded953d40f03d677498f0d19c477ee04555e9fc99c423f73ef05faa8349ead1d7647be0bd664fd61ee450226878e0a4c8a5327b074e86f71073c4e69a4a077e900d39c8a164fb2293037f656cc35598780f0ebcaa8c0d9afd22e3065e4a15edfd4b82e71c45860d1c8d1e5f4ff239f8e8520b377264c4bcb1ce63b2d0c2b9f6d68aba6916cec9c7b45923a1a52bb2f07290104453864f6d2516de7c8e3b280d8fa25705168efdff6726af8e1e79857f96a5239f9493d4872bbc1e4d4dfb186a3d6e8567993e9ccfd979b4125678eddee5a1a9526125abf003adcc9a93d554481255f8697d36e498c35a8224155ebbc75a1d9ed9c1ffa8f04c730c7b4df6e6fe9fc25b93e41d95b973646f0a7f2cdbecc77394c20fb12395f0b1a884b449e19ca71ca588ee8d3b7b14defd871134ea3ffc220a3f720eed22c77328fc10ed339c87b1fb9941711c99c7c19f70433e881e8ba6d58678c28df6618794365c6ad209d7cc2549ce0ed207ff72c28b5531df99d4a3fcbb09d74396ca13a2ab09a7ec2a64dd0489566d26fca0fe3ce68f35cb6a30e1d9bb745f889472b0f5126e1e0d9fa36bb58417251ecbc420e991a795f0dfaade3e085f29524a09a723db7d32fa78949293f0dca26886895312fe69ae0c1e1d44a66846c215d90ed70e2b64c925249c1829ddbb33a5922e1fe1a51c87312186565c948e70a23e78e9c923252936c271cd718a5ad4a41c898cf07d653a74e416633b5c843749b3e414b25d21a1225c4b216ca203a99839897025367ddc610e39db7388f02a5d468a9eaab4ce1dc2ebb40e54428721bc1c237d20e139469314c29b4a3f214751199226841fd65fab669f2c293908af2e0495a0412a624e10defdcf0709afd61e7781702b4d88b163f848313a40782672f75761d2a4cc1fbcdf902b85c5f62d8b1f5c8ddc954f34d607ff438a629b35ab6ccef8e0c6584915d6d91efc389e1cba87b3ec74991e9c98825d5a87fec9c195073f84e8108b1dc777b6f0e0e794356f26bf15c6ba83ebf669726059b3a65776702d6cc2255955072ff769c7b9a347e242073fd688ec40dad7d3630e5eceee961e678b62b71cbc4a1a25a925264b8c83f3f663f963ea8bebe0e04cdf5f86fb4fd5aa4911e30d6e05cfa8e571109f330911c30d8ebce6ded0b8b4c19f2ca99307f5fd5f151bbe10ce53071d522cb9585d839ba14722db5c4c88a106bf7c443ab6b7480d49831fe608319a79ec65f5e130c44083a795455624bf60887106e72fa9484bcc47b61dc30cde799aa7e89872989ac528831f2d7325cdfeb125916290c10f324d6fad3706bf277ffa24993e77c7108317a1e53c36c86676334618bcf940ae03cd124cd3c4781c31bee066180b2ea576c966e20557d5524564bef34f7e17dccc79429c489ca70c73b1759683185cf0da5375852017184c83185bf02ace3f36cdac11fed1829bb53abedab6919c3fb64819c4c8821fa2e2be3d858ea7432c3897f264df1837358f71b1750557d42c64a4325bcbf62186159c1c6677dfacf6f5e657c1dbd8398e3368baf78de162cb84e1c50b900831a8e0df98faddfb7de0999c8217db248449a99e66f2c59182638b300c181d460c29f8fd3972d49c3c59c48882575953d7696da9fd5070d26a44e592cfeb63f182055b1ce58a184fe8b25648bbe49ce0d5f747e9552fd61a89d104275eed82cfa4907e7ba18156c46082779b43878f30cff0314bf0a254f5775039281b318612fc3084075feef9c24d6c8f102309fe4fba0ade59eab23b48f0d6fe55a5031569d31cc17f995eef0c8911344611300611fca822fb3f6a6d086eb7c79184c41cb9c4b5841842e8da73bfa46304c18f4655e5644323463e06100c125307296ac80fbcdc71322dedf8e08e37a6c5b85c7a40e6efcec14cf43cf02a25252ace63077edcdf9922bad681a329f2479be53843c639f0b3e6385eadf78eeb4371e057faa0b6de73032f8b86908378897677c6b0814925e7ca746731520ba746e2dd2c2b5af8173dad66e224327766e1d4473ff389b62c6e93bc19258e85f7e95aaeb18485571da444b779b5c89157b831d53be6945f336c5ce1a470999c337bc8f05be1c7d6d829bade53dacf0a37a510555931ab36c7b10ab7831c72982c5bc9660f55f83163f9b6844db726a7c29c474af2c72e7563a2c2f73c1fd541d3fc3f7b0a377dac1e3d336b8a536ed55c653e61624729fc6c39bf5d7497983325851762dea8f48e2c878f1c459646f2460ea273bc280c8b312387394a6ff443e1791023e4b01550381b3165a3867cc2c9f1519e34d2e4709527fc888f91d6f368ca612736fb73959c533bc6092fee4f5bc262ea33da8411368410c4e4628b0c03d08413d22f7d1061fd539e4c381f7afa1c652dd9670c261295d26097bf84f3398a9d27ceea52d2125ed234c135bbd587a152096f43fa8fde424ca97228e1e7d618553e3aae8f3209e73aad87906f3b7f1c92f0b27d881ac3a5657723e18739bd234374dc390909673407a17a62e411841b4d6e12e308d77ebd3e8a4eef5d69849f2ba78df0b072dc3d23fc8bd5dae571ec5c2ec24f7f4babab15f1796ccca525c2d97c215b9e17d91c8a08ef3f6b4c1faf5fe6104e84b7dca14c5b6df20de16be5cc8a9d42782a1d45d4ea3808e1f6dc6f068d109db3c7208e209e839298d22505e15fae79fbab74200a89946eeb3e8e2640387193ed347bf41fbc8f43e71cc9a3fd4084f475b91493c7edf5c1b5e47f29a7cd714735f3e178fb38b6700fae87afee20c5d5839f83d89a87b3bb8418427454f93e78f0ef53761c5479e41e7707bf2c2c7c799437c729b583339a3bb68cf920eaa30e7c8c8a31e530c71da3832ff6ff9dbeedd5b49c83511573dc217b8614b61c3c2fadd70a9e38781f793c34724f761e0e84a87cc125f75418dfe0cabd44ccb176f5c92c089552f02ca4bacc32a131c22878193af41c778c1de51c14fc8ce91d85f4b149774ff0c3d08aec0e7382975324f6f6868ba135c1174d299364d487d162821b7294a3e4b496e049fba87c903257564af05245c8d2ecefb72a097e10527aba8b69ec4307129cba0f59f5417290d271042faf5fba8c7418c10d1da607ddf3299d53043745998c670fa34d0e11bc645d15dc21b81e955d730826590bc1cd1965b662d9cc1b0427be83ba9f9b6513089eac445947da4af0fcc0f95c1f7da776b3cf077e84a76fca6678cbf4c077b94f159172ff8407de5f8e63794739c00efca8e3ad4992538c8e72001d385ed6215d8b79b8ca0172e0845039ccd9c73f29e40038f03dca41c2a6567b0939c00dbc288fa255d286f2200938800d7cd91c2b74476ae1d846b9ca10ecc3e769e19dd684c7f9cd3d3c66e14713424c394e9bdab42c9c9e0876962b7786742c9cc91bf75148f9c9372c9c35efe0ad738ae9ef573817c3c71b7c57f8693ac5a887a487dd0a279d44e720e5ce97352bbcdb8ad6395ac50a5985f31f4a6d6c8d4aad0a37668aa7cc4a332ea7c2f76023a36999dfcaa87063b6edaccf1fc7fa148ea57bc8a19885aa4de18d67a57043fbdcb3fca3284941858cc20df691d78c2b0ab7deb447c35068bf390812ab8282abb168159a7ec2f598bbaa247b2a0f4f381a835cf8d8eaf46527bc28293ec55776f992137ee8f7d6482321addc849b3caadf9837849b50135e8478554e294cf62f139e4d5d761c478bd51b269ca834d9a1b2be8413a2dd87d1375eea6a4bf8513a97927b9f94a7ae84eb2396ae634a53c20f52ca42e6ca2c1bf524fcd414434e1d95fa7b24e1a5d4603967ef38b427126e0e326f4bf44f8b1e4838993654f6795f34cf233cc93efed31ec5fa8e23bc10b52e6a348494368df02bb52a4c76480b1b46b8151a953ea3c2ad6611aef4864647b5390c1945f82942fecd914984137a2a6787c84abe20c273b7b69aced4f09743f861b0581fc28518b518c20fe672fc9bb3570a2185f0ad627658dfa7f21342f8311a72c60aa61d4c06e10762ad599f20fcee50bff271a1251208afde526c594b5c0f083f8e393a8f473a4dfd07a7cc3ca60a17c362ef07afdc36c264fae065c8c79afae083a7c9c6638cedc1f114317f242322293d38f741d24abfec6a1d79f0623c6aba089ac3e8c08397af4e43dbb22ce60ebec4beede0fb658ec3b5c73373d781d90ea483db95b263344799369e831f74bc695210e5e0c6d439fabcc6c1dfa4b943f1150eae654e92f9dc342a7d839721d9f12de5414abac10d1d87d9632b5cfb6c83671a39c741d2d431996cf0ac5243541fd7e07465956c1ec658caa9c10fdb614e558fe9bc4b83ff7190c3b8d5d67cd0e087f07392dd3378bdedd1a5ac0e0f33381bdd7bc3bc6570bd83f5b05346062f25e6495932ba778cc17b4bb9376214afaec4e09d057b0f6518bcecc8476390180ccefdbf77c871bee0877e29671b8f17bc145273f061da2c962ef85a23bd31fabbf35cf06354262651f1efb7e0a5ec142cc3460b4e6bc654a9e262684d16885e1d7c482958f0b5dd6247989bf82857d8ae6445836a0527749810217c7b1456c14bde6973850a61b64205e754623a4cade1a932055742beec41ecaceb102938a15fbd3af351705cbeec3f780ebee3a1e07b0e3b753a9fe05cd01056511b6639c10f2ebae5ac9247e99be06594778f3f8c0e1a2678b933640a1faf5b64095e84d09b11568267f769f1ef9104a73e7f1c7ae88104e7fa7344f5f4b1cfe3084e0e3a42bc4c99dd6d044722653bb06c566a17c10f513307496622f82769a93ea423cdd921f892751fe628c71c9b2b0437e58e62f28c06c1f730735467962e4305821f27c96129da64f8c81ff822b612c2f47a10a50f1c33fbc91b246687600fbc4c3158a69b8f5b451ef87d1a6fb9fb8314b20327d9c7a1e7f9dbf6381db831776ba596f9162f075eb4ec100f217b640c07aeab4fe6685bb63206b8819b5fca26967d5c6f001b781df97a35c7e553d7c20bda21244d7972eecaf058c93ccc5acdc2f98aa134e7cd70175938123d3bfe2894e68b36165e7ce8133d473e2cdcb056a1736d7988cf2b9c303194cb755ce16a85bef4c85208dfb4c28ff6917fda9e70b16185d71f555cfe2c4a8e55f8d7b1c74345b0e05154e1ab767c3908396ab44a2a9c9e994b97b7a35454b8f5a9c25a8edc29494ee1e49bdc418839c8111253f8ef963c8e42be147e8c9ddd36ea34073d298a985635cb1fa370bd3ecc3c7fc4ce1ea2f073ecb952ce23147ed0724b33d11dc530289c1ccea3f9c935a7f127fc1017347bc7ed61c69e703e481eb6aadc0937b9698e6cd2478d2c27fc568f327290183ede4de09303a9f50fd584dbe9cd639294cc5ccd8493e67148ee9a63d78909ff35c2574a6dbd6dea25bc99bb54b163094f428eeea38e2cf66325fc28b6e3d764172585126ebabece7c7196fd49f8c1d29b6f4f648c92f0d652847f0f6be922e1479914113465c57490f043aca6f031e5e0ed23dcbca5da97426f089a23bc28a61d474937b3aa11fe878c9d3c3a9ec830c2f30e3e9c644c49c3a48b7025b2cfa25fc44c114ee694e1eb2b89f02d3a8ec73c06117e0c3955d33c840e630ee14a6ad792397b558d219c7416ca3fe410b92b85702b7518c27f74ccac10c20da95f4f7910aea5cc9c42737f50124178919e6adff1658e914038e3b1fa67f324361140f871d47879349dfff3073f6c9c5ffe2d898e1fdc10cd41a3c61c65f4fbe0c72c6ad71d84d2f0f9e0e5a9081297ce93f57bf07269d80e2b7f90cf430fde86ca520dc9fb3f79f093bf44a494c6833f371e9d64b21bb3efe0aaa7e710edae1df51f695e8f3267b90e6eb7664df1618e89321dfcbba43ed391de4a9e83172cfbfc6a2c073fd2ae58173193e5c4c10be396fe29c2fa0f073fb4ed896e1e43a76f7025888a7bfe4e9aad1b9c546fed39d536f86e1ee788930d6e6cb986e6c91b9daec18f3247fd1e199d326af0a312ef8e47ab7325d3e0fbe5b0535a5b0f3e687025ddc392cd61a9e50c8e4d6a0e22df9ce4308367f159b56337ff0ecbe0498cc86f6bca9a0c9eca4756f48cc14d93261e3a2ec570598ebd30b8b93e0a9ed181c18f88ff5af7dc51e60b7e468de778096abf17fcb0a93f66c4f4a1ed821bf37b5cfa7555d2b8e07768161beb3129b305af42a37cc428b5e0474c512aeec982df9192da771c2cf861e63c76472186d4b9829fa3beebb11cbaf35638086fabe0a73877f31012fba6829f72ec9b704ec189704b69a191829f3b9889cb11053f7b700b4bf93ca40b147cad309adda33cc1cfc93d4c158ba810e204a7530869823716b3b2e4ac262d61825b2949aae6ef9827bf045f353a7b234b18f195e066f449bb764bc93992e07de68f526c4ac99303097eb2aeb5f18ad27e1fc17f9ff6a8e41d7dd846f0c27806499ddbaec345703bd88e93c6f441083311dc9a8ab89b5893cf1d82f3eed2df9999735e85e05884c672d70e490d829fb286c98a09047f237bfa851ca4cff2077e58e40c8b613eaef481272947c991e981abed9653a2689685077ed0d1367fb13f0eb2033f567c681b3d126975e0ffe63953150b90032f6cfec366bfb2fc1500077e249d9d255d3e89ae0037f023bf5c3612726d8a15c0067ed8d7f93cad3286ad165e6cb41c2a365af8519a986d9259a7c92c9cbc621d694bcef943167e858995e314652bb1f0d45f2be7e8520561e1b84792263d5fe5fa157ef0dfd3ada66d63b9c2d9f48186fe68ec2aadf0d62b47b0b14829cb0ab7630f2b7d24e12adc8c728b2947a80abfdea227582c9b8e3015be5fd545e5303a8e0851e104fb0efaaa62adc7398537f641ba2bc7b1bcc7145e6dc4d4ea94c2ed305ad424e37711527895434f72f5d1dd3c0a3f4fca986286cdde8ac271f30b17257d6a86c2eb3a3593cd80c29fa9d7f8cc7cc20fedb183af142a2d9ef05288b38b3df127e984177f412c956685ac70c2ebb1f338a6ca26fc186e42c2868c29af09dffeb2df8924136e6a8851e22398f0256d5427e98ea27309cf434b59f5bf25fcf451c75399551ec757c2491f6d78743c259c143cca3a940dd932092f3c580a9e9364f99084ff31855812b6634a1f91f082a58b1e3cb49608094fb3624e533fc289fefb0f153bc24d1edb46f8514cc5a7d258b95e4638eea369c5738e83f02ec29f0ed3be43c7b1ab5584efa16a089dd22a443411ae660e55d4a453f78a0827c4b4f92b26a3c27a083fea682bb93cb5c7aa21dc786bf5ac4d9e2f2d8497aa766df3b9ce4d42f8a9b9d36b3a085f543d8a09f2d6a320bc694ddd51bb2731108eb8e4b8de83b4660908c77fe5d4a492471effe074a8e17fd38c4f871f9c4a135bfbc32746fbe0044f95cc32ed933c3e387ee1de7bed324bf6e078b40ea2a64eafad1efc73efeee8687bcd3cb81edfe6af110f7e0cefce76d91d0ca9af52f6783b90e6b73ab81e5ad6e0db35b92a3ab81d8748afcc1d2e85c8c12b8b2995dd2671703d54ef8f573a38781fcaebfa63c8aa746f703a7a0e2ef391f345e70657d2628769a80f2ca60dce46878e1e877414346c7072868f6369bf64cb1a7ceb0f79de72f5fd6a703d7be4a04572be701a5ccb7739c75f4183bf2134a4671a0b5e398313a33d9121a6ceaa98c18fead0d724a40c8e89a7c5ecfe4af927836f39ce399aabf1a8ff3178926e2ec4f42858ff6270c2fc6d75dcbacc1f066f2a849ef09863c607839f525ef320390cf71c5ff04337d5140f29217d78c18f3653b8ea4bd1bfa30b6e48b19c2485b372990bce59f684a40f72d0f1161ca91cbedfbde318b35af08367faccc741d566b3e046bb1cabc574d074b1e0d55b78c755dd26ed153c099b434d9f90c9572b3861fba543e47ca779f1b0a3b4c20d99a952adfb2353cb0a672efdba8ac52cac5556c5baff63ac3437d31db3a10a2ff664ffcf9b6e1e7ede48859f2de95108d73aef3750e1c5ca1aa9627b8698be53f827d33d99f2fba5e698c28bbba41eb2f3c4985429fca0f597a23b59be5092c299bed8162dd9a370a3e40fda91dce5ba4c149c654578b95876677cc7a1ba22caebe650b829ddff7564c7b61cb2010abf446c638aea486df9849b428ed581470a911896c28627fc8f5bcd42a8eb550b373ae19c78960e82b1c50a0e521d48c20627fc2862c71d346724e0c5c6265c0f1bce43e227c7686bd4d8a270b0a109b7f307edd8983f7a8f63055e8071180e6c511b99f083905c6d5372112d4c50e92932965d1313193652f89770babb2445788dd259870146172940b584bfb221b572b08401461717d8a8847fd9f5b17b4e295157b4997d887a7a109d7b42cbbbfc4938eefeb9225473f6201e614312cef976678e241d093e4e2675f9e28684973aec3a5d5269afec1187f1b87338c29678abb0b70ef70a79aff1289bd036c2550be92b98472184ba1b8cf0e37c1693a6cfb1087f725853eb137b091b8af0673d7c18d357527f4d02612311fe9a7af9c7f9d307416344d84084af6934c2a52c1b61e310ce8c4c276d172f4def8621908fb78314a23c0b001b6c14c2db681e2ce6d01d734cae0e3608e17b1c678cae0c51a9a941f821a4b76c99b3e5941e82f0831c66cc71777ebd06a292abcdaaacaeac7c5e51228df87ff600e1af44358b31440c6697fdc10feeda2b751cf7e4982fb65081f9628b177451aa0a3b60c30f4e4ecb24b12f52c7aa45b0d1072f870813d9fbaa2ae40d3e2053a7eeb61aa6d26de961f4506334dfaefed88d3d781163aaec9f8fb1adc2c596bd0bc0c0aa056ce8c1ff402cb43a0cbfc8835f21e4683347cfc596a5800b30b6b00ae374c10518c9800d3cf871b9a475de1cc3a4f9c25841171f3841170fa851a3460dde2d54e08215d0c61dbe08030c177cb1c51602d8b083a7f59623dfd47182159814a4e0e08264d5c129b79c3938952437aa0ba81eb0410727786be6f76b71f54e7080018651c1165812d8988313527ef3205c7a6ffa2a0c1674f1052939f076112f2257259135356f96b3db448c1e0bbce00d6cc4c12fd390d992c1a3a6145b75a0cd7bf1451860e80aba48c11746058a81116c711b70f0f387db0c39763ce98336dee07faab75f8c1eb9e106ada32eee4ecb23d4253eaf8a791c39ecb68f8160a30dbe7a6c99f6d1c63cd9dba20b161c5b18166cb1050646b0458d0d36f89f39a5c89363b61e8b8b2d2ec070410a4e5a60630d6ea6314f79324586ac1729b80d3538d61dfc88bc5e5ce02eb091062dadd3ba34c35633cdb6e66290b953b58f49738213787101167881811a354ee0450af2be0883051b68702c9ac6a0622f7e1ee4622b055d187206ef425b7cf6278fe09a2f5a00066e6c98c1f7d850219fd4d25c9e8bad93822e0c29832713730af3fa4bea09e38b1680915c8471a80055f05da4003130822d686c90c1dba4314721746757c7e08ac66cfa7d58a11d31f8315b8e223950cb09831f7f0ea2b6e251ee3060f06cc6265708ff3959bee05784201ff786f178c10f7334ef318dfb8574c149e96f51e9a639940b6ece9ec1453ad58c640b4eca665b16ee344cb4e08b5d6a0a92d2a5f6f70e26fbdd7b2c389d992ed9edea9d2b38b235bfa1d6a3d86105e72a778ef139b09c1d557033648d930ace9b7cb470e9297819fee73cdb43b1b414fc9118b2759028b8b6a93f8378471681821f89c6a658988fb59fe07ab86fada8589ced044f52da48f1f19a857013fcd0c346abc87124394cf02d36422563489163096e481fc12347555a2bc10bd1c166869cd2c649f03227b3489535d521c1f5207fdcae1259b53d82e3a5213ab5467053e5e8e314db577711b4cb9ed006113c3b931ca6979cd9d3b3310427a70c95b4729c3aa4674308ceca7fe4f164f74fc9b3110437f465cae1a291f3793680e06d0e391ef994424ef36cfcc08b6079e22323692acf860ffce875de4198e46dc1b3d1833f7bdc23d1e581131e9f63348c45b703e762b4f7adeaa0a203bf62088dd530078e7814e9adbe0d1c789fea714ef213a6be8d1b782aee5d975ed299b7610327b79a4711e9e31baf165e8a0923293b8ed26b87167e471b169ebd269c7666e1857d1c449ab2f02a3c76670a1b2967b1f0259a8ca51c62d607165e0e136b3ec7ce9257f817f239ca945aa9e50a27e714d3e5dd61c76985b72efd59c3c692301d56b85146e3fa833a4f59859bb54d3ac5b8146355f84126c5d87452e15f7d6b06f3a0c20f16730aafc7dd6352f630a9620a5f632bd67df4518a29851bf31e2ba490c2b3f0968c12320abf26b4c207d1ef3f88289c4d3d69fa0c853715bd4dd3dfc620289cb6912c62295f0a924f7829115390a858a9239e707ab3739035b30c914eb81aae1e568c8e909f134e5a86ec3064aba0fe26fc38720fb1f3cd4bea356125bf9cda1f4726fc54291fa61ef1f83c30e1a6ed914b96dcc2765cc2e94b1f65d7b425dc0e72b678f88e1ea42be1db7d18d971e71c7253c2d39c695b622b33d793f07360725922857f9925e149c664f69fce718c23e1c61434e7ce3239430c09275e24856675966b1f4184fbf3924d1de17d14c226767dd49a36c215f54f6da119c62319e1a8c744d5ca1e4de722fc8f467324e36d174e45f841d2491eef12e16f4c1aa53ffa87194484f7e92d857d203165f0107e040b734136fca268083723e51842f028c28485f0936407a9912384ff39b3cd86078f633708df0397bbedc958a320bc8f2aaad53eacc50a849783b07eb36eeb6105083f9c995fd6fcc1fba01a2ac37399871f9ca416aa434da60f7e289363f48997d0c8f0c10932397d58397bf0a37a771817a207ffe8c129cb9d3a79270f9ec6589f6c62f0bae0c1afdcd9730cffe023e60e5e7b7cb183539a3b2454eae0873972f4317d70c1a583ef992a31690ae123998363f9a3935c297db372f03de54aee7d1c9c742ba6ea3d1c9c90439979a455abbcc153f5a0b28accb4ef063774fac852eed82dc7d106ef5d3eb00ff9f38d071bbc8f4e6a9dd3327af41afc8ee73ebe540d4eda8a9d95638f2ac569702673e821cd46839f912c26979cc149e7a3d616dac3bc197cb3dff064d3e797c1495531f75493c1b3b33149511d8393c94672faa468a38ac1491be722a1d3a34bc3e0c9e4e06d29f64a8e04832bee41ff57a4aa07bfe0f95a88d029e8052f7b9691986ace65ec829f6ae1e319f34873b8e0e7cd9888892db2b92d78b93fb5848a18fd3a2d78136361426c89e7c8826f79f278967df96b58f0d275fc5a26623969577022222bf67c58e295150ec248feee5855c1894924a6145eb93a54f083c9db1e3705254f8e9143450a5e6a5289e935240a6e482ef2713c1628b8c96da225a67f7c79821fc6c58ccb92db3e27b8e1710ed5e2728cd56982bf39ce379b63cd39c304bf54ec249c4bc8d6c743865109558ccffa7c26094e8e2a4729a305099e851ca61c5cf9d7468ee0bb472166e690b152c4085e08398e6899c2f5e58be0a73c29c7131e4470f37d184663de48d13104b743c81d875e087e5c21c79572e430dd41f0e3102ec7dbea1db440702d8450267f3167f3074eea891dc5ec286dd83ef07e26bc6358c90c750fd61c2a5a2c0d0ffcb2b90e3a8ea6fb8377e0e6aaeb491e1df8a6ea1f07570edc8eab3ca590c581f315be56ed0dfc1c269329f769481060033f3e0a59f2abb570724c4134a6ce725269e1c76130d114d159f8df79629ab01256230b3ff020f7e6138bbac4c20d19636bbab78f21b0f027d8b5759a0c96bdc2f1d86453b694ef2a5de15a8a11e71f6c851fa6ac3c6b6485133bf038b29854307115dee638248f1682670855e19bc731a46473ee38970abf5a3e920f2544a643856b1e473139fa3cfad829fcaebc21a5c542d29829bc4a2e5b29973f47560a6fc42f420ab7ad4b7286ef706a915178122b872963f7077922a2f0abb327c4431f0adfc3467b8efe79c20714be450a0bb93d5e8d9e4fb89a3656e4303a5a763ce1dc8da758673ef1309df0443458b0bc2177a4e1842fd9999673d4ea39b309b77275889af346b2144df8914afa8e3a997574c9846fffd92c5b65e5ab60c27331b32c0f3999e6124e4ae7ccad29470cb18413738c1243ae849761fad206cfd9b194f0d64cb352ead6ecc824bcf4edb61d7e49f869528cc9b28f849f72ba9c3e9258110f48b81d2654943693db8e47f89e82aa457438c20d29738e432c6af67423fc38fa2883947b349a66846b729eb2868ae93e7a11ae67d9e09f61430ab5229cea286bca5227c28b219484b31c45af20c2b394fca14b88d6371fc22d93ec3996cdaa8a21bc0ff5d01f47918b6c219c1c4426757c84f06bdc73902a1d8493d7c3741b9ded422a08ffdd53e64eed953b32108e66ef9421de2ad70908cf23654df0f30f5bfec1d7709f29b2e32d1ffde0888ba528967a7cbe3e78a9352b92d1dfb6e3831ba135dd6a5f9c670fde64694bada93175f4e098650ec5c62f36260f4e5cdd5a0ea3078f030f5ea61c6bee204436b63b7875de1f840caff0cc0e5eb40a390ec283c831ab83df1263989842072f4a4a39889123c65b3f072f7d98e39e88c90aebe5e04627390e6e4e6917f5beeecc818393c7db3f9387cfe50d4e270b6e91376ef0ede5235aa749744d1bbccb1c25a2648397c390cf18d5e39e5983e3817f320feb715d8a1a1c1b19f1acb1b672d2e0fa892509963b98074183932ae7e6317557cc193ce94e131e7d33a02165703c89aac5e8f155f964706ce63c8cc1f2e6c818dc48313c3226710d8bc10bf9d80e3eabc5360cbe44c8299face4283bc0e085c9e123491e31a45ff03fec0e2486da94587bc195902991a84f92d3053f4afa63c2528ec25cf02a7d18973d795bdd2d38e9fd2b47f7214b460b5e856745084133250bfea754b9b3ad58f0b4cfa34a41bd8297572b86902243f2482bf8d27188ce548b746615bc8e9db3974905ef3a96d95c15ea564ec1cfa27541baa30b0335d6e04fce29a79c3e31c73fd5508313323b7a98bd37c7d2a7c14b52eba1e71c26b9bfa0c17bcb29575a98902c6c6a9cc10fd45c2c977c7e50c30c4eb5a492d58eb5366519dc4c1a1d7358ab99f385b9e200c7e00bae4106ef3dc8df352b1d440f1a83ff95917cb3e5387fe41083ffb16dff88ca61f0cb6d523078b162eedb58096a577dc1691993bce0c714d27f7c1c540cf9d305b727d347b221d6e0829f43b68fffac3b47c6d4d80256eda66a9fb121993a72ca9152430b4e287f0f5972eef5ecb2e07dc584a5a5160029d4c082d71e76567d566feae4158c2dd917b493a68615fc1cc40edb516d35aae0dda58739f8daa648550d2a781d9322f685cad86b4dc133f1b39f4b661f41ab21854a225cbdc4b3db3f45c761f25ca79428f81efc6f76924f179f5f64208c2e9e6a40c189317956091ead553abb45d1a0c613bc0c691f9a48ac66ab38c195892cff0c25175b2970810ab01650a3095eead452dede71b155bc50c1162fe8a2aca67d00b7c81a4cc0446d6525cd3d6ea4da530ea15e61420ebd1a4bc0c3ab3a66a4bd1a4af0a3e418d523bf6b24c1ff782e33410d2478c95cca23357bb8ce41358ee07cd011933a0acdea670d23b839cdb6f2dc45bf0b1d1f6a14c1cdad31720ec254938670c0053588e044f5285ba7ef188217a3d4f26d9ba7206b0d21789b377e7387641070950bad2f0f0f912daf48d95463f0fce7670d20b8f943baa5af508d1f78317d1c8597dc686c54c3075eb28a3c217610f56bf4c08b1dd39e03ffe0f3b13578e0d6467d798efe0ebc4ff2d7b93e7fecb8d6812f2556fea1c8f9a6a9460efc157b8f21fca4f848db410d1c78221f85b49023dd6adca069cb3ad5f07899f56ad8c0e9f4183643566ae157a7e4db964fe7a9a685174d93254b9b5938d1cc276f92ee78a3280b37326bc5dbe5dbbdf3a20b1ab1f0b5bd43f4f54fb1013460e17c14ab932f890abcf8420229f0e29c8374e0cd0a345ee10a67a4a245394b5be1c874960929fa8b4a8815a0c10a3707d6410aef1e1e73ce2afca02124e4281d55f82d39e88b1239346f4c2a9c50cb7d11613c5f5950e17cb04a1a73189e39f23885e313293964ba47e9834de19da4cb5c67bf1e79b0145e5aaa0b69aa228597f2f45cc8d1cf7ab246e1895864b5dbf049461285e7bd1d9235191de51e0323d8a20b1aa120bcbb2ea6e6ccba5c3a68a59453d88e06289c641d2264dfe827f6ac4ef35a8f9334f3ce41e5f65852746c3ce1b4af64acfc49245cccd3e8841f29a7f021f6458f5f2a3438e15ae80ed3d6e6686cc2f18f5972e4f46868c2bb0966131fb287d9c74cb895d583a68eee474303c384118307c48006263009198d93b4cf6040e312cec73fd541ce1c96a024625cc2e34443e6b633ca448e9b5425dc4822f9bd2e4c0947aa7e23c3497e406312ae6a76da761b25e175745e1725e6bca69a48f8a1e55053884820e1a58491508f5a2585f8115e24f7ff984c39d2d83ac2f7d0d73c8e36a68fc735c2cf41960d29d46784f7b92ba5c71d5c4461e6716791f1e63dc9c358af082f04092a398eee72611a89f043aa7b54f76977c88108673de6f83dc474eeef1cc2d99088507eb51ed6c410ce673b0fcb3b3afdef42e4b6a22a59ef6521de5539a8ca1e2ca5c8488f107e1c7b5699746d15680cc2f3bb10fe628809c297ae18a5a7c3e88f628158a542522542d6ace63ebe903420fc384b984e9321c337f8072f49769c2b578c4f1de407277fce2629c4e8e9238f3e34652d27ef51976a5e26d197eda5154172f0c1fb38233eaeb2ecc1b1b4f133db512fa0a107d73bb67e992a4d1eb677061a7970ad2622bbe51c68a08107278878184248be71e3d1b883375152d49e790fddb71dbc8be930b2265407ff553ec8418e2a74f0a34d26edb183e409cfc192ad16abd6146f49096b8d918a395a0ece07a13f556d44230e7ed2ec5959b4c39472c0c1ab8fb6159115491de40b2f24805b031a6ff0b2e5ec9d3199774cef063f98921c42da1c297ae6f8c2ab0d7e1ccb79ccae90d35684061bdc482a41c634668af28a69acc1cf1e39b45384d5e05ca808b229d633a6541af2940fb9cbe9a3c1cd39a38f7a449be990c6191c09e571ec2853766c9bc15b7ff140727f06b9b60c7e871c7a90c10fb2cdf585bf1c61b531705fa2219231657116eb115b44424aa798187c390ffad42a0b4356b1769e325612369f11338520f6c126248a60f03587f8ccb3e182b8c00b10f080c617dc54b38f1eede8b56337a0e1053ff9df588748a769ac9d018d2ef8bd1e7c5bce2c122b840b4eff871ea22593c61614abaea9913497d4d8b6df88f513a9a6a105377518bdcef3a65c9366c18f5268cf97333b738ea48105bf2d6a7aceafb973ecb98213ca254be890ade0a5a70eb36c39c8c6a80a6e5db8e49155f0cec1a854f0e353796c6c7aceb1e262eb042bd805d098829fcddd33cdc4b8d88ac1910253321540430aaed445394b9675952e5170c58309e91edd52b98602776b5b25325257e3a7bd91557c825f79c343a8faa8734e2f136838c1ed28933be6b553f9c409349ae06a872d1afdeb3bf630c19199edde90f925f8e9f279f4b14b3494e067ea4ce96147a6393322a09104ff25bb47c1e3be889e41829b7b735839a87d84466dd553bc624decbb3607afacb9e6e3a36104b7d3ed338fa56814c1cff1f44732398a06117c8fe9e3991ccf07991c34a03104ec3e4aa6ee89da808610bc770d5e1ec60cb1262708defc4848fe7cf141f00061ab1b95956c2dab8de84afe33c973c77624971f387296d2c5687e631e4cc3075e0ee222a5b1e84d6147a00b347ae04a38891e476a8e83845790041a3cf0d64224a4740bc933b883c2d42a3ac6cb5536694c7a69e860cbccb8cb2e29bbcce8aa8c14c22b648ec3252eb668e4c0f9d06f1d7ab686988368e0c0b9f671f1ec48534e1b1a3770fdfbc4a3d9102eff84860d1ceb9cd13e6ebeac4f2eb6bc08630b3050701816a05560462dbc943752c59c6bb2f7dc8119b4702d3349aa68698d1a555f80b13366e1a5cdd41dbc86b270227c74f5314962e1a47c9329c33278871558d4a215dbd52255ee362f390e8f36f2fb0a3fb4d0719a438e2bbcfa9446f36d8c291fd30a6f4a63ca9d31acf05346d8a8b461269255f89dee293586c7fc5155781333bf59678af4b1920a673aecd248539f6c54385d696424b8f97d4d4ee106af0991a9d9716a4ce19987ac3158cab210520a3fc8ca07673e299cdad05bb79d517877122dbb32224f8828dc1c07eb21797684c2f758631e73ea008553214a92b3530f72e8f8849f2f8f6f867464966a4ff8416ffed0622abadf9d70343dc6b2c709bfc62b4fb466e4ab78139ea598796e6e4d38e79fae836c33416b68ef50fe03137e0a2657de1ea690f5125e4a0f35f96b7dea534bb8e13dc8c16f07aa15530967d5633bda9794f03ab68fcba1949370726945cf1463dab428093f8c65f9bd031ff1c922e105f7b0963fbe20e1fd47e4d4563fc2e98e93da54d48e2b48331ce17534b39e7e723ca311be79d49683f530a964b88cf03b70e9e8e55e04d9a723781ca33314e18b847ccb895ae78c6724c2c951da76d0b1bb72cc20c2894f95b3d2d9f57bee105e0eeff5ca1355529a0ce107b7f12ab318f3854e219cba0b3174744f88b32c4dcb3be32ea55bac374d06f3c8caa4c283f03b44f1118bc8917546108e66fed01dc2a7e5c01208af62d68890feb2ab2a80f0eb62b68f273a3089fcc1f5c966b13d9ef8c1c9e01fa3f45dfab0923e38a5d1b3797ff0c18fd44335cdc27bfa7f0fde8f45cb79fcdbbf7d3df851a4bf27f67fc5c89107ef3d0e9d2b4bee8c4be3c1eb309447d25cdfc1ad30f93e779ebef1d90e07d9265b07e7435d75103ff2331ddc181ea6e093d2d46673703cba972441e33f2296831fc4b4f153afc9cddc3878722fe629f7a4dc3170f06efb93f4ff779027fa066f2d438ee4a7e330f36ef0ff7d6dc2e5c891c56df063dfc810739633d8e047464ab9af424ed98233d6e0686c984b31c786f7c0196a288b787cb2b5310dbe475f479d91ef230dd1e084b449a288f765ca9fc1cf72c93c58c80c8e7daa9ca9528644580667d663c911aadc543a32f89d345888cb6163f0358568c65c89c1f14054b335fb6c0e5918bcaeebacbee4e69a0383ebae5e31878acb53d517fc943c0a1d741879c1cdd453c9c3aa762fcb8c2e78ee1b62b5fa9f7a79580833b8e0c7305ff9349b2db82eb2c12635a5091e99a10537cc7868a5edde285f169ca9c98e3e87cb2fdb61e1b2aa78cd4e891a29ab37b96e91f20aae45becc71b48c153c559f8f3fa70b39e5ac0a4db47c4aa5c7cac7bbf95f7d67474a05cfd3d7490efb7328dd14bc34bbd9f00831a637430a9e7b581dbb07761f75a4e1408d1a4e9811052755858d51161dcf57a0e047592343d966b7cf3cc1bfd41922d3d638cc70829783ce1f623af72c95ecc0173498d104472c46df768f8a89640613fc38b630591fc7db12aa194bd02b2eb6eab2264e666653c7a8b4fe511382194af0725dfada0cd28c24f4b35951ca2f8404cf2ab8a85a08d6a8b10500dc30e308acc86c744ad6c59667a894657694a24db2314930c308cea44a9875190bb57946115ca9243ed9fb23bfed10c14f512de5b29bc71e3d660cc1b35c29b7577d9c334709c1cda1c3346bbad4586d10bc105d913768464bf00082ef71f420da27e9f3b366fca0ecb2aff77439f5d841d4af48ca9fc2337ce0c7fa383b6bf7356a7c61460ffc9aa88f1d59c87970171566f0c01b9f0fe5993eca5d31e2c18c1d3839fd85e4b4195386ac03bf2ea4106be55353c766e4c00f36513296c77bcc51b89881033f4d4ae49f1ce5067e2c19daba932c821936f02c4c6c6b8e8a149d530ba7430ef28dc84f0bacbcd2eba3a3dc453c347314a29d2db3f0e3e389d948933b458981116cc1820d5938dd414cb5a33f547446868d58381d33425c7f59f48e030bd7facffb7e62c45ce52b7cbbf2f90f993762785ce1755de8988ad8a874e903e3e10182c161c16824128642c1cd7e23006315080010541a0c45721c0792381c7e1480015d1e1428281c1010120e101210080a120a060806060008060606000006080685414276608e65301f113ffb4f02b53e38081a6d1e2e448eefe4e0fb5cc5a118bde5a418ef40a7a3ff30dfdc617d9b34ffd7f073d12f7e1bb37969b31c27467445e227f755a721f8aaf4ed82f77e9665f5bdba1d93731f72fa089045db88560ebb5199a4d834c95ed3e0c3d8e404d1a98cdaa2134f16942e68ba9fba41b2ba300222fd6a06b1c1ca6a896b6ccfdc63360f18c3951a5ac915597f4aedadcef6e00fc63a94a3a3b4606bbf1e2970253b672a21a18e88f40dbbad6a3435a3816bde8308bdcfcb169b680e54db13f5408b1acb24ca84317ee7365b9dbc93794b5579120d7a4ae5240048fbc521b26c423e8706e73d5f85f5ac1c5afa60d3a589fa8152aa14171dd0cfd013d14c91443aa1ce2f0943e1a836035a3a983c7980437517be3861a1f8f79b8c1b1f927fafd06f66e40a472275af5edc8473ad22bf37e0afda5492f7681368876d3003b81786e5f64530fec6c8e0351b39ae5ce166f2c92c2e4f8d4fd982f223b601fc623f544a3904486588ffa2f4609c33ecec7c0752a2af78c9e50cae72365db4ffd4d9d5f034c4951e28cf8a206bc3158b8034ff1d147bd080f7bb7c5215e1bf86aa9ef40ebca6dc46c27dcf35d31a2dfed9e13d1ce94590fa7dea84d510c01c78a5e7e03ea7262b79991f097b16d45de0bc047f6bf565f0d5bd5466c0150d689bd21e2d4b011cf4ce4d925ee8b33a8c9d29b4da52c74554706903e0f8fc281d6b396f1f8c7a4b0b1c7cfe80e15baf1acb1670945e9f36ab648e2ae2c6d954997c2cc80ddee4ee6cb41a4e12ede533a4097a029fa4a4ec773ec05976c55a48b8f09349043b5b4a28a671479ba5b3069e22dc94788a66345e7fb8cad240ac505a4a328a82ff007dccce8d4ab68283ef9412e3b125435a8180fa0a4a56afb162ed4be7443bd5c403962e29574c00cb41c01e849dbb75a9b68cf96f89d63dd92d6eed27aaeca953f48c9963ea2c04db3bbe3d7f183d25a25d32a480d55f84827834a980560455393d3e25adda443cc509046da84f6a044407c91208f7a3f80a52b9c0cca6f2890cdd31822bc35c105bcbce2f53bf4d765625b1393ed2d44b9268cd2bc06ea4e09b5bcd5bac084fed0034fb21abf6115ab06e62168ddbf0feb95e4f7a4ce9cde2074d78a9d21c6d57f7b5cafbd44dfc3f328e2e5b5ee76589391d0fd824bbd2c3b7d43c9e8a2ad048888d77a5745e48aa7f2d7e3a12101ba7a136ec898891e432fcdabccf66ea0f52ae4d4dcc70754345454b8a8052b684ffff4a5f26add98134ee9460064916abf75c718583de2c9ceb02e2b4e15afca12711fe70cba5fb1bc39e9e95e39764a8cbcc907b0dc5c5486c44c9ca42be1102f402ba5b95234f000fe40158a79905ffbc14f40d2525f74f40a85166555322f1f70d2f4c06fb2b339a1b2af326ab74f181a4ebcce5d6e6b32fb9051a7e9cc2341be933e18cdf05087ece950e94975163b55431635214680eb1e45e63900ed1901c956df73349cc845b2a216ab50ef40049e2a0fc6b9bb803e146d5aedb7e4fdc789983c9ed8817ce44849c0ca78be6c68b159b3b8a97609dc37edcd8158226241d48b004d8f039933a65a25fa0236cb4cd74d06c94966ecad561f4a198f8487c8518669a09c76cc6ab824819b89d942325ca44e8dd2f9795174188ccc62a4d2ea26b5656cf15d7b2eae7a0bca35a67ae7e83d26ef945f036f900b1030d8f107ec1700caa943234993983d18a22a49650d76d20897aecbc70e29c154e2578275b2af301cd9257af42bda9126b6a1b51d3cc92457abbf8a38e9985799d8b2d951ba41710f283d174e059d883163a0ba2b624bc6ed34d99ce504e31b2c173a342ffb6f102b49e09585ad229b0cb2bed38bff2f439d37a65995dbae0abbc1048961f62ee362f04b4d213c22eb7ac8b7342784be7ad77a30a41233bf7b46ecb290f83833f9bc307696ea4777580c982a941c4f715fc34589d90f834a4adbccef9fe469290344b86a7dbbea28281f0561a5fbca2408e420ea552337eac8227057a935e8a52d892e0334c9ce596c635652108f790a16cbd412e96f3a9631fa5b76cdcd1d096178a1bca5a55a454cef8f0f51949590312af52fd24695dab7da521e9811855046b01d728180095e577b1c659665fd3208243b8f42903c07959e8901640a990bb89bc9cf2b1b4b0a014182183ea944e1013da0aa376dedf002b4136c0533c1ba5ef98054da57ff75b7a8ba7f0905690241fbebabec72460bbe53b34434e4158a7a4c3bf7172ab95a54de9030704f411f2e3560e193d60d7a90b8a68fe143a8300f0acaa5600cb64c89291f98d8fee99156c19c26cb4a4c86c005c5b4c74286f9e05416ed8adc9b0443dabcd45031a54a44c719b5c5a3f1d93b47208fd96a81c9eeec29d02c5bfb7d31cc52c95b380ce2b6ba652f96cdbd52d64d609064a090f33b5bf85ac07c2019eaee010202013b6c352bb2ada2e5dbe4661697ff80a0678af24fda14cada154ad109d768a34502f1ba57b035dd4df1e3662e40725b987da53ee41685d6fde29cc4b342adc9b43efd6690cfa5860dedef1a4361a948e62504cde6d35ba2d5a49d758aa21ec1439e477e9424faf023dda2409af5aec88c2b99ef188836c7cc52ba3de648ab5b38515a91687abe5cbc924935d37f1407150cd2345a7b90f4f2e6af4e259950ee6a07676a7f2301b1c4df9ebf65ec7e7ad5133889599596d0595e355e213ea6e7bf67101aceae57912a7985c82d11e8475c5e4aac4c3f01d09cb25441a6adb32a478309590a6e8a47d3a1c16a3c61608f78e70e7b7dc2dc45af4983f1c94cccf2669dd08f98f5563319a15505053770e6f11298d706013bcf3c60e3c727feaf918fe988819a1a215ecaf25eecb1cfe29232477c21f97a611945678d33728766099f9a14ab62dd78cc888b346b0709f917598638607ca9305263612d850c308be4401402ec908f0454546af35427ec40a97580069739498b124c803d968a10f1b8a7afa46ab84ddeb2511df72a696ca1b55be0bd0ecd58ec06909711c7b5a46a28d5547ee7cf1daaffc286401a45201e09ad4cec8b8c0513b6d0f097de3e6358420431f80bba358630827fa5459c551b535731f80e59c2cde40c25e39b763f82009b4f293383d5ea81074fda72bbc32106e688a10450914041825764d7fe7f58f2e0081c24e56152e6e8e475250be06c6d984c32752011dbcc593b5fdffbfeeaf99c5022f40395dbe3bec2e11dc014c3c3a2311c13d063a2f0fbad1447b00f87c80490824da5260aa5c85ca450b745158e263cab2ce3196d4056012db99b102a3100c6a3f99e76a64bac0e628ae133b3aa59006bd2d43959391a90cdacc87ac62a02ee4288121dc11159330b63de8a333fc1d77968bb7faa4b0166a25258b4e4310767b7a0ae651928a396b91d6d7048fc6fc437a0ef7237ccd4d4e66b00dd59f29ab01b4c9373985b515a7d989d8d5c8afad9b9b72a3054e9f8e195ca45e01c234ecb5d4994779a860d15374497bdee5f84618095df991e2744ae8a492ae497b846f48d88e4b58ac7ab46dd8912832d97873e79925ef0151a2d5a415c4b0de7a664f6a342d0ee6b501e22044b6513ca105ee3ca45117bee4e33cb01d461e78ed9c4204f99127636cd27ad2995adfe6206925d63354a8ce1679490e0f7895f46a9384f28f8c4a9cea12b48b675492948cd1c96aa7a9bfd9cb71adf730b7d83b4d41c28781d44e55213cfca21db544ed4b01a96fa24c8bc80f16b34abd5dd1b6899832e75082d3261a560aef9aee4651f5a150498e30e4fa30acb318fe21e917892ff3621c4646ad88b2cfa491a410c4a19df7c6091657125e3f49297b7a29809c2f7812f1e64a04c27dc801b6248c043de9bd5094749de19cd6007d97af0837aa29414e5148890be1f44c0550d3c8259770c19462f8b426bc3daa66ec46d72955555e0daf4245bc289eb0c5213e4353d5014c141575761f954003e3d0381c8e60a9a3fbe47607feab92f4dc8c66a6edad89ef52645c7d3f5570a112f49ec6a26e92761f6242c5ff7634567ca4fc4754e64e5dc591501639fd3c03633e8a2c63ede1316ff31e4988257ef77ffbcdf7a1690f58f47c9c42d73bbde0e289a73074cea6149252022552b3fdb1f9229034ea5747a56744e6707b8d03ec9245596b9a45173a2e48c1e24a0e1c0a6c41201584a458099c7f64a85d894fd7c98d51e352810e725f2a163144a8d3750888740a5e9439186063ba8a88209cd34aa2f4e11e91e0eb79f4ddba3e334eb5128b6cc825536940f9114ab0de49509212767c01867d7b918bd325894adfa7fab8669db630a03f7f3ea91182dc083cee7c83b1e644a082ebb1ad0b8ce949f2a433e7986f08c20120b1651fdc53667b0d20fb9a07bd532eae5700d443a401ba89f34502ed9f5477b62a52c0c666ae6daa0a4c659fdca3d28aa184d1994b09d9ed5661c919d488dd708f5cb11803e6132da66803d121633adb0b90039cf3170e0b37e65862bfca93611503c61074734b1535dabe239326746a2902cd32a92a0ad1aa9dab303e5c74bb4b2713989cee1cdaca26c402c1c868eaa0249d7ad4af0d6795e3d3680d09b79dfae20410ca217cda3c1760ff686ac454c08548961c5996541617154f0b87333c72a254de1e097bb386516a314600e041c444a7699478e5b34b5385e3f226ae00461af8b234efbf4b22d1e89710095321397a22a98179e92936eb336ba64d1bd35f7f30d84d07f344595035b30978e9b6c9311cd1f7723ed5346591f1188be49540c62932a08be7228441999a9d7a9075efd56ce0034a8c000928ca8416f91dec4a7433f24562e75db5632be999334f2a6726b557845a773768d8c9b8495dcdd4e474c6913676198968f139478a3f7e4da5a1821e8d25c52de7dfb4358ab744c8e44ed8a2e70a3a749341c53b230937dfde8cade31f4db385b80239c1e65f29b74681c5d8f5e876f113aac87ddaeaf0da31c1ef83695c3253864d4d8329a37741aba91c3e8a664a83f0b7eaf34b87b879b1757469a62410d51059f44711276347a1c5a3c7c7678d2f3a75368ba8c0174a1f834c1dce04c7c4fb5455f438e008cc656046e1ce7ff5c44b86bb6ac6fb3438021ecfa34b8e70ae7718788987121100e52f5b6300cba7ae0f7e0aa7d9326479bd643658ffd181b0c1f01042f5029cdc15d051113fd001a253c10c78b3f10357907dc7407ce136c8ce71052a0370a826f2a24443da59a7a38c2807410f999029f05be30586edd3b7c76dcdb8cf23e5c88df94e1ef26c93f972c6cc04283f90b5cc8f3afe258b0f4f9c19d1791ae707eaf9df318fdbf526fa68cdb9756d9f0ff85c2b923ef549a5ec073d4ad22f2904404ec439f7fb5898e27ac347a2b0b1a1500eb7f6f5635118d18852ac24ebeb50868c9efa0ae0cc5d007a401778f2e11cc6877d98036bf02fec3dfaeb801338993300e53cdb670634730fc83c9e28b1e80f620745123475ea5a4252ff5d807aa8542fb0c6f03bdb04e8525d45e9e91a3ec93c55016a322d8a7f02e8aad2b2be3d708ddd5d779dd63b89385111d5d8367e74d0f44be72d9280c1cc985debc2eced4503d3f7878fcc9b1bd1904df9d9f7eea4bd11db68b4f84df69ec13e23c66896b206517b34ebb6a6faeb6aba287327fb50c167388ca8f4cbb17044f47e952efcd10cb1e9d56c017fc51dc959d88c5bd7fdaf374f10fc9dfa2b812aa11ff6a9280e32e91b4a558c522727a8faee1a7ed56d7f00d2debcbbd3d9f15524c4491ce740c818957da3f17700e6f4d2deb0a37628284503368c8ad4b4a753eae0a4d637eb130700519400d800ea3c0c686825aef012d8b8fd9c8ed0b896918621602d239a05e24b81cbf602b9d0fd0b50000d3c38c7af7b035c6b9d8b56f782f5957386eb766d713d478c5f23e3e2507444eb57a78817369d02eed898104002d7ab2bf811f05f0bf1ada8770030925b691310db3640f63a41718a91e4b5d5a811222daf022e668e0cf15b5f4c1f40f4a14d36c3c7a7cc897444652ea15844500d1bda8c6fdb8c9946f094d1a96906f6970711a21cd58598c5d391bbfa0da0d26207503a85844ef6c57bf8683cc2400b9cb0df71e41fcd2ee2263fe17adaa5ef808f106af4cbb2a01c25b09da9e166c64c1749a1a04edecb6ee4285d50607d1a546040e8f2671d9a63500dc6a8577bdae4e8dbb37371dd292589c2163477b47401eacbb169348e6f2f1d141bc0ca8d7d2aa6246e56a4949c6e45f2ea7a064eb745193fd209a9c2fa0f38a01437e719a862ad0dcf6e347ad359e0685f6c6f138a069fd5dcc685f5d50031d0f9607636e61910b61e9e17ddc846dcc89508ad55a9045e1b13b70a17097c4022216d2102e80c3c17b6772cdd19298762b73ebbade239b2146a8db532eaf6d378ff39acc89550a640a46bf1fc8d04f7105280a6858c3e9100aa50b2f32b205fda164ed2faa9613b8b34b6ac1f1fb26e684c67f17ab955bc686244bd6d2e4124e669db06470e9c2f1a9804ce35a3eb54d1b3137bad0f8cb6d1140f8c235c56955be911004ca25d810472a90df4b6d1d0cc026440b77f83d2da8f0a17e009b6286dfb5d3f32e22bbce92081572c018b5839740794c965b8586434577dd6b4ddb3b36c6a77ef30745dbad89608b3ffd70f06d442ec0da09164087dba36f038c2820d3e6088079a6821e09bbea05c14801a2e787160dd9db28b0e2414990c6affd7ad95ee64fda22f1e1c8d6134e2054b4a7956ac8d46f5bf2813e45ecba3690cc10991684333e92900dd4fc9aa2980a385e63174ff12f998cdb4b39dd3cd4a9aa56d61e93c5113eb3473ec4d9a4c3b55d865d770c1dc69a5b6a8810e91c955d32f3208a86098500cbeb1628c9a93aa33a9b4eb22af1f7e33e77822d73b0fc3059bf9ba5f0f0726fdc4f4b639dd30a748eb8ea9e93e55cc35969d6d4b6c70345c5d16588c8233799272522d2725a20f106f60950a3eac8fc60dacbbf0e0e270a38f44f1820697be78ff8630b20a7386fb94a5ea3c5d11a0cdb9b79a743bf6f7f7de689ce18a6eabc4cfdbc4062011cb263a198ad9f06dc8364a2997469db87f15c8814167893b3c7b649e7c27ec59825a18a57a6c71852b23968563e12e22de09ac620a5db2036cb0f2706a4b579c523cf402a5b41fba1bb72953e281c457604453293ea7001e1b9147763d2a8ec587c2563f7ffb65c8f217c7395b56d648a779bbd6170c0e9b99337bbecd9ac8a82ec1b880eb4b33a5ee01484550b3a2456a02715a5b77c2c3207c6c158a69c578a549a82c495e5aabd192f10566b7a9c85941d39c1c580a6883081eb744b320304d88213f5fe64b0012f0e665dd7d62d9be9c345334a2fa71305ce4e2964071a768b7506f17b670a94ece1ecdf2e17691168d6c3ac277e2347525738196b25356f602a42400e3682b33f497062515a4bf4fed6db51d3933d5f4643451866922918cb67fef684d5022948a3515f854ae1d8e0530e84e7f274041d60005b25faa0f7b9e6137c68ac301c8dd56771048f7a4ab331d60a333053a139c4e21eb48a94dbd80872461441c324a515d0e0fa6c52e5d70e7d7e1ff1bbf41ab3d5b778e0e8c355e323b6fcec0eef53063b366676e23a35971d3510999cd83d27721f01e6ad1bb7b26d37ab5f8826089200e8fbb587294f18deb220b75a1b437dc747e26912ec11934b95e1551ff8b22e060eac8c09ae15d0b4b6be9afc75dda0deaae5e190305338f758410ce49ad9b9b75787b61a42e5dc2cb6a147a351df04e35d38c992bf985dc1cb8e87808ad4e54012de7ba0d2a593f142807ca71dbd6c5706194d3be79c0c6c11e99a2190391dd1970c88e76ed7dba93a5913e8bee33e0b6b32af9ba6f62399a6649ab915e1d6381083dea087d142d8171496ca623808337a0e0ebde307554e19b96ec224d0e9fdbc74834bd0a4a5f8dd8d93582a7634660a9a11252200046da4066c5bd4298b9669ee185d029c790179a43fc08b2ee170beecb6d3f57b19ef4e9e14720867500f83ece013679e5bc5ed613b8cbbbc4d5c9664aa790984b227b2d8df330200f0cc0a16c167a0225d6e1c1bbf3623e19077a6fb8fd20941a0b9309196968477e8c9a12e6cdf7c8ab8498509b2e7baab63fa76c25c4a5cd06b5069e952157203c83654f51f5a2b2ad1a434ed5ba2842e875d78d06dc8f595d91cb28b81a0bdff4da939462c9e045f8e914be8d359d3eed869d42da1694bc64ca56acf6000486c8e844982ca2f8ef43139f6e93d76016772d40177d16d7c457651ae4ae45dd2cd16a30f8b8f2dbf30e942c9011d8a2ae5d2a2dcf237e24e84445b37dd8435f4eeca89af80e7f6c0386824180445586f6ec821d866ac93da463624e075f1772cec40b134eb44786f78a96fff28e09468c81594d4c71260676f3110589e879aa0731a888f3b14428a4d5422d63f4076c15a77c2862b420ab1fae43b6e5f0c2f831379bf3af7fe9748aac1842d262487139e3550d54e1801c63445ce6602a56b4998b828a0c49a5485a24d2fe624e86b88d66051cc1a01b960f1cbdbe3cfdb50290400132a07819de3ae92c500584fa09beff22efd3cc9f6d7db49e5516f0d70b15b4168441806c818284fa3b031d5c5251a70e21e28e4a6ab554bed883d617c2418cab0310a7e6aabe1ff60e9ef71c54cf7c51e99d4ebdd9b48744321d5d1bbaa2ed6e2f6de673a1d97bec65d8abc57b7a749727fb5fa28cdf46bc475dd2fcb79e907491f6b935b90eeae631bb3f9576722ca14b48b7be2ee3aeb1d7bed76e5e5ddd6fba1f5dde7441754de9fa55bacb8751de8913bab1ba82bb94d37b5e0c1d5cc7baf6d3ede5bc2a0b435a4e2bf2eeb0372daf015d36b5bbfc43963ac8d635dcade75d23bacbdf7895d9ab6e48dd1ababf5c776b650691c7e9c2d415ac4b52d74e177a5d64dd8abaca5d8d783dea8ab3dd42e87011d2eeddf4d6f476e9f5f0f2d405ed9aeaf5ddbde744cfe0f9583720d17dada1678d5974ebe93ed4fdd7fdd55d10bbaff990675408ede6f4727a7d7a817a35ebadfbeaafe2a66b9be1facc689bf8c930abca77684f8956c0b8e719c67893cbcf07acd96a53f5706355fe36225188880a23fa090a0983b42efd80622050158e645920a3a3e79efacc0535e24664cb277d86a2b1fef7660e7f141ad76eb950045eb3a80522ad1dc22b8ab5b73c6c58ac4cbf9b9b927bb5045957a867ff01ed2017181711feca5a8f8696be0879c9662ba0fd6f2b3215cbedb600ebce62638facd876494f9e84ed5107e8e6d857f580f4dda4797c5757f5cc8de220280f2e4dda454c90ad1cb649e5d9b0d7d09af97b2e89b99a867dbb64a904208b51fca950279fd0046d09cd38f5ee40d794bdca940b38025a5181532d6c071840baabd90215e000569667e851533f098d91226053424ecc8565812c46ba5997b570300abb2970f659d5a6d528fa2578736588e78d00ed9a2555573bf7ac19bae4736776cebb85ef1df6f67cccd87173c51671ffeb0b2a98163707103cb76c5f70a4a6f42e04897f5d591c2bfc5ede80097d1e43b30ee56f59ff5a4fcab15fdd6cd74f8927b0eea5398ef55fe7d3f44d6bd641fff3c20beecee4ec0903f129e187add46db60bbd1da525e3033eec967aabcf936b6c2e49fa8003dd1574f501219874cedb2632f573b05c430c4b21e2471ff5086a4e74c6934d3f9b8368e66a687fb288eca34891b8448e8e2cc1b5c8e438694800864e62d8bb3d1cdd1b9f2ee83b8a2798a4a75dae39b5d0fdb0f59b374aa7e72ff32137519b284623602b02a7755a09f82ee10b290d1be4ad3d814fa6db0ccc198eda902a1181439a20f48686f5f016d9334c4c375937569b7ffd5523c106436c2fd8c75bb943e57a0c54429001ab84a78230814c789c1732e94046e8c4c017a30399598114fa5581e2126808ea8e405601b80c6418e6bff97423c83457052f37c1effc7dacfedd74108147102fd5fa380bf3ed070668e82ba913204d78e6272d5ffab557d7347489406e20b7e00a411a0532ed422048bfc0af00a0b1a80be0815509bd41ba5ee0270aa55ec546810cbf0722e39a7fd5ffc005aea5af11c90288bc48f03209ebf526023c0f64b247505134a600e5d7a8e7194f18a3d485c363d1b58e491b807a3bfc3748fc143b25120b08c5999247fcef837a74fc149423b1c2485860e00632697ac8ab9eb3c1aa39f64b0b029001c102d902b6026481a4106432498381eb0f049169b06924441db2e7ec6b6ac12881c31a647cae7bb34108826c1f85c3707edc54d8f600ad48d08d505c0632f9a6f40035952023490215d22cd0eb60a01e373c18580c2ebb02610f4d0913f8410135124ab856bbd78c8f1a972a8155098d446776e00d44a7a92750511cb095f42fd205d9ab573352e50bd60dac46cdfbf659e1d2c7807d94fb0ef2360932c11d28838bc08f1235000187ab2ba806eaadba654f65ef868daa2029c195d1347b68a1c87aeaad0b028ab53b972f4074904207f003fc003fc00f20db0889adb5de499429c9f2a95e21e722534a29a59412c3d7751fce38df70a4d134079709030935092849abb65d984491a3fee424a6e2a73fd8564f1746d50dbd3b9fb4051d2acb8569cee2bca8a43f8ee75de8c00597d4c6926c49cadda2f0b4af6ab999afc3166693740853f7512dcc277ff20875afca26b409c0600436669420081f1011392cf01a20e03043072dba55adbba8662db216c654740a22f24255981db3308aee1953e2d9202965094420821a23344af0e8a4ea90c58c1a353c7410e2e181808e5898836753caf4336ad4400974c0c268723ed9548b5f613a59a7962b2a115718bba40e2f4abea9e9a81506b93f71fb16962459b3c268b296ebe8f419f1d32acc419930419892df249d9781d44e154e03e407346e38a02315954e6a54ad96f11e21353c6c9c19666a830e54e8f2d9a6b16919e3a5599f3f5e44ce969119a9e314a652f9920a6d4ba2b63264809c8083c314a610a679b2c80f35cbf9606b187494c220bedddc454cd0771529cc7ae55f41f4543d3474002272a330a6dc29b92d1fd5ea1685d94de9c99f243b140695a49473d4149d3a897fb0257a07288c2e6245895ad3232ffa602b9f30594a1f269dd88e12c4f484a99389df9315d64f8c3ed8d63a612eeba4dd7992d6a5241f6c8513a6b53bef52ea3927c71f6c3a3661ca3b2a558a73fdea8cd584c92efe255325ac8e124a5948d09109f3ff870902e19d0688475a0a3a30619244d733bdbea47b4c973069359ddb36276809e21f7458c224b99f08ed163b23229530a7493a8a50514c5aac3ed8da2861ba50a92f9ea75caaf5c1662be89884d154c7bc18714ad8820e49984e662f947685f3794522615272ca387d79623d27618384593fdd95595ee41e61d0caf124b784913dd20fb6e208636f9a6625a1a4dc973fd8ce4464d89801f27b35403c6c887434c29cbda2ab6ea92ff7920e4698edaea477d30f223b7e4de85884a9c4f1f867e1977c4c14613ad1392de82ceae78e44986cc484e9a65a07718208533a5954f81cb7435cf77176b9adab62a525cf8ea13f091ac27c27ae5fe5523a9e2d84693e9c924d90760721cc5616b4bd9f18b24feb1884c184ba98af5d259bccd621089312ba3a84b0a04018554b892245ff75923f40184b527be6268a8fdd3312aa20082724e4e81f0ab36929c7cd9c5bae5c8c396b3bfc6012dd52a85a1377f4c1e4256a46f498d09d8422aa821a78c4a0830f26f13e42cdc9792fe5eb83bb6f64c8888d8e3d98b34bc9b65e930439793d98af32cb3a85f3141ec22c74e421f5c935b4db1380c1086ac08083e30735424254d0810783ca89a73b18b6949ae06f723c39be7630db6a9f60a5e44fcb50471dd00c2b4bd72a97b36c2ce4f88f2a41caec4e07f3dde75d0e6929f44c47c4d1eb113ae6605e7d0df5bfe35e6b92834909ea5f64b8dd5f34fde360f44ee2cd3e880d07e3096af4fb83e90dc650928e39a69d6e2fd5e106d37d3c21f465bb0de64bef954ac5b4adc36c30fe8d09195f4b0d5f91d0b10663854b265a54e6684b01e149c0c10184ffe85083f9d6f32fc813e75d4f3ad2601a17bdcc4b9ec3554783712c8993cb44cb9a1b9dc1d42d26754bbcd196630683ca9757fff94cee313bca604a2f222da7920e3298540e65c9dc741a135f0d19cce81883297fe7fc8d0e3118467aa83269fd92d8274e4247180c3aad5ce5e9f99ca7c4c376091dde9c86cee9c4afbf600af1966eeef582f9c58210e7e9a4527717ae7ab313b7994dad98756fd17392439570c1a44dc53cb1e537efa28e2d984d7afb8fa6e374bdd81d5a30ed7d527ae6dd3fef25c4fd7cc8143ab280a990915dead21d583049a635cf4787c99620918e2b98326d2d3ea592d74e4ac60b38389a091d5630763895b3b37c324c4a47158c1754c5d7621d543029c9566684c7765653c714f28ef3ba9495d7935f171f1374523ba460cab1f4e7a81b2a5ce88882c953b0f6b8b5143aa060b279cf13f3781e9c196a19e8788271942499605234f931718269b404dd1e4c124ac95213cc3177fceeb3f466e799604e41899730af6309e6a09e942cda2525983a9cb8f87e9a76720a868e249884db074f7e695b4307124c723577e4ee925462551b388e619254924cad9cfc388c61b439658276ecb929f972068e6298b4f4092bb292fbe89318a6f333311fbd3bfe05710cc334b7a2ba392ac9deaa55814318a693175efc4d46048e60184c67c9057d9ae3b10103d31d3c484be62f4ce2e2d45ce9926f2ff4456ec9526d56854b8b7535f2b282f7961ca6ed854910f9568fa6b2534962c3c6c13b0e5e98cf3e872d41d8c6077b17297da5dd84fd521766f99225d989df0825cec59ae95ad1dbce62c75b5792df4f52427333345c98bce5c63ea5a758da988170057de6020e8e5b984ee85293db45877f12c400872d4c7262e1653f6ab8273fd86c80848c88c858ae85b18332d554911686cff6ea9372b8291316c72c4c26e7e027a87d979c9418b42cccd9a4a44b905ac99249e2e00003472c4c9296539694c8115a270fffdc12b4b980031606a54774897657c2c0f10a634813b30f0e5798cbedb35af34c5a61bbdaca96b6654b75cf3ccdeece25439f943870b0c23c6641fbd4662e5c3ee158857176f4a792649978b104324315264d0f4ad250f231482e70a4c2b4ed39c4ce84bac0810aa307cf3ed87c00e21882d2672f23b951c34b1a9ec224dc67c9ab25c94a6599c2ec1dbef2f7e875f1bc14c6b724e50b7d5143059b145bae2eabb4bb751d9e3da51c1e85d1475b4e59beb38e2a0a8385eb9653ff3842612e25fe277f89bb711d14a6ca72594cbcb44bd18280e313be8f4eda4c505d042e9880274c27b7569cf8160c383a61545d51d144ab50ed45e0820924270cfa4d7bd28fe979d2de84f16af446b343cb9e18248980b489c80019b9a109639d0927c73b7b763765c22cfb6796e34149e0c084a95285ef94175fc2f4a6cade74ee78be74b1c16109c3e79874262c8921c408a4122629dbaf99f820b231250c26464428256f1abf4dc2b4b26179b115d44d2a09d348936276e364ad9f6bc9038e48987a2c6c54ec29c9b40477c00109c359b0923fc1843c1765d4a891c8e07884415e520aa9e15102b6906f157038c224267928f996653fe97cb089fc071a39333c6680848c74c88848238c96529ef49dd6f296bc0c3818611e7195f7d361fa10bc8d20c0c0071c8b30474fea55d7ab83072545184b94a45b4f8e2311a68c7867a87aa8572f5372124ed7111019365ec08108639e24fa872d9d43986a4457f024491bc29c946423f3528927d7c85108e37bed5a901bba73bf1c84307a0c7593d5ef20cce339f24e0ce5f971148449a92027ad053d9167b22aa881c7e1088479d4847eccabd97ad00d191f62839b0310a6d8d9d3e7fc273d6a7d8c1c16e4c7c8b1914118b9e4f80356b1ec6a634cbd5bb37cac73107e69a2fe317258c0c1f13132c2c1a17e305f366972beae9bc5f203ac0a93f6492ac928413ebfa930bd68115969c162afa830d8a7480b699652509ec2a0e24f990a52f468690a83dcc53b154f5c44a530e5ae20eaba39290c361f74bcaaae52988cc21cd6a669aa4b4461f435dd925e234a6a4928cc967368adb1d4410a0a63c5d2f1e3efafcaf409835dda2755923c6192c4c4ce897fe9a64e182f48d1b024cf0983c79ccfeb1f59d9df84b9a468116abce25aaf09d3499fb9519e4c18f6b26e9c7c4225318409839e494ae91111a7722e61b293827a7ac5e7e558c2e4392a8ad2719ea25c09838a0aafcb2f254c6fb735df97c4f1741226f5f9fbbe939230a53f490811ad2261bedd5d4be13a90308993d352546d8ab6ce23cc335b7ada29b47a92234caf7b4932370b96fd469894f849a8934c18614e25c9b13bd48a5ece224c175774db3d5bd88b220c625e45ceec449872927e6f225dc562441877f4ca2cff9e68da4318845736c9f7358441e4986c117ee9e52c84494a55c1e5469f1426218c6d3909ba94386f826c10a67c26eaa912cca365451026d94356c6c711e656098439fb870061b6bffe2cffa46256fe80074f1955dd0fe6fa792b15b7b5d3ee83c92e8e0e9bd712223e984e3a0be1fe274c76f760d029087562c57ac57a305f8b1e1d6b1ecc2e7ad4f9a5783099ec15bff6bb8339c7495a449576305556f2ce72296bb93a183fc95972796c74481042c8f3cf5acfc1f84969a8d5fa3442d57230c9a8feea8dd697521ccc696d9f544576551c0e462f9d4bd408bdc130ef234225533d4ae406937dbc29b96b0f0fb5c17019fa3f8657149d840d2651c7732baf3598048f3e7b6aab4e76d4605092da2739c53b7785d3609273eace1f677f5e643498a4554ad94f9413d9f1198c9fcc44d39db1190ca7f38c302fd9affc32984ed465a98eb7e1276430783c75d24236bb4dc6603a7949145d256230e5d35af154faf76c87c1204b67f3e3c67ad082c15c2697d2299a4c4ba25f30765730c94b5e30ca999e57f4e4ffc92e18ed635fb7ecc5ff920b66bf4ae2762ab7600e7d1d4a27cfa99d168ca2738e995fa7f27559308a2ad9e7795b2c9884efe027bbed15cc2ba78458e8c5d69315cc41df8ba59baa6052ded9d3b253d03351c19caaa37988eea8ea4fc1d427e90bbd242779232918ece4caae2ec9841645c1dc37faf7f6de2a28818271ee5a4d499a6ae9f20483bafaebce75ae753bc19c3c44aa5eba09a6607f1f3d9f5bd033138c6a32c593bb4b30d78f4a09a61b694a2979b424985c2d0927f2c63d8a86048392d23e8eeef079bd631877cf4ae82842c6620ca3afa969db8a613e41cd7897f061b4a6c43088948f32ae0dc36c5eda73ca63e8662a0c734e1f2b6997eb9e4a83612a532a0973a6835e1230cc298791a7f3bf308d6c8c7a2f6194ecfb829d7dcffff47b613ae195f37249d0d6f3c218ab5aa12e8db9eabb30fdffeb29f9dd924e7461d86ded24654f2ecc23f37bdbcbc4380f2e4c2a9e3b4e7dd2fa975b18fc527c7f4ecac2a5d8c264e94185c9762d4c6a61f2c930a13e575a18d552f6f924ad5baeb33096bc54a7722a0b9370e287fd598a99672c8cff7ad2c68dde37818559c5da4d5d2e5f613893f2f44f922b18b968af1ea65698479aa5ed9c7e84fbac3097a68a7dc719b39c5518ce7e2df4e4ad68a20a93a47167b25352618e514f4a33541843c9d36bda97baa44f915222fc047db229cc39a6e9f025a514e6901e3474b2f029aea4305f92426d864661364918dd1f11da9e8ac2dca33b4ee8bc1439148651da4de78a7cba101486731925cf7d8a957cc22016432825c7d013a68bdbc104a5c24e184fce29e72f53c1c7424e98e3bd5cbabf8739a54d98d5e4fd88993ea9e49a30c82bdd75c29e09839fbd27956f553909268c71a5f774782e618a919febbea48f53b184397e99c99dfaab84f9bfe4fcaf4b09f39e64e296ba9c525027618e3f5acdd3f98f1825615215524f4c353bc945c298f2a9c4c95d694d3b481854904f9d5a2b9cccee1126d5f93d7ad6aaef758e30e82c3541da9b6c5e5d234cf9f49274ae7e57d531c2e426a7ff1599351b5a84b9b2945cea278a307c8ad7e9de245549893056aa4ef52375748a0883c9a57b74c58a501ec2a0a4943f076d6782aa18c29c93bd5ab6ba10a6de17b724ec57384b7ff1c38330d7985c2e72bfab654198f2a4104a4e6a5a9403613e91f1e649de2f95058429f8772595a42f53d23f982d4992a0e412f4c48ffac1bcf9bb21d45c929df6c124d42939db9412cc523e184f8af638a54a1247b907a3a9f423dea2be45d3f460ce4fb597d3cc83d96d47b6bec9fb56e2c16079754dafab787f7607934ecfad346d3da9991d0c3bea92f4252c8a685607835a4bb24952d492974407c3bb9fa0533acdc1242e49a1bfe29efe921ccc9adb41e9949224d57130557b9f54c2c16469fed2c28434417f83e164f9fc2eba1bcc7e552a4ec5d0d1416d30578d8e2174101b8c57733d622d073fd11a8c9f4b2853f293ba6851832997905d49fec84a953498b37db455caa2c19c4cea6798588207f70c86ef4a65a7543318e4fa49eb6b49977019cce9aa372cc5899592c1fcd94c6754f2b4dc8dc1a0c45f0c79366bb75589c1247f0c154d8979f5255961307dca5652f894625fcad7402f984281c1b836d23a97dcbf605dc6bbbd785ac8c5eaf86c51f289ac4982ffc1565e30499d8332fb924baeb5aa0be6926925593e8f6122545c48aaa7aff821ac2d187dc6e74eb9a985bdb2775da6c6abd7ebc5d3f569a9da56160c2346794ef9ce55410d3c6a5058306a48efe05a5fc154826acddeee18ff90154c67294ec6e560150cead57639c2945052904851c114dac376baf2d3693b3505c35eae79de393fd8a4600e1b65926c4af688b61f6c782021231905c3b5883a412e37c6836c302828982bcd9224c90a5a6e4e912718f54a3a4ff2f555e96bd47082b93dc6a68d58dca86e06d504c3253968cb6ef557ca144131c19cf27a4b92a4be874e9ba09690a4c52a2c2518c3ed7c734dbe888e6f25c1dce7a6e494d57bb70b09c66cebb174c24d10fa3e3b86495909fd1e5c94bc26c518265982bef0dd9e2d86717b33440cd3a9ecbadb6d7ec8304cc2e30459329e1fc46cc230ca49f15db9aea4124f1f6c3ed0609843094af283add3c88d326098e4fb13c424fffc5c930fb682444066e42f8ca162414bd6764b612e41872f4c62e1b405ab245b762fcc76268a67bf796198bdb45f8613afe3a81ecf56124901e2217d1684ec5bd73ed8fcef5806780793a4bc4a76bcdbb8bc201a37928709d00ee6b0aff77bb2223550d7c1b0196332342f1b403a982dcc7f4e594bbe2e3938076349fa4e89e19e1fb3837230d7971c95837e1de360f0a482d041ac2465c3c124a527a9f49c341584928d1a6f307792f44dc5995cd51c037483418f29a57298c90949aa65886d305b3ca9233f55de6ba0e7410d1fd0280107870818816c30aa992c39467e7efa6b0d66d39ef4a8c5a806b34793b55cb2c8fe5b300d064fdafddf4af2d1602a9db486e797eeec9c3318deabeae7e33efea56630088f23f65bff83924f1a60194cdadc73d6de3edd514206939ce3def3d7640ca69cf3c8d28ffd95223198fc3b89bde25fa353270c0659420a06a3b598b0a1bfc42f98dd84aa28fb9f7126e805c3c45ec3c552b06daf14b4546fb6dc74494bd205f3dcbbe9d91dd11d6711b151c3a3900b66ad68d5237fadde1d8d238305885b3076f05379f1839ed3e1c7a1164c999697fb3e6300b39055d66856cc5bbaac7026a79cdf4b59c482c9d34e094ab89c2b18be6409bae72dade2bf080216a040042e98809a26d00a7cb977785d6d886c85d1258daead94c42a2c77a544f9091da9603cf9ea94ec789eaf94e0140c4add892daae45c4b270d11d0484f402998c410b95ee27a2b58ea83cd460d8f432460140c3aacadb3cefbc14603a1602aebf58a71293f07133ec1d8714d977f3d22726494ed009d60fc142cdef67e12bd0f0ac22698ca2413cd32edb4ad432f036482410997bd8ff9f62f7a1f888c849c11912598367dafa42474b76cab1aa804a3f89f244c8c8b93ea43a3c68c1935108660124cea5795df7f1dd038211e6a238804e3a86579f224aaaeab814064bc40051c1cf908448688d9cb4812101a770c63a95c6277d06fd501a146e2c103746424e5658c243744560535f0a8c0196359ed12844a7e128262182fdd9e891a9d2742ec32561084238651de2c98ba4f0dc33c6362c9134bc7eca84e1826614de5557ad4b5985f304c96f2626aa2c5c1c1c131850386490e7d27c99b17edabe417c6fc53dbf9a2993b8dbb3ab116eff255d3f13b7e9e9ccdbb5e1876adde4b12345e183cfdbbdededc85712d49e275094a5d1894d29f4d10a57d827a2f1706d5d034b58f7f4a3e8b0b732c3ff1e27b720b83e7ac7897bee6399dd8c2942eeff5d80515cb502d38d178f9acbaa07a5bb93c7a4f0be3e7f50adb4fa5fe4274b859983edb28397d9770b230f889a6ae73f0b8658a85d1cf47ffc84fbb9d5f5898ce4407b7bbec15461bebca15a6f30da9a2f7d30ac42e2cca8ce5504b277a95b4b2fbb7362b0cb282ca13254fee746815d65636bb9b4ba3f19d756fe1ec842c13cfa20a838c097f429c1c97479c0ae3792a49e7d36e264e438559dcae4b924c7a8a4259123f5318ecb7cc47c7afc4b74b61ce65ee152f67cb49795214b5bb3aee2bd7a76dad658892b398d5a33075da553979432913af1385295dd8863eb19d47edd9830b85c153570afaea2bea754440e1ba5b8a5a49ad4f982fe9bf699fd21f4dda13065b3dbd2b4149272849d7097357b8a09fa4ead239bf5e384e18d46bc7b84ba10f3619c7ecdce881b94d98d2b7a5aa9cdb7388f35d5034613aadfde741492f13c6aaffa417ec04214a4988e1e038210f72983098a5d56c0f51f273b62f61f4b10f4afea44a091a42219c258c9f4227259c9c4feeb815e12a61f8b060a9c3dc892fdaad0a6ae06183a384a9848659c951547616651c25dc24cc2f4ac57512164a9c931cc249c26c52c5933f6ab288ae46b848942b5cca974ebcc141c2a04baf07a5fd5dcdef7b8441eb99de7aadf4f88f239284d59ff8a88db811e5fa12b9ee107b7b59cd3ce11a77a58212de3946982ddde52aed27d494d8224c9e54f80a72564ded451179892faac43bf3126152feaf972daf890afa21c2f425871357f62fa9b97708c355db7fceb69e214cdb77ba5784acfcd10049f60a61ec38d144b124e57ac9470873e6b789bf3519fbfe06612e3be526990ef5d3f69f208c26331fd5e402618e3ab25f8210f5b59d0e0e10a9a552b1512bfb0aed4e69f13c07d3ae87bc0e745043c62d10ee0f6ec8b9bff6d12b91c1f9c1fce147e4ca8c2cf1751f148bb714626e25df16d54a506a3c4e759658f1f8603c133de588aab894f3116e0fa63ad94ecb98d827057170e8800648c88888c78dd343fa29492d0b7342270f57b2bf92d5925f94c6e121edb157d24299279d34031b77874cc35a5ebbb3dae325b19e46bff4ea23221e36446670eee0ec603ab7ec7152086509ae0ee6ea4f82b613db5309277430aa7b8e65793ee751627330b7c94a9e2773474c2707f3f625f18adac5c1249ed2d5c1c1e849e9e93149c91b4c49861026ba17dd3de706838652fa96a349137f5d1bcef8545bb939ab4b66d244ad6851d7e4d860165929cec7d21a8c76764950693f4689abc1a4be453c76c54e17940663ef2549c9374283a99327c9c4efcae549ce603a25bdc74fd946891e3318e647c7d22e6530ec08919e7193c1d41a97b28dc1fc39aec991264a3ea518ccd7e361e6bc8292c63018cdb4fcdb6779972e81c1204dbb2f1844ebe44ef67bc1b027ac855c3bbfbb0b26a5d484e90b71c11cd482103b275b309d64668210134f44470b06b9962a3c56b260d0f9d35b5d8d05a3e737256cc65730fa5bba7072ba24955ac124447a6ead4fd258f2ab60ec92fa79a194b426f6543048b91735da340583b070c19485f2517a2918f6635ce7fb5130eaa8d86f224ab47728983ba885dc18f917fd09269d24bd52b932490e39c1e8490aed67254d304965951e6b4cae799860104ab4a52429f7a02f4b30a7a6aaf6f77c4a3089b85df83709a696d71a1d91f2be1e124c49990996963c865174cc536951725bd08d61b494214edbb675febc18c613f6c4b3b29c180635af1794cacfdaf961182f9a7af219cf345d18c633f5f3e8a76098ff656c642bc030a8feeb906f3a55aafcc214dae49c4bb6a074dabe30a8d0e14e16a1f2a8b917c68bd6a2836b658d14f1c2a4ca42dd785dca268a7661146149ed6249b2a514e9c26c99a7848b622ecce19df359958f38e1c2b4fde14d56ad3a93bb85592dd693203d4e3c6d618e596257b9a96ecd502d4cf5be7ae2b9cc2749b4302729c5ca836cd7d02c4ce13c78ee139dbd735998449ed83f268a85295b49f23142b030456dcf35695ea94a5e6110aad54a1631492eed0a63bf9eacdeba15464be993480baf92c40a838725f511b29d6f5e85b184d01f5a7c76b316d8ba80d905eeea22f0a103101a17280e5e0ea34001021084e402fe22228f6a84d858000046446eb8800001781d9c1b3aa0716420400022218f6e788d101b09880100020000000040000b00000000a080f71a342e20d2ef35683480012b0262e32c20008900808848c24100019c91e31c0508c019395e2384e300001800041210f21e0c0040001e708020d8e005828cf800c477812023323e448e87470252173666a05c24af3123152071615cf53c13363d086dd25b00e1cc4804485b1c206b812023211f22323c3c1290b430c5afe8a973bdb3307d2749127a61f2a7bd6561aa3e0b2ab7eccd95508d19337e06cff8199bb1c8aa64f9bb1cb38166fc8c9df1335013168906c88c54807c050d90198900e90a933039efe53fce2e5e6346fa1839337203d90a0419f11a336e7878242059617259339959b26398f083fbecc69987bf1040641c10f436448a8dd00041111a202332406e241e212dc8558cbcc88c9f61805485d798910890a94840383352011215083222e3c60c0f0f04e4294c996b623fc8c714e67eef20634adaf81db314064f5be2fdb32895a693a41885716c8ac2943a7b10f2f4e85bf364288c9e2dc8bbd7b7fe17144611622354e446e315e4274cd2579f9f245e8ff484493095bf53fc2a41cb101b40f84e9892bc8e4f52d65f7a13274cbda6b43de8b44f3a64426ec2e8a62b3c090fa6e2b7d4c4952aa558a92e7d651bcd36794866c258f964dc73ce0942942270c1041213e664b2c7eedb4a55335ec264f52727597c63d52a6909e3765ae94f59a98451fdc4932ecdfd9f7a29611c4fd13357ee244c954f98d60f3296ed9384e1d35c0a3e62dedeb48c44f2f5ae2297d6522be4b28dd07ba71adad9848439bfcfc4550ba33dc224687a16154a6bfae97070a4238c955f6ed1ac64bb20fe60f3b05163cf04d908f3a8df761125c60873caa8ee9ce453d7cf2fc2f4ab57823a71f74b5a146192f784caa23d904c8449ae54ce9ba1eb732d8c08539e7ac9a3a64b16cfa143983aa7e792f25677bc3b6908e30533adabb2562a540883ca1bdba5122184f94bdb9b0e2ad7c120cc7f5549befdb5fc1c09c270f2e93022fe48e3200361d6bea43b7a98db5c8e0e006170cb5b51a2a585ca655243860e3c465ea4d81f8c6392e82728a52ecc637e309ca04a92f298c925057d1fccf94f2a4916bdd926a7201f4c3a66265f540e22eb0f6a84e8600fe6dd3693c3789204bbfd834d0fa62ccb97041f59e447b4069907a3c55392924eec9c04b58c0fc183c984efb2587ba972971b7807b358e9854e2e27fd441a20891d4c1e4caf6ea7e8880f505a1d4c9220ed9467e9d7ce061d0c7736d25b3cb6ce723f0793a404d9498e92fb602b72304949dd79cebb566a486226838c8371b6f333c7a46839367e4424657088c68d942a483898f20815d3c193f20daaa8979c87a757fecfbd56da04991d0e0e37184bca33f7bf62b6e19465477f967daacc68241b4cd963625e0304366c9c909155410d3c6e906b3065c4c2dd67cc254ba5de56f24dd492d4e239483598d693f89877d134985d3cefa9ac9dd4c8190da60c75722c693c4b797f06c37a9c1394ecde0cc63329995c921cce949c530673573ce9697d2383612ffcd8889534f623442e30432167390663f52561f721db90111b2906d3e7effc24491eb530988338c94b54f23269a604832673c962ddad5adec5ed5a56944eda7112e4174c3254099e37cdf482c13bb848f5f4c92e98db243769bba28dff13a8a0061e2e482e98542c754bc7452fc73fd86cac6dc1a467ba3d94ec95fd93c8d91101c1d4827188c0059d055350c2633cde050ba6a454a509f369547f2eaf60b090a3f27cc765f52aad6036314ae6b3831ac82a982ca6c71331f53962ccc54052a1b94aa55ea72d6259ede5144ca17aefe24af61c264f2998af3ca5d352b28a783ca360ce3115fcb2bb75be100aa69caff760a97c82c9478967f1b2934e30951244454d5213d7441f6c2320890648c88708da07b209869393eeb3133da88f950e24138ceb2979c96df15c82179eabe7243d359e0f360fa3412ac1e8a59f6295ee83d13c904930894b7fba6e3a5b094a9e4622c1bc2545ff1e773fd8f818a61b517aa55f9d52461f6c42e8c17b84241e232033decc18e6fb64d9d1c35a9f30fee0699462a0426c8ab4301a0f6880bc203d80c43056ffc9e1243fdb123e1f6cc330d8a7cf262bba73e3000ac3a43fa4cbfb97efbafec1e6f13e001939323ea42e18d6ac59b433710fb54ad1732895a4e4e2df0829d6c030f8a8784e2a5b481715d1b8210210fcc2e47174cae7259ed2657c726478388d2f6f230830f819274404692f03847d61d269eb63ae27a47fb017869357c4ecaeafa8517961be8ba6d2a9b57117a6feb292e2093a8a757461182594b01667920bd3769f24fb4fb034b9425c187c2feba756d589ad6ec1c69bab5a0adfa51dd75ef25e50137eb0d53822201c1c323e3928e0e07819203666d4401c1c2c40c1a22d0ce22bf7c2dce42446570bc3d5c591abf849848ed1c29cfec4ec5de66fba6916e6184b4a897f0e29a69785399824a8fc21be047142b1306a7ce509164d4ed21f58184c5578b4e4c19368e32b4c9dd4ce6d4cec0a53692dc93636bcdeeeda56186dacc489134dada88f082b4c925ab14f4ae9e0a75d5c853989e96c71848926070668daa80a9368e6a2ae63d2dabfa9309992b257befc537e212acc16a746cfe675d78d33c0536061561d6fb39d366aa77da0298cfd5be779f6a4507e82a530b577f04b625ddf4ea79014e620c65198c3a5d47759944461300bf5ef24e93db92ca130697a0963a269e75853c9fdb48f50e169003ff103f44410b013e911a7656aefe43349c809c3b48285db8ba5ec953a4775f1e0260cdb167af5834c13c6ad784947e4a5f5e83013869d5382dcd88c983086502a7e5749f2323b9730ffecd58e7f32a5e7ab21a3aca025ccf296e3f2f9c70e0faa0496bf4af5c196dc10c112e880064872e3053b05a484d1eca4a9f211da4eca380993b0f8d1c46e64953c3ed0c899718792304d89956a374c3ed8426c00e13d641c1f7c62244c76af5fb515e4724148ce1ed1382322344a900609b369114ff3a21f7e1f3e42b170e9dd6d2e887a59c54fb2ffef9ca90374844168d7b21cd637c274c19364d7297950254698e452be2df33f96b4651186932d5b8a3028d3237af47789308baa8b735751829039228c95e4bcfc5582fcf2d0210cbab492fca8723f416c08a3aa0725cb4e8cebfd0a6174cfa1795a374218439ab60f2dd5208ecfda56595171d30ad71022ae65dcca4f4198468bd82dd11d1716d141c8f1b00b84c1646a494a673f893c68dc08f1a8e108085332d1d3abb5ac05ff60fc2d495e4ada154ae68a7e30e9132e9fe8174fa50941807d30f6de9d9ede356444d33c66dca071c807f3788f2511d332a2f4b80783b0b21cc6b41bf22325b002eac1246fa851f2a8fc1425f6c126e2a1c6031a373e46540598878951a8642c858342d27030168c44025130105236cb0100e312080020601c8ac502c2509ecab9aa071400037044263e3c242220180705428138200a06c3602028100604018140301004851e12b5466aec6dd202d642c7d1043a3303790457422b52b1483765c50ec603e390758149ef1a47ccd51a256ad7a2e30d23725ebd5c390a40dda5cabbb9717f88fa83d8b896bc0928eac4fa97dd102efab30dfb58fa3bded1b9d10e8546d76a118e1c7db2e8731f9f7d1d4d1ccaa4110e4e65f18d10bd829455e32ffae1b30f91a700bc1ca8a529d8343d6a8c7151890a723baa76852b405a0c5136421b3b0652fa88f6dfc192d60c7b43bffdebbab681df7f89f73aea123ce3b6576a6073f0fc6250e4f2cb84ccc00f8aced3ac643766987c360ba84482a1b0f5c1aa89f0bf35e10272e09a962168652212740c44ce96238fbd12658eda8941beef21a08cd2a481450f73a0818bc4417dd0255067cf95fa01007124cea80f88831e7652235a0a50afb1d7e731cf341e044ae71b71316280a05e5c349e3fa2d75c7f7a3f2d8db63885800b15fbad77d2f0671046049a64c10145aa098011d16299e65000abd35f9542496231ea457845d2a067cad9e46a030ccbbbb8ed374887fe5c083bd8fc8e6cc4ebccc5cb17862b9da243a72582a8118a599d8b36ba711a87d89898b4002a655995ca2be404f2a23bdf430e88093b2a0690be06977f38b56e30d0fab96c7ea6cc82c62cff16440f2171544c2d025c61f1925ad00fb72917faa9302493f43b14ee2a6f4e310a7bbcef9458daa0e7863a7c43752e0fe720907224f53870c716e0a3225ffb68efb1cd035717e80f26283493f19f6d3b39b8414abe59f047bc24ef9d6d088edc2de906c7843cdd04319a535bfd982e4459fd26ca2b9b1228e28e41497eeedbaf08a387f5a87a5e51bfa44cb2b9465972c196215d675b0f2e6260dcd11756f37f755e2c86b95d9a01d8102b782d16da29d22b8ae7b4e6f8883ad1f322e26fa57c80bcae8b747476d411b7adac62c233c2619ed943a4b876ea232ad1861471462392ef62494bf68e048d312d54568079b57794281c91f827e3c0a18f157c5b0e1c65d24a58a827e17c1a5378c930ffd84f3238cd837c11b905fcd88eb61f864b6079f339f45faddd987b29f6f8072eaaec4800ddb11689b7ecd78fb5204973113050a121e0fac233b40a106490989d6fff2515f0da8486682c5b705cf471df6279ec4965f206ca8896e32b9c34d589a659f18aa264672d106e0e19b5d3b1c66f6390c4519a8cd6d17f805b2f8677b6a7a8bb647356f9bf3effacac6914789ef01a39d5d775d634ec2c8921437e56501c775ea6f98e4ef902b0adf5ee88f9aa49c5f7010e6b16e6efebe134896b0c5d3c43afdfc09ae7f0588987216b055276cabb102522c103b7badfc9c42befd4e2deef1f42548e55c7de9985927e5a47be46ce1ddece4bd331a29b5a9d035deab7216ddc26c9261fe9920ea204740d41889a950cc5561d63b0070a4f8848e66172d69fb4f4a9ef5ad9a29ac9fb3ac05e14932e6ecae241ff586980da5d5d4a733f2f2a57d089ef538953137aa0883dbbd042f48f1de869728dcb57129f478199153eb1a7fd9760b4d585c6c808a537107d605516310bec2e7388d483190439d129e099ad6b04d5548a42ce1982aba0223972b107efb78e2568732db94ee9bee544b10f31adb6b1f9d8867460ac1bb5c450e40a5985c0c316ff80aa34138a361bfb291b466255a3210c72f296335e37c5073a314e7775f146eaf05ee224dc4393f28a7e8ca114448643bc0083899467574d3303e26854d5aa238a4786530804fb26961674882cf1a3f164063260b83eaa32e434b781cfd15f4a93c3bf8358b63dee87d00e9828574a92a5d539b45c37f24bd7a8c5d683da32b2f45e6859b6bca23221cb529d871ad042bda4cadf28ea4ba5bf07521e0cb0089868bbac1be6d46010ff54b331c81164211d7baa4711a898b9b49a27257f7e4017886c659e9aa19e4f9bc9bdda25c66d0fecde1dd224c7b9eab77faf1fc35dddf7aac41fb3b8102a30b9442c76cb10dd6b614bf1e3e692041d902e9b4e035a1d82665a484b778bd1bbebfb73897f9e8e0f23423eacc2d750314760364849fc49bfe1496c7f5671daec006a8f4b8305a75c00b937b20c78b4a94605cb0ae9324c8529847636b603f7ad512d5e02c681621a52f767082127b737acebb3b5b11fd93d8039027f1a47afda0ee071645304ce4bd77e458ee384159e6b5b587efef9df2066adc1e2932155486c8ab88e3a39f5616307c6ecaa44b573ebc61c2081d89d8428e997d17d016ef43d30b788b71182ab1e471588c1cced97288a4640029a6f51a74a070490db2c59403c751c0aafbe6102aa00dc361b6bdec27aee50f94a7be8695ece5ec5dcb98a80a8e18d4dd63c68bfd2bc9e14d42ff2ceefa39cef1e11084f0f1a99495a9d4c9f412748abb5e2fd1e0f45c9bd0608118b0a4460278b3723903530dd94d965419d70ee3a751e3e04a9e15008f8e51223fd921cd8a55f2ca5702231a0f64920d6b8679e79eb2c9ae29045d1bb37d4905abbafa59cbd9c5b197790ba6fce4a497be58a6a2f7a1cf12591b29226dfb22d392429adb9abc66b59f03ee64fd6d081af2197e63f971f24841804c03b129090293e14dc23bdf2f50bcd29f3e25e29e31f0b9ea46ef955319c7d788e140e97cca1ad466a0785adee2a000653f7b5b09b13463c8368dcefebbb4e761c2941bf36b6dc215e57731fd5c6633297dd722db240eacc4c98632c89e6a23d3e925db23fa3c0d4b176cd1cbc307bd7b22f6081302011e1eed53f45c63cd3d845e220701a9600d2670f78b802e9fc65c602827741b57fa7653382338de1e8f7d485d20a0bcc46f2472c9fa2bebf602c1e04ccc41210268a32699e42e3b906e50bcfba8837ca9235d17958ce868c2615f02ef8e811ae58cd7ddb196f550b521519713a543e5f8a62024e558258d37082aa6938d7e701d01b83d938945a8c84b4d1cd167b00ba7dd9edfe07c50e653f2844167133d42fd970dfacb889930514cc0581bc948ef89fcf0bd66b7968021791b468518ab65870c72a1ba2ce420d3bb897c5a55d275278d0fa7f67e638e742e83e872b2302b4b5f7bbe0a70d290cac1ac56c52d24fe589f3e1d3afcb49127ddae7004546bff062c62ca2dd8885450a3219cfd50fd84e4da6fe0fac56bcf6f9c6bcf64278b465a209adfed5c614a057ededcb7bff2c0088965aa85afcfc4d04273366da26af227c9971d84092bebdbffd14a074121d1ba617910216d1bf8edb6371c10b601340450413381867dc080cc733a50fffefebfafb7f80b51b00fdb7cdd170e0f13237e0679bac452723101dc83234b5bb1196f5165dc2aabc208994c186295fbf700b7d18e255965977a86f0eef1a2fca416265cdc114473f318f992231e0982fc26a1f35c3f8f7f5a43736b3dbddb3d1056673b6da7f83a3b609a38a86d8abb86a566bdda4413ffadaa1d6551aa6a0317812f3fc1c7769a3b8417da10e8a08cad44af99a7a8b45329702d65d234dc41b0d419d5e12c5abbb88b95e9f8161b439610ea18a474f62c40284c81380bcdf8c038a89ae78c34098f036c5047fa69d3dd1b878c53dfe695b5f8864d32d390a5d7ac2f38cb4d087e5984056ea6afcfa9ee0234576a10a784af94d92fdc6f56543da896a18e9d798f36a8506ea22f48f3eb5c22052a76de41527395f18b92e0b41ce1e4f41026061715a73fd0ec0eed3ffc31e1fc66525340bae443a40628a57f349ed51beda8ae2359c5f76249fe3f9f8429858fa18208315a9a298333d2380014232466f68e3d545191ccb7ab62e2a2984d5bc7ea28cf47c366a6218b6e0a8f67ec25ed86fb8b580b2cc49d5d9fa7647a0ad14fdb25e430260a5c28218c6e48745e3c70a16aacb1e4f074503a254cceb27b9cc0fe3cc191a1c72d1771522c51ec0a18d9cd7b6d9991028474346be9c31ccc2cf988bd23c0ee7c4cc1f4f6ab499061df36abf8824cd42b292014f9be97db9a9331eeef10ee40cc8561cfcbe8e4d364cf0cd954ff8d7ef613bb4be68fc195bc2ef2477dc962cc25551bb74b22d0477100727b0964eb70bbc1ac0baae647db86ddc9b4ab7cc4d0e12f150e764f73ab808b767bf438e3a5d42f9fa298dc7abaa9639303301689d189b64235f0eb415965d1ab3d466d6c6d1b07d4baf6075793f83dc77e41e28cfb80885827f8a506d32265c5918d036c3b23798a08f4b8268671e8435a73d7d9b09eaf040dba1459bb5d3375286baa334784e1864f848bcaeaca8607d2d18571c48938125c65eb8dfec2fd33904df9d443641eea5c250f71caddca2882c2824851a965b2751e525cef9efdddc11c54158d7dcf74f83f29fd681d17852d9d57f3948befa84fbec052cfb8109234973ca65d8d9e6bcef4f9854e5803b3aba0f2e8abad37568cc5464bde14ca3c5e76839ae278c151449cf0bb104080756b907292fe9ca1f72a631ad64e02e3066e31aa73d1b795b1c0ac4939a7314e94416dad496d5fb159fe49f9a1e5eb428c573b8aa35b47385f21441503d741872fd1730b2cbe066093f3d511c0dbe264f381361cb6f93e62da6e55c6445320de65a10040c8cfba98f908752c813f049a27450466a0517d21ce714b064cff6992cb05c330ad441f9b7862a904439ac5ecbc22b8e72375c6fe8d7c955391cdb338bff23904a681f7176fc53f05aaa07bf301b23ee5bbae34a9354369c25aafb7a39a7767509f09f5a364eb3ef5918d91ec179b9b594392f67a8fa435de527bc133ad6b489b2cc523252e7101ef287191f1f1b9854e4d2b16034c533418f616e6c3a96d426c1e246a90e70b8edb2bcd8e6e21fba0dbf250ba4d465dc788d6b750c00cbfba7e2477bf29fc19015786a729b9e3058c35d4a87b0236f7c74341f05766b4a6c51b7f990ce6736332bb7fa53b9e2882e5ee4ed3cdd0379cc9711b47fb751849d1f75f8bc1b060da4c1fadce78177c2f5c4daa6ff1c6d23eb9d9aa9b6e71369f96e243fb31702478756c77a7bebb81d6d48d478dcc6f9b44f43f9b8f29626f7d994b2fbfdb7c55d4f231dda56dcb0a04bfae52a949e6222815543d7c8a555e8241b5bb7173f9dadb2bf16c6d359273bda357b1ad603dc39140a0c02de290e0ab967f7d379854037bb712fee010c1b6e6b09d8e791fa9f9cb3bc5e60486070ab8986d4b3c480dde0e139601c63c3db6e80381975661f74cf32d8af7b38a2c15aa16710675ffa66547743fedadcdad5306fd14dd7813f249c0ecd44af802d327629e3f81be6b1e6010e004cf56a4faf4880a5e88267aa3a6143b4023ba0ed8f2d6bae6b3be370c09e5b89816926aa207db24e6c87cc073a506722c5a2d12d164a4fc0b025df68d9a52341d4142cf498c1505cbd386eea446a0dc7d8882e70b3adaac404fb45050d1eafc23d4576b21188fec0d7a55ccfc5c0b11104abb3bef167a6e10db4168d7bcd61d62c2df5106c33f57ce2cd51f9c26a0010e084986ec05be05b01c67d8e24fa75201be42b8ad29d07e5531891819b74cb2e78aa619632a0569c3b08210530dca661705a0b594a0f7b21cf172b7ae737aa68d87521b74265216d15274714d8592723b617e08ac5b7080ff010818583fa4619a6327d301130e484633028ec95fdb9c1e4906da575af185d373bdb36a85b4544b350418102531123f04398b8476dea4aa34d8c916dc5766f20dc60760d6fa2cd0aa41b9241ce69291ae44cb7ff4c2570c5b68a30001c1dd0fa442f211b1033453a6a1d3dde5b2298cc981ef9156de0659c54ab5fe378236de6c201e347b168210166de6508e669563220711a4702d104c0553efbb62bed3c724155ee6b7833365d579124d058ef11269c5f0f9eeee6ee19ed91bd1bd456d8ee55ef52f6673fc3bdc53f5d15d61e617c144c35d3752333791a4327592d30f0bb0da70f402e6e2790a3fbf842fdaf8da4b15611049a6b1f4a10f5446041f13edc03c0ffdb413a5ffde64d99e149fd68d044bc15b2c549f6ca4ebb5a80e1a4f9123eee4d989aaf079c113557af7f72a2af066bb5f58bb64e75792814005e2040221f6560e26ba7f67e8f86d98916a0442bc6a16464423454616b05976c11325b58510740ab5b4e222030173489cd49ebc29db438166d9a4dc29c8dd41a9c909833e22a67e56fdd3ea909ec3134f92edd68f6fa546e32e9803d93be02884671a1bea34854efde78ec6ecbc2487b5e2e6e48ab7731f0463cebb7bb9f6705b7651e911ab10027e22a19c9dfa324bf79f58f9550a965bda60f8fa030a6b25d7066f22df78ab82c01273d5ef08059ac93da83b522fc007601385e234f9207022f1bf4d96ca922b3d0fbf056b0aa5917e05a89962c79944fa505135c0339cb9c0a628096b65abb3fe79304a144fb142a8e0eddbbd63220c0f5420e1245927826e4124f7c2df0c75c9d6bc512680fb4bb91bdb0ef343ca5ff242152ab8f9c9d92af3b9913036e290d36d60ed53254d15ab979b5a2069914a0bc11fae5477c133419a3f144dd01704a3e7394026ec52d8f0dac21633949149988a4313cf04aa59ab45a70e03cbd269034fa25297cd3f4cf0185e94cf02cf8def2d91642c560bb061377f9ac56471eb2e908e0717acf103b1987701ba470dcba4c3147be0a26cc1915c99eefc75186ff1252abec10e15a52c67179ec0a4ba586ea31b0642303260191e6e2d1d19deda9d37e4cacde5ca9526068caaf02b6233613aa1d21b1473d9ce2e5d95ce5fae69681356248550d7eb45ce136b8028cbe1df541cad7953c25b197ed3adf32d3f9dd3442552f7767fc5c25f445c1a7ea7461007a2e3c30d76123307ac379982cd193ec6cb1d2a109ec509f0588a1b70a4fb0def95285a750f55987fd7460a90a46fc30c4dce33ff4b000f0e63bb1298c4681830dcdaf23c8a67db30e028f07db816a5ea9606b94f235838a5aed97c9fd322b9d37e887aa7596c4d79b6c5ef6715f28c44bbc4abfa191fed1d3e4527f0e0ca7865daba8858234f57140e3cb1e754f5c78370c587dff4c6e5d8f2418142ab553573f18979b05f95c489377cfa275a50118d20c6a33bcb519bb26b5e2c495b9a3a6749b6b4f921db042f860729ba77af44107b6a1d676c4a7c99b86565d489f1b302186761f4bbb386773373ad5839b53dc23655a6bb4d20cdce7d4dd7c0ab8e90211b3cc0cf6a2308ebfc83fc0c1e834d6dde20581862b576c5179b4ecb5f6a04a3f784bb73543a44b59408ebdc6a1ac8031710cf5c31171493c14ac4c3f9abb283cadb04a5adafc9bedd363e5fbeba67692caa69abe260c0d791cfa6d37e1055381dec62006db84d9f47421ef5e7268e5f2a85b3cded468531775db7d6c33763add8c7f2b89682527c30070e44b4d54102b476757c037d26d21613222918291bd762089bfe886d8172676723bd8c176f67a69663d59d06d2ca0babc54bcabf570a740b848f8987c959eed4a236bbc73ab7ca7138cd3962e80565749aff806cebe8bb2168c44b8d2d3c1c2c7fe26078956a5e887925ec48289f394a19ad789321abd6205972db2e6144671c9ad757a31db7fd97091db7e875762db7c01b601e4862242eea8ba4d980107701b795616499789361055ea7da88d4b1ff4c0455740b9cb2290e2d6c69268f7596bd5889b86f0b92823b0e43c4955236ef6f26c6dcd4adb9917de652216aa62b551a55af86dab96f4263fed1278db316990c2ffc914b2fe6f424254cfde29d37699582021d6708ff2ab76f21ed6a39b13054b6b3fa60e9ed2b681b050dd4b0af3efaa0ec45a912e549b4b682d289b1df507464d66ff2ba8f2e45e9d3bb6ec63c10fbd82dd9a6e49c945233587e82da87eaaee4490390263344fa1660982dc1959f12574ffde37845983cca07922fc0f75319ab217e664ebdc79b194e02ac958ab816519587e1acca4fecb0bd963aba110cc0a754e3b1413448113ce9cc9dec2b2c9880ab7fb3fc4098a8d445926786c6669651713894f8ba69b0698d636d372b785030a76773c2ba0a11dadacb9cc33f18f76e9134898c88705e007f12d45c3f696de4481a13c4486fcd568b47437371ceaeb5c3bbb211b4f9aaef4c1e3a0f96683ef45aec90f51bf23e2dc2dfb9ed2d2cb7fb80c7520b9dca4032174de30e58bc7f9d9eaf40e869664e87d170c6953c16d12b4758d79324d47c9c34f97aad2aae7eb61c798f9af34c2b96625e0536c5b0931fdceedec387b929fb164a0d5715f16f3d2de3bdd5f7d282223f65dc46e093632d9e08cedb10ed5d3554c263aecdea0540a43a5d83c29314bad4497d3e7f1c1edde729ec51d853d29c15977801fd36064ec15ac8fbbd59c7114f4dde54835c9ecedef3255a250cbbbbcc45151c92a692a04141c321052343bd200faea101ee8c72d9e605146904c45061c7a3acf1d34627048a871c6deba7dd1a8e9801162eb2b0540608481cb0e7bee262d2b89f93e462ce078802405eb63f2695d4edc9d3cd5c02a22b6c7bd95d37407b29e2a113da40679f1304aaa5ca92aec08acdc985f77f232a0b0f9de32c0fed3bb13b859e65913248b6bc2b71c186f5ac48375fd05bf7f45ceece8ca65b4ce327e75264d1d12486b1b6e709726058d8a0068a1107ac1e01a1b6cba60b1ca0054543059e9b5da1ba58686329ec3245355cf99e2d04e27d07f2446e8fab54a0ee41cd453004aae1b66c5d69e7a2a8a9f87dd546f7321d027a2c7aa358d0405fec0eb110622567ce455278607f05c1b032c04f3f1220deb220e2fd5f5552769bcde517a988ce1354fc0425935c8d220ea3c853bef1109994bebc66f2c26c29614bd832f0d890382d35814224e0c173d2e83b37cc3d8c8779e6a272c324ff5297d09e2f234b3f3c256219690a43d4c2d585a94460249e5c0c09d9448e457cdd9921bcef82f63ff8c0c8ca4b410345011c01f28db20a6d39865840299fa90757222b5f0da0331ec443241d43b115a342ef2a6c5c3395ee0b243d2bb52c3b9a82fda19439d382d48894b31eb3a24fa1cd85f42983a9c3dc992a52230a8d91e0e4badd33f5fe7471d01ba4b7ae89b7ee66e220b4288403e51640a34a984a3367834bb2ac8db6bce2341b144808087bf0cc3de51b9b0abb71f75d34f1b124c4a6b5b1c95897b20d828842804c2141cda623d4a2ca134e219d7dfd0f2754e25900a3dd8a30f1fdc87e27f58958cd7af7d8446a7fb3d96ade5e1bcb3f015de47e3d24db8508579347befe9324125afada274e2981d0358d5248a18122e124b615985a6c0eaeb99689270c2626c2868649c5ba7f6f17a0c7438e648f89d0cc57e7463b43a9262f489b0e650b9fa04306300b20632a90e1b6f4e3d69b3a4417cead386a6ad8bac776f04447afe5c4d6868fdaa95000ad5e1ba60af4460e710147baf586fb7a8816adfc98bd6d43932cd16b56e1d04d9d5d8bb032f57e89cca12d3cbf259ce0a8b4e00f8341494a94aeae4915a8f8bf7a202a3ca1291fc211451a6326810a12e25b1e4f9b2b41b44eac41bb016ac6c0d8c19cb8558052632c3892187a008f60b18d9471c356e8082187c6c16af703249cb6ada558ae264398b686694036bfd2e64d93506574dfac010bda8652fa7792b4a1032fa3b77e7db3e9a9a9d665b50ec1491e483b6b93959870cfb8b9a3e2c5d724ab616f69b970a425ac3aba6fe3677558506eae0262bc934d8a1ebde696c56230e5b55ba283e9c44c117a9e745f38e26cf98c39a435e1254db2813ed291394a19e866139934a3623652fc44808d60891689b3d2a35be87ad5ebd891bdf0c03d1bdf495c5604e5461b57736aa4b802a43d9b406ca00adcd9f337f13b8c64a7e58700aece3d08afe38918b9e9bacbcc02e67a94d16abba965ec0c53090747b6e93864c03897375fbb6ed9cce93bf84511dbab07a8cbd75abfdcadd7b0e099b19bf371dfd0a", + "0x3a65787472696e7369635f696e646578": "0x00000000", + "0x3a6772616e6470615f617574686f726974696573": "0x010888dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0100000000000000d17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae690100000000000000", + "0x3db7a24cfdc9de785974746c14a99df94e7b9012096b41c4eb3aaf947f6ea429": "0x0300", + "0x3f1467a096bcd71a5b6a0c8155e20810308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0x3f1467a096bcd71a5b6a0c8155e208103f2edf3bdf381debe331ab7446addfdc": "0x000064a7b3b6e00d0000000000000000", + "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x426e15054d267946093858132eb537f105fe52c2045750c3c492ccdcf62e2b9c": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x426e15054d267946093858132eb537f14e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x426e15054d267946093858132eb537f195999521c6c89cd80b677e53ce20f98c": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x426e15054d267946093858132eb537f1a47a9ff5cd5bf4d848a80a0b1a947dc3": "0x00000000000000000000000000000000", + "0x426e15054d267946093858132eb537f1ba7fb8745735dc3be2a2c61a72c39e78": "0x181cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc208eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e", + "0x426e15054d267946093858132eb537f1d0b4a3f7631f0c0e761898fe198211de": "0xe7030000", + "0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429": "0x0900", + "0x4a83351006488ef6369cb758091f878c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x4ff3897794d496d78686afcfe760a1144e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5c0d1176a568c1f92944340dbfed9e9c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5c0d1176a568c1f92944340dbfed9e9c530ebca703c85910e7164cb7d1c9e47b": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x5e8a19e3cd1b7c148b33880c479c02814e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5f27b51b5ec208ee9cb25b55d8728243308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0x5f27b51b5ec208ee9cb25b55d87282434e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca0b6a45321efae92aea15e0740ec7afe7": "0x00000000", + "0x5f3e4907f716ac89b6347d15ececedca138e71612491192d68deab7e6f563fe1": "0x02000000", + "0x5f3e4907f716ac89b6347d15ececedca28dccb559b95c40168a1b2696581b5a7": "0x00000000000000000000000000000000", + "0x5f3e4907f716ac89b6347d15ececedca308ce9615de0775a82f8a94dc3d285a1": "0x06", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe700e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc44f9aea1afa791265fae359272badc1cf8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e13000064a7b3b6e00d13000064a7b3b6e00d0000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc4de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f13000064a7b3b6e00d13000064a7b3b6e00d0000", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a000000000e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x13d4fe63a7b3b6e00d13d4fe63a7b3b6e00d00", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x13d4fe63a7b3b6e00d13d4fe63a7b3b6e00d00", + "0x5f3e4907f716ac89b6347d15ececedca487df464e44a534ba6b0cbb32407b587": "0x0000000000", + "0x5f3e4907f716ac89b6347d15ececedca4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca5579297f4dfb9609e7e4c2ebab9ce40a": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca666fdcbb473985b3ac933d13f4acff8d": "0x00000000000000000000000000000000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a000000000e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca6ddc7809c6da9bb6093ee22e0fda4ba8": "0x02000000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e169030e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a000000000e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x13d4fe63a7b3b6e00d13d4fe63a7b3b6e00d00", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x13d4fe63a7b3b6e00d13d4fe63a7b3b6e00d00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade980e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x00", + "0x5f3e4907f716ac89b6347d15ececedcaa141c4fe67c2d11f4a10c6aca7a79a04b4def25cfda6ef3a00000000": "0xa8fdc74e676dc11b0000000000000000", + "0x5f3e4907f716ac89b6347d15ececedcaad811cd65a470ddc5f1d628ff0550982b4def25cfda6ef3a00000000": "0x00000000", + "0x5f3e4907f716ac89b6347d15ececedcab49a2738eeb30896aacb8b3fb46471bd": "0x02000000", + "0x5f3e4907f716ac89b6347d15ececedcac0d39ff577af2cc6b67ac3641fa9c4e7": "0x01000000", + "0x5f3e4907f716ac89b6347d15ececedcac29a0310e1bb45d20cace77ccb62c97d": "0x00e1f505", + "0x5f3e4907f716ac89b6347d15ececedcaea07de2b8f010516dca3f7ef52f7ac5a": "0x040000000000000000", + "0x5f3e4907f716ac89b6347d15ececedcaed441ceb81326c56263efbb60c95c2e4": "0x00000000000000000000000000000000", + "0x5f3e4907f716ac89b6347d15ececedcaf7dad0317324aecae8744b87fc95f2f3": "0x00", + "0x5f9cc45b7a00c5899361e1c6099678dc4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x5f9cc45b7a00c5899361e1c6099678dc8a2d09463effcc78a22d75b9cb87dffc": "0x0000000000000000", + "0x5f9cc45b7a00c5899361e1c6099678dcd47cb8f5328af743ddfb361e7180e7fcbb1bdbcacd6ac9340000000000000000": "0x00000000", + "0x6441fb391296410bd2f14381bb7494334e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x6786c4cec8d628b6598d7a70ace7acd44e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x682a59d51ab9e48a8c8cc418ff9708d24e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x6c63e84bfc5a0d62149aaab70897685c4ba24bcd9ac206424105f255ae95a355": "0xb104000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x6c63e84bfc5a0d62149aaab70897685c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x7474449cca95dc5d0c00e71735a6d17d4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x74dd702da46f77d7acf77f5a48d4af7d4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b150e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e01be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f00b304f91831830500622780fd38770500", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f0001fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860eb304f91831830500622780fd38770500", + "0x74dd702da46f77d7acf77f5a48d4af7d7a6dc62e324093ba1331bf49fdb2f24a": "0x02000000", + "0x74dd702da46f77d7acf77f5a48d4af7de5c03730c8f59f00941607850b6633d81fad1867486365c5b304f91831830500": "0x01be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f01fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0x7a6d38deaa01cb6e76ee69889f1696272be9a4e88368a2188d2b9100a9f3cd43": "0x00407a10f35a00000000000000000000", + "0x7a6d38deaa01cb6e76ee69889f16962730256ea2c545a3e5e3744665ffb2ed28": "0x00020000", + "0x7a6d38deaa01cb6e76ee69889f1696273f0d64e1907361c689834a9c1cb0fbe0": "0x20000000", + "0x7a6d38deaa01cb6e76ee69889f16962749d67997de33812a1cc37310f765b82e": "0x0080c6a47e8d03000000000000000000", + "0x7a6d38deaa01cb6e76ee69889f1696274e7b9012096b41c4eb3aaf947f6ea429": "0x0300", + "0x7a6d38deaa01cb6e76ee69889f169627ba93302f3b868c50785e6ade45c6a1d8": "0x10000000", + "0x7cda3cfa86b349fdafce4979b197118f4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a8910c174c55fd2c633e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e": "0x04e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a893e73123ebcdee9161cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c": "0x041cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a894f58b588ac077bd5306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20": "0x04306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a89518366b5b1bc7c99d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0x04d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a89a647e755c30521d38eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0x048eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a89dd4e3f25f5378a6d90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22": "0x0490b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118fba7fb8745735dc3be2a2c61a72c39e78": "0x181cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c000064a7b3b6e00d000000000000000000000000000000000000000000000000306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20000064a7b3b6e00d0000000000000000000000000000000000000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d00000000000000000000000000000000000000000000000090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22000064a7b3b6e00d000000000000000000000000000000000000000000000000d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d000064a7b3b6e00d000000000000000000000000000000000000000000000000e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x89d139e01a5eb2256f222e5fc5dbe6b34e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x913b40454eb582a66ab74c86f6137db94e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0xa0eb495036d368196a2b6c51d9d788814e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xa2ce73642c549ae79c14f0a671cf45f94e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xa37f719efab16103103a0c8c2c784ce14e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xa42f90c8b47838c3a5332d85ee9aa5c34e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xa8c65209d47ee80f56b0011e8fd91f504e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xaebd463ed9925c488c112434d61debc04e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0xaebd463ed9925c488c112434d61debc0ba7fb8745735dc3be2a2c61a72c39e78": "0x18d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c", + "0xbd2a529379475088d3e29a918cd478724e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc632a5935f6edc617ae178fef9eb1e211fbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x047374616b696e6720000064a7b3b6e00d000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc66f2e33376834a63c86a195bcf685aebbfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x047374616b696e6720000064a7b3b6e00d000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x0040fa7f398074858a02000000000000", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb30e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0xd17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae698eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a488eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a488eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0eed43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19500e3a507571a62417696d6f6e808eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19505905fe216cc5924c6772616e80d17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae69": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195066b8d48da86b869b6261626580d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19509d4a4cfe1c2ef0b961756469808eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950c9b0c13125732d276175646980d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950d62c40514b41f31962616265808eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950ed43a85541921049696d6f6e80d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950f5537bdb2a1f626b6772616e8088dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", + "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x08be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25ffe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x08be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0eed43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860ed17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae698eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a488eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a488eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd5c41b52a371aa36c9254ce34324f2a54e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd8f314b7f4e6b095f0f8ee4656a448254e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xed25f63942de25ac5253ba64b5eb64d14e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0xed25f63942de25ac5253ba64b5eb64d1ba7fb8745735dc3be2a2c61a72c39e78": "0x18d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c", + "0xede8e4fdc3c8b556f0ce2f77fc2575e34e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xedfb05b766f199ce00df85317e33050e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xf2794c22e353e9a839f12faab03a911b4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xf2794c22e353e9a839f12faab03a911b7f17cdfbfa73331856cca0acddd7842e": "0x00000000", + "0xf2794c22e353e9a839f12faab03a911bbdcb0c5143a8617ed38ae3810dd45bc6": "0x00000000", + "0xf2794c22e353e9a839f12faab03a911be2f6cb0456905c189bcb0458f9440f13": "0x00000000", + "0xf5a4963e4efb097983d7a693b0c1ee454e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xfbc9f53700f75f681f234e70fb7241eb4e7b9012096b41c4eb3aaf947f6ea429": "0x0000" + }, + "childrenDefault": {} + } + } +} \ No newline at end of file diff --git a/zombienet/0002-validators-warp-sync/test-validators-warp-sync.toml b/zombienet/0002-validators-warp-sync/test-validators-warp-sync.toml new file mode 100644 index 000000000000..df4414f5c8b5 --- /dev/null +++ b/zombienet/0002-validators-warp-sync/test-validators-warp-sync.toml @@ -0,0 +1,35 @@ +[settings] +enable_tracing = false + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +default_command = "substrate" + +chain = "gen-db" +chain_spec_path = "zombienet/0002-validators-warp-sync/chain-spec.json" + + [[relaychain.nodes]] + name = "alice" + validator = true + args = ["--sync warp"] + + [[relaychain.nodes]] + name = "bob" + validator = true + args = ["--sync warp"] + + #we need at least 3 nodes for warp sync + [[relaychain.nodes]] + name = "charlie" + validator = false + db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-a233bbacff650aac6bcb56b4e4ef7db7dc143cfb.tgz" + + [[relaychain.nodes]] + name = "dave" + validator = false + db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-a233bbacff650aac6bcb56b4e4ef7db7dc143cfb.tgz" + + [[relaychain.nodes]] + name = "eve" + validator = false + db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-a233bbacff650aac6bcb56b4e4ef7db7dc143cfb.tgz" diff --git a/zombienet/0002-validators-warp-sync/test-validators-warp-sync.zndsl b/zombienet/0002-validators-warp-sync/test-validators-warp-sync.zndsl new file mode 100644 index 000000000000..05c458fbf4b7 --- /dev/null +++ b/zombienet/0002-validators-warp-sync/test-validators-warp-sync.zndsl @@ -0,0 +1,37 @@ +Description: Warp sync +Network: ./test-validators-warp-sync.toml +Creds: config + +alice: reports node_roles is 4 +bob: reports node_roles is 4 +charlie: reports node_roles is 1 +dave: reports node_roles is 1 +eve: reports node_roles is 1 + +alice: reports peers count is at least 4 within 60 seconds +bob: reports peers count is at least 4 within 60 seconds +charlie: reports peers count is at least 4 within 60 seconds +dave: reports peers count is at least 4 within 60 seconds +eve: reports peers count is at least 4 within 60 seconds + +# db snapshot has 12133 blocks +charlie: reports block height is at least 12133 within 60 seconds +dave: reports block height is at least 12133 within 60 seconds +eve: reports block height is at least 12133 within 60 seconds + +alice: log line matches "Warp sync is complete" within 60 seconds +bob: log line matches "Warp sync is complete" within 60 seconds + +# workaround for: https://github.com/paritytech/zombienet/issues/580 +alice: count of log lines containing "Block history download is complete" is 1 within 60 seconds +bob: count of log lines containing "Block history download is complete" is 1 within 60 seconds + +alice: reports block height is at least 12133 within 10 seconds +bob: reports block height is at least 12133 within 10 seconds + +alice: count of log lines containing "error" is 0 within 10 seconds +bob: count of log lines containing "verification failed" is 0 within 10 seconds + +# new block were built +alice: reports block height is at least 12136 within 90 seconds +bob: reports block height is at least 12136 within 90 seconds diff --git a/zombienet/0003-block-building-warp-sync/README.md b/zombienet/0003-block-building-warp-sync/README.md new file mode 100644 index 000000000000..311d3550f766 --- /dev/null +++ b/zombienet/0003-block-building-warp-sync/README.md @@ -0,0 +1,4 @@ +Refer to ../0001-basic-warp-sync/README.md for more details. This test is nearly a clone. We want to warp-sync full nodes in the presence of validators. +0001-basic-warp-sync chainspec (copied) and database are reused in this test. + + diff --git a/zombienet/0003-block-building-warp-sync/chain-spec.json b/zombienet/0003-block-building-warp-sync/chain-spec.json new file mode 100644 index 000000000000..8c09e7c7b032 --- /dev/null +++ b/zombienet/0003-block-building-warp-sync/chain-spec.json @@ -0,0 +1,192 @@ +{ + "name": "Local Testnet", + "id": "local_testnet", + "chainType": "Local", + "bootNodes": [ + "/ip4/127.0.0.1/tcp/30333/p2p/12D3KooWFvMbTsNZ8peGS8dbnRvNDBspstupzwYC9NVwbzGCLtDt" + ], + "telemetryEndpoints": null, + "protocolId": null, + "properties": null, + "forkBlocks": null, + "badBlocks": null, + "lightSyncState": null, + "codeSubstitutes": {}, + "genesis": { + "raw": { + "top": { + "0x074b65e262fcd5bd9c785caf7f42e00a4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x0e7b504e5df47062be129a8958a7a1271689c014e0a5b9a8ca8aafdff753c41c": "0xe8030000000000000000000000000000", + "0x0e7b504e5df47062be129a8958a7a1274e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x0e7b504e5df47062be129a8958a7a127ecf0c2087a354172a7b5a9a7735fe2ff": "0xc0890100", + "0x0e7b504e5df47062be129a8958a7a127fb88d072992a4a52ce055d9181748f1f": "0x0a000000000000000000000000000000", + "0x0f6738a0ee80c8e74cd2c7417c1e25564e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1809d78346727a0ef58c0fa03bafa3234e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1a736d37504c2e3fb73dad160c55b2914e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1cb6f36e027abb2091cfb5110ab5087f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x1cb6f36e027abb2091cfb5110ab5087f5e0621c4869aa60c02be9adcc98a0d1d": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480100000000000000", + "0x1cb6f36e027abb2091cfb5110ab5087f66e8f035c8adbe7f1547b43c51e6f8a4": "0x00000000", + "0x1cb6f36e027abb2091cfb5110ab5087faacf00b9b41fda7a9268821c2a2b3e4c": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d01000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a480100000000000000", + "0x1cb6f36e027abb2091cfb5110ab5087fdc6b171b77304263c292cc3ea5ed31ef": "0x0100000000000000040000000000000001", + "0x2099d7f109d6e535fb000bba623fd4404c014e6bf8b8c2c011e7290b85696bb3": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0x2099d7f109d6e535fb000bba623fd4404e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x2099d7f109d6e535fb000bba623fd4409f99a2ce711f3a31b2fc05604c93f179": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0x267ada16405529c2f7ef2727d71edbde4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef734abf5cb34d6244378cddbf18e849d96": "0x00000000071c0d84db3a00", + "0x26aa394eea5630e07c48ae0c9558cef74e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x26aa394eea5630e07c48ae0c9558cef75684a022a34dd8bfa2baaf44f172b710": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef78a42f33323cb5ced3b44dd825fda9fcc": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a44704b568d21667356a5a050c118746b4def25cfda6ef3a00000000": "0x4545454545454545454545454545454545454545454545454545454545454545", + "0x26aa394eea5630e07c48ae0c9558cef7a7fd6c28836b9a28522dc924110cf439": "0x01", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9007cbc1270b5b091758f9c42f5915b3e8ac59e11963af19174d0b94d5d78041c233f55d2e19324665bafdfb62925af2d": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da923a05cabf6d3bde7ca3ef0d11596b5611cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da932a5935f6edc617ae178fef9eb1e211fbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x000000000300000001000000000000000000a0dec5adc935360000000000000000000000000000000000000000000000000064a7b3b6e00d0000000000000000000064a7b3b6e00d0000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da94f9aea1afa791265fae359272badc1cf8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da95ecffd7b6c0f78751baa9d281e0bfa3a6d6f646c70792f74727372790000000000000000000000000000000000000000": "0x0000000000000000010000000000000000407a10f35a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da96f2e33376834a63c86a195bcf685aebbfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x000000000300000001000000000000000000a0dec5adc935360000000000000000000000000000000000000000000000000064a7b3b6e00d0000000000000000000064a7b3b6e00d0000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da98578796c363c105114787203e4d93ca6101191192fc877c24d725b337120fa3edc63d227bbc92705db1e2cb65f56981a": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b0edae20838083f2cde1c4080db8cf8090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9b321d16960ce1d9190b61e2421cc60131e07379407fecc4b89eb7dbd287c2c781cfb1907a96947a3eb18e4f8e7198625": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9e5e802737cce3a54b0bc9e3d3e6be26e306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9edeaa42c2163f68084a988529a0e2ec5e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7b99d880ec681799c0cf30e8886371da9f3f619a1c2956443880db9cc9a13d058e860f1b1c7227f7c22602f53f15af80747814dffd839719731ee3bba6edc126c": "0x000000000000000001000000000000000000a0dec5adc9353600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x26aa394eea5630e07c48ae0c9558cef7f9cce9c888469bb1a0dceaa129672ef8": "0x3104106e6f6465", + "0x2aeddc77fe58c98d50bd37f1b90840f94e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x2b06af9719ac64d755623cda8ddd9b944e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x2b06af9719ac64d755623cda8ddd9b949f99a2ce711f3a31b2fc05604c93f179": "0x08d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0x2c5de123c468aef7f3ac2ab3a76f87ce4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x2f85f1e1378cb2d7b83adbaf0b5869c24e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x2f85f1e1378cb2d7b83adbaf0b5869c298ef7dc060436e4ed803af07632b89b65153cb1f00942ff401000000": "0x601de9615313b00e8cea3ef84e79e50b2fb70e2c8a47cff478b9fe8b3fa8f2db02000000", + "0x2f85f1e1378cb2d7b83adbaf0b5869c298ef7dc060436e4ed803af07632b89b6b4def25cfda6ef3a00000000": "0x601de9615313b00e8cea3ef84e79e50b2fb70e2c8a47cff478b9fe8b3fa8f2db02000000", + "0x2f85f1e1378cb2d7b83adbaf0b5869c2ff3ae12770bea2e48d9bde7385e7a25f": "0x0000000002000000", + "0x3a2d6c9353500637d8f8e3e0fa0bb1c54e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x3a2d6c9353500637d8f8e3e0fa0bb1c5ba7fb8745735dc3be2a2c61a72c39e78": "0x00", + "0x3a636f6465": "0x52bc537646db8e0528b52ffd0058dc2705bea5c66d15541040ac6c3ae971bcf5f1040221a8d1f8b7c17e61bf21cce87411480b10bd8daa5e3c2b65f8c4aa364e5a8ddf27c0313827d06a2d6334e074db9ca7d876ac8d052862b9984530f4d340bda4edddd6e4de5ba624536717e1139e14df2d875c7f7086dc3fa3d398b64f50fffc58fbf4fb73ffb0d39cc2b86761cfe6d3073b9c1fcee7fe611b7f4875f9a7d8cfe208fdf493f4d3b07d9a73ad05c8cf39e5c4dde679c3c8391b9ceec4fd78c824d218e4cfb79cb8be9cb301eace3b7f8a274c182edb72e2e83349855e7e4a2a795d7e27998b94f46d92b42eb543afcbcf5f5c9e5bf2d52dfd9b2f93dc33262710e3fc79b943e09fe2d00f8dd7edc4350b92a692a2facb7adddffddd3d89e9babb7b8e0b9d61050b2b4ddf3efdfdb3f629e9edfe9c18c4f8715bebf42d6f6e3ff74be1b9c0c8b9187cb89c83618e5bce6ebf90cb3918729745244063391ee9710cdb87bfbbbffc6047f275725e904518e7f53cec48efa788c4bd67d104a4eb81534cd2bd67cbeebb075b4c32deee59a43da30fd2d0c979410f3db2fb2972cfcad89d0f822c7ab644bade3b59d6eeddc9925eef8f34e8645980eb0fb2f8d9b2cafddec99273d7bf234bee5995292c3051e1fa97dcb31285eb5f76255cff23de917c3f72c8afaeaeac6ef74e96462e7d70864ed2ef48be2c9ef05d8f1cfa6e67cb79dd0ecdabababab5b225d7f4a72cfa6a53d9bcf739903246f521840fe916b813c65e41c0c50b7b60fe76078bafc61ff4c1bbfac7d3887b3bafc37979f73580490ef389f9f5920c8edc3f3cee7075fdc3e4cdba79f1f9c200db97df896f532c31e008355cff83a7dff7e1791b4e5097b400cb89ef19def24533b4524b567fe2c96c02292fe233d5900f2edfed96d8f34d29c2680f3270934e67054b7a4b7bfceaf95fd879b568e777e395e3aa7139fded3ff484f77779fef3ebbbfdeb6364afae9f74c32bf6dd9ef3de714c839e5df7e908f8c208b30f2fbb388847f8a2354c1e170305cb6dcb3b2de7eb0436e9ff9edc33deb3fd24ed29ef50f602cc7dbfda0ac7df8f683331cdba73fd63efefdb2f699b79f45244e492a38b708a9def9f54e6ba3643efdf9d41acdeb964524d396f3ce3fd247fa2f7380e4736a01f2e59c72bcfd606d9fb266c81ce3fc32bc0d3a198071be3f3f8b27cc0f725b7bc6cf3d2be99da45f916a7e3f38c349d21b7211a2597587be4b05e6ceef17b908d1c4dd21112e95f0ceef777111a289bb43225c2ade9ddfffe2224413778744b8f3fb594c4271b83bff48831d3217995ffa73cff86bfb94f3faf3834ed69ef11719fdf9592481f68cdf6ded19ff91063b9c64df908b10cddca5e2ddf9fd2217219ab83b24c29ddf3fc51346dc9d1fb60fdf7e16934cdc9d7fa48ff4913ed24c72cff839a76dd0789903a49e5d06c80dcee7f629f9a7e59ef1f323cd2903720ee774f91b22454f610414ad21610c79c190160c991a32c6102286c0604808865831440c2057860431c469881042da10f28221370c9961881742e6186245881c426630248b1035848421248e214d42de18b224644a081b42bc08a11212032164081943081842d6102203212e102286902e84782184cb902d845821a44ac8154248304520a449080d3d36e8a9ea99a3478e9e387ad4e8f1d233464f0c7a723d5b7aace849414f153d24e839a247aa678b1e287a9ee889ea71a207891e237a44d053821ea89e247a94e81941cf0a7a5ad0c3821d11f448d1d3448f163c3af070a046c68e1a3b69eca0b173c68e193b65ecc060478c9d17ec84b103c64e979d2f76bcd8e982878947069e251e259e241e2b3c31f0c0c083dbb9dab963c78e9d3776e0d891c1ce971d2e3b2ed8e162678b9da99d1becd86087063b74ecccb133831d3976e2d8a963c76aa7063c1be0b9b293c50e163b2cd859c18e0a76a476aed8b162a78a9da89d12ec3cb143821d26768cd82962c769e7879d1b767cd80162c7033b42ec7460070440ec0072c70e13901b00b9da69dab101481640ba00f102880b807001b20490278094000813407e000204902cb51acc20a61390a51a0d80c850fb52bba376558ba3660710a5da1c409280e06a5635388058a9cda026478d8e9a1bb5aada1b35366a30a86d51aba236454d899a096a4c0059a386a546430d57b3528361e76987089d2f74dcd0a9d25943270d1d2a1d18e88ca123860e1b3a73e880a123874e0c74e0d02943270c9d3674ccd0214387063a32d0a9814e171d2b9d3774d0d0a143470d9d17e8cc40270e1d2f3a67e808a143848e077478d0c9a263834e0d3a34e8e0a0d3019da61f6afc80e3c71a3fc2f8d1821f5dfcd8e207173fbc981ef8f1c40f277e90e047133f98f8b1c4ab04f34b8f0c604288573fbebca26a54d0c8f083aa2af9f0c2c71735197e88a1d3c33787ec8e9009f6434d1b36573a869a0bbc2f3a778473d49e422ec2168453e11630203c1bfce0522347784527869a17542d68984219603d7442c086a8f902e34167294cea425023469df26e50e3a5878c70a926d5259b1d6267c8bebc5eb0d303cc03a1d2384307467490a583a70e74a851eaa0033d8e10bfc070a8568c3db8be78687c74f408e2c545b481cb4a8f14e06c51657851856d8c3bc8bc78c5c047d40f2d6476c83421bb416581cb062f2f2f34646ed42d7c24e164f1ec78a9e15223e6c54b63d4e2c38a48c78c1d95a9068d5a11f54aad04b51d5c68b86010adc61a6ad4a0597aa541a39463031033726aa08901c9445e21ade05841c6602307a9f423f827300d1b19f4d4f1657000825718382da827e8b0886ef4808123021c23708ae809030704384ce02c81c3054709215d709cf8f1c1f900ce062a10383e8852b468d0b2c26962c686991a7048b09303ce08704a504bc306861a326ad8b0d9400d0c6ac0c8990187861a3a74385063464fe12ce1c4e003063ec6f831831e40f4f0c04b8c1f67782ce8d104a8460fa79d257afcd003043fcc905981103764a2e8a1030803d005433e007ab179b201818d10f406a01dd48ece868e0956431743dd42bff4e8402743074387ebac74576a0a2a15758aae06b1aadbc07f60c68c971daf2f558a1e5a3aa577aae9a24bea66a077b8946a9868c8a0a1010d1d3435a061636606345e66e2a091c18c1c346bd0a83143c7cc1c3469ec0411ce40860b4d0b605a68585093aa69a131a25ba2c982e605b52d343e8c1bb0c901f6011808604f3c88784551b9c8ae646dc072f820fe88991bbcc0e88ae888e88c70c9d03dc18aa8319851a3f683c8c60c1a325f646e206303992b19ab191a66b0c850f1a282460c9c20707ac00142e60a192b6049302b3255c8a4e03df03f00d181831d4419d4c6b0517a55895334493b30b41534565c4b2e1ccca99682300d980e5e0d6057604cf306e31a3655231b3e62f08241f5e245c66b0c0eb48c408c3f8c1eb08943868b07e2d5454d1aaf2b5e5b6cdc78a9604c0387089c271928402e5e55d86001af6aba8871d4d0a0070f3634d4d8d1c3071b2b21ce268b8d0d3367d4bc01bba1c68d1f2ea859e387979a18c09a6c7880d95073364b3565d0ab9a19d4b2b091e175831a2b180d3859d4d421b6f17ae2d5c4cb891f37a89101784718c3cb0697198e858f261717ae12b846e02ac2b585eb042e295c53b8927081c015e59a72b5c07584ab0617169716ae14b8b270f5e0f2800b0bd710ae255c4eb884706d7129e17ac2b5838b07d70f2e1b5c50b8a270657171c065848b05ae15b8987055e1bac275832b07d7932b042e205c1f7005e15241cf54cd183364b862a8b99a2943ac439cc3e606110e9106e21be21d40bec0711273220a8068b169c3956433c3aca3c6c4b303073cbccee891c30e0e385c841b7899a1e30510aa1a143555b5276a4e4c2d707ee080033b576021d88961a7069c295810af357464086100bfe0203141600387cc13322600bbc0f100e8458f25c02dc030769a00c1787d51b1709d51b3807140acc1cb8b9a06ce11343000c204a40c58075e65d868a9b1012ba9fe00dba1034137448ba047053369bc6cf0838d1e3df464515bd0566ab8ecf0a0d34563f1d9507bc0116205162a544da22e5195a81e783272a2462a9e8b189731052e3b64553eb8689911bee18901048d57171d2d3836e0d48033838e0e325f006143c68b8f06b5a57a458c8b2ae5aa636c72a5d1e303a21db58a1a11b5281f31e058a92800a2850f18ea0a5e50d4cc21dea0a606231a43b8801101f321b4e25b8833a82a10e5a849c2d10296c533e3470c6a5c5073064e08bc1fbc19bc2b5e0c3d33e8b1a367e81a7a072057b4161b1d76b4b8c2a01903e452bb41cf17206a0021a3cb0244059d53a743d7d4d1d075a0d3d2fdd005d1c305080f3d5d741ce87ce87ae878e880e0d1d2edd079a0cba1fb40874377031016e00c61f3464d0daf0d9d1c7ab010b700d7d0b9a1870d73861b367676e0e14167079e2606e060c101838621e3820ea39de8259a896ea29fe82e1dd55f34091a0a97aa5cfc106fc423417304cd123448e41891e38408845884e834163192603402e4400d23c6464c8e980c649e64909029a207971e60f4e822a7889c12e488409c41e4808885468a460b1a15c4ba88c120e60558450503b442d64566860c8c19ab993b66eae8b1460f1af460237644ac043124782cf168e22143ab8adaa56545cb8bda45eb8b2fe245f042f838818f2d3e50f02178103c11b12b625bc4a44627c629c6a81814b12d3113f810c207123e86a8d9e0b9e1d1e1bd018301ac0cd8182316a31763163eacf0d1021f57c8acd00981ec8a0f86efca1783a884480271095087ea0270872a06a8e585e5b5c38b86d612b5205a4c842c08c908b1a835d42cd586fa058d153eb4f808c2070fad21ea0b5a21086f107e69bd117620ecc1d3a2268697453746774617032058d870c026861aaa9a1bd8d450e3854d520d1c364d3638d4dc5113860dae260e1ba69a2f80cca16307101bec28d145f42cd136340d4056d0589aa973681e3acb9ca3710d03902ee6d5acc1b4c184a3c76a5acdaa49032052736ab66066b164a260b2604e31a5983f4ca989c584629e60e6a609261573cbfcc05cc1b46246318998219843cca46965c6309f260c1357c50215983e3c994d4ea8b820a505a1261098400424f08006a49000144e40c001941820090953082300100091282c38c07979c648c16a072a6a528125539a00b1b0640938fd4914a0254b5abf8127529c003dc1f92b5c548401202551a440292a025a00f94c1cbc0c34bf24d4c40914222946455058f0e0956c3e89895a68e1f556b8a80848a8c7c7c0310f03b7700099c7f1e05c090935712224d48400349c3bb848084a1323a0274b80802cf0018e1d2cc50251a02cc10011900b434ba04cb94651805ac04013141ee70b03ad7003067a22c5c992274da4084d71c1060c64d4048a519426464551a0c870ea6021a1264e8c9c3421328a228500301c2b067212454a94254d9c44918200a3282e4871e2e4f6c0a9019429170345500a80814be2d020041c3a7270e660189c19b01ccc82132953806c70e26022175680628128529c2c8132e50255a0894d06a78a8b9e1035b90010d19215a24079120568ca1228536e91901429284481d2821404581c38780a14168a8880a25c40ca120c00057d80f3063f018ab20294a2282e18357122850a14178680961811499902444404c70d7e026589115011910b4e6870dae0222028465088a4b060e44293252b2c8132e53699f2244a1322127ce0c8802f0065ca25020a2292e28013481c361808c80520171290c213a015566892c20f9c35b828c809100b4ebc7091511420222016a240791c35a240b1800d4e1aec044a5114284446558096604068ca94264b88809e084171c116e4448a90ed06070d6e614913a3264f9a18011d0086734606a00835816204250a14191c3338c88914a10a2c013272c109501520189c3298480a01429e404215686214c5050b2c3172024404e54914284b96d0e880367c2083918403c3ca97cb4820e770b83b919cc66355b1aae694f951efc6c6fb273d1bcff3bc27c99b1b8ffc1beadfc75f7f5d6b77adb39bf963effa7d5d2be576e68ffb03e9f4fad1a6def7791fd013fe987a7b5f777fb4995b74ff9add99b2338b226566ffd8fbbe9deea87760f7d755efcfbbfb1b476ef70aced44a99ba7762f56e96d8ac3a99bac74ce64a65cd72cf9d3f0fdbe9d7ec2f661bfe987ecceca4dde9e73895dbbb72f5cf690794fac7edb4d93f76fa71d3ef73f7bc8fb6536766ca9429534ad9fb28f5f6ef83317b53a64c993fca4e653e66a7cced9476bb575ae5cafef19021428430c8eed4e5a20e7a575abbdd0586cc937a5ead1f738fcc9f3b6598bb5377efd1dd2be0eeb0eab5d6dae3d8b4dbdde79c3e67d3f6f6a6d4bbbf6edaecd46beaf5fc2af5c09d3af5599dddbf70c645db43f7daa42ba5ee2ff6f656bb7fee1574265d57ef4a7b093b8d31b37fd3dd41b052a069482b0dc31aef1a760fc309788712a8719ed3bf5ab97b727fdc13e4eb09e21e0176770a1386ddd3b9bb99fba3f9421aeace94db2140a97baeaf7eeef4f3a8473d66af52eade5fb7fb473fef8ffbc6fb3effe6fc981bf600dab4ddbb2937b37fdd1f7bceee7d2008a34054a820303bbfda1dc0cc3e3bef98b9fb989bb9eb66c7ac6eba772c67ee58ecccdd64ef581d77ce9a5dd771d7755d73d779c71db3983b666618666f6731c73033b35831cc95997aadeedd9f7b8e7b7737a51ed0ee80f6cbddbfe6699e6e8fba7703ba76f7a49501ed7937ad1fe5e9cddd1f7f5fc7d49dba376d4f61d20528c0030e9d7f780218051ad6f41ac7a969202774cb7cddb06eeafd79b57e8e0098ce016a351d6f1a86ec734e9eacf6febeef874c7ba594c62adb7ccc95a73bedd0dd457710e6e476b06d427666a6dd4ca33edd41f77228655a29ad94b6f70ca5b45217a5d4044a5db429f5da5e2b58bfaf6777fb6cfe3e9c0efae3e993d99550e7ea6cc3ce6266af71f7cf6b6776f630c63e67c8ede2768ecd364077e8edce727767d51866f7ca732661febc69f3474343536b7dbd5e73ce5adbbd84f6f6aa57efd09d046feaeefe7277eef9f5d7947e1f87cedeee4dab778314ac2085e9eee6ea9f37280382ec3ed929ad15a4947e1ff7d7d49d7d4e0ec3574e873e42483d44e2341e76bb7bddcdeeeeecfc7ddc55669e9f659cf979e8ec79dd4eabd7dab47af5efebaed56937add4a35e53da3da7cf39270c4c7be73c3bca6af72e403b28d33dfba360d7fc79d8dd9336a594b67f4e00dbc7edec3d99bbdd3fff3e76e6767667766f6766a6a0e73ebdbda977f7f4cf999d5266fed89ddb9d32333365fe98bfef6b76ff989d7a5f6f7766767777fed8bddbbbbf23dff7cd6aedafcee97d3319113c99effbbcef930d4000edc950af7e9fabbdfdfbbeefebfebe98f77dfc7d9ff3f77decf4fb78ec9a366d9036edcfdbddbd73ea7ded1fcf39448a2153380f6e0c10496901a805cf8805a448219a0242009e0011013d2192b2c427841e20284446207c0043d323a5a60b72d2a402424252402852d3c3470f1a1e464550aa00dd742d48e981620120a22996031b98284053a634b9f10b00450122ea0e5a806214c50522cb81058c5ce8c14115201696184581423465899322282ddc0cf1e95145ca058c7c3461a1c8475013274e80827c42e8e1c14188f40459d20314d444a8024b5688028585a2262cbc102128413c00f8800807690244e4032868895115a0283a3e3e807922048505274b8c845c686201a027b4ce88a88951d1073d1cfcac00a5888c7190220c00052d710245a8022e4c99111575d0e29ea22220a2292d4871b28408888a142740444b9a143d7102b42448904f083d539c0015fdb0587062d4a4024b58288212058a05661f60c002508880a870608980a200adb00400414208a1e709d0122946533040e4a4039610101520a125559ab480035b418ad09215a240596105294234b3051f0c446401275284849adcc054017a12054acc94169a181501b500c5e87b449102c5c84993a2284d888c96184169a1022e14d1584f80808a96b420e5499426443606c8a88951d1922022291730028a92032bf2596116821150d19227519a2c01226a32a5052943c62640424e9600add084488a51d112a1264ea4088140250a90b338c8922852a014110151594224c5a8c91328424da82c2982b2240a94262c38b18005a42c31aa02c401952840fe03aba3f986d44cf805a466c28dc462c247472c9009b7c20e09e9e8e8ab48ee47484820528b09231d1dbd8e8e68bb33399a4847474794c9d13cbacddb64f2c27c611e1d1d1d1d3192787474243261a48f8f908e909c9190e6d1d16482348f909ac96dde6ecde485f9c20bcd848f90909cc9d13c3a6a2647f36832e1a3a3a323a48e1e1d21cda3598f26d264723499091f1d39133e426a267c74c448481f13a47974e4313942ea98204d24a46682349991281346426a268c8484e44c9026d264c248938905bc342633cf168a9a70ff6aba2dbecb9bc2179ba6703ceccdfb193b61ec6d8ad3fa61f6369d5af6e654377762d95b3bc9d8a6622aae72ea625e8cbd219102ec61fec6fa1bd3105beb6f48a4d0bd3775fb6f48a450bfeb5c0ec6de580fb373b4b709e5b4fe97bd4d97bd7993686fdd14da9ec24d2876c109ea68a220cbde9296425bcbde904881f5f46f93eaf6330db1517b63d949e5b47efa9ebdcd29a7f577f6d6d5766e7a36a8a2065a90800530cca9a9c58c21d4f882082b9f3dea2b9a5002872dbef460a37656517915a572a2534d533447713977a250de975e0555e5509eedb450755fba13d5955367bb266fa25ff6d477757507185faca087266a3ba6cecd37bc08c30c20a4c2b0b1cdbf6cdced8f8b1dbc7842862cdcb0b1cd6d87735a7f39ab6eff1cc138bfec70b7bba9cb1c1c59a4d933361c31c711671c31c611611cc165882e59dc0c8c9c1b4288db0f5ece0d41c5104fcc1a30bbb3b395796543826b58f0c86e3ffffc59fbcc3075fb6f74daa76fffecd86d16fe36cee5c961ce39d9c66a98c3873984d395f5cc2d371260cf8668c26947f0b30d091c10081bb60640bafc2fc840c4b644c4d6cf362bf3cad61609b64773ea809492486310fd0f6e89d41d5dfa25eb006cc6ed2c11b57b26eb7724253b57a2ca562d8eb568a25fb228f9c105278b10f8e01e4da4ae9b0b281f40e625e61c9ad77fde00f293f00738346f3f0f5e6ef8dd3759346f7f8b25f065d9a0f9ac269a5cebdea8de21bebae0b73842f8ad16d87db348ce817ed8e586cf7a10ecc26e3dab45253409ebc33fd2247c0ffe24c16791e1bba8a4fe9c96b936cbb6936bf3590f9265b3a8d43be477e08c269237db8dc9f47f5c3a9f7970bade7f28a8b003166eabf159fbd878dde7cc98b0204667f3d1a38ddbe9b44fe9fd8c3baeef25842a30699f8ea54c689f79bbe7d1c274bb1fd2519edb3117299f9a5bf219b7ab3fe57edffc46deed07694efb74443d45254beef7f5fbe6cf32ebb00559a2094852782dcffb727edf64b51fe99125d2f5823c31c967bfff6cf98267cb79bb497f70762bf16f25d3b34ae677f737e00e425c8fec260cb7accfb9197c18c158fb78df3d1549a85fbe709564c270eb83dcb8fbbd47d68f2cbde73fe21e0c777e7b55f502d6fe793dffd83ee2f3c7daa7f5cc7ff339ed539f7fd63ede23dd16297e25914010fc231d9236f559396cb9f4451dbc07b95cfa1e07ceb8f441fa208834c367b1c00727f8437ec1076b908d92ef3bfab93e259feb03bf3ec86a3d5f56cb8e509ff5ac1db25cfaaddf21e9d2a0eebf0fbf92f3b25c1ca1d56ab56c50f725fd22a4138a906ef841de9f40ffb3a5f7a165f165d52e6431b4e5ed86ef912fd27b89394839935ebfc41cc4fb693be2210976a147fabf48b6555209cb06894f5f7c16910c896314df3df03d5b8a0f5a1bf0bfef94d017ad0d6895d4f7be821ed9fa2fa8d64f49fd6a12fa2c5b7a0f3e089265f836ace70bbe47dab0ec08dd830f5abe1c98e3d2071fecd06b3d68cb794befc3073d6c91e197f386df8d002601c1f767d9a179c57a7ffaa02d5b1f92e0bc1e396f8be43b2ff3ed8618b9fcec359c0168182ebf8b87c8d1446a7568039884410f5924c16d39a75802fbcf13fafd595f562e52bf6479e86439aff760fbe5760ffb32c8a1279e40df6d59af7bef950e7e210d3bd10095eccb22e7edbc9fff89273458c5138edcd6b3c8790d8024c56d3dd80a5926208d10e50e1dd52421cefdaa880024a7db7ab04396480292d31d62725b0fd6b0251a00c986db7a100c5962121eae1259a20948b7f5a02b641da0cbd7fdb8997976aef2a8e97a0f86218b65cbda2e5779e474bd6f91cc3a65ebe787e209fd2d5b4e164356927ecfbacaa39b4588ebfdbc959c17ac618b49e8fb4f71c86f903802d2ed3eebd9f91e34df46498981dbcf17a94e2f892c583c0a0ee4c09405072f138710ecefb7e99e67f7ec9136ddcffbfd07a3ffc1f5ffbec9ee677bedb5c7df5ee59eecfd11f7c8ee821dd90fce59c3d83f3b0f3b1a765dbff7839d38028be17d3357f3dcc976ecd907d791bee95b803c3b3082b387b193b89cf3418ddb405cce7540eace2ee778b04a6264a5ea96485178b774e1062b2eff6bf6303a1597731f58e2ce2d977340bc71752ee780e0c01449686541758798dcaa1feed091eb9646658c2103e296464f408065dcd2688632ba5b168141821f504cddb2286aea561c8eaf2effe56f1dc0a179e98394ecbeebbeb8f300fe6dcb5c08dab8f4e9cfef49da28e9beb8f4bb2f6e598474e977cf7788afaeae1ae9f6776ddc79c4964b5ef2821d3689733bb2fbe24e9184a72233ad512b7927c1eeea5a9b24f3fd4998efd608e4dc105eae51dfce96fd54e6f547bae47592bc6d045e7ea46ef69739ee7cd9eb3ffbf9fd5d3401490adae4fca60d23e76e38e3d6ffde6b7d25cbd607b1be7ef7dffb834d860fba481bd7f32d91aeeb3fb2ecb643af4bc5a8afebc31769e3fa79c70769d80f36699384e270177c7afd82df645f16934c5c8b24b8fe458eff91ae6fd2c317492f48c38f643aee8bf40b76e147f20dee8bec0bd29edc7dd41b3ffc8f643bee8bbbd9d425be5f317c316e48f68fe4eb8ff88bec38e8fbfa6e029214f5b9d63fd260f308f3bf2926f9beda24f11bbedfd0964851e070b749f0c129ce1a46f05d24f8305cb04317093ee86f5c90cac1735cb00b5d64d177a9f45d81685ef0a98864d230820f52710416e3820f7247bb72e0027f0b2e86eb22792ed8627d83e233d75aa0f8618b2cfdb23e64852dd6832451288afd21eb5b248b26b00e8b9fe707ebecd8336aed752beb996b7e5ec029c3482fe76e78c19461ec2ee76e880173ad83eff3bceffb496ae0fbbe2f0d9049708add5cebbabbab5ceb7e921ae8ba67711124fddd73e5225420097c5f0f32333373cf6772dec9af906baf6f3203a3ebc50f1ffc0c8cad677dfdef33307afe7af5bf5edfaf31c05792efbf1659de777d677d7dcfbd7a2cf0abef2495796bfd6f72bbcbfdddfead36a8fef72c1ea0b26c8974599375abee5eff880fdd6ed76675edda3ff9491bfff9fe4ddaf83b59c291cbac537effbd937cbfff2a9151788744b873caadcf621226b77eb5cc3a247c64593fc8ff7bb092966888ab70b86b04735bef6e14ded6d767f1842997f547ba92444322dce69abf91776b5f7f164954dfc8bbad6751c992cb7a9c2111eee41aeb2bcb32ebb83842f7b592f3bf725efe8e2cfbe733b5b5674e8e757e5880ecc1bc7bb0f307a7c86d03bfb625d2ed79fb9899ffe33bc9a1dbfd6693437e5fd33259d25a2793ee43dfd5d5d5975b9b1cf25bed9077cfde7f64f93d95f10a511c0cb7ec1ffabec07075a9d4921544bf3edb72bcfc4d86fd54beb27f8888ca778b90eeb42c2261efdba86febbd6fd9a1f9e5521669d497beb5493273d7bf4524fe1ec9e57cef9da4d29ef7b61cba5dfa5d8974bff9555bde6efd26677777fd598434e423f0f77b437e29b5414aa892f96e83f89b59b3e2d03327768055c6166f787a2663152e32bf6fee52f1441f1f7cf1a5ede37df8fccc7359dc7a2e3243f2b9c673f9bd24f5c93abf4e92ef145b606b04d66b5e904524fcc1c69eb59ef5e04f11c9b425d29d2cb2458295a4326f392feb2749655e16eb2bc99782ae6987fcda8ce077feb4cf35fe792bc9b241f5c1a7e250bdde8321f862fd14e7d2e86f59967f82952cebb35ab644baad89a405daf276c19f248bf49eac33bf340092149759673eeb8ff8d0ed7a3fcb5be490b3fc59e4541afdc726f97e64ed7a6425c119ceff489b6987e6f52c6fc1e1ee8feb8900f426a431a8fb0f6ed9d989a5677c3937830fe0c4d23e7ce9037111229e4b043f12a7f1f399ad6793966a36ef99626c58da2776e9d70c29632c85eec4d233fa3c68a090e4fdfc23fdc2584e2c977ec9685cfa37fed16974f8e004104e4051e17036efa96d92382a821a4010431027b0252dd56cf399626cfc494b6dfb9e6900b6f91ec93400db0d8914e6df50701afd3975e97b7f432285ef3d7b3be234fa9fbd219182f7d3dede69f43dcbc3b9dcfc92a7980632a8800837c230820c2efed21f1283916d483a6071821f7e98a2cb1233b0b18dbfe4b9f4594ce17138dbc4d23e1f5cfa9c436dd06ceaa63f80cc454a1ea457d93e2dfa1da547d73b332eb55de97df43f1bc5b3a5f742ae44d5edacf7a5dbf276a92b612d6cb89e25a2765f1e5d21241fae17450049b9db7d496d59e57a14d6a2e9765f66e052cf46e1336e67a79e50b0f30515f4062df7e672aee9cb053bec48226efc47afabab2b1f6c2c227033632861650d1cfd39e4575797085c0f1a86c8e244d48153125238a3297cc62502b7f30515382a0248ca55c1b1164d36fe3203b77be60030ef949cb3a1896b342f1131f0193696c5f11936b637fea3283f7c000c18bee070b66a7149389c8dbfbcbddb720a48c34af253d2bfc506ec7c31c5958d9fc506c0c81823047758b9bab2f1c7aeb77702d02925bba7f3ab5df7fe951cbbce6b345cece2b8b29e754fc979410fe97b1d9d9776c3c0bf72112f65d77f7a7d67bd7befdefbb3bcaf9ebf7befdfbbfb77e4bca047f2ed90b41bf48f74bff7d37b1bfaf3f6b5a17078df7d5e47ce4b495ab9c6ed06c8dd532ee23d7fe791437ad6bd1997a767ddb3e865ad52df7f5adffdcc7fc0effea67d68cfea774f848b3cd7ea770f9263d3eab7c8dab4caa3fa8fac69f5bb97b50febbbe72db71c72bbe7c145ea3b55e891fcc1eafb43dd9b9ed5ef9f3fc5fa36dffbed6bf37dcfb91ad4b89cab018d2beb597d6f885e96e59ed58e6bfc2c528f9cf7a891ba7625406e9fcf9ffe47cedb91f45924a17be6da08fef4711082c503f8d3af4f6d592ffd7e8a4355926280017787ca0bdcc08eabeb7d11d2f56cd1674bfadd91423529061868951695545b56015924a1a34fc511bc67ae95dd47ceeb911d126a6d92f8d3f7a736a89fdaa1db2dfdf677b353007e0f4e1109f8e1775f49f1998bb8de7bd0458e8074cbf0c1677184ea0aaacf1f9265fdf9e014d9835dced520c6edb8e6b53842f78b64fd16392f08b278807e16eb4592f52d92e7b3888465cb79d996ac6716d90ffe9106990c1f24f9822c92c0367cb043909c9745125a97b956f283e00cc1f08f34872059490c8ce183dfba654d526f688358df2059b27e2ae967cd568be4cb22f956be5e475292f68cc3e95a409e32d22f65b77b9eaee3b9dd773fa47b5913b7fb9bfe695b67e4763ab7fbb17dfabbaeeb9e733a1bc4b923907932cf27207fb0b0673114daa77c1adab8b92573b9f4bbff6af7f7f3bee42ef77b229f4f594b689f7264a17dbe37d23e35f77b15da87733534ddef25d03fd4e6dc85b7dcefb97d6c6e19bb2591fb757fa46fc0263f21a608eef760874df6952171bf07b927bb6b685e5dfe3ed63ef4bfefbfe79ccf0671953fcdda074baee797b21bf6e176ec3e092ebba1fea37fec9200f20e19230d586eff386120bbc029c3487b369f67fc29e92414d0b9b87bcdedfff096e3cd097b46a3009e230826bad0800439dc20031bc760d4b288643e1db17ce9b17d64b73fac3c6d783987a58d4badac67937336009968ec1ab6cf1c3b00b91c1b745dcd30c70d888445871b9e7000a34b1177d85804e7734ec943ce7bc1497b36699f4049f06cf9d7fbca1f8cf6cc96467e5b2f6b1ff0fd6fdae7033f16cb96ac6f3deb0b7b36b6cfc77a7ff023e7055924873f451278dcd096e1b7fe23f986a4915fd683b379baeb23f98224df168be49e95b23bbffacf95b771d3986be5cd9db6f4409fd323f98a30bf9246dd9d21d86195eb3d93652d2b7fb0c9624ecffcbfff9e7d4c52b29cd7238dfcf24f72ec19dbced69ef578bffa0fc5e170389ccd7fc645fc7acee569cdce03eee493b1d6366e7f289bc187db7ff3649ccff34ee110c96bc42b6d5cce5d11e3967ffb0a16f7fd47a769fd8d047c79df5988b40fe7ae7cb9fd46a67882ff7c1681c672d63d11cecdc074fb75fa67dafa8ddcdcb44f3f933c3d6b9d9ef57b47922cce581c615e9d9e4d71d6b37e0a8c47b7b60fe774371d0790c9381ff48e9832e2703818669f30bb7da4670e23e736c0e57e709d72112e1a3977258acb972fd885948bf4e5d2bffbf9a00bb26b24bbfd4e96936888af6ebfd177e9839ea8e4d52473adf4af9cc4fbee5924c1ff3bd24ef2ad64d97d39afff47da28f1bebf6d50f7def3a5245f4f54f2c1f52c73cd9f8a48ba196576fb67dae6d31695700da2cc6e5b3bb9d6efe209b3eb96b9d6967b3691be498d00b9dd7f2673bb2d274e54f2baf49da4422fb5cccecc5ce3205dbc7a7693bd05387f82977332bc60a6d132ccc0bbcb3919eaa0fea0fbd0bc9acf62166f06f1724e863ba693f37e97734c42b02ee7984470653d6363d620037617b9d621d7fa99ccc0c839a62dd72fe7985070cbcadf2f32c9d4c495f1e775d535763b80dc0ee89e1af9ad1d69e437c9c4e170b83b9f92464eef7c4ae7146386ee0f3a39599c7fa4bbeefd5d44e23d73f2eb9fe2fc16bde7a40138b18cf3d9320d387f9224f0052739831657d6b2cbb9a52fae2c1eb688645eced55075653d6b71885ee61a7f8b4886e6d5a5e15a7f8b24cc4b9f4916f927392f9343f3eace771109b5b19ef54f52c6b57e273530724e862362345c846fff28e322ad20fac10e1685174e923d58d833ee597973e7bb2ee764c8e2ca7a361f64ea3db973790e20370876cf3dab5df7471a04596c40bf2d912efd239b3b8b2af8b7bd21d96ef4dbe64ede906cf48f746d9fcafed35f1af95561e4cb29cc777b3bb22d213124c2f57eda3a92da72deee3db2f4db7d93fe47bc6d2ca6e0b6f9479a927c9b641bf74fb521f9517733813c34726ea98ddb5dce2d65b90c63918479fd8fb423cda9c4a70246160d807429b521d29c0570b204e49e913091f8a74842503fdf20b64347977fb200b202e81d2ff8a29d9b8074fbdb863df3f7a79d4742bfff91e6f6b119a1dfdfbd9f4f6bfbb82dbb0ff2a7ef791fb64f5f6fbc2a00590123f8a2bf84e5f2fc72de69c39ed1a70fbe4c18fdfb878e2e1d9a97da0a80dc20b74f7bc824c8394a6d8497a77d4a23df3ee5cc48fbd0f7d7699feffa8fedc3392531ae7fac7fdae639d79fdb27259834ff120097ec88e099bf976002e096d4a1fca71301d3fc69ade328fbffe0961fb478025371fb6b869431f214976f49adae9b20e91af5a5cff405705954d25d6abb6e7e51204319f90b14e42eecf21727e052afecd2f940a0187279faf7a5ce65066282f173da87763f85726eb524bd030a19dcf9a3fffcf4ec87acc6ed9e26d9badbd95228e73e1b23dbe6b482883b3830f586153a9c61732ba048e3baec7e4ed3e603891abdcb3928a86ef9777aed5e80dc3eedd94a96f5d6ffb8c8e77111afe3229d73116f2ed2938b4cae79cfdeb441defb7b5e93b567f57bef67b565add7fbeff3bcaea3d4bdb9487def6792ef76d523eb7b5d3f3bf4dda07e4a96fe4548b7bf497b98028bcbb929a2e829a06e0bd13ede50fcd3494cebff61a6958d85e5c33c2b3bcbed2f6197610f8021978342ea1af5f5279a4d65be006e8b27787742b14e7f0f31f2852a29974b446fc95d54e8d9a463246f433581ce3c9b30da339d9e75397358ac6736582e7d1bcf8ed0d77bf642055f6ef7448c4471fb5930ea1fcff517a07d38f74413b779b84c223ee3ed248ac3a27f9240272438f7441bfd1ef9002b73900fc045910fc045d133be3ced53d621edc329788015b639447c76ec9b9e35ec0131e47ac677bcdec30fce56f25c2682f4c412414081613583191c61632a2ef3cf8ef40fb5f1330466940f4fe0da89266eb71351dc6e9e198ce53febfb6e0ab2489e32185bcfb7f5f3bfd6f741f4e9839d888465025292efa72d83be9f22fd8f0cf23e4a270f6bfa1b15ac19e286d3283c37c83fec197ffdf69e7e29c473ebb31eac64ed591be3f77c9b45d6cbfe536dfdb59e4065de69a98dc2733d1b44bfb32358aeb3677077f7f9eeb3c7985eb610caa17d93dc44c5718650cef422c4236344172210e2f9beeffbbeeffb84be28dce5234beec25d7478867011f0f96ffa67e6f530cf1f8e3d00a6b7c9c4629e5b16461221f331b171e61b1623ebd9477bf67d0c19ebd9f733e437edfb1639eb190cc917f6ef3f32b0960c599bf63d8c1c7bf6fd48f2f4ecfb1739a467dfbbc802f4ec7b912ca167df87a4093dfb1e2451e0daf7ec3f449af67d3f7f8a23f46d59eed95763ba0b0128da92874b17c6e2b22dc51785786e149e1b147eabe4b9b30c6dc973c3cf893dffb70fecf9cbd88d79181869a467fd317248d3622cf8230cf94e9bffca9189e28ffe03f3e3f7c3902c22196d19fe57fe15df4596e3156d19de72e48bc20dfabe1e39d2d8b487cf96b3fb31598e2e11c808c67f1739739aebbb63f7fbf0b97fc2a67dff3d7873bf77912212d1d69e7d7fc443922c6577da3246b2ec67bb882ee3f89e9d43e61879cb9d638cf5997462ccb83c1632def295467e8634adfbafd67194c9fe673c3debc68f2c6796ebd9982582c575e6be7624c039bbdd29ed989cff8948645f8c6cbd18d97a3306237fe7e22a76e8206d752c2fac3d63ffce3fa8b3b567ccb3d6cbcc9fabac72e9e5cbb34507a50e7ed37b1aae75f4e69486d487be77e1e4e2fb96eeee20839f0472b808e55cfade046bc8e4bbe82efd4f4442ffa667fc32aef14f19c930a21d2dbfeffba8571a399805e46286dc1b605b9670895cfa538c71be1091cbcc52e8bc80bad3d9656a4ba4282e7df79f618cf35d88b75c3acb18e74f2e287da702049fa76e39a48cf5112ea202d7c42fdff5e5ec87884effcc9ce6e1f74cbb7eb5338db11c5b3cac073b474906ba30a28ba62bc4c3c5849eb946a74d7186e4bc2209640463498488ffbcdeffc88b046718b2fc27d6347fff58f86a8c446e8b88ff2421517fe96daff72f40fb94d5a7be7f09edd3ad677d68cb1b6f81ef42442eeb5d547253010e42405d21ee7259f4b2c8b23d7b7d2491c953617b4ed417e7943377bbef723b59ff94e0b4ee8938ad7b7e06b2c548ffd6b30ec8d408648ab19c5aa696f6e96f6b342f121fdae7733aa27d3e4b0454fb44b58f4d4f2cb77bbedfe8b4eebbf17295a55c05640463f725a8c045d0184ba6badd3379d23ef5bb670137a56fed530acdeefcfeeeab98506dc95c5c44e259233a9be05af71d4219fb482023602d3dcf184baeba5df7538c4a2255e6531212f497de2644fb94d3a97de8779f0f8b48be5aad106f51a28121b8f042758598ea4e7a3d925e4a7208f363fdc3b66aebcaf176b652e7fa7733f7d9c0df97eeeeddc75dd7f1cc72bcefefc85276f9fbb2eb3aaed1779f1e59c62e7764195eae43b8c66347070d27173d3db2b6c6e8d9dab376a6773ecffe32bc73ea08d7d8081109e480219433847f96c3c577f937936a72573bebc541bbae637ecafcb2aeeb28384da0725ba270fd2759f2dc39e79ce5ec9ad0b37e27857898cbed233deb9fbc85889139bb5c29223d6b4a96425198ea064d8b44a73fa1d333de22813b9fff36c5a8bcb94239977bec596cda92c89ddffd4ce7bdf7294444c65bbce9348020c8df24ebdce6b94de476f9df7739b7ab48c25a8fb450cee5a79c33e43291cbb2d28bf5cc93fdf5a6673b7276bbfca6946b311691944244eefcb6b3fbc299bffbd2f3f83dfe208fe716fc938b7e3ab718e7cf1890e075dd7bd77d10a5f426d67d4929ed485b32b9dd53727631cea746dded3a4b72d9755de74f20b70fbd6077777b7bcb00f697eeee3d6bbfccf432fbf897dd3788a8af5f5d3107807ee9de414e49a2795b095f5d5d6effb2bb8d5a890817c9a76701e98325330d625ba70b2a7391c945f896f4b9b6c6382f7dfa5e7b367ffec806f885970dd0e6394b505582d9ed449313b8ef8201726fc0eaf60c07708740ee12702901167480f38f984c7c694288af7d027039c744976be4d7bfc8fdb84873cd595150b18aeec585f791f1b1bcd4d7e55c1371b0445ccb69006b53a8456c05e1a247bc6050239d02165601d3da2226468232623130323cf50a3855408239aedf2e0196db3fc4c80765c84528002e915bde9044fe4454c6cb3ff4978a5fce5d4af6b8200d295943ca2ee88594e471c12fa4e4cc056b48c9980bb2424ac25cb0155232c805c190923c170c434a861714434ab62ee80a29c9bae02ba4a477c131a46477415848497a419890927ec19890927dc1180c2c2626e3b24446e3ddc9ddd0b580f30aa8286e49a19a8082f280169d86c262ced9ed05ea0caa840f4a24415929d1841249283184a7441b6228a1c496fa8d97734ac8515d31b0bca6568b0360750ac1277189dc12276831e10ae7d42b146364282e505bc46496f012c25c2616de662249092c4b58dd3126b3c41bb5c28493891a6238c74416d8f8728921d8625518170c16e04cc1571954d3edc94dc059c41836146e323373126d24e18593f8726524c87c11d39fad4a9ebaced39673887333b72a798ada72e6b69c35151628d87f3f8e0ffbbcf88f0c4dd9552fc8946d35f323e3f15134b6b574ee3fd9287694d31aca69dd4f4e6ba75c12b95bce2a9eba652f4199ec74923dccccc7bccccfe75156198c2dff636c29b3e5e8d45aaa6664ac66aa50cdcce0bccc30cd3055cd34cd3459c954dd923249d9d1025bcdccd4cc8cac8b4cf6fd4de53f3c686c6c2ccf6a6a2c0d0d0d0f4b79f008656f4566ca66acf7f3a7d37492eae9659c54cf1ff3b3cbf3ebd9a614fb39f52fb2e78e71dad74f29191a1e5353aacb949215999fb132a599b7223369ec0b323b3363a79722a4db39ae75c76c59636c39c2d85206b3e56cb4e5f7bfc8ceb195950b9ef84007d0880304b6594b2db551830db87165fb5c8ba8343c80abc3043656d73147087032e08ab0816c2859e9d2050c4e6063a7b95e7ca79af1c2644714554d6ea585e25ea04e2fd0a714c0616b24a66e4999a46ee75cb3d9d8b2a54c566df9f7b3b9dced9fe5b4388d9d3c5bd6a7ce962394d3f8525bca8e2411e534be6ecbbf50311dd303c83925cddd999f89e241130545c38366522d4eebe741e79cb28ac9c46233333433333c66666a64646256c94aec9564373ff333df3236375f4dcddbd467ffe161797c0d8bc682cf36997d61c6be20f3ad076913283e65125f4f712fd8bbd5b7555b5575eef3a8ea50ac677fe2a9b5bdb853d15792b1de45e6adc4629e6d4a32d6a73e86a45ae69496a9db1ff3e59c1a81d32d1b57c563d556fed34f4eebafa3ac9f6e3f68ad1ad731b69f26ae9fc62a1be7d47c9a5652d3d64f9d1a14fff55552b7ab6e4ff194ffc8c4bac81e05a74d2a1e305e669e9d36be8cccb3d35c6fc563cf4e0bbf63969d063e28533ff6e28c65a7b15ee65f3c2c3badf5330fbbc92ccff7532dfec3c3a2e0b4f93330b6e42e773ecc965566b42553ddf92f5b8e31cb4e73d992bddcf9a22d65a12db9eace07a996dbcfe33f2d5b56962dc7caf3f178b6ac9dadf3eb6521c1348c1c20e780ec3face707adaaa68d454e1b4fd127a7e12ad3d8c44f71fee3ef24d3323631ddae2375f72e5053fec3b62e54d40b7d826a2aae7226ff513a8ab1d5af4fadf0c9296c396979a55ba8a5c97f94beb7e255eb4c4eebffac37b93375d465bafddec5f2fbf7f4bd9fdfb2b63fdb9e65a7cda6a6f2a2e5f6971d75fbbbc97fc62a1bddbbc828eef697dfdf915063e950936d2fa34fd73673d38b97cfab9a39a8692515859bca35531729a7b537a99a3edda64ffed3475831061a1da86105b67e0ae53fae830dbe1061441054d8fa6994ffcca519c0b8b2baf2c2d64f73fe13e40a1ecab85ada62eba752fe339da82307263a70c0d95a4bffd4e23fb76903bf5bdfacff89f21feffba784fd55fc07b42538adb79838ff695914fa2793ffb0ac0afdb3a95a262f789605a7f537f52e5cebf72eb7dfddbd9f8971d2aa0f89f6584515ff61b0b2be9c5a2e2f4d1beb5fb077f173abf52087e2873ffeebcbc974f92b09b333cacb6827d4d2b4bd443b9f467e1496a60df6adfa254b7dc939f04b2297c32f792effcc7f6e283c3f8c9c52e48b9c5d5ce4a422a717d6f3d72a4ef347c169e2f38f22682753cbcea6b6a5cc4e2d4e9bb6fc3bbf9c52f367b30abe3f4f0debfb13a995b258ef5f82ff80e0cf9c361fb4ffd992a5eefcf23d5b724ec6b263674b22d569d4963c7776cd8a01e49cb273255b3dfd92a7a82dd996b3cbb664abeb930463399d9e6ec9565655fed3a50b1595172fe594f29fdb1fc5d8fca793ffdcc4f7befbc207bf64a9af6b3deb4bce755ffdef4b22b79c4e2e7b7b41f4eccc697d7bc1fb723a85f641a95ccbca589c8baa76fc884079b6b2d34a9e6e2175d31480a327fb76da7ddebb9effc3faac16188ab19779feeaaed7088b81791ba6698bc9ccf0a8f9a4a56aa37999272df1d86c6c0d0d199391313c666448598ca4e120f363c8fab15a1c040c6b6c6e38480f1f381cc425be46184ccd746fba8db445df738deb9e7ef8370773b85ce35e56bcfef737f7a77f0bedadab9ce650537846960b1c5b7ff3b1a79ce6b73975e3d9e404c29acaebec6d56fd847a7d2e741c2bd7385c2ee46155415541b1fe6b2a707cb6815fb693d89a4ea3eb4b1e5ff65db64e89a1958176ec26961dab954d1dc5d83e5b9dd6cf36cfb28dfa0b69762b0b9073ca59553faf7bfef71ffafc1ed991569c9256fc236f325fc91b8f3f8ab1252d8936eafdd2b4794d947e47ff567f69daeadfba168bbb5f9ab6ce5b323154ad5f9ab6968cbd252d89b62426116c32bf346d37245288bdccdf78d8db8c5d9a3619ff987f8cbd4d2aa7f9c3d8a5696bf9c3ec14a7f9b3ec8d89d3fc47bb346d9dffcbfecd6597a6addadbe8347fd14ea71b3bcd3fb44bd3e6f9837636396daa7aef8d9dd3ccd129ef998ae7e3aad69793aa658f626cddb3ec518c8d3edbbeafcfb6fae584aae4477a6447ca9c369f92efb4396348b3bfafea8ef44b59f9b79cf5dc31127dcbf5ce62bd7d4104bf5b2dd0bec00a9f3efb4f685f68bd7e3efb8f52d873c73834ef2765530affc624c4f6b22f802efb42f879569e95ff88564914ad67e534ffd02a85e1f753eab3e583560904676eda52d6b24aadb7525fa96539ca69fed47a562cabc47a2bd5be409f6d4a2ccb4eeb9f504efb703d778c44def7cf2546ce2181eb27a927ce2181bb9e37359f9efce766c58f626cb5f5dfaa3259fdd638de723edd9884d858764cd9acf80bec3411aa6c4f3d778c7fdb9673887ecf1de308ccb5b256f6979fd44c629c498ce5e7e459959f5397f9d4b7fca43e29ff994f4ef3af751c65b25b7e52d79ffdffe23ea74fea73e2336ee94d7956de54cf2d3d9cf7e4e13aa82e1d54ec9694aa63a26add9236d128dad4ddd2735e5c8be7bacab5485955b513aea59eaed55339ad7826ee4beec253d6eb7389712a31cedb5fcea7afea32074715a9eb97179066c766c5a9288e15ff52d361ec26e78be9b159695cffd2fc6cfeb2390f9be3d8fc039bdb9bf74c426c9dc454b359712bdc92676ba6b675cbd630b6aeb1b5b53508b6b6b7ee9984786c24c9ca6763e2b1f52fb9cd3b9b8b3697b1790f9b7760737bf36712d24ebbb637fa4731b6ef6bcf7b2b2eeb3acf8e6e6f4c426cfe4c426cb718b7ecb4f94731362621366fd21cf8fec334b6d34f5a7a99edfba4a5d1e63df8fcd57f929886d89262bf44c4d6faa5d096b4f436d6d7e797f94f12d34d28be5c3ffe528e0d7cfe318969664b5a2262837dd2128f2de6976636985f92d9c64f5ae2b179335bc741e6272dbd8d7290f9cc21cce78f215d1c643e0c297290f930f2c541e68fe4c821cc7f91321c64be8b8c7190f92239c341e687240f0e321f24652c647e8bfc38c87c16197290f995ac1c020799ef91351c647e47de7090f994ecc141e63be98383cc6f128783cc3f8a99ad05e8d15f8a6123494c3c36ff256a6ba71d7dfeafb25addf3879dadaed70883f1e78fb111262ac34698c21936c214e3c146986e646c842987868d30e9d8daca9c6684a9deb011a6b1071b6192f960234c6febe7c761234c331b1ba1964536e276ac61236ddf69f3f98fe86c2a9073ca6e0a89b1ec9e3a29f1add05c14ebadb00b4e500714d3101bfd9b9527ffb137b732886013eded86f3eccd7b1944b0b1ec0de9082a64e8ec0d034a503029b18226ae6c371944b0517b3b52c38730b43c75c9c1e6f4bb4f5a0a6d5e8faf55b79f296663799de7e35bd489aae3f134f63c62ddcb4ce13ccc146da2f65dfc1b1229906fff063ef9379b07e9e76ecbbe86e2fe657fc3ddfe9c8b1c7ccecccddf904821e667fed6317ffbfec614b3f98714eab6573914cdbbfe0675fbab6ee7d89bf81cd8b7f63692f6760404dfc6de68aec6dea8ccdefcade778d81bc7622f636f74caa7702c6d82c9be656fd487a5b4875dd2b1d5677d39e658f5a6e66d605ee4f1dd7a98538beaf657279967395131958dbd895f639938ad7fc6de50705a7f8cbdf138adffb3b7251ddbf7a1bd5128af72a886a2b15df53cc5e3c9441e33cf637c2fe6f523d46d575d280e21c40b47257abe100422480d636ff0b0421925c6f9bee3f44b77770edddddd8180618e12351a86620e3a345dfc10c3d02592e0e038b95d25d205e1be42100b1a1d43590c66b090470c1fc0843de0f020268c3131cee79c58c8030b0732618b0576266cbd80e411be154f65a10f2a7068c287818f9a3096450f9b7044c10dbd0963399b1ea10f236a7c846710118387cb0c8be72d3223e390a9537322dca0508e713e75777797c1096169c4ea87231731950c7d60016343191630ca41f82d8d342714a35e1d842010aeea410802217e10be72086761cb0990d2c2708c560521fcae60fd087d00e1e9842d233a2021521312e37ce7aacb5f1391f07750e37c7777770a24a470305bba13864174e5093d2e6690b053639ceffccc3b406a3a3f40a0cd3ef0a0831c0e2cf9383e7adcd8d4d0c878ccc8c46260785c43f3eada24e1b93eaedbd202b608af5e3dfc3aea125fcf2292f18f34c82212d81f6998ef594412c3748c7d0ecf902305402105264fa618e1926653cf3a4bab95e32d5a9ae6985a9a6696f6d1021391cc2ae66c82b7cc288edc1614de64ea59e3dc0027132ea97d7c1ce793771daa00469c3052654a1b56538caabceaf0c1881025108922c2021339bc3079c2822b8e26225a4c1832c5101550f0d20585145410d7984943b0a0c043029e128ed0c152470a5042488615cfc4a5a053021d223c7610e11942045ca30d9d39549849319bbd0c669f336bd54135f322811c26847264630d1e7cb0caea773f4b4452ddb84e2f483a2994735b767eeb33c1c82dd2a8bbad56cbd69b198ce574fad6b784e8a72212f08477f0c90d15df4d8cf5e54d7973c1564bd602bf7ecb96e07f7fc46364949b5b3ef944245f3512c7752b747363be18cb822c0aef5ba4904c268bc53e161945764b16ae7ffdde1391781f8fecc278304484622808bf450ac5ca5819bb2d4a416e9fd67b6194d82d85aeff4796fede772292d0bd1cb742b10b0b613a53c613bce8077e39dedc502af47e0fb6c872bce00bfcd7d3fea14d03fff5e10c403877c07f3d8fafffc872ca108f4f45242f6b44ef28bbe3f82f925ea3f009d7b748a1f00365b2da7a6f916578c177b20caf0b7cd7d719803a77c0773d8b43e1f56c697443f09d74bd8b485ce183e477757555c70dad5078c50767e822e975d156f97018f9bdfb1669e4d73ba8919d9c2c67cb967f5bb29b1bd9188b8de16d7d19b640f0f35bff48b3482a2db2acb7f54e96357c7f3a03c073a7a4b7f5fe21c93d6bfd1107ed90b7ba67916595a1d92292eeb31e299e403b6a67cce4a00890bf2231f2cf0180ff334aa9fd2029891f1b500308aa314b07738cafcb393594408063ac76ccde185d7074808291950608368ca3173433466f8a668391750508698c2214514fb33ad200c20318a4418307b934965890460c52e377830fc8183d393ec861a46f7840c358d360638c0ed0183b30406862ac5b7800c4c892ea800553d0e2780206682cd102348ca08931d6343ad862a4657460c6c802c3832c63ddc28df15b0108478c314e80f0c4c81ac1075d8c303efcf8800e5acec0b2c6194a5db8d8f2410f638d0c3c9861ac29f062fca2e8e089b1b5841523ab08da0c660c7141b0327e977366fc70671c3043cb9d9fc3050f6a9871cbe761dd3c7f09fe439f7f8afff8b3cdf3cf9cffccd6f31f61c17f68cf38cff6f5fc44fcc7d5e3397cfe09e53f391ffb98e757c17f609e3d781e9f5ff6fc28f88f8fe7afe23f33cfa408822deb05e19bb499fd07dfc177e48d55aaf99bf748cbc1dbffff481cabe475f0b3af24cd2a75f7b407c91cab94f3cfc18b648ffeeeebc3480fac07effd072f439256c9929ff33c481f568926f3f56948259b57f2f15978b9207c4cc7d6c3c7dffc8b54f261affcb02989efe36bbe5563afb86c386f639568fef537f68a8c4de9fb9a97d97cec5b1f432ad9c4fee65d37effa98bde236251eff7a16a97463afd46c4ab0bf717deb29a9e4b25704605302df47cb5eb9b129b5bee66dec95984dc9e6e9c3904a32d55ea1b129d5aff9b657aa4dc9e6fb9d66af4c9b52ec6f5ec65ef16c4aaef7f19dbd02635392f9d7cf904a337bc5c6a6d4bdcd07f64a8e4d49f6aff7e04352c9037ba5655382f1ec95974d297c1fdf81bd42c4a6e47dcd8fa4921525245b8ebdf2814dc9e6fddf5ea1d99466fec5fa7ff227a9c481bdd236a5f16fde8a1292cdda2b03b029bdde87556259a5f19560af04f34ab18ff92bd3e6e3497be5c7a6c4fa1aab54f357a6edadd27ca57e257fa5eee9e3904a3656e9cab4d9bc0fabe4bdd2f74aac9fdf83548af91bab646395645e69e69578bc12cdcbfecab4d55825f095c257125fe9f5ae3fe2209047b94b43b2d3f865e4cc69fc3c48149cc63f4356711abf0c399d9cc61f23abd3f863481ea7f1c3902a380de734fe919c4f4e637e1749c469fc22c9c469fc2139999cc60f9213ca69fc2d52e6347e1679c469fc9564c1694d4ee3f7c819e534fe8e7ca7f153b204a7f13b39c569fc4d4e2d4ecb398d1f691ed1ba12017283fcdc73c738c284e25abf0a5cebf6c058765303317ee397b381184ba6fa619cb7ac138a8b14f10f054dfe8f6a4e8f6a2fcfa45d62087e3b8d51dd62d5afbbbb9b54e02245303f14f47abe4530b6a9b856ebe787d97236bebe9c512b98ba8d858bbcec7f2d8e9a358ddf6539b4207b5354546b4671b5519f8dba1dc5b5fe26b6d591868061b3aa4175db73652dc752e6e4d04bfc21309cf3f54114ea5f41f4881b97f841f44926065121626010751abd20ea43cffa893a5b06512d3deb328836f5accb8f36d1b3fe209aa567fded14fbf7be7bfafe9ef39f98a5514eeb87b1ecb40e46479bcbe572b95cee874659772fb7dd8b7b712d5ab468712d2d56fdaa5aacfa55454949494949b5935454545454543b39393939d1262ba7282b2b2b2bab693535353535f534f5f4f4f4f4549f260e377113377113a70277e12edc85bbf0f0f0f0f0f0f0781dad4d342a2a2a2aaafb69136da24d54ae2ef234e322e00fb59c862068cbefd97ca2962d6f7a36653d9bb19ecdb167734eff30ca25866054944b0cc1a828aa16ab7e54542d56fda8a85aacfa514545b9c4108c8a72d9b28ab61cc3a81b2515e54d2d56edffbca9d56255fb7969b1aa179d5c2e97cbb596afcb75aa5a4719ada202499d5b555555552555252525252535a5a6d3749a4efdd3a98a95959595150a3cc5533c35f33a3a6bafe7d3ea55fa51a6fe80538db2060093d314b79c5d3ab2ec926b531c8173458a3c5abba9fba0f97c51e0daa4f1cf6c62f1cfecd234c50880203272a4b9eeb41276fdc18ea47736f98f6cfc88fa0ecddb3f9b6617ff91c9c6b1cbfdbc26a70d5cf3aad9e7d1ef1ec67276995daeff6c721f6532dad44e1e0c40ce017bee1887986b1f0a5c842f5bb23d307a141929b841e1d77cbf47d13e6590f83639d7df6c797d0fa9f17d60017b9c29987f2e314f7ebf77699f92af626fbfdfc5689f3248e639285dcbd7e780742c3debb7a433f5ac9f243da967fd4f3aae67fd3864fb20dbaa67fd3dc8be21bbaa67fd3664d790eda56773d24cd9e43167a60ce9627817e7e2538e854bf916cf79141ee53476b716b0c5aadddddd44aa8f54d6a19792bdcc5cb3e03fb58ea34ce6e57e2587442691d9fd50277573924d5cebff6c39f3d23ebdb47be9597727f5ace7979ef59ca367fd234c25d336960e8ce5f40282b1e78e916f0a5c643ecf8b4596cb392c926ed95e5860753d4bfbf8f45cd3ed7ce1fc5318e2f926b2a9b8083b55d3d7677debc1ef20c6a27ef7a74ced1334ad479111059628ec68ca65d9a245ca072c9ca684e0f2d43e41dee58a1ed13e412ec6d5d597ebdf3e8c657b89baccb5efcb49e54d5eaebf37799337f984729affa4f3b3448ce564bafe93a9a96758faa7b364e99ff6c187fe692184e89f3ea29be8997f3b8d2591eb445eb6ace0d8126d2963fdadb63af27091cf76d4f765375151f97ca69d37a93a8a8b7cefad6e7a96fd6acbff9494df1677529df15f4ad99ca367d496e3d06ca367fe2328999dd481712289930505a5df7bffd510fcd68b1fbee892c17ebc8979988f199a5f24f3b197b132b11818d8f8728921d862d5cfeb6833d163bbab5dec76a7dd77d8c25f6a3d75dbf676e4bdf72fffa6dd1fdd34adfe47acef64fd47ded3efabff512b46bfffa8be077ef7fd51ebfd975a369817addf7fd4bd0763fdf74720cc78043ee85a6ad9446b0567fcdb52cbe6b256705a7fc40261fe1efda3eefb996a6cafbf3deb8fc0ff7ea9651bad151c97bdb1beeffa47fef4996a6c626841cb2dd6d3ee8fbeaf7f3beabe6b59fab72396ad7f3bf2af7ff43dad479f773beae891b7fd0e01363f2840ea03b8d93c538dcded8dfe6dc9b3f90fe11c7d5bd8dcde965c367fa6191b1d9ab4b3f2ba62aab1f9dface02cb53a6aade0dc8ec49f792baf1a5b4d0c667481b0f075139b81195d60cc0b269b81898d2e500616139b898d30957a3132638f99188ceb8fc21f6f40d6d7fd91f74e440f1b9a97752f93592b38b7a3dab2a1b156706e472d5ad9064db69b9a9918cc08fe51f8aebf2db56c36d60a0e8dccde986a6c3ceceda8f5333198d105b2beee8fc2f7bf1dd59f89c18c2e90e57f14fef7b723fa33b1bf1dc93cccdf8e627efcdb11ec5d7f3b7a3df8b723f1bf3f0a9f35637364ec18b31f636330f6066665a395bdec8dcbc6449bf347a17dd08e2dcb2c1b56fbd9da21f4df8e3c1b760821f41f515b3b84f96e6987d0cf7f6433595220fb73fdaf7cb074bdf83cfeeb19e661cfb18f29737a46c690dfb37e1812468ea4cc458a64488e2d16890433671b20b74f10578d4145b8bae38eeb4135280c1a836241b2a09ba0b7422204e5f8d5d51dd7e71060cf1d2351918f21fe21d60fd51ffa7ec8fba1ee87e80f157d53b7bf0809d16422b6e5b39e8865cb9bfa44d596b2ef893e5bc6bc27f26c39764fd4d932ec1913515b52a9b9fe44fddedc6da9ccdc69735873c600e4f629f2bca2aefba12216ab55148645afd758040313f345ee324533333c8a643f34bf48f643343688686868841186e60fc57ec87f08f643ae1fa20fbe92fa4afa8766dbf2f6a4b1a56cdaf279d872e62626634b8fd952068bb1250ccc9631d768cb97cb9623f8546a405b862d5bb2a8d45cfa546a6e2da9d4dccf96df16d7f3efec10ced5d5d5a5b6a45273dd96df16d7bf34f2ae5b2a33b7ad6cd227c09b1eb3c8b44c33f3f26a6c280f598d89c9843d9b45da8e21172132a2b7fba1d7a532c3e2224446dea53f24c2a512bb5d1122a3befe437ea9c844eff95d9e8db5b80895f07645a8d0db15a102738b506951e94bed4dcf66112ae3eda7e2ba5e848a77dde6f46c1669eb47804c9fc8e87b7eda7d5eebf96b6db15ccf1f82a14b7cfe1136fe2bf6fc31d78589bd917763de68e60ebd2e8fe7978557e689a8d0cbe38de89d79a3d81d12e1da1019c15cd91319b52ecd1351f1aecd1bf5ad792222a3f1fa7822a37a7bfccd1bc9ee905f1c2223d725a2d217c736d7e61b7977be0fd2b936bf0749b936ff86ecb836df8664716d7e0d59b9369f86fcb8365f467a5c9bcf830cb9367f8604b9365f866c716d7e8c7c716dd66a23d7605c83e1da0cd764b816e35a0cd768b826e31a0faedd70cd866b355cabd56af3917c4cb7ea26a9154859b1245585941452725f4460031ee478aafae28a10b0ae48828731368e35a717d4000c28a83881162fae08afc8218771e6724eaa885b8eef6b744edd690f358c327777291bae63b1b9ffbbbbf70da3eb724e0a861b5ece5d41c7ed2ee7aea8aa01581241dd1e21c18f449090107fdb2877fe5123cd1e01acedd34dd6f993e79c64cfb9022e5cb8542e5c64df5812b9a38f7fe3e0e978f6bb9c9e7161755688e77a9ee7d93f03acb4769de75531c6f5aa80010c150b1876f0210926a82742d0c515705cd1e50a29afbb2e2d83c7e58aa4eb793ef4522b1db9e08d29b810e38c2fbcb03117ae250fe74ca29e2d5e688163ea06343eb0851561b0ac30e3035dd7759d155bb07458baa2b5250b261e991552d430bb12b028a55356f4205af154831532b8acd0e1735070cbe7f18cc86d69aa62cb16301a872e72d862820f6879838c2b1b6dc1a53fc271691572e8d04ab1cbb92d465cf0726e0b102ab83296fd749bca7f3c2fb3ca9b534ef3ec0b1eae8b6745c5349ba6962a4f4c3cfef37d7f49a47b1b25df69de5447da34807ef79d7da13eb5b10d9ce1870485044c9bf77d47ffb347ded3ee864403e61b245801ce569ffeedc8fbefab3df2fe85a3cfe25c0c1964c1d9e8d3eefb65fe4343701cd94f651f71e73fd934a07befbb299b06d0f79ebe376d1d3992b643eafe3cc0e33f6de3075f4f77faeb5d4fa442eb59df302caa70ab475a01e20b302f3a6883bd5b2414a6c2bcdcf994eaceffaa5ce6e9cc53d807f3627d507c5004ad4d3fab71534e9b0fd3c5a6adee7c5733b55337b5963bbf2389ba17c1fa300f635f807d7db689301ff331f685ef619e6de0b745d2b18f817dec5954e1fb986f62baf3452d777e8cb4f11ec9167bb02389fa55783debfd895468bdebfd871a70033ffcd737e026fef8ad07a7a802eceb4f5185ef611e85a4fa30e1d787b136f4679eaa386d3e8cc4d9d0e974e7cb58a8182975e7c790365e54eeceef4652e634a2a27987868814a0801f2252c1a601ad7f7dab9f48059b06bc5e10fff56c83f9b648baa70ffbef41d8b3a882f81f0c09236dfa916cf5412a2a806d49ad7f3500fcd6b7a882f8af175508bff52caa30da97b5f1774d89644856929dd6226ddca2c01d42974ada34928d9d763422755d5d0072775227c5643763cc47d9181bc7366698819513d440b5851646884ed45063e3e51c1549b0018eded9efd97432a767b418dc510618531da0428a2c7ea82148a25688e7765dd7f97377871b556429430d2f6164810596ee061afe676b7ccefbcfe83f9ffdb276dd8fb8ebdd6ee9765d47ed1af53f27bc9ca30204b77c9e6e066f94c5a8e8c0951e65e5786354d8304367a9b8b2e44d41b798428c18a618c30a8fcbb929d0b8f4726e0a32b8413abf2fc3eb799e57dff33ccff3bc8fa43da39ef7a3ccfb59fb08f15c5a1b07907368bf0da57d3bb8f38914a07f2ad5cc9dc0eacea992e7ce1c0a7077feecce2a140c714b2377caeeb4bad4fb3ec08ad50c99c21e1083975bdbc7610f88c1a9677cc1c9ddaeb6338e31e672ee04572e4f39317af7a5ce0562022f2935c11b9732d9e8c7fa87da28a5b3ac945213b07129a5fea3db8e83cb39135c71c5cb39134071cbf1697b1726f0c10447c891e3e58cf096cb43faa7daf87352545dd8e59c144997c5fe233ddd18633fffa69c1d19eb99900c9c88c1972b1a8050818d5e71e98fb92a2efd9bfee11c0e8793c1463fa77fd8466de95688675c63e45b8eb7dae6fc26c79813a3783907051cb77eb9e5f828005fcc3e77a6902f403a521b92aee58ea45c73bf8d9c7ba2897291eefb3b2e22c7954d27e9144e97a7e8aaae3f38d4fd34724e055d9e6ec9530eb502dca5560ed573c7d8cf391574f1efa4fce7880aa09ac65153b92e5254d75530d534be65e7e5fa774efe43a7a895ffb49414e788102969f5c68e366e8a8bb42dfa7ea8e8b3fec435ff69c535fff9d4a58318dbce2f5c73ce498971cbee72fd5b88b1ac01e01f87fa1ff6e11f0fa54da8c4a9150cb4899a041d536a06680441400353156030482c168e08048a26d7f80114801098b2545a9fcaf42888814a19430821841802000020002030334c0002449b3da1f71f290cec0d6a2eda91cd9c18032ff0dca5a25b87109f947ed71b47ba08981d9bf2eb8274611b9bb453e3e7ac8da1b052cda092de2ca54eb20d304da0082804cda4a1f3c20b1db431f06a0187aaa837b4efb965f92729070114f4bcea59b995635e6ab964472cf4a36ab4b19709c2ca9de559662a421ffc529d1a7b238b3f2aedbd15738daf14666fd4f24795bdb76256f795d26a6f84fde51b60d7db9446a2d08e90226a9d8d122b1a6cb88ea4185e8ab8e828c0b547bbd13679d2e8eb62008f3df7c003341b2a56a1313c5bee8c133f55ef2b6be3a8f64de1d95bb8527d6f859fb221f71f6bcd8b6c8a9d7e5ae19cbd1d83b6ef5dca812c912fb71f29ccf63e3881fc2ddb5d2cbf8e2013a84318cfc5ea0ddb63bb22e7865cad61f213d625e0059b3965d0d0ba019f1b9c8f463543f237858748ee5ea3cc8ce2e9169db250f69c42c4957ef25f05bf927d337805e7a9c127a68ab304437b4fdf1cec75c4976720c5cf675cbc7c9600104ec31deab1daa6da90ea2e68b2d3fb183b47fada1739a348859ad424dfcd9b5a51a636050bdaa24005bb3e09d32a06af898c3ed46bd7ac2df71250bb69135da4b3230e9a600952a3cc8dcc1ef833daf8af458999fb360a2a0b55ccab6b747098d712665194cb54527b1c476b4e091333379dbd6d06867ef58142c14f115b2b940d25b4d25e5dc424683aff6af940c398b7e6d19b796ea7247d57f6f1b29f5a3042643962d66c46aea38ee900a581e4719715dac6a44d115c8616ed20a5c64ae32713d2162995829cb618dba14a67e654b9a7aa0ad972cd3565b969a3ac543a5ca6a717047923c352c8baaad3acab74ac18f070c0da4cd214b5d89b3bab2805208b9e7870c36ad252ece6816850c9a571009a29569705ebaac0e73f4d13750b574673759a8e009465223fd72f4de9a84d861936f12ed4982c4395d1424c35e51c48136a3bb71e1d79b70b521edddfdd699a24380f1faa9b9e956b7b3d41859a768f66a9d89e720e4ef1c4423e57ef896749b13efdcf6ce9e1356c06ce10e06fa93e1305e565f67da0669b831e088c05fa2f19bc2ebf67657f7d691c9ce87d1f5761c8bbe256fe5cb3f027af104fab7322ded5187c0c5e42ee42ce8a270b5b252e3f050235054a7b7bf28ab5bea6c6b4f4964b56e2cb65ff8d2cea2a8a4b3f71ed05c0442127b2aa9aa3d5949d533991365698308826d8568c9a443f916060611e49de48596136deddaaca8651c4e1c762906a01a1b40da9f7888ada2ba960e322b592daa3de0455ba1582813a54d77f8d896cf131506088a9bba9f2e218a5a46f5ec66a4faf1697b1753c30fa2d759189369d38b8d6059246990ab474780e435d3f1772b935304f056102dd56386b9372ef6b781125778254d44995bab3eda025a1b15072bad43cc1afd5682b16ce25195ebae8309c013ed5bbfaa9c7820349259df8681249971dfd2c01ddc3db884b424f3a0126be38a999dec88aa3d05b904b4bd83d2ee6043c1d394cfae52f77a340521e074963371f336152fe576a72774a16cc67fe0188393e5257b75f154c9409184dc7882e8e76c44c617f8bbe191a9eafe4d9243cae15e21df29d5863ca452ef3245a288ba66b90922243c5425aba5c8c845af81354dfb276445e7638df27521ac547dbc3cba9a395ba982649ee347c062f49167dbeb91b7d93f4daeadffcdf3b893749e28ea96747a2251dd12dcddf32d81258ac5e1626fd71cad511914d5d789326ef8c6c39e122d373d120c1974946253fc4bfb63eed5e467fba65f46440959dd836ea6d27bc4a21e599bf34788ad8bd92bc90f8902deffc3049c8b70c9de0629f23c561da895c73531c7471978f3e349ec1402aaeb5a6420bfec49334613ba38eb3267466eb8ce8d59adf4b3e7fa89e2274c0b2c1d5e34047f6fa091d695fe9d1064ba530cecb62fcef245c4b8f43d09f1cfb284d2456bb7aacc819ca1888b7dbe2ca0ff83c8994bef1da1806f9c27a7ebb01f4066e489df2076af97793f05fc779fded8cc8857e1fa2017d627eeefda020eaf90d931dea0bf2ef799e2b75c10b0f8c339059b86124a8db6a81dc435fb43b9890082202d636438c8a86aba073f75df8e3cb00f25ec6f0720a6cd342f71ee185cf3ecc02d4534e7487cf64be6feb6ec18d340400426452f811407ee619e2f0a440bf0d20f1252a42c6506345663530c1585405f964c212073a6ff8b86b0d61a2ae9c0511f5e57ec7bb433e6514688a56697d15b4c70ba4dff9ab8ba44f7a83dc758e4cf30c54e781d89513d83d6c9915014b882ac53f346c1c4b54bdb424b3db0688c95498cb9cd1f3912197cad54afadd7992b8f40e38baf374eb32a5e899740e46de3a43a8f39bfe7c9f77fb734e886a2212d4c175032075d54e4f44663a096901c81b5d71042a0f83841fc18dcfbda063064b2b09936097c6d75db9116b120847d2f7e01baca50e9ee5276548f69e0b460954143ed52ace21ac28e0c83f9e3d68202df581b25a558ab3dea6f7903571f32706ea6f46e7d1e53f6d15283d8ec329093f492b9eb51e5911020f4c6275593de84cc4fa54886d513dfec010bf4ac8111316299d70238b13cb64835f4849c7656d2e0a85cbaf474a1b9ef97cc84dbcad93e7bdf19c407ddfbbdfcb660f84cb303a705bea1c8ca65fb7955df5a2799b464b5499d4ffb010399475a9bf24a1a5aa03ef2c7dd2742b2336842a1b2c0ad87730a7c4ca3f80e15e3855f446d650449477629f313c4f6fb4a4217a4a3923e2f24408ec1d306fe55d3ce2f2f74536516422b51e0064cdd56b59be5baedd1e0d3f181055d0133f7cc1fe93eaa5410f74a11b148f70902a9f4e2676a6b4ac09fe8adc5f2a4f92aa5b8eda56735b937214772fda15e585d2c2a28777e3e755039e14322c5e88726dd471b446e35431e4a16d93141a72c3cc5fc1e0ce7731033d8ed114ad8b28fb8134a6a282c1fc20b6c5bd8caf1dc90d42bb6a4b01633d71b7b117faef2ff0a21c6a04c3e80736d1bbe6e843733cfd43091f91d2457f54f5355960682c21079aa9029fd89d215e307f6a4302057a57c42e62e034e52adf29c32f54f2d3d99d80053b9635ef497c564f733fd658c2b2819f189c57e9ab1ca23f1f2edf474eabbaa0371d966c29c6a30ec7cf09e866b7f88dddf0491ded82d9750976336d482c070633bb3b74b3fb1bd64564faff999dc059947cee8bc9d7815955e5e7a395c65a9988e184cfa2aba11d7a67263257d80c86466de8f6608bbe5ce27fe78cec69c143a2f2fa3064b76765d5c5d29e4f467b4d60793bfa8711d61bcd37bd612e548d115f7e3601fb39192e1fc068ec54b4eb8c9bfbc03f0d32c346d587193b8ea03f89d7c3b0388800ce66685019b0302d438b54cda15e038f79fc73f15bba5e4eefa3854b37d6ed5b9871a4c68d574525fd6e5007eac2a7ab425f0f09b43d6b8bb407bdd007d57dfec15cc52977cc66dbd6aad4cf6ebb25c7c66c7477729dbd1e4102b4911dfc436e9c29b3aad343aa41ef334246749f382e62d8d479cac350015db653088a4e93b24be39fa1beb8e1add7776c9826d28c4e8a96d554e4e62560277dd21e413f73e700c1b10e90a442a210b5c402d206d083c21b9b9a166ce94ac1f82a51040b1f3125afd009698a7b3faa49f4758298e68b352fbaf599bcece122f88bb819e604cc23088b88bd725c06333d184a2fe4285c4adb755ee942c78e0dffe0ca705cd465f6cf41406053e51ba9f5d14894bc014deaee964462db16cd2b60296041e8c4028d9db38eb6acb9a1451d6f72e963f8a96bb4ca7913b7e16a174162118e1d94dcb0593f660ee859b9736261b26643ce67ff9f7cb76152f0f222db9042a421a9d4b642938423414faca9aafca9ac52a4e42fefe1fcb3a9769c4daabe989c05ddc21873df5c3296475701c66f8e8e05e3e4626b0bb90c5c81913b61afcfa5bed9f78b5d667473e9752ece61a6b78bbdc0c0f572d765d9e2566c5112dffe7a7967743bb8568337ee2c091e2cd153c350a1ef642ca8e59a70b18be35a73b227334de8796aa5a5f908ce82566553f0758566843c57684a09a19e832946a089080e73ec42c0e15dffa5d3fc3a22ed6eee702894ef04c694441d3882bab5e3b19b77bb39b9134f94c88af0dacdf39e785c32ce9e98219758ee795b329c4a3283c89609c765679600e120e0da74e43590531452a8370f7acdc890af227ef833ed3f89dd90c8c4212c55c15753ebe2d2d9f8d70034966d14f77a869443d5e7dd4f25387ce285d9fe78cffb02122ba74e6ec3d7316672bde6f746497f635c08deb205c8fba9babffdf05937e10c0ce4b7c43971ab6ccd20005665d40581912a831504cc53d99e2078a2609b21aa271506b5435301ef04c6d6457862dfae833c81651b0f1ba9dcc52a791f2aff85393f5d68a7855ab398698775e08463158fb97bbf52eb4ee24fac01c1a5354f3d257ff4745dd952081fbdd08ea83dcd92f6ae1863a07dd78ba04d4290fed64b1c6014814cd81c90ec593fd8fc8c9e66b6c4284518ca65711cdf049454e5a59a16f34e93c1fd23c2445da0b891957c66f9b7466cbc1fc32b0a382351bc5b631b89834eddb2a8d35d2c4f2261733aaadb940cfacc87c0403e7d8ba238d3f1314c24f4941115f54a5041b2fc58816ae3864ec399039a91f8502577fc5e9918a5c0e752ab49da983b4b752de08a80bf1d76cba9f556393aa8ee96a2d9d76f3a8fec390782b9205334370d4f25d0f29510e2f471c6684044fa496ac299c91e9cf4dad132aba8437a109d4b188b6381dccd4af3d725174dbbca4ba552a85440ef9cd9a2a2ce1b0658827443de4eb17e69e3ebd75c3eacec4710b3ee4c188d391abb468db4fce44553bfd415f77672ba125a64755f54e99547dc8ca391131c80eb8b647d9f2b18d07b17d015be5927b2a40a66bbdc312e16731e49afc6efadd90b887593c8f0b8ab7c48a8837d2918e7b9b2ce4b12cc5211481b78b474713c641762d99a8aa0aa5fb258a62a393fb6320e78f168251db5e5b57aeb0d02ea9adcc80d00245fc80b9b1000f99ebffebcd30bdfd3fe403aa53a590a57f94ebb7d4511a1e9e0442465b695f028e6eba26cab08cce4bd653d01a2494db4487a3dcdc294f84dc59c341ce6f57c976863b57e1c376cd23fea5c09ae5ede3444c64404597f3f3700f12dc40fdbfcc89cb974adddc12545701f469326f207c133d0e620693202b01a031c8f102835953a465db484773104294f553ce22e39a560466b228950fe8594d689cadaff46a6b27f4e6e6b26313ac6365b1a27268a86e41bdb58d392abd83920819849a02631c279e96adda9889d39e3be786535a4ead7082aa2641d98820623ef3a77bcccebe6adc609d7d2eafda7efdb410b95844a8542b575d27ccda880f4c430f8c40f9ca179b09fff92163c85214f868e9a91fe9dcea6fdc336d507b73aaedbf3275509a8ddea008f3a04e5847b6ae6b006c7b4655231cc72021e81cb57cae946139c848cf14f3eac4f842b16b066d8e2354def71ae1498e3b6dac3375d7f1653a41de6187acfef508754220543135350ca52ba889eabb99aed267c66d7a7627acbe02a279711a81bbebaf5eae1c9133973f49e66f9c6835bea8ee90033c0dcb5a3dea5ba9d7c3a65c454448d6eda84c84ecd37120b0d8e18f3bf1491ae04dc9fb8a980d92486aaa7ff1795f166f6ce68079ac865491a2b63250b5eb7728acfc0968cf9f753ed242c871a6a4d98c43393c93d281ecf57fe38bfbe7df0bbf3f5cdd764dc7cffb471273a2c76a9810446a1c374f97bc526978649b85703a35e6a6e02429332ac0d99f08a41bbea26920312ce7a72d4ad4bcfb2682e1d1de0174d36aa0d34abd484d402463d7916b493b31c135971183a4214ae08f2ef4c8d5b16e2ff523225e1d5e2c263d71fa0b1e0e2b9739fbc53e46261cd3e6f7430c2baceab7dacfa6e80f3c78406efc56e47a642c4de674c8b5d7cd35161e98669cd281daea8be38ce44a3a790012e4e78f2da5c1c0ac58c0340db48d6e7a1e13255ead70dd2e4a27c4d3af4bb7a517290d679628884204515a079c8ae88ecb4681903a942059fb88880ddcb8d47415132e20251249bd05aa612adaabd5cc0832e680c9faef141a7ee4abcf6c5613159737bc49cd19fe629a5f1be80b35cf8c79d78d591a96cb121569ae5c61df116e892e4090bded4b1b15628c1f85e2831f5414c8d0aadd23e8950c7d3694885a57ace181a0bd4b93dd6742033b4b982c5cce4c66586c3f3b1f682a9ef939a64b197e260b6c5edfc670847719916f30c7127d7b410344119fa2c0af3f72066cae9a77af860fb5ebbce040579efc9174301ef5ced5da526b5f20689a7905da5c733fcb7f638ffe874cc977a9d16eabcfd6f82734be5bac2249c28ebcc59e54a2bad5c69e546c0080ed310755e92eaa5a74d9537f8921efbc44ac80fa2a7775c46ac323495bf245eec0a02e64b99e2e6f0acc6f7a2625bb6d81124e2f7c857d09d08c9f065f0d70d78e3aabf2c8fe93268e87915e7121768a0c5dae4a80b502002d86d462394dfb084145326330cf6ed07196d69bd5ce3dfbbfbc629e78ff792c4071317a6dd87cd79fab4f436a6ec909a98233ddb29e058b802a16b4c66d537bac5c8aad17412ae3d0c3f3a4f663dabb21d51b393a96c23e41cf7acb5d10044a7e8146bd97e9f43dba6bfb67fefc076d7826dbf2fc01672e0762c4708c5cb2088307d946e52d7cb124cf662a0cb003702adaf04c0345175b811ec78bcd83989788f4e1b4389789fe4dda5ee4d74da8d54fbde74852312ea0c790ec4ce2a98a419d4b735def0dc3f20e6c0177c1f676532c82487b1d5648541255404d0cab3c6bbb9483628e098e6e823a18406276b89152fddabe3ab79a3fb063e06277a710a31197a50d815da9ba850d8633da651e85bf2945d10f58d559a9eb2790fdfb65c841e04bd5afc866cfe55bce1dd1738d1d2526e04e7fd7e59e1a35302c3511cf560c8fbfd080e1da8b05137ebb0e1ee3afc97bf49fc18031abc78bb60536b59514be897565860f2b22db46d52daedf17e0476a962ca623042a2a79ef79b8f2815258014be8610c01b17c89c7a5111ec4bbde5e67255f7ff18b4ef6a23a5e2be22186c1256b5b985809773e3ed2de021e8354add2140e28e298e31c7b19feb0c3fe768aa8c33f9ff2c6895f2fc12c01713c7147e1ed5238b6f429e70f6dd475e5f2f26645005e23ca1bc15f2872c9f985331550a198863031a98da57103656a596bbec59350157ea33a60d0428a78d711bd6a807d49371cc728c1abfb8a6aae18c34d0b2b36fc0533f4c929d6d01572763904996ed490ff3f41c37a44fa6eb8993050330e71b5463223bd8b53788cf6f648d7d2db560290c1051e6450723fb978a0c213f0bd1af931561c271da4881e8397c02f79b1a75b67db28d6f6f20051bdda420fd7c4612b20a3cbf9778f96301c4a70981940a072d2f16feae28627b44a64981e84bd589c105d1406eacd04b583fc2d78b2162e5c2b6b32026ca2794d38464d52211b47f2101ec587dc58e470e0b8bcedc70e31f5e02e2ae12d31f99fbe766657e943ccabf125d627343edde9df62e9de6a5b5838121e3f9fcb21d66a893986b295dbb78e95dca72a153110bcbd2efca9f860203b717b06e1b2a2640dbd133a3c80d3b7f474f176898ceb2b2772e0dab1f460367f61d11795fa715b91d3f17e507b47031d7a432bc7861d1f45db58651491bd0fd197a30a51ffff9fa2ccf40e934aeb4dcfbef2fb76d4e74c7233840be7bb858202832b39cc70328959c10045cbb234e06a2c1281acac44e7a2f84c7f7fa30f4308bbd045612a72f1377d55dae2ba29ef36bc95c195437c1a2cc4313c5528f6790c9120f38157128e41cbf10406a72b85e0507b7e1228a3f2a80e6b85d29554fb1872046ed90deb2d0e170023928856dc52f3671e60d928cf63edf42886fd2d4bcb17359f59a07ce5048edf58f6fd55965d770220ce1fafa445a0c11ddc30c271540d3f8135d170bdba4ded555d8ad4b3534296f2b0a762da4bea83d60d89373d5c742bb07c4cfb76c0b823edb23ecf1871ed27300461a3d38e360ee80d2e338fca6ad682b06481e8e83888d758f9c4f37764f83d8ac023944ead7e0a5664559e8bab6451b120d42088dce7ada71cf4a1c3bed6fcb48193d39482b288abba24643bcec7442d5759e9b3094c52dd1a1390a166713d6a0a37b3c7350481b187e25d71cbfc1feafcda1eb22b5d8730dedcf9be91adb6ecb16144a25631a496c5c66ebcee41d00b4640003eb9b1d394f5f8ee79736e2929ce3791b1cc3df155e6dd80b2bc7f91ad13c1997874e15ac12488acc3f4b56d50582f0423087562b03c5e14cab16d17910cd795ed4c93e496aec1c8e42b259af81988b6fa1b51a5e488049e14d63290d30463343ae3cf120486af08f1c0f37fdceaa01ae14858b4a7b33a1b33c1a12588bd226edc4b4b9e1950f63c6c1b91ed74cdcf08498cac054231dfda990740021df3369b6b917e083f70bd4650c5f4debbf50c304c2c02920f1ff289abb20c24c23f22a7fd0d09b3913960cfa389173cb50b1f33181fd6abcfb34618c82a073353a14dae776fe127aa44a747296904b4dc0cbb7d2462e042552d0f9a7770e103a780df978a841f8cd6e4d18a91eba4e27d15e4e2bbb5fd1ce94b332fc4737a572399f759f2d2de70d92bbdbf391252b54d4f9d2a85d6aa832c5ac0f259bb6e013ba2f6d387fdefbe287834c341e91eaa162ea5470ea969e6aeacab5edc457b0bfcf526f0be7d998d3acb54876ee31fe93b885ca08e03fd1101a9f6283b664cd8b66f7ae81af8c330f168269a3a00a2c0835be103c2365db06607e1b80df50733be2f24002d1af85080efdf2891ce24b98475708d11c44a9ada84e8ec23e5b6540f76a277df5beb0078ff182e145082c0b48e70b745ad2651bbb70bc8e4a44da8bfd261341f8b1b9f0eff3f5bf8f4cecb0fe8637b6414d0fe7dede56801b0c153b3bc732fe6905419785c47e72f5c74dca782f222d82c987d84d46c3f83849577f2f1312d9f7c43231455d231806b26dfb51d8ba02fa1be8a6899e3eccb2f5296a1ce3657f20cd4173ab6d5c6642f29621cbff6f5f3ffef84151fb5da5bee1f5de807e861ef139f4b5d0c8eb07ada9136931878d4dbab9de4a8a783a8672f17e87d089e3d744b63154270cfa78a78fe1cfa704129f5fa478885f56fe8e6cd20e377c600fdf148f36798aa837e0bc17ac305960771d14666f739edab5822869e9f5131173b050a24d269c7bc12eef2af6cfe83165b10677ec60f740aa0e14e8edfa28e6412999eac68e15de1d1fff9b82492156030b289ba7836893213fde4d4ca4d6489c2133a8332edc8f8e483452c05ab127846b65fc5930b858c4f6fd0adc85e18fc675aeafc65d9b9ced088ef36a8602b422833128d4263be2cf94612d8b6809da3fc68f4611417b55ea332ec007171df88497c2d16f589402e30bb79ed859d31d9d83f6904bff7f2431e7120b7517bd94dc57fe1ccc3965094d4c8e8be8a2e5481b575cc5ec4f76122c0318b34f5ea50322def9c366a9a9956b9089d7326c330655b82edbad92b26b29f2fd87ab40b833cdcc711dfc62f9cd7bf9eb0738d4cbeeeec7ddd4b6015639ebc1f7d1c01f716642d54edbb40c3a5c5aa960a7dbe6a063966885fa62d49b28ea00681281ccdce548ebfcbf26e751f1b299a8dc3c21023195d54d3347bd3ce61a69da8605f89c6d161737efe6b5e527544cdf3827f3e62ebf33a7d7520031b79f3b8967ff40822c9467bda6d2bf3c06b0e5b42791c3b04db66ba7aa48f10424ed7cb763ab0cffab6af5e679af3183869c74b4e2f63d2ee48f26712972c2d4e1e4849dc4c75f7a3100f5a165f5e15697dc70a5a3ce1f9d3998e8ee3e110c87ef83a5fbc2a20f64abcebe87293ac719fe5b20ac2afcfa7dd558e9be395d0d972a074512b12391d7a856e1eab046b1542cf803f928923f0350a1d76efe4800c91d65eb9659b051104f7163bedae3d2157fade540eccb48e49522780ed91c88b4882f1e6d5a9e4e956859ef320223ea68a57c7bb565981bb56086007e70335f42c08ff6d1ce6a24850a91251ccf43e9b9617100fee0f149ba9b3bbc944eb403286996ff9774228e77b6f9fcec469e0a3704fee9f7077cbef9c3e93117f8cbd5d915b3c53ad34a76cc7decdc8fce33cb69fd1a3db3d11f9963ec58c302e7288290b5a3614a0be453e048ac50974dcbcf9dbae46a11a2d01ba9d9493d386007defb4108f7504d77601c4925a6daad1c5363942e014ef97b2e7dc4a6bde641f4abcd86c5c178ccae69e39aed0f09980653a7d598a67d87587c06110e005a754854af55ef79fdf2034e5efba7bcfff842e16cb8ad4f524331a30ed6ca4b7d121bc637b804f90307a5c9175644db5e0917add4308549dbd5e51a4b45a208df16be7b50dd54e58545da5ec79bb82174614fea40bba4e5433357ff87c143611e007b7ed55347838820b5364dfd7de952e6fd5bfb344420f8ecf3370f611f1b8ca86c9f1dfc5151f5c3b3de30d55ec60359d2292e926c6a7595f5dbc7300730539f47274c2ffe43306cf8ee6bb2b18693b1dc4095bda26f3360a7d247ec03d03693849c8e3f91a802bad6cc4a9a4620f93c0d3e1a4c744c6cccc1d7a9e955431c43649497d61f3d162d53379f79cee4cbe6bcdc2d61751f09612519cae44ba28c9d9c481806c777a2070465aa43dc7b50ad106e6dadf39cd189d95d647b22a8ca6d931d7cd4da0bf9fe28cc59e705ee3a7e3368e5df8e746a0540ea25c8e769f6dbe1d9b5ece225b7c796066500bf6ebc057520700e429d65a4bf05bd72cead9948242ae7131f21941a627a08c6ee5635749207777ec455eefce4c13a8e956b4509c4a88bb6d7190c73399899730719b5c97974cb960cb578e885a1e3d1e1099dd25a1565d2e3cbce28987d10a8a7d9125c41f0163c50d7bb3bc7eb4a9bec9cc4facbcc08e202dd50f820bd081f446fe007d35b7c60d01b1fa136a46e92cf79aefff226dc939b539589da8912f1a6b4481dfdccad4b23ee88ac7312ca70d9b3f2073aa1a579d8fe7573bc7fa2bef54c696c6fdf7e78d4519e4c70c0c24eb83497faf40dad191c6e70faa3e93ef1d4c0b9eb43ec8716fdaa9ffe9f2dd04c99625a72e36f9ac234b0b681f406702964653482d682f1f31fef22bf1fbfcc0abb32da30bc7887cd6e1080f2d610fdc3c070a05da2ff98797c02832842b37a31486af908247de748b1b685700e2429d718f46ca2b34d19be94d8182562464ef17dbbf66dc9a516a2ef0715879561d631f86bad67db5a330f5d3143db4864349b4e5e439e6a145353bfca7928f2feace6eb5e4f935d923728bcfb8b800028ef0487a83fbe1b680b9a91fbc49d1847f4b0bc6d1d810bd1501affba3eb024258d5b681c5257f7bcf598307101af3bb497916507da43ced9205c51360fdcfa0ec6f9c3d6907a49c3944942ed6b1403321c3f47189122d85e842c1bfea28ac0f786ea654cfca4badd50bc7521af9093e0bb0ce3af8d27af12451912a7e03f9ba0884a04a6f63c436d3e690703b53f8016f8b6c884824a342b9d4bd811eaa40f474e30e1c54b7ce52f91e860f07616aac89882820a69da2f20aa2f5a7e48b6ae869c82ad837cac55af7c9e5a685474730c7618be629379d8a54b4bf5d6354c2dde2d975fc749a699de5bcf32dfe13e06054df534d6608db699e91390cb95e39778d8f185a045c9a6ded78cbb8bebb23c9a1657a9b4865c78a40a224b6ca1b7e087d0ad7113b3ea9dcbf4e5c377949d01abf2c32bba86950ca4d43aa3e271cbfa86608830fed8589fd5f57021ecbd1e54da6438a2454ef04a642a9fd42f4fab6590527517b7dd68956e86bbe299359914199efaf4397ae386da63f6c14e4a36c81f054218576a384b5108b23cbe4cac271234053799dacbd91adcd74df345dce2345c3a9707059cba4dca3537d37ee0ea4cec7a8fe6a79446e06814c687a9c68818f439d39aaeaebcd3e2cfe75774397f32430b66bd3877911495c0b90ec1f01cb2451211e8fac316c1084aca550cac2d1cf43f788f935a21c0a17b13f88290c543e6931996c1e25e5f1cfd818d68f1d718a14012873a7568e4a9c3ace3871dc91a530c3f8b3c08a115bbc421eedf719a144536195e12bdd800777f408e6e9aaf1d3b1b0bb87ffc3c94e084a0819bd9d3abf590030552b44ce0fafb924615dfb78247e8c7fee88ab90e7e2fb0f2f3d0369979b5e7fa727d6eb82cbceccbe6c2cfd363a489149a724e02405120bf626e544ca0fc97abcdba3054a11e20cee642794a24f5032853cbea21072372b64424a011e0df224280a169000737dc72ca147dfb1449e13e8e677e24480e70a695a9813dcdc5b1e3b94335331815de9e233d6bb925f0d7d308a3643d99d31fb370823cab57677ea5ad136b1b45c389b3894ae5f5d2f3b31d2ebb1e1d08906819b867c13fbe7975824bee474b0ed6c4081ae26e36685abb04e98ba00838ccb34b2f802a02014695e898ded7a225c4104411f9f92120a301c48795bcf49412e8bdce70dbf5198df4dc5113a9d52dc7ba1289e4b06191ae36940f6b13dfc92f6f18f669e92e0c3d5136c18859daa4a2557298b148187e29d8705f06809d260a3e5f676e2316ab31df76b55d641610ff61c97b823ed61e45fb25b89ad18d96d9168c1be55674a901cd7174e0498f1a1959721c12bf32c59a6d1b0b7acc96e006f714aaf451669379fb9637e82057cb1e8ba710c43ed117efddc17d4b67ecbec195561f8d195d0b51a654142b29848324853cc777a8fc1a561382e3e232004670bb3cd56762ae8341344618d4d47208f9ae5cc52718b08f889da9a97e06a80f700af8dcf73618428cb7379e7c5b5052b59918b751a4f32224c61a66047a9d609ef5cf7252744b2127b7b0dcd7f4d8d13419201b06df235b024967dc87a922b4aac44fd1ef94ca98805203aced89ba43710323d67c2ffd03dc483c224fe43dbadfcce5aea24ad2bcede482b31e9ab7a13b324162d56635f5aebac9329954e1d1636841199d58b8b0842b667111739611d939f35196b8329c0015252758e78be56e19c3f1d6e7b73d4973a07edee1c8f31d9f1d20c23403b2a69d1afbc501a68c5d04a537f5b6f7f7d8170edf8dfa4ea72f16891514b5d8cbb41345bf16564761d5c89ee29274db5a9a08350f6a2952bd1a9f8631b85a59ce5d056e32625c6c658d19dbc973c83bf165489269f42f7c502bd3c52ebad09cd44b8ab5e1762a868c442724e615c2e6aabeaa3e46c1ab7e6104a9f8312b55e914184f51eface583add07f9da6228d3b514c31ddbaaca1e2c5399ea3000df3342ae2ce01dfebf17217421e038494c4b3f8a52cb91b36c77299fa336043a349a2a5f5d4896a244afe4726e94f2f1cd02e89a8cc400eca22979c08e8fbee7aa0ead455ad470eec33fc85211e56fca0e18f50dbb16823a543c0268c73bdc87d7a13ad4465e7599845368875b45e64e77f9ac29910108941c4df04224bee7e0af26808da05b6f30a0d558ff366302d5b9441918617386790e390d43dd8972b5025c6c003aea8906482493b7eb6987e1c89e66b4cf8d66a6432f225636563d47896aa06320b538703bd7233d2d1529cbe7c7ee2dd4423412b1455299adba9e05be87f440f5e14012d7e44c0a70f243ea516758995fb4641dad229c4b2df55f4ed2569830fb84e55ddc040e7687690240734c5bc05981b9d3ac122aa490e35b08dcf2cb5ca0f9c3affeeca04cbdbedc41302e1f76e456709531f609d1747f497efa36735819d20bb73efffac7e9eff52b0b59e4d3378f92827dfd5044f3097f927bcc27172a13e535873bc7ee971f36e2991b3e5285ca6d09c9b166b6b2383a7107018ab6b541699593207c7925a0c5050de446612391dac6b634082cad3711cadd66844153421af90ca6ec7a352020316d835c08861f77717622817ab926a6137490c2df60c003e46831b23ed3539f988fd966b029475e30ccfdedaa30e92e76fdaf4b1ae25e58bc6623d7b0929aaeac3d7481d9575a46aeda762796905a64186caeb0bd257d996e9fe03c602b168926d04f5b3968ef45f001c25311619ac1114e23fd8b1f382abdc0b49754ab15076aff23f4159bd0fe8d8546f24b812f542c1fab63a0b775563536c8fab6282a5450a59ba64521d033718dbbd107e388c2a55fd0e4711900eb7ffb18f10f7f17ec0bc10d484c1f998edbfa3218494f575857949868393238dd66bb50341c84409381a5c5858a3090a9a5e3b58533aaaedaec495607d3fc465dbb027d826ee187dc2d706a0e058902d099e6bc132daee3c5c5e197d20835cc6942e849f9fd7a20cb3c51f707a9caf9af5c11c19bb3a83385cc47d78e1191d231f8ab3d1d9e783c7a17147e14f2e8cafd98a4cf8f1f9ffe7b1de3e8fd2385fe6e16743bd53565ba155d8b4f40320b9581ece0b38edf24bdebc6ba75c1fcf395c07a846cdd367357e89fe81b277ba6c89bf63ea5ca94e4784b72e43615990f0c0d770aad51c735ee622927c9ac3672eb2a61cb3aceea79f601296b9ee528c6354265d92cf2e03311e97acfc850799408287c1e6ea83720320c2a0e42edff504fcd6828da11b30e5fcaa543794f068cbf16518f78073053f5056c69896d91d70b7f471f180938f2178d0d091153276065fb61fe168fec0a4768f2e52ec14dc5c6281026d6bca4897d4a04f074a38b2710160171b4d2dd41e0245ef7da941f11e1c2d79d6ca006d018b395bfc52b92cb4473910a29ce103749da71e5f1feb322497717a0000ecda2e68e412d0ee340f70aab402bc7fabf24c05ecfed15496fd39a6b658b6f098bce75a34662085a725f364e6fc72b38368ca87693e2ee25a06cab2f13c10633ad73b4610a0edb78bab3efc06f38b29d3540aa3f72bc90efa1e3bf52e0bcd928af40844e9cc78b7df1e7d01f822e608089c4007f1578090ec3b3c1e9b25b89c2b07d48d6c7c7fdb0dc4b3899a32c7456fd13a8082b44dd67413911bf6bc98e8502520b8b90edadeca68de6641375b4cb4535fa9283490fc18de9d4df6e03e9b66b13103cc9ba7d5f3536e4e8814b5d70c2f01c7fb69161128cd4aff928ec7a4ab49e6bab1c7fe7927dc7adda2a08f0d1c37391d73346781c2aefa346499ea2e5d2ede2108fc44a03a8c38166dd0c4a95162db39cd9b834c7d804c4d3ab9eb3635502deab599d5bfb95d72b59c90614a5b0d74eaf9cdfd00982b5aeaeba9ccee837140dac541f5d0f80381e1a6a9730c4f8f7e8b592c539f96e73b7516b998db9b6940b65d5a0f0221eda939af58f93e7d77a24b06c49ff53589f8342ac16f6fc94ff29dfb4a585afee9ea933122e8981223bf3efd4af410088ffca42ee334992d706f527a8d8f9989af067e5a9a2cb1dcf0bea18232260654431dd51726d2483c29add94c217d05bb8f8df072f3290689d49212f3cea2fbc337c2c0a9812ee72cfb1d7acb55f9c35d61e5443e6aff8c447252f4aa7a1c1a61c06ecdca76b8edf64881827726e0601e59b5fb844b99f7ada63a13627730e93ec90310a41ee5c744b0c1870a1844a91155f0f08c4e160f9e8ba0a0decef6d6ced168987d707bfbb50e03073924d44e4238f11b1b9002bcf11b746dce92af119f877e66bce32ede73a19bab999413994040d76bc561c893cec7801149659b8ef301d6256931b573ebcb4b5ce0df690b72f8c242a9586967dfecfd553b34ac214c3dae889f4ff465a916d8849cb108a4dc1bb46d2e4753dc85642762e9d4283287ba83db9657a2e62004f4b0d620a89cb1a8784b54e34aad099ebc1ad7da627aba9597158639a5bfc526e68674a278d7b0587598890192478083dc5015f28582c8c423d6b7df57e2c03e92f99a19231ca14db9243bf11a8bab0a0e52b3bd1d4669e67461f34c00dfd0c48617bd01416c725384c3ab741c7f9c3b6da7fd521d4298563ef8353d0496a901838de4541b4559cbd589b083a13a5fb626ea4c8c042f89535ce839ab143ec88a50e3db04b581c7278f551a8a0c88f2dcc588482abcca00ab84d498b8fe29b401bbbbfa42a4cc226c1723dd88090ae5b22ba8917b5a060f2238fb5182ee2d41fd51783b1ce5e08f16626f5150a104e8d63eb005c41916c3b78ff58b8c4eba0dbf7ccec9aa6d2ccc3187a3a3c3e57243b0440e80824362fcfce297ae40bf0dffc84b19e9a44a14ae41cb024b9744ab18ede9e9e2cac6836fdb89bfc895a99275fac83c1baa87f0efc48166f755a68405144199f467d9e57b559937a996ae5a5f18f8295ee7db7a98a459850aeed35e90a245a0c6575de32d8eb0a2a7de98741e77e219d80119beefd82086712390ec8a2a37e78aa88704aa86a250c2f91d85cfafa9debbb80f82eb54509b6bbbe00e6225374e4a47fb5554c833ca88ba7eec656e9a5a403f7c0843687d500d2e66659f5ac6e26a922e047d0d0c9e25d47b4e48879a0caef173b279536eeea391eaacc1c095d2bce4f5e1a700839dd29cff2c1a158d13d28883594972f14f4bc943dbf18c1486a472445615dbf93f665fd7422e5b5382f6b9d148d0608df5f9209313c53139c979a50c6ad89841cf08101b2c6a84b18fab12f08fbfc2ca794c2c51817d67f36a01b56cb36ad8911d2fe033cc1359926ab888be8b04e6456ecd63805422364c752783210f6ea0dbfbf1607c35e9d40945be8d54d4d7fc90e7571e20cc7f8bda3b6547dd170a3e917606cea48db513251ec0129194d1342637a7ba2387ed867bfe195e002436a2f8f93fd2e79ed9eec513d79d2a185daa39101982a301e09c7c1c225ac2a62d9a3fb42db993f9a3f774e321c6843008dd08f367d3ee56f0543be85e04438800b0a64513803b6ab4a312663f544386e738e6f443e3fc04d0a4f81269f1ce79121e9f9466cad5c5eb475f26834f72cf718638528c3e086107f32ae58b8e29b3c9b8583796e9c240c0faf9435b770d89b007d2397b2746a1d9851f1b9c9b59efeb06afb1620d64dd67687b5745721b673c94adad2a4404c602bddbb2d23658d969318f449527b0f128cac9931845bfc300420011f792e93535fd3ea2126caf2c6b864cbb6bab4d30214e246464078127706dea43bd1db23d6563ced164200c029ecdd9e3330ef7d76092a4c2806c348170dcfdc08625bcb20cdcc4ad32ad2d289bf6af7d119037f605b5afbf130a6092e072dce1beb864023a9f79c5a3a5f98dab3a65515609b404d5ced7bc1375aa94ca605f68846ac4ac88bb194d6f6da7aef92c63733af3c43c30810409578e3d8e22d31d0b06283d6924c9bb4901042a939610c44bd3feb72862b816866ad20a54de52c01758813ca8b6ac36be0c97708225c799a837cb3cccc967080bfaed5163cf3d8382d0a7c5f482bfbcdf0a4ea2513670bc39a5e00fdf19370dc6542f5c3226d04b4d2c0c8a65bae7c481bc49ac926aed78aac0af2cb110aece11b8f2b6fb6cad716f50441029f1ffddefdce253c4708d22604a82b2f5016a63271cd02e506c7ee1c80cb4ced4e47f6383fa6eee82aa0472af9552e176672d5592362440923fda58a8509a950cad878e023c075f585dc3552eaa4af92367ec4659bf5cfd8e6afd04c17df69629aaf19d1855479b5f1d7c722602babecbaa9186e8895c4115b8238bfab23413046e63be7970c971d902fb5ca2ff4dec4a236343bea3abf1845c1b469d73a1582d164635cfcf2a04ec1e215e89b940fca1cbafddc50fca43805600a8e086e85195800ed609459d79690f4a0514c53c0f32c1c1b08222a1950c04972abeed430e0f17eeff06544212756bf8d66c5dac489283fc60846329da5412f28a0a83d30ce860d16f201887c0af84de974d009e0018964aff08d4a9fb14785d023bc804899568df516bfa449263b931b92fb3cd3a4f2708fdd851783d1362ca45ff38a0cded50a959292da82accb2164b0ba6e0d6ae606d2109b53f666359995afa62923bb4c3254b0bef114505603218e9a56528fef28461b3483249d1e9be49f2445b7a3b878eb4cfb1036ffc01f57ad9908c34da333896b780d4f748046462c192b269501842175b0e67d9d29481ba563f82c6b56f3421c637bce9bb65f7017c4cbba2ee7d9616657655095c1319e295d1e00c640eb26f2f431e1ae4587a4343a79b870d4528c8e0f69595a624599e938458ba643a20681980ab6970b017ac0d7efb1b5e83f1b1d1b063926abd58deca36d3565288acc2d4088b8e13896da1e46350c39c5e761abfc4703271e668c1059894bdff08f0614aa4772d9f72e40ca6e04fc34193503ef1c082e75c79095e9839c18185ff4a1551f946cd70456766db1d0a4f39e59c52f62cdbce32ed8a8d3dcc41fbdf155471ab636e474c6eed245703acd50824f44e0d6295915c1412371d0dd2536287265c93a71b58b937d47b6a8671bc106c95d7aab570c244ccba305e40c4d55127dbd9c5a9ba9447fb46c313266191a701c076b04f76ec98206554d22c4669aa6c85c0c762d20bbe3f6a9f1ca611594438f7895240610c58f75aad70cd75470f9a007a30d8b5a66b6cd5bcf836809b187d1ae86248f569bc749900106cc2baf43949fc306c496b9a6a34ec5baddea773dcb7be6e2f04e0e8839f302abd884c807aa696edf973eeaf905e67d6d1ec80e4026696ed140ae4713a22c64b59ea37cbbfe1b35c3ca0e35f07a2990236b360f30ad7e84b23b55ba60505cfdf008b59d44305570b0a6c4b61ed2fb085c6be5482d0a77ff94280fec16f228d04adeeffec810cebc7be2a3943f40c67fd14b0b35c4a3764e1b639249720dd7827cdb22a04e235c9299802b7cfea5b7e9b07a554046400639ac884ba9f7ca5c0315daeae4ad29a4a780f5bd9929780f771215b229624002975ab68241f120a3611a03619244c0cf5d911a6e70449cb23930c93c99c03bb04c229418c1d2547720e3b3f5f11a0e84f0f51fba5af4768708271f0d432dcd8c1f808bb517fca85b7eb18b4ed187e5fe7dfb29277cdf1e9b9278c766e79590745e3c9648a4a94b9f5111b59ff4aad2266bd4dcbb7515195fab0ba485c4b62bd46873652e4fe998bc4fe4ac1a50d4b0eed44010b2540927b78d2ef5310655f3234c947811482fd9c0c6cf8d57d82494189b66697dc8798404ea234f787ffd54f04af4e555e138802ff0328b4c181612b4d80208dbfe6fd60659c42b0cf8af21dfba0e02b4ae205ceb8d55ba19c43d801e50abce08d4456940b0b515ad01552167dd7149183d6d7fcc918d5c22d5a993660a5d87f30da0149d4d0b147d6e525d843a242acd3368170c7568ac5c4dc2c83b5623bc4197daa2e0a13c07552705d4dc3717ea70adad8004b6c51d8766d22d61599fb693ace46d0d7edaf0a79f426834864b8fc06c7faf4e63cff3dfc316a740bde3001f18212b212881d40245ce78e0ec4fbc0ff945338c6d62800ae1449d7404da370037535090fb2dc3507c93e817a9f9b5dc5ba646915052059b0130c385531acd4a53d6ef234cad7165e4983c14920a6b1dd439844c3e49a52035ee80c3f81caf89952fc057db6bc1bff7c84b99672ba5292ae0930bb2ffacb8c8c6eaacceb0de43af7b79575dc5e36931cf83f68165942fb9de822b40f64fb78069f6d9c4665b39d6c5a356dea6c7f23c5f638119327621b2f5921f5a38475344a68af2ac4c0f4c06f1dac3127766da05c31da5ccbcf18b6cf3ecd417178c808cce370407c25219795f8fd540c9cc89977073c9fc4d8638d7f1b18a056b42ba00da1a4ec79674148924d98e4d9dfd73e5260f663507af88dbd72c9b713ed17a919c655c4fca9b71693899e4d54dd0d7fda5dee217f563d1dcec94593801d3a5284a4968d5cda17684dd1ef507c10caf1ca437aceb7d0af69e89afc4bab4a19d5a3cb79d2061cea20e49495ff6ff4f5b6da38298de4dbcf9188b581a927935ae7da7395a63d717c8ac5eb18f1f5915e6dcfb781c4092d23fa99817d889bedeb5190597d71c448f9b70346d64334d17cae44d9fd1cd6abd6a1d1d331ac63e837550d40ae851ee34b314dca48f9de4d3d458cd0c7b422494006ca41798f11c6fefca4879067a76c39bcf38af2791c92ea1e810d041f517ea35690b4c32cccfa2ab67cad629c718014d15d0e79764f1e06fbbee7388d92db5c918955692f2e4e6ce4802bbc02cbc6016b35dabd0b3d7c5e6c50973edb775a83f9f51cc05e6674d572f0bdab7dc19438ace4fcda2c8787ed7cc0094b21c5127c6ed177f6364cdd5e92076b7d9b7f9a4859ac965fd2e454bdb77ab009e205a2b8cf0ec12c3cc6800e2745c3cd5d021e34bfb6cced466231f4446b3b0843de787a01c1c04d7b7380246d95a9d82bd2cae6d967b9edd23a9f8e6a2a61f96d5e277650b2057ad66aa25138cc5e91266e3be80733d781805527e225d91b044f2b3a1cecfabb3ae8e095f8b1eafb2ad148b8c6d410701f6c4dffecb8f431a49e70f337d1dd584b9a6095069ea7d87e14402c5483ccb79c815860349a6fc9eeb639cfd52fe2fcd5808d066a66c6300322d74e199fa8c014c78b5424abc5949dcb81c7e5cae3d6dd813a45c4582bd5bfb8450b03ae76ee5ea2ff5a1a3db944a2d5c638414ed72eec1656b46e2167f77dc9761e4243021bc45f7990b953ad35eda2538395ed7870f350cef1ba503aba18f172dde2d46de2343e22de42d3ea1c2ce175a73bd67f36c108ad619e7fb06bd27262e320e95d21774dc1b8cb65355f3db34c36bbd2f596b1e4334be8265e6d27e6667a8e163336ffaacfec3473d3bbedfd5b8b964210594fd36304cd97cb09daf43e20eaeebe79f08829e0700e685a3f98078c98e302a0f5fdb7d8d3a1ab30521ad8f42dfd27c7f9974f55f2978efe8f1124b194eda34f5fc79a825b8868e3796bf411bfbea58b0afdb042941457f3f321dc53128a36b89dbb2dd85bb9f96faa21961a3448033f29b2ee181443999cc30fea01d5e24ccfd0c5463eb8f51e9a1a8983a30093ba61393e1e8aa5287c92009f1a7058b09acdddee731e7a6c4861afc55c7091f2aa5c8ef698fa39eb79e0af6ba2eabbc5df753a0b36cbdfe42d955a3143cd93271cb8f012652c43e54775a31cc3e3c9b60fe8e495c395937bdfb8422a340a3071b8245a12c4f2480edc87ea8fc93101fed600f825a4314fa0482adf0eacdb07a029c1f2f07de4c4fd3a5c0107a6631078867a3a812c272cd2b97addd7c3b4c0c7c19e13396721783c4f9272ea714669dee5c51d246dee723fecfff678deb7c37756c4538a2b1945250e67c6c117e75d0a619163be94891e7f13ea29b7fad1edb8988bb19ca2af144ac13acdabff82d07dd1a6d72f527d0c1c988f3ed1bf0fa1cdde0bdec42b838035e659a8b2c53e1231ceb843235bfb1d7eb52dac6a6af0dd4feda255bdfbe90a80d5b24ab259f358e4f1fdd42ce99778784529ce1088e9ddb178ac67345e4c80bcce2120715a1ab4e17c0790abaa4b395439b5153ec5ad3fa4018bca598f242c5200df5c3d4ca72f72f4236e70b797355843533b291a40c63ad423ed5c16838c586142cefeaf2f4073fd06fd1198db10d4e327a9454a04fdb0afbd25dbde2d15179920e06664e6986a9551e6d0e750231c91f81065aa5cc56f313373c110c7f022e05a39b2b021b6f59d28733088dc715c31a410483dc68f022e9286016b05ca4a306182b829f5fa3140aaadf5160359b14eae2daaf5d9e0e55d52a97313d46c0ab641de352f092cbacdd880d85e0ee3be096076fa84c23cf3280fe1d4406da0bf9f233e1ea908944b8bd1964c5f8f0f765ac4c0fe9a9e0dbbbe42a6f8fb8b4b9e423fbf56393256669012a4dc005c89325a5ee52f14f98402e22b5bb2ff04692007853f3f3a376c7cf9c23c987b5e443c2d488a05c8295d0f0f84c6ca7360d3f22f3632f0c30377167e8d75b10c515fc21ad07824e0ef2b53971f73edaf1653c5ccca861cc44cae8354b3e92b4d99bbf7e590ab7a982dbfe883b872b2bf4c72841209f0ca3895360cdc5e7e484738d7e4b6886e227c1e2e2f1526d583117d7c654a75688b0c0f1310b6cf6f2c7dd49dc3a84d0010f2b83839852265f1fff3c307404f18b5926493b6de12288b1e703cc5f7f5ea06ef1dd2011202c80ffcea302c6758de23336d03c5d25f3df577790a4de49efd09472ba626a60573fd3320afc9893dbb35cbeb08014003cc61bdff6bb1d5c46590164c2c7a0b82f1707b8f42789a6e9b96640fff53f71c62840ddc5eb34aa4d3f3ffd8b9cc782a993dc2fff8b636b75ee60087bfec016c72cba50c76d30f9da80d8638f029d4db221e00d40a908fb7ca99f32c671387cd3448972d84a69459dd1a17f1b40e5512b04e46cc28b5c8156ad54c13720b0faeb614a97e08bb4a78039ea027bfccff6db0637cdd6e0ec832423ca170ad3deb57ffe3343c34f069a5349fd8eb8de975a56587044472f49b370550fff00b16e9fe39ce7e0afb4b8c0e43e234a4db043c88d04284e5228cca274ff92236ffe0f68ee67be398a65dc407da34fd518f807840732e79b881a06fda39874e17964b0f4742c2714e3e70d92055827725bbc7942d9dc4828d3227c7735267230ed24fbad8e9883907a1e336963125a2366390709741dc89e9448fdd2145be4e6d34a4d1d91470dac20b4eb76b970ca456f8572fc4633fc2598247b792687b1c7b331a3b4461f0012e4e9c9ee9b04dc587dc8371141a655998648ff89e91ea9264bba62c8e825489d9c7b6913443b8b2141bea76f451f489d05cd8241023b948b112a703a173fd65c849ff96ad41837bd93a18a59dd35fee4e6f87a9e5aaf775041f5da2d8112a138c0cb8594454a45ca083886b4e310d0137d663668d80b30a9ecaadb345d561049d459bddf0ae98bcd165f3d1ec3fda3e856e095d7d0800feb329b39f95c9fe291876825792a3203f4cdc3addca09a724f687018a24e104c3dce0bf414d27c0bb2d14bfc1f5860f3bb1d1b84869e0c8949b867e390207d96b9506fdbd486deefac20a0b1fc459983bc0e0b98aee13da113a36ad2fdc654a2f9fd8128409c00ca213bd405f60f97c85f4332738288791db25ca328b4e7b3adb7c0fef638f5cb4b1b31e94e47b9ab1e2e9a13753e1ac41f849edc5777d3e79e911225d79a952d93ac11bc52b67451cd6b87cd75c58e5ec3d2c1790d1637276914bda8b547057b6f09f9056cff04dfd118043bae7b50b13af90a8cf86475d6391ca5b22b0a23d769a35ade080c36174db8df670d0b7555619623dba5f03abe959f440ef0fe46f5c3b243c71d9f7e8a50aea522d08a3a89548150983254d893e3c26c8258f62008eac8e3187c0db5942ae141e62412aec3771cd63e1fa224b42a59847c6e7484ed31b26a1fd8381cc0ac1b86e6873ad8ddff7a50b08836baee156592c2e418a7c6b3294d246683b645f046163430e170d4d989fae7861782e9e85a4a8d677f8bbe23ffc486cbe8efe00a4832bda64d452a3000311df6aaa591268dd750376ae9ae734810613de6931414f2c3d202c82bec35f761dbf0588b62177647db07a0e9f9643862cc51e6403f02914657a27f457d717f19afa00bf6084b6b34731f5b9964ea7cea07d5fc5a85c12a00210272f19b38803a94e9a412f7de54621888be887f213fc7d9c336a07c5cb0bdc4dd73508109047147332fdf6ac19d17d0afb263dea52bf6a651a5a55ca912ffb69c71e36c05dc2484dc2aac4d0f848e18ee5718edeb123600c02b8df0614883dd215e67070e83b980571522882f83a1e9ce8e828ab2bfe34f42558e6a9817745974927ae5333d244821f67d9df9c95495ea16745e861e5f70b72027b74e25f0d125871b44f3463eb9a068bcef7cc2e5516bb9f3328e897121b563e302d606248b579f57b2deaa605316584c1ee99e24c5a257a590124cbde59c00253c501317b40c2afce813c59075b831ad1e47fd01ce1b9ea012264450f8d083cb43541c6b772bb254f0ea63ece3168b9a27ae823339c97bf53bcc40755ef1ad035c9046fc2f43d0730498fc4f82c511b831d1f6f671ffbd0f60aed8d1eda8b0a6c318dd5006684b14fe4a9b0d4289fb208146c66bcfa93837cbdc17268465e11b3bc4ec000eba5b06edffda7bb5b530b8f076b6de88621c368be445efc6934f7da18263e4945fff49415072fdc2432368dc42cd6fde6a9dbc6999eb59bea5f2bc2c1a311322b57dbbc7d66815760a6245b0a8801501fc46d4c86204072ea71310dc2170ece85a1a4e93fdb5f3279ba9ef0d8b214e89733782b3073cd170ba670e47d832f8d44d8cc452cbaaeda04e4bc0c8a5243f98713b02cac0047c88d02d7be6a9e661c47367b2d984668ed2631e89d848a37ff8f8b7466fca64fc614be9c9da3cbc16fce7138d531ba6d1cbc5f82cc7fb4988c4693f6fad48d115a621fa5a1386c3a158c28c25a8ef9dc7afe78faa7a3563650455c591a0d943da8b1854a3284fb24f48f8520da6e6ce0a50ee7b2deced0701ec88adfcbced1801c4f10b605819f80522e20b15084141fbfca155e9687368b7bac253f77f83bbea5c3a0a03ac22aca32e7dfaa5bc33b3ddd928ab92af639675012a1f14c7ac8e4370cde2f184e1ede5465f7e7e567f3ba9313ea13e3b371c0ab55900b58463fa2b623f2c2080b8bd6f0f040552f2a3177953cf405d8d64f1cd6f260cd10bbb1601cf91239eef86da322d064eaba184c4ff115858062197f40b1b91bddc8db374aadd3248036e6107e12d416d5b45e4e0a87f5e7366446c84d16451d93f985e9dc5dbc64394b513bf93b560abd74eb79bac566cdfd7fb0fefe8da81f9fcb266991ede82bcc349e8bf7302175f6abc5a2416043252e160e04c232deba30f24f5b9cec79f61801c199db899468416c198e9adcb709034392d70076c5eaf585cfc1f102ca014081a18ae0a5e32bc6acca708a6c160c57acc3aa20169e2bf91ea91c10aa8a2a58b2d498c69fb5ee647cba92832fa4473bdb495e17f6bf2cdc42a82b2e2bc0f7aa8884b16f147021f23b06554a56a21f9b0d9f235bffb1a1a9edb2e3b012cc3777409541acf5b275561741ece824ac7d026f0c39ff21e284ecaadafa04c34bc72ec88c8082cc287295d9c217ef957de0e1bd90401db8dc3b7361777cd144deb06d680236367a2b055381d645d6330af6005ce078240fe72b7ca0c303286c50f68ac2a419106b6d863a9b97f5cd88fc70aad3b8cb8cad39af5f736e88bd98415ebd8574e562f1c614eacf50215e1c82a930f3e2e82df5a21da4bd7120bd320b8f2eb6cc6d542e995e34d900988f5a94954c33bbca43fb6ade09cc973c836d9230ee66a2c0b4831ad630f49691ee0a1cb1465ec1ae046407f225fa016553e46bfdc4ccf459105440ee49e7da5db84014e2ca337069a8998de3b3cc25aba6db94dac1530fd577f6846e09e4e1f5c9914d6b0c78bf7ff28e9881437cc0e00ac98277405a7e8066963a29156f6a41d2d6a9630c903da922c9d720ff8fba2adf52e117f595cc200ae9ef0e7b51966af7e78c5ca656c549b6d617b8ed4aa9127f7ae7d80630f5536ac75ad2b56300dc9e1bf14a71d6b7850c312c702bfae60d0a6d50f76e07a9e3f6347506f636b13d271468b01ac1605de2217f5f0beb2f2e85c26ab56e6a91a108275b0bf844c958b0af0222ae7ecac2b96ea4f56f9ae0aee3b7845a7dd181ee81575fc1f440eebe579d10078131b2379103566478b904b5f28680a5fc9f3684f84bd7eb99967a9f2278595f330bbc41d10f7088c540d0d19a93b65aabbee34b26f2d6002aa1dd673837adbca65866675b2a125a352fb68384298c38e825f986f052db5bfbd1216aa11855509527d20411442b376f6424aa518d081a9371b6cd032718b22a6f0cc6e9667db39525e5a671575a706e6e5e644bf8b3b158ce06bdd9593f11a0f1d641109c25c9c0ab03b9349633aa57318914c4702a53223eef1923db68cb1c5020d905c01d0dcd38433006ad5255116a3dc5aa7e084d8ab10d1f270d1c786724d6c8760784d109314249181fdeef836d59c79ae748aeb2fadf41ae298d92d0ddba53f1f1f1ff30ea125361a23578ad4b937caa11a384fafa1c4978c19b71ff2f3b609810971bc6be60c0fb31acc3ef21cde2b85fd21aff4958e738b981883b7f30dc7ce85aff73e4ba80d4759108954456b8601d3c93c4010b24ece63159ea22bb69f02b60db70af792a2fa220b1035cc745c6018a5e967edc1bcf2e9a2ead4ce3077810b6f5c2847b43b85511e3ef8dc4dcd31a6158fa8ab3ec035d8bd4f134eeab0bbe1fd52c5c141e06577af66599783ab038af045885f2aa6da28f48408f667f8ea5b59587b5c475a1b1e55262b217157993e50549d4927766119e674a733cbe8caba446bda7cef55a0364a41e1aaa69f965c139b829dc7427e62d36873d7e19081ee1ca699422c92bdf8288e340b8ca33a9a35c58a92263d3b5070c62ba0b6313ba837040f4639a22d34982149ed38fa1204117cc7af4c959e0650dbb161f97ec704463b16a1589a08c096da40464431affdb0d6c0ab84e554db7aea6cbc316ac951f79a8c9459daadaff17f543c9c4859d36334b7518c52e9e06dee6723a41f96840d2386d329eeb316709c1d95caddf882b8cfebf27ee199854b124e1adfcd81b3c1e5f4ccbfe23430cbf72b49bb05c7879ed1aeb2acb1bc7325b900938318c7104d8dd2057eb604426907a637ed7e19c6d7336d83d112b83f3aa5fc6ff0984ad06cc6b576f6ec62c38f37f7b5080ece2c62d3025205eec7ef77c2f5c618669f1e5bf20d792365e6c44d3983f83128179a89c7cce1f7f9add93c736c36eefabbd2f501d1d423199663227edc8b90efa5256ad8c8206dae862489644544aae2b5342f9a2f4b325477667b2fef471db6c1144b15fb04e1f41a87a43f14ba69515b4cab35d5ba326fe8ee6917f867b9bd4e50a8f2230b06c8bba75dfac5785815c846c533e0983e997aac1dc83ff73577f4740df9ca1fcc3bed13b611da3ed055b25b12780f32cbe708a56a79952bc6e3d23c99312246b90d38d3e3c6c2e3c55ff2f04ea1303ae45b3617988af9ecfa86c6808f1b6bdba20c4cfda71e6430c58fb6c0e9c82cc0d0a9a2e63bf8e93eece12a943eb702e748081e9b4d2e37fb920ccbfa6dde536d9f6dee7f7abf2c6c13b0effcd1dd4990a52cc3cc6687ca703675dd1cc074b308e7fa60d57076e56383d5e5f02e1bfe29592fa6ae0e8c90ffbd94e8757f89cbdb196d1502cc598368cba4989beb574a8ac3022354c30a9fcd6a560c9e53c4975fd7757d74334459df724a84d6706975a56094de3e9d39090389951b903fc16fe19e6f8c80716f7b9137f6911e40b5540f57c27a47c68f40475000231e8c613fbde599d0a08d9276c2f58fcb1df2f111428af03f2e9d7d1c942c4fa58c67eae69bd5e51400b097d544b6be51e8c28c66048315e3d5cdf79aebebdca54118d5f32f5e024144027a63c6f162cf51527d130c5a068c8a213318b7f40776de479df22dba62d764b5b57b88c7b864a62d088466307de16f7e2d36751ebc1e2c4d046d2d1b6fb46f14c592589eb6fc5ed105b73692674121b979988efaa673f93b32dfb4641ad085da16e1e3a2cd9a9abf9678d3c6db4ac74c51132a0bb1d858bd9f247c016bf870a2dcb824231408875fda4ef8fa2c52114c7ee586e213834540e622014d6acb682ffcc2cccbfd13571f1266051578669dbd9622319359d66d27f14042feec254952d3549d8b027bad35780315b0657360aa18ef18c10917a0a9937d276cee2e8a4495b1c2d7c6c3ac8a06a69f31324b026b0295fdb3474d5b3c1b606eb62d851a1e2d84fc3576f99a300016aa356b11baba1e0dfec87eb544743ba15c92ed5c3565706ca8b521e5ba8631070dc1d1d63b5307fd2e871acc1268f0da20719202ff0f99f7f0f53aff74b773676415e9b1455e749d291e126bd032798675fd4725a2cb877d0e83d9840c74cc6e93d4c0011ca01a4100e396ad83ad8ba3bf80d3e3a93b4048edd829678d3bf9060af91c49c06c5c92d5d4827b310cc21d75817f02cd8bf4515155f90812d30ee24f69743314677574515b5c0463fd3a49aadec3d61be3067e41755a76cfa96e44d4dce5990ec80cb998ace89e5c8c728af14cbbcad72ef6559e0d65100e879db9934b71c4856f53a49b490509c2984f8612793afe9f285d17338e80a47ce8392239a1c451025f70ff2390538b89a5c1fade36f54e28cb17391294919f780f945fd047b639787c588ff4e9913e77348fd09e8ffcdad96fc16f52f72967b50d2de81ef3037455bec94e6e6f36809804b7306776c639daea9a85fe120c60eb6019526d3493490f65d5c9f4d7260b6dff838b311e1240f849c44e40a8a082a86843461a033df32f2c5f10c13c642b22563f15e83696f71c875294cf5706c5fb10fc7feb9ac57a4bb033ae537d35b204c4a9b0e6569c85a1e4ddc8d48371519ad438a7623eaa621a57934518f2d867b65d6b2582daee8f78be9db3a1076d945cef90d7edc816074cd461f4a71171da3536e38517aa1a7c1bd18433e0d86f82da4eddd6defbdb79452ca5d0c400d950d3e6f5e740975cdbaf8fce0bafc74d1527e6a3d2009cdaad4805cec0431010a42e2484bebc5dc1bc82648c8c802f16f563d929511f2372dbabab4173f59e082b69c28d2dfb188b60555c1e566f563f5fb665d50fe7513f250516bee2322fa4d73376bb0ad185a31b482546188288bad5f42d1b7055b39ad9e18824b4b1f134254c2ef9ba5e2eb0df3d7c777909f766f3368eb9b762b2202cdca2ab38ab0ef25dc8bbffda6159d3d7c0ef45db384be2dc821118538dc02c2f466e122310516687ed3bcb7857a1f74a48505373f852a0e4cb418af5bb8f3ead14bcae8a814abffdc9b8a37472e4a513497545cc446bb40eaa97d282da791bedc219544bafa8f0a8b045ee1ca9aab9b46b33547ab2e54735724faebb5e63edf483aebdece68ebfb66fd75f24559ab8cfa0287c3e170385c08e6d09c035f0c94ecdae541b7d977c6212d0c43f4b34f038be70ebd04bdfcfc9d57443b507e46f78d0f511bdfb9bffd4380fb7dcf0cf913c1d26c212cf594481e99ad79953a532289efe7932c711903e641871ec2768ef7e03fb7ffa15faf52889a427a82c8311ebd3faf8e1f077f9fefc68f553cce589724974cebe1943c3ac4d9cf67b73b1e64d92b566a679633b618b63dc0284f3993415ba7953c3f7aca5717d1a77c89117f4acf522b83ac64a0d287082a673c08959a4a9e59115f10bd3ccd267b96a083e7be9f7d67ff9cc4e78be0f7dfb9af8a526646b495fa871565d00e43258d4b262ac310f2ad34a31495e411162579a445191b221a95f52af9ae9edd82f0b687ceeef80fbed6a3b21c23ba28431c2c5219b4c3952f26309e9030cca68a3fbbfd218005ff7bfc96f715c328b3123746592211934ad04ba6a0fcb47485f1028743ab63c4dde6aa458b8040de6bc3e826eef4bda86851c8b68635ac210882a07f3db0f2c4ad5fdcb4174317efed3b73e842cf81d0bfed1fa29846d31aac9ed1a334dabf3ec6f904b77b6f89b1115a6febad22997b6f0c245555adb5d65a6badb5561c36e24ad1d55e115b9ceb91bd169b35f2077ee1a7f1ecda9b74034caa3c2d74a9e88dfbefdaafe62f15fd7a908ab808a3903f10346b842888e2f88de398c7118fe3388e771ced388e63ddb99eb3d974d10f346b5cf3828bf3675e604bb3863541f333cd6c9a26bed634cdaac5518faf6bab6e6a7ad9d7eb553fd8ce318eaf175996668d7bab47d54d1396c1f0af63fd9a69dacb34807db234611606abb9b437566dbaea982607f60d017009dea233186d26c6625acbec27e1e0dc9a8c6adcb7d5235a53adc6fd593daaae5303415f746b2d3565b058f176496c9408ae180d92967034eecd4ffaa29a878746c3341a8d766d85c1768effd120968afefa6d7f8988e218414ae36a194e998473c63b7767c7eed411b64d2093e1e0e4e4cc664d3bb527043f112747cfececeaecd096684b3c97c77eb3593dd2d1d9d9e1e1b9d1683d3d311f1f5c9251ad4040f8da71c7a67b2abe9687a67b74896bad866bb55bb322cd6c227bcc1a3eb4ffb13f15f7f8d89f5bc340b9abc6fd1bf5a866f0d57b86e53d77c5caa01e55c7251989e8a8e26523cb7bae3022cb219308768b156916b2f29ebbac718d8c4a23f2351a1989a191ad3cb0d247cf4a5befcf4f2da82654ddab9baf4ef6ce1345672a542ccd48aad5665b3134444434a335dd6645452c74ac458b16b0162d5ab468f1657c5bd8da53339b6640b4594dac41b66c02f2201bbed1846c4dee33dab56fab3f43f1b5280ac27e0800fefdedc15f5488f4d3ac71ffdcfaaa5061eb07733f4fb309b5e605e58a3a7487ecd0a7c2244036c25d5bb1b862e887be2811d1ed5654c4a285db6824d228864848e08794f145b24848485506db3986cc26a2dbad286909572b8b92058b580bb3a9fc8c32bed6a88ab08de38988cc1a96bc990618c1772002dcad88450b23b3c6fd5a8f8ef0b5b51e552f329bc4ebf8a2b5bf7bf6778bb6d66a3d8b9f495cadb579cfc0d246ddba3316b4598dd455377f6b43f445599806b06f74a76817481e64fd30d1d769607bdab03a5ba4ea464797cce72e3fdf33a3d8baa88da02fba4b24dc8b180cb3a9c6b03144d8cef147f90343717c91a5098b69d9114ece4c678787d6e3f353030a12f21355615b3144742b62d1c2081f5d5b9182fea21b036f510838216bade5d72a02b25eb2d65a6beda006fe6c91acd7f879eb625d72496bd6c05fe33e07f64350ddf2e8d23d3615b0f315bd17a05ffebefce12f5f20d82ec1570ffa8a6e0c7c05edad6086596b6f40777e8ccb2aabf7265a74e7382f0dda7a07f9b768e27b4b6c7a366bd8372ff89c03fbe641149b4d1fca817d83f3f6e0e782dff96d8da4afb37b13af12ce68ae471fd8af393bd5bd4935b951301e3111b8d136b6641b94c1d1204bc4e0deec6bc6bd595ec525e5d86eb3269d7bab269947c7e78869dc11631c866088437c86a82649fbb9ce5967b9c4d556b0fcc8b004cdcf926399bd963d332c3d2f1d7fc57268268c06334d8f5d530d6e8b5b6f9af6edbe6f1bb797682ce1a01b67f418c750186aa2dfb967dc974ece399b5245659ef50a559e505010134035dfb8194035b4de22dd0d70aa8914ecace68c2ddc52122ee9b7d5cd48b28b8a9d1516b4d45bc344cd05f57a3d23c8a0a939a0ded501add15253a6f6e3fb9644d67ed07a8b2aaff2ec54af17c512d490277dd658121b9cd1cde877e525f9b03caa06c201d56a49fc18fdd45ba4857323029cb3ca8cb8fc86baed77e51971d9d8711a971c4bd1dc719db3feecac9ff3af276b8db465362f7839462f4fdb12aa9209355743894aa1d26bcd95636c24fdf3986fd14400e9a2d3b8d78afd604755c2f51642cdfda061b53acb542bd2ce327d20c7c37842af3527f3d07f646809f5196791b26b748bff64c2d09e2570389e1de7c3e39b66b348d849ecc383d65b647b18e7dcb5d7413e77199e3bf6dbeaac91cffe9df5a6b55366038c6edbe3711cc7711cc7711cc7711cc7711cc7711cc7711cc7711cc7711cc7711cb168e3e91609635ca3618c6bbfadce26264b831f1feffdfcf47a3e3ebd5e8f8bdeb6b7ad4e4bcd56199e1a0d9ce3a9b7c842ddf8aeba6050b3d2b470f4ad67b519361f1eda8f4ea3b550f125e70a638c4b1f2743d78e4d04947ebb91a10ce63c8ebd5a267be537b7dd7078ceb07e20c7c3ec558865e8be619045caf64ac7501ae65ddd5bc6bab7fbc4bb4f50ecd57dba4f3d3eeba9b7483c79f6ca066d2909c7d333e3e1e9e909122228485ba49186e65b346ef1a9b43133bad974199ccbb7a8ac5d6e64afd1b0d49beba191e4575a74973f3aad6ca1a2462dcabec31f259863cfa9b91ec75e2f2ecbb284f59c5697b33c5c9443d27765122b52e915e631c7a6024ab7569bc7ea62386e9964ae694c342c920df4deb255118d2d967bcb8e516ba54f5c6fd86967588bb065aaf8cace0fe44000d66e9178384723866627419a571a6aafb2e72b9cb33d5c943d6334db5e6d076d755769e39cf22e9fae5216af52f8f5b3e346cf5e9ef8142dd2e83430e8e1299e1de4dfb8f2756e1be563073fd06cfa5ce7b43f3bede79cf6abc539edcb4efbfab41fb36fda27edfb540bf47df4733c2e3a46cff1ac16892c1d9f1bc7934e0b63be5b6c15347e94b4efd88f5e6b8ec777ce8fa66fd88f5e5626dbb348d9471f3d8fe8b661f4a1eb30bbcf4e8719a5dd9b0ee7689edd2d137ef3e685bec763354773acb3e3aa84e3d8c39af3713c3a769f33ac45a3e39cb8de46979db51ef59ca387423f3a6d44f78def47c72602467474b4bcb78cbe3e6bbd6df2b1f39cb516edd863a791ddf62c935891b067a2c71e137aecb0d76374db3035ec34cf7db397e716c1dfc01f3a79ee325b2411c561fe7c7739d7fa3882acb67d9f1ef41ad9e2ac35fad14551747b6ea3174791c6c5dd72a45be748ab93d7e2961e74129be6a9c12d02fd737b7ebef556f7d662b3e9739cf4721ad79ed8bf93036c36c57cebaf9bf675fbd77dfb5ac5ebb1f3e7f398cf5c9f3f5976fe64f4670132375d03999b9edd4471c090510db4eba0384cd78e63e632cf91c2ce9f05c47ce63f1faa41cc673f1ac0dc74989b8e73fe7caee339d29cf3e7437f1690e338ae418ee3f8e738280e183e54031cd74171fc7cae7111e839521cc7f1f3a11d68a0e33bce809af2107111e833df4171e0d8f11d5407cd91e6788ed4ace9ec9673d614e7aca9ecaca93e6b1a3b73a4b033475a9e9bf6a0db8bde001d708623e8514435b8b70c666d34f69b331de499e8006e1148e3de40bf4ff4e719e4815e6d20ca81fb145ef108f1c918a9b3f7fa6ffd585b5d35650fd83155af32b82e8a9ad694b484f6f377edda2da0166107a32bc229cdf9bb5e6133837a84773cdd5bfd8dabbab75aedc5b0ab9b45a1abf7aca0abdf1834f62aa6f8695c5e0884d669eb4d28f5d04081ed495276eaa744516b1acd337abbb70e7a3d124dd7dfc09fef0d076c8dd52ef500e7a3862a1e47a8c0c3619db0814956ea81a52188375569b53136f8d6ad1367c04ca5624ef0822e253f304c9a22bc948c9d3ab4d15b8cb5d16f64527052d8eaa51e845ae59945ef4c3eb1a384def982fb6280fce0a578aac8d653ad328e2c09612bbde77dd29638ea1345cce8520f88f45b5bc29523572919dbc9b23fa4802295064037e429256f894f2bc5c30b4e2989afad382b398344554ac684c6e82dead03e2488524826ce2abdb310114ec24e113d85de59d64368081cb687901ca4521c1ec89670aaf1a4d27a29d8b7b7be6342ac4b6f114848ad0b171d78b5fc02480fbc948c65367a67120a718a29466ff127434d255195de1d9c46d8a564d797035010bd45924de53255a54befe39cb64c38ee94142698e8520f5e5fa052187e0c50cbbcf55ae62dd2972bba948c8950f4ce2fc6a0ed25a9ce48e19091e155e68c68268af04c96b18c11b90b9b714144e3842ce36334234378e6074e991e6740087110c93019cb44e19cf9209b717a911973660d4c099c32278468869067ce9489612433251cc28c4e2c53430e9a15341925f98c0d26192e386782d065b68466bc8848f81c09c9408166ce8867ae40abd10c980f4d8f98192be11929ca7811cd2891cfac5006cc2884c606bc4a395bf9e94ed5dedd81533d98dd39ded226c8d381bd114fe55de5e5a97a759fb62c2feb70efbbcabc7b05ea763c7c957b1f0fdcd9a99e6eeadb029f2ecf5e7d4fe0957dc2b96f07c3d75a6b73c618631fc771b4d6da529cfdadfd35fab3c7da9f81bc00202f0dfa96f8626c36651367f382ecd96cca68fdf087718da4c7582b3f59afb5166374976fad78f517e77bd1259eac38e75c7ece18573dce5586f59cdda2376c6dd7f51cd7f9b9680633c7f12eebf312b7c02f5f9f9d77ea52603de7bc5597878bacec94f9f8723287312dd3315849be463104a770ce56657dbc9d9f6497b481ac7b09976b0f43de7a046e4e5bc0ba8d9bab3ceb343078eb349d75c739a61d64e0e95062862dbad4faed955aaf5d5f4d5bbb1e0ce993c4a555a0db03afc2ed49a25eaf52be81ff7a154dd0d77116579cf5ac16c9bd5ea50c8a3324fafaa967444367bdb9c8af67507c95344f415f17aa79d1adde2c0279bd4a2dfc7a06c55709d3b3bf6e33a2d2d7836e462d5814d59bc572f47a958efc7a06c55709d33833da5fc72dbda0afd792968e8c5ab0a8378ba5f6f17a9590fc7a06c55709d338b31d9a8dda5f9f310021fafa0f8d0120b938326a516f164b3d7be1d73328be4a98c699edd07c6a37bebf8e410647f4f59e0364f00287e4e2c8a8de2c967a46e3f17a9562f8f50c8aaf12a671663b349f5a905fd7200241f4759a03221003c60b1c928ba37ab358ea19ad76865eaf12057e3d83e2ab84699cd90ecda716e4a8edf6d737d080127d7dc7021aa02029068c17382417f566b1d4335acdef39cbb93832526a41018ba4a2183718442f86702b906c36ee0ddfb837ace3811b39250a9262c0788143aa378ba59ed16a6ebb275801928ba325a35c0b2516141425dd6210c1187ab10267f37b5d04ba26fa7a8e57b3060e5d054b39250a9262c07881ab378ba59ed16a6ebbc9bc5e2526bf9e41f155c234ce6c87e6530b72d436746361e40297c4c3470cfa3a8e930f2619152ce59428488a01e345bd592cf58c5673dbcde89e00f0eb19145f254ce3cc76683eb520476d433716462e703092969ea060d0d7f5102800cc609251c1524e8982a41830eacd62a967b49adb6e4638b3bc27cceb55b2c0af67507c95308d33dba1f9d4821cb50ddd5818b9c0c148525a9a119544067d3db64b6241000030834946054b39250a9262d49bc552cf6835b7dd8c7049f77cbf9e41f155c234ce6c87e6530b72d436746361e4020723496949c68c1ba0d4d40afabad964ea6958100000cc609251c1524e8982a418305ee0905c1c19b5605174231a5a6143cb7b0ac0af67cb94e4d741cb14c3af8b9609865f7f59a6d4af9796e9855f8759a6f7ebda32e1fc3a8e65a2e1d7679609c9afef58260bfc3acd32b9f0eb3e9629007ebd66998efc7a906502805f77cb64e4d751cb34c3afdb2c530bbf3e649998fcfacd32b1f0eb2c2c930cbf6e64998afcba0bcb54815fc759a69b5f87619996fc7a926522f2eb4a9629e7d7972cd3905f97619994fcfa0ccbb4c2af07c03251e0d76958269ba5baa74f94be356c156aa708e04cc1bffeafbf4e03f6d72dc0f9eb01d8f9eb00f0f9eb3382fe3a13fad7650cfdf50a58fcf525177f3d07e3af2b29fd750ace6a91b68cbf9e74568b743dc6792dd2751867b648d75f9ca245ba8e3b4b8b741de9d416e9ba8b736691ae1f9d348b74dde8ac59a4eb2d4eb748d7599c368b74bde8bc59a4ebb7d3c8225d273a7116e9fad0996491aeaf38972cd275db39c3225d2a1aebecc53832988af346feebe8e9a7d01974029db5f3e7dc3376c5b2abd563a761e7397715f3d8774e9d7357358f7d76eecaf5d873308fc72e3bb7f5f1d8f5193bb78d7aecb073db248fdd3ccb735baab753bef268380c361001cee5d44ae15c4ef56b44f752e589e8ae57352bc420baadcee943b785da6574db295bf5d8df1a599135337ad3bee646b8ab847f30fa559ea59a5279336e8052533396664425594a5a7a824ac2253df62f3b68847bec9fd1cd480dd7cd76eb69d9dc860d4b0c58fb40daacbc38dba83df64f033411dcf0c00c475bb23113c10d6db33133f280164149bb91fd46b9717cf938679ff349578b847d8be5cdd5d42784708a2d25903085ae8717352510117eb50732388ce912c4051282209115e69081d72386ac333a22241ce1252c011128d4c0c584294af092664bf8840b574ee083102e1f2768e120d38da03ba1491256824033259c80133444818035c58820cecc109aa0cd1340bcf940c90d55cc3819ba0a13840a6d94d47082324c3c18a306092134b4095302191680382289102098b9210d162354e0208a287e2051831211ae1491c2f77ddff77df58a13595db6a420458931bd20e80b1282d8f0f402991344208284f033cd0891a5a3049e8e26d23cc4e7127a7cceeeb7abf47ddf057a14896255e487ad11c6bcc981e9f7c1bef2802893132409149128165a5882462b3bd1c2536865245a4bb4866811d1ca45b4f0105a369c3f3014c717599a3272776f37c9bd5d276153f776a9eeedfacd57e92a5da57a95ae52bd4a96eae62c8f3745eae6ecd454b5bb5d92a79bb34f4f96c7cd599dcefaf8ea22b8390fd8b8b90d947a5c5f7d833bf35a97d06abe337af59b118f57c7252dd1bcfa8cab642ac1bcfa007abc3a0607c8c0c7ab6be080085ca5254e9efe78f59f2278158bb556349b6c93aca91b60b8cbb29b013211f3b4f419b9929461c0dac2e117ae7a46d76cca41ed550afa478bdef6ca8a59e2e469cd73fcbac5ba01a85da59cab94e3d76b39388d2ec5253f3f3229f4ec5ce2e3398d0f49c6a2746d498fe7343d262642dbfcdeee121acd34c99c451b2de1e11983e824dcbddd253bbe63863df48ca57bbb4b7496febace87b7f400ee9299e734da62dd227b756fd7730b1a837bbb9e465b191220b09e82af3c20a29e8cfd344086c3c608596276a9cc6b7aa5eece5c404d655ecd05dc5486ea0ba2a065cc4e8b18aa1f231ef49002892198f86063450c1018728200c2abf22901f1c2e585228c40e31466cdd83543ce194957948f1a6648a28627217ed8906f789203c8981c80a0d2040f3acc0026081f6fc2207e8040e583820f614029524723a465e9ab5dad6e8f5bda00575a57ddaa3b874e14d19d6347e3310284fe43b13f4675d809692bc63bfb5773ae18638c6eec1f8a2d768b51119bf12763d65a6badd5daeada005b895759342ec637dc92dbbb4bb1975729fba4a0eb571a37973f2760ce19dd9f874e7edf9773fef2f77d9f8bf9dc95778a637df988e67bab34b1ee172abae39cddd1b2aa156d49e3ae886efdb2ec2097204714f1206a7bb6cc55027368367d4c501122cd6fcbcb4df9bfd3fe0b225c31e2b7dde51d50570682d9e7339b3218fa7590891e65d0fcb6f7011f9cd83f27b3d9f4e126bbf572aa392ecb91c95ce6158699e7380e1c9fe5a01e5c29a63ce1a03a1cc741af0e17d92e5c64f5059900b184807db061041f9cf8d064874f903c7ee5f9f0f47921ab6d83bcb7d5b1e8bef2aef06aef2b0f87153e3f551956ab1e5906dcf442e1a211f4ce4f6015ce7d5074ed74dabfa92a9c9351648b148f87733952eb9fd5778573301d7cd8f2a44bad7f5938b7012ecaca3d9c6bf1c313434ce952eb590be7725479f3e6cd1b2bbef207e22cabd1fdf5defa063897b770917574572dd4b4254aa2dfeeedd3db4f2a4fe5aa9ef54feb09e7729a58c2422fb59ea1b670ce3a815579f76d655e9109469e9cde7e4f34fb41bdfd2eba7317a8ed2786ac968903fb1d84808310fc1af56b3db24cf5abd71564b56dbc55ad16abef8d096d7d6b7bf1ed8729460b153d543867c99f0e4e4e6f26f9c01cb41330ac584211872c16e49088664988e37805a7e3647c895680bebcc8320baef270608ad14205f8611ef766734296592cd83433486966090096f16f997bebf162c24e007cf51ff79910b0d80c724b4c972bfca631415928da105a2643eb9d37329c0a8094c0c9917a5a3d9a4de5cc723c23cc7494b6e8ec7ca1e025c40ecf0f49578e858796f318ee413c5aed19abff70095584e0e7617a024f5a7fdd678773396a7803b5440a13da30712487df3e5c9ae8fd20c148942474e9f6c1f2dba792397383086f8cd031e44dba7d70f8ed63a5c46f9f2b1f2c2d3e44b444fddd3eaf2b9fd795cfabd7f3eaf9bc7a3e3d3c9cb33991850c0f4c7c6872c495df3d25b061099b10ac38a10211e9eeb1f2bba7891d9a10f245ca153247d2ddc3fbdda375e677cf16d70b494f941792297f774fd54baaa7c94baa87ea55d5b3fbdb23f5d7693d9cab5746b6ac382199e2cd90b4ca6f9ad65f29765052f5419a3457e9a6ddf09be6e2062468b2a42cb1cb92ee1e2abf7b9c9e86fcf5dd03d513b5040dcc123431fe9b96d545ebd245c37a39d1aa5e4e34decbc9eaafd3743857a580e255094105104a3062ca6f9a5391d490ac1c74d0ea6242ba694f527ed3a0aad090c58b0b6894880122dd341b7ed3a4fefaa64dfdfd4d1bc1f08c607878d4f070fddd3c5dbdde5f1eadbfceb3c3b96a35441026a0f8e0893354d228bf79a4feeea4b2d2a4f0c31133e9e699aafacd531535044b08266868d811916e9e1a7ef3585d61fdf5cd93c5236664c243e49944fddd3c50533c3ea67886f0ba783c3a1ea7bfbec3c3b9edc48ea9a822524cb540c3ef9d094031460910281cdc2c49f7ce0cbf77ca1c4105c994a91a76a090ee1daadf3b5a7f7def6ced70b5d989d266cadfbd5335ea769a8cba1daaf169076a7cdad98d3b527f5da787732ba24822a48d14179b23a0fcd691009728322d0041c20e27a45b4786df3a5d1948212545083abce1724af7ce93df3b4e7f7def3ced0cd1a203468b98bf5b27eb4aa7cb950e564fa7aaa7c3d3b1faeb3a3a9c2362d3c49724d448c98a4aa77eeb0899a201092c4f5c31a12add3a4e7eeb4021400d1537663e6063048574ebc4f05b47eaaf6f9d291d2a91481b91889bbf7bd625eed4883baea959d6546f4aebafcf76385787f0f1440813c61899c22385e1f74c2acbc9c9852d4e4ca124ddb326bf670228a2862a40a208614c0ee99ebdf07b66f5d7f7ec6a86956646244dd4df3d0bb766e1d64cd4e588ba99a89be5d0c0820845ac10a5072e5f5cf89df332b264ca1a2e19485022dd394c7ee7f040a50405ae3146207993ee1ca9df395a7f7de76ce57069c989a225674a4e931caa9edf39555739505739bb1c29adae9c9d43fb9d133ec1099fe0e0e0d07ee3845538210e1627281ca9bfbe71a672af74e3508541da8441dca8e1e2f92deb0aa1645921542f84d29221ca191f3e64d8c1ab926ed992dfb22aabbfbe6557322c7009191170095994cc876cc8ce6f19545757974cd7e56495a5b7d65c5674142b53feea2ad04a37b1a2cad250597a9725f5d7633d9cab65821882ca961dba9a3ca5b685df312d28944cd165b1c1865dba634a7ec77688b082113b62d0b9a9926e9de4b776faeb5b3f6910490c44128bc5b0fec6b2c0581518e38131abbf1ed3e1dc986508104ce0a16b8824290bbf634e3574545cbe484121871ee98e9de077ac2e2143171790eca6d44049770cc9ef98d45fdfb1a918d5126d9670f317d6f575c1d47c5d5ca0130c7482814e30d80ee7162085cb0f2976f84164f71b56c14441452607246d8c18936ed80abf613ca826ac4ee044854c0ee9861df90ddb3018961518112b517f619f15ecb3827d59e69705fbb260260fe7727c70240c12416cd9c1c41babc26f33688a561b116490a2254cba4d1bf5dbbcfab1e48413aeb658e121dda635f2dbdca6c985c48c8264ca5fb3c95f73eaaf592565424999bb6d4afdf5b287739509146890f232246a2b4d5ae477a95501178e58f9a1042740504977b9f596c8efb28b053655a474ecca2c916e13eab7e9f4d7b7f9640ef97494603e1da598b24b89f5b7ccfa9ccaaacfa9e4edd2eaaf973a9c3b40126b9ca0e1618db92185dfa5539625b4788152c24b0c26a4bb44e17749d543af0c9621b8146193eef284dfa5d45fdfe554ceef92ca0bd9c68b9bbf6457ce22d5e42c2e2d324baba7a5f5d7c91dcee500f2e508167638e10986ad21bfc91ac4944eca0a503790c025dda409bfc9aa024001b36608992b4c82a49b14f29bb4faeb9bbc22b19e90449e9051a40f72c85f12aaaaab8ad46d9287733c49daa059e2e4c7961382fc7e591d200c1145b4803ca52993eed7d5d3ef57d60d26f8b801ca171d2944a5fbd5d3faebfbb5f5e2ca415e517290d794579317d5df57557e41e5d72ebf7a38577b505d4a1429e28d115da92de1f7a895801d2660713a8344d491748f5b16c8ef71851b98b4b015458519a8a4fbf5e3f7cbe9afefd7d36b081ec1e0514cf97bccc263173c62e1b10a8f3c3c5afdf551877320b841480c63c490b01322f5f17b749a8185146a006303971ee91e9f9e84df23d411dd0c312c41262b041de91e7723fc1ea5fefa1ea7462a6c456c83ad886e443522d75fb1cb4accb2ea59ed706e5cd2c4901e42662823432ac26f512a07942f49a69440e5871ebea45b9c72fa2d3ae143161eae7a38d1a149ba45de87f05bb4faeb5bbc12b1701091080e2246bd7e8b50184af4210ec1525d584ad461291ece6de089335474d0d1c2134a52107e873b4c6d21c14b218a3031a43bbcfa1ebfc3ac0d58e04161450b0f880ee90e7b3c7e875a7f7d875b215711619422c229e3efb04a2b6ca215527585505de12e94faeb600fe7ea159e0e2d4360d5d0c312e98edfa09690962c3fd610c9024256bac1ad0f7e835d7fa5da8809a31b42058974873a1dbf43a7bfbec3a7bfe1102d20182d62c474835db0fe825957e0942b100be85402160d52449460f2258874834f7f75bf412850eaaf6f702a074b37487589b4b944dcfcfdbaee4ecddd714d7d5953bd292da9bf417a28618286aa89287a48f737f56f7e7f559fd55fdfdf15ceef0f2bcd47244dd4df0f6aebf3f1776bc85fcf5d9fee86a9526303902e42ac4e904ef13b5bfded42a5851f94b8e92125ddf94a8adf994b0a2c418ad4b0860990746737bfb3d65fdf794bfc9db9bce4285ea6fcddb949aecacabb9da57232fc7802ca8e24ba256914bfb156d5902436403142e40459e9c650fcc65d5d57968082c85615978f74e736bfb3d3d390bfbe3354f83b473dc1609e88d9b88badc245559867b10ee7aa559b34407a3abef850a4eb3776c25039e81842ca933527004937de3df11b4bfdf5df98eaaf8e3656879bbf4eb8488d13d743e1a2acbbc3b95a86479612c82c59410a98d489df57aaea5a31b9bf2fd6022e91ea25eaefbe500ef0f1376bc83bfdf59c0670975205f5667ddb285fc194bf396618a2ebc70a3c64f8b2246de2b7b55a00102427f43802831b2ce9b63daeff6db9feda24346e91f56da3fefa03aad2586fd62ffe3d8af96b9b84e9b643ec1313bfad53056c8c10420424b844e892b27927217e408982cc942d4b6cd9f284e50b1a273a1de9016e4e57b4c7922f639e44118509e9c5b9ba0686105ad0a1c908315c49ef9aaf00981e11212c99024c557a95f8eb38f0affb127acb9ce82dd3e9abb76e6ba2b7eebd75f3047aebdd5b3791e8ada7deba6ea2776cebadeb17f4d6bab74e1ad13b76f5d663af58158faaf382de31a7b7aee382de315b0c7ac3786fdd0683deb0abb76e96a037ece9ad9b3ff48675d1dbecbdf59e2d7a9b5b597a9b5ff436796f9dcca2b7f9f4d66961f42e7b6f9d2684dee5d65bdf71a37739f5d677a0d0bbe4bd7520357a97bab70e8484dee5d35bcf50e84d3ad19b9c7aebb318f426796f9d4704bd49dd5be771d29b7c7aeb2f2bbd5f576ffd9545ef57efadbfb2f47e49bdf557958be8abeaad874588553fbcb8e2458f994da2f3f41ebbdeba57d1fbe5f4d6c75e888ebdb7be0208cee5d4ba4cea2ad5add00a0a940f6d1f3acc6c0a5d08bdc7a9b7ee60f41e796f7db645ef51f7d6675af41e9fdebab6416ff1eaadeb287a8bbdb73ea3a2b7389ba2b7b844efb005bd45dd5b171241eff0eaad8342e81d5ab10e6ee90d5abd75708dde60d65b9711a13728f5d6750f7a7f5d6f5df3a037e8f4d683b4e8fd59bdf5202bbdbfac87f2200882a283601623511ff27ce8a6d9149679d14bb34924cd269066a57768934aef9cf5d64d287a67adb74e12d13b57bd75b288de19eaadc79eacc780e88dbbdefa8bca7ac983de7807bd7110ebe293deb7ebadf7e460bd078bde77eaad0745e96db7de7a5011bdafee15c03583171d4800697325cd497cbe62e4c7679f65bfe1ec349d4ea77b4ab3111fb6f8d05f665398035daca3d9048a6653b6593827d3c9743624f496e9defa96eddeba6c274b72a5080f9539c9745f14fd5f0ef779fd7e979f7fcf3e7b95faecf92ac5f88c6585309fb1ec867c76d06ccab52a7d0f66b3e92ba5d0af2dfd5aa34b157419a5ad1859e13176db8d56b352a489ff3c5f2516ff55a50a840b9fadd98471ff15a1bf5ed1ee65bc1144e9a4e82d9e59e449e1b42a9538691e4b5f29320196b2b09489e505c58ac345d8afc61883a8888b70d5bde1f034deb6eab1ae1be7a2a979accbdcaab13cb5bcca9205b2e441014b59cac298a5e804acdc71428c953b49e81268069aac9465d1db5e5d5d5d5d5d5de1943b5b39a598c4ac14d55ce9944e75b553024599c18856caa674f5945a47f629655bc49f12a8aa572b3f1ab4804a5993a0527fd02554025df172674ded2c3f1d325a962da8284536b652e45a51c246182a452432515926b9955f1ad16e5c510964456f7b7525b228c5234a71a66b34bfb528714cd6f78b5c6599ac0d6f947345dc339e342a7b6a4eac4538173a765c95f1557f5199b2579ede9465caa82da22255e15c88da2b32323c268fca9b9a6faf663a92aed2b6568f1dbb6851ab12682eca0f8af592442a3f5114af704e0cfa7c75f86660448ab637e79c69554697067ffb571df40e1f7c38572cd2ea598de09716c1ec9f69dc100cbf0fbdf13d08826808becf68c6e126a4675c7e58c962d046ed267e9e453dea10c5a9a0ad6f1c4ec4b966d3222449ad5e95212b0d0dbaeb99d6355ba6cf33ba6fe01b2d63dabde1330404b83a5b8fb2676c367dd93400f9e6cd6f1c3add67b483d16f885f91b2571c21b83f82f8419403fc23fadf35b5b4f52dea19cdde1bf8b3df376f42803d3f652c6af03fd75ba4812e82a7067d8b354a54d3b2e72ca2f8f6998681116d7d572b119b4d1f885ad3003a2e00e1c20f016e463b80d3ad21abb5b5625a5355aa32aad7db757d6f5729a7deae5f1ae700ead1f59c8c36d5db1dc0bdbdbea216fca0d19bf6167ccd6d94d72da8325e7fbd8a4f1863a85b84c590062ec24966b5f681f6dcb6123bbe50160a63df007f7e4f31f41c8ae199dfa2153c71a8d634dadeb8c7f5fac65d259427de0f8d756b64abaa76aa4a5dd036a1592a5e2d4700639d2a5230377d8ba55b290fe01c0c45f57d6dcf8ecfd0471bb6a610b551e69fe5b4786934f75bb652958751297b355e9007d1f6642cbbce4fe801dc9cbeb8a58d2b1f04cffab9ad36fb8c73b6fd2dd7a2ecb5cf5b97b6c74ed06c7dfd1aab36cf15004e9ec67cebbc132e8df9e629ddab87d64df00b40ccab93d6afd9047a788a2f7a782e8179ec7412d3d1699c03b8b7ebaf13832ae6e9e7cde8c49d49df396346ebd23fe2579ef884f6df3acb0d51e0d31880d72bf2662ad2b595b69fff88fee342fe23a2190079cd831cfb2549bb23f393e8a4488aa4e8a26f27495288f42092249d244992244992244992c4c9f199a9cb4c9224e943923d64a9494d239dd42167a4e79038a4cb48922449922449922449922435192361a4499664261de8ac9782ecb553c445d97f4e1b2996f072cd45d77f81fe73ddc783fce7a219d4fcc7813c3bf6d1c5fcf432dd6a6151d4bae2a0f3ea70d1cb814edb858b5eaf97bf5e4ee3be5e2ff7e979bd5eafd7ebf57ac972665eea382cc6f3bd5eaf57cfeb4543b7c8836ebdf3d241b7d5fd6bf6ca7939ce4bf6728d6e0bf5afd7ebf57abd5eafd7ebf57abd5e31747f4e30747f4f26fa2a5f24ba3fddbf72d7bfbc76d64bc1e73fa7888b3ef739352efabcda5e2807409ebe2110a4f56ab356c7c32d6ab1b5d8626bb17f19049d043ff0033ff0fbec67cfcff3c901fe2e309f568faef730a9ba53ae9276faeaf9c4dddb122eba8e4bbab71b8446df2a7cabee5548020f540f3ae8a15fbbc4741a97667b9eaaf4005586ace71c6b9175515753ac47d663e7cf75d26d0a3b7fae979ed30bf07360dffc580527bebe66b1a184c7eddb8f6118e210dda367979db11376bedc9a178c2e827ecfcfc76b36ddd0ab79033f017aba9ca17af0a2366e0fba350dd0d3e50c9407d1254e9e9a68d22db2e29b674dabf9a138f14737554afe2ee14e1af5c8fad882f6df22f9354b95247fd1adafeeadbe58af5e170c81b426a8faec3f4cf4b6bb7dadfe7abd17ad36eee9817babb5d6ef73d2def2dbb9da8b737514f475a1223014185bae9ca1d8fc7dd94eb1903f300cc12fc3903f300ca33e10fcb21afb81e0976308c2e6efcb762a7f6018825f7efa2167305ac6e49c79743927f18484fc8161087e590b981cacad923f10fcb2182e536e905a22e74ce6ea03c12f73d1d223e74c43fec03004bf1c953f300cc12f47e50f0c43f0cb4168f8c1bafd40f0cb27a86073b64ba6c879899c57e821e76c06e37b31d8f5808688b77e1b418a2b34a001b3c3e38a4081bcf8e6308761ce91befda56bae1cbdba5895aa522ed14aa215fbdf1d7ef5af32cdb048d7ab93e1f9d5b1de3e7386e863eafce775dfdf33c07afb5e21787cebedcbe07f2478ec7914abcfc6797df55bb357276b48d3222a81c704705b2d8665d4deffdc667cc1effb2c0c58ad479fe31ce22d8390a4bd49f2ae3c2da8bf713f06f2050c9cc2ccfa47adf94a5951b5028be8cb9010222e2229a2158874ecb819290a43a464851344552c3e2822f2e4c663280a16405a90c04209d18e1622ecc06237a445f401113a2f5c3d845022081e0c584154f0a10513ed8344914ec3c614d9a071b89c408b3810e1a0c32945635644515a714249eca981826c9321493497c49001c68b3cdbc2e9c8806481b25c581b9423738891d8f3a5c52c0c0b7287a24c2be2b68308686b684665137980a8105f3ba0e26bcce9563c07e1201494673d00e92a353bbbf2d3c4070cc393412674b20ccd2c6bac9c1cc4038e48e341766b7c195f70ef05f7332fb8f55e6dda10dbc132b93373ec84b22a7dd96dce8ef3bd5d8b5ed4de7b2f112f116a24a9883d59c22024a0f87ae10381b0f98dd0d6efcd366808f26b42c3a277feac944ffa5665dc6a9dc80e38b06ffefa0df1b3571bf9bda0770622a37307e1df10ffe702ac538fac59c3fc8a8af588a719503fbf0ec8e987e2f020e7f0cd14babfe8ce135a3c33ce395f8b03db1efc7dbf5d5cc415875f79686cb8d7f2b2a665dc17ed5ec67b7b86042f5b9248abdb31da46f13194e0a1c3951f8e20a24c5a2b88820b550e3bd82401c142f1d66553aa8cfc96039f4f5da56fcf40073cf67beedb5f7c4fd4c6edb17f32a90f95ed6449eecd7acf11bd653b190f99d355cabd951129222223521455e4a368c8df5d0425dbdd64bb22d9aee8c6c3b91a4c52d0b1c044162042dafcbe59d00684221f38a10b018874dfba7edf96d270e95a808147096fd27d7be2f74debafefdbd64db7b9e936b7db8deaef6ddfa0feeefeea9bd45f27eae15c8e11ca08e11df18147922652277e1369fddd41260a0626929cc9926ea2267e137101e20305365c88981242ba6f5cbf6f4e7f7ddf9e6e43b41081d122e6ef26cad257445df4151196ee1155e91e114ff788acfe3a910ee7aa9a214852c0c1c81a18aa30f19bc8490a0612945c45e124dd444fcfe6371114971f393061e20b5710e926da1149fdf54d3445fe26a2d244da68226efeee21bd1bd2bb213d35a4a786f4d4d0d00ee7f609bcbc312187373a6459e2f7d00b3a2755218871626647ba87b67e0f518006303a6489a2030d27a47b68cdef21abbfbe87ae62bf87b0d20c1149331405e3f710d4d6908fd8d690bfbea24beb86747ac8e9afafe0e15c03c20061c6092423bc014289df2bac9ae84a82ca981c88689921dd2bae92f8bd220b4b6bab0d4f47e449ba57a8f9bd62af58c1a56545142d2ba6ac68b282eaef5e51155b115b115b21f5d76d3d9cc340cc112c167ab0c10d9a1489df362d105c684184351fc80025ddb6237edb7a1f6831028a1e5a745049f70add0aa7bfbe573cc17eaf18127b6203137b621363eb62c3fabb6d595536dedf6db3faeb361dcee588caa18525588c14699225cd6f1b9a188ee872e689d319a874dbb47edbb0b4c023ca09549e5449b70dcd6f9bd414d55fdfb62adb94980e156d623adcfcdd2aba624e2ad43871fd85c2453128adbfae628773357448783494800505efcc6f1552434dfc80626a0a4c1050d2ad62cacc6f1555171082e68a8b10639a48926e15bc32bf555805f15bc595fead020b96460511581a15512a7ca818f277ab80826da16e605b2a78fc75948773475a469280b0638a2921647ea35a47a889828d151d2a40916ef4ca88df6856932294e8a1890f51a8a41bed15f11bd5bafa8d6ea51be5824541a3c0a2a0538e7ea355301eda8447054361280c95faeb670fe76a9b1d08563d429042e607dbfb7d563160a80851040952b89192eed312f1fbb422021b2082d8d1852724dde810bf51a7bfbed12774084c84130c4c04317ff7097b3a614f276c77c276276c779e3b40f1c16e2a4a93273ec6fc3e3da0c5083233f08010c142ba4f31bfcf18d63cb952c1071a76a090ee73f7617e9f527f7d9f53e6ef930a4c1b306efe6eefeaa9e9716d79d6566f4bebaffb0ee754b8e131f1810213bb35a910bf7d04a7204ea08a820c9523e97630bfbd4a032b5ba8a0b0830c4d9049b767fd76abbfbefdcab198381126517fdda1cc29f7313584d7c5731dcfe9af0bf1702ec71122d0a4e08305166ab0f2e5b7908c1dce7039c1c525cc07e9160ae2b75056069c78a932844d1125b449b71010bf85b690902982902982909010d5df2d54650a4199423b5348eaaf07f5700e035d636e6842c9921f74c0fa1d54a138320213676ec04ac2857407fdf03b288745489a10aa70c16221dd423efc1672faeb5be849680898203060c4fcdd4159bda02ebd20acada0aaad20de0eb2faeb413a9c8bc0162740b0a10ae10b0ea997df414e2190828411509cb070440de90ebafa1d046505c5ca4aea863160d21dd4c3ef20a9bfbe83a682a84a266d4a266efe6ea0ae2935535c3ca02c5e8fa7f5d781763817aa41c24409251f9c8025e5e13790140792f0c035029b2e1ba64837d00ebf81a480a1ca13504988d8ca4a375097df401b0808ab0d109136517f37105409e4a3041a52d6ba4a205d09e4f4d76b3c9c7b00970f7690f24215ac255c7ed774a60c3145550a2a84a14a776dcbef5a16053c1c4cb08107066b8a74d7b4fcae69fdf55ddbaa7191b528646d4aad09d5df5dabead5a07ab55d4f4aeba7abe6f4d777ede96f6d08c9e4070cc944ccdf1f72ea879cfa21793f24ef87e4fdfce8702ec79414ab16744873830b4c58fdfe410ae2861b868042450712e9fec9f2fba7eaf0640bcb0a63acae9449f78f0ebf7fa4fefafe99027fff50b569d3c6cddfedd345ea7cd4903a1f2ef2c9278b7cf2e99101a022a814091a0f7349a714325323c00040002317003028180c8a06a328c9b144f90114000e63b65260521e0824f220857110648c210a004000008000008001a588ca00984ddc1a451fbaf9cfb961c8ff7b3289e65cadbdd42222f66c6dd6ab7c1bdf1cf354aef12ef285df769b694d6dc1a333d0b5a3f8c7961c74a0d18c4e3dd24813a7e9f27ff25e9a1dd91c9326343a600a343ef5146b088a2bb11db8e2bd1fc26e9e0acd637d1d476173dfddbcad8b587b7267872f7993e8e7dacf52614b1bdeaafcd3e9be3aa9665dfd2e71d8f38ead7514b0b51baa7d7eab015f23ca901cc98c0413bace4278363584745700bc2f7ca205439e90da644d1af0b2c967765d8da3d3df290edd2f9cf2f48b3bdca118d5d6219b6ae8b4c33159f190f0ad12d23661796dbf0d3d46d64c6096ca0565c41bc82e53925b09e0195e730346a1c0642ac0762c782667a657bd58983e3a8369db82fc14df4c805591c5fbe8a63c420b9f468bce76a6f6e011d37547d5725005fadc4e0fd345637e8e0cb18c4ae6dd1061371328c52b1243f80e949c444a63ba5761a8afe2977ec36594747496e8fef731c433c3eeb1d0d4fcc9fdf6b11859b24e1a25930c61d914d4faba54e98d5ed4fa360a3d63e77d8aa7829e273a89c921b8aed9487bb9631e24bb547289d42e9521f2525d787d157d6f87f49345f26d501e97472c6e0d1eab884ca5d0a58fd4dbf8f3918233d097deff66b59aafda223d13c89e77d3a2ef63da73678d9de076508298328aa1c0f6c8bbaeae0dcca05c424cec1a375b4140a5c3e1a600280a39219be2449bdc19889004b31d38a7abb8d11953520f59b045ae700a7da6a498aed89686f7e93aead269de6e7c31b9abd054b762f40cb42cda90661a5b4a99290828a69def47260d649b6b29dd69b987a21275aada3f4c3616e554ad56832442805a962a68854d48274f4a9379ea14136e4c3e8b34d6377e003f991b3b73e4954f2920b0a5cbaf78b2852ccc7d6f7bc11981e052475c585592380b8d49ed07aad0b6b4f3aee88367545be5f15a2636cf5a225089fc7fc859ff7d41bfda5379385a2293bc6a1e9bc464b9b2a059484323829b897417ddd929969b47ad0a1ddf0339df0f15388d7c1e88a801cce4e69b09988e7b99428bbb2bc51bb64ddf4f022d57f4feb932ef720745e33c403818245b4f786ec3445ff9b020a71299336c04ddadb0b968f1081b6981a7b05a70276f8a3e2475ec520972156ecb28d02b08c266e1ea436434c9b06eb243fd122bf46219028429b3c013b9b6746ab970370f957df6fd2f64f4edc25d7a53f220339b5337a1c145c448f46f9d2f638b9a1288abfe40a3ca171740f87c1da80fc6349a5cb97f64094a383086c0dcae24701e8b0e17ccc5c07accae4233dad06aa3f31c706e046b7e50e7e9012e270c941886884026fa8dd0cb94b8dfd96a9bb854b39853bb78b30b30d209e810e5f256e703a6a041dee10f28cc625cc31ed8dc3520c2f8593c4632196fd7a2a8d9a0739414c4692c11b771f94e059c92aab39bd05bd24ec8189dccbd68b43e246ae26b2169b03bf141a60d200e280b6e6826703a008e9eddaebb7c5c3e5aa63cd390c8884e9e5f203755dec2f9bcaf695e54d5990806af7b6795acc3450fbb5534e322068ed64018e2f83ed9ef25705b193f2eb28fc91190b3c4685e17053dbe12eaaad9e4b683e9e35b32dfa544f1e87e301a1c4a6a5519be059303174506af9813a142da9596c9f9be4025a77ffb80d9de20e5db07286065345dd331684fa9dc4c352704695315322e616356d685f5aa05b043aefcd8882a949190870984a2a0ac1711d1f32bbe380a5169c09fdb553805cb7839203d0e961b5c3b6712d736b0178dc0c8fdc3604458418ef59a1495ec17edbdb0fd93e482a505b98e645884c80cfccaa2ebcc151a4a59593d76476620572e0288eda5954c755846875d40ac7c68114296488107a4231b80358a59fa70b383f0d36f684a5119885bea4c36415da8f7e8e4b551d995b449472234e3e14f58c2e75bbb060e558b7014abdce08fc5bc86dd8d83d18df260375ab882fcb42866eaec9ad651cf204ef9790048beef049781281b24a0b6aa0af03f183259e16a4ae993d60d471ad0220cf65220df03161ea5d63150ac17e5828c4130603d662d2b7bca17cfeb7cb85913df739cc507e3edafa0525d79d0c432ee0611f908d7bbc0a4774701c159ccad6177486e090f8e1c26c2432b74dd77c9109bf448dc2b1a4d2594d3f90b5260b1f31bda13e67ff72b008d1c7b5813333008eabc0ab8e65b7943d79827b130f38fa6b3560131e08352224285d60ad88cf75986348845da5b733b53b4f6b2a722259df684eb8a197029e92f568a2328bff318b4f6bac434abf55c42b357f0955d4e879607e48cb80849dc3cee84f722fe6daedbfd4cc20cbaa08904212285acc9f76b2f9955511db465e06dd0a82d9052fdd53da7c962ca46052153222a0d7e8f7a82fd0cba40458bd8105b97c4fd3bd2728d0b0797559fcbe18279dab5b9a541ae2423f23e971d0fe1d60ef654a3b512932aa0ea8e369d8240358f3d177e410c37ec8b918b4a7e97a243b382ba64e96924e571117f7458b358242b6795f0afb0fa388445fe239e16cf02d1be3e6df042448478e1bc457769af7e9705c560038c239c0132892f0e270e30408e3f8ff30388a864794d9f1d8511e9940a802a88ad15716c87d127116145711ef66089106924c820dc365d58b73e8832248a65ade3ea71a224bf922885053a7b8371eee4c3988b55d516e545bc6e16468f800f27808423fd5291b48db1ea191a76afe23448a6af1277c06994c93b41354182680169c2843ab568f9ce0718fe09d05f3aa1f9b51aeb41cfa0c61be9935ed6841a1da7d44b15439934f91c5c412857b17b4fa0c89dd026c15ae385ea91c9a906505e3acc355dda2e9ea456de8a793d5f3299d80a9230f0ce2c4874d849c8041603445b9e26aed63592e7b51d4bdd4c88c9c39a7cdf40ac46fd4b48daba5523e440b8a4a44147ebe6666fb573dbf1b2359bcc94fbb656bdd1871efcf0d343a5a1cb3f4c545905919fb0e9da13a75a97a7cbe996b30fe5562190c095150871da3088bdfb3b235daf2378c3f1a6df731d7fc8875257a1b79fda8859abab929b323932b3e8059187c810170466ec1a012018f5c16fff5837e1e203cb1a110f85a23d09b23acc203f0818b7cb9b082fa1bd61446f68b5e03d58a54e2bd96a32437b3e817b459c661966891ada83fc2aac6da067c268ad1b58a88c1fa290553f059d3c30638956901de9d54485f5269b10befd432eec6d1a1ab0cc8a15b2453f2562c781526acde7801927b34893f06373533b7d45e6b48c4ba67d09900efee3e23162e238ac260bae8ef40a22b255264c0c3f21ede8b771d884692692e93f0ef41656946de9c9c48df1125a902fedfe302622fdfd23eed8633a1ab3c00a2b65b20fc67d2b913327b3a62b8912564d3781ccfc83713331dd27e782955322330f1ffb9859f8806e76ddde7021ad993051d9d6bd4eec18a7b4644e566a86d4d83864c2c426920dcd9c8895879b326902b2a14d5362e25053a64c2083ffb9b8d56f80b492721ce0ca09e3b13a3efedb415726ac3181ccff810dd5bb28d7e79890af32c6245a2eb3e2976034bd4e6f084d2f5aa0fd257493ff6cedd36c84b4087cd42bdbc44ba029a51dca82990a4767203b454a90ebefba85dbba68e356900edff736e47094354ea2855b898c9edc0ecc225bbb48fd7ee7896122cca36eb0bf3d850d82dae5665cfa9314c8b435b858f38556080139d75cf5da329d6345f4f12ae567c09dfa7213c130a08a4e90d4aa3b149384fed197e3e9477129567cc28f4a3847701d85cefb443f7718209ef39de54064c203cc2e07a9e31d01a934a57080af926fee6e5d37fe1ea521f6c674e5c8b44ed060246968eec115a0bb88e0098dd38be8e0d2a7832ba10eae4b325cf893e13232ffb06dae919aa1bae8d0d1f0b221c40a6ee862a2cb5e4d0714bcfb4a90fa3f3be27e64e92fdd64562c1caf3a87e0e96db1888000949f57014c69543bc7f05861d99cfe52db502d7b1af734c23b289c6182b2422d007b8195c932001c6c3fef3b1b3864f2067bf37cf8579a0c28466851c129e684437b6a7ef03cf06843c5d3cd0fe73ce5b215f54e373ffcb424e03654e12654f6e8fc1f49d92fa264323626bcfaae27342a64a33d4f09e32ed5a62cc85f0aa4c846381b297093222f4d1220fb4f64465e0ca7dcac093f3eaed7c36a7d09d4cc832dc5c15084cae1b96644ed951f18fb6d95930056e34cf3a2ca5b6ed0f6809c9da8b29517da5f008e020bb6ccd9799773b5999cd49eb715def6ec2eac642b4adc5eb185e9adf196977edbbd1331e867a8ce6dcc46cbd39d08edf85e80a3a967fb6a701457642b3b6f5be1855987f1396e9991d913a99868f6a55c23cb36fa9191101754a98a8e72a9f9e4a4cab429fd3a2771ec26d668590e339a39c94d7c687f245174db142e813901572587117075de0f83632315b37d0bf7dd455e649e9be53cf49c7cbecd459dc72821124a79601cc008aab5fec97a88375dfb7681a5aa4fb89d8bc40096e803c76a7ed9b7662c06a3f1327fd75d9dbf0a4f79729b0362ceca7466814a4fe0a9484d16b1b6acfa8cb5ace96f5aaa7f7cca31322ead83cfec42ce1205249bb845c14f9e1b7208f9c389a323aa820a8fe11c95a6a7d5bb5d4e35a7b190a4893578512ea3bed8484ba220a610d20c59aa5dd4c2b01dc5933a08e4bd6993e19922d8c509293b355736992042b58528d3280039fc0c3069eca6881234248338b9553a350df75c7f3956501c5cc0dca0c3f662ca6d9e0cce59f58b491c1aa71572702580c50698b7895da5a3e135e2d7a423024aaf7ca9fa1490b24cddc0e507b86167f263e5cf811e1b086eee70a1da8813507b5a0842b5e38a8fce326bd784108ed6a15f8eb5454850ee1d5a255d5cefb00b5e7a72234919859216dcbf7764b37cf55a32db7f6a6231a9b4c02b7d9478d263aa4ddbec17be80ae16f8bebf5f897e9658d4d7debff96542c658de7725754c5823eb134829f9c0da5cbb9d2c2f7d5f80e06ea244ccb5a3bb789963589fa5bc947dea01e293b509473be1dfe3cd60277f28ed50a59ba77c0c297a28d9e84629d64c80e9b7cd8dac03b02ac31f44477532884c09b00be7de1b71ac0d2c33c2461bcfa848d4983c833663d383393c590b80e9c8d9a95853ab5783c55c9c8c4afafba0347a7d192498e7b38829ddf3b8af967fee0023afbed51f7a5c73008cb8185dd3351a53b62bbd62fc74b02948005829938a905225c98302e795aefd0c2160bd7d113181824a3b8bb4ca79a17108c11faf4be4bcc4963b6b280f107787126fe5ce523a487019daceda0ac19d0a6a982247e0668f07955033fd80f668381a07443e6d854dc63156722aa9d71c9a90824dad985af379e78812b7e0451f161351403e74ad4bf00067c7cb433478e438e06779f3d7a1e4a8d69c1c63c0590bf4ca3e18b9841e8ae086ba00822f9478139895d6ed92e9e2a905b7d2daad989f46def36eeb5a37e2eec5899755eed980d75a2fbf5a1ec6e20b64fffe8681d999cda81e790dd0650bbaebe074bb9e78dd9ed1d4f7ed8e078e4ea2cf3f5cb501ee9b49cce98601063ceab24b4883d670fd72f301ba80ed73332f0edfd2390ff702226192f95a702cd767276ff17a11b87a79bfc81bfca5d02bfbb893eac560d4150bd82989659a32737d06256323d8e61020adc76a3a741101b506a1d501416d1a4abc7b9108bafc12b004f93da056573d9993f7a2822cefcd1034608d60f3be8d22a0a6311121eea938d6e8eda267294070b23e8f64994aafb95edd4708936005ebf2ba4282e2060176d1be11558a6664ac90e4eb923ebd25aee87d0cc9f3a83ff73d935c7e72bb8e794e6025fd5bb33fbcb32b8934164120c8fcb28307face6c2afbdf80ac6a06f640a816ca84a31380a3b138f96debb920689701f19327ae48be359c9b27963eb0f45687fa95f7d888beb4836607bf022c9a71b9d649079a271dd7226be9762643c9730b0cbd1b6d041baa65a447f231f84432ae121000326a32e488a01ce3d051f2d4e8b23e53eb0b4d5cb81712760de61828c35058b8c9ae89eaa85f8293eed0b70104e3e9f0997e4e879232bd392902816b792bf96264f442cf5825da4a24d6cce9a453c8e5188fbf0d508c5d468874c374f7050d8e58726810c81f1ed0062d303d0bc289b5ae7986409d6c586e9331021e874e0afb0e915ab0c8403b0c9e022dd6e974b7b61a60933e2b0c859df4bd48e0839288408cd1e1276550810871c30bd053b50153ff758d83967fd7c1779b9bf0b829c30c98d5c71df838891845b1f4256bc45c2268a33e40408df62df428d7800e4dd0bb0ebe1bdb84c7471986c0549cc2c0e7580fa422decd6a2ff549fb350766b16e063da049a96209dec7134ac46add0c501a430358fbcc7ba82496ad61553a5c3639bc3560d734fb13f0eba8a6f1159dad44f5ecbc6e0501422e8c18c223d80514bdf5e29b1734313cbff05a0820bf9bd124602d479094885b834e18772341028abb2d6616a820739c1305d59fc53323b73b7b3887b2da9def9060bde6570e7f40564ff7e598516f3cd75557c0736d25924e958430a0519a643a183d598fa7c50ae46fa34989ea6fc4fd0b0e5e022ea0b7a585b93f871fe91f0181aa7b8dfdf48a7e78dad35508581b5f75b7bf79898aef7b8724b53680afdbcfa692b72daeb800667fdf778c3232151a33c7dceba9e904e33642b15026e027593b26de1663fd3134c53efd07a705a2a277b6d65845bc8df1b1b0c754631ecc3dc1f622c1f19608e17727da8ed45c7ad38259694776dd713bef6ae7fb8296455859cbd49fe76b64e62ecb46dc35b2015882b80bf432c04c5e97ec9183d0e94053ec7ec53a24c887bbf63b4a30f3ac740cca4f0c4edccf83663764d3dde5118177fdccb8fa922b500b99e0dde9e98efb900cf727b1b715094475dd1506f070f3ccbd5330c7839d0bc2b1355c6351ed1a4b80b8b4fd662f1abe4677a0d39a82504afd79087554074b98613db55a5161c4825d19e5a173b9486a4fe9722201f8ac91b2deb558a83d1560740d10cb71fe45cbb125f1536f657daab26c995ac93ecd5623d0c49c6c29417475c8b7d7da8a49de1ef82e5c50aa85e92e9fa25bd3b59490ec1f182f90220e6c9d3dda03e676f26ef384c027078964f11b3acb9edf188734ab8cdc889bf71abc4041a7fc64e733951f06b49b551777b7f481df974784aced2540054d8bc14ddd38b8a593657a1390701e14b6178848346e0d2431e3b9f0b83b51e02d49bb82aa8d0907e71362838655a8d1c0b798a4fd0858ed8623f4cc7847f758be0bd1a894b489a54176f34af19786657be77fde0a0f66e970f0ab6c323ed33b9a2bd58d8768f535aa686c97b5bd12db1acc955066e47cdbb8b0ee0ffc4b78cf4e1104824ce3ededa5e168f6f0275c41120fe2aa41c4d56795225e82e18437c9f2464bafda3a7acf8825ee5eaf8346b494ff932c0dbfa1873afcaaf0be3f2d304ae5ac84ac7fda4baf5d3cdd94c9fcec4ea83d1ad99600b89ba928719117223081699196c1f058f0e2f458a9bdc172edcc8d6d833ebc5cf929a0ed3937dab80888a2e19ad5d2c6dfac4d842a8fb5515d4e7e278ea572729b1d75d2b8cbe65bd5d20ca72c5e70116d4a919b0c42458a8d1c5c0db95c7147343cd1ce75ae1cf0ddda17484995281ef575a06558fcbcd8f8955b78cedf8f3ad053482da3ca8a8c4268c6388b257d160cad4bb8634a3be743f3403e6b797014b9327ccc02d405f9a2472dc05830a1471a264ea008c968296e2da4bc80fc303462614614dcfcbcd71ac0a1af1cfc79bc233a4ae3c15691501abdc18440c409000d811e89caaf57c4734b1cda101535ae6a08390cc2aa778a44403e75b276fba8b20949531fa481ff8a7176f0e4a8b527acebd2ebc2af8a935aeab85e3b27cbb2643d4aa8d5cf052add460e5d0d50248abd0a309f0bdf883f843ef7e7219a968e64f9d6f7b8cf3334acd044c63a5316e1d0ea89e1ea13c6c0faaefe7c03c4853750c8bf591e6448a6a3dd0734e7ea2652b0b321d516cc98fe5a042bf754a5d6ed54e28ef1de1e6588cc280b1760dfa4b92d322d025f4c6c0f7a1800b8c9a31a8dbf9dc732d59a5caedf273963ca7f5b83ed9bbc38fff45702159e04ad5a41d9980b3d6cfa40f4e6e73096b17843d47887dfdfa867cc0899b16f57b5dc173d407072095ba490be9601d19560e025affd6c3fd7c72b888ae28a0ec089ea787cf622e4038bfc30c9e3b14430b77d3f9e2e9955c9fc53bec34e6b2a7fd9b6ec69a7437cbd112e3a8c52e5115aa5dfe206b53b0407521bc8c62b86fca04e4960f144d7f2f24972c019d6a001e8f5f0884b4f3ea124aab54cbdc12d6d0e7fbe953293a6c1359f3d2593f794c3a2898ed250cacea02ec41ecf08312b999794fafba93070a0883a7b1524cc59fba3bb85a02942bd2e0d4160f1686b98815f8b96ffccd2b406f581661b70e921a0a2ac80c15ff39c9f133aa0204cec49142024cb765397103bf1f92989af2d67116e133ea8a8e86bd323b5c256584661fe1f366d40e3aa20be536254ce39ae4e65f812459779a91ea84a1dc9f0d58533d205820462ad75f484d43151de8b765bb8fb060fd183739f834c8aa4387e483075a3c89f4d8fce0e7a163d0f7bcc9b276611b57122ade7f5a03916a861be49a04068093a094e11deeb2fa00b21795218eb897cf0e59b555f738c92299d7146995ea78b274a69353f632d5c31c39e4cdbdf79a91761bdf08305938617d5efc49f755aa0e6b5c6bf3223c9f9058b33fdf2a88479e96428419e484bfd46a8a741f31cfc7d4d521c11a9a1c680b1bff22b306173095c6d3224274dd22db0e4fd467f9990fff220b758e21b4f406f9acb6eac1c993250de2d35cda3156d71ebb25ef04476e3e0cbea9882590744c3be8cca0ba5146fbe7f3dcef9d7a74ecb14884158704ed2aeff71871939e9874a5c6606af1f80cf558c1857dc4855de7460d4511126c922215c3e742340b31d1b5c3b75c848a71d71e6f411274e3b1e0bbc306801ccafa0c24e30aeeff05bbf99de67a47cb6f9dd2a471819902dbbe4e13f2b700ec818e7d5cfdfb759c0909b0ffb1105e3475cc00d382ed67a2a9321e5eda935e9960ccbc2da161f65fcc009e8efd76dda3fb759ce9ca624b9a51bc1d25470eaec57cd68bdc9e40406221871d87eefc7e074c5b064de1f97a7f874603671b74156e2e9e2b8416234bfabb8b9b1498df26dda1995549ecaa2d6b47e406daf69f5c8824bd11d2bb6581f3f7012b74ee68ea0624c3ef044597e1bbc7bfd047c087376b1bb49a575e6b4ed52531f1ef9d8671e77c8c6f500a0365ddf2ec8f8e58d0177d8d8a52832298678e6f27781d35a3c4fd4c87eb124863e8f644a01a630ffcd5c45a137d57a14fe3a1e2f87d2f10cec055a5753c351b3355520821ae8e90144e65b3d4410b5046da3d54800aed416770de131bea0df5d0e6fa1abac86944d5bcef9a9061f5dc82757e998fdefb399ec5584834dfc4ba4f47db06991bd1c7ea8452443fbd12c9c65979f66093a5ccbe708e1a5498a1722619a93cca0004c355b70edb15d1f5b3a601aa6b8d647cdb147a034da108e24448ff708a5d34b979327258edeeadead7b2edc619199863ed14db7b5b4a7a54adb74f45a1988762a2e1098d81277134d7bae71e526b5473ed7a8729ddb5d0ea3d3a20a57b13c4825aa64ca96e0909c2384d7b0f30fb3de700afb1887675accb6e401711fba90632520fb680e5f28c518b2784dda01017dc01475a245e3560d324047758ea20dadcbe1fa590b641d3051697069985b00ac1cf8a5c1ff7ac920093ab8606e52264cbf63d7e8c034e32699455cf8d1a2ba5a7f80f77c3795940d8de31759c66af7c333103b7fce6182eabd3fe7f051c0a515c513bc87a5ab8b292a6d7b350eabaae8b4584e6d1dc67d85f9bc0c6e912f69209ecf93291272b659e2069d0354c36d4b744bf9d744e05aa98d94d40ef903cf4a33e4751b66cc238a9fba76e1bf2b136819a3be28e6fc16d8293d73b75ddc691a393f7793154d550d2ef70c8c99cf9de43c1fb58b1770835e819d8c590f9c34995e818d4734319dee4a18cbc9a4372002663e275d4283733c6ad80f69361f768fbd9cd1805e454074d487ebd9e398116f14f22810f94f826c01b903e9b463ae9d050e2a6cc801c4ef9e3d7afa8d174f172e44914d8208699d85f0fd77efdf3d7b849e922f52f24d5238bfa59e9be8e411b3cd0ed770f34fa8ee9d7e318632d1761fc03287710e6f86415989ed8e6857b82f81597b2917434d1a72f0423ce20afd758085b152d02e07aba0998e7c9a75993e3701319e70ba52ee49d07e65bb3491c9eb96cd1247779441ad1284a30be8b7a162188d97628898a318ad09485f419f44a2119d7aa6bb5c63b4885cfc65dc284474847c8954964f07f8db960545d6ed93fe8aa96dec8cdbb208c8963dce210dffa58f45a63923feef227c4f48659b27a2452ca04573f369efc7cfaa39e90f7bbc926617be5839d9aec0b85b0765f527ffa39deb652860e5a4a53ab64d6cee1dfd345a0cf0f7f7a54a120d6e3278d78ee1145c456ec76d58f454525baea32bcf14952008939d338160179510ef2ed0324525a2470f499733857cbb5132c3bede02842e5b5347186e8f5b13c3aee74af83818643eb4c23e7f10ee10696b90a07df12444af27d75765c52dfe9211869af2c1dbf0ad9bd768a31c9c3fcdbcc3c39d54a0b24408969c8ba15bc31398af27180bfdd0e47009e8a1a553623bf67d2f281a428e1ee9a70fbb98cbf234b4a0814be188d57bdf432380d3a6a6b1a800cda084184ef999d3843bd8fe96f4ccd7042670597696c382cdffff90137bd36d7bf6cef744ac335c5dd75ccc5250a954a1b600200a758f655bcfca7ca6a2448c340b242dddfbcc5b02ac6c7b62280067d49712319607c255c78b4466610e790eea16ebaf9765fa21c5a198e64260112c05ba9382fcb9573bf730504a0b59cc16f914fb8c640d9009bc94dd1128b260184970ad6d20af4476c883a557250abef836ea1a5820ff10456a84b29c24ca66aecb31dabc89b2d98c4dd50cc571101795fbe9677c2eadbe349b1fd22ad5ba6e6490e96934886376c79567291ccc13c10020c25811ea15691655a0417566a5c163dc6c60336e6cb08ca53e903ee50bf82edd7d5508169b2cc2fb2d9e9e79504455bce4ffa821b3bfa8c1f31faba1f9dfc81875f7b2f2f97fee01ef3f56f6e85c3cb428310494dea4454f03a1f182da2387a597878ae59f070b0b640f124be61e2d7b6459dac9288f438ab90b7f22cd750f74209e10d773bc2495909d36a13d4d4b6a3d5f96f83d9096a87b722d757b542efdf670a1aacfbc40b7f724b207ed9b0edaece34e7d1c4d651552af72881d9803e3f30b266654b461d6259f36125db2a50da697d4b561f8259f3652a5b6db06d3efbd6d24452f97b4ff27e13243ef683da48c43c1cc9b64b5a176d97da9fb86f02816b329924411489c501812d3a8ad606d0146113ecb42bc8b919cbaddae0c66948909a629a246d0683b65dda31d4605f79413619c505c757c809ac4c98c7780d25b9b032f0e313789efa0a4d4e017b329524018d15b9be4dedc4edbd35e8e6568bd34cb99bd4dcbcabd3f9699bd302e137b732c37f56a2c7ff6722c43eea558ceecede84e60ef233ae2c732a8f6c9f92658b9894c8f9099a9b60eeeb0c336dc80f19d7d5f01ff46d5f07393d53373b7ab2b717fa933e5865227e6ce566fcaad56ffc4cdaa0e999ba49e39775b5d99fb4b9d393794c7666ece708a19db4b663659197d7765ccb0571031c520420edda58622d7dec7e859ee4324e8dd874dd0741f3a41cb3edc04cdfb3009baefc32668dd8749d07a1f2641eb3ecc04adfb30095aedc32468dd8771da6509320ea2b50a79f4e6ce337e5499f81713949d4e2aa5969b2ed1e457350cb309e37bf76a8b26acd7e08d4250436914111a5a5020d44082e252c352148e1a84a2286888124553031214c41aaea258d4e08242b0861214911a5aa240d440f2e4510d5ae2ee01b89ff6b6d23e1967470a5b710389bd8a5da7bd22d105c84b6852c3703faab5d09075c08e60a65707108e6cf751226990044e47cae72df795af1f7e3ea1c14bb32978fb2d28a18b0c6a47d24407e86db92a3f3655e1bd88594e436c355481d09f19fba94f2da6039ececa6a2ba1db369304359350a72516ec7cecded51b1f4064ba791aa75bffc0da11fee13708c8b8fe9057483cee7b22cba86e228915ebb46bd2143a590672c82bf340cb89463f793628d9af50fcb3ccf3a6f0b8bada356f807fbe0afeba3c173428774d5b014a5a52d0ba091adaf7b38b308133229f6ab544fb40af14949c3dba11da660d92e10782c84df32ecab758743b1629114645abbf4998a1d0126ffcc3a1d0a04dc094fc10e31d7d7de0fc53c855d810ac644a513cfedad39bb61fa8218009f4549a1f1920122cb3b891ded681030a6e5060c54291c8a20af0068ff9f402097710e4155177efdb30b243496212cd5878d58650fef871238488f6cbdd69070db7f4f8a6dfe1baeea6ecd48060088d15101c0f9d5c2b175389f6f2d540243d884f0d4c47e0373273a72034ec36ae693fb5837a6a3bc959b85c6f026966187a10da300f610d99c0c3e17fb340959320be4a683ed3b530f8496ba55a0a18c527dbd8f347bb0e23647156a450f3431d6b8d4bde9ad9c55d0004012cc62418eb10e39509e6f77f6afa0c86d96fa2c3c27523d82fc5c040b2cf6a34bc06d9c26513a23dd1771a42dacab4a0c038d99f61c00b575eaa0ec8dbe42a773942d82a923816423a58af41b65d0d9528b05dd75c7540561e1611825c33ea0179594483e07d108218e109a7d31460277728bfb887a89155ea5ab6e5ba8f8a83c5a70a6d376ef4bb60dbe55bd971c4df31b61d48189dfdcb5870631e3257e67b5248517ace70b1e4b42253518224f0a8558b7719c4c91b9f837b10637c4cae21c6f8807fee5fb803fa7bfffed9aff4fb7dfddd5fe8f75efcbfdff41f066e8a505cb01b44e6242e3fd2aa67ce67fb8262fc0fd4ca1be1a153cc366ad50a9a5bf58296ab5ed0ecaaf7bfbdeaf5632ad5c303e73572c07558b9880016a73859ad0daa24dc943d1d3b0730078a86e85d311e69d769b1a321ed96873ac9775ba1240f040c27744670d94e8209d3049053f4c9a2850a4137916854b56a84aca506bca0d115e44185048d4dfb3b3d445ff8ba469a36cdbea834cb51a259105e39b72c1132aa7dcc519557243af3b5427e3df49070421cc0fb86d0a65a0059a4a4bfb360f8f49aea084dd5ca39254d8d255aa69a030bf3321dc609fb0966247802d67e10b5461567ff8980bfc97c831d6aad5181183f71c0ed3381b7e2d0b461b383b07dba7e8d2bc92670f843ef8d20f3bd74163e3aebd215d8c890036988238459bfc3263b6c3b889aac2a461f0b643f3cdbb402773be3f5a879120d2393a1c06c8b5a8ff8884d14206f8bb414c7f7bb5b3cf29f8472ec94409bb3e3083f64efc0f9e6d220114a6a02770391b31b86f320afa6d3d819bc6660a35281fc9d170d061a10796ad6000a8c8963684011c2a4395464856a656eef0193c3b662709eeed769bd3e60741b0ec67a837b4feb9f8017f097510e63f42b76fb0a96e306affe1b5299b0e38f9683ff51bd6f4144f551c2889b1838dc22db9a3b1a742e75ee1500231fd565b5adb1efc7afc6600cabdf84713377be876af28f2ccdf3cc6ef971b3ad034709c6ff8b30f1ff76bfc6b81541088d6f53d1f24d25a98f339704038d51be8c4e0ec2745057192a9c444530b38ba8d57ce150d15774a8c0654cf19debe0aaa829a6a8427bbea12b4f1a25b2c63a94b5fe5c9429ec51a617e22b3c2305c05f5104ad034746582c42dccdd1ac5d6ce523a2b383d4e98a83ec19aecd590f1c6097064cf7f0a8bf08d1f78e55b0fa5250c99ea43407d706431cb42248e4122686fc1f674860fd0765139f1526f451b415653c9dc1d2cd5f9fabb4bf2a0d44aea6bf4efd13f89726d653f60e3bfde2c92a1b871196f7f997e85a93fe8ec948d494be0597f2c6a873f780c8ef3c8238380acb747b462003e6463410a1fca9ca27503af986504a7365ea451c3588a5f7fa23c4199a0b1e6661587d821320eb323998176026156a3a0dd04495acf90425724b9c67f441b9413a1b02d145921929d5054711fe604cca4fa26084ead2912aecc4c16a261fabad8747859d19451036ae8d8be5fa1f479d40051d1144f6be80826c7846648a34df0af6f61674cb0d56adce67d8eff71f9ffa18e951bbdf9dfe66d671a0aec902c08e8fe2297c8edf6b823ef1010f7f71a44f2318612539b58ac033df658c0fc3b2747cac106a417305a37baff069cafe733c1a19067707713a6d543abadbed259b5f74247702e053801dca07add35e77ed4f4491c5624ad0abdc05c4d8641e84ef293327c875ab65653d8c81443a571f8e5f74e5a2f240996c441eb6567d64349392e7bfd5e513453d76eee12ea57dda92c2bb35c99f8f44a18ffe04e5aa8be4fa10d8558738bfb1ad28bc5c357cedb7e067861f186dbbb3a245960965927901f5c23f9004a2e27005ecb7c3b9352d4cb0c0b4d2d630d8e32371a64377dbd2af7900d45ecc30950e2643c76ccdb546daec8e6223e2bc9bf8b5425b0a2d24ad75e055472234cb101a81e7180dead1278b1e09abf3c7355aa4d5c0d16e97e81a711646c6975c6c3efbe6e8687a5a20304037f3e9cebda2d2978bd3028f7bcaa1d09e3889a49472e7655bd50428c67731fd43fc2443e0fe4063a48b880b9b75015dc58455fbeb2f81ba2923730cea811425dbd4dff6cb20e76c33e8e4f528506721fffde6ba9dad16a32d3949b4ef1930f8bedc94df49b7c002e4285cc5c8124656ef9021ebe6b9018aa1a7481daef874c19e7d710e52d1640a4809b3490562c75feba6ead42d17c6d2e1fef5aaff8b7d012ca1aa424838d4280e5052d2ff93cc1a20b2394cc889305e324ce2b5c643a06051cc679cbfd0811e503197350f38ded07eadd56cbcc0f9d222a21f14be5741b8ec479bd388ccc517c4d42908d3c398cf37b2ac243a6c78ade57ea7730c51b30273e2e5e637363ec74567635c6056609a3c6b59eecbede479deecd0b2a7a4655f73e3569f2ed324eba9e1ff1520a2b4905af6c0af514daab6ac69232e5637be5091385079cdc2248c063ca1b985149239f1433b06081381f11b81b3a968d90775d6f3e55442a4b8f3f3184fd181e4e6762f0afde111dd22080814703b1e8886fffda8de78dbd5eeba706bb1ed45803c233e05121f04a613dcac7ba7ee2b34ac59a2ca9f439ff06f7fd158f84f8d508a3d4aaca7505c7bd07b54ecaee5bc708619bfaa0ed09709df31c6c4f21310f11bdb3f2bb89df2ba91ee7f7d425116290b19ef5cc3cdf56404b46951c0c8874c678b38986c88b2fb6b2973c13a57269323f5ec79f876612d4b58fb91c44586ac06cedbf7ae14b67fb009cdb3c5318fb90adfbd6c6c48765b0b774cb05656b1ec65762fb6d728b2aad69ad00bc8a0f692348357eb87530467343bffeb1284262a3fb3dff9a8b075fada0b76e05065b0307f14a765f79af62ea7ddb916e23d76c7dee007dca04eb8d8187516c2ffe7f505d3f5abefb8b68c124288937dadb9930f77d395be797c2a4a7e60ab9aced9cc3d4976c77f1682e847b27571f284d8f3a514482b3f37b000b07950c45955f900f31d95c937b99bab8ca47d1e7c83b0bc459f11a19f74fe2c695e72bc8ea395cedac4b38abd2ff977eaf906c0c43eeae22a93032a7870d01e19bfafae85ffa9a0f3e069bc0374bbbe968176173b38e4c5383a35d80e4e863500e21905f53875dd4d78a0a5167e30fe1b87d34b058bcc55e4e22c83fe4f84cac685c81efa79ecae4a310b5946793fd85ad07c983daf78b0522b20105b88e251a85dc8a7348328f4ee44775e3e901eba5928c2e4a900c02be691f12fc17f014a3020f68370e63c242f120f822210281406693e1e897e47446785755bc4c3899b5f5220fb96a21742b45ff1c207c99369b21494b365a757307b070a11cbf3123ecb1bbb242996e907319e05e397b96caa6efbb7658ba41e4135c5f9962f7e0422be3ebe65e6d6cf69234bc15fa008f0780137fab22d3b01f08091f96e154e218c8c7c25fecf1851bcaa7a87a93572ee23fd5c07eb0bee3eb4243ec8d5d3fcfa0ac38931664682b7506be24d5f7927abb13d5220ada0198536889fa4369e97f1b60dea267dc127b17238b89b29de63cf74d19bdeab661fc9728796dd58b981f0f37a98124019be0afeea881ce2a58da5780f662531b30dd0e44035eff384e77c3c53d7204712b82224b5cd96cd69c6e05b61f8a8421f006741af436c36b573e0c32d62067db26a0edbd89616698bd4a2d7a8626f15dc670aaf110701e6f24d03d90aac01b7ee0ce42f957f7fc73b9c40d290d5dc885a1e1b911b9211398e8be81a3454f1994a26a89ea276f884bba7e8290b0bdf5ceff1eb39e70b749210340053aed762ececa2763938c8e3e1624aa79e9a388a5078631b4bf7b1d71b7ff492b03879d24f7d4efe48aced9423421656f270c5dc9b4587fce4a1197487ab5b92b7eb079cdbde043c6b60ce5c3480b4168ea0b335c2e32aebd5c4adfa95ccdb8796f854e097f0abeb4cd2a85d0be6ce5463bb14de77c2eb2d30cc17ea507d6a066e8727d2133c62a286376bbfeb5be8f6d4fe8adc44371685ebf9d843edde3cadb992ad9eae20272ec18fff90f44a602c13a64218756e0affc3ec8919d70f11b86eecadd3e481b5334de453a4ed9ba318a7df66a3366e18e16ffc6386452e0f1cfbe6f61e41b5b6423fb1cd40ae5a884dd68c2e396fc5041cc957f4827e8b796268b7c97c25f80ba7c6f8eb8906c240a52162402c6669a62f6953c77525d241e13a0cfc42e8e9107cddd492b08a452e9845a16eaaa942df3428150915e8e62a0dca2a6af6b7cb3cf2fee2a267cb831de31f1357f1ca9263a3cd0c05c87264e20f192dd59412abdff08bd5bfe8dad4ab0674a81cb631a1a202309ffcee12bd58ed3ab9c01585c3b20eb9aed7ebebae70c6326ed8eb19c1b05d48de221504784f30282bfb3a52e7d0c0ca25601abf79eabe090855f26880bc8b7f1d77c6514ac1f09ab2d29321de96c570d726f6467cde180f895ec3c2fbeae36680c66c08d053d7816d484578de03d64c22457b53b8cfbfa40fab9ad1cfd93a8343d7db99529b5cece2e0853bc7f0c20f348797e8f3bb208f2949e64bd788c9626880e8de2e21d092e906cf31e2bf756d33f27d8cbbf2a0a06916ccac5118b3cf9a15dc265f61f874637a17b3f026abb91651d02e2f74be731cc86d4d32e174bf2bbd84c31a6ae5d3f7e9a129d24422e2b61611f852a505706abe7f21893fd5bfb0c0f606260a06871796246bff54b9b1fb4bf57cfc8cd6d72ff3bf9b31471c84c1a51be4774e5c0a17911e8c35a3f4d281a061e94bb5a75df2b9e1f0f21b6bcb0b38a37a3ef32e549b74181e32230c950bb842061fcd8f6ece4b4178ff27b8b8c17da1eb987ac6c1ac84bb41afa445190652c6d8e21457b8677813399d59d40c9d5e4038edd0eb97992cfd3607ec75d1b5b2ff97ae76d93a041a598b9b42ae5e2a840a72058ab80e854af4e2ecdda6387520473db9c345ba22eaa93d33d6991ffbfc996ee939d17cde47d2d4b73b4d10ad6dc0762b0b626a8a411358f362e89a942c7a8b466cc21770b5061bcf26faa3811df30592b8194850377900d4da4dba5196c6eccab2cb091bf4551eca27df5afccb0a955b5f43ae71df731ec3b02c93ab27a000c7304cb3011bdf2db43bf375f1910d58b6c44e270423b5e480160b8301e05b28c0e2404d403636e0114aef7181ca5dbf17a0e274f34609e59fb6b023a50b969862209d4a0e0d1021305ecb60a13fbb58ce2f81306dd7ab2181409975458129d875694ae7a13c1a35495fc76647f80b310acbb78c4ede3c2812947472f011606919b6ba04dec376b3e734a07b506804377c7ea4f6120c7d851690fbd561c01ae76b8b79e1935c122910b263ec61be06c75bc2c7f611ec1508971615c22641ef0e0e3ae003ddd25a4ccfed87b82815fae19da3573c813479c6997212fe2b215be94be2921a73dec66bb97f25b92510bffa04c25a85bae883704c91cbc82ab3fccda12732d94a36b29823ca4746affa834bc7be26afc809abeb9269b5493fa9e9fd27794d1711a85adc3a93871591dac2ce4cc39a8332dab7290bd7423cbb63678dcf2dd2ecc72f243ec9489834080d2c2ee06f4ea5c20d69b9afee922fe2ef3e4c99e80fa54785d4d32990bee9051f460fe02dda20af7b70bfca7a1e3c1b5c42d2e509961a102745eacb08e8b3019e79dcc019444bd2042d4cef10f1bbc665ee2164051a4adf0d4a278b97dc50d69d387b4fe0a015a2d8d3b95492bbf54b10400af950c9cc5b7910ebaad51e09a0129f5183660db0f579242e6ab4d0e70e98d0590b427ba1969f05e949c2d28061351bf832e63c134bd2945dadf3e36823faa5f19bcce6b8ddb8af81b643b892594f0a4c35ed9b77383083ee8e06ad155ff61359a939f6dc39ad6af842b56b4304c562089a1e5f2b32d21a8a6cbe96cab661da67ce0baed4719fec895af4179814f0741b893bfdc21d275a4ed8a4d0af98d641ff746396e0d669c92fa6ec5094db8a3b10ac697fe3ab8aac1d4318cfa36a0f56e91f6de0579aee033f77e9a757d7284368ce5e8b374a08540c8bfa08d55ca6fa036d8c30ff3d6d8dda98c2b4e9742c68f1ef89c5935b0b930791aa39bc756cb2139b6114d856d9ac9cfa940abbd0abf6d959b56c169cd211c2b1cc72a89f43fb52b0e728ccb64d4b6b4f42e9d600dc0f8542ec300258f2275afd6c32860ea73f82f1d77504183906a9801372d70f56fc6700295b99a83e4cbfa524045a33896df7015237377d9748a0341e5a5faecbdd0d9f902f429913836eddcbeb276ebebbb1d2223beba63e69fe30298f387d87e1f04755200ac8cbc93529bef2bd80962a25ae865e4bafa033bf29b0924957812c228984c2738812d203f65af8ce20c281e940e9c9f0c6b99c4906ff397173ea1f815790e3a55088e78d3fd82f3f7638e30cde40384373c1e02477d9579bd6de1993560dae29e0b12e0624d3639217ed873c6926b3460688d428fad0aeb687db9fcc3b36af336f75806f920060b7c3c01ed9e76b43a67d31330f578187e684b9a7cb0773178070528f9e1be1f3c36a96f3812050cf491a1e414efda1f46dd1c4da13b7dc1614a627d27366d30dfc5e01a52f3212eb208398396a4818d774364bb814be91fd7e27f36f2ed61cbb6f81bae58dfe61a7b19a29978c79f9f1fa23c3da26a9124c81bd8a6e7ee09a5e1f4e6056c1ff6bb2a9131d0e2e5edf6034a59d4dc23beb14d673b55ce77618ad1e91b88a8ce0955291f773ee8be47fca68f44c64612a62db627296d50f74445b47222151142e0235701d166872d863298bad5d9a9bba068a030bedc470fb834cedff4dcca543f36e84515722dd1cdaec754a591e2b2ffe70c334d0c4e12e10f0fe9fbae674b9e743ed0f59d674a45b17c44de1149d4a0078062924580ef266394d70d4f5d436248bd5c5c9156342b9fdf0496988349aea451d356ecc639f8f2322aa0c354933e1aace38c4602b6419d424e27e873cd401f4d74bce890a33e6b831260c899a1be34f709f43f68d58796b80e3fc8d3970d8a29ea3d70353f550561faa2a76315adf09ee7f9414669e1ac9f87261dfdaa4b5ceaef76f9d34f9264711033aaec27ac1e5998e95ba38da9be1f83736f0b9057d0f6477ffff38ab0b9daee2b907688bf35681ab33372cd4431464f335ffd6039d1582957816b91348c0a66623b9422795d797eb7a087edce1b35e0debaa8ab2d53665c8e43880bf0fdeb1a0b38273e5bdfb6c16d52ebcf28db601d146b4bbee54857bec63d10fc2bcb486e7694eeacca8d91307cfbec3935d2d762316c5616d02c1f91553520266aa56949ce2487dd2617ac020d83e9b47f5d677cd0a455a24caaa1244126eee3d6c6ab6d94608dc5d0675658c3861348aac0f46deed4d0e19d1981ecea73f28e2854c62f5f7f5078869f3e18f7e77b2be0843935d50be495e9b5635978c77aade571ee4aac066895332300435e00e79418362cd2cb03f4164a8c1a348b80deec28c1553435e97aeea1a4cd3c0662108b32aeeec32ff109f58914e20c1b742c715e1b50b8b47aee944b64b11a691454887dd9a2316e7f7cdf38d195a2e91aaf97a46fbc29efb5dcbc63c89ffb5143ab8d4b243470a158b07850bf1084295148b422e024817830aa615c8874c208a8537a24385054725d462bfddedb83ccb227cf4b34287970257f545f3d34148e8e6c26fff538458b91ecbfd59229f178a2b395d68da5cf4a30ce4452698f0135306e7d7fcc0348ccaad10b25df686bdc8d131c46309012e115894744663756279538b17891640d460680f95cb89a5ad0434e13905bcf9da7a6fafe672301afef0c3b72d67aedb8ed8f147e68a164c60c4b65906765e564714f0b0d534ee1f1f91da6dfbe05f3109670b76be344f044cc2690898f25de1183e8340e5bd5c2a27a4b585e32b428d90a675a67cf610fe5761228b8e6c029961b36b64a840960ea1c3e9529cb1d6ade5cae23f6bbd671546d28304979c51f7aaac25528ddac359a11f8cd632758f401bfc44fcc19e76ba8832bb11ac80458cd8a6433b88821cbc80374b057dbcf4607d0072504c14434cf61de7feb2515dd597999f9270227dd9d7c9c242e9d5d8436a46d2195accd86d90c28ec236f44864c1363b734f6570ad865fd5edf34f4ffd3e6493b50b84509bff453382097ccf9ada958380befe2966463f86ce83dec5886e8fed484b62352fe2754bb457f411da78891766bcd7832f9eaeac14e4dd693b8c92b34ad0d821e81573b7bbff2205901092429cb9e2d8c841b730abe05b9d32a3f4081928fdab4395960d8a26e70b81d73030ef2a1399f708cc4663780716cc78ab8e56414b0f1139611735caaf157a9f7829f4595c4bcf35fcc61d67507d08f0aca0028e4f508b4b052737bdbf46a0f70a2d9e43d2b24b07dcc6f3052a2470da80d2c50d72204dca81f9246d4034838aeaa262608096de97dfdf00d491f0e8be274f02ca624a3fa3f5fb46af1ea3aa57e5f70ac1d478a4ab7e0f84460013ea1ba3d42517f185f1c7c854681c4218ca810ce011c30d5d205054c58812d1ac946d38671a00b3ff3ed8ad2be3f4f981f8c59712f14b8dcdef20d26d8082b7f139935e9cde131c350ef97b1f3c29f459ac456f6b543bb94b1407cb50d0ae19f0ee428f79935c73999730e15164627a23e8b7fbbc123ecc78bd505090e9a20c2f485f0cb8e074e16edebb753b9c41b74366539b9617f1a0753581eeb44402e93c5cb01ee8215da3f4eabcafc2c639ba18e55f192b29a24f8011a54716beb885ab10a79caa3bb75a690bedad7b9f832fdd5f112d579296fef347fb487e506d2d835fe05988130b3c7ab8058f735360287bee1775848e5400b473d8dee5e5e49e088065727d72673f788ad2d63905209c9c90d5d7970b96494a2733abe136454a48722ed19f392a6caefbe082c109a568ec110cf146020410e6ae3584444cb4d59754ae8b4d22d52c3bf7f2ec907dcda65e4ada081231df4562a7032f2c4d964d10b8a3287c615e89151649e08b97008e464a0867e15286277cb9492b71abf136ff0497ce8808a8c8cbf1e52f8a856dab61e56c1755937951ec7c70dea12eeb5ae848384457212c317d46c942f377f0463b30043b41f0a0f67bb12f4c0807f0c304ea2005dffad0057602fa7c633656d18bf681bda8ae2f7ae73b336c2d034719f0692398353aff7d16ca0671c61d7f5adbc448a1cca51f4de6d571c4751706094f1be1602be5664f6eaa6372071c8ccea8de6a2f06ab83c2273be27727082661878944d76228ea601c1531a01807d7c2f9298f0fa4eb8a4a2f0fa78b91ca7e0a9210dcce16249532a4630746383c072e303df59aa34a019e25af6f2dac4809a77d6f90a29ccfb9f94ca4e3d39473035e61594a49bc296d3ae5b1a804a7323a972a1552b56aa68f12684519f02be794d06c5e9849dd3f5150beac987d80d2572ba1103230c1030ce349618d8519ca772d9921d12a89f2c83103af0fc8b5d189dd90841df9c911ab72d28319cad9b544edabe35886309e107ccc09e96e3284e0c87948b598caa90b022fef9b03726c59b7f23e02bd34ebcc9a185d623f599527957a4846003dda1bbdaba360687323df9d5e9f0a10c911d45c4fb8bdf0e8557fdfb45323174f49bb33036a7046e8deb57e602cf8b890657da8ae5c1cf97c763bfcebb5e62307de5ec47f0a7c307c61702dff064907854ca69a55493da7273bb08222b17eaaee7fda7c0a18644d84505de606da2be649464765d77d111159e96a8c44d66b4d59bf162a1aaecaa8c464fea76cbb117b55a71c378cbdb38edd1e8ca58e00d3c8c97bdd8f12dbf147c13c01a4c9000e26788631d62d3b736036882f57a397a5ee2ebb0a9946b6aaaed0f56182d795941216a947afe632b3848765c00cb0cab8aa3f7797978d3bb54b088a4058bbfbb660377da40625952210c0e8083d1623402060ebdf8dc697ef93118f15190f36c911d9088a78ddb18ef7b6a353d16bfb28952f3df6a520abaf632a00a5115ac909586741495e172c1862cb9af65bd2472be1ad2c5a96093ad05be1d113c4ee6ab50e50d766d3a1021a790b9d20a1b40210d2c9875b8ba95b5b8e1ec3e2bc85d7c5e11ed456d4303b49408a6ed7d5844ddfc2185ed7bba8a265c63e47992ca6fb0e597721f44e1eecc1100b11e6f0b96ea0e4d7a8c35d79e865a32de001606555fe5c25aab513fe434779afb16190168038c20cc496e114c7987343d8adc9976bfb2e689e1e3da4af10f6b6c7f726a9b9abc9339e988169831dbdb1a52af73cf6cc7a83b2f18f6306bcafacb21fa1934c92eeb94f26e9b06b128ceee241f294056a7160f6273c071ad600cf1ad829f164590cd507011264bb0741676e4008d28fece5e0a8f59448e4b35a8b735c1ea2880333f07033d8815bf022e2285b0ca525ce3acac512b4dd1353e1cd97140886b1d48d03a6ca31c2d0b83bf98008c8ddbbd66a6bb192ec0689ed92c93b35f246f69e8e946e6af5e81ce52af6a9c2590a2790e46f6ccf38456a017aca453e93560f09b9981c2e422e2c834864f3545b026987fa5a01d729ba98fe84c0316946fcc4371653dd99f2e560ae0b4bc60162412b0bf3975987ba294b59e2877ece0b71b6c448dc2d99e1f856e1cb4b84699ec1311cea4d29a2e3824bb703480da5c08efee30408bfd6b9272481a39caed7c82402eb3988a6893a3f5f8576a84946a8e2f3600f31f7f2c9fc27aa4a70b6bba8f6609764bb2ae05e43411b34f4ae1e665084258e4c0904698c2d6b9da455f2b3a68c876c6ac722e7cfb22d658f26fe74a9b438d48f51f4529e02b8178f6457c832f4e8ecd3a7c762037552a5ee8b7fca1dee72be675431a99a289668201f6dcd4b37bbce033a3c59e5e115ca00f7702a86df9b5b27915122a7357b4a16bae0ccc855103271dc4dcce6112965d42de6ada72f51909a08351900146c317ee13b27d8bcadc42a860b1b0472ddb272afcdb71e89485044019a3ec47b3ca960463d651eac263959f01380f8f919e2437872c0759b8e5c09264e0dac8e027e662c2abf75c1b1940c21e84e87a754d703713872cf1699547109aec7dcfc3238ff05873f96c1554eaefd0bbe377a29e80de5b85d83700e540b3b9d3ce0b024e41104ba6537aa5820da19a703cdaa62056cecf1c67c309c701df078e6b31026ed56e5e75da84c77bcc89632450232d54fd155fb03b19508b05002a310906a7321517d409d21fef5df3695bbdf86bd0ea43c5562c30411d9ddb6dc52a694640a3f0a930a7a0a49baab77559830c7a7c6777777ff23dbc3af9478632d47f7ec6c867d0c2141fd7d521f305e89359e34a89f2251047586342258a1c41caa2e939821478d31ce58718253da345236ad60819af6464ec125205c6567da9a6823679ae60f3346ee1760de63188691e6d2442a1f46397f985e029991b46dc70b368d944dea038aa0a0f5b943c690109eb08365825dc7d102a232658b9cf1de8a5c2959ca28658c314a12c827121dc3c14475d62d7fb617836c624448d8633f4c4c462c462931a90452d65fdab4171489667452d77b3e856ac618e35c4f6e552aa191f387b9bdb0756d240ff5fbcee380f5e2a3e20ba5336872386b2576d1054432c618a39452f20aba075ac728a594524a19638c2d85849146d05926d823c5467bea07817888c5253e020285fd3c94f1100f4d3efab9d19e192c7105f6386b0510c208218430ce28230ce6466512ccd2a13a91214bb22c215a32549d159fb92f0956fd21843470f5603761a8773295793217c166009b4c5af4d87fb0496d9e73a7a8a87987818ea8e4ba39bc3337ea10c3a28c58ef6059e0cb5f722ea58cceeeebec316e841c9296d25f3e74a34590c51e93f882b9517e652a7882c5abbe22131cfabb7b978461779e654a39290a4b02859a1b151a827232544a29e5bb9498942e4991247f3d967a786c1913844390ec52c2b9514a29254c179bf5c984978c31c618a5941247062ed7faccd15bc818638c314a29a5143782a1b880a3ec22482388e2db8821cd1831677c4c76362f6463e3332c903354df81a33acee6f3bff9f25ff3d001eae8201e1c1d280cf62f5926dd3413a9bd1c1bd4cdac0659616b6bfa4b2468ed30b922b2ca8e48890414230a7a7102f44550955c34fc7e6805917b9c3366934d36516cdc666e34c610b988d18f986a8c928a18e3a909378521a5134fc5fffa6408598b17f8dc286b21e4594c76648c31c618a39452da3cf119a44c22e7d87d85aacb44af46e832665294bde8eeee32b6df444d34708da62db0eedb24accf199dd0969312ca871042093b1a21a53db06c73229b2fe3cbc766b7af611b0efed14b3db0c6a45319d316a94829638cac448c3e027667c87d53e3016a1c4069b7fdba1a3f2e25bfe66103d4d11f9da06c803ca00ed4d16f32c1bc984c5b47eae6954a7ac9f97af17968d55c9ad86f823a7352ba03777a073939f84a7ad13a6e6be277a4eebb40f587ebc507c2258645efe0cf0b9c4570f9a432533024647a073b14935366922435b94919a3141a236f949a4cff9e874205712224b4519b1d2fc28c9874f7f7624449ac0b82094921a1fe190fe551b0852b7f4eefedbf444a229f40a1a6a99238b842f59c2b01d86555ea0cb03451013568d30038f842264d28312c4a8ce314f772b06a7bf79f476c9acc49229988b4eccbcb71c4c663cf0676d99e4b3f622a94d6afb66e07a6e92b70bf34de90e9930336afc9d2073dd18b5adc622946295b40a564e7cd64faf73c14cac66688cfd090f7cc209baf7dc47ebd38434926ca038d5ee4a6324bffa2c52998905e5ed3344eaee2ca575a6f1ad72bcd378de39526378d83ab12b7c90702137fbec3ffe88e27d013101823f1e917bb13adf33f4a2af590118b353edd9afead293881788d2fe9de2ce504b411f85eb77e303e0ebb11f8f1bd3bfdf01adf6b746e7f7dcec738b6dca394524a19638ccd33371aa57494ff6cdfba787ea64419aa47f6cabc828357ef41e95af966d81dd5d97bd97254a7a4ffb68a55ffb83c8440df1bec4a75a70175f7652132a6fb4c4b4d2fa58deb53f6481829e388b0d715eb866c4d7f944d723dc0b9d12b252a425d5ca930702ab141d5dfcbf1cdb81f99a90c650fd8f57a977ac8ff7e6d548fc9b41923b2281bb03303180fadda3ac84af5286dc1da6ca4b4b1e12a090358e9c7d7ecb509975fb46eb78644ca7ac46e8b564bedef8c3126b8628c53ba122637794e789e6f3fa1ad9452c640e73b16b7927c79c1482e47e05b943039355226a794513a9531658c5101793029860e5ec293d2cedddd1fb58355a5f7df4e229fd88169fabf4d9decda1f720e2184117b77c5103b222cbab92f0ec9b51e3b4f2261ba74e9327d90b24a9cb3e3dc4a1fa594b2b4a132cc83d08a3d4a4391509913892e6397b14fe10546fb48caa67ccd378d944d140ff451933b0c7e8f1d9d7f2b976cc881b521e421a2d8cc629c9352938905514a8f95480aa29991668c2065267f9b1f9bf5e933444248299f638c12462965472b40992d605d8c31ff79f43f948cff6c62fcb7ae17d306a580ac173f3edc940ff67a8b2af693ba806e32b8189cc906c8a634b05e1361afb499b48e2483b4aa08114efda0814d01b15e0c5797263e0ce771585420fa8bb4cae6074eed7ad35597083930a8feb04a8e0e1a86e3686afc35000ed51f66dd733562a0fac327f273a35b946d8112030e688cfdc055a4ca773f62aaf29deb266c131554ca29698fd89d9c78ebd35d069a0d8aa46da5196440d9d94da67fcf43a16c6c823c212434434ccbca1333ec68d0abf092242d470fa594d216accf1c359e307a410e9fc2f424e39c93e161d773251e3960a2417c3860abfa0f3940395472a654ef207cc97d471cd6df1a7fe89877cba457c067009aeaa68f729bad1527439aa6df145fba52f7cd2d8618ea4765cdba1f4a23a0f0872cad7187dc7b805576356e7016e1ff91e3d7bec6183fcacb3c59857decbe24ae0435354dbf0c0c5ad008bd015dba74f1ea7567fa68638c1c638cec0426d9a3a0497fe8b0d46362f2bd63a23552168954f9f1715a8b5becbcd483df25d63986b1173b9451c739bfb93941e5c7ee9b1f8397eff0096d71288c8a1f464e760b7fc71328c40176499a9b35967c6c855d2c01d92e409274a90628857579e7d1dda33bc767c618638cf993891bb1ece1aaa8fb84a911a67e94865865e76d4d3f8ce7ad4f19042c71450a32ae1d48265a3254d9c71fa7997bae431f18507f9f506afc2fa6ca0e026d4d3ff30028723dc0fa41a0f5097da40ecf0ff461a21fccd803a13fbbbb7bc78fc31c343b0a9b7e26ff221d7c715075af82ba0fc117047a85754dca01dd57fd7888833a0542106272508fcf60ae63a5ffe8f69f09e6bf5fd5cb7fa41ae767a82428c744fb207860b158ac157d0d869bf1f2428d01a840898ba51a6718a9fe839638b934f1b7971a3fe37cc02a89c657fd7f60553ef683d6ecbd044456d921d92e95d4c5ad8932ebbe10d408975f66e75b13bf582310d57f13105d8947d4e48882d7dce28c524a29638c11670a97bbe646d751805109ab9450a894914b3da27b1258e543f9fcb2f301d6d83918fbc26646f2a098935293e91f6709972b4ae95e7d0521a270c8f88c1118b61ee61553e2476670007d0105e7c0cc10ca8b012966537a703e918f5ef56e08f67ceef28744312487d60acc103cd0c2d74a4f5d00e4386c22e4cffcfb1153f57fef09f5ee6f6f9aa691cf2528930aa69ff14c0330c2144698a65a8475cc3e3e6dd5f6f2f1513b30ac09c576ac176d24875a6f879217ee4b9ea3368e6a26129771cb039511930f8fd8d4f8b00424c6132c5430eb9622076cea129923d9a3f8c8aa7964734286bd76f7ef086f9539ce87a51f70044ba4f8630f5f3efcc861ddbe7663c713a8ffd6f8b3d423da90533d7ae7f3c86684298c30c556034e38c5ad1ddae75da2d8d08bbdf9e582cdcb00d42a197e6d3a195ec0829efa6d3e6775eaee44011e2fc30b51c0db744aa4f42be0bb13c2e36578193a253c3e679504b5f99bf72c45bdcf407b23f7a6eb3e5759157c35cdfec48212e1e324d4fdadb1053627a526d3bfe7a1b00d9680406c8bc79d84c8f0de7b9d12254bf68a26785652fe952ce11f7811b492c2bd005e86cf59cd1550f8de2f8edf6f56b73875fafdd661d5007ebf9b70eac6ef374fab08f0fbddc3291bbfdf4f5895e3f73b0aa74cbfdf3fadaaf9fd06e2548ddf6f2aac2ac0efb7154ed1f8fd0e6a958edf6f214ecdf8fdc6c22ad4afccaf017ebf8b3825e3f77b0bab0ef0fb6dd447ad42c0ef3bcb99b06ac7efbb4e02dc09ab52bfef4fdca755aadf6c67347d402406ea986e0600ddcc4c07b560b07f7a00852e48423dd0228469c70748e899010097030b88cb8145657b80edd4fde778e213f6f69fe366466362c8909941a386c9c60deebbefbaeff41cf75cf7e178fe9cbaa51edfc11e1edc76c3650ae06c38f8ea8fa1fb260cdd0bdd67dac7c191ae905ceced9f38d21047d2c2debe0d8ef4626fdfc491b8705a8bbd7d1a9cd684bdfd199cd623c36932380d88d3acb0b71fc36942eced534ecba215692a4e3b6a9afd14a76d619afd04701a51d3ecefe0342c4cb38f004e0b3a00a751619a7d03701a8ad39e30cdbe0e4ee3699afd02709a0ed3ecd7701aab69f673702423a6d92700472a6a9afd0170a42c4cb34fc391849a66dfe3485698663ff35998ee9b1fddbaefb5eef32aaaf29fb2f98c85531ff6535786bd53d6d1c035a3b2cf2e4edd1862222818d57d2ee22dac62b21169e9a98b62b1154e6d152864a9fb1cd4aa5eed6793b11f4e7d994fdddfcf7c3875823e300a36855318104685539c82ba8fb9302b9c6214d49db20f547f92a01f746547381d006e5718f7f1ab6eadba1f3920ae52f705c02951b2243be2b59272f25f82802a442b2927effc55dd88bde5a95f7624031700ee60506644bf7e99bfeac2eea34b85bdec0802b1b70fbdd89badfb31f0d00fbab69fba0058522327009ce3096d673e7ed57e1e9df992874b5402e52752e12f078027d49344f835c2a036bca5f6f3164ec9f77e6d967a402b4a38cdbac9200832cc1da5a139c51fa462a5967acc1fefce9c29d3027d1b7461a67bf6fa73b0e7ede09784c22a29f377df35274171dbbd79a1eeef600fc21d2c58425e0ef6a012edb727318944ca489dc7218128f6e092c642b4da5ee3966c53ecacb4b721087bf07db0870327f8c649e9d73829d873a9c7ecb6090a3f86236c0802b3391db0454ff5ca838f205bece860c362b9a1ab0366acf37bb6f468d85ee7b373fbb0a9a424519bdc5753e7673fb98fa6cecebe01d4993177c4a662d84f200da8185714a7b01d2cc51ffe7c2deab7afec6b08d1ec49af91b89f29ba52dbf38250448aa854f83bb08ab3879f638d2a3c41fd76d41802d51a0889c33ee3fa39853d01aad82a5330aa6c434424a808a7becdc203a7aa4c014b859f837dec00dbab70cb10e8967ef0e4fab774034fee6bc66d1529bc2a97cd39272966393e9b1a2712d8e5c70ac108203a706a76275a6588a644a938326e071e046da907a8cb8328b450616976a61a50d30ae8e755f8455ab52b081fe38a703bc0af82ae1076762842048876972e15b2480c5305160c62270a3b2b25374c3464b418982f75da155eaed546822e2082cf8af44b78044106aed5e97356383e6705c3e7acfe7356dde7ac6cd4982123062d2979f9527712927de94b9d92d2679fb37ae17356317cce8afb9c55ce6abfe376098e33695028fc18388ffb2f5ee07070a82ef4e3d7eb05d71084ad96d1d18b3d7eb1e746157b21817e04b437b0f2bb0df6463e7f16a2ca4fd8e3fe99e63ed4dc7a6ea6769f2938849c88b23daed791f6f34511f5eb27cd8a3db429ec71048a3fcf13a56391cb81e2eb753464145f928b5e6c0dbf47f7865cb61dac2b5c5b7e98f04e8b75459525ead74fbae30fa7bee8457dd5299c621d13d425aef038d9b6137792008e3f3d8e0e081c7ffa3867fc79acd4235e9103290e903838de86e823e827c6ae0738030714d61ebc7eb185838b91cbbc686f606cb57e34aefc71a855710a512ce510c491177bd35de3ab5571e85539c630047d810138e24fabbce8856e9b669be6f923117b1363b0354540559401a4e1ffbf71e3e78badeee2cfd630571b54e8e745ce434d4ae06abbcf5463cb8615f4e3d76b6f56d59449bffad5a4b2084e1d10a71702ae4ea7ee147f57a7e7f71e5a63c842e3891b628397ba5700fdd41d88e080dee06c70afa5062763050adff4847ef107a855fcc5d2f03387e1aa3c82ca1f9b88ca40ec12e257a603fa404c7810d47120f843839bc1411e2450f832fc32f8618802ece1600c0e965894870d0aabc2180eba60100f7de1e050d3f0938ce0a645e8064e7ca2f84cf9f9b9a1c42d6cd2a567e54da0f09d09744b3574750224799df0b2220bc756dc012ae271c25ed429a27eb149e579e23d3d0ed4aaf8b38329b6ecac3c0bede2a7f26b6fe4de7021d2c21e3fd1162f2aa2c21ebbab7320f6f86100837eb1058de20bbef6a62bbfd6d21b4211da424bebb057a942d4abf8d2db8be051855cd8732bb1ee1536d0c2a499e5452d9d56b39af0ce4ee52dd5f0428d3f4ba05ffca9b0f4a3eb27822abfc4ed4aeb0d08fc5ec8ced5bb6fc9ae366ea32085240cad346e57923302dfdf091d25a854e9c9520f50d7950517d47e6d0d7f3cc5893dbf38152b3f1b716a5d5c742a3f1f754f4761d5328188b9706aab58b1c3af2fbaa2d87eb814a17e3d31e9409cfa62cbc5a9af5f419cfa9c4788535ffca95438c52d22eae7565af5f32b7588fae1a89951fca92cb457e800063552910cd4048d2d4e9dd8c55666165cb17b5d81414cd08f836ef07eed4dec4e34c81a3f762cb435fc469c622c5916faf5e3d756f78a185851bf6671166e827e3cc429ef4e4060779b0d481c13715116e8a6020ae58d1c8c5346da6e24712721a49c95c69d84684a66d3f86b9fb3da2d66d98bdd89867571a1739240af4e12e0df7eeb44407afe5dc1f7d7be243c597e7c589add0f9b1a5535beaac6c7691cc66123d9c7245cb3eef4237ee7035723f2d7c5854e95aa2ab97515fd548ddb1589db55e4785055f83be39299075a16148abf8683e3005665f2d7abf3f73f9b39290d92714998a6794801dd69c2284545285e90b781df6462ef701887530b21dc66d80961af83b0d75c15d42bbf0338f590fd0b66be2144bd3237f110324326eaee76ef6e6feff676ffa1113a8311c3d6ece7d4ad5b82e1051ca71b364c3568cc9091112386c2bc94368d944d4c466f2ef5e330dd6e7709cc61662d1af42c28bfc6dceefe12bb6ef729e95e62a0eda6f87d57d5fcb0dbdd87bb8421057cd7fbb77b994de0b367edc7aa91e6947feeee1b77abdd95800855a0dfd736cd78393835e4dffe93bdc781df886fecf57774c83db3ee7cf8717a924ed215be0b216ef799e0cf55650f29cc370db71d5f76303eec9c3d538c5d0f5e8f982a7c29c828a57c9798c4a4d7d825e10adfa9e0eef0c6468feefe53da9003ab57ffe07fcc70c297330ba1186394d284ea41a1e6464d419a97c327cfdea29412c3b028636904d24b3df8796e14a3a2273e0c2207b1d6374a29a59432c618db05ebf38aefdec88f9a8a42a171dc7a4bda6c6757d9fdd6c4e84fe4dd87d8b418639c73d2dea22d6fee5bd45bd1dca84de5b701428c5146982e3c89b076be3531e70a4e93f4113e3d57e201f17be80453ba54434ef59fd8b70f5cb1d7e6d6a51fde0cfb65ce0e4957ac93bda51b62e745c02eda421e0da1e47c602863e46f1fb8eb41c687ab23fe46eeea48baca6e3958614bc84c76aa9c1897b1883c077bf23df6e4a3d893b5853a28e3fa0af659d7575a8853d8cbff30ef6d4e5658eaf14215146ba12afda769a4730b63b0873fadea16c6e5c022b928afe47bd1ded474517d8b2a3f3641950febe90822dfb4e662d5b00de36690251ab0da8f7158d5b00dfbaef2fb8a8c51e5f79133d991df58a4785265073db3a4b0523fcf1b6b71ea93d9e1d487c3d4c33a9ce256932ad949953cf5c381956ee00f6c6a7bf91ed814cccb1712c406f940ac0afe348d7cf9436a7c3c173c13a623a809aafc60104172bda85236e99d22ec658dfa75508d26823c7a0ad4217f4aee2754f9f0a755dc6a9ad27f35de53f9301bc72d2c3d05f2682c56877ce923a8e93ecfa8fe162d1f05c2d6c82aaaecda4efab1871c0631d8b0bbe6614f76cb3aecc91df65ea874d0d6cc981c82f069177bf25fba06624f9686a0f2fb49fbc497dfadeca589ca2b4c2325ed1a4ba95bf6b6eeeb2c59117bab7133b0275fcb36c8e1d0c05867f30215f49ba9f2bf47710a56f95e0e4ec927717d853d99752dc49efcd975107bf284f51556f50f8bc5626225bf8338b5a9ead3c57a5bbf1a5a6896a5d95a7db02ccdf6c04ed54bd9631c576d7138e36417f49bdf030a7d28ad5fd1ee84fdae5887694ed8c38a75dc624f7e2605fd2ab520bf0eaaf26515f49b55be7c29492cc384b633d88a9df268ad2eedbaf3273d73c23d8a5b76ca9a7f383585819808bef0c354b054c873a542d65261f6ed9287531fb37a38f5b98ff4e1d4d747d2c9d17ef2898c52e1cf1406ba822ea74eeb59b765ddc47100a74a0fd9461e65599665fd5fd72c6866d9c3ecb3eca83a15fea1428ae591b1a00e9f2fc78c8f8d3ce29e8c057970142d9bd977f771fdf88759d079380ae4e14fa08eae87ab951fcc873d1e7e88e91cc9a3c95d0f70739e5651da499ea6c93a2ddb6612f4c37c6a14f6a0ff34cd13c8c3a5581df0a14f2ab606fec7f5f327153e73e73eb16e95574ffde4d12ce2d409e661253dfc8cfbdc0763b52a16613e58cf9be6cb2dc097612c0fd904eae0c2a923188eca14b54af2b46c0279482b5007fc37519e58d4a4d2d9c9a31eb3339966117b5b785a25839a069aa8d4acfbb028f06510d67d33ebac3319f452e567cfcccc2c5bf667533b6bc9035f5a813ca411d4c163a5c2ee301ff68eb6868b34823c6418ab630b5b035f4aa30a7f4615d4ffcb58af0a3356c6246bcd17a78c38d5c3fc24b36a0f7f1e712afb197984b57290fe93473d24a195df3bcf9342f2cad6609143328b0cc2ea80cf9c1cc2d640199471415f062d2adb81611b58689a0679eb66e6c85c12f443d5c9d0e173fd6085023b254b564b20f30d7c23d710429844728c2ee690c400a94020f67aa83affb7aa16ba8999b3ebc8f5bc5a2d14b95b684b0f35d1164e9dd61b4b6751a27ead056ed0d52ad872d5102bb4b235fe730594e10b06cd49a9c9e4cd23e88732d5ee866c8d7f0bb52a87a5f1f77642757f8d3179c56f5467c2aa77412c9cda2a5e04d50f6af988a840e425561550503fc8059311eef070cabf5d9c3aed7812f5834eaa0f55c7e16f62820e6995d734fede3494aae7a2f07fa65f9b09b49dd1b64077c3381a8333f22396ce561172e2a8a8bd198891a8f061aaf93b46977ac4d0b46d7ef940302b2c6931629adbd8830e85edb319323eb26464c9c822bda67dd6efdda576271f5bfd6bd8eadd8e2ffce7e7541c7e79e75e301b0f6774321d5c1a9c263dd6652f349094d841939d1da8e0ca125cb0cdd9bd5b317607444f44396a96cb0a52b297eb78f38f886d1a473f868342e349dd897f57349e76277e58e56fede7a0a02f5d13753dd459d8831f040dae7598a69535a15b321b77f2df1a3f5691385ec8c274c594202afbe7f0bc2a8a2a77a82aa8869580f0b0002654bad30f9727e8a9fe393db43139b05eec6dd53458f267ce077892c00b2780a2490f6b45e3a1d4f8053071558e46b784c6d378cd937005b20026aedabf0026aeda1d9425343a29fd52fc736870529a8b559bdb0cecd9b007677085aaf5e00ea70afd96b469dc565802224fd082dacf251c505cb06a7748d0fdaf5b9bc5e4c072c11765cfa120027af9b0a1499090201042f86a5513f58b6a41e1179b65b0772835a1f8053db65a2d085403048240104b8b080241a01a5d340d7914424643746832b443dfd0465be58ba37ac4a953bf026a8cc161f8f810b9b6e8d76b6f4e2ff4809a2a4c85df437bc3117161556fb12a08e44d2a7426b5bfa85f85232a08b151e6ddea8ceec0f8f30e4c0627c43a974fec7cfae19bbe7e5261d7722d68276169e067f5eb2157510f6d0d7c1c9e4103fa75ab5bada2f1f0db8855d5781853bf6ef5f7cede34511b310d7c58a36b2d760c7ddd1afaba15d3aeee83810f879e40bf6e56d011a8c0840aeb0c20aac1284ee2c62eb81971a9d1680b976ef08d4b409c61bf7c4aaad635769f076af3cbd51f0cfc276cc43615a029157e4701721e226a17bfac20d42e3ea27edde48b55f1503fec9d21232afc9e25885c0bfaf5d0107c085953a0c154bbe3d7d6c077223aeb56b9024f6d22f6e07f5ce3b724015fd590a93dd4aa1a73526a32fd7b5eb76874ddea1935a05a7f01f4858f0637025441ab58a155bd76516bab8071d45b6a0c81c2d20ddc8d6d5d02d23380e82cf558538cbf476e87726009a5f6b3528fd6e1428d5c8c0a4b3ff6bfb81bf483a31ab95861e986fdd8ed7a5e7b873dd82df6206b68ab7c81a5c25ac261bf3b243570edffe1b597d4f68d39fa16fd50d1e18fde2ff8e2d409b6a08e0a9af00e4c41d8f3b335f0fbd43dc4a958e1779626f2963761157ce26a2d9cda2a5db02afc36fa9ce53a15cae8dddae1d4077f2a7c58e448ebc06e52e1cf2c40e8f0676fbabfbb1fb6063e57f8700aa79430810f59ec4116e4610ffe0b3ef48bac0a3fb25a059df4ace0c756dc8952a114ab2f36893d3398013f803e82ec8dac39a0cd5718c0a0ce0c83f6861f3ebcc2a97df804d81af87c621f9cea0adf06f8413875a3c2ff01326115f41112c2a9adc245940ab554b85c2ae48fde28f8397038f511000671ea94e2d60c15ae950ab9637803e7e0d4c9042b8ebd81b50a196d3f75dfb4022a43ab0200b323e8fcd951c685c422f990a6b0caa687c464a82ea949dd8fd4438a5217be738a531ff422b238f57193d3dcdcc888fa3997ba91c984dd476ad5a5375841492d4ef1d4ec8853cc5c482c66c9bc8cd88347ecedcbb05d3c3172ad92a0ff302441bdb731711fac375ef317cddffcab5537bf34de0de7b9ab69b8de781cb6e118688f6cd8f8ccc6bb8d8fcf47a7c7f1f088536e63834279debfc9c4dd112cd530bfbbcf6117b926f4c3a2d47dd3e9e38fbfbef9d1cff4fde7d9645bd85ba34affc37e6a76d4d5a031a3fb4c2fd37defc9e83e0f15a3cb7c62bacf86494033a0de65af0c003a14fe9715d57d2c0aa7e00d6e2b89fb3ca8eed313f7ad95e73e3efad68a0d1a6607b940f11e87e1ab55cc3c5e0c4ad02f3b621ee8a2d014bbece8e326fbabf116006e573e30c110786ab4a005364db30f03125ef72686ffe6c2f01fdd176c1efbf5d77a93813b09f1f6a80a32385a799fb3dad21d11b023051d1e2c75ffe38fb7442f728483ea26e9ee7878310fc31d51b8c2cc5354c4a9d3101d38d126caf8691f3633146193cdc6c0c23b2f226e93a90f6fcae8db05fd3628898fe5d2926a320618b36930193d749bbbbb79316e3eca6b150f41a8cfce07c6f14a060a283f377bd9cb975363f8e285080aff232a0ac2362818803ad355e66e69fc913932fb47766f006b414a2e5b08b9bb7f6fe1eededdc61d68ee1ee72faf296851281b5e158a72499886872a502f6d51108c319cc561140ac53242deeef80298232d998f6188a828089c1886cd396d5c41a8aeeca93999c79cdd3d7fd99bf3677f4fd39c1deca67ceadd67a23d9f605be4a62ab05ed32c033090753f96a6bf3faebe593706d45ffeb7434578f801b2b6ca16a6d4fe21adca59f5ac3a4ca8d72fc9f3421e2fac4a92ac853c26abee7329763fe277334da8425b06d21a5fcec7e128a1b47e356798a6df25778a0feb16e1d82113185a0f4966c57ee1ea805ca3f7472cc6f81873d862bb3bb1b918ff7cf66214babfdf8500eababa7083ba6dc951d7d545a7d22c51c618e30fa2b020fe0c630140a2928ac05cdc0750d7d58527d497baae30ac34c32e30c182164840794161042618c8034b851e72aad0b245123b34d882cb166ae081ed2183c15a931ee7cb462aedd30d81173e3a7861d1d30323aaf0000d586083214461052bf0a4000c29fead28d2c41456149e8085a585a7987eae94d27fd0050c301da7262a432915b02c91456271052be850da052ba84c951c0c5552dd2a2e29ea47a1cc00084940c2105d24618515fcacf81f85c222830758698214a0d0a0084f5c8941fd4c0c797a6044142bb4c0e4f50fc42a28383822092944a1042ef8c7f46f031858e0678b1457086309415491c50b725084235eb005125842f8008b140844c10509b478a9c0083ac8224a9421a8c064052c7801152014094e4c38a009275842093818421255a860c21646208494524a29603efe1447909ad0993e9b18429e40081a26a8e28a294498011458780a200d8b2634a1e352d4efb5d0821cb55d5a5842d69a257091832368e1043d2862d5388c9deabac2e84295755d611ccd1bf8a854b0042030907022075380d2802d88a2f841124f68190285ca228a1ca86c3982082bb610644416cf62b148f099989ffedb000a24559ad0320250115c6042164f88f84901128ae0420411522c9607fc60092ac842062e2eac7041e5a75f8427b60a472062a2488a249a5471e2e708294d3401b40510b0f4e4e04089297494782541c78a19c4ced4b680051e37f104284d749c3cf1411195d5ee20082c28a83882111257acf8b7c4a88ae5ab2b8c292ada335b80bb40a89f8c8e23258e6a503d0c1254f8cd50fd7795850549e86942ca162656fe9453eca4faa338e5bf83532fd5dfc60141f6065688048450b52be99cca51631735da38827ef4a12b0b46a834aa3f34716ab759c56f9dad12418c1bc4bf83b14e8a11f8fdb25392b392bfa520b23bed6a36cd0ef933d511e4c386203f4026426c56cb16ed6bb978a71835838d0e14ff0eeeee6462e61d6c89ddc19c3333c78da61a5136ecb54692dc45d86bced1348d0477b0581a89599cfab6cb163169e7540ff293cc9ab5e64ac6afb79261b392f15f8d19646c1a89967e3f07ccc7bc36a4fecec29d1dffbc36fd98d2cb520fda49622d7d978080a04b972e433576473ccac1ca9da53217a4f4d2750f51c372f6c6bd1669950d4cd3df3ed8b381038fc4b3714170d89afe1b56c79459263bc8ca02876a7f7f6bdf0f593e3f7b8d29cbc77ce85a9adfac5a0972562188f19a73d833a77d89db3824b08b977a68a46f9246fa56890008192f045cc950c9e84e41627c8c4e095c8940c6c7f89c553faa39c80daba37f72386c8db63725ac8b9c0d5bd36d037b1d3b0f870e6850ef7c49b8fa3369776ee0514046272546b7a52031bad3aefabbd44352c8da9ae6536cd2ae2bfdd0497774f611c48686acbdd9da5880426d1c4c38d8eb2cec7cb0c7ff896098860b34fa3bb393a47d64f833c31e435485306633750c8704da4882d6a8ebc2821335d62d4152851c3fbc02856010e5e01574296dda7b85091a407168b66da651ce14d331313486b6c9b4850d7605dcc10a0ae2d407afa87c8581f415f0676e948353ba586db8a810540254c9ec4d12f686fefef73764fc500c380489d81a7e22b008085ba43eaaddb75da01c0cda1a6ef8a7de2c5b8b534df8a196161f5566ee85bc44508761e987d7207ecac54a847f074e1580e115508769b8053b4839ae366e403f1311e80787b4186159960cfbd47cc6985583f636ad8af1a55f2bf205e6b71352408506a87088a6428f63e8da80705543fbfcdbb4fba829497ce1bef764fc6743e6bf18d58cffb25a7ab82a1a5f2a3d8d199c0c27835b4e3ee44affc221698097b8e592b07fc376137bd0bd189d088088f142c0558c183f572d444a975363e4ac62fc6c1ab601fdca8d031aa3c7c6178ed5102b7bf7ecb9e7413813c01ac3e210167b081288e8e0f13b76dddd9d65bf599665998c09676bfcb353961539e2cf03a7369e04fc246152dd1d8baec7c14cd46fdb1b2ed62bdca0faebc0a902547fcd27b0375185f21d5be35e6de480fa0f69d59c1b58ff22fb45f50dc3d4aa97edfd5d1301102faf9562d7dc02125053240075863f1b185c5591a629e2382de793beaf3375b351b798ba65757bb8aa18bf6d1f83dba5217d0c4782e14893f4cd6d5fe2e6f7c675fc2dcde79ecf73766d92b942bfc8114fd235fea08453f3b90577f885dba669f662077f484c89b4dfd83372423a65339048ef836866a872a551f9718208e941e5d78155db05178bc51ac1ba8c822a3f2acb814525f56d91f30a2a2f1121b74d235292341287636b60eb57da2fabb49fbf714a3edc39c334d98e2f28c923751e7b9c7133f3833a7eb9124e4132d2d3b9fbd8cc3aec5b7345923135daccd5fc96cd2afba1f6cb55863d4e3787c4541b07ac7a00a63a56bb33e2b4e58300f34e02dda50bf74bec1b5bb9ecdc5aa0824c38bc1c54a0eb7a15d5cff41aaadf34e1f0729c82ccdf2a84592bed69abb2e79f1a6ccdd57c12c744089bd5fcd65c69118ad691709a94713cd4f8b9bbbb8c08a85b02b253a8db4139f92b59d70a04f34190a5dc9bfffdbb20d97b17815d651d4b8e34d0b65535bad082b5da1b1ac4a11f6a6dd29a33e2b4d4cfdce9477c7eee3ed819e13f75e7fd3935a7f6e9c7e7d4e6706023fdd388bf372cf1b78c43ee02ef6abbcb3c789779975b08389f5f6bcd5586ed66dd94dcf743ed561a49c2d565d4947b330fde8575ee4299b3dba6e95d02ed82aecb8ba2fa4d2f788884306b158d78d1f28275f28f8fbde44eded1b08a5dcb8651edf9afe263bf2dd32a621f39b92dba8a0f4b3460753b1ffacbea72adb98abf252002a8db4101417c1cf6eed285b74b975594400356b1dba659f6b6b9abdc605b9bbc49595e02b72ea6c60ed4fe41a31663cf6de376353921ae7570131081f0ea2708947efb21a56efb21fb3bb650247b94fd16d59c55ce8a3d761fd43826da538ec9f6dc0e3028923daa497ad70092869cdabb8ace958020a062afc94deb6f58fda7c64969d9dddd5e7ac93a1fb8627362327af747d78eb6ad9282fd6e15e209705f2190c562b1e0d27856fd03952727c5dddd7d484b0a129b6a2449d7f95ecd59fde84702bb544e7ec631c95e0829486c6a9236407ce64a35e464dcaebc6309888c2a716858fbb18c93e20363dd3de3cbec7c607f8c1d092bda72ca35081debe07a1108efef770e562d629c0f2cb1f7d681d6bdb1c7ee8ddda8f2b1caed33f6e9c7c23af906d8a57a27a10efeb81a81ff4988c9ae86af2cf706f26824904beddaa51fdeddd6f0c76f23b1ab61ab07b1da7a80eed009ac5446d25db854f88ca4bb7479e903c3ae078c75c0eef3973fbccf5f7e977050e2f18708620948a56fa8442c98ca9a5432442410000000026315000020140a068482f1804cd223dd0714000c7eac486a4a170985610e04318a216388210400001000011019a26d003f377ab5bb613e6773c7b7ad172515035068d34b4714ff6b36f4da4ef25b18a866ebec8c5ed5ae3710351334a1fa1a35d8d8b174c1cb07a3cddcf370caa0a4f18ca71e5e99be454e27a5bbd3b4d9de8b10fe01d9231f0e709c8260969bde5826edd42652b55b6554c5c67c4d7150b093502931c43656081818b8e4e561203835855b6ba3580ab49429c045171e75af299afe39e2b17855625631fe2efcfd492ec320efbd472ac454ac0d48a413ca6d17ac683863bc003292ece4136a8965134b23c12d9811a31c2595432145f489df68ed5a99f95d0190fd1c40642dfdc45792c4d0df4a89242691056950718080448f40d6cd63c0ea76833a10ac5466b185d102a1a4e7355839cb5ebea1d23bd392a94185e4cefe1e6720f9f29400cd831ec228dea517aaa5eaa7fd2751af49dde8252993126f0f9581b70a8d136fe121d0b971d95314e17ec59010fac9bb7099e2554d4da0ea396844a78753a088b156a13c35b0eccd07f5e52c5748f32479dc65943ed57b9e024b7540fa8ab3b360f07ad38f0b4be0d40889f2839aa56db96b347e05a1a45117722763bb0b7ee74d12c4ae40f590b045b8f6bde26caaf3b7f07379cd3891ed63cd382985e53184e3302c1af07ae6302ccb3d0162060a79104d50032e374709d3b2352aa940126a124d79d880190b85fad77cbe4de686e74b00e1da5deb53274074d1fa9f4eff58fe56dfe05ed1b5a4a9dc50998fa3b8bc46dca3da9ac6d4d5e4878a5ce0b424aedccf5ed6175071495bab00615c6cdcc45b2bc936a77e46aec17f6a6741608de59c18565aaddf8f9a98592c3e6b9420226b4510ed8b4a4a44a2c55822d87c10f7293f21a2a941620984b42b51d2a423d1325f18d98a3fb7ecfd3cf49b48e4b259d678e7efbc6f2b21d87ba3151f0b7b8d3b16a8fb5bd8a3de6f575b6f462d367ddb2d0d6d04dfa37731b791b020fce10a45e8bb872140ff05f1fe61eda5fdf7a7cab18fe24cfcadb5fd4971c8d79274cd9c019e3c3c79af8af682a2ea5bc6855f9267ffff6699908005ebd5250ce40f72aa81d73cf979c417a32fa676b7e582309174706aa7ba44be174219de7c7521a5fa8e07609ddd62350b69e245fcb8f7506f9c9f803b330f2be207b12576284b970715b935c7925b216a1fc0e73f6124472a400b158805e52178a5c404a1d65c916f563415684f90bf0fa7309fe49d7c9eba8e64cb5b9784778059d16543dd218e5fd94730d675b81bfe103fef107f1bf9ae58375e39248ecf4625eca9d31ecbc31dff20b894cbdf73e49911aebcb04e34fa05dd270cf385476397d9e7c91acb81c6fc04bfac104d93f05f0f053c667b47348c0523852215fd47c6913073ddc5efb8378a65e143b861c687e438d9cccb30a89f7072af4b2aa5199301ff23dba48c45f6ba662b9a912fbd49533e14de2da404e36389f052a431bf84983ee5c586e2c73345554642e1ca39a4d0220162bd7e23a0b0e6cf0b1f051b8b9c8c9c11da9a91742d3b660994fc5a78fc09fdf2354dc9570ca1f3a798d40b861bc03d4916845d32063d460b76ec4e5674535bb53b1631522eb46873e13419d20412c24cd02ea3b0be9780bb0dd7ad4a4146a9cf9bea45012b4c3d643de2613dab88ed250903698938a8acc6c28cd685da380aef9d36c0f5a0c1815936a268d3e41957e15804be307f9ab066f5d8ef7701c57a22b946c337bfc63cabd55d7e2f1fab31951d9d8aefaa2cc918f74e3519dfba80947184987f5f22e05321df6a8ba96ba6edbaed4521888cb65a410f893c97482b2ed783d89a103583856d1e6108849072e3036ef39f6e76fe4d3a4b6111fa7badec21f45caf9efc9fb8331219df48783a862be17c2466eba2d3679f8e1fa728e84812dcdeb1c4e0d023e7f32e1f0768fbabe1fc896184363d9f314006a945ff67042a23813cc6b53ffaaafeb0f26480e5458c774b0a493fc8f8ead0774708975ca0d9a4069fb21354fc8037cb3c8849c2fa42c7157b89286b2da1f412556e66475199af4e450d6169a8cd68e07a65ffdd4c2857be10f02df592be35e5275857609e02980a7cf32b77ef9dfee5e6ebc6e5c675d3edc675d3f5e6f5e675c3fdc6cd6eb8ff19b99fbff927c4c5f109072ff7473bdd16df1df5eb8de6a5ac0df4edb08ca5579d79dda5ffe12a1d1018cf8bbe172168e0a7bbed5e2abc6d54fe26a8529266ba3c2477e67a6045d4e2ab5ef98173209562a4e71023df1a619548448218499e589ed90975f1c6d5aa394337c2e32f214e547d71aeeef764a028aae6798cce850b3f839ccee4ec37a4be16eb11e22033e67587b863675e130318f1d1602a414f824075f8357b151b85f110723038cb02bd8b462c65d0621e7993f4635267c8ebc5068d3df1cbb41e9b848967f232ed17db6dec092ff3151bc2658e3dfad7841f8a1e56c5e60c4d19dc88b4054a00ece8b87ac0793ebc1000eec281cf8b27615aa01f25e3afd0b8f7ce03c7ef8dd00898ad28e62dcdb11b408e27de71f3321341d9a8deb9f2f2db0471ca006148ec19054ee8302e35d9378e072dfcfd44e840dc61fa2550b52af66a96937c8a8cc5fe737f43c1a2f6591404c2729e645b9487e70efa7ebd05be6287de044badf7666c4ed4feb51193f42bb9613f79e293a96b204aa383eb9f941bed3347eef13eb25af4434358111b3096092ed31eeb06334504a4edf4dbe4ba59e782fc56495355beb7c5d6d9440b7a6e9489855ecce444c882fbbde221ce00c275921c6f93d8ef43ae1d47815c49201edf2aa880b29edd166c1748b11354ac7b365c6a14749454893af162fa8b78e3ab25bbca0ad69dcf3c3ab34d3a48ae6b4bf76e97a6a982c4b8449ac10c405c6cf077dd54940365253560b7ee4c4b4475403c65d28c2c759c53d7097235521fe5f5a8e9429965a43a6f548f83ca70427f05c3a35bed4bf1b4a95da7265b2513beee10942f2ca95c8daa5c073e6030fcbb60f5cff4cb003d2df06405e2d2655def0b0a1a7243423c409eeeb5d22a0f4cd2714d87459b6095b510db21feb7b7b4715c9b615d43bfa65f89bf941b27c80b1f41b48611daa2b11bda36c5bf3224ed2a4ceb1a377d3129b3af4a492815c298e734fd96cd5c056a1ccdb93527c65e4d47f11ac058d550140ca90eb39517638fddaace56822f30768faae5237e1f55c3d08f3a6321bcb90f8a599caf72c97404484e985f8f28e3c813507c10d42ba427ab83cdb76e86856260ed86b23e5a13cfe74041450f6013d516cb0aea6afa82aae6d1ecd1cfaad36d8a85acf24419c50e63da3ec8ee934cc4dd000e177a4df31d369901ab7670dfbd494b27a16dc6c8e8d77f585cfbf0c45bc9c8dc86a4584bbc24c1385289310d0e0cad3efd8a1a862d8b06074838b6a9b52dab0827a2cb60646f16cb4323da13615342c9cbfc866646e513f53a5b4101d92eb63cc811e198a2b76b99d21454e32f02d8a06c56446e4f90783f0d72ad3ca1f7eeb8e6ccfc8361495f79a53e1077d316e7dd8758c5d582199002e666a8a5e5c64aec2e37384615abe9c52a8eb4e915455144c7949fa9b7a7e69d81b275aef28d32a69e4cd4bc11e196cbeec649eb0838cad87536708627419d5799538c0924276af4e10eb867dd6448fcece16ec464d78856b2b40a8b2cb5745685d2544bad13666b4230d19cb5ab9ce9bfabd2a3e7f6df82b02e744bd312434e1c26c68a79f5a8d9c56eeac28360985291f9b020d4a4f30fe1c1da0668eee333f91d4208996271b57e39e7ac239b24d92f61561ee335007e08b3ffb5be396983ea0ed3dfc4f24cf8d3cf8a8f4a662870e057ce0924d031c1ba98bdbbca5dbdb0478352e6478089d96adfb243cd5adb2af412ffa8e85a7cc60d3bf0ed0fed6ebb638efa405bb183551b35f27da1abf6d5864845270eb8c284e1510f8cd8a8212aa13780eca6db6a02a406ccb9c8b7322c2779828efa72238792930b88594e3f7b14bde0bb007708869dc115327065423b7671e35380a8476a70b0c4ac28dc9ae62469a98a29aa88658c1f1fb472ea88de83eee85c72b295165d8922a560e249541274f441cea02cb6b885664052e1022fe209759114609260e06bbe326c2e73e725dc281136b5cf0417abfb3451ae9d83dd52657041ad6e7f70efc4b97ca602623581181f991d06e3e3fa25803a78d12acef6caaf1607127900b7225dc81e81cbf566ab1d005735278e026e041f3d10613981aef008fc75bc277bf243cb94f7af030530431d817a0bf7132e252fb3b6c64c6b5ad53b29290a38096823f22a23961fa0289add9851277f335edb08e90ce950575c2178dff053d927456ddb8c2ef58e68148685b7c4a07a1e429f28a71df96ab8b68d294411dd33ff1137600ab2399b1a2b9a9901fbccaa97fe886263dd8b4ddc7115220416d52dae0c3c04f9388ecca5e76ad5c84d09f60cade4ef45443733f0bcc55e1cf9609b4ac71636e7afcd59e03c36a9e2fe72e2de3108d2f1b75e8556ccf2218a9739721968add49eade57e3ebc15328cd1332c0f0c0e3de997b853c3a6b134da6865fdc8867a7186017dd812a1cb9026f321e49c27f409b26906afb980c5d95ad2042394bb1db3a7a094b4ff2eab417998f6eb7ebbe7bae9369b7521b6c87cd9a666c302ba0e4deea8a6904a0fb8a9b8f920420871230a3a4eb5e4a9282855b3b15f7543117832439fc1e7aaa3166c2bc45db3877a3b80569c606268bf6327093146a0e035134298f03c8edd736412c2ddfea52cb484618163e0b47848662586d1f4e19a6d17da0a561a68af1cf04c62c8760cce06f41f09ea0496fc186c4512cb54d265344864f81c47d58c043438485835d31b1526da80b46a3bda075df8616580db1c5eb11d701de4f37e86a8f11eb472ce4ccedb8884f038e617ccc486b6b4b34d7f39670a0ec2a013ec0b99aee1c2d473e2b1922d5b8deeb2cb9b359ab4ffd1293e64702c53a927288d6ccf579387a48622df94ad0e4540b3c0a4b5007de1ddb3d64caa4bc2bd52c75c9f901a973a0a41850961e52616f6aa27e4cf08c205b20704fa84588c891be84226479e1ac6411715d6dac3c00855e37e8633963f0fc291608a95132c856caa8b9b5a6ec7d62a15ac6fb4fea54299475597523ae48917c093ca6ed24d70138c4e89a67e8df9bcc7b7998fcf8f6c32a4f1d5baeeb2ac984c382b70d3d1c0a154c5dc28d60c2adbc70a01881e7d67c980f2e336c783399058070b2209bd4afd09e929226780fd5baa2ef7ff464031b05824a8ac42e889e85d9e6212732f4c3f30d3d8701ab37ad231a655a1ae023728a303557d5e6e98b68bd7da95b7b15e9ffb6b4de2daade39c9abcbc619885ece1414cb595c3bb9132b41e142c87403adfa706f0daef98af834507c9c0628962ab657a01d6e07e5aa1365d2e248eb7cf47b0a5cb42cc2b3c32f91000102ed721b51bf1602e7cd3d5a4f6606c96b6f3aa1745aa0b1a8ec0285e46f0a01d91c846d82b83100475fa9ed12250e13641b51128e5a69e7e123fea07aeeadb0d1627483797071053b81628c8bb42d0f5bc20cba0c0df9e844854704a116c3714381c7478f64e1daf2838d8fe7fcbb629cbeb2c21c3e060d1d39baf60068327c9284575ec96a8401c4791cc48ca3df2c1a1b421645ace5c1f66139222dce0adad47cea0174ecc3a374aa3d1f2b5e3e8b48748d85e3a940d7352a805bd7c9591e549b6a6330f31d851531a3f2780ea1729b0bb145e920610369e00446da2a428912e1b8509118fb39029bde68cf02093b047533bdf8d114c2ccb956ea7a1b90b6d63684be84d37273b9f06e65553e8ef6db3d2bcdb4e2bfc126edbb54aba3015dbc3e4227b96648af98671ed6d286f375d6dce40cccd60d6b2c34f910ba84dd22aae682a23a4676b7e34f3f9e0f347301ff35be4b1808765246aee8d0193030570510c2d16471e8620985b52f82282c0422a2d603a3d387599e1c247324cd74b6ddc7ca1b1ecb890a1c857454728a2b0474d72a7bb5163fa5bce701076dda943d32e8bea413af0e13253de6aa3fe9ec13016f30b828bf3be7fbcffdf3b36ef11b2a2bf4c8f95fa36aec40f96d5ee2085578b8008530dc2dfe6fa573f21702dcb2686a64fef40160e8738c1810b2ad7c298d37eb0a3953985f0af10e08d9ae67041d50d800c38028a0e38c7b9aad57b24752a67db9f771b5a959570e2efc39c8f900a6290d66f26cab4d3bb540d069ddb31bbb7e96d10cadf7dfd9337b95f65e006f0dbc8f732293c510cb8530fcd93e15e81564ea390a80ac2876b75f24e3ffbc217dd7d05d35bdaa11bc619e7734082034c7c485d2d4041b9610eb221aed05182242cde059461c3c0b818d4a0c03c83bbf864ddfd745f9a937ed2936394287350b4ccac57c6601b65ecfdf0a81c70e6e285dbfa38b2c47654621f002f12c1b33a5d4e23b45da467eeb6578f86f6840cda781b221c3f18b97ee9a2dcfef2a9c7fe16ede06004d067436990090c2585ececc6ee86964e02dec5de68eb3c64cbd016e25746bc18c491188c1d2c7fa4115bb05d1c8285ca51bf7090fe68ac50090f3a140ae70d88afd2d085b8649d146923f1facaa537badbed807b80d662ba08b0b728dd9f2e1193419b1a45bc6bcbd2418c07ae1b037c0b225487c7db855d19d05c4dcbd3537765cba5410df7b2fd08e030a4ac17b207bbd4cb9dfd6baf0e3d31db8918784055bbc3bf077316ea1dbabf74c831e5f7518556550d3e372ac08b28d3082ac26f5b145face5a8253143239e8c214cee939005d1eaf77063aa72b7e1bb6f656eefa5ab54d451f953ed61f788461d8cd50279074d341d1691668feb7ca4858e9d6c907e4e2401c4dc29077416014ac9f1345935b8b59beea17ea7d924471b9efd8c03971ae0d9bd40c663029954e9068f3974208b688caac6b1e90db97312e01d8d8cb97073cce05fcf85ac5fcbe831613a58299f07d9869d42bb3c84f0f448a1d9ea9850a457d590353d8244d423ea95e07fb494530d5ffd538fa03b85484d939dc0d59238cbc4726f7139d3ab699bd6a0aa99ba728aa1226a4235e6c09f038e7d3d3d28ca16ce021a2fbfd85c90e66281bcd2124808e92d5f1f9d46baa3577d93e58ebda38671baa9a212651f8bfc72b61bfc85b80da9f4d6aed850fbe6512c3b1740236bac56463a497df357cace294ae31493ccf5915e9d6313048da035c809e8285a23b711052196a82eca830585de3fc564e83cc0a965dc1b25cc0432e1db47f6d89c0969505151d5bfdfaf4cf02382dec9ea91e409b6411a26b5940a1bafc3da0c14b974a5ae1ba534a5afd108e6341f8f4f86bbabd07300007223fefeef8543fbc207342018e957b88802d6ea17b398ac4cb5a05133f6eab839537f6b0c89133f3a114aecab88bb776e409612419f09dea656c16b5f4125a3cca32a2c0938707e3663d6b8b2c0a982598d9b4c8b30ac2fcf5a71f858c0f121e871a09f8b2b259ddb6219cee464432e93b2143d405bbb5f30dabfb20260b2bad0be4cd45edd8979dc1905b57f50652ccb16ebf50ce68ccacd66f35816138e06406d5a45cfd67d1ff2251c29fb378f11410dcfa5d39886f3d6d2e13838bbbec5936ad912c562945812d4a02926e50fe89ccc29001572fdd5a0bc87c1213e829ac54a8bd5e39294e71e696941c1678698f5c9eb4bbd0c5b3dd834b54bd629eba7d45808c983b1f6556570f3d2ddde0860306105f3ef18b52632fce3f7d8511cd4d129180758890113d29c1f46e3910a1b7fffc8478a7e15a0c8a86510861614af5a86c66240533f6e2f5e79a997bbc05bb70fe5c69bc5c0dbb1242b2018f684a596e6928496b605db8962f91ad2748132247db5855f27989e869b4781c37df347cef335cd61ce99393e9c78dbde19e55099cd48251574931e615d6af68fb1830e742b6141c7347d0fe89f03866d4665cec8e188333df6a8168510f52a929de4180b4dbe97d2302d4dc733d3d057a0d44200eaa8562a464d429005cb804e00d4154df4a7e7572cd8c56c6f35b860416d80f4b164945f3551ee171b0e922ee3dd3746e77432c27457e49c9baf2c463c4eaa5fc1b2e55e84142773acf6cceb70becd25377050d1901fe5dc4f82777651ca2671a0994457372a88617f47e896365fb7d0d2630ef70d250fd8ad269a1d1b8d615d45b0e7947953867fbb8f82093ecc07501ae06f0699fd383e57d23427fda748868d46831683a375a0f2d522b6bb988dd8e5693d693882cad11b7fe74397496c79b7cfacce01232020fa2b13ff0b4017aebb24b37d2cae2ba2c8680ad70684ccc01923369eb6c08b2ccf3050a85baee3b5c7657c13bcbe4df0720766037f093ea6f3a745f7031b3367d24ac6f694e34c533c47838623335f49ad5a5f7b85e1980283841adb9be0a4b930354214391e796c051b3523d11987dbf4ddcc3aab83bc818191c76278c5e9468d01a3e9c3e3d08526d9c53b39e550b80abe04bc2193989f124ca73b3e4d373c7672e2df8d45a78c85608d61c02f81bfde5afab9fb845ddfb173d0e7c7719f65b89e3864524ed14e54887da35e192aa967a49e8712947449ccd465497a1e3f8e5aa4a56122385703260e781dc0e123be0688fcd2b4f655523941ab242aad40c461dc386c160a8ccf39ecc017c52d19ed38d1772b5c70d74e2a53232b020d75c6a0bdcec0d2a7e97114ff66f49aa358b8ab63a4eb451d5230351e20ebcfe4c6470b9825caff1e82b2d7fed3771902c1fbeb6dc2840f31e93b7bb44c18ad3244d152521210510b6e3d507eabfcc8f1169b1c503631c8d29d95fcff95221dc492a06a5d8f55decefef653e219e8d2d9c655507fe0de978befb3b694ecd660db1927c5460fcfea93eab27cbcbee0521ccf339dacac74f9323b149e9e50eba2bbd4e6d6f21c54e2fb489236e3266a0b0db1f27171b70148cf8404324384b17013f43c45f32690e92d3f36d24a32aadf180d936ce5e22a2f52ee46923b44be6ef6fa2e25bc257dc63d0b5ea5d6b7f54ca1e233f12d5a6c3dfac15e865f0aaa33e5d4b3cd44b62b3621c592e312da4c3884468c861b7e9ef5cf32d613a888320b8d506ebd336f404b7532da2bf93ecef13402278d1ab0c3cc38e2faf85a00508c675fc1dda1d407af9c9c9f0cbd45db155167fddc2a4fc702f7dc45e1205759e50691d2bb31c9c6ee900b63ae15f43c32c99c3d7843606f724374933b22405aa81700fabec8d034a6453f305f68eecb3883a579d90febc625194b9a84034fb72a95386aaa662e6b2b1e6353d5e3416ce3ca67e7463845179c43f633199ac4404ccef2ad050cfc2b1c575b368e14d928a0759f34664f3d078e56b17a027f3a62e19cfca5bdc81e5eb0cd7f73aca6a40adc8c45fd6d3ccbdf78362ca2f9e8e2f07e39b571dadfe9b08a959def576d7d7036b8421d45ddf888e6a0cc384bc57126488d1ec6779d9469f4494e60d4467c2e12223a7108ec564e2925b9e8f5c91119c4715a42035b4291474eed855cb294d69f232029db6b03b1a1751d34b038dbf934eb81d46c078fcb474516e9e04e72c4979478a2fb8697715fb18114e0e0320c3bc5e79835ebee5fb3468706b1d93d96fa2a3373ac7e5e28182740483bf5918405222e13641475310e63ac60295206a11fa141d1014101a4fbd0565e8845760fe424294b458a66670dc144aceae853a3009b12770926346606fa01cfd1422cd536bd7a34849d6d331d7370dd0adc365ad720063013bbf393dbc3abab4986280821944fd81837a65850e83eeae9c9e52c5fe403eafebc83775203a14a3d0d351cd0f0b40f6a9bc8340168dec8e6aa8b588885f687a1758a2a894ee7f7e5125d522cbb5f0389764815f3da7467e0e88f3c03ec8d2e15bb01ff53e5fbc22fb4211fc0dbc0472aaa8cee97bafc190b33b3cd1ed1bb281cf6c19c94617c5f496752448656e1abf3e37c3946a25845c2efdc8efe6df563c24334103132586c1aa7627fd95707308f33a693e7849698804bc99b6b0874b5e8ef94414c3320406021455faed68f05376b6290dd288fd50227966070afad681cfbe241ac50155ba00279adfe2a6d842f14c6fe54115efdcd503a264cccfc7dc9095ae909fdbbff6bfb881679b9e3080c4aecffbfcc043d7c2d731a652f60737a209361500c02e0099f2e9befd39d5eabba98626baa854beca1d51cdde819368b67195d12a0c3e7f0bb7df218d6ec85ef0757e764b87d00674148dda870d6e5cbc4d1476b30890f74687b7f64378da481c37183347c4143b0daa1aa4c6cbe35a98d9d61175204c73603e6814dc6cd00953b0f10ce6a5b3cbbba2e703c9fa0879297458793d95ca1575e03ff265f9a9b569383dc5e94df14ba7204f34aa86e51c00df341bc3a8eb57df6b135f16b01bac7da6ca265af0e8ef8e53d2f69dd9cc1b84d0a1337a8cf0901768be4d5a23a82deff1a7f68866d9280d7df16417bfca2f30ce5b117c81c7a2b2cf789ef2372465e31a9524fa965c0b0a2da0c325d0f69720d25bd49038ab1cee418a6e2d4b8a129f9dae8d07e64adaf9cea2485f477f03d72609bb99b0ec600af2a1e0a11946d18910f495872d1fbe0e3090271c03a9e74095da90234ba249616044c3d1038eaa3183a8aa9a8c84d3b673c7d840476830e244d1b19e6fdbd6c05bcf336eafa2128072b3d97dd345061b144c4bbcdafb0b16db59a2bfab1b847ea40d183c1bd7730787bc1f91db15e7b758a966992baa6837608eed4519ba45a1b611564788fce29e0f88d5c11a26746c6ee0a3e3327efd8a31774adafed080d4a49f3b0164903009f97ab9e532770ad0cef7beffcb607216eea8026fb497d6a48e4c0d8706282abeacd1a58bd876d39e5ad8b54e78a5f28881af27653c3539fd06f6d0d66463bd557441440469332b0272793e782eff237c73575fab3a030bfa284526d95d89c1b9c041ff0b68113ba713e191e2a40aaed97bfe44420a1b0954480d8fd5db81aa31e003afc0c292984250ed599b2df105124eb7afef43045f617c544424b07f241d151509992b6f19c09479a5c92185165bc595bc5a2e02b72bdec2eaa386667911b5a54d2c1f9e75886ecda5cc1a5af1ff3203c863f98a20f342da55da62b01ebdc48c93d3cc5b72171df8fcb62b4bcdd533d90d493b07ad2428261984a075387f702adea3ff1446183ca1bd706d870f82d6fdb22c8ef905a7cf0d34ff57570eb262cfa2e6255c331b4028c215f1eef3de545e069eca52d5a3725b57091296aae4b1ffc87987b28b2e591d4717015e6319c4182193e4269ec515993a83aba7c21b935190e56dcbe33aec6d6b501384c590fd7524464c370043c2becd636f574dc18d26ae1c27909b428be71190859d94ba77d6d64151825280178286b0fefe7f6a5cd8af505d0ba29c05bea2556acb0fde62878136fbd99d26e290816ca8afa03477d6be4b0eb4112c5bb7dfdcf962828bee15ebce9ad9bd001771241604289db16074947eaa6201a308eeacb2bbb75360e4ce80c32a2d0de4028c00d3a9ac8af175a42bf564b87579c1cb37f065751b9e53e067a70683c34e02bb34b95736acf6b31605654b86810ee64512a751d395159f52bf1905d156b3ab709f68b94b976cdc737eb6d96d47b816e91230cc51ac78f7de223c70364cc2214a043d4fb9349d26e94309540078b1f1472bbae0e84893f523c31440cda29c352a067983868b1cc4935c0981a7674a89c48fd3daf4524b300b9956e54e9359ca7016a9f6c29b1fedbc99ede995b41617240a0c9ab53a72b7996533f3584d3e2c6a0011346173ec65ec01b23578c0c358c0fc7af9173b081689c5930ee4b510967c602eaa2fbc28f56e66a41854363cad7b2ae0984cfec4fbeb4e96e5d559956ffa7fb02cc8991dc870b08bb8606ad9455950e9406c6b592f9d210950fe58fdb8ac101cff9f60f0b60fdcdebbb473122ef8730552e1479f462dad68a993a7dd1981b21634bbcbe9294d41493c87ffe26c1344572f38ff84900666250402bbe0fc1e86ef30d48521463e973a42085da45dd18ed0c3d59a56446cfe06fb8d63941d79a62f20715800baacf3fb40c769d0a7f0c290dff1764538a7e37f65a996939fba1d841ba9b90abfcc65e757b9488d38f884421da665881021f1c4365f1291e56ef5ec76eeab0d204c54835f82642a13fde36fbb80ebecfe4c8eebb56b13906ed4dd22829b48a0f530b6824a262be77b9171bd08d8c7154f5c0e90fa4691d8fa696a7835020c1841c5d549679d3473b4f07d3774390e8fca31f8c8f30c524a5eff708ec4e0f53b3ac9da378119046f14693918ff3499cbea0e0ffa0d6dcc21857c905c558d5039e1d0505fc174d2faeed90a3d1a24a80c6b50919e7f5af72841c03bd895ea3ec4c84fe85075c307e239f915c38262b98d49612ac3aa4ed0f464480ad3789251b2131012e1bea8efce99b60d8bae984ecd3f6696a61a0541d275d643390998aca62a9789c04de27e5644fe52182ca337614e3961df5ede92c65cbf8a5c6c0cf4045c229310dbfe9ef8a3a42fa34abc7a606e7e69466f8486615ef139d4e257caf13eaf634f6188bd04c037736ea6a7bf783463a23f40802f43bde08c80528aa6422f4b869541100a3ad677c41d28185c86cc4c3b509dc01dd1581a1f09635c547ff7b3bf792cefe3d3104040fea7302438492345dde3ec9cb916a01891323a9c34f5b38ceabe47c1919752dc444711de5670f702142570227e161541c4d1a900e32fc6b09b4e1cf4082e676647ecf0404c70c575bb90384078b340a4f4ae76c5c1ba1088bd518c4fc14d8c32e0cbc65c0a2cee3fc6edd5ccf2228f93a93cd622082c2cd4ee614776d45c67021e28ee6ec77e8c437f85d923ec6375a8d3d1500eaa5d465d08b9eee0ce59b9191dee77e77c37695a57a546d13b9ccf42bc8fc5bbcd2f70952921c20694411858e0225ea94d7d86d48e046833cd401b72ce94a4415233dba5b983e06a662b69f21624abc613d80398ac2177667f56489b48fb45e93c95636dc8c465aab680f040ab0c773e10e05cb129703c1000b5fc0dfc4f32ba4be3d9c738a9d6a4086c0706ffc4d1ec08bb5f9fa7df24a84a3924857410f2cd3e9186b42a0010fc0545b644de56e9ba2f209d9b6f95b8eacddebc8e2dafc10acec466e455d186e27443134b6e27f591f096de8bf365756a7eb12743c33ef03531a8e950b450dbd170524385d6d2ac9469357be0fce6a3401f647c3dbe8e8343f13000f3b624ed16f704855cf53804dc2af0fe7210abd5319f1f6558d5ff40a4323601c95ad2661046d2ec327f660e6b69aedff825b90b6ab01f5354e03d653ebea6270688e20095c6492fb5ebb6268851e95f27e167b77b19797ddaca6518d7283ad3e5eaab8ca880cedc37f667688085d4a29c1d791e002d983b3c31a87ef5229d3083ded95ead402b78f716989e315619b641f11203eb49b1a451b00701383e382e080c84d2c675be7eaf48452879f88d7516ca04baf1d5fa54d610f9798cc7928de56bfaf705389d6ce4438a16b7848ebb9f6ab418369c51f72f5bc16e926628666dc63148df66686deaa9d45aa2f36e54f14538ce105ab35ac04c44567be15387643ff0fdb6cae0ce9c3a85ec15cbe485e0b4e96cb12de946be7319c3527b8cecc58fc1a92e687ca66005e87e91fa5622e978abf8f508316a6ca220445901048167d8e7bedf98347600101d1c120506002028eb1750ab4c2be3473d6e4d0aae7c2cef6f8577a200bb1e6554b26de6e965c2594d76ed3167b341c0c49b48946902a4d808b82e1a36af0594909891cec57a4486581e37c04c89daa2181de7104f1c8308604ddd1e029b747f9b7af0f228b8690cfc0c9b68c5a34fa7c4d078dd5577bdd7bf443d9cca7823fc1635b5c0fae65b621276768c030c35d9d073613591d5a7916ecf5af7cdc0897fb68432d9e93f25ed5318b86a6a0da8e4126a42a011be885f0bf5df2071412bfecef2b8cbdfa90bb7e2ae8702e85bfff1e732bf541dd2cde0f5e39f498b65be3b83933cd624465b722a4e52e3038d4efe310be15d4735ed0570be421de546880fff9d502ea49ddb98dec4eafee9c93fb1bfc77461fdc35fb034092401cf74a125015b6f09e52eed0138c4ecd49ee64380515da49957512dad8ee044eef060b7b1debc48358e596e49f3f82fb38179e4d3fb25cd99af884caab2f6764613ef18030df31784ceab1bb40e9382f4aa696f334f908a370ddc14b70d316e9ae93f82b81b2e66a5742bca027acc94c7f1290aaea9130b9ee841123dcfcf6abbe94fc70621c8af7e09ad8b3cd3258f3621e1622fbd99c661a8431754dd49f7dd1bcc8a40c81628f2c0df6e23fb897841dbc7b82c2ab877739ad4e43f7466a987c20c4a4bf0e286fd1a34c02194c46280ef30eace9cbc62dbb1c21be78599d20979a5d8a4ecb31b5e8125031da50d8b3e645fa062639bd1ba340b2e22fcf06ba4bb2c58424ded7fd7c35068966bc859fe48fbf1c2e770cb89de049fc393e913a487a0c228c6c66f564f60d4c248247a51912a1b023ee9e92d43b3900ace1e1ae30201bdc4f59a8bbe512220a65cab16b6e952e0f98c7750bec33a11b96bb3eab5b03b4e03f338748eb3482e665a6648a6349291d068c522be61da3689a75749551c5c9e82aca10e62b8f05bc3e2882d4a30edb55d8f1b3ffa6f2d10d26f52b9a38e590aaa0115e0992e3406e09270f03a3093b26d5a9feada1e7aabeeccf8fb128be603fd7313c5e9754fd074a6e533d6033b62625ab2cbe7f47642cc719654b3b96f7aef73402b1241399e6f87b63dd6fdad57aa28caa1fc35dc11d0f6abf552a2a6a12b9739d226eb8029cf4a6b57114104a492ad170fa75e807f24c175ecd20a31a3205e51248fbbf7907e1887f013634fc21fb051d6036cf8f6dc220ed39e8d3056beed5f593fa00357b9c17327d14c480683390b57888e0aef319fa56805642d9c58965676340629f1729becedb473059650264b1bf2d6e595e7eda059f595a7edef8792c9e5210ab5322f509ecc5037e759e8242496f119a56d6d7a6b28985d7c10e23dbbbf127227460fc439cd8e9c30874c80c2a1951c1a15259f12644173088300c6b80d8b840cf6bee1f2e0f98414674ee7f323538b29a5f10736aa39743978479579f40c5ef5832fa211e315415578162ec1cfcfc0ae524fbe754e3c15c56adcd7e1d1dcd0cef8b9b7f1278f56762068242138372c5edd1f883f9ec903a12333966a5c71f6293623f7a63648e5e3d7924569765104b58374ad2c2654158b1e94c3ee075fe40b20c52c3da7450fa7a3bbd2830652121f56299af10a5739b87328886e26d810334f30481695bdfdd04650eb668628b8a487a6f045d423a90ad6fbd112059bc812c9791f9c4cdc9d68cbb791b19af03cdb7756b30e9a0d7041b58e091507df2eb039052d947011eab0a72046e9c55bc1a45f708633ce5b5cac508e2018c9a512fd48891f21888094e6ad3ab2bc8cb4ae8c804e0ce9ffcd5004872d3045cce3680e2f676912562a57f6260778ff8100ed544454f6b672e27f5aa0505ba5a648c61c13eb16579c76fa986fd82e6cd63cdfa1606b17d2004f643ae1b0fe0b13f3eac65dfe906376bb02b7e11c17438ca458b01733af6915ee5b8690fcaff7c0af759dd167b599dfc6cf9aabbca6cdcace2a94deebbdfca5c11e917e64cf961cc8c29168c410eca9f3f24fe23cacf51cf6c8317f69c2601b90ca761b92f3e2c063f5df41f9390af587acb014f8a19714c8de40dc0b234c71b2c5a5b8382c0cd9cdd1fc6e1690d48cfbbd17d60e0f325ae3feab4f9b7ac016bad58e96687a308263be84c924c03d663617ef8b2f88ce62381935dbf9a5b275a3ccebc298080083cff471d819e23713fe105c30c203e9ff99ae052fdac47aa6ec924e5a53831a5fdce3251ee85824284e0b1f45676e5cca603a45405eb98fc64f4bf8003d736e869ccd99c66fb4f4b4ef1387e0a145d81eeb6acd91d7ef274b60470a04a32d43fe53f98c2475149ebb0a425a3e87d8a16d664e5073abd6ba43d7748f54757d592b8c4f857b51f9d692a5a1b4620da0d95a48ee1b2626b75cacdb17a34264f91aecdaf6ea5e1a2a381a92af3546c204fac86a40a2f01f606264f4067a902c4261e1af98007c2c46e3148784b0bcd7ec6176197d64b76e104d7236eb577cb88ff4a3f6530b4bd8583a5d100cd02db88c64b6f5a2a456284cd022cfcd2981e8c889eb54464ef1650c1b0ea803974e05d727bc01723ec65d5dbd2c64195bc2f02acfb62165ef97ebcca9295ff6a31bfb96ed8b9c43e70b0e29d3925d43d6d07c1d058398c570da909dd23be25bb2c02c1b9a7f1885a5d0f7bb13317c76a772e698369948411a38a4a8161d0dd8be8eefdb6314702ff4ca8ef02da3500cf1b399dd291d086f008708614a1a5dde41ed364206e13f15fb00e54e5e4fdd7580e5bb09b507c542b09e2f46a82d558e6c640df3164e51d9aac1864cc9b982d6c8057ae2d6fcc31ff22533ccbb95a2df33566e071a306158dc01efd77ca1ff1a7b661af5d15330f2ed55efb73d8008efe6dbea3c03743cf21e8f5a6b0828900370badcd8df6e51610c725378646c5fc300e886b22104343ea1de191b41a05796c850e3b59c32983dd6feabbff874253292c6e464e00442ee1586b0b9452002650b7e147c55ea8aa6ca6129ef227f7b6d7c13442e7b01f48d98c47870328d9f961d9bf49911861d935cb18ce5246b04e0f3058d33e46a9f28d6e156dd62b5c633440c8221d71a5c12bf5b6104acea94e69955e2119ddf65b9fc2ad3d5a0524ec4fd765ec97cb9003135ce31d674d239b0a16a59b8021dd91aa215589618ed0b2e3fe62c57f42d1ed600707b5b764abf009daf8ed9d54d458a4b7d91f51a8961229a4ac01d6cecc9d9c1e0125eec0f2ccd9bc400e3a6640a1683e8596760ae73bcd9469bcef912c2109f0675eca2438a8313e7d4c06e087d1f8a07d8f50fdb963142c75e43d59801921701004f08b3ebee574a90c92066232a037b9fdd097e325f300b6f1d6793961180ca73dc58d9d5654cee84d62027f215736c2462d8e8694ac8039633419c563e7e0eddff8c591c5bec7f5e34d809d010301a55c65272a1d50418c7ed0edd1886e49b62e8e0363bbc8fecbc987fd5bbe31d1805251d88514cdfbf7d1a113504bf64133bbd776d5af1fc3e7785ba58d4334fa0033e20b4ac56974ea8b35bb11b8de12a3f2af067742f0774b2cc99645e6e3ff048887514dae88af3aac78f2257a5c627134c40a14aa10f42c8183d01075a6e60ab94012e58a7164ef0d714ad96b27d0395a7d9df22af6c02461e475fb1c03aca0e515831ce2164b70877ea4f359036289e1c257963aa827eeafd8f1a184f3dbec109f7728a9f03fba01846a9d7121c5bf6957a00a3c95e2e3effc5cc96c214450a383cc00eebde608a37f6e52c53802b6820b1208622bb870204332df8cb425c0230bd3faa6792f514462c0554c96b39301f7bc884c07d0b2b0ded407a6dc6829202e89904385e4a6c2d0bf3550ddf5007e5d4a18311882362ac5792cbf12b0ebc6aa7a358f5b18a72cf9821d7f804ca206bc804a0ae734bc86573c67e1df8b8e5f119274766ce6e6ef295af6706876065bf6b7535256444c58445f70e80901587d9b333a6f9dbf1440173af9e1632565ad9bea741eff3896e64f17d73427b24bcdecc50b80fe31ba345c84fbc7b3d82722a46070faec51879c1e5fd0b854eea3e09e2c0bdb009a06b7d5188c9aa1f904185bb3bf1fe192b7eef5ef7791c7c081d986b0304e3f9d3fd173af2d3bc06736aa03f50ba787f0d497509a0cd7a16629853dc84a8c852cb0c9b1cd72112120e5ac57f339af7448fdfd003dd04ecbefb19fddf55778b869d191581182c84da051f55169196193cd91a6dfa028b2e6411c7309141222c2d61947e5c5230cd69df1391f3312bac67045c1adfa0caa9022f5cab051d5302e36541083110d2abd78e7e1abb6222e1d488dd89a20e75a1112c001c4fc9f8747afa88516463aa48bfbe606a219dc30cef0af2f8d90fbed0344806558cba64997210f779e9e45f2b080c3499416fbb3eac1a978318ed9f4fd34c6fc764a689177105047697acf52d93e8472a3190c6798901b36dadae0864eed48165ce57765d61272031a229d6605be903e38b3ab044730ca7f0bf28c182cb4d892ecfeba20ee72dd13d7bcefcf92a053ca91ffaf301a486ad92fb396cbdebfb3920864116a41a1d9dd27f5ebb53e4dc6a6fb4d656a6f4678b7197132046141040add4b2a135c027da490c22ce2ef5fef5d569ab94275f00b9be8a1055fd0055a19870788f1f37e61e9bc24c8b4f1852ec1a2c3e622a73ac3aa7fe57c0d2ae1bf372047d1dde0b6e46069d71113e34824b9bfd6d7206d706b0df6b389a17441954b45665bad78ec6e24a5d222daa406158ee49db4f3d964f8d7d908a132a805b65b5439f62f2a679c764b7260c2eaecec3a35019a0ad9fa4b35be9dd113fbd9e3858ea8f243c038bf283856a14642f6cba12abd3b9c7df3843e148522d2a5906b93e035ffadf996bdf6c648c45c84aafbada85302d3053b4c9ad653a57490ae578358f6bbea9901146417586e9a11e57cf050a4d574fe8087db4b98035aa6e4c84c416f19832c0737f5578829dfc64c38ce32768adb9c81cc655ac1d12e19d0d696da098f8cc2d10c66a7fb8b7004e71f91376f2f35230988784b8e911eba83ee469078ba79d1cd8097e472ecc0d5f233bb558e6dcb17509010f71c225cab060d5991004bad2014e0ff4eacae4337dc325acb80e63e9c5963b7ad6e52012ef07748b21b7b5be9eb79dc97fd8820c848c8ded194317ba556b9b58e364ef82b6986629f56d4ff65aae25e90e94271f0825996302c5cf2833cb825895c97431e5f417a483b8e7f3a61c3fabec64f8b0688be30a1588d2a337a16eca94e4228e6c5e608586cf6dd13345b408b2650a62073ea6e3038a6e0aece2bf6800513041f4073de9d53888969ae66142d5dfb7e2f0ad8a99607f2deb46db4a1dce01b311608e7562587d1adb9a1e9fcb326025410c629e0aa2148a5863d6a9c76b353c67c43ecb158ff0efaf596570614edba03f98e9f55c31daf8a7f4d8200defa06055e14337a8db6af16049fdd257aba5f76c54556489ad91466f800751c79af635c37aefd3e665d57c830805428f0ecf2fdb2caaaf55e2d8ccb7f96219c2fd5306d931771d8e206a761300e50a937782372767f7edff86c5adea81bb92b8128cd17c23313bca0dc00db8bae083496e215a0de67582f6f20ceb75dac2bb4afd98f2a6cf9dd780eebcd6fcb66203bacfc293944cf93aa72441c23df722f07c07161bebe940464f8a8fbe6f9e3cea2cc834b7c30236d3bd6db17479b14bf63b8442aba68ac1e2573b79536e6155aedfb17b82417273558261b97f9bbaa2a80ea96273f1e8df597d91df824ace3452377c5b96a48bc8b434e838d2d95ec218a35799e81833c067de69ac2836a0f57cb37dc07396ae0bebd0abbb1072bd0fb0a2c30b2d9155b304754b175cd6d94cb4b44758ebaa38dc8aaa5957a9dd012aabf7620fc425d05a30979ef357253b1227b89569b685eac691d45aea740f30701b341b51869572430f1a1c70df06150df65eacc14a1df08d446bd51ba730056b143b086031a500a2a0129211b4f45fc11b311d37b0d9e273fb6b3dbd0f0cea5d13b3a67e5856a51af7835de2fa32904366fad847229dd4af2a72d3ec12cef9e301d8ea34b18c6531867e4127fcacfe419240b427c662b6fbbf0a7b969abd2fe6484fd6da591b8e03cbe21dfae0809d6de5b0418966ea510503b8f3f3599050b6dd7e11924f429dc69b2d1f82d9305a7401e716fbfd53f8d51b0b7fd5f3ec53eb0fe8b76062ed9260a740626f8ccd3cbeb38d646dee589075d039ef7208f83e3373bcec38b05820c05fff0b681ef98a384bfb1b933198f2df981e8445729247cfe186dfc7c756683914e5df31296b162623309c2f86db977e1acb8904f8ef9e3f3665826c1c2ddb5c3411f8424d040afc982042b8845f734d2a7c0bebf0f0da25684e54bcaf4062271a080c3a9b81f601378c9ab74a3a22d8b902256efa8c48c3ad3dfdb295c5fbecf286eab755cb8be7f5205ec61f17bebf8464f817713224da978b44f8dcb9a7cf720626b2a47aa3360b91d712477ed3c61220e6029802a904ef16cba696206f455781a71bb73f0127bbd002670c9ccf1f132ce0a32be28fec58d7418c1f3c5b4b0681c3b73fca26077a88939f4960ee07721f1859e33f9b8b2191022883e724494588741e2fb858c1c823b34c20c2b1b96c609cb396d4ef7891870ac572c5d5c60e60f6709bd613e3809764109305f01adae46105e533602452572f8690f09e8ae449ceaa82386c90d62c22c5236a31aa3589c6ac38f864238ab0bee3a86dfd674db6281557a9e034975d3da35976d6a3a42cd87494821d9dd5ca0cec2753d1a45f6f2dbae438fd077500bd3268389c57793af96d834854b5f02124dca720def1c66d285c63d316ac4bd482bc492147c60720ca0de0c1f5bc4608679e1226a1150a717a45c66468b04714ef17f88ad968169a43c80d6c7cfb8f50dde222eb957e7ddfe5ab517e8d17bc5aa3426097ab67ad2b6cb2d5a56e70494673a465868277ba7d2d3f5654d180fbb6cf727fdadb85260e3383e183ab0db365dd8b661a2189df0a9f93c0fba98640f5699859212933ede180d2056bf8fde789aa1317020d220b5236fb351112c838d25f3bbc054a197e76e4addd2b0edaaa3ba1eae32a39aab52c1a40d04f85e281a69e5f24502deabc3862b004d6ef22c6ef0e97bc0b7d3abccec9f40517bcc06cce9e5e83ca14229120ba21b1e4672f6a60b53543adb6013fd60c89de7bf59622350b5e054260ba5eaf461269d222dbcdbb3b3b1ed46269929396bae63a0ed98db64a35791f5a307c308b242182d1f881335e7470f24145446a9d71065a02759047d439e714783dba45693f8f9de0e490b834bf861c68d9fea284f4cb9c523efe3f9393591e25ec9cfec12f8f37bc8e42dc3664678941ad2768c73927711dea2f42af9256617bd527f339c68c3f95bef715b442c3eef239e07525cc8e68d2590e6d4386b111b42ac60507b9c4048633a390c89ddd42feb4995b8bcc71d27acd4d4452d071d5f1a7327f51b86b8b0a747f1b46faf80789b81f4c323f67256603040095a34d6477f47a17098d4661829be1be804e3d69e95fd3a0a3fdc4457c76de7908b73dfcbd24cb5cf69041be019449a573b5d6a972b1dfaf351e4c20224e70323cde8986d509413cec84b017c7f3de1349a6b78da62b5389060f37d5aa4165bd11c7f6238de1c0c08deb6c760e108897a9e5a53f281e8863d1c4758dc0a15f3e3e927cafb6f5bdcafa920bb86924c119c44e1a482208278e222140bac402068280953cef01e60838b1da8898112ff941a4969c496309ccb300fcdc7ed72457a131f67886587344b15e2c0b8753faf395b432ac1bc82af3a2e0967c250532f0858fb4f6bba4479952b1795a2164a78fa9e3aec4203aa5131281faec613b4489109bfb9d1a6ae516af4f11953d65f3b1bc15f2d66a46ed473caa07e321e697b06483957a0f1aad5dee791b005fc57580cb4b4c0872f597d7d7b5ebe53b9c34983eacd97a18f83cfc5697f8f9d01fc280f2af023efbb1a795135a1fac6d66ae10285bfe7efa17d4911fa650b088dc6e393340ec6cbf9879649c27f59733638e4c1d260fdf808ec9762254e6cb8b66e2694933db23a8052f11526c75083f212ce0b63831f5ffd93acb91f9716d042706c4d6ce06350b72b341cfe54fb30721a97e5a2e203edbeaddc482a634efade976873fe04b93679877fa4e17e1ea812930c4a755365466517cd649e53f9c7dc99406f9687c1620636971419c85cc37539c80823c9c4472e50a7c5e95ec1bd725c02bc768d31154bd70da3b2e7447ae8b438c6387d772904551fb9ec94b8b4ff52c3d9403a6421f946e10197a016be6e401c531497ad9ac3317465d6dc5f676bdecf28a67b05380d5205f05dfcf1ea6bd031f704d3819bb332e8a57cf1d74d66c82470db235f66f96483ee3b6f80b4e6ec1709f26c59aebb28d54b757a3b5e7d4b4ac6f53f596c6d9f0deb1f2ef343311bbeaed4320f22837114d58880d05b250203abbda419731584f001d30e633aa6f8395c04e1597162a0c3ea572e58c5b423eac970b180fa25b770b9380c120bb37e7968ad5da8fb6a58bbe1ec88f368a628a8cee426274f5a20134d933d785697cb3b58859573addaaf3e0adbfe6d182c2aa56d8e52d0270c2b44813560bf3739ee468ebe74e1e8d910885dcc48504a661506fd7744fc0091f8f26d08893dc85b0fc8917e273f129350bc268ac897f1792c2752112c64d3f2c18b4e3fb38782c3354aa0b81689dd087d742895d23c98aa26727e61125f764f4b78d78a6a82c543ed5423d88e395c2ae7e279794d67a6159c0faa5017b0c4bc03d231d53cdfc3abe089dee5c4209fdcb93acd3c802bcda62170d47156adce77cdaec5b6801e07adfe56153b80325d3a37df6fef3bc623423daa63640d08dd15a8b7a2a4f4650951702fcb5307980ef1fa87d53d874931b0d4e7198907b9c0cd1fdf87a79111376184e88554b08143c091235197971cdce96407d2749fc271d286c752fa1981c2871cc049990f6eec5c51a657fcf4ee7862f578562df757d674f80edfa86a826d080405b1ee0246e60e978241a6db45ed6f59a90b82843820294e269e2ea0e6066345992c4a607641c1fb48eabf528b2cd2ed3897fe3be18992734cdf1a1b6a0497443bb6a7688f70e0b3220c5d0c73569e889976c70e0cfdd9111ab1829919d9b8a840e6435dcd7274238cb8689abdba6a0b6abde895655020f6f4d28f7edded0ceedb8d77c64b098026a210ab6a5261ee66b6d73a4369f8001ffcc67b25c6760fbd5a8bb3c4ad8f3f50b00a85ff61a8ec6e96ed2ca5aa537f9b115af98f82f46c15780f44a57409c69884bf8ddc48625c4376874eecaf928426e751d125a4db14af2737e227c98ba713f69909b2b46ff00cdd9c58e647d9b9e1762da520607e8b39baf9b707584ee277c55e51b8ea0e9cb039f2badc3b1acb230d6da22f8525f3054418711c66c69fcae312bc3808f098c17ed489c9861702ecbc2f9b9f4e48bea2914c8c158d1804b30df29af34522307832faf004ec720a0abcb5199a18ecb816e6fc5a14f5090a541d264cf9dfa83c31ead138ca5615b8b72d94b321feedefa56e8c9b4baa64c358a974f9af29134fd304b798243ceb3477b032f2c638f73260c9685623c57ac0b33c450821c78b6be2f9917ef5779b137c3a4e06c28f287d7645cce97c8891825c1d442cfc38af9b9b6e452c90a71f27b55388ff34a4f56b5c5c1cd69fbf19e7947e1aace7b60378e375ee65ddd702f986d9c24c19d1ad712026fe021b3e98b7254c64634364626b2ec625082138c9517441677e0568326eba12b939c04e473641e49c49eb2ecd72bc0cbfbe8aa3a11d91e2c32db1ab2d90dd16b001f8b4d9711d3f684124c6d9bc2a9dd885536613d295d11bc51ac7b2005a4d299a6952a903c06235b2f97dd6c4cb364d41e43bc3b445b4a2afd81b86b222729418a62806ee42af7b877b67f9a1b17ce61f9bc907f2be311b7d74f70f300b4ad76f08480d3b6aca0b48ca68a57a3930f54f438fd046ea82fbda6c6cc76a87ae52a03f006b2b75b0b6f4a832e53d496455c3a42dfa667a8d380a72e8a451812f4964b21b1a67afc228554a8488757a01523d59048a5e4a8b1ee4ef13a45aa4c98dc39ed77642bf094d45c41e83919ef5b28eb06e99e8cf76066173d4b6cf7fe5662d18a8afe4c1c26dea11e79857135d25878abc0a02ec408a62eda2d7a6f861226c9a99b3daaefb4fa3cd07b9199609fb27c0d1af0810bb335c28dda91425542db993dffb7e127725c5002cd793f33197fb82800a076178913b54359a44cf5328e149cde8c1576b83544eaabf341deb45f394da6230e0862a1c7c6c10bdc66358771b5a144dd2db223b595f8cc32bc79225034fc05dbaec1302904c7291091ea791793e7adb41ee4b22564198445745750b274751cfa052e18f550c0b4a60645c6cc6eb748a4836e3e75153787496ff230f6b5fc9abcca6a19ca4288c1185fd1f27988a21f8d1784b0766304d2da89be3781df4b8807fd735387a4299674a0c2e6e005fb953b5ff8b1f6715fe9c71404a246f1cb0f06e96c8897f271a17556c7ba0896568590e2aa41638317bedd969febc50112bfdff8744e43475d6974bdc6118a322f69fdd07493254c9fcb47dc2cf87d89a7cbb65e2d2399dab7667174d7fb21e427b39614e2963155f18b22ebd8ae875c2dcbd8e864b903c13a8d7d9e1fbe7b46df27820759b844fd5bf47cc3a5fe53d3d48a2cb344217916ece7e6c00cb847ed9918a887c5373da3ca224341025ebc9437b903de8bd84d977493f3125774bb0fc9f6fc94e7a4af7397f97d60356babb2939032bef769694d79c291356d658ce5441a67ea118ccafba69a4c8e100d730389aacf46a6a49023bcdd9aef4aecf81a3a0b9144cbe9cf3cedf8d8b4040c3f6dae8e44a38c7acf6e0582b709f90fe38186b3d82ff56b800867b1fc4102bce2ad5ffb856725b82f08a7974f2ac14e52f75422834f1c589c6177166ac552fea5623afd8587d512973a993551454aee3e0ca01b7d9a2ddf44b0a6b563ae335946e1c485a2f149787a4c6e410586f57b1952f53c75be5d87123a0a733d3ae465651a508f172ed5a1768c1cdf1470cd7f1b61fc76c67daff94c3dd33a5de821f08b6d128fc6e72ce24821ea945366e05f0cef469c064ae33bf48912f1b03ff9ea20a5e7d0f2adfdd48d5de69812a8a921ac48f7ff90b832819e2c472c82aa584360633c32185fe92257eed0161b2f8ac8d836c7153f6a51957760b35a1d6d665b58e35a1006dcb37450e0a6bc425ddd4cdb281d5254f991c0a193ae1c1824191b8fc29133cf0f180cc62b2bfee3718f875f909792f7c51c2a228945b5413e15d5515e721576889f942c97657382c29e6bf201584929f4fbd60cb1dedebdc87952e8b516c069a701a15e0bbfed1af9517de5e85ddce3281d07dc20f2a0a65411ca6d54412d5763e3f8443e889a7aa49a831bbfc9c0e84fb1f04feec07c50031c2fe91e2fb02ce28261508e48fcd42027aa03d27da747b3de5f95f95ec1439262726c9fb27a676d70e0ecdb2472570b1e70a40f853569be95cc57bab0d7e57d7a6904c749dd7955cb55d35113aa481be3b98effc3d662f7a595dcbe57cc00cd7abb613a31829756462e2a971b99e8283556ad0469a981590da86f1e14e0c7fef61ea00ddaad8441bcd631e9f68c3ad1e420d59d24d184a3c5e0d1bc3f230a4d24fd4430a2907aebcf28322a0cd9cb368e53a3420f121362dfc4843b190fa026f56f9f006978e6913fe3afa1ee2641ecfd52cb0f1f5f1fa68517d9a4567e0958255c28787f6eb294cc01dde4a82c7c3ddd2934ebde822a2cb0c1d0343a7e4cdbc638a9345a78f9380a96af9ac1131c6d3544e4e734cd33d4e08fe77fbc32638b88b7a61359c79462b131b4a53dc3be738de25aef21dce548b2b462125d6e6e167c409bcfadb1dd3e8fd937017be40b0e2bd58312b13438d9aa92e10148bb1b496aa41d818446d48a688b4fb65947977f6fcb0d6a113a053bbb363a85da5dc6f5f91d54db2892d977f8c2c2e404ed0d403c8d607a6035e915164a5b5ef8bd462a04f24073f528649bab27510f61af5669bd98ac7a15b3d706e751f217e4f4582f9cac9e42a310c3688226076f761f5fbf102f2c9dbe30b573340401dc9edcdd2b3a9459a00021959d441cd1f59b53b2ee7bd62e8382351bbe8b5e3f95e9e282bebe9c48f34a24fe377ca46f559c754e46746ae5a2ec32367eaacf56ea9360b92b31a647542b99da86c3c8a6065c24e5f9d1cac23d41b8810bd2a2ec55f4a52ac3cbe8de1d94916d392497c31c0767828883f938c090f18db508274a4e80d206f0aba97f07f6aaa59b062bfb7c7f5f0f9d381ceb4b9e54103be29ffb4efcd43d6a076e91e7ae3bb79e2b1bc512ae0e54648327e0e3f47d347f5113aec681dfc71800d151773c3aed31c03e45d7f9a7917f144531fb726acec0fa583849fa8d7c36a39a7deb0c8c150884985f402e50b7b384555610150b7afbdda9d5a46c19e6e79ec9e14c835e9685bca997141c73f701cce9d94f4bccd5cfa7e1f25a0411ac7311b08a6e10ab633782e8fc2799debb0454b5adc5dd77ad97aabd3eff8cb373ccf8032ba461549508d74f7062aad45ac7e2012a07bc91ce41a253bfe2463de0e5ea2ef32186121aa3c65fea6c667c55304f776c621c0d99189ab5bd761deaaa021c011a6681084af1e61ffa6420b2a3ba73675505e7428138d47706aaa4b630e1fcde009daea0a84895a12bc1123826f9d69d0f3670778a0320039bc1ba93a43658d41139fd7f296b863e6a6ddf6f5ab75bfe707f1d97eea9fae733268e17e8f703400169994590c6f2da7d55b7a446c4a59e50c62dfac0670f261a87374b894b405cb3623370e0530aaae36982f31f57abf2c2f5de9f0fe8c5d2a226aba51ff0d13108b4f8c9d421ba9030d66aec6d88b0226974e219ccdf3617568483af6fb37485170b6e3977841048aa70bf493fd4a17eab2a20c7d41f4960e91a5af8d09ca7da9a34b62bf95fccb07353915d15cdbb238f53d6dc1b61b6c0ac853ce216c172183d971ebd9e90f19c7ed23efd3e642e95f8488bb55c8d4ec472bff9a8fa8accb8cc5a586990efb70f0b3445113479dd6fd517aa15e36de2204ff7c181ac898bd7129c17017d91cd8f8c57d01cb4ae8995f7043474a9e4fc1062d7f4e8d0d95cadc1a2f4f44999c02d962e8357981ced71fe64968fe72486563b6e1d070aa5f523abca9f9f99ac43fcafaa452c72684492e40ba4e8a2cb3abc21013b23508c34565318e4aada1cf6a645ae0906edf030b6b4936460942c603f5da29ca358473e7d08d0d53c1b5f059559cfae579bc3ada96ccf0fd56cb7a0b921e408d3afdecdd7cf36ea83ab805ede9c4cb1bd30ada118e2828899050829e0fa9afd306d53f0b6b42edb20014e80183d73f9f7d06c69950d436f8cb1be43a12a95a7b1abb94090d790875e92232cb213b516b36f3d9d4a7cbe67d55568fd095c70db0a52b2e0e18dff623b24735d002dad91ad0368e0f6f1b3d4fa3b2384056301a34e6406359f9d408e308c5b70a75db5f395b479ca2b001a2a5104a5f89c922cd4d4482539ddf20d589023bb0f07c70bd3405521b4a86b12036283a768defc7ec32b08a7e63c09cf4316cb6c9e242c56a207c422f4c97cbe0edbc2d3be4c7d3951567afad7bce6a7b8b1f85468f97d63e6fa833944dd5d8d31fd0c5ffe6af822ac9245432b75deeb6296058345f3a8ee36fe42aaef01edd48c541099e3182e76d1f19df1003f14eb997708dc25a42f46fa300ae10edbf73b45f748f002400f93de9fef9fe92c03b48e9e8bb41e92c868328c78eeb53e92908dd85596f04da1383a7b3e6194763d55187e3220780a7a20fd501b31e5cd32052925640135cde8219955aff51ea08c5973acba08816c681569be9120c785e0ed54b9659a071a036ea6b21ef874512193d0141863673402613c2b0ea3c6161312f0007c08161607bd97bc404ddc522b42abdc1a8af10abce2eaa93465d1ca1adaeaeb3535e269beec6977ae59c76d0dd5d5668c92444c02b1f48dc356ae6ac0c9891f597710e8940217ddda28f1d8c815af094c48194d96dec3f397a460e1358919defcbe6b6011f7e04670e96e5928a9e08d2aa04a8852c9ee8faf5dfa748b9b063d7013ed7c8c911b3d8f8da1f3a62a87b4dcb4da545ef17931d8beea0bfc86dcc99160aa56ecd047b54248b2f00ad85d38d7f693afee7fbfa2128d018f99937dce7094db93f6bf0eaafb4d736bb8cbf5b7fb84efb93150127f16a30b353a6a9c3d9273ed790d90ca5cbbea36d80555c025cfa488e25eba5ae7d5c566d8540dcad41c80be280a3def80daccb8be3958f4e144d8a52290017864e4d2fbbeadb30200f88fda044e0c7438d8fde36cc56dab54264ce91140c7cb7ca2835e28748aebdabff0f0c4ff606b69fec88aa00b5dfdc10be5c37db87e3ffe8c858207429d37d1650772c9e7acb15bdc46af3353219b0919c792334a74a3ad9fbfa646340a8961037ab4f5ad1879aedf735e6d092c6b778021529a342acf125a44047eb094a8066fc2803844994b947123c1d4548a7804a9a642a1b99636574b67531884f4068d3f3e84d795368fb21399462c01f79ed4c89f24c58755c8423fd8b821a96257874ce559d8bf92e8f00309587029872e9a0ab108f846a386c245dcd1892b5c3dfd9a86ff085478f350d12b4f9f11b0bcca23d26af3c53ffafc6120f653cc441d675ca61e6384a09a7b75b347b24409633e3bce09011330ca3e3e0a04728a926910a36e0ea91c9b7340c50e2347270ddc2a8da6b816507f0657d532678004a8fc4b13abb08d28a85596f293346d9d8a42a5a70f662fc9cb07a6703bdb242fa139c2d861a9575a8f6207a10f8eee1d6a0d832fcb39f65461e7e734be83f1795bad95d21646a99a9fc5f0bb32d672780c27fdd0bcdd9404d343a7526291ce4238dd835790efd1429da4bf874964572044e403430fa9161dba21f70f703e2f635fe0c77c846b517fd2203039cdcbbf8ad13c6f3af43af3a8c92fc144ad1b4f8bf6a71c1ce9a238d256277e93aac5a4c0521a150bbb01a873c8907479f28d820a51639650d9c6cec2d0885d7724955b670a346725a04e2fb3b33082e55c3191ed6f823abfe671ee28921d919c6a1eb5b11aa0ce8346d739ff81fdf452ae2d74dedecc6bd93c5d0b45f94d5cd1eb40b15ea3a2524197f3ebd198520ab466c97b0231fe8629a8c273399e17e7a23a7bf9b76c281555653a07f986c72bfde7636d4616592a953a79afd01f01286a30f83049e489b395903b0fec128f9f3a074d986f9a4764350f0bcd0e54c6a3cf8b29778b775fa95b50f0af0034e1ca4ed93c4d3206e7541a01c134c62291125625a940b12a8e308e2fab3962764a80735cfc823d0d08aab4728565565d1fc6a205fcdd42228492f1c3a6f17cb0f6b5f9ca0674a8c592f11fc6a98a249a52c1e8f548cc28fbb73bb6324c895f862b2565a60b31ca61cd461f52b523193e27c598477e46d043e87012314e7234649958b8d3ea9fae87ec089af840f1304e061b65736c7d8809075585dad0d3699a7904aa1a3a8cc510ef7d5331ab1aba16bbd2d94d080887f8ce7d5d42f4816d70ed637486f5d00e654ffe14d52c3081021a8cfedc416ba75d7d6b2bc7111729e2409f4a03602f510ad65b33ebea2e98a54988e063a851ae3f3c0ff43b8136206132793b4a3ae32ad5b60358ea4fd3fe03e4746a945183085c6127e3c0b7ae06fc6c0b4bda553f12c9538a0f9f0fb86925a00000393c60b2c3b410cd4532ec09b069c89a819f95ae60af26ec66cb619c502b69188ff2c7ac883445008b909a77b81f97c20e5027487020e1100223817a1ffc7ce380412c0151956c1884e72bf1dba0202a6b25e2f2ee1c876f2f0296bf6a8f790fda128c9ce581e5401caecb39b0605606d6680cb2c05b33e8069569f9150d0ec2f9defae1fd30af0dd01e4ca75f829c81d6a29080cc9667985b76bfbc799a2fb65ce1a43382a9a512bc0c4185d562977ded362468a8be1e9a3fcefaa1f6f33374fecf3ea3e4150b0dc108e5335ad6e317f426e4055dee4b0414ba9e6df0f73f270e3701fbe2031cdf2927723840115c8a08aa8f7072f303bd4bc6554edbb2cedbc12036ce2866200434117ae83a45e59526342d1ead13981147d1b91dea622ef50c8d1f378b005fc06d5ee210b61f92c6267e9cf46efc41b55d37b800f2a619c2c2bc255052399650e2a5a8b1f2818fef77d1ade04b052d45295a6b561d92455efc1981e3260cc568f25a496a164013bf83ab9ba1b17df021668be876f9b61a194cdb92a731269fff309ee834e8563d5ebcde36b8f801bc300c2c1140cbdc12fc28e978889f12dc25299acbd37b8fca11effa843e0b72cf0b0cd474e678844ac4a41aa39b4d6a0d4f43a0bc05c4034134e14ac7444bbd301677a2b10837d4e24cd467058d47aa21cfec762bdb72794a0a2bc0143b90596fb4a0ef9e67afccb35bec6c54f25aaf8d4edf6d2d1e73f3cc18eddc61cfc89ba29bcd1f5aaaaae0952e79f042a58ad68628d515d2a6ed0e9b10e5a42736b3172dad43e4659fcdffe275750c9be96506a066e6d1d839c255446991457248e3597dda864b4f00176959fc0f3a7c6f1bcc672f2b0ccd4a5ccd46ecdb3b9a63a05a85beff49e8f88e98d3890349f92207d338e1337b34472e09a0b098cb43c9c9a034882dd34d893a364f65131a07a2f5da74f592834db75a9b3066030ec30f5f28ce8bf08af571ff4c47e8af12c6196c85b0975f88a3f089143e835b7a1879ec3443460d62b2b60915b88ba0791c6475388f47c398d026d3ab1ee8dce226203e9fb13820fec0fcb9b34f82e16e60a8efc192e0c381a1417306bce978eb8d05e958931c9aa533e8d0116f32e0cbe982b0e6fff77e19ed904fd2ecede624924e8bff0545de88763d4decadb6b07dd6115e9d402b999da3ce19a66317c02215914d15bd016d5cc13e674c27a7d0e2221cbbbccb0092031b42d52749212c297ecf6c4ee983f48ec0756a318aa168b934838d3c3324a0d2498ba3206a7cf8fe2dd13a9a5c29cff5c905caa9469760c80739a75f67f28cd02299e57f46b8623484cb00cbf6859a4298e5117f1cc41a61d1943199dbdeeeef45040870d5d3aa48763433f2370b0cd2a52c4bcf9440c55f3492e2e65f3914b0b380a655cdcd48d49608c64d981a47de3933385535bff5952d9376b4287d3ead35b307663c79d71bb4c007e68fa298c080ae047064b2f636c189278a4bb97784de29675ad1d8042baa85a23f810b601dab69161cf493108bcf7f6a1f67c397db571cdd169caec777dcdda34abfabf6ea70beacb82593c7f680a58f8a09e988f845616d2a7ea2a15bb166d04c1d32ded1ddc3be2562f6c252a7e49de157a2482a4ae92715ea1461a3a0159a990fcb344c37ff81bb0998100252ed801d8b98b067d53d11801145e7a6300e104ebe01405445f37ffe7856b66ce4ca3ef3838e0d6439df1a4e86fe767fd16fdceaa0d20c74fe91f3bcbfe7078fb3cbc7a67e61b6ebc8044cbe1a72fe2f7015aa71bc464cc078b630038a555d985dd73c2ad80e4ca6bd372829564690c857f14f3671d7f4737d7ff5367ec68198a1569f563859f5c60b7443980cc1eca7e4a0419a6ac875bfc598fb890cedf31f430ceb416d344b7eb007ce3eb9c8483faa824c7313b84269850ce88471b05838889064ca57c31ac480678f38f3d94c14718995aed836bbb72986dad40dfe4a65a24f2da03fc25042a6157a95a30d815271df1a9248c74b1cddde5f6e2a8e6ba65edffe82edb4892c5790a2d1d4de854167a7c90fa12714c2f6e57e904a07904ec884081213bc56c66429fcb8f0660d4baf7a451d822a6ced5476bdbdbb61142c82664ef2df70e5c0acb0a680aa44ffa19defe76eeb3fb7cd3cc96d0e74806897e589f475bd55a0e5b49cdfda846f490ca54327d6693e964f2ccd8fc99407307fcdc79d6a51e587b2f0af583e092252c968c138aa9a43de5db8ef2713f7db7441a556b62cd1699b3c3c3e289f9c9c0cf8f063210f343b2dcb5d7dc6f246b4ddb4d1876bf1dc65dfb4c4bebbad6b49efd997ebfd2533ab47387d96e4f43ae3b636283b4414afaf6c990be5dfbec35d35fdc4d18a6530fb3e75ef1ebbe8461a4dfbfb4f697d52c69c3dcc8eb42a6c8403991647cf6299ff6d3c71de5db6efabc97beeea34f74d2177a0cc5560aa9a5b4565219625c99819f1f0dc4fc34bd01249555faa6efe354865da67b718a9e647fffb2bfa7bfda4918b65d3b8cbbb5daed8772ed3bddf4dd8f3e12d54894462f5c6bb3c3eeed33fcd27ec23052ab6297e95dabb3528652ae7b9209d7d488ec4b58dbba90e53c91e88b2dda212d405ba347b38436cbd60c3404a1826290cc913b4a3eee337cde515fe8299fe9324a285f8a9e4895862fb65264a0f0f8087991a0eb22080a1a81b5f7a2503f088a404890b5f7a2503f08364a173af71a138a0a78b225148e3bd1c87daca9924028b3e2ca7cc7a23964b1240e6d3343160d59b8b39ee4b8d039cf74ef332d1b02b283cc7d1b727517322513e9d3208c865d349443287353dcce618ca57470efbec3fbe931e442691f289b18f787931a55ca956a94777fa1bc8b13e58d5f29efde694ff94ccbce16946b32a77be7beee9c0e1947f94bc651304cc71ed65d068ed3e217ca5330eca461da9dfbabe3ba9317fa4ab8a686f49ad125cd0dce4e689433495f6c09098a693d1283a7eba2eb64b4b16449cc85424ae1b6a3bed3657cde51bed2492926ce76295f6c914cae26ae26381abfcc0cfcfc6820b6b5f7a2502c6bea80f47bbbf79a93c52e53d7e1549f8ba6eddd5fdbbbf73b0cd3e14aef7e3acc2bbdf3be9d708aceacab190f8943dba474a4fcf457ca4f18a6d387999ef23a1d05c34a3fe118327dde4b5fca9aeea55ea5772fa5ec7778377d47f7d2a7c384e3f44a385aef761f8de109f974fd1095b1d8be96acb985bc6ca150e157dc0cdfe9a8cfbb8c0fe5a42fe5a510377a7767f8620bf5c5968c2fb648251cb345e6ecc470cc98a99a583af9f991959be2ce4cc167e0e74703ac19ca40cccf9c2d76ce392de5b6a3fcf49a947bdc86537d199f69e9b8489f993bbdfbebd47da615aeae1637660ec498e832f37c8fe862f366a79d38b4f1a7a35e8ee8d29fa7400c65202dba0615dcd3bd7337dd3ee52a1db61da58461a5a7749d0cda60eb64f8e55d0686a5600fc350f009cbe86497b1291ddcb7bf381c67865fa6973009d7d4dcd7885ee39d7bcde8db4bd36efbbad0c813dd98fb0394f568c86b8484db970fa515794e94cf3fe5d38ef2653f7ddb475fe9a42f65ea2ef26228555b285f6c9dbed81a7db165127db1d5c5cc16a984c7496318895df49d8c19dcf0ae061257240e3102c547d9f203b78a2bb3f10f6cc59597178016ae7c877e48d5f3105ba45b207ddaa7407888ad9a1aeddb6bb297de3855b18bf498ede767ccb46444bf9f8f3ab4dbbf2c86e9347e6537615809cb88601b96d191317dbbf6995608e6a923633ae9abd922c2291da56f4ff545b7f6a2dbcf6f2f7dbbd9e7bbd297ea974ebae84be9289df454e35709c716e9a2df7b114e354eed4891020f4ee06447e9262ca3f312bd84613a2f7bd161dab7ef109d034a20e1890b68685a220ccb6e2c7d243cfa745ca2675f4ad3b62f25c23535a1d774afd9aebdc67b0df71afbecb1b5512153356f3fee9345640ecd4d6cd9d8ca7e4cd5fca4204933b33922ba947cb8a23126ad953597559248b0649784e879a29914095aafb821cbdec8228d43959c9a9996906575958078c3ccf017a03daedce0862c1edd129b7559837af2451799c562b1589946a94a5c913ff97451e5345d4e1f0e47e8014fab93dee9935538a20a39ad6e7a13264f70c3049ed6e91fac04318b214a5a76dac027a8a5332f714a0753e9a6cf64d267f2fd4c1e7d268b3e93439fc9de6772f799cc7d266f9fc9f63359fb4cce3e93eb676025c88410d43abd0434c2a0a48582f279b1c13b1285bc8edbec923caf61f19dbea6b1a2a06058095a51a54b5d81e8ec0c51d5f79ec3b3c4a851176eec398305573ec7c7160c4fcc22c9933c9fa387667122cf4320bac4aca5663847573c4fb36f2f8e9117aeec9f79d3459530fee40cdbd0d4e8a4224a33d1a45f77c9bd7bd7494f7bd7c54945b4a3d402cf3ad194807dbdf172afe86691cda2edf6bbdb760f257a2612fde2f08a3ec2212a775ff82cbf98c3d71cb877b7a11ba125fab65f9fbd5a955dc3ae2de85a8c03bf3498612737ac91b3d7a89fcc3294837db5e1fd667827d477950f9c1bee8752da2c6fb3c84c4a8baff59e33cfc69ca2077308e62035d71f2f097349ccfdb03f06c495c9720016b0c1d9ad7883767b613030080c0b813568823c4f6337bc12e8e6d44bbbbdc5b058869686532e6d66a69544fbe254e510cab2932909d3030d59b68087c7933c234b18b178354b2a521b53ce796f63db51074dc3b012b4aad0c5709e74b13b635629cd689665d9a946c4cd88c0a17999aa3ee5d9999167a785fc08d260cd3020ec3c519329f6a6c5d120fd15375ce5db60f78c46c5aee675b16b25907a5601c306701557faddb11a675927c4aca43102945393b29364c9901b213e3f82c0accacab31764d5b07142d59a932787489aec5dcfc62c91d56ca12d1db4b59a2a6a69dcd16c6896987b88c151e4c5460e14ea07c1d5aa8bce15f3c1751dd73dc41479b19143ce6957d765a74a4619623e9a25b4425e2e986649913435437a88e9e1e57ac1bc62ae18ce1671b55a1d86c68ae58ae1e1a3c60b1874151b66a3507395a3596068ac58ae181e3e6abc445a83c62a7a34583456ab154d0b2abda71936d21ce3ca6b344b7871a81c7c71bd7ab061754f36a8fcde19570554a974028971a3125dfaa5db565cd7719dc3c5b259d1a811dba28cb83d344b687bd878c111f323082c87cb7a396c34d8e2613b4fd6060e1b2f3796090f2bba34901cf25324c80f2137384790c07a2889e1e1831557807e04f9010403e2e142175552dd16635d276d78c4f49034f2864804830fa0dcffd16d9ccc71bd9aa55fc0d249906149eece85b4d08dfad260a31a943e70ef1e72f7ded13a49f5fdd436a359c2f853eb411aed57346a744bf40250184836aec6f0a033e28a6cd06bf0d9edd7e125bc73ef66e806d71cf57c34d87fbd77ed5ea4d5d60d5f1be98ec6c566511207896788376cd886384f6933959ecbf203bd3c4d9d81d7f2b8c71c230bf7d07eebece779d693527ad6fbe6799e77ee0b676c0dddf02c8b7cb8bdf33e9abbedd3b66bdb69839a77db2cdbfb1e0e376c716c508b59d34e3ba44376972be64d8cdb03c73498fd93c2ed87f127025523ba1fe2533bc46bef3c731ce5c4167ba459ea61eaadbd17853aaa491399a4c12a711aacb7b3451e69547d9548ecb5a87b14f81588caac1ca27245e57aed8b1905ae583c3eb9c678882d98a9aa625ca9a4c1fa0ccb9d06eb27852ba7ac4d38ddc88356e220f105e20df3d3621c110a793ee4f2f4a1691c71a50c37bcf9d6e89621ded2eebdf7765d4a323379a770c3e8130407bda49138b4bc41d2c8badddd6dfae28692c607874facf206f99b53cd4906f2071d0653b001142d450a72761a5996652846e415dc20d3537a148b434343938216b5414f2fcd810e4c34410c5d48c10bc400a345a3c8947699524a293d856e509397b36cc615395469400e6da091c525648f61b42267437c51051429a8428f0a8030040539fb8a358427677f3122673d446021672f856e64b11e1c81232160082264fa1bba416f2290f4a8c8e189460e67e44c256736e42c873472868224c09cdda2a0276747d9e4ec6f96cca6670814e4ec2356a6032a9a082253c0135114ba9145a146a60f152144cf8c9cdd339249968e4e11c418999e531142103b310959258a205240809cdd460186abc5584d105a90f5d414c139ab91b32ccbb2b39aa53ebac45c4f2917997edacc467616bdb3e50718b2e8c1177e983831452b83226755d080660753f8020c9d24b4b22539ab020956e4ec349a45cb599665d96807379663de71b133bc78b4f79e5033ac56356dc990aa0448f361013da19e68925920b23062064dd505828b18f445391114041a9bae77b64414bc40664dd3b4d3207c11842733d35059eb398112596341ab5efba34bcc5a56c49156c53c8125485a1573682f0a8922f754837278df1141bd44aa3dab59560f20834bca91878b2105f0220603d0c2f572e4e12227c9ed72e4e12249017eae8c1c79b8904245836b73e4e1a209aa2cae28471e2ea4001ee16639f270d185ca095c7ca1251f01345531d397f6420a99e6d873040ec5e962086eec0102498e3d401891b721c71e1300753c59a2c0f86329cd33356dfd992ad9627dfac90a77d0a499a40b00f1939f34283f693a30834c1f76900ea52a903a48faf8045dc004364b08a50f95ed65593f6995fcfc3a2805d75adad59fd962ab882af92b9c3801284b2a04fdcc96d8720214647de28a9ca1226d00c1556356835658a10609e5809a856679ee8ba0661965f90ea743d22df2c80f67827086d70a2f69b4a60f92d84e93243f4e0421cb276450b384bd24cb870d463d92e5659d42969776489697960a4b45db6021b5f0e9427ee15333443eb1ca611fe138eb0364e346e39721cd626f6c911b7b636fec8dbdb145ec8dcdb13b56490ee993e5b9d08dc638ea14341acda6fefc6ca11b5d8f344bcda93b35a7e6d49c9cd9125b3b4a787c6aac8769433726860123ba00e0e7a626cf7469b2836c6caa1a871588c70d9ed9626353257fdb07757d5041d9e736e1b13e1ccd7dae3f3eb21e4e9ef8110426a483105d3c10ad903d309338c2c429ba45a6817e92a6c11a9a2136f2ed4474f1403d9039c693ec236e91a9cf159ee4e863852d72687d7cb6f8c9d6a759a2cf15ac681612901b7d8c009467c8910a3168b20c51a04f9329f6000dede08189533be489c31c62f6c147dc22630fc42d1a89db3c3434b9af10db014dde22d74f1291370dde1829522409ce9128c0901b89f5c9c9d9d949d248942cf16162254f83f230e878bea027490c1189e14e2a633c2af5f80331e4e9cdeed460ac8127db49170d368bc4a913d56205c6624e6c036e0f4d1557b493a8ea8709c832f718bd85c9823c51d060c8bd0a23f7971869b01f672a82368c1134ca7d181905d0184e40d9438382ecc51772a53396819a259c4672374b7711841c71c88a0fc137d8a780912e3418b97b8cdcd12587987f40c5157a525c918fb345c3332da94d19144a1c197b8a6e1cc24dbdb6db6f384efba55ef6da2dd6709099e7e35313ebe85686230eedad8844c99e55d65a7b061157625a637615bb553a5bc64e947a79adec342f917934935996c52aaa2a204a7e5bc7d1d99ce4a2260a81c82f9c91e3b3500e5e68c38d2cdbb8f285b44a9c5c6bf5116333c680c46b73688e0be53033e56e846883f15d77e3dab822bfd0bb9103cd368a3a11271289449b4824125991481365a24a4553d42229baa1b6654207e4071d6e9e77d103c921e6781e628b5ef2f85e1f8c18b7c7c3f80324440b1720413daae2251039595681849decb595fdf0ad842c71b89a715483a89924c6a03b73fc0cdda89a0dba33b4430fc9d95f6237118c394630d59d7b97b7cf005914c5851bc626303dccb709721afc4f13458a18094216326d5ae9875570c3991cbb71634ce9aa473a6822884c7118794ec793a2c65092572231dce77005529ce77972b4aa51524ca49208273b59d2c7fd7a5493dddaa4cf5e633f7ae314c52e92f6793fd39ad9b5bf320dc3742e86d98f0edb9e5d510e8943db68cfaea3bde257f6118689bceedc76ab3d7b66394fdbba5006d469419f3c49e10923908c138acd913ba3fbd347bafd52dda62fd5da97ea6c33b99ab89ac8202020a01e69f1ea2d5a8cdb0f59150810d00fb3abcdbad61ad2b9571d2ac35dfb0764ee7b9cd231bafd0eeeda65745edab9cbd88f2ea3f3e2aee154cb8c6e71aa239008e20f21d16566ae08398434811c01a55ca6d2f6d7e8dbeb372ed53cc4d6a8d5de0f201a6e0cd6de8b42fd2048234392439b5d9f382f456688916232625e4cdadc948e3a9df4a13ce513ddfb42efbe7bee1b75cacb6c9145705c1966ad563a622cc678d0e18a2b73ce3963766b310f40329c6aaef4d36b4c4739c5a9c62e6edbba14ae54ea300ce5a6c34e2f95bafba5fa5d37a58d597d712a7b37c2a90c7bdfb68bbe947d77d1435f4a4777d15316efe82e4aedf01ebaf7d04cce707fb40c10a098a67c3af528dd63cb7b6c99be542d7da98a5d35f735a353981e427e4423576c47644457935db6b86e12ebdee91bef26f49d6cdf7491c6e99c50ece726d6a46f6c6868fa2674d15fba6f668ba88814e130fe28d13e9acf4b17d1377dd32de822fa265e30ff00a4def8a1e254db7881f911a45b9a8633591c25695347afe17eba992dd2364e1ee10d631a2c5bd97723d8ef5354f58c1b0dbbb6bee924ba88d73c681cb412d987fb4b7bf387c3b099968c4d9574d2431a049aaf466822cd32b3226da459ea90fbc596bc29822377244f8e92fbc956119c9c1d253cd927fbaa0d5da4735a49fb344eef74ac6fa66aaebc99ac4b4328994699151de5d34a1fe9a65166b78cc7498c931f4052020102fac1b4b5e8f735da4975db4aa34fd1b7bf441bd660573412e1687d709234362938397207f5d99f3eed29a3934a753b95eeddecb9d7681fdd6ddb4efabc3d75ff6a3761d8e81a9631c9a4bc64b5cba0dc3ebc2b199d17eaf73232ce7df4c9a07e9ff2c1745e28144ee9189d7baabffdde6ff81e86721998fb08cbe8bc4cdf2e83f2d139946f9fe9a3efd6c3d88f0ea37dbbbd8ed1ada69d3e9dfa6ef4d2b7d2a8f4eda4a75edb49df768c4678c7f6d14730edf7b1451a7da5ed2361ea1a5121d2a6484e698676e7822eaa507ab984d2f7982d324e23bd07572cd4c75dc6d73de543f9fd4cb75faa6ea72f551f4a652f7d4b94528a83524a51534529a594d24829a5946625d26b45a174ef122551fa55a9547abfd47587a19ca3b4eb3eee4b357e7ffb4c4bd3629a45667a1e37d248b7a4f8a08f4fa296b58bde94cefd553a87613a5dc2a97afcb2ef308c746b755cf7289f8c8ecc09a7babbe99321754f55d2b9a75ea47327e1780ea72ae621b62eaee941efd18be8b72e34a254f260a193fd05bd7c119a3c67509e4ea69db13c451867f4893e7724d752c293a74f9e9e2c325b66d054cdd9c4499e4524cedca1b8831a6806379c3bdbbc22a88a08d4134e27363c441e160fe6cedc993d789e3ca078eecc9daec1bd84b7c84ee835dc453b9ab5386c9a5463978753edfdb5ddc31b27c21268aa66ccf327679b2d5e1325b365eecc6edaed93adb933adbd77a7795a49fb28992df701dbc3f6699eed8bad0edd706defe6ce74622a993ff38a3c67153dd349c7162d21ea20a01db225c2b09956037dd3e666e27411ee9c4c9a25ec2e0228dfe91b9df4d7e824f0de843f55364e95bc992d323af332a48f2e53fabd7d0da574903efa8bf4d1c4afd22fbe53652f7bfec754d1897f78915e3a0fb1d50316838148b8f4b9a68a9ef48d7e1fc6d8c453c695b8321fa76af4f5177a5c0e1b4702149863e4d0b756a29d3ec66791a9768d6aef6f9f0f2dea6f13e1d40ea2cbe720c2b1c14dbe747fa16f5fe8338481b293af8b741291652ad14a9c14913c3eb14ce9c4892bf41d96390dd27358e2b492cdbbb36b1a4c20bbbbbbbbbbbbbbbbbb5b7677db8bea6f49109bab17128936d971b6a6e6f6a5e67cd7ddc3b0d0bbc33ce9356d7920b0d57bf7852ee2ecf62d13e14f76dc66cb4099306bad5ad4773f0d6e91ab3671e85e8e07d4a9cce0662fd246fa488ce6c5f5b1cae10766c255eede5de6ccda7de2d05ed4bb87e04d7409e52ac5edbcc3f4a13bfbf61cb2778dc3794e6a9f30dfbcac974344962e2282f5b58b7c98d9625bf5ffc47d1357ea43f76e441209e170fbdcae3d9cf7f09d1f6afbdceec3fca9c13aab7d2825f76aad943951552f71c87da806ebbb5a67d7dd13964570779106ebb57ac366a23613b9be999038f4ebb970c32ec2a2d22c757ece39a7176b6863c53ef44e280a1ac11f4184fc90427e48ef2fed7a892ef337c796ee947b188f9029f707704b8cce7d36b6157af7d508c16a856e635ba38732485ba0f7d116987d555f57347c61dc9adac18633d9e2983f32dcdac9195b52d1129ee8a1bf44384e5508a7ec438fadd7a74afb08a76ce83b460f61191d9911aeb161b5ba0b615bdc778444b8665e08dbea1e5b1e76bd7c39aae85b3542dc568c090863121610862203c25845a6bfb305e647e2e6a1b75fa4d69334190aee0bee15fd35bae8f222d14f18069b91f2a5d24f1806bb33a4df4b895ff7270c83cdd819aead10dd4dd3f8c1c34402cd1a1bdc340dd2110eae8ca3340b77da9d5e2a992d293ccdc2a45942a740d1a5e485c8f2d2a01514658a61d1448b08441095f2c596e94727c5ad398c403274e3be93e2663f82d450c4ed3cc369787dd83499c62f9a88c211b01006115f006ad1d3f0499b4f0060dc27cd92d96878862f5632dc50dad8d4e98cd3194e25ce8c2fa5fda92fa5e11f5ea9cf380fb13503c32e4e3db66678f8149ef1451a3ec3e50c34c8af741a3e199fe1433d86487fe9b8a21df5c9f8a44dabe869fb1c9146a8cffd2493d0279334d8207d0acae8933c9fb459c2f45367c212d0b27c04a81722486f634ec99ccc268925d08659ab06c15879f566d4dd4d273fa7cc892bf311cb9d06230f8e1fd927cbf679992371f0e28a892d8748225922f9b6772a49dcbef797e80244e6eef3f9cbd9261a8d46229168f4eddaa7e592c9f4fb1da5d26fe932285f4afef4d8227d2989533a4847f98efb139639fd1e05cbe8bc4ef85e86f4d8eab24fcbda2512890359d3aaf921448ca08efcf8ec072b7d2fae982fb644210dcb1c9b371ddc0a06d7b605940830b8f9b1a146534a299594de076aada596524a297e5981ac06a72b8c44c861244296407f3ed62c210ba6074ae99663cf12262cb511e7ce6740e858a64db46850cb6d93a495f4cee5473b3f3932269bcc16dba26f9be8a265fa1e22b2c03448b7e89698a9a27247f2cc16ee07faa2696a32c5400fc9b49443885e8a6bf397d2d1ede84ec23542bca5935d473bf7fbd40eeef7dc2fae21fdbef4e9a476e8db374ee9307bc2589283344e499be812caf47208a594def9d5a01bf64e2b992da553d3e99ba777327d67fa52db675aa52fb5e11f5ea59bce436c99300cc433ad9055c23a1d82a6aff4d100c38d2daed59deb1b7a243649a64b32bdc1992dd6de4b7f3fd27782d11830cdd243440cf490fb23b5904d1aa4177d92678786505bb48a9e760dcd939f2f32a5b28b4cfbd3b2b599f4c556e672b0b971dbc4151ddc8a5d158cdb8f65ddc5baa8427ff821c8cdf24066196872109bb3937e1fca941ea477c1348823bad8d3eb105950f4acc8928a200522c7a0adfb81c082e0e689551ae4bed1bd4f14fa40d64f4d5cee5c7d78999abfd3fb8bbbf7790fc374267e79bf1816a7273bcf4fcb327423a523f4d15f211ca76a845f9faaec33845753458f23aed07bdfa78abefb5834a54304b79eabe1a6f1a776bdc110c4f0931590e310ba004556a7bc46e965f6507ec391f6a53c8739eece7338dc3ecbc50064798e468a71acb8707978f4f07129b970c3f81379785cba1379e4b51c7ba0c8228777053702653f6861cd29d2826cf1405bf52f1d2324bac8cf8b20f6704d5304371408892b93155d4279561c2382084ed7d67329ae5fccb985161db1d08105686a9db3d67a591bcc76b3a19da15b35cb74e9af441c62bc2114b78883a4efc6e100b27c78036a0000c061cdf2214aeeb8d26f2d6e189b8820e24043ded0ef5f20b6b060a20b6509c30b510b395221776c61916608bfdc8f724e29e7074a1c06106f90af3400904359664ef89899899cac3bf372f6f8653bcc07913ae8e2bbb3ac3d309bc956cd128a38ac5bc5f2f7e44597ea925db6962d146e189b6020ba3421cbb3a6aa87980b441c5e30d1a5de47f2842c1044e6791f335319ca81c6184331c31ffdece7810dc6b366cbed97c4a1c61be2258f96e3c3d8042666d0c5568d7c8c57e9d67b6096090b2cbfb3de5213216ffb96e60909db431ac9939ab860e931a3138a438778c3fcc42c23b05e592fb86174d2835a7befa39356d95a71443de8bad8639ece5833ed2079c64fd3deefb2ac434a2badd92773d5c1cbd9bbfe428d6adaa95671d75f988591ce9a793674233e76ee7e16c3997bebf8b067e7b066cba4b392529a6913d3ec21a594ee44eb7516636a06a81c1f67a593d619638b26a594dad43cdb2c61bc8c93ce39e7ec8e0b5aa0546a418a49062d68215a21597e5a1a032dcc89238ef25352e9a493bdd560c5799b25c6497134d88f361a6c1afd15adda0fae0d2058b38cc2e0aa3cd3ea0ceaa4d32c3dddebd9d922a3aa3e461a7d5e5ec6b85e965846907a1bb789626b53cc0210714111d900446e28c71e17d428e17239f6204186fc9ba172d68344177296654560308a9ba98aa018ee28c71e247e4023dc9b630f1231247cae82044e0ebfaa2790544a2ae795d3e6d974d22a86541837a4d9569b9d5a39a2b2fa7431c618230ebf1c278d94ce397bce49bb637777ce6194360a3244dd21ae7cc7d67ec0dcb4717ee243b905503f8632a8e7082472d8345dd3437213c91da3a1a109a29b8432890872b408430c2ab0410f6243c8a1f4d9410e654c62d1319913ebe8a40540c8a28e4f72d7cc9c808830960862084bc08101931cca224608914389238fe48eabd9e24310648083253b4fb65085560b213721db196b42104f8c410b1e317e5a3d45ee5f9cece54682152a4600c1003f99126122532264c8f43674838239461c0c90b96ba11b5c4619b182fce5ed59e8c6c6534365468352e786a8972b9ab42afeb42a66ea04045a5d11c4a26955cc4ab069d56c41a1622d6092eded9b25a4ff28fd8cdb90e2db9843546e154185ac69a7a59f9c653c4520e9236ed8b19f1690868606081e1a1a3b5bfae7e7a68b4c234f27a42e58790576e1ca10e544e264c9495114f6ca1863dd682127e0450d4ce0042a827002103d4e94411453f8a6894c4f8375a2072567286591e98d0d5880844c8f0adda07726891b90a10c487821451450340f144540913296b08230327d4a8480154f38a2022504f2618cc1586b2d8ceb158b7fb960228d1a8df1f33dc4263e31c618e3a682ac09b3091fa8481423dcb630ae9cc748f923251631461a638cf81469157725c4adb13d7b26d1c959653fac92892b1fb1684a29a594524a69a59452dab19e2554fc4049cc162bd151634716d4fd4e344b89c32c561da02eaaa88c9260439e3f9805716b27133058b58cb3e7258e9f0ae8c5157a0a647f5149bce8327f85ae4f01f0b933f245e9e0cab7066ed7168951d4663c7d7170e5e365164839f6576badb931ea92727833496679f9cc8594e76d6c91a48c73e5a3ca6c25745165e200274f7ff28bbbcad6de3b43301603820f9cb83b477cea1463c874708489c9cf33450bace8eea69466b5577727c623d6c3478cbe6009cd6685c4b559ce9d314615f4102aa707c6cda1bd3f3589631aeca15dbb0f7d1e0dcec693ca39e59453ce4b1cf6889973db68f3e83135ec9a314d9afcc4f468c2c34793263051a820df0cd44595f06e3d37dec881916e8903144f1cb9993f11078e1c4bf29c3c730a2249b6b879d6501a2bf21444bac8da125411f2c431ada5d1e012f6abd962f1276d7b848a1f1b6a4829aba4524ada5236c1d3b35b543ac618e3a3c4df887059f3190d54661744e4f4e2662339311be4a9a494524a29b5d6524b29a594ca15488f70795e6ce0c841a9b53725c79e2558b841ac0875b670349e4e94c3a756d5c7305cfad076f40341672b23cfa3c41b503d327dd5e8274a5c49a963a044707e86700399be5a8066fb6dc3a7b8321fa2783ee4792e8227587e11661553d639e794724e29a7aca11b92ce1ccdd2b3e79c731291854b26aba856c898f4913e32267d60af4019963e71a51e003cb8dbedb577f64bedc09c2af53a890a52b841196a603ae11236b265bfa38629705a4038a086a755821aa6c069592c839aaa6aa7aad630058e9d42078253e5801a9e96c51f0d2f50997d5631a2a422d8cf6c1ad44595b0d264da302b1aa48ff93448e792e812338d5d6be9ec209a0eea201a1e164b071949b89904b61dce9b36d3264ecec4f521d833dbac26b38c7e4183324c812850a6ef9d69edbdf3872aa1c8d4c79b3eddf4e927dd12c21d9b35dd90da759ef499dc073355f4351c71a1d8bcf8dae17ed238b62a9c357348a6dd1de4136b42df1d343b1ac213e7389c4428928963278e3671b28933a5c8f491dad4bad32db932ff40e9251d22aae88bc834894b95c8f41587a84c73ba90e589a94d5ca19f5c1177d263267786120a9c4c3f71664b131155f447e4c4c0064ada2674a3876472c68c8a26964bdac6da7b51a89634422a8de905b4b9276eaf4212e85ca3cf3d71b5980b26ba94327d0f99e6ba7c0d5fdc17fbe1c81eb7e87dc3dd8e6e7b5703127706ad794c00bd10b6c686d5daf01755f442d8d60d2b35e4edadc4a79095b8c6d6c5c5f71e0d43bd6733e4a641b06d6e6ca88fe842730fd17ac81c92da214b452a71673645245132809be8c283835c95c835895cdb87c329b9e18dc3323aafd0b9cb78dfbe830b9df3bc875216bf369cb2dfb8879e7a710f9debcec9fa1a90b832f4e1a896082f727df7b54d6f32c8620964a3c4460229b119722381b61c1b4cd5b7cd270c44a14cf631f10a64693736077da4bb69cfee96565861450e591a62d20994859f5a4317eeaa4392c644972fd3f7882e2497d9b6e3fadb2605e9690d5f5c170ee2dae7a05fb1bacf292ffa4ccb1b8930ec7e34d36a199dd7fde832a48b2e733fa29d6374993f19882afa3801c0832b3ae9323342291da48bbee37ea44374d277884ec2309d1dd20a3be0e2062fa0a16991f09daa968e9c4662cb0be11da593b08c8e8ce8252c33ba5f6c75a21cd1a553de67b28747df2a4774e94c65e69eb85a5c999d3d9c23aed05beef3eea98b9b61f0ab22973ff50b72460e929d9c06737696d41c6a9454a0c9a1a4024dd67a882e33d3675996512a8f9044642a83c840f33386259206692c94e1304621d37758823c710f394d7bd29eb427fd86855bf151e04fd735721a046bf491adfa4e466a73844597104e7419407499b9be91ecc0a20bcd0258c2cd00b073654ed114c52eeedeeb87705fe37c9d33557513e3f60d8e146e289db48d932adae64b7243e9244ea18323b9737de7c4313861840b4b2801c517805a7d443a914e52605c0d770e1ec2adb48b066be3cc961cefa1eff01ec232ddb94e86bb875316df06e736c5d5b004c2d24983f5b80877c2d480c455d5fa6ef694dd54442986c595fa9211892ab5b6760521503124f4840655e8a20a08be0630dc50f2f0742f3ca28b448991690c43a6d18b4c1faf90e9398d4a9ed992b2b4ebcedd5afce2be61d8760ea7ec630bc6bdc3298b6d8b6e53dc8aa5120de322dc50eeb4f66672b51c2fb9d76aaa68c6e4d21c99dc28a5ec2ad2308fb8428f852b314824d2189dc2912194083231b129c1e0081f538e3d4b96a084114422a571efbdac958bd2d31a4b9ac8deb54a744055634224887e712bada75bf670db28addfb66b1bddb62daae86128add46a543bd55e1aa477f9c034cb8b521bcd224fa190020d6f96415759d1a55b624b8cac0b4417fa18558dbae25611df80d8e29aaa992cfbae596bad36f6d859638d353eca1669f871657ef20702ab42f0f9810abaa88223bacc56b7b8af1475b839f440a6b2d5511129ae4452a68d6f5cb93adc4c7f587b5936bae5029dbcaa32ca31267b8e115d648e4d5c54c84c0306c61583b9f75e18d72be67ac1b85e30ac55ccd535ba67ffd8c8227ee479d8c82e71ccd7a3c196f23c7ac4f9f81ecd127177ecd851bb7da77d3d627834d83c9a45daf77b584dc6f0e8d119c681590df6bf9706adb0c28a1cdd3de2974c91c3db3fb8a1cd194d271f35d20580501271ac001d820c60a045567d2ceadd034518446826788191eeeeeeeea63dbb9b3261c2c4a7079f263e384a39f62c319243544f7592592a8c484204303ca9f72ddd979d861abac171b53ed380c9cebd725cf6ec3e7030cdc2d5d75aeb39bc7d309febd560cd7e3badeb05f39cadb8706bbc6aed898248ceea911bdaf823457df7f282ef6db63be77d355a555970fbe14aae7498fdfe61766d2c2348bfaa24c8fd4af30441e719a748fb4bd355c7d16870fb3e55f516b5c44b532f6eb8ca3514325373ca6ccaf53d82748ba8de064eaebd53a35922cf929c5cebcf92277218bb90118b5c3fe9aa59263dcdec8c08223c8944c8f133a24bccf2973474a3b69438e8434dd3341ccda2e16e1c0dea109365966326571b3824061b6c9ea661a359ba098d1e2880624ddc7e54e95ad34515ce4991b8420fd420ed2f32d56692a964ee34487170399ce812caa3df67f473e77ead440060dc89a495a4e44b13091039464ae2cb934a1876b19d2aedb0119e69350e9be7c6fb911d7d8461a393704ccd9e58f2ccb42c0ebdfd3b676862dbc9eef9d9fbd15ff7a36b3a0a86dd9b1ae5a37b6bdff7afd1efe8250c5b8d7034dda27c2f5dfa8bf41289849a2aad278cf4128e5355fae88dbae0fb46dbb8e38a761957343b5517a859681660ec9b21779374644b2b99aaedfd2e64e38624301f7ae82fed63d5d1c073650ed10fc19138589c9e1e239de6cd39499ff6d117e7d612ef207d8465644aa35f1c5bb195ea1da4df7b1930dbefb8279d84654cf8e2b7ec6db9433bb774424316985802354849a49209e5f6d36442e9e429e5744af9c5d7963eba45f9bd2d7df457e9238cf28b47275de6a67c32a593a8a4ac1775414c95a2190120002000c314002020100c870362f18040226b931f14000d83aa4e6e4c1ac9d32888614e216308318400000001181111d2b40920ba942a8cbde81c14c3fdea0a82824b6abf724bd5fff5abba7e36cd081f42841a3e8d589954911c46deff43d4a417d352a5d0bf67278edc51fbabef31ae69679b2f1dd7c53718eb4ee3afb8976666a8e4a974971f14e7bfdf38fbde0ca8c6dc55ec5d42fa48ad8d2ecca73c5f6996e2f723d936103f66e275c9145969712f4612c0b4531b223d07758f54d2fe9d5b254d19c99c68c975561f8a97eb15c2e93bec2a5bbd2d9fac9874f4cbf027ae16f95b9dc32c4f565a0d2dde3ea347a88ea53a6e693af227ab8074de3b2db55046dc750c71d88229e79b6ba4ccb7bf918896d0459cd1e659e21344a9c2c948c1f580b88168fd34359736516b624f824f43d4cbceaa5157c429c9d4203232548654acd9f60cf4d5077d4f4e1249fd985da0b1dca5d7b1915f5fb94fc274ae5616a5b25e6d5170303271e1b05d68bf326a82f8d260bbf471cbaf7a35e9025707ca0043f5fb7ab67a840b8b3263ecc7a7ddcd4ca2c30387d8b46b73f237b8df39c7a7e7683cff2cbcbb2193eee4b46756a84d1c2b465119c01182ea14d3cd3412a64fa67c286e7f82c3ef05221cea799a616fb80a57c5270400df974dea340696b8046bfadbb678cce68e7c8d00faa8552057e02b5fb713560ae861610cf83d2e4e6bf81943810214c7fd0402c44a772326ea421f35d8bb1800341d473566097ecf7143f5b041522aba2f3f8bca1d41f35ad19a653f799877d78ea3a01b13047cb28f6a8a34ecaed45ef33d6bf160c45298e1c6ce52a533d086fe5afa71844882ac39f71a41367c99d302a77f10f59b4bd36df93aa7b2d1552dbc1232add751559116803536225b4b0a121dba218946e5136d0e835a344bf0ba5a7ce6e16d03068bdb3a852a69785b99a7949d32f24b00937601115dbf53e1e5f7a7287eae0f4532a0d76323d926d91bfc7010afc08de1a571209d26f999481337e3352418b104402042636689f359abcf1d93235f48d5872853dc3f7f3ad2bd331c7cdc15c9d26996c706b6d20785c8e81f1f91cf58df09529a6e46d78a0955eed735b51fd8ddbb65e6aae25de354b86647473697135a38ab943b7235353d654ecb64608e63cb11a83e88a9c62b920735e21b7831b5c2f39f289cfded3b3d641d8e781679335c8a9079e768c77461e814e3e0021cc9f25738c7225091cf68dcb2006da4adb010fc4f8c0546d0e76c93b1b3224ecc30c4ff942c4781139cc82ff2293312b3bf47f2aefc7eae52618ec3c09fc1d3df7203bc0c850d1039b68cbddcea7fb8c622d6b9d9c07aab8a1e94e21dbda163626c5317278f0f06ca4ba80220e22ca74839e36d422158da641399e40921df24305e7e82ed435c376bb999e24e1260d285b6a889a0d7228a209187a20001d53b9d38256b37eb7b20a24fc8d251f69ccb4dcf071ea9432bf5f31467bf588e446c442d0a3fcac9456c150de2f062ac465c443a4b26f569e763cc131502652ff50963634a3b7297ed7edca438a27f163d318a37d428c664bf4e2f501236ec7c18c1d4c97aae59be2c689146c1d4d0d90e5552f8216bc9e2b6de2c813ad9c1b7fc487a7c1bc86c4d279e2870207f616f06022e5fd97eca0a0f0c05f5271b51f3d21e15cfb147bb692a72ed7acd5988deeda8b571b821aa63329dc7676b69b753e1e288a4263bd9221bbd1271b345f8a62c23ca5c946d31b0ac65ee7c057af86d8e3ae749202f030baa6ca2cb060cebcdb88593f0733d995bed518ad7b36225a57ae709c23847026329eceed3d0a0b680e29db470a3ac7ce5b80c0d368bc1a3993d949e9a83e1a4319b740b02e58ad6a0820ab7a017adb3d06d00c8ba74dc4ea33e52ebd244af9152de63303c1e2b38e0b66d317bf611b9fb039518c189f8db5f6a5502e2c9210746fba55065f4631a725041fed2511b0da29ec8373c15a5efa4bb7eba86802f5d5a1dc854bae79daf08254c837fc2b36db3ebe78d09d7950dfbe9a5405010ea4de4aa0807a1340e88284e10cb8d90a2f13935479c3f43df595b3440f2889b7f35d87b3356e3b162afe6b4927ec0ebe42392ec9fb999b4fcc2588912d430e4558d174f54bb361c4ff8ef4b163ec0e790f9dff1ed7c29cf8bc9e64f572ff7dd22eca03f609bab34fe18a4762c25593cd178674ea7b502874384956254f085b83fb3e2d7f7e318f08a01cf8b8c49f495ee913b6101418c659b00c640104ede37e2a3a4024733529d721d4003b5e5b786c29a7388450b3a641ac258ad9091b9bb5cff234c89b72c886b043c21cbddb5c6102d24264426ba7fbe73faf96e1dd2e69c50fcb430792c64a0e3ff061b0439e4c8a88794aaf6d299ee70f8fb3a79c779936d6fafa153a11be724991b0069a8fa52ecf46709a6c4435665917ec884383125606c7775c21a035430de4293ac57cbbda062ccfc23f546c2be6049383bd3f83964a411475d24fbd8c9e43a6712183b120f5009fb080eba7d0b0aa3ac2149a8229f3091a94a820c14219ed5f5914b32bf934fb7ac5f128218b94c128b2b691350239d536e907b74ac039f3f2e69dc721927668118607c1b68fc0ab47f85e79026b2ce30f14c29834ec3f14ce5fbe05d0f1e4e60c1d9a1ee42a79c41d659f2f24b0e1f82edfd56bbe680cacf94694a1760c3144333d55bc7f4d9529d5f8489e2350ef480b787f865293c5152fd9ef71c432a358a0829676949b0fbacc62b9b36f623f03ddffb1e3e91531ec8c128d54407a1e2b82880c465afa49d2fbf0fdf099a8b6807bb2f881ee22caa658f6ac1f1616f133d5ca35d2193abb8d4baac97bd1c4cfc3128866f8eafb35ca1ee82f2b005b8575bea9f27ec427885d50214f1fe6277d71b09b07130556a2cabf00ed30c9c20d0dd9fac0dd6696baf139e6c07ca531e1c8c6e1a3084b0e6e0d50ae7275a2e14140c8696734ef8fc243ed3bb27ec253e2366b99804ce1f90b094e00a5826500b1ec278068d9c5bc017c71c307331f2b332ecbea224bdaa14fa6357ab8b300d2dc81d97144029a9c1f87efd592fe367c51b3836c8ebc51fe6b8b85c123e6f92a9fd7f0dd62bcefe2b0f00a51c75250851df3e74ba4e678e5c49d88dacea588556380fce909cff8039b82d811e0585eeffc727834bd8b8dc6601573e8fbbfba51489b97f8a12d8fe4849761f6578df5ffb8cc4a5ec3a3cde9060385227f28411e8b282fb124558a600deed95789138c922928e4de8c03a82eff7fd4d05dc0501e400388efbd2b91920156622d7f214b60721ff5e1fd3f75e121ad75f76fef59fe249d860b505865d44e486c6114580aa86ab911ce832c155ddd186b5ba2279c00a40c89261e14c926b4490364d17d1559fdb6f79d73482d6b400d73fb05aa973a3fa096317964ade506c46db2aa910f91ae70267c83d62a701817aac49d2d8db3bab47cc7b6198bc75a327cfff3695f557dabd673448d45115a151138d2d23dc5a31788916aaf88f3ca1bfcfb1240e0c32ad0eb13dc1f5b6342a3090695c1a87c735bab6f26a0af94b57b5480d4c9b4e91935899a82c662457cf91a7616ff42d541ba3ea0d46724a4729dbe6f5a063af1fcd6708ca048152fdab2efc64e85f9c2f93054524e7a21a743ea9824712d23921a4a1302109b4783609a22ef39407ad727ce30a02ef619ce69d4886502ea8703104510f52d1d961f642d0212b0bc87d6833b8e34fb6f4cdd55aae01a63fa305fe141d7a6b20541e3054642392c0dea3984fb6f7b3a8deb36c1111eba7dfcddbee491bf7dcf1fb590aeab2716cd9993af260eabdf21e3323295a34bf5f9583f6ad63030ccc6e32a2025c429ae047640390374f01822fa23c72ce5a9dfbe386498e706069f4895cc518b49f9069d39b2587875200bdbc830372e4003d57ac6448bab4402224bce39b1d2268de6cfa0e1bef3232dcc66810fa447280227ba8c81694e1b55a060295b66735bec3b69929d90e8d51db54bfaf4008ea06e5f7eec487dcadd53cb657495bff8dbe03d74bad404af68385497799faba6eb2207b6a943a63b3f03c68c80f3275bc1a0bd332ad79416df200bbce6ec7c8551de0ba6939d0f932a171ce482f1de0fb926b94ef5685febe1610cc3edb4aff83805153de8ea2e9f9e5e332a9967feddbc29c5a00e17b75b7d59b2a6d53c249730a749b6540c870f18388f102b085e44bdb44889c2d956a70360395465bd0e34b40ab5c009f2896278258f705fd28c2f2596a79c10de9d6657336b44f658b390e4e4149bb3682dddd2b2fb48dab1750726bbe1a059bc60c0831589ac33f750ad1beca08f5cfcf0a407dd8c09b28fdd3d9ec31fbd5ab1fc54978bcf9e6a857bd794c7bdbb3a4e9298d07ec1c855ad8030b0520d42df0ab770317a3235f78d1281622604401195becbee5143bada08130deea7f2301e3b6172d7a8715e223bcf31645b27e0e558e5e5ebe610ec4a8b73ee667fba15728dd0e2234009a7ff9e6b96579a3d04fc84dbb5a277181fb379315dd0dd7d820afeb4cc69668a8690d493f3a54604f06fcb73e0dafa07befe56198d76bc7e67d47da62698b0f3b1047386e110e2b5d84da07ce4930f25bcd6ec27fb98c2c7e52f5a4e3922fcd05587c3fcf4649449b8fea7cd947df28f9da4f6506fafd229008bdcb101ff1ad4e0493eff08cf720dceb6852e5d40827881130e003c2ad9e54f15794eabae60b5a7d29075d40c7e9c264d7c5c426b00719a1ac3369c1fa29f1c89d192e1304542709ebfd8a15e724912936aa3cbb48ade181c70ae8b4635c3d604a9686e8432bfe909f6365c1ba7ca85fd3c424f9ccd91556dac0a137022b57cbd5615a189f8c4ac403204bbd61ddc4fe568aa1b24eee6676d8e1a50c4e4d3aed42f21d0b0aa73a806cfd3561657979d4c335be15f0579ef847107b0562dc126b454e39443267a441722ac106583e7e06dfbf9704290b659e9e11ec901f31b00dfaf26e5edec1341ee2bb3260a1872ae7b471307bb97c112be0bafc848de2b7e7860d62e517b6f7516b2c5f26493d6f7cc9f2de67c93e44214cb72c04d46277184c4fa5a31bce681153484768618cd68e147553953517ba55c0ea5867a4088e2d480d31ae67c9b6a3c6d6bc9e21a32c85d89a9d4733ec97e5390942daec9e6d357b2609a6c58f20a76b7e0040ca48d0bc2fcea5132e9ece05fbe4669e275c5cdc02de7398513c79190f3ea0d16410e7bec85e7b055beeec354deb090617b64c72f4075e5ce16e590b599ad61cd043b603f9085be0d6af7d586e7359016dfcf26e5e18abfc99b32e4915346d9b76309d25ee98f7da743adce963b25b7eabf7f976cbd9d1af9f2f46a287c8cb79ec0ea37861a60838c00d2ec566bccd4e812f0842d4db134ed77359df06959eceac428239e854052fdd86d14941c71bb9adc208551f39e3329769104e0c8eb24742735b828c2e2247d8d6453ebb881ae45ca8887521833b545886039302b9ac39ebae366307a4d9d9d3f4f40276d073282e3c51e5f510da1cfdeb6ceb0f45bdb38a64320810a8efd61e0527d6290adceecbd775d47ea2b8f51cf6d06b68516b2dc305f11b656f6c3b6d8da64d4c7741a2a6c056cb64713abf8bdccb655be03cf8292f271ec0e8a468391b4dbf66d975a8005c418b8a56bca370a448b59aa1b37294616ca10c59fc130d7187520dc90a6b6864d2b7432d4ac0e3a7c87853c8dacb90c9630d7c2272fa499214ba803da96f7aba412410cca108698f74e0b2513748833427eca73a3e79ca3c54c6451611367efabcdd872406d8d3b28abbd91ab70c0ce1542f4464e957a59993dbeb2518961ee07201170b1049a010c407c01d9643bc42948e676f14b6271a416e05cb140832cd7601a2906a16876605ddbf5228a00b78efaab6662889966d902bfa16467ca55db9c9dcbbf43fcf4ed74d7bad523288f494f1c3dab4e2422c0ac4fbfc7e7d8cfaed6f97981546c5171023e460c447414c1bb1fe1807374da41f78d4240c54885655ce55fd4429484da394f1f7949b0239512fd02c2ba92398bc4d7eefdd94fdcd14532eec727be4e5c7bad11381c7c2544a7d9b67a5826f11b1e4776aae6eb636c2d0bf32ea995bdfb803ffd8a7a565e23587cd66c3986ae179f6b129399f286aad7e4ce26efce8532c01e85850ad58f609eb6370a7dce18b8018c85824f37e545b95df1b7e04638c11546c006ba5c904212833b48c0caac3a21e367370cc100df488d20e221d1c071e06c16c28682b7a877475b6e123dba7c4420809d3747b653928c2fbb55a8614be89a5aec4e20b36b0430939971975ba923d92c54a8fe760f14b5d9e78f6141234b6507fa84b06c87f47c7939de2dfcbe6f910c3d056fa4977b51eee5781c4c536ab4af17fd623b417e3be4c567f18c29353d42534ab4a460b55be0bb5eb85295b16697499d165de1d5f3e402d51563c83a3e25503ecc45343253ef75d71f358bdae4fc65dbd7397c8937530371b34c803d3ce74b464840b595b84ca08671a610281fe8c1b3db9ff89387926d21acaea6967ca3174cadae1dd6585dfb606ab396b2827e0a57195524ff9a1326d427964a27fa8d3dbfbff44e550fac481ee644e1d89552af309c9efe2cf841d2126fee500d5802f22b60411c899e14b0ff836ccb85429ca8a2050d4d623e691184941ee4afa9e29ef8b865110d6861c4e2f305d9a8351979f4ebbbe759ee0cc9bb1637a78285897fd321ecb937ef08cac77666e5caaab72e2355b11416ae100b304799f5d416e59066edc0b833c01a3410c1b9f04959a1b62e320316a9268d7f1774a215ff21b62ef2896b8353335a32903abd275760197aaba2c6d8918d24424eb0f492315abf235c01b1a29a8b713d121f9f32e0d48a952c2882191fd836e085cceac673cad2f847ff40292e3c12f2764d29c4b2931f2c2b489abacd2b32aff72b1b0c58d242fff95c88cc99bd122d147f6d6a270403c0d8569dfe492478fb051fdfd5564286104b78c1c9155c048000d26276925961efdd6b8feae919f1a5de380f474007b4f9149ebe8bb5e229e4fea7ca54e11efda06d5ae5b930406e0d8a94c16bed42ce73e6d217c6bb5fda479247be06b8fabec7b99954952feab89ca34eccc4a3039c527955fd78f12c85f3069a7758287c57c27a9651e4304e07e4acafc3f6b56152fb7f5d0484656d3c378f5ab38346e70735de0461db6ed4d491f9af1699a0cf3dcf6aafbc3b7a77a053433abc351a6af39d59fcba3aea431b19d7571ea22dfb11c71321ace3b8c37fe23c874766ff44a4320a75d54130d80ec7621df0219b8a8d3e116f5e1d0e85a38f0a84c15dd40af3ed4796a5291dcdf7b012b89c528a348a06b5f9db31b4e18d345f36ded3be82a879744cce0847b0e13fba15c013ae892f0ee8c7c6840c1a5ac7318de809d9c2ea43f2e3a5136e8858f82bafc7b14ad066120226191be7ea480658ed5103c85d3224b110359236e647d5163f2e2f9603a6851bd367e395c84b4898196faabf63eebf1e4314493bd14f09eee37c4f42b4b86de4afae186f5b5d75fa5941dcb0ed5afd66c7f4070ab0b4585632d3c0e8de767d1865ea03bf397cd50e4fe0d7e0203a00b656f2003d3f6f591ec2ea1c6ac7542a567e39c366ef52ae892915081e360014adf7a594227c8f4bb03c4371343f4e24ec34bc23ec80cf1e7db1534186dbada216d7b9cce945e87614222eca43e0e33304c05939e44f5754e7ede6ad2f6268fc142619351163e7b34778a5c7807ead0695d07113e7a11c176a94021c3ed9d0cef3e4b274ae293aed5d9d3e9cfc45e35f5dd2d4b4ae761229b05f8c81db99330c207131b319a13d527980cbc82cb0704fc465ea75f02c4cf9bcb9c320a9721e27a4e9c333133737412cf8af2b3692334d3c4a0fa6b0d6eec86f69c1f88582c6d7645c9cbec53a8a47658ec3e1de06c9cf5fa67e4031e6db6895b6ae9315f67339fa290511850ffdf64f1dd5ed66c08cfd53fecfda9b66c7b49d9fa242a835a9da124ac62891871c1fa5be35e0ad9eb2b27ce3cd42e767ed91e4486bba2ce72df8c112bdd0b79b3562ee5f531d0f01a97f64238ea8b28f054fe4f37b6ae859f56d17acea721d3b7eac6f3d7a85f2e8930bf1856b8ac75cde72eadbed59d53f717cb8cb54c56589f60c995801e94391fc4724e35f0551e3b301f17e40584ee415819b3b7fc12d92f3429c3b54079acc852647a023279c2d036a6e29d480618a9df651e150166a137efcc2e309c3f72b8bb590cad436477909f0b8b0d94b9e6c7e1d682d248d67403c55ce1ca82ce8207b626e4ba6d737a16c7612a61e93984dd6c28a46f281fb34122bcb7c248ae39207674ae287a1f79387df61395cb6679443e482493926bf10290ff5666ae014d6b82b6bf139ddd89a9e34460640b3caf0b70485f4d37ab6ab6362429ad0c8658b76b900602ae8ccad4bfa3582bc323ae946d1b75e44d88ca86d0e5f637a6150882ae0f6aae405ea12d6df77ac663e28ab6e63ac9d3b8082c951d2f83df4550f2e645d4892498c52a3dc023f4bbc5500a3dd810a382966ea97d0697cb4fbb338a4f0b078f367ba4938059f5ed83a54f39788bb8404c35309bb49943d730237444bcd1467f268d8c83c6356bd751820ee3c9c576d3e538d5a87d63e0fa4d35ec0ecc6c08976f9c768e593d7229d4f3e4670f7063698fd873ed5712c0bbc2fcb87a08c432d3523798cf0119d51270496dd5e2fe3e56132962dee12d2c2f3c8f41f8fb29fc13975fe24ac35d696fc52bde035f4134584a005936c80a4102fb56d517fbca6b689da0b0cbe0652848599c0737597fbae03741fb0bd756fd40ae849d8c15ebbdb1adcfce40091323cd7f0a2d06f0fcba69a35c0db345b3170d6d03b7cccaec060b39402f8ee244c649b2523db53c25b6b4ce6512e08d2dec88ec22b203a2901ce5f433f6748b6aff81085733a1174ec4a3c55219f0962c00d8dca47fb4953f768e0a7b45f2147cbb8fd0871a2f180d82af43d00b3cb0b88fd63ec21e89e1e2221d69c7bf8a01cac477750127a4d4e99ad4c9bb999ef51e493b8c8e39c305fa0b86284dcf6c1e714c40330316d555eddba7674a83d2bbc8762a22f69d4ae2ff64381a2fe68a6a2e07871d4f27a9df04aff1ee0ba714262ccd301c7c3e4f18ec3e05a0f9d49c0bf6bb287fc7611aaacda61533305e7b488d4e03701f8d26632847e523797198d2ee4c8c8865cf1876e9b2bcfc1cdfbe6271a231922616a2aaf372894f6f30d28b065e3274f89faf3e02a24b0a541f3d5995d509511d0eba57da48e857f40882b2a65683b1a48d59bcd823f34094b754365c2db67dee8857b277a206a21f42de6ed2c55fd11ffd8d665114c780d8b7de7e685dc7f6fb36b05db99afb5fb745d74984524677c6babd41bc08b1228a2288dbc19af134abd271d76de955e0c5f4aee350a12487ed1013310a72ae9bc44af61a650d1a8b3f006044ae90930423b0b45a9d669502b90bdb098f9b4ecc5c8622e7429a0dbcef72b0cf84c865a8e938138af67523985787f0ab9b97da16f9cc888beafe8823e0a02b8b200756d81ee15adeabfc0eab9e6a3180a6f7c7bb1dacc65de75bd082d13b1b5fd055c487c6f1bc423ece879ee7f87348cecb87b6a8513032635bd29017deb751c9e08582117b880f6c355db47ad76de14a141da2bb894063cadd5c70520ba0015b286f61eabed0d88ded949251b40a1a23b151a1877329e86cdbdb1310936f05cb4550e069192a93af62573c4b708ab221d94210b874e5825bd6b2c52e78e8644773420f3759c654ba1e5d258ade0963191b5a1dd2a1ddaca4669666deb77496021ed3a14f0ac93b0c7babdc59789ae8a7280149a8d38d1ff581fa38b94c16ee8654ca1b6507a9a2b8b0bbb06b58d49e109e2d9019766926667e3fb0c2c2a41e783f7e9eff953e86750555e0d24aec890e9951583924fe18987eb161cdb84b13161c8d41caadf7e0bd6f64ee815053ccc00bc967c84584fcdbe49890a747cb647d014d48deeaa024afde198581960957923c48d94b32e93af7f9eecf7c635c68c2742cec1c880f20207dde5f8ad586e55d6f0987ee5b2519fe8cef1379f8dce8b39a42e38889f7e1e4c6aa3066b780d7ef584c3fba1b54cb268ac10ea5db8aaf13f1ee2136dd87325541233fbee10f25b3d1515bfbf8ce2e3c66bb0867ff1ae219f8805caad54b0a3191ed46f7e61d18020c3c9d8f475d077e188218459fd796c46b7598899898b03a0464140a2154bb93cea55f60d6d6742781cb0494b0954483360abf280d7460b52d3fe84d95b60ac9ed9b977cd6b65cc9cfbe7b08dbfe3af58c2e8d8b847271e0bc4542c1989982a0a22f5453e0852eb76d6eb362ffe82e709133431e846acb9184fb4f44918fe97389528384c01f81eefc1a1fb7157b3b864a2007111d5fb554a6d0ff87097af4ccca2db136b7e23f79d6aaad245ff998800442ab84a424ba24ff38599655418db7c07da9376e5a474a9c7d81668f8acdbe6e17c3f49035e01954d57538a98ad3667d2b332916cedefb47e5202d76441e6acdef3c748ad0fcc267698327f32b8331bf3234590be98077ff14537119c4f2dcc7f64c25f2f76bb187dbcba8e5a227b4f41d43a56b6449279adeca752e449362c7d0818fd99a4baa620e7e057426b15b43daf1832c4219959dbc6534b6667d13312c8ab12d38f7f5ea7f8b69498eb8abaf8a716db958eb8706451414f956e505758890ccc4bcdb44e3ae04d1d2a21603c53367a9e6eb009d4171bba4779cba21816fe0ce2c371dbb09f018c59ca15813e0a04119475bab3c1f0d3ca79884a1ba3d8f264367db973ca699a26bba920aeffb8c98d4acd309a8eafc04f52a4f5d4ce1adda7e0dc7d89e759e2ff479463e34a5676f56f796f6ad4e5ee454660dd642cfb5a874d4886a402ce029744bcefe5f81bedb06b1dd5b1f63d6da35bf22636efe884ae42582bf46baf6e0592385a31c853d564e89ab538bc12ed56fceb7dfb3e037b93d70ae7c315edab48a9c561ccd9dc91955660d371eef1548c88cdb8acb9fbe5ddc4cf7e4f461c335e0f8a3e8f2bfa8e3ce1227b17cbb943fec9e811eb3be16b4bb442d3cacf352dc9feb05c0ee0b3f044dca5477f3994cd25088209bf36fdd82424d8d695d0c5a722aee9ecf28c352958b2882a901e93c4c10ca49e7973e17f96fd56b99efa37dc15a2f45ced1cd63b972b18c1f1d7a7fb6dbe4e483b91a6d842d8e141f3bba1277310b41acee62472f184d24aa2e6423a3943c05e408a20231099174373c763e690950b0ea6e31075d414818c4536aca58f98f5ebebe3488b4a1ffd1e023ea9df786666643d2a4b729f0060eb53886182a9f03435cd75f318c2549a79366761dddd71ddc21d5ff01aec0c9a33d4fb649f99759a393764df9c3db4119e8d73b53dac303a8ffa3f92936ca1f4abf8d0629837090a863d85775d3d4e26e99dcf03cca4c9cf32c9d98154e391b6c5ce72f28e912181ee7fda8046181f69caa360506644df207ee55e8a17223bcdb3719518dcbee8796a0a5ca43003a757a841e88563cd4b72eec2c62f2b081a60213e25b810ea0fcea7dad26861dd7ed56c98767fb7b5a5a6aee612b843a9930bfe15bbf25b6c2aecb170361c67faf22a5066ac01b1c4c9f416b13f4a1081604bd2054f9e46363b67866577d0c621046f7188ce6397ac60844e8d8a818ef1fa09e851958bb252381b84afc7f382fbfb9d7c857fcd79fc8acfaa2f78f88ce3899e0cf7146b5011880d1a1946103572b4eb3092ecc77c4d744d03229a4be0599007439da4ef36fc69c5f59b229a46e13c616064829997155caf03bb0fc07dcefba8477adce1d668edabd14a708fa5a496950148901429609dbafafe7d6ee9432376415a9d4ccdeb11446da6a17bfc3015c6c449a0c9a371fbcb7b24c9d75757ffdfca3f0c043d91124c776ec3b5083ff0cf93448ac917acd9ebb2bf44aa4f5d78b138c50cea407ea53887ba4c162dee9ad00aa1e7ae861a8219aedcade0f05b14374f8d12cc7562a4058c2620308902948dfe6099b0d6ab962ca116d6d97668413fe562fd1a6308ac0f25e8034d8366781757775c88417709273b14e5775f4aaae8d7592b848a2eea62cfbfba52884742dff83cb50b1ce52b31c2896077430c9b3e633e9784b6876e32d065276df0451a1d32c0cc6fbd87e75b93049d37e6caa0fc6ca82250742229863dc0c0dd58ea17249bd06bf7856b3e28d0f5959e9dfd0de7552a892821c1e9cebd1b54aae2bd5adcc79d6937997a086f2ac7e8d1491a27ecfa0e6bddf443b797689d04d576469934cd21fc9f80e65b1c8100fbf423a0a1ddf908dd24ff14bf8f05b9fc8506e4b8a90be677da9560c2afc83de491cc4b97f94403abac0f0a194fe64be626019bf8426dca240383b6b8b7f8ee2d330d5efad6939ccd898bd0d0d8e0142389737439d98512164a7c1a6882194e72292abb10cb6590c0ca258fc4eb274ea4e3c342bf379927aa4477b962f150394ea6832831c8fccf69a5d8a0b32128e18a4344f1551cb06d2950222ba23b1c8c89655dddab670f4846308349cb9c0232deec73a8f44e26fe46f03eb45e5818b704bc967528a9e3620ab508089ca2b9588d23a9b664742892c6927e758dc1223fd388b65fae4fb55b416935de3d050b654918b8e9f78d91b49b994a69c179e1a2d80789b4e1a70843111a8d3bb7350fcba57d154838bbc1dea4f9159001b5bdc71f35a629f69448d2650fa0032919423aa1ca27b606a6fb4256346dbd0a60e3384f5bc9e1f177a4205e8be892ca8f6919246029471131f4efa04f69f26a3d23a3ca07a57a993acda25b63457e1254400a61d9e4e5cdfc13199bc835bd22a848d6fe05801ae7b8fa8a10b30bb7d9c8e4cb583e8aa5ae4830c9f14b933354dee709f909c1df1c744e6124d06a7708e5b1dee475f0a2c06719bdddbc88e4e43b50733fb956e4033bddba17c3c2b66047f32f304e54f56db386d4a0d10e52f93b8440a164719928625ef4a683e9666a56b0320717c0f9f14fb5edf9fce986b23cff84a2aee5271ce5f9798bafa4fe6f6c7e7812711692e83363c017b946c0f91d6f8be40eacf2fb827bae3d54d6d4f9387b8e92f3f82a1392b25f22f64174a19e14149a533725048908b6292200567ed9fe2010c1a7ea3347727ee569d472d03e260065991e26af101c251ba23a7d7480643295fb203d5b7a3b3e6e25af36a9c47ad7b9c38498e771dcfcf31574b8390c9191075abab3f89d01bd559effae6fd0c75e488cfc6379b8bb0dd3be1f5374c87758c011ab5b0313a0b34a32375b190eaea9760f336c664952e07beabfc838b9b8a81a5c420bc1d2856a83b67e6ec93c1b6258aaeca26b04145cf218cefd7b613aa83e3b9cdeee9fb3f1f228bbd6ae65f874212705c407752a0b1d740caae552627de4ea66e87360a180e31827b7c9fb0835d9c7ab168706ff865dfc7e1deb6e85d1a544baf53944d11fa0f6cf35c6355e202dbcecc76c52f3d188a43db6cc9dd73a4f8469a824b196f62124d32370f5a85ea45c14abfd037b752383cb7c5ce4725f42251b4ddcef8b6ad07e5449de1e58cb0fe500f6251ca865ad36c08340110ece19144bab4cece6964cfe2cc3bd1d29d1f193260ce92e734034630f2a35dc34103de3a7dd0e8b8c03d6edd8dd277ab4a7785710528ba32afe6a9225ae4c188bc5ebd50634fba8cc59ea7aaf3c7d0d052325daa473d2871e2f3161fccc44876a1c79595f6a46366296ed1b0c1cc49bd5cab8e05f59855139b602dbb0e1825889b2315de3594e6883d720db5558ab61b58efb142ae4c35b26a14972fd034ce7a9c831207f2a17aafe4de3d0bd1dce621babaed2fc8e1f561660b9626a7902a80d0ea0867508c9c0236c2900dd38d595b3d1c5e5d787b3dd92a23edec5716aa1e56d28f1987b215ce02664fa6b212ac3c842ebb7c579b3881c61ebebced8a06acdacb30ccdaaeda6ea8dcf57cbeff0eaa54252f18709be6c46358d9656054aebb848c7cd1b53223f36e94a5262215b004176f6963f05102b736dde71a69ffc688758c0961515525228552c4f630ec468399d151891721bb00a3a1ab5a92fd29ba2f3f465784c904adae0b00190ca8b0ff5cf003e63c258a2dde978f139c25b7f591c064e854521d02336fa3ed5953f9e6b66795c1a3c58affa386f8e752d4d57b8b57a88a4bbd31a297bc82da93217c67f803278cc3b8c6618154445edadf6e512e79dddfd81383ccebeadbe3ecf42e7ad28a18880963254c9e661bff80a7c84f7d7c53aa7fe71afb3c5004ce2e37ce595e88c642599718affcbf64c904780f035931b5c98985693ff9c6403a2b10be6750e6eebc11867ea182f0134ab5bb5c79a5fb7f527750958cb0e08229b4377591d30f91d1a534ff23e87f5c2054c11a57ba12fa3c2b976c00c1346185f5ce14de8ae218266e45ddddf98344364f4f214178f6954bcee6855bd3296b610e0fb69c8bd219aad27987fa31879e4743006e5e0f92d6bd15cf2f4b95fa0d8b7218ab21647b0bb1c0b83fd16ef26d9148d389bb09c5963cf9c9d4f6f539d827fa269cbe81c815c4f0752eb32d86352ba3e3e0427b78244d1e360c5504f830394b40948ccc8686a6578759b6991a4df92984876467262e6636642cd55877420344bc6569715e12071e692a7cb22711d90feb500f83d3df3f1c32f2183ef4fae32de6e8afdcef1facf6bbd531703d0a2a8838a503aabeb506bbf9fb87e2836082e1c9c55b0e5c082c0005b62a7778580858ac9d4d82171888b2c605d24b137281cc4556faf70d786dcbd9ecb11836b15e5fd7ea078399ddbeea4c4394fce1c704fde18a3588ba080cec67ea2bc3312cacb2a500d60b8e54ef07e95b3ef689ededb284e8c9c30b587443c77a1628cb3340ca7796768fafdd32442bc2b182d8c30fa54967c312894179aeb37e6002bf92f76af8adf5c12a0a43a7b419a178ca582d4e7190fa735411f01b8246ddc10d866916bee3dc55b59e58facc2713350bdeec614c937012efd402a99c5ca83b1c801d879c456ec4ae621d58c01d65680e52329ad87700789000a5c802ec7734bd7048b6b03fa009bc98e4a7eb2c34d3bd5b811042424680f5bc6c257cc32726f24995f53cd1ff4c746631f090e60892fa7b9e7d1f0d683121e2fa89b09d8594974064b33d11a9ea517200b2d570355eeb9a39f8cae722f970053879cba5ffb90c551e72adaebbdd15fefdfa0f3cf4e04ef9b67f9fab96bfb5d72d6cbf196fa98f1ee1e878ffa145c9bc4aa3e411954494aff8b80bdd7049fb3c814ec70dbdb414c34db693d6f5787592a0f1ebf7c1b89bfcb3fff936d7d7cc07326a52a57206c33ab8571101a11ce9245a6196e1e5f922edd50a3ad03b8e36fcc21667453c4399b0f4ee39f15550985c911cce8d26cf7ab1b815e7216a8e96b8a812462207f8a51e9a54923138a0ac220bd00bb11ef91e1e79f53ac8a70783e7a6998680e36000d002d53ba97fd51a09774f18d9a4220ce50c39fe527ae1024f2f285848ae29af638a1f5eb056305609f5e15cabd7b90f15442765684a010cfe570db9ba66de5ada9bdcc9456a3957bfb48d7f8051ab2234dcce2b8b01ae557c182a8e9500d4867951925f01016cd52046d8325f45add9128200fcd952cbc13230d6ef0563b0bdaafc71ae3c4f3f6e68d5b873f164fa1b479ae98428bb1e3cc034dc658b8ea4980e0fdf7adf91d61992d50a36597734fece06d591028ac47ccc25d7d988101e6b5cb656c34e362af5094e92c20e0d1f3c36dbe2f8cfa0a84372210ba20357e76f18e47e3e4006f1cfa400d087a61fcd2f101b11befeaafdb2994576a78f75839b6a69174175af485abc0e0a6dd81e12377ef180101d3f6d21c36eb58ba64c26f12a1d97c086ca5846a36eb3462bf35697fffc9bc1b97d0266f85c8118dc6594cf4ca82fb8405433583610c50642728ab66df25b1bb94c5efba828b7df6eeaec9530cc9ea33106e18a71790e0e4e531247379efde8299d39980e50a9176e18ce6aa82ff11b5c7f1694ea16294f737bcff2a0d9b4b26bd39db9d4961a32210e5321c667a95be90acac699a906accaf29ef36762e878789970f13fe6160db446ad1d868fc6857e68ee66d6d085b863ed18846e55e501a8aed72dcf5f08bb7ff70b48e50a144e7a037d214043015c69b0f18f16fdb7f9166dfb55a68480567cadb4fb21fe84559bf57e35285b43e3aeb1e5ac4feee183e701a834a55b0cd4b2f61eb9f37ca8a9e1efdc97921304be01b18e1d8a2d50f2c7978d090590f5c8ad4ace6b78b551e65cea365c8e352c5b4d6ae57c5a32e6eb632426b6e6cfb290198222b29705b4219c632e95124ea3d35f2925e45358733ba4ad75a7490ee8e77f8c623bdd3ba5a7c054e922fb8269e684ae154ecc668738bafe1a0188dd3beda462d254bccbb8187a9cc0af38f7b86ba2750b4a83b7a69da5a8316aec1db8a4f1fae550f37deea4ae9d47ece3f63fcb8abd655a94e4a13c3dcea1c662ff564e18662602ee74df9841be146c1c537454c28c53f4ccdac8c632c4991e8eacc8671782fec92f9b7d2495692f01880d715eeb4be0389c94533b93703818e330155076f9aa9be168007635b1f526d1ae5c92df354b5595c8f6bed3cde41ad916a3ee440ba1b65a93bae9994c4f8e601e079493963ee67061874ed5da40cdc0dad7247f68596979ab7105188584309ab480e699430b5a433b3c3013fbd4a1103ee8b570ddae0e9bc2f4567a94da0d98b2f182c944f3b919caf8c009fb15b91057713ee370272adcd8199de644ded7c908803c28b47eea19f486e21cf39d0576605ad7018612958263f1e672d044026f20eb1998ce3a4eb5704353e56b9b34b852dc948dc9bdbf1e8ea0a92a11c0e69c1ddfe16f39449f730681897cab56c9ac9b7d010005b1a93201adcdeb8a75390c6ff54b5bde37028332de40ec70694ce8fecd780f66c031cbfd4aa7c7d4383e5db1d2451baec921a76bbfceeaaee1530d7bec19525713c63f37a1b18df145dcbed6e981d600a6e1ec26b540e92601e841eb7114379313738f413fbade80b2f501f9bea60d12a43a045780cb4b373aa6d610e206ca386b99ec7fa1ab6681a9320bb86402d6456a8866ef352efd816affa080c327aeb164b11c3a51c467a5c78ee303787b0ac4ff528feec12ca3914f778d57b31d8a7eb2b8763d95fc40662d230d0b38b0e5054c775ff232cca8182b3896e6c8d1a2deb67ef93701b60768ab267073c7c89a070257ab1c771f9859c63414cb4f7b00371efde5d423028016cd0110c06e83069ccc4b26762deb14628080bf62b3700526069166038d95e9b1b1124775efc20bab823a52505ba008f3be66d99009d7730983766a9101f6ec5da1c69405036fe5de161db26d6ea785083ac9114148221fb14858b5f911a561296961b2fb232b0adc013b6802a2df0a766aa0524007f932a585782a54be574ef37a4db1095cce6be681302874a9fb8b1f992e2e0fe8ddbb4e84898cc168153e21d70f2bb2c8b9492782e169a753d9cb525e8d49a2500d34eb0a2e938ed7307563d48b321749501595da7af439ccf5861677e3b90632669029110ba5d59e2bb4b6f832425830e954a04d994421dc8a739c9878282fcdc5a928e20cfb8498b4687f921469938265809e04fd7ad21a0773a96e034b02f066edc0fd0afbd445f88487a72973d333ca5037a2d9f90c5eb0619616dc4c575d80569d9827e26a277d841586f9eef6394a325b5a9da5816b0690c558aa29a1331c55c38ec713216efbfb9322ff24cdc82e4d4034b3d65ea8c84efefbd3e5b116471a9920b77088a9d898fcb49cf8c4db764fbebb39b39d7b7b25b121d7ef7958930de12ab652153b0e893673c23b12b60c96f8a7c28d0ba6b5fccfa50594cfa6890e32988cf4ac0cd60480a57a84e4e25f21ac2eed7c9ed841fad933e2777f7f09a13bcf80988dae86dbe877d4e2e6d57c49d22625db425d9c44611310c5f46514c1a2c0f6b61ff1620410c05dcba6e427b9650d3baa8ab87e031f5a8cab39c1fde0be43302f8404f5571e5af37901145875c1d3fce05d7f777e1ef2f012850594b004888133fac7d0e4dff2b04711364bc49509c2f7e629b3a4f991497d542efa31b4eab2e0625f9d3272247e1c4030f9a19416fb5935622bcace9f200fbd6b7704e655e223135a99f78ff664764b0174a824c4ee3b0192c2768094d7c30fc2ed5f70cc6ab68fa1b493902d21cad675f0934c31e77b79fd45011bd0e6c6fa386f39f942d62ba5adfe773eb958318a52ba4c81e075e1411f7538ae17ad9d89d7f6875940226f6d00f823a9c58ef23480e25949414a5f090ccea2542db643f22c2560e906ce76684a618e231164ebace175f8b31e3bfed98153775b3a2004dc9314832d5ae47daf51ee6a813b073832949408cf1ad7cfcfc7f51cd2bd4bdd969751863183fe118995b7a268f35ce53443b6e12cfa4bc34c084e6d3e9215b6b51e477cf5248998da802478bb3192781efc22bfd2b2f85fb536a2e55e0295c094f92ae205a15bbf77e709b5a6b8157832c2d3e67669fb054c1f37c535fccfa7e409f4cd81fe8fabcf52ea3991ccc37016b661ff5bdd1d362ecb0d547a4351d567e1a839ca9e3c5bf27a9769528d86762e0358b7cb1b5a51115ea31c2afa086c93865c2410096e29f320b21f1c0887f25bcc50b320934fc9daf1d6589cfd66bc267f77d1128003c18b5c9ac345e883ac5ee11c5c41f543df29718dcda23807b1736fcaf2613003844b709a76c35d89173668af77459416981f036830aeef0eaeb1ca09d711bea22350f2a49db81dfe1b5a97886d6a15cb98190dbcbb877a3fdf629c30f16a6dbc2d674285f746fb1c2e4dc81f42b52f064ffb8a32da578d4ce9b76abef5a91947760496d1281804ab6f5bc079e19f02fc1ac63f76402f7a01beda48a08230a9dee6de6b8c8998cece6a4b3d3a37e880dedabe170153c9abc9541140cd00b9be5a0678cfbb72f268f145f265eaa781c34e8572b96327a64fe48d57827d492fa52f48907c0726062fd737640661d835221668cbcfd44c18b8debdf7c4efc016d4def3660e89921e037c27c7cb8c350741ccf2ac9b16c7ec14acc7e4b10d25ebd205d7728224187ea93867da359a8a6ad25d56a6e55c9b56c7b5ee3abe763d6b672725238c8c6037e8435fbcad8120f61617690bb2c454aed5e3c3a96f7d9c09342a3170040ba6e8d29021c70326cb3b8187e14c9aa1465b010a933724962ee485d4a46e1aa9d2d34d485827f3cfe37039050029d6d9625888806cdd36c0e1735c0ef6da4e709518ac074949a3c299b54168abce3198140d1de4d1bd92e48d59daa82abf6efe19c71cbc62f58b4efe1290d3e53381f0fe6915aa39492fad9e830408fabd9f25f0aceff1d41e9f51670e3cde28d1f89dcca8c1205df05b2e6c49ce41a81a9bd1907fb8fd2827f41952e5cb779f1401f3893b6a09bd0aebaab5b136590235e02f74f98fd49390757d8e55df118b7accaebeff5e707b4c2dc06e0cdf03d6444b63ddf3f2b7642e585774130b8433a322f989b485f6947a1e9f961a9dfe9fc3ef02228fa1586503d389ccf054d5c5c75c3bd96e065180487783ece3d78f79fd925ddda2c371ffa5fcce34c1e1462d54090d56ad53fc0090617983fc9778893d016ffdd7d809d440149b3447c6567ab62e4b43a90c784f4d42c777dff2554775e325d1c3100bfaa1bb997aaeae09f5c8ec8418345aed6d18de9855388c3fc992e3dc70ddebbf76cceb7fe39883301da39e36de59900d16d9db725f864746823bb16eb2820f1f6e4f39a3026e90d14a0f773fe6ff0ee3cfc8ba0884e0fed0e24c5a4f97c6d0e5ed7e07606e06aa4d5171cb43105dde426b9f227e69800a77f0797874bc401cf356cbfc83f8ff46cf1a38163d58199403ff40ab0177d994b63771332b0a7adf6674aa05b720dfe350fe87907ce62cfbe711c34b75a3e6eef46582058425ceb36c6e28e091e262054059cac5b54050ce32e8c3174db761ab30fc66972d95cd0d64961633405807e0d007556f5102dc2a49007832771b84cc8253c71bd62b07fea9e4772a7ec7aab58b23316faa9baac9a6f32395c986bd4398077f13cf5d2c0ddf0e6ebc469f46eeaf22f1e21714ecb417406a1b90192da3f1d4495c24cf6ffd5d101283b4649796ce7d5a802891d59ece54cebee724ea25adbb4f35512ba0d03570b1990b77c0578cd3364b7e51def8863d4268d1d6f3ec90d2bab2f557233d0d94e1a364474485c72e07cc9f90dd393aa587200c7680cb7e0011238712be6eddae7d5658a5f87a60432fcf767210978716a2283cb0220e5f9724a85107dbe64019e2763722dc8c6319909ccd96161512e216b137d044536ff5eca2434182c31c0662e620cc56752162d5b3fc894812f3b6eb862b4d7b8da5921f73e5d03dc8599526509cdbda1d6e8d80198018a5a91665a9d9fa7b3e841b769834d97d49932971db5659070201c4116ff76a0ee14eac2ca39ebd5baf84eebbf3cf8c2e3af8bad70d973f1067d12744c4b652ce27c2c1b80112115cdca62fff9e27de482a5ea08c6a63b633c514c73b3b52aaa47b1d79a65ede2f58f5b8cf5caea9070050d4159c71c0b9a4d6b552531ecedfec19e61b69ce0591ff82c6197f73478eb6b8fa7e62743169c92b5dbddd52c185029dfe5b558c7af6c7a919dd947c7492798112fd9772a62303014c62702ef28846a4279d129118662791a8ad1c157b9095dc022165cae6f190824e5841cab8c67a9ccbb432a5f18b8d2d3c89c57251bd22d4b62e4865c1d230c32b4b81b825fb074665699c8507af4c97bf4582ee10a1829b3994710b27e19cc96a1d4a8b8385e9b3b95b13694750eea4820d7917b322e6817dd048ef06d7bb21069cb5e5faae526cc71d9823d58635d49cc3221eb57db3aa779b5d38d94c87261048282fabe0afb000f5c53a3ffdd98b3eee06d37c8536e50fbb6b54f197a94bf8ea7ee7036feb187fbe2dfe1cd57637a54acb74ba3bf009e1bbe308a2041c61584b1644fa562000ee0320f33f50d34ed1900cecb47f91eb82a2ccfce0c8707651e422e913aea2ccee913e8ec318dd4aadfd9f687c8d27e61d30695ca6599c07f7b0a8c03d2f0f4859cb69d082462aac6634c59928942cbed1a5a18e439f5e9a3e8dda433d092d38f4a1e2dac01f36cb7cf9e0ef41b21cd4e9898557d15c1318a884a3090364245b86af30149a19866506f0d5e80d8f946142bd4930761b994c6fee386a891704809442f8b43fa770185123c9dddb3918e6c9ebfae5adfe866f66c0627553982f7cff6112636462ab2aba37c4f6ce5cc77e3570354785c2436e49d1d774ca2b9b8299805896a4a88390ad8d821e0c96f8efe651e4f7da2bab403a1c7fb3c74529390ad51ada3c59fa0b02bd22198a3ea84e291957a0554f22948ae7262f74fc312b1dbfe4363a32c04f7d126561e88d0806e2658c38e83d88ce9ce88bffefba70cad88c090f9ad9c9f5c7f933e80b69862dbf43bfc3f0c726eac154eb71e6a8db358a60670b5a310b6541c22c2d81dd250fea72f899a641570219215d52145c703d39008b6e5d31775ca33c57db4818a6909f69ec5ab55a5a6c680775d281c593cb21efc3dffd4a1b78519a893d259d525ee0a9c8e980c9d339c348dd0315746f14b1d2276e5b34c83c60be9ee5bc173c5a7ef69a5fc66a93482a6cbb45690d0ee970e577ca1998c614abb5f35711e00bcb9f90fc50e84b8e47cabad62526873921bf58b5f8bce088c99cc48a377114c0699796897242ffb2d7e2ff4e334cbc1330d3ae17b92e004a56c937c58a6a44b386bda191846ba8d140d2a09bf46f374987d330d8789607a353f5b84bf979db1fcf591174074e6fa06141bc046725d481b39c60ecd7d1a5cd9246f3485719911c2e8a8cc71ee9b7897ab385dc5836d28aa8de7da5b44a6b16545267a718db680765bde65630bb513be1fd796097da98d13ff347daf38a8e535035682b3c530fca9438f7596c07b04ca338132899c851a09b0f0b9cd22807fd9ad4188c16b68bf28ef36a1b1942d474886d7d87518e2bb7b8fa8cabf6ff95038f39e724b979a600f73526f0bcbc59155c609ab86f5e13a009ef1c201616d9f3dfd08816988631464ea60baa276b8f114a8b25d11aa70871de5928f7c6924e22bfd23f8a9330e5144f429573b65d72c21697a070924a2ba0c96fb84b83bc369809b41dea790ec0ce2205e695cdf5e74f53a268ca6d222b21b134638ff7bc1752482f8c47c3bc972d15be545b56601eaf81b55f0d0d3c1eb7aa26dcaf3831ed471bf02cd247df61dd4a6919688ff15bf80b1f16f4d017a30d7121fccbd875372f28d585797202fbbd9cf499ab0082c1dc4cb12303689fbfa89f73765943e4e9c8615c9ecfbcdcc0554a06fb192ae3bd403e834b1adfb0fbf1e06e4f8e921afe7fe707fd0ec9aa0150249c80dbeadd8c3b00e409f83501c10450830edcb9c578e443ed9a81130731c15e41cc04bb00d0c19e06d28f873915cee7b67fd4bf4f1e56fa0f53a127d00337bef1b0daad2867259ead6dc3926d096c053bbec653f99cfd89e433ef5332b0eab9cf96ec78f7e4b9284330729fc23464064200f9e6a9d1da6b487db3c210763d0b6d3bb3070ea95b5d2604946214aecab35051e2f8bf9262be2bb49125ba70a599fbc0377f029a79f32460dfb0df9ee305211a2b4d190a82130b26a0fdad7270e88a5b455d73dc8bce88e1c9f4f6e02a2d9a558a1631406d2db441b32c513dd7b2c3739be0d2d5ea4b5b4a460d3d90af5f9ec20337c7c2c784cb9fd796b1b4e2ad9615a258bd11ac8e92652188896e7799e57f7f249ec23233294b88c8151b5995739219ef0fa17eea76493ee56ddd7c5cd47ff779c361a83f2d1407c584a4c9b64193f61d14bae8bfdd45d151484fcbd61566cae7b8f1eb51e8c958e398fab2bfa08c2c8f8611bdc7466b23230bc2469be14d2c9aa20de768e4368a6ed3848867296d88a165b009de62a00a4f512c93ff57a70dde11ab1078a74b5115be11e43c39517a5ca141346654353c9094d0d4bf35ceb90f383776c47d57420a0721040966b6c1d62e0e997f7c2d3bab51e0be7549c17982bf84e6950901acbb71a44458ef48191c6a7bc6965f61624b48fcfe41ea4c1d54327cb34f7ac221d063b2e0b25520fb4db3523d39be5f20c85ee5a5d88c7da538110dd9fda0d0a62e2391945220732a02885e7b98d0a58009d00a3a723ffb9e5470aaf7e0ef1eb2012ad42522930da389e2b7b8bf6dd8dd44687c5a40585df8dced13e219fc298c74662d9714039092aa6c3e1642880c6f8d4d5243dd87be90fdcf3fbdd9bf5813124fcd7bf3dbcab63201e88d252c876ac5194b4f6d086296d5935ffcfc5cb99cb54b966906a39cc28695a1851906484b392c27b7d195917dabc9f5b3958969dc0cecb925d8f55639722da479ff0d2c232c87d3981ef17a9ab12cc8a61b04ae12edca7f2a48f363c9d18d6d5819b19ee19003bd2e87455418272156ad11ebe17492f9dfb1d3e128689c15a16844f473e628f61fabd0b88fadbdf124eea6dad220042b32e11d87c6a1d5787dc6761127566bdab8357ed1b4768f90d6611483161d22b4a8389186d6996f643496734388306ce548ec410c36c00198ff25ac2f229b9ed53a29581cc744e2e7d8d92490dd28360c14c93598baac073f86fa123f80c531a66e8affc021df8e749432c82839a3bd28122108a8442bebac3434c8a16512ab55955018555f867b7642b9db597894a75ab0000c81f2d1ba821a1a87a1b3ec952e4d2cb8286934537700c6a8603d545bd127f6cfd3e51b35422d0b2e2f007e9a55c284005ddc70d85b2f8df037d3c75406614e0e6a1c077ed9d8d5632779eb870c6ffea54f21ce636b58c94e3a5c437c7d6976d98b3cfa85bde04e2de1553ab7a214abf4ef299652a954418e423bb0da3b49a2d341cbd563cf535779fd9630e88c4de16c02a17d7c36ffe88c10f29c1558e764ba7925ef6c544493b7cdaa8c64c604378ba3a02986fd92a4acdd341a8c0c7bb3f1f7d9d9b7ee58154979b526944e9f7751faa3274230904d4011379cf228be33625d4cec99b9baef68f972fa9f3211a9576535938589bed476b56a5729cb1d9707d4cf43fd6ed0b4905db78439c4318620aec3353342d9203c745d171e85652ada68c2171b556c98ef3f8b0b54a9d2cc0e83c6e5c83ff7c4a6ff5ba22e18d42cddedce718e217d9a21d08d61bebd244b612990112b9fe9cdec3872d4be6d386a73ebb4db0ba5d9f7e21476f6e9ff929a608f52fc711a40d68bb7fae3b06a76718dc9836f5b2cf5a0eb86b529d33183692b028de22db2127c7201a1522de8f22716d09b6cbf1c49b72fb7bd212cc57280db71fc19b9d210a6aa08410440ef76b5f40f73fdf1b7abb41b532e84a03d65dc1c8fe63badb0f5227ab847eb58772ce7dc80bac4287e1e1f5677e908d91a035e5808e9c907eef7876ccde5de7987da71a8f81ae3402dbce411f4515d24fd23bf26d7005d4794ccb965ba56dd83c7cbdfee0cbda9ff7005a1c6ad912a44754dae1a1da6ac49bf288bb8f89c8e7df2d49d98e0c92a304bed8974b46f3b0846296b6dcdc1910f82adfe392decbeb5711772e9f89cd384dcb39c44a1ee077eac184bcb95133c273b7654fbc32ca2a19d783555687aa08388391ab83687ba635e7c8117a620d2658e203fdd06f835623bdc0a9d1f67ea1899e5ec8ffe6224d1db6b3fab67e1252a5ec256ce229ffd39d41259bad1d69a46ce425a005628427d395c0971d0711cc7443e23f3e34b53066a47ce124d8a70c7fd9bf5d61716309d63bfd3aff346054cd1ea73bf0eefa90a1b8cc407069946ddc5b35f7affb6a2bdaddd9792c056c9aba563027e1dccb3119e25f723815bc87de3cbcc770ccbc8ace611f10e47dbe7bad85a2a77b17249c3cafcdd1ddac81f09bb8f70aaf17cb8ca28a5c3a4ea836ed7153f86773a160bf24457ba03809a6469a292ae03a58981a8b1fd87c42431a23ca944501a3c8be85c339c32aae8344a51882a537362f392112efd0a0f62e1b89ce5abe7b08e50a1a3788f8f5dc9e27d7acefde45394b27f77cd22ffbcb26002665e29161b6a908a2801615842cff2491dc1c32a4319dcf622f9a438eee9a029eb80f8bf9e3507a5b7ea938d7cd82169964b6e7d08118982b1328c5316951d699ce2b967607a94df334ec8db489cde8f03f85c1953dae353bc7580d248af29958f941655da334a4135a24539e0e9b88bd898e944d0005640dfd4fe42691ac8b002d08bb1f63c602bf4fed452c190a7b00d93e16cbbe4d24eef721a64d3c84063a32a86539dde55e826264557fce17c04e130918369ab05f5efa631156d2c60f7469b5ccd7ac47dad4714a4524af2a9586f3908d7b2b8a6803f3d6fb58d5a25a412963ac1417ae9edbe25ae17a05e2debc7b470bbaa96e75c3a5966d9daecef89f8b201277a91b8f1467691344bb74a9292822fb6a2fb56be348509e9fc25c1cdd7a0397fd918aa89d6cdc1d51ce9ea0f12ea90cf71682ee34c3362e86d4438df91269162ec7afe9c55b9446a957c4c54a3ff475145d9a6b6888117db4e5eaef1386c221a36cef00c16a2f045b9975e15386033a40ef364c3a1e6c33b74e531ed4592de4a34a84388d2c3aa5a23ded91e4095e5c759960fcfd6f92aaa77372c47715a99b2c48d256de89b11c09c7b5fe2fa78ff22af4e45b99d2c964e4b11c5a37764aed5b30d611ca8c24441e99f960107b4dd07932c5a866d51a0e0cc1ea511541673c44bb94063254860f2679491490640f8c9fb0eb357dbc7ff6c4c2c0778f245018f4da9eb1ebd01ded5c649568e9c6711d811727595ccd4fe4aa0d30647f6796dca042f24746ee7bbe4b5d8d97893cf04944666375771f159ece94482db13f4715a4cf55369689a23c53e170e0bb06bdfd2ee15f9a89d5fc132ade3713db680b7fc368d4bc6b39f6e1f68e7aabd8882deea7461df0211c5426e8cf441e863fdb02e051f5f2f3a15ba7555ebe67b8fea237905bff2e1c2722a412b7c860c3317b6b882db62da647dcaf31a8838088e0c3c0bf4dc411d807e81401e72bf114dea5c1c6436a1a298f332be146f2fec813f1bb54c5707f06be8707c016c9c5a31013e10581bba3c5858750e91514464413e7e88f71a89c92b29b5ad9859b920d7921c85643e18b4f28a75b7060a7218eca89946b62daab95ec39468e071c3861bd773c5cc8b3b639fea3fa278d65a75b9693f2dc0aae433aa4758816d5be7c52e8f64facaa5e78a359e4e39da8ba28991ecf200a3eef2564310bd5b2cd0bea1978c5a89b1a8f5898c637afb718dded1d2f173809134cf29ee3875e5bfae417608fc2bd3dff223a854b9702f9aedc06302d0681c199142edf24eddd33505133192ea41115d8d5242f14791067755ccd10ec98b1b8c4090d4dc5d1ed2bf804ae8eccba4a2d6b4a1b017080e105656f642bf99a80f71d98b7612a1c337048da3d10769c12373ab3c3866560abe4dddd8a01ebc0f00bf670b36e232e160ed07f6a4794d1f717cc2e2daec2e579baf1eaab862be879a8c33f7b9aeced65941ebf2bfa66d2662585caa87724d639dc1a87662439131371e71259f6307ba194d70fab5f59190a0f057b301220076dcbd02320d558602f7698781f5a609ffc291d339618cdd6f1a69e4fb7eccfae65c7507d1b361d880b2982075b676f64d5f3cc909cf50f833a2ce390eadfaa0d41d7996848ebcfff4333d9cd080eb120ecb0df74f92347bbdddcfae579dc14eb05ec23fe354b5d0f09fe94beb6d672706319ef985e5693703c818356242601effec71d2f204746608c53a1ae490d4a2dcfde8ee032c99d7057d6f635b260e811fd882eb490cbc510f7e6ace0c08a1f8e38406963829bd55db708381155a81a52c2b2b4f67796144d965b2c8d38908aa8f0372c3d91e0559aa37365a669c661380d49c107eb60a4c81440c33ab19a71234d167f8a89dfbed148ec217ed97447ace156b4537e0d460728b5c280385c65d313be39baa27ce3f44a532fcd56577947b2edb9bb24c95b5932778e2a6647b2e9bb4df2267eb0ae8329ef9ff284191aef781769b9575becff7d51c5d028974b4c3981c4f4c7b0fc47614d2a99791868f6331046603f3a2588194fdbbfdd998f8049a565bedaf51df07d6892174d19b4876a3f5b1c4f7c5e414373a2ad9874351fdd092dbf8f6fca5c3c645942809a341e7c16c2ad354c759dd39a81faafd4cefa27aa40a914d3c4a5c0419d12e10e039e1166564e1dfd0d1c11991b2f7075c212b69e1b2b1287a387a133a7945213e6d503ce976a5aaa58ea3cc4b0445b24a2c145bfea82778d1ccd086baae94c9518df7e185e404fdeda70e3b0f2dc53b1928b977cd4d9dfb526cf374b9e4b34e740e4bc975f1ead44b2632e1875773861a996388d9566c8037d19943067691b9d7a429418d5387314e69454c91ae403f99d717c92eb0f78fba6228e61ae2e7d01caed2c9fe645013b56b9926db39f0c4fd6d067c8b3b9c9d0bf72e040195ff6b7d6b8bcb5b78b302017869b8690e2ca1a728c1e17d9d154ae12031f01129fd4e955090f31098d3a1655ba9b87b307a9c2c78af427e7196470eb453410ff9870e70669b60651a221166ba6299bb95571825247aaf92e028c1bb5ebd9915c5215c462c8f4b1dab28a95dde7cf2bd1f5f76a7447dc3fb6521dddb6e7b1bedfd76c53d2eb43eab18b23a4bf06d4503e84054e87e666ae0339f87d1fca555bcfe01459818e053bc99aaafef4d5df2810e8db206e31068459c31538142c9311042036c65e98090f331ed60b0bd4b77f30000ed4636610e83a2a664f2e3592c7144fa89ef3e78fecf4fd4072168168a2c5620a5c60119e48cd8393fded6962b22d5f9091f83626ab1a0d1e47aaf37cc3c647590ca6450844d460c4bd8bcad09294af2f527bf63d9c9457826755ffe4d1494839a39b7aadf80198fd179f99d2260bd213770a450a58b7f4ffabeecb6c0cdf066210570ea29d05b2905aa47413d512980a404195bc1ebcdcb6620b62b120142ba720cdd4c36ebb705bf42098a0afa6b4287782b38f514d22b5829ef4ecd30240311c795e886988048f34acc6009a2dd95a82aafed03326905af4abbd258a09f834a312e5366b35b7eeb502ae405a21b5f8b2e68c3a9e71a28e7c2699908de08a9385ed73ef65351af80faf37f78d8a2473742f6df73d2f508cc790c8c0d23539d37f74bfa2a0254fda72ffa2b3acfe4d2fb99ae727738066ddd0b6eeab8907d00fe444a38ac1e8bf63c67f93a9de4944a54deebc49952878bcccab035b0e4871cbc220a5d7dbdd2aff185a6bd5a118a345a29c6c9111bfd96833283b6839664153dc43646c2deca06755095c82098e30213c985dbc15dd1bb967370078c65577a50302ddea93917f2060be444967424c96f6bf3b2dfcd1002138439e400fe3d66dd0d38969df0f61cfea2fa041ac8aa7cf89b02bb63053c040cd416af653ba2e8b7b9271f0f56f0438a491614917de7208e63e4c926cf514fc97a7884602c348dc20d02d14cd5aac8ec0ce7fba996e2ac4a2dca2a347876f88a3706f6274e21a91ff43596fb4f088360d314b7054c25c127840c00a7338f00ea50f952ca8b5cc6c3619aff0278317a7700498a3053c764c0c85331585d36466ac76e78b7dfa88e42ba633ddd3f1491f84a0c2cc1a6d56f78dbf4bcd48c53b9e6234e32d29acce12f06f793634c236afe1f360c294c78feb8d548dacd6cb5f861ff590b7d8041037cf918c25c5687e0c2b69f30bfeebabd6b76024f11fd04855e36b2f5666de151b8b681cdf2d97088a20388f0aba54c08123bf6982b2ee4a300ffc1bf292f25a5007df8b73a5dc5dea2a3226fb400f91deee720601ab4c63df6dc4ecc54661b9a18c93fcdb0199bc410ee40192b9608f1000b3a986ace01e7823ba0027cc1544ab4a003650d12af1227d66db74c78934d7216f378a3d20711f15723fa7b4bee9a880a1fe5060282738b75afb5661f95cb7cb635f42386dd299b28b9706f6c08b0a10cacf71fe250bd90b246b430972b6cfd8a5727b62119ca3afa9918aa36424121fa1ea0638cd4e4c0200585c1023d5127e93cf585a05dc7d485fbbdef7b68d6911d0ed0f4ab6be285a842ddd051c4958d8d9d89d403560b3e78a2d7cc37d08aac26783f31e607dd375f4c552ac477809712073838d30f4e57b24e729d580b781294d763b25c7d3f55e34fb5ec46922d948d2d364951129ba75bbcdc8592a67fe1355a5a4b42707d14b938467a9d43c5fea7a2ae564c0467281a6e3ce255ab60f0b7eff7f7f24dc7b057f2f02fb5479dd954f90f15063d1b4db248429d627e8f234dc2df15d639592e550b3915c7d8ad4e9e7fdcd4d43480930c450dc942630f3238c2e7b176e912e10d7ba2c590be9da0367e4ce7ca02c63a23dc3e2aec3f6e437fcb0753447260a556f8409e24486b2fa72ded0c624b14b0c53313c40244942de5f76e89cc44f1cd62124f48d18c1a0da12d775e9c40beafc8a36770863c9a58952e03ed141240ba04e0dd4cf88b11507b9bf58bf4746bee50cf3792d1af80db365290f25837da91f429a3d064a195876158a32c6ede883a0e245951777c163106de1822c82f093abda3ce1b5fcf61a77972af3de3f7e2892ef55dd25d25f6ae7df6b01927187925ec17feeca6a64f00c64f1646e4d3b52cf75706bda87454d01115b5f67202741c19b5ea04a3c9ae94a999346ad0e90c74d519c4aa4acc6a4a58affa368c5b66afbde5a6b0f7c4fd1bfb24dbce0df84693c0260f22366cb1b2d2aa24036ac494717f296bd1f4b6ce8e75a0451910c3c3fa2a5e48ebf0e25cebfe2738a50b44f313e9486e6ae1e50cac3f20229fe1934a7005665dabffbc725140d13788984a5c12b0c8095a648699946bc37ad362d2ab79a84cf8d4bc0346227a52b40b2ef6e7f1047bd98022b88e71767e8eb8f61f70c81436bbe0a17abad222d0010c26f5b0f26d4b28a3feb1eb2a34af3ec491d63ff6a7da0aae0fd8ead6977fc02b61961cdecf001a6984e41fe7bc0b9a8ad24dc6f3789b8d3eeddd443de6bd3a12abf34ebc2ee7152cc298369f2720e10371929b3b5c38e9a6d7ea91aa4e14c00115beb87b0aa0464eee762d5eb474e77a11bc9a8fe1462cd018dc18060dc3bc8f0703178b3177ea984321610afab35cdba70e7539a8120460383d11889645987b070c481a35e713a2db71acca2e9d06046a41dc2fff6d4d9c916443b5b83b7d99fda4a3b0998c292787b65e8219da5edc0a63488a1c04c37e1ad4469a2821bb38dc9467f05b70ca3f26e640c9b14675042f5125c54b060c70b3f1264c61d0fd0479034796340290a7230060b9be7969c07dd2fd7c01dd33f8542c67665266e51f59946be712209e1e8bed1847982c4788a20839aeab4cf35ffea55e9eeeae1eed2fe8566c81063f3fc8179dfd25004a41e41e347012c2dfe66809d1d659bc6ed1e4a0977a229ad8ecf356105dc845c1211758837d55fbd9ccbf6f7de4cf0fa6e9423e2ee376b303dd4dca73b490eec3ec7fc1253f4fc898802d593edf976c9b38c5d1f9d37d0f7e6d5cdbac952664913596750ef5805413101401176be1fffa292bce4dfaa2261300a0041254eb1ff38a75645bd4c1ffd0446a281ff6f5b1bfe820ed69d32dd47f8cb6f81843801e0f2ff3b52c103dd60abbff87388133e294c9411fb777bbc9818e26e5b80eedc054c40282b280fca915e313c911aa5a0d8f0348ed7072d0147c898f2317b2490e16c0cdf0f3065c035724a19245de8926f3647db59850f32e8e29fed16aa27df418ae91f5c93911aa0205a29e9c70aea54db6c261a569a8864f1002e89db5bd07ddf76ab8c00c767d1218f40f3db05d211f027cdf7debaa0e731a95df72381ef094df45cb9ca0ad7a81ae600c2df4977fb722de929c7f0922f41298f1cf79d0ae17a3e46a6e16cedff9490356a5d211cf8dde32f949b649f493065afa31f1f34c325c425132d0b9baaa196b8302354527579ef2b759eade6745eacbbc11dc4219cd6f1a01b9f05f82952f8233485c9f57079107a80aeb11def45b06c5b0c46a3c7040677bc09de93c9c3b21be4877e02f90f01e77492f25260e3ee9764df120b830c48376c86d206b5c0b2a4b4311b845fe757b7522f500e92aa636b539c14c01fc2c20fc956ceff9b74fd087c3314dcf81ff8e24c06adf0ffc669634bac29c1cbc0d142588bc3f52de2881b6290ea00750ecb9b2a9e204d45102405fa58af25c02166482632df5cc525c751ac1873aeaf9cbe6893f494c999b4bc2c68837bafa865949bbdc3c445290f6ddfce018de6d9bc19ec01931b1c1bb918f34755fbbd61f4bbb089c60cf9b14b169ff0a077754b47a9c4945a12ba50830f3fae3d6fa0e199f5a77211a942351979eb19cd1b9d97ad5bd340873fc5c19e7a83028fefc6f571883490dfbf1bcc41e89c0b7a404ae77b6b2318c17813d9f0ff29b1b147d49cc7553646feac11a47bd2a20c59eaab4b730085f06c0e6d19fb26922f1ee1f05328d76c7e11f0aa90aad0f7aff603d92d3d5eb7b99ba39ccfad6ee3abc1f400c2d770b0af645bc652dc6666bb4f24b8c93400179e52862beb6a1254c97ce6e9de196adab02b623ddde97d3cc766173290cded705e1a8e81519cc19836b77bda6478edb5ea9fa41ef2ed53561d6a4bef3e62ef84f0ed339b1eb4c9a5677644b12824557b609541f6690976ec64d7456dc1d712ec6fe573990a63537742e586d07b7a6d5d6b7a4f2ce3db2328905014762c809bf622ccbe50ccda3742ad30c9c4c19895b5681e6f338b92895fdc217f2bc408dd02502f9027723ca9b8640abd22b39a4332240a7634d9048838e0c3f5c18acbd36f6f87e8cb9ecc7abbb8089366a7d5eb6c3d01a7842c4af95fd11fb3c48a793e2956b3042e8fe14bba5aebd16b71cce0d5d892645ab16ab19da271bf8b755d4938cdacac96e64da98c20e29cc4acfdf1b4f56851e2d80e1dd277e1fd57c86d05340b602fd52c8059e3e3d7e90b6ca7bb78087c0d1fefd90b1a17a4db4cf817b33eb1d507a83929f9099c169f67c27eb6d83b85f48da368c4ab28db4a76de41bd9967f55e0fba0a4c55aca8222a3b79b53a7cd58a462f6234c2d210c60a7adb9d69370a20993a7fd0e60f22c9a0c84f3c4b8404f8c09a47f412e9456f8babe3c636f2500f28d49dbd3abe8678d4dc2535af179d4d2dce797e0f26881cf05b0ed804922dcabef989484114dad59acd856edb10b41400c10dd2dae21503072d95f2615451580a6f8e3222dfb890d5dbe27e41b46ae2720546ce8947d2e6c8866e7467366def50ac1f37fad5169d263cff7773dbb35f8b563290a2229df50a8df80b2841ed830a81e21ffa07f2435b0652bb8c51a279e60816aed7bbfe5ea35ccc2450eb2b3fb5728bcfdd1e5357ceb26b597bf2b71872231a136e67c36845990d8a959035456032a63d67a5a30145ea503994e4191ec1e2aaa874b6a029bdd3c29cfe5f63c1694a45895bb0f30e2c0a823ea2fd6918ebd058120d24bf33f000cbd49fc7ab310afae7e6b5ab39dd9481695cc0a48eca57e98a0f6edf52dd99b6cb9a59429a51475077307a907d099497bfe9e821dec58885b34dc62a1199ec80765f70df91ece8dd6a03c839ce9b1ecb949cd63268ee59e7a64b0d670344ff31a0d3fe4c73e9e6080cffc5c19cb9e460334199c3a78bc1b2e2e48ba5946506f7a9e8b870e560de892cbb5e41a8ad262c245c9e55a720d452162b96a66686a6866a6ccb2168b8b926bc9e5e2e2ea2f5a0708a00ac46a535b6538fee632c3bda79cbb56eadad59ecfb44be9071757abd56ab55aadafbdfc9801c9e572b95c2e170de887f250ae191a578d2b860ba929caf52ecfb514d3329454f8ec0411bcd4f9f3b3e7e745aa5a8cf7776d32f33edd62209583542c757bc80191595c4e539d5b696e37a05c6568af3f08e7466b50f24d1c0886542ce34080d948e0b404850a25966035a1f90915d0da63da63271858d37e25d39540e6655e93e156503c009fbf04503c003f7b7982811f76363054e881cf3a205eb5af407bdc8f36f39b0e9ff619ae37ae57edf9db88423346d51b29f413126fc00ea73d98b781049d9376a8da62d29e3b92f6dc6d7ca19ee448edf9c6c4855a67be7f659329eb3efe2dcbba8f5ffbd3fbb8c6cd6721b1cec73205fc6ab5f2b195bb130cdb84e924242714a93dc76213047579c1d23bbcf26f2425cf5c9eda9a755f67a9ce318ca53a9defa9786ae79380fe01d37d3ca7ee536d1d4472ee6c0cd18f4e28b95df21d99b70983c6aea886a391e11ac991609ac6096d6ad9fe9d5aa0f72eef4e2dddddbddd0db7683c4c544a9807de2e2a3f90de591f13892641c15ee63faf2148a1f2cecc333fceea6cafbd6927c6f3f3f44ecd0e7d7ede1dcc621cdcee803755724de8d3224f758aa974915cd3a6c970fbe3dc3e5127e7dc1ed128e2e48473ab0326f3098d829af227a1981cd023e8e6dc52716e7150316ebec62fb3859e34de3c2d736e6ad9e6cc6cce6d13319e38b746158339f598266c621b746e8b284e14fac1189b7308f0817e53c6e6dc01686824cdd89c3300adc9e21c8faaf1807ed0b91d4ce6131a0545c5b3a27ad60a9585733d558bfbf313655e724d64ba7ec935a1dd760bdf66b3395700cac5cdc6a43b0b0f1695158fca4f41457902c40a6a1555b138bef8520c2eae2d6973903c65e5886873aee01d9cc4e2e8ef25f848edaf41829eb039b7332305cee9987840bf89c2e65c0b262c9cd3f14d709b7304a89333bd0c87c3ff40f1d7b83dc7b429e0f838c939f61a0aa71ed88d630ebbcdb91cb4051ff4e05df483ceb1508344bf89b339a7faa2699173386a70a0c2e6dc00ba702ea79ab81b4d687c6cbed0af860ad114ed6d4ce8dc0d19977302d887ada453fdcde449eda7e198885b2e88f66085cdb9154e3882b7611ce4362fb5f151eb508ca3f105dd0eded89cf3b024657171692fdd45fb80e68bc780e8077336e70240e96989016ccea98063730e00345a10aacd391c9a1816b0b03997c26f3feb51284a592cae88cce3f03f321d94cdd2a9fe66a22f9a2af58959fbfef321fc98d75638f598a9ceb5f445b562226e716cce97428dc7ada76ab8754a04b3be3d3ba8e5d89ceb564504d89ce3e83c42bfa96373ee6990b02465e1d8c5b192a7963cc553d47e966271d07cf1543f0be80423d06fb6b0615c13da563ad59f718c85e324cec22e14b8078ba3df3914aae909fda04d127636e74e989ee27678886c90406980f563a2130f28d6cdf6b4026cced9548c6b32f3936322a6d21e4fe954bf9c827eb06773ae46c5b826f425c7454c45a66322203467a3ba609a01415a0cef88d5c60bfa4158dd7fba37f48636e0d3de81f06f1847671f7be4c36fde617af89a16398f5c57ad6bafff97d307a8fd156e8e7f370f7ac375e56aeab9f188df577fe7c19bb3a71ed039ef2ab150e89c066dbc58c85b64b2621dbaf70ea0f70e189b9b717c9a9c01e5f7b8bed19b3bc640d3ba09cabfeecec528bb5bba43e85cba4008210c1146e8485d60eb7c44e0af167d1b07be38ba75f7d2254e8de30df30e3861cba97295ef70159beaeee391e1a43d825150e835bdc39b5466a1ca2716aa3470fd3889fd0ae5c5e124e88fb168dee1244ef5735d15eb6c7b9c456d156c75f38e95d1575e4c4a6b5037988c527a49baf0d07d0df04244514a5a355a609dc510c2a8292cbc1861e2c50701e4404f758bbcbc4005197891818cba455cbc1c89777777bd07e70fb5226887eae88bde689df87d80d8d92cff77361d4df1674ee8abb808b63f5175d152151c8a8a54b9010de20e2fb943fc886508b14b0e3b4001862b2a7191c312e10ae6840653d7284912499e4cd102830371050cf08570922cf0935c1159052076eece76842f00156adf50a576078ed73c6ac307e23562988cd371f8bf19a7c296b638951fd6f0135446a11d028f69b9bbbb3b33bbbb4b8feece746803407c77976e7436c18068d105b010e5e66616742bd5b12240bdf26cdbb62c7b53f7e33de53f99fb28636f7a1bf9304fe427eb6cf8f4dadb48e0f4da9f3a1b6c03b5fff43612e663303d8ca9c3ba184e359cba205ac79265475459fea8fd9a8f8f7f5201aa9a71369889b3c13a096c6f83fde989fc642f01eddf068b61c3be86adc3ba186a78223fa66f55e5eefb51650744556568cf67f061ee6ce4f3dbc8ce05acce24b074581d56c27afefdeeee1856dff9cbe6388ab23226d67fb9fbb19c0cfb31b4e72bc38f0fceda407bfbab045b315a051fcc48a23c3e1ac6cfc3a30b4cc6d051162e35c6960ef55bd7567494850918b78a8b1a8150e3df88b2fb9b257e7eac5facd84709b335a2f163e499b2e3a992072e9892648b4d0c7376447ef6b7bb522ca6ae51932c6a7758f74dfac3a2dff415c4000911484a29a5921d322600e580a48909905026a07062d54f7c71021efebf091640a8b6402285d0a2c3115478d1610b0d3a0a2962600458e504592069a2a5dea8a1355ed7a889154e68cbcdcf809d9dfd3d39806de03e22bdf1a9477cc901cd1ff9918ba1573ff297fb4a50bf10c0daaab1f3ee2b410cf4477ef16d1460aab103414c855d13f9eb83aa7a9fe655c542f3d7c10d5f885054b78aba39f0c2a546098f66bc25a32641ac24f9e32f6164057dcc43edb701fa98090f7a4a6d278cb8a27af7b1d26c2c5c8dab803210cebbbbb8b104b19cc548ed25b5d7df37f04e06b86dd89e2dbb535d3c3d3d3972701c8dd861a0f3d99ce6d14259e9a7b99d6ccf3a8abfb8cff6b030b03dbbcdaecd699f9882148a39c675c03b1bf9fe1fdbc82e067ee8e37c54bd93b67648506f43825f4b010a9d2aa03f54a420b60895795136ecaad50a0a0a0ae2968cb0b165a32756b539cb4b9be29f29a8fcdf168157d4abdc3577a94c4434b9b5571ea0a500853a11acb3ed467887e7119aa4f22f2fa9fcaec3f6cce7ef2ebb3337c56f43e5c9df4a73528a4211050505316fcfd3c43fb66e3db55aadf679797fff019bb31f143429ea9b835ac789608f9f6750f959e096f3cfc9cc44ad266a2566a1ee100d910f4d6da373569f1dd42dedbf0ee22a9c72c94e36e5bf4d6c133d3d3972701c0d1adc921dec5848e3b8a5853ad1f6c0f7f7d76a402613b210b77a4a7bfe454de479d0c32dd53f460ff467d51e507b9d05ef2cb7d1f696a2edc1de86af504ba046d002550595c269c81e7b4d4a295fc3ae547e2bb2d002938f715c8922514f691da5a5a6e2de49d5db4b94b0e5cf4cdc280653aaf313db93bdbf133551018a7c9f4062b985663df889ad90fbb88adc42f9e57452a3d5b18daa176d4e134a5390424d2b45e58f2888c8777835dd79c5add841c0f49ac641272a47e4478b5c07606783d9605d0cfd3df810f0518d1c1f55c86dedf721ac6794a0f5c3ca5159541657d2c6e88162bb9caf529042b3ba3d337b7eb66103857228fb5dd230bb4e82e529e69ae81f5a89a0cd518259add36a9d1d6a1d9bfd45d23a41adc384db7699d59e867a42f7d4a36356ebb8bb43d6a232e44056b7d005f2d4f2f2b28fcde1ef1e720be5b81fe887aaad04a891b4cebac01cd81cf6b13dfbb13027a528d47a1116ead7409cc43f27a528d4ffe7f1764db439fc0ee4abf6d8573bfbc395c50de4acd9a720e452ed3a84973a80ba46432c51b5de94e6dc02d63e3a3a6a94114d62a72c22e65ba8a7a8f9654e2527aa44bd71059402142772c5852e872e560a4850fa7209efc415f32fb3b4616510db637afafd72b53dbcf207a982da520719c4e62ce15467dd278be20bb0ee934ac0ee93466cf749225ce82a579bd38f6384d20eb573ebd591efb0ab8196bfa0bec609fde2172d58c787faf4302fabf00ebd82b282816416b55f3e219dec4e5c49a3f8257e9142dbe39b66caa0cb7768bea78ccb559d88a6531a6a1da905c3749fdcc2c8897c2752147b353a510d117465d8546f922bf90e7520fa8fa2be4936d4fe5e6d8f33d2f6ecf397dacc456dee52794b6daea2f6c7952bb9922b05c09536a73fc6b81e520eb2c0d2f289dc1c00218b2bf90b2d9dd48e4ad854b752f003900ae04a87da3648a12446628e5fe2176050a9fd722585a2f40ec792455a678dbe78523b4a24416040513fd94232022308f5934f3e0954fb393252af1118536a7f2cb283fa451e221224107018aed43a6b34840a6abf7b699d156abf73f207949f4f326c4ad0fd4d09caef10908fc3fc99f99898a72fa3c37a0d7b882814a548744e4a55aaae407b5df3343f239fd854bf14b5bf0aa5ba45c21c6a7f0dcd8c944ea4112a0959446dd9fa47a1565f6aff4a0acd2003735e6391f65a0ac52f9bd32fc345256c0bfd948b602c01fa63b818c6b2d06f02a211287e59b9746a2e8af6d2aa58eb6ceda56269b55ad9f0b3c44a11205793fa39abb6890913f57325fda5f6c36521b0d47e9c661254fb864c5d1a59e5d40a75bb1c75bb9e8a6aa2ee6b6e05eb70506bef4af0ce6ca24af52bb5df93f01f76875b9bea4715f1b55210b5df83b647e968624747be33f30dd37d936edd87cae487a25dccd73e6aafffeba30fd68f839edf63a5d78e5ac7ad60cfb1b052db7fe8209f93d25652f220131065a1ae4382a600857eada4a4a198a07bea74a05f077590efa8828e1ec0eea9566d0f7caeada4a41463ec3eba425dba297e5e4e6dce7e9f3a4606da5229a59473e54af98e75b283b07b50d5d6d322c671c58afa8227155279c1120c6050210cb8a85015a5385185922258441942f203ad547845cb912184b8c1d5c5163f108a20c85042b7f20ed3096416355601000b02c1aaf04a0ca290e669fc385ef7d57736528ffec5213254fb97e3f83c1e0742fee6f7719fbee1a9452aea1cf8e112c40227f343082184703e67a6ccb565779ab529ffd9858bea4d5c2bd81d6eb50baad3d4af5932a8fefca4394a7d9e2cd45e2b5b84d94eebce6404ad47726c38438d147600be90b84e57cd1f0ed315e55ff72c4d68821236b129236cc2c464844d684213fc1d09249cedd1da4d0e3388b33dd3835128f4364e1cc6f1f90d1e38aca3539df26a04b039fdb1f3ce9398c4a2d79ee76d4e1838dbb34daca71235e8c8448db1c6e78931c6185315fed60005157ecf18638cd1852a460405810809546cf9899da5c62d6ac46a8c31c61b75594a961c434028e68c188649214241d7a0803c157d67a5e840870844d19212450f11e081d2cb2e2855ab8abad862071981a32f9dda5547e009a04e6d8da927ba50c20a5350d30d01d0820715b878f1c30bbc60b202165e144151188dba454b10a2c2d6124dca0ff55bf85ceccac085123ec16e0e413b104695dcd9f8d8caffc256eeba0f7e0888549f765a5b6653fd348ab8d40c28e8477d053ce79b9ea1474c4e547b0a80d5d4c1d9bc5009e324f562bd39243ec7249ac6eb0ce34f7f08b963b7504b11df414a29e50f5030821720615151855505952baee400042b60384141144140c8a658416b872794ae8851b4608a1e5e43614bc056498e3046080097141900200846528600c011c5ea1a4979c1f4a72ea5f420a548fdbef20c623a5ef50e54edeea851942f5555d7e88820542d46b9c3585aca855a90ca1f3dc98e91e00ffdd86584c414779ec244acb6a2c2671eaaa8f07dc0e7221d42d4f4df9cd23aa687dd42609d3f613f44a9eaee6024f6e13bbcfa767807d3c9ae7321d67e46e21dfdc3e2800f77d48f5d7cc5d57d2aef3b0c728783f763020ee3070af69f10af3aa0a08b650f5f83f1e32e60ba0ff57468534b5424431847da9e609cfba60386f43875db1eb7752bc4abf6262e6e409b5ad60cdad4d68a446b53bbfbbd9bb3b51765377f92be2852fb073e77c1406e6a3f3f7f6b7d6ac23dde0d55054880f3dd78ab03df91000bec7cedfaf2582bf575f8e985e8b54ebf00ab8f28b4b89ec9b471bc9e694d23a830fbc180f6fa3ddf89a153bc43015e60e8f6cec7e6f43fa13efc7df80560e22f41f14585eac1962da4173d4829a554819722546104282e20c2059d2404138694219a8a2a264802bc92859222f1e33671c5bc1206108e6843b8c088c88a14459cc00737cc2956469a8afabd0f2d1e27bc07f55b24a4badf2a1da1a56e171f688110aa68b9c1122d48e2c7b11c61a5ba472104510a8660a2080b58f0e3af4ae1080d848c8c8c5870c38fff0cfe85d6353a62ca1147645a767b365effad3d7f17bcef5437e7f48257f7d30b587517e00bd9e905b9dc524a97129a60fc7d7a6e4e3fbf8908b46b3f991e802350adaed11142d81b1d11449552055a943c95f24265ea1a1dc13a0248059de8eda671bcdef13ac5af52f114151d1d2121c9d87da1df561f7513655c0f821d4542f977773b082184104208216466ee7677a1bb37ef36ec61629cefee32fce973be4318338fd9c4a07c87d9c4645c8a02e5668f2d73df56b185fb37dac389c9fec9e35aa4221ecf3d1dde411f150f8ff612f0791b17949188d062988d8cc0a1327ba89bcd41fd6eaaff5052752f0249f522aaa83b447ebab97768da3069a8e7ac1046201f9610621d01a38a255354f80fb1183101054208e1a90796a37e5b0e8690d0208a2a290a5bd48c410aa0b8e0cb1660e030742503362842020a1f562084112994cc304b99c54e477bce037a315a70a37e5bc56680a562180d84a818851edc2a5b2c11e40589269696fc402427a298004e3a37fac333685529df291d5aad563b1ce105cca473a3d93eb1031dac503f1d15f2582a589c7051e14348045185464418c9c0880c7cf004880a9fc7e7891315be0f1fd6088aab2655da3a271acc099d60590192a7763a1182110864594ae221b1850d50758b8e0045b5d28c8c60c1beb0d59f856ce4b141961bb22fa43d66e6863437ff727333fbe021419613b2b44186c8204286ac8fca0b32a48656dadd20429a0767dbc9757f743e8a509cda36b406d55e3f1fd12bc4c7d6bf0fb70b226408f6dfee90d6599e9352f6b129fe211c7bc41844c8108fa73d5e0a524377b7c733a4756a689d8f37d03142d426e1ec7791cfb055d7263ed728e37d5a4b323284ba2e33011b5a49467e508178980c34b763f4fc966f0f4de5e5d5af9432cbb239b1971c08b03a1f62523a0c1c06ac7c9a0f8ba8d4dddd2625e0869658fd9c60e0971d10afdf7cc871434bac6a9f1c306374b2f380a972c7455a45d0cf592c2424f41d47696f635ed39eb34d487bcb4166e64c2205e140b043b86e0b5c5ea406b3ede90598ff1689b7188eab0d29d4a789ee2e96f6564224ed2d0cea20ab3d56dd77972bb5a40775bdbbefac2f4b4c7e7ddaa769e6b7ab756a7ebf5bbec3937ea8cfe3a11b43083f20f3634e3d32245de602e78145aa1d8043754f351d92b38b9fa6fbe6ce74dfa4b4fb509eb3f6b2fb78ea56f82d1598d36bbf9d7a9c3ade3856c241ededdb9801fd980933691d5eb5b73f817c27884dffd6227d80dd656dcf4cdd7724eec3ee746b53fbbb9f759f2ba9fb2dc4ee386bdfa609f473d6909f1833289d018d988052f92643710f399cbfe1792b6954fb655996bd7c9f6ea19d936ec6dad4b6507b926b26ed2dfca73931804895add6e12a65c74aecda82ee774ba875be5e62d24f5a67c906beb7eaf2903c3ecc25a04832c00717ea4a5ae7e101647436fcfb23e3f77765644e640c3fdadb476a269dda8f5cb7322c5a75f96568cf480333b4b7fbdc7dde02121894df1ec6098912c3a694d23d761edda5ecba854bdaff407dc6d8b967d3e1cbce3d7670c2d8cdb781e653e343b8df8120aafafd00a26a61b98f85baec39eac28f4788ac0e39fad9cc1a5ba8df6e0fef3e8f70dbcb511bbee6de428d1f2b6cd96d1c39288c4125a0220c5457f8f0a8fbd204384cd01d361b6a7cb871c2232a61c224e878f1e85015c173e2068e14aba3bd8552aada833096a0db9768eb1be37f83aaa11bb682083b1d081dee8e7686ee0edd89488e476032bb2123460dcd8c0c8d81396d5a669a37ac563c28254d89c51831d61833bd4f9b30e639e7d6850959c4706664680ccc69d33008fc40a2800c491998c105a4cb15083119a974dca360e3841b9b1a34503364c4a8a139c26225090a5ad26a11212464c4d0901244444d141555393aba828464455252162e97164a4a4e3049e70ca537287884224c663c37363568a066c888514333234363606e58ad8c00011d61b19204052d69b5881012326268688a92942c624f63604e9b86fdf8142dc0c711047e20558007ea42289b586366d6186a52ca8e060d4229a536a1d4349865d9369526f59245ccaba19991a13130a74dc34c33d6d025284609b1a247c1c60937363568a066c888514373c36a650408e8088b9524286849ab458490901143434a101135515454e5e8e80a12921549494a2e083119f9f52f145e365e27bc6e5e36af1a2f1a2fd46bc64bc62bc6abe645f39a79c9bce82be605f33abdb697f632bde62bbea494d2a7943e6334b9c7d8d160828ec5d88ef1438fb086eacf32c687f26394cf9551e093164c663730a74dcb4c533260ca528cd221364d28fcc7e3ff3b00f7df1675ff6d134b2576dca360e3841b9b1a34503364c4a8a19991a13130a7adcad1d11524242b9292b27069a1a464d2dca4b9c91402697a8dddf421e82ae794262d738f99d330db7bcbe0e61daf87612eb3cfa64f475fe218260a26b3f87abd5eafd7ebf57abd5eaf215f6e58ad8c00011d61b19204052d69b59e2879c164c623be6cbc4e78ddbc6c5e355e345ea8d78c978c578c57cd8be635f39279d157cc0be6757a6d2fed458490901143434a101135515464456935a4a3060dd40c19316a686664680ccc69bb61b53202047484c54a1214b4a4d57a22243444833324209df01f8f8dff7a78d838e1c6a6060dd40c19316a686664680ccc11162b4950d092568b0821212386869420226aa2a8c8cad1d109ac19f9de29927d111ca8093c8c307ea0c4c741f50365fe37e99ae69ca1f406857a08461631c905f3bc1c2a158f2e72c5ba2123460dcd8c0c8d81396d1a669a37ac563c0001c5e0998586c9ec690ccc69d332d3941590c115c3d80d3730a74dc34c3332c0c789a50e8a68fca7aaf15f0e9bff74dcfca7c373635383066a868c18353433323406e6b4699c24286849ab458490901143435388888a6cd85484c92cbe5eafd7ebf57abd5eafd7ebf5ca5eaf9764254a4b344c66386b9a7386d21a2854f7bf82e7e550a97a78787e7c8a16e0e308023f902820435206667001d90076b4c264e6c557cd8be635f39279d157cc0be6757a6d2fed95bd4caff992af1a86ac260ab89545ecf41f0ae6bf1a31ffddd0ff6cc8fcf733ff7534ffe1d4fca7428cff3c19ffad30e3bf1ba8ff06a0925c503364c4a8a199e17a78787e7c8a16e0e308023f90282043520666d8b86f034a34045912c1901b562b360204c44798c54a386806674f3099c535cd3943690d14aafbbfa11487344cce68ffd56cffc940c5d7f6d25e3550a8ee7f054f7239542f13cbe70c9a583e67d004a18931c69e1707d65d319618f6d8f27c3c11091aefc8fe9bf03f53fc6f9bffc198fea3d80e5ed39c3394d640a13ac6c138fa2197c734886910c3b48c7971ccdff9b0e7cc76fe3a176fc02e4a68b3757625774787f0e50eddddddddddddddddb0218410368410c2861042c6b1ce45dbd581d07b4743b82b3dba943a107a775dbbbbfb61175386685d232f5a35092b758dbc4842d535f2a2c8b99bf93eeddc56ee36fe1a6817f3dcd1ae06a89d1e06a6b379000dcdcccb3c09683a09c8689d4cb73f9048e537c51334c524787200063f21b0deb4d7031802a5375a68ec4e0dab68b375761f528f54833176b32b4177b300f573a5947008ab1c080dad8290c3135aae349150828014456b28b5f2c5529ce24a500a6a50b4260917248104126134667d2eb62fe1c77d47653bf3bda34e3d078d7f5de030ea6eceeee6f4360cd31b1cd00f35e1eac7dd77329e1f3304414914b7ae11e1f620b07824f49b3b284f39ca860a14109e6ef6807e1e8fd6b9d13ab1759689708344bb0685e3a9183e0a158361ae61355840b1179a4e87e93e140c46f331cc3c8d4cf779357b98ee9b35bca7b29fe9b8209a51ad63de61defdb5a3fa09e98a3dccc7701f911af37dea11f31a9f5c680e7b98a732446a0b8995761d04c6015333339853f7cdb9751fd53a1397753b6fd4ee38f6625454b2551886614934d635222af2bf0423300a2459832d7870a4a3f84f5d28e8f71ec70382b40653f4c0d81596cb1a1cedf02fc591cad403742e3249f7a6e7993ac36e4c4079e3c137aaea0bc0fe7e00c87e46f9180facced5d3a90767ee9ccd1b5e53e1110015e47cacc33887cec59845397fce2e4ad3ab4c3894670df40ab7530fd6d89db9adcced796d08bdb339a2692b6b6b304a6c9adaa3c466a6c9530fafcd4159db9db92f46754ed5ad71a76e9d4f067f3042df1cc6d8157e9dc39999b48dfb6454e7beaefa6b9a6fdb729f7fc3f63e186fdea142a8c44cda688296d10c0000044100f3150000300c0a870342a1509646cab03e14800b739e42745a329ac6b21c4761180419ea8001c010430040000666a4d100d0df795064311ead65739644e40f58d6d7d089910dde5a6450c003bd139a4667ee089c9e430be73a8ea1522c421564652a7fe88484262d4c7de3442e4ef095221ccd1477b1c01742d59e3aa3c6e52e799b1cff4561f37e28ac7fa8340ae0d1068d3c4e120163cf1eea548dfe0bf4beb6593d54af47777ca04379f0fec2dd2023f1904834a15428953881b2ac036fbb044643a4427c471ec9e520e7ad5707e81ea4bb5ba0c62270eea9deb49f2f50599a0e9c6d610d24017d80d3a1af5301defb70de48e57814baa059841ad02f0f1d09b6542fb7009c8d5a04bc01bc8bd6674c5c8a265273a75554ffbfb4fd99a50221497fb1c0cf988a351ed7adecce5f0d696c3c5a6d394f4f1b5f0caab64af889fab749fd56ad7bd06a8b72d83b1130f942ea13c1a34ecbf1812577ca9ce88a0df98353b6bf924231a30d45ba4a4154174554da53a0e06540ddd5bbd80bda6c712eacb2d393e50256644ca044672ac681d5721d57a3d2b36f5405d131f362d02d360f7086530543bdd056e6eddf7109841946237ad1030cc04927252ec439263842eedde300f25ce859e460148ee615029f20850064bd01235ede6813610c8e1b4cabc0b2d70d78539d0182fa1f40e5e7ef2d82ae070ca3b048407507d8f2e7f7f9068c31a46d093d8b956fc0840cd5ae720f788626094a750bd69c9ab65e7b8035c270dd85ce8e1f1576e0f025819324b9ac586f30663ee34bd12d18f376b7cdf40cb4ed162c0d75acb7411c4bb89fd10db699bd050b32b46edfb6b6070ce27a5c241fb61d386cf492097ccc059af8a3d4e4151d119235a768d469e6e1d565b567de6a635441691c91e989be5333902c92ec5e6cd308b17a601a8b6fbcec7c83d96cbb208671c05380771a8d0b0c3fb01ce3f1dc746f70d426ba00501d3f5dec5202669d090c68718fd50413988a3736373ec778e79210a88896951cd1e514514a581fab28c82f151e4da366e94782f3f1cc0644c571be3c65830a381fa0364adf7ce615553fbc65f8f26a72f4d6fb6c8802e65f61435ca39db3f85d08bb1f76ba6c8c3e0712a57d29c52e27e3dfb34edbc418de55a8696282919a75d60a099e5817c4d81b227c19f827ac932a455832c5bf4be1bba015ab1742b8b3290a2e9b9c2a7537624ee69173af4e34ab5c49b99b3acdf78586521b7d628addddc1a36859b8f79b8217b781c8f9a4b6b5827b9a1411aa811218fdc68b5c1c0678ca25b5081c0e3152db45eadd4e8cbd613bbdc991b758dd7a5e765a9b0cd76ba891f5c209e04be9196809ff53ff3f22632d9b47cabef33078b50740ed0f2a020bc8e1eeafe34422364d82264cc67994897afd3465dfe7944a7bc6e2a997e58ef464802fcdee263af90836a2f6456aba9cc6bfcb329d99f17daf7ff2a909b494ce5df9bf31ce92bbc7ccb9e732906ad4ee29a87541ee6d76349eccf5c9d7c0814262d4badc4123a4c6456da52462decc5c576d7df7858901dc2d0dc4e88c8712637cea1d1c82ade48f21dfc7ebb2e7e574b54cc80eba0634f80db44d8dbad324502308500c92e8eef143c24328020d45474dc3a0cc2bd01ea2880f1be68222e47278a9c6fc9382b08621dc09b4729b04c0f0e63d240dad460d77ff87d4595350c42eb728852205a312d2d76990c9f1c8589fe4f5b130e2194834cdcb54bb388c7f977d7a138e3d9ff8d5caeeb69268aa4b9fc0d43bef0c71c3dbcd5f1624ca8388d0b2805287b3f16fd9a66136baef238a7c3280add275d7ef67c499c4f69a30e7bc7f668c2bed1f33779ddbd78c0dc26615d33e7df328ae240e8c8fcf4ce12012810aef8ff1cde2e74124317de6ca06c6e5bbb7a182ad3c47c65067bf7135b1ce697ff70979407b833d413c20ef708f8007b0371c9e8db0f939ecfe9e184a5acd59e91c9d118338d091aa496bc48dd9d280ae2e67272dac33c635f0a1ba7dbaec6ca50c351344113856a3a59b731fa4667cc076c0158307c29479f835bb30b24c4523cb12808b0eedc8e31e260a18082814243048c902d9502516e584269c4fc8e50a923dec5ff2998a842f9ebc68a435962aad1b0ea76b7e793a917275110c2741018cb14be13ce6526387427d424f1e13ba53b4add431c9349f50b3c61f3d8145d6be94aa1b38a8e89213b584e434a1d253546f1617772f5e838c071b9127e40ace55ff197b06f35d83245b85ea3de48088a4bd2c4597f3b16fb3a7c36cec6d56d7b1ae3ad5bdee3ad791f54533837cf5a69a32628daf4f4729ef062f0a7dba733b7c8eb1d214936c081c953662ef163e7f0ef3ac14ee73bd0ef6c2049e1b0bacc8f69514a06b623842675d886316682a7fec877e8fdb7e0ed0eac30edfc049b69a97f05533d1fc3b387705fc2492301889e45595ae072f3c8c7eb33e420846a20a5c82b23668940c50cb45c23d467fb1ea415cba14f47f037a70ee9f8735bc0da0003facb729d6a0d0c8ec68413ee73e37905d0bc6de20c572880da62e3251ffb982d41c8f2e90906532fbc2d2a7d25c6747b0511a9653a66f7ac49be061bed33989e6f61b6c90dc4be5bdc3be29e1aeb4c1a9ef16a63e094e6390511fc83e453f3ea7c9c69d8e1b4b8eb7185e9db238b3ebe78810e2eeb7ad202bcff13112d149f469f42474bea1ab1b469fb67a6fa51577e5b8ba236d484af528b1c59fab24742877332ee36c2b08022aba9cc045685f7e274b3ab3c7df070bf839f6f3aa7f978549c77b6daa1de3baf7c5921f4386ef2ff34066265667e2c45376700089aebde43b96749b8e7fcb4a5ac0119fc22ad2d841b284f0f63f353c88fd675a0f69aedfa8c5f655b6caa866868907981936b27d7f19619686d503d90c97178534ae06ac93a59520ba355be79ccd1cedd67e15497a3c997c443ec9658b23f1b5f79164d52132b29a612c6723d1eb5ac9b8fa9f21e760f1d2c142b937cf45b41e068fac75015587e3e36fb249677e54df3fe83c318075450f819cf4f4b843a55914ca0da4ea296f3f1ff1a510220a5d8ec022b45f7c97acc9a07e477da97c98ae1abe554431dd3ce5725b9888549e545ba8ab119cc04468b444d5f11ddfca9cad6e7c4eb6f3b9105d5d57e996b4191d0e3407e46d068b36518a1d6ea0a3b52f7d87251df3a36fb24a03f7c5db8c5c6302b94aa5206179293f052dc9b3eb7aabc04b4cbb80848953ed738a210d7ccda514ba9cc1476f3ee61d96f4ddad8ad5514f76c840e205457dee2ed0a9479217e90e66bcd8c31079dfd0bd32111d8208e3091f20bf4f6f4332f128b596086ad7af5e09de29efb4e7c07e657f098fbcd34112aabe7b9fe07accc09279f643e3b8541f16f5ba4f32019bcd54c1eb531607796c2d789e33ec72c23216461ffa8ceaf9fa3d866307c0a8f6e8ad6c5a0b1de239f2ed0d923623fbbcb1dd3dc3ab19e1a610974a85f73b73ea315834fa5a42b2f7f88eaca71d4ba180ad2ad6c5c77d213cfc5d4a8cb2c3e633c4f38b0f9052ec8ec2fa0ec23087d612a77d41a8ae25706f71d8146c30a9276ff05138c6b008cc40ba9bf70f9cd2bc6ec67090291a34232045387e6d696d03b96703894d074ec635d0c15fc9cb736850c7ff4f6b98685ccc06e4eff2dd1026323aeaebfaae06e2b6633b998790b4bbadca9fd68700d9338de539cc53839f6a9a8b721d880bd8d9fa196dde968eef8e37297c57dbda24f3c4def97686c7350027db4760ee218dbad664b4c05b1735c77cbc73b876000e0dab04e5c7a3b34a60dddbb1a60214d874e29bba9155e5cd1475faec963ad9889d4abcac18a2bda43ad971332e3cd0f961263b155b63b19f6689acc082ec27d2b72a38bb609078dc85bd576045873172549ad4c600bc0e1706b5bf3426dc6ca74812025330451d81d8dc643a16817af9e13385942e3c6c0de4302501a43bb027fe3d18b6e156e19aebfe6b0bf6e3bcfd181cda0ed654b92a432ba0ffee05f613fc4bea680446ad040f6d0a2ca458b65c050769f94b1fd0ff492b6a0b6fb50ef3bd9063238646f96ae75a893ae15709aad73aacc2b8a7b8fe44d356fdf879ec319ef9985ad49e17cec4538b4e00bfb2c3d8495d1480fd1f1752767a18ee8459454fce06c8b02450d6dcf50dac32e951453b0c98e1fe7e2e3de61566cd3079f4bac8fe404acf07237d0406210e2f7f800dc2534ee689f48eab0fe0af6fc8e60a821759fa76383dad01a00b11f260b7cbad6079e05eabd455f88e608c3615ae191cd60727e7de3b08ee483d60689edf9bb43f765e7e0c47ffd1047d13dac247f1268d2c8e17a765d6ff48fc1fed4b3bb443605a5ee55b42d54464497785436a0f74fa4669dea8b3921012c73af3327dc75f82126a68fa36b0e8b059e0de4009fd6a20d715fda45227b9c1d82e37007702c52cf5cd929535a777269869596a7766301d9a734b98e9a8c8701e3e1711199a014feba06b91cba96b9234a97e3fca33b91d122db83a8d142202854ab2a3232761483f03f31df63de5cc63b58cd8b61252b9507ef8bddb540dc9d8b776b8b3174dfb65a9d626ab4219b01b1018d9146f59ce21a0e7e386986366c79d2552b165518b868187328943d20724055a8b1cded27b78c287b9494110f2e8abfc61794176ce0f999953ec14992ccd6cf66c85b5ffd798751f23458e4c177ae9fb3c30bad18d0ec743b40924b4a1f2b6e89250695e6d01935e3c0dc9ce3f2c61b062e739390cf475be0eccdab1655502b070949a5f499d59b9ac08c19cdf28c1850e5d2d072bd0eb64c442f1810fea0762972bdc05e69f2dca7de0a19b63f62c1f8bad0b49ea1be7c963d85f51195ce9f1bd0aa38a80b89c22216d3e88e7012934b65db8af45671f79eff83301efe623453172d0ba0cd103319619761f18fc4e34707d1358c32015e5b58d2e1774988b84db402d051572d3f9497e57ba965e3e3cad30a43a1f25ca25b43901580108d3e6170ce01806ae1a09ebd08a04b3e1169dbab9c1aff4cc3f15fc8e5e0eb16fc9ca8d0cf9fe54a5da107fa6daa580e007636c7146ebfdc2ae45a23059b58d90f203b48680743b458c6fa916e3a368d1720cdf07855e13bd1f53e9b0853754827d16f96e2d2809ab3cf1bef14fc37c0d36679f5a0ffbeae887b99a3959b64faeddd853285608e2fa0c9bd35febed6fec065e0d983cbd8c187924757664a8a1a52b2b8239682a5f8d825fc5d821967a86390d3f67f8587cc2a46a274bc406449f1052635c7680e4e9e36f918cf459759ebe40f5cdb278094c7a3fa635c163f922471f31902eed7d9e3cde038abad625f7da1ff2405cfb3bfe5813747a1607bc223ff872c27663e9c8113ac4b81c569b8adb78072b471bf497600deaece3631666a400f9d1d4993bd3ba4a98d7598443f529d7b2949584e26a8740af14e66aeb2974dfb2908a269e07a87a3b6d1910f956deffaada1417e463f4834feb0eba9bc4e7a58344bafa5de615bab8fbebd6c604a6c28f82923c423f7b18ddbb6c0d221a0839a40a9884d694588150112893a8a72f7c1b4984fcc1f0b666d4868b0b6f46490710d47defe952a20bbe4d46929a59086fd39989c6ff4e01f33775112dd31f0a1c6b9dcdd8b1e40c4aa7e3578ba4d29742bc796cc7fff3dbea996345ff02b705e253a16c7400479fa29d257c9024a38eeff5a36f386354167a59e86e7d04bd74238bc3fc1c020d8ef2eed09bf0c5ab03ebc5dac26c2670a53b3545997577faa1e952be4843c1d474001a87bb15bb92d4b919809bdcaaf77a2427b0113d4248e67253782de84f09487167295a3aa038e9091ea44ebc09b7b2e963b73bfb07312b55255e0777fb601fbf2ad561399947d7b55b36cadfe8c39eb7f07a606a4c348d4dfb7a7fa07c989f72739c42e096be1e0d3c35525f4799d89fd26273a32b7e1f2a7e01a83b7e4029e125218276b6e5568d97e7d5f78c449bfad2b66c991452890e2ec07bd555f5c99dd50e59212de51211af6cb6827cc6f9b17381056e433a18e4f3c132a4c6fa4708fcf3078143f80a1559a6d8b9d10a0689f4fc61425db27380b00e6cca6a2b6047de0ad42af6b6b7dd3adff083c0360a6214cd9e984c8f0a48f8bab19ea50a2b93d04bd972a3a04c05abcaa20e346732c91b58a5944c1505ca36ebcfae15ad9c5c0ec3f3cb6acc271be14d2927f4afa1282d5fc7673887d6c08a279152ec4292fa2a7e6a99bf32885ef541c4fb45c9d15b181c8511ed5c9d4e44d8b656374146cad1bd6c602b892a816efe15f4234266ea2f44d45a8abd90b06e54939d05055bec508c5b76017b815c893115f164dc0b9432d6417efbd137f214c8b4370813d997f26f2f0e8c44ab8ea402d6defb8d32c1879aad03afdc204fa3b03a8ad00baa06f5196b4e0cf582ba8e2c110856ff46edd4e524583796744266489d9423e3782aa9b25c515402456613775356028df4b348b77d9fab9612c22285022b40352346627a1388015821ea8a747d8ded86809e215402a9017250fd61b106f216d465a4c6e822f930ae7f723dd1c57a98dd91bd0bc3a94b7d01560477c0846323eba415220177449bde08ed83a2c3504e52958936ca2639b5d8e6a71ca5669686015f1c645e520dafad39addb64f3cdbc510127c4e495b55201d0afc324cdd87093bd90b6f83d0e0404a25d82dc0ec66a1bf7b601f80f65bb70edbd5edfeba657cb077f4969d7c5e323f5719a7bfb8662ac24996a8f4ff1a8762f057fc9211fd0cae983efc840ec734e605f5e6509629b18e64620206d58253bb22bb03f480e42ab31a796e0f5c1ef57625322cfd184843e6bcc2a34c1f58df7222a7c3790433b7b6cbfd47646155e420cdf42dfc1a27c366d1293ad92df4a7d01a81cfbfbcc98a3de66d935c8afe64335bf1480a8af8d443af41b0bfe830b940a718fa880fc74a83a85b7952e907385eef739858601124a21e0f9a4c898b21e04eeaab7c59315e9f40da080a04b94bd2403714ee0caf44d35319f9ca87e1a6bbb5f7810a100f957f95485e85713654d9fed3233fca2ae425c10475ce586bfbd55db18562ce13e57a293b11411363781cb950e8e70e38012d6c02e4f06251476c14a2a30e21f8d950860e20028f8bab834f1efae6c6e81c0ec8f4c834273398f06eac1a065717b58410a03c109633270851a1f7636684415fa4cc530efff811b7e2571760fb11ce09e797d0e599012013e376f2e04c6c68d47cf077ad701153f3ea2fa478348e50fcb2e05a1218e862bdf9616d603a8fdfd2adff361b9fef6f8b19edfcf2c1dfee1fc8e86ee47573d83ac16d7c871877bce5751bf1fb09948bf858d43d70f96347b052bf2054008810e84037daf658b41a34c7fc8678abb1b11eb37b2de8f8389e05bcf1fdc816cc736219bae92a531fdcd671b2ea485b6e56463d81ffceeaf34ba5062eb537249b136b9564b2a83373bc8e934bfed3d8c66ad83f0e5d851d6cb23c18832a22f4ef93e2641ddf13c8791f06cb2eb2017ae72138f89fe1c85f2e57f2438348e6999c0ddd0cdf5438622640b6ee30796a4b4d4fb517e425b1aa9de428eb56285414240aa00a418661b5724124e29d1f08af8f6dbc8e834f574e6c085950342ca7209794a794e14fd583f4c807860e87029112a7c58db8915d116499f0af0a1cde3867eba69ca6fec51a16c8bdc9e750c35181d4f7b54624dc6692a9e083ad55f69109ba79b2e5a7c8706b0eef5df4ed13f955833c4d9949e2e0d44894694540fff8cf50e253a26f4c094f4a176a162a8ba2be16d894b1c4dcc572d01e04e8d3fa6c19e007a0886600ed5e5228041dea74138ce05528cba891a70b935ba81b208001a75e29d2736b124a85a6ab50e440b147dcaec352575167876bb07fba9e06f3390bf2e46039aea0188f8653456b810805cd2dfea5fb7bc626194c123e97da879e63bb3be3b830c86661dbc29d29651e32fd9ef156c95128652ab3a47c6b5fad20fe61d18d1220bdbf6ee0dedbcfb5f549e765ee92eaa151908ef72b29baed959920547f96ecf38fded3dcb668c9c703d66f37efaabc092fdb4594b46b673589c67e4df6a2dde5c9525290f9272ccc963edc4cc47133888733c4bcfae643754f248e984f476e63653a7623808c32001a191fc4ede45b04afa77da9e2524d9b7e62f44bdcf8820c421022116874f40f2338146e3137be335016a8271ba53dcbae0485fa7a147dc45253e19c2995fb68b0385cfa8ee0e7b51b0075bd75d651fe7d3ddcd948edd303d9dd149c607b32d0a90174e7fc22f4828b0403040a1d602dc520ec239a1f6ddf523322c9fcbc2ea02585f92dab2683442d7fc519df92e6f0b0a4925c5224814128538ab53962f56acfb068e00628c54edf44a2c23014629036ac7211e07919f61ca1ec7bdd6d8cdc431e8a17b15f65268d5c5b46b84bc09706b708f62613b480bef02adc1b69a6d5bf041afedfb1015a439e3fd36516eae779024b7e607e1162b1ff69c5cfbcc25210fbe20257827019ebd97ca25b9d8c639c913c715a18636bffd41acac736f050da63012a53f6c1bc918b21b064008b7c952b505e20fdf3e6ee9b3487d92442d100326ea15da1162c12039e7aeb3698754ee526c182ac4b327596514a2dd126671513eb609fe787f0c98e03b048881f613a66cb636ef4b84e0d4066f1608439be18e23e60e42ddc1d423ef57f7094587c682ef243319eb1523482389fcaf2eda2e0e9a635ab67b0b161d8c4612ae2a8f7a465881762b3bdb04203e209d4c3a5a1d307d42af73016e1c4a47140d305e5d3d32df9b6780e7f32a5b7b7532ed50614b8316ca9187ff652744db4e18faf3658f9dac1000d88cf283a3fd0e5d6ca229599e6aaf4f8ead73048d5c0803e47a5516e8e80defba2929ced244a30f268398d2e6aca80367861d605b2b6f7881fbc4bb86d0b6c03bb4704782251d3935d398863df66aaf9531ab00649afa44bc478bde633e739abc7aaecee9387af290898538521e1e1160f9429f50769daf79506e85f3d8187c20d89555cbb5c398c502e376a80b5a5d8f853ae59648e99eeba0672e121da01c3f72783de68543eb9f24feb5d9dced675119f758ef2efe309099d5c33536eea5fa0c5960b9ac90694b498f4eea9ce02ea426d14a9b1bcb9bcfe8f743c69175a6d5f83aefd3b8a60d932c24bd6bb1945dc905949138db8b41f2da228efe3e7ddc80a689e77d80398d4b50f1a414ab513a3f9abc133a91623d6d20b8ebfd2d65d872291099a3fb4c807b087aa0c1902bee1e3501cf16b2ca68898a045e3c70974da81b4a0771e81bd8e54cba3ab409e92923b0a21a569701e95f800d2f061c752c3abb3d8debf906bcad8b952344817d84a486d4828201d84bacde7a8aa2948dd5488446ed65d34c3747b3631ad3e9f16735ffc227092f697a856f366b8014bc69557d6a7062139dfcbafc5c7b1c36662ce66ff97d3778d183693ecab98cfbe941602d6e3c60bf6a9388147a45896e29d4925fc7e12c72d32a25b5b256206640e8fbd7224202e584188f52ac593e13fdc7d4a5e81337af345796bbd65894e0b5e86c19d9c8bba0663dc6bf4a4663b167bc14592577c803916b0d4e2467c1a2d12ac1463744b18af81d28cbe8f822dd51c1201c4275d6b79e3acd08b00f475003057088abf5fdaa30571ff0e62e419c8d086994cb5163abb796e86943142a8e622a6efa5ecfab5041db15e26d1ffa69136f7ae3bb9eed26407385b72a02e81f3a2daaa6e094e5208c38a84624c64f80fc81d7d9caad4b0aca4953b0eee10529b4a4272f97ef05b49630273dde627f6121e39afce8e575917826fcc131cf113fbeba77f2a265f3eb38611c8a5418bfdbd916cfaa06f4b95fcc0ca5d1c6e5bc48cc1e5d221e3664f098c008ab98cbc929299bc6dfb89ad30580cb2d69830d9a885934218be71570c08f8aac52d4e32de961368063aa7f72a913d98ffa0e09c9d377ac42652ecfd29241ee6369a7b3f4b167f4d7799ac227378aac77aa1c431796c6cc27a422919556157f6f7e7aa17aef1d26239afbaa735b7f0d4941546e6e0bc27bbd4b848c2b55e3f7b7bd639fbc31ffce601225043bc09205b307f020fbf96e734a7f9fb4919dc3c30f82b8b63a35c88b6e007f6f82ba89c30d72238288599711e000fdfd84d8d05ccce8b02f63a5ef4af63764936f48a9b443fc621cb15e439b6043e98eb58d7d2ba85d0ca354025c35524c05393588c75d4ca621ed4f66197e893b5c4e5b04b9496ea94334d32c446d995da7c8d59335a42c5a0d4ae1cda57b3c3b5b6d2c68cc873e056726b059a22e7435eac8a5889bdd2d456c6bedfe386bf519ac9dcac4740f7724eadde37cf22de3a243b3ed97a4fb834107019bc8b97092036d60095ef0eea350d29cf0be4f1e6dde1691453de32a5469d2e675f91846284cd5ea828a85746cd7346f2f8426a1c19df7cb0815d9e9d35290772034f21c756f88d5006de821cae6851125a5da3dbb48b8725fd3a31a25c3195c2e8ba295521c62e856e66d7b68c3d232e2dfd4170939f514605c2a22f3766d52b5005b92c11cc0bf8acce29789eb33ac008f303651b3ff7c645650391f46d4cb1a87a743c524c5842490101eca0a0b56eb74421541bd9f0ac39683603c50c99efc3704c9bea057ecc451baa944b0822355e3f59108a67da51103337deff97dca62378c1d07f17393775e3800100e73e5ff1c24bfcada77d9fe10226546939a9d4b58d995fe679981f0533832bb11a9dd3eb602c7f65e3d35cb15bed3a6e1d8b11e1aa40177b45ed3199edd77e0a2efd584996c46a8913c6fc8589617fdef789253f9279e76c161cbe4d3a8f439c28cd963cb8163fab40b9a0cd8de13abf7c826040a5241fa1a8fd1324eb40906f1f40f9188944362e983d39ab89b87d622df8b32b19c1dbc4d97c4fb9b4cc287ff50de6ec9487bfc6767b2f1b781c853c9f139e1dcebe1c1d7927514b68eaf2b0abeae8c442c775a20598b91456910c819132d19b30fdce330aae2ef32a90db888ef5e09fd5d747c5e54dbf130af6c29677c120ea94e61812530b76b1e60c6dfa31a3a4230c36a90fbfc8846bf932620e3e28c4e863b7992012014668cca1e7346d593a8ac436de93830077cf915f948917d0c21ec2511d831b32ecb99c77fa3c8bbf8542b01ca252d179aed4c24109a2bba3fce9bce75b80f13a3ddf96af9094e3157edf29411e677afe1127188f442ae59a6a5d2ed26c9cd0ba8d9ae3560790e40cbc829b4950585e97bdad5b3908e9a4e108f6a3274fc7915677b74a16091904c21b546bb01beb7c3fee40c8b41ffd07bca9a1187d1eb35cd2cce60b1cef23d58618b255a12ac6a5f0badb9af5abf40bf5d0f201b91b4b9cafe4b0d66c60f7146a6ee25ef7f750a81d6f935027d00ef70e9f48b75d800b0d305db87b8d9e39a1d152fbd1abe6e6dfe0340c70fd2887af99a04a36f173d5ee7210aaca7db26727a5e5e261dceacd56ffcf3fc2038d07ef4a93c12699dae49de9079616e9b38dadbe6bd1c3bedc967f150ef4f02783de16eb8b74121287049024fef877ebd8de1682faacb7962a8fecacd5370bacee8c2302191b0fdef9a79d4a18cbe5ca5ef3bad66fe55fefd8d9dc35e08e8bb11ddbcb9bfbe9b122a6b76b8dc66d99a064f26faf3096a4de5465ae8dacfdbb4840f9334475b8357d42140802c104bbbfa2fffe827859ed650ccbcf5c7dee37b288b34158a355889067261faf5af94d6d2e2cec9caca7edc1662e68a74fb41d9d5ce39fc9fb38539b89e1ad71a7fe2d658385463eff43d63f106f2171c92c559885af31c0ab7ae85a8e031208e899844278eea1d3da9aa5e4a94e8d9b73549ead2085088f13bdd567e1e2e23923072ab76220881d015e3fb390687449bc8ad829ab6286823d18bb8440dbec107c72d15f281e316e4c0548aad0fd8e9d7e7706a427ae31db9c2732d4cfdd671cb99d5c7ad6555f9f583a60930ec2da44dc5423637f9a3b655c284b8269e9a0fd9e446096468056e7a6d598190398341421726f366ee0261c6d0ad1203198378f1881d9ffe0061319105af7f2f9fe91357cb5033999c0c0f32496a4cf22878368556c146eb1ba76433e3921bfb36dc09e1fa738ef170a634a64ad9c2c93d485a7dd7d38a0895d936e29284af993f425c224c3c22c4916790c311bd120eb970b43307cb9273c98501ead19ca024ddca20ed8a78b797d4b164b3223f8a33790f94f1daaedfcdf523808207e8383cdfc5fa2913507d176d1630a08b2f8a090081c41ad355ea28606ca789cc2aacbe421e13bfffc564ea6e245acb8f7514f6f94da50b60fa95a3d256697aec17c633e1ab278fb4c4ba3acda78ffdd628cc1277f9432d2c681419a8973500ab4ded261d4b42b3edb16f4198e89302822bf11727fe79a5143b43d40dfcb1ef38a6ad491a788c4bda413bde92ef99e44604db7fde66a0ef8a3c4c3a30498e5ec09b2a65abbd1c10e906853cf88a05119aa502b8f8fd03e9647c81e1887ef754ae16fbaf6e387db28643b8b66a4e9e7faf31d087f5e1350257bc5c802a07998bf666a33fc3f60b28d9835dcebf8c5809e622378670814c30b9dd457159fb26b53e1c95e168660809b882ddc1c3d4549288db429866d34c76220154ddd4ab9e5596c13f60e13cf96bf5f831737c841c40aafa8e905cd01d7ab146bfd12aad52c7a685d8515377ebc87b2a9b1c3e6a93e0ac6a54d68a86260ddf635adaf7b09598a7d21c060a04e0c9273540f449264330a1d8d3f2a33cd214b7d4b76489a8eb05246f31457281204fd66872eb4057ed6284e4ad0f8095c26b3f54a17ebbbc427c68b12586e5104ff44053f4ac1f17aa6a8a59a4567d7e30b5394b69ac5dda4b22ba27d1c0251d2e60f627cf05e2c984cf15f84e0a9842896dc232d1347dd9a1fb0cbf58c2c039258059c52731179d1fcb8913b1c1a158858a408172d333c2f894e7877824f0b1507daa2733aaba1cfcbeaf786dfac0cf36624f6146a8173df41f0daaceb7d50e5dc2472c079d5adc8361313e20d8f575c031ede05dddbb7865d982f272dce26837d7fb61c6990e77379061ae6ce5b9fc9f3355bd4ff9a7db73f940fdfe0eb48a1152e89c4928723e9edc14bce90b22779c1b72f36bf24856720cb33f6a809bc9d2ad421fed5227bce558898c88f3f23114a453e7af0b0a760f9483396b571a37a4f255037ea0b2986f9a1f398bc80869d5d7d1de832cc72a10645984cd021c490e97306ef075a842a3791b2ca4816f3d23733ca16e917342c1203d8c846b1ab1be32eafe49924c8e1b81489d892215e90c59026138d530d1577b2f8010057e218bf92446dd05de7d6b9678420e0ffe060cb9412de06edc13e639d5a655b08ac6e3523aadd118ee1b108b5902e9f0cd94f59f9b21b25abadd84112b91a60ef254c0f394da418e64431e6eded88c865a976ae8b6332b92e02c324ce0238aeeeb94f1776b98401f04da5fb7e167fe892753678f7bffbfc1829c2ad70529723034e1bd36b9ca33716114aa3b90bd8c13b4fcdb9fc8a0235715a1463a91d7ef8814842283b1258808f4ad671c0e7611d7ff28f98300b79b63e266b7d0443ff9c99f7ca59936cfb22543b319f4ae0f8612416d7ee803b04c63c275d721f6fad0b60dc7a053fb11c72e33a78424addca64160ec17c71c5f0094cd63d2df90d565873ce629ff1f0bec2412a5253932cb1f931b36f566f757061fee36a8b4c4548793ff9e71cf318743f4a1c9488be3994f7b7e86c6e1281db3e77804bf2c7622474d9d9c4ca648988fed0e6d84bca4aac8479dcd701568470bd61f0c0b2888a1f23a35b559c0b816acfd6c1e53b892f17b9ecdb8650498a2eb11c812c800ff12a02b563beb2ef9efdcaaab337fa9572e92eba5aea0bad4877fce4843427637b7fd88535a30829795afff493e9998b5aaae17cbb6cb83b561a84061beb8619da0ca7a2e5142f10096d9890c65502ac423a1b164c23afa34de779d0ff0ef6d883f44c9628dab15b6d5dd7ce05118633e841f563d65ebd0b2614094a2e1b0de1f9c3f230f167c8dd67e2a20fe7f90ddff7b06f55b7462db05987eac44bfe9432bff0133585b7494c9a4c93b76edaff2e6d22a0e93e1e9e1985c9f8dda50822ae7747a8ab46ba238f438985335129a133fba59fcb64e969414be2ed36efd293956e3c89c5ebf846283b6a2d78cd2a5e3858e18c51cfa4255a0121b4af12ec4a3295fe949668d6907f7b5f0b9f9640b239d6d6367073680ab93f2cb1851ed0aacb307fe3145bdf49df5fbd414e3c225d645aa86c26f5896f1d7aaf13cc0a00cc2dedd5d3453bbba6830d804ad401b8fab7f6510dba3a21d65d5468e1107a7ddd63c06645ce6c7f89e82fb95439384811ad02a1ef00f16bca32421490c87f6d3d140d1ba6234ff9a3b74e66e774aec2807fe18700587a5090a6aa5f0f00e868f459d93831af24186695eb3c33d68cf6df43d2d3d1bd6f6420f1d7ef572d8e5d1d60d9f5071ffaab15d02417b4d6c94ace7a8246fb6bb4c2d45f04ec2f44400dac6fdc84798834340191dc18b137a836fd394798eed4ebfc39be3850e33893de7f928deb834d8cfa9342e2eb1250506f0d555007098d0a21cc8e4e3a636eff171ce4160a842f925ff50c3b1d257d699723b3b0d47225b092139d06c9043b87c0bd370d17064c7fa9b35ecfa6c0acd3aa3ad8170850d3c9a5d2bcf14221d77378e28dce1070af311c3c5a72247d9a43eeae38c0fbede23f500cb8a372df5b2c6a1437725dbb82f94dacfe713b8854c1607ef473d8b437bcc5866cb10a8bf9f7d056e6501d2e89ee8b52546ac2c1153f00b7a765eb435eba65bac805fe592142aeafeb5019d6d0f15a6730ce85837e2a08478c2dbef608b0c7912667f3576817d187ac4a22836bca24addffac70c15cce5604b0d7749d856cf75f7875c50b747ede9a43d3878708283283bf908efb14d4544ac28e96a7ef042a54ed91599496c40bf9fbfeea1a49c7760449c5d4f7008d7224d4b3829aac6b55a3f2295966d31821e20de126a7bc34a17d65f1db753ad4868a847c95a79b6d1b037ca9403f12639c5d94eac2e8087b0d302f914fb068290f77be34a0ead151b5e673273b54c0a986c85101d3616387644d3d535905e9baa83618092c54841479a06043804e081821364af4a5bd7151e77606ae4756e415a6debc80fc31e0f40665cb4521ad0eb25f09c307687630652570d83defe779d94d8bda202dd68b64b7f64c39edea7b911f58b975bfbc2de1c1ccd55bbd5c5d98b3e3a6a44899d4733dd5e73f5091ae98d83cce87a252d710a63b68b6b4d738e78b24b4413463850def2ac43fa0a87b9fae3d84a53df24655d86dd6a367f92eec4f52a2dfe029bf0d39fdf1562f38419a95fa3f1a9cb44d35b944c02b499304de70db1430a535522898b77eb812a54d0a8bdc3eb5c07f12e5c215def461a268a01fc77e2f3d43ae5daaf1092ad043548adba9db475f903985cf2075e1dfe217d44105466f6f107349c0290aa4f7e2f5d3a1dc1c8fde1ca8c79332014b6cc839bc05faa9e5405948cb91955c07e00dadeda8f0dea0a8e6cc6a6d93d20b769821f5aa4db2c0998f274583e6e5a648eee828a996341e1497c63118aab06c06c7195b80aacb62d6be6e8a10fc50ca56b9b94ab03a3c338e3a16be7aaa6b3d385bbea89c81b5b907a0c8c2fed07770ad3b1c8390f4c1215313e6981235160fda3bf0f46421b834856c83c6c42a575cbcd255cf4e9419f56565647bc1406f4aeceaa601732585496c4d0fa05b221b55179c211b90934b756c7c00afe15894635538d2267924c6c244a01bb536e08dcd1b35887db3d746dd7bc14923b54a3497da8637dda356beccdc18696cf58e69ab762792d874aa1e44c5a8dd577974d1c667f1e8a0aa72814bca3f937786b74a13198b9355606f8b11fb174a8b8b2d9c81bbafe90bebec204a3a93050328eaa3285f2f056199597bcc353e14b308bb5c3e22d91edd401124e03727bad236899b8fc26d0b51dcacc2b0da9414aa7683c3af2393cb106ca6223d4bcb37b77706a31bff303252e2c015dd0cd91a28fe85a9c28391e988ce70827c1db78ced988d71963e3e6de4b0588101375f33b79cb8d57c09a1c3637663da52523aab19532c031b883922e4ef836f34b515b3dee861f7907fa04c0cd4e083d68225d86851cb9f671c81672804714a9020acd0ca0be4f9c33eafd9286b844fab9c2a8580b5f79c7ed7992bb34c19eb5ee1aa38ebd620c999b91ffec0902d5fa933c47667c0628272ff3e9d58e623049ae0c3caa0f7223a48bd647a58ce1e95a62ba21b7505121cbd13411982ddf338793177c821ef7e2454dd2180c7e968f24cf53efbf7e962f769ded72c00c7312ea2dbd47ca95ca1eff0aeec8a8f331bd7405b44840cb0ba3b0cfded40a208730fe62e537c99561714de7d4f3c81e0824d7a045db8da8f9c88e1114a9a0c97e1fccb7f0126a95d942350ac132157eed442117c22cd69487a004c08d03eff97569e18adf23d2b54dbc924433b6efe4e20f42311ccdd66943bd5e31db7d4b1052446626486800a178347c5475274751719c7ff66073d2120652cdc9f1affc2a350583ede431cc85ec00dc441a6b529592a8aeea99047d257a05744f281982f8178c9a1364e21c9dd63fdae573e2b47f87b138a4b3597377a507e898492dfa61bb255c40fafc8bd9349896abe0a08b191cf1adb21432660a663656c53bae3b8f7db657878b2bb2d93e62ea399aeb6f5757e775b8e7fb3696da882d24d6f9465f1566edc0d2d6c08d2cd1a5dc56f30f9c47f78fedbd58d09ddac40b279026d513b7f9c1ab721dd1f8a6835218b0c62d8e03c6af48d530d6ba7c74ed3f11b51ac0b9a8f319ced2cd2846b6ed308a1551551dd598fd2f406e8731af3a8da3dc6fbbed744e4b22b3d73d5331bbb7245082ac9df6d72fd59db877d9b8bd442d0f671ef0eb2b872439c1eeb6b92368c52b662536b1e85645705511119f00949d5f2890652d1a71d5457674eb761b9d02657498f9b9f7bd8a2cf1cd6b1d5b45d64c00c7f2be2e546ab6e74d491898addc3e762c4476cddd4e5711f5a4ab6153e333204faa185c56c2a81628ddbf4004ccf4ce649c120d20fa33219d0196dcc0c7bafe2e34eaf327171a72a52c93528559ccc5961e1d502b158a7a701114c47f35cfedff455c23a67f5de66887b46bcda187dcb46f00c16e2225e88fc9fcbe5667a0676bbd527d371bd08eb4abec65801c226e021da0e0526dd1882755901482e8070572a43366ac23da5a14bc96330afb604b5367f64ac9fea96321efb0ddad7c329b3feee015b6a668e4473aa520c94ec95d4e367b50fbcdfcfd81e173052526cb65a34aa748476112c0c25dd60eb7a532d90f541bb948dc26018f4443bc714c6e67c0a18dacd5c6141b2ba8cca6bc820658d6de9523a8a42066fb0b3b56eed8f9ddf2d3735f87fc49ed82542fc2f8c4b1b71b1ae93c62ea07f1cc052c9dfbfa52f2f180943ca05feb7d5dfb04f57d4de9136542f9facb60af908e6c4a047496bf382a1658ec47d3a27be7027ec8555222f9f16dff7029983ba3bf0e99a913ee6f98ae03bf295f86469f935e0c0e89658d116a697009a0ab5a99a7434d55c25a770d4b57a741a55dcd7ae33ee33e819bad15413789fa3f1a6f116bd45c9cfcd8f0de0fe6c71814939633b8cc3bbab36ba416e0fc45a5ede806620506a6e5e222f0eae213c7e3a3a359fa4354a6d9e04dab5ced04bd140a0ed447f9ca458a9899933495f0337ae057093e52ad9aab05c6e4e161a540cc70e4f24bc7140ba401b836897e8651eccabf4fc3a1c0c829668dbbfc3448dd5cb20575753e7b1fb97a098386a10c9fc551021eb23eb681dca3c440b98b9bdc80723afdf5f22bbe5989755e2129648133ac04c1975118af613dfe9883d2982cfae922a19b44a875ca75ed3f5b92f704a2a0c8ac787e9f090f1b0c6f66223985544b68a5696f2afffd15601443e29d2afc6d9ffdb4f32b67e4e8720fb29102cdf4383e58ccbbafb106264e5a2fe47ff0f0e573c2e98b849a9f1b3129686a071e9b57540cbad68dd72e553dca204ee40ed99e0367ca45e42ca058f9c14da69349c183d8fc49d8236a0e81c9daed7082366627e9c95f72f67acacb320ed35c915d1abb8e49340e41bd3c04cc591a115e1664a9b6d8397097d9a6622a7c60e7869194589be5c0c7d6f0854dcb9f535625af328875c6266c3c0ff3df818ade7283bc1aaedee4b29e90666ccfe4e2b0d451f0972105ad695abde4cb846bd99573060701d477496c20f5c8a611cda86e46e6194b8b2974cad6b5fed3af15df6447fedb1922d54f71e325e7f27a8303fa42f60c14c6caab36ec22053599b1091540235472e37e774eaf461a4a64c54b9a482629495326b53b08c825e0d19a725f14d12fa7e2efac47bb2cbdc598af68f24e7f338c5d55950b2d47e09b6037e30247083a29038785da56134b57863725531cd427c13af0298dce83e68f17d13a85c2e28fe7d34a05250583b1a4732ca0090be652819091c161205f7edba145eb9f65351a85ebbce227e9aced84dc34de4cab1af36f52a01f287f1b8d58f35a17306270a19c057809e24ff4b78082df40a77de032f987d7827299f616be6249f4e5a3e190cc6aeb85ef605e27b80001a4ec66357ef621ad06649953c201770cdf8ab794ba2d74500a65aad04b9c8d793237185e79aca34c5b8d3399be558c70e4e907e9e70d798d62cb1888b1b0d6aeea4ab6c90c9a2b045221ec0a1df232b6bec70ebd5accf45927311c96e3a463d03039a27c137caff88b36ad06388da213ec1fc0c75c6c646faa14da7fc748dc01be9e6b62236053d79a50181fffe0700fd7d78815c3b99445ae88140febede19210e7dbb3db72b4c55b63cfd78aaefd598595faa58f9e72d00bd9b102bedc15fd31f33fd3a840c19987a91b1142b1f26df9635230eacc243f27b4fce3b1ef62cbcee2b30f3ac216218376d40db08bc35ad5f4a6f5ac0008a9032ed17e78798de71ba21709c63fddc113f14f320799c29e7ba89871e9225ac953bc3457e45470ede7b9fcd70714abc8dc19a4802bd06ab1acd59d69816778d55e7c9f10ed257d28e9c6e9ae0e3d53485a20ef6c2a46d6355a64ccadb040450dfa5f12c3f535c296e08e1183a685b57ada0ff8960a6a0d895e09c511fc79aacbcd03026b47eeb3ab4eb57beac82492303e129c3e168028c112518bb7ebb9fa4269617cb2ab61484914993a79a3f117434dc928047f2f549bc7fd0a3c4e593c7a554d046a94e549cf505fadc3537301f3be458add522bdcf5fdbc9942dcb34c158cb713e2df0ef0db5c7ef5500bff6bb2822c71a88b2c094d1ffc7e130a53552b4b3fab54e8372be661614df20cb7396a788eb565e30979e3c2b318a51257323b0f99ae03d179247f888ab731227111791f95134c2efd1dd44ab7857440fab93bbe07e6ca3e9b0d5468aa1c478ebc60606f5716b6a1bb53c366fe23fcdd205369178acd5dd230abbb0734270ed92436b5e73d0bfab9e956be48f2c31ad986acd2dbc06dea2763ce85e55ca73fb13e3d12fb5e8267cb7450057e4f4a0d1a7e3436ab139ec637c0cfd8d5bf14cc6e43cc61665973524a86121c07ae6611e43a5adbebe35549b42a4488fa0914243d47d13368da811e38483b67ebe5a4ea2f09a23b70dad0dd3693ba771899dce8da192f9b9536b801d3e0391968da8416b762aac180e30add2c730c85e400c27b419a2af339e481522297e3283566d296aa7691c15a87bd8c3a831ef477e13e9ac3d44e62731757b1838ae3b88ea3a5db9eb9c7b38bf16aa75cbe2ae9faede2366a17f9a208b3da4af81b2af3a7ca7b6a8c874d5f0d59e20a26143e911d4ba99925a47c5cf23c72d788bcb756a75653bd55cb0c13cb93e930b2e4ab236318e85d441b52af6793996abeac4e692961f7d335baf155068a2e99625def90b8434b80357e484381a791ed7ebf981d7dd5c202a15bd2536845660dff3a9a1ca6e9b7a2f904d1920cd7954918812ba863411a026440fbae3dcb3459d8a2c44695d23eb3abb56e2dc1935632787b33989b9876c6c3b03e50021740d3ba464d536e85a486004812ad1544ed10c4751ae13344102c44e50695a569ffe6d4083d5b26c2f46d7c5840c27856f45938bb13bd91e193c4f61c1f5ec2ce8e3a322a3b535a992633c3c82b62e5a3b81eee21d620fab80af635b1f85da35d204b07e5b6455d3db7e352f9f2968edce3a0c34fb0ac425da027b98d509d0862ad351763d7c4c9a4432b2cf741e2385bb26e7acb333b093aed231bc695330b188f140dcb89d752b9f9cf11716a076bf19690e8196f0ac4483e775625500edbb88e78a3befb70cbac0634ae3b998aa0b34db39f0c088f2196550552015a405c20cb171bccd981445b21176c24ebbbc32b2931c15a0f84fdeb8a9650f6af563f60b374da42601a6f7c9e1ac6ecb854712ccd5a9e811cdbe2229d8d517ff7e809e758bf7c03a88884bd33f7f1cff3c4e317d612e57c42af0da0deca619ec7a16f49131aaa68d42c4119565da3af3aab8804cf3e81b9c4cd389bf1c95c229a049b696d2f20b348b68d1c41441158a1a866446164e9fb4367dd861f287bd543eb164f57b1e80985bfbb0a75be18ada1ab4079301502bc7fbc086d2691082f4fd94c90c9429c99a703d724ea4287b70d58759a75ea71106b0e9745fbac0ac1d4b8d6b6c06b885413e3165f0fbea378507ff3c470a9acf63d241777f486386cbc66755d170e8a5a80fdc88d26b018aa35739993e7e8992ccb2ccd4edcba28e6cfb6241efe1689dd2f1aae17a4c1f765e5136a2288db67da39672195e702596a0b70b47299741f4e83390e3f342c046f2545766807f7b5d7ec0d867ba5e086ca36067a3e9c48ae593e101c9b517df65a4d2aaa16d3c996a1fcbe7fceb97a1f5bba29715407900f03f4e072335c34be830a347f75b74431496e01bea0f6767c3e7094be5669b514526fe0e55e808ed29f649542d2b641308cbd9b5f899d3fa8557891cc925bce017ebb6ad0c2fea702cc88dbcee394038c6f0b340db763e5fe15587df7d396da30abb4e3261a3fd41a36b17646fedc3ab94e585e989dab3ab2ccf4601e272bd1340e99ea9ed62eeedc719733b3077aecc88a1a77635003e81c1192d817f52afe089fd96df5f61f2c0f4bcd6490acbb9ee362247b8553860b5192d32b9592343c52f51897b45e3184c2986c2281572bc18a50463a2047c234fe0e7182c4f80600dd2c282209d207027be10db7e706e71f17e9c2ea51fec610fe718a5bca17387fed873dd0ecd822921d280487ffce790df1cf8e538c3d2864ca7532db8208f4bddd4a0fa9fd8b38b87c921d762cbb10b490af8262ecc0eb162b9d4218721a71c19618ec7e0392b9cc9ee104ed99474ae02a21b9af976b7b126696d075bc0c253106ad98fb7112844a6f5c91d2dc74cbb81d0225b2ca6baccdb8a21d08b3c500449aff7cf3bc704827acf1ed25ab076a0756d4f435b96c7b3f78793c311739242dff63a0ea974782d6135d49222f2d66f3a801a9f87eddbbef3d4f8bb9d14697587ef19a190562f768ee29b79563f4a7c3b9f906cf54cd1badba02d2ed7fbd5748988759103732fb186d2d1f2107ee1e8a706dc5e82149d7ed370d425a67c3f61686c652616e5eb6d782798ffff47b0a697a02a767655534c1bc1ecb71534b1798dda5c59adcf3a606a08c470d630c48c04ac34f6fca0baee01628d8b52d35257db44e995f3902ed32a84115168399107f2e4ce29733e5e9885f890cf53bab977906c42053d92b91831f42ac0f9562495c29dabe1162d57b2afc61d53cc893522818ac13cb266406b6659c2b8256401656e6cf45e43faa3b08a05dfd0307e6abd2365db2c7b13cce1b62c296d1b839899fcb4ddd31d17929822870756b93efd48f65ce56c0456ed0d376d7ac275282beacdba789253affafc7c162cb205178b92814a447c0a58563eb212e2c57693c25a42d820dbd5bd9df82c38e2e0cfb37823dbb5eb9924a848c83f5e87d53b324855499615300e8bdfc06beeb90bb2357e729f1f5c9c1ed405708ef046d602192ed4c3314b2e882ca19a175bf3a2cf2fb579ca16a4a008147684d7d10cc978abc1a15df0999d00950b92c34fec9340a25bf95e3b82f74b046c20be1ee9cea051055961e997e5c2e314141177a745c328aba7fafd05149b4430135836eca3cf076cbed9c34c3f7a93b6a8e8231266b24368a8b85c694bf7a30a046b74e25955f3615d404585759d215c38ced97abc0eec14251fd0f3541a6124ebe4b0309aad20459f750910cf1fbaaa49b7ab023a7244815e1ae4f5a299123548226d512bf52995aaf7112bc5470142587bad7258cad0ffbd9e5cd720dddfb467a61cac7cdbf0ba8be4be4d5690cc6bf9c2cc7ba65e8ba0ac2bda65aeb969502aa4a1c78e079b1cab3dcac5fad3618953660b40eb0d3487153decebee55dd0fe8f3da986268e7f980ef85395d02b277eae1a079b0ffe9df8b7a3dc7fa208125193c597c5cffb5f6337d58374fc5fe08c0c11bc6d7d114acff3898afe8a8eb47c8855983dbf2c57e0742f3e909cca9221ca770001bea85283cae48429e1466ca9669a4823e5b2842c89a462f6bad5c9b257e8ba1d1adb45382c5ac695d92683f06fe1838ee8da71a35741d3e208e664abccac8f311e8eaae2702f75199eb32efa0ba3d986638fa160096ff52c6aa2eccb10994de7ca31d1b41e75e765d546f0d29ca1596c66e0a59117b2fffa8965517c79dcc1dca80245f37960f606b468de54d8c53a481070ec5a136a228e38daae7fade7a9901044410a0622afd92bd0f75e4186316e3848d975077b9cd2f9445cdd53f8b12d9e9b84a1253f28e335bb5e6b7ca142fc70ebfb9d1a0160d6b740c7f920cb6156ace9a7ddb1eda66d2526151335eb012482b2559b3dca10980349a6a79919a7f0a27b29ac634589715ec9960c9368a5d1e5ad6a54b502b99f81a72483e6db177ab9e6a388a6350b3c9d4dc1a9568f6ab37f0ca9fdff3cebcd75ed1a9d65ac211bb086247334fbbe72d1f2a1ec892446ff72e425f1bf535a9e352d6237dba279d24f2c4cfcc15f18a38073955ad568e208d5c2fbf44f6991d6bebc2314ef3114de5d33c5825c412fe9b8e1ecfdf5e6895522a11fd82c8520579b6699f756cacb3ad361d18f726f57607606f009de77c45a37eac0a5ef501ad875632f44b65696370613cee04283529bc539ff8ab8dc71609c730c682f997548c6b0f878fa5c3a915d4a414fa995b038582c18ac14cbc38eab69a0cd06cc3829fd1955736bea523608fa040ca5f25a1c3b078bd821a093775a22e45f03aa4e3f578d18e9653a78ee96968390f999a04d4d8dfa929f14585c3c59118acbac5731584424fb8b054105c18503ba616bd91acd517c11902cca023e9b339c2745b1441bf5902189d035786038ef7468d2102e1803ee2d0823380214cdb80aefe2d9542231a63182d1a4d1e8cb9aa95c7dfbec0e45fd99e01260ccc7f6a8797f842dc1b1c4fd00adf9c01213731d793ed1ef15cead98ef8ad8600a4b6c22c0b4a6ed630fc3fa6edab6ff1cdb93da7fdb01482529b0927e00dfd849da00a5f0a3af79ca756dc81bf38b55dd992a5c4b195fbad7879170eb6583ed6e6da9567f48b6c29165e094ac78c9314885ca81dce5e83d20ad5abe1748a85a8d77797f425d0c212ecb2d9c4340080c304ee4ec611bc1b4c746c2028b213cfda833a8ab031bbdc86f1573794918cdba8b9b12446de3ab41f432c06b0473964832934b056df907d4d0886174e93207012ba0510a2a720fbc123df3d7973994a2d37d17bbde65a9052c32ea4a9eeca6376f6892efaccb15f2d2f3147e6323012227e06df99c3290224fffdbefddfc0419b35c43f3ff605622c3f1f637d705c6f85735332539ddf944f08899e9f10db362360f1fd81887d9ff1b19883e160d33e8c43303543637dc2ef471838e675033d6422e75718011fad2d2aa95b88ede49e63515e01b4a67dfdfc10a7bdea9fc44c367b32b5bf1373c93f7408590214f8917fafdac853e0ac06caf29c02ddf46e3e9fe6dd42cbdba99f3443b32c03844d7317c46b3736ff1790c121d27f58d02e031464a79d14cb8fa87a0f36cac25e161ae0c86a579ba9dddc25c9f22a894988eb3f082a4db6364d4124b163c427433707bd65f8749a2fe9e48dbb95ec522ee1532647ef4c3d3914245934d1956b65e62c480e138ea381c008cc36ebe541931164619c0423ad3f1b006b7180a670f8ec42a1c5d23adb34f94e8923b6cbdab6e9bc461c1d4e760aa02c9a58ee57060bdd61e69e418db70caaf5d627c8c28b279b0616573d1156edd31aa896783d6b9d8476c399596f6212b2118d9c4252fe9582c1d7eabfa64b63eda4f02831d2c26877654ba5bff1f7221ac21e1978691d5f0ad133cbae796a41c4cfcee54742b8c32b3b3ebf665920ff9c5064256242f9b3123960dfcd6b8b565b05e6fa4c242869cd191ecea62ea130c4022cc75d73722e90214529268a1116da3c388f7a8444f2e38fdf232d6d937a6f927ff639e46822a590bc08334165ed541fe4df286b04e692b3dab68fccca13f343cf2b7194d964600e40c7e01bd482923517bb537f3dca989c551a8677e92a8dffe401a379e020e8cfc5a50f208cb28d2277a3f0b757346c8c5949910935816cff07c5907aa121737b5ec576ba6eb598ca38df1bf9f9ce58e7bba9a326f1f79c371601885cbf5f869ef7ab61de86aab68cb84ac34d8b00719f0b9d79202e34709ccd22c865f825a4592229932b87c68c24e499a99b9be071737a506e80644bda5c286e0eb35d7619a4a482c4f07da3b2775941a499b0b3f5fbd3a203fa0d071d210b358c4f40f33f26ab5ab24db6090e90ce194403aeaecd0c710dd65795a035a4ffd32be81f52b095c2eab2cc1c50ace483e6767ff07df49f26d32563d8694edff2c0dbe650cc0e0d8a78f71854d8b0dc1758155cfdce898d62e813727465086f016468572999e70c8f7599d3a1354c71f3e8daf43b02ade7568ebf03cb076ca99107f81f04cd8748062fa4fe710ad3539493982f23e810a3cd7cb9e0fafb22b939132219f65f8402a77a4dcdf086e2f299026bd5731088b97434779c3a91f063cc9067807b9929ba5c956722dc00482329780a6bc4934025743e02e0ac2b844c3acf44352203a5947804369db5ca4da286d75d68d6f2422be879c3877257725ca3a84f81a73e5a81878164c130a1b53ab3e07a3aa990fa30144de232cb800f910443088a9cf5087ec0425b0832b30765179ff971f412c48acc27764aa2970c71ea67c2ac841c0264e80213a495c07cd7b548cff5f87453f314772d0b64523a4ac346e9a7638a9778531cbd0ecb60ebf7c801ca4a42628954b6bbfb9fe749bf421efef45f71b970627fd0832f00159414c0fa210346f2d3845a5bc917325360827db8e375bae6c56f410c79ab3a7fd70f1a0fba61c1a621bd058c16c7118420a037f5ba8d7260f0c897b19b656565d633d350859f5648c8160c63f2ff1b56819d5bc90febcf4eda5c0e7a2ef10149d7a5baa45afa7f7d423f54220f4342625a48a5878718f04888ba5c4826dbdcd4555e8075537bc473dff94aefec723d89d81838f646611219e01df5ea5515f5530b358e16783424811bcca5e5316dfb61a8f67d84106c745b0b170bbb7d42d0197e39d1a6d12114ef37488a0715d063e84e98fad7e342e2d5b3b0843cc1f456b10a9a2644297791076801cad64081674c08925bb49b324eabfe08f75c12f46308e710d3219f6941204b87322f0d3164ed50d01c8238460597a83a713ddfa14a8bb5daa0e983775317d21c24c17cc8da373c96620ff27c31f57978213bc7edaf03112cfb1fb3997129eb31d5708c2a6b605a7d4b109540c96803eb938dba7cea21c5edb5ca7255aa480ff3ae4d1d188366b91b984cde973022509fd9d301368dadd383439e5dc7eea2bb0e9cbb660c7bfc0224403fff18a8daaba7fed38c06919eea938ed5adc281643325d1de87365f00aff9d50c68036a020b8313f0a67a971cbb6350e14f0a8c06aef9954c8efb6b4be2fb4a13d688109098852e02fc55bbcc01baf052648cae44a93cca4fca29168dfea1d06b9c9344b8a374f7c1058344a47001c9ffc08ed3d0156652d9f2882615ae76d8d1377224a84dfb298c83f2cfb6c3cef1f849519c9ecf5e7e52e62ce9c66f85a9400c63fbd02b3d37e13f2c89809e12741be003afc88f995ba60b5477650f63df60bd30ad03921bb65b4c84582d912426a153b537c7d824cec8e1dfbb33b2cf30ccb31ffd013232d3d53c691cc262cc171a3ae9da97cf5fe677433728acdc3294ea79dd250cea4d9aaf2bd51a0010b586dce393017e292cc0459a835fc652d7cd66abb0500d95e3f04eb309f3ca90dfec8c1119c691fff1a04a384ddcfceb74e3162940920b76e64c80cf4ce4a5b3cd449c839b19c2e0068dd8c46fc506e9a905a4a16c3d21665a0916014aca5468390f8ebcbb375601afe0544b194eb97283aaa499733fed3944241086a1d82001691b75fc051a011e1c98651318b48b8d109d821cef3663627986bc49ddb55b93d60fd720a8759f315ef947b541981c32c403ad430e5b9cc87be358fe514d4d9b6ed211c77e72c71c136412728fb037faddf07e84a03a91bb28feb3c44a96604d781e3b8d100b51b651e029d19fb6b5261307ebcf27bdadeeee7c8fc6658785cc1323aa2a7f5d12d639ad71e91332f80ceb3111d8e701a815ef294ce7e2d247052c99e9b64413e61c87e1b7e96198f8a6244b94b124e7b2228ef653dc35b48e15a148664a360d21278907392cec5b37ab60d703ccc71f2016f33eaab3fac531ff92d840230d2a038b5e15c3e482c42b2465ee1e0542fa0dda7d5f5241cf8536fe98c4de80910eaa0bffae1397a1967e7b00f2afcddcea6023357028ec884ed5e5dc38da4d704f6259f3058d4bc2614739cc6230c9f1467c35c8bb4b6421680020c365f619a407f45cc5bc0197069e6991aac28e3f1589bb59fc99ca1b5aca2284b08e441eb576f2b708b64d414b2223e46f6fb55225005395bb1c8ef94079a7df2d26437051e149f6544bd10a66585604ef294c9bf9a783b17117ffc3a05ddd7073b629bda6a582270f3ed12e3ca40a9a83cd1d8148aef840e8fde04a4f2942b5900b7c79dd941f72be5dddea884c8421293cf08e6527400099672e1288e1ab957df1ba3090fe9aebeda5b48ef6bac2a39fbca30b5baa3691d4ec7661bcd2bdf5ec4f2d7cc3018e9e7da8f9571aa2501f9d3d5678091ae413e81bc9368f1193fa95ee437cd27447d944094cbd034a04639f5a304cca9a0e7589ed157bf66477b1ff7608fca9fdfbff9b9b3b641b094267b4eaa879bbbe76e5fe3b015a3336b8f3383f78bf2ab4a968fc14f467b68e18009de8475ceb0a13775033867d5ac9e00a1c4d5a6f5ad2f69652ca9452b009ff09ad095eb8a6db7857f58d46d363fa463fc134fa3b11e9f6771a6f2818488a1dd0eba24361a01fecb7e7fa36c95879d371110431afe5b190ebf9bf96d76a7d656fd916b7ba6a3df72a22e7f8661fc8cb2f2f8bcc402dca7903099ea45a6bad319c9925d11b7ab228d140b9602559a94af294a5861dda3ca1823d7102aa82c902ca87294fbaf8e1066773434f1625fa54cd98ca2689c6d0a40b13830c459e89145190512203021688f0c2e48a270f6aa9dadac31319ea931b5ab0b8169a685f3248d3b2da7452da462ebdd1dff1a79452002061e5652d38a4db6f474f0bee86647a59b8e61d679836600c2ef3f08e993bd2f87570154d0a799e770156f23cb6e23a6b711d851f5b85978b599ed8595428782a5c7724fe3c65b26432b71b8070250ed368f7cc73698f10e4ada265209c0cc442f66c09f6e59d1439ce10eded5aed97ec58a3e1c4d2c8c1810e1a3b3d3e1198d4c795cf0a885151974f5e3c88b9590766f6251ffbb0eddd521c6ebf7d7d1222190c84c4ca6019d3ca72c752daa42802f57dfdaa61336d4e2a72370b4f3e661bdddf5a2b8714b8f2fb8bb96c96534a798f6af795efa4cd6d9fbf688773fab794d2369b062510490c2d24619e2419a1460b12aa27ac16992254f8d61a37355902b01801451528a84070c50a2c2c9cd43047c0b086504a29e5374f6e562062840e55508e65864ea1c2e3bc10224bee8e5fab51fc54a5544b96268cf820c92029cb113b6a94c8c2e58ef6a7c097f7dff368fdabf55feb83c8f79ee9eb61cfe3fb9818c4f5dfc3c4204e41c07f3ddb2ae879dffa59f74734f9336bb63694a306866c035fa2c758d36511efc8fb22f0bda7542c7a7deb0556af138b5a2f4523ffd61bc9f73e5777420326282dedc1d7acfb23db8e5927ee402261040d9ad896e779b226c2082f9cec2281af50be8b6afa2443fbce18305f7ed17c5934df138d48903ff4a7c8f2a74a833276bef4dc9334917a35d83673cfeb231b0f31484bb4df75eb7974deb7bee547363adf45084708e97be883e52a11e80a5fff0f03cd9e6be5fb17c97759c522f99e68f47246e5f3f0ef5c9cd978487146c51dadf73a845d96423590d65961e54f4a933ff3bdb09b73d22ecda17817ec8c490342ad9cc639a409dd751fb61ec4c2c73ef20776598706652cdb8c9c8a5c2edfe57bdf2c72bd271a59978b46523462514eeb123d7fb67963ed4efaae679b0ce57bcfc3df25067105b1cd04ccdf0104a9087786ad188bdf54d11c1c047105f9bec500efe543c0e554e412adfce9209ea844993eaf7eeb8beab7bebfd5bd271a79dfbd518bbd8732eb56e87d51cbf3de251abd510ed9f6a1d8514693b2497fd2bcccd77dcff53e88fff7dcef7a1ec2513fc793f0c3856c5071509f6bad5855378765cd1df9ea7aaba3b48a85ead67fa13bb251f9f38be4cff9522c7a19c97f8947362a8e3fe6a5dffd9ce6b4a6893e4eab5fff93e0d0044ad8d1e7d66ffde873d9fffb22ff4ff4c4a27e17787dfa369da2e0512ace18505f3e8fee5d0ce2dfbd14833815f9771f447efdbc56fdee6794b65cf5bb96ab25a97cb16e6fc640954f9fc7fc2a0611833815d59fe2cc16a47b29ce5adcf179dd34f9131e712ff4c1f6f31d6934ff0efdc9c73e9efc99fd4736125adc815482064e6cce69b2d6dc8124dfd6ddb5c4bec964322677cf75e00456260c8ac359ece6f67f356883fb61b1e0e5af6003d806eb6b89c81f0f2ad8facfbb76d878745be0c7be88be053bf96c03c3991465b656947d2c55ee68afec6ab70377c04422f5c1270212fd9275fd170bd9666d38d47d0d3656e7777e26ffa8fb991477207993fce9de99e48fd2d0ed5e03275609e7bb9cefbe8ff8d304dfb1ad7b9a31a03ef822d087e9fc4e0c49e902490a7db0f2f90279cd5eafef81746fd47ddf3ca8bbdd779307517121184fdd3752f7d475dd94dbf591dbfdeb7beadea978902b7dafee9d18a8fb9d1088fce95ea7fb5619eb504eebfe25fa93d3baeec5275676bf430ae588b3ef63247890fcee7548211c71f6893c40fab00f521f7c1eb08f7d4c0c421ff66ceb5e042964bfebbc8060c915c0652f3454dd0e08fbbdcf3748f4896f47be828560d81a796ebf7c2f949d2bcd1725564daa246c7e58ec8e9c5f65ea459b78cdc91d193765b2891d6560083e4ef31acfe52e3f20dd1935d9c48e3469e5abff4db6e1dd4343b50e5e5696071848135684115a6ca39fc19dfb00fea93fd20ec0dba2080c543f47093b7653d61bb621654c6c0b5944807471eb7718b6c1ced46a6e8de25632b70e89b9d5b3b2da4d4b4c52a889d6818586ba69484b3714c4d8ba4ddf3def251ab9bef546df473ba1c8f5df090d609b953ff54d6800dbbc6f00dbe67fa291914cfed43fb2c91f69b24619cbb6d6b36dfee8cdfc8f6cdd14ce5cdc81d438f953bfa15a8326d6f5f903a9ef79d666ddfa1a38b1557efd7e924250d599a450ebeb7b9314aa9f58df25d67770075238df15ca5752656a75538c27ab81be5e3ff4c176cf77eca66eaadfb5833b54af56ef755d68821df7a1b973d87ec3c73edf87c5c23e2c96079c31e08b604871662b3afa22850f30d8ba975fd4bd148bea7bfd23db2d72d96ab0b1ddd7df81d489550d0e25d95116fa60e9f31d87d8fc00a5852cacb842c35ebab9964fbe9295d16e4bffef0322979ce6f5482e8d4777be6d59f3ac1c7b35b28698a24b1a234b7a58c346bc61b2a68d99124c29520596272d1e2693c89552ca1987bbc1063254a4c8a0851a2c2c34998207a90c0e862c5ba496f0ab2b5c58f71cde04eba362ce39e79c73ce2b5cb430320409a41470b0cd3477ce39e79c5fceda862e1560acc8a003939929407089818a2b4c7460340095a58817485071e5031a299d264652b850a384eba1c817567ee5ca085a4c388922aa34fd80431a2f8e988200586438024a0b16397421c50f3e504a29a594524aa1585862045a72534c0461a3515c4a29a594524a7718c87fecb9fe3d6c835d72210127a2f0d084e588ac2dce5c5121a74991264fcc60254a114c707028de804e1c0cab01a3d3c4b160198105154cbcc062c604ac2d9e3c59a214849b27babc98f8110f63de2031c40b356099f29c95282a5ff28d99c27577e22c2bd084952d597c81c4cb5499194baec0f04407295c38391182167bd381e86047cb36baeb36b7c99e76dda54c227b5a7727ca3a83da878f4c27c1cc2cce21b3bae7dfb1879ed37cd049a778c2bc3e68d72d95c99852ea31f3147fd0eb2fbd88adcf5eadf2877af2a7abacb2ca8e79dacbf3798a9465e8e38e3b578eb992622945963ffd1e333f03b5ccaeb1fce3ebdaf80a37bc44ffb8c1652019069779f8877f8a2f83feb43b0ce442dc7d0acbfc1ecdc65a5ecbcbf2644e29a7b5ded7f758a8eb2c9e353c6be49c9d0b3feae59fdf2985dd009e73ce89355a2cdb3fca50b2fd9ed378ced7654aeb0b92a964fa94a7052b2995799776b13801dff33bdf231a39ed8845e1fb88463d1fbe518c17ffc866413863c0051fe3828f312bb2e07bde82ef7919e18cbecf1fd93efc97f145ff329ebe0c19ef231acde81fd964bcd18c8a2714f93c06df00b621c5780c4423230c1e03d1473cb27dcfb3ade747598fc8b618e205e1d10c7370feebd705e1914d0cd916866cfb906d3242b659101ed9186cbd53fda8b7257ad50a9647f430a33f4979d0d4070b9de67f3aaf0e5d5716b2edeb20e779fe6370e771be43fb3ce128de1d91e773489c30271c7d884e8b85f07d7ff0bd8f219e7fe1c8253b8ff339e17874731e0724caa1f43de0707c9e709c97e777c2b1efced3b0861536d49138bf131e5d1d71004d03e76738d6b8389f43e684e0e384ae8785e38c1b7b1b8eaf1b13619f43c2c2fa603895427f721a94d3e4c34022973897785fbdf9e5ca3975a5c089ebc18e8ee39c8e30899daf249bf9a79b6f87732e71c7c578f92ee5423df4e53b94074d318718e29c923f522a9c54f207e7342a6a74772d58bee3c44d9c377138a93495a6d2954e257f80b03f451c8e891b1af2260a12e5e0c08d4dc0e4dcac6f119594586105218c501652104e80bb630dcb1ee9788c283cb4765ecbf5bd4058cce2f4effc8e38e67c8bb30f76bebfc551e777de493af9f3849e6f30e7efe88438a1b58285af304aabdfebafcd3e13c76791efabeeeeeef4ba4f2969f2a7264a29a54bd9b49aacd297a27bd3d04567f70ef61da197ced9ad3a67ab9bd22a25129a90e70c0116e28030420abcbe0cdc7e497fb69c68269a2128173e5f57cb342af2ec98465ff67c815becff4cc1f6bc4c26853eb0e67ac19e6d36146301b874000bb832a7c509dcaf7330609b81a890d9c47b4911dc623f5a69cdf39a44cb3a5969ad34031fecce652d50b840ebc1e6bc01c01bdb61411bc27a6aac99322e18f9e23f1c8687913101a83273c41d2d2dcb0cee8e5ffbe1e3037c9cc3ce7f2a9b0a4ead31235bccdcb6dd563a2a4f539a62f676bbfda84f4d5198a0c0708e48dd8003e788940d51a02cb5129813c58b121f43bdd44a9dd4482f9daa23a0a8d1498dd437201f8f14313bd478a48891d2b7ac36fc3e5c14d8d086871914d8d026046daeac466859e5c614793e63a8f8757850ed3c2a291cd5c74b121c6ef776766f3b291c5dcd832a0d8018294bf89005408c143143b5193e9300307c9962070402c0f0458a199271b47bdbc94f4eca62a488d9e1766f67f7b6eb6a53ef19a45e776f6d37e7f5f0a86b9da68f7db22e8da56d9b27853c2b73a197e7341b7f87b2bbbbbbbb650f0ab6866c9320d10efa9ff77ab9d0ab461bdb59f7b9b13c293d2576ac5df953865eada291101b156fd5c9f45c36bda1e9796fbd9a1372a6dbed96c456eb53857aa2893b565c95fa1feb541d13e5e54d1e99e18e526949ce8f7249e74719e5098a0cd51d254e4aedfc28a7787e9461aebfcc492ba43b4a2cd966821f6516053f4e24a52527aedc7132cd28b21f67138c1f2795eb3f71e39c7222eb8e936a86a9e0c75935fb719eb9fe136b66e59ac0b92345ba1ee329136d9272474aa95cff9142dd1fa9d4f5a754b4eaca1d698e9eb9fea61e79a2883b56a5bae4ba6749284d5f64957c73640a8f73ccb4a24bd40aa557d76d236d731dcc94ebd31d0c133ef5a58601c3d379bcc40d2b9705d21554a67021e608054d535c3db504973b36d51d3bccf5aeea31d7cf4061024a0c848dbe347a13099cd8b9a353b9ee6ee5ba4b5d7f1e9dcaddcb1d3de767ae3f7390bdff3240a2ef8f80673968e4d1555421c2e72ff831079f0b442136a71e3ec4fff7219d942871bbe27ff8168044e1874cc59521e3638044322cf81e90c882d78db17363fc8cf19e991be36d0eeac628c3c28df133902886ec08a5dbf31580443db323eeec618044b30a5e06125500c3092617c6530012c190c97e029048e67329c85d0a9e0724a26082097e07249ac0f29009c1e5791d908867c7b6f33920d18ecef398aaabf33820918e276fce5b9028872685867098c04102078704575c1c0c2e0ece5b2717a704555c9c31572ecec740229c21da0fe530c25a31d61ac0e2b8638d6b9f77b8b600d7dab74c9cb9f66555415cfb9fe4daa73581c4b55f23810dd73e0c24b243ae1b7b10248ac196007361ff02896042e07f2011f8aabae3ce7d8577ecb92fa0fb7abdfd725f4b44b92f3134dcd7bb40a2d77fef72595d97eb3d90c8555b60baadef40a2d6f5c254b99e19cf76168009e256f96092eebcb9dba25b90c30fd5a65ad65e4d8937b6bead696ee5b9b5d65a2bce6b47833bf630a18416258a48820a1f3276f4e6eb460d246a88a105092c544b77fed3a8c0dcf9b5249ceefc184834876aad3a2e14e941892a374954f02d7aa0c0823164a6ee38e34b0e253022052398b418f1e68826ee7c1848346bad74bbdd54a0c2050c324dbc6a2c819bcabaf3655ec4dcf94f4408ee7c5a8d0821eefca122a898829a536d9ca25089c2218b93275c8b2c3cd82f83a924b7be8bca13b0560008d0c5aa4b955bdf135d40a2c9480d527ed022eb4a198f893beef4383144d4ad5f65530529983441040b524800e5a6cb08902845820b9b5b7f7a7fc4a44d15140d48a84052c536b3e8d283102f0baeb0a34cc6369ccb9958106768b398d99c71a700ee9c5235dc2995c59d5c906cc982021d3c18920b5f0a3930890bae214a2fa6262da4a7ef07275e0a2e235ee850a86a458d515289e0bca8354be810ac6ecb902693470a750b0c2f48f6a6c7a30813e52d4d30f01882dba225061e448e6c19f38407529519a8cfc1a2659dd75a31e8d245959f2bb82ea6a46c8ccbb82e8ec840073bbb8ceb220d00907851e40d086a489aea1839cfb16f31096e7b9e4be9628fcbbcad3c07d5583efd503220acec89359a459250e188cd68e785b89b9bcd29f66ddb098500e0c8cde624e5b374cfe8c8858923900b7b0fc7791ba55190204284e0ae7f8e7b61a10907f8e28bac5166dd9ccff910bc98788209fdc585b12d278cbd5b715ae3a0947a1022013cc536e86d98d75f6e5aa8609edc51d4223814b19c7f82ddd35cd88fbe060ad49b9cd0b1c00e625c173dc74057566718c8a75873fb5d7a3206732c35b718cc83c2cd46b080d3ac744bddfe5c4bf9f270d655fe04d368878281fa5b0ffb58ce83d03f9c56c28f9d43a32302c9f27ba38f348d2bb6e1a9e11bb06ffeeee839b631bbfd7e866dc8b7dfef51f08dd8f7fb146c23e763b19b63a517f6d591b173476e57e574ce2879100bf510b7bebaa2e2b97ed3d2c9f11c15da9de1343f83c51dfdeaf663d52072793be8cb9de3a07339e618dbb9b66a346c038b6db4d886ebfb3b8b6d7cdfdf6fd8c6ebfbfde6485af08d9ed234fa463f358dcef9b1dddcfec6826fe4bcce8fdd05dfd0f96ef2254953b8b86cd886f7bf5ad7437965d867ae988172574daca3601a7cf93be92a754e071ba3ba7df908a5db37a96f3495297da3ab9a46f78d1ed3b2a6536e3ff912032931902f5172fb1d89c770fb1d06a6d1af139e00fb1c49a56f3813a6d1ef4e6e3f0e95db6fc39ea2a3601afdb0b0d5308d7e306c2ca75d856dc540cd40fdadb0730cd41f463dd891b360583916a8aec8dd907076ec1bece948d2153bf6111eaa27dc0eadd44b5cb0632bc16e4f550cc19406ca12942476ec250a8ce45098d24c1de5891d9bc93e15914295a62939d8b19b5e4c8ec3228527a6272a4eecd84fb72e5c18e9e2e607282b33d8b1a126c09acaa2089b348d6ba99c1c1555509921d253fd85891d7baa02aa21724cc034558769c18e4da581522e4d19aa9c9baa3141d8b1ab5e4f5db6c0ea92c552e7fa8c153b76ce454595a686a099424a6275f5821ddbeaaabb4db539c3e436e50688a936423456b779b26363d1dc1553b92bca7456bf01811d3b6b3e2d2d21c40dc9c98e7ecb80ea48921f89620a8c4b1451a2b8442186cb992ca52558ece84a4f309eb8c0d0c40506325c9450e1b2e4c51214253bfa12ceed892c24a6284aece84c2da521d24861d5e44e831d5d09b7c50d6bb29ea8f8d304538986a7efc6a40a282b382911d8d1713bb928235f0c29e253fe658a1d7d6a4c054a5262c24889b9024b4a892b29313938958711c28e4e0584a3f2059557f918afaa54b919d8e0925879cecf006147cf71172afa06046ee5576e055261ad5003c814b502ceb1bc0d0f7674ac3617e4864073c5d4c2a73484153c0c8126cab3fc4d0d76f4ac2532aa2e5a246175e982885c973650ba643d7143a2624779a34f4833507961c591a4263bca233838a9b42489ebb66407a5255176944a4762514837798b2262064b0b17555143965882e2831de5d21230b70549d29314a62d48ae6c51ca3145b9c18e92a975cb59110595155c344d81623f6ff965eaf64b2a0f9ae0f6cb2c59180f629c1a9c3c63e5a3c4926fa6740975fb5b090aa766c9f5ef97563c68e6a27c725a3fcf95f2692a197971b5aa8c81fc9bb3f5c29b5b94239ae4a465a85879b57a6d6a509222a5345de1aca865802f4a3cd95ae2914d4a59ef63205bc50ebe1448d0643372bd908ea266cae624ffc8f6b54257c82091d7d55951eb67452d57382b3ab2e590267487d41582ecb68701d9ed6f2155b7fb1a8edd74ab27a56ced2ef4414122da6275797786de6dc9cec12e1fb3003e9642d236a5b777f05506f244727a908b3d7ec82bc48fdc35f0f5f821af8b33297ee0cf01797bf077df78a2f365f28786423a2b21b52648a86c4eb3fea3dbe2cc028d1442e34d53968d7d838f4e1ab29a2b2c4b36dad5f7c49b90fad5c8a9fbfa4642e8cfefaa2864be73d9a0ff6397019070b619b2ed897ddedddd3d0f62a64dc76eda83d28e763de83b9deeeed5ddeb8feeeeb5dbdddd6b37adb46b57698fdad5aed2b047f557ed78ba5e7ab35dca1e3de99c41d26ff4ed1eece8dde9cd3b6ba55cc1c06030180c06832989599c9c17ceeb735e39e0386f4c84812f58ef4cd17df7bb9444b97f9dda703d832074627f77fb9c507ff495df0ce57fde4f7faff6140c21f43c1cf57eca64d68e5eec5d9c9e373de8aff733304862bbefd75b80840bb08dee2995219359ae79907d9ca73fe441f43f1bbe9c063e4e689d063e831f607071c4e934f073b8f5faee2c4fe81b0b4fe80b0bc7d605c597d3acd36efdc4f979a2e7c9efeebb47cb1934a5777b3f767777777beeb3d66e6f47a1e594b8fbf1abc3d677e151ec82633803c17bdcd6bbf75edf0aa86cdf762f1cbd1edefb4f30047f31508ebed19752cfeb72f878c4583b4b782fb95852e537db70792f5d9fc33de48077fd7d4e087ff4edff5e14a42cf4f2a752faccccf45be2d8ba14e7524f1c615cda458a2fb782cb5da4e0e17eee7d47df7bf672ec6875debf14caf90a03ff25be2803ebf7f55ba0582ba0a28e1d2dd11347d98591b3d369948ab30edcbfe6bece6377fed49f320dc77eefe70c9a72fa8d13fab2e7795ef764af2b90c1f673b7cbe512e56d47a1a707fb150463f9c71d7da36f6dbd8be3bc1ce8eb89b30fe4175fb03b56cb6d12174873ce39e79c5ab6d8c2ca953f63c8c7837a9aaefcd7e8197dec3352a52b9fbfbf7850470025b6fb640d17727d5f1084f92ed1fb1ed6a0025ce2d823a3ff8130c348f90a7388bd2bf6333441c8ed76f6d2fa1efc75fd9c52a34b9cb99e8a2f97e89910bbde832fa9c658c936b7a4a5284f56a6c2e4ae661645f22047727ac9a90a8220deefbdbe1fb712ff97962ddadcd77f200852ca693ba29c0af9b6402b85fdc04ff080afeb3d68bf2d90081499698cdffbbf9e411082dcefa7260882df4f94524ee340dfeebdb0ef1667ae7f072a20769981bc10e4944c8281644b34a1e594d32437a5b0f9359461c620774e5152814ad851e2a86efb3355f29c2633b3bff4319fd2e94d3e376faeebe75f5da0cb5adc58b99fbf725caed073c8913c612e51feb844ef1369e174212427ef918abceb8bd06c4eafa7dff77db1b0ef9c733a61c71ebedf2be4eb66ca35c0652d592c5ddc652f97b56425dd9d1e0f8dedfe73c2ca6f70becbe572b93e27ece77a97cbe5daf120970873c55c2e97cbe582c580b03d4ef3d6cb157af7f57a851cf0ae0b043f19c85fac000c247362b1e9e5844e385370fea3af1fb621f6e070c99eb0d8b740d87f1ee8c1843d831dc4fe85a3033bf060de97f862a059072fa7bdc41eb18379633171c86931256ceb7b7a767660b158ec5fb1900023b0b1afd669fd31716708471c3d02046169072fd08a218c7d77c2eef6dc0edc715ae3709a1c62200240d94f1c77ae164f974b70594b9698fbb95e5ec89f1376945d097ecb55410d33f687bc2e79bf594c3c0184f9c1f7531c5dff89de77f70b47ef8b8923901b73b99e9dc6379675edc73e87bb4ce8ae15c7be31317489a06520091359bccc409d43ce5bb6f1bd74fd50d390eff964ff999f681968fe50dfc0611af3a5ce7d39cd479f11e7ce2b5f3db7e70fdba8f7831f06ea8fb90e13669ecccccc93999927f374f38db5878164723e9db3328737586571e48e3b35c6da8ed3e7015f134c9897190887813c7e859681b2eed853c1072c6bc1c2ea71d846d8d7f320b681e463af49e1103dae1d43b519bad25fe08338682fb3d5602199ac7910ecfbb99b89ed86751048170693fd912de719023c3fc11751f032d1c8a973c471e882df6211cf4f70646b71ac5d1004c57e863a73c10745761a4ccce1b19c3f22b23018ec3de070fc11c679753e271cfbe63c2c0473421c9d30f63938e2009a46ecc170ac71639f43dae9b41a56d8873d188e5c55e3824d60e875e883abaeace5b1abbe17569f41b33b12c7bd2b7dc4e290695d66c22acc6526aca42e1017bccc84150cb73bf0b1cf38b9fb56add5937df7934da92696bdebce0f5609c3ef41f6c5b2d0c3f88ffc0e3f9c170e48543f4cad46cee34c1c1c1c9c9fa109325e5447fe13159ec1a23f99d00e25f54bec3895c239c36e9eb7d3dab73c9fc33bb43f799e41229ecfe155f729c0c1365cc8a9e48f95424426f87f22305e06052f3e910a3e94fd054f64f616e4b8209cd1bfe0637c18ce76c470b623ce18207ecf1789013a5970d04982c5883539580069795d03a1b33618207ce5e5ba216e799db367bd4e66ab4e0224104d53c63ccd97d5837c24b7bc4ec7649a2c6786b1afee641b55555521034d1a65ecdc9de241f25fb46f592daf6ba6dab7acac2c20f2a78d1dffceffff7faf8a40fbaffaaaafd2d18940fbaaaffaaa87c23e791015176a1f206118ef2f9e959595e5b4fe2e060f1f5fe0052b6ca6d8e4961cf12d2ee042ae25edeb738d08a49c4fbb70a4728a3d66276957b1c7a44dbb6743f1e4e30bb880c4e6089b26595417545703de352176fb93bc7c7c811a97a358f8d242d3e5a8a42fb788cb5149b7fb3eb42f36ad6efda82bb26e157ff8dd80ec8e406eb7dbfd21bbfd6065a6cdaf5efd49c51ed7ebc6fa7356b1077d797bd4dfd9a9228e2345a46d486773d2c0bcfd7ec3dd6eee21d9d1ab4924a8e4ecd9fde524e9a0ed124bf0dff153af43fee8e4ecd84c5f241353333593753925ede7fcdc71e24d5a86d61c34b5eb81a5843cffe9762967cff939efe998f9d19ef7d211823733f1d43d5826f7a1c5c4403558a6afa1953f4dd5a3c4ec266e1eda9f81b837171b05a621bf134b903fb2c5212530501776ec269ab73463a96fcc97efd1a858fbda6b799d7b1e9691b1ba894f5337e91b93a984eeaef94f0fe508c5a6d15f4397459cb9ea2a69951ba16fd06a7fb214ea3c76b968dcf2baae25d6e48f7ccf7a9d4ca4c91f1d9c1dbba99b9a264e0998cee96a9da41c1e4c24aefc190e154046bb4193d17288d8916941e3ebe6c0b96c2c175a2707cf6b87d2491e340a09b18d66a0a6c91c363de5ec9e3b4ca64fcf0e5fdd4973da1c3a0077a131d0147f6402b8d2935db1e3506d2866c3c7e3bcfde1153bf298dbcccece8cc3c809cb357a54b02c8e3db7ad1447a32b9f8f26f3d16c1ad51ca5c29b2815982e6d1ba54c9f7ed75adbf5dc6ac960f00f7d119cd632cc62e97fb114ecd838cfb356267b991403d13fe3340f8d8dd1c48eb599fc7ae79dc9e725a2d8e2f212516471c7ce758e568a8bb1a455633e076c2376e988c325ca1888fe055d58cf72e541b866c25fd84cf887829d856d34e8f4c2e152974b1c5b864b41d90a3d90a8d5b53a113665297d0ba0b0f5ebe5a823315c7939eac8d2a5f5e7bf6a388d3e1dc01256c7e5c0698dd5420363ce29ddddc5277777779fb25f3bd7ffebe934af041edb609e724e1c9df8441bca5db5acd166481bee58c39d597ef9d8a756ab31820f1c595f86a8aafacd15bed16a9886cc72c75a96cb625fc1a8902de6d0c6835cf4b1e2683ef619fdd603a8358dda320b856dbd477f874cfe8823b05feb86be7f3d0fd7776290ee5dff1283381575ef1267fe3571e6570c54bffa19db594eab5f3f0463a5486b2c97c8a28cdb1d964315bd5a9349211db52a73d4fc26abbecadf1c634ba0973e093e74882085be2394f21f61f9c187bdeb3f4f6cbd80c81fea33f931ab99c46ab562e24bd4d1a2af1cf2879e2063a06efdeb257d5848935d38434ffecc8ac07f3d28b2fc697f7deb5f624b647feec0ed102b4b9039a4d0f7f475009142afa7fc632dacc91ffa3487d653f6a4508d7f78aa255af9c37e3b246ef669320b8ad8d90e24efbbc7b97d5da3cc2fff8de5cbcabef043990cf6fbd7fbcf9d1d2cdfef5fe26cc7f7b306b0ed13dbf6fa198fb6bdbe6d2ff1e58f8c81eac7b6589907dfb7dee8b50309c9f52fa4d67fe26cfec7b8d93462f9e3fad6cf6fb5fe138d5aa251ad32185aacf572d487affe67a590b4d51ffd368d2a59c643cfb2ca0069eb442b9fb0f4bfdb1bcd910d699e68b24203153a8c913514b1446e49c9932f5524c9a4a8335777e7725411365a2e3ffd915d0be36e0ffee9ce74ce661c53dc01986760fe5cccdc62af19a873af9479824c93eedea07706c6175632b851410635b43332e490666882f1c577b2d25aa3ae9248b232bb237380124c0188ba228204575277b4566677c4cb512c48b16f03922dc858d855065b2c5826032d1654a2089b7339aa0a2e071184a91285829cac65b437510c9171396a851ab620411249282811c3840b2637de9021b5d65a398082439aabdb11596b240d4542c026290d45e342eb7294162c53522debbcd25a690650589ccb5157b8d09454f941b2f672d4151c9eb8f2441645a4086965aa0c915a6bad33cc550d449e5a628604c1c290145ddc91712c1525451452e48cd4bce63f4e4fac60c4c2de6470439b0c6ef6438341d513187861e9e5a8dc981f2ad6bb1c950bf3349b0c5551556e789f28235f5c7a390a8ad51dbfd62447dfa33e6ad77a33dcf16b90e18e4338b424943b32d55208eec863ae3c73c44b142fa4a2aa9031442d3de9d45c5145d2940a498419e248ca1b23382c490551440b36f93aa4900796a678818d1a1e8c28d964eeda1ddae5282757dc9ccb514e7277b44f83068c90220d0f52acf04413f904eadbc242adb5562b48da90a0852e7e6823612002ea85c90313921e28a53074435c86247838628635e4b9bc4f9496a66b2f476959bae3d73a2f7ecc60f065c99f389a21c907334850b88105304b6868152d545beb981699289076d6b82e471589e2c2b81c55e48b10b59ad76ad349e9a44961e5e5a8225eee68a558bf1c55a4cb944e8660353de09c308249912f2cc8265145b87407966680c10b93149535b65a8d2a92e58e5fab8146abd59418695fab81a10245052a0c946432c40a0a2570814c0a6b9a904b34704314370481c20878b805e15794c8d8e18b2263b07893c3937f2899e48eb21741d21434ec60868724ff503cd29ccd1a2b68b24082839632b51a184fa3064a22e112e4b0830b6c567022428bff20388cfb5b28773327542a31c0a188353621ae289bd3f48cdc0808bcb06213a2010b36a749a9bf6ad83c30b9a264933571c6a37e97664c931b6c426057b6fa9d68d44e048182954d488e0bb62ad27c83fe23db375f7456548d3c230aacac10c2260414c25637e8d0680055aa18926513e2512bc860abf906fd95f61824b26461a37da553d4154bf787bc19cc78d059d39f4f43ebdede3e1b0528eedeee2e9f711fdb867090f083032a574a29a594b286d736f76e8b3ed3e50d1ffbcc781835154fb6fa4e10e89ebe37c666f48db159f913847d79b98b104977ac5d2995dc18f4c18eb22b7f2e40bb1c756b73a36e672e47ddc2005d8ebae1703f64a11ad45208bac45045893636f90d82d04d10c672d44d863bdfdd8bf8e4532674b87df2d541635df1847e138e6ef747dfe64aafb790759b1d69b1eb1f631b5dc17510013c639c2fbf7f8ade112ebfdab5d2ee64d470afd49bb8e8b47d8bcfbf63a07ec97dfb7b6ebf8e22179281a5c50b2b35866ef317e93c76fdafdee9d806ffcdc969934f5934b998b2d8c273045600545d5c58b20605ef65e50e76b4ddc213160d48724aacc87a7ab09e10b164c8fb2cb1e3d36684c08eb44ea6596a951953ab21c58eb5dbbca209d7550b433898ec3834a9a8b49082d3224b0e0e60b023e3a67094bc48c17991e28497262b2f529ae84840163bf2540c8b6a4892253b8a6cb0235329cd2b8e7c57ec204204b6d891ab3e2c2cb0a4a061040b70ce958b62418a112324f880b2235b794ab92ad250596151c28f2a7664ac1615152b50210390a114a884ccf695412c95b2810000001000a315002020100a87842291480ee4d1b87c14800b759852785295caa34912e428884106194000218400008c31c42034662400841b56c86f71fa51417e37e4a71efd4006fa43a135d3dd9925766406335d98e1f3e42b414e52679ac03f9b8fadb2dee55c91f30e1778182846c5412f0ffa798402a4f097292e3f54c25f9bf50c5a21bfbc262d9f838ccf13512bf4298100996c1df0280bc341994c870ab3476634a005339c3ac35044545f0142aaafcb7d202390cf0e4619ad6342aee4896ff35f326500502460c97e021613232e30b7204cbe25e8d5798ee4187c6478ed99443a591507d17ee3f4044db28af9c22619afd3a4239f17ec93c277f28adcd5b12617c01685ea8c298b4fa3d65432d88bc90533c25157b51de23d6e2949123f0c450d880224a9cbfed72488d92710e8b226ed4321e6827097a4e04bc62e2e0a1d64c3da301fbb474a3afd9bc9cdbfe5fe9b20f199d9f0a907e4e9c9b7781d3914c58569fa7036dea1014008d1ca956e1b0e3c39d5e9635d5bedb8c92cc67d04a3ffd72f4696a91e01014c7d70bb6622a11365cd44828e603513073a02a899d4b7a23e85e5a2bda008ca259ce31112c100d47009363ef61b191bc7c2f60784631d254fd276a92219cdcb8de3125580c78bf433350ff14ba7523a0f1c12370fb631cd1d121e115a9be519f7dd5c922f02fa60dc1de55cb7e4790180facc5afa2332de183dce8bee354b21b45c62d69236ef2117f24b4405fee26f65e12f22c25bca9ece60c8e7d7dee37f2c11ec111aec2cd05792923b459d8e705ea15603cdf780e8559bc0a233c572a11c44f6df3d4f933733e9aca49e091339e428d9152f693fdc3274887c1ed38afac03b8162d70c32bcd8c4cd123276b829764c4c55610929aa9823c6b90c8ce21238edeb5e85faea737c4667331cfe8021ce1b669c6a789678861770b2a3962896746e7d7c7e501f18f50377e6323d27fadba284e56d9c46440ddd99b80e0fb7f5790c79193e6991c8db54f059fb01842936962dba14f3a73b0b4f62b1ec615d9d9c4d6d16f8b58f5f86305e517f4a1e0f6f61257baaffdd9b5ce6c01a6f85ea0dc58fa0ea13706ffa74c43727a1352e52f47229761169b983a3e23a562d7721af8002cbb03f866f0426f1e1801f62f99bb2933ba87670e11faf1e3b5903e40da79c3c87b1f4f819f9e01842de0b853f88576255d8a9ed0af895fd66348f2a07b3a5088497b9551c71de3680ae0322c1aa22931084f63b6492df2705fa031976583f01e568ece8af6c4d79ce5f5c59859e18135340d925f33139bc3a3731fb57177ebc4469508db24e289f37635f6008889323ab619f542c1f4b14be1d67b08fba2b576748c32e764558742cc1c40d583fd07b256ce20f28c3484c020155d8c8e2adb1110e8c9e3a4df7c2b33e58a9a7e91b64ea71afa9de5c7a1477e44288d3969f0dc465e5303d132924e1a10225790e101df415554b9efb7343201ce30b3b7ce9b45930a0b5ea74e296274da48b24628f4c94f004e06a5c089d88ff510c7d32d020ce5749f9dd54b6144708df323539aca15de33859670536ada3ee520a7b26f779c21fd5ae9b08c9298dc99eaebf440c4ff7e789ae30a8d221a2818c72bc07627527953e2e6e5037b9e2df569de38a95a5297d9c4db126b4c17b7054d2185549476bc3a402612353abe2e2d53945376a5cbd34cea40eb305f028f655648a4db4fefda2b4b67bf9d85739d13963ec899175688d8a2d0c33cc15831e10040f02fd3d316878e79a528b78a0c6435da6c25ce340d11c9fbc71456e2794791c94c214d9052118ed20add31cade11088cf3a407de4bbe8102d7d28132a9bec53c4881bd99f47398332c0cd517ecf456fcc1179316f452c7e6c3249c4a0165eba2ba422eae611a5465b3e8a3faf6b35cc5c1a216b649e4bdb8afd3077940ac2635f7eb166dc10edb075d5e1735021fdffd40b51fd28ac28cd742b756b4db53133ec632cb56f5942347191cd7e6302f14fcaa6645482478598055d683c69c1a592c3f6ca1666aeba3d043e40afd031d11c79084d629c932679ad7f8e465826f199954919508baa410c204cca2a60446307875713040b242b6a223352e6452fc5b9374f90ddc0684fc98add717c36c9fa0111575e43c2514e535fab39d6392017714691276deab6621e547308212a62ac848a8d78dae616383eeb0f4326fc1ef4b460d6060eea79b96318c8ea79513259e56c95160e62cec5ac56e93331a81a1639eb4b8a6a687f4c1db40cd5850b11a45db9b946fbfd284085c10500c37e26354723ce43e00d335e227acacae44a6b3539f363cd35fcc772d8726ba3f44ba304f0c2f9722b894eecc9c2da2496672c8770a2ca56c63764ca5738923b8b37d5c42a0251e6b66d6c7d9abe487c1e7231dabedab3215adff85e1fc7f6513aed6eb14bcb70c19aaf24ad82d413e364b531ea42f54cc7858d969f07116be9bb2e0300c32915da84f61a6b1298c5472abd8e95228a1c660a62563268545b54ecd57ae16fc10aeb2c9eb4855e941ec079276aeb134b1c76d03d8f5245b8379f22e82b500939b4bc17639f547948145660c96af5ce8a391dc34fcd46d3fa2b3ea23c6745f799f3c4d42e8952de67a925d8f1189692d24d0f8049f239026bda8b68ce7d62a5c0ccf09ca400f349a7631529ee4e26e7332fc4ca9f623c486b32b20edd172fccb7354c6d032b3b9ecf5a5cb960c5cfc7c12ec65bbbed92983280356d989cbb4ebecd6569020aea7e20352b3bb2fb69aee8a6a51df0d067ce3790361571e73820820437ea77f28791374a8904907a70ad84b30595491009b21887301019d8cde5688095ca3b56317d39894ddf196e1128c5629707ee3949ccd528ef7c4e7a0754c231dbac62e0577390cbd9311f60ec3cb8e96c3d4f60a1b4019771bdde4bd4a7e4b780071829878b16304288c69aebc0d34444f6ad1a07dc9f2a3083f214f54b53511246c4134be6b180219d9fda0be16a1c4d1ca6671df8f1d72fc22fa607704f52c9c810cbce163c7e8bc8843a770b96c958ae5387cb6df7f36b210fbb3459225f06b83e6c7bc8756f9403ba56ccc47c59043d04d3b750a516e60f56928e3860504956004554f6b7a0affb84a0b536980414e880133edd9fbea08010fd5c0918a1ead00e6810c51030e0d6e17c14d3ef862857e0dd2c503b7db37c234497773a5483ec6d7d9fb7beb43b7cf25b7ac86ef370a2c42ce03b60848e6f18930b6f9fe19961a6450ac39d9a55de5cb6943fe226ca981a68d77a989aa17ab483c4dd957bd2ca80f6e5f9f61c0f5a5f5967b02d7f6161d0e58a7a2176bc64f8bbb0ff17797b7a1796d40b092b78d907b9970055e4537e2a6e3713772e07a58a7a326cd600c2add81aba8dc9f1466474ee7e126fe0e021300c97162ca537083d4c88bae6313e61c46bd7ea07e456504707cd1165880b636213703c068645cf205d855307ced1c81043a691c2351e6e571d93375ecebf127be3a3cb2c2094cb8a66d47dfdf26071603385f8661f65d099ddd7c22648f6c5e861de69184ae19ca63ee7a1d68607f56aceb9e369b12e7b1ed37261d9dc1361910e633806d77a8ea7a067d6cc101f39a2503ef46247f505726e242c3d2103140f401986032a030bfdb7ce5a6b1591dbf6381ea31e88dc47b03634fb1be460c0d9d7d91d7965e67c9e70a5c833de5d52cf12f64e94ce265efcac479f97809ff9ba13cdb4d7094635a1a63fa04eba1b1b1d3b71b3f2c2b2716cb92b0aa7922872de90d4be1bcf06ea21cda6653e7f2df5f3aa0b33ebead26ab7618e26cea3040d8b83406460f79390839cf83dcb63d26280333194036c26b5716aa10542667907dbfe5d3f1197b4aa0c4e85596d297f8527defa46280de22946203a49a2b48b363850ff260061072a49e42a7aec7b10d5b4e672716cfee4ad125bfc9741eaeffb3380c34cf02faf2be702901256a5ed80e4f684b87ffd87d962c46bcf7bc11b667d728da01e4f5f17adafc0e7e61baf43e3636b695266a2433559bcf5d7698fc913c59b518aeb4b6c66e8e8c37d30ef931212733329cdbfea178dd2804411f2b6591fffd5144a787f4b3cdb8cfd083257d81bdc4edac8fe5c2844f8228b6cc45da57d72b8fb0f62f30e42bda5dd50fa2e834090f509caf7e35bc5a4d43ba829916776256218d8d43d1094fc580796356c45ef880bdd476bbe9e933150892ef8a81b897fa46de126617d52c23c9d9d0aad823cee0e4f2bbbc0758b2ab0ce0d4d343a89d4779083544521a3b80480319c6f7d9587e08064381416a74e492478bcb0830a90598b90a2e3c115ded0db6fb04a438da79ca254c54bd77558ce2612800a7db2cfe7917076b8df4cfa1cf1faf20a668dde5d128fdca12374ec36fcbb1f77f7f1c9ee2bf12a744b6b53137edcaa9b776ea94f34c1e4cbcdf2bf02092e0a691e2883f16c852b119eb012640e7cb697426b133703df66e6615fc8753cecf57e28ac8ae88a6bf800b5da25343b89b6f868393bf0c79ce2719fb825b9568569be30bc3d5806aaab6a19f2d056857d275d563211d0c11d40d06f65d7a260cfcbafea4a6bcfd422c60ec284abb50a98465537bb4a7c83322aa591b6429b3a6698913153b799a0bf09cbefc7714b60b7ac6d3e20f984bec3620f53ae44153540daa67034fc85b09aff1f29cc015478da5559651122ba8501a67ea0ec02db71dd865bb49b381374fb84a23e44644badb7f17a6c4f606f6befe6b6f89a0510a10677bbb4d7e9e165010e3646b67beb85eb102a628ba121075705e536eaa2d6fe55bf0fbcc4250cf007ea711cfe59e5b6720850484d2890d591e5d12de986e75afceb10620b96f6ac0e27c8ca34fe4ca17b1a0852b392455912f45817f39cf02a968331031c823ed8e022c0d06789c61eda6832e9c192ce590b5ddc885d74fb570828880086f5ba29124dc73c9e1f18f8cb83a1eecc8d51ffbb0bbfbf4e671ea104004c4a97b1aaa346a87e02fe9373371ee006d5a4d4012c166c0e3f40a72c77a96bdde65caa49d3f084aed6f65c532abf408ae08cf3b513491a7e09140d7355c89410f16444cd855626314a3bdd0af929772252f17672207cc8239ddc75418d3aeb0e598d4263cd3eb0c4122b866b042d4fdd6cd32b200b0ab1e8fe08f5ca4dbc302807c2cdd494b91dea769564b1aa9d4d83529d1d786f471db47f9dbf812c3843e433611dcd4b07391db4d6e4db7ecff3cfc0c1ade2f451be34cf7d00d86a482614d1b015dc99437ab043756346f361466ffe977d8c6a4bedf4a37414cf7f8814a98a06a4d11ef139081432eb85e856f033ec62addfa2f91c683f52f6bc17108662b6cf4a899fcbc4e840b858fe3127d9912e4ba84352f88c8d34b4338473dc94c206deef759957c90157d712a0e534a485a8ea755d2acd8ff22b5ec754c21487e5d96551e275b10e20338bb0fc243cdf6511a04d66dbcd9a0c59b1657c9a924c78be832b1242a65c02f041293079ab2c598aeb600211b909fbe694ceb1a91faac3f8037dc370d8af630e4637d8644b517309f9074aba1c8edbbfb857cc617e972f10c672ecac8da1ccbc7c680d4d8e38dc6f83865446736e90b4b6540e1145a7e7d4f201833df2c22317721320a43f922a629b197460b21bac26470e3012cdbc5d384c5bef5209805648a41a4c5126e3c7129dbd1d48718bb316f71012f504fe1687cbffa8358f0667e528e578a0463ab8839ba62bfd36e8216d8d9fdf301723527c20f87cfa612652e6f657fc92d57f98359e733d481e8f1cb9ad14cdf7f725920572c678782d9271c8733aa1629a9c9dea4012e5b0e8117f1bfd81c74324a28d92661c9376196bb0ee04e3883889fd99a18a4131d189d9a0ca03e73d0d6c167d53c7f58db13aca35a323c572473bd03de31c1d69aa4337b8a794cf5ea962fd49c035137dc600bdeb23f5fbb080f30e7bdb3c5de8a24954aa534d0ea27fc20270eede74fc4d270781c39119a859af32ccd19c9ef0875032448bc93808db1c4304694485f53b4dc507b52879a027adf3476269a8434272bd12220505682fbe978702073925255462b3a5cb14d90742cc29592266dcbbf28dd6c3571358bc6428fa28993e333189c60b9821cc89f813ca58b0c6a3da74fcf0387f888f1943a1e3306ee4854577b25500dd28a84ac5fd1026f76933435ece7f38bccb0ef16c744284abf84f3a24b9f03d81a3c09449f6e2e7f684ae73749e08bf4992cfc9d7e4e95f3045ee15746c3aae5ff62b10cd9fb705b9eabaad56ebf5e1d2a4f975dc27965097c833016d59eda716530ad9e29e58e2f2ba171399cc3303e5ae3907584bf5346e6b119f0b0a9fb2f5a617bdefade95f4a36e2861a4ccc047b3a3f655b4ff9f65ed139f7c1308f32a22c6f5b56c0d38054d024fb26ad4b7c6220ffd7912ccfab37b59590f1a3490c53afa5870b8463d7e6251a9448d575e4327039a2416bb65d25cfd6ee136d595e6bf28e96b60a58590a805f166d4d0c8ea09eb431a81be04dcb6a5475dea07f0593afbc7dd138a9ff99aac9778bf440d3e8473fe34a663d0c018faeb09bdd5c1d283ac464cefbebc942b49ee6f4bccf3c2e59248cd7bd3c5c0d93c9b68e34c70403a73e2d839b36ad4b424adcd7eca3a4b34b2e4aa294c847bf977559c58eee92299ace0fabd21d7439c6659d7c74ddc78e9d0710354e45f45f6fe396a247be17fb7067eeb344b000787dca4e003d133fd045d75c97e176320d3264fa91324411d3d6080cb99eb2332a26f204cee4d7ce2d8d399618481dca8d8bb577a183f1ecf971c9b94ee228dce1384ee7c76a9715861055cb09a933ab602b2920d41091eb5c14ff15809430c901aaffcd70ecc051e87fa6d62f9a97e48e4a60347f96d0bce2e8dec8ec3ecafd291b9a2dfdcb1219650d1bcd0b4685a4b6302c3351a5f030a799172a6464d3032889fbe1777d41f42a5959442551cff5242c8247a03750457c416f0662b6041352d1435855348f81566769593b1c8eca46e86ae44ffa4eb90a2af3789d3ab3c893b54778742271342e390c1c686d59a22586fb3ac42439011697dfa5127a83f1412d36e3dcc7697087d07b6fa472b9abfa79535fde6adc820c5f0e65d1cdaec158fcb0fceee5641ba6ea5623b95410ae1367bd55a265c78805db58af2fc062916dff637a9ccd0afce96c349867ae41c7a1686d68f056219aacc81a8c291930f9f4602a9e861aa0af51710e05c594434d56445f23cdee64b8c5d43eafe2e6904e97a8fe59b36c8ad1e7e8574ba29ee1478a180d0bbffe8e5db0eac4b47f9f78d91285fa3414aa7293409848037188c0750cd2a349948b7c32795d0e48b4566f7255249f48d56fb2d1785ec3e4934a3365cb35af90b02ec768685ba199072041c4b3d6fc7f099495ae99da95255adabc5fc6b3ea26ee056a445950003afdc03891bd0964ae84e6f6361b98999515378da4037c2b2dba5d5f91da039afa378fc7a382be0079792ae9fcbb5e2920e4514c155eb025bd08bd142b5bc67fc3772fa6feb355d02c50f969b281e43804804b5b01703c9fc386888d4f3bc808c61412de13a3b70e66518e56ec8de304b2901d789effa31cdc8d12331663a1a9c61f62c0584c99f5ddd782158e3df32f5509d191e06bf97defdf704688d80fb48b68b42b0e009005d8361b758ad1fbcbf655c4b2a965449a359738c91ea7f13a528a83880022c893f51020941815c8f6996c36fdf537c9ae3e11272e9334a57a911f8a563c47089a4f33c6b05bc95c002db0e040089ba31503222b6b54525837bdc6966df58548e790a38941b7b28df87bc6c7c9c66860a15913999769cc125ef58f83203833625062ec5675aafb700b489366786c14d621ca9edb5297f590ca43f71a2c93211cd7e7338dc1dd8ebb1015c5d12424470c46a0dd7a8f2862171319cb482092da1a9866e56b5f20afc1cdfa0247d783368164409d05f95807e1f7fdc143ead9269e916c125dac91d2cc0b267fcb641e4c08a28067b4508b77d106fd4849642696f820982df7344ed9910a2004ccd8704c2cbf10917ad74c242e5808f9e39122299a3ff4dc66f3ef957666564a3225cbec265086bd05351c3ecbff9c41219ab8cf3ef1ac58501442b083c09a8d43b26b8d1aa886ccb046d04e152f471b77432816139a18ff6f20edbde91351ce7c4f4be25a6109812ca62dd66eb558ad28fd6decc03263fc7ce143f60af7575d2048213b5011b78ca3e2c78c64dd6e38291b46b34ae6abc1f188057d17296bbbecd5b08c0395120211f3e9e2d588d34975f8c6cdaa3929ef58fd1f393b085bfb0deb11b433c449961c48195ff4011a493492fba7d4e54e94a3c38d1521280116ba5ff19b627d858898c403b4dcb0d1c80e39fb9acdb1b836d6a7605e93281cb1f64746e6913a8910e27abc301b4ddf69a77038cacf1f36a4ca51706aadebe9e0313585adf901482c0de6b14154724ff40308a4076d024ade8b088441d7fe1d17b15bc8bed23921b6101b05f9436b382ebea409e6be863b340cfb103608a87da97775e8cd056c688353112942790ed56ab9da46322977ba0c426af2046bf4025cfa04ad8809dd04d9321f7087b135cadbaf5126dc5c1182421496f8b50d230f5d9a103f0fa1a2d42b4407f876a92b416be125ac60f0c9c10ead93145134cd1cd8b968768bd42af51da46073c0a19dbb405cc30976c1f3874324bbd425e846303b8c8c01542d6863233a122c1144910a8b2396ad987306a4a2dc8273a40d741b60625150922d0167738bac7e4f6b6ca52ab1cd0ada5e55d5d5752d9019f707206290d49b31c3018af200419258098cd159141c34d1fd803ad74a382a4fd32c57d11a932a0c6d9561fe176513e0cdbb61cee338fb54769c20084255b3ded5093fe0783796b18b85d5d93e650f5e39c53e317bc0e881a75c542a36653e517bc0525f5370c68487183b6e3c4f7e9e9ef258a0d67e09474c70c475e761cbd19bc62cb9714c79e9cc13e1d884e4c226f133e181068456373ae0cdae6b2d141dc72b477b0d864a57f5b5d3f32b6bec7e8d262d8a694a030ea07071418da035f40d4bed45df3c18aaefb09817a51e667e81e97d077849033beb4ecf4be80b504de9baba66bca16141bc89bdeab8bd66af424b266e6fb79d09ce85775615d43a99ddef8ad2f6813a7b0f8603ab8f6e6ff9b3064fef8b1843c8915d4616921e58c93593760469b8dad6017e6b289acf31b8b2ca59570ae8bbb622a1538cf7bcd7e41bf59101d56634d39064217d21d63ec8cce3a1b746ef54fcd124607fb34893792122e5cde06d875e2a7330c20383d2af3b07dc37a3389d1ec6197ad2e0586a46d700cd656a339bdc00792a796e3a07131a01a9f47d872938a326074abd889f81f8fad0f2634c1b8c6eac8af67ff9fc9ad0c1a862748e04fad1c8454e6cee6fe83804a0f16f68a6b4737aed674b5caf706e8ac8ef421e6e7f434e799bdfe8426c359764492900d6a7f1742e3a32a0277731c82430cb7bf2c4fde836c50632ee7cffdec82c03e336a1042d3307239ced5b886bc13a23e0a7bfef22c1721d97214e8c50fceb2b4189a583610eda9ad21a0a60866a165a799184b520baace846a7e79a6ac1dc74cbc53c67f30ebfb622a9586a3714e9592da159a5debe8ef376708fd5dec1b17e83d5ebbdb41814935a69ac4c5194d13927bf7fc501a5f5fbd929251f9ec33800c5632a1236f844a75b49106adad7254e06e48a1b13ae4cc0f09463d6ff283e68cd7a01c52720e0c22c9140447295798afb48c93c242df820ff06d70928fc6959be3a16cac9854b572de7f7006af27ec864b576f3b4222d91ed2aab11e66ae912c1040e0a1cf1cdefec6eaa263bbb85ae81c724145f093520014597200a9710d9127f0b3e6601928453910e17a85f1700038486bd8c132556b894455130a96cab26441c0bc9510a2977e87ff002991331f5aa8d4700632fb2f11ddaef0203ad8c14e2cc2cd7dc0163e03582a78a78074d103cc4790f0325056dc2f044b37a80ea8397ebb9581c5d22123415506f9a081e9424dd3413000460cb144a8f093f619971faf6d98a3bded5c8150e7413166232ed59b2a4f099696c62f47a84c39930d26ba981b41aa55d0469519d6a4acb8269aa5471e45c94652ef611a88460bb31c7bb2596fcf65929e2ca36297de3ff4e6027e4d503135649d8fdd91669d324ed39a1061a3439a53a5a60a326dfeb45eec36f832018b5b444f0bf1524b45cdbcf6133bd96a4dafa1c9efca189f8e1260cb2246d8f02c4842406161ad13ed4a55a87b294eb4789fd175437eed5b028727ffe7439b1c3b4962407c30ae26991404edc855c4a01c5b54010584e81680ce4808f1ba314b00ccfba5f19c02ca4a4e14357bfc41f98a3377238f98de8844d1c3ca022591707774f4352b2a1c5d3e856ac9f5660bcb587e8c3580d6c76fe9c396e06ac5e8040ccaa46037549f8d900d9c29c40f96a1863c86aeb7c375f5d3c484fb534902702304fef264025ab229fe65e509871800b224b20ccea288fe7a38787a3b7442f300101b79ce690583384cadcb2546d468d34318fb3280a853cb6d60bd4f33d2143dfd03c062772ca6c9ba4317a3b61dc658da3e35c91a2fc6e2b1552579c52359a109e27667e7294cffddd04a0937808253b7f84dd977f9a1e00b88a219ea7dc50d329fdf0d9d0ca0c23882d1aad09353bb1ff4a53f3250949c67d1da8d420556b4d399de51a0c1dc77b64dfd5c669e6d84efc158fec2313fe629cacd1733c4a5f8c71a77218f2a56950376c64d7302c4b56c31fd3d44959d6afad7bdd39dbdb4d514855c480f23d106dd50844209b0a546723a4c4c82b2f1d71dc3460338f8d59c139c842d5b22de4709c1317182084200a338990fd59f7ce54cd90991d2128454a0823be45aa93e14446f90d808f06ce74c76f3ed2ed03b0dbf5cb80fce8936eee86dd636dba53a6c66c51b567c1262da575c4e3f6bae55ed01830892898fa9578f4596ef081fc05ad20c6c58fdd0dcf9f316b42eff73de75ae1737f10f5f9aa6898b7c8838197a4cffce9ddb44465c92bb6e77a1f79a172ded3a893f17beec8431da52cf0ecc0f78ae3d6ecd3da29eedf8769669d3e9bc7e6e0e59a160110e4e82af2894800721638c65f60fbe3e80b6d89fc99b52cbfb50292f3c7f513bb221f42234d64197a56260bad1f06df318236cea18bb5700149cb01473e335d7cd6ec47d4ae8c902077106287d67db85809837bb821064c9ae0ff9552405a68929e054cb5af4d845b6d342184b55aa25e7efd55e379ceaa9f8672817762842a1958162306ff629d0bd6d6c7e991d61c00343e2b400244c5f483a163a9a76ef38b8e8750e0b07d4be5716ec971e0eb409db861747134338c8989bd4325f3d7b5c999cf7ff595f841959225f04d5d170522941e4242a972e12f5abc280ccb6738669007b59871ce7597f9596b003b41c233b50f35c950793c3a625898fdada168fd0d18c81db19f51313161545845d8db20b339eb7acc5cfe4119b746d671b1e80c3fcb29a0c86b22088fa77ac035a07eec5bbbd364c0d7c4258c3591cc102844fa863353590206604838b78558ecb075a4cb6738bd2e4c203ef9a6990b8077c627f472d0bee8c2140ef111468d8afa022d9d76117ca87b7f562bd0892f16b574a924ddbee141166977844943c09b3e65004a5bf0c34ccc6c60b70d774f6758b860de80cd08d5dae3d0951c5bda3eb9f3ead78e19b26e2b16cd280641c4a320c051ce540433997b337f5f2110be182a954eee183ef8a93ac4d9f250531e5a921c79e78bbde82d4995da489170a73f9e4db0df0c203655f0e52f41305e35365908131867fabcbe1a54d0198c1d001d11af417a6e0a7e75a7fceb8645563e33fe0ea2aaa4c4d84c7b03c7f05ec37e2bb1e011bca3f17df3076dc4ad2d2d242c42ca857cdaed821a0a95438cb5018b43e305a8d188b890adb3504180c7536a90daa81ec34130865db10c82f9501b67a8f50a0c02fa63b8a8ada4967fcd9d7096f7dce32d854e0e25339b217382d835c555f6d0d840f72d0b1a8f8078f55d7afa537ebccdc6a75981a74f60f7f11b600408a7e417ed66b889f940fdd779c7bd194af212853687378f56a0b70fc89b47aebc32dfaff70e392f74382fd14a06f4e43a74479bd371bbf86a09ecbb77bd6acd63e2673d392f61330dedc85c4531316016c08a1c3ebe72060b7a0df1615da352da4b849123a5f0390e3536b2b020899d99ad0c2ef0d9f963e0d203764cdd806f92e8485ad4537bac7756ed400946897d756a569b00687daf7f519e73b75161f0cddcf79cd4ab672e45a6d8e97637a3b2ed1e520a983782752ec266d22ab3b75479ef5a9ef74f5c2ffefa4523dea50a3ff507dadbdf1bcede271233116c70a9493fb8943b903b84b2b340b63109b327c12914cf9f200eb131b0e0df5f7982482c37bf784881c7d8c294221c8b989cf0dfbd0367f07202207ce08d0590e9507db901a2def4478df167fc7801bebeb7633ccbbe3aea84377b533d6d00c90cf9ffa7e20d8809bcdad167cf47ad4ea2790177825eeea966c020f83dccf087dcc27cd05c1bfe448419ca557c94d7513dd0228139b7ea58561c1ba303064f3b5b90580feab4ac661677cb373f9d52d8044c40e120b5b4459c9f795aac1ed50be1c1d18a72703a42ab317bf108bd2c701a955618b240e2fc14c22221085061c28cbcc83c491780115367c83057491b516cb15c1fefb132bdf617eb942fea8cc97526fc2ba21cd3266d82412876b9a0f83578e7faa3e631c16f8749329eaa03d7cd9718b44d2c8429539cfa8eed9ef2d634692c048645e5f2c641539c83563e8514f758bfd2850d7bfe15024421875917ae698cdc1108304ecd83539766e0b22e820edd45121b336072e67acebcb2e1a9beadb84f06248e4f72b0a00288ba59f67ccca91cd94b56cc6c1e8ea77b385bf2c7ce0aaf026c12fd2d7428a5e482a0fdb2362ec17be85fe971759ffd054919a45cdffa348ab0e5e812030159cc03b19d78b2d57be5c076a2d09f54a91f91a49ba38deefc794483c836ea63ed4252b1312f2617c9948a867d6d7c9f95ab61fd6af15f95d66c265bf9a98fbd75c3e73aa30e0af0f8b1e035b13a9d0bb613e8de0556ae00be92606bc95a90ab20462a7cd6b5b9b5fb252926704257f87f4e3887eccb60620efbe4d17cb84c9424c342f989162ecf423588cf36aad32cc19b0b0eb1b1ace3a87092bd070b0464216d203aab6ddef2b2758374d2f10094b4152c7ba5da4995e89bd4765a7adbaa87d5762846d79d52e6621c10e203ba175e7edf5ede13e761c2cfaa5cc17e5cb64618f69853c091ef9fbbabd8b57c9e2d72a14e23d3ebbb67010d8f472c9805423df1e68dcd526e21891d5b0f235e7a59aab86e75f882fcfd28fe8e2aebdd6d2b19d8b9b19a2807349686eb1606dea25cf97b2e587d203232137924fca1db45dd2be7de55b6a760dbb01e5b2661b3381994eb61a24ccbda60bd322646a7ac4114ab145a1c3473ea36a6628a5fed87a616fc3ea760ca41579e138b62731e66143167f791c88a67820b56292353c9f9f318db9a142be455127972a298055e93315f9e548c6518e1cae6fa1406b6ac8f136f4b0a217d2de576672ea170824b1dcea9dd37431707a0bf6a12d411f163bb30d90b5d972394d7b6c32e87b7b5b7b88f6c88e265c2324565f92fcb17008f6c5f9e99323271165a0aa6a81494490d7b935981dc43c5296da0ad648d16551654ee2b7a98bbe0f8d0a5841d2aaad430aa8cda85a2ddab1be7d2049f241c3e5baf1ec8d341b36f8eb49c1ee56083c6d28f71cd6f4653cbd98507081e795a066e226c0fb4d2c6e1fd7fb7e177bb0d390319b4e0948c720ba8b1ae58c6280247b777a7324eb1d57c9fe7330ae227f9e4a49350e44726936775937367fb46b624b6f69ce3a06bf0e7f9480d29061d72c495e0d7b4c9d66289d944857fc37925368e3c34d03429e1e5273bc27918535a15e7336c140ac8124e9c2e9b561880179812cd08e33ce09ea7ac52197b34c9da3aad9435be0d6aa53357fa1303870de46ddb4358694ca249e4632b25d56f4b458f9b0dcf5c91764bf8a94524c589c3756bc0b0af4f2badd46e6ee0ac4e6156723f6f6746312a50495dfe4d5a1f1fc85412b61363b5b1bf5f6d29e1e2c6946868852701d5bb057df911b71f550481dc191d8901a445c12b698ade5583ae67a0f226d1247d3fa6dce4a78a0cfe98d9b4b0581b47b88f36c34abb8e7737964df85e012e77640ca78265a13f4d3428329c1e53f6e152cf4715f8b129145142156d8dfff644233ff2d175d79f87bd37e1948fae2f574b1d274b0ec13bde05ef8db54f1982cc795a40119ee01099662614e7201635d490e7243caae301d912c0b703d8dbba7a78d299d1fa70293202bf7cd10a7f8bbb3bd231bc8a97b0b0060817e13e747687db9876febade1be570d631772395cb676fab1fce95322ec350d9672423da22ea02b930f3bfa90bda31dfd614047feed67c1e4cf11c4ca91fc2f6b74f5383521e5c1cec9e3d81a8e5d948b5400c87f9a0fac49508eff3e1f95911d5dd13f3f1df388466a3af4b1c397446e11f2630b55b061d9b76f2ff7b71885a8ad424ad8f4da9f5b901f5707b438c2246983018866826cf7eade1b921ec3251f5a6283d37699964998d56472bd1a08fe463da4a91ed118af90bf3a7ff0d49fc554f5aa79e38836f6b19ba777cf37f8241aa725dee6d7fc0f1dbd3e059c6b69b75dc280217426f1f3a251dda3fe726e24a0bd10bda449bd55df63cb9bc0ce77f0a32aef85c78d69978e9de58392a631dd21c0dea658065d1a5c2597763c846ac512e0cd160e9448d1d7cda8a5198cd8a825434d4a0170cc266aaef5a78c7d030ae80f136dc41692473b059d04b1e972943d86086ea019511d615e21272599c051e54e458b5873eb3450437a251dd7fcaa5f6b549f1cd2dd1c82dcb680be32a1af7f38f20067e7a249edaf75ff4699cf5d25b52ccf8c35945cedb620f0b9236b267e804141012da3b543abe6bcde1f1e363621ae94da82f61cda86eeb1b2c0e4523035499201ed5ec1933f73104ebaf827eb8bd1fbaa09f829a7206e8369619a47be9ef37f6563e31271c9cb44bd580747ec37cb50f32629a6233c07239cd8ca0b091cb24cce0bebaba8362701d7570b8d13a960d6ba7316ccb9d12737436617e8cac9921a7e15604cb692da81213980b4742882c2cf0d38cfe221b8deae1d01bb26e4ec56486e02cda2b172a7944cc79857a8744533ecb0e5d8d59a67be46712c48ba5d2fa34fac8fc8b3b12f3bcf5a06844ae2c44462761d752a8de2cb38b0018e868581ebacd22690392810704661b5b3536c8281fcb7e1ca7f216892a1b09abe4260a96cff14138920f514a3e86139bcd09875be55d9b45e8a674980170c2731f53f221f7937c9474a400d35900bc75a41ff4d50beef98cd403e4e7b30a89041d6a107bb376dccbb081368e5d5e64828a2e302ba0b5123c048249c60751ae5c747716ec4bbaecfc1c687ee3c34bae01e5ca8c22e1d791f12a72be28d16c4e6efb85c162741b27f2c9d5c7cf704734892da45aa0cf00062c390c5275fb52c7a2f900c1029afdcedd88cdde606925011a85c42d6f3c70af95c399ca9a7322b5cc23e58543fec4ee52a87532d33106f8a4d0167e9b7db2f16e016a0c86ba70fe6c03a712897083ead0d3b31175c331db5963c30408370c08b11e7f457ea44954a54a6f1c15ed771eb2a0fb1335a42967fd05809a1f6c3c3376a6fc80657a9842eda066c91f9fd9043b889f822f83c11fb28e83fcaf2864e5785d0ddbbf60da73b86539feaada1b551eccde7df9deb238a963666179416234587ffa89eb4e19b2a992ae6d68a07e18a672a958df1c69c1f872a9e2c9b2cea80b223aa626b3b23d81ce4a598d2d7c374cc978fff163eac42c84a6c0cf88ca6c279579ded7d6f621ab72e180f5b0279c7b72dbadf5e04ac30d0764834e83d5f15b1e50a9046a94c6fb653af7ef215a08428809b8a58032e78dad5ab058cafe3124c000144920d85333c62dac934dd83c7060d7dacd811dc81ad13275a020e202f8f9b8632577249dc1a8a2dc25ffb727fdcfdb7f9af9ac6ec8a6f096260f7af709b689a22dd2f187ebb73df5e439d2f9361a14b98c48f9eca0c5259abd3722dbb3bbf98b83ca4f440b6552b1837d7aef7c607aee0a78e18a0ff430591b78f62cd99fa314bf7396733b2e7d946b5ebc34d0d86158bc6175d5cdc99560970f34e1aeff5650cf47e4237cca0b53a981862d0e8931b0ef3d8500496e04dbebbbf15ccae0da299dc0fddc597027e4eaafef71bee2d6d74829fe1dbf3d2d7836115a80cc5aace84838ab19e5714dfb010b8c79b0008c6f8d350d3c8687dcb2e48ec269d8ccfaa9d773e7207e1dd28a3857144a4d52057cfaac8bfd41a34321509e9b52c16358b068917d5832c6654647c4e2d305072df04c35b2178bd08b8c29e91a417128ae5ebb95df1a24a06d1f1b1eab3b5b1ddf1818d613909ce3234242c2aa8cc5e163009bd4403e03732846ceadcc36fa935d62209bad635a69c4e13a71f4bed4f81a9d514aecc664cbfc638763add6f8b99ec84a280cf5e9683e07b20b11f1cb0fede3eb5ea69af42e40ec973ab65fe40f5bf283aeb72556df84c4808cb28231132532d2dca7f78a99c857e7c67ef2b1357b28b994988d90a8640d8ed9083d315e71d5162c7ce8d08d4660ba990109ab991ac399b951ce4c46b3fe7873a39021e7e12b04e653a305ba793a54003c46769dee3197f2ad465a298cbc42f3d7a1f480efe98d0253cd95342c4cd22049cbf0b0c267a104c8b33f961e2af95026fbda6d204736b18b49700f8e87a21d096acb73dc1a7ff29f49032c5c2e7a45594740fd4800a368f7e08758f2450a803a717f5ec012549c04bac9163b8a99ab94cd5941671781c0f8fcfafde5279e14be8e5360e18d26e85740bd89085e1df9bc3189fdd34c6e9538f47d061a2c1fdf2529f4b2494a5f0f6de1e40b3156dff9b59f4299844a507cbb36f7bfda9c35454f47b3690986958f46c80710edd2d116c5e0e4faf7c5a68682439bb925a164969048d9e86f2474aba183af13f6dbee1e839e9c36e0a6aa342c86722b6e171280e1ed4e58531ac8129cc0beaa041af085c63c777a5a66e21c2b58b18379948c3f5e5fb3051517b889348deadb315425536fe5dde70fecbfe37a5e3660efe3c87e8ece9576674bb93db3d0b47f3a771cbd8e7fff13887b7bac15735d7c6d9c6ac15dd22a400f3deccc24949ae9219c9ca1e8fc88fd798cfe5ecc8d259a84fd442be39514c7d0b7fd08ef112fb70df9f231362776ed9a766409b8b809b043c681159bcfb72331de75cdd6bebd4c453fd704bf7dd295499067191511415a4536897bb943a6f5f5397ed9a8ab85ed1cff8782a3e889769e5306737b81249517ee2a105479b54f45be9d580f83d85c8039d9b34ad6312d86c6bc48c30a3dc1c3297d855d49ced13e2f353f9e41f252b6a83e72b6afd2a5e7cf54068ee99f2191935166c7d7135db821b3fb4b5b598bb0413b1a30d8bb731e19f57b969083ee6bf340b5547dc0fc121de1d079ac11336e0cc865d22986d3b95826fa6de54a45e9d09f63f2154a6da26dc785d33b36e6004414424fd95dca6682601fc1d61417713aa6a2e0ad11c03e6cdebe24ef83ac1191f5c77f43135db0e3f7fe515b185873d6f6ceebaf125fef4500e4754569303240d40e6d5c7308cb1d27efe375c5a6d613abbd2b196cb04becd63fd8e1612bdb291848e6089a5258991254c0f4c6f4737f27c3e6fe218f59b7e3fdb5423066cd2eed6fa539b4d0018ae4ad65ef9034d7b1592556e7d5aa8051bbfb112236bc6869385e19ced1e2fc2c4db4625d439cca0a309146071687ec3f7cfc94e3aa282c84f080aaab2e4462f841537eaa96378bb74e2c2c7e24f38a3f720aa5f37b4b34bc5d1b2e38ae1fc869fc8b558a35cb6e509670a24a4fd9d024ff6b2d2ce730e0f0359495fcd98a2199ca42b35613db27865dafbcc75c719767c48708733e65b53d052ea0922b1546b33d76cd9b1b0b11bb1b79aed07a20e8c4a99f0a37132c6b86903f556db9130b354698e3303e50c73aabeaa670a6aa297c1ac7e9c66f708fc5bdbf397474a34274f448d7935d8b040285192b6600cd66042fd41400815fa9e2f838c315c1a02fb820c1dfb36d5fcbedca77aad73ce9420e7e52a757edef19e21909bcbac302c7b1075db767ff16e8c933ab79b7ba4b8938f1ee2e759cbd383aeb18bdc883d21b8d03d0a312cb2c307d754889022acca3169054122d191dec1546eeb6b12b8bc940307c36fdb9105d512b5d631ac344dafd65096e51dab35d1c33fb1c1bdc6f535f8467dc5b54b888c6e58e2894fea19fc684dcc35e22e1126a7520d75d736a8ce3e91655dc1e8f46a99cd05c5411d72b6e1ad6b34e5bcedb860ed62310e96a431fbfcebfa14a444bb134a73251c3c1a33f75c11f66d97c813fadfcf0af55f00efd498ab1bb1ca18dda349098c881046e7989b162888d63841172a5bcf65b42af81887b60e9aee9f27b6eb1a1957e6b1fcc8f807642e568621dbbabae3abaa7d03ec80b915f155d01e6aa08f2f6d3e24bbb4c881a49dbce2de1d45f7f999c8336304838de2d3a57db7f23d8fb29026021268cd3200bd7d8937fdd2052eddeb68d8510bf3562438ea0eb21f5eb5611f4f8e6e0871716e786e552e4b480c5026bcc85b7103d65c7059dd30239ea05a4cc337b658020b061174d5c00e5a19ac7c3bc42c2b9310dc491fd37d50451a67aad59a6f12ea4b65a5077e7ea387589955108d7d838457d82c90bbdf0d2ff79627ff9b604de9d2cb55e10329daae942a4c5eec8c53525c53367b1f3e971b63f4a05e36faa7065ac35eb21ae0dd8dac51b045d4fa68babfece59979cb36c54cef4e4363d86377d0d667bf30263d4d70c9f95d620d1a9b1c584bc3f08a940dbd6db17528dcd1ffe1e68d28b54be1347ce0c91e3abb56dda5ba9b32ed448cfde6368f41910160b19b908f3b5609cd833827c990579298fdbc527868f01aa563851f094847dca8644eeb300d19dd419da702f8bd79b2b77a2d210d5f849bf6f771533a9e9a5593baeae695252629a61fb341478dd8151571873031793548503f53791692d5318d39d93abd6cc5e77f5683bea336673382b647829101cc83b2c49405d10a8a50a5b4ac733cdfd8f55d4a2cc36d2144ac51c95a6878a041fe2c79302e7d69b11112d4214a3cc2d6d54ad40b9b08953842f1ae0352bd98938bb615ce3b9197a8b39e25fc62dfc7ef784695315aceb4724bcf8d3c7a4c0105fdcbd8d8508adeec78aca5d536d34840f2d0d4d33019e7fcb75df9becc703243141d5cf935f9d42f6b36e3f67655845ab79add0f050c0ce40e7e28179673bf54c52950469aafb65303d0e27bae68c18b741ba8f7c1fe8f9633066d49c47b523c6f09d7e441895fe50a71dcfac15e0fcb13f5082d2fc94a5df8965b5e11ddc4d11d36f69cae7e93694e214a0996a0705ea35139b3f6b9177343ab967c6d848a43367be183948083883f4765312b5094103b66e8c8d35cadfe3c776154649a9385a8442c86a391b23bba19ebbbf1d0201471337522399b781b5376b9c3ded4db9a00f271904655ac4ae42474a2cc0b1d0f3528f72c57e5e62f800a2c6c8d19413e51c42f6081bcca51ef3d87f16e16db9597beff6801a76c3adf2793a939d296f302a351902a3a9dffbd185ddc24f9eba8d57dc001be14c69b5749c6f6c6a7eb6163b1e1571c6f107abb3b9a1be3f3fc0f888e69d07bf07c24be2e01913f7df895511c74ed0197681ae1f86559ecb64a09f869c50412a9d8ea3759f356aad509da3617f739cb1b95da1e19b5140e8c0ceca139274d2ad7d41247b275ead5039d27667547520f7755f9e2c5c2c6836e5f4027b0160801bfd3240e73a65d1a97f3bb48e431c241028e49eb846939baec30c42c717f4cae9f89a62d2fc51ad6dae1cccd268f606eca0c96dcdaae574fd31152524bc787c244c69b31543bd1c488b29f7101f8e425eef3c6abd2bc0c55b308b305b30d1e7387462087522117596cf1dfd14a079430e312f137220959a27864c8f7b4a2a1fdecfdf7bce75508ee81679731f32d1f7f3198e3f535bfdfa09170ff6cc0a95719916083a0e8ad9d3e47120c9e98ffc7e9398dff0d74fcb3361b51b4f24d293c722cb7a5ad8954b114192833d3b1953ebc1e8d12fbab74116e56d0e07519ad354ab86056234cef3704aa91c1262b4d3db4fcc12c7a554a10051f52800a0dc718c519effe3fd329057226eee2121d848fe075fc439955a509a5c1ff8506c68d54e6bf338995b6750f9433dbebc89bbc3be0b8c650f2352925dd5b22dfb43834f15aed82f3f70c32a948cfb11b4867088fe0569c2f5ba14c012f23df7076eba3b55e2cc70527d42ab4e57191f8a25ceb41b9d4d01dc64a8efc67fada379ff5d48c33a0e1c3521387fb0952918d8427b695e8862f40c222f20d30a13c7c6cc8a679fc2cb8e469018c977c31168ad502dd28428530d1b630e95ca75d90543b64da9470b0a08ba9b1d45b956360cd7a61e4d9fd5f339de6c508b4831b55a222159f573d55de262c44e3efea22dccd0063826ae5882ffa001f78930142330c4d652c067431fb9a574fd40fc436b4b04d3e9cce778c02569f12dc180e092e8629fc7c38a70867360dc6da0d5e37c9c14733816595ec3e418887e9b9119fe8100374c6823bf5d06d5d1d1469354971be8640850805827dfa6f0475dc530737d8e552701cb47a35498dc2e01285f910aa51583e810d622bd16b450a78089c1dd5731e1cc5bb4fbfb2911b3c8aea094426c16985f27b085e89ed84baf28f85eb3cec2d31c65116978a44a575087b72150ddc6460babe6a332962faeb90c4002887d174dd01bd4194e04488afcfe402d54b05fd70f9d19daddfa35e9427eac7f4ea0c4cd24bbb1fa04e7543df268f32cf221b57b1fe9156c034c4aab5576d7e91820be44060874136b4bcbcc6b4f27681575d3ac2c0f396b3fc9e8913c225bf35b8cbbd3e4825412f53c6b4ce56305961ae0abf8ee5412eae33462bc489b6c28a3821e2f89afcdbc34fac7fd573699e46f2b381daedc84b34f91718ecb7de5268734f547f15d21089c88b5bb1bb1b5df6ff1ba655212acacb9a1d7afc9927abd09fb7493fe49bd010d73e0c6f5597c7babcc094c915c4a1414583e6239f0b75537d975540e73f3e8bc0d1b28c073072a1b0596bb36a69696c514dbe9bed38ca8b624962632461e4a784c64dc0f258d2c383d0fdbca7a08c39224e0dfa6e13d99d70cb5290cec2d725181c86ac2e4dc3fcb2a5e630ac60ac4085d76b162abf6c90a3969274ed348ff8c2493ab00b7251da422eacf82247cb2f0b0f4f31bcd82ce43c6a8160ceab52db4d5aaede3f71f878cd109e6795a01b7ecb23365511cf890ce9a45609fe555ee41680d902141d3cb45b46400cb08e53b688df527b14ec84264d0ca6ed9b3615f55e4f4384d0c6d4489cd75ead20a2db945632752c965d06a402f9ce0b0adae1caf70a77ca665e2b3642cbf93557356ba98b4a46dbea590f6fbcdfe359717f1c465a2b7aefa88894f329c22f04e1e3199189a71955e727edb4638f849d133ea0e747b1f3043400d4a2b8d881794cd24cd299bce6cce65ee63603c56d076a4a427b7b86fc48be1f436ac3cb21d969014c803f0ab5dcf72a72e8293db0c4f0d8b4751349e144df9aaaefdfd875a98cbbf9858aab07326e3e44d91c51813cdb84861117bde4e5dc7a84f9bca8e960801c35d29e3b592543f5bf197982646e510b6d53c697bd3518e462d7788b04d66626952dfc99e652064cf68eb6993eea16ea02c30361a1e69c413590f44cae96e8d6a9e1931a45379a742c246bdb78a11497d6622eb2e0aceb201781a1a22b6c1425319aaaa10a9dad236d99eb1019b884ff186d6759e18948b8fdd46d6cf83f5efbc390a14e97f8781fffb40811dfd57bc684fece0db13aa6a1c214493f19cb0707d9cf72d9602293ca31bbe5b8d3834094a8d0e92feb4aeafad66853ae8c3b6dd410abc5c1a5e94137b325200fcb0545d6121a790afc49a8c8cec757f68f5991b0c00fa1086d54040d1452a819a5c1aef4f3738a6db25505a7b703b11659fd0897b62c36f951a4e0abcf1aa3e10e0c9e0e44fea6c1a95cb7c5a36d9933f9445fd45752fe1f1f680af3e92172c55fc159d0f911f9ed2116a323ffdd64faa074e649017f7cedf28515ce3d46cc6d01611617fae87d82df6df30546c589e9817b9694e4db98c63b0b9ec204eed41186b62e4dc60e69dd9fb5e3db4264293b1111aa1be2bf9bd982f2635445f53c1688e52143233a10d374e01895d072ab317b6e878de15a65f991f93d367e634a8019b4166927c9a24b98ac0725278a7dc9afc616e381782d503be9e49422b45377d34ac9bfc0089b037bdfbb0fc7eddc00dcfcaa597d181e39b7ad17586fa8cdea62d8dd906f56333cc6a831b9d03cc5b90c37339b6227489ea9900dd5e1d5908c28020694d9a3cf024cbee07ac85add847c1aaf2b0b62b303e2d512b7c08fe72e17c603a9b7c581a7b3fda3c522c3e30f080921431e3a4596ffcc941612685df714c506e0b216eac8cc7d9691ade7cde74edc2fe418da8b3fde3224eb882444a4392807d44d161e5d6c51dd3b771dd6a4bb30b91a20eb64766b8540336473dd097dfc3490e8de885f345995c8142fcfc62747bea4983f02bb69e1d567a7c0784e456d326a41e697ca8a8f342c28203df70ea7534e6df76201d93acc2f9951ea250261215ac58348ef6f32e1e7fb5e7e69d9dc8107d9a253b4ce4efbd5c6a8075b23434b293fab8042e9ab62161373a9679132b775394c8c6013489ad462c86a24431ed68abbda932058ddd126a4b088e3b4575752404072d48cef7d4a83e6ba7034ef779bd527ff4906b6a62e15d4869f51bd30047a7b60c9e29522d6b02cbe992283552200559a76552a5cf4d684686376d2b69572d58ef12c2bb560c54a9e17295c8727e5ed5b601eebb2ed7bc9d4f52001b12d5ab85123dd70db77b346b12eba8aa653bdcc49ed987dcd9b6fa9e8073c71a311a06c31442e39f7d6954f05400284403af0c880a71f3a096289f9c97f8756a485baca2066f230e0abc16cc07ca8119caf43d44ed5ccdea9af525b3fa5e9c363121d15e6e898215d6a9dae0764599d66bc88bc037f291c879f31b05e9be4467522ee6d36fb1ce9603202d0f6cbb30532a962f3881e604af270b9d639d622457985216e1657d25bcdcc0df17bd023a5729c4f151162514e1874e3d970be14dd08704172488098370c27a90f74e15b0a5a1612d29feeef192c412454da4040ddcd0722963c254b874dfb158253845fbf7b6015b0833ca2cd024c9200096cc9df8a4a41ca0881011b1392401da68cec795093af2e2e7f83916089733fd4ec303dd9457e17a2e44eca9615f62ccfa3870ce672f2f34a0e92bf60072799442adf708c17c163c0d97b483fce83f12893abad2b3c66c1b1ed0e2484170799a561b5a6c9d6c3fcced52b61e14cab90698d4dc9093b6e09aeb491e278eee4c6f257fe4fbdf77f79a672cd5515c78e49e9dc3e81b0d51db9f4d970bceaa75e531383c16420759d972e8f7a7362410f0a2aeb0014fa0fa76b4546b42c8e15cd4f7c0b5228240e4f0c6ef6007debb09c1b4c8e63e21798d5554c21044bcc2a6e7a2136a6757bd456ea7217bd0e88aa3226d65611c205431747d132602d7f7409cd74c167471f8a7041f60b4d41ee292d551377078a5fb9361961513278cacef52356f1064a44ae356380b8bcb6739254beb5a3a6cc5feae7c4f99dbe81073fa38fd3905bc159d13b4e896bd023bba3f076e276444d87360b3811eeede71b96cee3682a27d35282fd6f44989e73f4138c4a29eaacb47cc28f2c5f2f2933218476c497737ed4501ce3e624598b6ef0efe7de6a37d25a051264094fb366622ad6f1be5ad5c3dd49655f0977c802a55e6e570622135047dd0ac1984ad7c6b0ef865ab2e64d15ba658edf0a89595c2a96470eb134283976accf90a919931e1247f39f15296f667b09700e990ca37ce8a85b9fa32cbecb6238a493a416201f6b24beb3e45371c973744430ae02d85237d510c2a5ba7106eb4106cabf774d04ad3a08c66e84675739dad7b11268a21bf2f7e51baf213f4dbfde2ce71fed0c043d37ab246a288d96ef4131124c9f6aa4eafba1d2355ed51a15711e2b827329ab1415cf7c61cc6d2ff614a2c9d05500b310efc37d8bc05f853d6da21d0eac7e9a91ca31841402d249db15a370a44f75ed3991ca3c7a51a9b84e5d765d316402c11ddb9df587908e08054961f9052c1aa5327002f1e2f8bbc3bb594fff81d8236558cf5548a40c4b1b03721e3275c561aa58b28f1fec346091afc844435828685e71cf35814d50886a0012f3fd0857e3303d15f11b2a0a2b6df2d71a9fac43b0084e11063cb411400f4fb08cf6e9021a498f018f66bab530b20e58f0fcea73f64c6d71906adf320cff8f1556eb4c9bd4570d362d1b7db57e8115a8bfde565a4a2430ff5bee598cd0e01ffea8b3fbeba0db07286a4fa5d61aea62608f6ca5376ca18bca8f3a60585fd47d8cb7ae18b2969adea0748bed7ec7442d0f88f5cae067d899c0b9023a76c6388e22cc050eb0b55801c1634a971b4ddbc2d3ad671a57928a13f321d9e335e56ad02fa724fcae024189cca7587d498df9fb9c978a2eeee7ce73c892459f0452ac026b20f1f08dd4d3daa48b8a508a5ca9aeaa8f67bda8fd57c837877d5924a4caff0f185688c4336beeb1034b700f52ea05eca02e38a3fe60e813522e082ef724cab829511e0fbaca9a8eade836e33d3356e41bf2e1f156984b189f56a464fd8cea525b448225407c1a41c845ff5e3fc49c0fe37ee766f0c0caa2efdeae03bba2c0d1a28b28a1d14abef0274162e109a54fd0279f86e9650d038bd6e5648611f1dbc141d110eea7ae35468598dff70a8a31da0516c45f40b5dd835c419fb1844fc593d343d78a2a68d05794fa08d5630fae41036431b8bb5393e139eba0eba01ea0876ed7f6a93ded9cc30733f55d9e67cedc7e741a20f5ebac0ad89fa43b8cc78aa42c886032cef1c11293cf21d23ea0873c0ef7e5be5179918b97d783cce1c1bb965c4dc30b6244e16bbc3d56deac22f5cd50625a95d3f6e4272609e3c0d85dcf23d66bbc3197e73a096ea919a88b1f0c62eb4eafc28162623cb1e96e523f6f08a1fee78a64f6949d1b4cde3361ea87455776582f7df350a30768c5550bd648d6f4234a66cfcf3696cd337357153e2a1fa3f57a6fad70b369b6766b46d9326ab4ddc3183f9e603c9ac3409017acb59636ad0b1e7a9c65743aceed41ddc86b0f08caf5e4e980dd5f326eabc28e1ce681f62307a2f4b7e2c031765f8931c449e8178688fccfbddebf6456f475de2791e12f8e62c434cdafb0a84cf00b5490a36b4548e59bd7a9a40c4417cad2567e8ab2725dd0bb6e1f31072dbe354b78e4695ab14c41824da053256cf8a8c08e118f0d335bef1d324ab554dc01e4b00962f272e9c092fe1134f4153f7f41f4362306088e5e936642260e35bb9a8a7de6f7833a0ccc26ee9ee69d5c5c48909f1bc01028fe64a0fa55bafe8b02302f0f8c2182c254a9fd584ced26ae066c3c56fa68281489fe8cf0dfcf08cf6a93fa9a83d7186145fb5034485b99724f8a3603b54fca0ba520d87a2151b20f75931dffb2dd128b8d79fa20a8da46ea6b97e8d4c4f2ec12bb07b6ba8d781d3712af3ca4d0b1a9f37c57ccb2bbc4c53fc3324feba1c7745f59ea2c010932994243bd40268841bc44eec3e40bbf5ab275a02b1abe007bd399e0b8ce14b12a28ba0c77864c89e05cb5a78ac626ba25e9bb487c5956c8eb16f42b8423536878352dac51a1c92b25b5f583f3bbd52c50a235958cf45b0497cada21a28ca88424c69bf6a8f5e270e27599802c69fdbc0af25e8f70b6e5d66360f95499904e5516c39e7e3b1486bd57a55ba24e002028c164d3614aecb67d141a5bca59172acfa2819f860aded79af98e2107d4da7fea951f16803630644afff40b27e8911a23916f958d579f7780775556001a64a80c10b3547119c29823c8e6f6221ea62f2cb7e7a16bae65baf063169db7b4451bb545da46b5f77f1ac5df1ff72ef38898164d53b23bebcdcdb71d7d45e3fa3266cca0027f87c8e8eb6bfcd5213c01407ee2810ca8a1ac73a63fc5e345be45174753380949448e786161e1de2863e4c76f32fecbf5443b07c1e31c7c579d4fc94632797586f8f363baa792849914a98484f91255aec102e65ff4a92d5602ffef95c437179a5d2c483b724c403d0a1b84ca08f7fd3ec0a78abdbb7b05e1bf110308fdff06e141927fcbc11f340c27e9bc82fb043e1db113239202307473cf339cb291014307da46022786cfbe62bce9f8877ebfb67134312a92345d24a88082d63e0cdd4b7870203e874255b458fe5f3804f3ed0ec25670af750b50f60fb8acff97d707741ccf770928634dbf63d6dfc3925ff9efe8f32ed70ff71f2dc5907aba4cf35a661875ae222693057ddeb481c668c00656b714d9e0391bc1c8c9983f2c308b2f4e69c1620a6dc6c3cae2e3e1873ea13e31d1fa29cc15806580e534e1a48c319f6f28f00e3c817ccd451c82cc28bb95623f4d8adacb54eb9ad0fe7e0b52385281a4930c28c2a33d1b2b2a141aa219dd6adee1c59b172159e466d0071b092368d658315f9e65c6f6ae63c376adf3309f05ca3411848e14af0347c2e446915951dcb91f2ce9918e09d1cc1201cd1da2e7ebf6c3bca0fd8c40985e2519c892915de2ffee9b50d76e54051d7d21353230092ff24e0dbfbadaacf9f85d56c9873974b4382f424ce4007b93d01a37490155e716103de87055724886d57c68468f440c5ab75317b0c101260d3d3f70b790b4e8a10a3fdd846b40ad1731a868d48bd5dc0ffcaa7c3fd6c0a4ce9e085386670175d056a53161b7b35caf514e93f4e51cd54e6906356c390b8c93dffb07c7b0ca69ffdd83bb7e0d309ed6c788bcd3e3d42584af4e49e103614dba2b78cc23b35b2e8228d81d849c7ff133bab5c8adf8264479f86126e1090b897df48f9b3778171049ff92b44010f1cf03f02e32e196a14fa09717cbc829c7cd619bf03ef832d995f2848d0a265abce8bbc8fa2a2d0e430071e8feb302bccc320a05d15aa8a99e88e414f2212bb81466179e51194d1c5a01d9e274d85782d49cddbc46cacf3a14ca1072be7e9bd1799621c41e050e4f423078e025acca7e2d6603f63ef4d61a3e79518036f0e1b0c4521b6dfb4fedc938308baaa7f8833282e24cc0037ce1b27ccb0a411c3bc735a0907045a6665f80e92ad1a844fd47eb4004ee922f5c7a586c46524242603aec740fe292eaf876d47699158faec3c784d0267142d2b9a4dab63acd4ae53ff7caf051615eaaa09d4293b0233e3498667e97a475e6472d8e9cf44fdb26729a570994d223d1e3a424ed686accf7f895d9459b1d5a95d79fbebf67057248922c6751163a1dcf8471b41c49470c29f1f922a2f4d77eb6d00da4135cb688e9960eb28eac5ac380a376e04b86b623b7b4a2cb1a051f5851ef0489afce94bb94b7a8c3682c00b4882051e8b9aa6aa6bd60eb0fc43410255b19749c08d4091fb20dd8e11bcf3f810ab03e7ccc98341747201268c649bee58110048d3d2aeadf623ef2a1fd0c77c30bd7224a93562dbf6377fad34b0dcc60445373e1ec6b2c591b0a450ea32f78623bba2cdf0ae3b1fb4661d4e68a865e6e4869d9727b371a78e87d7439b3bcf7125c169d47ec8d13da26820b215ca0cafac680bde1e66288d7cd7b5585dcd062272350ef5ab500a3644d368e5aac7379843a174ddfdf46150f926dc60d0fd5eb3b3bd399012c1ba36ad0d9d5d77e88c601cf410e56f83cb43896929e6745a0f8e4a43a30e5501205119577328783ffba93e6ece4b52cdaebea94e26dc7c228703ad2cbcfdedd949bb20ca040b786c441791a45a7c986ca4bca556d2b9a42c273008aa13bcd3309db652f608582348187809e4adfeb4a24d5abd7aa26da0791be3b799c1f3c83586ece4d63a83149309296f959fba783ac627cc009ad18f050c88a30be908eac13d503fbed0f6d981f16de606ff194cd97ee6c43407c894bb5c2ba7ff3102533fe649df333b924c2d5d28bc0149723578881229239fd802a8fdf6a56f1bb53c09a91a913c499649601248acda2845d015c14af42f0c9971346139de39630c20c5a60f651f02f6926b29632b641790516582f9f0a3e42e626228feb4ed6abcdad4a614e3cd57da7fddea64ce6303e4e2e6f26bbe629549943f6d6081ddd6997b9008f0dd4e78b761249f84f5632e4800c4fb05921191a6ca173fd1f17c4af8f2171c00509a48c4f8d960906cb86e3aab7b0e79fef798ef5938c8d822dfec981f2dd3bc31375fdd17635204acfac307a3487d0451171c12ee1a209f103cb2b7c45fe2d358e9c5d2b2dd9d300271d3735bb2514ad3adf67699c2b3ef3f802d0d1a08aebd49361c0f9c5b134d1f214768bc6e1cc588fb0fa3aec1221bbc1bb3757378d82807861fef9c634f13d9e9b9c3891039de80cfae1fb7c30d78212860da6046beb7317bc7e760c7777eaaa046cc4f02ba68314657309c19a50367919a9f3e8ff1b54898cbec5cee96c4aea4aa90266193660efbae272cac2e61f18c5c3202f108aba1620d8c2f020182801ec0f2100a6d1b363a90ae07c5bead6fe25784bcefacfb1bd97b4b29654a32a5145804040483043fc46088142a539e9052c462600b059835f57a71bd18d1a8dee2eba3d6d0f057b6592fdfb24adb238558bf2d8ff555b64232f7d547ad61f92adbac6c7b96678568f9172c991048ac96ab86c52ae960cdd130002a180e31f0dfe20d228a6079ec8b68f9173a2d4ac074b27ccbf7fbf2a88d96502c58e6434bf622d34637e410d866399b65fd0a673839d69095ddc8b1feff2dab94838363e87a1c1c39164ece6549232d9fe2d135faedfa95ccc655ba715d2ba3d168a5aa5aaa2a23addc609a6be5b7ea476f55a51b3682aaef01c7cacacaca252556246f304d2565766394d9b8c134bb7384a3aaaa1cd670dc108bc5fa4d8b2c8bf4625996949794524a79c9eaa5bca4252d69b9110dabaaa48d1ba7972775295ed8cfc52e33d8a5c205529230062e84fc4197f7eeb7efef377f98b53710deedededf0fd2d58227537d6edb0fb750bf166e2ab4143448c023aa19c1f73a113899452d3fea6d550a0c92367f93479ec98fc6e79dcf26c0f931d22c49789155a86425608733770ce71b303c2fda658cb9895df65cbec76cae9fb47d59178c75c6cab8028cbbddb238898a6a94a7a86e43712c800349a517ed49ad1cb6ce3afdee22ac6cc850443c68f5a0343662fe58bc7fe07cbb73c101931dbb8c25eb0582b2596910e2396571942acbcb3947e20a9c0e8c6e829a03256328de44cbfa24026eb480e816de68335ecdaf877db87d90e1cd678d7adfb2ded60cdc9dfc132a2b1d241964fb125df6df22f292fab64bd351ad1b0ac2b46964cd9147fd3118db8325ebfc5b7de8aa5527c7959d688c675c9aa5ac96854d9c51ae3304dccb6fd6aab321c9d384cb32f7fa755551fe3ea5428d4cd0cf86e9b19925e2084f0dab0c931cad2cf4a961a4208e146194ab028f22aa6e10c3de841163d903b4d3451d44415192eb420d4d811420861b8ac86f35ad6b2afe557cb57825143fe84ee1d3e7efb30779e915873dfcc211cbdaedc5d811e1743767e6420a4040c51e2c3f70f5e315648603fa900c217865c1a7b982afe00bc80d980ff227c09af8fa51e3ee1e8537c1919653060f6acbdf752098c0a28ba98efdd0fb01212cc7df5a92bdb80cceb65bc7e4735e05bee610f9faefaf70c2be8a073f3491852c6a7c409cf7c34ccf782fc5021ca3f29ea7192c4542ee693113e148a69f4c97c0f3303017dd2139db0d4d2eb033be3abacebbdeff8de7b3038ab015fbe7ced17b9c03097caba2eab8a80cef71edfc1a8117fbed23eaed8e224307f17d2c77e349c0e3a6174432402b044f38a111d57b3190c2430fc7d78cf26b0ffde87150249673ee8c31762ffbd10485ce603ccd409f1b21b9480adab29a989df14f9f92f2ad8f614d08782a53fb820899423528278ca114454a4f474a11342f8e0eb52ab6624c69d2396c2583e225c1fbb034405888a8bf921fe7a1b403afc36808080522faa1c71717952a031dc3b1f2b78e74365dc0b1f6c53206c8642d127eae40a05fa1915d113344b539518d1641a819f627fcf7746c7369719e1791930eb5ea559c2850648b8ac80861c4c71f1a1a10916eff251a2e779bc873023b06d77b41d229e69b4c741c13a6a138f1c6942e707a55924148a1645222c45c0b6f5f16142476de24f1678f972478029400108a00cb880c6708f4365acc0b41808d335f879453a3a7a042d113bb6e7b5f7105589cd8c065093d4d882699d796665db02d17126f73196743e46f72265541bee1f6804531b87a94d4735997879f898bc58131857035b31777ffee4cb2934c4e0d758c75cd7b80373bd45357e8d8198ebef618ac5ecbbe7c5fa1151889aba00379ddd9d1dc2989b1467bd8d85f7ebf7e083dd3b58b18bb0eeeef6e8839dbf113c9722780dbf9d045a8c7d02191a3b48c2ee4b62b06661caebf7fc79630f0667c5139a05afdff3e73d22c21a28ee04a902617b15c8e0a4bdfbf573944382f9bbfb7bbe648808ca69c60b9b9e417b601a7ffd1b3ee0e4d880d6a80c853c652663587337acdd50044c8b790ce1ab1e84aff2e7f253ec19ac640c6b314eb6d40a42c055c95635974073c7f7b2205e670fbe4cb3de7d8cd556104d8bc1c931a34c4e4142cffb47cac50a6cd3283b7ac44822318dba3410ac7de6c90aa32fafc4101114b70c3720bcea7ae5724f340b53dc498c6bf1c518939c463b38275d181244db97f272a2a3b3fbf3c7cccccc9c392f6d3de0e4871042f8aff478b004999cc1e601c6ccccddbd8ebbd7b97b9db9db77e3c608a360294c3fd74419afdf830f767cbe6193f302d6dd1a83bf479cac32fb1abf63d5de0826bfebeece56659663ec67448c4d42475595a5094c1fbedd1ed696486683b525d2dddd4b647b4a3b647bf4692cec59225de5e4d8d7d26e8411427f858e3fc5d2aa5e96aa4a36aaea5996f5a41cc9fe37aae17fbd0d55eb2d592ac9973f83b5eef15e4544395e5766433f6e31b391a584307d9dfafd02a834aaaa663e58eb1dac45e9d922f05b1deccc331d2d59609b0add70291bdabdddfcbd0203e75cf78e94eeee0f2ecb88c693b0d71f6cd75d42b573ce41e8205ce71e3a084fb09d73fd9e7b19cafd7b7ed1f1ce38e33d84ce41f7dcbdbb25bd406cbe2ef55477f7dff6e7de95600183b96285a5ab990ac1367ac04197c33937849c910ec59a0ed3c4f8d6c1cf6199a3c3555b7e4508e1cbec848270468c314a1d7e8d68b8846e79749f21a584988eb6fab78ebe33f60c2847343cba8dd6d1513df7482fefbd6ee8ba1b967e3ad70dddebe79c73eea4c8589285c9b41ad8defd1cce98d5d10e1c64deb0c97d8a515530ff4d0b01434063b46bf7209c4b30d9f735d7fe5e5dd3b217d6629c2efd8aec4c54877aef596f35c26a4403be3895b9e4dceebe760f3b704d20fad7c8f5298e9594efd9893594945e954634aaca23af389837aad16f392a7ef5562c954e712687bb4bc8f022f5a8068a6922cc50a7c928ae20c3767087df33379bdcbf0c87693ce30c4786d305ecc47f61158f9460694c40ddb8682edac60005d85545bf7e0f3ed81da7a0a0dd06bafbf337c39d80e955554546de17b5e13190610996830b62da0a4642069827fd6bdae904a439130e4370bd03054cf5af61ac397f2a92cc28606e27059c4ea7d3e974caa1323910718299519bbe809a0a1db41196bc7ecf9f7764d40d4980de7bfe9ca4fa5aa3833953ec42843a383848a4229c85333f6e899d9d203b5660c1ee6ed3d06c9a768108c484859a8914371c61db62f8b6f4a66abd9560baccb25bf55e611a46627cf32746830ee183cfc130835876e102b6ad0fff4cf113a39d4e33393a6e8e080d3d39d24414a7d3e9743a9d7252806dec73f26102867f9e3869a593ca9c26f39ed486e7d545c0bc446facbdfbf5731ef1c032e1c764b28ac482e8803b53d5f8da69820f112245889060dbf69c7a86c0a498462f1f2208050535219a1b6a6e1a4ad3b4d7b409401930c371261b54806d0c04c4049e5c0d94d12ce4031b4a2d2d9d6117c768afdff32122283c7e18e9e1f9fa3d7fdefe8a520522fd5cc96cb896ab840035f15725abf480d6d4c4eeb4794096d80373bd6532c63dd107c5348e089122fde4891327575cd1a40993b9699aa6695a0e36b9cfc1010b28a6713e37f8c1ebf79c6558ae3bc4aef184212228afda2f16403cf5b5289e7714495acadc5c5cf80626d8b63f3fcc404344507c5a7b01c9a016211b50bbeb2bcbf16a27227cae3b7b374cf32858ea520936cb5db0b43909a67ff3e0e6e387250622e59584c89c7a55e2ccca162e3b65f08135a1671a850de123826f3be57818ae19bd62a3920e9d3662a808c771fc3d0e0ee78793e1e028451cac392f6055c90de1c877590faca11c6ac6cce30724574604d30a2d85bff7f62d33ef430821fcb74bc4ddfbb9f773efb7ebfebabd776494214bb1845dae08330df7ab7dc7be355a262f06afee41f74f4387f00d114159c93670c7e8842a314b3848151ee22176e278dc107ea2b1141e622a6ec70999ee9f778cc638d818c9a4e289a955d66750356d8c646e3ce42cf0214e986e7bd1b8a8b95002dbbc4811a6790232136ac33e38e889d188f0a1280a17334cd35595654130c5a9d48c869026bed48c979af152d97b103e1acf66a466c05497092ea6d1d601cbdc1f690c6c87e395b9414c8e23d5f6d703936377cb6bf481c9afdad563f2bbaede7d4c75314d1341f99906a51a8cd6905093c78fb9af3a73df470560a647f6c8ea3e715a263f1bb442eba465587b9d2c1729988cf5611f159c6620e9b5d3ebeab6ac6c3b36838197ebe2466dfcc6010e40296a4335401900821e2a93c3110c6fd4c67b0a3e2722448a14e190cc806ddbd333e4f4989999b50984da04055058292ee22286c251e8e400036c632226e22729286e681540b8430522e810bea1202127344e34cd3d01db5648a8899290a48bbb7b555553f66b20e9d40080ad061b38e0520310bbab23c6a7043a84ef46131a7aa2314dccced41dfc41414242397c3a9d4e454551a250494d1e3973377b895d0574081f7c7e5d2e2e2f2f303537dc198a351bae88d1b58fcfe3e0189203a55272a8ecce0a41ddbc6abc82890bd3fd86000dc374bf2da0a43516180d816d27d4699f3323703398579baaa66da3a0408a2387ce0e95713cd4e478a2b041a6734d9ac09c50674e601bbb2017238675cf885b8e20c2c47d69bd04846387b183bebf0efaee3e04feaea43073615363320bd853e1832bccedc47cc4d21f9410d242480b283f509cdbe0101fbb0b86e3cb8be68af5dc624fe7fe456fe791ff65f3b930f02b1f5896aab2bad730ccddbcce9fdd7719863575cead7bfbbc9d332bb373ccbcda0e33332bb36bc15a67d700d8c63666deb25619552765dde3f3d8afbb338deedac58e0d9f7784cfabe817d67bafa5e9570783edebcd5a6520f4ded8cfb5eb6fa7704af9fb98cbac9bb461af99661f3412218470480adae7d160058e073310f99122b60842c2c303173f4158010f5b502942944491dcc1922e9a54d1c5130c3006aca74ed10591c9075d3cd186740125092225446a300322b438a245920dc8dce732401865f7b231b8549300d574360603d4c4598c262f99cca2433cd28177e7f2af940256aa02ebe74b4d2f231c56a24861e90f48489912450b213f54621005c67b0d3a8194a94127e862eacb6a6c357ddae8500485a5038c11f74cddfe0fcb296408172ec32a4c09218410c22020844f4addc27237fb9dafbbba2eb391f79c70c2df69d7775f73777f1d3358da74bc533f3fa90cbfe7ce637c8d84b5c8708d85e53a87cab876ce0531e9288e080b735e776b672eb2d6ceb96f6e02e327aa6ae0fbdf85fff103e27929e60a6cfbf8de1be2ac192f01ebdfddcdfcbb5b477392d7ef515fa639b42efc8659103d3d6329b8a7d52c805c6825444586869c14a181326099b0c412459ae05004b6ad90d0154588526af386869ca4b630379da2331dd217b06d8584ae48a9cd9b4452284263ad3252c2779aec1f137a0b163688bbc2ce921aba055878a719f705f7f71c421dc23f7952852a40010a4c60021290d080b9690dd0344dd3340568cd05d4a4b50d3b9a8f108478788444c1644811137b647f36898e0ecd622a16a652406b58244ba653844da11bd514217c9d18e1f2c408e52e0f0fcff22c4f559d2047336c11e5d3622c2dc6d262acaad262dc568eab7e107655599f62ab6a29a5bcaeb6b690a51baf2606520ca5d186aaaad0ec1b737fc75f2e8b18049ea1a5a8c1080a8542a152286772989017f02e84259e6ced0e0d64fab315a55ef2922069a84b357eec355447d5b875db31f78a7db590114c6af3def3e7fd1bea0028140a8532c0d2686035f7a80ca8cc4a91496db897f939e66d752b9b951cd0c95b6b8f394d406de09394308592b4484280d668cee4da5d8c1849d329801c61f2db080a0a7a95d912065e7a6ce0c6f0a236ec2f4a524db57d8ff1e5a53be3f1638bb83406662f31103f5615b73f8f511b864668fbb2022f6ada1ca4c9e3c7644c4dcbeb8ef0d81d82110f0d63bf0dc86c87fb63eef36a0c765e6776eb56de4329af070584fe5a922c55cdfa8dd1923700f568c4157b0a96b8e0744ab91a949af8af203e3ee48e838367480e873dc43804db3468eaa032ff026cd321140e1dd446a5081eeae66d2378dda81bd40deac6279883a8b48d9b0731ab118004100053160000180c060483e180703c502451f914800f638a3e745234140844418e03298ae228c618420030041963084146a1a14107e32dc3d1252b309dd7fa2bc0260079069572cfdd25f0912778bff438acfd9fc4976d00892659e065144eb16a5cc5703f884abc36ec39236ac9c68d22f6e8dfa9c3a2e0689183a8b9b2e418026f1413a23a74a79d2f4fc72542a96905712f3deb2e4531df19e7c0e10e287f6d526f88756e52b727ec51c9fede104e2c3f650d0d090a14c493773849f2a8d8e49a7722596df977ff473ddd6bf929325352d82d02c772d2f10a1ba7c21bc6f6669c807882078fcaa7754a09af00aca0de2b5c291d08c12261090a0f716772ee185e9a3f5fd7820abd449c3d71a03acfbabca4a9aa8ead156be86d6c98de91fbfda227167382dec6ba64cac7a1273009f2c48c16793ba387921bf88293cc582e19e2e058fbd918441e4d030758968975a6bc2a24ca886e9161b84f4f619fe4cba73e775f2985728c8d7cb80f84baef87cc221d1fdffdc0e8b36261c39ef596fb889522843127c9f02c5bf723e22d9c77103d53a8b3b467cbcd70d441bd9043c416a5b9894e80a28c580a7aad37a643bc85460e67af37ace7611ce4517666e2d1737b287fefbf53b37ddab7920e087a6ada1b2dc7a9222179c8fb465cd2c27124757e6476fb7317b7b070896f2b2a5725683da7ebd52634c1d0d732d25de98b488423cb0a07be5e440b8c3f7173c48ab255a8d4f08920620a0b0262ae3858d66f92bfc215a462f4ab9ec066a74c32c4411c77cbf2f2af5bd97ca2a0b8a4f34ace709d8862ad4f5c15a5f074acb7bc720397ea0e50f9b45f845b7fca5925fe750af5e3c4e039c9be96becf7affca4afd7315cb64d276a998bf5f7aefb6d64f70c4e0497604780aefba6bb9580ed4470635a02ce349bf79a2c87fbf87fb17def7793ec92667a06c322b5a46ccf986e89e52c221293b95c33d2dcf5cc8f9fea810e38a4c257d615f359989c57ed79212a9e20f04fd1e045dcb9619677c2353b809c48787f9c1f4c119a01b0aad3a8006a23623b173de268a308dd1f286643599a366c277fafcc6b304e80fd667c9474b61fd6c82a1d998cf082bb8c6dcf29da4957f3286bcb93f6e6d497669a4a4084e54222410d0d1c543030fd0e326c4c6903bbe3d7731db407a623a41a3d7f5b6943a2bb6fe63d2621e6eafc9cbd408e214fada0c4f7c6b7003b9e22c9ac29d851bda1be502af396da6567eef582742cbf80bdc1756e1974d332d2a90507934be2ddb9916c97c88fae15befd0b1acaa06cf0ae97b65ec5104b13e2b729a5e9c117c6503c78c55cdf8e2fb3bc1ae62becfed8a0aa76989c8e6e84e9bd4a66d308826a604d0a170d1dc5d37e88f9a1937630bed3ffd4ca3899990f005511c1d3999bf833e5ad7136548c3977e96c1f9fa6a22fb7f72e12fbbf9534f8bf7960de7e0b86081a2dc883200ef0046000d7b5424c4bdc443b374118dda2a3008007b99b625e52eda4b3d2180355a9591ca6dae395db38bd5e66ee8ed179ce12c97a46bbb4aff9a361f481487b12943c8e7bb34c577a180145288b34353dc659e999fe6142f6002ea0437a472dcc4d053ade531d56f2303a59c22a6ac0f213e5cc2bdfe40bda80bb0f41c4d73525fb8451d0c2506db1cc6a1c0948b986c243423d52ad65169166cafb029e770de7a25c6100aa75cbd122a5a29d5d2b23f92104ab1bc0d8569282a7dcbffeb66251c490c005c6f1c40ffcf6771f2230b80abf8468a92bcd8da023a0fc26269c3d574e66c9943d7c28614119e4e0a7b71b54a4a2b486bbfa9b8e6c996fb8cec6248db170f020a560765ae9df90214c5b1623cb33588f87c01effd151e26bf3b45650d0e2b18b0b1cd23b04842df3e687fa28de7aaca8436702c7a6e166569adf90da6e9aa1c0fd0c65f0b64812a2d1dc6a63924f3d3e2d13d648d97380ea56509c53526b002511e933543b46cb09463ad4bc5c3e4b121bf622f015742bf76835417224e5ebfc63bceb42200f80579eec1fe471a30905bdcad33ec7e36a33b3436a2617246af64f9543a422f53369a5a6e75eacf6b57307d8410b81e239cb9c130d45d143500ece27e0253baaf3f1095eb97acda7891c5755725c9452e5eaaec3e5fc740e707854b62185f74d26b756a599c8d82bb8126b28f0f4943d59a1c5d415d5597bc2cc0f643055500b84a455a768da16ee34a57696cfed198c89b6c509e0e2635c585229404e76f7147c5b6cc73d03a083a78e2c7ee96fff9aba14e925e458618540e171d3c929a094f3c396483ff9632e1c5ad8bfbb611097a85ea23fdfc13214ef0ee3dd06b67d805306a588029dd59873a9a84882ad100b799bbff483b44a54cf4af18961fe9422b20d9a2bfb20955115995842279365c97d77b145dc2d39b4431b753ab06bd573bad8a76b55d65a779238ae2c748150473a9bf3ce516a3283b03092d42f17d0af4109d54cbb324fe109a8998e40bc4341bb7fb2724ed875bfc04aa78cdf12fad6e0d73bb8370f3cd5dea055906a1472cf2df66e9f801ca39d174db1de9da875f416b7b1951d55a7d0a4681efbd7843f769ca8a55a7535f6dcb1df52df8ad8e82773acf407d7228d28b92851c60e2df939a7e5143b1a3eb0e8048ae7cc8a415bc276c6996d73f790f9f08ff254c4b7403481d0902e40acb349ec5f48407b05d5768d9fbfa0e5cbb88be68dbdc59740bf05ceb10d6785981635a8a871956b99dd85d25bbd2e8f5e5b3c44a83e1edc5b9efbfcb9cf902dac53f413e8a43b0942f521e2d6ab2ba237705f6bbd5d8d797e3f788b6d03d87360c5abaa967e74ab22d93fd226f5ca5b9a19875511abc0d883fdb7f060e520ede22391ba4e86ee181bcc5a1347a63ba29968ffcd16cd8c972382dcec61cc72083962a8ce45efad0611d940195a9b97e0b8a162d6e601507107a40efd12b09f455ff7953a34a57137a9cb7e7d95b8dac2068197c3786d8a8a11ba05213fe306bc99c070670be8a0926ef2a4dcebe3d09ac76ac206317c7c4acf6547dd51313e7f979092027c7951daaf7b24298a116736cfc1a2bcf90a91682571c325595548ae8fda47a62d8eea2670e7dd1a5d91c7abb4b299436ef807641cf19572ecc6fa4662979e1bd145efec0c26e39678c4d6984f45e143130c0071ae448a36758acbcf521560913c2600012135c879bb8dfd75afe6f568c571899c22744fdff38d2fcf8d72b90a691be4dcd0dc71d32953a37d0ae133ec1607304c42cc103e2092858d5301cdc7bdcc2c3d4090109071fa09e20142cd062af13d09f1da11e77a0711b97d0df886c21df7d4f0f23f5faf5021eeaf942bbb853d487048766646b481242ebc5e74d5ed648985208dc9c6a61943e7962a625c2d705fabbf840893302d6346f729c087c7f9d34ff97cb9ae3db3b49a215be9951a728558fcc2564dba3cece45a34799c181b5e2c6f7b03f98984262853fcc2269c93857a68ddd8a01c8a065737585202aa172cbaedd78228cd8f54d2fbcd24774d906e64fd8530170dbff84127f5ffec779c7e2a6a57dac009c357b00c9d63a8cc0c30ea20b6dd2b8916eae0f07db64199a8eecf0cdaa0113a8412866cb1336877f0c3d8a8800487888e03e0b0190ab936dae58cbbf610e0ab9e835963fc570b902d867781327c76d5d876268ff6642042c59415be738c04c48f1d46aeafcb171b9a898ea95bb7d2a142e4816ecfb96e5e67eae12f7965312111560fecd486b761bcb42054664986ac5a6781ce7f85070a611eea49fcda12684238fc73f90dc2d9c46ab11fddd48fd7e86d3d8f119ac2936bf1ceeb6b1424bbba8f0ebb8d1dcd02442fb92aaafd9eb9c028526a29d5fd1d34b5ed9c150cf915b81de6034343ff5e5ec45f1cb8cb82e2a0085772a728112c88b88f98caca38228e0479d8d2c4431a44dd7a4b43f513b037ed8a1167124230bb62434bf47786cad4b5ca2809ffd7af6a6c700b042d3c1d74e0590193f4197f65fb37885d86ff8a710cbc451f3cefb04bb7e13a538db8cf9cea37423d8ac44e016d8cf84c44b9f63f01b170b0a8abd869d824a399c46240043293b24c567b36b5a1df40a3119d4d17b5b2f21c70fbae7aa256159a113e1813f0cd272c8fa1edc833da0ab3910523c2577b1dfaff6cc93c5ac8f5ac33ef680c14786c6fd600f2dc180b9e96f63c896e35e43e13a60b05224f8cf11fed8d85b859e75d0341235f73e60b8d385efe0db1ca614babf3fdd073b7ac3eb03e5e8cf34db305188031b1899e9f30148f444074691256d7fdfcde7e7251e08697175d25b1a2cfc6a69e63eaa08a3bd3e925513b10c766c502eb616e5e2a34545682e7f4b12caf7d6a41c81197553d3185bf0fdaf187ca54fe7cc1af5c6463a41ca786c4d60e76a2cf71294fb0ecdb9da3e2765f679e3157bc48c7b684116d5e76a13a0474db2bae834e93e1408944a3377a0ee3e48e066e1a4b6d164a1f0dad493a8bcda73c8832d256375f7c8bbc3beecc3523ab3ff53c4b59bd5d7aa76a2d617df64d87743d5601cf306f1edcf67a742b2462e652fdecd1fadd5ff5a188847c090cedbe91b07f11be7a0c5ead55724fde695667b2748da1dd4b4ccc16f3ac9d1d12e238451e02a6e568299bbc482a695d7907131f1ff76e69876570e9fbc590547d0a578af61018046788c64f6a36c1c683f55c665e5c597f3f3c04149894547d691cd93a6fd3ead19c602d385a55ae653847f812c071ca623b58cb4aeef98861a58432e499e991c33d61004b102443283018bb8ff7420a1e0a0bfd955e79fdaf57c438b6687a7c382a93a16f78147fb2087a20066d0cc3645e4a6c0a7b6fc3428af50826441e7bccbb84c92567935caca940479dbd78b569d9dae0d693d0c83ae597223bfe23024292c36d1e3bd9d755fb4d7d59db78e75d9ab5c06af0de5582791b223606790bbaa6169cf38fa2caa82a1e46d549938987a9ec3de7df22b693c620f3b885c37d2a604e78dc26796016ef5b81a3274cddff09cdf898d84f9e1e990026ffce732c37cb35fcbf8f8272c3613b9a2b7312caa9b7556e16bd7e8ca811c85d8d59d3e358c1b6f44fa9449687a01a8da8878ccd2b73697f225667ecbbfcd166958d2185041ce9b03b0908070f44e9827440c7aa87653f354fbdd4589ff4ddcb27cffe4a0755aa4b1fd51602dfb3c842dc1c0ab78b748c7afd08d1d5418d874921bd0da8b447839aee22371fa4c2ca4a182ad249704d86db449ed91422fda4311018c7da08645f46d7ed8eb207349a1cedf2e4bed7449c5cf5615c16b887f186aa44e69e0b52cace5e8948b190b19ddab7a7fa22a7778589d3e7e1964ad2fbc142063e0adde6750c238a40d39bd93e3c8087822c869c4cd8e12c6d075ed958fc7e06a0172d99bc4328fcc952c60069e14c70427a91c43d8bd5c650cc874836224f6157563aa8af9ab1e1536d1e6967a2d8409959ff1e03b6ec44f46ae70b6d48d1ce93c1b3d38e1466300a3ec8980e9551ecfc90299124b957dd5062cdf648786c70d8c6596e8b57d287eaa87aaa1116e406df1708a2a03396be27ef79338e1498a8e82c747b099b2a791827cf97bb4b70ee24d612db8f6862a8bdc9553d831114be17ba9a1223d6f919e952174802a755753337a7a7ad63e7a2d9e3a5d723a2d530bb703838b20567f34101fd1c40ba07e60ed212f1bb6b0c89f963bb1133d59dfac88f2eab5c7fc146f51d321f830de09760ed9d8afa6c4e50bc02bc560f6d2f026065cc14107471d3b40b4ca80b76a9697266e6a58b239236ee06ecc25cb4bc5e353bee892da74527310810cc6eb7ac3f1f40c3ed7cba8ddf8e270ed0c53966b98797d06c6e75d7b55014c315617ae4560af12e639aebf60232b7f852ec9d479010b3f328affea318075f5731d1f68f4066060977428b1cc935a8a9681b4b144c394f8719e80db9d01f23a18952387d513181ad082e87264ed2f6ed18a4dc1533d0a35997712718bedb972b873eee40a2a2df8de6739aa9e322bdbc4fb71601e03a5a5c05da08f179dd5c2a1eb3076383d052ba2790b19d992f1001eaa70f61f17adf456b6bccdf0aae90dabb80cb0bf303e5fc1ee1fd129f74198490dd877e7eebf9a2df0e6032432bd0699f0229803debc0da4e58ada9f4979551a51f5a9cac181db016d4dcf17090910000abfb5d7c89f35f803f2af27259ea6f8c3e8c2849e2332697a89d5020f46ef07cd770bf1540fea48ab0fd336905e9c347d3379fe41f248a388bc004a050d072f381513f64f7843802f865ff3fd9805a10e93685d59403122f68d2bd8871d5157867e9b7635842690f6532d70b4cbb22b930548640b9cdccbd0ff1e6928f70d2305472d6a13cd4513f6b46745ff86f54318eef2eb8a023ae06211e0f6923df53f7c36574664c1987ed1df5c5adc085e3e110ce91cb791d1fcfd9903b38d3d6615d3e90e75ef81b27de481e9b454dbc9b4994da8b4ce704616499645e019c21d90d0a3daa60760832019816978fda118ba67f67ea0f930fda00e275eef1c1b17d7973726cecd9b75308f9631508f0ec34f606cb92a5d1ec3dc747e44b22fa88827059ae67f32d7beb422ed85948e4bcfa0b9ae1b7ed355a3792101e67619c9f2cba9c440d7f1c51adf1c21acbd7c46982c09467c4463fad0175f16ad4b1130b9ab20671d412274338313d0cba4caf1202674d2144a9501991d4585c8ac48d00cbc0828bfb15149c7311e1210949f74beb65e81a49403401d6a669621b52ce362a8911f04a60ec415980b78fcfebff641e093465091403aa4206bdfc7767b97cc0c8e6da82e97226ffd6fd40644aefec499a666268996d2ee7f27f50c066d97d2118d118b5f754c83632a791765e500002121ba23234c49e17310ad04be4592f31221ea5faf4ccb80af8c14befea77e4d3e1f1e056111a19ba29bbf688736fcfd4a1f04cecbb170dcf51c542ff6a3df4610e173c2d79d0708f976a75f7e1e6d8476294a62af717c92afe490d6221f5d1cdff520681516f47161480fe89c043257c60ac8635a3c57db15fba1d3cd8d71b5af4b121fa3c30e27fd2562d953e22343c816317e90fcb18784bebf259a8a3705fb249c4a74f0e178190bbe24cd352775911b53f09467fbdb3de80c9424a1f73b15e82895886920ee8147b3d1498a4a123d3191e680cc0aaf7a3295bbc64eda3f32fc3aa08764c2492ec5d59ebf4a002b2f2f1d42a6308a00fda7fef23b5c892317b24b00abd135a58ce5701c5d868642238cf32969f264e28803ac71f5483212c94b66bb0b58a719a181025d17f1cc381b9cd7c3c877d236ab68be2e5ca94cb8a3847b7da3a55af18b0dd460742730c790bc19130f33f6ea11019865b1ec1937ebb98378a26a4085c04b00b21d1a62b6b8e116242c5c303bd0e5971eece4e54230ba9ca99cee962b2e89319da81566eaf50dde148327073f93e9a275a665e938342bfa3843675048d0ef8183ab4bc015775e11722490a02c399a29ba427133a25b618f164b496807784f5d8a5f6ac49349d467320e740c9eb6ea08604874f07b906293e8a71a855298917b7060a845e40401ab40ed74082a31a97080605ae7626e256d4e8ea3bf68987c2f0a7b5c653e6431261a0b3266307673cc330c8fc84441abca6d1a95707d621a2aaaee762b0e01cfc89bbad795fc71b5bfef138ed619cffef71b9d05e2c8d394954c5c21a07be021318035bd3f2dd890eedcdff3d024740c9441a4c228651ee48144a1610886d52f07d52f80046620d17dbf58830fa30d1e086cbdde0e8798379629693a9b107643fd51430f5c20e85f5f16084b0a5f5971dcdabe0696c0fe41e1de64eab245d2a2ccb4811b2b2a5de54b57357a9a80a6bca9a79e1193dd1a9a6098ecf8162ea7b2c26a21484f08d32daa41b3b1a1d9826e575bafb7eb5d74b28dd46ab98f57d2d3712fb55a5bd55b955b518b0fc68ac7b9878e61a3c69c629916a6e6aa084e4fc17d47f48e3f843c7e8010eb6c8a88d184400f1bc4198322c1aaf55863fddb0021d3b3c0cf4fee7b2c685617f49e64884b3014e836c0e02a6c59516affa8446bb119c87ffff6b2aaa2fe3cde9a54204eacc1e38d49f3dfd656dda91b5cafe586f5655de7e4658625b600c1dac54176c7b10e81a2dd75f2a25c9743ec394c8347e10a225c2e0e5c0c26a6015e6aef8bb078fa888329d40b450766d639c041d3fddc1048f99e847b0031f09ff9acd3d97455760c55cee5709ad1a40339f879af2509cd68290c6809d3d0182c4cbbd8f611a40eb62ec1c72557edefdcecdda00e3134b5b8e41fec0a003dd2c3506b4bb2dbcf588398ee49040bab7460f1bf2af4cd0d061dc7126e54988505e64f596be25fc828a0c9f1b62cb4f1c5973f0cf9ed439a4dbe68414db052caeefe64008b2ea722b080ff3d0fad241241999f9468ac3bacc28c2776644250268b7c3a4b3906b4b3350a8d141023797459f6c02ede1b74b98db06bf44e30822aca84daebd80d82c32291cce91ac304a80b4869dfebb7151493169da0c419e2d0edab5813aa2febddd1dcdde643e97ebc2ae881074762fceb1dd4ccf9a57a03ac831397509fb913f02663d6ed03c2e03a41aa387fbbc62ef16d214744bf9d96df55f78392a4d25ccc34c0aaa7ca7c4cf3dd47f508391df3e839db57f7b93838c3dddeb73e8e75cdb8898178c868e325028a86eeb271394e8ca2e9b12fb2f172405d6322ab678a404b091aa52cd18978cd32230fa2e4e6f04ebc34faf5bab8f35e4d11d6ebb2640094da629fae19de32868d75ed825f8864108873194c39ebdf25417c4020d0f721ecc4ef525f3d36e4dd63d62a0b878a91a1662315b440f3b1c0d04140012744991c2f3585dd68bf73bfdc804e394ce17c9801074e8b270c616d0160dfb2e0e6435d3020f45de64f1fd55e1b93dd17ba2e6e4a2592351b44598d732114a764c45c895546af728cc919bc4013ec5dc48094aebc1b477d396c4356aa4cd233dc3dcd322e69295e6e246af8bdbf50b410d86136e0e38c5d2ca1b887f07a99d610e5f6b8c4ce27793c1358ab34ddcf63f2b64b65c6ed06207810576fb591543600107e80e49ca619c14936c4c029fa50a4cc2034bc398da83ab6968452f451c833f45b3558207004dc060e77e437e06a17f2c93d4405316ec26c3066b6c828abeeb80ca85c52c88b8ceeb1012513dd2e298c5625a4edabbd1e8b2b495337085df65eb7f05d12e1d03e1234721541f85546141d233326dafdb85a93ab615864e96c46042f700e6b4e43bc65d454b7798df39359e2681b8b6fcdb223d2723449626f106fb9187e043e9aaea82be91247708ef1111f31a46db2788234ffe29800553e2d3a7e73ec9fdf41e6e95ed35c97a4e28513baadb0ddd952cddfa0b91002c46a7a82d802dc73f66522ad3639f886d7aa52f32bac2f1564b819049fd87d7960cba44dcbaa5453c9144df9d684a708056a596309922cae60b8c123b88fd951b0859816791cc1f7232d01ae4fd5d3ab3bc7e15dab9224dc799c934e89e06d231f5bc475a685d4053a9f3991434416fc83b103490cf4e1d882e64fccb6d4aa3d4d19cee4f91a11002ef25288c2c0085cf053b4e7a81fbd1df14ae177f66382c271f78c0f605635d6b53569ffe09b339fa90e3ba63c08a25eafa97f5c554f65373ad254c359e79ae98404b5570793d20f9a92a323aed1aab908769de5c0dfeb13a808b4a58b832f4c6e2e9d0c890e8aad48e063c1b82e04c83e67e34f67bbe963404148bdd8cf6cc0797ae998c84068f3bb3dfbc592af6268bb39cda9b9bc55513933ef62ea18d2b69b4bb5f9ae515f718a77fed7363393fa1097a126847bbc556df26932a3797968dfd63bc928f7c73575c72fcfcf38e70a93e80f9823096bf7b920c2e2cf69be33486e7c90f78c01a75a0770ff0ea613452340755cd0c83696f19beefa7988f5a0772eca56b3db6b450d829854867462465d88a5b278bdc5b75a2dab07cf2a47b4bfff10bce98576ba64bc72cdc12fbd7cf0e675ff41d002582fdcc5ff80d4bc476130ac8fab9729e746a2c9a23782bb57ee515d01f4036042df31593935ede2305d54eb2bfa4cb64aa752a952f60aca5fc8d4fac98175cd697206c7e0961674fcb1031c28b2c9ae9b2ba8a8e861a009cac598eca6a72c26541f219f2a074cc6b88040008d5234f704bb44fa1a28f8b1a4e0aba961da23eb93ec452e8b1ce6d450c01de456eacbedf8845c0ef1ec507aa684d572a6c6d510fa3569066482f40cbccb6d0046227e45264a74139d3a31106d13764b6562f82fe0673351facb4ffffbdfcfc85a30b99b3f2ceb38a6858a7d5006283ce8d2f9526f7eb942a8c886736df899e7c8c6388c0600b4479ff9751013380d0c640ef9445c2e8a9c8d86e75f4ab0a045b12cdaf1fd0d81171dc4396f2b88149f1d6a714e58857f04b954be2d48cd198cf244150c8431abfa02a551d16db246199ac36327142dfb42e57dd990aead33bf41764b8161274801148d3fd506baed5aa18a3c39ca078d08f8b1823c1e0ebf83af0c5a5697407857d580e32573f717dc8a0325c46a8782e7f10af319c230c422a5c6d4f23bcc18e016b09fe90e1a0ee05072fa07e799abc40b7ee9ebc2ce6d5d835b05c7e17db5c1c813d05ef2d3b9aca1d3388293edc512f0624b95a8ca8a15012abf54b67348462b4efa5ddfb56491804236ead4d9d2ec0c1dfbe85ba3a47ed59b12c675e19308b515132b2601bd62b2a10fdba37550f9451e2bf5c01bcf6e45fb66439bd2239f2773c38739eb5c119d26c9a38d6c7475726dc8f6db7acb1bb48d5cd055a7bbf87b22d3bbaab0937d306a408b289155f748d63683d464341118270e86b631c2cc6448e34abf3904d6b27104f80e8762c398e39db084559ae88c3309a52a999bc40be1c09b8ed0c9bc027fd753935ed3cfe4875ec0a2354aee2fab065e1dc10b9f428621cec5c23e4b9058701c4efcb1d81fa68f5a6cf5b87415cd1fea63d5e8b74bcbbc94593373e9aa8552754554da69cce451d28770459d622b7eaa0e057f5a5b2174ce039c40fde8eedee81339981f5116e4b27de56d4d42d1235897cce28375aa95508b14cccaad574859e1084d0b9d581a637c14aa8b132a66c03d1eadac01a2fae834b083c7f606d911c5fd496059bcab9ff82c12da259eaa2d115f395c2a91f91ed154e22e4aa1a8e05e4527c4a19943cfd1c42accd41525586a01fff8e66fa06c876b2e80be2c22f1aa9c2a57860e18f78704999a83257e5686b197feb97f9ebe47494586c34ff91b587b68ce05700b9b72b94f28f1522f0f08cd2a1be22a83fc826daaa20552baf3c9a9abfa88d7313d5c99767482814d9146cc431edaf7849c12ac1a6f7828737acded992383d8622847980e30d116b00e70eec3043c839259601e7378fab8cd9ccda075ba245fb6a1f0b62cbfa3a69b8c1ae753c677f517c8e52aa5e8f861e54264a2c43e524a8e36d2fbbb8b949adcc2ced9dcfd09e3d82f3fd703acc4077cb8292f5ab61cd830c6d43be9855461ab7e2da48f233d53df31c37a058045c76ea4c7a2046f572f54873bcd2db17e78bd488e4af2002949bd5f3969f7de5f7053dc844a28702de30b8b104411c2f8d6bd527ab3321efe83947ef3318732bcfcc6a74e43800240fb0f028e53de04540b80b7ec1bf15605707140b0c57eafc8efaff8182bffefa5627551462394cd8ff91c3f3e1e87fcedd3cb504cc49a93cded15f9e2eb0f0c27732a2cf846c6cc77da5b3383d3c2a50d9d87b4ed94472ea7b55ba2f2ec0fac5b98fba936aab4bea63e791b40bb270775d8497c078d046d3069d28dbda02aa3e6ae0663e83bd102dceb4464e80d0b8d4bd19f33568075f3d508c1bf060ef9bb8a4caa0d15aa6ea98a120a1b6a6ebf599ab1b74d526edf7c9e00851465f26843bac5df8a0a3f604aa6cca8b088ff5859141a66dc6c506be24347086309fb9f161cc2b035475a3424c0ef3eb0f70bce43c280698aeb960f58fc608e662f4cc046af85219b88f460786c5b78f97aa0fc8f3e34537ebf86a2c0b4f5618d042b5c99c428068c3e79a9c7997d35fbd35c0f93439e48f022875a432755d5cec569a6904097a1a99fc1348413bba428fd672553a40c595da1e721838e0a547b6ab1a1f1a08e8d4265f2f9273258f60379ac15bd15077b8656239310758b422ac3667d1b14f4c91c88f6075e742a98b5ce08d1ce57e2643333ce97e42a76083f8f2cbdc484a152ddf43f9b26368cea0cc58987dbc690527a80ea8cce61cd8db7774a6563a3cc92983ad3564bb0e83be9b52bdbd32aef41193da50cf1e1a4d755997a5ede7288e4d48f43876080bedd047309cf83d2f651054a42719f599343978dd444bc6d0f3ec99f30ea4c0cd2de5e2f642784efe7aa4d595e3d6e3c528d2c3cb1f184132500605e51c9e2642a5db48e116f650905e49ed81373d1e2ed86726604938149e5f33a7293a013e9bd0d43d29f594ca0536166e33987dc61db4e0917329cc93c2833391f398e6a4c0383fe4a1fa1f709d9176bdb92536ab646d751e59dd02db2d7a9e0108409e7170277d5f858dac385bd40bce6e0886a7b4b33bf4924bc1f2423e3cc5698ef272edaa02bca8cb9000e4120fe35b43e7e9c801c0cc5da4a93013dcfb665d05f5a954266e20a9705b8799582f8f8cbf0291e681740caadb6cbcdae5eddd814e49c080bce664cab1d8ba040713f0d3849e898d7e36ac411919fb56018b80d403dfe62d603ad6745769b60d0b6046e9d1b4a9c896ece6ce644c2f6b922273abf60e825b639a8034238450f93f822f4d96630af25b68db1b8be58a8316f2313c5f09c6ac8ececbfff4081a9a938da4e1efe57ab3cfe69115911afa49897f0eff48f450c962168644b1ce7796058c260570ba229b8df9f7e85286cf1d38d5ce78b4a9f82d4b448a0124b1dec0f47e0473a5d8d07d9c13f42734ffcd29c450538c8f6c640d56919fc7f230fdbc387130c9fc8eaf02ed16cb74cf7a4eb3816371ab3192f6d82ce8472e42653d22a5fae0ab296d935e647c5a1c5d9909ec6528cf83cf7a4d00b0f74b0e4355900ed127e64baf6836cb8c18985a0bb48b28e2cddd0c83c05ecf088f45fdc814fd915e9207075dd8303281660411f70733f50a661d57d2dfd5a2a296313ba43c7571ee5c46e464826ccbe1a6d8726b67d0a4e56fc2ee76bf0b85c36dff559c1ecf33d4a32b97f81ac0fb3a1ef5fc8db98723f4cc5d2aff315ce2e0a4010112fb97e9b527f41bd67b2a66d43dd3586207f1d654948151cd44ca321e756c5cba9d44b9e8f724344c4e94814d82314394c28733b4877013d1e6cc237e4d74101c84f49ba9a7d00dcdb668b3c3b507e900887d9115aa991fc797609217b6a3de8f7bcb078659a668ec21166c538fe894eb143b3df42cafec0b8a583182ba2c8ae314afcdff3b215ccc60a21dc18b730d59d0d47ec0245c60a65e5440acc76c0dafc93ae373f8b6f56274e079d995ea79ab561fb4c15a5c307b19a617510b3c20c215e3da04ae61b9036685c3b105026d02ab1278da66627f11990970f347d22e47170894f02c2769f5aa0d5f6db46cf3ee996c68ede1dea816bf49158eb9a3a68ea6f613d4c4edd46a9b295637a4c6b2c8c3fe690d61090ad3aa6ea10321e6b559d6a453f716bf19860ca76fe01e8d8e794643beaf0bf322a0771494a2645a95016531daaf5c7a21b4db01c558749e3e159d952b02652d5f81a59fccaf8ae3f280e727c965e1d3fe67ee427e1fe64f1aab09032d7a09f5aaee06c2d4a34a897c1134d9a220e8e0c9b7fb935ca4401aeb6942732c99b8401ff2aca2398249c590f0cfa0d5639e22ecf1aacee33491a5a4e2d78882659dc06d3f0186b09ff8f4bbec1b6e161d164c9a8e3cafa1ed2278a2c8b283a85988884acd6484b618395181c9ba38abe4f7c57aef001fff311fb36504a8b11e9ae46272f810111dcbb5a62478691b84eb9ecdbb514a69646b4ed801f3e8b78e913b89de9cd902244d9cddf12244db728227795977a1814d065e8752d885edf1b4d65d427ac0550bba5417281eb56c05753720dc997c56a08c44a1332f4b46569e13c0568187d8cd7ac97a158c5720167da760119e015bdf1db7d893e0e4a5e40bf0254db2ec91155413d9cfd912a0242e6d27bb974a01444d99436296944c4dee492cbc1a086f250db52ccd47cf478b501332138f086fd367ba991573affd6bc0382975ff0bd10eeba529c6d9dfc12d987ebc49b7954053fecd8d1725f77d75704fcb258c836faf56b7d1a04702903b5a0a7c4c217feaa9b2dcdda821d7eada0da9766cb4996ebeff3fcb283897ae568417b4bda3e3c4dc729a39ec3e4274184316e842adf06e6a528231d82d3c2ddbf9fefbd06f749602089161601e4093f67c4cfd1e8aca4167104346836752cce382ba0868226f0d9428dfce013ed1da11469c158678cd9c8fbed77cde8287702e8bcb3a5f0b12caeb141701747caf5a6d9e694dbb6c24295241a1aed81a7d7e49e22fc0e4a22756e4548e1a222f3da9d318a387db1ab0d8e54a62bc9d2d48a50e98d7db21c18322e3032dfa2d5df302015bcb687910ac3a23708fb32b3128b03f653dae55d9b7370de05305bf6d5e54ee9488f2e426ce857a5fa9b33c3863b5810a1dd8664aac0b703439ca0d038c18b2c63fa24ce3575b5c4f42430088b140e7fbbf1420712f8bf92043c01bc522774d50a33da91a2f22e476bc05dc5d8a308a9829357e1b15477e171c960dec328cd521dcc3c8330ccdf52910b11223069e672ef024fe761a618befd11814951b2015da51ed54193c22d1d6836a6d706f4a42af4ab275ddb9f6ded2339722c9f3fd029b5304c30b07b0fbc0dd092a386b502f802aafd202f554242caa1059f85c7e41fc1d1b866b4349facaba4255b0f849b225b10a513d496c6ce70ba6cbdf166992fb329cf1f5ceed998770d56174de6f724881cdac946eb34d1c506f8ba86cc69b97d90c8b2a0e4400b54debe16e69c6654d2b9f1d36b34f80272169cbcfeed61c5a22f146da42a9b3b9411cd790b2a8b569c7554557e55e555d10b0d0474d75ce281cd59fa3701545170407dc76070bc03e81f6367175d54da3f9dc3d10534fd8869fea87afb0c7f4c527066fb025c2ebed3f5d8bbae1082a2cb5acb1b7bc659ec2ce0f95ff10d8ba2cb1a3ffe56fcd2e4eac18b5216cad870c9b81f4c29acdf211990dff82d7ad422d52e7f8d8b097358a596dd3f19dad9315fb5c51f1f9ed9a0095a396d7ee36c07d1320aaaa3df05d120362dd8490ce469a55b89b0b1bed5fe317edfa1c0e19106046396ab0934af72be7be909835f651a153c2bf208269ee4270067ee0c6dc0e66a52c7340c6d86da2e5dda9dd30d8f7a34e931b7625a30efb196ea2b8b1a1fac2d3250791c79f3ac92a92684107218119ef83d5374e4d78538b497f7ccc8425ea5cd1b7512f0ee1ba2bd1c5981b9f7741164743808341b91cafa456038417a15c049f2169a44e87cc50fb549a62cdaf0cbe85a4bba4af70adb5d479dca07fc654ad1105120b877c4d66f979e6149a7ceb655888f4b51d4d4f5e6b3a82e00bd423b23d334856ed1997562cca85921dfcc512cd01bffa23b5b88053c7d279720057a0933029fdbc049f8ceef897411a919a624198bea23c89b3211c6c84fd77712c9de5b38cf2cd73423ed15de063ead87a817cb85bbbd8e58ac46a202ce112babb60f648eab22a3e9a71e702d8a5e01192136bc1ef4a619d4d0310ac9c14e8d0e80ff9dcaf887dce2795a48416f4e64ae0188a67cb9d1abc3f1e85157bf3aeb6dbd3f6ae40257f95ec3005d530def59031e6360f6468c30a4cd34514240e08a8cc08961bdbaab75590a5692043c17b8cc3d6780d955c4f1792b781a93f45612b932e221210f17429b00b4b35008145482f218abab9870c30e191d0871727ae944c6ed1571978477328fd986509f1276fa6adad31fc978fc5e96db5b7404db0e7086d5b373be097a1390c8a92e617be7848e0856f5a5e53d4376f12c3967fddfe04ff868cd428131ef2444b573d0e7e7899b2a5a869a2ed19da1b2b70453b883e74394feb55f53ccb510e5e0965e2828492022a8a7f88c6f31c5bc6d72f41dca8744a54c562b22401a5dc368d5ae15980f0da0554a0ec133d8bcc023abb40646344e6d17f486dde77ebb0fe3e49ce0acb6afb6e11cf70d874de6c7c2b484e57efde757ac154c4d53ac149bc8b77f414808b2a8b71a3091a81c3710e2a9d36de5747066a9b54cca337b10ea947944079de1791066e959b3192d15ea7959bc1bb39663dd9cef5dbd1f36f20d6fe7b0949c37b7ae135ef57e70de21042b54266ebe7a743089a783920aa537d88f09208d8b392a4fe77d24793fd3484bf6de780b65b2b3fcdcecaffc48000d252076a2d44fde6c283ac595e0f99f8f389780271ec6852f3f63608b47183942aa757f07421b718522199772b50f07580b7e4e98b848e6d5f6d1d784e7b8b8994d0f2b66bd1ea465724f6ab50fc4157215ed830be3e792b0c908a61ae01875698cef994dcf63b8447532893ef274cc25b7a8230840d1d442bc30f3eccce9485472ab1b3ae7a20a7d40089e7d7287048de485c6ade633549feb12cfec21f13e9b93877f32302dc77ebf0bd6f20325c6590236c9c0eaf156777b282724361d3099eb116948601a6f41f5abf30834ed32338f169e4002b3d026231a48a70bef6d6b72cf3df05dfba0b03e4a357ca678b885f3f542c8fc195b52b4ae1e34df456fc42d28021c8ee01c3cc9ec2b6612d17044c5ad99431c9dd7c65155c85b8e0e8760b300f60e4e41e141743820c73b011acbfeb514662c53121e0e8d5e752eb1e8c639dae88638e4ef0334e50374d508e8b31a74acd35801724e8be6f70cd5f0b3c67854283912626aa3d8f2d861a2813752bb55059354e09025da3184235f983808bc11dd8371668c39d49360a4e81ce740d03cf7a4926726b908468029eb280c08f6ab1f32fde9b9beab2ad2fd2217c8c0946e928e49d8e6f901478ab904994568bd4bcca65a92ea0ac847efaf359dcce8517cca9e42c62506ccd0e8732256e1b0250603d41223da7346b75962b6129b46665962307d9563b9813049b0c4d099ee2b31e19544cd33a34392f9a1db0ccd83c7c7ed9598f484247a2d591f0851bd728d13607891ad099345082e1592e7de18e938315063c05cb5cfda080aa52b2ef8e12134c8015dd4287f456aec4b3a842b858e4e7fe6ac348cb0c17360cc32910022ce688921394672bfa1017c757c0cbb871bd85206f64d3c4530c1c6eee1435f61b0356813951920a5626a4a1ba512eb232940c80ba154db09a4782075e87c5cd399bdf80f8a09358b22c0dc5a65f3af50db313913b5bfc6b70d93104f4ea4707ec5c6413e96a849bb9ce589060b725689d5b4ebba4b5306c0d795eb505495ff90675b2dbbf023381e3271c5631ce9be67ef2d838684d3178625ed2d5be716e72fc965c690602f36295f64baaa5507984d45e3250971d9c0f97211b6e67e44a431bf3e7c09566de3f59fd7289ca0dbe7dcaccc49b6e68d8ea5fe81d2b9f793848c2b35196ee684d15d026e802013ca54a2798ada5b29e4d5fba9398ebcd5ab3db9baad6ac6b702375e8d3dffbf6ed5ec7794ec8b28b88b5a5edf578858ec65deb1b6c4c2ee4bde05c8325f9d3ff17a72db12083265590da06ab3f875b0735971ebe7a0048d4767e330647796454572423045047011cfc903a82dde9bbd4338319785fd38c34e19014a2bd6a920f6480c39aa17de8790334c562cfb61494a90dc14a50a95f01a2a82185e63d9190f0caa75a3daeb79f8025848e472da38c13036ce84eb48a79d0b0d1267a7c5ad0733d0bb050603142edeb4a06044d17427b781b27039abe21b17024b3f273903c4a7401957c90709df19a4451e404221ab48f05ac0914e0556086e15447f7e90309edd83a96a7f9ec3bdd37c87e8970f10043441363f3f769bdc48e299662b229b781c53f7d00a7d39bd8eb771662932fddc90d40a2becfec6c0206fff2a6a8ba62f933111591fefb4367cbab6bcc840d8bdd7e052925a878bc1d1369f88629f75c4729e2c7036f652dea484396fd48124f4187d144429aa57e9106d214e7dc212db4365ac8f4e0b3d24134113d1250b3afd606ad1bf7a141affefcd99530f6640e9dde85108b04474ebd1ab8bd673b51ce328dd4e3cdb314f0c8f962e3106b68589df552e11e7903f87527778c3fef9f4fcef42f7822c7ba86ec3e69ab27316a8f96ee6b9ccca6ac3ab346815144563d07b9be14af51fc21cce51dca2aec0910a5d29a8703331df63198139f9004459a0d90638ef7534c9028ed620e2faca92fcecd3aa57cbaadecbc7a0a9159fefd2ee89f793007820d3418991d907e61baa1fc668ee4f8ddff5c77cf488059b9a9b0f5a277b28842588e75270c1e77fe0d9263e940809a7bf95224078a6804448f2c4ea20521cc7b2171de2603585228bbc91a398df4dd927f690972a8ad928167649d34b116bdb020574c04370ed6da5e7f928bfdf263cf1591a4fb63ec147d880d0555e40c022092959e0e7e9f86014334f7afc874940e1f9d15d61db7c8d45ad7698db4b495d238494df84f7074352761afc8f4dc48da95174cd84f9fc02c0f3da4d53e685159ebab0c2a6e2a7af29fb8cd5b555f091e9d20ac014d8e7a32e34b3a030ff6937ca54596ffa865199e49c4e5257b6a5a843ae49bb2c8a58789c4639fd6e769b086f174d0b1f96a187a41b0f5656b8b80237aa8066169d12eb690944835ef2f9160212ec62378a09d43153c9ef9fef570953dabec02a51439575079e680d23b5a8bfe370020ff32a5909aa828f3b5bdb6fcc8af6d75b9f8ae112eefe561f51602a97225b3580264d105ec6fba8dc052d6f9e2000be6ee3c077f6020aee3d52ab39069c4015d8d9355386e7566dbf237d00b0f9326de98cae0fe1c44c4bcf047ccde6a4baf9a7f8b8d533bab5f2dcbea68193da8289a5d068c162002e93047de2d35e4f07cc52b6a65cbf66f8b536b1aba664e41fa7f67e7823b70894dd5937649a7c0a199526f9928bdf722e8aa444c01c8e31e8882a80d5d7ecedffde4637795161d06d9be4960d40ee103a3ca9aba9ae5e4d9beaa8c18b415d7e790c966213f8dd29d4b54ee55cd580d06b99427ead75fed4d1b1ce42a4ba29a9f41039f542d130eb2a331c41ca853941393e9bd1f7596d963e66506c4e06371334b65b79cfb677d61b6bfce635894c06f9ae1d6b5e9144d0c10622a57a934cbca5f6df39240ddba8073ce6fdc8454b74e43fb229e9f7b5450aa98ba4b85c0b14415cdd4433a0ac0ce309c52d7ad733d845952cc7246bdb406ac822d95096e90d814baa71821cb4e39ec432163f6f305c4fb76c6561cb2923dde27c17e5339145b47fb9bfe450c052d893b99e4f7302364433932e4a5e26b0184387bdb403c6e26e646dcf3ea58269f083ee1911b759e9af8d141c0a6cd24a1473194c228bf562426ae37667ea055cd4fd8b7f6c91b77040e239189a79607acbfa7f1038e3187ac3f9f4eec9bf1ce4f04b67a4aeafff2ed629e6ee031d7679774afa270bbb784175cb0a7644fffacbf40438177bb03ee10ee53346403c1a284430bfdd0de66af05430fe8413a26d6bd791c7bf4ebd1acf637098711efa7eae8a6e73aff547a421587cbf508c9b5271e8a5057d136dff739a1fc05f45ef44d553e2b95ff1eccce39b7c5a8447ae1b78a4a6bdb4a09505a7d8f25280ce2a7cde2f6268062a07c8c3985a0136001bbdb602d5713543e8389debcac84df0f156f61dcf2c3e3e462e49b39d3f16c009f8cecfeab0ee65b2a5ef168a9f21ae25e4061a1c2df63a409ab85a4506a2eee736139612ff79044f62bab0a4750e56dde97248eaf9d745346f5a4d6173849ccc8d5f02c2bd55886162c8a795332c2673c7c857f5d689fd37cba093c0ee588926a1f7f5a071ea3452340d41597682663f39de020bcc3b60b0f2c2a3890a4c893d41f26c7097e679a1bc798c3d4bae849079048866861b2305b8f7e9a4a111607c9ba33fa594b7922cbb620de516aa172e6b0d41c033f890110644963578e61e6eaca32cd2e57caf1825fb93bc6f09705f06dbb88160168ac5fa76296042e17b5cccd9c3720032882ff476ff7000ed4dad02d3186dc25bf0b41b02fc53f5a292fe25a4fac97afe7965a377a9fd09779d3e5eed756bc10b6b61bcdd2d354b43ae5cbdc75a2bdeb3a18b6736ffb671d5d1ba2fd8ed4430fca2edd450aad22c253f88805e2bfb44dc8d4a617f37f49bfeadc8b8a97b69f40ed43003ca5166785b30ce424a07026d0563d3624b9e585045b675d6f88af2f2650b0a863ac375b916ef185a765479f0392e82b1fc2c59c513b167383d6704629dbda1392dda331023d02c5379d0573c7b3edcb730b3f3cff125217befbde59652ca246584074d07580746320713d115f6efcb79c4d8398c0a696811fb8f161b4ac638dc9672bb94c7de85d409ebb17320a5443207fb66451e29d163b74618e76c8d1efb8ff65890635e038243192e13aa3d09276358e289e88a8b89ea10c456bfd76b074de4f9b5d62ded9980c3d13770f4e72e98bdf41c30fbbef454a8e1a763b7d66f10cb8473497966a6492e0b6369e462ff0eea3dadad991765cfdf2457f233f7d6124e32dbe06da5c2c9189688a5114ac3d4c5ac13cf3a33c59dd1692fbdd3da7c9a8a9b96b3fd1de89cc9134f3ba7a5d576fe80ca2b355666f369aa8ca394a336b7a320bdd5e6ebc9c9e916576e9bb3d3c0dc3d719cbc76823033a5a1c8aa524ad90e020eed6e5b314867edeead0739daa7e368bd4a5a6d7733ac6d574a99d17a86a33da59436b576d4a7b596a3b46a1d05b1ecada3603d2339ed4caeb5d64a67d6d7959d2caed5522f5c4a29a574aa524a596badb5562ff294b56a980911194ac9504a260d194ac9504aa64c1a329492212aa30d7112c76a3808f57e64d8478fa60c671680016b43397f3e85644efb0b9e0b2db6c7b4cdb6e0b3b47155051438ceebd19cf34e68c9c26915f734d055630d6431a95c6834b4032171b43f7d0b21731ac914125a80c7a389ebaac469d13d9f4353680aa986c8546b8d6a427c35e31acf4f8b9e4bc920e9124fcf52fca4ac1c4824f1a33c82f59c488c9933e69c530236c4d0303ddd508689d6a444492899dfc4738e538ad1112c871fe7d2e4b2f9386363a6ecf264c9044a82708202d49a94499432b36676b3835c2e170fd31b520288899a49b09fde4b930b2945e68cd9657ece897482a04963ce3969d83c0d973f3c0da741430a134fc367783d34da490d92124dbec6b3d75393b3a7bc9e9c8a42c6a71ce5f5a45028f7bc1e54f6a204f15ee79cd7d3d56c79ce2513cff9e6f5709b4fc9b3d56836afa95ef3aa6945535e4b0204afcde035c75e8ff601d2d0e31b24b47cf655e1ba520550bebcc5625c3aa0043de5e95cf434262a86a1dc7b7dd8c205cd099cd0e204d6bad488bf4d90f99bfa7befbd4fb0ecf82d96780bf298fd090e7ffde27ba99080bbf7de7b594eae08c20cab595e13a323db164e5d132c48b426ae89971c300b6584b6262c38920135925ec91f71ee02043008426a1206d0cbc94f08582043e4c48720169a849c9400a8014c8c9c2001c4c216474e9e48c2020c8c6aceb09ab55a926d5e221991428430b088b20598dcbd443232832422224cf9090a80999c7a89f433f4c50f124ffc40e12c2a9c8ca1095119436528a1b12e7d0447d4aade9609218b3c4e8b2717dbc4e4d3bf2ef26c59b98133d6a2f530296b3e8ba40b94c773eb228bf254b756ecaaff2856cefa9defbdd8859e0be93e50d5a2f5e0c81224436d2d097eccf116477ceb2d5abff19166aef8c287232e578b3a8fc67d5db65b89a399ba09123298ca70e5600229685c689ac660da62362c571966dba25a9dcee974ceef7a67860d25b78f5c0d25198bdc9e5d467ad0d57458ab614f81984d6dbd6066a90e101062b94b5d91658b1d7e39fefbc27de1beb8900497f558fa419f66901018114c245dcff51ed02dcd39bb55c9e35ca22658bf49a1ca505b94d8dbfba99f30cef99b42736922993ba6d2c4d14adf48be5b1cee7a08390dd1272257d1cb08cc8fd4c83775b5d84a734787d163cc2a6aa3986c435a5a4899e8ca4b6e400daf81ef837c4f81c8344fe5ac431910276338c2c818466328b9b8202aa2a34498248941248653bf60cc9827a630929e8ee88f112e308ac0782ae3e933901f080864a466a5d3cf34c9a7e9edb125a963a3c2f17a86a8b5d62aa7d3d945e8104dd3d4dedf5376eaa686758cba6a0f5121fab2e16d1acda514a3445a795b25a34396070934d44343f410ee21cad44df488aeda312c2f1db986a8100da2afef1b1a1a72ad00c263a74cfd31313149a61a86b0d323fa7d97c7d3a1efa3b6bab223629eb9d6229e007044ca5b8b268f19a988230178eb9bd763712de18857099400838594297489985a6bbd7992479694357ec479eb7289b75e7126810adefab7e4ad7bef48a4129c1173344009a4a126ae2d4b6468112788626b0e2290c04494f1d5b3a326967873f563f7b58aaf4844dc60f4d56db64ddd0f752888afd8fab0be1889b1394ba7c77810be7eb92ba3648a122c3708c5944c716448496c042a00b1a4a40b20158496104999ae8c5e896d1a2a6fede4412a8900f6d6dbf5226491062644b691921d0905c5c893525a6b6d254a7d299ca199acd43b0d6b476186f9f5254df333f46b91390d93396d43961842501ef1df681c74fb0cfde3acf9d622736a97e9332db616c9a342af7a8c18305eacf826244c4628500dbb89f369516cba847de0640c4a9c80f102230999ba1565fbd2b7a2dc33295545b65e613926501f75524f1b4ed89c3c351967f2d48425f41c75b6a24c7f86da0364be01648e0d9fa3ab38796daba595565aab05a9137db24ecf3c51273379a430aa449774cc64eb29bf6ec1ceb90c95023d3f9239d4fdb90b6af2a88aa437a1fa388fde8b9f312fd87c7a2798900a1b70bdf819e382f1c7a3fa68a4443cf50e1ca90d638b9982872dc4f450bb9ae480c482a7def1a74e943a5198cca14e614e79a44ed4a99337757aea488410947b86dbde0d7db2734939e8ca488b64e486f5518bd4c71c96671ed1d65affb99ea7516647cd534debd6b954388b64ce9469349350c2f3ba0c0c4d7120ab03459c1651a107bfb528c5a8c529472ddaec5b53ae97bb20933cc11b2d5acf204eaf2c0b9438accf10640e94b7d979b48e0cb717e4c92473cd79e1640c565e62573aa350f2f376ceafe79cf34eebd65a2c28161c754ed6306b9a38a4903a66986d51253b76c5624bb228e72862619c33a543928aa2fbf7e55cf47d2e242a8cf260b7b021dade03ba859faed3d3cde92967b41919519eed888a1f679435c13823a7ad4ace9ae60eae0989630ba9837b42e650a7463267883c72464f7d33a23c0d83e5cf45f1c7cd6886238bba165444e22029112bc5eb2079b420ba72cd1d163671507769413e04c128b5520cd156bdce84a44a4891ba85cd1dd9d3c4419dba164479e813623cb530091be229bd22f494a67ecc96640ef5322dd2cc296b6a91d2316b921486f3e74d14e98970ab2bf2a6183386081afaaa62a4e90a11a22b36c03a06548a10ba62435191e6f055ede029f68b4e9eda524b8d24f258b52c5b142f8f88cb4a749f9883be0d67f3957105ebbf3aaf87528ea31dfe6810875112e9a56be0248f386585ebb42ee348c963ad49c04bf77e988180a41ab13c561a366080687fb8bbbf5fbd33b106ddc7820d3a8c73630fcce4117fddcbe604b9060f073b747043c2d286854a67c991f2583de8a42aab168ff0d966e70be797f5e086d27ce5072c9d7f0902019e6c96f2511e2b88036673251cc019d939620949494a4a5c66d69522a961ad1f783dd5034a716a98002080a7dc45041b2fdd032f64e3d54baf60371ba857edaa7cbb1a68b028c256de13364410800e00bc748e881bf39512411e7108f57573268fb547cf11bf9e50109b624c4fe711ab7d7a3ddc00c258c794478c03e7fc7deea2c89a2fdc83bff4400679c40408ea845445373627050802238fd58397ce8dd065d747c0f325c4f79da1fea42474b5836e5eeee0965db493457d960e0fba9a35a6aa481e717e52a73c6ea3ca54990dcdbd837fcb22675511271c47bbc3584addde7b25186bedbcf25e6b6dadaa5aa59492524a697777eb349d61ed30febeee9e362fa90447a9e5e5ea65d5ba9f86e30cdf614d15f93e8d410c2c92dfe418c460bc6169cf51fc35fb5a29a55d6fd42c7bea17dbea9cd7d3dd466bf52c08e69c93ca7b9db22e9d5d6bad1a94c9e0386fe0b0c2a6b47691a758bbc8bc5dd5a2740dcae4e9b8c5fc72529ab95a25adf5de7b6ba594d6ec95b9f6da5a6bd55cdab95d8b6515d265aaefd3b67a2de6bc32da56b39a6badf55acc755e190f4b9e35f33a6bd5b86dd3327bc95cbfb6d6cc6b9d30d8b56450489cc471ae69ad813797a20b558a9a735a87bb70a4e135d5f7691d963ac2b983a250e36bea8e976da28f4f18176a21e6b84d7bade332ceb3365ece53e2a01fe86eab92af732994ddb814d8e3b3010cb28794a7739b95a2a65d97a207e6a62d4d58a5ce759dd763dd9b3b54ee58e917d549a9a3276c09d6e459dab33a795af0c70a7a2ef584101c1753923c28805053acf84c8eb179db658c278ce0a919a454f514c7a9e32a9e7aa6fe35d1c55377249e22393186d1d3536f297982de6e915f473c15a47ed8a1b3e20955840f1c97208f092642b07561eab0e8a0f9a880897be12d4bdbd22babb8cb8bae2413311f7c52006302063f3419b97af0b1c2c8a8071e7cae10322a010f3e312123293bf87081195d09f2d9f2649368c0c5899c7a896483183a10c94174caf825121120a62042048e75f9d571c660a8a4d7e1f74d27b80a562f05196cd76e3f417e8741ec6fa0abcfcbf4585a2f05d902cf1b94cf49aeadd7c33927bf10c1010636e09f1d7e248fcf3df2f8fe2087782d244a86915d24151f19327f21b9a03c124c2ff50e75ecf61ed12e7287b467465be6b1cb1ef398cb637bc563dfbae49a9caab42937e7061b80372d7c426e1ed0adeb1cf806fe8542a60e2598e2490e5a1414b49e68524b94d74f4bc890ee86d613940fc95460a5f544f331075d217b89969c280f8bae700aa6d43b35eea36db2461f1ead11c67e9fc84c3d63dbaae48cebaa64ef477f295bd5ebf1b9a17e9d83b0c6e80a7b06de50df20ac4c4d7485bd82c92f649fe82d7a7c891edfa1c7d7f5f8be1edfa0c76e63168be4c9fce6b1db29bd73d43b358edd26491ded1f76492d938d8faa1fad91063e5a2a1ebb7d519e2cb446f87fb42fb03ac99c6a46aeb06357961ac6e3ba4457d89f62c9a37db5973cd6a5c7d8ab1592875597a48e49231c3f3259cc58bd7c3351235532b74774855d7a12b86e8de80a3b8fa64979a444449cd68d9eda22d3f0b19f28914d980ac74c54c3f1c35b953c5aa3948fd5db3b98e11b0df0468bd81bbc115bc42e9f01a9b0fac6254fecfd243eb5881bcd63dfb6e4544d78420eab13c83d57ad95c730db84d43c1af2680f92c85928e4490b995f0f85c8319e285af282c9d5ca42ecae9634616e3fbd94134d049916b5883d1552a21631f64b947d70f619e00cf37f481d9b63f7a13ccd4457d8714e61e750a0fc0d94df4cd825b6fdf4f98de6b9d449af3095d22bdc44e66007e50e7a84c48167cf6d4bb6b676addfdced582cd11a69e196ecdf1b68034d9489e9e808069bb1212712a71165fa8d62fc7dce8302a13fcee7b6701cc2f43ee61005b14f30887dab5129bd6ac72115cafe45dbd712722c91bb27f2f4ab9467488d90c917d47ec8d2c49205f038ba99d685deb1dffe02d0b7cf97440193058c7c177d3b0cbd93999424795b6951ef8cd40895d23bd4c9b77737bd14ea1b6997af42fef68e873c9d47d3a3ad67fcc0c918b87079bd5e499cecbd768bead5722057f0c909f2250cf2acc19bad1952f08547af107f5459a194822d4a90b6cc0029055470607008b01553fcde9b44149a8e3a73ac00fa5a3b89134014e32c35e1a46f7e578a7c2c588a319d398382f602054f38bc709072c3d367003f9014b5010542568ee0f2d2ca112fa0f19b19194315463f473724f919ea19d8ae497ec65b6fa30204215d41f2d665116f2d75ba02e5c72e7aebba42c58f3de5ad9d6d9bacf0f58ebdd6553b640fca4f90c67333e90529c8a0797b7611dedfc673d36341f3ee6aafbcdced5a0be19194a6e0f670d8e099107c830078db610f9b947d04e9951dc149cebc1b49061a7d967111d2a29d209216a36fb083f71d72032579eb1f2c6951e2c7b9f401985cbd4738416925e8a5b74292d46225cb4b6f85ab85b20048567cca793427bd9e9437889de835b0bb3661fc04717ce6e998c9de09f4a3b802835f7e823bcf496e6eb2ceaa859f731bd24f5083e7384e7a0270411e7da83e9b1883a3086b7ae9e998495af2d33914cc043f4104fcf474ccc078a8f1bd42167a6d3d2d4b969fa0cd679e0928ef10b7e63ce8cd747933e705718338bc3d7f6bae037163253f41033cc7693ec1d9d1e95d97cf35d6b4c7a1750c66ae81a38f769486a588c14f90009f8155e6d010843eaa03471f5fd8655d9662ca4fb0009f819dccf911a47e56c1f80902e039e9ad90b578a73441f319de4cef97f3a078f47143eb13ec6e28b5b002cac151a565cb4f90f519a881a30534d087eafb06eaa8052f85ccfd2f289194927c8633e585207e82e14b6f85cc259682c84ff07b892d853d75bfa0449a22f617df5c9748515cffe0550f84f31b02998f0a479b4779d722762e93b351362080f01287a30fd5639fe1383c77935ef0d24b013b0daf711e3dbb098af017f452b8a10ccea3db6380e633ac1ecab9eb61efc0f92660e7c0f91c9e586250b68892dfddb44ba4242b2f91b018f113bce990019acb1645781b3fbdae459473d43be1b62009a416bc94621291b493a246218f8465e92798e3bb934851bc7e821fbcf452981b9a414db24703a2b1209ffed2d7c4f7216617cc300df0a6ebba5c3b1478a36929f046736d626d0678c339e79b731c5803e2c61c28df03b78e4e303ec1b994042848f593531e9b60687cf1aa1db2e73304c14249e6f56991bc0d225a1332c7e1a87aec5cf7dc8979b4e4d1b863b11853ac29e6147bdad1f2e29238a80d1b8f026de85c81478c375c446346b1a3182cb684029d9317dc41e6e040cd97ab45eb63b55e8af0e2c48b142f53bc24e96879519239f2b9bbfae9ad1e05ae1ed7a6a6a332f248e56533fcd16203c55c73fe0cd1bc0ba2a7855c665353513657d03c3b613e9a3e4a41f389a4458d869f6e83fc741cf167467dd84c14d8403a5e720fd11f5d6c8ca39e29682ea4458dca167dd864e7d10d94f35cd609c9951cc244b9e833922bed0a2800f05acef3681498f318942a787a097b89d465cb5ff1fa0f5e227591f29cd669e06c0a9bba145104d1b4ce659d06cea7a32b623bb449d64dd488af8e99acb92c0a421fb502ca37405f86d94def84ce35ce51e31606a13f9d73ebf54c07e14a96e102d3fd65389d66538b5665258f3e6cc2116716678fa45fb12c5bd4326e03c7efb7e975285be6e8e94cf753bb94d0b491dea12678eb5369168591517e7d7a5a389f269a89e425c99c4eca8c9b4b8f85e94e7a678e38484b5497d50ba2c29135def0e969e1db67e6bd834275a89a02c709e4538836f1b4931b37a68b3635925e59b7363d28cff7a1c2cc7edf2f160a3d2a6c003fca2d131bb7804af5d35b6cd15a950d68a4c84cd37a32db87b72ee50863f3f0d6fb8824c13a0ec71f8fa7d69a57c3006a3a923a2e3d821e71c32ecd91d4a185fdd2e9925d0852c1ce688777ce39a7ca6f03e970c9b8f2a0181cc37b7de2b468e7b401279a5e596fcda7b7de320686d2f374cb7a27e91eb3e9ad6b7367be756d86b66d87dd3d7dc020dd42d5622e7030c08035d1c2d0a2b52fc0d0a2d5c01c39509895434bbece594785e3073e47f3cf557ee3c60be78652bede8937faa5853e3e8e428b3a54e47b820a3e2dda51e543fccce96d816a5909bda3eda0dcde70b4c05f9f5c4d2da0455b82064e47813e7eb468bd877c9518294f01f58e9d28b7fea3772447bd9e6906956ac187ea512ffc0cc7394e478152f3f1f3c17a1cba4079a63c62593bc1348f8d4fb7311bf035f6ab815ab41bfc04259252166f2d90ccb17e25bf28400515578a145d314274a548100541ca6597edb89c2ca1c6a57fde0898e1d2bb1a0464976e933397dfdc9185aaf98df91167ee58491c7646e8a1386d0d88e92a4bbab2b2f581972cadcfa2deb961c01c737c367f1cef4b691e969559e6371bb54ddbfcef78b9ab6930f40ee79b11a9a3bab5ae0f2ffcca34c47963b264075b953c30d0150c94a731ced7fbfbfa0556c03b08a88786fdea320fe319b628c5021c8a88690103072db67001c5481364c8c08829a264b1c1c9ac4c492c0bc5124058e9258ba6af8edd71b22c82b8588a135e9d91c5ebeb972f6fad95ec3858108de0d3996441c4054b5d50b9610cfa848932342c8ce044d1b26035ab0373c60757181c7c180a134410511813bcc244e941284cd21108588185b102041038620af365070854390ab3050f548ec28c910304a8307d354b61352bca8f1850641a2f917ed26071430f4d727d897403098c18d1e58627696e90f2c31d01ca4285e3acfc7047b63280b250e1b828595c2e233680968881c2868d0fc9880d20d7123150d8b00145cc754ea65259a5d539314c9a74680919c215910255a69794e9ced6789d3dafabf1e6161d949ca0256488a6248a2459e4eebcc65acf7377eebc1a8bf20e47cf6f4620ed27b40ca3e75c8327c8cf35ed1c38c2f039e49c07f57cc2c42c91f97bcdfcbdb79f832a79ec180338bf01e3faf5eb9cedbae92fd3df8ec9ce42f2d0eb5507c943c306d3abebb5c7f8f0372cf3d7c72ac46da1d650c8e4c23485a92564c846054a4b0317326446085ad587cc2f925a4f524ebd86d6061b803588aeae6b0056a126e13a05ebab48f63ce5637dd5787b69bf28cf34fcfb824638662eb0163f4ed88b0bc7d48c70acae26e1067dcd8bf2b417b9ba391c5bcc5fe75ef5f51de34290abd39ee1f811c9f56738ba9099b40488574bc8900d8a0fad27d387cc145cd17a9272ffa8e4b1632160c1b9b763134657d72f1ef353afc92e6174c6955e082687b94e5dbde3f9756aa4773abfde31a963fbeb0d46eab87fc3f1eb2ffee6e6e2efd85ab080f29b145232749e72947b9ed2209c4abdea6eead407369816af7b6037b578bd03fbcced588bb7c77419b09d5abcd733a516efd2bd1e02963aa4c2827389b1c1b9c46c6564e1b632b2282063b744327669e7c4d22b9808902035408310960e175abcb5928ab7d60f80c3ddd039af013318c4aa5a5ce13a0e715ab4dfa7815fd6b44e2a2053df52b9e67a0d28e50afc361c9c3983d8e7401c15c7398fc6a07dae3b2e03f33b9f5e0fc63d1e8a0b551d289f66dbd488641a9e401dd72fa13bb9a32345b9430089a50e96ccb1ae52b1b20b6bae098ec894295b1954a8e08cd475b5495bd21fab4d8b99579f4b9e04a84fd8e80e4a1ded46fc0cfd33f45b2d896cadcd42ae660244965afc2f43418380b250e1a86099b2f9b724872046bea68750648ae937edf480861afc1cbda0a14dca921d9c704438224b2ce162441811652b632b830922b62a990a9d9146738a06e40fa9c3c65b0f2275c8ef495e328738e21979b528640119968c028f164340ca4b4b34bcd1ac07c92fa985eb47ac85ebabedb14e85c821189169add685fe9ac81ab6529cf81a560a2583415ab4aedc2f5d79cb001089c37a0d7fc81ceb1c9c208ffe35ab2e843e43e41fd631ce4ed65a4b438bd67f380d3fc21fd4696302280b150e0cb7440c1436c2d8b8a19ebd06fbdc504f79f69b94d7f80cf73e2a2160e11a8950074dc941720688025d59af01adb5b65abfd5390faad6bab5d67a2a64512c99456dcedfe7f54bf6f10305f6a02b6b9d26e5ead662c0ba0fa9a3bf71fe7c9664787f41ef258ebd55b060fbe284c8921e63230c14ce35440c60c6bc9a7208ba612c81a523de709c4f6593f9d8f5681fcdc3d2116f388e6c1a6dbae9e9e441f274109d403c6da78e01c993e94aeae09eba0fa9a37ef3279341bd83b2b0837c64ce03e48a3a8bae288bf53d2da8d32b8aa3e4116f3fe69f8eb73272ff985fdab6211199f66811ca8f1e48799447d80c182eef4aac0d883c666fcf9e045269d2a449d3aa2d64978b490d6d5a3b22fbc8829247dcdd587032862f498a94c6a83f5e8620f535ef3a07727da401871ba8f116a494524a432099b7822c3d0c643e760e879e0aa31602b93ee2e93ed7418c7f8621902c6442bf7012a79b2a2ddc5c7aee398f6ea9bc0b8b741de6b6ceafd7aef39f1a5a5df893726f0b81648e352628cfc0142acca173da85120cdbb9e7cdeadd9c5e89d0ac67f7a4938657c88bb195d01b88b73072b0bc9e59adb5f3a66a5eb34c63d54bfdc3517553de58a73818cf6ffab8f1d3592de668517a074359ceb00653a6dead754dd7a0647052aee44803f5ea3fd64720c058c38fd22509928451c88fb2a6907f86e38f9febd47f6ca5610e5feb877aed1bfe50ff09c71f6f4306d4fc0ca524806cd51426fee9429e4caf610ea96d812438e2f086d9e29c7399b3b9901a5a5bbb0c41ea03c97cd47c03f4997460f3bb85456a70b58a081992a64a165d80310619695a9bffd4d0dac29fceb9300492b9740b32d9864cd7d150d0cb08111b5a45649ad6ab466b0b73d8fc670b2f38fe781b6e60e4f0b57e36e79c8639e0d60fa62be943648c888c1f2e63ba94f1a4f5f3a47d0ba9cb1b1287d21e8e887ecc4743eea4fd907d74169625260fec4157ed1d8893701044a6432d8a7d5d903af0b7bf30a50ecebb8af1dc4490a7cdb6853917b528851aa1c92315a2453b45429489cc69bf61ca54c85f680474deee0215ea9d9132f9f6a644dfde4e2813a9c33bf0463b42f57dde8114a8574d5f1f6c91c7a654322dea1d3a4a399dfab0348221db4969b13da66971a38ad3b7d6a12cd6b7731ca5aea72727a7a69cf4a8348d1656410b99220800000100c314000028100c08c5429148200fe464df0314000b85903a72549bcb836910a3200a32c0184208008000000800801087aaa60800c804c07847778a62af42045c3eefb1395633e29e034b5cdf2546325fbc9c514f576782761573019d19b546730653acc2cc978365d94273680b7ab0d6ba402a841a45626b2a7826cc0148433ad8c14b87bdb74671d85c2fc8e1cd5df4eff07fc2f3af20fe69ede617637829b6f1ac2acea81d7b731e23d615868225d8d30e4a2fd73a9ee00fdb3bb0379f2c623787d6ff0e60bde4e2429651c41054f1b8e5eb177d93487f1a85c27fffeb02c5ee47001de08d4d4e3da1a43b78d0cd36823dfcfd63ab1559e12971e51e16ad1d9aac40d1b8a1d5c36535fd152cdc1dd743a6d1e713dd022c657f81aa1b1ddbb6ffcae518ad2be036bceaf2921aa2705002f800c7774a43b0ce527d08b6496bed3e58b2761805597beb0c54416617d55f1b56af38d66223fbd0a748bfc1abad4d37eb1cb6c53d2572046012dbc8ad1bb71d254c3a3cb7c2e3dafb5f5a980f5c0bcf895fafada1b01c51473e5f878fa12557bf4d03819c452e6f12e2930139c8d49e428e443fa2428e924dc486bc24fa881878a5eb7e4cac4db4c33e138c559a01664d15c9de976c24c17b5706157cccb889fe8b74a68fdc3dc5d1e105186a265886585229a734336ddc1636a4af7ccaaca6ddc215b168432cb21f5cc0beff97ed10668377117741ea4e0f3bd6094655b7986652629084ecb289add5684280d798e06fe7c76f2dd87b0b80c6d881dede7223cf0df9aabd8beb883623568337f4eabd45978e1e8874268ec27d758f2532d17ba33b412f940e2c3aa0f4ddacc85d4d89299fdb772c99897e86f4d6737bd32d3df748eec647ee3edc273419315e634e5a6c633eb08a285b4d240cd05aab26cd03c1faa733e8e2ddf046250cc643a48e2f4b14500262a0095c55191d8b7e95851ca50daefed250b591c12e1f27cea70f3844a68eaa475d344ce97aef58357f5a422136c8d1fe0da71686bef4fecd995dd063a9faef2bda19214a5b53976c43765fec464fd51921955d1cb6be50b676617246b8478ca199345ed4fe1a90006ac71abdd7e67b3b1780ddfa7087d73d18fc125f72379742ea53aa3087501ed05505f77d03e141d17fdca58a194ae6d0c3040397839e50c2346a68db07d05c2dfa9d7f84c1cf3973f67a0f7a382238e01e438756988ad898382dec85da89415f9d93bad46179f09ec8ef6e037a8ac7145f753d6528ebfe58bb482feed01572e5f6413e4f7eb147d9e7570bc205523acbbe61a3cb0387a5b206768dc06055259084c297f7d579bb1dc111afb5defbbf1d15ee906a5e81033acca506cb4a6e9468ed0563273e2d88d221c33e56bdb1c7160e48a876ce33926373eaf2aa82588be50b23e6366c5704446ae843ca862cdfec095680aab539b47e3f445bdf47b92032a3487f5b76d1e2202c3282bc794d2f216d7b16bf4593d654868899018f277eae3f8faba52e7c31aeaae28fdd02f09d4032d3c3b7b30e9411460b3cab5d78d182d63484b6cb23b69315305a9d9a496b9899c4defdcca32ab49c06efa833c6e110e30e0e8acc1ba2ea4561595cef8a4bb8bc6a5520ef01a4ff440c6bfd79a6d175045316452ce210924922315e8d66b83a70966a8f2962788b7730a48439c0c57b50a3163d96adcaf00e8c58928bf5e783eee9f7b9533683224120d8c6fbce093b5cf4e17bd49cd8ec0dc92480420e5ea6fa84c455798b19cccaf848dce911cba782654134648acd0024ba76416ea60957934c9b2586926a42e08bcc6a3915093a968ed2eaf9561160d5b5a798c41377d03a62ebd9ea92c56e65eaa1e918bb67ad93c4d8c0b7e91209736a37da56e81c540a097037024cb036586efa74a4262ca2d657bba891bea062f9026b07ffcb72aaee241b57c1d563027cdfcda26064625d9adcb55825ca7cd8ab0de4595c62f72e236d5595f588094b439dd8b73177fafc8b8518787ffe52df8a56d030a1819e651fcdea99b504db92a225d1202c6c09f9a13c11231a443ff9b99efa069efd92979f4f702dc291b11f316fdc25965832d7605c31193a2947ff72dc8dd500b70802f66daea38bda1eb816f1d2eb0a28fc2411f77a7d5445cd181120e65663e9ec3d12b9b088f9a365f215c0ced12e609c478f0f8bd0fa58d2f5dfecff35d7e121921b90809b93198aa87ba951c80be1f90036629d5b15c67cebf3d496f120e9df73c60bb3b831906e7825194d91319d9ba2b65720eec3e7e3292168157621f2796903e8cb0cd5ad2b5f85f5a1fdcaa42772ee681b7c3a0209ed17218475fa3928b0401e2bad58841677bc944569126e3f1d148abec182d675042ec67a6f1be04dc4716bf184b398c903621717c280132166226e5f549901c16d963e80b4d295543bcb52fbd3e57f548ddfa59c460ddc5a1efce7908f3af2647087f57258507ea2661a66c937bb28509516795888da0695c34e116d433e305ec12cf9d6912e881c2f8a76b1e2e4ecf4742f3118656312552ce3da13a8f37b4e846503f51520ce9d76b10f2419ee8d88b5e4715bad2ddaf1ad1ae58436353cf49b1a719577186b4bd0c48403564039398b9bd6054b8bf13732349794951afcc2adc2265cc215a8e7c17a2516c9a325940469611fc5ea0aed01c03d8d035ddf7fde924dd6b32b8866256f35fca8886f5a4153c445366f61c0bb452a2ff470b0c4f1ed6dd9bd229998be2910743241261156fbb9feda06160c6201a02782ca826232e4f799922bec0f2794a16588fa930952dc4b3cde1ac6a174df06ff9eaf4e46c7caa56040a429ce1a79f15828970cf01a5c806fd636ead0e9df1953890bde4c8fa4639c05db3e0fc9bc0078c67e52683aba9d5528ec84864fbf4e42879d1ac22c28f0977d2c5cc565455db7c7a7fb5160ce50a67a58ad1e6ef4eae88de23df15a9043b9b5297179c238abd060b40798bca8731db0557b35b51c8304a9d7fb63017622a05e2b0ef967f29d1dda46d5b740d2b9f72b4b4f3ec7829eef860d9217b54045dce60b23d4bc8921e5f33caf44bbbe4c05b55fd24d3c00a7f2fff1bfc02e6c2ccd077a2cd8fffbdf1d54245da179e3a1269fee5b49c77123fea85bba483f55a507b8d50f512c4566c2787a59514b02af6c508fcb0082fed5dd2a26b356ba7ef692c9dd18b6e59500b7524c2cb0722b25d082593da662e301b0d6c39769bfaea7f3ab1244e95c9fce51f95c75b11e383cc210e55194832dc2c4b5bc9a977ff0e205beccea0a7bb4ebd82d1bc3a9fd819da5cd48f959ce2c7298ca44bfa02023e4de912c2ba61389de414c837c8ce7bd587017ae64da958b875084bb77c306b8a0c8520dfddce7e072cc0eed5e62364336c02a841efe26f0bfbc79ca321c8eb8ac8935678c599cc85c898673ca03fa93d052917a94364561bd32d3cefd43de8c5656b538d2e151e9bf018f170ebf7f2a8b5a31a79bf049a3fbc9b00e45dce6d81b8d9b4f45e4053b6c741a8a3e1bb364ec15b7718d488fc3c59449992aaf97a1e24d108166cc200a653607ba74325e1b8f0ec9cb25a87c15b4188aa8fd4673e0d68c56067ed2ea874065176af3ed165484b2fc8b0d2f78c80d7aba42ef9d66e3619d3da9d10bb6562604d5adc509b5dad6be2c0205cc915c8f099118c4d0b0bdec8dfda7d266da81ae89f8a6eb30c3e515b8bd846d985c463fad024185eef147a81aaa23b4b761955b027711d49655df8854ec2ccb62206a45f8763634ff0374b5e02737791f02bb47edfb037296d897de719e91d50b92b27ec7d44debbb2776cef3d762408c1466ccdd991d906cb0586f0947fab9987aba1ed5b7beb899f20401f53a79c084d4abeacd3777a60c137220248aeec16e8db96666627e3c4f1b8a5edaaac1535925a8b17ab9595336c800c368f078dd7c497526858b2f330c953a18d1f199550d5b13b709f66255c42c90748c4d884dd8934a82d7c832674990e2c530523a1d9b70f7dad0da3190be9e8486735baab7af0c6a39cbf1f98f67fecfd9fcd81dc4d9d396b7d5862fb0effae3052bb49f27c3c06e46b2bb92977d4be43a32d513c89ef0644447d1697a3ead73d57eaba307b29d884289744de4937cb65864650997a207f7ac5d11c3553e3ca3005e683cb74e79be7b787c31d6bc2bc6fe1025d57cb72623819ae8eca69232971d645c381841b72de279ee4e7bb7f543841c264b85501bc492424bf1ab2184b7d2f4fe1d48450bac1e91512e1e6e117069f66298d3debb42b8999a261bc135446fa74d27219a5fbe8f45696577c87f6f7096c059546712b1fe7156b03d4428cb242d2b56290caccd8927aa18ab3f7db81bd0a6efc765266b76b4090cb6420f68aab7c5db9a54d8c326602dc6a0c27aa7689e159a148f494829b82bd2ae450757536e95c89b6b4da68e897fa20d4e737aa559493b19ad5675a451d27c785f034d5f3151cba0305840c72ea9cf0c6c3369a9abc998f035696f40605549c942c8c26df2591d8eeaf429fffe619f4f9afb78a10e57cf8e12b4298b459565b6f2ab3f7d5c13cb07ca53ebecf431881471fd49b0a6e0371c7266f4bd1529b546855c5406713d2373ae3a294c82ec1034012c956c95889f36afe71b77ff65bb7c0b05071e5f398d55c13537015f0b2f2f4d3c72bdb337addd708a41eaed5b626d09b49ce479e5be7676bd72ecbaf154a11d448ce1571091657c91cf73dd66c58160b61510851c17d90a9ccd9ffc435888c1f364dba51b11f65404ec8bbcdc945cf7b3d3ca1e758a130681879002bd0332d1149fd31bc9d1f2611754cc59d788b2b4bb40215abd0ca033e579292ea907f6b0f30dcc8540fd0ac55475698fe9fc28286208ab0d0fa1bbf09493889ee0ab19d4f7902815334182359be35cce0ba28918a8dba75202a470349f3cf4a8d00b9b765ec78bbca9679179bc57008ad28b77119b8ae8f0889d582318fa6ac6df0924af10399ca8f6c8047a88b651e3892d22d76e0166603796fd69522a3cb88c59ce36312830d16a88e35a43aae866d58d4c78b4c6f116de4fde286a8d9f2d8eef7d70cf9b28633e3760f046d331d15cb4aaaa766d4ee566fd6e0b1c15b29a870f6c335b3de247e267cd5020fb3c365dab1309af01107d8d4d69ef6824ef12d4b3bc5aac83bef9c636a27113e5bd09531089f5e514c09cbe9d80f8544dcf7bb2fa0f4f65548c2a827812dd36c1f49f2a85ba0337b1e278c26358b274335a3f9e632a2f75accb2bff78f45d8f3c142d34746abde7fb592668e568db0224aa35522c4fe6291670f0e0c7ec1b4e829bda17edc6cca97df589ff45b0aea3e9a6f70bd088d6c0959741423f87f3ec210c7fb93f79e393337d8e59f68ac40b652ef684322aeaea11affcc733676310be2860d8aa0183562eb00b198ec628ecca18edc841e533fa1a31b7a98dae68d094a3396b319997e289ef389d09c251f23296332eb3750fbe3c0bc9ca3b30c10aac1af8a89140feb4560004e758844d71fc26693e5020a5d35da4648213283be0862223c3bacca945e27a0dbb90939ce7d91d5e2ce1e6d2097a28c9a3237d32580e3183187961661ff6771d74aecd538be3c0891d26f0bbd9693dabdb1ce18135f2d7db42833d7a390166c48968397abf9c058ce251b279ee9ffc4c7709ccb077c0d21d4c915ad87c9143300c3c36b277eac6c2929d903beb9d1ca6251aeee830fed76d63844d7b73805b8846ddc7be2d377dd8883064528f0d05219905a70b370113a01227cc8fd4463360be2c62f5093bd1f7881d51fa7b71c69fb1bb1f6ebf00504dd41594d168ee702d81b9c6441d31f97d587ed7abc3b70c382eda4e9d3f64e86010196b8613090d3ea64e7036fb9ab876d5390daada4c74c0dc4fe89e2ec0735d3cedc3754d7b922a6d40410e51a47022c6477f036564555ba430978216ea1681fcc3ea06efa7d32ce98ab73f4823771e2c9f022cc84143aa1bc3913f9b1867c4fd3a27c6eb1d9290cfd457907655d44addcd3ad2726dd351e98a0c2bc831745e3ddff79b9d228ad638ce92273290bdaf47eb54b2d457c6df2226981c5b41cddb291e14d48058de736720fa7151ea79a6bbdb90f3779159bbd577b011680c7a1fe3db4fb5bbcb2ca2b7008ea0d1c22970fd6bae591564070006488eb5cd0bdd12f3a477125293a1fc7515329b1c1be54f53999e874f522b4633c6276e5d27e02bcad0b75c2f4bca4529a44b8ad2231b608b280bb7d9d1771d1baffedc9dcd906edaa43dbf43bac2652cc1c78586e520da1574b3161e043e755680380a511748d8d6c7078d2cc0bb699d6b2ed7e2e4d6a2508536462894188f06f570c60f182a92882b643350c22b85eae96ec9cf1300cf7acb00e1aa2e29a3872975be0ad3f104a126e5d37e640e53bfe7aaae15e74393cbd9984c99b293e9ea4636423e811dcbc585e6a6be97db4f9e187887568ebe04ba8e47375044fe5fac39b7446dbb1b0b196530896fc00c0b012fdbe40d13e5f2172b2dc3af6cfbdd62acd0b13174e13cc126ca5b44c341cc43d9ffda0d0cafd4b8a54ca125724f653e4864d51a797b63037f48c261745311bf8d27c410b60228c13afb6c184965469f25ce033550bbb03ce70d5cf28a2f219f7a30ad2cd72ac3ee52dc9b56a0fb3e5d2bcd8120c1260649621968891b1f1de6a626e14d7bb2f8389faa8a9e97838cc2a67c23bdeb028f6862f1fde349915ac1e69d419e5740f5971f559a4be3a2da91338831fa8dda71568e160f06021dd75dcc772b6802c70aaa5588087ff01d5795baedace066395bf32e13dc7477559200d799c4c717f28a21b82945a1a4c4a077e89a149b94d891d2a377a66d6eeb3aa92dc1ecb576ed1241a96814a46da8ccafbcb081b5736d4e90a666c3368ba6792bef17b6e02e5840dafbbaef7df9df9d84fd950d4f0ab930e0d735d93dbc923f4f19341e447b2ce22fb27ceaeccc67dd6faba4550c78b61925cd7d2fd789e7e41fe6bdc7e451032a442f547a4e64ff4dcea090dfbe5eb2ed04f5427df0d8808ae5bb2e9fa79626f4e2c2a6ec2485e54f251424747308560e11a24343e02eff07ec4961221f799614b8713908596d8a400bf91e5d741db91bf1890c3856a7bba7876e1f6d3501850eaf899c0eb76b50fa51b65876989b8634215a663cc54439d5d27a4999d4fe8270a00a2b30046aec809dddd8368885324c129f3a3ecb8cec18db0f046de988643a34a533b52b5dd140b9df3897c8c0aea401f30ff0892df608b26c5e7eb3b17913026dbfe757a3e7fd88bef645700ce3947c6868e369e8e60eeaf4e746c6e3f409cd6fbb0beb404188aaa6f4c5eae5e8ff89312f6a7d8063c3e008fc34cd74f97e52431d42857dc5f7bf86e214f78389e2464658786b5889d33c91ab278d2add0122ec450ce51ec529e307fb46aa7f96e98f925119ef3bc7d836346338d18bf4859e9d67f907748675be65321a46f3312877a34ffeb3ae7500429de8bc423ea758a55279402155d106a446566f0d684d50b77cc97b4034ecfd11a059e4e91e5cd40a7a78276330ead47a3a32c7886b4e3ab94d2978ca64adde5453ebd46df2f766582a6ae39377da521439479d4e70f03bb70b101a21f8eaa988843762525236d4212d1d31785f3b7f16911f897a0b0d6c4acb63fe03feffdfbe821c0d908bdb83813558b9db6253abec82205adcfe71e401e0897f30f3fd192e534f26736359ab24a959baaee44c446eca96a1729e4b900624533c710c1a95314fb106f15729dd62b74b7345dc242ac46ad0114bff663fc011cc2c16dd093413758ad1246c435c796d434123d5798baff894a6c60626b60750c505840776e32000f44d18748ec3b38dc2a4ef87e1ee4f130e2768b38d45eee75eee9d7faa68889bf22295914c9b43eb80ca335ef4a09e841cb00b2ac34573b342d4722eaf837091d6b4220e1263290f006c2500534ca478a29504aecda8bc7e1090fd63d81a9815af311f338f49bffae5f206385e2bd2ab9d6ccea77fb1b1263ecb428dda194dd756b5fb13ce1ae19532d41669cca28711eb44c86718bfae91a382a1e8ca4e1e502e8686533c04aa4734cc189df5c12594c96eee18d21706858c350a3129a873f7c936755e8ab373a35d90b2ef271673359fba688050318a9644a66737e2b66603097e4005e783656d71eab1b5c79c7dd21453cecaf9e417534333e3c4953dec02c44da472495a0086e29a017e0c5fbdfaee1948d5e4ead29c78c52eaf14fafdf09ec09c7a53a6a8773cae0be90d2b75c3be34dc3c36b6df59dc148c0163f3480746fa027a32c953d04996d1c8beaa2fed6c835c9a56675d1f2a24e731d8cdfc809f20744cc0c56fb7e0139769bd057ae106d0487954bbe51579cc42b303df1f07e42ca58a41714cac5d669e43457c7c387d3fc83975088198b395fd3b4965c42c7b0d20e2d5b231d03ac30961d6b9fa1d12e73b7078799f8412ca3021b4045e3a6b34c976b281e327155f67e5d76489d4243eba0150d3fc9af300926debb7d394b6b121adcbc2a2351465ad06b497059430d46f970d0475cf3c8d605c16094316cdb2c6f7f6a6141f4d5ae01ea3f476502bb9fa2bc04fef37625d89614c480a3732cb6643b984549bf623d33b5ba9fd50a77e8f86edbbc83316810b0ce86f4cd2170aeb9d611ec3d8c718a83c7a20b79dde15dadb7530ded64d4b5dcf38fce242582329f9d6b2d70e7f78add99abfe700b3c0741d6ccd0cb5a8a5e913724fc3a89ba16547d2b3ee0c48e36c1a5dda14e740b8955e03debb344bfccb25c1888524de63c6804b546c419fecb95ea41ae7324275ace92a99695afd356abf5c32d432ae104654a61ceae1cb409c5ec0c6e13a513ea6e67de8101046365b749c25d29e4fb1e193c224dc03ec48d9aad7d9687478395062766f1964e7f75243c98db05269174f145149f816113382d8bf337d028b2ed03b98449e48f1c28f3dd3669d9c17a8a27fcd18d28abdbd3a5057f054a8746ee9fa172a9467a1e7941aefa9f38790b2878f2b85c4ac240b06d9019761ce37d8782db30fc6a6ed3094223a3b96efd290ccd85702a4ecb8dfe069aeee384281f705c1a1710fe9060439b05df3c3f7673f38b24fbb9cccb87ad2e5dc84a75dc77f2bb03a4bb700ffa82a017d09b48e6e1373015469c09357e0894aa22d3a0eb5ecfb078acb222d004e8e1b3f46652b419bedf149815b8cd143402f70220a76046c1c3cdfc8b28ed911a6ce754c718c20fed989007d07389648f2e4f85aad282f0ec26cfd5afdd8478320cdea21884a44e618d514eded2d18e5f51b7c7cbbbeac558751ea844d937d7f1c7664dd10408aa2b23bf87dc57f8f4cb64f04bb2108e6899ef2f7b2e449f3e429669dbd69c5ff705ec8d616781a512a602469f1880c6ac596237d602ce35fdc4d9021269a97fb8074c890fd117dbc587d5ac360906cc07f799d3f17abdd6496cc8a9ebaf5b8c2231097ceef6409e63f2a25cbe306f79b00ea9bab38bfaa9bae97b2a14f6a50fb2156c9331c6dfd2950524d1c0108dcf44aba1002ea6a1ea530eab216c731131b482f52ea75415fe8eabf4f11ada7036dd8081523a17b6010b5ea8ef0795bddeac1e7f42d710371f00d881eecc0d7e8e0900f02927764ca33070ebd12a841d6d3495b380a2fbc346423cc4b11a2fa1021bf567c3a8e3e9ac87f02cc16098bea6ede1074c527dadc29a80ed57ce3eb684659039462ceef603a9ec1dfcbc26832f5e3a27c3b7b8e02b297e8881d9a528a02d01fed9b3c1f8792a5f0570c8e261a5a29713bda9bc0fa4c9b5af332d0bc0988a883f9f1dd4b1e0238b7aa34aaee15f23a90849e7be6af8919888128abd00afa80e839852b88bfa0298f878e0a62a39af6140ea1cda4d37c8baabaf07498239a04deeeb59b8b7f8e43fac96c7e3e11ac7d6fb613d26643189e8c068cba9ac075ffbfd3ffefa4c8f26eb34bc914384b07aba06eb957a21a02402ec0b4ca122d3d00de6c16bd64b04d98ada1ca2c71bf3fda34c0cb603558337619a531f4e786224777da08fe0e54e083ea0f6a48c019fcce2edc4732b16aa203c2dc77eb7ff6b7047b524dd9bdb88b01fab841b4c3cad0ce22e7c8870d31bce93967f09583e635de14fbb8a10fe95aa6101dbc5c4c3b1a7a2722cee3b0e94bdf39ee8dd9608e6e22e92c6e2262907f72f0a5a2b7af8908ea9aa924e41d13cbba7cf1ed1841aec16051a2faed66cf96713f9b05e502dcc5722485ac11dbc3889a08112c5f6b8e72dd57905ecf8891d98ff4fa615b4fca0fe93f56a21e7831d0a3eb363d5ea20b2120942b434bf4c3586b3924d375442c59411d8f91790a16ba8f2bf8322b8631628b3cb201b08dc02eb2f54fc6721cb2beb47ff5f62d01ad019177dcf94bde373d58991cad74b0b37db45e97f03765eb7f06e8641305519801e46dd422aff8c65dd78ce8dc2ae80552e658876613ae24e1e082943fc6ec1f53ebf60bf8acde3e5f9ccba39f2e443c190286bb4b526e5fc38c69be2abd470f15e3505e9f9446dab80712ef019abbfdc16a3d5b818225313471c0b45314fd9706126bc85c3bae0b9f7aecfc80c7ab898fb4456797e9a748e94dd08ceff58f18441e8bd41df47a7022239ffacad6a79325f9421d08f4c81c621fdee3bd9185c78f1768aae42f9d6e74c5d4b33fe454a754ccbd5feae7efb687004e569d79af6cc538eda04341dcdc4c140e22ac17b5270cd37b712b6195f3c0c6c299984e78aaf3be5d8dbd785e18b0b58de4ec46038d545227615983a5ba217ab7a0d400b1624d9fa254b7d8e092f06646c9ea557de72b8dcecda8267603759dc58a7ee097566048cfa4580f790b0e9c2a8de1fc05b48ffaa20600a10388a77d44b4829569202fbe10e6e54dc0d4c39642b6b0f3253ef7dc764c896d897a0ac5ce077d7f2aecb22416ec44e3fcae0cf99f77ecf9e3aa85eb7a50a1bc5a279c40a45cd0732a99fa967fc870cde5cd9865a89362bd5af786349e737b70fe5c54f52bfdf90f829a78072007880b2df682906e611ddbab939104299c0bb78c182642f8909a93e58d3d56002a9242259e0a06a674af7854203f2ff8c55b8f3c911a140630ba6832e7ced3799fa6e2e4f7cb6b2efc26327e028aeb52865b8328bf2745145bd868b7b5fe7bb1c03bda037be8b13faaa9c9da5db6fcf055e7f0fd59ddf66006a1e2b33a815bfc0d1ee7df62545f359ae0a07e001ac56b59a019674b2e0edaf2567a35abb0b40c612f65c21af620c1a8783a09cda88c01c908f688c893cc0e90f70bafdb3a80c7dbfcc574e0a0eefa05637be9d9cfc8856527a829b0207d17ad486c881e0c8074e8e807734523d403e0c4e7dc0a0bb3eb84aaf01df34d2c00069a8d3646224a7f4bce96804ca74d02069812c538bc4772b01b63d1895fb7e7a0771ca22e6efa73d2516251da333310f095608b9b9c9a0f8d8b808268e3d710218289a1293df318ba543521561fb57ccd98eef73e7565c362319d07b57a5c67df699719a4ab70e51235c18d59dcc014df08b1d75653369dc16b7102f4cb534fa828efaef19eec0df9f10389cbb233c06631ff5eb4e25547b21bc5d25e09aefc978e98a5af91dd5a748431d774b0a1d38423d271da3a9e1731f858cfbb6286c152241a667b723024f135d2a9436a035c5633b420f38175f6ecffe8ad869a56c14c78f89712fd35c5357f71bfbdac3104642b5a62f134e7fc8e9f047dd925b4d0e9a709172ca6cd3c344dccf0b5d17f7f882e2b5c81c570bf855cda92d1d97bfe2f103ee264332660fa57807a58f9ab6530c7b6b984bbc3a7daacce621642513e85f37dbcd421e6adf5ae764b9b64c2fdc47187192c68f16f615ca7edf987d117739dc7920de9d2b7f917034b37444f488161133bcb609a6b586cb32d41817cb502f42c96ef625ef6ba7045cb4c8d1c10f8cee86805e63056848534e04b01889d5e7614c1c1d87f9194044906db2bbbcc96d88cbcac930bb02a56de121a107b51eb67c86d66db7b59879ed79faf887449a34573f4bd38525acd4a355852e0e6328aeff41343a7bd285a9515cccf7d27636e3a0f0e755eee1bf5d4c8a716e9212a7d36bd7250d6255d7868f75ceb2510d8a36a14d2abf7abc0bcf2e33585cc2bff3784b890f78fea584dbd84d886ceb93d8a1cf6829147f22daa19d85d5a006e01753f484a99461619a567f1729f286b51e8d3d4205be768d22ca07375905853e5cb8c5a4b3532625f0bb4a1563df02ad233fe0f1e3e88a791ec4e0c07e1d621a9f052c963aa6dde02df6722649340088c26c83ebeb53c49473d0822443767526aa422afce8852fac6080dd90ed81b0290d3f1d8b8e141ac258c26dc469736855636f63e0f251fa4184678bb039ce0aaf5247a5aec47d93d17344b0d701a1168df0926e7551c847111ea00f37ec79d0ab674b5c01a2762198df803b0998561ff956a8114ddeac273b53c808c5774c632e26a1344608e71cd720d901db9334eb6e36e4c27773d9cd0a40de072cf3bd71afef9b1bff0d01737a8dab3ef9dc8cfc89956dd50e5eea663b7e266ff042b22ce2add235ea5628469ad7b50eb988cf020d80fddf977df93f1031f74994cdb4d88c3310197406d0fc1379e3b91421596465040bd500e275b5ef1f77b4bb105afc2c4684e0c6d6abf6227f48e8057c184bf7313933141581e95489902f6f0ea86c643d9ba018febc98694093300f851d8ff52817f69c805c1fe3535bd9ddbbfc400e3c217543950a5c3bc99838e114fd55ea9200b228bac4e9d20d87c15564f00d143d1e00a45954053da15242bfdef7ca2cd69ff60fea8896ff9a4616d6ae84d9408ab8733a87a5e3d642e6bc92e16ef57243a359e83c51d5adf6cf5f51636c9fa8ecac699b2ce78669db8ebe04f43f38bb53adfece5ef666251bd4795bd689716086815ef76c0d8d228343c097efdd4632555de08c69e98e2f91738776ef88d203adf3552b503fd4381385f9f7e7340bc8381da712fb952709199bf570b45069903c77dcb608526e8882724dd1a459affae4d999a1267be00a1f2042ddd899a028be5331860f9b39d001ff509e243d9e636346ad4189c4fd9351d06d4ba128c1c4e91df7f96fd71859cd07c8d378927d0d1040a31184a141722cf8e96d72cadbec660c178944a7106cc3a1b97c824c741f33d137503c1e55575d538db5032717969c7fdcf89d5faa692fe9016f1c7b7477185a06ec6ed03a660b36ec115825628b22f8621fe895c04e36d6f97fb523295d70a2eb1ec2a1c5f8ec2fe9dcad70094b63b890b05bcd173407f092dc8373f25c1420ccde3bc5084c70d5eaf2b2b1995745e4cfec75fa6b882fd09c31e5d36e2a57056efa4d475bc18f007891f83337fd5a9cf262daefb57e56d830562622265b29067fcc42e64867fc71501cfad996841e60f10244b41ee01622fbdbc5615050b9be2b08b4fd7e4f6c06227f720a8d02c410595ffe8cc4aed707f022ecf917284a8d3e871c50cef30328f69c5c526138b14298070215994634fef1cc2213713e90bcda24767b84172513f066652b9074f34a0499a6cd0365769c1a3ff5fa10b3380c970adead64306c51cbfc2d4c44c8c6f2776028b49f11718466c3861e40cfc49a1860067629f36d52fcf1cb222958c8c65269a947067662bf24082adf5d0f3fd9d34938be4e22e1ecb8453df2dfdd0f81c3c52ef28270c1b08feada6f9ec6ddc177b2f24b515021dd74df595bcc35a7aa154bf6e770161d2969a54bee6c3dca42d7cc444372da1542581e840c4c729259e51fb6e753fea441b6c4197cc6d4d57d3b491d68e8e4ad2a8f4d10f99b930a28695aa80c109982d1540289cca45f7267699ac0ccdf594598d370b185a117ac3afa9764d9525b550d12dc6f2143d41137b21f1ed0b31fc430d934ea2a938fdd60ee85c742645f24c6770e3448b59e7344b6a561afc7a76fc1a2efa8da16fd3601b9a6843fc83990156f6746e81836fda5fadef4c52eb54da10792a8e6048592d0b5f7cabe31963ef48b4f0c68b0c4b6a729de7030fc0596904f1b0e25419a225f407768e8253a989fd4306fd71001408f80beb6d3e7bc30755cf9ed10f0a515cb8dac5d4a0939ae50fd5e7fbf71e0fff878bc88e040a46f7c47439f9dc6e9e8430572da2d4efebdbcb59a7df4473e9c6015a84b742c519a8b5b9933a58135dab4d0feea5f0efa413d57737b3dba0de981dfdf23aa51e08db8aa24a7d68ad2caa1283134045f444ce97473271c76572c90aff2b4f10d006149818047cf2a900ef0ef34a6406e1bb9f5e438040a6c27e2ec197c2357c8cec32e231abb48b20407994403688940c4b8d6a0f8470ad695d24aa00c083992857a00d5889d73265894a0307bbc546ba01bf17feb60e2dcf4123a721da5d59f5a47e0d08d949d19d20c5ac7d694b6417916501c89fb0518b0c5ab0be8b3d8f60f15eaadb2a65511273255853cce06e7616db6dc48e79c06e63492d5243690d52ec79a66a0f1ac65d5ea2737b324c0a8a1c72a12768e406d54f8fb5419387a270c6627bab9f3f1cb00774e639333d696ebf6666699a574a283270d17d05d68c8e2eca900eb5e72a7d0e6dbb5a7b23d4c2e99ed7808131b5b04cfa281fc0d38cc5677e55fa3eac13ba2b325f887698d59029138dd3afcec9f950c6578e573973e04e6302a33c7a0a0d10544ea43d5f7eb5b5badbeaf63d4fc2d636e19aecc54ec6a12048226a878b66ff7e35c114c438c0423982df94ada9f80506e3aafa4f90eee5220030d15b746d0ab45b37ed63bda7c0e7241a84a46d79a6c5bbffac788e9119845b83364aabd5e2c1fbb8909f23ffa56001db509ffa3ca57da35c7ccb14d1ad21f89c1735a016407d60518c5b6bbce4e79dadd126ae0f883d80723f9e2e8ccd75990b80529c04ff568059b527b5c82cbb2c14851e7b252464cde9531eddd886ba096c8a904b516ea01f06f80e682234a1071722cffcff7dfafefaa73b2431b31e88271bb7cb93a596e48470ef32d4bfa7486d904fdd7babd89e1756b850ba6318c68f05ef45c6b92ceb47833f2f81950a19ff39c0f1042be5a442ba6fdb738580713b0058618b4685e081a32c8208eed57d50fd0bf271d721eb5a0bec1e753066be82714a0b93aecbb729d0df8ea368741ebec11781c067a6d48821822b370186b2e1e6c20e7b2c4b0e55e7d6e1211a3fd6fa81adfd3a7fd114cee79fd75c7aea2dd2e2aa87632840b2c75d13a4631a87d73c7fed14ebc9f4f604d22c1408ff7d27504fe68e4d0e8860daff1b9cfcecb29a425a63e004b454deceaf5f6e94b49fc775400f1ae021c6b582774c18d280c7cf6732e455798c1f719a3c42e6adf011716a38deb6ca293a57a1d4a51f9412851db92d054f22edb91dfd62e6b52be3a64f92521238d6393565a46cf0fac72f38a3cdc2b60a6ebb3eff89b1b5db544941b46cf64d68ff555c8653cb86ba6753b3e3e87bfcf62aa140be722bc452cec56fc8647d8b01edc7071ba5a04aeb598a2eb5067dc4c375cc81f553a11d586f9bed470db9916d6431708ef4f3f162549b0d8192f36159f20235cad552e822d6f27fb9ca14b1bab94dee7cf81be67aaf913eff0e512b7904afe179466adf109bd74bc0c105251b91557f050b13ed4185b69292a9131b03298da5dfeffdd8be5dda7f65f838d0b6fec298c8c445dfe6421afffc3ab88207974af4deac3c9805243cbe41a85cecba93f8ab6d2ee0a7cf30b091fc739fb7850d190a7289a0c297207f87b159a0561d55400fc6de42bc18db19b0673d45d1fde93d6ad557011883186c0c99f01439e51a213fea6958e784b5d7a89b74a64910c758a38425b9b9e6891c32665313b825909ce7c5cc3498b061edb562b8da00b0d94e3112051023c63848087e41cb9e9d5ef30e6dc1c772c730e92c36a00fb5a76f3184113b3aa680459b4e4ecb6575644e27144f46c93e9c2bb359428dd05fac819963db353defac9ba605f6506ebdadf953895a60f23cf55c2ada02c20c16f4887385383602b23aa16a6b75940d19b404bbd5a2058905cc34b70dadc14519a119bbcabd533f47de6f4756944966a96aac379460aa55231692cbbbba35606d037561a4769f19962d01dd2de5ff4b17dfbc4a04cb20ec7e3ebcb0d8326e0d77e9bfb3ec0b17dec52dc2de60b47c1ec360e0162ddfc82018b50962a7a7efb97cc038347646b2ef453c6af4a9d3b7c9e0df8a5d9018dafeafae65d8b6022d0400ea512bb80f3a3dff9dbddd909c792d289fffd196ffcc3fdc3ed4597e182b1cacc9eeb6de434545066cbdb5fcfdf0b28205170da8032a104199b5451e6a65ea15c6898e681531dd096741dc3546f6fcac4cce11c429574065544d085c2bbc02807993f386b6060ee0b56caafba312fed026f524ceadfc55490d8f41160fb4ed3d702359b320c0ca62944a833e344a8d24079ade725b853b3ac979023504cda981810cfcd6b230540da59783ee0cd4cdd773c16b1aa68f52bccf825fc6bf701c73b73522efe067ae4762dfbaf4b6823c6cd7d522a9b84b597283cfe5ba748c42c1cf9227d8a28d37be1843c54e91e2a7fc5cbce2f689a1b79a4d88898f8afd619ee6883752eee43233b12570db76eb9fb8791bee3ff20c7b5e77fde9c64bd8e8c3146850cf0bb81d74f72ac26303a866811da35388113158a891cc2ed6746a5581aa163ba9c736031e37b54bfb6ff00b424e50102554109567aa16b814cb4ee905800c820287c1512bb241da79cb4f5eb276579a83746181a071f6215b3bcdc90cd057e17fb06e43e19d92ed01df4101d43306b60b4aabaf073e4d10a7844c0d47ccaa06b30fd437488921521de8d354da6d2af9ff5873e4ab190cdc9445e4d629b5bd56d866a7d292256d51804be07f35c336a2a53910aeb12ee3ca68221e00d3909a87a0cf76a55d4140d9c5701eaeb7d05c2475610268207ad0d674f56db3eb62d8e89d800655150174b8a4cf6c747124dc15c9798e5ba0dd62a04e8e67b82f7c950dbf8af6aa9197382704a911aff2e458ab934fd7fda4a5620dc02185e972c93cb2b8a8a8317fdef0a9e7ee03f1babaca7760a941114f43c96ea71bc00f532a6dd63fb497aa8a4421c63eac8ca722f636a27cbbf94ac3a81f000ceb3b97864acd43c2f07fcd2b55c30507b2ef1481f6387c33b5902175de5b8baca2be6f900af631c26ae1c93ad9404691d337188555b4aaf9a70820e53ee9caf1b2f284f62908287bc1abdb7d55357006326a6177890a79642b0a270e94cd629d65e0a1eed0e8d340e901c3d720bdc3a28f01a1876e26db30e0d0323111c7f8b0bfdda65e88a6da16b9af2938582a1a16e9f8eb054a01a50ecf05b2dc5a7619aa6249d865974dfa1f2c69c5524029eb3a05ff382b2573c30077485397975a9e6bfa649a4a8072b7ec42349991b30328d330c6e111aac471c664210ca01ca57db6ceb3b01d9c3196140459b42f70df7bd24d1bac7a753c014e3a407df854e157e11b833c19e613ab7e7da017e16c1ac9bb1df67236ab56c013f48504c3b2dd19931384cc055fddbe83351d074af7da1119538b4332be2ba8406ac94c7cbb2c7e98cb68f08265d17f94244c0dd88c957bc58cecd1313bc1e0f1e3b2361381f1b58920d6618890860db7a321c8724c41d826facc5c56f9705b41d3efda5fbe0f7ca3bdb93099250e495136d2f8211b88202684749f86f3d26820c6c7b0c367a1ca3dbfb31f80986b1c3389b4872593d31a84e6a25b131091974764e01452fb5270637e41bfcc5aaba6632e164e68a2811ec5d17f6b94cd79128a9512b3f2f7dff1f18132dc540b36f1f2ce798daff58ef873e1209e6e2ff9821f26101241e8f47ba58c52875d6ca85dcbd1b72238b540bc0a9c4c41ff33868aa0822c70a8870b08cb5d80914ef160a3378da4c388d1a7335363806bbaf1b0e9cd689327dddfdb327b894b091457748d3dc30e605d63abb2ab27606473b94801791e3d9d0692782546ec582337c490fe09943d3e99e46371c1f788810f32b94acdda5936304a585b3d8ebe64092d1b8f6249989785cf13c6ea70bbd94eba8f281e38bd796c424dbda62fd2f9801690e16b6ccd9b3ff9ae9897b245294ccb71f25effbe4f0256d3f514e532794e792e0fcd5b017a4a02e68b86b0d22a169a1d42b7e20da054ba4f8c4d65fb330f729231de9a258a44895c2f926e4b9b4d16befbb2ff1ae9147cafebbe32523788f504bdc1f95b9f0a5e87423867d9e2e3ee6a44211021d46db7cececa656ddf6338ea0c77719217f192881d7cec3f3a26eef04cf2b1937900bb38f79f45bc95777bbc11636232d8e198a9330be3ec4b4677afd2342b98a22d6285279b49b2f6372f259f84fc38536f3900a4d63351744894b4ae9825da6ef650e1dbde149230482f64abc1250aa1c51c4c304db7323de36c3e6a78de0a5fa496246ecefb657edd048221be03d15baec12e72e34a1727947a66acd961c35368361717ecf9177ea0f15bf4eeb7c60aa1a5baf6e1b4a2445842cc3f8b2078b62df2424e59ac2773075a749661cf085119f8a0d997c8ba1efd3b84aa090b4aeddc42c730500d295c1462e993c7c63b338402843a307e16b2409a6b4bab98505dcd4e53ce1b339ae071f938aacf44853dd9b66c17d5a85e81baead308543ee1e8a1b64badc316b1a0b250d36b5a8c992c45379fda70f3dac5382b687f470b91640b2aebc55cd681f9f24df4b9da3303a61fdb1fbfb1efb93f38193c6d08b48d0946a2a7190202d08b7f65eb7fdd1567da5541540bec7314a250e10a1b2d4b476cccb05764acd82858c857392f532b8eb1de95a82c9df013709342ece1a1371c210791ac5b45b70e0c3f0cd0ba6048e413dc458b593450e32c63edce10ac9ef21ad1b4dc825c1510c4aa3278bea3e8bf7b9893dc9fcdeb4a510b65679f608db85940bad7071b7f83bfcb61d08b211e05b255e8b022ee90812189a69873934cf8e94f469cfc7a7488d51132ab8ae55d90bff0d5f9370ea6dd847c4f2d4d54c8fb6825004dbc32d869a056e9cf3cf3d1e0967579520e3eac734eb2621576dd5ab1ceb9fbaedcb225468fd3df2b473abc7578dcc33786773c1da1ebfe2d2108018ae2f93d957f72016bd2fa30eb64c9b3b9df3046edb284a9e86523040beca619afbfa1fe873c786c0f4d1afbc71928f58fbd1bd3ee2f1ee929a5181c64fc24a351d58b5567b4cfbf0228ec373f0a15834f66191bb7ec622ba406f58ae6ce5637e79d7bc83c632350845dcb8fa14241ea1988f0c41ac20ed17d3ab20e5b721737417532e98891c164717f0ffa66b1f8473f8bb8a6d5d2933cf26b46420f794acdd8e904e22fa4cdb530d8c43be850c85ec143b21c3a6faa21bd41078a8e509e9fa09fc48d5747f37652790bc6f29b60adc9f0faebe7ccc21a69670c58023355c4da98852a2f9ed027db7ca2012bc6021ca6b6fea003423405a3c3330803e7f0debfdd0e7a568ea4e9ae51e930dda296f42ff213df04fd1a5420e567125d4f27dbe5adfc67f4e8544f78a1fc3c78507e5292bb970d1be158484d394607000656bcb5f27a830ff7cc4fdce386ab7ad67147cbc66fd57d56a909c3e850a4adbe8192bd1567310673579364780f5d7da7a17dd2ad247b35580961ee44d288d14f72c84167204f455490aa47a39f955b56be55068365f87bf1307b3fab238801fb59ca41283ad2c16f85ced17dec48590f925ee84396ce2ad70ac400381ca959ff1f4129840a932daab92112788bff75cbcf97abe50844bc4166fbe97b7922617b5dd81a91b9c0a8f46823cb168a75b5eb9aaac37e6260fc40fa3afaefaa5bce19e43d1575e4a0f9d332a29f123aab1a590182d7dc1c5624de05d4508acee1bf9ee36d6a33f3385f347f4a885be7b31e0d4a6bc8249a1641eb43f1c1e6994a2ea3b16eebdec04acde26a27d6aa9d7d0339968e7c8721729d85537e38118519fae8a41c965b23c0b7f5c65a38ef79dbef49cd8f975dc7f87759560ecbcc4026540d90a142e953e571f25927fffde1891b22e3c07c1861cf1f37759230586b499a8f4fe2fa31f5645eedf1841ba6527a492c42d6484b25b7e0c2dc4bea2a70e13459c34274e33def0535ff417a851539ee782ac5db3621073d814cb7acaadf1834c73bb36801f802b4b96acbfa4c5004fe59d2bd32a24576cda0f33b41fdce3ab5eeac4c47c8419b501e22c94532c1899f50743a4adb682ceced311479af705b43f718e08946112c486232f6de6ec16569d1da3ed57174bf973035b52fd4d780b3262a732c1001c35faedf21a1d691354bc3662ee43d9453e5d393070d32b7c62b9f14e0ffb845a6d0e1eb2618d2e1b91d20c36d601620d4c64fff067b9c54891e30824e5f77c1944e8eee66d624d69e5465d667c326a9a8ba9a2a3919ca3bcf9afa0d70300337a9a109c3b77e52e07420b161cb735f8a55dc7c695ac5cb8a2bcea0365560c74f8160b10e879693db13a131f9cb6094a16b4076ba397c4bdcbc011189a6dd73dc7e9483a42ddea5ac11a7b3c0aa9655150eba55a3ba56aba086b51585b9037283c6b65955417cc8a6b514fbce1da452ed2aa0a83fc019c85ce05de6900191589ed772623ad73283a92fbb9b07299c89dca21d452b6537a695a55b286fdfa2e37eb4dda913c7cdc96d8042659533c9707a72ddf0bdd652722739402817887bb96cd8ea77fedf9da12e51aeb58e837b41002d4b7f563ae1f07f355dca70179e36cc41e55f0fde64a823708ae088a69694d4cb5db5f756a26f21b99cd56fa04ed050dde746f06162869244a8605d766074d590e3b5d350f06b68d731be29532bd24979080d78aac31c207fe53614a9e74cb36e5f8b2e5a41bbfbe0783004c77a0a649e1ccdab450f1544688da6ddb016d10a819c2793158997924ecef636c34c36427c29f0648423a59aa0ba9860594c1629f69a5039731c898fda902e17f5ce62e541b56a84c6917cacf72cc67a137ff34927c58ff84b6c8584c68141b6d3fd3a7ab403ecbdd08dc1ca63d9ddb8c2de8e7a9fb6f625140dd123707110a326227ccbc7842bfd5aa9ed027fc155a2ef8da030a6386b3312270f548eefc6c88eb20f5c46663795dd9631dbfe45575d5fa9035af6546b47d679b02036cc01881feaaf032c5b23241b675b41801d8b37be5a8961849b9f56c79e0bda89d700f529926fa3eab15f7351ab8d67a5fae2f6a71630667ced0ad0f9d5a754ef5ec8045c8dc438aefe8c0795557b2db4a93ae0b410281acd793a37b0d66697e15c1ad2f244d537a554bf36d53e160aa8ee9a77eb2053a3ea6ef5d435bcaa77ea8c29e9a373862d17038b3f41b7e342cee7b9e180ce21ee0dbd5838e7e989153353138d77d4903631410ffb91777db7f5b9bebd5d66aec6594aae2ef16fbadf63cd0ed9af01dc57b02ed7f489ab03d5e2eba8caf5d04d511840dce48355be0da1c8c5f65caeb8149a6acb1b59b50a4d8903868729d148e8d4274d7e91cb16b59e82ab10c8c3d714f4f54926c3ac55eb97b72d4ba78b72815187fcebef017dbeeed57b062df85b7df8f28348760806fea24f8c1600b6e5c9ad7e17e32cff29d42cfcaec2457f856ab7f30cd1ae01d321914ab0733a8921566407954b9fba629c0448a6ccf3187fa62750b70c07d89db7702be336f1001b33360acc7a05f2cc374a4f70ad15ff34c52ded921bb5599e5b86ebbb1e41b3117c465946caabcd25666fd1d7edf75c0706e2ab513775f8c8a238c49eb3b0e9c6ae3aba42f60094b7c7cc8ec07834d272dd997f7f44183bab7f6f5178e20479b7216729750a18ebf5677e9a1319e0b0a1c22d9fe55adc259ffaaba8f34f3b131e006b8ffbf47e82d244a6804293a9139147953c8171e7b0be689c35ff633e54ac95e6719ea7ea5cfeaad03f31c63a8dfbe06578c1abc8569ce1f9a00d1ef61d165e0d3e3b347a0ce03d44f4335074d86ad6e6a40f692d9415a01dc76a88ebc9030d41118f33be2ac3146de9345858c0d06ae288f2a7b7ac75eef79d35c84d1a3fd42b5cb7faf16a2a9eab9dcb33059ab867ac513bca348e091af38ee6ed54de90dcac28c86168038b5f599e713240bda2c40402984ace1410fd9c8d4e8fc31570865c574290a98f5768761ed68258807aae184709bfa986db86fa690129e9305a74e884df6c5cd5b45bfdbcca3aa3dea13b9b8e982e1805d21044a969a370889cced618bb32ca38ee18efeab32246355104e21a51ade50290b3206c8cb419c844ea497bff09863dc0adb8f5c3a025dec5e51ea3eec5f3ab824a5955cf590db9a875f9dbb52e735b634d8cfe5f28615474c354f346a0bbc22ab5dae87da94742ea3e7bd28a3aa8f7b0cb9cba91401092aa77b608d3518b2b04a7f1e93e065be6ae931a0e7a6eceb93ea78e57788bfb59d3b2b45ddb286a513fd0a48103ce416d08dd331f771526473435079441ca0a060e40f393b4b3fe77e16b8d6bc1c94b0800756e852bc0ddffa73e8c897dfd3c332ba21c507aed811da3d0be39ff6597a0fe50135a74eddcb7c4f68c863cb1837e4a208af34d561e0d84a7a45fe854b961d0fb1492db9862ac80f5ee5eccac19583283524049dc578b0b8284daec6181e1d4b308b1500584e5ec0504563a1de8fc00ef25a685101547a2aff6ab62886ab5811f4fb00e2005dffd74a21dfbb8debd16188c701514c472d12ca0a93d5e1f575d02cbb6520077f997465f797a2527d4c6dd28f1d1e98419c45d76b78f2af5df5ed4ba5de31127e4f1056f075a72f716d77e2a8d3201b1dacb59d22a61ece1ffd3e3d8e1b87e08fc7890b850fadf1334853acd03b1feb2f824fbc771c050494fee381c7542730b597b5eca41af19d4f9b43c22cee22cb27ed6ae5e0606f772ff849918b7de30b08a57bf21a1b89dcaf66c999507d032610e45d5094d913e842a98144744ccfaca195bcf61b96f9a5b751ca651d0a4af77e835c3e09e0efaf6665efda482573842bfe235b0ea00437d914d93277f0d76c0c97a5fed00b014560b27cbaf94e8e2b343d8869d771c9baaa7963a4f769ec11ae617228b5fedf936d75b7fb9cfda1fd0228a066cddcfde670fef4749f7f3b5ca0133893db5839bb2f69c9cc42d0d9854259c05e179f466ca8f15d9ac52a0401c5d4dbfd1606268457b7af377bdef8524079ed339e267ae1a65478999821828a6fa57108ca3a780a382a55b0558c3136e3bd3fe451731eed7e855d713aab78e9ebec43e6310ea79c7a10ccd187c86d35e9deb96c5adb4826783d21840e3c36048b3f2f96ccef819d125ab0ea24d9807d0535f1c855753cfa7def4a2863a8be3d3d40cac6055abf27b738bdfdba734f729ddb8f894429c05ee3efe25bd1d030a8d5a8e78e0edabb5d667b9d2c5331727cf167c6d5058747388a59844f0157ca85c4730251dfe464324010691bf767e287b21337a01133f1230cfbbac12d43bb40967f3775306b13e6e4fd7097b8694287d46f1a8671fa20cf33d51665f89de15cd6b8d4362574c690ba57187839447c95f69f5120e51e4d95efb0760bfd2c4c8991e1a4a8c0b79d1d614ca444c928a56675ecdaa31aa90931451a4ea464984691d322041bd05ebc8635900f4447809e503a112068034374f2dd364e16a95b85ce610786446efa473d9a0e350be60208cce974a09bb991a6406227c92711c01c1106ceec16c7ef1b218d0285bfb34af34d7b8253083eecc5360a820daa4237e441f071fc464fefd4b27edb0f480ef192ae587fa99a805045574ad5a0dc5149808549b6bb7d73852040dce7cbf0ed5f0721bbdc5824f56bf3875bd756c258d104e1f36f94b1364aa26a2e49ce0a11c1fb9d5e8dcb886abe4f0f9d1f27c0716ed71b1342b3348a599e422f56858ed5a87093683a82fe03022a95e4aad209fcd48eb1059091fa0744853cd044e69ba6c29f460e9f4d8af0a10b7fafb4f5c9c70aaae6f688925618bbc27d09cad29bbd93c37dcf6a3c9ceb309efdd0dd21e25a83645fbbfb2b87a7e93407e31264f89f6af4ed3e4022f4640961cf9d098b56fcb129cf4971dc3b9b8fc70eccca2ef332252979afac760e20af6cf26c6d09b54b42290f17ff4a9eec01dab5aa5d463b5c55573b9c7290ee33b953cc781fe99c81154fbe121e5e66f1750d326827c263ea3a9681e5dc0256d2a52975d400fece0314c8335a32e59980537ba80cf416b433dd6b9df0314bebb17446d0646c629235c5c6393d1b0d157094a1cee84bb1aa393074dbe4f16852fbae8a09db910af7a3bd2adccfeaf013d98587c51c78e96653539b62d4c636398491758c1746ff273716d7e0d78e63aea748bf9a48237a589d9baffe2fb64244480a4014e341c969d8fe6077cff35c998149cea5bd1706c6cfc2de6531ad1aa84ecddc68ae1c37360b95ce355293b8295b9c804f72c761f3b57ef2471fc2e183e90d14afdc70c9ef4e98e0b46643596e995263606c3867f45daee83114e74e50c3149d69fc933c0ed990789aa0b71f54b1c5a01a8afca063e29f4152054595a21e101d395a491ee8c6d9bf1d91eae4913a09d90910ac0513b10b0fc6513e17f96584c7d9cfa6bc86ab1ed366df18e1b4abc30d0efe08f1d3680096bf9b376ff81c43d04dd4cc802601329b3b26b435d2ec04c12fe278c66d20979941b91ab1ac55ace047b55014c0ede4ee29d0d99086c2821556f4a91179d56cd5ec1feaf0054a9fc0ce0af4ec6c917f156a639d91629985b13542d569483751a3cc134e22d614e84dd2049aab3ec31297a79acfdd5b2e77fc3b287baccee5157fcdaa1a2cdd6614f9db583b6abd06e90d50885a23489e7a9857472fefc7a5c475352a3aa08ad5d2d18845d8f0cb2f8859f2d758f0f67af6116888b3c5ade1e2ee78ced3e5f0bfe25940940b99e7bf1e45fb87f843462f3fbb7dfd8eda26c8af81b4b4ac7160a2e7f887edaef3a81912b5cd859c81e11946c72f6ffd0168437131f03a5340e714066e67f66b2ab2f55ed676e84fb27ba9428905ced0a5f7a119ace3a840a9c9af0d903e399ca912505c12cc4f3e6cdaba7e11bbb3135b4b7e36677fd6859830084d8c4b0e74ccd02d03157c021dfd255ff3537e405e3f1d0fe836c2f1cb4deebb392cc02e6d4c05c2dd910d0ffd18e42f674647bf1440f5166266f5d52be6fd9539fc52d487b82459e73b5e0ca251216d76c69e0fb4c4019e9b4a501ccb53158a967955c81eeece059a5b0207fc28eda87de2470627f8af7185a818b9893a24c81f3920a25e196d668589492cc434bde85ba8926c5fcb23c57f3d301937d59731a0a3d2008c28a5324098b2bbd263c0f9a1074fdabe4a85121b72a74d0b1314385c757be1a86b50537998f3efb14999ab9c69afd4fad3badc61eed9499025a69283ac0bb55bdea481d09ad2569bde73c6f6927e76035ee3a7f80c49cde86b0cff1bdd4cd575391b271755e2eb54e6191c58bfec1961029f178aa74497bfdb42aa6e59bc16d58c95073ac95842b62b46fdcbc309d9ee4b424dcaa48bb17c5dc6240e5b6a2ef2c3dba724a79f4ef1490a8797fad94073897f428eb08cc76b86a6bfd465a694e3d7c33fd9efd540e9c72d2ca80743774e246d1c8d47e55be6853f5ae0029283a522f46f93fdac991513ff38b7471c802023738a479ebd9d38e638da04d557dfa8cd4573f8a3b2a13c19e6baf482e8d9bd0885a58cfbe4dc53c107ac456ecd7a087b3bea4276d52a1479441a00505c66377d6fe693682738494b3af989a84b6252287b2d78fccb585b410c90d849cfe568103712239c1704148bbda4b342fe07a46d7b0cec8046a04fa23ff468c009ca41c4565a93b432fe50b46ac56cfc0f4e1840c4894bd3357726ca7c36ac2625d48104b707e126dbac7bd7b49f8958d6af26797c3037fa6c6cc0346872f0c4ee63a8c76faf67cc06f3dc6800e99bd53c3bd80ab522e700c78b0b1e052220427042ab99e8d5920d0ba4a0789afab4ff41b19c0d004ede52fd750e36730b063c54713c06f4eefc3c7a3c396b6910303b55651867664b102a26208e9c012ec964a317dce1fb2c3685af643016533fbd608ac93ef43cd92bd09917b4b99524a018508f708df0842b10b283f09d80a7026ca1f6dd7c69db8c65251523dd29d47d943e3f628b548130a0df5cead0d2e413be2a33efa78247feaf4d5d928234d182baf073e507e2ab859430d7270a60d30747f770d48adeed3292b7b2c958783e3717b685c822653efb304b304b46ecfc9ee45001ea15f7a3cf855de4a51a5b75bca745945c0e3f13649f070910081b74942ebdcd3517785c242d5b059ceb033d592b5e373a64d15b43f643dff7cff1d9fa3faf8fd9ccaebaaeac358551feb78a3c171c1eb5ab8c9056b7596719c4179d75f1d44acf1679051bebfa68b4625dbcfa06d9a8cf125ef96338ceabe6bc7866dba76f917bb5ddd7c0119be7f9bbe84a9fd1d83c12a5f408698c43bcaca2085294354c608856d9763d1b58ed569669943c1e3510f87358ba2acedb185862e1e8623619eb03d98ba6a079d9373b04b3d974a3df34c116997b7b0b33b3fb118f34ef348275baf7cd8fc77cd196936b2f97d2e6a215ca8aed9a51b165073dd125ae01170707890e3f77fb6c7bcf1011097c054780447e22fdf3175d571aa70659bc756783539292635cfd6b8eb8a2fbd6e5df1d928321fc95a167fd398411d2ca9e5c0f11894443c90e190ede01c600237bcdf17ff8d07848448112335ad5a0384f11013edece710a09d1b1e37b5028e54dfd1ce06a3da72ac05b46b57b833f0c6e37df1db78abfdca95c7a0f6d5782ceacfab292f060d77e640abd6f6c531891e69da9176a41db5f66d47234790340fcfded2d89c9ae5ff3411ef91cfdf4d3be397489123acfa987635d4eab837f9cb27106b37d55022c94f2bf2973b41c39648d59be7a979f871b035a1fdcdbd65993bc0cbeadf1851485299b9b977c025008c9ab8b32c97eff29392bf5ddccccc1f9ddbdca4ca42757021703b46f624c68b538cc98a12121528474c1475868c19d4986804b7bbbbbbbbbbbb628290e0b3b31d37f1c8e9b0c8fa1387002f16e06aea6e29a39400309c6c3163e8b5acf164c46dddc8ed07caa072c3531151173750bac1e886a4309cfaece8b19391b9eba279970b2e0422caef33b2affb141b63d4589aa67dfbde55dfe2b66d31fa5744f66ee8d2dd37c61853bdec3451882863163251926127d34f9ca5b9dcd98948501c1276b056aecb65f4d9f136668ccdae038e893613edba4f868cc2704c945f3291f74800d8907af94154bf3d173fa005536c50f9db0f69514aed555f90f6b5e2fbf8cb83e4d78adf0882c8d7be2097bf3c09b87d12d027f555e9d00534e587bf9ab08419d22e873242640bf59fc925987224c3ca4e8c3136e9581500329a54f9a908c61893d0004510485f286d6962058c47c42f0c1369ac18c71655fe54c30555fe46b184991a0863c450e5cbb83748428818638cd10331c610c3a5c6d805e7504950e26a982c66d5c8a096a48005944c8c998c0d1d8ec814dd1023f38215349119a2c80260c82cc124ca0c2616706a22c3e5862f51646650a444068c1b88c898b1c102543c8911030e16f87284130401c058c2e40585872c4ba0b858da07644e4d929a8c692f64311b906c20c306a2346cf0c42436864912d4f2482d6a1e5f933146c9bc05b7756577a4fa737b715d712291cc51b8fd40184e2b706202bba1090c3064326a46d54c8ddf0bdb997aa46b0b488e7aa3c62f4c81f6c9ad9bf632ba103f5617e2e344fab1564434aca9fd610df46de07098a1494abb3f5a23bd758de1f60363c42c812d61d2637ca006514a9c94c8606d300e1cd0fd30d6b8ad4292daea1a6afc3589732fa979ae72724137c62825ff7e2dff50a0a9fd6d6533a4fc5ce88f31da3e8a7493730e628c51070be87ed27d77e516a594a1ac45bad8b030ae0eebf8816effec662835d3b959fb26d77df9752791dc5a382b92b342a3b78203da68a38d36826ccce58713638c5e39d2a7f281c7f766f6707804fed87275687751ca9e1da3267bf8a0b6ab1177706298b56be3152e80baa9fe3c9a67f7a34ea8bbbb7bb84cd9ddd7290046d058c3ae76cbfc3a64386e1fb79f31ca9f3f5b93114afafaf3c7285beef4246ec2f776c13f8747e09f3bf6c5dfa4b5bb7ffb87ee66f160f6ff68bbda8b3c61a41b54ddbbe3ae9432eeee8e4be3393f8489839cda6fcf693cc250ff9652fe87c03b48000c60445c84616262064668a0c90e34389343163b98e2454049f913ef24452bd0e287a41df84003154f8a4fc2d6eeee6167ecd27425873262ae8851014622ea01c64c51c516269808a20897c1961a30c1c1f184aaa0faf7f6e8da3121ae80820c8c95b07240f96ba5c0fe49aa2bb50bb4c0a28595ca7f02901061988490c2534480a2fddaf342c5ff218292b0bf886b5f0b3d2415db2e4998a95df73739b93a21fef869178ba084b6526817be166bc7c7441ab573e4b7eb5d3cdac5eb84a5490dd74989863e4d34b9be38e38aeedeb5cb9d992977c739a7cfe9cfc349a1daefcae572bc82c0bfc06da852deceeaf84f6efa967aeee9b19e3f8efbfdb39eb5e38fb48b3ea1fee1ec7cf8330bdc49c5384e0a0dbbba404fb3a62fbcd4dd528699caaabba50c3195ab91ff020afcdafb884976482843631bd53bc6ed07bc80925153a25423538dae3dafa426b58f5a8c5fa87d2e685c94518b316ada27bf7dc12b7f8575b1e3eaede12cd5df54fe4d6a1abf4ca5bcd5e9dac57146d6b6692b5e1dde38068573c518638c316ad23deeaed4b64ddb18ddbdbbbbb5f8b1bbffa8abd32cd69ccc3fdab5b4bb37d6c6d327b46b3f046ef7a04b4d4ac571aa94a66d8cabc9d9dc944a95da1c57f456673fceb66d4bb172ea4a7777df388c7126d95dc6a86952326fdba64929b79a252dae5edd7ca6acb15dedda326874cf9152935195d32eafc6e75d1fdecdae9bbda5e3563c68341937d50a38c6892915192783855e09796215d7b9bb0fe936329b18db2aa678a4562c16a5fe43687cb058354dc494a879bc86de44d58daaae8a53a53457a9542ae7544e44cf709b23e78ef628b56d5b75504043625c6e95d3a57efce5e3a3c2ea476a05e4bda254bb9a48232202f235eeab7e56af38a75d3f9b8771dac5dbca472af08fa69641798fea5543ed045381add68243bed6146e41d94588004155debc6264a4d2a2a6b9e611082866fdf191ca63236e83c2b33585e91e0f92a4b49556ea587be94d6b2828a82abfc734cf92a9f2f90c17b54bee88d1fed83ba8e03687cb21a3b3ca3258122c06c5240595bafdf6feb1d991aa9b31b5f1a91bd05aa183a13585e6190c62908bf852bb9b53e551cee11f23cb1145c366637b18ccbe787ba8b13c1d667b3aca5f1c420c34aace9fac0fbb29ab27ef612ff6c54fa64ce577352aff93f70ccabfb065bebd87937aefaf7e1a4ff5b1886b7afe2e5bf273dcf5d3190ab5b35379d67ef29eee992b6366820fd00f7ff1bc12e3591028a076f17fd0792cf3177f121495839bbb6b9aa6757c0f716aaf2ae020c7fd148750a2822d757e3fb51857c845705445fac5ef1fab7f44188a05f70a4490534a54881a6e540b322068b850a9c631228b585d1dd4c5e26949cb512c06ab61d80a29507e2d5f30ae82b4a6b0c0f717abc0055b53b45f9065441935dc99bb76a4e7e3af2b43b5178885ba92c52e3b10b4cb818a29a104ae288b448a18d515eb68294a110c3c298a13356428bb9483931a466e77a10122f01ef91de0bf998081e1ac6bc4909e86b1a268f547c2b0a2a51f70a8fe4466ad2f701b2ea31449f9236fc87e78cff4214d3274c5d03fb8e9a57e67474b7d3796d0709b90782472d73aaa8ad66906a2eee9fad5bfaa256e8041c3598dcc663bdb59ca7be6f7e41eeeb7c7c63daad4eaf403790ff7a9f8d5dffcd2bef0c6497ee107b53fb6b7363882c4c89c4dadbdccd45c3a6132543ba85ffd48bc8784f66242ec9e552d41bbf697e3c47b4eb0affe18a692271c90ac0e0fe12f88a328d155f79777797957d3de59ba74e9d2fd71982e6cc27c0cd61ad6aee62149049dc5269a18d2344dd38e905490180263831a59a4903dc1a4c687a218c8a198e44a524a29a5c6b47d318368484a29935c79028b35a6502286882e9c698a303da8be94012545b524a7abcbb483ac0cd72df93442b04147101d0307b34359ff392e1e06604206008b1504c8328500aaa05add25a62e71dd054b4ef0a07ddd25a62975abbbc474e44d96fa0a099ce1e405982b76f8618cff2b5b0f9a51d1126954839438bb03254ee567668900a4d0ad2e130f3708a3868d8728d5290ed30e666aaa2e130f5cd490fe5cc2b7d490f500335fa0aefcb0035292ebf0af7445ffd36c41f77763661cd89318f3182d95dd573e689e7bd54b6edd66bdd77d230e272326ef3d52aa714a793bb1976d979d9aa7bf9bff7d766a9efd7ed058a27fd050da11f6b9a95decd42eae5c436eaa1c26a9dbd51ecafbd347351f3d1b7e72eeb86df536de115f7d8d27a45d71e58366cacdf7cac7ea93acec0d47357925e811627737efcb18a5bbdc26f6d23c2958d38b81daba1f3fb1d2c7499c5457c5ad8c454e9abcb84daae3525d978ace3aeac14ac7a58a624d4c36f1db091bf911151db02751b5417e2e95e4b1cacf58f8dba9297e69721813466a29ed62362ac2b1030d39eae663682855ee1b8fc911af4ffc73fbc2f9313f0d214284610cf5ad80aabe85e6514d6905b9f1fbb552d829ad203537bf5f6b63128eea9e7801f2976cc98ebbc48393ea41e51e1ba39913a3213889872d356c2a957bd7c9b35476a2863c2602aea3024ab43d36080530dcfc0d0d409ce7e728efc1f982664802a200e7614801ced3c020ce1704821fc09b1b5f1008a2802b213734006f40d615fafc9579da5c000605fb2a0e28d4c5507352eecf63a80f7ca0fba9d4f37a5b395ef5735c149ff11a66039d1f721217e73782197dcad971a620c314685a6afbfdf678ac34cf7e0c2526da5c00f6648408d582862b2352a47b3e0097a3b8547e3ed3b05dda010a6e871f87057787a356879f9f78f093cbd5319bce4983f382168c17322881422c45d1f442b4040a0c1a018b2d305088c13c394206c65640abb7d0c0cc0cecd14314483118c845efe834b16558230b224e76ee05ad0da03043060a390d4ead15be209e728319a240a1550d33d03f4619b460e6040aad5e8004fac72f8865e0a48918284473c611e8df6eabb112276494c0f616148adc38f4182bc0785a2e509834711919506902395f42c31846a0d026a31b460a18249093ee04ca4a1328d49d39e268dab08596797483153230888198c129060af90e47a076460a1033b06938228a285003a306982890eb68342d6b1c29f10ed6698b2632e712f58453ff10454313c8f96ed114451228d400323d84891a0c6a98411328b4d383981844275c684004725ad79e27110326723003b9ad6b2f686540d4c00914ea34aa84818019c0cc402e654506284ea0d00e94334dc09c56407b4108c0818c245068aba0828015283102b9ae6b0f0135d8900472abaebd03b4414311c8d174ed711618cc10052e93922e512047bbf6dc8719c8000672365d7b41ae0519658a40a1950e4f14dd74ed79d10f349881dc8daebd20f985071a66a0d03a1501469caebd78650b2d92400e47d79e63a146124720373dd0d13550e982040a31bb1358cc38710472dcb5d76a982992815b85194dc840cebbf63887299e20818cc31145c8c0a03ea3062398b604092b92402166bec107eb7bfa98dc75cf751dcd8faf7e3e7b21d8597561f5f397b43871dead9ea5303333f3b6d1d4d43c57b3d56c5b951f4b69573f8dec35e856b93528576e0d2a2b173bd51a5a975aa1a0aadbef340f8b45a993eb3ff0e19b67fbfa1989f2cf9cb88455ad41b515ffae363ea2195237b903a49582fc14f091ea4377e276f2399dbc67c1e94a1ee522384143f752bbdfbdc8afb5fd5657bf54f28e6d338d9e3ef8f0114be1595dfdacabd5b3e72b2f7e77146bd84523e0da0a838645e2d6004abfbf95be161fb5cb632967d0bf12553525ba8572636957c76a33c5ca1ed7560ae189324ddf708184adc7713268e0fa4bbed7efde1159b1a0618b69a7586d294c5868377d1dfbacd0fdb063304683e3a464667ed9d139c4bfb065a21605f8b55f41fe7e30eccbd73e18aa04d967180cf2f96b2cabe3bffef2e7dcb3820ec94f0890cd0d686a5ffeae239c5e66476a0e7ad05251430e780085b62cd0007fb4a630ac700128b45d010350bef605ad164d4e4a80429b1210a0fcb61510f2a8fc49407f7fad207f7ba92465c497d60a9f267ffbb4179a33dc40e70e166d0591d383f6092e567801063cec2fe5e73c1454fbd9cd5782d5c310ef7922eeda1e5e0b44b6ede5a1fd7aa14fd55ee09e9db5d0fc0d51a29188d742bbfaa714dba30544fbf8da47262a667599a880aa05a8cb4485953a9b87a97e5da62f522a173b5f49b949606b94c0566d86b2b216b5fed82cab06a46b7f093c1026a9fd496a7f3f0da4df05ed350f4800aafcb65d75733c32e198be38a9367599bec0bedd1853af799b53a5b7af79aa8f5e00ea265dc6df3eae50e89f807cd9d5cda95acec6af43a22e4c76256e66228d33c47df3040d5766831ab342458a07b6c7cff668ee9f2d1e83a7d82f8f45fc34ff682e445aca5ad6b226d24da4894c2dd409112832994cf603886a319b1cbac848e7b0cd3245462277195f70cf0356cbf67cfbe2af41ab600b1d587186146210000a0454f61e95dd0b5e4f29a985763191fe3eb023f87f42768c2d4d2e040d295454932225d174515c03494a9467686790ea0f6dc30119cda4ddcc8e934f4c16ee0c8b152ad508926d9a91718f4b4c14ef88f11943c61828471e738fcd8cf88b998d301bd16464239a9c363a90329ba5543355145c1a45508c60b1b2b9fcc53db7d6a28be23aee3eea280dc4aa8b7240c39d4d1ab9e7169ba3a0b151236ca53a9bcd664a9369622370699f1e4858b8a3847009fc710c438cb00c5a40b9913468d8b00ebba836a9bc89bc67f51edf699a703cde11ba8eedf3dee6711e9fe20d2e768cf8f8f8e4482aa28b895ec0eb4eaed1ced9a60eb149678a494a95834f556a23cee1a870dd8d0edd0a071a32d18a0627079a9a9b266ae80d26166a08b5c942e30e269b9b3528efcd0d336ee044a191c331e273860f0e19d0f8fc383aefe5222226f282fafbac8f07396e80918395c5647573a3abe33f844be0cf6647b8f1c54cc756417dce9821c30a13319940820924101111f9fcf08f65e5d0f16388dbe112f8a70008b0d9016b87fba6053d77543bce83c71b5de2dc51ed481c208af8949a130d37c6f270a763b2e3a345dec0a0e12209194af59f1d1f7f2d2e0e1d380e070dcabbbbdddd3631a0fc5a7c5985f24edffdb40f8165357edddd0d44eceeeed8dd317677f4c254f566efd8da1641f9e3abee8dbadb366b65709b13c68e9ddca6cdeac4df1882d4be6c44b0994046d9695b472937d9cd8a6f33048d326a51eedd44d149bbfa7520456ece2dceda1efc5df9bb591d790304d555fbea7d495509ba3d761dcbdf5d6af365949299e76b9d9c40ac470da58ddae51f1849d13185c6a32e4654b99aee612727560b8d1144598c9115437efae019c939c9830f5929c7c7c8454382e2508d2f24d620699ed6c718a353c4e98f1f7fb6d8a02def9b7e1869887ff0852c1d5f485939be90e5c117521c319c2f9c4621f59b161b34ec66ee365f386521ad29f21dde3a79cb521f51f50ffac610aec99afb2c5839ff0eff863e55b00d4766662766e06ea2061f93962dae2899a8419558a65489455665134330f183ecc9a104462e4dfeb38625a6b8e2a5ba6a36c1344e57643a7fce7905cb15236a76708ff8f30aac4625246afcd6ca47a4341011e58b0e9e58d0448728b248d1344dfb20f5a44445893035be8e958f488bf83259719f60cae149e205498c5102a96a4890a9dae758f9d05c55601113638cd183950f197657e454eda3f6338caa3d3d628caa3dcb4a1355fb474287236ea8dae358f9d05c581c01e6468c31e270d951441a463451841155fb1b2b1f1aa54bc49002eae8090dba28e150182104b793ce8ede6c971a76496324c5a0c6b759f9883963681874e5438b677471b50668da965043126a6842d59629842a59aab021832664f005112d20a28baafd5639aadad3346591a166a90a518df157ed84937c4dd3344d8bb17972545966aea6254a142f4a4a645c3465c9c2c251e4da1e4ee5892acc1237a8a416638c93a67eb99c06d490c7135a96ac3040d57ed218aaf62ced9f49d5de25c41455fb9d1554ed7d8670aadaffb0a06a4b31b8220b6e4372a2c6d7b4170c41668d1fd6b8f2e505dec5883238b9f221674e036a5c42ea414c8d1fe94e943151d4700415596600e35c9460c0006003d452e3c777aa695786543896a65099223c99da26b7edc809903094acec6003a8288c80c8949682f8c208880d8a5831a4a52c456c6abc92e84ddda52c2f4882eea8bb9445869913b3073821bb517709872527742883f67b6fef2e37af7cf65ee8caa05dc39f6550aee1772d5710e83a3dafda1534741de99e4e721e924907d89ebef9f83bdbc3e6e9872b5b2f56b387f86b6adae86071f7ae0ebf8dd749de53a45ff1e36b5ff1a39918d3a871e38259286a7cd5174bc5b88076aa317e38239176bd14347424588def48716e7c1f6a16941a9dd438654c20f0c154606b05185ab45d314aeaaa3102404643972b27d2744a8b18a4356541d6ae363eefbbfcb5fc85eb64820f945251513c8a694d4949fc1c0d1b51c16691e324ef6125fca4f247a548c44944f10b3916e78cb18bab18e5aa536ddf7393316a1a5be917d7481cda3d8d6ba7fb58feab5fb0fb9045e4323281e8a7f37ef82b16c41be22f21575443463256412d05015b828192f61a0c90ea9a517749ca957f0a43bd1899c0af02f7778c8cdcc87dce55d1eaf0774882f00f274569cc7bf84914218ae0278e19ad0e3bed60b1b4546a4e6e7ee4542b1fa9e7150af3b7d7e616a3f69af69aa63113738e07e1bef3f9e13e97efa83e745556815540a010fe9091d888a1b831565081db17d16c38aa53b62f88c1d60adbcf9f1f0cf3b7af06d97efe377bfe825b7703e12f2e0b9847c130e03d2278012606b6806cbb0aa27dad0952d54fef705610ede517d49ab2e3affe7eed7b97bf3af60fa02b50fef953d4a10b19454c45f3e733c1b468991d390c894a0f35eca893665d44651cac0b0065a2f88b37f2392a95f21ae6af281c2ccfaefc56b24df4042b4b37015086527e9ab2d509829fae18b183862573445136b6879f6417557ed8f116dbc3515c832adb68abf69325a1629d7cf7f7afe352352b1fbefdc79523f1fc6ececa3554cb5a42495977547f61cf729e15030d218a2e5964e02165e84aaee44a4a3e49d876bee7a4dd51fb926f6482f4915be891595bfd496a557dd8505256fe9c50ac1c28c74aded34ff6253f4a1155be92944e6db43a12ca09a71ad20a15c4a0ea7f3809003464074d367e8c9fe302628107e530ed35951f877754aef1fd9f66a73efc9bac3e2028eddf811ea9d1a4055328b78ceeee21cc7c0758aabbbbcbb8d345530c607bf47b7f5d506e57bb583a50ce3bd512b4e917a309f1f311e10a6522a8b7093f3eed62614157c77b806145b1c58747102933dcb66a620201fafddc0e2fa85d60031318b8c30b0a126223643cc4c02af377bc083aa040b8974254ee93d4766a9e30cda3e359bff9c66a0f7ad6b0380fa85d1bf7add607df4c1e4ebb5aad0f85ae2dae6edf5468533bb598ed7b4cf77094eddb8a53dd24b87deb03ee08168668336a9eb0a554212a109ff1c2c40994ad35bb99460d0ef7d1766d34eaf01a69fbe875d20e19286c7b96c74349dbb3d3f639be90a56a1c8ff385cfbaf185ac8d05866e543620a5e6a1f9ed1b4bf3a8d0db37d3f6d07e93cf79e1576ec8eaa777f3d10b8b2c540739d2757e3fde4fa14fbd01e16d3cd5afbcd67caef341f05a292198d8526b1a69076b7b6e87b75d10310403819a67fe4685053210a680f03b3ca0e6f9eeb7e7be1049e57e7a21923a9fdd0be777c441f85af31764a15fdb775f6b7eacf313c2c445cd99f3fb8f80fcd5d378ad14668802a85d714f65e5f168d79684d4aeed456883ce57a179e647a45ddbeff0b81ccc62e0ae2ab06df377dab5bd099e4fbbb64fe5405fc906757e612bd52d6ca4bafdb46b73b6cf0587511568dd1fb076a054287f1bbfbd233ea5fb58e8178f61a24e89d95e917d4261bfdfbd10ec537561bf91585f682b050f859eb457a576f77bce20ede2ef3d3e94ca3ba5fbcd2bb24fb55d8b71a7985591e45af4199130fd19699e0d970cba83052fa85ffcdb218942fb1b22a45d5f147de2518195449178096a62183f50f3c8a3caaf42f3b0a050fe1fdc2d7911537b3581fd0e6218128e88902726e2e13a21baacccccac04c6520c5566662ade4cad1d12b444f146f9f8ec33d7354a7032071774ab5c41802b03d16a2b85cf058d9bdb24abbf0a5400474b5ebc8ce1a63db369cbd462ed3021a6ba6e897a73fc58bb1878ea2e49115565f4a8e6e100fd10a10dca83846ddad9d99e6caf71e1769ca191080b438eb4a66d5d1847f45059fb8aac530a5d18473cf199554e56c2bf9d26e8d6d8cf0e25bc20edeaa75cc352146264f4d6d7d6b25e987d90419a877f9be1e989334a0051c39e2d085150ee57cfadbc86f217b39ed0d404bb75da50298772198c363bcc7bbaafe53df39eedb74a46cbbff9dd07c30743151852afe27ece397ffe0043270aaa1224154ed91eb67d42f361f8d4b79cea22f53d09c181ea43156c1f02559db27d3020f1d747c45faa2e54dffa2bf53191bf70c044f3abe2dfbdea4356179d8fbf1884293445f35bcdb7622b7e53bad80c8a6ac84620704137075dc6a00106c230bf05443ff54940fa0555f1dfbe06a95d9cdac2cbb1652876aa4d5e08183014ab468a30602856a7c020b4c9c0d46f9fda425b260216da5fd84f7593810ba63ed82603b7afc5338ef217d450c38e70f217ffa6c9dfbe4e1ad3444c5a16726da6767592bfa648bad2a4863d6b275dc4db5a9750ba87953419542a379da91cbdc3e6d6b61766a6957ffb884af3eccf28109cbf12b4d5bfe0a6b630abfab0f40416232c2d20db02c2ad9fa2fa84546095fe24759ff793d424b50544abfdb552bfadedabac1b695af27342bf260155e3ee744ee8cece67478436e886446d6666d5582bc149521db3fc6c36cf458c9663a9e90cb7d8ab7f10edd931455f869cd849141f691d817e0489c2e15504ba87edd1411b336307513c81421cc59725c02afda0062435811f4ea369fd3fedfdf0819ef00389f70075511cd43024ebf403c9eab88deae7871bd5b56b7bdea889a30534649db0f2a1fdaea226a36c7d0ed777252809ed72c7a9db05091e6ac84426f8d4241dabbae05a40fd49407e76fe1610f3b7fe62b0fb19ec8fbef6fe24e0802e9fa04203b58be3d57e394ee809f6e5ef2f85913e3e8a3a8a95c1450551fb439b96c02a02ea360d9d51696da212e539417168a1336e67bc8b1815e18ef0c38ebf711ff990d632b1d2f7d43d4def8186ac1424a9306385cb11182454c3c3141780f4855a32a411036b825a01fe351e9256803390d404e47327c5867f0b5989abef121458aa4735cc4bf56f22265090a9feeda47b968b24180c16c32e4d61a63ae8cf6d6c85612dd7e2af6a8e1d1224341c551aa2ddb3d2d675bef2d1d1acc47450fcb44350f304bdabc29dca4afcd3dda7f23ba11060a15382ae334cb56a354143b6c25492c4a86ea00cc54fedf2ff9ad0a54fabe36fa3023a47c0f5e90857b9339f676ca679564b104c75fe74549a4154fde9a0e6d96fa2213fc57797a2fab399edb1b5bb9f6284220a29b5bf9c6f9783536cb49fb6f16a94bcd4137eac50212cc4232ebfbbe3f7d7fa515598693c01390e5de718e2a3d0709f7866c84c5c8e01161242991583630c50804eeddf1edb7136b553b59fb74788105f7d47646aeaa8f36dea4cd5f9d3c6f358db0b0ee0df55737bf7053c046739fd82a05c4f5804833e3319e23d3f29eb818474169403dcc37dfc211bd81ea98ff353de065627fe72805ff1e38c7b465c356a8c435627c6c8c31237cca0e14ecafa77ad53051888029302abd31598abc57eb1646a8e9ae372fa6643b5046d9edd1e110c6003fbf20f67eaeb6f00db83045c9989b9690af1971764684531c6d2594c7fe9981ce3588f0bac0e06f8e5afa37e21463885e4b0802ec736a6e5c0a996a04cc6b22064a1a87c44d096dde430146e98392965b11c025b7dceee2c88b4d0aed412f42b83f277165886b828d13c6100b8abe9ed129959939b9c526eb29b638c31ae4777d75ec02a6f5d3235b70c547fee8eac52b1b721d3344d8bd107ae6776a8a3ae4df476775f01e1182a4ebf2825c3a293d9bb70817243ebcfb5e3f3365f3379720ddbf0cd17b2ea46433a3f74edf8a4bc3db9bddbb67c21847803a75ffbad1415f46342d070ce36a2e669178f76c9ee62f7a87e555e0bed496de7495bfe9fde96507e2d885b80431950606090d08241620656892f5423464b1358a5bf0532644370a2a88b47f7a89e559a27ff0b1d0214a479b69701a6a8a8a42ef64435756646000000008314000020100a860462b1482c1a1236691e14000c7b9a427c56990a84519203310a21630c01000000000008608066c4120025949beb342a08da99c14bd40aed6e5ae059c18bece3e31c32031202481e301737c55a5b07996a4ef450ab8c2c9344c846b0b4ed9c39f2e3e8455d22c6257bcaade8167c40387c9bf12f38617fdf5e8a2ea2c7bab614bd54564dd3a17670f89de9fe9a112a22d00169d178fb531749f72dd5877d220f48ff91ab95c5b8268c8f25c178a7404d68694635be7478476851009fafed2039e96ab767e9e83ac46814d3d4ca2b497301e6338da96dea5e2729b3d48a6dc9a6d87dbc21cfb8d6f41ed4a8735b32a40039082e76c6040c40bc1a446ba2c5f59ca560a3c5451c72bd6bfb325a370f2c9d559ff0d6f893ab4fd1f12021e520069a0b14516b6e0305a22945032da57ff829965ede15325b2ab9320edd598e99f7b3e45d98eb05bf6f44c1bec7a7860ed09932b335007cc3d6f09de42cb657c656d9edf2369de1cd2eb564b865d9fe0321ce12d8312c8509375c4f2a62f0cdeeadbcbcc761d82f2f5b80e6ca90018b2a9e704d2c94fcd6a170ad44d4ffa4faf775322fa1579b46436aa18636555991c2c3a06467aa2d27554715e24a3d38b2aa1d61f45d7398236e5d57b35a4d3011bd71c6ca5d175f0d215d1be0ea7eb60eabdea5ebdff72ed14081ac7342bb16eb2a84f3643b2707906d419451504e2ab2460bd8267ba6abe7e26782a7d0550e3d1b826450ce28e53d3d325785889bb5f17648aaa8e011338940c4c03c2be6fa9427e5ada20d6ada7118b4f507f07d0990ce6c835a1d9ae8d8ad303a4cc1a7975b6ce2a9b378a1cab18bef6e11bd7db76dbecc43830b5d3737dee3e5f06013567dcda06c34d1438de4423fe6f985ea5ba9bcea516c58a060cd26846d2b9abe4bc1c98b08d69255f68221b9a75c9018be647c0e5de9f24a5ba6844ed731fba7e47ac6fdb12cd543dedbe1c3b10ff92d4219b935d7485d0b8bfc446c94d299a4f87b9af0a838b0978f3481cab9933c6cc36f4cb86d52f3c61d63d307c9b28680d1f364b9b1c8dab150383a7222ac62492a5c686a9835a0d513a08dda2463c144a490ed1dcd06dc77f16e3c9d0b94dcde994b0f13d5069433403a4834ae2c03f76ac35318415d3f208e3748e1ee9ff10a859fab119769aa2a63ba9a7823d9c72f4b1ef903730a7a46d9d2cdbd77ff8367e2e871594a6e75dd028f7b9086bad736b6247c859011fa399f401c43b8a742cd9ae6cdcac91ac54a1c9e7cd241bea75782ae3bc9e53f9b7ca7c8f0ad8edc40a297681b2d32f574b0ddd1eae80e0eedbdd61d69e3390e359ea48a393985c96d8397d570f74d7e6e9ed7872bdd8f299815a6d5721d05c17845597d8bf9e64c8ebc5d33126c9c35b5d7011ee389bb5e19b8808cfe9055e606eed1795297ca82966dc8ba3532dba779e7f91f14c0f25e8460c9a4ee4f238247a07f5d624d25548d8f0d15d6dfaa3bf4afced947f0ddad0d1d9ac8b9254dc21f769e44db7bba0f96354d7b45a7e0d89523a666da797c33b17572127faf0ff3ac99d0088b7e966765ee61590731521e813f07311b2251a5959c858f9601aa44d11f53fa49a8e62fa06a5efff21a07a2647b5bd85e31a465ec3b2be336f94ed4db158173fe8719450e01f99cdb3486540eb93edb6036baa5853fc5f8d312491a6cad9896d0271892f12dfa7b8f0388017beb01c3faaadfd32a4b189b72ab2daed67499490dd426de2a40d1f7ec5d56271ae571b680d48d341c3269ee8a96442280579123815fd02e97bddeabd35bb7257ea4ec1af879df3f74ae0c01c6f56c7076fe3ef7e05bf0d552ccd2c733c7a0cbe9546053c312a870ee00db5f3ee538a969fca0ce58e672824025a679ce5216e6eeacf3f505820175ed3be7a5bcba5460dbf1ebd60ef02d610f3c3f90cba9e7648b4187455f724c5f9c9f7aec75da96555116ecf3f3fdc218649260d406174def4f2e2fd267e81cfc8d7881d1c92dbf9ea3f5f154e81832cc46cf1cb0344b7962e31fbd5471f728928e93e69d692670a3c84259f220c66045f1cf5b75baaa7cb0e7ecd804c5afcdb5bfc7c4f8c8ed88d8eed88eda0a6629be786b8048da6d3a2a29f2aa7d965a1550cac60aa52bbbd72c665f54ca6a5d5b2a96f02790f000dea89a31fdf32a9091c6cff0c4eb1f6fc6781d349876f8e1ac2b23bdd70d43348a09c35e3b22bba9ac6486b4de48388afd0203ed374b0346ef63650a0c0dc33fbcd6e3d1f550526c252c313beee7f8815c809c0a0aa431bf552228eabf8864a558b2c1139bec6d35a49e2b7d9d3289cb40cb3832960e6ae2a903bbface4bb5a99fd5b1a8bb24e897e62cdaa3518a0424df95d2146611e29742899449e0927ee966cd051005f36cf42e0bbaf5ffab32b0e7b5e892a665351b2023232ae6f5e8a59da1ce9b54d67b4c6ff3b1810cae5b9a154514266a98cf040645efb3dfb63aa5270217648623142fec434f1bc7e85c4dff0c03f4375fc9cec82941e533bd73a889b932f829ce7b343e6be751fa1fc5b7e5f5bb99af0842199218465909134e78404490113e247b12328fd15b10e40b4a9e70ba961ca33ab1a8098923d29288f1ad6ae190497d2538c52748d3ca7c63276961871ed89e44d2d781b8272a8b89eb47f24007038b2fdac94fb49cd859854ac757cd192a8c4346a01c6f3f0e55f0bfaa33cc79463166a2e2d1fe7b623b52ef03e2560e813f1ee2f13c9f125a1f49f259abf95b4add92ed9b2ad2ec303c41789bb1cfb3c9cff949653f6f967f462b7175d4919e65f021189b2c40db4cb6012c6db9462b70690208ef7dd6070191f7d000dbb022c630093cd3257f81b2d2c2e9d2bac7194ea88f4228766636ca966cd997ec13b20c7fa672291090e7a370e3b5dbe826dbb9f546e4fbca1d20597c57c7ba254d4a3b5c6f625ec79561012156c77d581878412c89f76401ecf2e684804aaab91525138b7d42255a4069cdb5d2c4e54234282ea76b05d96b6772ce236068cef19c5b0438ac8c37895c8352cc0fda0bc3add46852950dc1164546f8b853f1099b9fa9ddd38cd922481aa35c12ea5e1bdca18b577f1567d391fc64b7d5d04361bfdaa5c6536772f6e5db87efc213fdca1b38f20bf81d2a7918c74a5e0c6d3fdd4dee3c6db28791d667823316ac4e01b4e407aaac34edc907d790cc1353c67accaa7380c9a937fb58ef3630662d90e7b39b38c0101b606183eb5d5da9267fcd6801bc20d79528a571e3cce59b3b2e8da18fdf55806a3cb16fea53e3728b07eebc2979e108f8d2d9b532423d231e506c0546b2a7793284d9a07aaea5d03ecd9446d6a27f8b551dc233b1d869e85f654fe929477169c237d13542ad3810ef416c39e0e4dec26d093682873d6d909dd8cee81c07f72add41da08950eb414712285b63d1fa6a0a99b1d2a80bcf4dcbd8f407f3492cb86b4901141ade321c9b7147c20ec43306cf6d8d72b2fb3c962a2e1b547d18e239f4b9c43d65fb58a181e09a0e43ecef88bf95cc7858a6ddc8c0e8c8a8d3b3fb407e37d2a17cc05832d064a4779f1d3f346b74ff69f0a5e88cc5b7a56d6113aa55ac246f96bae29a6cbd62131b56973b7bae3bf81fa0ccafbde42e1996212a081d6d47b6807ed3a5d9186d4afe6d31535b3a71dd6fc41cb83f1906ef84eb21afbc487d95f16dcbf9301f03dc10cd19ab81ad9be7bd9a68b4a4e5d70d8d7d77789302ac51db2bf43f53ca3647b4bf3064c51ec0f5f5dcd40dfdd28bc2d94d132cc829d13349c9e6a0571e3848b2b0228222491c2ac0d7b31e56c942f5ed68bea2724deac0271b0c2dd8677f707d4b0636408e0eb619c5e89def5ccec3304378e622ca32ea91fbbdeb97849da203b157a8951e1ea4bda398ff50195f042fa29a464c76126be7191fe2365e7bef6f5adc696f30c5ab448edc05019b47533efd2d1a8e9a98fe2d2f4f71da215cb33d6b5527132174d202ce7f46eed2682782c472200bc296330a43d6164f4a4c0e8841e907f3bae7b61a9f00deeda2385a7ee777a3bf1f67c65f55c81ccbec3e582f22f2897432d2dbd6e071d5bd68678bb4562d645c79950edd76dc7ce9a26712c3b788875f6b7daf8dee73f79737f314c2623a24acbdbb193958ffe8c3f834c659dad64901b437b736e823824d066221f9f296c4b07c961e8431238518c86e4a20ad19a8e14bd1b772234e0568b6c690bd31935380b6cc9f50b5ef9eed3af09fb477359a3f226e6087793d5e49ba0343cc5e6b8e41534c1a6015eeda31e3871306aa5075471542bd9b324417e10f09789e663bae8f6464847544fb56fde9f8810ece2aa1af6df348888352d3d2dcfc0c19efe4445b65721baa97730f665fe1e3dfdd9cae5c7281e553868b48c43cc863540a8eec6422cb36620147aa6a07e8df4865b62bf0336f71d99058aa51aa302fb2e3f26aa795233cce1adc34ea64029ba611fb03070bf8f5c3a619cf28f282efe9deed6ef22142bd61d9e15c360e4df2b90b3e25e590b9ba564e2944a4966e7e334c64c71ec10163ed9c9e45ebe08a3940066b96bb75cab2c3020cade315faa88964168ae278fe6ffece6898f92a4e4820a84fc04da65026092b58d522dda43c703d746f61f198300635f47dd5640f3d07c15de9c05c05bf87e463e11867141e6949cc905cd84de3175e64b8918fb9a60297b654e8828a4e5dc978c3208b52a14f34a234086da23ae5427fb012be53d3d5a260e45755659458c9624ad84813ef999b24c3c05a4f1ac88e0580b437f4f001f7df7c344cc226f07dd43cb0e8832b6a330c0d5b89160d968c8795b246065516f958c9faad7f64532ef0c20c72d393ca7a0a63afd936835c53836e3c4949708cca9bd452f24d2df384ad2fba0636872482b2c72ca73a8272d5dca3081bb49022b312925c717be76cd4b20ba54d6fde0ba3a34ec9f520e42634a59f83ece07341c16e490bd7cb0ee488705fce61b00ba28282f14fe49dada049e96d3ea7c5ee08bb984c8896d16a83aee3aeb7255296f21b01bc43a2dd4eb06ba5e208fc5c2b343b691b96aba210c06ce91439f09ad60ecdbb596d885999b6758b9ecdbf8f96e966fc3af177a1efd6fd9197f55050c0a93ae7f5a4a1c38e855b462930a7282f7008aeb9d694f6e4134dc359085fda9ca36c205db9fd3aebe8bbbd5bec90cdb3b005c809d8af3aa4c1024a2afaf5bddab8f94a50ae23b4b5bd00fb9edb99bcf1fb0f3deeb6cf16ee2cb310f4b679d9470be66b7b4fa11a845eb2acde4687d1e0b19128fdc2d8815c222041cf6282cc4f7e2dfeb3a3aa13890fe4c19e223788914aa52082578214a027dcd12ac9558d59bce8bb718ba3592a6df05eabe9e83256bcfe2a00c114f513ffa81d1064684b0c9f8d488f913d518c08a9634f09e6a0c8776bd68e7868808832a1300ed1db4ab2719cdc4c6e4f8e2bc55702d411c351c29d2643738ee67ed12ce5da1d099d57590eabf96ecea7ed375516973bc9234af21d3c0c197d0b45a662133912d18caafcb44a09a5a3cfff99748e07dd590c5fce43d50631be3ab47936ee1c4e43f171f2b865fab372a60d956cc4025d6c93bb8afc259439ed9cf38f7a7c95fa2d7f7e3afb63e43b904d6f2cd12090ba11dd3ab90d08eee8d49b8fee235238d8d3275a8ff44962c4d00eb1cd41d2dc54ab0a41d7d86d60c93152cfbef5c0ca3f803831d0f1c14100c64a9a260ed6fe43e2dd41788795cab5e373e1ade5f354f858ec509d2b6e59736cb4d25f37749703ce7f6664f47ba82c21f2386b55aa9b427218d1195ca79f8072e7f0dee8d09f0cf7b868d14061a2cfaeea8d67791bb916f7e8d2a87b05c112730a4b0a9c064ab8134c6cafebabf20156ca9840a38e7e53975f061c76e8335c021ef521f7554e8eadb7bc726ff20a4c4297221642e3a7c4923b854f6888a9985fe6e3f1ca48296385683c5b9372c609068569459c9d9ece3d452826ef245008c09814804fc097aa40a7e0db1dca621041bd08207ca708c3c03537af66d52f4608fbf49c0a56e5d87c734e39a451c7c975a07d5a65aaf63005548b6b82b9645db2d6e4133b46cec7067eb8bac33f2dcee9e22479ed878b866b68fc75e51439457c42aec219b0401884a6f8db72bcd0a662f4e275c520f663565a10610135f5f4924e9d54a0a03d2ffde49039c684b95741e0f7b35c173c8f915aff228137912fba39a768cd4575b788bff724102b929155b91d74001393cbe84af7d46552fb7a9c52b92d86500fd41e4d4cf7465372c8bad7d15db2043fc9b7ca2df98cbaa90d1df75b504a7b82a3705423a17d22acc5c8de3444edef7c41e47995091bbf47fa343924752e6e3b51e210f1248a5960a79c3590933410efcd04a52001a73ea87c9a461d9fb476859a4d64a28c651056b2c93ce99e7f422751d21738ad857212e2baea08158efd8f47ce8b74460d6c928136aba46bc3674d853601026ad5fdfb2d485780be8da5b0d82c98714b6cde9d72a2a747fe6eb29389c40fa75c127812ad3afd5a15622fb55022f8dd0bf5fdfb79adbf08ae2de79131d476b7216818d133fecb35f3bb4c5eab12efed4e125daf8f0ac0a48e6cec99c4225ae4d913c5730186a081236616611d094d3b7fea045756a779eeade5c2c38cf99e5555de212e3506614343492f7f178072b698d77c124eb990fa12f80143ea745a46ed73e8b667d6a9e072e3d5c99a821918b3615c7932c85874236e8590a7814a3b9d8f6fb2c018c5ed045f9c37885b5beab2e5b8cbb05c4af5965bbd6e21ffd0ee71dde62559804070273904a0699d07634d6f373743836c68a5c0d27fb5c9e87ee55f6408aac5ea7aecf5dd543218acee9ca3183041a7993b42da88179c1a657f3a43bb3ff16241b519e8aeee937e80d4b00f892ea09bdeb76f908cb0d81e3984e42e8dcc0df59bde0448e5e5bba48437c1cca8aa4cd69bfc138a9ae8ff0b3c352ed1a489dd720555deb55f05ad0bc8a62a5cde8a82746aca9da02342d18aafaa63fde1c98e7ac68a3f832adaa9ebc594f70aeafaac5157d6d9584ce11b5b130cc2dd872dbfff93d0977f91bebfab4ed7f92c13c8ea7b2bb6425a6e7a501c819b76864eb305b27e004786d71a2e9b0b85a64b2a2326c55e60a586adefaeb15aa6d9357b542755b45cb27431f53206ce52c11f46ee6f22641001d83e28b801e4bad813e7e83c6bbe1a6ab456384f19723c140319846d7353b6e4ca01ffb7e5aa88ce57bfb4af38781b2d388b03864df8bef400bff1517482428067bd2f285bf43ac8593f54788b404370c757647705a15ba1e5d940cdae74c7623905d6b5d3cb356ac38badeff3e899a34deb7dce17f9cfc173613dcc9b62b1cdb0672d64a954dd90370d22c6d65e89c77c72b462014370f9bd57b49938dd17e6c4464aea2d0e63124ab89c6ffe10468ce7b23c828bc51a3ba7cc28d1457fd50bddec21b738340b0b5e6b9c8ab8416d01f89e36b76a667160d9a0039dfd885d35ad84264c9525aa531ae66dcef1822f703e3e909141e29f26325a0830c7b4972be562788ac5e50af9dfb99749cb544e0b21c3ae1cb1cfb44254cb8b162973bc17279c94215d5f4479d86f94f8910b633d6797d8a16adeeff1b126d6707c99d073ce089ddf3c475fc2a328917169d7c614d649c541a659155f523e44858fac6a804a31bd1252950804e844a430f64e0a203890bab444fd84425209496c5fa43d5f5452064482029c6c6c018f069d30577d018ba006fb44e4a0635422efbca3586892c0e01d0b35bed3abf1d02d9e32d02c907e9e4f9dbed8e5bf60a28c955dbd51dafad029bbfb829da53a655c8c3673ded35b7901c98c56a904b8bc557acbc264f38771909f90b2a20f79d8f24e37e41fa898068686235cae85ba9fc32e243e234fc3995bfa3d27ff8520bb491c0f356193557716b353a0f65e5b46acbf4e4a14b7f341b9dcd6122f9aba0e6aea55d3db4def82787882021ed08665181b35084f519f86b4e720e159d04c9c9915281e135c3f2ac4a1a790a67f591b01779214bf9b7e6f9f2c0b6ed88c1c907eda0e9cf552c6448692ed9829c2dafa593b32e2f7f20f94c41ca6c8098ab43537adcb9637d4d2e3afbd12931e875882aabe67a1c034211b575c5725fbbe98e4ae1aaec5aa44f1a9bcdbae868366c43968525459198fe89d368482d8d6154c0314849580abc34bfed219f171d08173991342b2b9dd3ce1e298e3dd5c87f634ea72ac6ac5253062cc4418b5c03af73adf7319068415775135d19ca4dfaafcc4a1c5c81382f689458b144d2d5c857a4fe9a012ce69b6398cbb8979b4c177f6f4478bb92e09f033f032c1b817d99e0968ef9f95b8a3b600ff4850c4830c8f226cdb4ac9b8192a138a12d32453786e4be7eb4abe343063e6a6a64454aea7632ec613dc872b3858fa4b2d29b1de3b111d8c1f1121540970999c73821623ba927afb9abd090c65daf437e1760de8977cda703e043f7016a128c2b17c2fb5ae246408ab280827dbf5525565507bc02ca56089384d007cb9dc4e649c9a4f4870137dea2611fe7dc54f09e2d0e2e83b67ec873c64dfd44f7bd25bceb52d08662389cedba8f174db93c24b7a6721170eb804d6953da084a6454c61780a8c4cc9a0ec816d26c19f0cfd5edf20086e037fb3905d490114c107aa59a08a3e09642f8f8197b7a5f121a6c61a3f3d1a3aa7e1fb7dc2974485f021eb0064da66515b41b77e0b4503d6c6351629f55f7be94859f4620c6aef4d34e2730255574883f33b4de348e09fd3089be315e1e775506b75455c5acd7517256aceb4a9650d1a6990f713b44512526d3cfdb064df0b0ce8788ddb2a433a265c67a6bf5a5fac5a5ee633008cc4271d044ca40e994420cb1412dff0b2c65b479dd20679a6ea5268e8a0d33b48530ad1b822111b4f312a4322ae2e4b7b00c2b8c4f1094fa7dc55798f01141d6eed89f88089cdb288315db50f1477f64b48c6fa1819c38e03e52e5eb563ad94a729dcdb8916a4794fb78dfa9c28d182a0ea12e5a560bfd34be1c3c413f13c52106e604b814f23f18ad3826a0109fb14c1adce0345831d39700d6f4f9ef22e8ceb46855c3609b0cc5c34a5fc546a97b74c5f3bd84ebede1e798de74ad54d963625cc00fb45e7652481dceacbd7280c30786067bd7b7228a0cd0c3427087aad96ab11684e8b59865373102a628605471a476023c9e79ca1096d1ef01f9fbc1519c55e19286e4909ec13dc20318f434c047a6ed0887a1a973835ff0ac7b757ccadbf24febc63a53cf5add643da960a5ad78b12623637959940ca7185a5b314e5ca1a9e7d777cd5002f682e05d3fc21e1624774086d5451b9420ad8b3d042e38b90079d78565be5185a68c4b113da0abdf7a758c35cc636ee7dddd53e078d77b8cc54701536b979491600bab02832332def1bcd73b34a441b525ae77aae318aa2786532684dc5ee13f7fc52a0cade15b086cb861edbf21f0907b2e73f50fdd3509799bb117522d3a5cc643cff9d5b07ae50f188441bee420d8a2efc3bb013f582b22439a5d0464919944fd8816edd694d827541a5c8eaa3aa7bbadc27391b82d8f442b5487f7100a186818d67031ea3103db375899b1eba23b3c0f6d41fee2eb18cc795a8c2723a26145beb918792112cdc5f5dcb93e11f748f18cc525a3459f474c7744bd5ea5b1a251ca33f199bbc87ccef9c0a3a43e2b82dedd3102ab17f0dec9d281fe0969c5f175eb90dbeaf92e061b667cf5c3819e1e3d5a91eef7b6a4d5466fdf2091eed321ceb78bea8e395573066d8df8b650215771b9d3f2e4fcc354a4fda1bf4d7fe5901903be8bc7696fe436a72e2e8cdc1205e754af016edf187a8f2cd2a2da3b335ddb47bd83949000d9743dc04ad0621d42471455f2322331e744ce7246dcca279a5f9f30bfc72d4e7de8a02d24930240280457b0cf212f465b7ae951c7d7ba1a54bbd28aa579375d3f3161a89c3df9f1740356e578b1b364aba992121962e36f2b96d4178dc1142d1d4cc5174e9b11030f6fef82541da218bdc49b6b115f967ed929fb019f2df5f51ea9c0a59fc6beb7ea99c30d08093d31f49ea9856bdb17eb1a73fb953ce362203eb7d3cde1b8d1a55f81233dff4a54ca3f60bb856793a855571004363099abf71a092bf3ea3fbf7f5b7241379a3b1347c603b0489995f60fd279c973f6b9f9d8cace18f2b7991591bef90fdf5283c21e65ad024fabac0de9a61af0fb69f268e8d69b5b5e0ae4001907a8715eb5c7f5c2d8d95830bc02ac5bbf059791c0fd28d1d2e2f6ceae94de1767058dc86d82e7bad218af6ba59fb1dd951974c274a43de9236d7f5c60a4394104c85b024e99140ec6a5baaede28bfaa256336b4274ab905c6e8bee5e92c55e8dc917c966015c6315f07c1d8c822d5dd036180a5dd2db3f6508ed8ee32a6a0efb0a4d1b8c59f26981f70b811ed26818c7004fe0ccf44e758c8c28ef019448be469422514de3fe4603f0cbb8dcd405029c7a63ae74a886e4ed392f374f742d4cdaab4d326160d47d559b8ec21f725d1cb9827bbec15599ab7191ffd09b514b246b7b50404b68ae576ccf2fb6198ff0ea591a7f15355e43a11af4c61625ea302a805b39d7fd8fcd97ca915b53c989655b2ca666bce5d2fc384096bc3a55d4e0d9d79607e829a708e888f7960bd6ee06c2e4e11948c2d35afc04b88cd50ce4c06ebbd07bf54999018bf9b2fa092d1e9436b4c1f0947123b27e5137858ab55b1a9f8913f3667d1c588d362065cdeb656cb0dfc1b1fea4b7e2eefa2e38ef09126c9a39926a40db466bedf2df53dd0201e00c914bb4cefe797353039141de0269ab7719d9d477b184ff63bb7634274af6fc5169f78fc65d1aba9b8216107bbb67bdaa3bcda654722add7f2b32582e798f8d074c838ae7b8c34017e30c87f5764d82bd020bd72d544a4fb2170061c19ce40d10fe8d11d5892a4db3331f055a757e6b3a42ea973efdf1e1ea6436755027ee967a0499b762cba390a1923da2978706703f28e3fb01456f3d5352c18644f16ec55e38a3907a13f2f63ca41b75383a855119e5d22fd60bdf1809d7e30f4b62beb9cad5d2717d97d700fa14480cbb8d6053ed86b2b618c5b3f680a00518dfc4ad6236c9a4128c8187c71bfab5c0c7568b51b2aac54d62b9fd604dba152c7c2b27606c3f8091367de8d04b703d3c3a2800cbb8b3b1b0cc50561fe09059ab97233a827577a435192fe00e8f1917008723551ac688068fe5c75081e33c62a23786cfed5a5c10b1e9b763cf4b7f384e8a9f382882aaef4263e8d4ca32106eddab2f7c87f7f15ef114577e0f1e0e3d5aea118158415f930e6b0f3135a77016c65f3dc810d64d161a9bef1611b7308fa516d96747743f8bf9a60decb6dd4e9f7ad9df420c25084dad62257f963e49288ee0dd309c854f73cc34d4a53edf54d654c49047b23a59c6f13f65cedb3902a226b61bff68b3b6e64a6f8c5330da8e636e0b819c0e76fd426132dbb12436a4cd280afedaed90e772f3c5c46c76cdb6fe6e7b770a521270dd6eb1510e59713fedbaf874434bd78f297bdcc42cb4c2f4da9fa018003cbcdddfab79b432e77cb8972a130a8b4c689d23017dd1753f7dad7c48de8d85e3933f5c9d718897119891c38d4a3a93e7454abb7808c8e4fc4fa230ef4925d0339e92d1ada7b5cc5a482df4a054fd1603548234a7443bc9a2d7b664967c3b5b84b6d9825e27f0fe8f3626ac41985f54807237a95cb2a9af87b01a7462cf43556b902a33b335cd17153b08780528d56871402c5a3107b1f40f1d62548b38b53346b7434ac301d5218b2a5b1a02078a1dfd4d75a27499398d1584dae3a7627d2e53de5c3b6bbb8222ad23a904863d68982a4646b027e21f6a8d5574398bd6f30a6c913027faee3b880a4f5c038a0eb7d3af133f96273c64c408a4001870a93384474678001a88789795ef91a3f214823ddfe8b833f64f61344e9ef3c8923fa1d2ea35927e1030b0a70921842374fe8cfbe1379c0d23c06024954fd4b4b77c98c8ef93beb96c6b165aa1669d32a0897eb705a9031f9636fc9ed8e98610a24ea878b8349198945285005435485833e707b3a91bf6907506f119aa4da0503c5dc7baee6e855ba98c7cc33abae0e23dae57cb8654e1e90fcbaa1ca35697a25061d225ba2b626699b4802515fdf271f324a3af9dac3fceeab0339bfbbf00934912ab1c05f80cfbfae102cd39c978437c61b17d391788a0b0613b86b51d8f5079a32f8498224025e835077f57ff1f055af2846245427d4cb4fd97e6c526a9d7c417c0f6e051b2e1d3520a45b6865bf53b05dfacaddc4a39ab1ac06657ae2fa80ffd19003e2448de43cef866a44d59b0e211264338bca0bc480308fee2ff9950660a2813029a85836ebc758abecd548fc96c020eefd2ae1ef93435cd692b26b72e3ec803eefaa7b8a908bcfa74e939ba82f50a49a1c4e45ef0b6b610f37911f8d28d01f455cf17588d9db97c47780cefd4d32be7c9a4309d31fa2710d4d0bb3c847e8b43cbae682a2974c01ad8fc5563954ec75094bf3f4e55e28728bc425160f4e9621a3a41486dc4c641c08ba3bf170b715d486e355d5059277007297c7a8dda904aab666a411a92a8a88e7314819cda8089653c38ac789fa7e1dfa0bcae9a9aa3eacf03368ca0f59a47e94af0b09a564602734b3e768e48e6b05eaf7be03586a5fa03c73121085295397903994b3c0238fad942ffa17bca3a774abdddc278de187079b60e00a4c775409e3b61dbf5a59b02df8b77ed1fb2ed8041dbf88f9028e36c78b20037a5d2bf60b1d8f58638e3c743289260a5bfcae7946951cd640be6cb80612796f746cdd76977522c4126101d3f0bb0b1f94bcbc9728cab904397719205ed21ee1ac9409052ed54e3e46cf391009956f1884a89c7f0c2b4dce98a5419cb78067dd7a1822b46777dd07945d68d4d7d34698a1a3f7ca2c4998835212e4ad403fdae85a02c45e226b8318abcff5b791182b64c1c90b118d9c29d91dc147d5818b3681154d32d3b33ff8e56ac2192d4052527cf7e8566b489135c78286ae6ac87a295df5d6561a9eeb3038836b58f8a563643afa1680440925d5398883f10508741be320ac6c58d230f8eeaf63140d6c60ac900d24f5c6b5b60f6d813edb94c991f5676157320f3db6fd33e2a8ab87e92f7f7c7718fb507399a85adafe3942f8267216486910f6787ad1b455993c05fb075a0fb6f048d01a502d8b9da96b3752356a944339ceded31007ea022e0d4796471fe2661040587232758e46d6714f81fa53883bcf0ea5590a9ec9e7c4b94a6ec399aad3b81f60e8a2a07f924efc52701ac53eb8e25718c301e16a373b10cd997f7a2ecce6f270ec04dd4c27a2c5636b6b20889e4e6911108764a8e48c2739d83a4e8e66f95a9121899625efaba6cd348aba1fe5b60ec1d6bd2803cb5eac75efab6472a9bddfb6453bc98dadc6c246a1152484c31352cd7785baa371a8785cf59f64fb0e840acc2dd9de1a64d17903e5cc707fc986271dbdba632caf479829eeaa6708945d4003ced1017b4769ff7038f83f32d21b67a3ee778fa1a5d2ebd33776dd796da8466ee68d07f35ee4107b61f00bed982f94d8069451b9b042e78e5ac365fc4816c2c9923ed63c483c7d639aa16e7ad4fbcb8354f748bc3bb67a5bafe70cb48ac175ddd9f9c6d86d58e0911e9bb5f7e9d598012f3135733a0c8a9106358d429ad64603bbf6c57c5d0a3a391c591dda9168bd1feae8de23640a41f1d7d9cee48798e21d0fb4a8d7cd8d7652d991a246b6d57d338d7f965d7e4683f0ae48eccecf0a475245f9f33bad5ec4c688943553a0f94b496de195b5eea86adad1adc0afc65dda9f70e43d853a184df9eed774a322a97d8d1b82304263067b17a5d50928baa384fae2bc007929c22c05bb74e3c7435330aec68df4a40e9622462881a9fa160a6e9493aaf00fc665c2baf1eca494086ee05b59cc74e4cfd91dd0818fcea0f53dbb50b75965b402dbd1e82664a592af2089980799ac343ec0b70c36754b9f7c82d237008b46994e8be4c6a00b545bd1186e370dad3e398e61abf20f980dd4eedf53003f9d3307b30ea7105590a29d2fe55e02388339be7d0cb8a243fd572bb4197e5fc5f5b912a69f09b6a5294848bdd19a652da4fd5c638d9d7879c04bdd441cca7a25a80da135373b5febeaec2f36b2fa4a33d143679e4eac89f15e2cf2ef34550c590c40426a854a627bd9c5ffa2c85cd2e43423ae40c1764a6b07acfe6be5040cc3477250b9fa725f4382e57e0ee2babe440e36434ab19b0ec2f001cbcf821788c2a02e8b2a23b525a7a7f01cfced901640d87c3a48bac72169d92d2c178fbc4881c1d8e2a9f1f411bbb578950b6c5b34af6013cc16af496938044b983981a97ef8031371ae8f028eef7d649f4a0560e70153ff6ccbb72c807b1f39e473de24b6d27d7b6808376b7553223fdc63ed22eaaa595c26e9f3ecb30fc6c9e1586a14773bdeaf2da5c6003e783ec65dfc25bcf354afb64368e9c538d3708e5dc635e2b62ba7a9f62344331510b7652f60e04fa399fe5723935d44d6cb78be7ebe96b57e7dd384d61e2b3cbfcd739dd13ee46959a537091d60b373acef04a3889286789287b060ed76d189fee6708c5f8f1af9119ca241f6b934f0b9a78d487f72912d943ee74fdf7267f2670e6ecc8bee4c2ace3dc3d6f61e3de2e92d993450d10cd5e29ac7e91c0f47c4e6b142258e075356271a64b122348e072762ada3f11899d5b71c8c0680a6b61aa142de67025b57640e35c577d937953d1791e1d34ec1529e7ec01535b875f87417cf4194e36843f664d5c7f72aa31765900be30d760fdad5245d4696d35a6cc05ea853173e50fac202a4e2d903b3d876b34f6ba09ce6eda810b6e5e0d47a664072d09967c18f71f37917aeb9e47cd1c5d02f5da2c0618aeba171719a03ceac7f94aeac2f10363f09a5cb8a3240703d1b6afe0d3ea0d00a72bee9acff19cbac28be12012a37d55b79fba3705763b75d16aac8209bd164771411b3ec96c0e7bfaaf604074e71336e1b60d8bb53801ae258c2d9659d9780514b9319fb8eef09bae972ceed2bfe91f7ce41c861b3c186be4119306f94d9b0a183ab0e0a0c20dd5e217cdecac65113f8a7c320e177544e6eee8b3ff4eb76ca1196eec3b722bd0a16ebefe705ef777ff4e86ed751c6a7f80562f1419b9e7dd01f7f9c0f7b919407316d824964ee2c14663b3c3b0602565dae13059336dd9afcfd9042c4af74abf25b5ad9e4bd49795ef3e01371a5e84084494823b8f93191d14a1c2b9d059be8169866222ab532b233f7a0aaf1613517cada50f71057d6880e118bdca0d8f6265ba1f8582faf97b1d04d8d85e26b1b22f3a501ab930aca7129a05aa7e8f4ab2d68110d0f9684cdac3af5332c753213deaa98a7f079eefce267cb69f7c67634c74eada140b30b3ed83a0d2f8ccf65e73d3259942d1c3dd5de409608612869a198e21c51598cb5b07d674daceb0df4a58640716e2e139fcfedf43da2de3a64a4ce6e7a8f672f3590a1a2cda3c2c4edcb1a885b09343439a15c62b11b26b44f4ad05935722fb4c98cdae873c9be541c207437ffbe2b7a80227b064572552f878e39f838734fde73ba21d8466f8d62c83321d354a10c3a6dbc81ae53c57bf03dee816b7de38c4144b98983e71d9d05fec4c08c53622a70f80c9b268928b659aa6cfd95fe6fa451d021789a9630a71f7c2b1d20e73624d3e7892591d9cae0654052e5caa40aa689c66b111f6e70ef4d98382a0e3123192eedaeb23de64a2f22e68081e533cb840c42db55de4a3e5646c9e0d7b3fb842f7a640dca6fee3abcbedb40f6ab311494961ee2e823189bf6d8aa91f46c333d9f384d3cd43ffbabfadefdf7a7e9a6f7baebf73a483ce68c775af34e846ae009318e83646edb920a31ecd4ff3bb0eaa9badfe6a1d26c66b86ee69e23c67abe4eef8638a6abac625605dda1229853cfc2c31fcc562179a6078db6a82a38e22c3cab143ecf1866d9fc41d5ce8cfbf1cef0eb67d989dc40836246e5e88331b479959983f4ec91d24061c0c88cb7f03c78a1b76e4f9303a2690f0308542bef979959e06f78a4152eca5f9899424b086320ad320025b893310c4c36aa0be34532d9f025a88b5e11e2b4b986b90c4e0511aa0e2c65d06de49d85bd1d7823cc819504f80b6327913e2b8b5aaa491829ec66d9b75ba5ddf5a6bb6ababc950d1046004fb04ffca53a79d8673d695468bb6cee9eed83d101e0cabce36add74ba5730ed7f04876e862fd07a2998c14a0bfe2c9d6d25bc8bb8873707e97197eaf78390a080744790bd61d5dece8360f156da94a3a61d225f68c92e2b0da4a59817bf7f81ddcd16f3a20d649f82cbd38e18637da7306f8968aca7de2c16072626fed7cfff047607001a7bdafcdb52cda29897aea74c59402564d11e9ea0edae44872ee60a8011867cc53fa38401594a3d38a32e060d84c0329b53e1d9a8190a9358f4863544efc945a1d737cd77f5c6e598605956b7c9211e4d8f880b2d8053511601d6289cef348e1f9727598a21a57f99e830d030a2db74ca7e0d1cfc621fc81a05087dedc0b7a3f4a302b63e5799e50d3d9de36534afd1d5e209d6756923916010f94fa44f079fd8f6e6f27645bb0abecf1eec911e1204f529db990528d435293524904cc5362a533d278ca267085ddd44ffcc2a0a023425dd41764b719cbe5b5c8989b40def1417caf775738ae660db10b4982064272a70aadeb92dc7720ca6dddf8625ab312058ced1922592bacd722d17566d4d8ef5212210a86c8c5910aaa06b43de1ea53aa011450c12e84bc58a1acf17b2a014a92eee2b8505288b880fcf8ddbfe7da413467a0a29635d3a6e4a9141bffafa63cdfb21d124df06b2aa82585ee1f178722d4de0931e88aeb9b0823ef4a4ad0bc04c97d02358404c8075ccb9bb71364352ac4a9223a55e009f548037d98f8c6f0d53715938b3fc607983a7990531605ff47322a511a7b7507eecf4889acbe8f2957dd5a2a53137b4ef3e920a2769834e23931f45f70321dc7a425b6b131a229a8c4d7c1087699c1814d751c7c806eb22fa120e07a3f7a59fdbf51c00494d1d9cc947b14ef3103aacfd51a0268469c76a628182e6b170dd8ce36f52b329f89543d85cf0d0c5b87ea24384f2242bcc35317d53e0a91c15d1c921657c05e1098611d2fda349760ac815b7c61d2da030e67aacd20358f54c255add543f22e5afee28bbeb969a661934590ecc1c3d8cd66633ed6207c7d8605a2c9d72bbf8fd6cb6652f37b51120e634c0a438938dfd68ddc941519e8545fea41fbcb2e65e77158f7bbce6c561a951b4ee6bbab3cdbb36ff2ff8e1048422aad5099f9617438ede1979361032887ff2ce2c2f28aa01f692116994dc05450f572ab435778731277000f48ae60288cb01609ca1ca60dbb48e35c1a85824934c9a7b8eede5408e3714b88fead02c511c162502bc4d0c8f33da4166c16acc1884336e123675460d171c33f20955c894bf05737a68e3081b5882f7441a2625daeef79ec96b244423a4f992628be30db48815a9dae2eecdab262f1f9c54df3603a419ed5fd8dd7e0353181c3035dfda84a06270e3db5c4e2a3ee695b2bf5391bb2008094dc4bf417547e0785c17ea56c1e7abbf5cafb40f9c428f8cb35370ab5092b38bd2b33cc88dbe85f7cfaff06771e4fe664cd89bda8f8a9e4695f4fbaa5b0bf148c6373dc77cebc0d8009c05031722303e7ff6e4f49660202ec20d6d6924d8b716364cc8ff60c837ad1086f484fcf64cf71f747bd0a72ab48b7c8ff61fc403adde3253fe6b6f8a726b20c9ab2b8efe03e6a22dcfe30f004b2cf5219f79fb881931be2dd5a5236b3bb3413e02548b28ad729b6e263c444c41d42d4162cf4e3c83e1c6985e84939a8c4c9f5995613fb353ea5f49acb808fb872f2f5d2382f451541b6b0eaa46057fae52c9d7ad15b2d2034ca8f82444098cd644d68c4d5003622b41cff80eaba989a54f1d9ee1be8515cd41caa27c85bd30875ef752e2e27d934f9f74d2d4eb94637968ef5a420a63676c83c942fe03f1dbd1b2aa608faa1a1093d9fde94e3e835ae1c40e29251a85e4ed40c2f5b7a2fcf62768650af7634c8153203024dd6d63c3d085c0a8ab720b66f022d1b9674dfc98e0ea5fdd854d48270b796c935102cc32ed0a389e79cb3fe3261537e741cfcb5209c5d4725161d2f4cb199737debea6b7497d5883567c00c967fa432b1fd511822a907ab5019e2d0289ce79d25561c95064a3e73bd54470bae829825cf1278dee01931314fb3ab93e573807ed7df3f655050ad51597e4d494aa55c08775be61c6c62ac7336454bad1bc8b9dcef3f6b8a88107f8c87dd0ac3c0d5372513f5887335905743d39cd66f20abde60bc0b96623dfec7c1d6c1962fa06495d9723946b1318609cbb7c87c1e952fe8f61beb352ab1f0b4b6cb14e970f48a730b69ff7be4afd9000572c6a83bf45168cb9948bebece7098e2b02e89b0e00141ca8e9e7517c3804c3f1d2233509100b5fb01138f8e50cde1414cb5d33fdaff786acd6f64854ff0d635bacff49aee8a62db2afdd05736e4ccd26ead9b99086bb046250c0de2a53b12a29e81b8f8241c33f737b506823c7259e9eb37ce7c50783ec24d858cf5503b8b044c962af1be529c79f2e1de78d64a728d02ec4e9360d7d69d5bbdd9a13e2a865e9cb6f9bf2c09a5bf98e7345747df37c335dea394143d2fec9091d3d3d237fdabbb4d21429ac60a0b83fa9db382dda7f0d444ecd5ac1009aec380b5a7b6264b9c4ad50b67575dac4a28e96686004f2371267be1cac849ac5b900cc51370d5d6aa11aab3d73dc102d91f329a5e94a60cca5740a9e02531d35e7231558b28fc6455d11b6b3e88dd9b8a59160dd646746125cdae8f19ccb41a47d33083e93073e67ab23d4a04f5c2f76d4b02340183720f52439083195a2964f8b661b7fd05146c1c6f783896e3b5cbf64a24aca407f3791dcf76103bf47343720029dbe239d897bb2d66d766a8ab1710ce4a1ca111d11e17ab3f29168f315d41338845bbe3ed30f5c75a0406c1d3755155a9860511bb05e8b5410dbd6836f0429687d10ae29b2171d0c2b8be71c06322ddee6cad1b6b6b3593a77a724184499a59a00198b83e1ea944ae574f00753d64191bb00d69db13db487946f03d97a9aee4b3ec6e105a0c771062349ee4d32acb30cadac76d5c68b00b26e956528f2d474a188a03489aa88744fd630f0c889b512bc993046919a97480e96ba2dd6525bfd567f95c5588e14d5c2c540393d7e11c9438e07e00141700610229138e94f92980321ba75df15de13b17d10e4d5947122e217d488590ba802142d1069ba847f060bfd6de1bcf10cce23a22515b86c6ca7f037aae88cecb0e252c5d76c113b07eb5a463ca95028861a0890bc3bd228cb77efd30b68fc8b134492b42b5a16b0b292a3ba2247d457844ba775fcefb05bdd7771fb79f40bb60f48de308a953c093a5b3986c06e0f0ca3785e0060295a59529e69007c7a688d815deb50230adfc90c3ed1df4a9f65e27c05a7272ba58f5a9165cfdf07119fcc228e0f5a32eda758fc690ef6a65b7b887b584bfbf54e7fb559202c7a6fc628c82cba006ea866cf288c7d4990c4ad44b8242bbf80f3d13658f4f1f8397349b61ae4dd616dc24d0fb3fd0df0e267c2409806439d14807a0ac4ebfb84803c3cd0d92b0ba2495fa4f8489ce8057625be66fd40cc99539c98f7849193aab82767fd2b4badcdf70d57dd86b0f670d89c24a97da7eaf510b6a73a95592b287824eb7cf21563f0f8630f767e80c3b04c202ee36580fee19fa80abc78dd28017a134093722db8b5aa122173ed4d1eb0765605bda2e82347487c78e065a20c52862cab7d059dff69c5f01ee140330163df3fbc36f989f9dfb39868fa608cd0dcf17b9e3bbe44b4bb31b790bfdee40d8f62d1c6a9e8674ab6d0bf11c151d4dc4808ced08fc03094f0edf5f73123f877967921b0529148db8f49180049836fed2ff509ab28b58de46010ecd1603680f0a687626ff97a5ffc01f63081d6063f69385b5c8b40c0c68c1efd500410a4292106afc743ae5b054936ccab21c7946f899678d57c8ea324d84228c2ca0b24b7fbf3354bc8d5697571ce0c47744492a2e906aaefa9bd2ac2084e205dd58e25d5427ae63f65c905c2b7c3c6abe82e8380334204dbdb66c32722f645b8d0e054d27abcd5773280234feeb8c7fd907c568dedd247c2bed67d547dd6fc58faa6486f50f4da612bbd9cd68c4d0b24ce2df10a2eecd4183753131eb191859768ecef0c32a5f9e26c8e85b068c9a922bfa9e216304d37c2c49984d16450e0d2584440f91edaa6ae7e6e7abb661f436758f067a9f0f43ca9b82c4edb5583aef9da02a6d0f520c3655db6916cfd8a3d8de73cb6618ae56ffdc2e16de5e4b3c86881d48934b73db6b47f144e2d42659034479b1b398fa2ad90ecf6bdf22c923de9e304070793b4d32ba7f4f05535aeb3a0af4fe3c90442323b331a5416dcbea2edc590ea359fbaa8982e73e28485e86401edc51b72560afee7c4d79f87d7e6e1703d360707fc01f17f8a23bc24e98d19d9f37fe1d19ddc2754123e878ba26547402e9367359d40ef916bc289f36a5b63bdf336a63f4e23f211a3cd0afa6763ce09237066874ccc77ddcdde214e406275aec9eecc7c85ecd666d54ff08a508af33c958cb1086f0b63ad85815eab4617d34a71f486d835602504ab82ba172b559755e6a200fe09e25d55b12d49ab4db4f4f5d134688b22e755a7f75d6316f4edcd88f87dcb8a6bcc9eaddd31d9508289271d64c956743aff3f6666f80b7774d44f5d6b35f10c31f47f6a420450c0ba0f235c80f6b5078dc5ea1abce0cb0144cf60f5634e6c24cdea5f4140b581448b6c70950995fbb5d96499dec3c12dd1260f26c81e98ce7a6dcc40398b9d1efc62c46908768c6ce60b04be82c2510f5411ac9898bcc04f469e842ab79b2abfc0aa369e9d993f37457a2adaedfcd84f52a127b854b038b174e6fabd668ec33cd8540aff6c73381732d54b5bcb6ceb2ef10336390e0747247032da9db6b275f196bf65f80d0d1eb6d06713bbab48bcef4cf78011abaca99c0df1f237ddf34a5e09a75fe1563e09425e2e4291d50bfcf6b85dd25fdd9d0174f9e654440e991296c8c395ac6bdf03d54827a437cdf8e61b7995adb6f1d5cacfe2faeaa368be7d37036b4d40fd91900ad7ea668fc0e614417bb16e62111cf5be8ccfd95a17d877a6c900fae797fa3b6162824adedf38dd59cfa949e797cf8b8bc5369bd3ab017d2cbb95fd6637117c08582a0cbc17ffefb5307a512ac322d9888ba36bf4d8928241cf69e0f866146accac95aa59a7e06115f5db1c732e7198d5ee86186709000915a5c78030966bd95c6e53e079ebff20f65f7bdadae2348880900c1229433ec0c5ce1f22c7302a32cbe860549dbb89890f917ac4141fdcb2900eb0f840f1bc1d8e083e5d9dbd3b38a12338345581e53068a07be6a1bc605617b87986124952ced9c7d199b4eaccb366dd4338b70248db90936bada8a417ae357cf54bd41e70fdce12f05eb40b47bb8bb066a3e7b3bafe7aa82e1c32f14ec3876d9f5e0b54e66fc603929bafeaa0799547265e800859f28a0671ab0c8588b015dfc2a4149d09dc8ed197e74a58fbd597ce28b43d56d8a126e508db508dc28b8da17a6880368dd884c0d1276cb9fdfaa549ba4096d30b58c41ad1338387b3479b791627ffcd9c542a65ba49232d7f94ef54d18c3fc60c29d0187e228387598f15337b64bef3f3133e8bc367a01df3e65efd14c8373c245195380ac2cb2b8760b8278470c4b1bfb75fcd9e523f5a69d9d962b4695b5f659afe6d24dbb26a0b67b857083f7f0f718a0110eb879fff0c79a80e5f72c13b64e8e206a4b36259d14fa31cf01a80617e5deb4ef092503ee1a01ad90e7115ef89ba7a92c55196d0509b6c6c15918d738fc1326da169974f6b0da7b77da8ca462181b27d75157b757490457220a7c7e93d31862e1230c578f8f11538a54148ac72338db3c11c6e494345f7bbd3ba0a486f151bb72402f641e321251c08b82a2bccfbd4d8d93204752583dddc8fb36dea1a80fda6693d38b3489ac43440f55b8337d5b3d8412ea35480cab494dd2cc3a8bfd913492750a35ea78a25bae3221590a8d5a8e054e53c9e926120c63daed779ca354b86dbdd5d09ed5fcabdfce30c9a53329e8517715ffb89d3a89a331d37ce70673158cc04e5b75f43be68d78ddf9e63b8b35435bc3812844648c36b4a836601cbad9bd754cc0f875158a627e48f6f316538fb309532b7fbc1a3051b2bc7c9bad1889397415ebdf556be1c587513ad8b04aa66e5dccfb36bc57e03853f52e73bf1b4aeedafadf7756fe07ce66fcc8a14cd8af03069a0e5e6135045f0f8830b3cd3e2e5f23b91378af1b2a22c5b4340fac8db4456a1b165d76975caf3d4c263abeff92b6ee9dd9cc379e8321258ce4322bee5b7bee0ec05357568db17dbf4df684a239ad0b678c40147e73faf5ddf2440e92142682ba82b54297ff48f1a8839dc62cd524a62e932e39d0eb2cc7db4254f210a70f80640df0aabcea7beedc496baa5f87d2a3be20124f456a1262990e93d1e850e0568ed0354f4128424504d600334278146ffc052846424e6ebb233bdb1c561709b182ec7a67e371eaefc4a09c650d7785c588455d3e5c6f2f0cf70799278a685419eda16fa0adc03883ecb9d71f53d3a353038b29fb48fd6c4c804b09e8be4de9f0749e18fc80065694ec1662cf44de97f2fe4fa9fd0cc32b04279c3b1a3bbb18924c98b524a55a7c5f4053bb65713d07dbcf74182e8fa8a7cb1291209a58deb47997b5fb5f281f6028515af21904215cab456e90c004aceac3e2a1757b8a8963d9a271a80e1856533c3844253679cc8f495dda6addbc98d8d8f0b1903b797e6f8d6736902e535e5d7084de8b98a98a265d60f091a0ff8f0415c654877ded29c9eeb95b9aa26abb6c7a21076ac8db903f358e94ce38b0adbe7f61714fa8c028340bcf92b80126181f30a082d88ac0a07c42fa4435ee0dee6fc6917dc0a0ad0db573c024d1ab868d72e876b0e428624964c952ef79fef55f85355821db4fe1e1136dacd26825abeef7971ec42509da3370ea5ff31971d5590e03de35096ccad299602a55047c8ab3485d41563196c5b4d3880ab605c73eb5eb8165ff5ee5473820e93e46d42cc71f700982a32ecf64ff1a565e58ae1a4649dfab17096718e6ff5df6dc059e9c65c4538072336a6ee1ab29d9138d017da1971335e8a71a433f6d13781bea4c80279d2656c0fae2b011198cfa13b0989e228fe2a8c29061d85155d69b51814235b03427276ee228c79a9ff8a3005e3260a942392a9b62ac89181456c40e9665a906e6ea42ea99d4a632c67f6c7958b10662b01541fa53b2e865f3641515d2a685d408db977496d8a610f8977abfbdd27d941c2add24edb67d0d86a7304cdb9637b75f1a054d228c600ff5466a4df03cf5a2b53c367050e8e7179be9e76e8946b9c3917d146c43a78af0756011af07245aa3d74c84752f9b0d08024c532b077221ee8e695d66e572a66042e65d47a505dca98578c8adb02f7d1c2a94c83f459d58f5ed936a9274805dc4ec5d29788edb8b6818d3108f0b73f733a623b08c15e176e8a40cc179e8371f04a55418fc081e5a9e9d2dec90b69cfb0ede5be668284e337c7a6cbc3209698fa366c5c413e3bc0b199c31409c729abef337ee7073b9b1e529d3ae0418054371e9da6979973be3c56b36e12c85374fe238865cfcb55e5f30ba80ef00d2bfc2c79022c8fcc31cb137c6a878f9969921d604ae98899e9d519c75d95171073f12adf00a2ae17d0940407ba4cc0cee2bc82ef564a87d5b0701df35e417cb9a113ced67f0af049aa5953c3aaa3bd40a5a8d56c8280c0b12d4f0a3ffaafae3503637c889eda78e7faebe83bb2b7c353ac04ad8f36c3d4e587c5ec1e0d63c2484b0aab8b297ccddaa1987d96be1209facf100166d11a7d17203d6950cf1cb3bef2ef2d4039120464f7f828f3f7bb99bf82d170ffc320f5f5bda400fc6a7f300e446033bc5f7ebbd4217d853f379fe1b197a3e1b3e02166f4d54394fd3a014b1ef6f3eeaed1cf8a538051b192df38f161a1afda14768b792c609e0b612c2826995414413f65cd5c6f827e922e8b8d5cdf5016dc62db320a212aac3d02a4c3cbd86b6a4381d27be74b4b718502e89886aa485784f20d6eb07a878886e2ced02a9f34d42016b8114d55eb0ce2efe413d6f0336ad4294fa6683acfcdb79520688fd7a2db9fcb9f3e430a24b3ef359467b858b3ac4aae79ec836c62184c2d666d2b1dc3ccc21db6dfcf566689f87e5872e7938524312c563545c8c7c187124f3dd1d4bbcccb0cb654d32efe785d4afa96f5a6ff9879ad1f93bea579049e46edab020ab9e30a9e8e9be68f76e2bc02c51b24328eb2a6d0a3bcce9e4625b0a9bab52b203b50fceeb063d898cf3184f400447443eda016f1874631edd5311134d726608a4523e091b7b8b7eedb22d9826274cf4700aef3f177196bfc1f92e1042be2439c77b4bbc1dee8e5ca28a69448a9014a1a9fd2d5d14b3103bd7cee397fa9d4e34f097f3491a49d50a5fe66e00e92942740839994ff9a88db1c00b8e28102f61d2e0a37e228424a33e8cb64024d34a8de220cfd2c51ba0091c2a227dc84714a60f0f9e4a0fc04cc852542216c5a756e33c914e18e3a297556ac19ad2d796e633de97daa53ed0d66ebb8153f8cd2fd01b197f9c8f3bf89805c694edf192fd134eb942f37820971df1bf096eddbe609563e7637891ef1ffd8c1f557afbaadc7de1cf4545647ce41e51444591b072431c4ec6cf094c2716c365ed2498a81a44f4f10314b15927dfea8c012dc089549f11800b76680a4dd946003ade35ac309a4c420a7517cb1399d5f8cfd91ce8e8d92dcd680b66e6dde5902a4f86df016007d489413689c72c2d4088f5535aadd953295be45b2edf2282593b14f9e77cdd4af04c2d7473b1d3b02ddf76169695ba6320271a1e0d33a00f50c024622c7f6010ece88e03e0e238af667af9d72582dd973e84bc5477593c5611cde8d79285b41c7096313bdb1431520acdae2c1368a2fa5fc7e2012f1d23d355c2bbd319471b77a22844af1566f668bd54b52b4500bd8887a037a843b747bd46dc97689bb32b00fdc518add482328b30461954db7edd1b158a3a2d97caa11059a1bf397ff81b971bd24e8ad96067edffbfa3093d7e9f34639eb5551f3d6e9bc1743d8d6f98a66c9311a14a86f761171f931200ce45d62c34341ff4f677a24b563309b3441b799c2d66b755ed84c571ac1a1747a8e2d460ae239814c142b6ef7d0f1328e11b620b956b5c5996372b9305d001ff4bbf6197fbe40f62d19396234191ad09cd27dcfa0a1410ece3a7355b4e59a6ec71da467360f0a80b9a47fb212dda2a3b2823822511289a69f3a48f6b913207d55a2735a07333f283287b4177c55d1a309be5287b23b94b4f30597ae7a843797dfc04d9d32f3826138f772116631bcdc1f0f252214c0be07a269d9f92e09071221d12e2f4df8e846d6265f78dcf95a07a6a66b2e054cb1f401ad7abdf81bc410532741465850b06d00a282c81561daac2a7945894d2e9ddb80ec7e84dab9c9444ec9a48aeb0bee07eecfb87b305762aefe45e613a62b2e1b5cd2125b945bf346aedb4f1dcb01ff20a70b9d55d445464330a0365ca78ef64c280c25e0f2dfd1f90e9b3dfb8255db412b2793cf91a914d66ee574d8c5481275f2ffc0bd20907ed232e40cf572f650f3b9b20df3c1da05cc511eb1cafc01f3460520d59540608d83bee506c9d49c5dae91f91cd4bf6af4e7ba7ca1913e0e3d22f3fed6f9fba77096a4bfcd09b47f26f1ff64785634666c682e0a2113af42955a70d68095e9567d8d65a7af37ef9d705ad91428222b88276c01a82e425c044cceb2e4d950d3109d1d1f0497bfa5308782de55e93648329c06aa7c4d0216142a4371d0ca5e10e08afc4eda47d343c37b63dd056e5202e3d397c0d212e66a5f1d50406d8c0eb047a8727d7aa0a6f5c24f27e3be894a262862960318f1e3b8ba8a44433f13932945e0108d3ff43439f5743501b415c76d031acc351ca63299f8987ed37e74d7b5365d9057d7fc72a5f8baaaf0b107b854887b09cf2a1c25a684211017a66506fdcacdf4067505129a0c93cdf33143f0cc6615644217bf6c9e21138174877b948dfba3fbef1ab9e99d5a1fdd4493b98040f186aedfef02e1be02e6ee091b958ce8cb0165eaf629331622a5c9705475faced7e17483e220dfa71cf2ae6b04bcf9ba76ad7f2fd8a07d23a8dac6a99ba8bfc929b403f86c64991af57d8c68d8c820645189038691069634454286100704555d647db9828d0007fe4409c9c35e1cb0fa283519dbd3bed45025b60a8e83c487025fca91d8e3e3d828d9a182ff331ebf1bf5a7244fe1edbbe172ea0af4ffa4445a91d168896e42b9f108de91866970c31b6d4d496e00d6f321c933781981aab303bddd364c6ee14978f6dfb448cb4882843516417bbcb6abb0d800d812d2bbdea9d3863af8d9e6af982d0f4e95a1d53ea25534bf23080f6077ca0a2579a5a810dd8615d6ed065d0981a13d10e0215410049c4f949dfc343736edaad8a42d443d0b0ef5b0a342564da2959dcae5d02014c8de1b16f15c0c4db6129e6bc1c1ae74efc756d3d74aa2eadf1236ba95ac256450dcbd78111bc9e38c0aa0c989f6c0d11169ce76ad9f1603eda75de1a7994705748b07e2fe33f707576c80983a3ab82d9235e63dcee29eace753bd8d2e766cb8966be84d9350bc082cb4f71db2e50c1912acf8766193de087a5cb51ae7897a423429b350e314fb57d0fdf3b7419d72c257df3587c919b6dec98ef14278a6c0eecdaebf830875e591f4aa98d9b15c99aff07071b1a98938496a230159b9bcb091b064d44276ac168268246ec19bd67d56453181b12e453ce92666d5ec0b18008d0f925f8188354108fa508834cfec3f84fb836fd40b3b6ca1d7915beb26c88ea44fc9c7de840d8d65cb149ac6bc1655923cb48c449a36e94c256ab6baad13070991588e4e61aba968630df9cf5783a7a0991a8593f0fc7f9fa01769feebb28e8dcdcc77b10a795b0650b956a94597120825018daaab3023cf6091bf7a9cb98ac96078f008731512a3caacf61e5f764d8a57acf0ee61661527361ead048be13be85ad95733129ab05e4d521a1da81f08107e2bfe8bd92533c9d8a756ecdf5b6b9619430b0fb5e4684d0260048bba35ef5daf623938731956b3eacf37ddcd8e268e3bc5351aa6b85367d7da0c0cf37fef0cac309182b6571fdb08775d887e75489865b6380c599551204a0a21e51e7cec8299a31180f4158b573c5cb6d3a8a3bd61f668c36f7337443d231cc60778b249ed011f3c5ac38142cb3e46fc604d40d33084180a7e4b128d0b5c89e25c439179318c088359b3777d2c4443159057c6c5043471bdde99710b04c10999538670a290935cbabc7a2129441b426c046d33421a35e9168b8d4b1b1c6ced3adaecf038539611e5a92bb4657cb937e775325d2e0b5953b448e7b9cbe586af04d7c1aa4442e8813ee399c5fa3a9f58ba7fdc6865bd27df63692294d041a158343aa41f4686de6c3e92630fd906b4c3b16cceb174e2ea7e58ebd7447d3a3d11dc5cff66820d6a0c7364092aa0be5bf0f8d6923bab94fe830956799e47c1c430c9bb6b6dfc9a649ec90baa78b2b359b288af9334259a3bd8c3437ed7c884fb94a0e5eb7ac86471d57f865326cb15618681198e001ac8822833ea83b2095fea41c8af99be51ad2c97308e88facd973845286a98a592e86f5b93f2a6bbcb72ccbe03c7c21009ed392c86cb1f97b056bad56aede49fe6489ad9dff61d489f59571c88fb64a6d7c2019ae3e980265cb43a44e8da2111a8258140c31e93c5d14503913a705a1c1e81edfa348e06d9c28b4dda02d98185868145cc43d72187082c388ace7e19ee9e2b30f4f18f25c9545830235b46ed5838ed0b2b8193b8058cc0bf5abe9b3733a735e2e61ad6a11e435ea32f77530455aa65c06be6d1c74b9e9d385d4be1e41856f62df0cff507ef5f99717c8c98ee151f72a828472a17e73b66c303054cab0965aca620beb0a43996cd91427081d585376c4f04f58e5f74387aca95daaeb2130dbc08acb4d9b81a4f98eeeed61c050bbc0e59d0a18f9927bc5170e403da59663b295e98a1797e5434df7d8cc39f785b7492fb31a65b4ef8b791f26acc252111837b448fef81b9bf0e6d24b9fd718abc11dc1bbb103a5643d45d504b467364907c7186df40c585352abf72519e8c73abc055a8e44e4d0681cd89dcad907254e84ce83b718a1b643a6b564004b9eb36b7340e834a08ad0caecb1f2576c400be6034647b248d2bd3f8a7c6b037034edf8fbeff6c3bd04f3e952fa3d0bd93673bc332b1abaa31b2ac2792fb742fbfc6d477c02204423ea1dd55eb3a0e9acedbed6f182dc80747f488a4d9cccd280f6355226e6e0473f3805489e77c3ac8b832401011f97b6283996d347e97f98d8d639e1142626365a3f186eca5da5a39712ac1ff366e63f74e8f5c4e13f40915f3e1fee2add369bb880ea668564c2d1748035a13c14f42f65c88732a26859a8fcae53f0d84c593fc8d9754df6639d539cb612b90e9ebcb9b0909caf258aa70fccddcefdfb7e12bbe4d9662936922e31bb927817be35362efaafff252f493f97580725810213b3af048ed0b1089729f1705dc2c1572a9060e1314454b57bad6f3505cbbea99fd98490fb88dbaa56ff2221f6faee10835f6e4584e8152668d1715677ec9c70bac658c2dbae6a9a8f01e6ba59b85df1cc63c0c893e04faf53af156a76bf77c707478cc177b71e5f90f9cb422f1428467b6a7c812ac44e81a38c9cdf192c85cb74c727bb016f9e40385c5114e99bc48d5cc0fa09cd763a9d80b5647ba643b0d85d1e288954c35328f1647bae4a7915db438f2f8e3f6d2aa88718ffcaa62af6cad51d435dff35f2d98c61ed1ee68399e8afdbb60aec14c7baa63ef8c38b03110d8fb44b1c5e9b293cd10f5bb0c7c3973f7ca907fdbd72ff83ff35011d0d1029a63110d1702f9d7e61827adddc5b1068d4a3879c252e494d88c7c0e1a77a7df12934e405e00f4e2145990404c44e284bed917c65a083982e55463d73ee058051ed2b5260c501812236ed8137268cfddf25e5ec40b74cacb8c2684c0df7a19fdd7df1346813994b61fb52800d5cdc878571b448da9320894a8269813e96b19fd3ae5a76a4990fa817caff7b9521724243f889eaac25cc377d6df64c9e501d8d44b30f71aba685f85b1ae9857a23d133cb47196b807d4ebb8766936c28bd58d5acad1ceb31cdb59a6826f25cf866d430c3b34af96d174df7a6dfc252cbb0dcf5fc15946a3b3253380be242e3e40079cd26d11c9e730ed48043047f81bca6523027f88a364ecd66b2c083a3dc3139907225dc66263da3513e49bef5ae844c9e4ba4abd3a2ef82f0c31bbbdff516f9d879bba8a8ce59133464d5043e1b1101a552c9f29661e6cc3031b478bd4e1bf7fac6b79e6c09125ce59988a7d1c1b7c278ac13b79f275fe53c8675a7509e6bd4ab87674206cedde3e9af682da4c76bc8b33292013c4ec41e5cf0e71b5becfb528190b58832fdcdee718987cb6ab679bff3d31809e48628a7269abf99e4ec376cdba18aadb642c990f12d3a017e0d26c617db836d2d0296a36cd4fc189050713c7bd7963750bf1059b73b1614aafae80bd0c6e2552abbfb352a34f2639499d226177b22adfa30429c14dd992354800c4b451a374f55490bd6d02d03663f3f0da91fb52c671634ceafb7b7f3e7499c3699c130f75afedc1ddee42e5940f73ce5463f96b857f0073f31476dcc42929adeeacf672f9325ba53c189fe2ebc162efc1a8478fdcd805796455cedf8ed31e87b932b751b006a33246cb2beb94ebe44a9257a5983fd1a9d9f317d3dd10bda11438323283335bc746ed4bb74f4e3bb98d559d000d490ec843cfbdd7587557d462f09ed0620f52c5c6fff7c54e4d593c3341f68405791c1e9b01ca233f029263ce65ea386b180f1e7a9cc5eee69154479bd017484f03269e4cd7f60226e2801227012f713d6abea192cfa6089383c856b4ccbc3c3e3845a507a5051dd9be7c885138d7a1dee1dd6afda4d84aec3b7fbc31c6232af11234b3a0c95c4e4a4e5cad9b56b8b325d69c257a037f38d293feb77a59c1102504f5040612f6a96cd6bc5ea000a626249aa85d0b0fd854c68217e9db8b6e0b1fe833d13f5fbbd6b0788e64961dca7c6a9a362aaa58d508313cc5fb368dbb57f48d9921633a92256bf49a13e4c00369f7dc4566a66f46732945a49baa75198abc50e170373ce3a381cb3bad4b040285654572e6c8213e2d2b8b4407160d4c17695a5d5d9d653b16a12e39ba490ef49ca9c075f2e01c701f725a5198095803ef4601ec5432026c8ed9d33dc38ba021458c24323bfc2345e006b5b029b0434f28e04280ed1443058f53228aaa314cfd1918c2abe80c4aae3f5582bef8576e4ca7a78912fdf9be2ac9d690852cd504e07e59c8faf35bca2a785417339674292cb5a19659a8a98ce9641c48c35dc831bf61d24b09c78c2b67abfade50ef405c288b036d6e4cac9c1271411af959235f602dc3e4e7e882406110f4e47555b44d04e788956e33439d81f33e5c318e1c156e148c10e003d6008641c26d0593cee1282e5acf7dcc4eccee4ed4b36b48ab3ed1b274046a83a5f102f808aa17062dec3324a1e1e6d415ee1e2257f3f93400cb5a6efc1bcfc517024fef04e0a95b3da94d40aab810f24943854a10dfb89b9cf9d0d10fb68a61085800ba14581100af53c3c326fb103c3c9ff012d91db2d44c31579f29f522175a470c89e8adc62bc1a57113839711395c94188003edf748e477550387d636b3f04debf1d65c1974b39c75202235b2ce9e5c5743148224632df292b88bae45c77ccde542c9ca16864ebfae169e04da5a365b1714c72460d4d4203e9385ac86be04c99b090bd2a071e28570b2f71a360324c0357adbd7fcebc8d65c1693c74956ea85bf6e7d823c0268c84ce6a57c7b48e23205e15bd2a68f43606ef4e8d3fc2a1fce594df950c95717af73c00c845b10e3e2fd1d168bedd362759654d113902de61c25859292b152529cfa200dfdaf6161dd96585be5a6dd9ec6e5decfcbbc5f78b99b18b9481fc8db9327b1a384991109d01a804f38756aba10baa0324c6487e339fe2b0db6acdc1096c1051cb303af0c8278869ad1439702bca63293634ddac1856f0147b082fe6332ecad08829f34a72cbcf969c5a24cb1dca073bde74e0cfd183631dbdb1870c0ffaa61d82f80de0c6c4d616a3117574a82809c06a4883ca63e0314342550efc395355540dd8bb8e49c844818784274bfd86465350a8987586625e41bcff984f6edbb440f1ceb42edf8c759aa184f3ee9fd0f06fa6769f98062978f7438d36e72e55d9ab190ef09b27037e477193157ae7b8cf9e808c3bfab668ec1acaf224cf21a41ae7068a34c2bc22a518129f7794f966369dbaf0984c3b4f88d070ca32d4b988c3a18d7bef087233d6ec03cf20d768d41c5e1eca012e434aa1412d08026a5de46c24cc370f8d387030657ef46d4e6744f342070c77771be046210bda3e075d27a4626f16e6a87afb45c95c524aa536f00ae2782aa0915cf5e3bb038159038a58ba5d1824cb04a237d7a94a842d3bd075a37770c7edf0dd11b20a17fb12c05a8223278d57788109a721b8052b0d3cbed9eac8c48e4ffc83e97bff6a9b184118e2c09169e79e16881127df462fbb4f581099a6031e7d8380b9f5499640dbb1f1a445d02d3e9450f96f9c8a5b43eb4e3205527da9fbc799eb5597bd8c761808add79c2e59d0a2161e02f60bcb96d2013f78d97a519f5be94a7827ddae8f6af915671ae50716b64098a2d9b0e309d7158b269248dafcf3666c47cb08d52854803e6e20ad4173e1d7a683d63b9b487204bce22a2f18a2ab87f0a44fc846a3e4c728c2691caa226263b1fcd6a821e279269d0e38ffd30d852947008a180bd1ab7e431a8fc83585c8d8968ee6fb2d6daa3f695837eb558064ce81d3e4f23362c7a30b8def0e1cad6b527ada0df038ed347461d956e7fcd60a7c98bd36b3021513a403c8148f181c03d8fc3f04cdd2b8f8e4124121c408f9f8cc945ef9169c0c8bf072c6826823242e077f2325d6dd0f5de852dd2af1fcfe123cefd014de7237839ef4ae393752bfc43bc6ef06a2339046561c81a5026660293186a703631fe24a76b13323b1509dcd7be0ae1cf6c6922f8013756e8d71108f02ac42473f21f80d70e710e3953f5f1690e22bbb3b8594837409e3eb7519699ede13d5ca6b0aa33765c198d4c0180b2cfbd743afc36777a346276e9fd27c0fc58e10dbdadf3cd45540204d718cd52f8d87313d44fe9b6e9993e519127981878a32203437695f21a5d421d01a8a5c68de7c23042494758ef5d344ca8c3d063d4cb9a570e736ec3e824f575e5d59093de70b271359c5491b5997d2825a5af0112b89a96897d93dccb15ecfeb0488b83d0f731cda2e920e388728daea48b29a32b1adf517079f3345fb296e47365df70aa9598a735e317e060f8a9eeb5c2bdaa2372c3bf5bb2fec4059703a62a0185f0cfa94cf734cd26ed49c06f7a2149d0e7eaa628f588e5d5afc74815b24c04d3e0d306e0ee0a4954f2290d94e7163d5722252b18561f1f5c1476d6b1d75d000c0cf8de0aef25ba24d976d7a7d128d4e2f9b1b41e6d54d5d5bf9397ceef6c311ae8b3a08fad0dcb013a1424a22a65bf7de311aa487bbdd42e7d12797fb46016fd41de6effc87ea790049b86cd186e31c56c448b8554c416482c96d423bf7e449250ff8f1f35d52e07b3188ea7379df6ec42bf30129fdbe06e08eee3481d8888e43ee481bca4363aa6800f53f4877e2ead080625b3e0bd0c71f16640996830d79e5340e91ca80a94e565d94bf4c8134504c581b878cf64f3e87f979ebf9c6a8858786e028568580422ba525584c8b24ea4711b217034fbbe97b3aa73824ddb74df7baaea1fb54b7cc6659acd7c2df81ee5b88c1e7e6b93c82fb04af84b5d583e3e4cb2ebb9a32191397f13b648140cb165fd0803256269bfe1d4020e0c01cdab84f30c5d2b11d43a57c7c3ef5747814508607ec20ca4645a1a0a3e2445d96ef6dc5f7ec5c7c73a9caf42252370b34617b92974017f9924b66764b4e4ecd2fb5814afb2dd413a8a9b12183ec19fed07b5d3839a2643229051139805de8821b962e301161321b1823398e7c3ad40cf155bbbe254af605d610aa6b9f2635b5c065c443d515fd49594053ac6d54ef8875631679637d65f12cbd9542812b57722090da030df62683e30095887a65d7f4e28219f3ed27bfefdab21a31b1665fe59101154698ba9327ee232453eabc5cf943d7e14e33ef703e4b00f891b0e62579567ea8df6063588001fc8edadb34c76cc8f49fbbdaf152e5f60e11285d13358869156283211fb0dd002962200707290494e6a6d07fd22cf3888e40b44d7f8222df9eef51ad11a94117a706c241cbf573f44f6fd502fc345b68cd12ed202b8b89d1072dcd76855a410c889923400d1f68ab640c0ce55e27d8f1e5dee76480523b45abf216863e158ace23579ac4fdbf069fab8476a3afc580859d170050d2d39086df7be33bda9d9881e7a788de0dcf7bea40905a6bca36bc2d04dc81d692a5e11a7cc3a6a7777731c40651a2b5da7acbcd00680c5d476a74321c9fff4ff3085b7eb36103cb0bff1e9b38d0e398e1f9d67c22288c1257ceb910d0f5942b26aa0e31b08bff4c1aad1cf151a952ad91ac6889b2a79e076ac00df04183578208305475e7453d6abf66c03ede7bd3a9bcbe386ec8b3e56e5b3643d97164a06a4e1e04ea7c6b945071fd73c4e12275384f4fbee39271612add7343edcd19048e3dbc72cf78b90bb2927d989274122064d39691326317055439506bf101c5c763070f87fc5cd8233b1c229fdd02a64bbf2792b86d864eccddd7c21a133846ad737deacbba40342702d3ffc1e8b83dd2c273e0cd7bd2cc7691dd290118a64c3965e1615df8bda12fb1218f3b5a0c9f0f79153f4e4590436f6311a08ee66044bd8b8a534c674385771d0d513fcef14b43098489126497774c0ff59ddd5de28df632ccb1a353b40ffa1709a40a073b303ddb557532422aa850e31949a768b4e7603cde96d075c87e492d291418bed49b96cf69e3c651f33247a5e9400c698bfb665b3d5a4addecee71c0057c84847a65a4b0af2f42660d02ce4a8a8c907484038ac40e43092aa4d050bddcb00afbbd70b253bac8233b753990da2223c17f2f03354f4778c8fd6d8d8b1f43c2b7c02e809c0ad65822e5b1688c1f70f0685d0c31742c2886793bb122ab3cc047594d387930b10adfb88ce10d84703ddb75f393dfaa24bc903643b889611c7b03d9b88faf7060b5c43ff8464a42514c8237f1d8fb11bc6c44ddc952f56954350945a02a4fd537c9caad27f120bc8e3278f91bf2b68b998991b1955f374dfadf87a978aacf0fa253743b735678f36bf52b1ff1783f92589e1394568e5c7ea924048eb632facbb07aff3456fa96ce301c4904173ec82c728007a2e7b9402e3ae81b1fe51e980cd41b5b83e079181389507cff283a610c1d4b0aa06641344933bcadf5f6248acdba454bb054066aa3aac8f40a4701a20ad8594a6fdd14a6db03b961347f407058cce7c18cdb50661b842a081ccd7fdc0de7a0f4428b37fb09ff1da08e912d083ab20f3e53fbaad162aed0f8bbff263bcfd34978d2a78e08efca093ac15d8011337bd7617c001d4b5c45253fd83edf9151f59b9b280dd531b3a560b21d2aa37c77c9d25b83a343b993740bc5e568e49f22546a8599d622874e276aae802166b2583a5b5503cef5ae8f1af4c6e5e1314be8ffdaa512705cf7008f849a45cb4c473585e6f892c06f620954f76a077b3c92cc62ca368f2de1847a8b96d7eee47ec584bd8514a43f8ffab21bc07b41104daeb82995c30ccba9a0e251d2a102168e36481dce2c0d163f09bf18284e080c6488505424ba5c256ad2ca04381c9334548b60574387598b96fd7f1d76954156f4dfc445d8b88ed585ffc2104c7858e5cabde0b8da691496b7bb735b9a594322519d9095c094609453b64fad3484b9e93274b261268594af776caad6e49f5bc77ded7bfec7c4dcb4442510f0da81f5e8061ca0b9313198511605831005330a165cc172699116392e4f900f0e1872ac44801922d26808225082856386fd4336008c0194c9c0126d7c7e027063450810214966e505a413a0d71c39c73ce1e6f68ae7274ace8c24486972a5670c40a9acab0a18c33c4389921e50c1472fd1ede50ad2408a28647ec124446d1d413793e8f8e183c5480058a29a35c7f07930a78c091eb5f20450aa6b0c01baa1b971c1480494111b97eec36f9114956cc39e704bda109801e39cc914222d725505084822272fd1d6fa8ea3821b50206b9be0e6fa85a219ca0c97133986fc7c832c60a79be8e37346f5217343806500031a41003863c1fbf5e4c4a00c316585280c20a098510355b2b7e07bb5b8c10d4e4f99f373473c894084396411846e4fa9f4385eea28ee45adf460d91ebdff0866a17f50421b07202a65cdf863754b7271d308ec895e96b784375b3628a1f2576f80835608a57a2162f9074783145982861b6f862875cbf07e88b2e72fd1f4082e4784375bb225a10ee00a36403cc152610c204b55c1fe60d551d1474395dd472858a92ebbfc010e5fa34b0121266b821072eb40c219f20620515ce1417f8708145174170a143aeffa242b48af7755dafa09ba6e2eb17242e50f8c224d7b7f186aa1135d4787122cf7719d9e450e143835ca3960aab61b8d428c68a14acb0a28b5cffbeac3023d7ff2b9ac8f5412c72c8f57bb01893eb03d5b8e4fa3fb21022d707b294450a725dd2e2895c1f842d74c8f52d90812d98727d225e6878bd6aad482899894133edcaf34a9729baa45053830e1722b0105197b8c872ad5aaae821572c5e86115dbcfbbaae976b56c18214797ef73a620bee36518124cf5f7d3184956b9b9e92c8138bce9309b9d65a6badb5d65a6badb566b92087ae297c9862cb0979fedd52459e4f5f3f905133a3531679ce295158b5d65aeb4e9e45042db1f7058ba2498597164734a2c832bbe1128563b7486293b76d73bdb098820595d78d422a494d97292b6437d784992b8b231b1750a0a9c2a866861566b67072452992a935d14c79e1621135c3840b2d2e453258acc0d262ea66f922ca66191343b785118b89122e8b266f04ab2b689d9621544f9c653a62b718ad66d32ef1586cf2c4633187f7554bb2802a9cc25861015a142d69197364015434a9b0002b8ea4a02149920287a2055cf1248514240be082f4399a4c6dfdb295564aab8e272e8d1c9762b872e472392ec5c0c50a3184912403d113198a7ab85549d365d174b020430e3218b1e446278ea0e59d2519a05892a1cb7dffc273bd9c80b224d39f5132998c05195680a48b4ed470845ad0c2ad39c18223eeab091597660af874594a607155392e359122ff52932b4ca83a4930189d48622ad31c645cbb45cce8da1c979a7851c495c971a9c91832382162e2ae725c7292828e14eecc71c9c9121b392ecd30460fa782eef60ebcd0c20d2fc8eeeeeeee186eb6736d312846cfb12f6b73fe25c494835105827e3ac3c9184252b6a726b8ddb382843d665cebb13e2342dd07ab5f2b16c27a0e17693deb8b70dfbd11211ae74cfeb45e7ebc2dfce2aecc7bcf61ef693ecad8ccdf6dd24209667e437ab2bf63a97463c60d6d035ac8fb8ef5716db749eb240eb7928c612bb7f3be48eb65dec345baafc1600d4c9c47642d066daf3c49c8128757929125966164f9f3732207392ca5c615d785a5918cc999bf1f8258cc602040637ce0032d5c448a90d6771f65ec65f007baaeebbec320f88005eeb7f0c5edf4dd87f1098b16b64464cc7bcff3dec320f80057996e7fe8442d2ca50616f7bd0fbb080bcfc718dc772c25077fc95509799f0ad75f853530a5d3978bc3885b6bb88e8d3c87e0affaf9e1dce0aade5ff5dc6c99a3acfdc8300ee563c70e9dad8ba1edec6b2a064510f42efa916c21f9124a3a5296b827cfa2e8d2684a1c76b307190b5142f16f0ba7466beed3126ace6613ffc148618ca498299b4573891f492319258732cad1e752554297e240f553314b563df7f50bc17f72d063f2064b407dfa1c547f0e749e2a2712e33e2a1c420d679e218ce0b92ec0e248bf10fc371a427d7ffbd14b802a048b00bc7909880e4aa9fac54c9b62507de95f8b3c3f93c8634bc41f9985962b57644d8350d67cec78bd646454d39f1479d8978831f95934451ee9a59f6a2a2b8e43764327f269244a9e1ff3c4b52831b2d6535ee38c58ff1870f800e4a5c28d1ebee1fe7020b2d25aa794524a29a594725229250754f5616cfaad1be2c2ac94db263729e595745220b449bc575c07e2df06af136ef430941b46a60dbaa393d2de6c383d95f3757c839e5a5cfb405cdaeda3106c73f78a9baacb7be50c0264851b9970dd95ef6d822c4109b61792bace1b6a2d0e7abb3bf548bd9dce0c8c9cb41c4d1a35256530dbb918db5dab238f1affc3c01e5f7212931d6a7af48064d2069bff74cddbe994d14a14145d249ddd1435e53eb2a1c254713159bef5861c07894eb20271b3d70f241079e4ab96b81209cba317686e28f9e3a2f93c8aca55ad7c48f58ae72742a41f1a41c24074caa111a41cc2fce6def5c1fc6dde8810cd07f3a22330999f79966c72c1904fb27cf9c47d4287923469e4a054d17db9c47dbc972f84e65d2ff33580e65db2fcf120dfb12bf9fd9c8a741a6e3515717dcdfb27858585b8de0617b179d7d77c456a9ee6af1897bebf74288fa202459372b0dae028635d83a3941bcd93311eb974d93d329c51a8bfd0a964f93964dc501ee121aa57492877a92807a5fb181c675cff469247625c9d12ae7f9d5df9b11eb9f263457271686404528aecb8a37040b961877941b9f2b54c18910c4b40e71fc8c854ca96c1acb8321ca1e6f72f00cdcb775acf644c65853e31e2b8012021414145351d4509239dc8a14b1d4d41426a922f9f38b9d374b8f2639634ea94ce9671b21286caac84f2c88a54b7d42487b28b4c924c5dee510cdab23c8a729fbcf84f902c2e618162924eb24ba52d619dd454a46b4992f2a89564902ea16a209157dca71fe93aa883918ecff151e76b212ec77795acbd5729c7176b2c71e5bbcf741a89ce21c7e74daee40298b4d3f94c68a1300ecea8e9f704b7a1ba269b24b9928d25f746de799d0f811e082e4ebcc0c504a83184e6cbaf6b2e80f939e6ebf86291ceefe0225284e4f81cb888fc4d8a101daff345e4e7f8fb51fee8e022fe3a709431d6b33ec71765ecfe90b1f96ef3fae834609f63ff7e278536bce12b13c1f11dfed5f7aaff1b4f3f4aafc6b79e88fc71190e1cda0f87afc7e1dfc021986de01a3804ca3fe48f7b7197cdcfc1a187c9f36138b434f00b872e26cfbfc1e1cb06879228cfafc1e1bb300d0ec1192c83c39efc6ae110c89540599d5f2beee1121163f3ab402d912796dc4991677e9ddee4e0fc39abf48e945575be28e4440e4eaf1263f39700b1e449c5865d8989eb8e83882b8dde5949465d6ef8b1ff2a03c04028f7a7179e73832f8932461bc1eebb1122d87d47c4ebb82279f52dc580b2524e282eea6c51c6de5e4e1d392bddbe5b750772b95b712b1528bd0d883ba3cbfd2d3d9244942a35262644a13f29729f965522134d2abb90ea48460a555fc6c9eede906398fc2f7706024b0e5545d51bf24ee6606fde90e3d8e28676ca4e5931d6be5ecf7943931e1539d81d11d20d3b5957f41a81aabeaf2bea64f4ad980fcaf673fb8ec83b99fb548fe2b1d9b9937545ee13ab4a36e52086bae12dba45ee538418395415d12197dbff70ba750d1c2e4d8e5bb68c206ba0fd48ca7268a537646be470433f72cae176d4e3847b73e5aafeb574433b7593c4a54734949472d8c972ffd1a447b9df33923f5b918cf51fe5de68b93ff490727b49b99f4611eb4b0e3d2bb228748b84b8e1565494fbb7221bb7a2dc5fa162cc63ac50381c52a1324c7e14f2280e937f24e6fe94e4e1bae34e8a314a96c450b6ed3b6a2a777f03a6d086ed94dc8eecd496d4b84706e972016b2f2d6ba5a824d418e6452fd418d64d2e112b640a3586f513b95063291c44107463c08893a21cc470e1c315412c49117534a3a2d4b049c0095210484b556c01430b162b284e0006892701133932753ed0b8187f36b757c6a8c521fd4ae9eb65ed638b438c514afa93cef8515c8cd14b8eef4a3551b76337998a5a26b65de692f27cf9752698fd5eb38836ec47fa21e0d0beea6df521752d8675509f6e1d1ac9f439efbc0ebed0089df282b813417ff9e188608f18f3ffc99e43c38716f24608f3733831ac83cff53fd74975b044e83179e27f9593ab56d56e5ceb762c4f86c635f3381fbd211a35df3eab5d754e62065ef4688841d5d5e3a36bdd97f9f845dc87f5f17be623cdc7ef27add44c34c2d6f28adf556206eda57bd87cd84b3ff149e0c2cf0cc949f48f42d481e8df474070086c890ae81efea01733d8d13dfc77d830176af310503b281a384d5e5e37b89b6c70e85e831baa9110e0b9c6042e9a9e4599cd198f95813e12f552e6b6820c6179386c31de6db647013d3dcd75d4d7611c7429f7b73874a2ecbfe1b0c5647f2f76646fa8c8e30ffb0eaa280639ecbbbb63b7fc09c31d06aa4a4375e1331c9791eaf999423019ea8e0f2304514970a92d2185822c4d542c2b54289c4890274427011347d4b6265078188e7208eee8f172648261492da1d229636cc5e860fc5983129b5015a24253889b1dad1bcb0acc4686f5aacd93a492c478fc8ebaf225c7ee22c76f2f3108a78d9cdae829c6be32a5ca94a51cbf9562108d8f4528e49824472639badebf9fc87cd84744eda489688841351f9f7af6145bd64443c808139f480ceafe87178f31c6da1621518aece4b129c420c7171283bcf701a44190dc9175a2df9cebcaaf0ee7a3dfece2099c4fe626314a5d3c71c3062c49eaca97dcef44b98bdc8f63e3833dec6dfc8d1b30182ceac6d719e7eb6c03ca0b8d8f7ef3937949889c1a9fd3942a35a4a7a51a9f8dcdc33ee7b9ce1bcab1b1b1b992f375a6f175863129d1f862c6e9dc45ce247751e4a9f968cd376b3e995b0437af2f03dd4e723f95dc35af4fe665dee66f6e6464648e6ebece355f679b36ea2691a7ff8bd945a7ccb532c098a1f9641f74d110329a06d5b35a19341f8bc5adbca119160bcfc88821e27d747a9fcc718ad6931721518adc2fd55bb2773f554b019dbd272191a7df931ff50264a7ded413000d72a62a4f0146b2ca474f4b47d6513f2ff7fb8edcefad93376fa8d6ce5de4e98f3987e8f63bd9e818153b2bad95ee94e0cae4c8d40253d1f572646a61881c2a6801e97e0b32b8a0baafeb7a41c9335a700188abca91a98523605a20daa1dd9a239392339894444d2f48321633a53b3f5c1a53e8d002450e9fcab53932295122ef38a14409dc40491253922472f83d3d4877f98fcb63fd4aaab9b13c57c751a970707ab23eefeb56d64655b3727134b6bb92eb64951b9098251ec113d063c68c19d31eebb65a57a6a37165a51c9d3ca95743bd9a1ad652afa6c6b40102410bf6006d30326e38694c98bcd1572f8f4e2b99ed7dfda42c20dc6c09c620dfe13e1debc8838100727f3880ec1d1b87e6ffc69cb37538295dca28a594aa18a5a4966e73d6bad139638c314629e5cab9961de99cb3fb557fce3a65f458364c5c8cf12565534a698cf1467635b8bc26add1c53bf2dc2e8a09e46084d9703d3796db6fc3ed0cb3e1469b29433a5d730c4ae30ced628c118740e594b1975295ce6c49c97594524aa99452ca158beb28a59452cab1ba19b59aad6e46a9ec94895bcf50cab9bae8451c4ae0ca6ec76f88ce53eaea62e49194524a29a594524a6d7c8cbe2ddc7ef9b4bb439d1c256aa5b5ba1cc442d4248fe058667904efa93c82e777106f5b0e558ed5089ee387f5e72aabb6ca7539e2d75a6bac35c6aeb6fa7632b5d63a536ba5b17155cea69b33da5a9fbb795557df7412a0945219a3d71fa2e30dcdc773ce9b5965f64c719a4637a51ae6e4258fe0596a86f4e5770f05e7e33c75725e37382e53029d9bc5e95577f7a455ca2e5772b9b252dc45b3abb8928a2b7174b0b3a1a1abf1c2d0b470984564ae6cda9a6290dd2d251829c1483926090d5232f13061e84c1266d231b015b88a3b779e71b13dc33a885fe414814ead9bc579e5f8bde2b8ef5b71ddcb7d54acf8ed31c8fecafb4ef515c144ba2d534d42e4f1b7dd4d718f74d11204c82c71cfa43062510f37ad9b5e03a4d00a379216aeb3e96a3c8fe5b2400d04164d6bc6a99b76901ef03a9879cadc1cbf08526e26d6cacdd0b4726a60d5d874d14b6edc9187bacd0d2701073f887315243ae174919b9a9a542a2f9c4b94d3ac553d7d57c96d82e5745064efa6d844ec2cd97d065a4e8dce02c10a883c37861a366cdc90403d3d3df267c571dfcabfb8f118470caa407afac7693247cca0a3ba87bf183b56756b50345f254d164af4a7adc53baa33e825620f7f2f5d8c7237214613f7e1d078dde0ccc701135bc1cba8f20512313098925049929464ca28989220fd1b4d773a414b122c2fe2a27325994cb66405160a52b49294548832836486d73243c76ab574726b47abd5f25a1f8fc8ad9f2d26246372eb6f95dc6afdaf00456e3d8824b7be87052772eb8156c8ad99536e254912a3cf67cff33c4f0531d93be2c4a4c2112ecb9373652e27733f392e8527994ba18bcc7939ca036042a10935b273fb28c656837664ea23533a001aaf6090c3a8a50bcd804ad126f2c9611fc9b49768403f66c994bebd24a420d37f2165fa1f0231327db0042032fd9e12aec8f481984a1893e903214acaf48310a520d3ff000428993299e045a69f01fa44729089c08ba21e32653ae14ba64c011f975799f33af9a9b2cd5cab93df96b9dbc9af669ab9998e867375359d8d8b66a6e686151ac9b00e6e3a37f68091d92ea6a38e7bccb9e1c69f2a748c1d63eceed8dd91c6ee27dca42f99f8f4affc22209bd33d467713b8f84831469d3927954e1d989c2955b6fb72e5538a6d94f5ba125b1d816a256537b74fd2f9d58f66d5eab33182f4adfcb939f5bd5a6abf036bdf1b52596c75b0364c4509d9c4001de3733b5f1402bf22345000e3884c68e7b92e020267e14b91d0055f040400e0e2284a28f616c8b80fd991675ec6bf70479699f91fee9fcccc476f68c6a6c647cf068d4fc7fff0170a3766fcd6b5dd0be686b2c88fcc8e5a375afff26f73ffa68efc6cae09c1fdf79ca9c30d5f5eebe2e0d3d40d250ddf7cbbbe717c7b43aee75c2e3a66cc182c3947a5aea5399af33b1ff70dfe0e88fb776cb8709f06ced30f07e77f382725c5e108343ffd6cd89367ce174aa770484f479de04a263188fecb071d72c5a1acc1667b4614f9c4084bda279b3868cdb8345ff3f6551f4a1a77b3a972a85e7a43aa8ae36b8e987138def960f279fccecf0f0653bdfc0dfc602adc001ebff330893de081779ee7c1876dd8039e079f0783df12fcac8c71bf5357cfad6256e178ca89f023cf77bdcb254254ca12873ff2c421b85ec7ff70ff5cafc3e70f77bc4d11b6ed13c100746772b223e24ad294f6297d42975c7243b90ffdde41c096a964b1581deb6547955a0afcb61b9c100115b42652fe944e93d2734fda6c311996fb6553948a8a4c4952b925131130e4502a5dc9dd3fa52b2146892b5f02259a6c9c4d173d0e74bcebfd73fd8effe1396f9f03df88d0dc79f085ec3c08f3e78932362516b20373ac0121e0f3e0223fb003a2101019eb0fb283ed86a5c03e0af1f0ce87bcf20f5fcd6ff5f60b3fabb0857d21d4bff987b45f97f375dfe3e3b103fbf09837d46117eb69bca1d5bbbca1ee5d1feb7574cf7a6ef5dd2865afcd9a94a2ae92d39383c0fe90f31ba1f3dc70e0f78f7b9a83c3435e19c7db1c3d1cffc3c31c78484fc6f1f83f2917e7693c87f3859db717a5b1610cf4641d1adfde100dd8f7309d8fded01724e7619653b9761e076f3938b4301cde1756816190fcfaf971a0f3f7fd0f7f7da10e6e3d8daff307e5869396fb713c1ed293bf9f1dfe6cfe3e188eef93dfde3f27bbefc3f1850d73dc0135caf4bf8f59ca9c9e6a505153535150b5272737f223a424a5a626a524243f8a9a729917f98ce6349f4d452935393dd5a0a06a4f4e4d4a3d6b5a1bf511525212521fb551d37a166b112a2a6c595117b56c2aaa07282af5e0f1f100b953e6fe87b7de88833c3c7a7c3c3e1e108bb0e5b033067a3287c320f987e3577d18d85e7a43f8fffb42780cebc0cbfe5ef6778c819e4c7108ad973df95b2663fd4684769e87061c108544e0de0151087c1eb8481110708fdf791e1fde1e9fe411b66ce7a3d08e0f63144f186b418840bc835fd25a7eadf7aff53c1f2cc88e8771d8015168078e3226b4f3300f5a68e75b6807f74cc65a166b29b5fec6e79f94eb5fc36dd4a851a3468d1a356a7c9d6dd8f8ba461b19b9769ea3f9c2ceafb7f9866e705e62b007e855bf3dad6559a353df5ce539387cc1f04603d315b698c3385f3842cd37ef72798d95351f63cd4b0cf6b83e04ba71bd7f353fbff0e6dd1baab979aebb1b8c3d8cc76a5c51f2d1915f71b05fe67ede34d4c29ee47ddde746df852b4b9afc89427d5434739fb02ff87c890ba0df82afa75c00fdb12fba00e4e7448d50f392dcdfb289b4b966881b4db511c881bff33e80b2a4d5ecef3407dbc886eb4d6a9fdcf9bc04c42758c88edd6886f2481a6530cb270787b8b20a491483a2fc96e1953e2e254dd27c892bb32ca72021d9194c49c6fa618e3bc98ac4342d4edd4a2d3b9a328a6a1a54cf6a71aae73df6c41028dc3ae8595681d093e50f395718033d5985c320d27654023c3066cc18a7bce1213de0ce178566974b8b415dee974631a8c34cf9f7909e3ca427cfef28077b4ae9facff61026ee2929cfe17243497bb2f3f40bb0b3f30dfefc064123f7a118e66fb37fcfef89a11c543d216950547ac8b2cbeca79def0989bdd41cd4a1a5fcc7c378acbfa71c3bd40ac9fd9e42eb3092d6500ef63794910e9f8018545219e5766f6dec6926e647fc3b3c947712dd34504a95a69cd035ed0195e74feb2428e28c07a294522a71f28422cf773a060633c6488113279c7084a48288a93a43539e566a92ea4b511493932f96cc4878400b3424478c9824c961f60025494c3308a1e40149a03a147a3607bfdc558e4b4a4e782d2d51a2134ea5ec7ee3c5f599776f68e667e48cfc5519575a4e46e6391919ee7ee190b7ffc3657dcb71975e7ae9a5dc8febe387b688f8e3c231c65898091a6c25bbccf159cfd18ef51ce775ed7160bf22c929e57c6eca29e5949999796e662674654a714711417a1c788e52463e6c2e711f9783bd4d23ff79e43e51fce72c923f52d692fcc9bbc830eee332647feb0d61e9bc8aab57e6b8c8bd27399555197766d673fe0aba00c8d2822c71228e3f4e8b5f0c8ab97b80a4bfc45362a6a46c6aca5a6855c6ad1a90b28fa48f309e6864f23a80cd23da7d2865f3894c5a7bedfde849a4c8e3f208c71137743146fe75e6412ae54a2499342391ac375d262af5d1d7aa54ef17be72eb0b6d9e42ba1269db5e25b1f538f0fc23f2f8d323f789b2b5979e3295dda5cb29739f302acd22f70925929c9262e4530e27d13c92fdeb064c4d9eb237921e20b047de97b419fcc0b2a7c9ad1543a14940ac9227f6e13d34df061e73202986a43aef9da424e33a50dc6071c3e8247ffec445a450db42f2e9c4b097833b40f901f184b8501123424032d61d6b4e515f28c150c5dd9d521a630c928daf74f9caf366712b0e87b8f0063fb3e31fab14eee7f0a504be72f74b8544189d6adb10d50417471814700722bb39a7063a87ffeee3ca9f41087bfe3669ca91a949d2528e4c3248e56792c14bae3f9da4bcf1722703e2529ef89b4febcfa7923b32790cd8de4a0fa828e01404073fd672e22b7b7f54124d94a69c90bd8833b2632b59e847dbf445fbff8cecafe39fe3378cdb0f5edfc8c8b4bf2ed78d6e5b75291b241f5e3c76d92af5188a5290fd75bc658c42e215c52aa1d65aeb96335a485070220c192238b2811ba0c6a0a054c2962db63cbde045f17483122c6242d081394185e7cc781d95540ebfc722795b69ad7495040d546011a3c313a229b6b43013c5161c9a98f7755df7c6315a48b1f0c2d30c4e2c4df1658b628a3050ee65e264c3840b2624a0b92e262652324e8e4c4c96e43075d2ba59d58aeb58565e4111003916c522abba325f6c2266895c7e8b1cff00d18bfebb86a5e3a4322a6b9b71cd0a04fe903f351f7ffb2800426dc8c416a0ab4d8c4c36400ad16022644c7d2ec47577bfeebc8e2e57adcb39e9e0ae643890b96d0ab7a7cc8c4a2929a986a456cb1b627dcbe3c0b3ea672c919679e71ff88f17452872fc05b8064282282006a9b647723b9b4569b55adf2d3cbd157655dd6a48b547b2b328dbaccea2047902c2cd7433ded0f6331e079eebd7d86c1988535ea441d105397eacd66a5ddbac962405f84f276d76f65ed4b575c6acb596da95da14990fa88773b95c3536b55be5b420cad9500f4c1b1b9b9bd7ecc95ed4b5eee17f5f2f3abda8e633236a2de946fecc7cf8caf672f78b4232ab4c693fab85284ebfcc95f9eca521f18412391a2d9623b1fc6bb1d6760a0009285cbe2c51b3a20769c50f38d81e2b434c996925994c76245ec9f406a554504a63971fa464fa3f4801b104105053aabc10c409327d20415ec8f43f10022aceef07223b65afed0be48354cd0d510aa4207e90c2f85025d79ff186aa0f5b7c209190e75f6f68ced083095a35381080872ab2f42025d767d9d04981c20309b9bee70dd59a274c5290d0a8c9218a19b9d6e7bca16a8520aa5959e014254b14151130b02c8f284620c5084ac8f3b74b850b5b6badb56576108369075a9e4fef9c53096fce39e7bcb88acfa18f5cc15cebd10db91e89c9f51f9c814987292865e40a14047777a0687941060c0bbe9ed490d84cc9987df2850a355a9c6c9e4c417125d560c181862317682470410897a32892ccb658418896a3308a8e987020e30425990bd6056e8b5703926ddbb62a68b40aa3cbc311551551dc2b872232a5db539ae569cb52dbb2406d59a2b62c531edbc026b668a7bd82a6ea525d2f2b64326673f86205da258a902e53b61cca6032a5a02205d385b26426048b1294274a8438d5a008a144c8144d509a8e087932fa9e90b4525a2d78e1d21c979a5ce1056dc9c9931811b7cb71c9490f3b29383102cc618b135c1a26a42d3a90f0e1dae4b8e4c48a0b922c39215a7232c61126883c168fd825b7c9eb89c72213513c167308c68e80628cb991e3120d496a947d4efeb42f3fa39634c924c6ecab24cdeca2ea644e30fb8b946c5f1eb98c1283aa945f3b7ded25f2d8a75fd71cac12833afbdbb73548a61dd94a22b2f5215b1db27d570e97ba8e6e2869921655ef82726592fb6c336f5f1a49c9e1502a65fb5151dcaa5379181e1585fa03e2ceef1cca999cd99ff6673e6924690edadfbedc9837498b3cf6372eaea3107d5648a28f6a87f6993cb40fd744f4a150449feeedbb0939b81231a8b3fd767ab7eff5972cf1a7919a3a83bed26664db28c8b6bfc816894bb6d6beffd05092c49f0cb808da3e12d97eec22b2d521cfc0c47ef55c4f6e3f573d06d477e170ade522875deba4b559becc7be00f0b222303f3e02dcd5f07ed47a1d7a5c130efa310cd47a10bf360e6657e06c33c0ca5e6a390eb6530ac6228331f856476d4a18a4130dbaff97a64ccbeeb039231fb349f53b5efbd878138686393ccf7818cd9bfd5daaf6f0107ed4365fb8d74a53390b3ee61df43ba32abb556ce3bdac9aea37226674d29fdd6b4b3283db3cf51caf2b2773e27d79a3d25f79cf4eaacd6fca6b56fbf8f74068dd43da2676d876d1711b4bffa7a8b085a29eb1ef66d7b1163f64390eddb318c5a397497451e6b27b6ff61b92b4b439a6f71c8765af943b61c9e13e987a7dd502737533fe9eeee4e8105df9698d88beb3fbb890d377a584a13f7799913dc59420e6bf2fceb8a41dbcfe96097a6fe58002e72ecc902a4e2b1bc96c8516210e33fd83f7d45c7fc6b5051397ed857f48f3ba970c8c3e250878d1cf65383919d8bee6de4d067339f55e069979b504ca7f9e43ee184623a4d2db3cb84c2a7c824242f90b4f09991cf7cd61598451e7f3148536492ac85f19f98524c420b2d4d579cb2cba7ecd24b76da4e336fba22bbf8ac27a7450e25174dd28718f39759f4500162f9255e55b23b11f24be4e95a3fe1d87a67350e7f83fe69619914f2c861010ac0fa2ede4b4b77e3150ed491436fca8e7978412412f672c3396b2d1e94a0d2c4b484d64158ac8779e081f71b863540f5dcdb8779c0c1a4c530676158c51e744f571836b1072a98c450ba0dc31c43517d14da3ebef7592b1de3eb25f8515122bb0612c8c106fa2181787ceaea73082eea7034a600f7f11d3948ac9229b5d4e577c45de51824a3d71e02bc2c710da4d537fd0f885bbf33dd24f716e3d55bfc41883807e3aa704ffe0fe346196b9a8392ca98162eead4af8fe867420bb551c4c07f03e346198b42dcd32e6da3c8e3bf857139b74fffe949e23e52616b44b123893cfed1480a8beb53fe3886dbd88bdb180a7dbd7c4274dd1f470b37eca7a7277f9232e9754a8c31c87c70c782e848efc84d73374d36b1ed3845e48686eb665cffb069e0911b85a2500824611207110a6b8ac8589b715be68ec3042476c05e2491d2332497888346dce80de1143a02f57b9c6d447051e795dc28e3d634e36f3fed4f4e2623e2e07cee05c38d1edee186718a88aa7fc619e777b9b218b42306adf2acfba99247befba28cad6cb2dcf856c61f1b0011f20108ee038bf88bdb0f10199b1f8292a88b7e7090fac89c54c62ed9670e88423f646cc65509d1845b69fd721c9c8d03c98d320b064ce2da1c999e3ce5f056c182e40913201597e6c8f424891a25d01daed3ea3ae5d65deb477fabdfbd75779d34470259b9d19c6a5f39decf6ce395e3edd0696d9ea55c51fadaaebd76a3af1c6f874e6b524bbb4b819b52caa77f03f7e8f89c73ce493ff6f4a89aa136ede89cb3937246c5182395b56ba594527a2da5946e1b1254c61869ddac8a524a6b8c91d6788c2f393b2be5fcea54553a372e0a183060c0cc31130c183060c08099748699632818304761cee0a24ed8b5235b172d99cd365b8ba88848a9e46aca71f2f1a4211670d07f840c38e81fb59a12e03e90dd1054d77943abef9e257b8040ff97f781fe4a5468f2a7993ce6b6afb41114775292060e42791deba35252126773582a57917163ed86317078c891c9e315a6274732b54adc1085c6ebd4c2930d275ca61b54d0a5eaac40083a850d4972e4fa53e74a0d5276e4fa968a5ceb2da2883bbf062436d01ca851134b84bcd06484059329a3291784b880549bd87ccc923282b833487408716768b8e0890f31302e54d9e1b6725c72018a1d2ff7e6b8e4c21535725c9aa184292a318733c69c79811873ea2f7d6e34b640e44972fb093339790f3d8b41396250cc114714b3f944b325f46d8c71c39ecd9ad6b2af8b965c99d0d3f7f1f229a5b321b87e1a36b636b926bb66b24cbe99db3af9b532cbcb5de6b84e06a5d8a8248df745392ad19400000001004315000028100c088402814828cd835db10f14800c7292427c5c369708b324c661144286180288018400038801305354e3007682e79eee7040c7e23542471cab3e8a5010746afc592d0001442be9be613fda755f3bff06fc67ae8bc9ca997de96f1d991bfee11f78851670b5b4b95668efef23aa28d84cff32f9e73389ff847f590a00db04c296af65c491abe3239626006d70d79e1b50b2a8e531e915c542949b08c52a423b61c2fda524c05831051a9755895e1a838004331485d7366eea40ea1053646b76a6b3398ffa7bdf548259fbb75d0a2641fe4b53720ccb00a8e2a12e7265874ac9fe1c9598a0014341e72b10ad74c411e5592742c7d0d6560d6e2bbf532d6e3b93e5cf47dc076b5e091e66f6ebc986754743fd6af4aef6ee3a306529b3e65cb17656e46bdba337a7172f212c7dc14a71782b98036879eeceedf35b87e932858369f19ae72d42f10e70a33e4b73084ebd9d2541774ed8808b5d2434c37bda30b660efdfadf39df20e4b9134759d5195464f7fb9e90642fa50bcfeadd576406b94d4941d98490e3b8a18b89e601e2ed493990b7f6bffedf0b768360bc28de26d84ef4c2c1b9172118272f62bf53f842898cdd30bf1538cacebbc17da9c752b2af19a22e18b48ae093c0f38d82df097090c7ee2c06ebb421126d1c4266c9b584e3c7f902d10fe7e96eaa9665ede7e0556184ef9038e40695d5a9bee647059f5c304ffb41a0a73d6cc0dab4cf8e8360a629cfb0b172ee198e0fd13b5ec739f7986cba0f06980554f3cc2a3e4ce8d29a624ccef6d7e09c0c5ebbbe055339800b6ce564edabd1fe63ccffb6a773d2d6812470681e7aa4a32e34cbc41a0e63582ace32718f8bf1771e47685e7db024b98c5a8beaf54ac4ac370b5782cf42ce8c0c190ae653f34ad15f5188a6168b383498461a09652f82a8a1aa1f530afbd18776ff8850cc83b11315bd5bb196e62e3475875ea6f3047d5822bc946de632dd8cd6fca0e287071e47bd136ad41b10b8496a6ae3876c5d8e381bd6dbb1a94ced56d323cf21353bf045b32e525b0e326964b11344d8aeaf6cd8ef23836171e31b7f7181a9ba0ec6525215513c42d561f3f0827d887c190d0652c7d110d751f2ed6c67f011590d1e39c94531a937ed345eba0b992ce21a48d4ebb9d0de364478d859e0dbda75473f819729f61af4905bd86cabb1f27206f3be97e06d9d1a73187a168b7bd811eadd8953b27b00c5eaee05d885db156a8ef6c4a801525026cbac229c472aef2aaafce5ff7a8e79abd1b6ac827e7e458f481e0b791cdb5c7f5f950f5398db7a4ac5ee6ecd3bf9a2e4992188fc8ba142ef93b5a0a7c7e357dea81219fc92fad06e6605d5d4d80d86ad2b506e218a887b4fa9197525f82253494a9a1008b27403dd97f83a2a960c9c57308bcec5825f0250c6b865ee6cc2648aa84018432a72efcd72da302166301ea4f56e9f2fc80f0006b6507f724242c13f630a8cb1277843665c572e9161bda2a4a65e24c5e3a7ad532b5a171a3e3d87cbe23531d81d99a4173d6b28ff6c42bf6eac3fb72bb9e3f0af4d2a42c1e84a9a0d1d271a7a0a2820c7211ea675bb18b356681e60a28be3fcb0dd198b81c2c7e2e1a9237deb891f2eedf971cade6de3593c824b140b528b206f9efcba4738f2be492128ee5401d147f2401103782899c198323182f9deffd9181ebed7962c5bd5e3f9f34fbf6646fcde6b006d3cb342c8618f122078e02c77d27119ff066b4918c31fd57e2130e7400fee29e9010d144ddb296c9d0665c9e3de67ccd0f513362b26ab5abbdcfc31ce12a2a91e98b3314671fc95f9df63b3690ea1759b18b9beb2b84fac899206d6a80ff66d73863c79ea7f39021fc9c8496fc360d121079a2fedbe07955b2751aa6e1516b55bd982b283d37eb7f959c43c9ce3d7b94f494eec30ab16d60d0efbc2cffa157921afe127cfe10a146fa8484968cfa0eefe665107151aad7b6bff957f9f0ec3779c898698d4fca929b73074ea4faf5c8a6a7171bd3263cf97a169e1908adeac7b3d95cfe1802fa74270a3034694de89423dc548ff858e8f22388b915badc1b04d8457c32b64b9051960a5d4ad684b31c2d8543afa5672e85b633537b399a97485d4468f74985ad28b5b624dbe2c011bc1599cb1b98061bd21eec7b63b66924c3254f187d00232fafeee16f3ca502a79ae6c4fbb6e25592463f7ee7eb145dbaa4d7d32ebe4b4548db2520e3682c79a7ee34470ecbdfb4b6755443935f664bb4907bf10c5275e485cd70559b01953e147c477f91bd24db739898e47a03d6e9afa1cd9355437f3a3d2b083c3a44addb3f8613544a052804c2e75674fdbcf836c4d3755fcedc7f2488e7a9e99fe1cc82de48ad6ac17a0a9a2ad38b03d2c57bceb38484b1678aa278d574415bd3e6d355d12f02dca12677f77775506d0f844e3b3a0f4c3cce3ad081b09c3e394db22955bbe392310cc33bc5e76903c2c46541748969f6655e1986febd04cf474b1ea758dec9030fcecc1dc1a3e4d967545d7a00ef220e1a127b326d585dba2f30da5da00b4181abc7c1e3f67a9a30b085434fcca8d848267ba21a7b1a5b7634663975ce777d0892c1cd96672a6b1a646c7ab01d863c832c9b47fc7478a0b3f07bd39e11d8da08cbb2787c4e2e6f903d2721e5ef59a2811fa7065e19d64e69e1f9a1440147a8ee0650083dc729a4b0d3609e47f7f829fa1a9b59b9b06fb672f996d43780981c3e3e300d247841f76494956d40f9529c791dfac074537bf78efd8e017a8e05b986019ea70b521a24024e68b525b16986d6d2f8379c909608178633e390fea3617618e8ce5b833088c111f6d03156e9f1f4d3059f58598669a8eb0aa278eb07425e9348a17472bbe5c27f5a8051a853dc0ccfd5c3f685f13ccd4c7efd7c0d1bd3f30cfcd2c234fc5f4a29d24b5f0de189f6d67d5a7f0a77c48e1b9ca785d1c02c3ac557cd2538f11ac84f54fdc5c9644c2dd0dcec2dd598292c602d2549c45282c581096f4b4a00d08cdf503b70b33e37b54fbdaf4b987b72da3501da27b5656979f7f0420c11b354d47d34f86cb3aebf5aa992819b0fae6e8298e599d5534b776bb1fb69b56639ddd2f2aefee859ef4c5b631a772e7e416d168dd080816f4fd409a194df7faed7765a51b58218501608f5029f9714501e090a281889343c139186a783736bc48c699aa58307a278acbb86e5b1f66a05c6510922d111a0f480a0693a2251a2d0b98b75edf55113c98962c939d21cd9dd11df61ad460643519705930224aa3114acfee91f2d9232e9c8889f12ac2aee97fe262a7299b7c18236e7650740e0ae8889418646be6239c3650c8de14c6d503763dfffb5a45c0222af22c67151eb9627e2686df42edb06e6ed1ccd30c81ab17f10b428b7dc6eb066b5548744dbc33f10c037b9857b166eee6ed0cd9098c744cbb8398241b38253d37584776b7a5f479db366551e4b586f720b7d00ead598bb921c4548f49d9368273500ad01c914a8456dd005a4346cdb2057ce80cec123fbe5bb3d48520c69b367b8e6d864ab81193fa1555ccc08780686a0370be40cf22acc428360e24f018b180e2a27e9598631272b0bff68a6b02531fb6da3a61530c5aa804575c7573c8709557022f7456c2b33629a26c076c98a98a8452242558c13416ad78b4adb2bd2b972a4d3358abf44f088dbf9111c7ec6fc9212c729bc88767724d99ccae0891bde99eae80c2589ee2b3a033d301d63b22c5e209172ea6b02edfd11af599309227ee974f38d9b77ce70a4bd642ef160ffd76e853d9c1fc7a27858ccacb653af36aa478d9a6f9cbf904d437de6771b70b761da17701c138f5f1a1d2dd0930c9e5c7a4e0356f1bd0e429b2144dcc345511955bb471514771f22a53bb5ca1a178b9229cec2760c5cfe549c60fc54b0ddb8f484553cf035eafe0c508b0837671ee314c11f1dbef267fe402024b97b32b40b954080a3e7323f2eb8e58cb71652bab0c1b246bb912190ee56422e3831b6f3a6698d4051b311192485e565aa53f2eb292848669909823d1852be4142869a19752359346daf96e7e49f85f2e152cadd4259339af1658d6e457bf0fd7cced16c8e686434a2fc5c0f89c62d589da28a96b452660a78d87aa8711faae79d2732592b2bd0b3a560cad54c9a9b086b9046972729f17d53d04f9d32a9fbe5d9d0d908c1782bba71f3f0f9749abda0edb1edce33b191de79aeeeb9ab4f6b7c7dbbf1932d9ccca879c11ede04d96dfcc4994ac73d4d8f5e5701ed1038133feef02f4c01a2c8d3d5b4e767a9b512c33c16dd7dd359f04802fa2e9903f2486ce933a53a9b84348997508e10fa63c50dc57d0accadc6004dd37791cffe308d2ec05ce42c60dc60b4484ece62c7a24c495346cc8ddd71984877f02bf4c338d4ef0a2d4ce2746bfc5a212aea38545c1937008cd03e20b4e81887a20b2d0a6a8a96870bdef54f5e943587d33c9eeca1511962ec300aabdd9167a2c59048797045a4b002a626c624e4da50a2635de6b619b2af32e3e0351635b254d4870607f183290cecd3271a8835433d7d10d129b194928d6a5c06bd2e94e589e4ca8ae13c03d9183a63394e3cb220787b8289aad9c6236d35b496023a27e85ed60a14c75328b3ddb69c7cd0ee4c4def6f1b7289089795a029bafe62f7ce585bd3cd1814cfd090cb0c560f7d370d91184594cd06fbea69afccbf1f1c20c31e15c1783c4b3c144da257695487eb793d0041d88b8f60186bfc9b607f2a2446999673216bd2cc7060ac49b079ca05bbe83f83a41dce6fce9afc7b24af4d55481f59de189fff0535159aad329a44a05626ebdbf1d94d18b2657037dec74412c9c000fa5ade20e3e01f49376b6fda09b8b5de67fdf22bc3e040e1efca4a25ebf80f3d8e03da89ea107709c74a49082e39f7944784c2a29479a83c742be426d4c95779174ed814c5b3a66b151ae1a52ddb3355aa2a22cdbff22ae7695c30090c2ffb9b46e33a0b1bd500d69169831b46cb5b9673d57c01c481fef575d16dd22ff5b558de252620cd732e68421f18fbd2c2e9a3624a981e56fb684f0e296392b0ef94ef77a13cc5b4a2eac92d805d51f3011019024b492496b40ea0a2a8b318473fb9a6221970200b4311c39cdffe6832770f85569c58da0be34673e2388156f074aa42edd75f3e468273306993682db5767d3711a0d14ba75655a4c64b9061bb831b6e5c2d0c58872fdab87baed90ef512c184250ebafeae305c60f020fb802c6a169db66651ab9d677a2c971c9b71531809c1c1d0bb6b59a74c33f30e2b32200b2328513dba98e30a73f4d20c3c18193dc02cb46e52684d3f209ae0d801de4b70167bc98f5ddf0885aa2e28ab142cfbc01c99be142caf3b3bd8e8b5c202df8b1ed4fe24aeaefd6b0420ce74816c33135f48233dc4a011ab8dc4cc13fdf762bbedf24257875e2e2d15818a38cb6617283417a8aa6c6a6c36642298cc7f1a17b65ce4902eb64bf247cf84abe02942ec4cbeed68e0b9cdecb995a32ec72683c4029fe4b2d82e946d458721ffa229c887196ddddcce7e20f42f24fb7f012c40c24526581348d93aa66c09d96d1e908df8643270af9e9fd8fd613e256b8b7ff340eef5bdc6597eeaf034621d3137ce4684aeb817bc76a051dcebf2691a296732082ccb6120336e69948ee703f4c50062ec8392a5adb464c004ec477fb56f457a5ae661068608d04ecbb556b577da687a7e67c73627c4c47aae74210fea689cbca5da2f031b5e06d625c1430ac87df6533504dc9f0c5370b383172d82d93511b9d759c7bf935d5230c213962a52a49bcf748044e59be91433e2ee556ff6685ab8fe3b938be2d57db7285684e1e0eae17fb4a2d6ad272da5b729a7c2517ab1e0f550c977ae955c16cb20bbbbbc5a26a2798f5e302ead4183265370dfbeb229a4ecf2398fcdac89e9634f4c60978681e5712a064bb8e2b07a43ccacf7ee08b4af294d8f986bcb304ff82a3204a0a487b72a8f45bd8aba3f8a2333137420ecc71c2d1692e24dee4c63ffef09b67e1dfbcb222422bebb4e1009cb8e2d1463a353529bcdd97bcebd6da2f11c86089e3f20c4a63341ce245ce4ab3de841b081865ded615e8354b45a77bbd8704905a06d50c1c17bf0bb072fa79fa07617bdb52f5d68ae777e033d8ae8be6829de3a20183ee72485f73b921cd56b5a6a3ba036a1e1c4c89355856c4d4b69133ada1997ee7915b0e6e73677a1b48bbefb015595566054de1ea727e2d6513f74c7d52107fd901691a4019422bf1809938bfcf6ec19ab951431acb030ab3dc52a071498643d027a1d41adfdb31dec49acefd200401d66f30435fd0a757f15e7340f89aeee55f573e065bbffc9b3d275ee4ed7862f610d47edc0997caa88ef533a590fc52f082a472957c27cdc09651811966a5ae676a1250a11f912080ede317449c7c84ddfe6659d5d5933c22814764fce43e42cae02fdf3340f4d0a601843d8cae7219a9acf4314d4a5cb93c2eddba4f9bbc9bbcc7effb373a88f34a5bf444435f11eb76f3934a4f404add9282378e3dc15b06c008f766fed68d1e15bcb27a976bd9fa54ea0201d986bb5317f40c31cade6f4b4c203aa967d2871214ee5765e88c8b4b6401faba2b4b2302c0e529385d90b4430c657b15531b57f4c8e3373b01c225065c016998af985014e5c5e8fe99893b300b96ed219b6b5468e4ef9a1f0b960456e299e138695be55514f1e6ae3bc8ef5f525b9884aed0674da427341e50163e1da6e72a2a2674d8678cd01634473defc80520b38ba14dca438d0617ae9642afba9ad3834305f7fd74cacc9ebafac2f4c74d3bd68ee6e827179f52d9725e6599d2f0d79b5668c53cc76a502e1223c729f20893b2cbc85ed5c02ace565c9c6e2d7161e9c96a6eb1ecdb01c9f549f260cd4884599906444059193ea5b27495be531a64c60a375d1caf782b9c6e87deee7a2be5754434dfe11dd10ad41374608285e23ed834a44e2442f4e804e885b556e7a41a3684600e6bd80bc9a21aba2f51024dd1f6491dde1fa6f198ccd8f5ce8fc56d8af9c6fc798adcb52e96572e90a051e1bf94dd36e4bf40d851920eb87be3ed26ac19d32effd0d51947f1956cbfeb13b0fea0e74630915d1f79bb786363b3637f9f3709f894cc5513391db01cce56e827f34738c26fbb06043b3cfc08152ba04e20bc4bafcb94bcc791e7560f054820f3226c58715957015472b5aeaa6bd0fe0a0be6b328e14e95a1ec28928d1d3dea3d85b07d9f2a685333acfd0f4e4f9800a911d2b83accca03ee80bade8909cafa71bc4a738b05d56839f7681b365070707c19a06900a71851ab10e5454f3ca17c793b41cb38e1a70bd96a2c38d9d84c3172032436bf3e81030d26d35c2b1de54350d7f18f24fc4c70898a8ffebb14d378a256defdfec1fbfba3e5bc54472d344fc1ef7b392fda337fb208aa4acba81da0b9e03f9d71871cc1142e40f09f57a2afe442b077d05dd98921afa5289630a4f699afb81ac776a99dcdbb919728241c8b28701b5eddb2aef8e245b88407bea83b0ea893442c61370021a03e5048ea818f2e90f81ed46c7cd0e25827071b00f4a01223fa40171e1642ac94c184e212486ce299b2988515b187ca3ad1c9f45d85bea3fd4705443952de219720fac7e540a77ca02d466980e21dd140d2927d38979029180f10de1c8fed5d368ccc6f401978475de5e12091c62d0149f64c615150fd58f30407cc2bb3923d8da06da6a8b5f52d1529eb9929b32843ea11c5c8baeec819c8afc5848763657f84f6dfa63fbf050b3fd82485e49c30e29d917c29f5946fff9e6b80897fce88e0688ca7e2b878ca1e44fb4b4905502d0458041ce4cd42f948099266302ece1ed26d686d49881806021ce9e148021c6b21d0293424136d047df2ab35930927f1f41c73f42e9bea61765b54af114dde4b3e26234ebf42bd1108696765729a8927443bb53311537c043102fa466c854a4a4955ae5800c7d479abab134b365e59e9c72e83c8c8ab35a728248464794c4858ad14339ce8d4cc57b142dda3497775dca2c1ba3412840d623bf28f6448b4c2ea5d1f283948cb54924b2d423df8761c4f5782c3b12487582925c3a10153ea7bd91708e9196f57115ee4bb4f19708bd23d391a472b981285e486766209210e2c518ca341884bc22cc84267e004cdfe15cac0d7a1062ced52d1d44e1117ca4230a6fb57bc33300f36e9906b476c724b6c13c72ffaa5528c0b5b45764c747b46715688ca847a7153213de367f2b0c7eba016e299455b69d31e82bade1a677fbc3b702fae29a5689e2c90351cd1ce808b12257a0a81e02d7aa7e65f88953bf44ab5bdb6dff66251830ca3f25bf540dbac8dc7b78cdd7e29378071b040de96448ca954ebfe545842dc40a712887f35eb0cd32fdb6489e6cfe715c0e903e8b212b7ef048abd9d66053fe02d60f5ae16d1233a2cd5519050a92992ce98aefb72c163e35ba664a259f6c17ff66fe19891cfcf0254ce9853a8f6884c068031b553d9eb990ec723dc06ceb9419689a14aef187d74ef71675614fb9d753af8feec9d97c89abab3ef8f450f55e13ac0e480edd2f0fc0e53e256809d011cf6b854a84eed9c432a33f348725593419df15eb381a2e730121a881abf7b29e600d6419a008340565bd7c866e6e10a1ccb6a9049ee9a94d1fa35c5c19720b6e25f54c61821879078a03ceeaedc6362bcd0898557b693f587a70971581fd0518dd6a83d2bf9cf1f05f2b30a5f0ba290f83849587a3b795c2d1e350b25e273b5953d8ae5c8858743b5a09d0a2e9d5f772ec8f1226703dd47ef160622d3fd2b5787f17963393184a243ee6fe0861e8d48cd1f97505d6701512ef41da07bb277bf79bb973027fefc9c8a34ce27aa50a99e207e54bb40970cf63c88c8104068a675d515910d24b777619dac0b09b022b7f8e6caf8ef2c2d7c91f476f89de58c90d4c611143a7f772ca2955eb543ff0d7ef5bec1d40cd2b6d4ef97926ae1b98d709ae9a819389e26445727323fc7664bdb1c8f5bad2ef90f1dc82ce6a4682b7a9effaf169adbcc3db015b357fbfa8f9e773061c9d42eb22cd795192f4da985833de501e775f14cd58af70cd2a2c1e71849602cf265dc8a5c654411f73d1dce6997c1c6a02633edfb26da42c6a7006728ab1068f80141fa8d5f9630084026b2524186b34ccf9e3fea705dde4606a797e1c3435a1fbfe8764e4e5ee211e55cd368926eca0437fe0040fe3bf12861dc21bc736891b23112631099456512cba94706e56f50e175c1153d36ed93c85ce8ba194161efb5f9269bdf86ffa35e626f65655e0e1bbd9becbf1853d6bed5685f3fe1b2a08c3be820f2853b5c9e83c66f28530d135641343e3b06498fde7e68de70837ca49b801d8a91e9c8164e23f964979e99766431b86ab2fb4785dac439876afc34491ae029c7548401ee818d7bdda9e00a795c303b679c9ff49ea7d00f5587c8faa59e6744926d643ad35d84e2f8a67ade9c476dcc8c19d56ad6ed1456ae4f0284815427c49f2277f59fb1faa29c6500f1baeefe9cd0df345e1a66bacd6adfec55cf2a2d2383816b48b6efa53ba673f121e915511ebf3ce246deea71578581a904a5ebe1692a8b71ea8fd9635ac82084625148c9fe6698191dd9556b745f8214b257c41a3962df35058ee57b6dac70ad887e76b6dbb35515cdb62c320d3b8f284cf94ba2e53c8f8a6cb4ce4a49b607125112ae54105d918f8b80670e8ac050a41562c3c73436c8a928bba6694c99dd5d1bad666879203eaadd31cb33292999a40d16d26befc6fdb6bc931622ea8fa0333843a397118a10892309441a83b82f84ae0fca6987d56972dd553229fe2ae1e48a80b6370d030bb131fbdfdc1f63437bca62fd35c4790ed5ddf0f1b3079231a9926c135072c58740cefedf2ad536922e3012d7482fc2ed5ae6a401c11aebcb0dd28a0149095ca1f27a8c13722c782698351ae6f7819686799733555a5b22029891d732a365be636474ae59cd31dad8c2972e42c6ab1a04b0a43a9bdcf1e6f1edc8420f10b7c2b189e148c60f05ca77d8ab184e0ae977fc8ebca2c4ccd8d7d03e8f46a4f7797ade07877c1459732a0898f62f1dc0476fbbe4421c6e7dafd02c2b577a4753979b583072ac9739b79ee952aaa965e31bc34a7ce2441f7605457739c0ecf54e8cb11c9604679b01faa0191869a98bd942d11ab993285655dbe54e2e920ed5038c202d18cb7a9baba3dbe66e2dd025ec5685e2738e3c10001ee0f0f548a71e4b47890280b675a028de6f66a8082cdf4a539cbdcf2959c1a2dc148efd55218fce24d410d401b64e5a43a2bb4bb8876c75828931a1b86da756dc2ecae9c4c4d22396129a76274c92f0df2e88753bef5b4ab8fa9a8ced53e2352c16b9b005ee3b5dc14855520f2f59fe210e0b82351c0517c1d8d16927c681b2c7bd1d42a6962093373ae9294fb7cf176cc3eefca7eb2ada239f4609bbae5bbab837ef2156d40c830203efcd2f0e289aab0a138ff68540dc0712551149b4586f631ba07d6f85004ea84b28c1cda635af7c3b231733798faee680fb909cf1c6f9322da48d17072fe433b64c60b256950312dd51b932087bea40ff0ae20ea468cad25d79fc0ed37b3b9305f7864b111ba58cc4ff7d23fbab72170baa458590fde830231d98caf2be60abacfa9150243bb86c0db1504281c9fb9679f33eb505ed5204a9fb7e5c8b56b3dd3788d328e2a9d15b445f9ff60a133f4c6c319fc1dbf886d6c98a2097041870447cdd7738b7c81424106fa275153a68206ee7f21d92da4153cd83c944f827cb786538db0334224640a096050075a01044200eb135ea712472cf3fbf96a67d9180f6ef247f0f89939e90b159fe1f3786adfdd6aeca0e092c224b8006b17751584ee068b5f5695e2966b684aef5b11aeb8a8fd2044275161210d9acfc8998ee926e7ca16cf38eee05303f39a2bca2f8e081954032bb78bcc3ab956a5b2060648fff8b5177eabbb704f1b5555b5de8b80ab10947cf52c1909c7784b8239e60d25455f35de3fd4ad6f6614d6fad5e596c59cf48c98787491785c7725a7fae719da7e6bcf43499db4b14a3120bb794bd99f31404a139f93b8e9ffe6e401b7e200b3e4c9f6d32310473021aeb814e8916c3ada7a5e82ce39246b052eb9219e1ff4d5a00e37140b96dd9d18fde32874334ae6ce2a22db0e31318acf98f351ab5b0e4637282483576f3906ac7c90a40d2a7fac82d52e208be812eb4e05fbc939496122560a7be667e82763b7d60d6150c7f09e10c14ac9c62cf551c45ce322e62fade950371aef2f37cebcb8e1ff09d302d90e9b85ea974e892d929f383ade43a8d7738129fb5388e388ce8a429783d6602f1c1c6cdb7128cb14afaac30283d87f5745aae4a74e5a72feb93da962033d6f4f49400fbe0ecd5bb40adce3f8fe41abec029d5eedae924d0da2df33912e07e09f40d9f998872bb06deca410f84f025f99f02af712e86d3e5e800f7a80da30889e99d62f5887d0410931c807f70ae12790ff941721df1fc85f92a6917c4653c41dc00c7af993400209ecc4d0e8e3c3d743aa198220e45935903411f05e4a033024f88724ea8178fba0eda71999c2c00f2bf73c888875bf17f96c824604cc1688c602e13361145d9ddf11cd5f1edb9e71f5ee2def31510c1578593b75c86c6585b6ae3f2e89d822507a1f7af736133c8d1ff6c9f89d3a09cd2e1ad8dd53a91b9970430699a8da912a6efc3c19d82fa8091e28d703c37d8d2293a3d6baea2445212e8cb1dde0ca857e13257ea888071729b78a170b6b6a805335edc1fe09946f59fd029ef046003dcdf9b02b2272d82721cbecd40d0a8ac8ae0b4e9f4c71c63076a3faa191aafbf096b33ea760bc74d5d78f0826b7b03657aab5b67d9f2636ccfcfcea5d9fe9b312e155a8d0971a78288f78b6a0bbd1384f40366383733f59657cd3151592a4d7a6132cd704c3f025224170cb3172fd66f49efce417cac2ea4f2882d651d3824f9a0320b493df7b410a38ad2722032a9921d06c655cc0c2b5bc8a6476b27e132c237d22d5880275f6a48444be5584281c0d76afacbd2820a766209cdadbb523838535cc5f7f69be0f34e59ebc9bb6ef9327a24e79b60b0105382b5b7ba673ffe1251c2995cbef1ee3d6efe8251ca5e5deafb2c9df5904a401033dbd0f3fb4d09a24b9d5f28b29ae396b4fd7100b6eb4d50e62d1af29f018930e0f5809082b0abd17f1eb8baaa0211f2bbf6cf1e2a51528b71bcac670d6df28e3618c8419dc63d4d5bda899e4bd4c004f13e7bc6f6b88e287de654a32417a8e273a90c588404653687716d675302d3f58513fb6b293e9a9d0c6c868a99a129e3627bcf25cb531d7a79d5aab4413871c8a9ea543d5bd16dd3b811b6886772a65c3bebc39d4e59378f1d63a2f67d48c6dea1f6cfa455dd8fbc38e939e2406d4e7130095f732b307d98aa190a4a32a92510592198e817211b43bf640b8b795b262a9a6274227d840f7605b33181316faf3b49fab71ea06b846f07f4a95baa2ea6e24a28c514dfa730b88da2c698641005672ee338672213a88f17441710a9858c9c64d6c7755689596291866eaf2d7c09c76433cadc46400fc522429928a004ad3f748042adfd9513c207c0208048295808ce7992216693e00b57b8c4bd24614389fb2c86feeb60dff532bfdedb74da93f268e4dbffbe3c0db41aa8ac39399b7d3b0b60d8879bebff4edf4cbc52e84ff592521a3dfc3b01e98d42e1cb57d1fd6de43e9f77534d6b2922a8ca937a0a6bcfa4c70be0000da13c8dabd6777bcdc073e0e08b95f3a3efec6cef025145dd1fa7493930c8561e121457a653ff46c225f1a56edc767b0e61e236c3529f19beba65e2362ccd2715117aab698829e1af7ca222bc91bc101aa9b0a5012ad6c9d4a35dbb130eeadaab51978c5cd5b04f0a4494fc432b1e485fe3c4b8c0d42060d5018f79b3642e4bb017fa604a00a25016e62f72ad335a8ca08516f516201b7d116571092a6413bfd03e3b1714d83eb6f2aceff06eea44745634c4d44826fbef2f7f372da0ee6bbc34cea1033ff82d7fa2a831f0067ed02f0f1a0d79cb26b4d0521da7259f234b25b8b5532c12659de38f719861703dc9b4cd3083324e0478259e42483a61d6a44e84fc3285bfbeb65c2c8e4960dd0feb02655f7418d81ccc99288abf0486318c8fa04c0d82cdb46adc20600e841e4168baa44f5bdfe615c66395e141f76d45e31599ccd9189afb234564a1a787c5e1395ec8cdf4a3a863cd134f55e49409e6f4446d3c18058fafd1295b2e19d0666b4fa2a7268932d307ef6a9cc86e1ad21da32942b1593c2c3462f9d1ffb36b2d88683ba6d7bc918b34cd5f1d412cddcc2f8228b34e1d96933c6c39bea970472655c53f977348e1412f7d0e124b478cecbd58f4a5063ce2059e9536ea47b0e43172c2a7c76a8e7bfb3d84327cd7ca671c957cb5c8b8d14530791061faa95277e8410eb3bcbc6f38daf0d97399e6467bddaa5c55dbd309a32e6fce3ad2e8940f2360dfc8f77b194d2381e55a9a4823a2ea3238041a4238e8bba966123f548a933c8501b10bc6d8639be1053a756c20e46310cbd5bb056ac50edf1ec389f8ec1c041074590c964a2122ade63a3c7857efae06971a23a26a0b18a4cd9c2e94cb146434a928b73a7aa6f71001ae4f61d0884ab43299117bb1626ba9b503f81a302380eb1c9275de3002749e05f112d29f5d476e25b9f97b905787cb7c70ea14f39909099a739c17f7b532216e5dda5a7156e2d907722b66c54ab23a69e0506039a33a18a3abaf859e19e1609d7b3115a4a4bcb6e690e459150b416a587202d30bb5395677059bfda741626babedf072474110c7b8a843a17a216ac57a9925142b179f89e537d0cd57f3739f9ffd50a6f4c02ac5d8443fddc2aaf53bd9f0fdb018c53db11a97405ea87b44b59c85014fdf0a398caf91c670cdb0805bcdf1e741ec4d36ab77e48531bd6afffbb96e6218f79d358a84e313c7bf4805660c38f76e0aeb106fee5197976f2f9cb0e6dc90e1c35fd09f9c68e5f8e3f45d8115ec6f8fff05f772c8dbb5c0d44f4d90ce03336d579bd68d22101ef72a9805e9c7b152bfaac0ea15c8f3ac21b1420c820d9dd580163fdd743b0ebe9a26bd0d6d2197296225e7b18f3e55d8d26538c5728edc98dd85567797aef9298e00f13c8e9bb80a2d956c944b42de90731553906f8f41be77af8ad36e7cc4a58f165b262e9b30466e1f817175d3a1d9dfc36ff68a0da3121ebb820ef72888c0331d0f06b1326d76979d477ad0438ee97710cc1c86a03a21ec778c4bb7b18fa9e7c854df32ed9658c0734da9507b8e628f4f3aed61101d928b65bdcc84cd9c70ed896190f58bfb1be0d489a4b52dc003987a0b4689642758041032aa394fa7b8e04b3011a01c11d020944b2cdab16776f0de19e3fe91e0df5af3b067f760e97245383f4d217e44a815cac4131dc5fedd83e89302d9d38c2997ed03f868d997f3a0083ba568c8ec8cd91082f34175f74c9c9b37df2627262079eed831048b345613f15c2ba1b20bbe40549874805d1635716205574248f2001195a9bd87dde611de01cccf96932a3f51475930903145ed5a5c481416fce8ae0eeda25e966034254c8624db7a5376772e735476cbb0ae36264e044f4ff53f3870f21a7199cd1764918a0c628b054e915e41fdf34d9aec399197827917b2bba39c0471ee85ad4c56d1aad9f2aabf35eecf0240817f197d0ca5844acee815619c6afc4df5c393e8c306f5f26aad7528f80321577e391950e62d8679be6e43120e186df31a8091719dd077c32d82a2f7a90a2ee14eeacd131aa6dec47637b1ea59f52ec76e63a9cc1200f666325d107dcb41239f72f0ced12982d2b6cea4f16bb6f4fbd6aed01f661c877a80610ee1007f5e1cbc83c9507725a9068bd3f8136bfaef15da0f27966aa17cf9afe610771f678c5f70f79d0bb5bfb4b693b9ffd0928c0297ed92f60f06563a4fe924757622554f32d673493135cf3a9ca668e7ec46e23ab4b01735857d18a6bf06981958d7ce7829fe0c12c34c646732fa1ce96141a65e5b99ec6ac4b8c131eb053b459b91ab9b09cd1a223bd789ae6ed3e1cf17e0b2c76b90d34f1cbcd5a002d8354ef18c01cc9dec8dce67e989119841dc514f427ec2d505453fe1d4a932f46bb1f8e7d9a65cb9fecc307362a5d7eafc3e4605e7b2364673ba95043a78cce173ac38c710b2620e06c5370120c15869299bc88b48eefe98278a83d35c46168fbdea497c34c613b31d4e9cdedd4628c4639bf2526fe1121a1277b03dff90605746b735eaeaef73fe08ef8e7e333bdc8e9c00c245840db48aa3beadf5538b3ae43f098870d05125cea03bd43f6cd4c055eb8547bc74ca32d6378bd0af58a846ac0965870a49fc74a7ca9f4369cfad9c21870220d76c42fd4eab9ac029dd336c0b6afec4f3a48c58551d04c6d15b9bf18fb9c2c2890ada1f046b88fec1391e17c60ff0bb9bedc271d6fac69610f17e8ea37fe74a176c82702f4cbb1c1ed44071421a6e296a1945363ceff1ebdb3943e72452c35ccf7765f378c4103cca698204c23884cb6048f4576c7cfd9de75667441d29740018de41d9e68004915b9a715dcb142480469f44b298bcad5d7f50a110472c98efa412de53ca818e04066d57415b8ca81013cdb59a006c157ea511efbdfcdc58d86cd3b2d4b96b209a53591cc17e3cb00762d5f62255618be056110092945a09310fe890d789b2c106b8b6c8abd6b54066246732f7e5575628795bf7d4e503358f93505bf1c30f136a43740e99322ce49bf9fd6ab4181a0f1fbb2755d8d993d3827107d6d6f8cdfd7691de9d8ee7f96212cc5df2af3bf437a2663719d788c39bb36a1d9a59a7b5d7ed4428581c248863bc71c33f3f8a9064a504f41af47904373c583e04b971b4996907e236289a21265b375a329fd217223815c2182639a4c54c03f038f01f2cf302b8c1df57710c4e41e58a8dbc444ee2cdc05aa4c168ff056099f7d1a9626c762e9992b459eb28c3f7db9ef9d4b63160422801b711f0413e0bb555a08336aaccb2aeedd111bb913ca118319b0061ca2bde5d6bb509adcceeaf11d2ed69fc6874cf7fd4021d8d7c8d7da0c3db868499997379cb22c41b8a7a408e7e5c1c5450835fa9e10d3cfd2fc9740245f260d4a0d2bd85cf0292cc27e7fe4858cddebd82137957308175dc04811a349e7f85f2c1ca58dfb9e04cc843b66042e0b40443f01f505360e79d0c0d0d7493ce4b4e7c2e00192cfd717b0b39e5ac2e940012072d8772106cfa90d9bc180a1c6dbb3ee876a19952000e51dc6450c02caa5ca0a11a12014e4239947749683af4f55352056abb144395d9df4d8bf67d4526925451039bdda0d1c744da5e4575145989108407320efdfe34a1165aa3c4608c0bd1a333bc68ea071363b64f8e228a9734727b43eb87df8410c0c1b05ea3eaddbcd7f53bd179817454c7ad158667143be6f7c025410386475aa646816fe4d30e6510abe49c585a5e9fc61e6262a17ce2b153c192ed05ac637a34db89f52a7180f9e0022be561ff29bd901111b5f22a98fd0ffb698270bdc19718833fa847c615f253be815c2570c5c9f87fb061cc63cc50741a52160eb2f6154dd41a74658a480420a1ddcd214acfa5f54f2dc0f108c4753a11d7a9b8641e7c9fe3744ed3d183a6b888fda6417d1f27c53cac96ec2cbf62c6268b240234e4283f07323b1a7617f726151f2e16c668a78cfc22507b1a164f81e2b0746cdce2d0db3c63f04f82456189b9469d6768dd409c370526c6d3fbcaf1af7d8dc7675b6bd63e05444eea6d5e41628db2035349dc171e3af02be77f81a6822d85c29bd5dea00b6d9e45f2afcd18f0622fd179c0f05209da3435174bb789ccebe4a627f11a95692eda3d858310b7a036d4a0eef435c74a5c9fa344a9cb37983b97343b4d2f50a5a42bb88854055d14828fd99e672694f309aa168d63b7a1e5b8441d5f799ae4fafb4c7945d8cca24d40999fd8981889073c7dc3bbca9b060a0ca2ed5c130920b3b0c03893bb8fbd3b5b9a79d92bd31cff9a8fe53deca91d0f3f5c3f7deb1221d58a894e0ff93eeabe670a146b2f18ac119a02b2c74edce75900eb87b2573e091034eb1c8daf9c7a131de8429320e6d3e9116e789e4a53fc619cea1e670b48305bcfb27d8dc05452e1f851c3978d076bcee70dfb8620318b1fe7c9fe3ac3305d737f7f51a738a500a392e7c8e0f2fd2dd7930e5392daaae1ff131cdc3800f1bbd40978907523751d460f1df51c0e8c8b51d9a0dab61b7f580489eeeac852ff6e8ffc6f468e8f9d780a6a650e9ebc3abe5d797e021a0f7597f2c6346252b7e8861070c1159adf9955993bd1eeb7d0ac155864f4094f9ac658dc85f2f32cd9eea110fb58512ceb393c8ae90aa9e31012d08ece5927312938ad0669c0a1230408bf6bafc395ff6d965c0aa9b0687a66639093cba3c5e8251e5f1fce2502da951447c43d5d1acf9985260f7a092b7a5b3e929375e635bd6a8829a1a1ceb4768a912f6249f6ec062a0ba9c0334b4b0017de97017fd2a7c2de60ae95f3be46460010ee4bce35e1f7ef15fbc0ba6970f8dc202fc1c3937fa6771c8185e65fa062b7e1b7f9a2ba3e7cb829abe5f5c3a527f60fecd62d271dddf1b7ea1c4ff19dc0dce809f26476b9f37b55b8f74012f2352683abb408053415a89e912a48b399f0b7df5a15cfb7436c94ccbe6d7ea3c94e2464e51a7e438a5ddba6f93ad9369884be0034d000a401ecd5b43a68ae644e7761f535b081cc420ec76b50165d94ae85a8e6da6892538117e41ed23cd75272cd98b6ac35925b03a17492c23875231abfc0f16f7b90c3b58f4f92d8b361d31696d704bf459abd1cbbc3aea66baab105f7b49b8bc79e7193aa07e696442f3a327c5d097f0bfb2f6cec225cd137807a8f87cba1b9fedcc816e1a0bdd6e2948f07e2b6fb0b71e3cb8886f37e0bea940119a8208ebb4d06106338d04081291cdd753b1eecdaa2270c16b2093c71d53aeff1494867d0c4bd4606785e24b7ac400301901f4209c54a911466b494b4fb183f347178c0747ff1dc6432d6b1093dbb36e292cae9e35fdae32ca9486c3c0b15441ccc27a90c062c03766fd07d77ae2ba283ea85f829e46c0231099d836da229458d5c700d3aa092d510b2b4ceba1ec11650b985ba1709604ca8804c3d3c18a4811fc433980e7d99df65515c5790f10410b8052080ef208b145ab7ea40214168461ea56d93d45a8426c30cc2699e812ec620d71cddbf31eb3974c35025e7acde5dc0ef5ac054a6e30841c805986cc1a42d1a501c0abb8e6934e1b56a40d70d0499de386a66287de65e84962c86645ecad5379ea2882449035d512649a6f6c88a91085de595f4c3b2ebab803d7323aa00c0a7358a47d83f2bb9094955494fe6853495c4e52133840988ab8a72b948578afcb2b209c126a344d56cfe56b2ae33b5b89f96c7d6aa35b788bd06ef573fb71a06075868e4744af71a4aa0b665a4fe79576219e41f2ddb8c77c498ad2167f11aca8c9376a0a199cffa877ba059bf4f461a1373a93c450293c3300103d08642af2b8c1c1ccfbe3fe4442194786d3ce3ca22015bc59259a5ff83908dcbaf623efd2c0a908191fff58aa355d48b44b1a82dce31b36a3abf6837882daf5047d4f5d546677f894b956242799e3a525b7ec3e825f4e003055567cbcee91b265e040d82581a7c19f7952547b41f157e5752f88bf60c5fe62ba56d0de6f45d130007c4de68ffc66af75ea5e89235a5f4b90a00647e79a3acbc9a0e33691e4cb1f18d0a7810442b49bfba1eb77450bc27fca89d6c3f2e631e3c2d040cca1f5579891fe1752f493555ccfb29e675fb7d5ada7e02c6d026d049a3534b342196b075e135ddc6078dcfdf4422e62400d5830233ab6b24105ab164512913578343620f3713d31a6f014b4850811dbeaf506122feb2041250a77720f52e746a6002ff074c9ee00829ad2ddcfcb8abe4b1d6d9832f9427673e9b8bc63de25ceab83be3aae9778918a1b82d6549683187c900bbe43571c4dc3673c480a3de7c9a8fff20bb320c84f2d20ae2bd98dc9cbd220330e27a78bcb1ba279b35e39ee18806cdfe7d7f6e3af5bb03401362a78d994227fd90ae65a5d63996280d857a8999b02e215da8d623fa12f3444ac98a94831d38b07e90f2cef0ec21ef7edb0e575a855ebb0a5aa777093bcf435601b82bca2b22949166a751e5ff79b046b76d730b000dcc8d75f56e7380e810bee5775d244a05f5b8c9568504f39dedff95b53ce24e68fa1e98af8cb77e5631e6dbff8f3deb62620b04ae1561aec71c8df9ce27bb7b7d64bc0b8de3c2d8525778fcfa4252e51ee6f1f366889365494a0853aac874dcde127c9941911b8606f46a9cd6a85dc027b7bc9cda337d5bfb04fa7bc193d2da402cb1e9c3f3d053b6301360a3806692be13812076fb9bb56f519ce815cca7aad43300992772fbabc34a7c2a30dcb375080de4403dc5a109d0ecc2f8de6f19c8b263398b839708952cd354ce68dcf2c6561110235908d65ef5eb404f77f97fb948b8c82108e41d4337ca53dfbe15f31db3ad487386435507d773e59f90a5e5a1a3651cd45b02773d8f71e4f8064efa3a902e815e1a0fd98446a1491f84d8df6c8e384115fa6d1ffa3265a184d3b98ccb21e885831ecddd4ec90663367c0620ab7810a0dcb19f101baeec58e171e4713603e69ef69c946a041c7cc1ecf088ea2956b55c8a9ff8b619d52cd08da70aed2f47a20fdafcb5d4f8912fdb914b328670ad64da9b0d335190d3399de7d611b9c6256b3bb128914c3ce18b34abefb09db252f2107fcd82efc9bc30190853c512156166a03f52a45922e9892f7c017e58bb3e8a7f1f538840317ab8bd931dd20cf69c64b4ab6af0cc35c56e786ead61cc8ccc8bb3c3c6bca3a0808ac03b8822f4747141704e0a783b94aaf7b16327a9787226d4f90786a832c5f2ef2e6b64111d9015073f4c8d67687a45db95651307ee1df2974ca5ace5481e1f1ea2bd6e4e41cd4036b4097fc8ca3467e030e785833f87654bb53570c28f61d52ec5aa93e37dc9f72ad8a2a30058319f8abeee5846d454267c231c982bd9a760a09dfb7824dd7ffec872384b01efb78653f9d84baa3e492c22a741d1b90fce50e79a877a28b80eb23061ef2c3b623cfcc0cb8a13c819cfe049b0b0d376bd0ddc1b8d2df6f249735a5754b6d16b42607981549d74e0538d751a716192777bd23357bf88fc3141d404bae76ddf7fbea845d6c0ab2472301c1a6d12274f3d662d91f3cb94222f8cc10e161063d204f11cc2152ae5a6116fc294cf47811e202a8296874577f8aa65c7d838c4cd9169052fe26a6382fa9f4ce52e92a659d41b6312bd1b50d2860b1c7dee8375c43ec40900223d9e35bdae127c9b459ed75dbe4f9caba443d2926514b6440a11362a04ee0cf73e407280e6d607f3e5c0646148228296f92815124cfd761bf584c90ee09ba17ae0d0cb94f414a65808a15360559c408fd0cc8bbfce46765eaaed9a4e726de66914211222d901d6fe45ce9b8162f1f60d9a90b31dd0d49972673afb202fed1ef043630776898c49ef67adab577e509a0a1a6f28f7e3d12288332ed022898c6716206176d8c9a61024a9e442adc7adb34ec8046447b1d0af89e0853c9491e871012940af7d30558190e150fba91bafea662a298bc726de678e109046cdf7ca2beaa64534234650af2c3b3f5e39580f8c8288adc95090e0a7bcfb26a7f1d3cc0c815e96e2a1c24bb699ab000e8a08753cbb53d45367116130238a4daea5d766deab0cf1b75376b3e3790f21a524a274bd517334da91689bf73e3cdda52854175b0e0d9058bc99101266105f0a24f29c8ac91420af246a264fb2ba2e0ca20e361414424f9a4529d1b34ddb9ce7af76f6c977df90edf25fe1517815c1e021de8c90025f64a97a0722d9afad4ab8766758d2f4461bfd50c9a91f8ceb27952a466a7f01bf4aebb3ee739ec2fd7e2d0f1eab3336d22b5a8dbdbe802362b14496f6a6c747ac0ae1dd13c1d084eb33d7fc1acfb77623477a5acb3db14f36572abe60141fbd4b56ac608bd45462f44d28fbf432647e2bb105443529eeacc5fec45b665f5009d400c76dd6d24a99a7a4ff2fff533bf9e931085aa92fc5c9ed7875032bb3d523bf8851b154cd57fcedfe464e908c1e984fafeff246af3f8082094a92b8d10302f001af04abaea3892e90d9085a04ce7c686dc64c9bae933ebadf1c556abf84f6d6b9fbeed692fa12c6e906264ffce27a9be730150c76ac24735756313f79fd5693f399bf50c823d5fdd8d7230d389afa31f829183f079f5e68822f1d0e891fc03b0bc384550475718205b5f5b1ca2d4b5c15acb571dd3d99de1e346147958693b1945054ede4800f78c357974c4168ce71a78703d63ec9996fdd4e911cc6cf43a941c14e66f2fbd3c0433497b45546e9e244fe17ba133022791eac05f9ae3d27fbbac14e401ef94321f6e0129fd0b936f4f125978873d5d6931e6fb576a4cba0c7834c7a451d8baaa5eb7149cf7d86c43fd55e3a9d311ea3b297f54193d741dfa7b5d024499735d23e4ed0a810018b7437735935ab0d0162850f01b1be88f6f6124892448377a29ce8e4149cace7186409e9cf99d90090b3d007d3a83ff4b652280772fb9f3f4eb0ac5888c06fe1d7745769d312fdb9ed0186a13b03b13d2e241a1e91ff2371de143e982ca0b4d99b74296e47b9671e3b7d90bf9858be611a5f1d433e0fbb5be5000c03405ca82d8034ad2afe97b6ab6421cb6877e2a8000308f393007a7bf13403087068176e16a16ad61a4b5cd0546a601961a263f922adfda6950131b301632e0ea696c1921c32e654b658285a2c267501907930620641a2a028dac050e0d103627a2a6a154304de401721c3443c231988cecf63b6b853ab5ad4c32b4a1086b4c8b1e013ba52f26c96c8254ac201f85eb73d53a56eda2e4b2caf05ca4c90a9ba417e111b6d17c84082370b7e0a0951594cdc1e211d33c11eefd09653f4028ce32b8697b8837bd56e216e73d71669328718c2499b453ee458745c462c386b8f3f129382e3a4c54217bb2156cdc131286d9b68bcc7af32731a8f03d4bf602a4fbf9e14869f0aac5dbd281de986bcd23c2e00a91599f302ff39f6947aab6a329011f584191a58637c7a44f105d6b39b41947686418dd33a36df60e621c91e2ea9e0558073f7a20385eb359ee016a9c697a17ecb78ca760da0b1330a7de30694c6518311058a8502e3862e507779d14149c4409ae8b10be32d70ba038201102ba80e295da21db101ad32a508796f5e0597a49c03c04f33f9c108268c861275bbb0e12a9d57425832dc42b682d64e3c9af3e157aa4408cc48d4f16a59a7f4860950172077d2b4693275be446499064f113846ef55aa755b06cac23d2179e3a951b528866f2872a4b5b599367e0c9625babf1919b3891836321e74c30a7701b2cc68608ba2ffbd986139883a47e937ab0972719d0813605bde34bd62e2106baafa345c03d6a07b8c8181ec883bdea7d6e5aac78414ae6402e608477a9f3d5d2f190f817d8be1a80f78d2a304e01ec631400b92f577d585967ac41f03511ffc78bb54a33cfc21317011a46c1b076f84cc4f06b42fae1fcfaa409ad5b93e5f6be0f0a60b02ea5024162856e3c263758b91287a95b1ff7d6ef443b6df104560a3fcd454f76fa30a5bbf6aec7afed8950e5e899ceb586b5f7e4f112f5abb49705059d4f2339a709d226c4e8ecf57e300d060b4e753e7571a0a71d7262c974dbd14d16f4995241fa9b9f8abc9305b81022f8747fd0ebfd7d49c6700535909afa36bcdee8cf1f8f5e757f4fe51c4d37b2d23a58abe1027bf70c445d4fe540143f0dac1bdd65477e96679efb045119353e2d8bbb87ebef4ccba05460030dec2f105e4ec8cbdb6237badf6c66c009d4d9c048aad6cf8274b5789c809dc8bf4c5b48141010977d55ed8a5c1c6b1cef254fe8837b1d0617638e48474d70cf38724d6d5e7442e4cb0185415aa02b463002d052bd4ee3f92b7641c1a33e609262611486e3caa3a7671e275e84e803b408c408f922470b1129c58a563941e8d4eb1784a614805c5a56e547b2637062a1cda4e737ccc41f1487874e0309c8c081dabffb52347eef0de26a73abb341f976f82ea82297612051a4f8240150ea5fa0b07c6521572b4cb827e4657c57e9e8e08362e68612514b75efc86b805af9cff34cf78d74f7f808d471506be09d0488297c168917044de93c68c2776e578ca6b1108d79429a8ff8c721a520192bfd560ceca52390b16d06c8b649aca0bd9e9f3cce24d2bf1fc6353aca13f95b1a555740685bf7fd9400615e76b6f64221419a31d5f762466068051630aa8fb144f2f15024c36d239a2efba2522a7d8ab84d563805cb19ddcd91c716b476c0f1538cbd034ad0fa73f29ea898652f712d060cbc2717a50dee8a4f2fbb86a350796e8676ae1da0737a83736090944627c91775baffeeed78a33f8478fa68e3cb496e129e41da07bd6fb2c41f17c7c24f803372c542a0ad7e46a7e0d0c753d95e60e8913ce694113eec95d280ad4b55488db6b71350fc6de4d477aee70885b8a22964602ac0351fd7aaae7c3a0f414239d150aecb50c07754bdb8c7e566470a58f38ae4f6b4eab52ea4ee2b6cdf970e2ee609179338d5ff506d46a52621fd44f5cb1550d507443dd1044e7f22bfade45b97690906d19f6788ce2a4383b64a791dbf395d0b1cf9076b1cda679908ee4a7ddc45a14b725101b386b17e493b0ccfdf647a01f5f115273c2eb9eb9aff8e20c7c2708091262211eddc41434516ff09dbc28c36fc11b19c6e44181070af03b30f8bf4fe41e69b27bc186871d360d27b5775f93b7fe8721b28ac5d814875eade81f8b656c69e00ed7eb1819534d1248ab61d52f84f1e004a72caf5c8df0846beaa4e8e599823d78eb383bdaf47f70ff92ba6b4fe16b352a57b370777ddc87ae43f2b9f7b3a8f5c39a96f3ef7bc82ba51620f89561aeeb936e806e0f3b426a4ef0626813dcc52aaafa76fc47a5c2b943ae8594d04366b1672b3486073d7e298e802078e9464a56fe77548afa6d78f996245e623568888b98e57b1a016f3f15d1cb17a2bef10cc2089fbe5a2203f6c505886104be9b8c6c5e7b3c3b18404d5ee37d254851d495a77ab348f0ff4755e0a38081f1fb738cb5864735903ab0523cfe065236f1e6a206dd86effeb3c58565744e24d26f813aad283c6dca20283303724143ef81dbd25b625c47fb7cb478cbdbf53d6391e4a6d6df4cea26900b91062025a850b70b0feb918ff7d43dcd06e9ac72b0e7bcb43f5db7e8be0200c2df9156ef91f7aae0f97fb8463080cadce9d4507be5cad2ab54adc68bc22e64fb3e3ae2ba58f99e8639fad36371b889e7f899d214da0418dbc3c4470b87552d8508a14b7f7201090f522b13a64b765e2c42ba472b5e0395681c8fa2b272207738c4439377e2832a87bb14b6fbe7d90342e9dc69a3d8051c6fdd602dd5d1f197f862a4023426643e8f0b3361c5ec2fb76cf0e47838fed41ff25114f917fe9d96bcbee1dcb3ec976a9e2e55e8ab01abb7afb39eba50a9506845e5ae168b13b2f55f0d9347482ed87540484d3856f17b81f7bf0f1edbc3eef5b7723b06311d84617dd769000aff6c2305767740a4a44260d4408d5ee0fe6d867ac9080b378cf41c80f1cd3aaf18fe8fcd5b15f203da2df112c103d3c4083f9f184b6ad1f22425b1f188cdccc215f8e0311a8fa202ecef048fa9f97471db630271fa81ced5d8f4788d350c0f1d85832c9e7611a63ba30e31db103e2140cc352ee720e4d6201c01a96173a37e3478a59d6a046d5afa24b3473f368af8bf0dac0ed40cc1f7ce3c1647ae998804bb353ac7f65881feb846f681650c856ae1a2c6cbe71190bbe14c0a948ae24765407b168ed6f25d85c22b7f10051d8aead42d4d915b70599c57662a8d5fba727034a91af6d705d432a5c688a0389ff69164787251c00f9217ef9fa4cf00c2536ceb1c039db86ca2ba39f1b63921b941e1d42c7e98f0e290db381b42d7ada32fb6f4b07127ddcc992e2f178a610d5acc487934e0578d07d1b0a5d9bffb75972851df09b982e8340f18705f0af40143c3db451d0821600952638b05900c965a2ad665938d625123f9ac3673cdedc791d7704c3f318a08aed37e99f7eb27bafc2bc49f93446b28ebf749c27dd644f8fe245a0d21ad06b16b3f185ccabd017d1bfee04c4ca16d92a3c2f8fefe16819161cbdbbbde145c0dfd72491f31d25c6b9fc661a5904589eb84a1c48aff52a360ddb3ce0dcc9cf64740d1ea822a08b2145aae2a0119063906e4ecc3706571d595ebb63ea13034012f21fddadc68769823642b863f9d33bc241523b221ad37dd29893b8c5161422e46ffdbf0e78800eadb9da845c5d5321bf1534213fd2430dee2aaa31910b88427e2c9a42eba7ebc52dab082a09142ae109f9f5bc392a22e4574a880323c0132228988b0dcd98faf8eb8698bb6160aaf3c5322c010bf6e1688bca65d5f791f5236bc930cf7abe3dd00096f3efbcbf8ffab1f34b365ec1dabd2b1f859c7c9f062b49ed5ec2009bf90f50ea4db1f983d5fec538dc358a2ce630a96d0040a8af12bbc73b971fafe33d98a6985f5def9b6c8c9cff80b847f6e144d0ccc5f0073f57bec3432e0bc4d7de3c05290edbdec0777bd31e7a83466405a3dec93b3ca3f9a7ced23c5961574568d370293ffe64c7bf559302ea6bd0fc16c7f59d372fca6ef20e24d26a65efbf96721e4889cb4e610520543158643f43b18598499627fa4acc8632bf9e5317994a99918f8c654d4bec62411b76f68680ade5500215d49acb8331cf63018d51fee0f665d01bc3cec895d05bb6cf8f0603014c2a1eca7afa0a2e282b7fdc63b094f293a1b6ca80026526290d98197b521e2b5175ad6723ec71cece2e6f650dbeac74610154c7c1fce1f0dcc713a6a426afe73692097042f0c1b9b7bc4c4e61bbefe2818a979ceb0404fe60a730e8b8df770815465b5d7ec234ef8aeec66d14ad2f25102170c8efacccff3a93946f7350143368afa0bc65de027db0db117978c073317d4838c9a5b140abfd68d9f1b136937a1edd86604f095f1cb094709e6b6ff66212ff29c0ac106d161bccb4aa66f383263bc66c924793909b3ec7e6fca29f9fcbbb7ebedd1cd0d5102f4146359f1cc362905d0faf32a863bcfc016927c1f6486b31193dd5ca7f3de37561ed4e18f14f39343c148c978b5be431c4349b511fd220e726eee022de4cdddff171d956914f188382e5c83b2317ca21266bc761aae7648b2d7a3243c29d96e3df7bcfcee559dea2f52f31654561fbfccab1390e4c2c6a436a66f9a453f8c2af2e647b577788ee386ae6984d68ebb3227edc559781751ea0320e1626b75aff587289ce04a6a1fe04acd230ac85f9ab58b9994a8732bc22c2e25434e69f88b34f68cd3e09a383c228647930c903f88fa2ca6b8fc7f5ae2cf46b99da9e6efd7c3855406ecd3ab83abdf8edcfda9283ccd95d17b4e500ede81967fcd7798c2dc9759894ed46ae3ac9c1dbf228ad8f15742c2b2efbfdb287e574822c51140eae7983d9958e40ca8f689de5361685c20eff44f0cf0923d8f5b6de6d28c2e0239e15dc55ecd392dc68f1063679df00b8178502d7943780674dcf5dcfcabbcbb2853615c4130b5706aec20b074d2a6058565c0b7a12cd0e6880fcc221f9d6c184ad336ae21d71f187bb0014d600f2f7ae93a764722d60ed8c96e12105a8acc09ca0daa53269ab0da10827c5d8ba322b2943953bad198cbd714bd93c06f0e017104a2c08a56cf5121755040e68aafc11ca5d2e8308d62a4d761ff257a5119a63a485624ca89162a01abf7f0227d8ca07dac6e54fc06b236e672345f07a03828daf27705a77964ba15db181912376b9765ddf9a2a0f62ec03df15083d9931fed94972de4bbaf6ca45018aaeb49502ae6ab41461c6b50e512be7bb36d590068a55045c1753e0c41d92d11e096329a5642b273ea91f7042794928ba2d92398d9365aca43b40c99ce0b2868983fdbb5e3e4091bc516848fb6fa85f6f2700a287d93874069b8600d101529681fa52890e537f331a79ac32b1930a41f18503666875a87be7b3ffb93266f78c182cebde965b2b114b67b21ee8bfdc3486d1cf380d17c52788ddb06dce4c298834dd14a032f791edf2b096f83022fc0d5420b69ebb9d739239f424090556cc8139082419ac90fc030f91dcfef2df8cefae62c01c9631f1a7706681e1139c7d25a2e6541cad2507ceb3c088487e8de6a0d60124f07f32d518c14e9f35c322793b89c74d60b6ac7988061044eff19ab982d0070f5134d2e1d5176be0e189470c26bb94e465eaa37f5564fdd6f2b75fa8449b964adbf95efda31a0786ddbb54e6337fe6a6a0a8cd8b5ad81cdc83b9e42899eda552da5c0fbe43b7bd40430ca4db196904acd59fc9dd6d2d9a1fcd896416c28677ba6b741f49e36cce89abb946c8b658402d3e71673f0deda41bc7fb6b6487bf446e944f04d7cdd1fe35d8c11f1136e2238274e338fdb982477072d596b83f0a4c56bc1a294ab6c403d4e2c93beb83bb2afa51d50f603704486579fca1a56c6823972cf7886c78bd09b99251d1dfe0684bbc1415c309ff09a348ea73477cd24f1fd2a87a78f2cb1003fde4e3b9786600eb4976e066c3f6418e25dc6ee42f9e587de0d233524e872570d07162edfa3770a0049c1c792098b8807e3f23b04569b2e5383648e10fb93b0c8d790e1a0b5bd85a7ac29d15fc5d77ed867f1310cc37fe6e37ca687a518c88dd5535345fb8b4719f20719615a0db182cb74f9576b3b8f1f8a439c04172a293f0c4eb34759cf7cb302e6ad0ad0527e05891b90c3b5122cf06957b37846e12038225f01bf6dfbe94e31b675c36c9b0c65c9db294c0154cad324a36b72413ee68af50c5e8f7076af3628b2230fd72bf44a721bf4cf9cddf65c027d642a18db7dddc0dbf1212ee4327657149c8b0189933b3a83a5da495d9fd6cb0a1c2f4520419ba8a26d79528d715f10335a0d4101d4f6da509a3c2e3cd32c0efef852114644575a7ed150c66813c0b7511e9463182517aa608fbdcab48d152bb9ddcb0494171f5253c86f8ff85cb1c905e4780910708422172cacc0962edd2f9d7b08e56756ef9d0965228d5d7c128c278b456c403c5756467cf3764a5987e0fd0693cf5cc8e75bca9ad130782410cab4d51691bf04f3b4130cb514f4de8b4e1acfb85ef18c3e689b2bbc07cbdd64ccb88fab44ba4cb9d7d7312ae0d4ebf8015fa4f9842c83268f2444bac6cb057717048eda2907d68174555cfc1c5bdd6069ec460da69962490f44e20041f43c23ac04964388e08d1c2db030734c10e6e84b17ad86bda100f678967dc6a9c8523469f1f362c7c1def807f5914d1397b254a21ca9d98dab989fadad6fb853412dfc3f63168e9e253ba61b39b2e3ce0165c5fe2de1730d6028030b144c3b3db810f773ce4f98dcb3ee5084a62ad2d076a12515d790197eee0cb0dadcd55cad4614d7d13468234b78b4d29625faab6768dc806c139011752dd36d096c57a4d9e4086915bb9d019673286ae37b02506283f13f2e272a31dad8dfb9eec4e2b48541ab683441fd5854d498757c5e0b0d2b3e31dd9b58a129671243444a72bb75e92fe7f32779320b042e8e1a908fea768a3c292e09320b415865054d17da8168015f0721de685faae1564ee66900744c59ed23d2a839c6cbf582341f8eb41120661b5363188a5ee9431a8bc29caac697344b6abe260a5d304be621a9a00cf171446d6d1de576bbb97864c7792b05161c7c34f455397eba9c509cac949bf71399bdb9ea97fb38277204bd22964ed90a0c0612060c56f7106af32da9f387ffe107d9d099771e509554c1fde9c36a4b4ecd4358bcebae2e29ed99f953e910b5891b7269cc9067f2c06a842afd18d157f86eac1be24a29c9c436ded017fa41a3a4adf75d5bb1cd61b35b2545070086dfb96b246bee7424b3323f379e3e398a527611ea627fa2423ee559034f58e01c67c1573971e42675fb637bbd0f9490d8d8b0a05275651c73d0f6ef35656b58fcfc8a09ba244e2a820769e1316ef82788093076d1e342e595f2b663217e4d2f3260cc6b1c74d844440bba0e4feb2798c03a0a0d045af4a16f65038739c54ee840df2fb9561d32cc8ff709213191d6631e313b95fff0de876c058e23f9d1eba5ffc141640f445c520680c96e8bf7431062921c5a5eb5f402efa7db7efb7998497e7c0caabbc6bfa51e7311ef277185fec4d8e91e0c8d8438e9c651be58019da1fdcbe97d1a046b9c0114488dd928618f71202672c33234fce51cb15f4a6b2733eedc8590044a72aa83fe8a3f79eb1e1bbbdbeb00bab7b2282e801957a693a4f4f0e5954cf88326ca59fb19c002656c12b5dd22e710cc14e72a947050735a09f168582d782d459c5424944948151263cf65b3ae5271bf377e11f235e82966524b1e9aeed22dc3ae3544ae660aae370792182f07d7767b4cc861193b79adf7c8716ae767b1760f958be5290b6fd1b48b0a12bc79be3a6e2330038bf7268d478f83cfefa3baef16f279a6717e616d586e693846fc49462ee5936632e0aa65b05834ea6fa78e6d4a9384bb905a1fe1b5d4fb6788a109113d9be875cdb7e33350e15d038d44b485c55f637f4cf50b8b6ac9769e400d78ff8f8a3ae6c7eba2a4be0763fc70e6350c6a1a2a64a18fb9c64cdfbd52428255502e0858de1a04ee3297136e9c5e588b50cda1cdac118452315f3282082e86b0c4be17073913718bb8346dd8756a47e59845d9901718d53a7b58ded58f442d2a6f1fb1d35b07fdb0cc2cddc5f0e64eebfa18b0b5e6ce5fe687cf1f030bf7c5146dd74c83b1ab7e104cf47ec0bf01947660ca532a778c02772a1cf4d23827d2b9dd3c79e0b9409d0d91cadfaca5056eca7204ef42ed44eb1b1e9443d81d17aee5f0fcc95723961d6d5f6389e86a1ca0b1ee7a30f463e39954207a5049280d2beffa264e30192325a59caeb9573eae34741cc635bdd2322b11cf113ad00461732dfa3458d5bf81200a66f9f79779c844e20845b8a8b7b67a0e64edafa20858c3567d3fe433a7954287fee03240d983a7cb93b46c5fc80ea253a622866a994c01d4bdfc3e235e551392b064b4a434e40d45950006c38b448ab40ae852f9b52c45604235dec6145f6a66bcd709c71396c903a196258591d6cd9bcb7b7bf70b0240106b47b5949f02490c1450002bbf9e312d0fcda6ce5b27e8a6a82192700054155ccf1a118a0d84c91603ba9075492bc4d380275e43651228582481a1a7ea22c35f20be435a52b664b1bbfaa8bbc86861e077f48c84fc8df7b8eee2f522c5b0e756df61474d3e13a95c6fc96e92aaf5da158307abecace3ca29e5b7066b2db049290491380a641f3bf5a87f12ac448a27990676571ffc4a4d4d1446a7bc8f25f5c5bd609ddd79ac0120dfd0520f4f3382e28dfeab834166f4bb27af544399c117df06450565394917c364a517a7a10c310d18f3bf7ebec3605b704ca42242be2532b00166d6a3534d74dbf16085d493c6852eff9a4b002f6098923ea8707857e1f08087b0e69cab39090569ab59df8e5f1035e4e4d21673831592b446a05eba947988535ba06b3ba45bccd54845d26d36a4a8acf5b6782e12a55d90e55fede0fe5f443b57e811114263c668e91806da972a6f6d3b8e2c66237699a1c5a1ca4396a91b1531d458adee2592247d21fa938709647b1b428d7ec9e783b807ae1f835a8327aefc9f4b359364f9d6fcbcb1c4870c0034c53b7dbd09452e449bd224f1854843f7d54000a7d288613225a6c66381050261fa72a2f9d7912240201ac735ebf77e5210e5befb703ca4a6300d7c0424639ed8d09e06c41ea3a21a87b3950392a2f783faac210726220b492b38f90b45b46e671223379a6e606cbe98bf66488c5223c665151290838b8850524ef971ab8d918c70948a62be09aadea955b105ae642224c6762c520cfc44a86db2908536e7ac4782fd7d2f4c4a151aee54982a1b4485cf6b84f88ccc5ad420dba073aa574da294dcc89232b951f5cbcd89abb9eee2a80c5a3ed2114c21e24537f47c7e52b0555d7db24b300e5acb4375ed269673149d42a5e1e64bd778792ebe96445ed33c946f00b0b1182dfe1aa888605d893c398d48892d0fe6016fa3de9665a7422503d10995676f1b4991112141d832422f23ce245c08198b451c7bf9d64b9989e4b5575dc3f70d5b95746a1b8fb26c7ca9490670908d724c3708996af3b9d664b044f7e4950210a2896d13e54a8c1f7d0e6e72c37b53195b11f30d1a620462c02c65470738455bddff7ffb1cd289338ee9cf3bffd169a7650f9ee481544605c7ce19063a87777f180eb77f908f230738a822bfc675d2a4ec8efe0d24fe7fb14a0ea168af7e41bdc822cd1cb72f73e212f3f3d8cffa62197a2795d2857d0aad8af5771079c68935f2727d1930466652cce0dd947aac90e430b2a3bfcce0326298cd480b9a03f2f721eeb4506f5df67403669ae6c7688576cb9dc4aaa0f7f05a11db6c48af522f74a69b506f740c4ff115f2cea0c5d2a93d8d591175445daab3c24e17e0da5b6dda585683494afb3c7423874a235a89de6ea4dacc0a3c48d6e06d83687faff2fcea016a2822cc25d578c75ac9f9b68ebb531307f8ba97f3645361c921369f25656f9b38e547bae0685105b34d0d0e8cd3879973c2ee6f1d99e63a669534d9fa7baadbc97ff89fa9f161ae23dab1cf8e388d355433c816fd73404dcc6901c10c6cef13b9b5bd7fbd03dfab0c3100e6e3dd88187d78b3922eaa803e0ecbe3e6c374cb85dac51f0233f075337912e82e1f884e4018ae22005352d4d3d159d81bd40a5ae914f07f26926a2af720f373be19c36239f2e9b50cbda9dfc7533208a04eae2cc68431bfa0418a0b976d3a204f71c2820c4ce2db4dfa5f66d2e966114c59d143841bc7e82ce9a2347ba002a6d5777fa5f8588185503bd70362e15a845d103230c240cddcb4d0edada2be960d095aff43f20d68f34271cc69cb4d40260c368066823d03b837e0f44c57a602ca1b70703dfdc05bf56f0ef9acd46bad10b58aaae821f91b114da682e1ad3556f4c28875c2e5a3bee0378c12624825c4cc305d20b162ccf76502e3450747f0795210099606813a90863cea75133814bae7767698a76c016faa3ab21b61c5498633d55466f2d990760c00fe48e7d64417e3a431358598fc88f0b78fa200165b40e5ebd40e35decaee6fc696ba546c929c72813384f43923a4138742a443c24c5dbaaf6184b6badb6dd32450eb05ae99032bc5b1fd2204ee4f1d149f836b084573b97cfbba9bdf45b42f81f25223bd427fa528e3718e6baca056bc083b6f89994c838f910eb9b2ef60e2c3f59f9f05b1c6442df0844cb906aa7aff0291659954c1dd510d9dc99bcb9576e21e297124d771adeab31478adee2e34f27e5352d8a43e8499e2f3b07c51babeb5ae7491b4183a66bd84a1b6b4d124e87456ea612a4da147ad49b5fde21ac34ce49c8ac1e364fccf0bb099a75af914e1f60166008fdef134e3999488461f9bb227825df4ad75ca08ec27ed200cd0419131ba6335bbeb9bd623b4d9ee96dcb4126f427e8a458567b7d36f13487dc140bb2587fca48162adc67cd0769bcc09d309aa6d09b23650adcfe97652e00d1e24f1d8d2105e0523ce3deba68e3eab113c5aa633637cf7b1ee413dc288de57f0588ed27cf8ba0732e5a90bcfef8419f1acf73f6318f7610ab07902dddbd5bf84a4068f5249e6d5f6fb9fce44c661759ea3ccc76c031e814453d05cb71461b84aeb028b6143ea3e0dd4231e614650d452a423b5a601db29685d05370e369f4939aaef07c8c1f73f17a51b5e82296cd0299904a098a7c97025eee55d14b29519e75f12f5dc09f4baf55bb8e58c4e061c80dcee40b8cbdcb662a596e9daea7bb60c8ac2a08053ea37f2584c6a1ef5e2e02133d8abe25848e94e61af5381192c627f54a121934a703ca941684582056b24a07f26597e77e2fbd1946fd6760593e9d316307dd13c1cc5cd5030680fd9fbbe537813459e0f28c39d16d1f823a43ee45b6a9ce15d5c0480fb04a5f9178aeb1adec2563e0008b56d2882464efbdb7945bca94640a5b08c60743081ce4f6734b7e06e248c2862e84f373dbd21591dbec59f971fca3e6421e61422e452bdd18390e23bba217535a0907ee2d6e8112ba4e646253fc3b3ed9a64c791225174c1d9138454e59698ea223ae742e713764a6302ea9b9f1a8874b32b211cb9187e32db9f52475d1e32ff6f1e12e1ab1cb3f1d2c6b05298505a41591c202ca5e7e2133b9118c9175771e7e88a6cb11e7684493021bc62746401af40634583445baccf58f5a6a8818298b28ee91cb754cce90c4cbb821af31847361c368148d5ac8cb183e2c09643e3afed286a28ff69b0d3c1ae25127c05ffb0858edab6133faed63a282cff63dfda33de649d93e123009f2d15e7a524640030e358c407b157c827cb4eff12d763ea48c407b29db47821afa4be0c3dd8ff6d1107dbc411c7f09619deab2b1cdd67991b91c5efbacd142b14bc7a13a2939be3d1c1e7bebf4598eff7bc45f375eba14563e51617fc3a38149f439c206907fe3dba321faf41f21295d59fdfca8e58b928ba568f4c5a42f1e6151f2d45ccd7c03b13457ab29af944fa43c41698d2bbfe5af66e6929116a52ba59371e5473594ba7858572f041bc6a335a6b02c1fc6a75fcaaf76257db61928bffa9939e77cb2f20060836d8761793a9ebb16747c7b355ef4d1e1235e2935afc36379dad778ec53738477a6cc873a2e657b2b106ff968ec816032cf1e83fdcc175aec6bea65bc50478c97144384f1629037642615f6a9172f05ca97f259be7c50e25c2925273f8787c36b216fa1bc76d2445de3f5136fc9674d91bf153971e5ef68b234f453895d20d05f03096e3c0ed1e7c627a58c5db279e5c735983a87bbf27b4a3bd12f0008f5122d574a19a5a419364390cef1252197622e3197984bac8bcc6426a5c8921ca9df94321da386e79814cbe15c5bf7896126a0946294821527d6ba5a514a674b4ab9c8cc94524add23bf5ff756c3a5cce73c654f2ffce60c1bf39f93e38db367bd724e2b95e0eeee320773777777c7648c4ba639991963c6d8c3306c323327ed650c0929e7a44fe56cda22e5e7be45d62cab5ce4dc82512e1bc7338b8f25e900c93927bb2acd2a9d53c75f2f74ab29c0376d01be2264bac7cde71727e63b1a0431df3cbbaa7508114782b5f94a31ba84ad52003fd8d465242cb818c3dacb485850017e81050d70c02209ab821dfac1b7018b28f216635800d104c3304c054b2ea840cb009ec06207af08e78a0757b2ca6abff21855bca06b34acd8c1698d1b97a051458aada2835395266b650bee06537448582cc9ace4dcb0e6aa9e7170552a555293ab4ada82b7204aa56a6eea6d6eea53a92b7eb8a92bb0dcd40d82e801a8e57e0123515f918070516fad407151cf483c705284aa904191aae474485600a1564a6b2e0dc0a5b40a1f5c6a45511534e01e84418333785812f2c1aa38d21281d0c419480c61791c39515153ac649538f2e5eae80834260c1555ee2cc09d54a471670db09ce69c734e71842b6bffc089271ac663871459dcf9727e8d72e7dbd51457eefc0771a494524a1b3421a59472dae008cf0656a42062fe9cd3872f9cd8d003305a70848b31e79c938389020c1c512461f5824d8b5a6b553d11860f9688a2972788d0030a0a96d4134d442726cb830f9d1051504634e1a29c9218628342d4219d407b22c479e1244b41940d0a317aa84318695014e107fa6429b341070c668582a21ac50e3c4c178448a25140c941c2ac86bcc5350a24807061082d5140e1c4851428457185095c2062298a1cb8f0c4c82ba2310304cfb03097ab141145006a14e1c40e4554013a29420758952280e0a40726b4748b992a135dacd0ca8526869e0041145104af6856b1b10d10b12b82cd0013e19db8a2497d31c41bc6a42c66d56312bb20c03e31bfc5206ef99be0faf340c4bf566bc31587ffba212f6d552c0c6dc41bbb1e3f319f51c1b9acdb4b09e2e8a834f6d3b3bfb8b69f2aa5a1bef03b893e33fd98e4d2db3310fcae54e6d3a25beeed1ef81e84f8e27acc47c45b3ee3840d75e8e09d7ea2973e19b1c5dfee1a18c6d3088ecb1db8ae354ad17ee35a6b0ff6b1ead724d8ad0df382284563217e7dea15c9324ae9ccbe30d2c7aa473f529b6ed9e37b5c62536c395060b19f397eb0fcac060cc0a0728512582831f9c497b10138f2ebc962fd5b7d5f911217507ef09dd8b7d773a77f8fdb2db8ee188350843aa5ff832ffdb02f94f383b0aaed60bfb9234e82d54b3df6d9e2a89b882f1a88a3f3d957d4a9e3e637bfe370b4f9126befda59c72cd6e834de19b99b2f1fabe37f968b3407da4d2ccc6524a3265a2db0db6524a3237880646485910e6a58ae09891a24a108260840440d8a3080218bb5d16a52032d60036ab0342f23f5604526c5d32a0c16e2ad9dc9c308e01300b0e38fc9bef10a41ba57b4303100f35e6830e29c80009efbb12b7edff82c9b8bbf79949977ce9106e30bcd205906c18285b1f4910e8e4dc3505046df12843484b0d20eb604218d20323a16383b7078ecc03140eb1aa051a8020b50d244af95ab594dd178de110eb361aad1b204be846c327a1a42b00ca94228c01d19315b380de756ab93d0a3b16d31cae8b1b52933efda36d4dddd1b7377d7399239b20747c75fd8ecee7677f7d8e5deb1bbb7d34c0d5863c0c72efe2ea6408169107b49b1ae07f65e986d15fb9ec6b288bd0cd2458be130c7d2c9132a08bbf87980dc18262d8679956a84cc74b78de3ce89329f64661bba7f5766cccc9699ebe4103ca1583ff5a6645469c1a114aa4255e8052e7e55a494b40dda066dc3c6c7e8d3261b9f37ea853acc1fdfa56b524a29a594d99c2fa59452babb3b7f3dd2203fe52ed863d8d4a2820df2cca47429a5942ea59492c1adde60e3b39452b2942e8fe0a72da69c5272ec32659729bbc8c984c904c90425cc39e53bc628bb4da13ad8f82e6990a997ecc8c86808a329514d4d72367199b2a949ce262e534e2967939499724a2e7236bd787c9fcc2e19e717b99c3c2544e614fb28eb9cdfe3d393b3004e6cbc731ac166e34dfbacca4c6699967dd56483dbf7f4f6d17e9edf13fc8d7a717e766adadcb6d7e6c7e9fde0bb3df5f8c6d82c03468d0d573d9be296ffc74e0c7bbb1f7fe727d303ebcf752bf2965d8fbdf97d27869d9f7db6413965fd2f321fa6d3b25ff96fb3004e3c09bddac70273d3cdb0d9036773c28a0f4337895fcb567e58c18dac0323a594527ea1119c989898ee7e791a2ffcf9617eb5aa55af52a9bed008cecf4f4c4c0ceb5931ac2fe6865dad98c8bc3c8dca0b8de0e0dca059794c66647e62626038bbe2602a0eb7aa41b5adf2d26112192691292dd5b5bf2249e4f5b76ebdf0486b1d0a853a9d78e774ea3adee93a7669cff18ee4b4df78476e9ba6f18ea66519bbb4fad2abecd29ef2cee9b59fbcc33ba9d75ef24ecef61ec63c18df68df9ff23076f59f3cca3c926ffab5774fb24b7be94d76699f3ac9937fca637679cbe7387bfacedf7e117f2939a97a4ec5a99ef30fc9e993d244f5aa6fe6552a99998ff9f92b7fc1fcacdf9e6b2108baf6b9af5e9124f2da47755f68bf7e6ec69389f12c8cb73a72398ee338958ae35e5eec498a8d6deab680edd95b42a53e0eef43c5e3e4ec943f9fb9c8a30c9b7d6cba217d4d03927d614552e443fa49d9a40cbdcff695808a7c2acdaa57de8af960ed7d8006dd47639b5f04491874432925a0529fcab645ee7ab40ffaee7fd2b447c27d32d05cfb1aa7d99e6e6603c8741880dbf27b1c8dc3636e75fbf3553d9fbea755a7af764e11ecb3eeadfdf6c278bbdf5e3e36ddd46f1b6fcdc9ab39e7f9957d65fd4217279f7947fbd3b7abbf507b1b54a47941454d57e39a93af79bd3d12fae10cb79381e6729f7aee1e502f12fa2128a27df745b4efad3f24f44329f39bfc90d0f65cd62bf3fdf621e13e294d645ee6e96f350e78ee7e707c4fcb78d55b13c717840da07a99afab42ca7ccdcfaffeaac121f3f337d5b7b7a341557fdfa00ae6653c1c3532ab97f998d43ccd3391f91baa9f7926aabff1d65f333fbfc6b3dc52fdca5f377ebe8ca7fa1e87f1c2005cd557a47bbea87ff14223ac74431074bbdf842eb32bf585ddd7bf3d5feb6dcfbae1d1784c561f0dda0a8c98811041508183283e3cf3d9adfe0d1e68d8fadb56fd45c3126ea3c947e66f883e43d48749908fcca67dbff4b46f85fafd80faf2bd5b81ded63efec9930dbe62bee3623cebad292718563e8b85af97952ee60019e2cd3ed60af5d23742af0c7eabd2152e98ecfcdeb6ea890436f617951824109b7cba3de6965797df0af5fac73e415d82d82d3ce5d842561ffbfa19e6dc7caed7bbc9427dac3275006d6a6aea72b125d46fc1a873642b5df9d987f5a994a1f7d1be12b4920f957eacf3d15fdb9b9f451d77730ef3fafb01fd45b0fa2d18e9b863a0e2df6c1d4485c7d073e707f57cf71c9fe3f6e750419eece9f69f7430337318743d36ddeed9f568f693b3fcf9b2b080db31b05658c0ed5f0037dd3032bbead1c468cda6c635d87111d6294fb27315e8b8f543a2e3d69f3eec4512996e8d4b1c79e29491a79550ac6ea1fc0ef2e137ec5898efdc39a0629f8fed6bc7c2fc2abd96e26e5e91ecf96a5e91ec63617ddd0b5d855b3f242adc1a65bb3c85a0af0cd87d8a7d2cd82eb6f86c5314b1b4165d6a72f02407368842ce03d2164b785ec7288f36c6529c174497df89cbafbafc85245801d9380496b1ec807451dc391e6ebf6d918b6ddcf95d3ba7b2e06d08ddb0044aee6cbadb77b92f8caf6ddf378c9fbd772c047953d3edf19edee16a1e17a1cfb77a45e8a3629016fb045b3fdbcc96ed2dd333bd6a55c3744cdb4ef54bc746f5a9bba6f9d67a6bae6bd3c63a76bcf1fd95c586a561fec81ff957917f26c67ce10ae60b2dbfea88e872eab35fb862b141842516a13e2e62ed86c74ad1ebc7589c915fc6d58ddc754f0bd51f6859776466e62fbef65d2337333773a41f63a4941e493243e7546c6a571ac4d260fc8c1081e185af71aea051609b99397baf34f8168b62ccb237d2491dbb3b7667f1e5b6fdb66ddb16330f89c77844e784418f02db4410613771fd91ba09de6116e294ed88066583ee99d751fa8829ae431147252d0baef30be79c7372546c3f7fa1ac517af4b420ba31f7eb12b26460e33b958d19bb825d91f259baf7732fb179bb7bccbbbbe3f01a77f71befac777799777787797777777777777777f71fe2eeeeee38eeeed57d453383a326d3b04a3794e54e5d4a05f3728325a3847583663523a324060c18d58b92ee74ea524a2cead4714a68ad7453a26595e6f84e89ccd1a111d939e4155b70f4cc112d34976a15ba3e31724c3774a62e10601fd7c23e397450d9857dcbef3a439d4bdd85bbfb7b8f9618f6ad79365b11b91ec52676e4e3dec2cf7077af2291d8f86347b99b73dd845fb6871bb95c356a3e8c6b5cd6cfcbc894f8f2acfad4c7eae476dc5ba6d6128bbe88568b78426dcfcf21022e53c746452ef126bef64cde8a356b27f1e6f9c75f9e14562391c7cd8845456e9dc8976826262277125b49a9a8b97051f21797c8d35ac49bf85a70616a2791a7978837d1ad75a2d84eb468a9f9c215eb0bbfc64931ed84da3acebea852b3fb81dd1b344966acbea4994fe67333623a1b7c4b1a75094530315b542fee728975a225525cc7c4c4e44e2ceaa4c4a553e294b86c182dd2a2a8480bed732dcec4a5c1a8466f914b6e8cbec68d436057fcb05d0bbba2141cdfd960f943d7d2ccccccfdee9fbb96226c8cfc6125aae9192628fe4a713aa7c38814bbfe1b31c8db3caefcdd3d41b1f4b18fcffda6715bf65aad5ddb3bebd495479388669986f5b66958779fcd8dfbc2eefbe91cbed147c3beb0d2d45b54ca9e1e75d2ba8ab179f577e93289bd4831c62aabd58a777a48b768582b76d9558d0d965ab1974fc32f03649ad6f508b2e21dd495cf383f1f9006654c0f36642c38b458f98eb1a0c4963ddbb24cc5230a214123a5947ed635f6fe713e730dd2efe94a64bdf31174315a44f7a675cbe03124b1ec924f3d1aafa65bf235cfa65bf2376fff56ec929b7b2cff90afd86028aba1c807fdc21a8a2ef5519ffea84f7fe3066b8cb49212b726c1aefab1eafb576badf463cb4ddb5b6bc79ae6f0dad12d6eb5f8c64fa0975ff4f945bf1dddf2c7be70bb61bdf38b4166a928bd1d382028b9e8fdfd2bedea9d8d4de6784dc5309a2465e87d28f6a5d0a04c815d724a4c0576c95700dfc8df648f14748c6cc8615c39a473c2958e01704de45100dfe4e01d156e5060f0a5c32ef9f3637c92d1afaf7d3603a25f310cab7269cae0d421a961c69578d4f9e062d1d6f9d8ae4f28ba3f82f5892311b5eb9e4f1fa134189f28b1db0c341c894e78a76ff7f18412b2dda35efb6c72bc63bbbc46c0adce7e339c86387d7dfab1566befe9749ac15f40521f0ca8cf4883324a2cf758f730af7ad99dbeb3dffd0cfe02bdd57d4d755b87717d7aa95e4e24e097eaebbe1b724e9f0eb77b1450b7bae77ff18274ab63c1ef0a7cd371dc6f282f88f5f8caedfef49da058ec27e8af15b8d53d19b7ebba0f756ef7ec5de7eabea79fd8fef89143eef6e985ffe40569b0fb976e736ee66562663e2b1353ebdfeebbfb5902d3bdeafbf156f7b103d260f72727acfcd87d4c2452fda97b98d3cbab4e1f0bc54259d4dbd3a34ef6843afdca5bdd9f76b0a7eec3d5ed7e86ced1713becbbeed9a6fb8ac4a2d9a726b82d08cb0daa61c615ad8b51b4eb9f7591c84776fd6b17857cd4eb12099273b7813962a3bf69596c162bef64cca33d6fd66b40b7e847af278506290fbb280a7c43a906d84561f016cd00dfd06ae90b7449763d2aa5f4638c315a22fea2f1894486b0f6535f14fa58402fac98b7cf3e2f1fe423559f7ad5c7024ad1c09280ea6154413ed9cbcbfc4b4d7d2f1f7b2b4e919bac1cc795b7e8c378ff42e7d00869907e7b400d6adf4f8354c6041de01d19ec0698c79f3e911778277bfa30748ea66d377adacb08d9909788f00eeae96780794e4f5f039cd33dfd0e700ef7f48b70cef6f441c02f1d6fd19fd1e1ccaefa272ff24dfdce7306eb731e65b0fee66d0cd6d7f1178a932e7df6fada47fbeb117dbff42fdfd0fae9b08bfe098adddc7ba1418abaf523c22e4a9ffd6fb59f7ad587e32dfa2f40f653f56365f5b38f95696fa495b4ac6a35ab0f7a8bfe69079b42e760b9d90734a5c1faf1eb740e4d143af97067add507ea9e7cb0b171b0f10ec7925f5cb71ca65b1e39de9827e31b7faec1d6b2d8adb9ca2ed724d75ff4954817684a833cc5c636dd200873a3f6608b5f37cb7ad8254176a11a1004e3c0cee96f4757ae87777a08107f116100ef58805d3c14e01bf9d2524ecb62b51b72d6f9f00360b1f236734efdf60360b1f5fde7e9bbed39a0d367bb49e9c7a2d91bd9329a51a6239dd3577e922c56de21424e4416fb285bf7eb9efbd67efbee7b0e48fbad3f963b7d23ade44ddb9b8f72ecc43a23421ab1c929de6ae951640fb51091d785c9859c49cb1253176ff563515af2566b51aa4a995151d2512735166ff5917614c588e3a4d745de92d2eb28a827442dd44344714888a94b149ae1486bf196901f18881c252d49e9e9e000e9a95bcc13194e7a9168860501f601bde5ffdef228e4adf68e784b7a44bc253d21de92de4f0d273d203838e9f5e4e0a4a7a383931e8eb72427bdff38e9adbc253d6bc349af7a4b7a2c80959dc14a8f0501f6a9de72d90918f216c8498f05243f18bcd51fe4b371ebb4590103e946302e237df1837cc55c19666e7c18dee98f30ec8a32d4db57cb627d75e58d4fead7803d7d09843dfd3e1616560d45379c6fa495e6d785d2f53fed6081a68438389d1373c4079106fd67f7d3e0ec6910c7877f83cd4bb6f0c6652d872318bff0bbf1f9dbbfb0ef3718afea200aa101668c31c6c8aef81e5bf16f7c8c4a4a428ef4e0064764ecd84c8012853ef56cda61981c815235246717135309619c77c5894f66b0f20810b656cac4c474e4c81115143664a623a4e61df9354b42a6c1e460436682b2bd98c15c8cf47041709d92ee74c56e27948a092818daaa6cb02133f15175e94920423a8bd44b1362528aeb1893224f04d231e945b506962337f222214474705216757a51ca89b261d5ae62d738fdc2f9be756c5ec13d874d32738c07f0c2c2740eb78a1dc6b0c63f295629b4610392152cb0e189248c80c40dabd665a4329eb8a7cb483cc8610d185bd1e83064b208c21658ace08c2cbef0342a30450926306083871900c194c4134af8b0050d2c43284388223ce1c5510f16a5140013765eb69203130428b2f5b2951c90a04866108159c961e86e97ad3461a3d7702d6104932266a0841437306a1fb41449741835a805549a8cb6a8428910d478c2a70a2a866842858c13302142900c94a0820aa32fcc6062061a407409c3084e4c21c4141d7079820344d0c0062e6010c590920fc188043734f1448726286109679cc14df082cb900cb890b16404dfe25a18074c8c8104222ba46c9101ced72c965042b542c4e4486594016385480757bb6c85080b256af31bb513467d74e5092b188a201714c1a8054760b1c610429e250442f0392aa3890a9a60804614a38612436432a4022f24242424b4c589932daf4587036891afc396e82de4d19b9c6c1c5fd873830d13bfc8c3374c126f94427fbb51250ebd0f76e304966f84e996cb1b3e08aff2eb7e649c445edf912de994dd4a3d61e3cf2fda25fcf894131b8db055ece5639827035d210addfeab55e6d56fbb014e56b5c7b4ac22f19034d03863256cf62b599379d4c3784828babbbdddbbbbe79c18f6cec4564694324eb0c619647421c50bc808828c1b968ca0ba8cb434c6cc65a4a5a4251a2c4959ea610c36c640c21867c85c461ae34ad218518cf182315430c6500e4968a265088b09c4c8811855c49881182e10e30731726023e632d20fb460f9c11596062eb8302c38228816fc608805b76b821117264b142c2bc0a2888b824205b7a302e5724610714f3f5c19228b10f7e53252184a9edc4e8d2b29c88282ab9d11c44d8101c44565f1c3f51b5cf1214b0f17ab82e8a68a78b8f40527b8d5881d6e0784934b2f23f960071dae652387db3135b95996a1bb8561828bf10087bbe180c99d575c5992e5869b4971c5862c4aee160329743b175c69cad2c69546b071ab1057d6c8d2846b2f23f5a0c915266459c2dddab8d2258b126e97c695247041421635ee16c6e730c3fa11ae5f4602030c30ae706a051ea51777f2004540480bbe04549f521a238db45aa01f20ef6d9a4cc005e6f1e71d5a7fae1a9c5f6570b0212bfdb4108109580003fcd2a94a3ab5cef97702fdb06b2ae18029ac341f5c71346b28c5408ad534987d2c1ae96fb4522c64d51f33d6844666c4e6a45fa433624f2316e7b4737e2c6c3ef6b1300c9b139bd954025cd5ac583435362be660cc38b2e2332a3a02408d0d8b66b5a259ad6c914dcdaf56384c4c61f31d0489c5528079f855bf21f4931f0e24f586b4101d6cb8ba14c0b1d915c22effd313db92597eac39b1297f4a39a5bf5c213e46619198babbab7588191860977c29dc775ab86f488331604f7f089117302f89bd1660579d31e98b47f128c3c17182e3afa885192bf94f6ad147b153e2508348d90c6c4c3aba72a59d81893c1790e11d0cb08b89020cde0879626bad4cb1c8a4e9c0daa0209ffea98951b3ac9ed0f276340fb0f22bc3aa579dc60acb3ef533d495f5ce2484628c1c86b591235e926e1561977c0ff08d740ebc61977c29db73de06ee68d088c300faeb87d6c73a1fddfd61970c13ab02bf56de927f02efcc977fc32f06230ff6f209b023f2642fbf00bc739379ae1df6d26f1579b0afc53749270800c3b00108615c6a0a51877fe8b44efc8142ff40c17da0905d9f73fa9c523be9d0d9d9c8ee0737d8fdf8f958f7c39b92310c8b18d6da09094a763f5cca1fddfdf0ee47164baf96c56675326d6cc3c1649bc7d91b564d567bad734194514a83505c4249c02ef923881995523fab948619581d1d22f3669d8feee228b294526efab1d7e3587772c262f3d3316fd8d42b6a566b96d55a9bb677cd6ad0715cd771b33d1edbaf6aa66d9f0e8ef6e9e0b827b3a4db9e28875d591b1362dbb8b94d8fe0abc5add9fecdbf524952cfa32e62b14b3e0ddf14619eac12bb5babf5c3191b1c21754bbe94ec352fab300d629c7bf18838c58b4b744b7e0b8cc06a8c825df2e3137ce351a278378c464652fb19fcc57df1894fbe51f7b410e9c355dcd0fa0d1e6558aee2b27bad350bc08d4bd5ca0b53f10bfb6cb8cda673e263fcc2dea625a57c7e9e99e117b317bf5bf2d3b055e9a773429d2b5f2fa49342e7c897cf2e798212df0ba5d58a5d2ef4db2b1f8877fcca1701bf70b8255fca9608e057c8d3b15f401d060dbf70bcc53b4da441f92f6ec97fe1c89ffad4bb55e7006159527ab9e1eaa66eb7425fec5babdd0a7db5f7a858edb3a206811a940f34a48508b04bbe8f1f2a4a1e15cb376a58b20d468679b19734d2ce86108d146157e5f100dfd49ae5f0e82e07a7c1fa59e61969100625360ee1a0842c8f06eb6799bcfd5e03d8557f93c9c1cae01749bdfda83fbd4f57bff690c2699dd6f568b2213788a37b97a74e2bc2afd3179d74ab7efd803882f635e1d6df28277383fd10e7662feeebc7a1531c7a20feb2df0a25843d7ddfea22a95d8f9f25a8afbb38d460fd9313566bc2adfa462c0961939e685eb5ded250d93357351dd8556313bea99cb7e4861208f970acec51570af771675f1c8252638dde12ee71781feea3a269bf753d32da0509ba5aac7555ad91f9b1b28f393ef9e2a2938e43cd796c0486ce89482292be8dfad3671f89bacf501fe8adfaa78cfb2efb581a4bd37ed3b22dd3b2f756cdbefa30b06ecdadb1e885093892b20f549f94b08dc958794cff64171bdb586b51d7b24beee8168f6ea598a7a5e29d137ce686f64acb3b280cfb0dbba134bf08f65549c106a5ddd1a0b49db303a6b31b3b20a306ab538e51c29678c7256c138505ddd3a3b03f7d2c20fb27d59f9e7dec07f954d4a7b0477d2c20d4a73ef5d150c30bea837ca27d98b7dfc37c8ca1de3eca4e89ac18a3576f4d4c27059dce99f1e8d3c6e8a96e276f47b7e6470f64d7041d4ba1c1d9e3adf9123b256193db61d32d8f6fe6a901dd9acf7f9a73887d0cf08e0cf602cce33f5fc810dea13fbb1e18a555767d213abcd3fd7c149887fbf93cac02338073b297101d1b73dee63983d86b1e6510fbccdb18c45e88bf5013fbfeaa576de67c9fdf29609f0ebba6ce9d18b6f2d6c4308c6215a3d869074b3f7c1bfa014d9961a60e8abc314ab13232d94894813b40558a6f60bac6dadbcf2cff7eb4f6406fe1780b356def4aaded1c2d126141bc96659a963d517dd243dc72a795ba67350832ab35cbaa740fc889069f68f5b34dab9fd1da5d295b966173238e1b639aa6a27ca06e7fab2817a7ad88b26939750bbaa7362ccca79efd25e3431f27a394f60edaa53c3ab90da6e63198ba55ced2e7b1e3045a6586629847867a67784786d5b5abae5d8f0dcbbebb8a79dbad35dda230dd52a5f8867efd2cbbe8cbd86035201de407e41dfbf45bcc7302e7dc20e0456774548b3a751c10c60246ad2bede8f73418843a901d0e82dd0ad86b0ff3aa1798afaa52ffb2bd9156da364e92b9f483d0d73c5ee2a688b2aac926d53e97a27dd65e0c49e4bffc9e35bc722fa2b6a8546d58743de910d100000020004315000020100c884302a1503420c8b2de1b14000e7398486654170aa491280762140541c6186000200018628c31c814155501adb64ad121e68267de05dc66414b0192b2c99904d78b62811c63441cefdd086fa45d23c4ea2dfddcce1af0e3e2b9ddea3d2e65bba93f07f1c59389cdc74e88304acf3892d9bd7ed743c5fea13601808dedfdb5ee3a724f371604e9c3c41cdd767795a0debb90827bb90a030e1cc1dfd6227666abe0f2476168b85d92c8105e546b2f625141bc561002aaaf419ad79bce52906ea36db067f465719d7696e225044a49a1944f2cdfe99b65d335ff143e31df13fd157505f6c8d55161dfe7a963941dcf6f1784367056b8ac3af7f5e79d56a67d2f31b99adab1b456bcbb4c903d55af6a1b08d975cc1839f970c4cb19b8540775c70c497c8614df17d3c1c6d92d3c4d6a393e768969844f72dd9378672fe1767d6bacf8ad3187b109d62936792a4714f90f447c46d1e2d714ab200cd582025ecea6c41c44b72991bca4827d5eec13d91c86d14333279be7f2495e5cfe611bd1395a911d89829d4138382b157cd759061644b49a4e62f79a67d563ed009d4439372611ab04b89027da8ae8fc1ff9e26894452a0dbbe52e816fa154a9015b680808e3231f1967445fa0b6cade0e21f5af7a2a63c6f04da34723774ec0661b00eae40fa42b09e00adc0c738033ff5aa9da41b66d1628c3473c368cb861967c284e6ed6536b5c83e56f0afa8ef4f7cddc1a78472bb7cafdc0cd1ea4be39889d426dfec678ad6e36fe4714fb0764e72713109a3682a42799ba8702b186ad25858f05f5a92721cae4265b49c34890713baeea52c19ed0afc09751e5e6ef9a776299d9a453d27c28b6d2355b92920b120a556c08a7573e95b04b50e1768a299818d10d1b0c6c9094a98d6413960a1f7d66434bbaebd38f3365d2a4e280a540453a1f4223920964ead75e64f80f9c31489363b51bec9705cc492215a5821e42ff28aa40794a95ec20868a655ee4de7e1ef6aa196f3308396955d43125fe9c24ee23ccea8dc5fb48a9b02aa67ae73fc284c2609569dd126f259e793a6f130c229054df76fe23a5da284618ad2a2dff2317080f55553d41afaeaa8f31e88530de31d809fa84afaa4060733b7d1bbf0812ed7d9a4f721a0b43615996bf1a0bcc6c2c9be85ea43187100ad1db7a5cd23cf6d3b7237ea15d8920f8b6e0e4e44545daeab9aaa6a6ed7c4376879ae63d2a103dd7c5635ee02e00a1ba53d33c3261721a8cbb66166c7dabed3e7250b2a2a6ef6d24b56610f6ce4092ec34e08ea0665aa101b9c74981c65fd001986baac1f9dbe5571fee8f6c82a53d14e384038a60c7307d62d8b99de4e9fd119b6f92533eb4f10b70844a04b294dccdb7e86271bc4248c5c9bdc46104c2ad0a53bc49089f8ed65274b951adbe23bca97a17d3c55b2a3bfe96cea8ff886e2bdee6f48d59ac02cb337148f8447e94a314117c400081ef43b57446fd8ef046e1073101741cf7fbaa0f1a426cf4d4f6d3ce32478e9123d9a85d65b97488fb2b10ff4fb208463a19fef524dc6d95a7f517e1247a350b3c63ca17cc9225685af1777215bb009538521557088e156155fde15749a2fb1fda51ca913828e380c705dd90b1e1dfc973a3131b1fb72a9273e09dd2385222a2e495d4d3a53572a27eaa1238cd2ff51cac5ffcc2c02dee07d88becb4ad30ba7c4c04939deefa2503d75407a767ec217e38393a0d2ecf49581f5c84d31163c62fd866c581093f60b2fae4815d615ccc74ee1cab7492e25400f5556ac244f267636c9c4c5a21172f42a8d6d8eec256c4133059d5c53e682798c6086212a1c7d55774db521a0de1cbf3f0492386efe7d1829b3be6d50b1989f68af2264c77d78fb6a38d6a9c27006900141c17f7879b60a74e64ee56fdce02cd8d5c25722d2792c9aa1029170c2cc372bced2357f48696905c5b53955c7a659029067ae3531fc7d7743cfc82d23c48c4f99892ea7d184ebd733a70c2ddc33752b2931059f67e41a0075b4a3d17bd2ff0d11279a121c0060dfc30b1a449f612e33d5811f7c63a03f81b633dad03f9c8c43144ba623cf0f60698c9ffc860781e4b6f04f7cdb33ef6831aa06f4f6824798609e339957358a4ed42f6d777928c205fa88959fe9dec7f8532db336203713f4f21d6f6122f9eb016717b46bb9d8860ae03f2d212f52006a300f31944876959f48a5ee16d91baab620d705407c31ceb3a6b42f00c255d2f3e828625d3895b49675f695e8b66fb98b5d05afd4ad7ffb9888231160f42447bf7eff30819e938f6a52fb9c04363bf97f4c5414a13b208bacdc17ead0456093b7fb2369a244f849654e9cfc340c471993d1e3ab3f4915eb311a22d0cece2fa919be8a2597a4427954bb9076a0ae69b0469c6f4ba1c2e318db7fe076d243429592c90a360e2dc889d999d7816b9a63b122e48508aa84dddbc4d8426aeeca2bee81e187d8e2ec0b9b82e9a2c9180968426a6bfdc06cee6d17abac1dde03a9d6af0e0874d72e753e993263b2599500d08d944ab5564051a10a0e536a7a935297c985d2d09317920b5ea574b1c6c8bec7e6d7fdb98a68581de0e38630036c1409bae8df108a9707885b5cf0841d69cd09abf9996b5e475633d494e1796c700dcbd74d64ce9220c66c32b3318a4b1d38094d572a390f65320c24d4888c4645a8f34dc7b6a50fee352779e8d90e9d5cdac20e0515835cb03a819cd61724debd0383ec328abe6512ca6ca1d684b5b019cce4d17ce46e18a5a67051498bf3c42745579427c8cbd19fca8535393bf3c0c30d98f04bb8a1c99dc07da1bd04d135b11285200767ed4373f87768a660c91e51fdb1a3351b43537b613c1fb9897b4daf87bf6fa10f414709dd2eb20176c1b52428f071c987b563edd726f7db9bee413acee6ab92da64de4e204dcc74dee9719701c95c6e4a7dcfd5af771184a209a9e80673226882b136a71e609ded70c7293ad46c8ff9c02da7c12cb3a4bc537060ac28867731a823c30cb62de804a186026e937c80e8d8880ba0be97ba24805564deccf2c3189b4708e543a4c873322aff11800904e9f33992cb4e7d1ab04dd12cd9fdbedcad5d25ba2a1880fd567ea77c9c6fb1f2bd2f8e183f6e3661f61095d5e898811624806b71b7404e5dc74b1721e353576697b85e24374b78019f0682b25c779f32b527ad3ab93c88f129b3e91629438eb288db08a33af8d489679378eec838c52b407733003a8d84b998d7aefb39b1c8ff18a370f083f6387460aa09c7450bc0f9ccffad59784491efb640622727de772c5c53b83bfeee59482dfd0fe0fbd8df278a45fe95bbd3494825c2edcab25571d2ff195b756811c65ebd0ee80dd600de274489d0478a5be74fd39a8710e7f8883209e926300270effe1fec05672780d41612633cb6420debb15c6145af01e1cf8c6086f233dff5925dc97cd2a89d2737e74b0da8c0486db67243089c587de1bee42ec1977d1d457f9b75e6d988f6918108c7903495e191c21938a52ca0c9a9206bc47c4eba798be72f9126bea561470f21dcf76cae091812455de57f9ae30636ed4780e78c80b3a0b51ac20e4ff375b4ccc4461e33a8f9757b5442becc0ae8e6b5b44f0baa99036063b7a01449fc12f37561d1b4abffaf89c7c334fdb47ec4c8583da031303907e0c0ebf2822dd5a76606d278a1290fb30e62c38198f6223336d8d446d4a84f0bbd96ea93fc0d692e291744ef4a5abe72ecf1c701ae35ea15f7ce98282df4617edc6792a00342b698b3cdc68cc68811bb8c9834dbe9e0e80d0cd163845b9deb2f766c63b035027ca22d3f1f1dcfadadb8f41baf53035988f851911e8388cdf2e14ecd1836bc77cb2d42ec19e2fb8b618f8c753a78ba803d726d770eaed0d2abec200ae69c04841c15acf60477b213035f72181ab0df77b1950886d7508753c4fdb715f84de16f81a75e871e4c8eef68aa3afcbca306f854f66fea2058670914c556f22e4a605436e3c7711a6f46d5689f0857025c46f40d7dcb1690d86eb0ed6ef3fbe5e0f2e675455277bfe33b09faea9cdbd144a8ecc79184ae29cf48108699f0b4546079e4ae536a8ba24518ebd7590fff3d5acab0ee3fd99e4785c854bbc5fe7da3f5e51385d2e4cc359dc6c2b5c6007cfbf530e4f6badedda96bac0a8dc5e65acc9060fcd1e884af99d102840b2889fc592780161a9fc48c9ec92d1135bebaeab1e128bb0e2f5a2754031f01f99813b925df2c51916e2e5916f54534ad849b34878346003e9e8ccf0862e789ad4622e0f787d25ddaa0d32b567dccc81cebf28d4d01ceb64e80ff753e06a17c926f695e315c8eeb067b0aac0b1c17e279a274126463c1405028fd83ae65490e83d07acb41b2bba5c77791d0e30cac1fce9593115b6c3ab017f630febcbdeb4ab1c8f6d07a11303b00a2588377ca35d5e3249d05b7df5b3a7f4e9480bca93a37c5f3fedb1c13033cc30977e65451a3fe467460782ea48f33c064d1c205bea5ffa8c8062a29a2e099410c688dfa9a8bc4f5a2351bf3bef231a131067799eb8701bc28dc269b4d1ed39974520347759ec502f5f64da015297d3d44a061e3980b1382529b252f07a8a5250cea15aa665fb39567afdb3d572caf7f4dcaf12c9544ae24cfd6e58c1665ac9e7e1c2ad8d443dbc5bb11c29cccaeee8ac42ead27cad770c705b238b0194f77d1224f4ff98a3f5be76679f43e25fa36fac564ba76f7031e90c2e784569c8108926ff075cb8cf2724ae60015922a80725d5ca5e1643a09baad146efb7067e9775a7280d3018a3669f97a2df154d33d91e2ff7915a4a843e9101efc20aaa1a6aea475f8218eb02e890ad1c1cb35d3128ec83d9fc7bd90fe5a986c6357f2f5c8ca05a42dbfa326ab7abd56140040ee4b61880aeb680b262f26d892d77bb8201cb17fd3ea5f8856001f3fbc730d37871d2ce69bb586c81023668967136e0855989f92d9fe7e00fc4115af2a153ff6fc50dda5ef69cb5ca4449242229cdd65bea42e6b98b1fe19b5fd97e2cca219806b17e256a29dd21abf2c5a85aceca816ff5a0c047b9a61ac37eafb0745fed28bddf11e121364a1cb3bbbc25dbde661380e0e1b560fe8ac69bab1dfe152fd34065008520c9b786783b260a71f5cc0c2e2ea9181258ac2ed1a304f87482e43e6bbe10be1f62f503d9bbd6555b413880ac444e5950c55824043303e24b9a14e88e01f039367aa38829dc22721d41e9087c55132be348e63d79760ed90aea7594db9092374ff8bdffc17dfe3f900f918b50be49a3baeca61c67aa5ee82a44e4a4b03eaa10d417c9352f79377dc556e52fdab60fb7b7f6633243665d9cbc2a5241b08105b2cb54003d221bae9b613dc03494c0766d500442ded0e59de875a6c587ee0a74f2aa900d62133981d7054a017527e1f0f2a06c24b0d09d98d9974d8d5509d26f0d3c5afae09ac6de5252536db18b420ccc31649a76401ba124e338af97d71bed34ac4c443d3a945ce163c4a5626061cdccea9b9bc5257d612abdf0c8eee23157458c013cb859286cad6913651d62ac3d81248287f84b32b7467f315b964d1334658a30c4f14179e5026ec2c30e555529436899953c9042b8a196a30443e8928434c31bb0efa6f29c2f8be7108532c9c2b2b46c512afe309045c5c359a7787ef4fdc5a47e10f4a8b7cb995cef769a4287ea4bcc0cbf885d16451964d9a120287346f660b332662cc7b009228c2e8ea9f60038f4fe8609930924b6b08bd84ec56ef9cfc564d605c0cc00e265e7cfe14cae009cb6834f16d93eccdeacdac6e7badadff84e6c80581f20f8e006e0920d64bed46d7fae47a365d89ad71888580282957c19cc55afc347edefecd2522ea26e9562f84e8a0efec7f898c7f05b82743f8330556be75c0a83a32f0a3606e18b3ff9f21b94ee15ae92f9909564b4a664fa72d8ae413223ee34f5d5b3235e659f265839a27af614ffa38a62d4b7908e526cd4e302122709f68f32f74c44a55ba2c2801b66cb199a2515823dcc7436d4ffd033d5e1fa0cf433b2288df9a147dc9467920a29652fcdb29c7d717dc576943ea90ae9b5a393d6b8586d1fc1477b670ff67fd09b5be4022b7abac859474bd8f8f3d572cabb9051ca547247b43489d339d63655bc6d135e4da61ebabc149f9e78da150e02c0babf5038f0984c13ac07120f20ee09c6ddd03f0793a6e622558c994e7350bb00e8c2b5289e6e87320ffcfb3a3651291984486dca9ec8c2026bfbcb4d17507c28e255828ff01373c36d96c97cedecc0f218558981e52cc73e11745d12c98d7911f1c725cce10339321080c68a01c3e2e169e025ac61e52c0dd4e40ed7c9ebe735ef9c229d8ea7602e75bb6746c2147b735a8f6dab5a5cce634f36a737250853531be9023fd4d6412b2ea58c16d055735928677d97ef181c51d170a405c87bdbd0303e102542a9d9cc2bcf71b7ad1510d99a6e46652c3b8814a911bd16ed4a7d54e68bf20e3139ada9727a4e9a9c38266f73f25e37616b53baa7d3700fa4b8be61852ea87ac2866267e524a5f6ee3bb7850cf10c827bafc0e323f1eb2172333df3fac7db189cf11534610db4d6878f9488975c1f807ca7f89490b55688ee625de50dd8f723baa37d137d2cb7308955aef02a312aab1c241566c843bf93971b4a0c204b0368881ca0f66b5b9315e03b1c21b1b9099cc267dbae4c880810c1b6b327dcf250596c7ee5b245b8f5413cc7ec90a515690fb5ed3d481a4ae6f921227349940c5eab5055d3ee976c2df0f28f139d11f79e009ac4356c279cf2cea91f97cb5259350bf1d01e415490060ea217f56038b5eb9720beac1f0a1580333275cc8321a48f3f56cf51a4cde698c6ee246ea9720e33741ce4896eea7e1064be8e5f1af395463b9f5f6c897e46a50beffc65d9de65f9a2089e92ab7563fe32447a76dfdc327447f73c7768904d29ea15a8fb875b887ea37cd48faedccec84b239330201baba091952963db90c6b647284ff145022c78827f368bcfa805afcbd21dad9bb5038dd18a525d890d66dadc241f6f5f9db575cb72d7aadf9226ab13f21ebb5db2531b4ecd5c5128f533a23b6a0f316bcb43b1c1c9cc7f9942b4ca940591cb9e30593dba7c433808c4f8be55c8ae40d9d65665e5e62aad402a16067347e61199a6ea79cc66469f814d222d5ff2bb7c9b8c57abe74cfc6b28ec2ff16e5f25f651950b8030be98a3471a2526937a7861824678cf2ce12598826f90418662e0bc08f16b98634d494152d84ac034164541dbe4fb9cadbe4fee6b7cc78068379dc47ea9c254c405103b6043ec3fead90926710f3308ff84f1136f67c325046fb54929860fef5f78814810def2b736ecf4905408dfa1edd3b5de549bf3f1a4386ed588914572373e39f97624c928c7d70ce2163b877e832e42e37d95662462f1a151f92456c6b1376575d94a073e27e88926e56164c9e080932201e22870aec4f0c82d17db623ebae21e5737c31a6b9d2e8f7100f88439071749e7fe15ae9019e9ae42a0f1fde6566a86c70c401fa9ff88bc6c56ce0d1291dd59ca6af157694989109ab1135f55a0bf4a48848a84f530fd807566601257b5c69e65a6a604c1629fd592047719a9ad3a62a03329ed2cc44008eded2255f5788fe8b5aaee082198d2b9a1adeabb68c2653b8f97e223ff0504209cfcc68b282f391956f3119447462097f8f0ba3f829baa8a2e753b3998e04b19eb69768d834492eb6ab304eebc55f81fbbcb106dc30f6c73a519118cc5c727402f623799cddace960f82405c571733987fd652151a3e2dc672a6c82c8cf00fb5b90d120ea5bc07bf90052ded24bc664dc2ab48b011b8342029f269b27c25ad618cacd1bedbb6b08d15389380628fcca383855077c9f05240b84a5fe97bb1953970691b0e8d2390119a1fb73ea4e2a7ee2fc69a47de3be80ed10cd7bfdbdb14595a4812c1b73cb5d5a5ba1c22541ff01ddcc21637b4c16039b5ded8fc5898f0fb2a61f8d2f8d4e7997da2c1a87d294411c4841c895063d1b18d2bc9b0dc7622c8eb38e4b6eaf2ef320c2212517cb0355b225730078d29efd51fa19501929e8404d46243906e2e2130820ee00bd1d849608dbca4c3a9e8af6c44e94aa48cf5654545e9ad7dec1694481feb72680fdbdbf95c7b5ada979c17bafb3627669d6b9fd619aabf60454228a10a99049276403b5ef266d8eb8c6d72d4ec508fa46480706267d42db00390a997cb11ce81dbfc0f933c6269fce244e40d07b56c8f0044ec12f8a0b04f841f5bd44e0d78ca9ee0f15fbfe1f46c4afc03960b83c37d2f39cb0d72e32b1f8f4bf484e0860a68f0d8b3c41ddf909ac4d5b3d7c4efc8b43203dcf66091b18d51d5571c1cc96ad2d143b0af68bfab3e5d4ee37684807983a730fc53308f6defe48bb53c323e088b6577647305e24fef966301e90a8069f6baaa0b8f8dfc62ca6e549205fa498f73f5a072a63fb44b9a65c2c9671d4e2449d69e6152e8a55413f93f1d45fbe3807240fa2aeb6f1dd3b012952a8cdea136be7f017723423763dd2e089475f1cc07836e2257cfe8d6d87ecb14bd347803ad37dc04bd4b2373e530869e3987d28e0b90da2b6e8d11e6cd13e2624a0435dc5918eed3963801269036d9865a6fcf642e7879d00c41e3acd8c1eb2aec691f6f09175953d90e4ea56c95e12cf98f2223f98da000b884322cb57388bde3d7d37f2fbdb1546cc2a629a8fb19cba1c6e8096f901986ef2c866c542a7d55a36a6bc989714cebf4e431d9d4e22ff30446d7fbc0309b5031b1c02f9d325d223f547373c4ca349e14e85d4a1b3088d1c495b2f512eb8cd15c2b4ea598736625782d600b2ae7656bd9342e3daa030e11f6b920538960f0433605e6d4c429c58e5b5ed7bfb49d1eeeb626c5adb2a3bd190add64b6f9dc3ae55e2967fb870b36e23b7c15a9ef35868843a7040a0d0fe2c8c274b532400074cd3e7589ca6efe4e1090bb5e41edef21799337a627531afbcdf552abeac42877bbb8dc9d4676f5168d0bfaad782f8cfd708ac2ead7ad138cb686279f1fdf58c2adee4230e4922ff48ab7bc062f5c6918f6a5c6de614613e9845d2f0077fa24f9d1216161a06adcaff4a07ec9fe0bf3a2c5fedf35b591b95d4d828f73c0e7f59cb747b99dd3433e5e7ae32e0ade2052abe4e2f48e1b05c76d2499cfcef01427b00d67698b8c733dc2d75cd9d3409a95111c306b748692c9fc74350d6799be551adaa09f1ad6dcda31f5e460f67e06d768e40982e7c212b64c78ce98542022fd9d591bd1baf2af3a3509b3535fe31ec1534b6aed8d163f98515298ab2701dc47f1f8e09304514bca92271f6bc8602f2344424b6b410bb69a5d25195bb6d069b038e72e7dffe79f1a0291759d5af51869fbba2e0cb6557f1cfae22d5c38aed8f33da45e3d6bcd4fcd23789dd5f9f932c35b64723ef8a8190638c965735fcb400bbad5954a73a8c234c889bb84260ab13d138fc0263bedbbc479fc2d424ead3a735ec5b0801198ddb80f317dfbe84185ef5f797d5d72b4f71128e641cab789ee105cfb0926a4e1a3f9baaf81ef7204ac1ab00b46fbc9842e6bdd997b8a37df849ed4f629028c36dab6c4a0e27780273135f619abf468d8ea3a25814a8018c095d7dfc0a0172efb7c644cbbb14db5f8333d0a7bbd70a52ad448f0384cd77a3f94c31ec258a0ce37115eeaa6b62fed5c8123453809a1c9fc806db38a1541d04aecbd593c6b1e741868764c5a2be5aa9cced303dc9beeb68e7fc96a24806f4d2f280266d6441fc0224b8013194bd13672fc08ace2cb7e9d8980e600b48298cd5a82536396eeb9763cedf26a5ed9196bd43d8256c3b3137ce8115dc16aac3b3da26bc2ab9bda444d3e8dca5723d26fe0cfff259dfce3ed4e52f89d622c00da53bec616d0ebab2a57679a4a8b308dbaa51107220787df9fc594871e687d0bb91b84a09f0f852e130c5298ceeab1d2296b741008acc500d2860ce0915269fd47af8f2426765090f88f0aa002935b708a26a24d69c34cd679875cf46442ae65fb0b6b7e0c85f7784c7bb4f5a60ab94e4a36b8ec67b913e3d663357bc76370accd64011443fa9075a9ca02e057292f0212b3ca46c4623f62b16dae1310cca836227de1f3f0ba40ebb4eb0a4ab6602343222920b151e9c4011810a15a07891df8552bf72e6f46af2dfd45d79c959d1c436dc6b9462fb146636125915027582c380ebf73ff2b8b15c4b21f5b99c20f88ddb81a26f132129d80aa81fc55bf1f29a6f0e4e5c65bf4a1d4a0fa451f7ef43b950a357809a10c1ed1501fb92e447ab44f63d4280ad6a45825d233d36c5c670900d0800ae3e0a84454517d6bc1f0b675e01689ffc156ec75ddfda8eb165d41ec411210803abe492e5762f10b9a44d2105d304dbab22281535e5ff570c6d6c398506803328abbc52fbf5767e510813d60c0bd1d8880f6a88b0fe38c8435d6314374378d2ea937b345e32dec1a0cfb0c64638ff8ec2e832bc99eadfc949ec1a97b43bb51da987e301e73ffda8b2ecfb60ff0470255263069a9afefd7c75e269c8d53f0dd46d6ccbb46069ba65888e6badaac8978a31f6724276962195df087fc6bc8469a4ae1790419e2309b486b830f7fe6c287e366db1c398976a79c60c23b7c94ed66e92f54bb61a1a0ebf6a041d011c6cab0994fd80df84d6118e080f072715ee03402bdc9398f078c494144629728edfbc61e26d6c7beea2568c0fccba7712839197799683e359db3710b91ed5f2cbc9e562fc4e1a2d9bf82e69c065b3b29ac139f03ad191fabdb544755e606c59807b6ce4ad4051decf223d840e4f63b7d0a1a32a79de8f01280c02a9217855d88b7bfdcb0f8416432b3539da890547adf85fc5cd367d9db1189e6c79a1bddb73b701b47c89d971c8749d7ddfc708959b6991af53cf421c9d88a5ff1a8c041cc7e94b926f561a859dd417c8d7ce169ccd495447322793499b97668088d899696344ad964cd94c471f80f97ea5642249afbae784d8003a07ee8f588253271f1eed3bd520722eb4fa2eea55059ea65e34572c60fa52489a4e31c029a0f034a1d449eb87d3284abd77858bf7910ea8d0cc0b7827a976b3908cda0a84d632c7ee4a5397c9856ee019f8f93fc0dbf3f374b2434789796b181d99859d3e78045f0898bbabeac292c9a545a931df70e40d3cc108ae2ea732bde581ec67f91cd860cf94218ed63941b1bbbdc4fd443a5b62dee7ebbf1f00a35d1737488a096ca649d118a1b4d3b9498adf1a3d0a72afaa62821639eb7643eccb5934e8354cde6328608204703abd6c8d5c2d26e460381bfacfdea6ca2abd188b4f1a98a5e8431f228e4e82658cb6f2909c49ae72832e8ebd09c1c2a0a588bf490349614ba270fdb94388ce49c8ccca1e2b500804140f54409267342c105bea3c6c787eead27322e8ca09703b7413cdcf6296ffb438c6ae1a6c4620cefe2dd0b16dd6d017ddce8457134e2f89d58c06608bbe299be9dd91f77c028813b6cd53f6debf885c5d45685a42d035744bf77cb128c4b0fb85749000dd0ca4671b26a9648fd3c8fbfafc1bfab8be9ebaee200227bc4810dd6aacd9fb6609ca331e54b5303b58838f39784477c80e04aeb1a1fd63e7fa6568a434d675854cfc190e922ff5583d0e4cf56d8a50700a5b1f67300b8f8fef2acf72005a950f12153f194cbaaea80d5385e9236b0fc53260aa582e69b4c3f9f5b0e6a47e28e780b556eb6774b1f75697f0c45228a60bd608a3285395b9f5c9770e8a5c11c8afbf79196a854a92a0f3e007ca8bf14964495ec65eebdfe97c592c509f57fab9624a345ee8168e76a430992fc7925a4275aac61493bf12f7ced7cd3a14aa7c8dc29266086ad7aa7ab69713dd9fa418fd0e5a43525fb1f68ee103df4f52a6d29277d51feeeea86fa830366bf0b8c6fa5571d881a7e82d4b7f487a654152f6d6029d504dcd60514232d84fc790a245899364d944207c8cfad8b9481c41508e11432bbb4052abc4912e8c4fa20d487d1964bec0be49224d60508e5afc2ac903e6389431276c6cf4656be7cd5b53049747e5bfa2b47d50cf5b4756c87dc9af30459dab09fa931fc9c6362339ad3c729ae13189ad538c8097f53267fa52eea1cd7e6b8dc4427c36849da699955b4abd18d3b10ec3623922d495487fd38e540871a69719a77935618f66795442194fede38075d3600e6df797f38ff491e753bd31af6c0f3cacea884c0b62d2e308534f9baa53cf4f3e5230a1dc6ab00acf66bdc6a44760ee75fee62a1767184c852534d5f02e11faca1429800d2da226c42b82e5fac89218067bee9365942ca8f9693285aaa98290a7ccd55451b4b16d0fc17a9f6376cacda42282939115732d20dfdb177005c20e7852a4b161d6e606ae2f88288556c41dd19c2656b546a0e1af15f5368f63290e3f7530684c80f0a0d5cca8c20d0c64a6723f8375283355aecd0230444f6f2a3a5b156c0707b2635d948ffe2859410fcb05738007aa0e41cd060ff20c26e70688999e52c7b1dba716ccce36dea3d70b3e32e43a5cf047de3b057cdc1870e24f15294408c659de16cbd937e9fd7e20de08df9f7b9efa7f44dd9472a8b6a1fb9a3ad6e63eb28fa6d7a82af61245a5d9cc21d594eb670bc70a733c7edf70a37de7d861b0943417285ad6b0f7c87899235a892bcf25a37fbc12012fe0d24d2c019271832ab12589d0d0de81914ef9d89d32619862bd7cbc405bd0881e34267976cb18c62d7f6fe6a72609ee8c08d197f801e1b472b1b870ca0515125e4bc0247be43bb959925b41055db1eaa204d6529dcb4368c5d004ccf7a58a350d93a81a91b4f866489865e0e490cef54ffbecafd5e6ed8d3bcacc32a2c7d08c9bc9f98ce0ebc6b93518c87e66046b0290723c5047bc773cd7751259e35ece5c880b04f85d4ce873736e648efe11bc1b823a07a0ff8c6cbf15387b4dfa0fd0cd4568450cf468d17d01dd01b417d705b3ceb5d86f4809e7e944382e532d99288ebe023480bdc17ba8673146075ff3031ec93ee9516c2a5800de32ec7604bd311ff959320b7dd88de83bc20d850d3425eda31286533a02cd628228c8e90348bf749ec40b5d1716c7e6d63e3a0be77621d86bc3b3a3d3ac1620065ef4e25130215a49ea8312c4681a2c548d8d9bd093366d3c923ceb86320b56ef84d29fb1691ea673a311a883e16bab6cbc5d5005e5311fff76d61b68bb4a32bddd3f9fba7c509e04fba8aee29d02ba40ffbd2c4266443877cba9dc18c1a6324baca6a6b54cc41b42d11647d8917a260fdb911d5872dab819f72ae6f3576c00e3169ba0b7a2dd06765bb67df0ada8a1eb412527444d27d3f13f384249ed547b85a70d8e6f00196d3e19b44dce1f5b152463de577952cb1238023a973ea392ed895683a8ff364ca5ac37c06fe436ec91c7e91363dc4b0fa194012ccf166557804f9c12774e1dcb6907d2f262a4b0dbacb21e3d984bb725b3e48298103c97ead4dc958e5925bef578d69df4cc37b97869977947d3f49ba071cacc65159dc11837e50daf12e5969b9a52ea613769a65137ab11bc0d15eccc7d7eb98c71173f5cd548f06eb0e8507ae04d7993b34407d389b0a046f24e192c012306b9ab5b4c9497985185430ba511aada683db869352a37e93948b2a7d75a2bc72588d54f13ec7b886a558258208e4005e7a0317a721a57fd6d2c4ac6bae4f1ecb93fd1dfd72debb8f7da4b986d26aa055d2d9e9a1b15c89c62097166f2cca5b3a133abcdd152d69d1f13ca0b4708c264c5d7a298aa5cce299e281ec9c95755b33a1c3d7a046d78c7eaa14df33458b642a6636cdfc0f925561f68c1cc7e868d2d31b1e5d318e419e28d73ec1a206cd694ce0cd4637956caeb778630bee52691252958def97a88f4c3aad9b29133b3502c73de0622b8cfd9e6539decdb3f356040796bd8c9de8ce0360969877b531636694a48b063b8f350a503e60104c598f2d42d2b6ffefed4f83e4086a7fa7690b095f867784e99130efc98a39f8cd322714603e072f41cc2b49570722375e631a8691cb32fac31c84d7a81b490326246e5f6ee53ff2cd0cc62ccee594191a781e3d5336d7e62414c70fdce8119c04507bc741436c56eed8131be3bf04142815e29f05287bf5d88dd0c1ff041428adf0500f7145225d5811a6a8be261118c32af7549e53f4b10727da65702dc41aef0918cbc8d75e20ce7e46c2fd7e7145e2efba17a3fd5e865964119a2785102c1a4fba30e0cb8ba128ef9c7a3c105fac94e51d1a8c47c90c093a298eec3b4afea3144832ea58b6392476b2121a8ffa50ca8d8bb0670db8b8bf39f59d71c67b6c1908f997201f05d1dd5320ef3fa889c07809a87005eacd497a6beb9e40754d45411cece865cba7ce6cab1d0f5c2bcc6dd14e7cc8d0fbcda8f446893948924e8e4ada01d87f81fe586edeab3fc61c06a3fe889ed179c01d18d5d18d2ca2ebf4de4266f1487235dcbec04c382e67b7b96ef263cbcd0f5b8f5eee165a5e307294f35ca0b6a470ecb238880379efcd3e57479b64b06c7436450b6dd38353709ed7addc9e61dca4a69349350ec90158c7056d52fdcf2165e2cd8ea06c8159dc20fb1aab5358f186f8496864aa2fb7f005b0e68e5ea7d58ad596be96ddb6ffa8d03e968b2c599ff49bff40ffe4a100dab2e0a9af88c8853470c0b0be4eb3e44d03decaf0c2b3e77f3586658099743074546596ddaa0f76fda9c4576f061cfcaba691d20d34551d89838c9f287169bdf7a4bf6bab4d845f295b1759ddf06cb72e0349af25df4e61f83407bf273295f84e712fdcfad67d4700ee6cfba8da7c724e0280c0e8b0bd5e9d94ea5ebe3e17a537ba9c0e50d67bb3fb4e4c16f50ea8b649a343cbe72b77dbd59ec588bf915790250202dd10f3b3f5a50972bfce46145a844fce93e7fc3a39bb8f5f264b404e0ce551ca7db9000ac3d471939e1d78d29affeb6fccac41e45d7d96559b1735cae8cf7c7e32787ac64f1ee0e073c0db24b015ab3b2a040c13fb4192420401a03bd87f85ab8ccbf80c0f825328c1db39c105b8d2c7ddbefa6b69cd34d5e75f828f543c76043dab30ea62387e6e257fb20e6dcc2e0c8590fd3bc6422653a582abae4dd1377c1cd6bf4a2fbf9833b99e91e1780bda61e32a47450ddf17b06d347aff92d1002e27f177b97751520827491b639036dbb483a0260741e109786e48b8c1938126b895de91ee94f073103aac2a3a58a092880cb739c1c63990bbb160442a3ea3c62822c92e1bbcf5ca26b114a958abc8eb6545beb3e93e13811d69f52c6a3098a336a024881eda852369d6f069c73edb6142c63e1d67607007b6d37ba494603064488c2daea01d2b542de8252cc39514caad8a98241538c42d959de569c1d2043992ca71ed59de90b91da4276a68a9517336254b041d202d4f04a74d3667d95d91e101d0cacdd3355fe86abbe8f112a05b31216225b114f63ebc9204d051d2d54657c80e661124a008d546542d2876804db8da86114099b19ad230811972a196bbc76e1bff4075832ac084ce91367c1114663bdd207c10dcd7173d7a546cb41df09517639272d6252f3816b5df0fca75b5c4f20c91f11d27b70b9d6685b97b46dea073b67f373cef11aee84a07dfcfa75c162efce3b33b576f50e490717440fc336493572651ed9e11b947e382ba64f94f246d31b51f82b02feaa8bc90c19b6e19edf9263c972dca1a4cce2958cc9eb47ac4e5b1d3955b8509952b50c7206e1c58b711a3b1b681f110d3e6d5afe66c8b57ec6d4bdb581cb1e2d6a07081b6d004bc7562e5ae4ebd8b034407c6818a6f6dccf49d99c77e7b2e19908c76bcf3ec12b3376af1385165842115ca7d4162644fc0db3354e80124b5c291206763fa8c50a3a19a0ff051e0d46afdc5e8ce608ab25032d760a1582320769b439a3f958fa294c79177a4f57c85992e6f3436e23cb9017e935732528fa7661829afa83372d51a7c63e1e946cf4c9e62260743436ea102941ae82cba04a9ea739b550998cc385506db268a9455c911e0a1099f755b167af95ca0b468a8913e7f6bd90809aa429c7d68c7104577883f1c22743ef09ec7395f5b533a8095be91b7c9ab6884769131081dfbf48ef8bbc4076e824f3ec51b460b1911a8cf3eb1dbf14b696738203d5ce2bba17f45fe1b4f14d02061486500a292c46becc7a688168df040c9ab1ee8fc6d4030c3e6f06e851cc54c881c14a82fea782b1315b22c7e3f0a1da2e5db0942dd642a7c7c9940b4f47485651d9a6fbc6cb86ed4f5f1d8d99ff922828c2ea50270ee3ea4c864cecdda00a894bdebdcce2c3901ed0dc601830aa276ea90198204146accfa749a74ac40d5b82a7b648d8c36d9abfa31146cdb284a60288bfcaababadf938f67b1bb223a1bfa81f0fbcbebd24daf4ba41751fdb6e4265c5a50b07ce69c4f2f3d96c35863d4445e188acf83e5cf76bc841596f86ae16f47e8f8cb1f57579c38b976e7a7386e8e6850cf778585e553a3945e61e54050923ad7eea7639ef52f26ce0d6766887c1f62202a1b850ca3c3b44a77cb04563b30ba57dbaf2e29ff450ebf5d60f83379c5af0e7e4ac01de828db26c4c2f5c11d7591706c756f2b6d1098b852de236e555494090d706fc11a3f4f9a2d115702c72bf211b3cbd8f052044e28f4f2f71c27711916715ad96cf035d1ed588efd2d17b20b02ced01a87272c05cfc99b9fdae190b3b92357cd5e4051d5b2bec459428e4cb0ede41e848a0a31aa809208bb531e032aa797e4824596da07f99972ff6d6c1b72c911655b01530221bb10c2b953e7a1c9bc808e751fafb34db23cb349ca12bb9f2833ab1ab5b1a51c2da99cf600917e6264060e10d2db35099904f932622ed48360ac334c7788c3528e668b152e9262d802cacf413df119443727ec3effea4468662fa6c6994d02159159f0220b73496906732ec3c4ca4c9bac873dc90c479ace8f52b71ccb7e549494666658251ccf8556c7adfcfdb9655ee704514a262c1076cd70929dce25c836573b069cc26986c3c855824339d761e4357d482f5640c956a733fe214b6c0357bc247fd8e9246eb9a256a20d5a10bf7b0deab4b241a92618c793f688a1f36b0c2160ea2b2d41f66678881af4dd91058ef52f662ad0a49d75da4073814c7c601c230ea59d524fb8f911e0a6546af7d6c7288c3da58aeac4e1abfb894811e212a2f9f541864d249c4332429e0c0667e463279474f277789105b5ba03273f0f319c7185205b18b4f073144a1ed081c9162ef18d71c652f74befe7313d130941952cf4312338f095163075c91785af6c8229f2c609c6909e6bbf117892f9794b67514f2c8254fdfa5786d5bf042b9922ce2ede45dfb6bcc6e440e2aeed12788c349eaadbd22cefc184c663b1b99ede3681cc52ae0d81cb51eaa92636f966ccca8c7177fb0a21faf331ec03e29eb4c3edcdd5bc2e7db0f29f2972668872cddeb0ddc7983c7f851cb6636bc52acf62d283689e1e66284e079ee6d3604f18f8125ff92b0dad007677a43ae102dc6c21b4cc31599e26d03c5b9ca1f1596a631cc2d92c13d014259da4ad2e027bdd2cf25b72dd0dab9e41995cef1085c2e8e1846f3154a4943ce33f92ba6d1a07ce238606c1c4d415fa78a046d7e0df504df6fd099d112f6c0c1f19b231d8c628f450668f1e31a7f2c61646b6c63c3fdc39cd49cd21853a22d40c66a6e5378e82de2ab0b477d1bf8d50f0081e4bf415caf82e04b895aa342b9e4c0cdec3ebefca5da545faa162628888333b7e32c0bdde0c1343f62ec2f9a2fc23a456eea48de8cb3a9a2a4085821f2c7f749c27d2144afc426878d64829245fc75edaefaa2dd0ffb94809272fcb776b2568c433069df6a5c64d440f06516539e2630cd50282cfbd9151695da13950349541d22b29fbf936778ca390a27727165319701ea0b8b8418f0b4184b4725cd3eedafeaea12984cca61a44b77dde4195dfc8a2b3a9ab1133bc251204e8e5a24da39f82f2c64ec3ba41a7e5ec7bf828440be64073bfd17448438419f0b6399282f2a2d4f335f9ed7d9f748b379c2faf344abcbd42e3a08c648a4205fdb037ea9dc48708675685c539e9c2b48b9dbd38ddb1d9033fc41311c35b9ade22a86dfb93415cada5646b6c67c3aa017ab5942c744be4a33edc089d7bc163c224b0452f3b7dd495a10eb1d480b7428d6dbdca88a504d368f6125ac15350d6440b60f3a0a10450858ca80f93b44da4c27a10973cd323ef86199932585250a6a3e3ab408c035814190e76cf93da062b5f087c8bfc22fb91edddf66ead5450e2ef9130068d78f485c86be9802553e8e34a70ad2545eca03885633881b85507c65bca2ab747b2992bd03624b6a5933bde7fab0482b16282171c76f6fd013b2c3daa3e7d5174bf8726d2c3f402cae5f240f5be729d8b024a95fb49ae6245f6f02dd49fa511b5de45ea6b4f0d8ff5d402193912b3f96999b4a7fcbed572147e0b02f1d3a78f1cfdd6300f93b9c4b18a64efc1b649cdc0cf873692ab249306c87e27d39c136c828b94163fbed501f392989ddd99c817503616ba9c453b3b8e4326516407572dece4e4c04dbcfe9bd76face522671f1318415ad5cd38fc7c1b1972199ab7e90fdd6bab8d63d3613af408f6acc76b054415c06050726e684ada91064409aa081d145761ec27d891df81e195b0e08e8640b68e126acc23247508b79e7e9cadc730a3f451abd483f01cb6e938758216c0f6b1d6e7b67d8a99acd8c1ef1dab92d2d14c977ecec03b872a74f7632a5e1e318749d923b9f8ddf0c0ecee5b9f0b1774b7b81e91b5fb1d59fcc13c0f16993cdb78a2457e657edd9541476670d447538be79fefe945f0fedfaf24b79b030ebb1a16536bd66b87b23a3f4d763b8291db1cdbe4ba366ccaeeb324d91b78a2f533bed512a63ce35b5b9320b643214a6f2c69c45535c7fd1a3bd0b3495a59e1ae8b2f85bf3a39f3efac8e3503fe01ace8c5fd565ff5cf207620c2a1899148d9ed0816bcdbb2ab8574fd54c84d97cd4442c0ee14e3dffa83e65df4b17607ef01dddcbcac52c88363913dcc2efa57b03a6637ec44a8c1e252214e728d915c0a7184b632a3e4f1a92b520c34488a45b57a27c2e98c991743e0fd8f916ca874364abaee5744c61f4024966ccc69482af7d817c4cdd00369285a7debf869fe646fd0903753b1440995a3bab952f91393f1401d6392fc643e213146ba6cc79940cdbdb35310d4d85382a2c1205e528bb24263e850170540fc1d071c4223b441f1eb8d783220681329d1c25b35480311bfad007d8e9b85425956c67b19e2bb4e62b0f6ec495595b37a96aa053d0b7dc54a2ae2223e1227e8fc3a16229e4f9a904103e2376898c56a6f7a032fb17cce48c298faaf103b433e7edb7f4222a671a47800d25d0fe2f39f7db4aa0119b7166a9cbcf1abe35869c52f801b803a759266018ae3c349ca05756310a64203f48905146a81f8a3d9cd0091a8fa7cf378525e350aef1ee3876289b166c10187b9126d8af1ce95b482fc95d47b29b11bf70efaebc56ae3378d2f9b3160907c76f80ed08d5ae32628efbe6f216cab582a0f693e8bbed4236c06aea5d886c3d4f7195033546dfc22d731aa95b038fdcedc79f827b4567e0de2d7f86c79e4fd0d491165bef6b031f83e2078847b52d21fa6fa6975f37cd0e50f3e25e893f704b6f06cba4e4846580f8e70590cc30660c596bd291935361fa23f00803277514e5f585709a71b88be0d5a2b051cc41a5135402416e307d5081959e85af76050b453227611f6d9ddd028dad8805a3c286405eb92f010a8654a7c03183c1bee9173a43bafc2b0cd34f7018360fbec1d0bd46f215fff365159de3047f461830b66b6527eea31baa317a4b27b24ab6013d434467cef156bd3416aea227b5386c11a18768f51f45ef9fb511cfb92dc127a24336c6045d3788951c6fb2c2ea14a73206b7c69d03a2991f563a9eec1fae7d42f793b61295862a7028bafee1b9d5546ff60d8350e56ba071b7bec964b30f4e0008ec1849bd088e1cb28b17366a110a6b39796408140386ffb8d4603830dd32cb4117f70da94ddce89ff8cc0fe4ae0b3355fb90b1c620fa6d85c3b8416e8cad051825ba064cf27979bed061b0132ada91b9f19259f83e5f419918b2f05695e690b64343c382988ebc69bffae31fa57d1b713111465030aacea73174d4f0873094cbc96817ef66cd4eb81c1eae442ebe8792b388a68062c039d16071cf244c4f191a07aeb5a30019c2c8d6d40facb5538438d0c749e5dbfe78487138767b40b47f499790075b8a5dff57c70bd67f03dc5f73a18871de36aecfd292bcb0a89b6c0e33ddbe35e6a541904c0b8eea67145f9cdb4fa0a79d7a0ce50d812bf579259679360cff1398fce1dc06c4599a00332959a472a319a46141cd07805c010c58555e054c56ea14b6087e11af74c0f6b99c82e26cf88199b95e03e0cfb2cbb193dd27233fff25e5bbfe64f9863c1118beb13239fdbe3500f77e4e02606e9c7d3191b6cfef270462df39dedf9430cd093d0f85ed3d0f22b457186870c036cdf239b5d177d05d79dd4a761af2ea46b3a50409e56da306422f4b0162347a7e79e9468c192c3ef773fe6412629dd9b6b0ebb48335deeaccf31a17bbc45df14f123bbb6deb3dbf854a3fb0037f6c88f7c3482bf8b4da1b802ff1b8322f64cf455f2d0b10f9ed120f1c93dac20d0838de107eb9bd1318a61f2dfd8936104efbac5f6ffb673562329227240b2ad50f65441ee15ec930dc74b087552f254753f9ecebe2d63bd77ce6ba9a6cababa926796bdfd5652976455dc1da7ad0ff873349ef3dd10a1a5e0b92b3a6014df06d80dfa692cc8407d163b5c0c9a8ace50d1bcf164636ba49b92b7d49931405be577114347cf1c4d85c2609126025ce2080783f01737879249de7aeaad6f33e091d54feb4c45d2830f88efa01fb32f0fcf67dc66ba98b2060acf504d0ea5df09eecc5a501ca7ec9901871d044145cceec8277a8728ea34483fc480f6cca223019c9c7261427a4b418e1d0ac40dfa170603193598cea1359b3291ce8085318a7cb67548aa1f1243db75719da8f17135064b984b9e5fa96ff056f638837e169bc103dc360a284c06d3e68e79a91ee5dc14ca97ba4242d3b7e09ce531fef4582ef2383131ff5803a36ba05e260984eab61be17d5607d49ce5f6db11c50e90df29def9b44e9e48227c5a6c8a1dd37b5cd59da7f40644fbfe795e5712064042e259063b3b834aa169f3cecdbc2c2fe16ed9d588942a664f6d17a9869f6532680f3b09fb420a875c225fff1fa2e26754b84fb4cc1ae54e4101457dce3d4b2c9177c78f5caba7b5498756bd6345b18f2d3b9a9a7e56289a368f4893b362ea98799b4a76a89cf3261cc1909ba5470d3e078872f1b7287a36c986d2113a29a50964d5c80bf87f3ca481cd6748a91c31e94e8ee8cfda615ab9249eff787bc615e014a11e75cae53f9f9573e8604bff5b880ef8499703463db5a71b093adf60981bfbfd00fbaa8c6690d0fc27b2aa75d58570bf3c1fd77912aba09dbff32274e73c575865d5bc73f9f1cdce8066f925ccd31049c777be01dad7452349408566f4c2810c13b95805792513d0236b4ba7e142e5c8c7dc26e0a628396c45861b614c5f8d627587adb6574d276e68b0f25bf0e7f10e439e80db2c8370813b4d2b4a03c6a5817767d5b328d1092ce45033748f570d0ca32b19d25fb6ac12c6f8b484a8e2ea509f4a11b7996c9e6d3abfe2f1d89ce3a2705f2a1adfeef2ec141f1f894bd5d5fb9b605bec011c4753b25c4f2d4c5d35cecd409ca6d290eaa52e416cb0d601918098a9a9dfea46cbe6e4ef61e67094b313f04448a5e0706d49ecccaf23d083dc8f4c8eaaffb8451746ce85851894960e7df687bd24babc959bb71084e93a0b575db7897628675b9139ab6a9c31bddd58c014a860a64103053ed333214a4e8f928459e521ba4bc1f1936e7e2c0ad3a4f35f5545288345ed1072a302aaa4bb422d041ae5518deaf10d869d939ae43d6bde03b25085483e13311288a27ab6e211079d9141ef89664ce6289cb2dc18097b444a5268fc230a81bbf4e01aedd925ce2d1e73a809f5c1bc6a33e91b28059632d1c7feac77df453a38b5357cdffa0d234d2a86ed0fc34d019e7ded9d87cd6dbd537b474d448af3cb74aa45ec1c6bd7c59a7078820f11e8bd9e8e8edeacb24372e4dde9451ae6649bf4abd605cd6730703437a2f36916deb13b8e8d183bb4d7c32da9720e9f1c23850663bda3d8c268471de84d892fbc28e8ee22e3369c196fbda6124cfab2c54e15440c4e27e4429bce9c4297287f8ebe939dea95324eeb981a14897bece61e35701393eb3ab659a3159ecb19e6d09dc0d2a630a58bdbbac1e0c536f2b6e604eea740054e425bf34f8895719a7be3a4117580571087f49088652af6450ec20fd209020693e31fe58b18eacda49b3b245dfb0ae0aeb0d1c0f036de53c8096582b813db096f3df7aa38da5803fe591c14166bee99118e227b2c81149322489a56a696ae3e550e23f88224c8db8ca0f048d246c9507ce33f901b859235a225e26bd0f35a74a6a7d85d95745ff6d45ea5aae51ba42f5fafea5d64f7201f905155e743d561cab3f0dc8d03398852b8e24a1e27558266a7a868de044c72d3d1d064775bc9de806a8eb7d2d735d70e37f34c33e4c1a08828f333130106f69c60f5648d4f4419bd6cccd57188417521dbd1ea4f0ae2257032749fe196667ffecbb3497ed10ad35873c44db037ff18123195fe916afebf28a5fb90d91c91c702b4c4f23e4e9fc6badbf1b678fe76ebc11333a6db4340e9da126dc84c2ca815ebeda2f7ecb7654df3d1958a7778ee2678bf64578ccb93cca228d770494f0d59ec89f1b2800756690429060430e77c0b88fd7c2214c559d44474b39fe269ae6fee2ef6a35d70b219cea7f04bcd11706e2cd1f56fa8ba8d399c0cce55b18a3e995882733ff0c63c5cf184f3a38a91a902b433dd3c2d3a0dc4c50e30d5f3f9bfd7f60519ffeea0ac96d4ab84c8b8f329161f4622656f01f7252006f24ca1f23d8ce243c4d070a30183f5e4f1779703a65a48da90553b6e410738c84db270eb129c82a62c26f812d9628a15f45a8427f6f0c14100e398c488633d2ac86151388697db61992fc4948da136040ecbc0eb76aa7aa07908b72cb2bcb719a54414c3755229bed338306bd4b5491fa29ee3a16525db5c1007033db451d5cf0da302052fbb766de86f3569af6e46ff0945858f9dd52f2a72b2966d7c54ca7131aeab8b67266b94d3871c513d52e3b532b00feac4bc93fc6399237d0ee6d69665b3af154bfa646a3a578b9f98f7d858893507adf1221b073f2e4c50b274ab280e1b26a169886e2bcd4893c15548f11c7cd830505306779061add04b1e5734bc67df84980acd74574cc205543f991f118d7284ccb4c4d9f54d1871a00c6268a48f9b59dd4ddd4bf8f861a3cbbcb5748660a8182c6cb3e1d6bdecb54f56c6933817ba12afd74b8e8ff631203fa294aa2091f40e4914355da6c1f0bac085388d17345766808ae5abfa59218950dd369c6e256870bfb5789f5e1d4a97340f9a962e4937aeeb0defd980e20598bdd68425518b018d37067d1c365bbc247730db31695240b2e2883c58a8edf1d9e37108b7c5f576fa2ac269b26883f541a7dffb25146bbae4fe3900301fcadcb8bc0a00b0fa316ff8a04a6c55209fb127da406f9674009c2e20db614114fc5743383c2122c52f27daa70ead2d5e00ed2a50de4b67d8542b08675fb39539adaee2338e8fe19846d9a18e6d9682122c8a0a622a5b835db000d3d8318a87a76d769d872a8ccf01072f5f71058770d1b1dd5cfc5a5af457e62cd01da88297319668c5a8645c102158c5e7dfad2932711789a44979ad7e7337a69b7dcc29c8b1279b4704172adcadf81ae682647fc684351ae5da0ecf6ee9d900cf8d7d29e7ceda7385b98b2c05f069991061bfb00b1c7dc668886954bffb0ec5a198982e4ec77869fc79da7d95b92cd901d8c36f6771a64ed81afc8fd0c0d8d5f79d4f919ecb4506badedaf8fda5e3a9de7f8dea577def88c1e0f1b0e14e08a47ff85c2a928f95663d6e2f5d1e9dcff9f001c1306db2243ab0c34ceb26432c877597602bb2e8bf463d1f9afa8e39de67b3b43f54e80bae59896dfc7569b043319cb53e379215b0c8c8b8a7c67f2e6698d72a343f303e715db24d77ad2acfe83d3fa1a65d1f94d719c8d4516b021ae0a488cbc52b862b54cc3b2a858be86a9437856a978eeecc851c8b0c0845cb597ce6b093dcebe0c05b375a5c823aa03e0449c9772e35bcc145c1bd7e095afdec8396017017895285b31782509314209e3c79ad3a9d80b0f7f54390d54ba0699d23e53fc0eb266fa7d777fa244a7dd9dfd7d9313f8cd3980076e5ab1e62c6aefd049aecb887ddf2323fe90df38b2bf76a36f305ee2b430ceb0b2879c63800ca9dca94a5b0dd6ff768d516224ee7d9fbe04d42ae16f8967182d55155f0125526c8cbb1f651ff2867c8f301621f65dc3334fe96b5ef49f9d913c71420533ac86d769173c2e9401ab74c243696de8db47068b7e0de56b63be10cab5a6f09f7740021fb9be99a60307a90676a65c5578f9963a4cc256829811fb2b45cd13085e33c2390f21f9cce78ace23cf2788527024e1041bdc1a430f6cdc57f36b617c1a45267990addfc14380ddb3fff321b0eb61bae24af2ad33de982b0901cf760ec0bdc59cf2a1859e9f0e4269c3e82c06c354ac7552230d26f955da5df27b1957e9570eb687b724da2ea8a377bd068ab26ca9cad6d50abd1d87be51c5d15186b4e0c898c7db513b0bdaf74df89b18fc88770e32e0263df00adfa8afab90f160e7f96c33e89d3533fbe0dfbf6e1d1e0799638472e75e01a2dadec7ff6d3dabd5fa79f3d5d3d74f1ae2ed72793031290757dbbc7136a782db59ff8c85370b2c08025dfb768a8d487797c1fb3b0568af71ac2cf2ce91822fcbebae6b7b154747d6cc2855262fc201294547a187f5db21858930fc157c57f9721204018aeef0bd79ec5d93271325891eb61f06fb70e6d4618abc0efceae62c20310a9063371e5cac44185c78c67254f531638d9c61ee5fa2a4f02dd8b8327c40b271ecc475f411375ed945f2bce50803d41f84bba76bb3e48fa814d610b83136a0589682a57d852f601a757fc627000fbc4d2e2e9035492d7aef38f07aa0b2ab0e8d444d6ca9f2c5b1f92253de07ffb0a17c7990a05665d1cd97f57c089001d3e627598132e8b9e963589234398950076b0876b316eec96c8148b8a2ff3fabccc76c488cdd72a928f6a53ed3a95296833ab8fec8b77be7f10bb1c14ee36c17ebf3108468039b7fdb468bf2f0c6982bfa45e09d88356659a47e95f2fb4952ad29dbc046ad40ea9aed96027fd6243fa4b0d4e2927b06ef2cb77bbf1ca0d66055146aadf8c967858499ee6c85ec3cddf1155c4970cdad5eaedf0afd1c9e7425871e5a1f0d03aed0f042a96b81bb19772dff69f012dbe5d5ef90d2c5052b493fd9c2d0f032d9ebfbc648d5c887cf90b1fd5c93647b4628a8da8d0bfabfc0cb568faf2926d741670a2bb384b7884da13df42a20b91ba8da822951014bda239c58d4e16a4e7d046c7082e741a1510b98c376c596d71227b03550f4cc767bf3c414bc2219c7e2a1cf049bd21cab406de78576d9be0c7aa08b2c24dbc34c752a6466671dd05bb9d0ee2a35b3d5b5828f3e0ce7b48de3f4021657c997e98db2004a823b5e7f98e5fa7d010d6422a023d7850f982512a0248837c6eff2322fb7b77b6862b712b3d094eb835559d72a14689b014ad943bcd3d978942bd1c5059ead3f763339184e10a9303b6b117e2de1eb3003d7064ebabfe52136cb6c999065ecee0380588c7743c848580967695f6c8e67a51a1286565e2a01fb69b104d74be196c30e4270505ef4491e8c8d0103116093c84414cd8697484bb6357465350c180a4eefc6a8399ef384bb39ab2e52f8ac410f13741d8c52f0d467a5462e4852c208ec49f65635abc2251f3a9d7975774e076aaf7971790b78aa394f1587b377075ef14d6ef3acd7c03a7b805f3151bb09847b4045c0a2921ce4e448fc1e0dc2c7833bc5cd4b76de9187654f65e78de35e0c3db36a464fff70f295b04c0fa2c4b03f660a5e875f1acafe4333cc07706290abc7fbae88ae1fa8cd454ee1150b6833e437fc7cf59c28dcf99d07123860aebf1e36f1d25f5c7f4dc3fc578f615dc25340ec59fc27b0c0a5d2b229a3bc7446e313534d9156ccf28a2dd7366ce439da125c124aa8cce12f62f49b57b190c0efedee7d9f49ec8f91013f863474e2df0cd8782d978db791f84b9027bc804916bea310002f7ec933a7f00d10af21d1176aef1a78249ff41ca4603b67ded5bb46ecb6c3e084b465e733cba989d90a92c839bedf72b51108d7bba586494b95c5d41906474ca12e87a9f88e102f0ec7d9ccb23027534bd71218b224a5fa1f21b21679bbaae9094339aa79ca68e9ce5e4cfe626d7cc98484f35db7ba5bc1409044b5e3f6888dce2e8709efd2283327ea95f037c4cfb2082e526e713fb8ebda8417eeab48e9d8c78fbfe74e0bc08129ddc3895533a2926798009eb2f19c884e2256983882467d876d1f6ac0bbf5027e24ef0dacc9967895c09a803e858799a4ad6e5a458b7cc3f73d68527f36ef3f2659d96c945ab4137241ae4280559092d502d739c30917b3263e7321b53bc2e15fe5fa9065ddc2b81d2bc11822fff8ff012c384fdbef667b7a3e8aa276126810f2237fa9a7b1c96c32b20937963729eb527ea326982b5017234ce330a12ebd1c93d05fc06decd5abc694d046e0a517950fe005ef86dc66238610be6716b67e54d9f427c845c9965a3146553794fc236b2b050d7aecc440a00c3d46c8a5149dbd902256a1b4b8073bf68521e6bc186c392b6864311d21bbbd72684415e21f0428891655e96eeae276a1958c8fa7ee031e2bba2d75ab525ae2d3100a20741823747d892c46835803c1dd38ef419474d9d0573d303239b10fd6c104891cb98554459d2ba41382241d86db4049638b1e9eef17edc3332e9038f51b15123f95134c575d2ac7e8921a48d38f7e5a9ad6d25c0793498c25347c80cc92f6de08be9c5092506dbc9a1c9a1e1646cbf7d21f9bba110741ecf26d9ede1aba247b7fb02fb8e80f773c6af5dd4807de848e57fa0fef3595cdf71010152e67c0b4988a28d431faf8a0ba01dd423e3e3d1030f29643fe17bb72f29a1dd80324a5f92ae414d907655e1d97f688b0140738afa5d1ea610b5a8d7348a508cfaeaf2bb8924e462b6516cf15a5f882b1df813803c4cbc0f1b821ff974364e3e312ee21c2a6d3fd0854d8e2e086979292a2dbf43d3ae08a016cfc384680f66cb64c97e8fef67917d2d3e34228ee539f47ce0595825361a264187504b2920c1182013784d06d418b89dafc1469d6f4fab5d8386e940ef11e46fe685cce87337929155f15006687de0d1b1bfd9f9487c4e77d623bbcb9c96da745ffe477dd362e7f6bbb7f37690c388529cab9ccbc577ef96cd0d32ec9981730cf47b2753fcc26407e27aa5b32a229ca3613c02806859097b75cc96c46655fbfa208aa902232821c2402e780cf933608b98f0f3c52f79b15ec196283f481d006bd43fce075e5618be101a717c810b2e073914c711549fe22c39bcf25f1d0b4c636ef92c2fc97651011b59f99c6a05157c7f7aab0fa1645cba9e7395dff61e13e6552bfbb2ef415f46d5ae71f0f7cfe65f3c5b39b5d34db04c5f166702b2fdce5f04c487ed3028accbf3da510c05cf104bcf5633ab4a44ea32df7814acbd72fefaa3f1da9fff62974d833c0b732ba563634168a75cbabd4b4785c762393ca75efeff7bf5150478509df5f8709fd330df110ca2198b5e4ce92fc5c613e4d41c21e79029de18c01ba130972557153d76b21c04b50d6e1a013158f5c47e8a1c77b0aa5ed18ecedc4a523a5d6b0aee7ea0a75572a0b54ad2cf96489577c86edf4e906b1913dea529cd7e9dda1310019c6dc51b4641a038ffd7924b80b9af6a12dc7f8ab09770612152004efca8a65395dd248c96aa4845452b884ae48ac74d7eca51b40e9bbca282e14a265938706d6b101dd7f5337d4b725c61e1a79e46a29303b78d512adfefeb0b1e7540df32f7e513b51def3dc7ce29176722ebd991ab5acf9d7617403c6985cef3601ee5496c7582ae4ec98593a5b746586fde02aac4a2065be599adbe20edb135e9d8ab13ac00add4fa50eb592d5630bfc4c2d325598bb97ee38956233d1eda5602601bdeeaaa8d2904764322021c3a64a144cdc2e8b76ca3b9825144e9ef8a5b9cb2ad281db08caad09dc51d19670b975128d5832d4a44ecab2dd4e392396eadfd322f9479c637e4fc10b55b2afc66e6de7ea30ddfb56bfed8d3e05e0a4d2751f49310c4419262269bc9a2ebf4f8bba0bb94d452fc60a2ed6bb0e578e4916ebbe0ff95ce265a115d4be44b6bf650b5a4eb94fa77341969b3c0ebcb07bb0c7821bc0709a93b4f2478454b741087a35cd07a13a685675ccc2eabc25d8ad3c7f5af234e7241f623b133afa9489f8453227b0a7a259d43721100714d2857f9615cb846ba363ef789bdee76bd438c40c7fea960a8bd55513033348699b7961cad63f6f42d66cc431e660e48bfc1adf43aa0dccd01e4b1ef20decfb4d5cd47021fdceea9e16328a0de9e33187ba04011bf12683b2ff85e8602730da0eeb96e5e1f2ce64112bcf9258ca1ec60093077f690daf1f0b4e95233a4d87c092d8bb758a856be895afdedce82e5148085f7e1aa71e279b8609cc1f89c8f2dfe7201868e754b57eca92a074b05ec878cd5db88ef975ce4906255b3199ffd16caf0fdd4c9cab70ae3872cd807b8bf4a364babb7acd6a989c89eeec35172fc47e398a85490a272b8feb983e69ba725f66a4d10556db21599d82805f6b186bec99ae06d8c068dcca64cd4d34b339b5e9076ab1a4fbd8d93d0a378cece535fb0b6fa581f66561d14b99b374f18c63063e1e7b7d1bb02ce395772621fda74625592a1509bfb7476f1089809039b7c2a26c2d64c320a5252f91e4414ba9061109f2860c2df52966398af4e4c210b6b269869702a909a19ba3482e066d50f8860673122de6304c1e9f974482078faffbbd3a24d040a2498cde2b5b943e20e430a95b7f307517543042edd7efaf93c99abbf5337fa85bcb7c53361c8bcf45bd1df499767c06e6b73ca5a7d4e06d6ccd0628eb48813e87d6a4cf519c1022b4dc4c5bb33bd6f5ca7d45509dffd2e6e76fcc88325810881ff9d11147fbc6a2a1b97fcfb9b5426003eae371b2992d95c0190ebc3bd6ef09e792f93e061a6ea994b32d401febadd90de2a5a6af4658c78c6298fb31bdcce8148b972d98b7cf0d3731e6d91f840062b4afac78bfb64b56e65b2de22231bbb313dfa49704fbf7cb077598514e25e43409fbc3b4e65db3b0c0ce5b835402e66bb09dd55a86ea57be1e582366d67ea58d365943c4cca29101ce70af911d6cc3cc204eb2fbcd07079a098c6fb99d632db6ed099b602002ae7000c4231c70832c87bf8b916facb0016104069e0297a228e4f24b1ffc9e37e8e604403ce491e4469cd0592bce9a5fcd2c1a960d2a96176952e6e6c7c53c950e2cc3efe17f4f8c0215f7f4c9d81c615e7c112cf37234325a9c6a041abee4f2e1483525ed7ec6f864c25985ac92e2b098f6ae42ee429a451305383627096bcf95018cb22cd9a1140ec78319de29c8b5211874042a1a2b6b2005f714d3c5688ad364ebf96c59216c32d325bd66b4149e8acf9817483c9f7264dee0ee6c17252fab5f1afb4e41f5d513ba4cd18c818c557174f6142668932111ae8886235d28355e8ffca886470becade505fa99472c03cdb64e9c76d20e2fcf77d002c69b653060cc13920b703afa67b36190193abfae2edea2b75ba65a8a94f147cb10c388915d2e620c813591476b40526bb0487d18e28d882d588be8c1b6c01b8c92b4423a80279b31e14408086d27f1795adaaee62273c4a2c09674948107482445230d40c2d51200613e4a7801e2d09158503d1c63e1913580a22056a8523239fd8b5eb7ad4d6688b65dd946c63c5e0343e9e207b744fe28390bebe69ab19bce1671db644f5c4d706294bfcf23b1045c0dae24a1b548f832d704c1171d668e046a29055765f64cd122c83349280207f713ec6d7e3362320df75136a5305a49103ba72b27f5820cc524a0783fcc92540a636b56512542300ffde127ca48755ca33eaeb4a3fcad21b18b0a6c2379d0b877c96e7020070fc623abe006cc2c593f76252b6698f12d8255385b3bfd9974e5664117d49c8de9b6cb9a59429c914a5072608a0075147ff084d17cc60fd434a95744317dfa1ba4da541305656914d0db60632d8fed8b42a74adb921786955adb4caab6ed3afb7386cf5c686d996964d76f5943d656b3626b253d844a20f1a8cb25bf64b6db5fa6d255772255752ca8f88a505d7552ec431eea87bee91b6ef65b41db17e7ba46e7941a26cc8532ecf3661d3d8909f7eeb51b2e1dfe84d0d4697c2cac299764c0505b9504389c5a729a62b2f08cce0cb7f8a89415f6a3042b9fd852745cd08fb5d7563172737642a9f6bacff1642080db28ff94db5213e710038c4184fa85f0485a4bb7aeaf54fda0c6020d611c6ebd3f3b95556bd12b3f14fe85437723e47b1bbbbbbbbd65a8f1c8b952ae254413535e54d0daa9973e7fa57ab34cd397343ae9a33e6c63f8a4b110a0b8ff598d8138cd8eb62c0b08e68e958869283e5862e0575e39c7136cd836f9443e7862e0525e78d508cc5f735cdc3d930778cd1ca78dd3b07c1e7cf8f0760c6cacfe353f3a8f5b6b1215715b1d61381244b57d8ec271b3b35c8c15b8a032d754d70f76fd67529068acfba633da64de9857c879f7a46664f0492d0a9725d2a2e71749a6efca3168a11ca56e5912d2dc50bf0e0cebe6106ee3c2a21def9448e14462b979fbb1d57b08f69d085441063fc1a638c532ed56044622b2c53c9e8a166fcb1f62012740130c813b99c6c1524a61b72951179b9cf6c56242bc29572d42ff71e0d36acd9d31d2bb0f46764ee699023a458f71d21c5584158314887f41212e359ffa21f9f6ebf75de11ffa3d677cff25e42fc488c7b7aa4f5befa55fcd949a0e76dacdcd3a220dd778414eb7ea394fb20acef28c6aac32207e97edba851358af1acefa8f531e867b46d528f04fc59efe560c31a3bef48f7f6e30a859e6fcdcf0bdfc5747ef835fb59c429cdb49f45fcd2af48ac5f18b38c9f64025a5e52cf97a5df7e475adfbd7722b4be9751f7bd84b0de9f553b8ff5ac2772cab1e91971cecac609e89eebd35823042e223e25d80b7024d4d0831bbff5b343aa1cbd19bdd67b566c5867b6630536fbf8f463adcd7dfb4e57146375a4cbfacc7b01b14fff957df721d2cd4ca81fae3e3ef55e402a53ddd667b1fbb1fafac9ee595ef79af44224ae5ab95c3dd0faa61835b0c165282aae5031755b9f3da07b1a56f6da15458f487f186fd6fd583deb930dfa0784fb2cf3340ef403fceb7b25b02dd66bdd1722c9d80de95fc9d0e76637a3f4b3c9f28a6419fd2c760ff0cb755e9178b9af886f5fe85e96d198d4d967c46fbf5c791ce8d73e1328fd5e3fb2ef05a4be7f28bb1fdc60483fd2be6d3912f4110cc443ce1e0dcaafe39c2cb93010f7fb833d5b4ae9359b93cae539e753c96c3ca8536c910a7318b558f0d6a128050a150af9cd11055888d983f8c523ba5ce9e5f613c1e011285879310822bedcfe1f18944188b90d24defa452706923f2c61bd58a575c569ea7664ea9aaa24314c5a2614eedc6e40ebfa73d26d8fdedefe8d0245d29a52b1796cee98bd6d904e4a4106ca420a6d308f301eea98655946a3dc3e6e7960b0ee7e10e0fa34ccdd12b1be8492a684ceb9a3259973ceb92599f354c164712749994e09d5a6821455ca93357162369973cec9056dd2e4882964d07ab0a658a1e7888642082396cc07ffe75da87ea54049ec3084154ed04045455e6102059dc354032b3141f650dcb007d4144e6ec84f5041526fc258a3a429a19452eaa50a2a6a157652a8e20e8d32e79c93ce4922abd0228a0a3ee78a2e0d0b090834349440872aaeb0e2c9ffbc79f39455b14605695dd6c75a3071bca0f64d1a1c59bd0cf5a6cc0dedcb293e9b72ce3748ace077ce39c12ede68916f787012f98d0d4b2cc8374a5254ddd0be1072e4c869aefad81aaed8a4e887941bebf0c840459fc6a0c76e16f16def039d75d7009f1d9dd545fc4a59f4fa31affc79e52743eeba0fa25e451ded2c078a28a5947215853806350f8dbff0bbfc610ed7c7618ccb40714d0458a8c793c043f139c64e36f3478e1cb24ce33ea7fb941d7cffaa6a610ed787c1ed8ed1234ecd28986fb33a76374f9613d162aea05b30a160cc2c2920e924e0d0691ea2d0c18e17477899410c1776a40e52bc10a5704297b0cc610799e509a5945236db9299a539e79c5bb42496ce6001536689cc218a3c38bab80e021531a490c50b7660708a414561270a298c608edededebc00792363e08a715737ba96ed0410f884c8df3c98b71a3dc6e81ee8910ad12a1f4941fbd51fadbe97907aa4fdeab35d100d29cb8b525b836050a305954be3b21a2d92ee2ae9fa1663e762a0d690b02110b96e8df8ae27567e3abc8ce66fd33b92c27cfa47d9d3ef25847efdfa1dc93efb65e1587741e6571d5614841dd6e06dfe6929a557ab11af3134e2d78b8df1ce90b261e50924ebd95aedd71e840e8df8cdbe703aa5596c31db7caef40b91aee6bd7bfdd4cbe873d461f4fba83639cce46fad785d57fb8d3fb0d9d3cf3edba4c746b0f68528d4a75e18847ff5fcbd7ec4d79efbedbbedbd82dbb7f7fc85aeeab18725dc50fe10d7d5e86fabee39efc8eb47f6abd5e7dacb8c662f1becbc7e5f7df4b28f9da669da735e1d12224529dfdcec7fd0cf68a5af7df54ca0affd466b5d7daf9708fedd1bf5733f02c7942cf15fd2cf7decf50be97f202265b4289b6a98628c5c966dcd5572488332f3a10648a2c776231e1b729c127c931cc75b59997dd7de40a615bc3be7ee3e65dcb05e4966862eab276a758f05451837c6b8c48a4be365f3e07169bcab2ba2e1fa0bba22d7054b66b817bc055dd10516fc8caec88219cf65ee8cf7e98a66f8bc8caec8a70032a264e0b83264bc1d7365442941726524a9f2fc262a05237ccca9f3863560d77d6d73bb28244144252d0d453279b258cde76a3f57d3c0b8e26a618401834c0e28305c30c01765044085c31ad7816ef8737de8ba7781e7ba175dae7fd142175baeef7822e9e9d28832521084d405cf972f7440a2a44649621142c4192584b83367694af6f0f1adab8b2b61b8cda52fe9db00ac10b9aa252e7d59b9b8e1d26f592323ae9ca9a109942e26903c68b182e32ba59492c55dea83c10d6ba4d962882a2855415cfaac05144f292d028719435c5ca5d1428d16488ae48f1a353c74c31a2b9872a91aa815dc61228b2f97feaa2ba23f7628163c0061a1e6d2d7ba22ea0202aa8b3ca410183c58e0e1e14a2d5f5ab42b9a63ae78e2ca9f49288f2b942e6582cea5575cb9d44529a55d0d3b6899e109a8289cc81d82684329a551892b5fca6a0410ad035cd9a4c7d2a89366050bc2dc71d59942698618a70bead059c167aaca823a5eac20038cd30c3b4e96629471f2b1d342cf1a19766e7062a798a69a62d82c515a524f3d45b0c022f364c57462a85a76d840e1ca24b1ecc459dac254e9ecdc5995e1ec78a18599daa044a960a856539c645254da0d4fe8d454f58185d9942121472853459960c14798da32a558a14700c324a1acc013471853e577902cb198a5762d599f79394a69e986f63fa62a9d3063c4065466c2c0943063260c336f02800233564099c90206eec1ff018518d3303ec3e6cd0842d0f17721fb50679e38d9accbfab8a82e33c1b441155b5f0077b03d337050c6763ec048605746dc60b2aea81e2c8dcb5067daa839a3050726b0f632141a261c0461e56528343074b0049a18b8a0a90216292555283478aebc1cc504889a467ec040524af9335e4687c085667cd149c6e7b4f919b9c5c9bfa393537b216850dadb5e062cd8beaf7e19fff28ff148b157ff8c7ff97794c5fd339a71e413a570623346082f8208f326d6de14368c4edcd5b2854bf3882f1166bc8c1732e37bcd8f21e435bf2016bccf736c8637c5f09a5ef367fc8bbecf7c19ffa2f463cc789fef288606868d5ba2d30caf4e4f460c3b592d5bc442710aa7f0001cc56418cd16da5aadebbb19a760a16efa81857aca9d78a8e7ed6f5fc86fb80d0b0d198e3ac00da35310cc73f226060aa3d395641836e5423e45471b100d19eb4d55ea80815efdc2a7e14dd2c730907ce9f235505ae2d132a9f565c78ac54ce632475d3e40fd3008e65da6c15a7da21030900bc52c0c237ac3fa421d6d40344f66fb42171f400b0a75c0408f7e215755d9f8452c0cd4ffa6898d71a83450a2ba336b20e221edca0fe10703a434c240f2a50c6a50d24062c3a0fb2fc45588104519a5121a141465f5a15f0d5783f25d4f49e23031c5263c24b770b95a8c04312221c85410128884800d150fc9205808749893161ee2d897332e3251472465494957f8d2cf3e99cd7eea713569529b94b4c4431dbf081ceefcfe0a033592fe72fb6392152b6e686fff2775a1a2a27092ee002e434901c3dd5afc2359eeeeeeee524af9bc926337aebb197d38fd6ea8a8a44b68b0692029212669a064e9ef7822e9f6c73b3db8ef8f639a47f7fd318a7d90109f044025e9c647bafd118a87b4efef383da85a055f6e57cf9eabb56ea1e5cbf52f32d1225abb4da5795471a1d677670083657deb8dcc4b89da87442974f3c85483a51f910f095bff82276c1899989a877c252c12b21ecd636e3884c7c8cee306394f3210fdc85402fd76b99828c779803ffd6a784149f0af3f6bade4b1f2c75c60bb3c86f5bdfc39e641c3fae5d743d362ff06d1e686b1ca8e226c18745b4f36fed956726cebb21cdf1144801e02906c226080201ed22efd1e3c945d4a298d94ee6890067957580b368fb075e9d3a78fa379bc44982fdf68befc5e6e24ffe534c8ebe16058f0fb06a9f7848dcf2d1ef2a74fdfe5424eb15d8bd78fecf267998f23ff1b82af20b1cb135c945f12ebb098041737ecf1ce64c3ef6e23b6b8758d06df07b38e0b6e5cc1b11cfbf0501d29b788237f4c6a9fd78fbeb7430f5caf21bb600561c35aabec8af43512af4767f9137511bffef93050af56b4bfc6cbe95397b3bbb3ec9d66efd9f7aaaf5a332dabd98a66b4468334fb5c0c445d48946905875da8d6af66f139ca961d65ec2867d3bebe84c423d947a4d8ecd7be5f46dad14b483752cc575fbfc8c45ffdfce339992753c97d822596b0239528a5948af912a6c91c33658b3995d426d9145a9d25b78261c527fa7cf4c1ee75d655cd42326c3ef5a6cf902bbbd3b4164d8a1224a14a73ce3971e0acc89aa024c86850a299d214154c4f64c8d8d089939da1d366632e5aa89374450c5f5889f23fadcb5074cad0792242894f09cbb97a5c574baa25852403fbf23753f431bf768add020ff9d7276020f95576fe0555ac43d9f935d34ba766f26abd9db8a71c2669d59572cb93ed0fa55383528bbbbbbb3bcd6a56b39a65533a15f13c72cb4c63a58cc997f225a5d3952fe593749a5912175bcfc8ec2ebf38c34b66f6cf04feea00bcd8500ea08b0debe54827ba942edb3f966ea4bb7b411a74b11c638f066316fbf10586e5675d6139cbc62a39dde8aa11b5029b1b81e460f969ac60f9392e2c3f8d26cbcf6961f95759587ece0acbbfb962b0fff1fb698d952fbf23f225443b32ebcbef6524e94bffa691cf77ed031d16654357c631d72ae9e9c95aaa6ecf9e3cd2aae8c3bf6ea25df050fc3a06a9c36dd9e4591303655318d6df2c75f0ac04d3091bcaa62a4a45a9eacd92a20fff4ec2427d03c3fc5b0505ddb8a18106afd70d69950c1937a455d90978c83f1b32a7e8237e5d259eccb2afc618a43583e5a0fb65d7bf12c2c84005eb3f4370ffa0c12f96a5571692672453ca9c65f2e7c7ccccccb17f91735e13e6471dfe8573ce497d4e77671f43bf3f32f3836c57aeeceb17cea79ff48d29fff44c4aeab37e6c3e98b579d0d9834ab13dc3fa4dfef931d07c39db063bb3e7ccb3f7ccdd9d4ee7a4b75fa553122e9862fde51070052a3fd9a046e99c93524a69651b73596599ad2bfbfa8594d2f7d64aa394e9277f8d95ae7243db1c1c1b3fb4769b2fa5d632217b7bf9f20db3eff5c35efa56725b99e1b2496da756b3596687317fdfd1b9fb0423d90581afc6401c65fdd8ddac3e0b8a3f4ebf6d153ffa8842d661433e30069b7d17f13c79eefc000085ed9e7b18a5dfd4697baf9e3b23f1fec8600525a8103e2019aca084822bb39a7943828e6ef6e4759ed39698c47dafd5b31e2976f45a716fd4fd5196d567c4fd113bac23f24ceb07540e04095a7536a66672c505b9da576ff34222770baa755bfde6199137669ccc3167556b500fb0d6ef2a653a72b7a05b3b22dfbc217eb72d7ba2ae73ca5b2f145d8146e1c6f899c07a9e3c4c3d3f645580e3b2428aa156d13452cb45a105153e3b1df87abe0adc215fec351b27a06a14a87d6b833de4a7860fb6bdea853528e0dae28324c5dee1c6d42b77a2a0246c2a777efce2d2276a10dc3afdab3744bbd37f2efd420adc213f57360f198ba2b4dca960cbe001f0fc2e1a6c28a5dc7e0078ff1978367078ee0ee53ad3f5a5eb9ffca8e5d29f1edc3bd32bc2527e7d27a7ef1efd1e4abf5b2cfa1db73dffdd322e7695b6d25418a8df29d392cfa5c914e91a4ad5604f6c2a3ce4b7eb58b9dc2da0bbf30e03f5bf1abae6f627e08654eaf6bf7ec40fe79ddb318e8cef5a2d8871e89ae843aa79b00b517bc5e9125df2f61933aef4e61b0094b131261719a582944be372940a4e57724f13cac6383ca4cddbf3126183e7e085800febe46f4a80beefe037f080707836dec3f130cffb0e827cde14d1ffcf87fce4c6c67fff46dfff3ced5efe7d363ea39feffb8838ac8170bcc77770bcf737bc0e1ec8037a22df9aa311c777248b3f8ef77e03ef7bd0bbf1409e8defc0fbe7c0037a98d7c17bde919f07df3da39fcf3e0eef083b6c83204a6c7c47b218d978d81fa9ff1db1f1c19ea877b8c1f13fdfbfc38d8dff0fedcbdfc8c6e378a3ff9fcfba977f22e0781b6f64e38d7ebeff1d58d8ef6f3c070ffbf083e6f7e1f81b9ff73bb0b040dfc16ff0e0051f4409d077f03fde911bfff33872bc87c3fb1c38be1b379e633f39fe7ba31bfff3396e7c471e8eef891c47920601781168e0e1d0e003f044fd9a36bca58ed9f8f794304140ccc62f75ec7f7a416cbcf446e0988d774f88c7fe6f784772fc8d07f28eb218e9f80ebe23254c121003faa518ebe0757c2fff7f8e1d2dc518d0bfdc3b92c528c7f7f2cff11cbbf1475efebd44d0f138de28c7df781ddf5116a3ff1cdf5107ff7f04f4369e63389ec8813c8ebd67c36387edc0638b7018d703485787d7081cc3f147fc1413c18d17018ee7588c85910948098eefc637af743061de5cf22607de065e00bc977c8efd7cf18af7925facf27dec30cfe398e6c5022eaf25468cbf315c3786651692317e2e7dffe4c243f6dbc7e743ebf3af2f74f97cf8e0f599532e7480eaae9fe2823a6e28a76e5be0c509b91bd2a513a40c2fdc71c3b914ce23b8c1cc7e1e3050f6d26b58f63b82b2677d2178b3ef6453b67d2bd9247d4269b02753f6cd2522ac9cefcde33d8e6d36bc23b6a28a229262287448a05012cb223f86b2c98697c1bf07802506eaa5a8a464632ff150d75ab2f1ff05a87e8a4f36bc230e63878ba6180a1d12306462598808804ad27d01f1f94929cdc3bf5794ba52f70ca5734d1c7fd31fa9629517e3c8407a75065a9c4e4ae716d65e8e6a52e786d6095ba700f3d849b54113343770b074095818bb41c1e21811c3096ec860b5cb514d7668e2060ea6d8ec7254132a514d5450a1d1ac71185882a8264b3026b65e8e6a920456452d2dddb0c5883d3fa0952c00781d830e9391d3b48ca39ccb67b2b495c7ae39f8f3355c96ebef18db0220ba6ece07ce4fad2e17e8deeede2e996bdb6e29a59452cae972d5eadeeedeedeeee1e4677daddddddddddddddddddddcc1e6b5633dbdd168c891cbf7ee2cb96d3a7f469a5f4e8eeee3eddbdcacccd63dbb81827c7e1abde9e3be7b43d5c10e7838dcfb1e9996105671f9d9eb4eecc2d2959b29bdc26b4d2404f4ae9eeeeb23a33334797524aca75c58172cb28375db2b816e703c7c6f7766666890597c5c697cccc2c8362581968dce5486d4a275a9b34b35c6b955a46679eace59e676a92524a3549bfd612cb0d4a2d8fbb9c4efb0b7c95ae71ee56f6d0430f31e41472dce568529335a3938ed46a46670f313030e2c96378d0608f1e2c7b7a68cca0b2517a936696de217aa259cbcdb362b78a110c8a42b109cf2acb326e3e07a475b32b5b4a6c7ca2bea2803b1bb7e23a6d55a90780411804ae7b0ccee7ba1ff2d20910b9b856d6b2140ef89e8bc613cfa6553775537f37b5f74c4bd497c8870ab7327cba46833e3e5dd17c9f0e8478b7ef91351109e103063491e8e3b2eb2a8081180f118765868c18b56948d597b2baaafd2a41f974b716c72f906881a10446125c1e2a3d75f2741191b461a590c38c242e2851a25e71dac2c275289f0f5d6acc8d5467dec42dc6a51b5f32c9a6282638dc5056915602bf3557b533ae1aff036a727eae5ae7ac9e03f31712b9fcee69316ace9e11d97dabf5acdf9cc7cf5baf46c36495a42b5c4cda925445ca690b978e49639a4774aae1b6969854030f456b5f0cd419e5be90b3cd43d3be6d83fe5af36885ace7063b20ddb79e763f58dffac2ee877faf1fff99c07d7d13581ff7b1731edfcd0bebbdb776f6e372fd5416c45663cb6f296b6b5ab665f57385f2fac88fb7b0fd527619374565839d7d3472552e06a795317e1f2748ea10db25730d1777f4b136f2701e91fb6958fbc795a53733478dc50c245faae988d4d1842de4fa823556b0ab973cf4bacc1139ac351937cdbb526a14b8f5d3a17b2217b2600e192f49b1fe10788abb27c81bdaebc3403e633608d1076d14f2164720982fe70bf22708f65a2f7b0c9ad81882526280c5ceefad2b821297ec09f2b6d2f471811c59f6072d4eb23069c70d665d7da275355ab027c8195d51f3990f78a559502de058210a777b9f79675cfeee4090cf711fe76d2bad66744abf1bc771ab8de32c6bd3a17ba1eeb3b55ad75f30eb16602f064d408860ac50b9fe12c808ee7c39878c97a41b9f8ee04a2963dd04bf61816a4900b458d762fde517065961c0c4fafbac33ae3ee7e0baea75f66f48d06dd939a0748d6ec6bba29ef7aec8468dfb0171ece8211bbcdb13750cbff5930de82be3eb0848323821c1020625b2a071136386ad7ef3647c2b2f94778be1c5cd0b8feecb7ecfc70ddacf6708775b9f0d3595c16deb3ce95b08384f3e6fab2f3cbad525b38e63441a6563fa723e9dd3887b992165432458fce49c1f90ee8847bb62e3e253c5f6f21b48bf34322f064454adc72552c788878a14173684b1248822b0a041bac10b2a52f0c49aa617a03883a51ef1a5890b4b5bd4489971e20e9427636a70a18a34f38212197c50228a175e52e044132c984c0933450a253985a5bb301126490e5cac5461d102945c420e1a178620c10ada784953c606364ddacc3047094c1d32588a50f183152956cc14d1650c92152469b2f06108252b5188711c783042c30209dc2041c39a3261d88b28787e386229890c45c43c0a9326f06899414e5211601070c50a2a2559e84c8142aa480f767218020571b248e14b12121f78c0f24445c78d12333411461b20f0a8417243145216195878d374c4164c501af00186285190c0f3660a05484c48be809344c40b4c5978d12189282827a0a0c9d243122e28beb800c60b4b9078e1e9d5449326a29ea2c890844422ce1b26a0f2cc80e4c9982e3d749b33499871a24c0a787e807204961f5334c8f004551518d0482e0ee7688a9492921b92ec70c5882fb248c1c1ce114f9c48228bb02c3081a50c1e20bcd040440d5257527053039425e4c82db379b0001c12acd0c59c1380791a824a082849ba8c31c2890c42fccc2084933a2e8019834706b1c110456c21e70b0c4b3c915a6201e29c3b1828488cfd183dba7497d2047fcbf9b894f2db06e0c532736c61e39fed7fdbd1e08423e55a96a282caf5e8cd0a8faef605c99ecfb4af861b1b1a91d7ff33af2bb259966599b31cab7d4fd8ec897ca57d6683953fe78330f61101d6317b0cdd98414d82c3e6134008cb178479ab77b780ecb320acfab357f6b5c197f6f433ff97f63103655fa5fac2cc5282534018b22ccb3249b92b9f5969a35936e79c349392f9d9a564965688bc0cfb6ab55a497777e9e2ea5dc732b3e5724193f53959f21429a59430068a43eee04f8d70ce39259d35f32624b394cccc524a29a5945232b313ac2ab6c15d00fed81579db62a27f5a16fb63fdceb2efe88f8ef891621c97fa6d2cfb5ef30ba284484c7298fc1c6e2cc7b4cf92e52542f6ab37ca7e95511965af7d47447e81a7ec439ee25864a21aeadb58babeb1bde56ba706e57b66ac7f3f295929656520fe4660398a853b37690496a35a48ba1a0f55285151b4cc1fb0a85158f82259e79c73ce6f86a72c2988422a4e9dd804e2ce29ef9c73ce282876aeac320aedb3d5afe25ca0bbbb2b9a9991797d6a9a94ac56967d8b2f708d4c6f0f071b5dabff5c1f1176f55eb4aa736a9aa6d1baad3c49572bc642c677df3801514c4e709e3ace5add7cf7b1ddd9fc699817ff6a6dfe8b962459b65e45f5301c193b4607a37750501838e4f72ef97e411359d34829074d3a77528e41ae17eefc1a3be6fc1a656c92940bc5e8c7675895b65d9f552297d218437214644727e8e4b084ba5952479ea15382254f986e08e2887af2e4facf1326782403eaeac78316cab6678ea8041ea25f2debf983feae42ba27ee7cae85826a341577c670fd2368d3f0100b2399f0e1fa00b4a13b9fd9cb9d5db24b3f01309003a8940278c0f5e79e3ba9f3263b20ad1f0a6c2baa553a23730deb276aae824be0a1eab12bd2ec4ac4bc91835d71e1c1f6e02786e2a9356ce2dc59410855f47294933437fca02dcc45d5103e709102869730f2045b7871030c29b0810c1236fc8025c825c50a2edc2c718309a27832473acd3373a20cf15b94133237e427285e739dd9506597a39c28f1840b0c6a2995a9e4b201e76e6c86c0c00e65bab860064e17282658f26632b1444e0d486aa05343d2172820020b1f9e9023656e804a933356a058b303179985c9d28a09262d38993f9f6813a529b436dbea7294132e576cccee85ae382465fff42bd2baac8fb54feca65d8e7202844cc1c7449b945ad5b41ab34863f4e862ea8dee4aaec7a406659426868ac2f247270957be7b174cb1fc52724bf68e6eb60c54b0fc910121307f80d4b1973cc39841068a3f35a4e5cf66d44bb03def60201b622cf3d768707a4b2943ee5a8e6777fc9658304ba794ba9c9e0e4456b2c75ab2f27988cf8df23959e98c69985d0ce4534379e7bd7bd8f8e7b3cc595096654fc199516a5d73d6ca94259d328b9369fc9a3d675ea0eda8044e974435e9d0d4000000404000e314000020100a07042281301c1ed2e55dee14000d7992466e5c9ccc635190c32808216388210410000801060898a9a1210e00686d76025bc4ea29c66fddb6a2625ef32650a17d51929d62e56f757534b5b9e7b69a46ae0504606dd7041902b0ed88c9166c7edae2f917205454e88abc5ac38df461653ceac3153de72f6d48708df4bdc8f5494b6e6f0e589b8d05e1a32f599c0eb4703af805f5e7ba19063c3213553b890b1e3413f8104878e29d927f86f3302866c12b308e9971a5f0e562c1f5372fe18aaf8e2e40d0dbf772add0bb868d736f6fa555237dfae0d3b0d09a62de1bf91eb8496d23c5ea087cc370f1123ee8c4dd3f680660bf4b8ff0ee53012625341a6c7d3a262ef00e23d8b42b83ae8125ec0359d869361d26e8998536ce15c471e4ef808d8917825f6f8af2787bd0dea78a4cc528c7fa1ccb668f297dba1a2587c1d45e900e0e1a3020ba5062eee10cb5761b9ebed3d6a6d0672b8cf10a10ad077cb63340cf968c0f51a2c04565806f62d25b8e8db508a6099e4a810b9d053c74d86a66cec8875fddde71ff2ec76a5123d2803790b430886eec3d83b57e1dd3db0386ad98029a74eef4ad1fd92abbf0e77a4fb6aa8ac22bc1126c21b4315717640458ab3492e4d0679644ed8d371a52efccc24715b1d5a908ab0ba4bd7c94ae6032833ea11ccf7efa2c004ba4fe30ee2a1138e6c6868bb86721ae3d088a38a0e8c49e63b24a275a614fa83877dba3a46d7bc2abe565ac528244b10bee5e192a90248b978777b897ca8af3bc37313b80edf8696418bf91832ddad7aa5055822c6eacdf814f3f9f9f701fc49ee5b94b70642832cb9b883f994f2a0050fd767cb1d6a7618dd35b7f49a74726bc5303fdda29631a435b091c33f32810c0cdf439f1d1c71e520e206f798ef2713ae4502fd133463345999c6d44b54c9753a4b943d2c1c56e8cd6e248e01d70bb887a2bb224da2517a133f668b32dddd59f4438ffe030bc036072a3eba9ab97fe5a621d31ff371be3ff8f3cda7b6446c797ae9ff1a3c0dd77c0202b91eb36f6048e8b42033d1e9ec14d211a7d430a44e1c9956731f50156ea6d7b70d1140bd850de7c9e4e5409e4519fcfad5aa90cf606e1bd68e2c8f49a2db2e94a3cd7ed0a1e52790bca95181efaab69a04bc593b91ce64548bd3343ebb108e882c57cf4be98d06fcdce6aba45422417e8d4c8a8cd9fd09dc95ac0e273bbf35c6a92830b3daf1e96bd7448a6a9691c5377bf6d35ab30331450c9926b19468c9792ef4b554f52349020eb109f6ea8c8540457cf80334706f511ab5419a891c01b27f28bac0e8878f851d49142885f64ffbe325bffc7080865f3c8d1ae5391de18526bce8b43d3422a994182d73656537c21ed39cf4a578c435568bd8dcd4d1d0d4b8aa0c8cab19efc2c352b5157125c28f771c5eb7b36f7ac89eb8ae6fd25f3b9b8feb4ce8c9861a7ca6a4a6121de7eb91ffc4aceb45df6e58e185c55316ff4898059a018caed30ba1dff6e5e5d2bc7489a09d323a2cee41a363990e621e5ae9236bf1e9d04c747c1cf713ba3a6c59d23ccee2e929b1fcefdb7445e8f12382c344a3bc2bb8fd904dc6bf3d66c6924d4c746884e0300298393c069081b51d59ed0efa673ae13ac788742757d91846fffe312d22b0b064a9269be70c4f592c0c6c0de52d8ced45ef73c43a5beb5b2ea119b026b36a1effb37f5a12692677ca2c5541421b230b03791102770890cdc7c70a662f1e82aaadd35591c2b36d7013a371e49e4de7d03423f2dae50a3b90e9179cef4ccac2c355af7b1e8bda115ff7f73a32137f52a4f59eb8c6910e1a1193ef2084d82c9d356319c934e1fdc96888d0be44f164cb03cc48e12cf2cb060b0a25a9e54b91e06353967ab779355891c3dc4d5630fd838d8540f72d9e2e0a637784e68821893b91e26c441476473e3c5608e067d75ecc04043e73b800a7dec75044d97c73f0e933ce1b83430b4a851104e11e1a6b2069e4a1c1a262103ca332bca7b0e252e9c66c4dfdf2ae04954229363bea1f88c183b70ecc34a2b2302d3ba0bb9a111c06ec5a2dca278802217fb0855a3ec8070b1176240771efdec29d0b4880d4371392a170cdf200faa9804599f96c90c7dc90260072255633517ff0fb5095a771c7c1dc51dc867956f830a24485d39f37050231e5fcb01e0449c1a2c49b1e4c41481a94a700c177649de02bde910e27fa12161399dfc84bcac462f9142f5b20ac5b29a50cfa442a04ba756843f75e50becbe095baebe4b075a1e2dedb473f07352a268f32e322013f4ac1caec9e9796843fbf014747798d3180c661dd459b583abc0c72ffa2403f583034288187c83ee4262581cfb45a1e6eff1c18333dc76a1b123518cfc9219879c90dc6413baf3b142cd965ecc06d11508dbb941cdb956c18dc67141a85ae572d5e9e7e78289497fb28733aa74b38115f72453178729f537c645adc677481290f448b568a5e7734bc7e30ed2fdb19473908e08ef89f0e4f7324e7f1c9dbb86166edac1bf0225e2ce3801a1ff61ed21c025793a38f267ec88b38328e908ff60cdcb51542c8d424ef440efdbd7eab13d3dd9c8ee930882250051dc5fdeeb70b2ac9eb2ef950bd21cbd697f544aa562c5bdc5e1dba3a89c20da006ab3577c6090257b5ab837564a2eb0faf6063c6d3c8dc238054fabd9e346ee26029deb5232d786cbcb997eb9f237288aab003a534f990912bad043603937329654630adb6c17aa52727e42b9e776e716128f077f3333990953b6d51d344adff581943dceb93a3455d0b577294bc41bec01d431489a681f505c165eef26738811023a4f2a564dd7112fdff8d491e168d15e8f3501af80b5d1e19542ba325cbacb755d2b8af9717eca17680e11b6e5bbbc04bd11a8ab82dda34442adbcf5f4268d1d0adccebda03c2d016e87eb329efce80ba8b5c80addcc018a646550acf33944ce4a8c0a3a99038ccccaa050f73980bb598976e5920d47792da8ba2a7cd5e94a0c0a583de184e8f67ebe25b5d8c4c9c1daa61cc4dd4a2529296f5dcf99226e21b98ab904fe9253dc4fe610150284f374145d57f6d9334f104b7a92c2783915f6d230b1f5a9a1bed31536ed668cea09d414b6979e37ad5864ea0e712b725889622f2d616fe61611e03ea358e44e46af0981ccf3b25a462220d52f354dedcecd5d3f03a2169f892820944e343bb2c9455ae24665a05401e39b6f7867f7a178a544c27a1c51888c447f7710021309b1bfd87fcac3a9ce0babc7d84fde08063f1a8fec58de1666fe375287476adf88e8d03a83309f524de2674618c0ed83842607ac8ddd1bdc5326ee942db97e00be6c4d95042884d9363ab6d3128274de8e879d587779b72e62bb96127844eaa1f1eabfd65292cdf32cd2ab58d76b040ed1b39d738a4f154fdd0ff34a2dc6a1cc05a078814b075386993addf53cb2284628838aead645c15605ada73443d9b002004d930980570187ca6d7f22dcdac215541e626cb6888e985c4e0487d8e2286a221a3dba8987177bbb57b52163203ce118a1ba0a546844fa000b36aace6391cc654bbc9427b45ab9a484d59b48a9a16ceb9f3a1f81479393486371a2e6703064575dce91a832a90fdd323954a60820eca2fa9b24497b94651915cdc36438809cec30630136b7514f7169a381f181c4f93c2dd854806273e69b96b74da636c1af04d7539f3d9206487aba6d63eb9d8ae9f70d8499cba1750f5d4e032e3b8593eb412a36e382715c64f066c874f6970150148bf9182abd3ad5ae846b1ed687ccda5cd9b96800ab09087052ed6927c1c444a5fbf287ace9521cf27cd561892629188c58425f26258a2ef57351e5cb1955a56500e640e0e2c6df48a5dcd24d071482bef17d1899fd9f5630b3c036568aec8d0f6169bab3b695ee320130083f9f82d6ef2201de83da92fff4a66424813cdb2031f3da18ae33a7dae28d83102a9a3f3ee06c46f1d44f83b7824e2201297183c742391ef22a5b2de7472c4a7d4c6f47b809f68723112eea94036ea46fa763d8dcde494fc82f2e0606f80e583749591a2012c1d5a04f4b26d545a89ef91098c5cc8c3ca2dc80be25d28405725a46c9d1aca405028b1073ef6d2f94ce8a7368451c9490afecad731e617e682989c002f0d1d848f63340ee072ceb5767fb370596f4c182cf6a836d63757370a8320ee74cddd8ad0cc46927e8f9e64fa6c1c4587df416f96d9798aae74089c3ec5f0d5075c18c3763dd516627cfbc9b2e8df870996cc7a99d3e5631b9c90b64589ed12f1cae303f8d52ca09da7c26865a5a6f66fff17bd45245a315f5df46e7aea209535e5e30643bbf9388dd477456aa2a00ad4951520f4d115801f0be5e62670a9ae6fbece34b9f6cf672ee60ee0ead44152a26a3fae43e9f4588daa9010d97c7e7b0bf3c0551b55029443d6c9c00135f0d7830a266522780fa1cb1b79ae7ea0e8b14f0b3592de045824b857558e5cff771bfc9ed31ffa6b159fdee0964a6b7a534b6b0b159793b1d0c9a9c9c24bce3e66eb8cfc4bce036ecec3fa88b40706299a6698f4bcda375b51ad53854a0fa537ef266b37e9f2a718818cb075aa6d7703b5cbd5458bb027c58d54ef05f187c1799177ba2f7ad38c5de65bb4ab96a161f6e46dba55fa8d2908fdecb5a622ec76821400a23fd771735cee9dbae0df327f24846f426b84857d027f7b4ce3b6d9994f29466127cf1f4eb852f1509791d416975cc5576f68b2eb0813cc6cf025f77fca11b365f6688aee19c7e856055fa5202cd01dd05447bd22088f42d0d06e808588bd35394526e023e74e1eaea32b282610c7bf4fa4fde324a374b4cde461b7709ce3560abd8592aee09afd0d3fb4efb25bd9ed5a9e2a1fbf9fdba4bef0af6d5f74db0c0dad119684f9471bb57e5bc0df284472af5bffdb98623d08c45fb29d7b905ec28e353409cd8fbfc3c5e4fede3a8f2af098c073ffe6156539dd7c65fcf1e12c3410a7b69a5a24bb20af60b6c707b49b1d4bbb89c50eaa91c077ebf3c5b5db1ddfeb78d4e993807f1a34f62e6d9a8f2e9a5307703304f2787700504a8f61b467cafe2e9c80e10e9e351242c40da681931aa1736100cb7196ee7802ed58efcdb7dd41c76a54f01c2d94b38880324dd9686a0e087aa8efc647adfb53b21cbe24ea0a625ee140d4762277ba9f4affdadb938622f6a288f3206ed7b1c76ca34e9a1bd000a2dd53a424b96558974dd1eb75991e051dcccbfa83e81a22bc40a3bbd472c7f623d0d60b2bac20c48e9aa52720def41c406505c0cd064784d4ad74339ecb926ae4736ac33d5bb5c0b0bf833f0eb4feed4409b5793b879ac4f9da3938101215fc84739c4a78e81adb26dcd612959e08f19fa68928b55d36a715156f08e084733ce8c686fef5281e8d9f97fd146aa81a5a45aee9a72c426c479f4820db4dfb9a3122447dd75a4c7f69679991247902e80793237fb42853bad956f94c7ae5f2edb4638fd7b510c6b962026e3567eb26543b35f9789aaa5002ae5f311cb1a5798d42ce1f1e9d7a4d77da7705bcced0fb37af384891a470d2a63b2886cafd7d0930148e77e1200c207264232e931cc4935afe50e61bc5f8d38c09d1d85ef1994803de4517fd0601e40dfbf91030bc49e8fb818036d03ea29bd0135d20b90ca8d39077286dc7f90851a79b598cd5d5dc711f95496864ba1418173c3f0eb74a8cc7e130896e7b99b03f705f4ff8b4fdeb26c31f59806cdcc4f09d3084044554d1b052c0691e9ba0c6c1b4bfef820ce8d3f4e81411cf9d89e47575a8247302fde9bd864c628585df5918f07b270d202a938c58d1c4f70591d6887ed0c4b07916c66c71019e86dfd08831d08593892a41db5499d17345dde2f39aacf439213882ae02dbef297dc7fbb94ccda52a80ad4d5ddccf2dc73997545b6c39b1f29a105e5e10cccc06d5ff79954632ac034cc395db74b5271e0f1184209474cb4155c88a8d409082df84124cb5ce45af10656ef70b8cd49a67c5ce923ad14983ac3ab530a710638c66434738e03299f25be424011d4e2dfa94a71b63270f9bc0db460325e402953f1517ab84eb0ab628451eb0a474d4bd8d755298d670e14699611b4ae1454a0485728536c6ee46aae4bdc678e7c2f59e23fed3a6c86f87874045d571135e438fc35bbe4a12504ac96ed4309d64ddc2d021b80eca9b2c676bf9de37164d61ed2ff9ba205fb23d285d59ad5d0632b8e0c45942c17430e20559ffa7d8d69a9a5a46ab4506d0ac0147fb1e53431788e8dbbb44e9df60394c6e47c624a28ed0c1da21f97549357b16d4c998457170f0e82628b4f1733696143322f196f7d3f978825f3a0b14478e1c94de9cd0d961e82478223a705421a3a5d47ed49a0e8a57025e6c765bbd8d2cb193acd09492e6042cde10aaa1b6024e226123908891b69ac0f24205934c1bcac72d011103744640462dac81c4ab7404de9170d629d1edd2a95801a962c4d7934f10c127abcd8c21c91217ecf666a99f4f8b9bc421d4a8832618a7a1b503557f67add8e4edb6f8155d174ae0848c8800d4e411d847ab6b084f4b49daffeb5f9c7af769a887245ea40b55bb2e3be2c643771686fc5f6ca27f4a5af6b7a091f7890d36c0ac8761e8bc047864dd09cc59abf21c638f29ee78088d75de7979b759d3800363d57fd7a01a939974a912d71d2834f8c9ca82b942d0fca5d849fa3e9cf23c12611a355fa22ff3397a44a9e86b1ca6d5281d6a67f4a7bd706a7939c17cfa753910e99b730ea713ae976c5701ecabcf3323d01c0a6fc1e85e60fc82fcd4d9b4779eb4464e9f33914289d3dc5521d1f331025d7f3d1ab5f88c2ee2ec72b274583d01fa3dadbf73d4fcbd3beb3e38fa7b962748761c07d104a4a62ac9fb2442fb691d83ada38153f65164de908146378ac870ed904ab8aa0f6f6608293dea14f68a10288766f70accaaa3d66ad4ec19ff266b4427a240bca7d17b27e74f9bbd272930f9ac40aa9ddf0ad088fe16fc3f41196c10d4563c53991a27453b4aea9d1eeb37d52fe06ac2328e6382daf5c60610638b0032159d5caec79adf47e861cb439d60c8231cf0ea05edc48382dc62a836c49c24b34a285cc5783613501101f630d5b545d679918955cec7aa40cafccf208044306029cbf03bbeb2c9b7dfe2aa3102a9dde11859c45a5daea4f2c6cfc506b9e63319cb90c4c90c58de303071196b7d363f9bc82e6c4c5882bc661b472fce6c836c3cb6952192a3fe74aa3f0e516aa24655c579e052b9b45dc74da903df117e411ce32b2c94cfcfb11249990ba9e4312fd84f8e114ba6c184ac91ea88b692d09c01401802ced0c6b2718d83c95ca4f38fd4f00c08f8b8e30f019c2799df2b953995fd32a314d6ae03395ddbc732c60c7d66f428297e5d802dec21a26d4e9881016d34ab9b28ed0012e3302c10a9902f2d70d0e6b4ff4ff8beaace306931d093f5cad0cd0a1657a5331484e71fda0bf5d5287b0424f25187a7b0514ecdf6699ec5f85147475e4993eadf79ffb8016c9e1ef16362041c063dd373590d14caf8fb191410e62c23bc4b6cb04f3105d4c70858b2a41cfa0128b3c5899822cb79d047b1687834dd053a242d1687d2854f8e9273f8ced39e82d10d9c0d10399ff4d7aaa75d8f08a38c108a80731d31b792108ac21e09396d5dcf1f9606bff035113687fcbc695ba3d19110d3e9fe9307600126e0e0000e166ce053633df90058e0337d882c84ce184cfc8b9b1c6385556258e1de4031c653609f20949136ec5d639b1da4d100bb5c48d4f42b17be11bd3559aa1146c16cd2e160d42354eb84c150c1839708c646b064e06e04d085e3261e23c9fd105ac363000bc60441416a299970701609e3b541b6e5045b107b3619c4f496a725b6112e0cd5c4d6ea1fd55f9585981df823acf7939d974c98097d9608abefff5d29fe1c88519b20c652780b632683313624f1f36ef5575cdf6dab54cf6afa1b1301d5c0f8c0bc2196142b04c115748494c20d2c702d84c80d51c4560d0aa5f7b1bb10deba8833a06540f547582209eb3643c356a914661d811667cf2aef048b29ddb45a09a8eb2e6e12968b9bcf40d8bae67d89df437d41a0d063d912dafd639fb7c393fbb0bbcaf1720c09821c9316f36a36d0f0a663bb546dd98e51dd4ed65cbf240064737662d252742b994fcef1bac11060e9b1c290b8570915c623ffd5506cbf02b1e8671323ee0d7b0764d906442a240c7e6bbc147dde0c391e2147dc9cc9a916006d545daa04109458fd3e39803a85ef6ecb8b98982f7d3cd52cb34b0aafea94c562ce26e04b53006a7ad2f0c1937eb7d065eef0fcad34ff6ae80dce7c0e72cf471d69565fe5eebe158a1e95d8d6160abc4782328e5e9ca8438aaf43843dd70690b44f02e5bc894cecdcf8c4dcaffd72dc6287285aba75e03adccd9ccfea2782c302f64be619a09af731410f72614b85407f9bc9f1ac5728f8a38c8fa30bc1f33bc05476c1d4355e235f9b198760f0174120c32e400ea5a9bae1044194e79f42bc9dc6282d2e8e503f4afff1d222d027d2e80386c9521a4737d9f19d5857926cd8f45f99b84e3d5ac10883664f674bfd5d3bed864f302e2e503f3b038ae0974c2707c42fef15a8ac56aaef21b2cbcfa5658b2aef3ba27c27df53e9742cefbc741f42f3c644318201bae7540184792af7e95e87f6b27a94631543330ffb8cc2a062a366a7ade77c166490ae2e0206073933d7ec2a9dbf4e15be9e5a5a9ccab5d2c6cccfdc984f89970ed49577a9669247fde50920b31aaea972d25a7849b893cf16a0ef41b7c8977a2aa18e7588bb5242327db9008d12f5a2a593e14616c0a634fe6731741c04ace10c3754acdc06faa11d3a61381fcfdca365e41eaa37f516550589e91fb1fa34a7f591fc42270d1f85d3e69f297d5a76795436ecf6e9a8968ec686b575e703e3ea8fe10164f7bf587dc96c6f50a4a71efc3c9e070b0e98ef6a8c512edc3071ebe8d16fd89781ae45e010cf24eee2958dc61fa5c30699af775235f552f849676978bfbee5c752242596185527380f1cd40e18c5bef5e599aa335ee04d6c77ddaec1fdc1217c5c127374b3b69c139fa19b83c7998edb182dda062e757e5be1a2b6cb6d36b44968cf1dca232a3658fd299216342724067762365cff57e14bc4d6470e5cc75ec7a09fbaa49be4dd76f3f9626dd92b3755a2d11eae9089a8733630ab481a9e3d461943bbf1bcb5dc750292c1b5ec0f6113bfc9baa8d258a6c03bd29536f62691fa4a200ca6e24767e9dd9b1ad5aca15a251c57f7d615d0ebc2a8943d256095a28835d16ad3d6e60f26411beadd4ce208558abdd529f4c7ef29ac36f8e0726e5050ab329d6b041ee84db56cd83b9b95191aeaeb2a3592edf681721d5c45f001295e8a89e9446e78ddbe23f5f15bfbfdf1df1bf63dbeeb431c906279563457126b2a7481c6b7ba0fb4f8d8e30ebeb88e82f2b350d47f1bd243cb4bbe416b786ef6d193922cd16c5548f5b3b2d5acb3245019201f383d3047974cbc8f3160fa7e107f0cd0177ea67a79ffffd0e25e42138a03e0924702976104cca260c79f78c17a53800b2ca0c6603f2841649a411ceb6adabd228ea97622a2c3ad195cf9e8c029d218e3f3731bd741c018807db42b59a44885b40772b0488cfb0f3712803bec5f1afee58f6237aa9904c4abfdd09d24c22889d924754512b6e435b4ec22753ac1fbcbc8692001bbf386268436ba12afc57710584f4420e4dbc04a2716c5adf49a14a1dbe201107b737c88a07b91b0ceabdcdd53676d5104c3203a71eb93ba402e26a8764bb4bf069d957c795c62751614a91dd2003fc8f5b0af86174049e2867867c0ac114f1e40ade705abcdfc8e4e1458719d1e199c2f184527313cb0da23ec01afa53a2006f37ccaa3d50acfd8e8416aa705d49e058b2b82da0d7bcb3e0420c824d8136306e6b0301e501f00987c21c1b9173ab946e931638c9d56a794cc0b3351da7a604ef8d4b9bc3e8c2a1b004cbd60f58f72b0163e43b16a9b00e6eef7b3c3fd2b886ace554ee41ed11e31fb3816d1ab1a07165855057baaf5aa13f38d629f88b5d0901eca002e4d80357814a437357e0468924dae70ac5293bc734790a18c7910041f99a8efd39f673198574bed10744bfbc0ea5471f8c1efffb4556db3fb618d91d041eeb057f1d436456205315eb75469365dcfe1fdc22009c15d8515306c4fa61db008c9ffd70aebe56504c6c38c8414f00ff9a0084ae8f928824609b259beda0bad48d33b408adc1a10a51175919fbcbbe057ce4041c7be2c0bcaf9d772ae05b48523a962cb06070210d94a0b2822f3fb030e21a08226702a2870a9cc637f8c2b26660cb699dec04f55ae4b8814a27825f01dd3e8134d082f07371799664842184afea0442f00927eb038ad4018e760c4f0b746e19d21ef70a8400d8fc89230e151207de4e0c7d5b32fee2f775c3063238dc205b52c208a860d296bbcc2d67d03eab7e588b25f271c87e37a6bea86e05026acee08d694655c62f6c043209a868c632602271383ea3e6952b08c0ce28ec010f2d62ea4de050004451e460cd4c4121cc09caf28ecffb41d74ff0bd3ee48ea25d71748504112df0bc35a1d6e9d9382c069e63c60d88289e837638596f57fa22da3e7ce906fb85202efef24356e0ad964f6b0be5b2452678af4e6fea45d28b4f8e138c77d1f4321ede96b3bd0f21223a1520d7b74b1ac60c4d1cdde05d054e47f8e01d2e44d0e8ce86ae1bcc030af4d554eafca9cb325fb2af676aa3d125db7b4c5571daf742bc7f80be3b8ca1474324332a4db35cbf6ccc7bfef3a1a7c51a38bd02f494230ff4baf7a6693f9aedb5bd1ea6f80d9ce8a17b35f51b079bff1e76291297ac81bd95aa39a708940fd4ec86f2afbb97b4dcb4baecb008dfeabf9ca52f1b4dda758fe388ac68f6589f2b6dad34554db2b74860ec5526bed67b95dbdf3e8ff87a953c324a24efa7870cf3d73025020270207bf7c4be9bc8a09e3130473def0563ea4353304c5d4c3383458a0100fddca114f74e378fa06145f5faec7c772e9fb9c1e78cef7cfff22e844b02ae7457b07691fa49cd106e03b1b42956ffb91929e3d0f00f2e2dd6ed5db63974e18ae3cd459d9268d4e20bc9edfa95124920783bb1d06c5d35c8191e6d455ff0a3a90d309c7c25c9af449d5ba745fa10609621a9817393973bf4b4ea75958faa39eb0715c35a2a3de6d6976a82a841ef45afdc1f24b06a6003e0b3a7da9e5c1b87f054aa09efbad578037dbdfdd7d2133abe925be59f9e7fd683be7362ca46e37923f00bc8768110ad85c387c76ecebd8875e97ec47421e6093631d09ba734fecceb8af3614fe20eb778d06cf6bf4ec1bc1f02dd391ce80fa96cd7877c0f501bdccfe8487ebc87788acc1445bd3204323f871f3fe0a28f130fdce254200b3f1a36d71a1b6147143f4a430517688f82f7f4e40fbe7bd29f864964b3017ffed1bf2f69766afc7aa372dea1c021aacb624dc06b12227e0cf1600802573ffa4e113e27fc16569bce7616da9da64681e4acf896d45eab051c55cb3de88c2cb8e9d6282e5dee3fd25c88caf75eaaed1ffe75a7682d4b3558aad1a1520d7dea7ec1c1b879e099c0e5a185628abdc28035ff34867a855011efba09fe7ec1e080aa2ffd5d9f194a16552e7c57a9605076d50b79975130283755b4f25df301368536e93dd48669b2aa2e80a990952cf42a7ac1366c946a5bdd02671a495eda21a8dd1bd54c67042028a4ffdd5ec2e3a2d943d0cc8cf2cda0d608090898c939a828457a5ca01774cb12a1963c265f8777caa62b39042c8176de6d63ca2903c0b6982f45375a44f85fd942077aaa7564584259b29ace640b53609d67e0306e757699d01b30ddc2cbb6323427db79cd07c9dd2fa0ec56820e00d1012a2953ebaa1e751ddd44e378c2ce97cc6281162e64a8be58d6c59ff8bb73e862a6be3165103c5c1a845825b93f1921ab432ab021d3e095307f8b836ae77ad62e8d090134a48f2b3edb08f891c2c8ac3ce18492006d6e84cb725f1164b5827fd6caace9b0150f1462d25939f0e075439df17b71f53bb5036e0ff77547a3ac59f3da65ebfb673b8662e88a2722511fbcca8e191896f5804562b1b7e383952858b71ade6407b42ff952f9d7cc09411e84b1f2ab9a3c634cfd258366146ef8a883208307b6091aa6de65c7f5607b780093bb429429694e34ee6fb8bf64b1ab45274a1bd1b5a11615b20a4b3c175cdec3a376f4c04bdd7213b9ebfd14192fa8cdcbd7a3ea0d67c706d650558684fbc0284ad0d5566a75b2b315ade236f36c82709b561e59ffa63f6808137d157fa0ae4754ad479947fd47510a4972bbc5523b73c47b8f505d24eb84e77856cade1702aef08b9d115b99f1246d271f1a2abcfc6c0af38b51a7779c500d74353352c98e825e3c710b8c5ec26940e8ed90de734da7abe28fd5b132874c107198c9decee9626a57083a30d822218f9b781c3395761b903be931b19132748c4372b1ccdc487d56629fd775baa481a7aed10005d5b1f63a023854295e80dbeee46a99df02216fb6a22142622d363292953843f5c5eaca138205bd5a4654c37bac8865ba2584b4b79ecbcf456b6132120425d71650c2324532b982a988e5361c95e114dbf0a8897a312296fbdbd10762e466e0c8f45623356ee903c45420ba12d390ab8780f76c14b08c64839bdf1f9caea41756170bc82022834ffff51cb1fda1e00942a8dce1ecabfb7bfb4785795e5c05f4f1d1a9a144e404683d85653b758200b984a151aa9339324d48b5c3dd1588bab30e335e9cc0bddc6f27f226ec17aa8e9b92cf9ce5999b46ed016f40124f8ae8aef2b85f93ba8cccefb7b1e7742967abc134e5774fff2477b997b570d74571037b490ccdd4d1110ff28401731b2ffb680d28f80a0606ad8aee68c7d3c60ae79a015d5a1aae5df92c74f45211a1fc8070411e78df07ed0e9c27f4e98f9833df9d906d7f2f150391639cef8a3994f0e968096395d9f00e1a2f4f358bbe7ed22d657ea8fd9da9c5edf21440480b606840ded9be0b0d5a4db98dd9d650381c2f1147fffda3d51e13c124e4dae72d5b59c9f4456148e026157f4516f95dc1261488878fabfe755a65c644f11b78018bbbc429fe1a79d861051fe5aa38fd7fec7e653e1728cf6ce847b8643c770853f19741fca90de277315a9a23ea1843d6c44aba2b46d67de4fa5ec1b037fe37d59586305b1835ec0414f4f1c867778154831f8311806b2b0d29923e96a77d19284fbf5518976084b19f54ffde40508fc8a4ea41f27d6817defe93125e9d3035ad687f8f460b5b9dd0616885a4eb540f02792f93b2cf547646315633c8427b9b80401b807da220a671fe0aa22da59bac88d90a7e8a513c9f809e80c84338faeb66b52ea111be9daea01168c49af7fd67691055f1ab15c384d0104715430f32ba7ce080f5bfa05509a95d0328738b1dfd9dc6771515c19a3f4db04f9a24911081e722198753a510133a3c05ce5e30ba983ea7585905940c8202938bc0c6835127306b394f6fcf1e5836b1d9d6eba112badd6f08db63c7a706932098e312f4f200695bad3751011e8b5dfe0053fae8ff2d5c354f62ddae0bfe2158813dfbd4029c2f5fceb959824e2c7ab18f513e7f293a023c162119761e2cc1520075b2f7be552faaa70c1bd6318f3bd6cb6fa64224671ed6acdcb2e996661373b44f065350d0a2e56fea12bc6cfe326ccc1b64513f117fa47e0ca8c583eb25bac76dd47801cafa753f195dd99408ea4dc582564b228fc505ad6cadb22bd22fb5ad412163039aaafebbf07b00c8798034aa81159635892c737653b082b9d2a723dcd4b5260f0d43c41df65bef89c507b2fc3764eaae98e0bf6000cb36f503c8a584d84b7d8c42eff3d05d8331a3126846b390f198d249139448f86f07e50dba15ca7ea3a86622f4408d191cf0dc4f854fd1b52c50c5128c41ee071e37f714eddd1450a64ff8fc6988dbc3b848c4a8684d02b12d87e0d0d3185344e053c89f974a4694c47d54d76cc352d74f86502ff871974f3bbfc27cfc3aa303161ee9a84880953b275a7a759cb7ec9155ede82be28c3f182dd9cdbdf315969d52b6eeefa3a27336c255951e4ebf8556a04b4322eb431a3afc0ccd49508551c24d43a7e1f3895aa0d8e9d72c15da0f8103dfa1f9fb1bedf88d1bc1d7d3f34bcda9f41e26d44b864cc0c2895525f362cd7257d11aae8680d33f56cc98adc548b47bba82430f7d01bd0a50ad9bc9d586eee2ef3620f95f3f37d7e8cac4ccdc17f7e91486dd5018ad3f2257dc475594eb5e8dce5c5408195d052b4d92fae966fafd851e458143ba19bc4e5d9cd3ccd3fc89894b62d96e0de96b8000b760de82d7733c8bfa25f4f5ae0373cf9f8eb2fea7903be204931727066744d0f933e2b24a5e48c262367379d49000b65c968560233a851a4208bf2f89a2d7e11a37478f588c26a1072a446c2d7f46e6700aed9cb9a358505b60d88963dced832ef99dd989eb43cb4a5a69b00b49e02e00a618c63349140041a62acdea08c18c214f9dce10243d69850e5eacefeeca4e4f87396b6aac652b748d72064faa23c275d668c018a685b44e12637cc7f90c2ad8f3236c70ade26fc4310d4435d7514bc708e1e54757325d7d8aca953c0b6a7b2eac1e91152480bd3d3943dd95f636483302483fa22c4e691279907af25fcc833e1688731da7a85a8bcf2c7090a236b755cbb7085f446214e6b36c02d1b9a228769ec69b85f69f8a525e90a7f9b94aeb73d7834b2cf0f84fcc801078361d228138ac043d59e54c240c269d16584950f60a08ff46ccbb1b128cce20305d8a334d2a4a65b3e6d6e646a90c0d4c4abd7f5516f0c0f5fbd7caf1435063799d972904554fff0f5c7aa9ea8127fae8be4b3c112bd8a34ffb2547310d00466f706e10f1cc4b050260d320d1bda3f8124149a0a96243261a6cf78196aab0f7b08b536c16eb65ae7c7f97ada7c938aedd41a0d52b06224b348f505606e53179a1dd9bc540fecff4032f7d7102ec0fe345bcd7229611654587ed28f09bbb5dc032aa1526347af6eae3d9aeac9835213132ea3e3e71b86100aab66b817c7cf7b921560b354514aab3ebed138d8180fc004557a97be9015ac00ed49c3437f9019daef2e95cdc99133519b845ddaec5a02cc0d6e994ad5b33d50d393f0fe505808140852e8a6af9ebfd1db2d8f074ad29180305f87083f9e3fb77ededa663ae9531ce44074bea3e3bdf23d635a0102a5df6415caf4e1d780e3f2f2c0c1ff3494a1102458aa9c58979b554c3a71471f4d0d80c408468633d02c7f645716aa5d1959f1aaa51fbd377584f0daa34f2c6415bae4c2ed42a748b78f5ebc08a905a8a6ea5d1d7d791f236dd456cf52c8d30bd136435afa94df4689094ba1663622d6f737c1ac5202b315143175a7b26ba1dcdbb276246d10659255848206d10a3d87664fb557821a1515b368efe5b2263500d8895464677d7864e4d51b0d3a26821d214f94fb027aad69b1b35159600de727f7846f7a2ff3993d9386b56fa38a8cda76d909d45686216d8535e544bb8d3e6e9597d12ea42db5481b8b925e322359f1717dd83a2d6cebe47a94ad52e3ad9d322c69487e8b444020eb8f3a96e1daddef17b5ae478711141b3120c994371518ac72f9dd302e1395c5ee75b641eb05ec81b84e74d0829c202616050fe320d6f31d53b0fc9969912cc9c205a17fbb0c502e1245f91bfa91d0b0a1b0b042a2049240c6f0356f88fdf56efd83f4b32155d66943f92a8f5d1383a159f3ee1482232f1da96e83c77deec7e7947fa771f9596029c118dde3f9f76b3fe12ff52c5adca917cd59a9b95577e3e132750644803baa609ed0cc25122a20c14531d5cae26ff8c9c0b2a0772401563d5218725924e8b27a081814dc80bbc88a414d2546cd768fc937e857224e3b7d00fb22d82c788bd8998b8f79877c225ee130bcd6fd231fe95bdb48c883332a4284ae83483b2f83299de1780da1abcf41c8884d21667371b046eb4e98109b23eaf2715fbc2d206ff444fa664609340c28131627d622484d4f2824d9eb0534f63706043f1f03f66969121c524c269ee5c3f1970a86f61cec1e69d561c6add3182a252d333924c06ed692e22988e9adc9253e19f8ccc2ac2416c1f0fc947435083d20bb44f3572388405a1335403245a03249bf73b5419100278f563b92674a81462ee0251e942f60671bcf733733064733606070cd9b69ce6f35b785c480c341841251c33e2379a75c6e4fda1d91964c53fb1a1b68bddccee1f266e2c5394385d9c4baf082a281d304863410c3ef16cdcd45f20a6479df3f1bac6024e42a10316e1e58f14fdf56c274fb99fc20f0c1129d9a8670534cb271cadfc0fef4a7ee4eb1fe46e50ff4730050d689249336a46572156d09922a23b5fc30d2f1469d9dd9d33080455665b330d9a9f71bc7a7eb21bb191aa3f3eefd0537754dcde2d0ec4a33fdc75cbefedac2190d502db251bdcdc9b73dcbf9e76a54fe4cbfb707691c92bb2681fd5ef0c22899335eff3d41b0c0463f25dbd0faadfc31258de222b7d70570bc73bf828aee036cb2ac783ad29076daeabfe694ae8812aaa522db506ce3da6debb5b78ddcf357cb72ac488b47b517edb7acc80f8cce616d4c4c25b125e6a21ee741d2ade25fe7837e475a32669b30400c560c3f0cc412a78052f5f82d1d1eb1bdf5eda234ab55f26c7ec586f93c95154039e4eb248f0d8e3b72e1cf5ba208d01a4305c929d8b6b420a9a64542a39442cbb2990856e9e8b61669bf196eb528f9cb8ceb1a7d17021dd34eb15b9541e290609c44e436b99faa512b6cf606f6225e6ffab94db941818f03106f200d426ded1512a1d6b8d814337e8c636ed341e813e5507de6fb8e7508c9e2f8290de405734b3af784298853d9b07cbf098f32f153c70db2a14464c4854aadc45641c744f322d0a4d02bf608d7469fc88e0585acd1b3691f2887cc02840f8217016e9cc87bc205c113a689cbc641b8ce797c32153de0ec7ad75a0e89f251fbd415fcebf7e88a39166ed834e141c34dc861e06c3447484f8f51fbe67cf46a80a6e73a4301504aa7872ab32139d9536a9c1f8e0c584121b2e200f30ea6739813f008904035976f26bbad8af4e090258cdc7aefdf712b207ad71de30c07d127bdc8dd9cf713ef009834628a1c57d510209122f0e2f41199e01a6543e58d6416e8c49f768ffe8223ec834de81bfab71db97c01922025bcb7e1cd3fea60e752d25b4879cbbbe5a6dfa00e32adbc043da5c62a6c1e810f7072d7b42877a81e354b6b4bf13e65ecc804e507b41eacfce632feab55774216b9bdd40445eaf151c457a924e5449daa02640dcff2699c13bf9a96901c0c84c82e1224976475619999179a253f823a06ca6b8fe11f9445d4c995a7e82bede49db3422cbe2bf9c7e81154f07767b193a41908f079432d409f8ccfc964a49d9fda188de07244aa9627807332a6b99bce9e95f700553fbabb053a09f11ab3c1d073c6ae4f4b2da197e6a1754d0381bd9a5d4d4fe45b3d555d4012089aeb442fcf05b978c65f8aeddc6d82a879cc2cd24a7d5aa45742f635be976874fcfb62aa4ede08b7a27b75a3b1768bca69e8fcb1683fb083e069a48e369840199025b737408d365c45645e40f6bf8a3f39d09c758743ba5dc4916431758d7b468d451686402d9a81a6a602c18911732069ab8b2f22d0988bf0ba9a33f728cf3112abff76aea06e2ec4b744bed12d1e50b099583c89aa5941c38c4203a54ff10903cec4f18aa84884402f82cdb473283f91bc5e60f7bd319db11ffc74c4bd3e31b6149be4d12309420e967814f4fb3f5daab019a58ecf89244bbdc20b131c2468e7dfa2a33f7436eff65a8ca751bd3dad736ff04ad0e9fd8ca6dcce3d46c9ac185d7cf8cfb397c4888f043f2cc48260a190bf1e5a6d75ecf5b3f5a31e5b67a34fde1297a8ea7a3a9644d79b98ba856e2d9a9f3e9f62a1f144b022f995242a243cea676e0c1e20553c7af32bc847d0a4376811d688a8effd19f15deef6473dad972884460fab3a1454f4bc82305e80261fb92752baf4971dd19b49de6082e32602183902ad2975d63885a2a40010114a8c999fcc934724e7ec68117872c18c60e60d9e3a15911537a47b5520ea6a1915d0f2b73583ec805bbff55a38422c85d2c1f70951cb793b76412a8390f1478c98e24ee1861bbe9a48907143972a0a85f48e6ce0bb729f4647429869ff33003f649638e700988613419a95e5f48f4e553e8b811e79001c0bbc73c2d291beef22c48a3ba271c604d55a5874fc1b98ebc42f029c04f3d6b6a7223bd2a50bcbb470c6c7541f50c69de850413ba77a7fcd6cf539371277d38b4ef6374aac44e46658e3aca566394ff7e2733e6b6656cb025b71bd694d236a0516e7b3dc12dcde00696df3eca092fbbe3123b9747c93eb179d925bf536ae37c009f6e021281fb2303a168d2115686ee366004201ed70e2405af6d00ee2b0c1065118684297e24119130ae6df79671a71e5e101264114c407e157f3a7e30bfe2a89b4ae619ca3e10de20c843316111df289d508400d1d550fcaf2501da33bb43adce25c7154c4a843c582c29b0dbb9a863c35a94651acb822edd59b1f8bd09ed5778880374cf56999150e204ad46f7f4ee903d6f548dbe00177afbaacb8255664bfcf4c9d12148bbe14fd65070bfde24b1f1434facc9fae1593faccf316e5d3f75693fe51620a249db1f6e191ef0eef924980e94fecbc6a32f9f66f3cf510d66d8b0e531876799eda5d40eca97e9858e2637d258805de1e43b69f0ada8d46a29eccf0da07e517624705d085cc35da3a92d6d489d04af97e2d748793a19fead36dc40124763b8469520b36e9c6d8fd9186d24e6acc52bb791c25e8be971989ba84408437b0a2f0577fd27ecfdb38af4e67f9144e27e6f9aff7f9e282f0b5188014976934a655a1e2307b2edc3d81e74a8fcfe617279e6bb0a3e8526856270efbf2a781b0dd96e3a1d471bc4519c60109e5d216250dbf9905e7537eaf9acf8f71afa86535e08e511179f3ba0024e2305921d855d9295dd3df55ea384f678a55da3779a00d31d262434f2f4b682a33d0c319472dbf56e9abebfedcffb06233e689f9d0f014f0e885bb767a2b8536fb5d85a92b924e01c2cfca852212bd0bddb1ae3528b3c4175a02c590e484bbc8467e500cf6051a0bcbb315f0902cca3f4942a33e2a026a522234fbe3ca064d539d617da5ff1f1a59f15ea18a386159ea1d4668c93d58f45a4185b57ac191adea4ccd4f4378516276c84ecc59395461e0b740e6fb4325384a04a50f48fcd15dd1f69e0244851dba66db4120e14a3b3089281b13433f57abe60137e0c9fc6c52043f9bd76b90086aeb4dbe7b850b750b6bb53ba8e1e71a052b7f08da5b816b91d9122a9abbf2da561d5f3e6209f26915f60c1190af367478b2fe33735775bace397878f302c3742ace2dcee927901cb09d1123cb7372f419ba7970809ea25c6979607d37d402e64485843c47cbf5c952185f33be943d0701a9d7a0a1b3a665bf9570d3d4c7d372f5bc9443714dec33ecc847a1da94dd54bfb9ca4be2c1f195122d0cc042de68739e9fcfe2d045770800829006da2e1041b09f8fb11f577f10297a6bf55584f48dc777ce34a415d1d01f0e264499a0880634f5b1aee639208017038c4765b6a0da523c217d4a6b3bc59fb99818dc5d73f576583fe6fd6300af76371115c07fe007808732198eab5e66190cfcebb22340e64d1bcb555bb2ad0305a2e6f97535d1b7bc2149895044d2332714a09289a698849876ef651b181bd3a42b7b20c7a4c72fb65a8b933458822c85b141234d1f591489a4a7ab9e3c0b4312257ac29eec498ced58894ed8b4e11fabfd5339dfd9e952de5e7de42234654438f3a2830ae8a3029db266f50420b654a228e1c4bbf38fe863bc6d98402760eda3f949441387086947b15b05aecd7a24c33bf2d24e57e5db58bf89c1820ba54cd73919c28fa28246ce977178f818903f31c585e8490f09c483fcb962dd9c4c7203472f7664b1e6892e3b12b7ee06cc9bb18e0b53ad2ece96bbb9eab356cb3dfcf8ac35aed3b0dad5ade14a34c80c53218ccffb769bae236dd8d1199c7ec7511a493615f3e6b4d68f9aea112b5f97e437714dc555f03b084070c744a9b8316e3f2c1d5615de61a7f219559cc7fe17d58206ed80ea9b57340be35960c2c50b25d6ac1c35f579fb2b506c91a8acf130b7db23626b4c2a3ef959e7d691d5c9eba7de5ba65bf4f0cac19165365ea57e921e6ac30fb1fa381667f2d526a38667cdc94155b1f994fdc6662b2a4f3e11b1a4a4e256e819e3afa069824df70a003e161ea426e868b7f49fff155ef02531b13d39907bc000dbc72806374a8ee4e6838baeb39f4aeab34645e838cbef0189c5f97c6f68305b59f2dc7f4e126141ee36b9eee6da581df7a7f840b92b9c171c6e11747f8f2ec31799debd884c75856865c5ef7f75cfd9eb31ec7af0df66a1857eaea47f80973c025c4e456a0845bb004e87370bd76dc530b8de7dd85c862bccd7e730aa94ee87f5c825e93faf2b1d9a4e1dda35079a6b6a763748735cfa19057d94b664c46438d3c5899520d9c6d3e17734840724376ec7809e4270fe686803ea53b143789f9f21fe6aada26aaa3e136c23167a7027a04a9daafc6e7b62a1b07d2bc08d12781e5f83f6cda491f5aa31c23dba13f6273a97b6f780955dcc43d8dc6d23279c337d3562bd250d61b168969b1ecd1f2f90add949a96345c541bcdf1ed967a3b381dcbdfbc336d8dd35f14aed206300f5eb50e65be2a77c1c38b791e6e0839452ba0805231b5df19166df0b99e2e5f8ed5857429e433864ddb549f1725e9110ab1e0737a62ab476d153bf3174d3d2505f1567d2b5b0ea20dfa332893dec5881655ebe236b199de5ab4f412f92d192b0e352a4c211b0f23c2a17a8300c947fb2404fd65db5fd68173b43c99077bfe45bed7c7736b3943017a314369fe560322ec9210c1ee710fb3f8bc597b6819a64858b49f503174eb37065ee96a684ab58dcb7ce4562c80d2c20c78ec256431fa245c30981911ffeb78d659312172e73d17f4397c0a83167f1ee526cd5d12d2d48ffde9d94803956597a386eac8d66c4389797f7f93c53f5d9f4c7c71dc2693aba15d5b95d0ad4816e4304f1c6439b59cadca720fa365ca897bd73a4e133653d93332a110ca1d68b58dc98d26561b362c11dbcec605de270e6889f95b4efa159b8b1edf8982c2661eb11c77a2d1a0380a85fe761d053c8a894028374fe60094cd18a869d87f9903b81824596ef0f920871bb9e1a60c7ee860beb741a1d36b189dadc644bd0a0f95cde7c45e51023580f81e4245cbfc28b23968da0937696ba007e8cf7e404173e6882d027af461ce72092301dc90e71123dc5c70d24299de0cf7b26d3b38a36c6adf0a165f0653bea90f8ceb27e02a43cd541ca43371cf6308bc1b9d8bcc0b2f7889aabeaf138a610a6833a8245fdffdd656418d0327f5c322a3425b6a5c2e1fe1a8189e90e3f02ebf9a77f348b03a6235eef5443e8942df8d3f63ea33afc37d2784167613abea4fb1465e8aaab51f71c51d35cd726a5d5117784c314695aebccf967bd185747154ce9557b457969f490deca1a805117365c47f75a3e4cab87bd1b00064ce7c2d975f9768a545bf77691152c4aa4bb6bfee0eddd98e790d7ba913738e99726cf2f09b8495e3531cded6d96d5119f5da971579f4dda5102c27e563d174d0f12a098139f7c469a135f4b790f350c4036fb67e6224cdcccfb2e9ff1a41f939cc68dc4c0b24070c92a7b8c42a2ab216107c94e621a2c74d3059e3409403ca2c737acbb0d29fefc69c9ff99e06a12ad9888928e4a2a353855d4e171f0b3816973812edbc6f7cceb6ec2a63628dcb2409d5fc385c40e9e01dd1ba6b10935222e416665957baa754260ded401f2c0e11b69cd174799a2c5090358413fdc14ddc0bac60219001fa97322f5b1bbad5a7e1caa8220fec2b222bcf1aefd143e52ed8b353152322891fd153c8d8b6bc8d7e45e49dc89f0df7de23110fcd8b99b28f4edd1497b5d679c769f0fa8bd3b8cb92a035d6788d1dc8ff7f72454812fa6219192a4818b7420e2361e0caf4493aa2bc1b751847d818a08e3f59b29940cbba99a4e6fce60252c70bed88724dee30da6ebd9c4795076d17fc94e8f61a62d300867ef4a76cfe433c1087e66a4a1bbb81d1ad209b579c442db285c11a1e89c66604012f0f6e7e142b5942a5b40909b38805d1b3c762327012a89444cfd78000201753625c86f108f5d180218827ccc1e6bdd402fa96e80a32bfb2b720b85ddb168384d38b36bdb31044fc9119dda0482bf2a575878d8989e3f4a1dc93b693c754cc5b27e065bdca5deb7c254f83b21fcbe680f0e817bd9a800c7c150603f59acbf7aa5c76a3c7f0053cc7889868dc4aa5d54fa216067671fefc11fa04511ab513d65f2db00605248b974250806022f163d1e3bf385a80ed5d2c60a8f2814662a1300adf85ca39dfbba1771ab2b3e534fee7e51129b90cf92fce697219cb7e20a06371a09d1f64e21f54c2537a3c5a0f3ed5398429db3d1591f8c7a66a4a2502bf4d0702a24db41e9df2deeda38fc0101d5eb3654f75c474afab54e8296b60087598967c1bb8a0db4056163b4403ce6dfb68e721f6db933e606d9effba251e7d056513117031906aa79307857c029ccd87c719b32a80afe7c8e2ecf5e2f60a729f28e230cd75a377a641bacbc2221f397c9fb62b9632806e7e33db7a5e5e3ef7d96ba2b3980dca335ff4a50cf36b51c8c90ab8208ae8359d18a71e7bb190ea7fb40594252ffc1c2b1d6b86840725eeaef5da16ec0cc337888ec628e9bf8f1f5c5b381bf2a267c11bf6c634f1be8115b8d6be52ca1618e5906942fbb6a682e4171d849af3c973d414a26ea3d87c23999bb80619bed6c4d5a2906b80fbe8a41af1127b2034ca403e109b7453c73ab467fc7e472f2f6aed293c96f3f1b32fd713b298e060781d8b564541be9c5c79d1475835006174b448490bd65fd0fb1ae60d0ae4633d5cd17a4d0d44d5492394478b9bc9c9650cf4698cf795f64fd40f035d1b72d30762f245c1c8be85f76981758001a1b160a56a812d5df257a297b7e12d084440a8f111bdfbd081a1e320a2e3f0a9f099d032304f77f58d13991b8c6b8ff822977b20c66910606412b7c5d5e8978b2d5b75f7295524db7679b4331c5827f0f81692cb710e20c7f3e20e754b34118defe53fbea9c3a0a65134387bf3cc9810a6ac7b3b3b15f1c8e88e1d068cbcba076324d732c9f6ecfdedf19214f41f85a78039f63da2447e1a9e35a0d8cbd2819cb6ac6b46ad11fe87aeb2641e9814386dde74ee5667180c26371843b569c1103c13b119397a269318f1502761a5ae0c3fc89299a914d3faae9fc95c38e1a5b7ce9083a4fa5bffb963b2770b900e90d587096a9d33981b179291c9af15036b1f9d385c6c85980cf9580c7b898534d88c16ecf5ea245ca0587959c2fc791376a38abb575a8b9b05cc155ce4572d474d35e323942ed289403210f45396309878a57b54065877f3aaa59758924373b47ee929b0830ab75c6fd1fd855e3a4321afbce4b84d15e2f71f01ec0c1610b53b7b3460988a6d9534da8c385188d57c88973db4d5eb8e2007b00f53ca35ecf0ad19cf7bdad3b2aa68cd03fe58737a6cfd2c8b370a70e9d4b433953ad1040def61a9caf1367f02698c694b840df28e078a0a4f581886121f860fbef28995036edc9e4b9cb8ea3f83811f9c8ec5a28252cb80d7e3cced9c82389e410c36002ecf25842cc523a58f7d046c2092c63006d8205537e3aeda22ecf2f4c479226cadd47aadd4985ebcc11e1129845d53fa3023800b358ce38257275ab3c8095ad61f6aefd20ce441560804247c8a7d73591cb1412b22952a29bd18485a0058b9742f6feccfb86de074f1d19f498a16b65ed6cba8210787ca0b3a9201cf8de5541bd599a62d519a33c32c0f7fbf329578322c6ecbde3b92d1be8241e2146d691d29e970832189746f968ac3bd79ce43a5c5558b3a42a743f3127a329c90389fcfc22d60777210f3ab6be73ef8cdfc5ee4b8a90f026b4d2b462446ab5dcf9aa05e37f1e24b47aecb77b63541228f78115283a615d40c8ac4a9d51dcfb567945cb12bd9ce47d1a0e05781d9d351ce863065d12f7096ba11d5133a0f119f930d5f15ed14fbc152a24f5e9b10ec10a1165e3b747c7ff5de5c21603d24676c3f9ffed4048ebd002e5f5427f5e40912f5799587348ca28512a2e4ab012dc66d7ab9221a55a8aa12c977ede77e96f7c0a01aa21c0c9c937e13d8e4466adccc35b4525ff01ac17669a98e31cecf9df6bc825b68b86a27c922104f28c0cbf1a6fd312577ba7f27aa1e1ed80621cfae46ec385706a295bde402cdcb8c0c6247b6f81b1d4cf222908218cf8fa275b4ce29dce03e956052bd1b3a4ae1061095015079d2fbb5c8cbc3be96041e4b60a5a019c8d71a2402301e86d0f5c67190bbd013ea90d94dc4e297211be06015864ba75001be8cfbe78bdc2f73a12279798fd56f9ee918e2f3e65b8cf84208160cd3c113446f2a6894c5136dc033353e9a87b38fd160392cf724a5b591ce8818824415d6bc50b40643d2f61541ecdff9e62e46085683614d9849c3aa6b02ead05f8b7c001b409d1da3ca7954f1acb8281bf3f53f62e7e2ea324247941a97063a2588578ede81f46ce3f59e7e44ce42ee6dad846c8216d000ba5c0a711828bad9fd60ed399fd703c8e9f2f1cf7380807fa462187080b71658cd17c653b152c22cc1bda870e98d68996194894d0867fc7f57a9abcd496cd36b19fe0dd1c54300a55d5451d5b67ea969735de4fd6fc3f06ad07a307ece0074b0d80bcbfafb30a8656a9965ab4c2d8f911430b11472ee5ef1b3a2c053e48db3e82f1d4139aff10d56d5a8d46de6f3e960a806d7202375a272ee99114935c15bbf286a875a28eff62b3f3bec83b765d03e5c8d01eaf1853a380226b842dd18c453ee0720c9a70b1b53a681200b232bea0d2d4db5a1ffe3c65ab15200c8d6bd963422bca72f4541397575b8d61c291b2e4ea95c3784e75c663ecbc073720efd1e8556112c3a9ab2871c408d1df32ed653cd58411034800b897666ab7dc16846defaa9026eae1811785025a33f9d3b33b6b0181335194a82b99a6bc4dfbe0fa3bc6788df9cf8e9c7d1f8098d14c5f365ce355561bb4e0820aa3cd2d786cc44688d226a715456ddbf4e483a555b1683af231b32662d7a1061ad6ef1bd4a8e9024522d9534eb88ae5863afc33461eb6f0696a74762e4b8fb742536ea0a1e01d1ebdf94165c2547d8342d67bd8ca87191f7f071ea412153557fbf0580a723b420121e101b24cedfe97b6d897b9be8de8779c482538a8f4cb047b5fcca13aff715880e1211e7119d2457d3484440eaf0830b6f845363f5e63fe4abd50b301bd0bf746eec90ea03a36ae540b6cc5c125732c9b7daa7878a72ffb84113aa157cae408c9e9928600db33d91108e465e146488c95636715048b86a9155a5dc4328a990c6cadd4d01bd30427de3b4deaf3ae5f659689b5240209b61523210d0576b4362804a554ba98c9f31b39bdc19e56e8cf1611006df99f7edc9ca8e94c54c40e235710a42bc200249d377c3b183f596c60fc03ddbdae6bb4af1480eae36c59c0d11b3d6e5a8e5ae7506ccbb7f884eb6b1e72d84f9695e96d5b481a5d9a4991ea5976db9a926527121e96ad9afd23210350286a5b32cecb68870ff1b9d56a2acf6d8a71c610234ffae297d84125b182a4809f7c6ca9b00821d46ec0a2d6a59a1a805b9aaf57b72544f40d54cfaf36ab7d55d295bef11044015d675abcb831a4046d00d29e109405c01527528f0f5bc821b0722875e8664976d7deaee2ba2b58d6b70f8320895593a5673944e3fbd7e4040b5811df184e069928ff23e309d149381e5b25440902429a8ce5ea394eec643549fc0512a8477ba48df6d564af9ada1fea8155507ce1e45d30be966853cc2b0806c5a76ec92d7e710ba1c0b9f855a59cc094699788e2a747a0971d2ed9a321fc63a22ff5dd6c29bbe21901344665628c55001f998806dab99d9efa692bdd340de9e19c814f4577ae7745bf441344d92320a4dcf8494061470d25e1e26612336677018e4e439c870022214ab11d9e71a9b1491ac1d0b376f0e30218c7e48d497af6c62cf4e7722e56030c11d26b1ca51434e4fe8003cba95407ce1c6438a6b2c99cd33bfde34b2682b3d4c2f8e60dd11e6881b8414182ec2bc9bbd06e579158f0ffcafef3610ecf0d59d4ada78161b2d5fc99548d3a236436b2e38d47f21fe34dd9d240ac85a9ec54d1c4890ced77124d27ae3555cf47cd5dfb1f0a4b83984cfe51d53a56f2a6210620ac6f9c9923950f70bc4c5738d2e4e9767a37a91714d969ad66b7bcf52c9012f15dc602ca95798c59159cba69cd29b6a4232cabeafc8c87be24dd1f02842204cc1332b8afa3f9e09e6cd160704e1ca7d1332b890a2151a94387b675564c52bf662bd6119952be8150c8c4a245ccb22e6db18a05fa121b2b577a94c71b08d7d021a5e64d54438cefc5d2ac4867c10868d68d6d0a4cb48a43084a06f19fdcde7472a84d23abe8bcdd37980eeba6b9ec6a356949159187d4b2b9d8a874312b3b9983514787b102add531e6a640e8b8fd240484b80f3d1ccc95b649b6c2750f4a6397d33c59367685e5cd2530310aa9bbec18ecab748f7545c5ed8c746b34b0102780954347e5650c6c1bfc23e66d7d9f438c32d4903e8326b927d26088b1317c2e3c1aa6db9f1673bab5fd7fbc9d45425b9e413f24b886105edbd1d23ae511b45384614a5ee7710e7b784205456d861425a6382390a0f73fe10770c4f3cf23f8206b410e9639784e632d79fe439db98e46193784ff8ba338d434ced3382bbd790a85ea122dbb1dfc9d474a566b455f2be3fd6d2f9bc6d3d6432eb437accbb4db6dd83271f07828e0857b9fdc64eb88cdf2faccb7dc00be0a17353eb1b0bb6c0d7695e6037abdfca41b88004fd3ad06f7f07ee9c8a1b602c7d306d4e9cb8f75fbbc24c9da340f406ef9b1647524a88c356a14e78072535bcabca2006086b852c903032639bc6a285982625f0b36d3440798101c8d4c531be35db8e9311c43509c378999a52c6415d666c69ca58f6ad912ccf5d91a80a9dbd3c92988222965ae047f8982a412fa9f5aab07407419dd5d0d4fe961da0b013b9fd9f08eea991e4fca2edcca85d4f43bbdf6c3fdbd478cdc2e7f9f9f177569d536f94b7f36dcae2b9a7e59164259bb533f59cc22f97604e83cc41da5c4ec16e15cc6940177352f08fb5345e4da6ac2e2f695bbe694587aed814e117d3737154e07f59bbae3080afc2ff6beac6988862d3700b57b5bce295d8d46816cda8808c4855c1da25c99a205d0a9cc344b69165b1fa50c2d32803cffb399f0b0e94806dedc32864bf1b10178c014815f553a6be84a2953e57de36fbdd9989fec704ec9c0895c4472f90b501d35e95092af866019481205e60e4cddfb82880e5448b9afe51e9d2dd4099be398fb7fda61ddae8610542c683fb0d3750e97e37c055e629b47e4d4e1f268a5700f133d81aec8acd014280bada48a6371d198b3a5920a1b5b2b88ebdb9d4e0f1b972045c21d34e1b542a1923c0a2102d3321ecafee4494e0960c4d798602ae3af5ed1c681fb8e85d4a26fb8119acf774214e61517eabc537fbec3ddf5613264e5ea3f8413ba8d24f8482b94c0109c662e50f42da86da41a2ac34e38bb7991bd6b0038180802e4f9b709feab5b9c444d09473d458e5ac30a6f9bb0cc4b00c24d32f79910ab8e9b36826b73102aa83a331f7d1c0ca8433602ebf935c217dfd29aca8f8e9ef1e3e9f5acf9bc7fa003cb1fe4548b54ace0a4b619b2c3f207c7428ab1803ca56613098d859c885a93dbb01d7a222607e27e71a15a34a7d1d1efe0655b818f044ddd4ec4c91ab49ab18d58a7970021f0c92e99c3a95705a797a99a8b536baa6f6d31d2a74c4c3db08217a00bf8026555a6f869285d79d8a7f8db9a67db608dc994a3d21e07bb7b333c2f423a5b31471a39506bc823b6310441d9f6448c9c7c8aae1bc391fff94419da3e5a6b4c5edd87df9c3fa25b8d4a7da86c44c2406099bbe6e80789c942450d325a5c147f1961ca74b7e688be7ff322616cf4706a9149c9fa114dc80d704df620c8a9b4ce483085fdf63d3154c533b645729c3783c6f4a95537a230c492068e61091298b3cc70e90431e26b516ac8ae634910e1c17f9ee734165f88f366a0552890a246f7abefbbe6438d93b8896be71db18e964ae0f49e9adfbc1e1b987f212148ee5e2ea746909d8f857a17580237b41957ddb2c0036b0db4741cebe30e42b5983f4ffb452ffa9797b00d55fd33f52b3100a274c909c30c60c0907a2ae864b145ca41031eed91fe779141baf7d871b6bf351d82c415f00c7c848f937b98eef20768782670cc962bc4f51bea2c23e3539c6377464acd12eeb6ebbc623713a3b4a22cdaf4c312dc9b3c90d4c1603d54f738e6f889b445d8001709ab1db64c24f057a4350e9de7772d559638ce3456a3852f8bd5f94d6cef422c1f2c40c6892cddff12edbd983d4475fa59bae4dc9b9b1ab9d6b6c3fed4046559700c194b81bd7ddfbb16e7ca2b620a2541c1a7e946a6b6c86036862804e4596db4e4fa6619def02764163512278680fbf40bd6f44485f771cac100d379773d58d2a4d2f21f2eafbd306f26ad52ef2a8abc0ba39fbb9b985f4d9415ce937181466a0a24070956e562bb9d00549fff52aeaa4f4c6cab434d017588775cd0737b551d7b7979ee32c12361a7512a60083907a5e6b97f061a2920a210a89b35750fcbe355fd58d7211e986633101849b9ebf53912cab3d50a61069fd3f58aa08d9f9c5ed69e3ba31563316d25d7ee6c62b7b629cd0fcac976e9bb0dcef4f69eda5992f81ecc1d90af0b5189bb99072377c2239560b8a9df3ac9f9b36f86fd2dc9e7c7b2f2ba82b671d29f11f15777b30544fadb41b1620f2787975a02f0e64d208d2bd3f9e54a00dcff19ba6fd77963227c654c7e358042a7ab72c24c1db6842ab300091f3cd7b37611b94d732b2bca6703fa096d23540b5b1a80f80cf3a2af3b8530e85e388093464a98c3e30fccd48a5fec468d683dea74504f645446f0ce029575e879d855acc68cba782af074fd4ea41169c71cc488d861d31ff20918374900346222eb083da3251ba1ae7cac6d26ca1fdb3d0ad3595f4acb0679855f6535f8d0d954e898eaed73f65b62367857daa729c5d315f223cc7502bc90fd1ef70f73df55cdc04875b5f8e387f2c1efa73f090e4320f31d8fb62833c5a052b2405ae5886314b98d6330309c621862634a6879816a73d67302b6a1a57f10c2f3e627b5f1790dc3f8b9f4025aeb03fdc30744dfdc8c979985b3a6096abd32cde191087125e03b9878f431e0be8e353a1804bbeebaa6723c9832fd53ae5ae10cecf0356a1c8d0ab70e720a280e69bbdfae01b3c7cea347ae04cafbac9e9b3cd707c8c099db3526c06803063b427eb46e4116df13d33c5e5b7df524dd1cc5bba20036f02bae5968a1d00223444030a4c06795c308c4a4e07ebde33e77b6cb6e745b30d681b967e9329debee0e531201ed67bba102646712e7e606ab87e4441a758046cea5487196e5741601cfe3abe995ee48e7f0323a400aabed1d0da27f5fa5fe013c84b437bef2de5de524a99648107790758079c16a78b2a85b0f3c8e5cf38ca2f7f06da5b083b77dbb87da305b88dcbc8a8ac0a5917f7e5df3e201db41657dbebffbe22a128d15db509dd44af71dfe2083df76a5b6de30664e7f56bedb5cd3dfe4dd4e2aa4389a8d7dfbdd6bf3312d14f645656b9fdff1117f7ab1cb0a4b24e54517295d359c1c5aa11f7b54bfdc27dad155685dcc57d0ed8c9ca70dffda4a437e6ef5125395ddf5330ff97c7215403221ceead444037f7f641b8f17762a9f735da9bce155815a2cfbd0c532028a80c51c392132f5078808b65220e15158cac0eb54aeb1b870c93710a0e51462459776b3dccca00be6bb50fde71eeaa268a289568ca2043e9044c7630aa72c3ab7a400a6a0b0bb3872311966a40410a3166ec127084c8709ac4c0e9616b72ffde264b708a6e1370cf599631683a24db15b20fa626094e042e2099b1d6daad5a6badb5731583765d7d5a6badfbd0a696035f83b6f427cec974f1fbad368b4319e816ad2fede27764cb0798810e43ab83e723c4ee2da7d4bed16f92506a69a594d25aeba6744b736da873debb8f6cee63d4086142d209a6086302154bdb96ac80e3c25695d4eee33d6729a4a0046f06b950a7547fc8fa4cb3b9c0dee5d68bb6e2eec6070469427f34a7fc6e361d757e90afd91e382be34a0a5338144ba8084848418924610ab25d6a319ef7939294d2df58bb8f783badd537a5d4d234de0c0ab95615f10cb5487e145a47657150b9f8a8ac2898fc69d3a10a0524f404184e609182e504242758151698105984329e0866a0c061db4184378345c90e3fc867cfd995dad67bceaed076f9addbe4cd2077771daba78d49c5785f46e57cb9daa8ffc97914ea518f42bda638b647a13e9c17d8478d2c3aa0bbf2c7fb9cf113cb0af6b8f77239f851a811bf833be8bfcec9f970dac8c911eb8362f7d543594a5a1232c7b536c77dfe8d02ad9cb9f74422211cc0078feda3e7a1bca172d2bcd74151169871460fbcf780fbcd1bcb567671bb973a3cfe39cf23aa763e14e83df765ab14c29c88f23c07776c5fb6bccf62d9da1985aae1799e8ee7fdac42a9f7428a73c0ef537af4b1cccfe5926c096d9abbce3fbbe7ef44cf9efd3f686d6fed2ebb73b873efbed6aecb9ca69e145df7572cf5f61e7ff77df5ca233beba0ddb443ecee9ede21768805a1fe160b945f1ed9de885fecaed3e9c65201a58f7d7f47cc93e59f12bd8ca78dd6f6cfdb7bd4c7b2b5bbf7442c96be87f6edf0f85d52dcf7eaec1bdebf5fe78bed63a980dd3d1ecb17bb0c3bdf41b737e269c3bfe5e9d04a74d93a7bceb064b1f9f1db2be2cf6287e9d74477ffc1d0eebec478d30f81288e0f8676ee511ed95dfe8ea81b4bdf2b6f042102dc1fd9734f8ae37ee7fd043b2f7fedf2571022b0d2c037f7aded818fdfc6dbe650acc59b41d7ea11989a78282929d19413b4ee50a95695c4de9d1aee96f2b2713470b809e71d1472973aa1f376da789f0a4a8de15ad55dea91ecd35a6bad8558d1a5cea1b5d69a47cd51a291e8d1aed11aad512154d786425dd34f18292d618b794c25944e3da1b98d1b794c24362567d2e1b123839214159817dcafafb5d6dcf64c2237ea0a88d8b4b6422f09b1bf94a209d5e05dc0021e67d3771e4f56a1eef36fba89498f2e94e8efc1e71e7feec6559d5f470ede1357f37356b31b577304d2fd4804cc1c8178d77bfcf9c16f9c235e79231ea70e71c5adca32a2f46ef14e4812d3d528436b0d7df1f07ff93fd1878e203527a774070999a6e1121065f99626d910c9a3d5c444294d559175a94ee5a42555268fd28406793c86643e688e011d7f4c0bea73a38e334c581d4ce872d264474d827ca1d168349a75ef8046a3d160f8da2062f48deb94db6a8e011d7fbc0478db797344db82bfd0b8cd5ad5082dcaf2a12f5e130a4aa275afa3e6ea893e74705059ee44e3acdd39a20c1502a29ee45bba7c411ae2369ad39ce65b10ee3dc80c528530a69c2b15d3e5c442640d12547a6cca7854a19a0f893e5e95cea882873a1fb0d3940db496506daacb4d95b99baacac6a9d1e5d090fa55001d0cd19a001c03cbd1cd2fde412229cbbfa85abaf9cd2938960e0d6dcc6d37af5cf0b056edd85a6bb5b539ebcf596da57a1363551fa86a65532b1b7777fbc6f278a0aa950ab55aadbb6d7776956c7a7baaa93d675962b14dc986440f499cc95e9c6c4f9b52957befbd4b565051955c41432ec10d97b3e72ccb122c66294e94ba74c512ccea50abb4ae5966594698a5304496a5166dbd211363062b495274dd73666506194cac1c69d174cf99953056b47c332b55665644d8df9eb32a5434616940228592cb4847820c461449153ac45c46e69922a6334ad018d1118aaa78b9280e3218417379db5745fa324a62e48a4384acc8e5e1af8a5e53f2a6e69a66a0bc70e49aa1612a7279de57c5a12e5d602eeffb2a1123b336c399970bcc2a83fbfef78f6c22463e22a4bc5c608c7c415489b9c05491087d45b1c2e4226284f322cb4d60ae490990f355b1e55290400a25971114938ba404407d55f41f8c2863e49a52e0f08393cbd349d1265f684c2e3752692da2d29f9545691554da747111f13756b481e332429b2081ebfaa82ce79264cb152e231e1043dbd048e4c2c0260531b88818018f704d4a00235b9926ae23faf572557105a4522a684f4cae3af2c0d92908168ea8d72cad793ffc0961387fb8ede2546a47ab5cf5f1f418d90955433c61cf900ff5f1ab52a97878787a7a7a7c7cc8300c7f7e7e58b0f0e6d461dcddddbda7c72728fcf9f1f171afeed5abbb7f2b5fcdfaf3e3eeeed5ddddab7b75afeeeeeeeeee5eddbdba7b75777777afeed5bdba577777afeed5bdba57f7ea5edd3f16fad361dc5bad5640aa162e5c00bd78f17a6f8aa238864463059ffbd00d5518a4220ad23060c4881123045285402a55a8dd9d060d1a34aead9486cf1f202090a885aa45a802c2d385d3161f510b17ef22c8d2f0c58b202272877beeeeeea5915dc7175f75afeed5ff5f14c571ac00dce1a001e80763c6a0313cc6ada0023003bb71108c1ad467850123460c13741d65c890419224eb63b166cca001127d90bf0d90c823d23a8cc78821838824594d44feac1b435313a9767cee2e43860c70877b24a88106f60542f0489205ee98f139050dc07aaf5e4a396c67ad7a33543342151d67801af82e8f78343e1d46d7a861e3d361b407000058f059e0b5565b1ed1416b7c356cd898b5d6fadd7bef68810ab444d7b1fc2cb0a0d50a400004f0b93b6500166f660e7fafa7503771b890677f8229b29543984f341fde5459f47ba2f0d74b6cc0e7a0748608c1ba298bfe8092f43bf0b40e4a27b5a3fa3014150f17180c96278b3a9329e44dee4293afd0e42838acc98f9a7a1e1084e6c3000e9aa8177ede6b308339f3260e03e283a99bacdc8180f766479f362e3562594c1e3e9f0f48843f04ba3836ae088802a9a031ac49112c870687866cfe0949f029d96afd7879ebfce416b4d431720702721890e7e2d36174e9b04d5f7c1fc5e10e03896aeac54e8a851a6db1c65edb1eea032a17a4d8710383f765091e304ca0dc69fbb73d136cefadf758079dc55830fbc2011833177c8863a685daaeb5ed3997466b6f81661b102bc10f3398d1326506850d67542d8011807d6dad6a5b6badfdbcf4b0b7b75b0b5af63685fdba98f12245972b33b60d2a427f1097372c8badb5d642e00a9b1fc8a0a04413319765b32d0d4cb605b7b5d65a1e2ab6657078c2d21d2c1026b3ade58264db143e0a13ac5b4f5b1888bb1ca7a5cc931f2d4c2b2d67aa84492cb4b489f93cfdac80e449cf9657b80294253c1a2835f9ac80258a2a4bad6705189cec24f18c8192d2f2a4224267470450b42a5401565973b6c2d3955814a31e578e909e662724151183cdb68c800ac7719c15d9374978928d9a8c683a0a941e28247179d223cbd1079cf4b0525466498f2ab42094f44821294d093db6182561420f2cb4190b5158e9b102d3cc032454e9a1a5287c41abc228ed527f8c1b748bb6ae93302a5d62c0f4cf9e332a60643ce99e3d6754ccb06054d4e87f2209a8c3708a538f2935fbafa9b899f23df941a414a51e5298286b4609fa1b018906f79cc59eecf2ff63574cc83ad4aab047512c07bde365c60934169344e33232aed0dc120d11f477264691d64acc2881b652b068d076cf59ec0d0d2ab11390446164333054d4b0e2031812642976d0b7948ef5edb5e38a7a083cfa57a4b4eead1ce9d7bab2550b92962d974b7ed15a90c8bea5f6eface307dfdefeaa7c071bceecb2b563fe1ded9ca356fd4e52b2cb9615cb1a45cf2abc2c6b183da8bf71c97fb9e0af2217d8afa8fbaa66289a7ab922dbbe46cbf1533ceaa840dba8af0dda69fb07e5f83a6b503a526ecf1e7c28da75fc1e47d125de5bbc567d774cb75b3b3e25e78341344d8ee20ff4898b34321ab52719ada98b15b4bb01dccb297b3bd60d1b59a35aa62a55d9b441ff0160740794a4a2149a8cae5f6270532af49e6002aecb7e1551dbc50aba3ed2a6235cd7e746d7b1ac62362d67354252b3983a2255a34b0be26a9a5fface1914a4edbbd47b622d82d1f769e6fe0eb97b08e5fe7e1db978701f73f17e13571b16b9747fc5cde954d11757746cdda0acfa198abea3f883067b80e01e95687f6a995021216be7914db4da7e22816117fefc9c08c32e0e7f16930cb9b8bbda461fdccf0d44bb70b9fd2a836e6fa305501b8b738a46b4bf6f969d214b0b33a8acb2cd3daed3fb184a5c6d5a4727f53535a6bec8ac20155d525965794f521cde7bdeac792c3fe86077bf4d173dd8368f8ea56ff781c49f925ee78d1d45a1b4fe3a16015344f54f3425b7c75f71fd0d2afd6a77c6983e904a47226030ad78abb58ea8ed270af5aa71857fe7552b20faf10864e755757b4fa5bf52fa459efa984c4ab45e478b7efd22740c2264d2666d7374e5fddc65def26800bab3d7a2c4609bfbec5b6d0aea8d1ae0711b29121f4850afc1efc421746fafc5557efdd3851257795cfda88ffa0df4d3478df48ba86812c87d8e48912829345ddcf66da8df60e77f679c95f58d1ba08ae8df54e26ad33c4f44f528a1e9caafebeac77dd42fc03ecfeba0358f3c63113dae7ed457fd4a35aeb6a7f4f5d3d7aa5f6d23eaf5afc447893b44c00051bd1e8910019380e94252040c109ed73e5f84877bfde1ab463faa2cee9fe7555ffa9112e1e845af373ea31efd6f8c3b5f448f2efbd28d94e019551c87f281a24828c93d4adc3eab258a46a2421388990a6b36f7f3a94c61082ecf8ccaecb27ed9dc6fff65af29c198ae3421e18c36f72ba751927beead134fec922e39b14b8a4604b235bba46b36d7c42e699bcd716d7e3a5192e3e85115a2322aa301ac292481356ad4a041b3b4942307498ae2cf8f8e4ecebb3651b2be57b10826b7a8b188116ac4930bcc7d9e31b210f5f55fe7f5a833fea847b2b2eadb2094bcf2c54214bc2ad3022bd22b94b4f6099daf104ab5a314d54a631b17c58a2e6952514c6f493c288eaac63e07f382fcf6194071786f3ffb0a98187c6fed27828077fe3eca1431d91215eab9625fe9cbb67eb4ad97d93607fc666390b66d6d4b8bbc310a25adee9cf407d3c6486563280ed302fb58a462a60dfb3489929ead688f8d3ea6d1db584e37442440c59a2362bf0405169c5ca8c713d079fb7a5ec0bd5d0179d44864b5414fd24b8f458cd027925ea8df19573bf8f572a5c6490980f5d7d4eb4f8d9f7ad4a7c62761db79c5b6938a6d8b448f4d1bd60a5dd2188d6d238dd531a6472221bc5e2e8d1a894840e9ebebe5d2634fd2ebf5727914ec803783c017a818b9ea1f71856f74fd20eb33463086852663a490418c511263f6449ec9e10972891c9a72600aeb9a5aaf67d4bd2426c98e729abb2426c9ae6e1a773767eec720d22fbe24263bbf2426c9eeba777edd2f8949aeb31f750ca85be0bac6fdf246d0b3f6c67dfe1af7c6102b070d1af7ea7b6fd4a8d1ba31c4dafe9406ae5af537b4bb6edbb75ddfdbdc1bf723508c8d7b8da449920b8a111463cb01b7a018338ef03c4a291d29c514079d94529fb5d66ae9acb5d64a29a553b4d6d25a6bad94529aad78d85acedacd5a6b6dadd6564c71688a81b51587b556bfd65a7b496b2dbec1db2e75ce568a73cf58abae14d56590a8d2c7b456d4fb9cf306aa06fd26a5d6526b29ad1445524aa9a6d4da9be7eb18dce1b8c3718763afbd1b9c6b3738ee70e824b79fdbfb06b67de72ab7d56b647b1b277beddde05cbbc1b1d7ed7577270be78b3783ca5a349d668a2f77b76ddb360e5f8e26f99005ee84dcc959f4e79eef50388efa255d12b2d0a95dbad3c59f76af7b3e750caee52cb89f1d03ee7a1b77fa28491fc438dbcf74733a42a716751aa24eee64371da12c74faeea7efbb7dcffc29fbbee918383939dd29a84a958b76d80ce99625f56b991b1a74dcd480044de726072414103ee47ce3061876fe9c6f78da59cc6c671c9eec8c030876fe560e5e86f4dc81e382366739ae862336670396cdd9a0c4e666376ca9810a8ebc51f30cf7c3be4928ec7b3f6c61df59d2153430792fc4c84c21d3b219b57e20cdae3448997de0f5b29d3d53c133d46070c0aa66681234c311a784b540481c60010509e90ad212dbb2b132c8b6d5019b5073c0a187292430d44ba749939cbfaf634cc0129a5031acf03169ca89e18b134f098cc106269d2a6452f4cd4ac830981743104b7009455d0c4d4eb825b21c83131dd8f2f8c304d92c8627273f9c182529e18712a6274b7e3429aa5204f362c20f136a33193eb0e4078c89862f84474d1586155186d8fa67519678c2e950ab42a638af598d8928706a90d2c40629566238d1a83d67525c8891468a0d240b5278505531d3ba33cfb7879a73ce7b875c107cd3af7f64eba074ee0952588ba3dbddb62be309adb3e70c863755681f353386d03c3ccc4881fe6ea061a4c13036c070820a309040bed1e0130d3ae584084767bc1cf900e648461e25bda0b4cb6f591454c7b9f33e300744695027b5a38259dae313feb05801b570e1e2c58b6305412f60c49041b266d0a86103002ffcf9eb59a4d37c727ad51cf6e44a5e3accab05ad00086000376edc20000ea19616d779a13e2f7f1d06bd5287404f7a2890e8e6b80e41206d732caaea8fe2708da4718a32a13f4a5677b7d65a6badb5d65a6bb3b515d7ca430577ef56ebc5b5561e2ab4bfb5d6da5ab3b5d65a8bc9b7d65e5aa9b5d65a7b456badb5d65a6b3d6bedb7b364684fafcb5e9775a8ebfa4368fffb41d0aef5a2eabdf782f7de7befe675f7d68c3bee5e226cadde7be79c2048543f08c869bdb7de5a6bad76c35d9e5d9e9823efbd97db26b74d8a290e71daa8b372b7dedfeebd3af587ca9c73cea04aaf7fd0aedb7b76ab7bb31f04590fdf6badddbe8fdb3008125dfbd9fba6cd41699dd40eea9b14476a47c5d393da51599e7b6f8f4f55f1f4f46c9e9d3d3fb5afcfadd5faf60189eefb7cb527fc6ab5f75a8b83c577efd517e8de163e55bc66399cbb0704614363f2e14ad5d6cefb7418c0c109b0b64934101f600eaa2a1929c551ca41699d8fc90829d0493d06a6902b391425264a2328cd5a5532528aa394da5165150f16cab55aad2664b2e867803a95f089762578c6d9743472a7692384c27b7cd8b051a9787eec1552850c096148085a488e1a9bb0874d0f143d60abd5f3c3a27ab9f3bacc429c17bc601ab88ac87fd808a9d56a405688650394f2daaa039ebbbb7b0577d8bb836ece49575d5544f6c694bd70baaefcd448b5f6de6de3a6e83ababbb5eeeeeeeebe7118679c73477d568cf3e79e73eebacef3be1a443be89c9808ccc9b19bee54965277cff33ecfc999f6fb9ca26a543b83b46a72dbe501411d46a374ddf56da523ce41a1505a6b9d21a21c3b7cdf0853a9d40e49546307dd2110dd2a1e90082889ae63a9bdd65a6badb5d65a6badb5d65a6bad7574f7fc5d1e8968c085abd6eed5bdbabb57f7eaaea3a3934aa5767676542a1511518f4f1895554e1e4a1d746010310407b90811471fde7f4e854dfa449f6a94299bc640a21ac3a2c1112f81c928c42177538de85315aa5b6a14c7a0569916d047a2f5cba68fc2941acb5386e90b3124697dfea49422a184e6508ae4068922d66ceab02f94525a4413344529a54eec1d4a2b0e4478e0043e74608511580e6cfaaaaeebba9f212165776fbbaeeb2a120f605ea0bd5cf9f1e440a2bce9edc3ed081cf6f6b38aed618448a20ad9cb8531fe376208a618bbac6184525396a61a0864455071c66e7f99d43cc984465f950f46cc0050c43d7387b8730aa625fbbebd976969df2150d8778811ec4b44d2bedf3af364df1fd27106cdbecf030d07f645d3b46f1a12f64dd3b46f115e5eba0a5da6cb2484d3ae2f5ebabc340dc6defd68632c44d3bb80440a2e4079c900b36d70e802b6865440a4400041800922ceb68fdbdcb80104961940f0b06d8e7d69afd76bcacd21892b650389364bc6daa06d6bb0586d34e55463ade5b66ff9d93f6ccb83990e98e101cbb64ff200c5b6df1a62b2edeb506261dbe721dbf67d70a01467db0fd203d3b6df8107b69df960c5b61fc20f4c2f2db0a23073b634c3b63320901cddb9b570a40993315a509064042c2cd828a4783c4ba66bdb33d8be3b3051e605ca832e75ebc60e4580a006aa103ec6586bad7fcc9a272d2c86e2e9c598374d80546113b226949a6072118e8953b452d3444d143557d46c69f1b22725b050856e62405816bbf23302a3950ce94aa8e689858ca9c86789a61f198d4acf12b05076022a3c163bb1e4041a27d4f8844e4e5025e1d44326cbbdf75e36489435c59459fac1060d0f48c491648e8a52aa906644eb423ba2acb9552119d9131dda0e19214cd03fa0e89222a326ca1346984ccd494e125d34193831d06210348180a6759862429b241d567852c44b87189ca461d241c624044cf88726b8a933a20967c491114959ebd044d1e606ebf006ca9dd8e2354a3fd630717a87244eac2a8c628a1f51584159739758ef1025ca089c76e80213c10e47454d453bc8a614a114bef036806025a9e0c3942f58b4dd7336850c8c351aef399b3233010833539a76a99f86294db08af4ef399b52c5192a55c2300446e0cffc16e4a19c4b3ff3d72f73e89f96b2c0df11756c624bac6298432bea5c50ce337bea7c39d3f89773884e447d8ef8bd8b3954ec6ce888e564431ff42270a744d47ba2fe1cd17b1d31e7679b0d72306d80ef2dd9d068a858ce1e76a9cb09c410bbd47ba6d99a875c3b3aa2426a9ee7c9947833a8cc479b7b8ffb12dc9c36f7a56a03c1dd9a6cee37cf0a4a72db6b73af83b2d8a5dd774baa4275ac5b52cd3f9f6e4dd306f7155ca14b2e4efe42716cb5fbdc7369e615b93493c5855c9c79c1b46eaa0836c7856073f768ab2579e8ddc7cf81fd5c57f46f5265713f5dd8c73d72e12d147719b11125b91f63dac77293825ff7a80ae1e7c27ba4b967818db82ed8c7d78fce09bd7db9955b93d1e67eab71b5cd4dee5dd034978f9ce2ce6883ef91b5770a4121d0f2a042add6745971ba3c178f0e9a73ce39e755b5bf01f9f4ed58040c10f2e9577b7f0392fc4b7e9109ae7ed8afbf01f9f48afef68fb8aab8ca9ff30884becd39e7d7599c5b67189651127c2ae2a2ca0261c4788d9594d0df971c6c73471c8c039f2ba238ba073f3f75af0d76b00d96dd1a8a83e7c1cf4e1487eac1cf6d288e9d073fd7288ed4839fc1cf4854686baa2c30a8829e9f3497d96036aa4239ccb1581592f1e0734719e87111e61e7c0c23638ce573628ee12d542897a12cf08fd0a08131967329682c679959a682b1cc91e3c68d5d724fe44ff0c7aae7ef1f71ad7a80d4bf2311303d4f64044289904fff882bc7385865818fc7b1c45a1c4b1dfe58862fc6f2398e5659e0932ec6926c31963fae46163fe1586a9fb1958f36f89e7f3c222e8395f01266c26828093ef9d365c5551e2fe043c6a7385815c2f8e9712dc44e1ad3b00c912345fcaa2cf0afc81d7134b0e48eb8a3a31cd59554361a734c2cf118d3250e017e55a1eda8b2c0bf9ffa9e3127bf6e4ca1679b0cb41da58e8e36f8d3458ad3c523e63594045f2566274a82bf23e636b94649f075c43c0525c1e76855686be26038d35e18a68b3608eb621b7cd8069f3baa421989b2c0cf6a9ad0fc52584667311b3cda60ce98c71fd4254397d1dc13f75431e69e9e36f89efd5a059817cc0d3e90fcd2d9bfecdeeb44164c3ae7c11e3963d206df836ec494fcde52f27b1dd43352ceb10450d71d73acb2c0ef9cd0b3ccb1a6a6a6a60d7eab0a3960b2c007a3d8e0e7b8a0e94ec494ec5e8b9692dda3c41c31487e308319043b2316748506fc493e08165901195b3f8b606e95df77fe551ea7cbe6d873a2714adbe8a82c1087f7f33d2cd6dfc0f74c4524603cbfe713ecc6361c5416f818fc2c967a83ba07fd3d8ce679b40dbe3375e07b97fc20cd20cd20cd60fef2c17798831a0cc1f73e1104c1ff3eaf29e1cda0922bbaad2ae4faf7a2269f2d5ed0dd779e534ed90f77d1a78f7a1df451e07ba223117d54163864dbdfe8304a823f4170aa9ad0df7bcfbd40eeb5c1df68756eb40dfe26ab186fb20dbe0b9a2e72a3ca025d1ca1ed57071feb2ab43df8215985eea3c632c49b2be262568d7eeb256cfa7acd765a43700cfc0625c329f404260620e00d42d8c107f3821bf49dd20c5c844d472edbdb0ea60dbf42039feca8c48c9a6962e9d08c00004010002316002020100a87c442911c87f24c14dd0314800d65823e665c401f88435192c34008628c21c4004280310418620c62caaa0ce82a27d81a1ac6c4d01061b5d9edcef91f453135597b57896b298b5c3fed1607cb8201197f8f5f0b436057a8d99f0bf9df4b0982c9d8a91fb0b66e869a4f43fa1ddd75ec69db3caf7c264881990dc6a65fcde8b6eb2fd58728b77d373a70d78b6ab8db30c1258e45eaa8e9bf1378e20dd0f39bce6dc57d9ee720f1fd0bf4ec72b18683abeb89f8ecbbde6857bfd033626cd43430c9911a89c73558d1ade1cc59ecade19f68984a28de704b00c86e1033c924462667a000997ad9e6656fff6e3aa9e4c3221c21951df47598f989bfb3a747f91fdfadc6fb8a6910d74f90fdc7cb2040a317413834047e19b9d105e45e54c75b7a7c8de76a4a3cb8b2b970206acbcca66c79930d6e481c8615bfce2dec4810865abf6c19f6a0c1c2a98c2f23c3bacbca2dcf7f44cf630c6ec94b5026011c8f8f599dfcac8e5c73b2d5dd7c505ab97fb19c9ee6409494fa22491750fde0fd9467f535c948f53a394e0a3fb5a51439a474a43ffe0da2f0fb6225beeae4d2bf425289b3d7d24d1411cd89049d38e1905cb5c1f58c5a7282a2aebb4b01a7f3cfcf1e91334257e1c78ba5ef2af4f6591ce6e3da3c315dd24bef1d6226f74ba6169fd7c5f7107c2e78bd0bcdf326af270dc269fa5692034440a399b139884ea4386b2e6f1818c921af102f9a4d4776d6e1dacb78121feb114e2da06156ece0b1294d364a6f9a945644790f06d10b189394897602912627164d109876a7d104b94a02d963b04c98adfe403829338635d40289c7bf7ec49b1885578e65310819420c4340b0223899f025f244a068b79d492266c9fcff60100068c9cf3b66322c9a0e59f6c641e7810e1f95b821c769ddc56ee1acf5984945cb94453feeafde9aceda418d5068c6ddd8431a9b2431330886538fb0b5f29e2cfb904739c92981f50ba5a8a09900e1cbf7ce45b53a3412d2199c79f3d73279ce7bf8c76555c28536b3115e28e1cf42d426884fdfa5657aa83f4506f8f2c20a335085c96c98e8c841e481cdc5f8e789bce4f5fed95e47bb40a8f0d98c2bb2805ee504f04ea7f02d6119965c5fda975c2f4f89a25a6b61b4201ea3aaa0a9a9f0ebb8dcf3e12b9c10b0b18ed82b3cbd7e99bd98d85ffcc22551f2a8ddb51e579d1c2839cee2b99994c7ff68af798c5550d55ebe53f2fd9c7cd7fafe95ef8b372883c2fd75f738a45ae9d2835979ec2e8472f86e2e9dc91b3027eb487aaf74b275c9bb45fd7fb93b914fc20c0163e2556e6820d3e87f737c6b4167f9950cd460687eaeefb159345275223f0d26ebc0348d4feccdb3c955e74251d4956a1198835f2549e842e1725db120c2984255cb18ae10a0ab8cacc0ecdedfc96e82e6954497942952d6861f9b4d9927fbbd06b62bae756af2d71b39b2891f5676abde8076e883d075b9e6df8bcb3856d6e558bfae510b77bbe76aba13ebbe1c8ea3c63af59f36c84d316917d238164dedec159226a108c8ba947c9c62a0c03014eb9c8d509ffa5bc05e41607c6c1dd535b1a1aefd55f6db9f96d7a3b4266d2eb39f69bc491c6705170635417c76f54703283816e6819c50faf07ce909da0355d728e3cee04d1a8a8918516c52d4e508ab22291c139e499378066fa2098da96ecea3b20d632bee696da3f856c7114bc00c7c795d6c50aea4a5f77c8e8668e51dd5269c2ef02b1c48dd6419f6cbbce964b8875173e27282c87f2e351353154d91a39bf7f803d6d3d51d912d98309669d2010b52e5839308a2069b02be7d3627223b48889c6cb50d6cfe56f7bba647e6faeee75aa747dae0c3b80d82a1b20897a22569a1aee7de6dec850fe285470d4650fd7ed5bff265cf56e3e0b7aa584af25fbca77bc576d8f8e320a50a595325a07b4a7cc9fe66bbf2e1dfc3683c6a8f769f2645cf49f1961276f04f5a9bc0cb9380747d1075107a28e7a5b6d071242cab1cb1e0ea9e3f70a6e4bbeba8fce40b75d97b52356f987ab24fa187d438aa92fc23803271ffe4d658dd7f9d4b4fdc4ccb8e39ef21b13ac9f116ea22bf08059a1029821fbea8ce91694f634fe3e20670d28b1805ac5bad5c52de942e18e92dd1950384b594cf9ee9db5fda8399a017dc3204bd8ca37c8f9393edebcd9fd887d35a00d06b25c7481116d85c65c45232f6495ad569fb53fd2efa112bea7534b0cb2d399b4caee03b8d601c6618d213f97632f991829a2d23a0e988a01184ada7b7d6a20ab748f24b75b7acc32c90d74eae950073858dab1ae2a47d95f83d02dd2e42ba19c89c073e8394857ff25a567ae5be5d255ebee0367125100a3d1895a5c4794c0679555eaf33c0f7fc85480836be9d8a913631f4c1fcd008e5d0d9df2ebc2d77959d0417b9eb31165b5bea1430194084028e88c6f2955dc9b9bf641c3a6a718d1c0ae442a5378d747440564a055d57a5e6b40dbd02471728212291f7bcaf2a6ccce8b9c4c47b03330427cdf29f9263bdc8cf8021c8cce00b28bdbf0af40bd2d25e6b0476e5eb662ee0ef846570cfd469589934a62d5cb8eb8ead95dd475c37147410608873b81452f58247c2bf07c80f241789faf967072df51961e9fc4b31927fa71ce272e5c8be516e761f8754f05d74b9bc8074ff31ac4eaf56e8985bdbd9640b994fef6feba9e81defd161185527321d3ea19f3cf19275ea7135a42da5955200b2a990c4d2e6625f8cb513ba220f1d67fd3fe90b28eda00bdba9fa161133374b3d69d9663794b9d60e423bb4f92222e3c011f1367d71055c4f9e49f007cd48ef0c6b59517c8d34f9cc2895bae29adca51953b4d75035c7e7b437a5619d2e369b310823f197b2411afae924ac4111d40eeab670594b05482e41417a181c89128e3595a6d09c0db69a6f9d5693a3fa789ae4c1265e64768fb4ba30d5f41b7e896c5857a57722bb24c2dc61bd42f36f78b5d972703c07644f72c2b0486cdbf9042177938bd4194fc2564851ce6342ad4d864df71fcc4d3c6755480c9a1ae1eff08b461d3404ab9a28d109975dd48eb47b23a73dc73228d20e0d6981777c23b0be501a1084d4f5cca8b54773e8c85fd612003940f499e88862c0e7e0b4bcf235a960fc5e925701b59e568474ba462a9ba301524dfe5bfea76bf83959878a77611885cb2265578cefea64cc9a2456ff9436bb136ffc38633df479f5325a5bd0587342436c0a3ac35da5f019d2f536810e36076dc04219cfb3ba8fd1fb3987d7c4782e7440bdb1b8eff106a67e52948bba04147c8cf4191cee1aec8d2df88cefc3ff8cbf5b353e4566453b35be6d199fbe7d073f90b35a7833bebb682133595a7c04631904f560f184f8b8240c30dd5a0e449e2db9cb5127c5a7ac0f1a70f57da79237af81cd81088e977d097e61138949481a1137b4de92edf28df8bb787c9102ce7befce19e382e5b761ad2d03d3b71b0887c05029fc4f7497ee6aef1cfb1ec7172feb4fa09eff82041ef92c32ec5de519defc68c2736e9c3edeee16bd2f794eebb42bf65dea99b110037f29017aafdcd7d91d036569bf32e00d8b7da8925ad6fe4eb49316fed23372c42983cae806b82a4f7cc58597a8f01fa84db9aefd1e48f975b892764ad8b1c3a7a9fcc9a27aece21e5883c40bbe52821d8c02b40981d6b65fefd7922ab78966632ebbfa852d3d724b27093a6b47824b0a6b0d88d2fadff1f5901abc0959139faf3fe39d1873be45e5fcb7ed134990172dedcad8e998158b2bbb5327e5a404d95953e1f51ecac2561767e18e45e2d0aae56a8c471d7aa7140f8086112591d9cce551e38f0ebd2fcee6ca0cf604986e4d1e98527302a132d103a27077a31d525c88986c67cf4a82ebf0bec430cc8f1ffe8f92a88c69672cc364ea2b5ae017ba9d74188f0c75acf22dea894fc5a1cdbcfd916491f296fa19c755201e98a3a190cc5ea987afaf44006da82152eac9ca23e503729f814944a78a604891d0a8d0729dcfafb6d58e98603c8bc8eed584fe24c37ad1b7c09658f982fb7cc585f4f8e7e637b3be7c20dc89702920252588efbbbb8bc3537f892a44958ce2719a15f1a5ee67e5b42068d8efbc16bebe6dc98000aeeb5cc297310fb464df60bbd49aa5265cc59c4134a009487c1c97aa94f53c318170df2f91110acbc96d6a289bf75376ea0ec600c3e52c5cab8c0743e878335034ec53ea79c790e617ed5ca5c6066885f1ec090f3255dee96b256f02e8f7d36fb9c1a476cdbfc23e3d68d2a31ab99fd207e6c60e5a74b6cadfcee3072e9a551c4118f3e28faf0be5bdebae8015ca5a829732ad95e599a9348ed881a992c356381a5b5f0df5aab843e029e0f24ef7e5b5861a0291b8d5a124feabd0d0c8591c19ccf10578d813cd4809e55b18f625f879221052f514ea5629ae02e7e80ff21f9e0f680cfd0aff1d1b821be69b73d9a34fc08cf43260813c0ff7a646541136e4394c5c584ee40feb07ec8f1def6592c9b624f73e47dd7542ead4b43b22b47c40ec1b8243ce508114229a823c2d9a8c8679a0c7133a05be73b83765153decb64c12d25895ede4ff315904d593edac9a0bfe41d5d958ca9c999c775e619e2af9d6acb34d2c7f521e11ebef4dcae296c28e304d14410664e6cc9ae8edd0bc5eb6d2c68114745428a0c29a69a5f0546b2d3d2caff559c304e074f7e5c6727ec59edd35216adbd139e32bb199d5837ed303550a2b5cb739ba2f977aeb92a304c20481c9b834e5a2bfc25bb7d9e547f640821ba1a82c5526abb8014898b0b5d051d9cb5f28a445e19b3a2dd950c01204f1c7669ae2131694420c44ed42cbfae04978fa5c57548acc773861715ab9c7f047d72420fa8adde633d1156d5497a5329dd6da7c2b340897cc030917c67c974e145fe28b04d0fa082f6270fcd7443a1dc948c9c297f66b40da218fa736c8e30d469189d5fc6675bfc5f9b5aeb027c3dfbe5df94c11f74226ba670c628ede39fc2e76321503e2a61a02915b098f6ed2584d053360e58a7a6a9c24cf8b7704ed642d943cbc97d07a992a1f5ecaebc20401e7a0203729716ba6b31ce7ebe75c618f998644fd9a890b581586d596240c7626a19eaf11c116e401875c37f5c7d880b10f92237f941e269710c77c07a5acc91de1e4728c5c4d89f07a235634e62f219f18efb3616102006a12c9f03cfadba5514d5a52ee8ef3e3bbde7c2c413a2146ce87e934659d4504f71b763f423ae176e6af2a606a6170f834f82346d632448cda60f28f874541cce70ef4136548a07498c113394e64a250902b398edb23d3a2aa588830149328f483ac398405be16f72fa02b0c214c2d2564c4f7338899174d7277ab2e2e81cb2aabb7624ba23bcdccb7adf73836718044eb753229d835c634dd9e04f1ed9c1623006d9359f2159fcffe3e4f5acbfe2e0f4a815a080ad0735a752a6b2d7b378c6ebbb439450d7d475eaba29ee3a90314475edc8248f814f88aa0701cfda12108a323942e5ffc5446ab97457776a384d1e7073b85d9e8717a91b9ddc1298f4221817d378a7f2f0ab05d3dfafdcacf15462448839c82a2020495d44204bc49a8fbff7f19000d8393c670b92f5e28f051a6fd336f00651d094e25b23325c330a803c32d013db104b79129954d725bc94b807d5cac913c61b84696ce5574615d9d5f6b5ee87ef037c3ae921a7477cfffb77e9e76986055f79479bf16e1c572bc58ae94842233149a8845a279ba6c67a8e6fd357c1cc47baa6257fbf55bea7c025873bdc1ab3374a3ec4df449837d3e841ce9a90055f78f5c05e9f5217e1437c09c05135d35e3da575ea1c2ff3f90c11cd0816a01ef9790c159b19dad415457589181ae8b42bc4c9235981e0d61ef29e3ddd9747826c5f97aecb0063e132857176e11030b302c1d3ffde49776f3082c3d30f3ebb62ffa322861e5a88b238c78d965621ce69d1dbfb1a5462893067f99452f2cebb646401be4550fca1e268fac4efdc31b1f9876d55bb6f6dbfdeefa8428cd0bf3a71e3ec12ca2d341395aff4e314650fe31dae67184ed130bf2417a7a1cd577cd71b3c796e75a0fd6ce479565d2f24cc1ce97aa276b2b9381644c5bbba4168c7f81147e60d5f0874ad8409424d7db856373147d036c6b2eba5c3a042d146cfacb30ec4663701fd2a437f2f8d155f3b8cac7947f0b226d3e25891cc7a5f2f7815e9ee68245c23ab9ad76683f27532dfef21efeb63b4288e29d8365011a4335e2385aebb80d558e598400ab5be7e43cd49d1be351aff492523477b50c6fcfedc8092456d50e2aa89245a932feef1c48415f849ec4259165bcb2e7e2b3ad2782b9998ad1a8f4eb0e1f2a959a2634d643e5ab4b0bf48136b810d205173c7799a3ce7b4319821926feb0c756d78f8486ffc99c46ee5756e4fdbbf15c894814c98a1d5346e95875166b22bfc010c74936f74dee4a177b3fe1402f60540d13c36700a8b44f7fab7e4106601b1f9e70a03c7b262ad83a9f9516a01e7c7f9b4ea9b7871970f8c35e35f08387770dc88e32bd6ec2ba924b91af8830fd36a33d0837be85db1113766167f79bbe241969656f848299f88218757d2509141648a25db98bab04bfe79e5543ffaa492f823fdf4adff6369b37bb93f6a607d73e534dea327536dbd957edbf298da5d7d9afedd156f54f96fe52eea7e407536053cf7129c1e982b8eab2538206b3e0ee40f81582cd970efd5644fad9cf72a46be60c6f537f10a6f688ff4a152227c333d81bda8244522ed828692b3362e1e1489da72b95e14c402478922307856c981ce5cca51772a8712f661d441d5126f9d7cc9b5d821395c7cccc1774633c06a7874c1227bbe284b8a0695582c685817749f7219364335e07068e9264512fb0ea7156be482e076aab1ff85b059a16a99732343d0c3f01b56b381724dbde96093f716025f098329b9dd7449f02d00a1cbaf7582f49e89a2c6347477834cc210947bc3bf93adf364c0e52b95a1c2fad7d8d46345a87b2d83e0b0c2ac7e6817bf6befb6f4552c4356f3ce9bab94e91c835c372da1abdb896fd86e1587567f86506b25ff1cc8137402724630d1a6e6795f0b0b5fd4c94b9a31a81a40913a5d5251b0a82f77c0dbf6f4375eed7e34d7499c9d65fc75fcdd5f839678c6918b015882cb57c4c74d030fd5e06c60661c23b3cb3e30aaee8bb3c66948f776a705da9db7dc6c89728a0716a7058c7ae127dee1c63d95b75324ab28f68ade867531222cbfbbbc7426fa6b245e29c0ce08c05cac466e8d6067f216604e72bc4d0319c93c69282a8325ee3973d5a56ff521dc0a62e33379ed36108e069c38247f2b42b3a1edae6fc9bb45f2648a3557e4280ff6b9209ab9c60821233e81312b5f372fe9de589d205a773c8043d33c16f8ef38462ed9c434624c213aacda13eaf674c20d75d712b4e992fbb2b3188ec9776a319147de8dae1b8140c1f7dad7d16869105dad409a0dd0ca53244bcccb90a397302fd208d6fc4decf8a9c147a7fa33800f5de467238164ea008916557b9a8f8a19c00bddf4aea3cb139f5f0743a83641b764917b8fa07d047d83609ebcbcc9ccdd4f3a4677b7e7acc37467afab81def59a09b8960694d199d0b62debb85613acab89ee5859b41e6f6eb891fce67b290289c0b33419841a70978da9e711a023a9da6941d5122660c84844ebb2b087c8eef95eb0e0ce5a8a1b9494f16e57d05c1e3a0fe13730f9c9fd6e026d1e4e5b68870ad0f832f5b8d42a691bc36db8f459f36c84bcbade7d19d89fecda504c6e64ca2ab9c710456130918899ea439ffdfd4728e2593fc80354cac56a517c6d0010ac764dd6818380ced161d6a89fee0ed9afe545dd749b6006ca89220c69f8f39daf7bde019eac750a129e1612de1c5b8b6da9e438898d667645789e12cfffb0dc5a1759c98fc5b02b387f833aa559310f062db5adc550a04de4ad05e7ebe46c7544d822dafdfb8782273c8a95c0a707babe9438f76fe5ab46335979328f0bf3cae9b4757b7f3ae364efcfd06a83122f0e81bd71a0cc22ed62e9614124125af12cefaef02cd343550b8860c38fb8ad0cd2d5f7029c1180c00007cec855a43d54abddb4ca207660846fcbf12cc9834c97b2318e2706fbb8e75c9efd4a124f2608bce7b5172e7dee2b8dd24d9cf1717a505051eb659b7ac3b0ec6990e77e3411ec89a86d7c106b21373d4f5ec6aa034e24f29f9b40a1575932636256fe1c2007a5a95db515d54f2351db807713e3ab3e81f29a723348b6e0a1cbd158d36a676f3c5da9f85ba3094f7bd70dbc2f824f4640bf6f4b210923f64c061ff99447daa9fab2d10cc8eac73d8cb2a398670bda1eee8234f747aa1650d2657da29ff8ddbb3ab44ca2bf9097bcfa1138391a68482f49b86cf19f345f81916a819ca3b504ecf853840ae4d56599edd4cfe61095c9bd03b69c75921c6f4009d04da38357c226212467928af5885c8aae60b3ee956e7f96046165fcc7196b2667d276980c4f8c51c143685fac6243829f00e7c90e63a59fd993261a1ded0b202e58c4c076be600272a364d229c1c9139b3426271fa5d889eb6522142d589a0420323c1e53559b27e161d9fe67d28ca80855b3debc04c6c906072584ff6c051f6c4e997c74838fb9d157e725d55ef61ab258aac8a18d1042a5cce74833b4e1151d3ecb39d9aa6db8a0dea13bda5854d8196b41c2b61cb0ab2c1d673d280615e84e2311557d97e542a09b359beabbfc494d45b810e82761c3c049487cfc648b7a9bc27c42e68fe89f2e23f3e28e4958e3083356b54fc04d1e5d70fbf034e0db313192aa688bf60defaa81a0889793e68daad33179b924af7ede6fb6f97d6a51b018e3af64dd9aff4879816b728e800f3acd2f2ef99feb4fa6f9860be97b95e9b9efed7ca5d0d3594ff73ae50f64bff6036a92be0c1097e08c24d1525bb1f1047e60946366840bc0c559c0c47716a2c79dfd095fbb35f54a57d0f2822777bc5a6e6783981ce89a55999814a31b83a05ef1f54f99910a78fdb2202088a369fecf5209531f46acb16648c03232b24b9e0245266e3d9d08268a59c394f58acc75b5a47fb5088b5f8b3d5a9602b4d6040a999a29864a860a4cdf84b6c3d26499c6ed6e2f71a74d1a7370a1057a65d25fa7bd34120cce69e954d4f7ed3b510d6c3f6e99fd4d24509bc9ab57929b742609acf035507574b0f619a431dba47ab9b9a61cb8b09f79c694c392ea1bc3271042e2c835aaf211dc2798c58ebbe15fc4b66be58859b631949c78cb0c22b42aba140d28a50dc5c55a8fa56705028dc1c044576a9546c45ecae16666dd4a4c8d28937d66d89d54a5fe386d07c18e924ddf712dd59f4563434b14bc406def999c7e9358ffdba647b9420f4e3191ca553327665ee7f68b0f6525774899f1fc840a49f5de96a5775990a1f9c7caca4e6c55e4af053c36b95a597f5c6a262c44ba364a551f21dc6d275ec05cf7a7b95d24ee44a02627283ed6e3e28202bb5c70d86bc83a7d8f32e2d15be65d53926eb1ac2031b0ff2c860f0ed23dce8ef2dbdcc3d588fade57de8514b067c257800fd300fd1e51f3ad58691b4d0bcbb549600970cdddda5acbbecdcc277e8f01bdf693869a4234b9a6cd216812172d705c8b69a73dc97c31ba0635f8d0811ba7b118b9aa855dc0584a02d2e5941ec1b749750255b3a9bd418ac5117c0c2f4e3b52f4092dc915ecf99af53d2aaddb94ba2d41b3bacf405d47bf7886fd3b5b0c5cc0582700453fb0a906e214cd35247840a12f344939f78e7ca5adc366d90088c01223f66be5110460cbe00f1e84a9128436c299f045500ecfee680d69fe14b27cd3418b273001b43cb65b4daac709bd4b4aea000e3ca8aaf95e8f60fae327734d716aa8d80930aec558bcc424bd2030cbfbd3707de433ba43c6122c014d63e052c08d0118f14980b576591afbc204ba19b80dc5d047fa7f7a3ff92e8df5da75460d85d2282bc0840f9b9f78f043fffcd317206558920c8f5295d520d88779f75436c0171eab692af86fa0ddc7744fe435522cca42fbfd081fea78fd7f6b984222219da97851bcec481d7845ce2f55f1c33bf6ed7517d949e698221c74a21d3728b3748a1f7ef0a3bb1cc917e656b37b2f55bf3061983a21d25889e4ec34a8784f6af12ae3d909eec0d58f730c2ce008d5d5f72eb9427eebd4423c0c8b61a725bc0f9a121b5b3a73c551f71bc72fa0cc90d2a21713052277e2eba3b5c253a8eda809aa671964a7426afdcad8e76005b37d918ab051f27dd13faf625cc0686923c385d7fc1408aec4fb0edd4a9db18c3d1b1023c818597fe145a0be2fc2d1e77be16de7bdd51e1ec88d2b0d859f3a9a1b85d51cbeb90c3961dfdba70952ba444ffaec9c233604976c382678fdadae4a0bae5b971fd85f5c6c6f38a1d56db282ebe3ef698c7e44b47212b48159a00c102e4165aca1e4ba0f781068753f56835cf8eeb97c578a92d0c020c2df5d9d29c532814b7e66c02d5d5ed9201c07843c4d8df519ccebcf0fa05b091b7b60a1884bebc103bae7a71251632517c153f982862cd855b904f70108efc6951b7cac2855c2bdf3a0658552a3f4336f473ca5bd12088fa3becbc8f05c1189d01012faafe0471c262f1af7382ed398134c41ea6a16958a786677efeddc8b77240862b30d07af6ade53dad1adb8d01fac102ae29db97e1d6357926311ee8a712f5b7f23d4353790a988357e13dcc119c5d9eae3d6696cf81ae24748401fa270dc3d6c5280c4403eec3d35125e8a8d3154f84a04d6751e00788a62af714ce12244a61ae16e8779fb231a014c71005fb58df9fb37cd0a916f49fae00ba77641461967b2b493babbf9a9d544d66dea97c3015f2cf04cc93c8dc239557d8503a3824878c788da8f7196781894b402fd8e2d89d30dee8a13eea62bc8bc843193e519dd70627b56a10a09372ca7a609d589c31e755900b0e11033a624200deacd23d58d5a261dcd289b5fec7714cc15907b986fffc9f48ffdc041b6707fe507a3b91d9cd15d74176e6963e2caaf777e0555c0f22c0747b275eadcc03560548278bb0d6ffdf9ed91624299f8f070246267dfc85eb161c05f6cbd9445c2fe0e17e2bf3425050ef35ba3cc0d74a55fe8d73b52feac3fe2106c18b0a8bbf02b207170cc541b61b829e8b958897ec7caab7dc79a17a886b5c2d0b27f69c29f458c6c64e24fc87ed1eeddee1b0ad699ae2fb6f79c664fbd7fa7bc9fe62077c1b5a20289650fb9fb9427790cb786720234980d00a435a097d1b7709e40f0e33b6180f2c56fd3eee3c7ab131772f57afc39e9d735edef9b9ee0d1ab9519fff1e7f6b24f685f8ed0ac01f199585590588babfcf077a6dd39c7b37ed85da08c9a4d42ec6112877cd25e793a398b763075bd44cd6aeb8a6d040612a5c7197da9221a1fad69d837c84b8d08fffecd18799f13383375fad267803dccdc461f53c2f255b9bfa28227f182224abdb06c128548006d3ccb0442d88ee898f13fa88fff7edbced3f26c8ccd568afdb6e481c9af5b21e4e11f79abe4da39c4ccb8961891f5492e5fbf0fb81f8df23f9fb3709714e4fe55b218cd035e6cd0371a5344e4ceba07f3404e20f6a0dfcfd232202345a7bc185cda2331b4e6f38f18db2b1ee94514234d9a4a412911a383a3149400793b7d64e4d511ceec5899d5b7d483185f47ffab1f1ccc61f70165c0f68e842e60eef4014fcb0bcaf6870bb9258804b4a2179b8f372818e90ea2cc5dd208031583ae89fe3e012c818ccc361b26e7a6a67643d47d7a06788f04383c665a7d71e424d22f206a7ddbe0e06169dd0e9c900ac2edb6fd67da36ace681ad3dedaa02b6f7306816e22891715ef946ca29b12a6a2f6a43b1684697740a0ca56261073dc15941cf8cefcd7760ed44d676eb9f6590d52a7a6ee80814fc43593a492d39cc9e6e80e9f85fba34751ab426703d33dc85ec262c751ca69326e6d4104192fd9280a1982f32af1bc0f43201a773374788be3f39d12ec031e11b7008733521a20f15f38e87b0be1ce2394f568bac2b3aef576a8b641267ac81f41506af7bdadb5a75ff18dd1c801b84128f733284e4ab2b7db5601eeb191b98ce5a23107cd0c7a01500f194df7c2627fc52a2b73fc2d955d66e14e6560e88447587054b8ba6dcea50fd3ecfd184388f7c6a960569cd2a4da0be53568e41a72a6e7645e1e6c435ff92804e925857e9f06622170510743db2afde1a329dd0953f27f5344afcde33181b6f010adc49c8710961b92dfa7cd2f55f2b0781808638e209c4c5cf5aaf94166fb1824bdb3c956444aa32379b79743b438972941aa811a527378863190c31bad24304fa9e4059e8afc284b980587683a8f14628b0711709aba9cc5af570870e22e45d3e84beddbcfc382d864324d9eb0409558bd437ca2219ade60228ba6b4b4ad13bc4de71568e492f8ee78f4b8ed1e2228bfb20e17920d8924446efa5e1a4f5c343ea31e9c0db8870fad37e4a1dc86f1618aac9a91a103d4f17bf8baf70a1ee61672b3faca04535b49093a155e38361cf78521dac54b70575ea75915667da5acf9e7ed2a453b015125aad9a8679137e696c4c947beaef0e6e84fab616ef04d8b0549e835aac8ebd9c76dd01dcf1024f2fe320a171f318be32efbda1c7582048bff3ec7196799b5b76d8f97df001e7cf9ab8bd33bd45bcfb73e6ec840bff3abb3979e4d2012ee291e3bca107030f2ad2043f0601ab1ff1be9c7654bca225b14730f6792d315542c73aead0c78bbe4433758363a00dbb3418007195bc1707b0bbb00861909a70cf1d5f3e51ae57bd428e793b071c1bb1240a7e3999ca9e9f57eec805de1c6d99dccf0a345e8c1e26780f32be0f849a3390619a89222be8219617044219a5e20f6c99c2024ace24db1f5b1c10a597fe6583de0d2657ad9024e0c14a2757f0ccda2329a077313a60aa4707da658e6abaed10fe94b078c625cac3c0e75b1be553179c424866f6e8a86e924f6c365abb22f4bd80d0b52d774ad757c231fe5e461f46312f310a37534873ba75162f6febf89aeb0ee1304bcd2f63284d9ed04417df1f9b5785b39b317c565096a7318abb0da0e264e77f2a28b41093569cea1a7b6001e557a9f540a3f26b094a110d0b94687924fd1dda3ab59a3c0138beb014a2ce2a39e3a88d69d98ac567cf394e31205bbfd3a7b406a928adcc6a604f19a30c1b06dc464d514f68029c2c7296a10ad178e9caa7e3202f2b58e3488a643e67b505017df4d11b81647b126653963c701e645cc112bf9015789b4ad71e6eb450e30a957ca0bc1cc935682503a6d51c79a933fc8c11ba37a1ee0941236093081394ad414e3bac384846cb7a72cf9b92e4e102b6fcaa288de03ca052c1aba40282f219f41b43b95d643d2cf0c2c5cfdb704b3208408f3fa3b50332e06d16ef4444bda02edb258ccf0cb8577824c6537389b8c4d0db14aec3299b84ba2165ff4bc4a6b98efe2ea6a38bc84b31f6b3194816b6bf93fa543a2711504cfc41b1c165641e77a718f310967289107bc231444d3c921540aee9b7ac8e3e9aca9b72ec18c266812a69834141bb9c70b4c99d911961e7ad05e7f28085839026f4f780f808fa168b9e236638584209aaecdb0eda555debbf4253825fb0bc3cc63502dc854cbf4341b9fcde7f2faf67070ea4b4b017ff10a8f45052e50d416b58a5303fc6c74556936682952093f31f119ae24bc07e7ddf3a6c2d591a85d96a0fee331cf5ab0ee6e9a30abbd45814d9790cca5fda88f586fa09568205a7ce078d04c46356dc96865a1a15c3cc279794593521988d615582e0c3940a9dc9f37927f0fcdd1aaae0960d36df722dfcfb02ee7ae0485231ae9af86801ce4a7e35f3d3c6b668218668eea9419d2581f1e52a3bab9aa42680e4b8ce2f06a98d7cd92b42ab46410e58d2443064c60e81200b38016aac53148acfc9ba6a9272c8fec5d3e852d0af0eacd018582a007be737edf271901d1ae642f903e319af0ac8eaab754e55de24a56b338ef2fea6a20af7e808ada3c78eff9ab3193f11a4a138078c66531b0053b691b7c939357bf3bd7373e9bb316934e6f00d13a0e488ff57a4a3f68da70e302ac956ab45028908d716a3b98bde9e9444f4a13241afdc42a457cfaf7e4f00be92e45b5864d6785102881d525fcef67c7d7348eb8a0da8e415f6f22cfa9e398cff8780d12af751a08ea5ffb8dfe8e747f2d6b883347f8fc3c7a29b2d9b66a59f3d3a8cb3649456bc206d5e301c822b7be430de4ae6a7ff214d937cef4718477e252fe6bab45aac5d1872ba20a0aa53f171158852e3e1959f2ad3bc7ba5b63cb3622a8f996dc66b5b71cac32dff3c363e1c8ec309d586e5e6e8144894325ea63836d56882ba1553a1c13e153ada13b49d0adc49accecaa10370d5bb43357c8bfb8045d6c4d105b339f76746d297d07ad8f177dad9f23596a6ec75b74f6fd692bc95a29fb3139bb200c550b553a8e371f7cd1b06b33f5e308729eee3f417dd08d3129c9bfab52d32af84e57d117aac2d839422a813d7f68471cd87d322d69c729aeab06aa3f56512d8e11d093bb84ac765670b94740e85e656168a3a6717d3aa9d2d818bcab86abc2f3869f0c563b12b048054d8369f16a08ead9d75ca920c769cc6a7315bbb4094ec817622497ff1865c86224f04f6646f899a13e8da9717133ee6c6bf830c3ab4fa90f328f55d8faf5f445620a9ec07261c74b05d34e11b4ba631f5cc14d5b1d5def056d978ac35a7500be44962c22d25fb878d333f6c13c3c9c5c811c220b83fe976fb2deaa8d980535cbdb565aadcc907ace4f6121fd6075cacbe7ced5bcac5c7cdf1b6f1230ad67771ff749bb994dd91c1f19eec37d33355ecf6ca51b49ca0baa9906de2c4f2dab6704f74d9e1965f9aac5ac4659a5792b1cf6ca60e818e8d23de1a43e95e88c41a995c90ecc0ec22a676548ca6ee6230a153217bda9dabc71c1ccdf6aa967b62d7a8753cb0dc78c835b3df663ce7d700be6d90b55f4037eee03ea2a1a910f2e511c1da3342727af92d5fd7ad83b86fb6d5119d2eb6c9358ac765528273b18d831c370f8c3e626966a13679469b41d4ae427b930049649565a17186a368ea679e37c0fd74c5f55eac3641ca1f2a0c125c37cdd22f12dd3639b7674cca3d0edcef440ee648690a69e96c9018d3c41f19ee6e6ef68dd1fa71c7fa7fd6a99cd04e8e66172e282bcf9245f4a6a8c0d5840b26c57bb6e243b95e9395c79be7bbb75c47571bb83f8fec0de3e0b37c33db2188838ab67a4d388893d58b061aba9f0721ec167f248cb02873e77d59752682733eb3436343aff1a76ae51487accb15b2c6220a157b3538bf8048c8b1834e9ed50a6415ef771dfeca20241815efb838331e59f63fbd2efc3dcf618755b0e8b6d4d0d6441c5dbfd969bc3adf3e1b8bfbf5fd4e212fefb093474ae278e7e9e46eee3231394815e0e962ddc696417a9503d8399310c459f1b1eda2485717544a50418b3174954d5b3c0c30284bd8ec86055be15949821fa22fdf7bf1db0f24ab324ff74b4c9e92b623f8f7ccf1759a534ed63a88a8595530909456e571d3bd3d7c6505b28203ed41b341a5ac67fc4fd82c184ad88bf903cc33adc8d0339008c1f2b8121d908195d2569e4a81f601a0d123f1b215905a717c9d23cb568716b0b7761acb2603fc95d4eb45543e14b4c295836cb48ec97b15f2411d5e1e285dd057e3636def3bc225ad757c23362aeceae3f7e819172297f8073457bf8c4996e52790c0ae5e361b6d563b74efb22c153de1cf75cc075856c86c40ce71bdc9a3211d19654fb457f06c64bd34d5e0cf30ce0b224a22328d74505ef2590199fa8e894d3b285f81384428932b30a644411ee61626db9ca111369c172c6fb3cfd09372b6c884a8e8cdf6c3c8b6c80800a9c42cfd4600bc0817079b61f72312d5cb32ab465a5dd8df1e6f13e8455eea31992ea86041188507e92cc6e7ac90383a25c88741d19fee86760804340439e724b4736e90879cc26e3117b1b96bc78a91e9ee562a0c48012c6f38f0950f8f2797ae61acd7cadbfbda4dd651b4fcf2ba87fab9b751353ad772027847e67d033c4aaaccc05daf4f8a736776bd890c8e123ef3905d055d072426127a717829ea04fd0b62204f25e12d48c57b13a8e7463309c83cc3c6d620413e95dae059ce24692a98291891b102fe314f1352e0fd91499aa48f2e8b444edcdaf736277db2d09f80c98adae794d534de325101097ba05d739bd7cd2899583b67977487a7a6f3e0f13e25682760dfba50295dd0fc82ad3095a76292056a66a8c6cfc802019a73819e0407164aa60b27603e3653a45c93407c691a982c9da0d40902a3d0f683523fa6cffe235e56b3f2f92d4c7f77860437d64e30a09f490a5d7c70f39db4b23c8c5a7f37ad5935b9913e2e755e8dc18477a03ffdb931bea03535e136922b1e514bb0bc97bcc948a79c063e274a61cdd7cf4dc9bc1431de207f08d7cf75c938148886c47e9e988c100232ada72d6928c3530ffd85eb6eb0aeadf4a9d7be23039a88d5eb8aa6707c88b0f5a9067ce15cc62905875cf42859b1ba74c2ddb36dad5c8622c229ddd1e4174f239031037e9a7a4e0a3947d359292c092387804039cad6321936c9a8cdc17d993634817d95c1a5e4bea3bb728e60da7da5ad46f1f1f5986542c84eb55328971250cbd649408bfe9896314693c84d021bc7da0666b257ca0b1cfa811478e5948dfd1703746c30cf117f848b40f01682bbf7f3fc7b5c5c16919a57aeea9e124a45418e148ed721ba7c582ca6d14d560a9c7c71ef32f8e4f9c9b7bc9fb99d9174c45bb238a879c75bbc79fc76704ceba7258af664177211ebfd66e11c8dfd2953b87e01181a17658ed3576d572ae5b42b7df366755637108d4b431dafba62aacddb46efa2bf82d4c48559c9347305823cd5b8324c56c923b13bca18b6e1ba1ad6347ae221035b9a83a5f27b9bc1ba2c65b8aca4d36d644d22381c4baaa8d0c462439652df63e96449b96923e70c15e43254aa89baa549e784beaab4e9061671b648d24efe7b3b5ea49d21ef40eb5e5059612b820e0b89730db20ac5825057eefb25b86cd2792eb3275ccce960f8b10b0bec0020dcc93610bcba5a0acb5e135c67f3978570fdd739a0fa6343b5a43a14d26e4ebbae0ad93553dc7a6695cade9f034fa3b15ebc838b587f78217897327d8124da372bad0143fc0f063d3a02f4548343df13c5240ad4f0e0a6ccf2875f2b7a18a4c90c333aade36b3596af339b287e90f6ec749941718019b0b6dc4ae7ac0b968741d6783ecafec30b8a87e042a6ea9f53630606a1a3c4d6a9bee5aaf1d4a979f41f68c40efb236b0fcb172ba10fde5289ddff7ed50b763c35e5075ecf6449414a15bd606d616024ddbab3454f940a64b958acd85684b40cda3b6c5245b38422d7a3a54a08083271120e65fcacf4b782415c2d21ff034011a120db5783589acc177dc67694864bed5713e4516db58264657a600d2bea9fecce391912439e7b2fc3c3161094e8639b46214edfac6aa178b49550007611456b0a17bee73c1b55cdb1ef155fb7b5d9ed605964c543d286719ca4438f2071480450bc0c1cd44bad920313ecd3f70f6f8d78f2a1c5b7bf2c4b23bffcc05828a5489ea0a4b66e8190eec9120e7381d824cefdf9792495bf7fe1a6ebeef23c6af070b56b5e54cde44b7e507115a8e4f8d1d4197fd2ab7b8795bf701831f1bf0df21b3beddd2f470765a529a23aa8c4a755cb3d898d39031065f6e2bdc9c60894f12d1b79e4f13d23e79502775abfd15d54a42c59c48306ef79fec4ba76b191f4f43733fca00514d6267367a057c42e5578bffff741ffcd0c79201cbc0ce3045baa9f4241fd14809d5f32bc178bc709b6c7949394efce4b93448408059bd4204ed5cd48608d7287e692e5e8488765302fa504ccd65269f541c01086142f4a715b6a1f2fb1bbac3366d91a6900b3750e681f015256201e1173a2e6dce48bb83c114cef2bfff8c564b3836dbb0b59148274c24a041edbbfc4ce336e9eb51391ab30afa1e62d47dc11db58aca8e872fa126d13eba3729681e5cbc4b10e5faf32a7e84b8362d155a7a16f27af5750e5d223e4a004870409f02d4255973875c533f4a78b0795e813334319a7a3a4f36880a86d46f99b76b1814803f3279343814d8f980e5513038ea0f0461d9ad718afa5dfe8554e34711a00621ea67414797fc1898a3979297a8bf04e502e119fcc73353deacb4c6e910ef8ae2826767fdac30b9f4fb26fe93cc1512a0c1c0f055335f1d1026ab425e80edbe137c42759755ccde3a703fa7f8fcffe0eb0f9219bb9d2d571f9219b23711231eb48d6421e3e6cd57e0b1fb99473c415df767618026d58a73cf1f90054c681c31988ba9d4a013e1a1cf8eb9015a32c62d117db16f3e3c7424b52d8f75bd54b99d9ffe024772c10125658e814bd67e8db2075b7f663cbeca7b8b96573eec67ecc241c2c5c7f3c1e837c2685b5e733cb21201712ed0d9be67702c5f56371444fb792f10d8df6cf3b28b55b950f98123055f97c039883102a48760b25efe6c70c94ec7a020b252fe3ef5e5ceed9877973f1d7d17ae1e301dcd44822c72128ebcf149f57163901081c0e5a47a23556d88ebf51fec757f38b87fe6f68cc34bd29bb36fdc77c19e01feb061c8437bba8ff1c1c9cb8fe77d6b0bf9dd933064f2804c4635e6c7f36afa1aad138523e0e8c15d5bdeabcd68ece9fe4c06a4c98df6918dfd657e030838eca2e080443832b0703d44e454e09d0acf0d0df27e43e3f9df0a003f05d7b49102d63849de00803d2527b285d1f4f9f87d2000647663846af838b513824ab2576ec9d8e7100f1745eb3ed4fb4796142245105818d008429ce040c2376fada931a490f225c7297c58f0076a00f88a2f8c422eab67fa75cbfc4ca40be363d91df1f92d49b7bef20114aab86d3fb4c91852e833d73e64c489e44fd2bbe810f1d534e20bd7cf9fb276d0a896f5f3ae506a84d5f2831ed0c9de277079d310440f9cc564430e97a541df9188fe49b9abd8a203561e77022b6dd1ff96ed6aa883aee6f3ee00e132fdbc8c4c4ed23b5e7a1f052a803b2f052106d87bcd8940e7e687cbef6e19a72005b30762d84e37c17fe121a8846dd9325ee8df5e60bd5f30c0d3225306ff6227f06d84218debe3ee16fee70f24a9d0097a4913ac309f619832ded87858522f90ae60a84e01f592455ca38ae13ffcedcf3f14b3868fed29e7c405264d143e6704477c6824aeb2cee72d7cb8a22f56c9922b4552777833fe331cd917635534929f51768dd055ba8ab053eaff83c8265dcae994cf52b99478b471e97ebafad6f13ee52f677925e28c1742cd36baf0e61d85658e080cf5c26b86363a5002a4d535da0407a29ac5b5f8b4d730632f76e0c8bdaaa4873602bf275606813f53a82552aa44eac4bfd1a17d4639dfd3b8189643f464803781640a224b54bb3f0cecc8acca6c7196be52cd47bba012201af5a42e40d19e6f014617aa3f6306d8673ffcf12ecfc3146676103c33d41775b420e199f9a5ccd9e69f56532ad56d2f09da9dea36f59206d65e1277618b9f978836342374875ff278439766893276c298271612655093bf8df19fa8f5bea42e94337b39792813be84f84f6d91a7c22f52a86eb9605871f3d1a975ea41f42895826568bc51c4233e059792c3e9daabd2439c0e343543bef3afec0dada24458e63e369c6e42d14784b4d3d0f1a801d9c1f4d8d9d0f1ef112d1e96f5a188f9be74c55f00b8cc31466bbc07405d72e5f0907bf6f05a29c7c60e0f497f7ab2342f9d14dd82bfd0a7ac63767b27d7cada2f29aa7c7afd3e4a0745bc14863c4e973e39679bb1d016d8fabe7fcc47665810663c078ba646cd126b2824b5f2448f194d765f22bf6116073980a984ee51966eca29ddebdeb045b9d09198af0f4b427180960e4429870cbff7054e9ac0118d663e7d352393243698f55b613d8e00965438fafb4e9f2756e80caf90243a48d2b064ad4d9c443c77e05024cf84a68bbbf69ef0241d7f04fa566cb719ec6c99005a9ee89032eaa23d93f105b0c1f4ef96d83d85998efe6918bec970e158f387015843fcbe7d9fea1e748ed5ca08c06868f6649363d86100074b6865f508af811c94247fd87462fca1b5c35763e21410c705c4b1e07cb081d632943e7c1e3d4f2edc22d698e85ac112592740e03c469a7fa8875be4d92dfdd008549ff60408f9866ec4c6e9128ba031c18285686c5ef6fe31f3ae5dba8b746b7d0ad7b2f7c629fa0312155759620ef37f4bc6c349c6b46087f91fa31a4fe51583f1138c7908a344b937d0c900f058aac9b2dcc12c4e90c093b5630f462fd1a8ba4cd5251a9d91f5982a24bb3f9e8b42a6f1d01401625535d7afd9938fd84ad2701a86c81ad463c49b869012f2c9655a7a22d8a786a2c1748dd295fc289ca0ab2fc0f80e16956048c51de8d5e1720a6b4c2ed0a824b55c7f1bdae0cf278d490f0abdb350c848b88992a9e456793c853bec5d0b3623d693f65e9d12d0af4fc2519e90b5e978d3c9ef0a28649c16f6609169ddcc044bc877e50e9102a04c0570080bc99533890e0c2fee72fc7bd9f1d2ab90d079dec7fb12b43a4a0579c7cd26be8cc968659dec653c3132d026391bd139d9f9efb71baeda2200c77539cacafd6dbc9d3ff142a6940b87212ed03900e936498010f77580b62b52e6e22a2b74dbdf09535fc7e1b2c33d454b61f7fbbe9254b46b58924427e0e36464f0985de1b153adb9ecaf06b007a8dc6c5ab86e7d76c266a428b10ca65faaa1127bfbbb8566f29a697fc3178a3a9938d202ae7480f78dc79fd8eea2d261a56c17d609baf4fd5195443db72a9902abd4f488354b660eb722ceee9cd58ad881485ede5314cf48955a82d44392d8b511dcd5d7a6726b4bcae7825808c704e8a880c892f607543711c43a360c747b486899a22a731940e582c34c6746e476873e5d6ff2463a1ad7a4d266b7bc4a63843701e25d4ba789ec91073fc655f2e08ecbb91f32aeb746a37e1eefa5347ec160912223fcedeee3175728a220d08b2c314cf54569da8cf3f39032f5eb6b275c849b066f0e0ca120c41da2907fdaef2ef627e0d12834cd2756422aff55bd4bb116f5f0dca276489db87550b1e38ed98bdafecd72869bfab0eaf7d114eef5e337bd7c10b9f2c69d6d764bc7906e421edb6ed9c9ff06f58e34bc004901784489a71e00ec1d90b72819d3b55f47922a8ccd7d4c9d4f7f36fa2a196cae1998c99a9d123743b57110d0e3fb0c744bf894287a627230b00a5cce0c0db8add0b690d1862101b07e219f1e4ea5eaf4ce0cc89f65f88f078c974926819cd36804291b8cf62b69056c365ddf717f0864b46f3c7ed7e614af9f295c9fc7fe0eb3e2101412cd38fd9ebf95a7750c7ad60683a26f876e69e33a03ee7887e4e2e6e54dd5d048244aba73607e410c7ec899cd2b7b50d51f75672e90c143c63f575e644b1d6913df6930e21529778b35ec01743d95c099c76f14ed0ee6ba80b127a25697f5fc885cf6f260d299884730fdbe5e7f4b51c270b9699b5dc5f4f8eedbc5fc85d33cd343d455c3daf0300a6dced162ca522b157b3d90c82354a9778ba9445d89ded62704278e52beef9f95e7ddd25aba3afc7e47f3ee878de91ee837acab605771f4f5b72c1afa40b76bb8635310d56a2bb07a99820bf686a34e5c8ccc230c8b4a9b91e46a096dd85e16ecc40e97e19f0959846e621a4b72e5dd38104012da037c10d8c2993a94960c2d60b3bc8d8a09de0ea1e7d2b40b26c2f1446e7bfe41aaaa1878e1da14fee86d9fe51b35c719e1de5215d78868229575f5fa3b849c6fea86a2a04f9acfe106ef670e0459d044fb510424b82d3fa3ed586a052cba5356949103e945cb4da21c0ccb16b6542c2c382ef5a2935a1e9fda1451ad5907f10486f4060afca2aa4aa0aff201a49b403454d7cb4d29802884986baa3ab96280878763b51e197ca4c659d704327dd8a966c10e969ce02af81411f5968e58490de59e730ef83dab89a3a123fec9cfc8af360e27a961345dbcba8f78e322cee41079ea15628b1c382e9f765e17aade169694353d0956d87b0387bf9494f6c3881d58ca9b9558f0bbb18082d4539b747675368823213d496807fcce060c3efaa8b5b5f6abd204ff9edff6c6a1c0584908cf483165a98865e785fd0a96556c27d708bda67b057929d6d4d6913e9d64e90838b92a86addd526a9fc0135d4c162f216c22c0779bf7b0e0086e014258185b8c2ca2ddbc29af6c7bb2f3f39fc5e77680c6e0604467256477245307b8367ea480bec0c23b86a35c937717f30d3dee87a46283e75f5f3f78c74071ea2764f53f66b893f123412be41b12f8efcbfab0f48dbbbdfbba5f899d0a27ba656ab3d4f16532372806f5197b9ee60dc89b69711f88997c8c745e08fc4d9eccef93a7cc31576ea9cd0670e2e017ac84a8b2aff23b3b240e54e27046cd469a25cd2128838e8bb54a218ab7a09e0aa409576d8de1d68c30228151b86ae33a215674cd059defcde2abd498c71fb1a9a2a1319776391cbe97163fa15232e47d7c6508756b25eb81563c12b17a3477eed186bba073571c1e766e54bc79ec58a23b7a7f8d611ff75b081659b59a4486ff4518f571a8123c6545d17b200b75618a7948a703b46f375837b33f3c810b53758ccb5a76b2859709bc23da25cac032c4153fe1f3e1643018b41e733f285adfd6c392ae62d444f98100b6d22b8c3240d21ace2d1f71870eaad62cc139208548d7739920055a703de7bb49818faac984b8874c9dad3e61bd2414f95ea476e43a4f2236a65511b430345e4eefca155816af2bc16229f41cef905677a5e786d64a790bd1b2ed61698b4f92a040fcd5a896a2de9349739d5b6ad2595e028ab0ea570b21ca1c754ce95a6dab084b4a70bf18f2c1953d698770708b1c42def9becdcca4c06f3357205460bfcd901a1a0979c4dab3bebed5bdf4bfb2b7dbc968adfa08879f3cbfc96a285521c4d8fc553eb31dd84afdf382b4f5a86dcb40db0adf644affa4387b996601d657647be5d384801ef5d841da65c3fc065cec94b333d5ad8a03b89db3ec1c4c21232bfa899cc079ff59df4fd78d7216386b88e462fa40c8ba7b4a30e61f0f0fc8cc4b73cca3fa4eda9a84de6444ec58e6e338ec1747fb15ec40afe8643d325e3fd6073017f0855ab0d994ff728fe242112aff087133da467a437f29c96d0f5a9c7d5f3e158a7c9384d10f4900f015dfb1096500586da61f10183b49188971c27e24aa620a564c2128c828653981fc5e9f034e52d9cfe6c6e222d50c0cde28d2206d19f723a42f88524e7b98b9832c45bc4194ee070df3d9081639f5b91a6811e9cd52025d2d259720de8c4529f311111d911e2c0a6bf4d5294e2841b819e5d11b043d4b0844f5d6d4708901197b5804f4756917588b02d97a18616f43e1f88658cfd8a863a01ec49bc3c8c833290876039da4b4f7d48d68bfa6140a6e4c22f6503d5b15a03bde980d07cf2cf6a4c6cff391abbd24d494e7201feac8c3c4668fcf9565a18622682be0884ad162589b06f95c288ed290569cc527e5425e31d2a5bec58b53cd7ef507840a4162e3be4184a3f496f75049f55a3491cf8b1a1ca017699fa910125cf93eeba2bddde17604152f9d368bd8cc334130f94bbc70f88b8ef9178332b48f8a4c38f60c21778c08efaafc4850e0b819e68b84106e6b1fb2c99cecf181dad6aadc3db7ee7a84efe5d0c9d42dfcd1696a2b7d0eab3ebb258768ed961395576d5e84c958ca2112623875b7d998185b5844901b6a1609297410753f91e9a94361cb1617ed19b9f8e272710a1ed0981102a04540872ce7765c9db290be76af87d0efb2b0aa928e85a54be50626b6181b9ba7526a7d45e4ae8aa828d91dd917ad32c319e5a0850abff1c4c8f2d690a0fa46d98c299e91131279ad55fefd96a6031b728589c8df4c3e097568eddf76c40a6cad256d3447942e4b0218958d2363b3c38ce5b6020df4ccfa1ce2fae3892b343aa84251d1ac480f4d218dc481b3d23c49036408269c428eb5c399b8696bc079662b0c143bc562840b41829860738d3ecaa3092aa46b772cbebcf497101da277be91c042174423a6a7538368083044c27453a7469c5b2ca7c35001de8b6ad60d2abc0b3015a1a2aea46b71af2f0509e61201830f9aad4919e75c0e9aebefbf58ebb7605e942c6b210cf9129b434b999f4074a66249975cab3c23e9dfb30db820d7e629bd8d14adfb0c32bb144d2d2fa066ba613c699c469b90ace8e6d38040815ea69e575b1bc4de766023fe3b2b842a1a7f77532539d992f880725cf03767168c21ce1f4698e0cf5888c255a297731c9756795c5cdea96f97e6f28a741e310dd0e841eeef5debeabc71081ae5cc119b4484b5ac841bf61cb3da27b553f1db4968ef64379c46149ae56159243910f9c30b14c9cd1e7196a4d5bf18950333e8c7622a82d9293bb4e615aed431b40d7cb30132fcc8b4fbc68b361599fdadd228fd701ca3f44a8538487150b9a543190867d0c3b91bceedfb0156f4b020500a02125aa8a7766662a41a32fcdd3bb21a58d94fb69470f37f7e43585b0bb5450c0c9da0b88e40df21e3a12e1b11f17e223ef98829854290c5eb9fe265f860c3ec79ebf78a6921dd743daebbaf151922babb4d43fb80164937be03651dad2692c996fb81bd8ef6cc7b697d9dc47b3282a07dccc95014ebd3749bc4fbdeef468de8a5e9653a27464b4fdade2cf8daa1acf0df840266549205902376a65f295018b5aab2637a5362076aff10cedfd0d788b503bd09413b83b476e06510b422d825c62c3870dfa33aa514e4e791f3b64e293f3451900e98c5807c9f844cd57607d89eebbe71a70b85197b206ee6c68f243809488281bd00ee03758334d8d1736c74e7de7c46c1227f22c4273ab16a1f4423211550f8e96b7265af7359478ac550937c86cd2ef4f3cc2cd6618451f9c5e8c01a879223118ff7e499711cdd1f0731b70fdd7b5739cd9897498f716738b038863a50b25f5c6150c27c5536615ebf828578a29d23adcf516f836e14772f93ee6bc694f69a845f91b3bd81333c97c907c8c885822518e06fd035ac09e05a7a3706d62aad3466c923578a5746c79041029f45f99f04d4fbd003bb6805de9a8576002867b3d4d4a6af0197734baadd2ea3e903aa492167acc9259f227f84116e45d529279031316f794cd529db374cb3e59917da750ead0f3b8e9b68de0436595ec1a214ff3d37dafcf1789c65998c049722c5a4d95db7065f97b69a7e5dd57ad1e6ab7f8af21d4d5f5dc042aee323187c7e4f1d82ce11472fbdb1e2f978027dbd16940dbdff59a1eca22147fc79cf57a163a8e877d8723423e27e6c6eb13fd7f68a5ecd9902e38d0b9e6866cb01c3bf9ab89bf3160ad2dff4e9bace58f929a8676e509a2bdaf6ff9fc27a8b78971447af602f70e669ac636e62d715b541017ae194964ae5c2dd7157a8572f2417c9b89af50bb9370e87986bdf96b52e2ad0469dd3db86bea32df576404da3f3c1f5a085f7be512b24e0045e11393be74f98d2660cd5c94589360f152af1cd8a46e4320d1e885204200994515879d601eaaac3fe8c518bbad5b893b481fa43261a3cf89e14b9e3b8a928055b8b53ceb6b5e86bbb20c76ae3d9cdd002078952ecd4b89acfc3465320d6c75a7cf815dde49c3cab279fe8624f8253f98cee7519998059f6d514bac27adba9fa7b99ea998a6ee41b3631b906c532d7be0fac9f2ed29c31c99e9f30edd132f8df2650e7aaa162d21e2c1e4e2e9ea81c6c085394f7b9d706adbf5d3f99a0147e9ac7e4070624bc6add5b9f21eea422ecc48ae550b27ff94322d174b4e4ece165ba3c02b75775a746ebd046b870be555d1b636acf23b9ba4a5d7fcc7c297fb6c17356de085ba43c28208d1aef0af1488cbf65337715646a7f23381f54024f8bb9bfe387c422362ce92af0e2e2de5ac1a84f75137cd7c66ea4052981ce51a173afa843b8e7dae610622f50c51756e567645d189fdf3a5fe0fafe1d2c56b11d531d62679f10c61df024814dfd95524789ccb24469ce1280760a9bd26fc0754a477b498fee0a81c53ff1cb26ecb5f33ddc867d0fd12a41c787a5c5919e0f25cbf221d67be6a12a4b05adf4dd869befa1ea453723a4dde1e3796f2aeb3418d33208b9e8923bbd6a925ae94504a98c48b7d6127a84cc4141a5161136bc54d0dc5069af5416d383a8d12783016990d706720707f85e8b4aa178a726328e9d5b003468caf3b8cce020dae4c7196cea937cd1170f3a4049db8906257e6808ea8e0cf16051474aa6d5db0df643121c991cbfa7c0291f5591b572f3e8fe36df0a72cb4b6d9e69db7f32a1b5fa21f30a99e7742c2ccb4a1c850e19cea7e11c394c178d5055d15c0b490cd15980b97082a1026438dfdf962f14b49edc5654f3ab2a972dbbd0125e324691b711cc37b9de1445ae644f02e4a618a70248b3232afbb332b58508b7068734f523b6cf7a49211e2be59f1cf3bb2ca8b77f891d76fd3fdd820ff9193a7a5bf067e39d55814119920b19a3a8fdf5434fcd901b8565257e60e404ee02617f3d3565bfcd90f0f16a3538d20c002a7ca78580f645b75514ea4bb5076de34a1a8e05657a89830f40db422bf921d38aa287489a3250d922ec8e620ab0414d48cafa09e28f54a24ba82ba30d37174ef53ab0249f3759cf608797cc14c1ff88be8ab0580b8639eb8fccbe8d4eb298cdd5c6bb4d6473024af389685110280d6b3a39d3395132b120482b223f1040bef91ba9a51f9fa287a49ba5a13fd591f6bd88d3743d68a7b9300a984e0790aba471a66ffec552c23ca2fa118d6a5b987910706cbdd69a486b7bef9da44c291e06fc063b06313186d8f7c3c418d42cc5c499c730f39eb731a9598a7df8b11e91490d8d09bc813071e63dec391613675ecff33c94f0610fbbd94c883d6ce6f5d82eb4338fc77e3e3fdf6d5e4e9d836a937312a15ce0d7796b42a75befe89cec797cd338f43c7e3d3c1ff2bc4b64eaacef1199d42cf57c90c8d43dbdc5d455624f55cf5550f3fc27edecdbce46a82f7fe622942acefc9bc6d7ff0c0afd7e2ace5c54caf23df84da38b09161969e8c811f5f36a967c9e3ea55f7f897ecd51a7599b4f4599a37f934e3427f4d3c9c70728eba7be8f8f9028b77ca65396a8a5c71f91871db25ece66f5f4fc4d6e053d8f413e7f9b4eb29757b336e98ea74e198c424bf5652293ecd52c057d159964a20cba62da216ae95148ac0a9fc7f06fb24d28cebc6f1a6515cff3c823ce3c51290b4379393983d223fd1ee9f37cd0579149cd6c845a79ceaf6d96ea07894c136ad6dae480ac07e5e5666dbe8fe5599b50558c21776380bd3883127bd8c744263533a5fa24f0584528de5698383ef85085c33139c9317c2cf561e26c89be12cfc77efe9134421ee7135c94d8628b34c418b05411479f8a33a5faf4617f9b57777a5ea278ac17b35ed4accdf7aceefc9b0775a78735e57856334bd2e64472f3363c6b722a7cc970b3a6356b73fe043cdea69607d4e452480bca44dff305439995eefa7759647fdf161b0c7b4e203ae1ad714ddabf67ffd30fdcb3b945ee98bbb9bbe7c82f94f6b21074a3adf0e94d2ef2e9799ed771e9ba07ddbdfb29eeaed5f99c1eceb1621bd778e6bc5c18267415f8caddf89c2012286bfce0d75b1d7716ecba06251465a2972ebf2392329162cfe3b96e0c24047ad51b912bff25b6d0f7babeeff55d650c14bfd7ab07cc049e87895866507a847d8f30d1a98e3cefe46316d86b01c76623dfc76c8f12b6e4238fa885069e11f672f45eeffa91d9c8f779ef1788e27e1df0142d6575326d3623231aedffc5535cd77b52dcaf0396a222287a9ec8ffe01b0aca2370ceb682f2211441d99c70b6d4bd7f27fe84e2d177bd7bcfe36ce9fbee3fb1136750bed777e26ca96302bffb9ec7506acddad6ace1a6d3cc9a35cb238fa0e5f1850b5fce3557de239226614888898a614191b34979141027e02c9146e825411b13fe98f902ce0e428408792e4fc1c9f2469d2ac8d00a3206196974e9fe66f7c7f31596a5e5237b240a595a0d4ac1acae7a66387966e5b1362734ab2bf975df045dcaf3bcc5988d62939a6058c3d8e7dff74d30ac611873a344886be8c8a51131024274a402b18542624262f2f2e87a8c6c7f934878a40271a344488c687e3c5fa50dbdfcbe3b12abc2fa0eecb67d31900868439a529425b3d6555859ddf7816f5b625465456330269ecb23ea9ec2604cbaeffbe6ac9d6d09cdf57d0814ab955e61dda615c6e4fbe073a254ad8dbec21ac6c21a86b1ef0b6b18c65c9fb4739c9ddd765fbbbb7bceecce53745dd775b3737767263289a6cfe95ee7eeecd2dd6b6766767707634176a89c6efb6e1d13cda75fdfc9cc36afbbdfbbe77ddff7cd2c14fa73b9ac9cd057adeefef96466ee84be3dcf33a27d9e47f43d337722d3917ddeed664fe8e3c3fc7d9e6ce3fbf9ece705d5cfab554be8d03c2be4791e1da5363e77f7aeebdc3bae1a92a9999e85c215d84c347b78320fbdea6ee544ea935851bcd9f9fc1f9d4fcff33c22666666cff33c8fdff3c0392de3d073cff33c9b91e7defff03acf3dac28d2ea63a2d9c373cf9bdecf6fafba572d71b7799eb7867d8ee7795ef53c4fc9ddf0bcce465d22129d7be7eedea921474ee75e97eb72eee5d49023a7f39c1a72e47877c7ebbcceddddbdf3ee4cfa72a7b6ad7aec2a3933cac509b5867b97f32ea7061b723af7ba5c97732fa7061b723acfa9c1861cefeef8634de66e29bdf33ed0f5a2151663201893ee8576b88e3aa52831295a563c55575655c5b2b4d0482c8915e4f323036b4ce94a46c124546df3925268e41ab906284868c7acc7078ba1c059ce63636267cd28a50965a5d34be2244e268331f15e36c4c34b7229f06f422e3f90ee8582e72098211e5d7dd14a5f3c916a1f4d79db73b7aa575de5790ece32b14e43d2e4c7608d85c5a4f301c164566214b22614d4cccd35726b4b6e493bb8de9a503cb69f62ed641b06eba86e2385a6d7f41a1e3c66104827336a424da85966464da8324ab3165454b7fcc5a5eda6bae53797f65475cb5e5cda54d52d63e0d2aea2bae52e2eedaaea96ad2e6d2baa5be6e2d2bea2bae52d2e6d2caa5bd6e2d2cea2bae52c2e6d2daa5bc6e2d2dea2bae52b2e6d2eacb8b4adaa5baebab4bba86eb98a4b1b03d52d535dda5e54b73c7569bfa96ed9cda5fd4575cb545cda6054b77c814b3b8cea96a7b8b4afaa5b96bab4c5a86e33b8b4c7a86e31b8b4c9a86e2fb8b4e154b74a2eed32aadb2497b619d56dd2a57d46756bc1a58d55ddda2eed0c54b74897361ad5edd1a5ad81ea16c9a59d46757be4d2de40755bc1a5cd81ea96824b5b8dead6c8a59d55ddc62eed35aa5bd8a5cd46c7e90ed4366ad835f937effa34d28fa113dbd857fe38f2a3714d5862608241022f5c89800b10b0f28016aa5071000b2ba830e5968294063060010a4800020e8082010a70020106608200a204000a00964a20610425114200e183271e74e08483264c36d060bc4b32c0e0022549922cb0211d2139520105468cbabbbbf316a28798b28d36ba8d968ab9bfd79d06f6647ef77d84950662281379defcbee979a02cc2b48e91684bb848ca2ff2dde9b74adfb93fb826ff06263523c9bc2542cf13c3252c9b4598d6305b9df0f6dd298e725ab594cdb2a54b9fddec9cbae1b9d7759f473f57d7d53bdfc117ece592b4a63d5c84f2e7cd8a48dd6354bee47c5a292c84d5a758aca7613c62b33b9f863c3ddf13fa589f9e1ff1c7072802a09f20a2202021219a50d08e1db51d42329904b21d43434386643c7810e131349b15cd78f4e831418f990f1f457cf480000223087cfcf861e40704ff14fc0f6b2bb02f8a47441b410448221089888e88220002040908519020b620408408b14048101a2d8926a4564b52a349208112096a43865c304402cfc31e17ea2194ef44ba6ebe5b90c890a20c8a883c4d2075e70445628a4c51640228a30b1815f912ca77235d47c59d6f04340a75a0c0df29303255010554472aa802c991aa232456201d5d6143c2c2025b164916689124690b2549b8b8408915061774910106185892811777c99bf17ea1c108c6061a84c16483ab264cc4e0a0c9184e3820a30327703ce8a08c271e98f1c1933340f8002b041032204208682889a0811194d22061840d94400207964a5003004b595000b04600a0b0112500710410458e0902e8c0004c6883000370e304026815e084370c5000385030c09c03a0400701078823010890430109f0c00214b0c58005ccd10006d491d2003a529062e796c207a6dcea50610adf5941051c0b2bc8e0001666a0e2001aaa50c9d242951a1ed082162b0fd802012b4e2e40c08608b870c395087079e14a1709bce0050609e0c004430e31306189e1cb9d4f9bb0e8806bda41061c981964e0818619c264a1414c0d59a0b4d4d0c3162d639cb690b1c1a9cc0d36f8c0e506335db8fce0a54b140e5e80c80187209e7210e2cbd3103a7c39b3830e4480d9010d0f6072617828424c1823a0c41cd103549a313da82133660265c820e1439935667c48e207331488faa1024044b1090208258408c2024308b1c49921da107186093444349143e34411b9278c28028a238c88224de50976e823604de6953db8b0af144770cb59344111232314547004c91192cd82a4244a2ec020832577d46003264d3870d281074f3e00210411944620a18425004009401401983000029c500003a07000042440010b604003a4a4709ba2c20a2c38804a95161e6005022e44e0ca0b12808129062c4d8d6b197a86a6a1b3740dada5b7b453dbd0373497eed25e1a87cea19ffa4bebd03b3498e6a1c3b49886ea1e7a4c93e932ed439be91f3aaa81e8205a881ea2cf34118da6735d441bd147749a58db400748e5f3db7e5df03ba5ff7bf01381aed755a8dc29ddbbc0fefe16865ba1fc39e9eb6fe1ed34d7fbee4b6cc1656f54ae07de1c67b3d96c369b4d4a29a59457fe7dd3a960a482d17c158c5430f2e20ca83be4f294196c5ccae35364bfdedfacb838658a3f089dc856faca96b31bec9fe24dca7ee3ae053333b333b3333333773a5871d74d766766eebae7ae73f7ced9d9b99b13092396396d8eff9c57d8b123478e159e2a030e381f04255127250fa919d58f35c5c645b174834428aba494a449299e73cea9a933da14c599a2443690352997b8a8c2284a92f644d82f9334d9a0d1cb967064514654196ba8c018e2f6b84cf585d4a53c332597f7f425fffb66dfd366cd639a3f452699d734be8d041ea9c87493f9df6cddc86307fe1229edecfb28b3e67dd3e8b2b34f6c81041e95442627de38e37f5d89b3251c55c3429b20fff54bf3ebf357f92f91a9aac55907dff77f4fc599d2eb674bae7fbdabc78631f95cbf444a291ceed266ee1903c5f073d92bb3e671d67cba220c2eb25c0f4a1e71185b9491483d4e0e430b2fcd9def69bdfc4a61dfd4afc6c5a57e98c8c4d474a52a8a94ad2b27597c05fb259e87f53ccf7be21253d087df34de786be61f7b1ea59db9381b413eeca1f407f7d6746e3fe801d9858aa83454fcd0c595623b95e924ae1cc2ef6f2bb6e02e1839a693cc4ef2b054124d029f3c37d9fd6e61d79bb027733a7d2394ba231df71ae7b3ace5f4cefbc029394059baba653d20112e92df31618532d1cda9cbf61dea896aa8eb346957234e727d4f100c4399ec1f8b6fe7248df62f9365792e97cb45eb64e2ca2a9db0b0b0b01a8b7e7d02c13094b9aa8bbe948934f4f2a97c1e6b2f264549ffc65b208c8a4d66ee36f5584fd3794a79c8befc59a59bef6f9c45c5f924aba4d4d02f99435686269c35d9535de22cf62c4536e2a409e34002512130e8a78707f6469c247b17dd41bbbf4929100c4399ec9f8a4de49589b3cdcb29ec2c4993bf43149508c127e992469c249b8986aae20a893723d7f5af9732834420d187b3c450bcc9c498787b176751795d4fad1117b9c4998b612eb1c9bc3723f7c65946f6d64d5cf9df7472dd1b67dd8c48ad6bc4d7ec90426d384141444e6611071c4f23953858a08ed448679d39b8913a9809e046da51255181a7917a5a4f1880a30b2a46262c3ed6185922c122d4441d4ae046ea22838e0dcc31d217951719a81a29f522aa0aa79156aa3590801a29ac8a306f9c461a0be3ccd59b3284c88d3484b2c269a43d555a5983971bda8cd4a74a2b6f0033d566a43f55daf6a2c50a6aa440555a2639068e54165b231316a6630c183a46352278995822c1c27696787a625433457653278bab910a0505350b0d6141868badb1a9b1b49c5274de2a7ffe94190265a2e99dc8d414b03343d588e5cb1a418944ca0b8437f082b3366b73fe1499769041c3184ea38bb44e62ea08ac91ce9601ea8ca7715a1e5b76179f349ff2146fde688db4e1ecf0343a9189026a6434b49863cdc85ea8c1658db4ab9de53034f084d548bdda5926b6620b17592396971458d3ca664b1dcf160b6c8d1dd8d9231ba6781afdfb0ec8a00b041f9c48af07c16f4e127611f9a078035d6e6f4d13c6840a757710743d88f4025dfcb2409dc4254e9d51c575f166b3048d3450c1e1264c8479b1dcef13dfd288e6cb3a21e850d0f1191599e01675443ca1cb433e095c354a8309e1711ba4c72d901fb7444111ec1087ecec7dfcf80181f511410f2033213c6a6e8786b89515b9dd51c4ad9091ea3688e5d1e74a96cc9a5ba0fb592512897c7aa4bafd71eb73ddf6b80def74cb93d434a9b36c01172555b754092f91ed16bc605a2592e6d6ddce25d52de0a20d24136fe2512d0e40f75707b5031813ff396b375b22bcd96c4be4b741a844d6a407b127e107defd810f08402108892053e2e1a30704ffc3462012050122a4469380c890a22213185160a40224478e6c481624495282c10519dc25e3061a30e1a089130f3a7802c207212889304209242c410140000410c504020ce0040314a08b1214aaad8b94038049c182ae262500cc6d0164a634a08c0af24b8187156a2b619ab48069720aacc9df166d6156a86efb884b59a86edb884b1d50dd76119752a96e3b776995eab6d15cda4275db445cfa80eab6cf5c6aa5baed212e854075db425cea4275db415c1a81eab681b8f44a75db5197be50ddf60f974aa0ba6d3397c250ddb60f973255b75de6d218aadb26732996eab6c75cda54dd760f9736aeba6da84b5b86eab6c55cda3354b71de6d2a6a1ba6d1e2eed2cd56d83b9b46ba86e7b874b5b4b75db3a5cda5baadbfe72693b55b7fd7469db50dd760e97f60dd56de3706973a96edbcba5dda5baed2e97b697eab6b95cda3854b77dc3a59d4375db365cda4fd56d3b5dda5faadbde7269eb50ddb6964b7b87eab66bb8b4c154b79de5d2e6a1ba6d1a2eed30d56dcf70698ba96e5b864b1baaba6ddca5dd4375cb772eed31d52dd7716993a96ef903977699ea96ed5cda3e54b74cc7a56da6bae53a97f60fd52dcf71694755b7bc75690351ddb2072eed20aa5b96e3d216a2bae5382eed21aa5ba673699fa96e79cea54d4475cb705cda68aa5b7ee3d2ce55b7ac75691751ddb21b97b611d52db771691f51dd72072eed34d52dcbb9b4d554b71ce7d29e4075cb6c5cda4854b7bcc6a5bda6bae5ac4b3b89ea96d5b8b42950dd32072eed0a54b7bc814b9b4d75cb695cda4a54b7ac814bdb02d52da371692f51dd72062eed36d52d635dda4c54b77cc6a5dd4475cb665cda4e54b75cc6a5fd4475cb702e6d28aa5b26e3d28ea2bae5312e6d29aa5b16e3d296aa6ef9ead29ea2bae5302eed0b54b70cc6a51d51964a18810a19618a4c0385301207ccc1085c94c025acac586a49c90acb8c0fe8a787ddfaf77137fbfddc7d7252c7493ccfef7152ecf93f4e82d5245a39097c7e588c87cebff99ddd9dde9ddf9dae3befbcd13b937c7ebe9c9c14fefc0ae3a4eee7f78909bfefc702f9d820a11db2211eb31e3e20f8c145f6878be48716c8060971917c98ddc145f2ab95d9211e5c24df6567b6870f2e82808b7e70513847e8a325046f70ef7c6e3e746df5d33365403f3d56640f40a1191158055cffd9e38621c82008be3b7844d63e6ce3e9b072b281067707ef6024215c936f346b42180909c968225da6f20682408420d8b91e04c5255cc27ea3b9c112ab1c6067c8807e7abeef6fb0ef6f3c975fc9f79c844e9077f0e922f2db0235e9a16fc810280c7f5ad18822b04cb907082491be9f3d74a875228f1c4ff4ef867bcfa7dff7cdceeb3a6f4eb71fd0f7c942300462d9114a248df6830c30743a5dd7d3764f2783a083a1ac7560083e4f36e19ca1ec3c91e6855606e8b5e7303dd91e967a267c61044366c92c999925b364e6efe9f1f1f9f9010262212166e6f7616666c92c9965b62b0b7213e4e60aefce095f96b649abc08856011f3d61da74226936224fe4d195bec2e16433f7a4311294ae4d6f9c6d2d1c15a564b9137410fcc0cfddfd7ae715d40ae378c3f7e5ba9e573d8f525707e5d2939dfce4d7e5acec48a4be0265cf798393389136f4ec2b27b0cec2ae14abb4b4aa566b85a083134b6ba2f181adf9665e4d2bf06fd3ca65acca772b086a69b95c1de875567ee12227a943c3d045dac0a38cc2418eb9f3a5d60eac500d23358e6bf3d34469219bbaa184e2ee88229c5ef0d1f7f367183c31506792c1488d9bb54e72a58b4c99a881099b6df9ba6ed7755d57ab7ce224d95ab62f5b29054b279836ff4a27e9ac2e327f5291c4c69dbce5e562d157f24a369cc6126d17e1f7b7bee24ec6401cce09c411e1fe65681cae03c12f8cf5f490c964412ebfd86f8f6060da1ce2a32b9236e9874a12063e4ac2b4f936499b4fc5bebaca5aea22f33bf1939fccc9237927903b5f5ec934241288c5664a4581bbb2ea4e09e7ce29dddcf99fd7754f9ba58c755d8fd2446aad247cd44eb491b469158b6959595ddd97e5d13949def93fd2ba6e8f9ab07b29a5c4698562ccb0e6b66d20b8288876828fca740f4c9bdf4e5dc79d6dc4937438a14c82651a4e5ac349f226739cd473e7576935919eb8487e895ae2f670e7d49a35d87d59394eaab0b55aab2b623271678e8be6b3010317c544ea37e600e74f2d31c29bcce5e60cf3ebb84eebce032ef4c8c3febeaf6777cb29a79c5e85334ff7e979288fe6f7ed5726692e0508ca99c691c031c8e5a90e7820ce9bb0839801e68d335371d87ce1431d5f48b065482e6808301b000347ca0d38fe812b26cd89ef14bc9812238e1c3b5e9cb1f3459d26a82b78b89ac30cac2b92703a6850307903079c115b561c78c2ebd47e38a18a34e83c3103163f40f91cb20f813a1abaaeebba2ba400e38501e99b31a8cc5c5dd7652a33767290800b194d703953070b0d0c31856f8121c7240386902cd8042142881c5f1c088c1a0f12c82185a03d5138b2b429734689ab37ee8120564c2068524b1a566feea06163ab8c91d11b2a36535084e0652a3650c83ca1cb546cd0b02923ed0ce94172d18137f241f9619b3754312e6f5c5bc891f2b6bc19837e3d2e538959e2d2cb5462d208d1c1853b45a46933831923d6c861f485c75185c5962f6e8ce12285865167bbec4ebb42cb04be2d5f842133a9c4784165460dd7130d5da6d222cc7d5da6d202897b7ba32e0ca3967d6c847e792a8e5518278b2f0edbcae16a035dd6b0628d71d28983c5957d5371a6be08c3e9727dd3f5bee9d20f7ceec1e3846f078931263227c021ecdb5dec56e13bb7fb5825e2c19e4c97074d2d806fe5bb9ee73df1264d37d9c47bffe941d3ad12abdd7f6114c19dc738893b30dbdd93bedb2332a4e63fad4c3e99dfc4e7ac83babcce05ba3b0cfcacf5afeb5ca0f884ef5cff2a3ca5bb4feb60cba9efbf8f69a44d63bf27ce963cef9bc6177fb5209bf0065e998f2542975113e1105c1c2da5a858d162bab245e3d0927ac24a85b3a54fc9c927ce48e0d1d634468d4c3d766ed6feb6992e428544514d59dd7e6e28cf93603a2bd75abdb50667e6de9a4e63cdb937994576612c293d89bcac89227a0a2594952c2325a3726fa454d59a36b989c4e3d8df663a89ac52a68bf4f3982ed233694796813374fb659544aa61f5d41c3797cdcdb1e6c471a9141d32edb3c7749996c2658143ceed3702967ca8040c1d28a95388460000002011400083160000200c0a070422911c04e248177d0714000f65783a64523215082391381848510c84530c432110c22086102619320ca2191b5000fe119b2f79ec20e98583a718e11691fa39be5f84663d1e7d5568c5c2e90c46c61fc91f537bfc407132d6e8571cb37ea078d81a85a8fe08d532b182b5998d69013d9c71789771c5122b9c17527f21b526f5b4e6b465ac4ddf395a537c3198b9296b86b57126b6c05b3df56f9fd74c84817f9900714e928259802658f5a4ff6f9541ad2b84f154e13037523a695e9f2754a5deec90a13f335a901a3be75bd0f54a724f810c69536d4d04212658b9171312f131e35024d4432e96ee8ad01704a843c4b94703ab55e0588fa1e8a38756627c2b9605bb7c5a310961b7cc6c6e78494891b7c06226f1df093469c07ccec0b16e634ce30f42f44c659e1ddcde70e4e3946f8b495d6ca37752f85236b222e838da1db0239b2390acde82ba8f431446d84acb558c7d2889d50fa64209075818a604537fd45d2ebd4fb748845fe54049309847ada679d79a6003ddfe933419ef93dae1aa41667381343252e3fb92ce76e1491e6b7bacc8b7393f999f6259c70412ffe03d4f3515225a586a51d6feedcf6c36189bee722c5839181998b5a1102b6e411e603f7f0e7af2cdada2827c62e90268448bf44887012665473272323d353ad65e207044bff3b76984da6a5584a625ec990dad613f477212189e01be8c37d06c80662b6d2ae4e82c990bd296c850da2f6ebd3ee6b3229660410843389ce78222091d201fcbce0ae0a4e77d2e330c15cbf9babef1e4c62485c2818e097c08ae0b90b40c5a02d552c262f557b55b37ad1f6289b77dcc81ec3d8c4dabfe0870de6b9757b0a7cf1ad492d8001ea9ce71494afe960758a8ec0a4d180334b21db9e049b1be4dac63e01bac7ad1c96e2154ca89d4d63d0775dad7ea88266ddd9921ff03235860c870cf35dc2553f666e9f5a1a359c544021f178fb55e4fb2af9a357fb5049bfd32d771df33cf334f0fed50f2e765404037058271620e6312754c01d375a736cb9e2d193ea3fb8e1776a82a5f2f9bc248828593022cb73661060810d2b1ed123fe001af3340a59303ba729ce3c9ca6ae97c74964e9d87fbcdeedaf805999ba5a38d6a3507d9a0d81f747e7a67b1cb4309e6bd7709f4b39176940d24ef7bb9c43cc8c240fcab1be31190c652caecbd84b3ef5f8c071c7f4338422e878481383f05dc987019d0232ac0856628df9a39c756dfa50b696367b4da19a9c14636c2d64af06ef86828383b3810c10601af19e6e9e9a52e632edd6502a897339fe73e4ff7f794598e517d9a8980d3bbf67959b26054563f2c0a90fd2e6caba6eeac97811db36c4df002c306c30c50868753b90adf445a62d87c71b5df90056e6506f3fa22b9642237c7920c04f2ad66a1dd70e87efc0e440b2b37748938e61686ebcf81f62ec716c6a449420785d09109fcab8211d1932523a47fb19207ff60e606f1f0ab329a71b13a23da45c5472f9a860dc1320e71daa154cae3e1ed0c26f0cb99c080e91e746fa3f99c4781d35c713a4b8270a16b712e3424e1e4ac969caf2951c96bac26d3189f5db3db9b95d41d4b0d9c465954e5a5a369035256e239b3153a57238a030dd19885b3501f60d45fa02b4cf21e2c4079808dbb9515fd47f4894987c633d1ad70c06862f42decc13071533f2c63a1c685854cfd38493f6bc9e5349b871eedb7e815f5e1520a3f31f6edce2280bc18f5922a8a170b152a5624664c55f1bd2a8c718590d154b03d03371f2f34837e1a926e954fb68cdeddc55ad52aea3d67062c13655f44d7978a1483347967030a30163b86a010ec9a4da939b3b4cf66d5ccd293de65caf3be3f1e2343a37fe7fdf05125815cac8b55ce2a431633010d481c5df059d2be3de0718ec276d215b6321612428f87c7c4617cf6b56222519974dfbee97734e52b1a3890401833e45f2f9fe06046cf8b81ddf2be892545fb65119d7887e95a257dc4ea2640d781c59ffa767c1e10be6fe4da8f33798c84612ffcfe3548d501d4bf576bbdefdbf43fe1f77dd4c718c8f755b895c6365ed1dbad8381a20db3ce8325f4787dae2bd8298b08eeeb50c4b25ad4e44e2d97f771f81680b6ff3c468f26eb627d0fba81bf34dff129a0facfccf1b71d8bf35acc5a75775c4a98692999208fb8f27e6ac4337016a171b90f9466e4f8e9ff72e6f9e3907eb2f29c5b817f8c01a5e11bfd238ffda9aaf282e86f70e87f6e79fb036d285b5403785e019e3e9f019ece2a6c0e59f8bb2016025be3ee1e29940560bead25b3b381d3919cdd91103a32f03a6a3546e8bcd9115f960cbfe89ac14a099df3f3e7f5f3a005f4808a3d26d2c1cdb5f0fe3a6122687a55a1293b2d72b80ba563dd0b9bbd481d32572cff070b09c5adb6e4aa242db23c49688e19cf0c895b090a37c160276410ed5723ad5afa835b9f3d1eb8a3df8691871fe4795b9968dbbb4decd698d8726e398ddb84634e415354c3b88d2d3459d23bf03de538685e1807535aaf341b2fb70919a90adfd36d82e1a88a089da0996d824e814511a080054bb6b2d4ac78215e90654433bbb1b1fe4ee9c2064ade943968e0d4cb630296560a2c4fca83eab3560828230277eac1d61224ac55eee676f298149e6772b53266bf0d6b47359bd004df6a2c534fccafec66bb18a65988d6ac84f566e2a40121162c11310c0cdfdd645f69d3c7701947a82116e12a8a94ffb363bf0e9b30fef63c3acdd4615e567fc8c6939fd8e3950f0fcb341080306317c8d97d6a716da352a5de4ff3794eaac4a0343a42fd5db26c349bcd80bd0124260d57952ddad545980fea828cc28f0265267960d8f89ae3bcde8151e1812e8c5510a2c4fd21aecd6e6dd6234482f83a5c1c0cd7ba0f6d7a506b8a4ce3a609b4628c9e8a2383943995df99fef146ce42a104db1061e4677dcda006328ee7bc2771d29ece5f8971c56300fdf1d3242868c845974b551ff2151a8cb280f0cc2075b8dc2d8a5806c370d115a20a9c546f3e9d998b7c90fe3306f558c6454d207ae51b0a514e579f05553c259a83217f068c3997434c6a307195a4ec17f5dcf04f137ce10297df7114f4bfbea3baf0b8f25042b84670a94d90bce6dafec1318c481d7d25e4b155181af2aab2b8ffb1f4551544d5fa5e16c8141d30cb5fb5a9dd00fab5f9bb37bc658009b62bdc7b0bfaf34b4a4e8fe239ead0968fab92a73050ab64d521b0e0a4656ec944bb35138a1a3e33f369f70980feb26c69a124c407824ebc035965d114ef6258d81175d3ba17756db5d69e5a0b427f039c7640f8541b8e12123d012837a8ad7e6955bc2bc56bc5826a4538bdbeb66114e104cdb134b3e0e15d7f58978b85f5c5d556515b54b7f52c67b338e3ae502e93c3257c0babe0b1ddd8e2bf86fe995403c4d8de3a876b92a8821afe47f812138cf3252ec9be34b3e0a6e07f48f6d1b881316c97b81dab215dacac75b80e40552c586dd1f2ef997adccf62932415d645de4ae27ee25beddad8a34e707de5948c2d559fd4b974a0644da4b64b54da45f6288aac52d9704c38cd364e5a46a25252cba012a1c8c4fae605165e241df31d11be60f7f0d71e3c981f2298dde1ae60495fb5b2165a7580d8f3b32f35a587e4034709081a38ce88be45f34b31dd85232b2b3059b10b22f9c2c50468882d0ae025615cc473d4a14a61076380000701b35e8285fa602dea80850502bef6ae6535645d258bae7d3e269941d8126a813bd24bac76d018d041a6b7258413a49f6b8a6c3611da3c7547a575a2825942539373cf188d96a5e0852e9db4acf01b52c2c29ced8f9f71bd360416c4fdfa9d4b3c152d0fb220e2b12f1bc376a197a4093fd558d2adc3e839fd6050f637276a83d7758df9a3b784cf2492ed6b2eea56d769ecf287db1c2e9a0f453c65c746985501fd6ce3498275c2bd57d2aa3ed12517af954593c6169b794c812c29515866aa7465b4569bdf04a80da439d6e9f6ba7a21edaf63a7130b0474ad4a822d850f8d1553549038f049aec7ad9245b456e8abf8c9eed2c716794a016348594e0fd48f3bbfcfe84bf82f65e1503cecb947d8cc1654278ffa7237b67d4aa97853d04bc9a736629e693d794f6e43711d02220c05c2dcd5fafa8640181540f2f49453ce9419b8dee6cfa512466834ee9e7bb937d3102a504ea62803561aa2084f4ce4ce52010c1a6ccca3b54f5015773f530ea1537f5060370584242c1d845e41a20c709e905e87bde3b6935e3b32c46a951ae533ec7a01a1e2873ab8550d5801471ed35795dced4aaa8c1c312a83251d4a84f276d6a94b9aae182e0b7ea72b184eddd286488663154d12e1697b806e8b8a90ea150c392164183e32afc05733ed8292420c87173ce394774ecd0b032405bdb7abe30529106f868e3a146836661cf7615241912fd2b9f5bfa5c1852cb3aaee8b87023f1555d2832d6db526a460982282923be9d598603f25cf6acb03a71b0d9387f1d655a223b3c47261a677d1808fff37cfb8ff39e7734d73ac7a073a90060cf4447920822c10f4e11f196de8277df9d4e20e761508747cf8d63e62461a431135d9fc4f2770f54674b64a7b003c8bab13812d367173c5d96d8395aab241f10efa96109f9d3cf674dc3474556c461dac57f00cdce6678bc99d842ceea7fb5dc89a35e9a0570ff5d9c91d7b64213f6dc672c1656100a7b3223516add60839287c3e6946b40535ae799aaf0df1a5d88dd611a8e4f5684458be52b6e15917d98e8cd1eccc47ba0cba9f1972d348dfb07c5fe0646e88685b68868bfbf3dc36b261c560ff497e5ddc71ba757a1d060dd0d3e9d8b3c9bbfbf354c460cb8c33d1571b47f97c1de8a959e53f3410a8836d7de7b6d3ddc6aee4d57c1df53fac4ba00de3bbd88ebdd9909f36636db9ec74b7f90abb9935e7296dc51834185b663a0cedf69ad03d0540a951c639bff9f224b25f876a4cb6a7964acab129a6853a2b54eb72897925b5844f589a897fafa40238d3708ab591f467fa2b952bc0e56f31b07efa71851e80e2b47ba2ad49a63265faf344b014756878b8f5fc3c91c0a09954687a2d7896c4eef4bd18951a43253d6cd1dc3ddc8a0c764afa8b65a00087fe415b70926a3a995b113ade89b8a59aaa42fbab28302538926a648caaafff58e306434092d04c233c7ebe0e789a0f1d6d9eae46dd8a6325f383d36e48ef4a6131a4fa204ccefaab8b87d7bb1c91ea08ae86229f364a39b68a58729e0ff2d352393412a547b192f4a14cb6668ae8f380e12d19325182a15add83bab2099e09f3e2dd5275f26c8bda0baa5a28b81721463728ef7cf2c90101d540b12c276948413f0474905d5e7206a8215c8d8e559865a082bbadf0d2bb8500fcba31f8d5139548bff03045de92c42236892de493e813caccb82980b031dfde3ae48ef6bc49c5c6332468f4b2105689c55132e645c9c88ea186d809a54b5742d11c32ae7fc6a0d3038ed887ab7be650817ca585c100af89aa50e25fdd3b7765e7cdde70ff32ed3c32330504e7cf6f983f369018d24fe3f7013cef17c845e5d8dacfa9ba812a72b5d9a0fbe3d9a5d47bcbf67b43d2065587e33508108bd4c53a00046b63ba709e6ecaa32afe5b6c87e03aec259f716a45406bfe56adc8f5e329f1a4b80701c4e9f6ac2a881d6dc126891634a0b77ddc18f214b347d6c692916b5dfbc8414e7c64399628e65162618b1e88169e013c8815d6ba21f9790854513cbe568f00ae14ec9bfa2442ceca8c6ad5c8824ace7f569bdcf5f2abe63745ab65149bc3fbe6c0b5785c116ef208ef017d02f5aff1636cecaa05e09e5bb522e0dbe0d1ed924dcbd121262bc9ad2a6a8748165ba15c542cbea0d5fe81d826c4d536879d1f5fe8584108012736c235bdf7927a704d5bb62d9d947a81058701ae58433890a51cf8e217fc312e5b169ded94efe6b6481aae83cd820cba68c406d27f90f8891cf5c13594748a56c67e26d5fde3d11f37910e98890daa9d4306f84d6c0e983429a0a2f5819c677b2e65b0c472a7778341b25f7f42443d38e0736f00be78c809c5e7c8ef3de915ad25268be88e178ae67bb5da915005260b9c24daaa28b84b517ca3a55d0cba7e0b3c546451c25141474f0aecfc3d10c0b37087c20f050ef77ea44268241aefcbbed82ed3d95871d01df3ac4ac58122415b720ca3115305c31c708ee8df707fa8422a20e721eb102814b232586ed9b606b307e9beb259e7f1aee62038e737fe2b99697c188aa42a7620bbe2fb6c04501a51b233698b5e5f515f191774ec0e9c028ccf5562ee5c4a4fa0f64fa85c186e9867beadd179e8124f63f7b85212459b236622de6454180ac475b59109057721a0826b8d51adaf8866fc028aff5616480418e4ed45694462b7eb5e963666ca921b3e5418354a3bd6ea488282b83b5826c9bdd8e2848176d789afd4f7e62b9fd17e2375203e3fd26366b70fca43bacac9b7d0560c57d5fb5fa7314162be7fb4ec70386974d8e037d22f7fa70e2a7df0b885d1e9a9233508e3012c0d230dae0d2baceaa3be857f4576969d40e57daf6240f54d9f37bdbab66bed2300e6574b711c8b703c6856961c039da44a855cddd5c8e8a7bffc5deddf1c7523400ecca6bfb749aa214f524530011cc91e4422f1e8584c75967d9ef5b8f416ef16af03942ada14299c3d4d70a59bcb40b460a92ad0b239513e7d48f4ebd974a4e5479d1eeb4178d41ee417c1d011242c4e17eacd8249955a4aa9f7540bc5b8e430fa8d8974bd850cd48171e1f5024996bebff4f338f0a024d13262ab3fe56807bfa9d4d5979a5f0b42ab3b04d4f19c474e80cf1fb2e8d9e612723babb57e66d6a326a0db6a6e18b48351d6fa394a71ca174aab190c94ae5d589f04c8f2d9a12b18f53150f2899e89ccb27ff91ddce3345afded292707144e392b581e4849ed43bfc051babf6463d175bc994434edaf9c6789fb9850028b8d74f431a033c8e6763268b4027f1c9108f6f218414ae1e1b1b762841fb748891b8adb421803f11c2bdb12c6a0d42e5d5de84396f6cf6d407092c3bf5c406b1bc1cecd31dd990da0f075eca171d3f0ada99d16145efc1e7da565301de1ed10a915d77e2ac5efe45430a240046b0145f58b75ccb4d98960109ad9263cfbdb598f08694c1011ac47e0fe1290b203faef9bab3fe1f9f82f6461ae1304f4bdaf6e6f949130f250fa0123b910dee0e62d0d3e0dc91a2cbae33d0efcf4c053d16d1233bc2091764ca7b85f29e8e14d2747d8564829f99310d28009c8836e39e3af0ce757ce1bf85c7238f822f29137dbcc37d3f0342207516ba79c607eb919e97a013257772e171eb84da9212a364dd1bf54d7d4e106c0c0be94eed395d5aa682a1f8b8b3421c6b59bfbf11b8bc8536d05bd80da44fd002548db3d605608e0bd664241cf19744d676dc5125effb00810ef332c47d0cb9484fd46715e40f9eb6817686a3084a3a40070ef204b109a11b326294815931e4c86c9ffaf6d0a314788d92c93b3bfca1a1e1733270a194cd274a336678e8833e0bc51b77e792cf4c0e9dc5cf643f2f5d03a93b9dc4025ef80f9443763b77f8ca09c5c5d2380f3f05d29c8c0df2d5a6e49ca33613865edc8527eaed125f0af8cd92d5c0556385ee6ee189796afb64d8190dc57d4f4b8b5f93dfe4d7c07231967daf15e21827c85cac6a977058356ecce0d1cd92f60b0700bac30df9b560702215826dd6d86f07270bdccfd475a912f6e0ec02a5935dfefb1138efd8a745008b223b1886fc8015d5718d48b860db05955b1074d92bafe5823d51344650853d86f4ff773c46aab645fae90714743de5361d050e21d11f9c4a5509ef60eabb162a204d1fb1243bfabd86b7ea3a1da566e56ae370b83acbed768aed29b3b18a45d1a87e23fcf0c550dbd663f581f34b0fd8c8c6e296c0ead83186c62840143cfb76bf7d3f2a70b7cf35b61311760ca9bd6311befacd0281ea17d8a0a6aedccf4d9255c4f529685987d37918fb2e5504d174c4ab5ed72a75d7e4a2d60677214b58f653f960525addcd7a4eb9603793e4305f5cbf7bf91e8b57e498a70834cfde331112142f6e9e51101fae817b11ae2584a8f8aa06213fb39b55027257e7c801c87fee64ba66701d1cef47e7840ece7d085265252be04958ecefe3dce6e3c29c0147d778536f985b43f85f163575d5e2b783386b5e0aea373ad7d5e83c5202e9562ef0a4de9107aa50f164eacba26e784292ea16198d270a14ed60506af0566f77b3cbb48a4f75689ec8861823376d0f7873af70016988a536dec84c6533fa55ef49e277c01b5982332113856c41fb8cba8b2817ea879a3d2f34aafcf73255dcac858771cd324fe60458d3e60f29dccdac5c9a0753a2f7a60238f6bd422853cb438690c929191a91a85529bf1992bf750c630517988ef7400ab85d6d35119be8259496369008aaa0a8af84db509b1c38abdc6d813bfba97c3f62237dc27d85030c719cce17a9fe97eea3e0a3824c159450c19dab45b7a648ebafc68328c6ac55149e703da4d0bad530f4ba28ce731296b5182553000642a9bac31709a7e39f3580e94fecafee734d05d04e2982173771a08f99ddd85506839e176ab380135759333bd4fdbb9ecccfcb4a15d5aca3ded4ee24cc6d3362e39ff3a6d688796644ebb9238a3e0b48dcbceb04d1bda2d2366d3964d99b4f4e4ed42e0554a73bb584008c9ec0a8610080903287e10718edbe0c30e7e045be3af47a372784dfba8f18ac8f5ab2491b254386530108451d5902513953d47bdc9bbed179cc8e0f0b93fef2a6ff01183a8691e15096efba1fd99295c12ef159145b4420cad3f90943d4b5aa76a425496a63f05041667c8d9a59e89d50b24ccd53d85d4c6fbc03fe4575584f00aef5b9ca2362312ef0a3858e5a32d9d23bc5971596cdf3864e1c5fcd43d2fdff24eb2b3b63fe861cc75f2240de001c5ca56e1321ca4e467f3c2197100ab2592dcdb213e71cb2a0d177842ff98c70ad309d398573abf47994866c8007b79d2fbc1dd24da88b7d24ce411dc6a516bf169c8103d6429ebe638fd4cd24905bae87a8c3d743dc87d753ec43e5d1e635f1d1f739faec7b04ff783b85fc763eca1e321f7aafb31f6e9f210fb75fb98fb743d847dba1fe2de3a1e631f1d8fb9a7ae8fb14797c7d8afeb63eea1e321ecd1f918f7eab21f232b13018c0ff4d0eb1bd5488fd614f64951d4babec83d3f222295dd63765f9518d14ec01105716d40ace3659687e27f6b6aea84a772aac0727bec47100faecf492ea65d926ed5d5d34ed6a6db3c7d1035a16591f08f09cedf0abad4fcaf5086b546781f4dbd3278d4fcde8f531596b3b992efc5a7113a7369a14bdb26a8f1080b1e13a7e803776389eb96edb7a8d40177bc3be0f28bf2a64bb690ae190fba85b10021f482bfbb1c7d1e955c6cd6ee17caeb2e8b397c6e45c76cf8528db8f74cbde4d551cb164c9c1eba3d480f7058fc597998bdcc36f5b5f1f04a179d2a70f1edae5dcf3dd3bea8c0868f66d3e7be57de0ac46f77715ef7cd07d4c5a3c988b398862534689d06408389a270b6369a66a5378745b9f2aa86fcb9c6db3b3eed580dc6552f875da3ed11740af332fffbe3fe7098a4f219cb45c93561d1fea806100b8dcc67ba65413e3b905b9899ffa7e3eb495d46ce782831787da586aa8c3b5cc5bf58b77ddff7d039818060df2198e9858cd961199b31124b978dcf4f11589bf4a3888301a14e342ca4bbd5e6b89611b5c353753fc7ae0c413d416477c31805bc64138fb9247c8502644c0224b99c80fdea37828e1253b892658b9591fe010fed1f42e8a28b7ac14301b45b7d7e246cf046cd92c6084827360b411402a22547a78ea63a80962b2fc60c69c3391d7c0b2429b534e48206b9f2581d805544da231ba2a4d9f702a23627c70210f08e0af8dc9214ae9063c5afa96249cd14f94fcbe6325049f256da42e24c9220fb7be600219ee5c06c7ee57f59463b657e9bd50cb1cfdab457ce9eb3cc12995a887d5980bc65c1ac51f99810909ce80ae88e04da59d6476b8a644bab221aa502734575203b12c05e791f6d33322dac0ac1140b981b5581ec8800fbe5f5e00dc869655544575a682e7405304702ed2deb83352045ab7513ad7201b9d04ba01d0b69b7bc8ed600292dd795e80a85e48056817e50a8bdf23a9a2624b6b86a82291422075a07da4321f6cacac19a23b3e5ba88a6a490dc2835d0e3802d6459650171444955fa3a7d1615d18eac89c0f4447185740ade65d4e17a8c4c2ff015ca101846e8a6819660be959e65af6038f59e7f1b01ac2ce956fdd8ea06471127a6a88b80edcdda7bbef0027f71ebb0925024c4dbaad36cba3b58df4f1dcb06cb06c2b15063d00a0e8403cb450380767aebe024c6ab4735241ed1e28059b191ef07077961cc4c64c9f79389b8103023b0e6fbcb212b08981735f3fd71909503990bac5c7e58c49623662246fe7f7964a50033b131f71f0e9942cca8a895eb0f87bc34664860c8f5cb455c123122b273fde5212b88988bdaf97e701095838c05a67cfe78648b11732256fe9f3cb2420823b135f71f839452c8acd8caf78781b830642432e4f5cf445e0a1811b373fde79095440c8b9af97e18888aa3cc45562ebf49b42937212762e4ff9747560a30131b73ffe19029c48c8a5ab9fe70c84b638604865cbf5cc4251123223bd75f1eb28288f9132767dd46b245882d72780e6efd77aad265eedb4b84e8dda55723b69d2280c62ef2fab0e90942f5fa2557039b7e24a8c62d71f158ec2d0274dcd2ab8f854e2040eb2ebc7258e83501b5de920b876d6f13a2e12cb89e58761221747ec9cbc34e4f11aae9975c2c58f42b813ab384abc566bf08d131165e5c2c7a81107a73e9d560a1af09a8774bae1c167b35b1349fe414cff74d81cc5ed8c9b94cd2f00a3a4efecc826e73f4b7dcd74007c30dc37521bb78fc429fa85e2f633221b82062d220578d080a74e9963b505627145cd330a94ac03ae50f81a5bb055c3cc7f0b5c399ad7d31c3518ea81363acd0ed1e6b43403afc3caae17c382a1f4ea7127f8c7c2a6de48e7f54e9e96341feebf8a60ea3573054867aa4f0838be382b17bbaa49258a856e19b386677426b161529f0c858dd04997d417a6d471caf11dc094e0e72d8f18dc4f5e67a3449ecfbf89cc04939b7397ae1565534992f018fa675bacaa3d3d2102954e6eeee8a6f6b21f9a4738aee0c796ce9bf98727e73ffc772758621db8d34bc8db42bbda0567ace869a44dc142b048c7ee9a8bb6834880fc80796165d6822176e2acc124224701275b491160458d4917ba6ff1137ef0cab2a2bc6d013b84e7e1e29c3163c961da8921edd5784d84a0f9efa0432db155379f1266fbadb50cb7e0e083f878aed2f9b14cc17c782585087ad4d33457711e1729f6084f1887d42fd4644d6a7ec756a363654d63c8ecd94676ce0e4bd1067169fa6fab643d27d224cdef44a908f2715c3ebbe35adc470a532f021433382ccf21f2968b384f46825ef81aaa80c2d3339b5efbf6b44ccb07a596eb14670e09adfe230569e9b5a8d16b1b8357d966c8ca6dfdf7baaab977e4397553c99d4c5650c0c6f1ba113e1723ec0853073d33113d9e97cae7e9b6e11d093bf854f3f6391702bb13ed261bd4bb968eb0a9b736cfc69b186dd11f2d60d349226e25112ec0d278f5bdcebfedc39492420eb69c221d67ae5c5bc2ec8de209df94549534b745f9531d56441e1926e3709cf2e87dd61356f6a6027539e119df563d19f63a28ba7af9e9ec79e3ecda97d00bb694adaa66a9f55ec31705632f84ca7c7d07dc6075e7754df867dafb4e8b095372e3c6db295cab859bda9df10201f79f6120107b8adc98be26969ffeb21611dfd7e7794a884ba5c9fc7fa3c75734a6104cfb910fe75c748e1fb1e59569a5267d9e96f054bb3cbab20b38882825bde1df1f4f62d6f61710444c18716487014c517a058f70fefe79e0be48034f5524de6989b7d50b7b65457bde92ff58026f60c18a9cd8c3a11390f64eaca5db8f721d5e137403cd7adb664bf5833941d4e73023ec694802400d29c5603c065d92ba5176ec2eceeca28c5c4265f84007eac27c14eb80a1d4acf4bca5e6cb240b16510cec5a468e64b4f7b7cbca46b7656195eb83658d7b25f7f179e6122c3fcf88ded36270086c9e67c47caf7c66c96e84799d51f25747716da79bb600eef7539d6c8fc9bcf4ea7c8d40c7a06b40d20390144f8f8f54b65f1306b07341e2b935b88592092318267b14509eebb573b5bb439a0cff63a25feae606a0096c4761b67a0cc28dc5d77043b944e5aa279045c217a39c17f93e0b6eb2481ca89c2e5c2b9794690fe9a8e821ed412200eb7684de10e8fc44de51706fdfd8a75b326ba1b32c53e083709700719baf45b5f30a653bfb002002cf8b21310db1773bc429442bee053a8a2dcf438768002021315fe01d43c7b50f26d4ada50a2e5d066ba12e27712f076df2f15685955586f194189b8c005c5a22e0eaf5a114677c1052c12b9c9d752666d3b99418d5ed2972214a32eb4991308974b6e11085db7873389a8598a6bb5d72bd447fee13dceeaa09a8a96a51326abb4d9e9c608217e9e9b8e2c6915c2445f4df18f6b43452faa6b3d77596e34caa3cf92ae04fbfc0227c91e9c087611612e5541b93943b1d3cac13819d66aa9b0130b8a7c55b2bced8f63134cb6076c2443a918f5dbd52f16b1958410250f56b4a0639b2e7ff484ebbf8f41d76058c20f76d70a1469d3165c51da10067bcf77c485ced737cb06d4f94d8eac408d2235c2ad2cdd3f4526fb7cf44d6a0090ed7bfca0319af4b2144ba13806a764a4e1fe907bb8193a4b4e1cfb13e0c5b85013683ddc08bac6748345c19d04a1d515e11d55e39d4b6f0b79e6077f73823bd2a506e076b73a60728409d622d6b775db016fbfb6fbe56ea58e550a825323d8b552dce3e70657add147d15b1fd00159a2967242fefa37d8022d17a0112e373a3201fc1dd32cca4edd3dd9dd8eb20037d12545596dc1818bbf09b676abcc273ca9b8a08d7761a8515add6582f9c7249f8a3145a461ec83d8ea4aec5adbb7035d8e66e8e619e20b379d7ae071d4838234b7362f981f389dc18afb8299f4ccd6b4c5f308149f2fcd89e888f64d6bd44adad62f3c7a5eac3ace4a7521248976edcb06a44083741105aff0f7c2a6fa71f3dea9287372a81f17e29991e3bfde777668a19ec16648c4fb21c21853aca3c045f3ede91acf102aaabcfff8c6a8ad62c1e612f9e741c102f8ebf1c02ad66fc5af60902877f279094e312954adc70c1ef7d12b855c2333d14c68732ab73a8c93e00c94565b1e755ccf4113c88e8d47c432f6006c326d8359a3093b6ebeb9512414233e72f400a1934174c2d82b1cbb4df7cc3315e84a12b50de71b60605ba6ff780859cd2fe063eb05afcb6e383537566752976787a58db6be0eb88d1884912e51950740c1bdc948cdc01bc7da5a585398132b9dbccc96ecad097c85656481f4042b28b8cbb2013d07064bc1199c3f74968034c15a14781f8086f0df43389122ab3ece51ae1b83874d59c99802d05b34d4fd06e9ad58cbf487a4e78d23d6ed0b4427e1ff0f47c2247cb90eca4688ccc5629b10dc830dc255a72dd65c0f40a508b24deb0e5008183dc1061ca6d06408b30208cc00e0bb5978aa5ef7adb862434e8f2d5fe71aa0a57063f4333408ec128244d7c05d253c78de38249c12bec676f1ed2aa71e350178565646338fbe1e155fe52679d282a68e05be44483e463701174012fcce3af8e56e49b4697ffa7df43feb06ee718a7d6e1615169634f3b109599fd70fe49dc9d55215585d637297b086ca92faebf359e77dea47703bbecb70a607f44332795f64e5c267f33db1e44420ec4703d661692d9b6e88d0e4f4ab55d9c0631885b1f44f36a2be002ffc01c89524ce544486aa82e15ac9884a7ef6ce2e5640d3bd334eac12905426b2b771418a99d924c55859504643e828a40d69de91d013a2d62c61fd5a958ee518aba792ffe79813305fab11fe67f9cd2250efaeddd98264b3064ab3db1873b13a0d124fb6a171b504f9b624cac3c6082ade878d6632a4f07950cd4ba95366c1bd42bf96ffd2d1b56b5483c140c0187a37edb84df314d538f61d0b4433a04b174b8390f5b641b25aa159e81835f8832ab19d3e679fc4914938ab897158bc2e2b309112ce6f69a542c36bf861df3c06ebdc296a5189fc2dc2eb5748110a7e749a4622e8018020d22aeac517a42912d56471d784d669a7c4386cf52d072fe5113700806d77b59cb2089786d9439212740acc868e6f7a02fe30c8f6ebe06bf52fa5328503819945d970a0f970e4355dbd9464a31f90a689e7cb3c7c3435ab31656d667077cc1060f53a6aae3a6d2a767cd050bd367b101b0041ad2fe2bb23f76944051e6df5a0c49a329167b4728f7ac7ab2f0386e825b3381c4ac68048d0410f03c65b004cd2b8395cc66472fd3513a05913f8e3ad944c8d45fc5ab1d12fc6b94c6ee8234cb56ea083fd6e7142793418a253a032874bc0a2efbd0753e6345a4b53d95403c4d40a9ac3c034d539e661ed2ff06688789e6ac1b8e76a4a3af3b940cde3c5fcc5f41526002e6975a8123c3647b0e1475a5390bbfa7c6899056bccb9ea0695e38366003dc56a62c658b1b4d990cd95554f7b33c472aed4437dc505eda3487b4e57dc61f3d7e9dfac66715275fbb41a68f965a002f3c221442f3864d53cf10a1bf5318344b1decb3999b39e1c06a8738a439429f5d83f966573582f050754ca212366ddb19e5784ab41ce278c187a92bb2a24b2bef50dfd582f2624fa81ec1206bc7fa2cebe5f5b1b66b1a3c424dc7228d77ed211341f90d6a7932a5c5f0b1330eec3d2f1ebdc1d78e9f3f69c31f0b268bc545794a4a76b19df37ef42d987c74a19752915a78aa64fc5c016a683ae0ca14d1a4e6582ba079f862e9df0d5635283e04e7c839350b4c1c60978050ae78f13b08fdf37e155b36fc2cf5c7e5d72eaecf2c7573e56ada22f4fa491fcf05ec4f820ceb571cb817193dbc07ca75871439caf688fc846a10be9cc03620a1525b233e2282f2598dc616370626d4b537b7e9d43de831acecff895768e953f037fe2432ac3b1bfdfd55d2d476ee491ec11d6b6c7b8e9e5276b0cc7df4758636ec743d2abeeb797a8b46682a7a4c9a1d8e9393c1185e84ed1686f9e385ce4c0ed0ff7cb518917906229432f61dd48563cf3e3b94cad4487eb08b1758e3660e914b6f351479e57199c33b1bfe4b56248e89240455c245eaae2554a54de941bb958890d4af51c8986951950fcd12254d4ba78b5ee41c400327ec2eb37701c591987d91333942a980f327a70055954f220f4b826cc79c099550309816de8e3c814557d16a1a73f0f8e8139a2135f69f187f25cd5bdc971a912ed28d0af229bd207792310f7bc3a9732f70c187d0cc55ce5545ca726d53fc00a9f3aec619bfba0ec4b4852776d589cd315b43e07c13311258379308c11b975a438e38cc50a814edb18ecec3f8f33119ec7c0bcce20074ea60cc3750b05683cea2f486ee98eb26d17515525c3ed37cbb10a12411dd1566cf6eb8f3681a0e351b388dd8303ac60f8931fb3ca71f561e695113b976156e7e9dc11311c9c118734e5b92341199d10957bf496ad371c10fa947cb48fe2d6436828a5581dca2c7668a4afe42c3c1bb23fcb258ae3ec47ebb44ef07a73a6f4f24cb9289648162ea6b51bd85329006461e921428c310cde6125d914906222753416601d453c686078cc6a5237b78736811659927c43a2b245e5374f0a0788eb5d63541644471a390e4e62b27812b3e2b84f2f05b049b358b0546aa1c30d68854227a2343ee3293d8deaa9019047d9f5d50b172545d6e8000cbc29a6d1cde162c1f4b91429e394314a4a3f54fd87744ee82304bcdd1d55e0310c830602c74fc92e094a8b27fdea2b286b8b09ddd78a89012d66c01c6a84545727629ebd5cd96c950c8548ed4fa73981c7039064185e4f28f72fb60e115b93835df6938d8040194dc447dd971a76f5d1a85ccbfb05b2d60a38a8fb47f22d95821d1b53d82c79402b872db5984a55352a181ab3a5e38e45e8091fb015db034184637304b5eac0b1e9c0386be3cad265ca6c67d25209915c6bb0dddc0893cdddad9027c4178527ba705b8abe9a95a4138a84a7487e95651db272e7b8dc344966e58530362580f8061088174a6ebafb67646227f9a89ad0f82ab479fd8afcf063536e726451460561b635465adc3835a05cf612ea1e97e9f91fbf8be34d6b264de119ea7d1dafa4b3a76062c1643e22ec7ea17bb374a24a893d0018b73c1a0d42aa9721b42912532ec8fca885b59014935543f0149716dc72f86014e2bb74badceaa228d5537bf72b538a27e52bed4351a8a06ea638797869c25b065924344010b811eec5cd9f60455e81943189e473542b34d9cb2ffb05c5097dc2cca887be639ed42a2f8634f496d784d2020eca218bf249298b9a82f3e69355ca54a44b0ad3f4cae5eab74e4900ff7c8261c38be5d2d3e7a44b79512daf3acd599e7779522c0087711be71959aaa4ef741648cf059c60800bdb9b96001bca50c8d385a9b02cd46485c3958f5bc850d3491d61927b524424114be8660ee055559ee32c4a6e488c37272409b062c52059cfec17f6356ecdc22eed08112cd85772c0e469c1683b673d84eff8c8a82db8c3faec2e5c6b62726044c6a8deb90dae917dde4d0b93311a13ee12bd904f179c601b49e826b0070dde35039fbfce2d4976b4018a876641e1f348c50ced53db4216c4895dbc05ffbbb54cc74222b11514a533676ed04b2b50315eb5fb75c139c84e5301a0aa9f21666ced5b425a0008fbfe21a3b60957fb1779e379e8d00ee2a85a8dbc45422ab71def9b92a992bc638932ee17a85f0f425f3ffdebeb9fd607501f3d35233f9e40f1c6fea8c56491babf5a2a0f8c08d2f54a98eaeff37dd8fc574a9a60212bb74ee8c4650a9e8b7e14d2040becdddf171b23c5ba7189aebb14ea0d7bdc5496e316b2c74a9dbc235d0706bd92a2e0ea0bdf48c5e33ea053a9e283e4c67f94722cc757b392ae2123124f03c88a52a8dab26b38cbac4347e1f70a5ff08e892f397159e57cbdd15d86cdb918dae75630d32c6724bf9c2af8563dae3b8325dd138534984194867173e7d9e8b906ac9e97667cefea387ae4a997dda43f1afb857c220128fdd3f12bbaaeb7fe0d1bad7ceb4b532f1456ccd5d7af3b8d66a74706cfebe986c183f3ee77fc7e3730f487a51cd8770d4ce115ef8ab2156baa07912d3ea1822d6ba0c5be7777bbff161ec792de29f2940e10ca0ca162708978450a12f97c1c5f5a631841d3e05991e4dedb4eec91a8aa641ced9f0d01ca8c4f991b117541c19fb4e8c141d9c30f37c0378f03f10a26adb849eeb21a16e2c6576954490c41ab7a6363461e7396a5439d208e1b099c1f9850383a402f010eb8a8fa566df863af97f8301e8359466eafb6df57c36a0e3ff90f9b214b6d4ff2879eda8058e7b6b3ed7c88c636c7e588b0a72948fe10a84f23923d37f1c172e21f138f3d23e34391fe85bf90bd22742bcb86a7d20767d161acc8db632593d707745b7e38be07e745ddcd3ebcc1563b587f208930f5bfa0e641ca8939052eb71e6fd74be343818e68a311b680a058c6bc4af003fe3a21d5082b94a5b6bf4edcd6eef6de7ca35cdbc4bd7f88da7bd1efcac9c223e6ebaf08d17d883cff270c85c95b6060c6e66cc99f99e2f92ae58f5a9e798a8e6f3eeafb0c6a06f9f86c7c009d53c6585e1e8e9de8edcd4efd4128a7f046077e353afe90489ff612808368f4e0200f6334b8f38309d2a4df7541515a9d65e727e8acbfb85b02942e15c2578bfbccf676820298cb6901b4b7e03b8322fc15ab49a73f19d7361d84ae9a9947637aea0dd511ad61c1040d2297b8b0a9ea71c7803adecd828267e2ea43451b878d47578444b3faf416521de3205af9788d1bf64bc580e0b9c16c165608bc544e9b16e6a13225b4d8ea58f2425772497ccb8f8a8871de6d405fd746fc0b15af0dd4bf8042b8b07bc8c5e1a656456bd1646676fe0c560a6e067c5413a9459a88919765fd2dd0f58b50a48e958381130d881243bb127fceabea4fa5a112aa0fc5ed6bbfd638da931efb6a48e439191f010489cd32b0b31e6b4553b5a41471a85a90ed1355afa5b41c2dbd525bf1ff945dbfc3b205467a2b602fb65ebf4c09d254b2eb2ea694e2f3b5b239550db193ea969d62659e1a015f258e648189217544a0af6422b02f0e5db2a1af4aaef5c0287f9cca3ae03ddb46d3f402c52f36a4ad3675affcfa6bfff69b7689f181aca4acbb683e19facc9fee7ceb50e32536e9db29495a41c93e668ad33101dbf7cca098506f49db7c2e4f928212cef2ac534c7bf489166ba002ba738997489e770c76dc963667b8e5524e33039abb17a6bb6980a6948faa82fd97bf5cb25cd1004f3525ccefd5cb046f2396e3557c24e83e7e1c664042d3762bbfc3fb39778568fc67363696be9c8a01b580b35cb6be6b46ab4ac055f09de5ceaad881403b310c8d3d7104356d3cb109e1e141c1f75de703225d15801fa85784251b785996ca76f9884c5d5602ecb3568499fa1ce9b54dc7ec49f8482d41755fc20d89175a1b06d362265af4b2c3d07ea82c5657c6da0b660b37a66ebe6536dcd66297cc53f8d3fa6bff62fdadcbb21be3c3e63654eaa9a0e2b8977b80bdbcfe23cdf57ab952d2cb61635e5e445ebe31c9efbd1b6ae534ce7ed0e5043b13d9b6d1a09e8e81080624ba036aa12e8657205816a703f9819b4295991c26757055b82fe96e34a8125773129a97aae8feecfc88f8292ae25cdefbe214013b2845fc8cebfcfdeea32ed63c334d3907afbb01306e789d8d7d99b24cbe15ff2bbfeddd6d6fb9a54c29c922086e0865082017ca6ae58c133178d272c44b5dedfbda311848f6b6b9e8125d97df43cf35227288f41bf8da1645475da04ffddbd8e28c81c7d54298955b468e2e548531dcee55c49121da6ac86cf9a967385538ddf13744fac97bfe86742f3f9def71ba0325b889def3a61722f15e14f6d7bd28947e9aad71b26eab3e1d3827f4f20bc2395f28fa49863cd024647660284412ea54daea36385220eaba3d65a06f9bb5a512eb8e0c2473d49caee99205c9648e92393eb329cf992e3f7533500ef3d93fc99d2e3a1dd4665ab7ac0134c61d1b48ca88707bf410c5ff52a97dac3505e99c6dcbc2ca58266d247f75570d1b48025d1cdf8080569427e59658e0b4180d9b3c67cf6666e7d9546b588b81c3375ad82fa6f1d7f193180c44a5c59101004c654db7f5b7b8591c1e1ef73baa9b099259fd97959033a328ac0fec77646f2628b9dc023e71109c059e71e743105272cf6d5828ec868db01899359b713f577ef2d04f959f9c59fc04abafc819ff8ebb7f60c7ee59875325671c0b3b36ecfaab9fbfe3286baab46f335a0c3ef9cb8eb25a8d9e4dae61ad86ac51000e173e15a6f1279bb66d75ab9ad69a0264cd08a4e8353e388a59de023ef97b9be13280f5ccdc09ee197425e48cff085c49cfef1cff10d8391d14f65ce7409390d073cf21098544fce47f822acb09f17777fbc08259cc624d5517c0bf87a0ea1a1ffd4f9a7d66c82c1f66b399aac539552db6142ca9e73d3df349a93837666656a3a767f5faeb703269d1574eae4f958e97f5c6d2413d9be9c857b779dce3038f0f9d5729c4853aaf65daa9e3083c422b5ba7aaaa565df3829c4e2693d99a52266b56e17d1e15b57893999987300a08d6b13f1208f6c9019fbc99d5203fe1f2fdf82e87cc52c352a7325953256d4ed77fae1ce5393e5bf7f975706135cdfd4315d871aaa6ca513d5389c4b0e3545def3b551368aaa66aaaa6aa67ce6da31bb349811030856f0b4b58774ee6fa6fe0f51bd747d77face17a3f7b8ab9953842278f23ece839d5bac123f449b0e9d0e8f4ecc63147b58f4f0c34f5079a5af4dda110d193422426ed4dfd2ea24762d24c1d9a82c43ce987c0c03c2914220a212ea19531df27c91975ab5b2d92625e5efbef9afadaf3e81aefb570f469d15bda57d1ad1730941ee2f8c94d3a2f6fd2417b17d29b86700ee9d954811717d2cbfc8d1f42fad18f42215ec2286a68caa17ef8fda4d01444e66fc8fc0dd390d29bbef4a6973931a149fb984f920313923ee685903e26441285f7484cdb9be8c39042d37c93f6a497425e3ee6634224a62d3c22c4e5edeb2073621ee687c43c4c28441442460ff342d810498fd7109851a803f3129a667844079963d2483fc4e54921920f63432c29dc2149ce2881483ff6c851bf7f7c1c2f735ec2284c397491ae2b187a1238fa17b0e56340978701edf367d291791d648ecc0fd1f1a5979f497b2dd4b9f144748451f49b1e3469323a748451d452e961625e8852e803cccbbc8ea7610c1da9949f489fe36352314ffa0ac07c8ed0a443223de7bcbc3ce7bc3c4ecb8032c7049ab4cff149726e80262d34e9c8bce97384486ebc298c22f4329f244706bc11de78ebf242dc08b9c7becc9b9e86314ca1f493cce871b8fce82b601f47f89cd3d2f29cd3f2387d03e49c12c8399a4f8b3e5068ab35764a04b64fa7564f7c7c644099d34f80a200d150ca74b956ab2d86543d9897ce051c81cfa305b4626f5d3d11d82993cbf694b15351ba46037275cd4be698f875790a50bf1cc6b039e9cb6f6e73e3d015767e3868e12849a861b87f90a7d9da7713460b6b9bfbed473b3f7485cd71a917b344429d16a77dd5f4cf470d400a3712a5e327be3182056d237cc32d1d1fdc299bc0ac27ecc3eaf7a4117e59f97cc8a7d35ba485f245ca481716d3cc9f22c847c822a4b803d37012f2347f9271e78f3d1ee0ce25eeec959c9993888ef7ccea03e4e0ceaa317d7af9f3a1b3d385d2756ee897433f9d6c61097bea17bdefee169658ed76ef3d0c98384211d6c8d9e9de7b9c762f7485e52b67a3be97ec83c7117647e97f5c61fb8e7f04071df7fb0d748267cbf9befff3c01e72fee6be7121ffe6fcdb3770bbf2f3b0449234fd149469112cc2f38aebea4427794c665b62666ecf9ba51668a9856935d6261565a0a1659a69bb49e9943c7b761bc8540bafe4824ffe2bbed144e7825132f0c99fae98891517ec6ae6e94dea69301918f5834f387ef2e7e71f16c73217923022d2a6df9f0212c55b704e7cbfc3944bc4515f58e78f8460bf173dbb80ac0bc552fae431d72808b67e5faddf8878bbd435dbc4a1022bafbf1372060792c69f03d9899c59b1941d587eb1ab45b0087694414138ea4706f19c6c92a1243a57ddfec0b130af3b719557349860a7044836ed47190433b9baf910c1ecc82eaf0bbb4edc6a8f90a6813a57ce0401ca1901c10601393b87a378b99968326bd7684f1f8a7f09641037461c91156ce415ec46a97ad4b585dd4b0bb6c4795de53927c79e0ca2f785447d4690489035da1741daf0bfdc201cf585bcf213dfd090a3ac3e1b561ca579c5e148a3c8c031af021c05ff6eca633013a4e8cf51f846879de0c5758e12057649200e9233fe0b0082c32e094479355d937aa6eb32744d498afe23e82251a23cf977d334a78b5f1cb4c353a4b08cb3308ccbb8feb39999081fcfeb28a5f46f13e19b10c81f8c92fce272fd475748189d1f84a36cc84d644b18fa7f3fd7bfaf8bdfe56217bbd8c5ae3943eda3075a3061c5cc54f479feb651f6fd4bd226f4fe37a4cdf6fe38a44d871a8938d8e1f99c96fa2210880d087be97b20101d18430f3f75600c9a46e4b26844e48d9bdaf61cd76d60e8a927fe10c029009e486f0b08d3b6c5dace748a42e16f5b0edb739b6df916b0879f5a207ecd012116fc8af9916bc2d5d1adbe725fad56950833f4959cf1245656fae48f659b59ec0e55729d2e99939d15faaa45f00b3b83e6939e554c2833ca9472a9346ba5ea993b9fa584e5ba159d1d3b76ece0e131c82f06bfcbb5a5a48445c2bebcab40def0c034cea335e5206f8b47e70bd8c20bec894b811debeb4ea9b0ae196b8a47977074165c7f1b8ead44e32ee4c95f148ec2d191f08523b331ba8baf3c9fc1556a25c562b158ab98d02530c0207a278fc11a8bd5181bb24530df43dc2b4080c2ef7243d85a46d78c9a6ac7f5ea4a0339e33f5facd335db0e5d511fcaa230a02ea067d02b940daaa24be40c738b4271d8dce9e9a427939e4b945cd8b65381da448df18d16ce17d3f8aa36c1286f62f60c8b15292f17eb89931d9e949c093a03c69b985de79bc9869c50b88217d765333bd2d89c6958b4202d0b4c8bf1c8ae6b53d0903447980212aebf26a5fb32b42854a05c9fd2555c2368506c21699e7869ab96e6735de3b9ae29b9fed40945ec481b2b8d6a257d2a83ca681579d343481a9736fc4e1c713d4b2fc1727dd450705f9e3f1fa2679791fd3c1a8b89c0c6f202f6cc0ae87ac26a273c2939e33d5de32a12f4895f66c1b2934509ffe84abc65ee0d83bdfa497743e928a95e754ff3901e86926667812618e562f009039589baf2267cc531ee420c867115f6829df00a8fa031a9318e8ba9982891418679475f854db0a34f1e7fcf022ba36bb66f959f8266cfa56ec50b7b498b211556b6d84b666490e1f341bf82a14bc123950b6d95ebc2b179683e1f737acc614e65fa18de041a8e9cc5570e7398c31cf672a2b19ea8a2ac9c256de83777c48b515e78043bb24f87a3c68489b4610e7d481cb7558d4ea64e288b52262d1ee0009f8fc91d61433ffa6ade2e015e48861df9d53b3cf3b7679f57cd91fa24e02c65748ac935808d4dad5cb7558e7d6e6cb0e1f3d1cf60e83678849f04dba55ac8307eb53845cef85356829ca1f1974a47da009133fea1efbbafe1aef13e0583c1d8a04f5a6451271e48574c542d32fbb89c30100d704f550d527ca3853a4c235f2e2951666c763161976f6695e9c574c2f5b7a3d077b2fbae3f1fdef77cbde62be8e52beaba14e8d21e47558daa6e89f278c979781a7e871c666147a157ddc27aa5c4dd2d5fc7fda4c6449e0ffbb0cf086c292dbaa2c819568b4e562219aba6344aa74ffc332c32cd8a56c67509a3726da0311a1b698c871dd887ad982f15d3b0158caa66f049759965be1f5f35c363aa9894ec302fac1599a73ca3adaf1665420f43c684529ea6640f71abb9ea15afa68cb957afd7063a00368103e0ed517d98c6a7aebc89ce039eccf181d1993741653e7c43af5032a81a3b88d1181176ac31719e214ffe57aecbe810b329a3311aa3311ae338cef27a7158633e6ef8814303aa1a5418d000a64ce88aaae6773978dc11b6bf7b80c7da75bec2af51aa0deb5fd018743fa9938f862b592e8d491b1ac81b5781f32b083fe0178d4925211b96569127ff3358577c281badeb7fe3a331baa24ca84aced02554e52df77196af441d27ac5ee97c10f87c841e02524640488e1ca51b5336653077984781bbc9f5ff42dcf6a4d23a6b5795c7311a9bb18eb1b792334e5a6445e91a1f2c77a4b11834a0eb515cca48633cd2980d3406d3112224021190c01026d2a9dee12cda0426c051e056c023810e9b389ccae7c3615b583956f61013581ad53c74e77a96399b5168e3fa0d6341d2f8944dd99465e954ef4cd994f5aa9d28d19e684e3426da121aa3313a2de09140a7d2a73e8d5ec6f5d74020b64b4326c8197f0f43d290402718748247cc810981d880b0b75f03df8f8fa76400039f8fee31f0ddb0018ffd4a8df5180e8b8df575bdc586e52b55aa8c5f2e98273dcfd7ab6b48307436d3d9dc5635caa0e4b6aad10d6c60871d38e03d619a1a30aa82c1277fd235382f83f5761ff018e4eb32391306182f47f912a220850f460002860e63c18e1bcff5a70cf3ce2fef48205865ac52ae7fcdb8ed5c4f8244d529d2865bd5e77edafbe58f866167bd4348328fd33e4c41609ef442609e04c3313f6d57c381a53b20cf40e9a798977921312f43fa98f015f348603e26e4d776dd95c819ff900d4b5fe6c0901e66474ad79048a153d0838298af098b79524cccf277463ce92812cc8f3a607e748143be9937240260ba2ff423bfbecfebfb422b612a1cc4b3dee1d8e78542a1d00dc58032679e2167a0c034565a9cf5c412d4a27720df18b0731a7422e649608967cb89d1d1d35f82b5e854a695299b58ee94855d8c5336d615533932cb9036f35b546034467ec2f577987b8cbbce578b77d553e627ff6db3b6549a29e01ac92214182599f674cd9436678c1a112b285122ac15661ad7a76cca2efdd219b6ca3629d3989cf1a755349e23ddad29e9896d9bb53446631cce57d7cc0f78fd810f7c3ebaff0008b63bd61583b5ae384bd7b077936a4cfab6d2a8988ff99898d077b2602983ca94981f3b8acc8f0de5c7aea2144a5f72653cc6619ef4386dfa422a2c87ac228135e6ef1cbf58d5ea9ab1c6ba46dac4aa8c8eded47aae2fb9ae299133313b8a77a43123357684e51fc7cf47f70cf62df2dd50c4ab31695345a2b88b578d711581c7af66d2356c480e43b6206da412d71b09cdab73951d6458fa9d5281b45102f341ec63054b96eb3b17e554a6a07ca5a451358a85c25ec51226d743273cb4f892332e73c6ba3a5dfe422a2c83da75f7771813541aa5b1ac8cdaeafa87bae60b8d05b70b79a3a940d2bca4cdca511e863cf97b95cdcfb0ee57aeaf1c267ac1fac52f7ef14b5ba2f5809a1239c32f7eb1e6d335a394dd2a5efe74898c56719d3eb94e9d30b91e8edb168ed6964ae1f8e27c98077bf849bcb1f58cbaaf28a3933ca476e58b573edf78299924470ea1bf0d39f1c8b2a7743e4ee3a07dd3d9abee884d5dfed20a6c897e0ed96db914a56346b340234643166854817aa92ca44003caf4b59ef4e7d26f4f874f87d0735fa2ff8e12620b815be8eb735ad564619c115bc28b007a72cf7a8d6ec198c67f0d46f50b5a72bb7e5ec0285e83595bc88d615a14e1ba37aed4a28858aa0a3da0d73369d3fd836eadd1335e836f7a0bd93ef056b7357dfe7068118ce7dc601c8644f54f0bd6b29dc8db31c5f2fd5ad2a1fcc896b562abd5f37d3bb2f48c3e9d1d20477d2f7afe0ffcbee58788de8abee5452fc4e8bf9739dd8e2c9665bfc586a6feef47a3377528e4fb914988e85d5ef42eef1f1250f67d53421381f502dbf2f6e76b2d282dba11972ca4729065f4489b225ad883f66ec4ebb612d2a6732cb7815aa444b032685821a2d05e88ef5de67743cb14e47b9717f2bd4b28fa51c8834c14dbe2359d2eda5e04f6cc82b3949cf1300d5b05599386b4b902ab5544af5a37c802a87bbaa7c56ab15a2c16abd25ab5aa51510bfd91f7e295a4b063b77ab688b69e9033cc05df74e7a0597d65d69f8f1fb88dbba3063c76ec96dc9e55351cdefc6ef88122081895180cc6400ea4c308931968c643b8fe332698cf476b74bb188fc1d1ed969cf1d98807505c219bceeb7ab7ba55c33d55cd62188bc1e2a1e26ddbea562577b3b85b0d04ee1e69b3d5c7e91fee14ec15109ab48089eb9d7cb990009231c38e3d6b713ff6ec093264d81b6652ecd82d192b36f41df5b6ba79ceef1f04fa869e65ac60719ac17ab7cab354dd52a92d354b5134ecd8d3f3b41edbefd0c28e3debe16184e597a965a5164b5857de915d2ef04fcb823bb0520b1400ddb17be413aa80a15185185c91c62c0cfd58ed8edde380f9a67e4a04cb2e25ac2bfc1ce0e0c777013812a8d25ab5aad11d3cbc17c460144bc127e7b055cd7a0106183e1f4e7c36f5fdbd9bda0146f5ef868f8bf09c3de72401bd4070923b9fc386d2270ffdd8be5ad8add08f1d9f79ce2a8a9e288aa11fcbac198fc18f492ff9e9663e853fa1fd58d3ed5b9aa2ea9e16bbc759b552cd6d9b556301847e66251dd81e703c27ed66f0669861003bccf218740bc851a29fc29ff0264e5d57a4fdd0431f6286c4aa67c794596805c85d9a6f6bd1bb87fc6c8b53c219084083c720b37eece4ae8659dda2fe85507ce11276ec1fc80e82acf1810c2e1612d53d7e6af10dab68705d1ae1fa11d28b9022b73c099574150d96b8feceac59f7a8ba877e17a56f6e5bd54416acc106cf0862b00bd6b317fc4967712cdee34e3c359b990bc04a7413492345b7a07b4a4cd8b17b1a4b8fae449485f60fcf6aba35a78d0d37ddd35724d713fab17ec7eeb99f0fde69d19f49cfd89028265d17bd121689eb3e32024b9b204775cb51cd6275688a95e1c28eacba11b116728a79b6b023a7e61dbbe7867eec773f1aae4c19836312d5e5b05946a4ecb64f11be100a1b2eb1b31f79f2174217b36ecdba35eb59cf7a26fa3c26020b3ac943898f3bc2f695339bc82bde829f90db4adab470b7d9b6711777fb99e26e1c645bdc9e4118d16b71631e39b33dc8861d79b56d630d77abd94a77fbcd13a174cdc8473090a3a436f211d2a6eff63ad28657726607470591286e224fdb6fa328fe7669a8847b98270b368a104a88f9de6f510899efbd10f4bfefb1bdd8b1778fd4d46db0de6e52272d6e4fc1f9dd90e476340c7b2ba8a5aefcb8c7e96a5ff44346159ca9db51cf3f5310d1b7bc10d1b788420f85881ec9e845db57b053d7c74fdb083485defbd07f4f2916ebdf813ca5c50dc84f9beb075310fadf5760bef740d8a62267b66ddbde51dfaac4f2d3c6ab16472e0bc4767ff8812ffdf938ed59c9630b8b74ac4ba2abf527581fecbb759ad77d921c1387eefe48bad1e6749bc71d6119a4b7d3bc0ddcee9cb7e3a8e6b579e336916cb1d6e77a61a0b0f46768da3c3485deb4bdbff726eefd33857ed382ccef7e0385ccef42d3f6dd8f9e0385f87ba12948f7a3376da1902eecbcff5ef4a370fb91fd9d963762cbe9de9a3621ecbf84435c46ef12f6f0537d99d37d3ff0252267ea87be203307c74ff5394e081cc562fb6b0f03dcfadf016e7d2f01b77e07ce204ff5bb8fd19033f537905e6945ced4dfea16f2acc7ad0f7e613bc525994db36cb381b828a52efaaa556693728a5344e80c01c2375ae801a619c2a81e7a60141bc127244b64194b649650a74d168d603c70fe188e3c2ecc8f31301004b8b29329996acd41b59eb5ced704a3ebac569133dd61bd22692a19ddcdfa457d7d95bd9a70f7578b3ea5c50a83e88458adfca38913153ed58c1a0b408d5533d86755cd60546da2731f39d344cef8aa09f729a4374ffdd1573d66a852a4c8eaac56a932bea957244d25430d2e083003d50463be6898af09868a7d26188c622b9a99d90ae62a92b9260a3ca078e01b2ddc767adbe19bfe5965dbb92f03f0d03656f8f020a170fdbb1b7e3118b4c6180c465533eacb06ea041a03e3c5d5a736a9aceb7c53a7a837a0c20b16cdae691e3ab52f477ea1c346702af472f118a42f35764375428dd526eaca058f5f3870119caa818a01f38bf9622bd8278619fa01105080d73140cbc1ab2b087835562adda82bd3fc15f00df73b3ca61ce8f37328ef568382aecc342cece8ab1b8fe3475f1d69aab3b3c353430e0ce9635ee6774a6fc4d672e5e72af015308defdcb1797aa7ee58122467585602f92567f097e12401466e8294338c3c46e9d3a173605ee60b327360428ee1c0696cbea84aa632d5ef4c5e0eda646575683ab0cf310e9236f20acb292642cef8f3b4c3fce46d04c16032495f3eda89e89dee22a88d2b4197675c85c388d9b1c48ef5a519616b65b1c871b26d8992a674a76738b1a88650ad27a7d888909f18c128ae019f425a830a86a4f1e7d3c8fb42cc4f5aa34104fde08cb0e394c9ac2cc112648481b094167de4d55abf90370a7d558a0bdef49955cc2753c9ec993c2e782eb80045cef877313b5b09247a882a9a54859a8a6735a4c08ebe72e2acda64861cca7c144a94ebaf49b9fe74caf59f54ae3f3fe153fc78f3256dba90289ec267be7cec8ab26c8b673eee4dc63aab41be92994f9f41981f6f9002cd8af9005680966fced9f2cd1781324703e54d329bdc80697ef46a0d3b726c7a9f17e220bb90354d24123128354dd3b417cfda29f31fc9b1561704cbdd915bab21f00d18d5293fadf8462c2dc17a5184920fec0e3855468b31366c6ae58a327250e81322fa165260b9cf851f3b3207f14de8fd17206d385104248ab330031802b286539eead405e2a798eb0bb30089b95cb1de36b07b408514bd7bb87fd08276c92460b1ead70e4f6a95e2d20784526d631ad157fac28c574cd3346db52af57da1a93c69979306ea29edd350ba74c36b1c8dc3d3869033fefefd4835c159f886959044a4ba2e08b6cec08e9d1a6bf87e882834f177434c1ceae488582b53bfe84dfcdef726ee5ec8f748a2e0f09174ff0185dcead5f51a1a8003878ef21c85833cf9ab80ac56da6ab5ea55af7ab581dceda4b6da9ac80a3aefade94f10887ae5bb12dfaa26411a825b928d598cae6bee2b5be48ceceeb691524ae914083ca4db946529a73085124be1eeeed259d0f168517a73523a5dca3927d542295896c297668b7336a1352de81514d4461b1bc7416183823627f51707390703776f7ab3062efca6014cf3af1467111dd53b6c481aef56725bbc020705695b6d1804050505f115244a6706a5256c12b7bb881a9d3a8ac2fae63d68b70abe9fa2605f73e3e88b9d080a92335e586e5f6d35c00112ae48238b531a965e99451a4069b45cceec20290976f4eef5cbec2c6ab123971718528c4ce9060e530e1d4e9d3a75da820b0f863b78bc000018c453000410c38c0c343d661800010840001a0a50538029e058a0aee1ff12cbd2a88ae5a17d3eaae631ee75faf4a6f7c3b4ed98eba5522db164395e776e9bd329f55992a5f652e9fea88a9d2df29c1e02b960f657540186cb3ba70004ab8424765cb67405c8c25cb1c11ded43a1c42500645141b1f5ca2cae2821801558d29559ccb2fcc006f998cd9e30843bafcc62b6e2e1413453b18d84f4765c39df0339e5a720fc14b33cc5101205c44fd2467bd92c2e1cbb899c2133909f805c40409bc370470682e18ed2757b8a3b843c39c75c3e72c683662d863c48693ea8da7cbe99a10d4c23830d4b48a147e6d3cd559b3229ccf09946c65260d40fed7b34ad292067e6ff8fc7a1b3bfc5b0a39c89dc125b2fdc125b2d51e41b8e314d174186c859bca6c05e8e6225e4c9bfc9f5af1a9dda8e97956bedda2c999bf358c6329ea99c30162bcdd34c38884e4da353abb5d6aac1180ce697f6fe42b6d0c21f39e392335d7bda5639308084f00d6f2199e594e6275866999fa858af07d6c42f734cdc3304ebfe1c964a1206b20556dc1948eee11bae82892ae48c5bd2466b438a5790a2f3c05d84ecc1c24d8265adbac00e445af487f5c81997c9aa4c26639946a7cffb5081b35ccc1823fdedfa04d1f0c0eda79e3592e3f617a1e198e3f673620c321cfb678edb438e5b84fb51738c392e573b346cff37c71c957eec815b84fe46bb508f8ee98eb6695be5667353db26a5734ef6baca6bfc748e5230bff3b6e91d0d694d84bc9fa0474790e4b24ff6c071bb73765c881d8aba6d200e9cba53dbd8351f5b05c769396e2b21ca7130fd83eb01e616e1e877dd2247e9e4be4e2e1c616e11e66ab8bd12db3f964a258fe3429a86c4a6bd168e20c96d0e9caef1164a19c3fc19a2da6fcf6d1bf5aa51285da8fbdf363727d7bdfde0c3f5bed2ed470edf9b55b0fdf6e78fde9bda08641bb7e547ee47a1f6ddc0859e0da9e8bb341cf9ebe7c3d6c295e511f9beef3322de5ac11ec4aebdee46b1866a35f1f620de16517d7912fdf72ddf712e2010f5ba70b3d62de4e2f238ce85b4d9e32a47f2c7df1ada14d87e17d3a86954f3b4f9385dc11be4cb4b696302222fcc0f09b8f56b38822417e6c7fa9b3743a12d7c6f9cb743a18f646248a4972191b649c281e36ffca8fd86e36571bcf613c717d1b690391ce1d6a27ded06285bb45f02678bf66540af45fb31a06dd13e098469d13e0c7843c660ff05c42167ecbb8039a468df04be3cd93b0a4b72c6fe078ee2b5e13712b945be1789be2e044405e5d4b45a1fc7ab374bfd31999365287466cdb7ca3c9d8990962307ad959b93a3b4bb2bad5b87638e4b7bc871399ee198e3ceafcf6d2d8eea9e8e39c2ea8190b675b4f98bd4ef2ced7e4ab939394a9fd66deba17427b7fde55c58027d4f9b97f26079b845ea87359cda15d80bb9c06e4f9dfbfe0a8a5db3859b87251c7fed37edfdb5d7e86b4184ec7f2ccb6d93deee4e6bad557bd7b4f75a04a2c2890bc356e3d1a8d0f7e9c51e4fe4008a99b1c68a7ed7021a36d40ae3fa8f1d3709a280ca7db9520b1410e176ecd51848d44b1bd2a54eff8926977ee743c8438bf4c31e5827240ac74fb46b76685e42d67440320c24cad61f590797527f7b433c7440d6701252a4cf3cb4fbcd93903552d2e71ea72cc5fab47eedf1a894751e929b03aaecd60f8542a15028140a8542a1901129bb45ea6f1f0ad5e7ed4bb11a7a9ee7799ef71d0e1ad0b045425fc48894dd51f4f53d6feb72e87efb31f4a328d4ea9cd5d6fa3538aabf3e10593726ecb6ddf0fd180a4729bba1914d41ec8f7e88fd91e887b4bc281c43ef3d8e87a311ed3d2058bedbcb9003badf3c50fb7e6cf543a1ef2ae87de759db6243ef51d0dfe285a31129bb5d38562e046ecff9d85aae7492c7e851b8270301511a82bdeaaeba542cb93917171c857211046b11fa11f56496140c73314dbfc0056b6182143016b77beaca0b1825d987909d0617c172cf546a5404172957a5b56a55a3dcd4298bc1cac10c542e18524f868911330315b74bd9e1e8c92ad30c54936a52a47a443c29508af5973255b03258ac8c1a56260d2bb386959fcb75f1662335048f8c10ecd8ad1d50b07c7e72c0aa3c907881b504005708828b284be041446bc62d2fe9a0c2cadbf24f856d9d524604c1375aad35a238a8540dd65639eb140d0800000008a3140000200c088703e2e18040a20782a83b14800b7c8c407462381608932887619451c6186310218400010118819991ea00be231d829503017ee523f858000fe934e6bd14408e7c2f48bddc293628b7668cfbd82a34b80ff58d0de48ee977fe6222a58955f1a59754c38eebc5b7c60c4662a7a14ad62fdf93d6308ca6c658645293f2e1525337a4beddceaa9adb35a60284b6dc3cd149aea25d23877de2a0634845399c9e97f975dc4b0745e765e4350209449ec12970fff12ca61edf472a8dec65c563683a5663114f67f615ea3374324360f9b59f8b8f8e925bc3c78bb47c48c70514cb1321624a297052b30ab69e8bf9920771daf3e3d61741ddc2d6721ec3530dd113343ddcdc17c4418420bbc44d145f2d1019cdca430c21da2ec409670f01d9bc8f5e1862ff62a1efcde85604ccf36a60e41eefb1c51bd54c743670b1d093a14bab127131ae1be6bfe2fbe49c2faeb40d62dc18686b29292347407b046573618310d36dbf6379cc9a889a3e08101d814272c68e62035168fbea7086647677a35f461cc0120f0aaa2516b38a08a94216a1af50e83a53f990341392ac1eeb63824c8f11cb85e3a110dd3b03e72638ff878994f9a18c49c83dbd9980321eb172f4b02603f81b42c38679fd8607d6870fe780c11b9d4f18fc5b767d9abc51d2150cb39f0e3c8242a02517254c14a400a37b0614c03a2fb04499aff770a17c9e0235fc2a6df0f0525d6e2803c56774c8da1946e0a8bfe47899b04b441d37e92841480725543b64421e69531b6530a15ff2762652e0cb33294d41af9495978a48187c25bdc248fb47135de44f3435d4c227b7496bfb3b49f8b91b530f7f94fa60080fa9f42be88c1634fda7345939f0c5e0d08937a205b297654355e34881e49438c8c72b0fed9ab6c76bbb98742a8f925367419bd2a3db73d43cf59b8e80988191b498d19aa73082fbe54b07f2f9e0e32917a77b9631facc2bb9a24b39108ec8df7f477e994587acb973b682fa487aecc635b9b891811f41140f41867221a81c41df8aa24bb1c86e2ba8055bfdcc9293845829ffba1d3fecc63b2256d91b01e1abb6f6483807e418ec476c25f1b2571906044e2e19d8f54dba583a52724682627cb320d59133c676da2aa6b3a56eb81983768bdcb47e34a4f07f9fc972a2cf1906dd924583587851246d84704f0e746604fb1ad19ea2f9db8a4bf2dd6689d9b0ad1320be5b037fe0e4bbc09d7a659b83cdf2c05c770c60ffe2f8509e77cdef3e6564aad574f657199f75f8f4d06bb80f0f7d322f778591aff05e6fa7cae2503f6b22a3a0e979cd644c86443a9e245a7946d7f13731c82a53c04771922436f8a7fb7e591329f7d456afe2dc3d73b92412e8c614fbde13a0a3ad0a82a6b2733b21132876a7effe14027633bb54b183c822787f1eff91a9628b3c1faf5ab44a14c909145a0ed886ea90d8e2d7e89577a32bd6c2b3dcd543749d839a898fc15cd9b29f1c933a0a48f5b354977e01ff0d5d021e76991e152b354721490af95754fba1039494ce715b34f82e2bf47ca3fafbf11f202f481d7dd457547616e1d373975cdc5a018ba09234bc86dc91d1da138f0752cdc0e5bf83d1397266496b774657556d41b2362e86ead366b7783911866bb7c7f441133891c063be15ca86d977bf421fdb963426a2d7bc8179ce6a77eaa924c3ccef1ca2dce60a6d93915a0ed72ef1efddd737d6e1ff3527a644940c341db1af6fa9ab93676ec97a9f2038332ab555336888815155d9dba9a1b8e03f3dc4e68fa81003aea8afb2fdb9608e48f8454736078d0debbc0be16a9e7101c7e6559e1e2f6b9793794426c564a0ac2612d2842ab453974678475b2391e0b8d9aaa26e1cd46f96de73e3a94bc92ae5d0f17d7e18528db4d444f2837f33d93a3f75e2e7796ed4b8031ee25d3c8f84bdc11a8298c821630fec94e76e46821cb1f975c3351414a4f9c48379ae87d2088ebe8b9116f9bc358cf770e9d8b61a4248a16d2909acad65c2b08a6c0dc60d53d09c59e838c4f0f1c716aab44e1dfd114f2294f033cb843edc6006664473d262991c1be3f261817066fab854d64570e23cdb817da47be1f9dac4394f8aa231d200dfacfd960e0f5cdcf96ae9efb0b07f703ca50b2d3b8b3d3cc0cdf194e84c676f3eca501150378d63684fcf4613bb32837a106310c9ef63e227e5c33872ca6ac421cb9e7c7d864352192a90a146e0314e5bd9f84ef527678a609848592d523c7d2c612ed23509920303599cd4a0ed794b0a5064d734c39e33bcebf80baa1be98636d1787e1b678d532253523036f182587722876f240745b41c385634f575ba32efc70fc162a1ef248ae23f403f5aa0a443145a76b9bdeaea3f92279f9c57af7c0d39d169f7a40b7159b1f0fb7108cee3fde500e851f6e57f394bcf7520c3da842c7755b8fa9b7d9b31eb787d7a2f5867d0b87433608c164c8ae5edf1c00de6db67c984ee2c6ff6c1a894f2230f96bc71ff709924c2d2ad0fd001e2c9ccdc735697168cb56063b188b193380ff0411ec4b4d77385895e7253b2371e3e4bb17667bdc9f226647bf9d0a59b1ea296860d9fe57fd70f696998460a8220749f2b08d28599825099efc44266468207b8df5eb5591dd4a00ec167e27ae536ae8e337931542554fac246b6f5120e0fc359051c998840edc109fdd81e56ce4ebbf9eacdfab1947f453bc034b3d2c27dc0928bd417a07cf5c79e225639a2f792a028de6885f75fccf8f71f70cc0b46c5f1d539a30d5e812f34afd09510aaff8fa4dddc0822549cab8e74126dea9a89aaeefb4f0307a083617586a0e2c508ddcd706162e22daf2ab62b13ce3d99d1fcd678e058821f5ac770d208c5078fefed67aabb894323331d3fbeb9b07ecba64aeea4e47918f6afd2256903897277bb1dc319c9cbc8e2ff6b700a2f99a7a9e10e4664525d6a821b690f3b41afca6d4b504aef3129a3e020a499760829b0086651e0fc8262e80ad8d7e8bd501481a2599666bc9a91f7e84f5184216793e54f10c1ac7e7214092632e603a6fa6c75c77fca21451731fcea82a5eacf26a0e2a06731e8f74cbdb7a77ba73a716ccdbd220cee1d266657f9aa96d9d1b9ab6b9a63ca7e56faacd8a19900d2edaec3fd32bab0fdc2280a4613f46d66ee7fe8523be24189fc9a1569d4d3db08e9d673e4eec35e862586773f2126ed00cfa240926df00a4e6471533fce91396e116474c3c0106f85291e72f4dc2f28163a115bac0c8f82dac4d72b96ef6141bc6c84a0271cc8ff009c38bb6affb12ae4df714c8d16bd880ff41bdb235e4f4f3679ed375198ca3c417d03bca9d68a9dcc8721a004479092519428c9942a80cf2d1fea4bf1359a0abcce9bc687105667e614333327cdc79f46d82a75f2b106c65700bc7c2fe13e1b3db829f677692fe3bb93ec59d188c96b38ab8cb27be0098ae03ba17e35966e73ff587cb7872717d8423254ed537054f98497aec21238c0916e15568d0a4bb2d348f4bebff37cd0254a121ba9dc099f059d1e26e829567f7ba94892b2e6bec56ed354a7c435f4dda696e12322fe789508ff8d59abae962ddfdc1f6d07a0a792b6c2498e492f0fc2a9c0e071981da593743964466e5aa8519997f355ff202c5b53f53c0df38af0b94cbf1f5f1c0a490c4e1e82d56c69188a612045c218c8ffbaa50dec70d26672b44ffe742e19a7f6fdd2663c58256965e953d367e1b1feec4362c335c1f6f5be3f2e2678df5accab67144e5f1d8c7316aa5637ef939915997b923018a2dafbf67e4c00f1ee5b0052d57454b90c204eea5b349de4f8658b1529760d362f51cbbac81f275aa24258a1f6119395a41b059d16923bb865a86be5d9b31bab77e9b0319eed1e88ee80cad6f9eccec4e0450aab8881e3b932d2cec9bab0c695fb0ddba3a238df61978543e1bd7e8555dbc85163f63d42f58e2762dcf7d05fc1e48984130eb09c6df7aa6f8547901942b079802dd79efc0763aa40d4a1290a9061effaa39b6b0db3d38ba62b4ed2e24677e4b5995d588bdaa417a52dba137a069e954c0bf9d893e5887a87478da10a3e5b2d238343c8975932ef3149c139ce0e52d704ed288279dd4108868e0c5fd4d408541643466f25bc4307535ed3bd21224e36b2f0a9c44563e81765c487668acf99733ca5eeafbd6ec2f03f4a4ab0da67e70c0404928832843367f7dd8dfa3e798cc300f067e646e4cb039be3f0b9ee84e2253a2689b4b233434dc537e5285d1385b58062363ae6ec809231eff1f258cf4839f6488b753c6b796ba50da9ee60ea084c936764dca09493d382871997036073667513e39f7cf84daa19e440b18cec99959a85441435bfd08fdf62e51b895b0c17d91247681ca79ede288f1590a41a93f991ad39882360c5ae27cdba6aaf167260429c5793cd5ccd9b1adef5016e64d981bbc6e58f84290e594de303dd5c45f7fe94c4ec6308e1ebc34decc3c5bc0fb7047d05e465b311ead1066bef6f532394d299dbb8d7171522e1b5cb450bfc9b4f0b8cf4afe402cf115c2c3d56f9f7dcb4428341ef1760e68a058af0e807bf42a18adef36a6c8dd3f63957416e08f4fba4f40351eaf5a009c1f3d50d7f9ed3e95fbae627b30ee21495855a4e512850cd9360bdd803bdf8449ddb9ec73db4f2c283f2ca602cb662438f6a1e227c3c135bcda7d698db6360d4bce879e81e189dcbdbd124c04d95ca03d5dc274f0f5011c68a5791f7a6c817d95d2bef506274f3b788c44a69464f2cb0000f3d7139b9ade9ef87659cf299f977d66c2ff1009b7178054ca3fa7db27ab62178c73cb38639ad5ac4b4f9c90c09897a5626750823fa3cc05a2537fd78f294db47a1f3a4a3f0df45231440ddcda4061a57807b421ec3c018ed87cd8fe48770cffb8825350f0ad8861e7a64cfcad27a32ea9370d26e0182db8f2db2c6582717a315adb60f48c00d2a966becb3e52383ca89c8012698d30286eae20cc8eea74f4011bb78e021da309366e49d047d1418eb580ae9b41bf260f1017d2f3f22b04061a0890d9da9683f7cf16a23336ad5010d61563857c890b1e267f11647ab93899b5f62151757564adcbc5202809efb6230cd1d19947b9fe6c38af442929805a357f3c9a3603cfbb996721101a13c5b2617512b416e7dec41999db32e8088502681933ed74ca29c75fe1f1352b4b94ff99731e1e4e79938ecda428228f29f160430a8aafdd90692c71b0bb976d944d1653f3c5dc8e567154bd8b756253e0e7580c0e6fed26b12003a7a8a13ac5ad76f45f67413fa26671ed93c0385c6df61b39bd6faaa109a0829e07143604f4801e5720458188e52959a5d7d6baca94f853d4c7065bb0bc0a789b60892ef01a8d6a6651be0a5803cbb28b5f3c3891b107ee99424b8d6069313d2ebbbc35e62c5805f0c60992c5e090ba1c32fd17e4d684ba47b851809d60113180c84adafaa24b7ec3bf08299051f3980117945db3ce2fd775d91574b1e4b3cb607a488eadcb387fc028e347cedd67e5163b6a34ab912383e3726ee01b85dd017e3ff17a06040f0357e474958ff9aa76a7c45c289b5ff245589b4f06287be8aaec2ca4b1c9c8f18a68c1954cb195a915f8b4cd8edde3fec5a9ec30fbb5496450144f8e2479b68e1e1ddc156405f45e28d0581eb1779d25c272096a1e856c190bc399c0e0c0d4d1bab1388b8fcc69e1997b1397ee0080afac909e7c045b2b181e6c6591e4ad92ca8fb6ca2e1602ba3d5881090048e7cf52791a5acf6ef22e49d18cca55ed2041ce1844555aeedc6334ab92b724d2f8962b5c264750a3c11f111af0fa4befeca44aa2c441726bdd51de875962ee9d673d212577e0494b893dc9f1f2076ea2b3af4555c2499f13d27d358884d030984c59edb302313af9b802246fc50a9664702315ed2c0bed99864461bae88b3e40d254a131a20a7e6ec4cb8081b6282a7be3a6e64c3ad1a0134be3d5dbed2c6932403b629b448fb656fc6562b68bd51bb069e162e09cd03dab434501b48d40481cdfcb9daabd62da73ce2a8a3bf20fc615f166713a7fc8be1ff082195134ae95c96247a4ba918f19fac2c26c4d5523ac3691aa5a5c132a133ad894579c03b3da7628cb296ffbc0f1c02b26d2fc7944e67915601c2d5184b79ab07f6119d595eea475d0968bb2046fa9fd0275a0c9cea1a1ca459abd0e309f723f2d4fcda9fcbf35c0287cd846c27b58bfb2df41090a7cdda70a508c473e33e733ede6fe8faaf627228bffe8085ed2f40a6936244ab85aef85c5bca5d02e0240350d4b564f65d6f27d3b9097fdd8260179d0eb1fbf33905a9dd9b693abe278436450cd299399907fe14032b97261eeffdbb20785798ad16774c7499abc7565588beeab1d9b62c6090d89c31a437633e82b1c861d126619c0d2537069f7f30edc2a2b3f89c9c0ef202035d7fda3d44ed9e65cb9b50b349766205ea63e24fc764c26ee49702dc836b427413b6eab33a95f77e0eba02375da3544ec7ec50997d46808267f42baa67ab44ed7d28aed67c781f8849c86f18abe4ef4c52612525d225ad2aa9335e7a7b6da33585bf1d6f4b592e853ea40d67ac1eb14af71ddc3b482026049b7146318c5b95051158bdcbe53a46329280273f235588625a9e726ff33c793ee859d082bd3151694f0f89028945fed400869570905306138e668650bff26a7f1b02a89654f0db86780258bce146720f3a14a80fc0cc503cfd3265ed272291cba4651cbdc0e4c3d54233b67a994ec94071e17b10fc9ad959c6863de13a3509103ece71197154f810b28aac71b810b982c5f69555cb0561bb3ea3b50ab5ddde324b7c4c19d2fb1972fa2a3b74e4a10354df1de5d49e35a79a8291ebf9b2fee7f6c67fa79058ee19ce729c060131d2af4fd861b510db865088016e153b3050a407f3d8754ac56616cd460a130f98c50babc8e9d575658cbed9d1d221d8b00651cc208058afa0b8310e85cc80a7ddb6eefaffc2e24f7a9bb7d886f1ab89ea8cd108b770f5b4ae139cacdff4a5753a2cb2a986ecd8d1a5672ae03184621fe4e08e210fdd2d3253ed57408c8d9f8a1727bf22ff4810deba87d44c8ebb40a3d7b1bb00640567909b401aeef254306c34412933e4a83fa0f614c494617c85cbd19061f9e8f1cc778453a8f749ef7c45c2ed2a362a8a9333c781fde05dcffc47a7280b957c8e75dec96b78878f9f2eafd40486538f0dc8a9e39944c785d1369c52172e5804b865177c45ed0e44dccb5bf2f1a5a6ab004e73e5fea79926bd0cea3a79e3a93618e933c9c954fa3a37395753930b1fafda91895b12c721f475c1efdab64105c874c130a5755b2daa8d82f4e39a095d9fa488902783718ef9ad1bb24e65ad1c63d9567cce61e87f6b5065a3e2929eb8d70e20aafa251934ecdde984a68e2f1955c91ea19b4e72a7d97c4eb0f85d5cf6bedbd8b12760395cc5b1f55fd90b9dc34f130ea2afad88ab917a9e66a61aaeffbb2946c79c6b23b161440637c1e1b19d7a79d489f52d347dad34711c3dcbc2a220054907b8b7999ef97cd1fc1b298a2a2864974c11208252cfa11882f9bb40d678d9d05956cf293512ee9c19b81fd6469576f48e8d1ef735fa2adda5059227711937d7cd7fd1b4f82fbc2b83b198759043ed751758359cd6da4fe4951974b66fa6156f5200164607de9b8bfb0fcbb561625f65e683200c49793afb5cd2f7ae0a9d5b3e1aea03873e66b1d68336534a935e6574c593d589b44017bd5c1442d51c1377a1cf7a020e84c220038eb037822945d2066bf8bf94383348cafd4ec43f15f965eba005dfa38c5f94d79dd0173418361c2652c4ca4014c6d0cda75a790a6c3b6c77e75d0c172c28e944d97def7b9bf023278373652ac0cd40851f94470874ec892d80e4205840504dd402b76863e2303983f840333d48bfc470146927868b43caa0f9fbe56af036f96627567684cf90fb8bc94437a27313e898aaf71de55d93720a664894be15e04e6b0692eaf21fa7ed279f8ecee91c9fd8f467f3010c54e58b8c3ee3cf83588622120379851b4cd506724796c55f7c137466dd480ac5c23d954a913415362dc572455ae454a2fa6092439b9c6a5632d84ad71aa96dc8574cd10d4143870757fda0eaa43bb958222f8d4f7e664e680ee7214a31420da84cb1fb486fae544dce469705c01619d9c14777395b892979c8276f4e656f3ff1923bf5f7da8e5dc75a76dd63ed1027b3b2512661992528744afbcb8384dc81d9c1addc6d13ca3b4ac70f33e04fb5c09c61d7fd5514c3109453fba7f7376747d22356e95a6c70e4c2d30e43d4f8e52825e83f163451592793fb1c708164ec42cd069b40ef372faab79aeb5d08e02aba14c8f89772c5c113ad6d4c635d718db58157d815a4a540b357f6686b29886a4aea35b1704d1423e1ea514af9eff46b03f7e40c146befa2e4a082743a62e493c56a59388c72c3b78637e7bbff6969afd01bf16b2fd8b8e0759dd5281b2d690b5445a2600ba3bb5515997af3c8d70a40faa3c1b6087b63274a8b953c88c22225637f931d6317e1d340abfe51dce3c8dd119ce2ef6c68a957288fa3e424ecdeaf16857b9189727cf047ea352b7e922e6735381b1a3e739024cfd994f4354e97e3df4227f08e61f1badbf04485f10658a42871c8f3df3f360f35c3990876a23d97f40af8fcad37b82ed38419ad85d44b05b3792b69bbf0a538118210d56c9d4333fa67820e086f4e56ad466e606c84f4cc9462702153efcefb9738e769f499ead904a0a266ec8e39eae699386d3d1a7e69c01345e61371e855938d850fdc6fedb294ae41bac012c82413f3f2f3488bda9195b8929b8c64d9e603e042d3824816f551d775c02bd3e042014622d81ea1455d3d8680b9020ef09b143345d436b4465d050acaa6bdd20dce09b342c0965c976d7e9c25811478f4935897280789c09e052abb292513246b7292ef97b1517837dbf7584d7826355e0c4c82ef4bd88cbedad2485170a98539c7c72774d0b2540bc64188c89bd4c05e44be5e66bfae8ca6d1844ac0aa6cfafcdb2c7ea527bb247fb12b663922516994b220407241a8c15df45c6b444b39f42ed0caa334e947ecea01e0e01b4999fc8495211cd2f03f5342e3dfdb1f314381cdca1b12f74ff7cb04bc79ed7b1a33347745c388eb07c760114d84ca408d1c2c6eeeb920ddd7f4b89be9c9c319e6a46769846ad9ba88646391849671d1224732c42b43c5fb2675a15156caf76d5d332685408b87f92245e70db3c999e57f8ee08e4cb6b836568e291631cb5a634c018363113cd8b3cfb6bdb6e542bc74b74e574cbcda6fea681b1b81da88f32a6412873c804b49388c3e136da31179c529214288ebf626180cbb29ada0c0e92697a3809ee35d5750a928a5a01ea0ac8e4f3cb615b002bca4909acedda0f333409b6a8ed4e3245f99bef2a4fc43a73bef2fb7e0e3d0e7347db10beec567b7f6d1a0d95bd9ca1bec8a6c67c091fb30685e80d6f9df290de3f41b8bbb3a4fec9566cea4745b2db16939930ce3bd515f85d5b054d88475c0bda4162217b89aeb02acd805495fae0b5827ca969cdcf76bd89480d51970bf68df2eea4a53d0febb1f76761c0d640586cc66e62c8f85144b571096a6c834dd44bbf2ecbc343ceee97e0570b77cbd8194313241a8d6f814684919f26ca4f9db682b84a6d591f1627ef86871d79e8a3d83d84f62c0c77325bb241a1b45e6580df458b80d45efcf3ea95b322aa279a250de6927100e51c66d354e259170e4afd76033d9977cdc7e34b2846d1e69aa3ba28c12c3b476e67b7b868fa73353a5cb2b228ed5ef9e33f85869e1186e487e5fe96b16a9b5a272eeee82fbc525cd913baa6052c51345e9e75a87570bb2189594c749b3f56d395d3c95392e2f4efd9452f95615b5ba1cb1786d18f655fb0ce96ea9525d003f817b37329f8c345bf4eddef37d1cf1bd719028622cfc2b325821db19f7ba70ca00ae07fb9e6ff0df889c884134f86796a680473bc76ad9a9b8b79ee7ce6b782d652615554de6fed04bc06d4c5f3bce8afa8171c83943ccd4938b296410aa9cd437b9fe052d0d9d2c078716da054ad7b430e9c20ed62cc48f3b0b12857949afb94e68e2d7779447ce82038a4fa8b21276704162160b5ae71a9a9836070e28496a2df502a8c404f5a13b6880185794ac46de8f1e1450c069cef43213e6d7d067b47545ed0a2bfaff20f11d798c13cb7c0ea1a29814602aca5d6a7d1116f97fb6bb8928afe32fc443b7e03448dcf4874939d2fb9dc54ec2be43f11509cc9bfc49e80cf4552cee392d321a1e4529a54b904ac7a88097663d64d949d90f9df9482f866cd027d9394816b029639427f392b781a70971c310ae20e3591e59c76dec0c768c1d3184d78891321861333118b865d15b14f08487316573211b30fe70bd8cf11715acd5278cf10a3bea32221d5269fefbda5878ebf5e45ddf2f580b1e0a1f19e3bceae34a3ca7159d3fbb125fee3b28405df5d8e0ae8d9c9cb25ea15711ee33faed09b2d18771b047053ee9db5f54c8451d475a20e5a17b14d88d1c8301264099a13358d0639bf96f600c542a812424348cb027f0497f9fe7b13e5732f99a389429bc1f11394787aa361f293dab8b93b52a80a819256b34183c7a99e35c1017c6de0eeb1da011d07809e703394b3ec37148ca8fea80e8692c19ee376cb7c6ed665c16753b526e43c32ef1f1bd40ace941cb2aaa1e66452427fea2ba8a75fab091612876a1e38d134474b6dc3ad6b9fd9b3f94809387112a9dd0ef897e794285fb66e4ac3eb393087579e873d8b645ca62407f0ab9d8095e7d9cbf75008d63c3906ddd2f69b194ad87279b308c445cc5a508f8a2c0227180d483053a945664be07935c3d10bb00bf16f8b34444fb3566a0bf04d0df57bae4bdaedb39497190f3556c51d19fd7353f41971cd0b7ecf145af628b364c841c6d99ded3d8eeaf3d12bedc1288585c112809dad72c3a1141ef622ca7cdcec4ad074305f961212c497d5e3da196ea4fa4b87e00ba79012d0914e225461a172ebf1c89cb49baccb27c31383893d463c2ebecbc5d4e4a2c3b54d0e38ac21b90c5e8a7dfd820d074d70f2dbc5bb1c3cf01c3d8153fcfe8e7768d801dc8904f5f2a77d0b8c91dea52209aec560413126372ae7adc81f0b6ae0d884be4298a59524ff80cadf0ed8b13804f9ee9a5376cc241d093b5d04bb43d01633c1c95082b975a7bd22f27878793d7bb7ab12a665cfc727bb4b30bceff3740683a3d17ab1a07db3e5aafece1ade5cbd1c6506e4cadcbf36a82c44c8714899b699bf1a88637b66f2c9a529b2c3875bf03b4d20e451bb4411dabaca8a03dc5cf031028da62da85bc971de37975f9f4d787cf24de9f236f7fa543f59c33b8da97a6c43227be219de69717263d78605fd8a262ef3bd56c1264c03834d1dff8cfd00fa49b9a18e46bb76633bb89227e609978cd67678572dcfab9405093fc8f92fa9fe8ea643ea110080bd1bb65c107c6d1516b5d8ec820abfb7948d8a91bccedd2219c6f6cb123ae369adf1cdaf44512d425acb64e3229acd232da5259af0681efec2a944ffefeb1cf80774e41f96e488f2f4e3d9e5252364cbefc358e8a3603e4eab29bb9362d37c3a0d0da615934e2d83c51fcd725a16454ca4389ea59efefb34277ab9ec678acd1c8bb2bd39db618a5816098fa94d3fd1207fd74b547ba1db55ae113048074599f588083968a4b4b85c0c84248c2454c3560c5910b455b2a3c58afd21f71767247e475082ab953b7e45f5524e9582465da05eebfbd6997e4f992b2f4f5a4db881c06c0a4d7d67bc1f89ea57781af84669ae280a0b005cb07592305274b13c07cd274cf89909791e90d7ba5677cbc703b3ac5b6a2bbc0458d9430b0f4e52040706b70b28e0a3d51edd8c334f4659e53ce3ea64c5818b52cf7a23b852634ae66282fed75974027c7a2fde03d72f33a0405017752da647fcc6586db16759d20ccd138705eb10887dc2b1bb9f8dde688f2e05b4cb06bf1f171dbcbd80c00711a3b064f5078878e0587d35072b00350cc416c71bffc577ba38cfe9e967af0126ee13f70d83e033a285c5d09334bca6c27ba71d4e8e328cd8a3824c3210daa9f7d9a57878468ee0bca12ef01190798b1a3ffc4228f214d449333111590d5e9085957caec71d2b76522de183abce98e283929bb0c3c32741a628e284cf4c42337dd292702d28e9a063b65193aa756b9b1eda37e107a249721fa66e0f566e236b042477cc24498bb8e115ecfca982a118f28991640a8502217a6d9af9a44964c5451f78eacb86ed401f0986db768170609ef253a239de19d036e78063c364888868eb4f10c2f1111fc102250502d7c51fe605aa6002b85e909bc48b02bcf7759d2e4d8c4208ae03b9c850bfd5234c496ca985aaeb6efd7a03c2916e8861f80379887ace64a8e5f198dbcc28c6d44d9c4ce7e529bc0ca89ee8dd376702f2b4a8b5c505884c3894d7888b7a620bd351be945398c0a85df348a5c5e1e617938bd8799b804ffcfcfb56b9466af445b4c140b745c255f69a6c79349a4c32a1cfd44aca486ff290fa160413f9fd7e16088e5304ff2428d209c36ddd3b0f651d636f423e24e3320371b8b03a12f390054ccade002aae2422e84fda2f007b3e7f929ceb911de67daabeded28e3995939401ab1ccfed6cb211e1fb8e9c187518c63524fef89127360f20a76c851bd7c98df9980922f9671015a120dd825ce4a4e670314d0d5923d733f64cbb2ae0954e47e2e5c9cdcb05f3f7a0d2b91111e5c1d641c654b3f12f543fbef36f58b32b80270d27e48eeb3d44c9987368972cd5938890eee583a4528daa243cb9d2ade01318176a1ec83400551d746c05257377d79dafe0e0d5e1dd9e0445983c947223a40ab65dcef9f373b469a651da47e74725e73d077ec8e6965439cee220736b156d504384abb47256734552ea69b7e3c8c6aefdfd39f684beedfd5addfe6a32c8563234d1c9b611a3e59682de7efc3f9cc9523a56e9d1beae5aacf5140103989c2fc295ad7a234caef29f09fb5e6c8108683702b27debefec2078b7bd8f30d63b3dbbe5a09ecc83aeafad2f8a7bca3eb97811049ac0b7062fe9bcfa42ee160a889461a9d8480ec0e816c7beda7fd032f960be7adeab3b9c8caec1322938b0c99fb87ac18e95726d3589a2bd914ee15614c8e2878ced75a91ccc2b267971f6206033d6c40842659c1870f6f464d0c4966ca4eafb8389cc5c979f83f208c3a8d5ea6a5ecf2902a3fdbdbe7d55b5e16bcd1e2d120c58a4eed2ff44c5a23af32a21eafe5274ae675bd202b1781b7b977bc458f31341cfd3a5fcdcfe4d16fcb1bbc71d36bb536bc0b41860ddba15cd57ac95a4ee5a80c2ac4628206b616561048263bf22c5ff297ba0589ea6596df46e90b8098d1606bbc0d8cb5c1374f38568380e479d9d93ec01835b218b3ed3b6017d4f146672bb16272407914d98677cbb03d85075738d915435a98ee133ada2bb344c77f03229762d1a06247fe9559683c493fbb80a63901fd4f30c9ced26ef9665f39a0cb7827a2303ac124d2cab1ad8efc8815588641a00990e4063113cc3ce6e48e056335808fff5bb61c61091b26e2409cc591941127f89df7548fd6e0a8fdbe87e72265b03bff47b848ce41d279cb386505bcb86e1512a82682e08f039accaaa3f1d821f8b8ea833e666c2bedb0bb1f41ce8b1f41b25704321c5dc7982e73cbee39979d9c8b07a7c1ad72f2455de5e6079621ff4397c95624d4c4e44f563a56fb85ce99d6693187bfcdbc7ef206e312f597f6ff9b4fd2ff8f234c3c873d66afc7ed2093f3fa336ca02f5d93830634cf50cec09e5dd3433f6de986aeab6a67dc425b6d72fd445846b305e31347b76ec7c9e7d26b0228e50bc1cadbb018cc32c211f40973cdf5a195e6c31f891c26d75cca0749b4b01b4d02309819702979098e58cca06f06ab16a7ed5b09ad5a57cb942e6bcc010428c38bce727881b00cfeeee52de91335e62b9b31c3c7e1ecf528d05502ba54c4e92d902da8bf41db79a83351565998c25dbedd2dd05c068fb2d08325c480095ba9da00320370fe0cdbbd9672169adeb1c5c013deb0c6f7824e796a75f927ba961c7da8c82de7a2a60552eb63cc2e7273ae33caa3e8c15def0aa967dab2c1235d47204ef60e8ac14ec39c36dc66272a09d368693d64e1a7d8548b501b9d64436f578cc0a91d7fa1ebcc0711a8a396a00f098dfc105b8fc4450ffc77fc1bcbfba6881b0130f04f78fa6199ca3e2ade1e464b2d54c3d9d36f3eb5f6c814e08db0ccc8a76b8fe0aeb85e22cc28c6b76c48ebc9e4c936c8cbfde80d50067932a70488eda1cc5418fdf0c1161aaa3389b47e3568e02efc6a8476a7a1c95edaf4cebc1693e46c57d739fe01def2e988f36c9a2d86089a8205a8f66a017ecd373c5fcf5c9a7677e5bb2564ef4187fd61ef230d5bedb92d1f3376296d6a1f738f8037c5425e256e2d54a054a277593a3698de56f2b439364c9a8ff736e74e993a741df893fd7fe589cb2097de9d4892cd0fcc27df14b77d7d6e2a4e32781d442a1e5c3ff5a487038e8bfe5c3aa06a8b44c53c4e3fea58e58c9d6d59e43f9c455e633b397bf3547d611b6e0fe3263b90fd73e503155d8135139d6ea9bceb09dfcbee6b68d518ef4833102308f05bdd9bf5e872d11da13a5cea7090c6215cac0eab1a07306d73731c952939fe242c1b2898b4ddc289d37ffe24a95ae4157389dc9d49c8b8f6244b2b55150a2b45e149a04005988b148c8820015d5117284e9fb3b9a927bfdbf90611f5345564eac866f1a22c833b2af3582bdbd6aaab9d683f4e45240ff7aa142329b53ecbe889501b987441c07c799f30585fe236fa0e10660b4c4bb9bc5daef4d047f0d4b9e69de3d5198d97343f303090520fd3832df14cf52cb583e3a6ae78a3e313981ecd011150c5f52546ab710b1d5064abd2d6e1a250b1e325e8dfd948e9b4635af7f821fb0f95e8ae8cdfdedee00b85f71430b41905ce800b2fd544a4f19afa98c9ba5019845343640308146e5ba8ddbe8db12775e516ccfaeef95d2ca8eb6b4b7c1fac90cb34a6f0fe87649fc06acc9d45139c4b2d0f4447a9589fca8468efd3465d36e01ae6c6a556df1417ce4da61366f3b4359e082c43f7832b46c64f52da8cd249d3c30163eb948199532daba5c35013f8ff5c0f246adba9cf79a03f0ef80038feb71211ee59bab93d725d11efff58b77a708bca225c6b40eb87bd468051d9028d183025c67c6b9b8e7c1907b8ed204326cc6ec953a385a8b13b532a145d74d78808b17360117c68bef7be55f3d8f9f1910d46be16b86c3f8d810235cee45e0945e111541407b8f3898c8ba21d73806765582bb4928b726323381f05acceeb205f0f65fe50fc70b016cdee488d459c4f6215b24f8e0f5e5dd2c0bab0a1b2adadddafbf6e10a9ff2ecb0478da5d73ee4691874bbf53af8dad139cfb2a97cc9f3b5be8edaf5602ae0f6b76fb911353420ad352d76693d5b827e4604f7d6ccc57556f35a9dd8a62b4676f062a3f9e7f764ca50b4bf98e15c5fed57208ec61e6aa52410365a32a65e31a3e93dede1b81ac0b921612618fcaf5137f0316924cb2ba91afbc3f088b3412f0ee0ea5c0f05c771a6c00f637c2ff4dae3abb303c073a4860960403e720c204e7f1fad39709caa27d337b3d8f572b435cdfee518661a1fab8e64c7aac1586b56135d92d04ee9f4d63bd516c0b8373841cfbc5d25c7e808eab1e4561e0a7a93ef7c731dabbdf6405a5f8b1bdd92a736942ca38c1eb349ee051910a27e10fa7b91ca5c36b8823aabef1560e81e719523f575bd954e8e7339e6bf5a9e8898e34a5044c255519bda83282a5114250c62d9ee4ddffa2f527888c3ac010c8901da01f4464842662a07192298eb8ceac5f06aacd9ec8c095d83b6fc4750ef763061710191f7f4c928bf0213922e17cfd6536e7b33fff955b81169749f1a24fb6bf3cfdf9b97156092585e526e78c3784ac03e78a6972122ec1f555c61ad2cff36ba604390421589e045a8d8b84c40507adf6880059aa38f437afa672929e0c3aab215180009ec6aac1beee8a69193f681eba3c8191780fbf1e20b8c2f1f62a7a115b98eb1e41499b8844d362555183c6e3eb8d203c692595ddf8712006d00f7be4fdc6b42c90b9d761a1a391f2804a517fbd31befa9c2c66238b965d8aaa705457bf331492926aa7fa412072f2a55d43b667c3572a716943480992cfbab67cba785ec4599643d24691e11e90ef8a5fe71226254ed1578e01342370d100a0b908d55a319bd39f03b5e4f8ef37e08f08132b861af9d7cfa2472173224eb4b1c38bccc3248538457c8053a530a8ae51adb598f00526cbfccc4c04a98af0306c82a11749d0aabbd1b6b792271985770eca1f4997a4ab0c6d8d5338515f50a40d8c8459d845a8b275a4ef9042e753d4f5db404563f4b071639572520742d9187634997d189dbfa0de42ef91b8763fbbb5b317d6ae28543b9d112125506f9e16be829ca297f0c1b3df5090a63763051609219350dfcf5fa94678abcd5be8ca8339671a17cd1c03ff374ae99f51bd1603a2fd9322b3e8e4cec10fa41f4cc756bc46f3a1ec5d0f301ed8ec67339f317579e66fb081581c28588ae07f415013c91c18fba3b17708d838dd92f789e061d1356d24ac02c0dd0d8d3ba44f89d5c8068e967ceb94889e3e9e87064450dbffd478324cf4270e13f38285755964f537853fc403042b298e68b8368f58259cd3b0ce9bb468b55bbcde7aa59f90b3b4629364c128dd11106a82541c474d47441defa9a7f9938c439b8a2997211d64c9642a607c27432e8c6ad1ddf089a30ec1f4509bc0c26b922cb9fd16c68cfa4cb6db18fe540352eaead0413a5469fbe9a65cda02c7ee85ee788a1fce28727b3a4e2eb0d320e1b568b040b8c4bb85aec536a096d12d0c2ad7adb391190e3a986aad53d1cc8640f6f42e1375e6a17673b6af547e90ec9d18f0c7e447f8109d111f3446b3c72ffbd4e54d4e95daf9d7b1e57a0a8dd50c5395789b2d50e58a0598d51e682997d6239a6235c1e3c1d0415cd0226df024a5d5a4c27010ab4488784b62d83eb2d66ff073183d34b028a101ff8c223eac5731199bdbb907c02cb845d3abd65008a26b44b5ac8195b12931140e5271483179949ecdbc3599abef333c0305bd4f7005d9bf6366428191c05f3399ce9027f82b5f5812e71a14ed8a77c4d0cd7a8b9084e4b95c997e3b4041a64a454ff9410ceba45f069ec5ac8f434dc2172e147c05c3c255a094cd68609be864c7187b95aa7c5ea772e295581975fdc8624c10bbb847b41986a1a18f6385930a229bda2ed9acf82e043662dbfb357c6bdc288f4f2f1dd9f77ca03ff6f0466b223fdd86e13ab702b56faa9a1d2124363f76b2e59dd73d62910cf27ca79b55bcd0c179e0a1eaaee09b3109ccfa525ba8aeb28c5c329b5aa16eb2b00aed95bf1f3d53ea74f7af1c0e152137d7690c7e59a8a7442e6505a20e556a8c6d0361215711ab69d2ecf02ca479a40cf4479b4d47c8e082d13e4a78a1fb250f3a00e2a7b03470a6e790f266847048d73c388ebbe0576a69a21867b50a56d3240b84eeb1050ce30f51df03facf240fc2737b75f20f8c1f0bfc00d985ab818a44c9c3b43c214af8684d774b88823520d9986bdc0030b48be3261e90744ad06447b62123fca6aea5c073ab635d7de7d15104668d5471c72af988a05676de3d14e2ff8b2dc947626ce4987912034bb469ec6d324f24d2e0ee99d784db08fe7cecf5b1a85aa0558531a752b115c0f2902828ced5ae075662a5715beb97eccaeccc5e2204c011cfcd0ff0a73b3f79512a78d65da5f2c448e41ef030c2b793a33b53233d8ca4caaed376b14f1439904c84c6a534eae9459c69948654fbe1094bcd09848c0bacbcc65289c9ad8b5acb7253c9e63d59047c13374ef98a28495f3dda9c917b0174726125df9f9242e258eb46ea183b467b38bd29773a06ae66edc46e5d3ebb7c30b5c5ba2d6980648505cb5769e37af64ecffc9c9fba4c3e1236ca722b7740ceb09f8f65017f45029694eb6ee9366a50715d36e507b1462eee151948956beb9ab25824611e58059e24bb288efb57bc0ae052fc7184c732962b75c3f599088e0b2145dee4b46cd260e50296eb3c4334b7da4757d30da6ee8a3dc04250ca2ec6158c4b39c02e002a30545b021a93569152362e60a4fce5e2b0adc835fb1fbc91e79c5d32e3bc4491944abefef218349fefe0615bb64ec6086ed398ab56074b98787878650a5d84ad8688c1a4815f5be54c546513d381a6e744de59086391621abc8062f4523f4a3ffe4e9355cbc8ba6455ca6044d64691c215b44bef2ef281d8a2074969fc411ce749b3f8de30346694764432f467d152053262610252f89f67f47204e3152dffa2b00d39b122377c1906cc63ad40f38e4bc1dc49a3606060517cb5453d0434545ea8e02870b859d2797991933fa02d40022fb9f4ec487b812a6b2c551656b6162d6ab1759e212cf2d42872a43e0e3ec372d24f37d0f7a0e6daf41a95c80bbff0e0bebe6488af7971bdee534414e3251f37d3ac799860e7f5fd4b5a093b152940a8b4bfd6a2994eeefb0eba935904531738fa657f40969208c7f7f2994a5ea02f7a8ec9d0dbc9c4cc21d7bdef28a753fb66440fd19f869495b0254b3fb5a76310280719d06aa424d6c656db36ca44013692b63648166333396415c843628f94abe19067717ddea9b9b8f4ec0f5173844da8ddb29f5b633229295c741d50b64d846a6919f798061e383158e3dcb2dc83e171a1a0db89c16000c92fb1e903789fbd75ee15ffdf1e465c35fff4364afbfaf0d82d4a5c1b64c31369c2a72b2f484b7b5c773234f066760f09a3b486519ec1804b14f5ff005528f40c6c140be2b27d7a87ad1cefee41d59701c540c5fe38cb3f408a28ae60fb1f489c352821e5f3eec3b983c7e84f070902cf36a5f0329dea20ce823e7d52cdd341faf293e4d3fca3b6b4f59a01d18d4fb3f0c8ecfbf3645782d48937c02f98d68dc1790220554f573186f890bc1748a4597574a351d09bbe7cbbb13eea38a36032b7520b900d31dfa05b628ef1c880c30eede5673e8ef87be4c7e1eb3e55b866e0a79dadd40aabd51e921593fb3eabad598bcd788bf04b2c9ab31c7e75d4e10c224a11e9cd4ba65f08e3cc21b01dc220f0f6f5ed04294f40b74bd946c45b5dcd841042204f87a0c9717aed7ab3d2a7e0cebbb3119690915dc9aca0012a7ef3130b7536e2e6ee58f192d5b0ac29fc0b596748b9540714395b971c789a720136ea2fa8be33155595b6d6d6772a90b580180f8ce6841253249b30222e85683a470d91e0ab8f4b70bc75d09e98134fd31aea8ec283a8f09de0a7ee68f366764003e7f76ae6ad0b9d7cd06599c9ef408b1dc97c52c82833c5317b09a78b3d65a375e56e4778f4b56e9b52f5b88efcba089e1a16d9a76bde1d1a2b4cbd5482a26a1aad7a7c7943776cf268f8881cfb7c09d80e120685fea9d962354b9b78278d0108992b8e7119a2e11882358e84b6a0ca60e7f372ea5a4288b6d51fb7484338e9e5e9c055cb4eb64f71eb6fa4a97ac46df0ef065bfd2fbc7191135d0314496d5d6f6d0d4ea9d15c51499160462b4659f6f67cb5066df22d47c1472e245c4bd146381a3aa6a735dc80d6eb96ee6631cd488ef3e049664085221c92cc74329a41edc4081a040d95204220ac4b61e7d42aa30e2a0e28860eaada6da05f6e0679e86e822c548782637334fabbe432b30e7dc3e7d8b0a66d3f8fb007fb6a188bf7533a02dd17b61429582066fa45ea122d13a8f2f60397ee7b184d750926b9fc28f8c4a6d27e452aa3b6a52f239f446ba9eb5507c1f3ab679afd9efd684f1845eed7a9da8cfdbba0bb96f3a2cac22efcad9e5dbf43cd628cc9c14b5ca529d572770bdd9507b3de01fc7ce78bd3b4d4fb9ca8ef177eb6d58ddd0812329467668e28bf966fb571cf5643c7faea3059afbc912ced37bbf97b964c2784b009f2928709a1825dcaf8ec062162887248ccd996342aec5b84102d5a6808ab3063cc6f35572a62e1d60575b5670401b140f2f797353a151969c240abcb8a20228c53ecca4065eb49f49c471e4955b3b000f70e0a4be99a45fd90938b70e66deacf1b407386deaf54dc40afcda5a6fc1ef220c125caa2b68bf22ebd66b1a2b164d048e9b91f043cd90b1eed4d2ef84b7a24dd553fc67acccf4008e53f6b294901957b12f5931b6ac2b113317a808dd0198c10ac51bce247a1bc793b8d6e96222823580bcf9d5e2f028cf6116b88784fa1477a281d1478ca9f35bcc636c3f7c5a281d85d147496f99f93eb35db15e8cc61b4d7efc7d2b79128fb569443dc1a64dc61fff640d6f6c766e0d12215657bfc6f62c4750b4505f78ac4e10a64a32fe686b7615b69c5c0ae92c0a22700f846c218276444d40cbb17ee03299ae07c477e25768117b896aad7714be3377a34a3d45c14dcb5d9f716d6a977369d902cbfce970841ffc4f5af51501f3884f3e731b1a35867322e79ccf47e886d3b51a4a280833144b7a5474a04d89b0964cd6901b48ca40eb973c2bf394752dc574425f982846bd873182f02d91024493f710e6c14c9b46c4a1c08594c76df400da0f2e01a8857bfb5a898582d00ae02214b6e6ef2c3454378e5c21489225b05db4ca9352e449d2f2eee4e5bdda8cc85296e8ac50286eae97a543630f61db0d222c49d757cc95473c494f063da26680a3b9513ee5830a644aad338cc6feaa84f8582bed672ae3304b0598e44f3afd3aeafce0814fcebc18e38b4355e87072329138114b99cbc3556467c8619dc2c96843a39cf5423fe3c5f823b705ca74b8e12b46a32ad5bb82ab18456b044bd7f221badef67c1bfa5b8b8486a96f32ee7604e8136b2d3b1463569c224fd5081938db3842338759a1e62d8975b51cb035efb896ecd57b988c9f98db4b8094598883b5774d6e1dee0ee66a529f4138a8b523d5f4f2ba775c60f15d6eae4df50db6d3903c251f32f6e456bfd982dc5d33e1a89a497b79b9dd5ab2b5bd0f546a63e5fd02c775124834fc825ca88a773ed052fb968884c1f2d86d71db0831c29fb86fc8f1f562babc4a55819fdc726bb5025497ff15f070fa2081c07cc1041d733fc712687c483b40499f62515e8b23b4cd6136430e1f8114dcf68937942d3cd88069bf34d093fd49edb71ae438b505b0db13f7fa85fc3c3f90a70a73c1eca04b893d396d5ce548a608b56aac3f461f3e1883c6b6145afade6c7d47486aff547ffa1e309464cd51cc8abe60b32dee670bf29f10c5d6c2f7b35403ccd563006149c698f491129f04805ab7acbd67fab2c9f2bbfe74ad21faa31db21bd92974f4ce58600d52c957c734a6dcfc42d1043c1063cac03d47808d022b21bcae03ecc905fab4299a5ea87db60ca89c75f3d0d2d6df536c884a9d4de24943b1010180183ff4cdecfdd7ff9241fd9a3f61ec02c3eec0649c7ef1927276019a35cc76d40bbae92d2ad134498f5c8291206df0f47f7da3b3add1c53e52f71b8b45a48358c0baf7b80207945d21a5a14b029f29c6c70cd2d0a7bdd7701ae00ec42ed4f9ca8876dbd0f5a4a714e3329225f7429d8bef3b3c99d5804b695a2a18d2241c265321802d0ca5994d49fd05fbc192997f3035d7f355d2f03040518916a39b8fff20832233f1f635301b10479b9428b107196bd671ad3c79b062bfbda63a66242763aea0c90d8f195331cf40e55788661425ecec129d767ab7dc62db50a9c6a84f3822163ddd55f8aaa6f9070b4fe71b9257f7fe4279840e06a57414ff1fd6f14506cb6ae73d3e82b04d2cf3a883b16f3c95d1a56046b6b242048c0990072ed398f2284a98ede53d156c3e4e8476ff4f68ec85cde5dca377c65af6f9b4c88d5821179841d8db743c4295d300d020c28541c4b5389179e5c20b3989843763fcc75f9d857f5e82e163c75dfe661eb532b4e5578532764f12ea9714994ca643d5d1029edc9baaf7000df0d511dac74852796846c1140f720ba2c0b81615470467bc1fbf81af2b421fa74f0a7b3097fa2f612a1c85999e5e965986774b936cebb08a1615d8f89af748212b572a633b99807a0de10d7eed93b476685f9203cf8e17dee0cb5b3badecc917645e8fe4fa6bfb31a59689760ed39378155441df9f182902d7939b78b6dc8a6cd193829dd2599a328ae56e931a69485f2420d909b73d9da4d84d927462421efa566b53b5228a0107d2fced97c73bee3e59fa9b712dd61f4a8197531e9224f95e5559bad5b270a3965b34fdf71ee4cb7b007dddc6e84e02a8610ddba6924b7a9ea6912e07d494c8523827b1894ba6bdb50e17f14c40cf92aa43dbd6a21fd9b01eb48157b87166b72d1c59ca7d6eb591d4bdb5a9bf96433a7e90c7bd05156dbb39fe5f73f3cea106fa4e084904765faae98450b1e28076b2fe166cc501fc13e348389cc00953170fb69d8d17ba463bd930a6b2fac7a720f214b0c0800f6303326f030a2d08d7d76b8d8702a916a057510362d2b2dd0484e9762b938f4b76836b77c4f7883706d9d1414ecff5c94e9cb8cf875985b3e26fa47e7ca633fcd83da2d6d97df527ce3ebc575aa9aef2890aa645f1ee506b4524da4930761a56a4536f3090473a97474df0f1ad17b5b879d385ba251ac5f9a5c64b7d929518719896832d579e0052db554ab831fff57550b9c2cbb03ab063933e05b1c0293f43044f9dbb623825ac23e12b1fc4e674f0098f1acfab472b1594b522515a416917106e990b8ce03c3c34091291c4e8a813ae3685f0ef1a98500f94308a1d756dae7cb2a5f6e658950c04e4c85a4be542a479914dc310bf9975cf4f322f5b52379eb11037ed306ebfa3655f630ce5fcc01e9f9c98166db4f5fe5e623db08419def7fa089d080841320d70aefef0bf2a4509736d4381f81925b79554291f956d85ec7962c30373e2240361958d5fedfd12f05eff5a5d06742e4678de72102b8c0549b00b2d21fe392bad65083ecc6012a327240bbb1ea6a11677274c30acb7e1e5af74a3b836b195388c735e1b1f97a0a6962a55d1d9f5c813896a7c0b1d1625af4bad9f9cd41c8c43d778dd373bf0f9e755d83c2a86a444a63c997ad0447c40f54632fd0670905deb60dba56a574d4649c51f7a314de94b19759f6c873753408ae49f71aa7b714329dc36bc202a6e976d140ff3529492b5c87246bd7fdf145c56e8408bde28f1f9d5e9feda39d0c5b952a073dff8627b7ea805f9d57b96121966ae616751879a27bc0cd9e7671d894aeb0148c3f6c6b94d53e09bc35b44753a9e4d775fb5eec89dd14b66cce6f65938355d55c0c5a3ed5d2d6ba02bf1defc8d29f330871c5200e9ff49c4ab5f0ff17159cc96ecfbca8520672cf8c917a4b5f3c632e862609f695b774ef219ff7bda407a08f15b0f75b81247dba1dc1dec5cb247d8dfebc17f17520556ada65aa06d490ad59fb8e6c49a7d9f7736fa4af0afe222686b141fa0158aea41ad1e8706f3fc1692bc32e2157c2a4e200350c06651d176e9f0fa304d0cc72a4931a550b6587f31c7c54ed61ce0e010efa047a58c50b2a57fe8b70e481b387e37da9976ac9850bf24c901ffce85072cf5865dac2a908f8b882047795b8bb78919c2607f8dc3c5a718887f70d08520dec343fb623efcb9f448c1550f07c1ba075b910c8f916b65450faaec150dc6c2d3cfa2b56776082c831e885cdcb5b69235d4497e65fab67bf4ff8839ed58004fdae901f09aa887e147bdfbf5d65c65b1999084ba0f27058139ea1e60c0d37cd68948194b7c0e8be9f585c64bc6c5100ce9b7a840c1b06714eb6f7f4e2cacf54326a33a49d6480b3742d5e3554f47ff171e187c51eb6aa0976ed496cdd87ed71aa3598c8c8691360e181bb2247e3dc863f5cad877b7a5eb97135b53dd7176d7dd3006d41744b58b861f889eb4468750ee73278ef2b21ca1d5950073c1e926da58250b7aa7d6478c4c784f0bed51d158f0ebe6fe26330b56c7d07eb6809de72265587175e8cb17e3ce22a05ba4500e543f3fce8e5685017928857d4793454fc5166c922f2cc327b8faa5825437b6d4addab460aee7116537f3cc418d47155f41b21132578edc403ea7f62d4e58567f007ff4c61ab1cb90d0cfaa09a8277a97e45a8b6e78b162a717221af6fb13d515431aaa4a5f1ae85296165a6ac3363d61740b3ab36e1edc34e8e73a022d724bdcd3a980ffab6e1de5e3e8e1ffa931f263d5af061e1e56a83d503791bfaf1c2711d1ee8280d94c371afa5baff85b174653d6c875dc2a9e609f4484b3cb16bf99bf630eea650ee31b31e9bac271579a3eca32efc7bf5eb044ea4589c080ec20ee8aadb75cb7bceb7cc6890a3647ab4a28236f08a7d7fd1d82f40b82a5c231143f6261e17307c6095423d57bbca23bcbc7c95d3a72d2c35b76f67af91ebff4e8dba568c277e13978078944ba48ed8b8e1758a17236f7c5dc809936a37e564f8c8d9d732019dc49802a5ac3cc039dc8c795acf1523141dce6eb6e9aa19fb4f72852806595ad795a88ecb557e5e7d3ec0b85d692981b1d577d4c1668b822efd5c3fadd2f4e82fa47e701e65100a11d776c292f27656edecd981e89245be302bfa8e5e81f18bdd1cba40a5ec19656b02e502fc18be709cc1699f1afc16ca9353829eadd98066242e4f88382bb879148e9da0dbe338fb7d083b6fbe66f3c816a3b20ff266889bec9a43932dc638867f85e655f1384c923de503f4c1af2afc68a0a1fbc2297da0d7ea3587929a9d2838c8e09a535aa4b76d6a054e267e3d9132bbcc60447606306dd0ced5c386721e0403edb3d8dc2b64d09f9a239d512b04e0d6b3814196502292a118021364eced07821573f4cf3da7d6ba94ac75a1087c6e7a78dcfe2f8b87741cf156de9d449a58e552df95b6cef5716428b37354f98f62023887376ab4824a55d27b8852181028d24bb517f0fcf60695aa465575b7d16adc11954f650496f52d24cdc67593ac11ca4291676f48fd23a342dd4b685da3332ab5f4991be8a6c6e89c9df6ca31467d49139b90cce8c4d4c06ea3b35ce854f143f1c03bb7596f7462b25f9c8262d49ad7dec663ab49a4ae0fb0991d0a35b6dccb1f2e7dd5be6ab4a33f302331fc2b2798cdde9b71596d2319478362bf842471b7ff025bed5e28ee6c509cf363e15bc4a846241b62a212c39e895aa38478592d1516ee7b5b95c1be73ec1f5a1285a9a09879cfb76eada1f960f23f59eafc89555a69a21babf1e5978eec61b5c6a626d0c22865e14d30c6b72ccfb049de909c0e5893bad84ba0b090495ebb2e3f0ab28a5ca4d2ca62b40030a16c9dffa9136916adea9c9d1ceae7038e2ad8d831a2734b87f0d7b2f6f4358785e9723027b308fb39385b8e4763e6e67fc9b5c8ab22928039684407cc98a5e7d37803e6f4c1acfed4bb1c37e5cc1963d3bf4c743c06633c68fc7dec89c9e05e61073b5a5d4e9398ddc8c7e0bddd5f35923c931b3d44372de55da5b9506a1ce657a24645f93b58010077e1688aa9cd85404b7c4c61971ca5ed1277364535c2de422f01e082a74240677c8e6289f66e51350561b86de8f2aa2441913567d00c7aefb18b124e53ebf057edda8d7f188b653e06ca313c8ad9d723b8fb32f1a27bedf5cbfb6e1186c367a54d0fd303d7bf0f4898164f49652ac71e0b38f0052b202d93218592a76a96372ce426b1b60e2959267c59fb08dacb26e24d24627e63b7bcf632f6d3d75a08efa32707fb9a2bced37b22e81c2d9d97434ce3a6a7b0ba434fcfd6017949e25e117db16365681a49d5cc4fd7363f520380e6a37518eed6759fa062b811c833dda6528674c0ab7c2be5159a62e8c2abede6270149c965c03a01456f581803ae4773547491a52c8b295504bf3492ffe5bb220a43e1cb6001fbc75948f49ce875b3e055c1fa2d496914d7f49e98e7a1678b2351aba528ff14a2252a6c753bffb0a044f76e813b84b0538fa1060144887e98976c9eebc5f89bd03a3b4e04b68088230889623d728f9096b1b29f97066974153456f15f5fe6aa040ad4df7ea73fe6d737acb29cd20e0b87b158d053836670a23b31c6fb27fc13df39f906dc7a7289fba17c1e3b5d0de847b97058c6799d409d78a93f441be76eb8d0719639cbe3bedabba07ed830c55704aa1054e6c08b7b26be44bf6345fd8bac2094d383bb1863e625c538050df18cdc375352f06e6665313d6789299d5692393fd1d468ffa8889a866fe5a6f9aab8288b89f68889896e146a7427c5037697a0eda944a3f4cdd0d98c308fa41d397ca52fbc7d653a02f5b2ea7f08e00831546719dd025a379c9fb87fa5f1eada8d64c1c3c8e43ff861a331588160a9cbf19027d095e9d4d98f8aa358f23ddd8b598aa89ae982d8431b853aaeb7ef0d859cc9d62c7e864e4f771efb18144d34e1df75abc740e2f84820a387f1042f21e3985d25cf354388f0b6632d949cb9caac901631b9318d1e92e7f5caa3842bb485ad398d724f584b910b4b985c743128953147c49ed9329acf4cecbc7ce2673590d9263be40a25f640c19d3df2d27d04cf07aa2b37eeb8a6cc08ca470b6385b85ba5be7a19702608ea502acddcd01486ffbfdfcd5de33045c3656954c9ba1be85f4f2060fc9faf57042bebbb7633e167f2eb34e47e293714a51a3341ad46252a6f6cd9ef476d3de991030cee72ddffcbc953b68367e287373fcdd7cbf2ac1b5a22d8847abe6ff29a7577d09f8bb8c0becb6fbb2220726fdf21c3deafb67ff65fe7ce1f4e3867c8a6a818eadc00965592df155e94bac654d4c3acc5cc779abc3dfac21c553dab8f72515a061bfde0dc546817b04189831efcb38affc2f17725eb6c234247ef9672a1e89c83791321e0852e196ae8094afa8fa1f5152949fa0f8bf1f9f465ca3a994d33f7b8c825872c8dd086d3b38f8d1f7874ed0166668a903049ff5ae76fd66343d5d093fbeea8e8036d7688ea6cac5bef200826943caed7f0811df30412c11fbca212782af7d19c8e913e45b6b7b46888e1997ba42e8592dc03d6e03f03078b42aedffd213bfcb1f592e4dc31725c33e68b7bf48057fea93548e7bc4f65debc9936efbbdd883e40e025bf7441c6e357b4669b008202f61ded44344c7d0c8b5e9c9068beb6537a4c2b2c3a7b49ecaed3414fc8d9a9609b38f623b0d14f1533e27264ffacff7aeea69e0339abde8a9b82e4564bcb9d90a9251a35217dce79afea1f3f4a311340915afa33a7d4a5bcf6d2feb0c2437cfe7d4a5f0d938dc078240d38a3d9b7fed5124ccac30d29f4b09ee7c0ce7ca317a3e1cc91623755f112d9fb510669083a3f20341fa0ea2ba4857485f7ebc7069755aa734ca69c14360906d9164d84c5fa62029db6416cf029f0e7862ad9d025cfb6d1f3f11bdda828310ce5ac863dadcb615b3b931a364b0d34d101d9134bababbbb8273b3e7f2b9d411f2702aa5b6208df89b9317e2d9541c42e2ee339f4988a49fd10310c3b3562775608f19145b66cb5c34046054878edecde7831cf2d8ba59f37467a525620f6c728434484dbbb32cca6cca8fafcda45078c2d3f2b3f27bba120fecf4dc575610d4229a0acc7f9498cca52a28105ceb7817f358f7db4582d9aaccddd98f5638ec5b1e45cbc7a86ba177178e9ac0d22d536df35e5d983e29fd24e654a1e196ccb60ea7c46efc720b94b39d0f86a0fe526a68b8cfb085d8c81f5f8dca0aa9efadc06858e3460e8e18cfd92f6dd29037017899a9b7cbe2a88f623383910d92d5c36579180ebdc884f95ca153a81e0e27eebfdb6315bf4bd2e9fe78202ef21cd02d1de1283fa8b8f07b76dd4012f09cf85d141978e4c8b0ca5c1ed57f5097f1e580b71a9ebd63bc907d847a4f233f60ec5aebfb7d059840d6d6ce2ecda2975ae2459ad4765098b69d0040c23db036a54ec11ff7cd04a06d50dfa6fe90dba93b3a37e47d1dc3feaa34047122dc329d769ecd06878ae8c1e912f0b9773e1fd2138ae19c70d3dcae23265eb67005e042d415aac143326cdb63c3e510632e5ad72a23fc46b8806810b02c1ae90c6054cd09b4521e61b7a606ce456d8ecea77d1064835b88ae6eeba25c81c943a21b365d98dcf830b070d590f74d5c20098ad43b984220bba0196bdfb4bc9beeaa1088ba4d307ce6675b8f1f3b96d9b940b82cab9f7f592673bcd4b6b4291ad1269c710d9b8b0abc45ec7ac7a85a2560a8f3494432a8bcec00a73fd92046ec9f35b22df7c065b2843bd1a0c3284c47b7c1d47437a70136e0f2040bfcb761f6f148eca114cc925b37fac8ec002a44d804f0c470fa8498ade2733158ab1b913c037c87cfb4b92292f790281b4609bdadee8b8c8fc23ebc390870bc157e6cf8e17303148b2a60591679d76f096378cba85a15f07bf357a085b825fae9737804adf64e537426242861c1eafc08e3eac8380ea3898d9d0a553f53374270a4be45a7fdc088d3f8923ff4f1779cdd4c2ee98926b855985b13d1fbcbfa42eacbfefcbdefcc464e88f56aefafd74314526e408b3b23a434c741706dbd60926e0db4f99b7d8b4b8b5e2e2e2757c9bef8567be60500096dd4ddd53b0be674f347f5d224f569a8cfeccaf9e80dc5125931b737873682cfc43a62704b91964ed8096c527255283c3fd0a6b8f6bcb963380a91e2b085e8dacb81e6577e54288f8be899b6b1f0c2a21672ff176dd1574d6bdc98ecc1848b122994e7a6edfc8d29d62d160c796fd3e5fe91772476be0d676724f5a27007862e0764ef792c8e8598a79ff4157d57e80756e57e29a5cd64ddc68ebd43255e6f8c3cdf60f548f9f98c719bd7d8cace6fb37df13903322e538eb19a8ce3e30a37e7341e7e8c511a27ef302814aff8ff40aa7f1e8d96f10cb4991815fa9ab3bcf7cfda0c03adb5e878ecd99f3f40d31812b34e8034a5bdb6dddee350734a9f6ca88648f63aea30722ba410a8cac1f7a58ba319b1c3b04887e23a85c2ac01397ccab62f1102c705ee10bb878d86b66198d5550233ff8f2ed7f3e22d48bd800cbb600a97fd1bc4adc162050330f7b1942b6f0b499d8e423d03e8faba442407dd8ee0872cdcbfc15e2733dbdbcfcf033160c6c9f6384915ddbc2284d5aa34f025ec1658b221e9efb94240b3ab170e633ad0eae39ee9ea6cc1898fc0ddc662319c322ef2d0cd714b8131420ea6c8cbb1c94a49cc877d88913a9a084567aec4fdc0138b601a8a7db0d8190964b867da451ad845da51ff99fa41493aa927000cd5132b0ab93bad88a3e68cb42430c0e3d5ec93bd91f43b260f983936f65cd1a9b4fb1a87ec8ba3b1ca85166cb9e1a4ed2a5d15690d94e798e1ed24f433dcae76d72e5537f6563046e38ce709ee8b2f600e89583feb492df6f42aa17bc3cebb99cf97832c72602813a5067296e434506ae063fd5fb0b731de2e367a8d130615c9ae86f884fc60e89ec8c8191740f861cbe56dcdf5c21256106951f651ecf6f1f34b57ff1171b763c1ad4ce936068752926c5a4845f048c35a9ed64adce76fb7a34ecb35d5a12c05987cb24718576a6f9b78268f6090c4e1a85c954b504bef34f75227f67b8b06801201887692945936b93851fae203fd54d1e11f7c24334c5c50e5f79cbc3d0dfe7a8be447031e4d00526ee6903409191faaf0e4cabe006547bb64060fb846a21a120d926fe4744941e8034d318e46087b91129173cd765d36917878797c0d15837fbda0fe2206d8ec71b265ddbe4bce9bbbe9387475ec2f2ef1302f060c48db264394fa80ef270c2c09da2d995d8a00a4a500051ecfd06d6ee59f16cf4c4308562021ba1091b63506e4d2496cb0f8274e49b96b4d120e815bd508efe975fc988638e3717a514a238e8b95ec5d6ccf0abeaaac310015763c49e49f7b1886b81f46e1534fbc28ca796b233e820b4fc0e6724bd8614055de6ec312dcd1760164ba2690ff3b61785755c603f7e8b15ccc2d7a5585124895786f69755437f6aa29c3eac0666c1699376c0b467a1e1ebf175c07b3ba2a2cd3f78910b9bb6a564d84c1e2782c73a5422c980b68d1e6dc016f0ef1156bcb41782453e4ca77b2ad3bc2bcc1596fd3ed4b0d6f16243c14186b4ca764df572edf2c3115afb12f730666ca0071549d07a2ca3ad2c20a1fbcccfdd1a6641edb2d294d819b292a5e40404a11af20b65ef638eefe150b9bf5bcc73317a6d60e410417724113a629a94f231d15aff1caf940f023b08456a3b4e3f780d08d93bdc58144dd3a3afc3df3f96adaa68d6a4a0005c1ccdf1163fceed06346eb3e622bf2d62388561442d825d597af6ae31726c156a8121ac9dd171d390e74603481852042909bf596f7187b76cf6666da36c899ba466965e802722751f6997754bc2f372a032a09985ad2428bbaaf6d81ee99c9bef3019c6582974926721629d4321dd289fa89383fca693a5d4bdc60555225f11566392ee259c6546bef9ac1e26b8241ad9846efcda391e160b99160b5157407f2e396e97bd163c9baf30a45c887cd6a4e1735a675374dcfd364c1ad5eb078d511a9bf493f7d54089d36915d74a6bbeda99c742e9f034b5e5340b4f5e791b6c987368b5ca6c019733258df6f9f3159d2a298a5c1f76e51f152558c1e5da90deaa4de403f20ee8f1ca479ad61394fc810817d97f9cf52eff06c2e037e3cf147f783ca5a20399350d48e50ed349958706f4bebe6b9db4175efa1016997b88b4b7f7628c6a42c62377d4ebe88c84f9401474afd9b490b760096ecd5d7fe9038a3eaebe08913395cad829669cfe05120df11339d3ffa978aed2e7ef211f05e2d26e420b6ca07f01322c7052a441b5aa24f1223c9a8def535b541e61b3f160ad3494afcd2f092de90a929eb9bab47a078009c9a53da0a2bd6527e73d000cc5431020f15bdd85ad5f5d2129ced3eb02d40804faa7fe885720d8dda80369d5edb5b5d45d553aaab4b34857914e13829bf64654c9261966fd63dbb691494363e6017c3f98d1b15e5edeece10d8040b4c6778c09a43c6e7e1fa7aaf3628aceed347072e5ff219fbbdb6eb9a59432a514520879087c087eea9ce0a6a445c1f6adb646981096deed8f665b8584a6f16f42d5c27611b2c216bab86d04f903a6f17fa24a61a908314125b1e812854419e1524b1eeca0422f5819fe42dc702e41d29420694f6cc892c6b54ad2685325699226699246a9a449daf4991e52b22e30d8d662adb6baaaab6d1549b5aeeacae3f16c9e0d06f33cdf30b7f1ab43678b1677f7f73c1b0cb66d2fe29a2f01a102583a37ae7a3e9dd753708dff9848645c86f4b819bddcb8eaf974de83de4396fd96224c11c66d2d13c6a50dd63a302947f877115edf3dabd94aa705defbafc0fb929c9070c9f361618844d28dccdec85a4bc39ab061c3fca3542ad7612b58d36030180834ea713344448406079124e9d30e188c460d56b163e4c8dd1d6bbfdad5adae358abcdbad180a75b1eb6a5723778cddeeeddddeddd55ae337e21a04704d02c09273e7849db9e1c80bda3a49b86646847b708c3f68731eaef3628c317a3778191ff24e172758c162dc7edfaaac51322b4c74efdf5418879947f7856cc5ed77cf732d2e75ad2e4d66e0df4bbe3086cbc46d3c149740385c86351249b09d4885091eb3f6cecfc8097b03267e2d73900ad3f803a15e71e71a7f990c060624ea71e3a00f0789d0502409d79c741c1cc956359e2063c7c8919bdb4837ec155dd1155d5b6f5bdc6273fb8e91232a2837d880ea27351d39778cd0aeebbaaeebba91c7e3f178b66ddb647c61258dab854bdbfb47572475ef1f5f481cece872c51797e8f58f57b024e152bf0cf3d35cb3b9a060d9c4262cae7b32f8ddbe245ce31f4534ae112e11e13ddce0bad7180cd40ea9ee1fe216a554a107a771b1ca42e5613c798d0ab1f5459683f20b232b028169fca7a822ba8ca476767676768cec189131992756d7bf8b1c63c766ee1be6888a27af79cd6bb4298d3436b7cf9b799363e40608f8602f1836e98d83a20c6d05d7f8b37f77715d8beb322e39b3d74d298d1ea5f1f361c2865e9bdfb2ce433d2c8049fe8ab02b768aeb2fb6c277380be622ca261e06cc654e664544b93e856bfc5532559b1ec60e5f1897d81f423737b55a4be9cd4d942b1959a462c7c8919b6541aaa591d28d6e3146598c41e246dbb62348dc464712f7bfa1530da54f5bfd12e0b569c252080a2023043861fb23c035fe096892049ac63f2626f46cdbb66dfd59c28a98b01bd3606ef464700ae4806b74b0b0bdd1205ce347e2916d6b7074c453b64ac739d8bde5e06f44b02d16f58a25c44a2592a12896bd8f8a0de5f57fc5183932f39c71048140111465deea6696f10517da8936ce628db19014f6891a0fae472aaec71d5c7fa70293dce5059760de7594f418a62937de755037a61b0f836ab2f12124ec0daedb8087507c018b715d72b1509c146b4770c913ad78ace2510947177089bebf6bd13b3c0b2ecd8f43c47c1881b81e6770ddb770ddade05db8ac068daf26f385f5da982fb432be6abfb0ba565416492d93c9643299cc23eaaed55c2a95ab93459290982c268bc9e88b40a2be5d8ba423227655aaa642f5e3944512334f8f3fd0c1ee641de8eab8fe11159b38a97a17772e1e995cef40d1e7274e0ff42010687aa0771b50bc71e32b358dff87f6c37ae383e223c2f5f7a468015ce3ae831376def0e6d33961411fde88e6ed3e9f8e54804471072deb9aace6aa2e14eabaeafdb82e8b3c445a27eae1b2b26871e102e3bafb8c2d01d11ea66c4597c4e80ed7fdc3d053d09c5630cdcca24e2eba2f9c604c19d7b84c76c5f0316ebb7a4b1458b82d8bb55a3b57aa4872ad56b550c85a97aa5659fc0212c51d8822155ce36f43f4825445528c4552e401d3f8fbcb5452ace213abf88395cb7cc7bd10f90b2e85eb1f12312b20f4285c7f1b9ec5f597ae488a3d341598c61f89d00d6c105f1012030b4078e0b0b0f2146b2e03c92c16f1e43b91343defef5dfc0ac699a0f7772bb8e4bdbf37a171b870a973a7fde876f7bc9bb255e34857f3983299ecce952c9298a5cb5ee9f23dd0099826922ec034fdcc85aac4f60c9a50b5e84700d3e8d802f37e25ca0f3d97305f38616044de4724eb47048e6630c10e07db656e13c618ae749ec700f7f1c3217ec4c1f3ce3870384cc1c1f3f1dbc46e33677ab673eff992f873df4cbf2989e7e337d3df8d0ea6d932a1feacc5c19f7b8f28b6e787c4f73cfdbcfe84e8bc203ee7118db866bec80b2b67cd01ba27830eb74e2bec0cf1bbc7803ff73fba796edbf462bf0621edcdccd03df743bae7be2453b64f12bffbb66f48f749fcbbfeda0bae9913fc4449b0f2c38f1f2b9d5e417e118b11c6cccc393723b0935fd8f62f5ac618ed17b20893524ae78c94ce58a3284e4ae383925239279d944e49279d91718a413f4797e3e278afdeff289fd2b05e19075d7a3f64cbfa23ca3869d41fffe3231cf4c3f81f3f5aa07f04618eebf30bdf47a5df0d78e76de639a2dfccfce49df7521d599df6ec4bb7377d79530e02731888d1859629877e620b2b35d394fe20b46ea279bcac38d8d14fc77d3892405ef711b5c79670a471fc8340f3f8b07c68cbba4262e66a24a46be63595758544fdbad63ce23b124dea4d6e5bddaa9452d64e344d94f2bb691e1df7e99eb0db13f673fd76338a10c491f02f1d9d73e33e54e4ad22b8a6ce561e0e085f5a1f99f191c6e9562f41eb86c25a1de9a6c56677773a2708052466abd5aa6e72dbea5665e5a4a7fb745252295b5cd33070358e5361239ae8ee09dbdd2f36ec58fdd04f9d0bf11c36b0f2b7207caba856d8a6545ee148ec0bf300c24a2965f7f1401f2ae26e75a249bd45d3cc20d038fd650b246efcb8ea2f34004dac5fd07b272f3fb8c2085b708107ea09d1017da148131f86289c564ed8b0b31076166ef4d30d0f3c73c2e9e674bae9d837ce830eeab43ac9205c43e794f5930506b0894dde924dfc1fef47e5368e9bdcdc56ab6debb62ae20b12b1e9745af9670389dce48992f4922378809972682780f083cbe4bd114d0a1203a05bb17d4d206f2b92dc89477151e1aaa8135127073f2cb6d5dddd5d02a6f130da861162300e8ee6e18fa3c7781fdedf112c4edb5fc138f1f4a526340ec7a5fa56d5ee9faead8a8d53a479f8cf4e32ff22dea73fedf1ccd37d8ee3681c1dccc37b1cf7f3f7e339734d7b4baea91f19e3c74353b85d889c71ce3a6b8c31566baddbb6515a2bf56a6dea0109bdc0355d802fd4c5ce90f7fe5c158d22c9fb66601a7fcf62b409a92159d56ab55a0d0912d0ac999bc66ddbe846bbd25ab7ba511a29cb91ec1163947d7a60437f5db7c1e23630b7992f63acb53d5c953b2cf921e435aeb919f5b8d9116385abc562514ae98e118ee3385624b1228d1cc755aed46bdc7024bf27d890778cd8e047dc66bee7312e51fdb07212259627386d8a5415ab2b602f07a5d36f918dcb5e047c87209114a35061d5eae08dcc11b13833839b629e394cc0e502b00da54c30e81fe2b83ce399bf654cb04d2bc1b4e536cd9af14516ad3355eb9c73ce39e79cb3d2a9044bb94b348dcf280cfafb84c2ce26521e45b3454534b138a8258c1dfdd01a2362138d11b949cbf5a781f58471e6a6985f718ca84d51c4b7e3e68d11b1a9ebbacf526b4c5f4f65c399e2b2d5ba6d33156933254f0e42e192d499a82e369c29192a6eb7613bd89952c2b2f763a6666a862849dc828242cd9443ecc1cb04c31022e86d94810fa8dbac3a6d0dd1ad6198a908fa60449115599755ad0dd56835b06e54b061b35256dcc609931a9592af48aaf1345054505166111316036b2ad11525b27c60c39992f9195fe39b9941e667fc4c7f43647ec6cc901a1ffa1a1f7adaece74b13c988362eac77e04c7f8dee97097d8d2f898d0f7d1200fc8cd7c144433403fa175e07538c6806d4d9108db62b581a2ff322206c979a9981c6c73c0664bec6338e1a49313ff318a8f1386afcccd7781c684c1c1f80c7c0cc0be0c3c1c6cf7c003e1ca60cb1f1338f03001e47674394d3e30a36e66bfcccf63a98a82bd208d1a1b0176d459a0304615de69dc6e733f38ee3dd0380884d315f030038be991a2aba7d3313a8bfbd0e37462aa22219dfa21bfccaf8220d9a074c970bf3531416b9309f13ad28aa60bef8648698bff14362fe068d2fe6852f89ccc739d113cd10b1292462538c884d34ba4fc83fb1a794dbd0bcc45a9c39e82e51110cae710114d7b7309b8856e825b8cf0d67ab8b619022184cd3c91b9b0a60cc142db2680e7adcf9c2ced4e748e31aff2e78c76bf1e435afc51d445664c52cd189e8839899a2551c74eaa22d185e60e387d479e0128dc6cac2c1808511f31d644b58a99adb8491b5a2d1bca4669309e54b644d9f59668a6be60ff38a0c08312ab028b10897933ba33839588b446898a9ae61a68a843cf3d08ce7be980f41b7f14d554ccb66b4ac4616685441e6eb2d66cc142d66be68b3fbccd46471892fbde16c4d29b11559363eb40c7470816052a4451a6f9cc8126388248e60d03fb23c92a612ce510c26aebfcf17d7b87baa65911669916524922218318c9d1d76967891365373ced93595a231900ed663f0325fccddd2255b9265ebd6db16b7d8deea768264b15eac178b158233078d98e233b789dff216d7fa28e30b1bca4e4996cb6da4152e6ddf754ad45056556ca122595226a7b82eafc89914b11536942c09939fef1e1472ff96b0de9a7ddff711c99688e5921549202859208b6e3af849bb9ab41acb8a8291d4bd7f1a003bb0d3e2d545f4642092011aa41467c9c0dac2b66ab74593ec275b9dcf7c06f35d0085b2e059afc2071cc00de34b1ee086f1d53188e8729b3006115bad2a31462b3706c125d6d7856507a713969382d6ba6ddbb67d88648049f1ca4bf67ac5dc30be5876854932ca8bf5928105856be2a64567b9fe339e39a145b9e2379e20595c23a3482b5255a0a638edc82bb8c6d5b1497f506cc8b4193edf3d0628066ca481c27e8e965198c65f52b92e81705d5eb9ee4956a758ed4a75aa53f3c5f9278339e7fc7c3e1f4ae9a4f3f3f97c4694d239e7ece1a4fe7c54bbd13c1ff77d2fc4396984b1c562b0d7bffe35e7dce6165f91f4953a6ccb84f29a5aaecf2ad79fce6df3bef9482a116167fdaad5da5048b6c00b03171b53602b08cbde6784f55fd5d5b6dabef0b7554c4d1516b799b05904977670295eff199bb348ea3e04044ba5ce28dcdc895ed8ed89839b152cf7d2003eb0fe7c398a2e5c6edb4c96db843175533eb5d84272d30da7943bc60d638b4b7de327aa41c77d463f380f88df6d9b1f3d2022bf51e55dc04d311505dbf160d914bf7faedc26fecbd5625d8f22d912c9c086b235a16cb096dbc46f7be2a02792810db727dbebfa6f5ad8fe30a656a9a852a55253958ae08d2c95c72a5315695cc776cb411f4f1c74c6e2e9e964aa3a6f015c5322029ddee7864bf1e30f31753363aa4a3855b2255b32bb4d617bf27d7860a76a968a3207a74a0a9646beb6205d30933298491acca4103369c42c0bd7bfb1c0a54d62c134fe52b8fef10bab85eb63cc18bcfcfb0726c9163321f55442515a45b43ad58598daa61853750256019b80d4af2879c0530906e36a3261c4759eaf364210aea726cc55515485a52fd9ba79d5dc6e5dffbe3245ac8a9815b1d754bda66aaaa6aad61d23071dec566db1cd0710a5b3810c0b103b302b56acc41839b2a7eb4fcf6284c42e0a892410308dff16d73fecb8e6b8c8c58e8989d93eb5562bf7484ecdea2aad9344d55250fe95db6a48786b8ee3aac771713aa83f346a95c2073a34a4c43123140242103233ac9db36eb14306084a2b5be48832bc1ff17bda281f3737a36ddb1a4581202f24923acfbbbf17b93d90cc2259d56a35e7768c7adcf8e01a121b4ab89b993bb2562a5b69531a69ec6ddbb656b54a35a9bb8d0eb791e2a43afb32bdcc39e7a451b66d71dba20cafb25d6bed6d1b218049120c491bdd91478475976b676727ba7622084a54241941a15ed2a6132e3d719b3058d2e4f6a11f782e993b4c9611ff18bf21dc9784fbeee3fe685c03055b7feb264dd6228df770e9c541ff3837340f5f010b1a87abbf936af73e2f22918424d2344e4ef390bdbc20c9f97c5efdf9ee4f0dc9e713424237d16914922f36d08a741922631ef5c8018670442dbad3f5e8d445b1d026940965512b5445a15c7f19455068c1429fd0551435a1729b761b896a169394db44978c0f36152342511445538d436920002736a42852acd5da900f360036a0a9bafa01846f7f32e5a05314d7f8e7bdbfd1c38b75d74db90dcb1bb24c004278661e4fcd43f3ec7864d7ff46b760352600950d23ab968a39d8b558cc16c164c5c513a594527631bbdd2dd7bbbb259df3935d8ff1c186a35b639ea73dd03be84b9283a9c240a02f097d2501fde73b8f6846f4cdf4c3b4e787783c0ff32561df9e7b1d4c3a7414f5c2151b6759708d47591436ac308a1a82dbc022699afc6bcc6d7a78b15dbb2145f96f35b7691a4579fd627ab0ed9a32a47bef4bd2d5ee874d53c21ed7e455589483ce84a6fa0b47280d8091c635f091c68d60c31fc51b273676fdab169817fb1702312708174ba5891bd22154081136e458ade2fa6701a3281445511445515477b2f1454a29fd7a924b112ee1b8fe466c9032923a402bc235fe9f13a4eccd49acc52d77cdbc702b553ce65dfcc51b07c1d2206ce855dc4626817a13ccfc2458c42e65e35227650ae34423e020ecd410d7047d4dd0b7e33c34f85e60e9875e60528ed320e0728ebf935a44a790d0a42291c697e8aecfbd39952291e44b348d37e1f3854e8450fd4287c29bb88d045fa2b71697a870a9fa1242446887a2bd096fb9b7ea87a2703b91edf5b75a9ed06f7b68ba66eb7113631e16e8c8c7a787a5dc9467e592dba8597fbe7ce504c4650f24b3fe39ce0c84d58a15e3220506e5b5a56585ae56d5bd7635eec9c27237fcee6bad5f3f97bc15144d226244af1a87da20d48496218108a126ec12fe7586374d7bd5ae48530433c7107694ebcf79b7f2e0fa3f5769d9f951fa021bb26c031cd000976ef00126f5e949f3f0f76853f801130c869a4707e1c67558f33084ebb18fad0ed6533fa127da4f6606b80083880cb65aad56abd5a8c7ca52e166a64c9bfb65737222a94a493b0f1555aef9175a96d69430c963c34858d029cf97e3a371b88f86e6e1fffcf2726898afd7e49253abf461060cfad3d0384780602b705919610818d7662c2b66f3dde3e68185a5b55a6ddbba3098f993827262f22122ab44b8f35c91b922658e48294084694d7a1b69dc00d37dc05988d0b972743dcb105ea24966334f3794b957ec8d1bb24cd6cfe7f3f9eae0e73381e8f92244081b322dbc61de22fd34f847e446caf20d43a0474185d828fecc32c4ebe9d787610a1b72145f50b9a22996fbeebbb9f9978076846bfc6d11d6238a5ef480f483b040c041e7bbcd7c60e10d13029e28fc206ed3d94c520cb20797e4fb1549001def36954b5bc771de8fcd0697526069304e7f29a856864b9e77cf15eb5d7bdd779fcfc7fbe1f916c944e172f6084bbb1f1a8d46a31df1bc1d0e8e1cece1e08d833e722010c4c16e193e88a1dc85a5091396955635143a1a7532274f9cf8cb10979c8e4236c0208dfafb236bbdf4a768042d9271ce398af3e5acdf10a7e98cb07ec31010d6e6c086f6ba7c8fef38fe7cc51084d840dfbdb0262463e380c1216c689c3e1de1022f5a57282143316d368aa25028140ab56d3b463d3a06338399ebe82736d4d0a76ec234fe12a3f333ccbbfba5773d3cb0a31b3f9e15e1d9ac8894348cb98a2d66d7218bb1d825bea2966d4606ef3ed7f87ff7c726f3b76ddbb61a5aafadc6c5a5261ac7f3fd963eccc370c586b1359f348ee7065c62e2243fbd22c953339aacc99d4892f1fe13758a246fd249999c359ba44bb6e42aca28cc87f2142a854aa15cb3fae578626722107ffe8caf03150eb5d08a40709a4e49711b260e63058b5a82e212f3c7631173f6d5da50282e81b9f10718d10cb771116f5785f58f556e186bddb6214245d87645527479a660ebfdcfe1a4c8c4458c1397300c5c8a33f8dcf0bdcb7db515bae185b0d8b88469fc7fa84e228a6bfc0a1bc6566c6d1f7a1f4ed38f058b6bf12d22601c04308f7e5983112e4d0528a0fbc25a3d5f6cc556addba8c7cd5cd950d2ac6256ad184abf9eb4165655a9ba9b9b1bbad55a6b7d52f7616dff4251b061e84a5291a8c4f5ef2118076c33df85e052ff4da4691ccf97a479f88d77439e7d5f6e92d4f79bfa85355f6e68641e709266c25f51dc2074872269014ce37fa35255954aa552a938ae0a19e260ff27369bcd763ce54fbc7054173f7913dae46a4339399184a4f6e068462c51a06c9f56713991e43f1a816008d6b39e71e9b3a104dbb7ebba6e3ebd61ab26f390e17ddfcfcb077d731e902888839e811d52a20474615b3509543525e9314cec31f48f4056a6e9ff421b2af7db047a04bd6d40e87b6c60d8ddb8efeeba0f924e61b1b5630efabb604abf0ecc2b895d389950e0f6e57432a14226985702533b9d4e261d1d2502a05513cc2b71c02c6482791d253cc6eb64820168827918dc98604c30cfbd6c900f0b9d05d3cc9eb0ef1a62d6a63b3d836189ddbe9909f408e6f7ac023341d0c2ed1cffe00e6f5a8f59382a41dfee5974d9f006c4041bf6cc51b3d96c369b51ea33cf65b67065d65167a08f500eeaddb6b1043b9271123b5713e7873bc21cd7a9cff67ecc2f6caddb3665a432da3ccd79d2d15698344b67d55552934fa78e0c82b147a3ffda5f47c4dddddd3d09e80bd8ca94c388c90f5ca629fd21c36230fcc0facf990830974b9fb95cea3a7dca9175ea0607a9128be408f5e00006232ceda0b0f23671d053ad1c2acb7c3a9d4ea73ef58952a7755268ec34b7711dfec5dfbdb8bbec642097fc9238ee03eb573b8f16d6efec9e5e3708d391449fa6c37d7d5a2455ee0b400dac90d98d1e5e2ccfaea36ed846c0208495c16934da9c3b68758c8e99274f99acc7b03559173f317a778a42987843d75b75606a24b5153bd7df690d6cd8ad4869b2586beb38a9487f09781e611fa0f72f82821b9c04fa680d0025712793ebfcc53b2b1e366458ea156b1efe138b834da4b0adfa9ed8119020ec1d9c808df81371555c9062080c3ad83c8a500029322292a06a8566383ed40aa5a4a452527630d26ad156abd5ad6e756bdb463d5a33a4b85caf1d2f586656645ec0b65497ab7dfb500f48e7e1b863c3762921c68a3cd142609c2456669f54deccfbf629926efa497ca2a2a208dd4a4214ed84b80385b5723da9f2ba5cd5e572b95c9c886fbb869cbc2e4e2754cb0a1396ceaa4aeac9e90482b20623c95a2e798de35dfffac562c1e5367d1b50b047e889f66f4fe9f6fdfddbf71ba91ff24e7df0648db06d65db28a5b6e3a55e05180a77fb3ed5ea606f2fbd1f5b1525c1bd0d6ea4af9123f2044b7d4ed70dc0a300bc8502c0c6a9410938416aa05d6c100c02c1a07f0d8d83a479f8105d584a382276da69d970c4c869e774629d4ea7d3e964b1405f1cc7c5da49b264151df9454a296badb5e6c076dfc1a049978508dafdcc5924e6863cabaf5a3b092781b5849429490e323b28d3e773e8b8b032791f6b50dc660726758d4b325c9a21272c7f58721d06256c18dbd5318fc13f0dfaa6312fd61cac5dc023c39b6133d948e30f33051b46d749a2b8a71c05b2dd30db33dff3f15c1679a28f8863acd185edbeb0b7442ef10a26512298c67fca941ef4e086edb272c376f911b1567bc2054ec80df4d6e73e569cc6fbebedaf5d4caccb89d30d252ad6ac58ff6e2bc27d72885061b91bf2dca813d7db65a579b812d207066740ad4820e42a0a39c42bd624ab5dd48adfd4502b4822a96b55d45c35b9721bae166bb1166b1e0fc735421c1c4284be6c00c1ccb4b1d056df4412dfb0870a0b739d524aa9bb870a6b61c842686665b15a739cac5c00b3b826a6c541eeebd4ed20ae7f6c988f9c9b90671fe010b007901841e33414be02140ac54385a5fc04834180328486122b024bc6daa4d06655615d094d11aa227473c38ab1582cd6b6c154890921726242c310a6e9f08cbd906ebd05724c08970470fd89c0000a283d19a6582a73847dfba9967e433ad50d79f6b2b6880d2152b013f0dcfe402e794c888cc562b198903977787c60e6a992826a321b89b551eff40cc1728c49705f9f37fa7f798aafe2adc899354a7b38cde69b1662778cb17687c57a37ac4dba35c6186387c5c65e71e9bbee8574b0729b9614a61ff6eae670e5d50e3329a3bf6f6d6795811d503db0d36bf0067430dbbd6ff5e7ffe5571539ab55575fb93c7592a97f90684929e58e9119dcc43dc3803e1eee330203f27ca4b0ec604ebfbe70f46166f678a011e89b3ba31d5be5975365fde837bf1d233b3827288b52564bd6a54aac61cda55d946bb53614fad128ca6ab5f61f0447a366f58b4b7c7d764faf1fc6a5fe1fa6b84634a10b76b3a351a86d121c3721063d58c294433c220c93e7bf54ca32d7d8c993274f8ff60cfdbef4abe7a39f1107dddfeebcad4bd8203b18b1b65a6b592c101073ceba4dea24c65837fff13f291d758a76c157b0204290e80ca100eff0175917589066120d1a39187683cfc042b854e3bae806e192288bdb42827cda415110075d6efd520bcc11fc4990ab7ad2c38b95715e95759356791031ec7c820dc1eba22dd81094f2fb85041171b121087acb240c0683c182ccb9c3888e8fd8100e7f99c383cc9e93ba8e3adbe9597be9d8a45cf274308d3febe8f1ea973c5dff26820d2e729a5ffdc9d51722ac97cb59a8e8b2e58637314358ce4205961bde64a982c9cd424595cb598ef0e2e2f6eb543f1e1ffad55bc4729dbda4384bac4a9f9acb7c65893de186bdf369653942ec86ed456e348c56a415aef7f0aa3199ae80df5aad0d85fe417074634508eb1e1020ec20783ae108c5d3cb4ece628425d8bc765eafd7ebf5dab6978582e338e68e7c24e9288119638c2e9c4c32af2383fb9890679f1f8c2304300ed83cdc13b14946c4261939ce711cc7fd370e17f27e8608f41f519bac884d9c2889cfc788941c9111c910292972fa3cd853dc225cffb000d73f2236f5dd3e326e9a216a1367454a723899bcef603ec7258ce805bb9e08072433433eefbdf7f97038d271a2d6e260f7f2e19182c5e16a975feed362a5b07cbd9f7201192118a3c56f00e8fd6788580121cef5070bc0a53700f3601b4a039530e8df34d063b8eeee340ee42ddce71f49344ce38f802b15641a0eee73d41d5686877624e420d708f1b8c5c2e3f1b0876745aac7e3f1783ea311087ebe1a699cd890675abc97306157b9eede00506fb9a410278512383f9847949b273a52ba05e5ca78fb31f67432b90c5ba4eb72201084020e1ab1e1c80d48224d7f441ee794d45ab972ed20b88542455253a9b5d6eac9c0846b9c874a7bd23a6e93059372228d8eeb6134185c0069d9c0862db3f12f12699630e908e7c3c3033bcaf3f5ab50a47893277de7dcaa7c00036193152690538324f4175360d05fcdc3fb0a4c03c5f5530db8d0b296a3aab85a2d6ba18e5454ff509d340a2593c96432d9a8879021a998d5687453c4061f3db8cb4b0b6d853a236c7f884bdea871280fd73fe4031b8eee0cb9949147233a1a8d46a3d1b66db5be55881c63c766668e0fc3e50771649a077fbfa00a1760a61c1820c41096609acf1f120117f1174435780cccd26336123bc03cfcdd3fac2132820d6faed74abf9b1b29c1ea25c618afb07d638c3146902cd65a6bad52f610620e7d90213970dcf80c001036e4fd33925e003f2f2b43d7fd8642a15088527a84945246dbfaadeb6eb08a31c65879d63ff817e19201a4c93ecc69f3d8a8a24b72e474bad1830ad606f0869f3f810e97fb7e90c83ad8fd9d534f06f91fbbddf2f35d07deeed37d1495a0c7f8bce78a9d119f8b9df48000e10bfaf682f0057d33de8f803ee8bdaf0718a0a75f1f06e663be1a693ceed99bc17ecc10fb319ff7cd44a0043dc609e483bee71aef0b6d002fa7c3fd70a28f8c7ad9ebb82f6c6c62fd7360439ef12039e06bf57ca1e8f6733f2bc27156c435d25899108ee71a219e837630a71466446415a6d66a63ce39a70e9348245598da9da304db452f3a78716fcf8b2ad61491b8754422e97dbc17aee99085ebefa8482272d2c18103a777a8748db805356534020000000033150000200c0a860322e17848260a728d0f14800c70884082583e1a48b32088611ca4903106210300060404400433ac14053a62ebe1d49c3cda53fe7eae98e3e643a94226bf60417b85a91e3e638bffe5089ea7e763537b32c3cd1b038c3ed906023efb9e63f336e064a8a5567cffe1dbc0c094a9331d30f3b69c1fa8766c68dcdec5dbf79c271ee0c74b7ffa91677432bea0e7760a7cc1827b9935fc8bf91b5072e98c86924426c89c405bcd9ec76dbe2abf8b798d697cbdc896c50bd10f35b60d3eeee2e3a9e6833d8c3fdc9099ab586de87e245e3dd53f5a77d96d350a46ee4a20ec638faca8d75b58722f4c9d5bf121a79728d4dbff51812d25abf793941e0c2a3e16ace0527081eb1399986398e835311a9d843c292e10d193638c719ef7cfd7f6db872fb15176b6af55a7c41259774b743e7789008dcd33fdb5aeee4c0821d8133f78bf84d086cce763fd2975d25249e98900256f18c87ec3ec65ba3796c66396f3d40cb3e4c9235fbd9c386e20e511a6494adf8484fec0ac303cc9fd5ce9de955f3b8c3d890d84fc9f49851918390692050b930aa0579e9ad95d4f67abac99a2156e8e93dccbeff8726d19dbe1f738d5e6ed7c81f89e00e560d99582d3511fd4eae2c29a878ccd80c50717ab9af4827868365075a7a67f4f981646cae036e7d550443e5b091d11a8dbb68935740a15ed5daab9b4b9c564bedf47b8c41e2b26eb9f4fab1ebcac8d42f858421a2789cb6abeafa8603cd6dc3ae1df90ee6c4ac2ee834dd2688f12d65da7c2fb57c0b5720d571f17e5b89b64333557a8403c519ee0153a81296d23cb115ec9cc8faeced45f44fabdbb023c37503c1bda0f244dd058a0871e98bbd4e82b4b63b49ad2739856ea36ca901473eb0b125b68361cadce9e8fb4c36654b25830f9d53498c9060d377291e29da82124d2f2f2b081a8f4863e004b0616448ec2caf20752c8ec48e23aab98bd5aa73d6e853ece07c8aa7c44562ff00118bb279768075bf267ac182846526963eace6824eb4f159bc13b95b78e24f09182615f1f6c8da9cedecf86c906d856644d1778143198c830b1d57beb955188d86a275864d4f5fa80a4c72e30164540087cc42ff9727c32262ea5fc500adfbdc04c1a29e92a9f226a78adad762c357c9c06f4635c001d00ce22e486d8041ce8b8063ddf6937531de8bfaac2e6631107123727a9dd72003ae25618b18b7d7895e442553a98a6bd0b626aaf43ebaf5713efdde821caccb1fc37d15039cdd5081133f990cdbd4572ba8199fae0fd85cb60836e247223e06cd98b1a2fa39f743322774f5ba60978abce004c9f467bb2bcd1b1d1f16bc0c0b1bfe4380594a60e8c292610bca3f87e4b333474b25b02f8c15541743895ff0d42318117dd915595f6cdc4236582b62383e04de142221d853474510a33e68c682aba033e40b58ebc76bbc6290c30773b02cf76fcd1178b3a2ee7f8e8f0fc7390ab6e209eaf8cda14e00a8d0df50fb9901c367b8d55d2671cb402de4f72c2e7e2287ffdb07044884a22902a1b17269804e76d75db1de3187ae9943411682352fa2fa673fb0b818702808f2048681c5785b1afda62f312e6b7b34085bd8e3327cd5a3516ea969af67a2efb5fa660a86b841d56073fecdc848b0ab4820cf1ba6a0518546fddc3aa69f5022edec05965ae0c3dc47e8a6e85fc73ac1b4e09da48ed3d53a56bc3688a032d9ee5c8a52c55e44998a3c187ff1d5957efcbe0dd644cfb9adfe76930432b7015d19ac16f64b416e9a0b4d35431c27fa157c4d6546b94469717208a9ef5e444089ea933e9cc3c016aa0d2d87cbb90bd340d5d291e694d283f019bfc4f428959d736f5b64ec3e26bd24ca3c5f47394d134b17a824e181534f7c4c7016c5975e862d77f7c17203f98155dc4bc3dde2706189ca8a0f30cdb6ff0b7ac52c187aacf776aca195613a013c29d387817a57895e7df921f24384c56e9bc980f61534848c2bf683912224128644ca8599eb24a5afe099b80a72846470d30e9667628615161a4e182ca3ecc012a5f463a792f424412cb371d5289c610963613d2c89bf147ab590b596ad168d09f959ef77dcd554a927e8557c038723f857f9a6fdf08cc80a557b738a86981da9b15998db868ef528d12ae19edf7e79192f43b0030a00dd35ae6422ac970c657fc6f05d16c62d285b56ae153ba5ebf61882126f6416070e6d6caafc044f1410cafcbec35560083280eb41ae14101b312a1f906d0803adeca8d56e3b55c8b7d59082f6f0967ca9c3013ee94d68236909f3592ba01c801f3066d9e8f53db3ee9917d641c7afabdcef143a35918ff1e0b3ee042966632845372cb10a3d3d7165f0781b38fc300e1a6855ff14a54d9e0b85da30c8e917554f58acb08ce62b3b4d0f0cc431fe514351b0dcfa6ee4a38048fa8e95ccc4ac6a6bf7187a708329cbbfb45a20400f766dc5fbe3f8bd6c01c11363d1d5403eb63a2e564ca500c7c5ab9e57b63b842a4ca541f88c27623994bd1223e82a53e4ffd467bf353255ec48c98d1304d65a0bc0bdafa55e8ddf9fa060cf497c2745543ad5855612761105814e8c8dfe28965282f1121502ac17f9e23fd242b5509870abb0cfc3cea02d28700aae84c53c47ee4bf9f48611b8c30af99f36881d16a91b2626e9fa5b2fe34126128a51b518ff14868931744b9cefdf7d89295be661474a494f2305e25a17568dc11611278160b9ea49ac7aa0c4f9d1ae59aebd032ee838a6fe11299727b97cb6f0f3891bbd69cb48535d2f68b8e28d7a7e27b8cf0b8d8e20ffa142478c06d8a0bc22ac0d7ba8b3afc491980c809b0eceec3fd899fc402108979470025ebe8a8a222f98194fb3e9316e16ef9effd147df3da0eba96289ae5ca9e362b9fedc74695958a965df4dad6b72fff8d05d4a4be04e5dcb923077c745526bae38d9d6774e4e7e63afd4922fb947b0572807c84f889a32f90fb771a682a9bdf5155a31e0986d469b0e69181d19624d0ae384985681c49ffcb2e141ea8440f30b0d206d9815d7ce97d04067f1001a7f9aa4cea198e2bc91f6310bbd35cb4658d06a1e560182dce36c20e3ce539d4aae733fbc4a2edb6a1cbf13e18d3c52332047fd2f87bb177025aaecd21a3d78b3f859ad953338f7d710c9944a348921805137c51cee1ebf3b7abd2bd2eff48bc6c2f9f3bdd0b32de2ab613313936e325cbe3749ffa7a6377a01532bf8303a282701f102eb21647f7cd7fe6703cf7f5a0f1fbb0a0ecce432b02d948eb1bb99f850eca8db8a04a2a42df4ae6d8640c26f51a6926fbbd67a5c6f408c0d2229665949e909f22b189f71e1b69207e6e162b7c085dc9b1f84c419878845a611d6c7e1500a61d132064056bb5329615241cd71aa62ee67df9700cf1ef5725fad2508f59f41c94cb7ab6362247b7d9b9914c94602e602c81e1e69b2746dfbcfa05eecd43af1277f83745bf2a2bcc8105fbea6e82656bc44a25427fafd91311c34049fbd38599b17536390b654c662572d6180f070a5e1a5eefbaee7b186f2a51d0f94eb86423f7049c09867a6ba0e8e831c963a0895b087d0a72382505fd857e22f100695c6b2db6d500b563e0d93810de15bc602be8ef3e3e953f6f5071a3cca42145469abf001a1d644e6470733ab6f9153268eb85fcedc950150ede8e01fb480db574c85a9a2f49e5df326c5bedc8ad6e28ef29d8fcf5f0188892809f680888a9dc93c51f3f66d98713b45e19ce33b05c4f11d7bb1fb015bdaf0629ffc8633b1a0fa3aa3800fa003a7f72e4a60588ec8743207b6f21cf9f96b0fe20cb33d3e5361989f0cfa05ee8ac497760cb21e4e253876ea6c690fc812946908b79e8a354dd2665f3415cfa23420fee8b62d1743447aab966c99df93050992cda4ba6125f713a111191a8220b177f575b71426f3c25aea18ea52b6e24be13f90de47ccec53fc53d328697ffa8601d6f804022c3cb68e97f047cc83abf819d3218d36ec4d9951ade708a04561c7eba07edc995ed404c7c1b7f174246a2509c5aaf325d3ab9e467377de354d7d1802a79804da159f2fbe2af88adc376df0046f15a7c6bd109475608da1dabd3043aaa2d9fd190f62e131fabe00eb1487c39431aff841da124546423ce668fe09ee5500fd5abaacf527d06e5dcc128e77c0f530ccaed2e9c9d4b0461c8cf5af3bbefff458dc6aa2eefefc4f69e522bff8463d0343815021966c9f5aee60e025d943f37baeca3e0421500081742a80c14d9ee95d07f88d88e62e88640803219da46a860f1d8aa905b12356aa842f2d8e4bd536e625d3488720018d8663186c8cad165e9f3e44f6b6ca5da5329850b9332056d0ecc5c1cc7eb691c14d2aa9344cae648988be775ed2a5a3767493f9ceebfb7e54268d5c133addbd8c83e93504f758d452df4d7387168061150bf8d1c2e562078c4ceb31eb453672d4e18c45a5c7d50b3d76f27351715f300aa0507b2293d88489abbce4edea2a30b1295927f983f4e9fce7b547a49b1f2dfa38a0bb207e1a3a12d89eac800d15deb523fcc44cedc16afbc04e160e26662b2da8f05d238d6b81fb8abbc5c40e5c7ab175242d54e20fa0f839d54b32ec3ba87ef63e407746998e6536fa169dfd074af8a30a42996c6bc6f155698dfcc3ae14b76c556f0c2e790ac3ed65101d3f746a62e45d12556afc0841c038a7f9226ac88bff4c9e13eaa2ced3719a7a6cdf72771d3d1420cf79af1945e6299988d9630305f06c1643bc4a42d090e97a460602988dd09d55a0f3338ddb4f6898290dc1bb69701dd35b1457e1a0170b56380b06e82a7e86aa2ad2dae523308089e49c7b499da12a5826b44556984b4ce04b44412848a638881b2275c58d00ccce7200eeb5771b9895f0ed4d841fda00b28ca8cfc8f9a3249ecdfc74398fcabc720b8d458b3557ed9fc641f213abf5a7856f49394d2cb8b1a3196f55fba01a9691fe8d5c96701cb0d89f20245c76fc4ca49b6467f2a233142aad5ea75b0b4599ee4a86bf48f546db9f59396f73a836c176ea6544b4a640d6c5cb5135c29dc7ca942ef01f1e9c93e1520c961a1333f1625e04057ffdd71386530417f1a5d7a7b6c91ca2c870f23eb8cce3d44f66492c44a8f353f836afba51fc3d64c372c2e8b473a721254bcf0bfceff3867966954f999a814c4a5d0b0b154b6a3aa5f83770e14de14c8e9c390d8410ff361d5f0838dfe306959254f62e48adc6112ae26527bb0062c12ba5efc99a6fa8fd341bca9730ca51a730e8086ddc4bfda344f91bda9685e9867f16964af46d2da38c4fa40aa814aa99d13749bf4ee6d155d113be71763d80333ccbe9ad85090b228552db7b5bc3851cca82e80705b740df45251a2b3321d7c5ddb0fc8e064c802ad6907067bfb6d3ea8bef3726c87bdbef9f5cca7f30d644a1f5919df5ca9f58d02dc53007b2c001b05008efd46bff4948e11197283f84526e333ea5949f592a79c9a487ee2deaf5aa02d976a25db0710717faf8b196f6df9939ea8f409defac02ec7e97fad3500286567a1fb4178d785b85619f23ef685ce551434af46516459e00fb1c5a1b88c1ed57300fec042a57f70f91aed1093ecc1f4af0d1e34f494adc503a0a40739769cc95808f2f1e88520ae5b3f5940995ba243a12576a917d1beee263657207e7ad94cd2b06b3068c011c60fb1ff9dfd2c07386d0aa3c7f461d382f9ebb705aa3db04f03b651ff004c518e63f7c65baeb517b0845d83523f57feb52cd31a0dc803ebcc521b34e3c58081033b77650efa62e9187d2291b314604b21df488e64f4990c8990a6f36a9367bc838d6719d2ac2d1a2d7d667b56511b37271a94e6ca3caac8e3a1d013a16037807ebcdfe18bfb9e0f6521cb87e4fe087283a3875e9e60b550a0ddaf9eb2b672bc3d3a708b915eb573e4eb36cd2b7774b21f8a3504794586673342f0e8b13d13e46855f2aae9493fb020bdb826bdef0cb42a5aba683fbe60c48146a1a77da9187a73b833550c8bb91060d970ac3d2ec5757b6353eac66c81f4a8e488fe6eb69e88086ca3633b5b3655a07c8dd2ce7cbbb26589b4b8f21d43d723f80f0c7cd8535217eae639934e30caa982bfab4a6c3b38fdf4e163110414168ae9a527a6605160885dc7199f8cc3d9afdf7a369f26742ea7df8486badc6bd765a2fccd717800d46c36962271b06b00dbcfe576984c58b5e7deebc5b0d24608a87799298632ef373118d225bed47cd647e298643587c0a430b01ca837c52e0788df4030c6b52b278bf8b5847f8498a9496aac1b54815cfb50c33e5f0581ddda77c7d95c38ef4d9ff0268aecdd02d7b164bdd7267b66e91f0cb5533ba3cd65840ae0303838d390668185f0f8b6235ce490134eaa7e76ee062c82999e666f30ad251cde26fa4aa5746ea2acbbc55ea1dd785d357099c6b0ee1686d9d4942aa96081d29bab3545acbbd2430500971ed86b1f0b2482530ed2ebf14f7bd4ad3ff94eca7831fb70062ce6731ed80454ba3bdf7475c4b919f13c491ccdf5669416d170e0c5793df76518e18c3772e91b5ef1c6095fd9d4bfe0d973b3666042df85b6e02f1973eceead3aa3502498508f7c082d764a1926122900a2c20f4366592563d1b55f972488987214d63853a6036339fc387248f9da2ef7214e7991c23a16b04431468d795e3c7b2da99257f678d231d8b89cc96a26a68c4b8d82a451fd8ad2c79546402bfa365c58f8bc31137fafdd03be7cef9c594a4b7f1ab17fbe233a665c1f071e5a3073c1cfea76b9b1252409bfb16ace86d006880d08f0a035f26e689f27a77e7430d41b77785b56d31c307d2c0b4b1a33d8ad0dcb042a2330947513140aae52d98ca629b717ece28ebc04fcd85c549ec66f63ed8dd952f1aeffc971f5469330250336e774f057afb15b224c07716bc2b2b33d1c490a67ac82f84147c54181cccd0fa76f5d3cac128c8dd781158ffb581ad87c8e0abda6771283d6995cc2e27a78bc28f9dcb6ab478a95f01047a55321a3ba8b0eda1db2d9e8eeef611a0e0501a83cd3b17567dd1888a1f0aa7b72c48fcdde05c13e5c909eed65b6fdaf0ffe305445d988476f4d518566aa03dde53ea7c45cd1d63cc700457b20e7999b1b04eec640c4e03c0250477b4c24b34513fbd7a58eae9ca9daa3edc905d52069886e01351a9d344404b52337532253dc27b3685bd71cc26f8626e9b1b1f8f0d51fcc83cabc8592099d3241cb7af063e03d71f688938becf2efc9160494a2ac78d5074e928382d6412719339a6e7b07234699d5147c1b8a985495bed6f49926844b2300a0c9f7aab8445de6796253f54205e446f2cd0113a8c3ebb7d3e57361f03cd71a21c2319efa7da6c0f3c417ae916fbacef331ca8196f8d104e594b55be7f9a9a11cea92feb65579f2faeb5d2d2895044ca2979969e3b65d790a0d0765aeeaf254e1370465677b6a2366602d1328664f23693de008ff39441030f1150733fadfeb6c38cff28519b02a145a4c6364d05a756b1e78670fbddedc947e13b6ae175fefd15a10564252275495f6b34c996bcfbb743644cbf89a69b252966f9f4047f5f21011a90f85bab824dd2974a5125f8bc6ad6b4a80090105e6f8342649fcd4da0d86ecd4984e72783f6d1e657e3da8b9cb43194c59fc429709e4e5023e8bb5c50e864d27a11a6d3742b5acadc4f23ae21456b9020ee694b7de5663a7a86670f34d5217f72564eae30bc40f510c2c60b081230adc5c2aacafd1e66f9568b5703f6446c504756ee69733fad92e9aa689823d0a23b97d46b99612182c95bafc9819ff1067cb41922db2dbd3ad5bfe33b48767a688aa33f393abf4933263989a7a564345d7c83d0548487b0e09649f112201c3ed470109dec0296820e1505b76575d44d4befff5dfd01c1fc49b010593470761108bde3a441c7c4fdaafb190b564170de9ab9df051e7bb0adcd9908e391dbad9f0ebf0477675ecdf311799e69b02ee8318bbd2a025c3c0edaac4a3592b79f62a4837314c11201a84f31e9e6d958e57866040a5f2bb06982cce859b06162e7a040a5b33f2be5623200756880a328c9a19ae924fb7c896f5496a60340abc3125eec47590fb92dd03ca44299e8716bea1d047d06acc421483b6e41fc46a4a745e158aca19797068758b833d51a5179d53d42138f6381735cbbb0c2b54426754a62afaa6fa98d0cb4625e0b03ca0a3d371cd2e6dd1ad6da6d2bdb297f5f13f597d5b7077ad97108d0e98ef8ad3321cb4370e8bf374dc67e3e00030171ee1601d2f95086414f8c70209dc1716a50219e35992df51d221d1fdc9a18a45e8b29ac81e5ca93d30d428216c45440b8cd926add5519b0054bb86a61b307c22323015401c2eb728da840ef047d4c2abf2996a60944aae607e0e052139c6d12df55f4e28505a16c0e7ac134af49afc298e4f7493270bfa502282bf2bd621c1aa9383e0a92d5858523b198cc46a14067c1e00d8de0b32ed1c531761786b9e130d373eb9fccc6eeb2eda5d8f75785b906bc1c05906e0097234487c6f9c1bce308eafa1615256edc6b6b87a2e70d81c86576634bba14ef1b2daf37a283a5f4cb18ae78e59b20362d53afe4710bcf560be1b0bd378dc7d95accdcd00d0c880634eeca564b4796895b914c7c506238ae3c395a68137a2195e000451995588909204a746379ac80f54b7755f9c430f7dac534b39ad28c28b99e8015638307e72450b8cb6d56c68b791582e86757a7cb90d014c783178b14b8bfe53fb1abf2a9111591961bd0131261c82275d1628a06024cd36e44e0e4fd5204894b6e17086a8c8ba36d0aeab3737556bc35642082d351adfa2270bc1faf0ba09d661ab6cf443caca1997093d787a97413cae68482221254ca6ad099580274db1646fc195b4b105cfed9fba2d3ccabff992b2e05a6aebb5d6ebc7ae40ddde56c86763007485ae72adbc1375be6925b889e3e02259bf24b1aa12b76f9ad65306490fd405938953c69b71f50d702e82e4c451a537200e3cc349f342daf3a4c3c7ccb0f15e616383124ba5de8ac5d60764c040ff56c415220aea6222300c018f7ca2ea3dc57d508c677b779fdfb326d77d64b7cc573d08f2d8d48e79cecd62f080f04b96580534a807432fe4e4f0433472e7030652a9a146b463f83bc36b46c41ffb48b1325c11a3538a845639d98c970e1445658a013a8d9a72ba36aedb034b621b89d757e460e32d771eb95782d6fc4a726c9accb93774b04d23430bc9e39b9c2181878fe1ceb632adbbf2df4fb39fc55a73fe78cf0a50a40791b2f94d84e756b7c29e6f7f8f0f4c44a87775423b21980066231bd3cb306d03a86ebfc70a38aeecec325b7f9e8c4ac1494ee261f2a283ad8a2970450d2cd5254dbefa371d98ba1b27bfa0284bfb1e4d069735b724225ad6d93912466c1a74f07cd9843d4c411fc05540c4777283635a8ba7322b10910fba436c84809e0c1cb773f53860351cace24da7f26f1aa05ae4c0a1a1cfca141c8d28c05a16ac2cb83404860d2702cccb54f37aa462a600e70546cd3e426f7ced4f257c8a47f406b1a0696ba3af516dd6e02e3d55df110ee1ad2539d3d21f7fdda85ead65d32d69791e0ab6b3be2b2a3f158a38b2d8a25ca8ceae350e29a573a3e1c28310a92bfd4cccaa5ca4602788c2936196e7b694c31001029e7e9131abc4e57be60c8d60d90dcb73f084ff7735d9b6b40e357f528f934cc700c8d873c4018b91b15293e280528bc1abbc95f361e5aa23a6df9f4c3ff3a5c6be857892e384b0889b5c844f5f0c63a698c04bff8571655ba39c0d6d98bb355d39b4eb75e43715665d93f7d840aac0539d367298834087915c53105f9282082bf20629b93072355d70ab1772d0069349560a1901db72e359a81848280cbeaaa41a6255212fc3c9d2c035f185fb6c3f0a95daac8bbc6b7baeb449199ca6e671f1116ff046fc4d2fe77d654cba12b157f2f217f4c9d24de8e021b95fc4bfea89bda26b27e2dff3956947cd72fe88c34e647c24f80a64f157eb3537a44f4560089e81ce39f4814c7c921a1fa0c5e85666555bdb78b272bde309a023bb70ec66ca3005144d6de55edea7ec7394d1b2eed9087eff331eb5bc5f92efde73c5b1449dfb5f8d822094a12624a6de71d2efba4699d16baf0571eeab785a885669f7a64c1b5cd34d43a2e27d12e5a726b993525c633796837224003c7b3602469c8e509304f21905f743b88340ed228bb1ec4ad54bbeb07e8216d08e31b295c69dc52ef205e8ebf038bed90be5f70c60bcda7782207f3db2650d2154d7d774ee622bc31ae36232a1563d1067b4eb0e46aac2fb174a39c0b066de4ad7ff88b6625f7d7db6823b3c919769223f5971942dd7d3c9ccfa912f38f6096ada99838387799883324dbd265f55e9f8b5d34c3cdc699464517ac47a02a9939a82aa997f3a37696f4fbd9555e4189f787f9f71d3d290864aac4a1cb046a33875f5b32e9096f6969c4948b49ef670d0c8add2dd394568bd98985bc772598d18c3eed9327befa440387134671d89c27a04348f8210988b37304870ae817268ea4b03bdab17d757423f12a239eab66631fbc9b8ab917fb0d2dc0b89ea17114f80e01006014f1608233a4fb1f11c9d1b76e4ec072518d98848fb2373b034060fe6b635dd7af23d1188d3873b11a5eefbc847ab0c8308b41eee2d3bcc5fb3cfffef4a800eeb7a48b65d6991fb630aa53788d49e3abe8faa94d78b2c4f9bbb2769d9501e8c5303be66cb20046b5d82acbe1496501670b2e05439b70e57ac25fb3f018d4d37cb45bf7c42f62aa5e1e221c6b9b28407e6e79f9cf6d1b358d8461b9ffa992b0d40fbb18c5de6aab2aa76e3808fac00e780342c661ad75270afe59adf06d299d15461ef5e081cd4e32aff98ba8f7e200124bb361f1b0116e66e6595819e6eafba9af907db765da91d61dda9a00156948626da81e037589f371aab04fdbe959b4f1fe0d267b311dc59689b8ced5e175a1906f194b21712770f3b31c4ef87a826ae9515df000e88dac119c99004266fc18303621595fb33b04b0b8f6306103924ff5d26540b087aa20df6d04adbb9c265cfb6d23e1de3c43204f48533b6d6a5e7df1827e8f8152c75f7bad60c0c4d3deb6b3de5392798ba377cf6824017190849b04e2e14d774ad42480797d80817db91e36a5f26841809fc418d6695c867d0464db2d4390f4260b4f1bbdde97802cae777bb192f8b093b3ebba2a865458d262b1a1ee4d826bed829bfb52369e4a13307990bffc22269b41963c938b646935bf28b90ab1cb32fb8f5ea03229c8ec4610733b4b09eb5997aecad42b68e119bff37536d9c6fc0e240892d488c8f4e26757f640d94d07fd7a4b49c7042f7aef009a8495af1b9aabaa32b69bfb611c4bb6f53958ee3812cf95c091b0f55220cd3c6d8539e3682de051b36c348243bb6f5636ccb51d3bb4ffdf525e36ca342d7c45c5e112ac40b02af57da520e2ff87c06fd575aa8bd9a2128b9902fe131d9c39a9c7fbf496b50aed58cea85b516ecd60996ff961d073b42bb01ea4b14b6b8608ed3901a7f29302bd825fb74f5054bd1660ecc654e76eaffa4cd88fa28caf6256ec32ceb2c60bc50c1544c0fad34d0a51effc2ed614630e777d126164013d729701fffa0cebd3f6ae55644dc6a0798e2a068fae33840a03f6468b5b22b60db75c6faf8c97e0f069fcbf8fdb60c9cc5879357f0188b68376da4d6a0cdeca36e729a63e0ce5f59de37e3017deb575791c57e9620fb2de96a7d8f21be0a7301939ec61819b8ae52abc17fac40ad022fb06304f71a40d20d7784408a4cfb10636de17817ff6fd862d7ac0bb7c2b640b5751078c4885f4fc28b803ce31b20267443e1251f6f248e0607299a9d912e73a18dda523911c36f840c7fd7064a245478505d30994d090c2291627f44552ce0bbc8a37008de2a018a38a4c55ec085301b5dd89d51ef0981523d72f9697d8158d3d030a48ca9aae08ac397545e443675925f75cf51cda139d3cf07e73397fb8dc520884214c292b43d343258314c471f4068682a463877f12c8581681ae56116198752e6735176004d5934c99e2f84f9072ee8d9d153a340fe80906c95d55a8c9cecc6d502255823e95d971ded6d2d6a1cea2f0496c37e609195b0d0e500bc15228be89f59c4d5599a4c5bc19e816d4f6067aff643c5edafa203832bb50cb18980704e560ad70f57546496b34f2d535c779a2a51596e8c7855c05c0e7cd9a8b916782abc2430131ffc773837f1e26bb650ebb846377daa39324f298956c08ff5cf0fbdf2a3307ce0213b375696e2be34ded5835a6a2d73825f79c92e74add5ab94393066d3821526a7c720e83f80a44abf231a3222f0dc7a4538d2f9141f7d5ca2be517827943108279dbb22385046c5ee71ddf7ea41e16b077016028f0a18f000f821cd83ed20afb1638be0296efb9865c816e6292d4416e165d8f4974c87aa9a1f9a49620494694ffb742a5ebe486c5777243355ccc4d942ee5aa512d80452427b384eb5f8dbfb2a53814d19ac1bb310b8e47e243e615d68d9bb18f33b6c59e6ea4cc0bc61a2a24672f23d9b1430d8953f7be0dba7805714cd2984c13f8a7f55932ecfb1ba008738515170f305854a408f2f25b2780a915575309959024221d8d7d3501a3538b604d2472a73282228eb7ab5f1f23ace15cd85aa28581173b847650731cad92b488f04a9cf1bddb329051e1c411aaca34467f5d1c6105c5cfc059d78f33ea865c8f3787b531c0603bb1c6374ef72adc8106690f86de4806481b7a199ffff55106eadf8e6a2349d3025e9c4d6bacad5b6c569f519f2e85771d601c0014c28c570c17659b9ea605010700789703e086fa669d91ca726bab52998ec221c9bc9bdb144304adff22d3738dd7328b4c19f76c16be3f76833fab29afaa5b0c0b15135d7c3a80421a150c48fa4f65792d9ecd9e14e7cbf07530f0122f58a1159e1bebabdc375861850d77449bbeb5ecdcad20f2b6b6619bb9e57520d187f8eee371a0f01c784d1491746686ce3415520922ff10b49cc4aeabb185a9f41c449129b99c51658acd5ee7418027c7feb40ba420ebbfa2e759895257576d9adae4119ca5949d7ca113a18a1e7aaa4a0f5574b0128fda51ac7f91453424e4550ec0b7f3643fc4f7ac1900803085384deb4e016f21bccc1007cebd66d52ecd217adfcc785d857f162877d41016f197b4e2f916689bc7d4b205625c4d348165d917dbb981816180bc59afeca4184c842973693b6568f36e477a801a8b4e6d6c81710167cffd5929afb761c2101acbf754f7a08583f33dde4978af1af3510046f0db6756684ebe3e1401d81c00e20f3185a863e216efba675453b202d28c62e3491d6eb63c7d37aa10035c85a7517795a330b154c7cb2f6083c136e9fbb25c36e6fb192d8fd5d55c7d99f40dcc6518a419cbe4e4bc143aa301705733a263402ace984fb039ceae69907c022f7219788edf9c91d30281ea6026149c39e2c208d6337e76289c62d7febb1dd5101338d03c489b6adc2a94708a556c6b3558e49ac548eb29cb428c0b584cdbb554f3473f478d86a071e67cb2857ac6331c1a99c89537623d4ade22a5b8bb3f58c066cdd5a78245b54d1b46c5508e9aa5ca480d6c0197c30ef4ec078d86c7c72b9b43f53201f6bcd62b5ab8d7b67b01078e005fb58ddf52c091c11fc63a04db4d4f5ba97598ba81cf668ca917e6bf1b021779ac9acc81b2ff42fb06b2cefd491adb7d616bac7d314212a8966e5c79f95bedf02304968fbb040c76d423533b96402fe7407535ae831ae5cc893342eaa58a7052658c43ccdf677c5b9a0a729ce66716d1e0e7138fd5713c02d3d3aaeaaf2430543c12610caceb5e667c3eefb43e9c4dde55220ecaf3df2d3e55fc660fdfd6d8f4a399394c1fad386e3a30945c406ddb95cce3faa541aa011b4fceddf4f4fa5eb8491ad0598cb7f35dab27f8f5d0179ee5c3ff96d970c92dbd67826be8b92a1012207fd80ccf0df4f7663abe11e21717fb20a130a4b7cc3f2a4ad0909836f3255c844d43e9eec2e91a5683962f89c0b8c5c7818e376b0dad205bdcce83f18f207c2e7fc4ea3f106ce02ae803ceefd8611c8b708b335f8c2fd637200cb678123037135e0f237e6f9f18b04e30947bc0f0ca60895407b3e65dbb7720ab08f8fa46332dfd0e26a4dece89b67229f4a44ce1228e404b179442133e9a6cf90af982dc87c8495ed2f55e69c3641533bb5c644f8dcb161f8373a87f8d90984853151252daf616c94e0576a0df0d7d8c8ac2c6df90eb70af87ba0ce497d35a1b0b9941beaa5f6f17d3866a3bf3eba92347c56e7195483a6b66dc4d504da7b010674f4197340b44bb4cdd871c20502fd1caea9b820fcd47c828afa01021027e538b49239b9a990ef86730c82a4a9372e956c04a92b2ad355c1b423d487c3cbc112a990e72f755b721057880a4690a8d1f92099757ba174d7d887f1d9db36756d051ededd04c9b5b5020f15eeda6e02d5bea38696fc277177cca7dafadfb0184ed207ad0f840e0d27bfa626e48ade790a78f6f0cd5a127e283a93ada77af832706bc1a66d87f73f7f70b26859093f05ca7baa3432cafd5a42981ace66c4bcec1404856bef94b15c0af397bada8e30fed61f41681050cf98c9bafc5f6b94d8e9adc8551a31608eb43ff9754dc6d389eb6c7eec194c6234735aaeb5ed3740c5712e0a2c4d550bbbd3820d8353c12a80089856d01d1f559bb8c3d85d141e571c7351aa84001f802004f06e06e0ee4f9418253194a80cae1f68f3ea80186a35893eac6c32be4dfe0731b4322014d8b666a58081ba67c209afea223cf0750746672e268094cb4b1464c4b47a6b3c00476b645250105717a8ed7b42809e35ba86b264e7fac5d704493bde4bcbea7f7852649929b2eb3f1633f1f9fbf8d7a208dbc9b3ab3dfb9b2449c00afcd03ba03660811f9b6bda0d92003dcef87b32e1856e5d1cabe9568beb8ca647d0d32f9e2b040f0c30ae8ba71c02914658d6b47e31d620e7e85d4c6e32acaa125dce72acaf3c3af1770bf1ec9a2c88cf20ab6be60c9a44b0a092432f54e4d31abd701d5515cad6f30f9a62f20a5b983f39fbc3aa7997b9e60fae514f7ca3a18312f396ea699b7362b25e2132c34c1657adbeed960afbb0aac991742f204aaccd7f80432ad058ac4062b68d5d16e8ff8953dc32adc059dc572baa1b7495a0cab58ea3eb14e0103ad282a56b91ed5d43d6e0f18f175c98e02eda980b864ce32368c60381a9912a4494a376d874de059734e878e1fe264c61451161c4d303c98339c530d2a5329245ee2f081964883cfe76601fecbd0e9cb22033e74cadb3dc5857a37c64b5e01c1d6f809f810563b38d000c7557613475297eafb8e2587192fb9cb048869ac64a48737d4971363c80a054806df784cc5ef30e083e9ac6eb8c1701b08495a832340270a4b604a9f2deedeadbf50a16f12aa03dfb2d8009028f3e9baf68732fc24a95b65feb3fd0c25e7b52b021a50c85c02830f7dd0729bfcb440fa7a471cd5ed864487bd068a0a766b5e15305934d2ab6caa449ebee6c51869a064dcdd418306763257289ca32e0a7be9a93e866f388cbd85fadb7d68f55e346b6f7fd5837b3a71ac6e245b39c1f1bf48b0937dcc0045d32a2c8f9ce0f4a29deccffb65d35af6994a24b743137efda26c028afc0482ca9034ef0005a8ba5ec7c450f486b84c65deab89d00acbcaffd3191026b01017b56bad2969c35f79fab7d7e6e555015ea9872557a551cf7636fa579cbaa67e1867991c93db45ec52ed559819a3701d6daf9c104dff2647451dcbdd51ce1f837a86dce8a7e6708abce62b85fd8d8130c8834c7446c571139b3df6a375b97a55556f7199c74787303703de9ceafc1880179a61a075f80761836b98ae5502c1dc9d860a244328d1a33229c3fd7b61fea92b8bac334cd36b971071f38aa87670dd178417e4ef29bab34e4a22685f57e1cb12c899605bef84494cb058b1458fd683b86517dfc6e142c603e48c6cdfe3813a65b8c8fec9fe86a0a80a20deeca116178e5ae71cbf4226aadb7098aa9a87265437d62583528de7ffb1d26c887e855de6befacfa6ad6ba55b26dd1dd27c238589144a2080d8a1dfb3979aa1c8e0e1691ad3911ba66b1ff705856e0d451f8720d2b05964fc6a11509ca76b3060107cadcb45fa3785c418348321caf7470dd5e52f756d89ef4642e241e06cc8626af2d0db5da0d76ba074c59a0f8496f45a1d55b61dc9abe52e250a34f6ec79cf353998e712c62e9e7782c24b68676813113269263bd5a36801f88c39dde6f7dfef6ea4c3972c4958fb0fe63b268dff39f7b3df2481ea4acf1bb5db76fba5c4981b7eab77db2c23de2b11e2ead7db601cd1b6f5e86b5e6c37418763555bc8458eff7e802b18335c72ce12fff68ee581e3e230af6a56bc0c9dc99921ebcbd1674bee1b902237f9ab70edb6b655e82cd7cba88b8e2849e2f15dd206721ad142984f65ef13f3f143e5d95c25e37c4672f3efd4393c0f26b5280dc330e74a704fd6421505f65a923a4b21c40668102f7b15dcd0f82938fad13c4747c591a3ee84725634186877219dbdebbbcb83b98118a4640fe93305b571e7e912112e63e1aab96867e438c612ff5b32b828b09dddc147db76cffe1dc8b6542d388948222c1956118a203456e37ba6178300fbf711de38c758531b004a227f1fdab53a826adf3e6329dbc6cc72f3ae57e566c502fce9aaaf7d8bd42dcbbcded253e6478429bf846db7c0185e590d391a1da5eadfcadfec21aabbbe9c8e97706c6eced6f8b1960f41e6086baa9c2f4385195b124df5825dab2e57afce2ce59628ef6df2f5c644de08071db5a1ffb3d061c3ba083c4c874bd9d040c87dce6d7039550afd7ba98da24f7fd96a07f44040639452d1e8407272a137c28233a650706cd554d37c5f44466dde0777c801ca7af1b3e29d38e2b32321cdf8f26d269a8c1dac3f5e95f0393d2fd0c1911bfcce84da0112d556df7b06748c14c66b1b2910840991a75184b669b0a83bbd65a4267d8cd65400e4adc7ec4c706a478ed4d5a7923e8bf68941a6f397b914aa2137daaf543c73f8415329a5277aa90f6a632d1bb41ba4a9da1bf741b9e640dea902c906f510af80843fb59f17dc8dee8f27c4b255c0338ae75918311eab7100518f536fc3f1927bb56e3b186a9526748c61f7cd768fa1ad1e4376c6dd8ae91391a8a7671ec8b446b47d8d92a27ae5aca8d20368870a731aa411a2ae493fd133ddfb2a2ddcee5a8b6ac3665a6fee0c64124f07339b24d4ae9ddcf0f46ec28c636ac7fa209fa4c8cfac5c4c0171df05e4d8c69b8516816b0df2b05862471f65a7612da344c479270a78cbbb54d6f496a2333024b818b0d21910658e6aad69ab2c816a8d9af76b2576b1447f613039a5757d131b2bfc37351642e3ae6a2b8b262d4160225f937190bc7bd8b774edd8eb2d12108597a1e5cb05213fdb0cf0e1458f16c74c71105ad80a14ed1680dad776e6213c2f7a65cfdca20cb7517802c7d30d25ced7df83a0dbce5212f427989db4edad7622bd731d9d74b62a318858e0586547395d772f5f7d0ab376bd6839b9c15be9c111181d0d5c3cd460a0d1273bbb818d07bb0ed2a9dba9cb59b0e839bc040bed2809aca1b069a83dfe6da1b1b800bce62747dd66a3d270991335d0a80f1fc555a6c52b458369db029071183296ed98990f888ae7240bf8ba8609111b01f64dd4c7f9df805b81a58abb8ef49be421820093b54efa715f42dafa6e71a205c7252caf80b72d2ae51c0c017d40218932150304b2bd548e4bb009aa5a968d182f9d4702f4141208695d230ae6f4c8f7cc28698d3b0672acf0df2c36e57500429b460cc89eec2846e14ccbb2c2d569121dbd8fac0f8f291c02e152e0f51889b02e88b8469ef16e37354f3f63fd038696aeb8ae5574334845ee23ab2ec48cd066aabfee7ba85ce307242abf794d2128608510bc60a4774da2c9074ca44623460ed6da1c958aa10a88deba50b1df4d783618d2a30186845edb4d44a24cf3aee719f0db34cba41ad30430db935c2f30d276f11e35b47b6315e82534d07f8b1696cfba72581e5fa3cba2e7befa8c8350d68315c7f758d87da0ca0bae484b910babc2f0153255da145dee6546d8431fe487998facc1e8a18b4b2017c0f8a993b08a4f853d645cfdd7f12294f6e65057717656971d2bdaf8846350e6a7b4db5565bb91a6f13eb8421f1bcdaba61f5da99fef77b9b8b6ab5dfdb0a1fcbd2624146e6c54efe99002a7d1ccef3a6a049aa79b3ac7a082c9a9f6702df0e8f1fd1c3e0907d13ca2d5c9943be4b5465b0a5922f69f1514bea4cee8368afdb09264d20a92b4b0d54a917b8554efaf87062f2e7c1441b8a6a44eea9fb61fa5d28b2dd1fd20e88281f1ff52d248786693efa499ebd7464b3738aced1f499222af7caebdc4e21c5893461621afffc7edb52f2f928747875a10593dc114836349b219122553d8acb0eccbb105349265538ff196a8cfe7293dfa81ba58ca7b7979ee3300d90c5393ac4a82c6276c57f930346deb7721b993b1bae09e47cd6272867e5b236920ac3efb9b9d6b41e29ec033a586071e5e53cb11e1a8293e1b49673624225597c49a63755c2f53f95e7c5d0857afb31ad163362c4f8535e2b2df837d64c0b6adaf017851baa167877f63733b6715993e3105d6b1a8f6202388fd5d6ce021ed6bd30f7c2145977e786dbbe968e032ee5929e65bb5f1f17ecbba8a587c29d69fe430780d30434db3f205e13022675a75dbb582c0ec8bbf00b37e157934cd16864af98080ce4789b24c40dde2d45692e51726f42e5f5c406deaf27b33c2e268dc7ae03f8d466f5a6043b814fe0a23cc85b7f6e69b6750e3b9352b34e78471f628ddee680be61a2cb8b371d324451181e54b00468eb1edd149feb6bd8b65621b6908b9d90dd1d76da5d212bb6afd6094a2398b2c54f41650082aa65511cbc89f8dd6b8ba81a187e288d75f120fc6581583d6502bb0b22bfa99cd1ee45bf31a3b9039587081cf28a91496152901d9fc965c954cc71dba5c4dafee67f7c0b2b62a1d7616999b27bcea58bcbbd55d7956ba41793854e4d2c59b51d6a9114142edcbe9a6e95cd0a1733b4c2ef80dfbe56c5e92d516edc68646f89ef271cd3667e0c3278c8f968e8020bbe7f51b65fe82cba0ba3104c48716d03d3975ff1046e6644b528c89f10624c1f99b4cc2936f268cb7defa05d49f15d5a82946c98a634d9004a315d50364ad980915a758b35ce3e91962e96199ede91d2cddd212f2f3b1efa465e0ed0626a5fea7652b4fd2aaec1183db649ee9504d5cc415a0f92aeac9a4afc1288341d8cf74a711984beb31a82fb605cbe77599fac0c08ab17929c6367ac54c9c547385518ce3a20ac03f700d97b38167f15e8011d92526a9d6391a57e297222d6a00a9be584d59ee91449bea91c52a80087942c66ef1ac5bbe7f38a3060b444552cc58a6b92fe4dc679542f84061ab27264ede6aafbfcbfc1d506da2b43970db032dafc7056419c003e7614b154482077ca1ddc54a0a446a8116a70f0c6c241ce2b43567bd8416fd29471544ae581740ce95a52983ec0963aa073755b73f0e5c12d796b05c6a7542d9e4e4d092fb8ee0dc89a8f5465a43e0a92c927bb32d16bd6055d2e2abe860eb4bbd652ef090dfbc52ec9fb3e86a069ea63fe3390ffba165177cb3768e7bc5e45d0b86b0aac27d6660426af993da77b80794d1532c1e20d8c52e89fdc370f8f1b087ab689df216285ad668f1a39985fdb8dae8e226e54ad723ac96f8fc4a731e0c4116c93fb97c2857ee985a8f60bfadbd82bf2b1d97387c0cfaba64beac91e72d525beff87935115581ab9afd1812f1e61b46d99ab2e9016caa2b6b11a6959d1212e80aadc5c1210bfca8c00d8ed767012d4cf8101a2b4654e10cac7342fee7fd869e8fc83ab7ae0caf55189511f266375b4aa66227e9f3b1f0fd37cf9ec2cd64d27e4c4eab78490d0cbd03ff96237872959b6b64e2893c80512470794b1855e63b1077d40bf835c93b11675d24ad6396a5327321c19543d11bec71cbb802aabfc55b9d09c7d83fca03ee3af414fda88a4eeaccbb3f4717e9aa7d07a0d19f38bfb3ef445c3f6a5d0c1d9d89a2caf7812fc36d1b1e10bc5a3a5435af6e2087c34e770c1d73fabc8f0fa908aa4e487a8d2f11887952008622ab8b8d9db8d981829c54e1a5de3eb3766d0f5955ab74aa2486067cc75f11b74d400329c23c07210bb1bbc39204f83c445de9875f9dcf94e0415271b5925eb32813035122555500f78f0c4a108d7e0a1b119c278920e1f3ba344c1fe250e56a577710377b4d08dbd7dc3491f4c3414bb9fc995fcbd34d7b408f36f3b10d8af513d3a85a62428f949586babd70c15b0630afe1db956d05ec8fd7ee4d4250df08ddf254e1c27ca192accafad9bf4c9301b62b7fab614d0667351334c92a6bf8d2db855b9d2031d371059e1569b4e3df408b4e5613642a6ed26e65c7aec3c2a86cb2533e29e80c08a3d03d0e653c927032393602a02797119d62c678870a7a13b6420bda1889804c73e930f65179fe8023f3fb1661e64d9e5479e247f7c123e9666c19e89b634c322d4d1b3fcc45c61c4b4574822967601096a83d870ac9ef98e82cec79c008de2e0b9c7afc95e86b226dfa81932ba175268f0d7e390951e40af0b057beeeb756f7983496ddf18228bef4b3a8549f943e88946ba49381da02aa1bd67d0d737194cf1dfbb93e36b5ed54887dbf908a353b65092afdda2d4021b575f5ea1d4f9cdd6e968a96ac3dd6a442343d90b1a28ba19d955bcfe338f40f59e53812f5be502e2a082e501c3a356200b47e7f0575bea4bdae77a9e385ea9c2b71817fb942971cd659b2c3b0f80b0342c7c3bba06adc8d75caa5d44209a8bfc55000053d43868041d71065ca61939017c00d16f9398704c052eba7e4869d72369334f29fc432587c5608ddb47bf5e6c08c3ce58bb1257b12973a89b3640110accf4f26a7fd0ae57ac5db02b7709a27f811117bb33669f1139e2043ac9d1286f288cae9696a75522e8f4177ff0b7456f6170892c7865a92ad49af6a85d11ad4bf6cee3d3e80c7388eb3666ebf4997812fe14e795f7515979f5f1ba3de1592c79288408862985bfe5c33a360dc963397a809ebdb21c20d9b681dec0590f8d98ed8d959ef9fc95de6adf19ba01d9049128f3f9f0e087f4c68d201c36b8aea07a3c7577aab068f79f64cd709551beb2165a277d2a2dbf89e653e512f0f4d905c6a5ec25242e220cf838f5382262481b6b298e32438221effd31cf0a49688e574e65e8c081fbf6a680fef754cd9135a16a4f932d23b9524dcb4831b9fc01e52c454a451759a82e776faea3a6f81cfb8373859bef442ed3dea358bafea3501b6b72a0b2886ab83a210b5378bd9563a7908d4d33622dc3e026d79e58f16a22bb7d46adc2a982cede2fcf5ce5ce7c5ab567c4b60d646a762345473296141bf0833e797eefe35713af32669b1cf9617f75c785300e2a092645bb62f23887c13089796a40954bae6426e1898b3c683bf623cc6ccc138445c5c52d1166cec802b0b6b51d52163e6d0c55989cda9f8d68270f9924a52e197cd38c0bba515d2a69a2002162dafbb5258a61581e9cda65163040cf55f7aff0117d6c2587ff5b3fa9660d53cd50b8880ee0ed3153ae91c591e6cc5d16243c1e06d45e827b377e988ad5d617af7f3f2686acf249ec0e5790615d0079dec7f9339106d3fc44463cac394108fc86c83fba3b85fa4c78aadf5b4d9ac8eb78d0c7291239e65a784091e77eaae8ba450a0a17daebee902daf1d1e460c992f8e5624583c8c651a130c14c188c585cdde36c00442dc29f50e5d3e0e88f6729336366920956986592ffc25e42d4fdd76fa9ef2b8475ec56471226869ee8be0e6bb0624db6c7808647cc9bd88740bfa19d9cd43c061ba4139afb3e22d814d546f9e82d338119bb9875a19294c034e881ab51a072e2888fe8f7a911a0f5dcd830bf4d6edec76b0ae66524519092b7ebda85eb38b432247f3874bfc0023f6c0876dd1476d29ea17c71804a813e80544fd0a3755dc2e37e8d951c8cf95d65192fc80bd21441814bfc249bc4d8802e361d36b86184da2697f3856c3dc4f0b86f1fcb8a33ba285d0a3acef6a23b7074aed1ed15a967d94339086ecdf896a7318f7fb32b864631b18d5761e2fdb6c79a1fb5c043d70de28d5f0e69dd533afac3b3d2d9cfbcdebb309b46388d649d0ed32d30d3dfc918eda088fa39f84c5694b88b6655ece8980b654ec3d47c0a0ec7b7797c0614011f7cf57f9c46cd8d2e90d9125404f92b9dbb36bd3d9f6a3abe9edd2a62e0e595a2fee7a2f67a344090120056b3023ae6a6991d7d20ffb0f38d673498f4cc5cf022c7565a716845f054fbf1573a6a733f54c324376112fc507c329feb837c31f671bd5ef970c28519b66b9fe09e83a5110bc14c031cdfbd4e39ff38a552342f369220e9cdd623f4143b3a4caa2b2a87c499f3fee9744d5d2cc85171825d988dc5bac0b26d8e7f43c25378d8827053bd88b5f06efa3854b530963439d108d92d87088a853e0a63b9743b495fa2d4ef797dd9f2cc3e6c1641d40f112b2d020c13da59676a122afbf6b6d50355ba901ed3384d743202178c557deca94dd20aa16223155b93e7a59eaa0416b6dc4ac8cacb3d39d1c28bb6b73638628266a42753149e3c30ab7f33b01daecd2bbf566f8aaf5a35892c3c9de83f20b90284115ad4f46b9913d9985f1e76757abd9e9c288cf1e45364a6d2623c0a04d031ed2017393f9f34923d5e79a5714ad7cb7404e3fa0a90ac881ee08f39967e6a5da7866d6b2075c293eebe7f4fcfc80c22599d12bc15f574955d7f49e75234d2dfc0536849a19d6f419eafd336789679cb96fb57ac9a6698539694b197593403daae72c162baad2a1988df8343b146402ae6e2a5f30e544cf5511fcf075ae834741acf68cd298ab3a07e7d8b53f040c677542af1221e261de84cd62d6c0956db0d92ef7830ccdc7928124ed20488644300a99f7f0cbf41513a84178b710afd5b47102ca794a3b267a01623308ae01d60f124d88c8af3695bbe8637250fe46f53ebf359ab6e501aaea643d3d597babc437b243ad1bed7c096d2b6e2f0308708c0dead206f7d6092974f0c23d10966b4dc501a03dfe0e842dd0d2418d35dfa0b7237ec43698110e8329a6b2777711535eb53dc2677383c901724df564a01939bdd9456b93aca7c0eb6117341533a9335a10e2d587c11d9916d7c4890c1537a439fbee851b0824b1f523bbff93b580126677f6ce02a9a5034fc9341b8fdc790f9ca2a24820ecffdd7f6036e4cb06bc0301a0f264d46e84621649ad65c7f24917f6dfead5d9385362a829d54c14829e16d559547f288d5a59081f4c9c6f511af5e94804f10d1fa4690ace573b82f10726297953db7c7d38523658c9d4546548acea36cc8d11238a88d8501462131b9d0010cbe488048b0f83d203107bad3d777c587608b6173c8c1d58dc88dcd2069ecd981f87cd9498196c40940ddb85601c467ddda3cd0ff014c7f779cdea0f039f28d37821ce4db6669195cf5f28741341cfdd84718b4d573dc633c42030ff1e7b8d11837ef70b52fa62d2b3c6a8df64e0cbddd91e5ca87173c32a531eb6a6215f9ac61d48bfda398750ff039ce903561f62ee75e1410b8c884f889bfd4ead3621b1dca902637cf3b4d54800eb58155c8869b7a5e1ac823c3ac40a191c894253979d0a7140ecde4f6c412ae707fdf858ca6c6de95db80205d1946460242d8887b9219c6eb1d67d94589dae015e2cf4f4709b5c0317b9513044a389110d22f70840aca7c8c6fbc81846e2a486b27304e1504033e9805bc41194f1b04a56cddaac691f9d612a6c95ab3d2b7dc4f25df31f5aa9349fae70ce2226f121556872a1c1c6333ce81a47de02dc5adf68e2973399991810d14dbdc2fbc6c022921383df5c03ad371fbd00b9bbb7e1ca6c63bb9205aef492813f654c2fe128c1e98c3c2843400218efcd34adce90f64614e752556d77eca1c9232ed2d7cadb928e330c1ed0f88ed9834ad62de3bd00eadc32e5b79aae44e1331b15ac08356eedf0e8e6132e820b28990b837246cb03f54e073feeda6a8ec4f9ecdbdae07921c6f0b06edb399f77895b33c843d434a86d538e0e8e37700c88110fd54dc2fc82c686866cd1c29ed427050751b2966acb056818da3e79b84c4e37b1779da3a101f812af231f792285c789f087941431225bfaa2512af3f4121791b987a83c5c46d2cea321ae16813056e4ffb6c9c6c346d79f5e9b9a7fa70368af5a7ce4f6b3feafa57d1e61684634e07221012e4a03787c7f8f3da6d3107bcf03eeaa47fa8d6b09b2e7549644bad5974b374dcc1da11221f81c22f9343cd58e43f229a3b188163606d4a0fe1826f6eb66d1b6d8a3ecb8e3c434d419af53681c842521ef7f29a3660489543b66d80a1370600e2ad5316f7d3278d376ed39d377a076e01b9e95d65da55c2cadd3a2b6a3ff7d40a3cb14545d08550b967e48da7a903fa632b90b5667ce5048ecf42957ab53b8b712f0dd68004351e2c5d89bbfc2261bbf76795d1388b034514ad4a335a4dfbc17b9f736232a470d5d6b9957d9f7abafc020523c74bdafa9b82f2d48554d56db16a04d0b26938d2d3d13e34cc3a1dcd9a0f138f7471973e050bbe26f6813ae1fed03ef91daea1cab3490a37d432f1af4dfa4304d1131ff4f30cacf53153142f348f7b51a2fc83e58279f0e8603393321d86b97ffa7cf21c8a1d198bb9a8ee3b51c65e4a4e0ab69b62f084410f38d9039920556751fe3391bf6560e2acb3151700311331b02fa54e44bfd4ba103afe621cf8fd1fa83d8c30184a15f6ad48fa9c29059867fbd5caf143a40282e1d819bf3c371bf31f62df1d80d9fbbc5cfb4a18db255c51b6d55a912bb0465a175c291ab13f838789ba549214caca259192ea38bffe43c84a87e4cd5d6498ae94ee3a0f99656d3a825b7172e9bb28150ccedecb77aca715c441b45772fe2ce4e7635ee4c8258274f3eaa94c482d097232a66b7611c8efbd80e5fa315218ab857ff5ae03ff33976c4ff92f1c4cebe43e993f7a5a9b8b2a132e1fda7dec957b3b9e98d36911829909a284ed6ef1240e02171b323e922212c78cf84ccd2453b092e928c9a04c22f7825300f5345c3dce8e5967856b084b851921a6257a68d933b101b9cd135fb12a159c190dd0a212960e72c8a0fe64bbe958025dd845d296a509a760702692575aad8f0bcdcd9eeb7c83c5557b134d999cf01ad47c7c1cd1ac7406af0a44d5fbdb9eda198aef9c41e77061506445e4813239e41dee6cadccaa221fc66f6ad87cb421821fbc9172d04793c0c2e044ec1be5d81e006e2016ce7339022b7d52b37b36e1e648a02f4400902e9d8ca229565d9d3ac04c1dd6a8d4368f302b789915aef24b76d1859947b9a551be34ef9b4386ecd5cba2712d3f4a9afb50bd2f2575a8d98e75b065c8e29328d89a1fc74f2aa6174ddb527eeed80e5fe83475d9a78fa525901e582a05e88f8ffb558438e32d68a341de6e95e6944d10ff97abf89cc5c4d4abb6a55898b09a9c46810b54548556ba7296aa31a80e5c4e2cec5dc58b0c8edfef3e41541b79bb844ca925901533c7c0ab303f9ca371879cb68ed6fb9b900476dfc175be1803f14848ebd88d6af69bfe28be8d0da3896e07d8b8d89f40f348b5ebf010698e92d44d4c2976962d9b576fe6f9bff1ba382a4a3da6a85c7f087df812800e3939c26b4c44de3a2bb4d0d5e5e6c5c09d76524318c2116037afd338985499b26049e7eab7853d6a773c3ebea01f73d010159351c9a303e11c1d5bb9970f18c4ff54d4b690a7107664d73770cf628b8606d13ebd6e4fe80549fd336e5c6808033a8e60d693f5dd3a339fb38a39654188a539d984cbee76662ed9d5d28a5f1a9a28120487f888d825f7c3541851a6220a2eb06c7380cb6733978ea5e41f0ce2abed93029a376c66fa7eeefa056d6f2fa00f025be678e5b4a25ecbbbef826495f01101205991592a7fb231f5e0726c00a4b618206d9a14843f4c30473b4e091d92f41f05ecc1269559fe17fa2bd7b881791513551312b058be92b229f7454c153776162f92579b10ca2b35ef37dc95e504b6d92a235462325f312a95953174a3c4bf8a9599ed4f44f49dafb0fbc49a3b2330b5db7588a792ec00d68677addcebf77680831cc74302c40f2b0268144b5f1970ab4839cf8d466ab3ab00547a45e1eb272519ce0a613866863b69f3e5d9f7b79eb9313dc04a09462c354c2845fad52e5406cc412b77a32c071caac2c8032840a4dac3fd01a6b4133c16e438ecf28812432b51ba02a939892bc84c087b54be99705e77a344021f2b70964c2dd445120323c85b4a2a16334c682fcb0869ac0503cced5b51fbc5abba6b528ab0bf139685f9b09f313c0e9db38cb3955d49eb5d72885d694d8ddd9e42531c6f11297e1f15ee92be370a967092a09eac04759681d0eae33e5043cdf7d2d4129f9c9fbb227270c4ad69db3b3e119002a3726783f273bc1abbcd469278ada4c7ff8acbc5a839aa52a33a8223b3a03bc80eda648a6584a220e504d435a9c014f2ec135faf78cbc7f488b532a868baa0f4536bd4323e7e27c1951e72c65d2b8563c311b676f7c2f7c6eeefa176085a6f08b62e03a23c131739e87bb9dac05c0448093a22dcb491b8389fe4c6c962ff8843ad0e76baa23a6ad6aaf29048e3174d3f71b5e1109852cce9b92a8c0b34a065d5b761f9baa13b335e95e8927483c95c7ae2baf97f59dd50d397c6714e0a5f1e3cfe4544057ede6ee94bd4e21b483ea50917d1aee469701008d0baade1b55e707eab23e929cce9f730ec14831c9be24dc7e071839714ea095568881caa5dadd42a370032a289fc0094731ba4946d479788ecfaaee55b4cd3466a5b4b2446b04a30d5a5503e8523d958afbd1ed1fc70f487a579d11d0816073daeaeddcf85cdb54b54f177e1bf3ddec7075ca8ef32f9735cf82658a145586bcda4a06ae26d61b84a59a0baec5ac897dc5663c81b3b54976e73c92d07f26fc98c78c733eabafb9559e843598ad76cbea68336c70786cdd7e7e0d680e7d9e73caaabc8b4f8d35dc60a19dff7373017d59b4106c569a2839c7b7b274308711c58e658dfc9ca9756cdff2c1716010df4eca49e2d1c2c7fa17ea110a979d7d046788ff86f57ee164fa2491787dd64a5e13a745eb60638c20017e013e04f3572fcd247b0d71abbea295dc36134b4c238b963a08b00e1a690a8374bb12be81ab11026a27aaeb533d117223a35d4ae0467a7dbbd0d5830d6917290a5870e6dc46f9d9398b5173b4dfecaef3bf7ec096ac4253961925d592ed75d9579b574a129bd1025492c08aa835ae96cbb461271215ad48cf5b208816e7cd0c847473feecf1d419cb6bba5d77fa4225a20288f5274846ae94f5987bdced38d756bd8c396fb245a9e2b4c1326cce16a120d8c30d3b086a2e8e6245a358ffdee715c32baff63fa01a32659414a0e24f2db0e2afaec89365f7c45e22b662f31240e7969723e008876479cada45ae650743cb4411d80eb74ade9202275304e624eb0fb12fa93f1586f671633907e4b713d4c05b5f2b1e70454916e45d71ac2cd380764527240489901ee3a00cea6088faa2c5c9a799ba93adc666e41ca3ab4839a53321a33195854748128a1596b4982f223b41f48d514b6a484eb9bfa92d5af769bed16671f625442b5b0f289d39e8449cbd8bf01226429c5f4407af4a07e75618135067374d6e9a2b37e064ed786e6c7dc20cd45bb73b95b794250689e8a557168277439e4bba35c842c1ede78a1f7944d74f4ff9ae151b6051612891b53a3d9f224672f8744d6d09f81d5308fb7ff59654588084f44158214eca054eedab94d6930b15f1452c727c5868f14e02c9d9945ac048439c393af89ab61a422ba67e4d8c07326c463a2c5712ed5caf34420296a3749ebc694fbff47f69753195e2489d98261da76dab7831f5e660aa1e21961104954140165593b5b0b6ec9525a337ac3d0722d0252cb0b1c9b958a0edff3471ef0d509ecb6ad68d5e602a17b00ea99fc3057236d18676cbb8bc5b6988a8d213d1d31634f94a952510fb1ee1b66ea3117cf65463fa42b44b96d303829acfb26b791b30780b87c1c7cbca3afcb97f18adf3176b79f003933d671d5cd48d57f049d8759b5475a5805373d53b003bd004790bb1a6403ac35a6fde7e9aa250ac2dcbb0f9872181ca2f6f1c11070a53a37cb88e2c4ceef08b0dcead10ed26f90dc3594b7d2b1089c4caad77fb6c505c9a141dabac72e3ca17234e5567c08f86138fd871a104dcc34b0bbae329470851487f16acba4b6da1f4f19e239a6d68dd7c7819706a2aa67e77a778db72d911a77801c443b404e6e7a1e91664bbfce3ea8562cc8ffce7700244308983df19e007043dd66d86cebf257cba8227f0993ae355c4c6a1e0dbd5552350495524e5bf594fa653073ca5a38b4ad6e20d5e1f4a4b44d54b95582d74da101c06625259ffc2020662b1738dde95a6c67301f8d887c32793101f74408a92a83c199492de55223dfca478067406586faf309c2ac2a15c450fb60265b27fb83df96ced792663d61f8eebfbf827e939bd0031499083c8e5274b13e6ace4991cf85dbd35621fe46219477bc996b6dd7163c85cd84c8f05ae9c7604e19a8b2cda641afe1a20db79a5b2d2c937c9f31686694670805cbd7f4251f99568fc1b9afbc6ca0fa401221a5e3bab4365240d75b84f64be0195a44da6d2e0ffb1cfa8416ddd994e15307f86225c777d8257ae54bde5e6c5ff8b89ef5e225f4b6c7f4c3bb50851cda62154d0081d34b91344ac33d72b0faeb2605e8bcb69a9135cafade1a1c540ac9e10193e95de6d58591a202918c7044bbf0bc59186736553289cb3397189df8a1b09dcab9378de24dd0434d46b2261e76f2173112026dde1483186ef7268294e8b91e649b36e83f3c92570b334e662233c272b9284a6c9a0da34bb670ab02f519863a3b50f874eb95648b740397870bcdce75b0d66e7d9ab763b04ef06cf5434c1b02c54c51e83a41d7cb7a9d1d3c516cff1fd371737211128e46a2a865fe1ed3aac76d689d9cf22711e879b8d76f1b889f61aa1df3b03dae0c35d49ce3027182ed32ff993444c8f63e4f4a8065c91ab8b0944e24e74cd1f1a244a05068b5a43b1497df6ebcc3a59956cd10b903fa5620b949dae2a184dd589154ac609f1aa9ef3fd0c3e687556e6c943a1975df244d8786240c59a2e490d50aa785bb0eb33545ad00aef11fc1db7b1a674874b5f89dda6e307a6eaac1cbe471c6dc279745dba6e8a982ad94f5e26ee00542ac96cded164bbc9b3539e4343606f84d19a41f76a3ad681a343eb32f7a67978a496b15f4914625ae662a173d536ee6f8a7ba9347d1131d7b96a3caa44533f5038ea67e9993a874bae31966bfbc794a1f35b9efc249a6076e639bee04c1ae6234f66cc53342b9c5983ec68e165797054adb636ae71c6ab6973b63997012747b504ee426a46230710528245f271b0b4b120e46e731cb9f978abf4be721391d0915c46a302ddf7b61c23fe34e0b7988840c8b06b22ae586443a2623be6a79cc08940bf004ef733a5b7a559fc8128986c71170c5e461334b812d87dc9ef3c46bc0bd667a144bc0dd7c26522db94ff204d942a10408f3107b8d3478f51018d0d7afad1116121361260f785beb5185b3a4a91fccd66e982ca89b438865f0d60913eeeb9df77b36e3e79738ea1517c888b6dd26d8fdecd1153dc527187c9a98d984552102752e695bc2c5ae18fe5ce73dc0f050e0d2db7c69fcc8c30b878821a3d9df2a4214869075ad75c1f6e5e0ba980e07f91e53ce10a5e8c18fba754a722f28a369765561456974c0dca108bb1f420f833acf526301b018564d33e1aede0335b000757192324e7df4f80e4c11e5b8d806e2040425b926c26f9be6b1e0ef5dfc70bd993339bd39033abca0237dbe6b2073d7327f314883b052f3831052c2626ed92561d50b1be74c5c423ea7df697540c6ed4e6f2737870ad91d6c80554a17ad37ab9727dbdb821662bfd23a2f8c3e8687e299c8a7125a75db559d9b4518a2b1003186fcd9838303260186ab2b0e44b35de5c8354a68ea69d174b7c33c10f3879709c5fee8c1dc22f6b0c07527011b0cbf6c4352b391f162e8c7424be55e855b27d3ff613db56e85525af830dd0233d1312a63e52be6f8946afc365f9e6903b23bee323535c3321f4124a404496318f6a1f94230d8452456d53ed43f322d04928d02ac565e5d5481699de8b7efa0ff5d9718a89e64d14f05716e3819b67ff4bc5548cd8d8ea9a94c313cd733c803f15c1bd8ae962af3a4d5c491f590f2b3b0ba26c925e48b1b9bcf8e7ca281091b0f5d9ad2f45b655a2e5ae9739edb51658c7a7cc788fda2c833646811f9fcb2792b89de42156a3c34c07a808879bf0abbd9b08343b8e08adee2a3795d5f24196e719dee7b220dca49eac557a741b2bf9512b46e665ae7061f0c9f4421f8ef5d9ffd215ccbd837fa15ac05195b6ee4699740a5d835912aa1d8ca58246b465e9462ba9de0f9fd555288f8e61863697560a366fb0d694f1a03c78ec79130a2bed12f4deb851f40d4eb27fd5c69b885484e8243138a5279c4547084ca1d9f1c6b56ca0d4e4e3c0b616485bd8fe6e5b8c237f8676ece24102ae885238456e04d8cf72437ec20acc47b707dd0d1c8338ddccce6a67f1e135eacb57e4515deac7cb2705b1352a16bbc9de10c2a059f1ac7a2156ae841cb41f6f0ed1af73cd166daba0c5d5fa8382a8fc877b746478b2564aae4ce2a998b097bc527b44b00ee4bbfa0a14c7b807adb4d31f070594c494fe1404537c644290abf3174fae852800e6502c6c04a0d00cdecb84e291c225fa40ddffd53f28bbf2bde3015acc0a03b4e6636ade4f4ed88909ec9f51b2c1028dc74fe40664a989f69b774ed56388b6cf2d7a3f5b657dc816b08766bc3c21724284400931a7a1d8fce0fb947cb0935985338ae90ba30e407b2bd5f5e85751f6be259a01b3fe64bfac0f2f96e92898496c51af5580638e45890a8b17957278a474adaf90835c9bbca65c39d6a5677425eebcbbf7d32c5eecc1c495440f1abaf550ab0347431c9b24ddf0f50ce24a2af3d964039cf782a7bb6ab822b85d6587576596251728953d92f6843d14c207528386942df47109f9d0a405a392e427f1f34b93d83490048db38bb81766e35cdccf2fb903cef71e4c9228559dd1aac0de7fd7658409169c468d382eeb512f87d7bd8e50015f74f207b1044e89644b87db3e0097a7ce2436cf90de5f043157bc942683d0f66c535623ff162b3ce598af3943f27d1114f9e67a466c5a08bea0d97c2cda582aee94e3f8a7f7c4802b7df4fa798b0da5b8378b03cb1927acd376467d1e78b860096683a713e7d3cd8416fda734ece30b6dd376644f1ce3f4dc0f77eba11b913ffcd52335d61891c368ce76e63454937007b63a96ac57e330687ddd2ae8a9fcaa6014849e70e9c0f981447e28d08eebac4c3b5838af4a321a535911f6c539b109705c06b051edcf5c4203a3c628891e741ec7285163099f07f471b47588adf898aba0b7189b80f525841d9f3e1093824b4372fbe738316a43342829b7b1e7374978610bba6d961b3ba4a74d5a504284693bc9d1ebb69854370d0b3a8771bc71dc02077982b8bb03338f600a3d2ebd98a57bf3d46ee7225c618f7ca9071c6a958740202610ea0282252df5d2c62a6651640c83fa8e37691165b359a786ec6858480678b62b8ab6f3431fe743cb023d9b1b8223acd86b169a164e2a62e3626b780cefe4a4088432df4a244be431ae422f1d586faa7bd221433e88866ce7aa7b42a71cb263301a120691d4b5ffcdfb6b8e3209762b3edff6286823aebc5451670792aaff5247b313378042c427aa37816efa2a06a0424f27254ac85ce13a04cc641403b8da44ae5ffabd82e7dcc195fc8ca23cb085482957119db777e14c787d28f82d83b243518e0dbe28ffa5eef745405e37b4973bb43c563124854b494ef58af000c2c182fd76f58a8743bd360478432d4b0ee97ce1c581aa660ff111e65f390cdee20420d8fa4d8d692076167e6b2ef772695fde4c3fd420b94ec04761bb129598ee707397da0518c054ce178a646ce0e9db289fc301f2b432acf1e1cc9ef5eb56448f598c72586fbf008c08e07fb047bb70de34e85f5dfa9706bdace478a0be5a82420becef94a4f1d94263de507bd21dac40648c4a681dbf628f8707d2d2c6dd401904ee01680ebe823304928cb0239193076fcb5efca1cdeb12fb1e109e8491e5207322adf4a044037f4a02818fe52abd813bb931c2509640afb1aa2cbbf218b08f98472092cf7fe0842444e10df19061e34523bfb51038ef29cf29ed18fac3f290d2f211e228a219e0ac384d3ee01cb628b6e210a684ec13b15ee16ff5c9e7c51e1d83657f30d74951381a2cf0f8e4bc458290ba4b340d7e659294937c05ab75c5ae3aa4a38f4a4a0fe0dd2497877a83702c83724a24de7152d459239ef472afe5ea9ef091b66012237412cca772d7b391d89aa8512c21ae54d3dd91f2bcb46691dd9d955ab171df60d861860137a5a5dfdd6fb4780b2ded6017d0b663bd29a4010cab8fd38eec114a37d01aff11406605486fb8ebd155f275015e2e3d893ad5220178f97addcfe06dad81826225888604530d0a16571cceb45635fb0c69f60ede6ff091074722a3d749cc99eaf08c522433b2a75c12532437de9053666968d66dbbfa4d54683e1635404e12b975997243d9012bf523ae51be174232da9065ed45c4886165434e87a136943bd4ccfb9c5e62aa2bb41ec270296fb4a2eb76f1091f79a2af8729b675caea7774babeef5a5b1dcbd7b88c31d83847edadc280a4e02942a1419c4f8511fff75e4ea13d3d40737ed88ba62cde622fab19725a98e018b5d51281eae636a7b547ea4941c26a8b7b5916fb648e274040c6ae13c35fdddd2f49874ddacda922a0ea56eaccaf1a2170c18847246c8feece050b4a1511da13d151f30e331880db300d0d58cef821c90d02df1e817e68b9bad148242f4e016169eedcf00ed03054e24275de95f013ff591ceafbc27a9beadda8c0f1adc4106a00e37b9f8c1d2334f097b9ef6602cfcb10f8c1347717a563c92f2ccc44654df86a0a25d84d364da7ec0d2008f677fdce7e454211244da0baedf597cd5a641a82bab1e959d71eee131a0b05682917d427db195b42fe63b504cd1fa98b8011218f17891852788d1763abc05c7980a2d88f1102a42db1d38804c9d72d7bf4f909c1ff1dd4cb3d2aa543927385e0c89624aaa6c6399ac10f597c568b01612dcc3a981d10989c4042141611b5fa3c7fa67b70b2e8e8016c41abe720ab3eef1ce57c2723de3a48133ab87f047f894f2cec8b5e4c5ad1d0dca54a8315f4e82f1f2d0d7c0c5f60f5c51ebd70bc013ff448b596848de3c2bbe96a42941b86466d643978dfc82667b19df25b491ac04907d66cb73498242f88c54871da6d1c592eb8b9d6d7b3f83276cc4df65ea541ce82d7b84cf2bd3e6a62a8631e3c9c48488183639399ff3e7a980ca4a0fa9f41b6ab69923725f33603a0d8bde144a4a5cd10b8ac21344be6d7045e58b680fa5fe684f3199361f5dec7ac2a331327f4fca6615ec591818d8a8af43a8c92436c72d8a884f11b9e414298e191192cfb6641ba2cf6f1a5f494e5207e14137e16e6d1f78a2c38e6d1cb44e36a214e0587323816448068b04e22538c1f68e712c42311904426712dc5c50e89908ca93707a60126b6e79cc09ece4992cab2288e226656ee3c83530971ea6196aab06bf41e4ba08ab1423559e22b009c4238335a44f64e6f68281d98762c296c8f5c0813904243140db2a3db11d191432cefd034dfd335ece71dde2303a9446d9339ac36e59e4a31af53722e19a8ea36dc0c34dee2912a276f1658ad823e42aab286e1273a11d804aaaa3c4be2dc36e8e5fc2378c64d1edd9f26d245bb1c0f7d9e3413eabdd20e0b171275eb208c94307a5320a9604b32af250a38ed1c1c7d103ab0051d1c0e781888440b77d1af5d99c32ae8ff3418677b3dc7ce24327f9a934f8c02843181090123229bf2c463f75bfba55dbad63689cba2df98a203017c8a03da1e8a6e5920da15a903d9abd27f458ea7769d7c31cc9d5666cf13012eb38c2e4c5d4817d6c9680af1660be02e4427ed4b600ce01cd600702076e890f040c95d081e7d3bf0c3b271a7f28ca15b4b4222a8027941dc6def2df79652a69402d206c606e40623d80c9ff1a2a4c1f519df2f6962824dedc334da87b3ff89c940c791b8153e3e05916bc120a89953cbb7a09e6680a26779fb32c0202da8a7960641508752097452ac766fa01f10380871b9efa47077e53d6fcd65480a19af7d3cf849c67becf96466762dd44df0c5869e665c0e5c79ef3ab9a28cf244ca64109d4376219bc005394a981692149200512e71fd59240d98013d246cf4b22ed64a47739f59f8f33ea3588b5d064856404f829dadc9f290460c262b7c6245d7d171978e8e8e8eebb8cee7222a78d286bbf58a80cd521bb2da9881804bfb47a24e6e57e089629ea8999aadc93387cc23e66a2a993ab3c8f567c9cc2559ab944f230a2b6a2f47d0abdbf990f1392f36a26f7911588410a719dff2334432b04922e1924abed8885a5ef4a3a9e049c97ae6e34d230adbaf434a6004bdba3db4a6bd4c617d786eeb5e0629e78712b0f839c958dd9ea823660be965c6b7bc10a7fa5dc04f31bc88f46223fad18fc09cd18b5e889337832451a4171b979ff12ea0444998ef67bccb8f482f3632fce8650047e0cbf72ee0cb07eee0a7183e0650aa402dbeaf3526634cac48592c2f56fba25d79d65c8245a14260ec86fe7850b7012d131b70bf1f31d109a0d440766a29419354e28f188f9458f16f0bed6f037de8d5953ab012a808c5668809569ca86e990c2543c9503294f6a22b64133551ddd665c4609c73b240f30769fae91af3266886283373ce39c70bf8a987840192127203c60f128689cd2143eee0c8144c4345e7a84cb3bd8c0178200b143b234d4088f18425cc9546bba7e3eaf63d5bf50dc8f340b16dd53d9de366b58a75cf0dc8f34031ae9331f2c701f246de0887e7755cdd34dab205700956b67a70fdb9d3a4681c5ce7182acc47f86ebbb7043e5066451ec23210827f304d739288fbd2f4da4cb60a010f9c59955158bf75369bcd4a386a6c6a7b3ee0669e3c7bc6672882952fbeb681de337366d4629dacaaaaca65bcc2ea936aa44875d59eca9acdcc524ad7bb58ba4206b7608204ec94f36385253b7dffa3a508d7a90ba7e0aa629a15d3f068095724565c7f1a46ac58955c7f8fe723c2ca97cbc31e3f1a94b0defb007def49972b7485ae303564479d02099575fd4d9508c35415c7f8fb083d28641a661a174161e57bccdc1dc182eefc5e76083de879f0fefbd16b94a88d22286c836255d2a2bf4d3925d80357afd65ac11e82a017879b99b99b6a929341d0db83f6cdddddac854421c0fecaea09b2bb3b6ba129d65b591eb6a42bc4a7461df1fe03736a4769d2885362eb0adc99a982179a68e11a4f7ceac0235e71a4492e61187f9974a9aaaabe5c66565555555555555555516aa393cd5062eceeeeeeeed6f89b58e107a8536c14628930c1211858d0dbcf612134b9623ce5fc5881bdd7e81c7c7f4e3188abf56f93f492c3143bf6583640840cecce2311f1ea71fa00baa28b2802907f38a658973bc198115eb8fee5e072ebd7e47ae86dec5b30476af1841e273be37802fdcafb57635f23d9db24d6a20848ee46e26b493d4a18d44702033b03c2bb01283361cc4c99b97182157bdcb6d0460947e720293839729c1c512c2547ac82a8e48e4c713299176b99ccca703c742c2cd3f8f4004e8f3aa43be372de6b37a42947ee69a0426a82650f6d3cacf2c2c94cb0672f9cf721ef43a00fd07f209f38506c1f4e0621c994370ab115e4389415e528479709e226ef1df40a446f973f0938b39d1ed6670f3be5212705ecc2b287b54acb98a60ad370d30939cae44892a3cf26784029bc4e89b1e29b92a6940c076d6a9694dfbd1ca24dede27ed324101c348c1ca261bc03454985acc17599c4f5e7da88ebae2a71a049204cb3bd8c42d6402661c428a1f0d6624c09639b9aa6699a8d871330409bb86bdc03304cb3b111b817390b1a5101e3c8710d23a76a46cf8a56b5f95a47032b8eae7f7f5add3628e69cf5980e12616549ca4d1bd5c0e66c369b513aa56bdba9b5568ed5e7176ddc7e1a2014d45bc11078c8347c2587ce128707d3c80bfad58e0ef49fa67d0fea3ad07fdd7b3c80fe034b2e60f0f1bdb7f2a1d7b4eaf2848f2336a15f791f1ece4bf7f7d2200f3cac84409046c312b603bd9f76d48034baf1e7750ffa1c30c7035d442f6cdb01345260020e6ffa060bbc600710217ba53886022c2b712dc9706432994c86c3712b51586245a68a8c47c8d76b072baca7dfe1322f9d16e395e08b7cbef385f8a10b560c7b8734596b297592cbf526055d602d8bc5816dd1c76d5bc48ae10da5290786f187b2e347cef9e3bd58dddddddddd1a7b5d8963dc9320b650ec77c55207c57a572c953a87a39690da154ba551693492a65269541a953a28d6eb4ab26989c545370fdac443c2748eaac48b0a2f287891058d2456641f70fc72a84f3f1de697c3b6b5ab3492168a5de176668b039bf5846b48eba458012feb3ad0c7f7476c3e90c197e9e3653e9f3a1a9858ffce8ea4cdc20633f3a862c16ed788d1bf09863bd6a5f88ffb78e83eac924fc9673e33b39dfea261fcfb5be7755ee7e7013257ecd8f6bd063e17ae30852f2440ab44f791b9a2ab1e7045dfe19058d9627948c308cb2c27e22a1f1f1f1f1f1ff7711f4a6d4891125aef689675234ddc7332c90bdd3a311cfaded8a0092638874ddff0da840a7ce246e65070a64acfbc9182cc27e8262716ab321addc4b46dfb949873f2e439e79c737a366a74873d86f4a9128bb2a3720c6c62e53fc7bc5eafd78e275c2e53b581654c8561fc64fa77b519b6b05f415ec781c03ae76c1b7c474cc704c3f7d0e50b567ee7c0ea70b95c2e970e4aeb1394520aaba1d2b44d6a018ef1f7c14d1501579ab6efda4198ca6d928d4d0678e81bfe1b49233961abc84710d989059886effb0e1ce3308e03d65ca06fd8d0bef1b0524d2bdda0d6ca951bd6b18e553b44126fc4f51ddf683d0449221c7e2ccb1c33478d7eece29a63099f1d2178bda28462b59b03e9a43ca5e372d2446b088b476584fbf9f9690ee4714c41946a744a3baf683d10685d292edab4c30b870215ae37ca9398be8567e1499c0aeb38a85e83ebaf5d61454fc16029580af6ad48b1e00fc8c30e8aedeed63fb27fec0f140bbaa2ad3d52a0fee43df7d607568914de37cc0391c026e714c7b1384f718caf9048adb8221e4ed95cf4aa6157a86c20e7ac54ba87e826ae607d0304a5e01c70e863150ce33f04ac75450f58a9fe7151e9d98fa760ab15acbbbb57a9558a4ba552a91df8467f123ce5291c351c637323831593b55ab162517660465e4f5a3c158562d91512984a07ba4e225defe2fa92eb9d27a58c59e9ee4ce34b347163b8ac8512ad4b3f20fa5623ddd717a7914bf295af6444f9bada532b5c7f2986bb46a3781595693a762cbc07bee21831d8e453ace46bc54dae285f2bf95ac9d7eab5922ff95ac917378bccd554ada68a065484693628388764b24295a039c6ef064a9ed56baa54413c0832e64260182f5e1edb9cc0e4ab5153c5327649596513922402c748270ee3bc85273df5b56af1fe0b710faa3e05c738e854348c7fa0e85c5c7f0f149b0572c370a0e85b340c2849c334283a0fdab7a002e63275c534dd73a01b218b24207f5fc95713962fa6e1a0742191c079b5d65a3f0f9c71988ee11bfd376a709027c31352c662b5645294c47660515cee84a65229562ae52c16d3905a002f4bbf45f9e4fab33c6c79d87ec475e2933e85c19a34119dd5800684218924830c2c2e1343abc2ca9f48b0288be52c96a649901315a534caf86476840f95580c0683c118c635d2d4fd5fea1fd9339773f68c695cab2995c23006a3c104eb75a100ae07be50d0f75ee841341861c546813c1fdc7befa37bd088abb31aefb9f701faee6342b0f48680e831ee4b9022bce7be08d0772fc4f51c071cc3c2728e7020112f35dd5f804f1df8e5d0af69df0d25d0c097991a5e72a0e2cbe6328515676ca0c252c043972aacc8aff9e9606f835270df2869b2a222b1a8158c4a4d0b6d947c73fb82f6b17b44b1941cb10aa2da49496696b254b2121c8114c0915c9f0234763e722b608f122c7dad6218e92cf447a91ab97188d82ad625aed1d5ae518ee338fae823a5212d1c368e23eb937294b526b19386173616a341c48aeca2410496b5d6da0edc073dfa4ec3a0513b49ac5fb1562c7058859dcee1afd1b7f01d18a094b8cb437f6f314d7771794a8c0a1c240a2bfe0522040926c6518bc562b1586cdb3e2ade88840ee69c7394b2f267a9d65a6b85511a6cdbc61bd76aed68c4b6246bb576349261e871746adbb66ddbf64d60bd84c3a07e7ad997b5f6e572d2398cd008b8b3c72f9670fd6b04b1628ffd55607b50cc61db8090638cb1c9a003db9f433f10728c31a64b5932bd2c2c10d26bb05d1c9345dff0b79e85f5cb333995ed970b4c59970b4c76066b2da53c76e0ece0d14ce03009ad262499210a2bfdc331eebed2beab2e97cbe572b96a0d39c6c56264345d78508494ac9e73cab64cf30dd1a6b053a206334918c169bf7de55eba7b0725a4d324edfad73f728cff04c230cc800d06d75f93c1047bfc306261457e694e58314442b550c21da3fc5ae7f49e98218e391acdf9df7279083db0e28ae17c71e4ee4d50a9ab80cbaf99948a85bd21f4c51c51ea9a0b344dd364386de29ee7f34fa93d3f0b81c9cbe7f423e485cf890bfb86bf0c0654775a9a0d8648c2ad9f6ea00d35e4b421e461406986366861e89a0c67db4036d05ad358638fc5ae7c2c76f43e3e432c7b48a5f4402bf60b5970a5863e10e775dad4b8bad53ac339f7858e8eead553c4e52d77e22bdf9931f7c9e33293089b5a8700d7df554ce357c63b274df64587d1a0c28aae13facb2eaacb50a699b22575244a0a6153cb078d04c380c050b6c9681e92249d837bef76fffcb5a3d572994f6b47abf59ab5c637411554bf1aad1608342b28b45102b9cb16bc6913d464f496d3371c023e30d23afdf07666a7ae93d137ea26b51a7434806fbce66888c6d1c98139559b54677c05418d3a3339335f135e33a3191d978e8e8e8e8e4e8dcc93f2ffebd7afd555841498b0a95500609af6de821558e50425d07859b15dcde3aa6258da4bbade76e530720414a0508da7feee9fbd916c38a6c64350b4a06c5718566cd503c18aed5251c0c3510f8ef177cd5c3eee72b98b028d637bed29f8737e3e33357da8cc586c4ad122d31fa4053327d1a3eaa9e5f9991f80aa27d18bdc1589fa7b5e791d4e830afb329faf769a3e3486478931c72933442d2d2f12b55811c9cef8185e943f35325e87c7a0b049fec8dbf2f2478c1faf63a377f99834b98c4a2ea492e85b5e9425ede98f07913f31d0ad21c00d3d2af46a446143ef695cb062c76231f9c3f2f67fa68c22653226757cfa0649362161a18fa7482c6154521cc6bf3562ad5432406fa9b10473c728188bc5c299b2ce82c5c62ed7eba78a131f1e1915d893e972b19cbd8df64cd65a0150afb02f3bf8a9e7edd7130b4a62e9e778ffe4e9974b1cf5cbc562695f353027477bef2b98537fe33ee783e11f0eccd940ff32419b8fbe8fe7fec5c67b1f9587dcbe971db4dfde4603e76bcf270ada54f0067c9960cd69eeb4abd52c579425ede258bb38d62e8eb58b631c6b17c75c1c3332d359348c7f17302e9a6ce18a6d00ca288cd194d18d6bb2d060cea94d6dd643f6e8a17953aea1693dfc9b2013baa78734795f396f7605fb89c350c07b7c13ecd8f53138c6ff658c9bf3a20f35c6e8e798264c2ae8f9cb53430d38703ccf66cfdc0d4f9a617e369bcdb6ad34da18a8b657cfd66a6d206f03792330ece5d3886f9de252ba09d7bf91f413b197741324987a4818d8900b6040032c532a75c9927a48538d8419d239980e81d5004b804bd3488ee6aa860a27c45aadd3adadd699d010b899678ca5bcb8675afbb57e53fa4cf3b9e8c0b287b7bf1b660d13895aad9da174d5755dd76a750e6460f7ce816a6534912c79691ed8ad195d8189bb89d40b266559135a2a9f222b554b65237beac669df01fe0007543d5a35fba902a98fc044e0d0f6105d9715d184b1a5ea3a15f75dabd59ab1999921f96db53c2fb451c251637313c4689b9b3948376b5ead6fc009c3d22bfe126c0f4bb84c831081dc9e2bcfea8bec73e3a107403058917d4413338f1e48600296916f5d70fdf3e1d90d0d7a3ac56efe36ee15146be07a6afd826cdbb681402216bb12fa40228fa5b3dc4a0d7d54d3346ddbb66d037d9625b4c20256cbad84be0ee46d9bd771356439c161ccb0d878c4939e9f202d6b2923d1ed92d87abb24768eba2476e4a387fe4305c463c58685ba4ad7c934130e5552878763b8ea412a18561c09c089ed7fd1a18ddcee1b49c10a366c94a6aebed8bc67bab9f4a582363f9a1246aca3c1081721d896151b562a51d8f852c31246f6c460ad82e5400a969955b0a947bb8ab83d706db3cd25af381a81110557e708a5a9abe09a15a18511d63ec2465bedd980c49088f580cd601b0c06b37198a60283e1a8117501d3509a864ab57886b08e5829d1d15028ed09b11a56a56106564a6dc60cd804d130c47ad81c48c52e20d4a4848dda3698f7844c715c5754adda0c2ce781225c6f2de8a80247e17ec181a4d62899ca63e9152b2af460b3fe8b1ce143e4ec2ae75447b35c34daf6038b6528160ac51282041344505c2c168bc5623729ce2d9526a289182580c39134351305d0a1936f7eaac05f7d05418d3a23e76c1a680bd8cb07f0e33e5ec563b399599624ab540a435913aad4101c1e33b8420aa8d38f298428a7232f3a740fae33abd5dad1c86b50bba669db65e7256150723d8c22bae8557cbabb3f3d42830e9bf564fb555f355eff9476b7bf49e7eb8ad7742758b00e7b85a26f996911bd7cc5341bd3d02b7a57182d87c2f22e5e1c9fba24f67b1dee45bcfe1436318df73c3b5e8d5c77306c42dff22ca496b724d13749868ccf01fd8c0fbd88b4f220d207e67c2ffa1cd0b71cb111bd8cf7d102ce781f22d0876d7087d0b7fc4de85bc09c2336325ef4392c3f03bc59791198635f460b89e54524fb73b290f83449dd8b3caea8bd0fa3db4998d10d1de83f1c159702865771186fc2add611fd0ae23e6da45ff7670ac3f8bbcecfebe7355ffef297bf5e2f4a619dbb38d15ad358e3e6315f5b2101d1b70438b4461a898063fc7de0186718ff2a5be0c1857dd19e6f09da6df01d2e235fbb3c62d7885d231d34287169db569dd8b68d37d6d1d9b0e083c372c79126eebad23182c5e101019a3044804d38339d23878930298e157c28ce26936dda56ab27f3dcc7758a389594942e9b996bb52ad5ab54b2566bb5eb54ba4a9afce42ad548baaa8e8690a1320abfbeccff022e13c40557ecd4a80512d515bb894de4281ca5e7f3930d1031ab0047b9f3c9ede7516b10ef4917061e848007490bd5e3aeb7bbcc640b516eb77f9352cd063318e18b91e3d8fac6711cc78dd2b46975d3ea07e27134053b63c269391648b6eeebcc04b94edb368eab756686eb487cbbd5753536375cf0388ee338862c47ecfc9186a156ad473dd28bd78139317401060f4e393fbe24a723f47ff40f923875dfbdf47b1ffa482ffd20b05bd50097ca94202b021359ebf3beeb041d232684cc9692d075d09282f3465b5f0c2bc060b7baeb5958b1a90ed977a3608f1d845220429060626cb55aad568be588bd92e79c94523ac1be9225537a75b80f0971ccd1684ed115ec775166bb5361a43f2158ff2aa511b3d9b8da09a2d2318262aac39fd020866d596c8918aa7e384cb1f28a55d37a36c36d7626ae5b212e45d899f67db9569487de1c116c8361d858adf2ab42c7cc35a3b3d96c369b699a3785d6fec31a0fd8a8e1ea2971bfe64282b1251afe5326d8fffbb55afbbf6d4e34959479aa092925ca9c5276777f1886f839e5fc98a1c4ec14fa1f9c173af6a52c0c36f528933e4c334720a314639453aeff94b210cf9c734e1114566c253bdf0e7d0abd46aa170c825ed10442dceebdf73ee47954649f293b1db8a78f4347677f3754903ddc260f13366945c85432954c25234919c704e118bf230f1ddcb1752453c9502e336532994ccaa48c521baa101566e6f97acd58c591a639271039ba48008462a76d28dc0588331ab94cd7754d0242ad10002c7c0d52781d417bdaae57932d3a47659acd077c75bb374fdfa82feff55ce7813ccff32a28b4d1d203d555c0652db450e57a2b9452daff9765e877f7d73c48e2ce27acbf46a2b4524d2b694a24a574d229c7719423cb15e5a85d51b21cb1e28f9e102bf68b254b96a2ab821b720c33b934338a5b02bfe24b77d0093b8ee3467f0325d0af51272f0d3e3703db301211ecf793d2eec8d6db4637ca1a1de0824eeeb4bb9bfea6c3f348d20abbc35d94d00ed4d11be86803450f78a0f73c1f40d77b1d1e690711ebbdc82e1bc01d38a858fade83409482a8b7c47a339ebd1e0741c3191b58fa6209071c331dd75f6d14b62389f5fa50e36a4fa5699e4a330c050c0683c16a361b25263caec1cd5c7b7e9cf8548945a9d5ebea0652a954dae488a86cf7ebadf5946cd2d4e329b1f28acf63b52bfef5ba4f88edeaa655971efbc2bd1097a3a105db2abe53f5e2513529b26a7d31b0948617581f47fa7d74efbd8f9707da8040ae46ecd82caa28d2b19b580f7f504f471949f97e19f1d281deca75001af66262d943ee786c93b4ef6feb1cfcc8a5dfa3722c2d4a6538ac97794a29a5da87615d00b022c452f0653e0b71bbd520cf65b48edb36baa2a0495ff30f87bebda2a4ed7a3f60130e2887f7aec97432dcff0c21081d8676fbab8f53a7a0a26f386b27d55af56a4541d106e0a35960c20bb60409cfc85a511e826046ca04a081384cacd8abd65e003f92c1b2700e467abb4e8ef19c458939e7a4829d1e7ee1c4b6cdd9715b2761484ab05c00f017b06d9536cbb65adb9b42a552ad60fec4c8ab88ab87a544a51a8d46230e65a92459a552188e46fc725273c3329834f95879d0b6f9b0ef3912b7c2bef73cac3c2805969910197299896aa70897993c3185eb29573292a61a2961ae699ad63f05abc4271a66e02a8e718d44a43ead907a95d349f002aa75fa315b4a749195ab52aaed4557a9422f690a7244abbff2dbb63dfd2ca001b9f31bec41fbf99ed7a058128d443fdfba4cd7812f3bd8173d0f2bcf028a56de87e86d079244dc8f6ef7deacd665e66b2ba4976949deaf3c0fa067017dd807fd0ae8c3c7ca7bcf27fe74a0afad9034ed7b04a3db81228fcb813e8c6ec3be9df4388ccb4d6e727b52c3c3fb95d0adedf3335bad34a67195f3381157f5ca55bd72550d35e0c06163a55ab9cb086aa55aa95e2a954ae594da50712e0315b8d979641e7329fef21ead824fac8fcbd09722f4a28c22633b5ab7c33457700ea984964e4bc75b5d41d933f668d1df0183892540d6006d8709f4df7b9236f5f03a4792040ad71fe481a27331db62e65968ee45928a846936ff828b2d3c0b6fe9788b894ec78a2988709cb7bcd571a18d128e9f6f8ac7de0aa594524a295da976f480d087422f0ac0d42d8771d7102e9f28a5945a92e85948f657487d0a919eb8fe4e5c06f837164d84437f569f4224543d8942274bb2608e039c70c13aadbc10276f85c4e30709b342e2615f447a11813f802ffd166c70073f853e04f62861502e43b94c119c63fa4801430ade5728740ea6f95e7419cf2079ac7c917d388ee338707379a8846392f40d241cd3e2987602dff07f3510f8868bdd9a7139c61b089b7634bfa783d7921a74d139ba7796b72f826edf986003692101a2eba8648eb87456b2110100000001131500001808060583e190583c2e9c27d51e14000c61804084643c1a09a4410cc350c820638c610600620000000c0c91a8005bc4b75540a5a7c72b1deac50145f8dc4928dd364c074088a269ee6dfe486a1253dfae4a91eb83683c7be2abe30ad51f973c7c268c732e8ff712c14d2b6159b51439e66fbb7210af987b982a00826bcbf7e28eb27efa429df022e047c4271939921063dd42feddf0f2719645a76fc4a962a073a7af70baa8efb54d55a27d93719c641f8c33029f90d5e58d35719c62d7755351f582d2e269d3d628a6a751ed31a34f8240329c070e9648c5802a045db863d9cf40445b0c278df7d0fdf26edd62eb90caf0a8a0a1d69d84882dfb62ffad8faa54d3307de15dd3a4cade71fc11a4c9fe03c21e02fac399b2997cc8a7581f30db8a441934184df0eba87bfd4c734b0a3500e2cabe6d7e2587073cea4c712c021a900e439abe8a36df876321bd5e1dd189f53fdb99d07b13dd5be66c12e3107a6e1fb13d2e9aaaa80a707b64848797408927c17d4278565c732853861a9b5652083c50acb269abb8d77f5325231858e3bc3604e0cc020993c41205628091b626e07eb718fa43c37cb4e2d484f935d82269a29d0c2698a6213032ec325c24380ceeee5160a8f8307a02a297966fc2f63df573a80452eec54da143bfcdeeaee51ec6f06c1c04c9b27c675ce3ce96b1300961209fab8aae6c6968d96a2ef24ed067633fb5349caf52c366b82f5361003c7d5dc4f209b46aec54ceae2bc361ea71d77f6779559c9b30548c205520b84d0c6c00b953d273ccface5a88ff49f5320dc4cf82f7e966b2d97d82ea59e90de3eb2284f69e12fd4aa8b8f3b63a290a242f139a2bb71e7fdaa2dde223820750fbce770f88ce7ffa4963bbdccd1923811cb1a3310872841489428b57cd37e8bca7095840ec583dc08bcfe2a9259390a06bf59c1972f6db1ef75c123888d24db519f82c9223ec9311750ca9039825b68a01cd4888bd54a844d70eb0c32e7366dc4dc32fa794fe4ffcb78259a2e16d2c1397a38ba90c9bf56a2f4060c24048bfb2304d1612f8b233594f77fc494f62a29c14623721828a1e4b5264090272e3efb2145450631bba11192cc5da390cab66c8041590d50a9bb19390378271977000321aa13ec11b9c6b5ef849f56990610c2891094cc57ed207da0b58ff2630cd3030a2343b72fbaf7863d5c02ef0fcce269c574fb398d5f37922212a71037e680942eaeabafaca5f8d22e00672ccf690b3efb177ede1c8e0241ea5f6cce84181921802f9e88ac7db43133771e6ffe87c5078b9880fab08ef7f586d6c305f763c9753bdc537bc200d22a1b3552a06463d1c1ecbc7b1418fef7470020eca9b44e47dd34f56179f4d3979cfc99ae87c3d50a586b4e45b9f38162737f84268c7f031aeef6deac37571ab87ffec269b6622c4b8ebe1bd9bcca9ecfc587f01d74ae4205fa715b832ff7a18ccd52caa79ffe0bf8ba31fa94da79c386099d32273c4b492d095d0dfad08c5dda8ec265996925e12f3dc56165fde30179e24c75ebb34ad4d869ea5e7d04d122e6278686dd34d01192f098e5f11178f25da24ebc534943b0c54da911116a0225c41a7bc2b1ddc80969369c512d042a0983f92fc084a8ba29eeb386d4fb08afa396330a5009b14ab80775aea993744276dcfe0824b6c90a3c105ed2385304f95525c3c7316f5e508b3e4d48265a737cc2b2427e2d809572cf6190871215ddb9b526e68599f27ca8c7e9acf43f30f9180aee798c64a33295cd17b24a643b62ea6ae59d78f71369b67de7a77c0fe88eeba008439a19e32ef7b88b2095c2187ed8a5bba01de81222a89bf6167a78184d1e358988615b41bf88485c56ab78871c2f25a76443eb9efef0bcbaf66041011f07b8b71797d5d82c0d326c4edb90a633f4dae21cfe522bce3b749ae2a19ea31fe08ea8cce9ca9d4c17157ee00b22931e61f68d277b4ef9a623f4cd7ec43c2958fa53a43b50fd4a3fc47bfe7a05a39e5be281b6f58110248ac2606f7c1e7848cb2b5a0e3ed9875d048c9e8e69af173ea71ff6910b7d4dcf9fadbf1661c5db3115cb9cb1aed85079706255abdc40c69d0d2810a2a1ab821f4dc5d2a141f3c4a74ffb7dc1fa3337a8e53db2777235f1bd902d7165f482f30ef08313bc4d8e827f8abf1df92fb57482ad4f85ccb67ff6254990853988f8e34c1ff7c7c2cc1a8553ceb6f714dced694df8c12b789fc51c671a3d56e358cc4ed05c3c08be80b4322d7fbb964c450acd41f24390420649b61876ca9e84b4dae0b44ec88662bcbd6627f1f9cb43fb4406e7a4231e6686635002426c5c5cbdc10157e2e0d03896c2b2a85d91c7ea3a2388270c6de417c82f0296b6ee7ad0a33c5175fe4bcf5c0488d85c42924c9f66caaa4b7cb5c3c486ce6c1e4f736f27c42c10810ea7e52b86ca09826317c1f4e791ffa7f970d753bde5b1d8890e2110aae3e960ae47bb62b05c7dce68304d4478e444d0b0021a16dc5c09e560d108b55f9a83fe4ca4263c25bfd1ba7a8cac88314099149e8b8a3302525ad21e6e171c4857023c0d22425e956613687fb84caf0e1085f04a5dcebd05aa14149bc9000f40ae7d0a979eac196b3ea77cc1e900472d196d9dc10322a43a4b63824b1a73cfcef9544ad41a9f541500cdd601ccdcb55239616fe2cf20805af10064c9a2c103eab785fb5afcaa0288287402fb8881a11ce68d4acd689fc170f0dd4df4605deb214bf82b78db0c5dab8a95077a0c43e074b61820de2d5b9a3d319331124705fb27d4dcb192e0403cc241667f512595d84bd36f1fffa60f0455f9ac022e3402552c38da214794ab8f1f925952569403a2769c7dcc4290de6d76a71702b2d5b8c26662793abadf79337f21efd7293cb1d57f27c43e1643278f7014493a6eadebec9eadca9c8c8337e69fa88b81601253b10fad222b24f717d93155181b51cca11ba3a945fb686b5e81fd95feb5aae1d18036c9d58c781f997df3afd81d4c32b8fd02498cbaaf172d0ee81b76bd4d4e8d6f2edfdabf747de81b5f0a0ee42841b7f50dd2e6ca5f682e69211372f3bd5ed3101ad0c3415cf89f4fa0b43f7049528721df18c30e6bee31e41b7dbd12ab574ed3128f7bf85a1e0603cc1611690fa946e93cd34c0464626e8f1875a506b2264317cb3a1d54fbadbd5b0b2117bb166dacf872c6f910da40765a58474ec680cd3a095990db68c06cce2be4490b31c7c2e5c63a1a61a95f9a026ffed3669fd2ff1cd5b14cd2c97b97c05d12208576bbb3aaab596342dd5653ba3892f73943305d99ed76676ab49765defd56c6444a20d36059d73a3467917a7462baaf8c7f5864bcc84a436553d58061691eb013920b0ff338f4af469f67cd8fc77dc77b15a3cc7abf038185113104ba82059b8b47076ee02b7c3a8a02af80e0917107c765d0a9a602cb280d28b8a1f1e37e678b1d6d1dffd1347b2433322eb500ac5c669a3f595cefd6e21c7fb2cec274ae8e60854636543912d940011857b03cb3c9e40071656d41a7ffa2b2c27ba5e45e73e406acb1722fd0611a45d56117ab5793c0b41d7b8e0cf58a768ba160db3ae9670ad342aac065a4e48b12a8648c3b2b0c52498e3aeff2091cb30f630f9f360f533113f92ddc8d77f4c7184a3950b9960661865dc046af7fd502e451a2c1ef5d02ee69f95a2810789004dd525e386936d41afd48aefc038aad78adb8fd03b3b28580a296c543deb10f79af3a1cb06670df1cd083434d9e1baaa7105aa1b1d3c737064b21ba962fbdea89fbb4af8d4d1ca8045700b6411cda7b616d1b7a5269eea20001de27a1d306c87eeef553e34df3cd071d078ea81ef12f9b34ada1905a9bef9e589e60554abb265bb1315d20c238760207929536f6e2ba2769cba4c786d75f9f3475e1b14ea60bb0ff131d7599c205b8dbfd92acb7325ccc4b069d0563f30ab50c54812c66f0a7b0eb7555c3105677d10c2d6fa795b06c9a2b6e73c23be86dbb3ba01fa702162eaeb905cba71a18041a138899b7efff68b82245aa071c00a3f2af2cf5a4f01fafb2a3b6ae0202394cf177e37d1c739a6998d2d3ec9e60706782ff3ff22d4de1f2133e0b22c6c04eb6085217a07002426f234f7207ca3bb5afcb0cfd66c61c8770aa7ccc0608c53c43e753cb300cbe0d3a1c2319b96fb9c510afea4040e9285c320bab877d9d0af1580c3498e6a16fb58566dbe542cd0fa7d945d8194518520194d2f26278ba18b76b31a4a74088c9c3c35060c85d9007a4707b6ee0bed053c580d2efcf5911213e2300b0b510058f2d040c9ddc2ce13f8e26063b40f862245ad3920a320fa5012ae625435b878fe65e367265e5e35bee89b3f612ba3fa1465a463c141c99e6da0250c96a9c2ff6ea8871bcdb4ae49448a0f033d9ed696bf24981cf9efe568edfda34912c0e3ffa3df8fffda3b699910c4d5c183d98d05bd8129e1f3d1e0fe7fc0d5c813375dcd1b51e0f4f8ef2ce5234a35051cc4c0c93ee922e7645c121ac28e2c7728129633860f383211aeb1c8311f3b9633dc0e506ec87ab243dc6acc4f05ed9f2da9423f54791deb92ee12e698f3857b4b0f7afb5e33307655cee87cf1c5757db9c25cce8c40aa0a45331790844606dc4d40a696465c4bf79c7d10dcab3f7b8425cf62826480cf5e108686b6f85d1f626e12a8107026d1d48d56b2407b22b34d4807efe83e83782bc1468dd24ca44f1aaa2856140929b19a24885d06161de4802df2f01c2f932fe726068cbf3dad0baf78b193bd59c0923acc2590b53ae4f915fc0a22d3657d085139dd3d52c45feb687c9a03392cf530bf92095889bc942763396398baf7a754b0e2539a16db4cc63adcf983900d2f5cf077192ea85ce7c3f94b202f32648055b102a69b500501ba4cf025ba411714cfe7fda756d812543b31f7920efd20968bc0911faa7c23a24ad8ee628eeee220337093e246d31362a87d746bdadba15ea2625f769d93753775056fa3cbdd5bc36b1d4198e8984b67a31d8727720d3f1351916ec303c58efe0a0e0bf235296b3c8fda15d69f8270263d26de3ce55556cb61a3c2829c4241695c6874c0e6700334c567df1a25fd379579364e3d563ce9375ea227f13f027e6332152580f80a6dcdce6bc34e17331ad311e9d76f43a1dd64e1bf8402b6066e5f9bc2ff61cc4e27e1469e01f9c3cba70112ee9a4fbaf73a47445e9913963d3c6846c16d3307af07dfb56d9d416324d3e933bc4644b67a61d4a1e4e9dc3d66a002207672bc3b66b8cdcb0e09af3d3b97ca754019f934b61541cbe73d7c109f3f9535cb97a5f75d5c086fc2b4ea0cbcea4bd2284b609d4a5718201e069000ddcec080ca97f8f7fee1dbd005cc8527250d8a4bc1179ac1d74aac425093c162aff0f22605059b7994783f3232f4dd96c5bb1fb3fd522d2da87bc2a28d94e5ae2ddb2a6c6004f2a51dd7c0c3e79640de48147ef7a0f70c3272916d86e3939c8d83e88328bd11127d56b487089a977093b4e2b676cad27f44dc3f4d28139a4d6d81f5a91342974a1ae1b8634396d64c634708e28e48d39df050ec23d228cdb2d45ba753eae3f81b3282838d9c1ebea26a5d07deaf56f2fdef6d014b4d7621d7c2797c1532378df8d1815ef922536e52b1305979a9a64bd3544a486f45c39afe07a8d4201407dc16d2407cc31b247babe960952c56986ee75c5c1ba142facd46615372e9e88a360f0165fbe01e8ea572300c86cbf3e8fd1499d999163cd8f620e21433a520b80fd80e1557214b7a644f09f897091a362adaba63c5071896bebd27e1c9268f425239540699e6807a2c23136c082aafc4c29502628f4bfdf396e852a62522538c0780d812d3fcff8991528c5ecc10800607df981879205463d8e9e2bcd776c3db3ea0bbfae2b431f324fa3d1636b51e554f219273ece699f8f8088bd9757390e4dcf481fd97414486aecc9ced42c6db6c2d68303366e8d51194e7c443f5a8d62783c4b7b21db259b9b52b777db591cca8e2bab424ef7abea063244517d78ac424b1466e8cabacdae2d665388a6f811e69abd342e1e9c55516ffb7cc5924670db1c26c9932a7316a88a4b515fd8e2ffbc378d2465811f892c80d743e5c6206b1c0c5b6cc23f0bc020d695d64582781fec69c308f352e89d3fc132cf19f0951766d7875d656172dfded480a15532c3c5c741836c39f99ab258fc55774a35042a3dc04b5c1d89fda1f0da289f1296ed453795248dd9542bb27281048bcd6aa8f192f41bb39c9ed3fcfcb65a3c28b4f09ea79940efb07de19203573248c986c7746c95a1574a9eec60078c0f6dbe5a4b7611390671d38d5f3181facbe02a09e7a3f2bf1c276aa60be6d1fba3124c272ca8c67701db7168dfe3f3f5f7faca407b43cfdca7d442b390eb595404e51726c8733da3fe70154f7a671bfa23d03c286b8aba37efb31977c4d3a4d6405463e2b504a896cfd76ba0f754f3e801bfe5a18633422fd50ba93c24302eda7f820584210c54ee037d37db88b24cc733511d1711e5530b02101f3fe70cc297d1f2977103e3cc33f7f50c7b830bfd2e485d5645ef17fa1e98d5dd430a7c3fcab0f546238c6c6003c2c48af161ceab17973e96f58e256782d8deee55172021c039455d71af78f6f4a9b93e73d8e665b94e993710bb710854c20aebdd021e6964d1909a875df30fc3755fa9b61e29443157998c09c1fa8546cf1202d8c01520d3e9a63a1c538de76203d87ad2f673fe4d0ad34d90d04fe7d4ccb14759e339d1e886cbe9c59c05f73c478dc5d6d05d822fd26e9b137b42a97756507811d77b89daebf9b18e7c6bcf0e0f08e1c4152bbe7860af7515db364fc86436f315ec87215318ad64aa9751d0ed9286bb5a9f1ba25b42cfdc8d62a58655d82f200e27269f6a43c8eed9a7483e24ab1c62b45ca509e9cb0b0f84819cd8c7556ed2b8ec83faaac3949d56c3a2a197c9f53970f2c6c37af0272c8839b2017316b4d38ace2151e9f3f47893dee1c6d61c31d24449c1bbe9a5d667f0f70ac9159b9171bf2e221524b470c24ea40bc16341e0fc172b6d3172488ff911e9b5875a0b2626029b8419d76aca1031f60e323054b5eb0e0c350642731f88030ad1faf64a6a4076f1f40e9ced772881da70437f16168fa5c3fe4c36cb3245a04e56b0d195c6b8760308052cf8931f62fe34ff28210bb876abad9210731bcb1e40f6e126a82ef504461f14788de7870662695b53b2b0f75b58e7c20f6ffdc1fe66e78fcbf6390f5c76d10b89b7fff00d43cc1e0ca9167c82522cb7a5e1c3b0b052235d5e96fc6c87f58310fd0873bd2e551b8a5e198cb2a27600f5057b94a474fda69a6dd8f10837b6ba578f561e94f135aa25d0b508a58bf7afd30cfdbdfa5544192f6c7adc0055db1bbc95499e227fd40b1ce63c443fa438ea2b72d7ba454c6f6120de5f48a79049f4a4426c5253981a4e0d382a4a965d3d0fb769ef5a5d6d3d6bdd28577a3499ae518c3f3dcfafb4ae3b2011dfda41d042bdc10b643f1b231d11659e6d8cc8ac395bcd948932e81da32772c416ad932f0db8c4ee1aec87cb988c880a31167c56777e7851648a451d7690089a01ae253995c2511792f4adf9ed67b40e60bc179c7b8f2bd51afc0c80f7631628cd6a126fa6c5d1c66d568db8d32f7f6e39da066f5c62b9c49389d1c9ce6a70c6a41293dafeb7d726cf24bf0d9240b0231d628690802d50238fb5d01a1958150c601a9821fcce1c9aa2593fc3f5ec53ec007c1cf1c7bf2fee13bf71a34e04a4ded34dee000cdcc9f7b51166e4a9a640c97ebd2d4b287525fc226a5e1959851825639227f7948b6e1cf3cdbea9398373f86c8cae16429426c462331cbbef3a338a57375479af28b6078e1aea62909b4dc30fc2bbc50843dec6f7f28942283bce03c0ea1e7992ee34982e713d0c6b11ffd5e5ca23296f27e97ad7714c8fa414dd3bae9c205fab2d077e6349da1e40f620ac725f145565b231df862fc9405c58b2d507a2b699935287b860ec7da0150b3bb93092925c232729ccc1251a0737b6a272b82a21a65cdee1f15974490e8eb826a6a9b57f0940895824659a0fd44fe73be6101b7ca86aa235dd3984a65b72cdb6d801499f1f4e14fbeb22827f4eaaa5327e2af6d472faf5ebd9c9d672d221c251d89fe619951cded6cec9788680e5ea9b617f5e667670adc80ebc230f4bb9b1aa1e26a73fa787c3699b244dffc88053cae09c51622d0954d96ca725a7f1f196d7e7c5af4da93afb134c6336dc4d471fe287701d580986d5e2f9e7ab6413380dba9aaa6b469e4f86bc82affec94353ef1344244873851ef8ff96aa537a6cda05c3f9dc0faadd387c0dfe1c07fc775a0f32dde5c4bdbec0c6e454a172de29f596ca138aa27f77549b24a193707289be392fb54524394f158abfccbf0920ae379963689ef1a3819932ece9c585de74d697bf0b5757cce238c27f5aa7c015500fc27b751339eac4655158f8029a87ee2dd524fb7cf48371d437d7507a664e835ea064c0183bf90ed0d504f11952aeb149d4405f6bb08594b491c4b891adc52251c800a6631b2f458bd3e2fffb06ad5b7ab5739deeaf65f9ca59d8fe8a1d5ce5c62a378036d148ccea23351231951cbb37e52f153997274782f33a1bff0120698706e508348484cd6ade31e378ee644040cf4b1e94a062cd992696a782938ac494395e597356e521cdee2aefe7b2915e2168411d38dd3ac38c72f4aec8651b5faa50c5d0ea5417b75fc5a042a38c5b3306c7ca817f50fadf12602d0cc1b904e747fe73ee15ca4f31d0ab20911ee0fbd66c972844050aadb1ed586fb23b6beeaeb80353f31dbe00c9f654189568ab15beb542b692d4c60d17a8c01b1a4df9440e41abcd118d3c27b627b78192eb976effc09f3f911fd4d55c4e0296fdd250d4dde80ded3991d61704414efc5de9a403852b6c4c42c6f015c8e75a6fdf09bc45070e70d189d42eda5b20aa431a3dd036e894e60523c20091c0696aa367fde0fe6e8011381c49b84a0a050c3c200f997b00248f1ee8dbb7c26d7c0a0928128f3aa103d40d548bb5bddd4b873a1a799e942d4e4d4fe9080804574149f75ec757915984aa798b75522da221fff0e37269123ced35d12b78ca6782db74e630458df7f4942fa7b910ebacb38ab4f45b572cb43322d9305d409c737b6929d0cd6fc84469134c3f432dc1f420e092a2b1e613a948f502a5165f781d7cb447d97c82a9ef0a94e57d827e051ead65655265a167b8eb4ba198fa2372913af11f12b88db454dee48893c4be33b40347ad01a57e2b2345dd483e1871a34e6c57d36af4b4dd15651891bfb71450902334c1fcf33e97c749e07cde87cd8adfe233f02b5ca7cf9746660fce1b5c0f09653979fec34b3bdbb86e56df7663cceb82c65b75bc31233df99d32ab520f4ac9154a587ea42f7fadc824479061fc095476e384c73722962c88c394968af26b1189abf3724bc4bfa296f4245741f68ecb71ec5325171fcaf44cb1fff56ba063234ad465741a2a11e6e5ea1818898bdb7d37517f6f16da61623641a4e26bd1d5588ab6a9c6129d5ca3a4259b943d295427dfa0b9baf551f6a42618c906118511675b49378daedf12abd77de1975a091e6a8ce98a170a3205b749710175d879859b457ec988bd89f2100eed0a9c02e95a0ce7bea68d5a18dc223b08d0fcbdc4eea3438e9cb310f72464992be3f5fd45ec6b3cbcc3a282cb0455f91d6e350169ea9045aae6e0b8c5dc190511845507f105caecd0092e6430c512dcdad69d622a6af8373511dc027a65af7e8f3d3f52f6ed97416f8717938837aad82dc000b797f739bdbfefc1e0c740a49b2e53df5339af86aba1bf116d34094ee24db37a0cb16eb06f6edbd451c7a5122d1688702a56c18352d5ea017838d9e8df9f33d8e51e9c80e7cd84253991de7f31b5990e44c210199b5066ec89b61d12af912333a253f76615b7eb899b29c8df7b591740f7c9162b20038a6342796fe99a66ec397519e602f352dad809548bb3a70403101c069c3cce76b40a202af446ef9145de16e0d33c646e9e36258f9a4ffdde85c3155603e59c6a07296a3b93e0003e552f96a6734f6e42742cf55cc1d425ca2de539ec2c4c9c7e123a77e40791534e0697e3d4482716dbb5963a051ee7e6e381d443ef62e132092387350e3cff79c22e028cd4dbd70b150cceaa2cf7449caa20c679576d15135eb22bed8a36f893c6c1848d96b89fada8850c29640b639fe6368975bfcf863c5d27b59422b181ab0ac18e04c57a1bdb2a98a9653d16f6e651853137c7a1b546088cf06530e1f0e17c0fa75bcb8f2f4ab6c29fc3dd81e5dea0a4492573e6c0a29f8c090ee7cb10eee41c28c7f79dfac954d95b8e7ba9302d7fa57f2698b0a6c496c77b483767e423778200c2fbb5615ea53b751e20daaf9964bd37bc787f61374cc20dd36482840401de24097d9610e2cebc2273eb99c866134a25ad804b24152d751a8596914c2c01c5dd76bd388121d2171205614445f8166625ff79ed11b425bb4f50de399f3fbc3011c78da96439c0a85db5ead0dbc568fad5763c127c3be6b2a8c2f0ca463acb8969fe8480f289acac064b8ce5c024abe6abe476391f258a051486d35ca14ed4988a94ad0285dfb60772d62a2611a811fd30002cc86a296752d1dbb7bb4c6a88cdd9a5c90e99d02d6cbfc97c64dbcba749028e38fa7fc33030f7f6e21ae7e680600cf4b2a765fcc4b740ab6229776e1561eaf196adb281bedc3f8d10efd1df336468af4f04438d94987b88993cbdf7f143143dc6636a7efdcc838e65213a1dd11adaaa4fe2810ea161a1d2cc154c3c5c28d8898a7b8b70344edcb82ba762bc9c8b35cde31d785c59a2122860b117b3078339018d3c176b6f07c8a5b2b41fbda86c63a48f74cafc3d0d99819debd46a724e17656abfb21d20355cd0572a5f85612ec6f2b58486c5ba80811a90b9c69ccc90cd754844a53e49a3f507ccd51bd2d8952de78ca474df3543913422312024012c670e880450cb97a18033a464d5356d57d2f51f2bfcda01e5368d8db7c89916c773d4341f75c51a447d420089b5e6d23bd5eff3f439e7471418c0d8b68deedaded2d4ba2dda0d0c995856196d7766ac0a1473f5cfd9ef411ae7ddffe3fb2e76633c5d17d9eef36a420a236e6ab7159dc573b5156b91d4ac6687978703fe02861ab9b808f7aac5c5bbdb137e334e4a046f370c4ff51088ace0bac5c232a250bdd29ac26e8d7db9fa65f93a12203996726986cd1c0a1458aa42b16e43ac1321a4a31a3964e5e0486aace2bdf936f2bda43402b080b083eb1200b0894a64820c37b312138815e59abd319d121456276f22973290f406c61c489fa920d75dc084cfd5c1aaf7660f238f0329f8132369431b5e48eb67c612a3e336cec069afd52994048045ac69c7065c410d6278171e99e096a04e2bd23213532b970656c5a06b75906b73b52f51c277d5d63e70c7a2e8ecaf58c89ddaeaad68e40855ddf76e04d3f1edbc88e4093d84b3cd0fe67e88b983fe444bbcd88ccef64c2930e1b7aaa5be2cc90122816d0cbf48f0fae630ff71d621fda69d62042e006ad96a3bbd0588a19e65d1c8712ac8b587909209bbf5f009fbcd2e0935628a4e999635aa1deac96b4abf7272905cca58102d8aa9a8f80943d735ac52faf6b816aa35e868306b9d1fa2b56249fd644a23f49c73f10cd6f089bffaa859ebd3a1694b8b21cb5b0ae9f9adc17c23db222d00e04afeb2b980db309fa90700bea810a590d23f809b03eff7d30962bda5fc942057595f79e2879df7eba0df55ca77453e84a7115b1dc9427e9c6dcf20c99dec66e6cc34962dfb81a60498a40b84aa7b3cc04b973cd63b323ed1a16121d826c572db5bf4e7cd8a8dc5748741eda69685280d8c3c416cc87a983181a29a292b201e32f40ac3aa6d035c82543e2955ba01d0f1cd355b3035a18b72bcaa9a3ef6544de4a3729542ab11aa9590ff1d2f1895159cd3d46c008c608b0c940abc6f6492ae342c7e8c19072e896e93516d749b646de09cc355385753472a71310e4e5627d4bcd9455855c3ce353cc0efeefc5f318af3f731d0d26f7dfa9f308f0fa942c003c2fa7217dde59b746926d44e33031d1ecff9574d0b9e7f2e13537b0d167e903fd5dbe050e1da40618e7382a709666bff057b39b5373a96c1d6aff5a10546234cf6eb08289cbe8606cf75eb41eeca09184bbbbe27c334ee08f0401505812346546cb03b08fb6affb5378624228be5211f1904987d4c6376b8191e58634fe8294ae97ac9091dbc67018bbe695492668c02dfc01819d98a25e3b26be8c11891250211e19d80764f69c64d241c49a7f5b5700efb0e0218dbeae2ab0d41c6959ec1f7eddafb5c19221e0e57944a892b6580b8de6eda32c49fae0e944e8d7adf8bd841afa058aa9c69b1a44988ca0aa1f2e2a4804334c581c96274917e99f3098ec0cc8ec29ab4709aac9ea68a7e9a5cace3f1725010ff310979cb3cbfd06e3de92119c9f3831fae082e3879909c50c02c71dfb2e83233e1b6f43c0cadb38f62cee202963846ceaf67fb202cb1e9674b19abb2ee12a57e757bd8707f150350b4a52988de0ff06ecbd83561cac68454511ee69cad9b752aaa851cd63787db35e6ea54122436a4c3564c2058640dafdb67a0202c522dc68d26233e1f0702e8150cf955c7fc1557cc1e90fe3bc52a5750d85ca24f9f83a0576994a75a300aec370eb29877b107c8e656df2e7b990534b0c1292b3c4f5037f08d9a7e265fabb8c0296534b59b1b364f8e6203173d67ac9805d1011661750192f3907cd633db59a5673125d4f7c323590187c9b0e0f043a307e83fb9b22722334912263c16177a9bb3dcdedfed2bcdf53c5d4c32c8522d75f76d74208fac5f80259d1300bb2730c86b93a029db926ea0f0ce83f45347923ba25ff2727ae66971c9c2941ad5dc4025428db6afc1e7b72d7befcc6db16976584a2c66ae1ad676f6245334bf4712074155800dc048cd340c0dfdce0585128d1d53e0fae1d68229c6d70542413b1f4e5a4b9815be942c537ebf901d98158dc970a00ef63e77de541172f7da53678bda02c0aecf2c121a40bb9a91bde2e84442177e397264971689c0240adbdeb4e83aa18eb0b9cf6b3986c8a39865e6949231710c6e720cd05a5d7dcba28e2ed5f934069ee0b70bbdde8c7fce7d2ea26825bd40b5b3a04d3b62c8708898637e613a22e4e351377c95e0851ea25d85b9276850852e80b2c69ace512cd2463493a0eb80b91f2532cbfd3f910c1b2357a468dba2e7dac6414ce295f36b5bce993ea757d450f78950c1040ae921e1838a881fa99ddb27b8de1647364cb323cc77febac0c4d9d400313027c21d205b02820e85a0942e9e4047d65b9597dc26e14b95e3ea1586740e113e218772e28c21cb8042bb68fad17caa1536f8c7f54d816a02ece6e7b0bd09d1c487f32e55d7c200e0d036b473f45ace576a1de4007b639f3fe2d7e8d09e63e6c04a0bb682cb80ca0918b71ff16ae4e94bd4b2aa600975ba444477c55798d321a177506d5861c9617458a73d153b67857c541f84aafe698cced7e09c6600205f03ee14646461c63527d70843c690844eb7178b591122f849cf1a5fec5bc0f07275d5d102a440e5912f2f75b8ac5c11e65328970e9fd410e55cbf4ee0850daa696581b943869be35e431a472951d7c145b5d6df832c61e5f1fb95428764f2f1f1b13582570f19f7f16b1c275a33a04b370ff5e202e7755483fa0db6bc41b142a431a7ce87cd09d41154022c7e36581e3cdcfb81cc9c20aa2f6a6e362c766f4aead5719f2c3fa95ef972bd1fe7a500acce6c360455cc1535692a8d34f030aabd67b52ce855d67e3457b2089e8d12f3468c05403f414f580b41a0e302c1b8894a695b939564858c5c7b7b7425e1f8eab981acd58be6d2094cc98668ceba21168e6b6bc94ff870de594f041490ac3cc09e5fc80c818c95563361e5fc42656ec689cf182e15e4ce3fd8dee70c89afde7c072f5dce544652145e04c2c9f1e31c02299390224b68c4b44c3a1bb5342cb491900d0983b70594c132176b9dcaa69271799715a01ccfd72b9e058d9c991578fa81aa50f541a55db386a327fa6af400548ce9c2fbf495b9ba520b5df6b4fcb5369862a0d4f43464379c3fad7a6802f75782c5bcc9863c5354536f0a4c090951511415cb22d7d20e8152576efd513260582c2a1d8754d61515fd25b0d4e9ed72380b9e2683e62a3a92c5efe570b8995fcc9d620c46f2c1578f3c7bb5ca4805b3fb7984e87ebd06062b5b5a68fa3ab60a0dae27c1b5851e337d0f9e01a1f2d2eee0d74e092ab282bd7ebfe2bdbf0d419a8e61c615fb8a819ec759d4c0d114f17306584c9ef46b16f7761a1f0ad8973714897e3ff8632595af32aa4839d6d7901e103a5f1ea6a5153602d2d416128e927fd978eec93a40d438e5bf6bfd535dd8a13673f5b880a9f5ca5734526a4118b2b53fe96f8779ee452b36d3b98b08cddc18e2ff0fa1366003ea20ad894b4bfc2d6d3c73f7d4ecf32850085c4b6a0d97e17ec7e2fbe12269bb3de3d8c8d0d135de8e9f97a5ba007f91399e1b30457beb9e1ed01d2c9dd9903ca645f022d28b1c29c4c80bf65b404c16531f3ef30cf5297c8cc81e1907bbf29c463fbd4b997fce4a5c21aea9c78e732ec67c3f551734bcd1388ddf7a5a7bb1c10878359805e605fb4fa3bb28aac12ebf267927fa7e776656d325f74ba29da5c2bf3e97413234efabcdb51d309c362bae4cb685612ba04ee50e7e9fac94df9445490117765325d9d9cc2536f9576f79e0b607495e3e5a75285a0adee504b1a78e65edca0862f2852d1fafa4888d098df513802120c26800c93d2d729a0722ac8b1d43bbdc9c61b8b0d61551c6b3ba6d9c76844f1da1399d459cad53f660a5b08f37bd7b5c60f60a85597d7f9bca4a7928ffe83e1faed5b40776eb974403f42477c91c664ab12a5b8625cd0605719f1cc82b9d18a2f100b279901ab9f66eca211f23d9c51155bb0d67bf434fb9a5e52bdb13c148a4b4f5068124bbb1fe18d3efb69612cc1b021d7e7859da69f8af25084406f289bee11e19bce8fcda2beaa94e02b552dc890cbd8068f04d6d759d916c1048bdef77a5fc3f3c1df687a8592b0246f0b476c820d7a01546fe5a2f0fe1d450eb551a492b65eba8840174351856bc4b4b1af8062263f8685cb81ecfcf3adc661ca2f34dc6089d3d173092c634266b9df1a8650e8912545984129363f4db3caebdc130a758209dc38777ad5e759d4039ac2e68a3149b1fedf082deb1b436284a6c557a6ea1b0a3ac7a10206e4d477efeccbfc3004ae888dc3eaff9ee62b8248c68e66b66701c0da40151b2c41fb02b3a19dd0c40b30199bb13db63e566fd6af3e3812f469ed23341e3c0972cc6e1ac0868ce0043cee79f89922b14d4b9d40a7b4c5ce184f5850d4a91de0e6b0497598062604245d60b27a37a27eb37faf6e801a04e89b12b080f74925705cc9107f29085bae3a8c5d368ac4e1a76bffd6169eb1f314100acfc4923303f72a1425e9643ffba82c3da63b90ed822eb405711f21ae1915f8816051ea837bd335bc10c6a3aee24e0fa4c1323cbfee3062e1b00516f60e50ea2758f725fef3a27ad16fbf95d7958cdc20b5e81a44aec8935f4d17116ab8f59a24c74a55746075848b991017651d3d1a6b9e6185e591c0bc206856988f2f522d132cf7428c2b45b678aaa38ca5d2e99a80303b95381b8d51450ee7d40c4f18cf6428a07aebaf22533ff86f8c43c192df3e659320c71826e4060e945c449d008113db777a856e9c89050adc8a96765a74fb13ff8853936d1d60d98d764d2d07c0dab6f795719c8283d048424295effe0711e12214489a970351b55644541beffaefc7da8c1a86fdde7deb735ca5fe3123ee03034be9bccd4ff3df460a9e8f31be0dfb49116a5c3dde10d9c955479994d3fbbdf7051aa94da6ab5c7391dcfd2903cdb350e40a2bde5710bf582eeef32128e3dbedd2b20204bccfdf0b57754169b31d72825dcfe0493f17206e6b06103c3091bcf693e0868183eb34f80f00763e06f04e2e60abf92f5f8103e288ab431266ec594e0d80c68dba450c6ecbdf7a0a84d4dcbb9bc5b21866364e0cbb6426993cfe5dc03a5e61a6d62805d8c2d8cc44f04b4e07fe2b97f0f608bf43d47b1678e0ba9f5095e5340d492a795a0d9de9c398a79a9caf105763ad6f0c56d492989fa088729fc1d62b2a31070f3a3e7c7926761884194ef95076370ec4ed8bb2abe0fffd4d1e8fdc7bbff8a912e552d42e44e91bc82fdf8878098f5eaf1bdd0d8e3f4684e13a778d0c5fc33ed7f048ca2f9a82e9460767949f6c0ee7fef146590ecc664b28e28c21e309a0b004974d44447fd1ff477ed9403e08f27864e4d354aafe3bd8152a89610276fb71342a507220493e94da9266d2e0ee186e0a492695111dd17793594b90709daecf592826fa480300293785fab87ad630c430163dd2871eeb1ca218d6d84d52d58e8ddd4a6d510444b90db32b562d4a5e76a84c281630cd80a3c41468757100ddde4d28bbcf1282038984171203c268c697741218ba0bcabdcd5f82fd377a1eac1e09f9524ff0d72a8d43e5265767cb79ee7b25946abde2ce090bd38f907ab823cbb3ae3255e9c1bd27c8f4173cfcd6ce1136e908828d1199cd6553dcbaa45cdbbddfae2ae13270a5e15b11dcfd28c9f040b76e1d189879138b807a116b00abbd1850f56db94a9b5cdfa1c294ac24811992e510c463a31d8dba010494d8b77700b186677e94fae402bb9f69ce396320c1412f65641314b73280580872468d9671b0c546ddb8b585ff76210e0e028a98132a098d1127b4bfd7b3488a129bf3111144fe1ad63e085f9e63c6b6022b35a557a34cbe8f8d82c58d6f015ad29d46d4eb607fcb4bc102e3cc726d153d3c0657d4171dfc39f3ad31962629769fb432aa010e43a5c30b4e71c0d8ba124355c5b2a111280037569a63eac424322c432758f7a007f0962720ecf5915ccfe1c01eaeaa75969ec2fe588e808da49ce8135f7d4c51d58ccf37a9b8538576afe6218bc020731ffbbbfd76160d546f570d58bb2a9131090dd2c31967d66a471c1ff64f939e38dc7b4df8150bf4be18b58721ad96a177f577c3b5eae7a398e30dc8297b476aca74ca8234b076a5afdf339381200b8279e92e98bdb4cf4c0aa48af634ed738210497e6d000eb99cbf1ae704071a6c431c9abc8997c23749070d53fe4fd53838acb06aaaa79aa46af9c564305718235a758add978bc65d315bc0d01e5a2511b4bb03c3715973a4666b9cea3cd9b783e86ac224443eb948c2baec8c99d18300410cc1713b92202ca86f88a2f877128a81abd3b08057c7c25ea57f2a1f8d9bb974ac05c7ab0aa100df66600ffbd3330ce099be1dcc11f846ba69ec08b0c96b6649ad5fd96e11634260ddfccbffc49665abc458943444d0b339c4c445e16e114a3b6ac5c498107ffca4cf541b30c684729c16adaa0dc2460b8aea248bf7e19843503b9fec3bf7f1c6284944f6fc63016e6890f07fe630427eb0cb471f191e238a43f194941738a0d19930e8da9a553d3c681673a832e013911cf85407f0c8dea55c91b47c01179f94cafa55f19480ca240c7b5b1f372b6e405bae9495d32e36b698dc2ecc60353d114f89ccf724414c2d28fe8cc36308a5e6d4116da7f59aed50d38ba103fad4bbdd6f183ebef044732439fe0963305ea5485022c199eb4241ebc6f89dfce44bcae8f927d67ee80467dd20e4ce5955bd51f62dddcb617d399c43ce46621f6e5dd0a94c8c1ad655b532a43bd8cfbfb783eeba32618e662ab00c0b80952688a45423558401a1bf07c8f67ccba199db609c3c1d31db6879fb919e477ed6e940020665062a84ccada77f96af76587e404ef4bf562ffeafc45a14dc8b18a4129dbdde07655c940c27cdbfac313d04e80b032574a434c66bf19f9017e715220ee28f478f748c72190e61638fd5e763b5fef74318115a0d85c9264f6708e002a50fb158144a4cfc23658848771d8fcebc522942405345dfe00f9b6e8c19f387b13470d6ee6907606300300008382d4686ef3de62517c93837f2f60fa9347d211f5b166fe4d7e531c05c57210b88b29e56c1405cd6c9b153e3f1bb5c5703329037b2b254be23223e68746dab50bc1f25ea87020f2d95c818f21b63f386f41c0fb0dd8254155b81dee18cad39426ca90e4a535cb9e76a27a089b2c3ae6cf104e69ab3a99e937f4ae18940efae603b8817676e4d600c46d4e427dccbfc714dd5589f65b645eb9ff71c70f9c0374304ab21d76e386d628081d98f0b3ba608ede102340af0e9b9ef8226a8a72730635b0921a25b3a0578a813cd9db37dfae130e929edbd48b44d6453c29f2bea4c53dc358e8ec9a1fab04808ac103f8cb8fd59aa9992f8c241449b5efd1852f7bf1c9eaa003074815f9bba43fbef249973c30fcd29e5d0de69a4f6b6ec6cc12950ebc85d7ace1d0ce29d7095a3ca01db64cf02123814f82fc5bcd5dec1eb06582886a34249bfca144872ce269aaf50ea4b7e71ece906d7ebafa2a2bf81646f4b66a203e1c8b44f7dc48f2b90f5e7f728b3ae5fb89b6b4bfe90c0d3fac21ce81545abfc3f0d7bbe37e209b71310c9f53bbf4236ca4acc586a8a70341397f12344cc97bcd25f6c3ceee685eb9a35c0fad93837ca52af8ea92d03221df9eed6ef563b23941f87a4fcdca2dbba87a8c2b9cbd05a106cbbca20e16f93d3c26415e84d970bc54ffe830c866ab4549a12c23ff84645817219f91fdc101048256ece05958c0ef86eb38eca2b55a28e397904a0f8e0b9f94d39d49c689367610fed215e50d17a051fc26f14b07743aba98a218a1434f2ef82cbc168d72bd658550cb8c2acd8f3fb02ef70a9367ab085d91a51e8e5c4f2424ab56f0f25382d4c11903956da35da763f89e4da7bf91a9cdfc7a54ba3cda529b7c04ad2bd23410288075ef88b13a9cc07e5c8ff4cdd79d476666c049753515aea4c54fbd2bbf28020b6935e6b3b93a106d310cf407818ef8aa6b31df4d6d190dc20094924513cf488e1610899310ae60cf9946737833a074822250cde9288cbacaba54b3e124c00da42ec163955197ddb58e9375c0907220c93d4113ee729e8d98f6a34ef8c6713609972666b4f84e10c7f575742e1f6f8d641d99524446eba86e9b3e9ccc180ff0ec3cb40c8f2d8ef8e520e180c289d3c4ef298d6f86e257cfecadd1e4638fd72f611339af20bcc7b98b2d71b55a6c2c468412487946907f476278c10500512a6bd8386e0ef261d9b9d3f4010487bc45823b1f36ea63f71a06bb1a772f14a09d9b8c40d250b6b8ebe87d0c7292b8890da226011b96addfd8a75a04621a851d368a0a1879e22436408d11e66e984338db2662e48130ed18ab9670d824c80b189bbb74fe25b90258a03ac816cd00a59566ed2e7149ddd341bed866deb076fa4d0ddc32c0952815a633eada34deda23925d3949eefd2c296d2b2b801d502933445214d79399fc2df95b7d6a311add180500d0eb98a965cdad919edcfb2b456c832560370607c0ea605330a6a25c63a700760279cc1f650df6bc8b7f85776dc6ecdce55b4bc62eeca940663ec638412300d0fea94ca33064cb8f4e022a5fc8ff9b1a71f15227ebfd770a511be21f2aed815b9d27814f88f35cd51a197ee29273714c2ac5ad8f75a898bb44cd8571aab8f69cb2af4db6676db7b2f530e362b1c3bd0fb67caa48cf5df5607e56f78083e7137aa97bd7da13e3824bd3c5e292c81d26f7d1078a29935e05f7503659aa56271c5262391d6c8e2a08fff86c22f6bab3f3c1efebbfe42cebd00433f555e517ed822fdbd34de4240b79b5c2dff9aac290c3857463180f21591ac197e218244125fde54dfe404420bae25f8d781e28c9ea74a8c2f746d428ceb70243577eb03f8321ff9abd41e8e6e7a8ba6b4a3bdee9168d2ccfa499303d53df271a15245f58e0ba28b94377ad046138e287479d9ff58721587043af7c89b03210cc1e8249f3dc5f45cec07861b08d7f8ae5bc094b0a52c2dff86cb1bcce9644477114ee9b257e4446ec63de1b488b155a8bd9955425f6be3699c5d2be7ee10a4ad426d2a693d7e0ffc0e38d2623cab55f28f05c5557c1da59896b38c07f89f141a4336c304cee5c289e9a05581b0ef813fa1cab7026978b74413d261b75f6114aa1d5124e8cea8cc4dacf091128675bad07d6f2b6b55ba182d57cb5eaecec5a39c6479873e0e37587fccb6d07bb0544437e2aa8f2e89a548e87d3329626438df0f8f8becba8e0c41f45a1e4fb5a5444eadab86d8156b6319654cd1feebb1289a09664828afeb4adf9a5839d01c85a238ee5be9a24569d7146f5c5a9678908c3c4941c72b6666e42a3e6f4db434198645b9686ecafe52a100316169ef4fed55e036ccdc099afcf298d73bea505df608abc60225504566aae2b83d2a221de7657579748efccb0a7c70ba4a6d0837cab4d58e46f9a48a07756b53a1a5ea0f7635516b907bb64881c5731e8fff60097fe4335b275e56df9d80d97a7739606a23efe4fa7fb2dcde63ea992acb3491851c1f94bcb67a42ef5f7844d293388b315223cf205409cb9680103af2b223dcded5b2fadbd0d339c75bf63b19042aa77869f1ce4d54932f4e9b7a864a1ad8fe889077cc0c885ab105d6b4ea87eadc70793f21aea5e5418915951d3e7f59783e9a48007b1dd3ef033c6140a6b41ec4c64f715c6343507d05b88110e024350867ce3f6ed080e90e6d7f9a480556a5a70c5d17ae4ffef9011f769c763cb0941087965d3ce4b4cecfa81dc37f6359e4dd370bbebdb1cc895dd53a3b36fec10320f4b16bb39f2204210ab0ed1d5fc01cd489023914de7aca27c8891dd700cf07bd28c9278511f3fc2b9da833aef11cfc46f4b3cfa147b2977b99930c98f359eab14c71b750b8efeef8265c868c0d15d5175e1f77dd22ec47efb08c44930ce860d12497ea7b9ee5b6354be91e6d0cd85ac4110cf0d84e60855c5cf5c3a60bf42c1034209c8508d516a49e360014f62be407adb5165fe7e6d215f9a21ca0f1957a92d3bf40bc6687b37e8b90347d3e854fc3d06146acc42bc6e512bbcc7b90fa3fb627d5e5a70a3048dad8ac721f5f3e11112cbdcee5c1afd6d1a31d0a81c91ac1d6a50eeefb966737e3ba4728275dd0a300573c37a22470f859ff5db3a31ba6fdf2e5522b143ed2283e5e8d29ae429c6b6b81b843118e67509bdb87739b1527c63491df3cd2916c69c5f7e09c7a7e6cca493eea08c77144706ab7e5f3e1aa8525d3cbcf2ee1111ceef895f16818b06901c588cb4f71680c411c0a6e7a811ee6e71b0f5096304e6b2469cb131c4ab174fbfdb82d0d9755612428bdb0f78f85fb7dc4565580a61ab107fd4cc0b9ac381a859a8dac21d182c00efc11d842834289f2c3f487b761447d55fc6732427612a66dcfc6af7321812de5277a3c681f7200016c0574a107f1c133dd68d1ce966ca0a5d99fe1ea1a77e61b0aecccfa1273a46036a1563639926abd89d323eac27de61ce714c9b840849937535767dbc79925dece58c574bf4244707e91aa2fb9f26c01bad21602bdc064682a30e3f5e5a48c978d2c0faccef90cb904fd1e7ff7b13b67adcd221e6fa6ba7aa63d05190598dcc8e0a1b60cf58ba335aeda1d983b6ebedf045d4e77d11c2270ebb158c2655ef7fce3cc45f39c0a83f3d0e4104d0c88b71cc45dc235aabfcc9cfa71c115a9161fa08a8f946c2d205c023740cf063e0041d98a91684f080c5fdb00cf4defd83cf92a813f93ea01e1e91e33d8fc60769c8274b254dca39fbc8c2f2aaea89d0225eecad34d1886bde1d9066959e853474a71ac6fbb44714026c3b5af802c9b977aeb83338d5479c9d7ec01c86bd357d04c80e4eeed0564ede4b2444a99d9121b8a823934c270906b48ba2d1b35e0057ec2608338c70db90b3c77e5548a002d88fca8b68d07136a647de9d2b94a0dd8f2323c60ff2185237ba07efd8d49b4bd809bcd0b22a864ab8a279cef7cbdb22e0ecacd8cddeb6fc8fe24d94f2788d91eae144874a53abca9e1c019351654663f5ecf40e1b256d13a3012fff19af4c119a5d0a3d256c87e05dff323f3d89af38b8b6975abc69d3a5cfd70a78bda5488d353a97bb20805262c3884eec00c098c9472a9c17661c273e05d409c1d7dd569a25d0f8e85859751609e6805a8079169517dbb8cf82671034c8ccdaf73f8107d191a524d23bf0853acc1ebc400305425371bddbc230e35675770b0c5c27e0d59e294db4032b0fac21c83bb971c4a57242b40c14d088f9b0b3f6fd422a951da18b66a38752273df0419a74579e1d18a82bb8d21628ae1dfc1744deef83163062281099fba4840177acb4f68e4bc97c249f4a98c1e778eb63309528785e6ab1bbf9f48f1f155b0fa44a76e764e2b678c4e3282e9d386ee6d5e844981fc0d068ce827e092c66d7016f28260cae6966b6f343a2721489a3113fc9cb5a7b18044a2b3c883c4a076c01cdc6853036d4eec04ad9737bd327a350df1fbda99a86da8c321057903c15f54c82eb040daf29d310666013fc8c473b3242f8643d1fea179e1a3539d4bbbeec7579da5f2d0b4a0057639d682b1cfb7fca089c02b9ff52a68242802b71292926a7c2206572a1903acb4f705704de9f6be532ceac4a4b30014691d0a94c12096d7d4659a86061fb91228c073ea4b0ac2364351573000dd3ae49964dbbd915341d3c548430916af268b05883b15a0cc851bb7e755a0325a0b12fbe7b2c725484e50ee3087ff80c2ab5730b91b5e5e2bea2d2823a40a68745e75d0810cc1a5e9bb5b35c1c13af83ed9a1dd43984659e4acb80bac93f55b2efd6721635a0b3713048288f2a61c5082af585a61c621046c37133df6877d84e79f9d1fac2490de14c798440701c6d41a00ea2996e1288467cf08a7825e88ae82358fe05293ae71f91ab4ec498c3a157c01910a31cc017e27af57f926f1b4a11273342924f7a0d08a48b707560adb1ce9ae81fe0cbca97900b771bb5619a88b133157fb585ebcf264f26e0246a29126c18cb7692456abdfb48c1a102aa54ebfd5ba3ad107faac1bbe07c8ac8c4fd217e29bf142ae7d386cb0ab4e560a9db9bbc25effbcbfb8f35df51ab5e76fb81790176a6b84331800015895b09e8d8caaf5a2fcdfc518a2bf8a31ab50ca30fd6a28ab11f96ffd8a1be471c34613e29ec673cbe802e17373373d5b069f4d8a8531066ce2687f0c32e0056c1c5fdebdf3d163b32da327c74d45f97d1cb31e73596d0f18a3a0622914df5d2716788b3831cdc3bc4459bb9943150ef090bfdd1742ac89b472388cf746ce7c6e6115e31964d4d22849c1e50edc4c42fe91cc4d7f2ef3f0f66898183514e2b07b72b73ef9a63313466878209f0b51901ed12442924d60512bd10562b500965b18e8487c73564df6a393e1ee64099c13b880d3004e31cd1d3bf0043a9d2bc52f6875d7bb22efb00e01bd10256817ee8c7d02935cdfb4925868c2e556ff0a0252c6f80b41e3ae221a845b2e0d07188a6b1f22f533a02f2f13bf2fb06b317577764c40f057808b3138e21f7b430cb79f800f16f899372296268643d6c8aa884beef196ac118b2355181c687341856628a9fbda1222b327c99420912bf62b838599195d1763c1f7be40aec21581be1a12b2d7e267811a8dc4e0cc3242973a15308c24732306a05da8320926255fec276050ae29782f8eb2aa510f1d55d30682edef929e6ccb5d0342f4411187407021b5b11b61a4922ef93e94ad1abda2fe548c4a81b79c3ff4dca1db0984345ee5a064d070d09c51b7a89a423e97e198c75d99d0363112df24cc35c051bd577e3e6f1c4dd0b8d66a5ced9ab0648c8dc6ee700d445c3c3e5f0368168ffcb0a13e21202bfd678a94891c7be25fbc7f8b9645fe645c8e2de4060a0e496f2e1627673064fcef19ce19e313c1fb3384740fe77dd6155804e46fac78b905d906ea28b7771ddfe2cfaba204eb48d65c1c33e2c93830940ae44e5abf50e0c50e67c7b6500dc981f7831cc7d421a9cad4119787c1bee501df5b3e419006a21ab5cfaec44c6b28bf082ac31865784d732ad407974a6b9cb3fc788702400bb233c8250dd636e4cfa559e73dfdff531f7d151aba75b13c93d007702759d4a0d5c7053d3c1f84a46dfeab5f1782e766fcf738eaeb2bce8edee1a08431aa0a2c5743ed412a0d68208398d503d0104080c86bc3cb4901448a1224f9d4a57e49cc5b06a2357ca97f13fc3067a72dbb559af7f347983854a28597e0640619c0faefe4e306aca40c6c836b92aa3935154a7d2387994c9eb371374eaa0f64c651ca76eed4d2683286ec2a6c648e9e4b1f69aa91fac2c083824a1381c583f369b8069c1df44e799f0ea0af8fafadcc2c0804db33a7fcb5e324d55f4271a63d70ba7ad9ddbc59e440316f1ea85ec74e62d1d5a9200d965145f1480d93791ab88bdc71cffb508d587e6636dbd7c447f5416f087196abf41e65823eb988cb9341a9c84549ce4856d4d35545fb1936c8003f2a637751f408daef9dca9bc63121197e57c1133520e770a5c19795d9019c9668a1546f7a318b4ce74a0c49e0f6363ea597e60c77db1c54e20c758c58ca1e30c27a22e4304be0207c5d18d246253521d122c008222248bdae69217f5bed647a6b6df7b43ed3740c8f2cb7d8239e0b0f2fd82253956b92ef29f123b034040c8c841b013f1cd23f3bb0b2e595d5862b560d52fc24342b656393e7fd5ec1220d0b97ace46011d61c31b2cbdc87626858552362dead4bb43feef46d346d8133c72f9f09786356bef5354ae1d1720be9e0e382cccb9215f514d52783d63749d5bcaf56d9287cd287230339627380ea20a0850b5cdc3c4cb413dbfb493f34c827670404e281f5d34b80e1cc129f2e0c99c6f3bcd475a38c10e261d22460f6c68e59c50c328ebc3e069d1bc1860c69d3bc3f7682eae08874ce7f8a7022e130158eb8488c5af85590e35a53dd5fdf54d716e3e7713a331151b25386de025b0eba5362f4b8297d66ad5e6f152e9808c7e921145ec445cc0712379bc324936161c6f5cc4cd0c353c12683aa6a424a502f3d78c42cad6a5f4b7db57d0c364e32b3e8d8cad4d01c6472a33f002257cfd7585af45a41084f932c2dfe8acb5b8822b79861729ee055ba89b96fb025d7a6de8bd9e6297dd89fd206e4fcc209c29ea187d8e496333d2789a8a6dc5e20ad045363805188fd2d43aaaf9aa99d066429916e81e1227977e065a6ae7f3b85ca5069a59dba92d96ab00fd17203a110f5b9892251fa731b8a8494c2818734315e364e8366f66f69170672ed93283a445207fc617244e11ce338ebd22d5d7bbc5b4138e2b9075055f91b5a28741a71c22d857e07865504197f63c5c58d9b4556aa6b742fd0109fef09250c4c4cbd037a2cf9a96dd8f731ac821c863a5130bf5e0f86f498f5d22554bf1552200f753ec72111150b412f80e2f55aacb764a9234525c2410545c445efff46ce2f2fba6d58c3fec874e694563b8350820d4af9fbbe6ba6d14c983b3b6716b8f26cf73457fc576ed54206e8eba3ba6b25b9cecf347d9a06edd2a3dae8ce97035fc246050206568ec815dede61d503cff6e9b7753604cc70bcf80404082eaed037f629d3fbbec371e7a5a077ca0d9b50c9d38ba11bb4ae6f28032af931d3e183e4421c39c91acf01a99d6236755b914b8655d29fb86c0e543569ffa995eefe52d2342bd951147cb1a4bcb4cd4d69d9d080aa7bd07b57e15a0e0ffd8f96dddb49381bd0ed88defc964af041207569e67777a449d6934dbbd359bbc45a4b8d218a8e9e6729fcb26169f74204145ab9e2e67f9ac7569aa9e70471d0c367925e9932c564828a2ea5c91f7aab2415285edd2d224671d34e54961a5b1200a25754824a3fb596b699cf108b538040bf2f00d43e8e158f36ce6a77079cf78978d94735332300a5c663e441ca07c7415f7582d52cc565fe08186c0939a0a53e596b62a424cc454683919280a218f8e21d8fca2e0a0b609bb035413167006be16a1cb9eae2f76893444efe8fa26e0bde54e54cf682a6f91a0d2faf7c4f31d9567972467452d2524982c6706b242d35989285902e7503c14fabe5aa442fef51a200eb495490e6a397dbfc4d594ab7fae388774a3a775a3da383e650521fb5936b40b0beb12c1b87bc088328785b80981f3510da3114697643df3d280fb6608b84ef69a7a5904d489334ef150a74ceae6ee6d704ee2e9baa7f4281fcb16f4bf315774f49ebca369678186912d871e9cd21f8dcf28eed11284eec6be42140e551dd3ec59e9344758468dde9e9ad00caef1f03e4827aec27e46586a51de2d26fc2e17c470ea67463130669f0c6d34224acc32458e2747edb44ed167f33fc2984ae8d0e75019673cce3130e6d4f549055ae941a0c6c464519221f73c728bf376bf0ee4e680da75e910f0f618b0a3c599f425f67acf2c31f6fdb95b38206ff8eab309ef755f3c2c31965131f290fa771851352125e7a795c08159c6dce4894d046aa1b6a2af7c19d8e8431c5ebf29997fcc22b7841d2039e5ea8c01195103574b2800ea16fd15388780eb1bfb2bb052062be6adf61884f043f871f9179681b40f2edfb9ae04d4f09fc1df56daf2a5c651f6c1655666822177b1f6c27b3a2bc5d5bb46476eac65e2be24e6095e162c138b6d46112c54c97c18cddd5069b836aadd18c1112eafc6ba8a044e07fec0b57f67cdcdd9cfa478890ddf61ac7b04c5abb47bb8525cdac4776185a100e74d6e95b92c1e8872adb07a329c9a7442f5ce4895dd1cfede15d6898a54b063d8d87274fe88b3149274b56f75f5f6bf5a6ca9999611bfb2e114742f96c52600b11a4a4491b09f046f5dd3a3e546f39d177f561aa35d884e49af9c0e2edf900849213edba22e252d5767ce0404f8bcc4f6c8d974214efd982463a94e769bc95ca00bdc88a5f62f5257e62f2f5f9f5d2faf8af85073b11a176cadae0c413e57cfdd23803405502679c8ce929c3fa88cdc3e0db896d470480b1cb1b4b47be284049fb79e04ed2d7396e927fd383716a55d6a9a1443fad25ae320a87ae51c06ff44a683636ef354d841ce451b79e8b5e896684c22ac6e54bbe6f73a01c8613cc80d52437bace8c0ff8b7576550ad2043223bf59fa7a000983d56c3a9eaa428b73e498832606a1cb8f3f7fc3825548527004fa224cd934c06a8d82ab3809418f8adaa2892395419acd0338386b6d1f99754625ce03630924dc74575cb37ea3b4151951fa9b6c1d3ae4bf689489fed48e86078b3827d37421ae1d146c4e9569bb0274419b72661c39b43376b9814ca79f2fa7b28ed9e04a3d498529413af3b51373c16d0ce9450d6b74137e12043045b530713d5106fabc45b8767b30c11ec4c51aa224aa6baaebd9950afe32c82ea597765fc020b567f681c7247275e748ef440dcc1c3463105d35b2ff94c83e1a73d2ce7d1fa0246bd20bbe58a97835d2830278bb9406767c80d8db5d849a0fa73d95e06636009b583e7a712e7627a64cfe5866efe2176890413af4637fa870f6340b501b0393a8c5e4a71c4e71f22a594f6eff9c74f26393e865cde5313fcb432bc99f000f1c1bc69b32aaa99c3fe0f1113f83ecd5a65915bc41db9799eb6b1b03d0fad3c6be77919ab232e9b6947d02ca6e3350f57b46f4d0068c0e319de44b7823b5cafc24385408f55a95057109fe411d2d77421af97c490198168677da6e59d8c469ec699bf93040b851a50d8f57923a2b36b25c5024b84f7225fd6a9c4c76f389dca07c64fc1987d8ced0694e106d58ebe39a001e471f1552b831b5f050da9dc19fb44171265e8f0e1696166579104b09bbd4ae41efde8ce8cf49f4f0a987f9120962c6123d25008387c6383b252b2d89435dc002017675f5efc51b486213c6608dc104da9ba66fb31cc044229fe9ac5b3db9efd0de219e511893b408d3798bb57215137641565d5ffd5095841b7a3ba6c4502fcea53910159284c2ff910612f2fdd2802d1441b242a0fab9d74a3fce6137ba649677933de48ab67d9ac2ccd62a31844db8db2016fe6753f2708e37237d8a9beb85c71e2d981327c33823a8d4207df0b0121a4c38badcc72f8f650ad8a3f1c964d61410a553c7fd5350e43063a438adfff5fc929b5e6e1d0215fb11cda94fd962a69645bfb0397f2dfd9c1a2267a56cf6c14234184b7365e5b880c9ee6fbd5f5e300502344afef7ff8add0203ca4c7e021f0901f5b79f2c99745496a16dffaf962d4bfe02aa3b4fbde7af311ada018384652168fea2579738ba3daec9a2a614fc1bc6bf633c5e4b5e9c4c29437885f927690cc55887aff87bfb58dfbe18f087a0d0789d4a454be9c37e733f6a39b1a14ddf0acc8c2d4c1db96b3a42511a01b3ab80dc2c50265493cbeab9534c8fd878902b7846b72cfb4434a63b67bc3ceba0ff42bb8c552572a560c04e9c8b76cbda952e23031faa0c39db5b158833d958451a9a6e320729f0b40c69dac259afac3ecfc852d05dc22c71425d543951a6f592337fa9c4d51b0306ecda6d23c7a6086fb5d96564dc0f717cac82b7679fda27bedb4d79c118f66cf64873e374023a55e29389186a0e1b8f64e5baa7f0894646129fe468ea9e0b39ca15be2343988e5a85344edd1fab58be7d8add79eb726a5d0544c836029d6374dbc85f93636cdd0fe783485e42d708c13344d208a9eb57a0a2bf368f9f1978efb3fd57501e5818debc2d3281d176a029379b0ef90b29adeeda8249654530ab16f489d43a6f8b04fca74d48d42d44752e2206c64e5185b3f8ef31667ab347b0f4908c8e963fe49d5cb246904ec6bdd574d016e0034701acc1c75c86c1c0435b9306473cf8a413794fb46840fa559adf12dcd7c3e09f86b7b17c912afeb705c395eddf457d125bc23d745674d3b7be5ba4ce3b9464b93095ca11bdc6a65e3e3470f2c834caf76ebb588c3fc654af43cfde26a0e0ff79626de3cd93f5f8f3ba5fc462c6a9928914ab0f2ba7ccd50d14d4e567e2e9b4e1e50f60c8151d11f962ec175609bab069b70f93b158618d416064138b4eae630bade7ca82bbbff0f54180b654ccde3528a950eb43bcd0cd30d5bc488fd5123ddb8b5dec0f395d73e67ea618a501f0a3d9a31d04332b92e9af355373c1913dd5a98013cf8f2637beff06462c9c5ccbec42e3237bdd7347374d9b3b16940be20ff8ca6119368087edb207bdcd6b00cd245d9447f9e92e19548e9fcf72ea2a8a6e1ecfce44af6864a9758b5eccae389e33e742f235ca10007ac79942f4cfd1e200320af1c6c6938516268d3823bb20f53b8a0282c6ef2d9a5dc3f0c9926685c3ec93d6a488a6de61b2b57271f7b05a997c20a87b080fdd8c8980c4e600c37845a4db55de43b023e646074816fb68e431660bdb72db70c661cee0888a9db65fe98b8a1d574b7950b5e9e51b24146007a2afb9a1a7c8352a10edd1fed170c93753848c0f70e708c21b552fbd9255ce646b2ea69e1fed9110a015633d987ca8fb6b99fec50402a5dc40d8a349a4800272867b9ae501fe9a94fcec2e7e86a209210d9bc2aab338720f9e28612ba1ecddd5cefb91c7dd5902f24937365028a879e11c17a227b2b38cbc6d2f79794ad52eab5d1a98ef13aaa3e22eab5db0c753e1ed856349e3a07ce6389722be9fb40077ecae38bc3b619617e61450d276646e5879c70c1d9ee90206960e470fe5da9fe925e8adb5672f3a7526665807f031d960c749d6cbc73a0ee040252e87ffaf36703e1bd29e1d540a824631f2418e7ffc8f7dd35c9327d0fe1373d08755ba1f290771fb65ea28bfa1d1ce24551743ea2f5f9e4f2aae934d870afae0dd1273bef0c2d6e85de10d9b59b601d7e3d0b1933de33ae204da73b335c366dc9b5188eeb059f7a5c6eb2228d41903c0859b0ead3d901868274fa4068e3c813d00033741ea73c915dc548822d584e368c0afb9c8fff7d4650ae2c2051dc60ee3eacb4e37afa2d870529cadb159d725835f05dcd2e56e16c2a14855a8ab285d8cc94af527e35605952cb29ed045ce53ce96d3012d2b87cb5dc9c5940688b2ca999417e43f13d350ccf33f13a8c24599a177032b13fb0a9828bef842959b645bb4be9843ce7bacfd24360f686807e329afd84902dcc15c93da620e0216276e80ad7e32891c526510d878fb53da51ef66f25e457e971c56158744e306d30ded17ba8cc9703f94bc2c663ca5456c99988df30cba8a9cee778d8051fa62589b34b0a5bfed80a7ac5e9f8b8067c0676581436a7046a87e95aa70532b8a9e64899a4a1d79d4598b914dad5df0276cbf5a0c0e6bf643dd2fb9ef53ea8e356417cbe71d05b1b81a51ae3ddb78995c1490f492ead7046b19c5fa0ce2c89175d9010c59bf2695a926bcf96ae79d347fbbb8319e9c2adb28752a6b9c1fcadad91d8d95b286b34326808aa6903dbd806fe6e7f1c1e899571b0bb4e22296cfd7412783b127b1c84d762e11ce0ff1d7a75752e198641454cba8d94192cda93586a6988c8ef74d97f6d083ff746376eaeb14ec5a1d6dda6ef4f261a08acc46032a2bf9b970e7aa8f6a6c4d7857d1fd2290477ee669400ed6f746a3e5572b849367d029632653ab6106db9fd2e639531b9e1098218a4f53473e2bb3d165b365b04f09cfcff0ca8b42ad7dbc66e93900b90020d984e76b5260a7658d0826120c8087850a135f7fe5fd8a30b2ae7b8c3d9fbcff4558b267097dc8eba576a2270ce990030638b75b7d462729830b5b57fa46638c6c00d6dcab8381c768a80131ec9b059b39beb66a77973a406c9339b06c4f376df92b49ef6904e741bbd04417a93b388909ab029f1c5db8a19525995b328138b99748ea71456e7a413241b33241d030ac8dd5d5c4014163707b1cd0e9bc5f1b25d7fbb7c3b7ccc80b57226f47da9cd3aa090ce9aed9b97c99f4b96b177130434f5f044b6c3c5a68bca0835bcf0b8df831fbb12c7e6b41f4dc96f61b6636cd2d67b4a5f804ff482464b9b244922654a29bf05cb05c2056384cf83cb4f3f94103246cb4b5d903f84b0582e44878a344992244992d5e52095e2a2bc4ea7d3898ad39f2845013eddcb896047740d85c169fcd3f79c565229a8405788ce7890f1adf0991b0aa3846bf6fa243ae6f35be130df666a2714bf82f37a2b5e892b89a6b1bfe4a7782856f0a753105bc4901fac7be61d03d608a5ba1d90130cc33014eade7befbda11008a6bcde542873280cc3300cebb1d1aaa27d62fed80fe5f90976a821b06359bcf7db7e58adddba17bb376443947da7c5d5c2d9b4f628ccab04d134bbd429ddb44dbb30f6bc1cba3f346ddab4bb5bebeead9b366ddaddadddebcffa4157b0caa7e3b615acf2e938ae082c8a6cbeb24c4921c6b53056f988e83be8de2c5f9cdb40a390f320366299ea609867bfee7eddb59b356a35ab59cd6a5ab679b125d42efd9ae6477d352d13d9bc5cd1ddb4e9fdffd13b2a84ebbaaed369752ff5bc9c413014baaeebba7c34cdd7ce78c576180dc57ea2e9780ff1835df5a0c0758566d14f7ea233a09675cd3753500582601af4e4613860f04070030e5f5ae6b305b5744c0aafe0eb743a9dacfd0f183a6763a6f9add6b71690795afe7c26d3667295b7bee5262c302569134d9224fdd60a0fa6be050c3de33770d3fb81ca0b5306372440cbf400402652355e04920973349fb80933796e3299a6699ae6bd366e4c9527582854ea89142567cf2a8a8a8b8a429521efba2e123c9dd05ff6b9f8d59b0f9a977edbe72821b0d85c847f9120830d5b506e830a19a03df4aa4fa1de7c93d237e99b660875a25028140a55832bad8ef58c45e061be011df33561cbd0ee017bae39d3ec206ce489f59145318e23a9ec1142ea9c929488d47154a94a6e855dacb550f073b57a900b374de747b221af72d2b1dddc60bfe1bad15123d040086b0e1e3a22d034ae23819e711e1ae673d7f9a85a6185518676e27ed241142fc2ebb9ab1fe4018badd27185d7573f198ce3388ee3385e570d1528a59229c584086aa754723a7215e148bc896badf59215c287b8f9f1136ec4061a7a16fb8b8ed263033e680002dbaf827e72afdb0e158197c475a7c6832ac0a107ff93826b16c130003d2a5da552a9542a595b23f4b182529369753b462514ef755d2c2ca15333b78897e4f2b067b25c696a0b1fa92762a75eb2104ff53cb8036aea181e5c3f2f91c254c634454d2693a99e40d44a92e5ebb85bb783cb5ade9ecea853601d554ca1d28131de386d0a3c23145b955facc0627ec9c2c6831d8e500a8ba1971a60f1caf0b2048b311c80d6af069a3d50433b096faeb986fb7da09d84f55bc15361d1eb3e22f067838733c24c099c4307f4491e29a295003f797af0d64b02f2b390b72b14c81b9ddc2852a6e83c89e61ee7799224499224795d35700f1126241126b2bbb3ceb22ccbb24c0a07ba6bc2e17b9b841d8e8cd20f94153e9ee79ea184a29b5ab2acd600411908d10297f375bde0fe8944958a2442d14d36b250c846e0ea50c500080eb836290cdb68e0b1ca5bfe1da41b6e735296d910bd6063ae80715374a4d48ea4b247888e3dd924b66489849fa91d59cdda56f762ec7939836028f494d2cb0bd15014fdf0421e0b13d833cbcb711f98a8cc510e904d896ad4ef420e908da734e2c9329090115594463c58f644e3687f9891bd241adb22ec108b72c933f2f7a8568764c707ed4841375c21e602cae1d9b1b292735cf2ca97b3925b720e4b9651bd3acda1147ace096b761790e6dcf6e9724e0ecf0e973ce3cb99915d3e1a38f728c725d3c026954fcce1f4c901d57da20dc78386f64da0472ea01ecd9872b30cd4061ad6cf0606b8645f0be8078519594dd33444f40c2dcb233a0b7a648950a435605d526849672e130df3f9f244d1636b60c78eb9453aa67e57eb6299acd0eebc35d99114beafd81213987e383f6c18586cf3e6871d473a8ee368473b86f5a654bec5c465cce639cebe8481c57e02e4ad00e28811977dd019f046017bc6403cb8669b5da39a96fd014a53f46819759a8d701f298fb68f86e71e61370f9af77bd22cc0f75e1f3548911162f5125493f07307de65027f7ed087b756268c18b65ae5ce977b2f4bafe15c86e3388ee39274ccbb736e39950e24e2bcc6008d002e947580451f3fa1180a49ce43d770dce751b4cce71b8ea4c1ef3e97a26368a87d5e96b4c440f9ef5d89810b748d972e43c3e5cf16f09a2d7fa6a169b416ab9de01a2e53eab8da0758995d1e1db76929cfeeffae95e3f8abf1d3e273d9e6bb5ce3931d3bfed4f87c3a6ed340b563ee83dc83e30a40d45a315b310b002af08cfdec9b409b66c8b699e301d05371aeab063686b5f6b2172e71e05e620232c4b2e7a74df3ee8e5d1d0e232b39fe23f41bc21a7a156ab889562d485ce9f02af40097452c671983720b9feeea64c0fc13b90e33500ee1e59f58f3f6ddf061be40349c7b98cf5912d8e66be19b45d1a783a11fccb24face177307c7819592982455178896c3c0c58233472411882e3382ee7cf4745c64ade01c8387ac99d5d724bd6998157be729917508f3619a097fc0179cba7e3e35df8cc92678058be1ef28887cbbef21a11b4cc679719f9472f2b36bfad805eb20c900a885f5436caadf4c8c572d957befa924701957000298c4acee7c301c85683114f96731875c7ccf8d1c67d7806b843140f057a84734b969157f2966f68596d2e777b6e9101bab7e5e215efa2c16081d24858ad3c2e832151ce9d8d037444e066078fea590574298c73ff29dd2772b350bc95c9dff01f7a8696a4a6e9c65155852af543dc756ef62cd2abddb19a200ca267e8fbd03768e5618817f40dc2a6f1ec45150d856ed131d46c9a6adaf19a9ac4445b49764c74a6a292784d6de2325d9324e19fa087a84dde888ac52f51533fd22fefada82615559b5cd7a97bc61d46afd174681ace344410f4d4343f6ed1338fe4838ca7919ec60424000441a01452f8f154d69e9aaaa8d1666c4ad3383d62ecf3c7d17fc450972601a24b744c915a83f0082a3c8d271dc6204174580149891105a110405c25741883a800054200319ec6244e2f40d2848d0ba66a661cb78e11bd70aaa083b516afb0788deb0842a967169ec01f09bc0061d82a40c2046038c2e03ab88ceb8c54e772d2785d1876eb652d86a9986853ead4bbbbbb3f4702ee8cfbc3cf0a8f7cd499d6ca82ec89946cb56a619c1287cef4c595d65aff71b390c2a2af2ed18d274ca9b121464bd42d9e26e76a85d918f4f0d0d3c3c3e8299c9e6ef5e0ac56ab9567c3f3562b4ddbc14385094f7cd05a6b075e7c20c1627e5a6b39841449962b2749cc23aa26282524e9799ee7d119aab3e309c93478244a0025144956b4472b27d414249df1d6553eab758d17293c57d4ac2a3ad3de2354499e4ca5195a928eab91349246d2485a95ab7255ae4ad34fd2248f409e48484574828c438a189d8c41c62145ac56abd5494de7399a3facc6d509c6d8349d3a72498ebc7704d8cb28f8ca66a8cea7d0353448cbd0d01d466ff452892c91259205631ae487d2905211a5911c499324c91a42a10c8436e083070d3d42a150284492a927caeb254ae45484f49ef968d7f38f86f9ac7d62163e97826635ab594dd334da1d734ba552e95dc1cab2fbddeef77341462095600123e89a175c4648d35c3d3c55ae3b991052a74898f530cc285f28c153e04678145fc251a46b4631cd9b8186f92c468f934422e517722691346d070f179c4a77777777f7aede1ca5949abc887690144c24f426ed0799fbf943b52ccbb22caf4b8b5ad6b2a2ce28b5274935ab939aaa4c52a9542a954aa572ee546aa351c4094a52a32fe2c4893412c1100b5f60b1969563a10a7cb5b004ae496a595b50026399c6f5ff6421dcd6a2ad90b5907a98cf57fb52a48ff42454288a42b196f553dfe2add042287eaafc944d7d2af5292f371312944ca3ebab9ed37c7d34bad68f8528b85d86e690f3c341f4aba74e0b267c7d622d3b500e38a168f30d34cc58ca06a7b0c32e7767013c8ee3986b78d5fa7d8afb5ec9c37cd674c01555cb0759080277e832b496652dcbb2ac652d65144192e5ca4912f388aa094a094692368796e18ed13e5c75d0198c1f80379017587452c454872ac791cb95640c2d43438004e1cffca030ef425ce603ed76bb9f9bf513310c046088816878ae76c7e7a6af78d4ef668a1b5409b502149193c934dda04c138599a6699aa679af8d1b53860e7ad5cda3c70668e8a6d775b917b829752170a61fcd1887563ce86ab55aad78d47aba529038d01ff61867173dd0a104477eb0d65a104c65daf54e5afeb30603b30985301b1d5c8a7a201f6d1c8805284001c228078866c5c84d9e032a48ce491afe7d3e2f290c0cf7f9690c8c41e1ccf12948be388d72806c4c588dfa5d28401023ee5df80ce47918c268bb3ee0365d7cb5b68f9447da47c39a2c86ad5adb9349a8ce94a73bdb7d9bdfb66ddbb6bcdda6792c14efed981c83f79d7b3d3f315a5a71ba7165813fbb3cc1a2ef34cd969f07fc7da287bfe5a7b67d94480fa639e557f48d2cfa86155e6891c2c165ada67d626bd9d5b0ec6adfaf5ce673876a012d4183bfa24318117eeba4a413cfb2e8e797bfdabe5c6ddfafb45c7e6bcbb9d5e556abdad0228b89f9b39853df2a0974bed9b42ca3f289f77e3e11e3eee33e31b75a1c07d6e898908d9b8ed9c1e3069cfaf9171f4c96144b88c565064b904abd7492f4141c71c4e98b68bef32e84e5092c9fd862884159348bfd84e513fb0cc72c7379cb2fb0e4cf083080ca27e3dcd2322d9fd8397896ccf26d94a394858585858525b3b8cc7099f1c98d9f252443c60aa8c683a18eb121dac183860771721e7427722ae2a907959832be33a2d66aabbd31cd9badffa7c09e6f9ae69fc98366c7d4d4fc0c5a86e6b57833090ad5738305169dc6cdd987606b6cd3b80adc0c30962fe87234b016b4125e1f17e13379f0632c0ec2cfbf45cba85208038b7ebeea513553b106d488f5390163d96607c9086db6f92b963021e2a6693eabd7da0e862bd3c00f7e06b984b7090f839d5074d62545665d4e9e60dfd9d1988096b33c03368388c27c773842e7953dcfbccec3bc83713ae974d2e9a4d349a7934e279d7daef499fdc8e4fb890f8f74b9bb63185823e482c4464130c46303363616134ada41d0dddd6ff0d07bf0114f8e0ffdeb4f131004c15a59250a957a2245c9d9b38aa2e2426b8cbff4b07bf66299a6699af61d0e2c7f56bbcc65b15bee99cacd8cf33a508f34500db31e7da77d8ecaa080b32f14ca26ca35bfd84d09bb37a29f348d4816ba41a15028146adb68183b1b60ac29d87996251d3a9aa63b555452809df4b913bf441be91bb507e572a6046dd206ba59f4f30ec1dd24539c43601d55879f5ace3e1a375aa641811e693466c83ce20c00b0e234d241013aa91110da8339caf2075a4223d9870348619ef6608eb6ac65d1f3c126fac66e6c422ba1c15f3dc10b25241fc10ea7ea4b4c4cd5a952a2a4c7799ed8799ee779de7b73aed06064b15aa723534c52be54e4c717c2627dba4fd77517cbaac0a2b35827d64a7edc7d3eb9eb3e9f87f38ae7173d29fd0cf9e17c92c56ab1582d91889e8e3a4f2ae564b59a8613f2319a46628d45cf9375b2ce2d735974161774c5e4c1a74d5c8687929e22ab7f1485d1314d2a53ca4404f53bf74fceea4c2c16cbc462b1582cd655a38edbaf149165fe2b58e5d371db0a56f9749c870516a909cb70b295560aadeeee6b93650765d9b5f9db319fafc96432994c9a76a2678f55e7b8accd19e833c34d7df4394ef843d163a1891e9f50f44e9c448e1a7ae17b87492c7a5ae81e9d494015de0cbc2d3cd149abf644777787e82a1402c19cdbfd5d6afb1158f4540a0473f6ae86e323cd5d9645279908d08d1e0b23984b8c48b14003dc581c0109967b7a1eb4f91b7d39b67d46ba8fcb9a566d1c908d38fbbcc44ce025f0767531d40bac7181dfc5d00fd21c4225a1b761437ed5cf895d951281205523b50245adb56bfb05fab073dc0812518c2320da1045463c9e37202ea8afcb36713a91669323aa22292eca9e7b3a7912272fc36ac7dd8ed3a8f0b8ae0b39bf3714047a825fd0a61c271dfcf9860ff866b7375eb361b96e9f13d1beee98eb52b400bb0ccb1758ee220446084ae8c6bca75396ed18adb5d65a8b596b2db6c1808a2a701676a1e88516eb70703178775805d8731294babf2021438c9aa6691a160aa5f22766cffb7080bb58a5a0ca543e1df7659fdb5d16168c3d2f6710bcda6adb6a846c885eb0710087459bedde2d9ceeacd2dddd38c0526ec011515102a8e85bd7759f8822c5eade7bfb2f765d1fe63baff3c511a81110059cb45062b4b9d0e2240fd2329f7750e94174538ecbbc7773c4eedbb13bf7ee6022d1cd329ed389349b1c5115497151f69c4e2b1b9b3055390cc36c9aa63ba92801b2d1d3eb7ef423d2ab4c0394427802cbf7c222a27f813c9d5ab4568e0a6b4dd3344dd334e94c2804821e78c19013312851a31c200ad0190269c4e3194808053838473c5a1645d67e8f553162cff1bd9c7212e6eeee5cb6a00fb492d0039eb9bc7508702f412215f61224ca5026ec25488ce193e4c665ff9c84611223b9ce45b6c8d8ba2772022c3ee9650042dba3cb2d1fa8613cbb69e24fc5cd2fecbedc315e2a7925f16710be8b5baed7855d6e5a1b1bebeeeeee9fb5d7755dd7957178c92e19072fcfc88fe401a4977170c92f198719d9cba4fd8e86e7195eb24b9ec1cb335e5cf2f5edf0f20cda61cfe4cd57758c0a26f6e5931be80554432b096dcf0f7cb8c16867809e04b9cc7814280c7bc44bf2c12dba00038a49924f3ec992f1019062b3eeee16ffaabb6d60791865fd70d68f1387fe1d8e6a2ba537887af402ce0f51005a851911dd116a02d0ea23882602167fa2a1070fd66a87b53f4c2c168bc5fa11b2f1e1a1564abfd5067cd46bafa555cb5b7655bb691976dd5aedadf6baf7da22863ce6ee8e5dcc8235b831fcffde7b5d49ca2ccbb2b274f7a157d7d5ba4a4f728328fa0676c3d6ec701922f80f2e0dbab0fb6291893c51c64cc6fa86a57d03cb9e9b66a84a7778749e0daf2357b0539e1efef59fcb4d5c64714583249779d702085307a1cfb235e2943c4cd96a95996e19966559869ab68347c912c4da16ceea455d6932f03ea30116166d1b823ddb8cd3575f7d5191c5b0ac0a1004310631884bd6087a4813d137b24c81f01f7c6479211c08acc9215d7f307a66253fdda125238b0f454616df0832726733686615460d819587fc900a9272313e2b327056c99fccb29245af594dcea8edf0b18a505dcb5a11f075752c2cdb2643fb8cc8f872808cbabc8525217564393b060c7c84f0b38a11c2cf1f28210dd2cfa23b40216475cc93a592e82caf94eabe3f39f102d327e7833db0582c168b755d66ad6fadbd027fbc2458ec966a478808870b85df753c5b48bbce092c86b4f0e66f5c81b3f01ec1622806ec490cfebd3871d3c239b55aad560b27f582eeae5d5b2ab5f4a2f4ba905094124a92a5cd46391b6b6da8c03603010720ec254a9284b69b408f74ae6cb3d8ad1728e0ed72047001fe2e5aa1a396ece0e43d95dba8a0ff1f23def702fc9f93f02181c56ed510a769bc898ea9a23405141d128ca309383bea055224a2244992f43728176bca759ef8576e93fa5c8434112962e3505e08bd2e6e09ced12a702a7c65d246d3b4ce33d6c960f34b16d8412fa105d56e0277dd6bad09ca186a6e7930ec0e077e50088c57f0bc9bb13cc3fdb04c02338e89c88ba05bad6e75abd53af2048f154eeb3ccfd3da8eb91e93acadd553700267d75ed9fd687099bf9db59496e06cb55aad160e86854129a553f0f6ff5448ad6507b81a241403096fadfd2ec77605c6e1f770db82f3dcaec09e4bdcc39bf252447e9ee779f6b8ae2c0aa6b5d65a6badb5ed3838f7629c7316e1d0999c5bb9955bb9955bb975b3ad954195123aa71c209c12e48887cb40424490608d78b454a9d4342dd664537466465e59242bdb4412d65e3bd6644ba59bada6597b5df6ba2856b14aaf6bdbecc57da41691eb889ea12814132a1e1b2459b52c8acedc286090c242c5c406255bea9824d7674976b4452c59b2da05f9fdecc97e3ee29456ddaa944e415114652d2a8bac9106515e439f582788405116c5836dc1684b2b9bea9826e7ca96e8ca96ac2dd9922dd9922dd91286995e13ce28986952cfaf188f42dd7b31db11d1fd4cf4f32c014785f0ff47f03c8ce1192108660e1411423734b4dc619685e84cf9284eb4ccbce9cb0b9ffda8042e3b5ce9900000000001c31500001808088442a17048342295b7b23b14000d537442825e3e1b4a849124c9d114a30c328621028801408000c150892c2ddcd8ab999bd4543d9452a28be475855e6441efdf869a9c7eed04c8b24feead4d784bb5e98b0eb34645472f2c0aa6a5cb1e45c9525fbb94b0cb7f78143c9c394150da8d0fb07dd83214b014cbdbc732b77ac29132d9d931009fcd1a086ecfd58179be1a213dc83a3c45775a5bed482df17138484987626691f016730dcdd1047c6aaf9a6b54421a328c5e79562cbae5dd98150d7d18fea05a2d2282bd9da99e3b53515262afbb684310daadc2e33b5bc08160d9c1027a6e280a71d70e120f70c43c48f97d58de2761878cdd226f85980c9a9e93cd6f7ac287d8fa75c023f002e713c5b76ffd48b5ffd85062a200daaaf1e0711042b3a8126bc2c5640cf13778af074eaa29f81fd4e241a49165a50e9f539d751e5c906d684d935cf592db0d35020ed627c712ad834431c04ac64a13f7a72aebb6809974d250527c7c41f7f427597b5cab489f7c2631f34e88cf3ae4f42baf87af76ece5576c5bf0b49233e4484a6fcbc4d814942b2a9c99f313cd00d97a5482901a68aba5dcd569425b9da6c8512bda8a04f531448d64143806f621a0b7ea653ebb21fd1296bf63551cb58412662de6bc64232558eab703222b9166339d28217a538498d8c55f451fd8d1af76ed151e5d86b2ff932ffe56c4c59b4e0610aa12840804debdf5af4577aec948eb8fa239b7f47fc6f92f4f9f5753b41d7a0a1d653f6c4625c01e609eb15af69727ed58b9a14b2cf2b0ef42e4cfc1312b79cc6a86359867f18b96bee4cca441a7ba31888c4c3ca6784cd22a999a54e6700f6238f43715d7c03c7c9345e2eda3e80047325f9ecb2c8e083dc48427a206966e39142e75cc1cc5a3263331e9ca51639ae5996b82cc04e58f19ea44f3245cccf0ed4bf25b31ba3103ad980316cb264fe8bcc08934ceac860f64eefba4d5969aa5c18c1d7948e8424d9199def6ab0d9a1152cc7d11fc038e70364293e7f79e80d20d7d2f823710d1549f325459250e8157debd11b931e82c9662e4fd850176a1b6be3b6c7d3bdd29ac108fd47ef07dbf639aa9fb076792061d28b6dd82aa88d417f2c7bfd63498e3a1a9818731ccbb2b967def42f8bdac8fdd6c19703c113ab6ce9e2e2236205166a3a77f4f01d4239b635650920adc89154b0d635a23c631fe10436de96ce08af2ca3065bfa1789a704e79d6c8cde9cd1a1923d65fa01e373bf629356c1448bb36329877ad885d023c5b4dcac57a10659764699d3e2f7c2c3b128553b6913ac4f039d903de3a342227c0212916db8893c904313ff3b5a92336f6b21242afb37af5c72bfe908db124646186a3fb597694b6930f12dff4a139fbeebd09a57638f42aa3d5c33258f8565a29fa0915866561aa8e2da28c3761ad2884407cef8e13bbf490ecd17d5fb1be6bb9366b8fcf79418540bb930dae190e431c90dc9d81a97ff9f5328331a95eef4b243f6efc1e13c8af4d453ac47c1cad32f35a98afbdb4f5303d54434b73a45991f9ea0be154d7fe48a9634fc5f5104fba3e8209aa723ade8f9ca96ca9c2bb4515f1128312160e910cb9249ee7b93b105d91834d27c25afd9ec80ca7d056b381a4309e6d30d258f92cf6059eefefa98d76324424840a08b839113193246ca59d296b6819e6765a3480dd053fa0bfa54a876a85bb1bbe512c7c105c9499d51b7c7bffb46680060d06dfc18a9dd4b735a6366b41a07c84124ea9415ba8dbfc6fd31553f857b0423e67d215f3b52a113bf8348f4c407199978f5ceb1a625dfbbd65e570d5485b6eaf5a58597b2103a2b9092ac094289327835c09a18acdb282964aad6e6e65be42ab39e767c9349b9fc89f96a4599b857c6ccb6f70d3b8f260017f8b9bc50ffaaaf705bec76082e2d52cf42ce46832193772ed946c0d51f87d935ec9517a2a1dd450760926bd0cf24167426b79cab2e456a44583a3a89de85bada3cb5f48f567504b26db24e66c26e00e01529e90cb19c29375688e776d1f7397e4739dbe1ca563ad4db46de9be29db682dc5068e5d8b1f1c944094d5fbfc52d08e9886a38b2983efb848a4ba6dd9e4ab1a3c6b5bbb713cc4bb07a21f13f378c9546447b40b9d5c3764295d85368a96637e3cab8bdab6fbe0f8bec7eda54bc205bc2d75953d535cf8a21d3b812a47eb8c76f51bfea362c45f6c87640d79d2bdbe392530fd0d22233d802a058b8040a129f1b7f229457e0ed2aee6a385afdf7cb0fe457e1d320bb0a90068c6b9f39cddd8cb0c7740ce68d61e094cd3358ed99fd5293a661d13250147381caf90ac58f5bb87ef6cbb67aab2e7156537593eab8f1db81a2d33f5d87d3d1046075ac7858f336c9e4f54b03065585d57298ea3d6a206b5db8de10eaf2ea6159684182c1bd447c407aa4024149f67dc69612c511fe0413d63cafbe347419a88e88922a901567780a0174a07ed577dd760886414ad95eab06d2f1b01adc55728ba22793c45c155e69bd6a40fdec8068107cb9e9e97518ffaec2545a5c3074c384f3bf7e14c80a691c69f272ff146ad861432a21a8cda43a420e1f26bd1357e29e1bd50630cda39d6c6d58ae035c4ad6120430a39c3d2d08b882463e094073fab43fb2905a94cf4a50e6eca31c4b0493857b59174a84afff3f07af2e9371857c2c53c523a93d180eb59cfc3e1b56f35a5ae0e377840be332dcf11f26041cd29ca979a20e84c27b2ed612a9d4bc3f00f5c4fdd6e8fd801bfa3c0ebf2f7e28c4f406a5f472558a880c2568dc266fe86f51c9b9e22e62df55e1b0ed003f5ab3533641e6f0d7842bd1b0776a6cdd7d63b21185e4f96ef84ccff8611ee89aca01daff09e941101c89de345514507acf9635494e88e928b08ee93bd14951b00416b84d0eb61b10c1d2f73087724c45d8cdb374b85bb35c2dc3fecd85a230860b1093419bfd64dbf293284571adfb27bf951c70ac945e9a5f6b54e7917e025f16b1204595ea3fedae44f196ee001af608028511be53e9f613e9c844dda4741ef94333cdbef91f0f16ba810baa132ae82c70597166c28dcfe3f0d9f3bc01f78f62ea20a0d6358d00e15e32783a88277903fd06f672cfbd3acecf5d7f29bab88ea85b3498e12827d984e489f360583fbdaaecfa6a6714aa7d21c8f23334f281ba678af6733400fe9d11e5e25d0d1ad1497716a3587a749076624f81178015bf47cf0106e6818b828ba65a2a287ab12398b605e17f4e97576db31c709d88e8a5585b6a1bb512e628e73731fa8d754677cf6ccd9e9950874337f880ad364826df075a46ee444ec74cea06b678132e273041644d8966a8f6368390a121b8528966239f2ec58e798a9fa2b0a8b5e04d9488fce37bce285e27b803237a5c45d382ffafbb3a0be967a2b126917e219cb090a06de48a5adfd84f623b681742e860197dabc1f004109972f51651c495bb32c0c4a9b93eb99a331cfe8cfdcd4df2eb753ba9c34715886ea2f07814b2a7e633e0bf1fd49e6585e0e07e68dfd1051ac0d928acc8a21dfc91331f8472792227d6d9102c800f8f1fa35dacf9cf3a57122b5ccfb86489c049f2c6d27908adbdc1d3aaa36f1d4157d311ff0003f7c2baa78e4f4864161eef5ca101c97eee9caebae37532cbc6b034f320ee1780e3c626cab2df674bc978ddeb801cf5232133ff18bd52044ccd9ff0912177a1cb610924c1ba15a2d31a35b428223c89d55a2227e2dbcc2afe5c4f835d8e658dd92403a8730d87b9f851d70f8a6668efb1aa091b3ae5b9cb75b99173432730ce5e2e6ed66c972bd3085200b8bcfc257aa77cc964f483df88c311b89b2d19606fa6dc3b7953ecfdca90afcb81924870dfba42edeabda6b7898530583e060c6f27164cb67b094a2e3f60838c84afd8e95a5af628822719b919b1f0eca7a68046ae16bc17940aba218da2401ef4ae03d0ccd018e2808d15a6b5a0718b24e6d4014e303f3826ced880eff2f206ebfbaabfaf8588c3c2e623f2f51bf1cea41e7e4c7a13974921a5f42b346b01759346773c6e704e313999fc3e198dabd2bfc8935e5307991f1253156dc2a2c9c516f3b0d8e8871bac88740b3f477356c2160cdfa29863121b5d6dcb917415dd70bf50691224a16809f09b104b0ffaa0e0652c28484274c198be2d0e36a948f873f36c03133caf074bab48916619934d17319b20367ace9ced6e2505ceee5aacaba9241ac2fa4d5a228ce9a7192fa2b7b2de3581f858b11ee84746dc640b9d31403ccbc34194c1d59d2360cd937da21bf0e4267503cdb2e8872c9554f209116b6c60c7d0b15f0e2c2f854088734cf646a874709f241d82ca9c0e6c1ec9d90be5d7cfba4761d64520719a94ead7fed8f17e085c0dd17115862d334067be0b9b8e4242daf953f16b8c9cf1108a940b2788d7138ce0e281b20a4353b231744df56a1cb8c97af32eece387a3340096528af1d160b6bc4d2a1354adc74e520ccf3cac1841c0662c122b12ae26ace04351ca9657dffd66dbc59bc0434e5e4ea63b1a0a49fc7bc3034b8c780fe46a50c7f8c90dd1794df8c3233b61750f470c31005a899268ac887146ad8c11e16df532592c70cab5459eb768ac26a80808ad4d0e3711c2acfdb21a80982b5ee83354b2846450aa51cf16c798327094c5ffa98a28266571992ee3929c06432fe51fa14ef291452e77c8c32e18de83a195d1cedcb151281454849890fb730ab7642ea139bcd3c9a329a11fc002aef36f923ecb0c664112d8a557dfda6eb513d98fb5b6e2254d1364a788197055c422ca8fc1fc917d038c170dd88d7a7bd2d9a1909e1dff0695f854635fc37cf9f03a066a6f8674b48ec80253b1b33eee4cdbc32e8cf5c7663b06942f0103495ef4181f79c00b83240b5d7fb635d31a19db5ad1533d78ad5eb7f5031e779ffbfd13b46aa49088b8004b6d7ae3a15b8644931ba55e535998fc83cc252b1d1ae62eb713e6f94c492a2d9fd6103a2b9d77e78fa7588ca6c6b4c91b26a3ec974954fcdbd4d362ec644b2bd68a7bd4ac95343f73d898f26ee3c1c358fa5bc574b24ba47094304be79cb4da72e345b8f33678a04129f6652e3971ff22047e77706ed21b38e44113a1f8a347582dbc64bc4dcdab25067118a38c438ed952a323a9f83a3c375d5c5d75544f0432c7d416fa6d0919403ca69756272566d0fe9052e013dbc2b5287add033ac768518eeeadcb7faf3ec4b9abeb7538e2b836f5530a2fe436a1314ba486c41bbebebc2fe6ac95e0f6d7e9ad1065c1793100f255eafbc923186230c96ba9f2afe0f8555935a54a542bc54c457755002bd6c969ae0006003a0f2b73782b98681d45941b3c5d1f512126f253e1a1a03f249e0a9b59d8c169ba8ce5caf66cb16c74fec5e74c46097c3baf86302acaf298b6434926e9d337b30e5247364e981e2250fc4d6f2a4e5af3a59ebc51bd6382e86f35831ad7ca7ba4b6a95c1cfe746325ab5569f2561fe53156622ebf91d08a3c377fb6425bd6b710613937c0436c4ec796cc1c23c032cb9fd4d8ceaf6122abc18169e34e098331872e81a7caddb273a6af965b56efa1bed756313a8f8ee8647f24909599c4a2196542a0f96685b840e6daf1848b4b1fd9393409d76229d402340a325f6b1ce00297cc4680b79fbdad7c8bbd341cae7c95b92c1024ec2eb6f4976f669099f0a4177828589554a1e6dc8d9f47b9164aff79661a1e11fdd1e310d2933b224bb08ff5a86250b04c7f6653a0ab6ac9b1e93b9fea3ed7b3ac9c76112fc240ca3b78319fc140efdf24770d3f4306d6a33b1a49bba76db61c478be52fa4cf948fc51a6a587b9a27288a5ce44d857c3f047db005c13235bb79019d1fc894db43914083c9c90a2cbe950491fc22a5fee2805ca40da169e83fc1e6b82f5e459abb1dd35e1d6600c3b4d00f973c77325b9405b270ff2f55ebf047aacd26df7c2139698ec5bebb31c9037c183f299d70e35e8340072f4256dfa56d93a14b9c851f40e9b94b89790533a4a808beae3c3b83d08d105656073183c34f863b8f48768bebe057e55cb5be67a04f0e2d7ea5a99725de18ccb1477a74fc87ccba2915a16a593d49cc52687254b5ada3b15f61f9cb721bf1cf4bad678c2cc2b343893f9f0cdfb6566b0a14623f601185b809f957855b7a6f7a6a6544aa9456d3e85da043b47521f0b60e7505b0c335a73b5fe20d9301c668a083dcc442d65dedcd77386ef481be2c844d4ae8cda706627c9598a6854c3c77a6e04647e320dd7e887a1b8466f188c363fc31f9fc94170fe55b210d6b80741c1fc746c954dd99d836399066470e0fd938428a7e1780e1f4be40426d1f529d7626584d6934be930bb74393fe6801716ea34919cd327c5849e2da71ee143b1b134cfbf12ba1cb60ad5a49a9d80e72c4de474537bb21d27e0612532e872e90de18a426b08ead20b64bb0086325754a02e74b95cb9a45dba963f10b630d5a15a6e4c09581100aa30b33093c4098fa8214362fb676e06ea4cff1310b4d694b05b760519e5570fb1e522ff088cd2aa1cb3731797f6df1503a4bc0a7857f9086747f6de797278dc203b5f463c6b8133217dde9d1fa50e6cb98ae2b6902e727512fa332c0735b6dcefb3e6144c3756e2c69835c79b29fc05a15140271d86d973cbbd5005eaee72d9724048ca0b05bf6839488fa1e5002d715a943d2820a65f1a58e2352b55fb99f9b5f4e492a12e4c3b0ba06648350b0f240e0ac455abecb750644ec9064f7ce55073192d2ece691d217c3507705d6c7c61524719e9ca3222f1b8475912e4ee159ceb3b798abe032330945a539458135c6ab713e67c7184f0260702683d2a8e4c0b13ec223d61b9bd01823603a25724e2d174eb60681b8bde11de521de241c8dff470f782bf55abf5128ee0d3902cb002c74c5cae10e76271d6d81fd0475ecf48e551628ac9485143393589e85878c1374c6f24e2247285330b599a56670381222b54e57d3c1f38eb00e8dbe5e7046f808477978bf0ada4a7bb7be62b1c9aae02bb7934731c3be8c964805f3287b46a2ee216e66d914b916b130d063616a6eb1fa44d004bdabbe70860859c9675f89fd5978a6a7d601a8a0c6c1fae93419903aa3839e24ed43ecd44a80df420364e7ebe333a295f95328ae6f9a3fcd908345ad595069bd72be9058b281f33e45bf519c5f1714fd39b82e473a66ae4098982c6b7cbbfb7b30d949e94624affc426e449b924d16cb8827a640bc76e18b170fa8cb6548eba488d5fcd4de90ebf8437427ecb2906e65b061d303a29e66dc3fb44902e69ab1836a86207e35841309926d3dcf3a3e6b5908c1f1521f4fb9c34bcdc203d796a8b0be8a385b92addd0a36b247beeb210c0eb8ebc2f48770fd299055864fcc5fed0f37d0b0c8992b6c29d2c3af021779503ed1aa89ed2c3095c43e2860e50491439c463b290a7138035f51df94be4576dfed226544362c6aba12f8b37837952208bb260300e83525abbcba683344af6f7c164c19a472ebe85f68c54264fa30114a9f12395e88ef9966dd6f0ab5d1d3bfea1e8f8e36196769a885f841a27980dd7f47c0ccadf58434f82ca748728cb91a525f466290c34993462d9a71e7a47f0e5bc3b2303be19588d96e1b89b8ce24a114da4334ece8466647034c093855ac8539f141cfd72e89a0de523cd72f2cc65bc1ce9997dcb7291c4424ac7c4e6c2e1a5982d36c6279fc3cf437dd149b5971db863aa414fcd673c84b159828a1e9fc66412c90a70036c181425d6697b84515b11df40700f9d857f1026f711c71ee06e94e86c476ce1cc6cd1b81cb8c69ff05a416933e06d6d3cd18761cee8060dac5cbdde9d9f067b4d622f39e8d3f4c221f343f8c1ecaf2c6d5b9287ce4dd85e43a99148aadbcba7ad20fcbca2908c27a0742b821aa2fa84ba5df63795b17ab7a7598b87fb9b728bac19576f6d9a940b44b964924d82e765fa33ad33924e0db10393f5c19985a36f3c0faa42717072f9e2d9a34e048004167c9dd0ed4f679cc12158d91466e08be8b694994c934acd82e7d452a9cc2b4408b084341a74443863cb6713d2d378627414eeac776fbb3cd793d76ab71abfefdfca86eda324276ce779afd6bb02eb8a6a8f232c0452273f73bad5729877460ebdf9a663ccc4f27e7f559375d252d37a0519b1fff036b77863480d83f0a2b529506d045422c94951b18b221a269be1d92846dc607d0da70589fcbe88f9785c6e60c62f37a940f889d7875266054b07a596cab30915957ea5f201e44ac5595279d2cc7e1565572481f395b24b2bb880e722e3d738a166a4f213a06ba7dd128e1580a5893eab2614b3fbf6d6db963071358cd20bb2547aadf04537d228fc6d1a72baaf4ec38c905c04a770f0b236433df01584455731321862ca75d60ee4b5fd7f5ac9ed4380ac7d113bfda8f65de0d5918109bcf810a3b401340879b4f4e9571b2f5c1c9fb1a71fec99ee75747f6b6efe9a9460c001ecdd2a5ea819ac85a6e5dd8886937de53d55d81ee982bb5d682b3c0e921d8ddc94d7122499950407d112d03d8c9310e6cb3f58e7d31a90e47a6d7a729fbc425871ab26da749446f36b70c8838088a34c3dbf02748996daf5b45adcf85e57a88049c85b9e71f2147ca5dc3a8d2e6a1f84b68bea956fa149ecbd97b822d50913f41f6902d4c58823d9e3ffd03a911d04ea493690bd0d4065028fddd9c3088edd487b5887f8588cbf8b70d88a160cb90aa62536b6334c5dc5001b3e4485dfd4b8de407b1537299bcf6c36492d7b03762e3122d59d2fa0a610e8d4067a0bcb816417b52b34ef41807b65fbe7545ad51f11a82b4a395f498bd87b90915b19d7e1ecba217de937d3f68f85b0e23ca03438a623811b25505499286c3d6bce49b3bb4fc701d5f45d9cbe229b17ec6b901214eea6c5bc2b28e8695a111e9c8e25360b9dd536b29193ee9683ec8101c3d02276b85d7bedc25022a0ee8a5feebe209e568e868b66ab75febe44352b82e8b6155517c89c4442f265a25c38466a0fcba093ca29c4779b4a8886083a39ce39c6d6c836476ca3d497a43c7fc9621e5eee377f5b295b9aa40c0592f460388ba129eb18683a861b447f8085f42df80a4e58f068ed7df32af9078671d953e839581a456cca4d034aa923db9bd42b0723c46198a237c0d18cd6a6481e08edab6fa438b4743cb2474486e4b8035ad6fcc4530c03068da1c04d62c472b823f2f11d7a5e6007c84b2a93c9699e6696c29ae5d0a4937b17d0f4344053f481183087c2bab8fdc5b87fa62873cd53811d911e598ef194408376707b8946030aaf60689f790ee014b8de67dee1ddccfd149e469a9644a4a996b2f8a7a8d8ac4437209bc8df37ecf054c9fdcf2eb5c7cf87d2e477a9a540286e6d821e898f2ff22d5bf63bdf879db340f9dab9ddcf984a0d53f1bdae520ce76cbd19d5efcdf1ed36dfc1259aa6c202494bc4e02f60db1e8cac542162ad04efc1b77b4db45a73604514e3519e65224ac4ca4fcc4156680bc0a7804ba455c8f673070a4c3fe00b02d5ebf47598a635a18099c18da48e3c5fea6421b6711e9d211b437ac2ad60ada11d3968a7e729f6850e60a06c13cf2db1d17cbb4ffe5905a41f877bf454a24909238447cb2ff6f60497aec953c884b35fa82c4496896c765fecfce8a2e94f8d4fa7160d356a1538a0d87e80538f9f0185c38342c8e0b5a7553432ef4185b06dbde434887f6b2f9dee6d0e8bc59a458d226ed979d86a342b2b331133b72d4e89dcc152da2a6952ca3f67ca0b678d34b77dfca91654d5b45e3177b9f2888c8760ad99e99bf2a21a05dec8a82e574b1cb8aec136f8d9c58e1d921cbed385fd5ca04cd98b20440415916e30f2d38c211a2d4bb96e82191f7085c495f65fc599f30628ed0426078bb8fc2a81eb485213d6b9b8e3ef5ebe8d3dd2d2575a11597e50e26f7d32a4c50a9fc762a95f55d07b189f4039af12e4f3afb5d1bf642568007b70d96e9e93433b306dbf826f8340fe257444366729197fca98c1422e64943d46fa4f36db1e819c3096b59e9b24ac4e0e4940943f6599fabeefebdc6d14eb9165edb05f330ac5b6c0e27d17fa953c96a2fe53908b8d88c4cf76a18a8fac1641b9172768f611a7d3c904c7d0dd0a0f7f732dcf78983d1f7debefca4b0695f1c3955a3ea6bd61561a01286eaa89249ce4329ddcdb6bc11835d5b09b4072d14d2b1f75addb4a908b39fc3ba179c6c23d89688d4a374e8fc4a44bd846400cd0e57fe0ac52c3da024e82b94c3cdb084685e028256418cd8d57430617c9c14884bf877dd871c74ae9498bcbfb9173d38722f84602a18575e2f4185f5bdf44c44ae305019471ea44061d52c08564f9259d3d73d328a636ebe6132ccb7e8d6b6841c1aa00c93f177c4a270b52e3579bacb8c37d4badaaabfaa7758afdc594a2d09cdc02f66d89f03c780e3a001b8847aaa2831d97c6cffdefaff8eecde42659dce336870af544a12a0b1d58af12e5886ea048a275e4a25cc01c98d8b66f2fa2b5c774aa18528259788466897d323c54ad109d97f05763c8e2e1c2ccb7bf7fc0ce200cf596c6b8e28ba55666e0eab2e3fdfb6d5ad00dbd1ea6773fc56cea76d806a02ee0d8cdb632039137e56347d69e18b46715a5f4809f6200ebeeb8dff6f6838acfeb260657f58ecc0e0c5e6a6884bba4126c68485ba31f37343ac04e89e863c893f129f3e92a14acb844976f6175c01472b24c56d270116c7a72306c070b99b18822656006d40253b1a7428fc5b990b717c568bd05cc7b11f1c13b9c2907ce224bd32ec89aa9c891999dd2526d9addf0734c78465186b42accea6778b5f06dbc507b27886c515172a13e991a915e7c7f95d181b30d20a864f7b7c3d8614c6dda8e1475be097025355c8a49d671d9b46723200eb0a9658b803676ffd2d575841afd2336ebfc1f53ef211d4043bcb34fe88c06aa0c446706b8b3b2283671eb71f9568e39e1215ff4372088d534564fd3322fd25554710dc387b47f44bc4fab8e1b6c707e9980d9ff268ea72f0e2afb2f0bb70090cd1b0c94b3d61031d64e7d23110b6a846b26cec0b5560eba1d02c03e54320f19aecf6aac4aac952585b8601800acb40271a67fe84c7c317b3214d8ab36dc2728a8135a780278e48de7475467bb39e9b7343ec8f8d08ae9532e8800400c6089778d57e0a12a7cacd0a7dc5b5f880e7854073ff2109118c9dc04445ac2f0e7ff78b4a559008c6ee1b841a88c8d906b00ee5acf97466ec723495241af1ecaa9fe5b87a7427bb9356bf05137f80345092d3fc339b623a826e6d2f28213c83dd1b95032fe22bc7f2687b2c88380ac3ec67ef491b7dc6013b1ed877371b72028ab0a506ee4d36250438335c1f7f3cef52435f7f7ec7495431a1644f5863e4d1a4250d60aa2b5536cfaf7de04c51abd4de05ab98d7869d64df4242174ccda7d22ac7322379e4d19305697244e5124495030241406573cd425cc4ee9bd4d57c033f277ffaae35fa1096f3d96e4eaf36dd302bbcb80f64ea5c4a7978dcb8de06b1824c9cef5a7c72d741f6609c0c572b2a65953f24f71cd42bfc7d29a019767b7b5885e1485bc31a8ae30148020032cf944c79cfd3b100d76ff7c99ad6641b161f3432ca1d70c5db91064f52f86f977e71b5b5f489167e5ec635081fe9584180a5dd21855e35b95e53ad4f8d6b372ab4a9a39a6abfa482a2d19256d5f63b837c4f3188bad3785f4514d00a699850f37fcf53708d2a0c06915286d87adf1f5bf8d355063c7d605860e73e537f15f4806dc6bc08d1093b77a6025beb459624ed64e539ed31e95e8ca5ca440c5c5193455a43012b7885cad713249cb47ce2846c0283a0c490e6880c57beaeecdd5103e99851790f4011afa5f325e235877a5a0bfa96d38f535810f688019782d41a36f358e4265466a0468721eecd40f8193352c4f6aa20432475984255634430b6663022534997844970e1ef01bc69f4f63f2814369d2f745cd6dbb6089657f6eec3d081850cd36827b73b8fc911951ad2b761d81dad57462aedb1102365ad864f5b7559b924e0c086b05d425cf2bf8d36d99cb56add289899f8a7153e27c6a266128fbb88abbb8ee24c4f8f455645177cb1c9fafb5a549f95cfe69970c3f9771e732adce75ec10b16acb8b716e0f22481889b92b846525942a72ba408a11c751b59aa81c7504123e09a4162c554dfc0911ba01c62e4434b542c78fbc35f576520ec9d2115e4e4d5a7369eb72326276a29a5856c96ce2a86635025b70f0ae0d84f92d5a8442bd4d8c797b71d9f2df80ec33f2f8f36823d38a39bf37b77ad434e09704aab409291dfda2a8c01775820ab32aa38020b453c68d7bf2b9dca0401d51182d56bae2b78bfb411ec771ef526ed0323de1777690c47c9a20566f74d9e86f03b33a4613e80c45f1e0c341fa127e0e68d36d3a2846c5b4e944a2da195f784658acea1e9134be42dbea2206f4555bf142020aa876930b272764283095da7fe21ff07c27489516d9dd280c12ab7184c3c55df743977c669580e6703b0f98bf269730b4036ffd27bb32df59e6c8f996f539c046f880a99f85b2602917da7059fe718875de643ffce75a9400f7363574b9cbd3223d7d718cb7d2ec3f3e9fd3582dc4180ea186ede1831c07fe295a4c5288239fbae18ed1d11deab78d40c1217e6bb663fb742dbfd620eb36950a025045a1296f1c8cc58d69f6e99aa882101be12048017edc87860a4a9e53dfab05ab783ae6ea58b237d364f0e1e9c5871484c94ec394d6cbf4da1d54ddf72c08ddfb9f4f0ff393c5ad830534777bb1d4f4cde55f3659a8837f459e5d729ae3184105352b3e69d8fe44131ce64e0cec3808685f7ac4fcd40684646025b97caefd5464f295e8f4e682a74202773e3b15dc9c2cd7eee918a94fc7c454ec9b789291769d0d32ead9070df8284ecf81f0cf91b56f2e79871bb100839c550e1cae97a346dd6c62afdf4edd7e810bd910e48f717c159f6765a5bd2f3d601cf63427009544f0fa6abed4c316d45f10e50afdd501d33afa9e105a6e4a01e5d6c9dcdf274e015b7f9f008a259974f18b9e4d54aab50c16ad33831590e00c96f9d6af6e3c016f14a652b44a327787dbe7d3ac986e84da957cb4aff994b940a922e81ba2eac0b8f4be962e3ba8d554dff58e8b897ec40d0c8f4a680cb11a228bf3105e32617b096bf67b319b64cac1d4c2f74445371329a21c6d403f65529de61419a800c9b0247fcd3cd8f96dbbdcafd730657e651519359cc2bb5b08d98f819158ca983629e2239cc82d13e46631162cfbe46262b1c7dada16f2c36d9b0c520ba9059e92a496226f9736c94c39d266c98530f51b2ae9cc0f3a633011e2d730a6a22563b272306dc398d5e52ac580310883bb690595d3957542b4ed3a711dd780585f5982df60fa42d3aece90db5fadd375f12e988d65ee80b90083614da7298a8bc39ff1d9d918a65f16ddfe890ee74db420f8964ac240c120e59d942b0eff6f9271b56b23cba943efd4d439fc41da041294b8f263f3ecfc14298cce3b6b22fc6365067143721287443c2b33ecc42ac551b30a3f5f00a75410a94698a6f9a87829e8de2d9c6b2564e118ada4c223d8f44b0cbf3f934942d02a4d4d0511a99c530618cf69b5c9359f826644addd9270d0675e06fc5d1ec8bc2e480762caf181a47ab0dc13a95462d5cfb93c3150bce59c9231b7ed5034e647ef9d4b21b960d0d6a898bc528551ce0fc3a027ffbc48bb6de3167f2a0a4e9b34498774b8f03c032f46591f1e5b8e8900d1bc41b404b1b756afce3c3136dfda82d4cc1a5cd7b319a2b314c33790d4b9ac47c22083743da1428b803910891909bc200536fc5ce2b45df325e0facb0864356634716975bbaa784408a06219900948d87d54e7208ae128e29792e4ea8fd560097330308a7e8ef08225629fd84ca6f0a92e96c2178e5184aba21dcf7669ea49a2722e93145ea34e90816ca4306fbc759641a882854e50d0a4173a06cb1d40bbb4af82e278933a5030d220bbb944100b3824a05507ad6dae71861c8d38cd42d68c62c7ec37be50611861950bf32661865fba32df5c787b951a86f8d775ca81cf594534503a4d36b76e495251a8855801b0d734579b0f8bf4725a16508503bd16f46afbbfd8a31fad25ee017ce43b32651569f5b26d3bd9e4db1354910b8cf51e40040709681115b9a05d8c582ada63aa48b8ad53f186f716852eddd94ca08a40aaceefb10a4515d9930277a5f10a19cadad1eb3e9f5930cbcb2879ce29c67a6e08fffc77968e454646ceac21dfa2704590027552259af8edfbf5c4bba74853c5abea7f7b512872d2c338a0bed9bd85de5a6b982f51d10903e204afdd090ac23e4391db273bfa630750ce748308c5428873b92858a481e771b90ace55c5de1c6ecd36def60e73e43491c9f4c787889a619d90a82298e3b0969a88a03ac0cedc5344acbfae1d3d1b2313084a2da8f87ba1d6f82dc2de50c66cb43c0291c5c8cc0e7ec135534e3e26ed040915622d22c39f7dcc0a26f137b6daf65acb96aa95ff2b50a6c7ffe7f2ab0954ed6e923f1809f872857f44e54c264ed7732d28376d9d4a1a055007fa10fb923b28d259eafc47a89e9756ed4357708067e0f6de7c969dd6200818def3bf5c421dab786992512a3939f467494737d48d0c0d9c79a78b7af3c2252418475ee41573d6fc47c9a1801ec50ca36862b1fe984b313fc083b0d0f9be92476e760416115bbd4bd87da197e1cd83874cda87c0797136c5e0a578e55d7e71d8aab97ffaf2286a834db791270e217c0d9fd5f706db36e8f24d481e821f17ef33dd99797e3674fa560b28a1f38af89d1312020f17f146a36c1d23dad75c23992c613a9124d3e5545cfbd3051b91e55db54b1b40bd43a3f8fd9493dd1e3d5f6e48ed5a29bf429a614671f9bdf410479363bf5e4a7ffeb86d3e013628fcf8baba271c94e1bf6e1f16da3d772b39d75304b746201a950052c08f9e7d04828e86847d186aa1335601ad0204e836bd51adb0345e22508484b6cd3cdc9c0950916e318c33b0e74e7f0de57678c8e712b1c49ba01e7238a15686ecc70963a38636c5631ec7df0b59b85d593f2817ecd8a432bf6cc6caf9a9729d7ec93af35ea781b68ae94b550133c35b7da0c8e0f8f4b0b2ea4eea7dba7d39568f2acbf999916ef77f3d9de4ed73c38969bd3675a10c04c9c8902210671f88711973785f216f81b6be9043ca3144faa6767232edb5f6bfa7963ca3d1c9f3d5d81b28ae4d5cef5e22275433f5b4ad917a7f211bd0cb08b0ca7663a2153329423139edd4443597871300b00019ab45225fcc210cff1104660cf48339124969758af975e8a5e7cda4d37e4603270c6985d5facb53334a215b520a61802926db529552da0df2c0a1d2463628b36857e1e1d94813e212d98c984013b66246fff8bb094a59fe4bab6ae159bdc900ad4268d2cc7465d74fa49cbe73cf8261180d1c5b71f0ed938ae74eeba71cdc19a4a5dc0028ec16553fba0bedba34897f73a25aac2a8b7c79fe2a7e287395812d434c3bdbab6b0a81441d6495ff76dfbb55319af7c93cb4b6e9257b095d2c008844dacb0825f6022fde172b29b9cafb5e2b7e622c789d22eb73eb78461043a173e2b933e28fa50ab224578da1dbd6fba8d219dcb0fc7d15a2c1acefdc32a07dae30f0177b15265ec84bf153da268cc814640642cf9224341849e9cdae095a95328ca43743242924f9c1c61e924c9b38e117ad34c2d78e90c5fef223119947ffae2460a98ffda68add68482ef2a462689a320894dc1ca21505b993ede407aa849cda1d073298c966dcbfd935170382c4d38eb7a72f533645919c6a71a48628a91a831da57ec9c9b76225a00a47dd99ff7b93046e2f6240cddbba497e997091b103c0e144aa447641422b238adde70758c94311ed098c55ea92b407569cb365a8563e40474d7fa959dab10cf06a34392bab28821b43e00614fed85356c3a1aaa55bcf61260f3bb43e93d97394845a885e55a0be27988c2c48ae0737fd175fb38598c3463c62018ea43c5272098b73249ef7c4befe1448e86384c893accd48514f76da6e391d2a91e7cf669eeed8971b22cf808d0e20213299cff0f6f5860071ef14a431ca3c91dcbd96acf168933efaceee2b411c3a05215e3f23725e70a63ca02945f908726579ca97a85d468d75945e883d9106a53dd93265564431b5747abe0e48d0cf96782189dd99e06ae73e4fd26982f595c6ecb4dbf3507304c1a5511a345be9049f10fe5390c22dd52d2aacef0235a87d1c5cd7a8da2825b12d2e9bc54b91b6dd1857f9c7963d8b58f6c18e05c073a3224ca1d117885691f9458d3f292e54dc6fbe542aa982b9746f1788e8e0a318e7b0e24c458a7d2389646df1fa468e8a4f0c275e5949e5535237c57a8ba7699316ceb1d407f199ac0893679398916ef827ee280f3b6e8c027bc8029ae130d35de8f4ea640802cbb44baf51c8c9ad262720902dd02424ff5abf0ae738b83fc0df9eb46d4e17ea60b3b887a273aab899a30a0827ca0f79e0b8bc65829edfe9119cc504a7c2a691d102e74efc41000a039c7ffd13d6267262f82b4c85cd8986f274dee642e56e0009e97e887ca37137e0213be7c5c6d16f771ecd40f15f69e95ecfa5234cfee5a426aae4cacba826f1fd7bff17633cd826251768fb6b06f0dd44f50c599497a60015dee1f1ba9c3c16d5e3b7cdbed23ad6d7bac65004cf4f9613977563f6aca206135003781de7617aa53590432a14a841c10fa17d3b7009dd31b0150058d569b0d735430ca428c4a00e98d7fcda98d9afad85c07966b9d0374b0c38453bbc5c36c331a3337a0bbf23730783a31f860fd166eeec27db2eb80834f5b84625e345207427fc3d84633141c661b4489054fe0969d61408c6162f6f513c8160516b1aea89ed26516407d5605e6ecf9d77044c1da1e63ba72d49aa19b49f1e4844c70e0e212ff9467ea48b4a5d0760521d03af1081b83fb61ff05c664034c70e4a230ef8236b00ae89c80e62a7051c911d088c2abd88ec8033522a68fb87db47dbb1d03ea16cf1c1c9273d99ee14c80ed27da91d1d341b0c78d00e1e36723b956a09d4ee7f2eaf586707aa9b7e44ba615d7763ddc85fb3dea804fda11ffe45b0703aae2efd40b27e5fd01f7096f3dd9575d2325397c3e0e09bfc5308ac41b21ed448b10a32823f2614ec147dd003f97857224a761bfd7c87fc01271dbefd6b8b63831746132883b6c432b878881d462f8c64b907fe23e234ae8611747aac8dbed0dad83849d648f99cd6b6ea21ce5483bfa8c6a4504021b72de308264694001579647cb42e6a66a07277a56b8ee3a4c50e6cd4a65961d14a67a7b84fe0c7313e5fa2c4c43d738380d8ae5e23938b361a3eeb4d770fe657e9a198e0c4a623c17e337d63e9961024365682ca5634ae910e8e0d2d495300ef162c670b78368f3027a87398e2d36f36948c8313008f4e062b85f7bdc560c9a66e1c91ca9de004f7b1cb38ac571f9a81c587204b47bda16e60b7d644c7fee79f66f2eab53edcd93b3bcfeae6db6dbbb63c21704fb048c8723129d2c40539d862e6f37db337df49d1412695eaf3ed515111b2a4aa0fec3dd429d127b449eb60a25a9100f1466ddf30d9c76611a89e3672b56fffc605839890635541cfa5362da2ea532a34eba9b10c9629fa14ef1bc0be4dada92d00906f811ae3028297e1fd156f84fe4f0303bc77217be53897629b1142cd96c9601f57e6725472c0b3716c2110e1b20824cdce5ec02640fe2bedafaad274dad4bf6aa8d662b45ac47cacd2d74ba53d8bfcf92957966010e825a1826df349107d95feec3cff04a335d96639134e7dba1606dde82c2ae0dd9b81d1651c37ff369601587a6c53fd5cc8c38adadfa2ba43c934c129e653658372f6ddb0df3e33544caf26268642839cec97119ea1bf82efad609ffc42b8dd46d474136a7986eae95973f7acc1acb8195a70d0b3596d931c24857839abe40c452e390bcf33e975a4e4255d487f5f173e56bc05db85fb98a7c8069d4bae97a950ad51fa6cdd788a20e4d2afa788191c2303977955ba1939e5c8594670690a369f956d10cd266b1409f0ef1b30592a01fa2a6b777e25149d24de86446102c11584e61e26f9d37b09f735eeaf68607831bbb16075211d896c5d27cdba5f99482edd822f44042ae570fa249e19f50563e056e41cb4f019935c04ce1f478ad260569b721afe497856c61af92923c2edf16028cf65477fce56724b13ffc3d30991e89b29f101831d715e00ef4a304bcfa1a5277a94de16cddf5eae3c5fd9ff562312777e04f4ed4ea5243179fe5f44fa3e64f26df2d1df59d4f73bf14b82f2cde5bb9c5109faf9eef0893137224a2066a6f9aea3358f664f383d1b8b8cf3856f80334d3958283f2d7bd74902b28f1c888b74948448ca1be95efef9a2ad0a18c57fc9759c6fb907c41c89d2672f12fc26b90b376562288e5549e95aa1ca447a245d842f19fcd5197fc771bef00cdb6c4a56854b006f6448cddc84be01382325e85930ceb74c852ffe206421747bc45ec856dee7e67c952a78c255b9e6a0f2bf3248e97935a65cabbeda869884bc533a94c7dfad72406aa24c37c80c23419fee28364b0837c7d2156c3a50b051e2d1d49cef66b9d7152509b126bbc7d7801b27c515311175deaa8384a0ec2e3e715aaaac39ed95d7de7ad128ff1273bee0d7cb3fcee70f3819dbdaa1339b41e995efaeeb5a6a65e7015e6ba1becf7bdff67973b606b5c936b6245d26d0af47fc746b53a9b4fb3b30ee36fe48970febe382db3d734ece3f7d43d0c59299686871cec6cf7e09401317d21087284f384c9d9ce30092db0c5fff2bed078e0343c35b3011fd138de43dd35567822f7a422476a558a381c231bed794f72028b43eafe664ae5394d4db411e5203c5da6a32658a444b4fc0ef4f3c6f661e73f1cb09048b1557c8fe6fd5885c8b7122b8ab494329495597b1e504df7e399e50ab21de2ff8e0f6b6c60d792816273432c029b431e8b17a111a7a3c1e759903671cb8e1ab0d924d294be502495e23b5efc941ca720e1e0bbf2f56352ef8262f0a62f6ed87d4629e4fe09893b676221de295afca56bc3247eef64e262c9377cceafec2ca688dd873c4be3db2abdc246a28c7cba2de78675d7361d76c840a651eecc0a2db9ab4e2244c53ed942c3e2a3ac6910a710b39cddca4ea2e5c52b2bc660c2568a3c7583060b5a38bf4c7f3ea190ba8dbfd33db917b13c1bb45217b54019f53085be7e1de821d500a90c64173c43782a49adf5f5181c80dbb7f41c1f9c9e13468140635d8cad9f0486bd0a10b1ff21bbdae377caa8d3e764b8b2439b38708578e672382de17b17a20b7a7fbae90e62080a143073a48098fe0e26727a84741ecfc5bb473b59c71fdb84f3caf7b6d1ccd13c68f599f8fe40efe26871f641fd3c2ebda29736131809387d833420d65c2360541096242876668f6344351d469ddd307640f6d2f58d48586c90cfc69340d7758d3ce9a0d9f2775e0835a6781b6806871a5ab7151f2190168919d21b95ef3ae8bdfb28251d50d6650cb079bc6e248c427e34a7366e71cf048584a90a9f7d185180076d3325f4f2c30c1be23a48385e12c2b3af8196e47a183e085eb1f02237317a52f875576d062d2f0e9b0603350312819bb6bf690ceb789319728b7c045ceac783437796f38b75bda27b9a3d581a34c3c5d6f41b982b017a991b3147c019656e02af822f1b05843ba721b4ab531ac04c383c3385c0a76160c84901a579db7bf4604a20da31ba8ef88886a123fdcf95048fc3a184234c59a17da25ede799bfaf9fa3b7e6687dcd87048d5e92ff39a229655c0d55aac3bcc07fa3684e2a357854d0e4bb529d7af08128fab5def92e858b01e2dfe27552b3cfd03c25cff80c77490782cb0d657141537ce0da170c5315a42671bad21b472d2c6ecebf0deca0596b2af5111695b398143c4c717b1bebb7ecdffdb65c62a81d5af918cf6f0d58cbd655da5c82effdb54960ff63364658aace409f922f2671e66358953347a58481570d3c0edd9a9e0a52174873424555dc29ddf8b9a5594e1751004d016ca8570be745feb7ec6b0a47cb2cc79e63af02d06865af1ffe14576f562e536f7437cb70010b23ffe718649584d0507b4f63714be9047529e808aafb75de0441980c5035a980530c3e38886fe823c110e0b10f54a8cccbae674f1126abe51666a91d97814a6970a32d39ad5f76d83a3319d1865bd14fa2fae908edff19564b2f6d5632d9de0e7c0cd10471a0196629a5d61e99b09c4664f07a8320faba24164460e1fb4e263a4d2c8cc356f51a08189f95e2589db59ce086e884e30f5dd3e3902ef04d632fc530cc4479f8f166a3b8d6634c17c6684b2b1ee3a6c5b7eea07082fa4c2199d5279de8567161f48145f7de3af54e94f47ebf4c066ad4a57abcac1730896e23cad79aa8b5f8b57e4aca258fe438d6495649024f0e308b376744b5c662a6d1ee95c2bfdfff2197d486988608a2aba9efd1733342e704ec0c42c80ca78f4b6a4572106ead8947bcc8c01735d465d4c4b77ed1974cff9f3e28a04137b0144b52a7efbcd81192855b8c5f3b9ad29f195de871c4926d4a1aea5c176ebb4abc54bcbaf0563bdcc26bba15a58f9adf699d4e5d53c500d6f55aaff687dc70b78e0e3f762e240385cdd1521f13bf5c2d2ebcce37baf8b5118e905d15180be43f3d831c7c91ba7674ead6eaef5a18be999e09bd51f94156683f40afe48e87b1d1a77b02247268374ceef84bd1cbb8313c75d3819655e66fc6f8d522b94a518cf068e4c9f76d72ecd5910c8354f7d71bb194007723d16be13bf81c37961c4f67cfe9b853ca7d350806a6f1c758c47573fac9da52d46d6675e361f698d66588260358a4d918f59edc01488f30e8ac6ed7b5af8951aaa062771fd4dea41deca000c2eade0a404df79f19a2427905b1cde9fc72b0ada361dd9147a168e2fa1aa46304affa74a4e6cb3a68d3b9db3d248189a350a6735677869ca4783bd1ac7663545ce792b1dca963bca87db8578b52405837412154a84929027719f8f84c62a2933f452f0ea85131a970369b24df26211f74cbcf37f263ac32a22e128d80981151c435e4a24bda86b105873e3d8e660a8cc23e1698e8a146a5005f2054e9db911faa763fcbbc0205c86ddd23e47e91a76895bdf3f63192ce712ecf12c6d714cba02ad6b7c3810066ceac7da83c1ca30b34386e61e18797161fc774163e909b6252152b557bf6bc036f319dd7537f88b4358a241754aade0d866bf689c6749151dd9ebef34216107da9ae61a0ddeca5d3eba7ae18e4c9653d14865a645973e5546bb530b430039486bf4b420373355953677a5f0500acc856c816a874d831f3ed858e9c1e7d3e2a00f06b475f363a053e6b846945b6b53e8f0de71eb37a10d49252acf174a529d4298db602619e057e041044331ad64caadc7b737898a93849bcd1ddd96e9f81bc6d207dca8e6f1f187a0437101c8d9175d6ad37057a3fe6a7346500126370d06b8c1ec92aa651ea3b7cd17ec37912f542132d448c3024f53e8d6d47ea321e98914b0a7c394be700125276e155e7256f3c532c0fd5d1a10ac14401137a776be45ab08127d4b805f9c83506636f4bd64732f9875ff0bf5a69e0fd42935c062cbbb816107e9ec3ec6bd5688e4a9b414ce28fb04b31f0be7b5fc48b80d194ed333673a6b9a938a8260dbda2567726e1664fdf581fa5c24a0ac561909f5f2f03d204133b2a38548f56e1d750368dbd4dc1cf3dbe246384de6122e22e2e022202fc7b060ab5494bdd6a67f48383b21941f18d376fb4099998f7139dd6aa271573919b47466c16d0cad8580d00cbfbe55fcbe13a0efef82ecf1bec2592070cd366d9f721db3d9b48315ec9205aa262e35f2868c4e33b21a0f1dd016ca436160d6690116086294f17ac4c9e3f402dc04c940d509be40d54e10893d8e96a77eccd6ffe7a6b268d4a1a22a34d1d939fd67f525cb21b8d4c60a09a22470112fedfcaf8deed7e963597e885c8b099843db0a8bcb386dda43c9f9961da5e68b1582d985830e6bb5fc285a07c92c27c4a4c537983cf5d6e10c1dec23d657b5d612ef17650efa05711ef6c6f124bf152e856edd78b2a0f1f0a43c88f71e1f15f40102d9bde3a49a798fc85eb214e8a0f72cbd3b256d3b5180a832cd500359f97edc354cdb90466611ac6418ecc6e40c4f40df5eb56f5052e000346f7505e3fce4d5d18ee27e30114a96b17d02b1dac569fb06f4cb33b85f8352438b93d9d4116ac376a3d92d93ea66c69c5440945f74a43ee38cbc3a5c2dc35f2632d6ec78ef6479916d47f768b0fdf3715e402be01bb07b85e62bb1d8b466868a89e6a83a8f1bfaa8a2775453530e4122c9f16df2ef24f77b7a986ec2975ddda3678406e06d0ba7c525d6610e773c83d623979fe9cd23b00d67f9ce87f1cf2dabe327792bd225d9b3ee225fe9de7427f6c3651910958ffd13cde55eb05d90af8af11211745872a595e0ad787b6837055ff53b5881ffba8833a332ae57b13721815a47540549e03a9d38b6b89aa6a0071439a390c3414399cc49d0a27e33259696df06ad09fecf329718eae36f58219823d8f816a0764efde7542999aed9ece46319b876ec8219c4bc8a79750b6e87d02623e293a63b038c50656abf90098de7d1667f0569a36a5cb19f7f2baa481ea8f53570bb0d744bf5ec6716f07c0a00ee32d835a428ef30e3071c9b278fc19c4089c1fd1abda374c939dab7ef38083315b8ad9676fc40765a22c727640fb24635b86da0e5b9ee8faeaa1811c5152154195d1808354ad9da2a5ef80348786b0c482c7aa44d81f39781ac2e9e87616504ab229033de9bbd63ae5ae105b9f282ac1e82a3251e03b2955191efed4060db9455710c618f92f4f57f512e5eb8aed02d74d71bcb11c0ec2e9c4e299a8ca72c600954829c32ea7f67f7bd1e1e1f9a9844d9614cd8f5b78da4bc455638707a299050ccdab95ad4d2a91bd9f81288f09225871a358af4ae09ddee54edb7801bd51ee01350924555abd312590b106755314e86b4c6e4daf14fef4d4cc60e7f21827bb45d5612456206a2bc5059010ab52ba3ab568fe9a8e1ff303c4ad6a6ab38de4887af7107b9cd2408d83e154295cf57da5500f0beea88d89854d180389837902504e11ecb1f53270de9d5811ac4224c074463702917194b2c822b8149a4293c1f02c82a688b49a1eb55594927b0d051c06893720d4749e07a417403918c18c2becad3010a9badc01605b18c1e5e37b22c4f508af01ab719701eabc0a7489c5bdd0ad4e9e55a01bdfd295ad85128f288092325df61963020247e7816491842902553f9e4b05668a367639fc7cef98cc6afd0164891bdea9c9c25d386c084dfd13651c7ea16022db893f873e5072e599b55d627f513aa95bd8b1bea372c32893199219199753993a484ef18e8af7930c893b91c5206f254dbd28514a14ff14064365eb1449bfd23165e47cc87593a6a84134292963c87a0bbc8f3e0c12a3ea649bf499b87ec60845c44adea44009bdb273fc21d236ab7e01e0ddc267def96c6474096a8afe1a49f55a2da00a0f30a3fee65047db0d4da64ab1153a01b7c9f7943c0b8e30df564b149b7685a617388d5e2fe1564ab3733c899ab642e91da185d424122d84bf410f2e9e79064e70497869ef50fb9b9d8f463114c403296c93cdcef97415ce4c0a0c5a635d388547f47043c623f3abff46f9c14ab9b5f1c3357198c456d2019a69f448e52f532d8ed2dc474a96522ca1656f0a770f6626abb5384c437ee3857b212b3b88aad59e6b565d8d47b6b79543ad7e0716d3cab2907f512b9069d5c7de3785e068ce7b312d1cdab4765348d658fa6f539a5d8a7306517c8d37113868461bdad4b1f32a7919ad25eec686b1f8f84cea836856a94a07456a51ab56fc96a91be026c15dc5d9c07521a50582f99244e8750f9b6d575e83373dfc4e2605ab5812c920b9a643133ebb282cea0334f71e5437d98da1eddc0f50f36ee7cc78966c6aed662be312c8b1ea311f8ecbd349051c66e0229b54b499a77402f6a3a3f4149e629eb977c58ef0c0ff87f14c8a904ba219f297141efddad5617102021c61a5053ebae4d9c9e40123ca730744ca68a357114b0661da17092f2491aae12c8b9c8aa72d3327723554fa5f75c7a5be608e7a5674ff534e9e82607355abfed7b234eda1a6d6ab9ad3c35814553acb070dc85d0ceb806dcb0f69f71ceb8ce3878d708cc6b848e8319275b90b83a1a72790c117f7ccca65503c5ca178121a89eb47cbf575a7b7388b6143e8d551cf57c085590ad40f9ef56f84945457140875b882166d4a9d22e92a5779fc880595cdb47265f5ed1241bed76eeda69e00eb5d61b272bf792ca393bf73cf5b3a51646851fd5af9f3937981de9abfc75a7aee959e9e2eaebabb5dcd0316e0a7b13ec0a24e67e94cac4375acfccc01cc7496e80738c5d8f5fdc40381d05b8bda1e770541f9630ba1907cce783756c9803efd43f977c8903f59cddec74cd972bea9de860aa5c18db6e4b47e591b265d91ec7d476950bb3fdfd64c8022746c7366a060b451c0b502c9a9e3df5d74035bccfc9d16058f2ef1ec2f5c15c3f966850826cd7aef05814aa51a7918285702938a996dd0c643647b384d4f9bbfb85f70209d4bc1cb9a7b4100c31d220203b88d597acc1d6060e7b986c1b111f9d22a211640b5b8c0e4bb40482e09242ad3c80d6435d2445decffc44d03d8053028147f33ceab88dcab9b78592cf697893df46af231629a8c8b6436090e95ef88b66167b75c7b6fe7ee52edfe56fe2c97e78aaf11c57d9dde7d9c52627399313d27427df2885488164dbbd231b56761df171e81648c14f158de379333e83f9b7e1501e67ae5bdc9330f4d46fe320cb49d2b055d2a18ebbc4dcb008b9e41f1a72d54027cba7687e4419b658e4049b5726eb240e060cf599b58bc06966d5941c8ba904ff337ae8fbfe85fa838065b99e0a049a0b38d3bc5086095ee0d5a298d364eeddb1cd93fc380ae6764d2e8503201393b6284ad90ab557367dae61bd08b604d2bcf81979fd4a643b68764d4c387022799fc7be21083eedc7134f643312578f9fb2d2e6fb1598eca608ff3cf9a49ffd41ed1e525077a9b4f889692d4310515a4cf2c3b2379fd533fb12cd74ed248703ebd74bd544da6b8bfb83f97f39236b916760f5df44423479df36bc6c53c5d4f03c054ac0a85d9076444e85026ef7c4888c483b2c46c010350563f5381bc58b5af76f2698b0f52702a805f1a3cb9a7c5e423186f93532c686d54d9f39c0122c357dc5b01cf2b88b2eb8bc7e18576a30d46308c46075267bf043ad3aa4606f8ecdb15f860c69d4133c03e42c1775ee5ce4c748640e859a20502e97df08009733f5f1e93a8a8a959bacf20cd4b46ce47768cfb268248e00873d8e7e0ec1380948c087419910717c2a385fbe93b5e206d8106729c2b7e6e74896a472c953f9a2f80c82f2f285d1725c272ea735aa949244fc9f7005d2a43d23faac8e788dbe840436f21df1a5c0e11e16159ffeeeb6ecd8850d21ea0187c41cf27aec5783b2c379a04cb65a6c209337ecfcc5433d6df6ee600156b2043b2536eaf00aceb28ccd3a6b86ac0f73e8c9f407950221f1f77768339590766c967c617fc651e0ba92e2dd006d0818e1cd3df05a29d5cb5a5e5da07c69a3617f4bd035874718f4e7b437e20ed2904258405d2d3b1addd8f0c3e92507970281cff5b1618db7ad3a6405a868fa739c2969ea9864d8f1081d203b6628d50f6ceb2c26422fd5920974cb92dc8153227eb4e482bf12802fc626fba616b9cb30cf3f087ebec37f8033ab4bce2cd1e0c5e5886cbdf6fbff038784f9179ac2800608f0175a8ff5745361ba60d3835273e0d1eee2d1437e2f7f8fff57be2baf823d78bf19be36b1a485b84031c8075097e19a71e8ae80882dd92b507b67152011f2d3c737bda76db5d1d316f01efaf9e0ed0fa11738f5e8808e08adeca6ef235aad17838e0bd7cbf4b15c53ec793286025f6fff9fe46ec95637e1e095e5dbb8f8a115ca14baa15e373c5816d386c00acc88dd1a3e472e4692b1960d082ad5e8a062a953ae6d220529e4ca67f17df9bc69cc2903db8e41ee9d7ae41d63d56bc0dfe076790456f18bb160b7fce44df852d73d6067791eae69d6e9b06c0d8739433d988f0fc769f31e2c298b9ad0db0dfc0aff0a22f6b3c008d09c906b081c7131a886c5941c2d9e79c7db5303ec6e504c7b7e3cf24d28bfec4aa12247af47f3d5171d5d9dac0fec0387de709201f235138832da48a3432ed961ea72a140420a48a820c8676a9449b62e2cad85f63f64e779ae341898cb722d6b5242a1cbc49b43c43022c895d864a772881ac87423db7a94fff7df2d763957156e1b0dc2431f6545b9603ae4ab2838e0a5d7e42780ed6121e4548ff02959e8ac6581eb102060fc1bf5738c7321d4e5477cc73ca51388e1603e00fd6580b4064561c004a4796ce7c0f0d561e416a1a48ba26a5542456b4f46a38490c4cc5dc4f826fef82c8eeab2aee892b78dc7de1363c86b019d5d4817f067eb7a8722575ded07e5397b727081a1c00e90388dc9ba09c21fd16b23ff72a1fdaddb012e174579ea4290d4709511fe0572de5df6f1beba6054d3e8dddd03342e817ca82384b9c487db5ca88a4e1b73fdf500dcde5d47a419419212a025ed51b6e64cb18310994d6c36a95b7b8e1cdcfa4a27d917ac8f10247e40a03f56497894bc86cb3345863c7ff0dacc3f602c0275e46e4c58d126a59e171544622cdf587776645ce89df8007a086d9f1b786b7c50040e4bceab847c2d9fac05bea40a6848f6ffebee3fd053e30aa6803d0e9136b22639d1bca08785ae8fda927a81959ea3b13c84b7081afe73e1215c97bfee0458580d59453c853ccdab3447149d260a9f93f960aca9e5949f7a2ca6f7aeec4c8769a2f4738d79c632bcc2ea737ef4482cffd457f0658d4ca8874a7140ff6e76d9dc05616ca4ce82bd05b7402c3e18103f7e21b1db179a121a823043777d7ac698a8fda28be778df475629d4e64572b4ca5061656040ba3ba79cc3299c14aabe35baa2b457232c12d150a4e9e0d3b7a4fefea3e3815fe300a090e6c6be04e8fa511f7d9a65112d3a48e35fb97a03077500ca39f10dc249dfaa7ce67edd05501135aaf30cd2935eb06de6f204149e60d41099dc619a236b7d20634209c318138f3216a69a1c9770e4c3bc0999b33f23c2be1ac6cc98e1f48b4da194ca9106f257c8449dcbbdc525b7023261ba145fc4e616b1f0094559b7942176c7824f3969be8c09eba3fe4310ff76dd5ef39b018990db75217b3ed7577815e1b96ec5c271f1bb8d6ff53d859ea0c980335217e24324d4ac1fd5581b6483f199990c6dff014f0a6c7b46ffd0fdc1d035ab33d595930dad53607e5c8dc585e72aa45b8863fd60f0e78af06e43efed81b58131b6b27d4b4fbc55899a3031d56ec65d89c91a1060a27c1604690d718705a8e83f9457e7f9e6c1d0803e0fe7bf2e049fe9f8fd2464b9b249152ca94646205860574053e45755a29469c4cbd47d662f9567a29a59ebd3806eb9ab8f7d251278aab64431b924e4c8200b14aac6889946559966559965f7ef9e5975f3ecd975f7ef9e5975fbe17923165d08636b440acf811c9164869436bc3907c7d98ef67aa4aaee4743a9d4e65792a4fe5a93c95211b96655896362c4b1af35f9665f9e597effd7f59e2adafa0c39e96b021b640ca29eee28fe1644bb2a17b5d156c68c37074981a86a10d6d68c397ab34030c2349154e766fce7064dba8497cfed7a84d4eca904206bb94fdc514a394d20bf3b277af4ab9d9a3fb18dbae05c7fcf0eb303fdcd590922dfe8b81b2cf2ac8bf0b5444d3ba4e52fb730139ae6c67cf9bab0a5384385d40eb77398b4c8395bc3ce62ddc2bf71572104eee63c67ac4324692363732aca6871e3ae9c4c483003129119d084729c7711cb771383f080cfcd1673961ba143e7fd6e74a702cda86ff681adc4731c5077a0ffc89b6d1393c98a61a8073f5351318cd976841b37eb40d9f4247ac29dfe3f09e3db9f3e03931beb66aa7b40d1ad2144c01838ebc851df3cefd4ee5b40d6fa552ad9c0b7b8bd56a1b1ca6651bf66d4a350d7fae33a59c06d399985eb7ba065ef87c4c42071eb23c4aa93dd8ac1437e23ab57d380ab930f470348249c3075238bda0280f3dec74b84dcbb0e63167f4f0416524b9234a29a594524a299d18e7fcf9fc87a80ae35528346766df20d8a30898a04b8f7895af4d05640a2a910f2158562511f742784c2d51e76f673b140d8ad22d681628ea0485eae137fcf862b22ccb2e1a97c025a2a64b494b391d3a0c8d6a5d0b0fdadfd741cb287d8c7eefc0ec077af8eb073ebdd8fe3862c6d829a0e60a35572857425128cce650da36f8b9cea6c53eadb97ef02b07ca3f1c4551144551144551144551d4bda19110c2d0462770220bd4a3380c3a800043eebf66cb081530c678bb19085317f1da22d9f5393add17395618c3b21a343964e4755dd73499ae1d95da1db9acd4e693bfbd817df695ce15ad533401840483115a26922a884832917600294b20330048e43967dc073d938934994c26934966fb81c1efee340a72fab6ab967620d6e1f9f953dad54c8d408de3691545892a0999836a728de3d829cf7d918c580a96923d675b1ea198353899550c804963219988ef68d2c4a2f965d883e1513c67005a64fa9d8f233e4670a6625471f538f2e0466c15c2e73bda862b691ade4614610a57620425aa2528811c57230e57e24a708cd738625ca372857d52366181a1cab1a9944629f574321319ec548c4aff1472a5c9b142ad344dd33c1d4f87b1bdae9661d85e375585154d581bb76d1cdeb86de3f0c66d1b87b7546adb36ef25b4790c1bd0cb5ace89dc14881e33f84fc76b0ba2584a3d218212726a624a8245b1568cf1171983583948a886babc94fcda637d755d0dc5ba0ed234b80e420a1264073a08d28320a828c49448ae3872ce5967d5461a83c5b410af61ced42fb848824344c6a1f8ca552a8ce17f956a6555f831ecf157ac93a12efecd64013e84593d0a93cf875059b6e322759d7380ab542a95b52fab8d8806843585aa6e0200838cdd8729ba6e7713864c43eee59c73d79b5a2f16dcebe14904217e3deddbddb79b826677d3a7d76317c83eb6f4de7befe6850c7eea67738cfd3f14b04891bd2f640a81ac83c5f5ae8b8b32e79cd66698bd40c8321b17727d50a60273e5f3b7995d4c61b6ecf0813cec1235c3de5a6b6d6685ec9915727337441081ee8af495d1227fdb9b9a1f993e58e307ac0f9c2b1afcf3214992bcf7254496aebeae797154a4722453cdf615b95ab739b8901ebc5f6b9cf93db6eceb532f5d7fbb52069adb83738b1c201ea20e39508b38ec8b21438e5c6ee370d4dc9426ea9b3d67164eaaf03172a2078d691199fbcbe15199938cfa9426fea02e8c658a1013dc6863e17ae374fc9024cbb4d30fee614964ee734b877bfe3fb8e7bf83a344d091e70fc2f3f9c10683e32ce0f9dcd22a2a313a1ff957de07d7e5ce07d739c863f437b017f58ac6ac742ddac7985ad72201ee57de022a1fa30bc2a3d279ae9ba20e688a3ca029ea96c3b4683fc4a7817f611886611a913a52977aea247d726fac25ed63b8df3aedb5ecbb3522d5544b602d655ced50cb6853ac9a6a69eb8a700fd6d2a804aa715243b4711c4712d7d295611a892d79d552b6d512564b9ab6615ab66d9bb665dbb665188661d80f2e9381190c06ff055367b2a9c5bc625ad1812ec568b46dde8bbbd490c18126071ca81986d4596bd79e329ece26e3b99bcdba2b32fdb985429a0efed3fbb7d66b1c845a6bad1d155e593aacdd42097b10dc6a6152eb6412932426afebbaae2773c64a53355902164d7247c3224f56aba80da60daec89ce93cd7f512260d876927d3f3f7c6a2499324258720ddbb45b65ae410414c1b4d360de736ad5bd98f16d96a915ce7d2c1c241692b8c1285c17a7466bcd036b6566bed5cabd4225b64a9d52a795a2d4f89eb783cad96c78343c7699082b5d68a22c639ff6b1fcd8de7a8a190e7e5bef7da720b916ba9c1307cc21fc6e00cb75dc015007cce8217e459679d2dfdf88710cdcf067bfc36d8e3164c9ba29aadc51275c1c0206a218af7be84481d23cc39efbc5bb4966d91e99555f22c45d0319e0c2928c20b9fff006cd04ff17ded9b86c5b6cec7111f5b37b51d884ba6a0b4b71c8ef97518688b4ceb4f6e87ecc33bf4e31bb82ba239e77d5eeeb82ead7c42a7d3e9e42cc34a0c337c5e064b0c510534452b20950c6a510cdd8f23d8b3fc87a5e333c8685106b98805f42023842812e3697c72376a10f9597e05d4b2a2f247f011ed8710cd009aa24f86719b8e180ed01a484706c845193445db16e33d5088ca5017acab9d9597e163f818cf926198eb1ccc029a2dd218191eb33c986374169dc7d1438dcd8d0f397ed001844aee782787ec743a9dd99d4ea7d3c99f19c3097905bf4a1325a81edab74c974c6d24c98925460c31805838cee3a1c901877b3dd4d8b877d33fe800c2bdc65ea094524a29a594d29e349c93e5edd7183f6bf8a60058e0a35a648a669195b7b1a295ce8710a2958f010ab1688a3416509f20758396c89770210c51a4f9114c9c208a348a8223986089b47a04a9092d91664f80d30529d2ee14486a8012691793205c4125d230145054220d2b021454a148cb84ec201469dac602ea2751842b91b66d2ca036c2881ba4441ab7b180ba0826544889b44eee84c0f3d5074d26478c1889b4012cc94f54bee24c1bfea4690871184c9c3397e7b36a25cb790b3b4f2912d3e355b5c49c73deaeb6e68c2fe68c279f4a075622a52ac4fd49dba8389346eec0ca8433e173fb2bee7d6c2892413b6ab9401a4259d1987ca3380c062fcb8e62bc2562ce99edc5061a3a7634b16ca29e8ec807da519de29eab3a1d8b4bf2d90e94d9280d69d8aa3046476d10af87e1741ad2b08661188634b4f6e596d955be18eff5ec23ce0ec018a6d9bdb53692318f5266cccffe721ce013350b8f34d4c54e549da84a1b06398274a152428ebb5add253587aa0b25b0c6d5683bc085942f56f77a09d5f03c29c158ad30cef93fc7531ca190e7652e2efe8f13f185141c0018e48f98c15ead1ca673accd716f6e3169f48a0aab1386e9c8844194e3c175e05f57dd884bbe21efb460ce894321defa8155b9bac77814dfb0cd5952664c06006cafac7316f690738693e9e2bff2e273af24968f72fe7c7c544953e54bc8988b9e9488d9b91467b9776d6dc14d0dc3bf303d475123a68bbf14244aa22a70161593b88f95847125a9703e82290916c5b6d65a6e7bae7beae2e07f18ee66c2b611ad1e6dc3a604d75eab65afaa3db40e773219c6183f6e1b3a7a4c7c84c76ed6a6a6ea7a11258aa2288aa11a353659d45ae76ceca2fa824c5f730fab9cd18b0cce32026ee4ebef20d41b5399c1df8ee3388ee3bda1910bafebbae6e4ba0f253f9e4fae992e2e8009865c43155ef3e347f6d8ffd87ad42175116f6ac81a92478fa68bff689251c85f230ab98507017cfdb4fb40ff7f5dcb0eff5d8fb300041924d1852d82a08552047687795c2928a145175f37a7838c530627596373e303d7d1e2aa21a37531e7acb356ecf3c7d35a7b85464411a191a99a53b5aa610d925347505a1508ebaa54dddd1da2636fdb5328582ba20926910f219a481729d191d9796780e763541ea4513e5a2ad1122d8d4a9a66b79e344e04db69d756b56dabda76752ad59c69fab967ebfcf643883815ee8710691d100da94b8b4dfecee78e862d990a25659fb3ced3d11235512a326847d9db133dd9d169644fa3d3a87b85655bd6cd3963b37fb9fb58afb88d82f4823aaaac7553d5c861ee4a4557aa95ea5af14156a9ee6d194f5cd7cde5836a03d7755d97733756e4f999f63df86fa0ff5cef85643006d1e4e01e8e1e40a522c66661f39bd5d92ecf59af8b5bde139ff4095bdec4a1987ecac950fc496a625fd2232715e0d3beb62dd01b8dba4755f491f8d51a02716f840251263455494dc345da8d843811235ae7237519cd1944883ef2d168f4987a4569d6a2c874a6711e001dd3316811fc64568b22bb1645ee0ffc7c9f6cfbcc19ef743a38e2e7b3849a2a52f173823cb96e063d3a4b02eb01d7389c0e2bc8af850cce164e0cdac59190728044132d22c960e8a3223645418aa2288a62ad9d1f4c1ba0c8f33b5a9669dfb1d66e3ee6143e602214f9b85e080f293a72fb7998f6825a76b83f5da5bad7407ee0c7ba2258868b4075f48218d4757e1d9b0ef795abe97c77b8ac6e71f30495fdd168341addfb121a6952e8a9892909a694528a67eba3df4adc05479c2871a2c497cf3452d20ca5826b65328daa9c24a4911494131393c9f484c94d3e254ac95af156cbd432511c984c485a2d8bb24b9a4c319551587346e5e20a5df8dc3e685354455d4ca696a9656ab54c36c8df72fdfcaed4d5c42f158d59b99e78a916148fe9b28a19e3a4542a954a792e219fd4674fd4c55b4e52d99469cea04adc85e2c0549a4a53d932b5544b98584c399b4ca4c96432994cd70a0c20b40199265babaad86d0cc3b02539866f080df15f7f6d13a9a1e44dc6f2f939038446413748327d709236ee79dfc74fe96dadc66168cc0f3537b4cb51ae7a24ee28cbb2eceeeed6b29a39a736b5d63410f7f5c7075bde344dd3b4a7716d730fc7d542d74a69571965c6d7b5d61e31abfdda3d8d5f208caf8b3a6a9cac3975ec1089f0002214d262e9506121d77b6f8dc145fe0ffcd0f4efc075bf7ecdbd5173df723740e045188d829e73ceee401503584b336248dc7b2fbeeebdf7d2eb567bafb5b8de8de534978c4c4a46ec88030799d2c8ac7d77c39165a426c284c9e19ef5c04c5d92df3bfa7e942cfe74b807f381e9f9f370ef0324989e5b92ed20006814a0a8708232423a31b15fe9052253831ee10371f83a1a2c0f6f71407f1ef26bd9c1437efd1ef2ebaebf1607f4d7b2c34f2490beeeecf7a41225914824128944ba3744ea187177afdd3d8634a626c9c964c4f675f12b810411a2dd6ab28cb465abbf6f4eeedf98e47104e2030022b93ef83a34c78300be3a031872f6f87176cf823a3e903e109faf3e8d8fa036b9974b2cd94734943503b5d4faf9d86cdf22feb40de4b5d85246c6fbe914db2abb2bc5ad467bb04b353fb00efbabcbba161bfc6204feebd1bd5a06711797d265d865d865d865d865f8a2a2827d9354f6aac3322ccbb22c4b6b5fca184ca04e27146b0a9395125594d493d309632ce5ca329f33442efc099281c4b4f8449c928373c8669c7d76b91b7d5a32636a6af83e594c27a793a8042b419d5a6082214af04dd2de93b34e18630cabb141dd40ca1778d049e183dab60ddbb0ee31fe1504e6fe911a615b66b047d5da17952f68d6da69a7c51d1b5bd3384204492484d664145d3f2f900f7e4377b70f5b6be0e694c1900acc21b7f75e5c20bd7365ec7018eb27db699526398d27c8f56ac2ca8a528a1208655fc103d58884095648018491342a41c2c1109c71f45e42b4c385765d2fb9862d642b10d7556bbd735a4f167d5517c624d684d32a8a1255123207d5e48ea3aa474a2bbd51a96846158db9c944594959b8062bf2a42e2fc83a8fba786800a50cf6489be8aa010e64e0a655f6e80a3d5aad3b8ee3385e17e785cbc0b6dee762528fc6e76f593e9a61b08690b135c3bec4f45cc084a906a37164afeb3611482881d3a3ab464db9412142134f89d290bbd3a0b134e744067b341aed20f7e0e19edf7882f60ba533d4d3c57638c820a6251d6cdbb6b57a6cdbb66d1dcffbcf4a6470b63e70b2b2fd56aba58282eadce4a03a76c86053065b01711c4d0e387aa8b1b9f121c70f9ece6a73ec8566ad9d765ed8cf8bf1cda670041654221f42d0a0c24a1465ef43081a56c84288487b2166167250240aa2ad6fb801c4e1f3cfe14f83835540a72b420efd10419d3316490f69daadea97698c942fb81ba150180a3b0c851348c04d08019309d38a9950088970088e1234eebd84b073a75a6f46a50b46f7de1fe4fc00ca144d9053349102711f3781918c93e8fa16283214597c6837eeeea57a363e3428a24b850fc0f8c0a9f201111d50aedbc950971c2e0d122a3e70b27a3055d5da9266e4de712ccb1d9f4ff9293f234ea271eebdf7ca9052d3552c91b14506272902df8164361473890c2394423bc4aa6d35ddddf1a9187bf083e149624badb5f45a0ce49f18a3753cdcc5ffab3b505fcdb99da3c0052600030d683b546050426b5c8d754790107c11da71af2b546305061a8b00df9c12c5dd2f56b7680c0de70c28c6d7a45a0330258bd9635bafebba2e989c6f5c1de8e3bb78f8879f86ee91420a84843b0a7d5c3a4ce91e48451cdbcf4e87fbad0b627b6e7bae1b421404f79d0c743e3f7d95ce67117e5af2803a201ca2f6da6f8f3d2b88edb5b7400bee2ca0fdd65151744ca7d0162d411a8496301948e4e11fedeffa9a6538e6873b1ede3474984ab32830bfe811cd4a352bc5eae80f2125d13d275e16de161e0ed6c562b1582c16c65e8e62db5a6a69cb58eb323273a63ffc7692572783657e166152978f0a191742768cfd6554f6de1a342a5582d184c5643585848275a94bd376bd2297ba346738b144c74fcba7a2fd8794431f7dca074d29b207f1cdaec8a539d3ff83bbb80fee6203039386ff8c06e60c77ee060ff7ae07bb74030cec9b9c0e334aa67d373bae86325513bb8175ad2a92c12ea14aa55229c707f740aa9b39c33327d5cea6811f43a1b2ac860c0e3452604c92d9c74c4336b96d5c6419b68d66ce601f27e348c6e29250f03fee36f206c69d4291ddc6fd9cdd3daebfd129cd094992645ed180a13a94a8c5c0d62776d0597d630c04cfd71d0e3232d78533a4858316929648e2e93c264a85802924896173860c0ea4e6311821b5bb975d204e51d92d2832385b94bed8da897da23f421f6d3320cd39ebac5a5649b4da11574b29a55893b06fd29ce990c6ef0fac94c126913e0f88b130580eb30a50c06c8a0893c6942839547cce240559cbb08d524924128944b2f6a563857befbc531445ea7934a48d628be258924183eca1b73c9bf8b1ff74596eac2124113853b7033ff5ebae676022b78c26641f0d046708a70cb7484b5ad2520cdd7075a077832dcb39430cb2add124947a61e2a16c5032039939ef17e2de776df45b88d77879b1c1061a3eb16c510740684d42b69f13294d454585eb171d667ef4c39cc8ff953cc0f96cb0fff8b3b7d1402dfdddca402dddd574495dfc4b20ccdba1bbea5e0b71af4527f93ff1092a7ca08fa6286f5996652903671cc7d32a8a1255123207d5641ce99c18ff77163295a9e99691e971cefcf56559d6cf910cf6785d374a669fcaa8840e7a64c968040008405000831500001800068442a16040302094cdc37a14000d577a3e805e401c48644916c46008a2903186190288018000019299425500c62fcfd202af25a3cede476926b63f2a54c0fc9206d818fc040159cf0a4f6aefc8f07baa91e60ed749d43d2b2075d1742bcd3bb80fa7945c6b03d62f00a582e2d7e383ff660afdfb19659cc6015385c8a50af437bcd7837524f7e494b6b04eac50e64b6d2bfa44c81c8133c6b6b6e73ced05c1a6c24ed55d67c4e793a6a61b9a093193f59769678b8a9688a82c81b1de893b294755e589a146de29e630e9526c64172833fec060d2124d7fa25cf39990a45895a744f0967917d7c338afacc4d92b9f99e2f1427963fb6d07bbcace34e5bdacc0d7fa2d28aa0865e6ecdeb93f5f49cd395c52ea42f19c6a8e88b23f8427b54f294d5953230599815eff254d31dc54b89aec8180f0f077c75465fd946cec4cf1462a94c23935156412b78c13bc3b0c5dceab9ada90ac8ab60db5aefa194ea07c7797fe8537843952fd2c2fcfa626275d37e31da9e4ccf61327d44dabe18340429de298001def786b431a7ce3484191777928e9314ced8158c3ea8e836b83a3ac51886c4e8d470523b04b12c0e8dc2eeecde12a9cc60f7b6ebff74d1b80f0527e7c2677df060728f3455f7a41ec225d68c8fd8579603fa9d283b0b1c37a7e5568a19c1b3586e2830b848d02e99c4dddaca53fc844f1a25075c611859fc4e19206d7511e6e3c024201e88d02ca61076ff5210056b04017c2c22a71ee401e494bd4f7f985fefd0eaa0e459f1b64a5c723c562ea632c58793adabfded5ef5542a57da5716045e64bec5801589356b54dd105eae52abd44ac0d454f65a70cfcdc7553f8844295d833232334965459bcb2bb59dc359b221cc1e6541b33ba8bdd2994ec0e1b34f1aef368c5baf83d472599f6467bc44adc24ae290a6d0b8a8ac76bf3d42a0fc9f668b18bc958f481124b044e07f00618073b1925e58c15c162a9b830bd28f0e61c41b2629abac63472c652f087b0676e3b2d85f830ffd62ac5e22d87846f368b9a357c2249ce95e10fb9080af3a98176a217ff4d3d00a6e4bbc6cb81877c78e0356ccfb560260c3eac23fa2d9664fd88a11e08fdedd7cb6ec92ccd8b522a1d41008cc3c232bb64545679de519890597285d53b2671ffdbcb0dea41897c74d49caf1341b38640217cdb816821984cd168191389176c94cfeb9e17e068949687149e541f57a3f69ca67466f159c142a3af050b1549b8978345f8029501125fd07cc395a18741557f54496a191439f69def98e4ebd420bd64219404b58f3c91af362336cffa5c9eb0e42640bec22829b772c9eede40fd4903d2869df7778f7ecedd5774f4b100e746776a2fdde95ca10a3feb74a75ed01bccbd4516c332926999679fa8951155178a92b9c692a0ff1e3bb012689f538ffd11e70539114f90a13b0da116f6e3c87c8860ea453c91c4518a98921d5c6162d4972b74900ae45fcd8753cc61c5370c9d9e11ded5ad755fe3d0732817759f78221afb637ec7c57e3993e30fd67becbf4ae2db726811d0ddede8ec02734a435f2b192e4c8217c0ec681c956644890b25453d4b6bb720b1302676c43213334d24ad2b627409b327d1bec1ee3f06fcf0cf2811089ca9eb4326473277dc27d2523161c5617e954bca0df6c2a9654d1c3c4ac6719087917a382dc680231256c87b822c5a9e22c8c20d0035e542af3649632d6a533983ce8842a5e13b6482689a6641792c004890c1f19842737664dcdc43b4dc79fe6452dad542b3c48a7ddbf4fd26ec1b3b4be258c89f982de888620b28d7aefb09c469da0dda58e9282b58092d48f7e06fb59753701b436c87777b513159b43ce6aa785751a5b7a13fda5703f485c159a418ef0c1cb98b569e23eb32ad5bd151c5a4f75409c3e966de62d24c66c405825a5caf780d4868827bbb2efe6ef83e3543fada09ee8acd91b92a9b0a426799ed9a2b40bab7a9abc3c5db38cf3fe312c2424237a6ed5cd106d07b71e64f297a4626c30f4afbe78e2325258c0087b639dc9683ef76edc4dde00a1a83b73fcea0e18651830b82d60940785535eb302f5506faa736f3df332b86579a9a462e8880934aefc18270b8aa317628cbd245e60eadf848b366b064eceb7d199d023bf3f16100a7b4247af47512ca48b29a84359047501d7014628363ab2d0c0942d7f851b8cfc5375d3a63dad817a3b8aea4733952284530a9b16fab7827aa40cb3a437d74f5925b64dc5c9ea6ebba9a5c47bc79474b1cbaac42e3d8e96c8eac84225f706d16e113eed66a1b265f84ee753c6521a063f4b09ca749bb03cf7caa3a76c7f69e8b1f1fcfb99dcedbb67f5d3ad68d2390b39759ea25be59ba325308a46b3023a49c08630957c34f3ae33cb446b0b5c9b5e9526978bf55862dcc9afef56b072f2ead393efa23f8971793aca9c6213419acf51fad0cb2b6c17993ac0a27d94827d9e38e3a8725b527595ca7014cc4496c734fe964eddba4eec1b5bc25595712997d68405793ecb08b2cbaaf88c396dc02201d1baad3daa4ceaf59775714fa3cbd06802c95878d0f3431633bf964fe213e9cf56e1cbea79aff2313b56781c3b8dbf7e13aaa0849bfba71b82643208a9f129918a03d868e86987b589aa90fcfec0b7c3c4e4bb58691cb1b64593e2d5d923d4ad3175c5cc0f5173beb928e59f8bb6840abb9f8eafc30ed483b8fdd9d245afff0c2ae7cefa90b6d35de87e1e1ee29d862cd3885499143fb27d970e85722c5c52cbb3ebf78ad6d0e58a9cc5f238cb86d06ddc69ee718b9286c15cb7108c70aba2b3e70a0a23568e62a0943c745060c106e314443933b9a5e18adcfb1ad909891b11eb136071dc8c5507267982866d613a0ad7388ab6c2bc329d5ed826cfc012a76f19c9a3988218e8294067e7c5dbed236ceeb30619a490a95906d44546fb7fd59222729df91a7a510eebbdd71ac0972b124012f970c39b4159582f8f0850e028d6fb234f1244a00d7d4bc171e21ce81ca925d2e2afbd23d4a56eb6a5023d7193fb1546da43da033abc5fb0f2f2bc3f54290fa6d20c8bf6047381407fad8c58c7a92af6d5b0514ae1e97e74670e750a1d0d976830681fcc6e292f87305515ba69d0a87b0f01dd8436d5a65b0d7dddd1c1a4353ac00f33e38446398d9f5744f1deaeeb4e2e164462740eb0d351137a8b3e5d63c9362a538e21928fed3076071d05c96d070ceac4705076eed0a1f266c64a22826c8cd4769cee50b3a53303fc4a5bbd4e210c0d2645487d0fcbd224d63d8a5eb6be5ea590c1893c18a1f5a3c2c68d287343f743bd6d904ea00c0804893ecf34395237e5372b59889ba0c9d1fa2e302d13c2e94a021aa63df3faa3e80bf1eba8f27120e25845ae8358c62998a84a232c5b43d11a4f275bf5aea2ec8c1bab9842ea2614ec2ee030530a52ba9101eab409698f939c7e1206d1aa3199288e860906bf12451fb6a486c9a6c6bbf9bd75fc25246bb6136bbcf4719ebd4212226e7b97ec531c84c31f66904e631171ed4d55c2d0380c682eb5f7b8a837f3b0ccc8dca8ee36381b7f5cc9267ddc4051203afcde2315f9d4f4612538025cee70a924a19ca6620035decde70fbf2ccede068b1ddb27b9edb09e66d03ed5aece4938ac49a8edee848260b224bfd6e7962d97ee60f805d230c8d1dd279911c49c01ba0f7a87b04075784ed76de05730e121c45a9043a483b9a235152f0964b3a2878527cb45ebce69eac7dbb2ad2021d8d3e9178189e9f73a0b671b28a2384421a7aaad69ed778b30e8e077e63a7173e756f25240fbe40ad1e69156ef56843d75efa0742a11efea63ae4d076295fbe6fa481a0417f63b7b1e5eed1b5628f40e3c78686b2a31587c1be4a591bfa28626150f583d0bde714e8ab8c753552a4f6ab55f77010a542cba9d000980deb78a603befa003f74ba29b677d53a72da7eff92310c0428693d5ee7fbe99b32f5d4b6fe01c78a5563afef4336d82e1e2214e2db9389226a958270abe5b7264cf60315f5925058e87bef5637502b2313265f7a7d268c1bb5ada9008fd6c01da67c69980ee4761022d52020a4e2eea3a905fc41783f12068291f85803b834847ab62adc49313ed6f52c87ae37eb8f5204fdb9699cbcd6887dba04511866690ac16199ab8cc8c7b0827dd10608367e664cd177b3758d2d1d3a2bb8e7ae8e62175da57d71b007a244438b459b49ebd2aa139882d373b52acb6852b1bf4fe480a6e351d3a6847ecc255e889ab30701322702ac1f31cbfaa16fdf60526665a88cd1ba9414600720906d97969b2ec9cc8dbea16bb005cdd17ffaa4b2eca181e279fabd82e3734013b7c862760ce382162675d95269bc90612924f1fab6e883ac02697a89a58d4e89f567a4373ebd3bd0bbd279d5d20bde63ff24dc10cb2fe235f7a5c3735f09c3529571ca80b6c9dd9b078fa726bf31555992765c38c5c6480a32da180196656575e9bc1044b9758bce811411fcf7d2f79b2632ba4bb46e4f9c1a949e19f07684770459bc3546a743fc4daed6874e254238b7ec78120514ad4e40858343c960ac18ba84ba168b793da5daa2748615689ed5e1f8d6b3a669b61bb2c0755e472892b60e97f16e0e08557544c43e12ebeff854cdf4a867343e8c32c570b8549879b5f3e323d5efce881b44d5298a23f8d1ad7e026c2df0c58fdbad2a1d1f57286163fd8faf0414fbd9f38be581b686316684b50c2a5fa6a54e2b41462f21a7d503f3aac04aac67c142060662c32aa1273d0dc0f8fb4499947ea15c2d629b125694007e3aabaaad95c1c475def01edc2243c1529e16f86c6bee1cbbcb233b38414f8f800c2ed772f81ea9a89a67405e8db86dce67ac1e266818a31a1dae67ae4f0053697d01f3190302d1de03d40a878cd722ffaaa693647f36581f2b4427c7070a17948e194be98e3eee1b62663f5c03944cd88462c01711e99d943610a93f2351147d0e02f2633a9074efd3295c7f0fb9b3c6c1994c911950e2c2e8170e98f0c85656dcbf80bcda19af36be7951e1d634b7c154a5861e3df779586ad4ac54c34c42e00323b66e1c3f6ce7ae6688a4b7aea13d433bbc017a036ec43ceb13e264e04141a93ddb719f9b3c41cce03eccdadfb9e8ba4ab95dfbd3712054ee8c846cab5f936aab94c202c0a2949f90c9bbb9937ddca230fbdfb10a35fd5b6082487f18eadaa173926a8324017ccf81574eaad92f8ddc70d4585b75e34e82c643d70dae5a300e26ab120bebb5a83c12917324849b9b8f1c5bdf154af4ac99435cb445eb02128417149b91c116081bda59aaf18140ef2bc55207ae028835fa038ca06210bbfb11ac43c50a7657c687a24508b74ed3a693f1a174261006d245692616a5fa3618ef21138a308bc0b946efdd1d8fda64d15e9256b637ad0ef58f0bd7e7a0982f7ef138555a04b57c695924fbc242f4c9acb15afa8416d5284f21424517312199d119dde35c73bd8625e7a54d7d266eeedbadf38b837c9fee88fda35690429069b43fea9282e84d47117a465160878ac87ac67df5bbfa8345a3f86fee172bfff0c0a2184e9914a09b02dc3d5e5f9aca70c70c4db170e68cfa030975008de8a28e2e9b124792d0606fcce338d5e11dc1121afcb516011f4167a9f56430654665c78ce470fa902e53ae49807a477eb9e6fb4e03beea5c03eaa5423744e0ac17d1abc548ce8989854726e764ac4b1c109c490064d77c6ab5d3f9302370a9143a13824f53e13288f9d5b1da91cef3031d3074afd2dbb3cf4a6d7e9c13bae16a0e108297d07e69e248354e6d527f76d9a617475cb14085ac564085c80276dc57d4b6a41031a3feab429dc45e61b3d19abc7582622174e7f81d6337aa97ca672a64d5562679c80d9dccfcb8abb6397cd7f8527c7fbeb5af40ae8e64b4a278b23dfcd0bbb6030ae95940ed9d30f21281fb21abed9ee79ef14c78dceda006d7b6100514d9da46ca952809d4d42f8ae722c9dcbec8f9365eaf22820ac3b907dd7ccff00b164bb74fc3fb8e26ce70e49d8fbd5289cbc4e3198515d2e80ec75eb1f728a6cee7a557ac117f0cd8f189c55134ffe12cd95da9976b9dc750c1b48cbb805f6e6a0e8e928d4cb407eb2da462aa9deb1c0ea86a0a5fbdf9528b5af05241d8519c745ebf80d00271df256a2969e7c6a0be9c8ee8b8bba52ae4f9b12b2c1f7e1f6c6cce1241beec981d945d15d3557a61078c11cf3f229e81f670ab09f7aad3bbc055fb5d2d3d16912d8ab7013d1db96cfcea00e5d10f5c5201aea9dd38d4823f563839eb0e60c862e46d7f228f75acde2270c01685e8994854fe83ee3fbfe42350f4bec63245bdddc83de2640a841670c1e67965d181850347ce1118bc8e69f630e7036f374b6f382e7a6f5d065098bc505d3bf725c4128a4ef7e2a06facb1878d8d2db970ba5d807040a4a4e7d5ebfaf993092dc170719ebfe63cea2f025a64dba471b110192343732696ddef6f38e5fa7e8eac7115758fb971745998cf4e0736073a719a537b3e16d89e999ee0f0f244052adff6fdf2cb6a84b37b71ced93c9915bcaa99f23b01f674cea1600da4f57ed62664d4f120e8da4a1843612b108688b5938086ef15b7e460a15bbc0978e60c7a087725c212cdbb0a062b5d93e5d12792641c4f3bbcc128c8e6ed5ccc0851778ba5861aa36a35bc8fbc4caf891a2fbba244a7d76994c7e7fd1f2cf449613c71b72f16ba0029fb0191e93b45261e64a74832ae3c1ffd1982e5ecb7157024bafd75c1e5812ddc30591b09fd2e72e95fcf45b060c5a2ce914234cb65d1acf974629f98f274ed0903602a73a6391f4f6bf702b8cbd9a18a4ac92b075260403941e4fd136500848f702375294b3b47ef6e78e0499b33e4deb9cb658714d2219cbb93f422d78fa87f17553eba3224871548bf275b646056b6514009fb8a6032fc0e9409750f177df7f303987bdd75cccbab3789b56c794ec7f85e6fb232c4de00a4cd6e03d8ea308b25dc3dee58fa5762708185c82bf88278b96d8e305c9453c5b830103dcee14fd87d1f76da413cb7a8cd30f2e878fa02eae90bac34967f7120a7379d0e0f528db0382a694bfeec64f772e546346ff0170137718ec7d2a33bb4acf155a03f18eac426612784d1ef466f4a258081482589ad54204e5997f3e92c2c1b08190ac78fd80aff472a37ba9731cf50f6c9f56afe1109ad1af84d10bf5ce63ff9c609860912d3b6c06ee193d855de9ef3b41049b757f5ab942cdc0df858e16139267780795e100e04503cf320a1fe06e3a451719a441e40cb9d0e78dad4095f62094a27b7b437232895bc338f5ba0b4938f4cdc406942722940c851e5cfdfc4b93691bd718fd4eaddb71d050fb1c37cce18dc3617782509c7175bd3625fd4b0d9e89606eb7fb2b00a0ed54839804260142eb72a4477fa1c331734e13798affb63729d209edbf4a50c4ca900a98bb4a0f358291ab785632966ba14b57b92ad73e04fcb49e963d07fa266dd8a050cb1d6a95eaa7c18a8c225502e5cf8d4bcccf453f5998aa177e5a5a53130ebfce7c7d7e76ee4c6668412a52a044d6fc987dcc133df96880185254081c738184688e7dfa69fade9376b03d5929143d3e480eb9d3d79c42865384e3f24eca2294d5a8dd6947442cd3ee8c2400cf18127261846273c3362956c65b11a2ad147e658006d611c68fd564d557e73e988ed819ad84c9b5454455adb63458d31bb7d121ecbde3e5e98476d89485bf05aea48ac27112c4c6e1dc68435c69467617ef027f76b7f65ea0cbb34b5c44ca0d8096aeae18832b541f515cb59bb64f6a08f7d8b69314824368fd2f081db48c7ef890c3b7799afa9d2c18201ab1b2202f2e42dc8f672e13f0c9a2be755776c3772181490b98ebe09baaca3d6f4b3a64e93c74803dd2084e34369709fb7f5949c739727af81c1707ff4cdb8dadfc8c00814ae121090c48a9af2bc882e73eb046c1e26b042c46dc3143ad20a78110a0704374546e3884b8640a5b06b57ae60e9600b574d4838281571522703b89a54526930c68a26c3c23f53946535c1d93cb7dc2e21bc772fdb4ab067aa14da4b2719be6416b92c707a27622c55e204b34085a743f77148d6a88972d6c7c3d7b4ba2600a1f59f2b959927ced9917331e3b8e5cf5fb76bf1b2cb3108bee4ff0d0a7d01a882d87e30140c61af2d3212c971f7ce678084e8eef547e0bc65ca54431a0534cff6a4826982847f2b6a8662669b9e48064c6a7e7ae0cc52ae6655518ba5765a7d2c57f4a7c3850dbdb2e5b8f657820f72dc215e34dd75d49c42454afa42b118ba87e195d562e56c346d57d4c2da3f5b094fd492da6b96b41737a2e632500f8d0f67f53414132b3bbf1f17b861de0dbad2b0f1299f402e538ac08b200b901d054222e08f4828b6e9062094cb11f3578de02b4a15db227bd9a111416762043725c2d7a533a518608a0a00ed3608a1cef5b5b0cada9dd7f4474236a6561362710bf3350565a1d7dcef49c17775e161c3a720fdadeb4f45696ab530aad3fd4e190227d0cab95f0ef8553b98f6875b514696f119c0a18c7083336aa63912d980b351313ecc3f66c27a4e61462ee23a0a7303be455d81a6b5140fafcc1a71acb192b30a80b3dbb29d737b67a2238e9c7d8fe553028ebc9726f6391c81a88f33a83dc9e372dae6cfd756ac34336d1cec50485f887843086f90d11b187d6fe9e68062a59a86e14f646e01156911e652cf54cce7ec15d98bcb68b0402c29ef3f8b2ae24669698192896d29aea2beaed1fff9c63aab186ebd93b4557db92ba813bdc8edddcbec07112583da2333f62e6bb075bc12c88f7c87366230b7ee84a3351ffe7952f8c03a0affd90a56745a28dd8f3cabd53a33b7d0e5029fc6af0a8f8168b1fd4364b1c5e2ab7fc0a94a2b4bcc7904e3bb3f58d139c2dc8c5e92a334222ec27a10a1cee06c18a2bdefbde33645c916d10ecef3e38994ec8a8494c2aabe934a70cda2b8f31e1649082252aebc457394d42cbfd0983ff21625ae9a7004aea66aa6c3b6b70817d1bffa9e810ea5599461276c1afc51694fb9357da37fd556fd96de313731a7e92ae2446715380694f9f8f3a652b8f5cda16db33442f575c54c7f42ef0da60bbc456c8a5323a8c410d10b8a134a49954a0c1a6d7cbebe91463756f0a526aa7b369438a8a6f9c8110a68433f6c35097176ffa01fb59786f85ef4b2694518afa5813668c5222faffad826b97ec92c8b0739989096e970b366a2b2fefde632f3e199cf492833be9a54411d9d78c536022ce502ac1deeabbd86a49682275b51ba0e797c18fe92708457230ce35cac66d4701dd7ffcdeb316fe4522f27a3e099653808749ad7d87c7518fa02fee83c10e1067057961f5a4725127e11a210ff99d0f9065629632f4fc0e82ca94107886a2f40a37731c45cf22c160962f3d59129142d642263f280a9571fd1127679786cfc51b9ff4b9f25f0d3da7b2294c84f3024376cc0932069de82f3141823447403e555fa6e04d6511272cf0d2228c98a2749be9ccd2c0292d060e4f2012278271f012e2c32b652360bf6e4701fe2b84adce4a3078dcafc8cb8474215346293afe42439bf02cc3d0da7d92979283d6c8f1370c68f286441858c61fb266776eac6f8dcf2c36755a16f7801bdbc9bac2d4492e6ebf76efe88df38f0d3b87e8c4a3999236902fe525b2db33e36e666635380697508fb3aea391b8978c6aa6585a3b3201beed8a582167e97a90ac9dbc75a6513902f9cc9c80216ab400eaf1e7433c306129641faec246b2826fb711a2b0f5a0d7da4bf0a249e5c8ebdec98ba026563e0b81ee30ba846e42d01be1374026089fa1e58a7f3900c1d04e11d15307900b4c784a3fda73cc3bd8ea1eb81bb8b232613c6fe6344627336b5431e11041e9b5dcfca3955c858101333642a04fb71b99172035d8070b5315a918c639043305f214c083388abd9900203e62c52c9c8c3382c276492b971746b4427865414beca5f1e2610ed5a27e9aa4b453083c5652ef161cc04f1d49c3b6ff5f40444af49ebc71e15de033589e68c5b982e1beab107b85793864a084eb38182c94d0ca268cb4d59c54d93be162f6fa4025baf51eca72dade4d00684ccb84fdee4d5d6ee3e7c85cdf13bc1ec699dfa0018a6e3bced9868757a34e860bb38475586ccb3177fda5b6fdc859b5600580b531706db503f2bf55a7416289fed1c5e877baa0f37871be7c09c0d0ef7b7ceb1301920bd49902f834f0ffabaa2611f08230fb4d2e7fe6e075c0a1765e753b899b6bb6a5c5a779754369f7e4ed93ba8bb5bd4cf7194b9580d8ddb60ce85da4e0bfb63faccb5d4778750f2eedb84bf80c3616f1f8542bea8fdbc22eb978e4a030819d8d705c0c4aaf12ba3f1abe4ea7316bb8a3f94d0e694fce11cf6c935530474e099db288d3fe924b72e99f64aefc04314e637a57454e930b43060d314e992b58148d3fea48cf94663b07f71ab7f1255924d261158c472bf0dcc1630a642afbd931ab7d19d340c9344aa8f4daa86be9bb031a1ba52f730573028c50dd95fe8e7447d39832e3cc2d5aa740196573a802b4b9b70313b173f0d503bad24f0fbd689e06a401fb7771ef79d80d88dc4bd36bd243a4dc198646261176094746ad89fa779a74dbfd518c23d95335cc6b5299e0d6467788452cd09106a3bf62e537faca88892f228851266b1c6343248853388634e9a733005a145c6d46ce45f1921a120864c79a9e5c4ff0a26603e681341c3bb25bbc16fcce7fd242a904b81271a8b07c4c75da1fc39925d9baaaa8575286035fe5d24571ae28c0af6abb6c96171e6bfd9c4ab18527bb2d018c6efac2764855d70044cc800b9ede4b6b7ec38ef28738a21933bcaa25d105dedc687e8b8189650eec4f5d1db1dd32ff0ecaaabcd16caf445bf47d88261c2f869394c525e10a758e30999ea6eae752dc4917c6d1deec71265482f4626aff6845f47be3527caceb76c1153b235776be5e6d2a4a4ae63c21000d750c548477260fe0891eb1b9e9f1bff3bd9de9ad952f1b4fc3cb47d12d2dd4da2e585fa095755e56e2c9b6aa40573bfbbc6f1a0a34871e358454c5c9faa2908d4b50bb10f403f1e381b35c9ebb39d00aaf58374b4917498563dd488e341b7c0aea43de8f160de4cb5c2a2883998f6668a48e9e21d96d04f8f07311f34efa84c37040e92591d9feb84c23f32a7d7ec04f35872b51a8438a9753ce82171e73e0000f2d7beb6f236f2ccac0f548b1b06b4a254df3508dbf3e37aad35604118eb601c0f4a2296ed132268a9b805be5a5851cb76970ee3385bcffde98f07bd7a968552abe772ed5dcbd45946dcc5f6cd302ed6b8570a31f7d356e323f12f77e24141f7fe9c76d60dbaac3683013e17af1318282172ea45f93eda84fb338432f4cc5d488ccfaac24c6eb676c8be18f9a2ddad0c7c02bd7f118d7d49c669fad6fae566fca49f9d9adb279d4b1e6333ff0610e1e253d632e83437d359ecec21d3de502f2fe217923327761663d93a7c80ca6c2ce5993b71088ad7652650605e8d982beb012c9159c317839d61d64a9be669f10e85d98d582c2c36af66b5bdcadf66ea4de334682d559be1555348dfe01e8ca584793797476d32eba446061bc900a050bbd3676c3974934f974a10d3dafe8630fec658cb812e00bf7b81586474b0e25f3309bb501781506e676da8086f2924888cbb7ffbbbb9df2333ae069acbc92f069e56e916ba901b6e9d82067a262d6700f085dc5a802dbe0e99697723cccc2e77526c26f15b34efd455aa20374acc9b6114c6a64e36809af56de7f30e72e3c1b27f92f1ffa46ab97bd003f0ea39ee3e75263c072aa9b567572814d68a0671fe698c82ec6003f6dee5943232214b76d49e39151f3069e13c8a378baf3b4d1d08e0fcd63e09b8c333703669bb65250270f60642dcc63b0eb78bec38955cac4db336834fdda5a419e9352c371372cec4f59928b60ee5fa87080c7458df60f82162acd52c531970f0e3a8394d1fd6ad860397afc96c67c400ca3f73f869f892af4b1a046ee5ab321e1f65b650a76e2ab4ef6ad459b23600150b76b800d6d88d3b36ef11dbb710024165580b33fbc125f073b4e6462a4d2b1a58f4ac0315a3b5709f7624e63a3dd1ba468739dc167c3887b3a9ddbc456e12853aaf91cfc9e2a2259cacf0c5794b6ed9f3962840f5418c6b772cf0724b6d6e3c3da2ac764d404d5e308313cd56ee1f455c5f79738b885cd5da77ad4a3e8e2b33995aef7c456d0a7f5792f19c840e0fd9b67810e2f52d7f503870003c3cf4d7508d50e2b6c55e5b93849ec62b24bc929aa822e139c9b57a6df12b4818a15622cf075f32e9eb4952e6bb9510c109e4608ff6931025cd02bd96d534175587fc4d3e281ec845f68dc2aa060e94465c30bac79c2f8bab7cd2b096e3b2ba66c8f9b18ea34813e2e172bd55d47ef937194cef571262da86eb9ab5b32d1a317e196499a093181a8ffea317296b928df806c3f6b39d64ad6fb8fc8a78b16b1da3275f280f95b13021ea4740c91bc8dad97ad2fbb7b6b2787745aa5a46106c7edae4f062cf1254e8435b535f477dc9f150e5488c20d88f0473199146c83405bc8b5507814d8811dd049c4430bdffba1dc7727fd5a25d5cc547b4b36d52a4b4cb2354dce4100ccad5b59eeb53617671a9c6bedb62c6ebd901743dd9061a73286ca38b830deffa0166562a5b61fccbba29db1056722ee328bc16b77912e85b3d807c41b0d4956d295c6698e070645823240216abce102d212b3ce13a24ba87c5ddd6ccbcf6238743943edcecfd462828d7c2ebd3be4af91e738c60686a334beae5266079160d4aaea3cf278132342a6909cec0090eac12b2eb41bace7b602b7f4e23a7eef8ced7ce0996b3b9891bb51ad6f799985d6850c0225452228c159eec8056261ef2ad00d85bd12ae2b9c5e2642bb2606f092c714ee3c9cf04ae1e4af59d0e0213975ff9c2de2281884701a08c2806e3e876851db50256794bbe278afce23a36d95b7474a3dc80a0f7b6f64ba8d3e82f15fe2ed04432f147e9180561ee837ffec21a1a13bf5e304d39bfbcc6b0c4aaa71b315a4a0f7cd437b090cc280ec7d860fc7e6ee35315ebb906138ec05457f98b36530933e1d391c424943dec3701995ac57221b8a2fcc90063d8a8d12402561abfe833083b300c6bef70d9d53d1b79eef8ad7a91b95e7f1d810071fc6dc24d44f18e345bd4882f30d4d186a09aa3cc945293c33cdd60448b93432aaf85651dade7d436ba4f0e77a8bd038eddbabe862f7818be49d9816b3316eb08e47aaa6ad756fcda54138663c37475ef6184ac38125863b4066babe67e3759b7523fd551df3073cdaa10e6aac83c075303947d746e0bbe7c0d73ff5844b7d15a45e834601395651ff71c1fc2da244af3131809795685e3d11297b38f7b5ccb9b5663cc2aeea5664959d8d65bf9daddb1b2ddcdd87b2895b241a80e22b326d3563c80e56a769087346bbb94a9fd96c989f158fbe28377ea9f6b55684b4fbabbd525399ee01a086e08ff5483f33ecd062dc32b54220315d99098901ca28b3403e0f89a07a893d67278a02bd865366f915f1dda5a7b2c71227c5314a1bc59e8dfb2aafcf87e07433d25c54e9b95a526067e95fc40b6a6fe4bdfeb78f4606c5ac4ce2d1becc716dc2c987fb919d7cdc25d395f2b73d54c4cb6fae1351b928086be2694bbb87e244bfbfe15362386db0016d7ab02864a3ca2aacce0e98015cbefeb3a773e7ffeacc296df5c579b3d0f07052a1b9f48aee5f12ebda73f79b95031bd48f223c4b638f8d4eee87fb1ff9cfb295f2977cf8f269f9f93109573ff27ce7e99b8bb31d79d9ed602181829515d135df8744601e8179f043a86fd6cc057b06fef9af34ba30168e46e553c5b3e0fcdf9921db8a1a419f6c7babd0edad46408b9b270e863a32cf896b3a1ac7f25992fdde4579bf7e95ee1cb161aeaff8af849c32ef86009005829537f0a521ca54ca16886c3fba55476b23068d7a24bff95c6660dacea458e1006fcc4197d6b8bbd20b77f6b14bdfa37c5c495a6c5b0374f8bab7622b56039fdab072030eda1ce03d6a1330e0d33e0046d4a70667c273a5c61af4f0b193b896c5fb99a68b26f0a7b442001ab48c9e5fc04464eeb386d3947db3e14a2c38c7a25186e75330c5a816c210b3c65b43a6001f358bc0c6d8406832c32ad5a3002fc53b65f49ba405240e31b314f5aa1fa4dc9ee366fa7e0c4b4c5680672a4b709da37d2c9448ac1b8a317428590dc47429ae841e25d718682497dadc2044cdc57540a0c67e6c0f96033ad7ed71b48bc524a129c0a4e0735a397cf0e51e5765f56279c2a72d0dd1d59247f8abc64ca5d0519a691b710c8d5516f27b1cc6930e99243c1971c65d27414a9c34579269eb13b7ebc4e999ee7cdc39dcd3ba037af9eda42ddbcd9d882d2e816f85b6e7bea388e452b75b7841dd859f25219d08f479dde74cec802e86c81624cc7229137d0979b3ee3342fb7f949b9ae9fe467489b7b756bed08764e7221da1abec729c513bd42693470f36f84c8205b9e793515d70b8618bd0398b6ec7599ecd8c4995e481770d848d81b6a1d1384646521155f28833fba10cef0e603ab91a2ee3e0e0116b3a7685f474d41122cc947b33199d6262ad28b06047715a68ef2c94b329b38ce8d9e428e894c0197cc66e05ef783eaece0221a2437bf60084749f82e3fccf9e779e129c49f8ec1624134fae6852f8bebb967691ab790385e6c3aa4a35b88b9fb1c6d167b26a43e6d10c3ead4a79a5eaf8fb68903bfc5165311637b894a16a9c34eb438170f69ca781d6885d6a09bbf8bf0dab88a38a34f75d0f427d33b3d2a796156bc3929b30efbc1c1e06e226cebf61795a52104f5f95d470534bd0bc28bd19457a79a6649c7625820554d2ab695f80ca0c708f0cfd0e7d59515b81bfd9b0758fbd2b9d7bc1b59e764466416c7ac1cfa87320eafb891d73ac579d984a3a6d41d82e671ffff9221b7c57edcf2187bb0b2db16e0e445722a1dca40c78154dbcf7536cb2527d87f4d3b56604b63640b6b3978bd10af4ce70d0608c4c66c8d6260c8c802e26c4df85291528d6a0c5097424aa31a680df5ec9a41070144f342d30c5d8a4148283cfe5fd74a4a5be354baba5e725078d7d563fb4939b36681cba48a2ff5ba201ae93347547f696b5820b66e436c0c2731d895458878eda0d6d6e440d60ae1dd3a5d34e824b9cfa96dcd69e8329237df73cc6d8be2becee34b5d7edc53c9503a44138031a9be24f935b8e3defe95a8abb6540499a91385d55545dee69321fc1272d08e8ff0b8fe0a17e653f0254aa1f7fa368c6d4d5de4f29dfa8ff4eb976df7b8f554a571b34aa371be258ac0bd21078f044ab5154c9aa10ed56668627ce492905a42feedbcf234443820730740fb1ca9993869643364dbf4b4c6ff4d92bee72e44fcbeccb7f731f85d23195fe8015f0b5816b8290fb37505751b8868c5365152005b97705289c65e7315b5b69f76193c4f6dbb69fec77be5f305bf426917c0249982589d14a620c0685b22191c4a6365d17b8f92b7d3ca72c35f42ce9af50f627b0029ade625b103523a367a696014f20ded1862fe0e33ccb19092868b48ebb4ad43969951db46390c6940d8c8e80d74367915a7780044ac04df259b3db4c7dbeba6e6f56dc61edc215b69b283639aa5981de21d14b8a62599a0e59538442442b2235b090a1c5989d4687fcc93b9eb0c22ce8207fb6a0389ba0870738319a125d6326332756fe54bfd33f9973c0c35e91490469c317536ad3d848c878711dc1e52952ee89b32f939596fadcbd27145407313520dbb32e13c5229e35f9b857cd3d4604a8bbe63cd4cfe9838774a5d3a72063812d1b66e9fbf1591ea449964d4fd451e92f5daa1a5fd6e6e48a21a4814247ee3ed4e763511169e06f9c197fe00ab271b234f27f993c0c8011d1401b3c3db4754ce7a3ec6be141f91efc5a2de8ddbba7804f99987ac9cc8da33607dc479de58405843f2ae6ebc8533efd97282c32de3de3e835afa38d634d1aa87d76a9ebb0801e421959959cc34efa54b3cca73927fcf653b56de764a20564f4feea9a7a7c2603fb351a5934b33c0a278a49cf94a7822a52b35c4d70899e6d1a0e7b384553f4416387808b62bf3766ce0eedf077fa364347b031024044b52034989abc811bf4126830bd443ef833e5d92f47b1ba082a6232e46cd3ed6dde994c71ab76827e18550cee24921d0f157683a711cd58ef8afd3b27bca1f97afdec1eeb2ee009d895ea9dcbbc99c3657c7328ddb9258c1a347edf58200739425ab30d9c1ae1ff84bf291e616f5b16198da5f74ef9c1aaf881ab447b549e7ab1486cbefe1ca097e79ef0b868d7a501c64c178a7756f7547286aa0dedad17b2bf1c4d8608588dea2b765f6074d57e54dc95030e1a8601650ac749c9395b254de1a79833ee153339322bf006b01e7a43a4b15441857e808593d775c7a85587b56937194434a089469f741a236258cb04a3d5dfea4079e9fcb2711a1dfe68c3bc22d150fb499aaf431056d65079029454728b086999864e2e7fd1f968f72d37ce77601a727dd182715de56e0b79fbc661e66c289b0ad74037a5e536958fc95eaf2c36ea6504917550966164b5ebe2e668ce265135ac4b5c949f9fccaddee62f3d9e5905a91abf536bcc029b51dbd38f8e01cdd3c414975086a2124e23ccbb19818680b2d896a750999a9b34b826e45b95977c74981cd0fa9a64bda3fcd5ca96042f914493b8c8172f4e6558594275025dbc90509b84c5580809d22cbc017b9f923bd1c71b845a928bd91ebe31a50af6e3a9f38d24086e0f3d00688c88cf8b4ad315b6202ee7cb25674e5823ee5f269c7c281bcb207ac90438a56ec79a5cdbce21861819b798c98d326da3c06644a4870ed890150fa72fc24067cc40d02d856ce689b315b63d1c92a1619fbd9022bee6a94e88c25209fe68446a40b229005f70a4e45b509bc78fcabf4b0995ccd9e46d9058ee6e0acf26349a9dc36324903bfc6001880736541f402c5d40350d0e331b822d84ecf43295e28bc7ccebd75d1542a6b137bee79bf053816251ce6ce89883b1918e6f879f1a51324801c53b3edfeb759a41730f928ce53d70eb3a01608931723075b9030503eb38297d38f3b20a444952e7c4054a0971ba4cf911ccc8f3abfa0dff23fd170e0cf0ddab25f5ce308178be9678a6f7aaee4b1ff45bbffd4e1fd85d9fe92cbebb576bf28851cff3924e5ebff27c1fd184c37d64fe767f71f492abaaf7fc96ac23cb479ad16c5bdb4019f4c84fdf17fd2ef546278f4c27f937a7c1bf053772a53be0e7886572a930ba9a9cc55be9190a61a23b51486c4e6c901dc17a6eb4e7626fa93a7051e22163f8d34ff8cb2f151fe5879fc8ec3f09fffd50510b52cdad8cad8f5d317614a36048f3391ce800c49bd09bb91e90f47091f96fd59c1a5a7dbf47ef884e14bcc756f59815555016fa8cc0dc3fdf9de32af5f64ee14ff57aee0a26cefd51abfea360c8984add837d82b450da5cb2cea355f7fe47b63fb196b8bfec78be2756ce9eec2cae5802662eca7f98303388efdb864fd1640f6273d59cf1f32e1d81fcdfeb30e66b9cd800f5a33eb1f75849a41fe574d2c5f0d4e0cfbbbd35186c238f2a7f064367c10594038be2e51cc942037a7c5f2b1a6866239920277ba1a65e7a60c4a23912883c430c41a09d8e62aa53795cea2cfb61624c836fb879a9574ac9ef68bb7fd00b5f3ca7f94766e9c9a7e225502fab91b99e0b8ed631c1ceb84ddb012b8e93836fbf77fbd56001b1f8b18b61a43872df64e6a94bb90c13e32541878b96ffcad4a8ea41924ef319c0a65e6e068b7317be246a2d1744682bed142da46dfcf04fc1656829a733f092bec40124f0027eea838a5409fb4e1fc1b8e1fc245973fa6c53fb9a9661b0c0f9e1ff7536a92488d09ffa3c1b5a1f0a4cc7fe88ab0960d04b17b4d8f79bde38f0b2b24f1ddf30a3fd75b853651b4e73da1c840a1f0fee536fbabb213292ae7b1ed40dad312024338167b60528609f15457ae7b6941e8ab01302c72f6db288ce77f7161eb35dcf89bc84d70d36ee6cdd0ebf1c6ed077bf5caba58ea497aff1d9b3bc4cc9802a7b4ccf2f125482d6e0d1c6c4cd9b43fcae01f82c0562a8fc3be3fc2e534e939fcf2aeaf4aff5b0cb7aaea70a5c1480de76e00c2b35b3a205851ff1265490d126f0df0614250de7dd3bee49b0b39e673f2e4a62cbd0dea0e1f0421cc345f63afc80dbc51d1901135342f71aa1d2d5862118c869d050726d2ee09fef3e1384078a916accdbdc7fac1bc9e4042fa26fb9fd80551a75c67bc8b28c6db6072d070f54dbbfcf7bc826fd172fb42ca356cb4c4a9761107e0214e95cc482d756a342d66b4b6618d976349b25b263d2ec79b53c7d439cb1bbf8a68d0bb0ad484246f111a908c2be6adf43360fc50e093c311deeaec42e0c5818aafccc6c815d3244d88999daa4257510c34b43eeebb2a608b674e2b3bc4c38ccf2659fd923c0ebda89cb2d767b7eb9dc727a4899a605b7f5722bb8882b280ae252dfe0a925dd002ea5db098bdc349b2d9a57d21210eb44c4f9961c7f89d136fb095f9e0cf685788fed0e2202cf27780eeac7aedecbd2568d63627683ace9f5b6c69d8391a6a2b61d18c967a9c050ebc93950dbff161e59cd6f843de895d14f302161659c0018c49e36cb7bd39bb81a8eeabbc06540c5933db06a557a904a3f9c56d9d0786e4118e1b4b53eca846ae0212f930d863b8738af580067a3ee5b6c6ef1a64c1c27215324817ae8ae9edd62d0af33f031de707c06431e6e2de6f00edd47958444e5e7e7de3da0349f4670a6aa1f38ed2300ac7aa606d111080b2695042cc0f434870bd3bf710cb5255fb557f71ac6957f04684ad49ac63ae19987e04fd0981f4930dfafa236c39610edfc18040e8fbd7afe72dfe70a04597c197fb41d998c8449dfef34e650562586b6d09a1cf9610559311b157de674834e0b051f0faa74b196d14074e5d78b5ac6fb2286efa45a9a1d1189ed4573fec3323dd514af5d0f5de4e4affc147d2af527a69fce7b5376a90a4d86678ca62e56f5cb250fe3796fe02ae90c4865677b9874c40ace6bc288daf987dea8c841964cc37dabac6b5885e52234433dd6b00525f3dfea2beebc068bff9f6fba915231bc603b87d7a227dece951b89e2c2057babef5c57559efa59b2f7677e183d53e718e978ca1df9137daf4359a884937b40596392364a48fa217255d2180817fea2d3eee3352533e66b8c561aac40a0fb33c0044280c7059dc11cd783222a481a0cc3cd85d985b13ac204b790a357a845fd1502e9ff07ad4c1b51885e1a2a374748c00af687576219f57b49e8521f7e46055b6d1ca53111570d217560686c50c58615b78c04c1e217d69cf5d5966d6a6fc20a0b5ad2d5c541750704a9a072628af962b36de467b563026438d740341e4d359759609402c1935ad60eaf6f99d06b02f8781af16b0cd381e06f58745309c882490170d5f546aa9221348173440a0a8f755f335ddfb2680a1b6975d95fee57ae0990c4714abcf53f2bf79bd03e8a8e8b09489dc474a958b10e62c9992caa0a5e96853d7f2c490eff718273224720119c453ae3cf13bb8b151dae622a1406ec6d2b00913c26423f6d3ad5f16ea1f1940363aaae7c989742b8530d7ebfc07c6cbee8dfb1d7911c91f1b18e570dec5182dde7846af6ffd200231b46629dc0b791cc674c0568c9e4ea6b8f86fe1ec81ef7ce5dfd505b78d36764fbd5d17cc2e357f95e38e885c71d3c954f17dd6209ca7d83975b9fbeed37dfc13966708922ff9e13ceed8baf7881c543790cb87f5d2f80643703c35c84351cec52905dffe9d645f08bede8eac7b3377119700dcbecfe8850b0d07b1a710a90190b66965916bace8ed1d34855886ae2e0b0ec88e0f50f61730a87d8a062fcd810156c26a3611373454756110286255659fe2515023ac282c454b70f80fe0629c2a5353b1802d35e33e619e4e4c06ff81420ba10bb08d31ba1ba20e252ee90ae239cbeed027d4af5f06cb7482ccf30bc254b19ebcacad8b580c44206ed0ececa6682e8f7ad65e77a43c663103b3dc23cfdcda54e48de000473fe9d6cecec9bf20dcd05e7b32b3ac8b71d522029809e537f61b796e185dc8d63e700f03c07e997dda89672a6e4f6eaf9058f0fb884ad6a6d3b8bef13f6fb76da64c2c90233b4455769bb06b6f3b7a91dfd753c986ff6157f6a7e653d96fd135e32b39aadab067499268eb8650fec8dc0261412928500763aaab80224dd009827262106a2c900a6273845a42bd3d07c5cf9f21138a82eb3b7b2be3b7c264ce752edaa35d423eb374da021dc2b617f189aaec1a368202bf297fbb6011ff5c0157e65cda8750b7e3adf2536720b8cb94419bca4a4ac02de87fc9cc95c1f64df8f02ea3a05e8fc19f85d63cfda3c9257231064f9979553bf202ed6582d1dcf75d8ab168ecaa3860cbc08f1f188a72fc24754cbd51fb5fac4f5bda78107b25d034480fa370074ce14c2f98e31a6340372b6a85b2dd7d1c10b65872871a8c89bb5b6d6291f1ee8c83118fcf4c6876d2cf246139e3c829f00c64132065c272dd26f5ac72cece4568586c93564d70375fc7d510ddb0e60f86a1f0adf11ff636dc17f622a87faa8226278d1085022421aaee77122a234588227dc00dbbe8c6066c38cee9bda5a9e93c8593b917ca05a114fc24e03e5775ec6095f9036aadd1e386bb00e4272617657d12658342e3426bed1b2278c556ded8d22ac052084371a8558bc01933caa1188ad95843ae00892f1479aca1f8e0c1f94654fe6a8e966fb04ad38c19ae7398c83f0b65d8d28d656215ab5ec576a3b6082e9125ca956472edcd6a12502db97b0bba51b225cbfc2e490961f3f3fed7880fa08b59aa3175cb616083a360c89279efbf5166a5c151d8d7993aa16adc259eaaef8c919bb1881ac1a2c0896bcaa74d5b72bb06706a6ecabaf198a65b58b72209d2378b9bea7a78b779289c2ec7254d75c3544a945e6d79cb32f42bb8a4f0b00434151163a3a4d593cbbc6426d369380a067151dc55c3c44113fe7b1e8da9612806087161ec3c75d206ea41feefa987d925e7de71b401fa6bbade669e311a6bbcef828393d468030486ad9d006611fa4aabdbb8abe9ba4aaeb9847216303cda8076fbd851cb33055574b202204a0c3c2c487b8dcd06b37c294674a88fc99b45c6469ab17d8585d2d106407b08eda019babdd79f360aab765d93f407d2f46c367854779e40c571bb6e430fe9930201960b2bfc186d20616637db5bc8be8be444fb7c952140884398ca66b1e00319a88c4f6b36451b2c899e79d3f4a8c5d5eafd4156c28b2ea970200342957ad083f86c10fb690b3b74464bbdfee0b301fbe685ddd420971c29401349a7a5e61a7472f3e2e6d676ef9ebbe86ee1cb50fde19a33ea892cc59b4222cad73022a4a917b128a7bd6b60622979bb85b2556ad77a382574afbcf15e4265c35577811ee9148a5989c3870370ef610b364f21914145a5122ddeb6f87950dd87e3c3ab69dc869e68481ae43b4fd0b36868e06a391770e823e6ba442b8b8453d80d2c7088bd6aa039971a91da348582c4e51fa7eaaa558a09d72d0cb232b291826038f626a44e4d0463751c787c021f6f0ad2dae04ca9cd1aa4d2466026c8622499ef7346d7fccbe7490f2d9df3442051dac959c7d784330b081fd3736317d2cb69ccfaa5f43e554e348a07c7a095eb4101278aae78b6991289f71e3a2357e21c2fc2e0e793a206011848c528fb79c64f53f219d9a6c5b81a1650908ffaa9dc7e9d7ac07c647aa10b15a8fd9c31be18ce4aca22f00499341521cf195c24a79cd18a2afb1a332650ab7802d8d1fe5baf5f42471019d50091b5ff0592954c789db462c19ac469a16ee40393b308b58e6f35104913ac56f5bf340e7a4d0dbae945c56cb7e0e1d9566febe127029447e204513d76f5fdc4a470be7917d9e972f0e2afd2430052b0a9f4a2dd4669f739f8edf1917bcea9c4ed0079fc420fc25f83879e54cfe90781411004cc33676ef018a598ccd600acbe051a3e1093ea39ee4d1fef0718bf9540164a5be16a1d48fb887b09f902676f91636cc4d9ee83a4ce38032c88dfa74628a7b1251732d4ccee2cd4ccb1ccdd36111bcee342406ea787c79ce397508e5fb2503af2b040a824356724e30a502179bb8c6a336c3371d4fc658935d4181f5c1a793c8f89eb10cf53ba5385894efc739781f1cc15a12f910dd0cb5fe0430a9ad4ad9e4569ac71aef71079484e34082b2df19e760f2a821145f0f7048d6f4146aa939984803a547d77255fac21e1c2ac6665f153400335f81a60630f396225f3e2c7465cfd14e6760b20e9d6edaf1bc6705f4cc8cf575e2c511dcfb48c8297d7e946c67bdbdffa73a0207c97c0ab0e262cb5a7c0b733a4bd8f326439cd44a81de72989a25a3d10853f3fbfdd1650b725039e2c57eac1650f9b10cc4473dc3a04ae8f4c4dfbb087ea1c1308b2e0fd02625aa1b514395d709b3aecae19c0c31c5d4c0283126e959efa474bac198aec8a1a6c53052b04857520e8bdbce80c7f34beab607e660bca8bb97ff423e8d1dc7537b1ffb2fe2a115d15c5bdd556cb94fe4475856c94c9e296ec990a240a0d331fd0536d0722cebe172aac5afaf47f25e366b9718e26bf79987f9202a9c829c20745c35d6c2e852c984ab202a6d556dcbc04de62c74ea6ab2212241bafdce3345136362a721c1c5c2019b6718b0a7e736e5acc00333d014d7ad6a56d040afde47d361f19c93947b37c97e24d47655da3327a9f594e46c9f43c58991718232130a546c03cc0c81ac3b12e2361222ec87ae6b40b951a126ee409e849cf57c5d66555399ebe38e0b95e8b2020b79363c5fbdf73e0ddf2d0f86165ed56acad1c83ffb6a1192ce5941e28d3270113c5100fa50a1ce683430c4301531c6e7b1de339a102f4b0e6b6db4f23b16b3fae68f4426b151783312d294e22f330dcedba593582e6af622e1fa298f97de5d2f9be1593b7786e33ecdc2a6ea96b036d7e882fd37e39681fef2b7c0ca90f464bff5e2a1933be6d8547859e972b94957afd0d0695ed206fe0b75fab9f04cc21ec242fb26c8c09024949feba9f5daf4012e25b6deeb4f25ef105f223b0c7d71fbfca6d43f5663f25c5ccf86c15670b0434c33bd77cf31563bbc19106981a6557d2e29088d67c468b07edd397aa99b1a6424687092c6ae79045722b4215280d7ac5f07cb1180fa0c91404b1ffd0c0dfcf0b41d1dfbdb1dc6e2575ad61f401f1f71827f541f9cac859a20b6e699450c1813eea20903b01bb8c29c003e2a4fee05091ca637f9e1fad0ed0227512a2a36bd7e63aa3f66b388bb52611d2e246c9cb591142f0373b06edf2c9fd78b7978f6783a1a7f090bf18bc5fad13bccfdc237bbf7cf2512431cc7c5430f313f0fd7d4884598b9cb2f8632099dfa4836a4a99296f0b8a347754e78c3e08ca9216a0423366b4e3067e76da5298f5aca1827f1d4bad6f03fa2db26c1c2349f32c0ba210bbcf835823c8c420880a0c8ab1b4824158d21b1cccb65b6167bd85c21dda6e3e50bdbccd0888b10144041e8674e3036d6a178b31d18ec327ee9a943954383479c93fc21a8fb543065b21654a118c13cc0f2c34c7889a633f4904931d5ae0d2405b973605be9cd9fadd91536d990f5974d7970239065addd0db7ac20f966b58c0cdc9f814d94e6ad31580f27424b7189e1d3b7976e03d52238ff767396e1c4f92966747657b04611fd9d63fe2e0811485ce948c095fa66ae95417786cb8a647c6074dde996e4f96d5200fa995f495de60bbd321bfd2ae379cda28fc82506666f15e7672fcf57298bd7c4b90f62d4ff8c2c8ba5ff6d285714264c6a6821324f29d794f9c40a74abb0bad2045c327a35cac25c12a6f60f64e035be2b0b92fa2ba60f9e53e183229f99d0b399168174bee85f673526349e43440e568724b72eb475515c35feecc644627f181723da4c27aea1a9d9eac2474b71bd45675dc744506bfab468cf8a0dca0b2b01c517c37ccef474b744b3baf5cc8658444a1c87c849f550b08a2612abaacbfee646a1bfb6a41759b1a0b44c0de52b38311ac897d613c277d5efcd100404515274294d4e93d9c1da388c9def1ba4b5fb66c18451f11eff5a5e4b25f62257a84f12aca9a08de493aadde18408532f7646c6bc75071a17773b2963e8863a8ad04f10f1cbb87670858c9773ef6195aadc4062e7f996f310e0f8a4d4aec1a157e4fba89a2646b5ecd292a75c1106a549dd58f3835089d5b00e074a822f49b00a10753b5bfff2b12d574d4e31daa9faf8a9c348e65b610a61d3f22769962ab6d4a853f17c532a59a29e36f93ad5c9db2ecae4ecb64e59a838b57e599d04719246a41ccf12cd3995bc7766229488946189c972b15ce6cc5924798dd989f05db20731dd2d2cd1f20a94c1063bb75b9e4832d7e84d6a1a4de7573e5673f217bab3320e470ad88db461ef4a5d85e6f00d782b8db003be0c420cccb532a782b8608a898f451c96fb7b92f3129d7a458547f8a928e373db2134f70a36f491a42164b162ccf162496645d1d8d9c4158ff59da588825aede81c172a59ac534ddf4a873793b168c1aecc059d8b5dabe17a2b865b1d32d0dd6ff646195ce9e5f07f684ff2394ce5f6e7f449b19574bf66df92abb02ace7520c1a4444789ee12abb63d8116024c62c7abb87e640e79ef8f18d96462fb8dd631a940e300c2ca9ade500ba0aae6152183d96bb6403537878613454272ed7867e19cf02678358373aecf4b66571b0f83f850b5a7c652e674ec8269e56348e02be54ccb12db18cd69b23767e7269993eeb8d8c5279645ca11ccccd3d2fd1da18d5afcb718d96750f6bc590184d1854c8ed27ae2798207ab7c2a3ae29907e1eff4235f7ebe8040cb990a7051891757bbc9593d08699c76d50ea016a7aaad68870448f34eefd45660204fa03d463e1c6409860edebcc3a27cb4b508ae45cf2d2acfec0d91f51c4fbee0c1bfa261c1c47d152ea3a7aff3a8f065a3e6b192892faf85a03efc5b5d6336941fdf590607ef8acec096c22f39fba11a00f6672fb08ddd888788025d740039f11758817a72c055ec1889daaadd3e73baf9df1625a0891e750604b302069619fc0036ed6c53ee25bacf4ca31e1fadd68c77ae542f70dab8826b00ed10106a541f99b5f54a5fbabebd0d991ffaa927fa78d560add28fe08fbd66145349c2e2591ee4aeb1dc256fd4ab7352f44310240b89fd3bfdb9d289fc9d437379c36058bf256ac3a63483b4e20bef9252c8484766c03d68ae5acc436ba65662a80b8ccb08de04fdb449cae5ce49c4319e60f5ce8fb4972ab858ad2e602af963f92054348b38249379e1ffabb2df5b9c220cb702f92a7062177efe5bcab725c5e53943dfba69f310f71d429d497d96ce300ff1bb5f551f305fb3492fca17de04273f2bac0194baeb838bb000d2556aebe7d40c31255ac6536af19b34a8a3a040718dc58f167d3d16551c87a017f87647a27562ef4830a5c0430b673508dd90c55260248731939ebc4bdd7abbfe6e5e081fe0009e79371a580ddf7e0717cb251225f42b267a87b675ddad92aaa3753463bfd230b8ca0421a22e61f57bc33b0d0fa1a2524643ea262cf51251f016a331f29ddb3d8749606f57fb6584b77cf6b90f742b7c7b4bd0c0161db3b173bc0ba93a8f554e5f09caef6ac7e5139112a92528ea04e0be8099047c3e640f55db57bd8969c0b7b5a99feefff4d60fb8fad07eb142c21c519129a669825994fcd0acec5602c9d0bd3b59470f8b43d8c0b730eb4b5dfdef619b2c01729bf2d596bc9125db0774e08a742d1a6cbc53f436bc485b1ba429198ba7cb428f9bb216e6efd17b20676218157afef3f81ed0734e471f72d90250c17a8dd75614ed910756f64d01f59be57060ab23c484d8f196327c48346d1929ad0cea5c8ef4e918a2584c625ad33ceff46f8952f4d7607562ffeeeee633de6d7a2fa421ef27a980da427cff4d45f5962c1a70d6b89b8ad58852aab47af230092d01cd60c98321b4aeddf0027da7273549306515d914fbc4ce5cd25c43ccb5ed24379381801557f19e1d7d2e14ee0e256d89ab0a422915727d16cde6dd128d9d03d11b51d5c69d7135534ca6aeed8e5b05881fadbeadafa458af811c91757f29e1cff2a0c5e12ee76c5f5ad9e80c4d7798fea8bee7b8a3dca2e66e4ae3980c203378dcd52172416d9855a0d0038099755298a1ae0f0b86acc3392c5eaf17c71bde76a3e32f3963e470ae263c00438661afe371977a22442d9b837b99251b354b08784bfbc3e21cfc5134a2c68a94bfc1b8027819581db89aa3763b775864d27c978fdc8746b1ea82581d063026a2ca293561a8b1c0decf1076a18aa4fcbe73837f959b7ca8216874b03103fe1a43ec22340878ea697d5d770bb87ecabf7d56bace48c11aff6a9b08309cb341b7539e3c707fde42f72c2166a044824360e24638c31065ad1b82692c0521a8871ed51ac032dcce313f6510e0a4160ce97a3deaa8e68eb900d87b7c2726ef86962376a7e36186fb9175607a6aacd97b7c406c367e85bf8f9a6355da3e167b538bec7bd541f191b1d2a0176dc2c4832138bd293b54c93dededf8410af2f72e0e37c4e033f3f8980617a9e2f4341798a5ab15a798d1660d4028dee75adee4d0af74649470a228571e9d94b594981a70a84916add37da6026c110e9bfda93072314f35a18f956a49698bb5fd8d12cb481e51ae5dbef7b5e53222c570be1f9955b4fc03d825b4061e195b5ffb07b9eeb3a750f40e848b817fdf24694282d0e17fd0fb860811cbc7565c19fb4592545b8fd971aaaa25aa548479b6a5babdaab57ac836201179f129a1333f085388eb3986fefd370dc56b88265e303ed6c0c0c53fdf443d9502d28835768ec33cb110f7d9dd9ad3f01c39d6585f8cf0b5d2aafd279c421edad415dc82ca599c8ac29caf5d6435e8cc7f9a52ed4cf5ec25372c543812469789f28fd8d8d96072da1262fc9b892ca149acd93dc58f18fdcfdf45728238554a64821c356a1ec56461efa9926f4a5e509b7715c341f4eb15237c60eb4c7e2b41340a0450b4d1c0173e7dec2bf4c0a7248206192cc37916e860be965b151587766cdd785fa005d0b20b2357bf7717ef8e240c619e8aa01c627b16de979b3d56cb8191983cdddac1893911d6b4dddee1591f6a01126d65a492b11b074415e2c07590dc3f015c1182b92323d2ef456a1abe24fdcbc12cc4f17b7734be358be389844cb19537b0527a42cc7a685c4bef8e4fa2991f0bebb45d38ba4f3f44cb51b3cead60c764fb02760c51dee1e32a3bb671f67585322e23e86173630c0413383e7335d77df0a96d0ad6a6161aa14a4d4815a3d7f6cb74c4337da781761fa92a77e06934966e735eee1f9340410151d5de5f0247908ac95e60d67ecd78b9e0794afda2c47ccf319ba1389eddd974eb73c23cc763b884de374cefb9060dfa5ffd031ecee0b1119600ce1d2d069986bb1f099c0b036b1962c54a5ab693fe44675bd83850079da9fab5f3a073af9cbc2154779793ab22e7225a272f7dc599386f61ea1a2c148142d37150ace6786b2ce67caf2fb90ff46b99a616fe544088d99d43549b818211980f5e374d0aadc7b38ba7162aea7e785548cbda4ae85fffdbf7afbbba4384c634bdf9f97d2f1f5bd540689501364980a0e47b9e9985154f91ff9407dc2437901b9ae648fa30021fc638ec2af60846bf3123027f2bd07e508da449c3a90701a4536e0781313defe21cb11482933251afbc50b9164c01b75f9327e7ba850d458f7c5c27632f825c9c4585885ba9f735b8ba78c2b56740d6abd9460069c5d02aa71814c5e4010424e079c48509fbd9d87254ec77dc26891177202d14e2a3955c91b46bf28b6354fcc4f99f5ea70983f81cd73b51d11e662407fa4dede007b5248697cfc3e000c01690dfe98597fe37b1b3714289b67ac7dc86f7b77db724b99929429b704f104a80477766dec707148a66463078f7b677776efbd3317a14c9d973017b219ac07180960383c2d2ae2869033e9d3592ae188e082f2c4154c18d8d431ddba278a3c9564aa2ae6d9e83bf2672af98ee9c7dab1c88b8a6e2fbae8e26c36b3b163cb8cc717fa640bed2189fab0042d82c160303aa444049986408623f758866c48b52dd2e7ad20b1e5615e248aa2488b6614082ba5052a5a24d222f145175d74d1451769a92864a2289b55516294a468ca501351f48fc5542a95ab602caed2297acc251689f090cc61be63c23277e46c478e335a98f9656eb1b9154571c8771e3063642fcb2de2d0c762624c8c4df1b38781cdd0bfc83136c4846886bea585c5863445c533994c9c899fcca0cc4cac391363be3391c4c498288aa29128c3c58484a702134a2975efee9eb5d65a6b952d1a5488ff7f57ffcc1f49a3aa2bc4fa081b0c8e87367c17a539b42d2f586bdbb61582a769e34c6c7928d03b27744ff70a3f9a2a5dc75f69cbdaa75572b6eb68784ab8ee5adf29ae11030c8c90439c40ee20ab136c249bcc1df9d32f2cb37665eb8f9b95c8de9de04116a315118ac9623c82c631a8729c4c867f51fc9ab5be93a5a4f87f7f1434e0ac22ee50074ef0f6f696e0cd04df91220a5246ad76fb82c2ad0341e4d7a13e35a48c897bba19a48f7529a791283d294ad143a186784513bc6bed6b04293d51fcc21c9b0a10701cc7b54c06050f1ebe834307997ee5e13bd68e76a45f47aa82ab64085f69c4b2d609a5b4d24a7311f1f0f0601ef9f1d42aab9492561a004a29a53cdd0326b96bd624ed56332cb72ddf0e494e8e0b735a9f0b3826332546b20ad1134e26eb3b6b36c8646fefd4913b3566f774cd09b87ae2ca2cfdaae47e7e5923fa407804d594a83c0de2314390134ab2224231598c870cea710cca525a60c4590b8ad6de60b797b5d676b693373b4d34786a08811ea0943efd1e57e62a3105d2fd218b92d2ffff27219aa064d908c857a8f29ee5d7e56971dc45e9b04baa586d569bcd66b3d96c369a1a96bc49cfa3fc87abc96469562b666a561af8f1bcf6214d9d37ddf6530d431d3434185ce9a8c1290f955aa1c306a3b00dcb632b3e8cc23658e8c3365f0aa750df89357540d64b524cc3bd3c13cde42c57ebdb8fde99424cb57dffcb9891dbade5184b7932cb32c7fc566b6db5542adab79a41a81431f43385101942fce1808f98ec47ce600508db8f47a94f4a653fa5f724b11050f020f4336f3a1f23ab905c1784c8f94ad22709e9c3e15aa4c6aa8ff69161a45cda409eb8d29ab5dd51a5846f79625aa4a2294dd38fb5f7ce94e40d759bb7a1a10a0299705da247f754fc61be4bcb98d84bc798a69fd7bcbd8cb1623725a48c892b8845ca903f73e63b3f676f4428d631a69096f144f20ce336d80de6dc57a5acfa7f251ffa2e3ea890ffdbed769bb79c6ab3b1c1c3906e45c62f5fab1175cd03305d92268d0680182e872b8f2d1fc4b1a4945f721314aad631a495ae8d58c669abd97258695d6925b55cb45aad56abd57a28c116a19f0c988e1065fe020c068b113d293214246b2294e46130a2144e5c180e3da59340c81e2409ba2f1216ac1949605316a1425c6bffe9ffd75abf4787bd300c433725d9bbeab24dee2a56fd731c676bc51c0854bbd51213ee31752c36b1660fdf913f9d65e1e8b85c978d2f9c0dcae1ce22011934f0be1328e528e59842a5505c9696c6a5a571694ca152282e4b634d0a75023f8f86e6d6d0d0ccb05e1ac8174b9c425dd673014f2770e9f34ee0d2d2d228e5f875266b39eec32fb3349868e84629471a6818979696c6a551ca51ca71695c1a53a8148acbd2d29842a5505c96c6142a85e2b234a650291497a531854aa1b82c8d5746fb2914ed5228da9530b3657574e9c7142a855a1a1dd3cc9f203dddfde481cd719eed4cde079e64ee0c4d0dea53a9e04085940d7e1256b9111752a9542a582c52ccdb54e23a2bab54e645dc572b10a8c32b160b9b3b02949cb9f3e2967164b1484d2c361b0b162c6e5aa06646d3e8c511a9765373a52865b1c614681fc78202064ee81e19cc005be31cede346264c1daf7b4c4832d913a427376496127b0002cdac58b0bebf333317df197c71cd6a0e8140175c32651f8805fc64bc930dfc6c349b5846486ee44648b22732d993f9a4a8a8850b9a24e24c9e96a32f5e80408fa977b893f867d8b13a09674448e845786791196bd10204aad8c50743465e94aa03b9924996ed6028896c68064dd88b172fc2ee9e53b646463264b676db368b5c33cc0dc607e01750e9839c6fcb38e6c4f87c25b1cc70da6208c08454abc9c4298aa128abd0aa003819df0c10acab6b309e369231dec4681f37cae91e0efb282b5a2289aa11677d947d93f1998cb229b359abd59a0102673b292e91424c868666cc980142081505a51e4ab2888c8100020821d0a0b63361894b450821844063004ff3311c36680cc05fc9279938138b339c414e3880afbb71efeaecec804016ef002db15baea44fdfd626423718ebe880401dd6010f309be7abe1c9dd417e98e3cc3b753adcb58f8fb64da69b4df6954c9c297d1ec98bff0f7b0ef8f89db4f371a0e2effc8162ffda7bed94d96030189030317c12872d2e32cc185923e50e294676982c5b0ac9a59a8d1c6292a584c1602afc4813c84c79132365bf66912ca51c132583f8a70e8f0e08b3ece15e62c29a3a17e46e8988dc81e92a625ef85a9029b661bc84f1c0b54982041b488211207b2d7bdb9eb20ccaae8a8d62d692373f4431029bfcf8bd10f48061c10d6eb8410c90c01811213f9e3539263ffea993b22c14a16acdd60c144262160a304240226c322aacf049999d5a8879451481320c31898e636d20d2d13c2676ca6ce2346de2f494dac4e9fcba2a13b0400465488d881f375800a302e48614252b416644b622455b84dc18628c727362c80d22642a03c06e298068cd56b843162a4a64b1f242873b933b4a96248cc8d2c48b24ae4cd08b1bdc0bc307395c9a1b865c9413183029306e23123e8c4938398c4e44318a995d530b3831dc0fc90c1fb8dd8a830b19ee69042c48fac105946b734749a221d55e80b99f3b4a12521049361843dc2f7794a42c2ec4242fb0580e6aeac8562a779425885a4bb557acc8d1e10f733f2b28ae3ff247822b747c0a8e2dfe562958560a8f6b4dc04b695ae155c5dc63ffa16354faf3f3d8621d8f2d4b87e31af3a79f748c8a6b4caeca99d18aca95b439dd87f45e36b7784c3127574aa10a787dc34287fb0aadb53fdc0659426e63990f4ecab55163864bdc76f7fb7d79369e9826a960f1be6e60b8af36aae1ee3e6407dbf4e1f50d0b22aec4ed3f6e83aca1dbb8c674406a8c104c62c07de34d164bc96d2ca7c441e99dd6ce1e8e54a526b9a2f55818cf6a59ed58c2ccb087013e4bdca3d216441b524a2965ad392d5aa7848b2eca66559424299a32d464766777766777164700c8159f26122560820387d8d0223ffeec0e47bcd793c58911a70991217f8b204fe495ad159f6007140c65b9c188177e2af5916badb5d65aeb10b533042830a29fbfa3180c29c84e72e068f2eb60554858d48f2d41669828aaf9e1e57a31a2f92166888c6d26489153134a170621b00922991b867cb4930e413c116c126412bf22221d0e2f364f052d58946ac871da8816645758883511c3d145e9080814457028156d40c85864051c369821c1d104ed28880788e0606244e11544a31caad150b8d9b25feb2c094c98e4aa88b1706d6cb872051e82d89e1283a231bb700205930683c18e44d1a202203da50363a6307eb860ee285aa49440cb0b5c50b95cee285a6090e40a102d4ad0aee78ea265091b19b4b828cd224c6389e90ac79de30548dcb152e852d0414787254ee1c76311e6be46c8d415e4ad706cf10a67e998d8aed0f1987ad8b9cab231438a9d5563b278b82f55b70a5f61a68e5f2d17a5dcd13da92c318feee964171e93b58345140fa878078bc96dec49a1e93d4154eb110cf4e8b0ffb77c3c010d645abf8ae90a02f36b4fffc71d6b8489232d0f0eadf4516b25eaf6705f570292a7016d93ba8bdce188dc12db2c16af6fa4cf8ce57eefb4923f3468cc90051357e220b7cbafcf7d5af54f25e276585610d79faf62ae9a2a06575cfd5692c94a12cdb062d34a369961ed1d8e533b9b7cb2c4e7cf4be6563672a1262e4dee23159a6489714c706b721fa9600308ee4cee2315845ad0eecd7da4028c862bd325044209181db81f0c421e6e97fbc8470b4224d73b7231c68710889323372a37dc0f071832a8618ae8c3f5721ff958c104b7e63ef211a44596cbe53ef201a3b9a37499511bb8a74295c245a3e570d16834174d872b878b46a3f9d72e1a2d878b46a3b9683abe3f4551dfd5c7db88aa48d009aeb9e3a2d1683453cb90728a0aca08e1c4613b2146feb8bd132de68477174d872b872b29c9f522486fe58438e10c1180c879e9fd2d9ba391ed235bc68cc9d45d8a31ae5f07a8073febb2b56b1b756e34da08a12bfc2b8e42152b57925cef2e9a0e570e575be92b9df4d2c33027c4e9300c3f10fc4cb7939886735c2fbdd58a31001f12bfa40b395c345a0e178d4673d174b872b868341a0d575351f46decc499aa2f9c2a3c2fec7a2fbd7be68781a9232fd05d5e49ac42a680219f913c94deddfdffff9d524a29a50fb8930419ffd34b7777772ce54bf9ff4effab1e67b2de75d6c479edeeeff56b777fa7a07cc09ffeffc6656c58ab52d15a2b5d514a4998f7b3dc8dbbbb4b4ce9af7000e4631266f84e030306954c316d3c95f3a8fceaee6e4b6baabf97af1cbeb057ba12bfbefca29452fa2ffbcaa189dc8ca59452eaf8e5b793c194524b29a59452ea81df077a4aaf97e9a8dbeeb7335fb9ee6a739a46536b28eabdc8eefbfa724f2f956259777ac78425ab9b6567284daba69452fa1ed8e0e1779f3add3dfd294f6d7116f5594ae9d3cad98e524aebed0429d64c1d8952eae269e5a03ecbd9f99da9ffd27f7de03bd269becc75758cc63cad0f3cc9dc194a29d8dd327786a60675bf199aff477dcdff7f8f4acd7c3b5d4decdda2391da331a594524a2b4d0d8a529a9ad286e2d777b516d72a3357ffdf4bf1fd972b255dbd5ab955df2b85faff5e2bebaee28a1072ba7b6581edb66ece4963ce3973e6f4e9d3e7df64efeeeeeec972ef6a55f2bc39a7e339fd954657ed7dce9f52cee67467bde69c73ce39a75fd895787e375783faffff9cf596ad7f73fa4c25c5e0e7513c29ab398eda315f393a865677ec4febe931f8d9dbe3563a660cc5e17dead0ee6581ec983eeddaf5ed9873bc9646e7bd3e98d8c3691f1242fbf4c6f3f0347926fbada6d723db9b2debbb9084d0860b870e04341696bda9b9412c83db08374f4b75c3803bab9532bae72439cb8194b21e012d63e21bd646a57d35dbf8367aeac84a95bca3bf338060a6cad10c3c7490bf07c9165b9e34501d2141fa37412808216a803283211b55e194ffffc3c712875454ca01c8145b8aa4859b2e2f9e40e7883f715552c5c95083235fc28f5a95e106a2039951f10521b38420e28b1c3822dbbe8d1995109bdd2391680890a932c00c63d783aa735a29ad1fc470054a7e61a32b5492ae8c30aa5c8162063e821a1331519d1626ae787de3b5c91b00305c4e04dcea9e1499c33cddd399c31cc7610018b935b738dc608bbb2cf0e3e66648cb6a750cae95396bf220777409c575b524667f28e7ab1a8ec3e15fcd9793c3129ce1309e3a26dce18a2f873d0f3768b2d85911086fa898a24409b2ad768616638badef38b6f8face636bb15d7140826bb1c53756646c599cfde1b0b5788696e369cd90c3ab2188ec0e5ead153f9ee1062270936db7188fb0a0851147a0c0d8e182b98f8ef03074bfdc4747720899dccf7d7464098e0bae29f7d1111a5ccce0080b2e5e7069c2e080e0aab0d4702dd20b1a2ee782704603c2881035301244d1f5dc47465210c28c14d9c8600829bd69fa899a4f2557396a6f3a4085bee3d66788ec58eeffff2d27f32f4ff3ffffffffddddbfcf1de3ffffffdf29a51eedee6e53edb8eeb6ffffefee9cbbc9762050e37777f76aa25e77ffd7dd0efa9ffeffdf72efee52e6ffbfbbbbfddd5be604de53072e78b2bbbbbbbbbbbbdb57afeeb2ad34cf39a7bbcf5ebd26276d87e7ffeac5a2cf9a33ecd56b26b7cb4e8b27bbddddfd3bb7dddddd4d83a3ddeeee4ebbdf7ac773ff5957a652777759a9bbbbf707fa40ebfca4ac5fdf7274e94afcd57274cc3fada6af6368fda1e72a1dc3d9aecafaf5e95793f7b4f6f7e0385a9fe3f073958e01673f06fe7e4f9ececffda9d71e88b912d3ff80f5ffb49a68db5775d8a1b5d60567bbdad3e451295d5c29c1126696ac3779ef792b5f1779deac6eb735752bef0329066fc870ac578ffc2c1a13ca4df8815352d938373ff89dba6350fa2c21acb5f7fe0dc2ca580b42ae500d342ee46082792f147102222345e6c8080c34b2966bf04f1d1f138d23188e589c2c13832c834f2090cc494b11f80211ca046c054e6ae311413c61e445952317828e8488744cf95b1c872d132d20c95c01321d8283231686402ff2a53c62416c2d31edac90447eccc18d122b1ce5aaca15ab00816a132b5635638523f9710a10e8392b5768e48a6d40a07a332b12829d5c71aad66a73ad15dffa43ae18d72752a6a88043814095e21a2247e4a7921fd38040bf82232235cc1459f98b45e6a60643029938a8d4ec5de88e1136400013170000180c0886c3218118071339cebb0f14000f4c783e5c5c2ea48bc3a1481003290a63200662180620c410630c610632a8ea08003bb571b1b5d28e6ab92538f456f188d0a93bc2db4e411b91dfb7b4856b84d6e480282c8c87b213b860ecba34d473f9b735bbfbba91cb070e27583551eb3da938d2af070a9dea78e31d45ae15bdbc4b472bb0e231cad3c4b1669cb35b5de25156c939cf179ee56638e10ccfecb6a85f71ca3d35163060397332c2b945e7be8b899d412132c255c1f44537de41fec2f8aa318cf66b4d8990abdc9bc6ec35b76dde7ac80fdc36a53e9d587abf2f40e01e3970815631636c60ab0a1156e1d4f90f3bb46b4f06b1e5cd616eefa50cb268764122a6fa3d35d117befc737f84ea8b99db2c2b047e2f1df8604004e03229edcb7c70a7f661750314339dbff564fcc0e9c9a593a0df6f6882278635bc7ad7f242e6bc7fc8b650b4fe4b03d8bbb5a6a20cca8cfb705ff2d320d58f8250d4e15356a630b331527f21d78f0b09784f078618b31f88b506c5d3532bf9d045cc023420f54490e05374bdc7cf38d30032a38b8a40b693497c3e92b0198836f00edbe47f10128bc16a040f0755a466aa3360164d6d76a313e9451b5ccb67e7a33897cb09c30f710a3d73ad16548444cb207e87903444ee736dbef78077cd2f9adc6e9d6d3ed530e1a06a5634c4d4df1e24d7ea482951f4d033d2e2eddd15e64dead564dc9fccfab1fb39bc2e9c18b095510f64e817100070a66a3277ae0f11241a121fcb9dae7e9388d17891a97f1a180b4c38bd1126224e5b5f1888cf60c1c7c3f6c8dc5c77bfd82b912b1b53194cbb4a049af1ed9f20d4ed3bb4663028c538739f5839151ced87d66d92105c67058f6ac9c9f2c29ff0bdaeac1800e4455c7948ca6556a6abf7ff36e1a195a095bbf4daa9614e0733785d569c6900a7456c9b1520e50b3485d0470d4c4ffc09715281b1227c1a850f98634100e43b1cb279f1ef91e2a0a7458833734d31443a01cf2af6409bd49ccda11d47271ae8d5ea0e2b1df7e0d5b9e358ec19838921dcbd474a9b967b9494b4fd6918deead420956a79bbc567fa0f0f1736d0a0c00ab61bdd61fb4dae7c1c1dd7fc32c05bbc67e5e4884425336fc30b1fd3be75c93ed4f1222ea65960f253ca2846d2c0f4646142dee13f2dd0a4587877bb31b64186d40d1983b70503c40c61f9fc363133d3bacfb6e5a6d6eb386ba0c6d09ed88f1daf1222abdd5a1fc352bcd6c50ff4dc9dd38c2c09b5f1fe02413f3673684382a1dd44ecf827e7fcc956669913b75a0ae217bc764f8efb0a243667170f49b01417109bedef089cab7c89c627a9f7a7cd11493dd61ad6b4764e46300cc5ac8eb08f3b448148dd44a212f5437073bf71efff21b519d91fcd1729f4fc7684d2824d965102782691e37a21667a4a99c44e0950d4c7229be3e1faf9d1031a03029070b454a7e32543bef3069c915bac9b164dfd320658d64a7573dd8875b1376feb28e972e1266fee6bc5c75bfb808279c340a64e7c16e01d88954b8a5e38e6411fe04adcb71e5b9b20d20001d2c3b3489378b067930d9d171d2166ce7b6059692dbc6b14fdcd90ab2ada6cbdb63c0177a94aa6ddeb62a3139d49bfbca036d955ca5d145d29616274e6074620e8250891af575cbf83f6842ecb19b97a1e8c4e684d010dde9685cccdc21916e103fb0570e37e309a892693f9af27a698227b21cf152c854629ca0b34cc9a11a5b82278cab805ef2a2fb4aef2a3de8f203ab31848683d4360d87393e574bdef9e349ee0a42f7c1e13f0e620d005ee4364af3269467a237b46bda1e525da1d3df2495a4cea457a4b00acf0597da411502a56c7e9d3cc79a3f1d3096b2ee52a95aee1640880977a17450e8546d5ea6f0808b0c8f95c8815b50ab034236e2203c299503373d5acb9164df38d7cf932b14842ef4986b174a9b2e4b87f125d2a8ffd96d5517aecdc05c5158177925257489e9d90bc2cd2d0a534717bd0b2e8f6d4444f5464642f883294c9468ce820cab0c1d66012be6bdc7e47b871e7ee9bd53748e9f69a75a1895a1ead7aaf550686e75ceb529576945f464141c587a4cb3204891a18e16122232c485208b71c93edd5ec52daa140c96dd4f0b30dc7845f615e34a5a515e7cdbca068b8b1767bd0a3711205ba86ee9d3466d51622022cf2c34f7df617bf6e2ff800f9fe85e708b8417cc4e8943b75ab91e2f0117867ceb8c38142b34a2ceb90167101b8de441a66b77fd6e96f6f826d3006654bd33989cc79ed8582d574a764fff390e2e84a2aaecc89409f8dd638dbc1c3b482b9ef0f0c0505acaa620c53473345c9b22df075a3130245c7c962263f3b1911645aacfab545a1874317034359eccc515a07d2021d340647c1d7cb00555ec111cf988773c93042087c75c7cc67b354c5d2b3325c5cc260cf05280f81744499cb1bbf2556b89e3866e94f2fdcd9a112cffc07d51feccca81b3640ba1224109303432c4625229f5e8d87c620118894ce78261509d979c5a7b1285229d5c3cef4e688978c40558fd2b6350d0c15359c7e58d328a8326c6eca1802fce969b51ac6fea73488359b3c192ff8a0ad9862a79f2cc9463e5957bc0008058878fb6f122f4dbf4b649f0e61cfa6520f9df547ed9cece0d4cdfc2ca233347ec2337e9d0bb503c31b3c221c7080c828dd2b5439409105a0ebf1bfce1bc31032d85921c6443319c7b75c39cbd2729c9d510ddd074fcf1c422cd6adf9c80a7c3f4b091a699d1e5bec8e3bfab5201c630048b68043c31f3b66df91beae21889687aed006a7d745e82c50943cdbdeac4f3ebbb56b81c14922723d8f1e163ab24604b350a78632168fc3e30a1ff41d0073f52684cb9f089405181267286c475c016b33f306eda46ac0481d4fb529ed3c348860a38e373818b30e32f97b8bb124a366264276c5c5dd345b9b1fbede1ee200dcac8e02e2fee8dc9537f0c9e715fd6f6554e15ff007cbcee52bc1cc1e088283179a5adc121dc5e240b593518c8defdc1a41a12ab8ca33f19833ad77a39d0d80c2326f993ad123838a40757650782cd4660d175a2ec5195c84a008d94d24a6c4280f6eaee278807d2de44401291366e1ffb067dce109852b637b10695bd1226258241652a93be4959341cb5131f29e57c7ff4410f8551044860963b1130428e467c38a12858bd45a9504565d01f6ba0c901a2b90acd357d4e0094e1bc5e08ee580141088ea9d41bfd321ee81d8b055e759ea417e1ef71aa9f3eaa2a9d730b028a003cb997062e0cce01c6842ca64c94693d1a10ef3d0ced97afc87991acf5cff9fc785f2a13632caa1976a054221de0d73272465f2ca3eaf7f2be160f23a6a61b8a24b5b6b2283ea1ea3bf40f20c75d8de09753543dc80730bb09e6df2515280c52e3a4d0861da1ccf871f04f18a15cd8b00ed03fa5da1a527bf617a5d1e071bcb33e5f66559a8dd31801770a34e0acd35cc1882befbfb885f41bac8ce8e1bb42b18c16b5d28ac308b12d90a1c20f01c405c8e8f5c38a8e3b09bb614d92bfa3cd07b5f55ee47073e6601423ae0d1cf349a2eb1a6c3bd4043e8b156d764571a3b1eba08519a686bc0ba4ec4af54ade220078b463b1cfa3f5051854869fce246e841b834ec0683c589fb5bec071f3642bb2188dae89042bb2cae7c6f76cd42d1054316ae824c36750f883dd77d3341c56f9837ffa0c86500de8a297ab5621bc4162ac042093f2349de0dbabe6e93cc8a00978113b173012111db42b857dd4c08cadcf8a9f89443f87e9a8e5f46a5a5ea26e805729e8b8fd2053a2b84de1f9794b99dbc9c076c166d61b321b68ac2ebae1343e839d7436e9b27873c26988b3b56eed102b978223ede406d382c081d5458164edaab6c6da1407a417eb380dee5721459c8e641d68a30319bd2ab71c5487b42b07d183e763c98001f6d913e2f1a2aa269bbf0a462c8c10ac7825ded2b39c74d3e2f344cb2922bc3f61506bff128bbbab4e6a70257b0a5b5cfe7ca82c838bcdd291a078233da2638400e2b1a1a1e592ca09c584dcda74a0fd30ed18dc5121838921208750de5ebb6442a13e42127b3881d997329bc0223bcdfa30a29430c59b430f5731578275dc7a8b4e50e2e77d0838cf293122a9a680197c2e03cb49b35d70cccfc043cb5876e4176adb22f4905d8bdc2cef495263851d456c36deeb236c50b70cf770b28380ec20c2f6b9f9e4108759127261bf535613e42e2a3767e140bbbfc703f919427277591623c6141a4f50583eefd812f158edd7841af0d01316d1bc3e8631a501883b7328caf40244e9f005bf83c6a7512a4f51ee94de344602d96f05140fd769a3c351ed80d9bd7030dce6b9c0e913988a4f0b2db1dcc8feed0dd28253b91e57d19330738ee82f9cc5890617a47ad0bd3d229c701749d200d2eb8063867707fb3466ae9a72e7bec7df4e2baf69bf5f6ea0de4072a8c3280e008afe7617cf388fded2bc9411892cf9532963c4d4f0a888f743a98f44ecb7fffba61941e9989f0002fc604a06a4c00562c8462ae47d433040f0d1c90891ce0491294c4c5c985bca61919ba3127adfa366db86b6ef2433397c862ede49572217db7d3b21596af8996ab62446dd79eee7239097707ae3610d7deb6435c9bff8e6d565eb652904d46badbc1f11e393ad78ecd64033bb1594921301dc629dc529fa815df6601c1eb1fcd4439dd9a409a59c8bfba4990940a014137357311a94a5e73ee3ff18abe813048a9e0d483926f9bd68e3d9e5490b35140b0bb5e31ceba77ff8396bca321f38b385b902f493c3a5b5bf7e9afcbce86fa1e7cc3e297270f6a68952e9fabd6377b0e946901ad59895ef6c34dc4aa56a899aedf3fdc0d14d616fb08a5ecc89d2b333764c38a07faa87460d064f557512717902b37029eba00d92a44844a117b8374d5153c65346b3e22ebdfd1c20bcdf030ae244c1770ddeea3a86995bebcc219107f897339b5269ec8e7cd46c53fc1b0521b36f3d7c94db3e2675aa9ff644b4d8ec76b75d84f9f4ac72cde4a7c4e034b2107a7081866484a7da76656423629593a7ca81c1e9481d0d41967e3cfe00b44451936e111ce5ac1c2c64bffe56c05688cbd1a1be7a025db666b0d717ceac7e7f6dd15347ba1ea26f8d3221e68764904a5d1ac3792eec48b8d652a0e3fa21a4eb727717638d83d3043b3e7f4f32560439dd21b809d36ec81af37ed0b55d1b89a757f790d1a397846d84d5b46696a3bd6a6d12bd68ff7851486b0db7e628759e7e297239afcd881d99ed3234e4a13de9a74441b28c7691985d35ef3d06ea8b388adcfb0ceec20831ada9eb4e3f7e7e6dd1af5c29b9897ae57dba4e4714a3a5d391bb49c7672602934d403e6b483f49099910ee98983ab6eb252ba4267ebe742cd6cbde53de7b04cce4cfbb227c4dfe72f2d82c7d65eae4a7e3ec8cbe9afd0f97d1a2f56a1130635012ae1222b38d216204882ca83aa80131625dff0abc2feef24b82846646a9ea9ab2af577da7a720dec7a8afc11a70798c9fcd702503e217f133a1557a6aeaad46f3f2ddab59050e300515f8e4c9bb06a4644579f066dff9407293003862807bcb2caa5d618349f7bd81299fdfde77c1043f7777e7fd5a0ced70cadf7cbce5588020a266fc1fd8f6c8ecd0f50d3d056eced0e95c5e6c00d8b4dd0448adacaf0e69b5c067607177c61748bb37c4ad6f02eb6560146352417555edb29d5d1616a799ee5c9fd5c092ef97f9bc92646a9ccae6bd7324de5812d5d5ab96a29b07aaf86a66ff0b7e4dee6fc8d26a1e92e2e317991507a1bd22201fd552177d1e7addfcac2d686330095d4be520e15e94cd0903b5b9b1bbfa03edd82dc1871c9d12e8ca1d6a4e6fd337b3a64db7fe896f2bd2a525899bbd62b6770816c21cf0389502280194cd9f91c39083da68306f8567e6f70adfe37e4097225d8a57b8edafce75be8cc88dfb0c4118ddc5df05c32d406edc8777dd0e459e4995d5d1a5020e38fcc2c3047f134023aed8328e7293dd1658bee72a3771b40f9036cf06c8c644b5e5334dea15f3e991bcf92ed441ed4081e5a1c1fb9396dc9da999c6caa5bf237ad0468633090dadcd9b6eb53a635d3d92e8f572c9cedf4076e34601040c3797fd1b619c115b48003698612dd274322a0ba5e6385c499cdcd04d7d96aa41bcc5d958f03677d18e9e0c858c7925b4147a189c9909f24b62f1855db48278587d726238e474971208fd2f23102564415f1621c31b5ae7020799b151f659769a7e71f01baaed61567404a65dd1c2ea7adc9550aeffff50292fc1161f0b104cb161ea6a704aad0f460b244c109e476304daa3c2b3898c466e574bb6b08a5308e64c2e194f3d050d85b81ab4219fb074758b2bfb847047d8dd29fa7ae882752bc52570e8990732144fc371222dc249cc9acbcd1613cdba85355700d5243166ae8c798de09341d44041bfc8e87e8087af7ba75aa7d51eafb78e09bd1740d58138b81f0242c2cb2db38fa7da17951fa94523bdada8a641129395ab908721a50721ef5482c106fae070630c3e84126f2e7026e2cac916608e2c9cde19bd584076d4e58ebaf2fa0e416a30058348322c48336798a719bb8ef477d4d5a8106d437baca5adcfb22ef9486b7ed95a21fd11f5de0d52d56ef777b0f86faff9bd024772878758fe0ecd4b4906ba855af06d2cf03f74d715a82999b355edca070660d5f4ffc81d5cab272f1f3f803edb52dc222d207bef40e0751e28314f23996faf2f444c7ae53d6193bb016f9082fe5f724f903710fef65f740aebd792c13a7a45a1fa03e6da6f334c5258f63d4cc66950ff56003518be09529c1f3f94197486ea0ff39e67c4cac99904fc4d0baab43c29e2546c9c013d50cae6f2c49f67a528cf309083eb0c44671c5fe34541f08090dfbbc6544a650d1c8db8d30eca03e0eae5f27e2cacd570311733551af20eb8701fb885d50e9838f154f1daae4482deaa00119cfd741fa0953135681e38c633b0c35c4b419830863958d5e895f8f14616d5c7565d4fb6404727bc2606092825728b4865a8bbc548f55954552e1aa0a4f03930a48f64f8bbf242355c8c5f7a288b81d4ca01020f1799afa9cbd4f76d8d52651e10956d0aa1720528924bcad9e97446a1dcc2ecccd12493477e6994fcd031647126bcba93b6d09f80ef115d1b8f5652778a10ac4639454dfabc77057ac09f3d69b3e2998a63d0b5baa4fd16cb1d8cfb8f9f8fc496ed431aa7a5748605d6f74eb7c71aa5eac94662cd0a61da51a3eb3240a173821c467965f81154a185713cc1d94427a9284467750a918345fd158c7e1b30910b84dc5b65d2f8053252b006cc75785abd70b4263d62a80233ab8922ae393216a9d8a5674c12ba44111b3ccddf2a7b02e22f6ca7e5ac943ad686f0a4cd76c5a826fe61fc45a676c129b8383021bc4396917a802602b4271f407ab4978046c92c1ff39692fc6bee0e29c46987074c60de4820260195e75091838e8dec05f427593b0dac948a1d76c265f0489d4f68ceea602d18ce71506212d51f864e60c1ce9f77eda39433b7bbf1a0b2f280cd9d524392b4ff0222fe8a62acdd6b36c2c2a64c07b946ad2567cc2ced2d0d9e1e3d6a56184c10712988631762b92f137d3fced2037807fe6d21ec72827813d073f828a6c3b5bc573dd7a60d265e64dfb7476ccc961827600a891e58027bb7b8f044dc299d4bb0435902ef03b31fe65845581c192be887c17cbaf7e9ca897c250127141ba4c5f76c0a3c422999b39b7b52a923d0870078d55058891277ca6b3504582dafc146418a77c22c0dd1cf99741164506a70169f1855135760b3494832766a53947b33bb40d7f656b8caa6138c4e628ed40169c036c4e3e2386ee31e0ae55852b5a769c78a6e2a9f7dbb6141765b01f0502955608a78a7098ae16e75b19590c20a47359c6a144545628fc390d1c8596b8eef54ebf11ec2ed84b0fcf34e784a65d72f7a592c16cc4b693e6e14f0868417130192a5cf2323aae6c51d7cfe710678f8c87cd61d4e2be5c98af5311809b45be29f67eb5e30d6c9c6a02c5b5b6f09395b5510454600f6b673a21b2a9c50f177cafc02a20c63e23d06e898f8fab8fe0a693c8d3eeb800d390efedeed045a871110b5cdd158c8f0a0b82adc973fbf98096d0ac01dd8183ac7572251fbdf2f2c71a8306ecccba9b430027e74ad1f5eb755152d5bfbfd4ca1aed3e11d7b10e117f51da6662015f828b08a8c16f1168356d79a7a3d2611fe5bc0f0876129fa873f06c5dd54c6776ce2060883d41e41ca63c08141eae13a884e2059ff6a73ad63e01c127a0da3a4587e5b54ba8ba97465b216b997b6dc1bba316196c69d9352b10bdd0f94684bb6e95670ccaf6d5002d915ec185c8a6def1dd1194dbb7cf828ab5f0ac445b23a6a9e87e407069a6a5a8b06eb13084c5ba02448556f6680497be2e7fcac45743a4c492b69bf053a5aea7e28ca77b1cbb7a63c40ff29d5230f1470f22c315df38e99a6556ae4e8b6e8c39c366a8a96fb8bb7a8a264ba12d7e3d0bd689b4d5c3e2267adf52fed6ac145b50640ea6ce06b3e63245a4fe4cdba64454b0e182a5e640d50f39cfbe1e064a5e0d24fb759b7741350c8eedf6a4f64bfa7007bcec918f2b203c8dd2c7f4285f9a37b3e49a604ae703eaff2c1c59de380583e815a07585faa489448890bca0092ca8ac6fd9f9b93a9ce647b864f8213447ae2d045cef5568e5fe7c0229e33a56bff2d42bb75d1fd3acd9cf60ea464fa8aeacbcac788d094219caa9bdaa04904b148401fe2f010c0b5698a438ce50c7861b0096fca4aa3f871702dfae180c8249f25abacb8655612d1449efb10dc98bc1665c2fe33ee7f92500c2ddc59418083a4343a996525c044ee8edc0be298bd13f28be8561fa401dd0ad150530afd1108238889e9f5fa64f25a71bdb08e8d108e82888683d8ff8baf82b4f1709109e927612d95719e402c2ba0b1b9fcd1fc5bd9fd9cdb18600a626bbce6dcd9653a84b08eec2191420524f6678dcba412d422656b898eeedea9d2f2748d57c88b655cc3c31ed054a4dace0a71d7b4fd51bae7752b81311d65d42b7ede5e3c477845f7a06bffbca4b41e4305ae1e4c428ab7341e03af9a0b6ce8ee438b39ce09edf48025528c338b8c807b737080003a0884764b972dbac5fd8a1d196b480f6d5f1d9356a206a25ef7dd5504b46973418090c0265c30072b3ee3344a6f18d3d19cab8df175db8b3890d88d12cf097ea8e2f1543c1a0c06946f556b3c23781df1af9ac3fe2dbf781e09c614a10697c1096444f7e0da43015471de4d2bad04eae4493ca8a56f6cb8a4cb78c10fa4b138451702ef10394d19d2319a05412bb045efbe89a76e72b7dd9a4c0de56367ed8b2fd1605c9e5a4b7b5291a8cc992d2e2810246c8f747b0de7f7992395cf66dc180ea9e23cc14f5a55945c28e822cf1b58e4c7240b78b22e4e0973cd5726ce60fe5d20eba9fb791a036c29aeb8dfb13e22ea42418b06fbd28566c9746891fa8b388e8791c48c000b868181b06d06376ab1824ab776fc08c60a26c537d49d1f65d2d4f6830af29ef5b1672d0ef57c33f3fe19588efa84b41b71ebcdc6c474af7db3907f989235fe93297d39a50c528769dca711ea614a4f773951a3d3f6e1040ee7df39d6e922ccde0e0a8ca9aa25e29e03e8601a4956f4eb518588b727f21bae53405946812e8c89a32e8abbf7452a6697e78132994a47d69f6901f05dca76e278121f5dc4ab2f9228fa2865b47e93564094e39546ba2dcfa9920b84e2c40ce523f8f5dcfd91b25729b4fdae63ea89c7b241e1c596999fcfc03575cbd39fb0695d55d8e5152a58a1597eb72981311e4b9dfa368b8ca33c78977b54d9d3fb7525162866409962bd7ea34e201131f8f6c21e9a518e98fa4668f1dbe156cca4771886439bb43d4567ddfb34cb9dc318f3731c24f82df80305d1be941461806a6670859c3fbd984d197d53f253b32045021f40091dda44c0175408d80b235f82b40d708bf5420b6e1612c43bbc614c394a8056e818b82a0fb0645e94fc71411c6067aba2cb208c3b420e1cad140f112112a978947b8202353855158fca35bb8a2c201fa1ad233f64e8891cf0cdcf6a73b12da81546f188eab38bc602850d241a538514f839d01ca624d5a7b05496136a5a4a80fbf105a3303ded484e83f2e380605eb4162826e800fba03e24ee8082a33e003748787b10d426d980a89f21af41144c7d85cdb6af8d15a60a5819d31658226128875c0aed86a401109c64a37565b310a4306ba89614ad0d895cc682a52dbc0663ee9845e66e46c5ee09542140406e70a9c80b40bc92905961153b742fd27cc0358f9d22044411da85bc8dc264052b8d13e3be0bb9fff5be8dac7006c4165f30f349692ce70d6723161e0a716c2bdd6c29cfeac2468d3c8e81bec420dbc1f82849155b98a6500cb1db90c2a13fb8237578424368653da4bcde9f95bbae556858a874b739ae2ad6631a0955ca9247614b59e083784a6d777e5f555f9b1a0490f623a95dc651ff8f81ad093c29d208a832dd5def508bc040bb41d2775fc1bb80365efcc7c14911094c1865c6b9ebb71b58c02552149d21db10dc93f09043d9186996c0af98f3ad3a06713c45bc3abab2590e318aee9e0db39a807566fab1a0e17d8a0062d935d08d59882462120599e6eb945e714a131af453fa70f40584ae71d703197676c62b09afc5bec59786a72db2657265298f89d7f3d5d029461480913ed70caf81d9f31cae1495513ba0b39451a8591dfa0ceadb989d6036971f24c2f75d36ace8b8f298413dc459b9a7c74ec1f372833b21cf0f88500d4d61d930d0de98472abf6859082f9beab109e7b9aa24f80f67e2d273ff3660ac2ac265a037409cf83bb723840be1dd83a47b5946a12a07a3128448876cd0d456554e4a2c6d211009aceb63e86c0137ce21cbccc84c23d59131265004aade2336d2fedc26a1c2a9cc5aa272fe0cf5f33d27a067eba590e0b719e646292547faab164bd0c24d5a85bbeeae1d4ad72155f40cd2c72248bb0a10bcc284379c751a96a5acb54b027bdadb032b2ca0a54aca352b6b0921913fa0e7a0618c5602ebd6ccae333feb8fdc343f2d42f3aba3f411d69d26c3c5004bf99b3bf535877cb58f101d43d93daa296a94bbc8820a61569671d65e61979eac9f2a86a2ddaeb501fab41dd543c742da0e4527f04000a1c48feb4d10bccc079c4b36344416ea0948dd4bd7f0fdbd682094910ff468616913c01b2b07850e81eb873128683e889d785589c3c0dafbe61b2661f1415db244c18cfac9e30e86774b2b19e0508f3d687b4ca86613442315c42f1244817a7eb07aebadb654b3433e886534dd3839a6da9a58dda0e68b37dce520c9ee66b8fcb8ce6361c8bdf81d950821ff46cc7b40f97ea5d537e578859378357420f722a3420676ad204a8d619948677d93085936cf314a8f5bf93fe690c4740af777268f6ec35f0427b45f3cfa6da2374ca57a03ccf6df6b1103388cddb623884acdc4de051515d958878757e1125044ef03c9ae121ed95b353b00efcad93c6dde70bf12a077e7e227f01e2b5ebc00d0e0bb59617434cd40503490e9ac80f32265cfd297efbe64712357d5027a021f05a6c62081a890012bdad8a8b123b1a38ff706c0caaa50290c5bab95294c4c5c1731831ce932ee11a59f9ef712a080343d08c527263286cbc0975a4965e23d37c4b519cc4a05873e0376443fbfdb0e5d645fab842e6dfd6950389cd7cafb99818b709ba10eab352f3946bdfbf381cfa0e644df698a535ebbb98124d42d12d2b86dc702eb21902186cc0ff88688cbe46f1be0263ca1762029b85414ded2ff7010dfad105920f77cab0d7f4cda2c2630fda6d469ec3b904dd1ac5de0e623cf6a8cb46c709016d0dac2e5bebf3c0e458bd6203578dd9ee112a57b4915149866d828525105ba01ceb9f21da4d9c828f28eb8e0f5f877eb362c0bda0c6d117727344c2367b10ee3951aac6dd98d2759328f0ca6a99e7ac7d20f361fca569d8982d4044a028dce1d28bcf18d548cbccd50900298914726dd6b47200331cb7cc8b519061206db8b39a0ff8b8e6a261f9134f23cdd44adfa5e30b2c7fff9da6e6c6bcf86996a6535df054f3b58029f557e624d0d08bb1a8ddc1f195a23f4ec51c69cd7ff81d4257ee3d52abd59dbf4e6233611d14d001dd26cd1b077ee3c5f24f278b34e8fe201b830da21be340fcfa2d17f39924e569830e31f6d2239f7a09e3b9ee2990148656787baab78b42418742346653ee38dae9da3e2d363052ff6d704c7f667852f25862069e583fadbb1690df6e4be619764b1ae7202e9b583683ddec99b2fa1395e3271676b68df627c67d35d2774a0cbd91f65a021fa06aa6aa0059e3d9407e79ec8fa2941acaa6cf0ff4af99226a0dec8ebf9f3cdd36b099eebc434b9b235a519367d2d220fc2b96e065d4a348fea9e6cd2523c3a56c2721aaf8f51bc270345bae13c2da86b3838b8e2c7d708473d1f2729958c95aa65143a597c9d2e4b74eb82a6ed184147de7dc465c2e2a8a84ff5a0003d1e1fdf17d1458932a442ad009a2719a2bb145b3baf82c39858b5ec5ce8674599fff3828a48a3ceab0df7e9d86ff04d6fa7cb6e6bc031c2aca0b7310d090d812372faad95c09b55f28162f13835933c3b46a998600e87588ede951b513b77a1f43af5ffb1eef6801f4b93aa00e362310a02ba26d2dced2754a19156d7d7c246cbc939a6aca78ace58983c035390836758af77463f8f9fc349cfcc27964549dac7778a658361d84137ffa7d46c4518caa2dfbe738f7629abf505936121feb49babd1ac44ce3275a63fcba3c4c6b0d3fa99a5baa69dea226ee5b6d4fc5a92af6a5e52e97b135e54dc89b7cbb690a93b5dfe92df775bcceec12e88bd322e63e7200d213a0a9f3c4e92d950d63051b65ed3f42132fba7ccb62d39d5800f28158c2eed121b3a17fa7d1326584537390aeaae48f5d97cf95d3f584fa274acfbbae2a5ecfaf0ba6748afcfbedefc419b62b55e2efb49dbaa3770d25ff99d2aa31c67ddc7d5ecd8f38f42369502c3874e2d7beddc255163b98374571302598979ad8b2ff1524934dcd8f226717a37680e0765d434671d730f546be0b3f91a17a78e6f5c7c707297950a768ab7014d39b0986a31bb9feb2d403567f2fe252cfb926c1d6a20eb6f6e74ff8c11c7a06dc871c0fbae4c48bf59aab81557ea515661f236a4e8dbb07b38f824ffd068b786666002b7fb9b477d457a063a059f9a0e0110363897dac74d98adc34b3fd1d8621b28f992117bf00979eb09749c50e2774db1efe390935726f1a32da030e7f717da7b12b3c375490a6741e9edb81045d994a0a677bb87afaa66c4446bf55d68a1a11fa55249e671dcf97cd664ae851cef4552332eb1c76c1af25d796f495ea10cd49ca740a95dcc4e6e3b49e899053d542bc8ffb8441d335228fa17ed3b5251a8fc3590ce1fbdbe02135e831b30becb6438614d5fda70e74e730777331f43efc9a63348eb8fd7662b0d97386d002d8e06cac2354ce136bb82b5619ccc9f85df17f630c783912085bd1e259c69657249d72835db4005ce694427d4f0d5be59491c8ec4cf0112acdbb5b8b60da1ebafdb377af7097684703747f45f63db5f62e0c5403b497349d457a7dfc1f1d72dca54b40d38fc69f86c24f35f13891fee5b466f6821b4a0472dee3333a8c5d5ca5d1f0c18ade7cdb6932d9c75eb56ba342911760349dbcaf84a98dff196a9ddafc2b8ad6d784da52b1461198fcb2e318f71580bd217b628e8a119fba34b2219504d3b0ae0d39a41da448a85f2474f35462498f85321aee754e204d2e05bbc1e0d4fb167c85dd27583ac18f7ef81268bd220bf2b682b4e41fab524c55a78048aafc06934f9c83d641afa8c4e42ce80445762e5b3db843cc681229fe5b7330b50797105598237cac3a4235c397d81e0b6d22f4cb7c9c9cb341c1e7b494b9cd48a263b27f1cf1945ef0b44f4fc8c95ebe4fa0e6dbcf663b1693d8cb636dc7d2c4ad5ebef3c35b861db3d257c945e2ae536ac32de15ecd5369b8f1a254ebc5f18df7026cd7913706734a377ccf79617bf81b85f0e36776db11cb956dba76101a033d42e33945ad70fb7f1c6be0b9af58444c7f1943c54b3d485ea095feec24b208885e10dc260a5050c889fb21b68280aa67910063523b8ef200ffb1397296039e22b5c9b46cf1f48a88ee9c9200de0e1b98b8733922c72f7dc615f62d6456927a7517836f64c98143990bba0905980acd21df42b0658eef1e6b7c8602fb0ae65f72397c0b0b47f8474aa43fb564808c64df422d25e81d93bea0e04b646d644ff8161af25227172ae54ae674649a25f30a5ff42a92536c7c678a1df5e1ecc35700fda37599f7ac4afe7c2b81062b6a375f4e224d3764e1cb93812caf44508c5b4a2eb3eff02245f3803acc018e2ea94da9d17afe89b067f96b5195943c3392b8816f3b08e9e1c0b7ae7669409062000491bd344303f805133b314417c91969a181a339cda45a721404d16ded7dec0ae7e9bd9cd9ea9810c70a2ca6267fd602c27e4119779102d1e3cf98c9188111f8f60b4a85c14be980a14f31873567713f6821f3c5d52c5c9779472fbc0cf6374a3a5bc44d91f8ca6c85e8d6d7afb5f9bf7d65302460a01790d50f644db96bb809cac62b42952e0d7917983166df5396d739d6e871045e369403c518413586d8b27c3d7f350b24a83100c70dc3859455671e3679a5c9c092e15cba6012882170131a7022fcc1b739cd1051c018e1975274a46664e82583d57798dcd4183ee2fa6080dcc7de6f987979db612c6ff4cf1d89b76100b7abafa91a42d19151886ce07ffc557a49f9dc49229a889725da0bd10d60dd5246977b581e74772a33774b06ab9dc8e941efe114ed7581c616b66f0ba3a4dc05ddf48aa372d0cbb53c1c44596bcb14637f5e1121a48b14b569e6b86bca70bd01fb4a3a7277eeec368450d309fbe98ca9ff87b2c237d25d5a4149de38a5be1bb17e7fe666a068ff156d7b5b0cad2fef355fc14fce04e2496317f82db4727209120c0f3a2fd8be93e0775cc3c6a6761b8b75e37f99d4be23496e5708b92267735723443da89f0f96ce450360b9d31d1dfddf48055efd76941f07f9413e86d71c5d1df2e1324cd89dfe84028ff0c4be4a36d4b16eeb364f7efc2af51be8d1d7eb080bbe41c20a01be827d098f00f0bd1c52add0b82dee12b137753a71baaf8c30fd14ab81f33402690723e747e894b0f4a3c1d3b3050ef9f669c3d1da11215de238dcdc12381d8c65b2eddca014bea6d73dedbf0c232ec5e8ef5e4e786cb44a6d4631a99322d9ffe0c3495c196a35df44d56506448f1f1739d5dca53b8fccb3819e7f17f7673368c8b8048ecd4e3a6daa1072d75ed32d8bebc5c49199163c3394d926e24feb7106fdcfcea91599fefdef18d524e90337a92d9ad1586545c1dae1c3df6e040dce7f55a9f488e0011a36fe9460f8528fbb3e177127de898afd0beb6927c13fbb4dfaa78617b8ea234574b7b905bdb0f974feacf00be10cc7e0cc0f803c0e1e94031d30b37793cf72027675505ce053c3d537ce5b74c9b55aa9a2fade9faefd027c2c60d293d13ef0d7728680a70fc8f419041c9470b558396ecd2ceb572221bb3a7598339d2abcf1001bb59edba032a8446954d5f42db8fb5485f68c626c60feeb555da8df251182fd708ea05b834582f7de84012ceb40dd15bddf0ad3f86fe4de17585fdc5d952832b995316fc396fbaea639da06c606029311fd9251797a4ae870d137d329d2ee8f33bc59fa6c755f87be7e3382409c479c7889bbbb6760040889297f1a065469ebd96313e988aea3372293bd89b626fdd375a25b1fceecae324cdcd6a708b53ec4574252eda425044b255c78d5e21a3a1c29daeb68605c8be2fa4dee0a026d1f80ee24e01bda2eded0d8e1c07c0a3ee8e80784dc22b846d7be67f81b618a4ce7e186beb9f5fa6c8daefe87c2fdf58691319f0850ba2df97e62411f732b3e714a85c46dda776c869ac65caf1a74d493f55c7b1d8ee15f0be33e784fe7174b34ed3beeafd92026445b84eb7979050c0b2d16733cab7a2e6202459d61f9053746283a111294e593fa454c66749a7254c150bf507565f0ebea91417d6d20187ec42f5fb7ccd77290f46294acbfc67564d0242569c3090c90deb09200ec87d86ae83b83830960f96b5248e930bf753a9d5c19f59a135b686b42df5bcbe7ee9a9ef299b086c72ac82829e8ebbf5620d777324a5f94cb1cd57afcdb4fa9e3c3c4a1432aa1a3d826d92a3671cec4262104b8d070bb7e7a515061383133f906604a072141fc965bf98de1ff341069658013977dff786461b3b13621f27d4b181adde0072d163b1572d8ea08d1529612230a3b9d39a8c5d83ffafc6e30576b7796377da74a31a985dae10b0c7b4dc9954b3f7ec3313325c60ac4920dc5d19d93e5798189f7575938d7a4f2635e635e7220fbedf9f0356e053f4bb4866636ddab29d13b2eb767d71fa6115cc85b492ef5d7452ec4399c670f00f9b453dfac690d1ec00080b69305949de57acc2a1326e66a0078191fa68ea255b76a1357456f5a125187b6831afda8209ca31716a06d782e4ad4f3988e0dd2202ac6e31c3916edb999be0a17fd97a38e4e61c7348bdae9dc7102de2b788bbcaa36ba7221d3bf5a2d1afef867a39a1f64d59218628dcf9f8048c942aea766a318d86c902d31226baf33cfeef09248a0a4998054edb0e98c3a98b87d8f3abeac04db5751e6d1cb6ec93d4d36d65553700c782fbe25bf44f9dbfea94ac227ce586521aba6ade6c4a9c9be4b4f467ecd151f997c7c6255b09abb95098f0fc4f56233168f9a833ecfe0c7faa7aabfbd7573ca46f6c23d2d11dc0c51c61b56557cfbc10501d05e19c860f34f810ed214e774dc3c2be45e1a44f2c7e9538a622f821861c4c92fdf5281a7d0377a7e139d3cb9584b921176745999a5aa9a9e465fddec800fbc3e54c417d6402f525e9e157cfcdb6e864f83c8e0e96dbe54efb2ee0434485c96aa3d5fa1420cf0ad78ecd2943af11bf5f72269e5a64c879bd9c4f005dd5465c47dc4d13c66428042f9dfcfff61865a926a5032501cc4853f90b9a23ac75a23a943e7d85c51330a2585b43077489602a60a3fa82bd0edf6f46811adb5ae829102279f4b526ea99bf0796b48bc5902521da50b95aa89dd91b9288d9e00dfd2b804f6cdc9b08394273464b033bb8b2013b6cb7e47f8807f129bde855d2b7242c147e66dba936f735c1123a79efc6c44c03a0470310fc0497e2e17f857cc340802095bb10fb857ff1057868c6239c054f1d058889b50cee6fdcca11aae53a6f454c2d15ab7a16039702a2e43539b60ca68e562c13d4ecb565d95fae5c29e3e669565dfaea37b7c5b4b84de44580c17af18278e7a6d9b564c406857746e4d42560c0a1102d2eb069cdc01d2ef5330a2bed9bff252b9e887d284d57c89b52b6633969511723e48d627ecdf26279c93d6cf263bbe7370aad25d10db31d38beecc824152bfe11d6a713da9df7eab0f1b4c452e8d0e8a320efaa6d49947fd82dc34b389601d0f8aca9db686f6c8ac376f4445b705330038ab7efe3b3a9714812b6b242b2d6a22eb23bfe6e3a613f33d4ce3f3be2ee00e02662aad313031e3f790969fa4276d7329de32355f77a9ca9b50061391b232cf0782c946aa684b99c7170e367e1a72a3284ac33751d8c95f28a4081fee4c86a0d97a094c73947a6e435ccc81e3ec3d0e3498ea5ae586069f8193e63aeed1282f523b0bd5f0c93c7fe16aeb05f70100d4789a3fb2b036ed7f7f602abb478e5d63bd3381b612b61ef7fc1bfb3589f88bdf40dcbcc6bc95aafba8ed05296e40080ba6b79d4664630a21b62934cebac5fc73a197b0eab7b3976cc8d5674d457b5109bd70c71089cdbed50f0490628bd5c47bb45effb36341cb17bb19b7ba3986e3966b0207411479311b02189672e33c4148e5bb06593f37659d196eaf9b2142ca8dd5bba539dc77131e50dc29426f873db21e1f0e15c8e35fa2eefea8f6dc979c0e09cd9d7272c8ae678b90411c97e9d6b004e12a37adf668e349f52189c83554c356ba386542ab7ab81651ca45e6afb8c3453819b252625e595c5e317f2aeca3133081de114e370c3322baa1fed60877745f37d21382fa7a0f95641d60570eaf0f9930cd14ae03840c1caa08d3a07b4cf0a9a0d34770daa393c75bd769a76580d35d2e68fddc7404d4b65c3dda54a7b7e3854c1fa3fb9aa0b7eb14f9979e8aa4d0f7c1a16b1d6cff434f2c52382ba795289d40f41c3c2f31b977e86fae8310e71fb58672871e92bad782850aa25dbed375eebc901dd0bd9e9273c087d2de24a51434bdcc92629f2ae19aa69d0bce0fbdae98c03ee5e261950b4afd1c7215b119783a68bad0fd1dee23391f8dcb35394899a352832d2754275e99746677801ee5c2cf3329fe89f3123193ce52efd31056cb003d2743615bf3e7515b3013cef3f388569f47bbd5a0b6862bd3530ec34707f4fa14360337feae21899e100355deb735777fad88089ba3b921627a53d82ab89795e033ed0ef0e879598a3163d4229eb86145e8dba7db6b9e4e05069928cebc7cc49d2b02f4b2793d0a6da10c5945366e2aede6a1639678e3ed7f2a4ec1da5b8c26a78124960b70b461f82419ab75ec0703ac0286f14549c844e937920c94347d20e4582e45057d2b3e5baa3a51c9a8fc05407ebc2c5d420ff8823ee160f294323bd0005052d25ac938e05f4ec0eed7d3b4c9e087c834c02433fbb084f2531c414bac8920b45ea75f52a61698ce6e747bd2e018a7d3c1cd1b42f96f3dece717295958d84b46d8d75a4952ea4a176ee9a87696e369ecc53d7465e4d3c759aed2957f5303b0db25675c0d4b889ccfc4f9150b5d96eb16b49f61794aa2a97dfb397fd3b6662766a209ac593ed08b00d88eb0131bc97179612d911ef00fa49e646b6152a13ef69f3fdb0d424df27496b44cbc3dedc3ccc7e25c96d8aa710bdf2bc45e85182e202c5b0d23dd65820dd6a65506376152d389a5e282c8b898527b317256bdaa3be98da98527c510afd369920d9b616058b2377b9e361af7dd85bca3d35331dc9d373fce60bff7e704678fa1e3737c79383e8c19f8760554dbf43148110a6068817e9c8ac81272e3fd2acf50bc1e0320018bdecf258d48852e9ecad58cc3faee4befc393bbfa76b18d66c07621dadcc78002ef3a9180594bc2267664547444fbbceaf8a019d7c22080086c4748b5a020d90209b3acc2686b1fe14c1b02518bf891fec37823f95658a15cd83ccc8a023c71c5e8fb9021d9a3cce4aa48622bf9793f56e2b3f7f9a6dd006e803ff2d534bddefcc3b24b60f90a508330be8390941d8cac27a1ff39bdddb2dca07253eb8ff74d3db78837cc02e3d3faa41e01012153f2b15df2423cb48332bd4c7625ed2436a61d91f1a0389906dd6a74a40193f4d051fa7a61472c5e709eef74f16f307e42fae827197d22412ad6fb9c9a40d09a9c7d49495b5ce2836ae9f82ff8dd452bd35b004e658a2818f1c32e2bbc345f80ebc54afd32aeb33708444fd8fc6c9b43f312991b83a8c4c7d1189bbc83fa131559bbcb69c8415099c3b2a14737596183e465433ee60189fc42c772a1175990792aaf00c7cad7ce604002ecadc5f73c650910570eaa49a7d602c6ab799a87a256888310fa8ae2ece0fbe026a68c2ac6fb40bdeec8a921ed3dcd3d9af03528a866778a11da18b06f8f85a2596f2170f572a6f939559cadd9e7ed33c225c55e4ba9aef80072a550f0d85931ac021f68703657f73e8925f81cd779fdf4184bb4cccdf667bf03cf4eeea24c54f08f4d158faa2574ddbb9edee98cea195ab7460685eec506f6df97d14d5e5989c054ef428c2b462661433cc2c7be41a546b09c3636792310c300da66e4776a4848b3a3ea6ffa0ac9896aa4d0f77bcfc15eff57bbe5ea37a0981d1d07131ef6c333740e199a41d0e12ff0c49f0f7117b31c49ac91ee9d33b161bba92904026b665d63083c07905ee95d9ffb49b1df8e4f7ee3b92280ed13a3084bd324e3dc161787a73ad8b36eea42d90f332cd30c8d10c34e65790d59095946f013a3f630b81ada24c075bc00b0837c9c54bb275ccf57a2ec947789d0b1c6595bb7f79399f1e9dc68d804a486c19be164a450b431381d82331abccbcd0cafa1934206e502f76478904c492be848b6559c3e8b2fe65f947c2edd8f245bd6115afd0974873134f6730b0cc74f359f06047671ffc500f65432ead4bea9f1fa6ebd86604267d1e5b5766e3d77d3c526e3cc2ec9c21425b53390a0398d49fb9a3775f4b65039932a8a6d3673ac46ebe94706b4c004eadf4bc158309a55493ab94997836fa9ad91e698ae1372c7228d282db805765cd2d761d81debd680de4b9ec23d23381fc5fe2f6017c49a453a361590a574c1ef05349642d9d65a687eb19753a73634726d6c6b9f10b21bd984ecbdb70c2d0cfd0bff0b3f6032f4832df2bc5c22ca9f792d6543552d8bb86a6d1131a33c3f7f1dc5196c49f4381bac5505fb655f2f419221d6ca13758d257b20ab394964a711b8081c64bf7894e7446113c6a978ec29d3b19ba2299ae26d748d797a244489f2a42d5a94679c731e80c7bd899e2623571368557cd441d7a0af8936284ed163294cbb32201e8e38a45cafdd20e92c8955927b49ee222d6f56eafdcc7643b416d073f373d3c40dd0cd913893dafe9ba01b2b1a47e92d858f5047a12877ad75b38158c2c0b65c2b55f5a9db49557d2604fb5a0f6bf58c72dfe3c9df25942b568fd1a071c05c8fd5a0716cafd86b3d35d2ab520684a650d028aa0b88c419fb7ada13674eafa74bc41994d7d37aca449c49793dfd8933a8d7d3261a87ca1571a519712d1b34f683c69ee819eef518111a23922b17afc75a72f5f22c0b8c091de2d124b25167d80e723da6b31d4da07924ceacbcebb118cf27933ca317752d4b94eba7075a264d9dc6c1f2fa0b49ae3256fd9514672ea1ebe7899fd3a07b814dc1374790eb6554d4ca95e8df0594ebb54efdef01f38b85dcc3fcf73017f2a7606eaafb7d0e2b75531d4e0d913acc6f52877977188c63a4c33730bf18e71ee6387fcac2dcd4bdb7e56f4bd5a5c373f9c815cceb67d2ced52357a9771db6a96ba54a3eae5aee928a4172d5826512963128855baaea4d295526a91c72404a8734e381e9ba2c48aaea3defaf35283b3aca3cefef3a6b934a317e7e7e689665493229294b4a4a2a259592ea4fa9a816957e4a3fa59f52515151517dc62357f54866414212a6eb8e783cefef2c4f2614399b954f9d23d95913922bad2555f55b140d9d904d91ab1572ddae1081c6a3b53423882bd792ab3aa426916bab12b1db85b91615e5fa7ad4bd5e13925bc0745dd7e10f3a212857a05c3b5bbf6942b9a6d8934c922b6d8bc6d1597ac1d28de56a465da36e3d5255b52cb4aa5ec80b3a50f793ebe6936b85c2e681a7a1d322570d0bcec684371e93a954043724106f411b904c6a1cdbd1651a7a26d1383a5756b5495a4faeda0c727df58255c97832dbc18c27d7dbbb3511bdfad3dd80a2578f72b723770b8a5e7dcadd9044af1e7537a1e8d5abdc2d49d7a8cf82e44a631255f543b2242c916e893311942b52bd8bab194955fdcbd58e90e4aafe48557d96594b91727de947ae341ea9aaef4ea59f5c9f25c915e72355f54036a8133a51fdcae596e091ab0d8b56d5d77a14fc65f5167fd603b98e20d713fe626496bb1d758daa823fee27d7734cc499ce75abe79a88ab529254d5734b74492c92a423b9bee3366ec95614bd7a0d73ade8d59f56ae2f25d52ecbb623d376546f326d474f6c59fc9492eaa611895ed57aa257bf618d277af52f4ab09fc693ebb72ce20ca713673eee4ce2ccc9ebb7a3380373fd8614678eb6d8a2d892e2ccb77121d7d7ed9f6694eb2bd251d5c9b51ee57a4d6e2cb7ef8d24b9aaaf3ff0465ed15b1d912d25e55aa170dfd29d6ccb1fd3552e54d235e051f0291726814217228141f0db91cc326bbbeef73c996d3bcaf5a5a25a2acaf5a62b03af1387dcb8c18a7d7048a6b883121fe80452de8e51146c7300948323792b8294b7225b446f834582608ebe59165b5f6c31814724c9f0ddf83b62a7a79094b767f77bfe6011a0e86dd656275a52b5bdef44125d230bf6834ef01849e2d99ce0c9db9dd0525a0e84f2f6147cdcc95f4e90a64dc1246f5f53a19337888b00e56d4950be4e04925c4123494418c915f6ed441cc915fd56d2a610ca17fe9a8a9dbc61970823a9da0e8d24c915114852b57d7b864eece40d7f3d4552de9cd0c91bc5441c49d57623db59833b20cadbbf9ec2c8897fd9edbf0e3601245792b51d36112457a9d3210b07b250be1d1a218a33a56f4694e4ed42793bcab552b5fd74b72b5bfcd9bc39d1ca1beea2b71d1a6999f0971519e5ed2da191a3bcbd6336ec033a81a49de8641a59a963293c91a604c237c2f0235ee396586e0a0bf3b557ed55833c1fe409ca54889ef6348e93d3d31750251a473d7d71faac7519c995fca4cf6ed02b2d336233204091b32430394b22ce9c4e9ff1c41994d36744e24ccae9b39e6c8938833a55397dc644e358393db5226657d82cba2df2b6c3247a43323d6dc995d6d276d094073d633a3dfd41af5a4e4f85e4cac5e929915cbd9c9ec2a0bba90d323d9624579c516986e5dd3791ec0ed5f18aded1cf34f4449a498da33b3d768421c5192c08f3912bec545382cf5fd0a5c08ea0b3cb039916b58830a1be0ef31ed8535848ead853c72e04e6d79b958a4f3d8775dd54bcb98e3d1ec39ec238a963c781f9f51cd6062455dbafa0bc3d75b7c36cd7eabace0ec643afa41b34be974c5199a6be09c10ef3ed02362c8f5a7336da6ba2f6c286e5baf066458457645b1692730086533cb06387390cbeb9708ac7f5eba9a7369b882fb072202758019295fa0f9d5b7295d5d20e4cd7791e1090d692ab52c9f3feaeab317c7c7c7cb22c3b3a7a719b554292484825a4125245aa48b59515c995d6aa3ed5a7b68a327d6947ae342359020a92463b3b3b9ef7779db519cb679291dc82e46a23922aca41d12afa188513b82932e5ac68b2ed6c44db5644e4026f45529564bbe58ce4aa32d18670465a129c1167945ddcd45fae4b0b22b532bd6674fa2d081555f4305d672dfea0138c720065fa43a3db82327da32e391bed84324464a5b62c1a47e76c2bda582ed7ddada86b703c5245b72a5a451fe401fda7f3c994ebc95453421aae458745a65b1632e5764a2520d8526dcd23997240dc4fa6af8913a971d04f2d7a55ea6b080624579155da7822fe361764faea851717b24a3b59666d0793e33f98fc957632fde9724c448f1ee5723fd1a34fb95c1397038a1e3dea7247a247af72b9a0e8d1af5c0e49d7a02f01c9d5b64554d16f4cb22136898ec8b9254a4672b51d19657a17772b922afa97bb191dc9959665d61e65faea2357db4ed6551f94ea93e94b48f42d5bd419dd5b2921657a969b237af4db8e5c955a454fe92dfe32fad3b6f375cf317277b3ae4157e80b10673ad31f20ae2a9254d1d3d3e4e8aea0d47b6735ee8aca1d61ce287af4970afb71464831227596957a95654f6c3ed4e5f45b12d1a33cd1a3af78db891efd8b12ecb693e9b738637a8933dc29bd8d33df29fe3828ba38f37156c8f4b4fedb8a32bd762457f49a915cb55c6a49996a4699debe3790e48a266a3e72055b156fe415bdd511d98a94a912ee5bba93f5c974e542251712a1842012940b83e0912cb3b6eba4cc3e9a5c5b729565d6766d65fad295b9f9891e7c0a17e1728d883a436edc913756a44adfb8ff88adaf93bec8f3f2bd04630b26f9a0501e42b22e5c845461f9cb4af7061f58a49964753a9e7afaab8958ffeef3ae234a72fd1556d46f22d57fad848672d77e51aa1d20a7eeb4aa9ea7a7d24a3fe34fd7a89f9c022a4ed56f18d65bba76bfeb80c9254e074cae18967047a1954f7450628e1f151376cb3d04b404871f9be51e025242f412af99bd64d90b98652fb21798bd642fb2972c7b41b199bd64d98b245a96d1f6608430d2a04993096343d4b3ac258c11c6a15894936453f41941932200810a2ec880154190239011723c2ace7017908e8ece0e58f15d5c75cb1700e42f86c68daf93c896596cd14cb27c7f7088849192892920921bf9834994c025566401990c49a20a199790b1892311494cf2452553c85f5ce26345969734e0c97288065ac8f2afc110b2bc375483a3dc4336384296af714196078207135ac8f20c18628249962f020a59de88a11b504107073ad8c10119788a14a1c7a788157e868a8021cba11f21211ea2560e9214191d21dd20cbc3241da441e7063b3b60034f1347e85122cb1e3282f4e30411509010114f963dc483a32c1f731667be4d28cbc34d28b7d014497913da8c907dc82dce40a20cb7242cb825c9cdd9c4d2155d37e4a36404435f68f101419e9fddec253ea8424a9e73ce39b119f97a2791628a198c60ce20075d0a2613544d6610021990010c43621c010c2be8cce0838ef95aa8eb1c1d637a65a52ac295f686456e4455bc8c19ac4cd7d10091e55b75bd74bb7c1591e5bdabaf0d64e95d38124b298cb6157d4cb4020c482527d80069044d8fb410c1bdc71ccfa41fcc16d0132da0f1424b0648f9eba22cdf56c877f2323159ee2119b4e8921944accd3da4441bb2b027b987944022a2b50f52061209d1d09841a38b4ee491349a9877727970b62f8aca646ed0d050e5c8724ead7236d8c466092bcd8e863703001e958d9a7925bb2eecba2edaa815b9494faa68eebc24362f99f3a4f39a5794534e39a5fc94726612ce89ea1848e3ec0bc503adbbe17c9c53e2ec0abfb0e7f40ff62cf19ac8c3cd715baa240f7c81b9b806b9d3ed9cd23f8c2c78c9d95ca39b86e250a877af22ea5a09439e9724f6933c197ef630e529a89f6e6f0afe687c3437b9beca7a6f78cbfd6ec03f68e496dfdbfbdde4fb12f7433ce18f473edddecf3ee5f56a29d7e68616f615e67a697fc8a0ee77bacb4b9c8dc51d3d18bcfde28e5e0bee6ba50d5fd421d75d43dd0f66ad391d50e59eeef77cd2ecfda1b37dca9b4b4067abf2cbdaffd0d9e21b5a5815fcd5bb1c723aea5b6eba2aaf9129d74f56b939b906c1320a63d086cb6b644dcca2075d60df6398eb05e622c22c05c71f8b4ff01dccaced3aea792cff6458fed1d44896bbf2973b391b95a7ee4bf4506f7977bf9cdcb1e49614fce5e494dbfb5d057f30ab1cd67acf9391913c9247aae0abca57aea9ace0c0aafcebb2ca6ba2e491f0baaed35df0e9d8e926fc7557777b597e6a39ca9b1302f5165c6fe7eea2ce72bf6794157bb8726d4565e5d637a75281e8ac828360d93e7241c03469d26447ccf51980b9e21fa8af5c9827b74326a75ce5167f5008eafc24f920f51c411e2323a12322a496fdd745349efd2793abfd47a3723fd34f6f6e07e4c9292af88b4056b9bd9f4cb6aff77b9675d7fa1aa972835c59e5cdd9a86020327f30a3700564720afe78d87b7abd28879c0dca4fb723109d2d0e7205d181264d9ab4b209ff90c9278f3b72b5b5aa1ec6e4faac56fc6df8078ddec997e7933c196f71d5aca933800891e8d1ba71487b6534cb7ace47c80361e7c060c4e090d135e2bf1b06e0411bf1dff38c4cf4228acbcd43015d63e60d442f7f32594787862a80479228a3a5101262e180d8409c8155682f62a199340a3212ba060c401b2cbc7681c007a00d5647d7f824ccf276864d58e8c3920f9093e1a14fcb962c0c90abb74adecba48c5d7d5d04d334e4e7f5a169c09ad2101861bc380c6491bbade8259d64cbba3b2667de0b68b210ca0c4050a28b88a8c602b2bc5ac5e50b0359decba65c49d69793eb9039148221b9331c02c18ea9e5fdc6e98ed3f22a6a8e38c8954ddaf12100ca2fb987424064fefa25244bce46bab80c41cadf3cda8288792ec10940c81f3542434b6e2e2e93682a89392ac87e14c800f2a674c43cbf247a51069616d1d60c269e3f716662d1aaf8a905518e474747aaa0e17137d6c5c54546a665c4564427c3eeeaa0582d5707c5822b6fce86e535918888517ec94d44e40845355607ca53de33af93cde97dc234283b02731345afe584cab18b7abd29775ead6be5a812cab5e6b0cb9b8118c4f591a2c750ee970198a27807cae5738d440902e445c6404ea6c03cccb31586951c3434f9c80704a6264d9a346175d7b0ad7427d09d4dcc9fe8c5cec7757aac00353f727af2751ff39d2f98fb28a78460e5698b0201d1a0d40e7a79fa2eebb2dc51a0959be5a02a0e3081d8586ee7959b659cda51b37ccd12a78290f312a778dcb4a8d103666b12b576928841d18b4838ed2d1807e3f0d8812c97df3c858720e103cb05a7e40e64a5a4cb7ba4eee272212d4fe11e2e7ff90b16d2dd05a724e6f17297964b2002f448f42e6db95c5a44e5b4fd2146b9a7907b68c808326d512671c6f4f82c1b621f4fab50c9f1b4886a91291639d22ce47829ff6c892691bce48f59447d81fd2890c4418c381267604bc21389bff664689e9d1e3b6dc9a352504e6f59a76d207996154c5530968251f00957fc7979074dde2890c9740a34b31c2dae004dc6f00784e9a6933b91442f4a1ce40a424893264d88323689b6162acbbfe4eddf34ca4e232f4faa745c789e6e9f1fc53f68b2f60dcfa049247a1113faf901029a447312116597372bf566c9899d3ebbe6d2b9d6d27579777a7bb43c75ec0ed1f2141662e4c6e5dd85a4de22790c4961173c892065f9d91afa409119904a1b9d5402b59800922bdaa29802490a84bd33a53ea4c4a91d9ffd494c7fa80f6542ca679c8da42d7903135b391e43527739057249e13b7bee1c123df9895dacc17e3979ee64996d55bce46c36708420fc60c8a5933ab21f6d115d7174b493450f529cf926912de2cc377f72fce6901c5b4b620f2ed48a38f3fdf801eaf191ab26b2f3f95da7adc6e1e3c215b82e9168651a85c6d157e44f6e91618b1a5d17863c9047082ef77161d85272e991a872b9cd921253a02a02daa2401f355a82f6f4c096cb5b6e4abe59ddb1a3388321c955cbe3b124aa4377e4cac5e3290fedd1828d3ab9b38b7be94855fccb9d4952159fba13e9ce23a98a7771646dee2110a4217ff3c8e5c2ee9690000422f3088910bacbeea624a62dba641ec5792404ce884b0c3a310afbc122b8127df041073b20db927be80936e427bcf044d14f4b99a3dcba054e2a3430d04498c92e92ba89acf8c88a184708aba50a3b02464c0a24403188e1f3a29c72ce396717a44ca459a994d1186126e79c534a1b39e9906c49447461d817ae2fcc78c10848ac0470de0c6d2e51d367ad22a3226dbbce5a795df68ca7dcd825239c48d7135910b66801d0132420b2001e9f2764d08382a427828082999d4048aa3a7b228a152c00053a3a80b0a311bb30ec8261041b2377145d10438c28788105387cc1b2e48ec20b4e14c10b51743d48b62e22cecc13cef4dba355d76d77215f4ac0130e6e1a84105a6b6d2dc1ec1f4d861d764cabb2cfd4d8ededd10768b3961ac34abf6e91d28fcd2eb863348d3078cdeb9a9e91adb9878e78e10a360bf9fbbc47433b524516062928967be8881443ae9f9c88a717b439693721bd539e2c90a3ac79e2c800cdd81ba50239c6d32cdf37058165898174938c6118e711391ecec72c6e51d7755d5729f7d01126e4cf7ee640eb188de330d5fe936b13b91ee59a45f42a508c9f628b5c638c3046182352aeef187f72358254df874690ea8d8f317e12212455f1f144141101e39d58bb76ec12512455da8910922b228e2e2b9e42286b4652a5d5973eb803a1acfdeb298ab276680429b3b68d2065edb0891fb98a2c4d3bdc01529ce922ed2db5778cc61323cd82968a5a9cf893ff41e24b9992513e428886ec59ae509f90c77e599e2f8a6ce7963458e9237bb06338a2feb514ada20ca5100a6aa01f9f9d3c44d1bd018a1ef4bab33cbe6b39cae52b7749a9682a97e5a8fb37be69c2450a1d54d20a5286c75f4be1c828432914ada0e46c22c65e2335ecf60d2df02704e8f04280ae8bb3b988446fb6cb8d30d83eca31c60bd2a3e84d9a459eb185099bfde0cc277a970a3b1f77ae9e222bdfff681010504f0aeb89de757474a4f21ea9428332dd30d91367a60cf2bcf4996ad0e9c1ce90680434d48321ac83f29c8941974b0cf58027cf5324712615e7e74ff23cecb1fd8ede8ec9d9348ef826287a2a1772f488521a74a9fc3aba7cf2853f382b6f79c461b9cba3cbcf4fea86e5a9b33ce5027de42a75d31df5eea86bdd6de95a30cf9d4279c68e3a73fe221267523e3f8be40a7578048d60512b7fb2a7855a9ff168ca783f0a75a1905431498a1e44f28e1e3475324fe2ef47ec9c1cdf728593c3ea4caa7eba46aa8fbd71aa69d668d30873f744c513eea993933ae79c73ce39e7fc49e5b84f8e3b0fb99a5a51a61742a4492f26d19bd717a06a7e9221cfa9863c4faedd0c64393e1edd0b8bae31df42996af205b2a767c87eb2470ae105b5679d73ca25c823a0b460e1b116b6445bc1c2a2b460338de62963906da52c3be1cf5a3c4dcfb2ad44b169858db95e42d1ab6e1983691b0796a79c22cfd951fc93373092a704c2fcaccf354543d14e9ae998e9167f59663793e9d7e46caa50d64a375561adb5e22f55aa37dd1f4c2653ad37d592092b99ea4b2b7d714a3b764dbb66d26ebda95ec3b2c725639e0c8f7a8f05746f298058de52106917666ddb36ce86fe6272ed3a6a1c58f40a8b42abe667e93acaf39ad46e56deb9a0def21edd5db09095776fc1428cd40b5139eacdeade2a1d16d2bdfbca85a0aef2d4102a47fd46e5a8577cb3f2ee3571720a6816cb4d5514eadd6f50ef5edf756fc1382bef8ea302391b1e2c3db98347b10465f815a10caf72a5206299577951854df140bdf3d7513092e208a7490194e18b6047c1285f4771267efecae2428a3312338a1e93c696604558ab8fe2104552055f449cf95a8a534ebba6dd5a51b8130c046257748d79b1d6c5965cac08b3e2c87e368af81167e8e7a4415235b9cfedf3725e6e1167b417f5b4788876bea983b5bac6445dc594325d339d6ebad8d7a9d33b0b01caa6db37a7c3def49a78ba3f98ac35996e4d266b712a6551ac111a8a351d25eaa06b74b6a65b5392d5546852ca8961589bd2de9673ce0bc34e4bd74412e38c104278b718e3c4d98cd7fdb2aef15d2288914a794929e3c4536230cee092f18a18931ec4b0c65f863b5ed7bc660e8573083b646a88fb983300ff86cf9be1af8d72f6989b828f390ebfe1a68688390e4f41dc23e638a47adc7018bfe1301e00690a6cf6d835b128893d429484a0241c654882dc43486802094b202189dc4354d8dc43541cc9dcfd66e64e4caf91abd4498c13ec611cef2a5c93c9958753a51838558a8153252144259309008f817164ee6537ca30e336fe9d9c7236275807b762e3a6e065bc5933ae77d57bc4b80c2c44c663c8c0292e47c815d0ac4b55ec72581e4ec19b19f70eefd5780c8ce3bdc671627c460a4b46885291540ade06be078dabaec238399a164fbc9be262dc1477ee9e7715c6b171ef3800788cd410337ad4788af3aeba9018329ec3eaa1bacc65b0100f5fe0467519188706d748553c1052156be29b65037f4d44031b816f1600b0e44ede31b2779b756760bf4e7a76f29314134e0d112386eade853c06eee10951e14b95097bb8a5ea6ba19cce1a0e17c6ad81fb74b38cba7d1d9e0809477213292122520224747294fbc18c029fe12c86848a868ec024cbe41e12fac94976b60d951ae21e873be01e079c72003e8c18f77e13e31ec63172729c141703a7e05537321ee331304e8ac3a92166dc7b8f19f7b0102337315ee342bccfc03d6a7cc6676021315e03374b069671151662e466c655172203f7505dc6656021dc55b8a5ea44939c0da732e3a64c4fa5a49868524c29de4a0af7d44a8a938153284f7137329e4aa1c898b1825354b65b141977064ea53cb57273192bf886c655dc4da5527a7087d67ec6950144e66a514ad69eb0acfc2b87f98ba7522e9ef297a79e4ab9dc9452397794afd0a4c0bcc09f8c8b14cff47217930aa7b8d368c12915942fc8cc283fa9bc26e664538a3bb9a9d4cab9a71cfee4326e6a050f41e3aaa738dc8306567dc6653c95827bccb88ccfc0324e375d2032a79c64dc96aad355b7a5ca05d2dda4499336649413cae995b34139eafe885ebc4518c1c28cf21f7e1cc2c37b82bf994d87af917106e5b18838739a97717892c15dd51c96891ec763c82f55dc077540c8e16f66eed7e1551d3e3b39bceac2132c13bdb73d577ac3ebba258883cc2026c7711cc75d1cfe70327778af77b7ccf418387572d5735826ece1d4094e0de13dc66fbcc7f8c963601c2327f826c65518e75275f21827b89fc3aa91f0367781d0264d9a30d921b3c680dbac184064def007b38681c8128e000eb759306eb35ea260bf1f5d84b3ec628fb9cdbae136ab46cedb196313b78ecd81892fa630e79cf2f04a29a5d426ca35ed1aca0f9a0c3921b0b6fde446cee6243e27cbfb3d4720c39fee372f67da6e6f4e8637c8b55ddeef46deb8fb6d599b3ae8350c51d77e733e85c59cf3f31e0d19334fe25544eb984d1e420861a4810d3112610bc96b0def4723c39b60609b551f5f83c87c437e850afb6d25d4077f75ce68640cbf9382fd3cf983468e9797c9e2cc87cab285a609e0ef11ec0735001c28cfc3b8d6dc8e79183fb9dfc9eb8366a4e4ec7508c04bb502086c9251ac853fd6da1b376e683a04e03afc6ace2687d7c4d5ccea5773363ba04ea893a6556b6db53f9c6c3c8a1eacb54a1e0cbb2857351b7c92c706361ee5ebd5daa4dcde949b6e54123d28f385630bc79947f8d3456022ef50842345189277f80e45207245cee11fcca72da0489273780e876289c66dd8c9c9c93cb9e289cc41871c0e374e07cc395cb3a75faf185669d0e9302c2c29296f8e8525e584c36e6861535236cbd9d4aae3f4eeb5c26a391da7eff0eb62ab9f6ee7541030ef70987738ccfd72f20edf2108017c00ef68accc8e9765f6b7efe674c01ff071bc26621be5ea75b8343434a75c3dca8534a7f9e9d809e6155fd7f59caf9ee535b2068919f52d25057f39574acad53511b25cabbf2e7b5d36d6b68c8a5a45464618923b77468216b2c88d043128b1b9912006241ba783dec5bbeb020a26906c81040a22499664fa08c5904c1fa13852842d327d11aec8f4ddcda27797d45de28c193366cc10d23366cc98819343e35c0aa72cea299f05a203e6ec33628d15eb5cb81cc31f8d8cc538eb23516b0591282a6971f6cab37036b57e72db9bd38e72533a520ee328efcc72181e5eca711e5e13b59ae73c95c2373cbc06e3f4701e8ee3c3733c87d5dc3d0f37957a7eb372dc54aaa62663213d9c87d7f4e003e4ee9b7b711eee759d2e9657cc741a4f0162937d06d681e3dd4df7b3f93fdd171708ccdd258763ec4edffd59fddf1d1e77f45e70b76121e8bbcb7b81c03c00fc65c771940b738c9d0a6773e10f275f3511757a146743af410e08cc28f745cab19b8284c728d357242208657a8c08addc435464d1e51ea2624946a293e9afe48911830346a9e6c3cdf11e2e0f7f716146b930972ecc382ef67b61fc8147cf72dc87332007f6e10c38bd876bcd5d3cecadf7874cb6f774c19a68aff77ae5250f0e31317e612ef0310c8ea84b4e08fa8a534160c791a88b44b65a8b5142501f6e0e0bbb9d5fdcce289defeddcc3cd61e1fbe5644a2477eeac71a86b184cb9306f712665666b32bd394a4da68bcba2579f72ba66ef1724460e01f51988d9877dc7fdd0ddbe539e723acca89bb2efe13039ce728be4c029fb1ce7e1ddfd307cc2457ac0298b83d8e1399c073cc4880e01780e3cc4c8ea489c656426b56387e7f01d9ec335952d09b98790f864c8518cba30290b36559f9383d8e102b800b00fd4777867980a2287d757ec03f51cde7905f3950e872ff8fb75b82e8e934f98b529b76731d9145b537016bddabde2d3399b0be6a77b17f7833987a37678773f9cbc3abd1f4ed621871b24e61c0e7383c4bc03e4ecb31baf34cfe24c3dcd2f997ce3f6ded0c2d2fcb3f906cd339a67375ee284a04d9a3469922bcea277e3cde9a0c137be9ad921e8c6953a5c49240057e670e51237b01ffcc99a83c9723dfd84bf99ed3526ecb79ddeb9721c67632d863f1ccc9b433dce543c3b16da75428d03e61bb096aea32c375a8efd861baedf7043938b93afb7d88b0178fa0933a5e4a4d4c81b302cbbc7c0313860f853adc51fcc57095bfffff3bedb5e3140aa2aabd61b99a80651db80af7129b4c4ca4f39fe753cfcb331efb8f2e1313e6058d403e601b754c9e0962ad5ed8cba9d61b4beae0ffbd34fa7afac5cc3b69595affe74235ff57e1727db5f29c71abb3f6e4ec10180364eaf91315f077361c6a48fcab1a75c4d6e30299ccd75dd0f02d95ee56ad7c500cc11c8f55f677a733627db25194bb92b1a2a25db265945454545e55f17d9fb5d08e4facb9aae1f379b7000a00decf67e3a646c0543a213ee681a07ecf0977df52cf8eb360065fc05fe3299bbc0aadfc62127848d9fbc466600dc0f66188745366ee3326c5c067fd606fe7ed0c83030004ee3aad74800dcce32ee97a9eed7e51af79347c8f0312ef793f8078d3c4fe3defd6e320da824d2b81dbdfe2582fd32ad844f5698c8f525ab72ad7f78390bcb3597bb84fde2d191f77cdd775c1d2673c1b227c76500c49e823fef84bfabe0afc38a60dc895e123d70080b49f9e61ec2c292ecb3454fa6711817dac9306e3fb3761921a9878c4044fa40266c66b6ec277af51867637116bd8a82ae016fed0a13ec2751903f591467ba43974395c34b14340ed36947bc9049103077f466ae4fb95ffde99033611df51626058939eaa864e070cfb396d6fbf066e5780fefc1c37dc0427c380fef010b3172e3c379b8901e9ee3cd8ab906b7a813756024459d24265107e64f0dd1c373bc470f38a6870f98077c1ef9e3917fc8641818dcd13b86f119340e23c62d0ccfbbaad398110307d59bb39171a1920b7188f1c3e06f66986b37f9f47a0de35a6eba761e9ec3aa16a786c8711e7e93e33c601c23f638f526c779380ff039700ecbe2efd69eee07f329e5d5d6cd743f1ab962d36ba4e9dea7dccf7b7342c0b88df9e9de70c983533b6246ddc56b24ccc5e1302e048231f7cbf2fd2f0e312e447299d4e8a35e666badf5516f2d13ad96256a1129c100ccf65048e8542f54ffc12024156340668b81402660abc2d60b201344ad97291c11b95ca2be1e5efa1ce5b0208c98b75cc1bce52ae532390ebb28d79f52ea4d3030f04793eb636230c69e973fc92393e30e19e1486e79e9da4ffe82c48cba8b766df3af18ee9cae35077d603e1df6f02ff3e19fcdf65f97ed3f2fdb5fae78b87dcd6da972711f6e0fb7e50f997cfaaf9d2ef409900bf330fd29817a0c63c8266c0893b040c35aa3721ffa68783519da099aca18ae0bbbae09afa0417a45a88529b33c23af2967f462cf75daae9479e9631e3bdc6e68c1a68248f1318fca9d3b7f13a776a0327654c660cf77ce1d67e4f624547ca09480b00d4dc02e7a51ecca4209dc381ad6de9c19c811a3f89a1cc8f130cec8432886ace5a5564d9c6530c3160d34b863769baeac35ccdd3967420dccc8b25d2a6345d60c4347168c11b200e7ea406bb96261d89583102c56ce3c0246b80520682c0d6e57abe6c90296456fb3b0db2c0d76bc6eb396d0b03967841ed0e68457072694110e41c37266a075a9bd920660074e3ae7829009373c10d4829d12a020091f104187e107f69a584e11248c0cdda00966e831ba90a022092d4ef043a63bc3fe6e83fc793468aa60227f2dd44986aaa8414f14296862086a1882c492b28972cf1b720f1535c9a6dc434548f688d6cf826d2e421e493e45d306441b0eb48ef920cfbcaeebca2ad69bede6dcf29cef0b4462b7c8764d6e14723371a66aa5f78138409c89327af2317a387048f476a8dd400b11fbc9e46db2c06e7126420ce9135ac764119569e24a9b8176552a22ee207f4d09c87798d422077104b4b09f0c16f693c9376b15bde806f2754920aa906692269d60e155b4b0f0f29b110b85748dbe0a182c3c8c314231681d53935b080c36fe9a71c618048b54d0b41c67e39c0c71ebe8d93d67f7ecd9b3bbc140318c6254ca8961584b2963f2bca83c867b46b694f328e8c7a6402b652f3dbbc41fbc32eca39f5d83bd59edf89af304cbf097fda2b77ebb27d72ef7c9c38c524c7f7439fba4d8857f1421ef8f2ec74b25d06fdae18d2da4e672230c9b768880f8fe341c44faa83888ec472d621ffdc1893fd87de1ce4060ee5f37b5e37afdf5fad227f6d19fcfae5d3aebb33b2b9e1b86951e5f7a8d2c5d98afc38abfaa5dfaed66af77be5ed330bd2d61fc75af8f2032777fe384886fec235ed334fc553c4bf7a15dc336cf4db5cd73977ec22032633e801c41af8f2012bb3ee23b5f1708fce4769c726ae21df1fdb8a3cb5fd32a4a7d9230768650681d330fa1fc25040b1f2f2bb0e53819e9a8699a8f2247c8b08cfcba20b88edd4706642ec281eb9d254e0eeb7ae471e1ebf04e213a595e0f8194980611ade116b90f9108a069dd0ddf57b0dd4162ec9eda15ec372584337a57b0d9a78410c2ac218470369411e9ef8285ffbaecf22f71e66edddd36ebeedeb2190498bd0f2184597e9ba8f932e7dc8106000fc06bb0514353386ae5aeb1d316bd993b0a3028c95df41aff784772fccb148260458e570840a0d1488185ef4d87488370caee6e8bd45300e5eeee9e32c6c4c4995496b7eeee9630b08d7d3492fc804681902a78db51d84290b2a09021514da7e4869c0228ab846b5860371067e2e171e20c4df4e05519e64f468706f302da8364d9342746310cf380c019bdc982655858f089d82b04bb5261ad15ab87174837418a335be0479ef9b94d21996cc43c4bf70d7f7f07b18d9d8795e422205b44f4e6514e60e790a833758032c59148e4c178628c31c6083725b496f029f2018fbd11208d0862fb4e6cc27e91076a10a7b2c76738882d0e49d1cad8e3cc1cce66346275a2146c7cfd23ce00cc1f95d8b586339e60df6e77967f453a1669640552d0c91057405a0106223a39620dbb189cf9ab3ee283cc8c1dc38c88de3c8d94c2d94a944c1d29298c3a3b3c30893c672a08cc47846fae07cb3b8c8343852776585d77712012419041100bc7e5382fc7717178533b4acfb961b99188bb474e47841c8f96d36b5a844f5d23f4dbdd3175c4eb031ede5410a5c797bea3744af107e3212704c70096a7ae414e01cd6a79730e60790ae31c216f4094c44abd7b0660ce5e23e32d3dbba597ba8b0341c03385100bc7053ea2a7904410cb883cce0b3ee286198258296ca474177c04968424969193b7b038c1ce97ae4c5db973c89d3bd12b510cd57d6641e6cc6cef446f422b8c66963ac212267e8aec767b7a9a379623715647ab112caba8e8cd883f1a18362961bf78abc3be747abf0c578046f6615fbabd56baf1f4f06218a5cfb0434b33faef26c30ad0c8df4df69e9f3a53c7b4448c31e66843b1c89361fbed1661f9d54165b97de441a1c1da4f163c87b07c0e89de1cd29de52831c218233c1dde53778dc0b35c24ce32f2d2b19c8bdc351695b7ca63562a7dbb5f4ede340cfb0f9db1e76478bfe7084c085f57de5c94a5d39b93e30d72e5151c0068e3462ebd466eb7a3b7650ee51c8eaf9147dd6fe6146c44f47ed0c8273c77a6ce144a7189fad947457cde99608b4e9e7df6dcc4990b70df80c8d8558185cfb2d935127e592ec272ecfea0c93106b119fb01224d9cc93e9fa3c6932b16dcd1cb6663a16b58a16dcc6b175ad135e6e98557942e3cca227a33ab6d141fe329463f9a4c6113585932eca92c8bf391888b900e3e4b92f2bc4f134079c6a03ce77c4667a6ac429e3a7205ad88aaf979052c8ade8482fda68ecffc9ccd3242cb9167b24e0dd041823e72c5806cde8839fff2010bff02027b09a5441b628071f61704cb7d7923ebb79d114e4a31ce112cfa2b23555146aa6472c7c3932a1739b0f0510a1ce4f8971cb0e2e5fce7498f860b27d44352d0c9b1832c2329f0e4af5b9c8e148a90bfb6425eb38b332f71669281c6cb08905808641e73ef319fa20530b862152e256cc8c109ae147477cbeeee8edd0db1abb1eb6aec9a92045a1c4084534a29a794524a6d421bb023b431e5001ada90d25a137607703d8bd08694529a8080958680ad602963ec18e337f3978ad0463fe6993b69638ccc119ff2d76519638e97dd0def17f3c92c718cf29ab223c45840712e8c5ed725e31111bb9a482831d8a4dbc69cb31fe1bcb00bfb81c5ae26f3921884524278c2a08497d863298310c3221614e3a6e85637567adfa3efcee240b32ba6db867ca984bf23f2d5860cc32efafe840328c5b671c1d8309e408c4e194f5e34db221c006cfa2a4b598c56c26a29533cc4c2d7c489d9b8a49098e7b5d2f6755b561bc7d4b87b6a95fc76a53c59205fb133407326af439a4ba73b2c964b1848d79047e44b7275bb22044e402206186c29f7901236d0ec25bbae086722f4e0fb82d73c84104208af39bd6c622f110ee03ac5b2972c6799e72c4d6cd2db7962d9cb8bab94c15206e11bb3557cce2cc9946491bf4e52a4a40a29e418e42b265f57138e902f254ae4ebc615745032f97bc9c1841e26846109567ceacc107476094755c8df96278aa80479526188880b4b90420c2588c00a4ab8c2da2fcbb009f644470be2764a5089f800cfb682522b49ab415675404b4f604e10ca86884c70cdcc4e91822ea080080f04996147676768880d44e814a5d44026cac490813de58e420c6c802164b5dc518ce104558ce1055f18c30c66f4d82c771463c88137033ba99079ee28c850c5fcc001e2cc67a3a60040a0ae6ba27e20c08413af28818040a402851f9ca025a60d489aa8ec0401c8058c249028a11205105c40052888a09288a0029311b2eeeef6011172f7690ac0a33f1b675266314fb0f0fd3883e7212b7a727fbe5c97854b50824496f88978868e872965747663d735a38c538832c2d2a905c20b4613b412b029ffcd39273c651f6d62fd9a98d1eb98bcaed933a31836e7ecee19e48ad3057d2161e1e375cd6be294c472c4a2db66d9b265777777675052011e368700982057a4b4616268c30b754ddc3171e144cd84104a3803f003fb6519a8a58456286163bc1a9b33b330c88e3dc3b28cca2ca318c33d6397638c1d73288c4dc4d87dec7336f6c67e659916331a23ce6a60d1314a6cbbc61170ce6641291cc9f663c71c081f20e535e735fb03ddfd5386724af99a78c1fce564f9a923c2a3c81d68b2bb5bcaeeeeee8e51cad9b108730c5d843093ff72329c17964062d89c58b7a9d492177627463367dfae715d18c5e6293ebc2e2f6bd8d735fb7a4d2c7537be2044b141264f39a544a26577c309610732ece779fd82311a6918bb21ceb07c733912c227c42cce5ca62ccec0381ba722ee5cca16e83296d1f79db3c212cce095e1a71c81d4eaadf5b262b54a292fe7155a8c51da07d5a880200806913065f6f929bb67e4bff92cc64b39e79c73ceb89aac289b905168cef63c19191a1aa12c7dc83e64d3eeabbb9f050dbeb24bce970cd3c83db3d2bf790c675225e72f0c772b20a96ca8a46d15753d8d3a4533224000008314002030140c8804a3d1682ccc13416e3e14000e839a4878569cc9b32c88614a19630c30400400404000406466da00c0c436764055e2e393cb7f7b1169696b44dc3d53a14306f060f1015863a2d2a8e5843b9ffb1ffa8beda4fb023d1822bb81e7dbad20d60725548b59eca7d708e201e3b7810fef2df099633a29698dbd2f1ced3defba012367169b549e4d97c89dfa747367248fa832b38c5672abb70631dd62fb7c83312f247bb3001a6313212385d857242d53ca162676cccbb2253d5c5e75268c23f738cc0ed896aff9c3a49f7b4211fea5dd17ebcc4092a25af46e284d62ad80824a16614b6585ce602bf3971b0c8d51f74d0ae44034f3aea9ecf41d62ad7d8108397e3277bb31d0a1a1c8771c6c2e2776520bd4328b64ca5ef0d7aa8ffc40782d4e095f26371739baa153e4cfb722c8e14744f4cdca76cbb26eb4068f060f03935dc6e692010fe6aeb8bd090278383d7c603c47eaefa506271e63afa1838ed54ae28f32a6b9064e12c10b0b4488bc7adead5c92720472bc2420b75c37d19f2ffee648bed2445cf523c8b70c463f768135a76089b243a87348f645980f14e377a9cea930fc076a81638f0d0d90086e1d54713a8721f80ec81b7ef4c39bda043b160e3243110d5424069a3480936c7a4d954cd512e0642e15543b42337a6cbdbd8b8d281116e2510fd134f9c9f57848ecf632a45b81b40d5be89fc85854397205ee5d25fad1ae49424ceda8258c431a9d380e047c84615b261da9fd3d61af153243bf0aaeacf3c6b18225e8c690dcbe4416ffce18c230257e1d8cb20e0c3e1fe18fcd421986c2399fa401b3eca1ef13d0293c667ddf4869e7858bd67ef8bded878b01c03731c2ff9be456fff7694ddff5f69e73e8a185cc91e73b8fe6a044ca8a7ca55efd7ea8f8961e72e36431c4611d3fada9655befdc3426b1c299cb0487195a16511d77fe3b83ac56a9502e95d8a00ccdb971c23c1dd65823ac470d3418a6fba0523b28c099062a9e98893b143d5a8e1e9b0f2478495e81b84ac1722cf92302aa9269b36de546705a4bd228249def296ff7fad39edfa4317049cde183030ff9e1a30389330ed683ca5bf18a1f1c5d1c51b32529375d164e4ff80208ecc4ac8f2b3bb7c4420fe3904d931f937293c371d5466e0aa0e34b00d6acfb4e0c58f7f476a28e13f9884e0cdee4b5eb55c5564bcfcb3edad5367a6a5797caf0956978b41005a2bba3275d275c5d210e7f919a800383ae15989db5bb8c2f32433a440bc8ecf88a61e53b75b7ed8a01c2e4df9c4062b1c2b6fc48f701d70a8a87e34aa2014adbc13e7245a93bb0c2a6f41108779272ec56c733252b48e88ecfc0c57e7b989db3a5ccef273b165d5394961537143bd85c1bef0447647df647a9ddb3eccdf51f021bd69a5c4814db14a2df1b3986e44abc4dd8c2e9efc10bad6e0edc875bbff776c47ef0070354dec4310362cd23194b7037e939e3c6dcfda97aec58aeadeccc3afdc97f380e9a284383367c04da44e6f14a2f6f6046bf80c9598f35331f1a0e5113ba8603c2f863028743239e6fb643fff67e8948545b549626777658a5a50cc544b4e1639e4888859c598a9279f210a81dcaa528ceb010399ad36a9dcb4dbe86e2dfb9b1ba3c9169a3ca4ff1c4d288165573338e200b8e6140be97975c0a4a01d11b881ba1b0688513c657481b947a58c256a855e657015c8437fe54329753b0f14ca5dc2466031d3003d348ac380c969c5bb6fb579cc23582874b87202068b76b906831396ed9352a33632cb81dccef939a2960823a5a8714fcf13ef3f4521445ad7fa4d496d3dc4efb1c7837a2f9ce1089147a7b683113975e80d235164f2c02992f69340182eee9aea434f4c5b3f53f804bc98b2c1e60c6d0c98471c064e711a9cfcb5111fe990de1f10265f67d28cd21a74a4a6384d49e53e43b191a7e781f1d301129f523d964b37957d8e146ac0e673fcd126e69718fd540016b8314dee79fbe432bc35daa3f3078388aad066ec691bd0709840ed1d930e67738a2b355b4cfda2a494a9cfd26c6463e17aafcb5815b5b48ac8445e24d5f1cfda0b9ef3777c0f24a2f58cab0e4e63fade0cb9d2cb593caa285d84c4125bd80a1c6aa230d2c06e34a2c78fd74dac4733a1287122223f3ce7351cdb88d925b0edf02711ad3fa38f264d2f039299df95d04dcc0b17dcd11f1149f079538689e70eee6257ba8ba85532a9afb879e765bf9c64522a7f5aec4cbd9d8bfb1689f41dea405ad318c868aa5391754a78f8d35b47db53e8ecf87fec02ea293550fef24dc6e0cfc11e93b502d626c3afb2e1600409277155fd28a69bfbf18f2a09f67cdf609f61f8a3c890673951edb4f23d78920cdb9e08b157b1c168f45ad2f8cd73931a232c2b62d5c4cc108516dce83a4bb3260006e8848136064b63b69c5e8fcb9e3afbd61d9fc4623bd2487b5ab41c337246d643af851320faeae845109fdc4600f216fe429290eddaf6aeb6a8648754188c8b3ecd379e96ded7132c80288aec874cd785721725ca353b4903ba297d656f76292b5fa5f4f9be561c4de639a6aa0054f89af41cb7b338048ac33e7d8ba371515f93c4badaaa7d9222ab3d6dd2906da792a2769d17164a3ee090c88c545a3694bceecf4ab1e484acc4d232c63fcee98c29cb492f40352662cc0147d07b2580a19392e91a0dbe45ca52a0252f0d1fe4c976749a875e91a9824a4ad32df1acb490a2958b2b571c5061bb57b32665108f383c4a7d463fe69469322de535b6436ec0d31348b52283e2ac868bebdecc72e517c662ea1420e2ea0be47f8df233a9dac41e771df965e986cd3557f8ccfc73defadacb0c835c53fdd2f0fd4b802e585a67f29d292d08f49912853080132ccac600496561df98a1fdb943f1a53bbf403c907e392a3125bcba002b1581853775d46ad8a84d89b9d0c527f885ff7a1bed4a09bbae225512e8eaf4bac07d6eaf3db807dfa8edbe84036738d189bbb8e0b83da99150ec914475556ce094223ddfa764366ea61d81ccf668c9a81564ad7c843654ef76b2ed5bd8407d61621d86023f2d32b1d5b38784d4179f1db676e9f6f96f97882a70cd33e84d51da044ce43897a29e8010191dd76e3020d94cd3bd912696a4b2cbf873a099050f5050e1ed2443331784cd4da9d0dd116b5681b20de4ce509416829d191ad8f178d8b6a168a1a68f7a0a9d509b59d6cd9d732b57451ba7d59caa22d1c723552f4246b63fd06a5267695481dfad90f4a1646f68917cb45692155f4558c59cb9e6f1612902f88593d046c10915ce832aa5a74a0cf22c141fd5a75c91e608260d02db438b5ae0c2d453b39ad74716da369e58239773c59fabd7cd27ba89d90a52ffa0c3b4624ae55a9d99aa145307dc6f5675e79e3558f1f8cb09086e9e61f976c3f7f9de8331061585d02b49e0961dc181c4d8e048c0529375f4f8395255be020b88ef7e248a757fbddd76ee806ed79c8533fb2f1f8b00f0b42b8e62e22726a128239fbe2fe73ffad1d0478714e044a519fdaf4e4706b801dc16332d2184bc824e9041b6a6734ce7585cf52fdeaaa9498aa3fa449102819f0005b45b9f6667f4fdc400bdeb3c8d4f96a8144484ac9b8192bc7c2446cc3f370c42559c58fd2304f901cbc2cccd89bd5c4db98ccf407e05db64ab4c2176dc67ff2738b667f9706aaf55578e3980512f3c94e1c83fdfc096ca7b00ada08c4b9a7f2981dd8c3f3f323baa278672bc14f3d8d8db265e2b4c21815a78f7d19a500fc55762e262930f4b3128e2a7327b23dfb944c190fd25da2a7a6c2f28809f1e7390bb4a77614e8d9dd4d11a9c4412a6b06f6829beb8d73898743c877264b60ec764d5fda11561eb330f0fa6fc9e29f3c61444838a18f3b7b598987bf87f83cf8a0c707d900fdb815b7b5302e2e0a0eeb5c011cfe26a6475f8608b5ece79af3ea01222d31b9b3853f20b4569878eb9b01778bb180136b8917c6130551a2ff655fc189b32c56875b63e8277465b8259b2dc8a724bfdcb9645eecdb04da3c7ff11aabfe6a5f52026c4cf1978ef96c9f31a56c230882b9a2feeca8b39a187e28e1294e2f79aae36e05dd3865255b32214d4ec03e3f435724c100194febe1452bdf276310313add860c512a98e9de30fcd174571a482cbf73c78e5c08b4f9c49db2bd84bae61967dc7b7e7e807d9ebcf9af37e5c1af22459ebc14c8b2108d54b7fc92f0e415b25d293d9c98c921088d0726366e4762d5c16b62a72d3e058764ec60809aa67c49937576038955367aac259476368557b563c9b418ccb7055523bb40ce828e8ce4ae77d8b8e9acaf4cca4f081361b3b084f4f935099791bfd4b1751eec0aa0d1a604816ccb54923ba5798186c0690090ad100ccab762315eb4d0ee2fa776a9d09933c891169770441c83ec869ed7745f9ddb91d665069cce12176d12dbcca8b1048224a5119d499cd0082e0b24f74a6d6cc4640ef820b6ab6836cd6d374099f74421d39336dd554ac6c59a6328d5c4ebfddf7e5a1aa4db59b44912b25d2b1637e8a185ac0fbbb455a2cc51c3d06059d4d3bc9b72b419c3527e85cfd0ea43c1963f023d279efb094fa4a962a75a8812a24bf50fd36234b848566b364e135b53b81e8ac88f11e3898cccc3ce692e2bad8a36dde9b7d61a6aee17a0ad9ee49284a283424f4d9130023ed4858813e0347fb41d54f96f7986dfbf09875b67ab3ed2a2c0b4e485391f37334b7280ca971cc8638e572d60e46b5fe4ba501c40d5b777741f316c6fd5c56704405ea6bc294616ff95d3ebfbed3fdcb256d56946b6c04e058d732bdcbbf14c4b2b5465581d70a26202dc5d50b49f321dbb18bdfa69a0c0de8227873ee9b67647f8a3a7bf381295f219414cf7021d7b633b7f95b04f43857ce6998beab7ed66e02ce435f378ca9afc2d9c97267c9289dc9ab53f92b4fe7b2b506ce9f49b073d8bde4c82496ea32c16c043674bd820f5acb061b56b0edb407cfc93423bd161eebcc7be83b4127e1fd4609651aef10343abe834f1f164a3c949275cbd854ab9e294e231946bf9d5bf170a3ee72466d9a6562d7656d0525d3209ec8d839fa8834a3af685010b97a062ff3ae0c1a80a111424f8828620829497fef4953551fe52ee21f884a3fd53dee6fa17644471f1c59f7f7e01f228baeba95416b67d24cc975394a85685ec21e576c7446fbe1a4a701dea87f7381a10a929fe6a751e80f5d7a679b56dcd61db6553bd133de15a585c1cfc32babc38e429df36b9cb1b3e93c10911a0ac7665edbb2de33cfa3b101312a0c8b11e14b129197ff0eb9f121a617c44a805c17b44943f5cbcf4c288a5aa32ee2a5080ff8b943be8d6e0a68ab835a78f68c279e43712e350c34c502ccadbb24419da5c3bf22bad29696e0f3854772d50f7968b9ca069a2728d3905fa00329552a684e4e9e5f82d55121c1604db0ee006f1e0de516ab1d70bc0ea68472480b58334b11f2ad7a5ccb5a4032ba260cb212eaef0be6ddf4c95e11c346772740989e9faadc8f8c11876f861e512b894d47a39921ab4c6460c6e8bebc09354fc804cf9b59bdad3cb94ce9085005eac5cf49533945096844f8a1113b7b4af69ee9e75f7d0099d6e75bbdd39c240ebbd0213f73a4df4cbee7052df8dc2cc845f49324955886d000e51c89c8bd9a78c043fd1e48941fa253a1d8fdf06e00d28860ba268e24d049d96a813972283575024338d4c151e95c2a4510653434596a7d1d305581dfb7be6c5115b9a93152c70f647244f735a9298faddff763b70bbfa4d5901bea34bef9f4219f634fa35a93533000336dd0d99116e4d3a23880084464052a2ec469d321ece80db6bab330ab185b32a876637306004978b49bb01081d3a58ec5c3b2f9b3b5db17a1a4d2e9c9236631b8624cd2862fa1c324217ef737e0e46cd79c08f67b5f39db24580b8716bfb096bc790fe1b02ac658c8a3c33cf064d2ccb50905963826c6850e3a4117db24ee9b2bcd77171dab1bb4aaf63ca9847a0e70cdfdf3baeebb801c617eb6e7dee919eeebb194b238fc8fb64705eaf05ca0e8fc4f56510939759682aa7d5277b968c89e172ec3aeb851a16f05bc67c4a6715cd10a8db04dc5deb3457c757a65c8a3c667c5e3ba3c81ceb5036cc17bae2a9324c055af9e7234664c3a306f5450f3162a074bc5048b6fd7663501951c48015fc300a7a97dd7ceb05a8045ad549414d086536d6ba4fb877585d07e99f7106a36e009c422fb77d27df07a1e93137268b1bc8a23be72272f76ffb3a32bac5248c7ff8fc7ad3d146e27950700e3c2c59aa1f5c4a0d083d68187521a83e36b473cd5005f19b35e6edc23ccf897a4c3fb93f859dfcd1f0f26cc45a40859488c8c4a11ed9aad3d6cac4c0c156f8332cd24c01b1bde76017e945da6bd1e13db405e8aa020319a69b4826017a36a4afa4a00d51b3705cd4f3022e88353c8a5d0dd1fff6dfe234ac3abf1e9156411e34def8a982a74b9911909931e5338cedc5342f31e5e251b9739911c2954798b5140edcdbc72c2db3d9802807395a921921326292c6bbef0c4572a97bb5d7cdd5049a2294788648820a1fc9bf143429fb3379fd88c2ca5be114100b2711ae057f598cf75df8ef1c38865325177b55648a1d15b7a5252d04c2edfb49079b9a3b4046b1e1f87ce526545a018a071aeee55747c87919e11237180b5ec6e02e41a2e5f77de1d43155514b9942ebbbcb2ed59a8105e89fafcd38858f8ae336a7afce90c14e8eaa874e40b64c03afca87c1cdedb4261c9ee486e88f09527e4c24feb0924caaa40831326cb457a85856eb55d282780f5b939baf9264cedbcc435326cbbc3b661ee33848592d6acd4d983c34e06a7b5bbdb4af61f1caf746cca12648d9da68786846bbd6e645615496d98242df15a44904dbc2e4721600338967605d57fa23233cd0d4af629f0ec9e6ab90fd4a7412817a33467402b8be0161a62288bb95c135cd23402a11918fd250f3d6d0904f1569af6a54cbe389600542e2fe0b3707eba68556a9c522e8fdfbb31b6c46212d765977a0286045fafb3918d38c38267b0e3a972d811264401cdf3586844982dd59af7e0058031992c77450b0250bd7e5764320797d48a0f3f36030e45c1a21e8be1cf44814d080a7b15e267236f4740a927d028e4ed0c97bfecee88272bd827341c703b1d4c857cca9fbd3d10a96b3e6f9fb1421e1eeec91d1cb30cca8506c120dc7a6c616e370138a02f2ded6940db5c1e92941c416c2f2af7d50ae5381fa970c8a0a44edc0fabaafba29f38c902a34f9f523050d944688ac0dc001df3ae96a435fa621cd81a601965f2985b798ad3243c70765eb68f7f67e1584400b5d9e2729315e714ac75b0a0b32c194a75a3e9c0f0ee1acbdaedd40214324409db721cbff86ca6887b90b800e7860b9ada5e3cc499096a954134f4e765f6a58b33abc4dfa1e0221b46fdcd08adfe3953393f16a8a2a117cc52bf1d0f428209ba78b10481c55803e95cd3ec79099771ed3507e7b5f6551fad4b49f0f7a32c71a052187ee35c347df512ebc40bb7ae94a2389137dc510b694233b731af2a00bd1507e07c441d9c2f219b02e923ed5e5b7a1bd0b544e226474274ec8f35a85130468320076bc9ce0c26dd033b154356d9bb33d46c556988a5ec27b97de18f4915751cd18aba8dee4a73eb172742819871c40f9495d1121f4becdffbdb6617d1abc3152254489d6236309efce375519506ab2fb31c2448b9b32d91efeaf93b80251915292cfbd573dd5ad75726989e843cf9e3934eab3bbd7adc7087be75d787647cd70a5b69ca3337b327dfc7ad5569e23663ca5ee844b7480054b31fe72b96387aca843c0b59334d599441dd664685b56bb9f4db46997cd731d2da3b0089ff13c11b09f98e135666da5306a248611337788444c59ac0aa5c614035ff50f8272cf339acba389309006f8c477867590bf080bddf6bfa523027ab143c68498b11728afac88f684ccd8e04806c91d9312fe8411b6a4937d8102715a0bc838a49f1d0e0b9e27c047a1cbda06fefe380392fa1fbfedc21a0651d93b20c81a1111a7ed0c44cbcb8247cb703e575646a5241a663350e5ee0191bb9167a60cb71321cd7211ea634b735db159bb5b420b343d4073a333091046c703e45a5e43fb48b98fbfba7761d74205c68f733cc25bd16db1a4d633a9cc89d0cbcf3058f4b58e231da5fe8de53672689e03de0ea79afc6538a62d36d729c33c58ae66712493a44c9799d4016ca6e249e2dded55ee119f8d43ad1be5920f725668c8f96465db3a1c9ae67468b8fa006363d6d2fee85ed9eef9a113896a08d35bcf3783d121589757eee278b94e71e4e94e41bac9c0e7bb844740e3abcb211ad2060947948086da401d702af6dbdbbb18bee1375ac4b2f088909309677193aa468fc3125f3d6170a1afd7fb1e551afbbfde8f61d0b71d00ac1fe932c08d62382c1f97af39417f7cea6981b125f2f3b5f7369060f67adf4dfabd5ef3da7d1eaffc64e3178c0df4958c132b673b7e24dcff797937f72567034de94f3318ba0f66c241df9003c2f09f9f71b71f7974fcfb6e2459dafe8f2135090fc1cc1468b449dfc2e5b67d926c0af2be9a478da0005d53f3fe8fa8356d2470d01185b0e45067293aa82fae2ce8445117af79631f84375d7b4f9f4e7855a9457d3181c3d0a6c13dab0a1a41c8aac32bf5c41864cb436c97e7c739d749b124941dd404c9af42f60f4da322fa05dc5aac283f9fa612567b80a664763787b9f1761fa06ba811147deac1207d0c6ef943c33d29e9a031bc144b10b32e30cef6b7d5c7ee4a1762825590e05f6b3afd1212f95458bbc1a545cd66ba2adc3b9a55a2aa3557770c610c6d051239da83d277001dbbf035b77c7ce6da840f04d43030a3f065eb5f74a0f70b78f2bdc7bc9785c5b1369c9115d790cbbd8cad3d8a23d1efc6db23b8faeeec31c15489bee096ec7381f8729e6886be1837658b80a135c4ee7ffa6d06c6fa304994bf8ba9c4e7edc8f50836ce1e1c3d386a1c6edfb9ee95e5b1bb52dafa58f7be9ce5b0e882195125a53c4e33e795eefb3352da1c20872bec804997f96659f111ad109435b95773eb88bea1c124c4516ee559e7c581d9b1143ec5ee10f32c519e3227882d82c4e74250d429c23b044616e0a70cdfb63b616562f6231c37bb53a4e4206c21ff4d9d1c431a65963fdd765f068daaa97d009778b37dbe92fb13bb0e3ebab4821769b56b7347a32d1f0631672ecdadb753719d6a17949fade60ec0f1d942bf35eb5fdef580bbda9218bdde66519d8fadae2c4f36ff389b5e8bc79e67846aa4faaad24854e95a55e654cc4c4da3720feec8dca98950ba6598e9ccacaa09a404d52945f55248e082d366abd210594afaf5101cbda6b4ea4abf6e3efbece722946f79c2de62772118514a3e5dbff9af6a22b5ddd7b152eec6709d74341278a99637a94e297d4cfb40c8e954064fc3810ab0ea6e5a2b8d77f6fafbadcbf05385f82396fd07a0441b857f752c696a469414dc1f63fc4589df6a018d5cb6a54bdb3c59219ffb3570172b02fdd65bddba677814fb1dd29ae243a7f060bcb25b7725286bb3745eb7dd95be95bd7c47c1aac839153f2e480b0b1ff021e8f4a4cf3b34e5978093a48691fc83150f2e875c0cecbb37f6381de797e0ef00dc8b883d25f9dc24a2a034e05e45682bdd7fb3c865a501f63e52af077a3ba2501270af23b49502945e25c95c44e2a72753a338155f4aa2825b35ff71efd112e499212e3b7a81d00693c768c07a13e736bff9b4e35dc5accc57a344bdbb776326d4fc8e870aba582bb7f1ce91ec7993d40247a9e4491a9ef95a5853dd4551a860f1c5b5611e2d6adbc2aa5c6d152e5a546a87f6a3e5a5e0221b658e30cfdcd11b9d873690b833c345182c771b0a14f357ec53a0d611649b93af066f6ee3a460943cdb21f3724219771f1977b5f3846750f6424112a885cb10b10f48ae93756d470ee3ed54ed81ce8cae6c3877db6641b0643689973367125ef406f696eb58c67dac6f1c1767d4eaa82d0f4d35236c998b3c0bbec8301ef4871eac90dd66739b2c73936dd6eec169479eff6d8632302955ef3e01e20193c49115aa31773c625a1eab0d69c01fdbddcf56b6f6a5a799d34d86438af30624bee30d6aa1498514d436a73927182302d2d4fa426634492cdff758548690817c85aedda916b44073f881d0e1ce6aab2e7aeda4cbf24cc324c61569186dad962ec6de7bd095ff7b0c8d52f1b64c9686f70c73a2e3c6e0e29c283811b93e76f4579878bf19a1a65cac5cbf1dfbd53ce1621dcac6b4bf1b5143a90661d14ea2287de3c97d1066a4d405e76c20e8d9ce6a622ac9ee42d232c60d2ea1f672a24414458366e6e12abcdd3918c01d7e70aaf05ae487994a5b84fb97becabc2e6a564d1c0638516c384cb27f148d497f208142b358366adeaaaf229e360b28779999395849d5fafcd6d89d96b05267ada1ed91f9bfac8985d396d38f464407d46017dcb4867d7c5d1ec491d235a0d1c65233840b9f8bdbd237097aae9ada2618c883db74dffb7b2f6d0adb19c76492332b6e8c80d8f3aa6c582595464cc0ec84dbab790bfb1de86cb54bbe24e0ad25b19ea57cac39504a65daa8791c17b2d3479dd373ea8e9f0b7ae0388da35b29953c9d46cf08c263d24e66a91195dc748a67ab3ddc1d1b565acd2a55109b199c553d040501ec9c3c101470bcf49e45f9b40088dd22a0a40c2895f8e28d58871779e145562689e9ba8893931faad1ef3b13fbe728d11f0dbdab3d898ca387ad000b817166914a4dc89800c81c1b526ffc2901db2bfc615a645ea6d5d49ef7728900978c9ea1a02ff31545255ee28100e4ec7053a2631e946ea050df4559fdda37ba1b8ede8ceb446ee7e24ef41a7d0c7caf81b65b67262f7842ac705fe36f803b11032c3f4eabd191de8bbddecc64624c49bb0c098b73095799d5c69b0394f3f884c4be4bad81d892de9e56b8562be2ef4d20ef440d028a9b13a11cd8fa6fb9661524895dd322739c762bc7c13ab66d7912566709ab1f522c406dc6e3b54549b86a7b29fea9bc4604bde5d1b4330817d0d7647be40bfd5030cc6601df002854190fd186ec7ceb90976503dc76238653577055a4ad1034198b32d30302fc9bb6560af0705596ca06b4b6107c5003c225bccd708edb6cfb9e84defdef90357990be679485cb9f112cbbad0c9513d166aa8184818749807b2665e0a08234a81b8d6ae0558c251e684d3c0897d532f09a40b1eedf699639a1558f6c75e3761350465114369edecdb9e04f2304527a68183230327280e61d2ca7b0619efa07e98b133fa0b860a735a04619e2002e9e9a71732bc51bde299beb151d4d5b692711a827bbddd1e43592bfe844ef243065056e7f40ab7760504c6adb9d899c2a601eb2e44dbd635c1b051a8ffbfb1dd8e9029c9d15a4fea5b74c905cd615ad9f8643a9af046d59dd90121092022c0aabacf2a7dbcc82250f451e008646d51f0c655a6c86796016fd266ff7b2ce86f13fe6154a4665c7333fb049d6f7f9198ebfc7936e74340d74c3e745e6149eb31988b046f38e522b2422a6bd154ae0fe6e8beb7aa4c816bd11436a59956eb134426ec501ea9e631aca105dc86c151c0aefac881e0009149a496091be97713360f935337768040b217d9c407828882dd48091779884a834ca2999c031ff13681a1884d3a90819ede08e4b07c8fc5747144c598106e02d7064fe5207613864ab49276eb27a370b197348bd556d9881174626102866a2f862d6d6c10004f2ca0887d7f17f17343074c401d78ac8fd3f47ab55cb19f757a18b1e33b324dc8894f0307fae4575108d37cd7a08aa9c157df624a116a225a1aebb0fa04b38009a9cecdb28d20df2629a4346f2b48fca22bb65573bd28652d02e4709b663d7923586ff586f131a1c22f29f5303db8e5828011dab99bfeab744e816123123b66bc96e9e64e758f805bb202dfda12395c902566c5f506626f0e2cb038a9c7676817dc72e1bf196942893112c3468e306cfe1c0ed432497c921c6b7db911b823b7ccf7becaf0d1dfa618accec50ac6fcbf48b2e01bd3d781a7a8d21bdbe54c612ed8c8e433ca4b7f8a5328d1dd9098ccc63801d171a0f131db61dc46878a0923693c19f599dbb418fc85c7c73b7e04e3ccf5786dce0e06e084541d9e1db27adddf10bade7a791511e0481bcbf048da90aa09bc7bd019eeb51748644e9e6a923a363e1bf5c6b537775d5e722058fe6402a725e61fcc89a6ff6e379864cafcac0c03a844438d9aab5a97792ef193d7d5023d706b9a5b96c865dd4873b350e3322886d3a512bab9f27c9fa91b3b870e26647d52626ba26b332c17a22be9d4add760e3375d028fd10e7a6df2c416119c56830b8e0fc91c6235d39a162a047cb692e56eddf63d7138b0e26aa3fc693d7562be780c432dd54fdd8e18a57146bda6c30623b846f82737d685b2f42141c03edea0f4924e55a45bbbb07031c9778047e62463849dc3a2d25f5666a2b502a1d3593ff07f04bed875ce1105562492876d59193e79ca4135c387d005cc5e758faf234c8128f740c35ddb4903e5cbd69150b9e5e145019b7448c24b12aa0b60c1c0461fd0db1ba51eb4a1b81aced55864b569fe754dc6418de827663bffe0b38aab8cb0b75ffba296d84bb6ee7a90d16ff3ec936fe547877db655672288790f5b9290ed4f9285947453ee85522da2e033a6f5ccd52c828803d458c84647e09ca9e66c564d46b10c85666900bc5149bc5c6468341b3f737987aa0b7dddd2804e9f5d1f0318d910a14dfc9202141cfd79a95aed2fbe82631584c8a44af2584d8ffb2a56c90fc9f1d97abd66fb02bbe384d339fb7baa271dd04cd23f7ffb3faaa7bc34843f0f4353f21d3748eb401ce0e795831e0db60024f47884def0f5cc6054fd99906ef4c46fb872ddf55d3043f4e6c23b3ad847f8232f7555efc0fe5232d7dc7c5b3dac37be54f9781e030b2d2cc1f41ca1674b116127a7ddc50febe8eb00f1d14298791c65381eba2fb49474523e2ec5307a31bc65171ce3f098087a030a79a70cb844e88d1213cfa58e54904d86d2e0222075148152a7d1a2efa27041789aa4f8eabfcb5a43f2f905a6b01200967a61d15f0ab11cc558d388fd40ed4206662ee52f41930ecf87a0680223f8ee0b1f197e21e1666d19f6f4d5b871c07a92ffb8b8e90e30c16213d6a8d2a4367b5d207fbcebec1842e5dd7ce59a9a3f330ace7e29428cb660a06b3436b169ec66b92bff32f7368608a25a3ed7f16ec4ae410111a226c0194e7aa3a98e7fcd3ac238f6f34f5fa5711c37b6081281de0ab688ee667448fdad44ee54da9cf6a0f6f94cd0ed114f019b5bf2f9e6ed21622f3699a4437136159275c437736875adc7654931be3bb41d2e753407be0984e923fca1b4b086a5912c24b104e922c7ab58fbc374a12d75be1bd2a48285b00365bc3692314369eb1d71b85c42f09d747ee0054c0d9eece12f11596e86443013058c81843033b96f2031e8c2c39611d1f6fb4d384109fed2869b4f5313da0b9800b41ec60907fc9894170486fcdeb5835960ee31fe63a0d21e3f8b1539e938f775b1fed43ca09b0d6f9056384ce40d992008ab0bc44696cdcb99d24ab7bc031b40ef7ad59c7bc93c47bbdfcee8f5643c0504dfa659d794fe8f8643a0bc2f3ac9bb99091af3b0ae2784e23489f71d22f42a09e46b4de865befa2a346de0bc882642231c1f76cbf6094c23002be31429d12222de7450de501cd8b2af9202750593bd38b6c35e2cd25fe67819bd523ca87e5c24b7e517b746a23016f645ec988393a89f1122b62fe4862bceb1dea888c825bd5960d4029831af2b9aa1075c647291690fba5f82dfaf332ed2540c9bf3471b8744ff79882d22b5ae7d318d70192b5a12c4460d46cf1af641a69975b7ff07d41ef2a65ff1e51f36512af357c16c414055e5b3eef7b5268ad64650e4fd8c19f4f2fa3a842d1f2cdf0a49005f5d0350141f9490a5e2061dd03eda013346af81468b1855f1b9718020d0d158973a41b38b270e3ced6b50c4ff42bb78b4a3361b121ccfef2472d4c32592a2a3f4155dc202a8e784b661a1a28d224714853636900f10c37a31deb824732a3d56aa2daf861ed337d7cc89e54b648a78666279d903d475f7d0520561a4a2e1c25ca1a11ee59543d90ee4b80aefbd9d776187edf7bc35b3df1efd2f0c8410d5bca2536c75bbf08f976efdbebdef84cbcdc40916b9d38f1a13f32ce1f679fddd7c24f5f926c1a2c0a5ff8f7a657eb0bd21f1a10a0adf82e62abd75d057b0960a08773cdd04168a40ea6b9c8cb1bce3f101b1fd1fffa4dfcb610905f8279b9d2ab87f2db75757ec60c93937bba28a78d31753e536f04f9e84d6c7411adf693c26ded1a4451d08afb978bbbb2bc12591f520a5d2bbf1aefba606285c479ebe5acecba1f008bc9b4afb3503739bf1e6bec461d55dfda827fb3195dab61702d48c505b88925e2cea0fd0bc86eb7bafd2f480562bfed37fbc92b2e2e3904b07100f8da40494087a968425c85bc743e011b137129fedd7752a66f1dcbbd975e6fb14a047ebad528168f98d9491fa140717bc20dbeeda0c0aa4ee2d0498b2e017e21fcc06c5a8dd63dd56c7d93ac939832189bb20e7816c0e0b3d93e7dd3cd51c79cc27397565fc31cdb4ee34ef9365c2812c341c70e4d312e0076d0154dcee2682a1ae2aa9db1881acd181954a2e1743661cb7a3f524a855c6f05e4629578c357612aad2b23bf8b8e9151b14a008d4cdf7d5574d330706ef158b04f941249c139d7b2aea6e5bfa16d649055a7706e8b883c0d64a3331d08a65e56dac6fd367ea257f84ae464b3fcef0555aacfddd6f6fe9dc5a2c28f2b0382069deaf7cae4c14dde2a4af2540967ab8fed78d2d377d70084c2266d135b61410e1463f212360c290d1c7c4792f721b046033d6ed7acf193405b973818af0ec7e026c80d0a28b9fdc09ea73bfb0d0fecdc347d193d107513115d520c0739fd34ae7525dd795d0a4d6a8bd189042303c3944dc2c3bb913d40a918722ad5f08777bfa45e90125afe3160a9cbc067918fff1847147324879610249f643f88086295a6d7ef1b1ba0c6999f4fe41f2ff9b88d54445b94f58b066d56d405b452b3b38c8f51fea981f4a21da56426c1cbd71cba4dcf362e432481e4bcd317b5bd0a67db67b0a559dcc252b459ea1847dced7c8c8b153e3abe034f70e2b29f708783ac9c8dc073240f49a7609662801c9f89c48d55772ba037be420b47a473329e21e033021ae4da13729695a7bb563f6c277ced9bc63e305ea3bc2b1f9c0e7227099230376bbad2091e51edec9e4dbf0d2854f8e75aaadf13a5adb51a95bc19cf53f1d13716084390d1e765f3d4b296079700bfd7647086e9e01d4e35223a303b9a10fc061962bb29667f31261e7d6d00ab8b044ca292b91ddedfbe6877d260096660e23c6b8c41e883763b636c71d787c019b3eee8665c401178af18a6403dcba1c8b6fdbeec3a0fee6b2878bc03f30a82ea62d2cd60c46524e445205c68cce4a49f145e2aea07be1e7e38e41ccf7e091cddc2ab0f708b9d210c8bfc2af7b4d9b7ab67205b53667c8e7f9274e2eaa3c5e4cff1b6d637b802f189ab81b1528f912e0f398a6e9b6dcfbf2cf968822d95a69b1bb98f6bec02f8b2d4dd4e5ea78dd4648d4e9835c2c5d6ea6e57a633033fef44eb478a056a485c7523348583bfc549673c39768c9e0c51649f6792be29a213655f54ee2df44610776c42223ebf6eccc4fa6eb7f122fab17d82b85941859e556ec947e5af5ed293382485a5dd2e8fe0e34edb3e6a0804e92f23a4bf9892102abd65310a2d65b1fb7147d5a29155deae468fb8e470296c6a9c55f04b2dc6d42ebf8d65e6ceafbb42cdb526f7b1af844b7185234b687b6fdb64a530c5d63b60b062db1a9b2445b6d424f563f784e0ec58c70ccfc6c6ce04c70d2d7e48767ce7ac6989679918446171e50791dcaa69a6125c580e5dcc4ca1bce6eda84dc3539c95f658c37075b16f6843b3f5d306cbeacd5ee5c14906f58afd327cde5c43d5cbf7530ac0d510b206ff85da670d5ccd1b4c40365287bd48fd8ea07d33241f7da4b68fb0b4a4c55f481f24118143c7a52c5ffb219cfdab489303321cd0dda1232eea3ad777538a4a7ceefe8b01e7e10681d51bbb283ced841ed0d0df633d40fb174de1397c9b193c0e638828e82e2c9a20f59329ca4287d61feddd39fc3e476e4d5800cf9fe1f2ec61ee4313409af24d344c38c57c74b3ca3c0d0dab0ae4e8b91c5266605797626ebad7ab3238d4a7411d554c2c233d6c96f0cd15fff9c548cb6d38a7633359294a4c2d203297e0fe8dbb2392385632e915ae5d230d9eaa57ae5ab014b4519fa4aaa953795f9385ff9459b05b7f88f98cce6821e3532f27edf388d9d98fceb5b92c3276c4cd6567375a29dabb52a75da242b7a314809d267959b263df1e6f373b9c77d8861c95569c9e9797aa02b7883e0027637e1051995b20aa1e33db0bfa6a0c679088da269cde95258d64874164f02fe2a716bc6805bb2b5ae2e85f77508f28d09e5a493fc7c4b4ff811d0b205399221532c678a4fa0b95df74ac3c736a31654306a9dcb4e9465a15adc26c57442449a8c83319efdecf6d74592421c0860b68aaf60728912ece1ab8daa507e32caa501484fed83c9a5689c2de2ec0af973caf23d907b403a64b6b1f3c21c2e12fb07afb69b89aa6b81718dd20eb03e086e3cd27a9b64ae9b033b693fc4e1592387b1aaf070269e2f716cc694246582a7a7056c2ff46985a397770620a0b33c81ca8c908f889cefde81ab7830d7162800443649c13961076ba66bf3ceb306c45cd5848f8b6bcb6495f7013fd3e84baad8350d369f53c105739851238a9f0c3bf7e03cde451595445093100e63c376aff60a216a5d51c43ec80bf1fce647237a7f22bf19c83199388be072c3982af0259066fe01afaff41a285f6d990bf365f5546d455ef7ee8aa0c71fc95033068d0fb35e36278a00701525151b92729f20716ebca2a2a91aa73b8b3d7cf1095874df4b74ecbbc19523818c603877fec492228aaabcb49cb0c5f8256267b76647002d288a0d23591f620067e987c241bcd08ce276a073e21b3acf61331edbec6bd129c2e7ea3df45be5808109dde59d1baa6456912390779193ef5177f0ff4e977552ad603758a1373dd6b5094a626b55de0f336a058e390abd183a49cca8a965afc770cd8279be614b1ab9d1e3cf458490030411c2e1f2f04edaf1ff1a33d3e08961988b2ce49c2b0636a076cec93e6b2c90520cdedac14c4a87443ac5cdb189a42223ee196d21483486df2a64820c35ebd7b680b1b7f6a3d5dbb5756a7ae7d79a82ff82ff440b4ca9afcc1ed44dab63bffc93a3ec0d87697d2290374b472749fc19a42ea1db1dabfc41cd085fe6d52a1a557e8ba08e9d01c7db50515c2a32e3c5891a7ddae9223edb98edaedaa01915330d2b84e2e27a2b90ab1098c149a81b60b522e094923e158d9fee0f9b099a281d6ab3e9445ef4f4fd062624736cc9e620d256743c62c0d387c348c2fb60453d74f1a8a6164cc68b81077cb0d29d4d2846ae974afff20a6eb3df9e464f66c47b4bea18301c2eac14adcd9bc56eb6a12c7b6bf45c0acffab7a7b1fe70d69f6ae5093143d7dd900ca021ae1c97700ffd1b16617b151e8fcb7ed40af480580c9cb4a4c92c69b383cb88edf652feebd84bcf52b30df8dc58f485d0e74a8b2fc908bb69f2271a9412c9077331f537765b857be7d6ff48e0601b03755eaf7e5d2434077215131d55aa3a361ced31dd9f7d055e6cd589a5a48a2d43b0121f94b6a8bcb8353526b8c16ad83f7501a2df9bb237f25fd05bd8449b295775fbb8b6abe34f0ea66038f385c2451ea912db50134c77dd904a41ab0c54c1173434742c690ed384ad073ddc05144870c08ce31fdfb5b86ac5fee2f9e49a276613da33d682a862fa272236ec96c36015446e356d7adf5fdc197d757b7d7258eac95d90481973785b6471ac802f726e17dc824a5860dfd60f98f253aa6bc8376c71bbf88e14fcff5f1b0f473a03229308410fec36fd004946a8b6808219e95c75fc36e9dea6cc242d4d2f076e3c17bb0e7602a8012afe9450778acec6ecbe39d865b8e73ae85f9f3c448e0d7d5ca2bba4fadaed67cba29b985e22a2442daad690380f751dadc931ac9285a6054fd05e01a8ed801b0db90749644673383fb808a8adbdda51000bb08c522fc5768e8566b4280b0408c36d9a6d12ef4bc0a31095babf53c64793a3b794eac375708c3769ca46786bcc2d90d8b9e2f8dfe18453cd24f1a495fab30be5130fa2e4a5b1d22256f43176c4a58028ff12eaeec070eb35abc8cbb5beea7a0d3e390864b13ccf18d74debecb0f12e4db6b1b2608da8bcf3c4a6c85eef8db244b691d5732cf20f0d8aab1f5b8aedbf4777b4f4801a491f40ac7f5735ad60c42689138cb2d648ca60fd67548933c869d5f640aa29fc533c928911e99d375fd8a7ebe28309376500c48970cd28c020c7f3a338fe889e1766b276c6d175e090263edb794ac371a1a22b9adda656a7d3e3712cf20dd1413999b0ac37f0ffd3227c40d136a78c74592ad3d91e89becd842ab08a3d0b16d0ea230c5994ac0bc829477fc6e8891778006c50533b83a68542d30c36aa914408f9190ee72b73379b1ee6cdb9d8db26cb50233137ab3cbaacd0736f5ac3995804fe4949bc0dad0e1108f96f39c07e1b63081908ebbf9576235bfddb489460bfb94217d09d176947e8d50dbf4dca0fff975254a3b275b297b6930b2d04d9115e6865208cf1097a0cd1c41df0f8ab22434398a976fe8872c37d4171202fdd969cd66959ff487001c41dc62dbabef24b40a14e733f824bd4bc401cf0961aee662ce2c08e7e71dde9da5e7fcb0464ecd7c7b652403d712bb9200dc6c4b9dc80308b91101febeeb9d0ee049af0670d0205307a09d386e1016ff3e0247e563addfe562bf8f1b589a605cc713459c887111c1a6bded18b0a33eb3db58dec051796312261028fd66af69e9dfb822439c9a0274162d61a1a4c72cf0b2208404114838055e53ff0fdb86856d3c6e4a28365056c9d28b19ae8fcdbad74e631bfe053aad406170f9692583f09452b3f298dc7cf2158b4429cedcf38619e620dd60723a056213103904f39505fe4b956ac0e7e4b4ce3735d2db55e2400d27d5201fd2bc24cf45d2bd8878a0838383450e6d2826565c180a2cfc76cce131ca61a05bbb11fbdcb8bbd67e40d668ad90fd0a11ac827a5d07cfa0555d2b59b8f2f2b4f90e833ff119da56c87108581c2d9b5a7a03c70c39e1005f18f72845be647e7cb63c6bdd8d58bced3ed7df99f355492a32ebe824ffe90b33e8bf8eea189c64038f7870ef9bd1aa9fee79a43dfb806d11e55e24610dd3e0a635a2d039fc55e449d1b62f22c1662f619891f0ab0ea2ff76653c17985fd1b25881be516a66449f1832ee84562b63bf104f8d327b1e56bab6d6e14ee347a64b5bdcb23cf1695d7c85ea181e875c570ab971cdf2c80a15fb49758caf03da703063f19abdb6ce6e4356fd1c131efc9b2cf1d3dedf968608391ced7b96ee6d071c6a8e38ae76cafc922c3dd99b7eb5c25da32c8892435d119e8533fc3bf65ebd02015b7013b82996ec98d18793a6c43625f0ff004762ab4df9c04e83b4768ba6df27a8b407f5149907819b5344f0e4a76c433ef88880293866ff353ff4f4b5f35789694ea52008808e815026fa225f0cd3636f65846fed129935f410b495045dedc4e2a706cdc292203e0e4188a27690bea5f86f22b7b5a857cee935cafb9dd50ab51b32c6ab96a104271a12afacbd795976718602098b257abaed5d77d1eef63650f2db3e2f815b83a359f31a74486571335467c4640d5257d731515bb0ded783e589fb8559e3d7f17f455115860d679930fd5984411c39c2df51e320acfb8c2b8249f0f80501558bc9d97e4edc268de6f98b60905f169ead6517061521999f68d1e38c251ebca319e222f8e48f084eacc6ce695f847925a83d2db6b75ac221d6e092e7338a703e66f5ec3a3a4930e1ec40a7805b167b1256af10622fcf44b11dd191f4f145ec0ac5fec7c99845beed924138ae664b56fe112ca3ff0a2212a043651ad8b579f32710de1f11856dfc12c6be0f817fc81a0e9e22ebc75806103faf3bc26ab1854291ba3ac0649a8aadb04452e18456af98617a3fd3538f315e060fd875bef2fd0f58f8c2bc0d134e14f71637cf880c955edad8e8b611258b9d0895cf2304e46380b28b1c598043e82dd57e44ad06bc617f78ad02b205c9a0d292e0ccb903a834989365267713e54690647ea678a0e30f8545f43420a700b965204ef96330855644eee547351a9153ddae1175c5c3a49615261002a89a3c7d28061d6870254bbfcf8c4d3f232c3b458bbdf452b4ee34f22b3848515cbb8621ae96977019660a2a566dd107b2e69d8f88b8b63a3a3db4c384833cb5dec81772ed77ad64ba5d2f574c2cd0f344976ed58deba32a0351fd15b151547da3ee939f239e01c7243e77da80e84b77ec27a9ade9e837aece030d51662288da86781348da2cf9e4d91fb1cf94d77fa9d82927fd506624718b8cb4168d64f1be9d785abca0fae1fe967e8bece4a59e416fe8fcb06e2a6bf560d9025878d4ec80b8a1a000f3460ca67d4d0f389276b674c70b4222c1c22c5bfbde3e51faf73e1d4e1457d17d911bd02e2e9d96571a739353a1a39d22247711eb86f688155a2726035b1db07adfebdec94af9755e8a48273d18c61d81e6f03afe27cf96adcada8b8e2422475af05eacefd52e17a3dda8c655a41d9b8300f1e6f94d59a743ec7ba5245af0517981f8d9e6f8baf8b86a0c82fbff35b84fc993861b390bdae49f3c4c73768f4106be0437f20d00e5b95a3cf611821aafbafa8c73e48c18a7012c4d20f6cbe87e71f5c6fab6e44556e79f75b643915068073f7a1d94604f7226ad60633ed37bd4ab16e4415a7b80d7903b448f9031760807a180d55a7f1854579b5272b11957aaf1612d02750c1b0ed4a59f6a0260da01bdc294e39416814845ccda753801cba9e02968325471d550cc2b2ef8c1fa0fc8700126355ab49b98e12c2b160ad383e59a00692599dc3a35e8b9b4467ad4f78591a0b8fae470077f55f1cc14fa24a682c61762b12d9f60de3c89110c2408f3a83c4a2a9c87d0f3f111a71d5a3c35fcd49da62b08414d6d34a28878cb2b1a625dae9d88dbb12b6385b1ce48e25584b561963cc230d98d1c2b6e31505279a1f0bf4dd9f44a5b50108abce5b4903110cb5afcd61c89c91f2ac4be423f7de443819a9b38cef2b501ad2a42123a943856ca3e66f327b4030242bd9639c4901443a99f54c2c08d768bd4bf755ebeca478b2aeae56c936ac081d81c97b3081a4a48d49978870f31f7f774a63f0102348cd06621a8465bc756d11f7edafaee277d33e7f2c35ac543f025e1db35138430a298f796cb30bf208294ef18e18ed8286233e477bd1e21438a8088e5b88fc664bdfefad96158f5f8db305babc207f361aa787711edcd0040de5d2b60d38652981e2f1346fcb1af0412113ce3b1340489010c7f373f1b4218e146858c017ebd9305f3524de2fcaeccdaa814c7dc88b7202fcdf6aa417745bc1ed88fe3e2bbbffdfeae08fc3196641dbc86d76238e96d608cc0444359d7de34765505d899cc0d538bf10ebcdeb8d945abe5a1ce710b676394190b80a76b7f6aa56ba74f2a7c86a6157ce8890a5dfbe0a62ae28ae7347f0bd44d3b67461b7d85a6739215ee65c9f304f1d64d34baed53a40b135f1080c599e30fd504f462631e69f743e766f7a8514ded78feabb6ca781789b2eb83ebe056286d212175c9e08c0a5f1991b14825c501f3e7b8b59e242a11768ce43902711722b383c7e61e150d4eb1dc3d7b99dd14b1565b2f98f16e59e3f6d3506d1ae3c1e4f6b310da49462744bf307a6b1d361ab45835316aa96ba83fe8e2e5d5e2d0b836b396ea62de9b5f83e1f76cc1fb7adc9c30983ee0fed148a50489cbd1435bb0d69d2d8ee98754690fdec4ad02506ebc30f255bb96c281e1dad74ed2a0ac3a477f597d961b2c7bd0627a42fc11d00d04e5b59c950ca94d00b64bc0945fb3dc5e58241b4c3c13b846b61424c6c86c751c55749fbc48a2545b6375a0b09f5ad8927359de381f0c4b1294a50d8cc73ba487915775640afe2c3cc45a7f060cb1ae44b2931ac78a2e5539b3ba79184d775a5fae5df3d433e32c312df49171bb3c7ff1206a515562446b7a5a0b399d22247ef327a5487927fccb78aee4d40fa26f7c0757bccb233d8e2af61d588da5a51f5a00f32ca59af193844e11787f86d1fc968412c1af469bc8cccbfd775580dd646523d2fb63ba43e608908e372216bac21f61bf3a359da80ccc9fb3689d5fb058f793d2bbde05e6f1339677b48a606868818db2ed04fea2c00fc730f03586e116eba4642c404df12f6a09879ecd5d620c7af27ddd8e5b72a3790c48239ab4155e147d0c26085b1068686cf17b89e8f62156670e25f1d1b0234a2c8c2dd532606c3f2b8940066deadf66f56a922b8c9cf60da212194af4316cd4536a0de98198bfc9388cc1b7c5cbdeda337e1ba003451175a7b390a11d9e4040894ad34eadf3117b6b3861d7ce0de1be0a2404a40f7de0cad44410e0f892868a392ec0090cfa482ffe87de1dba5402550fa24fb045ed2efbc9461e05b52ae48b35f65185464cf6b462e063f4f2c6063dffeb4848b50b52a0a14105f89f0aad3dac2c35466ca7af93e8723ffdf0271dfc73506cdfdd8320a28137221b7fcd608d32ac486c2909c1ac125b05ae3f11281019adde1c1329cb3df59849f502c88f1bf3b9dbe218cad2299ee2b5cdb14715068dd52809b001e98740673798fcbb4569e6b557a9f6daf4cd90b4b55f6e151cc2fd4b9646e97136b108fde318263696076ee49074466211ef7c49c13ae075e9d83845ead3cc005304d81fb57cdfe489f2594235f94478622c8bf8383ff6074a8daa64024811500fc192f8c1a925a5db63022e2821d52dc8c53e41c5134433693b7f67e0429f6d0a93422a684846bfe3e797c3dab31cbd0e6dbe76f6788711d335c3bdc070c8ad0cb4ab20c22102d82989e9baed187e4e21df4b23ad043d91383869311668ebba68721586a097fa504823f94abf8bee2c60787b4e58ed29486b2290f005ea5b32ab0df41b648a4b8a214457f8d006b68db587d26a474d55acfe7bc8bb9a795e2c101aa468001dccf1271148b7818fd7728108d3e94ab8d42ce7ea4e38e1e8429e985c08a5b5e2112f514f35ba2f6efcbc52b672a1a6a1d8bb31a3353bbd89267469059d30207dde74a498105c20be18992bc0304053b1c45aecb1e82d652fd88a57e6d875f16ef84f3e627ec84d1179fa9cf46e212ecb9728c4938b398fa82b1517b33f1940bb31e13ee69adc6d9b1ac1c4de8c40caeb788364dc5bdef3805cb6e2ceef695627b21b443c45613a8bb2f2ed5a0f2efbfc2aa0d428179a308318c8c7e5daabbcc30e7849bfcc11c8db56ea1583f0de3d322cba806c9249c5542b2c9877ede25c23475ab5727d3543e7cb7a2037b9356ac95ce4e7ba518385a5bb1a752e9abc41d73b3f36286c2f0599d3e9f23895ad4c5c4b442811bb430c470e634d0751a5c5eb0b4eba30f482f35b67acb748c7a7df179c043f661a2a1531e30fa6a7ce50f9c2162097b0b3d168abbec56b36d2889ab4a8af033097c9aa15182f3f3baf6508bb669e14dc0fc26055f4699522b318cb6fed27025105446dd3c9e62d3d796934d0fca083ef3e54123992cd5be88cb6aa4acb41ef6c20e394e220d368258ebcac7cde12b2a3231441db814d82110e9a078cd9c3afa8e7cb06bbecff84ddaba5e21d0a20f66938dbe31edf9d6f0b28b8bb04167eca3b2ffe703a230e8ddbd4bcbe576903cb061a907e2d01feb3220be336bf9640965e64a2d71ab29ed35b0bbd0db739a0c3a686f44b2d3c4538836f9bd32c4921b8867036bf9e0df731b46458c0b24100ab9daba55026c39d8ea78eab8951f7248d77b119191f91efa5d7b698de7e209089d1b452f538762a8b6936a7da646460e43d49d78424a3848790bb9c2b9bf641b1dfcdd9ab695301333bec634e610165af8860cff21e80f301cc0053cb26351fb4160aca6e99fc9c1717d635396a8052fa97029219c6570642c519ca55d40883f1a49b1b94e42d44da967bd7983474dae43a0d9c106ab6957f7bd7e98f919cd87e29f46b4394ae40724246f783dde38bf5b286fdaeca31981fc047567e7f6e353833e548ba1ee51ecb2cc6f575737568f203bbd412c8642d36a14c10347b3843afe9079b67adf8bfbd888633c531ce524aa8c423e4c106a287a733c2dc49892493e29a394ca38c289136c34fc730d6400d66e56f5befb615a3cf83910b1a35f704e7aa114a843f0bcd23fad81276504b8f31298046e11e91332dca33890239c82d924439f258e00e3f4e621b6ea4a645fa1c451a0383913cf7b9f070861a92b4b935b6a513803dd7e65ccb44ae88611f7af66ec68ce1afc575f9241afb39943550afd4e8cb67ca4e6c5cecb86de427b290579a207cc7d8252b27062dffab384ba338d4cd6fc2573f02d4a0421a51ed218b19f2f0fe3d81eb819f81b4c6b735a31edbc59ebe5eba4b081cd151ea943052f72e036b76b07a70455ea383f291b6904f1f9eab6c793b645c73635108abf378a9571fa6a505de763ec58f9195c4264ba895c09c3be0bee4644b65b029d48f3c48c16859f04251fd2bc24d5d83fede56fb05d8ea311abfb620e56b13fba59fef5f22c98189d3a94cbed22b0bf73396d9ed71a986d07bb75e56f6eb4955ac218b1243c205ddc5b6085da66fec5fbc41953b27b7b151e7317c3e6f9fa9006f0f2f89cb53092486577244abfa99065e705a39e4ae5e2f9182626c9c67fb0c3c17870e6a3a9514a789e90dfdf977865d22ce65218a910214de41a7894766e9d41b80aed92573c0c12e1da0fd1592a78bcf60f9f4fb0c5fa1cdfbc792f78f9714ce6cdd692c76e05684158b827c2fa26f03a9dcace322b61a76180d8e91063a8807731bf46b5a4f05db19373b88d85e5ca06893277c57bf2c1d781aadafe2256b599f2103d19a9ebc7c75c42aba47e25e31883f138f7e5b214a3bd32ac84c41ea1c60dd43cf2aa7402729e7f6cd68bbd47e26426a839c8aac6faef6552267d0d7894bf5e95618a6d7bdffe5c5ede31515e1b4ed325f5f1550f8886138ca405ef357bdc79bc0115462404d615762d78913c8f9292bc007c9f9abde4ff78a6cb6f0d2640ad4407602afd3825f50af05fa65db34e8c8ebe1460f776ec01e512e4b64f62b81b3367c1572b7489416368859071baafbce97d325940304d6f58029294e354f9e03f4d03a42e4cd0e30b88c61d9399fa25f076f9bf896f4733a986d1d9a63d17e26705f1a681a36bf5b9452383fa018bc48d01a3f9d8344204c989ad191c1b6ab65993d57ffca0e9e931f34883821825e8378a74e824e48ac94607602f5f2659d35d41682c275c861afa189b898101bec6e1470a82a16ace3c95a03e878d8b76c6dad9578668fea2d0952778403f082a683c5df519c0a2580eb9dde228e0284cf84c2936d3777395d49a13dc50d38bacacdf6cf2003d309196c92c17983a179777451abc874331d342e02c115ea9147a565968cd52749918105793517e9ca35af49a18626843b6f1a63fb087e3b4133f55cf2ff79ad6b113f40e04420aa1cdc447c6b3700b6041acbc2a17fa81712445a07f070db66f4edb4408a8e499fa7cd821908bab367cdbe5f235e3023c588d872f3f415ffe0c461d2faec735a339303c63e74f57088cb89775dee600eaf9a53e6c9608dc9a8467d8bb5b4eebd69c2ed440927c45337184dbc906963f627a13f13185898faf4443dd558ba30fdcdc4a6144333206c945d7db4d5d1460596c31b2c00e435d6326e95e239394fd8e9bf411cdadcf6f9899195b0898ae37cc25720ad74042788885fd66da309c6b13a0d73bc0678f0afc6b0a71fd7414e880da125f5158d23fb53567db4a42141a121e7b2326c50d3ab3ba34bcd2032ebd41237c056575d26c85c426a8ffb575054ea3173f1c6833fe8582a10a269bbaca3d082ca1a52e5553c871f0f82522deb356c416e88538dad8e1b4b912c176413573507db238d1a725533db557c2285d9304e2b7d19ff4c4d24fdb48cbd9b6f19d9194ce5c3424431c0fdd583ab25305ae6cf6b25811edf0f01f7224df67873b293930dade3e3672055224dd0f9ed455cfdaf041268c8f49a7beb51de0aff27365b215a241d71f41174257eeaafa0a68beab5a9ddab171a9db0721ac17aaa90242b6943c394feda96730f8f85312d12c27a7a88e8049486f162b3ce5b02111db0fe360ca416d27796f25d9d2e78ed1e5ebbc0a20b4083120865fe96daf9125256bcc530a291541fe87c72eab57e15c9643d2a61e4db5cebaca8f52f9a85a0c78a40ecafd1526a927f18039af82075cf1bc0bba98f7a00402c1b803f4c574f6d40db76cee91506d0da21402298b2bef6187aee3b85f7e153386580b82914a4b3c69cecdbf1a108c70174d933c9db0f54bdf8f546b514fafbf0512de6d20d780a002bda80a5726055bf8a28603219d862fd92910297e4ed0227a3437e42ae3f78e8e6e22345dcd87ea404d0e29f6818bc82dd1da4109e030465210fca758bf462a474af16fab51128c5cdd66808b9e038eed7cc7ef9956a00bb082b1c4685bd314aa0ba2492cbd7d10bea7099d18d1d6a377bbf1ea4db13656f600611c12a84cbbed41d571aeb9934036ed40536fcf813125b2ebd953e25bbd2a45cb7a24ee5f6622e8ab37465619bd178175df9b7c95ac64367378c3cd0140069f1a771f53c1aee04e1f055038c566b91b95c7ca22655c860f06904f68b7e18261c129e2dc1066f3675361e66e7d2ab46b72a4c2a377378da04199aff21e80ab70b35db6770582ba46656d2137222a01a683821b72e589c8aa7f49217a74c628dd2017a48bdf83b2f3462487b2606f2f47b1eadd7abaa23616f5adebf77a39d3d323d14938ee88ceaeae23e1b4c9b3e3ce1f472632f5b5f39d76c946a67980a135458b3ccb1c9509dad37b522981d514b41b4c131642b50067fcc118d524317d9e9bb442ce396a86e5b428b6a40b0e6e9e857065be7c103a77ddd2532889c1354383e1f9c160afe5590bf756109964587b7c9f3d92666eb9783551511848096b8d8fd30f17c9642c58cee5c0d6de55c522906e7c45d0939454bb44fbb47348db92fb76d51b2e1d7b32abbbea0d179d49ee3913f8165dd8456d2ba61a5000ca6596b3b9f3723523e0a7d82bfd26912cdcd8870dafa7bdf5b480290a8a735d157012a965415686d529745418d21602241402b778291d72f7fc183896d77ee248d3fb8b61648c9c9a8e19492e106ae93b0cfc4306970a53bdf9059f151b3001eea9ef41e1eb894adaaf54a13bc734fa0e6f6112685be9f080717db0122d99faa1fe97e05fc1c57e6cc110280d9ab0dbd8ca86243f9fbcd6dc56ad9e77625902dcbfd7b5a172cda669cae253cbb95c3c97c18ec3584f70c4491f6d34a2c55bca6d25d7c99801f94e7661c0a03320031466d9e9adba4e1981b7261dd844d109016712db57388c1d35d1b999928634f84d5ca611cac5e53d2e2039b2d2c38a0d3ca438a9502964a21ed1ea5ed8c044b3e022a4aa02858f29d365559f77471675ef0755b25c5bf04c2f9f3b1f39d920e1b498a3f92020833c2755565baf15e96b2b266a118d40b39653c63033d31ba1db30d33b5f2e9237b2156a46bbb2c74619f06619a53ea93bd56ff1fe58669fe7511c7a990ecc1a5ed2bc9e6d45f87e5196d40abc44772e37ff7f101988e8a9ebb9ad25949d694f124c02153cc6970290d864c9fc72aeff8e522298c4cd489ce25e2789c1921c1422806bb805c617c8981f9af5fe0e4e9b95e6e60fa69beb19c5fca08a0bea187eed957d3893e77b59a91fc97d7290f6c23b4e17520f9430e3a898c7259cf76caa23d06ccf16d36e0c7900dcf3f1c0c6916d425e20f7bba9b45a1d09dd87e58bb85a621ba8d74406a767ab59f97334fb7bcfe113e966f57ea87b90f6ee7bf4ab3660f2436c764f87e32bd6a4a5f978fc6e9ca350260a4c1d4d1dcbcc7c059ff8de670c2a5faf08f5cea1d9ad4f931ad45fceaab8ca38719bf8ae152e302a7e03c453bb3338d02c226c1776fbe8e696def33cc9a8c9000c6b680a5d20055aeb5f01948480f2206be62f668247ddf0c7424ca4916f324aeea057e551b0629edc70cbb75059d714fa3f5d707c4602e27bae0361062da7c6e503e62ff3c30b0d6ea39534d6c23d2f89dc49d6488392305f499865b2e861f20775641888c38bac092a1338f422c4e159c1e1473ff24bd214cd639a5fbf5823bbf3537745755101814a4cde63c0db6511d4390fce353cfa12700aeef529f39da47d3d78b77d7ffc08871e5061d166580596586a33022568e0a95cf7736f611b39b315357615d4a3f6f86211409cb0cc64ffb35760123cffa28be2fd724596afd020fd0606d159ef9d5720fefe5273afe00cc8b7ed5865a5d488980d67ef2092d5bf331f2fe24071b68d51254e57c1881dbef8b4bcddfb4553081196965fffc1b05b39562a191b41e6ad1551f9afb575c21efbe8616e516d9092005a6084a09459996d80416e9ceed1877dec58fb910045aa73407dda5e58104366d3712aa903d766c352b84e78676f30cbfd99547d47ca08c2c8728e5f2ae3df8a8312186375af03aa99dba2af73c21f87ad675dd8798a7c997a65a3ae958d01b217cc4b98bb08a3abbcb77237092036d9bc43092367d4d0aaa905cc39e28e11a72a5045123d211ca54d98e669240dff4883fa2de45daf38c752977fa811c1b55609c8309b3550a367079c1f6fc8ed12bbe2dc4c9553470a59d54d323420762f84022764669e2bc16e67d0bfbf723c270ade8cbae829256ae252aae31387d096eeb18989067ff40ecf6d9be0342960df95780ef81a84951b907fcdbd61bb4d88492c590411462e5e1639a5fa251c03212ab94e4778ebcd48ad5df206b2fb4fc4033833b9034bd6ead0673578d551691020256fac43fb95aa6ae497134ca08e7d3518057f3261387cbddbc1bbffe402c25564a7f2af46154124ac783318364a6f22f293e3a82528bf3c284babc3d064f142758ea9d291de32f62ce0008ade2de0a150a61c86b011430a144588b7ddee8ac29f9fc767a89615593c7cef9a3e02db71efb96210ffc816057bdfab5fd9edfed807ffb3cd84a99ed564ecd5acbf159a7662b9e83f401c9489b65f88d28c97b3be308c3119a80eb23877cf281eb11a6be5e765cefa15ac4bcc8dacf47d8e483877609393d19f95c9a51932adea06a1ae4cb3c0ca4867dcd8dc487acf0cc0296852ec641da9d7c90adc81ef02981c82e33da89718949f34877cbe0787bd80b8a88a08b0d6c8e100a911a79aabc524e257890db7cb927f4305ab2683ec9d378019880afa570ae15f6a52e8e170567c9a91092fbc294a7bf71f5278c5f99b46b78713fcf3b9810f421acdb2b014059e9cc5627e96660bb8ceb2dd172f412e4f6c614bbded915c71ce29d7d6eea3da7cd166caf4ce413a5f66002edf6b1d496bde3823c26117940b9bf5f4949d6ec4df19310a57bc43a6ca03b020a28f1bebdeb251d164edd02169a57247af1122ca37431883469aa901e50aee0b69745fa977c45a9477c1f45acba96be8bf42a68e4a48ac6ea275dc87d5647e2e4bb616ba6f4586aa3a8fb3deaa7b8f082cc90e104a1611654166541f14826d253082135ead870a659606b596b8adf85934b0b079d1e7a348209359f8d168b15d0ef918bb3dafe822f91a9131008bf9bc199f3a422af1388d5291f23219001a3f3d5c5e109fc8d4ad62a06c63db3ec3d1f1c59bd61ca52b8e4f8c4840570a2e9e26665b3d8541ce32b34a5b12d9c425857fa1bec75d78721537a4ef452ddc253b79c087a28c5bf338978dc89be8f76cd64a252ef0e7d46944b1d89efa1889ca5c81d67d660f50610fbe71ad304d29f313fdfd898071a4e0562cc3b3a65c72a4341a59bfd92fe192d481032e53482eff4d94565562c8f75638a1ba60594a8156699ae300377c18afbfb4f302ea6b3963704a0410b565f7c971d249255883432743e5001523f663eed6660591eaf966200fc61e1b87911d4c7522d50ec5ec51c15578cf50f497b113e1da18444d50dc7e723f0363c352ed6ca3406f959e0f064827e72f9c80f923804a3555826927e11609a0eb522669404843073b098ae0603a2041c38a2f4214289217cef2c63ae05beaa00c4b77f7620750c01f1003331775706336f448ad32d03974ea0ed155c22e2c0b50284ac48930d58be043c9628b8faf8661d782ea9437cf7dc50820019faa5d8c33e845ac65b064d007cdf0aef93ccf25126d3827b7c60497cbfaccd4530e981976044008879c184324b2281f396bea0b15df5aef0a80ab4a74398e3110515c98a8de0caba4a43eb33607c9a1bd0776134292c039741b8e986de6cb8a66aac1ae7bb9472fd97d61c6d8b84070b1ff7096d4fdc52e66d17ea857ec064a1b77821eaba5c7cf25dfe8dd9188c7a70f327a4386953898c50515c5e07b0c8d6e20e0c40e749b414ca0dc4d78f302122a0d60d41c0d7fca4c5f851b06e55b183f78e629b3fd93c1677a707fec77251ee896bc52d6e13432c95e54ac7eedab5a6d4f4bf009343e6a283a80128dc03b60d32c47e69ffbe15c3cf1539a46079ebd954b9b07ca34564f3051b25957b3fd9c146a43835ec85a59bdbece417978db00e2d86ecd767eb9db491a3f74740fa159f871790ca84094bfe32f747885cd831b693d2e63e75e963254a313fd01e86d5d29436397bf7d69a7825a8084a5d17d7fa6f130b642e0e61f86b346e15fcbdc868f4409d046959b16662eb78ad806784ba3bd0f467acc909bef00a7acd467eadebe651ced6543c8d98538731381fd0a23cf0ee97170830e471309294933439d001954d21c72ce443210703b0805e073ab3bba107a02a78b87e77fc3c2384222163c35a4c46582b2448b3c808c48c862f1a2b4ccec42968704a2a951d320da2c846537b0fa1b5d854da779109d7dff4dc555ae775322a750112438bc976a3628945153369e17c1264478f227c0020a773c6437c7999e6764911aa5d8527567ff4cbdf0143e23842f5ad3c06050cd6c4de29450a9f2e0c9d248992872fcc9ed18dff47fc32527d0b6fe889e000d54e71ce5cee2188dbf37fc98f2e3d34873d09982e4011564bac12adb29f0e5790b47d2812800808aeb89821365525dcbf8bab4ff7737a89f634a93bc80acda41e358e611a024f02399520cdcd11d4486c0908e134c2f3c7c18f93a5bda9d6c5e54118598f93131d9bf4631b0cc424b11f043741ed6204a8c51808115b9e4b4dee2d50a31608f1414c3fc4de83a5c7f1687cea2eba3b92782603bfa73a54bc78fdcc752537c60b0f5f1fbfb4e05140588636e9918dad3f48d75ce931a02f39d4fe0b39a7d0eaa3e224ef825cb7d94d25380e1fb87fc36c760f3f6e6b62e2d52d4d1682f8e6684f49df629902262fd646ba2a011e70d14f3ac26fd3767c10709ee101153500b44e422a69cf21b5381c6f4b15f724317092d9fd7af23e6e97258ee7220020fde3f01c465d26bd888663966848540372608437fcb966a8d309a7d75858d131e72f173b6b85f1a27c80a88328a1577b68d201f1a77bd0dc113b061d544978f06dfb4fe840c2d8981148d625c85dd069744b55b65908bc32a4211568bbb20af3525d106d0ac97a0ec3f7f27349087e89dc5ed7d33f3bbfb5711904b085b6cb3e3346e0c901a641874a6de568564285939a19e317c7b4aef66b744d7ab0163911b1e911d7bd2f5ccefd9fb12a298240ef6a837f32714e3709c6e08ca9216c981c9b1a582984dcf0059310c09c55cda92ef77a329a8e86fcf0f744767a6c4616726bab748c5a6fc32f1a05e45a1d84bcd20c2f0275849aa97ad4d3f0451d0ce73a04a45016f760d36643afb9eba6d021ecb6e53b912cbef11d1f590bac099b3e98e8fecf332bc009c7ce15185796d3d2f8acdcbff84a3b11cc89eec9296df8fed6a231a8f41651a673f14540c1190baf6382354de66ea56ab686d7361281573ff327442f7a085429844b800364a662ebf996a6a3b21769b9cfcbaf41806d7cadc138b57b44a2abdc60a731d9c54a6c8323fcf279c2fa13c5c8e8b97896cf3c54be90ed130ec753757511ae6917b772dafe94f521461d63cedba8242e1105f28a261632a30db863d7e29e7835f3b71168cc8405ae199b538d9e3a5d89d16fcb1a87dada4f29fbb52b717efa0930a636924b530aa4958d20941a66ef0564d55ac8f667807bee96e083e2d6bdeff0d73807fb2e08a6df6154fdc05ebf09dd2603a9a387f5d6f0d146676f5274409f10e064f8d88f54a6cfffe2e69b994d5d69757d57c921983d16a7d0806cbef21ebad0b1f139466ad8b0c0cb8a54e53c3080c616b026149cf39c3acc29e759d0fd6b54dd428ccd182614944a9b71adcdfbd3e8a95944f79b1337050feed6ce72dcd4fc6b36bac51a05083d62406d0d4e83041632a9e84d3d0ac4ff393059f7224bf0ad5a4cfcffba0ae9660ae3f350354522aa3394da4ce1383faa6723b8063c93262a593a590d76d00f38c149e69925b9ce9ab15c3b5e88f94a3980050ba82eea1d90ff8530787e72564125ad5d6c9a2347d4d1b1bf6fb712fb96bcf2c20116c762b69449118546829be48b8d980e1e04712d4c90136832f2fc03bb9ace910446bde292d37e0ba2e228b80ab527406c460004db2e108a23374d737147b1cbc22532b8cac84b1bfa9a55ad8cf29fb11e04b18f9834763279aacc000b88650c893678a5d1f4ffd844f7ae79e04a33a1db7b89e7a4cded60883703d4c430ba44c7990f59b5e0e1537afb6caadc2d649b18b19c4b236314192e3ab8c5c62f007341ce35a86d809756859fa14afa6184296f65a4c262b14b9bfef9795860986eee85d52d25ce6ad99cd48be425b91d3b6a18236b5a6b327532a26d2c2d8387a338169fb882cf145b7e8434bcb7d829f22f5e954caa9e86a1d7bf1240feced94f80e00eef078feb402f008ff05ccb20eb1a74bd61089d92b519064ff5c2176cf5be544a2bfa705ba62bafda1c7b1551cb4460bf0cc233e2570b2068cad2dbcf92b112521ee8eec2d9703da3b49fc33fab217ffbe97573271b867b7e6649fcfeab4d359ac0e8b3d37b2eb1bdf4fc38e5a8e7734bc6e282b34fd6d292528a9d92740468988f9a2be8e47a838d538a0cb08b914e8dacb50c9e996e91cd9e63aa1a8df0533a60289ac23c577340e6bb490676a1f1c23fc8ca6f918049f05425f7d0d240f1562f8e05ab891bff85ce6fe67b7f265e1903d2e44d3c9a030cf1b61d62ac6cb6ea29283319b65cd3d1ed99ee9b00fa0bcef7f83076494857a1aabb73d8c8dab2bc97452e2a10a1d158fec1bd1babf8a836dbc0924759b67a1168ddea7ad06b73e05390e8c92b79703e6def143f28365b62e3c1e0416281da2b8f3c9b3adcc2398b0a7a4a75d9617a8fa5a27a5e9123056b1adf2fee36342c98329970c01b0e9271b4012653d4355ee0552f0809c288fc352b8b71bb2b2adfb224fe3c3fee3f6c9c236781fa8f452c1a3da1aa50fd8072aec2af1fc479ead2505224c0e7cdb00cf9712e282d964a6202fb6732c77bc425620b1a36a5f60bdcab24b99a5a453c5882783a1fd8aeaab85865842dd03ee87b3b4b7713b105ea4618ac3563234e2c2d2a249f7bc63e75ab93663b324ebca3775ce4bd3c11e40754d45fa24be8fecf73240d0208a446324c7f5e86c2a00a735a9c8e21dacfa4acc9673f33bdab6cd97cae66492989a4cd1f0a2431e58098515a1068a1bb703539672337e9b7b25ec0747737630c6e2b259967efd4b77d44d2a92621a25865b3900657cab83bd399557bbe6711c0fb0acadeb930f7a42feefb9e21efded7a2329fb1a00fb0288e463824882efc21cbcf93f671aa687e990f23191ce25f9a4d0d5f7eee8292bcd4fc031a1b9cbb0774cd2b8a792bb8d81fb55485873b875183e5f91ebfe9f1de1d8da605fa615e3b372b6d5dda3e70451c252c39d4605fed1b0f3bcd993005a2c398c6d0efb2ffb421cc4424563dfb530c7aef1292627ee24226c6bdfd1ace879a94f379bce15e2bc50071bda35c0beed005b269478aa892aa8f605c5556668fa685f7212ba0523881d261005b245b6301cbb132a45b636bc5c8d50876738073908aef2e92dbe881853e0b9785630679571d900bce84b8fb6459efe8c6d6227a2f5be9b8a0e274e6a57b143f3e52a8a3a1b36916575a00c0a35e238e3ff4bedd51fc1a80c7fb01e11d04d4898ca07fa9d641695cbfc0b7bb94595b05fe9fec182ab259ed771f4027a3a892bef38142c92eff302e5b4a3ac9c1a95d339ccc253766ebb29b6595d32d1190e23cebc5338cf91b4f4dd9c92bcc677d392690519712713cac72f3df2161f4e22e4c153ec8dee26b4918bf30b507460a93cc3d6cad0c40eadf6808ba37e80d93e31904f568edde2130984a123d19d58b59f02915942a54364e8540560fa6d9aa95025d3adcad447d05a09b665f02d8c2e319ed1f66f8b2e2b11392574e8d83405cff9a54b74fd2da9ed6e0497a530078d8d7832c59e37c813f6f4da8da2cabe89ed55647de9f912af9cad9100f4f1321cb856191e57051196aa9b54c0061c834be101a4d700ec16528155c16388913860ded7c62500b6098be0589ae1ceae8de53123dae708cbd9c8a88e96472e009737a1ec6464fcc28fe1983d8abc58f5fdd9353065bdc79ff750beee71b2cd45fdd3251b223a95403726ce4635c5fd20764ebee9e2b7e725afa457513bddae2d8468a907ebe1cddf204c831422bcb07c36d046cc0512e1861adfe67af02477513763bc8bb4de4f2ae3e04a201c9af37c4b8bed3e8200297795510a0f166b4bf301b8020615120a8ea242ee1da6ab60bc6373cb32da51fb3ce051013a7e20c519cf821b7d1c36ca1a0f9854d52f799b0454b5da006bf3b83c0853995887f6443a7e545b4c29495ee0ba8bb50f403ca75b35f61bfc0948c9e033afba4edbeb576322d49035e4cb60d113b40370cc87a35328e1f26f8dc18cd70cfbbe0abf6a81f1b0ec1760e683c2bdfcfa11bd40c2eca814f4b4195819a985203b41fbc98377f1a11fe498e21485ef200af052d2d8b7c40c87c8784e0799bbbdeab5de79328f8803f8d942a24e83e190ea1a0912fc950220ec17daf0d5928d2e8e2de07f2e076f778b1ff6a955f2d1602aa5cc1dfd30c3b70d0a7f74d2826c6792a6e211535bf946a4f50c684ffa188600acf10f00886014d881610f3c6b3df2c8a5a265dd2c45a7ff7482790ad62542af58fb8b339b82fdc171b28269acb4108e886842f7121ae47cc9fc472f4536506478e182201dc3e246496513c05920a3cdf6a8c3b47d794420671e592b93cc258b73e9a5d6901607523af77118d2b7e2294e2d6ab989cc80754359177bff28ea4341483e3b57779c4b80f5a041ff07ce5fcd9bb9a2ae1b624f6aa544e11c5c55f7dd8aaf1321fd9aa92e7640038545b24f0240d7257de4f2f4b45a08cb26eac54fbe52e2a0267256c1be98f6e7a6eaa3eaa5794d21e19b3c6341bab8871dcbaa5ad1b32916f152f7d5baea0c0a9feb50745a268ddd70a3c1f73e4bd7ff1362d55147c2a3926f92a4815db9c9eb1610fd357702d3e74cfa28889a6f3f38caa7962f5670a3fecaf725d0048036cc1b83b6f84aaaf44d5e9cf6ca300d393b03c517c4c08aef9924e26e7d02e9386fcef14c4ae018c91982f1944a7e5127acb6681bf4401d975fc666314642efbb7793b047d45b3dace5cbae92cf46b1e97428653ec23c06f208f1a4ef13cf8aab69e3bbd257e4e27dba671f5e128a4b47c1e876d192209c353f721ce48a55374b98a697036105457ce1e8ff732d5d74b10d1e68235647a86ad785262ecbeac5342b20025229dc4e2735552f37da064f36a573e0913993afbb98938dd4c7473cf71b0384a630c3d4ec85d486b69525095815f4797375f97a045fdefb535c683e5ad08cfe3c47a0b75792b35b6ad427f71e0106e2229a3d83aa9f22ae155bd75f84151f5e24a0c2352dab48a3d577cf0bdeda6d7cb8712e48a1d3e25b677ea3742f15953e39ab5621f869ea1896f6536c472d79b2369e70f98ceff2065cec129d1b389417b2deb536bfb04dcd088d04189d9765182e11e4354930af9a80361d397c6cf92f6d7427f2bff27f8ba94accfaa5e84384e45954d02ddec3a6504ef07a1c45f4268660d2645b258d2734d885acde6e204a8412f7d7fe25a0c3ae57bcaa747e6c8aa9225a71fecde5da8cb98ae17192df28ae7a6db30ba2bb1fa92bc051a1995e8d1c3d98c474af51a6ef010fc9ec1956400d1b6908a5c5dcd5c408d9b3e20807497ca37247f17e1cfdcb7d8e44556b32a898eb0d2782bfbb90d68e41147ba2170536329f531930daaecd543012cc48752177aaec84110dad6809c969a0ab6968e323e658b64538ff43c5af121e4f66399092acf200a25d932ff59a43dcd2da420c5ee72d4f52ec56e1904afdd7a87efe2431635a7ebd7d81ec5cb836eff8d4b22634ebf11d1438f91bee07fb1c30031d8da24c10b1f6b66d48c638d48e67af9887f7e59985698c2c1bfe48ef0281115a153d84c6e21bcff24231662374f7202c00ac6bff57307432222bcde5d86c73597deb83cdfd73bf2f3961308d2f4eb625b46d16b88ab83264a2861f3291a4929636885286d79bf9f45395462169ff6bd53e898f54509b38cf24570c6dd89601a5028cbe528a85639bf8ff0a53995cdbf18bb8fb9d9c9abf96699cdb4e7b067f82b8e34fbb7f095c98ffca8afd10c29b1b217a6d91e8417c1f5fba6725bbe3895db33948763429a69526454262779520cf05ffd78a07fa8a8997174a8d7b2fc34d6b39c6cafed1a6032546b2f1de727b0385932851a133acbf694ec0fc4ec9565958f480b698c81431924924c8075e56a52d00327c80ec2e2193cabcc2810b7bbf0b46e5bf39b1ddf653195a5aefa3ee4081a218e582349bec13c5b46ad910a7d036f0d2c608d9ac8feab9b1ce781818404bfba61f81394d4367bc8bc61157d70ec7d878e63982905418ab0692e4c5f7b3ab76fd588231ed030e1003665fce7c9c2440c421d28381098730341048d10df3dabf079eaa304eace1e3fe012408a8f29a844371f2546350d8787b435ccd2d014aca1c78bc6cbad243829972abe2acdcea7b9a88b50a90f0c9204ca3a3f9b54305e15251d2f4e10ee00105140cae798342b731c20e6cc34bb3318e4204cdc37d1219c52bc53e32e6bf45c1d6ef7bbe8e72ef9c5aedf5f175380a7e0a7b76ee2daf2633ffb1fb5d61a2184904df6de7b932de50e8b09f50879093a76447829c34abad4df71a1b6bdfbee7c3ce553cb49a485c4d6ac23a5dd58e7d27295dfa695d2c26b52db287b55a0c24b4ba11dd5d3c70a1cc147820fcf43e0a5edcdf3f8a95d5611f2d05a92c2c753e9b1d4e381b492f4159e02b9b582f156cddd0ade4a83cd3945c05f9af5a7813e699f4b2d3ed3a8c33c092df3e47aa6b33aad33061fe11a186637994da6042b5ad57ec587a7dae4c7f311ae41a789aad475d94d66b2b48d89dcf86815c12f55871612d5e5f391119c3a0fe91647e95ca93918ef7602eebeb614c00ba73b8fa8575a3ec233e28e9eb1dae384946766867c0821bcbbce92df380d65b80bb84e8b340839c2853cf2e31ae03a2dd2904492a4ddaa77fd94ff68521ef41ed2a4bc89963d1569b187b4ec65d8652d8d8692acb29b0cfcd46828c9aa8ddb0c15a9cd4458c729d650e41ee03a2dd600c59d9556dae2df774cd354a7572c4a7e529cbeea58ad187dadf49556a230abd66318f68a5ea66f6573bc5bbfe396aa53f7fcd6b7ec2c036f33e8cc269c52b1d38b321057a71bcbb4c7caf4ad761bc1cccdac4cdfead22c3be9938267503b49bb89b2963ca8b53a6b8297e9da5bdb6dd2b6acab8defecc79cc8f7d24a3974dc7877658e77a5fc7437b66f46c7d5c48dd79f0faeb1a9e05171b7fee38a7dd837c5ddfa327d9fcdf16e05dee1906dd3759795e9cb402e7cce909c1ed7c791325c2347a8b8f559d7d5707349d769a1869a4bc475b6aa26b95f5a084228d3577e93e9cb3a7c63bfbec9f46415518a76abe227c8ca742389ff1c141fd2b2a712edc4ac4cdfea17fc68eea97832bb595606deca6e3355738d845a276d899f145c637ad73bd98dff7ae2a0be67a18c7d302f2d3ce941b6bdbcbc88ee494d569a0e8fa8eec6777b52f00c2992e12c2cc2dde9687821bd6071fb431851069a961868a8b91de35e2e5b4f0a9e01df96b48e67c4ce69125b13908267f061ded4076ea82dc136431863a4674ae5bb80e82b5031c59cbe7022909997dff42d93d12a2290c9ee00dc0cd856c6ab38d3fa2e0878ed0f190c048ce816f8800b8580b77e93b9eb2e743193e670e640518afde1e6707f78404c11ea6762a445448e4a55ee0cf9012f2f0a50c0010e308001c48861125304f8aa995c3145a0947efa1543692da0ee021953044ae9d4640ca515614a51b9d6cfdc3a2b40a51501b7e4e670b2a29fa1b9d2222287b3b9d4258199cbf67240f44dec6572e9756bfdd3b6573f5dde1a44ad94b224105d19d17d175e5a674d60fa344331c4c1b932af00557eb25d4d32c249dba619c82a98a6699ace9adc80fbc31f62f8128a6086c69a8991f605326603ee0f31fc1c55f0f1509d132287fb0333f363d4f813ac9ad42298a1b9cc7f98b9932581bc3cf19df5f6304d30e7348114dce95369c9b315cfa14e878756470d2d24c12666fa4c8c8c26b1bb5bd6701241f79ecbf4cce384702217c4cb229b9ac47d4575ce654ed932f574df6ff52ad05da7e6e9f5e445b7b0186eaebb035527a33a2250b00ffec13554d00f3815fa1938157a47b121bb56e85486acef3c2fa106d9a5e71f4e08be94b570e9c656b8945a0c844b7dcc39abae4d75cb38c508e1d48c641068157a4a9b5ec80f1f4da2d7c09079c005419dceb52e4ccfd1b44960bfee62fa7152f641698f26d14fed0c593cd38be890517aec331f41cb8e20fc657514015a0d5cc7ac8ecbea704f65ce79aa59f3d59a16a5d6034d7a344ec79a34dd12a71dd1cb534a27ba750ffa48e50fe7b4b07bdcd114fb08d5c9d8200edf11f56a1e47e34c6a524da2f4f5d9da55280d1910da40b67fcb6cd3d849e99be6d1679405f254e8619a690cfd43424fb18bf89029b9747b492e3d1cf2bc30d22dd4693dbd4e4d4e29f54c5804de5c3ae9bbb9f4959a4f85fe69d953a1879ae8a9d09b6893ce3a7f69f31f8fe65e367f63de8b59866c13510ca3306ad679012fe478417331152cb954dd668a1c7ba5300c135d27041704355f313bb56da6c8a5aa3bab88eb54a5aeb345c465ad4d245ae9167a4ba3a7d194de3d7af7281de2d2b7109c10f323ea1555a94af5e9b3e7f24e2f7a2ef0f47f2ef1f44e06b5258863d5dc210f4a2ec24316830484281a31b5a007d388424c13f2a185ea64e4dc290bb91eefd655ef66f77d7242c0fb4ec47bef04702e0e36e03e06dc17c47dcfe43e00dc67d94d64a4b216c6bdf36f9959f408e77c56e0684632f89c17979f2bc3e5e7905cbf1e5f9048111ed2e35ed60ed1c2903dcb86121d7041bc1787bcf7624e6deb89ba392e989979729fd84ddccccc3c396747fdda3dd78e63cd9d5cbb5caf2ebbb3a364a2995a109a967d4b4269c85ae7e160d527cc364e9c7a629ce959b4dbeca94367c85c8c8b111dc62b08974018df45f4e36b8c81a3edb586302699a6254d4c44da857d94c18b57d32d930a215674a4792e289f40d773c13e659f4ea6e863127dbe9160684ca0d893394db2ccc2e920ede3d1324ccbd8c7ebee49e9e3b6acb6a69ad8497ac7b6ce4e770f19dff10238d954dd69c954d9b7d397b6a72aa38c30d65c33b3331dc08b9664d32184104238279c304208219c7114a58c324231c421977a9d46a0915330890b823adc91a7e4cea55eaca9e91ecf051a792ad4df42ec64390423b46f48b432a091e7f26c0bb13513031d136d87ce90bd9f5f0cf509c873a999a7ac8881c5a50e874cf7e1034845f5782a14f55aeb057074700c768e611733a9ed15b9d4331f59e50ae31ecf856b322ac9dd5e1697fa16e3526f0875f7843c15eaa3e995ad4ebde7ac5665555cc08943aa1d911db2fe4549447ee4f832ed443b32329ae071340e9c5ef7112adfc9d82e0136c943f63ee7cbdee31ed5c99833cb442492dd92b8eeeebe1cf05b13d79500c68ddf5c892fa59452da1cf1d0be491619b2bbdd5996adb339e0379e9fe0f9f33a37d9e688b68a79685d57ce4d2a6a0c1940844e15709de8ba3bcb9672d46f73babb140bde75ccc4bc4f1a0ade567dfe5944c8cbbff7ce6326e6591ef1d2da1cf5f3b4d2dcad135a27de794c3c9dc77be76179ccc41db0a110133f644c34f15c98a0a1a1a18979114e27b92625e14cfa906dcee630ce750faa33f143b6b91dd73aba855d66bfbfc88405f8466bd287ccc5af34c93d3a19d3734316930bd1104e854f9dc91eb2cdd9189102c3f770ced7a811b1125ff39f4d7d1238566eb4a51c2578b78a79772716c8eb9889919f1a0a24b449b0c0051c439d92f23c6662a4e5414f599bc31d6aeeba24aa90a7d23a213fe57c2a6e9e873c0fcbc3b91dd30cec6906dcb35b9a5b022679c8a66f434c5f70823a0f4a5178eaee53c678ea4eccf39053ca3f1727ea79cc57ea939eb24e886278501e4578a74e9d1664a8b9eea4151ad75d67053920e16e435cf739499fece49e6bd2fbbddbb19c991a3a4146172f8b5b6badd5a2281af593e607e3ffcb06e09515c02d0c158089dd4401b01f0b00eb9e8ac766189db46bd7ae15acb0768b6aadd5568b1ebe52971b393639363936d5fa5551d70d9753b1b0bc1f2c34301ae5480d75cb54bd4a2dbb5375486aad20842548d1ee18a7b690d8fa130d2bfebaec46e34ea7d1f2cacaca8a5cc12c16cac2ac4c7737cbb402a5bc68c83f4b5978c67c979597646141b2524f69b362e9ee66e996eea9592696ee696261a1c162b195841f4e55732be6ee943912102b9f78c6f4e97d0a22247ab5a294ba8bd2aa02d1ead4c5c235e8998256f56a2e33d50d1942082194333a569e15a5ab57b8465f3e2a5e0d0bd790f45bf56a6efc3b535b5919aa31335fd75ba33159d0b2406dc3a151978f26a14f771faef07b7cb565e1191264a93de1834ea8ddd8e2d6361ad5b23c9c5b2baaaa2aeab22c2bcfd2eaa726312d7b1af5e0a4b3e724bd748ba7b2ac58c896524ae9a5692c5ca3521c1a0e8d998f43a301594633099128cb58b06fd395daadbee1d0ee75eb553b66a75544f5d2559d890987c6c2352a16295fcda558409c5285ac8644f594361dd36af66aa5babb4bcf61dd1485d123161234ce4718db96a8bfc768ab943951e6449913658d3ddd58cf3955c6c6be31927a49cd292fd337cef909e7fc46cd39678cf36c23f5b6a32cc6c738e3678c51d6cc2a227bb4a5f83e96d58f8da00fe89ad70a8e6a8abf2a05e37d9abb549c2ea5c5f864acaf4eb5eb53939f346a8a320b75cbe4b9982d8651ec5b758f700de9b196aae766ca96e267e5395f238561169bf198465f2fe1bcccaaf5ebba6c7579dd3a52bd562ad218638c54468871c80f92758a846e57d2d2ba3e4a2995e9a795b049e97ba04b3ab1293d10fb7cda0f175981507ea319258910c9a244dd234261212917a136acc88d9752f2b425b8527d2845dbce0a60f26c41d28eb0538f171149e9067aa4de4da9ea7c85c9b7950bb2156832993604a2eac330cdddf71e469908fc68d63d1a251bc61378720506af8c5eb172e14bb5ad5c4a29ccf215977abc62e5621486612b37567cc5153ca3c8159f06d9fc6024cea2cfce9a7557df9cf3692549cdf9de8cefdd94e731921bef1a47d5f3ea9ed11161dc79cc3d5807c6c94868b41b23b91047157b8a71be15aaa3cc8bc798df8fe71e71ce0dd25b9667de5aa93a1ef4899e950944d98f87d2fe71eb93ef69dddcc33ad43ad3e8439ac9611e47abedf989ca62769b17cbdb52dffae4272c8eeace3ead4c7b3a97d0273ff1e8e1ff04d79887559502c5a58fd6ca86a3baf3950a14179e9fe02778063cbd9b2c222823b9b1bbbb9b9f704ea5415ed47e40b8755ce6f279153f6ba55af5eaacc167d7ab95ce39674b47cd4967654a62d562947e7b8fdf7bd396e8e51f333b66c7dc4a4ab33bc64e7237c8dd3af190b6e74d6f9a3d379eb302d2f617fde8a7cdae6e9e33b38aa87f9885446631bb351fe33a59cc6e1311f101b9954af9de7befc9091ff7ab43ed7d4e69f110be5bfabaaff4ade273d17359992bcf4e2cbb2d5755256de97d1e5a7093794d6de93d9ecf5c8138cdd9afaab375c49c56e6e153aa533844d36d954de66d0dc5859fd4a51193dda69b792a1211752e8557c62be77465b453934ec2c046ea145267855aa5966559f3b22c4ad9baae43eb0cfa654153c8e51a78e3704056dddabebbbbbb9b7ebea76a62a25b9e8c333afa6eeaf3247237cf77dc86efd04dd38ae6e79942e014d28e7294f51ebfc7eff17bfc1ebfc7efb1c5a798628a233333b3075a5df3de0512cd996522d14f72d3c6d6ac7704d7e8b365091172e555ea7ba07cd7bc3f51762be75c1ecdf6b242bac86e22510d8c5ff6358f40d5428206008f26867d34a314bb89ee0d18769ba2074fec76131bb2b05f1ef72fcac8ad1446dadd3d3d0cfb2c0ff3207f7336cfe5a4b81c0bb3a869514a29a59452b6045fa78d4c4c9c764d9b9999395e1a2b3373f3cacacb8b356796894494d23b1a798675782db9754d4ecd9b2e5a727be7e565658544fa45229b1a7ac578ad57913a7422086fe6cc3291e82791acc9a2afec4be419f1f24eca29e4f24b615b040f4f1a89f3c96c2871a35b26750fd5e6a9d4e2c3acf79e2c83f51e175c43deb2ae495e58cfca64175ce3559627f3489c0b99f93db6cf96ac1765195e5bf2467221bd90d68bf4d1923837de4d69a5945276216f2417f23d89433fa7e71df3c82e7806851e2a2f4b4a0fac26cdad966559f559d985e42274bdd2ea5bfbc81b97853b6597142e0b7c4ce329a595d6bb801b0e87830387c3d970f0bb56b4acec42be3e8ae14ed3651738ac57d5d25092c9bccbd6e329ae5bdc8d8a419b2e08cb6e382afb8feca25ad985bca1288973a9eca24172e8864b2abcb9151b028563a577ef9d53cf7f5a7d6b111412bd6b464723e9c3bc2e59eec15ffd0b5eb7b80696754f697d5ef0e6c250bbb08ff7deb39856dfeb7e481a7a51c2bee3c2aef53d245ce39d21172d6d0936f6076fe235e1cd8defeeee8df6d5dfe8db625c7e2a2d9e73378cf3b9b1270fbf7ddc9cd9f4ae5b7fdf3e13b0e0af3f6d93168376649d1fbf02f35c9800d96d9e844cde7b87dc5262c72c24aafbdefc09d9130bb26d652008e5256937b62c24aa176f621f5bd0c776651141e1925b2fcbd2dcbdaecbc2de2bccf368d4533494c3d044cf34ebafdee83757bac5b554a77ee34dd2c248278293c4aacbebba2e6c9bbfbe4d7b7d5155d7b7fa78c77cbd32b3a8522b032fd49933cbaeeb882a0f7f695beb5c6923136f92dbf578fa94cbcac06bbdfad63a5993ae8b50b46915515fb2ecc6d242a2bab555ae4fd1c774fd621f978cef2fb846a56c7fc133e2a9bb14de5c9652cafe62023500d45063bdfb55dd4d0821105d920de19ca0fcfc24c3b76d094aadebb3db34a3e3c94f0f08a6df711ff5b7756f93a16ca94f29d8ef51946b50b24fa98d3c039ee7a7f7fedee127aef13e515611d79fa557e419f3c15fd78476bb7cafb1e3a59abbad3ded96364f69f5308fe12b16ea96e90ece73ad9555bdb37cbc0c12d2469e5155761423d7909ff8f23719bed4e9ab0c4f94ddf84579fa39e37defef4d37421b79c6342bb676706e75e51b0890cb80ebb4c8c9b93936b7eadc8a91ba05bbdb2a4dc675cfe5130f92daa354422a0ff3a44cb756aadf21733c87b4b2a5ea155ea67abdce5bdf7a2bd6b9f0cc966fe5d2062d8d3301a4096ed7a195ff3c5db9efce3585bdedb6322d66335b6a8b9d5af6b273a26c891e7bc4a2b3327dafd3697339aeaad44ed465c1486f455885444d621d76d25c0ecc8370e6e2ea08960d25982eb163b343a90d256c2c6103f3a866d95882f9d948b6a1848d259ecddbb96c6309e78212ddce8d9bcb8930220d455bc2c689c634547d137a31e13c2efa862fb4f1369488dbb38194cd8d87d82314dd0b8675228b6233fb44300eba732e29f7c08f34f75234e754606822cd3914cd0a7dece6965c07e5e3355919081d7fbad1561978a91c78e797e01ad596a8c74b59c953b6c46c97e01953daedd95cf825780684f444e3188e71391386a6a16827f555279a8b715611f2c6a47ab495c4c99cf37f11908f274368517b79efbd373fedb3598287801ab486eaaa63c69899e185e4b2484c2df2138ca45b26f64bc3ecf604126624d331f313dd62c53be62835772d4dfebd62f05570c9a4e4ed7412bed142cb489a04374612cf4822c0976f51f17d6e59b6da9c4b76cbdae2c7ca2c1a4a32ca6e38aa63f4932ca2343f59eaeebab3d8af63f05ab6749d9a0065f9090b09689f885cd578a8c51810c2d09d8f318220744bf1f4f58134cfabb35be8734b93af22c300e89e3f90e6aa568920ed5611743275a94d6d0bddea52543c11dc7f8c849f58b13abbc29c3a538b43f8c89c9375260fe123131fe1217c44c431d6737933097c8475f888cc3c5acde465f62eb3bfc73a7c8412429f01c9fd755a980188b49b8e2b0fb5f8c8333339de9544c8770e6691982c22a2140fe63d8b88eb9876317f72374277e11f64cd4d47d4fbaa3c41111101e1291bd23806f4f164d6eb890fb2d23cbcef482e8cebb458c3cdad5db17504bc59b74c0babd0ab96c6a734ca5611f49174a389365b251e4513b54afc8926d2d88a003d4c8bb4ec28dae7279ae7261ae8ceb2d131a1c3f46348737385c6cb1059bf5d43cdc23c07e3676e9cecadae2d783ee29bc23361159655c73ec7b0eb309d61d9371010184d4e5044175f8472628279387461d7f99e0c7b523c299e1493a7e5116c460708f41110100602496c4405c8032b9383b42dbb333a3010f611100c34b2c204948140d64d4037b138039d848e594f9126d831cc9a3c115e9e5899be26761bc10cc86eecf9c763b70cb33e0a320a320a320a22bb90145a369bd1615d24fa645d348964f33bae1545a2ec5366d15b728a2815616589b42dbb333a325196890ef344d63d5ae9ba1361229147748fe5c13cbf2eca3c22eb338540d84766a27fa6d004fa605384817272d2cacb8591797e896c36a14c2793c9149a40d3679a98dd5ea8cb5f5ae419565529bbc5d339e374c2940b90f0e1f20caed3420d3ad4e62a7b7daa7f3e9f4ff5cfa7faa7b4e3560e7e3e55755dd54c223f339b62ad5ebdcfe792f968db435ac9fa609f4ff5d0e7a1ea0a1da631cbfac7e43af60df4ab7a8acc747d3e17945f36f4ead7517e7255199cbe5d329a5899be21bb8d6086b37b32bb615745f908cfa86c35817aa9dde0a71df5e564a54d77b7a7adf3118fa7c23cd83dec394c57fca9be65af144eef1a7af560a7af0f81348b89d48f87eb2dcb6e15d3f3113ec247f888a7631fde7bef82d2c4e4105af09649e419cfc48462138a4d2836a1d8e430cf84faa595aa4965627299fca2aeea323953186326c7ce33a35ec913c72106f1873d9c31c6159f5cafb71bf35d87401fbe2c1b6334615a85b6b9fdde7befa395765cceb24358c157d91c0539f53ecfaa2aabaaec302fa3fec9ac2ca3fec9fea1b8faf0abe715f594f8f9565596c2aa8fdd463093d9cdb215351f84675074daedd94cc2a036ec6eabab99032dcbb2ce3956b52cceb12ccbb2a6c9aaa6eb569d769379599c1b13c9344cb3a65b293ae3115166b504c37b5d4d3a51e9289da6191dce4d229452eaa615ce393ae77fcad2fe7f68c54e3c16c824bb2cfaf19c602093cca2a28732d18927f33cfbc75e009fd089282546004e3240010820630056d3ec05100000c418a5c010a19c9884401f4f865d96a2368ab2b72506ebbdf8de93efddb27bce49ca7e9772644589f608c89b2494721bbda845f958db6e4cad572debfddad7dbc29e61575b95a5941565184b25d5c9d8e4902b1fe37c71aa26a7e88c804c37cd382335aa99736e9bb555dddab4d3dd2625575e29491a4717e9faf74edb6e2949a6c72f489051107931c403849275a0942d25948f75ec1631ebd5f58fe6c91c13b12a2b33ddf8884c131fe1237c848fbc19aca118aa93e15c7c6e92da96f5ad264739237411c6cb4ff67db20bba6137ecd449e6a4ddac539b8fed198a1180ca6e2f77fb6d945025cf360fe65c7913bbb1911bb21b34c23817e7cad0419f76e301dcde39171937b9732e0438ca9d7329c061dc3917037c74e75c4807c09d7351b900b4ab68cea99034e7540ca039a752008d009a0c6d009a732a34d66af6767b11800d80dd56006063d88d34b229763b8c13bb65d26e32f186401fdbf3a2a124c36ca52c2b13af7ca5a56a29ea4dd196f897d9271b79c6cbd1baea4839c2a9463574e74eafee46b4cd0df5a699eaa9d4e254e465afaa5274aa8f8054232b4a9647a6e88c80700d792aee163aac3c9e438b3d20bb356466666666b615f4bc5b551c5951bd149d1190917cd53b8f82c05797e90ac05752db463537ba2ce6cc3291e834f2a50504692a7a0fad5292409af7f2b2b24222fd2251c35190471fa722293a232055b5c518d55c8ad1ed2585741439650c1b2ab194524a91ddfef8c44a28e508e7d6f7deabec28c808c8950c318ddea35d5bd36c538a4e671195d018d12d0e833459641195641acf4b69a594a320ef103eca51104ea9ee24f940e33215792ad3776e74725e8cace0dce91b691a05c1d145ba089592e4522c95f0744c8329f25c5e5e565648a4ffbd225de4cac72925c98d1f05e11ad528488a0e458d6a2e9552ca51109e11290c16c01ce86f301a2826d7096b17a5924a2aa9a4dba36207a554bee9bb3e293e94e2c6243f7b554aacda9275aa4abb4d333ae4e994a7534e17a573cae953b7eb74bea24c594a299595a5205ba51d51af9f522291234fa594a1cffaeba297b624dfb4d69005514aa90455b55237b75a1425a594525217a6657f9ae89386726872aacd7fb4ea308fe935a5e0cb96e4add75f405c29b8c6f478b2e702efb1f3b624e9abb4e4ab86843feb67c5d54528da89963d15eb269a7b139b8f508a7aebd53ba7fa641151d15bab9452fe52534e33577f70d059d41fb7625d31106fad9ab5a664a1d40a83a2a09cd45b2214eba2fa030546b5aab3a5faaea1ae3e205cfbe4dbfbf17edc780ba556ebd937bde94daf288e5267c16a5d1e26fbfcb81b8c1f0c639232ba07868d012346c5f5c634b2392976cb60882c4bd3b585ee81a45d920042870ff7e528e124348ce4e86c6ec7ed0c41e3c25c8065a016641e22783e63f880a8f02613a010e80f211316989c7caa38f9c4a3bc8080bc6051018145cb20d231ace8101513a572cf855ff88557483b4c8456906d2f1df8cb4530be906d6e679b2d5b63314563d144da8aa6e2c19051c6944f18b2cded4c266c5e919d9d9d1d6793813354d72d20e09a5b724dca0c10226d0c847b308d119c1b9b259d835f6c08c1a02c6c707ffa30a9f74932a4c87381e767c43c827a99b21092f38a02dc8c68ffe04351649b33f2e8f38820c988b647b819efcfbe7c608d2fa09b1797ac5578850ad214fce31a3152396803ca03a47f16869bf1a89d3da2c27d771f9d6c7337324dd818c1373733f4630a79136fe0cd0d0d7838a10d372419cc822ce00c8921babce3bdd7ec5ebcb6995b430b49b08999e11ddcac04fe64c1c60b1b0e831736d188112336e69c73259b73ce25d9e68c349c18e6b9c8bf1bf15ce2d4a47849e106373827cab83b50b58103b5d908597643348d72908958d0e366a11930b89febb4888318ee969dfd40732bd6f39660222db9a19384910a2fe0bcd8438f1d3b9c010b254062e00fee0a10ee26a2e26ea41fdcedc5077773373db89b335273210fe690e40863f0811a7e78010c7007406c01270a353c74e2202466c60a19c4600d72c8c210a2c08798feca737140164cb8828a9c1a306108317d98e7e2b2480213867421061af0200e31fd249e4b0370e690072c6001061e57c4f46f3c9713242982199450410728d82066016668c10ac0608517841c89699a1ab0a1f298020b7d40430de620071b0401ea8006150f78140951c105e6023f5c4970411772a0c8208410562c8021b0e10311f480033a08d98153bc81055317d04007287ee7c590374494ebb61892852147ac232c794ec417fc0b23a0ebb470c313575eb7c50ad0206326c0096e3e8639b8a49e89a20f3588c3139840042531dd5ce4478c3ba02e60430b824c3d842e4c95138e0061f0890f52204245c1092c48a18ae9c4c4872d64b006eaf270dfdb62055fd09b2d64a0c5c5aedb420660b8db23139c1093c7f3aafb1e1a7e6be59efddea3a6a99d13629a64047ed93a17c47b6bbf273778af708a3312018f609a3bc5799eb2ceca591096005238e9a4df22c7c8fc4967e4182b8cd2cd2b34855ae184fc4acfb175ee21a122a5749a788a16ceaef4add14fd3ca95f255ad1ee35df50d58d6bd95a90bd051d43c45e79dfb085ce8357c6fea8f60e6be379d9a93faa43ea94f869714457d4ed4e690f023988980a31cab505db760539fa51342462744844e08f89c10af9d101d3fd996e2dfe361bad497af5193324608a7e93d7e87dd7756e92398b96da72907b67b4208fbd49b7a85da564d337bf6ebdbbbccfede2fbd57e122c699e57869638c18dba955e62d6db38aa8eeb42e68bdad0850b768ada89de27c73be4f32c6279af46467154c4f359e29cd2eb5f2360601949055eeef2eda8f217bef34de9ce83451ca73b6caf44b736fabcf2c3b3569a23e69f212c6e8966a3759750b94d24ef2512b8114627ea377b3275875eca2ce3cb4e009363195b7d0831076626a7b010b3cecc4d477e30220f888a953d59a13030e919c980a6f728cc4d4a8e343484c95556bfc041688a013536790214284c4545ab5c641a6f0e326a65278d0c3196a626abd830c68626af5052700e126a65a794092b3862038e811533124b44127a6665a0cb1424e4cf564810b3631f553b5e6bcb8998226a6829c165f6c01276687b3f9820b45980d40d440c36f48034d4d4c3da95ae32d7440849d988a52b5e69a015b75793603f85554b5e6eca0879d9a980a0346ca1e84a0849a981dcc6f8b2f906c2949dc9ce634cda9e15ffca4c1c7865372dbd81c86c1e54c935d420c48e6bc1b433127cb4f0069244f0a32f01132b4ce9c93aad7ab463dad3e0b4cd32f7b91ea75bd08774bd7e3294b630ed9e888ebf157a5286de6520d87e8f69f83da4ce5ce030d29e504a6cf1731cddca76def0ddfe3cc7d7de13403998d70b4e69c73e64a6d939fe4ccb52c3ca30ff366203318a4e4ee9b514d93e4a8a649324e31c2a74c2949ea08488ace2808cf98a0803e608f2689609a373759f328c84dd60f2699baba4b5578aa82b7e0f3d139345e562aad52578081c015229c627c36a1132f25949406a9c944f808e49c0b2f2584d7263dd122eb4ffc28a1d40236dc82d4c403d224f98034493eca4ea2fae9956c244f8aa86dccfda2ad502756b0818140b7307c40ac90f5b739ab2cc2806ff0049b98429a24af9c429ac472c736032b1c3bdd542ada6d42f17c5e2bebceb998fcc2ee5c16f26837dee46c5e5e565648a45f14e5e434e6076f6c9a34591a4a3297a3c3c2c833a669c7754d3ad922eb6f2ee746073c70f5c443ce0322815427c34db74e2554f284099d9311c94af5b07aa5dea2d349ba05b6924afe5d1a914664196495a253ed280864db5e344e1b699cf6c2088744312e83d4469450a7510d16190e918c6f1612c5b80cd228c8888a0b3b67cb7a07270bdd8b6e89d148b2ce190599e01458c029eefb2848fdd64fe1ad73ee1b05494972273bc99caa868a846d86847aca66b148b68944a225504de8609ac82df154f6f7eecea8e9188b2615b1e1226cd32d946d4e90c09f70daf8848cde15814bc838068cd38a1bb94811510c1263d1840e915bc233a285d0081769e281de3645951136f2399f8b3cebe122f7c5ccf29b78d9465a42e592385d5a6861861e1701b7623b3144acd396641bcab22454d5215b4a9b4dc88cb879cf72f78aac240fef46ba150908ea43740b5579c0e9e0a828bee421314417c5385320090dd850e2c69350272718418124346043891b57647c8da040121ab0a1c40dd2151941011b3148403c7743e2233ce4c8345d73e2234ca4d6a16e40dc6a52cc7242f0090f323e5b19275d46b790ba25f2890fea542dc39f40218b775d9dd4647cdf7b7ffc77f73ec4fbdf65bc9055ada4ec66f654445374d2da39391bda11093f587eb0fc60f9c192fd6081d60f4a29b590702b8ee507d532c7f2c3ad38961f593896898bba0d7199c66516c9c233e02bbe86442864a2e2d5f4147ca75d99f6fdb83c979fd8677dcfe9a39068cec3d0685c886252cd53ebf3232d0586283bc9e2c287404d1dfc74ab2dd17a59c712cf52ab65b12cb52508a939f9d637ba0846ca484bd1606432226522f8dca3bb65dcd8dda218f7bdf7bae70f961f2c3f587eb0fc6031695018429f8f941f2ba5b41b12d30eac91b2a1e828a69dd0a5168a62da9976604d4781c1f816822de4ce77145d73e79d9bd36e32b0c77bc6ce06c87e3c8f6e240cb66082348f0fbd6452d2504c3b4d82dbb46355d5b403806f5900be4d013cda8dfa27c6e33633d03d02b0ae55b2bf56e918768e2c8c771a31bb85328c3ee512dd3997d14f465a8ae61c0ccd39156a1d907b75cdbd6e62b7ac85dc4b64e35d6d431fd1bdac876828c940f29ecb5eff7c3cf71105d7b82edb51cc40bb73a3e019f1991619098470aebcf08c2078b9a568b32a605437a25ba205e214689715d22f12c5ea155622238ad0b8c7c76e248f15b1c52eadded2a8b3cdebaaaad8f5155fe306c4b672e38b6dd3366dd336f3d166a20e35e93d74abb6b91baa19b96feedc28764e1a060ce0a4653370c22e5dce74cc22e1727c480d2ae16e748b3ccc8e69999439ef6e9a7ea35ba08f2b35c7d3b4d24b4cb6d25f58ad3393929d243ad38d0eae11a8a42d98643dc9d4880000800001011315000030100c07c482e18838d2242da40f1480108ca24c6e5219e86192e49032c6184200c00000008000a0499800d2c3730312345a41b15206bdacb84e2b1f029f9d8e502d16e8f9e56813233a05d7e6f9c8d82ba6c3e017cb304a39141e336b391e3ddd9753d16ac99b135d53d35460e5c9d4d87b49be59f3cf10ed73be8c02e28ca0009158e6ca59f306c35e552500df3a93e1c977f46aa097b1a5348ae8a5e15e4a59949d7195efba0dd2cb502fe35239603ae269b8975ed2594ab2ef2d008884cd6708c21b7349537e83bd1c95ea4ef8508c587121c40d06bda614777b35ece57c29eb573b2c5e39897c65f0be5698e40cf11f4ba729757a4f707b57fe60cc5f42ac8155b70a1bcd4f752e784364612e9dbd2ee8d85b85346723e0c502221ecd1a6545dcc4740916040c75e432cc1fa2a228b470daca363049e2b5eda6fe879a78d3d6a3fbc1f772b2e3fb96440008a3a1e05798919dfc836ec0dbf9099c5f7304ccd142332e09710fbe421ee299b6b2a787e60fe0b62745cd66ded1553cf41f83ead56b0085dc86fccbe95a63f976d03e45f5507290051ea7a0d17d6f510b7137c179850bcf9744392e16f609f4f113e7141b2825be0f6e829c0e67d2815a1fb71f4c3ee57ef1363fec21af9ee9d7a7b6d93a30e144dd6a10435c921a13e479a70816f50b96d948fbfdd0cad29629c63fbb808a064ddbdf5f285252857c0103cf4bee02e3e05b67950c87a2b964cfebb9679fe16ae67807ac185d35eb265023b73ee520530cd95a3631b4bd85714d8561ca9b12607418649635443037306056badf3fc80a1d5e3c3c0078ba0e3d15fd830d039a2fcbbf9e7d065dc1c5f025fbb4045110b3b2e64faf077a995daa30a2f52860410493caf1dcf1a4b24c0f2fd148402567e9d823f9f392cb5db961b7ba388d1db9ca20289d2acb5e88b4ca0a881d2991ae4af7049156f359edf613591c5a28c7bec4d3b288a4d61a952cf63fda3c8104abe4893c2ceb4fc686df7cb84eca5422aee0c8fa1c05d50f2fceab26e40f8b6377951a2cb9e685760ffde5b4acf2fe30e3b506f89273b1dfdb8b2cfc03ce8bca3411943aba21d2045e22d41b7e5aa0722241431ca49a19f0a838fe78f4e4f0bf36fcacb4c416a46d7dfde6f65da9eeda75dceb65207ac21f8f8bfa61fe3b77e0a7493eb84190b88db1c52d340a04e0e25ac283a97f2c1b7da38d628323c35b96800c3baebe1380615ee94a0fc25f33e4b11332a53a01cb821c571da192c18775f599c7ac55dc161352378efe0869e2e10cffa43352cd755a583d4dd4188519a1f0495b647eed4bc186d94c371dfa188a1eb1a270d48c9c74efb4d43c21abfd81c3e981af146730ad2f8cb3bed16782e0f6608bc291bf6e8a58f35953fff4a149032b1aabd3688b405e0e5c3fa4bf3eb96b82f11fdef11189bc4e1331c4feef2b1ef1f467307b6fe095ae9b2755e5699579148032accc1eace3054df08fb56ec6b645ca7969382e45667f8d4372954896f0597ea72e32fe50145e8e18510798543664576976520555ccf44354df9154622a337427fa80c5e87a45350a18341ab2c459d9ddc171639e7d8f68c5d270a4a383210ea2eba3920708022f04a45507e3ce61341110aa9f30bb3707b0574b77bd27ac4c45ebd2c8b61a3a38b619e9b341faf3aaf399c3a9f212d899f9b7e2f534d9add4469b2d4397f9fcc2df99f8d799f5fac7bf69492a66143b80fb9616bd314cba11f48afed0edba3232e68e8d2654e0df4516a7563708b9ebbd10d82c95c8c6fb9498461f3d2788b6c8ad74f0991dece840e90c8ca46a2b7ff8d68d1d677ba7b2a66d72254b86cb1a32085e0569978c770c00683ea85c39c16c55ee0a3515d9205d1235f2e3ba75020a4da072d90618259b4d7a1e890e422efb3347f49c5e8e8239361b2477cd28fdb5f587d166b5266edd8212a32799f223b098268f14b9b7200cb1a85daa48ac8a3a71b9a75bcdd01f440feb3e3782f5ce66033418ff16bac6d312e97451f4747c996334c916b367156090431c4b8c2b2fb195f87f1e6cdc62e9c61122cecea36d525a24918ca0b7343756ba003ed99fafb9fe3cbefef7e92577c8e7a8c85d4733942deaee85588154a31fab1ab585f3c21115af0370eccc6c6fc6e146ebfb2b1f9a5e4737741cf39488af3744a43f4195bd5f81751919f4f66e505253d3e04db13c7ff0d63c163f94ba0517bbb3e38763a508fb4cadb14016e28a617072b3ae29b6a9bccb0f60f24370d93b3a0b6ee37ccbc89e3cafe9d451b4c7e7605930f74fbadc11f612e7d37f5a0db49dce6412738859d31f05f8209661263e7e54025df2030ecee406a058066334f82125c5802d9205d7354af750c9c109bfa4dc64e19751268b37bf527bc1906bfe9bc3d06bf3d8b14bf785554b3e46d68062de684c403f4adb71fe0bd69a27507af91a600a1ec38dccfae80295a8f3bfe4f707e300e7885c37ece307b775ce7f38d6a42e998411f4422ab2b2bd8c39ea86368432b41ef1e6c3b0aa2a6442b76a8afea7ff39ec1c3cb3f81e24c3ee05b46c55850828a1a8e269139d5fcf2d0ee87eadb8cbcaa3e9c67218c33bd048a66c2a3cb318ad97befab4cc05f10ee07f709c54c9f8782aafe31c65ebc5e590c4437cedec9a31d59afcec40f02ccd9f9e29125d747ed2cb669d3d5ba2414e19583b4854aaed797e51e6a543ab7fc63cb80897c30c4961ff42ca5df99d769fb69106937cf32018654bb3d24a3a8e2671fa130fa8d3ea44ee706f2f85d1319f93a5f075510c5d11793b9c88596802c48532cccc51abc5db62f942d97a18f20428fb7dd7fc9b44f85f3e026beae560246c11571579f0cb7da5dece975c8bd20fba98bd6cea45ab3e31a6abf5fd59a4f3fbae4108c0720d99b3e6b399e42d3ac95e2e52eff661b8905a17c150ba6e1f359edc0b2b2ef6e98ce0b7808329e33797f4562ba2cb85f3424afa7a1be43903d96a54700df5880870ccd76b970c9b06ae137ad74e4156fd40b53aae42cf87e9b7b1365d510b5a0b735d151e08ec28ab9c30378235f119eb2437b09e932d4e016ab76908d933da0aec442c204e436a8d48ff3521fa9a2bf3021ca717e0b019ec7fad46b3207bffa64cee6d83064633ebd431ab94adf8b14717dc2086faaf831bb0e6f25c05c811e53d1087f85c97bf250422572a9efe9fae2a2af9b709db20eec9f498f344422a77bb7f2fd0595e966f64609892e1880d675f0a0ba59006d55eb64600bdae4e503ae2a45ca9a196806256def0213130050c4d15e5a8823b9b5bc4509cef0f39c2e74cdfb5a953038f3bff437e456f8119ac619d45af52abfd48fbfc27497c1c2d73feb9c7edba0173ab8e2b47cd9665cda557b0dfacebbc4ea7032f2bf57f69694c91d64d787dfd0991956ee2be1a645e5f0b4b30148b3b01bc406b8551784a18c2efb3dc86af943fa3b40f78e046a6cecf5e0355a4ebfb90fa7b14244316d28f43c3b3ba143123281a61f822a484f79769dcbd1bd6c76e1ac8f4b5d8e2d439528d707f395c1ecc1a7715a6b431a0f6fd89b8aa0285801ac875a99989f3e2e7630c6914e8b082b4233a3636039949dc5c6cb574d3c5847f4590c821897d207a116f7e4f5c14846aa8ff8f186e47bd9a49c3d44c43515eb3f34ddbfa99bf462ed95f17a3496673bd525367fa8b70bf479ebe4b8847c4af3c799001a4304c0eb826607120e46d61bc52f1275161e2cc84fe91f48bb50ee3dee807373d20fa3a6c5a5a4b755fcf8f031d34c5af19905c962426b0f99072ac3fcaffd9b390335e242adac4fe9f60981daa5f3921fb0c8761169d05782e3343b4ab290f4289ea918a6f512f9b020e804f3a64b13b4c64db384924f591d52b773d28ac046a4b81df9d24d3ca3a23cb7499db77b4e339174cbfc628b6b46ece7eda2b026adaae926031f03d30dd64b475fa820c29cebf8f4c75bb0d94a73b0c96f8c40d15247f7491a7be9162c0aade681b0f2675951e43f1e496cca6e8ce236e1eeb535a58fc1075a399e692e36eea60eed8880765871c2b78697a233f7aa17a975df36dcb7c5bab1e905893c32a41dad72b8f09152d65e20bab56584448164c2478015161904c0191d7cced300c98b438b6f5c857c2207e066a3f5fc04fa07aff49c18e77e59248211845faaccea9263f58edfefaa4b511f98d2ac3e502315d39d0d6d67f72ccbca371c88e5f429ca63ec39548e20325d98951df6eae992fa9adc8037347c20c2aecf5dfd0f06a1f653a3fa82e318e7760eef521a67816050212cc15852623b4964d531e1318c4d4411d2468aedea76da63ed7f4a1f7dcd03028917654f9edc46e39a778ebae52991488043cfff9b716e5ae7b0a0218a41fc1d4329bc5f32af06dd2ce15ec3f1c3e6f541139986693891042874d6ad25bf9525e41d2273b1006e84514238141b1074775a74b98abdc71490c0c96f0ce7e8a938a355502dc7c29f206ee7461b7c847a87e4b5b77d77d88d1e0828f312522104725f0f311b662f0c176668966fd7c82d3ac63cb53f82902e16d3a3195e8fe41337e651e58b285ba66ad358dc4ed77a68e2ce34a06b09875abb4be97c3ef01b0e5536b8648f90d6d0081614c4328a988772313eb8ff5612170f15480b0fd826f0723aa141902173dde1128721aa7cf33453a5cfbc6ed941cd8f0b5c5d7410d76ee17a8d5112bd03bef2bb716f59bf27374d171192df29fdc65ae70874c5df4adeb7fa87f625f569c996b84e04faee6c017b8e0d927b7bf127c728aa2a6060755cb5d0e930e4350dc0fc21408da19b7130075f2a5419b86dcebdd1b1382d89b4a943bd46f0640e8d17ab7d0168b277a3d3aaa453fc3aa47c7e31a92de240718a50670e3fb01824386c019a4a04684f448b01de0901da054a557027f3851d17924a53b73edc52ad55493505e017320f83e6e57eb514b2a72cdf470d0e8ee173875194acd0ed91d65770e2172c6312a4cd7d15b20a3eab600bd928077d55aaf086caca49a0dd68a6d5b2d564aa0c38dbdb28815c3402e313f96ac8f40316b28e9d428aad7c7e44807800269b2435f3da5ec446610ae5eb39051838018fb9a7e8ec0a74bb6381171fd99de482c8e8bb5e3edb65d8f157c01e2f2965cd70c164611664a6d01d43afd9b4a1b5469c90645ea739e132281ceb3308508b039ed6e2db4f9e7e97bbd36599010c313f36c18a1bd3518489fe8ae1e28b16e980a8f786260ea37dc95c889fb59f58230482a16d738cd50b009efe79ab0b5fc2212502000e8bad67b3fcdd3926a04ada27444ae69dbc4debeff9758405f25504dc685f2a69b28b6e0050b62e6667c0c114a18acd5e0d830f3493f35ea05fccb27b7cd8332be79d87868df6fe6d6ac5f255f3a69107ccb28143ec2f2213025fb087a74412df4024e4c2fe0901f01148d6d84d96a9d4b03fd8654db33bdb15d77236ebd135b254aa86f529fc3372977d447afaf24377da1134b8a58d8a6a52c69b20f7c2c2182fba4414587e05a7cd21cd26518a018a9f7ea2a78d789abdad06c7b9bcb2a845863b23bf39b82d4ffaf616022fc3ed961f46d62ff0a575f32a915ed9f7705c22b5227ced444d5d1758c7c52f870cb6e65dee78d959aa5aa40d87bb33abf9cbdf601aad9a8336b7e922e0c3dfad325ce257842ccba8f26dab31312ea462abb91594b73595ca7bca129d9958710a7ac3f3fd121a9cb53f69c913c4876fb3f2fe92f17addbe494252198f2fdf09767feb85353ebccb79f67ad39910fc6fbe93e0c7cb73ee5500a3c81db5f208430f95512820f053ac814f2cab2cf7aae262a89db970da58d807d0d5cf5a287cabc1d4b7f3769383c4cddb6a314b69ecc2be9f35d7d305a4bd94b887dfdd121abaa897616f7e5a537461ef11b979c2823349b10db0361c86c0d0ac92ea4489b2ccc7c1522dd42463179151fbeb184ad8263283acebecc87c8f58833c1ede47913be93583a57755fd16edc611a4075f16d029b7af9e4af2550022b33236145d0ecd5aaddd409a5097626aad88a2133bf1f3ef6272979fda28c3ceb9a14919e78d9028b79e444bf07b3f615309a7d4cac2406ca7daacb996461a54f06f959d153cc60c25e46d3d09170276baf4b39d37d9a18f092b8c1ab2f99cfe9dae28c4534d36eb1b953354bc992ef2c4b501ad0512c372a0b89d3382340043118f43e502913c247a3cf02982b2ad82d1876d31edcd3fa09f9905b283119a088f0ac4ba4a5b5fe53bfefc108693d132be8234385feebff6ed6bda88ef6d1a4a4710fb9a6798240d3e33f1b385c20a221ecd9edae3e407547d2181987e22587dc4e1f6a8a4d7cef68dcaf04b0b697fd8a1068bbe667c56d10e9141ee5974215e6a08c1083546377ba5f7228f6b5121e54b7862f9af677897cd92ba8de74677a39f26b6cc83a8479e39f644258a1f808fb8ba7aad54dc90eeaaf9cc9213cd13a6c09f443e83c19bc43e82c197443e83c191c331d30524d2bf03efb7937f4afea8216701096a422c5f4cd105e286bf982bfaf0a454f1755f80653392c82371c7ba7c163418f4616ca4fd7c8d2b5e0b8d5cfab4e83d27389b3651acb6766d21b20530166f91033bf316e71779152568b555c6c4a4d0a6aa75c9cd7dcc8bde3a8c09949d0b95c2849b6c13ed9a560c7f62e04456389952d5576bd5e11ed6609a8bd8f3ebf647e19f1fce29182be444f0dec69fcfa249a98338194d9f2d5f14aa70d6df21b9d74d01a3b1e11c71368282949ac53352cd82d2a43dd679e8694edd19b5b1b77940b9c9d0bb321b8ecc01a3768b365d814848465def84783350d6ec3a1073279a687e3a01a63e6933206489a3bfb9ec7326d28dbe684ada197e82d452a984ae58ca7eae62510f0508735743adafe18d4077144cfd6957f9176ff49d8023fcdfc2b28b14cede2c456cb06787f55ec16d74c3876cac91c13bab853545e13781d1fa4130e689905739b0becc649365f083e5cbe9bd255bc3ed4b005b17ff569d6ba7dada8bd8cc6ba005737e80f5df14c4a282d7757648a3b07e40af85c732cf4b3ab5b4a2ee2d5c46c74e06bddb50dfcf5be7de0133dc7e565b21d68e8e23560f59ceed05acec5735a5950adc4ecdfabb0b2b1d1f468aed0427ce672d3e0a5c6ae31c1329ca09e4e4e6b0c50ca2135a1f30e2d5083b96c20d74d77755e20cdb04b62fdc48ae20cd2bf7307ed21f7bdf0e9602ded2cc6899dcf376b526c40e159f60ad2109f435256876f564766bb38ac6dc0446e6fc5cf83e2ff6918286461c95aa67e7dabf5ba979ddfd96338af4e739c29a2be051a7b05991e25049c9c7d155318e9c421fcec1fac42d5f971767e8f6cc39fcbe5b00620136ef80f94511af7751eb228d2e00202856f4d2913df71c2abf2b02cbabaebbe4108a4da91f1ac506d9728768ff47c0a59a735c1afab2b85b4d6ffeb4d96eb6de8349c4848a83ee7a0eeda83c12f9a0e51a6d5955295552e0688c3e5655502162c54bcb685f156777a7f095f5b0ed9a5aba2e4966ec49b6ed9329c7dd1bd33adefc20ebb6d174d9ed86126aca5162e8861ea7f21b43a53cd64c12935286c1a6d3a0edf584b278e91165c2af27c4d5cfe13247cef088fa19ebb9b50ed04c225fddc2bafa4012e7a1e30530cd95ca51d52f6513d54ee1838a5069839b2625d84043f5649f3a6af7d8def5442e3f22a6f41c1d776ce8f259e206ae69d5327c537abd30eb971eb7479f3e754f42b0b31c2710e382856b6af1c9987e3caa24a75a08d8af8799ad2882e0931abde72cd90bf8f0d4eddb138e6ca4c0de55121e1da4eae8bb616ae1ad94dc3a00de1989e995d837d5d67b839b6a0bc13384460bd7c06b0a3eaa00030fa81d8d27b4d2b617068aa1291a557f7750b6e626459caec50c25ba8b9c96f5beecc4a30bd570029621e0bb26f52245b513efc320b1ede1ac634f2ae668b9a175cde54ebbe1209ddffe70ff52d1f9cb14290b645f21799ade1c7a018eebf2e330562a6726949a215d2a532bbf5b918b52ab648cce52337f403998e82438cd2040e92dc85f363d977e8dcf2c958b263bee7dd0fe6b3cd9728c423e79388aa498be4665a7256f2bb8323143ff55532a6e9073900daea1255a6ec75a12a3a072d8bb05b311dfccb10859eda8be5868e18340c3c3846dc6cce73215a131b95602d3c3a1a7fcdd0d9129a8a396657908bff311b4d940e36e603b18eae4733e61430aff35ce431e5000456d0da32073dbd3a5c5b9a402b9a515d99291b1843edc6cc49c9e8e6cd232b8242361dd65002f0941254297863bd19a8cf32e2035559444a3dd0838f8c6585f31eb0f67fd5cc9c0f7bdc0f264bda06ffa52e00c5345309fbc5065e26b3fe190a74f106886807dca3963c0d84da793e31d630c16cdbf49b53495f996dd68e5e5d11f2027b73ae3dc8cdd33b191786b9dd4b84e4471eb4fcae60b347a0ad9ff1ba45e3e02ba8301a445724e772d51e7d5459d2538650ae414561f0a6d1f8e5eaf03ef2bad5ab0d8566e45287f81fcd9fc78c065a27a8a05000224e2dea7515e42cd053402c2fa2ed74762cc48e02b2568f24ac15ab297f4a0f5fbb2580507678b903f571e2315b450626fe4f28795c2143739e7c61a54cdc714a9a82d5f4187ff3154612a522ca7bbcd2649d92723d1141d2ab115ea2f4492ac6210e05100d510c05a18507f983ffa91c8bbd63e898d70e87d5f7f97f90c44a345339f319d56df9611eb4e7e915c893a88af4295817fe6db3c8d33f4c327dacc2c08b8ec1b9cbd3c8ed079cf6388aa815d269dc144770194a4ac0f91a2eeb59ae1caad7033151ce527bc4ac6a28834283ed82dd8fcbcc656121bbcb6e1b5aeb92188ca9adb400ca769b23485c65545ed95a55bb81561d316c1eb9f6cc5315e935defe7ae12dc33fa1495d6c01cd52bfd77c78fc681b67613be429ce6d53116922bc3d0234328b792563a74b2a614fb2421f43f3c5ffa356963199ef295415cbdf9a2811d60e93c105ed23dd070420e4b6e667409a3184532b622d27ac28498080e418835a30b5479b8fb0de499b1fc80e3f42e4b54d0bf3fd1851ec1fe92ead42d50c6ab54e781de310568ed41399df2d478643c5aa11249371596d77a86b31124ac6c944dd0a9145cd2b67ec0cfdf8e3b69a7bfdcc66512ddb7e657df1683888331d44e65ab17fddafaf5cfbb3e0362cce8bc66dfbcab67eba0536e88d5a0abdb266d2347b029cfc273afb498cf8954bd3f210acbedaf9a21ebdf72103b52a7b7102504d52dd0d20cf7944082bca52a74ff4b55907c48927b79e1e5f38d70b9de48bf0181ec28ac1b95139a3cd13dc57faaacd3c18eb45598612ae8e36153699b1b528ac8c5665b3e397c84137afbf5b479dc22d6ae84770f1b67c6c3262596178cb709cc26667f1d2caac1608772b6930cd23c3e800e084f019ddbb0d8de12532b1bc08406077a32bc451f22406e59fb055cf7dc0a16861d9dc680db19d93ac1ac0d1c74272821a1a6a53cad80c6c8e223f45b2f1e09ea79bc24b2ba4b56c8a7ea8213e341884445b15c6bfcf7e8166684a94f80b0ebdb59dae8faa5222aa6812877da3c7f4790375901c5ed0d1b152f42422e581ee58ca20c0ffddcb60c7b3795d5c989e6c647983237cddfca4fbd6f5c94fe79ac2633d15c057018713766cc4e3dc64048fe79894a30b3c0221f92d95477994c32ab724a9e1c66fa68e73433a2a89e1d08c712d56da68ff1512b5e5a36dd0c2414dbb39750751c7ab3b59902dfdde7664544d0fdb8d02d002d4917b4c39a0ff0301b600f2e0d501ec6111a7be9a7ec61bfbd5af0b8a9e9e28ac8c3d508d174a21b74f80f24708a9ce52aa104caa2e4fa38827c09908589d607e2928d8716b9675f6ff2efa00eeb1ce04719a4e7d834dd37edc8709f84aecdfe374250a82d3b488273a633f390a38a0c51f1e7d5d515b04d8095d866b8ef06e96691cd690a6825ae92dcfdda05cd5354d49f2a01c6ea20e0f28dd50b4c118fb1cf2957e00608d7ea0839ba8c27fef304fb29f36ca40695c98defae48e8ae7467566a2233fab24ad5df834bae363d6bce1b4639b3abe7e984a9a687dcc63a7987ac4d0506a516f41b2cd84bbe7c66d942f650e936242f191d79ba72cab6c7e970524c73d63ee116fad4d8b1f8658c3c48871bbbaec31d1e4059be678d3f4c8b40ef9d0e3e17776068636b7a39dbec899ced2a8773a28e91210b236b4f2be6c5368a730c016eb8cf649561b694aa72bf5f58ae1c63c798027938e3dab525819e7f44c609e1c5fe0a32f6069ce9982c973e5224f921f25388760d4ca0d8ce0386266351ca0e3c4222db7e72ea55638ad22c74a57d53ce6ba3a5f5e1a46005932785d0851ea1a6f1e537d145eaa609d0ca03530981532fd0a04a6b4309b85281156c6e700e536682fb7daa26e40533394de5fb501527aa918671ce1e3a1227b975064bf41ee59a31bcdbc37082012d470e5ebf79d9586f6a2274acf5bf8b24ce15e78395740a66f87024500d82048beeb7e720f354cbcf8941be0cc2c609d510e0ad3157120ca590ebaa437218e8dc088c4a431911caf014226ed4ee2ae1836329a9c0905cf736a96e72532009a9e612dea2f3a501a9c2064d6e1ed02464a7382bea98ad208a8193a2360aa30fadb820fc416fcff7f4782dca64ce1ea177968d1c20d0276ee573dd98f8e2528b313efb26a0c05f7df49b4229ce9dca97766166abbc567ca1ff8c73a75c54dcc3f231ca8eb99049fedb3a21a9d00ca4dc0fac401105520cf6c3364a5e223e46f7343e8e27f8f8016607185a54d8e957af3a3732c018026fb0522dbd01913cef0f0c9cd6e0a53fb63e2417420d97210f664d205e212860b38e7c8d58a8bda31226d781812307822ed8546bdb7870333d9c2383a7d624b704f3ebac798e364dd5cfed960d770fd34088628fef15362ed32bfe6dbf53d62052300c9ab35b1d73fd6aa19888d212f6aa1012eef5624e31c69c387c6be3154fe218c5903586961eeaa296f170b295e23317a1503d4adfcf20348f2317c32469765476976644e05e2a9b63be363aeda872c73205c90b7a8d312dd8fa2439ad0c4c04c77239dfbbb5fa6f311327c6d241dbbd428bc707c72e41f77ad2907c2351fd02f47029f6eba31e737d182f8f46fee73aa2f7db0e6d130745d3dcc2576e6c8bd9c37eff853a285e36d41ec7dc7f7964311f80cebcb5266db0a48a9bf2e7632aecdc86e6a04124e890d03647c9b029fb3cf4a5fcffd2e4fd379a34bcc5e2cc52f392ea8a7620acd8f4c548ed52e5d9bbf4ab4794117c60c38a30022fe932861f62a3cc432d0cea07f8c5e2524a84d37a232e870a81cc3522f29df0b94de6c83547a34f7182069419fcc6e8c5b0718dba8d8e1eecba729f4b29b58d198854d8db401cfea8905fb30883efab5863cd17430d61c3807cf4e03cd75f5cc39705436e1139a0509401349ef7e2a3560a585d8e0fde8733fc16dd98163e834cf2b4e34f151dad2f09e25fcff4439469ae678e730f2c6402762e4d184804b07c88d7df017e47957423064aef54ce6a30d2577917cca79c116997f237c82e34c1c91f152cb25e6cd8dc38fed27922ef985ac604065c5ae5bd2dbbb0513ae2b6c80f001d71ab882d24e294f55abd4b4c4fd31c8960b97cf4226d1531baa897cc01584aabbb7a85c5ec55703922db3fdd7f5dae9480501d8b66ddf6c81bb8e81be69387c1c85d61221685b82db91822d8e8532b2c77bb4bad02760bca09a9ea341e8699b22c8d1986bf1813ee90c1029613c5bc3462e42a47102a7ebe14f4100c7815f4aa70290e683a88d9f4c1040ce706487ffdfa86210373d926256ccb3cc506d44b9bfaf77d0a81ec20f87c744691588ee7ef1c42f3bd44c755d4661369b6c45f7c2cf8812bca8e69aa0d7d3f817efe793e07bf904109af190f093bfffeaba66b6f0f3b51867ed1af86f649b7eec5fd7861e1acb9660b422d91bdad796afeb441837f9d7182c8b670c4b546031591220706ca905003592156d8afa63f8c1dc1fac676ba257bd55bd3212643bb95a1162f9143c9c436be3701ab6d8bc496e0fbb9dc94828c5885e8d045aeb3884cd08a1ef822224387d02a8960d33eb872668ff53e340d99ba243ebf8db5726dfa52f3b9521d1451daeaa2cef858c1a27b5c092c2f22ef74091d58ec1ffa1968b316d5b597cf909e5a5c9b1d9eab2aaae491ad87cbeabbabe42eeaef7a48a3762abe8f13d63867405ae6ff25d6f6ef0dd729be01391a8b52f16cdacd4a5d24e9f798a5db48eae76435583b5d7e44bee70b68eaa78631fb0cdf66e21e510721bf7918d8b85c944c4746b6b4688cda0da913201f1302df90623809ec4d2e64fd50d71124f4a04e420da47b44a02ed2e1d723c0f9c7a960426cbe15662b15219f88ef463b5359f0394580d1ad666442bcc4c69d3fad2939eae3bd6ec245157118d53c6c845caac80d328412355e1c33bb8f66e4b7dfb549773cb76a260f36505222af4d58cb58b3ac47295096a29d644526b538c819a148cf6464c5d3d54e2209e5b4b688f62a8fb3a0b3922e1d3b945a5eedb3369d76d731f536107d0402f9d5f60d11ae067bd0a6df04ed9f8f8d216cd290a7b23f43e2d6524f4d17e94f7bedf803c2497fa202ddfd76b2271e9ef3b791ba4a014330465f6d9be68b70560a3c4a5c7a19d45f8c2782d61a5cdfd738eb4fa202ef37737d0c1ec1ac51afbe203940c978aba6dec1832918074ac7289bec5573ab937697f42af6cf25065fc532d983f61034dcdc45b5543cf1345f472090e212ba49f2e82ffc54f442d2629f02eef88b44c48bf18702dd69fbc61621fea404615f888584f6f007afda443d564245029d55c8ec31f9409fad5a27c2c54e8666bf571209f3f532becabe16b7ca0fb34f48ef9c36ab7d5ca3a1ef4eb5abdbf41da1351d1b39b7d3f16807995a501eda3bf6b110a5bec5739615df0078663d3e53a73efec50a1519f015b315be005f9ae2474be305a4bb1d2eef89c83c4280d5b7aa87eff4c36459684c37a1bd4a66659a5da343dd9cd9dda9507b917c3b11ed9c4411d299675e9b27134570a92b9b68ebac8c3fa0f8f166dc68193da21f3780464fd6267b634982ca6da1513fe03021b4c6eb1237235489af974a600430d3706a45c3b0e34199961d209e5442c85ac62802b516d9734571ac34c5d6afc40db8a7b88f156c329647d4d0ea01ad1d4b166cdbc04fb8de59e43e4f25577c05b6fbd28cefcaef16dab5645fe08ac572f7068ad2359fc9c3bb51886b640782344e7921bad8f9922001cf0a751fe15f7caa441948b870e2ce9fd6d6c803ff7892bffabb0c0f48854287916cbb0ae71890aceb0d73cf0b1a03af6ce96363e9ec46959a53869cb954d2fc5d068a90359cfd7fdc86c278b1d574657e0ca4685661fa81d58744301358e60c3a228626c4ac8894569892d2a8f1c71cde57a94b2a8a88cb9a2c06822539e1aad35d062096f816e3384b92b5f5433af902a2bfd7f97b02c7733c8f8f8af1e5f8d773326fc20d775ce34e9047c4915a725607157aff08f514d1184ce6928abb6b0d39fb6b569d1391fb68c83ecab34a7ebb76438db59d2ff6e567a48af888f60c27b70c4067b20f75bc7a07b5cbd1655a768de92ee5b2f5476bdd815a9736158c7c757bf4618d1d18d2efc32fda7f3d083354ccef84b7a4d1c457e9c5e9ff01fc0c2e7cab690277c5cc5e72d4ba03bae321472a89d2c54ae3cda7195a51b6168718c23ee689d4cef1d765eae266bf5426386a6af8486e9ed0b79bd66577de2b69bcf3a6b7ba12fd672773a6b6986d7fed0724e3862ce34c2ff2239903a2498ebe2e652b172a2e8a6314608ab7d5ea9137fa3223efbe7c04a4f23f0cd36a6f0529952c25951c458a83ba0b9de9f4b178258286bca029108e013362bf2312ecaa5b12a5a5c3b6a1f1f774d3dd4e54072f2f65d0e5945ff792e05173e191624b230da7a20aaaa514f8e0df5c213766980d38ed5102568916a877417dd5041c950fc5c88ee898473a10a0a342f74fa4e832326caa2a8f16a200dabd671098a36a9091d5f269ea23c23cd26702cfeab76dd86335730682f54d146dfde1c2ae92cd4863e4301edc51afa9e8d7190e5fbca1580053e714542b7248f632153d4954d3aa1c597c923b4252aa841bc45d124dc4c8d54bb48e89f3d48c649d31a1a988146fa72f4cbd7253f081336d624b14fc2b62b9cde8696bf36d480e1cf62a30424946c06aa51d93d0953aaaf543db15766020e4888014e6aed9eb87084cf80596cd3aeaaa910baf60ff55cb086c1a1a5ee91961627cc45889c3990fa842c6c08c76dcb2b9115bf0dd4c34f420ef6d927f971df43817d7dfaa2a75679756ab9a990e22f7054d1915b6ace483c12ae09b4e09964cb7724e92c928c988ceb063b1c6da6ca599e3aa1c064a6f00d6c3f39d6837e5b787400ed8fae4067552115681e3ed505d4c7fec01b0cc4aac186c962d010ba1a5c972e7c57430a228a4487497f6759152bcac87570e4644657bb559898d272fb04c9cd314c8700ad6c4917290cb58d34ba989896b5164291dba324f5af2339d7d9b825f4748446e6680f0db4e3a7d83f3175ee6a077c94801272c3452e7c679b2bc9e28a87560ed884f349675409aec70d5c53fec3f980bba6dc8e681d8d05b8c42ccae51a204e7b7cd8cf38542abe43fcd733cbc172a0473edf11aebf7be106b8c0f9e5bf52f14ff80f83a164a677c2cfb016ac30ae28eb4fc8a7a5b2401a50ac882b223f300e0b22f5fed4d99b887805e581814265d7e730bf1e57f41d3865bf842b4ae80632e004b1b8a21bde42c776efe64be3ac68d5aa043283e1390b21aa759d57d89e817e7454f95ed76bebbad9426114963eb162c92344b3041bf729bdbdcacac07e714d16b775d8839cb432bd9541e6873ee7236e47ee27bf6dd0c43cd01b40703b0b03bb243323aa34df40d213a0c3fa8caf5fbb6d3d04bec6e5ed2f1b2754e5be961b5e98c658fdea053561f7358a5367fc9f73f93161d0d49807b20100dca7da6ddde64a9e0f15e92b523f687a8745b8f11ece51d37dbd11a094008ef235c8326c7bf09e262759ce755f7936878ed6b82fa57367a53e243977509a0602b59b14786bd0f2ee2b1c909cd7bbf1f597e9fd55a8d5b6d87f07b8602006c55dc2cb3c3c03c1ada255117dadf5064f43401ece3c8c2eb69684aaf34914793ee5dbe7bf3a5875349daa6264b55f9e3f88ccfe567b68d3220942e3e3d54327fbc9329de1bde43c4f4fda2f5045f51ae7528ea79fc7be871c32a01da368e929bf31b7756ca174b7203fffbdc1cc0fa5eb7ff71f066530c5b8154af1960e66db0514d0cc0734bc5e7ff0d94ce19b97f4441d793e9eb496f480c4b29d7d2e4efb862c5ef4d877e6795e77cc39e6314a42e160ef51d3f16e90f75a9648c8a4e075135e78eb9de38db2fbff5cd203ffd4ae2304fd3777117fd4154d618579dfd2955f5c88beae3f5c26b892f6bdfd811c974c7210b4dc83260a5d4fea4566b5d6371b1fc2fea5a15c801a16fdf275e17014ba65113c77d79f7ca0ec170507da136006220a24dbc23949bd644c8a1b7d26fd40c507a0a328d8454b55596046ed3305bfc22f81ed4bc3cd88fe8b0c00265f7d2d83979bba4f33be987f2af6e6d2201521a06d3e805e1c7147237b309d9eb7ac0e9e93f0b833146d6798f3c8f2c5ec742b43a2413de78b9051f8aa22b1608f247d979663670a3e2ca54d187ece2734413d79c79e2e74bdaa9dfedfe2b9304bf46d5d3830a99edb9f9ef76a086cb06816d66abbe79bf711dff5bdb72d2fd4dd7dc2de0fe88b06dcbfb614d035904d50cc3ad799f7ddde0a75c7e081c8165c29b608d816840a5ff49e66aeb68a6f482b429358a8e00ead8364a78a441074e3183a9da92466674210cf93093c7ae62a175530abe7337c0cc21b152b199ff471071dbc1fe31d150089ff7338206fd9954711fcdfaeffdb37242fa757fbb192430e1559069b8f39dcab3945eb75b5cafc46c88fdb865366efe5fa516581bdf7301817f3f55e68c47ffa60a4ab336aa57e114bfb83d2e6fbddc5a1d9dc9e67f16a2fa66886cfb6a1b5ebb5ae8abd8a1e6824b08b3a98f9e9f8f2c2c20c8264f0a24334cf3380909082e4e9df7df4f387f4137cad1aefa28da7f78bcfe3964005225450bc522464aa646cb295ec863c1b06645cf590de5b344612639d96b73c30250e6ad691eb214b07b5859f253456c9fe757bc9f38b6d63a7d5341774dffc1d004359e88987ece05989ee9ec2b33dbd732a7ad35a267fecec16de07d321e16241ded0ba67d34b99a11bcafc577cb49ecc3d241f57eff58ee8059e57afeecfd766232a2c18cb06cf22d5323ad3708c77bc7fcabaeaf94a2ab3ef4f58d7626dd513931f69379479b74f081543ffbed38db4a61bca6c9c1cab30e5e6c07833f8067b3c92e216c23318d7cf677662da646b1ec91e9459d7367c03a59ca7f5abf317cdb67840fbb78ae39ea6920d49aa93449568a431686c9c8028ce29489ef977c7b90fd78fc58f09b7403f4c0430075998a41b7aa7d18dd335c3ca0a47d74f109bb9524aab6a57cc019ccc563bdbdc5d00ca097cda449f2e92f0459271939484aee83fd1d51f6a7519fc03945818aaf835a1ab726faed994b6b223a8341ef1d09085513872a2055b86d2eb9d20e986cc9a2724309838967271942bc78c68c6fcdb55953fd9da2e6c97d3791d3c1942967fd82424b6f3fd4a8db88c4f948f385fcc69934d1a195091a518b0c7e8fb35c60ced3fcc545edc9929c1a8ca165f59cfffd509ca85ee81d6aa3ecfda056b69b9defd0a19795dfd83c0498831763c29c8316912a55e45495cfd1d2118a2b6efc117b744ee1f29711a1c754660e570429c1829a5f8869344634a9d6b05b6c9644b61aa8e94985ea5ef59ef5a67b54f60a4c450b850a1b22391df37626ba5c8e2c0f2e9ffa14055513b47266fcbc83b94153b366864662646f9068d32575fdd73bf699ae90a7b20723672180492b2ea3525861ca0e6014cf98a8c9040a470aece911cf3192737d042ec851c9665b7f8bcd830040162c594b354234f1a1604ddf8c3105712f4703310f4bf5efed90af739c85e4bb8f55328f4cef5f9fc3bd2c11b6a751bdc96705af171f228c473e2bed244103ebc02682c633b3188389cf8b8896f0c847b2e3e460dc3aa4ca80f52812517fd9375231178dc73c78a44ba8873b038d030154c9faaecebd50f1da10f1eb7f69729554025e88dfe9c1cbd137ca9e1ea4aba164863ae4b34dce136292abb78c072ad94a57dc5beed0dd8d6401f7b527722b7175316165f47b75c0b072f16ccd768e90c8c45278e3a91000192abcffb4d21e8189852f8a3f949282b618cb34ad5253d7342799692c3daa5ad070bc006508fe1af4b74ae7bdeb856141f93e514cabc3c834c5ca1b8755e0b7d5ce1c6ad6b07df4c2a0e8982fe91c5c2a67643c3c3dd2549a573bc14c6b22f87631dc01d4d3a049bb68de86f5a04339d5a7c9b8ccd68c7013629a2f850cecfeecdda5f4ea36449785c0c2a5bf95b437e873a5ee7fe6eba6436eca1ae50c1b03f47d49325d2dc40efe8592cddb7b3e1b45e3026aed7aaf5e48da524b0ecc9ae23bdaf29edb767f5a26a331bbff04ddb492f8a64e2a83836d9ce136910aa436c85b94161d95e43e1658323f45ee2229960d7e7dbcdb3c42ca206850f93c62a62295a7c638900b107c37de0811c8840042eb0a412ebf9ddcd37589a3303a31c373e45d79aea6dc1f39aa54dea88066f3fc6dd96fdbfb359b780441d6cc55042f562ca2bfae51fc854271595351e4b8f33ff5676e362a6125ae6948933698b9b1a720353fa6efb4b1b62b3edf9da12240a9a401b47de63744d0c05f070c83f0246c98b92ba58facce6bdc873f1e23817988da1232887cb26115f65fdf03f32a551faae98d1beb2bcff64390b57d82b671887820715b043cf87728889ea8040e0cbc17f8e1f03d6c2eac5d8c1de7ef8d98e645d383be6c835f03bb300d58f102d629c47a85f8777999ce2c34da9fbeefd3659993b93fca6d8013323d4b37ef0934e7a53bed3251e78b36af1fc4d48fe3b09f6747c1cb5d60336e998aa2fc593a0a9041593b79d419f394d8eaeb4cc6981c2c7d80660134120b7091decfa710bc3ba99cb92b8f0a090e39aedfbdfd6ddef42ac24c5027b50a1c4a8c2720a8e51ef90518832ed4de913b6375c3306b80a7c065e1844f8faecb5ea5f55ff1e08977741f0e9e2fde7be3c0108e5afa93a3c70ad72163d4ea97027c3ff2aea2f120837f41f616b48473b79df8cdd0eb8892adc2f1b17f37980e326e1ddd21085693bde25b9a3ddedc0c0551af368b0b88f98893468708738500c1b15f5ace015796b38cba948e7f533bf719c3ae414f95c39a3841e5430f93524c3e38f2996fbe93ade8dee9fefe89b8e1e2576d135137f33e5d669ee9a56b9c61e05e97e61da3db18ae7bdf239fa96c8b4e114b97f02f430d02f87ff370f51fb3890ce9ffc9a58242d990a93e9584d2e98e2d9a473ea70b91fe43818552c6c2cc5c1a2b02ea354b27cf2f990ab36b0da8b51076e7c3b1639c7e81852c255b705b96df329941dc49ab8fd475f2549125ed27d70f306edd9a4eff8b79109506d9ba3709bea94fd03fa4b7924dfe3399aa09bc7342eae1109cbb5bbe4002ce11e2b38fad73f2abc6c86a7c521a1ee8cea929789d39ef76624e300ccbf422325fe99b69efb38c3fd420881c02eb3e4d43d3ccd6814721fbfdcd914a02f2de106320e7ada640e9668a54b5e32480f1a9da13411272aafcf202d6f3c96578c111cd4acc894319b27b94ae244d70ac21740f12432251071ffde208ba19e4986e01066f70ea7f67d8c8cc78c34f0378884362379fd124ce5d979152fc22b2cf4aa630d5bec3020204574dc975f7a1df3fbd243b0e934c6e535231434304ac4a291f5a2f4e725b0648b40ba8d9843cbd87c4f471980dcd35b18bf4e09c9e21852be7de289303183e7575f38444f058ecc4cc6a491cbb3bc0c2b875da9c8c05d66ddb09e4c2d8b5cf6efb1dd3dfef0768ebed33a9c4bc832b2239d0f10062cda84041b8882da16a23d1237cd4f70288638cd85330e97aaa90800b84f05eda706f012cc442f3dc3beaf8088b1f3145ab54c3f9a9558b23bd2f1ea4883bb64e8ee5f38cd25f566ff570988b7fedcf25c69701b1f48c7d96c2518754996f0fee2ea6f9971363fcf3cb7378add722801b5fb731e0a197e04203720dff274eb5ae75a8b91be936d5c2e0d1d78acc6c16dca9174097a85ec3108a03f1034f1aa45e0022651d678d0cd18a04a84d43d068ee2566f452325662d274c2e67900a85ecc0476f1f052f9fb2913a393f3f2c0a6118939084c9b9936134881acf77de67f3cc4b41210d4c984aaa7c0b5384c8ed97d9e61be5ad2df0e889c072f84ce64a5b875e32a7ee0403d843dd2f56613555f9919d3078cbad61f059770655b5644dfe3c05bbaab56b21c2a20bd15dc85fa56e6517bd343097f6bef4bf8e710adeeef020051eeeba7c10983d8e82d474774b052cc0484504a1a831a1a9888c3f7128251b95363f4b4c07d5a7e89ee555e1bf2b1c4e9c9b030715e849369e8c3956d9c654779ac06f07c1ec3879fc76a401a7f8aead108521dea63a252fc0a453387c2b2e8a5d9c267c538ace8fb66dfa9b21c6dcfce0f9cad69f2f56c2e15c35ada0937078fa86d689bf132f599c3ca34e4256875ce3745cc8bc7434581f9e66683e999369f5c75930980e367d99dc0340184c6771f4d0e5a3f9c7fa2d382716d2298c3e6029208c50b3e73b32dd4a0af8dc71ae9e85fa03c8ec81c56cf369eeb60faf7f9c04f8a053511dc943332018372729fd368840b925e73777a7f177018ae09ca095ac19eb84cc52153fb4a61384cc11059f62a2728a827c5ce390b280cce72ced14df6eb3bf2edd9892b42a384dab92a9e47f84ada74a2588d73a0e0d645885a81a08881bc6da7b89c48a16f042e18b07c12069ac97be7bd49740a5e61e918843d7ccdb41b41073670b798a61e7ec5126b60bf443b768dec77cea7f0ee89f921f4e20807f58037d0a9bacda27c10c385889bac0a0838a6ee245f69be64b956a368963d4ee034bae244579a5023b7f34a1c9eb63416204ea38a52d240a0d89d6e9ecfe38661209181a88109daf73e3e0d1107921c9f235e0c34f12db0134e090bb0b7c3f579e3e05caf931f31d3ce2b905ecb39131737f3facf5b26f4fe7e6b8c04c84a014864cac690f9189fcfd25db6f85b133717b483263b4d36e59f34b3a3d9103922c5cea5349db183e881c3ba97d0e5091b27a9eea32c62ff8bde0d526c633fd08dfe2a581d9f764ea896c77acdf4e0790d942b9aa8993ddb8e923e2d6f5c4109361aa00579472ddada7bb82390c0b8eb651ff5b1bd2604fdfe5acb6c4767ed8e057c01f34ae6772e8e8b23fab3cfc661474624fe477f4960cfeb884fa353983725b2b2b4e94af1a2c939f8f24ab8ee7ff36adcaf33b46eaeb29427c57c49ee3431a027b635433ee893538a2054db5641063858146a0f7f1ef039f70c0ecff47e31ffc53da337a302aec793ec52a2c510cbd57f2521645fb87a4fde6ec2af2958d13d462747a9a5a86fa9fa0b99fef00f829820fb3a38f2a23c4e38a468be9683ea87429b9abadc38c4427cbeff6b5ce6db791fb633f888a2ce1501360d121a3529f1e5e54070a837c9ca44efdf4667d6a7f2f235001345ca372c6eae26857ebe89973aff7b17edaa29d05af280ef3bc3bcd378f90d5cf7ada6955dc5ea75070af06b4b788d39f9b05668e3413db5eb15073d03f8afbf43871f6e0214f9b525d374b9f72c3bdeb6266c88c5c92779b2a27397014158985ccaa84069a064347df1c1846fac4d170318f5be1e5eb56cc61985bf5243c3885e1cc1449ab69f31c9c261a61dd8a2f0559c7bcf9fad0cb5ec185b3c944aeba2a9dd3b8cd83d838d3ac641599a72c89034ed58d113348ab64adb0d26554cfdd40986c7bf7e1f8ba2db85d7d5d3cdc49ac9e2f989ed13136a3f01a7ae460786347a082a347a9454141c718000c458a4c5febaa45a8ee4bc9eac82f31602a47c8c13d94be9e9cd3e741c71a6e2a90687082ff2ab3fbb5cfb09d95ed954fc2b02dbfe41ea90b06cac6a69dc218ce6c6193eabe4af365c0271ebde7746d47474c385541a698baff630a7e91322a6e425241a5686d6f0a21333fe0a7e87e1dcae29fac9545859b7e91c23ad042df0ffec48b03290a4f78f080e59e3011f13c1325dcb1af514eb19cf4159741b4befe620486377f5f8b0994d19991199a327825d5ffa319f852522c45e21683e32bae8e1304b46964562f0944755d64b45b8e8ada1ae86993fc95d9aa179bef28e74c30e2983628a85a36f4796315f514894bc0fa33971c25bc3fdef25ce4c605d72fce41993991b62f35b74d2c6c9d91c3463a35b3f384a57b6468f05c63e06b711cb34ce621d7e989ed212026c83a9d71d8fe6a211d7205f428ec50ae048e60651cb8b776b75a160b2ec371b34fca664348b5151b1b52a8fcb352f4dd2c264aa13c0f973c62a28d39e031ca875c1f0413cb27369a495baf622b519082946dd111f3256a5a4b316768a9d8da1b9e6606e279436b4db6781f676e2e51f0035944b83b796c4f4150cbd94664161873cfe8a8be4d6443f0aa7e048a08ce2f736e23df47550245c63c91bf0c590b3832c9acab6dae5ee7bf3f210175e4c9461f75c4ab93d425fc63387697c7d70ee5d9a38abbe986a676bed2fe4d2750fec5607418ebd0cd82e36d93108b9a8e55c6ae0fe34dc6bda482e31799fc2b095e332279b7dae068c8af9b01069cc3d1bcd928c1e3c815fe98e4077aefb810e66aa40c471f68375e60f8a496ba81edb5692b8575d7abc8c0b5a955dbe6ed69c7781343a891a59671f48287f466ec02e3bfdf9527290d00b46bdd9f84dbf3b703e55b97b9f72bde3b1dae0f419fa40c0468fb727e090600ed503a0162b711f23daab2d8027c75801e12355686594cb943441b088dce43c582f53177a4f119e97c02e9a3e6155204199c80832fef00b046fafc8b1bf4f56088ee6077f6e73981ad5a235adc22d79ca70b5bb6c3918956e8d27aa39cfa1876680302a58584627516896c36a2666db867f12074276f985ba1267a696603ab7aa25b2920b204c621ae740d77a143b0a95a6d264aacc87014a0610c1e92a695daedc44192b7c893b4c16aacbbb9e12c9c3f3e722d617f70f4a5af6df88003aa8f037645b70c9ad9802e788223153dbc3b115ef5ebc6eb1f2536bc8c9fc190fd565150ef4b2f46b79c4ff7aa256d8943c626631a738fa1eb6e7327a281b7a4bbb53fc340ffd8ab47298b52547a04aaee5d1106c81a9e9fb1b8ffc16d8a40d0bc83650e42485daeccdf108f363a983322a55c7a5133483af8609d56860e1ef5c78fb75e05636a90a3641366dfa081d836a88e440416cef8875504a3c5147ce98cbe05528a1e8e229b611e3bb1a28341e47a196931ae1fbdbba9a92ceca542caf4db7ad4a7dc99a58ab8aa0d24bc1429162bccdeb9cb30530b9e5cd3ad4d1618cb29fe3ac0726c71ffd4b19cbf61e1c8e3d0867340e6ba57340502207589c7dc237a0409bf1b1fc6520b372e4e6965bd5def18342a8e8c907d2abffe08e5860ba01de9a2edfed52cf2432975a3eff07bf3679b34e872138993a4e5310a2b1fe5bd48bdcc481a8bb4143389bd73a7e7816290b33b9b609301ea29380fe44ce498a671954ee342e55ddaddf8d2cb15c4a3ed82ba5c5165854dac4f7fc82f82c37cd77022438a517fd01628f515e4c872ae25d8af1d07310ae848c36651d834fe4d1fb8f68fdcd2acc2bb2fdc9bead38713e9668b520900f531cdfd272ccbbb1b72e6e677aa66bd538c4f82eb3a1d5d349febd312dc577458ff8d397c3d4883e21c8f6a30cc0918db0f457900cd49523b067973d599de240ec2cc72c0b7ecf83c1ec4e1daeae01773b839fbfa7a38d521d75e18d08799eb645917a6aa95e548e0d19e0fc516b1f962da368b9dc33585f4c6243334d721d31ec8e8066cc924152fb52297aa3099da70d27aea79f482559c47d273d07673e0be0178d90800bd57e979095688ce5380d063713cbdbcc91afd044aa169e49483034fa0b83effb162071b9575b18da5aa792310210cacd5f06b5431d8392813aaeff41b4b52c0d1b8a0fea18b84e0ef542aee8dcb2272744eb6d44849e0a1724120f39c9ce3f5e92ce72f78cd9ded2eb0368dd331e215a619e55ba41ba651779ad324bd55b3eec020df8b3c5bb073854edc765d2cb7b8626506cdec7ceaa61338df51e137d69575a91e9d3f9a3a1bec997cea751080ca195b19483ebe8ed1ecfc2c7af3ac0b33693f105a12def795cfe64f0adaf2c2a8cc04194d6c88351fe077f0d10c8af5cbe57d912203632bdbce89cba418dfe6a2876b287bc5fa8dbb21ea4bc167b19c92973eca612694972d2f9321ab8b50269c3ebef7b3310204942ab828a2087744b445c042eb69383b451454232fca11d333426ee9d234af604529e7899048f6a2490ed8a78faae832d20ce3f3b31cd1c08eb816c8654822aa6a7e53bae05db4ce2894667a07189ece585c684b0d66ea7f3ee24a565012eb132af3686c9cd777fc9deae96c5347e292e239509e9a0943ee7e2a677d0bede172fa9ecc2f940e8038c7e04583c1887021f6a8910b9bd40bc557f6c729dc6df6b170075f0e2df1a62225ee0ea55fcad3aefe7c68a56f73408fc718f0d5a28aa3ddb8252e50fbd6986788d8015e365dedb90db8873317fc472f6530aac84d869974a0cc69be77959a3529836ab2cd5ad13be0b7b99949ccdbc320cc3b07df37f784c5e57f4a1fe5f38e3758cde0743e07c7e419d97a26c9f2d0503eb98aba575e723ab0c22f97c7320c950f9f2ca8af1208814f391d1beb29463fa68e3e5fbfd035b745477ec012c51cc8269d638689392a2ba7e658bc84c5bc8d0a0841843e79795bd323fbf68259b0fdda0c5c952736c6f6ac133f5ae4d5472c9e3893ff81473ba53904506a34640e3f6b5cf32719923eb8200607aacbb821d97fc6bc2abe0956773d1f6c6713ae40cc0a806360e838d5bd9d515ad6424a6ff3fd04618fe36bbe06ff8ea461fa7589abaff81ee8d4507eb1abbaeb771043d2989acebad62c6be310eba069e57f0269b5dae895e26498b4b14bae39fab4625f31739611f7b6024a43316c223fb158db6b13b87b21166b7d5e6f7606c61a433d7d64a173c696a39ee79f5b3b777e20ee67e9f01fd73e2ce587bf0f6d52c77e4ec22b69481134d07dbd952217d3e835dd220d595cfa7c24768dd74c8a79e63391d055e1aa9e8ef04d0291fc594d8cbaa596df39fceb4bed1ea6a474f4550e15e377f9c21a6eb31a97c682377ecaa5882fee86de1b854dca73b0d9233923796d3364fc3c743ff62d94201cf0f30c5cd2370a7454d73489db1c13c76c5c0ba8d18984cc1515e005d13aa83f23a670fb62650a576f4c9c298357085dd8d321660a689aaef300024440ed6206be6c034dd1b63f2bfd5e192e592d1793308e2d64096bc57736e9b4433424fcea1120f7b0f90cc1c30d507e6215a81913301e22eb1cea93af7fee4621a287a00c2588208ec024d5196ad82f34a8317457292fda110b7fa235a7525e758365f56738fd180d5e1b48a784203e0554f30521a834e837f2bbf44a7db6b275be2e2d185d9dc7ed7cde7478ea523ec1b987edacea7de36a9728a8642729adaa35c093f22ea7aee4427e7afaefdbc27487d06fb836ca7c7944ee6d0041cfbfc9634ab6e2ade2c13a823cf00d603569a3d0c8d8b251688860e9e225e300ee3b5699d0fc69afd6e6a95292304ac4152311359429853824dc5703824eb29309d5781aba626ed73337dbe189629248a99f28482f4157be885429c89292ba5ab7b718c098b47bbd57928c8c0a1261602ea3e866d3113354b00127b2d4d123dba66bbc728f7902440a3d89e9c4a0ce39b2aadf6df50dca410d5ef42e3c19fba01650c2b1c3be5c5ada3aacaa78f06c4f06d6b4bb887818dba24ef35e48df618b74d70cd5f32216b07d68eb88c621a148fca0df027b9234fd6a8dfdd5c31ad44d622353730af211761c518058c379ee52fe5e48250e885ae5d70175d768f9b13e40e4067bd75632c2ac3dded4587d9575b98bca7506061b4d270827ac7fd859cb75b3ace21890ba35a4d0a69e4738beb255d10192f64bc13ca2042393fba8398b28c59ef80245036699da8fb95228e29f682fa549c4637b8f2276250e923faa944e10f947d7a4a821be89943fb9f1911fdc38b430a3f3077a4bbf5a8617e34f2299886a9441466cb2af85765f404c837861ddacaca14c32e31e5e5bdcaefa9186fd774bba26863fb2b8becf7253ab7ba019541520c81b154fc9fadbda940f61cf42169cb27ce500d9f0edb6f3dd8217e0af71b662e4426d2b3b0d7f37e935a94d17b8a4186ed544d6879ed22edabf946bc3a5f4535807f7670c007d52bd4f466468d947138ed1a847187adeec84108a1ece2b01e184840a2a24b07fe60cb0de83dc76a4cf99e3f4fa5b4a34b340fe9312bf305363bb8d97ed4973302585c773d97e4374d0e0d5fbf208d716a77d44d31497f5e80893fc2d044d48f91821264968f2d9f6f19a2a3be8421f4acaf6459b455e5d76610e443ca66f9fcc659b4304133d5414a8bb875452984ebbc5e703328864bea78478a5ed3897a296b0037d90d151db3f79c19c64f14adfe8539a5463e2548236085075002cfdc6a543719dcab52f9c71a67015aa0a0a295e5838965a109e79b2975158e2567c0cfd11e51fa9d3ae9aadf2e1f9bfc76a0d4968ae2ca1a2f90fcea9689578b8db443086fc17ac09e411eb0c25aca4ea9c58fbe81ae7c62763d02026f1c53048fae4135e7c500b5ebee3e599e29a735abcf5e8ae43be4878be066f9c37f96c727c65f7d525ddd273555e0abdaad4a84dde0f3c27ef099b872421b1dd1dc53879270dadb699adf06ad37c533aba059963a73dba4ac9517ee9e526a47282ce14e9de4fb2195fcdbafd5cd7be8fc008c198ea2f2c02536cbfa0c7d9a80280ac6fdf6743a28956477bdd874a1ab2ddc4a6b850ae75d5eb9a10a5a891562f62eb6d888b38cfe879a45efce135cf1397d004fc36a1a1809255c85c55140a610900de2a9898dc490df6e01c189d5dcf7338e2851455dbab25d94fe88def9aabef3334ee4a1abef1bd011b49c733da7bf1cb1111038889c82039cacea16ce858002eeed57ab682055cb78a514846c29a8387c863e49e31bc7c0aa4e1b83e52e1e40de5e3eb52fa43d61756022e82b26091c2c5171746ff28b227f119080392384a55764e35f50f1518384cdde3844d90da4b99eb4541679e6d063eebd9b7d75885fa6d73442a94be876ff3c8c45e67a70e9947993a756a445589a46a3e06bad3434a89519f71d3ea63c8694c583420b4d498e899637102b137e0e30f6c2ee779584a9655a31bbfb3838ed5f0184bcc8e1d74282245bf4eeca888ed708ecd35316a74bb29979996000f90a6e2e68d6bc75d689453be5b9d13ee6d87bc647f52fe98c51c1950dbbc015e70be8b4f80bcc82da7d5bd0dd9d29f4726a793bd6735c9e56c7f3b233e490325c5ee23a66441abc2bf3968f4e712f115a154576090055a53250d52f0d16536cedeb34e389112a25b1a0cdd0cde0eb101a37dec22546157704f411a7ed00175555aead7f0da932532adf7e8512a3dd3f5bd911eec74a1a2acf8fc699c43cbe7c54490b88d3ec4f4cfd08759d58c27b380d05f28b6d215afdc7f79a745dbe3553145e300bb017dd2f0a1d8bca99b2a939229c68cae867de88d8a17dc0d26ffd6440fb183516b384fcfdbacf27e2d0e6fd05822c886f872764ede37ac1005405f918b590dcc916089fbdbfe3266f7d0e8c453fb74308a736ca14e46e04e1645ada74309ea90cdf9e45fcc2b6b80fc9e420dc8cb90ab4f4f83655345fee478cd8b020f500be19eaa8ee3e741fadc57a06f81caa08204d5f98356eb94bfe786eec711683f89a047d04a6ea3217566f3f25efc9ffc39362e0dd5e37260139f13dceb699153cb33a16bb246ae99c2cc2ff6223b309ce204384bb946a06ff82c4ecdcf5b5ab0e8cef268af51d8d4b9a1a46565785c5095071e48fafa3875e2c297a01b04b6b8e2bb56a7bde31d60462e1e4e28e4dd360b762b694e17c96ea150339b417c6dc70166bca55cc2639a71105366537b56d93a81d1594215b46fd0d13c12b099813f5a176849660b686f5464995050581da69763eae0871cee2dd0576b62808af46b46120d2d572d049c2cc2cb90cad13860adc57c5160bf3c1265ec28f1741a5631ade49c354cb05fb8bcc2bda74e625c93a4e5a35d52f48de12b4475bd4da1e824207d520942d1a684f4ed9e6ae958faac307fcc16013c9075bdc351d7daab10d728efef4dd1675b94c4d0aab920afcf7b86461da940a8856be0485a6dc2ea3d33e246d8003948d2f0a5d439b7a8727988d3471fe89616c69bdd9ed831eb19ed9a95be1d2d77d6970ced3557cc0ccb5a0def14171f84d2e50cbd4d6c0f36782089041fbe7af430408d1579ee94a443aed3cc782a96dd1e8e6c6460b261026f4d41bba4f93a14a18af6ffe9830f2cfc341330626685c17b10c592e7e4df47027db918bfbfebbbfacccce51177392acc77ff964e1698a88166408d18600a6220f3b47a97665e2d451a2e6a65eefb6826fe9fe88d7585177357aa4cc9e27d7791bd1763f26ca6db1fb0d1b837a3304ab5965ecb647f706d71f6272977bcb7ceb8e2354db9ed5bdde25f2ae531848fd707f8f0ba7adf7f099c7a454dee7f856ea1c31b06d606ff97d6e2454cd366cd78bf04c6f22c8b29e440f9017c2d9181253d76ad9017c0c7c9f233f404c97081e913b8310e937c9eafa57d53b85d07447a81866fb17855c8ab2b8781466df59733d46a4cdb4472d77cc675bd2f074d9dc80d7b06319f14f4462c48cb6ae27dc625ea8f2e8ec99e9fa95c14a7b0619f82a1da97092b2b1527b77aaa7bf222358e4012f0cf65def2a460ce3213e77513c405587614418f68e66e824874803311df3f999ab7f404f03c9230cbb6773e8c8c4cac9ca0c603ad1c453633f902161170f784ba0f3313e311d89620cfd1fe5c73967012b81de8d1860266bc739139895e911f38ad04664f7ced96d4394630d4d28d23e40a6acfad442ef3044ea219504bf1380b1202b044e85027ba2f150b832165e90958ca59940ccd552aa7a092cc85eedf8077c908482494faa30d0da37ba05d8f12d69a0e0d1da25a451dd4720f0d25ac863a3d486be908a1d5a17e35a8aa0c89a0903af060da1efa8ec99c4065d726a22766620feebe9e9e74d7fb2ff917a279ab3e45ba2f5906f8d55d518fc00370f112a370edbf3f8713fae71fb2b2621786c335d14a49f338e75b99d6e514616244ec244a66d9e25dc3794f4b5bb52f92e42a309af3aab04ee66987a8391e746df02c286aa1005ffc7c1371e1cafc120eff56494869567b6502166b0293b089f3066603b042679cf92fffae2ac317fe16bf4b87841b23a1767558bc9ccc70c8d050ec6ddded14b9683fd28a783198366175befd99302c25a4e886b5f2de63122df975a704e1bfbf5b97696f6905cdd1b2b2035814663c59039b23ad60a8788b6816a7891e68bf12f2e299deb4b401d151425bbb06b4005d4f8c6d187399adf053163131fbd744851aa9deb41bf1b199cb9db9051d13350d9f75daf35e52bcd8a79fe505fafc960f09e54f554b3c2c55f68eeee845e9c28506c543c7fdbd95211e55300f1aa41c2f45dfbf337b2167c36313e879aa8ba21ea14e38da60284fb37e68eb58fd5b57a2ac205976344fb91e0bcf15d446bb32d85f4f70dbb6df52428289a5172134c28f8cadebe84984a4a6c82017f34a780611531e3e1a491fea603c54cac59924069ef6cfb800d201266c45eccd40e93adca972449c8a6672337b2c254f6e1e1adb857845b4d3322c6376406da41e0c174a002a13a5f0738123ea8218a1ca670d87f1658d0ac263764c48e23c3065d564619e05a68632052688c552a6f15e77cfc9f0652cd214f9c0044463460fd4b562031d842da2e40f58584614e896d0cedec8a69f4021aab53b942b259b39e8c42ee3782165f4dc43acb1332fc580fa39df89b55ea0f931238a557c5b6d88ec93291ef0eb721ac7d7bb33c74443644606699822e74b32c1de3fdf2530cf44cf1d12cdf76460be0cf8877a50c075648b7303eb9d911e959511b026ef40f213b1cf5f56fb3bdc44b66a49e4d627ce35e7e52704db1a19fac9350fdaf64c6acfde2c9b0633e8ae8b6be2ff36b27ac43cac1cb2515220e5a87738972f325824fd22e6b7f8885c69e3042b7191d61b004364d89ed3ea65a1ae340944628a3e6f90c4aa194bb33730354d7711b16ac8cd7fc94c1b6589b151e3e0d00d25346654c9103b9de31166288828e105e0ee7b56dd7121542ec7c0345e9014309f9e5f86dcf932b7892805d856171551ab8840559f3e7f0d24c63d900163cce1f65a5873bdcc6546902a1957a68bc1407b7e52e90cf120ca87e05d41831339834858ef80db09fac0f7ac672761bc048554d8f1f9d4a03020d27b1af27e3b2b2f981af778ed674edc8165745239177d9613f72d6e5d65875ad5085121fa920b6769a6824485b8a73352f602ebb4ba36c452beb37c52f0f6a8d9fef3778f531d8ca712100bd19d36bf71ba14dcf2c63060af9c3d73e58dd67da15029d97a6cbbc746a84e8600913b1320f4c86dcbf278dde947f9052c4a865af96426c0ab3629ea190de96cddf59552e86e151a200c5ce043535fe10adaee7a537a9cb3ebfeefcd0befee356719664f43f6fce44de17865fa44b430739caea90d49129b841e6b9840faaf7bf319c6b0ae631c28764c719196f3454a2619ff5db129616ccd6ea684491e83e2ecaf26d1a0c00203de21af9cef4509b79961b0c3836889ffaaa7eba7fe8588d691276461612240477e115add24f036438bbfa9d2aea987000cde69bb6a72fb2dadf4aa66ebe3892cd88c34840f1e11f6d92a55d90306dc44d5f2fa5810f5f76c848d2d07ad793b66137a1a9026e338aa0c29d87e1efa095773fc58f4324c506bcad8c3401bffab14a606f67dc5bcebe14af28a5d94c2cc2a112a4ff156a2a901394fed2889d0b1f2474792900dc0ce619cb9fc681d01065484a8e21402476a1cbb48ba9e7ff611d969f839b9259aad21b3cf08f6522a2a72fe6e246a9c41bb2c6a20f28341712ac448b9ff69a352bf0331921932c4be73e90d063f543644a96f76724abb6fc1c6ccb2a141a03b506fcb8ac4cf929ed51c084ef662903b9704100540b55a488356dc7f082e1f0975556658e008b1b15f165c7619778e5c9d8b582468fbedf8b4473b4b4f175aa128ac47f7ed8b81530b81123820f09c5524d56b03ac7826fa3df532f89b110c4fd818382b1dc74dfb5421dc351e03231a3ae21aa500a68583a769dc4c29e6254e13616fbb41c9315f02c13eaece9710cef714357ae4ae8ea14a27adb6332847ac19a906a97fe12f91d367911a4c470d6c7ab1e4778b3d915a93454a7e4e59bedee0e5e5a14dfff5a3cb6208798fe22ae54a128867ac81f9a4f452808111459881a02789f266b838cc1fe7be3ce8569d2d8385fa19449c9884342249dc2d6985199a74dfeded4ba3ad0724308d5bded47e74ddf8cb78c1d4ef631c1fb143cc168d1df724e697bb7da3b8bd3afd3dceaa3cb29d1c2aeaaea024ae9b9f529628010f8afc4130f8e3287688b8d9f2a823af8a18cdb4819bff816ed0fa95048cb5c6ea13fe848b468883c41a09f17e37cd1d018437d1df080f507c0c9822cec49734568e78bdd684bef6b8df10cde6d803aa8e45593e8bec39a1e80127a239975046f1b19c6c6702a0e8070eae103cfbd5515faca38f097102d22b09525c7a8aff0054f77eebf5fce9ba00a226cff44384d256d2cbf646760b0a421ecc25aa96d1647649435ce747df8fe1933272f44139f2d2f37cc721e013015885add011d0208d0795c3607bba3fe1127e3a603539c272d62708d52d2f37d880e3726b0ac55b7406c1aca5038d450eaffcb31c88b71e91104bfe90190e03c50dc485131f160bcf75811115f3f57a3c12c12fbcad9a25685004f81b3e4e422a6101282afc7c3028b86197dd7dea1a61b5cf1575e243dca003a50930cbc517e7d8538606233c32ba37f7d8928aeeb5d7320c237255054409c105c2f75c3c60a36d7cb7a1632b3353cfdab53d43d73773e9c36c5efc847e0a819f2a340fc9e49907797d535e31c8a651ba480af015c98600346e04a2a97aaddcc2aa47af70d8f828b86f11e1cd4712dc4c46edaada701d62dd9924d7bccdef6d304be2a85656e12e175b76a6f353d8f4c6b0a941f2351f619c5e5740e5d9aa1dccf99a2b68c5d50f3ba8d167840e3faaf3b1dc4d0ff979d8e2b090ec916845229d18725b06f597b2f2359fd06593c9057f1e8a8cc475c0e4a3fe745919b721ccca99b888e444423c3a1494559e9345a2a901ed7096db378b5127447a31c518acb7fccb20ea53b613575a3b078b9b5eaf24b045f4ab110d996eeda9ec15d1b7bf89f4d548347849549ef480ef0ce0bf7ccab20411531220a20d3d77d876e105e02cebf2f129e906106912fa1be7f020c99e30d35fcba4a8b5ad342a228531d257ee47b67bc45398437b6826913abd58048399c490e033b07a917075800359e6f8949060e180a35b7d82574152a1e9e7998391f6ddb5b157003f17ad50c40d580ae3fd5a320b30da6a9d5213ecae2fe8215f7d0a26acb85f0c772d5603438a9d0edc7cf420c67bdac978d75ae132a08e518c77315800db2fa4c52871450a2f44eb94d70eb3eba47c7a28e910077db28a2ce404a29a9130c61bdd24cfa06ab6df2d688d7bf5a5a8859098313a4f057165e8dfb73fb9abe802cc8690a9141a6e5dffb2f508720ba006915f471ec2acdce5aaac040438f094b0ac947786e4360e5135cd229fe811520863d45128bccb41413d17d32f97689a1189964a009e0caedd78e264a2341b0481e5ac9414665501bc386e657c79b371a736205b770a67ef25174176893a0c9f2709e14363252a92ec2e10918a8bdda5dd8db433e0241a3cf1062f064b534a8c2668dd5b7dd4b2c1878a9f9c510b55d2c990f819d43e438d8e03c75ad39ddab498ca308bada6d51537826ca755870444b49bda83c51b4edf6283850d3465a46b7a5918f296c0b5040934dd76874f376a83ac3bb74860ba07ac7a00b267edf2638dbb85cf293a281fe73d058af830aef9d87c0170fa790c7bec95eee2595f64471993171aba06dc0180a89a2e29ddb6674d1054186e82d20d4f8164e164b078ed49772b78080cd5b955ae49f7bcdae20e45320a4be59874e7f591a37b46303563e99274777271316c9006d44737d20811ad68ff462d7cab94318d33e936fa4cb07d1a5c3d490c72e7e411e18f0647400decf95bc540b27702313aaa60c21c28c608930ebf40d629b7608a4fdd962fe78017f0fb0bd367ff2b258af13bc4d1e0b7d625c90d0213cfeaa9331ca72a1bc70c910deb150c0f5574d10324b6c5b09156eca40582bec81027ca271e4b21d1af2a4585b337042a3ca8b8cc7da9d808051db1ee8aa1c938894e54e2d9e2563d4a59d467e2a8a357293884617148c947c58a7464ccc57470228e7deef8e70146530069aa49fa23975f9055aa6b735a27ce4bdb0783afa24a4adfa493e1543ce6a567aff2226ff3b6541860ca091171a33b7ca9f4df459e8903ff845a9fa4d87243810d57d7ffada6bb8f24d247cbb36db877df7bda28ead8721b3ddfe147185140a8e9980631625bcfd8e55893d103a32e0adbc6877e28a3aac50bc0e86c65378cd44e0d240f4b78566d92f83eb0992e984cbc4f286aaacca4d6dfae22dfbc0f543425fbc903478dbab3343fdff9a94336f154c06127f43db2c87d3b0aa52e3b20639132b56bf01d871ef98df05966afe3cb60c99d0b4c785c53480e5c6d5bd3ed563525cab0e6ef924a158f2eff7a27323fe8def7763762a41d9b92c8f5eece23cb596efb5683a44bd490cabeed49e87ffac64e245a3e310e50547fa21a2922e67ea2dcbb9df44828a9af319771338945b16816bc0cc70a2d8ebc166d4ede75ed2028af56841e943da9e4d0d5dbc7520bae4c0de828773fb53cfa4ac80d920e1ef9db2216fbe7dfd15777833e24c35e9f8a8d13bf9e11e8427e5d2c791662e4c930a6f22f54e68538cb310bf2f85de14b76f7d2072a4af3fb17c5f4d2184ab904ffaf6353615e06ecf0c4c83efa8fbf4bcc6e40328abaf1403c7885a5dab800f2083f4e7a0d415bcb8101e82050a2f3329c82a3ea96e98888330d5e9dad3926189c03e8cea2f5283ef6a17c8dd2e40c8b328a12981b867596143b095abb6203141e85c623d48a67ca8107ddb7b3278d4aa0d9e2618f7d9edcfebc594d5c8045e8e49668895f1b1f54feb519a6d3d2cccc4e8238bdc354c92db4078995de9c5ce02d49f608516c7c4b4537a2986db55369f12b72b4a5114139829fcbf10f08b777b494fbe7b5fc6c8c11f4758cec291b9905d25ed3863514fc80470f0f82f129bfe88c28fcec4333686526f389f26cea3a525fe887cea7cd5d4a70d44d076711067639138539097b5af5161f67cde416e75ce085285474e485d1d59e2d003bd3c778a78b023c7b1387097dfdbcfd6625f16e435748c521ea2a4f452de0dcc18bde63533bb837fd213265c38f953ff8fdee911abbbfdaef2df8f5de6bd9f76f56fd72f1f2398b9aecac4e32e2f79541ffdef3105fd5b296705b925af93376a68972bd882ee81e3f4ffbf60c1aeca6743664f0ee5133fb38472a6c39f6317835e28ace65e7474e99465bc0c235819ab61ae6445fffd0159f2bf11da187ed9c9cc87fe10b67319fa6bfdf630602860985ad4784a18c9a36ff3f52a61ae28b21c9feb6a81e05085eee3d8c6cb9cf893636e5d1fc44664b55f1005f35b4eb9ec3ab93c4a6c01926c85899d17a437b7c7bd745fa2d36775c80597d43845884747f52720bbd4d9ab2129b120fb5d222908af7c17c68453bef0544646bc9b52492b6d7851837ddb1982d42c6a8a0a735b8a6ad9ee4e346bec0143619dfc1194860ad9861a8d5a477558952781b84481085e6be1765160240e26137f9fc3ab7fa7f745cbd8e9f97561953a9c02f9fc2d6a3c264e2096479106d956a69088c54d11655246cd93e12d4b810c33d930d9524926309874200a913b1c2755119f3461b39fdafd86d726887a8b0218c84cd78b716a55e10ff5764990d0870a58df4a8ac99ce847e9d9b5a61d1eace785f2280f460481a9501aff644c28660c685ed057d65f7465504273d4eaa5b3864439f23b1cad7489e5dff0cd567244eb0e49c23141047e6336dd1046bef4ff17e949168a21df1453a62a2101b179e2fa1acbd5474548fab837d4704d8328ac181b4b37f4e491b9d6c6821578fab7807c8d78642eb09535ddf778dbf7e3c1a8174a7fd3c86f90547da5004f371dc794a1f6cbae88effca0a6dc590d4c3f4b31c245e40e4d401aeb20609b41528bb8af929ba5bc4003972594b65571548e264a9af1f6eeb5c8722654d6d81b4a61aca5af2c42d5db597b8126a1276dc860342662f8228c1a0cd2f13c0709c6b8ec68e46f894ef28c775ee049d4daa253d8c78c2c2f77adaa869f68bf5c2b83826c181ae59b73954f131f4c3651d7ed0bb09d51f5893565574741a1c9e64394510d9c233b5128a06a76382daca3e18b778f8be8b2febfe94ed782f5baf445c41acb8a73a65ea98d8820823beac0a50afd1252a8641fcb8800c2e0694e185069929b9f8366450920591506b440b95ff6d931b818f6c8539c125a3032e493343c194fd5a506e7de9717fe8b43888d657d63bdfcee3bd19c3d021238c803899fa60d24e33b5c8263c434c44336a63a5b11713e9d748be5db1de927c7603757e127e912abd8fd42d3f9b6ddcb4ad2327462d1964cca8bbaa28ebc296a5de7746bdf516e82bf3065dc0c165457d3845258a6d819350495b2d3e3c303961eb8ff2460d9794845e5494f731c49940134a64fd5dd0a92958eb91ca13814d0c4ed78ec0004a581231438b66aa117291228abe2f585ccbf634e63516db435671f803ac3157005de9908284cf932fe373d18a8f0e0277312d5c806b4ab5f665153399fa7e1278f3d5a8eea451ed8c5d7d85fd4da9d70af5762b763caf15a1c0e7dae15779c285ceda55081391b4e35e224862593d219ec2d1835022a713958b709ccebbef3bd89e9d6111c6fe3a2e5c8d04e9485227396fe34fbacc317cb738451562f292a12ae42b106f7cccc1369990b95e5a84dad998e1c953177a40076759502d224128c2b9ea43c984ed1dfc09b6693dc2b4f1b8e399cb3dc1ceac3dbfad642a883023cea85913966618ae1648a35d4f4b292833e63449e42401fde88ccfb66100d462c9cee7ff211da2c09d26e453b499919936ee3493627ad430b2d9667016a6e94a65cc4c46a7d90235cf4a585bd552abcc841925b3b8b30b46e3cb7649c738f38228efd0747e7ebac029992b97c52654b26fd0cccae9e896c936025c1ccb9c4005ad5213ad1afcb13e97a60681bc50c0519bcf70a1c1dd77590a2b86788459a47f16a97909f78dc73770ae5de15a8364bcbc3e35b84a64e5e26d857daf71dbdfb5a55985579027e7a793593e393a33e22aa6959641305eb1227f43937b592886c52abd4eb294dfd22475b98e3a5b431ac678a2bb10f9967a02625250431087e5d13e4333f6c00add16799dd0f2943a1a68b032e7804d4ba4cdca1c2df58d927e122987c62091c633e33f5b133dda892b25ce8bfbfbb7520e77b4b6205a5cb0e79342f928f65420329477b4eb85c1d876368726573bd84d7d16c08351f793f935eb6fcc615d6d4472e7f6285f9d1727c6ef889301f3839c179a7da1159cf1ad7d3d4d576215e8152ec6fba631113dc69c03dcb95e9f8a06ea97f596dc9ce39658803a35eeda7880deda2eca4d81560dc0b80f943842fa54e81d78b2eb8285ee472368c3908e6c29a822421d6e434c8dac773a62e8237d5a99d2dff0fb1a805a08bc23bf9c737d53199a02e7f9ab6728433720fdefef05a79659f9798e8436fc634998a6bf77a0921268dec01a29e0ad821600066705cda322b0942b6a5fde571a61e0678d80ebd0908230757f6142129ac318509f59d04041249c505055e7d62698c7006d610f7572f6cb1a025124cff694152481f4a062a7435eb76759c4c8c60b0906bb8230e1addc5c45fa645e1b0543e1711ffbbec4468b207ac1ab1a20b54ff0e79a9412044469ed2fb5e1179d92e24be3dca770398f794f6f5ede03d79a7108771b1d886fa7a997b41cfddf52237610ebe1c029a8f89bb211554a488387298a3c21fb0d40c96909558920b2d0339e9ba249019ae390ffeb99b7cdab6d33a392de967400f7f024888ecbf1115b3033c948c52663a79e840bcb3cfbb1d20d631fd4ff50db0f3a745bf8a98c6edf330c72eae6f61b93ac97b93d6f55359ddaf76d3eda3906ae0e6ce2465a0818cb55ef155389602bc8e4757075e18b7269a264b321c00ef58f274f599a77127041754133c5891c58ca66c9e671e7f465a4bf7c027dd1f34182d9fb153816570f6f40e602cb2250039e5999424c738698471cde073df46a8173569c6db0076323ee137b5164db77c1a7acc560d77c9177401520fafa9413bac9e23154b274c6023399fcb301cf901099bfe0dda78f8fc8db75765d3a857e4bfc5cb79295cb13a7a8c654250f9d6cdbc7a326bc416eca002cbceb30f4529d9dc36d7ca90788174e5ff26af9a45764c0a9655f8d8dc8458c4b2af0a92d75f33a511554ad465cb3ecdfbd45eb2a5e91a2b74f51cd9983bf92bc2d32b3d4e034bab0479304e559e908305fa7add04b986404372cb39033d9a164695ddcabc884f72108b6d30239d4f8434e53527a7aa8a0c6536b86029bffb9228473df97462d41de9d5838d02f364d2240f2ff5046d4c611860c8bffecc35ba2550a2c447d14185bc09ae28bd59f2cd1969ae4d8ae010feab22a71f5dc4d3cdc5d424ef1393799ed5ba1f23e33cc93cece99bea6fb93f950b377abee6c80505f0cfd23acbaf5117fd54e5634e5bfa0a1f1208eee8dcd096706ac748005d7c64ae940dc7d72fcb849f142ca3fe936e72c84d4d521aea97dcbba3a74a099a8a8cffe0f45b52b09391413883eb161da7c38efe86989974d059c9f842a7b144f0e168349fc571ec2553d549f0de50ff478fe5ea0a24a0f106700e8f56ce22daeed89574087f7fa6b634673eb54980022c68aed03209c423a4cb53a81baec9e4b44f91bdae3006fcf61c9d661e4d9b342cb5a614a10a2d8fefd473c3ad56da07179083be44aca65bd15d999173aa933c1daa50258991f1cab2efa04cbaaa273c5584b6113f6016b8bdf79b8ce3e62fd28d540fcee28bdc640725b7ca1448d8fcb7571e30e91f5a6ba0b23fdded28858400f370178722f5fb70538ccf202e6beb26d81293227c3b651650ba9d8f2f3d61acade1fea2463b38971bae63298a6cb2ef4e84d9c8f21b2680d495cf2dae338cfcebcb3150ca238e2fa34173d5c6409bacf91eae00dc8c4a906e06f86a41ba571961e79634d0ddf4887f9f96d291e3da9aeac649266762213a347c88db941d3519249f661915bc24e82fcb0faabb0bb5ac30f83ca7f1e826e9828e7cc1bf480e710d66cdaf124e1bf8a214556ee67518131e4e7bd54527d50c3e1535cfc449e8f5a45768845093c60e9b9628c233c92bfc01747eb181a64d165776d0b22ee13418fbb1a172986b3a20a24dbe1165a637ad3e990f32a50ca8818493c062e1eb4c40a562ace5e5657cbe31424ec248815330ea675cc0a291ccdca40fcb5831a5b7fe5f2af6a52bad90ec2113d19569fdcc3ab1f045427462610610cc211b83e8cc62a3274dda7c07faecfe5a94a41686afca89513f265207470495e741b9aeae82690ea82c3c57d854766f0b825037652e87902eb676a0cff77cce229bbf0f5f9fed23aad95b55c13677ded6cf1be087137880e06bd0cf71b8ed39ea289a2329a98f48bc4c44d4c54061da9f21dbcb5776ae3bb7a4b7c0baae9ec94ece7ec8a7a8b80a19afea7da117b641964c94b9667717175754ac80d26002f3736e1482db3fc840a5da348cffe13308c22a43f4a11dcb9d0800af0b7c20d4640ec0b6cc2342af6490cf3017a6e518a198f1f2290b11ec50db7512961467c6fa9ec01e841a5b84fd1ae0abfa5e6992eff181899e142a1662e529f7a4b96c2aa121b97540aba5ef9cdd14346ad3c166c7bd18946a2fd8a2e6feea7e5238a2821dc0059b6078120b2f8cc6dfb65de819224593a4dd40cfa811a210f8073836ae4e1da694e72e342718e7dd355256323c11ba386bacc59ca502e401e3257f9c2e060ae047dcc4ab0325a6088708a6433a7fc00e2f0beb063b7578b381001a8a711a98a05dd2b591ecb05b464a073482db1fa11b49d2c68cf71825668a3ad87e2521673128cb306a815548d5e1ecbe66c0f74e426c55e1ecb0e12b48ad5e5b1a44b4280f10aea59d290e27059595e92088cd523fa2db62275624fff44773993fcd116cbde923274c58b0ab45e32c1546c034a158e6a5b30145ea503da7996d0e7ad34be5c6d5bb8cb595a4b27a6ce13003b19ce0bd01fe258834645ce122ae22511c5161d628bdcf0f801e33eb7bbde4ff37afda35e0ad72ffe8f269670e1bee17d834a7ffce22d3bc8de64cbbda54c29a5cd0683068306346421ef166a21168a21c68f6b70842ab696953f9439628c514a295329a47260be0ec373c498715877c55239b8fa67a91cecbf37dbdc5870dc8cb9c17f3e5755d908abfdb6b586bca687e8526ef69c150041507e45a66d69c2f9f29fdc04bed56ab558469eb59b80f06b3748e56b3fd3600ac71c2a9a48434b46488db33538f252fbe79459651a354e3885a8475388adc110f08f5262526ea80f3b15b4dfde533b50bf7d9cdae19f6ac76c55ffd9aafefd981fff07aa92406307744a6e668750ca47fde6eebb6973325bb1ea550c72a8e1004328575709872f0870021cb2f0828315e011c5ea2ae1d0831516ae6931238cc023acfc35e933c3a44fac4a9f137b29a7c4a4f499c9a86931238cc02b50c6cb74333191b24c37f33134326464cfbf71291c8ce2994c866518f3fa67c2180e4de09fd96233d8a20eafd96e2fc60c931ebb3119dbdd1d6bd9b1bbbdbb1ba63bef86114497c8ed5162324ab92b252633e93353aec6a1c2f82ea5e4dc93acb56024c7c86566d8cc30cf231789612d774a96f24377f795bbcdf5203d62188661f10a0cc364c4aec062160cc33029b12f24e65fb29c207ac430b98aa9ebc36b5ac76b5631468c433df699e3ac0ef73634d98732aa8e4dfe7ecaeb788b07c4f3693048bbc03630b02768ed058bfc30293b66e6f828c6e9be7df8f00192c2e19f0f12d02984da3239ce2eb2931455c8b0218a183728e151d49095e090430d798897542e7ac9e0f0030350a81728dde0440d99b5a2640316a6a2c5450e95f9aad790c3912d9d53dd38eb423ba523575653829c4471648601c81083f8d188961aa7a89186276a0cdaa1a3e1ec2a73d5abc94436654a1c894804612938e245415eb34a35b4acdcf080238c948ce0e086071021a186b0ea55cd902a90f88113c8c30fb6a042b3ba493ff052032b66c8c28a1c9470c08245d27ca6a25543a6a255b51857a8ff8186f3851c5c23d001ae1c1274d21a657cda3851a671c08fdfa790a08cfa55fd313f7bd487ab1b9c4a617ef6d9e4388e7bcfe9e7487051ae598eece79c9aa6693eab99fdcc661735962dd89666052e622b9b73c3c9d628616b74cd987b394aedd63e7b02c2809a00cdd7be0423ba757e3f4d7ae6633f7f9a842a28a002592cf505ffe2a5fac854f91a90f50522a06e7ed38b64585307f303bbc5c2d23ef38f88e6a9fce5664212babdf64a4680e021b2ad2781f918ca53f9d7333f95fcf9cbc23e55f62c60d9639f4a3ef6cb6221fbf9f3fbc1c01030f0e243abc5ca3e955c96f6bdd7f8d70196401419b1921c3c3d0a68c8454c1e1134ec22958bee927f0c8df3d3386110f971510a09aac373baaee1efd81917a7d59d3776c7766e5eed0760c188d3eddedcedd47d77fb6777f7c605c70884b8bbb179cec8cc3122f18845cfc89c25c6e8ede8e87883ab78459432fa17af702eb6a5a9dba0968d3916a4ec97de8e8ed7fc40c71b5cc5ff188966dcd238f1080c74a87119290284225b106088224e45b21079d2220376a40b1aaca06c60ca860021635ad18e6e51036ab5c56be3820a27451732c0be90a2b58ae08ea4b80a2353617e4f5061607e12a930325ca9300f238503a62b402af51bced4144d4d7d4c7d11414da5beb86a4aa9e5434dfdae004449b16d3475b3a9dbc7eda79724eaf67448ddbedbbc58aadb838db3495f0c31c56e4e0c153930e20b220b787531c4c58bae4ccd9ea8d9cf8c0b126c7183c5f12e1543610b2432cb22801a54fff954c593106d26af0ac39c161796640386618cdd4036d920a59472092c5a920829a1c54b1750aca0b224d960050d590d65862c0d01ac494fad17528d3f6fc4f5828b11473380410e929c205a22882d3ecf2cae1ae4e83a0d72185feaac68e8344eccbe307e3a4cb5205269542f6848690c61894ee188af56ab55e428c9155c38f1e2cac11169440a58b0558c5695115de934ceb2fc38c3801ad2744982290b23598a6accc2881a93862003e84ee6933927457129129465d1b8e0500336b9f81024776c119471118286a804e362c411d762cb0e2dba48cae504397486147772692207def1d4e4353bb9b84068c7109c3a224e4a337c914a32801758d440802050545da519ba0ce0896e759566d0a244b5ba4a3334e598814969062230f0efcf565d6f6babaea7daefbad5c398e83ed7908584d846ccaf8c671e388732f3f22fe77c42f40b79a96e128c2fe4275c2ca5eaf093da8c04887d3c8277016d905543a061ece91ccec2d33898098e3cb1836e6d79c2b21fa76c19f6d987e9c4784171c207c21996a19ef63bbd2bd49199b7f21ac6da6582262ac60082d228652c21b1262645998c22d98c61034f4e46f2d46234d1260a45050d5d028409456d44201ddd8004885ed24c9c4a07b93fb953739566299aaf30169723b99117c98d932104c4d0122a538e9870a9182ba80161f63c0df2c8fefb917d3c7aaef037ab071acefa537ff007e9d8c91a769e4a61fe8e6facc5afa381b6a22520e2487b47b96b8ff25ef02f3a2f1470a0d03930dfbfc5878109b30f39f662e488607edbfcd9f3ce87931f11a5edccac82f13ffcb587e1af3dc752f3b28f44f40b7a16cc47157fdd4cd7f46b2ba09d1419541745426713db3eaf4725102f3e225ea1eb6d6d6173767936085a4c4822b3f79af92808ec6f0efe52330cc3b22fdcefc06f7ce9c110eb8ed8bf2c22d4af07a3baa7ea9fb5bf393d100790e62b4b284ab08c208a96111c69ea8fd9dfb93144897bf8de434b69410d57df030dbb97122d980d70659a8ef10f7908480fb53044c92eaf0024862f08d78486eb14841fc837d72de58361c762a93868832cd4eaba2a46432ca12449c9fd8341dad81bfe06ec092ff8172f3bba55e5d780bde1da5e07ba4a4f05fff2e5cb97da5f03168c2ab8cb4f676f646dd6a921585986d2b0d3b0659f61588c40acd1b1280463be61e9004850595789071c146009edba4a3cdca0c30250578987293c18d5f07b8ba416282dedb3e4ea4a1e1ed7d5de4041c36deae17352da750f32ce32b8cfcfcf21013a122833aaf23659a0c7d688cf41a8dd3132a7a47b60d82cb4649bf99918638c3ed1fda36ec4b634616caaf2b7ce3e9084adc19f01fa518bd2b2d758d3b44c7bceb0d8621b581c32c5e2f45377b1a07c192f624af582f2851a477bf911091071282e699cf9dd854ccbf862c47c5e44bf229814d71f7b0b68b8efad103d1d373c14741a94361290e0cc1cf584f9974dcd999cc9005b835f3ec83632b7c1179996b49961a856a2cdec51dacc50dacc309436330cb54ccb0ff3a9df1ec59307745d4f4f4f4f4f4f4f99cc27e38bf18594c27ca1cdc77ddb3741d000314954326d8d661b315a557e7cb23596ee8dcb9f93d2ae7bf94bd953f9924a83724a95ccbb9bcdcfbc179c911a1c82ae8dec896d645f346a2eb6067ff1c9de7c91696fe44b25745a098c14c6bd029aa0303fe3e5c72316274eb1a07c39e38b465acc17dad0afd5cd2902a500e8ba19f12bcc179960d080c6f7efcf8be44fda715e94050909090909098983268a6addfc4c3ec641937e554013348c46ad2a3f1a21b9965e4c325b63a590b115e4394258f2a390e74496fca8240e7d2c8a4d362858edc7dffd5412908f3d0b8e7d404d7a16c85f7eaafd88b25f96fc653dfb11ed9837a605195e0bfa91f7145846c0c2753ab51ba10723f480668b369a9d95820842d0d09f78dcc96b4ad8010dbb656f1844a914e60ed8c5485cb4af255751d1f217c3021abad04e7b933b6571269dd76aa99b406f2667a55ec8012b21d085804cda096d8dd49c9476dd3fd8af55bffa1583d7c407e235d1998aa6a061bfac6c4e347a6d0dfe4e0ba5c2688a1a7f33f21c9a45a3ae24ac3cc799bc26fe9c9476dd3f08ae564c948a100410cfe9d7cbc90926323de938adb2803abfea406aa30353e3529eb3d8a1a01da0159b6c036324f6263e17f109ce4a7ddc44ddc87368c450a1fe5da917a35517fbe411f5efcf3e9992c0b252bf291eee1909fb908b7060518a28d3aa5cb492de6bf7b51d0150824d6e1ba7fa63f4f7e132fe7cb84ac21094fbc17ddcc17d5c887dfc2020e5730a07c7d40a42eac6defc622a05d48ed3e9612e770648ccc004b57db8da334002864bf5a76385d7c2b362df2f5f074f8fd4549ae6c3473e906725bc447e10b94d44aa0c2212878e704b57ba90daccbc165a537056290456aafc1b8db33ea8f25dd8c1098b949ec2812282c68a83bd0c8e2ca502f6330d764464fe3958056eb509c12a69c815133190f27584a046d91648849478108251015d25212521185816ba94e2b81e68ff2a0931a9d8af6f18c6428da3d2fe3114e771d8dca67c2e224a3db37e98c5bd4c0ac7fc28edbec33e1b50b56a50feb2745637b06f6713e1075a7fa0d8739dcf2d240d2a69500a3528352bf46bc8462b603a66a6d0551212aad86fbcc92859888d3c466a50be00aa501551ea97c53dcae3e9d1625640e3b1044d793dbc463ee7f98842c36daa12ca12757e412410277655f9233f062168b84ffb1d7d7808fb9529283666a6d99b1eb21f7d4e4add6d36a45bb338dcddbf0f40b32bd95a93d2676262626abd8c3c92e80ae1fcffff9f3c74f0c0e44f791a696664907037604c1d9fe051860e51c60892cd33e3d91926a349a49991e1646a68e641c9b0e2bb86a780523495d93c681f3a7662f849229c4f413334b403ddb523c4483bce42317ef3056f194e345c97cf8ecbe572b9a490cf8da741973b0d6e41c3e9e2a01df6666f76d9fc4960760d13dbb922ac18f4536d1f4f8ca7458f025ac40202e35371d1def4c3c0dc97d0033ae39391c105c5d88a3e1e0eaa28d58e50fbed53ede0df9e5f48155277baeba672506fcbc2c9c582f196a5793cd4200a6c834bb027f433d1fcb888461ba59a4341d3aa54f854fecc04658287ca5f68233343433a8310ff1ef9df4f93fd7afce3f7f32384a948912d32d4a7715a1c0edebdc0daa06117b64610ae2964ebc2deecd3b08d0bec09fbfb855bc57ef1eb582816317fa78f7eaacf1f3e5ba54a75ff1117ebae52e5a8ca67cfbe3b85a3b32cee0fecb97e347aae8ee8578e4a49daf863588f9f3f9583ab0f4c642a476318b61206dd1b8975330deeeeeeee763b4de7d90467409b06411bcf46468ee338ce9b7783a1eae158f2632592df0f0858d46b38994a01e3662a076bac699a37350fb5715b2a7aa738b5198cf0054fe3540ecea3810395ca81550e47f584ea0ed55d9f7dbdbe70a91c5db9bd49e5f0caed8d4771c0487130606052746fd893c1d1e58895c66c31a8989898182d26262666c6c46431588c8c311ed3311c7302bfd8c50cf38446514336e2a1d496cd07206088edb3623cab8028a80ba7a7713819266e18b8f2689c6f4229527f884151a4d07c94204f9e2821c5b69cb9a0092b4840b97b70c5513061b27df49db3bbce93a06d65a3fb1d25879928534cf041fb2c15666ef6d2ddfbab06757a1ba73b7e389d7a7737b66f9ba0057dbd48e04e9d3a75ead4977a0f9c301a5a52d42038a345431eda6716d72747c4a7c9556471c2a589cb15c58997902926f880fa2c158ef254c3a74bd35788c0fbeb4a297d77e3faee7e3dfb294a09ba27ed966977386b0e58e065e7c60c1d68b82e1d3a74789d0e28ee2e82b8bbbbbb5f5ca675ce3f7ae156d9dd940b3c4629dbc4141d9d0f596989a61a3e85120528dddddd1d73772cce06fdf7c6bb2c8d954d83fbdd206f3bb3733132d6e43b8e8a7083b92ba0fc73ce39a70735f161cb8a4667db2fbf95ced3689075a46c273c8912585c477614bfbb45a8abe4a40b126c9c34dc3e3831738f06f97d70f5f1e17e0f1f3e5cd0d91b7eae09ede183db6edab4695d274772024ce5e70f003f9290cd86ab2c450a17b91e9888a0438cbcb8ffc18ccf04e59e8d836dd7e5f4fef7864a7bd8a250d73d46667e0c8b8286ab1d5f89c2737431f624185058517612d0fdd5f2036b4728818222e2729520690853e4c95a8aa281aeb866239818824a08945c69024915282aa65eb3cfde8c42775775f7bb068ba06b43164e51fc553d9fe09947d2753c14c5a1788009e7c46c2b3498f1a8c094ca39ee72a171f6092447955f5d7a340eadccad2a4f20d95243bea18519ba204de1691c6e41e5dff53929f51b0d12cd5fc5b430c405f2573ea2418dd3534087a2108edc5d623724e6ee2ee5638ca57030f67247e28e64ce22a8ff7f838d6152a83f867dc488e66373ce1857f06a7a31755d47b4f1f4c47f148ac79c3c3c5e3d0db644794124cfc67ddb1666af6def1e797ad9164476332fc398962512ebe1c2d7e8f9e8ae9bf3eb71a3c5850665d6c37d9cab2138d3a0fc3ace5eca28d4ff553bfcb3cf9a48fbe8691a57d983cb85246bec065b92b610996982020a8dd31f3d09e5e6666460cd38a9410d6d666823115aad5641336040436e794f31826469fe6ab55aedcdaaae68cc0f5ce980ab07e90c82814fc4cb781a3df6b71f44dd403f05ada599a56e0a0dbb89441123468e8a1c0d79e9ffd93ec8d6e0efe7c0720ed0a2096d203e9e0f8fc70edbe8a0da3bd9cf148e2835ef9be1ec695a8a086ac842362d90866090bd41816d70604fe86f4d0b7a544316a2c12d579cdc9091274f505f3c61c26408131f5e0d2ecc0be8d72a3289155ee136c5cdc1beae03f766569bc69993c77baffb81c61ccf1e11f68bbd7fcd7a57b520e857285f549957a50d7612342b92e4430d509200a19ad0cb01326308fd979ca67d7326314b46d33c63b5d7467be34e360b1115435328b122d4519efc3d07e6b51ffddb6bbfed0dba7dabd0bf793d1af427a18924a954654b65a52a5dbad544b0df9e7a305ebb521e6aa2e69ca86ddb8c3a339a935277327ad0c948a7ce18427b66764e7376dd3f08ae56298f480af56d3427a55df7afad3414aa7bd25ce9289bb398b795ea0d45f57602c6f64e1fc6c873603c877eeb35309e4e08528db37d1e6e40088c944783609bf6dded2814aa67f6bd4429c95dbaa687e74c963b35e8de46d597696ffadd488605fdfcdb8c21df0f9056cb63f3c72bbc6373de6b74f0146b65bef6bc537fec07c16ac45e9b3888b26d93d8172e5663cc767fdf3518d03ae3ea5cd1073dcca3c10e2af47ba401bf0f08743ffcb8bfa908ec77e353edf874b8c2d54cbc22290906f47f65a48c0da5734257980ebf12164b4cc2201ec64fa741ef874c413ea603f641bc4af9d9476fbf5583197ff9f2e54b10af1c0601f9e964afe3ada47f343a67fe8d95ce8e4605c90674ca84fcdd395b4d7ff71678b6c68ceacf23dc205477ff95e76c1f2b995f94b287c7c7f341a1fd27a8d28b51a5b74b489654e96d7553bb37b2ab52feacf263ca5ddce3c6dcf3afb6c68ccaafb33524144e705f0cdbb75e83fa580814a2a4fee8f7d98c6be6bf59669dab95484a438ab817b2f5fc0889939b3b78da41465da52737d84149294992ec411ba4f7424370b9719669869716297a9c148df3ae55f73abc867f7d76097c2574a7174e15fc4b5d50872ff9cb2964ca2ed5ddddf1812f35fc69d5fdb80abae8d75daa1493d1578809932f5f5adedddddd9ff6a8466d956e8f8d5a4dfbeeb92f1eca300c23e6a373c2501a44560d45436b9e0685a0daf7a980a23e5c0939aa3fb42851da2729d79e5909b51feb2fd4deb5cdb25578f36db291826a2d8b14b8fa2f14285d52529ea4a43cd12289249ee8d2c4d2d258c9501aee2bba0e349c759bb33f206ba571c23929dd710ffc81f21361cfafcad1771f6c20e857a41454f9cde840c30eabc293199c407a6bcca8b1f6f311b5e75c7d50fa7df40616e7c7829da385ac038a66a95dcd7c41ff8e42b9621f0cf10b7d364b8dcf9a4fa4b1eb86b74383d0b75016640213c8c8ec1eb19732945232b38c531e716c7759d37380809a00654f81eca380fcaa3032fae7de70f4ecb47f2b30b30068a0fb11e8df0f05546a0876529c862a55765f3c7834b83c1adce769707757fe569b06b7f31aafd9d75e40bfbf705d2b2846825acccc2c9955df2da9115d59798286da5f1bb1f7d0f323a4e74748ef7e1c04ef0a614dec8b086145212cd5673df1e5f7133beff66e2a5c5b2af69bc4e54a9dbfbf836b2583515ad92589b667ec53fdf601a558d83ef5a90f889b5fa722807d60830cce5feda7170aa9a1f6a86deaeaeeee0b3f2108e67ae033f13fb29fff237b08ec6826557e33a9f2db3ef3683a677b17ba66f6d042c37dcd6c57270e7a784a32425d2514e4b0bcdb02ca7533b804e599066bd720af409edf1f00196cc006a7084f34039bd3c2fad81a5cf931b038d10651962455dea6ba5d2a7f0536c747f658d095e734c19e2bf77eac2d53d897419ca05ca5122ace2dd719f639b22f7bc9adfcdc23f27cba867b40df091535d4910197989d6de1bdd2c44e287f051a87824ddc6ecb8f908406a8fda1571b209f90f168b0f7b5e2b1e113da3d1a0dead8b08d04ec09fd08c09e5731a0a83a27a5fc3d8f5461bfac9edec67e56ece3b9c1b59253583e494a8202a5698b1042114350ba326504fdbbbbaca40b13264aaa50a7d6feb033298defef1f90d7ac8a05195f7ecbda9517d2ee419d9d16dd8dceb4caa2a32d550175a13852523718fe905570a4a4822e5e18cc0b6630fe19ce9959b675f78f20723d7cecb7e6b01f52ca7010ab7f22848c0f50647c80a2a4944489a5244bfe5c8f80c2748416a10a0ae84f038b653f9ffd1f3e8ba5fafbecfba781c552fb53450985727f74f78695800285092682824aa8abb484117fc1822d4f9858efa44b19dd37c77f7fd0cdd05ed6c90cea1ae999f891e5ade195df8ddce539f15f04959f1da9b22fb514d82f5569d506d45daa61a8ee60e4853f7b8b880921a0404dfa81e2cf0f9bc90b1afafcecbde51ee439254041819af4641f1f0828eb57f64c2a16a26abf9f263dd8c7e79ef8fdfc3451f1c49fc0b2a2b601c58f5bcf3df60179150bfef1e30784bdefa77e59d293f1531fd0fc38bf75a075e2b2f1901050931eedb1073202d23e36f21a1ed201ea63232ed8a7da4f4581f9d8f764af7d3f4df6b517c2eaaf67fea878522f84c57d13150f50931ed473a5c04f10b3b09f8159d803a154fbc1d0335ffb7e7a780dff0496e5e3e535ec427db84da9cfc96bb2af5b5e93cd07dabe16c21ea5f1872d5419fb80fce7a7daeccb1e7b1632ec81e4671f0b1890d7ecd74b610fa528e0ef4abc865b3ee42da45ef29ceea55e1687def21a969605fd1a7aab72f7521f35c84b6dd420ff26a49b0a4d71bd76f77f47c78d0697bf30e2a8fa57c6f622e8462d647e75ec34b87fa5f2f7f2224992224746a4808179b98290b06dc9d6d86609d6060a3bbf79a8a75e5f714531c462c2a47154dc6fddfe432ec291eacd9be12f64241aff16c2c9698855a4c4941a32d2875cc4b193cd0940d3efa618bf947ecca7da3ee637a0dba8440ff6243154340300000aa314000020100c064422b150389e29d2247b14000a73864c805234944643591224318c428818420c308400600c101a1ada2600cfb45151019851a3f4ca9642e3cbc9e9e230c410bddca5922151eb6290133dc8040710390dfa9d5f2ee64468b80d7bbbf9b50c6a5f8db9db7bc1bcdb5b99a8ab1417d21efb8839dd7eef530afce31d0b0d48aba7fd31c98afc201dd4ad87cf68586ae87da0b73c0051f036bce1c9f10bb9d9ca75d0a548cfc2f04b21e143bb30cbabc158702965b9984b789d054b0fe099a6802f25f61e62abbcaea765bbdc3e6cbcc26b135de1d173f2bf57e8fc0fd30dfe28c902297d465cd2c6071cc1711a58b54da2af8dcd35eff98c8983a40eba74339db4b835795e46ed6de5bf7680a20161c46f356cd46ec6ace0e307005ee094ef35939c959e05e7502ed346a00d948eb664b84ea0fd76602d2c4df7c0c0e85432409a2e4ff07d3efac79862deae4ae6633d0f8980bedf9e1d6d4f3f185b9bec6e75b0a20ac2292149b94301ff592d1152560019f0896466a9bc403f9a3a599624420d5414ebcbb4642be8297680a4394efa251dbb909bfb856a465519e302c1d58c73c26f1cd072508acb8e4f6c0de5976b654bc022c0bf059154ec50115f6a43a7fe5d5c351cc885a1c8a568492fc6688f56678ff62d926b0cd78a9db13e0437b320f81ae1533d5d9f233600ba074ebb18800db48727f031e718641a090c8c3051827e9e649f29567b897d1d03dbb5b1bda355624c9becb19a793a07af76844b36b903f3eed7d13bc65189837eb28f5a6ff06a5ae00af533206d9a0b10e636db6ea0df0585da3706480490c8821838ff5b65cac4af862f75ab69fb17df951268f7ace8eca935978cab224883c18b96d60c48f77c51cbd60b0d162becee62040fe7e8f281813cd569aee986fa4a2abdcd66322ea8891ed04b10f5ace5e7bdad198ddafd2634dac9fcbd12c25e01de377a1280ad60b6d78717668c492518025b6363f5666e28537bc547ce8f0b61814684452c48ad1b77e60a60f81578d9710730185fcd868486b2977a5b38bab058db322207ff6c23a064ce0472356f9fb72e5d0769ce2d49b3690636f2adb9e9937e03fbeef0207049a13003c28153d0762e5eb25e380af784f8966daf63e9870d52d2787c02fac858b4c47d6105379ae144801d963e187d1cddfca6730e8bdc4a816e28752552286f7d182c57429cfdc376ef3d673a9e8e43080a275dba9512541f6c2c8bdb9bc08e030479e366609660917d9dc516f60c1694faba7be3b49eed38f89322139d6bbf14204b46ed3c8c0477000be3d3e3a8529c1983019e5b87b7362baee849a4e7bc9e5798c2979f3909288113db26d78ad91258c30585b7fd390efa1a9ae0ca5af938e648e1a06021a961ea51a796e5c29a0eda16a72c8b298aad7af5badfb3dba5fd94f5da8ee00ea751e8675a6180e552e03d9f01cd087876a7bcaa3266fc40f652ff305dc5cb87b22b7f6f32c7d0a69ed5d56baf6924935c72733ec42e7a5342072a5e0c7977645060151b4ebf484ef608807c17baae0cce493f20bd65f20f3d00cd5857f5584a05863760897937d0b04f8ea5d85cf263fe26341e0d0896fcd69d58dbb6ff00514d4cb0ad7cdeaa005bde134efe0862e7ae15dfab73e63d81905206e6363ba1a3475e15bc721bc90e92568481d88b641266488435d6d3dde216eed4fdf199359e04c5ae0dbdf5c3a50de72f2f452cba382b003b584abaab53659757b51f6ccebe345a9ee57ac148f806c0f7a4b386e32a765651a45d14b7405708a8d8c7114db2b1e13cca7e9ed21d465256c6c1a6d0143bbdb3600287fac95b9d13b4cc504ab3e4e5d97d5fd028ae8a92557592f2012afe7990db1b9ee1bca96ede4d3c4bbdec8c979ce08569112729833c93d108d91b7e0212ff9f2e484f03d297edf234fbb4f1833a5628b9a1add0ce938bf5b50b1399b8341c17ef00520c6a10bcdb878826bac303a286e00be168961dcc83115f5d1eff3521c85569ea590cb4da5eff5c2ccff80546487f15e8d6dedf2a81c60aa8e103466dc75bf52c691f731dbbeb9044ffe12da857b36d23007136e7b24f1d3db28db579c0470ca200692ac2a3bbb79abf16aa53d97c3d4b2dcff097bf68563d9dd21be546e641da3c60689db8417fa5fc6aead8d4484fea1c55ab3a2b24b851752fcd4e74ce7473d036ef03a3df503c2947de0983e82648e7c9685b37f8d246ec786157689df0362f91b6887737efb7f4acea4707cae198add6e9e176aae46c956fd416bdf44e8d4ef0c1415bd8e305e8c3b52603186061dac74f078d86f0cefad81b6524dc9e89cde71bcf73ad0db24c805349958a17922452adefce8cdddaa0eb1f553b006e6d8825a41760241c8f45e6805cf5aebb45000187fc99f2c91879aedafd7a8eaeab9db19ae57a65fb9ace508b8253a279d96fdd3dfdcc9ddfa85e0a7fb57112417d9e741625f337ce2a6569362e8f87e159952ccf799783c830d5199443e66c30e0fc95acb1b431a30ae451a56c94e98039899d9f2ea24598cc7b3f6d363a5b6248425be3c213165574a24b8c867a3dc4b43643410a826568a0e1891131b22f42006136368ad666e5d429a28e46473f157e98374559d29223b4a5314c4072e1a8b0080c1c1073a484f09504e8f30264ab5c5c1edc05d8e2f9fbcd4b86be5b6774dbf3bf3c10c51705cde0ccfe45f19c056288801e5768da31fb4d112fb4a0a827164d33526bad5c4a38d966731f36d792f3636c50267a092c5f52150b665896f1941f2d2a6aea15033805ea2465fafdfa94d2450d4acfb45e6c91bc48c2807b50da164cca7d848e8211656ab39d15febff3678c214ae11201b4beb9ae8b5f075b4e17a0b08f1ecef2dc53624743e20eaf33da156f216532f64a4dfda7e2f98be5241c96426e9a4dfc500dce2f202e276d59ed82002528555407d9a8b216d7db0e4c79621cc76579e6cc7ac2e8ddbdf2c1aaf8f263f0002b0410a50cdda8eaee3fe413c1d5ff6c8b55666e6de9947c21b952d650f112abb16af715c9b3525a59415724ac4fd808fdef64de05d91f43a10a36da60d2e847e3694f82686c174a00c84de0207f9ba02d20b1e73dd60fd645372245041c0be35f37a46cc77b83240f882950a6912e1be9de86b355c99d87d880cbeea3426c06ec937927c65f4edf5ed52dc224a3d280714ecb67490569c92db668bb78c7ce42e41e16f28089611362e5ae459d1f105d11947dee239fcc91350de0e42946e0f202308a80445749decebaa4a7c13f634acf318b3b0a74327ef2f7890a40162969c29ed09152804966498ada0e7cc6c2d3c6345647c6f8ec41093c7e30b0022ae3111e80efeae238fd5eb426c3c2cf35f81fcf370fb93155754fe8763d7d32880ce330beefeacc904feb219491e1845e96a950e2523a9e6f5229aa90132d071b8b1e9cba1e29d56fdaca15c0b940731a31176ffcf6f9c3ace50877b5f3e2eead764a2b447442af776799df1092c83f7bf9b561783321e18ff9c82fa043b7067a06a1a7cbeb3a534bf9424a8cd89ebfd375ddab26c0a932800dd0000d53d4ccafc649d8ebcce4a54fad78fbed1f4616ca5b16100def77b8107b14ab05fcfac65f23d81abd3e9a2ce180e3c10bf60352d6b1b460857c52c3210baee9715b94b39ecd3c97e95f5e3dd2d7babb1e6c4eec7fed7c6fa55d5c8b5d615314ef9e36c6c34427e5a7e8fafbc99343030489ba20af84c7c3fae2cff247a234afa1f9592ded8203578b1f80eff5c3a07be6d3c0a94131fb4c10571c8379d0d422eb743a993848abd36accd3ed0629e262279b3950f20061cf3d99d8afb7c0918cf1a045cbb793b1608205c52ab32cb6a89361319f3bbd6b1fb330595552ae19954b7fca5433bc9e413ef398e41ac00e08c220f8b58eea709e4b90f8829b4287a2c770dedff1278a6ec97d1900e3c3e84e35db91841e11c3693f44e867f51c2188968a1a79d24aa8e41ea4ec0b9e6c9080922e1b75d8d3f00dd21cb468cc23204180659cfe07812fa7b171800367e00fae60590120ffba117dbaff94556f5f7dbdf5fd126e3cb1fef00c8893c4f60138f6863fc4278f089e247add297f86dcf4aa5fb0156196639901e183074eda75efe1c40b5a87f289f2e6a21c6213a8083369a1f835b74d6c8741eff024e7f1852c0ed554881327236c7b04e18d495a8e99cb22289207518d8d36e1b0f98522f4b376f0b30cdb1bb326f77ebd949bf354d052a1e52dabb3d0846d2f12ec85e44b32afdc50b8ef43e4661ee4f8189009517b98033f8fba3e58155fa77605fef0a0a04d5ae9224236d0c5228c695a48a67eb09d0c5a998bd281da0115c50d438f19331b42e51210506418e64064d11d10700c5bf87aed1e270e2b65052e98ddb6caa101c20b1a9f57c880435a6cf0a3ffea5c83694f2bdf6caf32e183a2b344ed92d9a54fbbc2d605a78edbc3bfa02b57da010fd9725b69f3863d0a28fa9f17ae66acfe5240d072e48bef1426f375c6d6875d0ce3c044eec0daf61dcab54aa04cd44f97fba40d4d88f30ccd08f26d24db57050c640687a9a199ccb1c2fe058325a4b9b43b3860c6abb49a6f85d4f6751f25fde8eef2241c31844c4bc36a2fa1f3e5867929f37684c1d14abf247ff53b1edc8507c0ff76464b68db3c12c3b5889fcfec2a47d0a35185a9615f8c8be63c95a7aff06f8f3318e5a2141746d8d41363251c386f09f4daa7a391f2e4c1a82583f4cbc73e9d5de5271786e73b2e277bb63d4a27633c902c7810eca6c2db134463b2ef38b21749c708e2d949157d97a645b792db50f23e0969064bba67dcb2ceb7d5aae4dbd60aa3097d979d361341146a7d343b42e51dbbebaa4eadbc21ce0f2815097cb0508c9e3ed6d8fb692d466bfc1edf1b2758e11f00acb397041d9f910aa401c476738145d8e2da15821c87acc3d95589fd293beca5bb9fcd1eea4cbec0db7de335e5fb5249b89a1688d9c40dd884bda315a1e55bfcc5bfb9370a17476765596d25dda2f889b8bc66868d209fc8638e415f0cada750ea232c6a793aa64e38c0d49a7412fe9948f31f867a0e98c026ba719eecd328b73c1f954f0b63085971cc1823be1ef9148c41c38777fa21b1ed374064aac6778a8a9e3dab4755928af5025237ff98b89661366bd702e2f70be7d67a4608828d86154271f9349b02751e0030a4711abdb4799d80bc9b34527baa6a58de8a1bff5e7fcbb71c505560c6e70bcf5c58642045794b7e85fccf924caf469f2e83dbd4dce90588ead3606c12afd46c1b6587ac8aecef66136370eaac6fbf9142d344448c9fa7f29a44320d8f3655847c06feef6d65a2a26bdc25cb2081eb7f98bb3f9feab89c6bee2c457c315e5d4b0c80c5e94b94ce295a31990d6a18b9b4fa550c82f1490a437ce3c90f3ddaca51e892411f7a4eb3648cd332b2f81a68b7a60a87879997d748887d5dd3a6a6578a98e8bc3eeb78b9e40544a4dfbbe2903ec7bc879f51b00e33e5b59defa5adc3aa39e668aadfc08b42daa5209dab4b9dba5d98c6a3f12e00a3c5b455d1506e110050a15f135e06790fb49cc5259a9eb616e600c4d27a279d6ec272f4b73b5a4f8165c2350523a1e0aa69ffff57e54147216c7ee780a0072513c430a29a1ec5ca7cf4ebb8e4595162380a0f77861316828ca47374cd61e3393b36e40f7c9e5111ac710ccf0bd6b0815061820adee301768752f0a759cb219343416fabc042357fd143dda81774a865dba41904349eda712121c5ded7ef5fcd131952c3d016775cb5b61593625aba23d041568d31cc7d4132f8fd644770a4a5fc9deb28d073f225a8ee7534701ba3d240c7a6605cbfd8155b27fc6799a4e1239e29cf110db55ce5e20b38c33b3adb49987dac8a5b877f7d067cbf721d7073879347270a528733971e87ef318d3386de7a77a87a212648f2fd6778de2988508bd9f4f2d99ef9a3f461229aa1d3aeb46581ec0f2ffe2ceb08878dd9c599a5b25fd13a57e698c332135f3707b5b9d64a0da08eae159bc9d605b683f6802bcfef7fab771cad584cae63fdbf9521db0e066432d8e7f17685e5f6fe92abe10fdde2151d0d2321c2a8148d10ff4138f63fb9b1c862c5f2afa6221472d14db06f9770028c9007c426e18658c24f6f4cbd06fbd1a5182c920c6cc4a2a2631fa4ddabd21e786ccb3215e87f24dc2c3159c693afca6416cb3168fb200029b728a23549635c8a8943b69ea89a113f57a448ac548c1dfbc747c8b75d888451a660a47e1c9278fbc5dbab6e843918d788c66ec634af895925d9522e9a060463197d5d80613841827bcc22d714c8643ddd12991da40d766314511d420743b59187815e1b06e63caacb6f673a944b781ae9d95cce0a684611d70e86428c421fa6134e00efab882b4e6e468b2018f6d59be986a198225ad01d20ae21a42d6006f5cd7ae06f67edd443201c274e99e0099bbc47c022ba01c6d8969872a5183c58397558e008ada6ee4ea3e4b80d9285eb390111fbd2a773d5e487d08a0748b5848240edae13de2db1dd3f0a7d01d8a962217a92003db2046e706ad27962f9f8fbc141aef1caaad7de40778b8b757bca3c4d340befdd50aed43b7ac2e1613bb337982884b47f75ba9ea8fdcb1968f939b2ab90f8ddedbbe17d3f345dca2921695425eba9d61b48702f17395e32d24263299ea9a35de8c49fd6796ce5342f1a2d26300d90dc789e3679f4109e7d0fa9963be5cb048786ff5559c5596e040290f1eb6ad1f4df3f93a01dbcf4855514919c2cfb174cc4f03a1aa3dc2a1dede485b16ac3a9749b9420761dd2d70ceaf04f92daff4be6f03834cc0eff37b38177a171804000d5573e2b4ce5998fb296070879f9f4571e61c2ed78598559c5b217c3c7866ce52e5998655c679f43ecfbcd055ac48a81af2ba7d151d295eeb933caa58c9f508c9f86ed675ba20b5cc9e7352208f939a103ca2f164e658dd01fcca7524e5e963ec9ae2517be1f69ba3ccbe8df1f65e565197ff9552f1428b0ee1445f01177f28af03ca6f3434c120f04154990a0f91f1090e9a323f0bae498b91dca3f14eeca78f210d96303343ef4235037418a9f7fdd72cccfe850a6e4c47ca23e4eeb9b6cee1f83647781cce9277b4f41dc6b7793e2b27c4c8c186688da8d4ee1f81f44245a10577440406ed496edfb96ca23c636f75489334a81ea58c438270d60dc8b61b5fa2f0dc756fe9593e623bfcb8ccd5516c906f9672c51cd31cc452606ac662ac1fe2d65c18798f40895d1016b98bad17745099d8645c16016153c11673d1cc2d05d083b08061e66a5a25ca6c07bf782200aa82f36d6e282f4d6bb036938e819df0247060f27a08e80b0fb048c7aea39ef25874af96b7cca606731d2315ef4ffae78280016124b092bf0e00c6d94c98ffc9a9ee11b02bfa403c2a9896375a501464eefe342d519541cb33d807b0ac8036d5f6ccf85f09cd8f79ade7570b7bce9641edd35654c38c3cec3b67022c40152d6d00e8d340025f95bd64b0132ab2925c7929cc692b5fec458a2fdc3817b24876958cfcac8cd4d3afcf79de99652ae9719763e3c4ca6947711581a25062f8bab47dfc0b20c171db507f67df9a22f28ac5ef24552535891fe7fbea4ef15242b2c4ef31df3494dcd720c088bd2cca466198a44f03c2f4dcd9a782b3434cb044e5aaa0f7512ef3dd32d9537e098b55dd33c18de4f24f73f7af927bba3c7915e1fc93a38a5b9f9e993c1c9a17b257b86bf3077a547bdc40fc9474384b0b50d912523add5ecb54ab1fc1e48b7154a381f5203b700616d3a34796103d0c49eaa2864ec9e4b7203bb0a3a100bba733d18e1155619b09619ce756db14e6384d23c2b4764a401285b2cf1bfb32888a1077d0de9f047a9cad26f6fdda22c65b947c521f247367586ba46ce3a5c1ca6091c677e56a5e0b9af919179a6cf9366722dc779ad4458d5adf40f6f0671a3f78d54a2631a6abaa489f9603a82f4b31bf70738f1e64bace59ceae66640f46b6e3f25dc051177594cddb84063883d6c6e3ab2aa3e2f5bddb894a16b25c95970a630e6f2fe42b5c1f11901273876310693ca6e19ed77602a3842f1b2069c01bcb13b16ed80d0342bf3de0b05662dcd10da1f5092a38c5894639a0db6a0e62058b00c53abd56811f7cecf3927fbaac1804853a478ee0d1b52121110e52ab8f2ba79771c864de8a3526f5430ba4ee8decdb81d60bdea0181b56d91a6ffbbb4bf0442f1926b3e8e1e15e71005e8f1a20e27e35ea76cc162497604dfd9d0ed3b542ba8ead33dcd7b830e3ce1b12fd1f0353f1f45eea6d62a423502b9b364ffa6b92dc05bfd3ab1e0deaa79a2e42ecffc9d57647f1f3e01ff4a3a7499f8d863ec7a0593a0ea4d94efdfc5fbc38c996df201f174f0b32ff6e8af22446eb9c2002d4a7b30ac8c6c5d77052bba7ca078af639a39f8a470870e57bacdf51018363cff4233633e5490930d98ae9471750e915d0a456e7f60a3f6c646933333c5eec11aa345457c8517bbd13acadd4bc2773c03ec7652e80425c68e8ab97a357ee35312ed0474bdb5a1539cb22d8e10a87c70eefd8b9f26994e8b3a14fadf39450459e4ad2136e58c5bdd44a861db1d8892c10e1523774f8321dca50264f6fb5fc4578418546975e68332dc42d884e5965be52bc45272020dde40fa65a0f67add422dd70d25d3ed54a14c5618b7ad30beaeaf6d54b9cc6631ba176a3e7ae4c6d4f70a8e92f2b40493da63b7c37a98861d90f29a462a30f5e2c4a7ab9699b0d104b1aa8279e87831e51ee3bfafc1d3358cb9ea7e3694269954ec3e8705d42703d664722059c47c1bf837894ed15e247e23dd745e3d8397cb728204482bdb5c614de4692adef7110af2958dde7144e0d5fa696fed12354895d931765794e2e4c16eb89b5b2265444bd152914f62cea7c9330dcb37f9a0e32aa0a9ff651562924fee39bc24eef1e5189f7bf3efb3fa6ff27c0009890f2db33c298a22c0cf419e576a058afefc0a8fb3f9078cf344c58f8b063a683f833d2d2fbb057f0982b88a87d2b3155cc32223d1893dda8a207f46777e33bae1f58b6973325fb613fe57e8001bff08f219cdb20a9314de5bbea6225b1e781aaf796cef47c3e826c414ca71f0f85da08b7fdce7fcd7613823103f6a17f4a38a87e144163fd8aa5bfb6230ee684210bd70f3867ee4154505b74f00c62aba397d81e42bb492dbbe697de556dd1d33df7de63ad51e02ef888121854a850f93d9a222629591e19ffe309bdb1b9b9fd3eb25dc1df0e8491c19cd6ff40f2b2da332896c368eaaf581c83e8bffbe8416c86c6a6c34de30966865e927895d4b46b931f826cc11b94828737435dafa4f9558dd97b6fb0dbdeb7b71397c106de0b04edc022cf3dafde623e9f5481a6b7502697268fd49fbaa4fdce873991b553d36616614ad0a0ed37805522e4af6a952c397cb54294f4480fd3a06655c15d88f1f8c2612bc696f874a128f4ff25468d726ece0a80468e08a2bb16f9ec681365690b7461db8ffb4445adddfe973b19bad28fef28d812069e3c9a07c36698ac665c9a4f77c57fb7670f9279a8b6fb030739b53fadd4622d9323fe0254f06bb74c23982b0e2525db8b8d0a370cbb062570a2aa64c15210ba0bf5a19052dcb86ec6cd2936e927a013a45fb6914a3318c59041ef1d5ac6aa08b39c0d5bfb5cbc28ac0e7da844b0ccf03db957900f1952c60ccb413f53d801c15bec5d7678fc9f496bbf36cd20cd8068dba111307d93428b1ce410b3e2cdfdf5493d43a0b3a6881b0ee411a0b16e3a4e65350cdbbec972903ef25d18c8470b3b594044766593d887b211aebff1cfe9e2f022a56ec4fd6573a04938c9cc554569f3e8e8d391a8defc44bb3098a63dcc9595f43d73da9e0f41554fc9d7ddc23f1ca6bdfc60cd411ba7d8bc2c4ad05beb66e6eac530df71d59287b7ab247e52d74d27a61ce98a4aed40ade9eaafdc282a8e967e24043f299b5ef2705b5ca5c1b34707c75976d2b0a18b785b3388f8b6ca340a7d3d8a5f7ca1e40e7a370d48066eb8a0a36ec4fd854aabe6bdc8f7431bf22cf31fdc79192a4003ef0bf40c2c3f4bea46609293d7b0e3eb8065a5d3cef62c4dcf053f12c1a857e65bd9458605bbaf99a93b9d987accd003841a32cb60351735979967cfade3c2a8a69aea7e9a0788b2e779272ce4e359d2e5359086f79aa56417b722f375b3477af4d2fa2df811a15ca84e2a2bb0445a452e70df980a8746d658a442415ade9985ee34159d30fbe95817cd58b9b27c87a077b011e5e8b68602533556d20e4d9d057a46a697d79882911c99949fb1d659f521dc3263bcf4453dab3ff94c7d8b12bd81d17e22a144ddfa6b250d6efd11fc906b511fb5541083bff7c71929575ecd4d830a8eebf03a136ece40351c6b89c2ad14dd6413031c7ec8c5407604f4c3821b032b280914ee9be88a57ca953ba77e3c83884e912425ad2d8a15a73b8307a72e696634bfc6b74c8da6498bc9153e8aa15383694d045f325b0765c1ab48ee88558366e18d46010a3bfd5d7595f9944e2e0f39b2e02a2190069f3396bc04d66ea8ee1aa719047a175048150cc4ed6b8745d147f1b6dd266e896c5ea1e93b357087837864f3e88fdc98d4ece64b14a19736483e664f945cb0e38eaf0cb50af122620833064ff7fb307b6f06a494eb6056b4ab3ab84dd8698672deb25c390b0171d19ee6623e4f47b0a9e64b8e36fb3b1abc2210fdba6f3c94138f66720a85be5e025eeacfcb80d75eaa9918bbebd914f998b9c3ef581b465445cec8541b96ec4d9299156730a0bca221fb38421ad31a90bdee4b4a215fe8171f531900830a7209a245b3a6b8d02854410b06289263718fc6d08a492ee5b6a506985ed8138b7e00000c6f4b7ded35e6fcea4f614b8916161a588355a063061c242c0a5cac779cf927a3f88ded44efd55f142eb8be316649e3b7fdb029a23d638abb0f68358877423c6caed110e57a76a051c287f51b87000acad0344270991de63991ab3ade4738ecdf6bbf48eac914086397b7032f61f483d83f535ec19a4e4a5eb3bf6dcd1efc4ac50de291e9a8d2d3f99f6b812b855ef641ad9ac86a9ac6336929de869c9ca0b6e2bbccd1204dbcdbc8dfa470f4742d783a92513f4a0cfd22731ef3b0afd14fbf05b5329be949ee4e3fd5d76c59b5804127cc1ef2c7ddeba106b33858f2f62305dc5b75951c395f3bf0f1cd4d54251db70a39fc6b631065f70d83454f6b5517ea49055b491cb0ca561b16d0ad241057f02007426e8148d9d4669cc47f38e1948816e8457152d3b59a74b315772f5a11ae842ecd978b150c20f336f6226bf0367513ac98201ba2465b9e4745740f3e46a920a75cc052b69d0d41a42c46695c76210d748f57e889dd77c0706ccb7d629a946a12c7c21d560b273618333411114ac4e933bae43c063e86672463710b8b57b155f96b2a8fe0b7614e0f6cc8bc9d76a268328a94b5afdfd134d42c35e6e35c506cf7fecd518e311063c929cee93f701844925b80503fd58513058e7308ba981b98fe5e23ded26918d81137923b5dda76430e5eb6e7cbf53161a160770f28601873fc0c73dd7b843142ca56ea388d8673a8ce160f4908c60ef0b49d2489f225c84f3c208c0a8e719e201801c19205156fbcf6926a8d60cf69d1bd28f65f80893a0535b4023c25d713ce9be17e4c839505320c18c2835237fdb6cc5e124c766e659a17637c6dad1bac49ad6919c8b7a1318c687513f876386da7e77a99c001be4499642d9922ebd3a302cbaee4cf7f5a9bd322e5c5ac58ab0fd250d4ce4987bb213ab5c3e75f07fe0d540b6ebaa8b0f92bc6d84770def2ce2c1c905e4e545118fd15383b8c7ace311869db2e6d3ea488509ac157328547c8cd2e50c88fccc0340c00948899be0306ee576999e31c0607ce173cea4853fb765f444a0d753e47f63d7ea6be648c1bfcc60308abe7d862c68a1a90a87bec1b8b15ebe16128d346b6dfb03a1a7ba90477a3d81a3bf434a4790a5137e1c7ed64304d75e026467be2423121e955a87dfe82fedce59aa263baa83a1da5f83c2dcb51292e5fad1cf24a93472c58160964f139e3242db3f75ffe4eda4825c1bde6ebd7aa2b503bbaad18072ef1aee2932cb7c7d854b2f4499ecac1a921e40c8ffb8e6fff2450d2e3430eb9018f8c69f2dc707513cf522d5734d2acb91f150a9ea542e5daed3449809f8e76723154e3d0c76ee4a656d07df3bb452bfd872a270db53e5439706fb6ff2dd88c435bb113ca57f2ffe70d9fdcfdc41340ad89803bbaec959dfe9a32d8138f176793b6040cd4c54aea0a34525f18efc6523faa6b3ec7e4b39eb491441f6ca53e69613d3418002d92f9ece907b50f1a8e3fe1c77368d05887293770bc692b02640d9e6f36d47c00217f4d199fadf75066f2fac4bd3a836de016d9b669cbb31c41d05a5c8a00b9d69dd8d1c94ac1092b891b7aaaf62c2e7d22766841a3bebdf390fa669791f24559dedff72c32fcc9c97bb9b8b13445a2d4f76c6971daa55c2284c41851629b88fb113f138a6a5ff521b34a845eed5b2904e8f5ee2a2fffa07007ad637fa7519ca5a315b536c909aced28a720d6f2ad62ea4c7b48399d27a32a20bce288e8ea3228ce97967043e7e1e06295159ab85d7d805e01e86f312834bbdffa02a5ff2601a7b2459ea9f030eff49df6d3962580917863f2c89e32c66d68b940c424f894113aeebb6d3e857c71540ae5c9a0dd96c491c43295946ad467902f196103d54a33523529cbabb8b82c04a875546749a7a975320d0c201ee704dcb30e91890573fe6348a0d0f37f79badebef3cd1306fc4dbb7db1744aa045eceb66fa66bb2e171efacb4e1f0758fc77b6aadc819b1a7d94d389600384b3eb4946870b5f9680c28e6734d8111ab4dcab7e56511d375aec6001fa7dcea48c3236dbbb80836ab9b6b5b75f4ec4eebe3d2e8d64c5ead9e6c0e6d98c89f911de90ff118bb6e2039f0dbb55bc820fbf9b436071ae80ae64edf5d8cb85fdb7b7a0e21f02d24df941da8c5010b5a27db34681e704391d3b4d771d7ccb471a303a258955cf4cca0146459c903d21854a91373d06101419ee3b52976496710448db0ed5f4b557f670ed2cf5a763b790e18ee88a8e2aa86e03a07d564a394ca4d8c98bcb2f2c48b9903d9344c4432a9dca485a15277155bd38a0aa3dd34f3b5ec24580b8cea5c45b2469ab8ba6cfefe3e7e639082aec95ca0ecd616ab9774b83a099c0632b4fdc2a23210486dd1b6889c4825e3285e0672f88f93a3481ed4e0545b85fc4ca31a2c584a2b4b0fc27dafdc337ef604c5dccebf69acfd0763c6d1b26c665c0616dee4dee5d2d7b80cd332cf84586382824072d7c3a9a71cf02f3406b7f11cc2004822701108c4636aebf80d2f07e55b38331b4e496fc05eb00ae9370d33720da213a1dde48dc65c94eab29da2268a1eee83c5d6801fbf24152fec6033c4fc55bd14b56223d5c48da0e78559c6f6bb31bd2fed400bfd6da3311e96839a62a3527a9e1f616124751451bf8e77561693f0da1f0bd30dc005aadeec77d4070778f469ea4ba277d38c871a2b8312f4100bdc2dd7831bff46ad639741fb1eeb69bd2130c3a20afc0ea4867f04c80d112909834799749b25333a238a434fe1b9a947110d260bcbd01e80b69dfd408101180aa984e0a5f99f2f6e240e3e8584ab53766170d13100b7c95960d0cd37ba3c85cf48055414c57ff8f94df64caa0d624d36eac6d059f11ef76cff51755531ae9dc235fe0b3a55e3a852e73481159dfb1471ef6ea06f97cdbf859045ff4dd7a89c2014b4d4925b96179739f0724fcb445bc3296afdb13024f9eb54be0712671d94acbddd88a4d27850b90c1ac76a4c255a2aa611ca2d16c58a383f73f86c62b61ef2be35864f9bcaf1a08b9b89a7605b93e6edd011875e7ca6587ef6cd2305199a5c4030c6341e559ff7d4cc87a87085e69dc70c230ca46327e718189dcdee089f7f72581b61e314c3b376c3072d8e75de464420a86f7d64d8245916e3d71fa7b1021f1bb1815baf4f4ceb1eb87a8dc136f17a8130228f559f4637a94295e9d951f1e3ac48c1a9cdd9c26bd407b6ca7efb78206285b7b76eaf6518d2decee7d6895b3d28e6e2873a896b0bdb5b18aa822e68aa9d91f1f5a70c489d5e3b7dd6815adb6482a3e8861cfc9610c1569f967607e0b4685861e6cab162d4ccc78072c72fc86e6325a66168442461eb5dabe01979f07bfaf80db00b0109274506f0476588f7c150d3c6a377c2aff2e08a7b316efc89ddde2b8bbdf26ed0876f478fe224aaf9ff74e094834deb59bcd3bea1e64c7bffd439d85ba11a930c5d53843701821a772254eca30a85cd43052341e72d19c7ce1a6fb91ca23fd3001ab526c1b9c0ed518857d18bbc1e2caa0b0b3a35e6a7853ef283f2ae9a2572d1ceca57b3169313aab34cd6a8d920c2625458cac4d57df04e48bfc16f8b974e178909535e26b965ae2ca251c881a995c6fb6206cee30a49dfb6e91ef822780527430c5ade57c0572e37f43d6a86d6a62211061c1b27cfd824320963cea0bfdc0e6123ef780afe27f4c0b5a3472cae09a9d1bfd5855ca6c6b0e214f2a2dd0104b027b518356adfb12dbfda7772f26ce6f9c98f0e9d8ef19fa4ff34677a3d233c41c0fa424bfa7e34261c39156d7b7328100db7fd1f3242db62172e3371b2735ca7d4e67d2f3b98e04a0fd74b9fc3f555e3ccbd3d024bb482758fd9acf44c8d88bf73105031e162bdb9ef7c2f9c08ae7f771c32bd945a0550e17c00f8c66835e9d7970f9850eafd31107efc955143254bcc9a67ba36013176080185504228c705d0a4e6c7c8f2c1792c79163206efa5deab2ab97b2986c4d8248e0b1368ef585981b11045edb837d014d0110c64d9eb5d79cde7192ad70d86c1460063f80abbd69c944ec1ac1758e2bd239bc49429300054f4bb0368d0cb30213b36d14c4386892fe4d10f19d30e075074f12a507714a853bbadf4f633ee90dfe85fd7939185afc14ce7621cc87e178ac6b9622e61fc09d29554bb5186f23447d8a35e9d36f64b2a0390381677a4dabc577b6dfcd942772eec29d51e1ff351e4cb792a234694da0a8fa4804d0892f4ef24cfadcb93c767484cb52b912a7d4eb8417e19406fa99414cdb410836a53841fe26ef0fc68933af2a73170c7fed35108f01157df71dd1156ccaf6f7540613e8af39196dda838705d60f1ed1a1bd8ad7178cdfe6e9ddbafc2e61650aaec18fcec6193ef860ba31bc6af99761f5dcde3972e45ac3efb17952a1d7be1792b06dc11928f60b54d856538f41ddb6d57e24f53c19983b8518c97e321720cbacda5702f6cb194e83d0a5a3d2342d262767bb78535b96134196546d064c927bb91b50e7e708f01948ba39158744333d441b68cbcb14416788596ce5a2307638d3b4ee925298dfac9d96475727a8e26a7b950ca53242e88bef7dc34be4461c703953cc3c2fece071e8d8bd5a74ede4b271735394725c28f45a3671dfc136fbbd19b37e834663a272b8421fbfc15fbcef3d18a2cc8bb4e9d9e8cad0f9cc75450148377a698bb949a43fa5067270e6c3f55a5f79da08b0c4a9d0fe4445f82b37497837ead2dece261244d4535e765c6f9ba336fdecbd48c75966d0ebd207289dd62fbd4f0e531fa24bfc4791d8ed5174367a2e16a43840c6aad41e3b93e09691f9923283d1115a2b451efe8a527e8f89b1401dde5161eb0a87f676894ba5145f7bc8b2a2a08e69b940d3b9ddafa9659ff272874f1562a2619bd04f93fde11c9648779c592dea291be88ae8da90c6cba989d911d0ab05250118e56e56395f634c2e4d45bd78d936dbd70100252e56966554caa6e51021d605bd76edffd8f61fcd03606f2318f9a408d03a2605117e2591a326f317dff9872b30104391a0a4ad3f54cfa769f4974168cba0f82ba17041a8135633708a68b665e357e0ad0cd7b1caf45e84ab0f1736336d08a7808109210fc5ad9814c1796bd82db351a3e704b5f123fe9f8f9d30d5685d16f01ea9ad21ca79c214d0bccc97cd9556e2e8d94295bee1131524afcd01346b482f17e3be3be5a9d02c7d4dd9b9fbfd6edd86f65da6905c3f45425b8582ae89049297d46363b7f0a544f80f14905fb306bc7fee6ac69ae40e1b24d9811f9661cec821e7df5ef5110f88d312ac5339a50a1c4e35251d94e0befb8967fa2b906744b832845ece13c1f8e630e1c281789b60ef09e9c5fcdef4e543f042d3c058b5a066772b240b5d1506d45adb3e8293d7b247ef76b1372fcb1ae35a16e24884204d62882f78d7bff054301027a5c6bf0cede50987f0b8e7b1fd76fcf72a8055e76bc52bc814310eb2c9f999e1aedb3766178cb05e81ca38dec64576cc80228e00f1422688ae1e7f56c4f6718692f1d7309f99cb2215b997012bed94b2f08d6a9f4b47e77452d50f50557b6e63d4f524439d35f7a054332857595f77b1ef290b42d4c31c1d2d039c69592b8c671bf57275e5bd72fdb56129954bd5fffedbd64402d9cf0e23060656f70b0d0a31e8ffaed2aa6c2a1482662c8f46b11d682a1835811d0095f85b0f3d30bac7b6784c122056d0058ef4406852339d8141a44eb7a01e4b50240b0b16812758d7e2ebf621afd8508bf8f427f4deb467574e1e9faea1cfb38ad45f19932eea0d97cfe312ba4295a37efdd62152ac47677754d0fd71ca9f355274246f366a1e88cbca7970d74e65d9b7ab5c1aa2ee4e950486a4f71ef0f992546bd326236bd1ef11a692f364788936bbd5a039b8e11df8b082a80737d102d88a92b79a8a151c155a0ca04d2baecdc0102699bb10e6b6d978714ad7d07e092e3a5768598071c76e04e187e285c6fc08caf8563864e91b4cfc884e532966049efb50323ebc8984d8a1170f37ff312af579f545ead37cc8eef25a52796100ada781da94c7b4d5a0a8bfdc1d6f7d0abebf4af09c70a1f9e22c4953a05fcfde3b16e7394e2a0ceca462ced744272ebf47d16d6063e87f730a673312fe1f10412eeb2a74f33410c6302bb4c1b86c2ff362fec2ba113538167cef340bf120a35ca65f74bb98ccc92f6e60efaaadd78fd5ac41432769266e7d7591269e4da2aec889a4a51cfb69ed5f66dab4d38ce39d8b380f7d09d17aaf2d36f0b264225e733581848528bd415474cf066a03503ce3e72bdacd10bb94a5ca304b455c1f0317c3fdb9ef75d7857cc4797b1b37cb05a32d05b4a75bf579ff595025390933fba4df0c9c4e6f3e9aaa423a7233167d7d7037cdab0f6a872c4cca02e35669bb252485dc0fc066d5d13b038ea9a4eab91cc5cd1db21615d00ff7f742a6b8a0df1712a3a74eeb38da4dea4a9104f95f3fe8291e355ac9f4355d4426fa57ac340ec7860043d027ce62a152d54d94e5ed143590344f664f1c0dbdc7b9ad532c277d76fabd94ff85b7f5feecfc3ff9fbcc84c294b073903ed3d098c2cdff1b0f2ee17f474af20bbca5084ddfe2301f0b7152df30e6ed3d7f2bd1c49bb320050c40f7c4edf9666fa385b458bf0a6a55b34d45f03fef9f72c2dfba39890428084c51a675c2507dbd760ac82d69119bda9fb7a35d321ee3bf12c589ec01ed324e05f8fb22f0271660972e363313a355f469b380e495de7dd510fc099ad40ebb169dc1a2112be88a0693cf86dbf47e9b81d2444dd845ebcd236e35e9833c08ea364c4c96456e44108264cba85fed342afc882eec2a273e9b83c65c97a8bba1f51ee2f4aef6f07a96c79933ca373ad08592b8f3f8fae7dc427b363bd8974e287cfc41c8aa62f666bb993d92da350fc1a0284524364fec375f98b9a757959ec0934a34bcedac2181d2ce390f79635b6fe0d3a2f65e471f65e1a99a702a5572b3ed3bb71cccea078c5aa020ca489d505e833c853dd155989f9dbd5619f621c579412d7641ebee51c2c548886a8a9ad18a9b4d4eb102e780effe4f8c28ee094a08a60fe4b35be16eb9c17dddfa30c24e98124429bdc06a4ed71919ffecdec856cba0ab2130739d9ae3267be008f4189a38a1d5370496e39c27d8ba01e975b55e9cc7518654c1867193d074a24b9416df456504780acafaf9a033ead10aa18bbc673d4a562199d32c08f0932fec0c81b941d8dbca840abe1babc68698ca2456953b45abbf823aa18a85a35e66eb7307c9cd4f29c29c4ca55f7f9588a885b963415adb22d0f033fb980398171b15afd2c52a8aab933c2805e32837eef01dba94012a18901e9ab0bdcca10eec7da348bd220fdafd8f35ff27f4a41deb57d04cf8b7fc12b4492f401b96724808ac44f263c40bdd650e945862a2ee90f556bfdfc540c4d43e49581460b48ec146ee9676156feda2a23390a03ec5afc990409e48bde24d00e1167e04f8a2c204211599d378c41cf0dcab8364ea75885520ab190cff3b324c7deaf16b367c17862338a1a4955f87da0a1b5da4ec1cb545d060458dd60fce281012f13e719dbc4dac4ca7f052881123ad6d4136d22e2a4225d85bb8333e9d1d124eb6b15870f57f63664961bb2ec86cc7967940f8c92d04191f2f9ea3ec4f963038bb4c36245e089815874517aa53cb20c050d6cbe57380a65d74b3ed83778602f3e53190c7b21819cb1f2168aaf869a5c57a997d9d7cb2b4df98b2cdba73d1d7742bcecb5beb234b00b6a79415ac596378e61a5953c801c87f467669b355b31bd347aadd7f4f1984ddf9d1de95bab69f2393dc4526db9d8feb911a9dfa805b462cdd825a8eb15864bed323e516fa2581880094463fca5e5f43467cbf4102874c0f228bcd3009dd05c77860c34767e7b038b35034bd705967ea781efa816e9aa97c7e93b4bc933ae9d418ceae0cf2cf42e9ca82b4a8b87045d11dd2c244d9eac00ad4897b9b83cbffaf46f91ee00092e0e6cc7cb533ff384374e05609e9192176f7665f938007fefa9750ecc4ec186d6fbb355a974dda7d9d97d1e9d7ae666b5020951f2d56f9885b9cdffb2f1b59d39743aca9ca6cf1988369e5aa6a0f065b1b7b9a1a81e4f0b77c88a87d709f94af9f15ee23b1c25a28cfc944f4b3dbab1f336883d86380bceef246126f0228e6ea8aa1be83992b91d59e2188a3c0ff1ea6a68b0b12480bd52720e6f2e681cae1ad9b98fcd03eb5d2eeac5888d50a71aee9f0387e09b1467ac24b69ff0364f3ee7e531d0f846cf37a4e36d98e1a7b608feaadce873cb6ef24852cb88043934c3bcf28c6488edee100de2c38af1f8b795f207a57fc5be0f8f81bb0326b8c3f738bed181126f103f20dc8c0f4a7a796fe89858e1da184e7676a2a38f639eddfb9b628e8cca1eb313b10946cd78b4c102aeef53e503119cd142093924846a51e32adf82e51019d4b27598c28b9fad71e743d7d2356dc984a544a7e32a79f4664a7a367933fcc25e818369f672734d7083dc68c3ce667afc8f179fc3876d49948142ce28a279a32a48241ee59ef957c14ddb92d327130f14c315945690c21021db1b571411cd8ae6ae61b042c046020301e89080f673ee0eaf721152d4386ec1de5f08ff8893f0b6bf3a665050703231bbbb9e516f057b56040e1e68e139d38c7c00321b648ee97ea8cd62c2263cef0f4da533140f399631fe3b8b081252414d8a7af9ee09989d1845f4c719c0f031a18f96ec086c1b0383b143e7336a910ca8183b112cca18f2f0e35da8d8382f89ef22a47070cf204c7a981b4288f697d4be99e7e6658652ea6ce4d0f91b2827e0ef18fa6531fb66ed5991218f0fae7fc27967abef1868c4aba354227d4cd408b1fa01921f6650983ea133ebe354c19e694450ae93f9b2ca7297bb4e736d8fc3b50868eaef9bd86ee2ef58ccee62917978b3d4d00dda33a215dfba9328c029b31f5f4e8df522ad3cc211e7e0a988c803ffbc812ea5f0173a2a4f907742e408498191b5a6c767cb377870df4755281d5a3ff99ecac9841baf88b09b45ed6ce5c976e4ef7c7f56c162b9098620b922a20c464d1cbe20d34e637f8624deb2269184e9bcd345b88c40dfa5918a5e4798206cb82739e0b1c4e9cb4106039e3b1017129288eb15a09aa50cc0375c12e31fe941cb6918121a2a1fa188ee45caa997066e256c02c333b3edaaac90b8199ce11c06dc62f18a3a51042957146f2cdeb179c8171f73f41a2694d6e65a79c41a2333e3686486137b1906099395164c3d845c06a30d2d3ea12eff1a6da440f7dd34ff895753bccf3130122cfd22873a4daeeb90732cb568ef8771f321d1c6086049765efe875f10bb7839d43b7dc974a4e3ea6b063d171a9128e4e983633dd20f04e2cd64584654b45e938d7ea7f08e527e2bf0f0a5b462db45d89adb7fc61c553ace880d2ca49fd869164402117ac28dc87c5868bab71fc203b84f8210798e72de57ea7de9585ee7f910f4e9ad053bde203cf43640496f3c73443fe4a8915d2196053ac175500e940f8e8dd487e84b5bd9e107b8ab4ad30ef8d69632cdf039e433032622b0c835fefafa45d4f0c41b2233e474107d20b50485da81339fbd20911d59cc7d9dd4bc2e0b47f3b1b3ad42374c0ad08d40626c076a1ab66491377d3627a17b984ac04d13aeab1bd4e43040060903d391c4fef60a458438d7c2fe63ae70b38fb8840c2329f2fa43aa03e562df0e1629abe281f63cc7c2d8a5ac7b075a3c2f162aeb086d9e54b0c5ef6134a5b4db468dc77fc1103a92570c44e648ed4131cc630c4f03e4b2f9b7511420cb4c00a0689964a070dfde5cf9a82357cd17fd6f574e4a26db6d8d8a78142fd45b5575a21447925d7a6256415202a0ccec04bac5a174a843b202522cbaf25dd125310a691bf6c04f83ae6c306b34e697b8337761eb6eff4347c62e9d035e1aa30be9b033ba311bf85cb1494fb70c6f23fe780f72db9c6c8b4f8c8093c516fb6ccb982db2eef75239e5579868dd55643c2ebe773d84b4c3b53feecd86b8ae70ed562cd9e7b4339fda497f2dd35cc8c30014654864a1f5c7568bb31f1c20ed38c947d63a1b34706d01dca23543b96eecd5038aec014d893afdb0179a73e5d7ce0287213013b71d6a2b2d3d3329482c9903bfb397f17d6b511895c0311840c6a055b445ea38c1154184fc64d8596a1db590d56b5b888fb9ddff83c083977a57560821c66414ec6de11f15a98a0b3d853df49d1c8a56026226e7e1e9bb3c3b716dfb864773c06cbd39c5009d5abaf5509955651b9f246d50f64f422887de398f92aa79f5428d4be9006bca81856210b7781ae9bc5ac9cf2de3a22b4197e57a2fe3619569283b606b446cf0422d314058f148917e9dad43265a326e590a0451a8a35dd9443269d858a0666f34150da644c64d8e4745211d013009fe4f04dfecfd7447e695f12454ef5b7c9f3350ff8ed0761ad73cb4871e2d22e6e749ceedd5321ce6012cb316ebaa05c9dbf9d5234a75315ed5df280c1ca916e121cdc236871361097716fa3b35a660fd5729bedbd139ce34db8ab29c177fc656fcff5af6d21c7c4995cbe4e99c75a567332bd842c339971b63faca21959943f7d2ce2e374286bb8d06a5582151850c71fcb441b50bbd256f34a23fa2283909e6f34559605293d39015f52c82df23ed39f8c37e6c15c94554f3a5e7d4c593e806aea1ebbcdd266584dd9148645c521261dd70a108b051d936a9207bb082cad684bcf30d9f36355c13169c3c38a279e2e257dce65d7f8c60ea11daedbbec5ef894adf4044260f15a2a9e4a725805d866121f171f384e666ac0169d0e0d5afa616e58f48c693263559aabfe6ab7dcd9632b481c40770b702346b6148b74b6324cad32b362398b7dafbdbc8633364186bdc20d0881ad338f73b1644a663a7f3c290a1f93f7e0adfeaa1ffb3efcc3c20006b3a4b31cd1f5c3c01db6d2ad1f418e6e78a400cc1e7b2cca06f8a391b1dd1d9bd35de692a06888d1dfab601c37808991d79a6cc5e87bf936bfc090865ec7afd4dd335e745523f8803abb9623d54a3a666d00d9b0353910b30f8afcb836ff7c5ec5247425984460b62f64caa234755d85970bed17782fc7c089a19a544a880b1eebc14c363ee50a9114844eb83cbc19fa662c39d58ef1bd82f1ac7ec5b29feaa1ccd3fcf197647048c8c9791f38d39c58d454cd7b694fe84eb67c3357336af946ea23b48f154c5512c21b227e63bcfeaaa04ae87e6027a380a5b706a22cf853a51cee12fe72e7ca7ec50de09676fd36926998d16d33f539dc1fb59e43a824411497b2ceecd1aef0615a597ccf57709ed27e7ae885e49a9ae992c9b5aa05165db4b1315bb997b4906519bddf1c8976ecfa6af7c9b7895272c2c0972203eb4d96247568634aaa85fa93e304e375341dad0e19d94eba0e1774f4095f3fd82a187049ed8a52ab1ad9422dbf3d9b6a70ff0dc527b19dce49b7ec49a0b0ccdee5d529a4062de19ee1bbd980e6adf3122d599344ee2371b4d7f15435a527b09d4ea3a1f8ac5d5b2df9a01390486a5df421a7405f806e4de1de8f46cc9e137a1b0b7c83ac53e4205ce2e7206cc2bdf39b2164c3734dadcbf2b669fc5b15bdcf4ddba31871963fe457857ddbb8ff884d7b0399ef0c604df356decc4cd68717bc29fb78fc66a11b367c1efdbca11f4bae259a1da674f5be6580650ed7b5455aa49f3ff325f9b370acd31bd359165fdbeaa13f31a64340ae631efcad27eda700bb479aada4654d2a88d7884547ed4fdb5849e2e5974fd7a768d6a740140efb3b52115675ed730c5f8110a6ddb6b884370eff78261cf9ff8ea048b47e2765d21126777f38f549c67b10e06cd870b4475aa9394c50913435a7bbde803d26413ba908cec5550b3f0e1d341e7bc88c15eb715925e66b3840b8d2dfa65045699c60b7c4265fcfe59e7868e6958174d90177620f6e7429744d95f4310320332dde683bc5576d9917c72a4441db07827895c9a80f83b5c9bcdddf1529cad3d7b6d385da6ced6ca54c41058c45fd624ca45acea8d4e8d397a9a743ec6b38943659431c2204d7d180684136a4a3d61b446400d9de162ad632f6cd01b48ee56e0e3b8043fc1a37f38115268ec2e3712c7a263e27853b6bcf28006b4b2f05d8e945242de1f9ac69dc96b5ecb6a9b6cce2cb9e61f4647f9065079f8eeb26b1842f4f26ff31aec8c9d601e2f3d42346b2e39ea550ac438a2292fc4b852f1ae43c67b25e30f101f8baf9b6004e0604e3dd88f067c0fc3f2f39d960f1f20ed173674409b6597102a9f6e14363f963d7ba694e21e574b397d501c89d7cdc1f17017300a9605e662759916c8c513243f786bdc6c5204578db07ca09f7a6e7ddb5418a5838a4d07a3f3b98d9a127cb75865edced017030ccf23633533455c63ca68b6dc2b046797c117199f4e8bcb78bf24c970e1d95f25bb29436478e14b1d6f353603f5a1c09302dc040ec7051fd5d2b67b6cd6a2ce3404f7c8cbb61b1a4a4fb27722638ac0de2f52f20839cd40b3de6c194cfda7bd0927c9e03beaf13d8912f0ebd7323831102f814646ae0df83f72b5f92ca8b0a7d021975bba41e1a6d6aed817e976c5c7f1409fff3f569fd91be85c415e1233be46844f648ade9e60a445b7c6d9819d9272ba4547a88299e87fc0ca23f61050bfabc553cab042091b71d813459328534bdea20cf55de19499be78a1febd20ca5220d90ba80724205c2c79ed0648b31e4cb357f3083fa2d40baba705261d07e24f0bd49c01862379b867cab68c5e68189aca39acaa01de8a864375e2b4d643b1dd73f92a55d94232e36b7525cd921f989896137314a742c402658590ca1f0a0e8233745b5e90ce628aa07c5a839a1ea6cf5dcc0191129022a06384e9852b5d289094baad6e35af4bc7a79237e26b34095e11491c9dae86468baaeb2026433efa38bb1f884db536bfb337e3fb9c42bbf53653fd3fabb9a3a1192f0ddfb08c1dc13283062428c3a3acd7921d1bfe0b6f3680bbd727feea3a06b1d00fbfd81df8670de150805510ccee254adaddb9eeec820375158e95d349fcdb57d77e37e4bf8f220cc7789e8532f33a24195e72af28cb4dfcdc7f96ae906317410faa957a77335271f67a11f06c64853ae19f0eeffd284e11a3f514a12ba681028b22825913816cdf8eb59b488ef06bcb118444a79a668ec6d396171eb9ef03adc121b9cd3436c92c4334e4d811fa59e2882eee1264f48c86a20db05fe693b61a38a0f410cef60bee734978f2bf38972c021f04e14b240ac9fde60399aa406a1245f39b5ebdf993fb3bb137f2c3941327e7b91b54d9ed6b1c59c95eb0d8dbb473b1442af4afa8689b7c958dc4579ecb2cf36253635fac682f28801f37312373147c8e7779b52d9f63bbb70ba58372cbe43411137285a3e783f150ed9b68ad4ecca9123cdb5c1e59551637ef484616d76ddc85151c5a912a0bbfe7e7c98e98b0293f86c5b4709fd1db455d6afd9aaf89c8b1fee09ccfea4daa25df9816a3a06ce1f80a24bfd1eb9f039112771467575622a342819afa2460a1b0d6f80d6ec42ca711d76cefea5fedef7727f85ccc09486a58b542f52e823ac2f34922bcb3135cae97bf69cda06853935ad638d55046894c0dad57c8f44807ea144a074e2fea4a0b8c8cc2dd3ebaf9f4fb863c1de4cb0dd7203adfab18cda6e65c0da14d9b9d0e1b0e6b7e483c19aae9ebdc9caced6937cebd1e2e512479c062a11acf774e7bb8a379535eb9505f4b5e16e2ada1ce92a2e8927067448e958180bc76652dcd4975ed48bb37d425d0811d8f41b25fa6ea00c45dddd20db14b7cf3c0d10b38a6ffcfa429bde32419a6bb69fbcb70fe5f800ed529241314479b428dd0549fafdf3a55ddce521ec03939955a18cb8cda2f3550b4f3d9b53d8e8a9c22150335973beb48fd8d26a367290cabee0094be097d3410a44fb28c9a21b164bad8ebfc8045bd09eec244d3ca8e49cf60f0d8a9e8d05811d07b2cffd7533e5e0e8f2c4631802dd9cdcee2736a142c7198337cb6e919eaf5b8337efc1449a1266d3369992436fb8ab8225c76594dd5e5ebdb0b5962bd9ef4f9e3718ecc1cdeb9f53b494b5497188efd2aadd2dd8fb192bc5bf14b1de22dc4341ad54312fb0eed0f902fce205d55055fefa45cdff123d7e2bade04c8853db7f4195c8c10804eb691d704b96dc6cb93259a23ae23e1c5f1a76ae9fa9de69996040c94646b9c9579f414b935d84d6bc0f70ff037780dbf987c23faa0535b898b4642d5145ae1b472ed5b9f57ed04ad3bdbceb2665a8a3fe6bd527d34913685aa5640361f52e00364de62be7c500b1adcaa92310b880306f5c376d7ae82f1b050342f28a2b54e28e491142604a43c98ab360ff070e6f457e51a37de4537e097b0e3ea2ac501a63f8cf97f3d6db198f673c10e29d21c688c2898b4488903c6f5ac3c4737b1d5004d137a86bb15c0fcd090ba5e42219d026f8d8f2848d31d0f90dac423cb75ba7ecd3700d6065c7b81d237c277d8062459df83d1eab50ba865b15c117983f8e9c40212d6de1e76b189b0ee108691d763d20229c0051247c30dddbdb3a14e4d69e09f914384fd5550b55de90a502db8f81df3d0bf33e8148e0a265373f93b1af792de3b69363f11912d170b07665bc849aba574dafeaed60a471255597ea544f6d5172aa43d3333d9f1bb68f9d8eedad3b741a2bd21f0ce6ab06e6c947b91a090b85c764231a898e7c315a9e1768b9eca83827b92aae43a771b7d1d66c8e844d36540d67adb50e19a89c9184ce7c7ecf955e7caf1f454d75044957d54d498daeeb0dcaa3ab0a5e6f103f6027b0c8e21e30b7279f06c19cc071b98746b8d2c0e149461372b430b0d3286262156154ed6916f0c8a4820dc8562399e971d4a12e89690dab4c8d28b30be170f9c8a534cd91d4277492fa45996083b9333e9078508c3ccb690c3fd423d7fe2bf594c8489e44fc929831bc199a219406885ed5f00b362aa15cc7604a17a38e85dc3dfd092c68038516f208fc452fb4fa88c9176d2af8557dd242c7682faa8da1e730168afdfcce77760de5780615726b025590d92da3051193d013699a5203571153e4be4ef16717c6367b61fba8014a301210526258d14ed6b73d60a365c5338bc60486c92a44ce103f55260c9896a069dcbfeb0e3834e41e494cc4870481e025d732e05faf0936884af1e69632bc16adc7ab70a7f955f7f82adccdf3e84e4249cb3559a1ea8dab4f732a3ead954279720de66a152dd7e6ac718381f84c28b42e64625a040b6de681fa543084d732d64a774653e33588cc9c8e92788725bc928bf4a90d122c4fda2046def513f6aed96d24c363b31deeaa2cd0bbc2167315301aea8a320778ac59a6a9a866a0638f6b2c8192d2d19a5d957bf8cf51036553ac474634416e14bd66324d9e6db380c7b057ba5813acc65b48ce8abcbff74ad0e233938cfcab854af10e0873a8710a5924ba4bcd07b5f1b90a7f8608608215062251e48cdd1f3eddcbb7280ee1bbb873418a12688109dd342dca4a07739e5999988e8883d13f5fc8d3b1dd2203092a449ba500cb1e25a554558d81b0393fc46d260452ee645fdd18eb3c7b0c68dbb87034c38feb61d85b762e9f87362208afec3748291114f64d3fe7a06b7608a0668c95249a704ce32d88c3886c76b025e4e3b11aafc2c40fabe81eeaa142cbfc79506bf292848d26c5e3b8c034e9af8980fe60761cba49c8d53c8c5f3d691a35178c40929a58c0d32889ad674f36d3234154e44f33907290c90e67076c4e8ca7e7dd1250c9e2e1eb010840bd276c6ee25e3da6eae7d8e1910a4ac7a42841ac1545b3d5c144488e3277f99fc201f129bd3aae73bd3c85c32a327f013e3b6ce40ade0819eb831147800b5745047a04b2cb07ea5e461e95d7dae88c499aed0488a1e563fab5da784f92fb4751da51349df5320a9b459125efa30e5ebe0efa55e7152583ab3dfee44685da5a8cc04cb91cb817cf3ee65e22762dbb152cf4dac949c56d4857616d74877b790f203f576995f85e31eb8c94910113758fb0bf671002fff176e06c4a3e2a58bcadb903932b727ab31e3482a386a774d6af6b778b552e07c6d9660d3b416e1c9134f9441b6e1b43a30ccf40a611b868808bb9c5447720081ed9a43df1485b5182e9071175e05cd65352936e2eb53ed2267e83b7e964d6eabeeec399c6432f30104b1e09bfee48ca514cb383164318ef063c8b881b35921c0135a310ca4f22284b9d0aeae16b1c9ee16094eb5e5e827442296f7c533e65235b1e511b31dda7646a4fd2cc1d1b67aa716c32bd896979f4d1fac53426d34d45a756ca25c1b518fda847a511fc3c9ecb3a9b42e2eadacc210eb8599e19f4f7b71e05d21e576a73ab0fdb4b829ea4a3853ccbcaa897b277cb0bca209963dd77517e430be48aaf020228cbc70d6c42e926bc850bd313ba0f424619ec67d990cb5442a7a206aabad19580e64ae12a219e0ca366872c991e41b408d628ef858c208d6796989061538f736ea05dd600ef095cb8007e170176a6e3edb78b7df27f077cfaf778e219d27712610e2151542d873817cf377b25d266fd765de3b05891f83808c194bad871ff5d45514c4fa0db8cc5d87d5c6402e4c2c163e2709475783a8056e3182549847f899c41c6ff5f3d442a536488b12f76e84bfcd1ec95405c08a73c7183189d17a1f125915473b3a9998480d7256d0298de110854bdc8a498258f9a03fba0c802ca2ba1e8e3501c871c4a4a950314006d923ab12a39c5178bc049f02283dd1035382e900d8adc81581cfbe1b4dfb75853c010a30c3257eae070c0979180961061806d0918cf31643ec2d77a29679caaef3ea9854ea33fd34426e8abde615240e4cc01a3e8d4800837f290f31def003304643b8642447915c5a66adc13263b727f2a8b82eb516d0a4f4d16cc305648d9cdaba7e3a5a1d3e9a96e5bad58275cd3bf81d1f91f8c35bc1813e8a94531dfb3aef671bc5624212697c1d25ead2da6e586968fcbf5b9d7fb3337a9cee9400a5732ab2e353e42438e4a44e818b7c48891aad87199c76e57b38213e1739a0d46ee5987504810d96eb5a42a589e717cf832e2a675aa776471aa3e4d6625c12affdb37eeb0711babcceef5a72e38f4848e6b650e7eb20dbe6197bd18eb2def3e16d0ceeb0bdca7cefe194f6884e907172b2f1eac18ac62970b06f62d56a2e169cf025c13ca1c27dbbd109cb2f0862b9394a981ea112c46d054a8ac48587dd59b92c145da9d122203ee412871598332254ce4300794d219981f13db341aef8c36167da1fb862583088ed70d89e03c38e4e65fb44ebd12046ba4e5df539b5c887801f22c47e611b477d61c55b2ddd94557ec9fc865137c2b0fa1959d6e2ad0cfe4213932858a6cc4941509f2f20193f7a940768f59e400f4231657471762e4217a1253a47bb7ba45e0859c082553995305ea5937d36047e1040e5be54c21c578315b553fb39729932c57ec87abb44a538dd2e9a14229a96f0c866eba8a520e0c84ed6cfa1e57e8f81a8db4a70b4bdaa495dfa1c976e8b5c92f2e59ed30d0bcf266809abc7857e795951c06d80deec776d4067d1553fbb6a4c696e46ef9b54b54bde4e9bf90bb68a952c89d99cc7cfcb00b050be0c9a476993c2c49e4ae5a54505ac4c3104e6fb995cdaa591658c4ce02b5921d121247224a14591598328279848a05daefc1a5290e0741df15efeca455b86bd5e615f233006b94086960c088b3c70bd6f972c89f3bc2e79fb93368a3e7579bb074880d0b51cba2a4658cf83c6a6fdd6d3fd54974297738b2d58be18c9e120174661d656a45fc2ef974def5c93a7b2db5e24481fb46771bdb1b8c1e1d858b84bbd984ac13cab19dd8846c14c643a83721093c112dacbaf87a74ab723909423cbff584dc07fbad16f981dd60d81178bd76d23566bbf766e77518cdd350a0199ebb6bc32dfc695e379843f0dd301d3d046e35a51fd7ed9ba4fe1e9b9bb5689899ebff137120af01a2d1dc68e08bf4e179c9418883ab82112a553cd506e339a1b48782c8891071796755dc713eea0c20a3855a69a2cd55aeb93ef5d442b139f8e69dd1f2dc78a4ceee2b0c9cf0df3ae1e9bb61ec21ffaa567fcb8d50dc174e343eb0ca3718312bf16888edd414a3ddb1d644087ed395d10767031ed792e0cd591a55028828a257d4abc7656ca2fe3ea0dc6ac609176ceb92fd629c28ee0179723ab8d174c821cda48a91a6c317e178626e53f16fb20b2742b8dd73800df074756eadda83af3009abcc19eff36b80488a527ae8bf5719a12c3b0bd4e5a98b5dfc04ccc31c23df32888ec1fc197d26f2e484c5519114dee59529425b481906e0cf3239858a986c0d9e93cbf28773a4e1f133444ca12b9b934ba6515239551f4dca89652285c2993d0f4360547b30f20e64548a4ebf2ca44dc0b481faeb59ea5a6111907b4ed2d1fe84148189c8b1a8d84a4cee667e06b1e2d2ff677de30e70923ef554f175e9011a373be6e99d8a8299c13db923e3f2f234d8477eacd8ac47a9c56b97478431c0f7993065177a1a1522cfb1c88c1e47b9a0d8b4eea8b9a75d35432957c5fc4f9a5b15d6b68d0af483c50d3e97809f782f9bdfc025c575ad7759546656dd39357d050de280852c6a4d3f57e037866f9838ae595f637d4bb5fed90a38fee1c4fc12baf6db24e83f6b11756a203904db334b6df046451ff267de3bf2138b61bdc5269b538a7e9895e59411a84bf147ae9e98cfd0cd221f1c40e9fa7383372a6f3ca5f88d43eba34290879dc7a9f69cfe3cc2459b819b61c627243bf58aa5c6a7267ac78cf042f46853d9b06c963057574fc01a8db60a3dd886596705cd280d543274e3b8f8e96623c1ac5c866ee533667f6497c312ad9ea725ab6f53478579fd871235a96181e1193637de84e80dbfc105e7572cee58b0188d7f1f6ef51f24e8dbcacc0232833af1324339273ae3eec10f8b0cde8cdeffc02c572ec49010786319d463ef2513f320146103ba3b82745c308eba9a0ea58266fa7dc08955740b5c605360b57c5f66d484a679b773ec9080c49501be08c87ebe00cd3cc310ec0ae14caa4340134e3a5eef9b4c21c36e17e94fcf1adce9e476981795bd72a191e668c416d2f6eeb6e59652a62465070740072e07dc0bd93a300572e1e675c7820a9d5bdeadd42b485ba43cdd82ee47b89784eea7d7fdf723755048f7938988119095cc0947cf74aae15538971cd6420eeb7b2794d3ef9813d40cf8a98b8a5cd9d6a71df5baec791c28a43eed7674617361f251c3c6b2a4c54e9a2e29b0ea59df48848afc5bb693c3dac9c9a9072ff221284f5072e64bba27a8b09d5af4204a1d095585448c88a8fed5bd9cadbe71f8ea42229a9733ecb416815aac34208f1f0e7b5195eaf10761fd2afc593deb71c8fa9fd56af5abb0c8ccaf1e874054cf0a8918adc21f11bf2a0c419553b0e3708ad11ef49d195d56a864e2985505c6de6254ff2510cb8d64b26e92a6d3ac67b319d5e32d79227dface5c25c03b64c6913bf325173c36d2152cc1489f1476c8dc60e44e3f09f43da99346bb81bda5b33ff6960fec2d43e099bd1f7b4be88386df8e077c3b663e410acc7ad58f8dd43497a8dd36afbf11bc67f2d175a9cf7e273bcff3b13d15f2fd043b157a4beef7b8e46e2cb9bf91c03413ad7f0a5d216f2473cea80b55d4471794b885d39e9528da09a8ad74522389c136a42e22f0d8484923914ab57aeba386af1f8d444b809b16cea816bbdffe8065142bc98f51921f230f4722d60799628c688236fb2912b29e1516f99945b4223faa67fdcccf204da4a22b27122ba4edc17cfcab1f659415385ce118831d366b4d4b93c98fbcc621817d24d67e8eb65a92a4cff6d652912d14965a1acea4a31a064f30004699fd14c8811df772468374cf853f463ff73b4ecefc7e2a1cef0d2507ba30806176286228b8be9cd95a6e6ad16e16c98643e05196152061f6adb5d6edbfdc9398f80226db2ddb9f5062748fcd91fdb9c5beb4d6328151629dca23fb938afd293f13e48e0d9f6a9db485912dd26802bfe8b3dec88c035fd47bfa9ef7ac9008ebbdf0e7fbd41399f93ef53faa13f2ffb4de7b16f8d33debbb1939e03a0626a2f91fef67be051a01e99ef53cbc87267cf92b9933a3d77d2034effdf673d6fe3fadeffe8608f09ee681784ff3fe3f461e02e9bef53fded3fc8fd116026985f77f68ae7d1a5ab017befc573ff3ddccea675e4030eba9ea59e05773813a2ad44c1fc7182c6746f4f3fee52110eebdd4e3f881c493c91cd61cf8833a288158f6a7e028ba9864b2679e91ad2b145d4b29e58b0a9e7276695a7eb100a7e020a5e38c1aea9e9f4535e35c629ab59e13706ebeca27305db8508a23539dce01ff7ff53466cc7819353443381c314e38ba38d77fe128aac21a8e327f854953fa1196118eef5f138e622b1c63b5a69921d6100e41f07e27794f083812e64f461613bb7c275d1b7a0e0040d9b4ec34da2686aebb4d7bb282c060b87bf794ee02edee6edadd9476373872b9a7cf321f8539d9d32d09dc6b4b8f05842be12f5dadeeb59dbadcab7d4aeb04ed579a6f485d2d23309db2c55a3f2d787e8e9672a72f0d3fe70d2cc051eb53aef56f0872ad4a3674c9f5ab7cc09b7190476f8a1a92100293e96fb8a3a8354a84926ca5b243aade106292e9d35aab8e1836685143982a5c706156eb0fb9d62f5799ab0cb629d36f117ed46a24268628a2706109125866b516e54ac549ae55860f4383951c198431e7c40cb6e97aa93db208da9466004388f67042b24c5a70a62a044393a002212a65c9dcbe6cf9c2a54eab4403112f6c36c460c06022d7748928c05e9659b278829100e36499258ba83c62f10ba727b4e5cab20516368f3fa5dca19fa3ed14789c436456b42642f6bceabb4f2993974c2514322d273c00859fe0cca950f6e7a3e33ef5db4b0fb73c9ceaa1bba2543887b660224666aa4fed806b1e9f0b7fa8c2c0e31ca20d557082a13a4818ad4d010593964c8fc8f4471911f4e7049a4095fe03ba47cab7639280c9c2e9697ecef5ad222929bb97dd6f985ce9095276e9495b949e38653adbc79c20384cf2a0603b09da4cbe6829207bf27c7f14b05fcfc969d1715accd101d71f71b2879fd3a23f8665ff6d0478944e3eba7e8c752e830a5dfea20c4a64f7d9b783c69716dbdb3e2549292500b2a5d6565ba96f1b121dc8a38b07198490ed1e065078c4221358059afb45e802dcefcfe19fe2c8602f13ebedaf17bba1a45f0fbad1c740fba04438ff948729d057e1e62977eaf75824fbb70d652cf4b721d1fd5a6b7783a0088f3733d92a53a6d054c184a60a2636d890840d33ccf032a74ed1841f1434814e2174ce06cc16a5218ec5feb5041e63fff37380346cfe30014ea5607c4808f12121440baa0d50903aca1160a2d86dabb556bbd99e5270dd68583efd1b06aee1ab7fe60e63e10d392738f64474cace19043583dc45e089007beae7e7108147093f80cc94edbad40c21070f1083ad0dc4116d282be18710f865503850ae6c99504a41c19f9b5639a7a4f425f09dcb90c8becde958770cbb2b05430d47fc426db7dd22d28be12f29ed6ed99422e1eea8975d38b4c56e2a84a951151b266fc3e4cbbe9b531a7ad98244f75e4b3d69690949949f63ca29a7cc3165eb88f6e30d30bfeed4dda5b4a981c17db395baa49c51c3454e96b5c67a6d6d12d34b73299de15adea52cdcd195a7faa8089f8a8ae0addcdd6ba5b5d64a3b9c929293f24ab939b53555069d73f21081ce10bd6d72a6f794524a29a5f2f502e1480d1b192175e10eb9dcefdee1f8fa1173987c569eb456bbdd71ec57e5afcb0e4af9d4e5e3ebe6da79524a29274d4a19a502a594524aa9d7b5524a29a5d4fb542b3cf37d18a86cac5c18a75836683c0ef42eee928b044770aca19452997f83b97524a594524aed6d5fdde01aacd68c67696ad4dc8a5975061c5399db6a64cc70d9bc6478121c0130c3456dbaecdfdd6c90fd3b9c9ef18d5073879edc49659f8181cb868deb06a76ce4fe8e86c7b97eec1feb3bed9ae9775d336db029a5e1b5b9a95123fb77e1cdcdab064e8b8667737f0700ef29695455cd8df355708ac320a536b36bbe8865595d4d4777772ef07c1fa59452ca9aeab5e54949b3497a299da192b2384aa9ace102bb7baae54eb5dbbdabcdd6ceabb4aabcefb374deacc32465e7eea56ef348c9b1ee8c47371a8f4a4a6dabd64cf95eb3943b94565aa90c8f4aea140a6af4dae5dde6a975ba3ba6b9bb53bfb91100986aab9d9133f65f5f0f6c29ad332d1a302d9a992ff6267405405691810c6a6bdd2a4e8d960694564ab3b0f645ed97d60be31b3b43ab9db9c27ea9f7d28e56cb62b1baae7bd985b24c9932b6be36d059bd6e7476543796565ba68cad3602b8e1d9dc8875ee9303ae034a847670894c892385060e0f2c0d1c0f78aaad0183f3e2e9a1d5bfbe2c0daf96563b64da4a3ff09922c5cde3a4fb767476a7d56e38346eb27c7a656d819c84d940a062cefaf007c3968c97326a70c277587f4d173c7ffc57e80a4000bcdb354d19c8c820834f47fd1a35d4abb5bfd91a2d0dea8b34d4bab442e3d3e12ab0228c7e58b45e18c33cd8a7637b1b1b78392d977f5eac4c242c66789b6c6021a12934a9e0a821f349cd04d54766470644f4be173f1daa176ddcc0deeabf1e7d801956d779988ad7f94af50d994fbcce0415c8c88070a9af0d7456af1b9d1dd58d8ea7a323bb4957d66e98306f34ce104e269b4835cfa46945c804a1c9ad5e304436b9e4709c5b643602b8e1d9dc8875310e3a0eb80e3af0a4f4b9dd003c297d068063436a1107081a383cb034703ce0a9b6c609e7c5e3d1ec49ad25266ff5822ac5faea9161b9483497c93e1df46545b2cb200cd9b639adb488a58f9037fa93903bf5034f4a1f1f4f4a9f248c5d5eeb8dcfeb66a63978824e0eb23f75f798f489c91d9f14088cf1c54e0bb3c316960d3c44d1f4d0d492e202950f543a1fb2b84b5d23aa218fddd460f21cfb293799281fb8c8aca7cd63e38a79f5d28725eeeb28f900743be83a294de48e0c2e410f2f5c2994789062edd74f8725c00e284852da61092d1b614daf88be3a8871b5e8a04408997e875d2efa2e7a058728a40ce145174e66f4c5e6a9405cb152e562af66091e4516f20eb976453a28c1c9f4ef3645173599fe96938229327d6a37a5211e32a55fb9e4e420e50465327ddafda083031513e4e042a6ef395ea26891691352e0f18a3a50d8cc8cc04d578c1607585b68a0c4e0021e9a8122f4c28a5e160f4c544d2c612a4e3e033411b5520af29c545328e94690f44141810ba9273c2859b8c07550bab80618c1500a4a18306c475c1420254c51624862002786a2387161a916258a0b062041541422036ca145a1c2845e2e3480134b51987819c53aa6650b316cb8803924b028304cc1ad2cb33cf9a08114d826cb2c4f48884cf08c2cb33c49914197272d32e0e2290ae604af90d4a8823f2736ca60d6908d21f08a080d76c038a9c613be4c3570c03335510b5e6599258c164c61c0a8410553174416b08d013606e69e9c00df285b4809e307518c510d1bb097651631546a1c65119374a41613b3c5d6a493392924302bcbaf5f18065f292fe82370bf510a5aa048ee14787bfbeaf7d519039d4722790b715a746f5197c5c4609fc205c4eda2058a5db885f108c214b1618c714be90616dcf09447fcb48ad32d98eb965d0577376f5c33601fa9c733a6e011a7bebf9b9e6cd93e02cdfea927f8738e965212a9beb08884a285312420a168610c5af6fb94e3387ffad465a8c54240e58e87f52be55e8242dc3bd9bd48909ffa1da9972ab8a42f4122cf41dfee0d456858e7f841ca0f600b12794f411cdde37d7f2ce6b21c6070151ef2289b7498b8269d2b0afcae16a58bc0c3257379cffd9bb70db4a0dc3102cb5047eec8afdd5feacd5d0098b08ef4997912f1a72111fa5eb885dfe22482df3f4ad5a9c019e5b04afb81c4b1c3eae3f7503aacfaff784f7f23a13ff5fd2922aaf0e77bff2dcc6971ae5ef58f55ac577d10d6ab56ff839f151209b2fa51fd785618fb9574588ed81830f4ab53a039a32c14a1e14f051c169ee0b0eafd98d7f6347c6d21fd1ffc1ed2573da534a4d1b0c8cf1c72587dffafbe9c85627b30df7b2b68ad8f630c7d7f2449e87b9fc4ffa3ef3d10ffef5b416daaf0b5d5d716fec0db130caec0090536cd61f53f1003e5fa9c18dc42f5c330b8fe6c9a4935e9db42da6227bfaefb0de43e9532d21f391b7881c7f76a10264196c9aa26ecca4d690946269d4128773f16c99386eb8144e40b8018439b11790551153902055ea2663fdeab422233cb0e3800cd8a7020050c41b38e6a0ffae5ac88e442862a42332fec0f9433d79125a425ba24c70b4573b2d2626f791d61e9deed515e774322ef91bcc76ba4f99130f34c016d246107da88c2374273e98ffb7602c3a979c6761abdc8a19a67742427538b1a3b6a49f38ccd25b7182ec678ffd5c3bff63bd27b91910374a11f8563913cbfa1644079826d942f28c3d85244cb0e1a41e28129cae40df49745cd33bfc7b778911fcdcde57228177605d1bc47ce80f2903bbd8ed80277c2118b39798f87c31ffc1f0e5ff3677e5e53f541583346e16bb258af7a396b16e84e0ee5b0ef3d509643d603112322ac377afdf03e0172a6fa9aa74335e0091f58019e54cdab7e944d56d0680eebd5ff1801c1ef7dff83c339e4b0a079e4b0315ff89a0b50bdf740beaf098918f9f41088eabdf766dec88c45f35f8df747e6ccfb1f9a703239ac5f334ce187745802e4cc0487f51fa9be6936318da999504d5fcdffb0bcc7615007d15ef3ab79ef83d4d484aff935ac67853fdf7be16b862f04785ff341bcaf097f8c80cc7ccdffbcfc695eb23e48cd4cf872ef83cc3ccd4cf8f29ff19ee669bcd6d3d474d00254bea505ba934371636067f2a5169de64beee454d4475d94db432ceed44950ee1464f5a3fad53b943b2d60155220a73477f2aa38aacd3ea7e9984b290f94339af43ad2d1dc21e9eebed7e5925e28a5f5012dbabbb7839fc3eea639e48efc98ecc529bb3bec965da48d7877347436c7032692e6a18f3f2665b247ce205ad417292515593e0a0e90208490a70e52ac40210cd5202ba58aec3fa5587fea604576b17e92303ec78657c8b54c762bddddaa6e7882dca92fbde46a240716f8a3c97348183dfac0989412e63547f697b0fa29b0eb8fecdd7fc20b914db7392f40261ab4e840c68b306ea8412cb14d7aa82f7ece5b31b48527bc58b550c33cc1b48277d1c2e54038c3ad6d58704ba48262b663b3d25a690d16f097a59210458c21c4942d4244b13428e91c2658760068be27440bb93eed5ae9bf8e00aa1845193d512962d43f2f9411982deef9a51941e132bb7928eddcfd7a97a500168a64f76e760a159986f386e3ddc2117b8bbea82c042656b64d22348f540a6229cf20bce45492d29424733ad7c4a9e3155869bd7a3dfabfb3877bf94f2499c8ad9e5ee656cfd4864cee06b2c78625348c3e0d65c3c601f08c2040691ecf80a4995640fa804e7d053a54c20f1e4bc6abc210155aa443c20a04812f7d2f2e2a32a514082fd1a73fc42954ade17e377b7bf9e9f0d1dbc66ba7bd96c6451e71be933623df9f136886938684472fdfd4bd9f0af1a4f590835d54ffd6d0fefd8e227de41d7bc98ffdc40b7b68389f72bd3fadf4fd09a57f7669f55d5cb66071519953eea7eed3200273299003abc0f38fa84dbe3f6fdfa121fcf23bf768173cb7344f33d5ecbd6f5fcb3ce21291efbd6f8ff09422af0b04d8887b276dfbbbddedd29f9f2c5f51facc21b973ffbe0264cf8442c29898b46891573ef8401441d0c686c5baf77edf7b7f9cb4fcd7730d6dddb5928142fa90c3a40fcd15cc60aeb5d65a9fbb628bc0435ee7dd6ae55ef0e762c04745cde3ea82bd86a5fe0643f64c2aa496fa4c4c5ab4c82b1f7c208a20686353b93c674deed4af453e44471f9252e4ceec41faf4d76642dea85f8b724861ca9d8aa536538bf53beca5166b95d4932e0bba1916a64c64cf0974932dcd5b30ff205b876cbb06dc2b8041a579f05bd6db6f26b2c785ba903ddee430e943b34dd9af35acfe3e0b9c605ab48f417b05db22b037794f7dfb2e54e59d496a81d2619b17712f7031e0f9d43c76c4f387a25412b875381572c73a90f441923e7e8473c081b43890088236360e2404044e026f368923913bf6534dc224789c4dfdf3de24202bdb5a3b9de40e7d9aef4f02a3e030fbf762ecae1702ead30f529f86488064b3995772672d18c038bfc8f6bbc8f683d0a709914c27c9817d25773613a2e0d65a2640b98588b2fdb63fc14481ddc99b869ab2c5d247faf474923bf6b3ece91a562061f69998b468b922af7cf0812882a08d0d8b85b36dfb6058fb36083c7ad9be0365fbd6eb99a00cae0041d320041843d90801f6c068f0846590400c836fa8d0f0430f5e7e1012bbf80106b10abc7a0116633420c236592a159d40dc01d764a954c484175434d001b3b2542ae2c1865091932745405ac4d8a0c953161867a944d4e5044426d8801605080606930649b863a286126c97304289e8034c4440d8d082b72c958878f0614eff6eab57473a6520696459abc9f293e5947f6c88dcc98084f573b1828c269cbdae7379333a9547af87adfd6ca5ab16f67ce5ad4a2ddeb6b0042379ba2a75f9f43a4ecea7a33e6bce1c8b43591c48a704e1d69aa0a6bc413d29592d55cd274ef73c2a6578d2a3299b1befbe365b43c16d9b7f73f391306987636978423a413940381d9d8e3f59432303261e0600e87e8f36a98c192e9b9b215907fe8694fef0b1e6914a4a185dc9743f980274c515474041c152a58244169dd36ab7f196b0b91893f993c78e7443cafbde5bc311d370e6de5bfc28b7f42479073ef83043972fb490c169e61ea81285cdeeee2d031e316e9c7657f3c8dcb31ebabd79db7bd2f3878c6a356d4e9f41d3c1f1a64079f8903d43648f321b903d3c96e691251a1275df605765e50196d0b0f9dc93710ee540bda039e49ec354c90c85f69caf21dcca5269862a799c41b98416271641997beefdab7fb7c999021d227de66637ba6ddb6fdbc681258077ca56cda0a01fd913812119909e0464cf1087bd181ce571b3d611ab40f9b5825c0a747a9dfb5bc2b7433e51f70d689ed8090d6891c70c2b37067fc63a416207d528d149efe92b5f94116a7532bbbabb7177775f8735a6d9eb6eefeeeef65abcdeddded6058da4694de48efc9512980cd49353d214ea204a29add45b4a995c1aa294e21a9e2f7f8eb329cba7b44b8bf273b4982b78caaf1183e5d734a5d49b524a699d492df250f711e0299b5221f49fe60ddb2f3b245264e645ba6750e041b55aedffbf56abd5fe3f6989e9ffbf56abfdff77d004a189f4b964ba073b3d413575116e72393d54510bcdf0ffff1f4c172e5b269625115ca97708171dfd44ba40ddd3b55aade8a8a8a88b7e740d045dab0975a935d56ab55aad56abd56a35ef367501438518649eaaa8d56ab55aad56ab79d01fe9ec745aed76b9546767f267e77d95e33e15bd57b5f26da3ab5837f32cd9e4a2d39680986a28788f283a2c0578662061f55960fd16f22ad21b8a0cc8f6eb513beccda709824b9ffbf26717f9134b6c224d2bcdc3bdfc7ea6a79b9ac9752fc62ed72ff912153ca548ce225a4fa12cb97f9c4eb2fcc9448230b1cc1bfd92e674c57c856aaadb50ba85baa4165241518193502fa95a28053912d63d0b94123697963a550bf92a945d03f2fd2976affa7a7c4e85aacf87ab64119e3fd4505aeceea1bb86b6e22aea1bb078ae610bf50bf5ea1b81fa1337fc277b0f9617f39e6d06b56a0cb0102247510b5ce00596284f3851c4154e30e960adb5b6061f5cf08219c22ca104083ac9cb096e1212a37ac55092263b34f1a4cb17274e6c5189b842cabdc208870eb2b8e00b185b64698111f5083741e1c15eb1c24cd312375ce1845382c3115a94184af08014461d02045c962748ee155090d45a6b2d81184b0606ebe58a1a85972c668a349822040690c00287114c11a6c9163329a7796e9e33c9262182cad01412601ebae01b9e10a203415d82a3329f3d8284e65176c90e45a61ae571d226d05c32859c8825398fd34af6a67214953d004d8628ba72e5ca0c2cccfc633c4e9072e4055f4039e1441033ff19348b3cc9aa8e58248bad73e0459dd8d6850a76d8c24b91951cb4f8a18bebc08154134a6e70128515485e70e1160890baf9f385807bbbe97dbf79de77537f5f08d86c98fa1b522e05e688b5d31008f75dd87e432e1c63a1c3a6ffcf7dfbc3fd06e4be0d8170bf85f8b3c02c937f8ce8ffd84ffd6cdfbd7cff5e08b09f7a20f653a10864fb2e7c8771e06bfe1b995d30f5f7132067ae90080e2be46cfbf153b65333fbdbd726a6a47207676fc0097247be6784e7c75a942f71748fe01987dc915fbfc0217da8b4b96a0007e562381a70824c862079b6cfb593390c662061dcadc9a59e2c3dc81c09997ad0352a1a19302053ee3db04648a63f45ee7b0399faf07cbfbf03784e4d231407acc608ee4d11ab570f87f4a13e3bd596872cb616ac68e10bfd23a4ca6502b422b43801921034e952a72c81258621ea0b2e822f5c1430d45a6b350192db4215a0285a40e22f7c0ece52e9a8288f1fbb5e0800093cbefccf1175ac064af866a97434248e81b72c958e6a68131c31b9329924b11c1df132c6568778ca5d964a4378916189b5d6da301f688d59d244eb8b314b527004e83b526bad958b0e1c145c8078a116610598302ab8428c099284f1c3064518474a66b8ae219686b052a543681e2ee575aa6f05f290b0fb52ca5b94650f7658d3af3209bb72c9089228e3910273ae091cf852bc2fbf1ed4f3f6f8debb59db63ae902b5117b61ad65a74a28eccfed441c47aa7bf43e6eda6783856ddbece3cae3ccf48f6c231a65219c92a8cf11bc9d88570bf92592564f51cf72bafae563be4abbe7d16a8d330fb1514b9d48b62f35831d63cdc5bee6e7fef9c77daaeed169dfd252592f288979440ca7666bd68cac9f62f879f7e3a2c70819953bf43666ebbb5077f5c0a9c92b2147024a9a3af7a5538aa8c641a8e3ade774672178eb1affbcf48fe7aacc6ef57a191bc0a475927a4be4a0a517d55a55eb5a382ae56ef90d95efbd95a6b3710079939ef9d3766a1aab413cf5a67752ab3ba2783a58023b516a50bc7f2c764217c9db34e20e89c73fad359ebac4dab774e3178fc1ca7230e32b5d6d818f27ee02bc834ba3fdf2bad94d254caa6524f53a930265bede05d1f62caa72395024900755cf04542cd1ee851fb77bbee479c5a4cfdbdf47e4e419939b0befcb617f5177d9a690c31269c20bc796013d162313451327b05f929229d88528299117d2f7c059929303935cde67b2ee6a6eb240a542a0bdccaf2bbf995febf101aa02fa8b46df844b5643403000010042315000028100c87442271402498e8c22a7b14000b778e3e76582c1a089324c7511404418c318618400820001162184254441c0020516c50695ce0c5a843790fb716cc6ea340f88e8727bec67ff44a591a7d4a03d016515bb9882701a944cf604088a9ac816dfba7aebf6559b1951f11acd87833f99578132e8b05894b0396bc3c14fda0d81c251ae4becb11b87d6df5b3b22abcb0321eda997ccf0553aecfa2271e5203fbbb049c307d2ddd77ffdd1754039b8b1d2194851b34e639d94b16d7181c34417a142328fc0043766f574ed165c011b932275e5aa5044c2139dae091585a8e578a2d1336986dcef41d8a343a2909289e66c6fb733806b46ba64a4123c90e2d87cd49fc6e03bb9a0322f89e04b7144b6a4664b0e235b373cc9dbcc5d5e9d5fdb4c8ed074022da12347a37f75f2637d809ff5aaa49c1207e4dc0ff36febd6f733da9323a466254a02390fdc1ab8de241832d2227f81dc8894000d566a6afda11e08d31ac4a34960b097c22b653a056ec1c9659af2a2c92d5f952168d6d5986da6a861728ed695f7c6c98bcca5dc585b20d0518e6d7556507c6ccb4a8aa2567ab12d6b5f718f3e062835b4ed068940d137ff25650e8471bd865f927fa139ddf988d7bf7af073e58f322439d70bf6c7bf81e675361d6008a9efe03c9fedd952df8c548de5ae9927cff1c0ede0e57e01cc7b5c0299081af12e7d99a239d46af049179cd0789f2245cb38d45bf2015786f07f8e1a444d1c025635f92dbaa09a50a1de1fdeaf02ea3ad1850150a7045f6e050e9a2af0c47432503e80f843ffd477f8bce44b7a47a9ec4daced89ff4d5e0b1d45d5fc0c678f68eacf4b061d677700538d4a1cf10b87e09d69bf802f9217aecc4edf23d0ace0ceaaac108501a41eb2d5ddc178b53990499e3bd7b804cf315ddc373618138c396eb1b57f2049a2483ff4f90f0322dd830fd256ff00c58a483c6fca47dc0dd3bae6e76f484a42bfe1f7a8d396f725d1c9a65527795a846f3d77abd5516e4b1c1648c87d555170d21316494f28c21cc8b32eba08697c5b3e01b72b4be9a80c61682a1861b4266e0a026a09947b0c78eed58bd7b674232d34f3ee7f09b099ec69e57efc7cd1c6a0907d1d0b3299df18240ac9ed79905926e52d70c2b08e8326c3dfcc503b8f21d7e589eb1260be009d1fce33223a67972f8dac80c55953eb04bfdd6b53088b008f77a3471df79b12c164b189474cf3efd82833d3db6ed2baccfd79e45b364011f2ad740de9a8407745e2a8d7de6218527b48a82ad9e5979aadf0ad542059db4e4eb07f06d3b5a0f0311f719d8e85d7f1080d21523f8b68c310950d4300d1814d8d7b32127e42d4b5553eac0874ad205acbfd234958e4a54458423f57cba213eea0326806bfd7a5852a4fbbad310b100e89ac16834a27c0019f7406e91b94b91acd056b1e086f0326a5e1fb51a64bbeaa56bafaed72b597588ecc825c9f9f1d18b473276ebdea571e43284a417ffad8d58c788d207cdded3c885732d27e8ba41f2c5a5d03f1765913064f5e9dbd73d28da823dbdf12dc756b1106e6793d369c239adfb7dd33f786536e4189798ec9f9ca3cc61a638e4a5ca04083d6548ef7bb3e1bab03d208abb117d6662c2e16ed826661bd35e3aae3426342d13bb8e7c569d7d719931ec6d173374fb16bcecf9b05870168fe1508c6eda0ae5867260ed3cbeaa26c4815302c657c4915a5af1328cdcde240804e073031cba83425810c6b8013a379c53b30b17e3527a427b35937213b184d4132e9948f5438601f71afc250e2c874f691eafb9a1a88252063870888bc49ccd9e60c85ba3651afc0b875d9e0f69f5910d01be54d96a5bc92d9dbe7c20450de810987c49b1ec4a39c71e988f3981762514d65a19446c0ed02a648abf4b43e7910c4ee677dba24382c020ccfe9f8c90c81c12fb4c9a675a66fe31cda40caf4f4698734d3d8d1041ab89e910b5c700634d049f665aeb12d6fe1c771995dd38518b11a01f0fa0a3ebd4437b8733e2628aff07aca3fa00d3b69c9fbcd178bae094903a04265bbc15b8b67701065c920096c17a21aa166da90b79884decf2d5da173f54201b7919997b71bb9ab38089fdfebe4cf5c88ad97802c2ca559d966c49b1caa2065e3bd18cc909659a7da867a78ab4566445521db5e200f5698186f40151831a1404ba072a1cdf1ba4319e93458fa4db89c9679a2b4bbd895ba381cf36372b3dc9bb3500e6dd863033007b6874ab93f0e2d795e650d0d768e9eb827b654bc44a5cd6b404ad7d646d3ca8917eb37868c8174fff3eb4372d147d9d1ff188e24667fa9ee0968a7366c10e2c4cbef1adc26085982e28947bbb1c907cbf8363e5a3c4a5daddcf20f8e96edd4c5060ce4b4d5102f4dbe2cfdc6b523f559a4b7b4df7592db24bb0fe1038d4813adcaa8b42055120979520b5ca66ce2dfb229f4be442cd08941a1591ad89622a79db854716fe4a94c5faf8e129cf94de03152c15338c9c68a0790bde558927e4fb6f28b26fd68c55a61a1a8906bd6302deedc17b2b996b196186e54a13cd3d7c2c407a9e284ec8dc8add9b6fd1049892eaa0451f25be43898ef8d567697653862cc6bc1628fa5672906520457d2bc90a4df8c8ba0d041aad13904562a33b797b1bf06b63a33bb5d9602cab57fa57b4c9c729ec3691d09be1f09415dad8cf364190135a7898d270a88c1c200671b8e1c767971b892a02dca774301a69c277232c08c7739d1a0ea04b05e44f67a84c08d1900afa40c3b704c9b44bbef45b57eec3fe374b339c86451ce7300f0d58c0c92cfc22850e9c33d3806099e4dea1b02589b7d8a2713799084703ca7f4e5ac476d70fdc7a6979f264d810747871fca9237808392b5bb4ca721f4c99a7a82070c65e7794c4f35bc4bd1aa66fe42e76a6de91544f5e6a63d7d77c9e190d0f5c00c91a5bfe856dd3770c5e8b44cc5a6b145eb348b578e9e2c74c6b9abf4e4c67c473c8e91b57f595d54aa527c8cf69020f5c195dc4f3cb351a8ff6c5aa761bc46aa706c7a7d915363791a760476223edf30a6ce021b82aa8a7c3aa20603d009d37640a3d91e0e26b0c0eac4d5165ac679aa2884cec34761f38dbdd1dd2ec38dc3a31b4fb88812546426777cd3f8d24dd4840b301bd1f0f7945cdc8efbe6fbaf30fcb3f6033def57b0520255f1126e4605fb55d2544a1d3df0f77c64eef6a49dc46d89373c6989547b44d06925186815edd9cfb0197be2352f67b36f602d480af3c620ed5a206320a36b1529ce2d9b5038061bf04e5de27b0eda846f6623c4a221fd76652cd83572c9f2391b4600e92d9e3729e0c005af50d79013f851d7c98144ec7d9733a329861b046ae76e4d228129d8d46fcfeeea1580586971509264008285f2bf81c6dff8e9b3efba76dfd97bfa8fa2400e1b00a77a75e2278d42852a52d3037e8897d437e1afad513ba13b24821b0f604640821f003706f93e6f1936c0802995ec54d54ed25b18e5a9e4877c72b7e3f9b2e61f8041f3cdbdb019f7f89a0e1af3fbbf76140ce2bd03b89e16b36e0d52b1704d020a129bb7339b01ac4398c614162c0be8b55792b8cba87f93bb602ef2ae0f499b9191132c82edaf336c43a81e142947d037e217cbd06c9256fb496e75d4cb0a48cedd2cd3ce05691a418996e99ef4734b59626ff43a8677616ff544f830985dc9ff8e4668a5ccda95efe497fce8b890172f6ed0d254119ae31f69affe67fe8251055c84bcc7c1cefdfc6a007dc9049518ae28c4e559c733f96acb5e5ce305cff268f0e234be81045a5e9ec4530f2ebd060c813ccc892bf88d395782fcbbea4ecf2f9e95f91d090f47b2c9d1ca48058de4fdcf60ce8ade0916b2cfe757f6feb9d2bbd9f2f66990ff89d46eb16f31827665765909e4c5d2dc21d2706cc273b9e69fca00db21273ed7c06effd3a3de8ebe8506f5c5626f40927f7b3061172ea62c176c449fcbd5c9f41e30c4f8818017b9ef54531cf731415c7e875f283e601d9d3234f4fec46018ab325e6e9fb0dd8bd58f741a7f7832f8bdb81eff1b8e30bb2d955ac7c7643ea6d1a99779963d6a4d811629cc1975b20f9af9eac32c67eb9b67ee3e18d48ba7c6d8cf7ffdfeee7fba579caf0ccaa756587dd14e6aa8e49e8d79062aefa82e723228a2aac0da806056f60346e1502c376fbc55e1c7223245e9af5b09312f819981dacfbc1105996e88f27b6d2a260f2b6ab12d9b3386f80d78b0951874f8cf63133dc63bb2ebbcd76fc2e8805d8592c5685ca52edbd920df6a4b53718ed2d791f9005e00d1ef679edcd3ebf1719f808bc9c700ec06ace9efbc31d32fe3d033560c3a88cef583e0e2ca13b574c13fb9b297b9d74effd5a4640313abc947bcb48235e330b49da7720cf9fcccd159616af63ab41f5bd0478a8a602d9bc9345c76a1f2c768d0070bc2df3b9762e401c97e8bb2f1d1c6d19058897f729c16fc1cd33a788d110cef8c3e94e29b425e7496b49b33c89321d1774aed8e672925c966d7903d63a11b64323fd284267a03a1d5fa3925e58e785e51b5fd4f6fdad90eb72e7815b31d30f266b8c2f81da0d6983ab8c9e22618eadae426de7031deda7ca17804ef99e46544911d6a769f6ac7b23e441bc96319da1c8323726704782029267afd29635a373c2f97ff12136392efd0528bd44f635503d2b6a7aa3dcb7de011973d9547c7339209d46e40af776701ef86499c7628ba36146d1e12433b552fe630ac42bbe30669a6e871ef3d47e93023ee883ff5300c865707eec9ee9a041f818cd4188046798c741828b83c06ba351ac3a22b6c5d6e905d38ae6e37c40f0eab05871c69e0fcd2a190eb4311cc53219b458caef1b33cf7a71a8e80fba422cc9bf201ee431f7a0b6e77f411ca25b0cb22cdec8c9042bf84d26ef4de0256ec37278ac8722e894c203f5feb8ca2797a02f9d3845afbc130b3d9ec75f427cdd27f7d0449b07dd2a95f73c79ce9038fb03d2af94f4745114b63693ed184d4443adc1c26e7d5f5dc574b4ad1ef626146834229e5c9ed63185b7ee020c950d1ca715589b0a95cb61a2637b8bda10120a0335f616e821e4c2882b6a2bd8fa25bfa88924d14d7e2292e54eb4f7233ace3da17fa6f32ecdf3bf3a4794438f3b9f832e73723d0d393d5596eb48501bfcd3280e11c7b70a73b08927a43c4f28ae722771f07780a46e16bab5c3c937e23d8b55397514cb3a5cce89459f55b72b4088b91caab967ce8fb007747b3f38a3377c599d1fbfda1d4a7ae65002face8fdaca2b083553e87de3f5896e02805156e0d71ee5944fcc450d3938016013a8d0d709d97fa9948ef9207cf2e620eb3f2bedb7529e3901a75719cc88517f5102c4614d6253d2bd9d7f3ef1a96687742503a0360a71dc85094c76be8cda0590c17f0f5856b6c8e9c04d9816f8fa53b6c8394a6c2933e093dfc453042707d7f22f6aa79bc4e0d6fe2cdb17eef233ebdeb8570bbf30a952a69d90df002c1a0480005cb8a6a12713e7de42142ff5848b3fb7ca268e3e2a3b68876b6393064d894b7326f428824b0042c324be77ff4145704f90978a4a6277695ee7ac18805e2ed13220491286fd1039bf11f3b1610a83beb79f175b014af4ffe380c305f36abd6dad40e8e3339f4190bad78145bff7d0ab424c4e3724da2d3e20512c82eeb3075a5f26fed40f12974b1644aafa62b1e97558fc072255533367b0746defebbaf63cd091f31e5b1f0a80e5c22c4c34e1ad68eb0da9b6e09e9df2e4b0e440f8f10812b210395bc14e20474f06d54d54ad78ff27565432b1ac53dd1d64e3d7e9ab7f50875a3c330e759e76a60d351619070e8a1437715316f07157f0333351c899deb20820ea568cb675b71c669325247b479216461feabc23f2797848c42651ab3f4976d9de701e71d5df03180524ae6cb38a5c900f8924638d9271e1d4aee733217a2220062ef1ac5a6bbc00a09b7bcef4cb41146fd734fe0021f35c9287fadb76cde9ecbf00a4f8639982f1e20e837f12e970b55d1111850b10aa575306cc6c88937e41ec789a0f81da85df593a2c7ee0d7185a37efc0d726ef8a8daaa73a9f1b88eda2da7a2624a503349499692a1686c99990affad100801a63f31e2500cb32ed3edd8d85746625b5d94d4225971d6647cdbe4bd87d8bee7d788c2e16218e7a432810dcd67bc6a09266c149f3522282231aa885afc0dc742dbf2a0a645f40e10621d709016b6dc03aa38218ff47f8449dac78300e3abdc899cc161122be8f82e7d86395571c8db99ddfd8a92c2efcd3c0e9c084420cc8b2aa7d4846800d08002259b3ac371590cf50b80d50abae4dadc004c6fc387daf60d0f73e51a4ebbdebd725381e06c13d8afaad461d4bdac6dfb347e95fed32dd84707d07f32ca6244b938a2df10461f6158af9b71d3a2c966fd4e73dea272950b851a9846c02d36ca02d0b4c621e92e8d7300e05c198977b57e34aa1f504a2cca1f7609b776c55828c90c35a604d1f1af9e1e7815fd1dc88852026cd817d3d94a7aae6155ff30f6903703fc50f9ff9eb9f32fe84aae19c40179be7c0e0d927fcceee2312394cab7500696461cd20543469c71922e4c15f3a2840d5407946cb9c13cca6667b9bba34ef5563a2fd26b26adcee9eaad40c506deb068c87c28a25ce4ac7193fb1981c7e03c56ccd12bf8caafe96371002092766ba901f13c105ddae20b71281e304e62c78ef5ff360869eb60b84e979baf76ded16721e89990fef9757c959cf270fdc9007a92d0fe5860f744b21dede2b55f9f9facbed30acb4f27b4623add5a6a1bd44b4074fd531a3e8219129ecc41d81e185dc3b0467fb2b3193cbf141a8f30baccbd51f155ca065344ce171ace7d4a80585a11a6c073a9e3282a338de6fd909d7a9acd2c9685806d11f6b7a7ec843690d08de4758ca4c016eba7dc9475158dd36f00990189bd5b07a54f0256b2b59f1f41a361447127df7aa29af93fbbb1ca757b2b221c4ff0b4c464656ba560250a491a1fddf9344815e6eb19820539aeb0d3aa57ad05424a6be231b5624c67f5a3d28fc18442dd08512d909714dc8874a1c284a204882677679e184128b933d9e96c3a196bc83fc902ed313405ad257de8eaf0c1ef734314afa34d22b70319cdc419cebc698275ad5a7e0f7d0e3ea6634d65714e8183f24c12e8eed247cc5246843acba50287daac8dca394037d93e7e675890364fade86dcf4d2f603b7ed025608e698181ade8fe74a183a460f203a03301d1b6d687b5cd9af18f1266299f29bbebd3d393958679948eff17f5d158dd1457c15827d625e69fb0d0994f255bec67998c9ed902f3154110514b6f2984cf68f5cb7832b6808238148056ba51412fb86afb38d65140d111bf3e1a0d6b5febb1cf09dee7ff98f06b893b65b05a6871d696b1708848668c8621a0858a0aea2316c3a20e1e9346114f45f2c7ab3ba6529b167b7dcbb1f478316de045103127f2830ecd763d92e2372dc5be9d1daba5e0b5dbec18763e8256adcd88c32cf1623a6f54e8bcb99bad1a52ad75c164fdde84c114f6257022b386c038193941422130a3cfdfeb95c416c53b25ebe54777de417e7a79865af4a207329b3f84f366d0a115a3919490b917ef14941df2c6f27cd04f0aa8106e628e92c76b386abf9a35580c325f239808d13633d2359c95f935634f16862110ff3dbb64eda2e78e8b3408b41df9a142ab8e066798910c42a2e2c86f778a7c4c39fe0536876d398fb901f780516ee6f9ef91c001a22391747873aa6dd654e70d66690494da4be7e5a218b911f7c3464ffff6ab5c1ab906105991e9bedc147b01a25e2358f3e3eaa9f7e2cc1d9d06870495f6b00a236a19e17dc205baece553bc2f94b87286bdb113a4d907e11fb802b7fe0dc3748c6ec3ad468e4b3ed691b59714e2f8d78834f7b986706e245d9c5cb9c345d7bb1f6a1137232585459feff0a33b540bbe0f55bb801b70a63ee3423c03db16171fb19821ea326dfe2b6b058de401699069f75d3126a97a81111305bd5b336deb48c95a6c1d60130176523c1a6c1cc658912e047c54912e0da4c0514ba39f9e0cc0d06cb97dd6c367b37d6e4b20fd160b7ab8bf4bdd8fd4ac9920bc6742307d7814a0d64e44f9b0270d492111e8cfc43606b7f509b96ef2e9b1afea5e5b380a4cdbac54b35c5d77e0a53649601884b4e9dcc7265c8e498f944bd43268364c9c57306939b6bc52edec8054d740555c151c6b366a55014353356c4151efe858e441b2afb29f095e4aeeca0a9ac9c91e50b5616c75e515fb0bf67b528f3938124b02d10729b82cb0f88bec6836f3e3548a278eaaf5e6adce52bb018bc2404dcdbe27e4ffbebb16e9d44cbeeddea399ee4662674a28a2ed795576d3dcabc62044de2618792823058d130f0f1509dba8dc43108731970f7dbd05fa5ff17b93b6ce01e5141f63c36b34d11cc29939564804b9cf98fa249f296e16bbef9e1a791500b98c12d3713c4ac9a4cc2366519616a1fa155cd1146e33d8d35c111406c1e900cdfeda1e2bda1602fdb3479910d1254cce02482bdac45fe2188feecdf23492ab460f7765237debf4fa8f7440004671866716f439c3939aee079ae57790828fa349bb9d263a6fc53de1aa65f7c8343b6522fc59a19dea1efc54fd92e99485c3eaa8bc392d5adbe0d2c742cc64d08d007bdd0274dfbd7e4e16846dd8fbc017e984929526eda1fe97c23e71d8381d45648df37bf40dc991d9685a022fcb2430f73a8d1b755e18dc7a335c21f25552c127023ff180623d74fb8dd498c7c0c53986b17384ca4e0b1a67b96217030aa7a24d6c39dc41cb2103491bc06e0cbf638e0967e4d8961d5c5371bf354eb9a6a7529db08ae3ecd5bf60a94215cac55d92a107263f92e2aa3d2f82b120e70737471560f7b132f5d6195b82ceb476ec9a9f86afcdde12742402a5c9b227e582e934e83f8e04cc31d5072c195b491a00ec454738a82b297e36e1b334539516d6f86356708a0ad91bd17a3e2861af30ca145f18c7c98b646ad40de9478993f9918c256e14240ec424ff2200c2c2e9ee77c057e3cedbc9139e2b92032df6e4764cfc00cb65aae672077e2949452beb6341536ef657f79690eaac86aa564a4ee53279d7d0d1bed754bcbc7a245f24a7af98b0d3441b6a1916bf360ebf583cb3968289a564e3bee2055ff209388f81058b852e52243c1c49d7bb9553d1a7b649ce25e6c4a9c335932c39808f37026ea32410be16d7f918bf9d65221268f8996c39bfde4374b02342db7ac237feef2f9ce33994f69f633361f40cd70a9e6550e6c6c6336706df0950c26c55525a9f44bab34ea4a8afd1217c9e9d685badc00a47e5b009e6b15417f3c4591815b1640ce00f2774ba163c94449cef009505adc4ba48b53c434ba1431f1f818916286484b6ccdeb4f76c9259676a760d0f2e9c4695fecdbcd208b0e41e48ed125ea7a28d9be7c7e9565f5e8c921f2bce7c1fd8b03702d2831c039864015159fe7ea8654fde8f5fbce48815bf8955762b616da05f0e0887f2bb9f543611ccf635d94c3a751da38183f71316d079c35b84d7a9e628466c05a04bdc88d3979fdc3fd67c08e578541e6bade0e3161a7a8670a9d294c18f38dff319506a02f9bc8ad80b10d6cce1fb2e68f083b03d83cc02ed8640bd439808fa0e902e9b1bdb51c9db84a41534b246d4e5c3ca981264787129b1164a8d6456cd57d4187f155311b785b7f2de477dd1147a6c114e445221a6ce380101048e1501d44f5898136505f6841ce181730c0e68bda980f9804f9de6be6d8c04235a470124c35c3275b6b78017d61b3f6b8d436161f09091c9ae7db61feec1a27c02bbef8b141dfb41c8e1e268841a225e1fdc6237f6fc61461c18aa4161d06920ffe6744c87bb25d75a6308343d378e13eb44ee71f45f81b16a12a7f16725efc8cb585028e814547180d708f0a2c98d102e0f5a93cd99ac08949ecfc28accfe672a8b9dc0b44fcbad8fd1d461c76a4785d57f3b04ad5f994c4f8a3f60829484df216ec4399961eedee8111bb59b041e9232cb2e5993bd508f2dd3f02caedecf428c0189316ba87f4fa5819c0a9da6a8396dcf9874c9bc4455af3a5263274cea2697faf1f10784c703a3a8079ccc0f26eba12497080e1e73aeb9a8f995a63cfe5a93ead4e1d7ed9921c0c21b78af7ed9d9d516ac8d00726dc9d5c7922829ff2654d64ac3df22dc3e9f27f78b38f3f6a80d63e1ddfeeb59c42768c893168edc5435f85e6a135ca7173c5eb892df138fe7099c7c8829fe128ef49ab75ebc582d3fec530e87b688670d2b3f51a59b437289555cd744da04f10a7100f8080d8d13915d5029a6771b63da6340bc2c2b6cf1e7d87b1df146480e60fd6c53be531c02d7f31a4c50820e5319c61b4931c81b57f89064d8a1f5351c85386f431286892426ea9e95c099215f68c23817d38315e580e39936975036569855f2024f0f339652b4c51e85c75740ae3d9fcb882437dcec022489ba24b4e6993678fd4c85cb6493e69b4ac192fd4f77afdd3e09c51749d9b93a75d90c5ece0c6bd9eaa3a37a5612c74ba301f3eff94add6417bd2204c220f90dfd1c927d606604f87ca2dea73556214ddea13d917414d96099315a11c3d302b2b70961aa432ee50dc165bbba49cf4481907e71258e3e5a0734d19c2f008206abe48c8ba99067c19fadaae9a0899c96abd2ba3ab69162c91a215d5da5881e614f751c69a74c5d8ec0541a44321a03bf1bd64ee391915cd5038d361e37ab2792ac553080363067d2aca54e2722fc544d63423332d8bf308cc08e3d08c33f425ce8e346d51f7e7d94c3cc1d1b7fa20bc3b0b457824f69f89454ab1e1384c73e0e64fd7801919cef697dd5c24df6385aa71062d7e3a04952949e71af3804bd0b3d400cd8cf99a4d4460f3a05c4942d15477e4143f86439ee2be46286be9d90229b1d7881036e0aa80270a7e47164d5b8fea73ffa9d7657d06ce2b364414c833e08604ae54943f49c1c4f7c89eb0bad3a643f4a7bbbd4814726d02b7bc29f6731ed9445a2060d0938178914b0beae8f7e099838356247ea574a3f9f003858424155aa5ec5f318ac715bd218531d2626e1eae94eb99eeb1660916db862ed52f8fbcb132f1c00e3c1376729e78b6e1d22f022392c56cb67974b5fb413a1cb33ca5dad651c35be0ac24951fbaf1c6a8b0e93eb9714428b36af1a3c5f6995523ff86ca18ada0e1b6cff976b0cbbdae3162c284d0474c76219d3142c86f1a0f83351ba91fc487d863d747aaf3b0da2d72cec230043712e2913822629c2d91c62a5338c173fd7228d4769866bf789bc1e85307bc81fa78b8c26c24921b415ef83f2ab4875353c289c387a9e613003ceca0ce96edc0430c8f7db88a81a27067ae909a2b2cd2c8b292459df237653802698c8097552afb80ca8ce6c782812248c4bba259aed93063395176eba28cc17072f5e74c42e4ea9081ee104b169026353b9b976470f4c72e6fddd8f1e26616b5677e805d3d2ac71d51f895544efb86f0793ff0c8b699ea4626fbb2336039e2fc34ca11fbf1aea1ff22461fd60bf7d86bcd6f8f9ae30059de7583b8205e240f05a6f43b61d1a23545c3d531801e60f5c0794345072ea829a7f5084003caa1404b90c9ed84876b9c9a7499308a3e0241a4715f779a33cd5f302756b22df7357fb1a26bab7bf02d2aeea1db427a784fe1a9d878806a85b1fa13fb44416ebdd04746a4245ac7b06423ad9b21e193d5bb0ebd73db5fe8db522014398110173f5bb90341e0d9d38ac4f0990d553be5215d6da88cc15fb5145562bd38466a40dc320aa757493a26e4bd8cc12c5200f4c601ec99795c9210c916bb8941452bfca5eab46cfa7f1f174f4044db3d25c38b3349c5675e004c4d29afccbaf0a363e9610678b55dff82947071db794d94d186aedb80f6ae695c2194f3c7cae24d635670899d996977f0b4e3d4ef54190f2e9eabecb611d8e7836551dbc35009415cbba97e50b5a864094a55b5f20bb49b14d7e15f64cdb496bd219f8267b205aa024299fc8a5df6a7f3f7e94a12bb25854e64b6e281aa943854dc3c31a41a6450c3e1207acf8af9259f792fa99350769d58b6df6dbd1522e7596b6677b66a3f76be6f030e06acecf5f68d8a40c61d20a2bb01c22d8006f467573cc7b600f9bf859aadcf1efbaf2efe1590c3541f316428d05647046be359a52d6328fecd3f17c93f8f6847b2314e552497b4d47dfa1d5c91966a806d1636ae7df95be10bb5dc3d0c5b3aa1157770cd26017947593dd2910fbee83996c68d2ae693c959c7a732c8f3d5e9298123b66665c4cc6e24cf14022179dca80aa8292124653762758480566c448f9079840b2df1d0b4a2b50f644264db1291326804cd95baa7bd0cfa24e649777d366b895cafd16a72b0208812025fa3752c8e5bc6818f5c8ebda7fdd367d7bcf3558e3c7e05de9430f1e5672e579c507279ade88a4b255c3e593f410732bdacd11f0d6ee8464b71c0023ef79ea2a7a406dd5e1be32e97c074e302f73b5d99661b0c36efeb5c721d8297b8b55f92ffe75640d633ccd3a44a2c29f639a6361e462db691cf6b32444d6fbb6c88dc21d63b8db2cda2f012b7b46bbe2b253f4a63e8d0f5c2f637fad48f901167ef8aed89c56b4e7bd5f515c605b809ff0f41fa93b1eb003b85f02ae24deb775aff054ae1a8ef034aded829c7a1f7ccb718d58ff522da9f9393dbd1f130bb1dd5cdb6ee61ac3822826f9f61a678d6f207f3edf9538b5b9014848674b5071b07307b715b5b6f93c871ff56b835457f63366df690e1f806609675b9b6db0af3571b0f04420ca7bff53cb0fb062a0c609fc715ebcad55fcd9497e4e898693c690d7153509f252df84b7e9df9533c8d022876f93c39e017aaf07be45c268241c50b613a830b550dd77e7c9180ee43bdaa5916cd223c1566e96044b96e8053556bcdbe2a5c3b903eea33055eb817d7c36aae87d795f168247f29f19c195617b2e40cd6179ec4bbc577f86cd74d46fc1dea01a4f31f0cf07a7851a87fe99d472bf65330d376ea7cd3a0fe1071380d19a87dfe20e16e7399a10ef84f1560b71b9edc08d4e2d22a6f78a89e383b1edec4140b84191317eeb39055ebe31782fec44a591466b45583cec81642b3a89a6c32881be04c743bd5483b7d9757e0cbc73c3eea1d1155f35bef6af62a5a15b48e58589cf7502dc6432c0da755dbb264a596e9ff0ea95e14e0b0562b6367b27442107acd36a6b2ada732efbb8346349a3cc2b148f8e5894cddd2dd0fa54087e800e8bcb067228008870660f41e9614fc98335cbaf334c659e3fc72b442c32f9f9e23840b860f11fa5bdc2bc99beeecb108f33be8fc359cfb807a56b5b661fa08b1cfd2ad182b3eb3d0f32cf68dc0ba23bff5e92fbcb72b133c1497be6a76886014ef1fdd09dcdd1d58094a7ca599d2c0fd99e351d9695d3dc6462e97fce7b823665d7c94140b893c51efc054de99e2fbd28fdd14b80515a91c98254199a9724b94c61a4d07c3cb19c8ebabe8e3d1fe79488b4652dfe9c0a491f67094c6b0602ac27d3e8e59a6bae0f71147c1d6da1bdbf6cb046d5f7d33d07d225e9cde1d3c43c479ec8428192e2563bee28ff769ab87badaea5a9cb2478527cca698fae8bbf59ac49fb63b13e712d1bc2591f2ab0abdaa9d3e11d44521deb07cd5705d453de9339dc37128e7ed3a52a8e8f8ae270ff56b3e9aa27b596aad85546b9f393cd7e05cf82d89f46848855f0d46a375b08c35fa8b37023f32bb39202bc853543d2b26be7e00dc077438d16d4adb5ca5d8cc13ebc0edabedeaab6fb9229a9cb80e6a4ca9af6df21142f93cd4773821d122a916530cc504f05a0e789dc6b5c4d45c0c3c587944c4178e3685b2c496e4f523d2499a8ae5741501b48646f4bc853c7d6ea48356c717311763a3b16e1e8119f78e78835681cfcc4a1d88308e58659ced19cdcaf467c6383ed4f522cd1292b6d920e4a3e8890df8d0a0beb3729cb54ff02580747172ed7e1a816d35123e32e336c26cb58e608705a78c6346d54f105dd93b8849f1a13da4733d8a46a65b54d2af3399994de7fdd3a6f05446c6a8ddc0e9908c15dc15cacfe11061ac56d0501bd066dd121a212dbc5dc880b641977c325fdfc57cc020f8ca1555e45de688208b5d3533425e706c52bed8dc4a47d5dd7c4bdab9348f25d3cc78f00d0dd9dcc4eba2c731b3f2a7f822858930eb8f8a5b24c8ae664f3b736fa65a9d7cb44e9d7f287492555acfd877544a9b8e83dfd1c2c53a7eb1f48fdb7adf90333a803b247492f5212cb69552b7c4e185fe58c45a12eeb2975309a4cebbad6fdcbe1b25d57aefe23ac25cf7464591e8abb495b4a31bb4ba73d06e290a70055cbda55a452ad41dd15add8b1ce9f8b1158bbf5a5d183bd5280393ccd0e3a42d1779d5113d01cb1539dad3aa061b9e30093babc42a2127fe57d97063f7798610b747f02c495b55f1f4a03a38c706466cdaf789eebf82cca093071bea8499e16fa2c9b57c964415419fa240267b33a50031f358b83de58360e209e0bfb43fc69979a47a799f8a5ba91165129a9f4f59e2d30d61fd6787640ae1cead847455f7d5bc35eb7f29ca7441ad3964ea300e5ef3ebba2bac00ad87d1886c8cc178bf4a13493b29332a54fd01f60405551afc63ec6f9fdff2b4a75c95d80d8d12210192192c6ba568b2d93069b8a8956c10b867d5e442e179426a2b88b213bba8cc037687aa499e0aece384522a29388718309f40d5f82942e2a0f337296f3328f75551eb46ae97225411cba07b7e4892d3331b9a570f154ee6cd99293d4e7d8525187915f47af3c6b395a6270a009b0b258fe00cb0dad6f668d1b71a335a551ec3530321c3ab8a04085574af56c120880866cb76a6587993ef5350a9642e810d2f4f32cd927575943b6834ea1785424080958a12a918d49d2a6be5c1a84d987a5fb3aaf54d8c51aa439b29aeb7d901c9d0107f42bfd1833a87713aaa22570a4f5f96103419c0771f667c33e5c4d444c38406c5562f7b2c145ef83ed86bf8913f5f070f3e0d047c5fd9106249524af2ce9f384926023a33fdf978a4c6df9a03533b524166c5c83334bf89bf4937ff87a76981b750070583c7919014c5835284a422a9cb317cc2fd629746718f3726df44b1c9198542d805a578327e6f1fd548ec43063922866ab83303c1ff9275319f10729824c1fc63a4a9d9b0c89040c238079202a7d0ffb2b69250761152e280c439a2248a70ea2cf3a5a03139a7509e1e3f7f59b602039686a616b4f5f9205e13084a0715fc243b0b8f36239c78bcb3f5fa7867569fc3fdf250e8246dfd16d9601105480788348641dbda7250e6dd392e8bace96ae0ec21c21ed4497eea5da02408dc13a9481db8e0f166cf55f9f01bdea053be32c85d1fa2fe860ee6aa0b9eebb58d8812cff4b624c8a3c1dd43d41e7dae38e94ff6df8298a7fc060102b0fc378415461f26c79721b8faa27f2bffc059f603501228034c3430c865ca3def301c5462462777aaaecb279418d7a7b7d688cc9574cc3ca50094ce3e4393100c51ba8d026eb1888cc988c6d6cf36d056b4e181798d92b630d43b25cf3f02195aa0d4883828a997c9616601e8cceecbff89af12881a496fce5ca5c218c76cd7511add428eba094f587f0fd928198e559c1583e3cff159b1f98af8d2851ee7191ed3bcbabf45348451c26d16910c2932fed577670a07b4308c53ea14ccda7d415d5498c8ace6a6460bad15ac5dcf1967edd2f14610e57630a089742f8a9b13e549ede8ec60dcc72d5360c1dcb25d3ebca23e3467b8830cb08ebfdc9d125fa27c2e588d08f20d2a06632d94224626f56c524e9368bac3a26b52c9a6855d47d72bf04ec0044f52131a431468949f7251b3589fc7549a89b931e9df73ca42375802ee658763eeb567e0698187ebe5f7c15843b7ef6c2f9b375341ef43cf77afd621ba74be86555d996fe834d8c11c1c753d0789ba37669a0996e68b59e35ddf31edf2b04c571cf2433c5727a0b32b1d159f77c60e29e556bb81bdfa3b6b3346f88a0d55a532364d5fecba74da018e715d91c9128acae6558a70269fc85e1d904b20b0aef87fb5b1566f1123053c9dbe3db0d4933a239d53170007ee0119558b90cf63d961f5c15c6992ef22c95b022134317106c7d0c56a1036ac8e167b40d2a44a4c62cdadd5d3c77b74a513b7401f98c682b8b020215be21cd5111ef0ae699946c75f7bd5577ae152761ad8ba03fc9f2f097e265a379b954e2fca6575a2e60391e2e84fd160447724e743ba2571b85126a1c76fe514a1c3de9a781b2da681954eb25662ac8715f2e5069ee3752e78185ef019f9cdd6da7c7dc8a6723ec04ff8f543f441d2661d9a808dd914b8cf7530e1d6de2d74c6a9de31cc5036250388ee29851f0e85112066f01a04fe1ee405592049484b0793105390ab6a5dc7168210d89269914eca8fbfea500c077f28a69ce1a366a38fad2093d0cb276f721ffe4c59c1ba6126fa8ee071af515b3de22bc96b8dcf6a4ade847a01d7e4135292718a2d2e0add0384d7a79d3e56cfd25428d885e55ffae1f5556d0ee2f5587346c220e8003bf1a7916dc3ca915a4c8e71b2bc18b5edb5e934e8625bd7c7c29189d842c36baca0a7c06b4c1ba8f1a3579306f4caf559f016df8a5c5ae96a902a7c040f91e3c5fb9848df1702bf09e365c663061b02fd199f3d162d24164533affc4c8f6f8bd8bdf3b6c901e2178e43a27301a1c5902a1eaf1fb4f85e50cb5a2c85b4c02d20a4a5bb47b118072765f140e32963e40f5557b9222666ce8641b8bba8e5c592efdf3d38c801c5aaeba71183ee74c05d48de3da28b21bf531eac910008b6a7c3baba9e60560f036c32c16e6bfd7deb9d16896f5369d2579dea22aa2186a3869be52b3f636f34ce97c0de6d9601f8bc195c59288260b8228852f382aee59d7b668a1b763ddc3134f99113ba7afaeeefaf674025afb1db183dce84c915782a9228ca94834e4cc1f265b5fedb66504aa60d1cd82d548a830e8dea02aca52ec47fed7cd5d296e693c9f7ecfc67a80cb721aa7419db0ecdc2c08d6ec1401428972bdccc593043a5aac9bf824b70ca074d7f63aacf9ba6e72c9a248f78753b509971f0aaf4e79f386e7c1811e8699d46217d87a31d101becc98f8f8c757e510dcf7826f05b43f969dc401b814051845c36cff8c00fd1331ef31efb95bfa324c0cca391282cf75258585e3346cd91b21fe5da95ab2caf814a6784396b7469f4acbcb3603d5af5bcfb15c84f3bf4c6754d404feae786d2f176fe7783effc714a0f7910cba6f00bf6bc4ca2da359ed3734c5f28aa257b84e3a0776281ce2d7902a65c96457c311c89041abf41fc61ecf7647f0712bed908daa0021b0700b1b0c68bd720bef443c118f0baf120fe7c5fa213b5d23c017cff4692b9dd95df857295628c29cdad5c08cba7ce5ba53514834d74a4b660f9f1fbccca1e83e10e89ec0578c0959668fb436e51189053dc331d42a1607c679e4090c6017192103872cfb02f300d47343d7ea10117fea905532dd6b61630d189c501325e06b64d53602cad5aa31e1fbc82c2c8682fa16dc798b0c73216b402a849b564a524e27e620e0855102f3b91be8434700030511c12e84a21d75dd77f95f2f05e1de40da2578d4875106cc53a0864cd573bdac96f65b36950b00ab1cb04bb2e6edeb1f6c4ba90ee0cf09bc347dd955379566a24c742f3ce626edf6fb38ef185be73ece603ca1d3466676a39d692c74f04436c495d269b866c09281724f914bfcf6d0d5be93159e8abc18a72206403c0b50e5f271d7d66a047584f1039157516e222941ec506620025c6981f4a6585424d87751a7eb762dce3a2ea1207608e229a3c72abdfeb845a2660b28ad6393c294f5bdd3ba0527022580a06847c71c7d98f16454a88799e90a43006d69ddcbafdc6a5eca74524bff1002a2ca910682fcf5d991e3173ef3fe1fdbbdde64fe6b2a70ceacfccfde4aae81e146ae88426669d7a754ef29fb52315b25c48e2d84d65d9bc3109bf3c852af1a87189891c9e3260989ce8920dec1fcc835c959d088fa1b5483dec5a5fb397cd273d77942f7389a656c00fec3284ca0fddedb3ad337e8476c8b0eddcef591187079ba00a7a14e1405ffce9f50580e6aae652b7f12c1fc4d514c239bf2a95559663cc0fc85be6c58b023bac754404064b778205e52d894c70d6b31377ad734e5081d885060aca23e4dccd3403c9d535b8557279fed82c690aa12818c3edbb64742ad8f092b6046be6148ccaf2e0363c7d85330eee4afa7237802a6820221863e51e3fd6f9f5c3989db6ebebad5017e3490e1b5a9cba19b40dc91b49f9e914a4ee728e6a3213f33e1c94be3eaa61781603e32ba41ad8a4c5afd5cb9a9f3579e4eccb60e4f8a7739b1432a81a63fc3b5258f0e791d65c7cc97c94bbece688256fe0bfcca66596bc717d75338d89925da126fc4b43e28b0c02d6999af0c197f7ad3d15c4738aba0092ae1952f60891813147fc45c8589f74aecbba9a0827c72bf14fc06253b1971f276c75eb4c1536cc634eb220177d8035cca3cab29519b34cb2d55100f7498bc1f79da8533100e7cb7e9a87b35b5099955e5b47300d6a5fbc880aa020b7096a66bfd34ec19ac5467fdb43552b501ff4a4de5548a9385aa40b3edbcddd0c6fe3702c6137d40cc2dc28974692ea53048e60ba53e777e7ef3f4ab010c57b996431925994d498b6d43c15960162296a1ccd0a4499506644b98dec60f25e7e3ddf029ff067f1b499d4537ee4627537e3b3adc02a3f46cb0588341f116ab5d428cfcd860aa4c1e0d8ab668e0e01471d9597e7e0c7674e242dff7bf02a161d79303d0267d6ffe279dae4e8d0a9be9d9e6c057c8678b79b8afa40e9ea24387e2a217f6c2c4a1e94933567a43242d3a9856c3eb5a175d445abc9a8d09a7b5c586b22d8d0f535a88b6c64b46b4c2f7206ae474fe843962056d41c58a70fa86dbc83c875a6ec6f9cd9a879084ed19a3999aff795a996c9c084b75b33e88f5f374fa92e1e03193c6e45a02ffac065d5ae1bd7d62232c4dca862914b2aeb724f14f498f11c528cdb68b16bc46482c5e5b3d13634d3ec8e67c3e552b4664fbc3cfc123082dab5e560c1aff0aebcb9f4b6ec8dcf5b1b231baa59c32b5578ccc54b109e43e7160ea7207805dd334c63cf758e04362c69d093c1b68c6ac2de3bd707cfcd8da8066765ea5bdea941139c0db092dfffea54808cef8a232694177ab17d7f5c97289389347b83cb33dc72ef157b298766295c4111f8ff25f8f1b38ba00af6074eaee86b17e192db2932c3165eddaa1ee46d355232ee844456497ba8e103a5f6e611e7fe72f900798b31747a88eb6800d09bbf09d34bad628dc7a1b00e1f81ad874619b5f1a8852aa0aef05fbb5b5990e672ab6332d7a0fa638c14a0a0ee024549459dad99a125d15af537c977802974e4944d1000633b8fb48cff0945154ee69047c5c1050e0aac8a581f89c3447aa458d114fb42239cfbd132abdffb87b08e1e15f3354e4944f12f4e85037394897ca3d0a512183ffca8cbf1092e98d3abcff28f118a3a72a0c4f6ee83db674a0647e513684846c39db1c6d4f061eb3b6a147be006d31a3eae30bd86082595a0f136d6993e0cc071c81c9b9112528383107648ce3aaddb6e7be075ba1f4012c601a514368008dd85b2ea506d8719b805112227ff171b2a6c4aeb290d42437939b2442592cdfe67a48ea5227388026a0b21283a8f870281d2fe97973738efb8c499aaf3ded77fb2819a82efade8eba95330e93e2405fb3b11cb012bbbe3a2197220eb55d9de7d63af674dbc1064eb63ea6facc21cb214a1fdf9d8e96f05c7946005ff67294a825168fc654335ba99bb381fcc4216b76da9ecf19989e7097df11dd7e6a4915717d7f8b2d924c35506e02318ee603c74061c9fcc813ca58c1af09d21c4ba754f020bc4609b728edcf2d7c2e4a3248dd5405719c9e98df80bbb63c40c8e8bb99c233688a6dda36e873189e228c9574d37099dbe2dbb8f318b86af32aae615e67e46d290b256fd26a398d4fca71691f8ca390b9c7af221f079b2e02e1072bea6b05be4bcf656704ff2a980bef27498ebccfe51e289a86fb44a365419be647ebd08c180e2d256aadab69c3080a499f9030dd9b96f0779117d964c0c8d0936c88f132cfeee15365592e46daa6f1f5c884264af51f876c169010a69dae6fbc3382f048de9bf61338b73f4ab5f02d2ca32f84f91c45a7ef25a34a9d4daf98d36a26a1592cfc366999369f75d1fda2262695fff49a071a14afa7bbbe72385e9be60a9b21df9e09f435e88c09b615185c564375cb2d1433dda29d0a9cd5eca59376dbe4ee6b4f467e2296a18aa4f81ac02213296990b283a1d36c579e8dfb8f846e4330d139c598fd94cfa777f3081f2bfca1cda029e63da1b946f213f9da70cc6e29afe479444673a73ba945a1802fd87f7f299165abd1499c79da2684f26c901677ea1e453cf7a39e774b128905eee31cc996dbcc17c040c9359a9f1bac8dd063436c783b225e722f0312bf93730a1f5cfe16de425ed030b6065e85f8fdd6d7734ec1981d52205fffe8daf6d5224b8137bc05981ea3109d4fca1be0c810e99307cfb8a4e131f1ab357b785ae0425e8868d71724b79f5a71cfe61b8cfdd9327de5a1431b1bc77fdc804339982b96e026d4ce8ad1f53cc98001121e2f5225b4fb84a22469deab4fd2780d8e76b3703d467a465634aa08f79f01e34000bcf3f011b009c28b42c75a45111ca824441c147a89a7cdde1b242cac3e6443f8c2dbe8a20151f4962d6f62364420d65fff06d5f034d2da4b2746818b286b09ea7bed83fc13b256318e407c81384a7063382ca1f8bf6bc4e48517fe08c6297b06d0cbd6019414690a29fd30cd9c4342854e4b67f347c861beedf4b02b3d424cc0ac0a4b78410952cf11b4f2166f90f053cbf38fd104af7161ee364fefd5211423bbab5449cc5e7f035f2a2b1bbb313e110d0ee4d1b6b58d44c1c3de90c0fd0beb54ad1dc064b6b787c8ecce420137a3ed3417039de9765813397e3cd8bdb4d0ff0a2b7d9a6ca0031f1950fd96f9012f35f4973175c656443217d9fc8dabcbb458d9f112ec68a7987a40ca812b17fc5bd67f6ca70d89df2fd88d6cbe9f44f2f924f355860541ae6f936a94a6fc65b636c0516b10c5029573b44d9a529926d1ee84abaf68daacb8d4af046de4cb1747fb7a8b9d23052118b534a81f5bf6078bac85b7666cdd3e3f5294c198a43cf0e9edc3279d288bbafca0caa7ba44c50d432ffa00869c0d3e1a859c73bef7c9142888e02a6188e5aef0f762b117949480a72aef49fd642f2a7a2bcccd4a20a0080b249aa1608392fdcb81c6249d32d21e83c2d04c65f5542243e052756976bf7243471efcbace1a614f4ea7ed4de64a7ccf7a5900212a51db03b34be2f41b563ddd989fd079a732d01569dbf802727ab65aec2fcac2df7a1b7cfd05749930a77644d4d55c136fe5aa47a2dee5d9004a373c0136c9e2385c666fd415dc1a4e4d3f0061d313db364f6294f60f2f0ee974bb3662bf87ed336634c4531f8b3d051a9fce19790eb924b833ebe8c045ad312ee7825df5b41d45516708d2f9ed2831f2ca1739cdccffa17a318e66b94011c975c1721d8b63593596450b23d0e9b6cfcbb9ea1c1285abb561b1cfbb96417c8b4f922f6ab7f2b0ebd44b9c3ea59178d252b384387f44bbe22e05b00ca713dc5e67367f98fb8cea641c7e803d7493737022d1ac1da6b41f48839342eabb19687401211ce6f77d45871e878f9d2c18b1b837d4611640dba82e3fcb827ea1bbc432541546c2bda81a01879bb2fcff748711895c0ce8d47338c150faec01c85fd8f7b8707f37707474b9fc378479f3f5b93e593102acce44de8a84fb2c62c350716904f1ebe596ff6874ce5cc800a16f6af99dde74494af689128fa7b4b0ea2d2b986504cdc4f64ee8a74462ac3b51824b59751aa3191bbb0e70af3a16a865604df4356e8b3157aa03b55f192b45f72d95fc552c96e033a827c6f5937da44d6b16c78c5d43824603f495ce7d309ac420e9061dab768eafa78c0f7bc4cf3e57ef391bfdbf7e74aaeabaa204e8152e0061a6c2a71be0e8aefa0fc6aade9e994bb5439b7ba8de53456d61a84d1d59c867e50ea969df1cebd011d801e9a3139ed3bc2d0524f99c2234461e22545a408d539650b381defaa4f9a35106aaf89c038acce3623a0e9d0eeb3d3471246e03720a60b6c748f3368de922502319eb716bd260d74cd03a364c97f76bcb97c70018dfe9e26f6982703eb3e3ce831deb0a2dacc92b5d8576e358da850fb49e084a202fdf5d97f57dd4d145e48068c85a0fa46ab1836e560827aa32802c61544700fb0e0a97206ee0dfbd650645e99c0a686fc7b5f2d9fd95f693ff61dd34ff208cd4f3c26eed4a6267dbf574c3f60e4aa680e741271bdc522d21eaa5ac46fdce781b6c9a380766e3ec3372c75f2c57014a42735d20ebdc007bc41dcbcc3a808ddbdd24f99af727c1e42b24d9d9505b3973164e6068ffdd8d172f8b1204a558a34d38a743e5cb88797a53bc4de440feec468754caf0e2c341679e6840dcb677103d87f343db996a5996a20e60af6355c6392af3b5c90ab0fb180c9a20960d0e07efe1df80c242382306ef787514f4c186bdb9a514f76cb587dd556e793f36878c9c6e725db870ec5e7c5d06244cc3e4e6d1c64bde84779756cddd44f66391dd7077618d51985ca45cb8010681b75d7112f4e423a217bbc403c3e4432225afb5356ab59f57109fb3ebdcc919dba08b8e073123a789de7669b91a50dbdbc5b866b23c783198f4ba3b555c71f2ec457c5974f358a687724f111532c3d9ecf3c947ba5c1476720ff0fcea5ee031a0f282ef3e319450b873705f0a12d108442166e4494f4d82f2d89c3288c7b7ac5177037682bf9d0e0f404071f1f2dd1848fc8df11f88a1f5be4b4fc098161b2233d17c92acb861e5e78b83d2add5c58dfe591193fcf52413ef7bc7fc3102feb4c87d8f0ccdf64177626125f357470ee2766f9f806e8ca4bd79e2311136ccc045bad10ab2d691de1cc6399433e4b1f77b2eb47f3f39d32a98f2c96a93b0303c96fd8a2d8ac0a37825f07da91a8d6a7a270f0d43717402620713050032c60267abfd294e53a0a9150eb65445455e32e6ed4dbbae7858be4d8841e28ae254101843e3f887e369e7668ac383c38924ea08a1eea05051ac7d754e87324c60e805bc12766e826a6c7082d65a9e4be20c7ebb4511f6a642345129e55156d5d57442846aa5c83387d82cbcd64664cb737a171cb1b6ce97b174e37166aaded1ef2358695be1c94ccdb91495f6692e1114ea21bc920e6380fd997057788767c30cfbef53b81a9f2289eca0a4804b048f7150b844cc93e49c3911e6257f9a3ac2ffbd669a9064ff28556c04215c11ee35d110c7b555b0ddddf12078f2ec44d6d95e3d0686098a6848f3c6a78b55845443ba6b16ddfeef16f8c6015328ed8d5c29ec088ecca5dea9d0cd9b68fa4200671e553c2955620e651ed6fb4efef1846a70878639ca22622116f5fb8b7617b348dd54b60fe3901027444abc18337eec0936a3a880b01a74cd97ee2be11d8972df3af7b2f2d384b22fc6ff64264f698a3d0ead5aa4e63d913c93f13bedb0ba8a5f7af021e9db01fc76c5e4fe944ab6b7964baeebdb6dd3c056e20b70615296a5061126ee1c6f4947ce69e08c4b99c4aeaed95e91b99aa0336c2ead865cd1e507aba16d2f88fc695a47ddf946fe0c19f511602d98ffe48b51ffd11f3353dcd139bedd76969ce674cc201d681c2ebb7fc5ca21e4bc53864de4ef0f96c8c1513cd01e26982aea0bec34c8791f0677bad5ef617643321e164506ee4c16cea4a2070bf64b42002c34866ab19360ddc3557576f9add31d6feff031b49eb6e490922dca732e7a368e6b131910092f574b5573760ec0cf788a16a40dcb9bcb91e2e1bf455e3e310563d0fc8997bf67b76f03b109e46e862bc27da116018ca32887896bda74a05ea5c111daee99100b11665670fa740cddd8278ca3f9eaa110385b60f11db22479dc7c6cc85421681278dd2200b23cbfa8fe618d275137688d335111b2823082cb66c37f76104be175c918f9fdcd86cfc34cd8065524ddc8e741195f7a9785b8b791d996dde34e1ca109887ee18def52ecfb4df4b759483442f4d0112a357e35321dd1f325e5e30eb3c97d8230d1e84c5ac2295f54f12608c78391ae7a69f18c647cafbc20832684d3c06f12e8443291b8072c4c99e1b8cfc1720bd5e19beaff324ae5f874cd0af84ce694d00dd84bf894122bf6a53199d2ea8057db37e14e9a057be7a7dc82a36a8b50bb9243e9c61b9843cb8867c50ef273781088640da142595f80997db8b485275eae9ca772ded5f9fd685ff701d2ed3314de89634274ffb921639ae2b3b2e6dffd395ba0d77c0722060bd2d94ca323b937e2b8056ac6dcf2afa5ff00be75fb102e9e7c947066001e80c7a519c3503a727a3df72ca339f8db338a7a6e2b974d68b5e4a2f75cd283571ed90916b2c619b09e3c9f4102e74fc9b479171f05676c35ee8b2dcac462c8c223fa371d7b3c1fdbe4b04d20ec20075bf13ce68a7d1fe73526ea8cd829f32d79e0b1eea0ec7b7afa64fc135f9c8cc002d2e2a90351102231ec9a2962fb96d8d8f4891e2246ff9130197047cad5adca757c74437d21bc9c805a837a4b62636b5149e15c02e26ebf6ac42383ca48cb489a5c08820deb1bf9a309e0111b3d42ea3cdf09b581346ea12c69a80cf47e7b427fe0d5c641ed1e23538b315cd23d267931875064a8f537965db436f47269a60d6b6e1464596d9179e3c82ef4851c9738c34062613ded7d4cb8298c60c9f7a96f4b109bd75dbb9e2620a1a9e8954c3c68034ff67fedb3adcea225a5d96a5e03dbc81934f53406b997f454af585772711a7b3cc9d3535f66c80d2c12cc3e3d1813975321fde4493058b784769d137e9ce2a4a66e511f2c327e93334e40cc3ea1fc6f03c2aea330ceb0dc3e340073948f5d991cbdb0d2715dde35a11949f013bbf3d4d90749db3b2849907eb584edf1cbd951787be304de7989053b9e384ef79278f749267ab4c62c946a260eb9e66fa223992b44e564ec5c45edc0355c3c0370a9524e4429ed68c90cc30d123bf40994bd14c25a754510940cf2ee9c14630de2b52efb7a3b8c7781f2ec77c2193a8a0941e37eca682b2fd337c74a4bb12a0f1477758d9759f3f07424488568e1ae9c706c6b08ba1b8b868a9146b9667f85e41b9b7664fb433d04d67ff32bcc966df0d2044ffe35ef26bc05ce25917c1b492822e9d4de0064696d9df6bd8b280e1dfb7610715349ed3ff83da7639edb2896ddb0ac3be364a6369e6449a0ec7fa322a11b6d66a83c205378391d746bf7e12bbb1c7aa988fe8c08be9a0d793ef08a8f3ba56f32589d7a2303d665c4568616a7e477a2975a1fecc8ae785e61ef148056da1dcbaae4f2bdb96d37bbe87894292dde0eec39c426a342190168a17698ca7ad319cee9ee5f26a0c174174588ba1b61959206ed371367f505759d6481572db2376a285d09ad2f92378797e10881072dc8463f4a2e00d85778c46642a0895f65b071b1803995420fbc871ad2a698552a936d99e7ee844b306c450cb021fd073532159225a4ce29179c47c506b9429cb28dca138eed324291084cbabc2c4b3c724d1977d463dcfdd439435132eff08401140358b5e7abcf00decccd5df4012ac2530a2990c6455664eb910c589bd951c91a8c30c7eeddd785993d835d1000f87fa1f046c26fe7a034b14f9beb1b1e89d68662092e629c149e423a359f7659696f27457496e3e2289c1d84321e371dcad42e5ea4cef279ab217b78dd2df8eaa6586f08b01a9ac77b200206e079f6ce107464f3568d586e9375f2d383a182132f99ac15ed8c2d15b75cc4e6c8c3148e6e3699c12251a83eea859cbb988ae03ddadcbce8b49d04372a319a446eefa6ee2da51c6342519363918aa982b18bc5700a4a5f8a5c1a77f38bdfc67fdb089df5380de99ad73fd168234569e22957c80e214b52f698cc912a0fa817c4ac45cf732251e4ac8180c015ddf2728f271a0bb63b40ede29c23487e5dd5d7f3b75836693ac86ce070aa6def697a6b13062f5e6d6a0822a5143a14b28e60ad60ac30236ef71e96dcde405d68b3071941b79c0f70cae1905a78e7ef9c73c4c167743621afff5112adb2488ab866e35af5acbd6bb9e0fd2f8174feb0e3e3dca055ba9429705d6c668d8ed95c88561c50b84e8fe5c69720d7eed3a83237963f461e8627a0b1ff92d492842afb7a14eb436686ce71e43df5b7fbe7c4948ceefd6dda4509c836d46d619f25690c2b8cc1f2a69a20456607c8da030ae6f9508d85508c22eebec9e79505504022674a2a9ffa6bb1e54724326aa91d41883c53648b077be2e9e5864a4d5b5bf29bbb283db8a055020c877aa42b08a957206b8b547f34b5b692aeed043340a3332b229d6824575088f84851072afbb648f8a0ed3ab031f3134107ca614880eab57acb8ff718a2d23b6b9b0354f76a1479fe00fc676a4659bbb04e03bebc62c60ad9f56b6d4b5e4c959e430947d35a1f75638c200c71644cb4485941e2756dc00f9df4ea80f8ab3f86af5a7eba9481116bca9b7d24b74cebae16955fd8ec94c1d3a2d957684188824674aada8da4f87a5d10683a8860a50e1e9834c6ff5544dae0ab7be4280a713ace8cf28b405f8ec4269767a74649e12ef07b5a19050d218c530d6c29ea1b18d2133cdab7f0fa43661851b6da737116e2dbc401d208b2453be61488c657218efc64e520466f1c21892710ebad3dd9b8983ae22c92ab7004ecfbd06933ad5d31d3094b0131825b10499aac357cf4908b374efd10c81b5fc939b6c5a3107a2ad3b5f4084ba4cdc24d13e43c57e2ede44e60a3bab70e71f709c4c0a8dabd805adc12c73a9b01e9ba9f4aebbfb478677b6f6460bb135297dce487435f47bea5e4532e3182be4d2ddd3f2f06440a93005e4abd6009eaf521242c6fdcd1f2eebb3d5dc853f591835907b414ad9b16287f084e8fb174dc7a696d70fcf195d4a511c921dc79e00659595b6550edff40083121a4c8ab8ab7875be3813e1c34e63877092b656882cc976f995a34cf117cc30e30844707e4164eb91bdff4c166e262b7dc128b2e87620fe26d3fca32a9bad41a95d1df3ce2881bf832416b5d6692a128c3dfa0a1b31136f669b4dee3ecd1a7363749172cffe0787cb6870a47e700f2259d43515406836181ad5776a8ff3cf5915910f586ad919cf875cfc3d35a3f814e21c9f4d921956a29a1ecbcb9033b54a817eb0725fb3931db55a027a8fadd840dd7e0e0c691a67e4c1adb61efacca5ce07ea28bfcfe5112d0e6c896e5687b236ae1f0c949f0c89ed968f07fe49297b2680fe27428de7411d3932f50a040d2dd4bd89d172d49ff3fbf993857eb30d0e19763662ee4f6add3b37e273ce65ca70b2693b88e508b53031ac7e5290d114abdd77432c2f8a7636ea85a2061337365374d623ce9812dea8d0fafc9a660aed715a386562846ed94a55f3868e0a151c186032f68fe65d038118f1b94e1f8fc3ce80f73af3ea65834d8556f459e9929086e65b19a44cd97081fd46071e82c841a3dcf2d76c1b23160f54ab5cd0912bfa3a23af989069580d7429db033ccbd7d5a3f4fb66f258b8dfabf16d52cbf992472b16cb8a193456e3c322d5f736984f7daa2fd5b0e63d3c191796954326321b8e94766e9644a5f79b3ca394bc1801a8abbf45241e6a3b0c36dc59030af7e385591bff1a54e6b622f888d034051a56c7c3d650ae8431f405021ab8949c8e750bfcfed5b3f1bf58e174e9e43f295198121a2fafe3eef746207baa44d77e3eac97d47a901e7cdb769e9144942a282cb7682351a744af2b8ed865d21ac51a3c331b63ea5ea00b35610ca36a66e9cfaa87dcb546d6e31684bf197221d16141a81455d64e628a8677efd0479cc0c47ba845def81d7e79d86864652099ac6b02ea28c40d10aa366aec23a5326b71b5e1fd66d78b813324008538efe2fff289e51c461a7cb218a7c80f1ba0869f89334564c112251219d7d849c944d57614ae5bb442268e24c9bc069058122b911e703b2c24c3e24dd65bc8bb350eaa8aadfeada52920d779f13e11b2687d73ab80044965e09268789a6c090969da3b0cada66c38bbf1995368660b35a83d7ae06a7b843ce2bece287ff51a02abdc6b378b9d65199ef0a72d20219495fad061224d241165a544a7d7df22322786e74f59199b682bb014a98c170b1a39e52e9ee645184637613838b0bcee7f792d0e03dbf492390f1790c107a2174864ccc009e34995b029074e0da5cb58f52282adbb03e9028c0abd48f6d5b7c9f01f98617ada1e93d0d45a79a2718f3630cb1e3c51846d0612e8c08560b304fcf4031ae96694572056b24a07159d088bb37ab4d38d0835da51aa8c37e30f5a145d4c1f95f6dc3df41cdc7f89ef4d0f2b5a280998d1ba9eb8412d2862063671b8617458bcf0b2f0f1ec2812f7f2761e7dfffffffd4ff6265bee2d654a29052f0909092809ce0c050a43f9ccf8d1c44ddcf4352971539307e1f0516742ac3d6a5dbd42724b7dd9fc3ca8fbeffba17383d983d887bd51c70d75eab85023759a5d17e2f0e1e0416ce54aa7ea1eb041aa30871d390e0a6a3f053d67ffcb2891992425e1cde6264dd3b4ef4971d4cb90a3529b73e0d8337e4ca0107165e241dd771772538bad43863772541f018ddcc568d62eb1ee9ba29a9aa2a2fa888df6a14bd045f40aa494b2e3af518110203cedf9c9987f64f899305e3d5445e1ad90c4a9cb4c9849163281ea611f1c877084a96cd42d1ac2b6d57449ed1f4965186973936ef5373b912d6cd01622691af600e23853bc71998a780884890e15deb8501cf3e9130927c0a30f5b1c72802465044a9850b932f11828bc02ff0e063434a82b266aefa0f6e43250672797b75f9ed05f000b51a52376c09efe3eea3e2266ef9fb94e0cade383c7beb277cfe33a23d5afff0e9d11eb80e13430e71b9c4c46dc83ad48791ba31fbd1a43e37c37bffbe272409de73136a845b01d436aa2d322d890b29d76f2203e8a2151a49c6e9edff9ca2a223b5fa444f3f0ecfc0fcfb42cdbf99dffd1debefa1d2794f27213c2d884decb6f0731d3afbff7dc04695e32135be11edc85d9c94a759b1f3d1e48ddc426ec02d6bc0b1b74816df917e9d80eac2316557301998f79ffde52cb473794783c3ff2110f0fe223a46ec23d3ac63aba466a529d06dc15c720d6b907af8e7ef758cb239a221ec4aabdfd349ff0003ecf67df4478be48892cdc164130ccbec33103b59f871ef96ad7d91d3ee1e19fc7860f79c2a29eff4109d80b75e67b106aa05977f5fbde2a88664f5bf43e87178a586887e71b7cc2f319f8c4033c3c3c3cdfe11310f0b0f33c08ad6a084f58d4423b3fb3aa31e11111c3802fe048a3e24153537c446b67db59366512de8a65124edf18b1947e32cc69f2be7e75de56bf9cfd59fb6ff8851e73ee5e1385c73e4516575cb982a972303844fce910c77149781c87f38437e79cf3bd07daf371e1ae8be33ec5fdc671e17844acdc770dc4f5d4719d3490882b8d3571dded5f778f66d6b1dddd9e725f9d205efbbd7638f650a9171cd7dd3dbbbbbbbb7fdcdd6def6e6fdeedac05dc7577777757ce4ca3a9996974cb34ba750bd26dbec875edb7c1d1aca7af48a5964d2a25d5b2d9867b1bee6db8b7118458d6d3e7ba66f681d9febb1b11397f58477f90ae1b4d6faba3b5acb8f087cae933ebfef17136862e071151e1bbe1e9f413e9d40bb4ce6696cd9612bc5977c90a2c5450c192153c5802133db0024a18fe194b4e9bee54ae7a93744529cfa49487aee88af2d015a53c5c4ab2945366736bee91fdbae473727e952bf0ef6b85ea95ab0ca0ca5f06d23ced3dee91bd7c0d7cb1454dd334eda56a487b5074e2699ffd1845f65ef5e4d4fe47b714c3355442fa7f34ab8064ffeb929f8121eb604de1f5e414eb90bf2579628b5286485e5213545ac1e447e218c21be9d10976000589082780810410d60b2a595260858923a27cb1031e1891a9209531690172d5a985b15a9af2059309aad49e9e7c8a55952750a8f2fbc91be9aaab02085594f09c78fc19674870ce3a42c2ca61d3ce5e526609ca30c489976551781e645dfefead0d2a00b7fc671886ef0faa3f0c3cfe1838d411c22f9f439db9350b695a74cf62e08d2c030c91610daf9f25a5f8f9fea19c3635ad8a25740828a92a8c84d08670da88c888d0429b9060c6b42a6210845400115332294700e19222796b992081028a60f2a2601a9c24555125081a58f0e4ada50ff3c34fffb444e07575ab40614104d4173e8881020ea0b4a0700694127c48c38725218c98c263eed147abf6be1d3e2cd981ded7a2949ffd942c9d78f2b746e1c9ae6357bbe31eeb7d2357395d7ede1a9ef410c90c8e81100294cabf2f50d58f2ac74ea344a5cb4a6e97fe3a92fde55355551f1d5413eb9c90a830421dc594142a3fd4714775679f3ae006f81085038fdd3d03def2e7a4e598f66662726a402ca5c494b4af557b47b5796ef1cbbbcccbbbee2f594a29a53fbbcbe94bf8944b48ba03954ae876ce5156ded1d7dd7d6ed7ed673ea1a2f639e62d89f1111bcd1a780cfeb64422146a9af1f33da97ec8321f7461fc90c4053318021b48b0f1c4162ef025234820fc208d0dc30a2c54502e62204608360c2a94e820a2a80111413461c51629e613543cc992bc20aac9921c3861841c2009c9d45da2e2045418415997ec52aeaffb0fbbfbe24ccdc9a5e694bed3478b3b7d939967ef99d30f86393316acf4f9b548a9d47c6a99bb53f7fc05e27f546366667a84d37eaa25993d70f70b7082f0f8a1b668f16056ee53cdd9c185bd9d7c7f22d520c1db976afcd775b787fc662224b23e89e92c8085e6b3d00c8f3aa1f93a4160aa3f4cf5704c529f7c6086d45beeee04b1723034cbb22ca39452174cf1831a34955fa3495514c10a57f9b31774107e90aafc13092a5e9040c2a0326f7142e5a5f27753073989caef7d4a4cb85229156b4e9516540e42f570fc9a9fd54032955fb008c112a1a5e2a3eed2882bdd3d0c222485a12eb8a89d85d3c3f1abac22bcad445cc765ea2534163ff6c5aafc3204d9170aebe2e7df262a767fc0b053f97128eaa0258201c870aa2cb20462142af38e0dc76d5a29a1b0af7d16703b65f33c4de129970bed7aa8bd7358973fcbe51ea52c5072994b1135449f2975dc2ce28e9fce615ffcfe1c730846731e86d7bf54c695daaa20371e185eff4ef94d4260d2fd532e9722597cab8ff53378fde1f91cfe04d90d77f8ec87496818d2a2ff0c59a101dc86212d36ebb4dca1c8779ae8209ea45dca12abeeee51aaf7cb65225c8a649101240da4e3d3891cd471b354c7411d774bf528ef70e4a3f61f4a0668a0558281d2376178232321212121f506161529215f7e92da034c93d0f65c4759ddacdab1fa4383194461243662a7f6b3d20a414a93d52285c6af528e924003a1f954cb32b044a7a713b51f072ee30ac99be1c849d38974007054e0c9dfd1416c44b776ecf0f161b1562b24a4f1e767859eb61a74eb679b81f7c3492218346852e43b25fa026fa49e5786a7d33a5078d5078f1778238fca38108166566c70813ea7d2c7f3993eddd215e73b28579cd3bb5d91f967bef2ce8afcddb6f85795c70fc2aabf6ae367086e7f59e342559875f9b30e73577f1fa52ed5ff874850007649698b231dc5a4388247f5e75e985e411d3b987ded7bdf60e99ccbcc54db750791cc5dba31f306aeb7e6fc49837431b8658d3f9472f85e1adeaf385948f0ba21b4940fb2094b5a1c5551a3cb4c428225253021e1042dda50c284c2092d2fe20c1b6981b0c61426a2a8c0093880811a501c21460649218f5220b865ddb4c06b4e22c47df645a9df3e891038c45beea4d39fd2e94f7d12a1a26d2bcab62442dbe72d29bc19c6accbe927e4de7b19702a9e0c9d0af94984f8a5b7fc930811f156f69fb7fc95b0818b94b0617e1616cd90c32cd4b1613eff0c89784b4847fefc2a3cf0c75b2ea512549f413f713390992a85711b55d5811ca8937d12213ff256160e31ea286fd14f228484236909a698d00a29c14f7f8557c286feedc322ef3bea578876a863c3f6fd423a94071dbd6a8067ba4c7e5405a5a19199c42d7370e3c3dfeff6d53bdfbcaf9d97cf6a480fd3d81e9cc43ae4b7e49dffd13796ec8093665b37b887f6f2c66fa0b7e28d1b52c78d1bcf205df1c68760b7e28d1b3f321255bebcf1db3be8db167e6f7f23e4a31691963012d3be364a299ddbb67d2f1fe6c79eeafe3d4339ccaf6b1f78836907eccaf2c6cbf0e5f7dc06eebce83fc6e0f871ab39defd7380384016c89f55bef1db86cfe0b8559877d5d08d23620dbf55423c90c5194c2085cf71770394350c290cd34ef4f1ce55439de893fd8f097ec4e1286dce29a17ce328979a1af33b6683c6872aa79cd27307bddef847b51d1e77779e2b4c13e4df9161864fa337f0805ba90c2e77c04feb181cc51df0d33cb06b71d5373031adfe7b8e763facba626228bf116e9449fc49c2d37e14abfc61256cd00c0bc5c43d6e60daca5032dcc03d90b00ef9854c4c5ac73c2a21abffe8af7e5534d0ff543330c8460cf5c37a16615ee4466b78e34679686407362ada818d36b063207701398a8bfc22483c1b703ceb7f3497c4f484373253124331ed0b2ccaf13afcac67a1acbb01f67c08fe6f20f845907096653e335343f33b7f464ce13371fc80be1fbfc7ce8fac9aa5006edbf2df0177758caeeaffb5aa8b200aa0d370870682edd0ff5441e8f3f71f1ba578c0aeeef473be2807eb7960d1ff582c168bc562e560e5c0f139fc7b5efe4bf0250f0c4ec334790dfcf9687a402f07b8dee209e98a86b09d704c5257211bb528657e5d270cc7233caa7f4ec838e11716edd0a227026f6428661e3e70f0f8f1791f8e276fe48e7117495ff33c1c373734a43720f736e098a3662111d780e357679e061cbfce844c0c9594c450ab4ff4f91199f5e327036f6c26947a1eeba9b34050a6004419b2775d53aa5c2c90b048da97d6473eb4230dcf816c6c5f3eb89192edc147ac43be6cb922d3d03de297c2470e76c7806ce5a4265a9453fee769cf494ef695bdfc1e9c6f633ceba16a39384cbf37fb1f9e038e3d95be0d387e5b48c4ffa37b3ea9276435a7a38537a5140f62b54f95b3d2df2143d1fd354d63f99016ddb5a11d2dca9faeb9bb873f5be53bd287513d3a3b365fd26cd3380c9a492dd39848c76ab93e4052feffac06daf9d5cbf769209d97dbd39ac7f9554991b23ae44f1b2d6782f46dc0ec73b80de4ca03eee2e01c58ad9353d37eaa86e6c3681c27b32c530dd1988c66dc38e0c8aa33f593079cbfaa4dc3c18356dd62a9fd9864f5dca986769e53694f44fe88e3959f06e7573c5b0f76621d47583943783684445a944f1f07dc791e50e7370e6c8b9df8e504ae727670f1564fe5ea6f562b1cd8487e28b32ccbb44cd3f833d6f8358d5552ee93012a0fa932540efd351b671bed1d84edaa866cfe47f37378e4a7fa3bc80fa312e2cf6f03f2fb88dfc762411d83693f323bf1b8789a114e72a285fc1ba83dceaf6a933a60afc0de01fba88d5a94eb922fd286ff9ee7a438e1f4c0e3183bedcb5f7e0fd8f32cb474c594aa42dda5154851b9addb01717e05faeb8039dc64555936e7bbf365b2cf62f3733ef54c56cee7fce855999c9f116bced36cdbca79991cd8cd4b1b4e4543f60cdafcaa82646ff33f5a03190a47d1ba09a640afad0292fab9deca09c7ef8bc0d7e12f4a22d4f33cf43c0b656fd3036adf420c0ad13e87172a7fcf73cf3bf8c4033d3d3d3dcf604f58d442e0ab6af8079f9be0290d84f3f2198a9ced2585e12a6f8f6d75f07b9f037e20b860e76d215d1787e31766fc37515a942f6d42761272528bf23d203cd6d802aa1afe8bacec9798bf87bfba813776ac911a481ef991d956edc11ab0e61d1c8754c93d0d38f654999f01c7afca844e440c038e34f5a563dc3b0c3f5a943fa481fc191c7f5b4d49234be5a74758425653d230a332cb9a00af0784e28d0dc55b3f2dca90478b92478bf25755030bf5bc149ddea1c435cf6c0cdef632b353daa66c383938211b754e185bc22276c596ca2a205d659ea1b8ff088e58aafeebb4c9cc99f7fe6ba06d669b993321cf99f7dfb6779006fc544333cfcd8032328fa45dc667405957b56d1f64fef632a091e9acc463a175550ca9f79e9b41e6b7e75a45c3cccb84d1a5cafccc732fdd107dc9fe12a4f9560991f9992a622ca937ea56f1a24b65150d313133e10e560dd53cf7a3e5fcf6b481669e478b3099df3126a93ed4f0c69f6a0426138e49aacc1bf991097db4e8dcecb6f7a09c9cf047ba8536e07c4ef8ab67214ed6e0502465e35024bbe4c436b0e655aa20335ff332a08f167f1a0816c3ccd73ce772e7737e2392b3b393f3ae1ada5435c83af3379f6364e66b5e82453aaf63130a89f91c5ea8db1725115a3d0fab3066cbf92de765c0271ec8c9c9c9f94d27272cc2791d24b26e7f13fe34d04c081b3949a68aa126dcf1f38337728c97c87cccc77ccdc7bc3fd79c7fdf8facbaad827e7ce647afdbd374d00f6ff96fbf3d6f0f1c5887ff168e5e8bdfe69f8dcd8dccff6822fe3fef8f03ccfbab54bfc950c65f158587f3dc736c5f32ef3ddf0703859301b932946d731970ebb675db16ea6c32bf2dfe688ec96ca11099f0090f398ff31b119c2f52629bf99c9c997094f96d7b9c4d666395ccacab4200ceefb66270de655ee61b88261cb91ad95edac8af79ce8dd8fcf7d90e329ffd28f3313fee506dfe03479bcf6a6ade553170ddb6c604a9091db0dec27919d026ac0147991f699d792eeb6a40f93423d79b99979151e2c1e3e74746e6c7af9bcc8f5fdddcabf9ec6dfe47df803a78bd9101475a657edbfe876b5566bf81317f038e3bd49ab789a9b951d1c01ced6ac0ad34b44e70e498ffe13bd418702b0ee88123ad5be8fd0f9fdac3c4469a3a3214aa316d3101de7a8f92670403f2bf6eb8aa21bbad9543926d1a94825b16b7a5a0042f539c52cffb3ef932e54ab7e422b74899e5eb0ca5d3944e9eb73a54cd19c6d055a98e1fe52d8b7d71d9987642e08d1b535454038d2f53b234d0f86969a07163aa3d76571a287b62f251c4a819aa8de8981326df82249bd857ffec0bcac95bfd947adef7fd8fe2d6a5c5dea2dadbd868a1eef3e8a0d4c684d5d1bf5bd4133ce9d44057f665d4405b7770f124d4cf23f7a02dca5bfd0fe54bf2984c244a3deffbfe9d6a3fb7eafe76344de8b180fc37c5bf0522bc19760eb620024f470759b587c1544d8ba241be16eac0d57584f0ccc0a548b8ca306aebe2a481b693500dd42e7582c8ea2fab87230f955f9e6c946852a71195e792cad3a8b2ec52f925970ef22950b349f5291ea4925d5cc988698937d59e46ccde5289246d4a9bbcd5b4c9839e9a9a68136daa4dab872a25dad48637f2539353652b4e3c51a88164f7d41bdf206da5b3ca999fa90a4a5b825b964ef6fe498424a89365ae436937dfb351aaf245529d3fb27cd449b5ec4a9d2f756ae8976f43bf0c8b9490df9f8536f06f61913f7fd315ae5ff66c9dce699cfefaa66dbaa6bb1ba669beb3de3ad55c77adea97e6df51dba7368bbf797e67e6fb69ea841dc30947efe36791b986959861f8a90bc78f0bc74f85a398312b51de58d2ea8423ddcf09470f271cbffd2f1cff261cc51fa7ba5f53d4e2fe0ca8e44f1ee4635b1b138e9e172ac184235585e3b79f0a9fb670e45179763c6969387e9b71d4e7ad56d5aed439e182b4a7bf7150aa53bfaa2156b7b4dfc06596377abd55b603c1f9737dc415c5d868a4557214b7bb9ee4634be14029a17400bc99a4c2913a73a92c95a5b2549651aa75777777a7f8658adbb66da3f9946af324ea4d5a8c62448b1eb6931671a078631f31a9de40485bab9e38e19653e0ad15509c547f19c340befa56d92d10e13943e1b6527fee2e4005b8adce5e554f5587ee45d60eaac2b4aa838caa7b8191b158e5cd6181e7deb5db17112b551db2ca441cabb0172ad3e0c52a7f17846395b35851c6165354e134c594de6f75ccf046f1f3544e58562d761005a255baf2c21bc5da3cc43a859452765048291bc8a594325c4e825b393993383314fe2c41b946b8ed9b167838bb5c6a531651a0c8236994e549090a6fe24ca2b858699212a597f491962c549438899b7489aaeb5477fcf0f30e6d28a9d4556a719f1966c7cfba606ab4f0ba37088e1a2dbcfda902c2e14ae9d239ced65b9bfcb232548bfb315ab89549b2499728272b4d4871245fb2450b1315279dd4b12e579ca634c1463012d491cd42061c33bc6d91856386275b5cf6177e0e351970db6266f9d2259581d7ff5503b9bae4cf26540cbefa28a954964aa354aa4fbc9c107851bc7eaef25bdc178cab873eab7e1fe22a5fd65042cda50b78231542142a8b5027242317eafabd1ed491a6f60f03e1858bc39d91b4e2806cdd17d755128208f56b71eb2a095184fac1248cdba83bd7c5ebe290430242d65d1a820f940d982d57aa4a8bd1db252d4cf503de2e699952f7bdee929626aa6a5ffb75a3b1e2cfebeeee761590cca0d2dedd2e8174b8b29a823e5a641e73abd7f3b36d7bf6766f21799bb144b62a866c6edbf6ccdffdd9d63c5ae4267125b81567055e566f3450fbd75715e466e4d4151ce3e12d5ecac3a5cf9e3b3f6564c183673b319596c9679095131b4827487728d6fd4f12fa78a27d87eff3f2c9d127a43d4df7ff4085b4e7ae8f635d387a14d6c7d18f90f6fce00e407b6625d57d1819fde009bdb000e077615be0ffe11352c2c31f2db6774b2e8389b68294272ddf500401aaf2b3b80ca4ca3e95df3d26f73625b8e94ae4b2ac331f16173ed78c460b3d18161b48cc5695b3a7bf0dd45ef7aa81565e85611fd5a3f9eaaeb81f9f25d6293baa1ae232eeb79c4c35a48519a53506ae0dd31da65823c193a31f5cc8074d48891c64edd8910f42b481bcbbb329a56caedcdd9bfdc0c1ecacb265f6655916ee77077252ba37e7a3d56ce53ebbfdb33a5d35b47582dad66db3f74bcf54190fa6bed4ee70eb56372584558b528aedc3ca2ab7fb6ae7491b48ae9cd23d7bd726336bd9d7dd8d18522ed7ace1f1eec32491695cf71b854152a5675a8aeb568479f985d9a1c2bcbc84635195a9aeadeac027319c16e92fa970d795fa1457e9ceaa2031af814f62429a6eb1af74b986cfcc27959abb160f975da35a66c8eb9a5f7d672699596acb9ae653669a9639e179443eb9be2b3aa774399dd022ad780c6e2baa72c8b60ef0949ad4b215674a07b0579c9916b2104443f2533b32cd69449f9fde8d61607efb1770577c81d9647d79f9ed25942d6ee1762ad949e66457aebb751aa4924abd279ec6592853d02e8986fce7d3ba99f4a9fbacee4e93bdee2fdbff684d0b97abd3cee2f187da04b9669dacfb72594e4a29cd6896895e5716b981e68622abefa3ccda4b3976ab59e972911b5a24227be0655cb783ebc6d5cb8f3430d91371a7f09f488b383fc8324ddb3939aacd15a57ce75f51be0682caef330bbb06da90dae0fab90685c00ced48c36306c2758802631ca1e145e5f746fab4051043489a4ab324e5fbf134ead9ccb299c2104f4d68e1d2930b6a963dcdb3b2503c42cc5b9b83079e5677e989c9138fd65d7a427a3afaa8189c94babf340426754324fdb520ad90a1c4b63e4aeb05174d57f895eacb65471db70b0b7564231e3275e4265a38195577271b388d01060bc05063290c2a6218218695165c811a03690c294b6364c182022c3ec0f224d9f8f934279c0f599665d90a7c985d202105d2873356f0124402b6d8c1144b68d962898dd9832ed030a2ebe20c1904f1a403277cc185ca954bc8c1199feb8b20787c51459defb3088d104fa4c106133c28411421c9832526b2a480f036005ec0a0cef9d40ba73abd061a797ecbc593359c40a50d17b0e08a286e708457c45bfced130b31d3acbaf081a6caa75e0aaafcaf8b23bcb6041e1f7fd44a0e9614a1c9833cf6ac5071842c82948c045e84a869e58a10bd058a9c568850e7e4a57286940851bab54d50283c2a5f166fa55b5b47b16a44b8d2add57858e16202056c69e298f8f85341044870e2815753b70a12889043081e4ddd2a4868238527783275ab4015b1c5f3ea56814a4205243c9bba55a09c48c251168e36d0ee4eb99276f777f713ac245169beb06e953482f018143985952080becaf9ca853ab27cc61d3ebc1b9b65b700403a2f6123b46052fd084b5a3061c907598c60698b2b517680c552d3117c588ad20265898b2e4b3f687a028436765f9303759f7a0e7e021bae00c2084f0841064ba060022107180801064f28d9c11231777767ef27ea589cb999b9a04e46537706132a68a209f729f5f424babb3bbf3b3716777777ee31a4bbbbbb73636131199e497777777632ba63edeeeece3f72952419eeeeeecefd947999bbbbbbb337167777e777e726a3c570f666777777eedaa18e907ef9cd8e458ed14c6e021f00d14c58b25041d1b4250b2c47685a38220b348ed020d1a8e623e6880a0c8ef8b842fd8a91f6d405c9080b946c09131b40c1b263c6c90766ecb822fbcacfcf2b9c94317184169c2406d44684932ba0b04829a744c2fc058371449447c42f852e66e0c591ef8a966dcb931b68c142fd4877a4f3b94182cb123e57322fbb728575e8a93924e12da8c00041451954904c0ef2138cb043243d91f4184c2f4da62481090b1289448ef15333832e68f073659d382e74c0649db0f0a05e83c838b1a2081e481c898fb16259414412ab7d67e993f3baf63cc2922112e62b417e0ae9e124dc9ff3eeb7ba5b802b929e01d7aa96cf2a24bebe2a863252eaee5ab7ad92322cafc1254a149410c75d837c4512672409ed165cb4408a10e74a9a100613a11e609a8a580444f08249a8879ea4c98458194a423d30b310dc72309d8163513582a4ab94a10e5d8d64e1760db8658dac04a9f6732fa04fb73e160aab56fd4b0f1cabe3a543d104de56191aa13c84b755d0b852fd99634cb1260fe21897e618972eefda208e3df9a0b168f4732c8d6d6d9555352cd30f269efc6d823128a02835d2902a20502b14e1414dfe4c6e4a8e9a5889518ca981baef29b6f34fe6a7b1ca1c638a516951c95127c18561e49852ed576a208d4a03f9c7a05a037d939c83f3e52526a67350a3a99c0de7600ac7c14ec74155652d1ccbd262c782b0313354313cf1782f61b8dd8aea493918199b1c1e8e3d79100cdc77dd4f57a7027df625bf5f876b7b541874e8f87170fbf96129030c04e851a7eca01fdeea1ce4aafb9299191a9a2536526c6c6e6e8e7096c0c1c9c901627583d5cece8e6a28e5cf4d9e4c535982fcf3532ee5d6e01f0487b891831b2c160b05265010455185292a743e5a3ca8475d1d66703e9d8f6a48ea00e2ef9896da9307937442878e9f9f12b0a003163a1654433eb1206c10c7a0bcf5145bc22e618376ebb65a50d2826a8895ac88f0465612eb3a72ec8963593a8895c4941cc5b6d46625310f3aaafd635d4d04c1108e5d1d57c92b22a80b520d718c4b8b354835432a1c532f3ff32d16c531a8a6e71897ee59462584fbee635441e677e1287f863a466648b7452da8a35ab64ab1a8065a185aecef5440becad0a6053eba3ae3820bfbdaea23e48dab34e4070c36c4a0fa09a87d71c7a55e542f324625c47fbe2743ca4a90b8f6d105c7ffb5b8a4c509f3b79e9c224afdbae0bc1b5329ed53dfa9972afa23f7dbcf7129493f05d2163b74da1d1a91df8554be67dbcb37229fa6a47cfa0deaa0d5961ae552dd214db19462b917eea6b6712a9898999aa6a01ab685648716611f0e4131fce392bf2135ec36e5e8403118a98eeb86e873373743eccbbb2fc78a1ac87f6a6fa4a808c9b64313c996524a0f2606a42faa0e4c894016c80631605b41439c01433c08b62d1cc6f0a0dc45a08c064e4c7184ca7fa31ae2a5eeb9a8a3e361705ab75a655a7c299e9d8eeb7878523ed85628a26763aafece5907ad76b4c52692d57f2553d9fdbf00814603c1182e641686345fccafbb71e3c68b239dd4e958ac1c2b48da71e0d869121bec720c29c263ff1cacc5c3e15540629cbc71a1c623ab9a3d677cb4c41f899452ce0e0520bc8eb1f68cc4466ccc190d278aaaa199f14b0ec7917f59cb40e92ffd0325bb8208542891dee7d1789e2b498c2580a18329a260d22cc836c0860b90886289255af083904b41a35377498b1234b8c815aca0026ffcea2fec0bf6420cef3f807dc10600c3fbbbb02f980b42efef635f301f3fdebfc7be603d06f0feaf7dc15e3ede5f00fb8209e005f4f283f6050b5a97ff8ef707da170c685dfe2cbc7f00f6050bc0bafc7fde7fc7be603bd6e5bfc20bd682cffbb3b02f180b29bc3f00f6050380389ff5f375ec0ba6635dfee1fbafb02fd80aebf2fff777ed0be65a973fcffbfbec0be6b32effd5fbabb02f980aebf2cf79ff14f6054b615dfedffbb7f6056bedbab49779ed51d8170c8575f97bef9f635fb01cebf27f797f1cfb82e158977ff7feac7dc158ebfa1bfb82dd58973f7dff705fb0705dfed9fb83fb8281ebf2eff7ff7dc19e58e02bf016086770fc0c7af9821101e97dc18864f8ecf705230af2f4f705232ac0a77e7bc188847cf7dcfb67fb821119e05f7e5f30a219de7b98178c88e6b77dc1880ef0365ff3fea97dc18868f8ef6f5e30a2219ff338efdfed0b4654c3af5e07468480e7f99df77fd9178c2801ffdff3fe30fb8211d9f0e183efefed0b4694e3597fe3fd63f6052352c08b8fc2fbcbec0b46b4804fe15bef3fb32f181103dee755787f9a7dc18888fc0aef7aff9a7dc1881af03fafe3fd6df6052372c0b3f00078ff9b7dc1881ef03bbe0518118f07fa00bc3fcebe604437fceb05f0fe39fb821141e07d7c8ff7d7d9178c28023f8077e1fd57fb821149e07ffc0befbfb32f18519117fafafe3cfb82114de0617802bc7fcfbe604414f8187ee861bf2e7ea20a843f2800f6ac8b7f0220cfbaf88b803bebe29700180150675dfc100073d6c57f0388b32efe078037ebe2770068b32efe068035ebe22702d2ac8b9f01e0ccbaf81700caac8b5f0160ccbaf86d0061d6c59f00f0655dfc080055ebe2af011c0272ebe2a7014cad8bff00e0b62efe19406d5dfc0600b375f10b010bf041405f17bf0c60af8b1f08180319957fe885ca0f0396ca4f8001547ea1312a7f75a1f2ffb852f95f18801895df851e95df471895bfc7abf2bfc0a8fc021040e5e701eeba824220a7ca1f0070d7b5036c21a8f2b3e045e5070050e5ffe9a2f2eb00775d2b80bb2e17b8ebf2015500f953f841e56fb150f9452e2a3f0ae0ae2b07b8ebc2118e9eeb06b8eb0ac15d1708be0f2a7f0fc8cfa345e5df59e9e46051f971c0ef8aca7f9342e5b7b1a2f2d7b4765d33618cb7c4ff52a5f2ab72ecba381c1b48a9a8fcda8dca9f4dc1df2b0558d1c8525b485ba95c79090d2a756bff04fae5e98c2c6a4edda53396a8bce1d45709e9a91c7e8ff3e4350b96089113cb51f2a4d440dafbacfe924a3765e164e5e94af54d4b6d0f37264a3def7b9ffe3e4aa78731f4fcaf4b675ca9feefc4240a977538aa746a71eba26d51393b24f8be2d6a6e514d9452598ea234715c9840d13d1941c9625f502f46e0d07fa1b28b67f4ed6f514c4c4c4c4c01a3daa884cdf66441ec10d1080000004100c3140020200c0a084422a16028186ac2b07d14000b869e4876589889c32088519431061942080284106008010081a99a1a00f9eefc6488e4606f630c03b257007b312e2ae872714fe80b28e7b11eb8ee7de9fb51e6850079288c27a492b5b8cc4e0ca9bb0b089bf9989a40cb37aedd12c2b831f3c51c25944241c49080b87349c3b3196f2756f4dcb3d198e2ca92cdcb0142ec2e6d9a85122e32f3b3add43d7208e66a346218d43b459036438066a326c6bac0197ec1a6520c2e8177586b9871c38a360c139b33ce1da8864f61120b5b3b2150ee3873c08193f2949d54853ac1095ba41d9b08498ec8f5c1727ac9b53ed393922d0d19372d94e093d1a003614dee8426b5a2ee7a151f27bbd18d3abe4f18e3775fdab7e6c3dc5ab68d610427098dfca8290f6bf87df1fcb8c19f3f5470c50ab5092b3b91e26fa60887da360a52be693d1415a9a9b4578876e59f20d6152da1bb68e17b847b02ea233e1e05fc33147ad4899561697016d0ae5421736d6450c4788038dbb671df1df7d0d45864706ea12ba9637766a89a6a626c072680ae5847f7d2207541c40690e54b32c3cc0b56d824ac5852bb0c6dd0736b673ff1903c942b182afb33ac6d1d7227518eaec7d4f2b6ab2111680e17dbaf76c521d58dc44ed52898414bc34162d8b886d5c17be1d1913a10845f76c69d02de0d37eaddc0b966968e51ece602eb2b6e5dbff1c89d1626cbc02416219ac459e80dff86eeb4ea4f6f02732c4450bb31ad8aa620a833d6f18fc5c2978f15c36c5d7352b7a285ca6c824933d01bdbc28bafe835b5eaee12535a7d930c75ec6812c69bad96de61e4e0e1b876a6dc03d3df6eca4951ba959ebc6004dbdd9e79529d42d151f3ea4da13185da3ededf38b31a153b2c55a8484415f44dea024194b51e77933876df34bd09b06f0266e5f9cd888db8d8d18c699f7cef8b5f12a243ab83e1628118d96012cbac3b7782f5f40b66e9ce58a3252305acb9042a757bac77fcb6d75c5614eebb15e9259538483d592525bd64ca04f641689c43cfe2d1bf972a108a94a555e3fce690c34696ead7ea767bcd61f08ca4236318919832d3828f02b1e4949dfab3a0afaff772198230a1883356505b8c45802ffe1a54d5ec1362a726f07c98c54585572940f8c54fec247823cea4e16379e0e25f19ac40c7eb86c431d1572332914274e5def29ca91e70cbfd20513ca6683ad34c2ab64ada6bc3620a8784f67158cb47fc9a3f3aecc7bfdb47d4d2c5644e5225f7222dedf70bae09f1c2fc1432f051c83cc8c3dfad0f45f5387988768efa311aa88f10c9fc5a2ef726851c20bc7d2e4405d39d057c3428967017d04d98284bb7992f1d85ca071555b65112092d52c7015e4ea5d53aa95f27498b5e20bc9be0d88f82633bbe908aab84ec82d91f496ca4ab16bcd68b6f144e1c46158a7a2d738b6dd9b98489e7b655454444b1f9b044b46977e7d1ad5f477e067e20dd9a6c3d0e364303e33c65a092e9b6f6ebec860a55f0f9de0ce7ad3c54d0f9513bbc754574b23e9f157b04780733720161060a77eef886c4380d5a3f33540ecc2dec2015f41035ce00ceef0460bacb043f6b0f7c27b84403513f0761dab0b0231a8b42dc3889983274a0b525c4c89a73b6982dc600d5be0f354aee607f9493b4f784887eae02474533726db297d12ea77290102158c9c490613faf2a861e75309ed024bbe9b9c708a79da204ef695ff0df6a74b70c0871ffd65672ac0b7eb68920e9c031a3f461bb613aba7b1a2fa8b41d178002e5b3ccedca14dd865f281f9078b7f50a41029688ea389303ba059932d58a0663e663b275a8e13b1df3f734a14bb94ba7c33799d75beaca92a1d490897e38b2cfd64b4c0b788475362577926fe0921033a0949b9a22bd037894db162657dae80072abbe851d93bfc3e781b07cbc2a50271165bfc27848db09f3f223c43227d07a412d214578360a672b0f9b31c2d5ff4f74c88baa2ab6f141d1521525f4e08e9bbfa201c89bbcdaf1495af381bf3069f145271671a8191b17422b12292a9a961087402df67bf614660199c9896d6f5775e7c567213d4c8ae730cc42221055ff0f402c902a7e6c875859e566414a9f56b5a1745a81122f0dc94265205b401c0ad2b8ecbe818f4854ef8ab42a0b53f3cdb4a84f755b75126cad72242d20e5c6337928e939902c24af9872da09704a499f320f751f0b2fbd832427214c3cc2118de8bc28425fe1f2d1690077c6a9a7f59ca0a2bf3172667840627f53dfea699579ff59f7928f3cb946c22cea9afbb0032008baa6b79ff8a00c3667ccbd3fa1c8282c180202ae064f858e3156109c5c79c3778102f78806289ae434d4485e0b5f18a4f82bfb40c60191411f01d0631d3dc190ece090fae56035c442ead1f6edd0803ee7b953be3077b134dd02c65c163b7cfabe11b0569d68b37dcfe6a7e7699d667d91793561df202c12728d18db0161e2bcf0e4dbdc8ad74542cd6c3f0811f2b1a6f2a40ff9c8073ef5299ff98cf1b1b468aa088745c1f8ce38da69915dd4134eefef453338ede853eef7f2941f48609b454c5cfbef3eaf2ea21db2db879d8f3c39f2b4c2cde497c9b1e84f28535a747aad42ebb249aa31c0cf213141734a510a31e002355e1d03902fae08c22ec0859df5a90c8073145ce05762c85fc8c7cde1aa96b76eb26fd81eb773abd2523acb6dfce923ec8f9bd96a727931cdb5b724e6266825226795d48c2d19d543270c2ce777f91524644dc32433de2f6153f60fcde82e95c01c379056fd61f34ca02cffa4a67381aa0f09d244b816e08168f404fc930f2c1f9783abf0d22a948bdeb9b8376189f24e6331993837c1b232eb140589a9429fa5e4928e627a964c2c97ef85d6d8a3dba18156d06b42cf0345ee8e120062bbe6888c552adce5bd291efc0618875532201727df082ca2faa11dd46262c9a2070ee5de89189b5fa9a6d55bdad20d79369f620b1da05ccb366b3a37aa27dedf55a4b4c6b25305015b29b0323438fa08b07666eff1ce21d398e0e83e00cd09196ccd0ee46de74a5f1cb01059ac03a3c9a4a082b65a3c8988b9eda1108ec9403b9c5de265d8efcf13a387866484fac43407a92385077f9b643d9a386149b17b7a477750ba42c7c2a9a77b3e4b79e61569902ed7d4173cdfe2b54a31dd6b819805667954e5a4751965ac08bf9e713af8aa85f2d6f8ac1b78bb0b1ba07cd5bf08527f1a4d294733337e618f0bb259f96bd7f2ee4a32a4e4295146287d4664c82e39a09588b403a7178b9aa8b91e14ba3511707660c2c703e306c29d199f0d9d953e5ff625c53fc03eda24dc81d0b30d537178b1908e52b171b9009f3652a26dafc8a5cad5ef8a7f1be640c8037b92294ce3a51dee1eba66188313b5ce45d8c357697b607e24e153a4d91788afda8fcbc09c134498c4e589928bec3770c5a6ecdfdb99bc73cdf0a911f4ecb0e75e5338b93557f2f16b1987ac160e414943238aafd7bfd936b1792adb65449ad6ac6bac2bd8fd289a0211dd0b4ad78ce3258a55d24560973b48cf55e5051b32432411b9d0d4b0e67e4ed9f9937aff6861c7d2d0352edd2cdd760ccabce0e70198442eca8df812948d8481bc978264c5aa9f26f5c01f5aac7d9cc563c09d4e1b70bce7c3b1dd66e1c26163b609890aa0f800ee322bc22f85875be551597deb6eb7094ec0a4eeb1945136be237bd1e009d463c8ee43488352c73cdd96773a7dac0f8bea55cba42d0794b64a73d2e980816cfbaed781c6aa06b51f659dae1c81ed85d36e153b57da9885a525ee9120489b23c276bea0e895b697f72c31ed88575475858a9e42afa2afa1d7d053e8d442676106e7c8ce4888eeb66149ed97d5def5d62b09c077714ab6dfacad816f1a1af166b6b2014583d5bd93f5339524035472d1d75871a9e68b6a83a67598284b1a2d176ccd7d9f5456268e12e4a6adc6789792ca3b01f29e3dc857202a600d6d55de5c07273a86788e8ee1edf582cb9fbde07caa546e9005ac7f1c9138edd6cde523194bb7fe258c6fd7f4a6ea0d409ddf62d6a53ff0b4c264b89dd520ebb6dcb16594959b863d38ae118b060b7cf4c1e62949e437cfc78e46c6ef89393f728459fa8ae08e4acbe62651f2c31c513b49db553df2185d9f12e23d6127341507a0a3dfe6e74889c072681955c5cbd2e4b0ea8e0430ba57e159d157ed6a156f3ea0d2edbbb2ad3181b4dcdbaddc3870acb39f0cd9c57024c4870ce01f883ca9b2315b980860476fd4dd4cdc5284bec02e60e95ac48fbd56ba5e71c16b9364bab8ff688f16d61625e28b77db4f7f17612f7b76e3798f95c03e7fdb8a4cad466a36e2f0a3f52cd2251e51432f6a69521846afa678f18f0b8e9b6923b4bb1188d79885401e82fa8090cd7d53d2a6144687172a51e6572f10cd5d1b0a13c8b9e2be11aed72f650471983eba2f1535dc0b3d3841947673bebc42caf1a57723072c3f0a6fb4e8ca8ead49941efa7f452b5128c1d8a4bd58be2174a2349c0b0d4811bc5c51b1898b932ec9d5e91f43e6be20130cdd2a1a675084e75c10e0ab6b8c2c1f6320bed5bc0ca2ae320472303e345f7d0639511a2df7f264cbe7d245bcbc99f16ba304817cb57daeaf0d8508a4e8f431f7b351465902aa68fa5c8c919d158e6a1410bcc3cc8ff50773182a89cf92e5dad4bd7ef09b1b875d11bd1dcff16f2c3b5907dc0918b1a86e341d3cbd05ee0ada485d2cae7e3a288b879e77c1db3cd04210bb2c88f55864b6304f44879f79473446bfcbdb62ecf68020e12d51b7e2398502e8f3f006454534f913778b33cfb280ff6af11da4a0190a74ff92c27e8a93da8379e5c5c9a0b9c617aa27d7590946cef564bffffd3df3daf8689e29810456a6243810e2f2421cf3c708223711eb42710fbdad89b38cf2ebdc78dc3d188c833fe8b4626611c9c373a4b3c08bbc0b51e2bc13a76047a8f26bee0332f72d3889a34d1969809c242d351106c2b97b1b23cc816693fbff5e52b49ddbe2abc5793a96f369318ac083a04b383fa7148e83e71a0fe972a368d910649f01642683b900a7d4abb50013c72ac97b46d3611471f6a87d3ed7b7204c68729557d883469f62a96e911e89752c798a0f2853ea596b982b4ae8dd6d92cf90ad3cfc796d05965b52a91f2e65a1167079043ea34ee9ddd40a19d7e7dfdef008e5a3b0745bbe6439af15b14b07872aed34d68bad17eb2c3f4fe27223222870c5f48a7f0066cbf9136387d53d7f6ce32b8afe50e4fad9a09c812ab3f0770e1e01fd465b212956ad9325abdcf3278a81633b8fdef3841db66daa5d6eb4a26a418697355f3cc448bbe0bb88c6d37b2c31f0aeae963eb307395c065d7321c459dc167b20b9882f47e5c56b8b84f6a8659c993b13b5a5c0bf7cf641ebdfe3a0fafbbc07bc76636ea1c0579078bb17eec81cf79402e009d68279e7506d4d13dc0556c98dfb0ccf3c855cf16ac55626658f47055e77125a488234a347f862ac785f936de0c2b22459cf21cb8b26d8da80ab62aebbaeffa78a291f15562c46bcf813fd75ec18f7a7db6044e4f70b4eb3707de20844f64408e98b385adb9a131800987ecda89bcecaa57ce810c9e6aad1575fc8296bab0e82e36b5ce272f8a36288a8b36603e951befa3c4578390ba6dedc086f83a2fd7181c8c3b3f17384b050b49a0ee78f606c83280616e6a0e7d01e8ed0d48312662f6a6933e2ff3bf89b2d7fb194ee0f1c374ab69334e65ef795d9d483ae21a57115084892259cfaa7a995e7ce72d8869667ca217b9399047bee81d797f5f5b4d657372620d8ea2f08460e0c1f57c4c78c182b517e6ac1fca82f6f5e736feae807622129a7f861df57a09c9b5bcd35a866bf7ba81f2080f8db1456d397d0ece0d0cd29825a5c344ca7ae9436abd65e2f0c290580ffde2ac9a7339faef3a930fa94703e1df9b91f4708eb086f1e44fdef492ea567e4f360596f88547a110caf8be7614b347c9f11a1b503794bb29ba52cf13e4094075ab0609e0922a7a35540baf0fc774517278eb801e7616336123d375bc427dca20a6673344a030dad468aae6260323a6ba75065b192d897c8049da355bae2c6e72a98c6d6a637c26009113c63a9390fd61cec4f583d2141a9654b2f176a6c257885d3a3d0044db333eb8f8a5f60f680fdce7e72e702ac750ce9b221047c73de797910218ca882e3a21c5b78fdbf7254d489a291d089a001e171d6e1fbc2897b33f193e3463ccbfef423bbc8cc2156d37e6fc46577a1132a1bc2147c55d8b320fb3c9093a92af11a55370945e2168345c6173c748868676e4f359c901e68214bb43c883d94cd7e793cabf9469c024c5ce585c0085f6734adc0eac894f07303377bf4a1fb731db83346691a8e100eaae3ff91a7862f5805ec70a94f268f6c4b4b942e933ecbf6406988f7acb525320cbb83f08dd27df781403de7095d192e9833f77f49ef347b4e57d79862a600c790a5ee528edb0dcd28be1b930e9aa98a96e3527adf0afbcaf029dd8d9909650ffff17a6ed426861c53d0901ca0f56fd995fc04008a7eb5a290c5e235a4de9f5cc5a7767743eaebc0b36cec8ccb52c71ef68625c43253dfd6cd2a12170cca252f22b3ea541cb23bddf0db0065a3fbf3527d97e009a4d3f4693b3e5afeca161e0b8dca863691ac7364ff9bdbef4820134278bdb1287da0da96987f3028e000bd93145b071a3582fb980d2d11b57b5a7f063523dc6dc418188916a990f023b01ec20d1ab4d1a31d2be8c791276c027eebaebfc6e8c83154e4e34618e63e71bf851bd8091b8e20f6aacbf9031069ea5ba6e9fb3574ab74a3c760d72340d2004040516f60e1c4fe96b7b61dc0a665013c2259e69954130e24a600814d502fccf0656d7d697d818afd17ab08558deeb858a242f58851649c3644b12144d55cd3cbec6a5cf4a6098aefcdbcd0bb6df5252a9a5e5faf3e9695d7cebf410327438924e63f3670631bdd0e432434e0959037c2f8a085447e563b3bc141bfc14b3d95f66cc0cf1ac0f1d7658426e743742504d482462496d63d8c567a5ca3473b1ae488ac243d002540ca44d1c8817fd53b41231731fbd8f1c36eca96727aa78b6c256dc5182638b420e7787dbc626d2b03493a9d27ff280718d0f376bbf4ac6e576101885938487dd9a3be01d7b92e1559aa9e2c2d53f02f91cbfa691a0009618a2297326c819b2c350636d6938ef01404a53c90b4a13d05a548a402c87d8b4d4aeed19f0405989cadf35f52538fb08f31e29f30a1f2e17a226628b4c57a0fcece4bb0caca823db2ea24306f25a54cb41831502f5d94c510dfb8c1313ba0063b85282bc10edbb50401ddc3b3a3b78799412f20d3af55ff7e300c06d37a8469944c8c79f4187411f24dae4ea3736ce0ce41049476a877264d28e853add467063c518011a45e89baa3d09ebb847059b9a0c68148071253fd7a0844b3bc830f27b3a937e3ac187cd8e8f3d9b3fc559d6105d540dacfd4c69bdce8180a8ef7211e3c2731cf0a6ea0d29f3df8ce2089551465a32a1fec8e9724caf9f6b5960f9729b56d51c26c0e3e64321589bd428e8c7338b346e4ac22bb360fcb9b89cca0be226ba870e6c32146304991433a06a222bba8e49882772dfe9541997fcfaff30468faadef75533e3845f7cdc7970e458855567e68bbd289b5349ab79cd19567fc0baa93894b2d18c4188e445c66d1d323de802cb1a11a251acd0d9498e2dd446fc4d0c3032b15ff8c1301fa474c37f7a646c9fb12883b3450af86a2132bc6de7f53fb7c01f90797ee618ec40642e1386c2a33ae710d2ed616c613b77393be485e45a6bd065a55ba4b0bd7febacafa65477fafc0855ce0cb18124666cae0b747b40243f07f6c4b6b26e82563d7528a60fb4158d90d82cb7f13fa94e5c4c7a7f3fbf7ee5ca1bb43f4aefae641446f00a160fe3a54671802e122e632010f52208b5529271b60780c328eaa1e11dcba426f9abef358139aadad7193bcbaf2216e9a8679489d75c6ccb5766e7355a5144c388eeb661d3a1b138623075a7ee66800709e73bbcb430d2ba48830cd610c225f002e4bba40621c78b67dbce47bb925eb7d9258d548bb593c4a2cb1d0d1034e53d4e79f0979b90b795defe507eaf8a6c6f9347a97a86c1fa5035042ac1abacba4341db92d34b8b80cd509ad49feb9c9bd6e4bab7db0086288d704be611d42ec8b1db752cb0b84fcb012f0080f68cbd8b8fd6c008c7aaab58195b592725b2bdf0faa4fec9a63ba0bd26d171deac847696d21ebfab550aca17c8c3b5f64982c980c5cb230a0ffa18bd6e545e63dbe763b1f8a748a9676e61565b1e0870538a29d9a8bfa545879e246441969c4b8935e6614b1bdbf9b2d45e388413cfaea829489c4b3c8b83462c5c2e2b6d4d35aa9e8bc5baa7436fa5e8a9e88637e4022c06f06bc5106e48834a5f8b07400c5ebbec14d4817d86add15679174dc482a01463ad6387765bc3b23d5dac8b73578e6f88714638316d26c6c540c8eee972d4a22acd04483ed7d9ece3c62fb5b26596004fda624c29ea860bb85ebc04677dd4b37b3e9befb1c8bad691081b349b2695c2d6ccf483824651b259604bebfebaef1b5710226c97dcf9e0803fcec037709fd757545eef2ebc770698043b3bb7fb08b120876bb88e4cd6d678dafb77e31f606a0dec0b4d71635500deb1650f55eca1baac3961b0a85cb556d80466369c2952ab1d9af45676986df38c404ab0304d4950f0e7469758ea3f4ecae494704e07f55ee1c0e5f74ed884d1a6020efddf5ada633448315490f69801958faffd25bc17e1d8e00702d1909e01b6e929a79626b5e0f25d62b3b4a41ecaace592cd78b07d17c818d903e64d50ff820dcf75528e8773b7f77c27c2feb6fd4937be255664ed8e79cdc1e4d50de3ad3a456356b6b550d66c17bf9a01a5cc836b08dd355efd7914c136a56b960754f56021268a710b282c072a260f5cc2c506b3d223465acaf051036f1962ca6ecd3b769c40233ea1388c8e27e6fbff44390389a7af60c7c71f252f993f11aab8b1e74818753eff1c0966c55a6fcf94ac237d3469258e753b08f6de14361d6ce50e58d27634ed840f71496cea0793b80a128414c4fbbfdde27ffa2897c94d63edf2fff9d097c4cb3bddeb5ef7bccff66824fd3b6d7fbe6ff34d9f83038ed2b5c8e81d8156c1687d78e18db2f055087f08043287082b4b061b5bd211d224a1d4eedd93f867b88c25fff88dccf87bdc74cc690d08fa162d9b86ab650cd8a6ae295561a750aa2a13070de131b7a55913ec3dff0f4188bf5748954bea5c7e4eacf81bc17a6a184ac632bea9420901a2d108d1a380d6cbfae33d0342e108d1928512e9f7469a0695c20180df06dcf58d73d1080327a3e3291d77ed418a963466fabe6b6d758370e63f6fe3dec28ad829ebac279bd8c6d838de95e91f98e020ecee5719a5246ba97b8f7572dae58fd0b6993affcdd6a43e983f20c54533237d132dc12ee4f8c8838502e70080346b24d697d433ef0b55d11624f2e4cb651eab2c1fc2896ef6ec0010ba55a8e820383439e3c5f42c6a43dd4362c87a3cf1e50bd6bcb3e9194e6f164c1642129d684a497584ad71ba09d1dd0635a7260ee724078c2b558bc5be82d2b4fbd86175717a993dd06fd12db08085ed7467c0bcd872cdadab301ff6e27b9101f2a6ff4d8ba8704312e2095f504569e87822476261e61e809778629cdb5dc55219a2946ea6374db0376b3f2904cc5dd2b501d07ab80087841334518dfc75f985c4a834ba4dd6e863e4345e3ed98341e5fa42f858b3294dcb80bad954b8aa8c0fe13c8c8f704239f5d4df293ea6c28240240bc637b9070d9bc2d7d02e32cd697d1d8521d3d2e34ce46e7003c6ab88e7a6af2a4021ee0c984c62ed3b927ffbfb26e8c8d6971193d2f1c5c01adc17e631cb359c73015735a3eae6f2c519198ad9f42f9a1217a964898cf6f7ce9c45534497db944d5e99358580c2d5b566e2cec050a3c48bbb12c1a8ca3e9235e13d1444a80031b9d362b287a3143e6c86eeff4eebf207160a949dfbf273bb6a794cc701077326da9c3e2fbc208d63348daea79e08a8a040b10d38cc7da4ac492b6e893325bc6b0d534dcc3a4a6188c6eb580b53f505fdd798893acffd0b1deab8767c53cda342641ebf82986382ab531eeb875fac03c1e8f4418d170eeaa7df3247727f04c7c3dfb3598a3c33e50ded0bfeeda9c0b0f87a0bf7d06ef03c474ba2048c0a2353b7c85b00ea7937bfc48d442c7b85f3e65037fcf2415c7d38d447983fde0323c115b45020b2800c2f1b14df26f9f93c250f0fee7c367c9fa13984509933d653f685a040d3ebbd8f0f91e6ab89c003b579254c758270fb2e7984c0d1c0239059a8e74dfadef16ef2864d250b6d7e33c902b11491d77ca0ae35b33e0a07762ad06037a1244103429a3c19c023c42c5c0412101076d6988d04ae24d6129fc7301cb7021fda98d6733f9d6d968d4ef1c53554ed63959b5c0dbbc4f4430a9a1a288bfd6a98319824ea338414b42bebb84a2a42dedba3793234f828336d83c8c991d386084a60598a4ef671d412e5343367a5daadbf539272c0c67f323e6aab78d833b378144ccce6264d4ba0f5f65343588ad626f51f6360087e93cc8e82986456f20173ab4fe75cfe6ee70c9178ed24c21561a7415c89cecbf6e3d16a51063a45aef0cf534dc7ce32ac8de356cc528a5aa0fb6ee16542842adb549669c141a50ab2d4008777827f1cb8a4c106e3e4a37eac07689cc40192e910d71d5d96b911abb75033b6b91a8ad824ce7c6331af35805076d3fb0aeb20da0432deaa61779b3fa0d5eada633e1ab410bc4e8ac05a809e4e4b90f3005ac1fc6e0d34e9154ebb6bfa300022b28ed9be72e3105bc3640b19595810076f3aab0314817979526feb6eebf7868aca4ad49b1649a85f228a1a7804f22551fe9da5510095888600f7d6abc7303420398f1682242ce20f2b7dfbffd5eb6c215bafa7ba553f71866b0eb4e47d257a1810166960450917f769c6582443126c0241185f7372e1cdb77ee061c6c2ac43a0344b65f6387e83084712137e20fc2a233360ddb18b0cfd10acbcd5ad41080985d9b5cab7965c2ab27a3f0529d9dd6d07c1d6bf0d0be707b5d69c683b6eeb6e6a9e8515e42a6efb75a62fab2361f235fde9080fc7d07895a7d65edf77d9f1fe52ce12c9de1a0570ad7716088da90b9fe7a8527999325dc9bfaa9078197efa95af252bd39ef924e96d1ca8e0fe7cff1d12270d396d09d1b160e9560181973cc70561c65eb6cf8ffe3ad236ee7061c4f02cc90089e10906300fe4c173cca890436a8ae6899cb0eeaa6c99a0fab25716e367ea814df9f2ab2ca5abadf652d70cc591416564165e9860da457d370fbe2f69d219ba14f6d74e2c4cb42892c0853e1dbb028cb49694f4f633eebfc65a14fd4374d5a34733e288e3ab314e922e33104f9bce6d137edf119de8ad5fe4bfcc22b3dbe2d78f515b9f098a768d2c032bb5041026579e46e422dd6640d27d802f2b92bc125b9df5b2bb9fa36a640290f1d4caf2fac484cc06236dd14edc48ae1c028f7a0513cb8c233a0b153021607eaca4c94290d816884008f728fa85c6a321b595b8b4fee7701465e0a887b34ce401ba93d93866cd9ba41c12c99b1ad32d7919df403ee2cd9e4d40491a4c55eefaf5108299b0ccba5c24e16eff6cd88c545eb8166c8374c73588d8104aef40806354900dbbf26c25ab866665f424995a0fd5cc1b83ea7ed4b9c24426875f90e66e8763a71a01e15c60261a70648579dc3bc55228bc8c31903350a34c1fb31e389824bdde9e6fd45e876931f54e988aadb434e8553a6fec51d1d14df848f5b39525bbffeb45ff408293a6fe090607702248804fb3b8400f2ea2ca89af7211fcae41bf2676c57a254770fc9db0cfdb4551fd382cc2628738f898aca16996fbcc76380bfa17438fc9bbe21123c81d5e0fef80c11398cf91c94905d6a8a2a1ded801294f22383fff60e2556c549845dd1848b96539a212f87941a02d6e15c16f6ebf063f91ba840c4372152220b171057ca18ac6e01805915087965ec6c621196193a9a1e285012e516de7da1591166c516f4d79e720d1fb2578fcf25941304843cec79647009dfafdcd3be4d29b1b2ea47c4b8181d8a641f01297f052c66c56f9dc7a0ae8a8547dd9efbf6ad1c9f356986143b7ed6504582b7010423805e114f7142bf1351a616184518425104a67591dc9a5b04de73d42d29021b34230f542f38442d10a1fd4b8b9c080290bb801f7a4689d90cd6d851aebd95457c39e807a13caf6ded826a85a815ddbec8100fc9228e3ee8588126510eaf84bad0015083c719c3956636e2d921b4f2df56f48b029de0b48ac5b236228fe083e031fe0f925b77bd26c6a6053d0077be8c6658728823a3c2b925ad82a628f658fa5f44b3ef8b478b8d51c85152c9e2587ac7f9a7be4b101a953900cbe45b246b0e34cb082187843a752793cb002710253fb2f7aa45e8da27edcb3ddb313f7b094c9f54055d2b61f501351a5ab2aadb0b61cc80ef314ff4ab783a0ae16ec626265eadf8c5d71f89a7b2a936209dd8a3e5779903480b159f6ab0451580983fd2e747f12d1c00943483d09296da2690cd5bc545f152fbcc022861901a4da55b5c74bf25bb09feea946bb3bf94c7b7a96c5e554963638e40e088649163bd41b05988490131af951b80ac287f9aec86ca7a8faaaf0e88ee6ae090972ab5d8b3134aed521416ff4fd5d64871494439f1009b53c67e7f9181dbd68d047b05e64c0b5a89446eb7471a00abc6c6db9c0cd4f8c10b8d1fe93c5da0d2f0c9a59ce4f5bacb0a6840fa2a669ce1425d6b7f3577b6fdbdf667a98581e89de94c9cac8139eda2ef1d769421fc202d869e6edfbe74a0629158034f404437731a2144f4e03293122851c5941a96f9da3a57e59aaada82cf5ae4e13266b0db0b2704ce674a03cc99b0829bd35d88577db9ffa83b2d461955140624d8cee3723c44ec857eafce821e9d5585f6c414d0f90103cd1aac26e5b0b08c69d0068e5b59df96efb50fe27f9ea3aaf8ef4366f8c884d573b9e7dc819239dafc88a3911cd3294966490a404cfea21b803953fa5398b60796bbd86a070dbf95b2b32b75d43485c8c0b8e7cc67cc4d6ba6cc4ff5989701e00d5968a4856bcac825b8b51e9533d53f583990c3ebdbc70f0404a5c05fc604ab73b245eefc092023d94a49998255c4c165313985e2651f1d8cd9789b77cd9ec772ff2e047c46aa9819c5ab461cb052f1e04f5e8764b08dc882b70d2c90241527f53de8520f1b451df0a5de2b1ac3fc70d58d9de121a887ed0be9f4181363c6ed3d346a863cc3faec6b19e476d29e134b7485430c7bbec1949173d0dc1500c2c8df96c029a073ffbc2e9654b9785beb439d203f0b96abd44a63c551a581acc088cf3d18d8e010e72c18a0a8a238a865b74fd47940afeb524338775c8ad9f0bc41c15d5fc473d6331dc94462db9443c20eada804fa2dc046ab03c1d8a7472e72574f924b621099fcd72407464f46100c1aaf40ef39ae23eb92ce237d2ec071ae441c2c7f43e8cf9dc4cdfa1b16336c57a53d474cffb718e75941df08670953fbeb05dddf38fcc6432f2dff5ac80f736077746014b75f77f1188a117bc5c42a69d59b33f76761e6bd636ac5467b723eaddae97917767a9b4764afd5ae239f9d65e9a5ce982e6357564dc40fae15b92764b568aca946ec6685957f99140b098d067c8149877d329fed61511c444c684c67664ff362fa85b76b99fa97cd155e3e94e0ebcf8b22805e28cbdd8b3c8da2d47106e2cda75a6fae759f71e6b6c33dbae16a6f3c947123311dfa6717883eb2e38f24867825917cc6aab32c8a65881e22c628a0d51d2f6aa66897b70e4afa400389ad59c31c530f7153e5a69a6fe296117d8d9d60775020f4234d1be5f5fb11ad42fd944ada0be201e1c1f9d32d42c4c9c496f5d3e10afc540f02913590433038fff6f909f74ef61af49719e4e273a0cfed51127490d9c8831cf2e251ae621c9b3cc53e8192f969047d187e9225b439058ef584b3ec937d948644022d099179b862fcacdbf88cb4c385baec8d5470ae8a43e8b2d04f843cb829c759f153ff2f2962ce4b48d0ca6f8c3187436daaac1f8e763b3acb93c5d36b28ed82571a1dc23e18d28a75fbbd163c5ae51e9ab74736fede2b11c86ea792d4b8efef8d806ed0723ba9b9471a527a34ab574044a22ae4e5e23d36a213f221ae8f0f7c3993b5c3999c2b7b859e0386e4065195d9a1816ad0905950254be89e7b597604c60b385e1d40832e8b90d5782d6bd600de17050476f1492aaad58c33d5c34686b9fc297aef57d8d7a29affcd6801e8539980ab61dfc8cd6474e18ac0123c6794459210dc763d8d939564974fdc4e1e48de9e6a72059717bfdf76939bc52c2ce104d519e4be91691e8377f29ef45c7d4ffe8edd2d9673cb86eb25619bbc9e40df2d2dd678427283b4e69b4c43ac9c8905b8ba26113031ff381999cd07eef14b27e39cdb7c614bb39a7e1e2a7e831f4af16da5dd432424bf35aad0a28da8de7039738c735a39c71f0d3a02a754b3501563311d701d5224692fb40fd43466b81d53748a6f55ae58cd1165ae2016523d31a5a7242e8ed5a11f162ae0ebd0f94940eee4a909620d4154121c507b01d38a9093cbfc3d1324407c5eb5927f4b349ace513f906fa2af708d88f9dff4ce2e78e4fbd4a9d7ec3e2510e63eaa1227e220f6b9ecf112188bd95d213b5e19e6d9fdf6be208c62a989a31dce9c06cfe66e2db644597fca6763d424ee4896f7d69772295ce5413d14525c0b9929e7f8c39b3c742f8d2bc5796080dce4187661fbe67215c35a0a610fb03da8374b0ee051102b9a3b72ec66798750f3c7bd6c874da0eeb750085235baf4a645800c479a2347ac2db015a27e236485496027de882f1a8b8a32150bcc60280231d6522e1d20651d591f0d4cc1c0ce23bb0194a414f4186120d5bc6de6d8140c1e2a8b297624fe124c18056b02c346988291be19e089615a37e2a1b21b1ca01e6c1694345f49196f3dde3785cfd9876f8bd8b5566bf0dc185cd26a5b1faaa41d8e248cf76aa3ab737bd5d1d83182d7456b747cbead6c4494aaaf93848c4d7a80695d5ef46ce766a58fc5ab40b44f5ed5d46d83a280a243f1a93bd0938acb7c0a5b2d1e26770c65bfd2e2a149c30e4fed0ae649fc5fbe306a77fb243e2b01a399639eaa23922e23ea13990daa8922522c6c3e94ac880d8707606971fccdd7aff6bc30fec947b141df86c838c9c2392b68d30ac6d8d3bb8e0a86b319d0d2502ba6832efcc8a5600cd1586d31bcba10ee66080482abd4fb46ea4d3678279c018a7399db99f81233c39e5f0723cd8897ccf467c89260c326b9c41e77136af26a3b972cc61874bf8c4fa6cf02c11df424022a0f00776400c49c1b0ff8c630361b9e3067239549e250dad5be14361bc20242bc0c7886190eddaf7e3e3a9d63ce6734457035d02ca859beb04cd0841361c4b39a754a9d33376287d90647e625951b1eac1c5e64b520509b9dac34e5fd657322142fa119fe1e7eaa9dac1a44e9daf93cfee98e6398c1f4366f82c4899f97c3d4f1b8b627244bcf13c62399a1387a8bdb82c350258edcb5509b7db00ef35082bc9c1cebfcba4d5aa3ef6c79442b4ba809915b16bd346708c16a02a95b220dea6abd819ef265d0e83cef9ecfca8e5b6a6467c9b167d18524e2bbd37ae05fb700aeb5a7f74f5d9bda2f1d686b3a54a254926db7b17427f85de99e442425fd723a0d5f8359d604533f0bbf99a0762767fd89941ddd922539469150aa4753110acd9bd52c35672de04d291c1dd17a0c4d01086ac0afd947be46c289eb8480c0f9ce12f16b9ee07b50d135fb404270aab9db50cb3a73bbbc1df7f4b29db6b432dcb6e3c1b1d261f091e5eb83ad5fa6703565567b9143a2d0fcb79a1835801ca29764144a769da946381d69d642d555e26c064ece57ecd1d6c11f049a8215fe35545e453868bfb9158f693c03f737840bda1e72d66fbbababfc4a3b0280634c47279a531fed08030e50186f01b0d37590781ac41eedf8c50ed160954606e26334f8bb34f8afb3e953390b213b55e6c436f557667f7e87d0e53f09d0a51a2efe167df0b362f9ecf0b4587864b594739c805c61428054fc40127b4a89661979808036c7a8794548fcf7ba76c9d6dea1990fcc159177ce34ce0fa9ec2b6598cfbb2c8325a2c49321714be67ace643539e70efa3998992c2f235c4de5c6e6be17dc873345f5ab9f75d6891de7dea3e3c7d89dd2d13d6d03ddf6ee1fd6763cb8a4014623ca256af7e7f9dcaecaa487e82cf163191d5d54322ac0673119351ca4380ea6d909dcdc5e6953246b0bcdae7a38a2ccb5ecba7521fc310656e6359d155cd6d976689036dad860a40a57f2be2c6e1928871182acb859667c3edc337d3a1a25ffb5f5f0a732c4a2f0adeded62abe871816273726c26d89226a3fe4c612ba633c329a9f491ca92faacfbe3d5a5d399a7e0dbc03af97d0408725fe74458e2a2db05d05c96b03e33d064ee358ba03e3df399962640acb927cfdeffe993716d7e125269755ab1bf500ae9624dd1c881bd8b74bf0b0c49959460af1adc7da2206d70dd8dfdac74415e3fd1011d9dae8b89ff0e04ff1344b303aad860aa2d248b78749548d467b78509e4f92c3e61a2480a36486c41fc9c3491cf10418d6fc9e82c97ea2e9819bd63c79b0614c82a4c1ff79746ceb4b65fea2baa6f7686a7b9389dd0ec2647332a50de73ac11c459aea451b949579cef6fe9af50ff37c36fc8f61ab484d6907c34fb994e3f918e54264e03064761fa1e317abd33d2b74684a310fa876e97630a6667f9e23ea211e4b1c6a494d5a74aee9a7bba607d657d74f96b735e3b0f5e21f920bbffe78866833c079e538ed7f96624d4bf98b94a9c9b549321ebf313e745555bb4d53b4e7cb48fee313b72e64b6acd240d2277fa2e695b538e76ce6cfbcddd61ffec232977ccbabe80df1c229ce2189856afee5741cd37dc6b6df01599f1ae6fb789f14e9ea2b3871271f96141b69cf7aa061dcc0d2b46eb76eb46e71980f9f0d53484a1fe71f77dbc9a09476abec4e3ca089196f10850298cb63502ae7878718503bb50bac7ce8586a7ce6e712e63f225a52cb6e6012da9a3aa43b4c30ad819c30d789abcb9177a902bc9a40c9c079ce4db71c2dc429f3196468a3a7f13570ac5b7e7d63cb1992d602c7822ce274d23c17de3656245b06c6e4733f265ed5f361a9431d276f45f8dfa79953be544377398733a8b3e8f2904452a6b159cc54082a3907786326d1eb864b0bc618e5ba79ea8df84e37a42ca9aaad10ee11249b1a759817247da7351d3c9c7b640560c619f23b30ff08e68b9b18ae2d94053f5e9309478f00a222c973ec9279ab53c7bdc16eecb89f53e3aa9cb38444403ef52d35c69be5a828417190d2fc315e30299b6991193736754659db25f16763ed5e8c37b0c0ceff456c5570a2a578741565b825d17486694306f2b6848069c7f3c19ffae0ae87664bb0bc9c22c23257741b33c5b8186fa8a03125861777e034ddb2e8b2424f73cc9725b118dcfb0065ab64f47061b7d7f45323b8d55aaf60e193cce5c104249b33513480b0f65bb87803a4131f839917c738295de37cfa3b1548e584f4198c055ecf23b2c837321d9c861d1ec439d0a0b9b85d8a268251248f1c329dccbc1c2f0272abc01f145e784d1ac05c1461688a5e2ca8a14ae494fbd187f847c62446fa52a20c97be9415c4beaa69a7b8d17f145fceb25db941fa401173bf618cfa47366b00249b193202eab9f1e80339d2159859333634f1c1a667265a6653b75356c8cb6f4400a6b884e60ec1dafb7aaa79f09320b3053e8d3559b0d68e7cc4d785febb8c0cf02eb979aac9e07f12566fcc2a9cc8423c1bb77f95c4af60bb1fe945222d1207a64a44c07bf8561c7122faf80dc0c12d1dcd9d99beb7b2cf80dce237424df2f13e63f1671430d224003877c21cc7b4e25e5501c36fa1d11056b5b866a958f3b9e20b43f03d5047c11547958962f6e96447e5d1308d720fa774ff67314f8cf7c973667e5c673fa08f6960d3f243d74f777ad2036253e2d492ade5322851df4407180a00189368b1b858417c7207a728befefdae6bed13226e41506232d28e1bfceeefa4949b4dadb720cc85424259ad5e858d8f267c43c23c660fdfe4566745b6addb15be8c03240ccf353dcf0fabf7b7947200e9a52dd02fec5a454a152f00b10e24aa89810ea6707ee057d41c883a5893dc933a704e1134cfdb2768f3131234582a2a0975357c0e1a5c058a6114e0ae5b06ae7c1267ec5da3da86ff8ac861ccc7791ae7cd5fba1fea287c2137456a51030fd60f62db750358c84bb708195afb69116ed09ae13f963bccda18a2dbc52403ee6fd48f4f3403619a30305de273ee783dcf033d69b5d2daa58fb56832d3f28ac9d869357489602e0e52dc5804bc5dc684abd43efd682e71759858a0020c05c8b5188538817cba910d721867af7dde666e9a1b6209a54fb5d8c3f4656a96c96ae3830ef2a2c41b8da17e75e60a8745f52bc5934eeff30685508be9ac185c7d9c4979de4b33cfb266993eb579f21eaa0018725473368a422f0272d8f090c677c8099493c16915ab8f45e7f88370afa20bc714c21b6d028e51b34b3607ebbb00005c5d4157a7b1f4908a9487937e2de75b33805eeb6817b144d4c3b9626f313cacff3f7971a439a41df490b29e3ecb84461b3b8fed352aef0880cb1b16cafd3d618d5e63fa4f09e7cab2f5399cdabadaee92e29e144bb0b62416b958cc0ac4dfa72dfcac18c97cbb75069d0f38149f81a7be1a98a713ab792b4eaacdbc7d6ecfd19d6f722386246f1e08d84b97fe53724865afd5408f2248b721480a9663fa88a55418b77df3e1cf26d4f9cc126fbc937992084745797e5a44d326d393dae0e221bb09a19c4aa23cf8a30a8d30b54e4fdf678cb82df834773af527fdeaa13df84fa2308fd1196985623f755911e1e0853ea2d9abd7d469d82a8cb63836592715f5d8520755b9832430a508fb476aca2d9da8c1bb0c30fa3981b39e7834a456e13de0f19d37c91a241125f35a34077f4b328638098294d7303092435529df441595886c160ff2f558b5e0c342b514f32736a6079c11eaec23e986f50ec9ac4d70f91a71c5587758089e17ca857412b5832f899641d6aedf3209cba44b3082a7ed574b3a5dfbf5369ba002554fdcdffc5b1f1507e686fe3d06d6271f780781a386f472400fdd5abbe9ba9747e9322c562b18333406326806f2dd31636407a19246d2f219e56435c7a72bd52d4ce3aef8002f82e000e5ba7694a17744ace5c107913036adfe8237d697354714d5519b848ef6e39b13a9886f333fc674d44ca5f130d57c2798dd83a30c5f3336d995cfeb58a4af7570a735fea9d66e846693ea2c75a102b63a0ae7b953c51cadb05fcabdf84d4520b692a74b840d10b099f327fd283559775c30cdb82fa93f2762f95ab04584ed72f7d829971a3cb955de0a06046020828b9d32d6de7130ba5068031eb9f055ecb91fee8c58eed8e9f9cb88356450c620ae8eda543c53666c267d839389c32b7caf1e9dff661cd3b842b6e8b64af819de752954e643369b669861a0d29bc5d8242a9caa64d9ffe1bea94687fb2169b6d3a82219d95652f8e78f95ac2b41a57b2c9a5512e7322b526640345b9200ee8f54ea0a91278ab849e943ebb390e30b95f1c6ab3f0c3a9eb7df16b3d6d570e1f99c7e10ec65fb2edf08a97cfe6acfdf911ce92127d0eb97d7050100cbc2a853b91281f56da2a38b8ea14f4f9fc3439ede7d2c1dfec4cc9e5320c4ef15aa351b02bdca1831248d2042bff9656cd2dcd4e619fd11a66586a88d9fc3447f437094efaba76577e1eab4e17c4a56860c9f989925b8d6e052f294a2c8e4a0d4bb535b82aaebd60e5f55279a1d314ad4b18d9b10387bc3694eba406d60f2085258c387575991f709dfba278ec8db674d2e9ea12fc2bd0c30d742977bfbee9920c24e452f4d45169ad7996779d2b2a956a3caa932b94452f3894a59da1c15c3bcf77ec6353ca6e3381eb121492cabe9156b8586d8b5582b33bc53b03c8c6821626f0185873fbc58bbdfb31072a9e4f410fe1dd8d542f735eadd3981eaf29d6ac3dd1323010c1b94e258d27c215c088933f81352bac5decdbafeb3abb243100f18c609e140d776682e93b57efc5db13c1df64417fe89c6b7704b08fb51eeaaf66830c823e02938845456ec538a1c0ebf8f91e6fb31596c57c49140c6370d7c153563b69f935d05ac705b60ffac7108d45f33ba436f4beb6f436563d80341fcfc3cac5e266cf8d564b9a1d1c897947115b09968a23d4e30b707d0f195c3060d6b71d4b367bfb7656258a767185f9f7f69d5bd825384d7132cb5b87fbfe28bffae397fe5c4ef46051e00dd07a9b72cd06f573dacc0b426b42981049a287dd7bd0b1d41988cc027699e6ab550e8fcb445759a09890773e4fc8541236f1bdeedcc7932b8944e19235aa3c65956fbcc78ec85c6b0fafc92d61fab74eb0b2d2ddce333f4289e91791a55070cb537a64b55f53cd558af5dcce24ae30b68b73e6429842836f4b35f9eec1f0df48beeb6fdf5ff79508e830683c8007c230a51dcbce0ce2c22291080850ae5a9a1349436b0ee3ecfea689bd1b3c16993fafe59a850a2c034f02a9a71f66873529b74ca318c8834986125f6795bff498e8a91e726fff3ef0aa3ef3b5fd028d23ccbf3e07a125f8537812b694e828edfd95878b0606db73339a4979422b5d64f42a01a68e1b8a16641203f01443b33fbac2a062a9f0b65270cb3e106c84f502b823cbaec2dc26ed0a8b698afb39c685ecea60e51c35794af2092ef085c1df74ccc3cfe188c4466b7a37e0f1d310e1b438e10b2c7eac248594d3a8adf9c1a68796375ce682d452d32f7091dbdad105a5581c8364013d542e0b3a0e528987c3c8d1d8fdb4693f216eb4ffd5db84e4be09f79b95f1d6bf87b529fbeb3d1327ee4560823ff7fa6c4b1202ba1e4e4794ec472e7bac40a610c451814a77b2d950146816617410804133272a09902bdc75aca7378202e100fe28eedcf19d1d13477206a85dec5cb72b95bea22189ce04608a39f02a6b45dfd3e68b8532076d7e7f6a90de6fbb242999b890612b057398dc0271a9d0be9c0ef2c88129d0fdfaecaa383f197bd731ba37ea5738ced3abe820f9e804b1b588368dbed309e1a62c0a5a3c71af07cc2039b7f183fe40351d6d82a0785e085ad31593ef885a53358b990c4cc2c650462a05a3e963a63ff2b5decd164022e07046205611a485f3704ad821078410afa13af1205963767094a23233c585473f1002b79e8c1f22d12f241c48078826c36a492b0438a81c414541668aabc1c3066eb36da4a77bf7234336dddc5402a03bf0fe3503de1faacbbedc358ce719212712d5dae67c294508a4cf7092648e738be41a9c4a48eda2e4f386c187c851255390630029e21a92caad7d54c9cf048138355a38408d1397e6eb4e35e259c95e271b815d682e7f2afe741052d1b70c0876809277e067c62211a0f3a2921b2312e4bcf4f7aa44d0b57ec3e1c8fdad372e13a94aa9366747c52554bf7427f72c586b8191145480a7cbafa2c47862783a41a0fb2ccb1f0036d0e9faf5e5631a35668beab75c36fd5d6e827f05a6a1e5c714f3c493870194d35993218a55790d5af757520d484c97bd9640bd7422bb1d42cb2bbace5c6f5fda69840d95b4d4e097e657fcded22a9d297fd92507d8c0ec7cd9371b704133acd1db8bc6c3e1ab79f15e985a12349ed035bd02ea14fb539fe51233565156a9bd6df98626cedfbc934b9257554472cb75e6b35c986a20b89d41e4039d0f2615345015fd335187337d48d43a2b32094087f2466356aa54b0099104f0a95f92591cae96be5761dacc8c28909ea7b0296ee4dd651d9557177c6fbac2b549f9904303f652d8e808b85051b3078e68c524dcb64f4b61805f697948f24076667af59af26696e0e4cb59581497f9828fe61a48e547c60f782a4fc67e84fc9ac9784d42beb0881345060d544fe5fc3cb0d2846e732b3240caffcc60ea17429cfc9277e6515170afc4b9ac4b4f9c93f81b8e556af197bd5bb16186b690bc9f1cba7d2a683ea5deab15d10ba4d09c66fbea91a03c2242a4e1a0733c2e029695a8fd10b18ae74ef255c72a1a9ee9fd161d3677f3bc4a2ce9afb6233e0b9f9aa8b98325776be92498eebf9b2e4e443bca3af48c4c57ec280a68872e52edc9e76caffbc2d55b7033161617880cebb31241eeac80211643066b0cc04e1724e48dbb60d545db168a55e778f902971ec6de93db1eb56600532ac31f28fe4133ee902d2a88c2947907d036080d4da0d35ec811ea84148a98645f7ff9716672dae14c0c0561d895b5c17f13ca97eb0120987c4c9dc08d0e10bc86194f71e7ff8b5df8b82253d08de99f58bde72b765908d4ba5cf493dc15783cc46e435e4bd14a02de3bb1425861674c903a3e884e8d703294f68ed54e7e3ea95038b2cde679e91d7b25c33007a41acaf75ea2f0dd5f75ff8393fdfde6bbe300d66a95618ee036f975ac47e8c9f4313544e0f3d2a4287b82b1473368304d21d1914f5a92e2a94a3382fb9d29008950c7f70a5106f36c910d7d2acda31404a656d92f8186f27fea35910a43e22fb2377254bef888ad473091d7b51150ac99efba4b1b6c0c7889c86c27c9cead524481344710a20e5beb252cc380479677690bd9d633141a8dcb3b9285304f43d390b62e37018900e3b512871303f60e8d9cd1fb6c054b902417934e01f3660b0cc8886ceeba4d13cd7b6bc7951553ae520068b110bc1cce3711aeb01750c8c1f31de6ac7280320135f0fb5197c40f603a46648e7325d45e3df7e21cfda9d63226d3d5cc2d810556150fcd89d32747e1913d0237eafa57ab7646a15e1d0f7e928f19cdabaf10c089226bf661f41da524553c29379b763949f8585561392d604de93a0194701a7ad4824779aab316b452c3d61245832b28f67b7116d684d5ee495a49b4676028abb3cd18a0d1a44946aa0764ab0d592f2967c8090f4c7b3ea737598e7771a59d6fb5527eede2bbccab8231304b8eece0870966648aae87450c12a457ca021e76a7c0419551e9a6da4b6cb07fa53cebfab1d381b577207849e829fc85b26aef139d3f02ff6a55276b53ff6826125340c15fddf869757b7585e72b217e43af610c81729876da5020a81615e65d4b392e02228d2d498c301d7be3350919efc6283a51ec580136abd7373844676835c2acb575b4de25ea6cbc245bb31f394a93e39cb53c4cb9312431f58c4b12cf24b3e399844f02935c1b5cf0747ef74256084628427e5dacd6a29898b1d746b20bdb7e6cbd32a00d5f02139d3b679195c22f74a63dae4774b4def34752af12978848b33aa92f47632375db472f05732adfbb2ae4520e6246081dc2c79275daee00d32d8bb1176d7ddff20700b2ce913e8df552e4c8610ccaf03bcc734c04bc3888264fddad1985a9cf389d56df72e3335e08b265773dbb273742697d6ca04f3c1e7411aebe8d56b428ef06e3324026ef17147e11dc17f68635cde19596806996c501f4b60b3f6fcdf898db9086ccbcc8e37d87040534d3ce3276956c96184a04f2026327c26ac9b083ad4fa37749affce8c434f65de20c83ef62926750628674c9c2d8d32fc49e3bb0461f0486580582636ca890fa84f202e097926584d4e496a7f3da8f5a34dfa565df0236b5a8aaa117f773d2dc3899d9382e9bf5cf07c429431fc1392ff3460ea28353cf8b53ae210911b72b0f70ae623fcfda213b4d5ec7de833dd1b9c8af60948a422b7a9c1ced2a3151c3a4ecf657f91ecf41bf5a825611c50f691d1ad49444dc6a8496de21b2568062f6dc6f7dc9662eb9d3302d316282b22483012d595be867f40f69feb582b32339b2b97590a509722cee918e409e8daa367d5851d12e23264d3f6d073d81c25bca2c2f94993046b571b3a636ca93cee0db21a27a90836831d103b22f772a06ff5b61e4e074f00c0cfb8fb15f108585960cff591eb750f91365622f60a6788b460b253117e2a5055228272f06c436e470d56ba961789dbd2ace13c127e3ab9e600728ed4acc51c0e04ac2dce67312a7cc8903b5c0eb3da16b12a7f1dc40d53f6242491a940f0990cf035cf4113c12edeaa9533426627fa8207a78af9751f09e4c9c6fd8bb039bff2382a027b890b7ca3dae0ed1755c202088e88e1ce8384a66e6a9a4b46261e0de6f02b91e7e38a373aad6ec7dc9b08cd31b0bca184963eccfdc0f5f321c9a6760b933a73cc00cd59c16ef1b35506899fe830f02f00a2894948c36f934d6a7ce046558d0fc992182c9ef8322cf96d38da0e2cb9561306a7b8c48ac0f55a54d48aca0ef32a8dfbde94b278398bc645e2379dd76ac6981b2a4dcbb994c313d167d6468a27a8aec462031529d543d7be62aac314d036c2dc252b905344a8e278ee4ee181f3097f7edc64d289b148e67b3e3593ba59eb2adb7ed728d464a9cd06de9a410c2d2fc9bf31dd4897ebc9ead42ae4a8d201cbb50d207a7429acfa30347616bca4f497715a2523bbe2f1cc1ec0631885ee1352fd4e5885a511f85674a423598de4df2edc6b9e956f1ed77426b83825d444e461ec128b58d25176648b07f82e6269aade0b346da6781fde324a19c029e543fdc5382f8557046ec60be8e9fce64b36d20f9180f1ffb07bd2878f522628e6fe996ebd7cae35a713813d0b2a6e49b4b0d2fada5f9a6645342ac876b2aca5ea064fd13d9652b007a365370ff13f0bbbe7dd010fd7b294d091ef0de48e30193faf6d0c55e54d3b9adc1310ca826e64261755e4d5034e56a1f0d1aa13fe38c5af61105430fb29a09188e6bd23ab2b66819a5c20155f7d1fa011de8d6240e76808a8888ceac6e5bd67035e9711a26a117d67c5c14762f3f94c1e0ded36adadf680b3a5b28325331c9ffde477699d0f03dfb813176b8c4e7627959d1d877e474a738993f46137ba1a297b2534e75e4f908256ceb87792aff2a1f11ffe99ec0eff0e2aa0e4262715de4b79833d3eaf5226abc5846bd6cf061bfd9583b216cf248abaf1fd6070913a3f707ebb26ab86cb0b130871b2f616e8f5141378eaee594765b8cf649f80af3fab7be17aeeb753aa64ef8ec49cd32fc20dd5d1dd96c27d3cdbb581712da6fce8f987986f0deb441f3ee751339d439cfefff96560b11842f861d5354ef28079106adf7eb8088560e4a6b22074a849a588d5c75042203972e32b2da77bac9585fb23d7db17d42a57bc60b7d08054d953e64554de39a04b9c24d7945ef22d7cad9ddb5856b03aa400feed5aabf09d1bd60d06f089516383c311f7f78ac4c6acdb68c419e039a6150bec2e09046c2e4995f5bce65dd36209c3f4e2cc8054292f9f009e5f2288da554c560d8d024430d22d8b762a16d92cc457243442adf25d219a0fc89b78a293fdefeccc77ebd918629a947aceb601de8258fd35ac83b5fe228a0f328dad21b3e75efaade1081ec3b5a89e615f2489d72261356f43faeb0c0e02484623fae1d293cf55210752c0ddc40265ec0bf90613c142577b0c4e5faac573d715287bd2c21454401ceecb45722f1c144530374ea460de1e6fe0c3c3f2b3c9af1ecae2f2cbc3f5ecc10060a080a26bbeb23e1aed0ddea806df98cfb8fca05e9257e1139cbe7210e427d6af98b5ff0494bd4edffa01ce8d848efc1e34498b8a9e38e8c0d05e87fdecd368ef87d9845d2b0b34706cbd67c2331d22548aa410e88f2a7596d4e2df9d59d165b2bf4fa973386d8a6a7d15144cccc38a436554e0399a74a854e375d2faae9c987d99cb436e7da3ba0bba64821e390098571d8c2fa65af64681e570432bcee2003ca1c0b2d0115e87648948fd30b6ec6edc1c05bceb6d4154fca13da8b4df9208be4c1f33933efb2556f88715ff7facd00044defd8cb25a65c1abbeeb4656146fab35f054ce56ec50f1ba0c512c52aa02e5688c5069e12fd97d802895d459cdfa4c06c71552f75c1fef78f1b2e5911ec819a004ce2ad3c5548b49d52e09e9dba0907351bc4ba62911eff30bd56a95358cd3fa08f3e9201e85069855657eaf5834d5d52544248e08d387d6f75c58d4c02e2c021044fdacbe06eb5750c4fa1d18631af2d6ed49d79098774d4f6ce2a6e44d3d27709e05331b8de89c57a76cc1c0336b35a70cc4ccf58047d204dc7a456bfba0041eaa8e35992ece03f2982f97fd0c5d640d1c550c4f8b8ca071dab771364b32407a967741b9ad744972005361f29fad3d36a36e7b7f476bb8b6a6db48e02beba9fd4b2415b71216ce091ec825515da0251456ca44f4a2cfbc00edd6507a2f44890ddfa91d96c234117f87014388b171ada3bbec39e8109a96cb0a1bbd6c9e2bb6223b64762e0be0961b7e1805e82993ff9dd181f5d13e5ecf52d67a763cea9859603f27b97bc54ce62abdd9d1e18a107147be07c8b53ef2488c208ba23abd504c51ee8796b24ccda856bcdc24cd2a82524bf032d74b502d14588dd3b4ff3b7e1bba334393ed6984406714eb0a67192dbee2ba5783f6b2c20ccc7f4d0f6651059c548f99e65287da537d0a7ef056d5be81031ccc2229290053dd106232a517d40013143ed9c40a191dd8dcacdbcd7a85c0ff0342ec32f8e2f65a8ad45ae53bbe4500cb9ff783cee8bef7060d1c202354082fc8ef0e8fda0b09f29144210ab08386d30d85872c603442a19fcea13d58b25af1abc43e1440c2c1671eaee9c9b8fdfcfe78e896a0d3a08917826db2d815eaf9751caee6230466f25fe00ec3f7aa2ea033698f68b830e702c170068b51c3b80270ed68831eafe352a5bc4d5f0608808bf2300d4517f620976234e846852c629bf5c1f0e836280f836d336e0e674de2d8e953ca58ec744fa4ce0f6166a6519d8881c37d3ad4babda1f1f06dc81746a0ba281d85ff7815c1d35d417fc7a2997f8120d1df0ef67dd5ad22ad6668aec5ad5a2d240254a74faf79d9f90ddec61d0f426bb1828868af387edf13a3e87e0409092e6564f80370591b80a078a2479aa6024a7223417141dfe94ac6b0ba87632773762db9676db70b390f0a2df2f63501c39195c3ac463f1aaeb0e3c05111217505fe3d4077b33904d06fdf348aabef16d160fe25bec9db8b6501ae4cd50fc9d038904b932fba76e68eac89a9e675226da5bf35e16426a86ba2398cdb5df39730c6697d5b0062cc12f256bd78e83b295be22181ec09280621f256113204f8e34c49acb713f850b5649eeb55d9580aca660db1ce28743ca196f7b26afc5b7b6b6a67ed140e4713c37b3197010166d05a53d27f5ad16b86491753d3e2f391a48359c400a1fb9a7403735323392e539aaca2e5cebb545a5a32017433245e5de1248a1d6955e09edd3114a9a459482f356280aa2bb98fae329aa57f85ab969fc55c11798291d3cf59b994dc555557b5795c9b32ca1b9442d99c19210aeec001daa39d94893d6a1e29a831f34a613c8da73600255999931d4c7390e64ad457f9b602e980400572e39cd1b88e5c9f052fd930db7b13e5016718384b370cf7edd755ea75a73c736436a19d03215fca359f663c7af0f16818115b9033cecb7072ea4560f21de6d44a0ec1abd6d9f3af81404a78d6bb666b5912105d921fc65febcc9f272db2d30002b616d477eb18e714866b6e41890aee1f3b4061dfe4b3d23905a67a98a882833acf6261ae426d0b4344795e1748248bc3e89ad48af708e13caba08cdee3d3b94dbdface923b309005973861868e7c9295c34521e0906ad854e2a1419a4e5b10278da3345fe65ee728c81a61dd348e2e3604680285d2402085b52009a98e1d4e61f75e17eef53aaf407bff9bed5b07b891a7030d7eec01a3c3289d749a6adec6f78b930b702fbd3a8bb0c081cb2e11cefa88c0c0c3e7bf036aaa59cfe626990dfd3e85ecff37a0786e25060b91933a266451ca90d03c73b174c7f00641e2999a7034344cee08fa7d44043dc7db3676a00a5dd586c4e6fddaaef302db3694e511aaf3302ab443525587ead793bf474c7d3b6e4a3192351e83275319fa6cd042f962cf82aadfded240c0aa2dd9da2a41d60c8ae75441575a7d1e638a797aa512c20ec2acd380c3e77d321660ef8eacfa32a4514a28fb601e8f87f357eb7d82cd167c45c4969a3af2a0b6055d046d866c5d9ad8920eb90e64d7a2d4734713c0915f12444d9782e768aadebabe43373fc139fbd605c4dcc64ed934c2e1130b2ee25bba24fcdb1d1f5559a55543bec4c60fa68ca079ddc53e9dd7e4f447052dcefc61ac20f8c0da7de6dffd62328740522d9e5e7e8cfb119f86009c0a0c72dec3a757ca64c64495d9d16a1550a3d74bfac7aab8221bfb6163d45b0a53009fba81f259d09fcff4a9ee3f30b4303afd87a23789bf1a63ecaae08578d09190ba5f68de5efe8c920a922c40373c139ad601506b2f4bc60e2129b78826b17ac5d954de1ac14f899a603bcfa2ca18742b3d84a66e23da145b4a3c42ff5c773a62226c8d6a8caeb50a208908222b2f601ef3c37599a48fd505bab6b90436c5cb2189641dc542603de80c1bee938e68a0c28b48e6857c5daa05ce25c4c84b589b3137b280c1df8adb5f4417a497947f05217dc888f684f6873793d12caf19dbed38cc967e408b900d239694a982ec62b7abccaefeed39feeab13aff90550e70e6e5b97c2f664e8b09d49df95b3729ac67ae5086f03261c99ce9893e7b7c1972822bb50a06395a4319405ec18a632b53ba958dc67ede821bb0ab7d7b36afc78a9722eb3cf92ce6619701f55333e845b22969d4a896b8fc739ff61ab78b66242f3041400e8045eabfbc66ef5c4082b2c0990b31073c1332480aad092714d30d082c5ca6a74b9128c413c873726cd010ca3ad4dccd442fb8551afc2fae2ddb82a5622677245814dd6c37342af69c895da809f94c4db3ebe4232ee2198ff218b17b8fb9cfb36747aafddcc32a02d91ce5baac5fec92107d7f2a891baa738867a0987a92f8c2407c8f54d74a05e5310a2da05643be465f374e90f8324638d86e7aad04f8e78e58532eb0b1baa849183849482f33c2660a9de2f5e50b245b417ba2ea706a8ce83c55543fecfaa8816301d98f187670088c9d9566cacc2941a490b470867085ee2161786d1177d07f673e2c0a8651d868a4ba2b976e0f2f8dd06f2281c2291b6a92844ac498f929687965d36d896adb294ce9c643d0435c05979d56d159ada4a441f6101b69b8bdbc114102f63bac7ab7ae99d62f3237d4fc4fefa46e8e44b0f64c847cde1ccd209f135b8c391c4a441bd2c2813888f62b903e46e6d6ea1f835818cbef94196323d420f5a89ba871b54982115415735db870d14297cf51d844e6c059e8fe85d6edeb533217611da6b19487b3aa91d41a006447654a4eac0a0f286982eaa3f11d1ef4c4309ab532bdd7202a4fe76543a2c481d450055fceb279621e445a4324b4a30cb73bdc817d6e160dcd84ce914226b992d106e291267e4824eee77c0a9d465bcc8caddcacc50b1b2642c8448e5a8a0f826ee8300e2e0890ba7c84500c22a0380a81c759852983bc24683ab87be4814ef38e2ed55a3e00400c3b505dd96238d178af20e18b40a622bf6974d7a3a95c01157dff989f8d2e2c9e75312064cbb8963ef7f34ecc400730d482c3e4a9aea1ba95ac8dfd0a264c43694b03542e04e9034f85736c5a14a6741115453c244c33d87d9829f102fd9f2db3546e166b3fdb1ac0d4e7f49e0e7cdc3e97e9b0a9d2b968ebc04edb20bfba1da7b56e33d8a51345864119e91be84653069dd51201cf312abf895271e2a37311dfb3a9d16df93f669992c6dc4b208a62254e84a5b65377632bf89ff7083db0ff0c332e9064eaa61b39512645774d0e229333b48aa845b4f4eb8f93b8e69b360db8639100db2b50ea1d4b0c76b8b33c4b620cb528162665dbb716581c7759d6c9d2cd8d631b594a474a9e21bc65c048c9f6716cd4b9aaae4472329f49a7b9bf8c5b7dabb606cf6e47930f0df472f604a2a62e03118d07ddc31746b9aed06d79387de62e286343f00bb5fd56bb02373d25c62eb8b9cd082689b50bcd40aae1b9782406b789695b073c271d4ee38b94de5c7d178b8ffeff91d46eea483089603f37804bf83962e656543c2324689a71d63423f88fc2dca29e319045b90c6050f19b74da2b9845723bbf2924208d8b65874a13aab6805db757a398497e9a31ce5f011d3076e49c6a271ae7dd147418b061602753ca51e6a2f7f4e366c2ff4b992719d174bf3c21dc1b286800a6724b51b1097d1912d3ca829da55ea9850e2fc19aab11bd571fb743fed9285790b98402741110da9e3899321f0647c243d5cc2c7605286a4475108446b0d46c2aa985409b083117aaec0fad22d080401735358cd4554decc2d09f7640b8ca7a719db6b129f72d298b6381d24f5ec83e4e9e861300fb2d6c00f34fd4d78b619383106c3aa5093274bebdfc230b8255bf44c49c6952e1414594f66af4361cf24e1734a3e36553297e296496af7d8db57c5af27243b959d51f20c7eb4654a0ea2593852adb900b5c90c17d03aa8bd8e6b9954ed47653882157d694853eea42c8cf59535e1a81f7c0567c45441d5a9b4fdd7bcc2ffee32106a88bf1921d66970313ee34652c7b2db0c41fdeae14d3c0b93832d497bb572bc004cbccbbbe704d10f2c7018fbc2aa4b7ff151e6eebd78d87c010c88c11c2e799e9273ecebcd3b2b4d7eb3be350bc15a8ee8d648cc49115a99c775ad63349caca8e261316b716a94b99dcc9b00a72dcb9cd8448e470cf5d90e9c5a6883390eaa4bdd84baa353b949145c98e6ec8b979ae700ebb7c70b0a2f9a86cd3d94bc6188fe4719afcd7394afae850b8b8a57c1ed6ae883a520084b317667408b69296bf68a92c51e0b00910f11c1a066361ab7cc40d01d83cdcfbad8efa6400c7b2b7df9cb84b6049808a74d860d18fa8f347274977067aac57790114c6ab32bc74e8039b06ef158cc5ab529e8b57b9147575f9885846d0b365ebd0e54b3d60171f0bfe7ffc2445f5c610b8d00cbce00e62b43e14dc55d2a32d63f795746c0d9c84a09d19b0720b6522e490447cb42b4619c3fc12d125e0484836180364b16ef6dea3403c74d9f92d93028102134dbfecc0f17c46a4d6317c5cfa69ff395aa84bef9d4ca8e4a81fe5b9e387e8663083d098f69e666294620842b804e0b10d7e5f0fdb2c30e7b19c5d7ce9b0bd54f5f5c4ef25fd8e83d22dd6201cf5008a0957ef1610e8868eff4b34382ffd76a5ad167718a0970451fba13a108728a21e040e8ddb9159e2a4d66990f6de00e9f4a49da729bc994f32fd21ad4379c602b13edc5ece29cd88c161fb53e7b5bf221fbfd1544b408cdfb9b69738c0f444c38af947f04eef7224ae440e85ce81132ed20fd6a08b7e7c7a22d5d731145bc1af3cba061f15d7ae04e76aa435bbf6d5a20cd051450cb00d054926f86e832992c800f82a449879454bf11d111e40907e146d6827956a0d0b635c054e844b43b8b8155af0a47b7c1b92f9c177f588afa6a15e7f2c318bea4268bbc11af70604ec9be9f6165d827ad68d513cfdeb919beebfa6f2b92d8692d4abd2c7e1c109e6c2513504cbbb6cadb2dd4f45504b77f8fa26581d6a7f5d18250f8289d705dc1d8bc2d46c8c8b56a3b6ec5ba60bfe37b8cfdab5e744706f4fadc4814ebcca050dce2a784d8fcc6c994d4854f977394212aaa46f915d9825b331f921ec956e6e5a679d0b0e4a4a8e3b80ba385d93016fcb835f415f4ae21fa8685c8df21ccd8c4ad0d20dc4cebad442240f415d6839c597d10f8117d059e0533017288bd86258f90a03710924b1558970333af678dce4bf782cddc68e6c849612a8cf84d7ee199579676266a383fa729b69c1bb3fcc99e42506f55293193accfea1ca602199655b13b20cea5849144a48da0d2bb7bee180f0043134c7496603a65a4863ee49c0d77b724096229b309eb6101274202e2accda05f0c0c3b034b533330c6ce3b3149bec663e2ff0ae78e2264739ed3f79bd4c06cfc0f54cd25f655cc5b857b92be8cc846f037fc398a656843de93789529a672d354ef5053ca722c71a628119cdbc89539adbc011256f4ef4013751bf94622331b18755b188e32079e0059158dbf2fca29fa918ef5096a93e27ea1f9e7b9586fb0db0e4ce103fa71927c5a30523a4a1b557ba611d5181049f87b5d3578efeb15ed62555c45c95e739dc48d5d89b1a19346eee9649720ce20be71ddf7cecec471e3790bac553678cbb848f0a2321d57063ac65bad11fd0fea13d44d12222989482291bd690760084e07b0073203ca3f72503621eac4f840c0f5af379d7e867b4ecfa8437fe6d613874d1a6b4a8fcc7d55fa56383125ac63d7755dd5d0dbaf32f63663d6630880bbbaf247949a2e65159675adb56fb19bdc7f89497bcdbe0848763d92f65fe7eeb57addae6bd332eb177fd8d192f0b2d30b76e207f8e362e211f4b1711fe311d1043bc62b95abdcd33c8d5ee2499df89c0d2c184a1d4b7b9e3427b566fea491a86661463b9256423ac16234e0e803a683e455310fd8ea0f9fa828ca96ef590587e2cfeaa3ac2af0a5a2c71b04499ae9ef859d197961cbaf681bb1b634e92f866dfa6d03024aa53e6f67db7fbfb36ddb7e1b4a42b76cda4c992933fdd7ed4c675baddb5002bcaa3c0b226902acb7d62f86bdb5d6643299be6867d96fdbdffc75fb9a724f19f8b587d956b4b79c846e0dfb2cdffc25d998cdd56399fe35bf7e90846e5d61e79c55aa6acecf3dbab027dc39d20eb955b9cf97347b8e30aebbb95ba77c2eb54a1527f6c42e480487e4c79f28a0860c410d77ce96b342183d9797c72eacdcf4da2aab8fd10cb3f7fbea150e81e14f5da137037d78402f7e0761849ed72a6bf4aa2a75304429c03ef62d60df02b744ca92eb97d4c72e0da50e108c5eaa8a1504160a61bb7d485eeae89fba62c04203e0eda6674c8c7388e34044fa56ab351d0825c4e8b3aa755befd65b5e1c41ce96357abfb3054bfd59f875cb4737d473c884518694f51656cbb2f4071f47f4e1d5e73edcc1e8b6657dc51b96bb68647d9dd6b2acafea4c22ddc15495e538ea43fdc1b774a53f77aaa30a28a594d2ea734faf023a84d0e974a702858218ba6c8f12dfa330c07a9475fa8c925616ad547644e05aa5cec49565d518e3ccb9d785510cc32686490cc3302c6298631886c11a42955583449905b99910d72a8d6adad4344d46d7340de20bc3988d0eaf0b5bb7d6420cb3f6664259a669dbb64d6bc7971046cf9ac3abf9b6c19979c41056964bd5d0b48728c8cf77fc32b05d6f189b4c27df3dddcc8c27b42324ee1cc8d309e108899b833cf06b788c31c618b5bbfc19a7749f4b61be54b992f4c481982fb0d003949a77ce2b39135ed9baeb6c6c6c6c6ca2436d03521fbb00906a3acddc1e39a7ac8935355e032fed83e1749a99e1381a9a1a35f0c6aa689de130e73457484d07d40175b1734a4353a3464d4dd779363637372854aa67e762685574deb821a35f35375046ef6cf00dbe12dab0216dd88836bcda6442379910148ee3407983729c6843dec09104ae40638ef9835be24ef5ecd41c570e1682bd317f292171dbab23f3349d6d07e6c163c29b96f15c1e8b5d3c3cd5e27158a37d45fb5e871107077338703e5b08b5ce9183f32204f403c210421041870ecfe33a211d6ec70e1e3bdbcece8eb6b3b3b3b363632313ba2184c3a3f615ddb89109898f234772dfc91ca1fc5c177de7e0703964f41c392aed33a7391c73721cd29cf5a633a11c5cce0de17a16e48a0075441dae83e66428cc9d5474284f803d4f4767c70e1e3b3f42266421beaa954a553435654c792a958226ed2bd2e1e9e87411da910989bb0708f2b83c78ec509e29a3f3c0aa7d4b3c2f13e256277ed43a3b78ecf08c20a343c8b343661d3dc6e831422b7ff59bd0dda7363f222866f3d8b163270bc22313a23c3221d72828f8bb0e631129401581e107459e11a4ec4164fbc748d4790a0001c884049009c11eefa9da5734c2087284e8b0070a85d4beb1a3fe260081262c8c6e44c6996c77aa69a414d248e98c926a0de91473ce49941373a0110906d260a7f657043f3772a31d03f6f73d938804c03be0f8dfc78813638c313a86151cb2ed1923b7a5ac01f5e855abf5b562dc75305715dd441ec444d8092184dc7dea5cdcaa28a5d4a97b2c62d239e5c3af43f2ab6ac99773da9b1e2f289d93cea9ef3c83fec420c5bf0ef94f2d44df7277cb5d4f49abdc3306ab307f5214a8be728931a4958ba4f690fc1bebc72b095b0f777c5a7754e2db7591d4aef4d07c9737d88ffe0d135809dd951ea25aeaa1f83ec3d2972caa940942861bb24c1720e1e1080e2e3e40022f5d0081cb126362161d30e388279acc3082262418244a62e0a5035998a84103369c71045a31a241135f9e30592209337e904598264884e942c90c2906121b4c0cd185164590994195320a10850c252652c2fcd0c5bc922305195e6c20081f34d1032ae6480f61c0600b285ad0031371cb912d3e948004484835f8820c19908104298d1ea028230c2c494954073598628a256a80d2039931908821a3054300010aa32f35d030530922c8a032c4132c64446024c34042811684e940131fc22c9d012684230f0021cb18198ac082e6e0c8ac02c9092ef0618b3159a0e10130a81847ca78312687107421811552fcc0831eca50d9e28b333e3066098914400105151a7ca1e18b1c03893efa000f9618d3449829611cb1028e61860e70b86203165ba8902408c3064e0091a481249070010e8a8408c1126472c032441857c0e8c10f860801164258018510f308119668a207625809410c6e9853e091a9a50b078a30438a1f68918418f40bdc12850cb4b84203661c81c313934a1a5794a0063878f24412497c8104822993c609c68ce0cb1667502fb48031c313204ce0050751ba4092c2165d6cd081862852ac50a5235420819409242d30b1850a25c48069a265a6801a01c41622e822461844d01b1c992f80687c6982e54910aabc400772043f88c00654b4c00a0e5ba2e002890c63c268228827be58c2872d907c51c20834c290c18a3368a05e60900ca2f040822bccc0e1055a403252762042074fdca0c50cb309241698e2041fd042891b82b0c2060f38e38805629630a3075e680d669214e3e3012caa4841175e84d1046a11812aaca46183204ee00507ba1c296201106980b105193a5cc981d200491454ac30da010f1ae08013740631d00004305ca0000733a2805e661648341bcc20450b30a688810697b802314a3ce921cc0d42d02d47240f31474c3027619eecf81188f8e323c6075a01e365c7773137a72c29fa7895e84379b0e3fb951c36ddc1beb82cd378043525c09f2bf952f46ef08071fc3ff635f237778d370229c6ecbbebe6ed7e6642366f6176f3a7fca5f6e96f7296bf157676cadd679928b5b38f9910f69f27d5f8a4d3d1c912f87327950cb8c6bf67416abec607c90aa3973d8ef74eff383e6234faab1be37e467f7e0c247f6ee898e1682875362db5335dbddf6c31a569ab54e9d93769ff883ab08a8d118b91d43f5ec7bf7127db7abfb9827dc79bea3f586503f9d20fff127d28fd1ca3edc41dc7cc97c2c09f4cf912944792ab9faa554b4a86061a68f0bfe63412e44646bea05de92fc85a7de7db9e31188707f8835a64f6e3ba6ac5b8ebfe33b8af8d999023f1439628367d0c6a195221f14396a44d6b75749d4201eae931e14dcb2e10166310474675fb58f153138cdb6b6fb529b0efeddefb4375fb6aefb5d785b3bd32cc6a1b5c98fe946127d3fd7ab7ec3bc3aa1635acfd479fa86947f7de7befbc990d2e2c135a68616484e52f67635fa3cf497f7557bc4df0f49346e3199a65cf68d9333371e6bf222af42b38a33fece929fb36e5df387f38f6fd4dcb593ee2a9093108adb5461b5fa6ea4e297dabb2acaa48ce105007faa81fdfc6c6042f6c19718e990026ed6cdbf3e3c76967d3b2aba4b28ab01859b08108b344f73697aac9356832cc33d955057d4929a592beeb9ee8590f923a99f0965d8b51ea4facfcf564dde34fa24f7d8933b5397fecd9137f8252330aa5d78362a27814291d8a01dad388cbf9fe010155203cf9032552f4a9a46c229bc826724acf8532f341d18b3ef2639229e22cd0d81f8dae2a897434f6855536d4017fd5dbc7feabd087c7587f75d39aceb47fb00aa55a89dcf46da5844aecf38954cece0edeb4eca6a20d49db6e94e6bb1b98053f2adf649bef724d9e791aeeb5c76ff115b072fac32793ddeae55996657fbb1aefbad310d6e81aba66dbe38f3f61968d31bc7dbf313621faf8e3a4fdf2a7fef7fb7b47bad8b24a4846f5dd49f4e2e32ff873244792187c17945c3fd951cc4cbc5d2cccb9aeeba29452eb9a3e57a67f5dd7755d9665ed58182db263557fe5b96bbe95b66103feeac4138a65f1a665106e1d1d1cdda6bf0eebecf8f6b2177677f6ad738b9d1ccfc39b96edd8186d88db9b18d9d37f75c67f382fc1f4f4b993def8b7b75b166d599f3fddb608ecb2bfa2287b28fb216b6b96bdbdaeebd22eec56edf433f1badfeb2f7bfb417263d7ea0f76b96e3fbcdf4bf06d6b6233b7aca75996595d62972e996bf9da7a5d6da30b2875826203287655a16f3f888691b5828454a7acb08e38dbb9ed9d9bde5d7f443b1bff7d7b73c5827d63ec462f76545bfbd6d3fc153931e9aa318d556ce6b4693965d921eb33df9b56e464574af5e6dc78fde7f4f70259a0ce71883afe34436aed3b86d94c04bb14814a50c9a1d276a0ee081503fcb90f108d1d7d9cd817e4cf49fe489de851fa0d903f3fe857ab8ad3afc3315be2aad6f710faf5c3080c922a92dadf0ad8dbafd25fead2f067ef5bce74fe746cacba2e4d94b3aba34d8ba436d54377a691fd4d5dde4e4eb5d65afb35887dec83e2951fab3999f6d95c4e4311100c981e860c98be060762d24b1c88e9fd6bc41bccde94ddca4bdb2d7fddf79f8e1dbf7e50bc377b98ddec7abfd7fb755defba48ce1eb2bad3385a89dc4355d71b3d9ca26558014365432b631ced6e432b6178b095c43d4497ecfa18e68e25895b49c5f2f5156696c6dfc470ccc61ec3bece8de9fa7e6928a7d4fa44a20fce1ce74ddbb7b6be045e637c35a6ab43d7f9f0671ff5634d4718c40509680fa5cecf20cb940122e5f31fe265caec21a222ae89fca1c0102475744aeac82aedf7dc00266d7a85942c530648a6c542f7a3eba856bde97b0f8aa345c5b3735b6b71b6cdc9d976d3b2ebcd222cfa0e8a78d82da59f43297efa3350fe70dc9f3ecb50d9cf3c0472d24b5099462960db6666def4309cf457f73dfdf60d98d1a687d9111666fea497c02a75502b68da3b10fca8161c087e07f2612d053564c8fed5307a1d04a2d5e8ddaff9734ef4b0d4a9aaf937ce237eecf40001459d1f55918df10808298c5fa94fe833dfbfca775da55f5c8ae042c49e1f87442e457079b267915b5d8e31613552814dbfd60af557814df557813d047fd2eb3fa81dd312de2118e052041727732b997beaa8efe5520a5ba38f125a2bfdfdf6b1d0a36f037d6af4a27dfa1df4713d7d6bb3fc18f6f13be8436eecafdc411e0c67571fa1577dfc0cea602c75aabf740779aaf9357fdcd657578da315ee64dbcddbf53fe2d88ef9176dd75f7d0a03ecfb8ab8225225e697ce96f988efcf8fe60f196387434d9dcda638e83bfc0972273b48fed4a50fe370ff00d4891f97e24ffd751b092edd80392a9f1f595d58e8357f3dbbdebb2fb72fcebe39fbeeecfbd04b80dee36a9b4d99c6efd94353c61868918d3108ea55911d2058d29e32fbaa7ddc5cfb8b3dcdfe16524a7736f6f52bac6aec87b2afb6b705c06f59c11e33219f655b5dcf8454ffed6c0f8abe594b29a5d4dddd35f6d907492dfba699fe7636fdc135fee0bfd4c24cc80ef601d285ca9a73caf8517a9c310a71c2aededddd7166c09f57e9cf4a0404747a82458716649f69ee37cbde5ffe9c2a9abfd4dea6eb6ffbfb35b9060dd6327ab5ab65d909706b7a9e4cb46e5a6541e4cf24d5964f935465ca94a95ba45160d93ef6f7d29b7d633e5950fd97fa56d8b362d8c2ae6a55f267554d19b140daf32bfbf153a832feb9422985a8953f98a4e5e200891fb69450e290b976717dd79f7cfa36cbd7d72cf74df6dadb2b13d22eedf2781dd92ef616dbaedd58f6f5c8d5b2b77f3d561fbb6c9642d1ce78e2ae1f5f72ee9c007ec88203cd2ba41894b6f555cf18eceed42184103e9c4257fe9ec89db8932ddd0996f9d39a724a6e4ba93f4b3ef6d8755ddcf5f2edcc84a4f4cafa2a3e5137777443767029824b94524cd70594caf17672e4ad79a6653ae5ae6fe5c5393b2998049403276e3bef964d6ffaed31beb2148ab6696b307ad7d34a7bc8331fee9f3fafbd30fa571577974f63956d0352749154b1d839abdcaeb1733bcb72db78b1b86fb2bfd7da6bb32cfbbe70df6cafefb7d033a1cb923f6fb8bb7c1a7386e05029e38b0a0afd59299d4bd38bd8bcdc9f385d010da3870202fee6290575fc973adc45cb6e129654df924f89e694ef50cca5c3132cf214e914f1e33b3440922d9fce4dbf4ef74ca88a2f33d1dd3152ea35fac49f918a20ec0ef4240584b7c3e3e04c20993157aa16dc450a47f33635ffe10db91f1f38ffbd0f8eff727c6efce7f9d8f0f15f6a93f01fd01f798f12508ff2d13dd41f4dbe79541ec0771967d764283e5fc3fee4018c1166cb975c0daacb10cbb6e1aedb6578c5e608cc4a972115b562dc75ff51ee141809fcd5db6558a52607e049c841de4716f2353200fe270b3d4e4efd08f9c6f7641c2f808cfa2e432936978f709fa4ee1e6f6fe4111e47ee795416001553173813a2b12e244319244779030000a1548690077a3d2cea863702576f97e10b6480058d11f8abf8d264eebb0ca3e09ee66d6cb8e7381bee761926e54cc1dfecb20178e872016cba7499009c179adbe51fdb5e81bfd9e5126ac5b8ebbedf9e974a755efc6420df651fbb264339c0d3fc7c8d03bc09f9e7ed4f36e10f90a3bc0700d2d5f0b0cb24d828e3f4f183628c31c6184f32292949fb1a244934c4f8f8a2143d2f38d9bb401e2d5227cb95ec4e64a70279b4d871e6e3cc7f914b0277f9a72607d4edb20f764597078071d1e59eec5f5023f3fc4fde8f93771e95773c8eacf35d16c0b6c947509fa46e1e6f3d5e2e43086b32843b43b8c3c3eab143271f8190c7480c465be7adbc34b9a6e66dd4bc083535b3cb01202133f1307100f149f857c2dfec3200382df876394526ea24e16feadbe51e9c14396e974718d3651e202575e37cd0c3e8e1b88142039b508f7a9ae7dea9a03ebebd910971a8f7501c0ad3649bdce51d9312feaa08b7cb3c1e6254f6c9f931972eefb071464dd6f135b2f73f39e8bbacb3ad4d969b8b3128bba703d3641b32b5cbdeded2c58bd29718268e893a34206335c1dfdc71bbac236b419745e87208d6043cb7cb395b086a72085f23e7fc4f2ec0e3e41c7f23e74765111e47d60f9409f05dceb1b90cc5000678287f88483fc238e9e8e82893f025641f06c84c7ebe8607f2a3a5c02772f44bd857a91c513942bdcd8b40f3dc5b2f13b2d129146d08211a9506f60821e7e402641873e89c6d641f99840ca30e01725cea94701712f28ff7914b781bf9df06d5657d9552b7cbb9ea80bf0a80dbe5d7b2a58b8f4c4206ca2ec665b07bc40f923f36cca872254bd4898f83237b97e879c9aed4651c1b33a2cb37503de8b95db6b19fcb507c7c7e7072cf002e0e6a7b54ced91598f3b95d46d964a75293bd4af42a19e06f76f96673198acfd7644fca5e253b8d8fa692e2fec6e6f43701f93354e213aff245208e4c2327b3e35f2f351379141fcb1f66d4a926f2285be2e003d296a7a29ea8821166694ce1a5ac17766528d80f134e889ec49e524a31aa6d76c01fc678d04257b042ca0b51ca082c44ba1c5ea864a4e3f0822c2ebc07c2d09903936de32f5982e1143938b1bfbba39414fa70337607b168f8ef5f1915c4fef5715f9f659402f0df4761ba05540bdbbfb5305b80c6850cae040474420a2c044d89492bafcca4262fa015b000da3ffe05f9037f85e853450cfa1416b629d1c7faf8275c81572ad00f79cd8b5ed575bd705d4a1ba752de77152e2d0002712a4878bbf4bd9db0c5f8180f49f71ee194c14058941fa720825dd522123dba2fe56cc72943e528c658f557d5917429ef9d4424104fa6e094c147181416bacf0b6b8555b6a29765458b69d9a11069d179a5624aa6a495722c4e2eb6edb2b0edb3b7d9b6592793c5f055ad4bd334ad5a18ce9f3fc9b4ecca6a666536bb19ceb62ccbb2ccd2d6c3fa4b6dfe24ea443762c7d732a49457bbced2d5aa1615d2487fbe67fec6dbf64b6dfb0169f64d883f2e7cd5277bfb37b20d1c8ee66d7297617d5b6766ac9a1a341cde5c309994f0df87d643ed743a65990e7aabeb8e968e8e96e2d1921b19c9997884d5ac565c6b05dab5d628d68575524a5f51ae2cde188f8ee2516675448a4947b5bad157603086611886bab1e930a3a5a4384d3e20a57af294d5ac2e549fbfddc88d6a8862845305555512f6769c28874a49a717c9d94352d7eb2e830d4af287685403e6a287f990b2f43fbc292a7fc6915ffefcd31e3b7a07218ebf6f3f3b8e6cca1e7573e2a287bab9c16167ef4a5fc6ec48b323102612c986a92a33cd859d6938af945856e997bc6a8555b6cd43f2e7e7568c16b6ac9a1f7af531a62f3d87c86da94091c4c0202c9fbe3771a320729bc0ba419c49b5a6c41f0752ab13d1a3bec5b7446f8a11d047dc4044ef6807ccd5ffb0172f4b4a5e941cc90b4e5735aa732324952b55934ac2a95143662643af8c3f85d4a0017620f3514ea267e56ac99c713a207a2c78730758e6d0e03ed4d0e2f5442fce1d60a951190b90e7caf6db4490092bd4f0823fa8e4829c3ef4ff5fce6a42f4cac0df4bcf8464cc9965e01863ec2612d6d34bb8fe8b615bab3fab8a12bc8f5c9f046fe83dbef70b277afed8855595aed14b5082f711ff24d84bf0aff9bd878c1173c1f2adafe40c5275c35bd66412d630849694f2f3f0756537d72a3a05014e10f0e747f15f70ab7a3742637f750355f94a4a5dba402db54a596b4de29bbe53aa5128e8214b13d144d1f34ff544cfdf03d890f9c5b0e5c3f0dd0f03177861cbff70f67cf9f2cadff5f5ad4f6eebb3b6f51f8e6519b176d543d6e70f495165af28f0bd37a386dcecefc38dfd25effd7bfd7529662de6ee1935c4fffa4b0f5dae3fd410b92f6d7da67fd56b7d51b9ed3f5fc2e8535541719b3b16e176d17477f23bc659ab1860df9dd481690499b3aa26c8137fa681e15bb9304cd74b88dcf5bfd63a2bc9559dc6950a70bc4c948e3ccf842b4c9ec0401bfb947b54b06f1b23d0864040de8ed6dfff07bb482995e613f80baf9eb63f99b2c72eec8d0a78e5aa441e992d5fd62c5a556cd2c2209b69ff75d8572630aa7ed1ae4bb0ab2a1c24ed6f2b2f1d117affabfaea11df56ad2c9d84eeaad2a82173573f77a551d517ed4a9aa8c636f08728eac80f6242068e91df754bd004a5c90ae2334e8d023ff6d2eaa21731fb1447c88d5e9cd782f46de5740aa13f67177db069adfe7ca3866c77ecad22387b08c782546afa714e3b5fc87ca1ca55747ab155f96bfcd7c33b495984bac936b9abc10eb05d7f5516d837edb1e7d7d05f0f0e68467fdda95613ceaec5a84bd9337f3ec6b5fde7799ef6f171601830b6d01e422bb6a6e1145ad52c4dd31e4aed09007f807e401ffe9aa601c91f1f03ea646000fde748408e84bbee3d0f2905b4c3401e1b4db0fc48136d0c7270c54a1949fb53fa7120622cd5d002fff5d6d78f5fef95b1871b7ec4aca1f9f3ba3c436168be1cf26c5af353520790528efef75fb73b1b9a69b4faad0aecbbb24c58cbaec5ae8a54c99f52dbb6ec2d8b94dbb66d407b4b6d58fef89498923f15f0b2457eb0e2aed3df7b29280668cb16ec5db4681460ff20e9ef4a385e680cf9c79f6f59b7aa1c8582fc0f921615e3c4b6d9d08a182d56651109817b88481691f38a9842d42e54a99e05384c7221b53dd79e9f702935addf58ef4f5d000442c48958fda0777d1b46ee6e79162fc1dd5d471b46d4a9ae286d1245208417e12cf01e368cac1feac3a969fa23c2b099da1ff6433a1b811afb548a2815c4faaaf5148498fe52d0339dec9fb2b7699e55d70ad999a960952953a68b3a7115020ba10f9a65fb4b27b6bf10b7f24892fa4aac4f808c53d704500d210fa5b9fa2b53c15f3e66c4dad588b52f25c3cb7677f9f43d53c13315dc359e720bf74be812eb2d4705630f3314b09dc1c03d0462ca309c9e72798994164e6ffad44e459f4ca36af4a2ebfa033b03957ddf042e34157d3ea20b81cc3c0ca6e79e9e7ee6e90ba9fe7a9aaf8710d2ba29b5374c600ceb88ecac8361060629dbc3c0bd897bd3b3c069d36f9a85199d653bef299bae7d135054a72e30f884a01c3a60cc5941c41bebadeabcbc04140ab24c19e9310cd6706e8771155777568fd07b58d1670583af84e0b437e2eed6ebbaae58aff40266072f607630da514341b1b0952923b7f5371b71abadb9bd4e6d0c2c25be5bbf7080ba14e7e1bf1dbeac2afb002a239611e99e87c059b763d1726256915f6613e93edfca34c3cc1a328dc8c801998618dba4628819620b104cb144c548ee6049054488913421bc4288a5a32962f4306f589268c0a008b7d86cbde3b0fde368e5555565966559395525bbeaa165d5b7189df5a77f16a4c26e5996cc558e87392bc3ac5b68699f910f87cb84dc5dad2bf3b72324a4da15f2f8756596f541715a9635bfaa2a2d334f4fc86642e49e9047e6af0add29f3778570853c75b31bb66ddb766ddbb6d56db3b66aa373935bdc7ceba12589515828a79c724a09a3124cacc0ee417624ac2884b0a3049a907ad47a208f9d99901f417c4ba9a1f65279d4244621843da7860a42bfb9e1a441023838314e4a679269c54208218410429f16662d66ede4ec09fd5a95fb5b1e71a2ac2aab56abaa2a286755559555ab5511e14c5ab9af555b4eab5955ab56abf20a7ea5616559b55e17d600cfbb989d93e60f0bd19cd13c2177ed866ddbd44429eba729ee13ce39a7432ef037b53cb21ea594525ed100cf23d275d67e73cf39bdf2ceb2e6acaa5a41f841b128494cbab88b5d183666f81ca574469c1c58777777d71046f85083b156021b67430821c4d9fe9002a6d40fd8e3eda43e1d5c08db2bae77c7d66cb14ce796dbdbe9e142a8a263621633995278f372204ffd4c77367794dbca9b6977c7dfdeacbdf6f7b34c639a96f9be7342c8536145afc82b433420a89431b32102a4e2a8f44e164435656648002000000043140000200c08868442c168381c1266557714800b7e8a4280661fc99328885118a48c31c6184200000000006064a6888300b99eb317ce9b79d38f19cad04833b1c8ff42131b0f32e2999964f16c2d046d4001a38a3cc705b6129d10be10723156099b1938d7682629f39bb180e7a4e2d2c460f8679ab9eb21c7e75aa9be2a3f29444e4abc12c4357b7388ca019d6f0536016a4a6b6a2df0c952d5a04727c83bca2fd4a46791d9583fc0bef459748f5aad60cbd08f630ecd1bf3e78deb71fdb9b88a488d7e56a2c31190e808912d857b89b2e5e7d88d13b607671d0055a047d0112ab65471546478f726fce304b44115771db376c2d161300de24a17c947635f8a08372eea69f7d63874235ba36f11928c8102e2d83a0b319f9605352a11ff330506663565ea2b0683e34b8480c03cc22410f2d752dffdc6a6e75f2e6f0acaa709710f641e7ac302d9b58d64e7809009f169525f499a0da11d9b2ebb1ed9804287635a3f1c7fd481be2d9f546e5f1eeed2beb031d6df1e17c534c4397884520f796cefa41de72489ac8634ded1b5de383636fb96fdb0e943039b398741ec865e9bab63873014c78a6c603fdca883ac74969aa6a9d9aa8ecc840eb18fc64f4cf1ca82469952bc2518cb80b12c5570c69b39e85843c18d4e585596eed365c90fe498579799d9993620c0be9121b5367b38a0d0c3cb5acd341f42a2ac1ce50681c68b60529cea113c53de7244aa31abf5f90f9768e2fee773a0046135783a1854059838adfb0205d481ef69461b784496b4b469c571a4a847451729075da4a38519b77ef07792f65f062481d79fcd0e6703ef3f37c09551b96d0539db6e832cb1a875d2e681078783515811b70ad77d8d1c89cf7c22937c662279d179915282dfd73d2a3b7cf2202943bbb2cb2d4b13f2c5c28ea5780ec033a514246f39863c5c889e1318a98d2dafadfde32a76da48dc84f0bcc13cb4493feb4847a22f2efba30b6cc1a396d9d191460f314011d98a13f76eeff5602776c6f393fe2e448e64b5a75daf31342dc435ad5cf831aedcc2d644f924464787187b896bf375023fc83fde3f1ac645db2f41ffbf09793fb3cfa13b42e9ab0613846b824c7fe87e11b8dc976e4e808d1f310cb5cc9e4bd099dc2dd6c06940dc3c1135fe64fd42cc3d414af1711f4c34796904ed35fbdcfa61bfc93bfea10bfce4db3894c6441230c26656b60c249f3234bae73084f2544aa2db933c51a9e337513258dfa1885f5b5bed36ffbbc3a1a7e7016dcbddc085219ad490efb794af754e34e9ad505975b89d5fac7fdd887ab1b4f8c858c755db5901678ca35840a0b4119e2298d90d5393773172fbb46fd5a961e0d688d0cec930db2eca6a999b6e071aff0beb04c9ee8c0c95f3c12e13583607e022c73030038396d0e61a889e38bd82727b4a2abf82ac1534fa47d426f855a50ec8e4e2f12a1be4f82ce814035c0d9b00139bb218e4da797e182b160f42a3f7fb8c20078fba474de297aace375ceb374e32a84ac996179336d731afd90a967f98c835f1e143de1950dd86014171af2ede66eca02362cc1ea2846a090c39b9403360a76b24283074e9a305f78815c42882e18fe63cd3603647af0bcc5c1e49ce8af8cf5e9cc29fb524a9fb6e77d5ad42601d58caeee6a8a032ce33af7fc2af9e3ff3aad83fbeec2a253ae6dd00c5a444f39b125891f9f81be0596e0ca29a5e9e95435912a4390fc43289bf5c651cc768ebcac84453f2e569ce56dca979aa0c914f794878f3a62793b09037c8f0658015a81bdbd83e4578e37efbf969f1f71b770750dbb8f8b92c23d1226c15ce474955c8a92c7717c31513180820ecd1ec0d0ab7877d414dea391fa0aa6571ddb028d2725bdbc5958877aa48b31983b30f7d51d3057e84db622004103688962f0acebc063ea55b806c152345e113a8fd096f542a52b1960f9c9f573a08aa1d73c2656778a9c46df9ef4924c5241a6f9575a5929fa0c09dce942cc689dd8712d33a3dcecd4a6294e70a0b718872d5d055c893ace9e74008cc0ed83dfb78020b32bec9ef40f9312bc424b0cd0772891755d5a5f789cfa31d18a39210624270c0857d85b2d561c35eced9e04186a19499dca5c2f7cee1b4ce2c1aa34269bb43f8872143c18010635beec9acb090f30051de30754d3f0e70bda9b70e090dbf10d02a1c7f87089890d07b41056c2e479760010c25e9d9e62725b9cbd0dce946b56b63a577e44c0c64f7206fdc814cea47594d266ab37e84946bf68585542299070162c63e4509677e0c98a657df302f302e024c32be17f36483ff47a3c53f933550398108b945cc74b75abaa3cbf72c5a99a5a29c1d57a49246667e07d462b05820099f7d848fa7df70dfb4ef5a8f4a5a3dfdeeb72041b424d620f94a2146e79f187527959348c3c09d41d7521253eb3ccdf8b1a7fd8197393eb51e5d3ac04c6f35a66a584b3a33edb06bb365e1d271761049e28a04105ba24588ee70c2501b55507d45dd9361ed1cd26738746092115dbc9e52783e22590543ef18ab2b15ba1038bb0da8329f99b7f505a6ca8c99f0774f1ed00ad4a120a05295abd4408bab8c22a04956d845c58de8fcb3e42d46657d10a96026dee86c581157c78ce6fe1af30ccc2c4209d449ca8ed025b0b2a8828a945d3b900da61609c544e00af4a0bc2fd827862831347c7983c0b6acb92d0a1a6badf0ed93b7fb35a9a54c916bac9767a5bb71088229472fef590d51dcf300154363d3e6f33a833838d8c0c5e05866571441ba315915654d5a51374998c18d2ded47066e692b6ec8aa9c6226f6b9effe8709b402ef81f9251552bc7f7d59498d17df6c8642fa5422ef28569e8909afc4484b1ee3c83149ffc643cf156f24e1662a998da5dbd83b63a15ff86e8dae5e0785f67c39c0daf205cbcbdab1ee3ad37ae23eb915083bcb36e4ab4add7f87fbd8b71946f600a6d4c15037a06bbf43c00b6813888eaf5af34d9372e6578394ba6a64ea6bef3f9acabd656a7ca6f238775e2895965412c414f7b75fc8790ab7a9f3ed5a2c8945523b866653396dcec6bced98392eedef3e3cd05bdec2b655577d1c431e6061be6cf25fa43591aed0a4db74db8fce72deeb19d48a7d12eaf49db8f5f983d23369a8fa23a3231fd638dc21f2d2fe3ec882b6a660085d3bf9810e5573212b061a03d0dd510882336cf1c57474cb50d0b29e912cf67edf09817affeba1cf1349cd885e5bebea78c52b8fabdb94173621213cd04802a45466a12d5c880f21365115fa914a46d68ecbe500aa537b818d82ba52567b1693ff9af1e5e152645a3131b42bbe5bb9eddba2dbcb67c62df50caa35210c1946c439ba983a79b2a947d5481b646ac4e25ffbdf552f94a9a67d78d9012e7c2489e89d861a81362ce9832a7315f1c17cc2154816d4c606d58538f178d0f528d3f70c47c9654b586895304d42c6d77a800d2e0734b0b9d184b89328d207056a4aa52828b35f72b626fe6e626331b166665148dcea3e85f6721e9206d083212db6773841ca4fb34a442cae70e4927d3053187a3761842005ece952c91b224f74399dc3895b2e7cc32079b63a73d256b987a4001df2606a269115481a06b8286947efe218a7940cf896e017c2c8eca100f805dbe6afb6962205fbc03ed798c61f8a42f0d423bf2145f8c70f32b3bae9e46735e195a4327cfd05712b6221138d9cf86a76bae042b4a5960e389bede0d80d5341f6c129fc9318b13308a012c77fc6ad0f0d95853e0a77b262c970e43631ba11465658b176e0e6450d70a08754bbd8b4c17396aa1f0d25ec1fa5d591a37d72fd9eca151a8d4b8801c78ac45912942d8c9e5a4b46f030f055fde15f343d7d1e6fe103e639254b342ecba69e5048ab0d605b88f7d32df24ef3869de4cb0268a0e4fc9b544e651846934d8703235f9d604e4cdfd15e6b4552e15f44265cf4229449ca9430523fd125da087ebbacfce07959863acb9a9f946c4f5b91106167cb1f4aed74661a4d46c48d5da407058b6b02e64568fe1cc45433f51a7a74615a83ca4ff58e4f070796cb4ce4c553b88a59205c1517d1c72e607e47e09a4d009ad335a5bb846d843e41ea5c4f0cb9f1c36de3dad3c67b9fc335a37e9a711222997e0d20c6e13b19f04a08718b8bb8c77da05e25088d777661c588beebaa10ebbf6e49807b008215a6aee5031f8613a160f99f6310140c9b29794b6b8ce91b8d4cff4ade7a970193267470b55853777f6fdd953d74ecff7d8a001f26bca7761041c83f35ce36697f865811ac6b5aca776b035307a75432d9a3b72a17fafe854d44aba7fd5243203faea55ad071e492b27403a554d725bcf3aaa2905dcb9d1bef3cf64685d80fce310de97b92be5bc2c3d55a51d4973fd60e5e9c3600f6cae970c2f58830511fd5ceb40ba50a0e270286e6067a65d21e7638e193afe5b3a99b59f42a9546245ae2dc99773b291b7d85fc537bdc3ccb8c9ce9825ad4f8a18c2eccf386f476d9702e623ba28083251ef712fa083ed3598b4c90182d71612d14465182f8b0fd49048a27a5e091747e47682cd8e6197fe0366e25378ff345d531e34ea3b761aa39b545cab93d7d040c2a1e254e1d9e3a68906140dc237eca1aeba020c6ab7ead9295ba8509164571b5ffb10c1fb6e3aa333140f870f647125b07c5d17a18e801c8aa3d6044ebe3718f2c41daed1568968ca540a3d78733678246244a2b841949669e0d2751322adda0e6a0ab14785bbccd4151e89dc61111695fbd254b1d5a33b146c9742f811e2e30ae5e63b370ac12a2f51aea517b17c23adfd0b20c0bf6911b6dfc23b3709d2f3a59c59f1b6dbf40920d05432013844032d413c07543425033e30ee4551583b62f611913e2e1de56c4cf1f6ed9f5aef7bfe7bbdfff3d3b25de134d3e1bf7e79df8433831ca605975b997e295c38692004043d0c0fda08e9269abd52aad7ae03256eb66053fee125cfabb04131bc56aa00ebe350d23629807ab4e9e93e5afcb42fcb72f5c0c545e2935ef17d650ddca3b1719cc9118fcfd62dfac7664cdfb0dfcf43403b69de86320102014c4fb0391134d6463c8045df4c8f118411a308a2fb4fcb97190a35d6a83c199195ee22dfa342ba317817db3e4c6595828065c4c700e9e3d37fcfc888d7ad85a30108e776347a70386a9281160f82c1faaecabd40d6c02a81b2345c122dc7644cadbc3cf8a23114216312da2e0a3a3274f0d942a73e553cef868a0dab8a4cb1795d54ad5aeea151b603f65af270c2ce2dfba6725056b667f2e2745661415e6a5a26b69037f9527bf9e5da708e688c5be8a6cece7e94ae555095ea2dad49c0f30628ff1c6edc840b1c5a4b3f857b8a573e35de19717efc83796a945825b099d376eac2cb9dcf76a4c687396ad4fe18d260db60a976ab88ef6080b33941433723fb3d94971571a61804115efae603ae575b619e005e72f04722f589b30c64777dcf4d79763fe9c06500464a26aedefd2075f0c007e64fd8b3200c634b369cd22a3dc916e7f2ee4791474a33106c0c8fca1b4d70fa81e42ff989e520362f7b0ea08bc9a0ac1f420e0ed93f06b2a99efaee2893e45452fdb27d1c8df09ae6daf40936a894bd4880d933b9648ddc14084b5a4b3c3845c1b9381b831329ef1445e1b13bf46df20888263197b88666f2c0443e08aa31ef3289e3764efa6ffd91ccfb9abb5e4ceac0a71ff0637368a71e6d315c7260f435dead76b200ee3db34d02c05a8c01a231d4833b4800748787a364938383374a19c602ebffe0e4c8d497e682708440d0c114aa444f93c83e03b2a3ddb3d387319482fc62a35bb93242726aea90882c53aebf01fe8191d93280eb6ca556aa07b20e088b78a880c24bafede68da0b3f9da7e5e91b32f19b32d839d2036f1373512b90e7d20384f1300c8d23835382d70ab7a1b49b048f96c62fce14e5caba34df71d8591580454c4931acc9e1019b6f512848ba03fac040fa15eb0fb372369828e71b5e5763dc6921b32904060ae63f7c2032d783465e2c1ce4d013c10d5c4ca996033c374527560dc0981bf81a2688abda9421d62e30c371a99871544cd46fbb822c2c444b5f10a8096e92f74d12bc56c28eec9fa581c4e97062e0de451bd807edfd68f5b234f880a90b46841b08fc2cc4924204e8eeb1015df476961d99cc607b1cb1271f2c55f1c3d22208306cf99f3bbbdf8c29cf04e3c57cb047346c9273645e07e726be99d0f7206bca2a0ba926f227573628e295b2dbb725d939932429412b5d9868c73579eb1f24e3da71b8a47baa6cff968723b0c330ec95019cf7c1268c658d2975af238653650ca84f80dc5c86969ce1fce802c5f84e63158ce087ac4053b31d11374fccdbcfc6b0a4693293693908683faf26ee7f000ba79551fdcb208adb2be718f16ea802b1845462cadc7be00c8905a4cbdf8b07f2a8b900e0b9807660e760b25fbaf890b772cca97150b3b518616ccb0d02361d86341226fc53a51787348aa1d75771a7de437cca05c38f75608b73c71f1112d81e90b457a3b61544d7a928310091871ba15ce31815084dd0e590bd1d192b1a29bc187623768576eac95178635e9ebe757c92518a368fe143d9bb6920278dc3360568a352359977ccd59112072c3a91857464c4952a885f30ad5cae12a4100173cb1600b552a63dc0883f5422e26cd4ac6bd044d1e63b1ca9c6d1471c60bc40040771e74fb11a4b23f2d4988069c9bd276e7773b437f4f69fdeefae14a5a9924e28a063d97b42fd843e81ae8c34557a988ec6a210719cf8ffdb5a9f9e918d6694e64fccdb915fcf98b62af8b40b79c6cbc2bdaaa6e4fb1c8a957c0efc09c61ba19093b07a1516a96e858fba65a05e7cf16a70f117cb6487704a865c6bf6edd606cb5eb6344be7f72a2ccfe9fae24ec93b1b3e5810146af75e0653b343b2a8897fd398041c9eb9f2f537a96e27446ce9efe98e1bd72439299cfc46d5da6bd520aecb4bbae01212b0c7672a600ea6295f4db58a5b9c363d3ff7349a0317f5986a70e9bc52f0edfadc2a0f61c0596d3c50413f50f56c4e4c51849ef403496a1f22c474d0812a73b586921840095c093351bfd29d4bdaaa840c94052670729b7b40064a28609ce26f3362c4b603d6360be79ec1b67847b69510db70378f9230d845b106f5550753ec23b87c8591766494d8fb0552a5e4a437bc62cea06ad937d72aed82a790a9d03a589ced49829fa30dab9a35e96e6ad6fa1e9f8f9f021fc7ccb921ea6ee43700092038f24a1e337dba370d90b869ff2ae9d307f92d1fb7be35ba88efb60f0bc7b32986fae188ae5c990c747092ac447850645950ff40acf7b93f4210d0090f33b9e946821a1d5c3fa50f20d2e443d8933ba42b6be7d9b8a6d3858467c05e47570f207ffe1546bb581f7fb3e790d838adca144639bbed26a8e629ba878ced081b9aca610b25a2f001626ebc0eef8823620ef356405cb6ef2dda04dcbb9968dfde6a8d82d50ae1b6d153c28bd3d4580ee532e802c3ff26f42013bad61da5c54ab3ba5a6d42ca5e482813c52473c6913d35f3e376ec87015947394d749c4b9c952ec50a00d13668b62656a7cf703b2a5ed0e628dc196e80e35b317e8d1b07edf7cc1f17f6403319c6115b3a28e01c6757dd4509495c6d60b61d83fe3e921ba351caf987b34bbdc5eddadc457f8dc06de9f2a19f8971bb7517f2459166947dcc8d516e5897956f5bb0b02d13686d46bc44459cd770a20d7e7c120ef074212e6a1b6d41d62af94aeb428f4f4659960c88ecabe4ce91cc9744b8caa596e6808e89c9264577dedc235dfc2e66b8ded6c14aac8e166e0a37ecfd4b9b83b12a52256a4322b04359070c7b87ec62bbf0153ae810ff32777f3e6fc552af48fe6da06b8ab2ec8370322dcd5bfced48497122a8462e0d97c7d401267cc8fa43df07e3c35bd7d7db9afe734bae24029ee0f27a69cc7dd5b41dd0904c73dd33898f49ce93ce0985b73cc1db77e66f112ac2db6c3cca9b49dbeb188fbf2c66ce01665839d984c87793e28c1199f2ce136b82f3af6e4de777e1e3600ee06ef1b56497220ecd0edf2ee279fc5bf24ec9dc835c4d55adbac878ad7485e442dc44776bb797ad79ff97aa0b763a72cd57f766af9ff69ebff227ef453661417b9d33c53f39f7265c8db667a99ce3c5bfb46e3df1fc0bf70b88cf2fdb59950ecd9eb1b7d0043be8a7d956d175e6116aed345f9ed2ca638f9d27bf2558aa7d8a7b840ad65f87d4d5c0481553dc9f81ba16ed72fbd7d053a7b5897a6f46d1f94e27a675be71ebd5635f1962c2a6027a97c44fc68488a1c14dc378bd1cc3d1b43757f4ae40b2c9bb5853f0a0efacb75b13c8aa0400672655c46f05012db3faeebfd2e088db1b738af8d0bf1fffeb628fc38a694b0fcb91cc544b7c32a4f60e024dc5540bbfb81024ef48f48392ad571e17da4eb69201c47cf5c37b87df77f9865eabdcb2d44c70049b4faf25d220b5eb80ec617041750e855f4e6687628bcfbb9e2d741d68b61ec99b0101206dbaac3821073092c0e1e00ddc3457048335214471d16b07da708ccd965efd74ae15c5135d172c4d36f9b1be0c47b9698abfe904614cf3a451c92d1ae2b0d5a405c8a86f2ffc54b82689b2b0bc7f06df474055da040295a87235304419a86fc42f93c77d8fa26f1913df8d8885a5ccda66f2a322958d80fea2a472f6a333c8ab66761b5e7d776527224c0185361e0ff97d92a8c89641190a0d6e2bf100e1d733154fefc33f8e0ceb84d06c8d5388c831a512508c03593a29fc360c5332222cb47d3f76395a54c96585b46014d66816d6302c31f03888f08e6fef60e99c347083d686c53e66014b2d7f3aa6a40faa7066cb0941e9fb0fc1e45105d6661cff1df877487dd0405b62e07650c704de170a2c4350780848148270b78d4ee5dbaccfb8a3239655fc7b1f97c5e4d79314af7156063ed6abd8eb567db3d7bc64e0efecb94ee6bd6a8da21c8824d6a8199b6c3d3043b27e8322ccf665126cf77e56be0f99b3e618cc69211fe20144a515b5f5aa9f992b7ffb08b36fd95e7aa3948024d7d424042529062b4fa592370b355b3a743591f9916b06aab3c03760093de1c6d4aa12e17cbded4f4ad9969d1eac0f06d09fb04e4c3868323b3d3b247a144d795a936f5a9fd5ef8e0cdce71d4b68891fe4ae046f192f3a241aaaf4ac6a8c16624679701a439ccd1c58a24fb31045d3f84914bc5aed122851dc2ec9874e6d3c0f0ce4a1a4bc05a9b532161dffb13fe440406735406507259db663d2d1fbed42a564a445eacf62a7cbebe791c0723e718177c0eba5d032e1e7a681ef1293f6f9f9f9fb9e74bbbc24eb88a70576d1a05913d5606564a3214d94198050946bb09dfb8657a872c2274d67b2b9446c74a6e9932d4f91a1ae45108d83d450ee37037e0413f360a6660bfb34fbc11f74b4cb2c08007cb5f64975889cdd83df3af1c046ca98eeee5a6d61757272902b513c50c2660b4522956a22c7ca859b1f2f19563ebcca604d34ca5a279fe6fb14338d2cebaa445b95c36a16132f750a5b38c7119fa504d877dc61f228076e56deb965cd3c1e0a119211cd4046c72f6467375d0697a549ca60617b29d60f5f5492e6d24b1dfc7659ac44e24f69f49758507994ee1cd5c3203c2ef441026c3e04a4e54c4bd04c8b18c8f294d41142f20a1372e27e07c48897ec7b87c69ad28608c46eab274141075611c9d38140c4c60f4567a4a086028eb46c3780ae886344e649a076edea083c1ea3f66a7a9bdb8e21aaeba4947bd3cbc2dde0764b28a744d622873cda3f88b65a454288633935a1addb590eda51d04548d0cfe976f2a8bab00c66f16775034b13ed9e9aa509a3ab4524c5fb58b305782cd345a4eaebd31076fb6fdace4916229def1c7cb8029da8eb939d63f4fad2000152b351edf71249f0b2d17eaa263f137a89ad82c5a94a9a388441bbf20f4f2a40694d54b500853cfb16d1982e2c94ba1e7b2e1c0365cf71c696b4d6c848b5e3240fb7a071f52a9ed934c56d2479fe0abd077eacd60bd4dbf16d451b6842411c9fdd3374184ea5215e4ad16c20da9cff2a005f3036617eeb33b50718bbccb46648ba5e2f6a16914d6f9ad2867f612b81d50cb9e5d63acc04891900a0e311ca91a9ea5d606838622777f96688a41a97c7fd095d36030ea05c5840a13ac48c9a6d45dcea7a1af0349f8981ac4b6e17c616e70d19f8a5e73454c56f498c8f2a29552c826c0066228a5008ad43bcf2395da2980ab23dcd8b49732ffbc7773004e1466c8b189040c3e7f24bcdf8ab8452df50699f1457bd4988ce941aa1e09d2cc8d30c99d8cf6ac6554f2a53f00842131021c14ba6ad314ddd5c912f12d7173401e16f04c0580875b3e2f45851d03f7296ca22ac7a4aefe6e919b5ae5c49f7c0d394b66b17f73ef2ca732b269e4c17324fad68ce4e6d4cf5339f92c9ec33a61afc76a573073b9c661845a32b661e746474c17fa574f194da3a76733f944acb529b7d0255ca7ae62d01b9b7405cf08e6d87f48277b60830e4fc70f893a247a080211ebe3f12e8e3465f4970f8a25adfd4531368b847d81a59200693ab4bf6bba2157ea81f2fa4a4aa8b672282035ff49e7511867a59b150da0cdd51e9ff4f52d4efbfd24b85086ef4994d6f0a495c1493bf36e8bc3a1a818f0cfdee61b623c248884acbfdf6c4500c074121a98e1682b0f7a1b366a72e5ba45101bbe0175e32e8b134bc5ed847435a7d06ce14023d7dcd0bbf2cb716b88404a286476b8419e888ee6beb0df853519b89ca7ff6c0366936caf064b1c5f1b2f8c09457d92ae1a8146142001bc60dfc05023f9127aba0f7a2b8dfe5e64ee53f29c31e3893a12ada442423e190911514cd040f2493563837a0aba541012651739564d2cc57b86a75661aac4a202eda38991f8e126cf74099a4222ed67d22b3e8a06baaac3b294d91379deb6fb5ec77c179b72631cd62460902ac8f34eaff4c40c199c0f6e75e68638724bded50f8aecd1183f4ff6d92455a4009857d2b41778faea65b53a570489eecf07d94b68013c3b296a59ccc39d12e420e02668a4873157e32de3645bfcd77663916e80bb8c92e8751f4a03a071f14efa11c01e42332838c8bd8d3c3aa7f10fa705f2ed537867f7101c6f82257a0b7552da28b4473ce54408706adc18fbbf7049ca5b4b511f4d1bd43438c1d6abb963db66cc378caad73431b1e2de595eda73bcaf5e2ff872fa6313c2bf673621b85ea366e315ca38520d318445b05b7c4585f40dd4beda6ff7847d50b6cc0b575d391bf8f083c291d288568bbccc0da89a001d5038900a4ca35f3978b4dfd4e064cc0945b2532c3e799ce52e36b46ee4cbd6f36b1a1a6da315d56a3b8948e87b6340333117ba62e80db55389517e5eff846210f3b77159c687b3f68bceb817181695fd23fe7470d719566c0c62e5cd69141c97c4f538d56420bda604900f48525179c7d4cd514206515d5722697c14c80674af95f37f6acffa2317ac376533e787db1009c49bb48f6acee3ac2370b2780630aa56862cec984b12d12abff05637521266f7da6ff97781e1c869f7298e320c31c30b296c064aeb5356b6d7d5ffec855bf7eea41e4bf1936b5de845e2975dd16a0dbfd44d16e8354ec76341f35ed6d7f795e4739b9eb7c8a8864d390286deedb7ebf8f97fa790713599f9890a2034044a77dbed0aa84b375589e644650632983e7a4e5d035c56ff29f2ce79181ef32bb67c888c23d85c9c360328cf5126c309aaa97f041f04bdd4c3be3efce04eba510afd55f7e9c2293300cbcf32d1d082dfbb7953b114517b3d2d16fb6f3a4b31295a96fef3606c1468a376fe36ec12cd93771112feb1a81588e040faced0ce8f08f3b3931211b40556d5e4b279831a0c0db6d3f612cb82e252c0487c6a115aa42983a06b41c2e8d05c44b5943163e80b2cfe5e70fbba3aa8633cdf268bc435cc422be60673819c46d75897712e1bf83ae2fe7c282eabc82e3a3588ff984eb3ee7afd448e789758a6f6776c4a27eb335c529d164741b4cd6a2a4c669fb64b7be6a7b83684be167ce376914fbeccbcc322577810241b0aae424b64580d86e1632be5d05d3a6b7ddecf61649b06b3ca4e44b0126fd6d1b87882acc47b88a7f95748efa7480c80c0a2f11a03d19d5e34b9d4aa29e482164df30021f1e272f7972b44aadaf7b9297af02f96a4eaf303a743829c675c4b71d777f6f0f4632914e31edef7a3226fc32af38816dd7e971f5c4a32170517f4941d766176a1014161807efc3a8a23e5e42a1d6eb8b1714835eed4de404eeca1349e3f077e09da66f80edfa9d8b82b15acc897ca200ec1d68948387bfbdc01e44b4e13a768dbb7e3102b7aaf6369f5f280a2d45e14bc35638d994c44c7c71b0006ab9097a1db7ce419150debfe9798ba93364c2dcfb0f909f71855f232360e6be38bc597790cda887e00278f5792fb53cc61773040782e728d0be49021ff68ba4b69f574b9d7d4c6184564d08850dc2788a90d14755538412278b9fc8b340a11ca64ab3edb14d95701c206ae1ee36d5d621e744d4394a99eb0002bc498d67e81faed6dc91c69f8f5089a68c8b01551edd80acf0437a7e2b6936387179b38e1fcc1d4ea94515abe0d3fded7b171ba5a72fea1fe1a1201947383f525cd41639ef787c5bcd22bff0982949d6ea7c152a990b94ba60c4fa0ea054102d1ebcae1d0da2af52b94424e229e2a69ccef43a11ce71ce7f8b19e05eeb1e03438afff407d122c93d430d8d15a5dc3c8d269fe104ffa09d0c7d6a1640ffd7aadcba9e5897d966f91efd580ee2a3757ea53955a5509b3d65bae22210e0c0bf9520f11295a53a4f573197218f5cb167b5cbc71b8ea2a3c3e3103dea21c81f6c0237150b7b75db481321fa39c116886ef957f7b328ae5f74bbddca7b6f5344fd09b1db1c69f820d503f49e6a7cb8810ea6ad87ff290511670a4738b6a0203b9eecdb116d4d80646f747c8d97ba311c0764581fdf98ac28c76210d4593d81b3d23be4a37ddbddee67e5470bacff2761551f00ef5d47aca184cbcecf6296137e0e37df8b38bb9187a6345336572a1cc34096771ae6a8698a68e5d18ae3d965cb178f347f606939b260e2acede0644bbee10189ef89abe5b0d7fca0d4020ee4dc8338d09b4d6e17ed2aa611683e6f5420f1da711cc5f5c9c86423e28253eda9832dd8db6233bd6e4e7c468f6cd7c1c03f8d53889e816f44629373553a1cdab5fdc48851c0d0b5c778784fa7e5c85a3e7e72d91815f6ae51423bb985ba596b1dd28ea32d9a9d344300bf54e75bd76bcc1d3762b57bd9ea6b90afc445764abd8fa4f3adeac6a892ccc91b7ae8b56ef599b0df8bb4e384470be7ca10f866194b55b90d12451406af1d447b14c8762f36b13e5ed937500a9463462e16ee2be628df16d1ff7855a8274010fc8e577c65a7e8cd6f2b66a4a2c848b5b6ef99ca7967ad81ebbb46ac16fffb7b235b008b1f89e20e396955e050c60086021833c3fce2d4e36b95a4d8faa574d76c6fd147a1e04702d3da269938fbc92185de559ee53dd0db0fb96eac950f01b39454317cf454fbc75ebd1d7190f5bdb394bf997386fe177aabb6f60b4d9a5b99d464f8c8b10fadd7fd029483e1562dd22da7874694d392103f92dd7ddbd4d89e804e6133495d48b0a240c97b9978362fcdb18ceeb8f39c64d4bfe4c58e6f7c7b69d73ca406102966e160743cd8be15649d6408e4aef42162eac6fffc0de44425cf5debe7de5534a3154e50e3a6a789d965242217c8b464480a4ac41050e96404913441f9b13611192773423fafdbaf6885252aa3b019bf35f325d3a5ee30295fa524e52ceb100b5df4bdc087e6ee439b2b2186581e0c7049abbd701e89fe00bda5c7c2795d30026b24a464b5e654bdb507e9c9c482a6654e759c998c8206b71f0a9627f59b0344f532792b55cb50f4f796c2b345b33940be328a27e1bccd1298d3ea569848652542a314acd926cc951e4229d1ddc4b2f728c58248f6258b153986ab68417fb008fd6d3a58e3a8d47249d5b055cac118afd30adba1a9e04107d04b5534c5cb77565ae04958ad52a709af8af0cff5ee8b715ce97c08f821627b17537d6008684f31de2a6c09c05655ed87d315a36394669f10dfb07d09f1008fd87721af0aaec924d82c8d246d20ef3f158aea12687d5c5f4d6cc1e4ecfab57b42e610cf870a9433781468f94fe50171b35da3fa1cf93e5a3ab3e8faebddfb1d566becaa841e0980581981fa32c9f8e6db4dd169d66052cb9d4925373727953a7fb8f19b14d0bbe703c93588e3ff35054398b27302b087d03fae6d92bcd29596840ce942a8edab1fcf4be58f3e3db8b52d91b243a8620390ae971908242bf7fbd5360faeb19c70baca3e96f3a8497acc08cf6df4992888edc6c67244a065169c118df4dcc6be1d44068c39e26693fb64f8df61f56c6c079e551f4ba93fe60842be5f61e015a21e43884f6caee37f962d3540c8d0bba43112d012f6d567f98d9fccdf602d123a249270ce2f59eae36b2a1f6bfcfbca9d9f7f44b13f70612366125fd13472b1a63708cd7477e3eea693d7e4eab06f3b971e2e519b2220189c406680668f9e4be10558ff819b58bc65fdc7587de86440f1623c3bc5baee3efed42207283b67a2e2f0f4f705de34ffdcff4fc5903d9aa358c3af4ded11743fbe071db7bd98bb9ce40f9d18300be2af0bc2341437e4b240728bfc42d66fea9e9ca77024c2bb671dd142047885293192ff78f48fa582763f3428cab6d6fe9770e90833b1480c914f88d0d77ed73aa3cabd85e101a9d2e8f109ef134893b4c840c15c06298c97d590831b28be4cf20710f89d3bbb012ac1459616d50b2b4c221b7f363283ddaecc9690e66a419c52fd5e84c228681300844ba7925655bf98d010006feb986361864ac1cbedc63a4139ad3a031f8181a0981e6a182dc7ab6cff79496b8aa4aaecb2b209f2ade21197413dbe7c7fb9a5eff83807657af64416300935704677b44408a6415d523fd027dcdee8a19bf969c0cebb8a776348c7bb627022a84d873bda054c5a23c1ecf3224ab2719fbb9740e6fda9626cf229d4ee323809d66c30e1fc9a3bb683c0494ef7f95fb2b060258a7218056d589be600bd8fe28a151581751915c782674e6ffb623294023b30bfe1d5f9ed3ea15c0d94fe2527148a4b1fc5458522480363b82d0aa9334a239eaebffa5eee83c0175c09fadf69180ebbda91cfb6d3c67effd3a7db74d6f53fac42699e8ed4024b1e85e3ec528ce53c772d9e483b249c64757696a3a389ab7562028a671ec60ec8329305ec4a2456d9eed294f0146aece6a59b79713c3db73ea49bcf0722ff33eb034147ffc5a175387e5032a4451d9947ed8652fc01f44e7f967de58c7c94f525712e92286d57397a9b19f63419089c836b68ae9e0004c2eda55dd4c82c4859d3ece3e6ade01a78b614ac3fa4ce12fefb52a0f552221b57a08184f021f71456ce7cb3371d6d94cb69e04f2ade483c7091360807caeff8e204a482e4181d2011410c78ebbeff126703844f5ad32e4aeca8b4eb4e3f305b8106bda8bc873773b81e7941168834604a42571596ea245852fab2bb293819247f52353bd9ab2450510f710eb540cfee3b50952eac8f0ccd3ded4089d4e2603691e8658832932946beb7c4a02d4db161cb8752858a2ef236c78f807a5cebc38b05435b6b3c5616304469a9c9fd245612bdd066308232e09a94485708497472ef2498e0e875d6707a68e35ab7f4a22633cd4b8f69f1128ffdca5c0624375e6e8003c0191e48d08f0cb659b0d08f7ab0950aa1e20c897c561a85630a3e2b18f8d37bb1f2a756ff8c927198d2d24584099fbbdaabfaac20274306087bc629269af20666dd7f3524c1bdfb8a1e49541d1df30b53459ce64e6cff92050837fd4fd93cab47c80e3b431b941267eb4cec6720534528d3628a6360f53144610fc4aed0fe1f931a7ecc9a39c2cb7ca603651d14c1c2aca9bf0c61069486d708ae2882f931f39ce619a5e70cc87e6368cf38ed97131c1145c8492ddef5676bd56a0cf659716638c7e7e8df1f1ea5047a45f8e5bb1b6045085504f13f518d410c7892e1a36909c9034fbaf63db1e0896337418c11c686d71d142bf4b445ceea2daaa91fc95fa69610f6a1950f02ac5db4582b9e472fc78f5eb896880b4a74e762342b0145927d6b5f9a6e43c6683226958b02e0fbbec4f0f3c96353da961fcfd1c39f9c368c2b0cc4797cca794037f9bc06da7db7bac34d203a9c06788649db16d1bff2077ecc6220bca84de9b2c183159f0ee2b5280ba525bfff3df986157965284d1890b3287ddbc664736a5ca7eb25e2afee50a940fb7e3a882586f6677914fa48ed865154753c7c524d63b785d374d4f53bac436b9c1760f55723be6d3f9d46604cdf32bf76160427b338587d8c0f46e06d367530f2ab4bf0b2e69b446f177bc75da7185d47f5dcf6ddee7350275e65fe0d8470bc5623a3ad6a0d0902bc21b06dd8e2a3abf905cd410de32925bc716a3d86267c4c04115a21a0d20dbaa7d3e96df04d8c7598f0e8fa15d2b742657e1b6fcd4fe14d2fc39f9aa6219ded46e4d3fd876e02e4aa7e41f148cb7181b6168123b44401b1188c7ebe7271d44ecf7c42c8b6eb529970e9557c202b4a553411367ddc656f85a06cb8214a4b20f7e934128af6dba4fcb8c23dd2a5bb15385262aee30602d13390e5f7af58d0bdb01366b3675cdde503164b9a75975b1d96addf1bf02bf2be712c259b7f3c498e77cf011663fe9e65bc98eed0a458490d2e621fe1cd87008a190b09c2f50e68e5167de078331734608aa5a3efe994d0b832370f99bf3e3a67a894707f98a2faec0dd8cd48a24d98f059a23774f793bd33bb9f79972a0059bb340a6489185a0db6c74e76dfc0e08e0621e56143b0e0f1a6c2298c1d76a3114510528f2e08d901b68d07772067170a05718fde10d2476743bc06d8e34a317ab2f4bfa927a1507e1ea18ac89de1e33d2c3f6da054d2c3ffe0291dda8851f1019d864128ced7bada9239d9f0afcd9b753e2c442eca4bd223bdcc490b42dee88b6f63edf4c94a4505bf8da54c080402746b217832c831e3d587e0256be3a82a3a9c2906f22298e1e395d5cb4131c5ba91657135918464e1e18386f3cac20591d5f9b8d1f0e89904fd163f3fcb631a1cd77bc082e6599a3f2a4f4c8e26954c7600a3c504fc05f7d167290b1774faaf86c05394b553805b7c4d70865c09230a43aafecfb1272fd4284419521b5319b03a598629a005e26c8fae3a9eeea510c3d761b0f53cfe1b4061d42431f48c474b70475b4abff4b99820163596fb38a4d8af197ba6832b606c1c939608cf1db118e3cea5670e4d1e0fb2471cdae58a626f4038a684686c68ae64a9f2bd74759f0f6f91ae0add030864421f0afc8def56f58f3131ebb02e99bd18327ceb0c3e7ca22c08296a5318ba31a0868a3e80bd4ef9ef8f5a63f2951efc71bce656b1f891b766f0adf619698aff31d5d11f02ce4e93022296c7abba33da3e83aecd24d791d24642ce0f88fb6e8c8286ffb887d22452b4fc262d5a6e915c72cd94c424cc98771194ba74587d8984f42dd0f5d2cc1a7e4b32d7ab0893de63ba3a20e54b266352b800dc2de80fbdf33598a68cb54ab9f38722c78ae0b425ea6ff031fd51e7fd0b27dab233ebe41e1b892f606ceabe49ba1cbc6edc9d2fb98f7b57e78297d3d6e086af4466bedacb4ca09776a12ec2b6949265a49bd4b6ddce37d9bb20aaf44b7e1284f9c4f5276ff8aacb3453fc2d2ba51c9a8e0c50e4fdc278ea5d839140e527de30726e8089e3c0c5c9f7d4b3887ecc5618b53da00b078a5e2bcb0c50884356eeb5594a1c595c8f74fcce6632446fab8dbd306c6d2aa59f99a01a35126919bcae6cd7a825a2cda0b3c2e542f41155747998f13e54c38f75d9e281205c4796e2ed31a08154c99653bafaf9d7502c69dbd0d3c3a740f3ddcd3b3e5f2786ce0c42f10d8e951c2d6e255c02157a94093cf4c4619b6651c44f3046cd0a2bd31ce0bd40f8cb2151dab3e13ff7acf1d2c3b82198abfbb861422461d3066cafbfe77cc489a52d3981f62a5c766ff6c2e29f3349c48c1c0ce748e2255e85d70c4161cad2c8acd3528889158ca05248b5e30d933badb774cc585c0a187a4b5424c9b15a322e0c5a6320ddd445eab8881fc7e34a7d751bf20a0a4f8fa6c05b55ac0e7eba2cfea298f5b3939383c24ead1a2c83bb5346958a5fce57c0e299b4fa4fa5601c06b96cba86185dd0bc115e215c7c7c9fc1d78d242f5cba099283619e7cf8aff8ee5f82ddcbbca6d33b869528fa4e3e598ca5d14b6251ae4ee0dfdab080d825abd6d08b91c28cc34cad010a3e6867c87c0cd14d00f2546d75e027765f88ac9e3dbde2858920ec6c6575ea103006f19955c3ac3cb5a8d41d33b8475bf17f05853d1b8c09a1b25784a66307fe7289388ab84bc438a46262f1aece39e3ca57bb1b2abf550d063334a1546812ea9d6c8a205e0e45b6ed2f6e115819a419c9578b1f2d1afe888c4b79b666937ec56d2b3144bd26adf100e41172bdaa1eedb2df12ad8006a66659574c006c344702e24b54fe09a3fd7d2a8a87ee3c55161467c80e26554455d2a0f0e4a4910c4bdecca2485707658734a6fd64483de1a901caa2984be1d108d66a9b5d04f6c2326b6cf8511ffd7a049959e3383604068a3e375f950850e356f5d2573e5f77a4ba2c04d04ea1b20eda36b88ea044f3706abbd37840b5f3787ef14094179129b54c283393fac80db3fd317a2b8f553f6459a7419d8b158589f04c5c70260255a1425be6a2de5dacd88961fd5ce8f49f278627b4831eeb3a6d735688b7a34124f935e03c60714db3ae3fcfdad8c8dc6ba4ac3cb206fbb7c4cd9310bfc0786f1b5e815b5360c49ee7e8d26f933823f05675343d7b9d53e90602b2aac9034a94c55c2e3210a6a9b6344e6d2ee78dc77e31fe38ce639f862654488cf0bb548eb32fc4be9631583c6440edc3327843cd80c96d27cca7cce4094cb60e3a701f845b02e9c91fd189dd9ea29c9e1e832d15084b59fb9bb9b213a02b70cd81c4328748b9a8893cd0bb1f845845ee25b9c1854f0166512cad266bf874687e298b8f42785c2ea1014d25e3b6565a9116941be22b8cf919327970bd956cdcd3dfd226c80e573079116ca1b223adca1264ab3962ddb96d0693942c0f33dedb2c9a662b9e3d0577c2f5b220892cf439be7c91b9fc89c270bd4f781bda9c1b97a8d5e650afa681abd0bbbd8c4c49641c0912a1a558a51243ff8a13fd7a1e7d0090ecca7eca19726b082de1cbf65a747b14a77889e1e998d769fa068f6341dfc433f85a41a086898f67007a9579ba3c29b5a899892221078faae6fcea923cf067a7d4484c1e2a412fd7ca898cb1eac519c3e00cbd8721b4ebb99dee40857a36c4db9ca470f830d13b5893788356ad0da70be61b0cee21197c6e74555fb5dfda90320a4284ab13daf28b1517552f50773421aaea469b2233b54bb2e7a78f526cfcc56c4d6040394b9ff8fdd1640d3b2bdfa797e66c22d2b29a155a2c25037ca0961dde875ee5f547602c66c609e8f6e53138cf986d80c78d0a5e8117ac743617797dd99970920daf81ab550f6e0a4f1caa0fbe1ac3f52733169a8c3a4bb8d684958a8c5029be4e821443a8f4145a04993ac4e20948642a2f35b6bd54d477f704a8ff3c7417b142b2b2e8591a6c0cec5c2de996ccb2c042d81e137e5b8c945e4cc536e678b11cd5c32be80e86d2f1ef04c897dca657ab54e4e4defdc425742c0dfef9383cb34c07b4ee36c5bdbeea8b84b0d25f58a3593b61d9a80eac846d89e50d9518ceec09ebfe11ac102547149125790c512d72ad759a36de382e7843f62758ea980317358daccb191184678632b5b5f1bac28e31ba1ac35605bdecbd9940ef1c7f252e73736057c43dc3cf543f284a4252f5fec770561443c5da8fe24e2481dca7fd0d38770cdc8299ea63a3066033626d8ad96b5393541a5b873aab6b5d53f8e911d02469ffa36e8c19df658716d0bf2500f3513811b7613eeb49488d02bb490858e2e017b17c4156be46083bb2b4e77d57cd1a716d0c249cc14ee368cc830c5698020ba66d958a59d0c1ace285f2072852158d12a856e9f31df085ba114c9a721e81447a0e2b93744e94117352b29283f8b541a1bd92f38221b339b3cefe0855819315b104d4cce7cc5881ba176991c26f5b3fb845416b30e7a3b2bdc139da18904c0161c42ee339cddaed9e88933bbf4c101925da4cad0ff333ee312e0decd57242d5f2e6655aa4b6afbe36b2ac9914e7bec2dbe0f8cc107113b5576f3c8a4ff7431110e59b26352e81c378118fbd9f70c1da569b62c64c8c1fe47da2deb45d10192a234d5089fc44406ad98596d915e87864573f2ff3f8484888c8fc5bda034b998a2b71450891b70c2257aff1a81f5ccc61aed5099702ec7b60f12985228763492c18322534ab3afb11105febb0a3f394a5b1e19540ec401127a76b16707273e39e80888231e373ec35a71c711626beb21d2bb07e413926454ad3d4cd8dc9381fff122a73fdb05d54b0b02e4aacf22321e2ff19a7e6bb9015f6293708a953d7c16b245a55e17c946cf08476fe4a0899f6dab3c14f7a6b284cea90375b9f3b7724dc781498297a398cbe274eebb29077002e863af2cba0c82e31562ac05ad5dc2c86331251ed000a1c830984e3c777df04c6dc178ad935e7683a4ad43294d01f6edb9e1797fa15c9191f88f6185992dec6ba7ff961be0c6400014790b17d50850406bcb019b0d0eb628f40a3b4366d68d7fb4d0cc4aa706bfc3c8f4030915b645af3957f1b469189de2cecb86f5236e225db0696937b7d3d56216548358530a994fa7fdf2276ecbebdcc42493b4fcd2fa5c71d9d6f166aa0b225b37e8031f67da4fc222164e2ba52deba42aba3566fe4a6105214376d70d1751f284ca131d213cd879792fd7ff1f2e9b1aa9f896b8924ef7a8e45dcd8fd97b9e92032850cbc3ff76d330dc40b45d46bb3543bf69f051c598e303b568a0e1e53a2c5542e8ca32f4336011e36a9805ea5fd446744a4e5ea5f55bb7a773899e43ea13aaedc9b53bc1a1073c3fa01dd01da8830228726146c8de594f386a4c920009602807bce027bbab820c95335269406bf82e703fac04638089b38fb446ea734d1d178fde7f1c4e940d2b52a8e68145445cc38fd55968e9fa3d8feb9b676d080750fa7b698947971088f12aad99ddcf41f1a5c93ab9ac679edfe8850c6ea425ac39c72ce2b5b814bb3adfb2f347c3e54fe9adccb4411f083338eddfde64ba2012d45a5058ff1255163dd71e4fa4ae4da46faaba14297ea9efd2e6baa41a1edb9508e3b67e3bc6e8975c0a29a2fb28fba630cad14773154d2b1abba50cbf7ea2108d8e0a8ed1593cb19c506dcb42ee260114e6bdf8821146933cf7f5e3272b13dafda40c0169a98218d85eb79672bb1b2e1cd0853262e944ebd01d21602514a05e342248d4b08e22eb43f8546b4dc430cf8a8450393ecb1a31b9d0dd8a2a61da570f94897f5beba7a462c9f5367805e043f235782e7e537c70eb4531f3d3c3f1a39be4fdaed6ead26b1ed761e539c2230655b14eebc7541b234a14370994e7d6c78af4d41ba467807ce2218c4d82fc1087a332d35d99b1176ef3696835e2e40cb4c6b7ebe60edb414cc331e8be095a3ded7e6bf81c5d0dcedda76d04c2aa7a1a0688db4fa6aa98f3c45d61210d974c0046fcb5d11904094c6700b73d1c48cc20eef16ca8dbbc0181e88d532d18ce1a2f74b5296c1c58ce32491535290fe453d41413d9449d0cc5fcaa79b88d8423569cccfbafa3f502d44b056b689911612580699422ccc95022fd7ab5919c285284bfecbff3a04e50b7614d971b542b4c4dff36a5de5de251090899c8a99b8b2ae10121dc2224a5ae9e0e84338545802c060681e0ce5893465ee1d2f38a883f85865d4b43c2a240dde6857ed476803c60dbf578d8dd1fd2f32172a9d02b3f94c95ae5f2cdf2b065452615b8d343d91a915516b930730a58147428dfa48064e4a99fe91d550bdd03812a92333e4b3a78649d1f69b484fcca9a5371c6367682e864194a4ca01d90eb79ceb9a35e34e9a58adbc528268d2e1580fc197a89b6b03086588fd187cc073898f437bbc669cb128e7b7cdfff8544cf517f2b833cbe1233a9aedc78a7b9f446d3530d1decceb6e3c1e8f5d6419cf025d289d75badd0f3e18987558f50b526cafbaf35a6d913aea65b16db180a0cde4c0ba6b463730267efd591483f515730fdd8c224950870f1049d3a0e0f1a4c4d2efc3f125466c1b318c394d0f134d919c289fb21801438fb85ee4eac5a561d20cddb2c176733a4b502370d1ea802202948f44349da347a7280f466c96c5242756b710a85d2e88f0251ef70031e3e85342a0a960badb1a693dedda6da76e02d2c0993235a9367a14e38902903663f6b3bc7033108de9c170f68418c40a3f3b9c90254f7f4372858715776d3905f7e2cc4db9dc83f2c9242f4d73e5cca68273ea8a5e9048cfb50091944f7987974b0d8d9a89fa6c8e81456c4f5e1240e889f36a26f526b753ff549a7b483c486407d719eee4855cf8e3d6da9b7c4ad92e2cac7612ef1ff41f5264711a8a797d6d424921319638aa1652a77a4c109a607182f9a0b22b2baff5bde75c20b990697cd60068bedf5463181ca2f820fba50599e12eaa09f97cea7f7768f1526c4dd39c5158167182c1fe9b82373865a29ae35e1d76956ba68ee21428da18121c268ddc9d38a2364d695a13250d139ac47f5619fb8afcf04fda0ad034cd5971e1d06038e18f431e4c07c3953f792d40e26c10cdfa523be712bbb263cf089c353df708a60d1e13ca5ad964ddc07e979b15b208471676a0bccce2238b2d5e6710a175d31eec4688a80c66056ff03fcd10fc64244551556fc2059a7c5058d4124fd39a2cb493a13309f1e49d3200f6e0436e0fca00fab147443b7fde8158bff75a24eb7c0acfb4cd047acbab1735b61a0eb6d61fd81f9e606a0cbf94a624f30649adacf1c742fafc048c25c8e1c08543b105bd7f576b6d38834673d34abd69007f76c311c4e902bf80464f914ae5acea4a4fdf20c1a67a41e6153e3c87ce422ad85c3595663c108bbac3323f791d275387bcfab21666c357078436d9a9f9ed2208320f0cb6322c32125ea1673579a3eaa76a8f0e726f9bf2c0bea7abf25aeef491fe22e674ecbce9a7831453771a1614cb67187d419a1ecbf8ffb5c108b085d29867d7e9846d4eb6653d4d99dffe46352533ea85e793fd39386e312ce8fb5cfda468903db37e52c3f3281d889acd0f2df8afcd19bc29b4d8aec5f156941432d679804700c38d9c07941fd17d5b43139f192effe95247eba61647b77caac4fa21584ef124b0bc24dbcac1adb18bbbe264dabaf339650dfd09f5c0f647915c15be98b165a8007e345274b0ae5a72883141e56722762750415c47d6f36ce0b0da24ceb7274ca6e1be5ce260322b2a698ba737bf7050fbebabf344a09f1cbdce722e7f566af567dccb030fcd51c9f667ce744f900b22ff84fbd3e87dac73e0035517eb2668a14726758cbd9710c944b9d51502442be1db150591dbe1b078e3d7dc020030b32feeb27c281600c928b3a36fb04ed2d077d4dab56e97e9c2707a14553b158d1692640ee8345c3fb2fea77d25e4a10ff8c2cf10df133e888d6e9f23f14d0aaa2bb7731cd578bc5bf541232736022dd9d0c7530b13059465556cdd0e5a4fcbf2bb92021e21ce9f4da86dc3a93f3eaca39dec791e11a7e6ca4f37594f36b1619221e90b7df84ddc509b920a610b61095788677c95f09b4867f988e7e3b10405f19ec6f21aa120c26cba98f05faa090a50d14288d5c6542fedc914db46f093dd0df6c61bdcfc5dea94ec72e647af7951e8e884701024296417074d71daf1f269bb7f6f077f76a3bd82f272c6849719cf20ef8b0bf9bbf56f7ce92ca2c17f0a217082387668bcfb0cf272312155d8a28a45d3fc9edd1e733fdfe1168a96657cce44b4f891be357f75dc98f826b4d4590925efaac589ded1edde8a25c4021928b612cbfd7c1bf46a7ac8f8813818112eee90017cd8a2d46436b6ff39e169a8869f69773f653ecc2032f57f0725745c9b986ee452a7f064284628f32599d3a88c429bd34edd4a489b5334f1f4dcfea84badfdf5311ed0cfb48d2bf4a9535b3c4a5b648e8085896783845bcd0f53bb3eee625082e383883a4d0ea9b3912c088eebde91def124e4cc76f808787156a62fd041cccd82c79c2d1ba2401661d22787125542e19655782b635b1a6f99cf2503d7f5fda5ea6e2a73b6bd5d291fd5e916418aa11fa78c2c68cb6036805224e562df0ac5ea32d3972450f5bddde9f68b7087328f846fb8b6f81b2ffc41fa3e1ff6e3e52a2ba4fc56eb8904b832a4dd51a31799e4aed56acde29f080e65f6388dafc04e0929ef018234fe9183c41b0d41694070ee547e18e77758a574b98518e1b5fc037d11a307ffc2af06c03dd84d68cafd7129c1dc855b0d829fc163d15bfa82fb13a929e3223e52b0a8b46e29e0a57618064d62d5bb301258ff190132728490fbb4aa5a45e741eead75352ad2bce7dcce531e20b9e5cd2c3390f1d2517139fff5d6b422e383ce9457571c8a1732fc160e02aa97816230adae105acac57345bb50959dc3f1635194c467068c4c05d4aee649594727181f8d8492e73862f5cd477f5e1823532ea548c301e85d372a72c968620546494e902e25d7922ba2029b7177cb8296b58e8dfcd16662e0d878c5b33c769df460822a6dc6e9a63612c2d439aee9bcb48bd332f99d1df7314a01f25e35cde0d85e89150e2039b0ad69fda9642c5095cc09f7d8ed98bd01caaac4bdbdb18e4c8deb0ad1b632198053546057cacdeee19b503e455d424c111852c65b1cd87dc726b9c5e6218ce5340d446150fd3e2317479120f978ee9bf60420033c040dc3d667e92885c6ddf33763c4dd15164c7803158194ce2194e88e94be3548225ddc1bf29625cc0eaf498d97ea822cf1dd7e31e67ba4d51a94dc327d85c4cfbf17340b8d99b73248d3c3cdc82442b338271a0fa01897b4489cc5a2defc5b8bcc00842c9cb252a60a03588e1294016b3da55fe866df3509cc94dccf0a410ce99e40afa883971bef29944c619217975196ac4a78166343d899bc0e76f87842695320178999c0ebd8a241f39fefafc583f2255973ce4c27776c36dd1bd7d78d1105686950561e9dca5d172ef96908c6ad7ec6f563fe314eead35309ccc54f983a0b06aa39f68ff588a982c3e1b23b03d4a7732a8324e2ee50ff43f98b3158373e9b470794c9b9bc1704340c05ed490fd03050e0a1f91a2d4831d4db04ae16db5b0117f6e5d81a463f52230ff8bf52e6797281089b4015a381fcdf019783c291b55965fee711f1437089a28d338722e070e8e3e71909b1196ace829e45fad7365dd8270f4d4e6a327725a1635b33b8c4a6734da08322cd7397cc6749342883c7ff136f0932286e41a5120fb12c2d1d18b23500c608bda0029e41bc0d6a038452a12d34641c28c6d42193ba12bebea464eb1fc5a00ba6df921f415ada821f2c65d87a254dda1f4a0fb3c9448e3e8fc4fa2e8f39ad8d20a91f7bb65f7c4ef62417feb761913fd68860b20978da8e4133a3ceb95e2388ad1a5bd10388a795d9ae8ed3cb979763f42b912a411cbaf22842607bd27417fb7a200e9f61133d1b4402183267f99bc0369f42580d9e90819d762e5d5b5ad0182cc1acd4f243f5fa1de20bcb921d5845667c453ad3649be0316e54d17adc98bea1bd4fc46a303c2ad18d1a81f721512be64fbadd6ae367c52d6c91f663cbc41ff79aef786ca552ba820e01280617a83059db9430f2cf0e9550dbc890f0af1309cead5e0f209b0a329b5f315a0c8df0408780d40f1c876dd6d7d973ad67818a0734da102ac34aea624e3bd6a54815e895cfe6f32a9f460d07b186c59cbe7b0ffa7b91aad31ba977733b4f2fc3bdb758fcb9c31eda20f69c346368bf1713100dad1932d0cbea5cd8ec141e68b5dd5e8c5d1205eb830dc4a78fabc0b80ab1698ce5fbc7e96abbb2a629b74db6488ff4559050c79b54da564e5305e21b10f799cf54b5e347b432ac60021ba63e53d0ada9e384dc2cac71afc203e2f180773c5a510030765143a7319a2bcd86513238227c10ab23ad11fa8f5628e27a555d548309017f61a685ba17ea7f5add012607a1e9ba3ce8f4999a6f12526628f7ae8ce0e709867e7c550d026ba9728a9d71a31cc110cc202d9dd4a61f091cb7852730fcb24b491e079198a4913b26c2b0f32deba055cbaba8b184b8f4f5a90900e8942e464ae1e00b52ac29f3e40b7704d9d074ed8dc368e08d46954a53ce64e0f3d99daeecf4af88acbfccb9a7441958494d18cd0e3f3f2bb704815b1c595937852a7a26c058665297d195f41b1cf3fa17a3e7f66213741450116e043025b6a56c1fb837c641b00cfc259b17f47b367be1fb6949582a0a0f8631f2bb1fa63f3b6f089b3fae8acf289451ec8bc2f7c832b5c29e415efe905d3aa3527b5761b4473b480e00e244d990100e3e88632491ee5d7bbdfcd69adc481a15098e42cf36694f5efe0882b005c5a41903f0f7f905296c1d3787e00282f164c2aaab966a788770263f095adfb748fc416d63043e45fe3aebf159352364de4c8e6275039b1d6a2e124280a03f5e6d26529d855f7aec10f8ce40afa49054606028dc465c9af853b3145dd61d875a0958a521a18a55f6d286e60d160b83f2cdc27b4235e650714ad9c7d00e0c347e8071a1927b880596e745354b28e82ffacfffc3a59a006e236586cbc74f1fe1d3cbece560e7ed8fd1fbf98f0313d3c1c0d9d34115e38d07e831a8f87f01b695cfa39ec8c3ca4908de6abad71b9e3b9c9c24e05358589708d413934ab1bac654ed9e05c96f3ee89b8070c022d6d61a6f7c55592e8c7565323c1bb0fc3e30781b485090d0757decc9ac2b3d09a1894aa213b74d18b588cf2f935528e8053a94f940fed7a03042a109eb702513fd9b57d467c1a27f27d9586d09c337e34fff214cd8cdc6449dc220d581ae25e2990d8e407a66026338b64a1842d57c292d466e305ecbb87580584136b28b3b8b95f573e723498e2a4b4313e0fc3754fc9b31bdbe7e1237f30aba8b6c72b66f3cd4bcbe2e01ed602c88bf20da00410fee28c37def869f5d06563cb232bd8d245f06db57deed11d1f59849f6b2ec8cb075e4b9c76c48b8ed47a1818884d306f2734aa8389c5cd28fa0dde97c13cec3c18cfad30cccb11ed9e213c79b95124ff1297b308cde9ab4ca097a0fad72e480b95d06784e572752ec1d31e0297326dad5725a8c0de32ccb4ba5d6f2af6f0e0aad26e0df52a25eb01b054c8e62e68081bb606633821f73d84c2d02df1dbbe98a22d453113448873f34e875d329e502a743acdd0e6f3f492f5874dab25d3c9a13b9237ffcaafbed1e071443c3b9eaee983586663964538018b2e12053a147584fb5fdbf5e51bea540c912f75cc332acb2884c1a3cd192b5026abe19e01724d8c9429b6450c14f4dbe59556c33d4e324345c14af86cf84c17bc26d1971752687f1f535065cdca38b7ca3a9b9ff6986b4f4335f0d1bdcaf6bba96482bb56384b727f5ea82b01ab96f86ce556012c03f06e4fac0e551a417dd43e9488869d55929df97404c29da98ac4c1d4952b0b25294a3ad89faad5438026399620ce36103497885a595b3bd4a0d526a04480dddcb233f07464ccd0b1e362a01365516deecde5ee9e28b329e2179504515b1930b6107a437a3d65a866f206708c879d88682c74b673f2f1dd99d24908c68604ffd81056a82a2b315b8abdc828a735b3e5a6e3dfe66e1917a628017a0f2856fe3002574b3c5d46d5f5b48a971a9063f1ae592d4f456f7411544c103d578b3d82be3a40c90fabead4c326b2e359ef86d07bcb7fa31ffa43b6bc96dc750aa05c4e7b3b4a4448457d806f9c8df1a5b0f53833425ae384e48bf565ebb45d119414456350dc8bf033dcc31663b2e0fdf523d8406bafa2010b2002b82823f15af74a3ad7c4c3e87fa4e03b63ba54465d5af2ee74988cbb32ee29365ff2083e21d85da37efc84024e6b51b541f8103a3f4eab92172f527d12360c74691acd3e77f1106849b51ff7f19a932900b531201875b719acc448b6b792741020ab055262581e78213a028d13c3d901123aa2c8ab098271374096ce0cd3ce108ee55f5e145c1f2a60157851359b7e5308ef576f08e3e9227233e868220ccfdf6ede96b6344f3541a141d1044791d8f08c30773a50df496cd3c4ef93e014870475091b88e36373e984e89994c5641430ba57bd7553582bc42ab21b88e179354b733c2e6ccac454b283ef2f350e02b0cd887fcb8bd9e8cdc70ccbe8024598fc03ead9a6019e866a41590211ea6a1efedbb79433e6020d83d6f690624e7ed401d6e90384a1e85d993cea9f83c199e16e462be0f383150186cb789dbe08f7999985d028b0b328708fb49bac9309f99266a1977b450abf80e5e59990f0690ebe02916cb5b2817982e40bb403afa0cba3a58f81ec7827f61484fdcf27d1169e597f2252c2188ddb984c8f4f0e8514f1fd90aff02e878401efe45b92dfb6ad4a317454776067fba0ed215a01e9238790280f33bc782272746de898dcdd85436a30814318ea9410618f35cc4877c7cad245548eee207d8225919594a024b8afba397e6d689bf4c15e87501e923e810d234799b415bc869a13fe4b1fa4deb99234ac7f561894783a90e447654df6faf1d98a9877f61a148aa1b2ba37d2624babcc86cd63497393681e70c67bcc959407ccc0600dc1f20f8770953a4f87a539206941ca30f24cb7e9d6062f8ebac08d840688818dc5ca9f98054a25af61310817e05765198eeb76e7c86487bca056f582fd1a39671f70657f1f59ba5622000e5443f6a3c442eb4315b6787451321f0e5efca5d19bdf9d0897b2e71d4c2a7095c2a13bd7591e1f55e7614d9deda69170ed7d1111f2c6216d41e8ed050bee96c7cfdac1318086c2915b955bec2821441269a2ed0cb5b114a82a416a5e61e2a3aac6a16af8a4854c242cde70c27ac553f4e939288e5e85c7a1ff319e5ad040750a69ecd917cc87418fea409359f757f46a28160fa2b457e99fdfdfa8ea7070adfac3eb5edd733c55e929ac8e364470da2ce336b8db6f55f3276d9da172c8a1f397bf59640307c2679060b7a183a6c1f4f1a5ecd19bc74c82cb7883ac9baad7a4c11cae903186766c33632bd780681f34bc6991c0233310b9e8ef70f6a946bd1186ad330b1668bebdd19cf5861fb06f4078f5fdc8df7d4893f208ed8be7a0050a5fd52f76337581ad61b9a967105e5cea8336efb52cdb3127e0d3152d4d1df52a94cf12a23076da06c5f63c9e725a5c23fdbced24fc4c8af680a56196dac59d8f2095124c0ee825b54ffc3348b5587e075a6fd6aad72b19a40aee821deb17be888fb4c0b3eedc467dc76481e01a506422bc1f2d977a98160923d3a75300934132e2184b80ba5d93b93fa5fac96db9eb4e43057bed1654203c2f3f2baf095fd335e970772e52e69418d363974d82829674ba0a3c064fa407695fcf82f1e691b6297df41d5312b8e67adbf4b2404e9b66e747cd3aa25abb9243111ee1f82fe560dbb4bbce29d9689f5f6da7e59f96dc046a7f7d85633b7210e684a767bfadc5ccd200f5649d0985229296971d5acad8fffdc2c1afe5b8627a8437e16f754dcda395d1f49ab7189988e3bd9fd26618659a50ac43b1bae60fc304e0c02ae1868cf7834bdaee9a5c7fd0cc6096eea06d17e85ec997dab35352eb77af98236451ca72018a5aab42ae11b02fe99ed0bab03222c82022e1bc0b2b0267f7572e3f8c4f6038e82642b023feafb046d7fd3c116be8fd7da587881c0ffc5ee8796613ddf4f82df91398587a629961ffd65da28963ed2eb25fe33962bb4b86b97849f434d415a21b473e6f0c9257ece9bd99ccc9cbc2c04da63a093a1cd6840617407ee5ad5a84dd4296d09ed2c52ac376e4de4303a4e3bc4aed34aa1aab836386f7a6658c3494b9f2dced9d9a96186b89c889d20a985266bb8aeed13de92a03e903cf33d54597003e49fb427172f988ce4aefff334f84f213eb76a2ca398b8c1135fa2d70f74f5e52a695f9d974c3dd69f8c473f5094dad7fc92e092cb212374182639e71cf99ad2fd4c9b97dacfe3e367ac6c5438de6169c7cd4dec5551bfb1e06be18cfa45b146b08bd3f47a6f105e54496f5301cc358aebb03d6ea5f7919a293cc41fa6c4305a75379eeee9080c2b402cfdeded5802e1c7d0e3dae484279350e1fadb6206e12b8c2be0041666ccfa3a927bc0267371cc849cacefc73b5ad89295708113ec15cf83efc0e76ee14d252ce68b2072b25b8814c2533c9b387e344dbaa3aed98fa6e886061ba3afaa42c566ea84e61d7ee0a9e1c3dc5eb74a5d818ac0356675715d48b9492ea2f7f18be5317227d7e0bd8163eb2cd7cf018379712ba3e23d47afb61c270c7c4cd25321da3669df270526dcff1a489fe1db9f313f8abf0e3dec5e7ebf5b01693499b011fa95eee93e3b471506597d4f4ff37e0d2bc9f1dcfe9b259baacc350f4d6ed4bf7b891f6d5500441bb3aa3a9fe7c8ba49faece7be1489c463664797ce8e279a4464162c22706888345828fe810774e4f6b38885810a1146e84e845bdac08a3eb5f35fb9916c73b0b1ef4e3e4f4948d3272169977558048fd795f11c6519b8b63e2d05d354d24fed29f7be3fe302cda5e24c7a52ea7d27b4f7988375d48b66d4dd0fa5da3efc27b562ac95d895ce51ffa7715566af1cd69f7336572ba4a7c5280a15f3373635aae65f5201b6b676c86dc124d85f164dc759da2d5fa4efbbbc5dd1c4f91413854a15876e3f748d2715f98068581421b25f8ae29ad78c69c4416895bdca8c46e03aca97be88e2ae480449c2640df963708162b07b6200297c07c29f051de67d817d3988db1a879ac6167ddadc78b4e61b831095e4e309a7082784d6afff3ea16ae3f35cfd7ff1288067ebfcb284b4f07c61e6feb52750c79a069a27398f4759838e6117290eb37fdce596cce00814bdf6fbdcce3af8466de3fb9da66b9d2cf9e09bc2983c0071f28ce3c67513f8ce561ac395ae40a1d7740c6545c667f94ab2301b361dd6925445b4c558d02a2b7503d6b185a5026001f7ea6fcde53a88e6f9be701b5280b5fa57a98abbc6578cbbbbfe9183ffa71d3de546698ff0a3dd8c85e19de7ec210443d5e84c0f774fb699c25ee8c20210c51c28f001037289f6d27f312f5f45fea8006b110f44c100fed60034a299e4b9b35f216d905418041a35b26140e196fd72a06c77f61cfce704164598b54f27b9959723aba4b8b2205a086141877b223f1cdbaf25f172565ff9b7cf077cad9163b10a1b68809ffa3fde074fe796546bdd39fd8138c5c8ebb503d69595cd968995113450f87beac4c6fe2b66251029a43adc74ccb5d5fe21993315e3434ed13f2d8e81ed7dd60e76fe52f347feb704f036470c720c28b965fc656808c2393c0b6a1b517977d54d17dadf34befb4fa25ea6c7cc0a12a4cc00aae7a174b443e483ea686f414049286f627a3aca5589e935fee235c3518f8f0c7c20d2319e8667dabdac051178d4a4cbfe77ae67cd2ab45aee7dd200ea90920efd30717f8bc367b01ffaa0a6bd3c0d8b8bf8550dd81ecd80ab3d1f4a248d2a527386223317471fca78cf6b619ce7e36f052882ec1dcd68b212727de8dbf39bc9c3ed0226a910bbc6ffe5097b28a229220a6382841d653707fbe69e03f6bbf280dc353d887b88a093c403629fbebf695022c720887bfa65772233ba09f782c8942a90cb1dfbd635c59390297111e4e646334a669c5e573f2d1ea120eceadc5da012b584acd9237927a702ea2f97d172d4f8fd0f900e6a1874800bed1ddfd771d644e99ee54c58c5e32b500b8787e6fde59eca28b8c114d14f834c74a1f8546fc3d3bc745ab9b423b369353d0b73a2b9c9c1ba3839b3b91a557fa7173fd8ee029698c59f0de3bfdd4fe0f70714aad4d743b6657f1ef7fe7f1e794d2b33d068a89a04d8051c858f90b14f647c5b1214e25ee8b68ebc1ca8edb6e386dd18ce9b45fae10e1111317114aebf4051acc57b53fb8812c68a8d00deb63f130f05896aaa38bbcf6f563aae9004a3eacf21874f74a4120aee8380ad2e459c28a3ada04ca520391d832e2d83e5de943b110679f3a096407dd3de0ddf802b0d98d3c1d82ddfbff4e7f3dfaf23a888e3b917ec717028395ce7372a8f05382201ba57d1fdc37126be791e2f22feb0777d4663a940561a2c457d575fa57cd5596c99abb3825d187dcc7e42cce4e7f2f644b6636cefb479f7f9012b7f0622ce5fc033c7fe846f076808a11918afb3e4355d223abb820c741f03375303b94613a16f926beb660ff832f46db3677982ac8967af136b65a8f5dad46a506485e3cf24ba69791961704161eed3413c376975940a190d2152e5b7f3cc7660bd0667adb8063a90d72da36ea8017647547bf07ae857f5b36d55e23656493ce74b4059a409a71b054a8bed97acc3966334cac1f98706240204f14b433e3d261a7b1d08a6d73309bc9fae4a3451991395ba6e565816f8ce4e752c7072d440a41fe121295398de1760865b178e10515e3cc3608139eb711df2fc9543a2287791342a4ee66ce97d4967d1b200f549fdb3a43479195541896cb4bf5aee709e8116c93d40eb0ff3b53519bdd9818bdecf69e7ce0013a4115715ea341b746a4c0260d3faedca6b0b89a3a55c7080054764d4e7147c5193e900b7400735e040c24e0f2023266b9f33d4d9d4a037b742a07ed223ff4640d512125c2484caf71e5439097ec2555377a3780e788f266d9058c33919017843aec43710e98f204fceafa42f6c1d528df1e2ce2084958060c6f078a4db164b9a4eb7c3e2ca2bb35ffdf49b0df7740f21f0c80e5ec59367c2d524524d09f32529eb4d3af8dc0f08e65b2277c3f2fabe23f3d347d8e3598a110d1c66860b2147e1e851a07270c4b052460c32cf4a3381f13e79b39ffe91962da126af94c9bff80196f0fef0a525eb4ec9f2b63b76bc2ebd3df1e4a3467fc88c12cdb62a24ff01b80eed8303564a02463c0989b52f669f70c37045823d470b8602fad1a40681e7b23effc6241df80b61be2369bf1580f260a414b5c87343b26e8c16fd510187904bf8fad3f5b1bc2ce905416e0aee0f64988f6269250882cf9dc832ef83b4cd21e035f7ed89c11d351c9a2971f5517c515e88eefa5aa76d65b90c5752550bf9bdfd0e3ba634129c3c5af487f11fcd480d82db932caf0fe43defa070a5a4a099ee2ee033054002cf5a4cfa7c97ee8eb9afddfc0dcbefe3a8f56a9ca3027669060524e79a6ba13c3eedc87cfb051346b8146fda85329ff6765a983fe848ddeae3048aa8911f322580e5a7dc36fec224d47f15a324e43fd4a091c691c28f79828c20461c14040ad48a9059305816a503f2460d49daa6f7c11696a91b2ace1fdec5ca0e4acd0c517debf1827a4a560c79d233ee01188823eb83f6e31fbf2120e805054e012fb5b9acd453c1ec3e0a9eb42c2fd3a71e37b3d6882464efbdb7dc524a99520a8b082f0924096ce3f2fc31b04de9b7ff0e70f90bc03f7adcd45c8699a91835a697edb1593aed47abe99f085a208c03472317d2f3921b3f15395a4d0f84505115284d8458b2fabc6b6a22c4922ba4be4b57eada51f1539ef76f32d5486986d5ee5372499fe3ee84d25a4da67fcf4947619af8a4e824eb2d7f01763cc4ee6b14dcf859eb8b17b232277cd1aca2254a04ab387b6444a21b185525ba0437c227c2aa91c58151d3464ebb1fb7286732a83097871899263e864512f5e3160fdd4d24953f3b69647b0bb0ca83a898c528dcf848fa870f3407c6f1c18403e3c88f5f01b6a11fff48ffd070ae8f6f3da83a0263e81f9aa5ed237299fdb88b25da48025e82bdf8f448fc4863afe0466ec59f338051a7d5712fc608551bd3f931c79d71fef5eccd39d342340c596760589504a2f8474ffb4892823dda477c2c7ef4e6523c299aeaab3577592d05553d3c444444695c807dc48f11034c13ffe32624619a780315d5488f7bf157c840cd3ec5d33f7ada477c9389bbe809e34699e44d5a4e44a41425da673dd9fb50b9381ecdcacfdeb9202de9921d4c7630e91d4baabfe60e37e8313a6c814629c76fe6523691e88d3e3f66dac7052ea55fdac1a4b27b2ef6f3b9f4b5b8757d7dc7924a5feba6ef14c228e3c31bede773c568dd7a2f97872e9699a664bf24977eff88c1ab24fbd5113733837b300693bbf7fd70778037da1c6d54fe90cc5c223272965984069fe5535aab49723ef25316f56b2b577e3de54a2b526c2f91d85bfaeda5cbcbef2aa5ce887bf2b7aea7bb0148922c27b9674e015c6c270de49efc1b3ba8d98fbe81b6e81e67d93dadc76db8bff131ff5118d5cb5fcc46476d2fb76deb71373bbfeb46b40d712eb9d0d8f60ff9b1c42a0a51f1a9cdbb17fbfd42ec42edc267c906e055fff0cbb4bb18e5d1f81ee338d3f4e7a1d806e2a061fe75d94fba8feb2fdb3ea4c52105a5683cc331fe57af9cec45469960a49561e662988d8e9a636818e39c6aa8cf3d03483ffafed6ba5652259d6a7079ee8bc0fce874aac1e5b9e781f9d1f3fdb970dbece95443e9b9e789f9d1dbc02b2641949efb20627ed45c90530da577f922257bdabe74d28ac4d8d3668b909494eabf3c4ffd17eb43457b1f97afaf591e5289b91d262ed6870a8fcb97de06ae2fcf3e80eb5dbef4fe3563b8d8afdf6342e8676fe4eb217e3d3414961196e632073d7a7818bd26f486a2853806b7213dbfce0c6e537a664e7d8c35f22fcf8d9cd47594daf5144a6bcdf8923aee8ede2c7bbea2b615292addec575df8650d5cc76dfae9477b3ad5b03df745467f03b3ff6e341eb7f93ed3b4ec6f80c0cfe93182047e0faeba2114037c1d0dcb201c753f2bee7f5640dc8395079eed39bbd9c02b1db8d91c1f007c5e610e2d1595dda3d67fba64cca5131a1fa065646ea497c43ecbaeebea2e4a4d1ea40db1c3184e5b2adbf90c2fef27949ea394de5c8a7d89d48d3a4dcbb22cf34dcbbe3b2dc3fec22c4b8f269899f9e740154a6b894f4eea4e3e7a9ed1e85fac0fcc8fdee7e549ffb3d26a77f25277727f97fa31d607e6ebfbbc7ce98847edec8e1849a24e08388126a1ee94591e785e1ee679b081573cd8007f87ec6de095cbc3581f9f9f950e1456cd66f918cb2b98b7a71d983f65d6065ec1d8d4cac57ed7e7c08924759dbe5f3ebdd4f1aa76bc2219711b6ae3cf41c85e45738a452a85d16b4af786c20a2b806c5a86d16b426f247c8c1bcbc8fc78771037373032333d6a6a328c5e53baf715db651b8625faa70ca334cba80ccc81af33037c1ef833f0bd0ca3b445c506ee132efccdaf0c5719d79ff20cd72c62422b92a71d86fea477f9d38ecb9f6ce855e94f455625fb3fab51fd6a3fd2e8e75e2fb3a4d27f7e8c310c7b86b0ca0863f04415517c981bcb9847be6919f642ae2d5bb6b461c31f39055c9ba9d248230e3448c37d1b3532a1c243df1d601bac2bf935eea8fd66bebfac0bed3c11f981757ba0173e7f462e7cc242725e14cb8000c9b0ebc7bdc1000244757343afd93df03c39af99197a4d197fdc3b8ad643d492c633d767d9c32c756fe0f82ccb328f8bd13c26bdc6dce8bb23d92fb5731996f83f6ff4fdf57af8a774a07f8fc949cdcda8f99db94392bab687a8eb2b0ba1761bb98ca2d55c9e7fa9a82a7bff1e32c398dd8b3fc81fe87dc557493f320e7c5a03d9356b3e1b172e818728855dfd7ab8d1e7c69883da3f82bf74bbaedf5ca575596cfb23a8d35205f81a1ae754524ba7bb41a51fd4b1419dc1a57db40f7f26ea217aa21d9d19dca6a18ef6e1ef704bce0d4ced238afafdf7a0dec0f3c1ee3bbb8da8c76d3ec67283c9543dc6de41c9ad37c4a836fe31b84d9b4c2c1d45d4af9a58cbb2c630da5996cdcf4e32649f39d76599f6cc651af69d61df18f69de6f33dc1e73bbf7f7e6f9bd6652719605b1ffc9da35b859164c4fe90c48e59ea4efba56e64c991be1b6354fc517c2ec6974c13396b993ac22b47fa909b3b8e2285a8332ea4d18778e10ffe23bd8928d643ac228a262e57a106d1355dae420da1abcd8d8aad52b799736a327e47db9f034f50a532cb94aa3019a6653d1cd146a4116544ac1e5f613a355373a660ed69222d510888a2101951374ac9d0b40c4208bfd4d58e348241240bf58854fffa7e97eeeb9503b9209ac6715cf771cc6d9ab669ad75d3ab75f7f5fb302f0e3ecc8bc3f51a65ce24e3dee6d20b8eefdb2503b45f920b3fd2adc298e6d53db7e95cbfd60da7c5410608c60584fa994c8f6ddd551847f56bf412aa9af6d95fdda7b19643ea6acfdca5659ed1ab65dd977d0ea98b437c987539a4e86557517825a8661442088f5c11218cdf18c42086c53859e8daaea750c5fdf526a822fd75913a18ca6d5e8d4bd858857d9c5807e328f818db4a837a59ca2b2c5169d462a4f3e1351f9e88f80cfca2664f214c72a1fde80ce0d7f06f866118b6759122b9f39b5252f4895009245ae7cba9e5f2155f984194032713b58c101a11bdce06f5bbb9fe566aaabbbb7b8ada2374990bf2566ebc1ed7ad855951112c9229698b8c7c84ddd9a0fa5ba9fe1f17b5cdf7503c0f591f640955a1d5242474bf2b0b64614ea06ab2202a42b9110b9a72b5645e7c980b6efcefcab2a36981a80883fa4d4dcb8d3f599355194ed68dbf234ac52edfc089d01d627141f108dd3ca8a87ff9d2c0f7cfcb57d608e2c6e7314585cf438a28acd13dd078e693555a1746eea018d48f85fa892a04b1b4206c412bb55fc0838aca97af44410dac9abcd48dac0285a75c58c19310b0d0a47a603e6149994058e124f017ac2c454d60c284279a587914ae7f8de222faa9824514222d2d289cb8c2440635c0019117b3ce14c040e3410aaa002209fa53646967a235c512ba5e6e4d31c6c52eb7a638632ee1964401cae2074638e2889567e00b368a5a555650831c1011456bb68c52469413aabcdc12a2410b51849c702688105b422a9861e880460f5cc084222ba2285d6e0941112a020241ce979fb4d5e553f9358b269c70a5fc198f16492a09130d88e81a0c31c485156674b9e58328acc009723425891478a1c5d009b24c2088b450e2074b10f14bb2c052ad27435c78b9f5a404330373085f620330890c942e7c53ca035888f0f324063e40620c278e085263b25670834b0bf9de9c5860190209126230c61a5b8c20011663e0e00acb77b0022e8eb82e27818b9607d0b8dae5561223b8dfa7a4179ab713ef543a33ec2469263ced7ce3369f0a08dc72bf4dc53852de7a250a625c1cb77a97ddee76e8eb4c033f36a1ea406666da1d8bb40bcb5ef38dcab95ddedc15aa5cb4bf6ee6d2c138ea9ad736394d9bdc77525cdea53341d4f59adc3431aa76dd8cb445bddc26fbeb395bddbbae87a155e15ff1da6a37ca344d7bcda52bfda8ab245bca3ea3ddd7d787783f1dae666d0ff1cacf58e8d22b73c9a28114d757dcc69d57d362db42125d8fb7ba0d13d10008d7b3d0d80b26975b5d10e072ab0b2b3709f44117f785cb2d1f44b9def6d98cc63b2e8344bd5e931886595ae5c605e1614acba8f32737445eec35312a256dc184228e0b82618df110ef11243dbc9ef934db360c8b379bd0b351f6b48cfad55c49ad8431044693fbe3728b4807d7c3b2e36661e53211164358586935717d5c6ef580755b5f64b9dcd222a8a585932bc513222b54b4b0b4a6bc6cda18350ca1fbc2e556184eee4d182c30b4c46ced3736a216308a89a2a0445554a22b626eefb8deed002304444caec96db8d583347a6006902cba3352ccffa2890b80cbad2f987891c665e172cb0b2d2ebb4d0a5b771fc52e04eec428655a7344cd6e6efa0271ef86c76fb0ed65b820482ec65650d3e5bacb7a7c3b172b3d89e3b867ae318c7bf7b8cff127759fcc2565309051b1efdf3ccfbd1e34602f93511257847d003e54faa998d83d17b82018666a1c9c69078b5aff8b97f437489f034725faa4cced4d7f4f8f7b2cc3dd998c2646cd300c4686de7284edb84d66638744cd7ec4da9e3ef6c17041280b580f0cc7eef180e168c85e875b3d381a6e68b820a4ef3b391ab2b7372f7041483fbb9fcb6e43b296723464efbd4cfb7976db60ed788897c3be2dcf65a6c1be2fb533f0509da7dcc51e062c286b815ddc90ecdb520cc37e623c37dc10eceed434e99e59f36c2b526e3c2da37e3757cec76c534a7fa326f784f808a1769895916f4fa0d27bfd8672f98bcb2d2eaab804b8182764be96795e4d0dd6a3e6eb713d0ec37e731b18c69118751bd2f76f15a62717a473903ad2b7979e392f35fd18a71f439b922aa97e8d89e962ca9db9dce24204976296ed377aca29a0ca50a737eb4edcf315261405d1ec4906ece963f6c4bd3d8d9eef0e90f41a471fa3db69f4a3cf3a24a30eb39799863ed67d29ecfb3199d49d76c6bd1bdde584c56245b1cababe64308acda069d42dd63031f9268ce2abc3c51346f1fd523a5c848069f83601aed61aa715f5b071d7a02f5fab8fd9cfbb18d0165d5c9acbad212d9752a1e532110d9e98d6b032a58563ebcea75926c72c05d2217194fcee8eb8279f0764c02b81189151b8f275a070bfed7e3c04733f1ef28e6ef176908cfe00714fbee7d5d4f4a87949d57817e769cdcdefb6c0b443e2cdecc634da0f89b7ed28fbfeaf9b1bb245f7346d76231b8d44b20f8485961c0dfdec5e4c74d3cc8ccc0ccccf984c2d34c60d696cbbb98921062031b8e4c47013b99b1e82ddccc29045fd642ec66dabca588692b80db786547025bf9becf4e830ce842ac85e3ee16adaa6a3c3c3f333f04851fb4bcfdc901dcbe39eff484ec7bdc805e9b1359765432c3704dee6283b8e4c4c83bd10ee694cf7c120e9993d86eddcfee6b9d9f76b5927432404e532d11466dc7a99688a1f5cc62244e6621b0c523037a4da9feb0f611827db8128f9dd3b5095044ec93458866118cfed1bae7342faa74c872475373bd37ddb4bf7f5f880dcbc97bac9d1d951f18ab254cc758ee55e8d769ecb9739a2cadcdeb62d320efb371df9b8a75ddb75514a63865858a8c609d13ef3ba947bf27574a0b6b4a9aede6064324ee9fd4d2c73b7d7816930f5291629a5305968a42f94d2178a75497cba6b56a963a6d9dc2ba27e48646ea5dab6e1c802d4dcce713a9c2c09e2b849c905e976db4cdc93929503ca57596e531b4afb111d90b88d3b71c20549c283ea0f01b7d97a6e30e2dbb68dbe63b8197e6ee7b8cde8e5c7e03693dbb01386b59452a68a6c6580e8ca211fb7e116104eae7c20a8dcaf9bf8fac9958fb5c7dbf2c1930bc3e5561845578b1c91fad4a9b3cbb847b226a6c9b2ecb70e89877141bacb76ae7fc605f1a75dbc3cd63b24a9cb29ba318e0fc72a258c922fdb7e321c1723ff8b49e29efcce89ea2f547207cc40cb1642b707781b0890107045a5b2986480b52a314e015a6700310d544df93e3ff0184e3061ade2fbfc302d15f83e3fec006a22c54a5a2afe3f9c90e0240aab68a9f4ff00bba7bbbbbb9bbb25d3305b33d0284263e85ebe8246153097afa041e52a216a30714a64326e702ff6304dfc1e8269e23ccd06729b18372b56b86a004f3aca57a144acb287b8f163931bb5dc6fbb3d04e3cc797ba02ad54050c580f9f282ed610c685517d1a8f8b451f1dbfbacd5a1c3e57e0de4d240d9fd98a853b8299864ab6a1c151f4866858b8a1df5e9b86dc3eed9fbd11437451d5a82151d2c97c8029b3aba74a3971df71adc5c84b809aabc99fe3143fb881fdd541fa6bc51c9ce10493656fbf9e5712fba1ced304d7c6e8afaf2a4d7a24296444a4d6a924f44462f474f6464a52881f997973d2fef43455a986abfd14bfb1d692ec8e8e50b67bf929c82c0ca932f6af6f1055a7c0d08e3909e5f875f98e8729c139e7bf0fb53504260c6174330e2076aace07f12f5a331a298c62f147d9900d195f642dcaca85ccfa50845bdfea3345c598681a88078afeb3dcb32bfae8797e501cbb21f825c9d0f7e31c62c8c9b7b1d8ca3b668cd6235fb88b3df79c8fc877b79f7d34b86a88078331ffc660c337b2f8e645cc805d9a1b708f99f7b7549ee9675356754aa79ffdcee7a80dde44b72e35f5434fefe1d30a8dcddcfedab76221dff41a02a56b447f85b33619c7e93cc9bd6e034cc30fda076c15ad498f821c0aa9fe123ee39048c98a0f6efc06afc68e0be76b5fbe8b23a308d3c03890b8d647152effc9ca2a09f73e67c966b8822212834212673ce391909ac1b4ca9c22ac210583810ac882d253cb04407a49452b2e042621221a594920821823099fc1586c604a65d6e2da16289152d084153de4c1ce932839bab73d30e4302872e5fc07ae372134b5b7ad069b55a2d1aa79cf26504f5e316141d776fb54c5e8eec3c881a39a1b5d3cbe436476f416186f0d84eafe8b4af1ea259d0dec1851f04a1402743bd03cd6196805e58e1f5c743bd1399a81f0f0545c9d181aaeb31acbbb9baa11eeaa1dee954efd01d541fdf990981cc936ec26468c883a8388b70b0019310a310810842601d6c00160d018628832068af92f04fd22ff767ff588beb115a71dd9db280b852807e35ad8ca3dcb4c5c4ee43d0428b085fe8dbbf75ed4c4878c1002ffc0a9a8fb84b957697339c5e466e1eb033840bffc3b94270e749c5d2156239c2de11a8a233720eaa2a1ed94280050b96200e64a1290e409287c1e9bb3c2149bbcb091c1a01bd2f6aa47fb4b310ee13d158a224d1f2b11653d5125f9878423e8d523fd6f24510be0c909a9afbb1961b8ce4403a618cd02e1731b61e333230148608cce5089cc9d832f68b0e3e2ebaf0a111f814c6ce2463cbd84d90b15fa8162ddc6be92088824f9b35a596244ed4efdf29a5f0af7f9d2450e55df65dac549ac4b3e2a5303d519bd0a2458b162d579a802549171742f8b109beac8d10cd43a935fd27a07f6054a856415a523fd306eaaa53b7673810b921f4d239a590decd795d7f79b2ebe109cd34a99fc908f5335d77db6148d4bf9f77eb90c4620713a66a6194f34a82d218e5bc28966914ab584629866159a66d1bc78d46a49ac154227db58d1bf568b4916238eeb71189546ba9e4e2f2f2020313c3059111a2a670828a0387c9044d2693a9965c60626e4478c36fdce81ba5928bcbcb0b0c4ccc8d1b26538e6b4aea5293ea1b3f986e4c381c070c4c4ccc8d1b3870984c3972e8d0b163c78d1c384c394c39749cfa9443c74966470a9d5d6106002cb4e0a15ce0d1a3c7d643cbb01e3d7a6c3aea0623733ac9ec484185ffaeb3768515666600c0020b2db4e07928940b2ef020f1e0c163c483070f1e3c78c8ec4861c78e1454f8ef3a0bbdad9d2315989dd9f9bfeb989979054e86a8020b2d78330480112c1851ddb6d082e7a1502ef0e841e303a3970f1f53fa88d07db40f1f3e5868c1f35028175c7081078f1eb2478f165af03c940b3c7ad0a0e49cd2615c30419927d5ad0b1c91be3d54b0525fa83406a18931c2b8bd20534475eb23150001c0d030d0d0d0f464a1a57604d0c44e53ea2f4e48b576f0f3217c1d05b043085d4c103d14fbb8ac89c7b46cda0183eaf282faf1d0ced0d0d05016cda1472882ee7403bc4cd47ccf347acdb75150cc8d989f4153b02a49146c23ffc6cbcf1903f3da283ee98b8c1e3e09be125237e1dec9dfad40d4dcf120889a2e7467cfefc4a8cdf1475f04fe8b55427af823ab840a0fc9d257c2308524ea07d52fbd074155a985588539d1f532ae1a9182b05041198c93cd3bdfb340d5280860f0c0abf028a8b7c0b55c9aa983e3a70e5425390255386c433171769b1ed3311c754d0451f349dd7cd9589ce5ded472a73371d4f4a02677ce6c0605050505ddb933fa2015044d79a9228a4934499fe32f5d5771b1429a329af34b9dd441fd3cc8a3b8cd97bae236e1361f5fa9122528ca6a742364481097c6c3863e83e80d44314ae97f24034c21dcdda1b7684d027ff83b3d146015cc04d826fe02d440b1d70fb91a78355f8b3fa80b80377e0ff6a633b88ce8232957bcaccfcfea212a7632399dcc41a5514606be3ff48f1f87a40fe66b0f5f4a7dcd1d0c5631f7847f869771e79c139226e9739c3422b19063c1e17090a036fc18db4ec76243afea9b60ec72523b5055faf93f02aa71554f9973d6e0ab6a5b0b44cdce594f1c08aabcca8c325f9b92cd06a276e55560155877ea8477be5b619c22a88a6fe241c5825671a7c6927d24a53404898afcacba055156b8379f347fd4cd975bd4cfabdc6e5d2b6ef3e1b8f3e7d756dcf9f3bd8a3fb7c324cec7c13393bad684c65544612da6ba4d216610f793b935f08a096ac70b2adff95feac6eb239f986f7c24442e50a2b8aa003db46125b2da38c2fdda89e6644b116df3c16c3d4666701b6825063789fbc9780aaa4ca6f89aa9b6a03244c123d0623fffea4e6e79307bf213cff51462d863cf2b2a4fa8ee04edce1188e215c686ecc4a81db12098213333d727b5bb9b075a4e0c53d2bf28d54107a8468d393ab80d5f2918041c85d99c2c4e90a81bbdd839295e161887e03e10830b4c8c1f18218740c500aa720835a057d890c17db97c850d297774f90a1b4a18418b9bec4c321d17449d7115fd7adcf8f165fe8377dac89e64f624c3bb83cf9d94ba0d845235339e4926422168d7f5542c503108e96bbfbd4eab590db400d6d095fa555335fdbbfdbc5a83c6f0bb9b7b98377b0cc3be31cf7e0713f54b5d8ca35c71ef9cf5f961c57d8e675d0f06395035ceea78a0726bda689d00d1d9d318994da5eb73520b600da5628c99fde0cbd3c8fa6ccdd1906d4fe9e8475d6454fc4c067a3f9f9b3d5b0fc69a5e4a0b0002e6e3a108bbcc651513ae818529a7f90f4fcb8fefe3a7d89dda9e6a80ef5f24beb44aac1225fed002271afa29e9a1dd81a8f927cdbb93664f0c900f5fd3ac0f0bfe0ff195a4a43d69f37f569b3d6996071ef9dbfc9fd5cf8acad0cb27fd977a5e916c0e0e2719fae989483ffdb6bc92160d2a27227de9f7a5f644447bfad4e2405fb35709159ef8a45772d2de2a89f674fd4923d9d365315b44c6f7374285f6c045d7536b011f08e143fbc3e8e147ab0344cd6738e1977d0ff05e492ef673aa21f28c3ebeb4cd8a3edba44ada096cdb411035e7d7a021834ef4e5a9066a79e49f78e25fb6ab448e75bf168aa27e9095e33a6a8450b975f97e70680a54f52a0668c40e75997250bfd8795abc15523cb1c216bb16b819cc6cb16301005bec00a095a07e94852d7633a4245ad862b74266a57e3176b6f542282b958b6e527f1b680713ea4763a782f6248a4381414d1cd58437c1638bdd8e2b3b29177d618b9d8c1604510d3482fa511f5bec4edd35907b4fdc6b92da62a7e3ceae9d74fdc45103a0976d2088f214aeb4295cc805811c6f95d413d032cd4308614321681042c834d43d18a78410324fe9ef90f414fecd5950598a00ca08a10cf54233cac0fae95fe1cd1ea39cd7d636878e085c18c39ecc21810bbf3df93206870bbf2b506fec16a31c07cc3108eb978fd3598860ea7672351216da404848a1ad0d40b91f674983d7b8bddd0b060927eef5f17a0ac4bd9c105da6a93a228b4d76a3e988a10bb3d48f7d84509531704be2800563a8e1a40652ca78c14a422c57c600ca957fd9082f74d03ae204373ebd62239ab1e2c65cf9b4827165cb881604910f85145bdbcc98429314dcf8728bfad53bd345577e943b50a144171f8001129240c51cc2054f5c90c78d50dc184d26d642064a6c37625f98da848171c42cc2093542965611683c2122cb101ae04887208a39d064060d40d1c0086628424803538c98818829401b116f6ad305e594725e39c38b39e7dc1202477114da84a3e496275cba90e58c222967248104356bc2fd6a3555982b50d0407d31a668c1779b1391f9e3c49dcf949a482e9f753a5c10051fcb16edf7e180848beecbe700affc972e076c2bba97d5b1a4c2fbc5e81c14a8327dcc8dff2a0e1badd501deecbf1b1c94dea875c441b9f061daa81f5774e18f9ec3c238d9c3e7cc701b9787cf157164b88d6ba5aebe465f7244b08ff2230dd84986f8d8c7c7e1e5a37cb12719220ef24b2f5febad748a5648e9e5cba7f44b5db51f0feea8e3a12f673fbad9af9ab6a2cb5a6f10420cc67252380a72562e8c10890a3f76f386b6606e6ae5b2501ac08c4a9d97e7cb37995e4e8c820f63fc669c1e1a4aa9fe1fe58860d76d756bea72300d7cd9e948d598dca66e9c05f5fbd44d0e8cd2f6d0d9f5cf201bb186eec052fd53eeeeee5f5cf525821604868a6a6aa3eae0bfa30815a3d79411fa476b819fab83ef284265efafd4abc0a1bf01f601ff45d32e3f64557740882ea264546a5ffe6b630cbb54aa948c6114fc43328e0a17c20ba1055282cbead800023f16b2018a0f3fe38244185a2be300c13832e3867037da8c4a85973929d9cbeb7229d910fa57f7f98df2e3d55bb48b034c03bfb92119957a5d08c4ae99465a23eec5ef537c5e65ef2ca86e84280650b2930cf179e2cf3dfd4cc3256a0ee318b98171e2c36f00ff507259f500154ca6528961b43e4c036b9ca480b88d3b61038cbae3dfe3957d270b1620687606b59f4f1e1f5a254a564aa215b2e255bf9351bb655ba8e3a2b2ad1d79a87340c6500ef291c02d5baeb4523cb2582b0821dcb265e55bb6a8608d1ca4c86732ae14fe359aebe118e0459ac96032c84083e4af792785452507ffa692833fbb3d11e9ebfdec3fdd16883aa51b5f23c9e719165c22e5b4532d7d7ada81d60451713eb5a79db9a24fbf57d4e12ba1d683a808df2f7b0351d1e1f717f16739e40eab10c10c6a53aa5258f2990196158b48cb8af6546415fbfbfbe10f90f084751b0a1ad30cd1a8db44d88d80160eb2dc1c97afb0c1ba4360528426975b4320e372d1e5161945b74504a13b4b77be469acf3ebda2fed12dad19a36234da229ee7d8bb5dd2abeca36522c40156d803f52a7b13f6d967ba41035a1d889ad9d78c7105d9a11f29fc48bf5f89126a7b6ac66022fafe45a875ab258916fad7f350eb4385c7bfffb2ed478022d03750901decb52c5a9f5e65dc8e0645a3cfd9688b5809a201f58abe6619a278a0f56122c40156f4817a757db44b7a757d0d1995fef5f1a137478fa31ddabbd5a27fd9d30eed55967daf32eb43c5065e3151424549665b6360581a0b44c167ff22bd62615da06e50e7e5161144440c0115911842689d9999bd2111124a184550462a0b8d67be54b4ff10de80c6348a20d283a296106d04418408c804d41aa24882c0d426178470c6d0d51982e8679ec0c48d975b432cb9dfa7588fb385c6333836f84acbae8ecbb0e6d0f12e36bbbd020f6a578710be91d9f5ecb8c745468ada4a670f59da9caf75bde209ffe32e43adf3558a21b7a062d6870a8ff6d9fb68f2a177d2c8e415e43c2baa7dbfd8d14752b677ae48af36ebf3c34ab3b36b2b3d05a2e0639d110aa9b568d4af592c0a37eb2c6d0438b6ff622ebffc41ff9b5c4b9bbd69cde2308dffe7c275fe991856997ef20fb9b10d86b33d7f896db4cc740056298027c09effd55120870ab00f7f78861ad79f70dbfb180ad75f07721bd75b88eb708b3567d4ecd93d6e48f6dab5c50ede03f00eaeeb34ca3921432e4ec80e8d456e988d8614512087f6e17f791a5a9e70fd75988debef5bb2e06b00ce0895c2fdeca400d115f7b2930254571ceb466b290bea47a9e6970d349ee12e8fa3e27677e20a4854f8adc58f85e4e7b88c3d76c73d2e6af721349ef99ca734b3ec295da59b7094f7138ef215a8807d785b7fbe5c109bebdbe569e116f5f3a12952aef721efc26da277e143dd41f01f541e5a87fee14a149181eebef19477468eb837c56de077fcfe243a40d5cc525d4d5a0a520a1da6aab8d7c409fecf8516f62a767de7f714f7fa79056d80ab695700aa2d5d8ad1ec2b01b48a95c8821b072251e5f54fa42083fab5909523f0fd752000a7e00d3318eef90d91bed31eb9611a2f2280d70057974d419415586ab75a0868470e2a7f771087304608234c615829478e48c1a85f4f99d21e1dbe13582ea0ae7b8eeb5fe93a659c78994bdcd59c91011e3a08104ce3ef4c7383f3fd9b4c371815f5bbbb3bfe8fec2b0dcd538c43baf33dc639ed29cae799ab537c25d7f34b5b43ff7c1e2554a2e5e1bf5e49ffbc94cc1d58d47e36991c06acb5e646a0ea9a454a8c1cb9809309b08a7610377e43a14d8b2660e4889306e2099742f1208a7d84d11c88da99445ad48f8b8cb8cd478f40c06dba280235bfe61bc7385ff64def97fd67d967178bf1bd0dc6a4c5987dcc3629a1d45ed3ec69085fcdd69c51a9f63f37a319ed7a80f7b25d8415282cb8b204145ba4b18a9e8a25d0e0ddfa87774b54e9efb90725848e43fc1901bca07aeec1a4b5a834ae9574410c15d1000000004314000020100a86c421b1602c1e95cbc2de0114000a8ba0447e529849a32008529842c610600c2080803100032033325b05e52e2b6ac05478bf273b1cc876a547901d81ba146452eef7bc0a948c6ed2dea62f4c9784d52eed030910924114fa59e0de1db03f7be46813a14b0483308478a33da0abdd88f57483bd0929767d894aa9d6a74997a1d1efdf20adb8322ff4b9f2db32957ea26f2727881446ec4a0366981f409e52a16cba27c834a97f45c2e8d2266ddfe35961756aafff73427e714d0be498bc34156c2712b34a8c19376272f61ccbb04846d1069abe353cffe41950d37bd97b971f906e73df893077e4af3278a08a15d69543ee8a40d25557e39c1447ae6f27d6fa8623ded381c5db8e2645ed5362123465a0da19a66d735cd0901b33250d21d3a40c601e869e3a9b7e400274800cf624a71aa248b5af2ddda3104e8262d332a7d4534f1d695510b5435dfecd623eec499ca60a19a64b673854e7e8b8351a8c1ecb49f3ec57b9cfcf6218b3142dc652067ee3018ee3f99e4bef8a6bd3149fd698085d9b26191563177429c4204ba735ae485996d66c534f02d8bea5a5f99ebf4df263bed1a7ea3db6ddcb779e5035886f68f9ad01e7e6a411591c11a285e9d386e9b95fe3849b3b131296689b60c7047fd6af67300e35afd4afdc8b1af0cc57d479aaa5bcc3b0495a6ebb1a4dee9fbba915a5e6b9a7b7a83e7ca942115f6a4c7636452c6cce9b2d3121d0873852869c1acea97138132297234ad9f71a7036ed93dce4a3d06876107bdddea8a2497b7a103032e60360b535ed44e2f434a3f286c8db3040a7724415e7855431fd9d2550691537de0b6b9ac00cf6060fe1dd1914837aff900d09e14cbf9b34ce17ad80f44f4cc1c7bfecd97eb4ccf1e734863287b6749e0a597b3217d375eea163f35075453223b0ad0463ef194305c5e3ae9d5d9bfb431a0b34fc277c567413af7ad0516f20f7526b81b41c14f0285746e9244f91818bdf417f1fd299aaa46dce45ec861de8b6ecc22c1f9bb604676a3429910131b10bb7fb15b3b27f806bcdd4dc9918ab7e5c3bcc653d35625686ca8a00dae6407f0e3abf8444079703b0dc69b0aebec2138ed0e46f218f23ed8fe9337ccc1d3174d3f22d25120b373ebfb55aaa7927d9e457a984be67937bd4400b158c16a7582a3285187737220644d1674744542e2491a3f12e3e2c208b0c3e6a31cdfdb8db67d6d421b0db3c3561a3deaa9b3300382a6375739025324c9f99e14007fa8255f8a775a6cd7b7ba1ed1430fd3e025ffae9914960d95dc21732ccbe6af91a7d969d7422103823d92b876a54989366723d29155b1b652a4d0713868417e99da2230a84b62b9ffa95df646bf920c6f5a5a209df264518f62e82fd04b877ec21c699d05a8fedd529a529a45777e211905de71d67dad532308ec29c450f9704cfef7205cc1d5b01fabdb8235f93c6b229c1b0ecd0316a9a37c2b468962deb93a00266bd779b973fb622e9455b014f10e126fde906222f62ed50787f916944f8b3c326ebb16f97c21ef00c3a881b683c196137cd1dd2181a9b61d00cade4d67256ab6c36811520dc8e8f598eac6b96bfa24807d0a526715456ad9dd5392e45d278fa52db44729ca53d27f8c746fdd628e7680b1ac639c0b4479ea1832dddffe9310f7cafcde26b1ce83ac0aba9fefde4f20bdd4a697e5ad76b4658292129e9aa3cf3a3c5e64808cd35d65d1ff1831043dfbf4e30da8f62e853e320e2ebaf6b4074bdadb306c5de1c6450baf472f59d1f579acf9cb60c940737e52f9529d9d54980e634f15b3732c56dc00204014a399d294e26a748e514f939650c25c9e4833f71bd3294c0602053a530d90abefd540f85f5e707211f476e865f51315c05ff6706376ac6b493cfbca1cd613bb8704c1c043003fc100f0b0c98d6ebde89a300ac9ff3cb4d9e179da4e307e3564725bfa5470c94d164049b5828ff385689b94f3f892c0f8a4de70594968634ea62f95b603392e10f934458131cc2829fb71b91d5ab7cc5ba8fdb090bcd6462d6d6b7112c209022d00c703b61ca3f70769d25dd7ec93c91aa1a3aa08b1fc2618dcf4bfcc7614dd6968549b6da939444e589d17260dca0d9defb984bd573771752bbdcbdeb245fa129d25aabf4e755a6d2ffe4a6070ead45120df5eb2ecee3b1116a37fd61d0bde355392efaed4f917cc8029f80aca4a44bab6f04824e7de31648a6a7f1bf33665d0fc8547a8d0cd6d5ce61e82974da717122f615cef89cda9cd04100a3f4fa2fc88e870b2d10d7a4ae535e15adaf48b6941a754789547fa6a793c16fcf49acc54866c396dd637c756f06d458caaf97b08192b89a957564ace6fff982c7a17a2e31c0e4393eaa2db87b007be308595948fcc286d4990731ef073d9cb7a3785dc08ee79f883b352a2f98d000978449cf4f78fd59966e09315f00af3fc7f9b408bbfd74fc03ae091c8e1ac83859f9efa1d1f78f1e10db9accba489de6a94231165a627439d5e81455eb98cfd404f86fe605e103c650133029087cbd2aef653dce4d82f41264787f5bc94d02006231e10dfb31802e9d52fb3d96277c0add8219480b9ec5f6b7a21d674f51b46d88d0354a802d758d6d430bdc4d9050da6b49b7f4b30ac0b552c13985651d3339aa0c309281050b4ac8cfea394236cc0da609e7e904b2159195e69959fe560b265d7682d212ed3065409136028c1381a0a6296e0b1c7c812f55d46b07b0fea6a3c346c3c451c03b24b260f8fef63aa97325c0246fd5ef1209533ef63d32cfcb5606d063cbed33632929786041d91eb23c1b6ce4f8538f013c0cd045ac3a21e793ff965ec171bd0a0537e42b9642b34c221238dac2a9bb72073ef45cf5f82a757abd14608ea9922b491ad78ba3bb9fc7405c6077e5c40193d65d9d5ff7e750ee19d2e361655c4e5dcdc4e5f09357e400891556e5ecb61c11823acb329a7ae7e864e5c6c8c23d75f74b9b5ab47844fd31d2aa0307b2dbee95a1d4c53cd2df7de0159c3283ed866c9232018ee686577dd0adadac3724742074a9a5f37a41fab171400a989cfd0c878270414f62927f78d20875e2028ce5acd0a66db899a666ba5aa361de96b9da3f26c5baa4cecb276a4e5b79978f005b3c25b44f3cb2edaca9b53fb08fc9e1084eab2d037ceca42956fec090a18a154383bed00d6a7d03ccde11317107e09c2351e99af5eea103af304365ec0f94b65a30f7d228c5bf72a36afd4c24c1efc99419f3a0c0fe5db33dd2358f60f78a87c52402324e1dac9722d94b5dc62f7543d5a09e03dc74cb3ddabe0228c297a720f54a740a663202a68d0e04d1367602598a391c92ff047c8d34f4446f95dc3bb60e0e5b86a49ce615ff5df72bb576a04f11d13b706aac6f23f4c81e540d31e5c0cbed861ed42e1b2a948a2111fe46a8535d1f168f9de235ca0c1807d8cc2dd870b0705520a0fc511a2fe5d442972e5d88a1ff98e7965ac31ca22d3ad1ab59b99b12703389017d6c1ccfe2df1abfa3da2c94e9e106b9bd9b64d112e9a703b389c8b3c9170ffd7ba022d5255ba58b040b37b066a79a934e97b8c14e99cc02c64d5de8051d715edc14114f4821039c4a7103750fab7d994471a32b9934018ed0c73b1637cbdbfa5ac8276e26179f5a8826018534ef1b93aaf04b00ea485c9d93accbeb24b6854fa6703077bfafdc655c67cc7565c94a39ce94922c5d64e5ba933bbdc1727df3818fda7d1cca32445f04abcf1be64a10f3d70a47026c02201fa35cec8c5692b88ea2ebcb178a51568d406e51b3a418c592a246fad460028ac0519be451405a592bdd065125d37818f459c1679adefb20963759cda88b6f52afdc628c9f1bdda3471d5b02344d492da4a48a83befa10ef1b8944782701b3c16360662ebbdaa6564130847ac848941251c5b41301c42a43d12e0c6f3defb328aaf8287fc051846758c866c5420f50325ce3a81b8c038780f6f81f11b83250b1ed750f0d2732f332abab1f854f780c34ec4af3db42b048ad3377828785e680d43e4da611d579e97de4b8b2c7c7f8e4caefea89e321b5fe868c617e68dc3ad178bacdac9c04a71a9f4f6e0005d1755752d04bb84a239b5ff2cd50865596899ca1cf3e5fe2238ad0d87e60f9316e150c8d873b08f7a08cc86576b2c79d5acc09b8adb4009c6528a54041815ad12a0042d1a7f5e3dda474554576152339e9ad1faf866494b9dadb37ced17c7c4d0ed7c8ada71d3202246697637015ce7c71d3bd6f1325abf119eaf2fef79e2c15c9fa7325a1a638fbd5028136684fc7e76092aa31c3f3550255fc22c9f1c89817236b743bfb573262098a462b22a4596de23e096f79956d744e9026aecdd18fe05af37ca753c6136534ab3de739f19054798923a4bedc1984f5e92e18a090ade04e7c8e8d41171e32d38e41514000bf47d3b57a52a07405eda8a9ae0bb5f2f329467fb89c10164176e2df92ac84c578c384f9bed282e0b07e3e5a46505e2d0f8e6ef0afda3d054e7e86908b1be913253285f1115b4e9931aa0f7c11823936d2cac2d05857e0b4bc0f558f2523a93f02dca963fb21a3eac080c54cda404946b72f727cc8fc6990d624b86f2d92ebc50d73eff95e6f3d6b9774a943e9c42f027669b6198a80fac4bcbb12b6df2fa333fa408dcbaef4016c3860b18274903728e23079b04d026968e5a3a07282190b052f0fcfc130e0d433a8eb485c318bf20e6d88ab6205521991bbe3adb22fe3c67eb88676c5fad8b9b3eb4a4fd405bdeade12df9fe364edc2114449d2620257ae4c46d534e2a41e5d09707e814995c550c7038d47c7249db13c408c308f782bf85f55d97f897e37a61e813f28e73824720d7517e9a17cd5320f8edb31ed5980ce787d3bc4c490b1067caf6c20448176731f37f323cc8a1457c04f7f26043a3f9d1b8d814e3c62b7c454b36d158da504addd16639b109602714fa48cbd1059970e4b7a2d4cd4e07ea37dfdf7373e1a18fc6a9d5b31497adb38cdf641a8837ae31fa397d79a303731f4ead154cc48aca45f0bffcd6d64684dd7dbaf5c7b87b6a04aaf049400d48c1a297bdf2621252653aa9e6638a24d39c06fbcf75ec2f87d72b15614142d422db44cfeeb8a15c73890a5b597664220c451cb0524579543bff334149951337c918623c4bd5a4b4f2327843ce48cfd78e24b716f18e2dea98361256ef990714f5db2f767dc5343ffb88ebe8f7861d07c9bbcdb843b66bc696174c421a27bb6e98b1b114fb515bec98854ac57d9040de57e57130816135731b67295d3923796e16aad075132da0947464dd314b4b2646d4e2ff48089b4fea90f0461d6b4cbfd8100e64523a02773ce0a3fab64e6955b2dc76ed4eb52e7231cb328c714bbe4ffc9caec215593343167eca95d80de326b113b0f1352add7b31d634103b7100469be7264a03e31a9e18a44eb0432510b6ebc53bad80323f531a8108c7accc67da2c573fce609ee4bd78cce03f17c6e24a31f37f2594b5d1ba0d4b573b1a4341635da765363f2840c748850c44c5abe535e0cafafa284cdd83facf9ea77ab4312dcfbe7079430c314c669efc11fa9730d3cafdb5a700bf5c1dc58443b76d2f1ef700f9935a57321f382e44e6b92c3b700ccfa43e6b9c4caafe48b19672062a126e26f3092168983a888cceb61799fdcf46b207075d13fdce31ac66a0905b6bc7fe3626d19a51b461b3a6887be4c55ef37f25a431daaedbdba49a0b14e9bc0ce4314c2dde4a2f7e8db506d6655a851ee2f2c350a78a5046b25dbfc326643802370ece24a4e445d7deb158c71a9bcbb02e6e180c00fb9eb3859c676d9bfb5e6415e93505454adaed082eb2810f364a619b37a69daad3431e0cc185d5fd28294b4e01ceda81d819f527f032771e97324cc8c94387aa6b67617ad4cfacf611eca215866d6355b14a6013cb2dea0c203d685bb19a6ee84517863e21c9c4ffbe2eada1ddad0430cffc8b49a6f118ebc3cf73bf846dca495129d3be32f45be73d2b126c500fd6e4fac65e5f9c7ae21d922970e43fdb2c32d507c99e60b2d67a5efcc1125302bf2dfc6217123bfe65005d9055ca9659c0fe162ac480015502b28d1fa26fa605e6a1c409096c70e0027cd9d214c8d5c82b8ebcb06cc80c05ad7e8e18cef5974e9f07032f889f8656e7a8187146f73911058b549f4d673f378e07be6dcf456db0f6168fd51fb2916d6081703634833e860f601737315c6ba830b14a3212b7e30d3b7d9b7dd7ab340037b2a9a60715e175acd103c6067dce1cb6e65127357410a9f6bc4dab2980c3bb30a911623d193d1be1162243e4447ad47455db0e3eb7e036f1ee08bc825ff2f43deed1791a4c9484525e3f4feeb73cf85a3cab289544c588f452e74beb10ab51bde1d8cdf162cbe246fd0a4958dd75713cccb3ccf6027f71144df0263c46fd652f25a15c81fec0739798a593dcdddd56db573a0aebc1556b45da8f826de5bcbf40b3760f6c8ae86c870c034fd72dc27333da0c62e04b858c1b0bc2e04896a7edb1bc9716f065f681e97724d7b653f8952b3ef9dab0fcdc95709064aa66041c19153ddb10183132ef5700c1068ec7669da674b56f3ee2111fb33e8d02ee44f8fa49353011cb0ecb865e52d0116480b9673d063f9ca93d33cc429259b33dc6ceac1d09e8bd113e80957871b327b9da37c9f1a6eb120b46ff416836669400c587eb62c3762c35744c22494c6c97c0447ca67b6e60c7ce7aa7444046e40849b290bc8b37208c395251a26e843977eb2fdf64a9cff1aad991be60d5a17d1f74248813c77863f37420065dc4886ff338bd69cdb46346e2d5107b014f6ab990e7169760cb9b9aa42a19f6428ec8fc640a8978b1564427dfae96f63602dd2646a14efb2b1bc191d364bb0c8ef68f1301de8e2edd98260a2a98034a2606dc383fb36e208524e19d799c18a913ab05ddce6e89915e29a26df4be867b87379d22af9b0b60d54a7799782ca38a1f390f5da577087def7a70b9b501729ca66a08e7770fa695aa0e0789415d7e6dc901ca6b75d769a52a86bb42d8de711fb3b83c4931f07638bccbc113c2b5de1126a45d0ef455427a22d79c5c22876cec885b88e80e40f6dbe3cb852057e0610bee421d5b02ff0f956a1a027ce331d581190187118a7988e594c16429df372c76704bd81951bcbc5e109fc56336b71d4caf960808092b06d3bf7301b4839954998e16f5924996e79cb5290c81b96a34d6e067f894cadafcbd2e2ca672cf96352e6064ab72cd3c7be6aaf35a9896b76ce81ec1afa927a30c2562bf7982aae4fde3ee893ae1b137d9c01cf19621788ceda0073592ea93b9dbcd4eb9f8697bd6716c6b3a9cb9efe994f1924f8306c34680f71042b0804f4dc0f600721efdea709803520bb08bea44d79e0aae25534d2e58ab3e564ed356b5a7c4bae03fb3e8683e0e507b37f7e9f1008ff0384017ea1ff45f2d46b725c10ac1b457e8c0dbc245d3b630eeaa26e7177c92c0d3b02c7a364e3ff33bb0db938f51de7e8e5933ad8ccb64613246d12899651599b2d35dda96e99a179bb4b15acb1a3508d1f7b556408691c5f169123abd9c66237443d96f9d4d148bf5c199cf283b5db45b0e7183e884c43865c7f3567d2f7e937198ef87a6266697e3daee88a68e2d374eb7b4636acc77b9e7a8f3cdb7a42fb43b1d531b2011d8aab383c5ed3def1059c01245a681efb10486e94570191376bdc20d663188519153dd063dfab66bbd551373c0672a3d9487d029634cabc5876413b4c7785c51e8ae70dc11f050e3b1a64d02a683094124bf966628b8e068390a815f7f79358729a59ce44697a9c11d2a43a5cf1572287d3835614391608cbefedccfcc730b17c489ce03c5d9954f2f7a24a11236a346feb1d3f3250204fa0eed4e8e8f45bc8e8ba9b10aaf9cb8c086137a75589328d3657bcd94055b85de9971b430508f1f824e7b8fd1e969caa6acc22aa23fb2e904044239d1779a0bccc0f6525acb21e760b624ffe5a645388865b7c5db599d6096723f1e0a11a8109a734ee1f21e84058bce70a939426bee69a1026111c802593595c890746b195c18cd42acf43986405f82389520df7eb85605b81c3d5b4516d771f22a6a89824fb651eba0a60b624c77ccd50a1249b6ad692183f25917c96e447cb60122b5b911c774692130347d8deddf74d9281a088b929047ffd07fddc94f6f59c8a249a7d0137f038bc0c7ddb825a6351636ab377a54217c148ea145fc2545f779b4181b8403fa22a92389685c9a8542682534c26bf4a1847962bfddd31fabba703c94619ad48e4c982d5e71643cef7e1259a7109d9e5a7d281a0ebc4347120a4244192985f3a5ca245e0f8ae86700ed946f00a02599e9bdd1d5dd3be72c471b0f3074a15461616ad8f8048508354b727220b2959104d84b80185b159302858c7a139d2b17d0a32c9594f4207ade5b51ca0e30eb67400cc8e85b50a5f28feef9b5be983bb277e40c09fa40520957205cbd7f3e993383b518d2eac89c107aa03299086ab82c94002590122d423526b08d0e6fdae4b4b2c7ed2b83da3428fcd8984030eb5bfa2a093a9203b6954ea25cc151d73c182564d38022c89a43f236e86995f82b923dc5ebdb0402826c969d42fca782b95fe01ed3379a50239446922317e67563af0033351bf28416815746cebc787ba436d66ba23a3d0308a2346ee384b94bb48ba865127928b9d220b48c97efdca93fae75e1433df6f9802bcd50becb760302e69177285054448c61ee5f76b9d570f9066220534a11d86deeb35f7ee18b9d82a82e06721c1dff1fe039cf0faad120edb8aeddaae051777bf11aa57be06d74615d0772cd1783234d8fdadb3743057f6422e6ad6125ed2292834d149219f63e05f7cf258f0c8f5e5148409116ab83d7f731cf02a553f81887b3903eb4fab00aeebeca8cd29366ecb3c91580ea15adb83aa02e02e07184df61ef5f15985e1ac6db9d976616ac85673fb471c2eab814a2d23c4f83549c1ce7c0d8dfbe67789fa2b0b09ddbb01ec673e709059306244e9db32a7cd0f4f9f2cd4658c68a1fdd8e6010e1d026bce66ab451045e18f861c0735c3a60d6f2bb220111e90223f5125166e66282b72db65c4f4f581eaaa7f2b79fa23e0a33bfb27f561346031fb0812a2a1c6155af99e406a80c33baaff903441c69d96f0a3904c56c7e35ea3901fe440d13c6716121fa25d7e49448a88157eba6f650d50e02ad89d0e8dc02ab765488e049a1e701afb1061de9b4390aa12428020f21bd9a0a23f1b73bcd8de32a4d076c4ed6d0edc60c52fbc1cb0c4cc7850f1d10755faed489edc0b925868d0981540afbe5ba5487bc28d6ded347b0d0a0137d4732e5cf996161685018e3c7d2b900330f2667e9a82bbd074b4ce64bb85e6f2f3e28785e6b0e2b99a4785c3bf403eca880bc787965b5079e63f5025e4c4b3cb3ca4d7dd53b4c30c0322b1370ce06fcf188aa477bdda064c09cd63ccca775558d45c9d127542f3b63134b80f82a53b75091b826dd0d446feaf9e97eafbdff7b7eabd33366b5310103c652141d439f577892f5d0eb154eecc3ade4eb8ad9c26eb314d8ea205e12d17077b752e21292bd69a9e3a5ba9b2a786956328eac0d35432dcab5b009d03d35f7a34fe39c42fa9e49d5763a734e8b0d3f9539d1bd0c3288bd6c26c4d6a7c202286ccf43145d921d206c7206234557603067703d32c5257bef912083ccfbaac09bb3846417adb8dd823a047e19bb001633d6591ff6565caea25db2121e10ab7a7b25ce6b28b05b821eb41b5d1ad819fe036fa53e8817df628b4b7681810fa8dd9f2eeb6197ee37f12dabe44791c2063ce607c874e79683fd441243d54144fd2a40475b772154dbb2be2330df1fff6db04ffb82c39c8bce50ccead992c56c6f09dc36197221b7fef7ea2bea65b7206237d32430578188a4ca203f7b1ba94b012d16bcb1485d754912f53749c8d760aecc70fd7f8a35b346a6efb530aca2d1a7bfafc3404170650429b07f09f72140ae078798680edd8071c8995d0cb9796a691907975b6ba8168d7e9973c287397dfa401e8ad47bade0eeab76fa47a102e707c147d960aa870f99f494c4143e571ccbdf32f79cf6134187b3170f5618ce44d5be290d31d9a19738319a00e66d451987c1acb5bf2445fb02b5771193aecdcb086e1281e6c15c8c13c7f8b9bddf936d8ba73c41335b3807f493a4e581e8b7f8b066c6405e0562d0f9737014be10faf2b3f36dd0fac95ff0022083e00425cb96bef9b6477dca946fe1466c367662aac4e83c2708b9ebc283e4de28d92316d62d335ea005fd531c96dc34d10e36af8e8b3d8b6a7912a30377c8ef8d0b7fa914b95ade0855ef8457a313ad35bac31ad48059abda2ffb4895598d1ebc2bf805627f193a4b2bc61f2280fb75aa655b0e427a14a0e79abd5ff2344c24478885eedfffd461216c5627c268089e8b5a16a0e5a41027543e0fa31480c212ec46cda9dd4797edad82c9fbf385825214d9331362824742a45970dfe90b3a21d9ea124ec478f86011689493cfec428f9e78477c42340c207ccfd9088861f9897815185850117b8fa94e9ca4b8684746816948d47485dd40708bba1c218e0da74801c05b190e0412c20eded8216d4871d6a4b869e65a1ae2462f1ff983a71b757c8c42a8dd375130c6a32121d22bda395dceadca9d2bbbf0677efb43fa0ded84bb0f40aba920317f0436b35f96c0d76ac9be17bb87886d4c745cfd416c4e1b10aa370ee30174a760ea796a2d265af60eae5f6a6245bf28a8c3be279d4a1781e7e1893158f133ebf1d0d7460817f465765a0e545c8d61b0d2547cc023e62ad2ee741798cdaa218bd753b6595c6076c8d1f0da89589ec7b07f3671d098a0479b70040fbf3437cbbb81b60433434f2795328a0e15f58aa42a61654e29332cfe10d6f25efc1b5d40c0520b94344d9e463a501b9393e6cec1fef15a55807dc181bd0b75faf9d0324c9c216ff41813fd617de51c93583ec0123982bf6c1749db84152357d2ea545087d48b89a033f4f54a8372d4ab56ce3b1ac450d81f69053ef372d379a1c7a0540ac7a24114711e08f85ead3870bd92d92db746df7cd7ba24bc6ac19eb8e1a08caffdeca19502f4c90337077113f211db1055bc0dde8fbd8d5be02aed29fc06962e91aac9dd3d8858374f40dc06db90f9a9e588e6f6fd90e20b9063e57183cb544e726ec5a94a02839308859f2cc3a609639647c80e81b0ce86376bb8b1085080f705295010f1d3e3912634f6b603d158acba7abf7a9f815e919988e16aa9020e927da69bd4adeea9afc705d5735e81e5adbf47643e88f9629a46b3971299bedbf2e05d1c0ec7d4e7c3219b256500df450d9042c2b2db7fbcaca9fbf282514d673b03514b03c706504d281c1fb9af02419aae94e1dae558341596867ad56a2e0d3a98169499b61e9c095be1ca01b09be153adc014935b2ad29b40ccb45f52c2aa089d9288f5409038acb2094078045267b96bc6596e498ff730a34f6a4fa6e2c3828de795c649a2866029e009382fec8a0d92e90da0cda6c2b84992ff75dc7be6b8e5f39be7c1ad5c4fd4ac5704bde5a2a0ed77edb3cc47779770ff44ac4f7be53e739bd0799a420f46217d1e6a9fcf876111c796708b2a508b37088aaa3975ca087a05ae7c259fcec9d83c3153d1f8f5eea9f17a8d6412950c45c04e91c8738793e8e21832d7a2a2b52755452dd17b53b1520d6868a7119f94b56ec3bf9ec27754e1f8ec30ee56eca4bdc4009a60f05d3618ebec78d266e4da293bbec28b05999fa9cbc8abf8c2a95634d1996c1745d8654e0580d41d48765eef94679790861f22f428872c64180283038dc0c741573e28686907140df7d5e299e6c5b8a2ef05ac1ffd1950ec858bf4780ccca2bdf06b42c20c51df415976a08fdb1dedb0fd7d021e3e1c4b2dcc5d5b5d77a24e671260a4237b29981e0a5c43558a1b56e636f97ff5169966a2ef2282a1ee075b6e00431a936e130e9a820a8d9ae34191240e7102eb753c8263f83afcfe1624b4065d3cdebf60c476c1b852155f6f8fcc3b2bafc64d9382d1cbd11d6e59c36f1272adc52ce743aa3c3d9c96b99be2f071e2704b32291bd74a80c2ca9ea9a26e25475cd658f864678efffe1ff456d9276370dc58315333e6096c1f3ffc30a1c4159a64c8591a4b997c24c3a32ce7c1bdfe84eaec3a07d0b7b6fbb8e374864cb49bfc3ade10d9babc5fafe88279ba5ddb805325d88d7bda0e4f229cf694e2a2187f894df0fe1465bfa997d8e10c66a35ccac58dedf0d3a0010b24e6a2c12867e83edcad13cf7663ce119bb8d48c0c977e3319fce73a6d3fb6e9b10a7fd9893e3d5d236c9cb03b0b0ed3680404075212903fbb664189ab5b9d3c5489349dd64a81dba502dffb2f9ffe2e171b36f98eabe52934f7d58d4a893da075e9cfb61d7f80d53668f764bbdbb2a4b0822df2df6beb583810601d36bc0c180e3683e3cbee52cb4ded0e4587c4e86d9237709c9e2c93c93e133136c4e3188bf25b8665368e489bd87c0e407faf38ed0f8f526d25d073aee60fd01071defb843bc933eaa99d4c1a5bff5a650ec5143ecd050a360f9e75cddb7f711fbba8ffbd2df4e509f0d2614d0ef85602262ee633725de6d102101ef6d8080c0fcbea500ea1fee0f981b53ffd6f77b735b2a5522b212d201f8a7fbd79081ce748a4f95779db0aef77eedc3afe6fba12fe1a6414c2f0ee5133726827bd1dd2b788f759cddf5b215878a2cb3aad2d420a6574352d733d389c02d19788e4bb9958bde5cb86dfcad33c24d195d56728b15614ea4a4553e290606bc8f0103ff3eef8b7dabbf1cdeb0db1273f37de761261bd966393628017534dccc4f3204588bfbc37760dc4a263944fc2b35a787acc4f6c6e912c458a08ce238ca2e104b6f8d7e7d43dc03bf26e3ace8ea7170e18a55256cd694ab3f32a3d838351ec47add1443284f26f0f13656efdeb73ebb211cb726b28c5f1c6519193212b8dbc8042873525d136da06596a3f4cfed931766ee502e6a1968b5ad5fae3e23f41aa4eb59ddbdaf57b75606eac5d2d8cc14adc5be57b75738d49248e2e611e8e5d88034b2aff775df9f7d36f132dc2cd385089ac4e61c331d30d995144211ff08eb37b85f8aba618c6d3102971f07c0dda6646de24e3f92fa323a029611f0b4f9a5ccd6e14c1429e4e10948eeae99f1b40e460a17e29cad00a11d88f1a4fe7055e0131a7b2091cebd4d2cf1e0c08264fe128cfa2f95a2910b37d4ac2c86ea313e45e02bd49226c3ca7c0e8e3573b788deb087f94337b62c134b12a3291de3d35848974f950ec4d77822c8d0fbacfb3f814104fae5c3c46bdf0907617a07deb27bafb0cf0e82f2a103c0daa6897e9e017d988b103842dd3c258b4b5e7a99f77ce3da4985507132212e56558983ea53e23c52ddb4fb149daf24cdee054df3e3f3863e40450f2ff1d892306f934d98e029fa929fbc799010883e7f5968d932bae83613f008043a3abee220b67315e0bb8c6a89941de6d4b90e1664d88cbbf4c9a78eabe9ee1809fcd8b87cd61e8caa50b663e6b308921fe509f29bde6deeabf577cff906883ec57eddca4d29d5bc4ac010ac5ce2a14ece0fb834e5e18b7b26ac6bd475119cf9f0421625071817b146c5a0e751b952fac022192f78050370ea198513055291067c37b37dc3b51042815482f41dd007a5b7c80fff57c941e1d7b1f01f0b196257e536541f3a29a0723e718a19a188aabe6a20d630a466cac365d1f95be9484035d100b48b29e141afa16df78d8e3f0f1c9e534489be19d2ee6389811e86207e601fe5afa1824d730b7665c0ba55958d68f7af6a7eab9ed802cbc32163a2418fef7276d2eed8210729d0829d63b1e5a5222f55dd2b392a8da58b34754f3606cc2b723a5a3ece8f6389baf21c95d91d4743e1666643ccc15969fd5141714b30a9a73d647f7dd4b4b6ea2380b9b2a33cfe202e2ce94f615782dfe4f4a61c0868be12d6debaf37318f32d3c59812542576e32ce58b4aab773f2d24ea282acd03fed7e727bb407b2da611f8c1f59ab4ac8cb515a1cd96a39871e5bf0cd7237665f2b6e7e3a805839e3bbf2aac6808e8b0434eea516d7255366e13754cd3cb6439b6dd03a5d1f62aec269ac1cf670d4bfcbc297d4418e823e8d63f2a256a142a31c327f3e6ee40368f465e07255484f8068a1a46f5401c78a17b8a4c0c39402de3cbd71cb294f91c5a3d578fbed0207a363d35a8bf7fd5ebee5f7f3f5737adcd4c7d390e04a24b87137b36a74dd91ec62d197570a677cb4bf4b626056b51ac28632302761c27f6e8f4814ba7f88ff15fabd97bb8a4f9872cacc1e970f12b118f235aabd43fa8d5a5fcd509abb03d46c26b300ad24488c9291aa43a086e0180947840f0f5775e8c9b4e5af33e4897857875e22c35de80b0d249c0faa929f73ab4541fffa518549d436de8caf339b1f3a6456b35eb199cb6a05c82fa9e96b09a68f930e635e204ee9038e280094a783348feba6158fd8b41e3f8c66ea32e071e066c0b9d048caeccf5be097367484985fc5a843d5b0f001cec072df05745d47957a68a1121b0a7f94ff3d732f865b7789400641ff9a545f2cff2a750601a287c880f6b7276af38c4c7582acf5f41461df3fd5d2295e864e97a1533bd725654f94291737eeab7629ba4750f9118e8b131fd0682a253097fd78fe4267b9662cd7acbeebc82318abb7886688a835b18fc85b4ec91ab0207a4afa13f6ad192c09808fa5ceda217b17b0c8d5e7e20ccc7088ded78678f66300a2a982ea80f0534290694b16ca53e9b8e389480e7c2e2d733dc9e8a2378b7a3157a0bae8233885003ad7ebbd501fc96aa7cfdc08fe688c06dd261dac840704b43542b9e5389422846d5d27d1950c6a40e800f03b07900fd9c6564cd312480bc880d428ba8fe4812475a41c3f301b4af8f19ac43d371687a11cda8fdc87d4868f43c9991e94a9b53c51fd67b82d559c24451933eb22e9dede2e0946d9c61cebf2bfe09d94eee6f6e410c595d2e770fab529b2a2f9607b9b2280e320d7d6b5ca1cd6dcf9850aa300c5714f4dfcb6320332552bb45667b9aaad4118faad946cb3bfa1c3daee34b99756c7eb681d116e418fc697e0eda2d872b2962701caba7942467c097f3bba2068860181e3f935d04292c19816a4d46bb1dbeccede72b1b00474b1de160731dbb3029763efaf1dc8c5c6a2ea43e15118896686563705eb7c364d9d800912f1447dbe0a5ffd5fd92234eeb1e312132d7f1c7cbc80065252137e7f842598d870d800fe2c540285449058c09ed2f1b7f5171e8ad80d18000caf9840ed78eef4e07914efc0b3829fac53f21ce40d17bbd8c2ffc9ace978909d5dc244d55fe03bdc9e1844696b4f4d801ae426b0ce8a86ad3e16ad7fb324039be10bd8162d933b09ae8d9520be283a500dc9353fe8898b70bde56d449fc091389ae4cbe96d2c5ca8aa791f838d40dcce19b56ef6c92d88d79b0c9e7ae87de26d47cb540487b80ec513c7793d904038891068f0cf1698819376fa8b352ce56adbec09293568bdb4e099a58c716ae617b69c78c731a7038166a43fca05c2eaef19caa4c425053f957a47826175372a125220fdd58ab6f0d02b12da8b56926e04ec4a8edfe9fed0321aadbdd501a94019c2bb96fdea3ba2645d1197841b2add2fc5d5c837b3334714ebfdd60ea9b6d021fe7fd468cf58edd8c6ed1a90222c9874f3de69a26b74d322bbc8942abdd1fa622934526a0746b69b2483032bc7a789fb33bc94d826ae63769222b50a0a67be7f804ed819eb556fb35bd260cb2857a50cef32164bc6c414a78a1504bf3bd08059109cf57e9611f55934e1179387266f1ec47477506e326f7dab99261d568f659379fbee0cf38ae81c0cf988e250466cd033a70de8dcfbefa0c58c2ef707448cf1aef0b7be9415b08e1df101fcbe2b49d1ba5e1c22db2983ed9aae7a6502af143a479cad1c3221750fe03ebc24057c111f856b36dcc3b01f127bc283c69a54ea4b423767d06d7a535f4c51d648eaaf655343005c03e4713733f39715467c129436c627b526306527dc8d965421ee4921310441ebcf07dcee60d92a87e0f3c11f1d436681e176f8e81a56f78b20e07608a114653800c8241194c14217bedb270d0a85e61eb993a29576cf77cdaa2c79a4c9b5026e1172d0a30a9c57bd3236a770190846d0be519b60d058300a18481c07cc0d342691179e2ca010aead2ea8dabbb103a2a14b3fc8aea0f9944f75cc2bc48cecbcd0d254f89fd473f2f19ba1c40cd0e2d26a1b6d98fc92e3d585aab68d2b38ff988f7a16c9064f9bc968137ed1c73452c009a228b8ac0d224352ca052617734cd24c610cb1492250854adcf81469a4701f8d0d02efc3e944dff76e86efeedb3d8c8e9124b42fe6c81a946841abc3490341ba4ddebc39dde83466e3918398f393d66f256664bd89cb8b57eb423e425035bcbb8486a8715445c80fcd6c8dbd335465fd4530abb41f6105525ab9d83cfe30324fece74f59123eee3fef4f25fb25a9189124b55256fabe9365ef435e16b48bb78e1b75fa6a0ee1e4c383514446b4d72e95c2e36555a3cad7001210e4fe7a67c36cd58cde12a657b1402ead77dcc32db34c1dd34a3628c9b518e5c2695c437d9d3e60792960a7ed673411feaa3cd07209dde7e6c0156283eef6dda6bb9aad4c57dd05c1792c6b607825ab0b47e4a9e0c267748328477920056abde478941f12b0a8a38fe9bb5f695b84b82e80f24a74f13e96b1a8e0b255022170b2eafe70d3ce53f65c9afc4fb8348b90598a9617b66591634b525657e3e24b08710c06c5969bfc6ce0d24ade764b4c2d857ef0db3e50b5196f1c5f1a827ceb5c7cfbce58ab28d37ab1dc9ef3a0c8ecf252b806c684fd4f75b18279bf812e51e4350b4069c9e2ae22dd8f181cad06258a8c4543d88f35bcb540c95e13347d560a0ab67a5a1c135ccc23c490c7fa3c3e934020ab6b3652fb1c6b5c819c514b582ad5a66c4194ee2a5f4edde7d824fb48e8371ca629667b84219cc30b0fff6759aac367349d83ef960ab6865acb24a6a7cc7eca1e8452012f01dd20d2b467ad84f033c7881c262fbdf4e60c071c112a9962102c8002d3aee0bf2378ca56f129da8bbc9c049330010936b92dc63e9f5a21b998630eb5882e2b8bd01517c0daaea8af9aa2b2e54dfb53ad7852c95c3b32a3a46cfb7cae9fd5340afb0171f63ae53744da451796bc30f3aba41f2bb99bcca0099fad28ab7a813f54677c639b4bc962b5a9b77b24e0852a29e4a0b720a926bae75202b9f2a44d811e7c823b77d8219fab97015e10d8e549aec085aa7927df32b558c02fc4db9c5c7b81f5f3d9a29663c3cdaac81f0f1c167405ef2e8e2abba60a463ddf80d1337c55d133038429ab6217a81908f5b9b9b23517b964a37fe090ef1efec62ea9bf2a14fc7e18ef3165dedd63d3f1bb2c64b925b6bbf51fadd04de5836e74ceb5cd57e2cc8a741b6d6e24145e159d1a852235366816b938492f085d98bedfa8a955d58c7cd73d9ae548a7e5ce6d0c8b7291548280e3263408800231299049872da1e42116a7bfb7afe14097301e2d0926116728eef9db938d209d888921b8aaa12993f6570e728081aeb50bda2c6ffb5fe9e9d98e33bc7df9d952a43e140d6a3b06e76220b769733d9b81bc6c2c7b74e81c33fa1b31e0dede5df22b7c189e1848f704dc7051eb83334187b2112a43bc0628f802356719b0186d1caf84238b2af6cf719e61a27f640b3ecb06d33611f929f6145050c43ef8718cb099ee9ce4ce008bdfaeac68b59ebf36024393e5cf679ed5271f8d53539eb40c316ede5009962324cb1fd7a8beefc1ed507d97174cd7e7cc5a839f133dc90d0fb171a8200c3fb463651dd033dab012b34415ed13a3afcfc25ddaa462d1420c6a86410be35776359938bf3ba99cd195eaaef4709754cd6000c7f0e0ab62f49a92117f2ba9c603ea143f24ddf45069b263db0fdfa2f09697ecdcb2da448f9d53e0c029e068e539b8c2dfaf76d382f9ea8d95304f57c5f0171224e85262052032ea7c2f1a0083bc4822480eb55e2e09b504a5a8bff361e3378c38fe8c20151cc774a8af9558cfcde866f858e6459fd3608b1e13db70ad36cce5298769b27a8bbece2c99dc99f91089a506e36e42d6baa71d396735f3cb2abb0b3d6250b83c0528c2b412ba00be3c95ce0c83e50ef944f05dcb454bbf7c60ab8ecc2ae452d0beb89693385bda25e7f87e19a0cade78ebc2ccc1416076092a42edc1b52ad8f1bfeea5ffc8f91a245cfba7bc607184bdfee0033361d118fe7371b061395de9c712bcc649b4a95dbec8a29ed3339f83d5418484730916d243f7a3237476e588dc8a5f6c72e2d4aa88b29159d5fef08291ef07444d588221a63af7992fac83ca35409e09c5bc23a72ad641221eaaa098c90cf6383dd0219c6fa8a6efc24cead13fe4999642619c61f7858b484264fa17ae8da2ad05e86f1a50b537f1fc47ed14579342f1db00b1b85d2cb9cb5b53590a0878cd7423866961ad5b776c6828fb819d78f5a2ff62792a53e8ce6d2ecc94a3a1cfa635b8bf9a5a3fb9e61888299662ae648047151c46e303264c517b5447c3a095479edcb64c65b76cc7c5d98b6d191797d9e9605b5b4606f379761fc669ecb196aa76c9165a8e80e2c021b36605729d9243c2b6f0419fb9ee1a905cf82adee5bf298d297156f1e6d943dc70a3fc40c0e07c59e9a9866f6b3119c7549b05bfc0c1bd3e652f4751b8d56cdd52060976b4db19fa827e895efb7698ecabbd038c7456e29de81e1e061f65075f4c8244b8d4c004943b266402deab8b2921e55c63c5fd751782cd0201c78070b68a5ae6a2b21d9fcfe3ec6ed896f4f3386c4365fefde1b6de95fd9c185238b206ef6e10ea6dbeb4e1f9ec4686d6c5270dbcdef08ed73bd33053e2f84df5ce6bbe5d4678c07df817f9e0f9a9baf629483209dfda20577bc5192c09c89238e8c98437e22a4b46692f003028a86f8fbd06f251d6ffc0e9207daa01de51c1563843888744a73d8b61a505fc576d26265e4c5b5d232b2308676189173f20d26127276e98d40b9a3ede0050b2d7c381b7c157555bb16cb36a9498630a6aea33564bb99bee57596f0a92de5f3da3f8801801161375ac220c96acd41537540fc6397098681da7fc45d7b1cb940959f229320d70ac517ead4c59077e46e9abc8acb386008f2474e8adfc9162cce5f7375c3e001e84f69498b4c7b67b64e62996e033e9ab04d7b439a04e8ede2510431b08729ece639c07ec3c9e5d7dc4ec82668e08e1b7f5d6489bd065e8fba125f505301951b0a985c7f66cff44cb49c7b62346c0301fad1a61c6db6f5b09ef16ecabb60a83a24398412f43843ca833f1f363f847155d5daa59c17728f8146b315d12ca8c238eed165b0fb01141b9bd65f0e6730a32b3b6aef82aeb4be9f14b993e7041b327fe9f6142ac7fd3654c271005873a6a41d4a404bfbe05b77c3d94abb1003e7a9daa5f6e52bbdba3eadc26aaa4fa43be755a6634d5cbbc6bcc87668b4245d8076af5defee1593d2e71e0ae0b269b18ce6051a6758669b4eb5a6276d280d7f870245c3b185c92166d43d6f026050714332d109fac8ffd3ea3ca511740b6d1d0e4d47ca048761a79946aaf5177c8335c3a622133500d192f412973a8160e464548a00e3261c8ab390fb2474ea18dd716bd9b062261e7e8f6e99249642e9aa618289a3fd9f6d1b925067b0580ef27587ef3fb9f23db0bd91b3c166d1be5e962b9320261cdb07a8f9df29fc698078a46b9c200ad9cfa94077537d8b1aba332f3c361bd0f8fed83762cec4838dfb2d861f48f1dc41636994641975336f0a5e667964951c40aa070a4812bf1fdbb01d8d0b73b37c3886837a06522a61d8f396e4509eeb0d238bfacf05ee7afec6b713a22be268a8944d7f0f0a44e3480e922bc0dadf40caa44542c5a2759e4331a975ab10f9094f1b56bf23cbf8d15386ff9d0c5c47c6e0cae4ffe5046c4da0ed542ebd3f8869fc872b1b71a866add2f050d7eab7d293eca5a86b072db2db7283091c218eb969220fd5a3f3d18253a23c0852fba7520593d00fbb7b03a783bbfacfea0b50c41d4549a5c3582f0299f0d5bfd0f8724f06fee71eaacc90724271b09b482a3ac4ab3f14b5c4b356a0b87bb83c55bb812130ac77fff131eed246bbb5575c355c241787232eb2c85c09079d01d20f1dc3f04f58df90becabe2ae8e4dcbb450b3e207d64be969a4fd136a2f8a051240f38a6f65210a14e3ce9eb575fb0774b838a8341fe90959a29a80a892f5e86a819bcc41120162f2248a13da26ef90785f240bd398039063e7d748fd124885cd07ba3536280ba47baff810a84a828e146b5219bfd08dd867ac61d67a3a85ae181f3be78e15d4eb74737bf7f79c168cd836de2c8bfb2e21d4fcb049bfcc594607c425de3ac4bb1459e07dc1e28f1e02e547007825fa1328034e7c2e18d03be8b713b4f21a24cd132e14ed7cce02537fcfe7696eb7b95b8ae96c0b6a8e003c9948517fb617be3ea21f5678336078c62c68b460a88541e235890a90b7282ebeac89c44b700d3f1513da84e1c9b8fa5819f5e6dc916acca2723db81fb1557659c8ccbe2b9e35b997347c4b5dd168d0ef4113a60c1e1216114d7d60585ad20f651c76abaf908d6da96c20017f26f6702ae133d2cc70bc65c43bf1e65f697d2e916007cc0c68de275055c71b0b4a35dfd21fb7d64633a04db23f49d20bc194fa4f72c4a5e08be701d0f325a0b1340e3ace5c979606571cfe042cffa80ca1eb922d362d1b968f8750f1b95d200c45e9ddf396b9f5563d7b47a1f1fe7db27d31bb6f415b9de9f32950ff0b443e35bb8d974d1fbe2029657c4334db586e8c440021b1119a8c87a3430e6e8d05978a21ffbb56347c176834fb1eec0da0a6807743e814e575f3a1173d6e11409af62e7ed7d160ea76499b3849dd551ba9ebab0a1e4f7e89da279e443c112569c9ef0848e9b88ff4b72ffef58c9eebbf0073fa80e4f38892c6fddf1e9ed68a44ec631e06fbc12cc8106d7ce3c89172f5fc37e65608550fbee699a7e788cb95e0fae5c8a36ac4f47a6207fd557fdaf3cf465ddf79da157652a67562f66683668990200c75138a4a210e439aa8a249e804423807e69890ac91c33febcbb36a947dd85a2106f22361e9d93bdf7a259a9c8f9b4e0818e432ebf4ff1290b31e282237ccf5cb17f42b8cc57b9e2d73db46c8d8175dcac384283f3d841c802af8ab43051b9c9ffac2148e8da667ca6a2b10ba1ff67ee4062bfc5fa66ae5d7a542dc197bd3bac25055683d719bbec07689aaa7099c3c00558745e7c1cfa938e05ceba3d5ee9cf2e0b7048750045011b9133d8d63ab2fcc076c41fa26bce3caabfcb5949af058951d9ac53c22c838d68a4221c28b3180d38f1c8b46f3cd4d9f3eac2b941dc718d40916f8311380703518ed721c1f042d7d18268f6e2239166001ad003339fbea7c3fe9c9a203a50e3433c8fe633879918975a8cc8a5774411bd959f69d573b791a2360bdb8c1ec23f7d5e0427b7e6780199c81530dc8d21c12f14a024f03e220a49b4204c88012c48397306736c16394d090c096fc0d75f5f1347e128077abdc0002016900d5fd6ff70e360b6ffcc1f2bf3c03b6d5e1496cc58e8ad0d6e162847c1eb282b5909620187e6800a636038096b4d4cd9f097b8d2903e531bf674e9fb159e34b1b1050521639f9d50f083fa5128c1ea11cff78f022f2b9c6729a1732e93258cab6f88502f6d7a222ca9ff41cb81703726a7f250193ce455ca70f3f918388562e19ae35e374d6aebd4763d226caa9bb28915577eafecde1ba5f28b47caeff0fa4ac42608066a6ecf646ea4231001d0386743d6462769f81f71967deba1117282c90958d42bc335215a451f3fda094e5c6a927a10c10312188944cf17a4706c05ed4e344c37d3f3e0b50e6f3abf3138f3b5e2401c84e10de31f09754988793241dbc580e656837604d156e3d5abd96dbd549e9d31c04b4ec33de7418904545294910ed2f60c70d3aaf74325f38e853b9a4beb41ee442ba49525fee31c2d4e4f2acd9913a7d584a13e6d793818b80202ed4571a626e7bc3e4e9fdcd3f44521049f63bad551b6c733f37545abb4dc07668a2f51e99f35d4338de4638588c64d0a17619c96f6920884441bf7d563c744681108fa96223862ae7bdc4dbf46eb1c4adaa31a40ec489ebed66921e3d51de1a31dc6d9aab594dcf94eedee1ec71ca4196edc8a7674ec33129bd56a616339bb502113e16c4e888b5ea4aba1c5520051dba1b438f691deacf598c3c9151c28df5961fb637feea239dd576582a441787b5f364c864fca9c4aa1679fef747ae3f488fc032921a30be9ec244585a2564eb7065fc6150d1b621b4026013e5abf5d057b53190f08bf10723373487a637355cecb13dfb6e29313157f248c62d3dc2308daf3f254f06f3f70238e671e7d9440d80e6a8da5ab67db447d51c519121fa412fa23d011afff5f8c5c0be0fab2b2da751d0d16adaa32cad97e7b753a7ba0111ce340114b38145c66af14b10f34d600a5782e15790f72b7abe4b214d040f843e9472679fdc9df40679c7da2177a6b9cbd087dac25433e9e7e549df68eda3221fea3f30d081914b7c9a204986c846db6ee2941661e60874f8b752afa147b6440c53965c1aa75d3dd457c9afd7dc687829e85c384efce6a6b0988789320372eddad3e957c20b7c03ea4e777219d0b636442f2477f060dd6385946d2df75bb47cf4f7afaeecf1e57b74ed179e9aa025a29e435fe1e7f7b7c3e8aad1e88c6d608bbb49bf0c07457f9820d8c9efde01ef6e8e72a8441f6f4b98e02c9a527ba270bb0f922788b11bfdf93fae5645dac0a6352bf39ef36729244a0245acf3ebedaa4089bef9a624ab56d18f03e602f773d8e6280ba3635f55db76e6a00fe2bf95e564bd954f1403dd3be0cd9ec81f185298d9ca704de1ea97f7694a973b1f5899bd67f12efe210fdc0a670cd4689cd43ed8e56383b01b84074205f22f47b8933f5954018ac8d98e61c6573372978f177938c1e446a80284e0c28d23103adfaff4b27d24329d1d685ad791b957dbfb0fdce9d208adeb927d9874aef367c0004c13ae0a86fc3fa18497c5fd8ef2d92efc176feb21ddddb02510e9888070ae8a517ddbc7742bee4b3a1a03a03caffdea17fb3c886de783adbd0750e388d207babb1e7de82bd80ce0bed9e949fae977dabbfe6614a4e881d0a51289672a5ead9a75dd32238660e11b59c81e48df6a45407582d593f8b3f34f79fe26aa040c5ce6849287c31384326c94edbb2744472929b3412b4b084b996b987afcb18c24b8ae4e0550b5c0eda1110111a011ea270c18852e70fbc38b29722e43fb0db79bd7736b1f0fc519e3ce6da2acb6f297bf610f9a7bdc60bc318ca762af290043af03e799962b1018f497b132476b87515dbebb7eb14cb1c28e587c58cbce3d33d66e9c90ed6b768795c83542308246582d4333971824364a40ec7c3990e73bc0b9f5d4ae59145dec0809fadc3e0160475703aeca8a9088530bb7c95d4aff4b6ed6c9d421240b7b815248c823f03397c85e2a41c29a94eb3eae0232027c3d82c9265dc480afa612fd78159eff068e81da3acd5a3aecc341431e9d86a610f2bb8e056707cb3b9f044aea4178e78bc7f0d062b51a77b41e6932f133d11075b4861ec78b9c2ed8994e2921597a07a488468ce35fa15ee4a5115a5197f62a66329a7aed8962d0e4fca1e4e3e7f92286fb70cc29f96fe414f43975e9f1379323f2cdabd545c5e6b6391afd1f2b30ea39c5affb62ee9f89ef15277f7dfa2ad35921f0b3d2b7b87df6922f8dc7c4524ed7bf955a554bd5ab27b7e761078daed1dc56bff2b50643c44ee6a54dfb705469fcc90078438e330365e89996268fa47d9792841c7b09f9df2ec14074af02cb26b708c85943cd94364167e06231e4ba6c31e46213787ccb67e6fe47cc5e3b6bdae95d6741660898ca966bfdc8b434b673385ee702dc321c93e04563cf5cf29e95e190411f7f1519b0a3ce6339759d0753b56028f702dd83be8c7db0893ab19dd503967060607b7425838271edb2cd2dc74dc530760483186771ffd0c9bec7bb086c4f420bed030b5424c1f781f3f9582156f54cd54a8eea0371bf2a39fbb647a9be623314c706390d2f1cf636b883c543c08ac7ba16aa77ad9a4667b93cbd15769a0cea2c7e92d600cce287f8b73c11497ae1f04b54a482e9a7573cf022c468a2a8b5b02a714cff14ac4c7bbf127c0fcfd47f23f367226806faaf7682b73b178e13429154edb5e544c88fe63f4305ff4baf5e7b95d0e53c4885053be32053ae00db3f4f052d4a30b05486a1dba7c1ae2aab3bf215078dc9a632442f09d8b08e9b7f5106eb72db85f72d7c493624505abd09c32e6cd49689cd0273510ff736057bb1d80471be62c7fc1507c11ffe57ee1147eb8d3e2cc2dfb8a3b6006d9fdf8bf67270535d3ea01fbc055c7c3640ffdce3a9cf878b0b057517d7fa73ce7f752a731676979663b6321f93e6c32cb615ef3c3b2892e16a83b1dc250f8902d28ff9163647a1b2531d843be84368ddd9ae942aa1e1a912462172cca85e91c173703a90ac2d0a65266a1828639a47313bc239eabe79412b44e96743a004299cc418c7a733d5c1d5940475d0898c96274632f5811cdcefdf03ec5b1b852b89a350eee2b0eba0ba749e872bcee48b8991781ed4cc5cdc4c5c7942ff8429f3cb818307b3e9e359cd3dadcc8dbcb3e0de0dc05b0bb814d065ceda06f2d07149510131107202279bdc7e3bd2677d51211f5865da3c003ba4948e5160db9404d879ef3d19f6b420c1613f10a2dd068d0424b18b7dcc36535aebf215aba4ec8246fb4c833dce17c83a06d4a1d00960bb1c51af5d1c57e5fe237a8d665197b2e42c956f8a1b262871ca2e791d1a93918ac3450284f2b143197a36e303a9bf624a6d445384457a4cd8bcbecc86e83af04a31bc05723f1e6cc7728360f2905e2586fe02952dc6489e33859abb869df56b6d6a46ed518a7bc9d221e58ee918444c0168e74ba8ea96415cd1390a56837a2b41a6a975af0c830bfca70a4f25c644a5c83016fc3b945f40b67c40d4328fba9aa4e6b26acfb27bc3c9ebcd8efa820d133cfb260101ea5b83ae634200bec90366eab172bb63cf01838b26d2004aee5829c1fcc1269863bf9ac7836d1f74f9e9ec631a9513d2012752c610043966be300618ed5aaae82b05e2ab33c1e0297fec247169bce5b62ffac3614721f4818abd7943eb6e6064fe2eddaf09c2f31572e26ae93bb008b7ae3518346548de1db3676bacd119f3c2d2c9d35e3794bc0d95d788b785627f69534b48c82b073d39ec9621821f0221cfb998d3fd1329e0586fa1909553da42c83af706365a35db5cffaad4e830bb5c71f77ce9920a4c7029a406f6bc022fd1a3faf7784e6987b8927d4e5e382254cb38320fc2b8f2a7491b29a5f33eca1e84fc073936b6b53356a808934396c738ba00034b8786e6a8868512d614efe9b33c8682ae3e56efc4832a1b4cdc178b0f865e3eba0e0721ce80209ee56f076ea7b622c39981a0243e5cd7e378d8832637f13891a76ab7fe9d8a5774e5bae530dc96bb60eb81ca340177e51a562a3657b6d51286eaf050dffa9845889ec3a9bed78fae9c0dca40e52b31e04cd752aa97c6779f0990afc40e679fa3d10ede239c3203bf17464b0366172fcc7647c72ac7097f79a822194c0e52fec351e9664096ab892be59ef697786a5d9e1d91c768037d13b4a289767bd53f241c499a13f13baed4b7a09f44ae5b8f2d4c74f1e259f9fcfb987e33772e1f0424e9d0470486df547e10617e8a08f1fdc641a83eb4aedbff2a2f9f4aadf9f6d1503719b862958c42b4f1218674b47929b136aaf84cff9de88cd10954e2ef9481715f941bc38a8db262635971e359a3b1acdd6856d148d63696151bcdaa1bc39a8d67e546b0cac659531b07b152091549c06cac0213b78e07aef383d4f5e0753e409d1e5cd783d6f9c07572d0ba1fbc0e7cc05be707af7bf8ecd479e30e1231514c69cada36dbfd902c8f19a6143d8bea66a014fd10a01debdd63e222e0f2b33930c137ba7be763440d00c7b83d756c89cc02a6060450c696804245d50ea43794cf10b03db03f21ea3c7a1b122e464cadf9ff1400fdbcdf1c9e4f7f043b4da142a898c56af9454af79d857782f7636e58b4c8b5d41536c325002a5be2f4f864bfccf4ca293b152b5c1a72cc1344e64996d167ff1cd4d774134e2176a01ea4f9f678f3e5ccbd83e3343782d9f1c87dd193828aca06ad874e712f65d9629f929ca9d8fbb16ef6b4d4cf2b12e84e91ba20281cc94adfe7db2bbd21509e6e482e004853fbe03f5a0e422173e95133eaca3d979fa0336f0477d478651a9a9011f48aa8b053e1a00a1cb6cade011b03225cee9cc0227675e31d3e16e0fa102c2568f577d0a1613cc4643c24d0417a7e0b52f1d038a361f0722c10271326eebcde6236144bd1f5518c3dea46eedc3869e90942de91ca9bc2ed7ff2428c6e79380a3b1abfeae5dc9a358887f90cb10188e164e4148d8b97594a01b85f6815e2a483f19577335e4d91bf6f784e9e0ed0d97dad7bcd7f0ea53afb6be6717f8e79b6e86161245a8be2811a339dfcac83db3d8e4720baff9e0d5936b79c75713ce2c68b5ef389cfe8fd9d683dc112fbfeb2fa5415c74e265e4e9fee8663093eb4b48bd4f681800a6d6191d17351c52af7ffc8d2164001a9c4b20d9be4781ccfcb0a8ef2a54725d3b9e00568bc4a818cd38dd5005abf2ddc3ea2b79378aa0f48424ad9e0e7a09c8064d2a20c111afa95d31ba7113aaccc9067e6c8549d640dd307d79c53727e6e1ba9b854a3204901ec76db04eee47e17540c092c4eb500ad60e228b8a0c6d5c8c0ee9b59526b2128e1c33253624181c06b51f0a09fad74efdba1c2121eea0239cc40e9130028bedc45ec61d975ec288fd0e7d2eb5af9274a0d6f91227d9b1ccab9d0154845b639b524ee5b5a91b2eaedfba29c82af83a616b9d31e6d8efc9dfa4d2513e8ded4da8b17248e2e08687ba6189f3a96ad847a26de7f3659b89c77fb972868e1fb547e080edb9c42edbbec1f422f7d46eab45d1ab5713b516ad95a22875906ee8c2857806c4ee33823fefcd92df0cb8658ebd950de6b58be2a1cf4fe9aa827f06622307924c1bb4b1b3b59e7b8d5ddfec7322387820884cfd860174813567db4bd4027a91a49a7801504782f37806426f0e19f74a4f59c2004627c1df0ee635b2fa6b20f3a82dd761ebad1272e58cb1d82f7965c320a0306b3d767ece37077d5cbba293078d32f357f0eb0b78ed9cdcd921b5346ed56fbc43e18918e8c772875ac17d3b30658a5c90c0621b22d2e4ae82b8648c146bcf836673f5eea9ba5896051416d84268f7c3a5f941d615f3107d763a26d914c1b6f04e59fd8b3e88967ad9f94fd6f455cb8b571ef473f5ac9a1e44b760e4f1ff15ad2ade6ee0104a53a98af813119d4aac13624fd6a466195bf6505f6941f25d7730bcc2e08bd0c15343665c2fb2fbcefedeb848994b6b0a927819873583a27c61a18cf353aadd06d2426825469a1b4c5fa3851e66d82aaa6783efd75b88738e2ca7156e209f8d75355572d5a65dcc617122f112493ed94f263033ca4e908ae38046c3d8f2bcce0ff1ae2c1b26db62f4db4e2dfe08cfde841dfd2ded1a96b6c15ba2ddd00ec5f49d6ae60496bb8b36806469ed07a2391d2d692bf3bafe08aaa1b2a5053eddd499dcdc9b323ce93997db5123135a8bde95285883f3e3d0c5ed3856a52fb79ef0cdd06914d52fc9c1c00bfc16a9e9248b2ee7f85ef7a1c1775a02010043139f828239b722178474a97876c431302dd573bc85f357eedbe6d71a89c2d07ff155c62688582d18cd591e351b3faa04b17b142f16ade4a93bc6c1f01c2b542baa8d92b59e11a88d34d0b6f47a7665e9174f7bce57e62ef90f7c939f2a87266f01b400788defaffd5aa46631ef391bcfa234ed8179a9a4b8a74d19bfbfb87a122abee14fc047e65700e2a3c0c3c3185edcbe20c65318278bc1b90824ab1de9a32e1f805afb2c00c0a156c5fed2dc98601534e145903518c896071d02e65a9f7e9453e39d1914a78d11dc2ff8390272e0225a32f75dfc239f938ca5ca29e568e99ab18a301071436ed07f672e29ea893fc1aa78368964a99b69a4820fa1ecd2b5e983c4b1685dce83d01b301c3a146ba672a763dcaacc96bc7be4ee0d54dbb35bb0c4a23f5be655dbc8e61ab15f9cce82e750673df208d78ac87fe3a149802951309f5e7b247ac1eae45d263e8905bba61a51ab771d43c2d23ffc00bb0a4fe9a8b39083b31dae12da68c408b61a00328687fa847cd8afb8eacd465fbf054acb2c674c2c244ad50b4af5afc9c1f666eb836545297a7060bfa7da8bb58300e76e4d31e5224361e4b5e45ea3b3ea172586f46a95901462ee15ba77bbcaa867aebb39b656523f6ddeaa9b6a9dcff8c12433380fa617996c1774ee499986c7e6b141e37f1d095f2e102cbdc4b06b655d5e03de7990250d3f478879ed20eaf74d9285be921c1f3472befd87120eb9a968e5c6c3bb772ec329a369376795351a89aa0d294f16eebf4bb50418448a7156dc385a5d5f5f8abdbea74f13ab2681ad49d4a7bb4d4d406c0f2fc3f0993b498cefd1991b1f75c0858f96c2e49d0124aa0e2ddbfd5b0c5995db19e23d02a222db6978cd045304c266c623fb5e4311df671f70dbacc03fa16ba58007701f6374fce0ca1d8772065779e287442a30ee2bad4e088c503d85d2a4e71d2d666d170dfeaa851a38188c399344ff04fc168f902e9bb9237e159d3b48b5c5f609f147826967f05526b550f06d5c2cdb200126a350ea6b54c84bb185876399602e85fe9a3124f04dedc454e2e8ffdac819505c6d02ac0949ef4f739ddc36733a6f7520846d4dd4aa8e618977a2343b2253d0f7e617bddc79f12b0404b6f6ff839bfcb00498ba49afc0b3f7c4001da0e7556418c0bb4d5cb3771c88573a6a4c8457abfeccec09215eea152c4831c1a024f20897660fad3ea0d2f678e9dd897f73fe8ce398babf8a87a00650a148de1e07dd59ca1433c15ce95cc18e65dd55a26f0776e51225ab8584abf80752f44ee6e67c046a10e37360e196fede0fe1038b5d2b6d1243d8a5a6a5b8168deeb0204172cd553ff26861d5187dd0b1848a192f7efc1e73453ba348e7f788f76449696fba56f674bf9d59818f58251325a4c5f10aa2812544c2fc806a14b370b9c446abf86adc2c98dd83ece888a78a8ec7407895cdd33d8cea1b9cae92e7f9f4e8fec81232064bb7ad8c37f6258d6dfd0a760ea4f4a695d734fb841b760789b1ad3c8e7934e5b36cca5bdd53dfacd927a3abad36fd7122722d0c614e4d282b711281a1ea444275157265c6b7714f6276876c5d34c8ff3ee5243f800eef3a1132cd46067b39caffaea48409536fdf4b09acc148472185d6ce77904be2a7f25e24f500527b43beb0f0efa1a4bfacc3c92bfc63a8b3f9c06759b2fca2297b65984dfd4ea47c62db8a08395a99193686375e2f4ab81257828e1069e95f4fa12261ae6dfe55896bf1c4321eb401a6945c98e234b312fddbc527e367ec353fa9facefaa4af46d8d4eb4376dcb97dbb74f90efdac7656938555eb49b47ee78eb81456501ca11d6df07c29f19acf71c75d9868d3f6a346642aee4d1701963eefec5ca446370d11d7eccdc098220779e359418cde4527a0115c1b0b9d3d22800da87bca60184d04a21ca40015b01a21813115420377200e3284805600befbbf0506e61c9408ecac7873d75c36d222463fed21a0935694e159c31293b6adac5e28375b6e8d8f8ddfdd317467251da698b8429c019584e7e1e3c7b62b590d1ef9396658ea5ce3080697b2d0352da18366454938e8e7b6967489d75e70628bf7e02cc134956693a4e575156549ba2d2607108bb34fe78186fe4723941d11132a799e7b6a6e3042dbd6070a80ee9d048e7ddaf19ac4bccc37984909e4801772b7cd1491def5bbbe905e4424bd8e6bcc19a2d7e7cedb793d75efdf173b0b17ab4944fd7cd7143fb786b67a3d1cf298177c0cb21e82c95fd0ed039786c5240eb9f2b504e5262eac612f9872fd781eab41004a72efe87ea9dd45fdc17085569afdae8fa4fdd1cecb5e8685b544831171aa89cdfe478ecf517f6e464fde1987cb019fa3ef4e83f6a9909f7f879723085aea16a374a2f0ffac47c5980711b12f8676fea2e752ae33252f626df3419e7e358a62054335d447329bc43860836af0f8b7154d2c5dc0ad05d2af46092d0959208cdb6d5bd24554fb94bc2a9dc4960d70bdd673d6e03d6d1d4964735fec078158822b7cfe44e99ff5e8249854b11a047c18971a49cce6b061069b02de621dd49060e9d8ef2749dfbc1a3d10ae775dc7d9ace45612950fff995d29b26de8c6bf1196d4bb52e9126201d4878155131f45d15c2d58207387d6fa28ab1772a5aed866f4bb46bc1f16894589191d423379b27b37a5ab4f389a49a72f728264c92eefd8c04afa85e8d1ceca4eebd7a1664b48ba68ae99cf01ad4b861cc533b83bf6d4d679bf2e113030fb2be0079832fcd115a8324bd1a7fc6a9b45723520371f3595b21c95c067040465a69e7387bf96265d710d6cdb4e089e35add86284b09d46b6f3a5d4f7c7aca86ec6913dac00b17b00975ad8b34c07a2deba83bd630542f280b22d768e9fcf7c2a4a3a8b86da35da3e3836a0b71281fb687de0e6abb54440b77b599e58f4eb163433985b5543dc9dd784d428a667a5787a3f0a11c931011e137c607dc3fa9f04ad95f6279ba63957cd576add37bb27cc52c0c127e25f7ad67ea4db2bd43fa52ac3582e159d726c0d432201ddee8e36eee44a53ffac797a95d683a9b42488f2c1896a773af7aed21c287b678045a99f16f724b3dcbbed1ea075726b40069a31a8bccf178e12cdb11dc3aa47f494ec00015095e3b0c586b79c46f0ad7ce1dc362f518d8ec4c628270417c565b6d3f996acb7fbfad6f81739681280f7d3f2e1ab93fad83c74e070dc4e099c6db654e9a0f7811ffbac96e853990481c833092b788cc624b0ecc0557a529b771573f664216a4462a5af7e7fe9f904ea42112e75f4fbab958a2439ae0b624141d1eb5a4e5cee7a38934fc4b9483c8a1adbe2bb51933a21adc42b1b0245cb42825fb5c6000574f7d0f86d41695d05aec3f6308fbd3b3de47f17e23446f2914cb83c462437a373c8522be3422a96532bb254e3fb80dc74cfa5f5af7075db85e8868f85b72e5a9c114e8bcc5af3d9680992312a0cb90fef5e29c0faa58fc72c973c56e8e24169171399cee3359587e7ca71a225d0932131587174f4419ad995d433e24abb8b156eeb38de9515448ca6a820aa9beca337b53cd666b6503e5b01b08925dae3d91ed9f878895ec6097885c2e795acb11913d93a80ad2ac4e2fa2a3ca209197db4887a4909664feddd79f97454ed22c4a02d9ce0181c4d466acb828b794773819e80f5837f6fb957202ab6410559a32a97643e40b927981b7578af8f4799f19178353ed0a3b41067743e46ce76ce3befb6bee86c7d80f32595c76e7ea33d4ec87d8b19c6fa9b1a2d3f69858db9490e2400ccb71d7fa66cff7c1e669711abb1bdfde74c14fac9d04a126caf9bb5ab5a84110d5ae16007d46b15fcdb46c0046a1cd50048dbed5effe502b5858fce71fb8d55686e6e59ae21f0477f687e6f33a4df7e6805a1e4176049bc7f297544ea8aa4915b5a6730e8b2c3b2d200ff0b2054baa6d26cb46cf87ddae15aaab8cadd5c1e2d835adc7c0fcd81e42a6a72698d10d889395e4d6deba38a0bc77ff9442122033e0e247bfdb225b825f2e1891dc57b958e57a83b61083da7d1829d235e5983cdb253ae86b32bc4998c9b00a664e471163d80cd352c8066528ff34c1943a4a07322bc8176b3a9946988d3f494fe5769b9d97ebb447a4b5f60ca80073ddb6e324b8fc73c959131f1b5823bfbb85c27c4ec7a25e4f846b7e09afc532386c77ef36a5ec108e9a999c0c768acb0b653be3451f989dc2b83e0dff812bb86fcdd4050be19bf70b5ea9553e10b134f642f1b6ef2fc1ef0aae8dd16782ccec29588fb37b4fcc3c58b6ca06a948dcd88198ded0ad67cb45410e06554f9856e51880f1911bf3abe0c4c27663c7f8480754587990f23e01ff25920c8996177d652a2b8900a8c28d61f7ec89caadbd843d1ba7792b878a7dd119ecf37845ec9267988c0f3947af2ff6e118355ec3aa62bf6878c5bb41015b3491e361c151fe2bd4ba6162b93872f4cbacc439507509a418f1fd05a9aa2e77d5fecab00554874638061349471f2a0b9bf4058dadf5fdc8aa236e62641a90c2e5ee315c377f9f52d83811a97721bda5ea6a326d397cdbd17be3a81e4371ab8e59dcadbbc9ac94da1c50e4b605d5431338ce34ed9aefe851cf15f8e029c702dc1ba355f5deb0068dba91eb2c3054b42ded2ac2ba15bdb3baa42259ee1cc50844f020022deb1c72c3e7233dae06c19eb4dc0623c3b9fc9b05e6c4cb1f4cdaa33bfd033fd46904275c5bb873064f6cee7a422bc8569699f39adb506341c6f0fee328f35d9a966e9320e4ecdecb3045ad2ae246714b103951e55031a610bd89be241bd8921cec4307d29da4e6e03e89087a28d7af8a104ec1c0795db59ed657c459ad73a005ef9759977a213b95ccf20959895148d969c988825a75e0da30e990573797d1a16adbc6372ba1ea149dd10078a1cf817db91b32639e9b7e4e0d3d8080818c1dc44ac37572aa5308ebbbb9f804c85136290c71bbb7c17123bd4e8c8922294f4a664ab15241ca9dbc1fb0e06850c7d4208f48d1b3b3baef8bc2a6cab91ee60a7a8e9424968532d1af40e6c872afcc7ab75fc08e27784f13dd4f729502566a138a723137cf026ccf275cbd4c05bd7f5ec14e24ea7fc0679e5acaa1d7b01a783325b9b1c5cdc12e30045a1c4a8b5328376b252d93c700544bf93c224d4a29661e074e05b80c997ccc3f869b27287a575ff300205abf720dda05442179b15cc62f4c1e7099b664b095743ac5d4e0953298ff7526950c1a3c728750fb93a77abf94e66defd6d214ae0bdb7a0fd7085bfb850bee5407f3c9dc1a973cd8a0607edc5e042939c59c49009bd84e2e5886427c7b26864e1e51e53b7a2be111be3855370a067c22f7a65af10c5c7307db899944755676ea0fc52a3511c8c302bf6c2342369bef5b265b11fec35b850233673cd70a0491b91a777a3ef3d634db61532158695964da4958c9b01b66e08b0dd44eb26c744535939c9865445c80b2f6421efe5d75299afd016c8bc60aa1783bfc0a356ac17477b2a85da4622d3cbbd496feb5089a7ec7b81db171f97277dc24ba4fd3fc464d603c1f49ec032d08e742accd7542671d5914c163af8208d1aabc614d264623ee8ca4c44359921a77f9ba92928d607815b8c6461f983db426d1d0616b5b333ae1dcd392977ecf181d5258d5550d4b6b39bfdf14596b19bb7b6d77283fcae385eb5f1e2363cd66987f2774e6a3daa64230328d1a8ad6a8accc6095110dc05d4a52b4cf7d8d6aa7e849c51ba043e0a823f57295f66caf769555c3e24df49850f388912941b94648012d5be687e576d3f5bb586c56654f748b48b26195e3f52f18088fb0beb82affb87d7aa0df53fa49ca086edaa16b4614b543d3d6652bfec05f654725d0b9124c03d278efbee4e6ecb80875e511a58ecdfd7128ce61744963c6e8f07ac783cea320dadf749066dbcc391fac9a9bdb651ff26838c9747b7cd4876ee717610ee7865ffb4f6cc853bbb5f6db5fc7f0b89d4af15500f2db06518de6939a10af925da60cd3b235f128bd1aab5e070d1b7643cc7e2b885be673d3c39ef359c6bac70c3104a3b5f03f3121537c78fd7a8aa6a4cb3fa77af704301b0c87f2fa5224f87711772e62e2e9747e52b7edbed1d8849e03facdde91592090ba9216032cb8aa12f76621d24bcbe42f40247e91562ede15ca60e0e73cf36e34eeec7d0b972db486382914e00c06b540a96a4a1a15754213f8e9aeafbf78ae17fca807ffbad584ce691ddd116db74608a126b4b2bb7b07280925093009878f64c89faf29e986c70870a36f874d293c75f83515bdc9e1e9000e7f0570780e85c363130e2fc4e4f0480270f82543874761c9e189821c8270d8a1cf811c5e861f87aff201d209494a9492f0806489c861cf7d1c7af097971e58f038fcfce8613db487df083f720880f31c3ea6913f5fbe3efc76aee3d0bbcee12597095485c69c1c5ecbd20978890471e4a60568a5c75a784e6e5280bec94d50b4d2634f7e23372140b372d301b4d2632a7c959b0aa0b9dc64a4951e23facd4d457a2be0262ce040501e04010f3ac08914e0468c7e24173d96b50d4977412731403bb9f4177b082edc85ddc48501e80fc8052231fd0de0db85105cb811222e0429e0420b88b5a0939c72dc8fc845d82100fdedc75010daa1a9e0a024a7adbf1d990206a03f146e08f73bbef7d69f0917da424f7242417fae8b1082fe727e11427892d30efded0bc004fd091d85c75c3872895c2341416e420b408f692a5c0b26c472dc480b381ed482b621c9e94f720aa23f13de447fb10fe024402181059d9400edd4d2dffd072c18394c53b92c10799213131602a03f120ec402d01116989c080b1f0480c883b0a0bf20076ae1421040c2139de484a33fd881682af73bf2c308d049582204c87fe0273f9ee404a43f58d6c2d07f682a9789fe7edc25b0030101e92fe84234150e48c8939c86f4d7fa124d05ffd0df8d7fa0a9e01f1f007d48c8979c04168280e80ff600682af7c87d0284c89320fa0b3a094f8290f0242798fe700ee4c9cd8d3c810179921311fd053913fd91f0007cc80186aca0930ca09db0feeec7e33e3495bbc29127399db082fe863c4853e1562012e463051efae37e64051e4a8e9cc80afa23f2a027ff29c010157492938dfe4ef85ed74712fd057d08921f1f9a0a7ee9cfc77b682af8f524a720fd21c99ef4d0542ea7bf1e370992070505fd04fde027e9a1bfd579f4e0f1242724fa0b7a929f23f990158c9c707d5c252a10d19f910f5181c89027399da03f9b2b51c187fe587fa9e0e3f524a723fa23f213f437e44a5e82510929e8240268274d7ff73a9acadd4d295c9e148ca460444b21058d3ba2a96429e8efc88da8f022994ab69b8a4a20d29fbe0e4de5f2e828c11629c2a3a960ad3f9eebd054b036a2bf9aa97075ae692ad7d61bd1543823468a1829f224271d3afabbb73a749ee454f567e4b5c8ed4bd054b2dd9402bd3c97a3c21dd11f7d099a0a77a4842739a9f4a73a47c4a33fee5a53c1db8624271efd24a7a9bf239ffa2be1f442b3c90004800293000c2d394149122426e4111ecb247c67a00b65213f9247780999841bc9402f92859c481ee14332095af2205ff0411948c3ff64215a02215ff041f2085a2ac917bc0879e72164efb00c0290ec13634efe23effc83ecdd47ee79650f64ccc97be49df3c8de79320074ce5566cc894e72ba863bb47422efe826a2eb648f4877398ebcdd9553cfc9d97172f7d46fe48c95b7e39cfa2a6757e50ecba6a5969466cab4dc225fd066bb021ab648b36bce48a0c4022936cd330b0d5034d833db1d408006061269861b2b0390fa059b1095741312923f14683bec0bbab7d6c3251f30952f68067fd08c9671eade9b5f73f6744d3f037fd04c14970b6321f9032bb53686242fc8577dde00a65f50845d1152cceaba6378010afc052dc52520848290148989813c3546f8bdb0a504a955fe1092af0d52f943deec801ffb87760ff5845b9b457bcacc9eca1e94e5a0fa2053b21d81b9a98439828881bfd4a68f6bb6fc96af6ce71e10d1ec18e26e9ac0b65dca6ddb9036ddb659bfd42715e035e814358928d6180a5f767d9124b33f986606f8bb512aa828a5690618459f484a67a500d296b3d62921851b9452eb5c00471d0586cfb2419ecf428966d494d97996edc44bc9a4e4de235fe9710f7a1ceeec42d2831ee7b81e3d1ef34e8f2cdbe9d123fb0ef723eea8c175e7316731c6b8f3e57ce736eeec1c764a72ce390046edcf4a972d25e731ea2931c61863ddf9dc3bfaab42b9d89d9093f3c5ed8212235f59b7f32c6f3b99ae54c897dca94b70c8ce65a6eb17f9c2227f2a14cf79e83800bcebe438941cbeea3bba3e411f8946be7ee0b2736c789e9d9d9d9d9d9d9dd73452e0fdd5331b03816f7cdffa3bfeb9f60ede3bf9dfdd3bfad3baaa4103f757d15435f2e78cfc992ad04e908a334e789eebb8771c5dec7b2ef00936fa3b9975a83594af9defe84a867cc119a94a3fc121fa3baf3c348ffe5c00d03af487f74a477f7773ab1c38f42757aae3ad755dca75eb82bf8894f445fe7c53693fc91ff8e40509b67c85923fdff2144afee0b83c15237f762e4fa326508fcb53a50924cbdcdcb8f987f78d9b9bdfc854e97a87fe5cad7f5309c7639764e7b0a34ad04747850257285d01b0843f59e6c975f91e19caec3b5996c1b98efe2ece3dfd619ce728f345de8bb1cbf5bf5e30d817b423ec7068fde5dcba32dc391fc5c91fbd915a608837b2120c9407d7a78f93576518625797b8fdd5a5a56f9a994a53c96acda30bfea81255923f4b6ab63c851b045ac86c18bb376960798bc42375aec02f36ba2ecd6a96d5d71398db10ca026178d8c00221d8df6f012b78883ca899b7c0123d7c07353c3019ac70c8384f63e424a464245314d04d0c7e8b2d180c16020c0683c160416021c0603098bc71ef4ddd7befbd5ca4f04e209b9818f8a548908bb1fcf9512395d3470a31126675030c03fd91baf7a6eebdf75eeea6eebdf7b2388ec25b44ead01d1e3e71fe7407f2f011a2037de25d96c3ab584b1583e7689c18771004304c1783d3255f31062790168313281583f7d4c01d776f90345832cd73624ccdb4ffba344e182c082c04181c026130d85dd96b56579140aceaa81d63d448afb592dea803d35257e75e1daf6b5ecccad5f8aaf47256522ee3a0bea01fb0204499e783abf38ce7d2ecf5c5f3ea51af65b97368b7f66a2909fba0c72bfbd537bbbeb4529adddfab8704cdd4eba5c15ef5be226dddea7a65daccd125a95aa5b5d2d77dbd347d656e9787069db2fe7eb384d43f1d7c09947d2645ed6618a31986d938a29860a8992a023c85fd9285a065032d6e9cc0dfcd1004db4b93e1533ed205914bc041261ba1b9c88aa399881accd13c643765d764d37da6f458762d27ddf058a6951ebbba6a164733097bc7061996c803f4d1b265cb1c76803ef2ad2a7006caf121ff08c1e1c6bc82ec1c8ee61176d0c01fcd22d01c420e8e66187ef2389a4168858066203b8dcf179b067f540747f30f56991d1ccd1f442e5aced1ece30648981182b504d30d24da28022bd910a1b45a228a85c40d2a2598f0129d1250ab2d565880e34246b505aa025b5354b7050515d09ad0705bc4e021c504d4b685064b978bb6a58d1e889a404a82071fca1041c46529892474205ac24c1250e84094849a24ae50524a02044a444c984922cc0f444d5c72d0783f2c1de3964b2703d2019e293c2df98a3aa49981926ad75a5b2e18b561176994f100e9062f2c69d6e841039cddd0a305b8ab800722c02c0cf808036b4df4e0a00a2270b621183442e0031a5bd88059649090907e40a38c1eec5cdab08b36c6ecd71768a4410234d668c9c10e5128a17936c4062184104208218410420821841042324284f1a201df15f830836dc07c8044163d5650851558db100a8926a640c28331f0dd100a09227ac4e06c43282480f0629352ca29a58c5766526ee84329fda69c73d25aa994724a29ad94126fb9240a4a4965a694ca2ba794320535958b0a0c2184104269ed9c41a6a5f605a370a296ffece79cf39bb79452fa694b27a573ce7fd99cb37ed63ae7dc2a47d3e06c2aa7def47b0d01ebfee69c733e421ff8cd39a79caf5965a5130758d21aebbdf495c4a5524e6ae9cd6c5d73e9a51ad2bbe6d24b335bcfd09bd9bac6d6ec525bb333d5da9aada1d7d635975e7ae60cbd6b2ebd34b3f50cbd99ad6b6e5cc9ae94935abc532f6c5a6d16630d5b461abbd45a6d4699905e88823489b42012e88b16a01fb01e286f8f84e5095165d2c2c46491b0543da318242c99ec329753a9a65f28e87649ee6f778284d9ddeea2180245a6881441905ad3bd1913d2182631910b8d484abc540ba1cfbc4d9a8b71b4914b47648184d2768408761602117e80f6c7ed6a64d7236e30220c1641881140ec29718c1016f4579669b892704a08e12319f8c988a495b5d6c2664f7cace2880d5ce18123a8382266add2b6d65a6b6d50d06b7f2da62c76e02f36e5590113103d6bf0878384d9456c20db6a8004f3bb94527a420c3050031a41d040882e4a88c97410812f6c43c51a5bdbf2fc8837ec2f4811f8655b11ec0f8eb19ab59dfd6177aca5d6c62d2dd8918a6db394d9568b6d7fb1eb2f180cb67d90d01222226eb0ed9f403962db4779c1896d2f0306ae40d262859219db42282268c0a4650c9722b874b9b22d84ca0017f6622f62b6b50fb2d65a6b7b64e1c0bd9d076012a679f1210363260566071ba6cbf562881a0629c352460e148bc572831fe6089e6a96a40bc826504f7488206213305db238819b8c008d7429fd8075288049d8186a18e1011835baa8a106163d30c0aa0dc1a8d1850f23f0b6211835c078f980ef8660d4308303141217c8d9100c1b5b94b0fd6bc5bb6950a2440d0b54755cc76198109109a2cc182595627e3b929b3eca5e77b6b4cdf96d9bd56e75d65a6bfdab6e72665de04fa7428922034483f47a990934ed0f50505faa8e1ff0847042a8a5c45077f698c2b4f6846b179a98ce16a4b2473e35c67ce268ac8184a54b9d99cd7296d15c245f2ca4745c8a4af0009882aa81e16397c472306315a0062ffa090cb56f179217f38040ad0c7fec06c47d7e3e19c357e5bed12f587be27927962e775838252ff504f89387eae2cb9697f67572437461c134a286e002239a2070902a03268d96c833bb21b9ab974a5d0e490b16c102a1845862df6cb92447d8f2d5a60ea14fe5eeb32cd24aebebddb093ef5e9044c70f986e4be40fd556432d44f312263045a26b75419f3a035c796409f3f5d556faeb512d1443227bea5363e827b00b0ef194c46d2b19ae0e6838d82e1b0eb5d65a71b85d34e104c5e102693629a5843e74d2092f6574c176e50f84b0a0bf30b51267905286b1d18927d0937618a38f59529b669a3e92f747f38b503bc618e352f67b6917a3d0de69aa539a462c942e118d80116ac9eca187b14d6608a554c9cec84a4ce84911282fd4280c0485444949f7d99388b6db0c6348287dead2141b78098cc92529ef0b6212c6e6375967ada78f59c4e244c6a6ebb387c296b4826c7d1ae36a4d2a8416586ed8794eece78de636bfcd6f9e91fc7699394f49dc5edc72c3edc566f6e2535e6ccb5ebcd985a49682cd9eec3113ddec37cb32884642b1411e2fc2a889ea679452a289346c90e766cf3aedc5ec475ed41e91ea29c098eadd8faa38191d6557fdc8a9283beabaa77495edddab7057dd4a0cc660ec1ec6b6d82591303c8049dc148624f644a90bc978b3176b517ee23951a9b25ffb5de9a3ecde8bda48fbf58c52d9bbd76d4bcdab5c5a50c6068e422505c1a691524a29ad144a144a29a5dacca44501a5748b0d0689b4a1b673d8c01149860914af1ad883da73729f7ad17d4a577182af72e474a43a3db2bf87faa83ef5953e9a3b5be0d5aba84e6f57ba4ad59e13d5e9bd4d17a9b4b73dd1d7a752afaa2b94fad41ebc7609460536f8f0840e19f822765f349f7dce5bed44b347fb4b0d342640230c24ccd02296a9741558ff647595a96151474e45a9cf1fd17b2d2a0663993e4a69cf89fdbd077591fd7d51fdfc07d33ebb14622c3b9c324e95071f4351f6d4bbc77db3077fe4b4fd28f5ec474e9d2ea2bf3fca9efacc1ed41ea73df84d17ddd31f51ed412d03d2ecd17428616ff336ed39493dbb51ea993efa117dea463706b5a6ec4252bbd7525a5cb249d4ad84edda29942812804bb5ca5a291492b5d6babdd65a5fb3a7ea084d69eb6b985a19207f3eefcbf142945a6bbd078740b873c021f465e8195bd40287c82a1026f100a39658041c120f213c03a11816ece18bc0a86311d0876660533172cc025f458cb1650c228b0cb6bc05e08b7e0661c6968f715f1d97a00fb5400db67c84102ecdb02712d22c6385495a3a2595b27e61938254920542e3820637a8ae6c61c5997a450d2ee98cae0733d6e4d0829a43adb5561dd81c542c11a8ba484d81820c18d134031bd460072796a05e10517be0806a731b4205f1441a323d5833704825692b488091c314293893431850c82eaeb85c9c01020b986146cd813e08a61fa99b1d90b698316306f26c6e674ebeb07cb9cac03c32e6e00d4d087da2a69312d9aefc81504a2965bdf9fbb561841d84a2505c00cdea05a00b019248427290fcf9a07c8970f856ecf0ae7d781da1c3bf051e067c012f177e2800f6c307d97148857cc18770f822120eff04e8f02c08393c94270c48c0a144913f5f130b4f1d3621e0d961910f3924f2a0c320ff6152618b017e806f874d2b3c75f83515e0d9e157a484c34732f2e723a2e5927cc11b397cfc42fe7c418a1c3611bd0bb596912f49867cd517409f7a9d31f017a3caecfa28a64c7d952f1813c9c817c021364731d58ab59672d9edb35b2dc3feee69d7823e57c7a5ae49a6a5dcdb674357dbfbec6aef664deceff1afc6f28573fce14f71ed7b3b2538f8114b37021438fb1797602a95c588c56adae52e6ec121705b6db16427e4ec387f566f5b8ae3ba221ccfa1ab38d9c3fd45d9df1457a577ca989a65bc21fb794d4a8fe178130e3d0395426892147243adec35529ccd53cde56c9e69e4eb862ad51b7942654185c6d96dfbc23d51a8ce091597d516ccc4d26283e50610ada51b1e709472925c9ccd1509c71a39d4786974bcec50a1a3cb0e343f93cd6832005086e70b1e647a8cf120ea25a6078b0f9ca05c7e9cf1e16c9e6640d802630a418b085846e06ca64004e16c9e68e4ebe69b10cee6f9245f37f7d10dd93cfb06c4470f10204080bc80dcb46ed8701b10203e3e5aea0201726f06044896d90a8290114408418810988f8f8f90880195a6bfc9ad9c314b6391af8a5bae1db18c9005c3583d527d646aed2cbb37cb62a09bde52aca97c6dd9cd0f80adadfa223457f4aac8d55b6a7596c5b8713b460d73b4e02f46c52f7ef922ab8f59764fafbecf3ebba1d68e5f84dd907d2a73406b47fd4dd9574f69ed98e9f845fe7cb41b8a5fa895765a0a63a6fa64d0da3bfad1fd4531bbe6c8f214d8bef1cb06a486fa4a757d9623d1ce6e65c36e5edd85eae805880f187f3a9773bd76762ecb2df9aacf9104cefe71bb56224bb2f80241807b491623803e707f5b48de7b513856ca6eefe918357beaeb925e8a51f255731481618cd5323beacfb5a18f1af88b513baa4645b5a40757789c00771b42618145191f5c808a170a7085c203344d6460c134051630b0580a8389ef2d9376564a2b9d954a2979a49411afe6953f334a29a994f272369093569b5ff313c279b3219c509b32c29b26ab66361a279a29291a0821a438986dc468a6d2362a3d234633319aa9b48d4acfa06a6cb322118295ceee55ede479e6b287e59bbb2648d7b8eebd37621a35b95811850ffb834f104a0c0a9ca298d00006d03cc1c36cf292e68a2536231ac8ac5146cd0d67ac985cae5043857ca0a905300853060c282618b7032ce056adbde280167b75ba5a750087bdbaaa4bb282647457acb1bb735d920e7eb9820a1d5a3069a82b7088612079e1e588e8851559d02ff78c0d60993744696ddc0d285dc08b316b54c181304e40c5ec54625b0e6cb16d6a5b6badad628b12363de5a1034f154d6c7b2d151cb0d65a6be9611535dcab04864958e67401cac53e09319d18a14622c91eb8bf8ba948c1929323a27898c1131531d062861717164a2a8049188f1164a0870bae1b82d98193193b90c247961d6851811d98000736c2e031c271411457ccac59139b37aab7d393a79e8c41d86403c71c5faf18c5e002671b424df18507434031850a5c504a0821a432d628bb9871ce99d93a29a5349b76d639699c73427875d8a69c137eca0c3999e79748e78c311ecacb28c116e99c93b26084c1e61ad8a54c770a241ec86092228d1cc6589a820725319ea600a20230589ac2081ec6809ac2e90632a26a193268c0e4400d17186f08260d1df6771cd2c08018692cb1031ce016b918bb18a59c52c6edf5028128683e79f5f888446e76fd5dd3734a2112ba604bf92412612182208fef16391a6f2bc7c2b3a8c50d053b5c601965193c378c0c782106195e908f686478216628398a9297d4a7f6b844b36060cfc33875f001dfc727e910e2834f332249b821d004726965308d724e55d376d919c9d8762dc7a739b9cc1c9880f69cb71b45b24408004a48d769bba9018ea773de9c118b04c9d79c954a092f9c3d70aad9828a405953546ed5d4da2a9b3ec218dbb2d67cec7a043cb6ba2b48fe84005fa99ccaf52134c15be60440583369a780ba014e6d0825451a3e8690e28c1460ec6e432829a2aeb665f4a9d4a65d0c8120dc5259769ba57eb56b7a48ee2f6e6fbb67543f9f42d5d333aaa74fa16a7b6f964d2b76862967187b6b96e1cc663c5b4afc7876566bfd3785a7d6ecf6fe8b99f6ae1e82f2f54de1d936d6db5cab151ba70e5bab556374b58838216a2bf5dda44024983e9e9eba4610a3f6b25bed65ba08ce9e59290f4f4f4f7018f18293524a338dab9e93ca182319b576a952d62e55c71438be4a69350c744f3d05b6e52c82ca1b1b3480e1c3e0cb426b0d1c2fa7f0ec18e3149e3dcf13a714f16903f7e9be60eb42afb843b463522960e9b63293598aabf976d92929678c12069b3adff13cf2e7b6648fdc1c2e93590d60f9ec35f37cc600d77f98eeda35a148481084792a9b64b2ce6d730432ae7602d011bee6ab6c4277f6fbabb1968f99eeb867e4991ab73c805fd76d60082505d37e9a3fd58b33a4e8210db659e3c504c6673e4872c1fefec396367cd0805b5b6489c2cb52144cecef4544e103de36848a62a9e5a262a622a5f44a09e79c73ce39e79494d26cd23969add44e29e5945266f2be6094c6ef7b5dae9b35b0f8e037ac5f26ad36bba9596bb5f225ef8f311827ca7fb5555fabdef5663745b53937eebe6ed00666760c7233b17bea267bfaddd45aebeb9ba7347f5987ba5258bbd93247a98b7694d65af5a5db96c6255f50c5adba24f05072f3aa99bfc9755be0c8d9dca8ad16a594bb9956df0843d549c9cd0be16d3229dc340a21b4787230c0f1355575f637e7cce8cdaebdf207de4a7180a5a473ceade3b0d8b009f8e5527265e008e19cab2cd35524105b21b1c6524b2b0dd4d635965a5a69a0b6ae5943abad960c5bd7d0325450c69a32ce9401633c53c6cc4119136a4d171c37395a5bb5569bdda036752e417e7435501d2c9ab05bb2cbe2a214a4c496ff766cf81d48246e8920c821104875a82ec378263b60e7248ba57494d9338613714b4c8a4848a94deb5210c5002865b48debd2f2a203c5a170bb255b077b71af1712a15be7352d15bb244d97e7aa150eee82b9588129dcb0eebee068c375e1c2850b17c8850b9c40383155c76d5a54266f78c117632c9960192424a41b9aa0816b820ce73406eb092b5450a4e0c6179dc4b277ec8fdbdd6119bb3bedba64815b74832203504f8800a7f9b89db556e044164e423441868b04504d54e00699a14698113c098162424c1c038a1928818118bc98c0024b21934503496819620359d81c3c80a4451f3a5cd26c7afabb04984d8f97b0c1a63c6c8051118c0ecfa64d6b36e5e2b4e95d9fafcd8383314a9c804c53139b5eea005320586bd3245ab029941248b82e165603aee86a0527581981034427040e2f749cb1c9a085458e0dba2daea441a3b855dc242db57ad84a80a30dac04ce96ae0d17746d58e1eac1b526cbb6869a6e0d3170769093a56bc30cbb83141b2758ad41828b46b5668bba0136d4746bba986bd4c099a00d1b562658b3858574c5f502098acc0b1acae03594e8aee83ac688d41536c4d8aea051c65e69b9726c08e5010f4499800607f07737f528de7e105949795c3b50603829fe04d20e537fb2e93fd134edddbfdea5bdab6f2c51460aae6042aae2a4e9abedb38bb9632e45027be8efdd6c96dd669aa76a29b019b5411e3c81644c27661ef9caced26480716c18e80e627d09910bbf68d3de3dbd8c3ff29c5c6de419d9cf6b8fba48fb165347927b11eb9cb6124be922ed5d113ed71dffeaa26df39cb05e5fc47ad5de2dd2eeddec292df0b32cfbcd32d6b58d9b167792ce98bd5eead888754d7b29dd2435fe91bdda357d548fb5778db6b3b41f79f746dab973fa086bef6a8f88c5fa1136d2479c3ed2ced2de2d621deb2a4ef7ea22acd266cabbe7aef69cb058f89c3eda8eb7eddebd11fe9177b551f7edddb7145665a20c65f664d7f20bac55f62ecb328c6941b3278b58e42b68674b4c536a4d1be4d956abcbaf3a1b1b9bd56ae5c47b77239823078e635d25e7385ec57b8e5b8949d8c91f79efa0777ca39cab728e737ed4ddf3debdc87b77a98b72367c2bab17b5be7afceac6b1ae62f31bafd2bd752b31a9d25558c7afb2baca4a0c77c72fea8e2f8f6daed255726ef32adebb5b896115cc717c231c57e9a39ce3c8398e1f79cf7118935d124f7b91ebaa17e9b46ef491cd6f9ca5bbb7eea55e74f3d58bd4c70c7a4ca27dbcbf70cb6faae7dc087f471fe91c3f47e7f847aeab72b2c77de730e6658fd39e13d7736ea4f31d9de7e8fc4875d7a63d4d3b715d7503b8b4a7dd4aec06d0d19e86bde39cbbeed2559c8a5cf77415a873e81ced11b9726238349e3d32277b9a2b7b5afcedbf6718cb89da8b5bd7dd888573d63b9cb37e74e369ddcdbb1be1b08ef38e759c1fad7e93d24e6ebeba016eb497ba95d50d80a359bfd15560dd9c1e91cdbda2ee30c624de7eebb2973a8cd9642f05e4053d4ef51f8c2a923d267adb2cc53ebf90a1e46c639927ba70810e9e32ed31a1b7b75a4ad534c6b2a2c9951a2222e882572f3902cf675789dcf544f2e7a2205ff0dbf69b67fe22929ddbf6cd6e5151db3fd8e6dab66ddb52598bb0bd7d9e955d13c8357bb61c047decef9c73cb36752d9522ca02a77e9ba4e007a1b8772b9b1cb77803db1cdf86e3baaee362ace3ce3a8c6d94734d208eebba23d6b9c318c775b7d968c7499b89595fddc8c6e62b7de4347fb4ba11be8d66d91cff88f5d5eaac17adcefad445f8361792b24b01c66e646f7e35e75cadceba951b87bf916d329c3dacbca93a4e952f9e3d54c70af0fcf743bfbbc10e4ac2a8e9a106259a6a30c40e492d08030653982fa830636fb03a2d68c286304a6394400aab0318558c1b5610b5c54c0d586ae982dbc2cb952bb6060f2805f50086085090051a25184115b11578e0863ddf72cdc3a0b2508305582e13e6e2ea722faf27a8a079844e912ff839e18c9762ec72fd305850107d3a3229a5b4a5c33329a57386286f1adbd4b822f88b4bccc7bcbe809171e67e3a1066308319dc6aadb5d65a2d7de40e63174e7f3344cd3066b395da0f8aa182c6d7b64f368535b395521d635652334869b3c43441676064bcbef818971809effa8179a0b4caa0517391b09274c5a5a02818547c8a5d2297c804d1ecbab4e76193d8658628a6e8c40c63b643910f0a92d0ce9066cf25f055efc248544c970be309e565cf29e1ebc99e8f51863d9fb62836388120661d679bbc719f6bab609324ffa26cd5b56c73b86995feb46df3450922e7375025e0b259cff1b2332a809be7bce626dc59df62d62eb3bd0e13e09bef759800cfdb6b9afeac703902d31b8ad175d80920eaefe611a05b4a8edbfbcb62e521eddc6db04d86d0475bb1ec6a65594aa46c36e3d8617baa57aced3677dc37eedd6137b45dcbdf0c43da59f66a18ae24d25c71336c284aa4ac0eb7262db4812f6c7889a4bfbb39dd1d4b24782eaf20d02449ebddeaac434fc926a5dedac36f96bd4dc45ad4f4070314a41d03dd10b0f138373404ac705e8e81f5ce1b92c27dbbd552585f5d062dcb2d7f43aac3a8bf21d576a1b9cdb0a36c2599d35f448236f90171d35f44da753b772119f12a07cd9774c1107713d5211c426f1f5065775f6596a6b79a9eb03a2b4b4969ebc5572fde3ea5691886b6738f7a6875d6ed3f2b48dbea08d06df5d097b6d552bac32ee7782a47a4cdda3547a42de5e639cfa199b09e73d90ddd3ca789eada6dd6f2eab06372f39c9c9c7d31d055f68658bf39eb25c002c505991d332e764d6e76d4526c0452dde0ec7532de2a8abfc16e88fbea36774d589f8da68756d71a2b472fb3dee5cff5c9b0216641c882c75c406d94bbc9ac1bfd459b3f6ce1d6c9707299953f2b4838ee23d2bc4b37c9defde6f84ede6e471638daa7e2eebac76fac5fad752931d0ddfa77e3f0f6563f40eeb8d29fdcab6f5cfea06c2e8a2a43f1b27795cd0b5bbbbd97a1fc91e190b5ba71564b47206e9ae48b9bdbdc856412ee5df6ecf190ea9e3debabcf72df70b657e5d50ae7eeaa5577782e7fac7faa73d7b2c7d24338f7549e6a75f89caf3cd5f16d6eb377e3ddedb79c7c739cdc3ac769f9e639593bce8da6a9f4d0cd3d8ebbea387b9c1e627d75789ce377d95bbd3bf7cd266fc7695d95bfb8556f1d276fbfb1d19f95bd1d6e57e5ef861eb2b9b7fa567a8875acfad665acca71af6e337e97ed6d32ebb01b6261fd5959e91828d7e5edb06312c28e31c429735b1d01b9777400efba430b1c0fe7032c7c96e385663774b3e373e40e33738bdcfe5a0f889b76f837e32ea5303563fc1253293d3f9d9de97ba189e92facdcec4ed8ae6961da94ac54a7dd085d707c2a03c2f12c098eacc304584a03b27fc39de9b8458b7c55fa6d76f84d38eeb5c7b93d0ece715c38872e1d97628775131c1db1e4fc669c692109378e2cc9903d395a96e1e814570bfa581d976aa10ea894ec98a972182a44230000000000d315002018100c09450271300e26b2aae90e14000e7796406c5099cc634990c4300a82206300208600630820840064942aa20301946a70aa9e4126bc9c963159da9b7e6a1782514880c1f32f774369bf5052c700b13e82cb399fa52af277b9f0d0be18b0876bd0d1747de2522fd6b50b1a412694da9fa8f606ba690ea60c289d1859eb9757a711b4702dc182bd32a8bf5bd29f58d408607197163c02efa19be661da82597e4de90c15f52dd204b4dccbb155ba44abc55220889d5250d440e75fd6a27da565ef7305e2b31502c2c19f7d8e10781857526cbfaac8d603e3898e6577258a12a014bf51ec1e95ca69561c00a6ddc5a608be431d301860c8ed0e9787ed2406c2825bd58d10a623630f58985a1801676ad2893ee0aa3da594a1d5b4b63cb02e826708cd6cc5497ba94dd1aca2a9dd5dc3dd3ebba452329d7322174473a262d4a6f510c7818802106581ca7f830da512e18d059778e5591a15aaba37ea057c195b8ab37314dfd9e7993adb4ab593cd51fa3c57d402a5b839d5d9a2492cc4404247883acc4420601484f3c3e32509d7f4d2c15b8db31be32fde779d62291ad12f7c08e24d5c84601ac8688684c6819f30fa0b6392a7619a5db3e8078d97710c0c3f517aaa1510eb405bf551329e288bc2ff9056fa88ca11fb7dab724506fdd8fe764cae53dbd5447e6898da82e3324e87191b76b3fc09ad65d42f271fab29f0b484c439502f24c003fc647a0bcd03911591df42bd02406a38b8924d50c05a00f190840ea00711de30518268952e84556ec3413eb6469482923289a966e568a7dc7aeeefdf159baad3f4b8d5e50bde370db8674310eb77e59379d9a708bb8de482ce0fe90e0eb41e9f366bb18a15b55730320d591aada7180f4708bae3668f6e0678307549de09f455d8c0b6cf12b9aef5a36b5c8e461425e7cbc1261836083b22a328e9ccd70fe11aa5da44b57014ebf535d503372bfe085f122aa26f168e462e048cae005da3d801e6ecdc0fb443e894dad8b204ee41b52f491bf611459364f61598c5ed6f56646f12232159dd37a3f51e093754c77ff125fb62f8a9a21c86073ed636a8306528554bca2a25db1278d603e83e8a7aec0f352028a6792710d15705a2807129821033548d75ec52cbbf0c7ccb466dd056e40db05d28c9066c9710da27c56ab21b26747771cb5fd6ec1d4afd5824a310322a27c516079ad0322ba7f33d30d6d017b8c47e0c635085a7904eef619e2333c063995a1f648d4af8bfed8937aadd61cfc061820cffc6434bb763c34a9469f74464d9b11014c0d82b88f6c3275a18af3fa2b053eccbc81f34f059cf05e5fa10286f6b4eb64fc53d442c55ac87a72a648b153e835fb66d132b61ad1adae757066402db534052afde3751c88106a10cadcb854350d5eb2d0a31d335feffcc303e46f324740124ff816f40f4aa2d945578ddda388c22a4bde6fe2942a09285cfac04877883264e0de93c8f505097a28be9c0ff28e7fc4ac6c8de4b23292e46474a3888090fa88f0a431e962ca8fc4829dff3d90755f0dba504acb0ff256e4643d7c8a090b63591f5d81e4bfd6b04b0041be29c93317113bbcfbb55815384933a3291b2f08297ed41297e88123c8732329ddbfc545915e56a7550fe97e658ced39b28d4099f935b00b0f5e443b20b0a2cc3f68fb1b4e3f42936b35be4836c0877ab9486a0f5081f347c59d72f0940ec17b1deb8107098a7392149972c8dacab1655b33e7f99a6adb094374b432c0d9298a45e82d9906443b380d9063ac1eb9a80363b5814adcbca7232f47d5cc19b98fc726f8ae8f807244683456a59b43994f645e2bd28e0f4ad7926517f646f4998c70d332c7a56300922c1a5e3797e0729fb9d1f8aff46695713a835c773c1451123e07a2a04b04f943c88c9ee0b687c1d6aeef18e71e4a3338505bf8109201099ad46bbf7c2837dda08a6ab02508bf0b3d5a1ae930a8ef2c208a23340ed3ad630e46967a73cb17c45e148f5c1c9a9cf4065f9ec912ac3f9ee6d11830f61eef026e2365957a02f820415dafe6ae30738c188b31204a109a36d95b03b2215fab322183f36f5934ba754137d1b0ef3c4afa1758f90fde37819d434b84c74011bf0d6f5883406a5742946e507d3c0e5bf204f09860bd9d2f2ce7326f982be1383b0d8438443745c9eaf514c7ac95db1d521151327b6c8d50063ba8a5882fc7372186824a45a886b788000e2db420d801a31c8fa1d76bdf45f84ed82478a347920e89bf93e1e4fd84ae03e90aa43c32a7c4da37295fdb19269b69b50c0fd179dfbb1f403f33e297b3bb1df44318c470ba591bb154592d76eef54e5b91e54aca063f1505188fefac26c045bd6d53a82b32c5d48e6995950a0a5161dc71ae261c9772400637b1f6bd1d3a8c2c58516d98523e787c62e072c7f421e206552147ba2a8286f120fc768f8504ddd869b109e1929ce61a2c7315011398dc6b07cf5f9f80647806d0d56fc90fcbd6967940dee1e67a841c703847d26dfc27babc58244805092230346d4b6c81c9086625d49577f793a64fda86db5fabc144d5e47de5513b60eeccdee0159c5786939a7224193acaebdcdb915b3ee50f2c71720b3158c7fd940343018f99931b24577086451b9856323104308868701b0ea87945cba099cb6e789a1fe861cb7a2336cef279581d2e13c1b889714b908074e2028857e04be0ba074e77f7edfc0d4022476e3c2034199af049d206ea9da807521d3741495685d4d77ce4dea8ce1854cfa64592bbce8da72af921d075f4fae73a15beb57742f29773af7c84e298ce4496ff7ad524ad47b96d3d46057b4db5d3de2bdcfae76123ad6456fe4feff92161ec38e7d4374d572448732fdc518acc302e148ce9b98951f47d092036369e59f3264b60ee3423cdba8bd1c846425ba95cbeea80b7e3e296b0badd524bb8dc4152fc7fa8e9f95434e31c54367177a2960930e2259e490e9b3b03298787912c0303d3b43585bd119e2686ab6899081d3b6bfcf5bd4d23a10d760879f7d574848956e50e411c831b2e5e698828b9b6171471dbbcc1f9f03ab5c84d9d5f7760b13797d82e5755b71f7017e7d60118f914b1926e8ef11c59286b602ac20fadb597cd132fc4c40c4594d3b7acc7e809402db5066463e7e5d48c3d8bc678e8519807a0a8bce6ad127a1e24d452d4ca096b373390265b2954b8ccc8d4b974fc361668f8b47580d2ea00c879dc83e700c917592d8b91167bb59981a29817f69b9ee6a806ff97ca8c821180d09468c0106221275b05042773577f7eb8fef0f3fbf7d489b68645de51e027a1a9b1c9ccf2616f613e654ecc0082bc844467a52a42f2c00c1ca99479ca6fc297dc4a235140cf204ed843676cac14cc7e770000d6fdc65404f03142fc37d715e945950341ad1c755fbbbff3702e311cb9450f23aef20dcedde5e52e6f02e4bd2fbe7fb0c6882ae633e0626e0f7838fee265aba10dae4817d1eb425c0d77b1a57846c2cb6c15bc93833a7cc250ee94debe58db9a8c0a6d83a014bac2555eef64801b548eb46f7ba60d2993688d86fa79cabecaa0721be4eb4f521df625cf7fc864c188afe2baafcd7e99e3e17730515795485e99253cb35f7b10075c878086ea5b58931bc33e6260535fcf1cea904697353ee7a66969854768685b721c4c67e97f950891cf6631f34016c8004ffa45ff2561350747fec1604d219e3b26e26de4a9882b55494af9706cad4a88f73e1f9574cfcb85db1e183137ccafa14d378701222c9be12f621639bc9480cdea0707a13208a91ac6b208c9d1f20981a3163c3da617b87db3a526af0625acd3331a73e0960c6110ac8fa81d80baa82b6b5480ca1353523f79f5f314ea0a8334e2814371860230a1ed33d3a98efb10b4a7211e67aa1c5b9908ba1ca54c57c877c42d66992ca5e601bb3ad8c34c9d8305b6554552cf3de2f6f15a1ade069c88f83355cf4a839a04d83c11e395888b6275f71518ff1a0679768233cf483ddb2bb3d3e326e6166defd884c6c16b208ed2eeb989469529213ce01b111826905af023fb7fd6146df1277b309dc28e102203045ce2080d9cd81677fe30b1731997effb4fe988f0b34f96c8920c117e18485cd32eaaa74c1c1f84dbeea2cf5096d66ab9cfde0e65cb24c82554e9165476e9d4b935570cb0adf7fe7c15951bc03c26e12295d8da204220424782647902575b6560e172535669f034f5664e8d9389e9d93f58bfeb290c4b5d39084f924bdb2456a75b7dd6a568be9531f0a69d372011842c20567a6f1ac496c63ae4f9b48bb1d285a412afc67284a350d5bfe18eb049f43268b998e6980155e8f231c49a5bd4e8d4e8aea4b2ea83d252c156b08c973bffc8b4498bc9ab33bd5b0fdd317e160b031bc5753b6950367b082867da2d11a5d34c5355dd642993c54b5feacc3b1448cc72f0ad4df1159ea12335115e7bafb71bb0d76af44c9bedc2070fcbbfb36fcb88ab86684929e1053925736ad2e88011c731b293e0fa73c202fab60adec36dae4cbf0252d6c46ea76cbf8d2f59122ba5c01057595955ae2d9acc87ab821c423460fb5be1034fe9aeec9561adc80d95387d82b0e924c6ac4af58f31a20541537ea762b2fab57422b684ca95204188d823d2f7c9c900e0ea445863343847e43520938bab887d81538e8eb936bc4aae9b8a9be5f18cec1ad2cf1a767b2030d5f1d85bdcfce8bb6d21806c9e7f0b9f77b86be6f633d91ae2ece2e826ac4c4e31b875e25e476b2ea89894b6805d349bb99d41c1108cc84af010bea728dc5768a9dc8a3d8f977299e0f76b3ee3bb3d4bd727c569fbd855e410b93475dab51d61e42a40753011e3190d58b53e4f93b6d64fc384e2ecd106f16fc0afcdfdd70849d953a037b60e9eafaed1768446fb468287d11f4bb42601f8e3e41841ac58826c6189acda3fd0a70c9d7792f368791418c8e39e4e26eefadf0ed092784ceef4799be02370b075032c3179aa66558db0a2397d5c8a87b50ac49661d149e32ebeda5b0edc419afa4c63ba19e6f71eb3a1c2d2b41d5a4e357eca4c3974bf00fd300ff66722210ebaa53fc0bbb239ebc3b778e603c36337ef5deba04ca0c9d388a917907ef7539abd1cc32f9cc4f364449ba01e3b7c11dec29590ead6254c078bc35cc368b88a9074775575c4b084718b99af999e57599e1c1f619a740200aa6caadff73508544da8729054cda08f6c93ec723ef55e7a737fc8d35c10d6d8ba7930755c01014d1a930faf638b151c41a4d93a982645264c9c6d1a5dbe08b4d937bb900e7acb4ed2560731fe6bda0f946cadcbf4708fc2e0a2b097b0f259ccbe97460416baf5dae28bce08d04050bfa6cf886cd577e3604a42af3c3507587519e0ba5dc98eae3c1e5e95925a5de75320d65ddf7163de8d203d00d12cb197c4e1093638564893641620fb3bffdefc88119cb59b7a4790a5cfaa07e76f1d71716da62bd8c250e082ea9470df3d462259532917fc00b5fa8d9f9eefc917de2483cdf2fb5f6a34c0fcc3bd0030b7fb0661486fb452f6c508376b55ea3c0512aabe3cd9e37676dad4f1119aa4cb12574eaddb5ab31a4f0caa7bb268493cbdaa9e4ccca2e1082565e55b6a3d7cb33282a6a14e64b95a7c4272c5a6e43b1213e360e6ed236d447082d97eafa04a58645371a6990a78a2fda40f12ad972e1e5d6aa9f0cd3498b93096ac25c89594dd6f262e006f185e3a0e811ce53433dc0cdfb887b3856044e7f3e105b0f046f564fb1c9cd8820c381dc287e714e0cf0e14289fa7db38944ac40bb9a15b285aff73643e5a83edceb5cbeda46b44503638bd7fdced406e75a00f1012f6742c4395dbb78d0b62073c117525475de6c41a9979a659ff6062790c6cc20101e43f59026320631cc26855fdd56bcfb43692f9e85e9390b9e10d720b74c523304a049f6d5a25595cbf820c52a41c5b683af0081a627181712e3597b6d002a123d6a054bfa532eaddaa1f9c2fa43297d1db09da529ba77277c7fec848d095121bcc1fe0ea2e4d71b7804deeb1194f65b3a3defbdeb6ba0e8d7be27454f985105883d9f19c009c25ed2f558c8552055d4fc4e7beb8673e5feb4354df06238859f31b5cd5ca2c1985e9d7dfd6ea5e4cc78b32783196a30052813c453d402707c9dd481d22d66a6c454cd65959d106b3961c1fc04838219b66f3a9c0d745895f849a2d68e8bdd2768ab90099289e4ff875b174dd9d0aab3844220aed91f4ce8c8165a5cdf54cdd40ff0b4b683708b18ea4264ec849a159cf2500a62320c49be9d0908afc423d8ccf40f6f16927c7c9e029ca1e27ca8963c20a8f15eb27cdc542102bde92dc18bcab89fd3cd6689f698741cb659681d73beef9a44d50cabcd6ebdb92a88f164b6352d93b05d236848089ee5f1a07598f33279907e2c1b9fc55bb73df30945321dad79a141916b23296d07b9d2170dddccf9fa146534333a9985739dd620fb3bb3c88c2822f7bf4133e9fcb6cb960605fcc06cde5e5b94186d045bdccd248845f7298358e49832b4a3ce2c0238747aca0e883ae39af6f4963ab029ef3d78814e6dff8220f5ba41be765470fecd3209ba44955d00eccd9fb924ac175967c264c6a445f8fc5ef76651d571fc7e4ff731f0a564b408835144d87b883d7aa3f24750404c06d78c9f065b57a7033331883a26fa1d3de69aa65f453f6f732b623b23a21d6cf1fd9a35ed77b144ccc2cb0ae39a7b744f8eb4856303818e5c82d2018bf22a3cf078e359753930dea267f48e4e9d3db1d5002706c724e494e462ad471f7eccc734b7dd02a384c4f402b662ff33c35be93c06889897d57657e7977dfce32a0f6d02ce3adc77b34af9f22263ab404f88d1d9573e6a1f1b1607927e81fd79a4d1e8ddbf3a2c8e1a92f8141a22c1a22554bc88ba5be93af5a54ab608d1fd8cf2ef04404b5f8aff50c82779c3e0e68150535ce4e04bd97aa28541bee0e214e14a204708356bc113cda25fee0b1e50b9c00645de6cf6ae8c113225a3274a6303b34aa7408b8e5c5e35c5cc80f10f24cdaa6c87f18b4774405a24e301b712408287d9e48a72daa726d863790fb46dc3543c8b7cb7a3a1394b70afe84867141a6594d2d36040908abb65d6eb539929de81e27217e49450411fba87c8a175df0beb3f81c64559f45532bae17626adfa837056872bc8f51628726817274d0c17b6703aa0c559397133aed1a7b12bb1d6029112b79cf5a26529069098aef22f0331c814159d4bae0cc91db603c5bb0e3481b479f951c67a06f075937d19a4e2d4cfc7fc2e604f59c383a88f1775fbae93a4c22c529a4581a0ceec498a782cd6bc6991f4c08133f7b14a866a7b13861ca448d738ab9a8e60a2015dbc2db192097bb1d87df887f85cfc2d462cb6e1c54ca4090374bb12e91d331c83da119cc96c8dabdd859546c3c69be55a6dd44924ddc6941e219f2880595749d9d70f9ead23a6838d09a5c48cfb800aefa1d9978105d344d13eab180a0e578018e103145a6445950fa86728cdc7c59ad19a7030f34b25f27f7fa0651f8b667db49140dc1596c6fb969a6fd2b99d1ca04e3b380172b77aa7262d428127937d0d8dee9f40ca82450a11a197bf7c4b5db0b1bac56f7f28d629cde4694893d7ae88b29a01bd2a2944a8afd05fbc0adcd3c32e58047433ac6449c48f360bd922d28f180b8c88629b92a95bb77501769136a98b3f2a12eeee8e53804cd74351a6b2264c2444a240b25c9330580d820ef0cbd726f6e1749f6faf279e37d1663976312a4b485f1e18abe117d2ecdf162a1d116b18975bc27b6e806df24cbc0618574485beab41b8dabcfa9e0530fe93d20147ea837038e13072a496be7c23ef60a074574888d979b6a690a4cdd5b0f67921f20177ad9b782d86ac95c2b4acd297fd34d84e81be44750f0503ae406e6d909e9d0f46a9009484942124af377600b4a3037f68948f14240406a38f501aa1257f4936d3a0736288938b80a2110c6a2ad1ffe29445959a62ccfa95f719235e4cd94c83a863f75098cfee97cf6c7d5b0dad3759b35db5ed8128cd0322919e2fab0b7d08ecc302708b99df12232d1c824344fe3670a0c21534f89e0bdd2f7740dc2ec794de2728657cf141e0456cbe33026be29b8e544f6a4e655bc6506cf9e9540d80a3b92bfc78729e5523c8f06d4a942d72fb1bf7cd3451a01c0904e7980ee45956d23cfae32144941d336cd397e37d509e6124216742ec8548210b9a6c9cece18671639c6d704f63bb6565f7690f7678721a690af9fc2987d0548595fe30dcecabf6192d83c78f8189260caee6bb691d9e5e1c388a1a4089baf06861450ecbcfb02efdf7cbf0a8ceca30eb394388959f2f2375cf46470a9dd6671e306563d697e89c7b427bfa7897273292062565a4b0a3f85fe76a015cf2ed6e4e33417aec98284e41dd54f517c69324cd5892b9cf478a8440b876e37d2b6bdf2d6833e91cc56071aff6dd82038f0a88bc643f7a64bc074518f8e200719813b7a096f4e435f632ac612c6657a4ebe74d23aadc5b5134dd160a474a1a0034352b1daedddd909917d152ceb277cfa11a74973f15c2803abc6d71fe45cf1a879adc8824246f99a8c3e00db7b29c5e1b3058b7878fa9db805fe1da0f2aab3e50f687b5aac7023f69c779d41110cdfb54beaad3db9b7e139be161eb7b381debd203c96d3b71dd5820e229067e67bc44cee6f5416fe2043f70a46a9ce7a88a4a41b037f501374cb9968144aa8606ab69096c65d3b6a6d6d9f9bc845e2d09011a0961b64011f35a737b46b4679b68599013b56e54b5e277eaa384b819ffdebce902a64b8d002c094124e91e1f17d4b2bc8c20d23510b6c9817e45ba782a94284683fd4dc18e474dbb636981adead3251354106dfa25c1ce77da0c0130ad39f1977d592629e1125846d6a56336a105d8fb58d1ceb641ae013772cd3ecaf0b48acbc4bd3ff9b70565709f5989960397a587c4812e8cee5d01e27ece34f5b16bc86c37312ef8cd7307aef4e6b6b66ba1fed527dfd5ae2d9c77f3ae61c6378422f422e9ccc85541904d391dc4fb7a0153baf24700fb94965adf575e1596c694a3d3a381c6d2db755f630c490017e9a4ddbc505b0bae695a6d42dd2a492782cb59c5d5bf5922af9b5f3202120c53b3eea8c9f2021b0dcc3c943e1ddd0e843a7d9a8ee0ced8dc87a668f6deebfa164db991e1b032fb462585933b6751032b651960d0f2d026eaf07565ffa40cb8b25ed15fe84544923eb111e29d2efaffdbabfee193a5dcbcc5c30def70bd7f585d37cadf6f2a8897b0a5ace33f8302b64b284f547a59ea976592065a23280b35f2780231becc3198b035913e200e6e78f38a3ef9ae646877463bb09c326537937639be1320652dbbc72cf2b645055171bc9601cbdcbaa70eadbf64c54fde5f78c42f17d03351f529b2f3cc55b1bb9466889013c6074210694ba8c3944ddfa8722929e668ab163625f7cf03d3a61078fa779879d71879d9f35c1829c26e55e83adf9cb255ff4bb0ff7fa237899ddbeb2b71fdf93fe59de5f6418793464477730cf1faeff90781cd1ae17df7ed625dc71e3b4256547fd8586a819b0f26dc7f49adbe7f09fd7260259d5845b50c43071a2b2346410b054df91ca0b1ef371adba99a9d556be71a95ad49980d26185a8c6ae7808a999a34bb9375b423557648d6ec92d1a68e8aeb3152257fd3573477ef62471562ccc4474707c056d612e2f1863fffd44a0685990876b1a7c1a6c68fe27a1127102413b432700ea794187c6f042718ff6f6c0d5a532514fb5c83aeae80856d64f0d5c169e4119e56cff1e2f878855bf37df1a5fc22cc0c6c98a933efb111054a7538bc1c32976eaff63040876a34e60eb67e442f4c126442abdbc764ca1673e9a8a45a766e22c30224a2c41bc9a86b10d6d741837914eadb275ada129bf185b0b9c46eb35afaa91f133d5428d1fc0d251ea7e8e1e9db869301e22078810d9a459a8974cb822724f588298a4b0b25c69fd6888c80b94df1086714c726af358b756f1fc6360b469571aea4556fd16e8a2760a2507d92a05d2eb7f6e35e9ff1ded52175d7b163de694a05307c59ab70071fc791c81f0b8ff979e164e3d0e1bd3fae6d44677e4024273174119f23bc87abf1189d5a34ce780d754415e295301f711fdb51c80dac45cbb17f40095220a97a113c50aea2e979e044aa17161b2ae9289f408cea251fc0a36f84174c70dac9d7d4aadc4c3ed722e7c2ab3362e7e02751cc9f7145af7821fa032d5b3aba9e7fc3fa4e358501eda77ef6138ae9ff0e0d1cf1bfa184a350a85b5fe1e75293357819acdd3afd62137e1ff59077f0301d32eb45ce0f98d46a4d46727b091a64f9a798093b97b92eab9c249b00ad3674e842c58fa9329f1378297a8a75f1f41961ba3d9ebaa2ac65354d92af723edb1140eeb2b557117f04bff12fee3807f9545998edb4cd24ae37ffddda6a15eb750be48630bf0147a7c0f44ed909d96bddf04658fe009600344284124d14327f0724732cd16d26d9c4271653faf0646318bd4c9198fd8a7014341a0a8bb02aca93268aac4b99da82c7e5587e2cd51c45214cb7229cf9b43ebd20c72bfb14f73978d1a42c23e4a19434d1052cfadd44c3b45122ae519d2023d8bc19f61fed2f1cae851b94ee122aed9c09884b283524967ef9e0f80fda203827de0562e198c2e1f0936a1d0dc14c53e4a64e2de08e5cd384e32caf568e4954579c32a2fc83b6aad4adaa702b0e8d64ff2ba18973ee6d49a35f3c805393b71a1c8a3429d2ffdb623b166801409c82f00374482053462dd309db084a50b73f5ee42de462c1d4faa12d3fac9ea167dcc4b7803e2315bcca7a75154f5fe1802291b5eeb94647c580b297d485e8cf17d47b74dc55632fe149cc098dafa4a8c577c0d713db689610726ef5bb95f3e4ab1efff63516dd143f51ae6766f91c826df31f9a80e75b5ee97bea1e04d80677ea5ab55e08fe736d5854746185d1adc0ad50f3867058a3439d6d38618d85018251831d37007818056263c73993f21dad787ed20e203e3673dff800071902fdcc2089529557c86a5b842e7ef5c7f84022a1149122a8e17a059c551802f07cc64bfb93b69b116c247f26b3e6bc5107af75af33383b87313d3ebe6753b6d0682f12a4ad0b79b4e23a8760a8bf7d03fb4cc6e65c954fd578471313115ce7e2d22898892ea363828f9f1a5980509bc78ce5d32e048a0d86533c881edb05847e2e922fb12283e738f69c17d79229f848e923062247ab94b8d17ed6012d258695917cbcdd64a1913aace0b10a61c6c947798f42ec81d84f2860390d48ae9bcc269998f2c79e66ec5bea917a849283c3c705851b90043dc9c784b8e5762c99b03c4696258c194991e64ea43ae20dc996f1f86f9db8e670e56054d126a679c15eb911fc048700299b6ef609c9719e946381850d8129079a058ceb6252aa2322b2dc797415e196663f7cd40c5edb42583769dd8a547a4a6cf0a1d7f169796506bb6cc9eb1e6ab4badeb91d4335e6a9f680cdc4060b9e2fbdb87fe950ccb19928ca1742f99aacbb63c2c0a64d7698f654745d34155ccfa44c18c3a841390b6d38c97c0c2cd0510582ecc6bc3ad17b26ca34a2fb215b1a6e112ed79508be134e09ecf97a84ea5fd9eea2c610e4aa977da1cd9011207630af99bc0ebfb13da475e51a8b92bd80ca80017c1adb0b53d66d009cf97e8598139cc072c423441a75501f6f45ad4c1afdc6b7c18ede322a5adecca571632a7ca23e259d0b3aa7f207305f23a17e45d4a20deb6e684c9dde60ed83b6e121ce158c704ac6ee642644befb05667a4b5fc4c01dd37e4d476adfb6ebaa138e93648f17f90c963a989aae13e793d29e1488050d44ad528692c5ef8444193c4687d984ea17efedf49cd452264ff6916c5c33a5306323adc1f4859fc30df9c4a09dff6eae7240a7ed4d6885fcb63ee56e975eb90915dbe883501308aa36b0eaefed2da7d5f5e7f10e37c544df6f643a7611c9947346b59d71cc60c46a1d5d6c41d450dbb410cbf79f17e202c43788d89b275d838352962450fad12b2348404315c347c994688b95544b9f85be0cb3ebea3d961b99681a5b9bd13c329aa23d0a6d2ca80551323394e1fc76ef27787d0eb4b89606161cdb68e5f538a41c7528bd9b525624db2efa1dd18be7f14e7e858cab45b2306e726789d1776b30347fe90d023495b923b74d674739702c9b9901cbcb9a4e7d3fbde930d6ef6c1775597df630baaf859171dc51d1d93fad28e8a452ff234801a9ed7d2cc2b1684453bb99527263b9a8c11d654ebaa5c4d098099e49332a29dfb6113293932dc50d822bd19af295c1fa8265262dfa0a5973e750e33ecd037f8c76538ad373ad9f63b9e40e4dff4b85e67f79660e27ed1e45d10d710ff4b92e0e0a887bc72c915e5fdbe6a5f2648ac95f163a8edd061bad1be3234b27308c611b91043d99acb0f64ef6f6ad04d56bbc4e779248775e1bc128fe0118e33d3d6a44c0efa759e7d857a77b3436f6f03d455a29286c20e6d9162cab9a5a0d24d1c31fc7db8964be9f4fb6d7435a0f2d165ba0f50010af81e0527a6170e738ac0d3f3c1ffc77b2cd750627a768cd1200c86597fc8d7671a41f1fe151ea7437534874612c25cbe5a29bf7192c5670b807de735659a91990d10938e0cb80e9a3ec6d5f53401f3670b9f3c2cc31ed0e6cc1311a0eea6989f92c9e8ce89ae97073a0e84194827076ed2e7e2e540485e5c5cd3c68ba16aab9b28346116a97cf101c815257cb0012250658c798a256a8e8144d3eb0da89423721cb0a25132217968207a076f8e0d40dc0607358a35963e6e74ca502187012cd8405efe5070064d9f403f79cf56f66a0c0225a7c7a1fa97d0ef6563bb6a42564e43539fc60006a717829a05547a3b5af02c43cfbd40f196ccd5c9e4f037042c46439e805dbeffaa7ad0b5d3018f2f3dc72ec8521e1c5fb974b90503d5c76b1a80404695f710028f69e1f87d34deda7b7b28058a70ad53a5f74eb429c44a52f046500f5fc9eba780256c6961d14835dfaab413b21fde04a2800dfe89096bbc878648bb6880680d9047479687fceb201b1d57c0fba429aa967c96bef8e6f0ad7136670656078e3ee3fbb4d65cb0af1d4f2a0a1b85fe8e0862267f748390d8d45eb1d16e0fa3631fbe0933de0894d3c9697e5ada267cb8c050bf80906ee0cf5fafe4517cb801a1951b17088ec2512afa0f4be5ace3ae61c343c627f78e354bb97cd0629d1a6e924571008a63440fad7d5f69cb6378a2f3f456d2ec4f617f5e8ba8504dd783b650c988e5b49585048602970a1b5642a00089405d71ca4f7b40f617bbf36185784fe04ec4d9b5a83ec0fc96e1c76868e8ffae0e7532d6b7402e5864dc77eadbe9275e403bbea8ada205f2550c50880e9b0342851c5c53007b7d8520847fa489c60b3541aa4c35a7856af6496392486cb14bf83a271a3b693f742bf102c2635b03c601cd23efa33646f5fd9b46fa5e44204727ccbb221934309c111ff8758719693b6ba2e89d12a3ab6dbc7b243f4bf6c8cbbb5d257d00e40f3e1fa92a9b8e9e46493256de71d1c9163922ab0b2a7d0a9f5cc9a8ae335d000918099ca029229a66de2aa651815c8428bfa1b94782f799057549f606fb8ad3d58cf1a37065da603094eea4653bc593ba402fbd7b20a3366c246981378dd291aa7b44442e78bfc2084ffdf38ee82b8d612c7f020babeac14ce5e3ba1fb707dc997652417564188382db5c25be645a6d41b16e817488c1a5120fd866885048788d075226599c81c1df626c59c5add1e5fc1962baf239237f6430c0fcbf97ca33111ab99144d798e18e664d5f8e852eb48d85396620b0f19cbfd12e0d867aec51d53cb01a81382e184fe12b8e90f7ba568dde8721f7abfc30ff322cf097b48b8eb1ebfd004fa5aa9600184ba35118a8cad1620626be68c7bb9592970a2785759d6e86b80a0d39ba9ae6823d4c810a286bc913df00452d023c6e0eb2f04453fd514b413683505c662c005bfdd9be192be185add7af4524807145492000d2ba8426b0bfd8f5de09834e0ea1f36cc59e79388f0c9b695fc273858ae101d3bfd2464bb53854f9a82418dc01f374dbdfbc5655b938c3da8dd6a0a0ef05a0bdf496a7d24a919181709c5ea58313e4f5aa1c1cdfcc38d146b4d784bc92e95c47a132b7023b613795646873fdbce42803e05d0cd01a9456a3144b18020e76e185d232c6edac45aa67b135c050526101ef051fbfca73b21c0a02cc5a9516dd0e04ea10ba00e3fa2e674c251450122b9f63a24cd1f262344f9350647719dd8fa4c24eccbaef7c4768a043cae22a5bf7a659198f14ad7cb7533818cac48bb6fab60a25723aae79572e14d3ce7bb56f96c38ccd7074a86c5743ee6ce79036e66c7dcf7e119d5cb789e85bb10dcab84b35900e4d40d66c1c34c4f2f821968b3923fd314bdeb55be17aca04245380daf4d6624866311133b40e4993951ab5630f9940e8ae0502f8d62678ed5278773c3faf6fdf7721aa02af6bd7a7f8cf2f71adf29087aa8b6b77715765826b9f7cfa73934d1c16c50e8f9101de132a073a7b3fdb97ba216314bef4a53bb8dd17bb72050d5e4a7951a176a57d6a9b4eb16bf4aa854210b7496ab3a461c29ca6bed739e81f59e071b53447c40be7ac99bb6d166de1f99e073f392a193bdaa7eba1ef3f7f5bbe1c9e92e063a8a816e8ea3f3ff2e70ae67c4dedde7c332d6b01fa00b3e8c7b168791d82b8be9a15d6118cee92786ac1743914515100db77d168a298b3034ea20e4a4d5d024a470048598a83695829585f56d995eaf536ee0dd0089fb9c28184563dc83f1436837b83d7cc3729c3e9e10f26fab415d501835712bc23eda7a28c49a63a5d07d202aa2f6fdb1da32dc38bd24110eb63c8102e11d5ed1280b7ec3f741b7da61e7eddd1b044a973750132aee7b96c2d839e0d3d41d506bc4e2cac0d001170cfab6e2aa593b166b19b9140a4f472f02a51d0b38e31165294dc7a274ea029eec584048b2332d551be042a60c59cc91149f59b46162de580a2dd2e2e7d225f60a215f7b96b89968390c1496c075d1015fd598842b8612607d07212a77294fcb0a3ff122eff95e06a5a208bfd4a6a91622d34518b43f5c869335d82009033422fa0383c9a47c21a2f00a80dd96aa3545e1928fc0e47935e2305da3e61e48ca4d281fbb197c0b91c43cc1696ad70d51197847dad972cf9238f10ae36552ea649d176f99f987554654a6b24eeb74515a2e909a2718594f4a3b47b385aadc00ea8c6ea49e2c2f67233f66e14014592f92cb2851836052b393df3df7690ff668a34144b2fe60917fd8ac04d177c3669d12cff0be796aec9e94ccac0f0e024803def499f42c8edcca491055984141164ad60c442a9e344b62ed9d559095745ba141602fad45d1b0562d185e6bb8421ea34106620010d574da18ccfae55d55f6a596afff2b474b8ad57793be1d888fe579e4eb6047f7356e68ee30c442403bba346bcb94d66620b8d24ab0382e818fc5de51b503c6f8485959aef311a511dccb861f546a9f074dc0ed2028b02a44805efd094051c4a1e01d221b7bbac524a60cac58667888073bf6c4803e037cacbd4736bb1759db6fe3ba8d48298e8adee76750e04a2a4cc2c3bb2292f3122468a9b4774e4e91d088fb524ebfd01d29e094cde2009fc7ad0ea755dc1048f53dedb49bcb3d8a128f61c8f6ee56d8ec2355bc5af5e0672a9a0f41349dc3a8abea87c0d9deb8693180736adcdd35e07e75c98941db24d6fcf50bfa9a76055313fd48a8b6f5e2ff3312b9c492c18ce7a02f6cae990e7705512441fd1ce32c511127be3b5c6041b76d3bdb196affeb68a85dd6ae813cfc7ffdd8488ca98227914a2bfd21edd4e80e091bc8c3258540f3a9055edc0321476f940732778d9108ed69971c0c79a603cc46a5530199d638aacf3edd41c8d3714e6d4489f31aaabf52fa0a0d64743e87ee2e5d8f9b1b56365dda61731c39f528f477c2d232d62c25ea7bca7eff94333e9cc22a9c127b9d5e46fda01fe421fe41d7fa742c5a847bd351e9681830a2cfc31ef7dfda2d8583bfa70189164754ed51e284893471c2c27277a98016d5a330930fa3247ceddd90474f273a09f331eb38d09bd3152b1c44f5fba780c90be1682f4315836691597fb9f43e8b3b35240b80bee139034eaf42672b3b19014954e1f04ce9552d915dff0a9492a37f6f5337d4df34fe112d808bdf25ebc349c6f39ecc1cb74af5d09c193d11b3c798f07254258fa60231e6994b609c0a8688d707ed82bbc1f2c86ccfe380785bab3864d9de6eecb703f2119491ac75ece3b445323b0eb24c81b6890d5212664a8eebf31639d2250ba96994685805762541f83e458e8f92d603c08ca8c17ed410f4807b06a68ca6243052dba3d3e6a6e4d7ac1160f9e83a6c4a9653e718fd8a0d5d0200db4f5695ed3ce9fe5d74f104b509e917a9f537191e36ed33b7b42100bf2eea794d8289967b95e7e37d79be102ad82ae718160f1afa88c76ae2b6a617f7e93bc7db88e16793d2ae0b400860a8fc6eda1952c21931799da4d7475828ac4e29daef9c261d69c4e04aecb4340959db8705445ec0069dec6130f1d18a097102dab338325abc82800d14818e9924d351f44f95773d04f1646dbffc889a88bcd28467d6eff739443b8823d9a4cfee693a96fdc15da5a0c17566ff6dd7400ef44e48088d52ffca79ce68e2e23d73458f65bf9467956c74bd504377b207b4e605bbda5c8e6750d7b5d7dff79339e0895e314a3b7bf063d8aed54dbac394b6efdaf477768559e5241082529a7a1208e2fd5ba55d85e992ce90d1c5e44a2b47a97ee4887b4ca778d408fe721d0f9354bd53cf17c969f398a76e721a2f92e0b68e4fb10891dcf060102b484532149295345eefbd4759c0db7d8cf3dc88c6fc34e8193e93abba1622f89840f2d21f72ba4ca447e0b6f73222a32e56b5193ae4f2d2771120d0228072d7218ce77ee8197fc01397ed084346eac458ede2413e411fbc95b91e78da772091de8592d8b4356d43e81d1aa8651e463c219a120e154abab04ae4568b463f104b57296a9bfb39100d49c28cf1c719806fe0d79ee3d5e32619b9fb41c510fe83ec729f218884e7bd181eea68f415fa688c4bb37316878ac5c4c0d666a5d8135a3d6c827a00e2b9433325ceb6d7c0696d8feb1c950fb150ed54a0dda88a563a84df2b5e37ea0b678cab460c84411f1538d0dd65a3ec5de4cf0c3ca8b487da1445c6e1a02b97982a719828c486b34c59ec0c16f140e44813e2c38e1da7f54dcc7c878281ab1492167021ffe40a9b5c56715f051bf525becc89bd97e543a5120f916aed56d69019d8c1d703d1a19ae38f1791c4da632f9d0632d000ebae327488bf46391d0abc3c1e98131e95823c083dae1a30860761062cd2d3cb9c60463cbcb67953f019077f52419e385f410119636220194d6d1c48cdbb04d933a1e591ee8f3df70d33a92841a57383610298606a421e121aec7e35f1222585328614fadf7674414e635b36459bac721c5660a70d9293f4fd64d9ab6265e7a4de49c205353c8f20327dfc910d531231da5a562a7e28ae1192545238bfad2e616d6892532dbc09034984b5905956b2f2794bbeedfc3678764ec104f8507feffc79df90c00d614914a394954f45575f67fe4d6a2f823bd476d5ce8d252b55175f698d725bada9424d08b47413abf077ec37c03dcb1da1303b8d97e9f1a1c227a51101f6d75921294074fd37370a0d65541fb9fa4977de1d542b98d94742d08211b97a483ff63e4976a36f51ddd197a4e865ec7c3bece84d88fb3659873e5a2cb75a18288204276614bf7b516fefc2300761dbf29007e1dd84db6a51d5d02dd5ffe26010f2ccf569eef88e331d4d6e9b1b6d3be695215ab6540b5335691721decb8bc1301c0e4eb547d008dfb916c49debf5e3d5c77a5b97cf756459c5bcde33af75a1c9f5b432ee3a79e77d36bc140971d256e6a211d5fa17644a06cbd00e7ab791aeb804920d8410b5b79b4e90cb72a6b29aca3cef5576e268f812d75bc973f604e2b6b4e5327194ce84fb7b284d560589a67898df7d06f950f1300de90fd34dc1d247abc374610ae63c971482350d986ce88f111571a4165661b91800b72be620aad9fefc82310034053811a08f0f0aed56a4d8f9206f11514d1f0ac16db842515c04fc15be54b047ff9dcad7be144dc765ff566908f0044293ae69f991733cbe38652dc66fcc9413af71f4924e36add19a907756c38b3b5b801a561953994a3befbc082d13941a26ccd8e97b8fbe9d9edc7f1e827b0ae786657d97e9fd106d8f91e6897ce945c81932437b85ad516698b6b1447bd8a5298cb87cbe8616e69017a2314f53ba2af6c6ecc00fcf9a0da605a1dd220f483d14593ff533bb2e92cee4746b4a833330c9cd3bc648982e176d41d0e965ec1318ea6809e1c64c63417f87983714d25950d26e0e56fdbb8550baade8448fa485cb2d804b12016be1d9af962fc6f715af9cd141fc0b58f3912f599ee1f8de42053688b0274cfdc207c413296a3a768088e1f4e0bf6e45a89133932e978dbf5e3a42f71781a845be8b484f40114be285a63da57b6e9815bfb9062dd707f8e1716f7b527550b807bf6285b2707e9df9de14134906eaa0e7409bf4043f20671926abfc3d6b417d748df1cc1be5c688fcc91349a224196e9421aaeea1bc67845c58843e1a92e37fc903ef76e52f1b59018f1e4b1178ca28170a622bad08664350191cf3f90500244dd754327407f8ed1b3629dc3e8c7a2ef525899a8ce551972cb290ce4b0006b9d45e44375039b0c194d1ae29381500e3e8c51d3fa971bf48daf18c8e8b9d0763b8f141961e80aa2f1ca4ea20fafba97909c669fa4fa9db27cece60092e3ac5c7349985d925baf86db82c86295973d183cea0611eb57b4f9170d6a4a8f066230dbbe15ef97d14f22308496b62b16423af6c4e3d32d97539c01735a25548d0ddb49119a6989196bc81f372e2ede51187d0f1d1c7789f744de4a87de6d0112206268057e7fb982681acdeb6511bf2da70ec3a391f1542228c3a23b9025c158b83e00f03da1bb9e3ec6c2fe0989c519a9e9e21b4f0d8e07d2367d1d5aedafb38e87be4f808da21ff13417f8be908181af67ab3b51c8c121e8174e13af04fe85b93d4651f541f6db745b3718883726a25cf9a0d9283d1e1e8d598d121859e8b509da3e64aa798ad086e29028806c2de4438d3775a060943d0af375bab9284e3c3a131d981c8cc3785faeae24622e5f87b06fc83119f3f728400fc7d790e70572dfe909d11013218ec3e5ba52884bed9803797f11496292f73d795ee0206ed23225b5af1a9e8c722cb081cbc97a68a450d455e382fd2755aff05da5bab498c90abba44b3f03fe9257a4cbbee1291429d986f105d3d8aa736bdc624444740e3c744576994fbdbc7a1bf949ba99eb113cb3d90ecada91d6b758fbf07fe79c56ced06962a3d51181d8e47ff0a66bea5d4fa7f3d4d9a7681b99b497e57af3c437def6360a44f3ab9c8b7d3ba0f21d73eb093336846324a120e47f6537f532a8e7a328f53a9a8be269318f31ceaa25304a0c4db6e0ceda2d2dbc504cd174ba6f864510e628557f13643afd593c2e382339ce3c6fa24c00221250cf1a9e441a35af53ea6de4d014e37a7cf5484ac45156453a4c5565e74369115fd505e2ff7fe00a296712612d3e9714a6a38dc4bef3c308f0a62f5095588d2957f362fe5f440d820d94040e628174b28450f781771ab234b03a282a0b93b325fb5ab4456a32e071ed83b539efc35413e5734fb527a73e4e094407fb8e1dfe2cb5d7e6e1e4335291f5fce22afd1661185211a5fb0f6497f331f7ae3b061313dc274763de4ed1e7b9feee9adc1d319fb2874ac162d99cb4ca56852017cdff765021a388689ce8ac35b771b5acedc6f12e8d377a3fb10c126e1277a503af594d6976c6d010158ab9d8023ad011742b7e035bf9c29d113912b514ac01eef3d90a27845a4094ccf63c8cce7c3abd5aea13aa45a18bdc4aeb6fb72e489b2285cb736f556e712a6e1088f977bd27c953385a4f1db7cb1a9dfe14774f3b4dde20bae242d388c90b4ea63fd515330ada29a4b0968873d14cd98d0cc88d20a67f0444dd8e9486b0a957cad909885705deb791089b40e5e8dc4f21d830e9aac746c093d2f1923db53ee725f22da3ed90e0121ccd0a42e169586e0fca06c3e4e9fe7dea2c232496c625f6011b3b6bf8e21d2bb61aaf28f7fa3afc03b7e15e5bc40f858d723d1367e65871d8b25b8b917d8009084e69032c0a618e7b5b218928b01027b4cc8786dd2f4d6a846cd8e22f585d985e0b8300710396beeaad4cf252a27576f1459bbc3e4eb7c5f48cc44c1d5c1adc721c538c18a432293a310e9eaea5a1d0736bef4bb64c6dec56fcc2a0d75ea141722fd2f8f9ef1e15b70ba4d678fdb167356613e4e264181fef776b60a605a39629ba7c8675c456fcb211203b34ea72deba70129d399bc749c0411c7722cffaf43359c113454c07c5dbfe846660e374bdf84f022919ac66f9b73c3defb6db5849235548dbd857cea9e3005a31a13ec81c7a08e14398ee23f54beff73d8a8eaa0d7a658ea1142b219033070b373bfab879f5e5ee9a9dbdee9060d68f5e04742a65365c23685db4d5ada6785ccf70c96c185eaa177e407236e27bb4d29b011a76be2f323601103524a6678d4f88df32e5855b3fbe59f0b24ec285e85576fc6538a7e9cc75534e70816766729c05198fddb6e9d6c248d4cbb5492f10f8df502029027823f808dbe6b42df07953648d789633607a5f1d9eb5e97c54abb1c8f74f1fc14279eedf9fa96a2090083751e866af3c9ee394fbc464bb5a5922e65722ab1523502c0e88077f7700861148da0410fabbaf00ada3c88eec1de7667cf5be321f77838ba587be1414e9ff540ee4b1829d38d06930369fe3b3cb206157aacdbc26e446bc21a14d49034627bb476c9b620091431ea590dda99280725277e81532655e7f1e910470eb26b33373e3ea08d0c76b4424ecb0d79d78734df5934b548574b5944310697556b7bac23071ad838482192c44f7cf829c1a449d4d4661ca3a6862350ba1078e63619c5198378cc83fc1df7168f3d4b775a923c1287dccb4b8dd6280c5294ba3e59dc04adb14f461c1e5b7de9ea9aecd4550b85b681b66c6b637d8a607142f2d69ed1c25d6ae0c5525b48a63a13ad597303b451be63ba66eedc75ccccd40765fd9fa853a81e63e83292c2de8c6d6f415d8e6bb7d6c4bc070eb9a456a2cb5a6f59c532aab890a2f9dba868a1125817231eaff7e59268376eb51a2dc920d182d176897d5847b49c217f0322e22c0aa6ce63d67110bd3944a982125e1216505da7bd61cf036a548197034a7cc58612ea387a6a9dc9fbe14908908f40c06775da13e384ac2ad72e404435b387fe52237082e8e708d87f29def0e6f44f89e9eb85f3560d5661139281699a8f970408881320c1402bff78c3569c9484e13398030a464d8d764f6f70d137c341d5fd111397a9cc0deb4fff2a253a6148fe01ee9676ff1ad3db52c073537e72b869c4d247ee53461987b433d50c176b1b26bf032b8384944290d5fd8f6c7e9c1e90b91b1c15bda0eaec5dd6aa08b9ec32852c60ad47c4a7c846d6954e88da3586bcc26f1271e90888266f54b54e3bfe31c02a88a50aecdca378cd81c02189c8d46436ebb4cc9695347d33536bacb80d6666b0cef3f3a3a16a4b631d197ba1212aa92164d61aaf3bfafa3bf7e691dc2363f121db81e95c49c8f84ba8e26f54ebb543bc7fcc78bfd3e83d550b30b7e55f90b1864aea21c4babebeb3a09013c37a02b193d15697104f8bc56fe431f655eff81170712f0f269812600215c68cc1ea446548186c3c86c43504cbec79a8a53aece6c9d683f5de3c5fd588ae52096e3d9c1efab578fa0aa902257546d73420bf6b84954d7b8d9997e9f204279dca44742bdbe9e2ec542e69ea41927bea276dc091dc6409ce3dbcc514944961ca08e5be73918f6431acbedbbe3ee1443345a5142eac0cd457fa51671150092f3f9137f83866abe5e8a28079b36e8cf042c122d723f56d7808ecf26b1e2c3ac53a12c3247b13a13d5c0790baaa883e8e696b799b23ba58ead308890676b35c466106442e3201418618589c805b7fc2fc2a6b023d8471dcd6c083c3627c90964ef5313771e414cf48cf4afe6b6508a99202bc95eae717769e52ed24452c267b6a789329e351aade4756027b14949a248aedc23aebc17afea089924e913c6830cceff3a670460ba0fd0d1cc6b96e0cb33ea64ceb5b3ade67303547b7330c0d9ceeb6b2f71e6d60cdb2081982483567da1a1a7f0a413b4c6e720035550eaf902f34594addac78826b09b7196952eb72d924782296790d6734e0e3c29b414224564ecf307fbc8466b70ff638238977e6b2b1bd0b60dd0f1949adbaedce51d778e3a92296f96cbc047007c457de78093d0c6072d4e61430cc89167fc073d2dec81923acec6e5a896ece6307b7c4a62c542b4995f3e16a81c6fcc70b58c596c3e1879fed05c1e3639ae61504c48ecaea18e2f255ff09ce0568f1ad9ed298022b27980fa9da7b5d2a84e4be56dbc6f3813bb443892bfb698da902a9d7b3f85a86b9e6f34172e6be038704ad825e8636cf341e9a5b1c34d53b7636be62e2cc3168fdcf99c6d84544fc280918134a5a6b35fc3b817da4dd28711ccba44864cc4f74445278c841be91d4f8f8a6594a5104199e97575938dfe086bfa4616b742409f06f7df7da00ca54b986fbd20bed4bc881050d7b5c62a9633a778d15ff959b2832aa5c5565a55c5d456193ff6befde5f7be58d0054210fd9579ca64ab835cb1784ef6028df3a65c236c56ab5e960e1d8b1c7f2503f5cc5ac4b25e58ad55b452917a9f62d98e94d1fd58e2eb2a52f9269c14826cfd2e983657f83c1536b25cffe5e5c753aaf6a6ceea7c94302949ed7fdaee5feacc2f5d4a5659dd673d9fb45b948e8c36a65b1aa1f2b90c94a973938bc185a87d8563f9a9b3beceef98d3b4013b31f4fd9c92c6cb61bc65617aaecd4f5ad3dbd49bf93520a77cd506e41c28226aa6dc16f4c25bacd16bc889161b9ec39446b1e88e9d65b1dec8d575c568511d748d1b2e3e977b3fc91f2fc1190f0473601628284d2814dd681197b9a5244f379ef286722203997978cf5f830391ad1acaca6b910b28a3a894f807d02b30bdbc8a2bfa649b1a0b10956c055311facdd3d5ade6339834c085543ca553d7008103f43b58cc96a4211f35f0de33033d0e73d8c490e83334d4e7c8b0a8dd8bf686f4cd26e79d191a6675ed85f72c52b4a86ebda19cd6ed01692fa9263ae453462e331961b2a862ddb0b79a3341cc727fa27d4202a0bc98cbbe698848b58a0ea195ebcbcd2b58df7e628b69615ccde5aaa14f8eb9869769614c64e55355e7a64fccd48cedfa2076831a0c0420d736ad59ed31a65e55e15255729e2eacf88ec62f0cb69476ecec16a814f705452c5ad6902f72f8e3559a838fafb4acf35d5edb502b9e5f2c5c1517b616a1eb76c0f9d6240b76bdc5ebb0ea3b40145eae000fa9d42412fd3788d9e7817185b930a6eef4895c61c37fe7cc2e3f8c71e5575cd44662e08381f314e3251e35a24a680b97af984818d3eb8871967ff84c0409c89a1c70511bc3455379204aa6f2764eef76652335ad9a6d1632d782d3b955081dc72f9e2e0a8bdc2a42c95954d9f7935d0598f104aac431b09d73b58cac47e2a36e4e207fab3773390cd83e50fd37c0bcfb587ac99b6f30375e980a840260b203b79a7afa81823cf02287609d4fcfa146e6ac642b4158149f7a4cca0557a69d3ff5f733edf3e06849a0845742e0e11432d494b6bd65f3689b2a67d531ab7c5e82fb70ad2340e30f2c49a264496d0759224323f63a1895a43fa3c44c83073d943648fd414b3f74f10e46f770adb19f5470b7da00abf5ced32b191e3decac62d7a97463ca5244b40f915955df7fb20f662431676038156331062ee94e1347c5ca0f181a65049218917bc12405e33cd2fe3ee1f468e0726b0c409af029bf214d61dc0c0cdc26955765397c77a12336224854e61959e735b0be7b501e4271f0f19b2d6b570a8cfed4093062293ec7c2c18e22c7cadfb4e2fd62f5179b32e60a376cab08319b6a348108d2a9deabbfc07b9526d75a32ad05085d9c3a9b9f47e6e0410948793a11c96bfad2820c6dbf540a60620bc0efc338dc9522be3e8574c422f7b0f4bc128fcd30dc95fda6a4a73a5c2887620e5192a54339703d79b369117d603c9e1a83d924215c7aa3eb12bed2a3bab202ec14067168507376f9fa2dc4cd435fe57559a934d0d50e8574f5b811ea881ab579de04b4d46a55539326847741eb13dc4b400b5cdc55367a0dbd46060ace696a280f638c7956480fda1a8bd91dc95607f2bc00c2247b9a5eaf478eca8d1d8bbb312f9de172b5b6f120ff914c48eb7de349a497f4461a02f9b5e5eb43457bd2240ba523ca04570121b817d007aeeba11c17e756ddf3364bff5da359806c705dbdf8cee8b7e48dd417426de632d27fd140a53aeaa62293c5dccc2d8b6f4a9ac9b0734216294d209a58a788fa18c75ebfdee7a92268f108b12f4c1b83c9d5be8fa4e346877075b41d51a7c6c5e9c548b83740995a87fca255ee89ace12a6493c9486eb7dccc9b02d67f53b846bb05afc56b1a064d86080258d6175492516995a19a634c37707721209125d6362cdd600adfea90bad40b0eb3ec7f8d9eae2488f5a86915a29d3447e7597478530a2f086ceb2d4f582cd676467804fe98e280c381dcb2932889578ce6a8990d57710ad256a6c8398ee412649c14fc7fdd1a0f3a848d482d2e3b770e00d5eed8f26a12f044b8e57578687d4c93bfb88df125fc104de081b3a5b801e26e2edfb4da4e3257fd7d10515a2ef164ff78c627ba5380f704c3bc0c093d9b32cf024487e491f4d2beb110b8aeb8ce70db4006c240088f4d1a60e414a84d851501418b21eb542bee74d24fd3a6b7524b19491ca8724ecee9ba9b96b3f26d3e4776ffb93148c45d493a789d83de6bebee24461de41e017039d1980b67eca5c8489f14a4a75d2221bdb6d479b6bc9814e5b312d2aa485700190a83e0df213fafff6e158e50cb05c570f29026ad700ab9508df1c1c10abd505952ca0e6c0c6bfe4ae3b9ad7d5110f11325650a41cc050c28e90ce815c0ef7fb4b1e327a5cc2fe24afdf46e4db41517350372b77381c69b8d00a98b4ccc451814feefa516035f0b2a91bde09db77ca8057d4266b2951775955dd0541cb34d74a4db39d35de5f9291f4655c29a7babac2dacdad8cd37033b9cae554f05da38a321b205ec0a0805b85edf40966b9acf362ebb585c58bf467e4d5025f30dc1d52b997acaf80358711f41c4ee3da5c7df582e4d87029229471c5d06ac69cb5f138558ad1a91b2c7e5d5308856642827eb4799233ac8a58048dd2ee3a254a5a13250328a35ad75c505aa6ed342d53d0ae11b2e91b32f97aab90b0318a6be6742987eae3fda663b44812b8c924db6c539a63e74c1573b22b4b52dbb1d3a6e64c2a07248e0af5c7f446470615049e9f6cfadb2989eca66c4bd6877cdc4286f4d0eee92ec82a8185af5203ee8368730dbb0b9257afeca0a34aeed888672ca74b2900ff8204b8cad939d07df4c215732b11d4fddbf0fcecd2f41bc3028674bad903e4b0e2e362dbfbcbe471771de895363937a63cb5086678e3594e7c09cc29ae4bfaf2137e086d5d4c28a20aff0e24777f5c5aa0587a5a3e82fc009a9a873a1f3a06fba77772716228f1c7c5abd2a1cf38de455ae8bc2c5b08e9fd2e469c4ed41bc27de3caa3f82ce00d8f2d76089ba66d4f00d13d0c5c57287aa6b9fa1f758f8b76caa09b662964fbd719488ef500197caff04c751384a5a83e4efc7693bc727862c04231f9d1ae15b38e70cc710ce6d4521bf6e4e7b89c0b369aeb0c8c6afe240b22d814b5738936b6495f33b5f5cd4327e264a1acc9f7d2080038694af34dfeb4a69146e8b872917c6b5ad755e4338b3a191fd6ab53f465e571d18f5e8cc22f4633afb92d01a8408247e5449f3a41eb66c959bbb9ef416a0a790503f534801a95e41dcf8284b2ed1d91f2a4bf174d264a33ac7d82b188006c8510b0e394fe60b84f352c8f67029bdd15829bb65413f0a650ce13170c7a52d61ce26cb36679ddcecbfd34ca9a934bc5a801ffaa8b62f78a2add22ce17015ad01b06cfbd1c5c1c00ee4b7129ed0a142394784535ebb24f2028d9632d3743c63429e5aa5ec2f0bf5939632a569cc31faec9c0f962678c64615f7eede107911a7feec98884f94160e2527541c728e620f708b647d54d96e3c4e55e85807783df46bf7b5a9640f71c8ca1b7f35d369817625ec824efb6f64aaeb98a11f3d5197ceaf3f2129720536761d3ef4e0aa6e9cd6201b5393e55ea6570849cef792461ea65303edd41e202d279555107e2a8a81e135be8acef324c421d19335d1f63f7b612623b46a182a1d38e9f8e0475800e010f586ced1dc8dbb49751023ba70c27e4a7ac8c6cfc045b65fe54d7842b9eec6768d024670413c49eda57d780e3a745bf8fed7490fe974737a943cdbb9a1c7e309fc31148f1bd0aab135e5adf6e856af3383e1d7113491751638caf37f2ea9de3917e680c77fd255c9d938a0080bc9560bd889ffdecaaaffe432e4bc8adf53b0f99015756ce4c5e435094c5742e751526a59c212e9042967502a53cac0e541ac50fef424e7b19bdfde79c4d13d53e467892c91edf86c69446a5940ce242a0bdb726ca17945544e3979aa8591a090c27534cffe9a5aae4bf50b39ce66468bdfc7601e2ef9d47ea32f63e3a91c22fa4960332565401c977c98674d675c281ad91842aade990dd7325360aba6a77b60ccdf76beea285c15a22233906ce039b524d730af213b3c5139355e8572ec045d2392c05f0a1c82440b3df97b515d912de448646f8bc63a710dd7c05481eae14e120a25ce9490f7000d134fa9cf3e5904c7203137e5c6b7999d073950166a931c06454014f521c3c5280395cdf99786d0877f51f800b92774853c0f30059c8423771ea84e06bfba1610257ca8c989aa7ecd5e73def82b538d2986bacb6b0f458c067934cd29ad551ea422742c3e12924a7fbcf1165beaaa039cb5fbf55eb05c819ce95f3f4ec4b11d2c3655b856b53e106607f9e9c8cdefdcc88cece54b01c5f81b46146cd146b7826c7d9ad93fb9c186cb46bb7e258e5e83a905348171b0b0824e7aa121c53384844e542dbca334b7310436c0fe4d1365a2301e78f79c900881e124fe20130319dbce3d4c68b73290677495eedba5caa53f8ce246a400412f5f1f4be8b6f13f6e575c1571d5ff6c274a8064591eec224ca81146ad772d3603b10db186d22c6839597d89e6436fb28c4d8677010e7458a65651f928567d4dad0d28e1c4728f056dcdf30cea688d929f439986247e173c685394b07bf4b8d26e916874af3e0a6e650a39e2a3fe4a9816016d5aef84e3cd99ce6c1e93d7d56238f53ad78dc0f4c386a5c21d45b243902607a9cf730e4183518bd2d968ac6ca82b2fb67dfb2f46bde8478e5c10a5cd7167221e392f8478ffac9787408f35c64a3009e904186b1179b94d571a5e01d790dc684eec09828914f9c05facc660b003d1b98b9301d722f6198d040377f0b3e8000081676ccc0add35383e5b2adf3d3f8a461109e678e97697666712eac072090eb94511f3f97e905186cd32eaae0e6f196db5c1123437884480b2ccdbb95694bdc0feaf1f2391f020c9457b02d1939c83b386759c47316d2cdbe8c765f2e25df05995f8a3eae348d3c5609642417015d966aa991974ea66ff3176d08d00849d88808a21028a8b6ea396daa24d99330cd1d3f390db7ca46c0b810be0420ac4957a0e14a453225320501ca580345cb71b3b567b3e487cd764cf441fec6246e89b129004182b374ea4df53bdd15f4227dc88ae00c8216c184d75ea8aa4a5d604b0c4403ef80f42c7152bff9ba9a22d30e34487bf0033c21a29d6407154ad64e87e13cd4e2c0c6d0ee0d59421f678252840cc19723ab54803f1adb66eb9aa56465c079887d09101e62b23c4e513242dba9994dbeb241ca16fa8888617b6cac2258d507a4bbd8e428dbb8ec71d341c186257cc8c0a657392a239c9e2c15771d14e83fb8818757704d07e07887e106b37587cdf9dc269a007a31d5fab398ccd4a09549ab9718156a892aa82a26327347da40a065ea5838f4b208fcaa462f29f718d981253f769071ded21064702f49697ecee70965466d56f6c4e961b2317f379f657ef561a6d86b05535726e79cb89a6582ba228f29402913164dea1580003ea5c17e0bcf44a26d21dc189f1b5996351ebe9a9ba9eb08675d167208e1f7b8f3c655b05a67468ea6c7d8612ba0b7a7cdbcecf735568394b404c0266f480d1800e7935342272d346405ed2077d4b16529302cf50be2c4150a112bf770068914ed6ee2cb73b4ce68c80e087a31dcf01f53857ee3f04bd3d6385bb3fe4eb47aa682a9633f299ab45504fa30bba25a20181ea50365fee81889443650d29450a0329046b6fd2852c97fd42a8d6288a74733fb5daa8a5734901a1e8b0e40602a430d721efe6d486786ce9b85da93d80e8c0c12dcc87f72017ed466d1482344eebdb72444ee2da54c3205c10a8a091d0a1308cba1b64dc68eb0d714614bf5ab5e0e1d6dc5835a870e09fab0e97dfbee9de3d9e45bc132489ce3d1114e7ff1e30f3cbf6f228fe7348ed55471ba8b0716decca75e101c250c8f1d613e9bf0c40b7a03968518443cc142842221b0603efca00584161fb21bb020818926c876c08204ce5dd4576ee07e61b1015bdcaf971280d00de1d323134b99ea51a7582855d0475d3ae9bb4164e7c60e2ca3b1ffe43ff91f6a471dbd02d4e69c6d4347af002f4ee3a04038effb86fee6e104b1aa94ac815299e4df8c73f6c0b1c8e284216459c214ed20adcaa6ac3d928512848a7670430dbe60507c5005113ae860adb5292f70b6b8b1e8a2c5931f9e40825112580f561c618c0c0c1d72d87a10460cb8f8c1890d37f01026c9078717d20b0b24271886611804a6c082071f2051f485123e8412b0c801ab825a11461068b0c10b0f7c40a58b2864c000dbc1042fa31f54f811f358688ac30eb04ad3a04783fb25c50e1842083d5ea14165ce89655224ad70bfa4d062d24aa552c954a158cf81af570babfc8395d3b66d474cc22affe2b61991b84b8da37fdb5880f13b8f368ebe24b0fd7a15d5c0fd7a49c14f8750bd94e2c704710047588afb2545ab251936e84816fc19c5129cec203d4631466683fb15450fa2c892443d5284a31d18010544c85206abb528acb8200b31b4a0610c1aa81811a910e560ce395357c06c81c208cbb75108a9748840c83be79499084dc909657b944c53652050d1875e06fbe1959e11986300810ae718700f7c7989a832c5aec1fbf4522b36abf2e71bd65885e3c5994e5e3a70b53419a3d795a42cda0ca0d71357b61cbd9ef882014df11b29425ec54aabd5e1e4e81568e9538161bb10c202a0e8af4087e025136ec0b1044b107e94525a81a7115172f4e6681cf355c0b3051c6dfa663ee7cd1c3f5234d5e444707cf8cd99b95e20220752be1062092928c0747882f900290618866158bca1e70d3ef002888d0745d002872776488253ce2909a0c3133e5a580cf7cb892710c087971361d897135f946007a9c54910aa1356387105899a52428c51ba7faa7eea2e31eca252cf11720501fdc4169824a4a32d46af22a221215710d08fb7c024211d6d317a15110d09b982807e602b499118601812a407cf0ecef72a4be30d6d9f2224c51525080ad0931f9fd90a03e64b9217a42e475cb66831caf2ba526485a8cad0142129ae284150809ec81fe9235b6130982f495e90ba1ce1c8c5bf2d5a8cb2bcae442b18be4cac2273641cf20f36a055a9e7c0c7b056a95494cec7eed481339940972b68d950f6370ea861d80fb3a8ab2ab61fd288f147e3801826bf3bdfc02adbdd0dbd3b5651a9e774e350bbe9dd6982edf78657409fc6d1bf42c2c6306cfc9294f26569963efb5aea52e931997d2c956ec81c52874ddff44b69a5c280b319638c47266e2fc678638c5147e3d0c2c647c2aeaad8f89d819f4ffdc78170673103b2f1e96331fbf9547ed693bd7c2a367751cc30ece6ecbd2ccbaebb32170aea9bbe34c677ea39100ad1385429f78edd1da571c0d9ded0c334170cdfc6dbf48dce417333f5af1b626424f12fb61552cae8d0217c6c4305dc10e78831c6d88ee35f3f0d42ad34424f7637f41a6608a3c396d808c4f16626d82f74e853d8c611e3f412c4be144208a97cf83146195d89fa594a8fa671cc19259533f60d7a851b8d634e2d621fb10845504d1822125eb1ddf276479af50eeef9a7083d6b658c56222cd5186354417728638cd325ab0eee170f5e30f5dec6bdf8589884e49f8dbb5f292a65b15008ba7ed502d4bef49ce784fb2ecbb86f4c9534617141b8cb7de699ae474d5ce9b94c268bf52007f227d8ff86e14ffcc77390fc5d2e21fef96b5e10ff6acd1238ceb3d38b306dfd80f1561218a4a4a38c3e7dfaddf46b2398443d4aa9c948c42068235cbab9c91026d246f46256544158f82ecd4d3ba8f33bde28638c32668865308319cc604e49cc331235ffba6191c7de4e187032e45f3b0cfe3915eb5ce94df533cee6f937717f1edba1e4246e9e38afbc99480a639e39a9fd6da3b54b395b7677cbee38e38c33ce39e7752fa2e085ad31d41b05a41217f221d991e5ae78033f85969d0f1f7ef11c5984096a7002777f1912496929ff2004821204e4aef81e04545d5268201272f9073f432ad65d10097ae9241c5da631e2cca49765524a8a69190644624d85b9749ffdc5a750662944c77cc8d938e263790458becbc611fd739904991cdc638cd15d42f7ee6e82c148c559d3e590869e02f78b065ce0eaf0ab77a418ca17f2414b4a8fbdbc13a09fbd054a8ffd122a42b02ffd92ece92fb9b01597d09ba16083758c29814ce800c40f2bfb2aa9bfcf9f71f698b3747bbc9218af0a089b2110108540f1e6aa58603304a28fbdd35f82fd5c42450866fa55fc25f4aee21582bde9b1a75fba4bec53ec210a4eb858a5a75fa262601727de380b99a784092c3a4a428845456585092c42c207894505aa9480503c080bbd4277bd826b627705e75dc1f916c03e7b0bcca793e54a661006146fbc95c4bfde02817a2b02b56861f8ace935cb814c4bb877b7673de7a440d8ecda60118a63d21b8391a3fe61de0e367f37b20da689db8e4c857d8ac2ed471388336c8ada36d555f686e794aae9bbe3f4ad058861961dd91eb44ac76d9af7e8b0e96df60d91fdd1f7cc945d29318d086dc77322c690a27cdf7c4079537e63a39277020725580d840314d6b6040e9660d5504103ab9e208955e32809563f4ed4a971031a585dc50962c0eae7893a359a3062d598020c568d2935b0fa87449d1a47f8b0da280545ac0a861358fd45a2ce0c0e5ab07a0c560f01074fb03a0b568b210410c2b0fae11735b01a0e5181c2ea87425147c715376cc16aec0a56aad503ab1f16451d069c008a17ac6e2e4760351080c06ae824071eb4b01640469820b0ba817004563f4c8a3adb1090b8c2ea8760a2ce0c2bac2d095b7c60f5fb4fd4b1f185d50cd0a18b1eb0bab9d081d5efaea8d34f6869620b963ff1a18b18b01a5e8185cb15ac7e278a3afdc4d018276075562507ac7e7f459d28041a7c7e585dc760758d82d5375a2005ab511083d5ef495127e78814b0fa1d4cd4a1b185d5363becf06383430c58fd3128ea94acb04e5a94f8c26acd05464060592b395ec1dd6ad5d062e598c5b672d412434551abd58223b072ec82f918b5688062c4cad1cb5cc2a8d5e202042bc72f9468a8d54a4207568e61729c50d46ab5845859fa401a865a3e43706165f964fad06ab55e50042b4b2825318c5a2d22a22cc1a8d5ba8207569652a018b2610b2c7260653905cb51ab25e50656965588306ab55e70042b4b2b520747add6105958595e916206ac2cb35001d46a4d798295a5162cad566b4a1656965c8060d46a4d9982956517295cadd6942fac2cbd4cd751ab65840f2bcb2f9507ad56ab0824585986d14ad06ab582d0032b4f9f1db45aad2d64b0f27c5284abd532e28795271428865a2d2a62b0f28c82671449ad16951e58794ac15d049ce794c8c59b121105064ab8683084137e68a1e086ee07a024474a10a3862dc2d0a2450e48c440e1435f09e20b134e98f8011440604f4a0003ac8a124cb181e27e31618299c54998cc09866118564312a4644e5c590d4a603e3870e2653a6902f37182c5886c87ac86221f319ca8c009941a56e083854fd0124e843003ccc70a4c8ae924cb747202d30ab61ab0d87c98e0848b520d5e309f273024d42838273d30dd60d2a20626947cbe2825ed207d8c40392182fa184d2758780d48d2490d309f32680d5a322735d48004adc2e704196a092c2b4a90c29ad86ea0e2414703550fba329ea072123334c1dd2ae6e177f7f5bbdb737aedebed89790ecb486c68da69d358d0fe64e266688257a8efb1df3dc779317772777eccf7982e1288ebd7fa266f65bfbe49061f9a0fee63bed63aa36e35dee4c1168ed15a78f3f29199af41e36df733347e06024d5adb68fccccc8c47bf763450dbf7a3be7e4dea6b6f3dba4a205dcc57cd43691705adcd3694d679784b79a798467d95b79bdc6bfdf579220daf717b9ef5aa97a1501268bcd2ea6bf5b91b33540e50a8dafde9f439a6c66bd7decb4570e5acbdb948cd6ff6e65ae365319f9d22865dd73dcab35ce5b6cf9eabd63eea3b7b5731319d8dd96e8fc6797b24367077572894e5663c279379ab1e8d536f3fe6372f23b181eb5d71cfd53ae3ae667cf6b5a3a152a95434ee6a46ad3277d575d90ad5dce937191999e7644ed9766580486c60ae7bae7b8ecb2bd423b1814fbfea90d8c0a9bbdab2df32eeb355f7dc67dff6ae50b747e355ea7b621ef5a998477d4ccca3503116d5bfdd1e8d4fd77e67bf7b6bbfb3dde59ef372132b57546ee308198d1da9ccb22c9b32fbeac9995189bdc96b20d208c43e661018f612a9c2cd07166594df110806047b1963bcd9b1863bf8c1f027180c1f8b3ee6bb40b38b4120d35b09e978e3f21e8138462410c326d4593ff328eef0b67137f372924c71a642666e8d6c7bcdeb18176e5ea279a8d7bc7e2de6ceb83083aa713bde7030cb31cca83f83665e04647ec6631e90eeb7afd0ab368a4a6fa3c8bfd69b9b208998fee6ad7cdc9e52c43d343e2b7de5beeb7adbb29ff1ead665decc0cdd4cf79907b9ef381fdd73d9e28c898ffa5a7df8dbc60477db37ae5f2fc775377fb769db6b703b3dfc158faec77ee32311c2c6a1799ce7318ec5dc9c3df7ad3dfc2c4391163e126b66840946b1e038f38c408ccab8ef47fd8c77243e5cf9b83ddc779fdd1eedb7245d9793601822f5aebb5948d73d4fecbc0c5bd87ef5320cb82d77336cd9af50832dcbd16c07ea65f5f2aba2f5ecb79b5da8f1db6f31a713fccdd3eefc2ce2d376a7e6f59c2e1288b35fa14ede0af5d99fbe5e197c74cf3deafad85efb2cbb4822ceec0fce6e0ffc7ed8c21a677b647eab3f63860b333366cc98e1d1cf4af565bed2e7b22cbb19e2da5e861477ab9ff92cfb1a5e6f40b6ea75f576bf09e12ae4bcecfb67b84a5da8b1c9788d6be94b5fc38b1b10fadc975ca871b99bc84526b2373d8f73dcc3af35e67b8e445ce37af5b2ee6b78b546eddf8a6054cc968f64db8eed396f957d97f1787685d4d8be765ec6715cee3ea37efbeaad3ad40af5fd32df7d7dce5b718ffa98cfbcd5e9ed675f65bcd4b6552ff5325e4dd5cc6ddb738ff2561beafb538f7a6b7ffb1ae3699ff2627ee5cda43c2d06e5c9004f57c8ea57ddd7f899cfdd1552e36736eb75bff2666a78339f79dd5b2feb4d488deee6263337dbb4ed884724e2ec22716c4309ac09eb0fdf48f64fb1d62a4ae501bff4b5e4d54e48295b21f6d27853aa9f6d6ff2b8bb7d7ccdeb29bde94f57156f4cda735ef6dc9b5cb0b7238d3726edf6946af7b5a4752e9cdebee6753727c10f79e2f6d27ee769dfdd476d092e7d73df516e2e9c6ec79b8ebb99892dddedf4ad952a8f92e7037ec97a27ea82fd936f3c4a3f6b12acbdbda5ef2fd19c04e7fe23910eb14bb0212a0ff826887de6955694529e689ade95b4a7268ee39ef332cd4df0f63909debc9ef9be65bf651a7dd373f44d4f4ddac5b6db33bf6eb908cef47b66dbb671a026d99163c0d8e72218bb997e73df83bde9b96bfa8e01675fbf1d7bb941a0095e75a6ef4ccf5d53fbb7dd9b99e04bc4e9a58e3d5d7afb21967510f7b6a374794cf8990ff8f3963c8857a65ff9b8d8c49e2d51b7a75f79401ff4e13b2e79f9c31a863f4d25ada4693717f91efaf135edb5dbfed5eea10fff74fa7af2b20a9fe8d7a89dbc06d2bdfd9277bab9082e55d5e312f7f2e310acbdfdb8eda8df7d8c77ba9989bd72f3e19876d7f431a51ae371f5e2ca83be0ff854f3aac903a27dcd9007fd69fa7ab3e91e91a5928f1e4cf001302feeeedeb50df6ad7910620d279401e2f852cacffec88633d99836bf62a68961d83542b33b03f63360d894917a0c43a876dd104a2803c4fef1b13fb2612cd29f5e9c7f8d6417890772130c679833cc4772c2f3ce00ffc826af1107c3c33ef6d682ec913e9f05bf0d53c8472046e23fbd8629e0e8829a24a043f718a59c13c3324a4bfe512ad15dde4ca394d11d4aad524a391ffacc4d5a18c2f70f2787841f3ddec0813b50d4b699c1ca145c7ab9054a165cfad209c3557c8927865b32188768290e15c5a1a11987e250f63568b08da314fffca97fdefed92d72cb9d9fe116eca85780d86112eec691a11658bef4f6a48704627a73fba77dd98d4798946a8306b604553318d2b6adca703fd5fc53f506e5b7bb175092a8e76834bd83fb5337b04fffc3c154985723088bfdbc99f6883a4d55f87495746f6f4b2e36f76b02bd820c1070fc20bd02f522bea3e0c5f2f817ff42b1f187449d8e415e9166c8e67e0d7905c1b4927743a757a08ec10d48f6f2d634c1e62638cb6872608564d96d9b1e4651d5793bb17ae9e75f5954c1a67f6c2ad53719cd943e9f4aa58770276e3b7947339db49d9d5ec171e95f88c173e2973e01ad43e34de96d13a552a9542addf0aff4d0abf793a7496347ed665aa2115e161e9bea4308e3f6a3093679d2bf4ea202de6067bc7400d4ada3d34d5c83de0f65613f3d21de6261df5e66821b89c4f3573ea00b4f29c4b2b039e52502c3bc8e37f1a64b8c3addf166d88addb171b401ac804f98e8934e13db13bf33ee221d8630c49006dcb73d24b2850ef767f92b1f262caf912b300ece5d06ceb1210bacabc0426323b00cdc6d44766f2c50ed8533cd6b074838ab6854dac9f60a2aff3aa8de48c10e58c01becc45db443165f149c35ec7d053eb1f0e59d510bc0c58d1e0a40040bbfb5da36392bc231c26d488e4038027928f0f78490d2c60f16c3cfaa9fa803595a8df30a8e39d8c679fecca9ddf9439b5a193f3e1d22a9a492528993c373b2a7b452fa254c353b7fe68fcd8ab227f03d258000d0c5ca79058531ec7c41f8f13dc720a04f02277e4ae5396129009ed8f70fca1f2fc31ffb36b039fec420cff1279e13dfdf15853c67e58de1ff60f8ffe339fefd8a51081a57d81efdaf69349988afa5accd9b70b08fbb0847881a7c7181e784dde9d18f43a343759fc63fcf47bc4a1370de91b2fd5f08ebb9ecbcaf9ac2e66f35bf1ff03b6e780eec07ecdf63becb2fdaf9f58d63c9e2c252088c8b0984e5f4a1819d3f77b600f0c4629fa7f7640a24dc2f2c493970bfb02881bd28ea74f424cff9e239d8536aad4af59f7dc99330c6f82c60eed1344deef14449e341b192c6e873ba43b1be333d27fbb983f30c4df24ee6e520b2133774d8c05624e227bd817d9c77200b6cb6d8230f2a9511a4607f9a96299bf96d039bba0afe190faa3f4d0a435e09c8a6704bf1830d70c0f862849f1d0ca8c7173b3a3a0b3684fd69686cbadbc6a87245a11f8233b0425f7441cc2dc1660c0828eac496c4801a03c2802612306582c568c0fed15f13a971fc7847d88c8393031ba24540571c1afa420bf64f55e9621176e872211f7215fcbd1b6c76210b7d0862e853a80c611f5a41c83f77a1298dc387a20f6c8e4371c8e3908d2245684a169b5bb168b6aa0f95f9363c0bfbb611b28408c75fc5c07a44fc359fc8c23b44deffc1c691cd2aa3e789371907e70b2bd83f878a46472aeae0dcffc1661cac81f7c17616341ae8151a0f11e239f05a213635a968a9c0b8b0bf121ff9363c8b3e0621ccd8c4300cc3ee4ce22a804912755a9c183653087443f5b1e16790abe02fbde97215fceb0d6c76232ec95aac1575e43bd21637b253c8735e5f68f11c0f8a32c5b93061734cc2fed5b760376a1cfe52e6f862e1169cdd08c98de4c55aa61cd86c53d95114b66dca46f3a1225f95020c6cee97e5c2661ba5b0d94ee1fd1042082184dd72f6ecd9b33b1e51c28521e7c3a3961747d83f55e408f491574629a38c52ce38b18a6118065d49112fc93dd247b08beb7ceba936212da24256db7047b0340cce30c8837e9e508d0642894285f5f716ce306805bfbf0a2f84b03fcf8e0bfbebf0e207b3dc85c0b9afc8ff6c3a1fd82d7ed34714c926a97a00e65b7ed33c55fcf80d069c80f19c24f8e0238e6790b874c2400d40b5f2c40a352712c28d31d0b2e2397d8597271870f2c573561870f225eaa0be7bd95ff2eb8eac8b78a20e8e6f363552d8fc7f6505406c38be6f3e6a188c79c4255fb00e143086cd4712c2fd42e27271bf9088f0c72094e765e9674b64e1e72560b07fe45183b1db8d63debc2409e725449888bfe21005fc3a0d540034c2a25aa9c23305431d104ac110429d4fde07f0d44461db891b965094420bb500c718a54c410a9e7640eba85a075509f46bb0bc9988bf70bcb4461296c61b252c8da276d47126e8c0e5d55852f033c452d2647c91c31616930d08b6a0f809623181b003b56d4cc0840c8e581546814511ab8d78a084114b053c60e16209c065049722569d5ddc40882156c54cb04412ab662ae04110ab9b8002c688d542b0c16788d55f04e1082256ad5af4025084a41b92584a04d022a208442c260bf029c20fabb1f0c20623d6f7050a767f1bcfa1c1fed45ff3af2d27e5552202564361042d7e586de3d1871a1920060f492c252a5811c6118b09aab5c5ae8448251e049630462c29fd8bb025bc42861118575094167c253d7a83a10130a4b67130411404ce1667095f46081d32a1b60d2d515a8247248636efe0ece8f01f9e03b1a79023855ec133d3f7a75cb31ff4e4767568b7fdc6e10374f88d27a015d0381e4feb04ec65af5083fbb71da58f1efd92e7a3e1675007bc3ba723acebf09b1d1a50b025d4b6f96edadd6472ffee99be898ffb73e933897dfaac61449f4253e9a6fc939a855f189ba9dfc08fa988fdaa1a47b4c266faddf0bfb468e2a07ae907ce340e7fb8b1007deb4ef9d7051715f42256b547d7a9ef697ed3ad00c8650c9c1d6741f8b0858ba3219c298e9716a17295ab5ce54fe3153aec57173dc0ac975b64caed11f6039d8a4aa148e44352e89314abe1353affef31f9ec43ef51a9f9f69c9a87b0a606974a5a95534c2e29bd42728a7f9e9442fec1cb43c2d2974a39353fdf8f681d2ba7749caa79a5506e304afc9b9fc4bf195f06c1d02b34d6817d53f3b34b566a3ea7bce6b367993e64e3fd08147eba3fbd816d024f14bae0599ace05db76d8a8f16abeb71d36fed53cf4e4973c16e04b1ef03188829721aeb936fed53c8fd7ac6116b96e1c352fb51a221f721b5658d3ffc0ade636163ff38691c34ed6c6dbf879e239415127c99c70e298b88753382821b5ee39350fe1eae1b683e6874005603fdf850c748ee9e767ea7db618d5f0516ff2627ff0318f0686ff1d84355e8e2f74d212c6e3317daac18d855a9a621a1212f2a12a442f37f22d6ec5b3b8162f9a9e93b10fe2dfec998209492121cc83b8cbc67536de8643b75274c58750f81a358ff6d8781920aef9209e93e10fcfa1475a2ad745f1cf6a5306c9201b33b0335ef5331f5f0a094921cfc15ec7fbf9b268d69f2fab78ce7605cfcf320b9e443d1febac149242728a1c62c1b124a2c9a7f1d987e8cb8b04d2fbc33f1b354f1040838d5d3cc7c6cf8f4851e73f3718e9aa79d37ccf931b0268b05906f504f16ffe11887ff4c8284151c75d320837f42af3a14729b1e9e62518fb98e4dffcf7e217bf99d9e7f6dcf322cfadf837df5d13859b29ae71570d2f435ce3b18d4786b11bc348b1f0572ff333de87b46cf19c159793ed52cfe334980fcda743f2ae543ff3b9c1dc0008d702f73e047f760b1c1288e5cdda97bed478c653d1f088f89fe1c9789e0c30c6cb5ae643783eca93211b1267d723eafc9ddca659f8df377de502c8416d1b79b2fe4de95311b083cd90c8debe90c8b30f3de939ab7879c4b76f79649915a845eb582f30169180319965df798d136fb0a7168b5f33af6b11cdf4321eec9bf8292fc683561a75ca2ea47a94274f39822296619909c7bfcce413630cb55608f405679969ca2eaf2c528be422bb74af2b451c07adbd48208fcc73b02dba544aab86f2a015ffb0c77995ca62367c605ffbcc7f8eecc582fc27eac43cf60ec573a2788efcec841d798e7dd4634018f3208c611fe36547d4b60c83b3f7417a82b39f5070f676fbd104a3ecad5de7446cf0ca08c4a74722717c231d8ea887017591407cba19da7bc3bf18fcc39e03fe61db860027367b286ce5348c728d655f3b9e7c3c27d3bab6297af705ffb0a71a3d71dfafa32f3843220989b85849ca6d74c57330ac888655cc84758fa5fc9b10a848d4517552c47060c0bef4190da1b044cbaaba5ff58924f19cf8f57dd4afb5d65ac3e0fa4d1fd6d7d115dc5e70bfb2d8822bdce6170c3f460d7a595ec1b008e248875863187d2638bb32a9be464fa9571657f0fc1dcf51f1788e7ffd1e5555359aaa5458efe15fedbe81945ef93d6afdb6a9b707b6b0252e300327607051c51554848912813862055bbad8628b11c48041104198c11170f02148881daaf051c1ccc28814203184215cc9d283186300318f3065052a3f4f4822cc183208c2420339ace04b0f2508eaa10a96021370f92268e04cb1e822bfd287d269d432460361c4a8ff0c4db089b5d05af0bcab161cf3386e8241cf72e79c73ce3c21c5f36146fbaee4cb246cc00afb3766d89c73ce09348e7e991c6c8c3d5b7ad1b7146d79d11d5e5b68c1bd4516dc2d01937c93d79b04a8cc62fc58a2b494655966929b04fc8bab52896634e062bcaa69b6b5afdad7faa79b93606fab45f0f6f6330ff59d1773aa7faa9f699bb6755e6e125304f7ccd3e87e86c6cf68cf3df184c6411a35be964a5f4fbf51ede6ed2bd7695fbdda6d0fe16b5f3da871a7aafd8909e65ef37c701f03bfbb99d6705caddc37d6b6ef3cce9ba1098ef99819dcc77c6f5a0ff68dbf4298bfc678272fd73fc9a03a542a95abed6eae1f83656ca5b1b57e198665d8845de68427e88261199c451815a51a9e0076d12ab53f60aa2382e1cdf32b0438ede38cf7d4335ed75d8d4b693210aa64361a3366d7fd065f06de4c24055393d31ece7ad2b4874422707af8dc77deeaf4f5bb6fd7b4d5e9725fbf3e470477b0a630f7f0747bb6ef52b8bb4452d86e3eb6db2a1a57ce9832a914d4b40e7615c22e057f42a85d6ec26e42082184282f13c1288a632c8e89f91a73baa70ff51c17f3a76fcad9989f93a6f0bc99480ae56522a98efb69396ea6344efbe9715caac3f3bbc97199480a6b29f88021449a0834006207a926020d80a08208462919341428385ec16928500e10a1409161a0a86cae58e9bfa3809e9ed669321675b279a1fbc8c5a44cf6a22e774f73ce79bbdae1a8a2a15aea53aa97b1aa4f69dfbda6c99c7ed6ee352436b47a35cd7efdd879b9ebba17509db742fde93b4a354d5ba16efdd39fbe3ef7272fbff0029e9afdaebb446cb8db23a379446cba4bc4065b1999542ad5a53aad6adfa53aadaafe55afa2e9ba17e87ff5476783e21ef539aa5da5ba99ce90b9a993bdf99ffb19af370834e11ede6c83b99b89d8745df7bd751de7e5176c1e431a0c6930f7f6db7354d86bff50a9d4ccc39f79eba1663e26e6a2ec678feaba1770ccc7c8e7bc8c7acdcb2f74977be8698ff24e27cfb166d3a3bd041a732fd8f080386b3df51df7d6a3b14d2662038548f53b48e0e265b383042ea4a0368bcddff78551a1e70fdba1cc44d052e7b7077fe51f83707c611eaf3202e1f6a3f8ddb862db8e9ed3ddfdca7ee9d0fd219c4d6210c4ad1939b09926486e2d10a778800a683ca1d7dc10ac651b9a54d6f0b77f98b3f67f088637737f02623f88d6533ffec7496562a6bdfd55894ff1e5576dc710863fc385e1cb7cad70d6c80186cd45f0f63602333ef5aab740e055f5d4b75f3dece373430061ee51588668e1f9337cc4fcecde7e05f290831ce42007b99f0fb98ff172fd917aed2377737cb1f36bb511e87ebef6a7b7945abbeaae8fd3d56e1e82e773f5b31e10a7b2fd989be38be5b8db937aedf6a09e7b1eafdef4b2bd443e9c7a5444cd78f8dccd7d258d7b2462998733785cc6cb4cf0b4d23f0e73af453c2390ebdbe7aef4efe625986adbb6c3f44022de2260aff48ffb7ab59be11f893be66b0fad7fde839dd087faa64fc2404c587beb0dc1311fb71da6b6c11c4d4e61eedb5f7a2af5b6dbe9db3d4f4c7d90edff69a0642490d46dffe03545c0f4d56463eccb08944c9ec5e202171ce1fa36428ff3f210cc7d47934c8cea3feaccd030c9a86e7eccd1b83350375b999b8ab9f99f279e3c2410c73ce765d467eff6ebd7efcd879579ce47cabe7c6e0886da43eff4315ef78e4f5e465d5a737dedf49c37c487c675cf133b2f33c1f56a57fe87b7979b29fee4264343b0840800220b128edf48208e3e64acb0f99f86d34082111536f70bfe8e0e1e8feeaf84a3ce6df1bbd83d957aebedae92be4d58a62bb59b8fc4a01804c4bb441e2250e4e19b51a21882168c833b8a214809426afb90d5316f9f55a800ee4d5b3ee2700b6c6bc184bbede7c642f7f4e186e439d86f2fb71d5b103679361fe79c318495dc4ff9dbb7a4bf3ddc1e6e011500f1b67ddc766c4d7694baae7b16fcbbcdabcf794db0c435cb3cacfa54a27e663fc76664b1b4c78f209e331fbe0e16f59aeb344b7a2688e49f8ccf04671e12962859706309a20b5e006e2c41bc009f1e7af9886f9edc76188165e0550bf1edb310dfcaac04fb5b8ffbdebc0976ef8863eec22d600a5a15ac3d4fdcfc88ac127fde8629cc9743fe491f700b9882fcb943b5b63d932acdbe09ae52cb3e635a9db89a30186b0eff6edc10e0868773d338fc336f003005ff8a6d3c20748f374e4f320cfb52c55a0bf1b3cf10630fe3bde11f8df76b1c3c7c45b388aa695aadb53ed5a6cc5efb1a3dcd93d9c758327daeaf51fab546198e64b8745be832307d235d06ceb2c46e8271082c3070cdd77cb09f0f1f43081bba3c1b9e05855e76c8c6095df1ca30f11109b1d80c5d2f03c42e1d58f80cf879d67cfff7b781d3e61bba54345909be242fd8ff864dd9e4584db12f9b2d24427e5e6343a737a85654060e4e5216ec9f2388902e3aea9bde2524e47209b9a440178ecbe58a49415651d8243c858868ad120c12f2cf8d8ca04ba607d005849424c465250616167e4c7a127bbc7afc78c0d10fd5878af419b5ee1e416c606310608f30ba60ff1f3f640f56f3cfa6781e8025684a1ff578ede8c83255a881cd36017de34f593b8dc38da4c80115acc900230e6ca62d486db05118f44d5f4d1e863ffefdfc98624d0aac114fbc811f05fb13ec57608741a28e6d1fb48ee91d628161d185ad7ec1332c77c9a8a5fa968c116cc1a4cc08368ca01bd9435711ccb3e159548a24b1f1163902049b3ba9086c9e8f3aa55799262cd18bdd8e37f9db51a4099a8b0a55183e54a3a13bfe2561338d5b6cf7079f8c2a5d38fb910c024202fdd01f22ea47447e9421911ff99117ecef5ddc0b9574c88f2891fcf123223ff2a3272da5ab8bfa72c7e3c934efb8c7d71be1fc38f39ab08ab2ec2ae97893a3500c6ac28a40334dd878e54b62c1ae2c8a3744576cde21a23f06e5080494654747961865099b77e27b32409c77faeb5ec2365204f2276ca6389a3472c7be4082a14a10215d62908b0ceb4f630516c628d115831ac74c1376826142e3f0998414dd592e5bf18a85374b2224dc464141f3c8bf56bffc7bfcc05a3d5430ae42eb09d6679eb0f0711cccab83b0bfc4c971251e6d61f3e73f5bd5874abf0dcf2abde69f7b3c5e0f256ca6168a221838c00323f6b1deff82fc117cb0bf0dcf03b4dc20826a7ea38ab7fba879e2d314451f30700073ec042d4ac148c2fe5acb070a1582dda14f6c862df8b303ed1476b6eabbe969bd41a2e9be406fbb7fd65f94f0cc9dbb53bbb0721a1164084c0f2ed8ff07929730ad658b43a65716a424239b61bcc2e608b47df7a7ec43d14571910a84bd077424ec434343eefae2d50822253dc6c1acb77e1cc883a0f52050bc29e2b697ab8bcd146b911221e43f2fa129561c0a7c1b9e657a77a1a12c363798f8039bfd487beeb3bfb68fe1155f18c78289ade8f0667be1960c07c38144b6e1e6258937fe9a67823ff1860b9b29a65a12f58c09306058e9a29ce8f57062b7c4de1277b06048699982fec91dd8577545f6937039d0904b4a15a3e8452b6c7697bba45004fac1591205cd90416404042a8c3081135b8001f4431345c020c51006fb13f9b2a323862265d84cb514582f5d5aa6a0f4b420db02a42d43bcbcb0ff0b54a3e932c43f57c11f853036d3a8c5669ad48497216fad228c9708ec9b35b10d88ee8e2a15fc197498e7f3b90afe363c8b4a868fe3dfd71e4e7ffefae2dcc0f95c857e1eafd16b6bbd0461ff94a4f24694c1c1e6ef7e9fc5e68ff6f7e9b8b9af7cd8480b0fca947ec1fe5a9047f129290bbff2d830f66a1cfe364a80b9a20e46146fdca3640f92ed4921a2a82387a4101196539e0c2baf0c722914848786a43c9248f4775446b082fd69bea688a894ad7585452d11d10c0000003314000020140c094422c168241a536565fb14000c93a248745418894990c32084900100100208000400000088c8dccc00bcbdf5763e6c71bb01c0cee3087158de58af47fafc668d52d7008b50c11dcace6ef541765362c9b20775709e16b79d1e2c8ca5fd113d585aec72495f95a9b562d016689e6e6d922c58f60627a7e5170064bc88a92bc017595dde71fa0805c6b5fb5338e888b1ce8242a363b4dae59a0cfe25656837fa81860b6dd383116b0544ae81af24ce2a5bafdc4a8308f5741483cc46319365a504c6be0e5bc65d12d3daf2fef471af7f1d91f59de9ef45639e56ab34d38417e9494cb508995c9744f9449a6a3909af86e09a94982a0690e6e8c28aecbde3bebd5e8a9f54cf5e83dd01c61ec3403c0ffb3dd6b1f750f5ee416ece557ddd8b94243f0ce583c9acad7be96f3249ed9cde7e4707c222cec723d0bb89738586c6904206add0bd82ba833c64027d57388382da5eaa8340c150adb0b3b07b53699df40684ea7d299eabbe986c4f7d87a0c0d05cfd008b8dfd5d85f6025c6f6ceec522e21b48633100332bdbe1040cc59c693db8a589a7dde0c756affd0bc1a6fe293560497f05b6f2870427302c2298b9c9dff58a90c1563f85691b21146af1fb6b6c2f7efd8b5ffc321fdbef62171035ff6cc51c694ac1af619b8a96c0c9d84000fe920376504d4acee79413ba024f8d9cb34fbcf2da288c33154ff8e28d18176376dd4c91a0c3e4969c85ceb9476e2574164263d7395f3a9fa3a7686eb5a79cada1dfdb82fffc84143d670e47677262eaff857ff6532badcede4f8f3ebc0f8aff899d6f4b46d11c8a819d0b96ed2a7898ad8b98a6f6965b06fe988f04efaa17a1520d020ef7cd69d983ee35621a4ae0d0f997742e366d3ec9c6179193ee51c54aff24ad0479472076105a47773dce41488748fb927472b8eeb82ceb988796236ab76dd152ce500cf95dd060a41556d6e03ff9323729cca2f563c671790b4c4335e868ba18d520c042d23214509e5718dd5e9a6a6b566be52c1852624b4e9a721df7cf094106ed7919aff9f1f071f06bc886188a6388081bdeec4f752ebeb2879cff93dc511e1fc92ad38a3511a483f971918cde9ce732846bc7cfb927631b868a5571f61539dbb0e13f71470ae4ccf9b0769b06edf3311b561bd0f337c9982c39a77680f6fd7483b10eaf55a387369f519d6ea5769b0fa9a5b6b0216d737e565404e89a7a8625a9343e25f753b87941f2bf64f38148b0bc512cafd94440e136ff4c6901e8a8ee9e1fa149503e02fb40d9b1476046497a64fea740bf5c9bffb48fa9756d1e32328ccf4af0be8cae66c24fe16e3ec865fc28df801d155298633e82815a526e2f779ffa687a0ee25a9f2f15c9ac3c0e7a65266f3467a6292e638f1ed0745895ba109b088a6074749acffad589c30ac95b854add954e3dd31e384c874ce359f09ea159a19feac963e71c660c392a1873510eea586f5504ccbb0b53fa2c2ab43d3cfd85a7aaeeab5785cf7715c6534b1c23af9b221e53910aa53b475a07810bb32476b1e6001b5817cb5c9b17c719a6736118962623323c10e32a486a24fb9cffd444c3397fca3a52cd70f0bea66e38cd39b1fbb6e22acfabcc9ba169e7e45f97be0e9711d0337f89ef6340cd72ab1e21e29e81a0feeda5064feb16dfb9bd6b128c405ac79fa01c6955df03f79cfea36f52f7079ee5b4d1ec9d50f149f1c0f6dd86658f832d60e088d234e3860a66423b3b9be9cb60d3d3a2451371c8bcbd32bde333731b619d906a8d341f009362dab908ec4980607b93d3a94001305c7d0bd5822e959bbcd2868fb30ff3b0dc88b74dc559a8f328fec37a1f39bbd06da88a34b31fb030b1e5cffe26e22af74b5d1e45a689c9811d4c58aec15de935c61d40463f52c8bf9b1330fd0eac0cdcb096a69c0766b745a810a546f79e91cb3ec8fcdebc8985e2f7bd05cfd34ffa1b62dd312df30fdaebddefe3d495d032dfc4496398498b61ae80bc23e89e1fcb58aee1052ad20d5aa1cbd0993abb70fb21fbb735605ade496bc58b80d1963aa41803105e44a49e782b48602fb8a26697874799b3c4a71bcd2988d8187643241c341a6166f07156f5b41303f713d488e69abe29b2d413bf940df1e7f56c14a7959dacb56df8c9b85dfd5e4b14cee8cf8a565d3f9fa11d3bb9457a3a782f12227d3ecec7b87b706c9135bad7415c47a48bfc682326992b439349301585ac1023033cbaf14586a7dffa9d1d17bdb975cd7517aa8c37410cde23c208f28783a0972997a22f00fea8051609f5ad58e1d8b47ad8ddfe1c64ecd3a6a119e75e2a1004b24f15a613991bf90411eb3461a34ee642dd4a4b9ed255e4ba05c64ae9c8368c2ab8c2c39bc6c44bca8aade4cce4235c2391de27b35db5d113c27251a382e4336fe64804d8ad5b13f161233d29a034d2257cea0ea8664e0d4b7662530a2d4a582a7de90239e8486faf5dd8453ef9911ba36eaad8631a5131319769c4b0473e57bb92252b6490c1cac908bb23adc8a9e830e91abf3a24576d7ebe5d8213d8ff942ac1c5c074a675942782b2ddaa1cd08605b3952af210c2069706d119893499d79bcdf9018dc59d72754d0e398e86f3015909e70d13423543ef2782ac923817c9059df621323f33ea39e7e5b3c4c1f614dceb990ca6ba30722ad6a7e183dff41f72ada08cb49426c0531f8c37a211b13d1b5ce424212267ab333a4fd7c9819b40bcda9d15516624df0a46ad4ce98e0e5eb5c8dc636fd0d2d58ca86c3ed73cae0d307d72b8d703922c848148d41040c6e4e7a1f48192f34f5ae45f79215c30df061682a07c3fe089f2b3b6754cf4635c5d0cc6c70501098278e8de5d29c8f0c6381827bdca12bbf22d84217eff3f7404ba81ae8afdcb818c64d9bcdcadd521281e284adbf2cfd9904696189b8e11a370e9c2fcdec7b08287d4a26ebb8f0b349121f2b9165ab0602a2a0ddd4bea3ba918c5381042bbba75d0cab849d66b1851677f2d80ec9ddb9d3ea4980451ca5baf77e003fb8a0177d2a4505e439f5cafd53ff5dd2073e1150bbf84366d79bde689588fee9ac52ce67a67446aadf09bc379cb106d1ad60b902956fbcdf3537e9309dab6be41b135e86034955f0a108bc753fdb584fbe2dc2fda70029cada1a8dea300b986ab35f26a03ca6853829c84cc864ca023c3d26752c1a2db0ad89a52ad9e3932a3f0a7bcfb5faf5c7b9a7772b708dbc86b2f6a66e626975a836d85f676f12e4c2227ed68311f29d812191224f0517526739bbd30cbef7de5570093dded20bfa76daef648e194abccde1b8f0eeb6ca48371f62f25929023190d0981f7dfa946767405ac0cd840bef0dff841cbfec4fbee1913b65405b751e6c103957595c9b2024437de8685632aefd073825c6127d4bfabebb3fd24dd3337302a9e1d7e48a7968fefa6b015bea15f37216868eccb46c37faa5b790bcad875b6c98d3dbd9fea891a711a50f77f2399c0d73ec528490bc1894b0eef3582d5f9f12c76f80e10ace64b4fb189b09d1f3f8b397d0ba0dcdad2f84c257ebe2313d3b7e9b9d2e98cb80ab06d8a60738f2ac258a52747b5fd0050167f5efb745223e512fa089e6d2736205c34939f8592a478e94feeddae146de36c96159713b85f8fe6eb75611f3a376884071362c27414d16bce21c536c7b2d54e2cb5d5c94401091f497d088d88202bf9409590b3f67545145e7816932e7e7afe262efa42c874d4c136b94fd46ec2d565ca8acea621ed3843e4d59bc831c4d1d554254fcd417ea6d0224f7ae40fc02d4fa587f37bfc605eccd990f8d1f80e95a4f83a15f4d466f92c87b1372befcf7ff9bc278038f2aa00aef575bd28045660ed57fa98d93bada5a14562397179cbbb048769f7bf888d8a8b7fc6266551d8c396b35b82a09c8514edb8854076b8b7c82f29a46b59812da87560ea2b934f9cb062b0390be579df51869a11aed68f06cf40fefb0d1e9db875a9edf413178213c8e717a8967780e5fad8be9c27926ad3438e85bf8d98780fbdb35937a0b571d0b755f010c483c97a16fa67730cff49e55b38bd615560da418c0c73fde0cd06657b1b78e2b86ac45efe18ac892bfe01d0b2829039f75b80f66c172ed913235c221dd7b2ce73546a9adbad89667b466603168f529acb08d2b6b12bef714af6437797072a58277e402ea2c9eb7a64c4ae5912053ecde75f5497957eb98f90a0fde91251e4dcc12f089d4366610ceb8178baa6ed38ee129f83955949ff37ea7dae43456ae2d875a9b26a37ee2f69a4c97c3fd42873c5ab026d9dac78af3337a77ea0b9a094913b0ebb9c840379795e061cd88b5ad0229745c40b9cd824967801d4b75f53b1c6574b57fddee2d9209c9646391f643c939b48fc4c2ed03753fae15da873183239cc943d60dd7e61e1446f4b66b653b7cff29f481b0398b82c434e919aab04f2fd21f67c097f71fc3651b5cf772545614f3860ef8251b76da66e435a68c1a913b511d39a9c2f141d4c9f44dade3c4f0cfd2c4bba2443248d45bf242836440dace00c92c106c5597cede299364e5248cda8a02c2179dfe03365ece914bb80090b7e3367a80c1b9452014cf7dc5a1171cc0af17df33f618557aeb01ef14a7d529cf190da28fddd1270d7f1bc4773cfe665b1f1d9020c4a35e32c55df1d934b98c8879ac9d51943632a67b0b84c77791f813833f906deabee4894c1793655dda5fdb72ff2778aa0828ffad5c6b2d03eab0210fc18f385bd319d0b8a4df5d6af1907a2030e497c9e104110740d0423410c5c126983222aee559ca093b5063894843abf799b46d943b2018431edf113a6e6b9d47c30aab91e2760ef7be455040c9596e9637a9d061dc1bef7ac06a4bf5c0c05155a5bc5532863557ba890bdd350f3b81406a042de67f8b320cfd50d4a1f6e1c478ec2ddfe7e48f3e1f222d4ca2313b09b39b4cc57256056d8e760bee7cab961b9f4ca7fcb20148cd7dc0e39a87e6169ccfab6b10537702ecd7e60c503edaf671d5796319db236c3b9432b92b621053912bf51fae699800d6045c8a5c0569c5664c6959a02cd172dc55e5e0a7c29fcdb450d10658528b28e9089972695584dee4c89f1602616bc765c57aae40662de3f6a216e8218678c9c6f90718cbfb40625b2f377c262e862a71af1afb431405ffee10f17d790b14c686fde64bca4455eb755a360353a81c1c3f3700b7c40bcea213a0a2aadad071fe16a37ad5e8aa28295b4aeaac4891f26fb2d1f2266da1f73181df6ad8cd76c2d89512e12cd2a498dfb0539f8146072f58376138006177472f901a3a5e885b5da22c7798e0f82b50fc3ecf53486e84f3a499d3444211220ddf027e64418f8b1d603227ab351e564a486f436cfc6e5f4ca98c73670dacea3042df1a217343a6d97d8693a6b115b9a7810af3b4432b8b167804a005135895d1bbace946ba8bbc199f6b0dc2c333ecd2e054db3b4bccb5f0a380a776c05ca1dfd8bcf55984a9785581c2bfd0ea79666325038ad436b5a627dac2281defd6a352ea7bf1848033b59653e9986eeb4b173e72edaf015fe94d1406bed8af9b3185a865c34005460c67463b42ba4143181da0d584219a0fb6d0c2b44d3a469deb7e3e45793663c837ab29514723eed0c88d734ed1f3960a04536a3b023b5021db3f3b05acc76b3194242a157414c37920258153cc9aa3cb1a9209224ae19f7d222015d37313c76662a8ded9bbfcdd564ded0c693d2b7f637ec601481ab1bd8a30158782528c3ee190db110d5244089ea11d5b34e3fb196a644ec97acf61598ff7e57dbae3ad27c79be08f85e0b4add6e2d845863c3a4e5e361ce03f3f60b32d08d97d19efb6f12aa4b6608aa2bde3353591ea0e855429ce829d50d9fe7dd02c5d4544472976b9db3b89073241d899236e1c7f021f2a31f5b1bb927d6097b502c62ddbe9f438f32302f572774ecdcf25a5e62bb7f72762781c8e28f32d056642de8fe2168be17e77b96c26bb44da5088487794177ec8fd1e3fb03ae797705db034ddaa6c421e090be3f82f805f5a721520e54922cfc4584c2da675761f0b26c800dce8528ef89ab60d8e6c962edc2e8ad3b455051ac350795f55b49e896243569540e38620f58ae9318c82513425a4e5a0b00c880c8df865bdcdabec578c65d31a7766013a319adcd5e08a7c393744c04124ec55506e093e8a10bc81a1022c500660c2b6e11372a852aaa75eec54f694ca101136df74a591fd3076d63eb7068186711e3fa62100f8cd583044dff7717b4382a46fa60ec84ebd6de0e5ab58759a5dae8e6dd82be47991b72e33195b76a49ce5549be04329ce0a5edb4f4a441f481a1fb6630befcddb44646bc7ceeb49e0c1051c04fbf9d1afa108e789b22ab61284549c6d3b43fc8abdb64d5ebdfd64b1e2239513ffbe136deea5e41cb046ce5bcceb09d949d60cb224562673e000e60f28f86ff0f64a4eddcce9b1a63e58a0d2dcb24f89df13202f5baadf62964d6fa3a6bd092b9b516efc9c2361d403355beb7a7e7606420f968f991fb279cf536cdab4310324fe01685f5cddf0882e10a6b5d463668a61ab986bf8d5cd9876a11f6f290dea7f2980ce94485ab250dabe997c02a473e399000cfabe4ae5eff783f6d281e4f57e909983c250cd3d1722b0d6c915d326b4ad57744ab6fa07adb81b578de5fb8d3cbaefaa4525a8e84a9900f2ee7a4ac2f2fea36137964de3ef2a524a30aea830f4680ac29618ec860567873e82c30bb54cda869e676758b27a360bfc99bba472b505dacae2a0c2b882413f8d737fc882b57f46e09950adec77578cbfc8c4a9facc280644d60b5d49be8ffe70479d610f50422f813aff62050c56b53f3dd860dc6ee6d37aca382b673f09aacb9a195ff80ccf6fabf93b6ea79212a0dc37c4c7db5cf95978d429977b152adf80ca9956d07cf80fd6fd3a69762ae4fd39d382e540e837d5d019ce8299178db971b7dbadf2b5a39fa342607eee133fd1ed9dd0414efe3c47acadee50ba6205f69ef82524c4cbe793cd73bfc6628b010a9344d626f85076b62e41afa88b7811101f6bc02d89171c79145ed83bc87328ae8886ef772a31fa4270d487c97f6290dce582056b4e3da0b60e666a9f9251ae843591c79cf7f4d519e76baf6b7171a90326ded473e6465785bb40eaec41834773adfc90ce8ae00d76ab1da3f16433b84799c26d1b9090e65ec19bc7dfae3d403b1a8f3592b02196102399580887b4117cf8a324fcc894e4fa4adbb83737a66e5157d0e130254fc2c0d673bc48b34fe46af4cfcd35f8ea6f8981a7afc32f3c592dd2a37cb879a89a1b5a54d112b29fa26e5ebc95d10bf443a691e3c1d91040fb3e5477f8a8559acd64c14d970bb17bf02b04184d426a068df17a5e31605307bdd85f1542510732b20ff8dcf39427563d9bd147c5b2e0acb2e8535a6ffeb11680671d62126e5baf9784993ad0f50d23f98aa56755585898e16d2623230f584acee1b1ce551b3e32e007a2124c4afca4970c48f646725aa770f7976c07d0a817a2d7d778fc4f5261374483b5cb6eb46ce340b68cbed4ad9ad300a86d5ae59da4132340a3514911f7deb56f4fcbfc88c940eae3893ddac04d0990c616f49d262b7bee75c2269121d9ff9bed49af63703d6238d4263f59163e2fdac8cf598e628f80b320517ed7ef93036e27b3526daa65530063bb707cb7eb5a018f28151b090f053804486272ac002764ca34e2a907dd091755b4823136a525b5c78e90f95b171cf8890524394912bcf068447ad868f0d4444d1474e4e3bcae03968dbd503c16797d0542aafcbdfc5201e26465560c2bdcebd33d0eff34536670327786342855c6c64e7d224eb00091a5fe8f9631c4f575754657f51eb2a4ef2ee780a0c2ffb13400af9484d3b86642649d00740a26fd0c0ad8d9c26f56f9b83bafa44f4fb7e0f6d93bd4b7d034f95636ca01566e60fd584c4fd6676afb592e8cc86f6c016c4b6d2c31ebb4d99a524dd063a7791de64f796854799a825ebeb537c10c91b49023c77594625d2d36d3e06365977e2863f29f8f1e6254879550f54a9b90a64650716bebadc514d06e3384380c9003b5290e42989d4f28ccb4bd733a0e5dd88e08edaf1aabb02aa09ad3ac24835b8043bc7c580bb9d061971e4fb334402ac542b17b54a641c47696988f3ebcf9c33980bb39c73d005b3b78619454ff35e1ca763443958441a1153820de26035086c0c012e787e797f40351738703850a22f1cc198a37ecb3a07e2b310ae52b57db03ee03fb27602d6530dcc1955b696b2ff0c27646af7e21fdcc14589c2f3caadfc98b273c9574b1931991549cff4dc5b657a51419e2b19135b07caba82a574c092313188015574fe605c3e05a275603f3ad7a0d50955e3e29df7ad8d91a28372297fa11a1f45249fd7e173d467f7fed9aa699621c1509875e2096c5ba815507c32d33a99ed598f5a14830fb4d4ab9fc1392cf97043e6ee82adc5a8eaa91af973b7dd19381a2fa81957b4f921144d9ddd6bd61cc4f18f432e932fff65d4088f85827cb1c5e2d34ba0267f52593aba9deb026229772971d928ca843aa407f027137dff4b13eba0ebedd147e23ba8a02c03eb65a3dada1bb84205450a7a9aa25d92ef5854d54e783fc2fc445dbd04c1fcb8bc73c85579c12bb271be3861fe027c39fe5b1cf080a4d79cc7c5eac2e896aca823fd8eb09e6b0704c1a086b54d9f463540e0191e000b74442d695a1799a4e3b955ebabca6b3034eca7b6c2b3bec138022a5b25c8011c6f5646c81c3fa844b9891597dc6539987ee13ba184238f0f2857348218eb79455fbdd88e500d5ddd53a9ba62c881504abe4cae3303daf32d9113bdd196a02077b24c919c2015beaab82960ec3b37578aa4de809a62bf3e8f7c63ba898bf0c9f3cc26914bfa0479830785cd17578ddf70306b4422bdc151047fa90386e8089386adb24b6b8d15572c7fbf01026c5af2de7eb0c2b5adb36c25b9c74dcdbb061b131e38e0bef894f90c3a36634e02851212fbbbf8ec840a06893e00d5fecd986cb7f6fd24c5c473fe7f5f66fe582c52e2b7f23935f19bd81dd0961016839db5f21991a58d70f893517121404f8a5622d4c0a074c7bf6f150d3b373626a8c88f27d5308e6d48b7594147a1f16f88f3c8f173a5c8e4390190d6cbeadac1e8cacf980becd5e9ba39d3568cd8381cecfab96dfa91b10b7b58bc89da82659afc7990b1e3e97f1e035e92449c97c5f6e7acbde920b652f89f785207e0b56b92c8db69d80064817bf5ba94a32227b959755ed526e7e05e98581bf23b9e3a8368b03508ca034e49005c802cd4490aeffa99db98a33bb15fbe8f836ef5752b14eb7078a1c43225c3d2fff9882b6895c4a3bab235804e6cde26d6bc41bcc6bf0cd72d33aabf709645b3829428d9a39512b1e76570af0822f8d8f878203b8346174a61638943c54c4a2ead1b40e4320211ab1d5bf0a0868346d582dac8986ca3b9011243c7f85a851a72cefc6d019c5a82893f5b84222c512f5763c73473ff3577a9274791eb64f101b1953e0c22ee1e30b918d815690c3836e779d011d1bce269ba653c4985b5b3e2ef479d2418b081064647d19b84aae48b889e39c943316fbfcf47ae349b2417dcc27eefc2be900cdf9719d693be3695664a53771fef8654bdcea05cc41004c378fa12a2cddf50ac98f43265e3e29328cd8a2222dedd8cb2d0a96563ba029b66468c7b8a56c1f02579af90519a0d2ba936922a254094ca8edd7ca4a7ea9f0807a074ad8dd9aa03098d469fb5df074d6da54fb92968572c024cb923006c87a22fd81fa59c499a3d0e773b4e68bebcc2455756380fe895e78b014f0545d3af69778e40ae15ddd4532fd3460808262ac9d30ea1994c422785cca3e629e9867ecc1855d80d799056ec73326366399e8656c693811c8ce653f1c07756c8ef1097d3b9496f1196e0a00eb7924743b2b8152a4953d11e42ad9abfd39499f4be64a833dd70daddab9d88e0ea1d46c50d83da768497574e74563eca8bf5777be0ac635f584cd65b9e9cdd064778df888e18d70f92a1574a904ba7209a61ec8e9ea2db350b2b764bbb78114e8c4c40d7828399a097ceda9615b2177c239981cc333a1eb5e75b14eee591eb486f87e41559658fbc9f3a83840a72f79dc19c5f30544c80212c36cd7c633c6064d527e3144907f87083da6c76fef47c49d85837bf7872fb5a3fb68ffce50649e11ff3632f2130de24fcd2029101c40a517b8de2ad59ac9dc7b5e85b514e8b47b281ee310278a5a4085633c5b16905ddea50fdbc8009913260e8065655320de44257f6bb86c6424acbbac0fe8839895d4fafaac62234d33125b83082864c40749d805fe81e70d4e9f6b67c1574e82d0ae40a8a829adfbe0100cf122ab347a56239a0052a5d1c1734141c1667fe9ec8817d2f6d7c62db5e3b1726eb44de3624e9d25907093d8080c56b1dc81858b6a36d8fe49cbba0291713b03cf99ee70030e00f3468eb2a1231d51f18abea950ded7b3a59c28714476b38e7f8507281315cced5d2130ed5ce20b5bd82004b3836f98b6e3172d971ffc5ea0b3276e6ca3bf135c9dcf49da0fa7336b68ba399708c3bb89836b104fb69aab8a9e3c431f21453c013b37a21d25ed2f3aade04b71e1133b36184bafe1d4c119642d280d81677c5867ac4d418fe2358ba8296ee4a8f0f2cfc9e9226823c2a5b36b1558fa19723d5f3723dd3cb2a57ac4cf185e93a7d91bfc4d4833c9b0cef64222965c9577134b3f5399c2e4bf5a6d3b74143bb20a04530371fa8612cc16e2c26a279553eb061504b860e838d85807a4fe685fb0b8ebf112014bee891d7786eed29ae0d6ce127cda968be7faab74a86761d727fbbbfe483630b54e1771a59fcfe7076762c00f27475f9d9bccd1456e65f64c9f7952f3b8cf5a61025961ab9c2f28f9322e95daf5de131c27f026755bee9f136230595c0e9302a85d1115ac6a8e785b4db2cc06b5e55282b81c720ccd3a5c6a4aa2a7c3661de3428b94782e36cf5e2dfee4b58777f38496da8ffa05483c22cc126d4c74af752cb35d22981a3147767685e515267b275320877ab5d69c66c8240274233c04771b59d4a9faf952ffd8b48a894e59087d8609285a738dd3faa752c2ac53a195dccddc5e6d4c9089507e2a2bcce59310c2b7ca15aad2e35b2f302936a0207ccf1da58143e4705601ee50c6919e430d6b039d4d79805614ee1db5ac9f2e5e0efc603a3e9b17fe90b60f11a781038b1eba91949c04bd49e4ac62d60a8fd374d287c649abe7780482399d05147a29113b7fe23bd5d3f9723fcd6adf25d5771480b111e7cdc95e4f538d7d6b66a95fd994256aa795bdd59e7f3d59990a36392c19baa6b0f77280f13c45eb0d73dcf1da213e1883256b7c71a00e8480605c0241f5e8065ce15200203d55bd87f360ad5b640e84aa34120614a0a5e456fa942aadab667848337d5cc9a117f10897d8cff7164300889796776de4267586dc7f647be27308af6aaa74147b31955c9ed5d6d528db089511ee643ee422b8c68ba8daa06c463f175a3639439fea3f1eb5a9422d6da4430b93bc9ccfd78c1868216c745cc6a865a148185d86a13040cf8f4029e99fe8511e69d52d974ced5d8737f24b5410529c3de2ef4221231098eb8fd4f42dc92ab85b027a300803647f805bd1838a707658a76f8955a7300dcd55539970dac5466345b9b697397dfe73dd8a9c33b6ddc8d7c5a0e74c6964fd7bfca9ecf1da92efeae0fc0c31f741c1ee1771a9ffd78f0c66d009e1e0e7260029fad0b242cb3417ddb607f20b204b8b8396f25249e18c6839e4e9a76d36fecbc7f813ade0ac4b68074a615c0c2d47d98bf12b1824e6187dd0a27bef1b0ba43e257624aed64c7ebb66f6539246cde441526fbcea52afa6485049cdb7901db46aabb57e6862af9bc93c51a4e104384a9e8f41c5b49dd40afbdda07daf023f8fd1dcc294bbbb709b8a42dfb07e46a6696e7f6cfcbff343aa54e471e2ebbac8a2144f74fc6b1e3cc56a71f2f34c48193d6c9fdb8d6b1f21c27ca74adfb502a81c0916ac79861090cf068b4a77cc07dafd7b65d18973d3b414b658d644a66ceeb914b1502de40dbc9e8d4fec7f214c11a8bc0c940fb79fb2b3bec90e0bd644d0b0510df7e7c26e759e8e867d657bdddc42a5b3b713154b64b94e0037a05f18e46afc587760a6c5bdd26b50d74253749ca59afbaf37c90bb74a37bf2ea46b37a9b0418f7bdead514d04fef50eb1159b99dc9e881869e071350f4fc21ab915a0da775ae5f61d4c7e2c5b3bd94cd5d39678b511b3c546af275cbf2691c692c7d48fc04ea632842c16549e632d3f907199d84f9caaad40bca46b809f3482870ffbea262fdac543f19d0fb176238dd863018a7a4ba2bb6b025eda03a8561e7b3f54ef0520ab683e32ed13c4e085d00845bc5580747a032c29db7e65d5e65dee425cbfa1a170810b5d1b0bd6b21ec90bb9f60c6b2e1653dc07df8b1205419eb7d174dc09d8b0a76111db1d50996385d3605e85885d6464ce1a0136eab729452b5adebae5addf0e1aa1a8bd61388ebf57a69f3a1d59fd4a2305cef3d814beb1dfc705656c003a0fba8b0ccb9e49e5bc2ce10dd2c0289b9509bc925e431229628ac0f26e2a95f59aaa7e188a2ab64b8a4114a8212c8caa7032220c07f5ea13a4e4870f92f2f35083443bacc68ee1c94b5f9f0ce784b5b2b6673be54a913ef889be364e1e2313464cab6738f7a797714894c3f48a1374212714a6ba2cef2251871ddae1abc1196146178b10a6eeb7fcd5a0186cd9475960133ec2e0e235c485ccf1672f6b4d0c9869979a50bd02574c77e36da2eb2c85cce22f3a4cd6eb00d1191c93b3c1c0af0be5965158e153c873ff8086b5a05a2d06a3a59502a2b98b7f7f12025e72fe85cc511f0396fef2053f9875a4752e429c7a11097edb0c56e0b0cf8eeca88ca796bb13311fc6cdf3a198697e5aabe7192e1f666cc86c08cfdaec702a506d8c46e03ee9f3513c9a8002f3786cb5b4fa83234bf2671c32b65745007c99c02a8827da804deaabc33ff29c39a25aaf6e7ce24510e7c0522cf222ecf793d018e19b4aa639e930b9df07879c1b9891306daf1eacd6b0eb9164df44d6e2e033de5e70173815c310626679f2b20bba7f03d0f1830856d970acc922a04f4147456881ca0116703379429663df745db1ec3c4bf370390bb00a9d8df5317f1f0dbf458629d0ae7ebd3c1632f39fde2ac412eda147d296acbf6f681de73b78c47ccd037090f51511208eb6d026e499af8196f6467ab17c0ede3c3f50b8e01f1d626d49e354c5e4d8ff9ee839061b92079a77b9d6f0372be558dc652c0f9e6a788591f64aa93abb59cbc014459c423250e06835f90e624b73f5f5d1a1e84c37852edb3c26c1f4abd664981f2d5852c9e377039bf01de7918687c8d901eb3a1ce1984db8f968303ee066b6a912c51c046fa7832df2b660ab588bba9737dd33d6c15a9bb5da75246c86520a8a0490148e078ce25f25b674cc05fdd1514ba073408252f414eb4d3c791845aec4424770b5a4513cac9d416a67be8e075b3dc5925ce50d663e6108c4eba29144adc2b08eba0becd5a3d2f5e36683e7aac4f453150863669c5072472bd230db33d06bcbe0e6a8eb602257c4f10cfe03e3f712c238125c587ab72bcab0cba1f21a2ece27431d3eb12c760eeddca04f41bc741179c3f211619fccd0ae50379275b4cbfc4ba6570262bdd8ba3face03a6e65d2fc6bd9c1fc4856a279de8a0c5e2ea58304b7af1b95a5ba6c2098a0debe67315acf99def2bd16adf079cc9532fe3f50be04123445ad2cf72e855eb36ee3ee3af5c958ea6d4436f397bf106727bb8dacaa0a0a044cb31f456a3eaa7e2c96800a3c59b6c3255ee8326545d956c9119c2a0a697168e06acf621abc8f4b81b49d40f25a1df010bbb1c423a37e0425cad5b2012ca5d4e3a0a33809535c4a730221ab583f478be52edc5f740adda94558c02a05cb0b56c88690e729bc549b94abcd6c2c8625ad95f31bcb22ecbcc88543bec00ab75aa7dcb1060bbb0577317bd8cdfd0a967d2654543e5eaa9f33aae3e901b2e4f1a97f7d374918f86ed083bd42c5a4b996d2739b7b038bdb46bcacaaf79bec7578ab39d64ad25f73f65773e859e2256f225d23fd74ee4ba350e12455b79330890dca9b7f42e8415bcf418991255b2df27434257b5f79831b7f2c19d930405c1f06b5e75a503e0e68398b1b0023e21f1923b34a75a1abc787cab4f2f71cef7d63915d4f5603d508159e12379ec711291f68b16399504dd2b1467a97397febe16770c0f2be84e1070bd7fe1e8e1e5ed91ad8d01ff3d9fda9380a4549d1ff03ee8e7199adaa900de48ba7e9716dbc5037dfe3dfcdf8287e58d41ba0b0974b342d201c991870db7aa1025987f13d1684998c2349877e37d0870fe0afc7187a0cfd23a91313c4e80ca78d029708da936e8b639735f06ff3dfa3dd4c0b88b836295347dbfe18db44f88f48083edcb1bf8d9b6e631c8c024199ec4a742276a570b12d7f6d9817a641605708c51d556f44f2410425b799f17c9d0e5598c89c75c86465e1a7c2fa137e45e7eead8a23058afcbe34ae1faa1797524ea3d5ba8cff0d807ddd6186280c5d452a60ba9286dafaac4d5462a6ce0accb9c7f2d9a2dff6f8e655d24213e46593ae40cbf59ba930203b0494d8590ceb027611ec73cb4af2da26f59d3158d51b921011e0513376b951babe06657128980117b3236b8a07e1b6339518c4bcf802621f2935e2541ea1510604ed5568a79db93029ed547322b7f491d814cc97b3dd98e957eb595f210c2a6291a01afd7f7322ca4547445fdbf605986ebdf5c1737dac4e0a070ba15efcddb901c24df73ee1cabaec7fdabbea8dbb7a21962ba70d042eb7eaec4d3d6a4b2c4ee8f17a12b7ae6057ae1d8b7f5bc74eec9501c25193300e1281f59df0099b98d44595c0d89e54c7ca284127fd416fabe89988564e4b669135d83f9ffc5c8b83049e2f0e8bbc813640e5953558df94f3dc627e1da26a1230f6caa37681bf48c951748b61e686c7fe2737a89680c6356ea4ab9fe392bcebd71d6439764467b3cb33758bdb38ed7e5dcd9d5ec9826a81febe0f5160696ee01bd105f2d693e5b3607a3e60b370fc8213065ff51080ab42c1bde746d3be6cf51ff385a9cc6b6a5ebc25eb4f82265ad6debfa04af85cc0433e6bd81dc9b841c618844604312de794c2c35a40b00d44f295f7fefc42d6245c853c9700dfccabaa12921e732e3bdc3316ae6d2288b5229ab0994ae45208d8106fc81be67429b16e51b58c2526a201e87b0c3b592b449df3c577d71f8a04539b6db1bfadd365e8e1ea1f857b310a7269f8ffca013ce0040e0a94402d721d2dcb7144842230d8ae5ba6096a0ef0e385442a06cc3a4fc189cb29ba7c73f01bcbe4578f2220fa8e9d8eaf2b3b30e44bd345fec1f4465cd4174d36df8286691f7de610c68036a1da277f4f4b6f0dbb9af096e28143741fab33a66dd42986d0325d7db58f8f3f1aec37532b5fe98f4f6712b05132edbd7a267ed2b61497fc5326fba562c135c5e3a7cf6e69b5c79d986fdeb5b5cc4dbd2f2feb682ffaf7b3db72c769a910c38a84479df4e6f44c7d965313fdfec9d38de5ab865afc6bc2f54d61391c793408189158dfa73e1326506638c269a3432c8a6a5e419617a7703406c8ecfd28090fa106b6fcd4aa0b694e2fbd71660f3c158f1ed1a71dc6308ce5e20e554460b5b6aa556be16f32fe045605dad9f9284a2bed96c0b38a5d4e3dbfe65669253d5ab09fe7c3f11499a8fa8946ecd4a614920d903daf0240cc858b286627860772bca0fe65cebf983a77b7e30b505f9f441a956f3a6dcdcff57d8185c2a02b625ac154d9a0c6bff61ae24d1e922c4356b5ee11a261034ab120f1c0d57b0e0ff17637de91264cbe05dd5bfba08aa52305499d3d066024c992e30ead93c16a2518dfa4021fb0642e0840e5b278d7b83130246640a831e05218332971a8f3a54f534eb50d421b7d53b055a6bd8278b46dfe7e5815cdeb6246eed85dd5fd7f696b988dd8e956345175dd20d87a952fa740653813bf5d6b61fe75d4f22cf02fe8b6f174f40e3fc6f5bffb2c390a69b8d8d4c2b57eceb9ff0cd485ef96ba1806728d714ecfaea3ae799b20a9005f3097fc71a5a35e225b8ac8732a79ccf701472f36e3f4e4eea47516d88cc64c1392908e19247c44fb364024b8d423a8f38c2fe59c47dfa238253a0e6d79301d9d776320b921936acfba6ca962596d9dd78b0988a802556666f5433b3e2f6eac21af4ca7913798aca6e28fc208aa14527d3073a77d12624deb7b20815ab75e1813c3519c1c5c37520a20b6aca1725d068671cb5ad166759d49905853a92e6c2490f2178feb5e8e7a215abdd4c4bffc239804416b105be934b40e1541934858416ea4cdddfa126f4038608496fe05cc14880c9686f4cd2e5c720282f44893c8b1ce3d65a78dc57b40bdaaaa452873b9cfdf65f872bc031a2c0c70191469a362e29b856639a90f17f131393ffeaed7f7d5e60bc82c8f7b1aee3fb798511eabb05e370cace1a987e43fda50243f5f5e7af26244a28e05f3d530fe25feb942c82d3d846b453a21f02f8003ea688a09eb4cda85f5db4a84b92a203adb2b91fcc1e838beeda0a61bd634bb4d7f3e07d81e70ae5ec0bc7e19f46d14da93fc3aac6270c213b42de1dfcc2214826946a151df09f67e61465d77b315040663a254059eaa2b2f7442217898c44e916ff6930ae8632d576d72680681391edc4aa1070d28a6984ffdb07fae5fe720a84e655682fccca14b3b95fe45e3534d2dfc9a8c33af0f85e9efbeea970d7ea6cd67615b3cbd1ae6fa268b3e944a1b257542308ecb256693482c425a72058f851080a90de600d140c17db9529c5f1ebf25624bcc4fdf21632702463ad80557089eb12bb31f885231372294c6b89be502b9fb67970b0e1342eec0f2e79a978e3ece3da45e3710375d4574a7d37b7ea180012f55c028287d3f196292b8f15cc730148c01b945c159079253081b0469f3f173969d10373e8fa3e1551be375b6c7130257dd0f0c64454faae33038fc283da2728131ddf19092ed4cca205bf64b58efde89a5f2588c80813b57c5eb62935436ab33317d14326d6d1a8ea568fdc2b6ed08ac46ac01a7c6947b5bf12d025d82b0d09a51d51b3706b8029b2db32204e8c14c5419f7277aa22177da15b9548ef6a1a1021c546c0a7ac5a55624793ba578bd21dc25a704f71dfbb7e00f0bd3a4d66f2052810cc522e9e6e2fd1fba9b82b60aee239697fb49c3c5d70552d7f5d19bea8bfabc0fc2b0ddfc9af698f200d9eb9c36c24743ffa456f09b23e9e5ed53aad1d7fd0580d173ec03e6ffc819e846c1b48e11ab6f5affda2359252d08c1d6e85e3b1ad4d54b987a3972609006169090f3a2ecd76e36025e1a63e586f9e7e3ac7056dee318a69c14d43dc3e8d10b3979b0a1ea2d5d295e1158f018553ae51bfdc0dce2773b4b5b40dccc626ecfa4315d5ceff2a04fc9bc5cd17155260dbbfb6aa4d8464d7290cc72a8b6dcbedff33af42ced74343d79ea8b0ccceb708278046f390b0e4de0174542c8b348f49e974dcc7fd668a3b60584681de095c4bb2459a0b4137e64a3c14254d453902b02a538db34632bfe1b01ce2aa901669e666a5a729a3418750525523195c1a880397813006ad911821c16b8a8036775d650b3b4171eeab7e2bb31cab766b05f649c881a2ed1e7c75c14f31b14128c4612a88809e212672cebd6e33722be749fc0802a2327d7cc5754ca6c4ae7da09a891c1f0fe58f9b454bd4e0a51af5e2ecef75f772b497423c4c649c12251070f9ca16cfa0738aa38968f569de57e21d23ca41e19f2575b819719b570ba228fd43bdef991de75303c8ba5293209598e9ff6b0f526ef6fa43060f12746dc9a07c8b2a5280506d2d499218a00488ce0e065ad8863dfed034bec077bfafc3161ae11163f774c9ec139cb2d5645518a76590f1bce60bbdac2d1cc4fda17e58ddb79968b07490841ff343cedf3ac1a30457350b99fdbc68e947c76eb6f11f1a6e0a5b5ae848e1bffe2dd4089d334657116ca14ffe2eb60d124ad10c009a9bb07005b3bf789ff688a22326909cefa80538a43198460683a9e4471b1a53bbd8866e349b387703335543677d9a4a54b02491062ca06643ad4a2f7f24b43dff7cf10b6143ad67cb1b94057efa53d0176317d56de1f6e281d8f69de842ac03dabc5548a7c980f8c8a21f6b3078814fee14aefba6045c5be1901993d17487fc98217755fbb946c6c411329b65a23f1beaa30c486814c43435bb9c359369b3f51b18f1a93f5dda68888037f42830fad7cd6950c6a76caa3eb0aa1553ec0154a844d5500273429f0f5099cf157084e086401591b370bbb965798bbedcf959deb54a07e91ed9a85943f7859369dedd6d94f3d6544386eebbd81189de3d5d7e804ecb2743fc9e2b1f55d390ecbf8923ef407b8891b187cad86a4859bbad945a5bd79d6a2c3dc5ea744dfea26af63501160e57cdb707b757c2b6f7aacad8fa76e36279837d4b118865a31125bf98e4772b2bbb74a397d1cd0ff53edfd81498facdab178efc1abe1d446f8d81074f6ed8e0e4011c36de9f15ea607049fe08e1a3d5e9edc7474ad2407c1d239ff4b0bc3678446e9122070ce4adeac8f87b5959189db3099b04c8eae02728c87591119273e30f7a02d5666093552ff32f35887b66daf533ab82c2988d4c1c6a5beea8a25a66d08982e842b471793713aa60b48e69c0a2a0e8e3d844e287e928afad1e0182897b98cd191355c60525231682082aec10d9edbf30243d076d89a3a9b10b9852f603b6d3bf65bddfc6dc7a9759f11715cca376b9d9d5e6fcc3b32da6b6e8e06ee65eca0102b82ff39f059c6891caab193a428729acd9d4bfc23b7c215c7358ca0f72994cad2a3636f244bd555399c8fc738cd689127fc465ae80fb0c14a23cffe05e490e4b2a671236d64b7f8bff6dc556664665174097238b61ba621725dfd58a6fa1c5b75df1d7ae3a10174c558603399f5c3ae3c0f5bf15a307d5c0cda1fee964db22b8f89768254c97efc8142d324820a856b1a00ab6aa4cea9bf90a75c5fd2f4e896c198a65b3b502722d5f5f333c066deeaf55da4814831026585a4bc27922c3f4b2b3c3ebad9fee018d5c454a7f491b09093edc15283e58d3f1f864030d00374f745f38ddfe4c28dca9943c9f5b9c3c368f8cfefd411a186a12862e4cc28f79b594c44b30f5b3dc42bfda773ee74147176acf1a2b1589a03243cda07a9508a02c9fa7d1b3b9b79c96b5c9f2d3f5ca7f88896a7f1f652eb43708d58a65b5b9db09456f17639dda832f0996f46c15450a8c404dad293f53a5b51b5201d72fd818bb8cd000d4da8c724eedbe4c84fa14f136676a679252d945d2c7e3f3349f7a94f9beea5afb334d341079e415b691620f70f04e0dd33a974800f6a81b68fec66b060fb7b80f2a6be16d0c484d78c2c88a129cb2caa3d7324d82d42a70328bf8262d72271a79dbdca2c1827292b4bf4c19cac9dd90b150d1179c4299c13a65226bf94e2a25cb5e0a44f2739f49c932539721a425806efac7923b20e85931eb52ebe109ff57261b7e026e0ed79eb54fba47504a94089da386ee0975c206cd5335497ded0352ee7b17aabefd6c059277c127397212d0a2619d6a3ac15409bf529a1bd7708ac51218b7ba7a4385c83bef7c2e2c74c0d823e220c930507d243a2b9a5c2bb944ce17ea5a0bac841d96af901769472de5ceb2dcea0230b7cdb19d76207f06815eaf8a6e9cb98ff004b23bd05db6ccebb87bcce47052c18104589be143e2523e7c3231874ab8ebe862ef4aa39405b87d512e4a113588aa315e72a3ca3ff692951a38e7532738c4f7de9f903bcf4b49ec12115caa2bbc765a706b32d9c3a7fda899315ac053a1b9f0882519ae659be9325bcbdcb0a5e8f781b88442d7cf0515fd7350887d2243e28e51352adac496b8d3f80a11d5de5f029b6c847d5929c985d20ba849ec6b29ff50af08b7f8936806281d2142383a45c1193efeb21f025b3581d5c3e2c2a025cb32e862812469bbe785cc8f4c9f5f33e0bf19827380f193fa5b4149fd2cd0c3da989d7130f4090fdb16813f460681c915ddec339f0ba91b75e50556fec47a8f3ff98d20132a371b23747458c783c76b3cbb991d5b0d65124b21711f927a5ad4ec04092a709b53c5057dc5d2996b50ec75a134e08ea11372f79ff577607c973cc4f8ae6a659d700b81d517a2e4c8ae86ae58e270cfd8730007945a36e2a961a9ad62e910a2f49302f72e37b5da2b96509ed24e34108def9699c60d764a048f4b15bd8c6dc5929832bbc255281f0d1b565b3437d8d5f79c38daa64e202f1e2486b6a225d84b6fc8cd10c6a1cfda57615c56b56493c513528e1db1f698336880c37dc6f28f003734293ec044363692ae938aacb95063b6e836df1a150ae99e4b620500ffc6f02d3c2c5ab76eca4e4f5f49be4b62b9d8429f7b119dbbe8f5eaf32378c6c974d08dfc816a3adebea48ef26c87f6a5a31ef4baa4ad2c5711bd842fc4f35b5b6bef928bc034772e29d7d12fd3b3b61cc39288ce02fa2aac3f5384d7795d09e161981bdd706ac5975169f5564fcde8730c8e9e333aac2f6b34c82a265896aa1f0cffa2c3922661c287dc70d38775a6102b0362580cf630bead344a7151c89a78b38b32e14e69e7e9225651d4d5e029c1e3ca3dcf644d57ba33a68bfeaa1a0774624230c57310576a5763e20461afe83d0aa7633b40abe05035e1227f91f23e20b2d2fb35738ee96a9b25cc1f2b4d5fe9e0b721c03c2658362cced80d794c73fe4987831793fe54fd9e8ceec69cc7f42d1ce401f9ff3ffc46a6b4a3def35dde3169f11afe2f511bd129bd3b98cb50a044d759978ccf24a5b2c36735f4a8d1ee949d3a7d2b2e6fa6e04c773cd9a9095188c777ba2faf365212be1a127917c8890e51b967bffb5d795ea2530742b83ebcaef03f0583b5a4b25dacaa5b0ec576bc8c8d130cb625a00fbdb9280863228a6175d163175dc7bfdad5c8e0f0d3bbda73b69623b29362c63783b428f3d7922a5199dde691332522cc5aef067f2f08f5a50081e761c4f2913036f95efd24994adbcbcfcc7b67f2e954c773e824d3d95f8aca04cf5e52bc671387778f56ceb445f671dd88f421e7c434b4977ab40ee5f5608f9b7ea08b23f2d870dc6266c3605194e09a9d6b9501d488008e1395ded04f2c8d0d744e23bfa87052621fedd6fbaaaca5ae1b386dcd2261468c7f76b2c289ea6e72743e3fb43f8019dbdf0b4504878d7b69528f44718ae4cc75d8038a4c2327acdeb3c6a6c59245c59c82f2120b46149093f23a21709d6c33e8c9df6fd25c110e7e4e248f7e96b1f914a6a53c431b0aee78db8ee06874dd6367473594005f6165249a87e8d5393cc1e1ea229a97170b82a5690513ded5b63712d0ce5d205d0024198f17a228d8f22bcec62a047a6fcbda1963d5d21832ed074457f62a30bd8f21aa1aad4284515117ff9607538eb51d51cc531d0fcf88ca9900bd28a07ef11551a114a2e691d76822e62a6c6274c8a57a33513f4f08d815fa6d6c4e69cb8c0fab17a8f8ddfe12c19a92aa13223213437ab2c1a8d60f4445a4017f2939598dd3a66ec29b695e89d79a5856594134095eca45f19823022a4c667b5791d28359c317c523090091f4f931db7c78421370ea8a7db22efe62cf1d571faad191899379ede566f02ca7d0c66215a9af4e8d8eaee682e4107545233283eb5fce62ce0149a554abaaf182ce649a4127df77911efb59e15b5ae5a98f481bb0ef1c99a95952def60ed376b73996903c459aaccae7f91011a30d6f7012136000dab754e0069367732339d2facc095341c57c88862ad0834fe7ac044dadd64adac246ff99569a120d11ade2f87666c8f801278075d0789038a7f70966c77d3e2eca2ddfb93524ba5b7b7fcfe082e6f3d6886b3b42137225509d7239e61520e98c0231562b6c1a48bfaa595b97eae369d9fb867b9d2b518b5ad89d95010aa1603afb5131a64f2c77524ef052659fb2d28398a995494ced22d049a22e76ea742391895acdd52ad57392c0b5295ea0f3188329614e748262de426f468b782a8be5fb653de30e49a986083863d93a004bce83179b28551d2d637f9ac90c28eeb637138b582491f55ebae4caf8fb76fc4614bfa141f62bb84b3e2ebef9aa8cd8b3479500e4b8c90ee94c49e8568d48f70a97f810b3f92a302e475bd530d5287bed51551d208922e93ae7dff3826546786d01ed0187a239ec9b8ad563622c4d849a7e5f74b7e1f2d1d13eecc1f5eab5ec6931d34ae1cf0649b4cf3086e85c48e6af65d3a2c97ce4b90c21c1048dbd9f76d47f3d77303a7d4d4228ed7329207655185cbd133a401dd27d6dd8936dae049bc6fb4a43a661700d94cd8ff9fd2a72a5a1037d5388662543abbf6855c2bcce3ff48a120208fa26491d12899d0c65882f715ee6e93f28bed2679a5aa7559b3ff172a82a96b1282f642d40fe5ed612e91680c93aad62000baf15bde39069cea3150173aef8acdd9c97784c6adcf93b5dfff574021eea110f85e88b446ac170a5293dd211ab2b12ed2aaec3114f309c2b4c13e883d91327ff45e8421ea22af943ddb9c214dc95a94a7624dc40faa378d1ec3c755e25c30329de3398ebe0327ee892ceff16b5adee16afc3806f8b6bd25739f23907521c06e90320bee22d3f754551f31555653323a9220070efa5242b183fb3365a7d8fdfbf8a95a3138cc71673b2432bb2b54ff07c12656c1bc10066a6a0120fd437e91ded602f30e13510dc192d657d4e6244189caeba892edda033d490732f8fd562c1f85dbe9310cd7df7b12cec8c21cdd4c9995de536055cabe1913b4d1d991e154b977a19512cdd7427b68c9d53bb59be4fca63a8b4d4fe9ea2714ca07cebbe733bf3550b1a9f62105ed08e7e1b329fb0a9c4cb7ff26a9470a29cbfb283543aef326aae261555569bc0efac348f9677f67df59455ac5755720b9d611d1b571db4b1086d59b8ca443fd1eda4197616bc8657e180ac710c3d8f21ba56cbecdbdfc4336755fc215f552fd1693e37cced71a8f242d3328456d0ad64f2ced4c33dcb08fe1dfb71fac591a99c151c8fc9017b175a21a04aea05ce380dcf14aab1ecf42e4487a57329b73f176c9b87ec17108fecd0480ede99343ca5caad7826f11d8903cf63b10a1b5c2b917ce04001eee1a7fe8aea78e1c32d0cee443b16e6d785111ea8813960469687e1eddc4d8b979be86295df22e6918045cf3e533e9f93a100ac582e3bab559e8bec866405f9d55369ed8768448232a148336d1253f6d3debffa81da1069eb1de59a8d955f8e00ea8168ea8658c16fd9ed04892e7af2f523cefff25de209e56e9c3b4c448e7e32afb5718778d306a9bcf61e6677aadfd604f15d433a567671d63f43019e94204a550f4b356273e5133f0aa468623bd8944236aab1c5e19c449aeee9c620bd057218a9c6cab1e1bf80d0e8a6ebe48a0eb35204d2e951bb9c752c71acc3259d5567c8f82babdeff387965e59caf39c86c53ef1f400480067edb79b70f388417f09ee989d306f35f8297908629f13459d2094d4c45f252fe6eb9212cb8d77960191d71191c03fdd7aad5d5aa7afa696e89c99d120179732aa8a3e6ac933abfae0606fa0e078f761c1f09f6b0cc64f0f8e167545bb22232e773fd268bf9695837f8ff3fc90e0741b4fa71a50cff68d9e460e851089ca1cf66640038782fd8c80738ec6dc1855f74d0cccabb4d84e5f2af83a98a44f2075759b5c23745d0e33b730cebb73ab9ed8c8c6e0bdeb4996982b13694d66d6bd341074e5b7e56a1466e559807ba23c1a2e2373b6540f3facc01aba690805205a8f11bfc74d4b2701688baabf495d5b87938f0baa45fbac3736f024880386b85009edba18285e07a8b46d7355d246a1bf846934a464a611e3a230512fff853999127e3ef347f88b0c563627502fc3de9b11a4a80e01e14fa6f1673c824eefc0b7d3f1aa1a8549365a63a95ce8bf166b8c8ad282a1d9a5b06f0132059517569a5a8a2f7b534f822c7315536b0a88a930a3137892a6a06c86d2522e013a4eac9832fa08ac24e462417e40a005208eb86b691d58f4c8bc82b1b75fc6d07b48f56854db1fe061ea5617dc1cdf3417179056abf928eef12563a60360d563c9daf969cb9737a525cd4d974592f16594a4375480c2eb41978b41e03fb48a88c0f2daecb9376ef900d4d47a7dbf7d1e3f7e917f889a3b2b4710e595d386b1e1a0680de56021a9487e3f9c613075d839febf48c5c3744ec44fe6552b11a6fd6fc485290145bd831a9497570557013b7067486df6ac1862cdca39fef15d500b7e28c2595923735bcf4e3ab95509bd45e52a4bc3b9ffc38d25d3e27854a3ca8e4e26a4866eed9c6e3604fba0db65a6c91029707e0067163fb52f72abf86deda90c7de18237fd17660ba92fc427806ed598d4a3359729750abf70645f8d3cdc5634d60eb338bc2281bd3710fbccab1247cd3eb7963c9c46820da15cf66e1fd5cefd9ff7f562f4a72aaa15abda7be8dfda8435662052e36ce1d1f4fef6c1111650267f682402ef3f9b97737c5af86db8c0c246ac0fc99c43995eb8cb5b4353079463af2a90b865c2041a111916ce0830b195f0a00874912f3b771f39631836bd29057b5ad4dc71ab517c396cad82e9d31d86a691e82afabab71340dfdab0b2677ae437ce823723902a3015b00edd6639b1d36f669c67934554ed04e7beacdb6372fcccea641ca77779ebebdd308ef4c764ab61f71b27caf467fdcadb2d36bee49f103c4df75571b4257c2cea459c15a2bd52f2c2bd7df629ae20c9ab853f25890be9dae831087d2161291cccc48d6bfd4143d19bad2922a20ac0316f29b277dac55017002327b050ba836131257ab0db76aad90d2a17183901b325c6cab9861d6b593aa447dd3ca07ad9e4658af4c51d782308b1f83e892b04c749ecfae959d68f12150cf309f90e8247eaa7234b68025fa40728422af00ce7d27ae78c646ddb0083eff09f72450b0cafac998427f618cd1715ff8c18ce3205cc47652c2c993568a40345f5eb72779b0004fd86eb2c1764525afe3d44d1e5ef3add97eb3ace00e9347afae4cd5f9511c33f807362e6f855098d3aa38bd3d26128dbd8df44bd80fd95ebc643f328a62e1a2f2421642a0c95a7eac4e85ebc41002fa5cfd2f7b40de7aac2c749dc7b65400625748c463add68c337ea116fe396e13ee1ab6ca65108bc8427dc1ee73983942f75538651245b4b6ec931413bb4c13a6bd514943fd65dbd27171183f6aa2127c8449369d30b6f87c8142f7be3fbcf1ead38ddf0c6157b0df3f852c1172663c8de5823db9b351429a6b176687428203e0315bfde28dec6596b15ed03be9c8b32fa4616dc8424a332aa680f9b53a1502df5ddb13bee822c83ae4fcc2f332e1d3a57fd18a5892b6d5b8639bb5f1813560d19240c3b4810faa1d8bcabeae02916d83b83474920a261d51bb09a8b871359647d173a61cecc81336c525edb5dc713e8d37046da3dc58687a93811c13bc4c0ebaedc5868ae89f85b59da4b02d751af3e7986556a75119e41753d10ba14a1116a127c62bb841a668642b763eaf92c03386436e1be91e3d10e6f83106a446e96a338e2ea698738bf6b8b34349e56c0dbd9d4994f4633ba2962f0cafa978140ad480dd24c8a521004c373f3797faeb90e1e9ca4b3e4b5c470a39ceff250c59248714e0d0d6e8381986fce4c510c3008725e6c1e6b04fa9dfcf572e5f1a120721472dc512c74d2ae082a4942fcb8dca9d6b3ee1764a0f3c4c212a8e30260f3d098575b04fae84a67f97e5fe89ea784a0279fe26f784848a2e4212b492bd6e24bd4cd5c14d0cc2a3616202fadbc1622e94bdd5d7c85f9b88c9a0e8ad273f0591c67ed80e626bb1e9135e137a0a49ecf77ff6cd310973c6500ff9bad5b5787b15e49179987fa88f077fd12e32e9c04cc217b587fbec7ec833d02a131e705868ae52eaa64d83aab95dd497c681b0d2c7e502d371e776cb27038fa7b094286daafbadb96429dea74f4d8e8f8e3df30e72dc6120509f172383d77d235c20373565d566be43eb966f8121fe23cfa3824e1ced9bb1091631be68f79f7a0670446479b462817c02eb3c32172d057557dc3172b12dd868099eebafcea2e6e738d213108a4faa0a7c30d39f5e9552e42477fc0704b9314b14cfdfdbfd6854d00c2d4b08cf33c52caea7c264433c68c41c6e80c51911c70e93f9c6d338f50d19965bbbb6e6a37167c0d70f2669d5d2feb413f7245f90ab387378d8bee0dc4a55fcb5760f235d0687b51b9e2a66cdb9a2b76520dadc3b58b101da6092bdefd940ede63791edcdd93bc71e82ba79c579e631e0760cba65f32417d977cd339660b4ad3698cba07bf385b0235fe192739f05167bc26f23b33cb891db00ab20dda7aae49d8eafb080829705033f324db6eeed74e327b98c736e8d91ee9c98ada6c686ac3e406771b287264a3d0e53095b835c723a4dade439d8e51682676ff8860b94499e1407dc1d523e8640b7751b2a0fd376cc50730cdb65551bd5f64dec28f7179538337346787c68e0ee50a240d563758714b06ec483e2486885d943d65ed65292434b69ad965271caf1f97d62380345602075bc3a41419cfebd0d88a681c4342b9b7753a3634fc19d561ec449598621227c032591e40ca818edcc5dfb3aa86450c4416c3c92a20f48bfd1421a9b208f56ca8cf8e58278c6077a3f10ba37413eb63783aaa089b217e755692f1b0d1e6d7bc4455153b60d3e8bb420968181183967b4b942fe0db86b1de6a7c1ac63dc9c4227a5ce1001950b6bd1c65cc8ad1e67e3d664a06bdbcf820e3b473cb09567a9d6f93bd24be0880165ecfbe206fcc5fa3e2affa5b682337a7c16c377e9c65500a7119f621220c68d46d55e57975a25b5ed45184d7c6e1cd95432db77667cf31cc57f6f2600494c1fe51be80184222db904ed2861a59e36fb1ace1ae02c068250ef9f4e51c2c235fb2f94b3b00f7f1802f962578723dcede3ef035980bccb6d38133c7936b2e3eb49ce18c9f2c0965ea153260e59136ab627c154b84c9403d45cc58e882e1cddc92add480a26ce86b8372848c3ac8aaa2e1c320220b52a8eb08eae20ae68aac994e2a88824a5eb791afe1447d443447c15852a16dac0f102f6ec6cc8832924983f4070cd22551e7ebee86646707b7e3b1d7fb08b816a7f77a68d9e28ff43d3264efe0497057676198446de33f15baecce729ccf3cbc77415a5d64af8d3a0dfac6d0c042ec1c811d7ec4e973049dc6a0eb9e0b04c91ad08fc7e0f4c08df76e33986c8da0c7a97eabb891fd996ff3f02e104b5dbdf9b7831c0326b8c48956b200d8b95ea7fb30d3f345a0447c39a3a5db56a664420fbd6e4896346f4888653f7d72e813cf8174b385feb65679c62f0f95d0fd3f88c4601165cc71f47d43f3b801a4ead5876e08903b2f523ac97733e25e5f5e74b757406165397a0045ee3b3eccf8513ad704a08c732c921aa775c7e36c469d20e97947026abd1778493790367dae22b9d70e04cc6ccb76b5faca3e5d6844e0bc50b7c2c910c53cc872cc5c010874379875a83cf0abfc92e2c4a57b644fa0ab02ee43c7e8cc328c8ef1cdc04bba8a29c0ead76d08963d5d089c0dc31aa4968d634f6dc803b7f7ca42fe10656ecbfef6f7f98faf7024a6d5f5049d3d16a97e23f366243806c58119d2b20188d0a9a209512ecde8fb4d6a1cc3b3c12ea4980a19eb3ccb7b53ad52209856cb5ccf01b73e420ec7e213f24e47bd9198f4509f9548ed496250749f49dad347e4043f6dce0b9735826d4a554fc043be7dd4d722b61aa64c7ecb3fca0eb59da5ebec9664b5c3dc4755d2647e4d41aaa9aebe9753804e38b5028096f964fed9a5c86c68c642a3b04a10df45d79cdc1a4641170e84b07d7067b07efd713c11c3c08bf401cb185ac46b4d4f50d69c5426f180f5ea342d7cc38e214d112c21a5b2affcaab8970480308cd3289c468b83a4f5b8ed822e2c2bdadb072aa5e8e3c1f399ca90d1de3f4cd9a00d474ac42e05295c43e51cf4320db89f78a8a59caef7f688d9a20f42d20065d9a19d3db3c9555da4680ae3562a7e2b612b7d7df47d5e447ba40e166d365a9a4b61cadbf99f16c3b75793b43751df1fd5547fe2fe44ee3824e85a54f30d0fc0f0fbaabd656a87a197625e1724737c18017109960a68f042148cf554615361fb46909140dd9ba5e0165279211b909a8d0eae444f9a099751d0cd60cdfe4b604c26a05850d247587165af630eb2688a9bb0e86a23aa7370408c4fe739bae2f1a63ac329eb81d076a8ea3f57c124d6355b44f35a2875cf390cf4f62a8bd1ddc14b825e4bb18a4fa236a949cd991efbd6651885f9b0caccf96c8e504be8cd53c722ab890870816744bd8ba37b25b59503a92ea64c13b296168c9d25de7104601d726e6559e9eb466d1777e1f79945b63eabf6ea511694d22d915b4313b9fdcb34ad9c2d9d5d450fcccbf82d1431bd991f9dfc05cd95d4fd877473e8b5614859ece1d79584ee30ded1e5676c76802679b69fd6cf9c9413feeeec8fff29c5b3dda9972977bd7c096cfc83ea76475790f04a3de4657ae229b7c225facc13ff83a6599aac01e25a8697d487febba0df9bff4339440462996045cff0245aa5c7d69b0dd04af41d0e238f92be64bfb4624970907077d2029330b864ea99315d9811858d58ab58a404bbf5485161a1612bc47e6a0676e451fbd209cfd024146d456f39139b50cb5f7df726be05da8b039bb246307542316fdea80fd80ceea02d0657ce2028021626f9abe05757bc020cd009a7a764ae70e82a917ad45cd24a193cb048c8d14be9445b63b7dd5f2fc084f309d1bd9b19d093e7b09b5cc927894594ab1a683e0d2ff510e7c414e30036c95d308c5a04f88036606ade109e013e48019612b30097e425c282368b5db048b031f201798095b0193f00be24099410b7002fe0638206686e358edf16bec1afd8d4ee3b7f16df4373a8dddc66ee3dfd869f48dbe46af1adb0dfa763665178128689b75b0dde187220af485a14dd085f09939e78cc17a9c3c7e66ce21635a4e93d39722d798395b0f13e3e7c89d330d967372fa9cb863c6d1324f18be47ee9861b60c13e677e2983246cb71d2fc19b8c6ccd162e6e4287d2e73206724d8f2a17f52f89a38c6ccd1629e18fee1e0486e091b4853770f65f1f97be0ce8c9375aa895121d573ef05cd9e76df3799ba14a82dfc5053b97d38009eacdad67af1c8c53e1d92883a5ffad380b420cbf229f56115f427e85bc3d2c87f7d1f293b507bbccd5667587ee23b807c1d60933f0325954c18f2ef11e725acc54a522370da8206f7435630a88d778e2b60aab653263f866d7211eb82031e018b8231098cdc8962bf5d82a539c7ddaf7494f2972a24d1c04eb3012101a0f3043e2959d42764987ee2f5d1e11afa4ed1dece78d48118c83b8991810f822692371e713fe66b6b2912e3e04a4fadeb90544590d3e7d18581f773e1e56f61641acc0573de12d6af6a0d78a59f4037767d25e33606496cdad5a83d215993fc27270dd6d22ba7927499afe0f3aa77af935be6fa7680965757c7cbc57c42cee0ae6c7833cc01d6b72eaaae5180aa3e852b02c973a85c017094b6d0f05bea797a0247fefa4cc50a5dce370471b0d9f6d465b6d002af78089e6aaba1dd5c56279b79c0357350f8a2eee06d2084140c4b1d805eb670f6d3bc43f1e54d8b391898aae102146e2d10e5e1cea833c895fff409fab2b184d9802e5cee2483544d2aa06975b483180a0f4fcb2928ee5ab0c1c775182f8ba15a882bed8c18f54483fac31f9bfd65d67c7d3c56f49194a88e742f9a92bb641c829a6608558975106ddbb6dda87a00ac68a70d12e5e061d2ee0674031d836b29db95eaf35676312dae63eab2f5d48c9ab1840e06969e395d80ab766927e3475ee314249585a80749d980c51c2e0f1d74d63b9e6835859f47ede59bb2c80814173d427d94d84741945cbb4f7d1a2a3bfd01fa4b512d40e1e36dd8c0c05554378ca76f78865f39458902a6f3071bb5fdbe7cd5f86e2511f5b77b2efe85eff72f5f65ac020091516dd31002f052b539222fa2f84127c6ad675d7b505b1621173952d648942805f969d7e4a3f8bd9cdf338b5f42f5f39a80e50345f3dc5504707b78ba7328379374be3c4344101f7b24e78ca78f6002977546776606c0e46db31b84757f95e10959184830e28ce5ab43250401e3b022f7340dda7db83bbd4d4e81456f6f82d1240da1cb35d077cc185d141b1c7ceb204581407cc6a52c813b618a04c7f806caa8df3d09b04a1d1ba57154867fe95318446e12aafb1b0d77cdc0236af234f7724a65454ab80c144f3775c92cc15006975936124b1c7cd40cd6c4ee3013195452dfa1341cb50228cfae44bd87812e15a0e514f5ca09eaef397dbcb84a35220d686f59b634be8810cc306c03170223c95054b99b8f33c1b14b20233cb3df430a868cb390c1ec34d3644fbd6bdfaa555697d7cb5cb24726dee2f5ada3c4da530ee15a693ed31aea1bf3a5e937adcfb1d68ee301ef9e76773c0674bad3d45a00adafc360214523581fecdf8464a40481cb22d541abd2e40b8ef0c6b2c64f2bd60ac3953522a148321ec7ff786a5cbdda9b39a791e09dce1fdcefbc08bd787c5aaaa79ca9602cd6e0f78b959163bef69198ac99e8cd7f9a8db0ac46df5df67960829408e2fd72ab370d786214cd223374d57cddaa898df01f7dcab64767004b9bc907a697cf090f10447c46b41506910d18ba78b4920dc3924dceff96b412bf23688150348a0d51cffd1d413b1aca1f142394e3afcb46b02a8682fdc88b15ac753d5b6f2163a9e5b1c492861478723700fab7aef7f9c3c7859627491a888feef4943067df6c42040a9ba8c93acdbde4bd03921b6278d39fa69e13dad6ecba0b7464c6b70ec34b93451b05f4c4600098e8b600b4681109a2c7340ebed12e62b301a936349fb7dbf7b0e4e7cd1ba284a71797a4dec830c731b37cb65adfc25e8fe8b27221b9debbc7084b026a1d9f53b0cdfa5fbc066d5d2d97311b27c8ae715085a4cd42080cee17c742163be37a9c00495978f9124df0075a2d8d77b3b575c96568a64a454c20c733bdaa3108d771c809e8d4a577d566f6e071f2e68ce086978f01af5eb88f80c990bd1154f98c607d6944bf6ca5984a8f4298491569d55942a62734836f07b17303723ceb98880cd87f3e0a82094cab9e73f7973ce217cff33510839860ea318856af9d58f16ad101c2a22d722e456f77c5b5dd27c5e22230a384496832e095297502587130b11645065f821443d336a74f310b4add2b779c18afe515f513abb14f1aef373d4f20532f612bce97183c56da0fddb77ece6313092d8fa1fc45bed5fdef6f0dd3b68b82024aaf760625dcff38a9a49c8c74d0cf8e1e574ec4e614a12a01905863b599398275b16f12e3d997c5049550196a893bb4a2ec5f5b6eff2bdea83b986d1e7a94145cc256979b5f956bcd9767498fa281c368248c8ec512a762d76aaef4ce388c4527d7c7b7fef2f5ca4de219be1f66189ec17409a0818efec1e3b1dd77c15a05bc63d0031041439487a46bd48e9d05a352734f3ba923d262d0af8a69ba2b4c1cc58055932742516311aba55c8b8196837e8622e1fabcf89002496e234019ed7deb3440a910209f8917e2ed152aaa86ed67618de87795fbfa3cc32e15aef20c1c46cd599cff0b030dabd2daef1df37ad186a75e2ce2addce2ae876876c3c0b823b30515f434243ce88baa3b9b881a669ab4a75630a774227a03a866ecd04f178be1122d2e99b5e00d5e0d343620347ad070e341209ea49d52914ce47111c809b802aacb43b88bfbb9a568211df44629f642ab2401623ba348d3ca60ed8e619005e1b9290b08e04b2ac7213aa7440a30e903847d6eecd39f7580eda3ee9dce0ec220c05fb2c51dd34725acc8ce054226c6426e942f7f53f24efc8b1a4605fb4e6b86cb4d6adcaa8bf53a68512b1814773b77557d8f6f32caa4370198910a00f99e9382967342e214d981edbdd03d83b91513fdf576152a2e7e70500c63f1ee95962dfded85fa3047526fd03398c5abcf3a3e1f02dcee7dd60d1d2d0884dd6631de69037414374dc5667e1180790e46449e10b637610e78e4600cfcfd6c7e1207300b0e1dd01c7a1923269269c866f8b6f075f3d5ac56904516f9ee9c5937a2002e66a0ff7c9c4c18bc9950c9a489974581e781d2779628435592d6f4b6b998259e910b67df459514a19f9445601155232449e94d723a6f9b92ab9d6727430c99c6282bc79944a43f2aa476efabe79332e9baff489d26676a4ef05f07964008361b60cfb10114efd31a910886d93d72ec3605762f722b55f26eb894e91db206d060ee4ea8e9238b974c465cea1c14a6fb06543629ee1165a34ce39bb1ebb617e96d80219105c3d35e3ddd246e2357a1c893e4f660cea2398632d567c28883d2e87d99baba04af7bf04542c24b2058ce767cc05232b48ab9d61eece5868b05533401dc2ec8bd7682de66ab711e89848bad04f56732803b2c3fd76080f299e7224e05b0a9fad8fc250c608b29be452ed8d8b6306556acf9f389a1270836a04413609a1d1ab6e1dd5d9d235057235390fad6faf8e21fa6fa9fabaff3fe86ae8e16ede74fbbaa32f73ad664d9c495ac98eea0e279ae266b9be1be102fd47c6f91570a91938ee7d3b926e76de3f3d7ac2ace80d4cb2d12a735769c2113368f28b44ffce9649faedaa6b98b7469a2fd3b5664608d66d6154ce85e4330bbcf3abf9d513c40d3d9da9801482019abb7fcef4e749ca616b0978a982d3e9c866bc8dd77f93f510422bbf4a5d47502761436d0d6e951822e396c3ea2f8e02b8c2b14d00bbae9f9cfb97c9d4492166458be0e3ca2e31a663b048bafc4cab50015039f67ffc3ebab48fd1ad68fc7106b4f36dd6f69e6b75264fc11c5b0c791de3ce77f9ba2b7239feaf4a87f7afc0bcced94842ff29d82192cf7daf49dcb93c2028b56ca886177fd293591807e18384558ddb900316b04ec3d623754250071fb8b6af2a4d87d18fedfdffb807cd408cc80f380030762e57a05d375b465a73f5fc798c68f54b4e809a4cc519618f7af1a468e126ba73d1a07906addcbfac2296f0d03de079189249d13c7377b9968a38ee2e0a2f6413a0c8df57327b2ba920f7595cafdb8fa79b58ec9a0ab4f5604d95ad1f697ff02e33c5f76e615fe588546fe0645ba6d1af5b6ca803184912f778b0db503c8e9b44ab8f98089b7294ef0ac01a60a4f1187a34fe4262909f561fde3d3ed1e6a5283c15b9964b198444d106e50998092d80a547d492e12f41a0c023fdc80ee111c946dc629081a3cda10a3b1e85c424ad107a4f6dcb99cd9811e6b225721990f41add00ea8b35b85387e09132f7e05e3a0ac49c9362a2a56b63d10f7c511b4c20250e9cde6d876114608e5d05eef5fef43caa203487dc81f6b805f97c80a0569e57b159e399600614a1ecb84c013930e49a7f4a9294f5c0b51e682cdb43676ce46f299b344ff2a8ec0b8dea68df35a3b10d5a5498a561a9f73f8679c7ad9fbf213321144d11ce971fdb9001581a8274a7003bb88259a7079903779b33dfc77dd5e2c4e247448261498158c4a6e2cb858a29cffdf2449b5f66584447d9a0381680f490accacac57bcf6c0ea0815357ef47bf7427a59d0de069675a8c7772d4ca9b6f6489b1efec5849c33ab7fc125cb50656e7ebecf740ba9ce542581c42a27bb853517d283e0160e445096a44577905f4b75056f4f86ee24e71e7a26fcd67309053f7a27cfd25d5e25cb7e152b2593e52db08390b96e867ab683cb6bdd64de1fe0f3d226486d5260e21c98a3c1fdecc460aa4589c9c7e2fe5c773c307d0a126c0ac11a4eb59e36214f7d9cf588b5da94fb963e3ac5e1690bfb9e794fa207e2a37bb1c92da3e0c611e02c73c9800c8abb6e3e96e9914e449d4ef6a1f531cb9c04b8bb0254dc6b99b33726e85d243dbe6afe96bddbb7364db8f90ddc2c55c56b7a8787cec8f99c2fb3016637cce6cd02d4c8edc45dab90e92470757516ab626ba967a81fd753c543e85630b70d7af61653abef4c880877201e7686d60c24773bc15c9a8fd5b4a2539bf3570ed1794069d31a044eaf39408a99bed0502aa6de4356809527399936eb60edc68b1be4921f5bbbd45c5ed17540886310de573e9024bcd290724386bf95602391dbf5c2a3b3c232d9d38a67422c80dd2a46f04b2baacd4b89c52986a90a5bb3a5e41740331f57e28f4acf81b60769c1e4302319c427dd1856f10704a8457ea09a50d7dad41ea1b7cbfbd428f3c7c2baffe07804800bcf8aa33785bcc42c8c910bb16217f466c237eeb8c9650d606240dd641329c2c4a641807a5cf4af5c070b261e4e3b3db17af687e2f946db01cef665837df1cb5ae982815b0e072ccc5eb6ea4820a553460be7344177a439c41d4eff30d84d8c69b360cf1dcc3c571b4b37b685ad6af7e7f30a7cdd4bedbac884bbd3b2d6eb4dc4d68779abbd387eba064596bba0c1765171b336d0e6c69a4be90223d0bb6f287b517b76b7e1ab65690b6e9cb6c2a71722ee3be095a78b3d4187e205c97e13b2f9212549ad7ff06a72ff5d0c7755feefe737097489ec6006b38a3414c8a8993f9f10bf5a5a0e9904edfc24584a06992e6de2c6738370cce0ee463df57aa4b77a59aff5ac9ed54fe137e5c6bd996ee8e6546481d5baf16fce1b74e3bdb6f7ee656f0aba21de6b1c017d88b85367f14f656a5b0bffda996dd180b98fd1d16854290a47847a04939bc1f9f6081828da7351104faf120eb2338274de14f873d7a7a969857b31957f1e323c82184b50b6abafc0b446889e2712db8579f42dbc752eec51e7a1bec5de6a4b7a27f678dceb3128d7b4a897e49e75b84352cf4e3d9374aba3be893def7c9f20fddde25ec7a05edd92f6b385b3eee562c585c2df2cdf2f41fed0d2abe7c2e2fab2e297e15907cbc50aa3f4e3d27d89c21f593eae1bf0acf657c5ab7a710dc14a736771e5cdaf90c060a5ce37dc5c518c66dec5952957538a577ba599cfd2cc4bb43206654af16aacd442b19a798b2bb9a8a614efed95373ea3999768259a942dc5bbb1727b60cd0e45375756db89b7f24a54fcbff2e8e8028d9f433f905edb8b0363fd5fffb90dddfe47466656dcf036f171a95ec15a377913e0e325eb8a5517b4dfeb54330b3c9e039aa3aa855b3052aa9a74ea14ae59b46ba470d6de23dbb6e9045638eb56919daa4b16edde55cadd2669b5a2c429a0621a8c1a841fc2100400529608e28a8c5c5e1ea20781e27fafd7eef55e12c6bc4fe8b1caae37b3b6d612a2d636217b6fb977e508e4071a08d3d5c93063a6f70428304134ade323c3ab90c4aa726154ac90fd26ee31c4ca02051e68c07c56a746a353a650a1d2f05a5792fd581347f922b613726c3134193e653493647590c28e0c9f2309f6abadec35b9d845c49f179b712d57aab7c34b21c37755ccc8d0c464f8ed03351226c3a334f003a10e0a199e7b7951c67824938ee24ffc9155bef803039213421d9ce10f6764bf0afbf1726e86af49d8afaa60049d3c8112447342863745f9a123c3bbd6b0e172d4ab65b75981ab11486402f6e8d33bd12cdbddc0cad938f1c795e1332e70b82a71e28fd59125fe1870f3848f8ee943391ec77aa932f48c92c68606f6822a00e00a226fb644596baae596ba6a3031352bc4d4acd082878367430cac5981082d980183e3f52b9b34934930482f47469c0d334a22986143110e36dc60030c830866942237ff2cacd79ea4b36389b2560abb59aa5a666a1f1d4c6c73de4c2980f6726d75777777b79431a3b3bb71f79d73c6ee8ec51e514a306c7c3f4208514db8bdadd37bbc72856d430c55518ecfa109413e44987ca802473ccaf111c9a7a5813a56ee5e72779883dcbdeb7e1022773f80e1451047b0c1858f4f8cdb5879dba8e46dbb0d236f3ea4206f3e4d70d00417b090b5c336d0c828ed912965824fa64cb041a66d84e00a0ebc9a1fb478811442190abde4243cc9f2564a964b4862038d2ce5490a1940b104a38d04d5b26125890c18174c36583093e784a922724ae8000e9450865d2204f2473a401cea92e59328813ab03f293e225ff211952f0db046fe88c421007cc93560f2d540470d24a5b5ad96ecb70e2c0cac819747f2104e2c7b0b14d8efc740a687e99b4803b38940e4696d8f1e666eb6daa3ad4ce7040206eac82f85dcba17038170b1c7bc95f1e6c09a590f03759460a58c929bff4e799e7042ab65ed5773e2ec0e869929868135b1f4ec26f172b64d579c2c5cd77ca90d3b7f8900b9d820cc566126550b2c0e7d13719ba065467b1ece0c4f3ce95c99613fd86bb5b6d592b03c3529365894bf24b0cab550a7b521ad8a7603ad8a5625571f6d48aba255f1628311484720df92675865fed9dbfb0f8349b0b6d24674a8854025a5437428d3a436a24374482bc106fb86cea6d995f436195ac928d7734a49a59452ca7e46339a6536349b369d4d2af31349da858c8c12d205ec66dfd992ba9c4497a3f6baeb4f292b97cd3eec322a3749adc8ee6e39a7fcbc72ba9e645795bcd3f2ba1c7541a6eb8926a59cb3bd98156d104688124883d50f5e6dfd633769ab924e94ecd47c581626d8278cd504ec8985524694230e803a992764e8c29ae8cd63aa0242424242a236dc0013e20398902a3f5202a56f909090909022ac616541ac0c3c071c5720f08aa214a1a13caf0cb1c168a9a252b56aba9d97e23830fd8a515830b231a2318ab245571e08a2427a91e50d07884068769a8e3d22cd545f1401348f4304d234d3b2c841ab2a14d4920cbc52d5aa66da81fda051a402eacca01ca240b1816ed92dbb65b76c198dae1a72c2e0c0e0ab6f61366258179113904498bf885d432ebd7bde943833d6dd46466b7777d3ae5bb7d464679dcdaa6514d367df289d31993541d5ea49fcd25efc8095313c0476ce395bad5ae984dddcc056fbe6dd314653de24d7a5bc4d4e526ec21a9ae54de3b2ca69336f25aecabc99b8d2c699386eeb60e84f53a96ea7086be067d10661a6076b20aedac4301bcdfe699aa6497a654728259bb3c8890decc0892644232072e287a8893432972191134300b52a154750031809694058102106900d8608030968074700418a15238080046486116140e9170c324018525a5fb4b42108815404243c1a08f10321bc60d2da322412628bb90358cb22592003322db0801416f0895181dd32240a228d9717b6cb902888a3208c0e932bb89665190f8ca094d23190fa05c748aa3efd823ffd82325158ae06444dc0c0082b55e4cb5b020bae341124234413506cab89272e2840c05a9adc618433a0384a810704bbca70e88c19c83439a307af32ce08e3c76e190e9d518433d238425249ea17f4f9017232001b3ca12f1f9200449524606087164970923d20609905329a65b406233489126589295714418a200769e4e00a1499cd3ab0840c5c800552e4600b1870f144e3828b2856605944404de45386444045e4cf7e8600a21184d08f1292f82162090330f1092288688827800c1044510928853434308411c4f8c072190ea521254a1a53764c61b30c87d2c8c11a6914e5c0b41ae3bd980426d7971a6918e1215c202221a55f5068a84a919101c038a23979280d339a20c1450f9080381a52034a9e191201c144fe9e63d6c46b85268d6a9a0788d08630541a011199107caa0a8aa05682b42b8698568a321a1021855045be604dd2c18f5012183f0308824f52108418c0109eb4981cb1321c4ae3072220903a0ed4564ece6896d11d32b02c19121921450b234600c508275e51ac29432223909899c2d60c898c18228527b6942191113e438a48aa7d038980e0402bc321352c906986444c9068b083ec8b21542a08c328096900d10408d90b8260d37082524a29f5a10645449c0f42d0807641240411911055e45586444240c9c0061be76bd958637422e67863582d43a21f1f3c614b39466984221aa29f2d887e84b2cd90e82708099d26c020065460618231b6b002762101318020c6154a88220650aae813642ea0598d28d410418449b135c321353a60a5e529cc73d639e9a42d3bc3f0cacf4b7b764398bb7be291a452ca86f204310a6a6db568b4c9df12aa840f980fa9048da22961e3db4a53ebc369b610c6186b8510c28cd65a82b07ea74bf7eb2157ac69a734f6a8a7771a654a9f4d082b841aaeb7f488187494db89510c3a3a12231e5589c10e9c32d4ca964a14231e9dec91ccc596b5167f47e8e930ebd262eded6157e4845b495aadc3c0e9dd5bdd3bddfe74b256d5a24552743b9db6abeec7ad4edf4eaadbedf6ba52e75028570a0f3992ed37fc1dc991db56aa956d657bc2495a79d5b276b3276b397bb2f69e77ef301175d90531ad5e254e8bea36a53aac3dfad445fd645d4e27a96ab5be242d7b249fbe42a14ef4747a94cac4fae9aeceba4356ad2399a6ae32e1242d4a8f504a69abd59242c3913c7f4f4669e960043a20410ebe324116ade4562283f84bc2e5c943f6194f5e6997b48361e2af475e6d6e938bf2ae3e6f5744c5624d6b97b4ecbcfac7d2e4ed3798b5c7169c211e8b1aebdca7655d6a3bcad5e567afaaf638777ade263a224a86441e18ca5ff421f2405046d939ef1019a1c6d98b8a1135c48b11e3af872c53af29db4b7872fd112526539b5a08cc8d75e0c94b622e9d967209efe4d4c07ef028d325ddf23e7d9276a1e5f44794f0b0b9b0d8b05fcd137a347449cb9123fb459fe8d2e28297b476a54a9c167c8408fb4558e4431bdb108158ad830748d954aaa1d6fa392b2cbd5edbd0b0255c85fd603089e3daaee5489caae10a68e7b473d8b51d265fa68a2319b0c6749369337da7ab15ebe1b767fae5c242261cd668d8e9d4116ce9dbe72b761160bedeb56123f334ccbb4c97788a02c0189379790ee7c897e93058637a16b604435ac24876d305c2f4cd74984dcf3293e9f14a1384d2643242c34ddba0098394601f83ba54a511cf8305b5621739d6b05f0705b9b8b05f0cb22252d12e98b029c3a69214b2aec0d22089bf28f412d4d2500029cd32186986a70eb209547b2624871cd8c6a15a39270b8311c2881f10a3917cc9d2e3c53da55f42edc94fb902cba47fe44b66b87ddab30200f892bf425a6e47ca1848338c0e2437f87a81a82fc5580f733d84b53edeac529afd19968f6cd8cf47218993235ff252ca8db2b0618d5a48ee0c657767a8a83d9823cb1af68b463627e7cb89460be897bcec97b4f25808b6d7b0972b38326219adc07eb625676715daa8264cc861613f2e77e9f51c9c0a308a9beab36f56577d536587dc94df917d6b3abdda7b755557a6306c0f6733645f222f12ed11ef40b3499b37562b1c93a53a0baa57f2f3f26ed9252b6a3ad4bcc34b86404fd62e85c0ace1782a6fbab2934175d3a5c49bfdbc29752d85e54d513b2755df698be52546a2ba09236149bc446615ebf5dd11c95818ce4b16d4ea73b58a744523356997a7ab29f134e16fd219e349c618a3f693fcbc277b59f1d7d3b7f6a68baacd5a5d1586556ada94f3b0a39485e55feaf5a6b7ee57b36bda670c167f3d64fb1bf47e4a3255f19055acc3c669b1f0973afdeaadab24abf08704667b61961d0c31b3e01d2646328964d7aeb14e57afa7f67e3c6493bce9daab6eaa3e753b6b37c99bba260cafd99fbe25d2528c7a4569d9e3915ccfdd8f875c67d681874c7db2e9d9e9ac37ddd2dd81568c645ec21bbd7a02deb8c2de6897da391b141081c01ca3ea281c2916d1135572e9b36bd7ab7a765195e5d46ae138b1a42a4b6618c95ccd194a574d24594eaedf2267fa2667e3c0588643eb829c2e0fbb19703cdedc48bd85e5b55e53ffc63c0ed527fe9278a8cf241eea15856754c9d093f56c62d7eaf328d5b3ba425d7595eaa2eab74e6567611b346c868ba48ec2486ebc9378291c6f1029ddc02d38585866df49eb1b3559e6e3a90b72fa9ca8393f3360513f1d09d667f774d6955d90d33728a5f4b2d56a7559575d5befa26ae16869c1d1ab54b7565917ee7476d865a5a350cf6cc8cdc27acbadc471b93cb1d81676d9e17ea96f6f8c2423523a0a6543669d85bf245e5f55cd70afaeaa73bd5f0a73a89f90d4a3bad6faac7ad945827ae7c6daed4e579851f7f452a705d990b598a9e6d26737a41a3520a7744bae971cf339ee4b75f984e4093d221460828a10a0e4d96f400e8e6ef96122216501e3d1e4c05e2cab8f40caf4ce6d6b1df6e7e4e453a491461a69a43021763ba158261c2d2d2d1c374c2cac154a754ae570ddd01423aba5595690f59de6b8b3dc387545b673f61c8d34d24823a5f6f4598e7e3b6d1cc77d5ea69c7d8a3e753f2179bbbd9f907cb2dc763f7a14b641c352ccb19c4b92f3519c2427af72b8961ba7e75a386e64f6593764ce701a2267e04ef614638ca79310976ef58d863de72454876c9556d1234eddbea45870b4b80ea5cb057fb505e3b8815930cbf330ea2f3939a7d3e92424c7c60606a3a1f13c1898974c5329fcb5562f2f17b645a55eae4afd2585ba769b3a9d8464d555f84b92934f9fa7f77e51084d5d6b734e54488ea739a79c534ea6393d14010911410e0c511221c892e505dd7769d71064e8eeb6e96e9a39a5940d9bedcdf92d838778d2b477f9c93cbbbba59415ce43082184b0b1b0f15edf05c3cc9d638edddd52ca150864a073ce49e3e1acad496e42fc13b249a79c3383c910692279341952da40e69473ce09e19c734ee9cd39672c452a71b43e73ce6932ad86a8539bb46693d60c0b4d8cacd6ac0d3a6bd6c69c342b514a33adce4cab6d64339b5ae50e4b39d59229a514cf9969b50d9ad579941dd92c7bbc966571ceda4696c11e95873933b9711c47b336b6d66446a306bd5a67d775259ab55192198d1995117758946a7d9c8f5aa4996cc3e462b29964367358b0df4c6c3dda68464a6fd9c41efdee121482d58ce4c4d2cc5a3768d846ce49b312a534d36a96d1c09bec309842f7ade3b49c1cac65da2cc5eea62cd8a314a7a6695d106d562a618fd852cbb21964ce88c879ad0b5232c98de3b8ada78cad96a6698f734ec945d3a4997c74c969c51efdd3a90b92fd3425ee6460e3b78823ac291d962e21ac29cd5caa9ac45940df007bf47510d0b2a1c8b4997c5c6c6cd94b86bff0a646afff132b371b6736246a398116db0c7e3a289c0c32ece0940c5b9e6d6db0f863b9521619ce3bb3ec5ad66d935ca9656545412c323c4b140875e0155d90599a32058532540520c3774d562e4c6929b2df64e9b490210bbc01c4494b0cfb554d27c7aaed08800c9fc30cfbd5b354605d707060c07593e1cb0a554c3fc86153e558c0bef999f4c7cca6f853653287f98e0bbb212fece1af872cf3ff5b22730a7fcc85d94b017f3d5906c73ce6dec3dc2f49fe51b81f0ff99f401267c624fe4e6f18dfcb7b7859f2c539173604fb4da01374c49e03c809bdaec7ae4b629a14a357fad52f2161a7933b86cdfe4da01a76dec265574300ee32e2c2f333e6891828238a50cc85f7c476e4cb85b9850b63aedb933f28d4ba9f975db838e251cb40c3e2a359dd8fa5fab4271348ca513cfab8fcc5a389c50368fe68527f60ca03a894dffa7ad2480b6d5c402816007c172e8cb57061acb5b0df4b3e19a2d003d703a45f58e2b90f757ae4fcfd55582925cc95dc3832347d03899c90c102bcf92a111368e43e0b5e041998729321511257f2869af09008102726c199675c02c4c185fd9a8ceda6331d8d3c3d2aed75dffee263589c21911256e4cdf428c4952ec5e50a4bcf4deed18af6e6e9277b6dd0b0d9b9974ea58e08fd40760de17014eac9aaabcd60061f108aad2e8cd13c69c8d917d608eaf4c82854de2e045ff35ba954aa37a7944da1de57ec65b5e74a61183aa7acf4913f4aacf0a489932c63f3a97b031068dc1b7ca4dc1b7cac189aae2954faa6bbe89b2c0bdbbdd59948891f64d8d1c0e43a3460fbaa734176dee83edf4e248eca9a7092770343c982f1178b2e5ff47da13cb5d0679b08590e91e74e9f2ebbec27eec6a1f66646e9bbcacc0a2091124ff2cc3f4646454748502091123e793e5e699c199b2c325857a816167633c8dc425c5724fa904df1ca7663924b8cafbf7081f5fa2f096c521c457fd13730cf2efa665af1a34a1256a41c8a55da9b309b88058c859cf626dea13312fbeef371a86fece72395be41f53773f7ee1bdc9a333d93c25dee54a8bd69cf5dfb93b53826a16ebcd2836c833a3023319dfbec21cfdce1ac339dbe9ba1c3a58b8496cec52f2ea1444edd3324f45c9472b3675be4b8c89db647a510d93e01a3bc3d0bb97604b0c66422c385c0a13cdfa44a864419a0424786444d8272ed23230b54f90132f1404a224a1bcdd9423e453f5434d013560bf58d8b0623edcd33c9f3f0eb22f2bc227f2ff98b4938cc17cd204b398b57fac5a47f7a3b71d95dd4549a79bead50c9f33d7718f9f8f8f8008990b022e94a9e9ffcc9f3891a59e44f2a416f2a6531a96fbe5894e75d70664279beae31b90d76a54b848abec97a32ddaced8264575dd9d1771d91e893296e9f7825265161a54fdf449f3c332b4d46dfc8f94eea9b2c0679beaf40ae0b925123f8ea8250a23272868d1660cac1a8e298c48124141fa9c2da2e86ce3ac4256425b0665e754b5ff0f0d0de844935f47970a804c10ba1580f2a2d84388a4d91447b16a970451f1f2aa21441e30fa7ba5b1a484231d8d1eff4eac258a4a26f287629311b294f3aa393f6e6a3933cfb531e8711c2f089b5d1eb0914d4c9269d51e66b03e3d1c06ce8945067c6676159edc5576d6641676c1b9d53ceee081f4307598cd0dded75c37477b794db0fdb766d64f2e7f23e578d5b6c9b9492b694b3bb2184d0e64e09d31ebc9452ce9b29e74d7777378b1933c6d8ddd9c60454d671db35a5a8a9b4954cdbc695b614544d2ab1a026163060177a3108c8b22cbb1a8dc0d4b030418ffef480d2cd4c3b64f938e98dd09b593725b69c34d36ac984ca3a6ecbc1d40d39caa6a8a9b4954cdbc699784add90996ab5d412947153c1958eb449b5504a0f4367162fa66cd8700ef98a93cd9291c5524bd544e965e9565abb212518fb5431a52c5a76ba32bdeede012b7f704a6b0651b4a1812a489841132bb17eeddb1f7253a2223090e5a7a4d99338ad28899092270df294b80cfbb5e2df3712a7c2e25b0bdb928d484404144ae1b4b48b4c7f8fa8064e64d99c19064174812c88886022cf4b0ba02053fa6eb528a5525079decce844885ad9204c865289f8eef21b0a36b0dde3eb86c21636c3b300e35ea49cf234f9949d7e3a9d8ef4cd119a4ef58f85b3faaabbb0bdee50da0c7f47724881adffea4dff2250ed2b9111488c3f59266529e31131b4296fb0a87d72761c72b0a1872f720f4639fb7a28ca59b7d3ed451fedddd50195d39d7d3be12fbba6e57acd27d767328ad4eb5d40e3a4b425fd2acd9c83c499b1aaa484848d4eee18361e0730723f8d1f2107e1d45bd256354dabb5d67af85a6bad75d62adf46d0d7d80a81e54037c9357badf3d5f45a7acd6a56b39a55ad6ab5e5663a91984b47b0116eb29eca6aba34d55a6bbdec82546bbaa9be845bedd563262c4ccdc0d0b22c05b046fb27a59c00f6d00e1b6a34b0466b6523ed69dfe917d92f067511500766ed3d05d499d71ead0415c52fa25133c9da95aca19135326ab536d33ad6ab430c30974e24e61aa98841b046bbe90856e62f0611a9efbeae7e4fee21f55ddf93af4bf3b2a6bd6a1046c3483a886eb141984f46d3112c15ed49813a11eab4f27c4ba9711e28fe54b15f0775901179be89140ee4393b0479ce22604d0e1bd8f8af6310ac99e768e267add606750e1227fecc39e747137fe6eb5733fc593a23eea22b869d8f41918ac681735a213527991af66bb5e47560fb72f6105bced6b8cb69ca696fe65285cdcc46515f7e1ac9043ca49f33c2181ff19c734a29613361d30eafd596f059649b854b1a47083d7d292332893fdae96dc0a16ff2b6c939bb66edb65cd23b3932c86e4913994c91357cdf896ac27e1d254e429327a641c80de050922349bcf6280d0f4f90c6dfe4c951f69671a5ef745f54910cc7d64aaf9f37498b7cf759fa2cdadd8dbf1b3fd21569ccca6296eab3342e229f1599d73eb34f9361f5794de2c8c5db1fcf0ca8f75177a1f010ab952c863cb95fe5b56f99bc0adba061255ed2a60c23493deb159e384bb57cd62d9bd5b1e3e4ba23ab2fb9ee2c4efcc943daada4edbb7fa4af927e92f6e83b3db26b181b84b19fcc9f39334a5bbe4f46c8cb1b833cfdb431c3fe30899bd5b2b5c4818e4a803929aab0cedbb9ca93ce3927b637d0c70b0ff207f3923f1a14963841a66f7d8c4c899cb8608923583c60bf7a030d00654a89342094e9bb05b1408a52831f2b404145d91414f06056cbe5d01c019b1865589eb9c670c6457c10d30ea14f64d84e296c1499cfcf1623955fcd0da5644d395bc258fb2666b03dd93489ad5619bbb9da77e609a314aadf44ba34e37433510961686c5e28945f6cf4924a29a59cb2b1946163d19523c8404198ad8b36f1944a7db04e75ab92f1b46948d8fa79170cac2aa652e7696197a7700bf3a45218d2ae95da5c6e4fac757b622e41628ed7aab575f26a0efc822d8cb52e8c9d7e52dd0f16a9345cbd7e49a011068a98f205ebdc65bdf6cde95bbc2c1dfe7aa06caad4bd9cfa25cedf72ed0ac75d375c5c386e6017015a7f790d2ecf812bd07297bfe00a4c61a4e52eaf80ebade85dbe4e77b956be4e6fb956be526fddd6679363927853a943a39cc22cf5f67eb08b7c8268a4bed35beaaa3ebb20aa14fe6656d9afaeebf4d5a395eb3adde52f6f5dd7e9ade7b8ebba4e775dc75baeeb8461e051866df9098f462f9f393e1fab481c1d9fad4f97d5a7eab3e5fafc093a72bcdc54aae59eee725bf7e3c9dde95d27bb225a0ad7f6ba4fd995ce42dde20f1a65fb6d83028bf2e9f5d6bbbc27d6faea56e2b854980626aeb7bc75971b69a9cee3c2aeee3d312117ccd3825ddd7c4f2c5691afd9c29607106c8ebffc84f3205ff3e799c288cb5d7739cf144674bc75285f2d98670a232d3f01f3b4dc8579a630e27a0be639825d304f118f10f99ad7818f84fec12328a70bb6f2650f632d2acde578e9c9b09c041659d4d5eae95b6cb53cde8a0c893cf0247f3143220f00e59e58cbbcebb6bcf59e584fcce5f6c4b6e65cb727d6727b622e37c65a2db885e3c9f4ee96be23bfea1a1291588dc3044c78518426a18c13e209e50d7cae0fd7cac9adcbe43241800f251f175a38fc76f9eac462219cbc850b633344171197eb30bb9eb9767cde35249b3962279c4a29a594524a4c23941c3972e4b8cc711d394eaf0e281ddf6989c216f69b4a344eec32c05c73d8ed38cd611784c7bb570c8cca9c1e7917c9eb3043188cb91f4da6df646ee797fb45a21f2b59c7656afa08befab5f6511fa150a878b4d91c7641fe83dbd1d65a9965be513222520c71f3ae87e311ac690e8af63428da13584393b2271914f40ad499aad3a45bbb6dcf41c57eda2aa35264bd7bf8f3be09e4e32426665d0c32cb7ca35b0e1bd8d3e351f7f2ed41b01f4d0282afbecd5b2d2929a4d0accbdc06aa3505e1514ac66ea35c03f50d8c3906b79356a2bd0934a5f44d6645df08411deffd7905d489691c9f4050a7f51f38fe1d078e7f15c7eb61c4a142f2d527cfe5e3f92ddc85ebf2811f50c3ce33761969c1c7835c88cb078e22860be02f3cbf2796b1eb0139a74236f807feeab1900bc3bdab09c19a7eccd5ae006279e1c2f018ee0b177235ef76197605e8580c587bf2c2d5a0c070352903fbd52a4faa6d5581817d92dbc763de9f494420548dd803ea7c22017500a09d51b54308849e71d8d5b07397911682e0d9c27b62d06524ff85bf8079048067c62ecf054304c0231b30108a452658098af5c4a6d88f266dde8579fb71e58f1f3e6e43711bca1be7eb5a46284ffda4f336b95f16eee30279ccd5b97763a645126702c9d71911084da26a4420da13ed45b4e88cdcdf00c0c2ed27b0a60fe43614edf575f2044aea6b4f2290ee40ecd11a4d6a0ec09a06c085f9476fa081daeb9e026b1abbbce7bb30f3a556f40afd88a9942dec179de0098499b03782e0a4f0afa270085bf986cbbf13f28df714a813f3be975957e15f0bb30efb887cf559ef22fac6c7bdb7cdfb50e7c76ffed9dc7d857f35775d8f7f9d4f873918763504e02e1862f69ecfb34d198a65ecc289793e0f2c0c1c14c5f25d784fcce64ed81eec5bbe3cf18735520014eba19e119be2df7e7d7c8b3f5a602294f3a54b10e113f37161daa38771fda211c80d5e017f35f7c03117079c041c913834fda277a187c1f4f42908d15f8c8f54c05fabf56da3dc0b7f31bfa4e7c93cc7635744c765300c33af8bc47bcc3bb286e63cbcdb93775c9867ee47e3c275d510801674f02314e371e13db06640149b42e29c980b329f919efcc743963bbc1999dfefb4f43c997fe6c2bce3fe97b95f4fbebfdf2287a5bc0f791e314efa464a1e972a7c02f9b8845d7eda5ce2efc72790c4d389777aefe27bcf3b4cde07cde58ecbd3ae08bd5764e63318c21a19f997779a5f7632d47ce6927aafe3d79534a7f7bcd72576edb8bccce975edf8cce90e3c44e6339f91c1de77a4773f99a3e0d57ca729cdf13d8ca4e6331809ecde775ae62e91d9bbbc5f0d7ec1fe7747e4de7bec86c03054012f91d9c7e93dfcd97c3ff01299799cfae0c1437a7f7d7b5def255f3352e6de5f3ce469cca5f45e9799c11f95afe9c48977893f27f2530999c71c097aa737862686493e8f6fb18361067f3d7966077ee11d352c9acfdc14bcef348ffbd5fc7afdf5172eec155ecf778a9ac3ae480d7efd15f37b1849cc656264be2369ee4e7ba7a7f9eb13eaecf8ebf599d35cd8deccebf51e3dfef1f83ff31d77c29a99d7ef7d7b5d24b07b19995318fe7ac8b0abf099fbd93c739afb29c934df71d83731f7b1037f4bf28ebf5e7321ac79e18f27bf7ce04f490c0ff975d8373f6e73087562eec3067f3ce41daff98f0bdbabc19f92fcf2813f9ebc037f3c4ef39ac75c086b6af0b724bff0c7e333df9134f757a1e630c37ae01d62e621e36124f432f732f432ff8efbc55c739afbf19067bccf5c9ad7dc1d34f7df71fb93b907bb22afcfdcbb1f0fd9fbeb4258e37de6e55d99ef38cc01a13ba8846d979435eb149140000000000315000020100a064462a140249808bbae7614000e7a9c466a509c0bc44112c328ca20640c308410020800c6001821a21901b02068fa44c7b603af965f4e0776b3e92439e6ee0df3f9043d0ba2916888a315e6157695466e2f4a16c7177c6a1cc7c508d3950d20e02481be892c2660f0d4ef419fdcc044cbfececdc2511a38f95a2efe2825a2e2ed5e42266650b9a123184c883a5985dc84eabce2305f065d9498a27bbbecd7322f50b349a2c92f2e591998dae67ebbb9fb5a98498346369818f17daa06af4ea3b9e2e3ee3d1b2f9240adec3664146f8bd6d5695ba63485d14ca530f552cb4c6fd15505d5be01c3bc03cfcb9d94e9788e729dc460dc8fa174560f28a237f4f44b96f716055e376589a1544f00059fc8abca1c349aba8ee5ec02610197c6f217aa1735a350b00aaec080e719a366ddabdaa7ae547d03a1419b5dbbe2c0c01dbe09070e1549ecf5f3cb5f819cfee76ae6d199e2757e38286671c5e0f6022810b3f32a86e6c3fc6a4e144ec95162ec4bb6bf2f85ef10518b454c17488d565d4221ff5f65c52cacfc016970e7b3140ee3e25f4865c7613e5397cdeb5c4db0ada37f9bc309934cb518f52ed3ff0146e557ae35776bd7e3deb7018a63c34eb7b4c1ac7d263d249e85fd60c762948d33bbcb8ce8946607312a51685b29f83b344c73b300da18ef91478956b4fd6377205d8c0a72dfca52cdc7fe47a5e80415542a9c802c4f94d679a9716dc78ae3f102c1653b2adea0e183061f3752ed685a1d88e2bc25dcdd565f0f2a1b81964c5adaeb4266dbe492c2269614ed8c80b0fae7642654bd006fefae302f749c2c96a2d873b9cc1786fc2e482c6326fd0c2e36e18618dda6b4aca0597a408a3f78815d2e9a00590a481de70484cb6f8d6ba4d0023d6b0c22a14d5876ee92f73c7b703c28d6ba62c4a9beeee1ec4a3e06d529de1c31e19c342dd879ccda2ff965a389b5c5ca79d834cb5fff56df25967095f1ff54a70d53b8983f5dd55de7cadaa146e8916d286eb4833f1d1a123bf24e39e5db4936262573fc28fad5369a46bc26dec02a0621ee02b1e13f7fa5896ecf3609c4c8ca5fa93695324f7e2d138916cf3f7d57d070ff0a99d71ada9e80ac8b9cb366cc399dbf0b8ceab1f55c7befb385a686eec46363e992967f1ea8416963410bf4f09ca5601112f1a0fa208e779d3f869a4493b048be70835bd1f6d6480cdbf0f75a27dfd58dc3f32ada23a9bd1c5b1ef15fa442cf400bedf203dd468bcb41c911a33837d941f71a418a2a96d3daa7e235fd29e71ed169f77381e86b1b2a5cd05fdc0896966360e1c621ea85e291ed2810410e98916b25f054327e2d7a5ed50d5fe8f5f3b5a93e52d6a6ae004b79c272918756ecef181daeb60355d748f975a7bad227bcb252f700e5cfaf9ecdb6d44b17144c155e2ef25f6eefd6fa166ab5fbec9900b17b4a9ac4313f0ef70c1dbe26a7469db161806c76f41e4f67550a101151da22ceac33fdfacc80fa9db3da808ad6479a05af102851e1e1b840c572ebee8f06eae4a6fc0eb4ac65f7c93144d98fec669b216d1b140b1f974ab36eac82fd0e1247b03bf2492b7237e6b7412b64f566d79c3ea4ddcd0e41013ee3a51d10af92f9a2771245d1d39a4c4a1b8858dbd38402f77427f1fdaeec5575d6dd6c5fca8f9a092f760863dd6e2b4af47e5f8e6050396de8972cd01d24d2bf9aaf82f4b1f9b9df5f217a21f4a22244b7f4f0745a3d5250e36ba70c5a592832e39960c15793debebce9dbd577826d60755aad603239865e8e4cf58851cc09ba5c8e6a2581ae77dfc27739f94d0cefd8e5c96bc4e10c3b253c4881138c0f4baf9f441e5c16a46130a90125618ecbb923494a6c27ad25e0d7c683706c3b9fb186370fb4c813e580ab3da85243d3b67d57b34da8f1b473b214806209dd225ecb78840b1c15cc7bffc78cdaaa108af8ed90e48e9fd1c7e6184bd1d328bc8153ca03bdbf3c2de2c50b60b5d06ec36080295bdf9c1c01fba71ffc84c06f67102f109780e0aa37f88b7c4e105db150b328c4fb0daaad6fad8368cc0b38a96dec46ec059bafcf0c56faf5d683a41226754a5ea094c6e05dd889b59bbd0a39aa8f870af1af6c19575a83f98d013097e00f1dd8c1745a6588e4b5c8fe8cda2b5d1e092a7923c62308bb7b14e8c8de31908216d89caf7a5524a030100143944549212aef0b9a0dfbbd7503f78003bde88f82d2ab8b55c555989d7efbf6b3ffbeeb98e8cb23644c0b5b9bcd2baddf06f1798d07b484cc6a6ea7f38099f162393b6867c91dc844cf7ab68e200dda96fa22b835aee20a135a349fc91522e4939b0a2248fff9b982f7720d0c4a1ba5a004eafa3c4f48776cb6027ad64715d879259c02660d2cc768cc4157d78e33a7c460b270cc8e5f372459b094dd6f9e36dd99101d1a3ad0370806813ea517d860dbd89995a2ef3aabafcaebcb0b56e293ced9d4aeddeec9d84856c52779acc58e39ee0d223debf7141e440ea059713e20bccb8e292bc0f0b51252ec61ca8663581f1280f81c245ec1cc7977418b4216fc57e90596fd21f8ea9c76d72921bf110a570a7d5aeeca8678de1425c72a6fefc3c7d526a557aec32801de6effab5d326c49950e16a56915182b2bb77ae37a2995ba27d7a5e7dc4cebf07804ca590e512e6f0497cbc8bddf992f8351c5013befde93cf3ce1ec75530b77946a453ba6882e26f13fd280c6d2c2a9b941e8ece17a4150f12d0a7861796e4461283eb77ceccfff0c3e4119dedc9785e19bb5b232c3f750967d825c20509f3090bd2b8d5592d298de1bcaba35a86998f366f484e85c9f790aa64e1161b65e314c551255ee1cc975f91cce014173bf4f6393e67b681cfee8571c3c82fbe7287665489203ede7879c856ac0ff738e169c68c47ae597811fc1a97fdc31c8ecbe470604d8c944003875809ad1f07001d117d4bf41857a92e9257b229b5f0e3318f1ec45b2ac903f3a4572d2136d37d2e0ba93bed7949f719b95d5fc55e45163092bd1fb729e78a9157299862fccb460d12b24c9edd78512a725aaafaeac63dec0cb282bd0e7fac500d7a9d9357058482800c6f5de20d0034bcb860b7e178165ecd39d90e1765bb39e3f82694a29ea30589469ee19a8a3af6d06a1edcdb574b8238b7d1b60a1ee3b0b02a37db4bd5f068d36cb2bc070cba265c377010af6c8669addee3d1d16a836e666a2a1e439307d036be7ed505033db9dbb60918a99360b296529bf9fff472ff87dd20283145cb3452e47d540833cb244291429029c3e71855ee3cbce409e16553e1baed26a875957b7270910a90ec1e021015e6b8836af1e01bcf6db986d23800d2c0b5edd2f9467b2e5fee8fbfc4393f15b763e8d2d9b0d1c5d9d9223cbdf06ca6dfaba08dfae1b9bda5d35eef451dd920383a6190570c17d03d64a1362ade067b8e975165d3825983264cca8a17eaba7dddc8a3b0b63bf1cd0cfd4e28f2b275a0ca6650d1f999e4bd822715c4148cbec4da862377a0679ecc77bb38b5ca3082c4446d5af019ad54a74526c3de0b0d1d637f3ea900bd0f1e776e66b57f97a80a3eff39d57c45393bd6a2ced250544e518cf086299b6b5f296d4130c9d9e80b9ec55a27cbc8680cf4eb0b55512489908ad637368b41bcf4ca700ee22c84696a5b73ab419913e9b3b64d6ec5603fa793f7fb4d24cbc5ada6f707b029f8e87f8f574cd1590b0880296d27446cbd45a38f532e4abb51621b40aefe864adc4ca0be1ec84581f2b0aaaa036e86ca1876bba5a30556e50981cc483375721f0149ca674291607e98ef668094e200ac068fa3bad6e6d046d1ea357ff37f743fbd16408c3e68d238162ad19d0b4273742a2c842520815cc3954bb9b2e0eee5ec8ff8a2eb86463dff758d25182754e764cbac00124b950667135b4e9a5f1b66eccf693dc8e3cc8ba6709a8ff8c5668af6bd15ad455dfb8b1cdcd295c4ca9937db9b1215cc07faf0660a4d0e9ab56fc481a6482e17492508167ca0041b812cd62b4eb015061120f2f775a1fca969af74d37da81a5bae7b8a318a6260329924495bc617cab2942392823ef4bfecb5de493a6162049225133400d3c10c815a0824a8b937de7fe7e836878d9a13bc5cce7a15dd2228e04148aae1b38382cfd7fe21a922fbf24e79515d01fedc6d677c81caf82c76976e8a55cdc40054dec6a240019cb5180393b207e357076f05cde25057d3fffb7181a273dc280cbc4260b7cc1356b76e249b759197ef93f1e3638f7488af8b46adf11bcc19887e8fddf322673de1c7bec2eedf12f31b9ad0a3003836452c572b3202562c8e82cde5071ce422396c10ebb4a6168742ac850a430f8affab0182c4130484d95e7d199a629c8c8cd73062c2661c4d143bd218c32e2cfe99f07923d6adced813c0afb41d836ef6d6e6acc09d375a63a1607f0340feaee97def24ed69acacfecde6dedae6eaed93b15677143ae87da89da0792582d9437a7c23be25ffdcaab5985ae65e26fdc262683c3c0fb892519ac27549a32d5271919e4676313224ac2755b6132e280956ccf6c6d6a6ebf2072cde802a3b5b0008372c4b6edd349408e74106f5b222d6136f5d6add4cab9350698107c1e026f8ffba6c4e75f070fa84cdc2ddb1bfcefe156932bddcda48abb79370f3b92079d35c9c4fa50d24a344705b227ff5a50d2e83c26088526e5f36745e30d1200a407a0147b889d7f05cf5825fcfb53c4c60e3388327a471fe42f91dc4441349dbe205e76e9c993feffc43864c591212b019558eac49692fb46949e6242d63bc8cd57b5e14d0170c27c569abd1cf025bf65a62b86f5a6340b4f03e265fe14ea937e51715f4314ab830239220c504c15615a643da015781eee3cea4a943c7f57b05a409245b93a1985ba856c0b50ec4779fb3e55425d4fa3defd0c5bddac73977a50543c5913b672ee039fc54e3dea3524caf3b7373dd9a4aabb657a603293b800b6e87d7b38dfabb00eeff5f6b28af5907621c89e5d82b6401403aef75a7de21a7538cd39dbb3c69b81cd452e8bb1e510025d04e7358296edad4e258137a2146c36a58731a9a9bfffaa915b4cf3cd06329012428317524330812978d47acfdc3359eb2400a5898668ce2807413fc3ee2570359c260d4c487debdd38bf9f07174f7206f49e23dbf6fd5a8490f8bb90ebb97af23ba7373ca85e8fe1ef1c3ea5b5df1b44952b850b67d9f68b83fbfc760b40357cd3eec28f634b25ab5c358176170cbb1c00bf74d4bd3f7f3e1ddee2417c77bcb0267d55db500e398c3cf6a05407ef0d03c9b04243cae235c4d319a76cb26e007aa40bf2122abe7e70c1a1d3c308a2c523a7fb4d1a351664e9106cc2674255f7df56e8256ce2dfb03e8079dd127b5c9c36502a9562f163dc76840d594785978433128b9b3eb835c2992650d156763670139fcc2768e9c2cc7056c7cdc09d8928f7b26ce7904024cd3a34c558df59368c563f7c917235182931c448d523f278dda44f0a3712a8a5ddb87809d2d72478bf5d1b466ae39f83c648959e191173d04e6f42705d6a281a6bfaba156598f58034359d6746261e1439e3865130eb07452cf218b39cc5334857ceb5bbd33c2e35e7090149de3bfa9c5340b9b646e817ed0c5bcd0f36a3cf1650b2490ffe9ac2ff05cacd54208e80ad08e9dec17358093dc7eed1192173e62b593286525b865a646b09266633336c06bcc7ca48504094d935cbb6290e1436ef075fa26db2902265f3d46b92937d2c7e1474cc8a4b5771279cc7a595153baf967ea9a8b50c84b2eb41e30e1dc1d4d99da4a5a8ad4c1caaebcd144ccb6b3846a04079b56e73e02e8b15d7d334d8f40a4aceade42e3333d32de4a2310b14f1a3b91cc6007b42f46b25a11f4839db46db88075cf5c01dae1af370580c601577ba06bdca77e43b2059a15269ab2c48f70d7d137361cdfaa5fa53c8e6292006722b63822fa0ca3b6be7dadf4b1d3600c70a77fbd24867202940b735c04813dbee21bb1b94d696eff9d7b9bf0168938580eabd7b2ad8911de8949511bd92071db1d0dd4c80f95b09e78f637f06d112a11ec9fae82a42150d667b55a4c03d8d61cca31ec1899993ae5a66bde19ed685e210fbdadfabde97ff5e01991a81661a227ba94641bf9da21095867aa1579a468f3fc187dc2c7023a7074548ac090f68cd1926d27aaad2ba709d2ecaf13d015787526ca3e474016e9aae3c5950c2d581805a1202cf5ff54e16a601d422065c4baba8ded193d825f3e9ed84230d7643e5b94505a732336ca7756ad47549f7cd06185dbf17312c0a285cc36d290c8e545dd9f63d9a7ee87b7f3d761b781cdb58abae61ddc99c5202d804353ba22c53cfb79066e2b4efa603fa5edce43bb5f4beafd3f0469f996ba161a78c893b2c28f5da804f84c0fd615941028714f3133c6692220fef1436ce0fce5d98a6aa38c16cc18a850f2680326aee68ba3f8d9d53b6cbd69167b98b7bd35e992d88e27813980f32218a3a66a0ecbcbc959db9661bb004c98596bfb91a61358d87a099b3aed0c4551a5cb42041149f101aa2e471818f793feef44f1174d7555b69179b7571af375ab28ac8f8f1a33e31c41335a8a18d8cfee8373c7845d5cdd3975e7350c6224ccebd87c3e70a81ddcc184c8ad6114159be29ce518285bdeb5808fde507751b31807335727d4fe4cd95cdfc384f6a4d002ec60278dd209405be92d4259a7e38229f3d50980c40e8e9141d44e2592c092d11d63f0360fc83232b6c074b7fafa265ea498d337ae6ff9349807e688668b5399d690292931c86fa51e33ca3202a9a77d68a9cb326945c967376a97c3d3938ddeb0952a492eb3b16652d2d473af01a4b0027858bcf837a424f989081634bbf7a11b2aac11f86eb03d31526ffac2b82dce9352e8b5ba1f26aa6d477b641a59ae62d7af011feb251191a627382a4c3e2ae2d8c05c1279439ec1793e4081db183ff46569500a9157f3970f642a37dc79bef4ad142533d43a0a24d75ebf7fca1d8ea65b1438dac006a74e6d7a8b238c64e000e846e63f7bd08eeb1f1d3e4ed812c710205c106fbf6a7e2d29d03ee7cbfbbbf11415fa5430fcddc99a95bb947153dfb373db0c0723c299d7cb3673fc7db6e4ce7a3d0076f7044b6c39667bf435dc5cc346d94bcbb05960045cb5ecd32d12a29dc4f984ab8c199ab97e51f77203b22bec7795071792321d3603af129574be29994e96186e7df2f5c57c9cf9d4cafa34e47054261e2bcc6069f094e5c1e1b1e2cde17e780ba90d113b305d8d9bb5913f7f3e489c0ba1d9868f3b816b835bf242ae9be4818099c2c8e6dc6b17e31235949ca12b9242b1a05323da125b2816710255280f0cfa6b768463bc4017b82305bb6815095324ccf5750c2d829552d0c38527c00874bbb6d6a6a4ddc8a65392d3bfb1eb6418be3c7ef0b464bb3d42bd982cefba7cbef003e4cb4c0e1ee01cdda7a0298854012a826b4c9b61cd1d4d0351a0415a8d810ac275c3e365563d8004ee64455b13e044e1c8951f8b3d8f37b0fd057da759ecfbac0a724e6abe63d01c662df91d42b8d1ca2bebc0b9b601fbc0194f49a7ad2fe5fe1a9136b0abc104bf2a2056b15ac89bfa2ad91e0ea53b116dadc4b4467d3b6bfbeca01e9172618f2654c615f4c61523f7e0a7ef9ac5b0430cec126d63c7af3ef0e0173d0c333437380175f68f096ede2f42b81672d3fe4156ee26a6c8dd4f241eae5b2f2a2f7cd8e544cde8813cf45fb96031970ce7632bd0820f2bc77deb00477a490eb47e2701d83456b2a08c4cf3158c455c8e217301ad11101fca9c80a1ae0da80ce68928b70d70a6caf878bdad5736d36a9793d0c8f605101aa4818caeb61806dc6ccae7bfef71affe90c02583e6873a1e1ea05319952a6561f8631750ba03c55f83698dce94ae5a8c7cadc299fac95d54e738e22b0e88ce40a330619c44a720c1ba2a844a07d6655dbe62e5f40fac450ec50d9f22404c3b624275373288065db41ee54921d27e4be57e1c0d8bb01471d7149a452a8d0d5a731ad11eaa1c37652203c88d2bb84f393f3f59898e48518f0ef98d9d6e260fcc29323efacd3cd5c17d137939c20b20040f1328dd98e33579f201d04339ff4017c95d243df3aef5e566f25224311cc6cb073d92f584e4ed94d0265b53a1e0208a6a4c996a3b8fea42134c42a6bbe74b4545c31142fc9ff13a08b0c0535486eb5928b4681b92482015c14ee9ff7d10791b55cb8725a024ae8c38d0f449205d38a198195d0fe63bd72f6f408b626126c93dc5353752e1a3c5c591e9efcd33efc937b4f128f48f9228457932695c3cd7f27004fab7508d0b2d3d539684e268f4f5906eb96337fb7aed9a89eb2c1537f7471053b9bc4a0bfcbe488d6f75fea79d30dd1793b00480d8e8c3da3e32acb1269a1c0f7930e89043dff22ce348f45a3d783b239c8e22967a544f998fc0b06275fdd92a0ea1a06e1f21bcdb68dd1f2f782f8db3df79af89214f2aaf20ac040f0f7cd3389357ed953df2767015479284956ff6900536feb77524a738ff418aea4c04cab2bd0d7ee14d356ebbc9ec07f2a7dd0a7e1ab7a65bc64d1bb2b0863835b08420669ea0407c0e3d37ce99d460e0030475e0ba4a470a07903d82981787ed991c565652e4c4ed6c156852f305a749e46c241e848fcde89608f68b823d08013dfb81342acf7ddba874dcc144c10d8257d24a3bcc8d2a936d7978bf89f6efc4db972e6321b9060a8a59c671348d3d9a2fb6b1a6a7da31c5a58bf38e01245e297108d0db23e060e19538eaeafdaaf8cee62f1acde682e041baad57d55b2f4f10773af3bc098c0772e45f4e15f8f4aa3a7714cc5c6b3ab22fb5bbaa1b16c11549421803cbd486009e5bc2734f3059c39479b6e7a9cee54d42fa56b67af70513dc55fd823c2b29111ac96247c4b531478a82d7102555114ad92185ac0b1cced4b084bcb20c5cb8f27bf85738d7d99ec2da8f5596cb5f5c5d250148adbc8634a17e68fef426b25f9797a85c2c6d9e175e96258af56656bff9e820110769060218a383f7f02a9cb56a5200b0b11893dac17a4d84dd2de42143ed0807201cc328d97c1645d4a0c6ea9ab7b8f7e9c09d3f13b00ea9c812a860af5fa1641b171e032aff3c8755b956a38a8f074a79a3d3571a1dcf10450f997c4c3a46c4485dfc52ab59b1216b5428e5322824b24012066cf2c857f044744156537ee79ed3fc9a2f94a8b18b1e160bab61e43df306368d30594f25867e4b220ea4640e11a64fad3fc3f94b537fd1dfc1ce268d1ebc5be5762a03de9a10058c137f913ace1121e1d11de1c1139693aff947e1fe9d734244b0459dfa2efc19e1fdae752d6c53c2fbb945cee06b882210c5db15c132d2f1f619bab4a734b56319aaaa1ff10bc0821d6634c969b0b29fa06432f2422d1dfd82edb74e482df624d0721a88b846d28d9296402eab19d0ad6f0a9ddedba7221dad00c84c33f1d16715f9842b007c8dd7231cf75310cabd2ce5d3dfda69daa7ffa79ca9f23c1a64eb1b66c070b6f1c7b9a909d4cb830c9582c6169dc100b188f1fbae69450c80a319533d4f2efa2e1803250bfbc6ffa61216af8dda157763ea727ef9b7a5173e8a884f39ed250ffb64153316130021725882b3752a37169df64b16c88ffa1b9cf1b784302eff90f006f02beb80e0bbd760fca8a2cd6a235d85d9dae54e384484f1220ae12851b8514e69d9e6bf1a475a8adca139b8fd7443aeec2dfab20ff7c1aee889e572817ded39fdc9229e66cddd4fa8cff1c7bdc5e58c43f37f9b6b7ccc79831451c53b3e7a17a810a42d1b8322da0bd8df1aa3e12ef8f305b1dbf8fc5a4c54c7b360b3de04785e97f1e9202b361949b3c8aefb3f1da0ed894a45e254bf20d0fe716ad321ef782fd02bdbebf3f25b27b1a66fcaae62683f6c8cb951222c03274dfa0736ed5a7e117b321e3fe16a034c40517aa4386d4e1898d8b725f298a99cc4eac79074bc99c1ec31a638c52eeb8ac6f48ccfb245831c612e81b733585c6c0cca2be6507d9f05e52e7087c64e004a065c845a7d724e786a5f7528abd56808b91b04f644d54aa09e1e5970ccbc44a36616af429b902b2c0e3e461a6cd492c69f0367446c9da40c3b09370b00f9c72048bae54d2b5300c5753c87389a2b65f81d636b5f9836751b7e34be400bc29df146bf35e8f78be6ef5219699d05886a315acd0c828cc63de998ca30c6fcdb285d05322c03e7d7f7a2a33ac2d7f53d2f063ab9a360511e721d47aec7e6ecd3ca3ffe28ed76bc7ddb89e0a64d3be9073b22654564acd9f9505e75c2728533a9671a127113cc9256b1557a26b7173d614d5144f6f66157bc2ec287672b10b1d3122f1f028a79bf6d4e74c7d426d2dca4e086de05401b0a9c8ad7f15202132b1761682ec6ab4a0264ad49e5c74146d40d4b92880173d9576243f578c641d63289eeeb22eb9e12eea32552d3ec026e0e7d89035155b8d5e47d7b607eb2fb2bfca7fea2c81ec52f1182cb1421d057a9079a420ea274a93c7b6fafdd1d7dcc886e4c8c3479515faf485a07082d5780992f524a3a674ff01523ac6010c453978eeb06417df9c72f486cfe88e826ff24ba2738c39d218b395ee884b772e14aef9bd615bee5cc1e9e8cfa7b4b627367733e755c44ed69e2321107fb97c68b39bbd40e54bced37b16655271c9a6d575e433156ab80f9e2be2d1d1f55ad959d87dd4bbb1c33140088f927382363dfa681710517d850a1171589da3b5df4b691404f733070420c7e9bce730cedbec68f032d352d868a815ed7ac43ee5b26e0189f62aa67989423d8c0bb8240398fef46469b9d016e3642f6a976ebacc63a2b6de541638d8ee5ee16777f78a7b62ceee5e71277752faf85a78c82f9b38a0274678964e9f056eb843e9bd5b06c50c8236c71f19ee0dccb07b2cd311affb8b83b2e4bd9a8ca6385a1494726986aaa51c2959e791f1720263a2e3e1fa9b26be238ebcd524fef1443a5874a3bf36369b7dca90f3969d70b869a6bd68374844880e5fb849704073d16c0877b35539f94e6cde74b351828d87eb4ad8dcda85cb84bdf3d8623fc6f8ec2a623c2df5025c666ef5c637f70b8ea471bad36d909d54676cfe99e56e500bd3c984463082ce1c58dc80aa7e577ec9ca3f9c73a0582f13ab5a86cef34fadd02fe0935667515a2a17bd6146f0fe878faf163c0172ff17f46eecc87192d23e961fb4b711da473a2166ffb21d49cb647a42bcb09d98cad42f628f4bddd12d09fc8731322b9f45c7700ca2897761e81f98d03823e4344bae104143574ca3ecef34f0d048f48ccf5889e0898df0527f9032b0a4d62c094f23fe38c78c9d0b9ff6a31c6a73cbb06f09e16d81b28e34703d869cd234532a4f90a91968560edf633de1ef7edfdc02157632debf0c71108713b30e143dac30d5ba72a55027b9721b7b04818d46dbacd8c6c9b2c53e3ab73f1fb2232118f5e5e35353bcf59647de5e41347aa9da68356a6df0ea20a50f236bbafc3eeb0421e2832e6de699a4287569517aab185b5dfa1502a5ddf6324e3428c90e97cc5c69f8ada10252c49671b277a116af7666f6c649570e6933f58211d8d7f50b91fbcdc36b0b84c89f42a7e47d68d612920c454053d4b48bea5f94481fe43476c61add25f8ea9ac634726360dba16e49f002fa64b70db2232dff0e020c940c441e659b9eaca5478fbe3345b2afc961b1418f01f84c61c24fff478abc556c8cf9182e7421ed7d5ba3e353770f60fe5d15c4839f671ed2c816194d64a588a24871a59e6a168a314b9d714db9310d6f99573a27c7a17be654ac317ecc9fd6ef11cf828a71c7b4e7f62d3398a759ea00f9dd82d94928da72cb5fab68da11c2d21e64d23f96d1834e0d0ce8f97aea3f24cd735903bbeae0cbc36d66e3ea96e7f028d066361c306aa914119015c316ab6617c0f932d881a518cc22c708cdb77157b96dea6e8935dd7243c271045fbee044ff88336d1059092067ea51342678ba9ad0c285de1165632fda90d50e1b7209c69d3c275404d3ba2177f6eb63e3fd5daa5fee0a7bc8ee9a1a54ba4d7eb2358a2048f8019c16bcd7578bbfa48488b59b8b87a188a0ced4358bcec8d86599d973899c02c7e16357dcee2d54f7aeeedef295685a3d2aa2bdd8dbb4eb3293755c3f02b6f76166dfa9d94157874a8c48243785c590303d3334a8841abb29f287841b2fa7cbc9ba280de3883246a91ddedd47c67b9d013424a055c0c24f28cfe6cdcaeccd56c5c744b812cc1ca50a6c27086ca610830def270da47468cbfb4c4d1f4260f208d36324787cdbf27a666009f760f4d36de1d9466dfec6052f80508e3fd17981a349b40e06ef05400250df235f6a9134a257937875bc11bd55f52dea1ccd927c51489ed4e8948ca5b86a8315e913a95b2492e725a029d69a13cb646b044006c93233672c9a586a803109b6d0d208cbf8def49d2897b03ed20428c4e2b803fa0cfa37d435d31aecb57737e76e4bb76679e4c7435d2302f77eac817650f735f069730d6cf2cc64c052ece401cd22eb4e24f2f122a6f0c16f56b95802d516a68a017cfe9d50d00fe396885d529006ccb32864d1a8b3041c149d3514a1800609f430caa7e3775e45f629b8a8b882a0a627b9a791ddd3376b499bc5edd20c2f73b3963876665434767afa375e1535699096bd9b63b31e43a846fccc5a99446c9259ae26cd2ab6d046f1b40f136ad4c9085c20323959c775c575ff12a1a7f3ea338f991e65822458955ac38f9bba68e34235e02db4c68dc73a70ccf4cff33636b02f34ac21025084bf42048332e1641a4c6b4b11caf4187808d510174c997261fdd06a5a8a0443051edcfa0428f94c32db3e7f89fb140aaaec66e2d2af45d4f7ef224d488cc488d17a16615d986c76682840f6b08bc44e66c60e8b0c75e4710ac75354783c554631a8a3e0607e090157e3a476069d920ce3440351e175b960f5d542362d11cc6f10be071d1b4ea4de1bc554402dd9c9af70e7265479ddee7aa94c8b97b7c424e666ce5235b519c478fb450819519c2f1f1054351eaf23edb19092dc5b96b674fe8ea22622481730f3d955183b5b66d663753c19d3182b72cb8344e63ddf0d4da719d3cbe513d6f13ca766982648ac48b669072157753abd61ff11ccaca0922c0fb28790db39378143cde2983d44479fb9c2fe50e87e83f11078ddd623f0694bd13708784bbfbfc46b46c17e0b5fe87c210bf3034dc3d1a9439ce9004019c138be7d6f8ec5bc010b4aa7cf01ecadcde2477964810d87c52c609a25bcc1001c0af10525f9654138dbdaf25b24a58a22219bcb2640f26cc4a507aa8024ab04de3a07f34032307e1b8d11224080724be7bf4cf855ae2a95e5a6701ad534149e920bff64a31043b415cbfa7470091a8105091065a4af8c7dcbe489b2de24858aaa9245d8b287309ef17f86b1e570cdcfb01c7ec82a2f61767d1c94e47c5f0bb2c7510063b63298a1e8457e5ac23ba3726d85015ae83ecbd60b1415e9c94dc46db990325e022cca6de063ea9215a053389d7b49b35276a49325269d0f94e96862821ca4c655977251fcd7fdf6ba895842c782c2c89addc2f89a5026bdbb9f48e23d942273621210fc2d5046a07c156120e6b04926d83b593e53469c61cb736e6259f95fc6576fc78722a05a1382c7be2df1fbc8bbf7f7237d0c775af540046d53ec7247c0de200c70150f7e7fd5127390442da085bf0367af3ee2f89142a561ea05d4775c4463d821d92ddcf3bdbe60cc776d97339e25dee77bbed116f4da22da6a26e30bdfaf36318b528263d71a2d07f1db31e18421c5d6a1f02c6ae4c4ee723489cee042e21ceae0c3b1da6acf9b2d3298ddb0335d2f75bd9593006f22131c6fcd30f57411c00f4fb95a6665edf27979c6e911387be4322767c88844b431f1120b5fcc595fcff81e5c92a3d1f8a1a64df3e82722c2530a8f817d481d231590ffacd4420607df6ca37fad9196846ea5b51d62f8a9637c40ad852b5eabc1aaabe12a23988161c0ff30bb25851089d8641f11efd7e8e7146030800a750badb7e4ae18910b9c796c1039d656c806fb38b0a3477da4940519c800c473b1dabf5a9957cf236f97eee1fdc002700779783379cb577c7cb3b030cb9e56dc00fb7e7c265317d7e96533ba78c01b4544c1e7171b9758e2a465950559ac6e902574571b553e8c0033933e7750bab6e785ad66290e0699cecd744f86cc1e3a28a2a3f6e6abb557cec7613cc49c33ff13a82b127e2865005563a44dce0a2a114d523f24f03f4b372ca2c40f5203f5858a9d3bc6e97b31600ec94898bd3bbc39d76dc8ee513aa7588ee3b152790822963a341285685c9c8cd095020c74ff3d1378ff9ec6e52d80947f723b438012d01590c2d897dfd5cb778ee793dc5ca7d7e6e44da4c0d9eb2f5a5d6f376081cff9ba45175677dab13255a6c0498c3320b53353ad4c9a4e18972ab2831e9ad7b4608657b1add140a8aafa3d4c923c04558e1e30e362219faa771a5592e01c68b0aa4149cd86873a8805a38f69008f1747e6ca3b6589d5ca524ae4ab40971520ad293c96e01ef592188f2ab977f674cd1917d30e911411ef7aeddd3732fe53c5bc49b72d90cbc09f3fd116e11438e061500c0abf121c5926f3b7f209d86f4dea6553c5692fa0b903074b9829b1ba0d1261ce7a7bd9fbcfe69521f1208d3d313130b21bb95cd5965dbbf549d39079973a237cc393218359cac66a14eb71c414d47587a4d8ee6930bdad911a3a2e72beba01293ae3781f4fa442d440edaf36a75999aa8373e565d65488e42e2f536031caf9e90a05aff63a621e4ae4e7c41aa42561db13cc50b353bc08d8b1625d33e46e6325b3da2058a3b98eee5e0566518a793842f2665cac31f32e523728a45c62cd19012c11b8877d84a0a6cdb0e74dafb450ae330a2317d174bfccfeae03fd09aa4e264f31df64d28dda21578ca331bf8738682faea06b7d45fc36bad03f6d4f0dbfd44d0697933e5577d1ba6e4537a7e6b7a89979c155b01bd8dce5867e287c4c988ea7d748e11df60c9c40e5bca48794f546268fa8ed0b068b197badd674c75db7d335694736869e557a6591859039cf1bcdb35a2f3239c29c3818457d8308a623260e56370861c5d1f91b7cffb07648ed4b3b5d51c3115e4771725986c33512f16735a5d6da85a040cacb2e84147c8af0e3c800fc722864991ad49259432d75e0d729a9587a681ef98d242b4db20bd95df1f3c079d4c98c05958f809cb3fe9d9e487c68edece4c489b6f2aa1887976fade781c44236e1d71de5cf59a64bc8f483384264f9ca1697dc5454c027811d29ae28618af915b62b2ac280d8ae5705d879103afbf99e0a86bf24f0ce7798d61d7efe5ec7a26cc95d0264e6f24300c491c89cbb52b144d873072fa34270c1aa25d3d7b4e2c4520587d9ed06126198fbe09fc6d32ff3f786ad29a4094c015270f10fb78cd4c4646cf851f903162e8608b69dad535a02040e933b1676c64d675c64f21e70428cbe12c06be37838bca025caf060850b217c46efc0f86c9a8478af759f71cd7ec9d5b51060d755c63e8e1761fc1feeff4ef3c93108d3666900ca89856d0c8170045850dda988b1122a50e50e5dd94cf39a98d28dc133457a230414d86c91824a4423498512fa1e9dbfb1dd1467eafcee2078e5189c4c2c131d3c7046eb1777f574ca7660a84a895625edcd856bf7adc61de7ee5806d28a399d54310f33a8acfd6ffcaa719fe13949f2b2af71f2df36043920a3abe327558193ae4022f196e26dd89ba84ca78d7b29e2487bed123cf01f32e9b392731cd0af8bc7735e481f670ee52c263362a687f60ec00dcf561020bc3fccb073de519e08e5c297242d6bf7f9ee3fa434ed0269f7ebab64e0a69c72e693e48204ff1f3e55ad59cacaa88f7cbd226fe11796217ea9eeceb06205cce67c2d12bb05ca7c6eb97d46c743d4a6bb44e4b891c5c32144610dff67612ead7ba95aa5e9f25d5b32b3c55197abb85bae026bf2a252ac28b79eecfe6096b08acf263def8fae9e39902b6412e5bcb3d5026d78d6c28a9823fea6f3b4c30f3b862c6c9ff50686b06c23d6f72f17a17d3735a0a602976856b086f53ca34353ae9e4bb0cc4e4a153e318ff55ca27c7219f7ac44db6f2906d88f3fdc382f4711c6f40dcf25a4e4dac4d6f6b1c809546c234352bd805bd5cb222e031824e07913aadccfb5737630d89c8c78d0ff87abdca010fa12ef0bfb431563d8f582bcb6c3a809af0d6f8a492e84056787288dc7f5f1c3e50b0ed1847cb7ae2ac003027e04d88047720076c46f32e11259137838f68bfc98c55637617a6f6b4aa570c0f182852f01be59242f525a1eb54f922e38695a99fef75a40c7f1140b6eb9665d30c3dff2a948882ac700c83669d8c2cfea21fda7fc2582829ebe923a810a8627b7cc7962eacb7048f2051344e2e656e357d4992d095e60ea0a52e7b4039e535bece44a7b42830b45468752db46c0ffc7250a71b8c26581bc3c89f9f4ede2b8843847a8e64e504352f117f026a5561340765a7901a71221d02bb3fd4c64a65c6200c408ae71e751b924bb92d5482375264e8ef531ae536cffc09c4d2fb3bdd1a2d727e8aa5c428ff47f69eeeb22b65820d1980ea8acf7b479e20747cb207c2a3b64ee49982346200461b8280b5bd1b0f9510214348582ad93b821f6dd69451c41356c323e9b75ea2b891071e1a858beb05e6e7658101b743013801297888ff787758c0adf91a5c3b4767ae292d5673c75a418c07ad272457b4f4740e103e9778bd15bd741511526fe62e28f6e0965b50a829bda3d69a6d4bdf318987a8fa66c76cd228d2e540dd5c4741a095d5b02a417fb22d7ca4292f544243fea08c42567772c28485b514a16c2077a4b9c81df7a8c57497e01cc5291fe29e05f2a640a03262920b5f2870432dde660c4d99f10a2f94b640c972c8c5cf27d4053062884958f496638de088513398203964fe12b51a08ead32600d9e02e4f50b177073b25152e53c876add683812f6ba6cc44c87807939f048baa221d200735c02acdd02c58579a01e5e3bef97306c4b79bc58eab3cb12b513791822ef92b84211318c1566f553f41fd7108b495d87a9eac0817ab9a34f3fd7f1bf94f14ee0a834d0c01340af85086367af9b47d4f0b0c8a784edf9dd8de2e697a7363d4d925402b43ad4ab08a619f6317e666d504fe06a2622f3fe449cb6cff387e49f59c641d7aae56cd5025246d896958951e4ff1ac80e83f35623d37ed0c517612be47d96f1005b21e4b680a6d6dd17492587add0c634f0e67488e10c5edb30abf2eb7015daa3fbccb94d8d81973174ff86760dd78b47bfc3a957977c3a7b39ce558e2353183a50bf9eb72802f3de1c06a7504608a4cdc8f8bedc3291ec6152cbb71603d0155684e29a02d218925086c97ea100e5c280458e9193dd949230f5d4d081fac95e9d0d62a7916e8e271dca11e41485e6aead38da1b65766d3d312fdceeb28c6fcda1142a1f7025697f436fe71919bc47b9e6e91723b6096b4833510431143cabbbc579500ac15ac664ab0d6cf3b43ca2a8d4c086d50b9ac8ba65b5caa0300aa23851c8498471038afa8eed0f78acc1dcae0ff851e530ae22cd32580d46c1d26a3e09deb6ec72aef402e643f63fe0e6054d0c8f413c9ba1a209ce7cac41a742244785f58f30f40edc3180cd98d8136d3e816aa4209d2d46d9edaf44736f1bee359fc8ad2f47913fa4c76dbe689f5d301211b528c0e8b364a790039c228828df3eb460a982b5093f1f47e5f4090a0dd1150440a2f26b63562c8da0f0dfaf186c8b7f7868189726584eb56f0171e1d4556d209b84360e97fd7240caf04885e2746215c7a09b157a0cc8cee307bd4db08ddb649dc1e8f27e3b81bf5d42865ff07fe31f1f4230c11f4d1bd065f1f9f2de940594cebae04412500906897d400312b36f1145f372e23175646693bfede6891f9816c10c2cc122219fa35877b28bf9b92db5f4d77f1639cd12fe69dab78f3b7c52b83b6131daddbf204978b220001590833aa0744408b3b1092c12aec207b35fb49b5dd9eab616093d709a4280510b3bd5b3eb02d8b5d5d75507a33d981cee236ded3df00a67e0bb2b56512b5e0e8b848a81e34547675a0bc88bde54bacd38062981339358f5d3b43e02a5e6bc004813b2c5baedf4449b2f7ccaa56c40889d1656f214b62d79feb3638b936315857dc31aeb6f794a93765dbe470179e4f1e17f0f452540dff1538b0409bdfa08197c51a92418c91c86c097f50b3b4981e293893b82a8fcd4c1e3a693c057f480f40ebbb8e0e5bbe72e07cf8d70d1966672560c79b9362312ac623b859f7c7e6fc3c680808d344c82946e1390c3f94d234451a936954db84095cad206d7a781a89c8fd6b49dba45fc32e09d5705717a8d1970f22cf3b772f4e87bcf483af6f25200a0b6e6bb250ccff9fc4c03aba70351910fd5b42dd293e999c0d3d514a698b27fbd689a62411a68d10255931f0bf940fbb62746ff45ee238a300d02fb11e2b85266122237119c0c4b37f18021f015eb2d6fbd6e58cd3de1ad5d8156c4692fd01132389a3df001b6200eee64b43dadb077fc8b0f8ce3d714cc26a764f8b7a3d8c30ea04bef18ab8f6189687f6e589ecab22b9f0349a61ff695555571a8de1eb2cc07ee3729e393f83c4171030d7321b65e80ea3cf71a1c7740b20400ba51c1ad3b3f122a4f83cbb1021d1846ca5c059477d89feb81d55dc74d389b4b7abd76d56cae32a9506c1198a8fe2a168ce27069d16a0105b19b226404ca39010dfa5da48e4b2ee27545c288d96ed643118a6b0916e459889d14a2105bd6ee75c65dc7c80b072f989e913ee159625eb230b2b30bdcf153092654f06693475ff7bb51d5906b3f9ecb74cc67f46047f98baa8b44a7542ddf776988762a6d66abfbc8dbeff6c3314eaeb9ac9d12971d92a689d58e8fdb0f93b395008433ffc876fa4ab638b874e8d404867da41955b068b59f990f265bfce2466899584d147bb16b96aa568e3ff2efec3ca73a8d27db14142d66577e4a0cf79dd634281bdbc9199e303b374ec928beae8d162c9c81ee96ddd9e5ac6ab35f88c46fb0753e7658b17253ad62039c6f843b0dad17eccc6b7bdbe6e7b8e5ed9a717b9c8c673e9370d21903f4921b85e982afe49396e5bc35686eb499d99cedb4becbfab9442baa14d200f6e8e7977a6db458b86eb143a55772a9a13856371a73b8fc846622ddd61d4cb4fb18d4546665692c91697b9719f22e9ee7d08119f76dd3267b8b9337e836e31894a07bc41ff9a84739a00f8cf91b51504ce4d6e538a214566a8ff2dfd355433fb7f1732c358a4409d01441dca68362e22a257ba5d723f7cd04669c09b47633c8b25ba49d3bb5f0b8f233ebb875999759aa1908368a33d068415394fa2e3481119c00506dd26e8b4e1c6d3177e4c3e75e14193bf933887d798a02219676bd71429bc1d10e176765f71cadd8e0fa549daab12193139a263aba7f24beb642d7b1002c13a759c30840978c4ba097c62331d61b281db82f2eeb29b92c0992cab28bfa9be1d52ce87ec06ef848f6943b6d2b7f872d19753adfbbbe95037d2d4d057872be6993884a612d24eac7fd854ea7e87548e05b595085942e0281cde4194c39d88d7f681dab247acf6d412885346f4707ccf2c5dcdf7bedf5dd002c03001d3bb957538db3ea7fbc793ba2cd37482bbbc3466aaf1344226fa3799515777dc0225e1fbb54e22d921ec33654102ac1e8a2db9b7bb4f745c317ea8bb296889d75e19692f86b76ee90bb266d89a2c09fb02520a1c581378226878b1474f50660f78c3eeaf3c260a4b3f30c2c23f38a1ae8a8ac7ca9222b417a796a021582890c817ef97ab63fbe5466255a0c3f1b684f8fccc7e6eb3c47a435fb8d1cc817421557bd5331a2862134aa7dabdf5e72c4f595aaba36d8447ba617722ec28f9484738ce3f3ff8c3543c9df0120c37f9d303181550530b6d36cd7ba0b719dc0eb47ad0c4c9f2cbfc1aef7b96e80d215f4426b34f6b2ffe33bc2a1ec878d5cedbca4060e06db58c129fb3798ca7ee07dee4914667374f93813afff3f772b6249bd649b781d2941f049832e3771fda8c5fe1d0c891c991778f63a7747cb88b3e05b8b884828885440f2cb7ab021d330bc4d4d0d96ba6e470bf7318e6e6157e1b3addeb2eab7e6b2299743fad1cfcf03fdf026213ce48fac7f91693d229e64f103702f3b1b62a033a274b08c765d8c3791dd4db8f04022f28f57ef4cd9bc71616db93d1d07d7207d3cfa3c83a873491095ba3acbe5da1a05d3cc29f254508ceb6476a4a1bdceeff6ae9f9f31450afa97f31667e1004329f9a9666ede9e806caa4ca1efb2351f7a1af6b378ba37507111c56c2e4d76b5aedcf944b9b6bb6fb793aeb088513836d475a20fc2ef361a1280e1079d5747027a5efc5d623941d4fd8d3579cb17dc42f13e538f5645608cf85ee5895c195325e247e5824e31ec5f83fd6d5b868036bcbf4f2c24be4100cde8178eaa66a034a357c9a1920bc1d70b238ed62e33cf0e37f42a49b8b2f9752433711d94a3e6ecf9dcea9b7c8d4ddb7a7c0058ae04bc61bfbf7a05bc7acfc1823d559be4dd06428cae8cd392eacb5896f2a6a4100b3012a8ae9f4f400cd1c88a02c68eb916021891d882afe5e623dc27fe1abd55d1c30f901e40019d65bc9325c10ada4dfddf4b59dc19731f2a0aae1385ac302c8f5f65aa73bf7a4582881b3d27b467d367ca2530b40864f471b57d13809a7dbeee4461033bc99819a926a084b735c70369f9e61dd347c97a931fa5167c3d51700d7bd79ad25681cf152ab9668dc982309076927a1f85e08e6f551a3f89c10add3602bb2f5e19a12321a0f204f7bb8d8ac5a3be4eabaf01cee1f687a29b7fe33e84436926005e4dc311ab8a44e581cef9c88638d5fb11274d8b1ef1e6e9064f409ac0b992d31e99d324031e09a1cd0f82855921d2d2881f0f5a4207748d10a403a04d465c008856251c91b0f3af112b6afba0332f621c6d25934a273234057948019dad33947af6d962e226857644bba37e0c03151cd3aa38293aa3ace9e2a9bdd16469e5634a4fd987f30612ae45d6a067edf7e51e56f10016a4752208096722e8fe6dcac78f34a975250869359c87ca8eca7d230b01e659769afe11d4fcbe4c964269734cd7da3598e3fa6f42303b19c450f983d1b973ecf774aff043864df2a2d2b31d160b93824443e4907bd8ca5c3f07c7912101510bf13b734a58d86887ea95b5b9baa27c38c487151b07d3381db4c56fe65b49abc9cc76db119fca588c8ece62a6aa28b00273867918bffb8796bcf3f0884287d8d7830e4c3662ac45c1823f34c4e5bfe6a98184e378fa9153b1665be9e9f0fbc610e34754aaf5c86a9f03798e2487d0c61a53413e0dc849a0080dce9ec665a04b63152adf34737d9d3e6d4c582d593dfe9c2d3ace9634555f339d3ee17ae4e295224c6f1643bd99d04eb33ac7416d041e9a496491c241ea668a806534e925a33f199a5cbf83392f4deffa37239ca8e07c7f171358415640ea4683fb80cfde05c6f30f3e8f9d2f15b8c3a0e0ca3b73f95bda3705858bcb4947686fc51c2fae87bee5726642c8b1a8154990961250b6cee87f9eb1a32b3657cb5ceff8048d9bfdd0a502ca5271c0c56e3b237a782496ba620809eb15efa34e0afe524a1a0b56db7c58d066974df6c1dd24a23b4a0a48bff6a10592abc339644316129611fe9c49c0f751c44f88195fff0353d69731c0a444e4aa0f7ee350e1fa18f12e575978e25beee04c2b042a0ea206ef51fbf0cc707e40a904a83e2a19c8904d3d8bea774934f138f720bd88871ced7fe8a1811a68d8df742a608849aab7fd90b95eb8a6970698430a3c720e05a2fd3b174cb6345ccbff282d7da3356d364ea437f859c959b9d10639221c6133c94dd357d45080d9645b1e804ac5fc24e751e624dd233eafd9a4bd46e45faac314e108ec77b940bca00be9caad4671107c91883dc79078ed9d242214bd5696484d8786fa7bb4b303263b8953a8686a2c1f07f9e306d844a4afe6c245fac954f8d667a09b1026cd8d67af985fbc8f969efa19f0b666fca97553a335a06e12df3b583f21b8583a9a79e8372e1a40c805c70fa402573beb132faae42ca492d80fc1257952c24ff524025716a9dd5461c89af73c9bbcec7ecdb7b990a85489c9d9960c7bccd8e745b880cc709e7687027cbf02702dab7260000df6df86ca3314693964a7147de667560bb3db2656ccb2cd1b7493220a9488305617330ccad252db6eb094b581c2a69c06cd773c3c611a33ce5bfbda061378a657d1f6c31a2bd1f6092ee403156ffbb3334cd49a67e47a5ac9b8b751f2592ebe512c3da705d99840d246dae7bce7d8154c2f35f9483d0e0939d1b4ced99b6a47afbd99a0d3c4a6036e7af1de05b4f4d2d1b32cb7c45d198aad21d3fc484e2c0307b89b8628dee366ba2e96b0c2a1f580a38255c17192792aba80badf7e978e30e42b346e8cc93474df35e2d0319199c0200e2f0c168ad9803aa83408ffd9c458802d226956c41913c17490fc5003e00e67925de798b0cda8d5eed7431f7535d0f1d0f052c5841f13396998c49a48a37dd00a4dd03a4d29561f9b311f436fa1e5740ec6ba106f1631d0e652278cac462e6afe78082a2d18d0109b926633c410153e54eb9beb1a1ad23cf6f1e577ac8f1b3546bea0c6d0d01cdb98cde4e356ca1cc2d0d0f897978c6d1a91fd319ed55d914169d32c343406945bf228ba2ffe2c0c7ca5f888b19f9742435946d01731c45944dc6d2c665625f0d6587ad2de39716aa244fccb73369412bcad4ccec3237ad9d0aa63009eee0c9b5a0f334eb31d73e415be6455d70917d28b6dd632fa973a1a9c63df2d45ecf2c4a921ffbd9b4235e441a568c8abe79927889bcf7e7ee417b2f23d3c98eaf770ea9cae755ab006cfada004af056026f401e18ab5b5937f0034641c92b417f59c627ccf2539f2bf5c4064767ab39ed6c01dcdf8594085856064481b430f8b2cc1c3d167ec61f22cbba8890d5f17baf63bcbdc505776797c635953dfb126624cc7a9ddffd784963de0f4f3b300ae5728085a8e8f2c2ef9618598eeb1b8daf5b31e6eb67eaa46f1521e250257b1e3188758b78a21f7f1144943b7e4aee978bfb1dc28a95ea03b6321a0a0e6b5f53cd1d10527d4176cf93525085e97427478cb5ee6abb5d6238462df3a2041b9aec01d99f20219a8f00b011409ca22a7909f239b1da1580b6edf8571af32ef8874c0a3780302b83c41f7ed57e30c5828bcf4bd04b8d9b293a31cbdf05516803134eecbc0df37d697382baaf8c66f9cb06621c23da3ccd14edd41428af45302d0a0ca353fd16f30f8f318c0fce43a6bdfee2c330f8ff0a80b91f3273e37b6484baf67a1a3945a394b944f3d165931ea5a4858c560800af2eedd82e9c3812977377c6c6339bad9351e3877cb5864743b44befb2a1a98708016247f8174b1413d82efe76f887630f660ac3039f4101458cd25b5c6a05e7bf31024c78d36f3ca53b5c91f672a964956960c775e80228c81e73171e5ca34c2cfb15d98f78b270a93b82191a0ac5482d882884c4eab21ee7f5a3f3158fcf6df191a93ce10bab165b5cd33eef7b3675ff2b2262ac5f2fc9dfaa9f5846e0a1c8eb23ffda8e459860426f1ee2c49a5f4d14b62d80d48d6400e6a42fef3c77862148924258fdb7fa4b0be1dffcf25cc8ba475c7d3a78f051c20350c4be802608ca1d4fe8afa390bf45e03bb94846786e42e03ccc7b7f842cca21545df417eba1e7469273ae38c4e2117f81f772d1acb24c4acf0c707a600654b21d96f9ee1cd82577d650c46b2fc243fc8a92e9c4377f25c8256b981afc17281a2d977578fd6e7e5ad2d5567b2464996508dea7544578541efe250c95fddbf958a373b80c57eb1d04fcb13b14cfb3b5b8bdf76e83713d1a4c320406af41fff1ee23e906deaa6c0041f1d9adf5fb1bb56237f607866b7345c6557bc119774f7dddd676aa0b1efa4a5ba8f10d7b6741c67311991d239e2318db93651af27e587d3f5604f5a0675d24b71e931b531c7245fe27d248d5d5ae8a9a64d62b341b039f90ad395a99f7f014a3daf22f6b0d24c1a38686a055dcf94b2d8572cf6d382444cd31ea8af32c1bb011a98b8b69df8cd243511310e481bfdffdf59dc9b4013ce32b72a29497f527c0be9b2393813e7b55cc0f5a1d3f9037950fbc53af8b0c185c411f5f880e0deaa51287a719fa18b82bfec6be3c1af461ac16f981cfe7ca86c60f8110e3ea286fc0da5dcf294058f73e5e3620cf552eb074a766e2b054493d12341c299afac1e4037213af44a8eb785b2b4bcf3e081c49b9685d3e7e0c0b7d9f4bb50861e4e97a83216cd1ead54f12f894667136d05abcaf09dbab318ff0904bc0368e3805ac82d7df57b57a76ad048f8a5c11c11c23048cb810c0515f63d8e3b960a64fc4d09f8a491301f0eab4cfcc505a86df794c892c095af8148d84b48d4635bf7405dd5ae9f35fba1de27fc18209f8b924e0d9be53e172ae94aca3bb26dc07f650c12614c479b1163dda2d08625c1881272be2a36c6e63b6412a67d85d9452e5a126c1f46d699b1b1753ea684cdc30a4777a4e520ff0610cfc484ff24f4d8c29219f33f660737182befa6e623963890ff53ca1e3689e9a0509311ac7ad383a17f95a6343234573f5c62970c9c3fb948fea27f995fffc4e162b55896ed0526e9fe392c077da2082c68fef00e433168ae3ae8fb6109908f0ac9b6fcd409d415e45375f0e6213e16533d626911fb7b5360c10e328c04423a2ab6c106f94ae59e35bea3903e235a29f4f10cd642ba33b09bc7a88e4903ab6157cac5b255e161c43f252ea6d26453128a18f4cfdf3b68e8e7cd61abba085f7c9d99c20f4b9116992f3eb36331f599f6845c4a8825e3d8002c33164799075bc1f9f3373ff49771179ab89fc7aa56cc60af0d474390761114c38d71870fed3fb9462bd63c736966c78b45263d7d26e17981df2e1baaba9dda2b24d2bd5591fcf2892f9984d128084fca1475467d2d1da996848948c52f8e7de949b9e03cc46612ed03d1dae1c704d17998771ce7af4ef8fd1188882497323dae7136732c6332217bea2bf2481ea3b4e8a7cc0596333c5a5a274e3f9e29096d3ab72b3fa231d8415b2d65cf362ea20bf43e3141e62f7c3312b119d79d365a93cde83494ef914afe180b7178864592e3fb6dfbeec3fac5b8bf680ab6edf411335a55e939597d0692c0bee89402384896516361ed06470a3e325e9d9a4279a1cfaf0652067a2d413f0471c4220c5cf8e8291fe8369f6ec633c9f7d9477b07aa6a7a10bd1da89565df506f25718716080a2a123678bd00e1557fce34d72793f8d1885c05f00ced1cea309cc8f0c69e8003675230a0ff32a552f8270cf5dc81afbb35ecb5274655790030677e8dc96474a0ba38e6661b573a873f4b0d7d4f75b9cca52cf29005222cc8211e194710899691afbc33017e9d5948c1cad5592f100ef1c603d7aab6c80e486f191795922c5cd3ba61dfe254adb74e76fff95c03f6d2e423a9c62298941ba8c1a2a42a1411005f574783983b6fb1ec8758318f35588fa83e8dcb112ad1558838ee0693c06bcbb1a7ee5461f882c292bc6f415ef068a64a889c1802f195fb3db1526494436142a5d3d894e606c4c33917721928001a5ec743fd55714cd366a71774b0df8211fde296c86eb9d174b1347cd51ed7fa73309e72edd4a93e3502cf31b1bd00a71e6a0af65dc8e4030e7d4a6ee469a0c67a9bcb2e24873e972ddbea021d65ee11a67cd296a4e365a0fd13e1ded90f219181ad20ea7d3ee2b6e773fee709439beefd52efff063776bf24e57f1cb9e92edb54a8bf904b46f70efacf844c4415ecc7413bf5711a41c1e12f9044b6a882f900facd184f0abae86dfd84de4169f4c508162492d16d858ffee2df7f7c4bc6ab0b45874c700c4636e706a283db902128d610e55e1dd3c41c694bdc988cdbb592f6445495c64cff52e3e37235c3de85204f6775c82a0810f39b0968993e4b7b70480754cccf08ce92770d60062cd4fd68c0dda4472beaf89fb2cbae928892146d4643b8fa367f503ccbc0301a83c190aca98f5c878fac41bd25c9149e0cb4c2b6247dcf99f859d307ed7bd2af9f3988b1079b751967558c42ef424eeeee43e5439a00d16aa98f9a727c699f033a7cb2618be02738c043186eea545597050865c060f5b0a154e494974daaaf0daa8ac4c2cfe1dda1074565c41412902e8546d9e752d4c352a0b4b5d1f4ac3292b687153ad9fcc93b2c3035532c26a96b2a7e69c5f982dbfdc2655cb3cd1f889a2a08bbf883980fc41b454b33572fdbd6d9ef24cb080a03f8bb92a150f9a53c12f4b393051e0516cf233cb9583abe0d26385acbd5bc1e9c67755112a739b9534f4c00c9a55c6aef0621824767528b11e3c066c9adb185efe676fb347c2a860ebe8e58af54d300a9b304d17b38fa486ba36bb7b61cdd655091451bf55d2143e1ec897cb62a5d4a3456361824e428517f5af97a77f69e580dfe6b82842fb89d0c49bd3c5f36922ea7c890c55de4faf2e7c02282c159d5734bdc4c45a84f292c721f2f36461b9d5ef6c107663cf30426d4d1f3160d3f86e101dc7f1642b7b092e4c2ad91c7b8408935282197bf974c975d201e95b79634e5c33864eb958273f781cbc5c6a11fccbd9a306e048c6d425338965f9728ef992a969720747325af16b48884f3a87aaf8d438ac9bd906e97b82954ace7e2ba378db35179f949bb09854e7705fa1fda630ce122e880b457c68670172050ea4ca02f2be8743135c77d9e740bd26194ea78885476dd6ee42125b30eea87102aeb10c3841073436a7d1402c69206f4f0802f9baa8bd67f39d0c27de9db3a5089818a9d2fddb2c348a2232dbf7daab43bf5cfe4d2f73f32a2ed5c5fd29e4a36a25c8eb6d07421c6de2a7f485b98f9baa861050cd7ccbb541368f73668e93208312138a528bb2336d5671a429747517c712e452e0cac4c18d9503b5718c8d97d6eb3a5b8e5923ba106039bf4dde3ebf2a01879ee3b668d9e11ca3851ae06bbcc74802c2393c0c7996907342b4ef20606cb52a39cf591b3fe72d67a34fb3f681de528ed701f8a40e0359db72be58f12c337f8bc7135ad3f3f15385a6148d2255d7a1e5af3c5387d494c6b97772bc9a7b974c46576719db46d5e8ba12e6c15fb3e4294428170d9e4866331eca07f2872ae69ecccc33456ac6855200a7173fb0aa4fa828066652ac1827ea4810e6d5daa94aa30e134cb9c841e6d85d961c2d86c38019dac91487936f77e5658c3234b54addab6502b8870e91df3b38a00b3e41417f55f49b9a956ebc265b5722dd684eeadb3ba279d0222b78964d9e685846ea4a80ef7d10f575c8ce21b51a29e81aed1a935341ad84cf4ea799281e035fb2f6e81ea599950509d224b8ff372d1997cef86eae563bb0a3d887a737045930675553f9200e48c9dac89d21c1a154bbbf54cb9703f29899032d6516e9dcaaca79c75d2dc761816fbb6875ab6c22fb79b9917451dd6bf9ebf0ca8ed822489056a3befa110cd8f5c95225395cbf0157bead8683939f48131e5e76ee3528a20d8323ae619288b8d77dea8b9964b1c1b92d2c49c459614ad50190616339b8281391a5b6bee82f2f8d098c9bbbcd9947caf711366e345c126f6a2ed9852eea45175ce0c0c07d089e70b7d8ccd3fba09f3363311ed55904aeeddef3f00dc3352bd18e24c433ec658dbd02f3a013893885adf7b96b4e1d899bcc91e0806790dd4978d8c59db5d075f40e5744f576255bd7816d8217cfb58c35e4a16f4b1018d528fb3468b5f4163969c78f45a0d2d5a32a281fe9c492870eea5cbc3d9d5f151380e46a383add0c5cf1ea679fd026ee68f42dbbc53d973cdcd530cb381aaa910daee7822e423564a6a757f0160745033b92854ed4133523be8d1683d369b6d980fc590606b3ec3a3b201e6fa64b4b1e02d3d2097291e0f54b4a059519a7b1a4347a3cb4e288f8125d357dae726638b95d4136e5222ed1cfd3fd5c62329f48a0fea5a1553a2b14cba21f55dc969ad86f1d28246d2945c719acf5c590412df332ee4e4fa1dbf6075c46b058f1cd5d873c67333c12c91662062fe879d6aa0e4fe330ef1ed06c92e7a9ed6f74d8d704b79fd37f9343e76962267ce9b014d51604557c3cf429fb43690f4891801fbb738a2901c86a64fbbb08825c7986052786786dd5d414c32861f2ae4ec2e0d0438c6a146764632a3dd25e4e3791f9b3d1e35ae7f4ad6cd17f0e004475781549a9c971954390f3ef0b7d310ded069e6896381ee5dd8183f9d69adce7f222380d918fd70435e5088df77150ac73d820c7d353b6a9f9a8d2a53793063242643a17385555e0eb9163b147fc9be7e208fbed7406b155c4336afc7dcc7a671a89b7daebb6a8574a95e3af8d73b7ddd688df10d0c284a0afe6a64299ffca6beda704ccbd12b24b6176eb888cecf2219c7b0c04b495b334cbb7fb2d82648229856f71419bc62f494d51b6dc9937caa690394fe598f3182d11fbb527e12b1a8017aa004f64ad4dd2f9721d88a2493abf35f94b65d025107d0d1c7a466a08e90ceac13eabee18337a8869507fd1146680ae5c66162aaf64134957c7592c485eec09a15fb31a56a6ce48c54daaf542b2878383b7687b2e63a7f411eb805d1700bfc56d072c9a651448073615590c70eaacdfe158067512cc79e6e3261eb13d55663b82b65b18282c8508dadc1b36776b196890e47cda38bd521ef61fbcaf201f432218df35bbdca3be2280dca04fbc575b5fec3bfb4e45505e6f4e03dc5a2295f2cfdd929619c3a80a92d3ca9e799cdcc6ddc2bba412c43c568aefe0909857824b6176f8728627b898e99b1683b124c65c82bed4cf8e5f177a87ad263861a0ace1efa56d171ec5c5bab5a7d70b4b1e609e70733ff1a72eaf1516202ebbb2517c1ca2a5c44f9f7e72282127882a977cf64faec89a235592698c73bf6dd6afda36cc6f5a9f31220518e956b3d3ced3f35157854c4925006d22bb394e17ac656609acf1f69b0e8c6ca12d63b580cf1336f733e34ce992a713ecfa3184e506c80be221e500c383112008750e509a883d70e0875aefb2029db9689a9048ab9bd9a1cf5955682669f85e164ece85ea1b3296e62edd4f22654fb26bb4758339b66f242faf49621dbb644b5a6399ca344099d9fa48e17aa0bbabc6f9f2c6dd1147dbfc777f53df961eed49ddf126583585efe9023743f14232b1347a183dd76f3a45c5b0afd89896bb12d5b702ebd31061572c10f894cde200b654171027b610c193ebd95e82565b5538bc00f69769f409b40709f969a6530d984e5ea22e0f689912b09cd6f796954e15e4e91fe09a862fb2d35fc38a34dbaa978511e74d0a26011b121c07ff2dc1172a23ddbb6303233f2ab7cd7749f54ff2723673d173225d4daaa092af0b4158830397061b2b27565e1ea7fe2dd456994f32f5690afee26390ebdf8dd67776d0b53b4634bf81e2fe734580efaf1b8e96d5dfcfc51862e12273270167a2c6a8ddf808f4f8459eeb8c0dabf70f39e347a38a943e073375aa435a8b6e69bfbee85320a82952e32d3839f56d31d647bde2c52422dbbcafd0d416c3a80956b1a946dee704085f4266b0a1ba43d043a1a1f9a5a305c15351d9981a263f758ddd46dd4f5f7dad545b35ba6d52a844a7eed36a439734fa8ac5fbd02c2071e99211c00030b60ca0084d1bdee9c330ad85c00f4001e6d6bf884261f632ffa08f661bf35db96969a889051237bcbbd777309f508dd0914239d623b3a15fde41487a563d4bbbd77637b775defcdfd60c3180cb3d664fa51287aaf77d7bb1b932fd5c5342a7805bf53d74d267b8750f94ad783e8dbe5556f5dfeba3eba4eba56377bbb6b92e3e17615c3ac35997e14eabadbbbed9aecee8750c117c7a651c138f6e7d5d464abf295df2136bc826d64fb9b7cddb03b664366d1adf683e817ee21fa867b8c7ebdbb3e62f2752ac2a347f96b4423a625d2e57997bd2eefca7e5d5e67afeb9862ec2a81bdbe1bdac8df65c3dc1f3f44895e5f595c9c9edecaf920f27e9663ef2af8266fdfede554e075f1a86511fd3159723ea8264ab668717c1aec42e52d4ec3f2abb46039b6d1295a5bb438cdca555ae0d8a92156b00a8ee91475717f69bcbe2a6f7ae9d977baa961ee53b2c6a12a571c62217682435b42e17bbded1db9fbb7b82c5fb9f82ad7050f5c02eacaf18750b98b3777cab1727c1a95bb68f17b9a16bf2dee7f4fc3f216bf1f82a5850b7c67963ec48902c22e30feca695c541bfe62081bf00b5c6f8bdfe4ebe2aa1cdf95c764adebb29925d75d931c0faa99f9af7c0896ab5c72a71cffca698e574ec382af72161617c72c180fe102636c3b557f9323e7ddff342dce72f1f1102d70c4bfc9337366aea8dcc7c8d0468ec72ca52da9948f3e28d38629603fd891ac795506512a67c823396229bbf664a98a41a9a44962cbc21695d4c28ecc17f9932f4b620a841ab139383ed6c7e463c24c39312726873227f644ccc7fed4253144512cc2d86861478290c8217246fd15521b65117246edc6b2882145f4758f75aa5fdc920c6c1879ec44a15e8c883076a6e2651147f0a1961563376e1cfab8f9aa17e56a5cafdd5267bef4adeb32311cf3b22ade262a66091c8da1666248bba34b84b13145fb6e188e9457cf7c8142cea0aff5f2c93ee8a504ebbf7e4171f9cc970b27d72b93e0e8d4fcb513031b4a9e70e664b90509836921ca5c4a228c4a7ece0de4190653155450c8533221cfcf9c1a8f1d003aa6145812c8224c66b060c8510b39c6cb0d48184c6a000231d65c3df345093e3972b561244f8ee461e1c99949266a5e20b64c0c3420a6e6656a0a24301561e506e6bc0c19b173c74513368463a7a475b16da2304f368526b5276b9e4dbdac96b4256d29a93c6273e1505a7d35166874935c6f0f36543860202f8bc82132070859e3001ea6c6b14d727cfc04b2a625130bb547faf03411190a9b086661d60ed9b2e843968caa92216710892a18a88e3d2e35b0d1464b53c5228df1891d72bc8b0c1adabc0559289040598749fbd8a64c122986c35196d8ba381eb24ce32dae064663a634d6fbe2a519d85035b34d548c14065bc91c311f1a04ad5eabd7b5ee9e3cf29440a84b72d7b48f8d3eefe8e3a3ca99528b3e78a6009d74e80f5d45ff4c54979460c3b884dcc74153f3c3290575c91591bb7265b3edb4ab8b4c14bdbcb1f796bb2524d8f0cafdf71160e3b2648dfa0dcf5ba169337368d533a07553c919d9eb89e4199fc81816976057765d5886165556bd33131533511876591aa31ad81e206a7834cc77611bf3a5071c9fbdb65374eef0d2325ce24be8b3ba648c7b56ef4dce64c81821cdbae488645765a2228d5844cd5bf333da8ae1409bf8bbd7b78b75915c37ec93899cf1913e1fd2d19641d2bff9e678c832094f2cd4209d7b64428166e9922e0ac644d1e310e110f349c0cca523368c3d618c0d1c34350ee01181eddee9b7399a30a589053943e659ba71a6b267573681bc11e95abce88626f0d68899043e720ba8f524d2eb4938147dfb453a463a7612965c0244b71fdd2f8b6eb2f5b66fdc76356d6241a289f96419a536537b943342df5ef1862d19437be56af4e9294743db4626194389ac6a9a96b56b59bd1addb6ed35e837bc4d99b357ad46f6d0bdc9968c512d2a35496f1039ecd3c890af44af9b8659c7cea4f36e44f6b0d570e863b368bc62be2293f9729d0b5dd355f944d5dce1512ed1c0d277cc0e13e67b3daa5e1527cc37fb56c819327296e87eefac6bd866e4bbe876e02b26422126f23562f6f0c9be07760f63b7b7b8b83d729c8f2d6f585a2163843eaff8a25e28f039e964cddb7028a42787dbad87b887369abb6fe7ba6fddf6cad5e87e3d05e85444ce88b9eacc977a9f1cba538a89a2efeef49928ea44a697c3514262c319c47ddbeef6942be6cbfcf614a0f912f3f6530ae40c99b7538ec66671b766d775af31dfede4add3e6c6d5d8cedd9bec63eee40c9f522063580fdd30e58a6c552443e89e2c57b161ac6a6dde2bc6236f8fa21bfec81b0ebd87a4b6d96fdebf7ffde1d0a6e6efdeb4d8fbbc57aec6e71d7be7c8193163de2947c3ebc9db75514dd4f579fbd25b6fec920dacf53a7f5d47c06579de64e27d2ef1de24f0bc7b21fbd0354dfbbeef35e6bfdf84cf1ef7cea6660e6fa11a44262e59f34298bc4acea00fa9f6cc85661a96216364af9d3bcb4e391a19932caf092b31f19c3d7b942167c89cb964a793f220afbd86ccb2d06916bac936335f58868c41e911edba76dd38b1df647ac3f3c8b15e77562c63a63b4ed98436fb864172bce24d8eb74920634c26b9df37979439a031a714b0886152461f9b2c420e298249e3585da04523489c884a62f013951811224f6c3373054cd9c694b3bc917202bf028d2db438b5162da24c8b162c2c518685656525caacaca8a844191595d329ca9c4e2929512625050525caa0a0984c51c6643a3989322727a55294299548a42843228d4623108c32202812451991e8fb3e6ba38cb59e17653cafeba24cd7715c94e1b8d8b26db165ca685a9645992c0b85a24c28145b30ecbaa2cc75595694b1ac5aa34cad94764799ee39a3cc9cb145467e5ede486909c104e8a70a287c789e48b2736489247472884821b26c92c515414b9848f12325ce1563d12bd30341a91461bce28a44b04215991e063d55ec9e2aadd78609c33353071a4329c5e982166d3062c3f853310cc37eb09f8e91e590962c8324905c226be426251356e630027b2387c8481f1313132fb52ceb9a40b5704c471965bcf53246a5ca22d7371614f3d496b1ce6add1c88b0b5946d620e43582343b055080707e41a56a1cf4c17d4bf19b1218dd9b558b3cc14c7999219111acd71c26c745249295529820d9f290a063da57f31bd2813e7fc8dd6f5a17b93fbdad48c61193246fdbc8dcd46422ddaaa181e949cd5a2966559169667a439d0faf29a942b298388af51e6ccce224f6b725f0338808359b81034777a16d19158c43c859846e44924cf2b7c4cd49c3c988e649922c655cb25661892a3e4287e01e3666640cec9040b47004a120435ec4451038b0832d87440822c18c18a220b491822054cb010638cb2bb37dad87c99b13b7637a594524a29a5ddb17b069452ee1b1762f63eceabf21095efbd76ef09bd61e9cd25a0fb09c342144d0e4bf701239f4e3b03418ce9ef3d39dd996949890a9ec8f5f4c5fbe4f4c43be9520b96d2574a573979a9f4caed78711baed9303f14141b504c9fe92b5d7b71633295dc0fd4647aca0d6f72cab5de5ea484348b5cc0604fdebdc9f4dad47cc232640cd3eb0d5db2e9338d82e3448d6ebadf090e61e412e9c689d2bed1b649ee878d3b0fa1dc9de3be594b0303857af1827a1e087ae1e87b41a905bd172f6e9c29cf761a0d8c9b0dd7d36b2fb237fcf0c5855c476fefa581a59f3032f7d0bd4b031bfa80912b07235399b78d865b16343747727cc46e2002d01460dc4004202964f93b6d3802f7892c49c581a3ac54ca48230b8a3e8d52cef328776f87ca4f1aa651c19ee6554e3bc7d1c44e698f9daa9a17d6ea795ead9ee9a4e4795da779ef3c4eeb3c1c59f4d1e93c3a55ff712a1de90453b92715fbbaee386155a72a27e26ab5a2d368441ad9fa5315590e3c5591e5b8d355c141a3824fafb59eab1d47e34315444db3548612c98aa84a011d38ca4e4e4a99157db5d6d1886447b556558761d69a4c3f0ab579efea3f6b2b0571d0cdf36a15e1eddfbfaf6a15f7b0b5b31da992ba3a1a8d46231044a12794aabe4ff483eaeea24f54323d364c4a8aa9d4892cc5a12a77e0c88a4ea3118ac99e946c6647d64a1c22eb6d9b8a7b941908c2714284aef34cf5dc8e2ef33653ceb28c4412812392c7ed40b9a97298060577f7c07b60e76d998769baaeeb700ff01e281a91482452d7913acfb3342654d7ed309d8b3b4c184b795e77af13bd870dcda277dde8ddb59ee79148249287c3eff624d2f72e7e64459fe9a298ce75dbbbebdd5e2e9ecb36d3fd4183824d9c0da5d96a48f32b83165f70a2ce3ee3911b87d9bdec72cf3e10147d190e4dd144b1567bab918636b0db87ad4ba4087e66a6b86061b32cfb68a3413c5b20e5a1e5ded9ad9769efd7d6b2eeee5dcbb2cc46f4ce3dd3a8956937d432dc63c4d1daa36c949548e006825a9659dc0dea43c85d7b86411f344cbdacaf69a73c3051df1eb8043c466c27e7dd4d33ddb3a311062d16711d531c87439baa51aa510f8737dcbd1ab0dcdb02325b9f5a9665f778e4d6de97bbb5a91aa59a46b9db542eb4a959c371cba1909ce1117978641b7a808b92c06176a2b089e298f0de81af4cd64e940ef41e078410e00d6d3278d1fbd8d75bee3a3777cab2efa60b66a2ce70e33851deb6813eb64d2385b4921dcd9ef49d466f9525adb7eed4022d32053cef74020fe2eddfd6ed7d8fe0d69d4de313f813f8ed21963f7c02b18d981dcc0e3ec3e8f7ed8636fd6dddf67e05718fd2777d6cb97bdfd062afd495e8bb114923913a52e77d218fd49fd733f4ee3b9d2fa47fdf356aff99b87bdb4874b2ef814dddb9ab8936d2a5fdafffbdf187c1cdebbc5028cb361105414a3abde1e85696652f1da4b7fb4e0f7e7be9f6f840cabdbba29344371c611f5bceb230fbbe7df47636d93b78698f0ed35b1f9ba5f7e8bd631f9b8fad1d00848d9cc1143370828f2d5b40ce608a194c71b0061a8eac659444414ae9290ebb6f9556db3d76c39470889146f560b5f5ab187b2aaf44aa28f444d64fbb77dabee3f495ef60b94a47e9874f1b5ec1342cf766da0bb7c74feb28d52a3e59ed9eecbf6b9dc9844f169f729cbe729a130d8eace1d0541fee2152a9448f128552cff3a84769ad59adb56ef68638b2cd40100c5119040f5ef35431df907f1cf81e7d2822d2e9f41da313dea182a3cf36adb7793872d7fd2405e524238128267b52b299c892ac5da17693f1047e74d2cabfbb912ecde923f0e65059b122ec43a5cdaddb56aeca4f9765bb3954f0cabd4bc382e35576e52aa759c12a98e684bdae533e545127c7f88855953c8f69a284e0bb2190fc715c08a4e32cb5f6d64e6d3be9edb449a7a5531c8a9a36f77a8ee34eda28e57ef8fe59ee5e73f6961702c99cd55eefa9c3373e0d73d7b8ca719d394cb9136cd35c49b471b7d72c672d77bb71da6739daf566efbacce4c9effb6e770bea903d72a14dc665dfd7894c9dac36fc914db7131c3de68ee3344dcbce5dd2b37b83c4ddeb6e3df07e3814619bf6ba73b7bb373ecc7157f36c9ac33e7e009196069ab76d17512cdbcff4774578b30dfe7b87b189a216f7681bd2bc61edde64f086417253d228c3dad4186b1c961e47f4deadb2ebd63fd87ff8068edea3f5bebd36959c0fdb1b9fb4f749c3372cb5d76ebbcff2287e05efe11ee06ddf90721ee79d50d268e4f5d6dbd6ed7d1bf5d6bd511c1beb9e7dc320b96d08247b07ad873b7a20f9fb4c77789b280f7cf70f6313e5dd7bf779d47adf4bd2edfb3cba7db4f33c9499eebede28d5b6cdebee9dd2eddaefdeb0946a1dd5a6b6d13e958f2d1f6fc1d0df39cd0b92696d8be93bea7d97dcbd5b9a17cf8536f3f346948eecebedb52e37ca9d62fa10f743fda9fb0e949fbe43e529b7f76e48b377eaf0a7f23d05d7efd486dde3f70bb4debd4b6b101fbc537cea4ecfbdd60e9f72a0fc741a144ca375df431a3dd605c9b5b35564b75b7ac38aed462da5d45afa7978d451da7d1ea51ca59bd5a4773311a5d7d2c7a3ece88f6ed6729f9974fbe928df7142d9918225f8a8a23693bb386b6d7c7d77b96bf6ca0e7ff5745b7fba33fd598cbbaddeb54fedc41dfcc87e23909bd67ea6439b69efccc2bba1c69dba7fb728f7f41a84e68463a3fcbbddcd91627f7a6bd7c65d8dcbc20c64edf494d39c3c1a149c929e6ac319b50182591a24bf344fa8d0dcd5b0de369bfd13b5bd5eee9e69db7ec8b09da82d7236d43b098d28c64a1ae91193747bf60377efd96bd753876f70d8c391defb7743eef16ddf1eaeb7eaf71f3ed4537cb2a7f7bedd933df72db3f806777a79db876da8e5e11ea25bdca3f4ed3393bb9496b651a4a40a8259b6d18df39e7d0343d9768fc361dd6a287ba8de1f39d4358cc966fa0478b8628ab9eef7ae7613b5a139f49885425b88f36e2fc7891eefdd74b14e79f8a10dcd75bbc6dd1ee063ce32108719c8e049f76e77431eb9b321eea6100e79944e4a38fc91edbf874c3814dd8b273547ebb32c3bf8ed8a0ede1ba21fd9e3eed9789b6d34ecb7cbfdb8c16d59e761198e1385c30cd02c7328c3a1cf4c798ae3478e8f41fcb0a4f949f1d988c9d7f7e9d58ae5f0d73729babeebf2baaeeb3c7c5dc44d527a123d985db71dd2932befdde34a7ea661bccbdb6818ee339d92ef6e4da77870d8466c628a4159ca08b4e19015b6626ca27866628c266dd22ba48dd094c5b044aed6afc6a185758e8480340c860384a988cde9db68982cd8503ee1c08ee1207285fd66c523ca707986308f4ecd03e9d4bc69e60cb1c252175fb0615cd2d6112bc051a2042272e844d949a58ff53a64be504bbb8f9462be08315fb29989f326a6200b64976098b526535c92b5deb488fd0822c2480f84423f3a4513c0c5a01f5186cbf4fa44a04e94b1995e2a1165ae402f8970090399864c1fca3164fa5076215329852ca74842a65c963ac814479fa892a9f5b09deb126fd7bc3f36943a2af43b3586c416fafb043b846297f499424829268afee8a09a3cc844a9a53a211d580b63132575e4a9951b1882944c21043948304308da27b70fd044054d54c7051b4aa06eaeaf4929e62402a353856c222ccd3e5186abc2a2204e27664e9461c9fdd93d6530719d3913d55fc9819539081334dcb507d98e8368b72692b9c444293175e6763bae260f620b1aacfcb952d83851f28a896a30581994fb73620924a1882d7d202020a03ec6e44661dbe78a4109a494b25b36edee96e10e748724ace1880db15c84d6311912369446480dc417cb23c8f4024654c7c49656c2368e4e755eb9810d634c0d13638d189638606b20a22e53080af632c1cae7d0215afc0e98e4013be325cfcf2000888619000160dca0ba005a1ee3383cf5500eb18e43df7ed8f8882d3387088a066c3873a65c71df68e623ca649f8fc9e1f34088ac8cb758394bca63343d4610dfc5637f8fb1616450a772b827ed3a3cae64dc93864f3964dce53a72b80e39dc25870bb1e1321e5777795c692ff029870e77b90e1deee2722101b80e38764ac33a5caec375c0425077390521b3f65006659f993208c705c6f8c696f95f20266a3e07193776aa050e51319d9a2c388c91658c1cca1d142a0587bf099770682d883fdce1cc0396c31b96475c1061b55010190efb584e177ca8b0c72bb30a071c7050a954383cae04704f9dbaa7c6a71ca994000420800bb1e1a947ec12464b4b0b0cd461a00ee34202f096c7d50df7249fc3e32ac63d497cca11e332aee386e7808500e0375c0616c2040d007ec385d8f0183711a0c88c1d260a35800be3dae8d4fc0d57755b6e8c8bc3cd38dcb852ddb88a71e3ea861b572d37ae60dcb8ba6ecc03a83153b7621bc6c400f4c53200ae0dd7c5ec7a9eb042cf135638810f0c0cc330152a8cb1d1c34f8c12248001a358ec819d748a9e3040a6101335491e2801b69a31cadcc0fb94353632c6943f0d037e8a5aca020cf96a7b289bc8f31207d200db470fe5111964c5555d49ae069781c4969969c17af77bbd46c05bd7c8106c057ebb427440b5fa3ec4aec003a1848a9f6c3373c5272c6f39c87ca9a71cdbed4fa1930e4ff4791e0d730a611ddbedb7db875227f450e684b045c2d67b8b74f7547fb3e2eea9e209d07057756abec34150d8e3805cc38a09eb71b53d443d5b6df71e9aac0d441932e4c981f842447cc9260b268e9c6123a708220c378fc8b3a661e28a1447b2c995a27bade65db9d3a91bc496f9eb4a2457267165114964a2e63c42c3b29133e4084a105b72e8ca5c8361d95a12a2c2528cc3c6445d2558f9309b1a0c8a80c4bbf6ffdc4a7c7a1082247890a80286945236866158cba6dd2d553ba07aa28ffccf44c5989999989923e4e4d0443125f24420030e7549b17a58d775cd5f3c72c7eb560f375986cfd20b368c3e34354772297354a2842a72d330810d314a4513ba20a2020c83a80917801f20428250980daaa0e9a805d49452411c74c5c1c1c189a886b1d13895cab91a89c086126789d9b4e79c162a478e20c6e80b4b1c598279491c216cf8bf982d67cf77b71665a62414d8a9c4199ee059459410d8b9842a46a0b3924300421474565189108ec0596975a3777ae0034728b2babaf0269f6e5839cc40c630264a7e07497a70566d25923f471ffa5b3fc1d573f55cb5d65a6bad97cfce5cd26dcd25b5a7f6486176b7a58a735e2eb55eb556975a65b89c40c7d2c9eda3840db71c755a4746ebb44ef5913b5902c9242a974deec82676e4ce04ca5d8449c4c2049a4016106850ae3ad3886d64a241330434880659249e39ada83ca8a1cd933ba886a7791ed03ccd9306ec48c67ea8851db17ab6755380381ab3bbad6e7aeb5eef3bdb6a2b05a84522c8735a279d2b03d9c2575b97b6e0a433516dd5478aa38c98b47a685c92650e8fb8e4013c2a0f2be877af29b6cc6b328a177cc0a7872592f8acb4ee001337f05969549ec00849f05969b5e7a70547569ae5042c663064a55d5bcf112772561ab60149c1b3d2b0adde134d0d129940e4089e55454d948cd911988004243b44622044d5348a61d8abc51e0a65c65e6f45f7fb41cb189edb7ca83ececc8a6d990b43f7369bd1639ac5a4155c8dcad19076648a17ddce46928c5d912790b442c6980f61184603bbee0eac0402ca92abd41a4111180cc13ccc091802e8ad6953656b94a996dd588f6d974e1486d5502814ba7693af4b2e4e1476b2b01d65d942a0efeef3406fccd7e79b9614b8ac895dd69453f6b02c7cbaf1d97362cbb22c8c7aa19a2fd3b22ccb0aaf8ba27f81cd2dcfda018cca4a9bd2534a69a5b23695b9be6fadb5ce1ad82badb4d25a2b0cb6a2d9a78956276dd95d67d3ee39273eddb8c2be2c8cfe4d9b36a530daaa1f9bca6682165f8419a5216d02413fe9a7920833833a456fd109058c760eae4b42b752a0dc9f411d6d6d7a051b5e38b92b9389eaba24b6583d9dea3fd7ead3934319106c587d8e4c597daa143f5950cb8699ab89335f84c064bd62a6ba02a9c0ca26dd6448ee954ca9414d727f0275aa59a2b0a16c02947b7234b02c9bcc972c70e68bfcb5a467db208727d8303619126402992ff4dd6f988a598093a392152891c3d81395a8e08a091357f3a1b5d1b9c646c300d17303096c189bf467a6cc01d23038ab6e15489181f851d3ef1f551d42a8923a3c35353535353535353a74e8d0515353535353a343870e1d3a746056a5d9d1a91361d69af00e150cc334b15335db6aad12c73376fb7d1688873db1a7fba14caa4e81a3930eb2a87ce5a39b43e58461262ae4a715dee4ec212b6485db0f1277214da35aadb5fe264b59bf6119f2862ed946c3ad6eb20cccd95e10dd90268baec53967ca34cddb9626839fe91f7b4513a5eb94bf5e1a581819c60c7dc0f0ace63616154a555cb0bc45fccc2469dcb56ea41c05ff403ab7e3bb218cfc7ddff77ddc51fe5d7b7139eb9e4e180573971edf1e46b724fadda2f42b2cd7acedbb28f8a44bce87ef28f8548f727bd23dd57b2761584a4a4acaa9621a1bfe22936e714893519e82439a9c72edc5a9742747a3f4931394d3afd3c9c9bbaee34eb89393128e13757f724d076fc82393aec9cd0613a613755fdcb89ae97b4f9484618c4e718fd12deeb18272c38a7dc0f856e88a0b96d7d3b75061c1a1cc2a57b1d7ac8ab5a00f16ab60dc8df644a09d74eb0257fd66e572c36962fc86cbdf80c3553808ea383c48001ee3372b19f7242fe337ab1c6e8e1bae3acd0d575d5ea5ba0c1c0400573d880dbfe13738a860c4b8a1c58a2013c523771afb25777cb082dc30bfcafb53672a315fb61e1934711ae6f44e61d189325c6619819d79f495539ffebd61b0563daade84ca4c54131657465ae982275dd19da09b8e8e4e5ca5c4d549461d2327df3c651fddf4947bcab08ed14d271d2b4ff9ca532eef094bee58197c7a2ec98e53ee12ecfc699fb9a46156c209c40903e28a470d5e7918814ee4e36af430fed8187de561ecc98d250f228c5e4294913bb12507730711e6e43d97903170d00521c288c087272b57f248cec8c953ae3c5d693252fae476581c9c21fd8e28ebbf574c599749b4f46918d23b943ff2d3a7979033641216b7b85e4c5967b12e77a24c8bd8622d911b473c7ba6ce9da73b713ad54fb9132574d3a87f72a576a513dd133b05fad0c82e87b49cd149e4feaa74e38a74e36a74e30abc713564a56558eec49639244485c5b0c4012277e2e6dd78d343a2c51761c5e9e6e9d69945bc775c983af3257a98c5a2a9105b0a39c345c628437c41437ce9236223117b07b17d109f105f342a2219e48cedfd1e4394e1de2d3a0bd974c58b1332cb3b0849e40c1a19830511a6a65344fa501841471379896cc402b1a53fd7106180c89cb9634413816147c4549f67e6d24397f4eb82b7aee8d45ef2ecec441996fce5fe7c22a50925a223cad01314f9294293d026624b9feea89cbb31b69cde82e5ca23220c7d424c519f4e5135649ac54579764dff9158fcfc5891a3613ab9d65ba649148c541a5deb596777727a826e42a49b944824d98488dc44a0373a6dc22b92441699454091682659913c512672356acfd979a73b0d439774aa0a3983662163ace02574872ed9c9134f21f7357be71222aaffdd898488f291319634cc8c22a65a362137cd625291db9b40b1a53f9f0841c1ca1e9eef4b1a66a3594cda853c0aa91ae47da88fcc51395d79651228a66b335140666a1ee7e7e4fa285d1ca4db0316728604628cd9303444be9025164d64938804223662cb3c7861c8ef456c51cdd4fc8c99491a8bd8d2e76e8f21b6f4b7db6490319e1051fdec3615b1a910532e72464bf1e284dcef20c4543f0ba62b4c468022a67710db07b19188a8be75fb8888eaeb6822d3c8199d8405d146ce6822b7409f202ff1851cce1d34c4598688eab7b8f3e72ec9730d3175c41772bf81763ad56f2372bf89c8fd9d9db91345c5595983145738d13384123d4284a1064e044d9c23e008e28c789f997693b10a9ad812438018bb042b63da0c7194fbbac194410b42ad71b307c7449d64287778b7a7e9fe5dfef3b0e9662531695d550c8aa14924cf1c2c3bc2863268c917ecc4456822163f57f830a9a24891bec4b00c8aab89716856c8162a08c68499420499a4496654c3c8d5270e0cbc3083b4287a767777239948827690c8254c41f2874221c2483004fd04fdfc502c836415f45dfb72466155da54042919423e620a9f385f26206f83e509d8d82c7d105bfa920731c684913b9bcd0f28acf56a447bc368414131413287f1c746187d76421782854e8f248b7c801c95d820284b4e869e5d8876ec4242cf4e7368c7ae23f4ec9403bb761dd835eda1ec210d9be46dc8277cc427b2fc4c1256eed022cb5f3631280605d13c658eb56a1017689d97143a4a7c8ee8d66182c57efdc77cc18c881a4684636a9a0830b84294a1292da74882fc81d40167a36144efc7663981053fba10d149affff9a9a9694084912d883f3a37b042981042bae8f5a7799ad1c1d3902e7ae44e13e718611007618206bce83a48d8060e0262535c31a1cdcc17d90f0390e56d8833a40a628c3e6647a4cbbf8832f4f2a4fec804da9823d8981d6acc6067cd34733eb4c8da8fb78ef4a4adb15d5e9e6e5ce217b1e593f235629638a4f9b352fbd4dedd6f363314ea50167746091595914e8e99b19161a973d56031ac424dd4d502454d1d6f762fc8c15982176ee0830023da6d4c549f6a60c3c86363c665c26857990608f20307a771e41ef33e54f9d2b02f582c54354723b47d08eeb153563643eb4395352c6b59166ca4481c59e28989ead9d34f744ff360938707db393251488e44196ed607f1aa77337faad66bc534df411ce47b909b5588cad67462727227b6f4e3ec79e4889c3187e496526b91687411e95e0983251c4464799774633ad5a3eb7913d38c46d6da8b623ef06a7434127d640a32ba27bf5b9ff634e02df80f07b1074728d14d28d3e88ab0195baae4ce13d4fd79c57ce18932a1dcda73b6b68d63aa8285827c820aa89f9bb9e9c3710f4598c9d3a966429e3b1665b673aaccf950a5f05899b54f80f61139033322cf6f9916ac7422893cbf5db3b6d8309e05bfef8237766f573e617907af63748b858c22f81dbc8eef2016c2040de9e085c44e7d3ac07fffb01012963dab4943ca9f3cb17c42c3fa137d9f083cf7378c45babd90d2c15ba4dbd3906e71e920b60d630fdea29cc803ef8db090efa35b17f54b570eb10dde3b0de85d748969be8fb010fb9177d169bc8b70ec14f8efa11c228b8097c6298777f034de413c318dfd085f744ff3403ad51360405c7d380810f866f5b8b28f40a25be3ddcad8192957ce876885acb5d44f9e7a2ae4fe09a70f0ff3dcb7abcd356459a3bb7c8dce872a6fdbb34b6dd2d0b013b7cb5f903b3b4d828096fcf8c81596f1e4892d54d81096488e4c54b73c92fb579ddb4ce9014e963b71623151f44d66d0bc42c76727f3c1ce8e8f4f94b93f55e820622193cc9c91800a9c30740089945276cba6ddddd285ba58392af14952440335c6ce52e4f0b109108ddd48f639e79c73ce89a53c92e7e90d6dae4c7ffdba82f0ba2f735e71e412ec2573a80ce51902a28930f597e71ab2c441c835e41313d8d02501d488f6992977e6cbd430dd7246392a392f68864fb28fd91158ab4a9e248c01a358b52eed9a9846bb8957d42bb6a0601cb1e13699ac0d7d62d544c584aaec32511d635355a1abc246faaa186ddb3ab3b6b7296742b034dfd0acd6758955666aab1709565e073c2d833e7264a29640f54347e20c2901349b0911466aa18166048a2d68b0611fc97d7aa49768249d2476f7916f8fab1cddb7ebe8be5d88761d9c90ed9926638c38fc68ecec9eb28796cb30131ba23cd61128a04082fe749289a2bd83a491ec20a1d612b1859a7e620b7d88084b7f58320dbd4c2189f4ed2c456ca1a71963622d1c62cd23c40ca080a20a3aa0a2044248a1831c244890f8f4b060618dd0914148924c89058e13990faccca14492f9c0ce1c4a2448649034431259be590645999bfb210f6820f73128260a934155d8cbe201425225164d8276ac8462a2ba39196465108c9e257a7a7a7a321250613be7f280165f348f5944eacc588247a684953953c26648d896417c8999a8b89a1151fdda84c7090b6c189b0c6131c1ac41c87a6b96481aeb03e9543f74b1192d83886aab890ad5c407045fa062b0d288c61796437070e490a6e8a8740d97b4450b158d0000000083140000180c0a874342b170409ce7e2201f14000d8ba64a7e4c18c9a32087510819440c21860000000019001860b48d00628607bdae1afb40ffb997e327352707c5c08a31b0c6489061496de9c085263ce1ee420974353c2c846dddaa2970138d3b18366d0fbc58a44e6f2755c2a99ec67717316a3968f5c2933e9d4a07a9509afff7b4d8c0072a1b06556ee68ca99b16357f77514715f4bfcec3981594865e7c74ef684e03124c027f2215aada7ad1c71c1d48158e77b03cd4fc2c5f47fca94f3a3b503a6f068dc3a14d770f61384a75e19e8350a5c96202734b3c1e23cd2563cc4858b4615c368ac40e99074db6073aff19db91e526668a4a7072c223bd6307be64872dc40879a83d1ef899ce4bfb6b618c07a9be7434516b19268ca6a26f768c7541a9fac9b1395b16992975874c8297f399d93b2bc8546075c5dbb88b3094877b48c6ce5ebea85d175ac86f32b60086657cb4dcd95ba2c0381608813623b9345baa6bd30eb626d18c83cbb42ec939fb0fc90d535306a2a58205c5c511a1a5091ca9ab75e852704458f038fae6d9bf35f610fee3ae4ea846008f79f9c09d96d92200c10fc09486385854f58036b3fb8e650b2e1ed5cad70fd2614b82dbc82c225195a5c037425d75298ec532e34b3dac180fb07c17d37d9907ced04282a239fb43ab9e2727a1fd3026154f51b048ac9ef95c035f76782d4c597402d3a9d849ae540749b0f83a42a8d6d90c57e6d61464600f2a1f95574651e19dcef569e2995595c1efee95463d23adb4e1dc57d1a2de6e3917041a4677841e0d66b28ba49bc5175b51c8543c4b9f85fe166ed878dea8ab12655e8557bcb1afac9ea702558d69e64262721add1b9c861405efbf33ff7f7cd13d4cfeb2c795ac646914ad58a8ccfb8d15c581c7a7d40f073cfec2318cba85327ba13f730c068da5b202a9aaedfd3268591242487227c417d30b10c7591f0ac52c27b329bf3c217d124c148ae91b67433e3bc51c1ed8744d2289ab68817f53249a9e0e9ac40a71fd9266f51df2eee8a13697d6c8a226179a17e5640b9319b345329d930945838988a6d2b1c567ec232cec72f6ac0c7b9341589517e5ae624c635207b53de01dee30683cff6824f9fd8cb44cc91d345bed990a0c8dc0dd2545aece2162ac058f5990a5906c64d92d397ac8303eb0409117a34282b1c09f12035caa5027c31cfcc875681038f0e955ea9954fa93db312722d52529df8292fbf3ffac215e7c5615f635306ccabe11c97c65045162946e63c9057a3898b4e1b6782efa7c93a4ec95be695438ab73b740398d623f8d223a59820819336974526c021c28877a57bd7797f39353d1fff4350d8e1cae2164946f6acf09304d23b83869f0d0486503b7be90e812aecb82af5ca668363cd0a569900275cfce2ab009d49336b63c6a101e24932b70b82e239e16bfb2795cf7261e54dc7b53e49cced8d0e171a252640fcef3768e04be57755ddf9627e0143dcf5d536e1254271380209a0b18a602e8cf3f9c4d1efe12272acd42c954ed60825dca15a191e53d4a3a7385ba96ff26766a173d0794cc660df0fe26156fc029fab5ccb1262bedae6bde670e071cc74896c7f38fac9d5682547ace9172d764fed0504e834d9629ca27d0201d5c920e42c882c6485eba7d9224c6940e44220959dabb4a007a6eafea42f4b7b83cbf27bfc8705919388248198133531ffadcdcbcd56f9abc1822647196122029a7be49bc44c70f27d12df41546118e97af53939bf11e1cebe8340b1782d65e3b5875633abf152357bd10787073e6740d6ec3974ff5b4e4dc5fbab4a08ad7b963ee6b44f8f9e2fab6e2a120ac1ee66b8079d50d8bbeecea329d7f8f4cb4abcc816d77d9a3a407f0c786f636303394212d41cb90aab2a242cbebac4f6fb8b68f862c64f8995485fb4d4c566c09c6c928cbf6f4f4f775522e4f0671122003f418dfadeb5849d0ce0d775d16b852b500ceea511325f59d408aac02c3eaa75fdcd034ee001821539ffc9d73c79918ed6f981176616bd19494521bdc5834977e4466b16a8493e390eb3dfc251ff5ba78d2f1fed70467b114f50dfeb7969c8561238d03f4af733406d58a33b385cdba9062060e76790c65d4e4a652e92536c0249c3bbdf6cf9bcfbeb8a2317257cf0f0bf72bf651548189d29d586198623adbdb8329db4b4102da44fc92c9101fa8d40734f338817a3873031052d18e4e31cc6ed614822e4ee1fd7330e85259e1f6bbc3a49d534e4ab8b95882641385884e24e4cbf0f57a1551935c6c313910e4d24d957e54cd17522035c54238519f022732cf265b1fdbba166561e8f75912cc26d2389e762fc476b5eba6aaefb2cc6d87fb7742ab16807a8024882462a0b81f5f003e853a8af3760165530ca366954ac30601bb138321b6091c7ab1771b82736818a41113157ac72e446cdc877a21884d9e049dcf0a1c8ec227d75518a4baf78bfe9c60c8a51da32312b2bd00247b101c3a0c83b8677373f1b5cb4c527dce9df333139d690971ba4d263c570d21d7e14e22f70ad95a12d3710215d0c4ee0bcd3f475c2aa0550934d93feafd5052ab294d3885f34e0a33c1aff9f8442fcde8b8288da1144f2dc59cbd148f0ec47199e49684cc99fbf0f9d34cb47e86d0b42124188a2a9608fc1412ea4dfdcaad5a170a62428036542499f762a7368ea7b4ef9929636000e27ab13cde83b4c686b35f60d700d9b92b8c81684a068730b64dc00007587f7651d9316341b6707be0d78ca87c031bc46f60ff0a6a3feb8fb1b15d0a8b3024a0c20cfa818ffd458f5f386dd84e71df6b8561f86bfa9162384394a0fa5191dad7fa402ceb7eaee813d7e708a166012100651a13c761f4fa95355414d5b00e99116730ed2ec688d398b195c8691212905c3604e4b3fc171aa9098f86281bb1f2fcc8b79bbeb33a876a292bd1615ffe92f6875e6bd4e92e30350bdb859a6a6c30ed5ba14932b74540e29e3393735ca1805634b22e24f749396948c614e402f7221eb8341db72904d642b8e90f9d81b4c19e7dc8c6b9044f37755eaf65836cb796b7d5b2f7a57e79e95bbf458694503e43010ecb7e15194e2e5e8abc69d410e9bdeb47da050e3cfebed7bb28d6a0eda9dab852211e3f1c8aa61d4a6b87ecc2c0cc3d96200e086cb06a057355030262593a3aa3aaf06dcc05c091e4115da068d87211e456e48e8670f61537cffda54792aaa078bdb8a58f804948875811c155f0c5c6bf021162b14b78f017ac709d942374e81675084201990bc04711fa700eae9bb4139622beb02511e0368077aeed34718113eebaf255c0cd9e84897a0216f8f0d80439c42ce08b641eb4609550c1a8dc839c583a72b46c259c364135cbf95519ba2e6c07bb1126cdebd02c29e35c413bc09f3d2ab7192bd49dd8322d8c41e4246273c63db828c8c4a871590115d0c47233742654423fc0565dd3f745f59ad2800020eba82f74049a69fbd2ae31f4719b8744594a360c7053e621819262fee971119ecaee6417c5a2e40c984f8ba0511ce55283841a48ffdadde6034f69a0481da5356d607128adf1239e86c8060ae2ff24a230a3cf6869e0c7a1dacf47a5d7b523542d17349c9f5512b4ed8d9273d23684e6c953651c35a4aec9ad9d232fb29ec33f26213e242950131f49b21870cf5d13e943f498db245856e3e20631ef9e438027d07f35b8f216871591b2498410ff4cc5c2a49fd6cf44928ecd20a970d1716e4e060066dbe4465c5475512cac110eaa9ccaee36bcce727f7571b18b702ffcbf630e2d839008d363dbbb8c04113077513cfb21d616b9e8a57975615e217b90194a05f6e20959125dcc43562ac889ef8f15d87f61d35acbce8484a008f1b3addbec08bafcf7c5252c390ea0102a4d8ad9286f1c33b23890c77628a1f1ede59e88e41df705e8c7800435e2a33026f0c481eb8fe318112365407d4361c47c2fbc6afc9ff2a0eab5312528a3a090eb894341c9e9a56c496620f617703bd1dcd92d05b2eafc65caabc42ce362a121fd3c8fbe59c3263abf8dd979c0a25ae772e1bfb55a153566093f4dede7ecceeb347b07d90ffa5c990a1226ef0398666faea318efe3bed2be149e85db476684276718c4247e04f7970af61adecefbf5d971c639af891e5e079eb98e8e3eb27a8d1f540e37fb815a27205661236db8425d2c749e1a7fb1edad2cd9a9ce07ef05633e0be4c8018196f2f738e1f1db99e8a3f21655858e9309016c50007f9f38593bb32b81286d4ee4771a64bbc1b59e259b467a845383acd5d51a285d68e63e56ad0c58c1819459aca1af798c53cf05bf7bbea429fb01759ecf44521394c9f69b1c1edab754c8f752eb80942976c1f2ab15ba9f956d347d036d9fe6b1674dadfdf48c127bb2807d227612c209d8c370eb07c5297abfb93d671c7969004f132f4c01a378483b45fbe74bc9dede740bbc3a04be43c19e2f6baa03f662661e28360803f26592a234abb6c8852f19dbb10db288c75f89ced5494a66da9eb9e44d4bfde85244d64702f1182a9b867d22b0cd8690b37ecc1ea0532650ffcc3a03556e49cb0ebd03d5851b10fa199244231dff624088c4a9f6d8f622b14646a672e4c6fba9ea28bb27e6734922b49398f15d37ec9b6ef7f94916abb4df63e5c802d0ef67289a5d157aab46832c5f281bebba26b1228b94baf9bf8f8240c3088c86c6f089da104c7106f78a3d13aa6918e5811cc74508c82ee0b472d6e55421d64ac22350535c4465ee37cb3d1d062ad3bd5e960ccfd89905b62684f9f063c239536c8f370964f866e7715ad2dde7f03729dfb203a4849d3320dfe406bada3363f2fe0e9de3d9a1928d06932471682d598db0abd0e28bff96ffa41b966c8b162e335c1ffafe241bedbdcaceee3f695c9bfe9381f35cac15b0c38a578a0da2760fbf93f4eabd2d1ed969caa04709f57dc274de1b35120e30cc68825660a31e2a5134f749b2c25fa4273d21a51c7a8aec8987a9780c1a4c69440a37745bf360220bd646fd06a710c3f9be977b793ed9a10a89372cfcbca3175001505b4f2dfdb4dd6c00fdfc623299202b95e1492935e4eef5241c75919cbe1d62ef7d49dcd22beda0da22c26db1a98c3250207254f40e2ca469788abe28e60541c6219357e7cb6058521b352756f68d28c883f70310fd94e01178ca2925848cc952f7b55fe3f5b2e00592b4a3e900032d46d1bbd40262c05a78ba3619e26aed24bc4b681ff97e0515e909e26fbb8270a4c9d97829df39d22f1036834d31a7b06a9a398f6018cca5a04df981a30ebc332ee016527a101bf9f537ac8ef50cb5bdf83d409fb1a96d72608da24b9993687560c33879029266a53b397bd6621856e510280ae19da8dffb1da775a51857979aee9c1b2812f7287873e8fe6b8fb3f95dbb15d233a1f3ed0a818ff671c3a395ffd9f954674800c3af45f2d94b8443dcc1f3104766083b71082aeef22f40e8abab5208af1f79ebcc19fb841b0f3c0ebe5ca636a3edf111e79404ff15cdb62c9cfc5a3d39d76d5f8297a13fc11d8da2e40ddacdb6f4ec0b52bcf098d0b544405b1b243131c23c0d48e9fc7e7c5febaa2a2875a8e5c421045d6dadf5b904944468f5a0fa93a71b5c41dad3fe19fda92723bb4dbd86e4f7f3429e66fb326470daa4d047c3eb23c1abdeb3bf91a16b67e0fe3ec023f76fc99c329c2ba05d75812b313250beb0c05a049c04dd74f9222db0bc29c14a2fddf76a60012114f913498285289cf7f96149c1e66bf8ec66201a80ba7b479d478ac6fe18aeaeb96a692bf36547c78ba32e97f59eb52fdcdc7f1cbfcd9677cb896fac05b718cdd3492336468dac9da2705b63d08ebb5cc8a9e52983358621c5c3ab6701d80e7480fb10966b6a3624d1a1bf94f7862477df7abf62af5965f984398216d8f4930e3c6160c0b9c9bbb771c5306be56a5a10b07973207dbb42afdca03c045241077a37151104085067f980f54cc70544b16187d45231a718018fc8131182f3c429908e426e7b8ead2439d1d4761b4d516dc89a71b6cee25ca89b3083944b19268bb4b51a2c3d49c020d9b1d1592297553a38cca8644e2231983fc526bd7f9a15cb36888f5f08a6a8e21df4c959b1f6b6784d8dbf7479821c47c27917dd804b79cfc96986ec1df61b8bd04d7637ab6b39469a773acce47ac4d34f072c54466c978692a1e3c85a7975f90fbba33cc38b257638f1069cca3b47ca15406fba6af7e8ed127de7f3074331f232fdb18f12a86e29e7f86ed692c710c6ff74e07ee390268572264eb0df42e504801827821136c2c20b0cd670e9e92b5fb730ecf6c6abe183253916875f1c37c40f035befa1050afda8adb3fc28458aa113810f2922229e229f08c299d4a8a8ce0ce1866d1a0dcfe7bb27f21acf24c43033d295ea8fa097b94c83f3816eb19045e6b1b39ea906d251993d53d55480db661563b1ec6b0cb36d03e55a016cdb89292da34e08e179e2eb28818d95ef5b881cd035c3dc559934b9d8bafe4bde69b865cbd9be2ad4ca7fb234274d95a2b7325618d106e54d8b4ee736132f0e0854727c15be2794b37c0414a54d89e60cda5c7189f213a28025646c555f2640759771a6526538db1c2a41c3f95e6e231ec59e6e0c64a3f9d590bd703af29e7546f8c8a4d680e8bc4a756e0557bd719aa0a885a4583d9b1653f6e01fc9e65b225c12333a3709809e431f8327cca9efb862253c64ca2683773eb78eaacfc1c07bbf07cb0b987f0bd82f2ae3ed7ccd3493b4151d7748fb640b8998478008edc8ea9c746ffa54703f235349a9f040e6866b1989113d064d0bafea15a88625ea7d09c923a8940b74944c73bbbdc9ca18a3ce4ca53102436e24a8bdbcbf7e2b9ebe3a9db201bacfee7a7fc532af8e73a1d7f5917f63b52bf9c36cb5da2c5f4c8d90218b390ad98579f7f661c2afc818015eefd707c9386629e8f4147ceef50e4a4a9de77a574980947bd891146c0f88fb6636c1025c3491ad48395fc1104b4f819124354c26347a5b03230cf0c0eaf54269bfbfbc7cf3ec23480a6a9909c1727db3c0392993c332c9efef8565624840dfcdfef80681c5d803009cd2abc50e0d33a5c120ef1b93ae73756945ecc203967f25598a90c845c2049f5fb8da3bb952187946c820faaa7bdd84f24bcf7d6b3cb630d2302aaf334651025875b762363f3f0d896c0fa43bca885c1725411b32619c47927d7cbecd6e16e4bc216bb0c6e8fd85fcbd9786abfa4fad9f0803ec11dada9a971a319c17e5f4266ac96251ee9a30beb28ce8d6cdfbf7c25596a48ed12112499580306b3425989eb52ac3439b96916467af337b6d6831de854af6e81884c80e187b9975ae6ad9cb9779fb310ffe17c9b2efdca7a835cc3532883e36c444b9909910108a65e033abbb7b050431f5ce27049108b97f4a3d8b5efe5e2fdaaf340429b8e4c548468f1bb312b12832c1b8dae39db1a438b51bb4ef492594664aee7e694c278b8a7b77de51fabd9fd938265dd22289ad49c5d340939f9e950a7e0d14473c7448eb6bc8ad2131223cdce37689592259ed4e6dd0e32e8a6344f88928ff66a1f5136c9c4b980693be0de0af7fa5cb0919523d55078a17eb8efe8f6b5b1965d35f5ef237eb8e46e2336cd437c57c892393259f241705b7bee0dca7b7a80cd073ae8aaea5d8a6dc89dde5109be94a2ed11b35b4d8cda6f8d61c055492e92517aaaec534ed527c0fefb482c8a2288de0330d6ea5094ad2838aede9c554859fa0c22623d18fd120166e49ecf970e63a1357ef70a7c830a8ca64cba4009d94639b29e6c95e3925f522bcac6b91b4d5ca75ed383f4dfb5063cb3bef9ca551454c044d29f618100a7ec1a78ab22582ee6472829230fbfde79cd54fbe284d893b5e5e2742b60f43b91528f91a1da137d64a4a9585be30b30e8f77eb1be799d8097a8990c6ec04c9d6f7f1f99e8678cc4f21a26f519aa3c3a16091fb9470f882a95957ef828c85c6415925cdb80b4b08b706e25dfacac1748d2cf3d77f96db445be5aacfd4ca5e755606e2601f042c45787794452f91d7a91bb4f4c5e42861d542b75ec6a10017436a247dcaaffaf751cc0389bbe19a40991c3c33ae166f0617d0ee1a013c49d5c4159f1f068f1456acbf894413f8330b027bc5845d93c84a5666e2943cd39b0f4dca5e87e38cbc53aa5e904d6ef70bb21f0b6b2b3244161b990d808a66826d04eb60f50da8d071d7f47e4a5c3fd2d9f7b4e1e0d8c0bf8a96dd7bf4b0d6c10ec1fd0aa18f524d97939b2749e4be3edd4a603afbd8be4312467102d1c5ef0f2107d514bfe681f5d5b64702bf0d4aa00643d1e13b30512ab8e61400949639ce12063e6dd402666e3ec437a24aae3e864835d4fc6fb38542625f60d5ab26bda6d1806e4b70f85b872819906c3a43f5a23c084962958adfdc1eb9a84ac70d4ad690306ac0c33ca56a76d1bad5bae88feec72256ce13578550e1a6b895ad964073ea0c2d571aced296e48c92e1fb358af3f810656a0da77c47a681eabe1c36069b4c7b0299b0faef33ff828eb1761f7f75e2a9cfe921d882ee73d262050d383335e924e0588ec6265f1b380b7b3957ef0c9ba0af2a51729a0d16484c9263d7c2a8e960707416ace6a1f31450c8263c3531beb2ceade9f05737180cc2723685368ae1302cd054111ad9e82bfb0ac0054c1946e81c720c8f83345d749d29562debdc520d595dff094aecaa3b71307017e3665c50f5044747a4116feeb6af777ac391d60735d04739d6b6ffe42fe970f01aedf0d308d965cd4bcae1083d6b1fe7428e8fe6e6003702571e299da69044f30f6547cc7565be556c517e9680ad87fbd339b48bdd93afa66873ca2f0e3355801d64d7659a050b7704fe37acbb43a237bafa8f573facad95cd93aeb74e850ef383f866db75ea8672014e23d2a1f217e984d2e8c1ccf85dfcd9837ad3143d3931867fd56d91f118d5888c04267a79cc408890d8739bafd490bc3021d5738497ebb59952fb28d18627692b51125a36858dd7f121ba7b8e2533fc70e9959c0188057b24c853c5597cbc484fcc741d16d30d35a69abf55471ec44cef6a7c28e04134105488fa66a503d20253310828d89aedd00137d1c24a6b311603104696dfce1ab0d69729d676acc8613b8c2e718e601debcec66092ee4727784e94355b6701d42de6e0605d90a983c6635a74dd9027bc5f57b969863efc1f6328fbbb1c8f2f1237382bdf13c01b1a68729fddb928b73a23e579b9d60d9f6f6a13348218f7bd20819e9a9293ff7c2489e4a69f98f77b12eea61bb57df466960334794f41589fc96261f28e7cb3c807808f18adf09c4fd3e6dde7500590712400b0d3cdf905a24b1c5bcf9a63134aeb1827d71debd7cc6dab84187c7e3c379847de41a796567144b00893d94b39df68efb733746b0f8028fa83640f158ed8b40bda609cb7f53436cd55f9499a6daf7662cdcad645e721b0813e3f4bdcb57fbeee427ae10ad2e9ac1e852588cc261603e8b984f5e4e94f3e97f9d8b4f6bb44d2b0ff7e9f93468a8612a24f0da13b94874f1a0096eb23763961a99116ce80cd3cd1ffcefc8ca8b33100cdca2f4b0091a4c05800a4a60914debae28d5707451154a325447d4cdfd6eebb9488225ec69cdc962f93b7e1f952ef5c6ae8f8326df8e362d6c1ea2efffd7579681734c23b637824883943b588a757eef5de1181081c07495c7c3ac01092c0baf4d648a54adf7ae347ff9c6359c213b49a81a795f64a14400b5a8fbdca5333b80122195d96709f8c9e3042c2063c8587e3ce9fcd1c3ad0527144b2d99c95cb1966b8c2648c9c11c1a30ad7c5bc46dd168aca9d4527466b2862991613f10fc96b8b225b8bf5a0c941e0a7a47ac3b19fa0f02b888ae93c6afb0729b52c1a3bb038a8daa25b73695d8ca4a960331a65267c0c13ccfa0f234134ae8696bc7846e37c744859718411a7658fecebf6c371d1ba114dc6d7e19e92e2bc772bbcf076fefa78ed5bf916c7431490e0c49801d2eeacf3d8e9f6bbbd2673c7836c4db574eaa25fa0626b82085ebeb8721e98dada40716b49bc47c77ceb46a40c6ed8e575e75c3f96c3fc186e328050d687e57fea9586bdefb1f29b32fba775fd5f7b2bff3f5f109d243b5f45516ea67de051c45719003d14b7940c4f5ae016faeaa00e7d9cf900309370f48f76973a6d41817cee190c4377c18e37639673936e865ca289b498a64f1457e8ad064151e87ddbea0268786fb15624ff1b5027fcedb9a1c7919856653a2fcd76f5286965c6b5c284691e94edbc98fd19cb81d92b1f11f9202464084cd3a645814e0d0568396e02ca72e64709c0fe891678b2a280d78ec435b5e65cf80faf27b67b31ac960098bc9f1539f5ddd76829adc8c84f529230d97a601ff8831dadf23a927826b169b4fa450678ae6876a81b05168381c21ead4a65a8475b657e12fed4eb040ce8e4358621e61cbfd79762478f948c4c98d90867d244b10dd3dd917c844f111f683a39922a2ed0c5665b1f40c17c21c4f5e34d4ec1202892fd68cdc390336fc7635f067121eeff5894d696bb22615b9ba04b08aaae208e70056b5cfb3c7f037176951f0b42719a1871ae12635ec3faa2740cdf1022a6c7597d0d09f243a19b2320c4e4d94d6e1bde4af0e310ec4796b178af415b80ba4780f045db65a3b8484f65556d88be28624466a675d6ed55844afc335a9b6a35c14d5218257b5bdb2bb28f481e5055307a4792da78ebfa5e5bb1b8aa77c55aeb27c515ce57739dd690ed3b77348150467808aeb64dc2252d8a8cd83222b599a173363ae9f40c1a48ec84e93c0009f2acad20fbf9295d03cc597e28ccb27022f861b8e0d895b28d597ead89ad120a269cfcf71567f984c1e29071bd6e4a46f00bf793a16770f899f2c5080287495df92945972b825897c96237b2d42beffe64621e9068ea3d83d6b75930d57d40ea0fc88dd22e5d0571ad8c181ebc05d0ffd8c7aaea29eadd323c57b405ca2c5045ff53aa3b12870b42ad5af5c69376ed4c128213ae66dbd9ec3d4444fad85a7075513bd444e0f8a7250dc0a3741f2b4af687ca3251e9acea3a5687e4748f00a06e4e490e088e40ce20b732a3e7ebeafec9a6711db1fcec9333660171aab313aabd25696dbdc6443e820a5f1ae43a3a9f411b321e0ce47ed072aed688ba36cc6052fc231427974a93e83d91d25ae8d9322abfbec95a875df97fcf56bc679ac7f062584d3f244d6e28bfaaecdd78c8674e88b55c2c97730906c3ed27d1cb97a1566b9f0f00ebdaec9019df914719de311e1a98010012b04db445539417487928dda744d5a72daa1986921f625d0282e94fb8e671d8bcb76380a8d475df23d3d60e7bc83e2361d860be83427592e15864c454eafee81397d5a601ec49a5fb5e79ba18722bdfd556f7978c0afc7bd15d0b01b2c0bea72af2383dafdb9f2729b2d976b71f28e8ba86b8c6e0bac7febf26b6e2550aa2810f2efb39781981ce990dcae355caef5f80f62824cf324f901c8011b404c708997a1ae19864e3f11e050959ec6b15aca471b17fd62a46ea7625f4e1b01cbe02b3a511a185960a715306f3b6658e3fb2fec74a1da54ae2a8132977bcf40759617e3094457ad059f36a2446e1f4745ca015cec3ad6f1af4c7c899d258af398b07c484b9040e249e0ec07d0b7cc1a4a405824a1e440cec3814da7af763aa0c42854161e3979b5d74de5a4449886ff8c36e22859072351901993ca87422fe7ffe4193164b185346dd82b89a2f7ccf60237c3bc52241cf475fa2c073fd639869f07a89406f75bf97f1d6a2726f1f6e065e382f700c4b13977b02e26542bf9608668310175ed9f69ef3004ab41a3994319a9fceb397b3eded87c3942df923a102e913e92d9ade92d6e39c7a95b3de92eb6192d5d3e2a9e81df30a9b807a51a4ee4e89afef2438bd146341b5b0c21b863b821b0a6ef4c36d104ea08f9d294e93059400bc9e9fa5387ec31f9f1b81a21e331fa0b96054e9c554f867dc40223af5cba662018930334467196fa3ff599e0dab1522c9beacf43387b0cc8b34f033627e81439d3f8fa5ee3729c3f57ff6e937d0952150ef75c728b3e6d67bd9843482cf48f9aa1d004c24977b56aa024f7518ec7d283e83ba8f7e511f9c0f50baf1368d791f805f52e6b8822f98d9bf91bee8832ec104233cc007700b6cd8d8852a9efcc9ff5bc8fef8d4041207a7795af176472fb0da6aa9a85f8502b70b41c2fb70ea286a1424e1c84537da386753c3c2a96666c521defd8be2082768e90f1109e677c4c836819cc88d2ae85cf5431a0872ecba05c421d0c3ab473c0e2c0f7c63a9fc55839a70b0b93e8e51c25432ef1032f8eac30e43e3aeb7df872e8a87feb378637f7104746ba3b77e1a6b3304ce5aa88ba42d8532bf064a911589b8eb8436b988a4bcf4174a2066e714d105a6cc8ece993df3a6d91551d858603c853e88fe1ad59cb64540e0ff0cf53da6258c87e5862b8d8edd13fc1e4713fd16f2aba3bbd356af62574d3c0139fc55559a6066d908551a34bbc576e5cb688cbff6acd00cf49cbe1adb5c4397335d40b0071e247c903eacc0e4a68ca3afb62e4c4a650d775fd5e4a4dab5ee27883ddaab2a1c0b5dd7d1998270e37ffed63f220eb1c6d9915a78c5cd2b3954c3bf0bfae030874e8fa17b5bb59c141046e4c724c2e20daef4c82e1306484923990bf7185a4bb8c2ec5595accc2e99605b865ba8dd15e0891b57da8264a71ab3fc267e1a32d2054d1817993d6b596656c65eb635a8cd0c2bd255b381dd6ca1d73753f20b59178cd1bda4d6a612fa15fa6f62419f5ca5947a5f666d32d790e8d60cd064373654ad147a47f401cc37d800311dbbd2ae7dcfddd1f2f8d95de384d6efdefc55e3dfff87a111195be01a1b59166d8016ac40695e8b50e30e34f1ab02338901ae5f101f011bea97f90386dffbef87a6de33b64b6a6db42cdb134d1505cea1aba117bff7927b6d32c4d5720c8182f699c1d3cd073a68e6ceeac122c96d1eff180ece1303b0ed8c254501da78a1c2f1abebf8e9124fe81bfb08c84469c200b715eef0d7ffc7871ef4ead83eaeb961d9602769c104f6d37284f4a7a5a9cf13596bb34fde37151e8df0f21970a3be913f52fcf9fffed0943e4fc59275d2291a87cbf3e71738b359d5b7230754b68a4ce85709ebcdc98f4e6f52e66a02f01daaf1d512a416a375982d138696206c89f31568c8fad663078d804bd53f657de6c7287effff7de84aa063fbb8e646cb808da45d12384e6b84d0adef87febcf70c9891195d08d4e6bf58d1a21ae983a01ff8a0c4b1a59970aa892722f4976139dd51304fd15b291e05178d4a63ac3655845f23bcce9e1ed9eec8026ec74da1fc3cc12fdf59df342160e1829fe90286226758fdf284e4d6e286b1201e2161c1a7534bfee7885062c1960d390de077d0ac3d5895d8b43af33c4287b34e5fa7841ba73722972d566db4d9fd28276ae28c3bef3236fcb4c6c7466057fcbb73ab9b2ff250df546101e259bef6b91b9bf6fe354b45660c5b05c36bf9e78c7a9c7269cd1ffc7648b285d729240b1c183bfe36c9dd348def7b44bceaff1f1b43ba1e94878722fd1744e24097a2bd67a760ecb8f31b1d506085028f63562b716581ccce300ed36f9b87d78498bce9cc63ad17c58cc6da32bda4a63132ea3cec0de5534c770c4facdccfb73f97245a2700929129aa3e774656b18ce47f44f5ab4a89ca086265493e1f45abdce1cb727cbdfe9dcf90da5484788a7b330c3d97d01c7bb8d4e051b6a87159079599b4f04b15e7f08ca4bc7cb2128d018c2ef5ddf4517ae5f35f05bfee049eff138a9bb8a717997c62ceb77d91cb9b1f517c0a5aee084af9f7e22b586e210597d33a5d392217f97e95cbf30337dba09ff5555631668a87ece94740ed49f475b82647209a400fba51baa9e12efa475ad5a456359be11f2f9e7180a61dc9943d5ca236b5f9b4cbf7fae8be445cb1d189c310ae8275e6b24696d1ba5a9b2c12ea22bb10d80438bc79af361c4066492b3d4ee12ae3b1d03dad55f78803a395e30573ccaa678c54cc6aa92cceb5d90d5a90d4063f7794f3d111c8a413178bbe4bdec5937e668268f2b3dc8fd5065313795b6a25167e3aec9de7c9bd1d1b10b817bc2c34c375fc71f479d3051bf6b562e013f9b0f99c2f67a2278e4cb75a7d2be0c2075fc8b5f33857a54ff423922f4acd89f8ce79e3ba4fdceed7719ab91fc361b647b79dd671175dac08b2e50f0968619bac5659c591e35c8f6d3e9a935b8ab8d5d82e0f57bdf9fef91b9fd6c0ed88a20571c619c9e5eaeb955db03e94eb2b1a435876d38f8f767c4d0366ffe8963e8154ab042fc94f4001a5397150b12e169d154930ca42980c41945b31025369fc40b839c275517cf4268e84104cbb83a5e85478d22181537209e56d0d37fe4af4d162ae371aaf6f63991f8233d77dd2a9d2d66c9391cf7f2af6f972b82237780303ff0acd31be0bd5eb1f37711868d430f000d4a338330d917ddbc8f059abdb0581005e13fb645f1af29088ddb3457d2259898c1fc54c83b94bf2faeec7ccb34f40df3511c88549703c0b361a7b0c3261dadd19f960e093f01abc235983152a9e14ca00e06d9cd19220170d90b8fad7e87b68a6b64611dce2ecf05d54ad871dd4684cb57330e5c0a8b0350e1610fbc593125a338fe97949ab427fb3ae97f33d08e12e4d3b74302481691c06fdd3ef1ea631b3cd7ad9ea1dc2689081e4f609959359d9c5c4ce268ec23f967cb2ef53af6f9d04029f05f7fda2d4a22646cc26cdafe5c0892012fa0a6753407fd671c1a59d73890f6481300c04121e5ab354448bb34d9870c1dc3d69c75abe124010becf4b456b444d0921ce64463ab12e933c971a930ccfa1678c86b9f7ed2eec857214f463b43989008a5c165b0c24cdfeb7e1860990b7135fb8319a7ccfd9a1104ff3ad6b5d1aa3ad51a734781d1e33dfa5cdd0bdbee9b26999a3f776fac94cd39ae19914e0aaa0744eb422e9b98d84ad5fc7b47da148c88926c916331a1d51c616352cd4111ba3f3d77949347b844432210af0d53efeb1ffa78d30b3820a08ec9913c171207c56a86a4e3ff364a4e8774fcb1c30c85fcab65f4ee540469e1e85b4f03a167940cb6099cbcd566e6197dd6f7110bf087ba164479c5fc203fd6c4661836b4dec8808a7597d8ccd43dc086d3c0fc87885546b21b1c58df4b1409267f82b4c196d93924b4bc5885fcd17d97b2b751791fe64fa21c1044c9b905737769917efcaf510a2b257a2ed5d197f536a009bebfd82c5a3c754fa5f365daf2a075875d92c9df63dad3f2c8dd977803a46c7363eb45dffeeedd59bd2fab8dea7a949aec59f3fcdb58610d1ea35957e249b3ffcf39491e4cf50b9fcb75815cfa3a6d7b6eebfc61e8b226b054f1347bff07d3c681fda6709ebb1f54b5a2eaa087c7a48ae7bacdd8a6012b8e866ad8e25b5d271d11a369462582a686938b79162ca33b8581242c40f8e623d274b6b030808daddf556787808c28148457a3f4195d5bdd4c60d8ab9d2add4fb49c1d5ee8630813ddd8d4ccac2bcb5d479db2f8d0756acac1e4b84db705d3fc54d0f78c16d343ea84d8e4f6dbfe64bd6ef6982dc2814e19dea1e8b442784e86bccf37118343ae302e95a26005381d43b4846e5873e1c815c18f456270f1c5ea4fb9601d01bffe4718745561397f3e132f14df213d42bae99ed8b13c4bea4fdbe026ab82c345d19429aaae2c00f78dafa8a9799486c4faf4f487c91b1f4f0de324b9f580989d0631f4f74cf6d63f9dd727a94a94db30029a06770f11be353ff5e71d3635d47592fdfb1bed6d1cb940eebca73c01c0ae17d7b9b803c7f9ed8b1e333aa6deb53a72b804843a31d10fb74375d378843ede6186da44ee83be5e8de78db31e58a1180f0f7957d4e21e9c7e5ef046f6e2bb291205ca674ea22915aa36ce9faf73a856b63e010be1d8e9f5924f6668d2f144c3f3f35614dc808101c0793877b3f53a7f8b96c29d2c7281f45450c27a3b6f62ff30bc51d3667f1aaccd18179dabeff0746117c47247a905a4b564c4bd5d1e5cfd2b9667fe657498026ac77dfc22df02398351d82f4982f7741a6fc9fc790bd3116b210c020d7941f7404a4733e950453dd04a8e9bbe48f3c2eb3ed7def4be2f172fc69eb551431ab0e9b530c4eefa7b5d5e42f7f26c7973f3eee7dd5c65becfe7387d9b2ee3f6fa907d19c2df3ec01401debfecb1d266eec8283ab30401d17d462a667df3e920ffe9af30e2259ed8967a65921988c96850e4786da3f083449086d50c02b9593441bb72ba7c1b66455dfeecaa1c79f021e3ec84c1b580d928a2e3901fcc05c2f43eca28fbc12c3c8a33a0ab94ea3e53f52e270c024494b9010c2fc945c5e11b3641c7112420be99459fdc2cc9fdb7336bd5e13882d6c2954819a04a05861710c4eef55992a978acbef0e040a0408c836dbdbe4061a366c36fc6068e8bfd9c7ec889af00e72a81109b0482ccf257b660ee7b5d20f85fa8abc3333ad29e1a70f8d2221f6989fed0a4951e3d663374e388ce0dff6fe171507e53cf8a8fd3c85db761ed251fb599d7d7515a657a0e2400f20497fe7f51fbd8ff9b1b8c67c145738ca58c57b2cf38506a243370a3c71fa9d5637a337855a8b78cf610613c94c419447526ffe84d9226c102d2f29daba57a780f1831d10264baac003bf984b3f47f18e37c37cd455781d7b3a6de687af9dcf18ba8086f1228f9c49dd73818793ff97715ba3dc67693fde399a8c6f0ba498ba420cf2420135214cfdd89011f710fdd80af0f288f4061b62dd24175967fc9045203f01e7ef298092ee97d64b8db07fbc335acde38ea0e201f83185a3efa0d87aece6dc907365070ee5dc5e0e7c5034e9fea5a804ef7eece687d6f088e7adee2275b2b888bcf0d510b3ca98d550fcfb7811306d8c9bacdbe6109c66355965bcf819a678996009a8e8a10b41176a1e368bbef54ff0a128b2e8c2d4a52b31243704101d71beac519ac3f10aead0e9ec6a5e4550fad8c54151f35e5e4b5bba53fcb20abc57c413a2607db266d6b5be8511dd1ee54bf7c83313acfa2c606305f0c701fafd50b58188ffbe11c71f50875c5e9201f520aa7f0b6a9dada402fbf6dc3548b15317fed91cbea69c8fa3a1f426e206c5bbb3f7506002824bbc2b834f28bbbc6689fedb14d6e14ecafa532d10b8f328a1cecb28402affe93b5664f5302a76bd085d124d656b6ccab8b85d1ba21c287d68a1284777de096be4ca8f89e98e4ed65404f5293f966d179300708f97c7c2ab961d5e1202afb389445add2318ae66b554b8d91332a782e4e49e6a473fb79e8466b34e16d8f3e4864a31e53b98840a65cb50c01c8030c7952966618d8a530cfbeb29764056f2c168efc00774512338169e582347617ba91bebdf60ac71b8371066e1b3f5573d3c5df5641f25b57fcd4b572f8a5a9c9b5fcc57ba58778653173d5fdf71aa87e8a8e7e8a4baa0002246c176efbc41d425ba12db094b8e64c706865931268c3330fad6effc93ae9e662d2b99ce5b51c1fa265005f25f15346528ba902fd30a0e3066b24608d677a850a14373b904e1c38412ac7fe332266b245e166301a26db75baff4a675aacfea15b9c64cb9add13b2ceb5b588cb3e7732a8546d53321002a384013319da24ae808d02b761033a6ee1fc57d430c54b4a0f8689c344e9ee9e5a383a832c54ae5e65dab3a16fc56f200970aef5687587c055e35a41d4af49dca9425950c7b03fa04eb8a665c34c1802e257ef3df5b002ee01a709584ce6e7c230ddade4691b50622da9d0f65cb4bcaba29b902a2da60eae7a467a9e71ce51632795416d279872e267b26bbe98396b9ea6760459fe23eb1af443f024f4b47fa5a0b4ec2766ba9e5ac8701ab033b536335917aa21024947d324c04057eb812753f8b77fcb0b0d490cbdd5c487ff0c1b916fb1620e4d6353873bc7775b755626f827352dd3dfcca7057d05e3db356f8519708239e63ed51035742498451878dc444eccd3ab5775b6450cda8742de5f380548f5e769fdab294ab3b4356fd91fbf0a8bc10c2e7ad3e2e55c92156d046af0571d9da3b28d460074e35478c0df8d7902e12012f8c61454b8399232538206deefedf20b0ef564a4eb504bc379ff4fbf80e38d5fd03f37a8cfc3b853f7546c879aaa6d21c99c9962555dddca49cb2715cc86f28fc868ee023dfc724bc89f075d7af1d81ee88bf4fada419e2e6c016fcc5073369c186f4427417e150e2879c86112c13f61bcb9a97368832af2a9e06f182ad3556bd93b434f03e8bc507dd231d7d56de8a0de4474318f9b7a613ddb4357ee4b37be0333d0f2c8d747ec185dfe7f69b9084d5b6b53910b01a74caedcddf79bffd3f035cb1d900a38739d7f45b29e9f7418fa07353f7a3ff07025d3fbdba2621b6c7fa0569e0a74292fa6d792af2b25f804176651387b73b181ef324aae94168a8f4f81d318f06f9ffd617de22821bd28d1b9017c6e7bdf0ececfbf18fdfd7bcffaf2bbadb522714c3d67e7aa12bd20299c20507abc7e2867ca6b39d0c253ab94ad8ccc02688fc7a887f802fe5f5a8c284a3be640362fa8af2c55a9ab38e923c4246603c39521a751d04930b043ad947b5022617dd64ffd8982c3dc153abce2937d5c544994cd69a2d54302e69e97a99e9f42ee2003dee33c67545c6b4e57b6284fff0344c7dea9bad1f014b335d7d39e2a240b6183e17dc9a0db7c17ba5cdfce15eb1f0733d059251e390353211db06071e909907af6ffec9e52e02eba2def6e40e340cdd5cf4dbdb1aa9c3c257cb22930936376b16ef0507963a54570e8f02c7e0525e83430889f33ecfb024ac44cca9937a9bb746a8ccf279e5d7f748d8558a774aa7e3f2803f86b0715d49345fb36724d06de20f8b0606a18451c662bd35601dce441b86ef1907022ae11a7c87341f62d063e5dfd6be5dd1225101f5412d340003248945c9fb9df7fa265e84679e600a1abb15c0dfbf45c050331f43675d8ba296ff00530754fe991a46137c216298df5706588bfa80fb779ba20a3f1f94c14f5832e6ee1afe190a1708c773796878f9cfd4b5a6f6ef531a31ce784f5d531c9ae1cee7c6b611d17f328e69953199c48cf902f5a206865b5ebff55090c66710084e8f0372f80c51419a77d12b388c00cc840428809a6b83050a2a660e3e5367badc8572db4ee333beeedb0139d72e103c9d9a03ed7bbdca9d9a06f9a17d4eb714cee7ed62df778772e8a81896454a1cc8f58c34e3fb74a60b19691bbfa238691c624279862ce434b8ac9e786734e2fcdd8acfc085860cf992eef37b5def6085accdf6f284a824984cfa1600ea710114949735afee0951e9fde04f09761c5a8a6e9064ab8f69bbeebecc5ceb070af858e9837c0747e4511542e21cdfc99556051ac94fa44ec01cb2e14b5641e5e33a229dc858246dce5339f74ec46d44a5e1dc8b45cb5b51e075c6b40d395126c5883bb87a4c62cb13c2b7d9a25a47afd0de4122c6d15cb4578539fa51a43f1cf6721c770f8fa4e41db134df9fd1af59a4b74cadc5c3420aadf2e02024780ddd1e320a1af632b002aac3635e75000feaf41b5758cf62eba81783bb7a3dae55af842964ef46d1747390d3f1c80e774e42f53fc760e7da7f097fc231942e06a91deaa8b8d18316e7d610a1aa27a73a58cd04381e30222c033c3205769b4be4de8545371a9fc4b2626b4b165fa08a7d0262ef024a1724e06077f1b54c6c4ee5dd9dc6f3e6a22b17df717326f689cd014fbf1e14800f14e1d4b04225288265d303ad3a167951599b43ae2d0b0a111cbebefd654926a4774eec0d3b97288167f6bb27e0b96a0f82faa8b2e13d0d4b8301b16aada9478cb8539d3da5797ecd60206f6295dac05a809b266ee39965116d346fc8aa4e2ea0964d18336412a14d1fefc763590d972acfd70e39900602942324b7ffb2142a1abc461dd50717125d06b953373f88bb64c6f72c4d5ba14a740d68733a11c9a22f386979ceeed2653c5d5b13b61b8fb3c67560a0a187b443e4fe5aadf7dd9dac47390c4437569894e779df2b5a7f57d6a4ba449ffd8ac9438e11fd3c88bd327378b4dc410fd3c2358455f1a9bec3241dae84b3ad5b6e847c370b47fe2d82feb88387887595ed99bbc1ce21dea7b189e98eee403315b48fdc1dbbeae94f72c9cce4d8a6bcf420c57bcc13265fdc9ab4954f52aedab343de063298f3ae6d2cdfb5ddba3ea6e27dfbc07cd807f2f2b817e6cea1f9be30dc53ed779730973c903925c69df96ee75ad45c9e01bebddd27b72995eaf50f70be3c6eaa85dfaa490ec1aed418531475c64231c2eb5fbb9b070da55a27191703502c632eff3fb6e8fdea0fbd0866568105b609b9806587af021b0e1f2f827354369587d78b5e7a7db95e2146887f4d4ce1022c670f269a2abead3b17199fbaf0ceed703e06d6e56891d6ab642782e7708917fc252d0aa663a8c3e63d8b5deb91f1b548e56c0aa7192e82826544aba0bc698e10cb4e9d1c0eaf9bbdea40ceef248bf896f25b40c7fa8b762d7572d197f4ee686e018bbd064f29f2dbffa32ed90726d183e05cfd02a84767336a1fd7c23663815093f2ead7620cb68f0760f0c2d736c9fa79322dd66f5068770fd7eaff4da3a96c887ab32f2d00454efd616e464a4f3518491fc0c24d2dbe0acca9aed5fdec2becd6910dde915dd61956685374929a46573a2dc0beeb81cf90f799a1adcd1324f547d5650978a904bc17270f8dfe584f6d3e8c67a959eb2ab7b952bab14511e45922a05547d128d1ec686145c53c3ddeea28a36bf219e18a95800f6231f833e113c0c832e21bd480e6a906eae3cfa060f8831e3856be25caacc7f8e24f9d68a18cc001d85722fa9463ac83eb58945d778c474bc1713df6a0b7a00877ca660ce3842b31f2540288ecd9f263d6d4c5d7739a968edb4aa81f7b945079434c09050ccfb2052492239d0e509590c731dc1ed1596780c94cbce128954f3ed4b5f2f16a280d1c5a0ed3ef01dd9ca46cd0d9930076c4a36f95d15e4734f7473e9918935b63e9529d79562f4fd9876127d95bb6c3400fa97d2b756856e6ca37a645c25da54aa1d785afad3c78f5763e4af2e1b038c02e3e62dfa549d230206f6db499367703bd96645701b1b82f2dfbb5fedad839168767f3ccde49841f8d901be51c2369ffc9b86a7b57b58ea47aae7996c80ac16beb51ec75a8fbe390a5f1d566c03947540a3ab66f7c0578ab3c5cb3d1b599cdc572d6e425665c1122b3cb52f82073c679d6f8ba987c3c56c3aa3d9258e10eb988f595fadaf7a8fb714486cc032fb53824922ec3d72f13613dec139e19c86297f5b1b80066a3e7c6b3d875ce6bdd0a5e4f59512ae4fb5a830971e8ac84609f2ebb0a9ac0c1dfd7d7e475497d42c11a7040ecb5b9e4f9087bcdf643fadce4ed61e6c17aca59ea785bd7f4f6f9dc3eb675bda3269cbffc212d62b0920a4a947815ba31b22a7068032609208024fd1420b86fe928dcfc91cdd7b306f70a6c75397da793aa2eb240ac0499aaba8ed640acf6ca08ba70110fda1c68839b2796014ca340555d0ea0bff51a555d06b2453cecfd1c3fe040457be16ebc0d967fcd158437846a4e39a6d094294b018a2e3ac7d59e105ce86035f05c938f245ee1d730298cdbd17c55a4213fa39ea93efaaf8e0212750ebd525b97b5eb24dbaee862cc7de73d9020c85bc78cacd74e6aaf500ccbb657a9aeeee9819824ef74f6558fb850570dba49f38a33510ed58d40eb5ed181a347e8815abf4e7caf5707b9257554d49f0f950c76b047664e448687285da13d0eb8850b5fde767208872ab4b66770be68a5594929f497c64a65d2658f007b70c17ad8e28ca3949400a8b369b57b595bd78a5a56e12c58fe22ab7b8a15bfbc2cb33c4fc03beb67cf206bd86809d09b7d98e669c3eb26ba485f8d5f794d0046fc360aa4bfab02a50c129b7b390b56414eea376526659e01b9f1b923965c25b1404c98bec2b654576aa70b30214b4c6070acb71ff7e8dbf145b0a912a829940c6ce1cb2e8da003060d50118ba6de5946039661342c445000c91f4bdaa7a343befae0498e685e8c4be658eea3c939322d3177452eb0c1c2b39a13045d07f336216b33435c79aba815469009191840ffb406c6fb583434b3643d5d921c4fc5408de8e16430590a4e6681532cc1690ca642ddfc79104b053e859b4645a9f9a1fe4b27c7f07532d8588b512e0124326405eecd1c5377b22619c6e24f29877f6f36a0fcac1a7a3f8eca614067a55ef421497d32506443d5b1052f479eb164e908172796206286dd576049671a6ace25607663c262a0a2c06cc0246b4a1853f26e53f00c5ee4e3cbf2940560545c9ffdfb9c60f2daa22d0b2ac7977676e6ce159411b00147a51a42c7f7b4c9e12cb0109921cac212b9ccabb2e7b10e17f570e5f2b2d95a9e511a246461eea60dd847b70645d1eb8536d01bc0816dfbe3317f74785ced5409624fbd81c53c49520c4f0ac13555498f97c38c1eeea51ebd20fb2c126875911478662a22565d50071a8b85e560781b0b50a49783212c8c65af3650693129bafbab400a3790e71d65a16d62a00cb3e64cc0b36f22d665a666f73e28f8accc080cb490b026795f3ac4a49995f6f364a2c1c8e4d2ab97c1d2faf12f4ab92d8e6b6e35e2512f70c67ea236a05c55a67ce2e34ba708f324a21ef50609567d910cf2d0079b6418a758138f755a1e848d198b9c1a763f244436aa7305272fc5a708df17fd982ea707dc08045b9a2cbbf2a8a9f3adad3bae3046e42ac0654b468366e2e88943cf33a363f29e51c17639f5cfecd9916623415abfc58928428978cfcdffac16600477796885a2e3eb75ede17cbd1ee4d2923df5be3e94dea46e47261ff987827c74050368e6acd15aeaf17b8301f60905ecbf50274d27dcfd40cec0fd189d9ec6fbc1c61d593ca8701bc32de304eea365189ad683195fb42b7c62562c4391b33eb960df11aacd957d11955671a741af1fcd1834ba832506110a51453387c7d68f049905550120684db6093fc2e376ab92726e944272f92c54ed9f108de09ee8b8fc8762bb926c04e459b476d88fb4b9304216b5c2f738def7589368849250026f14ba30e2073c2b83d8f75f702318a42ee8aff18fc6306c6f73e39f63671bdba214045c8c508936db83d8ef349b7b12aff222a08295618f1222b40a2f30b3626fb3930ec2c8445f902d689761d7b71bf445006e3c723c1a40a505198e6285807898478a5d12763015ba061bea3341ab38faf0fabc9b8b1232c2ac4c5a37da047491907e910ff3d82c66e3320283ade7b87a0fe48aca078952b4af222fa48e8a59577c7a9d49e0a814ea0b82d1db792e2a218c9a7dd92f69d556eac2a221d430443c53dfb9e766a07821edd4bb66400be005511986dc2ded50aa9fa4f5a4c466ed5ab0ae34a944f40a6c06d4862b848df4b893deabd4920a8f9a0dd99345a665923bf33790b35ce07b64b2e8f3f6d2aba5f7e75345fec4630164643cdc5f41fdbfeef62384947023cc67375febb2cc75e5c83c21424a5fe24a15b511f98f2981306628ee197baf77d7830c298e7df2744913a80056b9d040aff9af96896fdaa437b7bb82f64e0ce61aa06c4b6634c180a6b0da2d68caf2c1e633ee9cf704655f48721eaf962494d7cae0e7fe1cb00eb4d298c873bc6d761568e661fee4fa06638fc86d5cf25a607ed1d53a40109cfad398325e70afdc48ce9bbb6f093890f3967cc2f6d9b111b4689651dc209879d00e4e68538da47a9f067d0a11f1a0ea353cb93a4d182e363d673b0d3a59ef13ec08ab6c4898f8496a8a09f49e9cb728284b1844df4629c47d09c90991d20069dedf75f889fba4704c0963e27c7f9a4f6a7d06162dc6ac10cbc07f4fd0e0200f34cf26bc24d782d0a026e0e30e8445e9390da239ff4f00486f162c45b86da762ba40fa36896bd03b190a50a06b04d0a05ecc379014f4937e6c98429938084b45ef3bd2efe03d19879148692429ee40dc9bb4e476511231e930bf054e5474032daef52d61adde609f9a84973890783277862188dbb69a8613969f551f6228e2714fdc8df000ec92a988d1882d865c8ccf1ec69b034d2cef704ec510890e845d099d999060f0e96b5a10e88d556060745c338533eb7d0b838116e555818cf009735b56cbaa26ee2cf2b4a897de93cbdb0d1453af75f5abd20e2619e15369dee064b555ea1113a1e28c6e516ef465823c781c9b073600539376c9db1a829b1b0e4db98fea241a1d81857194f9f0e6e52a1b9b09c49f46c8b08623865ac8042a58eb62ad996032f86fa38c95a5951aaa39674f83f82cdae16468429e2c3282c63ddac98c38666b076d704dcb4bb72bbdaed64356a8c56000b5453fc80ad84021dea26fbc9483b0bbb0f993408918de67402877a453224672508dd36972c00a78adb2004df56ecef3139d80ea9c2c4099563738dc152b7eede3413f938d53a7c22acd1db359070d4795498c8cb2330a5ae21e56ec9d6d66dc590ff49c0441879826cdf5d3f5d89c8e8710f178e135bc1ac8cb24de7fd23d350c2eda84b01a6214a746efcf0dae462686219c3805ff66494ec4d69cfeb7e608d87619f9f3d5ccbf019e5d228c6a1d9a15d902b60dd4a6d72ebd7a0c0eb6710e5ab013f0613faa090afa930898c25cc09528b44030d2af60de0c8834a51370f849cfded966dbaa61bf04d1b1d4fe485f1122b3e6252513bddbbc5a3b5264c759a49941f7d7e214586ca401cc9d4d4dd1bfaaf32350fc6486bb0d22d0e8851afa22dcd0dba8f3d0aa60190169b0980216f34252187a5bad08b7b19e2a2f8e59233f69cdbe925a2e8bbfa635071575e30fffd6d2ad0dfc864ee86d9bb14513cc8202aaad7f82120d525b00626a906b7d852c41a6df1c17bf1073e109232e30f3db957d7e7566b7de694d0c673754298e8765a9d97976f44cb0773e0566740a1e0489d405219a1ba19fc5cc9f8abef8b7423862d2779b8e9abb19b109dec7b5079a253609afbbaba7879a03da8612221f0c8a2c4c3d204cc7ab3a8f1965374388a9f7a94c1f2cf022b3fb5cff8c0a35d7ff0a51d2f902a295714621d819aa7fa60c1ed787dea6ccb3961541a26769039be9e9ee6b7c0f1469ed8030f9d64f63094c4ef0502b20f1fa9c513e65e66829b1e7936b18e9eb9f7c180c80b025c96d49c22feae016a16650a5b099f2c0ce4db4bf5b86c97467cd37345403060825cd333909978d93e57a066ad6899acd09c5c517565202812342cda186889f80391cd41a0799d657388749462d9ce25138161b505fee4938ba17770d04c3b689eecd51159dc0c02d0962a71e14e0426f04eef5ff5b22d4d1cb5546b1a58b26c0e2c2b5bfdbe0992c51cbd7767600fbad66e6c5dd182681fe9c613d68f31fc00bb00b39c681e411b2396d125b852c2e0adc29c04541354499feedd46329229b3ffd412ebc37646075fae66368eba2e237ba646c61e11b8e14e1fcf9abb1495bb614d1d45390ae4414ccc2c991e6b0a1c6fc20eb4e2c3dce8db1998ceeb607321183e4ddab3ec49bd2e94de4965f43aa0de3b6eb7632f8ec3d8ccfeaa33a502f7a015d0b79681ca9caaba5ad6d182d27101bf14b5b0786cca9a3ba025ef9e5ff4df7598d0462ed92cd46a0bab22793148cca27b85b1ccb470b05261f0c3a39c4406de6fef7f6386116ff5b0b5b56b752fc8ef18194ec4d799fcd0c0c6f61ba22ebb374a01783dee533df1095cb6e91d775bf1442c8ad7ac447768f39eafd2058ab8e91e2604e0bf82e4405b386e92bff7bbc11f1dcfeccb2ba3470ee5fb8b9ef2f550dec104b991c325075686ae4284589c69a07ff948f9d9e4ca544022a7f6279ccb2ed352ad4bae9e6e65e9bf34e6de1941209e93bd3507acfdab285d961d473b7b16d9d76fa0a5adb04373962582c83098551d512a6baddfd23ea4181e401647b799c38dbb9604072d617edb9601cd7bfb6dc912f96f8f19de6a01e4f61806d986737a7983be290e3b417a45dbabc81dd355f38084812ee0e2fb66159fa67aa63efbbf157044ea181062b9a85d29d26de6301ede0d1c096a9ac16141850e834bc6905c6ce17e12c0096a28514463fc576be070ac037da5e30454d24369620b5dbd3b4ed6829d3060bd02b5a9fbe10b18e49eb926d1f2f6cbc714cf49c2a0046c6da30713d62f44dd7d71e53bd98a6a86e1f446c325bcbe1290d598476aa1a3426b62a7e248559bfa573d03268f9d2a8fda8138efdf7b7276ec25a51ed1567265414af8631dca963e38631e93df9e6a5be373a672760ac6cc6d5f30662dd7de7dc7a75784fc02bb9fb157bc3b76d9027bdd5e195905163149d795bbe36538c00287f67406191a462c6db3b3d62740d181fb6a7697ddce33c50bbe574df0c624085791db6a8fc3a482f651fb613f562eae59d23f3dab0dd2cca8ceae4cdfead67e6bd9b05a2c82b832e1bfd75c1cad825e7d2550b274276740889b18e5819529a98047cf3e73cced1232151afda058c6cfe5bdbcd6a230c3ec0a63b332769a30124806b1ab73b7945091b308a3c8c7c452a6ee2526d2d1d043518282cd1dece5b1a778d978ae3a1eab2b96352fb124704ff7a6e20f3da9a1a6123f4e984d33e9517949eb17da46825c5a83e4215446dae239a015512a7851632a3a202f0ea8f8b186f4356c6bfbdd05a87e0e67e78f713334998990ddedf2000d18093088372202e96f22ce482ac68c11d239c5076952be9548dfe642824905b24ceb58d49995ae0214b655630b44473c0db8b6f4f15160d936cb6179b43ddf380acaef00875f0de106c747a3c88d419fba00135e3dd579a71346f4ce33cc2d7a9dc1f1c88d2d585d74a98a63945003fc6f9ff471db7ca582098327f2217c099843bf4ad2f82e12542fd4b2900289a3aadf1e81c745db4f50aea27a196057d68c0e7d450f4ff72a460a512ca60a06bfbb4ebfd1d844c5a9c1c5180177d86d40efd6ffdb47310cd96230babd22ba62e5fcb879e39193a1abfafbab906f6b0c14fe2ed9f2f15e76bd84d2e078da2b61cb13023e941725d6ca63446eb370ef99098d3ebc9c6b2e5605a3aa24434ba202be22bfc9c10ab01f110e0c7dec7ee0673a087703cda24e68b33df6592c62b3aa55013a49306423b269b8db14ea2ed051c839ac3c5b57bd01d717cc04aadee10dd53e30040728e723185b9bc9c9b813fca7b9d8f0e06b665d1add0390f369e31f0034e6b7f93746407abddd81e54e05c8167e6ee2c21a5751680d6d64ce0925647a50ae00c9434c80bc34224273bd40722b3680fe26ed73a990f20bdfadd20612340a0f8dec82262bdd910175c53c457f4534a14351a4134c213965c8c5fbe9e776fc1abb9c9072af9616d2e934928cc6b4d46ce86f6bfeeeaf059e3c6c67b726f3ec960a74dea364ef2e607e276bc10512b6add49da43e47de84f6a1686cae93442a740109a251e70463d79bff20f6a8eb31238b7ad0fcb0f425a1bcbe6a83e1afd11173e365ab8b9191c1b70eb3fb979fe8a4f0c959993c44e68789a89f52b2bd3cf4d5a19bf85575a9c97f5f66adcdd24416185ee936c340738f68e3b4cf3b06ffa69a74a4d62b966b18387311fb786d6361b53a2d4ecb7e759011afa0b6ee86103548f48017d5d3028df992e21bd264b033edbb0a1f2f70f6323eca4a85fc15e5fac1d3d047fc5e762490255ebd8c0683976a6f2e6451325d6e08bbb8340a3cb871b26693c6796e268d926e5d54fa1a8ed277989e1fd86b892e152a106b2d6417e4566540040ab1ed099818015dcee788d7ffa55610d63a31507e66397adddcc35d9f9a82ba9ba4b5b1e0731d63fb1126b521fa7d758c329226f029ef6df9ea5ef787b25ffd3843cebec7b7111714e06501f4740d5ca2e61a24ea7f7b42d9ea8b1b38a8e001d706326a6015e3dd6f77604c0a19c532661406c4905a5cd5d616c483c7d1363dcbc505463f97652e08ba17326a1e807e3055e7b2d1ec775d54f668799a9523cdf8d2590f9bfeea4de6c2cf8c3a17334da1891d57a9c42d8185e425343f41a0ba3b98ab9a4d003e5d0a4f0109c5f04c6dc48a289cd85078539ef429297428f5061c65325528335a7b20c867f0ff38dc455b583bf1f4d83815e2a96eb34c966af1677e36d9bd6dc5ffa9314df9a1c9e5963ff85f431f4734ce22d3a8be55e9ac810016573ac31dee82810be687d8c0d45d439ff32c7eee9c34a55f6f20d116701b08a3d48aa2b99feb410a90abcfb328ee655b6f6f2de5335e95069c10613c8bdd392d08e77f629cf6d068b19e4146e5d443b48153285d633f9d1f1484c2dc75e62c4923b74f392afe401f215d81d285dc9aa5b8bfb6f43eb2633613a4539e1e5dccab933bd410f11c14853c60d2f73537a9821d230d7ea0c52f75ec64ce11d51cd4f19a5509c462d18f153d5a56a8f9d867827d80eceb02e7d439177a5d3071049762228f7d6d6af60f1ed7b4829a2d053e1e683cb2ba12ab57227f894ead70204618ebe08d246c52d11cb70f15e698302ef7b87ce31c5aad0a9090aad6589160caaceb93e8299f27e5715459967f98e577a20052c2bc96f5e5a4df606b05dbe961d90a68a71a55c31d8903d489d7078f8a9c683ed70d4352430478fc095c57ef58fb322560a1501ad71b809046a763b8c70a43cb983ff2a7a1415c6b07e991cbbc1a7445e0798aa6859735428ef049363016f4d988105cd61e3c9908c4676badc587b6bd1a26105057fdbd1e699b9a9b1f2b12c526c2ee279ca7dacaa990049e7ad63f0c53a9914b010164382c2f9687fef7b7af337077beb9d640a1c9d9cade1a972b1ab129efcb9fc912a52078d03f98cf330c7afb64cadfab2b93477788b52f3184fdb6bb6f712f86982c7c1eb2803a4362f3cd218e2a7ee80c7633ddb64b087092351ae700b38b5465a057e2e8efdddfd47a2e367af903a313d9a8df4dfe203169e7c0df1f881c4ad6ffe63d2a1610ab2b147b47bb9af58d3deac4771d64a9eb6e89e0b2cf20e683958653a18e49dcd69c16b36cd39034700d28eae67b15f05356c3f3d701ba69e71bbd9ce66395f1915f92039c0135790dcd492719bde78cfaecde9c6a93803e4116c52c67bae250e605796c7b9cb141cdbb8c85aea434e8388f9a84357182de00297eb62e88917fe1b4b5bab71fd839918ef48d6022a5cecbb706b92b56e1c9fbe49b9405a88a2584340c594bdc492908ba41e7479c479df6d3f6b75ff88a4b86ea6976868d66378587596ce93faa25c66a837795c27511c4cb3f6fde6091614f4cd567bab274c4993448de70f80044f008c76ce7174d4f85625fb4dea6f5b45a28eee96122f2d3fcc4ab02663687036bc3d9608e9ccbdb0773dade8cf4e911884d9f1572870cb913caa319ab42c45804a468414f3d40113d1bb04950bbe93bf4ad809274c76ca502b3c0e5db2172b3e172e239b5d163c60f578dd934100b62fbf009bc3d78bb31b4673d7213a46da687271ecac2d04d388653094dad14c81c147f4a49ee060f38b5173b4128b3474eb3a81bfb1f5846c48801ac068f0e3225a38184c1cba33f91a0a09c61160974ed849622312589a5c7da85de78be4768e1363ed1fe5218a26ba0905f6dd7fc7b111f050e9408adf80bd00f8deafae2bac60f19ea04b664fdfd9a24b9ffeadb0ce78db4a043b8f05a8f178adb10ba2232e30bd5d7cd80999be2cdb89ae1b18450a5a40a13373c2f232e3774c9166dca8eeff625504c8d4d5458d2da0db043a5cb190829cf5d090b3a5262a181075bd071610f1a3febc70b38e6858714508a25c712b8ca430709e191e5605c79c82abe3f5dcd156b99dfd7c53c0da886e979373e141cf8977ef2fd280df6c977a83838d08558e78546c3d0a93c57338aeac138c06c26172fc4fe4b38497eec5f834caa65fe4d5145b3048fec4f812c0f66b37ea11d201dd0029783b900fb21c0327cdd166a1e1c307c84364d012ee39fe04059112dde1ddf05454a7871238e45e69645dd649bdc921f060a2de7b68a8d7015350333450a3e5cd57187e5228cc9b7de5ca5d428bd61c9fe64abcf1b43bc9e335a76f3885a0ad906476904b1dac781fa9db05a3d1391be2daaad449cabdc483a79350e909847990eca042c538c4c83b74ea1063a47112b6af25ecc3add7cc6dfa563adbbadfc63e619d9b063a8098a819cdc45194f70bfbdf7901696fc0daf5e407784c158506d370564b1a814a4a7182e2509ea139beac8e44beaccfbde739b6cb3ecc6019af8ac6d115095cb56f039bc2202868f644755abc9c21e60cf48b503e853d535f417c9171757d716ebfc30520cb4648437f9ca3963b04c7da77e5fbf4f7b63ebe14bd113afee73dad00557547482ba7a5e0c46efd5af86e44ddc5332bb1403684b537f1ad9d7da06d00ce803b1934548378d1d06cec777b0befe9257e14d327260ff3201fafdc7444f5b15c546e9d55363a75302ef6b184358c678c7f6afea592d017abff6a67ae1df8083e75667a423cf2033b83678f2051d98edd63f56d3c5953351871486e5bca5df552b7f888f53e2d087ef1457f5813ee8529386587a762adf2c802f0767cda402916ed9586f5f8bceee5f433adfe7ac1119f656aa0ccc4cb046a18348c61471f7986b2e84af3353c60215037d5b1386b6cd95c5ab37d215353bdd321fda85c6637626f86294838f21b3bcd6bab439a592784fb83247345c51894b8fc83f39b5db053107132ee40090799cdc41900f577d627062243fb0f6ac2fdd021a828b48402c46ecf090dd94978fe11bde4d40d0f00405340280df5cde43f1ed2a025657a744e71c22d25e04bc201ddd20beb8eaf32b30f690d6fa9a1a02cc626704e8b32138ddbda631c9e9620d70a3a3e83280dfb1487bcb16c3ce81a810e1611dbe675f6555d123ae87217a27095f18b47b8cb58dd44e551eb4b54623c4282a69b88d54fc6e04e0a13d51dc9461a938f931f7fde18031bef785aa16e3278e13cf3dcb712a447432570cd78707ac98661033c6fb22fd44e6adce7fe30a1e308020506c5c73d25e0fe75814dd831155cd7dde542a6a4f1287dbf9e1ea79ea3dea1e78fc8d4f20c79ba0747d187d5a132024535fe86334aa388f762eca0e4ce498d978576f0307419e74c45998ae3e73ddefb84de251dd37bcfebb579b95ce8664d7056260f5bca0e3768d01b607c11321a2cf2187c2ee4f318c7ccf4dcde900b3c659727747d2ec528f3c9c13e76208f4020778d0a3c0c51e16dbe653346ec71d8cee71630ed69b4097b9f78cbabd0bb05581b8956dc853cb9a3707edc49227656060c34fe3f60b994f6e487e1b1047f105487e8898b30f3ea1cc2bc8e78922f2ae82126b7d7af5596316c4180f1e0a1bedcda9be7e82dec06e8db6f148030b832239c6b5c629afa53225ef50b4f02cd8bc965270fc814b67f08b76dccac8d8f2650da286d6845e603a608a455455ce66a02919faa01612c9f7535ba7fef713309fad078f2e157740116d02f17f14be0f4677fc5931c5d9de49b251811fa03ddcc4c5165136e41bae56a1d3ffe83bfc800a263dc6082fe2ecfe15ef5353567a824dc3703a6afa96dd0fa395758603a064dcfc72d8c4149eded1841efe314a01e9914faec47084272cade2bcefb0ef7b4d30ea927be12b1551d2849b4664d955a381db40757e530d7b4a3f6f86d33073ba4333bdb907fa2a3a63568b217bcd641b8fe6ced657055b31fb9f037f706094d1df567f60f39850a936d0ca4c81aa4532e987c72d30f9e59ba57406f7dcd1b84017affe2f9af56ceeb8fd45a3d9bbc98545a65bbe48712be9fe960c63a25ab6a6999b54bbac29e8f1f6315dc302541a060a3715bb2f91a949cb510e48ea8bcf4cc1c21768bd3d2a12d40d01489c910140c869bb9897594601820ed3f3feccd57b7b3c503c0d930f1182b1b1a38aefe13ac206cf910035ee7e4df6755cd09454b5b642d3a21a09040d6a0c42cb6721cd91a8c894b148c232ac224a41f697940ecb4aa220f73b001efddeacbb642d4f56053e67b2e4c560e7377047809fb85ac622c794c212b85f435ad6c4884e132f22c474768b73fa5ca69d763c78ae74192ab88f7c8f67f8df8ae381ced2769d67e23f6ed00d86473c003609de6d07a3d884a32f9ccf8684652eabb3dc08e0b99f7e7f5b31b83d2aadc707f536eb9e97f682e46509262337d1e5e17c627f8ded0148b15a6764708b00601e4615de3027212e4321abfa73c622d85317c88e5d9584614f4cf23680892c1c506c11a0908e7e6b793ca49191ac00038a7775e6ef0cd5f3e3c7314ebb16ea6698dba5e947cc808b48b300d7462956f6a3aa1a49db126d2a612b3022592ca24638010569d2e69d43843621e8ab09f4cbbb66b8734fe2d8df62807f20e17b21502f9052a101e44c87e5263a40021faf533bfbe313974b7661dc2598055c55c0c83a660f9b1417aeafd372fed6a202344e15e4c8e87fb0cca3869165ef0f36ad7170f57c09cfe824b671be17166e360dd040317fa65b6bbba50083b3d782d8eb48692d646700f431793e29d38ae8df0238a4f5625113e79b98e672130b4b8a0196464627e1edfda6146ec714f906083b54bfdecccd65223ccbd07fd86870cb4656acb541007021a7bea23027c6be2f6e0d4f91d98766f042fef26998b50f52a2674205d22396a59eaf2b8c71649a8e056189c0deebcc5f405355333ae3419941c74c0561774b0b14007ba9079ff9f40c4df4d788a094bac77f5fadb24aab7074d76622e47f28370887aea4c6d1dc87d523b365252d9efb86fc16fa321900616046ad41515e26c6f49d3dd3f560ccf85aefc5c48a04320019bb596fb3c901c766bfc86757f4725176382b301b2d360d9d120c16180ec60b07436b371757f4e824d53132653417600e15775de6f1c8e6f36dd5f1e21f71d0f50dd116a059edb803cb789d03c7b431c0b003ee483bcaaeb971b072382b703b2d761d9d331c1d67a3577078b18789a7b940f17a643429b7192d3c649b89951286f1275db9475d55cd21a6796a68ecb0fb5776e0e1eaf9f2a76d808360ef6b3c51f76bf1d768c7e56df7d677018c76ff10aaec414d58fd882dda02c230ea249ae1e0d1b886cf1cd93d5318c4d7607b577164c8b95a819e4487e99da2763e6a6d55f4159f66f186092d80f93a8dd4e9d8022f075cc01b56d424518bf05fed964fd8423cae7dc25478968f5ae8448bf6134b1d89731a69c3ba730d505987a8fae1ee1759272f6c5fd112b33a4ebca4e122ff67475f659e562a66c84ef9a6de37436a4acd1aeb5725f8c6831416cabcf8c4ca3aaf2814fb5a7a10e6f94a77789d01d2b71fc9396b3b6d38003be261f7290be1ca2c1018f432c0951a0f1f2a9e1208558e4563227be3c332da12d26bdac7ce02edc29c34da7344600448b04c3ba059dddacf0be424eab0ef2cab9e4800eddd247571cb18ff6d07d1d4a2c01363d6a8d19872d748e29cf9f26b3800310769dda6ac68319c5263ab0711c9a0fbba4d271334baa9739e4ce4450e38b4517d572498585b54613a9b5ff94ddc35c1ce11d13f3eb60332962022318de3141c39d000b03d84039d9c00ba60b676d997d99223eaeb3fda3f2ff36f3e0df25c58e4976536b8b3771073a59215f4aba9c2e3e1713bf6a659c9a06ae6220794de25fde6555341e00aadbc806e442260faac721d9bacdd710e887402d99dde28284ac60dc583f0d36f1fb2aa43b8dba23581ddcda33e26dc5ae09ad356da98940681fe519bcec25aef6132e54087f437ab9d941695d9c92adf9d8d904e15246bf2d9cca2146a29b7738f71bbbaf0f9a44aa8564d0e717afefe421fae29a841dd9b1b7f6baf1c1e688d7ed23f47c0c8ce5189acdef3422ce7c58b01177494da04dd31ce7ad3ccb9f78ee1e125d2db4414f5d15c7f09ef036ef9f2e94a0f80c402f0264d44fd9226a56f229ca6c576e2ff71cb86441f1c689b06b8ceb0dd7caf85b82e3a9efbafacf34591dd73d689fec463a1f11f2a85349c7294bc99a6585c1032db728cc08691d99dc5888f081601243ef02484e938af4ed38bbd6bf0d349e425800a2e424b602088d67c8a9970b158dadbbd2779026fe1e877e036eeef79ed82f258ad63cfe5006174ec9da4fe9d22c0883be069b9d20eb127308d31a363b7844aa5c3c3e405bfa6ac2dc5ffa74be0c58ed62f1650208b0cde362cb9708578904628c80315bb0d521f79dcbafd177527cc690c702d9a16a193d0b655f8c3ab92bb22843650dc74167205404713f6ea64fa2d0b70e8a0098af23d6ede4dbe26fd38a02189654ed8c523fb3d12bcd3665345a071c6159594a84044726ca62012d9547fd9c5be115c85b1b8984ce0713415773c0a51b8e135aa4dfa95568959eddaa3266dacbd5fd106935e285a894a9d7d0a9e5ac28d63aa3a4ab7470b29b6e1cadb0a3111127f96ded162420edb795c13991dcbd3971ada597a0a4f0e895dfe518776e1e9b61939fddb0123ba5d75501ba1983b5711894ad4a4ed17edcf5fa9a442930c2630f23d80068ef6645b7724e9c3b0f1318a0ae8bc3a7c02672281edc9b16d630fd4b1d84de422a783b7ffcd758d0b04c684b4854a09b2f0a39470f2555c858ad088f6d57cd03a9e676896510c15cbb44f2aed2ac5138c3522b54cba4dd5a53bd5a02f34d40d61f254723b05557b0e291faae9aa6fc056b6473601f8a882a261567979df3b08421dffb10ca6296e84fea9f1c5bd3ec6d011bbb6f4924a73f39c3ad1cdb011847ae3e9b9842aa654f4edcdff8a63e17280597e2b2aa5ffc7a0d8ba7b45c68dff41ecc6fd99c4a75f56b616d9109c7b86978b11ac9d8c1a4e4ae280b9712024aabab8b22f5f56304ea6ab78e6a3b029d5c0a2e61234463fe89506b44a4af04fa3874c86f6ebd0ca96fabb25ad8fa0311906e00fdc65c0785d9e9a3bd053c9952e5a34ffcc5d298a35cec3cb9562123a026e8f1e4e0c3839cb042afd09d35ecfb25c61f332521359aa7f6c9240f9cf03e82d731f9f5f487c6352aac0314d3021adc555f5d30522a9b0ef1e28426524ca8ab663d160dba674e3ce9ae2b07ef3299aa251505685066cfd96aac86c2e88aebf742986220d1c260a817bd5a040af1300a49a1878ac3a36ee8c6b98ea1cfd6ec45363dc2c3881006697befbdf79629a514f205ec05b305bcc1aa6f321d9658d2df45ec244bd23699d3b4134d6c46b5ed88924d4868a64454341372f992679b6c33da8e6c421b0c969c6626d995bf139e127a45f589e44e9cc9694e482152cb9d546772a6ea3f7da6cc6936b02bff15c3f3292bb72935e47b20a7acfa2f973306ed4f6304a095181512d28c9413261f274c4cfe32605e5c5a586241b11382fc6867fca9e850f00d7f15d19fd819234b125144900aaa3f92ea7f1a8d6a5852044be2b02bff203e1dc6ae8010fa41142f96e462497f1fee7994a652ff2c9a5ab650a5a9e536096911c36215344755152ad86847a5763763a2ad7e63d0d94d6b9f20744b4edcbd930e688082c61c6b7287059e1d9511d485c9c3d3625848335f3e561f17cc916e8ed067128428c8d177baf9afb95bdce11102ed6f74706c1e4f3c7ade77ba59c0d25800696767fc4b7e17d072a1b8ff8e1dde9722eed0817a17e6a7fc875ae1e32aef8cb747d5c3538b5bed10d4b7f1503c3d288ebb33de7fe20b5c5346158d0951a211954f51e93fb15869a7917d5040594dfae7657ff81e3e2bfc920c59dd0a47f73c4a6b18ee0afcf04330f47ed206cbc349b2bc479c26fd6f9af45edaf116bad5dedddd9ca404a9cafcd73e511f9c73776fef2697d58ac024454535a31e5a8c2c94b6265ca9d53c218618328218b80bde82c80b3943414008f1d9284dd6090e2d6029bd89113257152aa8cf76581097299c4d66a276c9486c1f765de735c88a60f275953b13a9b00d3336628193daeda4b6f3a6b03577cae4c632c54293debec6a063f7919055a8a0fdfb251b19ed380dbf7896aacc7369ce1d168e2a3f4fb7542a03d134bc03ad856e6dd211231fbe823f5ad2911ad42621a63a7b5ddcca335fb10de81ed5ad658afff58d41474fa401dd2f3d5a0c18f5ccdcf4fd295dd0929b18caacc9229f752b759310e8696fbeff6e44108340552b8c0c60dfe6994273543e4785a69df1dfc6f042cef33cefdbd7f721d8034e15f0555a54a15248814853535393d89432a2787ae2a94997265331bf339f128e5fa94923b1318d59bd99a8de43781f48d44bda08e56d14f6964975eeb899c29c4fb8ac03dda4a127d5b9981e4dce9f58932a1650fe79b58ca4d2f78b240ecdefdffa8790eacfea968f9e4769ec4b560a9e47e79093ba4944c84ea89b34645439e6349e47692af54fd24a52c7d6f4cf3f000fa9738917069dd58529764cf56b117b8ee4bc8c2edfd3f2348a501dd7e1f710ec979f2c3465748a1a9f54c00a1d17957548175dbe4c81638f60046d7997f7173ba6ba89fdfcf21268f99751022ebfbeea98eacbdbb467e4e56d34cdcb4f16d2ab493a6be46efc948b4aa37e98e73c75ddc19973ce39e79c5d4bd7c9a5630a99f3873961384ea3e226a7a2f2a8ef49993eb5e5e72e94da32ba5646fe11631c63ffc6719a14d88523c37666e280f3e7fc9df96541b7295a4165a6d71c0d7474c7409949e8349acd8ececef8cb0826b0d32c74eb7bcfa18367a88e584644cd41d5391acdc66667fcc313db6c0daa04c28279192fe3431aa2ea05044710550fa8f1345ef532f2a8f134543cfe47f8df9d5919550b68797e7e525fe8ba81ae726056fe960752b728288bbd33a597e2400a1be0cad230ff29fa80c9e543661a7f5ffe357e5ffd32aa1ef05fe379fcd7a8f13d22fcd7f8ff1f7b667c8dadf1e2f3f81fc71ed5ff8b63cf911cd5ffd813f335c67df1c85166c840cb55a25e8aa6c02e28aa964b5bf958f8de4029632c4fd1b2352c5ff2104e78e28c0354dd405751dfa807bba0a7ca309f82498daa1552fff22aa4c66d52060ccc7bfff240eaf730e2e732f234b9b38526b75a001c3bd37f125d402cc460e0892181681d4ebaffd00474541ae2be4610f71523ee4bf579b5db978e163398a63fe1cab8b8870c031428309cbeffb3719ae7fe24daf8f046da7b11d2041b279430734300374455f8ac07f21280a80a6f7c8e00fec6877fe3c6b36ea0f040588fc2e7b01e85f1c71199ff71c2b364c61cd6a3f0288c3fc86701b9210001bc0922eb4f106f3c29a2f0366666666cbc09e30ff26dfc8f137ee68198f0404af89cd597f0fe2500e04d187f98f000f81f25fcea81bcc22e4124c09b200ee04f10713c2916e00b20aa38a44d0735e941429b6c9b55ffada85ba51fcdba45f4bd1f756bcaa6d256444b9739cc5d4602ba7de92815168a0afb44856dd2fb243b653dc6e6e019ec0d7f0fdfbfa3fcb04b363be34db6a66b939d014717ab9e8630b963d89533d9b08e01f1a7a880d5324505cc886398f7d33086a1612a1eafc2f97124e75ba972def3bcb1c7cb540dc0717df58d2e5f398f8b391e4beea1fa566bb8bdcb33a5eca9dcc8403be33f846e23ce78d3e4d65b1647449894c44ec0036cc9107c9061a526fe61d09295941aa502d7fd2a003c9abc05ac96bc05ac763e36af79881562613db4ccc51b610d1bb94993fecb83eacff15619a5c29c356c14f79d08564f754c0bb5ec09a526bd95fa951c1c42a78f4749c29559d7d1a36b7be609db85fe685ee84f6ca157fe9c98819df1f7440cec0d1783765defa679c2eff9fef43de02f9367e4ebf7449d1ebdf2f0f4dff784a7ff9e47f8fde9797c7f3a7d189efa1576037a5c5512b45c261e4ea77b50605b0758d27fd4b133fe9b8b72bd35e91f9c1c96252519aaaeba494900a102cb8e81205210c3d49834e121d03b2684fbc42488ea34c94c63e11a93a6a1cf29292a2a2caf32aafa577e5f3d2e9f32aabae579a47ccbcaab7c4fcba7742ce3ae8cb4855d46d95c79069ffdb949b7fc9be55bbee507cb188295f108f840640da482a3a3de1f48ff27d3e4c3ac44600625163908d5e2096419d3f1855ef17b3bbe62cec72783944f797e1ce620670939978ce4a4b20e8bc5c2c961d96091a806a3bc4e932c43b8bea27aa139ae7d7aa9c7acf2ebec740ba6f2c340e303d68c80a23207618450e5e7e1b6f6a1f2f3ac80b29af4b1355bb97befbd6edc1718841a89fdabfb7e1b4dd37127f6e895b7d3640fd761b58d6e7d651f652eb8011353122d60814cc935a51a55e2d7f62f5f4197669064ca508ffc31095ab23c2f06ceccecceeeeeccccececec3f4d9ef8c1120c8911c90d03bcd9d9bddb1b8b203e3423f18a27962cf919829f109205121a9ccc40d4440a2366109a2205480cad00c80a211560b14b8ea2348164288a228de44807283baa7852e4480bb890a17a90c44fcd071b92340144d4440e4ba408c202206a53d8c086fe6a30248aa1708a2234253f0005315bf2e467c8cf2c92439325419014418208dd6700ad0ea9adf833ede73add15ad6eec93d965504f3407da64044db5251d74380ac2c2892896242931028a2555bc000914330025d54cb08276e2892f7a7bb777bbb7677bf7328e6ea5e65094a41cfcd0474562dbdbbbbddbbdddddbbbbdbbb35318a6c3e0b850c507a77b4df6cbfa3cd26de6c37dddac6f279e38dc51bb6d971c3db363b37f34df3f6f6762729a9010fa1fc2bc3738c7065ca19e379a9909190883890ff78fe333dd2c53db72ef315f820f8a7ef091fd503fe49f56d2dda6256dd8af1c7798977b12b7f1e5487e24cdc97ce44c55951b36e0d6d6b386762695a881d52ab25252d13063000498ae28c192b2bfec38d2de33e3128d7909a74ee3bd4f729323561df09c249b757211cc1ef1b4bf0bdd1d5a40df8a50b649b19edefe19272cf7395bf837ade7bef5ef7fffda2bb54f7dee55a66eeef7b15b67e63f879cfdf89464eefd234a7e79ebacb5bd79237c30a8ca458b2e1081b64d58b8a8a8a3847c7b89bba514ea44612edbe8845ff6920224affd91a21b666bb695f9b118d8b84d0b29b98748be35105ee69547d5fe2201d107eb79db8686b9886add815c91dbb2b716d0c42b92fb9569980231379230d328fe6b1c744b4d3149a327e9616626139da1a2eb6e5c5b638116cebabc1c8b652dedf89587f626b54dedfa1e01cf4fdbd880dc261dd8773e4185547a2820a040f41aa7f87d151fa07953573691167fc27a69e13617e135f9e672d24d4b3ad49a9eecf21a12b5f6e34baf2e55caa48ec846f30f9ca7ffaf00d26a698524c89c608ad168b3565b2297317b896dbb4c9362396d191c21431e651a2ea19899db0f44c8414a1991251d14c28e64b9ecd52336488f0cec4d433522966f2e11bfe2c4c3ea713cf58624eb3c9b4d81c9b11cb268bc5e60c605e5e3ad641ddf4662e2d2cbf4b61be9a49aaff36c36e22d84d86b186a5d964be82718ecd886ff8a302375f10a8d731d833099529e6e8ecd6a07e64e2d6a854ffee616bb83fd6e4b83bd3429a746ff6a91ca549e6906833b1c307f2f2e7ffbe0fc71fdd7f3fc0f776290a98042cf749d79f9f213fddf7ec13efc1b11b7f80db0528dbe709a862f04fcf237cd4d8e385370c68d7bebc574ee7ec8c3f4369928beaf705dd4616e99afdd5e0ce35aeb112d7d6a64316d25ce3a52e7f546ee4d919ff79946a22a209e7740bf8fe5493ffdc6ca265ea26f5a38afbd2b5286e544057766d0d379ebe24ba8d3adea6d3f188a3d3e9743a4de634e93839364dbab7747af8f42ec8118de3d435a2a0dd77834968d7349bad112b5d9f5f14fc83782f7f478d3a76e6fb8276638a8e59501f62b6851cc303cafd4682ef32ddeacacd61836ff89f3e9c4d22c0726d8db73cd82e7c7d5d8532154b20d57b70c7b9abea6f8373acf646006ad7502453314dcae040b9ed8659d8db5077aed2d5bc28540a503420f1135b33674a093e12c40e218bdfebe8967faab7a6605229a430bbcbc36c9ee3f2309ff3f2a95f9787791e2e0f33aa1ae665bc7c6a54f5ef4b064c932c1fb67c384fa78d85d296959516951696efb422d2d08517683d3d7f8f2728941304ec8c3f8b07d81b2fe28dab6b580f964e53fc0441c50e3f4d3c7952cbf4459d7329e5f5c8d3e4e4f131392e683fe9abb94caf81e5d91a5ae7b7b035f3a7d75d6f403dbf471e2ca83fe934fb9aaedaa3aa7ef5b301e5c61f996c3184a157feac49a2a29e3957f967673ac811184d5b88f301d1b32e6a1943018202e463c856bed334bfb8891d08317bbd66b32e6a23f07fb6860b4f10083f3c8da2e72b70f4888a7ad69dc0cee76ce65238773559d433577fd9b36de49f9db9b1a9cd3c31361c20f8295ed0a288ac269bec221a6d224d21f307e6d3640a3893b6046d9395dea4f96a095a4ed884398d7bde37c392aa3fe581bc548d5219fba76ca31a34977a3661d2644d9cb48954fd5fcc1535bbe8710a9593c6cfbf094d19bf50b7d8f368bb029f859a94cda029e38d43a5c0db269b42e537e9a22f35e94bdd2a276c0a75ab74da1c72a739ad5b4bb37103fda269a949f70eb717e107da2f739a09e3718eb359396130d50ae1cf9f4d544e581b9934186dc2605ddd7fc07ca1c6d904035afa12adbad3684bb425da52f5e762b4745a1735e9bf421335d9335a1bd168b4179a2bb139788cbde17f7a7f37da1a5e62094eb21d25cace84e015d33b5117b05998000a190cdaed395bc35da38c9c9e51356c666666661e4b162ac1064110fcd0719af410f652056511fc0ec3adb9707442b25b5d99b9bb9b6178a1f3b156734078cae9953f07e27c238b6cb21b6348b7626b38eecb0e22e18ca3d3834611fabdf7fdd327f4f91ec461f38bc27c4f0ce2bdbc07c520decb27c8bf7c82d01b042867eafcdd0294ab98ebe58de2aee602c6948ff499d0d92dc5e41ca8baefeebe49cb5630123ca33d763847dbe0c2f6dd871cc74d1f581141dcdcedc4af9ab3dddd45207377779fcd9b37770af72278639962eab123b2d0a47f221d83065a0c4124690a307c50aa0205330cc1233aeb3e0f5fdd75980ff90816b63531b1ad99df0b09e14043aa7b91128e6d0dd7fd37c28c72dd976a1b550ff01e7c1ede83e1e63d0f301c55db6824e5f7857af067b5cd27e6b0263b51b571ddf6df7be1d8037aafdac69c6f546d1f760f82dda8da461cd49f3ce63029e8fcd261dd7ffd4593ee305fc5be316837313549035af64c24a2a5c3981cd6ad6d8288a0ea5dfd55a40d87910e837143ecca1f89fe61a2a9899734a9c32322adaf4e25082ac776c67f08e5d1c685e49afdc1a07bb7fbe60d04b76d034fa80de4f0b481fc1bd8830a4f1f828f7a1ee0a3c653f827149fb6499511dd9ef3d8fbf80b41af1b59a4cb0b9237293e1cdf0a9acc24740393d0fe44564fd9944da139d4db9753c6c44253c6513e1aa8ff2e69d26bfe7dcfa32379edfb5ab7befdbe5df9f721287aad919a5ce2481f7f1c92d71ca95bb56e95dbac2ee99697be4b886aea085aa66ec881fa6fdd7ea8032c00aaccac270d9b51032d1bd6b186354109429747fe1f304292c5308ed6675d9e688114a5f881996b35a41d983908a9492d46c3a0297ada2f29cecfdd5d13d0fd263a8201121c363b6e6ca80eb45c9a0dcb85c4ee76b1a9269ba363aac95e124ed0316c32202de1e8baaeeb7e7b9bf6bc6e75dec6799bd76ddce62c1da5efaf29d5f28774ccccbfd3e4bfd840cb55e57fb18167078784176c8d8778f8197a9be69a060e9479773218364d34238872111045b3a259110c4533a2d98ca8685624039a01cd8488848a8c8478a1fc30d18edab9e4ff264926654eef6f83ecd6d25411ba35cd02e367cf903fb5710cf364a2fbada496293545f4f62be02a2a189ffcd80e0aca485d32d2f6cc48ed791cdb5533d2116db78924c4b4a36ef5126666e6187b50d8083f0943b48f5033d0466ada8f44747fc4f184d6401032d2cea800a41af1cfd95e3efe08c1cbe5aba01951cf84d0b2675dd4b396b5d00d347eb2240041f702e1ab66f469aa49268e8a08b978d3e4eeee6e775721039224e1323bb7cf8deb3ce5846e935bf3e6ed9f8668ca555325fc50c20f5984ac9202ea620141f73dcf474f8905eaa72eebbd238e4e4d87524a757056cca9f5eb34b7e1e8f8ceea0f4b70bbbb2c8fba76b89c481d22bf9c39915f8baaf9208a154504a55c43d4c7225e607676677766676676afb150e369a106456c8a2390581f77a03d7a23120b78109e7f1e8f3c9367a7c9de259eff7f1e5fda6181679b3b7ec33bcdd3c26c6b1ff3a9d41441d496b400fa82871f9808a5bea281f2c840b3f907887fd88128a514e88789fc004d77ff719afe2939c63ecc3eb13f516f7e6143bb45a98bb5a36f5650a9dcbbf8a09df84d6e2e624c933d95e09fbf7371b04df7418fdece30b703fdcbf4b82bc474ebd4a44d7b0cc29529d746b7b6da58afd5bf2fd336627ab5a586a0278f8996d45bd24fb57ca1ceb9a47f7b759630d4fe90674bddfee6474656b7df19cd48a86eaebad18c92d4cd6773fb6fefd2adf9dbafcc06841b76288285926b89134860f1ecb25ed014093950fe0ee830ab22072fe020ddddcccdcceb62c540ad20e2c4122d6214394110314c6f6b31e0d06464440f458ab868a2429644142a9210516406126858f213daa0ee7b1c6b1e19c6318eb14f73cd6db1e6d9692a22abc74393a7d960fe556c51cb57826e1ee7799dd771dbb6b9bb4fe6cda793208313473b14b5348184431bbe4e23d34bee73415dac19ebc00721eeeeccce1b4f66f776253da91d014612417c3cd07dcf4ba57e64f0191181388c37784ec42b8a9408519188a00454f480c81731081a628914454a569820091a9e7c5794a0464407198d99d99d7dbabb7378c40a21b57caf1ee1dbf438cfebbc8edbb66d7edfdc260f68b948373898210521995008a1b933cfc9734e9f93594da8b8412d7fb21b75ed535a9951f76f7cd53a9aec6e1860e4d3344ecbe9d636418488466d76f473bd719a6daf69a71a8711ef594f4bc5a880f21351e0088d2882848e5e30272087a39a0d6666f73ccf73f6e9ee9b3405915aa650e09344b4248908a8d5b56c752d5b2dce61b444cc0aa35aa66642c4c2f6be267a33bdfbbbd605922ddc87de6cc9bd8f364d36c77936dd363b96f3fe9bee5f990e4258eb13ed0889d6b414122d6c9e328aa0f375b68679bb14de4057b98b421c3d7030b033fe2a4ae8f6b3396cf20183073eb035cba1c0564bcaf3a8bcd3dffa79bf4223639b9a7cc8f89e948c6779fe1e984f7d8f8c7f7916984f7d0ecc987a1e735ed8c5260533aef7b7fc0b035bd39e4baa49199514d1d5e46260c579a2a48d20d06d3261b174f7245c19274c4b5b33756bbde7b8aedbbca02ed70a3a4d2ccc1d1a45481a8dc6756291d3eccbc325a7f17cc543cb223cedd74bbdb494fa9c7bdd9d4be19e6b7323f3cfcec0bc1bb613ed0889464b21d174b6c6bddfddb1fb1c705c5fa57ce5cb5b4a1982da8ddce6b51729c272eef771db6cd431f55b3be543433ea55c4019b56c847bda743f25e81c3d1fb706749f3584ced5a4ab28a1deced20f1ffefe3b3c5b73aafe18d89aad66becbd4251c4d93d35b4d3f2a45a69629d269fee43852622d656a7f7f9abca8142e3745b9746846020000d3140000180c06058321915840281c2d7a7c14000d77984c724c98c9c37112e3484a29638821860000000000002a008188010df3fbc11416fc68e1939ba6d428385c039439c70c73e9987a22a438b8b27cbd3f0ec0bd2f1f22d8870c8ec036211322c125d046ffcdb2a9682ecfd7848c359941eb62eef31ea41a0e9b0f22ecc16e6be837be6b49fb4794791d7be959424e05a7ff7975cca5eda5be88d77d379a1029b74aa3b986c2e6ab6052ce5e6d389b260c3852d523674af5e37e735140f685ce331425c46ef50458db95b84efe0fd76345e183d6e036bb9725c2e9d85b0a114569fb336e135508a236340e3d4f9a84c68fbdd4043d39cba62339f6697f30b53d7440adc6af45b42a9ef1c7c420053c20e4b87035ae92f842a1980ca84a01b4146da423fa5704e52259f53c57affb6d941f5ca24f58482a051301dc8b753579501af73808271fa9c0c101de25bb1341a86e21ba0df56907a5f7602ee0ca6f61158c2e1171b889bbbf67d5decbf929e574fd363e6f34f6f033bbe9d0ce6ae59df543f4b5ad0f5cd38eacd8882e9400b8ca96134338d0060ed4598b28d11be4b90407759965c2031640a104adb086765c590900c830007eaff2eb4dc9aeebf581a4dffdbfdee4d2df10eac3c7f2dfd6ff44e8faa60729746b02a1964910da4b533abb72d90939ff7afb5f288cbc1bb2c8744846e69967ea7750d08d87093ea4586b234ac576fb484b9a2b67f5000dc66bcd8d11629fbb0868d2861964711ed1c7088495f57397de8d4a309629a1d60c37c9111f46aaa07df2f60a789a99d601eb52b2b87eaf928d464531cc7e616f056b63c4425681df837349e18ffe0665ae9fb253da2fd7f7ebedaa7053793adc4433f30d94d30558bcc756befd89445c16753f59b64fa10fed62d23c4cbcca728eb1fe505c37a1dc8238ce0b6547026e71310fe90e73d0c730e421eabe4ca58f290e85bfc7207c840ef50cf28491560f6d9d8b4b19799f6d4d1cbf5296b7227dec1b4af47bf42b52f3b52852821db91d977c65dbb011e82fbb65e80d1b6b753668d8185d961abc42463504faf64a8ccd40d40da7a643e0f953cf77738fae3b8450dc3f88744e504222eaa0c036d13661f55d427d44c2b46cbcc2279e9fe2d9e92494a6bbe651ccd1d5af1906c83f25a542fbbecf013867219334dfd11a35417be63b4c2bf85e68ec4c21eacfdefc7902456f12be23e185e177779c3f78d9a0803cfbd8dcace043d3c748d10c6cd1fd9f34b0f55003326deb343fe8dae37e632b0803713f5b0473dcfe09f2d421053924c2eb654f1ef0897079a1909bec637fee543fec201de7c8caa3ee2046af66d309015204a001c4ea0f83865337b95722c98f149e227d09154853184e3657e2f15662ebeb3477047081690e22c62b91c12207ffbd4acc6a948974686815598c5a787fb28dac43ee0d361bbb0d3e945ac7c283e46ad40634900dc1350ff6f9fac9c285b9595ad7f4c281831772578369256fdbae12c7c01f0a86554d72faead2dc2d584589a82fd6b3739ae140685a093398e39ab6da2d3aef3bdac73a219bce352b3ed8a71a64741e8d500f011dd18ea3b2a211737d0d6b9ceeec07f7f58ab2a8bfd17484cf7484d9f9edc399df64d1d8329e6ee816adedde4596aa0ed2ce129ee8ddda18fd757f64023078535d1c109bf4eace3aba2b11e824656104e1bbbc0ad1c7206f3255041dcac6317b8cb230a81ad5ad58c196afa96b160506dc04acec05d3350029c34483d9b8990c9d32aa309adc08f7b491adcf3e47a45ad764f6024d1ec84dc78dfcf17038cd800167c74bd155f586ee9a626db3ec90c8a89cc5695601388dc52059cdb936c6c1281642042e301eb352ddb5d6a7af88ca9bd6fe37567816108c9eab58f80525ddec093759e7e9ed9d5f14954a5b9ff4cb93ecea763485445cd3ea8ca0ae5853ba7ef3fef741627d1fde17d3ebdcbf5f48bf031a2b2f27bd360a4b536e4efe4ddae0ee84e3b314c11f98ae123c429195f4fdede52e9e6a7f7c0d867ae158eeda51c704bb242a5548f92743812c2124edcae58c59b47cb3a6eb33ae1aa8f309153c7bfdc3c07f5f9129f16ecca4e9616d0ae86298611aaf0939b63e3e16cc5811e971dd290654c5506ea473c3c5a1a21434f1e1dd52a42951a9923228637f30edce85fb6e42209967c60848484b5e8db2a00c53ab31863cece850edf8ca16109583c72b0dab416c0f9bda585e091ca3829c40c0b9432203828fc4b3c3db08ff4093bc61e8b02a22a75de6e7e51fe8a4ebc88b6217ca056c90ba61ac70fa9372bfad57a9b937d3aa3fd8088df73cc272f4bd6b96fc5aaa1dbe4a61720dc781225d90b673814afa0ce53140d51ef58cdf15e7533dac44f8d8388fa25a89b94f235a44935f5e75ab342908252fe5c5c0ce5700a8009cb8061de7a99d0d6a56df6dc767e725ef29978d1e62294b940a6fe088b1b9f29f3097127957996fdc654a89a6e4bfbcd04593f2ff565b9e794030d3d9d95ed54703e7a0e4762f4a05f10174f4889fe5126645622b1e311eeef268d62ed3555e1c38529359b29cde014619720c59c0d2e8f7a17493a140ac916335708ba24eaaddf103ad728f2e7a10b77b5c9a737b76026b17acfe11e6f6779852dbf661d56762db623cbf2b8050ffa974d53dad7aaf9566b919b7774c0efb0212a41dfa65e84c27bd2b5ca9ae31ad02a34c7a00db57b5b2d81f113350a10b5fbe747f8a51f74e02ab8b3a6238e47a8b877c9c63a66219e6b0e440e35ef9379467501042493ab185098993d052fb57bc502c01e9f2110c4618c72c4678947ffaf2022cc0d86cc64634f68bbefd1b0fe8a74ebadeb1013db6b19a730a24f0409a85c80e5759d0f8d225288e0430afb41c6f48c8d9bcc1d6acee1673388346128589a3eb01e6be135d13eb4c042ad5b1fee5fc7b8407ad6a8ac11778a785105e0da8ccf8b86155908791f15f11c93e4dfc2c9617e4a0b246a3cdaf7d7c8e31800d588090eaaabc54490705670e4f726ad1caacb41120cf8062cbf3e00433aa2855a9cd4c72aaaaf9d85c6f015d7001194346ce6a9411a74fc147068890c2715d6fcd06e5d36cdb157f6e206062edce0499fc2468dad53d73bce1016f06a5d534cb4b9b227ef86445db74de10271c7a88a014c344e6911240ee78b4c1621e1b1f7a87964034a6f5bb9a7b5e8d3a7931a1f53a748209e099b771964db42dfa653315290639f3503b06347def8d35d579c9e0a6fbb0978615f728d1eeae36e6da27f945cc05c8b29d2b0752b68c04880232cef1f964e66818f6d1a2a044030d23928af467772af42de8023e2f503a37bc2972e2ebbe8cbabb05f130a2f4db22b6f4452ba201ea3c16d9737a898de8413056b292d1e93e18b393624f1b6af4982db65dc955ed442d92fb2d4c44295480a50f4a6763c960f656ab6d82737c5b4764317905846e2967fd1fa144fe84e828ef904740b2d3509a15568fce0733469a0af51749040467c9d8abdf3ec86678da489a86c51ccdf20d9bba9c9a10a21493acefb89f4124a01ae9b5c36f2a21321b192b18c7910a134bbcb8a326d3a6ce1a65a4385aaf1b0b5c1a8130b925c92fdf42c98eacce4d8930dfd0459bac17a15c2275d1f0e214bb9edd2b674fb403bc41598286f41ec868fb086ebf84341617d1c820cd5301caa7eaa34906278e4d4f4011aff783948690480b6e30b670a58955b24a82f8e8a940a2cb59dd548066e16a04ba71cb36f78394bfcd1453f49415dc2855ecfdb4f42d28849ed44c9f51e23a53a25ee003a6d933cd0781ef91d7573db0f63ccf87cf0ca26be8495eb1e06d104eea5a87fab87cd974c1e626a745ef4685de7261b4496e5a49462d3b67568ca821d8f2f65c9577f6fd594ef10a414bcfaab2d96d556a4f8fffd00cf0b3fe662de8b9d178203e0c6d861d95f4c70e8913a75b38f60ab00e3abb0aaf7b5de3629a8d448297982a21e20a019fac1db5e10a33ba3c00cc301a40af6cc506deaccb982b981ffb64f97a769b91b4de11acc8ffd8694e505899da04900f87f65d37986004c1222a81ef06be634aef422a700bf783c7b3500b2398d3ebd6024a70cd67e3ed21f1c2c27668981d293d9b0ad3c9b59fa1a2b38b32c5e18ce0e5b9ea9491807c5caf9b7405f5e6a292ab53334be0f03f8d750f7f82848de80519b34628e62fbdd9585c772fc459802705df60fe0831dc399ac70db73c6a3e542e320c9fcbb80dc4a3e29a010ba6b5bf5f4cebccc06ac54f1c33407e39a45507e2cfb3bd5dc2e451e36d7e1b5950cc3640840e89bc45c0174c59fe31a22486cf2b62dec2fe1a9ac65ee04c82ae9c3946d6ddac1a8053ec01a19a5cfab8afca6f3e0a7f9bfdc03c568a5f12d166e503659993e619d3b145f11c7254b8497961848cc62c47714d43cf7f7b84e643dd1c2c363b73383393b10324bad722fc410b60c6d38b44f009ac425500636acf2d180b34acb4c72b3b0a31f2be2eada2c785284742f21b81938ca1a52f7e3dd27412434d316947f9c2457ba69b9c5ac75cbb81ced1dbd0fa701a19c76d3942fa5e381f2c0c4f274cec4458c4987c3d8903458e47314903657956fa29af88a22cc49cc08ac33a9678c5f318d4074689d12b56c90daa79cb65e40025946822e6103e18c8e9b8910e7a59fd788c31fc30d51a9734eab0f25e9090e6a26eecbe8da317d7d8a8b99a60e2dbf698ef4bb406d391976a69f7478027dd3d5db0ca93dfcddee1b8892a4ec8eb8ec66fcfee2593efa570ee13fa7d26b7d7b6145899bb7c0ee822460ab2aeda0b219cbe3d678f6e3de043e3d76d1fd1d233497dabeaf31cc47d48591677c3033e7b0fde8047fcfd1d12415b3dee8d6f06199ec55c04904dee665234f66f59ebf7ba840220fe5cbab3637f4ffa9e8a9c59da675b25001136619807f7a5acd1319ff2ce814bcbb01c3928bbc425c40caa166eafb78278b116879c98756018b8bc8a94922f4a8eeddcc4c5c9c3de741deb995dcd50da11232bf78e242916cba97ea1fcb7ef479d55209e6c48913ad83c8f110446f7a1166c44bf180f1bbaaa104158ebbad2ea2a4236f44e658f9821884ff8f90cfee783d4a161c9a0d12386d3e27fd98a27448748ec9612ba99a66258300558863ae49d1ee342b9be15b06d4a4de1246d8adebb57ab312861647ed5ba9034fd49c4971346cddb1b47501dca38fd799ac04c0cf62a765cf339815d62915197eba18fc5959dba18e731419e55eb081613cb29db10f4769ed8c796a4fa5a2f6df7708aa7ad6b831efadc91b9013ad3c4c976ac2c3894096a117aba338b63d1c61ab4e049606b054989c751eb89c4bbff00a5aff4339b6c29e421e330014253f3358bc34926ada2b54bd483e12a9800e9297e91bff59abe75cf90fa2c53779cb012fd061b25e5ce26370ea529c42a5ee0bb88961a61f4d3dfeefee4c03480c6870bcc18e42830b5bf7028eaca7d9f6c3d50378d47c0f7b354ddd9a260e9ea118e411b644082e9a92994e94e665bca2d0670db440672c70b0027a8ac42ac6437f400cd99dccb6c74089f382f314dc486a2f5e3af36915360642600f87070e3bba4c06c6840b2179d1a1003ee9842fecaabe753511835db8df110998418100b370cb6921e37949d9771b3111c2dc74a86fe276d68619a752f332201ecbf315138f377eef90c7761d78c6e268ac2b2b9a9299ca316065f6a1b75defab79ff4aaa3d1722a406b0fae6686b6ba8ec1a621f58644ac702182bf5d9b274b863c83c7c608df188a437eb6823b2049135837851a590c1c728e5b745788eb0f4b5808975b31701f4941b40ec6e8133b80ee02a69e14e7bfc1149fdfd10a62a70fed82cce1e707e32ac6a11f59ba9ded4e25796024b1c729b0e9eb1a9e5f28f7e29ea2a7f2c37474df742470a44ddf75e180414b8a10290cfde0d97237c67395e942d233be269c6410554e2dcc5ab313af66b608c2aeb861556b383ae4629ce308778595f389b974b24f5dfb9cb8ac9019650f80ed15f94c67dd048a858f6ebbfd8551b393bbfd275495eaf6c4229bdc9bc885bad1a13312a8c6e73cfb003b18d924c02af490f4e6c86d3b8cc83b8f493a2f89a22380a5d9dea0e47a68d950b697ea7bb8f1a69878b6d870f14b99c446cc67265c2c78152dd674e4ad9ecb4c76b62b77c0d0f58655eb1b1a0144150a7926bad4a49c39b6ef8af0218c05affa96b0e31ef895f9ea0a684c072c18cdb7c3a6d833230eb9a91712fee75167242de473aa6e5b35e6782982ab068a5984aa944c7e9c85bb1ed8cbb05e5d140e0736fe3b4b774fb72428d714a7dadcc9b5779e9b7ac2c22d0e35471af7d2a3ec91bc49b5b58ed3c85e415407df96cf8fb67634b8b35901f299d421f8d7da1041f09580b2babb23fb6e8c93b16c1ce6eae2c60720211843db7a5f857c7b1ac0a4a00515a138934bbf88774314ade4b2beb1ed3f14dbbe19792a026dadd2c3451881689e228cd57800950e378647520f372b9d0b866d953312e1b8e9248cc12951b4cfe3e32940a3911efe1773dc191f220a154fef99848803cb8784cb8d8c882c8bc7c15244251553d13167e196446b554053df7da43d06042e5ccb7a30b12a6e9eaa6a988c2f6748dadea5c6ee8313657fe6ec84d5567cce4b24ccd760fe080f4f465aad8147d8fe1a317803c6d2aaa841f9e8e85983d9dd9ef13ea14b104248559a6d4a790ec178f57fbbeb14601983daeaef49c06b9658d34599b596b548d7d161482392eec030769aea81d1efc9b2a77fe0ce54394775e9b9fd4aafbd44db4cc79e8787c862e9e8b3c794e9200521abc41c4952d3ce651949685340ffa65219bfdb6d9801c26f58c9eb53353e5d4d085720e2d445b17d90551572648b6941544171101571e88216b74b077384a465568801c5e6ff331bdb352f2e97a7539c446949560dd1516b092caa3603a946218a40b8d9fe36957196de6869564abf3f47aa19e833ef78e90ab46e1590a113ee67c415576651f3e91e76481e1919c35b12d2a56755b8e5a44127162cc2abddb09e09052529cc6cdeb3e58cc03cd0e14fab33d0afa33a93e170c6a0466fa51114a1f5f3768c1f23e4815cec692d8662a0bcf136a623067a60f1c9093d6ef8cec51d4b3f65217b9a9b6f8a5ae6598004943279803464c95fcad3a3a000fc1f9fbf002f3d26873b4406d6224c261085d7fc4c1ba9088031e5b0e5f1f483d7a1a4ea80da9083a8d0bd1e397ac591886f855b2de7c375f0703b5398e0b0a3440b19f3cbfae6e09f492f4328067205035224b6a4fbc44233d78f18cb43a13970949f5834c4f40b1495b8e548efa78bfaf29955bdf5746e76aca1ac9f4b15e4184ecff0a35fede4c1649c6993bb28136b88929f0dc17260419ded7ec09d8d1e19b4037e48f57d2de6ca730eef147de7ee11336b3594356a2ab2657f2b1e720e69663d3fe36882c5ce9daa39a988880b0d1ad0fb73f660e15f6ee0e6cabbf6624669964f2d1207bbca2bedc8997697472588cfb9bf14f993adec19cd118909c29f31416200e89b843e74d79d0df5c610ac3c5f6ee48889c7e1fb01490cb1a8256ff117f837ed28fc761e81049eea7c45f6d130b29c52a065645100c4b000bd424249490a1d37b6443dd88601288d22f39b894501d4c1c8b64495c2df3d39d7efb21540f646cd9be060fc7c389e023752955d64bf53cf5da8b7fdf5552194bde3925aec7d23d1b46b814bd0ab50e82da3e777a1d6b278b71ae994a0ab2e140e14b0659979a56c2d45149dc04231f9b1dfd1942fc36a95864d4fd4da580e187c3b36e400fa8a70f193fdf727ffad0d95248bc1e2ce03ec11d5d8d3e1e32f331c047cbcf737da00bc35460330e473ef7166fbfc75dbcf387be533b00e9615d86a430fba7663af55690463d2f715a8715333b1ef6aa2e0170c4d2e3e912df3221e861128efbc10eea160079bd593ec5bf4af22eb648c8b45bee1b2da6796d50512177a8bf1025f1a4da4a144a5d8341bc40d4d8837327f36fed53eb1eb9e690396539f699cf9ac483cecec692175d619371b974e3ae5de58e2dc082219fcf1f033664986f15bfdf4941b48686c549143012e42f6123f4d8be8c19c510f3b506a1b68c1eb5437dba20277c11d94bd94a671b2f0f071c5b43c3c1dc2005007022e4c1b5c45564005f5bbf67b8a09eb06ef25b36b38945730f1d903fa8445295c71b9a706212cd02cc04c9a7ba73025dfcab49faa1776fd64cc2419501071d20d37c7b001b5b0a4b21955be26d966eb4a731ad37a0b9c8cddc0cc27def34a06a00cc8edc2d982c4eed931cc894cbb3f4cff3933b7151b71dd7f132a195ab05f9e420f3f6ee5a6bd2a189231887b9bd3b46f3a7b8864df31537c50ca7500f13692fd427ddb1453dbf872dc35c36a7f88bb69668d8672b9b48c3bef9e99e9a5e1e4d2101ae32bd079e144039325070725f0609aef3c552d03e5fa17041e3825db33711064a59eb06c2f9b7e36834bc715a4dde151641832f11efee1219db1419c1000fd6f221b4ba9f31cbad579905c6ea7dbf142c9da8950065a69819f99dc4862146bd2ce2d48c2a83d5ce0b1ba64d7baac7fc7a87254fd1ee0f762e0aa608338393eec2424f6fc169a6a6969a6a682f07a08006a03c797c0ec2392813cf6e75fda81bf6c3c93e844b61747a1c5d72b3bcc9982ef8af861e5299fca2ad589ec227ca8b1030b63a8e83e4bfdecc7abcd1864be4b3b45267731dcbe4e8d9752413689f2e1c50f009f438fb9ceb0e39296c816c124d44caa09bd2161db0f8911a094c1b487471575d552dbe1272e996a0660d2daa49fb03dff9b1a1252ecaa70971217b4533a0e7e59d8f07871c69f8ffc3399cd04af434440a8c30ffade43a287eb3117bc9116626f4c1005652d28f5468ffbf4f4a3f17290e27b06c1a0f4a37760122d4c283be28dcb9febd4b063c1a5343f134177e152e99bfda3fa150d25c15b6a18c69b8a46c953026731f4721853fceac236415266e898b105db0a3121b4d682d90f44770eee7901096b539863155e3e28b8fe235e5d474b4b0529f0611f060379a3e4f37d8e0eb397c932a01ed678d9018242fe6eb4b5d36be482b957c0d11e7ac0cd842261417289430d12c5d081227d207facc291cf02d22a75087ec0d9cbce192600c32fc62c5ed331a8fc5e463dba0d4a0711a1545b4a50acb31a43f95991d9b746d162c099f7fd8f1221224a8c416fb84a589056def4002369655ae7708909b982b4e7487b6d6089d9d0a9f7bf113468af5364ff749831a6c407cf782f2f99927150d93ca91b8587ef3223f2693fcba2074ac93fcdcadb7e89ac564c4d16a83975526e813790aef8851ac4b6adff1e1c41f84ba7bdfafa026da3e291802810610d78c34329825710724dd9a5367515d5813ed59b5c8959392ed4c462537da136ee3695e2495a737e5ebb0508d569ef820045d693719f549346aa410975f73a1b2f062bdb21da90df7b420a55a0b5948b63133ca6bc586d5c4117d8f1877f3f13832b0ed040127ab0873a05d20fc06e857fd5d43400a8e6623e5253e0a8ef4d85087bde4fd177122448e6abcf6803832ea731aa1c95fdc574f60328eee7304754f625f27681a37264897c84df8cdcdff3eccaeb95051646187ef0b716d64b5043189928a3710ff9e2763b91841ce42b10489414382b319a65ceb0a66571213ca6d2f546df4fe7d8668188389035e5b435d5d719a83d8c44079dfabd196f61fc60c26fddf345c7214cb6cdc80050a7a1212e9cf3032be97285421af45af006c4a28bf674e487d13653d141eaec1b729fc085faf2d6c6f3dd8769078bbe3aa86fb8b4258f6a344a2cec17070dfc698413a5db57ab9ed6ae9850e6b8e460b6d6c08853d287950af928d296fe4bdc7ef793f811a326f74a23eee7166fdb83971e174f140f2d243c9f98a33cb8edbc18faf98fe2637196898be92a3c94aed3de3b5a30d233ff5b4a22600a5b91174c649e5e956cf66becf2f9b741132ee7f8391ca5ef43d3f45f96f64a00b50b9e087589e5c15c2f42d57e0a95e1ccfb449a032b6f75a196323eaddcc37f24cb75a1060dffbec358a00b0fcecea97d04d09a50a578f14c92c71ae60ea816c4b8daa0e43a61c9ecb19c2b557b2637445416e91b1ac0d7c6bedf4cf8298e1d57dbbeb25a25e3321c2af5803a4d1a14e3921e4dd681ce948d9ac27b82a43f6f23119b0de4ba31108cf02ec93c53038a7163f2539588f0262eaee92e66ed030cf299fa03dd74a3f0571c66b5e76f95ba204de9d3b955354f53bc7509dad7f8e7169de94c534c0cbc3bca43e853482439b71d5c67a01bd0d036d65d76e5fe1854d0e923985cc151abed7e4ae614cb4f20aa988924f0b0bd7ec597629640001583632bd388419db9e27947ba6e93427fc3082de2dd24a2546cf3b7024f9eed1b42df7a56b588c888e88816a1dcd43df2b0793856693bcf97abd227ed703653ece68694f8d2c07495ec490c722d02331b960d59c9c0fc7ae2b55c4da7ed976918924dbef442795c2cc922f031c995bdc79173a291c0e13663f964288efb98d839289a615f8ca700101a22c7ba52934bec4f54fac46b11f955469bc14bed644e40184d12a353945c624a4c04f2f8ec36ba18b1024f4b01698c1e5f644983216ce2e3af9a85e2206a5c683c013c1e9c06ef95eff5ae4180e3c9c923eaeb40e7b57180702cefb6d368de9b81f2f6cea761bc7a4f55fa60eacb19229e5026e68e0892a7dff4183db1ba46ce0bcc19e0fb994accd11e87915984c1a508e2c3487fcf240e721b0cf35b1439a51757118b1c1481c0014e1c9d78d7303623d9ee86291ff9589d9a82c7e2d28b14955a7e526b096a8b96df6bd71e7dfdbe6a0968bd9d283bc7b347c39854eff2d18407e77e6d4365230ae1fe5e3e35ed5f49ee1e0b8465004dc2d0a3a559615b511ecb0c0d88487908ad1b29f9969cbe2935093c030dfebaca2454ee37322224fde714d23e4262e08f87f0e063a0bc9fb98fc9e2aae10812e2202da38311851983a9d9c901408159b6ddd435b87e4d29698496557762d9e16bef2b71260ea3d507a08db74187c1e8270dcda32bd8a543a47b1705df594dc0d1cff4e0e3146a0fc3b0778d912e4c8e34b56ce63e880558e2fe6263a4f5a80e963e5b0ddabd64c5bb73a6cdfac1bc260447d243aeada03a64d8dd5f094a2636bf62421f8c25d20b4b26d913508c9591420143211f55929e5b539882047029dd47d7fecd7809fc434b1c3fb8a4a940e9115d2081023f38bc76faf1dcfa48b46b1e20e6915c18ebf4328ca5f54858fc2f4816df280569903e81e6f68db31796c12d24223487aa2a178297a14e4ea390fe483bb8660f24e962dcf05687402390042d30a2c35818fa9de9316b3c79627400688daccb7d23d96e6fff42d7f63dc560675861cc35f5260f6236c922dad351e69ccc0756f8ad67b6e323a13faed0408200a0fbc0dc3780b22e7677cd3b232237aac64f862f33f64084ae221348546aa298c789a0d17c1caf968d3a262a08ee9bdd7653747dcdedddc3dfbd79be29c625f919f786811dd8ea280a060b0631b69941e50b0085f1dedd87ba954b097f5cf7304b2cf63160351c78fa4ad3396b4e58de021136df7c042e51440bf92556677cd630be115e14070e272fa0ae6a6f9fec24bca37cda4a40ddc37c9c204abfa6af193787b6718eaff64c0ebe362171dceaaddc6cdb3b88d92613cecb7c832ab7ce503aee2a0a64ded8c213b91ef35556b84f542792f9b5321002197392f180ee9a78cd21cd98333c305eb1f53372745721af6deb742a1aa622e7dc1964e377dbb9d319ce33e94d11a5d211964bfccaa969003e1d533537ea2e52d46f98bf8917f57247a4fea49c91f6711361f04bcbb320dcc97ccc81c84eccf956111de122cf40fed5e855841a1b4546af842fcf0a4d1645c3fb9505e887ccaa61389e4825dbb23ba77335633c895b41279e46279e02b18e96f566753825a7fe98c2c62fb9527edde51e43ae85263a1ba44096ee8135f2030c7efaf90720e8a7cf44a58953d3d6903d7390dd5dabd20ee242877b680de21fb206a75bf68834759d4a87d31dc2719b513ef806da4519b6271411bfe9afc5b8f7b1b6a0cb0ff02895ef908066ef2609a9730be939a87ce91cbb273e7c442f724593e24b06a9ed26d1ab4a009e67cb218ca5798f35c26ca5b71a2b065c7f52070d5f5f6ce7389f3654b49e888bc081df70334bf20e2c7b2276c07dfc2dd78e0fe6f260f2205c60f882f14180ff33400f5b9badac39d18f13ae12cb7654028ba0d88209d560d02dece6ca161f31249b7282b149b84d973de88b45044d236c23861fd6bc3884989f8a667dd7226a65b7419a1be57b9f50d40c73006ea32745945deaa4a7cd2cbbcddfe101ca46c72129a295db4c91f21895fd63dd4bb9cdaabecd500475633b252144fc64b93ccd6815bbfa47e73833511ac69fd38559a9a08fe6bf67e6197f2fffe35d51b0dca43059e8e54d206b7ff5fed050e71462428f233764f9cd883067ff7477984140b0acdcabc8ed97b6c001a6bb506571f5989e750145cd7182f8217b1e786469987e171b8dfb6007d39873a0c2088b278a1957d20d06073cf723b09c01008c52f118d5886749f0625dd612e5681f8ffe209a0124e6d0df07a9aca39029feb3de5840e3e61754e319a7628628ecc7312b19ab64cba12aa665c25348be574315432a751907eee057ccb1655b97718795713db1a76ae05bbeccb77395c9318e8fb539bc6790098a7190775ebfeec78ad4fca06954db4a18275fda419023f85bfd0a2fa705d335333ade35d661170fc0898a8f0759d3b2bd097a2080119db3d99493a626e3d0c5e23e797225824442b5935c28e2223020639764006eef088eb8f57a9d129859025f2e1d5cf16ecb8a6bc6141866192d813765ec3c215f2cad6c0de3cf190d94da301551af1847db2e0796aad32049ac830c25a10fe15fabe4fc74af39f9285fc19d6be8a76e55b190609581c8d070d0f708b8230bd269ea6c1f2d7e9681009511c0c9d4394a90b6548963b35399633bbb9b054a6b36c44f28cbb1860b1fcf65998b991c981b528b27147e7000001ef66a784360cf301a1bea9274bd7ca70baa9ecd245bf84fc825de6e579c629aee8f7ec0e867b30f742cdc2e45a9edc8c890b5075a8bb4e7c2df6c7e4a4159c989d941d59451bb67695a00dc60474134656936efaeaff697739af4a57f9272a9a6aeb2507b4b536be4643ec2189681bde910fe99dca4479c6681937a77e4f1432261a064efdde8819d950b5b00a4bcb1a4eb5b1a696ea43aca006ae917eb0ff5922430eb1f772f6b97ca30a578b4ec97585ba188025fa12ada180e563e3387a2e2d875e6db425555f31b79dccb1475a24fed96d52316aaf6a5cd932f85cb107c7f9824835859b396ce9302b104951c9c736befb0704ce8f5947894f4bd204065d4faa47f212dedf21149a602d261954d7cc52a896e81d7b578b381f9c0bd7694ccc7bbe1806537cd04354c002680976308d4f637af2d4d988beee962611a905c8319e3d360445f3741e7977eda96c0678465a7e08f367be9924f75bfbdafcf078b89221532a0bf145e6b5b9454498d4b35c2f993a346b39bfe603c57969a17083a49d724b09b6e2b92adf050c6e40aa8f90d38b5bfe7a3385764c05096cec3adb24e6e29fed5199b2966911bb70dfd6801ba3288ad9728f00ebc8aa431ee24fcde9f0457780e88a57a7872734befd65c282c5385c13cf1b7e20dea9f92ea843e015f2553c27a8697e0de286f011d7de2e5f185bba452553ee65bf820a04309bd75dcfd79eae5366060f09a9836a0723c4e9beaafd9f59ef4c2adcafaf8d0446cb7f84699a7efff1d32411d807e771e2e36b7156613f36246b20cc94ab67013d613b6b38cc7799445b435f1f8c0ad3e8d2a051575de7f44e10f00f320e664aeff8d9fc5f4ef2eac3b64a6db536b18d5e135d0dcd30746fd57fbbfd3c5c26fd1062e76e9594cb2dcc424cc8145025b79eff727ddfc86b24e3bce3161eb5a3471a1a60a12333c7c49496ea072ebee09659f78f7ad3dcc29bb7dabd0b1a4233f623519f32b6604c9feb7391181b2f6d304024dab983b833dccafd795e83f1817a87d00618d12008456ff77f440214ba82fc88143e5ff403c07a82a3fb39ddc2544d7d45ed2ffc267034b3a60291be53480a266152a8a30c16789d4f90217a19f7e5b4fcbbed992bc6b064bf494888e01b2b91d04d362478b4f6a653f4b7bba916655cbbebc8bac47745b9a16e34e4c709b8b05ecc39eadb585c847a02bcc593d964e63c423e414ead3113cada04b6fd6db9fec86f951f3a7b51193ffd63c8f81dc27106535c05c26c0efa52303114f43ac43a16f757a8a9e67f4d85f1f5cddbc190dc4f58f6f6333e0fc847ae275addf512e75c638f9bbef4461a0be4eece9566d315e9aca6265c8ae288fdfcf474c3e4fe3023cf1d4fb04e1222aabb1920cc118b7f489da1a1994649d644482b00764d04064ba7d23f054957b666d8012eae22e413485bc2a8d3ee75fe0a8bb80582f50ddd3f5d6751d44c63f76344f6d220aa133df4de18d86b5df2fe00c6a2fdd0233b1849b5a644dd3fd3b3d137f1a4597402c6faa9fac072a9f9c55106cfa8f4a36d3be334585445be77da1e8c072b20d8da1d464ec5053cd0b562f183cc8d8f1a1363aac40acef5dac70985f2842507870bf9dda91dced1a5c2c9a74eb0be67ae46e4ef49945208933da97b2a177ba5525859524f4c394295cf25fde937e1b4c02441d128a4345dfc4eab765004af4a97e88343dfd3e93521815972bacb25ca5b7226feaa98986cb2a1d4fa6634f04232adfc92cebb9f66b7bd5373c4a37c323f3301fd88708c52a01201ea30e6194ff2109128b318dae1af48bf420f4afa8e5b18236acd8717fbb265a7a72bfe1c631906c4b794e707b5ba2f94d243709a599da6e28724bf5a080b193315f9bb3f268f3f0a2980aadeceb715d091d83b5f048556b8c65adebea09ce19c6a526c00f44f18cd92a7ddb288bde3c681a513c099031b9af5da8f94388e36d31097f1dd3c128b3a88d941841c5afeb9cd295752766d3e85353ca02e3fada6f6c785d63f715bbc51cf480b4861a3feb90280b8b1276aab953290e8c30654f6fd3d11a6dcd1891dc51e6a51e5859a49c7e1fff56aa0fe80d9eb4d97cfca6490ba583f388702a3a41b23787d1b4d6aa81546e6eff9cb013a3fcbcb115e3fe7e068d8b21c420dd364283e4f1a36bc68e9139476546dfda5982cf3aa138181aaeb04f7d2b2c49276e90ed4d438214bd8d471df05f61c706ceae72ce548023919a98f7fa3169d0d467f982154a0f714c1f7eb547a58e98b57f43c4c5f33f6ca381024499232988d1b58b565ab2435e6cb9c35f25b1209669968f2f587d4a73aad5eb9144dcce87c224f6e78cc906b9525cb50775048c6a36263452d3af46260b8f72a15036431a1e6830fec2f8393935609f21f119798baa429fc4a201fd789dbc597fdc1691e8114849d2451cf3772b9178fe2712b59c9745ea4b8cb103924d3b7db400e3d9ca1bfb912daa478008c21c2345ef9aface9f66000388bfadf97ac2bdd523d7da6f48f88e828bd30a5bf9e5751a096e5de821a194d30a48c430c34da648eaa5bcd34ea197d40854be0e927de87d5e3a93a8519f5b56e7a978271d207eca08f90e0197bff84adb1db5c48c1c899540f2066795462ab2d86a01870eebea1f67cf280b03ba5ba14b0ef7cc42ee7083aa1f373386abc9160c50262d10f93556daca5a180bd1213564d6bac95973d0c9519c0d8559b38a2b334645a554f2d563069e7b72e3d3bb89a89ddda15b31c30c07c373e39b5e674863b72ee4e1fdb42265febc6c16a9df5f2f8819fbf7666f967007effc78408f46fe50f619187349984d3f59c7ed3b46f27bce1d9711ce83e43a6f657d05a47f3f5f4ca39da20a6a947ebeae524270ef6fa44fa952dce8f527eb6bc8818319e73b05a063723d673108e2820b30285d8f83eb689e2eccf6ebcf2ddd63e313bcfb3e9497f814993a251420e79efef3004cd65f44cd16ee16c4777dd44abd9eb8301342b3d53ff223973471d2ec5d2720810eb5e1da51ff9d583e1deac8d97aa84da47e315f31942480d10b8e3f7aecbd0b160a11084df7b12259a0d99ddb6e606f1439551202b33c5bec2016815ed70de6b3009901855c7a3e90c515a8662bdf4827a25ee665ca178c151ab0ef90f464e42b9cfc00fe404e2629087cdb0b57210d77238fbba36f7d53bf5d302580c34805f77a32ffaaa06df248fcd36edf08567e5c99286b0ada10138bb8db9ec1e7917e5303eac1d3b7165aac99d97095718f43d1e1e21f961c8604360639657878cb8d2a988cc6fe14b4838471e93d9b5f4000d6bd16096b047656f0a3c4a6fca24b4ecfc37a1e300fa3b13b437eb3db4112ae99913a82eb91ef2dcebc719aaefc0e0301a1d18c76b31ac25d20d8837c504df01834f6a7a03d248e4bef6deb4e409a82fdbf2cc780073f4c748503d9a881cec4005eac9951d46c53b792d3c6c732168496899510370765aaa9ab611210a35bd7bfe0938d4a685ed62e1ac8a09346973a91a5a8c939eb7243388edb0db00673a351b1733beaef82776b4b567812666fe0114ee0617e2340b0d4f0d4358080c7a09a961f25cfdcc539ef61621ad90036cc9e15c009aeb6b252452f3e5d3f5d3094ef5c23e793a22fe130e3898f7632c0278ad9cf005ac99510a95d48a03afdc33be9ff03cc32c40af01b2832ea1f51d181c14b186f5d155afefd093e31f94be008be92bc005260751aa4d83009b20357749053ec65d5aaf98f595c1f185807ea0ef0ae10572e683284cf7f4dbd88b4def0249eb4f946a578153bd4866ff0ffd085d7d6768c37c65732b9355e67356034f17a9079541f243c462b031951cae285944be967cb6148781a62b03fa70a7d4dbc7a1a755c2b4d1e90b0d47d97c14c3272af5edfa3a6fa87f3c0a80879883c2a4cdc96c406b3e461c7281a6c26a1ea040428e70d2e74caa11eebbac4639f8decc0edcb82ce7b68c590a9b4dda57744bde3d7cdc9e3c274ec5b73db9642d10e3f47cccb4614017154daaa39991191e2f27352180cb10faf3cceab4dcfb10e5234038d9f61c4e06106ec690af1cec6921170f0c623e4e04337a7be0ef7a0ee1230da2734211c5da6a171237cee84686c0ce14fc6553531cacd0e40bf13ab6a12841f6833d39481fde961702522b84588f44be52b874fe68e5fbb461d716d7a9b49a923f02208ab0f55920afcede5c53f3ee39e083c9e01b997d9a6e0f04021e8678dc8f6a3648f8783e7734c6cdc9b8d7dd10a4e79435b3fe402733bdbd137db27667a7266538da8747be52d6c9909721f9b9a13bbeae1c7b014423a14f18913403b0b1bbd2ec02f1485641a9286ebafd37018471432318e0871b29d78683042d07c6bac0a083c06014dee087606b92c2e9219759d7bf85673c7a398e72682dcf681f29cb410db429081be670285e91eb4ac28c70d5a9504eb9a502f6382b9f6f77c250808b97d1f5071b1015d55002acf2f9f3a09681050c58f9b70deefb7addfc0d6b02057f60426e21f6236cedf3d9f54eba4a743f5e29d1139ea8ab6681c02bc728848c03e290bbca49dd959b82ad7defebb7c9d789696dc215215cd533684f108c8d24b2016cf0f39e0636645258475393cad28f784371b83c5c3f7772a4a9f7dab0d241694cafde3a086230b668b27391220a6af86bd93c4264d9a1b676c320626ab14187f51caf9a374a9961ad5064400834365d01f6f43405128d12353200506d6d3c3bcbc184c7defe8346c67f45a8fb83b1b679cec59a613f762c992583b577be8082fca01b3b230b4546301e02e3b389326ca9eac9abcec968451540b68171a2ec2b6751f8218e2063df99b88ef45c76447dc1c6a6bdc886ee0fb7322578d10acb78ed89f2a9a06890b80ad54e0b48a6dc8ac41c04c35019f06a57bda24a7866e461ccf5055832ca556880bd87a418065eabbd111f1f6006dce20aa55efcda4387ba4ab633d31679eed833ea693f82c6c6fb7a9b826fea1b3d3d1655b442cc4fc08eed39567688d5a10178a766d115b21a1f2e59dd32e0f7c6a0d7c4f06de1659aa683b09962e31a863dc582b2946a1de2fbf5c5aa4d01df379e4a7b78ada380cdd52ada2fd737e043546c941d1407e156953a217cf7f7711aee380bf4a8c6bdd176efec9010a614074131beec2b52718696f623212a6c01d18ed0e23c6b8bbd3fd63e531999baf804e971e1de1e4dbb439446c59f9da1b097a2a95ee30bd0b2c7bde9a7d03c46d8e6ab01d7a59e5267704fe8d4668b5c82fac7192d91a60d547b123dc8a8255ab43672412f15b0f1db95f0479fc2297017f4336fcbd9faf54bc5495a22de5b0d359c6c33e872ce0fac226d0a1fd042d0c6448282955b92cbefe9844d7cca12b4a19167461109b20552387d25d3a7ade56ebb79441d50497b923651be1551c5eac20100e88492a54bbcf211861b0e41204c5336671b5123d2317e45489b45abc79fe55e0b5b9972d21f0199994d979e8111caf33ae07d13a15853f88aee26a0460522a3f9615954e35aa7a3d9fd064951b960a9a15914e2965286d56639043f5d4dd29a364dd73414d0643a9550dbdc940e351ede8c5bb304db3158c28dec2445cb016896227985d7588fb225afad8707771f0d78967d2eb09f6efe21c8c648cc8867914ce68e5590d396c74bfdfd20fe8aa53cffa8b14b3091d466139a97c82b6caefc0ff078818a18f8ec208c20c84fb214b2fae3a80d2ca189b181d6f2a9850b5d79920069086930a0298df90a71ab052d52fb2507164f3c1b7518c71fbaf4ed5e12d4dfdbee5b0199f4b7d0cf1e237352891439c9aab3852ddd268f52ce00464f03b6443aa40e4fef5838f2f5e90f3dc05d52d8a9e2d7d279615e5eb321260fd19105023763005ca06728d2bbb5620abdd05a5cf0cc8c0c24d8ff86d5ad0223b3b243bb50ca3fab25bcdc39760c04d8f2a0bf814f19c68119b5c8f0fdaa0bf7aa75274cfc30b1672c1810a7c42a0f9ecb7c8db58786ee6a34b91f792ae2f7a19085890aa653f182c844819aeb445b387341bfeb21129731307fafed2e5177b3664611e9b805e242207a2090498f8d3b41f7b4f03083e21dcb35713fc0759fa9ff6f67319c2f6377a7a95895e2fa699737cc390bf02d15be435e2ef4eb4678aec76e69264b880090eb241c04488c94ee9beaaa43e3f6c40d6a8d637a048477170818d598394c88540d07c155a3b1baed30fd878edf0828862e5dfc9147aebb178beaedc8e3919e2b33e5493cfb79f8c39267c8a8ed757a6bbdf93bd0d2ba6e643d076fe1fbb5746bdbe755cb2a665cdfad65fc150f7058cb38e7190095fa859f9d51abddc858cd2c86330b585d53f80d717af937454e00f735ecadfc0ceff89c2a0d9e196ce794d25e8d72a55f18bdde7fc8ce1943b08721bf63789929805d48a3ab52276c320006f6ac1e0db86b8703ba61986ee8950a2ed1be38a44defb2db55393de7aa1486a1e02cfacec5d1465a63007fffc92c5af01fab2c09451eee72fdea689806842475a2a518a982df3aa8fe691cab893add33330d7e8a17e1f3223a4b0c414303b7415d90fcad51693565eb4b3fe26812a25befeef5f5b73fbdfe182f12bd73c77061949c82517d30cc1e22a49dc6198c4211af38838ea5b622e5515568b297eb1eeb2639e09582b1a3704b2d636335d85e916e08a53b1f8f175155a098cc6330e6307c7466f8e790833be31ad2bda23d935b7fa5f68d74062a8f1a18c9fb7b0f80392ad1f78cb5b40f61a7220fb79caf4b80eee42b0f6c17df375e0b4fc9a7100af0c9c976cd33a37189c9ae39d853e4e9603537be8c69a6eeff1522f4238208c0beb20ff3d474a47196b0433ff0bbe60ec47e1157bda2b753de0dd8e0784761f146a80949dd0bbf526b87879ad9320635b3bd9df280018b3dc1c65f7c848dde78b2eb20e7981a9ae7ff7a91f2a8f5ba347680408afa6b659749bb26c9c48f72665d097445d2e7e4dff201c0ff010f48fdc207837713eea25af7f158f73eb610e53462024c2c9fbe75ccea90d13bc7f1e448c4b36cfdea86c11641f7e1922d00f2f9e1d4864c91d40607c97ac8e2d8031ebc7f5ba288f5e1337dfcfeaf0d3d5a12019c0fe4c76371811ca54069801aba595d32d7538ae73707f3612655ff26c88d4cb4d8831d5afc0833563d4ac2039d2ea97363434801717a8769013c11ac0ed6ae54b034d4eb059503c60106a7e21fc60c36b599fc90ef24603d84dafd0c4176e38c3ce392f7fc0060960cda47953c2990e57ae671ee8378368294ee13b3d59439897668c106385d9decae81d59278fad64d85ca62f08228ceecdb38bdb70f3f1197f809e9a9a30a6234447e6526f07bb7d535471cc348e7012623b308df1ac4816a19b529cc81bbba2c438872b0da2afca4d3496a304c56ad403a6070ffbad1d02dca4c3b05d90f54f1d82f20c09a1e05c7bb094bbe282e06ff6fb91bd5801229eaba3c1d71676d787b026f485785e9c17993d7ee3a27d53736e86c814a754cd144ffe2283c1359bdbb09422b45c6d29f592cb46da5285927d6546b4554da13cc65ee1fa22740161bdfedea00c7466b2643f0c3005eaa968856b49ad193fdd639bfa7d1e3674c91c92b9aac2bb01bb26b0a6225e6da9ee4f4ba6dd4d75d593ce761abf0998d6bd6cd2d9d54eaced69b7423d572862cb204970875912faaf2350f53e0a0d36e159c77a6f91603a4902b2519dd0d93b259571604dbf3d55765335dc752fc94b23599b08b050c2ae0b5448f0f97dba61425e89fe7d4f692c3e57d0838fca8777e9b514e9af1ea5b4e804608a616539000d469c7d6b33ae34f0f729c360f204f912944639c82ad312c4f31eedbfee0e00b7834297eed56e23388fe37b328d8ae927b4a8efe216611d8d97d8b835619f899c9581c2e427ec6915b2d2a4ebcfeeb0bfe9f9801f4c0275d10144548d8bd19c13e712d93349e3141c881a715d76259441ce4a2195ef70f99707f86991fe35b0a36561d9b11ca3fc497f68c613581ac83b8cdcc3dd9d1bc6eeffe13dddf0f6b95c135c8fbae10b46ba6fd8b91b427cbf79dce92b382b5142dc4469d28a65ec97e83913bea81ead74abf065ad254eb6d3bebeef60a89f5a25cf5d00bbd09ad583fdd17e7a6f186f8179df9d91919469b5c82e9045c8dcc59aca244253ace5f99963aa04ec1b91588041aa28566a89a220a3da54eeaeb5f889d87c1d75796bd00f5437b901c80da7cceefa3254802e8e2d62050984cf78fad896c8178049d84be4b4e1d6a1960795bec6df3e9be32b2c7ae6d80041627b398600d9eb6b7d3a25f718d40b5f18f5384d27782b4de90bc418b5c232b1614e4520b9fa6b47f5700806530b95a170921e1a53c4fdcba30f9aad59ac8b28710208d5860a529e1a1ae1208dac52ccc0dacdba22230d71b63b32c857fb1ebfe939d5fad572ae0828836a8d9fbfee7b880cd3a4278e5a3463b394849e731c6ddc8387ab43e443f6beac6971730dc49f700632d089cdc7e00deb0593caf0ec76d05df7b192f73f23184d42b4f7ecff851adc419d75c077eeac863014de8a58f9fb325ca546421fbad6c62aeebb8959f51e7a1ac9d29b9910ada085c85d5f1837e1b57ae24b4b93f5df9997cc73e04aa36d6a07e0d550ae270cdb3be37da01e4af63c3931343b912cb7debbabadb7b522d5541c335ef0a5d0c712d1e5d38de53f40df5585322467df7845f810c68c76dda9da9fb5a6b7d92fd5b00e9019dd88f5579c552ff95aa491df6dd2fc09cad9ee085f17ec652a4a385ee91732c0557620bde0b8b1a71d7e6103a8e90330d7f82aa9872d9202a630e76ae9c1737d1b658b124a124bb19e866afe9ccc25ee99b0e00a8214fc37ddd4600b27354bb58b332c9d6d78e690052955f4abb8cd7492714c59e1f1a848db44fc601834a812196c18189c36c1fa60299c464e1b246264be61436c4dbe08be8f782c1d1383b32b84e6fdcb922d348c2f6273c4c8ee0524989d1646388fabdbdc56fda910ed365a5cc8694d6bb314956ae4b39dbf999381cd7623de15b9becbede367f99c1db1d373769d30db38909b162e4339603e18e8fbb08d1b416463454ba419d9045a350749ce859abc5cfa94364b688a6939f88b325fb96301319cdaf405354b9cd62f8dd2c2fb9b90189a60bf9ab11f3108cdb9bf0fb5d7537f67aeead166028662ca07aac57fb86704e0d83217d64bd7fad12acf24dfecd197e25ac6bf9cbf0cc65c9bf148ec0cf7fbfd71dd6ab89358a7510a4f014879568fbc7aa6c6391c19f026879578348a59c7d35786fccac19f0bcc5a7d790080459e0b63d50606a551bce726cbcae2c01949aa3d35d47a5aa461d5301fd0b5069c03439c8304e5e76258283ffaa5b01f32505105f740db868ff561dbb0a45b2e2b72362250481d05083b10862da728fb926bb0b8ddc35de8cf87d4169562febd1a524e9b533ad75b41e59e1c8a7c1cba1c9fdcd16eada51cd5d86769b9073a9c442e7acff625a7dd10ade5c05fd17f1079c86334459d97ef4ba29007a5b4a64d804ce84b38bdcd8e02c61d1f793d50c60c7578cdcef582ae4dcca74625ab6eba6a2828937c2f9e6ea9a56ada503ae4d900b0d4ff84f7b01c51972891285f8676c659227ae3861b0efac63ca07668524c3264ca2d3ae502a0a342f60856bdc840d4a0d81f4018378b36c0cf7805f6b52405f25a29b75417622f25eff1ea2337f286acbae0bfe368d0c4580b941eb97328ad4623d743de60613f6cc48f248905479e7467abc63b1d0d232c3cbdd3cc0b05a7d93b49cb47e1bc20b05e4be2c807df9dcc11ce60e8f85c6d8b2cad1e9fd424b5e65078c778c41de4972bb1136dd8acd57fcc7f5f62d57585eb39d43d807705ddf6e0b25bbbf0ad35e417c713aaa90d1e75a3e9e411b583137f019b9453bf00d948ec7e9b88bf6e193e0dd3f8286bec8f32ef8ac2c8588858bab1c984295b7a44a0f43be0a3bdd44cca25f2c6e344304047e94ca94bef7d4567f71e1cf7660284223276374c01ac173b543217e095f77ed05cacff9b88752b540c300f6ab1612f8c8009e470f8e2b7f3853244fe9342640b5705949f58279d57d5a41e2b73ed2942981cbd13e6aaedcb0cb56dfdac427700bfabe36698f757af72b8ad9ba5a25eed143c1bfddaf0f0a1ad613d3857212e6a152a0bd13934a7f9fd567a5230516468f2deb78fac82a90909ce81073f2ec935291c3f09b3279609283f490fc758819e09d92de5f3ebce545a35d564e576f67f45158da1d4646f8701bca2063db25cb02a087817da464a2a48102e3877e02aeea535fd4da1651a1dd565618dbda9c8de4c4fd77c41818a9050688b2578ccfd1b4e23c17de61ac40ed2d61167afcbae548a807fb67c010eb26b9fbb4ce3791f228e70e06b48e8bb44086dc5f4072352bdcd1456582f206bb3c078d09787ab047ef547eb688f07e30c7dc30870156bbe07a2cb6f716758274632606f0089d4ecf3d73658cb392a0528a80f19779e538d600323ecab97a0e33e9a25d1817902306e131759c315b6857453287c0c46930b4e80f0a7fe4ee082359b21a222a1bc1d4566762e9dec8f5dae24eec8ce4ae9b51d492a20e4f923aeec1b74660d4b5afa370eeadd288e63943c6c25806f60a77137546ffba9091b0ccaf2093b6ea353abbb5cd03c56ca06b08a4743232cebe01817d8f75aba377972b0c7386814b35c940b5b1243e75dbe67d8f434ec204223092f4630adc1ebce4f276c2604c0b9785106138d63df3fe69681d790d1a054d1c18d4ecc2583cf7fcb087d5311404e5a8a51613f932174ff93e1bce5580b80abc9a025281091d46340a90c63a0ae926344f531487a6dcd78a6df4dd7fb8b419a6c9d2ca3031aa8c28b5d30ccba9a0ec5922617dcc1ebb501475a21a6bc70491b98de1303dedf668a235f05dc25451a34654439ef38be72d3c3d1e6cea9a2f3a7aa3040a48afb45665745254483e64dd3bf0f21a021ef8af767beef6fd2dade7b6fb9b794324919670867084508159ce241391f8a15cce82573d99e4721215b0e69f32116492f872d7b82b1e7130e23ec1d8a6c5761cfa71cc4d0c721fdf555917376396f38e47c05a91b08220404bba6b0ebcd3b8edbf88d0d39a72b67a753994e4d3913131393eaad16325f2b538dd5e9baf47495313131c9549ccd76739bdf820809827f88030df156557df97d9021df97f3f6e921425c3349fa97ced70f5299aa4e22e71f80ccd720383f3ecda87134b665dbbfdb6c1accd7f82017e69036af43dedc5811799099f7b75a8c968b5aa53a3950c5189732ba36d197ffcca9413a362f833ada88be74727490ea2269527551261a5d8e32bac4b54a91677454ce5fd6589783973dc3ecf944a508ce133991d3245a8489ced3292f60a638207b782bcc2115e044b3ba847e7e5aadff71ccb97e2ee70d290781726f8abb6a1512fa11125acaf4b9a319cf01e3e75a2232d7653ae21163cc43a61b7f0d32027feaefbcc7737e9f66e1b0ff8d7bead2e777d8b1483211bfc683c4af9f45b2c75d78f44e63f14642f0e78526267470ba56561a0511b267ed7ee62df339dd6920d3b6e72cf2965bcd04ffc45fe441d8b1952157ad80f1ea3ce6398e35dc98c70935720005f53eb814972e5251f2a42f4f33c98125792b47d3189d51fd64cf9f547bcbee40d2d122cd9921cb38214f5697e9aa22fe39ef794e0aeef0f01dcd2ae2f99de7016f686e6e6e685c2c0ae1b5f3532ce2f91d6dc4d22c9adf791e2328453ccf7a239da74639cfa3270d196408c9d344a6bb88e76368232834cf13e38f5e34ba88f53bbfa3ffe8455af03b3f027ded3c110bf45797b3a6ce4011eb6368a30de60b5797ebdc6258f05acfea22f5cfbfe6d14444a014fdf3b09ec86b9e8fa17d54d78e26b2f33c12d8d19345b2a69e3734f3e666ded0cc247b3e157948223188fc5bc062c578124a510ccda32d2089b0b4479f8764d17c8c9faf1d9245a3591aec3ceb79f07c0ccdd2af2745f67cfa379466521a1a4a3b3a6f665ee88d66d2190d79c81d92488c67e9fcce4b20e7799ec863901a5717cdb348223abfa369e88d2e799e88cec77831e7795e7422a6b5e753932e36bdd5a0185fb67462b030d68941d2a51f37dd79fa19c88393a44b3436714c2239cffa1d92f544747ee7a994148ed40d95da8941ea3c0f99f3536469a0f33bcf43e777348f9c67fda4d134a665c89c8369ce5325a7b11c927271973f5905cce2d0db796fb5bc2585e3230da494c65458d08fd14af3294c1b3594066b01be699ea563ee3987687e262fd190b5c8f4a15f436dd414260dfa744697350434d3a7359cc77ffab331975c75d618095c69a8a555afb5d65a6bad4cc250e7ea129fc5037cf1411edf8f19c4efb94ed0ddedc67a02196f501a9235dc55b1cba02293d60aa91a214170e74136220265e6dd841944542f3083beba6acab905de4e65a02eac204e2f5e359ab56386e6c397919973cefcf44b9a65e67c99cf794e994c43ffc7ff0f22c4c8c78cd498a5b254760e09aa770c31761e78cf3d11d5ab5eb45b728e4934876ab63f03e690b7c73999f0a5cd73439e7bbe549ab5237c9f2a529470c4fc11e44021777b4fc1a713739cc8fd383a753a6e6f15f520f248407ec99c95166a6cde62e17f244b0310d4b3ba42fdbdf72c1edf7bffe9210ca82ec7f97ecfe25ef5472f4fb32e0f56f7e37bd5177dafc29d2e12fffb4f1b893a08a82f77b923a77dc31d44e5ac95dcd37d1fecc8f287d57d24abd33c42cdc21f3eab7bf18f5e1e8b8766618d9fd5e9a2f0456d04a57ba30ed447af8f7ef0b3af2e77f4e81de09da20ed6dcf66a16fd72d6e8d482fecfa169abd17e708cb9734f054a5d8f53e720f27c23a55b7da54c1f865cce5a8f1e07b0f9b2c70e4bb78dced555675ceefb9cc39f9a6c2197bf417f7fa10c727e19199907694d4d4d4dcd1fed1a7286863cdaf4ee194d004a23d778aba7ccf8f14f31973d1867f0bd05eaef896a06d2336d8fbbfcfd26230579bed338e7654ed81caa2d6ff5a81c9e400298600c24597cd5ff84b5a6227790020075eba706f9f8eabcfdfcfcd4e3fc580522c6d179297871dc79a794cd49085b4af0e18a06e8a65fedfb73d0cd2f69be317dfc9843d5fd68535dd20c4c1acd8714752a66d55bf365a871a0f11de87bef6b70e06e0f480edc8d835f4562ee31891f879c791539f3204896cf7a500764913a640e39a74b66becdd3901eccb77970d2d8d0d0d0dc7d43f33536244d8d98f34a8be2ab4abcc3f99ffe911cc99ed68c1a42b46cab8c29b91cc739c739de382e019c3f7b621ea3b573a649ce9ded4fbb4c707eaffb3c4f5928b6dd976ad26cc81cf222ef3feff1732c0dc0ff5e02a09680a785b86f85dce99f96c692e7972d1c5626cb0586a6249854d75a16955cfefc30115fd4f993a2b69ad0aa7c9a55b40d0ed9355c7df82a92a5c1eac3950e593c444a021977ad1f8c1bd4e58e0d64dc5de7e9ec2debdb3748c351fcbeea12c8b824cf5de6397acbdb75531aa30375da03efe9db0781fcd8ded34969800f9640c6fd7df71fdda08f93d2a8ef7df7def4967f4c545fe341aaef6ab8cbe61a845ff6c7834106152239bd14402e5d6c5ba06a332c539c644f9230c59468d294182f37292e5136da6ccf396d92284e2f66d25470d3443c6923793e408a0c14e61bc9656b0aa1e5219583214186ccdb10212121a1214184dc820819626b90213ecac965bb73e9600a5187c900ce9eee098b11d410eae004f9870f4c6f4a18e7dc026343cd98261f64c4f063e70e358c7bbed39eb40c310224ea8fa48f15b9be9d35deca795e1921c8f4e73741f10ceafe665fe5eaa2df91a3eb31d32c3cc9db3be77c3badb5f6ae6ec77d3727be42ba7d6fc79153dc2f338932c05bb5ce5ae7acf34e3be79c13055b6bcdd95b638db7eaf8f3738d07e15725725d9b75d639c588961662a081d6724d87f1e8f3bb3927ee640c81a95bd1b39221b79eb4e2c7176cc894882cc3c8c4fe018de145a64bb116688c2679e09772d9836589d326632b4c1a2cd86c537559186c069b59d8ccc26616366b22d3a99bb764907053ed404377bd12b99cb01ed56dfb615b9876a5f6a9f745f7056e194d7269c19e2419e791abd5a095e6a654535c4d9ce26ae11457fba6b8da9411d989da2e51b9be6572a0259b85b1a5551f32dd5cbc55bb484d9fca79315dd56a6e0a964beb54dbdcd4ae5c6d3b3950978a85ab511bb689d2a8514c1b1d501af5837e385057a5e610f7775251a718b7e428efc2843113a5b3aa5845f705fedc4cc06d39240e26666afad8bc55bf036ae37a3169d4af5bdc755740c5ae17cce865d7e7b22b4db25daaab3ebdddacd3cd3addacd3cd3add5ce64cd68969774c9cf8476fad2e52a95e0cc53ff2de282c0a8f9ea8c2c96892cb7a67170bd4add52099b7b65ddf3a554bab41aa9fd9da76a95b0dfabeb6987e698ce524298c731ec77f0ec95bb5e438981279979cffd1e66033651e3919b5b134673427cb7a756ddcb57132991179cee6d75dd65b9d554e565df53137e33e094e46c1e92d3b9bb1e8ab9e65ad18beea45ad7a2222c521cbfb30b47615eaf9b29a45f59c3ef44b9f85abaff9c51aab41add68f63ceadd6ff38ca623333dae803afef3fbae5ce2e964b232f945f6bcba49591d689b4b4a66d6dbd55579daa4cada58f8d49bb3e0ce39cc7f16b9d7948642a65f3960c156c5cadf39514f58605164eb3d96c369b952e639a61ecb719bdcde8cd6b22d74a4295aa37f0e7f392e99e52f5bb881b8cb01a3366cc181ef665b5d1075eb53e91db29de72a95d7acd69d5e518e73c8e4bbee42191ad936df296bf0c156c939d551a387b26b561bf14aa957e65a25f659b665b6bd3a60f54ad2e511a54575d0a3975ea692157cfa272d6d65a39ae723adb8c33ce18678ec6808ceb679cbdf5b80616ca00d32c0d6f418328eae1ada794524a73b05f605c6606549c39cb71d776ff5383ba9f5151c85f98c277b2eb2182a00782df78f366f8148681873bfa8f45b6df3d7d20de83e39ceb541ffe286c485211a50e7e08f93c1c9233dcf579b88b6cfd430e7c70e01fd698607ad8aebbbb5f5b0f9bb5e500ce1e5b6f489caf64f717e4549d4af5e148a9be8eeb4221e28d215ef27df1c3f70fe242fcc12eab9e95f3ea698c6e0cb627f1f126c47f08927f5ea998e03ce87ddd6c776fb5c01dfcea5578ab5ea5cb957ebd77e28dcee66b74996b3eab9e136fa85636dd156f745e87f3385ff3e5940b7a5651ceeb60b104ccec959375f6911cc9eff90d828f7d71c89f19def2bf194c1f7f2297e55b3b20eef27a83780b4847d627aa371e791dd7dae5c774f95b5bf30c360fda58a82120f84bb9fb297aff1b74a321b025d9932948494aa0f55fcadc7758b2f7436ebee36ede717e14ed88f3f6e7fc71fb569348320e79430e7120f0e983460e3491863890ccd33772a0fcf4296cf5d4ea00bfe671805fa3774de99f679ee68866c65b2b585efd78ab355d572586de83159c02998c86d050197d42a750249a4495c0ea59b0bdcf14e73c8e38eff842c89e0d91db904614667443da10551b4cf66c8e9c1a3284088aef518daf9cc0492bb520644a0477bb373724ce0be15485c1781b57c8be3343e2663dd7e49d913aab1625a34dac69a9537aef0d0a83cb65655df5bfbdb07bcaaa7f854f5f2b4bf3d5caeb05b32390ea8bfeca3aad766ddf03b5c5813820de739ef5dccee37c0f9d1f9fc82f4b039ccf791ee3b334119d1f3f47138152a4f3a376223b8fa367d5e58ebd7a276985cda119384f593c5fe218ef896f693528869ea00ec9b279d61fbd7648968d6669b0f3395fb4f3396f63a38b749ea58d705ee78dc6dff9a3978d4c4d6263e3394c55a66b4f4a28bd0c8dcd8ddd25072a2f9102aed24d427fd6d97c5961369a01f4d26a9095c9b0954d1ffa443e3ece4f7107d2f8384f6b8ca624b0d826270762fdd4a035879f0c84223082e390d7e62f17072aadcc81eca67fa11c683e1d9164d3bf3507cae9726f5436fd3b75c5781012387b3ac2894d9f43f2200b6637ca81e653d4d4a67f6d35887bd1a7b1b19c549303952ec6c9814abb64e640e58dd1689c9403b176143d7120aeb669cd61534e0b962807e28ad8f471988ca903eaf6bf4b3568d4383fab0b07e779d02f548680e16818cee3e80fb09e48901fb50f0dc57ece8f5f4e2928464a694eb39ae7894c7ffc9c37c27919231aab2efaf9f3e3905df2ac2e1afd95ca1c9279fa76c981689efe172b6f2c77311b2edea22fc5066fac065d8cc7f1ffc67c767963565f30a60fd78593f2168da64f38313b2b12c86772c76d2ca7ef359ce791f3e3f3c0799636ba800c45886961d444a014e16896e76896eb1d398ff3b24f72c891ac49d5451f87f4c0c8b6c9ca2a8c3e7943f6a79756832617382449a18b2d7ca952993e14ca8322df9a7e21e72faf8d78634647fed5e3c8bffaa36d146e9ab7214bba6d7e454e9fa2dbd4c8bc2569c8b267cb4cd7add9ba4f9f1317d0da20186dd583dd8ab55a55195285d5a4ca549ba6d42ab5484db2b2dea9de82adda5161d5b54556ddfc7d7b4356a4ebb72679ab223d0e79539d36fd90bc01c91268979706e4d1c7974683705d6115e6f75eec15565dd32515c67947362e002720931927f1cf880f6230dea6cc271bc46cfbf84bbaa452894856eae9862a9b520c367dea54836848e0cd061836c5917a9a12934da1af12718c2fd972308740a03bffe775b853ad38ac9736fe6d2d7e2297799aa791c9e48aecd103f45820f87d0ffa3528cda6bfc34707f48178902a03fa440da00f7e5ec5f11b8421db3e91ab2cd8f38efe434bb69a49d2bf66de897c86ac71ab500d2b43de0c6a58fde32d2a02ede77afda9d57fc22f403c5b38f006c1687b886badb5d65a23300218a72a3e052cc24c728f1d38640ce8272b70a9a21e357aece851eb9173ce3d7614d57614f5b876470f216fd11d0ed48383a639a3d4a9d42541869111b8121a62d304304c774ab3830e5ee825a4160cc8c129a54e67b0369b0d2ac80bb974a6ed4c381f26a4b0810a909cb0c194131352174b4b92a49e9cb04198272b389cb04117b35188bbe7930964bbcc2f83821fff01402dcfecf954430e4872def3a9862700ec90ed9e4f3530d16252c30930b89271f67caa610a0cb8c8377b3ed5f005064932b7e753939809983e882376d9da4118f1f3d4e4896df77c0a22cb2effc72211c2fe8d6e416f357e684de6533f326a432693c96432994c269b426cfb348a6ec102de2aeab8dc17725c48e61a2e871c076ad61742d1eebd31bac51cba975cedcff3eea55b68b227f7094b8a9555d9cf8fec3ec634aaa3356a1b41d4dfafb26bf316a63525b9fcd9f6e7caa2b2f7fe6595714f3c8846d9264ed31ad6b449466bd40ba5516b5fac179096a907f8c1efc41cf8efddab2c32f7fed88696cb9ffd8910d0811ffc4f977401f8131d007e1962f2febda4ebf89e7b1c9fd6e57d0ed08d3f141fff10f086d8032f28d6f01f070aeb1377d9fd5383ec4bacb24f844c65a0906f8da38597546dcfd21a9555998ccb94d04daac3b4265bf27e6e8cb36c2cab7d8c7f7e644b41e66d6aea66696dde682d28e7f5bd87dfc3182fc59498c61d5a1bdf5bf6b7a53177d55fc232d90f77d5bfe1f903ab3f4e5e1559a65ee89615d4cae742d16899e63d7eec698ae57b0c7edf7bdf73ef69968efd7d9a45e49f8f92ec3dfe3cefaff7f83d7ddfbb98c5d95a17f97e496b58bc55033ffa6546a35e6acd05067df095a0bbdc4235c87b8abb77a1246a63ca31c65dcedc385eed49d3870a2565406dcca7424d55897d1afad21cca40d51cc7dd0feac05df62decde3fda97e671a636a6aeb6193d6748dc7f1f57bf7a5939be520412fc05e5fe07f7ce9996dabea44f92cc913c4f644ee7b195efbd77ca5bd6ae7e7a6f6b49dd9747ae7e5457fd624e306bbdc7d883e9a0df7d9df2d6b4b86e6135551bfeb2da7c78ec8703d9c718df171f3f381590c59fa28efb62102bd47c78cb86399bb6e9be242bc7fdeeaf76240c2359de6dbc7b64c873a685a9fe7b5f6dd8615ee891d5b38fbfa55c4e29cf61db83d5a095d6512191579fdfbfac369b03ddb78e64ab5230aca914b539aca43fdcc81ca298f3481c7acb72da6132b070da5e68f394a907f7bfefc41cf7f183dfd1ac412b30c4a2f844fe7da28ec9e56e100c6d9e72a981a3fdbd0881fbf8a9a8e33ed6e2026e0e6ecbbca72120fef7a07843f51e693748c57b498f043db2a45946836086035446fc4b8a1f79ffc7c6ef91e5b83d2aaab4e7912009641499e417c9efc03359b7b8cb3ede3e6a107e85d5e642f6245d4596b97924b8b915595f1ea9da5ecbc95671b54d1ffb9f44c67fdf6e9b44436de88420b5648bcd1f35e847a806e159fe1ab3dfb5843cfbfd2f91ab6ddb47807d960eeef1c330fe74b5551bd374d9c738e7d1be85652772e96050cecb3e2659dfe3cb912cee3bf10698b3bdc061298b124eb25011c5cbcea72a4fb6ad71e40f3773577d1aae65dbe8f597dc559f09cb72d3e8447ad9f57b68c81ea334be8d1d40c5ecea91de86c1e61006495ec4811c68d33fda251583e9944c88c3ede202e866e5c07fff6a0a06a4625e3dd645f9655e461be53f7aad7e54d74c4bd39ee20d16d644afd5e7bf1ac80cf3020a609b95c36efc7663cdd241a996dabe02fb2939932b301e2b8703e87b9a95833bdaa0b8017dfdd08dddc8f4194012d89edebada6394467d0b033d00571ce6489e54679ee4451ce6b0fb778b035d2ae5405cbcd6a25d5a752ad77f3a356d2dda6ad1a9d61cb25ce89649a5f6dc6293e026f0290d0acbcd974c706ebeefb857fdd7618efbef31c73ddd9c7deebf27a737ad0762fc16bff79da8c33ed656b3ec7bbf7aef891c7b7777f0c3813e6ff5f777d4a05c5d36e5b4edfb7bda68511a57e61be040f9ef9713887dff670ef9ecfb42431910cda1f0a7456dcca8d1337cd34c99e11bffcccbc8e819a51fa6192b34cd4aa1cdc29739343708923e4a72f8f8c507c3f0c110fc50b374ec30fc4007a859e0afb417053e7e9508be07fe07824f37087a9e66853f37793189c36421fde3adabbbc8dc7f24ad79ab7b16dd5ed73dc61df54269d06f927151497775e8fa56037463fae007a0d8179f7b20d6f34992bd0faa4f926c27deb9e3c8f34466d9b7f9a317cedb7c11cedb682328f68d6c6eb4d1cd8fac1cb583a37df7557220d5dff1ef5359cddf2f2993cd25aa2e990761dae821c481c4eeef1b7d39b9d818ecfb3fe690b53113640e797f7f86f418a5e1e37ed09c5b5cc5c53dd277d7819c7843fc2b36e0886e8fa43ba434fa3e6a1c94f4874d5f6a5fbf403cb12f9d4d1fea4630893fe78f5e2c5c84f339361ae7b12e1a6ff4f8472f1cfd63247fd8903e4a32cf13f95fadeabadd83fa4afefbde5ba129b92626eae0bec43177900f381fdeb24fa5541e328194feec1e634d044a91c703130175d6ef1d49ceeab2d406f63ecffbc00982a27e0f5561a87aac33febe7b173ff171f7de73f841a29f5951f75e0b3f58ce19f73d66e9a2efb1ce2208740c8bfbf74816a73350e4fde769efe70b7b9df6e93345d007d0079606dd7bcff2c7f8e3f19e03651d8a1070cdcae16fdf6e513adaac1d451cf4b01ab841a78f156ff8f4b15fc5397dacb621fd942c9313cda93ab5e122581da6ab5aa71ea65c72b53974041735c5944b677272a6ebe4f489495cc935683aae39bcb7de516881426f81d3a77ed9ed59bfbc78bbb73440434f674b52f04aee3607e806627549b707f641a863c64c4ac3f334b65ef747bbd3737bdace21ea85d38ec95032379dd2b0356ec67536fc219716466f30ff0a951f67cd894bf7062edddb126c093687a610484df5a32a54857258ccdf22b92d525d04a424d8f4a9e1835f86f75683426d911cd4585bd86d5b98087259a796a6e6d017d446ddc22d714b4a21207912ae23389dd93974c31e11e492dee88dda006361b0593dfc341310cc5d7299bc75932c29792b367d02fe2ab29b2ddffe73d2f007a56a10385dfe17cc8266fbe3f8dd89053092bbf7bfa26692f42f03fcfd02a0904b260678efbda73a039656010510720e4a5597006c752a80ab0a352b87dde1db1dea92c89ef2d6fd21d2c5b8ebfee7449dbc75df0024ade2aefbb8bbe0ad063d2875bf8252fbeeaa2b92b7eed7f70fc37a54c369094b407f70b5e78d1a5bcc9e9e16a5e0ea468d2db2f59608d62099caa4b6cba0613a08e298f3fd0390867bcea8135c89392ed8ab8a63ce39e7cc60cf05503de7a474ce39ab1017b470410b75bb2e8d4400674f896f168c07e125eef2b762ac1824a4224536bd4a5769d3db2eeb33b164cf2b7b3ed9a43678410bb509492e31d4f6c7500e549b1c33d5251f6e870ab515e32d5f926d937bc923b67a1e554a4df8be7e5fbfafa0934cbf76b5ab5df7714fff821af205dfbbaeebba0f042b58a93bfeeebdeefbeec16f00bb7bf0ebc00ffcbefff48c06e85abf0026f4743c20d9b2c17724501b7c47e26583efcf3de824d39f18e76c671096c9d9b30024e4fb17d490edf7e0b76a1087f3c61bfc48b2bcdec23fb14c8fb7bee79eee398740a0377fd101fee0ddf4afce7101cde10f3e48410a6a0ed00dea06685b0013aaeee1ee131c0c91448230823852041058182145f45b1533850eb31d5e68f2058a2ec9085937ed0e6f29ed700205296870d2430f598410a22b2106ef488f274640a2c5114d767842c2b64f63bac0965841142f4628cd89050c2b4788e08624685c3a101851041fb1e285d203591246872637dca0e48a0ea966457ea56cff61c8fe5697970b058630010f5d58a01dd1a41ba126e5dd0bc05050124b0285a8147422d46c58cbec06a428aec49a88a10ba196053c62830d2928a288305844d0a183d576a0a1476a0f15a69890984a32c6c68da94de198e4a815ac0842052b5d48490aa31685b3b956259a8070fbe2851794623a7814b518564990a00b1451b4c46ee0c0f0e891da0bb7c9044249102980e1e94b1556c4d490d85a922a27a93e4e8044851121d030821b56501be132a9553bc50aaec4906a21c4f0851630d65afb46f28eb1ba8ce45aa9d0ca83521213145844b0a289175abe702e7b1b8a5411831329a418a946b4508195b4446103432e47ccc930048e0d159640291155c44a186e4acbecbb4942a0a549971f6eb089208c161cbe7b24532d33d0e4249e444b0ba1529416116c8ae06265e566518497ceb3057ab86551c11547c2e8418a1364594060c20aa618020bd74511537858977e6b0a9e84cd051b0951bc38c20ba78222b6747ea4c90b51b531379cb083162338304520e1719574627b62427241072e382e94ace005081f76f832858a6fe96e96215172052d0460a2102326054530e52bf32506206e43d41053b205e74b59e856d17a1223c4174d285061845b1125a86ef55202272a2588218c089c1663b02b390b3cf4c0c50909214449ca62e64802bef80085890995159c705fa400b9903282929426b65ca14316599210a960295964b952801e9414e1f4830a9880e15464a1225ac942823c44eb10173c48d990c20f27c8e2258b920c9309446df1c4c6b43404150e6a0c9e4018148288d5848872e3ae80020372d34a1434ae64950081ec3df244834c0aa72b2a4ca9370c111ea1800e26e410a50a132454b1026aca4a890424a258c941845b9410705da0986aee110a85e403b27e641a51220cd314314f5e64a9022ac67147fede7baf9523271494e0e10729da123a380aa0eebd7763d764cdf4f1b7b73c87ae8bf92c3f846003942861603b7054101106c621635cab942423584871e10a264b8480438117d78fd401200166892c4e8cc0040e5c88d862ccd3c7c6841a5875a18b2b218922060726aaf8a2045db829a0f08e14e96207134819610a14332998e8c00c332c524ed084165d4411757d3a81029715580821ea85239c1411b4d06a21e20a771d8718931980b08202a530382888a8c2390d242c9488e24452d416dc13449400ac4cb630c97828e10712e08965b6f260d10483ad8a2a49ccc09d40866dddda975f212289c68a063688a0092648c022b6026e0b11307ca9fb8c0bf188148d5b6a654631d101d97be413620431b0c005942c60a858640939772b26e894acb5d6e71347381183a2e9052a489c280178a402575491c2298523312e4d2c11876841c458487001092e464040e3981822d6b9d2bdf37ebe3fdeff5b45b02f09dfbdf7823268c073885e24c0ccb09e5c266b7d66a1457d7757cade840acfe7e47499afb676de5c75b02a7cc102172938114105596c4fe0287984791de167937a6489ceda24e53273bf4302b660e2842b5d84601ab344ad6b22e2e7a8290c122eac70c10a09342580105725c83e5f2da8d5cc5aa11a84adb5b26de8c49624820f25f060c5cb72496cfbd4c3b65fc381eeb6ba0432330bb9c4e37629e0cc1864e1436b86002c11802f0090426b842727d70bd99312002926188491710c00a892710b4764156a7882b9a2a4208a36050094c8aa3d9f68494cd17ab8e04bb6d9f38956c3055eb2b7e7138d89b62484ecaab0f29f3dd9b7df696387d2a0efd5bebfbfcf397d7be9e307d874cef93bd4460694067d9ae9fc0ce8d379c3891c4fdd8e727eabb575fe4cbc13a3d571f7bd960e28dee8eecd1cd97117efe018db6d0f95ecf70904573dac7a08c1cfc3d539bfe00ff7feb8add53bceddbde3bcd65abdd66a2bb5ee6ea79d6228aa9c521a1c9deeee95d269c15a6badd6ef9c40f54bbc7184506a963cdddd67786b3e56d27577da8eebf0c59ef5be0a86d37edf773f50bc4f2c8ded8fc505d8aa050c7113f3fbf9de5d3a5b5dc6a1f50f745eede52e77b9ce76b8ce983fc36e5a71f8f8b10801bb3d2ff41e04c3190ee4aeba4419e41aa48090da30402b4a559d70b57abb7aa562ae4106982e7f4fa62b69585d6b9986331ca8c39d9d1ef550208e16dfde67163fa420b3fdc71f9670cec7ec72e5cb9e60268df98c12c1fd73eab4e79411255d3869f20313a957d41662d3a84d8308c116df3a90d0161f146f882a104cdae18be2e73065faa26a47fc4fbcb152893a0376871ac89cdaa01e91f87e9635fbbb32667fdf13104aec0f0b94fd3d166f7ce3f6be136f7806c0f80a958da5367e4ebc8127d00092c00cf083086a96d96c5b5cf36dafa8b7432b0a12c25851e40606edd2d0848f4000c60e2e3c4d75310b02091fb6503a6bcab3f594a504b1fbc41bdd986d1f639ff2230863892b6ea0e104501c314495afc3d16996945cfeb7ae053b3ac4b6fd6c9f7c90b22dce6073dce7a80d04051412dca0786284941767c4e6fec781b8afc1bd02e6e68e0ccddd75dba78722db5a2156d7ca0c3be9e8e704166d267c2a98855114f1ba9802b338018371fac29410ab12c6065cdc02a103a8942df6553d0b11d90800000023150000201008054462a160349846caae7614000c7f9e487c54968ac324c861148490310410420801000000034668688a036d8f710c200871e6e8a3c2fd99197feb07902cce524cafefd92f2272bcd65401e29fd44a851cbb690287d35105a34ced90c269de87d3e0be909d91bb5fa76f679e3117fbba73089e70420eb9c6e748c104ff861eb8ca2b4f51f3746c329ef600837f76a8b554fa6763110b7f4471eb75b4a8843c73b2372d6fdd865942867896a2d7345abc89abcce20d52a29b6128bcd81db8c86442338ec234cbcb83b6365398fedf854fbe56c2e36f1919cbee65ed49651a84b2d2021950d80245c84933fb059f494e33b854c2b17733857119ffdc11fbc0a82805171c16ef79e67f9e508bcab21dab9bd39fdd34e89117ceffcda36a55d60a51af47ead2570ff91d6970548457d049f538d49fe984f55a7448217630bbba23ee863dafa605f916929aa8c9556831477fd2d67fdf87a78a645e24d71f83fecb1ac1c4fba479b8a441b2ac63d25686ba86877030a0368225201cc865e5de05230a1a5068326f85ae1db86da83a1b84dd5ac66478224ce7c46b31354232fc493dd4a20283fbc2611d6b827ceac5992aaec1f014a6f3c4fac1a279c44aa76864b811808fe53ba7669060cb5a591fdaa5d1593f25a4afaa50e077acc90d1386c54e71e81d4c865a2e3e2766196e6a177c2e6d2aaf5f70402d7349561510db41f2151cf10e2a10b00f33e25c212184dd76d121bf4b8884e12590f16df0aefe8a6304f006d9693b9f6a73f5e80ae38a013717e144eefb77d3705dc29d984dc27a6cec0ee48796056bfceaedec36043ca5fb045d17d472061b98c4feb21138944c98a089c2cbababfa11c4827a16999e6a3b28b1b1342e0f87680392c503abef768825b451e818765757622b81ab5e217f49e4da8db92182f14121fd66bca5ac8726f3ef89b8b271097bb930e24d9eef729921c195378342661f36e92d849eecd130e7c7bc327653beaeae6f6845db752730f65a72a78fc49768ce0112f6225a100578128f489e79d1885b9f7bb00c1b0dd565cc633936e97440197f426c243ffb76c482e9826d733c0e3646f34fc6c67222cd93bfca368aac23def846c5728ec3b129774bec3e2e4d0b649c49368dda9644eb1b9b48bab135c1de477c18ee7221620335ab8a53350ca6867735764d98a5c238056f043fb133de1817074fa4834fa1297161212ad602aa122bd622b67531cce05f4db869de9249d103eff160a216b67be296351a73c72c2a1db5fd7291fd89b5a4b3db780b28a22dd354c8dd48c5db792cd964413b400e6d521bedb08fa8aaa006b988ed2eeb0ce3d13c48f88270cdf57794d4a4a5b0d35288332432c4e6de9dd37e9323f52637ea01d43b4b4eb213b224211288d915d5192c82341d15eb23c94591665a3b6007d41f901e3af918fd4be2e548e833b5c30ab5d456b8b90d58a1b11e01e170cbf870024d1e774975734acc1670c4f50496653161b7488c5c799244005f420b4e0585e7466a0e51aa980f59e468eb3f42eb36006b50aa93726482f5569b95312cd5489087d340e3b00d339839f8f88fdad053e7ea13d8c9031b388dc5256f34874541a37b04672d2944331b4eb190e0f665ffe24aba897b774ab1170dd37eb49c12b83ebee61887d905bca2fee42a7b5dd869d9725bc5f8cb96d3af5258c4e73e9759806f80bfeb254a4e61cac8551876fba88335ab9cac5c2ff0bf3d0910cb132763f313fe432b2d06c76b958cc9398e9a4694c911cb32c6168567e2874c415115d534da8e3d836bf9cfd92f7521fbe09297a3dea4465612352a76cbabc51d4e78925eda8a98a86291416b539ad401209a7864744a5a3013003c65e2ff738b6528d7b8c404ff0637ab0bb45f3b8a9e4cc9f0593981256370007c416025a8ca3f3a329754d015ea4ddc946922488b0ad28efa94a8f453a8f6657241b86640952350a862d412d01c35369833306f0889643f44a46a468659483a5d84ef7f2aa297eb72a8aa46c5f7b63215e395ed2948e3d05cd5e3f2a5ef6bc28208d2186b29fe9fd4dc77494f40476dfa33314401025e02da5111fe31baf7155acc08219680ddb1a54409f1b2510cc6ee9d202e3322841bf706824106fd43652eb3dcb47c204d3ec8b905d7b1b2f2031b13439eb7f887acf40d21a70fb149fa59c93cdf295a897643186806b32be00f7cf31ce2bc369c9d4f35356bb187f51c289e4b6474b5e2062c6114f8d95e69919c89b352f09514d282bc828404c2ca8e753cc7b705dc7b2471c693b62e9420a6b981402c1aba62eaf8b115d1f53d468bfafb6d517d1d9d6a2501e571d509a3eff881e11de5b7463775a0002d7093b55b24ca71acdc3be4b8f474a6fa64bd24b98a5aaf99027aec3e0ef6c4b7833c81ab30b7edea79ecc071c2ea37038870a228971b9311b9ea2ee7910f26f5bcf29655e4b85e89031b5f81381fb279dcaf80242a0bee71db5506125d5285062f242725ef1e2c30c78abdd8e7ee8b2d374a91e0243d59c7acd54f46d2be44c0b7b27cdf1ac684158ab40c58e6df6a2a3ead8469e3163be4178ecfbb53a6a43b7a4c895246d9109f0e0e58dbb7e596217ab5804742e86c292728964f7de04dacde186db7f09d2522751f7e021596fb0d4f8da8e5df75449f170178618fb34b60a24c43586d6217183e275d9325f509aa248e94c9cdfd6c1c2bd2cd3ed692ff0da7f923146fdb2b39ed7a835aa9d174a672adec0d8936cd7222935e0cf19db4465f0c99efc30418fe5b15f8eaee5f9021154af5e8e330ee0c2983fb1c0d811c7a2bf6f744ba0d54b9108a13630dac02bab1af867a87a5d53402da985c12b9dc7c25b8eb96024a864b3988bd2f87b917d19737f8f2a2ef98a492d7085fcb34a90e9e09657061d901522b85e4c391ed0c8f083c7774841bf96ef6b9fbd4aa248e42c3b4cc206d5d55040124989029dfa364c8432141044e462f9f74329e06031bc263f792c4d88203bb0841ddbe2c6815fe09d01c2ddc6de133771c4a536bb97abc1efe29c7b970dbeb30e7e6d6a9d2a88b8419afa7c9d33be8092e32fbe897cb11a2308360a465724d24b35cb3e7cc167193fd0383b9144214092eb5964e8ab220d5c4385d6a15620befc840c3f1b6035aba29d32d11e33a14a7dff61bb931429c3931f29a994b444999a4e25a0114ae58f5e406f947acf1e494796846c1c816dda64d6729874de08856120e43a2db6120af832ead6bc970323e358fa5358b5cd50e8f194f9e7af149fa7be620ed466a74607c66621292f0adfe88aa038abad0086538bc9038ac66c41fd1fa31be8901988b219a5bbe18615a36fd6940097221161d7a7175c87f99b85e48026aa096c8454404da8972d8bacc0ca388c5d64e5ebfc9d16b448c68be0e1d2ba9ba2e3080a23f41623edfe3af551659af70b9524380efdce306a16f46e3c409195f195e23ed018fada946f30f6e871461c0b732d16f356898f072022ead6b864ec4b721944d809c6ad3728e469e0f375b4ebe0d1ac2e1f8b82f02de4f023fb37ca2466ebb0a0c5c7da2603bb893e566dfd6dfdc1d79dc6d2f4e07e197703dc5501e476e68ce57bf2a323faf2f08a6f86933d638ade6fd8b8b7e6e78b81f78169ed0ce8a3578a6676e518006674c169481a8b30be9c541519ae1972961e2bd8b729810dcafab18b580cccde253f6bf5386fc86c44e95071b47f632f19fda19f2e805858b8973bb426539dabc414195394169b693549c9c2074ac903f0245e0da0ccbe553bf07821208bd1f823940bb08e175d42a68ab2e4faa8588291332694c31cd210ee6e3a96ddd2c4d5ab7384009e6ad5a6c93ac577593037626fa88e7cf92672d52d324c5d6c96088c37a6f5fb5bf00b9543cbcced90e4f84904da2de0d46f60ed4415fcd54b8fdcd0435a8d16d5d4010808fafaf9cccbb657014bdfd651e5adc7c5cd75d514c1ef36529c6844ae9b31ccf9a7c4b6efb3670e11f2b53307f0923963de024dd8fb8180deead008069c27b10d299a84d9ed1be6d545e560681688a0bfa8a030a1995f838ad76724519024bb1c4a5f9af344f037cc6f48859c9c2f44d5925e65f0d48aadede6812dd5f3b77e6f90d6a38c5fd0cf474da55c7b768a6b0673454eb32731f17f4f8140ca2846a640c6d2855876d4fcf9b4df27197b167e251837b7e175aeb37d945980bcbcfbb3ab07b350ee533fa234d0c4bb22ba140fd9458b1fb63029c5ea1f7a37416250e553d6b7eac8f53d4c79154134c91088c5c19ea7276df25e519326bcbcff9c10e93a2a7ae5d11b3d7a2bfed0ab03cbd130a10391f87b1e8562519ef4f5a46be5e5f2ec1af93ecd8ec786104230bf4db371927ab3b0323c7cc853adbdd7bdc025f08887230ad5144f855e8afa121cf0a919b6ed61a1ca4d66ee6ad03a9374ae0b46a0d7932b700191a815394efcebea5e5ab7b783845ed7a72fd88c9d95d35b9fecb0080d36a5f92c8102a6182c0380aa1d61f3d956818174caf9f764c8d22b1e5039bf27844b9d4ae4d8a808e08a984a384dd3c99ab9a21b4bb9ca29dbd2fb3aefaf0ed3e4117221cb8d9a42c72da1640e6a6667c0195d92b54d14b802c961e4f31e7b3138245b85f9872d36624cc29f254d617616ffb5dc348850184f162305622343c7b1061ca69db8baaba312471aa376df5d78327dc650704987df93577c0fe832e32dd2a6cd5b60208433173a7d41bfc98947a727c0c726d2d70278b9899ca3be51271d4a9eea132d2b7be91693bf8d17347ca2a34ce481958b9c06589e7329b3192160634a420ae91a14d62b01c01622994da47ac829b4159762d5a45c456d9f5984fe08070a2136323d20831b3f76c492176975158148989f1064b25d2e6c660a7f8d6fc1c8d2cf8986483a2963671cf3b628752e265acbd42669eecbb6217dc5acc344920aeae4ebcaf63252835efca55c6054d883699aab499087d42c14f46c96f0f94825e0519e47aa38d7816db244346fd4cff28e9ddf5fcdd81cb525e7457deb5f4de4af15bf410d113111ac6b87b493ea43948c2422d4417143680a5f56073cec1b177d12aef3381a84079785f35c8b02da768ef2673233a18baf719a00f6b6049e897a377604f01b510a3d668df8e7ee6952464396a451223ca0429eaf7efa3d25e267ca04e83dcf350563cac94046e59a8e3679fab9f16b02813ae0d4e98d5f65743389611621f6051afdec4f697f6fae3367c7fdce313fa19100c6d596c33937d06ad2a875b2a60d3bcc90433ee06e44162ac5ac41c02f6f96e2ca1e12958893104793125b10c0d891577ff0c18c6c4a21573dc159cb788c67d8862f73604cb5d50e75421a14a4147d62f6e2f0f92bf7051aa4721a58e208e31c49037979472d1557514b7483534a21550498bcc95d88d3f813d5127d82d786263e77939904e550d76f72e8b74aab3607942d9eed3e7317b1e98e5ab5cc178e1f1d01757f49bc3c20cb1ebc546d66004f303a981333ab505889b7320a029cc63fc3a337750a876c65bac26e82976778493a943deec4bc73c1ebec2c284e57a6ac0cddfa6c3c5ea55ac1c7dfcce9b7a5780e535be8331b323dd7286d8ee090ba4c6c9cde7d2996a38a0698cfe9813f3cab6610d4f974c964e41d85233a6c8b8c05b673fe67eb04fc31f19fb36f503d51f3679933c137fa0d0290590c18b32620155b1023c388808b9de48df91d1dd87780b6c70426da691d7834550a2016158e3256354f58a082b0afa78671a0423808744059d32990555fa91b6b6cb80be5d9a7b599c764450ac90a4608ab145f80308809adf3dbec8c2d6146286d9c7dcbaf1b07da8e11384f603eba4859326d156ff6ece92da8c6e406bd3456bfb315eeb386146708298679951248c8caa31dc0370559407c32e899673b52922a7dce31d52d7d35f0291768461d326722c9074c9b64618b4f26f1c57f17bc0a5607e0294850ce26dad78e7820a866f2973e5852f79bf3cc5d1449a39beba67408b5a42c50c91d054e8ac9df40b01e2d20e212a97d4259f0a88808cd0defa879ef11c3e4ec28e9acdf3667fdb86658a08cd11da9e81610f86ec02125e81c733a6b3491dbc0eae111b6050e594da4dc28310bc0875902ec4e1b692ad73e984bbe83a601910115b6cb3a4ac1dcd080d3951b941d55914e9293b33aa02730b2c232694da072e341105a699575cf523ab14044927cc8c714c0e0c26da6352e627c76670cb35b876cdb04c24ab7584d07cf6041830cd0578f380df3d9e0e0586d2fa8a1c029703d6429317e6cf0dd08ebba8cf243c9646fb3b6969c97a233816686b72088732ab2044209b1550a309dce7722289f3bf8aef8a248de09c7fbacc20e7fb775ec535b86bc58565426e5df5ecefd09d29564aa41a5f707f28be0d1531fa17c41e87dbe76a74b2ce9627eb190a5a8a62f6bc2c3aedb778378f18b5593477a45942b6da568eb2615c47f417cd0a954b3bcd884f680e96754dd20e0b0577dbdf039682875c0c6ebe801c5821a776197d26f9f5337b4a9c217155afe6708ac80d55f9c155915b68ab8730983025824f3d712fb8d787780d6b4f7219c21b77e2e5e32579e63026066d29e68d3baf8e919e54e1800edbcd8e104662f69437f217d768c58e7d09e35a09c23748251392646e1b56183cedf2b3a44ed73d74c160e6f473f2f002757b0d4a79527641cf89fac7b4202e9c9b4ef1f1e0cfc59b3ce9d87cb3fddb6c8be1ee52c5b2166d063fa64b8913abe39e4e27288cece83d183dd9fc8a1c6150dc0045f2eef76a206b60c154580e39f4c5ae5bed93c282a60795905a2aab3c9fad55e4960a30db96ab82d4d91c3f93a36168d8b5c703c2abfc37c69a455de463d2c31596cdbd768bab16d060810665c9df288da236319d763910b5cbc549f4a88e04ba39041d55be33d1490bec296e9bd74f3d27f90f8c8a7e595693367a8ab7cd19bafa03a9e0b1b1072df9014ff2d91874516160e8035439da747e4e3a1de180509e3660d077dba87dfc185a2a0d8d4b01d84ffc279123ee7fd619cd553baeb6348341f391c6431515367cbfba8a4f9abfe1d345a283880dd26cc29bfb4e76e2fa68c3eca4518f1d4594a1e6d2edd7c8245ca90735dfac80dbacf99f58d063a23f884913f47516a157106a122cc9de5d63460ecf0f6565380e9610da6117378590ac4ae726d276a69beaf2fde81b8b0c2326492769a6fa57af0a62ab5105bdbe3a765b8dfdde93f5f89c762a933463b412497cb0c4659c673050e12bbea44ddc5e50432b690c37801dba76c973f096484723726ea2c1d3e1730f2ce7a06631a4b0f7083a5261888fc691d3843f29f631781f6d4e475490e8111fa08a3c28963a52ced91484441c16a5a5c1e1abd0a7254b621fc7c7c847e975947de3318d52667add960d7c1a659e347eddcf6d36a3c27edc22a853d734311198959d471b20d6ac00148c48828c90ea6cd7142b83a520e522fb7f72c89722581cc1de63af0b058ff6dcb86ee09f0237fffd486cdbd1e4ee818f93c1f8c99b44632beb11f998a9d16e79930cba43d691729bee0379dbaa8058d4dbcf45eb7500e267dd3e88bc696bbfb1e2feb6c60a022a1d7826ed24ea77de0038578e82809238578e88b5a93720dca22235a9134919e0ea949a0f3db03283b7b0bd468f07b143f56f1e0073ea7fdba5d85088ba210db497ab80fd7c68dbe409c4c1853467d0acbb5cc21a442d49cfd634d1118a251884becd7c27f63b002b979b1939cd9ca0937248ca431020190e2fe7b3d10090ec1a88b3117c7a1b0ea28bb3a391fd641c980ca5d0fe5c4c90ad02a7915a9758d28d42538e3fccbe117adc740e3acad8caeba6a4a6d3414a39850c749b4d2703bc6b244b5d830c4ac647e4b6ad74d7a785acc28ae0d5d85e1e90d589e64755ba6b9c6ede53ab4a89b84b358cb82a794bc2f8faed871e0a7685e059ecf1618de400afa4526b43ed5de41d767c6b827cca2792d3112f5b5481cf86527919ff69b064935feede3e023882b2361c2548e8a9c66ba34e0975bbdf2a5285366762239fc725335b4cbc2412d1018e336c0a484834e7598d1eb147cd7c46257178b34012aa80f1a9c306c6d63d4ab613357f47090fd42844e596a824340b3680489457bf366085cae375733d30b61e4c64f4ec6d833d1050e120badd0259df9f1154de9865955b733738c8996aaa1cbe091966e68cbf13be8d9ca71ce17606a9738bffebd651c38ba6b23410425d1bb2b8eb91db957a96409c33729636345d9281c1b320651f8427e4a8a97282cdc9d57a2d5ad34e1e579622e9e48504aced3c1bb9c804e5d513cceebf69c67b6e6efb63c9ab0bde412b46a6e884a53f98cc2d817215302c7f5e804bbb9d7434f551f287c419635faec5497de2bb7fd319265ea31cf8bcac5f4b9fc52d5c4c60dbac2cc244045cad802b64d0310881ae4b811c7d70e70a167a31275e12185fb901f18dd254b79863a352ed85dc4a1dada9c7143b189710bf845c32f5685f872cc34921775abfb74f17a32f85fa7ad99a78e0d9f502dc5b479043d531d84060c454ee33697f3fb87785225091e6460b48997a89bde974e202e57caf7a6e4d84b555de206508655deaecb02956bc52a50b9dbd102b73a2818ce8d5b24fe28e77c10969539505d5c5a9571139882a2d2b9697496914335120c26fc917c3ccbdb1d4fa08a19f9755be20e44409fd7fa6a699198aae6314c2be68c8d17bfcd156043347c335804541a288e0ed573177f9fe336755b2394de9bd9a6046ad8fffc825de8b6507774001fa222dc46ca404ee0f9ee05b032bd900183d587158c4aa81c80c5be47099911e36dd6e1ff21e6cb36c258895fd616744426461f9c441e03bc8920a1df2034eff5de2a8f6b554c61a437f4cdb552a04d6f62b2672f23dfec82c71e57c5c2ac5d60812d353b92df92b41877bc289423a6485b3cfdad755210248ab7324ecffae6f0a30feb9b26d0a4f1749dbe10464de05c20fe8e58861585010b50c6ef69d84438265a80005aa960ea610d425c433f4fe86dafa281860e27b5921346eef35596eed0a3372324e98a3620e684096b49b32cfddddd03423b14ec50847c12480153070b4923fedebe2e9e74e29af29fab189b643ddd67b3af9915286c13e69bd5c8da852425db90ef02af3eff628d3513b8b90203de9c28b7e4a2f191b4bfafc65efa0db26fe78dc60ed5a95403f28f231fb05006b75ceac5f1cdde6d47c73058480bb8dd6ea5d0527854994cb35d99842b634249b01e9360aad971fb54123f267a2cb78c90bc9394f516543d0fe79d5bcb447dee6d13ceee72470dff088ec66e67981b40b50bd90f499452480d9be1220c53d80eae6d5b1c362f4bb8e3efcfecedb5fd621671e7c32dadc987abc3a51eeeff0b86c8097eedc06210d4177a959c6aabaf0ade1332c7abc33e267d1f9cdf0af150d87347b4804e15fee3bde26bb4760f58f5efd6733b47b7e21b781886eda52f2d20914c871bc9aed6842218a47ecaad0932cdc6f4a71a44166db01f873b5db97ec511448ae13d7a18bea9c14ba4a004400d9b9f4bcd59110ed1b8ce6c0ecd195dab4ec9fa98da84adc0c37dcd0ed9cf2f82625d90449a78d34d09bc7346f579e981ba7d21c29aa748f7815a5aa8c7ff6a16fd90ea4169237690982119dffdc63bc501fa4095650e8f6590e6c34fdded79eabf797beaa983ad183515369235cae4312ca01e3920db2a510c17c298fee689afb94f7f54fd79f05a8a46f8ff3364549f1a21af4224c9bad5faee050c4fbcccb16b4c832243f7a493e8a71c5c26e910bfe08dd322572a85ea26b5efa2d630fcabc8b54bbe817a72fc6981e0ba285f55524ba2113cb19a009b68905cfbcd6757fb75f569f14f297cb6ead4c7402c301262282a644081a97b35e0305febc42eb70e132c9c7e2c6e9fe1db2865f796e8c4fab8062c16a3e8ac69a2749359f68c465a1a42dd3de2de2af20e473c0d85f6df729b69cda455f6784f4eeda12e1f8008b436405e5877b2d2b2cc2f71e1822049cd117660f86032783d892b040bc52111dee68b8fd05950adb710b8dacf4b0f3a732622a22d18eb841f0df15a70365c4242142dd0670cf257db043cd878cfb6898d3a49bc27fe859db052d47840f8e4f406bd7ecc599a93533928910dbcd45f871475cf839fc643b470fc8b1c89805e226b100ac2fa986710e1720266d7148d2a55bdc0e70f8a2f10c19320f05a9ece04d13631599749bd954b9813ab11960385a9e55e04090c57b4a4a0e4318aed26a947aeee94e360a72ed4eecee06e7b04b24722d5c4808e647bc6e8e1dbeb3d5c5b7a4d842c4b7c2e0ec7ebe99137fb0c238238fd34aee8b21c45a280055da9204c670a5df10901c8fb228b38ec71427dabc1e54c8b2601ca0d9e4fb6198b08385011c97e4f74d6f9811b17237cf0d2db135a188c99ad81736fbcfffccfefe6771f2a60e98ee426292b92543abb636b1a2a8244106f2ad7721fcd19ca66171c6ed70322bf162a705ce8eb7a95a2d0494881ce5907cc328df0a155cfc453c0057f405c10240ed393e898e8e507e2e443f663fb4508a9679e70984d6dc598c4f1d603b1e5e8605d70a05849059e6dd6caeedcd5dbb051008a81fac4513f74891331e5bb94150bd0dc55a87e6d5f33f7740e051604109564b8b49c4b9c518b726aa45f460c9aa33efc2335ac8573edeb3ddceab85a4a604684ea15248d4ac273359b83f45032c7ebfc7d0d568557d261ac2ff01a42f0f3467583837a6011a62634323b59b107957daf7f68fe3fe947fa2ce34e591dd3491890c1ee75aebb9ba3d90c1674814bf8a5af169e4bf4e00f1e732a78c9b0cbe05bd01b8a704d76777bee7c267219ecc7b5affe90b02bc7e09dac7f4d1975c9a8e7f6e66351112cd80bf16e44ba13011d2a0f5d011badb03789366574cc8f0e60d29ad93cc40f58360eaaf619adb6df42c4d4e0e6639b429647c9f0c4672a5ddcf7c7204dbb1b0d568e08ca0dee4b5acf3ad746a0a13f6f1600d64216c34e69a920616891de81e515d884b80f49f9f44aca97859e79b42d3535a1f2007e83cb70d247be91eb3aab89ca9b357b0ee84f90c9fe3717480185bfb7da6c27256c1fef6607b6346257df31eb705155d27bd341647421e26667dc98482ecaa88ec4c50c84451a0e4952df50c7c540a09bb490ed13b0fd6686bc1e92833c1806c67007f1fba87f10c49a20604eb996cf479a8543bb003111baf87b8c4316620f360d492dc26f11c4101cc274653dc155cb045f9f9bb90f17b9bdc54aa84ede78f9837e4816ba654c213634e5c55b8e5a0538baa4a51584f98b4b14186a70563ae4965a812a6d290fe2d6b02b933d012e6e06d53a259777a86a5c8d40c496c8b6851db7a7be7505d74193d4e86cfde7b5b4b147634eacbd96f0ecf8373e702119357b6dbe10e43a51887a52d75007cf1ac203c162f802f77465d6ff9257339231577b7e4388390b6a2bcd42aa2a7b27d1462df3dffd00f92cb372ee8592cde9fc4db6ab044308a4709dd38d85bf319b090fb53011ddea6dd4d703322f8014f7dfd7c9e42de6a98490dc36919be84b09625ff99593a81607a21ff99d80ac5de1086828d5e10a45d500f5de27d91c041e03dd348c7adeb04aa11efa968eebae1012c79cea5ea6de996c4f8b2038dd579f4afc71873c4fd9dc4302854a26e5557b5e2cbc0047f9249f269fdfae83388af2e5909f79b1b2df13b2f104f998009dd206140519f8267f284c80fb535634f289f81a81d49e7c2713afdbbc5ecee769c2e26919a5142cee8669879c467d6fee0089960d84af82489f0878742b61a9ecf9de3201609bf453f482a6171d10d7516f0e4c93aa75d629435b71a352f47e9f08e2d6c29838e9e23ffcae87cd3b70886581210558242de111134bb7584a2ab3f9d168f18af167059eb2a2af2a461f300c7518b33c56159d0d978a51a1a096389fda3df65dfaf59488c862d2be5762e1a34bbd652ef43a7355b50fa7e47f5b306c6c6816d98fe1b0f0c6f2e12f981850268419ed92ae5ef29308c7377ebb7d1c9ccc2cac28edd3cebed585c3fdffaad14f085b43e78ec5b1274e910dd72e11720a054e4c117d8c4954d120703aab5f42ec2c8fa5ae455e170f61d2e59e288d21721822ba9a6d0684f015e36a60cdf80ec50d28b6dde4d572dcc6263cdf015e40d50374952f8f8adcb41e8760b9de7213825123db2978483f9d22c1106894c9e850c4759a8dd4d592cf8e3d2b964b1ae1a2caf9571701cc2eac65e1130bea9d9dabec0c4498f4300c721491b3aaf95f3d54cd2a2342750c8beb5c35c289d96e61ecdf84d482237a4c233eb4c9bdc5005b92afd41fe9d2bbf6fafbf3b38cce7250f592fbc52c90adfedd5fc4a2ad5d734c3858a0a06d553a0de4f8586b8655321dd980652a9bfb8f1586fa61d3332d3948e1b5738ec7a2a73542aeb36efd8ce806025c2a29fc21db88138ab40109526f1514ff1ab0d35d9f323265f4bb09d59769acaa6288da21b3f188e0c62f4c7307ab466a6daa1518cf375a9b037de6743700142c23d9f1f11a01a78ed735104ce10a45f78ded08cee6d4e0cfe7d5d8af32e258b43c82de6ec22cb43aaf3ec33ba4b19752d4f8e5c04245623e415e17b55149b63f5db00889d8a451e158ba3e867290d6816f33ea5f10d54bbfdce0a6cef939db5c694646f85b0752aa6428a2d05d2ed8395e3d3d476692809a7b044f0d4fc417fa741c5f837c609934722c0bdf518599a7509ca75b4ab4539c6cafe484a9e9610bcaee2d9c17fb872aa0c23d26b1fce5c043a0d174a5c90c33e3e50fcbc34438f15fe7c3b95b637922d2cd4448680ba54a3407d3827765301f7a00b8b15280a3a4fe5327a6579c31b81c737b192c0d0eed19486bbdf532d4adc63d10e6e79683d98583213522c0c08c1528b88f51dc9529eaa5fc2e1959a28385a5d6a48d25e891d8b033f378695c0eb15523f02267cdd728b3fd08bb42fa3d8f07df9cb5aefceca2cdd1a106ec25185b75891e00761a78160dddbe03e88d7597fe0613a6e3486c341343f789d68f7f2e372aa7688e9bae12136183f6f7a92bdeb67e881bcc38582ae26fac4f8a9329fd751e93331d5fec6c7ac725098341daaf34b37480f8e9cec1f59c9095d9fd1354461578aaccf2843249973c4c9ddc9bafb33ce3e0c4d098bd079ea8861a7f15a019aebe7c62b0d4e242679592eafc949b84f04d7e16b89b3da40c83d607c218d2c374e74023a7f44bea6810e14126926710e5929b84f8cac7fd6b59fe2c936915d7f633cab9d400fe0cb3d2a1b57a5b34e332395453172e9e05708d5e5b5bcc39c1321df7f071236380fde011dd26270500aa519b2731c398a384696c81d06a3022a6293b5f7587d5ef3112ecd748bb950237f4937be3b3eb206aff085485e5dba38446925ef4ba835a196b0cc0126f1aabd4b4c07823a12ce845452d334582f6bfcb6a1ba5ba612b9175990eef9606d6f47ae4661e97868a89b4c845f1bf950d4f98fbb51b1ce3305a5f893dffb315c4cf95900a0c2567a19913708405c0042e8dff254dfa600fd76c4a242dbdfd08cc06eee27fc3a2f10048996755b18b67ff419c1fdc04abe3c3341c06922e83da7997058857f7e79cc50b16018f309056fb7118caf53fa5061bffd8bfb35e9dc2ee93e73f235c1ed48742350619c3e99fe321c4f96731288234f801a076e7ad65a7944519f70dd3d4314b5a5c41c1113889da04f23db4c87915e0124e066ff9ece35104f7217cd2ae131abd9d335ee583b9abd66b0b56719e0bfc242c98ce6cb8ec78bbea43f6a8667b20ec84037fc787dfadc9c0d986f8d9c4d4339d0ed4bf86fe7e543297569c08a71e928705bee168d2412a016dad6c2b5345a51df60f9288080b07b844ffd9cd41998a9c7c7ee05d5e44d0807880acab4b39cfaa44157fac4a0e6ed29938a01fc586b3cb3a32de53117aaf55b650441a8b4bc2eb179fcc87e1f50105c177cacd33b9751a96315a4869ea779257f4cc9a4865b53ae60249db0242ac9d83e408940675e22347357426d14a74b68d1a18c788e30f35c0706a322bda97f2069b571e51f28e3c3c59588d3dd24a8de4c11af3a939e9a0e60248eb9b323fd064f08e88cec5647d2b14dbf99473c914cf2952982388d60b3dec2124b78d2119210d326fae7e2e4988ee40ea7f138925e3e3ae884849fa290f00b62965ae31db3c2bf13d05daf792a7f51302da1e2719613bee873331678720367c6ef152b7b34243a406848074f16ec6a3eb40a7588a6c8e1f4de399b899eb807402fcda1fff38b1580eed0f5fc0b47da9d526e8a4031939f4801d10a3ae242a67e66c24116065849233e116d74c9d8033354908aae13a73cf137c6ec0b40fe464836345c17628dd7f586bdb45a4d6c2c86e67286234e548e46628595d2e87ea12ede97cf2c652e54d99da85b20c150309211c38717f31e3517a0ed4f294d9055bd3459e755e2bd1bbc1cc462fe1198a287a27165ba3c6c10a59050f91b319454f80e72f39cde84183979a6102333f9c403b698f9d3fc01c2449bc8a46d3a469210553b227898ae1036e5ce814b4cc73c8a0d6b412a79d769b31f7a1fc869570f75c4212e5dbb6be6b4dee99c5b4b656376f925d079fcb70605cb936096d0beb93639e115ee4810b271ca59cd55edd4737e761eaa7ed90a1118621542011abbd9e36ff4a9964eee76ce62b6b17f7b1dff4eec31a7bbcb40c497ec175a81dce25e7008932facbd713030ce1f75030ddf17db37a56af37c0ff28ca8c1503f526384ee4a2e04fb1081efa6fa34d1586de26c6d6f43a99b08e0737523f9d47a0bbe20070cb23cf04eff3294f8c88df955c18ea2cc91b263a9fc7026c568a586b4c1ae78e6482ee154d1d10c95170db63f19dae95cf08871d959dc56f2837cfa66a9c8b1d61f012480822d831165960c4ff9b9681c566472bc60f5fa520199de14d2bdedb624cca0a9c8d24ea21c26832b9353a48729affc205c5fe039b9b1ef52f305038bd97bd4b13cd7b3a247be40148927f1ed4c194408965c0927b7a2966d17a4a178459b16c01ebfc4fd4ae043cf7754bba6a131c080c9a2151799a1870da1b79da09a2583f4b3147255ee66c6e23ff9454b20ea6726b97f6614c139a376a49cb8fee32ed2441c0b4154da16ad885eeb4e2631b5da6c40d44ad475baeac7f2ace1e7901f2c465957f8fd929f2df134c6ec3b0b7e953e5d0ca45a1a2a43480033b8811c3bee2f2b29f62ba5f36f4c293299c9bedf24fde4b11b4ea9e83001976ddf4b760a07f4097bebe0a562f666018eecc31bf67823dce1c02d7ac28096283a0d6342a8f4a237a3e154bc52d98c381082ebe1fc17aa6169ebe3af05d0aa35b2cfb4efbc6a88c006471847dd72bc4c3057498b1c79f5da2db6386586ea2451bcd500983385aa7233440fb126f352068b06e1b50b477579e7e81743a56f2398a65494fbfe15409f4bdb829b69e255c26102675c3ddb8c3d1cf0805ce6f86251602252c366eb4d3397767b4a0a2279118b4a1e9b34336dd679666ae3ad9f98ce959b3ee4180904ee9a40c511b808025b18f71c55d43457b8340358e3e8aeaf2a28093633020d404780d89b28eb12bb999098cd4091f2cc2dca0bbc39e5bb07be190c7bb2889264dba7bd013601dca77ed76debcb6039a9be329c2257d709fa004095c8666ba67aafc0e63f8e0f0efea3718ce2a1c7255f20f8e4bbb7459f480e4b1c9e7e910f99625da242c64180a8a3c153519882b1b836ba19244e1170eb94c5b3145507bbef996412b10a206c648b716e1fe9c085d9f7e660f2e21b1d1fb58ecdf8721cc9bb318067f9468a91efaeed88462584c340ccfbd9608ff46fe2ce7f6b39030bb6ab2f1ed2dd3fb61794d48131a17e09b664753e3f60bfe441597d0125773e75ebcde6f526b98c0e0ec3d59a406d51da3d1222ddb3940f674c98bece6fc8d38531f23f876e598b65307b66eb3fc1d23f21eaf406b6467dedc07dfd10ff36d0849b4262dbb95a172c39251b2c00ba2d109e0e603eb0addaa1a21dcbec670d05917ebb2ac95195d35d0008fffbbc0047fdf8cec334e9df575d3ce6cda5ead503da56e489d90255e0e872d8b0db0635adec800fbcef58138c39c353144012007337fc63f38dfe4e82723ed0b09287ea2ba6bedbe05ddd4257429aa3304fd764c3b47282fe00bdcadf3f37b730193c343da06c80b9015536be084d3e341ce2b225e037c9a0bea2ddb76ef97d57cec9d7f2601802571fcf5b4143aa35183cab80c6b7ce62fa25c557e61009e72ce9fa5898c6578572fbbeb6591cdf5de9bbbd48ce89f1d0389d5ae22d465b6759d17258453577aeaafb9c6750bc155a29f144b6cb68d014c39ec1e4099d201726271e95ef2e811eb047bb971c1688fa4e8d6eae1350c50dd60c3e296c1a2287e4aa14e3de4c5de840284a9638806f3d0206871856b14f8bd48747bf1ebb7cd5283b09c94208b37a71c4cd38144230744ff9d15054e5484f795c05c7d25774bcc89fee821d095070db7592ddf18a84aded3b59169696daf60c997a2a8ad2847cf72bbdbd0232f73882f8ccabe44aa102d2fc61efda093fd21e7b57180796032d3c35e0d684fa1b1e0bf83fe84eab8d3f865ca56b54128c8328f77cc936bcb65cef8ae279fa62085d34877578e0c5837b374dd106fbfa35f955c40bb5a5f87ac93d3c1ede77b8ed85368e9c14ada827a898b2b900ccb1c7876884e3d3f5186c5f31ddb8d2e9393e277034bff8a0f91dccdb00047d80aa1feacccf1e1749e5940c5c99f85eb4ac1c0256abbfb4264d07a77bb1f7f3d7ec6a6883b937fdee1f027cbaeb4220d814e06b66373768c580f988796132ff93b7e9f2f318133d286e78773f058d0a8d5b46fab4ce94720adc581fde2927fe0ce443577c50a7aedf8fe75e21d98eeed93567833bf8c878b5b088131128a7456b593c21c7f0b8c80dd0012cd65dbbcf6d3f132e097081f1cb04c9385e1444cdd836e2f019702fc856a3ed1ad0188af8637f830320670f0b331342fead9091f1edc33c5128bd259d9a8ba89b95afd984e6411f7a2ff19440657edf17c8fd071a119901de9faccd44b07bc9197b5e8d72bf587a583cbcfc096054f0f78f0f43686ff9e7e95457fded4ae03359cf0db5a98844e7a5b563096f92f0c96c463209a51cde59c5ad4e3180628b7a2912e50362801433629dea3d3a9e41438301fecdb9150bed31b03751d5861f604f33697378c396867988f925953a2994b93cad955f42f8f07048a81f58d47f3ed931cdccd80ae8ad54d063ea0a0c5d3d19edfc72d777276cb652d90b307f2d1021edf9212542e54b1dd7c3d2efd90f81831d8801b68850b3471536298481492ed68d2940a140f8a2b4796cf91d151eef277e2eb0302c64212eba6fa5c0b38a1570f309a2f79e4372d09dff4ca193339f9686c635a7cac4929764d1fbd1a1d09b29416b72a7d3bd0e82567842730562cdc7e6ea759d79eed85b0ef9591761af20ca076700e276985e25fb12ec662f0db86c6e801504857202d6b50facb149a6f445449ce14f62480ae455eafac6bd218873b10daaf77fedf387a71751418bd7a9df80501829ebf41b4edbac835a7a602b1cb334798d0bc7f30eff00acd3502fbf153a91bd66e37a372ed3e58ba073d2a06dd1fb539cffa98ba07cfeee901e1ec28e121069922a5152793c5cf9fc180804a0e726a8a308167989cc9654b3805762c194a693acf66c3c0e34e7dbad11850f1bb6fe1d408364c4cb10f4f3760b686adabb460bf486844bd2e67eb69a10eeed5599f1bfe41e11a59110451a1e914d6dd4e0832ccada086a36bf620eecec50993f63be23304cf04a0cbe725a35afd39abc971d049b65e0c4fb18f285d382256260eccfe056b839b8fdd70b2237f7b060b32932aecfa2600452425b2009bcff687be3814dfee877b012bafcd10b851a9e44fc1fb135408e9b1f9f3a910bd0c1dd88c9496b45fad4dfdd97bc51e86265d863cbc328b8ed8c18e511264f894d4c668d178b5d86811e6bb726677296265f5e494fa9a15fe058628c06f87eaf3bf88e0c2036eac63e0c91ef34f50b0595bbf4072f7224f83664695867ae1bb678015bab4035e2e109b949ecc6d66a341839dc7a25d8fc84bd3c739b90a1be9e3ff519571d624952e46bafff3ef676debc25ecaf377055694cacbfe9acfa7703c946da7a0383ea2eea88cef330c855f7dd8a107b31e45619a4a9c5b15cdadfdd01a5891c289b70372b6837a44517e56066bfaa205928739260e66cd3fa8e6925b1b1fbce097d3e0aa33eab0b7c5ea4c07e422449081da0cf1771b30515bc63cdbe82635e3025713ffc0f56d6d1900748c3244887d7c68d9b94da5cea781599621673046ff43f8ecbb14e53ea300921b16e5b03deec222193f52b3b325dd9ad631c6e933cb147d83cbb355ced395242260f66e87059cb03568922dd0c4d4b20ddd90f3594e823af705800bc4b4ca4b46ed4544e9f3ec5dd01fcf74e28426042237b53c373fb7ab2ba08d7784b2ee8dd6548c3a4f1234ae6b8e3d630b52e61ab4f8b031c349f124caea74ad91ba16e1ce341bda6abde7b1ddddcbad2762666762cca329da72292876967ee170366736cda2a40e9e3c2b3cb523d29a7c4bdab1b8e130d5b493ec3d7dea6a920dbbeb0e51a5c55a6605b7125c8d0ce45db87ed89582803e338efff9c748e30ba310384fcfe1af1f7268c5fc5f7d3cc280d6c903cfeed9d66110a02af41a2661128815f02498b1d48b0d8a9eea79a54ddea152b552b4e566889b5003f6069e273e49ae9437ae5d59ec71ec0cb865a135693202bc8c646a88256a0cf712a8b1d3bd57345b148cd4e53171af122dcda5d0af1529621a0edb20baad2accbde7b45ed1f02433195802c0af09bd866e6fa8fdf9e2ad2690dac35c5dda13f24f37e2b91a3b890bb53814b40e32a963771978f5264235dc7f63fa82be5645ec084dfd211f57c5693ddd32ef309ce0526e2d226a56f498962c4b7bbc054e8170d8f8855dcff4ac4245d64811a2e580dd662575bb33a2d72973736a41bf55830ad71148335787eec433525e068093e8cfb2bafc5b9691c3ce2397dec4b1402682a8103b420be13475ad71ba9f8a9aed8d94f7f7f5061dcf987eb2d3c592253378fc14d0a3aa0d10ca2cbc59fc1345ede6b5e8b9aec74194974712a33a46c853cdb52d61c6343c784ac5e6f77be5263de5ee219e9bc670346edf8677756635c386ab22913393c9fc2ee7916b130897a55e2b9d322a1185212556c16b92e45ca748e0eb0b0422e27503822524a7c9d23db635a49ddca2b07864f2ab00c0cc0c97e212e2d35db65102fb97fbfbf135a6cd391078f7fb81c85cf2b7f7c9e36b0d39161ce7f4b5ec9a63392126d2f3b1085a10500c2cc860c8b24ab5c14b996467aa3c1a45cb8f48e3d9935ec51df6973fbbeae3ff0bd2bd1f2d42386d183c2ae4d966796a6773e4da07b73b34881c627ecac67a7bf3ed229ed927582558d1c75fbe91588ab00b2c160de0a456df45981f73dc98e95be68899d8d13c7acf6017f02802fcb034e168560d1404c5ff9eec40adcd935180b9c33ffce7eadbf6e571d741dfd3c0e92403a109ff6ec9b7f70535c6df2c1e60537fb3fafe6c99ca6c8b0acd6c2b6756b410630d3b060b3a58d0937a9b042b6daace8bc39a0f87d122c38209d96553ca6c745d74c6ea889b7dfc20508033b81c9aa083a0c93874038905878fbd84463427651a32acda86d16cd58f92e2bdd0d97a46866ebd28cf428accd2463049144cff229dbf3368a6867c3c2b90efd2dd5199fa10f547bbbe1e50e0951c1bfd086d5947976d04c2fe4977fe644319cfea2112622176327f4e129714d5dbe33cf8bcb95237a7e510e1668fb7f5c51023ae3db2b8ba49ddf9f0eca1efec402cda3c2d054fd682187693578281022468b3a6a604adfcb66b6a718344fd0d5a1ae03173449ab78a1cc829980e0f6dc5d91f8a07d1ea32c31b1ad785205e776638523f788c314347c11957bbbd80b374ef397e5d3a31a105daf920493b9bb3beb701ac738bf906b4fd03ccdbe4b09f923e5551f7241f43381cb5a27602af570624c14db3774dbf9747fd800249c192e67e9e63aac9cac9e48057fdf155a577fc35bf9134317099b64b85a8a5744ba9b440872ef8e6d7abf35acbd04861c2d59b53496632119b0007a89312cfb97cff6d4f57b2fe35793677261abe12b459edeb996f74c30650a0792119ef9700adf973c00b3537aaafe3c9b448707c100b30a24d3f012d414f36cc291b6af2ff94cdb0f76cb6ea6b129f466b702ccad0fd6d8b99835ed8e6cb3b481426c2bb11e57e117fe590091c4229924c34ef030c1144dacab32ef4fa807afda534eafe958be8892b6e843f0deeba39d2f711851a4aa693feea33fc437744459e105712a2160bc5eb054c35c35b424fe6c8760c4bc8f925ff452b7913b4e416749ee48386d36e3e6e9af29d9feafe3ccf9cf42194b2d24774e9da182a59e14268bc9eb81cc2dc0cfc232d5362afd0da1862d12b0264a62c0a9ccce61648967759e0359ca7f0ddc99e25682de13876c551f23cf4336f5d88716008f9526af3f14a99e1514872ca14569d290c9d9f36f655040820b1255fa0e97b3f80e17417959aac383f8ff197638a47624a058f704e1439c13fdab94e900836fa3911a13328a2347507b0cb4ff63c686a122e23b8ba0ee8cc21966aa22db1b92d58c913aa876ca8a9dd60853a4dcfb760ccc9e50e710b4339eb01bdd9344f2a30cefa95317b017965bf447100b703a07451085657c8df483679788121d675c2820ae1070248b46284036925edb7a06132ce2a943e111b585123be604d8ecc0b1777af93a540bc5f9e1e7cdc5799c65d7423fc518a862fdc698c5be32bac20fbb1d0f2dc41d011d034319fe02c2154e064cbd204fc0860cfed60aae55a8ba9230780f31f8a9631c9acf94ee20730242f7b695fca20b002086a4e04a300166947cb6d7812f754e5a83d5c76faff07a0072b7500c7b125e2e2ef15f6bf2498f4b7812d2e83149ea24a50185e09ffe5078f9da944a60a391365c0ffa96e6055388fdcda03eba851ac03e5d736651ade4aa54529d0612cbadc5c4351d28a5b7dcdd890d0dcaeb5ad2d8b048e1cc8a4466c149b577aaed0a357792bb1c26419fc750c681e2b5024b1392ddbbc84b1c501fa5c622ca68a4ecbd62740e5f96c65387846bee10b14382e177af167c12bbd6756a15e64826bcd3df531406022af9e179ac496343dbedd3cb7532cd23c6f3f429a5ed1d0400f42eafddcd34a6776372416486b5009cba5ff45a70c3104aa7eb6a54d66ac272bb83c786e4e3364e1e786f57083013d89721fa7e3a192e1b8633c40cdde621615498ff3c5f1793b704a27bc6f36580e6a2a5c1799ecf3b2fb1c6a516ac4a12c45c7ef87f19bfc750cfffea3b64c34e9d7fa75c8b7219b70d92c6cab6c13311850d18eefceae8df35d00642f3a7f79a8de06605c93fc23a9ac45a419e4308a89382675c9e79aa7833d9f52f895b26b0309938a7af24520e061c984d3276a136b5d094532d36d452537325942e7e7ff4c6ceaafa25ffebdcfe1b5b0cd7e1fa084ab3d18713cc8295edb83c64abe890725728f5459c01f9d46595013c41fa752db2c6aa6653bdcd0552384ccf118ab95d9d232c06df5ef249132161791026c1500853755faf0fa6caaf099d90526e7859863de08dcca5a5139b1ee568ae9d9e7f0093971d883122b9900b382b612094d05ab9746b332212b5aa40d02003f66694b1a77f52af97420a43f2df22bb29a4111a4912bac8fc8bbc4251de925f96561df87e38875e6b9fe69c1053c7c3019993f1ee0ee11504cc0f19810b0a43ab4fc2b76e71bd1fac498cec8a12509e856d6011e9881304cb23cbae4f84b5aab03de24141a7fea7da0389e61e37497a878addc42b5c8dfff061727bf557c580472b3351ace57e162b1d731f1125c5ffef82e93bf7bf6b7b4de5491f39cf1de74aae13d6e1f1fe30235a49080046aef913925db858efa3012f9244bac90106393713e26cb871b5f27171c1dea719074323faa6a5d900409e28500b0f057a3b576d045196118a634446f23802d29acb8dba93922543e846f905a3e5b1470d30c9d95b808c913a6ae0178d0cf0d8481e2cc478250ca4b44e333e3d6c0dfd7bb8cdbf49be5833198708041afa67e2bab4ab2779fe9c34c4b5f5f3be759a5eb9d9ba8d50671bb81231317858daf82ded9663245d1dfcb4164ce8d738f02ac146cc7d008c1fdb7f54c0f4803a0dbe5f8c3e605f2c547b89fe3339864ed1b2da0bd991ced317c40376c5f2a354e4627881315db417eb20a85c08d1bd5c4c6955273084307a62b9e42af0f55262e54c5981a6e110df34e8e1fa0b9904628adecf83e4fbb2866f491c85f8106aa1030aa96146296543ec21e02945003fba134e46f6e25f393dd0a6612aa102cc58965e9550e48c389c1c04a326470cebedb427e442f8b848df82a8c5d55eed0d1446c6e5d6e0ccdfa2d219019ad3d400fde350d690cf04d083844adefe5069c7066b0ea24854ef3f5853328d7bb2ea735b370eccec5f0f777a467c98f040c423fc4c3df237ee8b1e9cee6c859c860c0297667809ffc0b73af9815b01384834fcb56d3e782ce11ac9124345a3d34fc22ff7e0c3149d66f7bc8feb9743bb54405ea88902011cfd4ed07ee19c6b8e6de6dcb08e2e86427e5e675dff052652af14d7af936f032bbbe508c54fd4dfbd16921e7b1c1471e96f3a521b911e32a5888b5a4083be97a3ab843a1dacc19e60f161fd84c10fe94c9952829da6979a83ff25faea14df6f6d5c876f3103a8a93deb51a3834c9da185355a8b189585f7c71ca082295ba29e326e60b9d08ec5386deca533a03b903541380cdf616135e348c6b09a5cd4a074d7c2e3d794e44a0c4ab8537f3f53b12ad2c2a996c5ec0a1b6d9a25d920a782bbf2120262a71f816a66ffdfedf5e5dbb868d37517607e880d697720a0119077b19df4e9e8709a177bd25508b00c6c79d4b1c1ca7f607aa7b18e6f93e40c8ac9d8705bd9deec9626cd4aa06491416430f1e204177b62d1bd18e9fefa0da4d6e032f785851dcfdd3e2a2058b1811aaa976c30a067d9accbe4504abb7c2a39492889fe518dc529cfb481320dc05a785cdac3fd225936716b149620ce252d079b0f14a947bd7efa17868935b6483516f7653fe108e5c1da91db5a58a3aaf920ecfacdaec9cb997d80860e966fbc4c2cdc52f41fd3dcf8bfd70fc4a28f99128b1b5e0d412c91fd59a9deea1d9b5170e92e1ba12064847d72f9ed9a58874ce906252c6679851b735cdd5f3f17e35e0ec6117e83b9718e85bcfb608428bf03df02e2950a07e80844403f21b8b0e6ce349c5ca2c260aaa780949050d2d9fb90f890e0d33b4a8263b0f3ff72d06ad34072f3567c85a6e301d5b374d9a78a94e5a3523735e3f9c2db0734aa7c7c5787879687b485f1fce35966c742b896aee8f0361d98ff221b58f0746a0bb4ff1443b1f21eb26e1503187f52903a842a278d3cccffe3328876cada9acb5f109f1679265c39ff58a2f5f63162117f63556389d00b7ddb9cfe0346c62344ac0c9f130294d0178425920cf8c5623e09bd387d8f1d1bc4a7df18f8f38f20680ce2734505fccf2d7e13a0fa68ae5f7f949346bce6896c992cff605197740913534fc38ab50d679c92bdd04e6682ffb8581c3de476b13bed9e72cc7807e6ae6a0f0fca5cd9a03086c1a5971991a7e0a074c128727a4fb8e63240df8d4e87ba2077b50ca0c1de5ca6b4a09fd2d16022aad1342b0dfcdf706d13d433db3f592bb467f911fdd09969d1fcb4fbe4ac156a4227dfba8999642bbb7239cc6bc02d4e1cc5ffa47642171b9807bc2e62027befda4cf5d0b994e36b110580f40ae0b8505bac989738219bc3deefb4fb85037482687f8f196ceed78ef556907987d952b042016046c57e36e47e9d5e4c71d5fb7bd2327809e4663d75a402c9b6d89857d2d07375273e3142dbfbd1c4c658fc64e2436ad5d8a714d9a626b8c6a26c3ca5cd8b7bc75162e5ea63a1644e4365ed6b16107b8462e7186586b0b2c43f526f0656c544b70f5e59e809462b1e00853cae8bb57e39c55abe0e2435c53bf68c0dd39e3bcf6da3250f4ad1d47f1e9813b22b109c9f2ca6b06b362f8a0c11774cd90321561ea5633ae94828698a5b87a338a0a1ee58aa9e3759a68ebce835ab5bffab1cb564a704c220e8f451d02ca511d3a61071e59d7a638c6eba218fd186a9abe7144e21dc862d71cc14fbe19898710ec7321917cb26df10994c2d4980eae0c9c512cfaa4068fed52d0c57c9dc3fe75b51f333210839a37ff90e8508bbe215d7fd50cc4c90daa00e10194fb660fb38b9a85be80afd5232edba10ab7c98dd8e5ca4eaa6fb7828b529711682b92e5c980bc877a83b9d7f0b848181110e370bee7ad434463f90c954705032d56c0b86305b83f61642daec190941c8b687f46d70c0c0cb4d627c0af930844189d3df40c1ac2c5e0f19e598bea0d6405cbb41e35fd850f26bec7aae8873c883ba0fe69c268839681796ad11c090e891f6cedb944fb4118092d8b4397dce87fb4755b1ac74217d12597496c41370c4653df24c3df308c2c9605efcaa3e8fb55250eef97ba85bac046d6aa82d249fdd348bae7b1f6e658b8072a2aae3dc22920e419e656810cf8f80398bd4dbabc4df89b4b46328524ca43edc03677156dc05a215d2c504db8b28e876a1797ae0b57de85f634151a8bde6002948ae1f540fe4d5f53186fd979992d4bd95c2fffdaa0d02596e17b439ab392ae0f50e6bec0af1ca9668890801d5dd1c9fa69c06ef9fb04b9b0260e8279bf93f1fb0de27d60b0c0d07329f986aa78e6660a8d0785903e7bd71fa74fa1ad87a952a22ca0c215d035817ef038624b5445a326d84ca96a7486058add15801596906b1d78381b9092f504db0008628d32fbca6471359d445a248c0019ff3b8f6157b7b45c14c76ef673a927ac5e425c0a2cb71cabda5b2f323aa037543ffe1c84c1833ac61c016bbcda50b219bca8b2d210914e2e6aaf5483c943fd4e7c68228b26bf751cb466667a66529cc4c19812a8306a25776f5ceb24a601baca327cbaf470a61955474f3d9ae0aa8fedef83fd62d1d1e843a835c3b993a00f09faeadf015cb28d01b01fe5ca49864b667ba0cf0ceea8439d39cb2a9ce71f7b3a5a9b2774141bfd68baacae775d48ba40cfdeb1d1018f77ec090a44ca5f4672f8a1b6110421edbf5014dac6469e980d3e207f43e25353c1a7d4d010974e145a7cb8a5e05d7d79c7f4eeaacd410dab582adff9b9ef8ffadda3682651dbd8ee69d60464070908bec020c622cc00cee15aed981698ca7d413b83b78fc1a2e6c85bc11787374342b243093d26301476c94e671d7008c7f076dfc7d5ea5d765cc4e82b0fc85ffc8c6637909142f4f3fc943170002ee6e4ffb8a3807970aad74a2aa0faa7a3bdfe4c925757497aee9bd3c1ed174e3aa9ef62d6c8df963d0272545e3a19aca824d7ebb9e22d1818f67e98d5dc3e1b3c45fbbaf8a5e053db3fa77af51d975a22ab70321bfc40af2e3828542f4236adb4755552c9437ce84d39332588d33eae8fe5119eccca99d74962a3c9f09ee67eea15f24e58fdeb5536db348f335c67ddcf19dfe848cb1bf5f2be87f5641032816376f9dd3a983822463898a9ac4fa1c88294747715251561bed2933c08be3c006f05a7b3fce71c179e9fc1bb1de53392ba96d0080179c058c441b9e370714526fe5a01dd1ceb43fdbd75efa6324d799955a4cf9441f50c25e456c6436f95fb183851782552a4e3ba51d2597225b8f2b84dbd01ab4bebb18a49712ea84e86297c4be4e394a3d88646bc8da23d09373a2534fdec05540253bb9435ce0f9c1ee444de9557f880d3bc81ae16dee47085dec4022057b0d3cba15569f542939f58609cdacadf9d5055a9b123be4fdde7985a8a4eee0d37032769527587fa1e09cc7122c8323d1c7ca23a98dd63e970031d8545288d6224c9cc034e60731a9212a89fc6704e67f4ff3638366168abe7c5300fa1dc6b95f56e079cc9b97595750031d8d346244a5619031e924c5eaa8ab82200032737621f4e5684fa19c40de1ac18cc6d416ca42dc59e52b07e1a69f3dca3ce63ba55a4c1a3cc761148f83808c62b840b9252ec3fbb2060b9e7c75f6d94891722056d24771bdc8806d3c5124153b2e07ccbb16b02987c0e0ad70a29b48ddba6a3201ed15f4c055ce2b9e3feb5a7ff082215587c9d891c9c02c2eae1c5efd8625c6535ec57be13465f5d4b5acff1945f0a7a11b0e41546b1e8f3a3ae85fff4ec1c89c2dd44398d04e7689464ce2c631929b4652c9e7645684a0857d6c2cbbe2363496c88a5d05050fc76d488ba7f953959cccb5ce8e062c203df001a542cd5e3ec4fe97eb15619a9149022c9ad71b9de36a3a5d257a0d3ccca684666a703ded560df69a53ba546dbade63093831c306d04bf0ab91ef00f96e22edc0e9745ee374693ea38e7c9a66f766d395be678036af3520d65e817f05c7a9e4b5ee15decfdd2c40e6a75789c112a2cd02da7e7aaa27b57b50626a8f989630b47550d39efbdf17d08da5e066039e2be80c51a0221853d0d840fea4e5d0a818de468b455d1a99b98b69ba89c118ff0bdcb250fbfaef760e3eb3098b92512e0cbf2cd32052281727ce17a1d55824fe953029190dab087eb179c5981c1e785a2400bcacfb5fd63025983d3841b2123e21b529bcae8f9162068db42e1280053dae8de583b3b713f2f78738df67188b7a01f40f0918fa1e51ddf85757ce251e903bb996d87ae4c845a420e6333e0a3c292521190d1318d4840446166d1241f4a837b47179ea554e144991857037225cbcb44e782a9c08d58f166da0e7dddd3b05ff2f7cb5ca53e445a3e6c06a1e3e03c7912619a8418606b4b5a6d8df274ce2aad313d5ac4305024352e9219046a0ec8ff68f6d7ce075490b9bd41fc9940f8a73fadf6577a4742f08d0524f72500d944e950ec1a7b45d2bdd46df4bedae251dbfd53c97a4d3c746de80214c46675522ca77fcbb13500597ad629d5625f9c2ea4c38d1334225b00ec20b9127dc4a15d65f129f4f95668768b51b02341df16c8430c163878ea13ed24b683b993732cc35b987c78e2bd25f1fc6ee4e152bd489baebe997a247076d380c092acecb79c91213a29ab370776cd2c3b3871204cd156e2ce3ff091fd31b65cfb824f14f400f6d9bea7894b8e2744e60b68cc0c05084c024e1cf64867a132f7429c9411fd0c9e225f4828bd9f523bdf3f5a8c21b09dbb8f4770bde3a488928ebdf0ff3d2c300bf6f4eccc94c5b1e4551ef25d144cdf78fbab3fbf37f01160ecaf91b153e6a9322575c082c4eefcfcb41f606eacbc87e9ad7022a0916ca782ba8a2bf4f408d135a1605c85ba1e211a78747ffbd1ba7c36589bb7cd801166efcf55a8e2e9e24bc6cbcfbf1ede191a777fb4a6a67a39bb92498b8d06b287882c859d668a7757716572334aa19f4624fbd7dfb3cd42a34f6f12da60c850a21a2e9998bf4256b961200e57621dff71ff89860ab8f1388b889e6836fd22f120d9f42e08f4999454c0523038e056404d989a3dbee88cb59a57efd7c5bd3827bc85a0bc8c02953ed0d7dcaa108c115a48ebe00c19ac6a0b7daac1b1468d6d8252e93f061bc381523fbea0f248d4de1945c704fd01510d54aceebfe791ff1a44852613dbeddecf2e7fdff1e981cfad3c615f65ec413546c787497f2dadcb2022a6a2aa463bdef07ce1f303b06c8413357261f8d662a71d344737504ba69b8be1ea8252e6838ca8936dcd7041dfd6a646d1cc0b12436f3c89a7feaf8f57fcd34f860017798b1cfb22395d2f2836dd02988ac37178f82e5c543a98b8982e59a0729168c866526ce53173da0b9300e6c17ce85a74032f052a82142b290911a0edb93bade04baa6e061678107143823fbfdbf3c06c0dd0de671ab6567cc3ee3a657e7ecf6d10acfe4ecb89f9698307623643468e0ecb86d9d9b18db9ebd3272d674b43d42784367f2454b3759ca6bfdb30a9001d8b0ec48ed1378d40a33524c3a81de9f331833c8a3e9cee970f651dbfce88b26ed8f6a4240fa2907529e11a44d4b35483310d25e3c006f2d7d51432acc216d43a4de48db275262f925ac1669a7e6e9317fac48f5a8c60620767062e78bc4d93c3fbd5dd47a332512d2fda7447f76a7badc77f862f290ee4b89804280f591a6badc290d3e128c46b681e69d71c3ed2a3b6cc4c530e0479309d6287b692e08529de31d830126afb4a4652a8aeb3303d3547d00b93b661edc1b226d6d79a7761fd1e43264a090a0d8e9cca3454ff74b8257714e299ed3c6f5b6648b9c26bc78ff09121b9a485282e4c5c2b240129ac0819c8532b0b9cca4472c180836f7285d09386581375779cb546d4c8ac6043b3255ab1035c1d6e07647f453130bbe2aaf04e5b7bf49d0cd6474c25a75c67901a23870ae77c32e61af3ffea207f8749100649c3cc054c36691d4127bfc7ad18e1f018ed75f0609001dd83e41d6e2ac66964c248eccb2802c378c9252146c8674ac23b81fc13b0089add0152a1141a6472370df94ae57e54ab1c3a26c55f24a1dd0ccd360e794f0a63987a08d50ffb84a76d3689ac1211ea5d8917dedd6abf81476dd0ac1c7017aee6abc9ee1a41e6f5edd0b89b9d99983857dc32914abc1e20c29c2c2882c21923c947c980e43393500716ba97ec56ce33fd4d01240988636baaf5aca649f87fe606606fecb888b6a7c62dc99688774d648982ce1cbaaa3282467050963b31d95c29f55d427d4166b142f767a695ee5be116bfc9455f405c12a1c3e7dbb14564f94c73dc0a7af0aba2b89c172b2fe5f9c059c0a51fa792481ba1fb6f97faed51fa1e1857eafa818290875f34d876e2a17e5adcc7bebc7b01b941b9934c2e4942e9064d836f976444ebff2dd6c52f44fd9132f4a4a0ab659f052f5b80e6696d217568dea33b7a90302d011384961f7809b0238ed7e26a28492676e4368ee5548a3f5be6b7c3f4342ade03db48e82bb247dc95b52638fc38802881d0c2691f2801d0be3a66700ca06e22070d3987ff4d44db8f952832f0a74e43f77b7874bb68fa67435fffb3a5d98b034ae7766999e8c9563366a70d86110deff01dc7cfae0391be8ebad2fe2d816a06daa82ad16587e43c91dcad74b672541420d12c50f82f737de005ad7235436f404e87ae5b3ac732a92a34bfa3c20f267fd4c7ab07947232044ed716b52a0eee6bbd8388d7e4d15c904a4ac17bc8a7c57032a9e8e8a5c30ceba4a078e335cf0db5a938e3233d9e16a503d741755da53cb2b848c86c25199e1245000f93043ed0b8ca80200a4d1cd64118869789d5e90f2b393c38faf9492f91051c29563e737b8d7c5c704dd199696ecbdf796524a299394015b080f081b083dddfbbb0e92e54cac171d082fc24500c43d3192e533573280171d8acfb0e33fc3cc2ba48f17e79700bc38bdd079718ab17b78f8e26c5abd38b570f0e2740200fef30889f313080d369831481685caef5a062fcea0a9e47b518a91645422baa2362f4ad98e17e5171e456408b2077fcd8b52a900dbc50e7436cd8b9f6366bba86dff1cc9aa4168760028e60680a22e0104c59bad251901c5cc0050d40b004700c51b8d480450cc0a00455dc4a5116dfa2180e2cdce50208062fe0014b50730a84d3f01a078b3f3880050cc4440516f10146f68363a0028660380a22e402b1b6dfa4240f106cf7c4031f780a226c06bb6e9077961261628661e50d41d30316dfa406694857f80621e00286a01f88cfe8ecf70cb0728e60080a2d631c2ad4dbf87d175ba30baa20f0050cc1c80a25e81a2ca69d30fc7bc012866ad0128ea1c5054c1367d1c50bcb92ebaa26f3300c51b15088a2afd81a2a6ff44f90614b3e6018a7a0798b46d40f16657998cb26c0d28dea87480a24ae70045adaa59e300e9cd162d28de6c51dbe055ba1615499d99a40227a67136b3e19bd31a5112b09766d4d42475269426457369ad198cc2742a8f5dc49c9868100d923ad9680924d86ba63edf5c37cb3564a1ecca2d2525a9c314862ce6e4464652c777b7e1177e022761240c0b1386c32d5c05136121ec72fa7c6f4e6540c96257264c8a3109690ae5ebba3f5ca32b044605a9336363665032398620757cdfcf375791ac601a93449c2d933a54db5c446c2fbccc10238707fbf9ae9f6f4d4657f445215949636df91d8d4d8fa40eb67c20b2c73f67b1ba7b752315f46e7bce0eda5fb6a9267b2d49007b326dad71b66912d4a63ff67c516362e747142005d8b283098a40f6fc1f76fec654c294404ca94bd3d6db9f7ef69c2a5a7a983d147776a84a2582ccf6f7917252ea5cabe420875486568656665b9e200fb1b0b267ce13a6e7d3393fb9bb3d6d0e91aef9405461026da74892ec49bd6c31f7b03939e80955ab5beab9d30f7e53eae1299cc4113ba16def4bf92738cb47bf50d65aa5fa705321fa725aedc559dbb8ce4bc9e899ba6bd2e66cd0dcc081812a878e1a9b1d3c6efec3ff9bcba0cbc02b92b327160f2d0e4e475f3467ad73cc3407e3ae38a741e7f3714c74e57405e6a17d24586ca645cec5b99c27c8593e6f61b287522ec63ddd7baf067f2f0777f5f7de7bc3bf37c8fdbff7de7bf3f7b22ef8f7de7bbfbf97e7dafcbdf7de9abfb783cbe3efbdf7eef87b815cd5df7befc5e0effd7175fcbdf7de1c7fef002ecddf7befb5f1f70ae0e2f87befbd37fede9d5be3efbdf76e7faf8ffbf3cabd7706e0de7befd5b95368e6affe7befbd3deebdf75eeeeffd1000f7de8b736feaefbdf73e8de7c1cd206783202c9e0e80fc188000767c0440a7070034c0d10087831b2b1dc20d34c8c1c9c0c6df70cfdd703540efdbb8edb79b8d9bb1e96ab66db3d96ab64df3e0766cdb66b3d56c9b8c6ac360db3ea523874c25c340e6d1641be3138d8d0e870e372e8d8d17a7c376d452b9361db455c964797399ccc7a7f18e3e6ee08ca6b5f6cbe59a1dd49f6f0ee394cdeedd14f7b6e7eddf896526f9d898eca1201c398bd26a85d62b58b687f3de572ad2a702ad5eac55b6f7b548f684ef7d6d39ab6697e76d6086f4a9473c2f5234b677f35e7dbd9e5e605e514ecec3a30ae953953a78915ad1247b3e2467d5714c1ac791e98af37c11d2a73e0179911a01933d35ef3d1d725695c968d191f754c87974bcf7148af4b1403f5ea452b6b703ca59d6356b79ef609cc7e6bd772fa48f3d7ad1c1d81e064bceb2af57d3cbcbeb69741e1c4e481fab248017fd0925d993e3e52c3b8eb011c953bdf7ae83f4b14f3b2f3a0fdbb3f19e9579900f156d2fbff7538cf4b940d3879d63c2c89e1b31675d974be68272cdb6477385f4b94701f0debbafd7547a2dbd9ab647dffb7984f4b94ad387d5797126b1bd1aeffd2472d61dc76934bebc991e2fce29dbbb4fb23000785186b1bdcf80f4c140f3bddf80f36cefbd0ab207bb64ae984882b3b0cbfb22d2071f4d1ff6a5922702d9de6b04e7d1defb10644f7def43e749bd062f6eb0bd773c7ae08b747b9f237db0128eec71161ee9cabee7fd943ef869fab0ef79af81e78197b4ab8bb2b8eaca711d4d1fce0acf1165715cd5ae97d2f4e15e1d286dbbc964a36c94d5a7e9c30101a94fdb7e9551d6368e553656d9586540d3677bfa01e4a2acedf572bd5c2fd7d1f4d9940670f4a2accdf57abd94a6cf762400a56d35996c948db2a7e9b301edd8a76ddfca284b1b472b1bad6cb432a0e9a33df900725196f67ab95eae97eb68fa684a01387a5196e67abd5e4ad3473bd2511a292bcb64a36c948ddbfe7d9a3e1a508ffbb4ed5f1965e571bcb2f1cac62b039a3ef90900402ecacaaf97ebe57ab98ea64f56e2e0e84559d9e57abdb2ebb5ed63a5e9938fa6064a948573d65886c76d1fe7e39e9ea64f069a3e641508c8b7889ff653d9531995ad40fb3494d98083715634c801713200410ec659d9f6736e523c38ee75f3dc6bdbc7f9bc97714037321e321907b4ed6750d3e9189f6a461de3f8b4ed833b389beda554b3bd746c2fa56dffc360c3b1b9b6230c36178ecde56daeed68dbff1c9a4ab6016120c321f3641bd0b67f6323e7f1c9c6e88d797cdaf679dcc034da4ba96ba954faa5b4edefb8976aae23cda5b934d7d1b66f53c3ce649906d4b564b22cd380b6fd1a239dc7a73c8ee3530d9ed68b872fa5acc197d2b6cf693a2a9d4cbeb3ebc8b52d78b4eda738a11c3e95641968fa6c2328aa545a63d9ce40db2ae98f7b926de3f4e1be481ff66d4bc5c5b4903a761bb77d95f7816f1930a78aae9b6cbb93f3881369c79c47ecfcf3ef590478fb53698e7329ecaa8baeecbb5c1f3817fbb8274ee6a22c0e8cafec87d996737151dbbab898d4b13ba78a1637d9b6bfc99c07cc06c5c168ce5a4b0ec90d50b995742e666dd6aae7643e2ac3e2dc323f559a0c78019c1054903deeeeeebec3f3c0531f0a1e62145cd91c6c990505aecde974f373ceba757fcf885377b73216dbf5f3a79d6ea79b1e50fb1944e1c62b32c116e6b6e005a48cba165fb88eb65402e8aa3fc5acd0b10279c57568e31ab4f15331d2474a1fdbe3a761644ff7acc9346663c7130a8cea098bb429f519ff6cd2a09851e0e1d2fbe0c88f15ea7419e5219612fcd1cd8fee681af7daa6713bb6d7669ba79b1fcd5aab7e0cf7dfdc600acae7e119a9ef3b3c112a131393b7349d4c934903b9209d134dcc06e3c73b4c745535d734cdb70f3450771ee6fc6108c4cbc60fa429c706cd01e92efe1adffcaa5d8105bd2b2075f05340fac05545592af80abf93e8318fd9e0094e210cbc22149c4473c83d83f7834c1fb9e3e1bcdbf4a9efb258de728c93e1f08a5cb07a78856cb3baedfa2777cd5da76d910034dcf950f594898eae83c81eb977360d695efc1b3830f091db878ebec2ab1b9a46e5ab95d672784634217494d19ebe9812e9c8b4f1774f71784552600b73cb8062eabbd75ee519d1de035b981ef3108b1e63fad1ea400decb4ea7d0b75e74722b3f3dfe764073e7d8e7e3a609a82f0f45e58464270b8f3a1ca5b9476ef19a1413ce4412226991327041584ae38cd30464a2e0b8235d1592daae17970f347e319c9e01c0245ff4964348de88bf7690b48c82b72535c900e7ff8af52693de60ca1ef4feeb989bf7b7102b16b7846f2fbcf18394f77f49af144c8294febf09b4393a8a3e338e6ef5e14f2c022fe4da26f0ee550c18f82d4c1eeae4e88b350a02bac04b524674d58ca1fc0182d4e214b97a40e7efad151882f31fc9f23c7cc0c550167d7b450b73f1299edf9b387ce231ae1c04ef4d745b6f7231c38893238877c0a4d21222e7bceb30905ffa43c6a26a78c02a5ed15f1dec116e607f953ef9e7f4e7d2ccced81297846dd0764fbc40eb60bf18cbc4f9c401c89418c266c638c9fc39db79c07c85d1a7822e4f7bf224773d65a2ce4be14e877812d878aa64b7404e188068f70e0053cc4cfc2dc1b98c28f17f0a3e03cdb63cca47ddcf6e579c5c6184856d24baa9f4c94557de5df650d631ce2e778ecd3af7e8d4f888c64eefc3b94e54fbec20f66e3a8240fb33106daf88336fe1f4fb4e87f57a8fee4f8bd22146c61ee0c6ab0fb68cd15c6390b37701247ac42bbfefd797fbed350d09eef5836fd1c22cd45a9e09df781ddf9a5f529377c737cd022654d222faa26bd9484ecc9811e217dbce88acf5c70e127e8338a44dff31630ffda22f3535e91f917949bc23aafc8b49bca7bc11e1e52b08e30412959142657f591b4a45de98823750059813ee2ffbb3ae82fe93fc41dcc41e8191235729ea35a93f66d39cc75643ed2f695fbfaee84ece9be7e37df03c5e9b2bd790b98bf7945e63b9483f1996c86690c6afa741a8bf5a0459fedd8b3524f531f7df21585499a7a5f39d59aaae87f28f34b1ff19887f55998dbbd7828ba93c3809c4774a75dab4bf6c8fa4960c6a830bbfe0f18b4e84eeee43c1e73e9d291a8bb23d125aa44b160f19c4daf7858fdb9499f3cac150884e915c8c34a6352a7be10fa087d16a62355094495c89eedb9dfb6bf9fc881e274d9e6b0e8ef3207e361fd9c1e747d975587c252a9087675905e79ba3f63333663dda657a897c7b111298cc2fcc859da8d5ce25abffead4857a44ea558bc68fad023a48ffa957af926d592a64a49489dfaa2e7b0eb733f5c5aa43198f388f4efd72fb2c78f50ff90c8587fa7a1a23ddf7d3ea5e01cba9da8556eae503757bd4e596d8a7a719e1e3416be4ad39f4f9f648fa493562ed5f99e9e8775856a77f6d01271a0ed3b149c3c48ee0fd164687c04c9058f50f07ead427f86eab306ab874c3879438b9656cb3af3429cc77e0c170ce11b2104e7a9b1e007b66efb41926d2b0d61841c9f5c51f76ad4ae4f6badb2d65a6b7561df2c575cd9a21ce5df0fc179ee8fa0a239abaefc6ab0fe21839e2d0fa5644a638271cf33342e4a29e5e74c8f3e2769473f107ce30fa2895cad0e4a991227845215826c0d04d96208fb88fc4e0c617bd769da431b5fe34f903df9ad9e2f93f26eec39ffbe8d1a9fd42627f3a53e51a5f70557989f0a415436dc092dd0b267befdd944f638cf7dfb53c8ce2b2205c50f6c79c54e20e731c1431baa54f2e7bb6690ec9992fbb7765224e9c3bea863b59c55f3d321849e20d878686d9289f4f22d9d394fed0312a5f3d39f4f73e6e867bf15ac7d77495fcd7befbdf7de9ad4791ff8bef7de7b6ff579e9bda9fb61ce5a7b185e9bc569698ba1d427482badb4f2f39772b6b4f33895f1192dd55a6babadb5d61c1974adb5d65aadad44b22db35811dbf6a5d7df62be9fdcd509cb96599cae6cf7a6b8ed59634f1d53f64c4daeecdb6cc73c45db97dbbf8ad98413ee0ff10bde90e9743138b9e5146999c50a2b5b66b1e26873dea9bce8a6a524d1a7a391d9bc907c2041640f1a6364687fe44abcb1e9933da93742d40ccac3a54d43038af5b52bd0ae11db63173127262c5b6b50a4443bca630a4316734aa540d199f6eb8924249895dd75a0e8aeed5406942cf6b461528c4948dbc6e1d89819942c67242b98c6a42b9bb625dad93b5b4be36850dba62f725bbe4f9e3ee8ac51482964382b1572456b8c8c8a85d64d4f0149f6bd56a98e7bd22dbd9a4445b28238e0c5da43035eac4294f0623d8204aff1fed40cc942c1c88bf50506bc589b2ce0c51ac3086ea30ac99a2e115ea42950c08b748b222f521684e0ba08c99a30105ea422f8e0456a020f5ea45912f02285512892359d10f0229d81c88b94ca7e91f630c4535e4896b70ef0a28b618017dd8c02bc485f1022e38464f9cbe745d7d2f3a2574180173d05415ef42725193209c7599208be3a68d0f225b883657b9c33d775dc10cfa2de19c43f846e1e601ad504a7f6947e62b735aa499f6e339d765ad5d1e049aba6b1d91857156caad56854a1a6654da3d99a06567b6f9d9fa837e6b4ac69793e0627c6396bda764268a9bdd652fa73ce3927a53f2db5d75a6bafb5d7da69a7b5d65ad05f033bfb48926ed68871d2d68a55e8c40e23a163bad79b820e4ed061462bd44de3e10d2d7a6aa973bf86ff2a95be5ef40929489d8b8287f77384d0a2def763f7fe910d50d6101530207d24307ddcbf7f5fcf21b13dafd8f367128b9b4ff82183963d74df9f462fd0dfee6f1c0625dd91308d9fa6e0f7526b41fc9c6704532a653b9b42e5e94634d28b1bdb8dcfc627e5782754eeb49c2ad5dc4013385054a924b881db089a90672c386b8cb2868c4e20fd46e0e40f710a66106824db390f96c9f9358953923d9e13b22725f3a91428cefaf2b8fa929d95ef7747cd987bb31b38db665af5219ded189d51564542ca39d248cee37bfe48c791ce2315e6e1b45291ea8bce26fe5918a82807b3e7ff8041d797f36cf4079a1fe22c1af071e813f5329b3c9c4b37b468fa1cfd56a8938629da602df270cea4ce3c729f85b96945aa583aea3cb282a92c903d5eeabdedbd979f98023d707b2a6e4f917eeefb5cf7fdc61487c1b2ed3737fe56f02d526a86ec997b3e3d63b26a8caee6cb6098d5c99e55863d5bf8452057f34db0e767d9136a4f2e629dedf99c5791a4cefc9c299a22c5f6fcf91628569f8a64aeb9fde63c582e6da24e1e4eca654fba14c659f3e9cc79f03f8eca662676f3e992cfa7239d383634b469be3365cf033ac58abf89e59b41334916c6de2966e447328ac8cac67fb7ed3e12996d04ff055b98437cfb8ed0df6610c8c29c51b2a788b3985a8ae9a455fe10a775d3bf20dd860ce930eec08b69e8af02de2dd01e73ccaef4936e7ff6f0d07bbafdfc193567d307c8f4d1e56c247828e5061bbf9633b246eee654fd9c01458d6303c59f3efa3529545f8e5c6dafa596bd3d069f137b7b1c05d8dbdfe8607b379201c59c02b78de6dbecdbf87e6c3ccd097b1349d81b68e3e5a64988d8b06103c4913a1b48f372d31f9a772109119ab79686e6b7ef7aa0b5294d0992880d319c2158c1eefb8979cb9ff4ab88b3e55b210090b187b0fce9729e2b7fb65c30611b412a09d9b549ea95c8a46472a8294b6b194957a9d4cb4c29864bcf820553f5c019811f28394ad1b4ebcb3ae794b5f370669a5b3fda39f6c22581edfa72f7d8f291d81739b03d10891cb37fb0f162fdfca1f31cf13ef450a652a9542a053ee8396947322b985f4ad943aca037f39904afbef74368fdf29e013d1089f6168362b69e0732bf713232325aa60192c8e55effc63da765b4d6325afb8ef33eb1f380842593c88dbff1338f648ed95ab6f748e698a5ed3da62fa33f2d23da2da3c1f7d0f33c24f6c5d4b740f70f1dc0f172d71a8fe3fb99791792903003fe2469251909b9806078a145e4c7850f8c59420561948186983144665e49122233a0129abf910291d8f7b81f66de7e343640570e22336093999f81ed0cce7c3333df05e8ca312801a9e3afe399408b322684fd99b742ea27765063066ca222a264e66b804d32112599aee42b513203bab055d1e1081b6658220a23221f904e2f314a9a9eaa4843082292aee42ba9f14a667e03945503d47425df41155dc997719926ae8a2a3bdd25655df79a4384ae4fc2f431926082ac596bf7604a1d9f2931498d8273ed39b779f9dccd6bda8df69b767301674d28bac23f299eb3e6757c8e573d063c387edea0791b357ece6821431210c20824384f9680b3fee7a7e03c746aee19d1344d48065050380370c240700a7da0e6f1353cbea6be6351b4555b66c1026873b5dbf1dd3cfdb4ffeffbfafdf0f6ef979ffbee771ff7ded77dead3fa6bd4781a9ac7f1dd507d187c8e4ff53abe1cef9f8eaff9fc6d3eb96f7e884f580db6d9f1e1afde0778dffc64a2fff8e70ed0a6061473ed6abe9b9f3b87082d4ea809f5a263d9f8455762e3175d48083fb7e39b9b47cddb7cf58cf0f89aa73cbee639ef7688c0e36b7e88efa8f95a987bc7cfa8300e14f443fcc6774dfd0bc8953f940cc5d9b2e1f1edf8b2873b3ebb6d3e0fb4afc1afe39b613cc49fe3f39687f8559f0379881f83cf5d9f077988ffc6e79fdbf89cc88b3ed79fcb7cfec2ef7d0ef32b8e743fc792e44a1e8efff9927b53f61975721eed77fdaeee5aed27b5cd1e11623577affed683ec5aa767a4babbdb26772ffee4b328143c6d99a5cb95dde5685b6b6f68d1d803c1e737a3b0bfcf3c0cc63b34e7bc71536e5cbf9a9c358c71ee7276d0339d87136a688a21595a9ab131c638632fc8e59e833b7586fb09e53cf9797c267bccf846b0a0c433aa89640322d84c318de5f01d408420a58a27050900e931e29e160eb0852440f2e0dfbc6eeff0fb9187dc73dcbddeb4b7f7b6efb64f4db1b2e4bd8cf6a7d3fbd4adb53a6ded3ded53e3ce4fff62b132b494df6508b33508abe831d163a2c75a942ed5d73b897414a2ae1e3836236da21e1803c18c93e331ee7307e278453658d5caa55efcce73ca4459381ed3590d6d882a1ca03f81220d0a6f809906f428edab0ef41507e617f3c679de7beab74d6fdb96da5e664b6daa6ddb3ec8b66ddbb66ddb96e319d9ba6ddb64b6ef3aaf03c189e319d9c0143cc45ffe21ce851dfdb66f6e0dcc86e99c4c9db75c4dbed7a14a148baf9cbe90ee47a9481ff8c8431c16491dfcda4789fcc96555360633141415a39449f6d0ad514da323dd9af653fad071fac0afbd967a24323bf5be395013b2a54880edfd8e8cf7da93f1bcd17b21cec3bde7b3bd2780f43e88ece9def33ccf7bcff3641ef48c78329ee7698fcd3c967951bfcc8d5764060cad56eb094d4113aebce5091213bef6de7b2f045c509a26fbb95694ac3c011440464b3c26d65a6b9178f295041365021261804c482208ef689a1a3f4b96905ba8c0b0d87105587111490912ca4c7ea680090a98c8518a1f7cedbdf7de8b63df7befd52266b6efbd59b4e82c29a8416ef1824836114608cacb154ecc96f0e0f262975c5faacca62934686826f304370dc99e5cb9f7de19165f49a5a526ed01332f610d1dc0884571c117504d88dc1df67dfd65df961833049530c43873d7f5a2a8cac507345cd5015c185da24a45174552b80294305151f282285230a94c38456152b493f1c516638ccf10a22b99f3cf0d32dae414443023308808dab6018c600c125f9c44b0022074706d3238e9321be221bee00756748318980f485ca1a105dc1623c238d9a10902a8254304c929c080c26310e957d1146ce402261915d43003bef6de7baf962186f6bdf706cd24b0d8333a202bc5ce40492042a889097624c3740415453a218b714eb0b04e8860ad566b0a26da320a588bc849511dfaf071feef903bd18c4411bb4e6890c517638c3186a2096a639cc31014add2345adf5087cea801430e5880d1058a9f31c65011c20816dc07ec300baaa207275c15474f3efc4802821611484cbc10b1c5cf84c2618c1fba128ee0a18624ae1841144ae6932e320419618c711090af649091a021adea7e5250c49210c8926a80a24395a11b4034f9f071fea160cce42e859f31c61896e42b393239d905c062a110205c3c18e1258b2b58680f5d8af0ba94c0a5559a46eba5314362965c308c31660213b431eea1cb82461951b41f262e6ac03180382f78428b2d404c61e50b26921859c0822c323198145d2c90c08c20ca920ce30e328da32516482d58985c18635ce5c3c7f9b7415aba5cb10d6d0da0220811784d00e921a78d68cc9596eb0b580e38074b74022a122a87555e2b4843a9e80730309528f785289632e00a59138ca5d8bb64ee501c7c1e91341fe7c3c7f9af42450bed89b5d63e31f2957c6598465225fd183ee009d34e102fb90aa127202a85dd42d3b8269a2685144d4dbeb811205e55a986a46ee9873a8009d7bdf732f1d68e945454996d8cb355420b0d8c1d722ca850c2894b8a6108638c9d6839c00917187ff2f4848a1374618124c61833f9f0713ea84b0a0aa32219c688fa82a50c5c44654c92a6699af653e42b7964e50a16a5127e9670184230f1e104569c0cc1840c430a1745d65abb84155fc92b198b56522dbd91259ac29d2ad689fe9a74efbd5a64f0b2ef3dc2044a206901c58c9f228c83982c9a946c0892df5a6bc388f94acaa08a84316bb98a4009e12064c87eb44ad3688d6fd0966d015ddc34848238e8d1c245d40ba2a2c002862e36fc28a1699aa66521e42b4964f482212519c9624ceac249ebe2cb931fe20e5cc64c7162cc099e98627471e24a54f5408c28ad20664c5c60a96734a518d074efbdef23048d9a664ad012941d6ed0678c3176f94a0a11d9225b18fd1845c8b0a488a84b1337bc649210d22a4da3f505bf20995165a8082943d8862dc61a59b640810e5f77982e8b2f166ab55a31608c31aeb131c618bbb6dc2283d0c6d8b593c060a225017df83817e3ba25cb0d395ca37ad444ca3d62ce643269c8702706891d8b2fc618638c316e32c2cac6d80a4d0ba4705ca0c829622fbc6028081fc4bc8892b840c92243030e5aa4f03065054a44f105d7d02445152f0a0d5cb23c63a4186717bcc08698983fc6788c2669204dbc61fb846b63998d31187a581b20c62593f029484c1c146f26c5698b202fb27851a362c0d7de7bef55c0102d4dd3d2c3089a3811c2e8c7059806241f2a52102e269911cc7849914c235dc96cbb9c819dcc2636bc144394b404132a4ba4ac00cb11627cc1642597a525268c317e828bf6908d1248100514315aa0d81002a3218c5a60b92c1947284a10dba161009391d5b20570400206298a20818bc98aa19264c91353d3981fbe98155130b6502e387cf838ff4d59c420858849c489549c1882660b4b2d529c5ab70a30568c2a3b425b6a88a2822f4870c49319943c29f1a24a9221588b0a0e60ca408a61280bce0b4d0372460f49d0e0c44a165e10c153367e5db497e8a00417b2041e8ceebd178acc86dc321446b68778b652f45333a8035e18b5ec4d4fb49c8a1f2daec75c98e84ae6265aa569b4963286a6699a56e42b7964e50a16a5a5052cd19414dbf7feb8039a8062b2538788f94a0e21cb4340e921662a225a4f842bf42efb4ef184114245f98bcc842f505a0b0b70d0863e7c9c7f210fdf7bef76b7eb5234dd1054a150b282a12a6ff1c518638ca950a2b531c63570d1fd6895a6d11a1301652230553af8c08f1645428c71c4c513938c222b5eb884a317141c143f0049e1d2c36c630c055f7b535130ba925bbb4b2d8021480a1aed892c964cd932cbe2880722a2789112810a2334ae931ff287f7091a829aa2e81283c18910d5034baf2d34887105891db00c398c51daa116162be2e1c3c7f9bf181812a30727c0b852051522b7a6b12f0e44eec5185b0d4fa182102c696202511469e143154fb4cc10021aa6e4f0650fedc841e34806335e512ef8c20823820033b524b19480a21ec000d3da320b8356d1524516a9a0d1e28b855aad56138c31c63936c618e32d474c1be36cc7259a949d291cb64f348d8a29464f90f8ac598c313ec3e52b299489f403ce3052bd6be609eed2e2a29950303ea93c542e4442331d232ccab862044c284144858b0e2e4e8a1aa880e2046bb55a4fae6482b2e2c485c9101eea92a4699a06f395444a1a999c620f1843e62bb9c52049617297178a6820818c66354dd3b4fb9ab7354dd3b42366be922d971091d16d00122f8ac3fdd162888240f518638c957c2597624dbe9256ab2110415aa569b4c68106dc84039525fa6b0c3a1830851394fd7b31de72451a1bdf8dc128c4c4ec96518a14a37ad444886dcb134948f91d7c826af1c594a9d56a51c118638c319eb22d9bc288da188b2562d8b62d4cd0821ae38b26a204e1189ad208639bc64499819e41b110143f685439da4952e4840f3f28e170839527a6174c70d102a858abd58a6225153324410521021fa298587c31c61863df18638cb36c1ac18f56691a4d0375103e4041b1a40b204d9f08f239676cadcdf9adb5d6da28a85c42d4ace51222ca259061a47d9a267fce5a105cd62a1a560933d8943a672e64bc76cef96d9ca7dbf91f27eb336625a0d17a2c72b6399909d56ab5a4dc2bb534d1b4ef6c37b9768d2c6440fd7815a3195e4489bd60c51437c81dc408e386fb2f7b780e60c6921b6078418b491573563486071b3235b2e4a22b99996427185ba9513c11e48442b9f77eb7afdf7b6b2d4307269e68594195344a40e46229fb76a1b20315306b5dc539bfdbb8fe9481914559ed7b1cbff2c5108d0e3f186f50687124b3c86634bc38bf7d7db981bf2ffe8ca1f8e2858d9f461546c9c66fe3aca0c5a864cc184a7b9aa1a90100000000c3160000380c0a064442599ea6499cf5f614800e6596526c50369d86b224485214c510a3000000000000208800829073b4a3001789589a955a9b67b75d52f2f90cb9b4157bcdd2c16ce3e00a990a31292226c739bbbd9835c20adcc6afa81012339a8037382fdd96c7f08b065c974661e27fbe4b6b2600cee951ed6d1c1c96205aba8556c83643ff8511e10c604d7a368661f77c56de01b4617dc87331da1eb9a09829d58134a28a237f74e4ca32aae8855cf3505a7cdce072f6b98838a5d6a375f2bcd63d2c995f60756cdabdf3d30f62ae806482f2e47fd2d0745d633cb442a43114e0ec4bcc81e0860d4f474b978ff8c71f480d3364e88b3bd2e3bab5e32faf69da18b2cec5c44bcde9d49c5e95099b68a5a701e4e6baa1272bbd14fd1e7692dd4b3bc99b8fba5a9c3c8ef5ec7a01abc045be1013800ec5369816f763a72e882d5cfc32bef807ff526d70d39601b99b926465357582a9a4acccd80c1d7c0992d1a68bfeae692239d12300edae46f6ab7ca0bb8d1ee94cc9925dc131abc92cd9dec37e20944b2766e17a123882e69f99e7e34aec1c67275835854084012c2b051d2f3360ef867c98be957e8668364aea0a8a4054494ac64b56fa0f73e6a633ffda7d13a6344c142b028d257aab535d8cb8d4842da4457587a87159ef481344ed96963c519c67e13addafd15397b633a8a08a34b8f0ee2193806ba69aa9f7974550bfd34c867623b14251abaf7165845690ee8d4aafecc1a9c3d4cd2b5efef7c82a8e9adb37d6935613b7d97c1ec4c8d7f0a486aca200fb12e3a03bdf243d0cdf6b57e274dfbed096f8dcbef9150e58292f0d7dbc28fd5854239db3400c10d241b4e16e47881bf28b7815201a36e38c706f89ee09557e7df2edca497860a1da903fe008c478d59a620c59a6ba01f05cdb7e18c0433643bd3feed737260631c9e07d006b5bf63808adbc7b45b7ef99aa0e5083a693e85183120e430db11e3e40907ea4618c467097b8074336ebf41d69d1874b8927312a54df0b10ad126090f2f62175a70ecad31d6ac2c30535e3e16a8ddc8948978012eaa875417b41318f703dd749607b67573e026a9e06665b282c4fcf8e299fcdc5c45993af3108d9f7046db141719d84240fcfdd6f09f3a1a9efe7603bb0744354e9c20019d4e9f5e7bbd596b9fc885e9e5e36f9fc8a9ab77be6c6a20d05a692df0b1253154b6dd6d245696b2afc72accb89cbd07d6ff1f4797a35be9f69ef5debc61b848a4c37341738aa729631219d35f0b839edb3b0d2e07523ea99bcfbd208cffd0ea2b9bbc9596451aa0dfebd42dc888495b4cf1db9a9ad6a0e38963ac28bc74e88861bd68a7ff8fdefaa4de013b5af3d5f2a02ba7e43255b2022065fa05515dbfce106f54db4f28bb7552e39881a4cd65865cf8fb0c5a1b38338633248e159842ba32e12268214f8a898b87fe3865ee5ce60ae80503f560080caf3abfc94ba346cc56d73435b2a66abcc7a44d1a1cead361b9838097d26b0ae0d8c8740f7ee85655c34356d631faccad21663422484ed4a4f5fde07582a2273f61178228dcaaca68055963f8e756ca37296d4b64cd06f6f05d172a7ed8de872a3081d11de94aa91798c980f1db1d6cada53246d0cfb194a6f62a0241bc0ed903f193288c7ca587c734b5be99d57619649cbe0f4c3333ece7b34b58c9b4e27f03f5b974ab95e9c89218d10b1ec4939bd4884953396d851d368ddfb7f13f340bf227dfd1741bb8fe5af4f3ff34e5bc49884ab6eabc5254af84399184029c3e5f463426b2c7c695e5542f3876a1597d79e0ffdff15a0a423cfd07b6eaa2756eb4bff2e7696b6fcd01092b9f5ecfa9e43cfed87ea1b0cacaf0b63910f719b5b5424e60ef026281698ffadf90c95bd336d7509676ee0250887152e543fdf4d0ce9b2835c2d8cd087e5b6e6f6c4aec81e1da1d356a58ede46d3f906226a0e856db6ae22c73f686e3f4bef09d476619aa291edf112a6db9eee92f22dd96cbfd36e35c315f161f0b46d324b0f879bcf98adfd8eafdeedccf22c164249ad8ac917983e181f9f3915ee441bfb6ad680c9b45468589bec364fc56c0eba51a864602bb0cc7161784c4224ae9089b7914eac4ac63d78e58190f1d462b80638e0bda7f4b5021af785c2d7805526776e6f60c979ce3d52ab4432dd815e35e4a0c4f94a25c16741d6911b6896fa3168042e74e8193560fbee3803a5716ef90d40c94fa423e994f9704d4563e9c484af9c5410bd89c4d94e3b1fb3f4e0493d562533e9b8a33f7032a42b646c56afb7b85d5e98267d4435a07e53b1f84a77321bca95925b94017437c005b39a486aa3415ab3194553288c478e017678604c213751d05da19359bc4f7ace368bbda50b60392e5b505e63b35aa39bd0d7fb811f2ee6a0c232cabdff2f178969e5c7fb3bcc63d0a5266cc2af47b925b1fb997088f34e21bfdd7ad8fbe7c0d518a52d68a54158b8e57d8d853c9d7b41062a71f60a10af8796db6cf4f0a7be6c8f36d4c199cdead3c01f747b2f2b830947dd087a8940e63b820f4ce65f5fc8b2e2d0bee4bdf30ab550cee9425a937136018405d5ff245d7ad8c077f4018cddfffab03b49e78750b570a49f50eafc38f9a12d526bcc88b46059a4c234ad28fdc13291b2e6cc106f6b4f5aaab4a50201a6842fb2bb6ded57dd6ee06b5567a8dcec04917816a71c1be608e44bc836ebe3958ce016c3f9857777952765a778f18874585ff4b64e6cd430e319939db0887f30c11ff5528158b08e1792ea4cca196046a792fddd925973b6012e7c3441355007fdabc21890579fc706e18737901ff960b62c22d05b65ccaba1d3d969ead8a4af0cb4bf12d6815562c66d6f3e13f2c54fb4b6ed0eb74c3e5a099abf3f3833b79d176563c9530bb040f2cc04db2c351e6f998d0e69b4396c79639d498a5b2dd351e306d8dc62280b9547fb6615e3cd399f5be9f2f5b9f7dacaa9699a7068a0361a48466a4c4cb17476da2f5f47ea44a089e4849c94e3d0d3597d7afce5193179f4ebbbb45296fb3e1e059b747c7340866b8e310673e1d5fb4ba63b2911115dbfdc595574b2fe99dd5e26680acdceb8e799a95c836ab0d95ad4b2b908c03427d96985045c49a4cab93d0f609765b4210d4aac2933e935c8d6fdefbff8a8f753c03b457ea60173cf748bafc448e1b4ebc7fd64d839dd7e7d5143dd05c1b32ee9e004b6c6aa99d00dab977667a1bbfb1ea856257b7c49ebf00824f1aaaabdf5c31433ff30f503f9635521c3bb9f506bac5431512b0520d378e0aaf4d90fd8022f72d806b13fb283189e59191a3e6c091c75543cd6cc87466b42842c45c1c1e8d0e71e93536a3fdb4d1b8b998dba6df603ce243cf117eeb6817d587d830e262b6721cd541856c8a0347fd4edc940436c308b33634fb4e99ac15d2d5f6931fd3c1c26026f0389838850355c373e04e41188ef2f9f49cbb6951ebca41ed52f8cccb902750158c937424795facc0867281b3268eea29f3f3addad0ebbc8840512141a9eeead73452003ab6759f4d68225221076ec90ec1668c69650907f7fd8ee3af136ebf9e2b6681b7dc42191c1b33dc6e669eaff2a3ed0897a3808ae34541d35f771594ed5b8ee6fb618a21cd6c3ea4006d6783b9f9a58fe76802bc45df94f7f5b16767e193a3a9af43cd6f0968d75e8a554bc605cb00399ff6365158bfead1b489b280ebcd5ed20869684e1de3128354aea070461f9c7c2c9634ce70001e97bce1f355eaf9948ac0330701751cbe76efb18859098b1b79ed39d974a016a58dde34f0e0c39ddf12b8ecbf8d7b40d7697e1a5424b94d7f062f46e184889e7d4be6b0fcf064bceb37c45909a679d3826a0cb8936ac8b26a0e45bfb0a4d22689f3ba237fdc72e41bfa32b6346ee399f6e9bc1350b6a9ab827f80fd3cb6f2004a13f6e02d567ced5c13344306bbdc43686c24447436d390603ebdac40a0a44c8f375e1f30a5d02be287f97b02fddc2aaf64b879c398ed31eac6f900ba8ab2528816cc480fb4799602d3fdd8caad6684898ecd63211cbee13733b51ce5fa9c21b97bcbf4ab59b7497cc855e666d904deab169136a81c1b34a455218a8a74388eaa106eb05050d23cd599c0b1b91e4e39464527a120f8fe91a612f693794c90321131a6bd42e4f0c08955e7f4d9a2db9112b83faa8317cd39fac2cb94910b282f74cb23dce4143925841abad3ca5f1dfc098e2165c3b5abd46d07339e8b1737cd4f7ab84d3250058e04982db947a6231d2ba7698b342020aac7fae284ddbffd43d9e816a87a3de6dcd0aff6ce0db0546392b5fa8b0e9542b6e5fd3c944ef5eb794f6aaa5a3589b13d059de1d2c71b6ac6cf0c0c5d72449d795f81883f62dd32c97bd8ee704a7d4367a014b589bed95bab6110b91a8cf39d23635d994c7c8780437ffbd4de259f678358f3e0e2738adeb753e3396dafd4ae5a3b16f2f30894027dff081cbe640300bf93240ec925b0bf86a6efac326311762adb8167f316cc358293a39821bfe952bbd81b4b6c2dbb51619cd0d6106261c7323a41ca0dd5667c1d9c7f535caab2da27f06787de384f4ecfba96b6fdf15f4ab0e08eac7fa3f6cc713b323c7361bac2e96280bec301cea58518c880e4db1cb61e9ab1488abf04ca7c0920c8aa3bd7984ce1587e6660f2b0dfa42bb0c5fb48eb78037d22bf0aece3dbd3f15c8e073605e04e427f4de09dea961ea93b2a98966c2607436d0a554619bc09bd9de262669ced06e1a2b0cc061ee782dd2e0f851c74df6467246127020c73703a1ea980fef963cbb9048867fd464892f49cec9a1f5ff783117af870030c73c4c37f5d99daae862c3cfa322734c74e760c8ee0c65b4ea7fa5e74934166754317c63ea0746c6d434b585eebf15dc4697ed53f178dbdc65a672853f4a6ac12e7745b94e53429f3bfae444d989a450304f4a46ebc9c491fe7c812dd6c941342b3196dc54fdcc9385e2939da9fd842a90cdd0fbd9348eece2503f9592ab468a8f864f6d41ef735e0380ac00e84e11d9ffc7ccbb16b35a83b6cf030a042873c97dea4a22394a1ea657ccff9d20b2515e133c75741df6091256d2c206b8510ac14ede483a066513e26213128eb84d4a4ca4d9442134063bdfeb22e64e774ed8f77b9be1d70e9bf37454037eeb53ad91c6bc1df58342348ce0ded737d76df5a3976edf231b2df75e38dd26e397f2634bd9acec8cad7dfdb84fe4ba971430111f13619fcc9344e55fcd7619dcfb4b427a1da24e10cab86da1785560eb19d8fee95ea9766f3e71c0b87c4b20ecc1f8e572973cb560d5d3920b951670218f17694b82b30506aa73890abcc8b7300384bf6b258932d1f9205c1494e44d981b7922db02a1742397cd3d8f66379bca28a990a2f9e387fb25e16daebe96fed5a79e729851c9eec0ddb79f4b673b6a9f4ad2239a17ffb39897185b4c266d39778f7bfd4d4ca694d5c2c3b4fc2dad44465aea4297525e633446c6a57e5f781983ee1021776ae690926950c4d6095ab7620b78ebc894d4f8a1339f109a79956ea64caa05acfddfd52c5b2af1488ecdcb0b9982297219c97846e9c14f7c6b2d561059da05febffc313935ab09b6e3d3e6fa69bfd06cb36ca4add2df7e66cd8d5bcb0a6f1801d39b5eae9b1bb218558193518f763a38c698a702df231f8ead8bc9d6ac55cb495488b18386c6ae39b58433ab9114de31ff5d1f4b7a727aa9979e19c75de1cd68f2e01e3e4f3f3fcc104e5361fb654c0c2c0aae4c202f308f370da17e580750c075e1c2cbcaf86101a7eeb99380aca3a15c0465b9375b20b912bf46c4ce9dc691f34cb057d28f458be7b3b25956b8b0669d234d2497ed1122ec3cb84aca600a4111ddd0d13073647b901ffa5becb4a8e354435a900668c127176ca3d40301cfbef411a268e7218f7cf23e50af1e0e4e3a2b2c323d3a4312265cae7a124d426a09dfc6099eb50a25099703c7da69b261dea18cbb6455e2a6cb76d283ddaab5328afa244588b52ad98df7ae3200e1558b00b1cc95fe3fd5ea8b94ac8b1f98ddc17581c780394bd45db415e705914c22efecce660f107bdb2ddbe618168781ddd0142890026c3ce6f55278c0c5911d3e6f9a371e5332f3c5d8c707217a9529d742266b51f8cf069f3471b78feb66cd8cd56ad1e78a873e44ebacae533e0ebbdd7e118d785a67286133ca6023264953a19ad6a215bb961b7b1350058e43a35e6b22619893a16e45c8e0575ecc3cdc1aff031900e37c9b8fca8b1265fe15bddfdeaca365ace8cf540189f30de1ac62cf4688007a52a0ca3199eb76b6180ec718053d6cf00b116c20b9203de1ad8cec04d0b8dffd63b28aa32f945bf6703b0140272cf50939a40f93d342d2cf143c3c1d38d573d0599cf963fafafbe1672d7587ab6cd9b7ee641f038dd12cb9b93f94cf7541290d1ddb0258159ec7105697facfea65202212d3f4c6549731712c6186ddd32888562aae5a3aabc151fdb64ca73b84b9180b1a753e9da5ae2eec31adc1440d814ac76b75d0510f7251a633234d0801f812dfb00b73bf1608c6471e0a127edb5fd6cc403d0c30cccb13f2a7918a6428428e11f40c90b3bd7121d717a5032e5791c714fa966e258c99c8c529489ff5606aa729cbd7f865836680a8edcd0ff887766e65ebaf81f215009b466ca1295afd3626468d7bddff5af49e8840da54e7901691308b91e6619afbac60f621595c6c7ac3ec7ddb00343c1e4824653b9c6aab38b30c052236b0a39910517957c391bc801fbbd8505b9294fc0c9682c53537112b056537dbf7bf2e6d752d349a679f0a021585863183dc88d5f02faed84cfeeda1fcbbe2899e2cb38098b5d815d408bb03d3b8b5a6052841f0eac59b9914e783ad42ca8577caed35c3eb485be09b41c43d08fe53acc92b763a1fed1f8a453de2556860c3b521adf769252364068ed6b730789fa7a0a66fdc475a61e1a93c0b8046e4b6bd11aa54726faa50ad02f00936eea76063a81c2a22e2350dea6d6d595fa30916d7f1e80ab7fe82c21336cbc289e3c5e08a6c3f2e0d7426ab58e2b6b953040a60bd8376710b639f5408dcbcdd1ecdb3501a3564ff3beee3a5d7ba0a6758bca2dc81231e0233217306e6283071a93875903b49955ecead296480b233d18fd9e90dc1c06dc621c1e1e37c8f6d7bea717f5a48cebeec09a7cf974ad3eebdf259f9731ffcfc732855afe4217b9190d7473185ec11bcb2e56a42cf4d57932b7a5fbe4fe0900e4e1c0afd7ddd3886e4564d801d9bc023ac63181ee9f913a0c6e013c317474340e8fce98d6e10837a197e636f782fed760c1d21bcbc473f8a18e12243a15aa9bc69124080c97095f74544a121e55fcf38c4de25e97d7f9ac047f3927910d770dcf00d0df614e5d3aa0537dc4b695525805eb702025228c0cadd5f7bec55e0aa4b6b64d4ab14c6e41ed9771f07b6fa135764ab2585988bcee7ff00f6d4451ff7f4ea4578244f5a848d496cd4ee1dc9d14d4b6109be34551b7d781d92058c43b8f700ce53611df569fcc1f615479b6b31dac4a62899897dbd3d066095a32aefaea928bbd9ba5e367406dd8359b9ed0c1a8c15ace934c229b0e8f0281671b316184261549211124506650e851c03036b1ea8c858c428de10ae205115a1cee0a593d4e37ef14eef1582caf7a8a96c52291772ba28a51b5df6ed897527aa45ee514a6641fd3afeb29a390247d9ff9a3b79599ec9b38301b50ca505f8e5bc9db5a69397e36a663db6524bfe555e9ee726bffce6352a8877ee90a17ed44da4f4028892ddb5b9ac9073635a79c7a04a62ba8d3a196654e07ee37fecd2d94a4bcc10c81923b420d8bb30aa0cab28f384d2e4433214219d123c72474c1e8cf8203d9ae303a22a5d0c7c8a29cccd9e357e8711dce9d01628d59a5f1019a24c518c02865ae754996d5877c66ee5b9b8128314485706ce39a8a59f62198088ff674c9db5304feadefddc6a53ff5753855e1743124bae4215dc1df5786896e80330cd8205ba3921a1f7800b16049dd546ba937b7b1b06e0d92f4e496c2e5cfe16eb2de57032cdaa0c92840d04e98c5c58a05821d4d29b6370000bc76c7bc9a303d4aa336b6798b0dd11480d6916f8c164502c38a598bec0a4560a6b7ed4dd5fe37343951320dced3e2af9c44665ab358d9dd4466d9abc90e06b32119e3ef818a82536be931c2c1bee502b4ec8d0293c78c5e78707d370b2495f52c60149a5de65c05897e1db3aa52fae888a47bea337539644803957c5ecc7969473270fe7ca7022678b75e6447e7753b296166e8c0c0288aa5dcbae9718ef1003f5b850bc0e09af26ace3b98d6c94e5e6c3b31724f87091d8f9ff2d1892afdcf6f28f44b0a4fb1cb96e8f9671c840c20a00eabf249c1b2e4719f03d3ff9216616e617eee4eeea27dad466666b181fbd87a26a0b9acfb943b803a80ca30125506721cabfe288e8defa16688228a8e977273afbeb419d400ce206614c5052b257976b522809d31aad04771a44ef1235c3eb4022135ba7393fb944ee74f767f899b392605a93a7b2c1d6ed32ab85699afa22194e6c840b680acd09170a72357f7716cd9ea73dfe565a6146de9384834c24d60a176df7ff0e7b1fd2a2d7272a7bbb47936f108cf377e0c157eb8ae1a27278d613ae701dca901e1a567bbf152845744d0c5df4e370013e3dff3c91aa8d91088d25a3adff19d341e0423d33117bacd8ac2d3b7c5ec8576c8e6008227ca1bc9c645bfb190970d69b54b11993041dfefeaaba69ef6f988e71f4512502626b07201d364c84d56c90fc3f619a9b24b00f2d19eef175fb6c7c9db3703019e000c10c5ea0be93c3699fc5b94a283ba89e13f2b5a8afb2a3af5853d5070ffe843a1e9caabd8f2e3509da9316711ce0d398dcc19c3ad271ad5cc3475b5c11107f739ef604550391d957d662fe4a50e34726a73fbe47120a1e247b545e8def59e39976046985750e79abf9b4c5477cd508286e1011c309539850ba6c9520bc109b265b6302a0b170a0bc35f4f59ac0244cd82370abafd888d4ec14f56e502d53ef24c03f30793c010daaada864dc5aa204bd4a30b7b243ff1152533a945bc4836f89cf813e10fef31c07b5b7d0dacd43ccbea61880c8bc35c5f0b4f5e59630a8f1aeb822febd9320285119d72f15ffbaeb11a4324331e2ce0a9347af1e9101209f680e00fabaa0b4645a4e04ceab5e373e54acaff45d57717d929c69cc0c14fa55abbbe0312b563e829e464aaf3d6635ca1cd3b2d2de4a39e79efe3f55b847fd947c36a13447afd162213f1edc32e61caed08ba90a20b5e6da074b74a2621a0eb29e6541db16ab48534b772cad16738898bdfd3e842b890a6369c79b399f730a6507b4087d1c5eaffa76078fa59623b9aa813db321c290032deab6718561743f2eeb1853ed0315344a00ab4a423c8cecd8d146b1535ed25c0a9ac2d15cc31142e7ed4cb4151a6dee7a89c36cdc3dda901a771b5d23654f2fb767d2819d72d20c37795e493fbef4f9add9c6aaaf9103a6c179633a0c10d7b176b265ad039a53e0e4cc37057a2aec74f6061e2b3ec963f18aa9b0c74327c9c80a1afbf5cc133c2e890ade8ec574fe1001752d77b49e83ae1982e88f0516961b742e0d5044a4aa62db5eee43b47da06be9839efbb397d0bdd8a174e4ddd364e5a42d54b15dab354199c7058f06817686f30a907385fbb0ae55d72e44564ae994bf3d9782d1483d2180633772d4bc2d742488b5eaaa7fba0027c3460ad5b487be964988395d633cc33a7f3f94eeefb22db89413618f0502b875abe51a563b8950aefb3f57c4bd270ef316d762b7d7ab329e93a19a5dfd506ec63d200ae5a63d6e02d985611481c64ba2613238deddfe28f1eb0b2956934395b06c5a61c1bdfeb857736a3fefab01756d581a4cd30b136f763f6cdf2569456f4e04439f20fe90c1485031132a9552fcad82a3899445923a5edee0307867e96ec2e7541d3b5897b0de141b01703a312e0c2a543d9e50df7434fde4857117dd54a436845d57a7ead81f89e54ed113903277db3bdc2a233deda93ad255f118748b2ff4c9b8e190aeec224a0b0ec9c8e3aca01beca1f3435584fcb9dc421c808410f17601c3c4622cc40e481dcbedd3a18f464794159791bfd98168fcf5e7c9a2fe2a88cd955f853d0a690e4b53095c1b83fe1f72d918431b218f9f94f900bc624bf65f6e2a5180f95bc259802aabbf352c83a2a2cf586b3d4cd2a5282cbc2c96aa5f402954d7db7224c611a442429c39d33b0b6c4f57f077afb388b46d666d8a9290cddf9df890856b65c71ef967e03d316ba7301edeb98276a745b202e45e1eecc6ca12e68804626431a4de55328eae00dc317887a8d28a234beb74a7cd19650fd06da3ae4d006c0e850f09eec6d2d5549e46f0180a21e6ce0c8d997e327787723caf2e3e346b23a9d309f0f16e370ff47e43580429d0888ba247ea44a034f1a7a9588982170130c0c03edc250f928f669734fb7c1e90bebab943662ee26c2344357632e7aacd0cfa5d47c512edbdfb70cdd173aac1e70427c81f74970889e79110b91d80c3d6440f49bae8b98afa20a27ca8fefa46baa837ff42a94f952f15342798e8ff3f9fc773899dc29a00336144d71bf7854ba10142ee4060303af38e77a3e5d3b0832ee792228e2c707b03edb572382678c336979426834a5d1b55298f91296e9cdec3e6cefecd6c0f43d9308b54ab9c5719fc0d9062eca721844d4f6eb6fea7e959d720a06d48100c4620a3274615178b1b4a4d7b53d713f0139b51a02cb46c1488b587051cbcd70cad1674047382e9234828ff8b460d5e15e194498abe6e51143f6516eb11859e6c8e8e8dbee3c976ec28e408d81c9a5d390a75e5a5e2b06fbfee77d4d380ece5015ff9e0d4b9154d3160f627fe2bd847fd1298ec42eefb6d029e91cd7f2060f08270a56b6c04bcac807ea68e3537b2c4d0f1d4ae41d0370089554c840b586e59cadc9594dfe893119d0acaf1bfc1f65b83392ccdf8f1b847644c04b4d6ad7fd5d4ce17989dac28253b6e2f3232883f2d8ccc30581a68a5c21c1b10bb1c93a8936a55fbea790fb8aa7a1b8a5f31c1e39e040d2c5b457b67c3bf7e4870f4677f4a19c58fdd3a6c82da9929b4ad5484d34d8219db0c9a4cbf5a606232c40a137fb50e0c2c85488b1430c5b39f72674c12276e356a270cdfbec9fa705fa874653a611ed8e245c7ccf69f9d1454faa1c3dd1d6a94397de4c79d2f0902f3a5262a16274edffcc87b631b3948cd4cc2947542cae2104d86ac5f896115c782a00ee4a89177da304ea6ee50ba0aad21c80c5c34f787ca23e1003fba884d2625b3a4bee767a0f10b2747ca10dc81777d797312079718ec227476d6550449152b3daa840d252f6f5b10e82f25bc9c17ed61e1bce605eb46ce8758abe9f0dd3911b66b212f1390d0506d2c11857bab27525b5272a5a4b95a0357ae5f0d2c4a8350b2e8042d740519fbc09de395c010ee6b4e22755e694ad5df71b09b033b765aa3b22d527eecec48cfb47e39097770a09141e36e45d12736344e32ad630361134448d3b126e990337d485f2279f0fca461c0fad128f530fc15f8b923ce006407785fdfe2a06d5f4194e016d6f3c57cd5ac661ec10a9a6a8f7ed9f69272d136376b182fdf60abc24f497116f4d572a1b636f9eb7e026ef184484e35a07eab254173a83fbe147312c6106a646d9ece9d448e440045e1def6f90b74466721029b409240a849e98ae9646342e3ad809641e74ff6c92eab02023255043d1f0e0e67168acb766272c1506bc14e2164b4ca7101ca09d33b6c3361ce6c2592526bd50990ee93235853096f573040e6daa11fe543f02558e453fee17af8c669890778e7b2c887b3ca9af3f419347050bdd07088063703ba12c100239ed2e9d56c1ee7d00a832a0b5713e5c0f2f45fd227757aafdd15938fde3bc5b417880b7e49c89eeb35a46a68d94468f7249077eb55d1b9a39db78d86de774aed3e3e7b574e61363daffb5bbfcc7dbb1f049b3aa9e1949dd566bf9d31c9ebfecf8b1c2fd5a66253f62d554c874cddf04a4e879f30f0428b6b9990b36d68f1314dbf3d1070c1ea71745330e60fbeb19ce7f9583de72b7cbb76d79b00991072505162a4c4eb6d70444ca030b88416de653057f9f1c6e1df800e13ef3d456c6ce9eaf825faaad51d7031f2a06f51d23f475e89a6621ed9c2d6a9c38998ac10d1fe344e90a106e41843d1176883a62d35dd119102fc7428eb9baccfe4a786dc0364c5920501a97e176d7316969f876f6739c4cbfab26e1251cf021cb1dc48bfaebc7c2a1661e27221c05315d1f41220f8c2835dd4bcc4b78567bea10c8ba4090021bc650db5232ac295337e7d40d43896e5d733f5fd8d95117c707a8d5c662681f4f3bb9bfc143d06a2d248108fef9506536d4cd3980881ca12035c8604f2f32161027ce85c4f7507f0873e2e7ae53bef9467828504d4e3675b22909502c79bc87394d48cb7bb209d60dfa89234ebd8eb3772631f80f01c85790db234ef58808313f398ae307c6f045a3f1ebab74bc9d09339e7770b569e744937bc2763cc4d6636d3bc682573ef2b996d96fd699dc7cda1c7c6ee590c6366666636cca774c5156e6813420e5816906a7a41701fa0dca1419b42d543b2695d649ff95f67ba61807f370200635939b47dd3c23f4ce54b1e5c2e155e9496418e240f31c7b2462efbb7579e897432e4a1664e357374a246216ab4426f97827616306dff2c7153692b3a8f433e40dd47ac317836e98ab48158b0daf241273c5b1998eff9c2728ba6575a8de0d4e5506ed77fa35c0cb61e4f899c3888eb247b615ef28ad104820a97474f8d5b2c430c2560f68b9e27b8411fe0fb85496d3c5b508c7828d3c3a536412607372ee4ff630671c31047b250640e72ed825d012aacabc7950998774a54156f78dafe42781fb9a8ba666fb8c4ae8a4f6de0e43508321dbb0e0a243669528246c8cfe34f41c9e1f11295c46ed444e1825b8a076b0ef8376b6d321ae0bae95d83ee51f95336304fe008fd93f82b58b36cbd5f822dff0c78d63cf1eb2d40e9d4aa7f365aa688f6072bb28f2643fa25816012b3d328a1f24e638e4c016f43e31856744d88586d60ab2c6072f897fdc64f9f3bb1850bf400f4a378dc6bc46833dc661b1e586a0a869c826b54d5dfb1d7c2c9f2a21bdc44792ae0dbfd8ac8095f69b919d4f58060a2e88f988ab14871c0cd63f0efd42403d717cdac51f26443a69e1933386744ec296d79562831dddbcfa541b5726ce8eb1b64cf3ad9b35cce735e90ee36865fd75c97e4abe6aec350fd9e043835ab4b026857d392d8b09cb41d5a4cc9a35346f52abd1d989a9bb0d62829e6cc4ae43ad77095de51a73fdb46afae55203c86bc182cf1e1f22981e93851ee4e88e1b9a7ca2ca8d1faf6a9576bf6547b6e63d40f188b1c52c48ac51d7c215ba3fc77c5070790c41a409a2541979493c9320b851a44767df89a11912e661a85acc95f226283ae522b6ae63592fb868b6b74a622d80e80e96d4d9663eb78ed2a40b2d824a4acd9540db9a81c3831b0a06aaef679994d5f2e10c4d71264b7d41e3d9c67035789034f91c479d5410df09ac25deab0238987124a67770e26ce4a54e42352334fb10c9ccf3a7d0b00c34f3a2e75fcf1e00e52678fc3210c6228fb8e510a0318075a6c0082fa8b8ce72ed2a54fd8904734f199c77ec18d217a34d1a225c2e8c682a60599f0aa0af2649974306992da04e251559a4e7382f1fd3d8a4ac6370e46c20abaf6099d2d84016e9f2e7ad6b5cff332fbf7abeed81798cfefdf44f2cc700e6f62ccd5a2006922ff8461dd0d892630f3709aae03f3b36daa95e53b4b196ceb21ad33f81ef200c2e57b83664e44279bc9f4fdfeb5e6fc92f408298ebfc144551ef4304019ba7b583111f9c30ec66082de8bc081b90ac28d273857f285e61aa311ee13b676c4961a28d3f102b41be004d188ebbd6ff37feafa90bec272ceb2e1ec33a0099770c744416d9e5341c1ccfc1a588b6a8ed54fa65e78466ec6995df8db07a0d1e44a801adf1a85fb14b19d30befa0cd0bda14357ce81536c29445604f3775831e43504717de0ae52972832c8820d74611b1a2bf0ea6483dcad463f2d0adb56d6a45b6c4281e32c715e9709edbfa7089629dc2dcf70b4949632ef32dd77f1245a60c9c48b161fad4c855ede1c7ac0604324da651c362a776bde28fabcdbd30c5108855420f776d40b10c9d5dae25c1ba3889f8cdcd54dac626df8064bdf56e765519d82faf2bd9124f804a58b2fb0fec80bda3bc0ec798848dae2621a1339a1bc2802a628f5259cf00ac225f6ab086b6786278532562970502f380d49b3954da170581d96b3bd1f79d34afe3886eb49d9ff2287f1b5f94691089d46fa9587799e8718ee1789515ea918f42f8b11bbed6a10451ebda1d12be87cc40b3582971c9596df7827b1a32fd45f3d479967ddc34d3370f8e12b0c43f5e6d49a87b2596c20a6f47a4612bee31ac43da2f96ec073ac1da3bc56b731fd774d3f822008ed3969df670b48d7223108c3451d193ff1dfc9de0cb000642a8f188b5550118ea17c95b21f68507e705d6d125a078929fbccef65f63d16e3516e1cbc4e80aa894dd00c2afd388199ab9de1d1e7352d889c8b6d3a637453d0ed844ee3a01ba52fa16a147783217a2029cbe4680d14fa0b5ed0d8058073c18ae9851a603d9fd02b8912ccee92526e75f0c42d89309de25c7a3dcf6c3801de6550e908c58537a9e41864310661c61e94672375efcee09389dcdc5f66717c86c744ee8d50940f54c85f2fc9a8ee2824d22b362c507ca4e9b7dbaeb0c80864c58a0d3358310b769153eb580d47913127db0287d75c1e84dda7f1e8adb3c457e010231e23be631a2b806724ca20ed9581920fd857b32ecaeb0a1006774e1816b983bbb29cd0351c6d1373aa10e8923ae9fc94ed108a52fabc4b0c5f1544c59c2e0d4447e50e5db7d8d07024d204d597e0e7ff9a4fc7a9e5b64c171939c21005819fd344ed80529addbcd97083213b8783a0d35328a51954c085bd1fb7416e21e4ec2218a2423634bdcd969cf5a593beb36ce4cb7b0354c6c42d0e4a870332fb0a4b6da29f5fc4bb5ff2a6c355ba6825addfeb60bc0c287e97e6c951cb19be1371a419e5bac0b9bfc3f28690afc62aefd0e9f18b04dfad528291efee35fb8b4c66fb9bf954b9118f2ce1e1276385855ae0a2267e9f8e665b2c6cee23a5d7b4b9a7d82409c1b1cd2fb1af8f0ca03442e0761c8f4e3ea25e86a46174ee1ebbe84613534f50e4e1ad2b01645d9122082cf84d589198c717c228fff447c95be7b5e558a85e52a2e94c55eea20ead7fb64e565d32b50b7c110bf4b8505bc2f1e07d76b363472db05391359fb902cb97dc265c6cd52727704e6134297bcd346f58d4218dda55f9da213796d347867a569c5105a40ec6f7ccf046aee230a361082874e115264bc25a270cfde7a198052a3c24a6e8e4f40cdfa66e9cc0abc9410fe492b4cf60af66437d5897896d8e5592e86cfc651ba058f8c87d90d4cbf6270f1e46aeeb1c4a3e77847b169b27c339a87b91d66507156f3e87a6331ad5e4e22bc89e3b6416739dff4813680a8d0fd9bf9ff9b52191dd985754f271d4cd9f21becb512408d8531304d380c80da54ab02291326d27c6defc6aa9ed1cbdc51c05cce30238fe112e2f3cc326877d6aeb472b3bea01b9909edf4fc2c16a54d9bde746590652bc643185dc07f3f15347bb2acda8dfe1cae8b9c914d4a3c8e767840c7ffbf0381df9c55ede8ab9298aa18428d5feb46410f388b846ab63da3c946309471f5b0a982686a5988502d6e8f1dc74c08e7ff1bd320bb46e1780776109c2208eb1417dffb95a487597740497726180bbda7dbb831979ea8a51b44d70800022a8b4223fc942327e7c5754739388a7f76d4c397805dd72887c8738756ad7ee74939376a8d49d7cacf858bf0a08a5aa64ca0caf348114b309066c67a6868255465d31554efb2bc42748dd9e69484573a032d9a0c880a90e3e12ad2dcef8ad970d8b04e3eb8bd48e347a658a892c73dca1c9748488ad029173fb7d58b753ba7dc33005f4e8d3e5f3e9e442da81be733bebc9733b95d55ece240f71e891c79a64bb23a8647f0778a845858ff85ea61aa4a926cd5bbbb9d924e76017843c0e35186f0aeb8ae12098c4caf06b2a5331d83006b47d39f6bb84f5add049e7b46c744ead670ffd58a2ccf22a618671783c387bb810662f1957d4e079e2de68f1c421a6ee779019008a2e39814b144a6578d579e121819799201ae4679499b01213c686037a6c28c35d5a1b686f1a623c89191a19358ec71bef62f6a04c4fb52b9e1399aeca00d993a5677e3d460200cc2372ef0dfb911543e44165453f1f23f6b787dd77772c9734a80443aee6a300da711e271c9d70960df3ba5f5817321da3d434d6c1313365f709340946ebd6b458a061b6eb73eb91459b00ffdc300bcbca89a16068d22d3e910624543c395fa9b28416518f4acb060ecf8cda51427c5f15b8a63f1c0262cee1e8a7ed95d7264b5f43170ada9ef062fa6a3f3bbe428ac9b42bab5bb8c6fdff66310611422fb262f6c5e1c9bea9a2b3a45c6b1dd8ebaab36ccca1a99d75f82fe1d1bce7c1c283ccc3db84842ccaa34245d895d7f67fb379cfd6613226ee557a368cd37974f8eed68e8be29321ec737d5b524c1e73a25f64f1589679b2ad1318d6dd2d825d27e5abf1dfb89b8587bc288d5015315f5a54e28041e73749d522a92db017d75e1475979b91da947d821b21723f3db9295671fae647e0309611ebea3620bb31d7f0aebd4655df43ac39515daa521dc14f926a59a5177e338b6f2a38a4459581af11b73f58b9cb2e7b05797343ed813bab7d56b4576d41f3bde6e27f6b9ebcbad079f650b66dda0518f4b2c86db180d81e8b5eaac61da76509846def8b79d8b64b12a4e735c16af521cec3ad27b15cf44e82c46012ead88bf4485c5b60fa94110f14b54f12c21c3ad3e7114782a589e79025562d2ef3a5d33cdaf40ced8c094d900824663e18ae84263c07840536c220a17d5c43a6e85fd2cc1462bf1bc690f48c273bb01b4505a8da50464523e594c34734edd07e31e8f93163955c7ca056a1f223d174272c59725bf2a6ca33084abbb765355a19f9839c3d6d87f7ff2acc55a79552b52211d1acc5017429875516336307e3fa8fbbe2cee7a817f2c1aae411eeef09ac0a4ef27b4ffa4cf3a58ddf9037fe984af5d78ada657a779e3eb3ee20aa910e1ab38681b03e1ec76e7613af7ef9ba47350265158fb494687f2cc55bb6a2d67e0b53a334bf585243e13af5c2452c5e0d2eb6aea38cd91c4275ac567f8ac729d47c61335cdb6bbc4317e9679436e5bba709654e49d96763063ffc58282a4fe1883e4a7046913bd508344e2e4a2bf6f6b62f6ab44c099d8948988d3c214179389f9bf2d43a42c5ac20ebba0d56865a292f1ff3c93d178df2ad6b311021af1c2d871aeaf2ea40b1768c0ff3cab3da038b08a45e1019ff77699096470c044a48d727bcb7182193401574558fe58b61b56c7ba3585a3f4400c4720206ab3b0570e881637a0ecb05885458c5cfd7929e13ca5a14951b18982e92945e1579a6ae36884547c38e0ce353195ec03e374e07f65dbde85b2e8f174a0603a579e2132496c52466d1125b30e68afe2d96b546135cfedb8adf98e8e882f2d5245939ca446df3985394ef080dc324ab9eb061d30873734f572e922d3abb2faa370f4fa27d59b1362fe09a9494d1c1f015120e0219a610c8a582b160178c142eda3d49e2f3e44eda428ffdb546b7af3b13aae2e80822c3752c041a3932aa0bf2e94492939a6cf95961328a6450beae94aa1bf0b5effb754c98cc1cf492edca4859c5f4b26fea5cabc9692c8dab6308091691426542c378737db809e28da8cc34a699b75427a76e2f2e6493c1958a35fb75665b9efa57297b107468c987c12031b34fd6b47084b59f553e9833bd42c957faa1125fd1981e644fb2e738d30dad10c473949dcbfe1d3855c3f0a32a2268b990c7443ce0c3817ffe1d0df8c74428ef154f0f904af5799954bcf60d85137fccf076e31124610f80cfc1131cf42225945f469c878573509c0702920cbd0e1ee56ea69a7d490580e0aee677ef78eb05020282e97c7b2548856e825c67f0c35fb6185c23da3777acc48636401519f28c93c93b875e8c09ca28d092315189624c1deebe190746df0fe88082551b308c6303a3417ed682c9f1716b94adc72b80787f0697a4062e6f891b0701a7a1ea5e5bd66c9baeac354de33ac65e0d995e98d91fc98d79c350e29659dd93c7c1ffaa83c0dd5e70312da0137d228abc1f074fdc606caea84071a0d1cb0869d705137135746e68c05260471ee501b31bdb490e6cb7d6a170a87274cf30003d1262642c2372fde55b048486d2dcacaecf8cb280fcb530a3074bfba8b8e823ba41600428cf91605641879aa3d1622b5be95b98751166a27011f6debb0803dda2305dc5c3120986da95839ed014d2fbbbc92466991ab207ffea54f17231cb2fac40d9682c2e1df2f48417e11feb2679f20465b474202cba751a60afe8651da7f84d118e50b4d38f275aecdf2ef7fd89022e3968074dbd00d14e23ac3c533a82c1daeec2ad0a729c0ab65c5c0375c0234fa9d0cfe4b4a89c8e3e20cc522b18c73c56d62638b19cbc7f3a0b5d03b108fd1b912e0c5458b14f3cb6bc49cf6158a035a77cd38af323618f5c08311d77298ea4324f08266a6c561c0a0188136c4f47b16ea3603648432f45764eec0b090b0ba6fe03bdb057e1474e6cb369fa78a2ee484a13aa2bcb88452d7977b4fdca0d28829ed07f09d3177fba494240d11bf0433cbaded6ace02fd2d0e7303f0634a00ed92a6377e48b14a0483d560bb94ab55a4adaf5a34e2338483cb14440e9f8c9b172d52d59f31aee3f7b73f1f714048fe511e496c8550e06a700ade3b983a0b426d5cf4f6e29746893c8717946c88f5249005a271637ead003df1b2cd41f41b08dae54c00d2d4d8f42a3b100aed136485b260844d9927126265f7a1f05d301499df38d4a003d27a22e4ab0f382b559f66a87624ded8aa84e7e623730f26da39f20075f6231852f8f1004b1cc032395bfc362aa9c067209ff305ef02d2c6644579a9664781a903c2a45621d8ed70839ab84f3e9a952293fa5e82915dabf83f07de1ce2652e0b5506cfb20f1d0648ea33180f477c69e451a7b58856312409bea0f6e2274e65dae8420d8bdc060adceb745251edc9fa068ae23006276eb161b7098a5432fc9d81e81b0ca498b24820e5b53704462a4a655c0146540ca16009db6b9c5af9b52d23b05752d548596c60834878fe92e070fbb5daac7ada19a6564d4fcde160b013f20ab5786d29ccb5d05ec4d1a10368c2ca362b935d5b2aeb19ca10120f34403efbf78b6f425070ecfe16ddbb993dcb5519adc999da7da9a7e3dde8c2cb9dea2fe843377c538dac473e6c06f812ec5314376ce44fd69b872ce849ae27273c392a5965a03674da7580a229b73263d4b7f48bbff652cbb78b6e69cf9e0ccc71363af5a23a084c6effd28e59ed35dd98ecca00e2c3f866a7a692665d3f89899d95e8ec7657a7955f4498dc06e62a4687655cd99c119b468e804b0307ac311e44aa3b007e680098e1f1f24d4c8dd9fafb3676a45212d7986a5e2cd3310c09ec7daaccac3590c8a3677ce9c10e20801d47294dc0d74da03b6fc0bf6bc22e6cf576d1ce6d25c9cff9c2369dcbc99732c72dfbeb7fe361fbfe4b2c4a11f8fc51ff74f4e4cb51c428c0133020a005bc4e94807c27f921a0c8e82b8154fb2df29c56aa6ced8abfa6439b8d02d55d3fb5b4f381eba5f3f536573c7060849301f96ab71dd6cb24f59ee5103ecb2c426803595d3ede6979f9161a9d7c2aee61463857b7f79da891926eda4b73b87d794c9c6be9f449ff19141cf65b29d4e93b39c77cd4358dbc4cf6b4d517cc52b61382f91ab660195aa7a87a4a28926b3237ac437c7b9502c91e17bcde2a847e4d19b0a63f603da66dbd57ac7c11c74ae130db409f89e336cc4d86bfcb63bb3f062b255c54cd7d6356656dbb3c77997c32b36b126343eebcf0ebe2762ddcbfa9ed03166a55144d630970207dbacdc3d072172b9feac02046e8ee44ad24d3caccd7fe2491cf998c16061c78819e45952a040e1d1a17e56e8661cdd292a7b6ca1c27a475d1e0850512388f29063d46ac41f2347ad28ca1f4129104685b2703c07dbe18553a8225655971bf1fa2185b414fd12ab568ea40af9831639ac47532a31c62e9f2c967eeb4290c44de9ceb868fac83a15e4b8e5125163c4bcf6fb7ef57554d65dac7da1c15a03a4a981c476086c201919aadb3931f35609cafd067ca20fbf98879713a0c87fcb299d710060c274cd3da7f0a4f16611cfd6873c429e504474b25c65cee202c2779de260b7c42d202609125f4a9b6ffff35b97f2c3c21fc08ad1523dbdf64bbf65a937ae8729371c039b59a97c0ece0c438c2efecfcfaa941f96e0802a464ab1f4fa931d03ce0c07040e0c03f33da93132116f6ecc5dfb9fdf9a94272ce8802a9414324f47e6c14617fea724e5cad009c310fa03073f5e46c3d218a9c7cd5f5c94adf2d12f26023c70f70131e2c2be3e2a3b8f73a62123ff1f9871f9e7be742747d5f8825d5c29868c6a5745ba28a55d7aba30fe2caab02f4ab7638107e4cd629aca00a87abe0b5b81a86ee00b7f5d577a38b2581b1ef34ab46c47e3c62a4fef2cc02977549bb691b55619ea0d80c6ec46814a159aae992d400ad34ca3f5d719ce9b97f61a0186be9ac02700494bf4e0acb11ab2cb4d833b76060475e49600c3a30df85d221107f8c3ab0e485e02f41907a1be6cc627e90974060c54c7928dc3b5b07c040823864549c52d100acd95263c0f78405389802256be0f66d36d0a78a608d909b4236a5f59ff1382fbd2b7fd5c5bb6d1586dd9399ec46b415f6330b2bd9b83c2ee55dc045759223f394a1974dcca9bf72ce36ab44f7f0671f894c47b836c4819b6fc47916d9d20de9a727d8342682d80d22e2ec2ef56afef612ccd31eaff59cb6c451c5c2985c50e836b65d1e22e05513bcf83be4f9d9cb44fa54859d38007174068f305513f2e00d43cab5abfb108c16e39c41f6cd19d2ac91970867433db8e17be369ee72671a052ae556f8466aae55ae132d5b939b7c2848a6c884fc50cba5ff01a7e942355f825d8340bf000b202c2be17de0cf0bebac8b3f6dedd69c0640a7a8118a823210440739526302cb9c47706f50dce50934dcf4e5cff93c5e8b9043c6d9d9c18ee15e8431b4f12753f2557882fa29fb4e80d72d8db86877e4e17bb135f62fbb3fcf9539b0307ba32d1fdb14d561a464dde926e626261c9c63608303157b68143136213dbb413fb2d347e396f2f048c2be045b059c60dbb255171dce29e764bf8e961d12c4efd4d83365eeee69ca83635d7bdf185ca86af01fe972df4ee0ebf7fe2c84a98cd2d52994131ffbd34b39d8e5beb60312312743946e10dbfbd44c0aec1412fe265fd0a65743c52366c4989bdde2dba576777ce939bd132a6128cba211076a45c070b5f918942b7d5e0b9cfec2695eb6f8ef4cb36f98ffc4faef6a661a64787d9a9f2299d0f4003d375a29d527259a539292337adf7e389415ba9f73135e8416c589ce0477dd9bc5bfa92d3808fb5bcef1284ee3082c53efd4b3bfd2feeb99817637b8857f14a80dab7840ed30ab4584c087887806940d1e3d8e86201e9e97112cc9c7c110ccfda590bd825742733f437b9888f068a70fc23efbdb950930ab81f9992c341c47b73638827e0ee4cece8e3a1ad1a63414e0e854143102ea69dfd75ca1741496ef2bc9408541e1ad6f6e58b93217a3ca8541dbf4585e282b4b277e7154b9b805c43b33bee0468a650914f324feffca65063107d770b5f3e87fc931550b2918af665cc91d180b2f51910b969b2ac50562c16996c9fa0a160c22a19ad734759747afe6cce98b9ce9807b07601fd8fb05c51f4c55f46f2fc31bff122e712e51f4d5e25cf58b52d07e6a9e51cf1c1c28803d4a330bbc883a08d3840fbedbad55a1f0e4a89c25f42cdc8cf3a4a862eda5c0cdea2ba741e00c31f218c94231c64d475ff24038b85a09dbab0692342cb30685a5618a3eb5fa322e613b896aa3b90c9a57ac0ed2adcf4ac64a71132223222e5904a8cb391f1e1f297da3287cc276a054a4fa0cb2e24a1166f2be2ead1d03a55b84e49dd283c387f91c0528166dacc5231263173c48c6ddadc01f0e8aaa79dd00a2ac9c05ba432c2057ca72b9d51fda11038fbaf546d558ef17465bd32fa4dc1484e63006ae48b113c60c6a5f0a440af934349506a7b57e6c002493214713ef762b9b0c4dbe15f63115aaef3af893abb55591063996f41fce58167b74a303b0d5df7780d12d558ddd0ea7696b8bcb509b78e1bd8fa9302a5ba3943fa28d9ed46234e8c621174829e22c835e3fd34515e5e55d2b65a4075235543d58028c4c74a64f28491cb38ee45617fee85984c42776829f537c0be85b234974f6a3e8e7d613c0015b817eabf2ba2fdb93262bcd16f443c0ede0078550b362e849f31c4d7a3fe6420b6182177634c1dd57f3aa998a01651bfe33b0b81fc7a18c48515870c1a1ae88b4158b41b5244f89dd9b5159237a8123be905a36e8b40e5a1705ce35fb74df9f8919e1132b2fcef2458f05171082a0327818affad2fa20d0bce082b0305728a008c03a5990a1ba9b6bcea0792889c1d62c2890f4042330bc71a81c19be930b9faae1356287928f2351867c811ec40c82fcfe44ab8e5f94901edab3889cdeea6c9aeecfd5506af9e849d2f79cc765952fd6cbe79d9588ae3bb33b1ad6df653e0e9086d6a3dcb84fb6f2dc9a8637996684518f43e2672995b6424c0936a95b3e3dde2669353de7a2d140923018a452efc8e2f4c1fb4b6bfbea0150c701788c07600cc1442f73586dda14054a97bc2fa5b881d9014d8aa4971fa487da281e617020c0b68d4e2c48bb381965b63c5e0a695d5c58d0671cc496e430c787104e6fba2fe6f29d34f3b1fde236579f52ff44ce57f7149d8bdb7ed3f7870428ada5055c921dbb3b466adb907ec6a1b6a990aab0d7ac04e6e6ce6df49eabdaf7ca43b7b1913ca94e8dd1b9401205aa90c4b36822b391329320c9539478c1e071d77e8d011071c3becd8a10e1c39e668393038d7adef08be63c71d39748443c71d3be8808372e0d6a1fc3bccf22a388987f307072ede4a5f8e4df8e2cf5ee527baad4a855f9b59b98f7a82d4cf1756ae2955f16756096d056c75e1f012c1cb93e7d6e271044171d4e2a96d09c04545d23084a3152c267480532930ccaeac78766da09b0aa9159733ed4c092e7bc4e6c2054a1d9f2aca981eee39afa134596875af74774bb8d4ce720b594e5a73d6ac07eeca4a08adcd9a61915b49bb949519663cf0165b85b26ad61963ccd6d451d69d31cb8cbbb1325a2bb366bae456d22e656586190fbcc556a1ac9a75c618b33575947567cc32e36eac8cd6caac992eb95525a83c1a7048b33a1bcc50f975c53ea2710484f4fac26b5b69d5943d2a3f105ecc467fb9e913c3669e39254a7be09664257a951e954720ca3c00de277ae9b976411645e567df59ff5acd045eda356aacd8969bedb64b6c9a756dc5b4ee968bb56cd1ac552bd6fa719451d959f12542f0dae8d7a73b4ae59b87b22701e66a075f90242c2ec653ae791a9d224235fa72ca738273ee7261339b12c2afd6d63092bc9806aee44e00356d86b3772f6569b2e05219310f0872115a38928b10d4dae0e17ef053494dc4c5886c94901ec8213a50ba90a0b39afb1aca722edb1c801b9daa513fce47ab708f90c2aed1380f51b362a19c408cef9e9874b45376036ec1a2f11288c07ebde8948b57a4d2de8c5c019e18db290e1cd28e50123eb0fc2364b962b8745071da645ccc002b69a070c48e080d49601f1cefeff5fac933fcef1073cbb7b33b9a77ed0a83d1eaa8b6316622d4bde0cb1f03153adb0b1f4cf6cfc55d3e9c019112e55b38ac7e79ad21a95f930bcb651c0bbb24528c474aeb49eb00b2ec22fa981d676968a07b14c55cb54728d31e6d130142b838df9ffe0773354ea5816e40c96f5bddb6d0eacab6daa465ec3e911d378ef8d33a746ba2439badbf72dc0dfd020e980ef23496df2a0345b899921f73fe8cd7604be6751d89ece9a2f97bc57929c985633c7c3203f1b5fa81a056f8b952d0cfa80fb73c93a50e626335b61c70c40fc813ac5c1b1b8e68552e05e2e974b36c32add18447b2d2e177b1d14f05f3ed31042e8eb8dd186027b4ac3e2e4edf0d7bb2b2b84f45a5b51d9d1b0f0511bdecb33a88fca40ccf9bd601d2772319555e43858ae9794786a41c85f1ace26db60e4a73da45eca6d70ec50188855603818ee005100c3cef92308b8db5b70f11eb1e0f0ca0dc758992a783c8119affe8b87871040649c5902be2e2e4c860ff47b3f0e1e63bd38d9c40720139bcde1ded593f2bff48333edd8895abc437d7825be87b1976c0d15a813d4f22055293035db3e895038c1c31e143361404714eb59e27aa7813d3d2085f61e18c174c2260689f317ab3d912e5f0dda6e9bbd8985d3401a4059e4e5ba0f496c36008773ef3e4d3e2d298947882372b6b5cba7ee2a2709ee1b1a3672fb4a5a1bb124d4240fc64afbaaca3f0365acc1f8b03ffb723ddf4431bf3e7430b97092c2f7dc31876c101f114c3abc90f4d2f6252ce98149ecc5ed8acd140defa71dd636ca1455be13cb5e9cababc4def79878e231e33ff1d7fa33cdf18befab12f3191839a49dc568e4f3883ebf7b90d9b0b1d63a8b95c5b5b20afb6fa3b0cec9e160773cad9030412343bfb00b5ef33206a03e5d03b661e7ed88613431b2040eed0aefc126997e7336ee124fcdc86cd858e31d45caeadd590575bfd5d0b764fcb8839e56c0c02099a9dcdc16e83f6c40b35061946a6290149643b4578388b4ecfcba63701696bd947ea762ee6861bde1a765ad8f5272463887f756cb8b3e67dd97a5715df29a0e0c5d339a223f3d9d26af0b3b6bdfc5cf5be095eba5a84dd27928140ca026cfdb1065040138038fcc890b0cfb159dff8d76385cef2e880b7091d06da2810da3b9d68e30f4be18b8deb236b5d16918c458422d83c68bd878e9410a2fe3a4a76ca4709e7c62febdd8608748864c5e6a8c87f49e25193061881e7701527d99d4083633f1749b4a6d86baa84ce8bb79a48577b58b8f866b283c566230a3840b26a7150ace4181b8320e06c07bfc1b181cc8439aa33ab0cb9a890238d60a96bab9e0a1d157a152a09f17e4ea331a96ed1e031046450025809b16a26ec3c780a4eb9485aee49549e7a9d2bc505cd06de08b3dd0ee7d9a474bbead9791b69b68b64d0a23dde3b17d9a16bacf56ba96d857b561310c53ac43dae331958f2b2476ba0e0428ce6f3417c19b8258a512175b58fe9c75f79909bbd4ecbf0033479461f63dd19fe943dc395a0d6e5ca99c2b98f5ce3ce4f9f3a60da3ac0696bc84f04634c159501c5515f4eb2b7ec8b0e3091a739a0b8259673959de6cced29d11c561575e34588dba84c7a76665f90decdbc1f9d8d841705ff4a688e0d231ad4ecd6a1e8466744b1b4240a1c2f1fc4cb86e19a0ee2943d448a0a297ea7b27a9e96da16edb608246a4d5d3c1fc8a7a5349aa46163423eb0e15fa8554454d60846a3cf767a340fcd2f0b35b456c44282894150461685fea1cef0ea0f316fdadb6373b7e882e835b7025a236544b84e753d6a8c1abf1a60940dbf970be16f8cf7b5a67139d8adec08bf3dc1d4afdcce654b21a3ddc8fc4a9741cf9c29322507b176991554ef53a71ba7c6068f1acad74da87a6a47856174631ed7e5a20bb122055ba38eaf43fd69856bbe98d2fba8cb8cf45e649a08c3f767101cc014c46aceb07561c4c04a67a4017995aba4c3790325af2b52cddb4fd990aed8464981ccbe9c4483ffc9f121c579047ae82c9282ab67f83394ca0b2b013c5d010af4dc355770ce61750acc7240ae50e5c6eed0e64552951712ddec19e28f9907ee30612477136af386f61118d7dfb570de7c4c681f1a17cbcbe442e5640ae7627934cce10ecae6a60ee50db66f3adb8ae077af16e826774a726fff1d310353034603bbd91abffc38f9013491ad3faddd27ebb42e0a815ab25104e6c8c69b1841e3436badaf84d45b4a84c494f20daea8dca3b05a62389b43522d3ea7e8c72edbeceeee454bf596369d4f280845d13751c939e77c216eb5eb2cdcbae49c732e06d55b3a241a8fc8f7504cfeeb160114632a525e623a322358a5206f51e99218d7dbbfd8b1ddb54dab893df7fe5ddbb45a4b654282836539f671ec9680792cbb1a42d13ccf6ac2076868beef8e919022393c2e4f366b46f1e60d14026e7c26b97d77f7a757bda5c121d108e2e9f84074adebb60994b4ee09aead75dbaf623173aa4ac22c2feb68df5f4d0a526fa9101152fe208959b2b4c64d388e3c5e3d40215b8df242415b3846476f06a7d06050573713ac8cdad0ca2006f3ffbf4b918434501865e6bddb3e0b93c403bb9510dd21c32b47a7a9882964f4a5e4a4fa78d7366def7ffd9b2ddf61fcd4bebfceffff1fa0cd9cdb2521c396bd01f5c13313beaa6214a198f8ca5131817ccfeeee6e347c71eebcb603b1a123693161a59c734671aab7f409fd0f28d1dc1bfc0f29c3e4c7bc20cbf250396041e15ddbb4bd8bf07b767777a42c249c7b4fe7eebe33a1213a24472867e3c50b628e0b0a510dbe6727e672391c77777777f7202e45a2f1c4b907714076e7f9406f79efd96b3877770722aab7d4a839001db5f6271c9c3bace52ac6d58f7a25963688745f6cd88d301d399ad1e5ee633f50398ee4cca880445afa1191437d91c15ddbb4bd995eb6e9459a59bbd62517311185a42a2c9f2a1f8f9755c60739b2e433f4734b5c9beb2ae97a9a58aca666347d718506a31f2490e5a91f8447166fd9658097845112381fedd004dd4d582da26b83dd12d7e6ba0e5cd15a6b2049f5962a974de7f30408d42b70cf2f7067edc0427304deb54ddbfb573282918f530b41bc1c434b30a6b2dcfcfeffffcc1d1edc7fd631710861884168bdce05efdaa6edbdc232a237e3e07b76b5c5dddddddd3d884b959ac4e02f75bab2345592daad2e40b28ebc5ddbb49deeffbc5c89b95c4e664d9734904b66d48b5fce90231f4515d8e8124b2fbb62964400843a939503ab36b304cd8a22b49195861d58402c79027ee1220691e97892e1f3b58b8699392504b24d5b62ef481ccdf07bee6469ebdaa22accc2ad8fdd1a6e11b78c5bc72de456d24ddc8133eeee24c434ce4ee3c01a27d6383425232e30d6955b3ac4c6c7552583a9b5d33484daf9704319c673e7fdb825aecd759943ddcb4fadb5f6edd45b0ac734451e10bea09c73ce334cf5964a5165759dcc889dd0ec5ae5114f1844e697ecc17a1159660c3073d0d3c00a9c94183c3830ef4e366ab1b5c23291c31eafdfb3bbbb3ff5732765815dd05299624083d05411244c4d5490cce869986293dfffffff6f2cc5b890915b41d972c29b8246997fe55aa429168b3cc5a0a25091a88854646a560928eb55aa9135dbd1b0f1003272411f71244e73ec4312791ec1c4258466bc51878ff7eceeee4c285e9c7bf03dbbbbbbd18c13e7ce33519fef7a4bdb15b7c4b5b9ee9531c32c1c81abec98e80549015271e532637be0947307823005757757712084ef7d72090af311c3062a2689e297fdff3fb9ded20c9c7573ce43e0213bdffb493e7c21592e930b38764ca86b6b63e085e6d18326b2851d764b5c9beb12d9620f98bec1f596b659a78265310192e20aa9eae7ad1a3e65bc8899c96514a2ac788ebef0aa6924516e4c315b286dc9744f392ec664f411926819ffabbef5c4c9b80e69261ed30a0cd06ddf3f5725d55baa5c660e554dd7b9e00c9390fd40babbf3dc6cb5536f291ceb132b1aad81447cc35313fefff7b9998318d0dcfef37c81102dc6863a44468071d9d440aeb04a61ec040f32c566e728c4696cd8d5530ca291330386870ab6e20945e5e55f07f724393c1e8ec35ff332a5ab088bd34d915a109d56bf3361c38eac6b6bc788ebbfecdf2d716d25399bb43d77f7a41a1a9c3b2cab81758ddc32153b7ef944249d6822a1313d323306486fa6f4815f1519083da22dd39d791585553c4392a8de5263e6903cd60280ea322cd3911bea04800a0bf2518304588f1d3771115186020d4ec0e44a70d960690153ae40116080686c5ac134d9fc40be2c19309bd1110ad205f463e1844b8b1b0818f05a19723c423da990fd789249326aad354b53bda5ce27d4034b34e701f8aeed0666d2bdf5e9a6640a366af6fbffefad37c6a06cd8cd1ab6407ae4e55cf9cdb8d0ca11c118cd84181ab934c1a69c463f6c58cb2b513d97ee3f7548f4dd5f49885c386106754b9cedf45a1ea9c771a8b523f366b311ddc731eb04a3f9822175aa01cf02459c197ebb756678d7366d6fd7efd95b9d5b92ea2d55b61d5a96bb16c248840550aa0b860dd205fe2970fe42585860435081e637b10e25730f28c011fc6cd7a312523e7777f7b8c697c117ad91c5c1f596b62f3976b76b9bb61d8ed16affff4f51aab774d9747e90f2f4921b37d3188b6ac75a66c2ac20502df4cceccc3499c68c18728c6e896b735d1d31b3fcffbf2a12a45e51ad183b6ac4dc58a135c3bc514fa6920afc98a4bcb4442075df33d5e399a6ac66b4d65af7bcde13f49ea1f710bdc7e83d47ef417a4f32c984738f73ebeefe434abda554595d1d7e88ed3a54c15fc10a6cddf663fdcb56eb0bf4c05ddbb4bd31ac00b0ccf994002061c7468526a08f538c98b8c5c56d75feff0fa2516f699127731014e4debc0ea9e248584700f4065207e44a41e8e05ddbb4bde1155c7777e5abded2e0507fa0246a1d858348486444c72b16d1b2820aa8a753cc8a698d94a8962bbbb6697bc7231d4c0c77aa663a1440129dd24e13834ec693526f29555617089e18887717ab42761d08817db4708a95814b24b0495334e9da7819feb9b2a45c87a477fb1e4e617f20dd3cd98e3094268aac64ec70295dd860b1462c60a5bb3b8f8010402eaa601848979ca1e342e28576f4d3f56c555fee9085d46dfea924274bc6ffff1ed3d38e16ce3de21bbbdefbff07aecc2fa432e92b6c967da0146559d98b09d22763cf2f6719e6d3028d4c0c7725aa9bb66b9b5693edfd2b091203014530203a3e2b462ec213575f31173cd08d207aabb0686c5878666830069deeeeb323142b091316954284104aa68965450015cdf32034eeee4094989669c8e9be6bdf1febc50a6ea74eb3ac36dca25f1a172e71906a5e1362553f532b869e47096a6473584efcff2fd930e2fe338fe5a9649885d906a7d74bc3221ca524c1403183a973f4a29725fbffb96877241ca872ea0f1ff9c99261bd8481129f2d0f832e25a70b9c17e371a5015de6b480493acbd199cbe55ed4232e352221750c03ba25aecd75571ebc382ebe50311383d34c8704616c0af0a0e9d803babb2fed1071eebed4a38cdbf7d7f9ff7fb9ded256657bff40adb536a2516f6991274808841191bb3bef3dbbbb7b8ee77a4b5bf6cbab60a092fb3c1c5945474bae1b37b61e05ebdc1ded162c2a63d3966dd7a089caa45c6f01adbb7b902cf596767d0822b683e30c7e84e4eb5d5b12a77b5cdfa3891584172873f88efc766dd3f6ce3f150709a2a88c651485081d9c98c705ef02f72fc40264bde00b3c7ce01da11a3122d6236b66246548887ae755ed150b295750e7b925aecd7575bcff7f9f32c33107e36628e377241bf13462e7844c0ac2c5b8c2ed8861924437a42899c2ffff41f70e46a83becb2cb215459c3eeb8c6cc96505fce395329d55bba6c3a9fd00da8a239126954acec11ab2073a28627a6451126299e8f4ccc8acc8e64d218f62ca2a43453d48f180e50345b9ed25b2533072be5762e38c32939e79c6984d45b4a84c494a2cada40d38562b66b9bb6b78f75e69fce9945a2e085988ddde9ce5b89ab556ec5d7dbb54ddb7b85317410b582cc9325684515c49a31a26cd95d31c3da646d7a01bbb65d5e6979b1c97b4871746df091c8c26e896b737d85b58e987a4b77704c53f413238fbb5e79afd65ab740546fa9f1884c9eb4a074770756d07ece39db20a9b794294595d505828dd8575044f8ffaf9273ce5f39f596bec121d178f4f08564c36e9e96d4647c75199e81f550656042dac84e988830861eeba64caf67be20d9a35733f6a3455cabd81234bbb6697b8f2d6742d7bfde0ebe829bc54ce8738189e853b3719622f524d51d0a115b2a502d18797e85d602ebad0fa452503174f0f8111183215b412648cc1fb690d1e77f001b2e7d252f7cceaba98feeeebe96242aa6ded21ddc012cceee6e8d3578f0d414ba2619a880089d1c3b7ec2c246a4d0a79b212c95f79edddd1deb897307aaaf6b6389818ac26f23e9aea991109adc340d64793430674eef6c802e6bccffbf12972a091571ff4a9c056a9bb36fd7366d6f1a361e324052d21328b97b81ba7e5804edc080250cd103244aa80495a36a840a6631035385d09d64ace4ae6351d82e8851c70fa8488015b7c4b5b9ee53f0c52d716d6ed4f94e09470c39edfc8f58002f5b6eba444cc6bbb6697b67e07b76777727a025cebdc689c84aa70ed912aa3ac4a54ba1e7cf4ce5bde02a0b6c05a99899a13fc2c8604442a849e83e29022c5feecacbab97e37d6c6624651b881cddf3d1901808804a1e42b2b0cc6617785cbf614909b7bef7ecad9635b5866bd820789221634a450c142c1b303f6a52b462ffff19dec761e2d803b61d8ca2be357d6a0d7b5390f2a8342d9e46c5009d00331a041c41c3401cca12b9a6031480070d8890bc7888d02c1286c6c180480c0086024100180c0602018140202c8a817096ca62b805e646dbf3bd2e64f64fe4f004eb23ff0e699e3247d24b954b5088a40ecaa685d8258c2a3eb4ecfe165df5168aaf83b3641af522ff11f7da40c0a2cca02c46d2efcdcc6a1175a1daa9d70e2a750166303407f0b645b3fc83a82b1e4899184fb508d9357cefb368f6496662150a6f7e785018b411225e0e15f5890e2d8d344d1149d7f438da024305fadde9c440f6f31737f3568755bb2fe44eb98682b9ed86ba66f98ab4db8a862750ed262b4417862278d552ef5a1ee7874ac9ff53c3143d4951a8845e4981874b338fdb6644167f70da9070c9695afbc2886b3fb9fd8c545bb6e2da3c96ac914c53d3127a343d6fafc05c2dd69c4ce918a410957a367bd3c9241d7c1218cba1ccb4637b47115544336634360960ff0568b26be93a44a1a2d9f59b24d4d66695674084fdbd875a244b6e018a06c2095e2dd4ab8dc170c6fe37ba6bdbdbc1e8b152458909ab6d0d8a7ecbca1261aef5c8f04b1c3068a2b35b45a74db7b089e3dd78d2b48c6259262ccc5d1dad46da316394643eedd95a6410cd1e4fcd54fca74e1f6da5b8bb3250970d8d5cf4a4a195d4643556a2188b7bfd5559311193eef6871797c02bc2de8bc8834292b0731e924411129b9ca537a6ceae2e270dec2c56a22b96e463f562dd90156539a401cfda7ebb33e130c2b32ef4653f2413e0940f5b53b2c237b31677e5fd1a5ba367da9a8f52f3ba211cd0a86acd14aa4534574928ada454c5c91726a7a4cca5c9e68a194a25bdd2e1ff64f5ce58c21b4eaffe332f7dd0630929ead5abf96f88b98f4712bb4d25861070fa396f6c3f228aa769d1bef21e9e5f3ddee813d442ae61f6e8103da1331e1fa9e78d1736369bc1bb1251fb2176cbdf28b32e55ade7e471266c81a4dd06282c44d1aff58c1627dfb68302d90a51f44d311893b313a8307cd6fb1475788acc3457c7b4fd399f0880683d5c650b11cd9d249aff68132935cc4ed966f320996adad48ae4546194780947df9a4e180080b6741118468028346c2867ff5c8cb30a14e029c09e75d282ea573c268928293a1f81872649162790292cfc5167a458b89fc6f0fd6686b2a058e8390076939b60b212624324df61daca31cfee89ab31cb4570f2e01b2296c28f8a721385c653b853610477395dfa84c8bd4a47210579b988cb39a3a2c755a18cc1bd73f346275a053857a265e82b441021e8be4a6224d4d03359ca241f3d7c14887a47646ca2c411139ff02fd8d3318ad02125e321b6237dee5811d423c8a9a8510786e2f6c731a10a01da3d9a5796898a7ca49222908098242196da2adf276c85713d91c840af479be33dd44a5eb0e00c6ea0f66ace750ee1db88d1ed6f050d177a8d4d6c6088a44ce2b2a490949a94b4b2a527de01514f2f3a3301a46b8f2609de770961ca6bc649fd8aa25cdac6883f60893904bfeddb91fe918bdfef655f3c8e517069c457ef5e66a650f89c7757ddf4bd252eb8bdd1c0613bdade3b21caf239f0030c93d7aa99a04305b4a17589908f2b2fac28b7981b3ae9e762258d3f881e1aec599b58884cf5cc63bdb190a6736247b98d5cb9fcd2610614e180a3298dde507fbebd8deb27dc403ec12cd41cf3fd70695f9192f71dfbf654235c6e90353490315fd6c832bde9802d0853ba985356e9db8af0566a27858c95138b2db3035d7c7a3a75dba3b647d4c995ce53d2c5b4d91dc8ada94cac2660ae2d76e22ba90317ed99b94c64fd6e32287beb800bfca12f1d2a2218ad7d1db5ca5d3b15a28269a39f7cad6b19a4b66f7e239af7f89607a8a796e0afd971daf7767d82898e207218b3bc828092d7b227d770933e0ce683636435391513bab68dad1389cd709cfd3c40e78d02454f3c51050c97d335cb8a9278364abbbcaba81e076ad8261218b934eccc8c43dff240fab4aabaa8d5c6efdba7c461f7be65bb39cdccadab8549a5b52571d0cb81fd05257f52bc54670ca95a3f890155712e5ed711a3a30a6ddb7ff721dc7d60c33917ccde9916c22a0b4b04d0af2cf4d33c4e6e10b9e3dce44cc35e553ab56df458700e79afb0220d95858da2acac25d9fec14943b4a5a1fff0bfb26a4dd03fd1d0b18b36fe593955a56e6c0b044fb29e7ae1b62415fe0edb3738f03ddadf16285f195300a2f2ee76deb793c71105df2539ba939f5340d31ede2c94a4d025862142884f01f8b598b61adddd5408ab29283531086f384921340b5dd1eb61b37b68394340723ded2dae6516f96f14f7a417ab1907ef8f3d8d002807e59119c18490349de164614cf2a4d018c482df8a54fadff007d1eb27bd59e53fb0f0af55089995afded158869ab33264e0d9ec7bf7a2abfb5b2d0686615895ea0b03f339048da25177b97eb715f87d1b76e542097067eb609b821735cd01863e577b6046e6f6ab46eeb5e81bd192f6d9586dbe510289c53f0e68887b0360b42f22b057fc969c9d4526900a413ef5d1d03f08bfb46d36078b080c9db4de95ca92f557d53820708327b4cadc8b2892f67e0abd2f158e8c14f38877a6bc6b9c4f3276af85a56859bc1a55a67fd2bec14f8b438d341d5082006a530ba240c0df3f9c5422ddddca3933d61c22138dde9184ceb36a9cbf6fb9b5fef5e3ad28aea8c716ac000560b91113db931cc3057892624067762b3a6a9b0941a5a9c4c7ef8dc2f56ac24df912b4161ccd44b5ed0e896a88f89219c826ce8f87bde318ea29d098ca286bce340d0a3ea2b03a4dc786543814d0d7e91bbd61729a3651746d1fa26c0c61c4f34605d342b5c3ac9f66ac3c78bad3d11a88b7ddb2cd19c17b8dce701d600e729e31978ed283e17bd13975471a3cc41380f61b06e6398572c969e0a97c8f0e0e1ffc1e75de3e8ad321dc86edf9aa0ae222941c5ad99472e7b497fce170223b142e1d9d710f84883ec8f038cf50403ac8bfa5be213df902c8c309038fd2b8b108378a2f35bfbd9111e646f33569271d6b03fb15ccd2e646434a6bd7d12267fb3cff5d27d2e98334a71d985bcbabddebd1423a96cd10e7887b26343f81de4cabc0c072afa62e8c7cb05c6aeed6bd1769bf89179a93f1a6cc33976e5b49095f86a79dc57f7cd358d583071d03968b122cb8d09a05fc47adec2af778011f7c542ec21fc2ac4299c316b9b112b064e15331e6cac305901a167eefdd1defcaf6816b685ba73f8b3cbecee1f82bdd2b89654323073de92f0279361ddc40b851462f2bef68bf7c6c788e0b05565c089e13c681f12679ab7ef9ba9ff896d673ef47529ac26095eec0fe4123407ab6faa661484aebaa842b7201eb6dfb528c06d760a637fbae62f1d3bc14d90c54b814df5806b6921652d24ab8e6143eada637b0cd72fa85511925f5d25b5019bc983960a35c72aef5d1235a162eea205db3afa039ad0d2e604bf6e5f0aa5b5d8b602d54635bd41d408a472636a64855314e8282d9b4bbf573350423b6d42693728633f2df5fdea4fc962764d5f80192fe48a89bfd148273cb239add4821e82f9643abd542ec7632e379c8cea7bebd05e38fb9e072b7da4c52bf436734d31fba1ec750e77db919b597124bdfa5214b2c8ecdf37ee72fc8b79da76f87306ec947a989a549238b1e6af86b9577b39aa4d7f14d3acf0d74b922082611b0eba19c2f31d3580b2505c2c15283ef06b9132264df40f716c2c541561bac5bf141f24d724920545cceede2df3f42b39d5aa37946d4b5ed98f05e1fb0e30bee5a504e93f4498f0e111d342b496ad2f4e1f4ff849c982949182dbad8a5fa4c9801369e0f9fc5680685ac05030ad4c4241faf9eb0a0e17b0b25f92004ccf3f3f5ac6bc5d61ab9e36c80a5b3397a18d538253934ee2c38bc13353534c1106bfafb9dd6d6cb96c74f050444df427c6c0ae8fd0b2952172f5f981c4cd6d23cbd4789c704e0d6f09bc12e080065c7516174ac9d4a35b5035767f959234c16fa345d631204cd16fc04323b557bc95c8ccc5953e3abab2b23c449f7f2660da91a9d322a49ce4fb65751590f6623eef1398865d64680c9156e7910a949b7f4048c7010441292de7129ce5036b030541da3d701c3fce1c711ef2e44b0e8046bca4094dea630ed38264db66adaafdbbaa81a47cc44616ce544e1419ac089e82c1be936dd76769eece46f515713c1f02649320b810a4062230def881095b3f4937fe3b0e8c6dbe3ca7818253db8ed10c6d2eedaeeb1e1ab8f3be1ff8f77209a1a6e5c21860d572e8f0300173fc6c2a2347e456ba045efc66b1abf12b2d76760aba0ec363e19c1a04e44733a3ea7bc8ad605d8e9725d0d49fc0c25d494e0344c50b616f398b342edb8a1894229c75ecd67eb20a2d7687cae4c330d13435ee91c21c74aa81054cfd8170ac3306e3fb097327717c6a0cbdc5de3d921fd7ef7a56c2e839d01a4b883fe558446cf79df45115ae624eb16c1960813dd326f3cbf8594aa969d497a53f2030e1964eae09908068ffb047e7ffa76a9d4564c424e87b4d7e412f2dad17699342f204022683428ec9295c3ca27e0ddb0c2f6e11701e9cd20969fd2f6c41a10902704f0ac8769bd1a50d1832c23d4aa888bf3c5d6bcda86761f92709fcb0c60c26f20e1ab9a0955c7a446dc5a28e9e1e371b81126b7c9588b349d0642927c3c733ce462d4b41fa86eddb5f655e5ae883ce7799b4156466c33b15f4e0f4cdd142eae599fabe121b1cd09c5fbf759726074cb015a22dc7dd33f7e1dd1ee44b9b9999cf6b7afe8b95e1331cbc12822a14a6b76411b37f65ffbff06f45ad9a19d4f7b3dc8c2ab945aed68f4d6a80bdddb98040e8fabf9ea2e33939ed1f67f68f7711d1ba6b4090d8e1ad131584f88aa1b87b0d1794ae4e9eeffae272335d065c65885e150a41a0983350131a91f648dd8ed9750e39c62b72e532def6ea17e0bffd88aaa70451cede72200c471d3a5002c5e0f6fa24180d1471824492dbdde688bfa41fb4029e138da56dfeb755a3dc949d459cbbc5a31c0aaebfade577cb36c49acf1af887f6679dee08e0e36bfa2d64938bd418cebe8c1067b011ea30f7dbd88d884d65e340493895c03c835bc0ba48e1c402839721a7e20a414691b12ef4ca1ba04fa31f71ea6a357967a29919bc7e7fe5edb61ba0eb613ccec12d7779d4cf61a6380d3dec324e84529b72deeb6bf2ae9b31323744956ab86e8d7bc0a41493c245c0ca0ad88cfea98bf6a97582645ff908de9a238ceb9c1415b68eadeb2f8ab10624f72c6559612ba7fc1323dfeb15bea4a857a0f56ec1353ced24deb1172eefe871e56575a0bc12c3d50b90bca175f125b9a3da0bf442cd46a68e06a86a547ffe5c17cfa11b977d968e4a02fbd41d07ff9e4d54d866965af5736f497b826c02f6d9d3e1e9cbd13fab1286ad5c4965502d99cf32622d97a200ac8f446cd0a477a938c0c13c9703a4dd57eecbcbc742fa1944608a93744ce7051996ee05a4239cfda769080fa6744df4ff541f1d08e784b07876778caeb692bb9e788673d9b17bf49fc5dac918d70377f1c6c587b8ba233cc12320a303b55b31d2b34a115e96a5eafc5da000152a60c3294c4817b3077df6cf4f3b60db39983e05f13e7c66e084105d287db7ba5328224be105f81cf482361c6d34632366b58484e8d25ec257da93ff08871708b6dd9ea121730964a1b8ca3e88d1063efb192853e840942130a0669a653da7d2df75522648aa574bd3a29257594a111c53b7421b60b088916063356ccf5d84fd6432eebe295c3552803800da55593b9f378fc8ffe76e2371bd072d3193c91b24adf9ec7b751630309762c578eec13a747e353128e64daa4a423933ef1e90a451973330c923cdfee5d7712d0ac9426173e69ed3e21c4b2cbc82d5fda3292b2d9a5799380030ab75cc9f823bcabdfd466e66a4f8ef611c1c7583d93470ed5c1d64217769222423cf278a34bffe5e1c513f0fcd84146f85880f415c520cab65967690184994116c5216fc10793b5c5c415896083570e317e7d7ddeb3719e333f36ca82805985976c9e4b50f355f9f194840ac79abd232f7d7d32f9a6efbedbc36b661bb782ecabbfd9ce877b745b38e77ce3e681d601763490bacf0a99d8c177aee097ed7eec233aea98077c7ac2b979e385d474805fab5aa88bcd8a964ecd4957841b07361c04408e03909bdb6cf698070725ba56209131b194cb54404694c68f6c36a071ecc559dc54e5b87c7c05264aa022c965107a3ae4cb2f0c72bd56eb113534f7590d4dcfb928264ebfa28f548becd638e42fdd4cd72429abde6d3bf20c95220a5c6a481da5ea48ed54da96b1e8577c332d98bc5a6b6d73267ca80bce40cd021d4447ae98300aa46b93c2d31a2d2246d8c45411993f7cf9215b7624817d8779a3c2db8328d10db2204bbc787500fdbc5ebc9bcd71e28038a7da15324eedd453fb6a54e921c908e3694405019695788ecca1f3b46b20bf381b859732ddd868edd1a7237ade96a05a5cf12e093438f1ef7f3b33b5cc7436835a6db765cbb14643b682b0ff9dcd4e4ef8dbf1af4097881b93880b6456dad962ab116944774b732b3f1c45a0520ab8514a0e5700cf36510c31584b061f1836b699c521ffdce1e99c0e89b5dcfea76c532f34f0f92d811440128a9bdfe03e2f4d24683d7fe8f183977ea8f2d944bf544358e0918041d55223dc07b43685381e118c6a3ba1a4fa713d1c2a4a3a97839c994f20c3013ebdddb59b65bb0011f6051f586c24eaf547bac67197cbfb47e939d5e9b7dcde3c5d4ea20b1f7c060741c76b732e4bda7ff60d96ed4821e2b971b86bba909b6939b71f8608eb662e62914c0d4b538c147a086beee2d3dafe979001300568cf821027573870356c330c6e036badeb53df3d357b97f1c7f9d7d370119e77913b12bcd79dd62c0785216292327e3a6880af7b6d7807929cd3342eba9283427020456e980d1d643f21d73522a0cb0ad4482931688409d813baeeb6ee34f654c2cf8cbe81eab8dfbae134ec60deb1b3ead5e5eecfc8cf8c804c49a988d5bffa77054d3b6fbe58bd941948de3e200e1fc316a70edbb85925d469c5e2897465d7c3e5c010646c36e09f1593e3f51735adc6bee6f9fa557169481a8695dbadd30c6f2dee99e6513c54376ede961d573c1f3cb16db9bb8185c3b908e91bb54be0225aa6d51efe24857de5891a7709bc9eee680c756461af9753bbfb475daf342e6ebcc6fb748536c58fd5b7dae3d2d8abdae2af7f8a116bdad5cb00d4a8215a349bd917ff1d450cae6d3b975f40630270d820fb983a30d602c66dad4f0c1cad0ec3e58a3315bfe59bba5018e46b956bbba8c77281aa25360bc431f499e549e8f928ba59527ac5cdcf39c07d3042e86e39d6a6ae8ef0f70a08d1fde3f7ec1bf6f66b9dad0cf8ae90428b8444168a90c0580156220318e5bf2e8b5e70e2bc6ece9cb04fa0bbfae26f607b804aa5736168509658c609c86a0cce70ae5daa08b14feab81e48c9bb3c59c1b0f62ddf690d91db364ea3286cf219e29f0f005579897d1bd9a8a1c97a41b800825c44be6c736fe3bd7047d0a2bf3124d921b7254bfb9d5993a440c0954d4c6ec53301b96e1735344e1c425a189ef90c7293dca0312772189f5c57ca07e950dd954a766d5779a191922e19966174075facb6308c5726174b9510075770f7915f3b06f658ce2a5c7b60d86ebd88574fc55cc1c02b4ac3b4320748eeea6132dca3dc6f5ac179fe71d6bec523be767cd55a9f31f6289966041c98313d957871d67fd236e24cb549ee258f7fe3171f1b09ce4b8f4d696fea15fcd087c75248bc919c76e974643797a90056c9b6398a340bacebab26a790fac76ceec942170938318fd9cc51d85817315db8580f5e34d1a00c1af6d05a5cf14e76f8e84477425ec910a3dac1e6c098c1f147c127e95b3f4bad2e01dec8c35931dce2c7bee53598b6bde7973071a0a6955d9cb9972566a80cec26f0e8ed81e7951a4efbb575ad6610a864e58422045f40d1f252564ec548b5546695e831bfa1fde8b6c5f919eeb6c8ac9e52753d0a6c678609984a35f7b19cad9d965d343a2b71f34dc84040834cc3d4e2fcce21788c995a6662684ed2a4be0e04f9acac09651a0754727b7dbd694d2a53536b13af58c89bc8f73397012d16edaf370a3bdcf4a5328e898a1626611c9f10af07b53c1deda04033e1b81101daadf8ff6df92ee534e5c8c3bbaa20b44253fabea55b28d22d2391346911816d0d8cc248a2140fbfcd8f2d0fe273d0e462acfff6244408d2c5b54e63afe35b07bf388cf2d7daa7fdc18785549b578d659486d0fb1a18d1809526af40485a1a19d2139346285d54edaf80860de4d4ccaf17529a48a69c4e4122edc0994dcfb1a90fc88ee511332d5337d4508933a67a95d0dd4a23250ba18c0c1235783de853b9011daf25daaafb7eb80b34e1f5a26cc4d3512b453fa11825a5bebc5a20d0910fe038ad381bcfdd14243566610fb1b5a3352cfa6b5be72c1704e45c9c956ee598be33456ae20458f125abc95b84b80240f89842a3138b5ccc9822bf3179799844872390fb597743cd1aa9d2b44d4759fd48e2d162ed7fb41c0bc62122f892364cd6b72927fc6061d4cf26958e1ce168a1919b6d894173dc8d85412268b0d379eba4528ce085e30955786f12d57962fe451c2d523d51cb9baca878322bb7e297b4bd87e43b7f6801cc12245a7bb8d7cfd9ac8ccc44795035eed38ad0ed080afb4c3c5485a52ee4f5e620ba11240addda1868e97c3bdcd8a83282330f2597b97030d425ff6092396afb002591b82fd07892c47c68bf3f62e4081783b7a4d67486693a735b34f2eee3b1746714ed4125aceacc82d19c9b343f54535b23619509c2cd0420c1cb025183376bac82afa30eea09ab0c9ed51a644b4ae5e5a863a63f7654adb046b295240b0e88616e53cf40aa602bb033d908a5444e5a8b78adc19930591c01946db369fc3f6850c874119197ed6d45624fb2a56890498be906d1ce405ab12ff4ac82ce1e23002c7a366128a2f758c59bb3d6bd306e7f4e8efbbd3d244b555c0a5192564390d69a9711fafb7b379ee72889f1f74cf58806bdfde655bc93d6b07831bf75cb4c71033198f2e8058f188a297a486a32835104968cf14f3d755d9bdecec0f94035c8679618a245f22d81a0461ad0e024054dbe3016f1fcf2a879ac891fd5106608ffbdcf3d11f45ccee091dda2cb13f2aa44f20f1ab83914b7b0aa2ba97b88d928a4b8cc0290f25af77e643422aae968415e4bce91a313487745c8d3c0505cef3be10c1f036a336c9eecdb3d4a6cc3d0e51cc8d0cd274f0e7242fe5f3139b383ff3f9515d9c97c000128f1522679070c725ee9fc00736a3c7441915bbe9c3c6ea7bc38e42b1d2d6f17717519c1b81961ffe69773e700549e494b90ff1e010340a9d813f2eb6df8887acda12fb90000e0e505db744670a3208f4c0e7e920f7ce35834657f4ab8c0184cab5bf9f0232a88ac0051e5798174988806eeea46858b5ed68cae539ea970199c98b3207671c4c270452761cfdc2c7119139629d19215776be4531642195195a6abfe50b50abe36bd26ac4e9b72536890598f4ccea5b5bb026c21ba3eef3f14875f10b37d783f7120bfae425dce01f2323a04515a6c9ee957f91dea4811d11b7f188ff5a272d89bc507bc2eee213e03917cd2c8c8fc27cf6ac5fc6c89dd8b629ece1f6a754feb15d595bf71bd6efc3947040427ae02fdada3d6579900ad5d668c16804ea9309ec56a54bd70502cd7a8a787833923604a3565d7b5fe2ff4e17e53cae04ec8f7a783b4fff7744826a42f38d87fc31c88239e828ea71d96cec115c0fd62a611daf325c5b310ee38c6d306358bfd6c1c6943b37aa6463083c843acf5a56c29e8ad8a2b92972d33d2ad507858986e49f9d4b578d2d02bfd5bdf320bb32fca19adcfdf3a1ceb49f3b1066b5af0165e8d449fe64a3e206270fbea7495ee99a4abc3eed2d591e4067a1a5a47655eadf13388bdf7305abf9385585130b1136a61b853b6e18118905c18629862ef04ce1f9139aeec31c2b763aacea6371067789a8f58c305fef29f4f6f8cdc789e5d7755120169e858363d8ea82ed17da2381a03ba9a4d94a8ec3bad452f278bfffb40a2879205aef144deaaebe4bcf2ca13d15911cc682f0a0cbe43c023f742dad5ea478ab83d98aeb6ec983b57c0011f22db7a8cb99f2782e7d055ec5ee8288f62d240627c95521277bfe3d2e121ceb296baed42a2fbc8819e343492a39730a110977e7d1d8054f6fad40ed6dac8c31eddda1205b087243335b323b969591237811282f7e83e6639672f9ad9fc553c37d93d946de542851b058b3020664141533e76557d75ca8fc5b0ef810ec596079e81d1460f3091bbda7a7c1efa3aed6003185ca144617f87e3a7ede9f743e6e977786f6fe53b4fea9a3619261d783f946a13c3fb9114856aa06f18b8c3be73ed7e2866370ab06f7979a58c16f7af20ed76d2d6f91b0af738cc5264d6f2c6a786aafc1c01d598bb8fa84f6a7e454d0348b8bcd41f3ae1f7b028337faf664209272fc7ec0611a8172dcc269acd9f607e26acfa24ff16d8e772dfda4283a63a73a372ef55226ad58d19144a526ecbc8419646485e042106076b482d87c9648963d2368ade1b834caa97552b417043cb900136df1c242e3f7eb56d79ebaf56bdeb6e235090f971905b7f2e182548929e025697e2edb295f3bc57b63a16f9efa707c277dccbe9ba94087596c46f533dbbd05ce3a6d605f37f1d3358776ffa50d7228d4a5237d2ed02d77a834fcfa7fc44c1102aad4e244a6cf26724aeb15dda5981cf8177623f249b8b763ba4edad621d119c4b017ada842f58708e25a503f6a3b75858eda21f429f7aa0d80f9444ec0bf5ab0cac03d0c682caa3257748598db2963fb767ee8393d7c5f631bbd4b858b236b4ce4599c576b9d8801cc9dff2af3ee5d67be05e4a5137c263b04d64f6278490f802c202f70230305157835e2d6174702ea87ecc213b267e7070ca091e77e6726d5951d6937555e6f6ff3bb8c59c9f73ce990712a59949a5071e4b9b07a42e674ae95cb0544f6722bdc59c1e8e471d4986c04cced04526e48825672ccb4430a78fffff4d2ebe953f97b541d6065a98a7aaaaaaaaed8ffaaaae1106d0e6eb05aa295befc88f891210a6745865536c5cac54244229692549abb2964c2477944a301972b960cdb411c2216c8a563645f5c99868533389a46555d814b635eae3ff3f972e83e0eaad3f9a6be0040fae9a96872786232aa3a3af1ac618bfb428cd7c717a8084bad60e4d702bca7ab2ae555b582d0d8d20451897f91ad2605eee91f9d329019cf1d8da8367b0bbbb3dd1bec88a61e179c191fa6c01ecf88211c312e300ab0cc74156db5b51d693751dfbd1fdf021d5c2939c2140d61b74621372a222a25122e1c2ebe3c6a6a6f6bbbbbb3ba010629cfea829943526f115326b5dd30865d5b2a2ac27ebfab076002a143a70eb122f898b369b6dc5995c318b5043c9169d5bd489546fac0a9bc2b2972003ab13461ea28c32ae13c8140d63580153dddd352bca7ab2aeed29b5b505c4465c3f13292ea09e8fe3865b7777d48aa42e2b63570dae271ab8da08a9c4058e4b77b7cb6229182d3696dc9278dceec32e5ab66c5ddbffbb2af84ed830fd9284c0d29a5e52841d2fe5ad47940f175dd45392f4c678ac7954ddeef64c8110774337f6ffb18e28cd44263d60298b584b9c333fb843b537e79d4bce39b6d6387d0504f3ce30012e3039f4aa0ce994450301de8ab29eac2b0f2de608012128bbaaa1a94645950c4de4d443b49112e0032a9d4bb76b8ac061bb9935c3929715653d5957fc12d6c7ffff9e77e57fa744f0007d88ab592308930ddf970d163234323864f87f14c39c7d5a5c162e276fd99aa41bcc38b6c0a2cd669b494da72041e0180faebbdb4ddf0c052676400b30785cb07462109da8ce14c12dafeeee0f3d1dc61b5a93b6ff33862494663e19ad368861eb1f72b1f2e7bc588c802220b960ed9011290a6a1933b8009821769ab090d8de7f96ccb53bac509ab9d5e56187d82c67360d4d76dddd6d18aa4c69a6fabf10d562fbff4c4294660e3308a662ce6fc953b2f7df4ca621b6a47a6b8d6cb8f16289dd97354f0e8ec063d0e45804b3a2ac27ebca7b9b73ce594594661e91496587aca5ad8396eea58fffff3860702bbf0e1b2d8b8cd40c59fa4b793897125d379272c920998f1b215217f15694f5645d77217cb6a6b6e4a5bbbbc99029cd549f32fd60e175bfacf8733ea91411e3f0ff8150261ac4feff0dc00986d588592e07dd750c09142f70090d21239a351853b06c77f7900fa5993f6088a160568f6aae45edbe6aa4436966cff7fb6004bc0ac618639c5bbabbd95a46a1f63be3ee6e2b94666ee10e325d286ce35815368565310c4ecdb1350cb41a5be3bbb229dffd4a5ab56555d81496c5ed77523d9f6d5d328258b2c26b7ef9ffe70280e9f6ec51eeb8c4136d58fdffcbb91473d3b7b19aff1fcb86d24c5d4f032cdf8f76769e865c923bd2610fb2fb6079fbf8ff0f2534b4f2b7b8d6ddad944c69a69abee4803cb37703c09b84ec171b390caf16c22ba89996a797946606cf30ee8131c637cabe59368ead71748dd36b1c5fe3fc1a0728ecf7ee09fc727158f7d41dc7b52a97aed89d7b61cf20dd7ad2cbba36f310b6a42d8ec8e7f032e7bf55f3ff2be9509ad9f3fd3c5002fe3f6f841a5b6245239c747c332335f5cffdff3d6c28cdd4f57cbf103d805db37a8b31c61877dbf296addd880132ba8e242ec618d718a234b378442695226a2c557c9382e2c17b51067892b62bca7ab2aebc921b43160c94d01325b613b2947104caf7bb3141614609163bac5e7e3ace760ad8fbea1b3b4598c7aab0292c5b03005e77378ec40b39dc4995245b7c97430a9cab1897c5fdeeee6ea916dd4a37c61d6731631fbe106be707a29e9a33bb73e6f1ed8992b1f4f1ff9f461289e4089b9e8eb6d7474b12a511bba99c730e16519a794426952182cb1aa7deeec52da705590b8104494f46ab7bf06efd3d4f91c32c901b784f8b218e9a8c68a05c0cd13a8f3b7eb83a154c62329634f4248f43ad087684305ce398f0f471146488cd022bc8f811923b2292c3e3a846850efbff6f3184d24ce1f0438be2ff322b9ab44769113c76e1fcffc16a5cdc92654a33d5d95bb25c4f36e89eeabc227470569fab730855da6cb69a99596c912872dd66deff77e8c043aaeb5f1e6753532f898a2bc8c66206f9b1e85249810848d68b209e196228fa884ecc540437f0d2d5e9e888712372bb3efeff5f97338e8cad29221a6c36ba860d163d97a5006ecc38ba526c94f08731cf3d41f410b6d8a4694811efc61e308dfc5a862472445a384ee494d0f755c345219b825d5cd50659dc11917839a71042af8e28cd4482b84afe38ae942b3760e5baf45dced852300f0613cd6870bb181508993671ec97ec7e79250930206408955717d111e70c4eb930f2faffaa224a338f1eaa9039dc345ce668055f1ebeb12a6c0acb0a91f4b4d7dad16f7a8325af407a5891347700cdaaa189d1825a6e93156528cdc4b52f4e4f0816406d8117071a8c31c638aa09001a7528aba5458f251c48bd4444aa5154342d3ad478a4fd7f18d44be97be9eec6f86d0b3f2576ba96c8ae545c8105d970c09df8cd7a5cb09afbff9584509a5994f464eca0648577aaaaaaaa8a337bdf5a95daff1f86446966324cb9862d51c46cac984e45ac57b3402522449e488f9501820792373d2db723c1125a472d406ee648ea5815368565956adb4ef0eed4d0b8015b21c319b60454b26abfbbbb7b92509af96414d16385c2b6dd3e3b2990a2ce8ab29eac6b1490b21cd54562e66c81a7d301f3824caba8c914d21b3143c0d6d4955113d412a8e753c4cb811398b2b46010f278f05801416163fe08fe3fcf645194c143bedc691221bbe15b427edd30b6d2afd8c7477d39c77004df6e47621cb16952ca51559574238a2498d24cf56f4dafa4f9012a996b915d581536856571cbaab029ecdb4c5d6663d1c7ffff1cb61c22872e6723e4520c1d442f1518bc69acdc7835e49d36ec9e2716d030a88fffff01ef8fcecc06d23d4368853c3e4470158cbb0257e002a6c88b5b8fedc724e34176cd7a39d7c7ffffa82572e5ffdbffff3bf5509ae9fb013308a7a0caff12e0e4c379f14ceec9399b90455ecbaab029acfae69cb38f10a59943b5b81e51902c083e49959a8bae2ca6254b6e68d316f5e043974b956a50ef58153685655b75c74077edbcf08562c44c52d4901f27b058b28bc2b12a6c0acbe217dd993986b86555d814961debd66505807a594f742bca7ab29a6bdb8404d4ddad43820bb025b29b2043390da51563657adf740b57c3d5fad121ca41e4f6c562654a33d5decdff5fc48d62ce3fbefdbf4c12a5994a0f64966f115343195239bcd8755dbabbdd54f9b7c45bb0375bc21155228eea85ffffff15302436214a3387c52332790f6cca9fe4869f7ae07e1cd0bcf58aff8c4ca78fffffc5b0b295ffdd4035030fd44c6f85f24d81f5758e7530b662d724fd09d6201fe3a2635a35e6fc254df00e05feae0257316163238af8308a0103ceab86fa529aa97eb744db59dbc7ff7f61cea80957336e37398ad408eaa7e12e9733054d3e2762f070a167c609d70425263c7182f39bf5b8bcb5d4f8432cd5b5efb74f27889baf1baf28ebc9bace786b075e60415c42e38e19352a88cbb3bb73a8cb8b0b7c90a674c4909161a49b8080b93881bf9b09d9ef37c36abf33eeeefe4262115fc995ac8acc07b79393d14cfd55d653dec0dadb060e54b5fdff1fb72c0df81e9596924f3227118d12274a4b48079f8527231f1c28ca48109934f460d0c1579ffbff91921ae9a9918c8d64d5485b8dd4d548628d54968998ec54555555754bdda563099408c20092540449662868464cc786d5ddff3f0a08a5994220a28a5694af92165e50f514b946388854a72ea6c80cfa04b595ba6ef75b53fb9d7177b71630a599eadbeeee96b7cfee99931cc298d8e8b101b4aa6deb1a65a4ca5bce2df43ae59c734e12a599ca10394b9baee7fbff16530a08885b1c331bb639e79c5f21946616059020a8946ddee6c40079131a68040d0371248cd31863001400070c6a94b4787808311687c5c1a0480c0484026130180c040040a140182c0a82d068aeaad17800a9362d9b755087dcf8d252e1468564365bc5a23186ea284aae53a43daa469d13895e5178dc5b2b30f6db8c5b8ee015ec4a8f461befdb5090b46549080152a5f5c6c1719314fdc262e01b2f7cb846499bac1e5a5c8b5520cd8497002e3e611b62c8d6356057eee490bdaea48a573a2d93e28830caef4f278d631235b561e51fce0ed32674e53f5140a552cb7c0d081c6388e0e53a5effafcadb1e9bb7b2e6da0865e6d8d3fa48fdd8523785e51f71ea1a27128cee1c5d7b313a04ba1da2b55f9be41aedc5ca165f290751cf0590cd0d4fda27e79761ae35b87b26687011cf31c28ad9d213cb6344da3e3f74b2b3537c3c235bd069f7683ed20d21d4b088ed678cb5fa2a8b5490f1b7bf61818323e03ed030f223019ad1775bb4d2eb1c7b0de8168040063925f1d163b92332dc60a11e8fa243ab93c61fa4c3ddd46864a2d2308e8fc72fba05207a5cff261d3ce57fd7f153a8add024690786bf3c69bd9e960280f148bfdd347b2c4e8917a4c69317fbdbe47b65fe1897ca90aaf0343c7295c6066ae25db25a775eb1efc34f746a1c8bcbd876ca1a2cda61554b1aa040360a9311f60f9eb859eb6db7cc115a2a1a8a6941cbebcaf6f168f51d1ad31f04589a44a940b87979d0107e60ec29246a8ccc420d8fcb4e3ea5c9ad027bd880c3948e6b7159b4a8a37f4cd55e4911a95427b5d31f4d2e256e27bced380dd8d246cc8829b585e8f429a07f12dd81c90e4aef2cc53877cd856996c1478ea4231f03dbb9cde6d8de7297ec59b50b45e032049ebd53d24e41279dbfbc974d67b9938564044581bd86a8ef7d6cdc08a29870510420372809173a9fafcedcec4568d34eab4f895b5024e18176a716b453bba8d17c4aa98641e0ade052f9272f7b879a028af2df79c3a056ba73b0dcc0ab1dbfbb3ecf2a45e574a9214208b332241ef642a9b3a700c8f059c4882a07b51c53c0c4c948530ddf491f68f199e39d885c90506d27e3097393314eaf300cd5dc2bdd5d2f3e57a48999a80f83cbf9c362786b75587cc2c110b6df4957d4e698db840b57f3990871489c67d1bb9709db69cb1d53706044b9c4beb1b1adb6a704c44f90b01ba8672f30afb5ce0a199edd47bcb8e111e93344e453b4d2244f262a824b6193cf528a7639103ac8b8f2a9bf48c2343523120f50b2cd953e8adc09dcbbc14975bd51999a584ada2ca8feb61edad30207495f2dceb01a900e462e6039dc910c907c662c6d15f58cd2e8df6e596b650c42f533dd0d81d1c710855ed25db4128e89420b34545bcd5aa3e8d1c07cf9370547599d25c9fc3535068d70d49bf3178596ab1957e640d3c1c9ec30ab08cf814acf3bb05ca64ff945703c097977487372036f25de21d993cf004ee36e30c75fe07c8e237a9a01dbb35788df885ddc18fa956f48bfe02d9850367135038276ad7ac5cfdd5aeaa97facc69751b681d13c3fcf0f0452f8018b861cc8eb837bdb995d8d04681627cc7ad38588b3024e671d9ad1941ac2b52ff4a94ff9ec9119c77e26f4f16b2da84947bae7c04807e6d7896a4eb000169b8967d635e87bccbb421fdf28d726084833494e7f3dd1f6aa5243fb3ac63ed839d08a4bcdc162b3595e9d5ebf4459d602799f8bfbd5586690b47ed354eab043c80b429f3b4ca1c0e42edb66179c066fac66bde8536217815d0c77fee1a07f688191d7f3b54a0584cb0b21db23e415fe7ad635084a5e405de7c966d880a6746f69be38a57145cbc3f38f131b5b21e5bdf0498a9e2bb907d5aa73183bbd5e33089b4311958b675a65fc285b577710993c27a5231644a4e96ae90a487ee4b5f25548fd8b6d66f546e5ebf31b5b22d90630e8e9505e9479a09a0b589aeb9267946fb14fe12e4b5bf927e8662b339ca794bcebe7924a432b6b215078fc811dd261095648c9f765445061abd52791a68bd1ae7496b0272b2185476bc1989a6dba82cd1e6595ba632c3c2539e2cc4490fddc1983701b68a2790f7ff8a7b6f3504497669a34a6b5df26ac7103b1be2d273be43d06cfe8503da2511ac3b9093abf2e5a23c034eb8629158e83605bf704cea275cba29d285a1868e5d6848115adca25851956892766d702a8f8df05f238a80c2a402072804596d9116cd5da5f2fe3f834001ac6bb2609735f829a9729c75aace0d836b8065dbf5c8df4e036076bdbc0eec5596eb839aa9f283ab945c837d4e89aec18423f40e74d8cf50c31ff70db8457a528c77a3e540551713c65e776e3272224a12720a9b9e434ddd663f81bf7a6ee289d5fc5c00297698a96aba2e380fea4cb7fcbfc438db3b037c9112df4f081eff2c5bf86c1c8ca5e279d4970ce10ac5b53260a9c2fefd849d37021d84db480268086fe18e2e5b029c72a3852341224da2635397b4231b8fbd5efb58bf18e9920bae9463a4980e06a20cf274249c6005bbcfe7ce8dd18c24732cdb4ac1bf83d11b657c55bf551d7b85cc81352d7a7c53e89bb842e069f8b2c124a6af601f9b69bcef5e6740a0ce981c6c7714b376ac1ed6ca411b2609a0d4efddf50162a9a18c4213803c0ed22e26c04dce7b9032a2229a2c083b04df26a7f4c657e471792b80fb7e08dcdd5298ea718e1805344b41e197f95e6edea21c20a06b24c5f28922d3d784a3db550c565354db5df30631d0935751bcf76da87e0e9a011f121f4da51412404c9d533ea8f1b47c2a766ba97b8751079d3abc46d9e1afb396038767c27d5737cd76cfd19027c2517ace2a75d1f2d7da2387c4a568d9c5904311c9fef21808ab53b5830728ccff0c1a15829d66e8dc39a93d514b76b89e25e9d555cb043cb9d3a0e67a77c8ef637ae9e59c1be788c702cc609d62950454a5d9c578b09ceda25e6bf646822edea84c8e59816c400a69df0178f8bda50a373bfb87b442a100764cf27fa86762ddb6f1c5aa225543fb481039a155063cef8bb3ff2e592becc16f12b8f93d10ca7bd85a644beaf8ce26b7b20a3ca57a6780188e2f69f9d7eab8449aa11e447e81c35a2a2c7771cdb03c1d4f793c81db60088efa7525a972757d5d2eb1776feba731f8830be46903f5f1411464cfc60e10d96c79d2503bd6befe426afa55f50d91c298ecb41f2cc0f06dd7fb4a868b05a8a9ef77b074e8fba84274b104ce035f24e70b17b13a5dddc4b3ae4afb24ae106e84657b1ff6c49fa936154f1752f996f6ea52be089cb271a71c6f783c5997e91a0552fac57f96413a7df75af1f4f8a66abe2e4135ccbc3d8d8d6fca963f9f533e50307e01cc56d5bdf4feacc7a481144a30d3c7c55c6ab2a5c9ac38d13b0cb04ecc556f816d52db834b498546e7824764d532fc61e67023e4d178d2707147b0092326e06550ccb01985b2a541513973be2ddf77c2c98099f8990cf5b973b5286c9544566989fb38bc5247f2d5a1236d6beb0cb0f494e67b19d4dac0e73700df4781b55eaba76e47bf70066d446295b25417f50d0814e4b3530c7024fddb0944d737fced13c0d7468f2a1cae92ba079f3a212f1c0875d95c9f86399f8bc530c36c8a3da191668369fd1b569cb210f73b09820cf233bbe24d37d370caad446009b2e668b8ebf147e6fee3cac7025d0a2ead12c4e9e4d048658313f79ad5a630264c542096ffffcafc920a94d3ded10ef4a19a576f6e80444ec71712de5894aaf9d467b4a712365766e2347beb00c4b98017a2fd4955a3ab7285639dd392b77110c9c1c1b2d12b20a6db882c00473798ffe2d7b3ab67acdf9581ac9484a5000817f0b57a48b9ece8473f75d62ed4be91653391c90280e4be7b125992693464a0a244222174232851f3db684db69d822b1b8c75f36af99cd6babc646e9796a2a2e1551f9862c5df57abbcc8b6873a900b24ce7abea3c9078941bc4807f073d624a9514c8b2146fb8ed92221633ea1b5444cd927035792ae8e2c22a206cac941f7da4616aa0e32cb533bcfc704b421a5a676a2e93b0505c95b550d4c544e00267dbb6f72c08576e2350e1f469f3e98483749111862081de8772d4b56e7a49e96a234abe8eeea3e116b5c24746bdaef6b7d3493275a86ddaff53e254618fb4a19fb07b06e30ae735850e508df75c9f19de0ef29821309f3215956c124129bf0e4098b807229ce8086074b272accd3281655a57a501733a1da0399981da2ec635447792f9e548724a19f09c3fdbb2b78e747385b38d78a995c203d80254b4994420fdd4e01a32d812921ecc8b9c7d183ddad7d23d1f49477977a961f6ed52ee658fc49db2bead3b00d4445d8cb97b6af667936aac950647d04f7dec399d5bb2afa1d386b5c29a41583892242fac073acee0c7740470225faea9d56a87c6215b60c2c7ca9734afe6bf501ef2b16a25b8c64276475862d9a795aa5e5e334442f9a253d1aa5a930b88a83970585559b7ee75ca8a2fe119d85d7b8249c9ca40a079b1b873568e1b5551ca752b629be3e06ff7eb0faa4b490e11f169d15fc97c4dfa13f10fa031ae24e8080137e5b3690ed6ab8a7ecf87c7577fc2d8ad055c8214e103cc8920ec43fadb5e332b4e05261adaa0a2413b37b1ff24c2b81ef63b9bdad754cd1737d28904bb5670779d624f5bc1068cd0801e6001b8f0306d27a5957f15f60d1f3b443ec1b2d9802710178009518133e01a4fa23257e2302ff1ddff9750f6fc024a837c6af43b46a17aecf7bcc0a5d980bf7e91c36b631cab928e7652a3e5395289179e82399b02c978398c4e4796c2efa5a6cf4fd704ede56334970e33d447d6146bdbcdfeef36d319c0d40d3cadfe267a4bc688acbc30c9fc52689aaf22b132504da6328d3d259c692c8614c6f114c2136c651b2849ed57681cbce5d7ec868975d13f3b22e2bcb18c369a150158f43c588f848518a1c3503f525491444e881a8696f16b519af71ccdd9780c76e973e452fa293057a143030a0b4e6bd895823cf05a6dc82cb945740fa2e2e85dd61fcea1f0fa746c15674086a9839964f7382b9a0ffe83fa03816808fe035a21d6a7ca3675d7b1bb7c16eadd24b159d2d42ac8cfdc552389d1980bfda9ecf64ea08426a51851b7d24f05511f813af01fb2d15ec806d423e72ed88249a22b5c3428e0e51b9d1d6b3b180430513c2b90157403a533edc59300a243854217afd93138c291a9d91c861a66597f1c0a75708ce831bfe516e8a23f9eead92a0086d518d7da90b5e096652efffb956a443fa022a07dfd5aca652255b4bbfa4d6e7bb38778a0ecf9441718c9fd6e352c12b38112bb9575bfd5bafbfcc2bee0b8d36cbdaf4d8a279fb2f2c4688d7900602553a9d939afb7bded4c3c37a5d2fb7bd02ea964bd9a69a5c263234759936adf5f51f07cd2cdaeb75dd5ff76b4902adfc34dfa2ba87d1eb5bf0bb7d3aa9ea8308dc0156b5ee68c85c67cb39352aa13391e126a2308ca6a4d75e5110b0e9ae17cb0c50de55f41dc61f0cd5ca6e79715841d7bc10692cd9881858bc51c94e9229bf1992361e2e2bf02015ba3a9b1cb2f04f2ec93a44956c4af931d28f597f0c6ca20278ea4d5888d53971a147aea2cfa9aba84a0e82fed663cc3f65ee777e3e125eddc757ae872a6b7dc2560d07201882badc1fd2affe4b7775ef80d45ff2feb7cb12a9121ddd54807c1587bea92c8ec3bb40d154828071b6e417fbd5511957577aacf68db03863d4776bac2cafffeb84b7b9182131aabddc536355432bcf0f696a9aa126619489e1c2f837e56090a568740e2d036e1ad58ddc669f3eaeb25d781b0ca4a2309e5ffb034be64faccff6797b8a4fc845ba4f183033f1ab983220670ffc2231999ae83ffadb834c048dbb4d7fa5ccc3bd941084927cabb0c7ae5e10f7fdcd441232723b42a53c91c716cfd60bd1371d858f58b717353649911cd818916db570b2c071169ebb7653c9c590d438783eaab595ea2702f0ce7b618fb342fd2e44324ab75a30a4e6d5d41dd8d926328e96f99d871e4a368dbdd0b747bb8532b48be47f39a153c69b843532a8e93d7af5d643312d8e5b5a0c4a25f42219acb58f2a70185476e6e08384c5c6f49b77819579789c11ad80cc72568b6c9e9ca2dce874dabd6bba4de1b94886a49fec7cc5493534bee914bb968949d4e9831174104147cb09ecb22bc02aa12f5f38d0b5f26f0053046901c43f244c279943ba89b11e1f1c79b935d56d06f6bef29bb9aceaebb31884f5044cf3a013a91e663af156b02a26eb6f83c8cfabb0ba9646523a610d6fc98009973a394195fe11da460de87f866fe5573402d801d4f263ff9965a7280e28bbac1fb3a91532b116666f1014580a361bcd554368c3ca907f8255487d14c049ee329f36d1c965f0fb871c65390ce44bb2842a5678922df7aadef656b933226b0e5f49f1a87934e662483344f5b90192273be59a78d50521104002e0bb4d70665d566e48f7448cc15fea67bc89905dd64d560c9edc3772d7fd0fe79f04e1e16e153b4c198788d4958a48c5bf6907e77553abddebc4d936111042d296b4a8cc2094514829e441437056cd6627d8e24d3402595c750e0d6bdaccf61142b9eb40d792fc1263427c75c4804ff7a085386eb563553020d5a2d91f2b938c451d0e1e46a5e477f6fdf6230c4eafbddeb1e129692601de45a6c59a2902133e5f6a230745579e1ee33275b7949954beb877d6213b8f9e936bb6029697b62096eca3b90903a67767f7171721fad61db7398a629fc90e9e847224950390feb0d01424edb4a4e35b638e838aa595cb525d25225e5b021ec60f55019655ea536c72983be55d13bd4e96587c7ed40f3b4f505f0768d27f3233e3826a779b6da96ea128905ab30ae2dd9a9274f0d2b6d99e1d25bba5b08dce4414451e17b3fa4c6859ba8c4afc647dbedc69b6fc8b76d45f0224eba4fd5c40dba0b542bf719df330757f504b21a1aad7f1d401e2a5f83d2c6d2def10155855e5956be4409a483234b7c34f32756ffc36f090c50992fe760a384fe8b8b759e16c0e65b8ce2469aad7cc75aa3126b186eb7be7f494dac0d612e66872435f5ae50ab6971f21e6e8e22a4029135a61b7984c9f5f7c4e18a0410c3badf31e95fac7cfa3d154312c3a4682813c11462febbe9dab5402b434a4f05e49f052ff85a0d4f45bb7192106f45466f5b20c3c70847587331ac70b6d0f4bb56230fa6e66c50038cfb4624fac635162f6fb87cc1cec3726c00722b670a1efea68938df478446285c70505ffceb73aa508ecd914b96c4918556a269d38f495a8251b2b0be5adb91bfec46f849b060967363139e2d45f127b22b0f8439cea55ae51766bb3b92cfca3f4180bc8a1ef7f548bd86c4cc348b8544cb3db6ee5cffaf1929356d9288cb80407e847082711ea1d233184ae64a78a6a46ae59a4510ca760ab19d7d4e0ca3059ec005c0e864e5589b654150b015d2d314b519eafcc0b80c768df4e54836a95ea3ba878b035b0a05ac8826c5306e3659bd8126f5a261dae5b3851472b3cb8e118dab83603a556232584de2ab5d5cfac6d967120e469e0a249c2a84e7c2aa22391db067a3a275dc2a092b81d30f0fdcbc34faaf08bda5adca3346ec6454a80d366e899c2f7cadb8f4033ef6801f4ce7505fb9f5e9297932d4b73ff34b69074d344473edea2d1c4925f5c2b53c8436839466313c99b431c4a191275219dcca464842fd4a8352f4bc28ca6816e5564428a218d1396eb5f4fb77fd978bb38af36fb6e95a205e34cd60cfdc31d8d3139427e3707d7c278e09dc9b8a779e465269b1a008776d0858f259f86cb188578370b958c4bb8b76a169daeffc32533e6dd1c2de30d379e0bfb66a448b0f3060859cf1e061ee028af35bf19db4166d67846564c600ee1b32ed8ce2f2804d5646aad46f6f8777af521c4dd6a0bb77abcdb88c74ed3e9aa987517d220108b1a922ebe9bdd0598967c5d22003d678dfb92172a40a02c641e9346e75e2312cd96e7ac7691062d74ed5b2011007f14b9d6efae4e95b3ac039efac873f07191ce29a3a77aac664ae8246c66268b102f78c60330563d7a56c026214a8af6f2dab6207bcfa4f7edba740107d51fc2eb52afa264c2ddfe1827d1a1d7748cb4445b2f45b4bf953239bf7e60864399f74f5d24c1561c1176b8b564bfe086fe44fe158da0ec3a66ebcda0345bf73b5dc84a4698bc1c3b5bf5181f49cf3c818972d1d5b58802a4b7314b83be9d99ec89bb4015c386d96e87e99b3ced7eee25b9a7a22986e0843d77aef9caca912b5d0c88f55061380ea1529cdf84a222eff2e5864596c8cd9124646384f0cff8ce257aa2eee2dea3c3845c968e7b1c794963ab7e0908141a0d262886a99bb5fb9cc2ecd67c5574ffb4e7cef3e257c8aacc503f343ca9f02380b6e2726d982d1591818417dfa0ecca45fde0ed31a94b84e858415e52e22f5b9766a6daa8ec656da88c25c5c79700a6b9a107647fbfc45bd95232a2bdf60db1fb6bdf309bdb0ccccad1f11efb5dc1711d7cc3edc0caf0ca1909cc8283cf2a0042aec2a386cf7460c95d20944ab34213b702d777f31df7d266c3b12e63de1627802154a13eca949db37729d8d5ea9638b3e2706bcfdc5f20f2f822940ccc657083040464a9f93aba30924406880913b6f50cc7c28ca331d8d1d02f0e1def8a485517c0419fc11c39e23914fad2699e57923472d7145fd0f06afc16b379d8bb8a69cd0273254d470cf70030ce3fa02f1baa7213f2c34953515ac2e0ec98348f36fca03d91e8697cfd74614cfccd74c7e4798cb3d28bfc961136e2ac71ea2aebacb81bbb0829a0c07f672be14b67c03d8d9f31f0e2f079bc71de86b8b96a0d04e057fda6d35a7bdb49db820fd89817a7a9a28a5521eaa2d59858b96cb65a380b589ad04836f715661cd76a7eb5fa6c9f6d0a2d59634a960fc11538d24812af0cd6afc0d4b093f1801b6d001c24ce443e21590c524d303000805b45bdbb0e03447da1360ac21ad213a6027bc06c3e10587b4e26967b33564b96b530a58e6a5a8051ca179fbb5547e2e57a0d43287cd018565b6b75ee05ae07b8016948de2c4ca919a920991e025149b093a8c2fdc9ff78c7cc9406c25da988d7193b2bbe45cd379fed3a6ca7c322b05e4bbe017f1f2fdd3b2dff9d641e99a3d566d6134550196467e4326a20f8d1b090b8009c3633d80e776b2179d52854d461e1a8929d1e5096dc8743cdd1cfe7ca31ae14ad046ca127e58a3097e81c1dc164bf8e17d4829cd8de665a551a0b1de6c4d5e094ea71ee568863068de987841de2327e4b70d7b87d846fc1e05d1939bca72c93146344659f71332aa2166e1ff3417077cb28429b10d1c22da1a2ffe7c673ce6ce8fa1e66f22b1e28fbe63a915a507b095a8aab9bd16244cd369d39451dbb5db4145a583f48c2d2a7365424c4e77ad4999163905a029b4ae74f9a8720f310a7d4012a44aec6af7f92c71f53f5e9a9d92bb8329157068338468af607b2e87c5e3a455459dc62ecd7a00fc3a18cfc04b5e46c352bd863720b26a9369ca080274574e2766041555b8cc21515907e700c78a89e0f619a757e999b034773ba04951e3f7b70ddb668d91c8c0698815e16abb03d03ca9813aabf069f6ea2a1583eb4ef5288d43257e704ae86f80404db7822f605e894c5997382d6adf78ad1d86fec29589e76e887142ca7caf53df09fd1390b84365b0ba89bebaa1aca0fc5510538954961b137296bcfa152db73535223a74a4ebbf9d25eb30c726e450229ec2bf5a7d06026856c6281c0a62e534a486ea8f80fd2ccf5356a8e50f54fd2677db304d5220fbfcd29548d41b91a9971e84594ee5bef6577bec7127915eb0d24ac0c82b0d207e565d5b130d4bf2f1b50230b381934c79ef5cea9f8ec62c7344b17c91b3fbbf0f85753f70ebdd44d7629623304549ba870cb87bac1b9343d114855e38c4d9ada3d145541990aeb857eab77e2f70e400ff1a8bb12a429b3647749339e20a044d26bd685974d10be43e38266dc96e9c543670040e9443eda9b180d4a8a7d95f7d05ead92a4ac3d23723de7658e57e3d5693a945dac2d5962852c65fe9eb597ea9b06b3395f5d1132bc756c03a1b5da16d1e87705053bd9ce85a9d73412ee9aab365298c5cc7b0dc456282424934ed9746e084c088c62ce9467c5b0b06338b6dd9215b763372b481fafbabba955234d57fb24d9e93bd9e06afca8855a6b057f998b3e58f3d5902dd95cca4b81591a7bd3c8ce56b2915d27dcd51679cf75d43925efb841a040bf357f0d0cc86abcafe2456937483f7d5b58ddd0dac538fa3fe2342ae65669f760a288f9f523bbfd1e24dbc76b196aa82164a287a63a2970b881454a346aaa0dec14f963cc01bf266e801828d44584e73d3b94320fcaa79691c947539aca8a24bf6d9c9c693876e5f9cf7cc807051bb4d4c21b53bd5da722bf7f58a36b964d03786e4f165e04faf96b68a74e0f09a628f297cd9c348275c32d1dfe014aedbd76714faef4d670835d13d71bc14a868a5249d5c94c8f9a698afdca73b3a0b8808c4ed165f9a5552a9774963112457136bc5be16f2c70510dacccbe8a8dae3b917518f6af4a772d573e18b947a8af60a601823eb85ca0d83d429d7ef7cd186b6f555203a9729c067e422178ac293c12158962c6c03842f021ac12960562090d1930e50943f6b202ede345de177ed4ed3b36f485cc931ca5eb7bb0efe1be5bfa857a41381c089f7c31ab085328e55aaff6455cd76bdf74cec0ad2a706c47222ff2aaa7de08e034f88fdb6c9265b4a29a59452060f064f05e8056fe9929f4ff23cb951a8526506cdb77ea32d50a8e2350d30dfd61da62ebc4e6eb2a432f6d8715dc1376b49e5eba796c7d66bcfadaed88e55d08d22ac42d349a0c736271980c342bbea6cad5334dfac3fef37eb37fdeb60db21ea53fca9befb767e4c807dfa1011ccc62113037aede8fa0875db05e091c8f49f3a6680e9c8fa10127c18e2c310128c48301f307d82286c1c8eeed8b4b3761405c2911d7958a2322a53b59487274e4960c4a06f735d8ad7a4d2086b243e3b0ce86bff94e8db98f08baaa331e85f578200d37ba309f37712d276eb49d5f1f873c276eb2e36055d40a03abee3766fbe0c76bbddf62bc6a027d0d7fee3f9f5f99e6f9f3fde68c2f6ebb5ea288c18291ee79194c18cbf30e6d66ef7495777dd5eaf1e23e98e3f4a736b0783578192086b6b558cf17d0d773a9fafa3752c924915063d12104186e973249201a62318d62740ffdb19323d86b5adb5adddee2b1d79b0c9a0153fcd39270913ac3e4511767392f0ed38518281528abf5efdc784f9b583bd734ad5ed18e38e247debdfce6ece4942f50e6e9500d46f55013c6ab09b9384e954ed2f65e1ef18c3c4d7516b521b7c2c4c9f63d57427a4d2d26eb77bd2a25d8f419fc4fea3a4a9ff589faf02d3579faf441f8fa41d2140abbd38574defaedbbba1edbb51706e551d7a77ea2675e10d476ce709c9b945e2276047d269cfdcc20ea356af7834c13a75183a9f9f801d61d09fec30ee6e97f4934713b05787f143fdb6dbed7e97f809e01146dded766fc71feab8dd6ef7e16eb7fb3a9223d8ed76afa34ffed011c9d365afab4f479d12fd3b924c33fec77a0c134f3da9baf730ee6eb75b7bdd8f1d91ee6e8e2aa04e794cb79a3e7d822e9474da6eb77b3a6a65ea79a14a7053880b7bc2f0525532bc33be513c69664aa03ca7f9d9c0565a6b6dc38dbe23377e7829a648c1a1e6879427301a3b8ae8e0338315192bb12d3562778ee7799e1785a40a4d1db4a2744520ea4bc8961874e4889385871a58a21c3132b342738351394dd92f1b5821f6c2b2318354a13903121c7632c49adb9c1ee004e3032d8d0b61d6c4a0e5c0522bd346cc6c5d34ba72be72846c25192a6709c207f69ef09495f6d219e79c73c6b203c9e76c85735416d09c78a05f727659da861cb6160491fa9a210c980db46e121b1ee440eed54a6b1cd3ca87d5102bac2dc46bba57ceb060396d3569af1abb26298a95f31496016d870796730e2ffbb53515f62009db4c59e51ea2c072ce5987a5b185c5c985fc14e2c29e708642e4d01e58df7703dfd05c36ec9448648e95225934cc98c1e2a4c79490106c8cf1d8820108a719dcbc91e2c3cd072288e3a508950f1fd28cdca0851b865c2a62419a3cc881dca93cc881bccae9172cb2b04748539c1c3abc6e13d61286858700a283e389d5cd063872ce19890ecfcb996a4e7d4d894d8d4d954d9d4da5e170ca0ab03a6269c8c25b6bad776455a1b9c585e5c50eb01c84f7de7bef1d984ae692cab173e50712ec39799003b9df9cd6749855c656396e6c9db5d65aeb2f3055689299e10c7ca179d72d87f94e799ee7a1d1aa42334ad797d818129ab29c6f50ac0de5fb0a2cecd13da692d868c0f2c2ce9a929c73b6a17b6435e51db308980f6b15e2c29e30d4509e3170669f51f3a4425cd813864fb58a191f39e79c438be79c330e1daad0d48ae2bdc0d1e520543583f2eaaaf185c52c3b612a32990a4f210085302d64de10b1b1a6254d142968b059ce535e235a588295c52a889919179048299b52662fa4d0bc6c28d953daa3d20526c4ca856bb5ad9688588098f51045446dcdeac992303435ed2b2aecb1c156b69293af88e11757c6c23651c07ec0e94aeb41447b29e1f182f274b5d65aefc0aad004436676811d5a0ec218638cb1a8d010c5a13144091324374c512f4faed8b8e0799e6725aa0a4d1d3e866029c9ca19b0b2b51583b132ca0e0d8d0894a3acaf7cc656b87a54b3a979430e0f7220c7e15b67adb5d63e7455a1f9855ff820860b71614f185a5d1e39e73cc40acd9d33946faf365f61cf5c43c32c076160a69cb9b141a8a992b3508554716ab86b1ae7d9b5eb8aead2f1f365ea862e555e806839d124376429bb6d74641bb6daf03ccff3920ca942134b49d6169705926091e2f9408b0c8e140e4da684d1209f3d0ce41d75f5ef08f28497f0eff2b8bf8bb3847ff4e6e23cba5f0924eb493dbe9d82483dbe470bbe5081e98d88befd13fd8ea4ad033109e7b89194f1382fe15714a3fc48bc263cca8f4437218e4b23584186fbac215c17dde623d82b562775fc7a093149c9fe2091257cc745d1667331898fff884a1d8ce4895157bfc7af9b709b97487ee867a965f7624fdfc7c7250fdcdd472499dec74571e90317dd821d9081c293f8b8530a177d042b8a4f7ee45e41f71e9164fa1eff7c4423a2bbb8820c0f5c1c771eb8e8e2e89404b799dec5a524db93e0760a1fbf8e134d8ce4f5126f93d1236adf587cf2230fc62437e711939464bc55a2494936075d474c02475dd55510c484369f7f9de66c5e08a4aede4692c9366d22dee24cb28ee9cbc84cb8aad3e6b3bba7064d1ffa219c4fde7e7efed56fd4d5cf9f4f1029467decd99132fe3ce72ae2cff3a8ab3bfee9781ec9251df1c98f76becc32bd9e5d0b923ff1b3fcd2f2dffc6c37193b62f6273f5a41860374c61de83ba3d3ac21507ff2a31d9f479febaaf6efbbe988f3288b641593643d2e25a5907dfb1e5d909d8e3094ac2efca414b26bd7a30bb2ef11468cf0a92e7c3a82a54bffadade9eacf1a34f7d6596bd1a2e0a6aec1f036ab8a15412b705668c807ea4655450e22fa6e5a67d7d1c93fd6ab1d9a12e2b04854837c070d4109f5a894e2b5ef3197dd8eb80d4d09adcd37ef8f7e2ac8a3cd75f5ef6d86b7f91fcfed5655648cb348a9125dfbc93e9f29b7b5bbd8a31e993e8be4d267d72299fdf6e59b8bebd86d48b7ce4db36f6dc2fc3cdee8cf1984abcfb086b5d65a6b69a5420ce5a0eabed65df53beae8db71522f59ca1e849f6596acf7448d5d5749ecd5f55d7a6facd61393741583fe2dd9e6f644281156f0a044b0ad306b089e7b62f5db6f9deff883649dcaa84cbbd6b16fd4d5ab49fa4e3576edd397e8d2d31208aea75e7dfbd6d37c5bd259cf2fd0e9acdb3c46f53ccae0698ceab6915cca696f73bc455db535ebea6b9cd3da27282655a78e41a48ce9d674c4baaac017d709d66cc23c81aedda7a01e93aae7d18278d7a01f3cce5183f926d0f4eb79c612b02f62d4c7a3b52246ca4bd8b5679164fa3c7f89f41c8fba6ab361512242e8ddee0f21f6bb2f98cb9ec5189e5f18d975da2df644a5f95a544af176d4d5af172ca12255c7e30573d9b32761d7d5cfe3985d571ffb1ec9ea7be9eb579c382425ac0ebea83d385bebf883547dfcc17e6fd97707209ce59b45cf27b8f1c6f72798a95ff10af200f8595ea97aadb7f92dbd90bcf5ebdfce0ffd10da2add493afbd775f5c9eda0ef7182255ca4ebb691b479f5adab6ff36f484a9874c78b048a4950d8595b5b5bc339bb4dc4f7e623ec55242fb9f4365b92aeeaea83180841ffaa67723bde23597fbb57a3fc0824ef7f1ee547d701e0b8346b08365f9a3584ea37af3e82ad22769b98dd139564bc9d3f95c4a9777cd30066429bd5818b5e502e2d67470146418e0b6fd43eb2d38d58bfe3dd7fefbd3731d6628c2fa698e28bf1a423a65c18638c31c634bfd9b1d65a6b6dc5855f3dc6feea2754b7e35093903ecf4f33326d8ad0a8b1153c61da396647d2da91720de120fc7e9657a27e3a53a5b71cbe09e4d488d953aeb20a98a75c4fd1beb4a2e4c96dc5aca472f6d689e8eeff05e5fae9942ad13525a0a74e9d9e5ed15987b2c2595ad1a4a6547ebaf59b751c96b78ec2ab8a59768179eb406fb73c9559a4442d28012ae5afd73bd67a6bf5112a9d32595c914e9945601d95f653b7930575ccc23781b8688b4a43389d1a992c54a04e4f8dcca2e92c54a05366d1747a5576ed9e725d3de59a2c5498b3684af93b2a5d9172cda239d4249ccfc34565b2983f47af348474050a55b24c21aa02852a5c66d07cb2443f4b32459e0ca7e0286e4d787f966488fc0b7808cdfd2213e47159645ad0f2659f40b4524b2bad14db4aebc5bbde6a2badd4d24a2bfd284833ddd4a3975e5ae9a596567aa9a5955a5a69a5d8565a2fdef5aee096c5695500e66c0ac0dc5cc19caf259c787bcbb9bd53800230972911270073db04604e8f71a19edb24a802fded2498b3360198f35a94a08e718d4004604ed7b1253c2200735e084ebc70020463521f70d3e38131617a3ae0c5c381314e466c80c4cf72cc11129ea701132898f8598eb132918131347830b07381bdd366a72ab7b0402de1a54405c05c1633a5870245ec1056e5cd981a54da1ccd29b4ab5e7c5759c504ae18279f04c05cae62b04a44404c90db096272801e04c09cb63c7378906e6b6e1e3601cc691f311f17602ed7305478aa4ac881396db5741e400298cb340c92f0280c91efc3f4b079de083a57228491d2a9724003be2b068039af0518c209ab588039afc502324f199e2a05b0b02bb6f7c5236d2ac09ca76d08604e7bde170f84045c30595870002330a72f98ad112c30402d401198cb3e477c88866e7845000d064810cf95e709008c140fa90390d33784131e01c008e6740827bc126d0827243da7c9f4785ed61f8039ed0198cb148c44f103e6b415a2cab6b967f34e8039af0af1e46301e8f916e96b1325c09cae42e0f020096a16b807869e9e1c2484d8f1d8353b79841d03c32f60b71d9d9fe5172a3a399d45ca93c57305f2fc2cbf64f1dc2e7804ac9a5305b6d9b6ed67f98588cdfba263c4fe3263a785ae56cd56a9c837a3e52b15f866b47c47941961c190a0ae6739fc4892033956a094399a5368d35ba5955a5a6da5957e14a4996eead14b2fadf4524b2bbdb4e8467b80436442ec57af303ac28e9b2cf06ba2a220be7a5151d50f2222fdb3f4d2f57a0f81391d6a855a679de990171bb6b641606e5b2f572ee8bda900c0dca640800ca06616f20bdad620fbd9f8de3aad12a2ebd7165e6ab8c06f9de2aa21604ed32d04e6b4cf522c2dbff53a03055dd41643d54bf3be14b616f583efbbddbecf368bf22cca0e7af0ad9847da5180df9a2fff5c151de4b107abe63ec102b7efd58fbdb5ceb3487b9e45791669c7b328cfa2ec9e0952bf76b7534509d0451e730394313f67d0fde1d303e6b405261b1cb85c59d2a6c6d8d9d19c42db00604eff6774805b058180b9ed731655d0057d17d6d6eab9972fb5950073b69c776f60b75b09f8a9d7122d7c6ac8155ef580b94d02cce9902be41971afc29cb61f0f5fd501c19caea012f08ade28a8048401cccda2ecf7bb4734053df75e679db5cf599447bbb5beaab3287b9d457a1665a7b328fb9c457ab4d9abb61f90efa330dcc03c6e16dd3cfecf52ccc757c73f4bb12a1a0474819f7a16e70b600e8435e1ed672906f533df9eba531f6bbe17d605eda12092756c2faeb859441d378ba6634ad388cd781223e769ff2cc3da8461664d62c29cc48499691213d6252c882c61c2a4a6be84b130f5258c888d2f6147a4bedc301e5e07a91b0bb69f651891c79561476a7801ea92c5a52bec8bd70416c407e6a50bd7ee82a509acece2e6757e965dca3ce946f889cb962d624358b0167bbc25eda98aa5a23df8597249c24506babf608489ddb834e9b02a3a5c747e965c5830c285446e7b5c452969e28252a48b930b45ea7a9003b933c1ee41976aa77af47ba9950b632ebca1f1264d7339a27180727707b23f4b2e3c9e74231c85b9e8a0c1858a06d63f4b2e505a3603669415fba8dc5256966a4358f0254be811e40895314ca444d16052394790148c703831c61ed2788c4bb0a7c74d47f88e55762ec1d6bcf7b30423b36686accc6d8354e0b2f9da7bed9b28cbc6a986383acb9e565c7aefed962599bf77a82b9ca11df3cecfb22cdff6b32cbb7096212a5f52004291b5660c884a0a463e346839928291d84d6bbf9e97b35d6d133ded83745d7b76d6a09bf6fc529c8fe3dcc7e636dbbdf4ba09b789267c489be3fc2ee96a7d1f138e73138e53e1a3a5ebe95f2d5baec7b7b82392f7775c4724975ec74391a42a489453412848823ce15e0e020c10609ff6db7bb494bdcd7b2ee882e9716e13afcf6bc9d0b7dbdc8adfc5b93b6e0cd96755412fd5d9b75e7dee7105ed19037a9c56c80a51eab75e2919ec2d3390d82d631f080a7806ec50ef06fa8e488fbe0956a082db77443aadd075901e55318b7e459be79b45ed37f18a3de3d4592c5b763e2470bf7c73c6b85c732ac0569a53bbd3715352ae613d39c5f0b55bc7dbbf4058fdbb4048ddfaa66ef6d55c830098945d19fb414a199b1c46787864d4a2e6b8409416465504d60f31305c3655a9e6e3ef258a23809fa5da1335264ee4c08173f3195182e7629c091d520f4ee40e7676b5811fa4f8f974ac93f0504c5b7b6830a76a3787aac6d69c1d95cba91116e1b4d3ce9aae579a9a1a358d0cca2b2dcceb428b2a02088e0808341f3888aecc1e7eccc131258e952c376c1c695d706940e0f8a8d1dc1ac104fc00132210e44035688407a469a5657d0e923c693425f4668892064395b42a1fa8421ca449ed90e694b5c624ce12375c663a1041c31054c2d42096c3161c80e8c1ca13ae1bf8a0c4890d9868e98065c30d34fc10e222368478089220497c34b1dad2c1c3d4912828729ca4c8d975a53d6944e4240edacf6aadb571cede5a3b9f9c5b2b6e1dbd1de28c11051152a15cf587195fa3ccf8ea1fbe5dbf275010c484d6ad6b3047b30e9b3c59648c9da4d41bafabc0f4d7317d4ae10c29e3383db9b3d0b6beda6aadcef4aa6489b2d8fc6d60cedb1ef69c8e6412f6ebf9d314c479dcb388fabcde2c7a018b152b40568cea0aac2535d850264c0d607b6ac43d6dbd600398dd0b6bcedce00104b5e3e7aa488d16d09c9419b3c58685001421bcdcdc20062e3548dd70e81143181c9c68b88216150000881b6ac7d5e981c90e438c64e192b59f7a44e07186eccc17133332ec1aba0612235fa4685185d94e24ce3491e18c5a94263a84d84d3ce8931f3eb2667003856ac8a6915d20a64717acb118ca7cd93376806ca95135264a8d72ef78ec10e04401b91a0325e4c99a138979099b51840d5f566a1e600992034c4790afb1393c3e48116707355b7478128395b41461b45a984104d85a0c7276a000f2c4860f1d48ba1479128707cfd65690355a3b9098ed008747070d086288ac356072680305ad040a2e4dc2d8a80147899c227cde802468c264cc4a0e6590b81971f6a19064ca922142e03809d2c684561a1498304b98e4c06687b766876d6681c34406316084f8f2658699587821082a24438ad090a5e644106b80f08222c30d333748c3234cd3c30b674d5ac072068d0e9117998eb4af1b67dea8e932a22b4195cbd75a271065b26d88737c31c645583dbe3d38b28a7111548f2fc618bbc9222a52c4eebe41b3d68d9bc73f4b37664f863e2767a218c29c513833f33c22ced0c0d24d0d1c4432e833448c8800c10043d566ce72d04424c428c4b90715a0c9bd2900110539d33ad3218a5e7b56c313d190eb40672c9cdd78d28d6adc13fe555ab3ac01332b239a5aa39bf4106bc8f29a34529b524aa9d2b54e73e02caa3e8baa4fb1cdee6910ce928dd9d7323f59cc59e267c926cbe38a588326c566061b29363b9ed8f48022b2c126091b18b2d7b7066dc6962ad9ecd03f4b363d2e9ee19b75d61e280be41ac1b71cbe9f659b27f98631c6e2cc5d2bb6016b436593f859b621a36de46c9d1fa4fdd6ebdb919cc1dfa68fa8ca66d1c5b4379e9f659a1920ee6799a64a47fc59a639b293a6eb71651a227c843f4b35509ee7d93e355b267e966ab04010d4d9517306809fa51a2c5fcf8d8afeb334737adc8d8a297e966633f4dc48902041a2c7a73413c34d16277e96663e3cce6ff3fbdebe1d4915be7a6abe59777e6e21b3f138141f02ba6e10451901114519a1f1a25243d3c44a0d8d112b35ff59a2817af0675916a70c08b5322a6a6550d4ca70a05686835a190d6a65576a287e966536ca769ad2595429d2dded763b8cabcd741a00dfb7f43f7cbb31ea67a46b2bb53adaf588ddd65a9f1819ed8c88661035116146040d22a7b5126564b839738ce600cda13866bec8ccc0b9beefbdf7cd073fcb3354fefe2ccffcc0e5242fc1676a88ba6ece5c455937e31918a2dc9c6921cacd191e4e6e7269268d99312b381c1aae88331ceeececccec86eb41650ee4d8882dc847960324ab01e776ef55632b23d3579aeebd60ca64b995f172a30c9781722b73a5ac24f3fafcb3249bf2a41b8140d839555427edb573ce592b8cfabafab5c5bdf5ad935557bf7eb55043656988208208a3216ae08166082238d00c71e3bf9fe5103b3321ca6895917143e68ccc1632568e944132c496d9c5f7de2f7fc9b4fede92cc87bf40d59691d1e17702949ba8c4eb9a2a6a1022c50810000000c3170000200c0608244194244110835cf8011400095a924e543228a10444a130140c8581280a621808821004621004a3108603599e4379006f08f625a52f948835d9a91cf4e87e805b4d6f4df601d52d4ec33bcf3d2d3cb2ef4fa9b91f13e453a7286cfd7e8fdac537da566343a7491b1cc373e4b609d88932d64caf92684f9c1034e1d0a249b244afb01cd0faaf5e890543d89466a24a107bf452882f2f7655c21e63184f5a1211b5b7854fde71975188f7dfe82e7626de93b73ab7205a3e88db850b9ed2700fe052e8227d43892d0f9e0a890df5a8292dfcdd9c319a368becad0b221fe3d33c420a543c767b20ddfe5c764e092255a42feed35312f4bf3144d892cb770951a74d32648585e0da50e8ab43760ca2e7ce409b4da4706f4b8b06731862c939f45b5a5aa71b3e8867c6f5ac641db00cd6ce4e1b222e12dfebf302f45ee3e2d9ac0cc6ad6d7d4ab823b4b06a5fdef9ad22c8adee66d72d36d8ad786c782661414b245898c148db51440d011450f010ee8398131faff942ec5208f7e5ef2d1ccc21a262a20cb37a500841a3031097ad9f31483a2882124a7d18101198160045036a53e505710e7a3168e594c5dd7f64d1398afcdb0fdc23f5ba786357702997c9c5ddcd6d5dcffdded81ddeca7bbaf9bbbd6df7bfdf1bbb83b7724f377f376eeb7ef77b63eef0566e626a3f0e16c337fbaf1ffa39f18cee60f6c5246bbd2b22f01f85bfecebee5079eafebd571fff9aad298321a14848d670ec65890022d8cf7d4f62be8c0b3618661945fbb2a405c71d2c938fe25d95ba286e9d4f2331374906c68f5be2daa789e8528d054e053941107c14d5c678e05c3ead5a18ec07b3ec040230766313faa8069b20c3fa51d5e9eb27931cdba7796860e2f58cbbe8ecf560470f7310e3cdeb508a3d87de9a87aeca0e102834ab3e11b9296e0a3a9b298010e095f65f4ba53c2c2cf8600b69d2458dc159b07a96aba364a935308e1d0268d2e6f9bf189eb43cb4d61926a1c0073c3823cb6b7d376abd199cb3532de9876240c4c7401d9f8193c9b93ddd927f3807637c0cb07eb8e2639ddfd99c5bd22df986b29663626a20a30ec9ce7826e278953010f7f2300f5556f619473fcf97638470f9d64532c1b2e142876991e660667e5105037cfacab5670b835c418e009bd1fea3aadbc60b0118995b0b90fcd06d804e1eaa2005c4baa5e0ec0a7ea697e289f063f44252ae61e92d5069a44c3f3c0f2c192f8fa8edb0c10b20be72a73b5ef8c765af2c9941fc0a8db7d2d321d8214fe2c548ba653ec3cf7c25dba3ce1155eb54748f790bec9e87d2d11f41e83414c1a35d1a17c99e47ac3bac0a1b3757a0258a3aad9b1f0551b16ddf8498b0eb2715daee53d45e4772fb3dd9fee4368de2ea00fd45bf955bd2b4dca24bbd71975b113df686187fad43635358bf0fe976cb36f3cf86a3de28fc03cc35f141da38c9e6450a1f4081578f3f04301d68b7d5a2800960bb15471668714417bdb2039d7b2d2789f11ef588a2381ec7f122d7eebb1c3157087cf1e884ef48f14b7a032a4e607fb04706330273e0dabb033e68fa6725ebf0962426a4d04df6f55eab0db04f1c406dfefe849aba0c61ea22ffb307c79d75940ece2f8cea004b9826b80a72533d1a795a3244c3fcfe326120233d650a87a3b4900d8ee18e7b59a8f7ad35e513acafcdf96cc1a5283cea440c46a326f85bc4d489b05d0de1db35b54b76e4b31bc76b159c3f7ff51e1660e9e326cfea4e88e5e376b2152df47b7386967e694e57f1775a680f416f8bb63f83ae6219b423a1f5739e91e0f645fb4b84a2fba1039459fea0e70a618044bd52f66be2a7c6b2e2f8b9adf6b2a3fe8a3f2f3a7ed93d59eb3fa9eb137a246f69f18415f05ff76397fecb955367a248a54519099e6bbd81a0b21fa5270b0b85ff6e7d993e6f75b7f67aabd8f9ed41485f237278ade530d445bff18c75b85ff7fc2f9de27be68f78e665fae71601be0dccdb7ee91117f18fc46ee142690239f18177ad42eb53847210a930bff3a0d56d72547cd9fc03a30dbfb49dabf82fc7bed842ea0f92e9d0f90d41b320c2e329d4690cf682e8ae47866d63c0c00bcb07237ebe1a216ed20cd2e9570d3efa86ff968e2f962fa923e2b15671157ab70c8fdbaf07bb229e33c2d132922d035f529dfe292dfb8956c26c632bbc66fcb21e435e7409d3d27da04ed2aa043ab300a267fedcf7a96ee874bdf6ab1125ef22a33c8a36cdfaee5bc0305c94604e52f2a4da592a6a59c1f4ca2c583f0c87459c07c344b4a48de1dcfd77d0f08c464f42ae4e62002637cf83a0eccf104d644ec545c9582047d19b027738d00f2ed5510b93c27be604146f3d95e207f8dca8eed6cee43786779a57831fd86de207a99c683182ee11bb6e1acff97e521abdf913497f063ca2e616dddb6a2bfac013dc1a982ee576bf4565af01f4cefe7412c19ad9dd912fe737913110040c4de2d85ab80db42ccab34779d582a09e8522a70104c8d88d12751c020224d481dc7651f0bec65e745638835c539c06a90165d6def00526e1a641277fe3d17aad37c3229f345c2deee09107fd449ff066d17201a95e194cfcbed970359ecbd949f8a39a83fcebfc10b75d046603bbf8b83f6dade6e002116f63d82f71f9c3784abc1c3f2a252faeff43631d7818bfc486e057c117587713b4b5b11c6989e10ae4f565520ef49d5811721a5e2dc8033c90f31299b78b83721a3a39dff6dd94a441374635b66b3ca41fa23fc105447415c0e561e45bc2755ea714402b7e2e1bb4dd55a6866781b6dcf21d705221bc764621fdc95844111d660191010ae80eb3170c3a47aefae9de817d421010892f9af0af0086fe30884891fce2f9f14f4f8cea0edc00bc96bf4a3e32704131b6aba03055e1135c25687890a19cca4b8b63f20e7842048daa4534863b80e3d39a73c03d12f49e8f265262a256f36651730e8c4872ff782a1200097d92203e24e1156ff35937922443dfac89d4b6ec80164959c4373830417aaf5db1ad5df4b2561695b89b5dae157857c1f0b69635107800919e869959546152853edc074579282773d03043e8f9ae5d1e5f4163700e5441bb6fe4c9de9a663dd70d03e7b0915db4a6fa5c582c71db6b0a0948c4ab3b4bdf01d249a5cb5b38dec3da6f88c9e2186f95c3ad0425ffece3e04e380fcc166865285e67d20f4c6e4c4addf7c05fc1061f13841a2982ea494a8354d27827790d5589d111516ae393bd6087192577c8d57dc9969cb9825debdfdb12fe70b5134ea01b85b7f14ac0bdacc11adfefd5baa661de989898fc06e6592c00e29700e0b32ed9bde950fc166dda832ad7ba4331239a281e25a61337fd5feb61900e036470920aca9e2d67c2aad8cf4727f4e613dd3f02c7f802c19318df719d543fe6d3517ac181a6a347b4ebb2f9507367149ff3895d96740aedadbdbd2f44066c564a8036f731457f571b4c0fc706b476c29b772ab2aba1e8f62f9d43a716fc497b602f17cb4708555721f80ca5a46a001f17dde0fdb01c7b771e1f02d3cb73b833f601f8d5d108c43fc9479017dcf36f242a7c4ff8639b13585c9cff49e3d24fab2e47253d7f39cc9a57ad2546d5c53fc6f9516a2b14f1c31f832dee4196f1edc98d6402c949e3a1e7cee8bde2880bce3a582f209ff609c7f1e285ba2c60c6886b82d9b8558c6120451aeb7bc254adc7afa8ea87ac48a52f2b949173130f1eb32d208a10671872194d3cfd8d79fec66015f1ac3f384426c6396eb9eef3144e459c539b68e9fe79d8f0295c504e78d58a5e0377d513000b0d28dc92b67c765a906168544ce77bcdc3b5ccf06758c27c1641103c5b86f0f5483424d44cf614894a71e02801eb2fa757bbd5c0a661d40047c8752ad9087c3aa2e42ea5990d41a63efe3f40e15f44b2258e3adca0c7beb74b35873a70395eebb7b138ceef7e8c02f9e26536f1c4ee7234bf51034ce338f65c04fdc1c57f08f0a72a884b225e09833430f5d2565566edbf0f28f8d7a16097e31711f9859e7c9815c63f17e6f5e4c8b3ba1ffa74e64f602ce27fd408967b8d11264c8b0f283fbcaf3a4751981e45d2ede514808974f1599df6516e71ad2c8a0ede32a0350d1abdd6d238d537594124f384f6dc7bad8172ea026a376b5d4ce47b144f6b861249c80af9949c9807850ac769d6c7d57bb00960185c899fb79acd3dcd896756d5842fbd2937c437d3413d517f06d79df13d9d0a82a310577f76115cb7ce8f183a0488c29acfbb2270c5dbb873d63e651fc08a0a2ea50b9300430fa204f4d527c93bc13a78f8b181254304aec9919c15a1b27cf16e80fb954c301857b01e7f438effe7bd7d9f1d9b77dc9d3fe6ff58269d59e5df21f3bcf57f2cdbc4cc43bba61feaec9b6e5e360ecd3baec94f689ca7ff694bae5b1042127e91897e3842438bfb517b16cb48c9194b7d7785cd6354bc336d879323a2355751bf1d66eff21d7393752a040f2103d5635c1a8ae0c790ac8de30878b64ef22a5bbdc5fe652e9668de6c7744b258d21e2e7e9bcd199effd4858c2956fb182175e66c579809dc0b0abeac765d1b637818b6f4f32510c672220788b7a25b4e2bdd2e430f69d893881d4ca51d274e36514a3550436939f2a9c0bd90ec3c53fc63664a4c3038714a5ef0667cbf5b318b15ca28eca3c3c5cf206da3273f211cbf2e9f8dbf5bfc651ef5021145594a72cda05965ba0d5800692a8b220f104db55839af75c0ceaf16f4d499025f029762a8276271c3413a21ced80f784aed850c286ed166c69744c0bec04b0088202dd3f3cbe6a7ddab11bd9e4919eb08510d48d32638a3c97d01eccf7aff0d902d7935616a35392fd9ff1ff6c3d0b05bc362c63d466f0f087015abe01b838d07bb5ccda1a6c653a6d3d912098b8414c63a9bc263260a889259724a895f4623008181bee3bb060e97fb40e9a1c45f92876d28281e855352949800f2f41410638338aed668bff597401d05a4b7132d88b4494adea600b8e511fa8103bc62143fffc39ba50b69a32f1811fd4f05c3fb3f45e312e8aa374494acbdd5ebed202d3e5ad841e35568e081091c1ecca26a4fe73c74c644301117a3faf662b3dd8dd49055d05b1691958eef1a7cb3e95455d8712745743d274070b67654a0f242ba1e3c5b7d94333aeb1886256290e8b0383326fac8cf4d4f6855316046979c9c705c4f6d8f85dcae983952f8a5a37466220985a8db6c9c79c81308fb1603f8afa5f631d01bb6b1ed0910498fe369f44cfad48fa7338dbecfff4aacf5b4af05bb3ab2bd779c46c139175c39b00d03844580f37bbd0c3974170a42ff3a77781003b754ffcd2c21e82f7461f0522396e0d3592ed83ab75a7ddabd88ed66f08e5b48c6124963897a829c8792771e13b8ae9eb53f7d2d3b05188705678a5f4a432cb55a7674f5cf23d34994b377aea71cb9c5b570cdb2c60fd22103c79908875410eda70aa732f0b9f997feb18761c70c9362f0d8ba5294b580f534d3925f35b0e7d3d9a1250d7f05830894ef2e45d06db64355b1fe17b0a388429ad61e864c93b72a1ee94bb5c48c02941a578ef17a81c0c965375f333dce1d7af1b58a1899265dfd8d866268ed80ddb500f320475d38a06459d4879be976e8fda7f657846a8e822293dedb4e89a93c7a965e48e21b58df6d4f019480120112204db01082eadea8d3587279845f21e9fca1ff6ce0425adda94afe106f2b3089ef95e088eff329b3d69b1e68ae5c78836af78e2af38f7bf3cebbffc7ec138bb4cba69fb5fc3354438a394d721b5b2bb9218d98409ca79adeb7a8ad271abdc95e036a34de06410d5187e480ef7719a17902fdcc970e27903b58d10171efdb31067d98f72605021ca9ba6fe8316d59588e410edc4e21a5fd58687113823b94f49b6f8e67d06391640dde45ece52890794aeef96927d9c6f826c584b880d8b25c0702b888558a8896dbad6135035c70c2826bdce0f8aaee34beba7df23a229628c9487c9b814fbb34384bc14e0f9d1a8b25519c376017ea0522201a9002ec2f0ba8b1d98903c7ce8387106e0485fc0bf9a25dc0c3028ee35e65d53e7a0348892393e599761292b3b4589d231998ce01d7d70813711dd4e8543bdd9c631aa84d51462dd4c85dece81a6baf4791c0f0c74fd7673827b6e8068f4313b96f4006b7061fbd7e7bba679b3c6cf1a3984d9d318b2faeadc4db92dbda621a0f8fec3b8887fb4332ab5dc64aac7d0e9521cd3a3370c6867b3cc5846a21a1649a76734af2ba22a2852ce15b0ab53b6022d3f6f5c2c5d52f6d20299302e737ab6aa4f788b785c19fdf6284ab9bdec3d55ca22c9039e83a809b2cae9ca098013dbf489fc3b7e8a3fd68ad92a2a47ba26e478d49f9c55c0427a9e2bc970e8c80465b30ae794be41c338289ab6102a4d108059e03500fc88a65b5307f4efff528134fc3bf3ed0b3f6dda14faa9a5218e194d6d2beeef229795c8534313b9be8a29e56f3d0363cef8814a31a13b873615f56cc4c4ca0cca8013ea56a1632e52de396aa9dab565b15538221aeabdd3094e22f9554fb6d84e3e2d39fbefd30adc835b8d0b9b7b96ac0ae194b4e2612a466f1078bcc59e50bf17330882382284735daf404e55a5e710cb2f92e8e21269efacbfc9171798ed1a39ad5bc23a59613ede5be283750d64b3eb62cc2b49e8201a62a2a3f255668d4f75585913144c832becdccf64a1dba7494d94716e6f695e73185f0529457e7c967493cfa6fb365c50df115c33aef0eee52c07ffe50384a208a9662347cce083e28c48bd4ba36a9f185f4ef91a3bd85fbf97b6e1c96b10f9f59ce03468035899bd61f9753503a4d74105935ea8c2cbd206ddb97d5a1fc1f4d4558041f427a0c8219aef47a1e05109dceb79e3eccf4090d4520f599ed31346247c5aee8e09fcfdf8b4d84cbce98bf32def5a5c2c455c672ce30c1f31f2d3fdc21e5be3bd85555052d991f125e4509826ab9ff190ed51c7b959d636456f20111a569fa29075d44c58df552227a5bc2815abbca36f535b5ef380d14ac95826688542774e5b7bdd7f8675e6a0e4e9ba85368164a4f508023811c835df08c85fdf908c4f5f5c099ea6225c7a1380914a33c65c5287e392993afc3eab4a91baf82c4947ce631d09648998f534f2d2780d4f4e8b2464cdabdc7a5ad5995735a44b080ab6b82ddab0f4f36466665929a9ff34565ff06159becd0f696d7ac246c750e36d7a6f7854b9df58ca295dad97a2066af1ca4c706b341b8cd5d90c5c5dd3f85042707b66e47405c19ec9a37a7e9fce015207a761c932f11cc6aad441341f29dd6b6833d490d4c6437cd36947fabf40cf55ca5888498d774033adab5d92e766807896948cb1dd4029d3278370a24e8e28c8884117195254238d531f6ddf04d634f8239745707938c581eb95658582266563928be82482a1f22c86c4994a14c3c12a01d70d827703e090b0caabe13f34cee6e1bf6ce3747ed3835589e7b1a0e4d0207e3bf03baee1a4f88b1cd9b9f229ba0f47be70e85258a6759d36da31e27c1f017480295217f2a5f6af7b026246696c99a7f8bf49122ceaed3c258514ee00fba085f6a2bee10d69f2574c27be352b5389b1f7e1232c055d8fc5ecba40e36785dba784f59696f39dfaeb18fe6e738f2896b3849a3122cf36292fb9ffa108ee0a140b0a5ed177c06939de4ef1db3b6708707d16b094ef7ec3059a454d8d443da37d1ab0fee2bf4c0cd2c31cc6a667840df26d690dc65c78e14d956605ba29beef803b87e2bd550e101b0706cacf77832b56674d5bc429d5b9fea85a919e12f74d64ab2851f54821b9f84789b187dafcd813a4d73f9bb7a869ae048d031eacb48bee13485b910d88268bf068d753ed9b9bf62d2d2bbe9229186e82b3a24a3aa10cdf4045484ba4fd0b0c96119535c5835fbe17d821fa3e76e016d5a67755f9cf9d98010b50c95ebda505f2754c3ee2d7b38bc398dfa81e9d4b0dbb3a38c0cf2659db4ac786a216fabb604639ca7833e7d39011c742a12c7dee8cc14a3faf8b43d2a29a69c6bc3e1eeba9ac3469e4dc672b360dab155c84db6df08ef16b263f2ef264bc56092583431252e1bcd0c7e4b8b689535d6cfebadd0eb04998ab505d93e3c2713d4f74ba7fa12f429b674c3f3fd2b11369df79667840c09d0c53259edb1b9e99ed7c9da418b9ce3346b03b80d8d5ed68d36745c12339eb03bfa0f9f47581dcc67393091525429b08842a16d53c57211e606cfc9d2c64923932bb2560885f8ed8149b09ffc2837ac7f654ea49ea2a03fc457fa3c98e78cb4113763aa6cf85a6559f4842269fa96b19026a33e0a93f7fc4f42405eaae74f2f9b3de7582af42db3ad747c5ee6a72b28e36ad50bdb5ce98d2f9043b6e42de9954e15c8f4f1686ee2652273b9ec7527b800e4b2279dc242d4c653b7b8934d6bad3cb56fedfe5ff560f2328160d4ea4f30704026f3974125939392d595cc7624476ae9f66ee1997cf0d3e395e67d5baa43af7dbf596bb3380341314081a3d82a0726006ccc1f4c5ab85f70585d7eb402f2a13535776807fde8cec59141882cb347633c198cd31e6a60664c4e749f18443fcfcdbedd0e52c4d1276440c47d8ab9dfba245bce3681f83f552db95d4e9e95883d007032b2638ac1cc972e6a796f4852c5f3b69b7eaa6ebb26ca62b54bb181533fd6ee5ad331cdb711227589ad63b43254284f79fe478acdef813cd26b0f86b3b093325f2412e1bf68589c401080d833fb97113e741d0e427dda0af9721742e2ee89badd512d9effdb9cf8bbccc1db77103974f07ac4512ff07e39473de93d8593be55cdf78c88fc91575aca8e387dd5c46f152ea3fd14ca040e65e1d7e48e74716ec3a4df6ea7604b5dba0eabf15017cf221eb846a7e9545953906e3d868f366017f08f76d53dbdc550363c6ac6f57e19cb34d19acaa6e8adf510967a1ecfcfc00bfa2667a78103d3be1fccef7d0a61a66ebe4294c7563633f99eb50c38c4dee1f1fb9558e67478e072efbc257a454a716cb1fb7bb1b9905c2c55a9ce7e0f0110fd853793a1c8082feed4e65462e3b9e49eec511e0da82171b0baec5ec8073600c5abec0d3e1d482a71f73c42cfafa8f1dd0d63fa3ed483dc1b8a87698eee95e6b9323dbdb1f2bbde05f48d0d9c7e4e596e30da07422f458b3cdb8827b41d9c9d3089bd1cf1e9dcfa429c6dd5c2f8fe5f20d151761a73267e07ae3df15aa6ec951b0f65d69db94139fb9c76ac6ca00db0893dc880b8fe81e8e1f900787f90596aa97dd72acdcb00ef67438046e38096398c53349fb95a6919efac8d03d14c41b183205d5568cf86f7e36ad2ae17ebdd3abe6ddb00ab775047d3cc8807c21cff016025ff9f145ea3b2352e975cddd8bae00f08a0c123ec9cf2f31ac75f45d30ffcf9b5d65d7e7e338ff5f400e80b71ba45eda3f60be08d71c484c8268b26ebeef92ffb3baeccbbdbe0355bd5fab1220f29c08eaa40c96a8aeb53fcdf4ac84585dc372e95eb1e6060ab8c6730b4ec04d05690966679167e79862304a8fb717f4d05432a9ab6082f7d35e8646cdb3eb69a31bba7868870b7871ed44826d4fe400f1c5c43537f80173a49a1088f8abdbf171f16e41368d3dfbb8c7b1cce0a85232440e33dbae5f61fd7ddd17ebaafef2e5e06617880c0054e000b2030fbb5ba4a02073aca13eab276c1e874c376e2a2b76dc7d4923d6905b65245297165d41a55e95c30e623c0b2d8815b5c9986bcd801706f6b9261fa9b0d26c1ccdb8e283639600a9d283cc8e4bbc4b9564ef7df332bc4f9905797a26e05e5cd7af38fd655260c32ff83a26ef1746433d23dc61259ec835af1322767f06703f2e6548d858321e4ac4939f8c2c77bba97c91677b3607b231d0c774fcaeb9394dfdf96c9a899341421c90f4e14758c8a9a048d125aff20669fed7b0d474bd44caf74b52a336a671c493db06c7c92a8c1f379fbe2a8382bfac44d244fec0e84cc0d61a7c30798093b78c538158017f884aa3347092aa17ea04a0e3151fc40fa48543b01e00b85d8a71f6aefba7a299a49827fbe147f9ade3a0f052cec21a0b5de196599f4fda1f050e6633fbf267be3c63d997e12c00f9c84e322d22c6b94a226a31dff4823267a748cc81eb9ed25589685e524f629c5c69ea8a3fde99e60f68bc92739b5c6e76c6dc1adb3bbd7ecab4897e753241a8098f1c2b97dba8bdb0adca96f9ea5e608509c4d44dfcb60eb73f40254aa577c1bcec7eb270f9d07b4e5dde04b9cfb41df49120dac2efeddf431725c44bcabc887a98bc5d5bfb7e873e442bb1dee089a00b955970bcec608d2851637fa5d0c11c88b5727c11fd2becd04cbd493c666b77eef24949390b5df72ce0fadc30eccd3027f67a2363fecbd4285becabbba57688e361fc1822ab6d1046d67ec5c44ee785a1eeac309296fcfe39b33c275e7e502372bea67d7e4b8899a573198e0194ff835b19bdfc91ed42687dff87fcce6997338d735b1c019194e72f9efc8aa4a2141f45371790d2a6a4c9b5b9dfddd693f556cf28ebf758ea235268d712fd9a9f1ee0e7efbc51f7ea49e15ff3cc3a138c00ddd0559e6c9d93f649937516b4d630667a2fdc345c1bb1c1d218e5bb96f924271bfc2c359e6089f136c68330eec2d46c3afdea2ef18aaf9c35f5b12281635f88c8a6ba24b62dba60682027f826b3117365b3ac7b37da680a707a9f955f98fa417fdb1589e36a471c4b7c7f66f9cf73a619885618466a4db06782bacf095a564ee030959ea4a0c4e18f861e0a61c657c2afa8ee3a97b98e67bcca8bff16d76769cfddc16d37d163f74691e65e79b790ba4d3624cd1f3afa601b50cd72a52067077d31a24145638b9269ffae4ff9564d2388d3f63f917a95d3407850b3cc464737060a4bc6bd79937ce2498fc21a0f56963bcc95b34e578621b9246604a6eab683367bde78ecb4edb56aff1e3f11b71cc3d5bc75279babf9bf726c96ee469eb290793d075066e2142fe4d03c09f9e4da606077404f91b0382baebe1513a7c9e78fbdc9fe4d53bdd50ba2e3d8e2ad7039e61d23928247b29ed788c7fbbbf2748197c4dd173c5576c6443d630ba44f702429924ff2751666fe2d34e266eb3234fe3e9714c66aa2bfc16cd54bf02b827a6f16e41a67bb87c8a87ffe9233faaf8c79274eb1ba74fe85fbeb8c2619975e415e60df508bfe7e8c8f4cc26e34f0367aa1070505f414fd9897f828378f7993f380e0f7b2d672dd2fc5df4cf200607f63d5a4b0caf05a001a9932a53b00a8370802fa03830a95e4ce2578f304746103c5d9b605f0d6bc60793165b214dc65e507c31dee160af1f207ca541d98cc67776368152f50ca6fb84ab326283d00f220a93e7c931e615feff41bdc56f73a73be4ee12531407f25887fbe693b0a4b6aab279ee25858e139838c1ab0404cd9407597d45c833aefaa2771760946c16f1e3879a9b4f29dd8c95d8e713f11a3a49406bbd7d6d828e0bfc98bd62cf1d1bf8e36c4725fe19941dc9937e89e32f0cdba3b8946c7da54063260ed9df879c827c92a529796c412ab0c889999c50729d28f042e0112978e7811e2a5c42fffec9c68668d577d89fb78229fdd5d97238cb33259b33c700f8ca52a25209ec3e10c742618e219105eb9bb6735cf6f1fddbab1bfd0c8b05c6e9484ce5599a819779a6b81c386730a87e39ff12023df3427bb02522f80456bb8e2c7bb6d948b8810d8a80acba3a34ccfeff4e31bd1c2671b0e555abbe6b498deb0a0d7d07ea096fef8ce84e4986eb8b7004141642c85faae28193616c2fe7b76b9de48db86600b8981c5f6ef747df083382c2f47aef577cb4346547b7f43ac0f41a7372332f2f3234289daea64c95c769769895c7340c38596cc9f5e31bdb5526d9bb24917b5ce6d354c4186632a9d6299e0c8618b5a2fe36eec5e37f2801fd6d84e7ca462385a1ac58f486fc1c9d1832ebbbadc41a3fd0f82503eccf798388548c348210cf69f4dabcb6f3e70c332acdf7a5b54a022e98a81fadca535bc7440f4a6e2aa361d68e3cfc733f679cfb2120f07f63cdadf67f5bf748a71d19d664e555f76a3c7e4614c97555f232439625ab73c0d8feab4f534fa91354acc90dccfa886b3e71e3454a0ad183c0a486c219122dad0c7488d17c88f88f53d65ceab9834eaf8166a982539b69b8bb82bf0c3b30e9a2e61ca91202752277e207319a35691a4f347bf1d6fc56190a3e43cb59e447c41e2aaa2054d3fffcff323a42789e3f6218a4c4162bd1e574121b69294acb375c5da3b70b8b010bd8a629ae64054ed83d63802813bc8af9782146efca9565d7d32ef466cce972964afc1b6e1190708292ed48ba12085844f6b3a9d5a31b0b2641271378b5e5d216568db0e19c735932d15ddf7ec63e91a59de4e3797dcc628ddbbffa20af29563e64e78313e4f2656c6f57ac414730df464ff451e603e5bd2d2b8bc6ba62eb3b2e1e710f20faf2a0c232f4f4c2053dafdb14bd6e4fac6b212e81d23f510a4755e8dab7e8f543303a1494be1ed505b366f28eb0a93c476f02605e0ca082c928a91057112c11d02a602c7a37623cbd4d599da03434f1884fba8701b9323c642f0dcf983e39b5f682e014eca3ab06d1734aeb415a714dca15188dd88b473b5b06c985cc481f4e96ffc4d54f1774d75ffa9a7d58fa7bc53d5bc7fc4e465a9d8e105900ad074a46ad711521a3d4d5bf278acea9a887ef789d962b17d91dc082783d0314f67e4952dc5e0cd507af0d98cc92ad91f6e2d1b14b395c70f22494713ae3cb38ed9e6028e859bb379fddde512295d8220cd7d16968dc3d7db1e81f6575a7a63988839cca6ad3857f1198b2054c4a775f756d2c3fd60b922ab2b34fd3065cb487ca5b0f19a0ec198a45fb2b4b9efaf8dc82a260a741c148f22acb2aa7b6ef0909eed5f7292bdba20195ed81014e2582511b88c7c30b7a0334216555d712149fa73c7cf034a6363c3fe1e18687ccc22fa64160ea2165933a4a7a09033a2a4f71945ac18e70aa68be25493f37e19d9e2adbda64a9b3db6314c47461ef63c69d024d5d1c9bf5ed32a350ef6787b722bd483e131a1eee097f49508e45fc519b2c0780bc95f0caab77379de21097aeab9e2cfc5720f47fca642370e17de94b0830632b1ad3a28c98c76776a019d3708d0a6cf276777330385716cc014e04894fdf4c0b52cee7d30bda21a1dd22d4756847447b57975ad120205f30ccc1900a5a0b9b439e46428b168c2c05350ee1c4061e7c4de9284aaca9b0e2ad5f19fa3314c26ee93a79b560a91645ed7a6774a2af802865dcd6eb6ed4621f96ede841c6434803764c3bcd27cbf92ff7ea5b76d77a40246c274cd83f37d7f3ed70481a387e1fd6deb790004dea14dcb062f3f5975943ea801e12887f364a76a8a0ece689b2c1d589ad60fbf6b1e3105bcf3b111cbfa39f6b01af73e4cc51f2162631e79202452c805423938ff5c1685e26ba5e0882f6d9e1eff0c465a5f571f3a181a20dcaacb49078dfdb9bc9aaf63d25d46c9cc683c352e40d1e19a929bccdc4a6b5125d2a7929477d57756cbdc1e9a3e72655b5029aede61c395a29f566ee33bbe66b0217b04c2597e4cd0218b436abbbe6d024238d08e4cecda778d6e3e50a5754689d3bb4aa8668f1bfa0a101dcec9edbeb730c6dbc1e83264ab96b6ee75f7ca748c876f72e3bb75f053c879497bde09092dd653237a79515b4d856563482a13e969ccc9d37a8627d7227fa78a7f8a17939bf92574aede9e7658a80dae2439228f95b2e3e2d26e29583da6b80d3cdd3d23f025729a2322dcea557d531583ba9112dc0e061bda12a9dc07b420d6f7d9ed44e0f2eb8eef8fe34a96180e4ff7a3d51a365a2cdf8add5a7569f0e01a2398c1413d3bce43134b0a43db1dc09133ca6b7b4813b7bbe553aee80265f375a0e8b0219bc710255e9c52199b9de6547aa4da874aea2d5ed1552e3cd13102ec625861fd9c0d25c9b7908440c582613b4a061d8b19611b0b25eb7f1623a2584ee9f5a64fb9165047756f7edfdefb841b41844546f918e694e2c46b278f30801e370da56434239909ede42d6f5b61276759949c9f9ba347ac972de480de83d49d55b2c10749f41da528f1750508b263a956a5d3d3b35417881bd9044a97dbca8c956d3719e38f34a9767082a2eb3a299381ee90cb65485f76c76848bf3f47ba156fb9f9f2f0c869f163b54f84583fc84e74c1df5f0e5c708afaf324d24dc20ab3f8d0516f30df92f869c4a4100e318abba7540d5ca3bac2424cca56c053f0a5837810712f9462fa8ae765a5139f4005d0b460f318aabff72a651fd6b4700946804265eac12461b7703386c04fc3e4904925df87d8d7e35e9bebd192d8fc1c04854d81f9b94d4c3d2196e4498f85067fb1f97fe7030262991fbefd912909e6041cb7def18c7e8ae85aece40418ccee42cb1f1843327d60f1ff419316584a2a67771480e89334cbd1a6b33fafe419f10d55dc34c597003f2ba75043be6a725f83aed41ca91c8d709fe780145f4201a76a54bda0cbd22030350e89a3116da16a6a6ea7b763eee767206d057dd10216c39bc1a51dba4d007b23dbf2cd6325c8ad3aa82d6cae6a5287806447dc143273f500998b85400cc27b350c27c262e0e38f0a50033e5e4a9cc95587d6098fb1d2bcc104df3f1f5e8989c8dae1a48540532045e742987591e2bad48df05036d30f35ef3aee01c10d983dbeaa944ce5f3406a141ed59d5fcb1561397129c48323a965dcabd89127ee444b01ecc83aef5a2466dd2fdc4b59a5ca3abe50477f42bd881bb9abef562a7761f9eab54c04f6af33eeef4bf240d158b0488c4c19376ddad59c4c01e8d59bb022824539e7a00c67a8ebc97a3aa9f582c3ddfede0fb47db07f297a6340f0a8e9651bf416fd3ccabfc12f69ffa81ab0cb04817cb566ea7c6f2e98109d278cc6552492bbc8b6aa67b885e9f38694f970f7504cc35091f20a97ec324b8e912aa3af3d03701c0137826aac74c548dd55a05c75323a0f950efd6d29717d2ee7414725c5da0660791248fee222cd0b0c02dfb61597e0ee9e51b809586e01c1390c60564554f1b9d0f1ac86a74fa59834d0ccbf386a0cc6cf644fd97b4e795990e53a5cca75f7a86dff1ef33b8deb8be9290a7da49805921d3ad50db89d1a7b3ad1c7ae03e2ce9642f69993c144cd8e5965c9ada5c5360098dcb008d84bfb33e10002b73616e34cfc076ec826d8cc02879b42f6fc94e501723797dbb816627ad5c48f7e352794feda0cf77fc1c65e4dd6e4010efa061c09d56e7ed6e8e114676b50a14abbad14cf2a664127b9199d42c543e5ef4692b9ff5821da9f9b6891bc74880d07a0953d504ea3b5d27fcf6d062d1ecd58f495a60458a4061ee440dfb8713b34cb5065eb18392851c69c52420a5b0615ec80c0ad1e54b7297504273aae6a4c434b2b83ebca82f80870862959315e11292a5a8414c99d6b1bdbc7ae66ac0468a39ac90342357159307b1580b2883b6ff98b4e0caf6f8816f94e94a0390493629de113ee80cc56670866aba0ff70c43057b0921eaaa3a313cd090d8862b60ac688c16f2731051c1844eaa168335b6e884fbe093f1ddc0c707b714d750aa4909a91523a8cd9504a2e3a20c0bbffb753ba4212cbd3c3e0e0ebdb6a6be818a149e114c5fbaa9831ed830d80196a00c4be9ab8d4e2f3c75266288936cfd0b27fe1adfcf500f4d2cd736165e80b765598297a57f28ae75e54480c96d3b4a39206f9f900f993909c28697b553601a150d33fd921d36425483618e00d07b953ca7a5eec7b9e0cbf187b6be811a159d0d30bf94e3e03f209d3066dabc90a71c92ab37bf6c8b1726ebf32adaa58b8a9ec1ae9d3b0d5df3a504f2df55f72c2c4c7d24405b55f81430a339b74e40e4ba5adb599ac60b1934a785ddd2b7a8140896571ad23c2038464c8f087d4d3c81902a14b282695f3027ca72add8388c5d21b8257243d666eee44ddb1b36afb76c05361bbba9087ec58f3a9f5a993fe467d2b8fd674a66cf05264c09a52ed99342145c9615b3b6e2eb85a6e2ef96fbff37f0286fe2189325627e8d9338f4805d089edf52626d7be864964406fbd39ac2ba5e3125592a7a338d4a5808e9b3961de474701222ed554ad18bea31b4286b9d2f2f2a19e34f0f71ce6136cfa5849f05032660014660e830cd012dc1445ffe19ff4c158de1216496446ded1793c296578baebe6c4d2e07faf117cb0a52250589914f2da356311186b53bb6bc2a36f2e43cdbec8114489f9e1e128f0eaeed7b59a531fd45e91f423b7a26e3960ce64cdff14e48898f806366b1ffb6a00468e6095034103464d86b589ff2bca59d92467f245fdfc69b034df08ef5f56445abd9f50cbdc19e80f0e55c6dcfc6d19ac5bb598e5183ec69d7767f5666599eb295c1ac3bdf2e735d86f094955746592928aad4b2ef59cf815107248a3cb00d62141aaae8f9c3a84322e25f1802030ad5114f6b4c7f20ae4d70e1ca57b2725e2ea060e5a49fca82bad1a209e6508bbfe56607cd1ea35ceba58d02c45c036ab4ae8e5710400b398d01e149323e741ce94282775c1cbe664cd0cf78bc3d3e3038041707c220e96dc40f08e2196ce00e3a9c734bb965461c169cfc91795d7063c9ad56944c24484b530c129166c027bda4edbdf7de7b4b29a59401be0976083808ac9353cd9e9740ed11037f37087ed23e38a1f688859f341d4faa3d62e2ef1663e2276d76729eb1573e727cdea8f6b0f7d159a07a778e3c69201557b7b97570cbac0320ffb02ecb3f9975200bde9083fcf31d1dd6330e1c30182c76e3062c0683e1c071a29a33396e1c7ae7ac4c26dd9df31da24a61e10db7cf5d2e3dabcc759ac970bb7c5cb7d9e7f52a16b9ceba158b62076db893797ec5a29d83532c729da8c6ec9c7356ba52d9f890509c89e26c0bc5ccfa4c0c75e48c0306c3913fc5a29c43aa830bc3ccba8ed0d20c7e9073ce3860301c19fc3399e40330b3fe41fef9160efe65d669fe60b00ff67ddf7798e5e0cbac73907f7e7078457b9bb778f0d3e79c33ec200c070c8623e74c2679107a99750fb2d65cb805688bec9e58c4fd070cc5a22d003828166dffb1c71d2091615907aa1067d63bc0f63f99836fc9b1bacdb1caace7c85ae726a918ece02a1c6b0f1c27ed66c57cceb3a393734a4feb653528ca681f168002a954e32880cdadb3f278904e4ac18f7aad95ea8626996c1bda27cf99e78c757bbef29c2e9d3d653cf94746ea107aed613faeb85015ce5d69f5e34f0d975e6350e0ac330093a513b0b4e795bccc4f9a5417327016c648bffa9cb3e452250a971780a85d239aea140e58a26d57e2eec46927416c192597a62d658ce145470f863e51b1b0ac3d4b2c2eb858cec04e9b0cdc0e9d6a082f54e28b022c431688aa3d4b1cc850b5b09a325e61853eb488c81197d031b353f278d153e61003570e3678814eb0a8180d3e586cf070830b8e4d887360f297847e224727c50f7705076b3ad8ba0000f8464606101a1ee810e703271d51661b0c415c0d3bbe1c84b804a0fb22002ecc00c6f0d89e00c14c8f2a7c64f183734300170c6d6200c16414603bc30059d608c1298814224e866d032dcb01b61f10e079293242842d09214c8c5066081509e0b020c28502362f16805fc0806d8c226518d9c106475c20817bd28028231a1cb0617980c743095d906c4440e00813c444800909909980190aa0a9c01516d0e2026d30f045065ea0013cc606c8e0001a1de021ce094e1e8842c2705443920d870ff48002e60202292525588204d298149e4832130215a8508a60e362052f580869ee61043d8841821ed4e8818cae5645ca55f2a20564e28416319b3e5c4ac3e446dcb3d402a7052d3770e173d2c483a1e9b3c1c9d3f224e5c40828259e744f4091224a29650553c0a062c60b3eac11e504860f86186e90c1d352456a06303424514315ae89ce4a1457bc2a6cd8e2062fb0747b963f8081c30f64e4f0031a3a401967a9c5cd9ee50f37e8b2dc273b6c5378d8828593a187eb458b962a5f36a53ed8227eb8466ca94870b967800869a64fc6596e11b3c970d72d68367d1844b9e58a2e21cd5bd4d02e34080a04e542b7d01fa80f540bed81f24077a059a80e34078a03c5426fa036d02bd40aad81d24067a055a80c34060a038da22f502a740a9542a35028f40985a24ee81375a24db4097581b64099969828d5483002165610810a21484a0169891210a0f0812447a4074ee8000736a0810c60e00216a800052620810898000124253cc001460d20e18891220c588002882460c80842442842c001689b284808062800902102fcf0d103041e03104000847604cd747ce001908c7ae1e1059c75f686da4a3fc51e33d35d457a2a7a818728b168993f188b0ba071e88efa5b430a640b412eecb20a75c80debcc8bf16e3224aa219e43ddab8cdcce11118990606fc799c443ee3eda6e0f762156a1ee6e88bdd4aa30d64becee463bd61280d4623dafee7acc5927903a91e81eaaefe69c75d634f0efbd07f1ad9a5cdad75e9188266dfd148be6d0b5b4d2004e2a83fd5662a9b2377c70ac3db66bbf7dd364bd1e527f74496ae935d3e6c7062a6e6c90e2829b2b6b5ca141b7512d464bc616639581b5026705576220b45d79c10ee32fe3eaf1c5f89663b86779858b2cae5841668a1d5dae33ee4721acd4cba8ac041ab88ce5952c54289c999a2b51b212ae4071569e5869e25aa8426f996ddb368ee3b819a2700933581967e08267909a82c50c59f00c3e34c1a2099e5d51420d6f3a2c35c0296ba001951916698466694a19d29431b86056451a5f182a2aba1b64f8029732bc6046c5ce9e659518ca2a4d68b629a680e14dd40da228226c6226862e3786219a988941871007331886336270010733314811c312294881d099281bb80045d40c9e405146a9894243e687bc67f608c65946899965d4112fa0a1d2c5940b9a28f1829b5bbef0c54c89d59e651497282d4d4b44e5f0b4448e3dcb176cb0c73dcb17c0a863c488f91123342ba2a4c2460b9b4549c58a8dca1927a0d4134ebe259c7861ba22382fdb0f984a96a74bc586272a559ea85079a2d2c2940e0d318a31a24c61c113a9295c40494db1028a14b76739e54c9d22834ba3a9062e70cad2a40627dc7456baf8c270b20418181833c41a6160d0c50c42d4104319058fb809d700d54df9d24319a58c32ca9319988e8627700aa6e012b426103540e9f095529e3c990921658d2dee594ea161fb01bc1dbdb7c746ea78fa88fb778bf591fd7634bf4ca4de9b9b7ea9a3f489d9974a67523321b66a032f332f5e84bc04f113a5890da0b0186f9cbc3650d438e902a58aa72e50cc38e952425162df3d4b284868f9d12234fb014a540950ac64114b285252663ef4f024ea095409e504ca05a129a2c062b6eb02ca666bc289a7c56dc249699b70c255819b70e2546736a431830e0a0264bc18a18316694c896206275f6ca5131840cd6ed061fcf62c9d14b1c96719597b964e76d8e438db611cf72c9dc0d093c6c66620b15a40fdbcaec92c90fdb474d746210b641f8b19e14ec2a43560d2b8739ac802d96fd6dace02cd1891796d2fa5df178469f12908f668fede7655f6c5fd7473e380f6a5145cf66bcfd2ccd49c7a0b1313a918fd3c08f3415f8c731fdd237a3c841eeb9008fdd535b479fbbcdeb269265228cc4b5da1d8fcdcb121b7f4a1ae22117a1bda9c635b6246dbe79ea209760d18555aa06d5a69b440f3320b546f81de8c732bb92cd06c02aa8fc58c6c2761d236dd8049dbbee96981e683da4c9a1bdce68ad0951fa1d1f5cbf0b57736c30c66348c76cff22906fff9f62c9f58507d289fb4944e47ccaadcfb11cc0ed33483a17c9262877b964f4ccc62289fc438cd6098c9308bfa89aa325b69207419bef85219b93d4ba72f9bfc65253871f9716ab2f19e65d31a9bbcd0396e0665e4b8321568a229a149cd78abbce9a87432b8b965d3174db328a3d093262eb4000b16512e3811830b61a4b890830d38f62c5b88b3557b962dc8c00550dc38712a5b58d33473ead9b36ce189eded59b6204605b3a69f0a52f108f7edf786650b58c0883922617ba76bdd9ca25d1dadeb1ed3bad7365a775288a375277f4cd188a3795bb6dbbc853b087ebab644fbdee51aed4e8d7cba0947eb6a4e4b340e09f77a63f2aa71c47326db66d26efc4ae90de396b137af88771dee1b99549f54ad97c9ba79e0ec8d966ec62ca60a764ece324a28c7594c0fd2a9a9da23e73c94a3534aee47b0d336932fede9efdc3724d6078d7a3da6c91e982647f1ae1ead8647df05e05b4d0ec1c7b714883dd59eb524ec1bf73a1ed9f37c251609373ee5a2c6bf34d9b3d2a2ee71e9284bef82e915be4a14427fe469f122c523eef44bdbbb7af48ada8b21ceb2c1106fa1b5cff67787d9de7e3c6108c1675d27733959759ab7d71efb23aac7bb73a86bbd63fd870d7f58efc7eed9e48fa8f0944671e948a1ed79da1bb577f26e6f0c0a8414dab85672bae101c27888814f4feb022e3dbe97c9a4290b747b7ac6118ef72d098f1d9e4cc2638f5f9d14da5c7832a9b2b26bcee83b5148f8a35193747b07cf8945c21dbe262d816ebd3cb6f79516f51d8302093599c2d2f60e7e78db1d0b1b1f9cb4623c04dc9cbe63d01d9b5e72d17341c20b12511b5c81734271c76f1229081e6311c4182b31d97e1f625dfb8e7f2f88cfc4fbbde045503f714a4cbe5fcdf43d7ca89b9eb8dfc7eca1ba8334226bb311fc2f7bb9c9aac39f342e370942c3bf796efa096b16ee2d65f2d11a46100e73da16f62ca90ebfa1b3377416c71c42c281e3c61c42ba71c3c61c42b261c3670e21f9f8c4e610522c069b434830d86b0e21bd5eae39abef9943483d3d3c73088967ce26d2ce8ece1c42d299b339849493d39a4348ad166b0e21b190c639ab5fcd21a4d54a358790542a710e218962388790c2212410fce610d2f7797308c9f3ba39abdfcecd21248edbe610d2b6e1398484e76c22dd399b48d6d6398454e7acbeced91c42a273ceeab7d85b3d972c135a2f1990f6501adbb40c23a53c224b50963065182c5041584a1388d977cff2082c6ae89a103b6dca54bb9174ed490b9f73257c235a6dfb146b58d76ad3027137028fb7df1b8ddffe4d7d17f5953252bde36e24fe1ee37f376a1d1ffcb273f0463adf3e6a1ae57d3c4b8fbab675bac6b9f97eefc11b71bf1775cd5e4aa5299a9a94d178d55bbac6b4bdf51d4dbfe0e7e85a4bd754ba268affde690bd4a48cba83e7748dbbea2c4db35820fb51ff7ea56baa4fd76a9f91dab4405246dd435d0375add3a3127b0bc46481acb54016ce54944bc8cd97f11d1a8e66d9e074a3cb0da74734347c995a4bb4764d5c3f46a130b2ae9b6166ad79c86d7aa2d56eba6bb69bc8d24cf72b3709427b95814dc50e956fc38656a25fda75b777b0c87b2b9374b7be4cae003ba973925a391232b7f7bbc57b393956ef02e77e8864f4c76ea4e32f5d938addc675bc6643d79ad0383429fc987efdfb0d55dfe1519de7b121aa87573d3c19d3aebb32a94335c904c9e8f51b37fa5f52484636fefaf5d44a2f5d7bdd86a66a5e474b82ec7c09dd54c78680e739781eddfaf89dbc84b28680d7d1b24dc5a2159c39be79e99abd05b24216c88e87d16c82a6af9b60e33cda8c9306804f1accd69e4e833df65786caf1d89b6c5091083d08c66207ab88a4c9462cf69812130e0efb8d14f05eda266042f2d2359fbf0ecbb5251aec20169134bdcea483c360afacc4c4e730cde4f31bbfa19b9e72fcf598911c87fa39ec93168bdd464c3fe5386c72f0bbab26b98b26e8e72cd1a658046faa49d53d4d2f8e5a05bb49d93eaa43b62dfe836cc402d97b909158207ba06c010b642fcb2758207b00acb0c90e72931507fa29c7f57f72932034a51c9a05ad270dcc643da23ac96eee5527596ac58a15fa653fe5c75eb7b0db5328ebe3c6ed6fafcae48d5cbbf1a77c92aad9b406fb537eedf5a77c1baf0cf5bacf6119ea751bbf91a15e875dcc47cf50af4f0bf43c693e36b2a7c9240bd32a6c9bfe95a5ae5e1264537dd47d4e6daac6fa88b9b946d0a8b0ed7ab049109acf6d1c6bf0b0dff807e5a39570fce1b43e9e9a04a1e1b88f37e79c739ae08c09ae6cf086b73e9458a0d1fed833688791a46194582ce46e4f937649907dfd84e3361e821aa9eefa271c879df44e52355b3fe1f8eb9f263d9d6477d52a6cbbd35b7068166e5c93dde8d22a6ce44403b393898d87607da48fb6c07eb7bc8ef1abbe2ad3066f9e531b9659989985d7412a1a79e94e24616a4fb1c8c84b57a65d7596782a2c0a905ee602b3eb7dd4d8f53f3a8ed8f561ddc048bab6cb045e76bd8fcc882abb5ec79d4dc0596957e3f515895dbf6202d273f182a67a7963a5a0ae141a2c55c6568513d309f1c6abf20d21a682437c117e613262d75fcca22ab3fa0267acc222224ccb4d0e1167e858d929228c104f11687a8a28c38505cccb8912c08ad8f58f95e04c47c4aebf4f09c2a835f4e9421b3f6a0f72b442c75a6537ca6c602465bb04f366d7eba8a51729bbe6791e26b8d689ea14774e547b6253f43951c5b1e553b1e8a7a27ef7916635f9b32b185234c1449b4cfbf5a0a89174fdbcdee6f8eb53467afb23fbf0a7f6f8d1640e9d446853fd7ad594295f8b5a8d4d93aaacca6ed8c011e28073239f60238c18cce7f3d1590255460fcb6ebcfdfa2b1bf5f0b83a179a9e3c02cf199d9c1d6e47ebe49ccc1a5b5b4b68e4a1125778a563d31f51067ee10d35fd7c3aceb3455e38e293f786db8eb3cb41555e8461776b10942028425a64e0ac207532762fb08b19651735e6ec83336fc410420f82b5522b82dd5593b2a2dae96b6fc539a76d0a620d4ae9bd58b62ad65a8bf1ec02a68b178a8f29bddd5c24e8e244e57400679d73d24fd102f1660d1067765dc2da0fcc998228c38c2f3679d7ec3341a02008306a04a125082c4148d9358816bc33d000c128206e00820b07486a054a0a65a24db5246befbdae15949cc0711cd759dbdd7bb9241aa8b2eedafbce765df729165924dd08f75a6befbd5cc6c70aa8b28a801f171299cc474703fbfa24f1193b34ac74a3eb32a11f96c88e60c422b11a489254abb4494a558d098261245dbbf688e1df8df127ad8393e3f6bb37009cf4d931ee7773dc274d567bc4badfdd019de4b163deeff6bc4f9a071fa1f6887dbffbfb3e691c042a9fd5b1988f5c958d7cf46bf57195c4354e98365a693289cbba88f6bc975e3af2d49c12a441900983cc1764d69464d4b852261921c818b15d5546a689edaa32b1c85a7b2f1932656243ec26992ccdaff1043ece4beea990797baa91867c46363ed212da66e38b30db10d96a4fd28d978cd9b0bda4c3be22cc36334c2e3d41c6cbd681e76da6db6791abc95a4c84f94d54afb8648526175a60fad1bcd54b6a9bb47bda1e829c18a3904481c1519b4e61242b6c1c45a144d1a8bb46a98413a5182e3c6e18bbde314a3258ca27a22e1c7a4b409b435caaf0eb36a702d44981602057870c757e18bd93a110581098f1de2704a8d36a08f5aeeff8e0b66d0fa8174f42bda897b6973fef987ba7667b79d6183cc1b4b977f4c87e567104a23d750a74177520f8caf6ae6c9f936fde41b3b9c788dc73bf9af41e3b5704e3e9ba91bcf79c80b3b95b27dcecf017f69b7b6ef34a2ce285178570dfb99777deca4721197e7c4e0cf6b95d1deb5d8ec1fefa3c7830c788848f3d7c4cc7605a84a5adf31891d7592cfbed3ef73cefafac7357d6794f2655f7ce938f5a9fdc773419de7b8efef169e523eff9cb3fc2107bfa7b9753a0db1efcee0782ffe07c48df93b60dbaf9bc10b87692eb66701ba3c3fb262ea03b772b825095ddcfa9ee0a38c1dfed6e9be5acb55673f4cecae3c7be97ead4dfe625d4356773da30f15d749b4353cabbb5dafbf42ac5f976d786f0dde25ff6203123f93d29f528f5b890e46e3f02026a8feef63aacb5a30b88917e7a5d9559fb201fc6b9ef9c33341c131a384ab0eec2a9156a0f4ad636f7a9497b255c469a5465f5e1deb24e95e12b49f2a1da5565415323793457034ea2f10b51982f15e3cd23d41e56d7eef3642b1079199520203c3d39d11059fb638739d4138dcb5569d78dae5fb68530bef435c90f5a65ceea83881875aaac5231922ed797129cf48571962880736febb69dfb100410cd213a87e8a6dc11d7e31289454b689ceb537be053fced604eeede7ab77db1f16344ba6fd779eb5b26bb93db5597ecbed3d444191b0c37b1a8f52e8ff7597d3b9d7346ce59158b6c45c0779a5c0a7f445184a5cdba4fed71bbdf1355f137c75acff9d4f94efe740e5e47c75a8f11112fd2a0e60c00d9dd9e2727b3deca222c6dd6988f56faaa7c24ea4d35a867956ddbc34dc7865c2626bbbb734328d3eee8c6224c279898c02e2fa17190964c384c4c3ba9b5bfdba4707f4a7687b424c84edab687e43d692b6281dd1dc792209b8a0958d2c16972bbdd0e7ae208447b4e1de417525184edf76ee19cd15f1e73ce48d52e1a3267f44ce8924eed986e410f0c2d78ee734f8fbcf7265dfc39758380b43a317e155e0cedb55693dcf157994719e3eade571f3fc5d57857f7aeaeaa0b37f86111b8d77349b8d6a515b55c942870a3ea3ad5491d7cef05bf5d95799431aa1e923faaab4ab27baaf4e78c3eecf2b418d3d1b600ac32da755dd7755cd775f73ecf3b97bd9fcd6df73c6fdbb67b9bee964cf0cb28fce08cc24fd0970dcacf4f10f7feb85c5d5c231365302106136b9a687ae1327166cb6a8f0e0926c4f8a8341b54c558c97ba918708a609c251352f62ca7b26c01ec598e19635b4003dd2d0aaa629c5546492a72b4e6a02de3fded829a18f161468c6a8f4b373ebd32268c7d5f05382b7ee7bd83733749adb5f66a926a926e22916f7376ee3bc7b845b4cb0a1861eb98b36a64e69187919c6fc8a9a6ebc6f122b993562a543fef18368b9a33f6f72c91107382375b68cff20447ec316bf6077b964ba0d92092da63e277dd3924f748a2ba3c7af7529d1e46fa2934d74c09d41e36ebd833828cab7707df913e36fe23d341f270ed1177e1a827d7a9ee6a65ad95816098b9306fdbc3fbb56dde0eebc422efa0e775a0d779777361b661ae9b2a1106d9a031e3fdc765cfda5a67fd06054f2541bd7d77c73a3cdd778e431d34c448ca36d639c639cf39fe8f90475fb14887874727e11dde8a444810274e1c347bdcc93af9ee9cf0adf0ac1c7ecc2178fb2ed424f80d1cab6cb4e1528bda34c0ef344f3a3339dd6cfab3a9fdbe8368d22c99710eea2f1f71ef4e43f2d3a4d0feeaf45d587d8c3e58b2fa31cfbcaa3a1eea805f69f23b47bac60dea2fcf4d392d748500ed838c18c77b9ff60ad9b9432b1689a08eeb6777dbb98fe3b88dd3dd9220e3eebafcd34d9ab78d9261789ac9ef902e0972ebbd7462d5f689d0ed4de669f267671f8ed3a40edde8ddbe713f8fef62c662d1d6d11fe4ea0ebf1fd60785a25142412818e7c711bad1e5e2b237c5bbe81673901023495baff5ad30d41d9875aa6cc35e4ee2dadb7de80873733ae9120912274e9ced537bdc5d6b5556c7aeca683767219971aca2111c0d1262acf73e394c8ff8d1d212850c3f5c497eb424e19b8483b4586a6d9b5b83b4586a2bbddba7a236b80255d9674feba3155ec79539906afbed765b47df6da758f2d177269b66d2ee0da476bb1dbb23d0de6f47e03f91ac7583a326936ad781dbbdf86a122fd536ba6d14cb26f1bb7c64cf64df7c91d07724adb249bc7145c8baadcef656e049efdba6491a3541bd6992d3db266ef6dda59bce57c786d0df22f4f74828140572bf697b49d6712764fb15b7eeb2f4d86d611e3539d708e93eb1c803c13cf7ddc07cafddc11db72ee67095bd1359ba21494aaa6412ed3e7d74c9ac43a15020780d39d71cdd6fa7dfde515c414a528dbbe3cf7b2b4f0b744cce351b7f03bb7fc7502810aac9ef9eb7e1cd6df7debbb94d53287407be4ce892aa8e2640b3c34d4f8fb388b1b241d8b34cc24d125fb66ccf3209a89dc4d4067778fa338758dc75f66c82b067013a0ea60ff0cbf5f54862309705c1d0f33c2fcc47dfec3c2f0c5fa75f62f751f8af087da7c9babf8727aa96ce2cacb53f569aee8fb4567bfa2824bd935693176f4b35262b6cbc052c30e24c9318b65c41850aa2a8ae6c19d326aa0d531a4be8a06123031b19b059019b2deebd3b76c0dacc0e1a54e082cb1316fc0046091a3a2a90ec1a356bdc4cb1a9114cdc7174d924467aed9a3454054e382976c884146c9a9cac0973860b35d600b1c11cf6bc55765411da820b7c31be343466b0823245b0f1024a67868d102d3653556ce0650d2d35f0618935d6e852c0e6068e0d0f52ba30b808830ba61d3e44c1268d931d336cb1c69a1748b005182da0d831650b279aec88d2dab3dcc2cc1650ec90b203ca0e2818632c05638ca1f0ec596a41841634dc90051a38642186942cd66481e60325c66ffa510b5b4ab1e73f5862ac65137bfe0331e5127b7e0e81dc86b76ddbb66ddbb66d4bb3831b1a7cd12587285eca8889aa200c4e64740146142d5aa03853c375bcb8811733dabc81c1541764ca266219258506db0a153da8c1e50657b0a07b028b3328166840b14135c801c6932e6caca81203369d1358783143100d9ac61a039d2d60d8d1640cf72cd350b1c3290d133b9ed29c200d1120ad61677de0d1de90ce3bbbe9cd29ce1f0f4ca0fa273218b6d0aef5549f80c6483fc27d9d3540cc780e11374e614f7b2bb52a72697f01ebd7b4b36799064b53cf9e651a2718c400c60e4e51bcf083161460d1c5c3020cd4fd7f8ebb26ac7c3c3ce1eeb550601175b1a8d2744518ab2b6c3083156058b105973328b5e18a356964a046898506563c61c598d91560e4809ced79f2677b9003888ceea827b3d783f4117f58a07a12ef793a8118a13bea4998b38a4fde15c8790653b7da24655a9190c3364281502cdd28f3e06eb25b6d72b4fa225183825bbac5943161a140e814dd515fdba841ad78a18939ab27690ebb0a608d917e09bd00d61845a05e689b393465b95aeb69166b7b90581f9f40f6554d0f8ea9c3fee620538725daf653db32794d6698f16346694596b20a2382c8b0e2cb0b3228abb0a18a2a33801304a78a261ce26af18558bdb43359497a7f846f871c71f7fee17f07bf4ce3eced1fdd2038456b6b772ea9ba6ad326d58da92882e0e7ed4f18aa549fafaa9cc71849a14d647d048931f250a9c2f0f334488cff541f736aae3e7e3eac218c9ff3a178f04eaad811f1e08d446da4a2713658c322234da29126279553a8418dc6ab1d0cc02ff6f6895590666fc74b58c80d0fce1ac2ea24f74d2cb27d92f6e2b7af32d4694b35b216c24ad76aab33991584fa5941507de299c32e5bf1f75ec81d4caa966e5cee5ec5a2ee134f1d23b22f96520565ec1558ef9e3cca18aba55badf5081fbfca2024c4a6eb84c04e889b29352bbaea446e953b0be4ddb31b05d7de7412d70ec573a73a488b91fcc1719947191dd771b6be8a4482bcb12a166b6419b13ebe356aaa5b44c273b1d811d77b1ee3deddf66425969411eb3a571a5f93325a5d47d75a5fbd9673d57b726c7b4f6cd3b123e3ed1fc07a8f566a29f5dc7ed2d46cf19c5256ea79ec48cec79bd0eac95259c9953b0be4fde6696f6cb2a302873dbb14c447815ca8d2b5255a4d8aae9668abe9810ef62cd164d968ca6c3176c47ebc093d67bdb33e4656cc84d65937caf99863d43a4bd728ad4524dc44234daaaf94d493747347ec5937a1e7741cb7d7723e6a23d6afaeb5ced25624121ad9f9dc463cd7e9b119dfb36e643f3e288b31545a29f55ce52adaa4c0abb292ea3b42c4779abc9be3b873bb23855c737a625e42396f87ef9680d968751dae735920bc6d22110fae08e20a9ee7c87890314e1a8f96aadfd14a5246aaafae94f3d675b492eaab9332234d4e393ad6b5748c7352e9d8f758a78d8c629f3ec2fa65fdc61e30de7ebc655d95c757d15b8dbac611d9952ae9824b5e650d31c5384193b7674945952e69446931861a4c6a58d03101a6c9146fa6704393b2076b56aa9f349b4948816eac5370c0a435717772128456cf9da8727702c598fdf6bd1a71ffea3d13ea3d5da3b4dad43d8c1df13414a5c58e78ba064569debb1b7d6288892cd07d988dcc32e211a11ad4867b585f754d2a76a49ebb513da76b52f635ababeef4b440429b6bca75db0dd27a410c8149030f52bc55cede493b9513fd6afd1ac0e518be9320340feb23f49e56a29fb42526daa7d8804933526feb9644c5720488c65a29a6aa684489c93df7cdbaf0d138cd74bfbd8e31fa983edaa69598e0dfd18ed955335d3d2d90c5126693e3cd6ca9519aa7c4a4bbf70028136a5094d6bd09a57577a13b372f53a7a5ee39cd8426556b914d934b8e92e0578d023ebd127c7a229b56825f7f330a98da59af6a098d8302be1a85fa7a2676c9d6aa86d01938e0f77df105f7867b33a788b271e61451db942cdb942c47490cd034b5efed99333a4abe8d82019aa6b635f3e628c9ed42aa33b366571a7669146e17524c5b8893c14c83022e87a926db9ea51456ca28b49465c204855146b1840b416bca3371ce9c31069c3f9df5517f4fba40fa39c1b97acd3529a31004b591ea56d7a858f538f220e3fed34d55d7a48c787acef3daceeb29fdbd11fdbda7f7b491eb56d7ec5dda8805e24e754dc802713cba46a46bb740dc7534f855a86b52de6be1bf7bda687515f7d5e9a90e754d8905aaba46413d2d10ada7f5352923faaa6b4c54d72cf0da78d6d428c4faa44d375ceb9346395ab8cac9352923f02d5da334f0abb3b252f8ef635692320a35cd6295c2f368b3d29a7eb1403cc818b5524d8a8449c3df3e699f56c237d2544f53c0250553fb0fa0344c9b3fdc6ac9560e88a02dd86cb185150f82f7de7befbda0787aef1559e238de0b8aa2a8fa15c14f111441711445d678a20a8ee3388ee338b258add63bebe3f5d6c156265da35095b56eebf82f5d65e32852718e5a3cce2c16164f1031c6d8a51297c2f054960f2af1b67e652bacc9d541bccae291ead4b5319e26c05658a54ae2529565b8ecfb9db28c15d74ed8c3b3dae8576519a8bd7a584271c6bedf61a9be8a81ab71c43ac6d23150c7b491cf61f778cca408ae34fec6fa8db10eae56ac1b815ad4e409fb7e458ae756392a955e81fa28fcea44555cfd86e1d5a46ba7876747b55aa956ab202edebc11bf3a61b36e3beb03f6f1e098b15ea9ce3acea1e8da57f599335bfaa56577257189b48442cd06bf7a3de7ad1c4d8e2f55a71abf164bcce40946ad7c04a65de3a793b8c4d545708a4531f1ac079cf4a8542cb7953d4d32a56844000080048315002028140c88c40181482c4dc32c647e14800c8b904c6244154a22418ee32088814806a1184388318600630840082169ca46001418201158a2d577ecb846043cf14c7c438620918a066b84dc99b810adb5b4fd522608dab7562a9c3434482543684e2ae184f7720053e0fa25bbb099c630e86ea9e253d24e22cf2a5520fb06205a3b3072bea38bdff101383d761ce609962d3c15cb319fbe29163cfd34a72b841244ec1567e48281e58e78dedd6962e25c31f5d174f9984e107ef44b6d9597dfa579430bb81e119fd5e5ca611fb0048bdd20c01136cd6520cd1d5d2f7680965d00fc0dfb2ea7be4641e0fde1537a128c00e5a801cf37a823355a3366cfdfa1373c326ca6440f74b9ecdb7a7fa4cff0adf64c808f8d8e820dea28595f6a786a5019bdef8f85b0dd0b91d8ad6f2c15b117104d2e9800f1e4f54331d4ee7fb973f5dc16bb608988c721082dcb6c09157725a4d5ada288a1613cfad6864e2b49e5e6d61884974da39bb45d670f293140fde30e14e353c9c228dc34b30adb9e86469172b7108f28f01d92d6ad27832cf6c4bd1e7f026f380318cfa1f1ea6857111a25e84f96a5eb799640d1836f89bda1c3b99c4b4a188072fd52e4225eb2eebb54dba0bb4e5771a738717151e011e3e7822503212650da0f504e75b55b6d0672bf9b9e38684aa5d5780923fc4ffda11e942e141755b0d4c3db57c73aff42cbf65bddf4d9ea6226e14a93d5401862af123730a1d581ab5ab4540e6fa0a46e2ba4cf3e1ae5568aef82369a1505e04df7939b6f3a3a9ddcf5bba07370867aabb44bf228e799f37739456c8c5472c455441d03e73192f4679c9155d22dbad5bdc11690208bc3a31a55c2672d0bda8157957f8dbb8e178c961c6e803e4b1fa72f35a52243dfba9e6142873785c9643631bd9acd931d0598658d2f025e1ae4e36e6c6bf4afe3e3c0b4654e3361da24632834b6c04764095429bbad47d91ab1e92b163266d15577fb439948d46f0e6b55dce9a6c32dcde79c6521aed209409fe2bcdefc98b4cbdbc5b23c4cdadc6d1c3b8faa7ba3e387c6b1bf7f1193d675f9479417aca3d35c84145139815c5943950309f8b5767069cf5a6c828a2cfe29f5dec48261ed07d416bbb18931b4a70c3b98aa20924623461b92225dc71330e809ec9e420a6cc45abab3d36c9055b31d17ce2e59a1ec4f3fef3fdcddfbe8e784dc7a03318a3bc16f6965cebcabadc0f45734ba6c0a7828239a31d0d5e29b2c83ca48cbdf03a5c1c0dcc573cb47f073baec4a8579203466d78788264cdb333e353061ef40b0976bcf502fc2a95a18cd877b695c574000df50501315e9905e2a0e0d0eea9d868795231005a79b337d7e4c7bfc50de8315579b2ef2cf93c9feea000cf22632f0a3bdd2f7e7db25ac11a9f2b9520e7561eb6794217968bd43d754cae53266c9ba7a84cbe3e823e2d4ef9df0afa1d6b7fc9dc719a6933bc89a3ea6075dd69131473f3a15c54f8a59726ac4cd86ab6833b4c77f4a57ddd59ccacd97f8e81f52dae0a6feb685684bd4f798dd99eb0b492d364b22304da8f2fd6a6f342a3ee8cf2ce8959f6078cc9615537dae85888671579d11596cd552a32ee40982e86110cdbbd534333e5b4b0d4dd821d83fdb99434342e0d6a4220a5d991cd6ed3f1bd32355552cf0455d48161ec5eca4394e0d91c76b41c0f5e86cc5cc808471854f70a0e83bbc80176579824aaa1c05a121d56bbc80abfd80d42829f246270492f334d6590c0213fc29e1630bb7ae9d2169dca6a4e62624281c0c7cf88ab8d0c95b4e12ddcf76474c59a4cd54918fd262a38a67a4ff30278d39a4cc396b0773f2845a70d8dc884a7a4e94ab1f1699e044458b466b8f7926b6762e5cd44647bc7977d9600407386f4764cdd8d3fc5a6141246a6c1916c75713d117a8088d5c0911d5589e13a9f905d9691728878f3716430500992e97c55ecf3aaf48426eadf3816bd7015f66d469fc836fa6dbc8bc9763f9b34929ee790370359082575303bedae2ef7100ea15307c9124af2b487f4c36988dce6dc4c1110ff6cae36a44dfab582f39bae77ed0bffda5875a43d70b39df6e266c190b6664732cd8a59dd5cf99af8d22c50550ded7e01e467aec7174e46f0eb511706ec5bbc1dda545b963ae2be154d4867522a3ff6c4bde7ef707051ab4777b96d2e3796cc529c578f57007023492e112defce4105e17836e5dd9faf2392c9e152b364d24f29196bb3eadb04e26c8b120eb458e02d5570e745c8989a860cf2bae2d7c527fcd78fffa2a0ae4a0cbcc3e5dd689b4b15e4395bc90a990fe72c06c49d8d57a74a838c16f50a422c023201350c8e2eac4c65ee0b6ea579a611859be6da66b3a8ebe99366857c2cac83d6303b83a1beb989b578e79c505a0896315688a114a406c372fd4a36d40ad32c24d29f665b90db211a8b7311484049be543d6035c27f10754b1bfe2ef3dcc57923dab2819ce3c2de0002208ccce3a89c81f3ee8d88645bc2c3bfaefddb1c286269d6aaba2966de2f305a97b90c64bb67ac0e0933cc4cdd861d27f8c54df2e4d775d8d5ab7e48b825ac5aed491819c0a74168230109b7220a1ce231a59dc2c77149997806544932a2f59d4eeaeb15bbb1d4f064949f863e4ee640babec0d969ed3c9f51a1721015759d104e9b6f83bdd3f2f4eb557ccb8e9ed8afa8e211a48defde0c42e55bc44ad102611e5104370eca4a2b28bd1216debc0a0bb659fe6a8d4b40f2813c9aea27cf7c006386f049395bd50c77a0a83fcafbfc79600953a9626dd3ddf0e30ae36e7827ce8aa93aec9a146e04ce289177cb2acd8d38aa80d2e7da2f6e6bc688c46ff53b5494260bc9ac10ae3b9ad4bbdc0c2ee156e737dbead3861741e58f12b03402c1bda38ac4b9ae58b312b468cf7e03e7a3d0769724fd7a24c2b6c37026158e80ff0fcb65a31cb1031f471dce4f2719bd4cb56773b013326b18cc40e1b982a374ecf29673c75364da7445a747e6e2bdf2428c8e7137bc5ee923c60526eaac1d7d89e77c983a9469a43299272fa4662f8786957720f988e1e49071002f79c4536547ab25f50cd969ff643cb502b64b0a7e70668926d1b86d0193416498134febbeb97d571d89e79fcaf044908a129f82e9b0e8911ec39bb08061abe4e224f8487ff0394b22f2f46de9619bcb45042bdcd3396b0be0d337dfd1433444775e6c17be52892cd9d65428db381c5879bbcd9699cc0f16190c1ecdf217d3761136ca413878bb2a874f2c5a5ede8c4c1940aaa66946eac4789163c02dfa94e3245aad9b124897c8a9098e444e66e1aa93990f1437d62b2d95c62fbaca8615e7c0ee5ac76887f2eb9865b3dd9b837adf70c69bc516efa27db985e04bfc04944d5f1d81d6b270ad73cd80b2a72c4f96c243ad7c57014fced287e3055560118a88f8c4f07c266528686030f458f58dad06d2e6e5040b411213eaccd3a820bf7d82fb109b8af28b7b86361445f0488940b6f7ca48fb0fe93c716b6c3eb253888830e42681674514552f80242fb5d08dd0752374e9eae6ac68387d7296c62ad4340909c900f0c25bc84b6fa74884b502787256d75a6a74d780ea423fe456ae079f0da730ab43b0970d0e55514f9acadb6596ce46841effc8a0494421cd83d84da3d50952ddb4e9f3dd2cd4f7d13def6a6960b3948dfad36577137c589cf2240a95f60928676ac26b6f03c1a0771839728595c2bcd42e0c4b0d205f38d120e534a69e4dc4df04eab0a3160a6b81b598f620b8313bd4c660ca0480f4a5313b02c324a169b45a5bbf506fbd36b269bccfca182c9edd56139f1bb574c15569ffee7097b54de6cd526308db0392cdb9dcbe6586a013d4d54b0d800c1143cda812d4014206724322e23089c2cbb3dae362557b0d5fbe0db21a4b8cd069a6bb435313c514b6cf1bc984758b9054a5b741be6a67da1e3f13d3b2657633b216297ee20db866a7ca34ad6c9a011286b180d5e89d450854d21e6e26f06008ed6b486bd635e50f6a71a04467871806996965195405b05586ca45337cc5245210597b0770d6c9fb087f0841c896650604d4a5be54db55b682d726e0045e34888c6cb3f44ed21b78e5b7583dfabcf1ebc46138f6b59a30f816ce2ec9288f7d129591f422e1264018696828b2e5498d2738403ed8f280b071c74dafeb0ab27a5bc22b70c1214301cd0abe059225c572581aebe581c7b71ecf2cc7481f0acbdcd3e8ca0f02c829f2a9312c5bbf4573783cdee6df8aa512db5e418c11b9840b975859a9ac3bb962c74f92abac648af97556b435159b6f83a4cd98651fcf45633b749aaefe26ad15ba565d3b922c927538524f2e0686e6be7e3879f6a88436a4b798bdebaf461c335563335d7ec4d4ef810b524d4c883cddd8c1656fe480be8f63bb03569ddddfaf0c2880dc1d17530d9a832a5bef9ff729aa5cbe59c75372846a79ba3102d47cba719215c25652ee3ac264fc1b496ae668d85cb7083d4ddd77c79280f4f5cbc19c5c1dd39b1f92d3acb8d83d865927e873aea288941e04f51946ccce313c62eee9cfc26130ee18bfcc5a02f4ea19eb3c0f2f4f740366abb1fc199d0a603c90fef13c3b3ede3d4422b714dbc150ca278f50a2f7132e0b6c3d68dabcb3af5e2bd93ddd8c013ccb24e3e36cb483ab1f5fad65ae8a24a135aeb4516cc29349de02b2334fc42e92ad9e106674ce3d4d0287d80293b2897382b954f8f040e67279e4c1f116b6b5b777b51e2a0bc80438dd2406c2da00cb04dbaa2c3e9ea9cb7315022fe5bb03025fc85e4b853e5fd49f7ae0f83f83addf427630dafd03c5ab391734f255510e166c3adec2325989a51810d715e031979de12e289ec574c04536aa56cac1a1bbab7de94b918738cf103b427c5751e95da33b2a760d41148c411674cf6a0542954a44307ebd4bdc65a653af4f80165e60df92850dec7a84588959b929f99fc9432ea48df5a593fce625d3e2ef433243ece4230cc3d015d578b41f183e8d07f1e999aa09e8beaf6827df7cd003e430b11a8a89b2097b84662b77b1a24840537f1906c183a34f7e997689597db11e924fbe94c22e443fafcdf2050040fad4d9d8ff043643a136fadb908f8edede1bd265b7cfbd621eb9676d934917726cbad248a53a33caa5bcbede8cc2901136bcd575940e08b076b0ce328b516f9d496a21ec416c93249275e8db92d267eddf462c26b36caff620ec9ca86b48fdb624a6fa65c2b86b59345be98ef171ddc0927b6c6e009c42d0a32b06721b798e82b8ae916435f93fd95bcee28a8b64cbf8602d11f48bb5bf78f10e331701629ec2117bf278912667d8c628fbde47f0ded3068d6a5c4c575e7b581e920ab8341bd7c0d6c424059a490321185c70336757bdfe1df9c4ff85f4e2d9cc6dcd82c54375667da9f20bac3464386d65da88e5a709231f7115be4eec5059ae911adb158b5084448aa5fe3426e4a2e492af50ab050a146e7b89ded71c1d5938fe3fef170b84c08ddf2648905bd214df6dd94398980d2c6ef7ca0e8f35a3b4d2c78dd6b95fa31a7d9fabb35f215dfadcdef395905b1389aa782d163e49626fcbb8b8187ad981b3800d331f2a384198b98d4c6e095db5e76c31e0949667014c98efdc882d184551cc1af8f608d2c18aab8063491f0aa27206ddc796aa0891218c583e67588164e6ab23f3c18160c143ff67638d85bdfca873ea2b7a95262648cdb8a3b2cf64c0db22fb8c848d455c6f575d2e9af63795b44b10eb773e293757b14c4ed552517f9baacfe780932df19c18251201ef67d5a56666e4fe7e06c3d622fcf2c6331fbbe5790353ad622191c3145436e43e8b33b8c45c09db93f80b77b23ec31f25f6148c466f41e4d270b04d4c0beba7f6a7788f79c9e7f1b4ebcb06634dbdb20ca283ca8a8596b28053e56bdd4723943562e542c2c88d73689e50b096d78c74f04390e3eb10f54097a91ec29928a9207902b73b037408594195ae0b9658f3cac217002e5ab71e5b448a32175e4cd164e355f11ccd26441964dbc3db7eccfb27bbb65679789a10ec109edbc60a2e8793c842d7b0c0a2219ec550c8d9d04381a431a41e1d4e5c7f9f9f192be4b2f188a223bf0f9f109396eaf57f0c55a5e063b34114cc9df22d8ae2ddb49e0c360079a10bcbe59624c96bb6202a50bf6428c4363d516ca8b33e750614ca58e1bb72717060220fa2d2cf3a2d4e72de9173a9eab61f8f464a1e56edc1e1d0280a2f7594edd1abe3c6ee2c8ed41938d4feb4d50574d49b1168e2bff7d74d76e790016faba103c30900278d58d5ec2327bd980dca03c7211585f13d78f06df3a7f54beac95a8fed34d49db5a080ef5d4242236ef2208d0fa9e731b29c10e500972d776aa751db54ed50a79e232b253bc4652656eb74386e08c5931e016adbb3d46d735d1172eeec6ce23b15a4de1c318861f5621cefd514f798118b52766f7245f93bf269f7a6eb501837f38134c470a4fc591c54d0fcaad7984f767351bafcf222cc1916d1dec2bfa0f188b2e62586309ee71268d6e4a2c8307f44485419bd5e3e36a4737425d4ffa2078ec0366bc60bfa4e0d5179b05de949651af3ff17f593a07442d2f407353f0dea9154f620deb7927b5854cacb40566bf67c592d5896012f2b8395ee4cee2f65a723ed0c776a90c2c24ca457be678511ec67cd7a19410112c472848eac9d020e5bf6c0d88b2606806722dafcb323325703efb9b44ce6045132c3a5cf93b355d1d4762dd8cfb590df85e192b8d675d8e54b99446e915ab480321634036277003cbe49fe2606f5366955787a23ab085a59306b1f3d3aff61b5ea80a2095ba25936e8b6822253b0155ae797262ebbbf49253568035cf625925c07087d8494088f983d8a3b5ce31749d8e42ea0a01b4892c48a6224a17904ca21798c4ccb9f12b3cc39b9d8f756701c0d66aba6c60043a012b81f6bfe850ed993211a5df900b6a8f473d762b778680b7a049ff91ab97d520cf4924d5b01d6bb42c4466d4d2c63725f83ade648f3bea9aab74f96b58812ba31876f8d054f32d07ceb63c4127955087ed1b53b5f6b4d753d274d1790d3795b994a860002f97f369ce6042a8c434111caf0ec191b8b171e505abe32883419ca136ed99d77e3429cab897d5b2a46f77dddcf15e81e4c292fb8130e094a1b13158b4d315fac1cb56784f8bfa8cc4bac21793adfeaf43e3b72f3b458d8d81066fd9d636d490f4b733ef160cadc8d890592727e5fa6a393977b0c57696511bcb776ebd7f976f322db442186ce6fdded6097697a686535d09cd98c0f6a95f6885d761fc7f1f105d377f85584261f750591f1f94249139367836d1387dd25ec235c1d700ecfe4fadf44e911faf1df6bc5e12eae2961050a609f99fa4c3f66239bb980a2a492b5a64eae164324b5d7761b49aea69c8adb37d832e7032d0a041d402201e718e29fbfe3eb633185995e1e848da250b7686500c132733b40c41605077dece7c5fcebf10c1b83976f9dc912cba624a08b64cecf20df211a3490bed2f093edde5fc6d7e4957d9c74f09dc88626a6136bc3cc55f607909a1e9c5af38d4d9ef16ed6d92e2600ee55d130872db418fcbbe23a8d37bd4bac0e688f2d669858b4f5c258a5af97b26e5440b13f7ce517a9eedb4d6ad897403979644596a1d306f02a277e1c9c76d89e9353c1d7045ab90ab6197a0647a433a6c21218795b57bd0d2427962d057c1b013c2b03772ed100242fe40818444c4398293ffd87422fa2c9f83548d9169e9e7b578569a05c89b245e22f59f4c55758e942fee57c42db33d874993d496263fdd146162674343e5852e65136b23b98a46ab0c7a907e3c8d175e76fadf384bc60993bdd784fe919f585f6d105c39b299b80f39cf4a4b44decc421371c1db23d7cc5ba70043f73b4501a47e6e43e9cdd6853c182e69d57b657a370a80ce30ec4d81dba877bf005bade09d84f4e4ce24c3d47d180b4a1551b4eadab3ca8c0bcd111501c5ef9ffceb915adf368f364c0f4abc48e0e32325eda3e139b904a2b01293a88c8bf8da8b50b2bfe4da64433c66b50a7ba47d56882b81ebb733b8a4aa5d16ae8de623485ada78d5c87b2e001c783b8b84cb4b0c2b8964e985f04e0218c4e8da8fd5576ad25735fe70371e065101982f061e9197b75a5fb87bcf7871479166c1eb420f96985aec42e9f56c01acb10d7c091a188caaa7758449fef068c420ab9fa7299618899fc4d3a0dcd00b8d7537f400839e8e28408d55f9a99fe6d0050a5de6dcba337544c108e2ae71e883e3aee780b812f88edddf472073c377c5452969952a537e5de6372da1c2689c18fe038a472ab5e742ad61c7d4606b30d98d7ed0843d7ad4690f489d901fc333a893a3e8fa676dfb51494a94665d6e2f3f213b8c04f516704a216dd47c6cf2162085dacf9c06376fe60f9e688d35e4c97c8d20386185cc48f556f1cd395c7f3a492cc2e136959a4f05cb0be84708ac56e1aba75129f1456d874b74f1788db8c8d1da72183a5b24519d0264eb7a8ea863d96b2603c76e592c25a71cc0d22b7d0c142d047a867e43d4fe21b39216dd2a2d4ca4c8cba9f5a6bffb1799bcfbe3e6e5e41dab005d285e6a744d39e0425250b4528244b80eda2ddc4d2686dcdf4120d9027b14bb70ea2e03c32a68d71cb586feb421fb806ff089b90a9eb5a809b96ad9aa8ece3166c7f415c2dad714fa14c6f344155b567ebfeba835b82bbb06578a17959ec13189e44807354fd6eac811dc674ac748c28b3b6b20eec3a97d42d948f8456814512f440eb16344d79c5dacc02f9e5859afe90e051e8cfaca03218c6695845968181991f5869fd9d57fe7ecd53f5cb2c7141dd5ce9d9646954306d07efea29fce502535de8953d5ed681b1872241eb7589ca5c79f0d42ceb08794c56b7ddb869f75fcc8a28245ce3198d4875b4739b02ad28384c65b8c2740e43914d591d6eb064ce622f95feb007592082472c02a5b44ac2da580f5a4ca73485803f4e814ce40b65b71586231de1829694535980c02ba85566b9e54bfc34a81277f0bbef96e4df60546382f8a1dad379549136133da853646684e36bd9993cb6692668313700ebdedd683a65babf356e9436bcc67e513221dcafe34a092d113bb55223644eca266129da8990c3140bae2af9f6a452e28b04298d988139225646683112c64be939c2f7e5335eb328e265744103723aa3f30c0c4e1e1c929a6c96290177cd4e06d2b44a671c0d8b45357cd1134d57d81acd39bd0ebf62f3765038bf621448d10b0bdbd858b1073cab64a6c1bf7c39ed10ec1ad36c946bb09c71a66fae7a9fb5a013aa2f45e4c18d3a3e76d9ca9c381eac3325ec67f2eaadff6c01ce2fc0ee08744a34d447fd0a52467a8382d2ec745f8bc75cc57ccf53196000aa4f6da565cd8faa726d0f6dee6264bbe46400bdfd2f6ec23f1972ecdafe860d76f397bd135d3a74349767d5d7155dba54c409c15e6e3c71212225a1b0f2039acbaf8883a7d6bc0e09903a2b2bf518ea391e897e4426c454dc570946fa7440bb3ccb62e9225eed0ba25b35508f137627d46114fe3afb1091d85f7e5c1a2d50ea3d4993aeaeee101d7289e74b8b40716756b27ae4c9aa5e3519d059e303878a9cf7054222af30afdbe743ad99f53058152c24586b06133f836c6e16c0cadcb9ef41fe50ad84f7829f216e08a0aa25a3828c992d92da3c72a1eac348f9a136935711fc83913dbef91e4121f4037749868819045d4c97e9c8df1b348e8ad724dc0f160f7e9ac50b7546b45fe6d72cd91b88b1180b80e7e87b261c17cb4c3fd172dbf078f71cf1cd60114342dae02810f2c390f9cb920451438b811c93b68fb0d8dc2878833930d27172ab53ed1f6176e1bb20513727b5ae557f3adc57edf1b7800238aa988699de524570809d18397ba7ce4b30944a91490846d550d0ae8a762cdce2df65f7915191bd2f275245026867150a18a51223585513afafcf13481921fb3f61dddc35b4e5454d6d11762f2306e4c7e0838f05542207a1254bba376137115c24bb7a4399ee50e2f7dcc668fb888ae82f37e2d594ed567387abdaf284d7ae0e4e1c56fd75d8f5b24eadd3a94f107b02f02d37c226f679348cf3d3c0d7a42491d21d3f98171e75e0c208a47ab4b4a32050c3317757a4cca29fd0d8c7c672e94eb02c85696f8d985d79c8b21f624c31febab3153aabe275b1729248435e6438b9f7a8e2e424d5754d69b44fc73afa6b1d23a7f19617c1b5a7130a902a267740a5f7e5da973969ca48afa4516e34ce2e1ad9b4f52a6ef197030bf27663a34d673fa6ced37fc82a044af5f214149c2dcf99b10834866512b16db42ea2f93ed44376f88b0844583791ca04057545e66cbad13d2384b5ee738c47546b64c9c765ac6cbb626e52401da95f315a172a284a8e4eca5aaab700d9fbe90101144068ca09940837a1a5ab9f3f494801776bf5a9750230c98be899d98b6d8587f7e5e2915a161287170c9689b7da15d99a01a9a3eee6422deaefdc265cd65504531114fd30d800225efd3311195d0975b448cbf4928931ff70f1fc1fbfffb33f8937e2c85c0a241868329d434182959e123b420dc5004580d120fcd3c8eda29a1e98ce03e780881d06124c559fe0ba64058d82ebb831732dca707e54ee41bedf0d2f02823877d015d998818a3a63cc845f6afc6c82023ed95d8b90925c6f7a18edd906da0a5cab62c85ae9c04c209225948b3c4bcc5c53ebbd872fb3bf281e5b00721f9dd2344f0c0b5a344f0916d02e4ed0ce40a38e6af79f156083b35e1116223d5eadc98ccc9b3816b88257e6187a0372709f26f9f2256fe744b7a49885e41b434e0264b441aa1a98f0e4d915d89856d1af9179298a7654851da3b642d9cc4131c9a52efba9e1683e772a793e4d2edea95b20eeb2883d212fa42b4795be85e1580f71ee2eff8458e3ff2a1b49fcc1a0b4da76e04ea6ca9b90507104d5bcbcf62318b2877c62c24cc11eda5c1dc9c1179412a4054b4f2028193a67096d2095256e9589791fbe1edd128ecb805beb82a37639c6b0c243b351f93b0fb0a892061eeaace2fc0256c84ef0032e3a8951d27d1550897b07a21f5e2ed31e671fa198c76d5bf12ce1ff6f109c79cc48f8015af107f77f2b862a9a3a12e48e4a6b4bcb74968be534688958bf6cbf2f694aba2c0e50068d2dbc9978acaf21e33c2534a48ed044735b1a57fb75a2abc68a317f0beb8e799e3aecccc5198f7b39cfc1f330c66583ffa7c3f66122d40c11cdbe53818a97856daa051e6e2b03d221f9fec210822bc8705fe26e20c0433a41ea1082d8dcd79e0adb1b623c78257aeeb6c79abdfa428b436ed940c64f0e40ce61e20dcf14895f1c0cdd80a7ee22a09d3fa91316219b1ac3725c7559b18873be6c730d65e7e2ac1c2ea6c228920531adcd85e66daa411384fc76f780ed06837b331607c8d0d1e18d6363e6e5075eeba2212e985b6af168ecd439794c183d38cfa6b4142e04a877886f80a578642a1a55ee1573d5ffd61d682ab2cc57dbfeb234915dcd13bd5ed6a2d50a8022a2eabfb118135086a627c27016b1ca17475ec31fab5c73188e67bd9c1b540984bd4a70e46a62917a4500f0b5d058c995c74d5ff2676f897d837e0cfc11fa8a408061feac5a8724dce74cc41c91eab62a48e2be25195d66a7f841ab135a56a5bc9e64ce161c3c156cd9bf707055bde6cb889c28afab349a93832303c08b6f82b7a7572d80a0c0efb6edf9123a3c5a9513b53b13eebc8bfa5da07b2a5a72f7391849e472820bae446d0808d5bbe4613d4d8b1c98adc4e6206d90840ee460170a9cd28ebf41cc126b54227adda5dd26adba8fd874dde316852a6f27d7441a2a3221fe46ebd5f780f21d77abe0916a086e4968c964efa937f2f2486e014c3094dfaf186054babc7864af938623ed3c0be3602dd20e37b11bf5ec588c1e1e82707d9bdc922baab90d8ba78c96e0e45556a40425d639e3e4550ef3c0698e541046c47b85917b521ebc18c8398516c0da2ad35f0ffda6fb9abaf0b8b6f5ff2636e5b5086537a6f512a2137c5b209bb94f7b092102067e52426d02d87622c3a86d81fdcaa9b571fae25c5f50aa7f2111859b9bb6b200f838fe36486b935eaf20611f27381665a97afa43920ce3013b62f0160dbb52e64dc9bd31675567b4c8b97419fbcc1bbd80e342847de462911897583dc57898ddf4fe3f9d2fe6fb75187b1021ac35c2c10e4dcb3a5cc2b15911e1812b921d79b3e9d1f39d5833c9de929d0d06ac6c9b71bb0caea6ba90cec9f5b3977d83fcd0412289c800703161cf40b7c89740f7fe1f03e226d97a30474f851d402a88cbad3e16de4079f22e0baef28003cd11a8a82b8f489fd9df0c9c713fbc348202e48f3e24bfd4e8094701159a97f35d378c4f926f535a11c251c7372b0bc6b527fded5c4f3de11d6a120bc44f0525858d99ee036aa3062ef140ab6355000098c37c14e167713f485c28ed1be2e35df98fccc84805f15b74c262beb35c9c148656086a540e2b027f491b6e08ba409b070ca0386940c1cace44f2b4e4a81e4d38b404506adc094402b6beb329c24e578844e0b0913398bfa3741f64e371abf3bc30b737e6cdf1da88eae298f859b5d5937d55cd680bda55811f9f24731b4997168de5e896c1dc83032712044ce90294a54a5d703d40ba73523021214e29292fe4217c53ef313c286c0861ad0b424b76606c183cbfb0af1cfe945b0d117e58ad30bde837887171dd11801ec082574f8331e29ca3bd752582cf4e6559644af3808580304f0c2fd5343b69c9807705fc98db600d52c9cee43af5de176495ac7ee9abb7886f31a1ec5ee60321c4096bd7fa513d82d4de573ad6d91fb1dc8a57ca4d24509d20997618879a585f0a06ee639ca66ffa8a78e5fabed09cfc2042f5ab71e2960df79936d1346a4a1631ce44993bcc6fb53bd2efd5770853b081dfa6944879d822abc8984a6bf1d8b4b190663a001dfbb4dbd2379ec5c21a0b1309713c0a63ce8e0db91079c46525d0f27434e3be2dd52b9dbac8d9dbc5a0dd613d5a3fbf8bd279679fb019ba519fc7704766bc44d997eb98efce58bf02a2481eb83ddd23259b14a9a92bcfa0af23e4739264df77238e2c25a0a26c3fb564b2a6aafdbb514917559c7376c07c62c5aa3da013b9a001b7f19b0db3e6d8fc0727cfcd26be6929f721f204e40dbddfa2ffa45797b08f870f6a3f0ca2738507d98be8a80c2ffe0b8a07cfd80dabbb48b9b9ff9b7eb8ef113973307e8e2c5a50f261097d54c0e435f6418148c733281cc31bfc9ea53484ead400d4462927cc812c5bf2aa664bee315ee6b409900cc7e71baa640079768b7aeed6f41f22efed0792493dba054f7e43088ee9332c1887af7d4e2f5edb9511294f70c4a1f4ff82e3b5fdd0bb3666d92fb93b65590c7b068cfa378dfe5e653f8be329e0f86d60775bbe4fb665c6a20be4d0c995f0e1fa060d1328802fb0846baaaa88fc736bc6d73986c94f9234ec521ddf2fd9fc32aa851b38adcb575cca8b61bede1e81bc531ef2f194972bd712e8a3b807bda5175799b2f9693261316525a824f99b7b0bb95483213ad8c153ed0da04c353399c26f23cf90aaa2c0da4db07c1272c4db047acb8e39636a9dc0d61e5d2bb3b4ce8321146e823a6692c3609fc0b23ad060024c7c8301d99a05f7c418fd2202aeb1724ad969fa5ac66761b1a4e0dc0bdca8d5133f2a5275c4f6fa6362f428a18149a8e551fd49a90263665d82c88363c201e451f79906f5047ecdd1409f959c288f19a5170cd15a8265ae0e43a8c27f52811da31f5a4292ae01acb9e990e333976597e3324061ae3c258069a8bc1696b102014b27dc607222621c7c2c37d3a0185c555903dfcd69c12cf7768ca6643a59887417ff0cd9bf90fe8d3cdf14ae074e56cec4eabc51e723c51a35008124d0a4234a4240003701b3ea2d9639847f2b468a3324650f5a537071f70f05c3ade0bb071beb12c6cb7ca73b24bf4d5e6f4910a8225e62eafe155730cede643cd362247d42b5a200e23f7b6fe46492f911b01f910c05456c0b210c0e697e4a4ad432094599e1268a7088cc5ba9a6ef48b039e418ae512d86e96e86e7e50608d3bda774984e2faa22e2703fb2d1991c2a48c4da7b80dc28d722d83c3369a57ebd8a96c5d88deb65e40812872d43cdbc2707d0f70c67063f06c878010e11b7bb58f863cf545b81818dcb14d6171edb7e4a2f48ebc81d9d1113d95ac78be7aa964b53b0630e680307c42bc2c1fefe16b8e0e748685d9719368dd1b7ae53774e14bc11cb22e6152ceff88ec343e56462344d79785e60bad0a081ff058caac14814e8596b977c7f924260b502170e58d0a3c387b50861e4c615735f69da69301e48159cc60fb238ee2b378e51c2537781bc32d492a0f57aed8e12f296e20da5a001e9094b0e312dec8d0bcba0a60a0efe6044ad3a94553266559d86c1a3a8e4b451fc065aa24995ae40c5ee381ed82a580d983d865d83b9997bdd02c570f0a9879d1158ec059c0010569831e78eb3c482a77f2c1c8fcf227f0d7128a0ac460fa1c634e4e1d84cea337ae915392ca23d252d320cb611511d0c9febd06795c54e744f2c00227959a67029f340f8e8bc51837478f2c4aa5503a03774e03926419530cc3c372fb8249a7a2e5370b22cef2110712c2ba151dd6279a91a8386cbb435bd9392f22c508304b79fc0ee10b5497b4a28594bb697f01c4caedb62fc3f3be271b1c092dab0a4b8a3d9ef890bae7284a0a562b115c5ae8d86bb508878059ab170ac245ac291c1406a79c1edc7d233d20298fba9837868a42848540f6ebd8a06b04a1e91b07f4900a619d8012c4d1145edad88f1c07d49214dc5b75b516aff81fb237d2849890b5b3a711bc7c661e9635d44cb550ea8b883289ae737bcdc871e76dce438d2c415080d4eb8841c9ea3d72ba0d5a493f81574e256bffa7ae6848ac0e75aa0e0248ff4a28af4b49396290600a29a8dc88f4f47ebe2cfe06a2e2049a02f4e45dc4bb1801dc005347cb83ee469e32bd301f2144978a5a9018a0b6494e7cb6bc4918437cc82fdbde1139989cf7856ffa11e400901b009f45d150d020f5e74c15a2fd1a4f830e650225c31604b74afe17d662a568173590ba710edf45edd4769a5f42a709901a0d9132789c77a19069ae3f5e19aa37ab47cb5ee3a3047a6c36059a88e5012621737dd004639218ebb134c45bcf09da211e6dd592972bf3f44894be9ce4607014e4536dfb343898762436cc712947905cd7e8e725151bad273cc7e134bb53544855bd903e19b4e200cfbe4527594d5733937b02e171e311b2e2cdb5bc0989af370095f9cd03221dd0bbbc9933ac862f377dda023ebbe4d1e940875814da93074d84437018cad2abc1a484a26a187d5c126c2fdfe460b46c89976b02857e086aea2e61fba5eed0e4c4c0f1e843524aad3723f5d55180701bb94b87229e9753fee6d56b03e8966697e69b605b941574ecf0a733941d86861c16cf935a2a514f88ef40a870088789e811a43462a81e5c85208021086f6ca5fda9837051ecf0a337cb68ea3c136762b654279c8c54e590041f968e371b802038e36d8c5df3570141671b5cfeadbaffd7fb269b428db56d31174064b119508b971e7917ce6fb5497195a05ce5ac8975bdc266732b280638fe83968a65c7e88e62e97e0a9a17fa728824ca35084c55d88da80d7872caa71d091774707b2a8d3e93a2292db0151f5734402f0008db4a08b7c2d8be43859c006a62f0e7fb6537f6ac8fc43d09b88f122d7c5432d06460b30c2001e340fbb4a0f323c02b8b412a447181a116844e1e24e11887971cfba6638ed6d1bb13062e85f8623e6f6936415eea6fd264d9a8e3551712ef325d48560567a96c6ffe962d00f5a059d7bf33da7fe2279eeebde8433b00ad5f810d7fb625e6b8ff205ecc732434406f51a5b86a62a33b3c35a974df0d0e128f47e5d49c0d2b856718bc9a30451d148c5f51e77425fd6a1c37b0d797cd6cc029a8ddedc020518f7c2c102a34a8b416dd85aa7bfeeea400ec3eb47405c0d611547925780f6a67cf6ec26464b70d8927a20d1078cdc01909d1be1d52a77707355b61ade20b1177861d8ccd01c435d3910b85906c78e6879c02288d5921ee560d7906f18223ac797160110207082dbf5ba5faa0203332d3eac7534ef46162a25742a440b13a882838b091e2ef1eaa0ec1c6286eb15f18c57b346dcb058d93a085ca04d7b24460d148395efeb08993bb5a67c800752d4f2067a00798f67453a921e5672101ac6f0b3dcc15e6cbd97f3dbfefbe76e5e2768d2272ce7782ed67188bb5158f769771b323e4fe838e566a508e3f8bdc84d1475b449da6266a665c098e195ed38c7d54408c954e59b78ae1c0f2528d779380f6a502cac503f89c02f5d1f46a8c56867e5526b0e604dbc1a2195c980d61831ff9831b8647e20130c5f3c900259e15d1da5695fc89aa2a0b1f3302c3adb87f068f868465af72d2c264a28dea23538cbfdacae66038d525c9e6f5b4b1dcae87ae93635308431adb0f887b8494c650b89f5b33d25ce5f5604dcd87cfc4448ec077ce6a152d0c954489c8be4909575b0052fd516c5305cdd9e01d9294878a4e01ed1cc185dcd43f318c79cbce8d09f47e57e1da7fb1741035de0dd0e2f4fe8dcc184da5b4977dcb84f94bf6e3c5c1ad2850e413d075c1f59b376cb0bf9c3ba1aad0fb4870945c70a4f376f67e24b04a640d0c4504fe5f13a292a32e54df1e396046768c641766cd5bff9ba508fec52e54ae1b05a46090004a69995b01d1a754d3c2e4a5b686f413b5a92e2b62130f37f1dff12d29e9861366a1d379a08bcafe3b7665457c56508e642bce2c202256dcf200cd7488f3348fddfd88529b0ab754fa9dc12a5b545355fbfbc65dd206635f5f44009a1a6a4bfb99b0d5990f33dd67207c2f14b220012cebec72616ae02f60420ca6a6b861e9613d4413977f618590e4f5849a82a09657ecb51477076b1826a6861c21755b9e25111417c13aff1d109f3611102f514487b92700d19115c9d0a24593dea658c53a061cdebcc2f93445565d83a1eb2d8fb8e5a300a98af15b30618237416ea8f41902f6f9b496d31e8d6c24cd8f5e9e305e0141dba80c835802e8fd4a86ce0517b2b90ead1103ddf455a75e664aa3c13046eb53eae20d1f0ec2171e531245e571c25da123dc77e1b06063bc2d890c870fb227839556505a418331498820de7f84d0a7eadfc689f63095fcd0a9b96d80e20a96e04d79f109f59daa0dc831b4f31667a84feca68d2c9bcd0ec2df902f0232e05dfd8f80668f0d8863105171586d83e878d3ae9e87562e2e0be0df4a7540bb1f62d22b28975dcb143384c9ffb7051a7377fa848816a1a6165216f299ac9f0eb3c13379a9a65a37c29a08f7b5ca60e703388040ab2e0f9f9edecac72f4450fa95024c13532a6b7679e1444e62fff8a9f4382d198aa1c0e4e7d0913fd01dcaaeacec8411fe98e9772d2690ea211e8733b68e596295fe0bd30851e067f3d5df139d10e04f95182923dbc7a04d6c17de6a5f6c18103cb4ba19d0a5262f85faba3e9aca6cb5fd9b4d3ffb29ab2b45afad0a6d0124b57629d0c0b1e2b71e859fe745e9254dc898a282beed28c409adc68705812561e10ff93b62177f3a71620b2aebec38c32ef21cc0a76591b5d1a22674d002a87203ea46e237119404ed6ce8ae1b39b961bb33f6eea42d498ace063f1a1f2bc32a123d865c1ba36d0234e472b9a5f7bc7d37d40cd10b08742de7d0923ddad89ea3a503b838009acc9561881044293aae5cbbd4a19190f23c064e97eed720669da61f7c0752ac2a2cc7886e6ab17453299409a888059330ee742b6322734b89791f550bb40bb6cd9998ad51508bf2f47e660dc2b3f922d45e58b98c0528ede4515d416b711c7f4a12d30532aab07aed285075441aca0b029060f93b363fdd34e704ff392c5d88ebbdd9c93f34a06842252d65b63b1ec5ae10c3b6d76c35bd25596fe83515d2423c979cb01c199d2c657ffa60acd52b6dbbb5b5cd6a560155c79efee534d6b158c0a41811e292878cb2f01e257b200730a05c943054e2ae1726ab063ea6780f3838782f41d8cae302566cfe669a47ee88af71320e551e06332a6bd73038e08f24f55b0a01425b1a2ef917f3217846ef50b2dfc6ebfbd9f105c66568c5e5c7551da47065e5d659cd92415b739257a668c958701491ec3bb4266a9061dfbe8be448852f25d525debec0316cba2cb325d1fe09dadcbed33122953fed9b0fed4bf6319f810f45f6b1f58a5ab2dee38b6e777161af5b8ea60008d75154d7d7354ecbbb6b7d4de39877797def1affd8b7ed4d9f0fbf43e43c7bfb116b76ac33932fc27d4107fbc987e42fb8597d8c4bf8cb26af6418f2102cb245a9792760bbdecab359e43905034e646a4affada4f844e4536d3c411e9623ab415aee61f215922b004d98c18e71b74ec2cd92d58433287efbb9700ad523cf24aece6b30d8965c4aef6cc41972859d0be6473f5ed05f476295ee252b5ce5fceb6c932d3e2bf695ba16716b8507a87db37545359747b77426a969d7f10f2ca4d06fbc73bd752949be25b6a56f1829087827b2d715ae272b90d5bc20adf6475fe1aaec9cd74790df01e540f8fd3e93ed9441a09eaeb52c73dff7b71b3849b84ff893a0391c9f0085702b11f16aaba8ff0e3604fb50835b140af2a742b352179c89bfc0d4e013a98bc0d816f7d9c93f37b40b68d3b12835b3d1a759f50c8fece6258b5b11e2bf0bd0f9cf160c5d162b51755ee79d26218120c03909aee1633c3757a3eb91cb3a401a387ff8b24c18f6d31f6c92abff1b7969bbc4ad2d5b7d45c2cea1cfa1e622935c00539f6475204b12173b017887401e4be0ae67061c874d90b05495e68ea178adfcd1328ff7c08f01d61e4c389f1efbb1b462ff8e205bfe74f3c75362fdb5626e9f389455ae4caf39b00088fed13b1c3d43644eeb04f070d57e6b620a6c7cfebb6016c0897f4bd7568dc50cf105fbc9311391ed6bc3f9a93467a151c353986e030228e5d69068b9111dec876a91f4afc5c1c13ad3b90537f38bd235e915eb0b85168199f58b4c6474504bc562771f0833b41a632658b5bee28f371ca848f1636b73fd8dbe0fa1bcd66814b7843ed042d6743ebdd707dcdcd39bcb330774a616614f698a01f2617d236e2c8891d4860373f1917d3d4de40dbaa2076d69394033d8dfdb6caf37d1f53a030955cec1be6fc131cbe153cae0df6cc8646e5cf289d96c4e7f07bf035d10702e39742cb724f55299ab3509009c42934188929030682a837e984135e84bb8b08a481a948e63016477d93a377adc2a804cb3278fac40387bd6677205f9b3221b2ae8cbbd43ea3965b58c78be4f09c71ebaffc76e524aff609021074ef9b09a274b7ba62d601d05a50c31869fb23315d6c5d195aeee1171e15d20454eb4da390970222290fe81b0c9c795ab15a48da09ec80edf7067dcd1e488677b3546db280fac43a7d1cf00af8e7ba02538e711329d1623d925bff89b63829990a1e878c38994d687d46700e2417f18d087ea7ea9698c432e3101b16a0a4377a356ff10838e9c358a38ffae21e36865b199a215b9b2332bd7c920603e27367724c6c2df1292a9b13160282cfdda2518838b1098ab036b4f3b0b1a2fedb939fb6f3e4e5f01317da779f0e780538334fca2ea1bac5821f8385ec1f2ec32b19140c1c6b02860fdde082e5e446c2c08060bda3223c2e8b8d5d2648744dc435d2287c97e31d4ecc088fc2ce16bf4c05a8acd1d59071494793b80ee41d44432de6b8480b664df153a8020e6c140d182561b8dff0edae34acd750c18336bf9edbabff981c2ab4ef9b67d6e16ec5975b93575221e1a8054c811da5dda424dcb9a37c873d6d48c7b3c885fb395892390fb84e3a38fa142fe24f0ac0d434ab14c25072d2de9cad4943718fe1b228592bdd69964c789abf7390c69c1842ae08ec325b6422017d5d23bc69484b70815219f3077b966253dcf87cb17250b4ef00c621b25e1f5cd123c30f04a99516a831e3ade53cb3f224cefcb900d5207e26797d4a44844451c0d9b067485f68ac6fcc03db623dc99bd6395cde143f0d2a3744eeb1597435ff1ad5bd2b50e8a5ed591b170b35415414ce271129aabfe8fa9558876ee508f92e94eabcd2d755986d5793dbe36ef83b1117a7264cc4d741aa6135894c5f5cf751d4965fd55e3b2e17060d33f45a91583d9f08278e4adb71e0e26e864e4e079a064d3583d96871cb60053f611c0284eea9bbaae7202df7cb3bb10e8565aa827c05b307209c890f73dcb4ea156dce923d578fa16fd5db779597454264772701fe9e9f858758750102f7da75791a8a1294e206e0658efd6c26e8965652d4892b7514c70ac83f0b998d7024ea34d515ab93f85c82ee17e43d01f5b7c48cb13b687d1ca54b1640141200800a2f56911a8b8aa82853069ddaf66012cc3f6c1f8a340bff67ecccf45bfd6a9f031f6f37f87d6d963e7f2e93c557fd3f7b3b28f4a672c4ea00829584abfacc4efc1c1ff78574e5b02a8bf9ff229bf1e50bcdde0603e3cafdddb374f19a5dbf4cdc7ec994745951d71ef4f39946dedf04b483c4e8d9e39da357bfef40ee0914dafe5369b2ebef0cf18d4535cf0ee296ee3bfc6bbe9040f71cc361bb368b68e1f3b38a5d0f35b357d1f3e7a54792d9b1af5a84f94d3616e5b9c577d42072adf36e96d8ce1f56151f7845991e93cd2a5f023c42e254cad1be990a36396d0200a21116a7afbf10c308dcb202fe115a264f12084b31b32e1988e892556aa14b68c83d657cb9fd74d80fac6266c19b17aeccd579c48551421065f59f90b0eed6e51a8d381e3ebd2a5fac1e93edcc52ba959fe687dd0bf15a62d271691236bfa5614a2d4bd80a91900b91bce59b227ef8b8fbc2bb3c221b1c9d4afe1926d7fcb90a5193671d2c0bc7f0f9fade4a8c742d8e9a8252056f937a771d53cc75ae83824c404fdefb0dd6dd4ba93acb1c6fcce3b7e0770705d6cbf30c9cdd9a02a6158afa4d92043ac6a2b05dae97856fd0008e417d6345036829d8cd37be6322a20bca1cff5ae6fd08c525c8fda31a151fb1661080cdea5858a99f3a5e25552b194d025048441370bfd12bd187aea4f08d8d340512e61a7823723b48770c7fda02cd45101b1c276799180bc375a6910383f93b60e62789c3407e587322ff0258d8c25b5ca3800a59d5d61655c919dc3ec1d2cc3de6da90a483b23c0587dd8060f586c72d02525b67f6d3857eae8e80ff881660026376734314003ea52e3a3654ea6b3c8b0877266d6239421926fbd2d53dd87ab40df0c19e466c623534acfcdd593eb5f72af43b400712ab7799aadb6f6da7e95c97310aa43d9451097f077dd6cb601b6a4724410864696511d90ae181b71b23af5bb7a2d0129da6d43ab643a93748c6768b5f5e91128106a363e5e020342a7661e8455dacf528d5b876b59a711597cd26110a0175b8daecfc4c646b395342debe32b4028e4567b4d7533b4a5bd22a7eb48187b1257b53e49356e85058dbe0f02a1d4fd1508319bf588ad3e4bc77a04565a3e27353784a3469f0752ed1bc76b49bae667e9d6edc955c53ddca6e9bae21e56dd0f4128f38ce76875ebe3238044eb5668d0e0f730a5b9e1bc2ea5d57e226dba3dbbaedd866cb4ae57aec32633d5fd150cb1ddb84aa5b77d4a686f0a071abf1ea6b5b74e579274ed0fcf408150d359f55be735298df6b3274069c3edd1752e7d732bad8f8f808110b3d94fe1aae62789d6edc95522bdede3293020746ab6eef674ad7219d6738b9e31a7cfe01564f314eda535a6bd02c2d161d4a8d18df93344becfbec845f89fd4a3acb91f32e5f7ebdc5e2709f29f0cadf0bc7dfc0759f5707a7bf03fd90a0cd1dfdef8db5bcfff255bd090fee84dd827c9da6ed737f704e010c4e42ec41f7b77e82f320b1ea2d33fc92a70487f7ba7fff4be1f8028cdbe30b103431fbaa0ab7fbd88c6004fb9fd4af5e0c1be917cc85d73fc4163f909542ba244883507167566c96021699e0754e8d9fc0f5edac2c2d4046b4a192a2681e1430b18d11d2454965df7dbebe52e9c90fa5e0de7efa61f5576fc316a36a17166d84f674080348781eae1d840772156751734d28d90cfd8bdc2cc791f874dfd7afcb572f7bfa39b94fefa9aacbeb2e6d8b11fed667f56b07329463e306c5efff263ac4fe08deb47c963970032bb1160c1d422119b9ad2497ed8a37e040fb983ff5af2b1fb7f80bfabed6dc251e13015dd984f99769bbaed1ec83c941d7e880834c29eee585be12f8bab6978e8f5ee0ea216f27e9daff5079901da399756d327c192f6a48365d869880322e18c572370fe3a0f724bf22fb7abc157fefada15473cf55fca1653a56a0464150784821d6fba4b0b0cad77b38815423bde72eaa24b5eb38a67eb1549061309d134405e7b9fd1d8a27af697beebc6dd580c624a21d7acc42a5624a22ce972f16a4a7c9c401b73501a611e07f285f42747c157ae801b8a2de289924794f86779ce4725a62d91aead5948dccbdd752219682119b45bba2d0c6854b04919639c40074ab1907f894662c258b4c6268f2ce49acc42c9cd23a5c674591dcb8bf0476481d205f8dcc8e8d0f3c89f0c2b5fd1619be27f8accff0745c3f9525a6865b10885b359a4d42f1ad2ad821a1a64818cd3109047398d50be4a96e37b6f5e9262853ee67371d1f8fbfa4e4d0ea4c31cfe6bc85dee47fc7439fa2b78ce2803172d9c5b5962665b162b28422cb943cf1b5b9e78b5baef3a517a9a7dfb372b6a7b8685c5ef8fbfbb21e8000d7be14c738ccb66a631ef7584aaaf4860d0740bfe0fc21dde115d4374e8d5da5d58e96d319179a91168d93c79f1d621bae6d1b792bd9f90812053ca4ed4a90502cc2536cb80008f171fddf736df8a7a62986899c8fd7cad17028b0ba10d08dab614602261ae2b98f31d3f4c47d68de2abc3eb6a4656437eca6b205bea11d53e8142ae95be95b3da8cd18ef76ddec7cb851b7622d506a87aee324bf452277d932e4b7f795e41d434ef7058459d3d0eb7ec64ee8d4954ea0c4ac23a448a20d5794dc2c380d9bb3cd28561104a0bffa92c15436b516a7d459e265472d9540c1c849657fea4b25db5f08d377f8b0a1dda891a8f582d5a585bac33aef7026b17b73e43d8e1f540d990ebb9fdaa4399a9aa65fd4b406bef88025aa7a191dd2554c88ab39a93b696307d3656fa01474cdaa63fa00e04ef19ca1d428e60e2daaafec19b7b505fef43dc6731a6c9e3b8bde6953f40b7678c6970b825e62efd77987ac940706b21f5bc8b4e69a19b717a4bfc659f88d2579cf97781dbb9134bb82dd5ff48752606c331234c1a71bf3adb953b59c563fc752e0fb1e3982f83d01845e654fca040e700e69b193ec82fe3e38eb87f456078e88d5390f0b20cdee94b3ec55c343ff114e57efe2fc35ef73a7d62848ff7dd405f6c6b769349df166b12aea1338054674c358903cf8d8b48bf737394b7f744544d2feac34c37e72c9f5a59bc2d557d23962623bc62fd3efb72b6140e3abf81fb865ca7368ed6d46a7a85f224ee5e9f6884aa0d9164302b3f80510360bf169168ec1a5d8b38bcf19e32d84fcea3be09d1bfc7d701b3fb9ed1d0fd27d9286928501bfca543ef61ed20a1d4bb7631c925b9ac320c5830d2cd81df5f8e8e0bedbcee622a6ffd305a70de82a53b876859b3abebc5c901de69fea666d7e609a0891b7ff40f467483b87bd967f0c6cdab30795e5d715d559c9fb8fd95b789651ab5318a1b92cdd0db88b63736c3f12e009631799c125d08ea60d12ea03e806a438a3557d2fecc8dd28524555f863638676a8df56ec46e602e7122d517ab4cc876155f1fd07a70f50f71cf09a230f5c8864f3f8cd2325a822734953e5bd85aa3ed48d4772516d543e691a7c759ffbbc39d0375e4972e4a8173d7edb990a66e54fc1212888ba79c1581d7bd47e255084917dbf56f8d26058f929534ffb48ac8c558bfa9a8b9db2722c52df3085e1ac2a87660af55c4d5685d8c849c8634ea0cffda4d91ee1674e2018cab89a554a6f8a7d974ecb45ac30513631651af6660f9f45c7b1919bdcbe29fe62980ea659c28e58ef0f6440d2a59b764c30901cbf4afa9d72b9ed69d275310f02fecc99548619ba50de5d1252ceae54035be7437774dbeb7799901171b54ad0d6540098b1667e450fff2ab2e125998ee843e44e9a6ccbaf69aa42a7108ca0ad3db02fd1ce0ee376ed3d0300b7fcb421f11af3330032b4d9a7f22415f3cfc5761776e880566d7ebb32956d0d9a33534d1c3e326b9faa744da12c1a491fcaa50fb5271250a65ea70eb7a5d67523797dc0ac05b7593602a46634c29781b6325e36949c508e8af3e76d84f534a897ab4ec3c124abaa0e367cdf1d06b97780efac5528e477e88ba1b87ff8cc8901babf44e0b19d6f2f0e551212d12a7c995638ed8f86457e555586b1d89ded3506731853815a27fe195ab9dd25788de93121aeed1eb4bf8172ee91990d8b7c9b3dcc48e6ae6456b68386e3c91761d6bd0d43371515b4134bd8a59886633ffac12576c5f3a09d7424de3ffbbbf4793efc90446aaf3684ce09a50e73430aab60a5e6b588c99da6fbe825c7c9ff45449df600b20bf77ee9d039497d61e2d8ab6ed6ba045828635d4907ec5c7f580701c3049b80284817bcca4506eb12cc7b54e97e483c21ebacd67e2ca1b565e119d49c6e84813ffe9da21faddc3c4099a151cbc04f76f22472703e6ec03f218c3e5722c66d3e0988b2f49108a13f32f5445de7e68b10373eba433ddda86816c6bf2acdd40bc07d9cc31f4fb93e4f2c4c0c41d57daaa55b40b8eda685bfa3be43ef755d875e4150870f48268eeffa84e9f725ac38360fe6c036d711cd0d67cbf85522a2c3788fa84d802e485ecf7b241858c48097f055432227d19ad9b1d8a0046ff16f668c50a9961e4d69fe24f2eb0f0d8a215c99e104c785c4f26970e14a788c8ae84f3b3cf111f016610b17805e6bfc7509dbe5111a3f69f7281235e1e91ec8acd47d273ceb0b05740cde61c2c8120f0f110808bd24ac59d11dc3821aca83849cbf868118bf0d7d3f98633f4a407cc4ed7a43d3ead57bdaff4eb2e5bfeb932fba6af2f7bdd0bd4c60deb887a738fcc0dca647b4959d1ec76ed97f1e0b72edd5a63924e0119a1e0bda7706e1122d0d72c3b7e0802876cfca8714a0dd5fde04bdc655a8b8a984e063bb256ea33c6caf8017a2d3b6193a868f6494b506c944c51740c6e1fbc69be9c859daf3f16d2584e374f35fe6b8521828f6023f939c68842198291ec47d82d13c9355a837cb49d75475a6af0c81b1bdeb81c42ddd4b186ccea18a89ee699bf8c0bf77c626501dadd9e59c4ed5aa4d9b03b8e89121bc67dd8dd84ba0f3c18014b75d6a7d5f0eb0981546063bceb4fce36e1d4a869a4d35e10d9cf3b03a7ffce7e8dd94c45f83688e1fe1a4f8f0b3712865e2ebf17064cdc3dec5709d3a1a1b2b2426f4846fe4c19e7791b9b3116a2cdc3cd84cafc528a3bdd167a63b372f9721a46aef15663c7e7615d6023ec6f3a368c1c24661a128d0fb5f5ef0a34fb7a68339d0ad5a78bd3a1fcf0561291ada4fd1269709e02076abe9eb848fffce711b73ddd557f01aab18e773dd45825174035165107bdaba788abae04bd3b2bd56fe1b4107deff3e2e1eea6bfbbfac25e1a277c02d4eaa3f9788a02750468e6b09a77083a6a047e7945c50f979a82f21c49b4c537359682549599251ce618b011554411cfb1b244d6980da2a69a45ef3d1c0a708003e96e4de7e4796bc52ab1ea4a2104a50955d5cb989469db0f2d83230a7024c2be5108e72a04fdeefeddf883ebd6abbd52f757d6d7f25e51b7bf181ed348a838d86c4a54d208a2944326018cbb01fa3b254459404c31930e92f708f42ca7254892a7b48a3d650cda9c609b016bd8103ecf350f35795247f6966aa65ff1929d68ec07e65df15ee9a609331e46676e78508e2bf29a6564abb910a462d88c01fb43d329d436d964efbda59432c9e20370049f04f603021aca40426d32bede257ec8f48ab60a81aa4157d2ccdd0d10cb4ceff566b5e6b9905716cad83dc0e0d21ad9839858a2bf436640a637cf6a0d93600bf996dbeb02ac4405742d234975e5402412c50464504480cc5a6b2d50ad592f7119888307d50e90fb8e186480ec430681206dc82fa0404005f88024401e12db5af08548ab2e0d13bae60908b556fd839229dfe8960a7c7001103ac0554bca21df9c3dafcec16bb797dd25d28a446b06b6edec1b570f6cb01561db85a264ebe11066b6b62052a135b97a259feab4d43c2500fec1ed5e85cc64ece02c6b00aaf7822ea2d60a921c3292946f690ae2fa976908fae1af59e2065204c1592e69e41bf8a0830efa0c04414b53d15e10ec85e400f2e74852d8735fb200638c31c618037170dbf75a7b814a24510014cc8081174a5a59df2f94958562c837bfcdca0ae2b6c13718d61e5435e811fdb7a1efa5ea1544b523093b8ec837f06df50ac2da0dd5a94e6bf7f42fcdda06e1738c3fecded841f7f97dd8bf8afdd6b63e0bafab9318804c6f05d885dc640d35087f4023e4a96e8bea3178aadb92915dc8f549be799341ac576f1200f9067ead20da0b05d666da4276a8d6acd76acd0a69c9e550adb5bbde0cb97a45412cd19f33fdc412fd3fd95620529f8c2485c9183e02a86660cba50c456c2e324f7568bc7ee8a90e0dd5d3f184f74563165169108d8038d3695175104559a64e3d3104aad72b8ba73d05b22003183ef5b61d54b7ee8d2558fcf55bab5bc763071b209ff644d7eb4ff5c1edd55adbc482f6f5c8ca245f5a85aaf20c8492db6fa05fcf17935c2298e905bfc51f13f4afd3a00dd7e96f28d17fc90bbe4978d0cafdb70bbec1bf8086d000742f19e47311155ffb915f4692aa640c1f416b0637f0bb56514abdec81d8b5efaed59a50967cabd5a8b5f65e8bb3d50bf16624297b9d00e2aca621f25497060a83270106d5da750e252e3b10870aa2faad59c1187b4c53d1df1ec473ab1848ba3456df34aae3f27a3a5692b41949aa5a37d1dfe51ef770a5b49a91a4aec7f01152d891a4c09a81e79e974b39c8a3217646926a135da29f66fa095d095d39096146bfa4f0252a84995013dc2b70184c82de0a21279e8cb434835e07fefab58d2ff83cd208ff256f69895c8b73a5dd80fedeea1863bf98620dbe1066d62a5fb71a84e2e4b6d6f6f5be77f6445f3fb982f06eb55ab93a7decb5d6ea4454441a7433dc5a9b0999c94f240a29c9ed336befe7e16bab9610bf21e857129156db7d868b16d2ebce84fd6df6f5ccd6d75ab5745a37bc445aa5f840d2e5caaa2b57c78b935c560a4ecef4c89a5241770296d41a158ebc11d3e58c1126a9ac5c162e86317cabdd2ed06d696eadc5350bb0a4990293142142c40319c4636a6510ac54e30585cb26c34c149d39c296d284174a2d28a6aeea6f6e53a273e46f6559eae810512acb058cd1a93107690e4ec69c11c2847861533170d812dc0d9382b3615c706594b892c2bc4018be5e589a7d61cd10bfccc890993ab2b9954b1232ee4165c303abb284bd345d1b52eeac031b9351c360e0c8b026187276883aaef7f585f565c65a7bf157d8979a6fe6799ef7816036c090c2300c65229810b01e198c0aac0a98979f9f9f9f9cc1bcc0d0808dc1d0a04183860a464adb86e9086b12755f3f92bea270412878aafb52c266cd1bdb13dc8604dc068f0fdc26076e03f5c2060449f925c3502e99626392e304cb41222748154521ded49299941997b032615c1efeb4441b95af1f5f510f546b12f8baf1851bfb1b18f65427078d9c3272ba7cfbac922410c3e8cc419a830b81ad6a8a4d08d5234629cf17374ecc4c098788e001cdd5e71b449f398b111b3e18dd8782e0504dd0af510f35eaa1c120d9c7212888839d7d08b3f73c030db924914762cba5ab0a98fc817cab0501790909faf1d5f38826623fbdb42bab6bd745064806441b946c6b926f05b8304010e95714ba2e23d6b69567344457ab37f26c9c2ffade888271f6edb55b7c5104dec8fb69916948445addaf83f5da10883626b97d66abb61f41a5add65a35bcf55aed07272c6a42d040c66b3d14eb060b55cee6d14d74535a67ae0d9493ba99727572907b01038f4a8e474e890c3b27d8f9e5ac9c2470b016c02a1255c74a8f330e948f25a7184e30e70372755e9c7abaebca19811c97ad3337766e39c99cb95c97b37339b293f8e4a29c5f7250289e3a6387c971a08d0b4d6294f92145eca90d1593932cd725b40e387dd4545184349533399aa74d2873684ecac6022f098a4e812125e5f4713a97b301b90cb9761246bfb8fa044aa795a8988db38d20c06a03a60db927670b66fa85b3079133b73586926918b4502f37627a085503e6dc71622585c8751634e700725761de50aede42d784625f504c60319090ece82221e754e46c3a676ec9d94072d4ca996b1c58ce3267063406addd99cb79d13bfd8467860a678e3e69ab14ec60da0585734c379d39386067aeb3c6ce386797e91044a95c600024272dcc7d9d09c869c9d19d9cce33078637540addd3317427fdd24b754c8f8c335752acb042a72e1b3bba8a9e51e3e53c72fe6893739c233820c849bb7214a4f172774e30c7845ea15927f5b2878d99d30590294cce496cce49ab206970d234f77e3afe63932729cca163e3d52442209783a2d12ed66439c3a8983387651627eacc39e1ce38556270985c4c9933c2c3f19ee73645de9b31c2db21a7458d156c4128141b9053a4ae9433945983f554b726ca8f4adaaf9a286974e50d3b1998e2844c580dd6a8e60c53995b03e4c7a7ba354ed5b6ad55e55936954d69aadcbb01351580dd76c0c2097df29708e3ae569e7a74f0a07a49f5d587a27c78fe8d627d6dcf6f50ad559f40b68e1b9344d7f189ebd80357670f63a9b57660bdbaadb492ade5d75dbcddd7fb3a514731c1370dc4175f4cded103f286ffda296289fef60c3654d043105c41677fbdd28a0278b4c88ee8e08c5b688a02ee9ab01cd1d9d938c106d49c393638f8032fbabaec50129ba366840d97bcdd7a20fb74c794f5796a58d1c082038569c9c834e5851bcc0404000e2822c606cc489a1f7242aaa75b421213d3d10c0b4e06a61647f7de3434dc5c11588d45dd71fa121287ea6a6c097735c2d0b0b9710fda3181799fa73ba62a7ff35274022956a8844a035a98e244513903c74992375e5ebba5b1dd52938d4d8804a703e378e06c06e2ca1a720e8fe07078876b4ab271ed70562f80a73bdc1010bced374afd5a15d501df5ffdf12561bb9ba4fd2c6e5737698ea041a3956fb91a5df022b0d0a0dbc988b3938164cb1af2435503638cf1ed70ea94f1c0b5f8763392703632bb195ffee7e96ec694a4322cd8c6d8c6ec94bcc031afafcea12d50e37e754aeb0fdfda1fa291ad1be991d65a6dd0c7ba881abe25e9102e5bbb530af3e2d39d52162b0fa8c96262430718205be10a6d90feb651f29eee94aadceeeeeeeeeea63a6a8d3e25bbd80a6206d415a8167d6fbd679f375a073fafe14802246bed4d4713f5ad8bdfccd2ec539b01032e7a3cdd291d79513692189293c3d184b54e7a60146cf28215db7bdb12b5c57e6ba7b576f3a8607baddd293d398db0d65a6bed968c9fee9470947894722b28c9f091f7fcc5bb24342f7bba4b129382dbb2dc005b0ecaa15d1294b74f77494efe56ba90e46fa5ed04a932861f729ecca35c799524c3b694d4428d18d51b130757a9848c102fb61956d23ce14155fa50a19c03048a470a37cc183196cc3c315e64b1c3244e0a6aca2e664a295faeb0983daeecb460735ac1dc09f92ac17684a3ac8241030e0ca43978ca3a5f43d8985c593274e52e0b10296617931f53259714be4c305fd0b10389a9dc428a619a90472aac44a16b4c2b29cc912d245009d3c47603898985c8181fb67387c9e6a849c99152a9678f39858e0dca4d0b68ca27634aac2e495e306f58915162a351c47c8342c906aadc72be98612be1858b72471d17e0b84893a38c090396c9c295f20428659e25a610236282bc4082798e50da71812bb5b838ba72078a9c2f6bcca918cc2c30b61830d8784429a5aa4c24165018d2a3470c39b0d43099762d605c19b3f5009b6a23e74c29b6339b6831974c797281c891b284a812cc092f9c6cb83d364cab3d493ef4849993c5ab44b265961163eee11255a58472528a49a2470acc0fb127734dce84a1474639a6a785ad8598e9e4cd132e3364a47809b3624679526651c551c13403a424c3c336c20d332acf525925cf0b5b0e3b5de2984e6a82982959f8f294432c5e7278651aa932674c955ea24a273e969e909c34c696cc2263472ae0090266f2e82acf6cd99ec6bcf0a2bb6233e1aa6c5255de99ba1365d2f16112792a879c65f02cd5c073545ae1319bc06c4d5d65922daf315abc9463ae985e57260955361c53e59e28b3ca8789e7a954e1b4b9b8b364cbdd392aade03901ccacd2d5c25679648cb98217dc9512cd559baa335368a24c2c1f2d9e6c36ce387696ca37768ecc1c1e9b0e302a5d495b25d718938917b3ce95d2cd559b2a38536ba24c2b3ecaaea71a67589d251b8c3a475478cc12c04c2e5d2590ad126c8c69c38ba974a5c85549a76aca1495a812061f604fa69bd34c23b654fe103b3212ed9e5bdae798835aabe5bf0736509d12580d3a005aa7510fd5ab530fd53fccc822ec065f39f8aa0297b556bbc90e94be1d010d61bd7a394443d0c7b7471b4c82bcbae184e71774a72d4f773ad63c039eee743071acf7a7bb1d475ebc333be219fe661eed5e0ad0f71cf44b43d06f4f71ab8ba8200e7aef44fdea462060f1f445d4149e57cfbda741f6e2f0edba672f066b2de8b1cc2fedfb482b0b41ecd73dff2ee897e679d05f32c85aaa3abbbf5eabfcf5ddd31a11cf9a7641152b8a3eeb4645df5b3ca2c0d6abd75eff76704c7167753c71ab4822530fedf4b675f2b9f7f50718d93a55cad741bfde3444e8177c9987b4a29f6fda4d067afd79f8b6980e281db86ff7c6beddf37afa2c76fcf8023cdded40fa158d45b0bd968d24ba7be620f8854dde46bf79ee5303123978947a18fda3c93c9016f41f088e34702cd16e5d846d1c1a5ddc0dfa6d44e1b9d7f5236ab7fe91e2877b3cd1ee919e75fa91e8d0e4adc353a71f60e4a29f6feaa1dbc11c93173d0791a72f6281c23d7d1c632ff3d0739079d143bff9f82d8721a9211465b4998fa1e8216de6e38c06bdcc43da8d05d8e1cdfb79a3fb335f711f03cd380061fc83431f1f692038a290b948fed06e78863d4f45d1087e74a3eb9ee823f369998ba3e8230d4d84a4e83ea2288e331765b4cf7de8480b7a198dbacf38862842b0d6c21926c70e2e7bc1040791a7cfe28b3ace53fc2376a03416df23099ae74d54bdfd03699edfebb74a3159d46f9d08457bb56c70407d88a7bb11b27ee714e694f51d3aa8e8dbe79ed7cfbb794ec7122568d1a2858bf7c8d9075ceea7ee7d14d3a0ef403d50f77639afbf4ec35aefb8c147269bb94fb563079ecf3ae4b16d588915fd9f7f7091653ec296ff5c7683cc5ff1861ddc121b464a650ecedce83ea52e8e764421238dfa3b45ff8cbcb5254bb47b200d6560fd3c046927bc15fdb3b65cd7ed05afcfb07b8e6d4561dd5e9a11ee6641b463ef89e0f5d89b4c1396af1e3279bb3b897cf5d0c51fd0fb1b4d84dd32df50d44fdde87e7bd1cf37e9e2471a759346fd94bc591abd61577122bf08d543ff42af5eff0b7d457fe8962422896827784abb854e43af2e86e058f4f32250eae1665358f2d63145f5108734a3fefa3d82e486131f1149547d453f2eeabf41441a74fdc3f5757077425d10cc65592b55dcbf95290ef04d0e0de0c38d6cade592df62aa00a7d4baf776f616c4758f8487e997a2c5ea15df4a5e3a14f68d1eb0f803807e1d743c9e188bdaa81a358aea449804bd2c22ad208aaedf0bfe54500687d935cdb94d5d1f3edd35698979156a674349d3111b53029023a804cc983162e8146d800093160000180c08060422912446a250dba90f1480076ba22a604026a74a0261280a83180662188461000040300060100041188ac11854fc696ae111e10cd10d4adfbc73855d6c5157cacdaeffab8fd83d5225acdcdc68de077071832af5d2cdfabc90ca8e2f7f8c1279d154a6e33d9d7558cb590c132fc61a6a10901aa598ce0cc1f20c0ccb2640a9b6728ea42ba70bcbbf03fe651d285140cc105c091dacb07029941d5b2f1e33e25c8b669a1dfc76c6e6ac9840555cf00274f7292556e0b781ee185bca54e5e5559c2475ee2120161b6c648dd381deb760b75ee8d7b9eb6ad3e9a0d46f6f4b2f1cdee79429846ae5f5beaf9532df5bc74d7d3181fd89326ed2c0238d99ba2300cf3b1c8729d8563f61c925b8630918259a2b756a1069717ea2177a486599dad36d5b4743f8d45b02eccf814f269d4916990b8615c0150c1805a6a9cbe9367557b336dc615b3773ee07ec408d282987cdd98a249f89508a3bed15e2d030f1b7c65196289326d671f83f4c878ac08855dd3d15145450e449dcc2e46af93e83280e881ac1020d2cc3a45c96ef1714c1c9b4ce3e988238eb9141308294b8486afc40e39761217f810a6ce77e0eb6bd62d73bb0bb5e9de13343551cfaf0293203fc8ed9a3d3179c0fceae403a7d1ed6aba382889dd9c365da81c119667005bb2a6264678abb08f9c4f86c8377f47c0c10759f5adbf434f2d7a12ac4ba24b2c88528832468a6dd6d7603127a32bc643541a8053100f150193110540072110aa43d4d604e40eb86f280a677056eea8d98a8091b66bf68ac3d5bbfce958c2bc68ddf448399cb0e2736b27cbf2c090e7c3447f81609b8fa5236dd012b3b2e7092ec10984d1d100d3e4cbdfacc0a81f6b55b82fa622ad9fc8283971daacc47ac8a602cd2167804472ce35b200b78f860d56d07c8bf43cc5c36226dfe2e8603a3e007e1fd3ddf6f00b8a51595b6d6bcc8f6586c39f7f950014c6acf1d7ee5dfc5b21e7639f2faca85c490d85232a757185e1379443f4766b451b3978dcf28afe121259f5a428cfb9b8a05fd9dfa241b6f91df05bff4a848e24d692ed71484f1d701dbb6240c88174a71a434aad0b97aa508af2a69839902773466c15aa40a7e8ca38ec26fb4e9a88d3ef3543582d0950342440f1d07de537c36772a18692039a61e08724eca09b2e20a6bfe040f4c0cd8cc82f0e68cb5b8cb10f300857a0de483f3e358fafb7ca02f03c68657034be02aaf97dc25cbe11021142143908d6ff1517bbb0380726303058f17ea7a30baba110dafee98c9a78d0289b6ab0c425e5b131539ea8010898a0ccfda467de2acc708de1b9305bed8fee4859cc9b998872d7c3d40427dc96ccc69cd5f37ea15ab0c19ba41203c47439b7dca07b3e5849fd63221580870aa7f1b82d297dd3be103b97117fe3305da99aaa26500d855c84a1bd93cf9264cf7434dea47152942bc4684482632cc0d2a8da1d03804eb8638036b6985d590f6dc058877b64e9487a434499188dbbc8fcb1a8e226da167e70e075fbdf7c048916f998a9f18a96f32da0bf692e218396ae83dc27fa11a79077bb774e01c0526fdeb10ac2d3c9d01d230d23de224ab256f81d95cae2704eb805268241aef58b49834cfce937034b68750c284ef428d3655ebf60466ff44253edeae876ef04a7308533c65f3a2191604fe675815ef8ad254ae12403751020103a396798a16f409de6c177c113fb874b652893761f5aa8c06d4372431fc9c64b4394024a02263f7e842c4a28b36ec22ea43c877f561526c6d4223e0ccaaf6eab4b6d5a12e11af82674ebcdb3e5889b5ee4eb65b828ceb40c77c219825f6613dcff4ea2a0e0d16d1664db8be1aac149300db70f56bd3b7d8406a156b11107e30d626d10ad5fbb75bcca7bcd41f140fb3421d85e5103ea54945bd62d5749a7a8423c7360205160497a5d01cf00bbbb96cf0a03812aefa0931a6d6917321ea4591dd0b0511ecbff0c3d2f7b4c230ffb2ffff626608b94c6fa52a698bdb39feb55787a42fef2ae9f537adb7d501c308a5bdb9b989a9fe6b57f3b49ae23be0e1cf1cb01ddfce3921218a2dc91280dbd7b89357640a411a0b9f52a181364e7790298afa9213bde4ea2c53f71d4bec974d3e234dc72a791404ec71b1136e8c7613bdad0a6905f138d70b201d85633374c3f9731ef882e7a96f0939c5238d25c881cd8050ea74cf1a34c6df452c976e59fd4e40d99066c1ae90d6dd1352bc401759f0e42a8abf4adf5b6a0631ad27b030c6507a22a05c8971705026832d817aac4ea137775eebf5199d576358929b989bd6673f3287903d8509ff64299b3ef273ef634a9e55c20e7dc0bc7199779071513d208746ea4342d61b51297f052a79c589f141f6b48fb5885a62609d4de4aba808de60b2bea5497b2599df07bd949890766321d1fd058489cf8c6007b19694f57d25fcdb1a1742cb12875d87a381db63700d6f236b9dc7a2cee5c5e7f93ff13bb92852ce48c39ae53f3c1440ce67f81a7744082950d5d7e2a51a26fc946437189e0e40de978a6ab75ee422d02036efbb1b301b35b071867907547f39288f674ea7a324062ae8cd21a2f510d52a46d6ffa945b3991c109be2ec2d87bf52471c8f25b4c44964ac3864e66c12b6cc4b21198f8ddf31b33da55434e883a41ac67fa30a872049cd9cd3d8845b4de12aa4f25ff08c414f9e142d4bf7ccb32ec6b4830a392ee26f58b33cfd46426017271918797bed8d8f4bdd09569ce95cd8224c843345b67e4d1bc6643d39e1813bfeb1d249249c56e445d599ba67895a07c43fa7164723078dd1d15c1e9a6bdb2759666c06d0a48b9cd36ebcc7aaf844dbe2783cbcbaf3826f36b7bd63331829a9e5a261044adf67f1cc81a01331cc06ee82b106540203e234cc3e20d36c6c69d5ac98c930b87f633efcf8945b08d4a0c9a8a43c37d30eb00a4dbd7b61a925a5ac77bd835c4cd7e3ed79bf85bafc44ec9d609af201db1c6bccc60ce906ad489d9e9674a526cab6d1c23321b4c097054f62f65244e6c0c7b559d85a45a8030ea72505a4fdb5ad856177eaa9005d99a8b42a9885af4d202eb0bd029488a5019b0f31abac32b9376389d784f8d4375dec91a61cba3fda995a65e3d32adacb5ad33e1819f843c288e2cbc0dda463c69b0653cd530da60a1c59792b1ecf899bfa7a9d8fedbca79893e047a31e925855e70ef875be3d1eb4294eefcc6d1b38b092d854eb75036bc24bb5475a066ae517b0c869eb91caa8a24442f68899ac572c3a81c16eb4921d276afb4ce383f741f8086f2210e3f8797928702898b743a53c5b4d59d8141c420e221db033f651634ecf23cd438eddd1b3d9517f1ad3301b7d514b68d7876e6fb230092a1b5eab1f8c9dc942364eee66e1732b188514d13dfde661ae15713916075a818e245c694e46fbad8a73e80f074caee4ec23398a30f83b0e76731f2524a8e1893fbbb38cee05a0f159ff844817bb5e22801c6eb222bdd7b528476a6c1920008cdabace58c39f5035be4f5afcd1a294a801a6f74d42b20aea1440aad492cfe492a60c503f4eff54473b9a1bc1aededc5249cdc72c7d5bfa1440ab67ca8f334ea48c93f7616a9d4b9514357a2e55b32c4f63e698d22d754b10bfb81c97966f138222445ae6959923db6693ccd474dfa64767cdc863f20f2cd1682b1a82f03e652a9c5f080e660ff75e4549610143e1e35dfa75d5bdb1c1bd982873057734dbb6530894b52e6c9912c8b9d45182d89d03208974d0b8749a1099cd4a3c3317a5c8bed30eebaedb7dac9ef639f070642bd6013df7b5cd1e3b71685bb1297cc5853610912173779da50f3f5dc400d1b30f55208ef98e7e401fc5044421fefff76913d8779e11e93bfa38059a96cb755ef7878214bfa9d04526be9866ec0d1c136fdad2da0d20b60e4d5c5d73f35dd8684354afb2b4a44994f63846f3a0d3b5222caa64010772638ce33f271a7f03b649bd8ae339b5a2c18fa4f87398839c87ec7cb41dcda1fece1a80ef23c340593706486b07b1e18d80f827ceda83351477e8ed76d2375135df7626abda085130034f22880e054d6f84f18528d81c5adb32cd85275d11758c30ae049c7225ee1e0311d04db57dd4af396edd1bbd36be9ed649f12c82e1065393afe5f314c8db69cc2b4f8811ca4e63115843018ebdd64a6879c86fd4db45b5e452dd8c3b58490081cd7b08c9d02482c9c20c3d9bcdd15723bdef419732b5c417dbb7f9f63d471a287a80b8563e2c19692e596e5038abe631affae8b8866e02085530f33dc65ef6031ba69c318ee631cbc0515c723d82113835ef1d998c264b4909698caf265a53630897a2a495c5c716d5d8c542e4233ad5d542eb18eb04f0d00791e082b988b574bc3f128f9704ead41cd77a9426844103a98167d44d97ace544a52243e87c04e2d94d18cb89f0754e6b9a69b19b7c7bf4c58b3a37589bae0e8c8ac3e927a4b25190d030b0bed72c0ba734409e3b68097504ae3b1f8a48ae31e323b0fe4e2a4d7027d0ac60acda63a2df71d58b61c418ba08b0ae987e774c204c8ec405126d35e30b300a838fb01444b75f7fda0e2838a3c749c6b9c8cf04dd644420a278bd85a41d38b81376fd08300f263e09622509dde9e1d751835efe0d7d56559986d160d432021dd8df83f3e41e32a9058c54e60994f8cb24978289123316b9679a738c056994a4ca0b0aef3fac7013f7057a4bcedc14765d0ef7dc85013dec130b174e3c13e266cfe592d74496f1decd8598c8db4763abcf863c878c6cf804f4642f306171163a8ee22ac2d1ccbc1474467f20f508bdd7098f732c60c8f41b303fc6113cc74afdb0d8a8af79ca06ded9c1497f6b439bef2e08d0d36a6a03fc0888bd6574017c5c9bb845aa2723878d0857f7ad68bb4a9695c7ad8f94695d6d7e4a38e520ac37b7c7c1862419144a37df5c014bf1a6af88d38b2293b0b2792c52a82f7c90081a0dc47cb1760521b1e52259aa8452a46de052d92140987b1349d1c58dd979e0254d7f54ce0c772c3dc9f5364f3fdb2c89de47b390fe3c268438b06a516647b3aba8e9959401ed982bb16b901f496afa453e5d6b32bf9171c93f48cbb3a2acb7c85c2ff7989c71b011db0e9b6d61b62acf96b3f08619e84fb1a0a5cc13ce9aac1bbddef4f8ce4f285c1b975271cb9c422f93cd869019817d34a5eaa920faf2d4b24df84e4a0bfa8670911c6f3a2c42d6d46c198a021dbbde9d046e1451c5dd60c5b5bd27344f4a80799f54c7526c7f4b2746ce44bd45cbeeac757c26fb9660a3587353c0aad0633aee0e4964d08e25def8072bfe54b420b050d282a0c3aa32fb2b721e96d09bd1f9c48d66cf325de169cda579b32f3ce64c525c773d0f79d54b7e17e0b17eeb4ab505d72114f250d61c5645b96e7f612524deeeab54c6a0bd71bd661864fb08baa6e2a078df09d9c683f13249f2655f4d018a83bdf4562e57874e1e34323d47a49e67cfd546deda6ff61d1130b1da5694c65723f416081d3b6d89f2a7cfb4b0adcb46f6e4fff19877ae73cad32226ed0458479ac746d41a3c22b45f1dd622856eb457ea1e4a0d283276fa9d91131b4e4e02072a17cf9c2b6e5d4953de16e98e34052b61c3edc5efdd4a22989aef84692c3eec2171efac1a43d46c4804b506a4cc3f20efd2e1a8383cd313bdecb2840899b0c49d74f753eacd82fc3f1cb1ee71cc385a84837be940b9a5b16da816af903ffcece73d13f6e993c40d7e9b9190d917046adddec151a39315bc2f9550c75f0c34d67d42321da1b751fcdac4c4de99b907358a19584069f280ed63ae55f3dc88eb41bc1a75abc9490c5a8227dbd6a79ee2d481c4fdc1989d3c4f751a0ae5e14b9dbf4ed15de08ef136c73e517c36a28ff34f6b5e3a4c940e6f5c07990eaab578b23c8faf347eb4acc01177534c20596a8792fa5661ee71b96d0dc71419a2fa94b71f0641058859fc6273452ecaaae827f53be886484806f7d5764fe0c38ced1bec1763a8c6390b2563ab10bd05a245b66c9af386e6ca65d23ca45d8f8ace2fd12423e62e9152fee9079d7cae11ee0c46404c63f16ba36d3269c5da10f215e23e5cc3bb1bc26bedc6c97e77903caa6f4933c07629f00943aa6f11a79c443bef83a0bb521de7439a2e061cc09ba5d8739a15a5196e7d58c9a45f178c59dd9162efa08e0837dd0d3ffdb3f8dc0755158a8ebd9e7d482e1221b461966a1536855eb9333e1abc4a5ad08e5771fbab8389c10e5d13fe08f5502abc93735d579031df138cfd4655067855640cd072c7ac4b62673cfed9288feaa01a539c6e1c2d0783946eae08666b6efcf5ec235a83213855303ca62777f8a70dbd9588bd7b2c55f4702e113a1442f600ebf58410a41a685c4d8a99dcf81693aeb1d7fe9647aac1cc74fcd4f9d3a40047c4d99f562a720041283d6325943605fd7c171ddfa2bc8c82cb737beeb172c02b4ceb2bc618e63a86bd1c3d95cfc93ace95110ba10286d1830410fd85efc91f18a9cad9363b419e3a28f62fbedd09c648f5bd30095a7ac99ec7fe4e9ff83502a40a6581eedcf45a2d591bb0bfdba27f6b2f27486ce5a095102841bca9e1c09043f23b71d2aaec20dff05c63c222536acb6efa65c672062b23fb3f3f334108f6d36e2ec5fe0df601ebc87ec3a22a4944b6e0f34256bd081cb00673e0c6fbb9f47ef25965bb915629560cad0a42028f607b5905aff98c05a7e2ae8f57249a5040da96205f343ce035a89809c780b81e47aa17243d11083460aef2f99e399fca13aca616b53b2f895e37aa65efbf75a30277b8834fc5e873149fd7c666d2a6fa281806736265093ef5ee44c3ac18a198bd0e7b582d253388ec7a630ec1ee924b60721f0b48eec1851c2826900b68815eda050274b4f6dcc7c6dfd4f1e0cea735a12bddbab9a9b3d977907889782200a117f75b1b8778b816075e543571dbe5c96d11384bce3f971772bab81c616dbee9f29a78acfa3c5848b5837bad7bf192660a403dc88ac997c4e941c812dd4889402a349e02ee3f56ad1108e8c824c58f2b8ec01582ec8239e720f0ad16f10b5e14249364631e181e626ab5b04dfba6caec86a3f05f5027171c62acf2e1c2246e4487d39655f218faf1519a4b6aac9eeb5a2b1de13c2a8591c234a89fe2e04891ede138eb07296704451920370205497dab2db1055167210c6345eee3aef8045d2997921ef2a240d9fa8540b8c183c1ca36f44d5e8dee48a79a20a5a314609e767159808b47a4c2e2c0389ac983be28ada68a5be3db009d2e233058a0e9eb887203061f5b0b1a567ae91bebb30cd7a3dcc9b86782b838fc2509ad9ad6cec539bacb0970f19ee0d66d95ff0a3e7de0ac74037b8f9e73046e59f95bdb146499a176687bbc6f582dcfa49013d47a66a0a1a393c10cc2efe68854a1e66d3a25c4cf8854409dac16c0d82a44de006faf8133b9c1bf2ac77aacc6809707416b037a94ace98895a81e7f3b815a2d03bf08f6be3cb2a5799fe1d46fea7c61dbf9f74fd86549e24c686199fef9275c49ecae94726cc59238b2ada8beca99b882ed9c6b0b6ae9829ff8401b8d28edc4d3996bb17f209fb07f446e0821b00f1dd3b00c78c214c09d2aa3d769ee8539a846b1dce63d83e93ff6ef37b4e932b9fbdf5c5c807d02bb9f9e26ce61c44a3184f2754d61a5e791cf609813e68d498541ccac3e5a4fdc134e8fd5062fc075cecfead1b846be30f65e15a95619fdd85a11fff33eb5866150176209bb004aa0070190cb867409e6b01e969569b10aa9d9846f32209423de08368ec038fdd46d29217c11b408c08b003499c526c21eb776ae72370830e2c7ca05178141f819de12234413218bdf0fb2ede2e903ee0610af440143f41b30411f18e7609abf9dcdbec32dfaeed8cced654efe54bb2e828a2330709f3d3816703eab66bd880b27ac724df5108de342f9d13936c40a84de311a120854373bc648049c15579f3dbaf3e13ebcd62e703e6de78f8816e64f1a9712e2fd08ca699f1a6a17899f9a438b78ef5b01c1672e762ef253aa0e43f7332c07ade707610e2808fd8b66c29c115f81fa6a47418e0effaef627bf0e43fc33b5250594d6bb759fdde53eab960702882158e43f747456e07faedc73417faeedeadde2fcc039822196112f840888310501c1a7b97370d11fc372d02e458c7c2ca89bdd92d667b0bd25e8020d2ef19e789ff5ad0f4231021f041b44244255841e842322c5113f0df3cf9a2d0dcad266854014e4f35e0e63f629ddce20d4053a8ebb6875e9afbafbb2959bb2deb13039ee79d52811bb11e088c0207c76e05820f8d49aa54400fd7f5cdb2e9e3e076b2c849fa4db59c43bc4388269884d107f10e940203e0283fdd96c592128a09a7411a6407022d409c102e1134327b4f8437351237e1055bb5e7e6a49ebd6880b7d29979b870bdf02cbe7c78d22744238431c8b3081d08cf823d485c00f016c0486c8cf789d15621f89a344ac4e88a0cf4f7b4b4704a04f3e436f17a84f96ab4680475c422884f808210e61265c248dcfbf8b17741c250cc048264bd21dc6c2874edbf6574b3446a74892fd915f8731fa99de9e0262d16d5c68ffb88f352df89f8a70319da245d6c7f706a31118bacf263a16909f55e383501881f808b598fd17ec2673f70e2de957be63c06e932b281f8c5383c010e88a37ed6c8422e21191e35f3721fce4c2816a7fbeb462207420441719e14054a1502c84b70d32a2c5c347e39e42d4e9074dc999136707f904ba154658f14aff68b140cbb5ef2bfcd06e2915d1e2ff09b6abe2f919b7092116205e208e20cc204c1b01d4f7c96afb84a0c2f4415a3739ea936d6d1174b1baabd20bfbc7eb680c4a1f49bb5b5fe573afc98b70401446e023b622a0405040ec8350cf8816a34fcc5608840294d4900db0df68a55fbafc8c65cc02f5e43cc54d5b118213f14e0446d18706ce0a854fad43477c10aa362268fe73e0e221c48df093f823b841019416a6b9e1c4533e99561501200816970f953a6a9d3e15d92e923f2bd70da13804e0d715c924b9f94598223623a6b7be0844f976408d98a1dd828db8618c9cdfd743e1db1d73004ead119cc5fad16eac880ac4dd0807083128be19027a4b71258e1ff68759f0d66c994b3d18a323dcd18cd3fd406a67b04d11f9a6ba33f81fe1004676014597d65c88155df8aa582d9ccbe104f904bbc208e5106e448a44b460fad41c6a08f17db5444ceec813217e562e078218818f48d60f8a7db20ea351f273e5ba1157fc73551fef001839445f18f8bcd0e4a3d14d5431848ef013f80fd7883081ce1ad144b06810c67adc093bca4985829345bda9710f90e7d9e0b721cb61c9668aeb3366c6c2656071dfc0de396942d496aa8534524d91816a37785ea7b69e1632d42c2a8082f6c12f6023922e74ba70508c6c3d8dad4b0feabbd3855e720de3e7c033df67e219269e088187828f76292525dba9f2f02273c4fa3e72acaeb1f17d5d06ac51f942519e752883cc8c104a31ca6dbfb0d0ad27056cce2c74281dbabf04b164849eea78bb0d563a40e03af63efddf22790ec5ae19baeae5f51c9ac1cae8a9668937d4fa622593724a7cb20ba44965d7a2e73f0a75174dc61806c13c60756e02f90537bc085b4b5bc2f02329b9850037f6e4f22f9af210dde652fa91af1d5f35a972d2c2f29b3ff152ca6208cd670a662f7f9ce18c40877aab9f5a23f04a4b4e3013263b42d0be41490b6735c105bb62f59e221a3f7835e5a12cf6781d742643e8c94c4581d6ed1df6022d46aa8a570c44372f61d8473c759e464480cca582c34cab1a7341215aa45f418e9bd52486a3e347a6ead63944e7dac11010cc17783a52b2d843d0c56cc17d75cbf045e3773eb550c54a4d7b3ce7ca5f0c3b308e821abed32e312b104f1977cb2ae372e25ac8bb2bab65c1bfb128b14497321398e3de11c120044641a31b42fef43dff86cfe76fd9cebd7f39cb4d455496019d4b289f5da96e04d061397a11243a1cef3080f5e1a9c1e15da744691101c4795341f017cbe057837d3978085c2387a53d0c06528895e1e428fbea0f59518a7c0d26fb2674c41453445fe7e7beff06ea43a92e791cf61584c5adecaae6922ff22125a0eefe3fd6b102809473f59ee10b63178a7b177b358d86db064f65b867ca6e8bb5002a921841ead9742607aa4d03f7a8aa58b66c18066cb0e22572294f6ee02d45289e277b0ec81e36d3d36863c9566473e18689a202f15a576003f8ac00f240d1b0fdb6bcd0857a6e7b86eaa8d277b99e50c9399fd48a1cdaa10b63c57506a039eef84e4e922c267376159c66abaaffdbb186b6d4f691e88e01096f081af7add35a6ab5d5bd259549e9672d48f7b1c8be26f6de0cd6fd3eba1220dac1b416752d3a4b46ac7713fa55b3ee3f300c2df0f08eb5992ed682098e77128e86de69978032b80a0f26b909b04c3b546eef6e727836a12daea981ff85d0f6d3e385c7668be1d2e422a859ec0e921e7dd6bccf144ec4fa805fdd037905708732b7f5560810661d6678496bbdc706201ecd62c2b1a02302605dad60517727a20679aadc708d1023db52dac037851174654d5aee99c8c1c633e150c39a0cf06aa4dc5e2cb630f4d492616b288f3ba90cb38dbae6ccbdf9e038dee09d196ee63df81b309f644277cac67154cf54da4eb71fcd3c1f5789e73a8f9eb08d579e2b5bef842f0330ccaf29662ffa74787ce4fd8f1e29dc46133088960ec5cc62528f4dd0109f5720d5fb88d13a3415c1be9241085cc1bbb6c5e7e13cbcc1d5e340e91037b99f62001dc4288ca577e6586e5fc4861e3290fbc9baf51d78ef7f46720e16bd840c451e99ddcf172f27a7b35c6fef2e3c82535c9aa6f9d92384ecd130a49f6454c16060525529f68ee21c439ea9b1193f8a5cf7af1d0a09f4516a905b138283c05ec1c03aa4ed05d274155bbf5d5b7fad5b1ad1218788d6646543b7cf5d2b85cd366c53ce0e4fd7287047c7b2bf1d2a79e956f0af1d99ae73c92dbdee061f71e4f661646b1c8003c4cb356b5f25524ea1fe5ea07196b40c2221dd2abc4158059b9122be16b618c64d05a763106bc07882ddd768ce7bc0650ae0baacdb227a7514bffb8336b7783c048603927abc8201cc87214e24a7b0cffba678726b8d4dbbd590124811b1427c05fc205d37730a9914140ddf0cbf8573b05753da8b48dff27b6f7c7dd5679db2f1dddae9c7af267c218c62fb9a7729c846e2cf29a0f11140ecd658b5ac6e36b8ae8490ddb84a07214e22824bc1da031cb9fe4549ea348434b81c43c153a70ce1cbd08a8bab1c8cfe4b0c052d17ea21f209e1118525c287ae01a8dda41e658d16e8f4c015ba3fe94002a376311e901a18d15a199ab6e16c874089284775ce7594970a56836ca23ce68963c9fec58e96590a81b41b37f17486065e0e3f472549d33683d2ed59d6f968e08e311ed075b19590760400b20c23aca3d19ce1aeb9e844a68602b4515855e5f1b24ecb1c2d9aba5dae071fe50b4863e3e9723be9fcdcb60da082c68f3d7758b48333eff20fa89bc0aa46db3fe58df6a19f7d1d46ed781522c7cc0abfeed35736572dbb640fdb980e604b4ae5c6aedd02051adae4d77c9e3ee6985cd7542c50bed2aa89658e9f3522d7f47baa55e9f13ef23cd98b79447f27f1192ef038aa2bd2cbb70ff4c05e27dd8c645dea92bd070675cda7d7ad8bc0c2e3ce6193428815ebd698c6700de565f4ce8adb9812373582755924d124f5a8dcb9d81b2f44eb2acbb6d73c2b272bf310bb9e67acbdc84f8a6c260a85344ae80e3850c9a0cd28d2225f0bee6bb3023adf19fe1c224c16d3ad0c238b5472fe0cd50963c0140e592e9a25ae7489ac89c109492c885595f80b538264b2b04b96e74b57b9fade342c1c9b6b18c9149665edd7698c8e446493459060e618d80a9b8d6619a0b3c8b2d621f75c598de46004895960b53f09fa9d8cfc9d3d110f0deb8ec5521d51953ef6f5be2b1a2dcda9a5c4cd3452bf3f13190916eb4a5e1d104f1506bdc8e8cf770ae7a2bc2d6ac5b79eacbf4c6c05c45bae4f447058d4e1fc6e79769f0624ce03b8d267841ac2d2fa3bc7c7c62e1a634660ca15eacf306932c5462a6005e08aaacdb4fa24818b911629ce0294f3d5026c1f53a640ce2ecf177011235d0b119c3f957e07f1b41189a265c89d11c904ea69bf9c3ca62ad86a17d9ca1adac34d2da70756712e90dad7a4a66fd6bebd694088b53e77b6ac51a9396af2356a77c5e67a6c53abec0a6f8a0526332dd61942a4cad5bf7a9a8ab88aa5e5fdd9bec59f9a216d90162e1d64b5742ecff9ec71695a1ce4067978eecdfbe9e02a47137370bf21b931963b21002dcd74eb6d227419154b8ef69a70eaf013eeda25eae5e2137cb7e56337b32615bea5c5745d20da9013f264ee084af59f841b34cd65228fd7226611f2827be14ed1b0f5c84c56f3af448be1e6facb0a9dbfe1dd1c8160d56824dc12f5177861c7496cf06868e4106d7e14eedad125f216b6712818fe4ce48606fcb79d6f31a56606384d57d5429394dcde3da5c62259d35262b5c5e6f89c7694e03dff1adbd75687d829a46809679488135536786406d6dc269d4c0734f04f1478fbb1cf39facdc42bf87cb10450acd1c9fed1f5a10e56be65eeb4ccb19f4036de83023047dcc2de521a38ab690c6499c113c0751a8b09c73539c16030e9689d52bdbd0cd8c4e96cef17b8b7fbfed1f6bcbf61f9384aa415394febcc05b1522051710e4647fc027e8be625a2133c691e48a309e6f03fd81af657d13a0b243f5a37715b31d9af8d0e45d98122c0d1cc3f5371bcf25ab655a3ba0aafe770f0ea8d463f18220a522b0827f52685695ffb9d470335e896d8501313ed174b78bf98b3ca6bf0a0f1bd34a814c4153ecbc4db6f4835aa1302b2e41f15cf1d5604b730cc687f502f73dd15a84d490b298c8e9d77cf0d3e78199f63084760959e1ec7fe0158ce978e22e99714840f1f425ca2e3fadcd58b268eba294b89f5a6e799b71dc4a42b47376527678ec25558a98ce88fa1d0db7341bfcfcbb94e1c520e5c608e6417a4ac1dd03d6c6143097460de87e9c5a3d880b770a294140ccd9b4fd4ab73b3b4b31833a4350974b438378614420676ed08f09d0db01b8360f6d51a721ceb74d91bbadccd14c122612c3388942f7272515262179a08aa4d9ce0353c46ea7aa09c4609b7d4704106941a5687148bba795e1ffb2cf3e922ac1f3fcc448131cf3f9cb21b157d88fd6bdb633b8e216e4b3f3e6680d689f11124a8fdf78506f78682978c367354e234dc8dba6ac475e7ee6fb3c9878e62f9bf09bed28b7899423c99758fa49ef322b0851fbe24ebfd2314095c9ef755895b25439a5f7db90a99521d1e7947db30efe84c51b5983329e62f99dfac60327fea0790031d69e1f3fcbbd2b0598b9c6f2d318f26476e15627e26b0d6b27ad027a50a832a892f1abb7cf21d4bf9a6fcfa05a62fd4da6aad5ded61ddddf405196a55fc552a782d2ed840a065d0a925a1b626cc83f3d737e9d09f6af0f678609f301efd750755af7efdf383003f0d10df0322547aaa1cd2c0c19598951da7d35d1abeb3ad4b720f1dac250075b6fa5010573fb8efd79ce826e9c97cd73d78f088f4a13efca13751095d35d816235ac5ec0a324277032bc9034422e354cfb62c12218546c9896316b456f2473f0647aeb29aed0463389d1d98c92518d3081c49116005ed6d9011d45049c448fd8bcb6fdcdf94545df8683984a5c762654b391d629b68ab236b62d1c3a00e42eb673a2ffb48b0b36bba70dd2b1789431130b0bf3837e302d1dcc45c5bab0aac1204fa193ba45410380b4d3a958292b819245d14f8bac8c1917fc8f55059c341d6c367a511eee873a983b4bf2a3ed17923ad32d6439de64465637772e31287f89281863798f56d40659be0800f7dd3d79b10b9e5de726f296592329909670ac40937dba261d3d9a871dbdaf463fa7ed51fb5404a80e39c9a0a95e63eca79fa33cb856439389ee6e070a19c108643c7d400c7c775e792b0bf5973a1d152ed32d7856e48ef175e172d154887a8a492baaaeb615a6e1ced3beb51c93796f6c1b723576553ae0acd48f92dc65866abef77a16743997db9c71fe21be771f4bcd13734a8c72633aeca0bfff74fcb3876b1ab5cf66ef7d5a6a676f797d3b2ee3babf1d55ed85d3b2ae1b2749c032fb0fcec72bdcc433c39dd75e5b58069f4fa4e00ef9c57ca4efeb679dfcdb0e3244fd7fd94fa765dd8fda5434a1d97e4f518d49cbe2e7aafa79267be9cdfcddf5b379299aba352959f8bdecbdc79b7bbf7c7bb3fdfb230b54a2ec27200e923c92453503b842d6b59d66cec1fb54ab6b72114fb5b28af8b339425a994f2503a36896dfaf82505cc7621ce7b99658a6b997d0eb69549d9f7bd135bd68e58b3b3d0b6fc62cb9a14135b695f7de9f75866297dc251dc7648f7f73f5d44bfb189b8dddd9dda9724fa729571cfbd7beeee491c4f8b85116be6951bf1d453a5d0b55472e4fae34de2deea3185238a084ae26e085a0d8b02fd79eccfc3f982d18e76958523a02240ae6ee0b6dbc6e3658c87de997d12e028ad8aa82b1c7fb6cb8ff8228791fc9c1cdf2d6d8cf3321fc14ec04fea66521f078efbdee3f81aeaf891d2211e5248cf04627300ed6b0f6bdf56ac4039394d71266772da9a356ace9c3143864cd69831565bb64c59b102e5e43465324da64953523ac104139628519204638c43115ca445d2470810fc187e8b35d00fe06b6323aecd0487a00892c867cf2417381bdfb81096443e42a08b509268680ab94c336b9a914478d31d379bfe5ce342f8a9cfa6ef4c2e347bfa3e85be4349229c4d71a8f3de4b99d6f94ee31d7aa6af8b5d8ed7d1b2c739f04bb13e0f7d25cf9440ead7d7d1b3e994da5f28a54ff534ce773ae7b117a4c726df0f0e7e9806f1fbe3e8cf458cb5f7327bb77ab5de2d7313d7b6619023c0515a1d7121efe90dcceb91eb73817af4a94dffa547579edea6672e6e3a08c8082af777e40757bc53eb48c2c65ff147edc45f7bb0be17e4a005506e22225025bb3e09bb86586afcd3d34d7ef6fd9824badbb669201dc0ad7abf69ece9b1674cb26baddb05e201dc9eabffd551c9c63e5b126ddb5699cf86c02f7fc7f8af27a1c0b93d97f52bcd37942ee2f00ea15dbcafe0ed019c7b0e000fe0bb726f38d2edddafc3b3da1b5ec18fc318e5a143d208080bb83dbd1fb5c2dddb755dd775f75eaeebbadbddaeab33ee76dde5e4b66db7ebae54aab5c9acca29a59cdf757a84edae0b9dd039b76ddb6ed7dd6dab72e3b83be6a36e0451f472c582e50ea516fb47abb848ab1938366a6b9b61cbbbc3964584d9f793b90817e2efb3fab571feac9e3a838da9576aeb66b9ed726fa7784526c6e9c938c40fe7f85c39477ef0f1ec9bde6f3bdfd41fd6f9808800a5e8e6429ede42a4ba9dcc36738a0daff4f164432892f77263f19b1a96419e6fead92b16f3f14dbdf33c3f32d2f7d5c551a9aed9536fb0373dd6bdbd72527e309481cfc4beeb651bf77c5617b1a16ce328f97472d1a56fe4a31c7a6175d1f33104785dfcf14d1d04fbc13ff473f2910d83f4641b4e282b59407f11007065cb9c645db4e19c62c322cffbedc73905cc36248239f8a65602802418c8373509f9c88623d44c70912e01ba027a930c3030b5d28e137000bea9a1b8487f837c64c314c40db0074c216d0a7df4779aae80738f730aeef931978e8436f87c88b988ef219eef878f259f7ca423c88faf271ff5841ce8f80048073f40be00007d2b74908bf806e1e7f3f2277dbcc701f8bc2ccb49de6fa1ccfb9c2d807ce4e120200fbe39c585f2910df1feac96b522cad838d90bab6be3a16f4e11ca453ca33cc4f7379fe81be5232f9c4f2ed6a2a6eca9736c1ce483b2c7a27c64c3f9f461cff3ba7cefbd2e6eb337ea0d0e85e1f01c311d3ab2f9c18ed9ec0339a55c5b28a54f372a39256d3f43d9f6397be72967cb8ca4fbcfc52bb95bef379d7400e586dd0db65724833d36996d5b6d682483edf5f3e873ba0bc79fb16773494625db86b231ec579bc1a65af6499ffab216b5c496fbb5a996b5a82eb675b18636c97e8eac1151266733813fafdbbfbafe74004309e0323b8ab69d8816ec9752ddf5939c86b2ed9fb3e9d109e1092ed207c12631919ef084e65f7e1550ca70e3a8d4512ff67ece106807d0e5d2b15d86e30cd39ce459eff364cedbdfb8017e4cfadc07f5cbf5f77efaf3eccbbc05491f294baf69d57cdf7d1bbfc077bf7428632a298fddb8db79f8cbae1778e30607862327474c878e6cb6a3c7eb10003b1f978106df75218efbc73df9e8761f11b9bbf0473632dbb46953e41e4fdd7803f1e3348f8f1fdfa6710f8fb8810619ec0020d4dfd3c36d3db6d29e1e9fb2a7a703977db9c61e93a4fc15e692e75d15419c01222a0832fb67cb5a10426c22b0f68f2d6b4358ed4aa368a896a5965a6aa9a569aca5340dd572217a75110896ba58b508404080a3acb9bb13b9aa56abeac90ad3f660cb9a1058423c6d71cb5a106bb610521bef4f6be317de91b01b2fbfc78774bc0b71372fed4bbb727264fa755dd7510976ddf7e1f8d98121930fa46946f052ad3ddf8ef2ea7b6b434b6d38564aada59b7dca86e65f615b6ae948d5d8d1777e3a522dcefbd1c30f36a433431a8e124be7836c33e7b7e1927b414bd350ab471c618e5c84bea568a6106badd61e73502dfb99fee4be28165a5d2b1ca19ddde7e40871507a63c9a537cc09c710c78d90a6a94fd65a6badb539a4692690ede96fda7e1185e91b389a7bfbb582e04534cd8759a25813699a2c5114459aa6880e2da13cf563348d28ce5c9caf43bff32c916662a2199a86a6a15a340d0ed7314505c22116fb99b9bb3b9d651ce642561508876a95ea17f5b19444cf66cf9768b6dcb20675b5e57355f6ac01d1b4e78fdcf7c409b767524e09640338fedeef42236c4774a17dbf3a827ae4be43c23df732042592a64a6f3b241989fc2d23a13fff0b6b0d61341fc90956fbd3e624fa3017a23f5f3cef3bcffb8e8aca0a4b92d9f275bacb5926dd7b45bad0b3dd7f437c6fdbb99fb9c8e52e0ef7b9396673dbe6c2a03520f76e2b13ee6f112ebca18ccb479c6f6ba3ecdf2a5137d0e770b8dadcdf1a7dfb03d5b6bfe16cddd6c1703a9c9b0a62b910dd32270f99569b0bc7efd6fb7e7383c97dfb37b2915ce4de2bb77411fbfef417dbf901818892254d52609af214e5bb3ece61f317f5753776df9fc97dceb7ff26960bd56c64d67ea8dd5ce4825f918d7a9d925ad7b47a5599f2016acb5a0f637aa8b2ab13285c9b366dd66c7f2272bb162ceb102c1dcdf61f97760f4ddbff89cd7623f2e73be1b6943e54c909b7bbcd3db513c9f61e72db943f7fc2c07bb276ae7cabba03399807529a10a6cdc659898d4327af36fbca073dded21ff57b68199e5f0ffddfa34708f5df8308fde043207abc0cc77ad41e3ab1f04b1554b024152ba2bcb2658a45318abdcc0890463a51ae0bb1904a4ec223c849d8564145159f638d2063abf3bb15d8aff81c8b4cc2ec672f33093b7e470826ff949c646dc8491667e160430d5938d54083163748f7e123d320a50454a4f80c4ea6ea471650ba0ffbf6addea26ac76730339a9492006168b31f4880b39f99843034991f10022d480d58440218275537f0f8d9f3d060709e879ee160d72f6daecd0eed248dc81dbfe3f10e4d250c4d4a0250e1f13ba8ecf8998f2410e677682a3c2210829492003cc21b763c952aa49404a062444a49801da14ccaec67dfc6c63cf4ec77e85908267361b513fb1d58da198cfd6a3f87b222b2cd76fd93d1dbf997b62b63133871bd077a6cfb733a11c196d7f0678c35dda2d1b0c676db77854fb0abc853a0d16834fb3a44dc477d223323b28d8d671a8cb5386cec0a9fe4d8f604d73de882a33c3e831732cc204399186488e18ba6189ac080a10986305e80e105332ebce082184c2e30e5246cf318519cc9a3d88c8216d044692127657c06c7340bd3d34c775dd179aceb8e6969a413429946ce42b664d0622f298fb3e0b49c9471194bb1d7797b060296684b46b12f721dac6b2ccc4ecbd8a66085366da05161b6c1861950d8d84a23823342a005822d47432a85ad355c05521f58430d5f81d4133562e06aa43c100336ce022925363068810b3048c3054c69bc604d9417acb1f90527a8b1066317e4a46c711aaecf2ecd819b972e6e4d778d73dfe04c1758db829c84b385c1065c9fed962bbc925b7057c06ca43490df6e99915258dad98575dd32e7f08aa43c210b7252b63929631b831524e5c9af66056aa8b00215ac01c31bbdf04650548086ddca001b18f0362e205d44c3db54000d2d9a05fe7b54e0c31ea1a43fb472923d232759cc0205ce48d3820969cc8852821968982620e98ffc92fe40930217249082325ef8644b76fcec6558864309ec0867a1741f2e970be1327292252327590c0304c838d314813328888104148c21c303a4fbc82fddc71862cce00031cc48698099306818210c306a6000185fd8b0802fca4c5140192f46a0c48b2ea824a08b13dc808013d49c0e20290fe5a99900872492f2982087241370a18301b8d8a20a922dc890e0089912ec60a4045af050002db2782a9245560f44b2b0f001098b2b7e18225d0cafb0c24261399a5600414410578488b212d3d583ec635ac410f6b1cb155abdb4b17f33898b5c4ee262953792b8583f3f969e8b888bf57318640338b3c84778661181e509b847daa6cdb67f84cc45f0cc9a581c1185c511565c215584155255606131a20a24b0b2646b8ff86a7051918f6eb25c4d8184540d381f7531b3f1e3e029e4141629a6a28022892c502431e6092590b84f28e18418a025c0515e0de990479ccc540d60ddae2b464b2e62df158eaeb7a1554eca3fdfa56b189cbc25090fc2e423bb05cc124a80c94776892f390967259af8c2449516aaaa26bce4249ca996e8c00b972e3929632e4cfc74e9c2252765cc0490172ef9e8a60b5513df9d01a832c02472640ae926101da101260d47c9c5ae9ffde64e2154f9e8c64b133909e72a2e1d34f185899c9431123924529efa92a80322457d44fad4072285a414698844f2d42e57fecc3dde2b8998c049782e919332ee126609305bbc7860b5258c962aca23e6d69f5aac94f8e2123de8eb111b1209a201ac5b9c4a88c945ece770cc8f13ce2472120693434cc37c3a74acc8330637ae7141b3658dcbcf96352e519b880bd95dff48123a54d5d068b42b46b54af2c831170b242379e4bec20bc923f7d863d39f4ae423bac19619ebf81cfa4a8c0b9badc196352a339b4b95edd4a963225ba2327d22d3c79aa34bf9a955c66fb5efa598c662635aca5e17f3c774900f4486985319091124f21056b224b1fc9d452227d9c7e1a8c2c63fb34c2c5336768242f1053d05fd008ee2ae37378f6f6ebec8a16c8c839e408bb5d537373fd68d83c3e69ddf7600a9ee1c7ed207f64875db10c7fe9c19c97503f637b42ce7717cce37a166ef6c596b628a7d5d283fd012201d123de7cfd13623c1be1f2fd6370bcee320995bae8b593e9f8b29d68735ed410178e3c3cdd7b7f928e79b89251f49e558943dc2da27827c005f145db48fc371ee6f1e9191e0178dc8491c11172a221fe530c4633eabed7e72f71756176995d207052570d3ef2410fad3888cc4ed22425991fcf4732829a51308293eab7d06f181fa4aec5fb1cf3199b18ee9ab6ca372127e1b32718d4bdb85e9148fafe424fc396492dffe242227616bb3ae547c56fb967428f6ddc7f2db70d6a8a030cdfab0e6f6cc45ece330874cb6d436912d59bbfe91fa236c41b3b7ffbe9f511909fd2f1c71759ac5949b9d572af519e552aa45946a7b39bd7d150a5581ae50976075ae20065886a1dab2f6456b6f59fb5266d620a0a67489d6aacaec9b2d6b5557bb56455535a5a5caf6a3a5cd05cbd98ec82389ec960db1db7fe3426b61dbfbab412fa007023501facf5bb70fc93c8c19e1b7fafb5c8cb9582b8db65ddf4ecd8b43eefbdd9eb761e15e8a73595db57b8abda7f4066dfaa6b0c7ed95e81eb9f7be53a2bbf33617c445a0312067eff6e4f442ee6efaeb026a27773f99edab9da4b0bde7f413babdedfd7571ebac68b4cde94ba3ed4ddffd4387ae91bd4c8e65e3fe93443226f37dcf876d39cebacd761b4b1fefaaae9bfede76a48f2d22bb33dc913e1ee4a27fa7f78f2b70bcdd7cfaf631ddec36b739e76c739e73dd7bd76dd37a0145b5767d92e1d844d4a25a55d48698aa597112291a9aa6a6a5a996c416d5a25a5ab82ca022c0ed81b4803a404180e38c7dcee7bc0e3438723cfdb94676adec1817ea1e470eec6b3ef26ecd431cf6f6efbdcf7d3afefe876327e771fc38db76ecd9361cbde7381d1d9d7abf3ed2fd1a5a3dd6d9cb8c64ff5bda9e92ef998ea635176948cbb8e8e1c08123680d98f3f5bb2e27279cdbd373e7a836775716def8aa618fb7afd356eb6c998ddc8761acc70aa4051c41a70ac79f8df3e07f2106bf778c3d1fe5d70b17d1ac713cd6399f8b1f0dc7d9867d125a5354ec211374100c5f5f445f300b1ba2f3301b8ebfeda8e4f53daf8eb36d4358ce17b98e7d986b79ff3dd6361f61dc853223dc778fc47df74bfb8af9f3b7bfaf7adcc11ac77ff5084966d55c04847deda7759ac0b685ed1b8e4db6cd099bc070be623ba630e2502a0e6c00c759b3db47abaaa8a4a4a29e9eaa5029540a6ddada62a3a585a64c991a16d6555515959454d4d3531597e252bc696b8b8d96169a32d686b34c6de642937b202be0c855e1aa38f7e442e0d3e7a05ce8f5f4392b2e14c549f9d08ea7cf6d71aeca85acb8312e247bfadc1587e5423a4fffdeb7d6eb3acf52ab54b74c532c2b058d8bb4aaa96bea194f5b9a65b2525ca48fa3ed14eba46d156d9fb485d2d68ab65156ca4e511e5ab35476ca46d9a91b3b65a5ac0e930b8df639269db1fb6dcb458ec907d07ef73a9e4917724cb4b9e51c937352c66c66e88666d35243e96f6c249113a5592e44b35cb45496ca5a55b9500efbf629968b54774fb35cc866bde895d534cb458a44b13ab9eda5575859b6b3d862706ab9d06d92443852aaf8539453f995a37136be459ba8942af48946512a498463775c0ca0ff5d402d0c3261a009238c23aa6a5848dd1a16524e566a5838cd1c2cb91ba08b332b5844050d415f5a3b6b5830edda021a3a0c7c7a16f5c349a3dffd5e6e8519acac8dadadf2256baed7ebf57aab9dd03b34b73b739ab5da61ae2d9c5b538d9a352019e7a91fc6983d4edafdda69b1c3a8daf527533e723dc2c0dd645ba7ae5f7fb5cb0b5fa112359cf6a68a4fd87604f3169e3102c7993545d6aeb0b287a851a5bb3e91aa242ed465b1eb2b39c1ae3fcd58b9c806f4e6a472201880f42f1d22522489fcc798f8b3a590696602a93f694276f8ba726d210ec71f3353881751858cb1ebbbf628e953a79a1f57388aff0a69369c520ebd201a45996d8cb070415580726fdc1a1c7e21d32b1c63e36fe12882e1f853db4cb5a16a63d5e6ca6ad74b27d977e9fb8552896e1cd2bba51ac923ad607369924736491ee9ffb968b9ac8dc071aa715aad6d31edfa738d0fb9118dc9df3fc7d992726a8f5bd6b0ac6c9d1080b286e5b41d8b861a16d38e6d55b165cbda14517b0cb2eb1455f608ee3973af428bc27adc6546eccfa5cf6b54646d599b226b0aaa8d5fdf77b511388a9bca5a154f43d4883e08b4d61a7e77cbedefb63d2ce643f7b77fdfd95617b9589fe621419840b9e70701c185ec66b939e75f0bfe136c848282762af5f7e2a12cc0192c1d6389a574f7395f4ae91ece2c5f414fe058eb9cd405c614b425352cab3b9d56bb71f7ebe494f73a4d9973765d54b54229a59e67a9acb516e3acefe3388efbbe8d8bfc755dd7e56ccbb83e8c3176b9ae18af2fe79c5fafda9519afd70b046b5756b5ab346ee7f2170bae685bd6a4b032b4652d0aad8d5ff2af5700ac4541668f5f83c114ef9cd86e345f7e10bb85a00dafd89f3a0853d31bd970c779fc03c179fc7d0c326100e957bda38348008e4182b87b6d02120314e6fbc25550068dac7966bbd33403f40538cea63d2ad15d5fbefb9979668c33f30c972a141403388a206c2db0cf19fe7d5bb775e307acd8b4eb2cedbe7694d6bdc56d928edaabc5dabc7da79bdea8fb4d2fe0d5c58a28cd493a7bb56cfa553bf1dd81a57da5690046f73b2da3f7a3bcda307a611add67ba51b8f04b9ba512b828befaf241d8cc797e5c4cb3ebff0405b9d2f446f7cafd2d518c19b3062881ac00679248a604bc4f7feefbfe600dc7d8f62dc4740efdfc70027c62b713dac6cc9e396c10e6b37eb1988d7d92886e0973ebb9c8cc61d7a7da89ef273c7607969ee4d8f49d706ddab4b195e6a68f42a561559b072cbdcc24cc9fef5b14dc753da514767fdbb6bad5eae0cc1d080b1cc59f2049c4fdac2ff6d46a7d73f73bf7fcd86ff6b9cce51acaea2f6da4397fec21449440e68f3baa3f28615b76bfbe33f1eeca9a5f5e6e47fa6c9bbd57ec09b29bb538a410bfe36b6ff7b7c7d4e3aaacbb6a99fdedeb6452dff7116cd372dbdfd76df58ef4d9dccbbc50d43d4175c666dfcf07619228ffec7e736baf75ab5d2e5babb4dea7e7acc6661e7e0745d49834dbfb3eff700204bd2f3ba0d2c097f9e8fa3bf7d51f77daead82ff2a3ee6dd697bb1bf7f6feacefdfe9edafcecfe9fbdee7eebdef5d7bf937ddbdebfa786edbf6485ce8bf415142ba9ff3ff1803e6f979168b6dddcfffe12eb75dfc713636abb7ded8ec9f009df4d8575e7fa5c91abdbee7c7a9f72f50cb94bcfe755f19ca12a3d7cf4c41ad02f85830205dcc21942546e0cfec7f5d1a757afcc2d07bcf3dbf9e87c1ba3b5d77d01aef7bbff7f3ffb6cafdccfe557a2d0a2b1bdcb20605d5c6fe79cc4519c39b8fdf96b1ed5560100b402547ba1eba9c471658ee602969bd2ed211047dfb21e667d69d0f7fceeb2e0491793fb7bd3f2ee49bf7d4eb3acfebbcce9b92a86e32fc7333b1ef9f93b9f187d508feedc96b7f8939d7f7957c63efbbcf7fe622fe4b7b7ad37f8c01f1df4fd7a357de5e7e9288e32a955523db738fb47d0d47285fb5b908c65daef5b1add4529713baeb7363de4e5d36fca4cffc4ffa383ed38d9956b3d2799f60da9452f167df1e637b661b561ab0dca9d1f98db5c89da87242ca2e722149eba54377da5cebf4af19c9d29ebae62217fdc56c51316b36a55fca88d932375be9f41eae710321dd02cb2ac64a90f9b9e3ce29a55dd7751ded3a988b414f200a75dbcf5ad84be8c7c5faf25f624cccd39cbbfecc56ead405ba58ab0c226b566a86dca8fe111772d0b6b1c3ecf1ab5f3f4912dce5234abd7c4429ce479466bc6d5c7e24b97328733d931cc402902eed51696357e7d1a58d75936f7be11c832949fd0ffb7da2b6bdd6c07cb1a75fdac5bb74a1944efaa4c7eec0d2ddf32f165de817b098569a26e8f2055017d8bc617bfa056bd33bdee77ebc746f5eaf745456570da7d432f9db1f3901de97db6545c227a3127d3752e48677055fdadcc9c0f8e5e3a01ec0b1eeca84993d6bd8b2b685cdde9ed2bff47195d6c68fe3ea9edea7f46fa6d8d3f9257e9c70ecc01275590abed2ddafc79c4fc9ad808293d9667faf74f78dc7292022bd2ac80c084686f3f6f38391e184b58c04c80d7f0952011846963ff63a1f46e7eb62ffc2b139e76c6dce16678cb1cd365b6bede36cbd4c11e369cb9a99293355b6179a1a8b93858333b6cab8cbd4d660cb5a97a68d7f83dd54fb22b5ede32f4fdb7e96b2edbbd850d176065bd69ab0b263574e4ca8d9221a34b52f5b1bcb28282d5adbd2aaad9da9b2367efba5ca6ae3cf531bbfc501bb954f63fc1e95876cfb3106f595181157a28a30c28b741f477491e222290f16aa2c4d4817916042d21f534b24b145521e25b4685142bac8c316aa2a2e5c7838420a4b961f9000337dca9503ebd20587892e436cce63ea60e4a4d585f3765b2d613739385ebcb876c8593e243e15745e1a61397d4ae743269da598c69ec467b502965cef3e6424dff9259e5f7e25745c3ada6aba16595db0776b5d39195655758343e501972e60c48409e3250c145658803dac26d0e36f70aa5cd92a1f59d88d0d9580b09d93f3312ddd0358cc9f90d3ab0be775b434b25ac26e7270c488717db9e9dec5e423d71739d6d13257a8048412d32ac87eba4c014b46b247802c64922ded9856c092512cacee817d9a91dcf390b9f3c7ea7477e96c9996ee8195696984a54b31abac2ed74b23aced0e02421de18804172b4c1f49a295489ffa405bee85015d01ceb0deb6cc0f98dde0389820128459920990463a0a582241152e545c3eef522527611d7212b631adf3ee057bd5e75f72093dfeffc3303ab8870ee33ae4e0841039e0f0441038388d0142d21f3fb2d30d5040f90d4e258a0f7e4c8192fe90e2ff6325488944f645fe2309b0c7cf5c421835324f20043541628045b2843051be00a1b5d63d5ee6231d5991bf0e424a22f4837fa8306af43f54086a663eaa40987f0da5ff5f66134230a343203e94edd0e37b7c91b761a4a3230b7bfcebab305162efc29291ce13a11f40219d90244aec75429912ae8d91ec9ba064239d5f3292329d708afc61d404fe1e8e75bb7b38e7f436f0fc9f17631fbb41412e607777f79d9e0f8c92b57b827e071673ecee2478232c71f1e8494a915b6badb5d6a7136d322961c0c5265068d4cc336e45e4c80ee0085e38ceb6f759793e9a735a2f66f3519d765aaf42d1683423b46a5a66b4d00819414b8d04729565b1f5e1c393d9291380df0c7b5b5a4ea0d6534c8b494b4a9bfb094d2c52f4979edda6d068b422d068010d19d0a04234021a3b481fb94751ab055a6a481eb9399e3d33cff65c25a536d3b96d8865e7b6e196e88564382bcee30e05e55330169ee246bc9f4ebe23d2cf0a8fd70eb97ebc9e8fd9a69d52f9c80b4f00facc50f4846ba7f62b1fe3ad61e546bc87e2a27b3e6545a1fa6ccc7d77737ee5629a315b5e6d5933c3873464368f2d6b66d036fe3e97669a3fb5008d3389ed3fb148b712f233ac7462dc6fe68f227948e83fa19b89fd8c94df4ece1bbf90fb201d402617b0ff5bda198e998100fec044cff3685704841446d8534f2bd1ed791b80ed26b0ddbdd5f94bb3c6a6d9b26730d93362c860db4f73c6a6d1923cd2676357d46bcf252490d7e77aeb0a7d0ae509a758a1e16e755535c6cc1ebfed55533e45737733cdd2c52d33aa064a1771b76409a766842556b5ed242c51c3850e5506e8fdfc51a281ca5ce8563e46cb6ace3d4e2d2b9f34b5340a75e70f77958e4a3292b5f6b367ea0e2561560922c58726564f0f0dcdd0dc02bb7704c31915f533f18e985f31900235e5d12268115e28a5cfe7539268066d012671d15fa96a2ef4e69c33045d7a4a398ffb7bd46ef2cd4d697b3fe73767c6a15bb9e86fe4542e84b587dcc87dbb4f8d0ee5504d5c68eec7e1940b4df7749369bd30888b6eed2cd2b6ce992409c544927c01bd1fe5d591efb116c5fc9ee7fd8b5380dedf1dd145ff3078c10f9ccd84c6ebc16658a0f75d6ce66fa3820459017aef85452e42d9f6c799883dabe9ecf909fa71d1843ddf5a1be4a2f778fa0407aa021ce7cf2817921a251a6b59b027d6533799f9d4389c9eae7b84fdcf1e61890b49179241c41fe95947326c596accd4d04c810ad22148871a1a5a2d055653413904e11054058a4a5a535881f3524bc194329acaa8613f399dc97232d85032d46c4a51b0b5291952bbfed46345e228a594ba4615b6529d61a5563018e953ca29ba65ad0c2c9bb3e252ce329c8ef02d6b655839ad428e81e5639469e26063bad96f62ac2579e49a2bc616c814932279e4185434d8755a33341a2d8b917c9b8f26077bf470b4ef36dcdcbe2f06bccfc43ecd48740d9540a1fa371bf1df68341acde650060b4b8951b5b777a92d6b669a6c186cb4dca37fe6be0ea003c1796c102d37b2db7eec8d4017b228a8b2ed1ff1a16a44b7fd98b51f34b3f63d23b9e1f6b022efffb9906ff14f6efb757ff5eb827800ef57ed142b5502c980f49164fd5bad24b25512552a89a84b22aff5c8927c77a79bc317c6188763936f672028c0b933d52f7757f8737bf5c35f6c02f1ffbeef8b4d21b319def63b0912e19a91e8c63fbb8ee7f7481f7fa035c0ed69917b7abc3edb9fd317e62edfb66cdba6e461918f1ede6aa5a5096c4d1318f6a7be54270a58eecc2fe809b41873e198c2fe0b8e31eb2485cd8d2bece984dbdcb67163fd5a819e00e757f965297db52bce34f92e8dd66ddbb6edb73265f6b66dffdb6f1fb4d5f6c664fc1932b15f5fd8c9acf4058fbbde538ee3386e72f3bfc97d94523ddf6afad16f7ebb8eb0c96d8ee3b86ddb40342ca5da7e2b18d097f6f69b06b29a77537d7bc0f3fd3fd8fc7009969503e36ad39f4d341a7403385429f79de6ae7451d226b00f89d79cbeb052466b8b6d6cbfea4274e24e02d938fab8bae4b63d92dc8ad89f219322f3392ebc2bec51896e2e8d335bd6d638eded83b6aca531b5a50bc9dfe8f66c48916c3849369e646cab4d60347db1820d5650c8c64e2551ec2deea4101d1248fd106c01942e521ffa55fa50243982520a906e7a933e4b9b7e5b248fac926324962423cbc82fce482934c217670c7153341acd8b9114b265b6954a81c1a80206141851604c491ec964fecc74d2bf014ba01580b2e6c5d31ec52dfa9549840f41ad54216c101b101cd4fda1f3c1eb013f7d3ce41d5c2478550175b891c30d0e384eb01b7050c919418e29311b74d4a043834cca6c861d32f088a147d3c3a05f085d0000d34e940c5ad060b2b0c1a4896d78daf0c1c68fad9e357cd4e0200640d8fcc0a0833402f002a035412ef0a005422c18521364051fa880080d215a209c21803403302304344529d865189141803322a0e0688c2162209921124611300af085913247bc40d285014e90544b6282037081802d124046490914a0c502b26040d6085834e00a0758f1002c12aa80001511984202574ba49840142540418131263c5101272c802f6085811032308051004c4068224403441bf88003414e1872228482071d085202f240009e74f0819f14808080835d1f87c067d7c722e881f243051fbbfe0f5e816749dcb40d360b1ab49041941d2600b810bea061f8a61e31f09061c70c3329321a746ad061436c4a8e11e450c17103cc0907879b1c6ee800567991c06577701ebeb99f640fd9872f88cab77fc0501e107606918fb60f62012cb28209f105d1bedeb007a80d38deda15185bd6b8300387a53976fd118888ef471275df105d6c15c5ba58b3ebf704a92f42ad9dc069d75aaf50fbf381da502fa4587ae5036aa3678b3d3f364a04da026908c406387f02bd001cc51e203640a02d10280d50ea88202a178b67a9486c53d7fb6c46a2405be07c117ce886d579b65742895a6dcb176c59db62cdc69e91eaa661934fa422901ae0267d828c00fdcca7e59b5abebb059979b5658d8c947de9508fbbf291e8a2ddf26d81f948144ba02575d8b2a605995d14debac45767be5214a108680d90fb1da02cc051dc3bd37ead61b5e18e0b855a8cd9b2a6c5963db5d8b2966566772085dc90407a7e2491c51bef0f943e1f13f9888614480d900958133954312e3a5d66db66a9171e55559f8f6c28f6c400b4f38bf82504b353c36860258af1e144cf131c8cf9812200510449217415640a222a40a86200584556185d21021643b2886451002d8e94c00064926c8180cb8512132ca036c2091cd00509d38b08dc324bbe28010c13c2b080190c88318ed104051b387302192894a1948227685230230469a09cb182160d8d1654c0b48217d434b14086164871410d6ba6bc804a1a4e30c8814d9518eca0c6d31a3e6c41b111441b56661b2268512c18d18254942c4c532e28f1c216189868a28aa18b0c5533809162458313358cb1218a295723a0820ad60d573865e1a0450e6474e0a24a8d045dec508607309eccf430860f677e28030a0d106982d012420556d40cd10222d65c49238a4d116a18b175c46c438a86a5852c4c48bc30d594840c4a48d152c396294b5061c2a9891ca8aa70d9a1cb93171faaa0be0401c64a1822aca2c418e184d41359c64c41a144145ba460e28a6a8a2e5454550106cbca0a27ae1883451459575950a1055609ae2093b585165c90e9e1221fd1508c02e93bad76e36ee7e12fbb5ee08d1b1c188e9cd90e1e3d5e8700d8c940830d441e1f3f7a7c3800f2d3410080823c101a0af201911010043080108ab6110144381a8244a448018c1c416280a42407404002942860010c18a1010e78000910888004964ca0040a9850010b5c000319189934d1c00638708213143aa0e481271f480104211001141556589ab4c9c26c6146994cd385f9c2846136cd18a60c73862965d2306b9836cc29730493cabc613a4d1c660e538759659260ee3079984fb387e9c3fc61424d206610538869650e3189985766d42c621a21e71153cae5c40719fb0b03f2024a3046a9ad3e04969f9535b140b3e79c73ca39017caf5b05a4eca854ecd8e992562a9a110000001a5315000020140c088542a1509806622ee61e14800d86963e6644950844511aa3389261209a310600838c013023403052c200e2bc81b72899f9e7cd6fd94238a6202ef7316b79e56720c2df59f15d4193a3422d7f5c7fd6e72626066d5eae68c65d70846f165ad570db2644f984d2952ba1f4e0e586ca8c371cdb51c4f6f77d77b6616085a60f624cdc9e0dc6c06f929dc35e5b3213203a3748268a061ebb5924a00487d941f763060fc7d3ba613e0a555fbdfda1f2bd8633d15304cf0398122568d56bd3afce8293915f1dd891b9ec8eb639b13be4a9e945db66fa17a9b3bf4a9e6e04f2513a4d8b368716b6dc3fe600652f29387dbfab43bdd8c698d5a0cb29b303139af69d500d30ed69d5a0f3cc21a78306b43c8ee9eb42301d35d3f111b422b84340c03e52ce0800c03c5d1aadd1103ca840326ad89d14edae928aeddf06655fd8054580bf7fab038a354b85f296a9918481b5f28945d9dffa1c53f85bee953d5863a4601803ee49643661d45e041ff98531af7f5613b878f8d5d52e46a3e2865da20efa1102ca37fa4a4e02cec7d743f9475a8dcaf0e14e6fc3f4db09727d561b4b742c5a77bbacda1dc07866b5a3a32988de12b71c0b9b48dea7830b802e65f00dfc029ad1f82f5f7feda24371e01287d1db568c0255e39d00983baff1e7269ddd2618c5bb79c63a748679a82debd2c32c79b9ac3f2f755277b8fc2ef4a11311a5c8c0e2a0560a138217524d607c503ee1a4d9efd70ebc87c96655eb7040837e8ab04919e28a6c623b96b0b54313a53227d26357a8ceeaf5450a9699dc13a7d5bc691205c0716e5b6d42ba580d86ab6232670c5c8d1a06b48a0d346af5be24a4a5440b3e93278e1f1e152bdd4b198efd6f8cf45f53ad1aa69852b31c8d4881c38146c0b028b21603bf50a8f0cab564026c09f369ab3b91f7c962e517675f7c23f9f3c44b7c1574b2e6799a58d462330eab8c25dc34d6a9b2698575f9c379eccea95d9b3ee5c14cc0d3cdc078604a30c0aa075d0437c5c5d3c50504ef991ea190c5ee833435e538c176c606662cd193aacf527ee6cf4fe6a2852ff3d025d218a75801f149a5484cc8d703f25ccad9a19947fc4b56dc33cc3a60a2d88f42a0375cb49e7f4a18c5f6ddcde3c3b479fb9832bbde149554f6f0d7f1ebe1f3a32f3d722c90bfbfc9f25d95ee9fae61af2f68cf847731a078558e5f9858d84fc4628356dab389a8d38cb6d26c8fcf6311a43a8de227c46dba0d34b1daec67cb564618ecf124c93eff025b4955a2d3f738915c805796a65e3a6e0a7674ea6a89bcadd5a89b3be58fcd3d69de8f59370814fb7ea02cf7f1b00694e9b31fd00fa807b07d10fa4e9f0447df7e93033f2e0c3c36365993e1da49fba9173ab4eec947c54e6f0f2c184d855e9725ebf48be60afa19053011a43a0353fabd99c4b43676c5610a55ea1bfa35ba5325a750f315f2f13d06b113cc67a18a0d547a5e85b02a76b01b1da7c35eb0871af840d7b91f2c60b1ad80e5351a6a526f49115c3726f0ede861559fc900d65b5c675bd0e548220760d68d41e127c26a11e1aebe2c9628fb2735676dcd6e423d1303d46a2bcee761964ca712c3ef1b58f8b12b74fe0bbec74da5bfeef9416f90c34d028ddbef4c31a6a5a39e36fc311c9eebc68dc756398c1eac625070c1acfa8dfbd737ca4d600064b03ade029a51040f6d7efb1ad94241d662dca5776f80f9518c19b154c8b064b1b87fba8981f0bb3ba40fae8dd4c0c74c2c4c2baccd0d9c97446dc773b4d53cc9a60d72383a78acc78d4e598702bccc80dbe41e740e60ade3c209b57cfcee2a097d661a57f9788353118e2e6944f81a68a750c34635025b81047c3433d011b8ff9a7c262bc1f383761486ba3ad7cee4ffbac6ce04e405e454e9695045dcf28706c5fbfca4e6948cf41cb353b9d15b983130a1099f708e693f8e1add347df153023559984cee54ec537866262c8088731f39283370ef6c9cc245d2e4962e3f09646518c53bc3b61188f5c044e45e91260fcf9b7bcf6a06df0c35902601bbbcba0a0da24986afbd612de7f7720a8f1cd5145cc91358ddb0bef95df19f6dbeb02135836a1f405dcf40b85aa04e6ed39e907429dec5e1cdea3472e0b7ce4938b47012a8d0755857b8e80ad7632944e3f322ab2a7fb2d56160a9f3e1358f8584c399bdd8b103afc04ef958704f4eb73bc1ff53b89a2782dcf13efebe0ca17bf3957df2466148761880605472c82f24f3dc662575c9cd8dca3e65f68a713321d67177de77e55a965644712ee600d17a760e724c5da0da5d41a5a799449b9370c1281ff496e14dde1f5069be1799405be2554b72d4bbb836a9e5b06d31288b464ebc5a6c76569269ff06e52a409b6dca59a37fc7a102e8d9c359d447614a880409e8c5fbc5bc7c3c46536103e650ea0e0856a5aac4a5e1147c4fa46e9d7eec862e76ba00664cd79aa1944130162196065f914d019bcd1b55a00d5cc12efc0b9f1fbcbf48790ecd4c5ac4ebe89ad469c4bfa4479794b8114f1cd04db45ac61aece26bd43c51e1156b4c3c499faecec5349a97c9304edb9dfb44140dce5b07cbdfa8abe5fa7869ff93e38e09b24467a9970d43c77e62aa7d2020c6e613183d3ef7a630ef56446851267735e272af9fee95ec17c39b3e589b41ae20461847f342c8d22c4b3c28d6d1d723fc7d3c8d9ea04365266ca2f6dfaeeba9662e91c82d820bc157e222f70abe265dd3ffbb236990c8fe485e66860d81286a402ab310950e700f60b1f4ac262a068b292b3e3cd558bdf128a0643590b7aee983c538408edc7dccb1a8ca5791f9ba5212e63b6a3bf20e51151184497ada94a07f3c1c2b4d0aaa2422b4217cc9e1cf4eaa66a8bce14cd267bd6e60d13fc343f221852206855b9f71d7428936b37c0411d29eb12fe61da96cc93b5c4bfee701c875e6e79f4629f42ed817c38abfdeb87d6be6d82af3a342e3ed8b8b341776b23c5edc42fc1e9db4de12ce3d93c9159b9f6b6465e2de74a3d496662d0290d8d11efbc0a94a7da4b84d63838671389ae4aee356740782152d9565383ea3a22288649ef2b7045c9c5db760e1b05e9e112b6e4b50e0e9fdfa2cb9e79d7204a42778af7b0fbc061e16aa4ff9ec28dd39a248feb2d4dea6e84cb2b5c6d4ce19a453c1dc51d0f40c3065f7913f7323b0e79122243e8f6e481cc5058fbcaad7960870327cf722ff0947a260c8e5f71c7f72670f7883d64ec1a2fc45c88a2199f84e7c5f49be80d1c1392259b7580268f38fd5c41b06d0202454ba5e79d5f6a98bb412b6639ff57020da4b30d7a13a7b6d0d86685a55a629e10c940a8d2f58ba06403abafdb4e916c697dec40ad9b6c2c336a1676e88c0f00c65e445ba53614c3064658fe9c261781e36afccd7884808c97604bf4d485be8198d19e4ba3134092864bcaeb80f29cea2973c17e3d7873cdbfe54dd48ad7d311002e1c9365e0cd7bf4ed9d94620f6528c4c2cf3ffdc6dc82efc1a9a1cedcdfc230ca473f5fd7e83ff62834d36791115cba1e96a503af9d223eeca1c8ecc9f830348963098ab221fadf84c87c2a765a45570d4eb50d7f830a65ba0157e842a23a8f9abd0c9ff73348735d3ec253cac949a5eb40bb5ff23568191d0b6c02e2c8925eb976758de3e6fa4d73c2b409207bdcd096c1f76a6e06b30217eb22772b8ad39bcc819f97f91124240384ed84733141a4b54d5b96759bc4e90b3312c9b7771e8f535c8986d931a0a9b365b101920956af64c19f449ebcd678bcc7efc5c73a92197f5fa5629227d883c4dced4b2fbeb1ae49de71f6d6ff052c815d537f8b087b7c6b0cdd914b88cc104a10355ac69f383d3e3d0456c1cb22d3772a750b3c1626011eb3e36191baf8de2c459599cefecf5b8602917f15c8eb3596847b2f1f21b58b882995023a5e7cac2767dce1a8f9f16a5e8d95b8445b4e949ba84932f0b90cd15320fe3d237dfde415e6ab17d9f3a3afce9cca17b5ae3a2e706c0faecee3750f125cc104d5e95a16e6e481da1c7991cb9127801e43c3e0ee0280ed0e1f276d93da76783df2d360f54962d0ca421812e4269b2fe3b43f86debc6bbda8179326201011e62e6fc5fcf48ff268e83ec4cb3b1f44a6e0e4080bb7c3f1bea8720e7620f45f87cf35dd3e05e80f357f87f70db8b095b917b38df685a4bcf59a4686a3847828a571d019550116edcb2812f7e9a04f0d6267380542503804048a6ffd2b3b8f38c05db4ae9f462cc0da1a963ba847b0df8a0ec775f7817b3a7a24b86c7e57a0a4a10f19d9509415089557846623d1ddf938e1d72242743114aeb0e4c23afe0d7e56638747dea688a4431ae834284bda82f6cd161c620f6601ddff296d6c65c4a754e09f5ca02692d57705c944924b46aef351ee4bc2ca11bfae85e06c1dc4c59de534875e42a14c499a5a0a55f213adf2d4da62c1ecd55e0dafe9756133e86b3a038b7ecd0db0f32de7e3af94eeaa0d57653adf62e131b085c757585a7e0ba0be4a25a8a23df3de50d8dad786cef7142b2bdfb24d4b1fc205f8a450ed9e858d3f8240ae1972f3e906080f852a926bf25315031950d0f1f66720499f819ff31da02143f676a5318a04470038a9f3288bbadff3e00e028cbc9d1077b201bbe547be96ff810736205ce889171f2749ad182003d0ec8c8ee2a82b395a41b3e8c90b48a937605a0d90a3b8da2b575fc5496e2a24e04a4c2c8465d34c99ee11cd1c7d09b3b97dc65837547222c25eb5f8a9b87095da22870a2b1475c9306f7134a6a23e3893a42f69249cbdd3bbf97a0cd4bda7b64a8366971b6c215494d6fb841a4116a5b0f9c7c68cea28f1c973ff276566544fef557c389b13846d89d572094f8786e6d19858c4538697eed2dd0be18d2a14d581137ac04a21fef59a7697c6b37abdfd733a1c7f5c9aaa69d9e1e69b6b494ed95eb4f1a577683e94b9bb1631cb101284c2c4c9bc394824e1c3354a8215b9f512147e5bfdb71c723a7bdc60361c0aa41c676d00a2b7db66e93eaedc344839167dcbae76bb076d3d1beb383d8b5406233dcf0439af87f7e86bd73d42bd63bd86d47c2c4d8c7eb223f86c400b902ec0d55e3bb744eb2fbf8da782a49dced8137292f34623b034ed6c9da3588b1ec938cdd0b894407e1f811d5ad5d61b84ce5f9bdb8007cfb3520d857f9765f62d8ddda5f0973a9ac2cd0839190a434e5a62c7f67ac52560c47e2a2edbe56f6fa0660d8652691841966f69d88856ac048b93d1538e7ec7012eaf9d3af9cb716a1c13c9e20caf1c152ca5465a1be6df36b7b05d738c009f4dd60d720526d772375b058457cee3825413b021663aeb17fcc26b7759bd0f5e1f1022d7623bc47ef50c86ea07c5453cc44fb9452deb8c11ee8c535b52901513b031101c28efa7dd0fe62b3d53bb53f5a6a8a5ed680413c2e722bea9e935c6919fdbda6d5ed4bb8f9b891f8f045b60e77ad31dce4fd096011ec46a95f1ac1b7ffcf39184ddd4eb7bd82e49af45c054f568bc5f22626c512e397ddc93e023e7379d357ceb73a6447e5bea952f206553409643bc7bf357ff60860de76e6716306127e580df9e25ce2524115b0681bb4344bbeb76f49f7591f842b1ed698e26b480a78f6f0c5eee593ff2c5a8114018755abcfc5bbd4f11b3bbc5590882f8cb82c1e00f614db9e6b7773ffe1cb84d0141a6fcb85cd7101dddf7c211de38c05756ebd0b378884e28de1ee3101cf398010e5242a1f2d58fc81bdb937e76c28689e0c743ed976bf23de71ecdd6f501adf74ba9f0e4f00849b98371b3e330e49506bc13a37c5520a5bd2c3b78c3c9ff1ece4dbb7c87c99747627de3941dd0703a1c4c19c42503ecf6edabe189d8f2ba864fa9f052585fbd82fb9d863217421de006d2200b4377880232a199ba26e7c39627cb2fd2d1e16c81f96a5b044878a8319ec215b2182df011612e3bd1e3283997fd82725181bad0e48d7867d3ebf3312aaffc0fc9b8d48f61afbb590b197aec69df6c0968b90ccd11e45a560ec4aec522bd5440cd43d3542b37aac71db94355e8cea6583edeb465684b99178d30e2de6de19a45224584c49cd531e2d81d3bb95fa97569d2a56c39e38c9e89e46ce130e6de1867ffb2e5803f9e8805734a705a699666bcc0ade326b6236cbbb056289950494bdd34f0d2f6ec26f88ad67fcfe028954557a8a939c42484bfde452b644e63fd2dd404e6e172088810431525f89f7026e8f6bea92a27af34032955d78060083fd40e44bdd507c011c4e68eecf9a13ff40031b102d60a7abee8bba18a57076e3a3c9b070b03a6ca59684ef810ade6638bde1bb863bedc0a1da6a8278429f41e138a6766ae122f2ff014864d3d6ca0e17293d5baf400209a633227e9e824737576cace05089c6daba68a0600d23d4d72150bcedd66a158e2265a85445830b5532a67e9f000436edf6ec90a8d2a3554a1426c38ffc750418dcdcb5db40c1241bab2a1181c2b5cc243f4740e146aba55d3856f2a44a050928486596fa3f0a0ed9b058b242e1a54eab6b910102148cc87f67d0d0ed55ab050444d2bcae8e142a58c948f07100176f5bdb5a8522250e2aead060c1744689df83c0c0369b9d351c5ae2b0b212092344638cbedfe67840e0551151c5ee111b76faca9262327e697e398f59fe54fe1c3e196f39bf24df2cef2cff0c9f14bf9ca7bcf0c709e9d4a03d30f960487ac69389d2e20bb9cb21c9f288f2346fc273096c34f946438d861c0d3f183e26cc20b47168e390034347c38f84180a371c6e18e260f8d8f0815046a18d430d861c0d7f53031fbb670c01afa0760822d2aae28824b125904b23b04874faa18979ddf2aa5a31b7b392c7c9f1d406517e9ed0ca538ed43bd82b53a4e23010c6a8ed15ac7b5abdd874b0f7fa61cf9a1d267dc9025c633c5bc5855a93cb13c5bfbed84e9eb51c0ebb85fcb41ba980c40457629d764d09904b9800ddd7060ec6456c11384b3c06f4d4da0a22b942d551fabc8ebb6e6d0e7accd4168b6449114bfcab358eecdb0c0ab0a07af0e4c840a755152d8af1cc9fa9fafbe5040d191a1d04ee13664c132867aa8f079898dcfdc1db1e0f60d73aa542b75e860d72dc2df1020cd8652a6ec310cb6f143ee63b848df5ab40b254ffad4f3f2a33bfe7d88a3251630d93be51cb333c6ddf6edcc6a862c3e450da8ac972a306e23b74e8275117fad9d89b00420337c66ad01104f15bbb5824a4c4c608089730b4b0c9228075365139fcd0b9bedb38704fe1c7c28c4e80c7965d3751e6746038fc9409cbf32ee2f0d3fc911bc6da33ed04c7155800a800929c3763ea3f2deed7fc97fdd3f61686b14126a995f074949e622156fca4c88333ae414d1fc8e6b0ef9b1cafa978458d0174dadb79805743ad61ae0c068c5d1e7ed40f1767232ed7067f004ea14575b20ca0f04898a73b789e1301394b4b8b91989f38d36f6bf33725890bae0ee3994de5f87c60b6b68fc5df2999055468af7e00003c1a99682e2bf05b6ebb6815f67f27ad51976159dadeb8de690cf2635a9f8ea67d5ce848369a1941e00e2f815c8bc6199effc3c70b000b79917fb1a6107ee7e2489cf11d8446fa1ce223eb29698b484d47468e3f4a828ef49aa9277dbde84b6f0fe436f6d95bffaf40d592a58887b1c746a660df8a694d0ac050a9d01e1b38cc73e80b7ddf7aceac7c0139de9883ffbd9ed2ed57bcd4b91e6f24b0aaf15a6dc124b271a91371c862575ab3fe10f4b963211fbe877e7f6f09ee3d72fc000ee43370d138c5291ed3cbe4c73a4c3acb9c75145916cc3fa9ae775443c177584949c0c7940090525dda2343e937ddda34467563d703d0e30d8ee3a0b80e8af320380c80d360dc0dc671a0b8078a7b001c0cc06d70dc06e274509c07c539001c06e16e304e03e33a50dc03e2a807556c9ee940dead9fbb2b051206fc950220a85b2067c91b5b9d3e5fd100384de83a9aab7509608f2a5b54479e5d0aa28d47495720adc03482d00844151c5da0b402290aa6100c45306ac1a805a4149032055cf4c2921dab2f2af9fb202f833649ceaa9749ff7cc89351331927b5c5e4bf3df264a899d079cd12a97f8fb655fd42c9ff4ff9996849e4a8ba90f4e7b3fcac9a9338525924f9eb915f56ed449dab2c90fcf4c88b5d6b52c7b58b257e3ce0c5d42ed969d565a9bf0f7860684aeeacbea8ecef87bc0c342438575f26fdf7515e468d091cd42d93fff6cc2ba306d20a26e0a4b298fcb7679e0c35933aaf5920f5ef313fa686099deb2e4bfcf2c41b5b534287b5cb657f1fe1c3a42dd1a1ea52b9ff0f79b36892e4a87291fcdfa7bc4cb51371525948faef395f46cd491dd72c90f8f5cc1f5b63a2ce5597b5b487f05f3957a2e2bc3a19115fb4094434904e5513d1fcc07e9720f58bce8c56f56f9a127a3291a103a03fa19da6a481d1b667639227cb94274dcc3f9200ff85a5f316e5e2547fc8d7a281cadb2300401732b8b42a4f28d77d30731258b5b647e1639700835fd4683b59d99a50a28e5a8506e2133753073b8654689ed0dddd208f95f159f51caf8abda5cbad49180528bb7ff87bbe4df882a563ce084310b3b3e8b85d559ae817785064dfb9bee6ccc6f8b3965aac4f4fc5ffacc6b04bf82d5949d24ddc836a211d4a8bc5d6aefb50f9e3c058a910a429a5a5ebee014e60014fd62ca0e52b2657c051464d83270715eff06f62ed35be36f0d5b534eea3217368f4729b88ab505f70d821e26754b37f5e46241b30bceed67a401bacd48bbb2eab814d5601040e0123b3b23009e84f17a6931a59688cc6b20992d08210e88561646c34968f540c471894321444c60e53ff66f4caee184ed262684ae6ecb0bb45065b056c77c16eba925a13b11b582562521fbf94873d619eeab70531e24714acbe94356bb91915c0fac77c542a93b488724726e8d6fd9e1a49216a7631e54a89d4da83b200cc283bd4d5731180441606e3d7cc53ed285a6580eb22dc7c814bb801b0fd45c3a95f1d881aeb23c85cbb818bdc1267304b300fa1931a04c97265ced83100e11f14e58db4695f7c1c8f9154906181106d9835d501370c59e9948660ad847152050f176cf790761b770943a707586b658461134187b090bc5d141d0b490e01861f715c3c5723190abfe31685d8a2f60e098b82359419927ef08cfa9f1257c6a0c43e8a1e375b7a460c2084c5c8c69dc9315dc12a80a46e5eafe083b67a826015c0ad9d26a66fbdbc2035ae9cfb0c37387c1bf329c9827c7fe557029b6add91d76d70298e123ac84fcb22ea6ba6504c237bb872d875024238bcaffdc7da628e79d15d5b93620315093de0b9da45ab349c77125e0025d3e14192ffdddbd78c992ea16c449f177c70db36dc6ba90540e44ad7eaec08310b4eed30a3df91ac1d72625c5f80f1e1c68b700c63bc1472d7fac01e7b1e1e1d5cab15ea14c89bb6cd3f14a7e2074df41270dad480aecb3004e0041681fd598297d441085ee3104b634c22c2b123744afdcc0a9ad4e782f5e7edf042f138c6300dbcac674bcb1c1aea0445cdeaf7af129f5d9a11807c3999068be878336933957d889cd239c8a5d84869ac317676cb639f552d1ce6872a78a7c307e1282e4992e07d906961041e389fb11d767dc59235451f269a33781a175ca40d697112c4019f772636dbc157e396be82e5d36fd8ce34036fc3d0e34f1824c5206f6c0f7ad4623cff513d1ae8b997598678ee300ce48f30125de34e5ec7ffe71ad0a71b18c0b9a297fe0db5c3cd66c66b940823ca88e66194138a1c93d979f5c5497187c71a826d3789886a1e6723ba6f82a1c11cef5893ca3373868fe4625ca454c482d179f2d961b4724a0d372e84e8e47f24c2c08c32569dc1d69dc6e9f000d4939d93d25b814ed3b709af4f6e2132bbf7ee55157c27c9fe51f9bc8efea51a8bb93aec27ab903a9c277db20b8dde60a96d149e539fade01961f450c9521889251468649401d99e3538308dcd721a4092ff426d1dc4e52de7a05a902bb29c8e6cb4c6b400021a4be749c5117954894d4bc38d041c2e025eb89df769703c125d2fb449d3b9f825e20e0aa4cf309d77cbdef8d99d7d19fc8be3c44b942beab9e2dacc55fe5dcaa3cbe4402a6a29edeb54433a59c542172be559018c2b2cb972441f3078cf0f478c4b3ea98573cc54b9d39c7a69c7eb702ecd5ba52deaa59da39c4e6db914b92693c743facf9cab8481815aac13f7d9bd8cd655aaccf55654a1fa188a5167a1dd5c7da4214970dd09aeb67dbd7e6de7b9cc72a491c6d0e443f655c03fb3c9d27585162f326ca2bea75cba0676aaa1f7ec5661c3f898862cba4d0d6390992b98d475930ad4219128f516753fdec5e9f9a3261f88c3d61bf3ad11795fa5fd8d13bbed798f3ae5c2ab572aade07c861c7199fe5de3e246d6bcd7685c0c16cbee624bf8358efe934a0443d68be4c9be174e730b76c0261e92439a23dd8562f1e627c3a72067ace8fcc744fa54ffcf29a98ac765225a52e5c64f165bd3171e2256103ea07815e661cafc66621aafb037eb88be171d14db7b3fbf322f2ee8ccfc69e40cbb637fa6716fa5601922da82231017411fd955f291ff5b60f9e6aca056464fbc8ec546f582eca333af1de4f8be44004f5f49e37d8629824a304d5736822ee25d045c4a955c42cc154e267ae73830f73fb4400ca8662a96ccac90561a3651a6c19723c0b279ec9a6b10c600ffcf6f08802846d2358ec19624f369fe6c05a4f5c5fc70f1737911e444edfa7d1b5c79630f852ee8593381c1572ce2a01bfcca1b7d0d2760d39e25463f917a216217f95b7480f8c35d1ea78fcf2399c6e8cc74486bd29a4ec7aab838a5ddd2da8336f85dcdca54ef3e71ad737f8dfb02eb7cd92a0bd07e861bf766d8a1f898cfba50b5f9da3215224b78b2073b94e35e81557b6a420bec86ac754f18d25a279c6753a20e9cd8619cec2ed7c5e40f3fd0d91deb6d5b695c30839cef5afe5694ee893bd8c98ee16837e97ff8619d76678fae59038a343171f6e48881a14ded0777b8f31d467ddf88c7396e3c28b5a0c0b1130bdd357d4f57543cd76bce087367ddb90aac059bb9f7ad5b7b7864e05dcc0912e5e001fbb32cdae097e6f7dcb8f8ee8abc40d111d336e43d78600ceb1654c6ee7a2cf58a117111ee599ca301f8e51d390694c6852b1258436e3b04ae4dc61b19948414e7769919d74d76466c0c5f085272b4aa891b5615a2158fe04e2c0bcf9947c9d22d1e7c08a194ed6516285f5b22743f1b3219755b0ef53d2de56c62207b9865319c97aa71205eb1a538f1dcc31bf7d1acd549d4316199187d44f553d8f4ca4b7b37148ab57383e182e2fb1df4660fc5acf0218b81df436baee404be7a864e0456bec5e71e16de4a6da1e46a3db9692ae2cf9b3eef3be15c99c5d270273c2822b3fa32a769a55b1bcfe708df6e2bbf6b0da0711953c2b8f0c994d21a2b6a0141ecbdb3461eed3f8ec0d40fc4cd5f41728ed510676822062f80bd7b6b3303fc0b6abd4c540ebe99cbf3cae1c2406285d4d2d8e196fe5e32471e96ad92874b4617179ffb70175fc3faab89ba677b890e7b54eecd7b940de69829815257d0d18828705ecee8d1e1ada10e3c4ca96662d1987e879c69c5fa8ea3006da4a3acc9ad13b02cf6c9d79cb094ebcfd47826f4afcdbc443e6cba6e952acd52dcb85b5600fed6f06139ad32680b4552d272c4125d6f87e8d6a10aa2aeaaf64b83f21fbd4bf11b7135ae9e7160db71e7c092d171641ddd2210aeb44428167e1b1f474942481d646991391424788d606f8a12e0544bb11edf5e53413b556bda8acf173b43bf0a80d59a65678c5435a356746fe56990bdab94a57142e3d472623cfc1d71cbf21da931ac49dc89c53c667928155a945770d529bf1335a7869bc80588461ff00e7a4b7be008851b841f29c2050a0aed7bf5d9c8fafcc42baa72df290ad1460150a57c55699ea612977c7200c375657df1acdef3a79437858f541ac3d2dc8702ede5f19fab9913ab0983f77e0f4c5c0a7842fe316c6094f303cc84707f018ddb5fab080a201762fe80d824b7be5e314a8c0d071e20ea23f470162765fb29875637c24725d919eb3c9748025432e9d100f2ff89c44757c2511ec3fa40c4f2fd3f78bb6640696eddff515e7e79e9d2d2877505dc676e6d8f20375f7c126739b5d634082f53d68a29b1929f526bf701604dca66981f86a23af3e80e264f2750cb38731807453791178c54456fca431418662feddc54784cc29a18e5942485f609b4250c7a439553b91b4ac79397712b8c711e8dce38a6243495c741de7f4ee93b3022dd89e03c0ea25463854c1c6c11c6be59a07833e5b21d671838420c4a23d66dec9149024d0425e86718e431fa799261669ae56685a03c2b8c21199162637f1effbd81bc538df7cb13e204bd2470b042f2371b542231ff1db1437efbb1a7fa53dcb25a1213c96ce28db42568cca10984c75a1a8a2c27176572da05cf340fff011c729c307ce33bfdbde6d7b315393024c2282bc5ddfc46814a12d41760ec5f96df39c397d54adac182132eb666aa8766ae9f5f680128105f99410ec7f50859c95f61c5269a63cde2bdd02361958a9f1ba3bbdbe5197113579560252aff0a57119a638af448cdc2ea2fb021e9dd586c6b519b73d974044fc3d1619f30f3e28219e3efa78f54990ab470b95bb035c8e01a31421766157e5544f5c5d615890fa801adda41379d19b1c5a3cfcbe92128644abd19ba41779d93590e6492c8e906e7454805da0cd2679ebeaff1f941a278e3e34cd2cb76918dd238faf48b943d4eec898381394a41bc3328518124d8b3a954089d52c9088ae045065fd54058d9bb60516b11a76bc3c24389a0901f80747210278c631e381384edd8308b1c7f866b30852a49bcb74411cf8fb2c1901729206b55b077c3ea573a6ff8d26e6c808942ccb88c0222670bf09b3df601cd76e344a20d53feb9f016cd34d9394412dabbd1bd7fecc750dc6aef2a40230633318f76019b0e4c89b19a17d3c63b6c73b3ef5532f67942bb4a7f4a16c16a0d60c412b311501d0d51f3a5a0aa8d692539b87eceaded56ac132e2acf2d9738ec4944a45a44c40a1eabf38f1149be81277190664186be65eb12f61b027c3d3a0c842819830d212ab7635d58c976c14784e414aae792c9d283302a792809b2c02e5db13afd385c2e6625d9f79c07d58f6093bb4653d3d0c172a51c94a49c0756a9b7f8533499bbf3384a925b153d06169c6e02367f8d4ae0facbc659ffd89c3121efbee219445f61dd6eb9a057e199a973627288034013206dafa58b194eaf76090d801219e77cd01df6a1504d5f57ea58d351fdc20be8a9ad5ed4f2ec78320c47a754ee8b0ec4617a3935ac5139d188a575c3fc01f52334350e14d65891dfdd2615658da107bb3b5ef1bd177828c96c3d9df2cafd10487eca304507ef4ef022b157d4e2830eaab088f6e5151082f7f43b0dd0a983acd8d50862058db0c3fcc3fd5863bd0c7578144eb4e42431c2c4d6f435ce6f795adf34e919c367bae02b5241a32e451c8db62e7a1bf275780b85e7e0b8996a0127d920398a6ba60531d7ce9cab62e3d12c634f9cfb015cf027364827718d40891abec56e83e4ab53bcdd1da577583d8b2ead4380bba0ac41e8b817b310254a56957e14255320257693c97fa87266b1aaca70d561cb397bfda00dee61f0488c7e01295375aec4fa7483532aef4c6d1750f8b9e2f56d1816fb3aa83ec428485c02f2170249b52d0c24bd5854c8e3a46a92ba5410b709952e55c3673f58c7d166d5bc0bd547b7a17347ae9cae38e121b5c4f182a955e286be5ffec09f1032a063c7d48784518df844fbf46cda52aa51b1591514f4ebc0e1260219c372d5cde8c922ef99c5e5f5517c62d73d0591626287b34cd1ccaa6d35159c0ffa71b16e8eb13bc702168e33ba90185c946dde7a7a08c46f79d731d7a6a0ed0c1201094fba0f53faa6155be8053aa1b1a7dd85cf350332a20084b86c4482af597e17e3b3920fa2cce942185c9721636dec466f6f93660b7464c04f861d9aeea848617792ccaa7c5d520381bb1d360d38b2794b1ec9d9aaa44094ca90593e9ef3d7f15a0601477cb852a8294bcd4545727e98fa5d138ecf05f86c6f8723e886985c09fa9d6491975fa00577529f7b638f6d086ad051e3d952f215771cb4abab01758fa1711ee5313270165c8bcfdeb63c8317068d9268b0f7f6f22108a16bf38f02697b873309489b038c6c40280bd28bbaca0486a5adfc90dd0017ae736ab3bb0bdd306b8003c5d413fa2809168bc451b6b61c310af53f64b4d784bb43c532ee440d5409dd25c0303b4402ed7b67b94231a0a1d86afbf5398a48a2c024ce33238e93e27673f3526e87da6bcf9a16e991ad0d13c81611f4d5650d77366a17de0462de76d02b82db9357e23171c269c65dc92b215864b5b4e91ae6dc23e0bc3643a90103b06dc0c450083bf1767b2875fa0c7c3e264b91e3a5c0bd58727282493c2f87f4af126172e39992b8c331ed13e5048e8fb5eaa3efb74032fdd44e007273274d3662ab306bf8bebdff987815ab0156a6b8ffa0da1c7b79b86ab5192fd30972b1c2032eb2db38e0809c44c76538fe910e1cd1a4e785ac2f5150a0752d584b2bc658c8a01b524d339a4bb970bb5b6ed43e82e986a1fc048f9e2a491cd9bf2c78b08ef486aa02e3917217c8b671c7b45f8c8ba700299ed127c3d0fd3f12e8bffb7c03647876fa96552241c9eed3782df6315a43252a4508f12aa200cc080c7e3bb8ee8b8810b8def132f395fecee287250f6e7bef415ff10119afcbfa5ecd1be4add1ab234fa30b8fe5350c13d8c17908e6728bae1ba4fc1e1c072b8c94f42faf58efbbee59de4348271fac73e1dfe7d33ee46b7558f5c4d20bdfbcee3e82cb9f1618256d501a2e0e9a81b8d8f0d23c04dd23fb6990c884b9e026590f985b7b1d31d6cad366394c811a88fc52544147dafcf8a57bc068a26949fc0c26256b9fbc7c3f503fb9630a2dad9f2802495eb3b898cdc6aaf95c1c70bf3630edd9352a5faafdd8b59c78aa4de6a33af874ed960854ddad1495940564679810673f13cae7f9cf014664823e5d40ffd8fd6cf0f053f6d4fdb8a938c9b6784c72659ae660422036e822910a608c50087de16aa0243f3dc44511681a42bb82d9d608ec7b59f56da555af749f0f1a524d28e4610ecc15817b9f4da8370a5f98e3ae3911b0fb12bb3fb1ceb70323214537a74f0516aa8726468636e5302645dbec7b1fbec1f0006efc71af65f4c54d84bcf7b33f71ce6e233ee13c743f169279595000270ca94757f4f6a3f2f23f636b40bd317fecec2243fbad6db8ffb3dbf1a50eaba69872cfe5c2e5eeb6c29d2637faaf37aed8639fd19f8bdc51f9b1341b98ea34baeab3ea9689dbb02572dcfefea6efe9eba3ef0eee9d9c569472deca19cd46b1040753a4967e43b4e22243ace19a2c03e5d295f28df8435253d1f85abfd4dee5736a31ab10e155d39544596d82845fdf3aff5fd01e9a4ab2d3409ee730406192df3f389f53caed958614f1bc1e69361012689030b0ee149b52d508ffd7c21905d1a9050709f6efb9c53884318428c4858d1e84bfd64d10f1d6a601f320ef75dc27ef429e329a90884e58fba1f772fc0f5dd7c210f3bbdc3ef860851841fb59f2a26d249150129dffdcc5ba1fefeb7fa19dbc64b08bd8e4410b456ddc15b59862bfec4086dcafdf66f97f538585d9ade8b4735f8d084c9407525491555cc01a798c5cfd8d6915b653ba1421e095f9f36d36d9d54314e842dfd3e4845af1670a8b444320281f2100b47dcaa3cd6e88f414716585a23bcabe91afdc83aa9fbb4d781f7cf4a93931f424cf7ad41d3f7e9f84dc064ce7c3166bee18272e8d234196161b5f3700aa67c2cfc2473bf94adbbaca9a9a5e6d2768fa521fb6359db929bbe74eff0906b27e5654c0aa82ce381dfde34df49cb993be982fd633df33106db0ea409144e9d8af5099a657d6b3e7048111ef0a8856b7028255e375a118f55aa4c75ac2b912aa09fc79e8ffc4b9ec686b2b7c5022ba457fb392bab81e9ddf9cf7919a07dbda1f41c7ee671f4473deafdcfb01f24ec3d74992d38883508a31784d8eb87d995275d791bf8975626935584fb06f55c3fee2769115e970228f6b7f4d1c2c6428dcdee9083a82026346f49d6f21927895cdea9b7d85d8e79b9e044d38d72e1c925f6e1fbd26958299c4a6c2c84205cbd03a12a57526383052cc0c24c90f62dbacbcad10c44de686572c99702552f8b1b37a9bee72d7f2e7e20aecdd73046c95c818e66b66e6142a8523679822434c4221076fb208746202dc2f8abe131d096881c366668de033d150164ad7ba27ba1a3ae5eaaff0b994323413ab7198536fcbedf2ff627a7cac37bc1dbf624eae3f8b7e8a8805fd163144ff5b9356c87e379eec00102d365a4fe0fbfa650f0708340a39020d484505342dddb0f04658d637f106642c9b55fc6f26933468a51049b8ad3e59b05efe56c047357d553e9f428c4b44dac044f4485e5c85f42a394f96532c795071021e297290f51724e578b741882ca0d4832bbb48dc05ff6f542601d9414462538b97e3df592c901a4e754b7e4a4a76ffce0dfefcff0d54264302a83fff43d7ebade7d0930f5fb038945503c360d5bc206ff5ee18369bc7aed8c57d61fd12f04fe29c8a97c14bf6b434223105a7694cc71d795c315f3e37c936a20cfcc42600d05e0d0b936a2059ad9eab76815c6a938bba286fad6544e463b660065cf476d600ff0fe641cf3e1ecab36ab16e30892ee8ad8c18a724b8759a7b718ce9ae94728b2dbdad7198cc3ad36fbd4eeb8a1855e14d989888b6d65b34be223d6a152d3757723621886ba44e380f2ed0082086126310996e82ec742d3d52045bea78dbd8dc37c5303e583f18db09f8e075586237f08860662393fadc0fbc06afc49a57ad50a7fe9e7e9163566ff93850b988f75a1294bc1ac150be1f449883bc9590124193dd51410463aa68afe6c1b29f4509c660f86e75dffdaacb14f93188543f695f0ac49d11380853c84e2edf88bd83ed99dbf9012f91cc251a6856a240eabc5755dedbb71e30b8330b80b41a75f5c40bbf3929b7e99a8ed341cbe01bc1f824b8c0701e3c00795581cfc9bfd4b5f7de1ce533f9e60106009a1748f5e1798be5b3184dfd696dbd4249aa32e6846cb5817da8280d221e03288b93eadeafd768f4b0d2055ef2a32369e6f71eff7d8db951a88c2c2cec294842de1a62ac0a7c47ecc6f3c1f9eb1337935dab2fbb8e63637cfe067084ef586d862e2e9a48d1875f9b2df6bafa846d10810d6ed6422d8e3f3115f8168597f6f0d7d87bdbc378da3db6a32d2d162502a0f9eb7940ddcf532ab6182f57b7bb62317ec7cdb6ec463d6c802747482951a55bb7842247aa2e847af215ea81e93afb1095cd315b839e4073a97f717f9cf251374fc192ac190390c66451f1769f2b895a63fe6092ef8f8d9ec09a793857ac85684e0df92bfcb6a8fe11b69577b23b0addf2a6cb3806c7c643397de7228e16ff1b4fd8f3722d5450027b652fc7c48804c71ed239b773c48b72594db00576172d345b50f287abc1284ab52c28b818b1906429594439344c6a4e5585a229b40c51565751d52e659851fa1ab908d237e649808be24ea99b0405e8ababe1ca765307343531db4187335b096c689d668d582edac7047878825840ed90a00f8ef6681dd759f34462992265498c09e99f4f779829895e02041b45d43b525bcafaf8e600b33fa5b4f8012059284992b1bc86bbbea350597a498abdf3effd8d31ec6852fed4bde085665bef81ba98b8c9777450c473bc033efc8b7ce275868d1ab9517abbd105b9fc2504db4476eb44203a62aa451ff06f8c38697b7aebc416cf000bf6c17772f27122ab280afed2a2a81705c7fa12754d087973f7011c960e17fa3cf4df062a10882a923b035edc3893ba9d2a77126bf1ceab18b383c53e3f0f81b0caca25b0ec797312a8d57c1e02c096979b40d9e7e625a06c2b6884f990776698c2a391b8cdc50feb8773050fe4c8112f919d25b08647e972010000d2fe751f230b254e1c2e7162f552154ee96b0376523227ae1e8f3602405e3d0d0769043b35c76ec6ca95d9603ba2f8214038b98df3d3e65502f5f3105616d003b5d22e35a91563fd0b2630fbe67a14cc1c130240859c88d04e194c0db43c3cf40cb408ac76ee7e8a761c38c3e90272c712d1254a4f63d9dbb663c7e4b6b7c6ddcb6f9b45779f73fb6b748f1469e55c5970d2fe586e601b75ade37bebdae9fcde5cff7ed381d00f5c40d1876f5b79051c8c040aca7324ca7f83646348f48c1467e15e19cc18e4470b2d4c73acf9d72df9e3c8a329b951ae0da3d18da13229b40197f94e5aac6a58577c7be682b66955bec4f2a0c8aafa07ddf5efcdaec4f35b793f17395d791780630cbf6fc5fa5f1e5d15bc7baacac304111f49b7140c2733d98166e027945feae464ab690e085f60a729981d8975e68c2cdd5fec1ce27e964d8f7b5d8a0a9c23eea98c323b36287297a4bf55a67a6133a7ca4dfdb90d42c8b54f4a11f9e76e4ab3858a3828a9bddcdaa6552b7b64c2571f0464898c05b6f6a6b747b4b5d30c156e5abf1f956f6e26914dd8bb0f42cd8eee6e9c5f2529d9e66e27a61d76e4e0ed4a0196af29ba99ea099453bc7f5529d9676ed2d80f9f270feffd5505c99fbb99627f78de6cf8f75728257fd66d9ab2878c7c52597a1a983d52152027aabe702fde2ee6f65f1539d9e5b7a5da9dabb705fe7c1489e497bf9568f7bc9e1d78f3a12493bffc4652fb734d6fd84d8111480a44b634bf8ba2c214061c08c5c742db378beef95f8636321fff326c5b249c0efa1d682aff0dd9557bfac0739484e920afce4f1c01147ed3661e81413e6fc4c584a48ac7a54e9d3986cc87a81ae2edde442ea4632ce4d0a6929f5856a6af0f56eddbcd9ae7b8a668129fcd2d23a8c9bb8da47d4d16ae987099d1db582c719f7b87d02c20cce0ee19113ca715f61e08b0510174da6ff7d18f7956db67fb09d7987d98aad837189535117a8637c0f15d67009477cb2d6ae2c22e01c5d8d892456885dd30340a6e327eb032138d28c4fe50594e6c164419638a1d74c7afd4132b53b7772d8aeea61ce16159c97867544ace78921f2c1b4168eaf6506cd9514622a98eed4cd1ed3ad55269e2382448742a1c7689484db4c011bafad42dc73d5d64f93b03097a0f9ae5886745283616a789d2f1e5e0d88852acdfbb920581066f3d28336ba02dd0887a79f7ef3b2b055f9bada3d5541f4fc7cb708a3a03c112c19c3d868c525e4f9063946c7740195b7ca0a9e2ae213f4d720ea413b9a2e3abaae397ab33b06eaace27b8c3dadd840cbf5ba6d0dd6f19d8038ca627b818e3fb9d4951c7da80ed3bcfbc046910776a87389f367dfcf3b24c0c1312f8973796161ee298349cd2b5175cbab35f89372a179fcf2ddb5b798ec6f9f4f6f389c69bfe99a1394020321916afa277a4bbdf2486bd5f17d1babf8ebd59f4f2f8bb3bb7719ea973d7edf2d2871e6c796df39fa54cb17aa50e6a6872d4eb9eb8e8e8be4a6a24cb590ab1c5e517c7b18625a022387c174d22b6618d2f582ba289852224c9fc69cc3207ea084305cf5fc1301672e2a809331fa7ffefa04523a67b6fc43bd5b5319c4ec73d10cf44d74efacaf7939a755aa0a683a1aa5db6388174242194fb1bbc69602a23241a81cfe574e5245c556dbda03eac64ece5a5b19461225e3006aed1ea401a5137c07a50dde03604889e7880917cd7da84df57e9e70b41bb5e2007180489fa8bc11e4e8b5a407ed72ae35b15c0df7a0d3ae4a532504696e61ba6f9bda0166b2b8d7fa49ffdde1eb2414418e1df07ccba396dca45d2cbdcfd72ad66c54a74f9c87d2d8fa006e1ced177a8645d7b171f1ca95789a8413de4536d70eaa16e1c24cb1066947de1f78a3bc61c60d43a7a259c3fac2c88ab1acd05b89d7fca604c872103ecd34120ade48c48ec79b0f934c07e47b167163ea3fd0e07587cac6b01923b6ab899ad380eef4221f5b25d736e590f5e6754b8b526b3f3ef2b0f3607c3ff3e8ce465b32826e98132d27039d605b9dd9879f12692667763020c2f10976797c03cd1d17181f0966b1c28e3ecc02f21e5ed3c5e06af70544eb89ad87c4528a41142a17f64ebf53c37b596fe249c24c05a82089e627cfd0833878b6f0e42fa30154bad0e8e60d86d917d300a5813967eede2371672ac81cecfedb7fb665c905b4b82749747712bae563e28afd0464039f17d232d581f42e2d11d6fbb86ecd97e88c260eed466455c4509de45dff9b147036af7b13f2224bc02d95d399279ac5e4be329a76bbef6cacf5135988aad925d772a87af5a5edb0b4afd1250ad4a816ddfb33502d620cdf8af3cd12a99bac594440b70b449a5b9cc554a2d346a41f6a66573fddd506da59e98bcda9f3fdfa4f2e0f296c427f320db8f664ea695a14c39e5fc6ac557e06d6082f62a185e6b46028ca4cd3f5bfead0113ad9f5c5481ded52605c2253d698e7ea006260b63377ee59c46c62e8f88c628c09bbe306fe2d2976946b51eb0c6ff710e08db721f1385101ac163a52149970892eea45666c2b78e941ee62e6ec7d9a66828620fe053b607fb5bb1634a5f4a4890028f14b8b4a835ab988ccf2159ce73eea956810dcc221f6c88cc43f70a8724c6c748123940cc95ab39ad8d70e47aed04d303f3aa07d87e83528fa008618157ef03ed4dda648927cadc3ffcc0407f0b8d66c80a6b158115659586a5ecd8b68814d246a9fc4674578da4299a27eb1618fdb5bd4fa8d188b84116d4e4df7fad24b449f54d696d5344f4a3230b29e5db91af1d6f2c6249146b1f09444db30add7f8e88ff56644e834ca313fb7cb25e394af3bce32102f9b362d73aaa16b9cde0d4357fb3e5161b0b4ddb7c351321cd904626be85561da98739397c20339ec68289b5e58b83e0467a0ce8274e6832eb6e43650224bd2974c867ffcdad9440dca8c9fddbdc8b6fa701206ae776e55908691fd9a2842dd136482f139e1afbb4272de27e0b43df896a91e0fe088a4b2c2bf2d33f11c30c68a19e196cd3ca97331f742249d85e8a35c6fd685f28fa3e66afd842b8e28865035e071ccb12f62dc7100ef6952c032e1178f8db0958fd2b20fdce1b5ad8c04d65410646bef32a6b28709b5d8166e302a6e8e97742e158b353e26b0e9adbd6c951dcb4fb6155f24c93f5eccc17bf9f20b8751ada81f2a1d1d2489ecc3df5e8bb7c288fe91be75c22d6a4b3a33e58de84f6bc2b01a637ce05cab51484ed0a166bb966cc23ee56431200c1366c6f18f4fe64a382aeb357c7c9647c9340881f6ad95b6fb66b2d3e33e6aa1cb041ae66024b0e03ddb2fb17ee287be38977d06c267ec9025bdfcf9f347bb9f1490f2e492c788910cbf486a1b5e87588f4b4d6c31d4f140be49b8d0c689adb3adee6a82e89e9ef1b96b3f00a4b3a81853186140aa4cc599f61882a55ab5cb8a818acdb15a6e6037f85eb0e411a85ab26da0f994c16cacddbd562dd5d9c11b64e4d297bc70cf39c32c39e5b514f30442637573a2d5d5bc45367c74168e68a45e830e72b165b66288af8e19d8d2465a255c7d966a3ae693f92f20269ca9588f4ee5130c91f8cdb4820d022723fdf40b916b2d1c06cb2c9e5708e35c726644dfa2b90fe627befbb83b30a5a59cb119d0f6c663d7b69dd5b4bc410ce5050685387610f70f983f45e5d798b0bc6845c6998d7888412bf77967103b6e399099a41feb3166a887ae81b1b4e649341c9c9e51e6d6108f3bf4ad754598ff0821673ab777f87f861a09fa27096be0ee0b098377719fc331bce8bbfe02ce9e2b24f767a56402ab456f37e0eb21200575135286288306fc9c9dd4a10ef4572a7341cbeabf61d6d5ddaad70eaee1a23ddab6ef09b9c4d1d61807439282c37c798894d692030d16e8e98b9e18e1e7a28934b626b4d105e22494ce31642600badea37e18b34866762aba2c8f9dc4fe641e99c38045b426d6e40069e08378cdeb32d0abdaebe8851edf018661d4da4f875ba70ca42fe75b48cb0edb58e8952f04d181e5ab76413902e26fd1a0e93382b2e87da7a5aa1402b94ff02cc9d2a13d9b56b65feca5f5089f74598e42a5c91869c25c432482fa14f218b60dabb5f6bdfdec9cd2df10ad2d750570728fb69e244ef6734612630215ea29ae0c0c02fade927e1fd177ef2892b95fe9392f13550ffbe0900c9a6f7603e2cb13a77f70ff1fed2b4f4efc2396cd00dcc344809d8ac9f9b0517ef6b9839430a1ee307687a72e588fe8a926da4aff893636ca2f8ce337c7d8e30e7650868e440edf1e1f1a0efa9810626673372228e1350ab8179ea03b9dfa94e9995b3a3b6121418b48af77a4a2c087fdb014601fe736ae80cc26181978f466b221967a2ef7351d60c7878c2353c8141a334c6ce5521b8efc5a15a0b31e6152ebffb9dd5c3ccd35a5cc4d39979a1db6c296a171b5b4e62a97000ddc05b6f0a9e93590eaf8d09468900a2ba254a9dc21c8579009c427b485e96406b5ec6fe0d6ddfb165eb5714aa64036b1d99db667b8e66cc196c64827d6d967c693c8bbc9015506b93b05b317b76ec0eed64889fe618dff9aecbda004915fe88aaea7d9d06b4332245e660571f4fafe4da9e84d32261fb3216bf3c2355cfb18396844186eb8f55827beec0530812aed470a68ab3fb9442c9de9ef26b3a405bca2ad5e97e4c352b7c45cb32e890c498bf870804eb68013a0b3f8ee810182e4aad47d1d65213d3ce905b966726392ce69cf3955198bada521cdaebd77d538b3659c6ea428ad96d7918ee5b3d5d50289add16e33378f28653fda087a527ea08f33e8a1b7241a3e6770f32f0d9b78acab098f8eb1529553e88daa6d68619337a1651ec27659ee9683f36db19086fa73be70e04548dc10235a9a29c18c0fa48d6c04dc134dede492089619a1824587f1a9526397483f13dabae8f2b2172496b66b8c5d860d940216313742f3323a9e3c2b03e59dc7ececc679c49407b056d6c3806b9c8d0123913b6510314e2de36953533ae63e76a5d125dea4da9c5ea42e9f3a9e25599d958b216c2e035c13f04b2615aec119dcc812a6b17b1ee4a09340acb1846521fd056a366c9c7479b49c20731f886a56c21e0796d5ee19c0b6fa4547fd89d653c56e0cb5f3847c15b29eb1b9863982d94f20be6507823e57ac3e92ce3b10a5f7ec1398200a1fb9555ac267625f9868d5301a25969ae2a8d7bc0d19fe4fd8730eb7923e30c06e8fdacf345d2bfda7adfa2cc5652b8abd4ebef29f3190c3dc36f85248876fa2a3387d544ff00f64df30534458aa0381028baed1fda57a5b329ca2a628a30e834c6f7e09af9178fa2eaf2fc49348dceed4f9417717142be3019b94ea5a9feb4b2fd082e1b099ff8350ccbb653b8370b4f9ea4ee77cc01a4c9f4894453645944cd7019e4a09f647024e843e4a0a06f1b639470944c3e9b5f40e28d49858f86dbebc5db762bea9cddafd721e09b8762adbc1eb3f8c0a026636e1a943874b406804ade017f5fdf223a380321c5c98618afb3364209ac28d130f3471e4628473d2a3b23d6329bb11e2da10068d1d6b8374bb4f740919bd7f8cfd9a5de82236dd38332acc8e4440060f8be477c7209a745a4665b8bef4d7629a18813d7bd24ccbfdfed9021c0227a7fd1ac755f644eb46bc9d18c8ee4da56d2abe4595594fa68b54c09502fd59694cbae8dd33929c59fdf546020954bc63472b26f28b3e321f306c91256be8c02a7cdc99e6b43d350fba9d24df29fc8a93030f93776590a357e49ae0d03c40b5b300e499e90a2022cb8743b9e1af3834b633c8a8a6320f34c5d9ca0f298c6a17738ced89c2b4a1f826a3aaa76fa93303a979e3a04250ae7b211cebd2d262fb4533c9796e56d465e1a4f4db56c5027ecfec255186b21e31b5991f615164b2682d3706374d6caa680423159fea37b475770ec849f0534c9a516922989aeb2e61bd965ded4210a2b28e7fb21effe49ce119ac426e181f848bdf3fe6435bcdc9e114b5a6b0576adc87231ba064798107f311f292bde1e62a0f6f79c33bc7f61820d90d4ad01180a4a7096845f8ac9c2dd09c941843688cf3168216ea6e8bd5203853f6f6b5ab978950258a90b2bcb637ec844f9a96e8a0dc4ef08e4327e8e4a2ad493e1560b2da6645792b08d316c40bcc6c2f442727679eefd8fad4c19adb18e07f8bd74537f06eb9530330c0b992a91e0d95d6e62b3acb7be3bfa50ea9d83ad1d00a0b594a813ff505bd01f55381d386c0978bc589cb4028ecd7519d91144ca24b8b7e699c1975c5d5193d697d0240a382051943c2444c1a58f5867f364cb8927cb55b475c0b445f5a91821b175456bad3a4617d4e09ef4ef756179c06611eb4a49cdc0be82efeaaf5cdac56d3fa1507e285155d40212bb8af207b8bfde03c4956795f187a121cef8ae1750865d4e1914bca7710927441933b80c68ed16cd388ec6c8a132573a1e7abba1ad17e9f8e0b9a93c9d0db11e406c7983c42e394f500bf20bed9cfd781c9d57c2c3798e3b52722cbf8e603cd82c3b7692b38a02f42ad838a2ff4930ca63b8fe6cf37a56d9a48cf5878d23ef04178bab4855283bde449035a147bb4d108017f1aa777a72272aa7c389fc5a8db2170fd820991d8c5070834a17edf818828da398614b6aafc9629e4bab6c1547801a826dccdb79f78ccb38b8eec2872c5bc7d6a605831bb2c67a8c8052a65a19eb049a63606a0c1c2b67d82685dbfa6a9b3ab76dacf611c516c62fcfc8de84825393a6fb9d42ee38d3fc3c53da8557769b5a98c6528c5a50146c61927cea5089e9c4d596b529e74ddd3d9d13cee41bb744db68b753894486c6e897c2f5392e1baf303290dd72a530b8192e81c25c66cce8dbb0ba02196aa855bab08ab92774f8cd8262c3ef04f0658f30bf56142999d0f75997a18ace4045b5d1da4716ec64a3b3725e6f252df4134b2e405b41a06d669ed010ad204fb250861bddffe08f5486b81a01d95c5e5dbb01022cb05094c981e73a5c9cb5e9e5d03df8d04cb8129731b93d2f8f9916c2303010f75a17ba8f54f05a5508380159ff21084cda8d5a55e31ea9784b666c4306e82a2115d19f9ed4979b3774acdabfe5bfaff6874fb5c24e1053b42fd5892a9bffe38901c8ba110319cfeb39a03f769b67aca52710c7e88fd75738e1aaf37ed04b2b295326cadaaa6dd7346e530ef88484b1bfa769f50f672b3b82feb7d11e946dbe8c050cecb6ae9a441109ea7a17d7ca98694e4dd149d1e292123e28b31055f320b0f4dfee8d3e6e2520ff74245e1bfe344a8572c5b441172adc12a8f33b393bbeed8c0f7e2624b7bcb57b7da8a1f53fa5c8b599b7778bd85cb1e8b90cd987dea1170cc900c5e80db6450acc99fa4c8dabb159ceb1140a9d2aac75468400494e733781294e93b38273324cd3b9260fc7053b0ba0c69d78bcd230467a9a4ddc5d8c06fba4da7e2e4983aa63498dd69c04a5feae7a93f49c8d25bf83205a2cc0f6fd176d32656387f33c83ce4eb96275f532358b9e45bf6a2acfedf0147f17a310f8549884c3bfd80eb110ec8ce29cf41272220fe960ab2c59964e20a9d4d2186b84b560c40167412c0758c51503fd0ba4d3ba864308742c75b05dde782ef0432ac37deb30add13dac0e6021705649e82d2cb8e30b23143e04c643738ac69cdda9cd97f97cbfa1baae2b076fa78c3feccd32962bf2e9339893301b94f30fcb31e8564afb067320608bf2fc177624ceb4a1612a5ded969beec3a10e00b84db5e8180579da0abd40dcace57de8705d80ba468a365ccd0c8c29397fc10a58101a0b71c4524f8b8e3e85d6b720f08507542e71b29501daea58601a2f8c9ec9971cd07b38a18f3f2bc68ff3b6c7532d9de6c7f0a9a69637f427f2bc787eb8ff8e40bdcc9bf18ffad53c3610eba57a53abff388e44db0da5db1367e80b51fa709a30323c59ea2e0c4bb7c9b4ab335648d49149bd827e9d13fc7e7c977aa5d38dda79c7b9cb600b73a115989527e80f9759ce975fc544bf5e00454763bdbe29c47f2cc7d97aea1368fc44c3fc5d9115372a9e429188985911900c62fb737308e290cec5dd26a37a1d835414d620f17167cd15dfaf2c91c911c26ad604b79195bf96023745a32cff21320bd862cd4b8c382d92e8d1bd75e120e27905ee7e1748507fcd0fbd8cc395c46c641b4d881e1f4a7380a7abf9d64528ae1a0bd256c41baab928948172a5ea1c50862454782b70148575c771211488a495cc77e969c74b4fafe09711c6d7093ce993e1f190e4bc9645352354517c34bb8ca173ac6b7f6e91e56d16a6ed899020cdf524af9e93dd333b09b48764117ce6eceafa4a4a13702538e667f8b04e99ada0b28902161f7edd4f0aa5d13be4582d4553e409425091f71ea261820277a1f3b392e5e7bf6857c3aa5ea07f07211f31203572012654e67223fa74f351475492bfca950119b4a456c0d9a1938a56181b6ece68e5cb76509a6ff4ed0a797d5b55536190baab4be3855014104c153e9e00853e9596f5ba6e5fb6ba70a6c279c878609faa35744aeffcbcf3e230a0f945db6c042ca2fec4ba59e8eec387f50a2dbf815c10a229a659ec44b63b3f866979d17c97ab4d34f0e0819ae8f627bc5a03216b7c1738513aa8905c8763ea82fbf15d4a5b2e543a30a913880f81702e8531d9f9e3ba5a2aba1cc21a1c1201f9d6d3984d6be271c24567791a708e638ec311843eaa32c93b89971d0d0b948d414899597b7808d7360cd5cb10eac20be53dbfa3ccf25984240e3accb26ef25a55a3d4e40f3f83b978032d24a33a07c78b3973eb5ef8f7dd4c63c8c2fcad8cead4589323cdd8ad1a4ad21b9d087f2154d954c959ecf3377c2e3665e9adda15310fc12d38cc20ab519f7ae365737c9b6ff33512a8f82efc44c9b171fa73f85b7fb8241ad0a4d545ddd6c606cc18e70006e4e0d534815d66d7911161ba83add966935550e28e1d90841a64e494143b21c5ce32e664a6487df95e2aa585fc49fb8cbf32bc2be86957dab37855bd4c99c45799a1d39835a02f47f4bacb97aaaf3b6f19a82816636a2f80bea9e4942e18087dbe52b1ec92a8663ac03fecb21747e7b0d2da2cd2e9bb80cb5840c0c27960a577b4cda60e26dd948b112d9ae621d81e2a56f2ee0aae1a270146179a6593246baad2a5fe5e65d097c66e90eef4d04a0c2d4ca9f9863fcc54ac54d9065758b8609d958a5c65b7c49de60847170dce5e6edd6590943189853c27ea5d049dbb21ec523d46b5e0d14a8f528ad5c750ce35580c3a61b4a6e6ff68a6326b9677178fa67e84c60b15998e339492af73ad20005e7b0ae34c7e1146d7ecaf4874fa4ce97dc541b26aac593349b32020a8a6ba57cf188cf3607170fb101f86b4310221b0c20fa290d96e9c42111a286e64490c3dc45f2e8dbe1546fbede92aef6a51fa8480fa5b21785f59571792ad4ca1d7d6b3a5117b5d20c728eaf627ddaa20034a08e10b6538096375847427270c646ea1a797ba6880a0dd36e5d00490904022f9643f0b8fffb53ccc01546726428cfde3313d66d98468914c75230fbd51e112d38cc50a9c27e6da079282c651f9eacf760e907890bdd26f1838a9ff1915e20fd357008b0a166934f8bc4f83d8a71adb96ec23c6c25d95812a28d8924e8981bad767ae1e8e426b1af0dcb24f7306a4617d25598e3188525caef981c4dada077696cb01c2d3edaa301844ab3270a22addf3e2a8b59927e4faefe39db21ea7c8973ffe32ccfa1a245895c513d8f329c6a5d25126eb5e215f53554dfe601e5032be3b7725907d4c5b9c3f96995f5970c6a2e353535626c7e3d8a60b4f402efd6183a6cfee9139c5724dfe2bd5ea8e9c70c3f52b2e91a23bdea04675ff7651c06dcbc35215e797ab1df2a2ca2fd6f0c9aeed0700e01f5127578e807c3cc1d86d41f79f355b0124e0af71bef97f0b3bcbd17cb0078548b656eee0daa5cff37c2298827e7bd3b03050798b2517c069ca36c644bc8816d6c0875cab47d22fe0549d0bf73fbcfc44bd25020e502b0ddd32022b065a4b9f7daf4964d86fd3d8584856001e6c480cbb400da434149dc4b71f6b0d234497f840b36ff8c1da4638dad478c276773ce83ec65074ffc67ff750c22b449b4c40c040cb4966e9b7e56456f3cff643a34a5a135f976773473fdc3e159be69b6b87604e4b04385f9abe759f4b7a5859ce79531880c78018b2277113bec70c9fe48693c53402fd4c1f8cb8c4a371be0c46cb074312b2a2a1f601608bc56d09eb47c9010a0a0cb428865d521ff17b757f6c9cd25a9cfb45d0cd751bb4676869d73aced65105a1606d94c9a023731cf4c01e9dcfabb27a059678697ffd48937d81dad0bfa98590f977e248b39d17aa2d9fe2edabf51a2283f8583e17bdb0241992fd89a9007f0a923ba675320a45daf41b7f0b00490a107115362aa16f1570b9acefbf06645a41a3c868774c7249ae2b77375c94e93c4e9e2a29d8c43a60af2c1f9c3dfaa6a234810105db3795ede906dfc19b7e9eda26225e4eed4ce43e4a4e9d41a71556f507e78b55c018e6a6f6997e3051bda142d4b84c530181b93920c8e258c4991ecbcc8a06eea8408184b88def4fa906f59d2eb096532ea2ebac20f4cb79efae1814086516486f35de3459bdeeb8d9f12d5e95a08141638128c063bf2b52d044773427ef5dee7360270b4a507bc36d37c8881f5a86a99ef3abf51594c9bf079f391b59464c96fc2319644a5be30a1c009ef9c205b86ac903c21c11092daba944714d2d997e18b2e3502a7ab56661501c4c7cd210678f42b6ede3005168d7b55e3c5e70f8770243f9e1be5d489b090f30d9ec24a31eba25be0524fbe5b710306395bb930f7bd3ed4ffe0e2091be6495f4a40ed063bb2bd5ee21193836273069e9a82744d37c9d31b8d10d8b9eb4a87d6c8fa47b579519c85ff500f592df69984d18d40ecf2f3f593aae4493f84c69a6f3b6f09b4dd9bd81d3ea079234cf6ee08281cb5de7c6f1894c5d35757d598535a83aacf5352f447abbb3ed109c1d3601122ef7f6645bee1426f1039aebefb95737363a73999fde4655c093872dbfded9053c4752d364edc0a1be8668eb8278004bc7fada6e1f286512219da67f59ed3ee4562b4c3ebeed8c9522d32db4374feeb79dd4f9256ab82104d13267d0d5d05ad4de6c21ee569f310158116189063624cf8f687d8da80d2ecfcfb5390b568c8c941bd30b7d7d2ff1899dd792138601cda5046576ce52b82a112745d6948cde41b28493571dfa3b80b0ee792468bb009e9c9ea63c6397242dce0cc71666c25deb59e1b4f54ce16c610670b4343bdc5acf74c36b32b5a06f3e6cee020e8493935cadda985761b567cd3b394ea7a1a9c19cc3aa64fcf817e4a96a7e7fb7c81f6860d66213cf187bbc0dcacb429deaafdda5757a41d02918a122103cdaf0aa6558747fa4c32569e9ae490913828e10be0e6956c7e1317ce5e46823f9df6f275cd7eddde90f2ecc1ad638928e4f89d38beb5d7d661d44c3c099e22d5af1cacb7ceb3584d095a579600af5e8dcbe0aa166024377debdf49cc82cf03e2b786fea659f3e58c55917ee649b530caa93a3dc78abca9d0addce4d17bf5e74f34719e58249863107751ab8e964acc9749b568e811bc71c35d67faa6e904397d57165c09491ffb2bc69f7394c9923e83cd39337bdf7982a9ddb7adc1603da7e9b98b91a98d0f960386fe0dee54f01f3e8ff3af17690af8728ecbf7ad2baff247d9c9cedb8e07e04c7663cebdfe8f47d200e119d7a1b43d2e482ea8d9425f27fa32746a3a5cdf86fdb034d182e5ed8d6de535a8a16f27f361ffc852287fc1f8ea312737d35a0dac8ffbdd4b044ca5b84f3299c900854576290dbc266c830baa9966ae14b4f81260163ca8e9d0020bae9b32ee93aa93fbbb7a628e23782bc002c1846b87ccee32199a208c6bd33bc004c92a42a56640fafb6ddd2f34567dc5d606f117c5a6895aaa5708cc2909a761bf988c6e033be00accc897f2f0000c5a326d0771ec4008c649b4f25501305f7e2e6312c142ee4b47615bbd649479bc2be7d8572f091acf1358309109d61e47651738a4ff4d5d619cf0e6ae30989833ef2377ef907159caf89663eff8703c112fe00f10e93fcfe834addce21061a032aee988c762ecf6cc2a1014b9bdda2f7739425e0bc25a0818b2860b16ea7377aef064ad52e85f7ea3398ab808374d3e4efd9028028085ab7be4002b38a09266691bd868718505c36c11e13478ddc35c7b5691b09300c92ba25368fbe11030a2bb832adc916840d5069d92514c3cf50ca1b6083100c88e92b7cbf84051a7877033a754c4391a4d0316dc0f41308a79af4606db8bfa7aa8b00ee274b2b149cae04722bb7e0c701a088bff2d44399191901c69a394b2988f32b7e0e20fd1d0fa8c3772ea557c39c9e8fb553327e8bda430fa2aa3be8334156dbdddf01ad0ea21eb0b9a107e84070f1be01080394058be145811efe5a5d1502fd37c9082b369c40dccae3244282c08241d092872f204ae3aaf7b21fec48530f7b981f29875cf7d4a0ad91ed091ccd8266c4e45b93acfc37d34e81965d356ed50aa22bc7de55b26f49642248bdb744605deb38162d30fc2a0b24502df0c95d031fb3455f60964987088fc992d80bcc52f55c66421d3603ff9eb1555ee0e9ca4117ebff8adea3c2959c1ced5f0cb049613168fedb8aeead139b57284a1e5a62b56fa88ccdc54ca34fe7675d0bafb6831fac4f96869a717b688fe82f71f4117b846d1fd80eb692cff378cd599ddd4a3445fb898502a00abca687187461f2d34d8fc08ab5449785e395947e2ea5d6a817860afba6cfbf0723d100163adac3282cbbddde96d95cfeff4e85da9b4b6965bccdc1ce203c10f64a5fe6dc33a09439cbc5eafaf7d104c26d36912fbe55a7eb02aa0f260e9dd0f763d44753836033309bff29aab095be0fc70d944f494f170621abf4184f908d78fd945d44df28171fe43d50bbfc9cbfe7a7b45478e3d8c0712c9e6f8e509a5610cda471e6dbc1ebda75aa44ba0821262b16f9fe71f2a616940556b7d116ec7c0bbcb560be6c24add594b559094e0234e9302a6cfe8e38954615a3195ea7cdea8b36929506b793e50df0d3b98a89a6d823d0ea8123815e8f297250d33ac85ce0df2e55f01c338c547450abd85542382cec31a165241a29098ddae78129196488981e1b5e9995df3699f264e3470feab7656598ca52ae1ea71c6cd1c3f60491026bc4dc2bab9bd929fa5bfd00c1c893f6b7b4f04bcdcd808a619776144c89e308fd88681cfb309bd2dd7203da1ddeb6c08abdc051ad34e3adfc86539bee35d5a423ae301396736da34189d96982adbad2b031ece7ce3939af8dc4de689fd2aeb2e8a9ac6a2230034d6bacac920588eaad65a4ec5eb0e39891bec00a59b7cb957a0a49cfd0e2cb21fa43bf2ed23174592a41d33667ef5ddef028e86f899cbcd5b78873cfa6cf27c1a918cde36ff40d0ddf646c9f7d339e1a0ac0040ec096e5a64adad5b347fe40038bfcdf95d9a99d003dd8eb5185e0d9a7b31dd4decc53af4d2856649e62ea74e2f2ef373aee9d7cc15889473d724d809b2bbd45f7ae7a179ff682f29aafee556925d1c3080bb65f3db3388195d04958d674fef3b5528284231c95991735aa5d1b64f9300c8980c749d1cab0dee9f89b621299be67cc407da1e058d21e7df217ac41e2a559de995930b10342bcac95d02c7c2f981011cf505592aa2f9f4bcd1cb3028d1b2a82a79215daef167014ca99949638b3ca5903dc073ea50f486c695a35b43b98b6600a7cea88aa6ead88248013b7a48320c1bb1fbaadf40177dbfa50d2f32ac8f04bb85c2942e50f9a6a0e43d208616f32c26b98bed6a34ed8da24071facf8e199ca173602a6c233a40268bf9c9b4db9ac16d0580789f42cbd13331945f54bfb092aa2279e4f032e92cfabd04805a670ba89eb29349e015f36d34d3c9aff267f90f45db44489d070f39fc787de66dda5e6c32f371451af3c6a68417f6de7a4ff622ab3367dc64acf039266559628ea6a73935560b439d2750418678b18588647ec1c7a3cc4307c32f55ebf73480f7b5c43fb56d8c49871374d85a4f38c0fa2df8234b5056efb854814551030ed6a3a0c581bee38d40832fccd155c40f303b5048c0ca7d3e988fb81d36345d58afe29c46e29194d64174fa891b4bb45f88b4f249bce8df5e3f3b0b70b13abdff33aa42b5203fbcd31b4fd40c2ba096db2770a1216e814f9125e91b7f27c3c1b2fc7f3bc27de05dfcdcae5bd40eff33c98e7b52cf87a78e0e7b140cfc987e3adbe0bbcd5eafbc00ff49c7c9f17e4bbf16cbc17f8799e07f3887c37ae9507be40cff38aa8f703c90bca6a0396b7f26c3cf087676303c3f922e402af08e87d5feb73bd1756308463c5f33eeff33c2fc85d9ef30fc8786abca8e1d44b0f6778e185870c1174a96328b1458c303c8a47030ed5c3041eaa4ac09f7aa04287271e9c454b0f61d43855a229587ab8a00456ea9021273dfc107252751c5124868e91229e1baf89181e0a9accd6ecd084a6c0c7991c1704cd88f880c2100203d121241bf000b40606339a8301f5b1e33cf000e333560c4ec132bec17d5386b72c77ca8c6a7b8bbb23f98b9b1e87a1741eebaea5ac65e626832daa5c362e1b50c7ab89cb66452427ca84a69e974febe66504e6ecf0c09c960cd4ad74562298a6cc0d552d1d9fcb0604d22162a444d50d979c1204d261c2472484b505f45a50185015054b9258e6036f402c3f27a802c10948bc30c163c1440ca698f081415641398f470a6b832faa5a2d0c9ccc54b0e30885551513005005c4a64b4e8f9c1e2250a920f7d2376e5441d936556632842e9b950e4b0a54a105135ea08e16116fc71213ccf0c106c905ccbcde18808e0f873545743901affc17264ee0c1f2b9a172a3410b041089cbc90952f450e504235eb0201c32a3436259e18a2a9cb38643191d253b3c2e43564f5c409a9870e372d2ba79e958152d555911b9a1e246d58d6b4907ea65458812951b1b2b3950251429aa7c80404d6e5a301e3aad1b1ca59e1084a8ba7959b980c642156b15b2bc9696d60deb490b8994239cd3e6c8cb4aebc6054427c9c608d864e3a3c7100d825880e4b5c195270d4cb8798dae253a4b8a969c5c39a02b080bb66a6293031ea1535c36ae1b50887505349ae2b184ac7858160cd9bcc89513a5273f5e3716d8149980d48443f4f2018d80465a465421d10126b136605d6063041cb223c98ac80955a390b7aaad82c0156b849e10046005be4b28e1051a269630028815c89040a5a56a4fce4200e1030fb86c8172c2d2811598d1121f0d322842412c1cb0660d1198c181940c7a820071f110012e8610428b1207302215e4e0b4aea0220149103104103ff4c0830e5b7c8e088d38dc1005b5660e13d03e58430d05c8b028c30bb626e5c98d8b88186028b2c9409a2288803969e2a307cecdcb35440802a000e1cb2c4d1d2c5151da21002a0060270c26552ca1841164ba70296af2a30738012f4ac0454a942636ae56132924c0091f6a10000b231460871d5ec8d224e5c9911c4f8e551cdf1ba01aaac6770668c627062b0c4f031f06be0bd87c9163816f029f043e2abe26564b7c526018cf8887c18b086b880e90cfc75781d743e4c1daf9442f04757c38ac9bcfe6f5fa5c2ed6077a2bcf6bb9ce8e9590951555a00b5520961612eab9da302114a28ac5ca7169a00113c01e92803e728038a2b5421588c5c6829b213fca54d978013f585d80b35b392f2a2fa3d6cdeac98f1bd61655af28aa7a8060824dd14a073672c0a862ada962e504b94055ceeeb1a2cae6878ecff58425c49ae14c053840067002d842d50e9e1d3cab2051e8f5435c73026b8397910bca6ac70aca0b88aa155312a1d64d0b89aa154e2b0808849364e5813920d00d15d6062e2fad20ad9b56989c1e393d6c7ed858e0f2b242c109ad202b264e60655195411810476519100161393b5a22e8ad825648a0538e8fd6e7eae212e26a6255a0c36be1ac5860ce6ab5fa56aed56b657333045cb9b6b8767638dd88ae96cb5bb940225008f45e4056424cb0c1807544950e244c105ba09057d209201827f4c8a8af498119194833012f2090c569838a0106a5230ea214e1bb861a4247c21d2fd7970fb234ade9c20214c0e085211b0630e50630302081072800014b0c21346a50c3b95fd0b90d12a8e1c0171670c001c8941002081f7890a5a90a150c2ef061011553905942891248901119822f063ac0f080185cd59a149cc00d10bef8b040c4c003b0588002100045132f04400559085eba64695a9240175844a00a1bac6c4003127880031460851040b080421912a8c1c0170ab0820a27cc547db9a424eaa829830c3020e0802912f0441131b8f0a52c0410be7459527a02fb117524d0050474c02106335f86d0c11215a527301d3aa097418604bad0028b2912f0441345c8100033603a588af204564494e46747047d4d196478400b0860e1802912d0441132c4100017cc8c210303020742e31c7260b9b243470786c0d0020b1e7419f2c10516765449d8ac59a5e043014b8e2f8ed711a0115f11df109e922fc96ac81b7182561baca805187c433c21ae202b20de0fd08767810d8f9d9d5508eec8d111ea04c9018233e446b471bd5aaeafd562b1569e0771bf09739870779dc73769755f9b73425f62d2de9388bbe7ca31e32d0f73b884394d618ecf0ff5a1f44babafa5dad26de9935daeb9de485a2dc57103878d10870cf719b55dfb371dfda151a0509f2850e8b5b6a7e13c11e214e13ea3354cb5a57b4703338ee397252bad2043eeac273dac23ee8e818738465ce7f1e6dfbbe684f20641100457abefdbc0431b316cb8b876eb2f29ddf852fdb8a43f3e4fe613ebd046091b1bbcbcbf2bd5a1873654e5fdd0868a0d1135690d539a712dd7ade9db6ecd371b50e731df48dce3849eb4a6b639c1dd2f70b719e185e6b5596b7fe61f77f771f7235e194e92502934e79344e2c489919322274d9c307142e4648913254e92381972e2c4c8c8a8c8a88911132322a325464a8c92180d193929322a2a2a6a52c4a488a868499192a2244543454e9a1835296ad2a4099326444d963451d2244993a1264e9818312962d284091326444c963051c224099321264e888c888a889a10312122225a42a4842809d110919325464b8a963459c26409d192254b942c49b26468891325464a8a943451c244099192254a942849a2644889932446498a923449c2240951922549942449926428899321a3a1a2a126434c868886960c29194a323434441ba21a7a7717e2a10b059d47dc798536aef6199360dc5d03f0cb0d90cf8d0f0994043a02a54037403723504a2975f70cdcdd88bb17717790c7354461ae9c1908c29606b27077201eb69698cd28ec066836a3b05d7b93d29bd9cfbed96ca674d7def4995158f9a6ad355da34489c2a3ff46ee8aebe1095b3b2a0a74e408dd95962f45da5b732d2777582bc8dda9872d1da7daee9346a9b67fdbfb64d2e19cf151c83ac37d1cbb70779787ac07b072f01f0a050a4de243ff34eb149dfdc54d335ae9c574f6e393ab59774f18ec62bb735966b47cd3ac240cee3e86e01cac9355bafff8e4b735bbef974b9bfc30b42c0270f72e1e8273b8cd82977bf9b88c6b66c5d1a4a52c45ee2ee4ee50dcdd030fc1289d474de6ea641c0a2a2d0edafa62d2dd7b90b9923966b3471847913966a424c9eb7ae4ee3cdccd7a6eb296bfc9cf51296d6bb349b76dbb8f45933976b39df82a6ddcb47192ed5ad3d61cb5db399cd2cdb2317eda5f65dfdfdaea8bc9bbe4ee629871ce4a9ff1862a7fe7ce9c3192fea9fa584a6bd6a9bb9431951ac6d9e97e95ecee501eae80e83cd2acb933a65577dfe1ee3a3cc7dd71dcfdc693c1ddb778f899e177fb16e0ee14a6cb7b621aad61ba4f4cf196e93c0e8d49c62441faf78e26b4448911f94e68a513274a9a90e42709c26172a49db76d6dbbbcb5b57d16c2b65cc951a84a89a02d4ee871f82644a340a1fe7a1955e0e1477d5b33a7e96cd7d98e879e09dccd1a840f0fbd27dcbd87871e0f1e25bfbed6dd75dc5963bcadf358629c10f953a1e7c191bb5be0a107e4eef74d0a853e8d96e6896f9afe506d7d28d597d7d29fc2b8cfb25cd56977b771ff4a77ffe1a107ea3c6e5cade5ee9e679cf3d35b69f9379ffb33bdd8eebaefe3decce12cddb6e64d6dd7ee5d7119d768fd5aee3ec443a77277d1439fc1dd75d8ace59fc274b72d597395d479247f4aa6ab5988cc5a88c4a6598576b9669a35dd9db502ddfde3c0cc17073840151b1f6890f8410004c63875c08aa89a0488d0c831957c784060e4050fdd6b8cc3061134992a006163d4f35380245ef000b2c2b8cae924231c843983c20faf0a00a98a02c7d15455c57c05c48c0d38384467bcfc4b013473c6a3291085600d2b72de3e622c1ac63525c5a25e071e134355d5b3955a3d521db4284b0a4b6b4bb5a817fb5c3ddb8bd9ec10426506779c1d3478d4eb99c1a3cef3ad78563c5b8a45bd954dec6bf56c2f06ae89b1c01e2a7c7bb1af870a6f35021c213492080defcc19907ed4e3317306ec49420c07522b2ac6d174c159519c55cf151c5f8959992f332798e07df403bd038020084a7dd4061c33f55156cf5e51d62ae6d1970be49162f5881913b3e9916a51d69415eba32fa957cf981808ae562b70058220b85a510ff43ceaac2270dcf3bc559817b5b1a1ceca1ba4ada98fb6280be411f32326f5f578cce31163c58bbdc4ac3cda23664cccd52305d28fb65a3d62563129a71ea3ae7365471538424000234408e9f5c011a265b5e3078f7a3d3e780a30b3a26652203a62d13154ad332dea3a5bfc8845cb90a9aa02573cad9e174f1488b3c307779ccfd3f1c0a945573cad568be2b8ce961665f5ac7870583a4e2e123ebaead2a2df9702092dea1d7d2e176db97a52c869b5563c600a24b0a87704521c9c1565f18c2173c43a5af5a4907304d2291f4bc989453d58d1564f0aded18e35472cb0a70aef49c13bfa7aaaf04a687d200e38ab2216d107429042c1a1be2ad2e1f9705639383cded1475f3d551ff5e88bc78f7272a88deb06a4371ffdcc8034877a3c24d850efb3d1a17e94c343a6aaaa8a27050e8e56f486c7cc19ef684553e0e008a4ae23440b487568d4ea0887474787dee0d033ded10df5e88bc74c0ec5f17848b8a19e8d4749c0a15f4ec8a323848a8bb6a45c3d43ec3edaea1962b7a21cbccce0d033d48c0e4d811edd504fa7a76a453deaf59859d19b9e2a3329d0231dbad2f940878784125614a7a76a4575b4e844ade877c363c373c63bb2a1383c6652e0e00887e27c3c3a2b1d25215a40aa433f58d11cfae23163d385cc914e0f0994841b4a020eebc686278524c470106b51ef88888d508caf89b57aa486d87d74889d102d20fd68d48a7af4e5b4a274f5b5a4c0214a23fca84789ca086d70848000867aaf55d19831257c74454bf02848c72411e5a22d3ac6cc197a14654359b48417bda13e26057ac4ea1953428bdef49ca1472b6ad353c2ab87455d3d25b43ccaea29d3f26e06aa8858c3c31a1b5060460df0f33ed002de1c4f78ab700e1318f17e2001fe00636172629f8ff7792b2f470e325e46af550370d05cc941220e35719cc1c6cf8e385a8863863872880388389088e309c7f158211c73b88ee741ab1d8671a461f580ab1c560e1c5a787050c08343031e1c6378dee781ac9507470e7000010712703c010715703000fc5a28a0f15eafd70f9082a0d11b676220f8860dde8a7e3a5756d4a3204501e7fbc21ef405d27ae0f84794055116a0e7795e16edad413dd08d39dc308197e6042dc4da30b3a24f629ea6046956ab96c33cf6a238613eeaf2c0d8176bf57c445d84ad9694778515868c7f06d820063aac0d11ecf8d183c562adbc1ed08d1871d88f188b4533f0271e63d1230ef3d80cfee36de4f06a0388b00d247eb4f1046d638a232c26aaaaaa8e38720aea286d10c3017df5e908332616c67b85f9562c23282cb0e5020816b96898bd22dac24c122b6a26057ad4ea49e33908e6803daf2f8ad6ebd57ab53c25cf080a96b0c7aac56ab58c5a2ed7b76ad196165e822c3e1c56192008b672a07c3facb0022c232deab2f96c40b0f5d2c971b9e84b47e7f4540163383d2c0f02691530e6eab90163208e0dbde159c16c28ebd371b128c8638487d2d0c5fa7c86582c106c5110a42d90b66e22f0b2f93e9beffba80d0fd0f7b32a8aad98b810ebfbc0d647592da0cf670512f9a409d18055d8785ecf8fefb55af5f8b4284be7f54d59794e73404f07a43d3c90b27ae440f3796872c0f93e96fbc4b4cbf5ad58ac6fc5ea01f2201608aebef0c2c603592005592c10044116c862816a8a7c7c7c5e2478c3f5234c8f21b1154841230e6319b91c06d2211f209f17359263c40b968d0dcb8605d2cf676504054b0fb0e56ab568185713578b7a8cc562fde09902335005c3ca87e903af7c14a42c5087e5e2a161fcf359c57af8be8f8705027d31acbcd56a459b4cf97c5661c80611abd54b07908d19b13044b195b7c3615e0f140f5a515087c3be1ea75032107ab18fbedef83e8ac3e209c3a11378432c4a8d7846565febd3f97ac2901a617d4394d230fcc162b1a6f8a0184531f2fdb0582c168b0d13c4582c8ae5e5c35a7d3e465f8fd1e7c3a25f0fb562c4a250b084445f93152513db1ff5629f7b01c6c7a24ebf8f05ae562003beeff3becffb28a53f28b85a7dabefbb22c4f1becf410aead06a79200be4a2f5b558dff7b1be6fd5fabeaff57d9f1cacef6bb1582c568b25c4cae359ad56463cf40182e2b55a2d6aa4b55ab5807c82a8cfe7e3d35ac901ba3e10044110044190822008822005bfeffb7ad0ac84f0224aa91b3c0c7d4386b0f156dfe7f91a3455aed60742f1a0d52a8afb185d11be62af1e9607b97a7a8c7c3ee12aa4d4c8ea034d80b302f220162bc87d5854c7a31e0f088a7a7c3f2cca3a0ac3f8c45814e88302bebcd52a67f5d1950b9ee785a18a85d92b4fe7079c149c112312f33428382386f3e540f97e563a4a203562a4275c03430812690152d0e7c7eaeb6179d0e7638445874e80650804b1d547575f48519066e5a373586fe01039200c12c359812b6781617c629ed7628134e61ae2412bfa83e54d88b8cf7745e831d0d56a40f8d12ff662391112e147411e9607b128cecb49c00409a220011a17f298072420c27f5ce90b12033d88f5d9bc1810b25e37397ef4830436c45674489832622b8a839383b3001d1c34e1ab47478e8e520ece0e1d0a087758118a6115e18e4845c863678a1e3c1250410f292ca8208ad0870508087ff870203f5e41807c3150489055ec3544089121de054430b8c0631fc580877a3d3c457a3e234532300245e8c5400d32008f68f0f239f2c5c01f9f55ec85e4c7db0089c73ea00dbc20a06f0c3a40e8c5c098d0f8443824648030c9902b49e22d51f2112d71225c31216aa20993a22646454e8c604e9ec0a03c8902454a140ea4f8140e9a884d398a211d252129255151aa42c54a95252b1d2c79573ac07285094b13539626a72c9f16a7272d504f51505ba2b86c61225c75e1e241172f1e7ce0e5cb07207c01034208606421cc647546ab24ad24bfacbdad76b3b9bd85b152619630a570e6c5e19bc36272598cce5b9f7bead44d51e9765422ecbc114418330209634a20814c0955644ca83ac18432279829838299145000400a6700f0a9706605155858a105165c6821002ebc10802560782106186488410032cc20001a66a8810625c2d5006af06c1800016cf86e20000e37ac72c041871c7c071d0ab0c3c743017ae0c1871e7ef0c181f82108203c2182580d210411437811441851847784112b248ef0d89704125e6ca544125fcc975022897015f3623945433e6f091d252442228e083feac57c3e9f1f5f8f9107093124844e15108cb55820cb83be1e222342cf0b42e7eb212a22043d26dcf38cf08ef090f092f09af098f868921a38fe799fe7b570c6f3bcef5b83873572f82e80e31f0e0f26741a6081173b40b0461b62190b66f011f3346a8c50b3001e70887e48a3c61b3e9e6d0905c230ce819847b178e1feada07c3e1e1aa02120cf89ce84de43c641f0cbf11ea0c762008ee779dff7b97b40e0e0b80750f0267f8ad69f9bae5660b028baa3d1f2f77dfd39f7d98792368737adf4feebf6d3a8edda5cae32d1716b6bd299942828331cce358afbdba6da34edd6f42f7e1cd6f47525e9b574eb334c0d07d9e8c6970a1fd1719731ee7141d7d2b7e56a562ed8897e356bd7d2d75697ffb63149f712cdf46934ffd45ffa19d73eff6d97b7d27b526d29eeb78529456569a263f925d574a4589b95026dfd369b44c7dcecf7493ed5cfe88e46f743d1b17cad9fc439476dd73e8dfe6c4bf1d64774aee27c68ddb5a49a8efaf1a63a579980f2dff6dbf0a6fbcc99f547ffded1e8b52399716de3283a262de56a9ea4ddb9e668bd96ee7b6292acd434cf1afd5b147d5c6d223f07f5ba7e26ef128ed2e57f1395128e7a1c46dae6596b9f7471d3be2746aafd13ceffc1c54da6a5629a158965dd1d8b874dd870f7809cb9e97a7b7d52bc29fd1237a13005d3665398a64c99e264cad0142153804cf9e1394c7f32f0a1302a447f32a01428030acb610a9401f5a1140644c17cf912931c8c117290c58f4f95c1a8b62deac98baf0c06d3175b2dfb8c492d2b7feb33ef7bca7027ce5e8b334ffd7947deb7e5c7bd6cbfde16473bb5145969a6596b325b5050d0eb7acbc9ec364f9c4c26cb057d142ca25c41f1a67f96b4527a4f4a0d308002a8a1608a7c09a354b9e3bb7b29ba7b29d9a6fa1b46c1e2ee3f40bb97f259dd70d3d5ca0d06236dfefbb6fc657d2bdaeabf5f85fc1cd49317375d6c757e2e545152e4df90b4bb7ff1d06805771f8f9268ad96a5fc8cb73e738e7eaef4a7bcd527ffbd16484bd5cf694c5e4c526d6f9bfe131393fe9bae56e8f8e3f3b72d82d574dfcff944a2e3c6f8893e3131d1ad7f572cb4525d6f95eef24f298d74eba7d1bfedfcdaca68f9a59dbab6dec86be9d37de4e6bf45651c6eb4a3c6fdb61953fd53f5d26ba9ae375ac3389bb8aaadac56dfa465593fef5e4af6b65b6f24cee69c2cd737495892bbfff01006e44e6174574af1a6bb975aade1c4012ec3597cca82aeadfb9e1817f4a6547e5cc6e1642b94c18510c5c3221e34ce2f02b55dbb332fa6657d93e24df5b634baef5bd2d66aa7a6e5ef9abd69af8bbb971e36190a8baa0c2d094109bdf5b6a9d66fee6ea5fa71b9ea13dba9cf4ebb3e6de7fa379d475a509322dc71145f8a420e6348795cbd34474f283a8fe49231c9b82488fc1293413f65779db2f94b99aea65965baca6ee4ae34d99319d77010ce864cc6b853d89798a46fd2ea8dac9fef49cbcfbfef5398596ffe7dad69da9a7e5bcdc972f56fb2fd5a4b917f2365fbc45f6272bfa6b2b5cf3999695aa97d7fea65b76d7532a9ec01c0ddab42a14c0aee1e0965aaaacca040c68cbb37a68cbb7782bb47668c092578644a70f748f0c6d8b48a0cb183851934eeee1d8800cb147fd99866452a2de692df342bd2b5369bb3ecfb534a2f55b7be5bb6ae26762a5fead6276d62ec84c34d17db8d999ebcd59a666c6b6bfeb6b72eb66b37e662a72e4692ca15f74fb5bc9fbff64fa5696f4836cbf43647fdae1b6a6b8b25ffc6a7187bb76cd356d9e5df1b63c93826ad73364abfddf7c44736abdf5671f6b3537ef32cff4642d9b8fcd36ebf2531d46ee7ac49e5a74c8ca4f55f1c2e6f7d322b6933deda5ad14fdeafd5b2ecf24f6152dba4ddb67fad79b3e096707942e94dd6bc2f46dae553d7cf513557de9aab3c0df74cfa7f6f8bc361a41bf951a625ef89917664cd24c67d92dde6c94415b55febd8be8fdb55a728ad9fb4e55dcad99bcef8b6cf580e57b7a6d5a3ddb6a6dd5ba05aee1e0b74f756eedee7ee9ebbb7668e14b83b0ae658b366cd9a356bd6ac51e3811d41d07490f3317a18a2e6cb1743fec5131e8a5c98809167c1030748d8a20a0f9c8a2b2e807326e70c6f22072a649600c37d882132f8788115ba0e4faab0010d32ee0250535ac00010b0e22c2469428a32f3e0279c004a04d504463e8213aa31608059c0b59a2b72222079c271162c6dc1ba800baffd60860d178dfa4c86a812c86069c13fe04009ba0000f9866f21a10d1548f0a83be5a4e87abcb8c2af2ce14060e072e15598406181230120e1484e85788c0039073750a0044172e44f9caa8457c4c086177571802a3868428d139da06404272035e0496c38a091a901f27184391e20440a667c032a5e90522543e93f1a4041e29e1eae0102e698a3042430c28d5c1eab2f4178c07ba86c81238384294ea704e141024202fc020fc439c2a8220a1fd2c2075cfe00250fc281344e24b1011138102a1ec482a478c07d3c008c27b528a15b30fb3e1d01c2f00a74c24c816abe701e5d5c4089129070f8ce0f3e2230fb32858b2a8469962815e021990330a143180df88e146e74745140e83a70a0330001c305ae630510358cbcc0798e1a45e028536081aa1c243b6f728348154e0e618e38be38c36f1ca0c119392d3e54ddf400f5883aaa729b33150880074b44f8eb0d165a0841115ff82ba705193d607155b94810a2f000244bd0b8a83ee019c00808d0b85c5ef870ca41cd94960e26151376e4d0b49c3cd933709929ac0d14e172058f31ce3a018513725ab8d0b0848c24d1c1ce195560184dc810430b4f54813588a1c38e1826d0805a2e0f4e4a48e26068010d60304064ca8a0238802047192e61ac6c6040991fb43853b5a26db9917a5a4357421828e9e18703f44b811a2c3ab88d876f0237a87066800c0f9f106c48e0cc000c345f095ec430c48c32fcf3a2230401d040c1ec6392c20f09b373f6f5f8f184c36ecd14cf04407851c314aaf23090e3816c2799e24d41b48117b10d26bc1ebc0f5c5314a8f2ce7c30479014270c79627e7cd08d71a5cafbd265ca122c47428f8a042cf000a80683b784840278f174c6cce3712ae10c1a95175ed8442a031c68000f9e636004cc8d4995ab910304264dc8aa7c0c2a940082891639ff620b0c0930a2829167214512d59305d4a9d800174e78651879134d883184a7a5ca87180241075128e0e43a3479000910350b9fe1234de004151f67e18887324cdcc08497711d893064051e7c042b535ea43ce1e33a056084b89d271c572400739041430e5e63c283924106239fedc0438d15de08fd83333c2a2a6aa2f02d67beac41abf0c29d44708df0050b16f02b5130a9c1880788bc8a94595e8304261ce9450501243471c63910cdc05de08a0e7fd2a584264c200081177d41c68925e220c389ced470e4869b1c3c096e07270b8ce1858f2600f1443411c2375880181d3cd57082ff6c808010c4f18305ae01154e5c51039a13dc4836c1cc65c108efe9c00d0fa080c790d3a4d66e882e0bbfe00b03dc2813a5880f81010423527c013d481844620fe8d1c0812c4981104769ba0f2d1ae04511111db78096002f661b08f10a76c4c143842058701e13e08085a33796f84e1020284981dd719105256bbc1a21f0d0040490b141d7c37700600425eca0a1e33a7230233785180db88e021c608610379e3c670d288268c30584e76c500230644fe538ba015356d834f11b2ca6b2448004a5df14b111b1c11983dba83087084a700002fe82c30711e05c89c25f622680a68808d6b88b0d170604504214eeda6df1441ab013b8eb75844d50ad89b776805541f24410de82f58862c464c7591bc8c0136b5838c1596592fc80033480b384b0c0608ae6c241319676590c2b1c1c404e052b601839a8a50d204168910107431a84a4b921f45505766e5d2c5184af08a0040514e078e02b9a15b80700b1c65742277c51f2a307ffd630d930841948f8370136b42461060eff8650b23519f1c23f3217541942c60dfe79e172850f4e49f8c7448b36c41cac7f3dbce814c1f1807b2610634a097e28710f034a413ca00136f7a6085341014258e25e0f443d7007d8c1bd332b40a14651927b62b8ac50822139ee7d31c104213ca8dca3e20606d248d9b9b7e407d7132ad8dce3d14ca0f161cabdb08b0a42ca18c33d9782c50c104bdc49f00309317a38701f230d921305d871ff029b2957f0e29ec5049608634607ee54b08145911cda706fe20164f0e8c47d0825286e7c2b771d685ef8a2729fa1c72b19e1e1ce02540c60b2acdccbf85072021408e13e4297d3070514e1be73e4b0dd10c51d37f40517ae22dc6b54907152821ddc674558808b5c18f70f4a108ad0e4343194c6b9a66fe6ee4e1e1e4912439f949f4f9bad5d4c525ae94fd51bd594d58618fe73f34aa0e1f6e4062ddc2b4107a327554adcbc12361005ec880b508fcc4f1a2713f821c22303001b8ce038c0cd2333471646432870c6ab72c1a9484a4f149e09517e349931208d77828f344cc8a489c22bc34345c4030ad47866ac0d4bd07031f25090438d1230aca01e00cea049d6fcf0f25688319184522b9e0b47c218e240c9140f06285e42217441e3d1b0c3692c41270bef865a55900eb8303c1ed8b00244c5088c3c22e60863030ee8f0c273228d131239a060f0ac20c11b4882e03cf022a0059a1f27ba2a0f8c149ce1e3010f67786e6c21e288ce89303eb10a2b1ad470459a6fc9076832388006a2f840a02167c4026fdcbe17901051c4043aac7c1170322bc2834e5901f142448f122546ab290578c1a305ca6c8506541a9aa372063cc930c2e0052726583d51682e50e28334ac36c4f048a09aa2777fe562abf543d9f2ac17e3ecd32aedd66cb35d2b75f34fe12af96bfad61c7571d3eec6a4aed5f76fe4b6529fdb45eda87076fa421f23ee9ee3ee373e3d7c42164280c14f664139c2dd3d0f244001267e80f1450ceefe79c93d1df1458f352570f78f081f8a70e231451645eeee0dc101047a7a9082e2e6ee1e07327cc902830a3a50e1ee1e0356642ad0810544b8b9fb0780223d0af0810cb4d8c2dd3d032841731d2a84f0e1eeab3646108213483880002370f70f8b120765b070848513b8fbaa8217bc50630036504101776fdd40c31b9e05602998e2ee1e196dc430668e017ca0c8dd411c662e02dcb6080386fb97f8067d89afbbbb77640677271281f727376871b331ce3a8f34a12ff115ba96e6ee42a8c119552fafaaca84aa9b2af7aa2affaa7c55e52a54390b2cb8dfb88dbb9f715fa1ca6daaaa4ca8aa62a1cabdaa6a85aa133c03203cf0bcd048933baedda52f6bb6e5b66c5db1d86def9208d6acd75251da99b95c4cd79b53d4932ca71f57ef266b0e87916030f24b4c4e2959724a91e5cf51559c14894d5a36ebc65b4655715415475a7d6dde542f7b5d6952942c39a1e477a2aa387ca3703d61f23b69184c8ac4e69194f273399bb786e174d541c9e2218f936ff273c624ddf86a4a6b96526de9dfa2e893992c63d9acfc9bb6d7d668fed2b4e791cea3de16674ddc6fbd73f79e7c6bec846b776997f1aee5ad4c66cd9fcb62565cc64de5ae3ae9f7829b16e0ee57dc1decc06d75531b8662d179dcd7e2dc9271282809f9608ce813a3f38b14287a579dfe6b32bdab39adab89742377cd699ba47535c92efb66b3afabc2dd3ff010031a4c7bd2928c49462541b8766dd09b95d4d766b5beedeaa4f36856b382e0e105398ce37864bf09b4eb671bddb8898e9a9ab59a27695692deb6d551dbb5e53de9e74abf0bccfca9376555df9a374ce7f1a6ebeda6eb8dc81bee3be39ba618ef9bae370a7b9d7fea33752f7f2abffea7ff4c85d4160783e9fb5278c3605a9fbb6ea8276f35ebd1c556e7e702e5ee4b56623c5eb9b4df34ab130e43fdc54d51ee4ea99090d0ef9ab7a69d5a88bc3b1d94cb5517b4ffbea5e1b008414c868476dbe69a2bffa6ebed9e30d8ce38973bb5ec7135d7de5633de1a067b9cddf7cce1101e9cee4a7fd7ada9bb2be94479380448e7b1bcbfa9fe89bb87e0a1102afcb7fe2bcbbfad0c87734d467e4e0b913f25b4847c1a49d6222325342743b4a2225a7d2323263f4454abfdd00f91b526444e6aff4542e4d6671612c283bbc7dc5d5f6c37f9a692c6b926779fc241fed24ee1ec54774d72f72858e8f772f7206b0441836ac8c756a39a624a58b264397a52aaa214d5b4b47454a569e9290a8a4629452165c182654bd393d21295a5a826a5a4251a85a4d4c151162c31a5a724a5a8d89312d3962625232a515b94909a94b66851828a5282226d7edcd9e4c424b6f4381b8514b5e57136aa7c11ac59f3dfaff226cd6acbc5766dde504e4c47417ab8fb17da9465292a86b484c5c92906e40cf72f146989ca12929213545094074a514d59908044e08af23779e69fb2dbe26cce012102481021901cdcfdc78782f94295a2625c969e9c1e67a37ed7242af74dd97d53f6bfdfcce1307997cc4ada8b6f252f6e622a6d16772f2a2d2e6d16dbb5f9cb9c653f5496fc5359f297f59fbec44cb61af534dcd964ab51eedee4dd9dc9ef1dedeaf095ba5b6c35ca76edb515f7d9dd85ca2f31f965ddd5dd898c6a7b8bbb8fb6baff342b17775f72b1dd56e39cbb2b49e2eee5b5523f427717bab8c9ddc7a0c761285f4d0940081b511039c8c5b54309a50deec2401537ac983272af0cbe19c0121470971b3c2c8d300a59e36b44e0ac1a58285aa2c600f7a810d70ea008ff704f96e860e7e3ac1a560d58a20113677d6044d20898a8fb8f0b090b1b487197e2428b2d6a80e15e03c8c0e044cb8cbb801c81450f736471d0660133c81630e5ae14b280018438d6784b0b4e8d92c51a7757fd36151ef817022b63970a38714781115560a6a4c1a24dba18038a35ee728205c90394299c65860f330fcd8f7b44f83ca9e056e12b262d7878a1fac1bf29381d1c805be32dddc203a6ec74be9240129a55d593af9c9ca6907440046fe13083064e90c08b7f5128b102ce41c45d28d0204415961ddcb546083954913373ff2288395ef8a183b7841ce940070f4c38d0b559b3be0856ef13ef2a160ec29dedaea66ae62e9ccb4107a378eee2c12b59a2e7f21d1ecb024f87f7520216f96a06f002d7f77ab1c095e7b98f6ff4cf3d2d20079ebb9eb07c47cb73cff3582ca20fe773cff3569f15cf73799fe7ad5a227cdee7b93e0b9e3cafe5b9fcf374c6d5d76af9dc500ffc3e10060ff4bcefc66be3f36c9ce581dfea3d2fcce779df6bcb27e4f3be16f87921f83caff5795a3c1f9eb7fa5e0d60ad5c3a3c0b3caf82d7cad3c1fbbcd6e779df4ae67de08d0dd197e34979792c23ab289eebfbc024ab9beff37e7634f1460093e05059397d0efa178407ae7c40d6e7ad9c45833786e79f8761e89e7ba0f7796a78af0f89e77dacef5b79de124fc87b7939dff77d2d24efc8f7819f37b41a7180bc34f07476827c03f0581f100ff4569e7f4e1fbdf140ff6ccbe9cc7361e53c0df07c0a16dfca6b79a0cbd3f156367cdf0bb6f28c56def781f4cbb1f1f1589ee71a02936030e47d37add7e7f23c98f77d1b58b53c9b0f5c4d793ddfe979ab20cf06e7f35e2eef9bf25df05df01159f9cabd1d5eebfb3c1d305c811f90effbbc9607d63c1b231e0eebf3589e8e2221df920bc7e6f368af1cd7e769f15e9eb7fa3cd0b3e211f99c7c433e9ccff5b1bc95f7791fcee702bd156be50543421f3a3a3d425146868bb212b8285be3e26c878bb31f2ece885c9c39b9389bb938d3b9380b808bb302b83863c2c5191a1767127071c60117676a5c9ca5c0c56a818bb5888b55898b758a8bf58a8bb58b8b9574b16a176b0a2ed6195cac44b8589970b1a2213244489070274890ef0499e23b41b804a9b9fbcb46c78d8e17d0452f392e7af9e1a297222e7a0172d10bcc452f4a2e7a6972d18b072e7aa9b9e885ca452f29b8e8e50517bd10c0452f45b8e8050acfd90152e53b4074be03a405df013283ef00e9c1778024e1ee3a383b3b7038e3210e34788803101ee280000f7170000e5e7888431a0f7108818739b88739ec789843110f7320f230072477b7e0a54347c7cd6b473cc377c414f8ce8e10dfd929e23b3b3fbeb353e43b3b567c67678befecd07c67e7face4e09beb3d382efece0e03b3b41f8cece12beb32385efec2cc077762010eee84143141ed2e0000f69c88087348ce1210d6b7848031c1ed6c0f2b0061e1ed650c4c31a843cace1098e0d8f9d1c23dfc9a9e23b394fbe93637d2727fb4e8e19dfc91980efe420b1c3fdc78d0e1e54f4e10a226a4972510b948b5a4c17b59871518b005cd4a2838b5a9a70518b025cd4420117b594e1a296375c7ceae1e213918b4f4a2e3e7171f1e9baf884828b4f33b8f834848b4f50b8f874858b4f5db8f8f4858b4f1e70f129042e42b180fca88085325c64210e175bd871b105212eb630e4ee366278d3bac9e942878b5dfc70b18b222e7641e46217555ceca28b8b5d8071b10b9b8b5d8871b18b115cece28c8b5d04c0c52e04e062173ab8d8c5102e76f1848b5d38c0dd71c257053b3b3a628e4e8886150fd180f2100dd24334b68768a0e0211a347888c60e1ea2818487683ce1211a5a788806181ea231020fd158e3611a403c4c63030fd328f2308d240fd370f2308d99876988f1300d321ea6c1828769d8e0611a41789846123737e08e1cf10411b87842095c3c01052e9671b95886878b658ab85826c8c532442e963172b10c928b659e5c2c535d2c835d2ca373b14c1917cbb4e0621902b858c687968f17a8f0f0050778f8c2161ebe90c6c317d670771d3d6e76585e7c8765f31d96f61d16007c872583efb076f01d16112f177a78e802110f5df0f1d085200f5d60e212b7b8e2e216d5c52d762e6e11838b5bd8e0e21641b8b845133a3737ad1c7104352e8e00878b633e17c7ec70718c0f17c7f0b83806c8c5314d5c1c93e4e2982617c740b938c68b8b63662e8e315d1c73bae3d8bc76a060e03b5090f80e1426be0325e63b503af01d285b7c070a18df81f2ee9ec3c383c4a07818b3e261ac8b87b1301ec600e061ac079b1cb101402e3680898b0d90e26203aab8d8002d2e36008cbbbf5eee363a44295070510a175c9462002e4a510017a510c245299670518a2a5c94620b77efe13f2a403dac00918715e8c0c30ad05e2b8400063a81266a8490a8d2dc3059c08f9a963339f8f0717777cd016483bbfb8e009039e3ee3e840511d2ac59f317ffd7644a5e15544c910029bc6789300ad77934cd271100841705029e78e2092928b2701b28c078d0dbbc83b874a1a3edce92b22c3d2939c994aac49eb24441c9a2b06c8951514a82c1727a0645df56a5ee792369f974dcd12815a48d9bcad7fdfda7ba6bd2efba93cadfb8a98673c58271d4a6fa7d3152598f5c8c15039c711f67abf9960bd25e020c50827b5036cd7a84136fb048276208aa4eff547070e215549af646864d04e167d0d6b5f6fba4d9bc77accab479e8ee3226d494f8dec0040c486552f91b731162d49a56690d676ca7eaed69f4379eda3a67fe6dfcd9d17cca5c6b146baae968bb16db2ea6e34dd75b8c8eb7df980bc6517a772b1314b08912403ce58b60931841120a4802091f7f7ce88f0fd57d936f20092cf507210f930091c000121170172a4205891328d535a764c9d2eb5a0ddb5e23f1b27da6478441614fd69c3ec201eef4c17ca1528228982ff4733bbce9085ccd1141c4d027f74d992c4a141a8587de376532234e30a274f73781a8b6541bf1b9eb46fd9a6a7be2923eade633661bffbea675679a95ea53dfbfaff18da4f9c45ee8a8f5b96baeb95c4582a4eafe888e9a8a036d8b10f1fa4244263384156f8c33caef04429c719caeba7de456a41b1d6d9646f1aeb67c1cce527de44d5a8dca475493d8bc5f969696718d6a4bafc566ad7c3deafcfb7135d71cdddd8ad35b6c43667dadaf7d7dca64667d0dd31ac774e44869beadd1bf6d5cc6356d561a366db47c6d29dd991796c3597a71d67d6eb7ebc5b7f699ea2bdd6133f7a5ae369dc7cf35f7390783e9f0b087325f62a4f16963a67f23a9e829e69f70ed2ee174d5c1606f9e2566c2e9dac5490b1e8c0c50001378a9c10ca7ae9fb7b7410188dc33093b5c2067964a36ab754f5599469df1262b95aa6449794f9a3f77a464c9eea5663f3f148802f9f8d4f006944867d6b2acf4568ac9f29bb45963b849f754308eca5fe62cb52cfbcca2df66bceb52fef23e958d9b1e67a374180bc6510b88000e2f57c2d96a22c51e57c93f0af34fbba5376d0d0349e58abb91f78fe898df29a9ee9a34669cb33b7fee5a264a55cdaa318e665cabb4fc769c5d3ba3aa4c33fd3a6ce6cfe54e6a566cbf466d75df27f3e7e8ae4ed726e56878d35dae96b4b5bf4fc3367f8eeeaa3992d534fff6b7a829c6f2cbdfd18d6f4977369374fcb2e234dd35d1ec74972ed6f59fee16a47f7a2a4e4e9fcf5d719f9d76d54ae9706c9a066848a26599818c7a8ae12242d10c4453ae8305dcbd04016cffa41040e841210313ee3a94e18cbb8fe6b87ba90de8ac5cfa2b33fab7dd4b5d1dae7a26bb78762373fd6bf5a92dad371d2d77eea57e4aef5e4a1fd15d5e53dbadb79bdda4c5e18bc937f3974bbfab25b1d631dcb4fb2b7af75770d391f085ecb69b6f32a7e285efbe08f83315aa2fef520070eeeec2085c9861bc27ce9d52189bf46f51b18d9bf25f5d968d9b5e6fb94b1b37edfbb7e2f23bf5f0705a00c2dd6b184f27ca9701c0177a9f4f28d3ac485a7f2ecb98cbe1b3f37d2e2fa80a095fc28211cf8a15b058817a73783f4c48e107ef042e0498c0a4822f881745dc9dc943326276e009cfa8f11d15ceb91d6766d079a491e0eebbbf62cbb12b4e001881cee3eea58478f67ddcc50e819cced92d02a0072e4fed1545e1a6b5091d200510dc69d50be241e122582d82d594fe94dd41543b482827a3da55a6fbfb39056688c0e9aa23c778688647ccc657f7170663b2b979f2b4f1cdb3c43819add63ee746f2a97eeabe8c6a07bd2dd71b12aedd258d9d6edb76c93867a5ac24954529eae97fe3a9d2c432b36a5bca64af710ee7dc3d339695b9eaea2edb1cd5cb76b2d28410689bd54964b25c95e92a339fbc95a495517e69a768eefec3dde570f72cee1dc0ddbdf2e9cebc5d4ba38672756b9cddbb97ca670e67ab49fee75afeda8bf09f6bdae284486d7125249570848434db0b220c770aa871b754ee4454b80709c15d47c8033d9c2f4410a3830d170fc7784182061437e93c925fe26bf3f642afc7aec434fd4487cda751d9b57a6b6c45ffb53b87fb6b83e89fd7ee8b49fa64b773b820bacbfb3ecd616cd28bddc930c3dd87689428b403236cd179cc7fef99938c433058fefd391725ca58335fbb7b30fc7801f40260d2ea3e71ee9e0b345a4b0884225819afcd591d9a703747a5b82a76402cb14b9231fdfc53621c257d28addc36eb38e04e3ca4327f9f544b5419bc08f65a9c2e8cddb6a6d54de1ee473cd4cd4033aee9a0cf3937743aa478380543fe6b71e114964c472ae114f8fbcce130199e599c30c0f699dbe76ddbf0c41285d2b1fc6c4f3164a5b5b367cf41cb3769f53ea53b8c4dc5487321ec5b19f43947a376583fb35208676148c9994217a1612029108549a114262b479ba5e9253da4fc325752db9202510e804c327320cc298cb66b6fba9a9a0295f7c435aaf3088389a180bb0f7928c6875dcde69fa278973785c9caf277ae1bca842abf0c736ae8d717e7f4677a6d7e27aaca14f526ad72b15daaca346afa5275eff6b5414f5e0ca4f7f91b4f51dbddbba0fc4e6599abee88eaa7af51f356fb7c5e5b6d957ea678c360b32ff4fe943e546f186cf685d69668d55495a96add5fa02cbabf663dc53ccd585baaef53d9d6274e464fbc3be98977b4b644476db505ba4b74d457874baa3759f359526d6fa5c553555345e5bf96e969d44772b8dffb75ae9a4f3513ba757db33e0e88c99b395ca5aa7f8b2a4d7cd39f6f514723544ffebd9fd64375bdb6f6e514ce395c7d5d6f495f947136a3d54ed1fd1a898e6fd2acae5f2be9d36cddfbc891fc37f235dd996625ed2d57b30defdc2e93b4ee23f7c926bf56ab3463bac9cfe19b5f9f41789f95de5cc5ed5e0af61b5f4a85755174bcf8a4b66bdfb4371d9de959141df5dd42c75d29ce34cb5bc35f31b9ab398a3f1f79dd6f7b8ad15074ac7457d376ada6f5efec5a3a9b7d9efd0001f9cc806274fcfcb7b2fed677898e3a6c6a9bab4c3aff14a6dbeeaae9cecce1acde96026d5d73f4c97e4dcbaf541fd9f5b681663f3ef7cd71465f9ffb48173a0e01dd2c74dcf5c949abb634f7a4b65d8bc39b3e8de24df5e53df1d3ec0bd5968299cdbee89d79b3bc2dd75d7eb6fa934cb392b4bc95a4e3c5b9f3fc530c05ca1848cf28ae7ede3a77d25be9aeff64263f3f8eb45be36babb6a4362013e34d6b65d097e6597b7de65fa23b3357a52eb68bb354fd2e9f9d4cf3a4f2e79f5a9276d569e37b2b92d67fb7ed72234f268ca3a06a56b25d9cbb986645d2911bdf74ae5e28dbb55a5b268ba39de5ebb069c6366e32cd4a6586048ed27d9502c0c26cc954107931ed6ec9556762ec443ba1f643e5306eca65d16fe2271363a7fcf72e99183b955fe6aafba7ff4ca5bcf82848489b18e7645756c4dd7375e3ab7154b9a4dfc64c249d5faa6e285db352b984a3cad7b5cf4eba66a55a96adef92be18a966a5bb74d3d584cab896e5499b9d70bf9732a6a243305f5c6b96f54b0a547ea61bd3fc5219ebb014894da120770ff2108c096688decf61301f086c80c001770aabbb96753f8e0251204f05af052077cf04ad71eeca721aea6302fb86dcdd8b8720d8b8eb208d735748ef70b833a944dafd95fdfa9f742e577550faa9fcb2fc1975cd5220f2b9d0f281cafad98c6ddca4f56fcc0587f7ad3727d94fe92a1ba01ce10708f88008779fe2432fb696eed8aec4347acf1a0dbab8c9abc20f2c70f29e3526e3505090c5e9aafbf1d9ed1cee65da8e5264dde3a62af5e36a9396959fe57118aac4b88a69c6ca9fb24ffe53e885ee6836363637e5d32ff46f5136363637509cb68c5a9b39a08c6bdb6aac6b19db9c8b518fc9582bd578974b5c6652a432d6ed6f2abfbc273e82f2a7989acb923f97abb9cb8ce27ddb676c46cb5bebeb2850c654e858e9eb59fe5d8bcd3e9f24599fa65fe3dba6ba52a0197eda54df34fb5d7176eba75a7fb6e19cab74bf8de24d4d5b6bb4a4ed72d5fac98d2fcdbfab49ffb6819e3c4b9c73b6ea28ad5fdad22c4b8bdb9937db896ba7d5f47537b2daaebdd7da3e46475aded7146f2a85a93eb22dbd91f6a6a3f9776df757ac94bf2b69cddc9984e361d400a27451d89dc27c288551dbb54f140c857d110a1acb2fff2f7d1add55dcd66798b2d2181defeea5e8678cd3b66b6fae5dbec44d4bb67a0475a604a81c42dda066545bdd5f0a446d9546f1be96525a3e982f3487180aa0868271f75a70f758f054f0ce3ccde1ee545b2cda568fe8135ba595b6746f044f51dc7d2c5f0c1d6d954677556756baf589bb3f656d5b5baaff12d37490b6559ab6260cb6b53565da5669b2500b07dcb4b5b4f8a6a395da2e696dd8a4ee4ee5a11610f49f5adb1bf99beefababe99a34fee3fedf33965f5fdda97b4326182d5d21264ccb852a730ac1494131a77ff1a7d6262ca389c530e7ed3678e3e313189c185061e308ef93535eb7971497514e3306ed76ca92dd04c8ad109857c2e359cb1a69feb0e67b159ebef9a1f775e253a6e6c6afa38bb6979ad548c8e7a679cfbbc7b29aacb8cc9cf949626bee593ee72c5d99cb35d6bd66a1836654f310f7b5aa555006480bb931e4a796ac279c8e484dbd8d8dc8c363636373f423a484893b80cbab6e284b01d474de2524808a6737bdf7cc0e82f1487b73e916c6c6c6e64b251976f9a1569772b13c6519576ea5dce2c31ce44da412643132770318be2b5a23796c0506584a2260f9dc8b0883263de00983061019fa6ffbc946f9af68aee9bcadfb5cc6df9122369fd2479425dc9c07dec000519e7acb4e4610740b8fbb8a3d1a7d15b499a3f47f7ad36d729fa1723996645723262020535da4ab754c551bc69fe32637aed896995eaf095da965eaceb3fd191fc9cae26f925ddedfc399c73b467575cc6548c9868dd435633f7595b6ac4040a8a96bfebb559fde5a88fecaf426949e34be1a96be9ad24d575d712df74b5b275b6b5adbf34b10fcdfd4be128131d7127ce5255dca8b5ae340ad31ad02b7ca4175bfab7d9e7dbc6b5bb14dbf8d29aa53fa50f7d6d562a749c295972cea8b67436a3bbde36c59a96678ea263f9f773b97aa3e7d6b586e96c369b7dc61b8a8e5b63933e0d47f579db369bcd9b3e8d9aa6d5515bb31b5f8aa9feee8bc99abd7fdb74f6faa4afa3354b5f9f957c1a9dfd507d79978060b0289f19fddc6ed37adbfaf36f6bdec8fdb51ac6694b6dd74a9135efb77953fdb6fb536f5a930cda379b35a9d661d3ac54db5b141df17d129bf46d95b45bdb6b71f9f797a6190b578765b8fa5bb65fcb642fc3e5a9c5ec828725b744312110438628a614f3843940613f35a5bf335e1f10718ebb2ba1484a13e72a798af95246fe9490f65ef8bce4bc38e2fe49e02ab96f8b532a31d2d23be17e5b3a56303318ac66fe43d1516b4bf749a3d79eb65cb1e05dee44a8e498b3b129af54cd6fbb18cbb63636f8a62b6f2d1f77dad0efa269362a574ab1aeafabcddbb4b5963bf5b6f6da6de96c47abd1ad31d216b6eadd54fddb94121dcb92ea4a6b56a73f8916d1389395de1387c364d54d615aa51b5f3a96bb343fef4c2b423569f9fbcdba2b2d2fd171091de9a8848e48744c4261356c4d13933f4f5e7c295069653e375d6f96becd9b522448487bf1c617e39020a1556fa01d8dfe6d9bf55a9bb555b35631cd8a83aad5cea37de66e3bf749a65962a42f31924e2aa3f69bf6d625632afbccdd25a827d2e67094d2b5f586849b4cf3a4929431d25d2aa38ca84431d94eda27956fef273d796bdef7cf7aa352da1667b1ecfc53ff7497c6a0cf3224d76314121a4537a0982ee1ad31be91b4ac345a4fa09d69c713ef6805dae7111d3d8831c1604d929cf5f3b64d929c545b1caed666651d957c316eaba9d051ef74c6f93fd0549569e3a6376975e7f26fe4894dd36ea81d15cee52795d7e20e76a619d37d13aedc0ff5e6f9a4fba62e11e8c2c4c58d25d3ac485495699c5155a6d99b38e7cf69aaca649ab111cbd3165aa919baba6bb6d18bafc6fd97678ddedf516aa3f8a6fbdc6ee39deddff47d1cddddfae689f74f05fdc5d4acb67e50fedcb9f357f26be69969d2121285a2e2b4b4dfcbc64d3bd37cb25d5b2ad55d7395ab63e56f6db16cdca471bf97ccbcc5767f5b4dfb2ee58dedaa99d336a9c4b84a979bae56dea455bc9fcbc64db17a64bbf6fe94adb27153d393ce4f83884e1470c2c2c90ad1e9004e41884e40388d426140b4e695e194e41fbeaec99f326d4def6b6b5632eb59da29bba54cfa7878728011b35bf29e98fee2268c9f9c3c3756529e54053c046207aba52786942859a2ab4fe512d59757ebc72200bcdf5e5bde4ad3b67cf3dcf771b7caaec56176a65965bfebd49bffa4ddf8caf67d9ceca7acedb3dd52a4952acb2ad32736cfdc99497dcfb26233777e8949d3aca4aee5ef1b79e2299cade50b097959f0c690103b7811820086b20c93c8c8156478f192c6dd573a579c33775f5d40e5f396086854a102549859d02447142d2531a85a491c54dd88415e8460c498c921012bb45c85850e0cee5e1099fb1702980f0423d18a189d472128f2a764e5c3609f3129cb69a95c10ee06d5f29458850cf7efcb0b5e9a0fbc78f075f9b8b8fbce7a678854ae70779dc71c2e7285ea8b4d133b5524291263d9d6cc4195b996b76ea8df780a069371d992a4757512937444241744a4a224aaca34eeee9333faaff3efccfc5398de4a81b0366374acd5b08931d2182445d61cb575a5328e3b73df273fd37aa3d5762d05caddaac36128bc2feec4587e632ea369ebbe4f75b6625e5ff3ac690d54c33be3dbd36af8b18c6535ebcfaee17d1fb7c97a95c68a4dbcabfaa7ccd7b8bfed29bccdbdad492b7ddad20446f3d4a6b9edcfb63e7a8ae21cce4e4e331a4433a64f6633255dbb945b572c331a4461fb7158b775f97febb67fd3edcc52b7b3d570c6a4d604a81a233f7931b9b48fdc99121d69f7a4f71655ab54a540f43fd7be56f5eea528d0d7ea54141def163afef8e830962326282bb8a97cbc9f0beefc8b9b366ec24ddf943f97db62bb387779d24ea5d04b551a853d65797a52a2484b55b260d1e57d9ba938e1b5c0840c77a7957a669830e1f92032a1721f77884ca08ca5a5e9884473e0fcb9dc8fb8484445d21a221094b84844dd475bcee0640cdee2c5c50d606711387c8002359ec64712fb16a21206f82c6a466b67eee24e5d6ad3d6fd49356f2a25b39a6f052a3feed41723e576aff5934acf830ae28fa954774dc2b51b8583192949a2c3d72693ddf6cbf096555d92ffb6aaf5e364b693f6a59192244a5eaa9a34a94a922fd584891299927ad3b29baeb7276f95edd732bc652ffb8bb7a6e1f0d6e1f2b36cd7de94e96afbafc9f096ed9bcdbeae6299b632d9d5e19bae3799b6d9acaff799bbf677ddd56ed3ac3625667d9dc4acab28442053e7f149f9629ee81367b2e2e80cccd47dd99722b39b9cfe9d619f7341bb52b6db7667cc24139150e12fdb6d9b71988a93c9b6ae35b3de8cf74fe1cedfd5f6e697f7d45b5b538aac264de6791f8ffbb883e0e29137968c4a928c4341578733eda6ebcd027b3582149aaba44cf7d71b722f26664085bb5746ce4a337a8f84a399eb538ca340206ac085fb78b1d5a7495ebb6ded4247ba2bdd57d4c0080d78a074a741d5b559adc1cc5dd4c0c8dd450d76dc8dc62441e44f9966c569318359064614c8dd5b2e66e043cc20c7c81cee0e44a94a0ba39534e3702e178d84e114a6716f7ef95f9ad8caae8fcb8fcb1fa5fb262e195f9bee9e99aa32514d475cfea8f14b4caf05c2f94530cd9a7ba99adba273a76cc4fbc7a7bcf8c876edc65c46fd1b4f9176df6ab3365a77b4ba77ddd5f2e223a47147ea237f73a73e127480714675a54a546250b9f33449aaa740e5c5e443d1b1e24ead2bdd99db92f76f2533a6650efa916dfd3459c5b92d74fcd9da874add9a736790a634466177898ee5ffc65c70d3ee5626fd1b73d1612cd509476dc9475b9f4cb92d4f4875d7a4df98cbb54fe2902cc4214cb88f60be50994c1c8254fe99b740fd99a5b4d5a9441282852b45e97ded587e06434eee9b95866daef7494da1e89804a6ab619db18e3ead029d5a5bd95012ba35c6d1bd8792e0bda3d53069f7e36edbe659fe9959f5adb3fb393ca3b7da5d7310de417a2c2d3d62620c4ad9f2a76cfeb2b4d45a4b8fc44cb3e69f52a29d9afe8d02d5dc89b38ddec8aa44c7dc897395af3d0e9354dbfa64cdbafc396cea2fcf7b8e26c63d8fab3629dd436bf8cc659c4d8c73380cde26363176c251da76b1f9473bd33ca9ec6e0c1f4969fbb471d3c64db66b77386ffd49394bdeb8e97e2ecbc64d5a5355a6b7dd7a2bd24f552b1b37e9c7494a493b53eb37f33b55bb7375422abf2c632355652233fe146e5756243a8e74e3a6b734729ce9f09dcd92d00da89e0599f54baa93222b8db630eaba504d9d5edf6cd60ac651e597a749be49de2d4f92a73e731aa8fcfc3977d7f1c8f880eb74eeaee4ee11f1395ac3f4f5e7b28cd9ae2d317995caa5fcef4413738c728af889ffe29c9576db9af5d4a2c3587ed7a412e32a5787ef953769758b8e5155266d2bcf0ea82ad3c576d7a76d71164b69bea50283ed70ed2e5dc0bde8b1637f3ee428815781cf480aa23802538457c3cd8a1e2b7820031e2b42308128f643957fe57629bffc9c549a59ca2f6d75aa59497c69e1ee321955c56da1aa4cf9a576b72ad1f1a7ea0dcc175a67b66b67d91ac95ffba4fb53dbde13d37d62a09d59b37488e20db4a3d15d6ff868fc9a95f23be951876f9ace76eeccb42ab35d4b9ee5e719fd4a9f6649a819f95cca9ffd6c9631ae6adbadfdbe35d7d336bbeddc97b2202a85cec61f97aaf9e54ccbca1ff3dbcac70235a34033aa2fcdba7194ded7c2664034887e99594c858e3b7aed98f1e7988c3f3324549569e6437f664832a632f3a1fabeeeefd03834164d69bbefefcc7a2bb5edda7246e2fbd987eadca969add5309899ee2f0c96df566f6522ef4ed36b75a59d9896b6e66ee489d3959ae6ebea4d3fd151df9ad374ac396ffd379bbdd5fc407a06863e91d9ae7dd9971985697d32d18262f59e3f3e54e34e325bb2ccb8ee68b4ea6ebaceccf2d65b26f363a6a1a1da121d1f97bb7de4d6ac54f3436121fba3675787674d3e74d4d966697496df29e3a81998d9ec4b352dce6a3ae6af2d95745baa976efabef92595324f1ad561d3dcd12ad53847a544c719989a95806810ad59e9cb8ce6ff817dcd4f6756a29c9492645e9eb244412d59998d60661a067bb2d6f2ef2ff9e9583e59f3e7ba2d05ca785b0da5c337a09a9f966fd6fc3b303318cc342bd2eccbce2c7f77ebcedcba62a1637e5cc6545b186c46afc59f31131d731547b59dfdf88c3f3e7af75230d8cc9a399ceeb30dfa4c77401bdf5aed4b4ccb16e86737dbbd946c36f536efcfda666b3fd75c5a3033aafab76dd26ad0c537577137bd3381b22df76b8bc3e4c89cfb795abdb7ea7cae74a16399abed33c5b56b7be84ff9a70fbe14a8b65471b61587c3e4d32ea6e99e1e5abe6956bdff6efda4cd5b7fa6f9c4359d0f35eb8d3e79f3e77ea7ebd3cf47767fdb9a92cf65898e527467d22c494bbdbb313ae2ecc524d5fbd69c1d6fba6ea88d9b8eb2ad4e171fe9cf98093749ddf386244562f36ec14d547054cd4ab52ce58d416ddc74dbb94fdae95b7350b66b97be1e95f5c834ed0d496ba41da2c13ae38d6b29d57f061a7a4655d6569f0175869433a09861821faa2d50f93ef43735a30a3372a035bf19db8c29666c60061133723405d259e3dca5b4fc0d822940d082ebf0a59a4661b319bd5689cb1254931215a5a4b17e2e57631778088271acf9a16a7ed96b58195a5051861265cc8037fda1d7d26b6906e60f0fcb3075191c543347a3902187b364cc408600dc69143214400d05f3e5068b6fc903bed50616a020021068a08a3044f02b7a946002d896d21381980fc75c838b18b450830b2118101ae2bd207073772c4b3bb2da1e8bf6606e029dc7a0a08b4d0fc5f02f314e3ffed24e6d2757e3815b7805b4d8c0e3e113f0882ef00037c3cbc019303ace4330a090dd6ae66460fc70778f7a4b55996a98aa32d1c7d9a8f167b773b8af9bce481a4493cc7ce88c9cd16dbfd27476cae66d5a5aa5e1a7291dc58eea789652a4cd34ac292e631ae776e6d6d5047a9cdd77fc793d9b51bc4fbcf301ba38bf083b6d6f7f6aa1e3c5f4d637cb5b6bdef7b7a53a6c858eb9936e7c81a4347dda8d7cf25a283afee84cc378d3abebf8a3f5a1e553d91ad5554777399cf73531ee326b4a12d434a33b13a87c2dbb78f69a6afb43e5890bad741fb9b8f431cd1cdde55a5e4cbea65abf794ffaa479e6708e3e4ddbfb9f3f476b96ce1e87a16654ffec72cd3887fb283aeeaa14b6fdbeb6cea066411e64e91245c7d9d08ce27dab69d68bf5dfc8faf966a1e3cc4a549526252cb2a82625a62a4a50b2a62c4b51485b9ea2a09ab220c99e94929ab6e44e5ac3d706e52fefd9656b5babe1a02f6bdeb4d297aab88b2d6d22eb7daaad3573b94a475dbea9c3572ae85a4c06557a234f4df56b9ccb676e97abce3f55566d2b694dda99a3fa27e862fadabced97b2a60fd5f6b3aed476b1997fea75f84681b48949bc71ced16bbdc49870f937dd19dfb43eb125ad5971b492f74fb376aded5a9386ab369a712d2a4b138d82dac274920f458bd01109926cda7d4f3a54feced479d7abcbf2c5f01d61e9cc34add48c664c672f55f7ec46e29cd24780717fadea9bae99a4444d4e5ab3f4c95f4cd4e4bc9138f72ba92caa9800cf6c637363535fa5a7d5239d475aa5790165bc56e8d20c6e0850e1ae67d44966bb7647d6d296b792b227ef89efe729d997b25bdf24711859696526c639d9ef3771969517d3eac5b4f24f99aee6f2b6adeeff7f5773b2fd78bfb63299aef965f8ea376935ff35cfa96b65fbb5ace697e9fecacacff96b5fcaca8b699f6fba6e2dcbd5cf52a495d2329969da1b89c3a4ec5a9c6c67e672350246221024022fba7ba9f2a730131721e0228b17c1227151e5eeb66bafc597c2fe9e19c685c94554adbe49f16ee2a2888b0cdc9dee5e6a579aeb9b483a8f19870bb798dac26e11e5f4a6eb4db6efe79aa354e31c85edd7b98a93d9aebd67ce651caeac32fd26794fd97e9dc5e97ea58d33677c60c226c783591a16a248410a6e3e8454d0c3bae361a51ed6261e56260f2b081e56ec612de36125808795080feb023cac1c0022a651e4621a486980e0621a615c4c63bbfb4db86347f8c4ccc327c278f804091e3e0183874f14c0c3278cf0f0890478f8c21a1e3e8185874f7ce1e1136578f884093c3c802706c1710de1808b43d47071c81a1789ec7091088f8b4490b84824c945224f2e1209c14522d8452227b848c4051789e4e02211215c249200178960e1229108ecd0218e61c6c5316470718c1c5c1c430817d30071710c03b83846152e8e81c5cb25fee02efef072f1071e2efed0e3e20f485cfca1c8c51facb8f8c316177fa8fd70baf80309eeae73a4021dae1c20e2ceccc51d9c8b3b542eee14c0c51d225cdca9c2c51d0ab8b8f301177748e0e20e1c2ef270177954e0220f1e17798c2ef280b9c8e38a8b3c40709107ce451e23b8c86306177900e1220f285ce491051031bce262287331a472771d56887cc7ca91ef5861f21d2b5c7cc74ae93b56c4b8bb8ef08b91875fa878f8258bbbf31095d81e2a01000f95b8c143258cf05009273c54e20a0f959880874a6cc04325d2f0500939dcfd95138a3aca7051c7085cdce1b9b843c7c51d405cdca1818b3b9ab8bb053f441cb68b38ace0220e3bb88803142ee2e0007767f9d849dabe93b4f39d24169272707642f0c07742a8be1302ce7742d0f94e0825f84e082af84e0835f84e083cf84e0840f84e0848f84e0850dcd8bc5ca22836b9808b4dca70b189085c6c22878b45372e16edb85854c4c5a220178b602e165d71b10804178b6e2e16e55c2c1ac1c5a2165c2c22808b453fb858a4848b450a70b188022e167dc0c522365c34f27604a8c4a804f06da5999a744c213333321009000000931200304024180bc70312c16c9e86ab0314800372a67290521d8aa32849415219c20c0100000000000020981a02a5295a29c7c8d19282f33e01009b2b0265b1c6599152c7f19b177053c43555c0be14b1bf9c5fad8f1a6dea9b6020874aa802915f6811ad2cc131fe698eb2978a280d50a1ef92c4c70a46efe9525d1ac4e81034e235d5337003df3edf50b9ecdfb7fc52a17fd7f4086af45247e6af2f529cfe8e3149cd0db26bd903b6e488f4226b19e01250a3e9a2110a17132954b2729741f1642cb47251420a7add4522169a8e46a998080f4d3c70dbf53a0f36dd9ed1c8f0df14b9ebe397d2791b889d13924dd5ccfa3023e766edd6e5f676fa397900b4def30b070fc2477525eed178d3873c083c285cff25a8309d66ea8a70a0cda27360f908165e7a25aea728e39205ebc3182a9f66d740cdc774053a8fc169e04bd9730ba7ae045646414bb46fa4aab1c482844d99f544e36a4259f867339763fe05898bb9a20a5658329e25a8d15c75f9d01add8f8653879714a8aad6362c8b67c86edf38147d9b817573d83271a1192d830a836cba0e6fdba0230b64a236790111a66f81a5c10025160bf3b1c71baafe84642c27f24d5aa5b18e4a0d42349f848a78a8c5202d01ccbbc8193300d990b5dc5ef3a30dc7d2f8c94a8fb15806e435a5310fe768b2f226898bc334e1510b7a2d8832e01c3ad6a8577600183ffc3dc1aea0c6f80a6ea843ac6c2739af045a022eb631c4714cee200558fa4f71291f4ddc0f7c327e2b8899b4e1cdb3329b898df6cd3ab645eba7a92d95ec51ede37df57e99b801ed5c31978614f484bdf085cf2f9379c6af1120197c6cb5b66bd50fa4cc28e8bc48e48d19411a1545469298215a4902f6e8280b71a16f6ca743e3034371caff99d6ba142902ea7ea1086f52c049b0fc08ee310b25951c59f3b14f485ccc16fa5ff90fd808deeb3a1a08e59eac455db4ab051a1e400c887887a0bfb39a36a4e01da402a3c3a68f6a2efb681b4cecb44ec43a9ebffccc0595e83e47958328886dd0ee15aacd5d04cdba3deaab3a039a5dc52f0f519ce93515b2dc2513e911cc68f85a50fbc8fbb01215320a1f629f5a82146128b840010e72d8235bf803c66df8c2769a9b6728b807c3c830ccbce0954bc0b7a6c81a5479db1e6b6a1c1ab2054f8ddc9786ef7c608c6aac64feca2dc8299441eac3ec4006dee5b87a887605520c18f44abb9208536d5ff3a004775f8087e46a35398e9c9bd99239b8dd985493b305d7bc1dfc36a84680bb83712ea93ac6e77dc19b02843dc5658d2a48c3656cdee3e633a8652e69e51d218c104c913ba5229253f3b5f86fe348a62ac219d79ee8eb07221073e3671eb8a0e38b5b1be86b644f52ea8aede9db96da275c18687ce2625a8eaca81e37b2c1ea06d007a0eb32feb6e9ee7fa8ca87d8a12286ba76c42793dcb7842e72b0161d36b22fad1f79c893a5cfee9a3e5bdf39de13c96632e5a4dfa92d310fe400b42884e1bf8bc7d66877da31a00477e4e039009220603780f1dde98ee7f890819d0022e88a8ae4203cc01a21c1e19541288e058e01e7a016e78c7dd60600bce0e888096c3a436ff7ebe513b3f3a7cb90bd6b244751329e56b5e45f5930781b959a41239a2796118cb91708a260dafb8158d5bc416e3d8ad1a2e8eb9a4695851d5583db437d32eea0098e4efff1c8538d7b75b7b179ec8909901f5c4bd627463240404c14ac09ebb49dfa20f840094e90038886fcde1a7fcc3dc8ba721a727db6f1d62f32803a81673d13991381179cc736790a9155f57f35e2266486d9f7b1bca38de118a5c8eb9098f3c9de038e6af2da766307d48efae1b6e13f86d6ab2585d86dc3276cc781c28cf08a2ec7501620d10b0e2bd1bda1f7c9ec93531806e46d096a4521a3ef7fb00b084a6549150e5f2463db5db0e6da1a1c2d439b35f1c7b7062be2c5a844adeace61be787c05ebca890a291224cf00fbb807edcb6cb25f99ee38d82d40af621a3611f1685c7b2af18bb111591b4158ef6f0f334cb1e205439a8a88f00483b1421695df54f7fe4cb310dbc651faf56b3c6089113f87bc71e8011b0eb24cfeaa13e71c87319ac45469861e8ef0c66dd44eba1cdf368ef2a4a7fb53fee63260d5f839034786a9388b3d635b10174966d94df2d14686954b5e877c1bc53e2f96db90f8712979933cd632179ffc90b229d0bea9c5472297da974a00976f8de68b1da90c550fa7f98ae401e0ad26dfc0612ffaa83cd97de3432c98f8e8840dacedcd49d146b2de82944d8bb8ba11a581bc28573335f946148b0fd5aa7bb9162d183911dadefa20fdbb894b5578203e7524ca6d1869e0296931f0cce558f1431c54de2ce131f31149e4445db510120612a2a2ff227d3811547ca8f1aa21936d55b39b3ce73b42347802869f2287c36e9bd5e58c0b13e5473b2f08361a2e82357501170d42d7b3ad97a92ccde36342c70b4b442b8d351931650afa7340eb4088a34907604de457bc0bd4e77e585d152591cf9b366dc3dd46ba6b64777e2fdfc3e35e71e1b7cecf0730156c9c5c9ab70412c47c1be40be0da9e5dd53f4e357538d3d62570ada641da09a5f071f27c4aa9ee8f295d61b1aa2025a69a62468270ceb18b6def77924bdd6e97ee68d29cddea60a01cf9f529e77fa3498499ccc9dc7059a0bfa99d54fbb93fd9316266891df4f2429432624cb5530787f41792c433c1f2100fa8165b49d4ae8fe60fde9ed641140538a16078310cd2bfc96f5447273741d92b0eba157fccc94a8c22483acc34d222f5df2324e2dea7fb0367e354e8d76565969d2cb84dc91aee3a58d28ecdba617dddd6fe34f64deb4455192ec92b02fc8d757f90caed2538567840a37ab042fd0f91d221e0c22ee41025aa123f5a0a6703855edf03e4ec821f2a06ad04872b03046f0edc776d0035040448dcc7f29277cbb9ad186731b0ef6556859b9da7b5ae7651af672ec568a241597883a16f9a553d031a5426d87534edd71fc0d8c1de4be2c5630cb4781a1f40c770a345bc2e9d3cd23cc154028cf0f34efb0e84beebae9ff904d60ef88098bb49138caf8d30e15e2e8cbf035939a9ca1c1607bb793ad8efb3221f86c5a0e80f85e9ce1b5b36f69acfcb5890cee7b7043de4cd954f378b5c218a1a5cbd28585f7f7b527d303f92c92a5e2b7ca3496ff9cd2fa7fe91b3db65acdcc4df634e483fd35507818e0848bf0c69dde68d580e176f59aa23de20f1021f2eede580327e2963e955e0b57ee1b1bdf42cd74bba8149927cc17d43a69e12d0ae8ae63b550780397835ddaa317434d1eb604918909ad781231636d1140c1b3cfa67530fe761ef9b2a98ea0cde1c51f1769845824feccf0f9fd54f12871cfd878f0780fbf1d60f4ef606df7e0d4e16a36d6439c62a20a8a5cd7014a828e69af6a1800089a0671460b23438acd3796f99864be1b682eb30f5d50e90922a99a35049934a92238c8dce91cff51de3dfd934b783d8ccd7be13bd319551841047369a7d41671cedac2ff1c6da4b0f8922d4a394d442de5fa35c64cc6f8fcd322245a92cfe6a7b5b7fb10e81ef4e587994876fc98b3a2fb7c02863ec020dcd82ecde9650599cf0ff767e152b40d0b1cfd91b5007911aa8834467ab8048eae88771918c100245fb8d2c3a0e110169673a9b96c3fde2ffb930d2019db13224023e8a27f168e14d59166c8183710ead7229bc7a3e6b3f780111488e81897b0aae6c6116883ffc1192c84e6890a92148352b2c5d35466abe150e5bae7c34ccf91726ca13cb2e3f1e1c13af06f3aa7260736b08ef9fec83988549592c14a872ec98e69e05c11ef3e9564a6d90516ad899bd304b88d8b75a480ccd2e1c7a1cfaddc05a3f5ae3427d6079b01ffe349e67495e128abcf630ba66324532bacd578adb9cd99086dc15994f4d1f79a4bd85eb9359e6b0df118198f55d99d841e54f7fca270b7f48e927acf9a7daf36a5cac2ea0974045d4811f97e21a61c4d30177074876aad26316a95eaa476beaad9678bb64a326752da93c346c878c071edbd15f39636dc7da9621a8a6ca71973297c94afa2e5580bb27f5a3e5794f6be7c5dfa2a1f71477a0c5b8da0461d4183de6800c3724a4f023fbdfd1c70b9b7acee58d28cc2ec9c81996e13ad4e09c771ed94503fbe24b97f30215c7f1cc25315cc01b6d1e7bb2b28b8c6994561c4ba5fa605b35f358a0518b14159140c06934cfe69cbf0b86730630812fa559818e81608a55a02af318528ae8fe3d5c405708c9329bd78c36b00e3a2b45b31c4982513dedd629875a026822fc2e54697ac53b636ddb016519463e1924ba9640b02c1e42eace837c8b3892a7c0200d97d54de3ea9cf1c38d6e8b5695142abdafca574dce53086fdaaeb4bbf67690fe105df6319a1aa8d3acacf51c58a8b279faa6e7688613f822b87717733da453eb9ca6b52861d13699bc35d3a4de762515c1f0098218d6b8c82793f899343cde6823fa06f12a2bafe42185f8897a7b8e0f944cd0e510f3486504eb821cebbc22068640a3160d34c41f0976cbb3e710bf957012a322cf32e4e8ce574d1e4a2a404484d2f3ab355d144a703781422113c92238adfef0f48c356e9017455a77464921212c1cf496a528c66333d3c7804435da3d4b6fd8dc197fdb3b2f43908700d5f619ed33ae395a9925361f82a7df1bd1e8b154139c61cd3e8651d57b2cc48ea71a1411f29084c65ca89fff7661d0d2ad61a6de140e82448ca7997c066fec0eaa7b58387d7108a8c1bfafbf54e8bd1299e2608764891abf95f6eb6f7d5c31bff1a49c216b7ce8aa00773547cb592c57ed96ef8320ea846810ca8a5ef45e64ce028663af4fb2586f22cd354a86750cd9bee1b345ecb213e36ca183b3b538b928a54cddf5484742e46e920ccc42438580f71cb8cc9410e884c1e95191796b0ff55f07024941630d6ed2450a90c5cb8d3dbfc41b8e38126148635e3460901c2e9d2e3dcf22f3029c1b01a6fa8305524dbc8b9f52c3a87ceaeb3ed09a74221ecbd19fcc6e4c21d92855e936a902aa6a724cc817c08e5668ac0c7c10c2bc4278d8e57ab0c28094ba3ac20bb5692f4b7fb35c30aad25a952ebac0fd084cdc29a499d1b74dc2472f23089ea343465a12094566a9ae347fd5f38a78b31b62ad89ead323a884983d1bcd97328fc3d08081662bcbf0c021e7594624140058de19c854f9fc248aa069123e25218884fa4905dc1018f523c8e05c9ccc241f143a17b214d4a7099d7cd4b381c603b9cc94895d58970422c182626dc44197544388cd6b92a9b0612f683084a53b114c91094b2e5dedb992f2bf4de0005bf6deb0f0bdc143b9757f720778c692b474cb985d1022d2d5e7c9b639911bec47134fb36a3160449ff29823dec41d34c035476cc4406363d480de3e12220242b653421a15a5ed521f3d89e95da810e47e87a356a9c0837f19d72c092b2912b33a7bbfef0c6064828675706d9994ab3ae7dc235d9cafb852356300e951c8b662b1719382a78dd900856fb9d6b6a59557f38f7516ad15465e469e3ebb1707c97bfb060e48e71b39e2f5173985b27242d4c12a00d755a50f7336bd665bc125e8a756faede236086949c3d5ec911ea6a0b6dfd16e4fb2e4050686c38764a35275a0506f65a920254c173243675610d5b5e36404ec005e93e43a3482972e500fc9ffe124a0133690f08182e830069ca1310c66bf2dd407e8160034ec5098caab0c84a1297dd45d59d388093e9710b6fef01ec03556c745ebb103c72a805f058bad4de22772743a34a6d4c4d3b8a1116c7d4c06229ff4a5fb9d7e28dae5b4915c2268c0b9e8632a5bbe78913f569325ad9b329b6410a3cac52b8927548bdc64d00652796ede21570f3f2fc5c0cee8c0a2c2a3f90b051a11d7812b1d30b1bf8d544e381978b903f008cdbf3a0d13008bd1d80b0bc40cca00ee27d2fdf56a677b1bb9e757483e09a2df3de737c18286dd7813efba4d9525309b54d313ca9fc01779acf153291250225212a284eb69e5b521a11fa2ac38458338725ba40ba2dd9d7b7ecaf0efedeed42c03007458ca80f6185db70bf1d59b0faf906fd07a821fdddd4189a1cd360cf76fe4d8a46823d16726e2715b68c6c74f826cbec7b893f2ea5f48abf73109c2dd111cedb1816510885154d17d0935d62bde262898d45c5c1265a7bca14ac19d5d1a5ea251592828a246d17f9d90cb99ab4626480cc614a8812004f88ebe8849a830f6c29d754bfa2c732f14f3557f4060c2a4308b93536304d19626656af1622784cf0e78ee96bef16c465465c3d315e9055dfd49497c12f6228da394f56d95bc0f0644260eabdc14bf0a7ea4142b713113541880880405656484c378b00ea9bce23bfe17c4116ac23c0d8be3ae81138d83a533d45f311d0a10f8df7813dd3ddedae798d7243deb938ff57541e3625036effd654d855f8e459b31b83bcd6b08a6f3946899e113c9bd1f119318063906d6e2f0b3ae2bc9246e436073a20b3061e223cba9c51d8d7f7ef76e02e01e14faed7d77e2cc5652496d850520e96f86ccd3df7a03a946416e15e72de9fd83dc605516721de3767af2f9e518067105f4f87bb4572a7bf328fd390f1ed2b3ee51894deddc090188a1bcc2f363b2bb2f3d307254c0d8243056c789c395dcb1562c8b4addb53942970d4da5fae223045644c5fc6dd31697d648f048362059b5da8dfc6f652754ada18af960e30de9ca989bbd83c97d301256b37c92ee23bec863d9a7f9b07e28fc754c189a50bada349e56f95e326d75066dde76b855e16f792ed8c0178aa992cbbf7d0b14494d7e85ee60e9d05615d92c0e4b16f6ec8d9afdf610bf311c0a31f23db028c46296de47355db2653b57562f35a8e905c19c9e126a43a0e07adf810a5d66a12c9623ebec70a1d69a5ece3edab920b6360099a9df2f2088a1dbd2c8e0dcc9d0fd5e5949ad024d0d82c402a506ab8fc4d25290c5f20548818bf6683d12761d566adc07b26a6231f39ec1c5b1e26af7d594b17313101fa188fe718b009677dae0c8d53ae3e04142da5f6d3bd4076c54f3775c792d073cd84da7d7025903c5b908cac3456dc8c4a7c60a16af90159fd6ea02f0744b2bc702f1ea05646683a67648c7981da39354f4d258abc3aaee0abb458e39352c0e449282c57033d189305ab766fe04f79c35e02dba19dfb56fc3e6a75b3315c59430baa303324c0ccd8317482ca5842d23251bcbff6fe5b585b8762c5b906cbd60b16e0ca0ad2a837b7fdd86047b63e1b239fe2267bd4b61eddd6bd0da6d5c830a82d1035d1a30d9fe73cac1120e86f1fe8948764b9e73985de4b542c8ba75b98b275ca4878ca9025d8a3b40299849f41b7f01dd3f456c11fc919721b9c06c0dde4b6d9a22ad7abd2da1e99729bb383bef075e74ee91e41f76695a467f1596ca92900b6d3064edd5d3f33a7805935c02d98ea99efc922966d0d2d7bd6bf87fda18937316d5e5b7c73ef4978c34e4ad41b96fd4beba2507296ead3a778bfef7e1b4e1be6dc856dc3d9102304dfe139a2fa8680e5de887d3cac6418ab65f51f03b3e7c47f57320e3ea2a16d5b660270189cd275fea9d6386a7d5bf491c161bc28ec2f3663446fd0e4601e832257b976a08b631a4df8c9c4e6278c44038ab781e02ab1a43a91bfc2e6760464cea889a51f1970bbc07588a9908a7af5807963306ad4a57cbe6864d5d85e2598b9c8e5223e1ca1a1522f338edd78842ebc824699e8697f1f708252b028dfc4c7165e8a56b0f31da73ab71eb160569f8a5b71d8c101e79754d5d26230d80ab5d7a005ecdfdcca1c203b7f977dac8744901efc6069cb10674d2884baf10111157f76a10c3f623bff044a0d7f271b4d4e12c3590628806a43f3c2a88a87f84e9949369ccdbaf68a879740cf6f38e18b8e5247558f734a9ac52ae94d6fa159bcf5a3d193898c4349298146b8ade3cee785f7691027815cfa0690f7751e856517e1ad27ecb6fc44c4f48ccb1e6fe45b00b556f1f8170afddc15f31bca3cf78a23d8569ec74f8b2022f0700f7a62328a11945163121b79decdc5156ddb05e50e01c3aef189b61d1157386d4049098b5b94f04cb4046038145a6ac3fc1d144bf9877d9da82955461304ef72d9cc75ed5d81013f92461907c8979e11cb2f301250e0d1a05dbd317312b6711f344571ec461a3f7455af61413600390b751107c90c1f1affb88c68d3481071dec063cbca549223b6d34a1631fe1d2ee644cde5af17a06352bfe0fde4d7b2da0e3daf5ee2fa07f196c15974897bff81d61e435d489b27d2af986be6e297564e520c167617d323e94d1c6de1e859962a4b32074a63d8c8046fba8e3eccbde89c8c65a8fce93abf3db8bf2dd82a68f38744cd98c663a6c1e643b47b8ced4e8382f4f9232ef24801e6f0be047252cda4abb5d78bbba4145b2c6dd810fba1ebdd891ac38ecb7441ef62625bc5c86ef723965af821210f2ac7783e20ccff44fc0d60b258be4f3078499040b033918504eccfd647ca4f410a1c7eddc080e3421f4711933928d5af62f489dc09087393bdff1431ae54943b3d88a8882e9b1292ccbc506384b963915fc1610a1ecd41d36504b7cb8a14cd5ddbcedc487fc7600ee88249eea728ac6b8b78fe187aa05e8ed8216f9d107452a1cc0f05c0483074c5ff949301f9fe3c3942b6b80cd55ae5e1c7e18401809a5ce6b1ff47737540aadb96f106f7691e931709ebda44839852ef403659d1583f5390e5cf83280e0be6ee837d6b7566c919c95696ad77aeb91301587bab76b564f3088cea47caa69794445f5c2f74e4d1a034c3661a32ec8270d2d45b7d9bccc53204e57eb8874b932d0c4933535106e0e10593c95e13bbf42342209fe73ada9948470098aedf8ed810e607adfa7a7b37a25a17ec5dacf3fa4525d3f1a029b7ac08b64068cbcded6d7266ee2a550e8df04a1561e3ea2b1d9c0e7f1782f3838bb49441119ed74bfa7702e54a88354999ddd77af9325e073465fd0628a627dc0159a7391da9c322a6b15d802af107069d5ef217338767259adb17310c4cdaf9913d82d395dc7e827fcd0d27c493887247a9cc9a157c373f8ad9d76adfb503c03e44a795f499b823f7ff69b9c479c23a7f11f776436915cf735b96f07b2d63a52d7bc834f581db62c36a43346d7d4218025550935872da447019bc06232b34c1f710005988010cdec2eea65df145025e2c6b55c50b77a467b91901ad36056b203372be10fc682d64d7bc7fef30cfc0c0f9d20dbc14aef366739e3f9677c6e3c00c371de4e70dfff9369a230c530db6293c4fdfabd16895bfd64b44f703e9eefd42f8d3f165140316b529244787d0f11e321e4d840af54b0e2de66df5ed6eeff61b9825894d7dab9e9ef31b2a5b3afdbbd81e9ea7517154725317107ae2b530838061a34f0c546a1325cda3658f00fd7858e0c3a476796434d16f057d1188cc3dcc9b0341b253a19a2e8965e3f06f61ca9d3e8c9ee6c084ff5180536f3052eb38543076843e08fee0d3f62bb2d7bd3baeb27528bda8f39c927b2b2bfffcb9df83c96302ed5e82c4342e42cd9f734b8318aa69be2e9e201d47d78779840c179c8ec37a76d750e09dfe7093674021b4f55b708cff85a11388254d88830d4e6d1e8f63ac43b2610f483ad4bf95309d47e3583825bd10104a1ba3b5ce830e8c5808930ea274284c0941304ab6509c64e90f9aa8d8382d3c3bdc88ea2fd9486e38c93772665d353d2426e32e69778abbe50a342174d4b25ec58bfe550f375ca786e6c8f3d5661a1339ad41c227f8ca18f476bc457257d6ad6b6a9fcc564c6dd50aab81b2962ff5e3d034e1447059d00c4fa5d4936ca004aedec2fa03259317e05daa1e5d04b1f77063a4b314a937c0d04426cd991dc05f68dc37f99f151c6cd4ff7fab42ada690df2332724dc168ce9c5d33b6ef201b358b764b37c51f6ffaf884695caca94f556087d26e82fff1ba2434af893b9052aea6fd1d37f9cc490268535e6064f07b7682dc8f709b5bbab8867e940df8fadd940a8865de41ed839a63290e35dd577dc2a89ac58824e8cb9ce37a64243fe5447b68ec3a56a96202232e77196bee9f3a8db17f7b3854dcb871440b43855bce9d6e1fdc9485f59b2b33f8a8e1f8a8706aba83fc199317b8fbacb1f8b278c6a92558ab7b3c005a71c22fcdc766d71416c50e7e5905a02b4b90ed6350f22cd012007738df16b0d473c31b4676dc1f4a837d15993193be17cded6e673922f043bd2b455ce4807ee8e2ad64cbc45693182239433a83d44a8648120c66247c7a530c23b4c478600878b14c74686193f5382e921ed7c1d5b97d1bda96c1e2e852b5be654df1ff2a3ea0a84efee9c70b20909697a2e2a6ebb2200de5e82e8600b5b39261e9ac22b41706a02b0d371c344ed7e665bcf55dbfba1a78df805f1431059b499fcc9bbe0d835470bcc224458a89bdaefb2592531535f31b61ae10904f533be9890e0995d93590fbb4cf5dad08d3c16cf2a8c5b3eaf51b19e3b66331f55a2321e1158f985955991b794ce182d52a4fc450bf54ef735eb7c7d84f256979b6cb24b527877cc01ed758ae28850e3c514fbd9848f15135ae7feb59470336d008348c7b7f293611f6d3c439060297ae29016f12085057487388e8dc333b500d59a0cff3f2f3cf5ebcd07ab7414cabdf15406dd99e22c629cea505742622f121a21d679a40b559143edd23041c6866d33df1a1460b79853ee318666455ca8abe60c2a606b3a13f3be46d15d59ccbe4db48fbcd19ba517b3e9dec272fa6b200154117274da09a43d9ccc93730626ef9a6a40b89449660c03f8285bd03f16652884116e02b876ed62e183f5789a580763efd60fa8feb4aa6ada391b93c868089b4658c8a795fca8e4df7c20a5ec6bce3e63953df8a0a5b2593690d0f061fb9b0e8a13d4626ba5ce5f5a2a5a120c4e9f601762068c31295ac0c2206e6090f3758f163171fef3f1842c8e48ad21118ed83fca30a53388987fd77bfbf0461ca7be92fe4c77ddd27d3e381a87a8b3f93258be15e43481f43975df687af40b743d6c5e87766cec57a0d946a6f46fe9118f6cdeb68fd1650bc74948325fdb5de23901345b6d7958747c262e79249b88b3c423d5eabf767a3fcd6ed80060c1759707d24323f41fab51405edac29ed4ee103bf90c494c6ea381954a2f0c268f69aa394808472e0f5c64bccdecc7b87e8fc34a6cb81ea685140ace3d0eb36cb8f4692786da468aa84572b06655b1d5d488f4c35c958def19a31f7a3aea1fe5e3376f8238b79656b54a668808c7f043e42d4209f85a9d95029702e5ddc9f5ec38e4b09f8baa002809855e6c21c15883c8da6b95acbb99023ed0276975052022109844d216872252b3bf3a2dbac0b2d46deb08fad4fbde0d980c740147fa22b8dc88c8ac26ea98bae55e6215caad98a84646105982c15b879c83c5b6b6a8faaa4010f0e11244c8125e10aa0d6aa80d2b0c5bed83b554f813bf95ed2e54e6fd845ac6106b32c1921e06274280897936a262bf86f8634b023982f166a00a54fb4c3a23609327e2c02c00021fc19350fed96e111fa48912fb2a250b2f0f6c850fd698ef14c7ee9e764451e50ffdf2e17cf86cb1d4bfd56929639387e1459e3f3e4dcf0c2e0f7af233b0c90ce49f0a3d8b26b8ae43c4e2ddc4e59e3f9f27c759afc68c58956d3172c0967a3413a231430834807f85f734459b8552b33b45096803bcad2a1e04079fff6984667d0581ba0221032500c173da12f621606b564b00e5f5f57fa64cf0ed0c350b1357aacff45c35925f29476bb652b8fb606104c07245fa575a5fd00801a03361673721af9f2a0031cc4510a6488b910d985fe560062a65f8833277ecd0a9baeddec616cc5dd8334fd1778dcf4f9b66e71e69c96510f3cf0ee848dbd73d327092640187220e451df2da4c531a1022848296cedf69fcd50f6bab042bab36c68ddd0843d2612ee7c92fb517ac3b1ab37fb99c57d4bcc02d743d985380921cd4d8b0a78063026c720af59e05192c973d36463a5561db939423df2fc5999e89e863538df06fdd8a42fba346be291ba10b16b35a14ca539db0b4a0317a9527aafc05c147bf80f0f39387bd40b950c6b94e84216867f98e2c59e5b53e8c42141a0eb32016e1cb866a25451a0e33444f390a87babfbbc1784f54092a41685c0e78eb9d8413eacc21ffda28365bab4dc0023aa64e725a35d1698995aab2f268dc0f870650701717151bd860b4f962273e50936f6f2bc733a8091e4583d93da75b194b724b78146057eacac9c35f93df546c7e14bf3dde6464f85859d046faa98a437e8a136e10d0df5b95365939fe61038ca788038162c12053a60223e8cec033d986c11e02b17a56e5ed2d9cd7e6d0b45fefefe86a21af0a9f0e50e7afc8ecbafbf25c53f88264ceb4650790abc7b6a5f7cfdc8ed53643e16586f4125ca3cf7af1c88a9acc19e2951c78d72f2af47020c5b600cd8fac92f8a22cf9a270caadc88d1663a63e3289f304f3dffc26f1ee79af36ee4d6d5a5f05fe6d75ff65473d26386727715ca204da7eecc2453a25101afc33901258cea2a825223368c13db01a4135105899ee2a4a0c0b41b70cf2e4cc974c380401f49c30ea7b270aa0d922db7c72ef27ffd4c8bf2a10c9958422f7cbc2a7b94d0439d6a17157284c1e6725a338d38c1ffa4f6ceaba03c1774dbe7a3e0bc283c9b2e44c94ca1d16f1b7ed85cb1abb280b6bd82efb3020671b682e071e8cff8f38afdc68ed8470712fea2b908eab5433159f53314f28869f8751afb970bee9bb769db23d8f3b06a392e7bf5050adcf841c8ac1f702fe0ffc97e3a8ea77879e991765c2a20d19628dc69f6782c1d23ccd5f75ce1c8d81b4694894d56f30613ede0cb37262ffd109043945b9e6facc2238a5cc47f61ffa5ccd73988fa83629c6cac00cc7fb35728253405745c91a11380e476bd01618540e8443c3301ce825ee418ddcef67f09d70bc24f44b826f41db1c6698cfd4b8a046bea4450f0891565ec3b7efc550d01cbc8f300cd2dd9ce388b43a3fe94b0e85816c4b92f8c33f2b762a8521d962dece5df34e99a3ae686aea1f168d201c65c5cd273962ef517ad83c8cf6ea259c75b28299f3d9e9088847dd365240d0c0734b6bd91231d00b3f5776475ce9265a86ca4c13d747597ccc9dd0da897b14a3c5e46227d697a7ec1597d018b325e8fc93e14d1a2de9f4ff22066d7ebb8318713e3f4fd977f9ffe94b8c5305c8a2014fa309fa3f7e410d48b954e88fcff301ae85e1ba3e855458789d3b197495d069dcbe936324c46a5cf12507e2bebfcb98329c3f8f941bd2d8878f47438c00d5f879db370569e0f13740d3e1d798fde52a9a633af5e5168ffc88013dd2af1d397cc8408672a1ada8905a9d0d8f43412eff5a893fdb6a653d768199ff98020dda889ee9081c043db59e09e789622ac08b99c87d03407fa4bb6de3a7579a5d756014a710d591b516df34501135fa3195bcc6d6b198c86e44f54ba4738af34003c85ea49be5f70bc3f1c95296b6e049b8b88b7650b11665de106ceb6fb48ba77e81f07e0bd69a1a30f4d12278900db6d4cbed0a950addbfed611966407a91e0b7461c88332bb0225c2905ea7d2552b2727ac332268ad8038afd2d9685fa15bc6257f25c9423e3aa1c4b4b05db0f7b122599f2d4aa430957accbbe8a243246c78082da2211fa9073155e6e3e1bbd688058941d131c0e71cca84b24d41a9ce4476a2e290b02bb835e86f4cebe786a56429d71619dee7a0c04930af14e4bc4bf45dbb8b19e0a057e0d17469b566f450afb6dad06ad08cde0dd414c67b49bdd490925af05e1d5c08ca5d518957ce39354cfe2975a2803b6e949bd61ab16cd77db6a76c1579dac5e7a0c17165f7641277bf11b86ed5705bb66f1d53b2f40fbce1f4b3e55f8b0817c7125e4618c93b9469e8609fa82ae220e50c32b4322f0ace8bc800167498234a4633da9a717111f984f0719b405b6fe8a3ea66b181591baf4e6dd4187ee0555fddb0100484252ea8c8711728073c74dd47b45748edd9c0cb6ba028f79cab38d5c97d478745e73ee64b9a52c52ab1657a56c75cd312a9df548e5fb084b7db3585e0f0756415cae17b10c3060eb91889d66d84eb365c658c16d856991a33b2d26cae66a0e1367528297d40c93e77ec10425463c878289141df2661ad374005698e2544870302e0655a32f45007fa69cb3499964240c02658fe3735d4907a8cbfd17daa80e3afd991cad35748cd248ed4a973d3c74e716215833df7f4d130283269a48c9e662669c56e964be558c0d2d7bdddbd4734880e341acc2a2a2fc26f1bff619414ece0b6c247799d31bc6ad8cd7af42013d99eaf4cf7341580c909502559ebeed4463233df5606c6e8e169c3edf2a5410cbb0d13b5ebf02ddb823d7abf05e932e93dbee65c6aebef06db71442b8c1c4b0f1ac6098d32d017a05d3cdd837f7ffb335020fe2db8d0fd4073af82a3e40213b5421f9700fa50f747603a8e5fb7f82da1ed61cdf3d6a57bf1efe4e01f40ced79ba5426ce7ab5bd84aa783ff5623453afbf19028615c9610b09e4813b39bc662dde8d5f5f504cf7cf2b459a7f30c2ac81272904cd1ebe600ab92c0bd12b2883eb7164b4d505b2f27838b1d66398d9a10eeb9bd212736d555abae41bb441885f7a070a6ec6b68a951b37bc7f63eada6ffe696e460ced8d05e2bd6ec8a8186409e63b762c2f236e9e9c2b717e319d33a5c6c172d0e36b2baf0c677b868039bef68558d0ea381006215d09174a586def6f309a5d7d209f3ffe1685938043bcc2ca972ec7d4a1e418e706da36a1dbafdd4cbbc0b187b936af694f44962c38d8ffb3a8386dd50b44ac7dc2c7298cbf2219d94cd18019df32477eeb8f7b62dfe99f268b8616e7e7577db0c83b93f2771025280e86ecdd9ac94e940e99141778a53deeef584b99a7eda0e0def498a4bb9670fe149a97305e9c804055025720ca0821082141bec4589030148c093200d5ccd98e49409ce910b29b1821af64a1d473c42e17fa713576cb7a620ee32b3fdcfe3c3bb38c93090c77370547a5e330f368bcbe8d46e54918085506bd7c6ec03db423a7d60fe0db740796d211ecb51d640aaf1da43a753c7ba4d14666da9bdccee1b4379881776473bd994f762fcb4f211d0af5a3599f657408536ffafe15f666b3a4db544744e70f22fd63f446d3a375a77782aa2fae9c93db612bbf508e0212dc1025fd5b93f5865bdfb7fc48b7193399ce21dc97e1b3e839f39cb72d824337b1c926643e87ce835b1cff22dceb4a87874dc790df12b5bbd334f5a471b3ed70c9d44fc673156b727256c3ee0b961977936f068878fde313fb0e1e54bce3085ce62ab3cb4037bb3d57f51def027acc4c8ec5a2863d1fcededb5b38b2d94c322176edb135fbbc966f13fe2aa3e4eb4497df8684f75affc4765eb386969828de234775d3d9f203c60f81829f41cd66a1f1e523f53c702a5fe2e4d9ca33d022fd2bc09d2a517772cf978f3edb8b107ce37a00cd5fa31b67e7bc7a6a6fc1cf48fed7470b5d70d4c68fe4dde08dd4533bbfbc28bd725c3176663e1319e90792078e6b7b6672cf569f8bcd5603749053dec3b0c60ba9861f38cef523974018f38c52f7d7c3e2fae39151f5eedc5efc1b78916ff651cff13cce3da58b657774e2408ee20a74431bcd75ada8c51236e86e0c89f600d62fc1c0d81e6b983b8147fb5592c3401f0ec6a17be3969ba705785e6890c735dda9791b9d3115ebdbb77e13f13e5cd81345aed5d5d9a75ddf41ee8e17c48f9ef3c7e98c8ea57e24983283b868b6d44683ca8bc3bb347f4f6964887dbaf1aad183f2038ea4f6972ef2db371a470c8f40700ba284ed93dc75b7541c299694cdc976beab9b422a9b147a1cdeb716a26bead34ffaa7655379532637ce0aa6ef5905f9a5b229947a10c60d5efe6b51c0a5913cd40376036200da732f84f50952d1b507adbbfa489f58d49275e084cc165a62d3e3f7753a1dd2baab16536116cf8d9a73be3957498c471f407656b3e80a730a3ce280b2fe5f88242cf636e176dab073e0fae4e3e0eff8cc41b097b8225706a2ed68b355626c1c26f2f16bee2dd8f55080fdbfcab60d43d0a5683984e2d05eb88a773ec27d18bdaf3606f1d6eb9bfaf4ed973b40ce05236283cc90e50c98f0f94ce62675f0bef5d522441e03c0633a3e59aa681778b8c14672e7714886313a55ffd32dd99f9b9e7155d99a052b48b7441117581af151929d5c380ae0fa916596c716889514e6925ca2c2d739df4d4d8186d4daefef2d5add409eb980ff670166d286ffbcedff087ef85a2971f3bbf96ba315f9d50bac0c7fee089107e0cfa54abcfd8bd2092baff6ed93b621204b8edf9f3b39adde28cfca3bdcf40b2d06de1b1efe726a4d878171acd11f96c5f4d46ab9da20ec2040fc42a6d3d8ed1a7fe9821dedf2929e3c10f5590510699a2328a046d545c71d1b8aa9a953c046a8bca0115c3daf4707caaaede9c552515ecfb40e7690a7bcd10d70b435545248b87b957a74d8c4f1320c7bfdba0453ee320ad832b2a9b3d9da7215b584ad709469a89ad272663efb4b54c9b1ede8cd8977e7d9e3f581caac677e4189f441ea59f96bea003f8f7a5b22838e38786d7fab8b2a40c8ea29801637f7915ee6731535eee47acf4a88646274d3280edbb8e27706b9c92550e848662ec0daff0b1c5b274765223d376061f2328c817b6bc51c1115e809ed41e1edcd1e63c2c2a14dade148688a15b881ce83df9b4ab7d0149a3c8e2305f02fa4f8ef2e1b5492a8f97a0f82728a0c7bbf0cbfa8dcb4882658b950fda246fd78f77bbe7f1e626492f60ab6da1a02192bd4c814121cdf169af1247bcb0e4eb57280993ffad34213398eef69e547ce6a5fbb643fc658b618eabf3898b37d7a729168068c786049b9ababbbd4d964eb22adc3db0c78c514eb6360402687ac02ad42644b14d07da89f1d07f101f3a42ef5f5e02025ee41e4bbf0031ef283323d2257b3ef8eb6ab03c2eac79a2387126d5b9f4b1c8448131c169cbf3d9f814e5881991d6981750d7dbb5df57ed15da96d5b333a8a1f44d56b0b4325556c44f56111a95e193b7c713f21de7bf791cd82f09e0118b0d22132ff9a6ab406a27c82afc57678c33f41a36c019c952e4a8e0696a769bc0416667b7a839d7501864fa622639e645c764a1b9609f371cb2e18e59fbcbfe1f94b2c1dd66e62038a4188247a2d3d999fc99414b307c83d8121a80364597fb125187484e676fd793e69f9b9f93b480353b8335d7f7d8d3af8319e5ec694568e83799b2ffa553f2b8dd2ba1606a53bbab07a16b2d99431355320e81004f18c73d851504a19764c0fb9e00d860ef5ba38051f6cf9c5e3a9155b5e5fd240463cec544eeaa23f5a3b033faf9c05f2efe42f78ca17c3d63ee769c74b0dbcc8cbb10b199d4b250dcb15e1a6aa0f0772da15690f08097ef239b7492d8a1f698debb94e03cdb3c28369afa037149f8ef4bbba748bf3c47542a97cc6bee1f9182dc959e4a1d1f6cad492c8ea5a8509a0360984b01ccdd6650f8e56328c6b3ffc0c0da123e321fa79e017fd1ed75d84c446df576aa241e70dfaf73dcf8708e62bfb84ae18661daad9d9689e9c2aaf5c341b8a861072cd961f8024576615767c78f70bc63139d4f19f86e7adc0ec0c2339821441b74b9af4de5c1a93056140e5c3bac990e692a75b941b36a33404a069ef2a30db953041beed4c08f4da8046696574d7e16ec20792e87c885034ebc1c272bf7f70b5b3dbf0b6f4aec9b742f9ff569765300c9489192f01a9236798e51abeac7c5cc7aaccf4cfd142ccfdfb37ee800330ab03fe8e5df92b2c2ebf768a463040f8d234759f75f0f96b6555e161be384229cd65450961bcbc5f3ba0196283b1a2b7bc33312d396ef2466b515fdd3ae713bc7a5ea1a7b3f7e8657112109210fec788e5fd705799f8fa0b286c401ac754392d14e3eada14edf87ec881d5fc1e90e3b097a272a56001773c1b111003232aa31d44491e80d1424dc182998d79c3387b1d98e63fb804d8ac59a41b97261b2ca373ca784ab9186e9ac0cb6cffef6e283384d810e68afa646a1d528ded0da549e686940755a125b2f6488ef2a3a1d16628a31ba48a9d2788c519956598e695a983fc583011f136f64794719e7e11407e40d0c7362048550f8f2c49e4130373a2016ebda8eade3804f73c874fcd4d53a686ac7e2545c803340477034d120ef7b369b245221e924194d7f587bf62db6767f45df9613ff3363510db929f8aad83befc91e50335bb13dc8c3bc3baed84e10eed5e8d20c8d099ea33e7a79819e237ea46cb6c090cb120e8c62a59a9bafc45c69843d3f0e7cd6e22779455f2fef10f2a367abc72631e57084d20956a8217c0e0a97792105d830b85038883b0ed2d4886650afa0582e16b677c4267ba46bc3571ed7491a2ccb28621892af552c4425ad933d443e878ceaa7d1b70e16e0ddb0c78c10c63602d804ca208e3b348a987e44d36aae5f02c0a4a505ee5ca9d8c8bd5c631ba0ef37f4383aac774b6031d0fc0baac33b83e95b1854fdd261e0c45cf297ad9c931e4c04100a25b4289dc5959fd48ccbd4f426e0f8f7abbe16d32399f0a1c6325d3b6cda2ce53c73bb7f641542b0ef8bd7cbcb39c4c81831c987d7ab380b40e91813c83c561f098c6fcf7f0e97614476c763f0950a7448324e155e76d4c6f4b685583befd23575fb8684434e26c0edc04eedbfe6a8bf2e9a3b1307f29230005559742689b1aa1d6414d5b422e0c86332a9c5a857c6f1a8c5b911c85018d6b36ec6b5c83aa75f9a90c6c7c025e88d7081adceca07ab1613cb4611869e1d622f3b33d0a5d166fc4c3b4f7b42ec46b76c1e34305359f33268d43b3db5ff2800889d8f43b90380b22f19c07c7c38f199d859f41e470d4501c5e29c51dc9153b76964a2cf7c054d360e472260a363f5dc859ef68eddd7e9ec5c1cb127396a517be0b18370ee509558817a622035347efdb1935ea02cb9e654e1c1ecb08eafa78e2287c60c9f4a946be43f7d14adf8c303315983a86d7f4e4c1588a48064eb030cc05c2b66eaa3af6aa51b8f32e92f2e6b5fc55a2ead9fa72138e2c547cc18ab96ddf1794fd0bfe13306123fe54476b906d5423cd05f0dd72b90841afaa149861801cd2cfc0895d1bf2d0a2e65453a2d2ced8682e78e3d8a76255da354846227be9eb18a98b346ad7266fad0b70c9a1f7cca00484c79cbc383ae39c3c4aa1ea7992ae0e40b0c94718b95171c0c9c00273eaa24f81aa11a341e6210048bcaccb07b8c73b735ee8dbd0796706df803e2a5bb8b825991cc2dd4da543c110b6ddce16dcd2b81411f4f1a3b5f5dd1e5f7e8613e14dccdb32c215230d72a949b41e5eed74c1e2f330bc812cdb81211a5bc16b02d97a547bb3d5dc3b1521f1c308cc071144d6d6195c460c0080c54f561fdf70a5607f8a7090edab11e2dbdc60dad6d8ba940bd975438dbc1b6c0b595d1538ebae52b3860f569ea786b75cb7e4067754b2479d1ccbf9886dc2da6cfa6080d14efa3dd4a952d6fef83643ec807b383a9d65540e7f0252f8456e1095527ce440565152fdc2bae1bf1da201a4d2905021e33ac0692e300ce5acdc4cd316cf9296506fc0c3d1d9d1d0c9f936d9c0bbe7515309ce20411ef4ab77dfad988c38d96c74b1f0efab33fcabf7eba317c929b71704171e13a108714b4f45e09cc02406f961697747667d5db00adf870a115ad4366c96bd72bd009f6a1a194d205efe8adabda68d96a526d1d242856f28d6ba70b9fb7142f2a15ca1201ba572d6f61da6836c2071535accc9ffbbff37b71e5ee5e5edee8c941ec8482ce04f4c7fdafab9978fd0763d32888e9803641e18e78dafc0f89791dd08f4764140ddd6cf2855cd408c6dbbff117ecd2e82c05444805e57f703ac8222720321cbb4743efe56a76ccecfc226e4b287f75a0e58209f974a5da6a4e5d0eac8c74957dac09960ebb102b2b05ed6738adb4c24c14b24ecc2da436df960103bdc5d2d39e5d032fe162d205b7761cd49e966b83118f74bbf010cef8286790db3396ee24ade17720f2e2670b2083e1a069941b48de58a1872ab0116929b5bf1fcc9cbdfcb1d61f22a0634d91f48c056184f9c99a92d47c840469d32dca7c0bae9572c9d082604c761f0af30c1a45689de4dd4482653162324454e0d226407e1b8ec08d57ea9abe7eb3af3d5768c1967e091af01da1edb98c8ee581a6a2d3434328b5aecb9c66bff89b60d988ff9fbc9488e514e828fa6fcf1cf219f3d49ccf2805c09be8fe3a3da368f5319d4d16725733f812ee8b187621449b8a1ddacd7039d180022692aa95ada864c78994f97fc7220eb1ee1c762dc9e4910859c5d5fe7e133bab771740a063f9f71ac03d32c138b13d2872d569b58393b6cc5734201ca4a6820739df66ff041944444f4bee59ac051a33610f49f9ec1e857b61eea2ef39eb73425aaa73a69ff871be0d795f9efa9d52b7a8d40dd1cb2609f1e3b718c531f416de2d142ebede5edeedf53586e907aef3a54b333579c6b677ee1f6ea111aa616ac05d6892c9234c4f348de55abe4633f1ffd9fe1b55b59599d2f0eadef785cb0d419779b2e652d1ed6bdef3a9d57e2f4b98672ad88a3583a936e38580fc2dcd0ef8311f79ebf607c8d611d9554674dad6c27e3c05d8d318a1ce1f73188d9d327b10fce1a709c2368b6e1049d0bed51e2913dbc2de7d348a777449a5256cd1158d40ffbdbacd222073591f572df5f1ef1239254b17e50f0b7b0e88fef73a091f732733cc95d69d319241a70422e1a6525239ec872f0bf670b7337f5f785c8efad9636a666cc0046902eae8eb0e95d6e40f2c023d5a405726a77d1a7ad3ee9d6344ab4b48c2bd441c1b83410bcbf7da3a89fe3a26bdb079c9199a4067c9616c411af9cd97309b7122ef1e1fc7590268bda4837bff7b4e4d9a403b0cf78bd09ea63d5ae74df5954ea57d6d403cde7415d33cf365f921354f7243645b9de900480f177f983df3e6949b79668679552d1870c5968ab79bf751b1337c6eae8089ba8262367c4867d8ebcbfc3dc470f673408da133b67ea51bb93ea11abccd5f9e7735312454f604c2a692578ad619e8ba324dc5e849a3c8ab93c8466f0ff22caf67bb8b03f743af0e5830747c67be40c9c28ea584d52f69a869dc0dc14516a9a2b679244b5b70c1f186443f3257b6d146be78958decf68aa1b9d2de88bc908746c2d65a31a02dfa4ce02af64e38a295d83ecc49834e2f22df173c107213cd41f95e3e0453a6d536599b52f11941ef547b181e6136390ea8e8d4824321dac7fe143211af79e4903846767ee2f2d6a78433fb2af5ae29c8c930d2f16616d569d7824dbec08bb7108e9c621d364af3dd3f56bc7afaa03ecc232c0371532dc033fdda996a5d51069875623798dc94b4273ce6192e3a60bedbb3c0c7f2d487656edde1a2cad5a8ff7d34b27dfc579c8de7448bd2621ee1e47ac6d82d6b8eb90576477c209541c4f6067e7a0aafc1ac90035032d82dafe55f29264b4863ca5ccfd383357acc6b9a4c69792b4e14e30aa08f03854509e9040e5b1fc12e42ac6fa66c32324352195138f0880d86dd83d12b0852c75b5249b91bc339a5ed86f98330d064e11b449be44439a0529b556b76ad7c3405388971cbb083eeadd1b07b67c9fc660e852260e7c6bb686a2c095cd023a630a5bcf38a51cc86ac7920d3adbaf9c99a44b61033fb25e53366c67f1856aa6bd5c574c1b55f43f0df311df44b9e567a0350bc32fd8699f1ec5d2fc03600a2dce51917278f94f71e429fa273662ab5fc250af96b6854d99075458e225c1cc0d2fd5435e2126fca7aa6ba7ebf8e1a74f628ef71b98487bc11d4a3810492a14f09a113e6d191630d1eb4d44ba4d8380844e851e4881f208d21176c3d91509148958c6b5187b140afe3a8ce1380ad66c40f17b1b82a876c7d71cccf2b04b588de6fda296e84a71bad0c228d08ddd778a89bf075ed3326795343d7e0c046479c779d8013d217e559709f2d51a02024be9d861ef1bb072e973c62274c02aa6b6a49502537e90913c0e17c504fd6e6a586209b0452e6fb48ba97ff8cb162aade0f0d8542de017ad79d37979ca42e03c8ae5bc556faf2510661216cd6338e474481656d039c04d48eab98a09f7ab2c479718bbb6624236832defbe44cc122f5c36c1a178c1245b09c7bbe06ac468f18b085fd9fe21ab917d72c92f31e4a5be48bd2490750084eb4ed06a9a9d07ec6bcb4e34a903f013e7f429f25f71451659edd2f975ad0de32e05ae3364bc0979a6aafcba2692a36c79ea44041ec40d0059d5cf73466e174610de40335c9ad48167a0f7512d97a2b6cb335ee992d83b639c01d9a8fb01a9cb9598629172829f8989f70a365bddbb97d44aa990c5947a9ac07e671eefe3c8682017850d75f460afda209e387084ad8e11ed5cac5ec3eea25bd24443ae1869b030d6f41a6ff573048a19f45c463c7d122ec16bd7e32631af79e16359464da9380e800373dd4e5e844a5634bfa129e015663540ddfd8b4da3c5b9ca7f01131e151dba39a208cc56adad9cc70182205ceb56b9046ace1e2d6a244d548cfac5aece3a0077990b9915d3778b7f2441380f9509fcd89f0af3dfe54d44d05a31198716265637d75b4c5088be49930b08333469253f64dc15ccb9b995e15433d0087eb869b9e8d4dedd2bde5d1bb4eecea9b20c740f8c190f076dfafea718a3667afd9d8d9e2b2ce668ff6d346685a307d44584684982ba84cc3e8a213e005922b7777c50d8d3720fcc5649d9219d065e98d212ed5e989d10f96a7aa5921a5ca3f362c30c89d805e81f96922686377e4bcc5435b82ca75f3587758440af27687a408a0b6a15625424a9ed21c1ef58c7162d7a703bf5e36c061111adfee4c099351537b5d2a355cf4be00cb17832a03893de4543931d1f6d3ebcef84ef61f5f609ff7fb9f85d2793a787df83604b88b2ff7c6ad2f6598abd4d39596271b930005e23f602d042e4fdfbcef9174fe0c2b061f35472db3e97f10ab09a1f4dc3565231b48d39e51e002e5cd644a08e18db7a3be901b6f52571dfea13e6c7cfe179b52f8ef6423e3919905bd5e04daf08827c9bd13f6e1488cd0a04cfcd850443d5389c4ba251a3c06365e48cdc1ea5c2af27d262eab393745d1592c211b8dac952a930c3a2e24707537033da6f8d6c2e8fb07f3b0b0e6caf8aea09c8a105a8b99c2575ad3a1a4d7667e7344c1e6bc1d247c65f8e9770fe0c7a9951b645167c9cd1604abce2fcfb9b30dc15cd9993d5ece2203dc7a0a8666ce3dfe87c2ac8a6fa67a684e012f0a9bcc5ff25cb334f4fa9a76ad3514ff5114664557a32bc55643851f21ef644b0992d4f28e533b3ca43f8758cb88ea7f989d96dd91b965ef639ba60f14b892ab6de99bfba5c437dee44a6b5c485be7cc82d4a8ca08bb0de5c7987122a566589bc661e62792fdfc168c0fcbdacee2add9789f2e5ebc0ae92d0a235ecc7873ce1b40bcd74d1c3b6d5598aa0ed5b5f5007e293e395f2c2921a6e7f446a836155e4cafd075b2f3cfc60ea07893ef8eb40b9d59309b0601e3df57c2909340ce8089e9ed2325deffa7b310cab43ece4e4d0d27c773c10075228ab59e7f8182965abd0dcb6b652050b2d365e7b1d05f649b19ce7b62f47ff95a31d052ad4ee397b2cca64e292c6cb37dacb1aeb0f547ef44c998b4b1d7980441e01b7446e164fc643aee74afe9a6c5a8e69a0a34f1829750167641f1749da69f44809109a2a4c5d0312929049f38f9929fb7191ad69eb2da3e357757973d86362bc6c9511a347ce88106bce57e35c4dfe9a33d5d76a1a6d7cb74766356456b7a15a66b7d2e483647cb594768c48402704e09206985cd644a3e61a51bdf1c1f451a55910932e37e1d11e7e3d60929d96a29f93c7c382870d29deaf4666f9c6498becb1dca7a0f03a63998377b82d587aead568928700b483b71483f53a2543c1eed3b9b4bafaf2d45e10ff99da3943aa77ee5c2e1a84a6f0578654ed576171308d00096ab25c93f244ba3104662f94298bcb6a7fc1bbd7f3bcac219b574146ef64bcf0cd1feac4b74bc276102781f62ca04eb2c6d085012e0d06a2c2412ab1be32698f304d73b0a533c64920d8b33530b65e16fce14b4d615f1bfb236fab226c9c90b9448490db19bfe5d529963885ff0938a175e112d72c6c4327cef3f44702cdd7f83ba63ddea8565b001d6a23fe51eef3471f828f5568faed064ab3e2390c86882b5a6ee8782f0ec6c37d98acf968e5bd613da46c36bbcf9f9f0707590a19c28a509b27a8bfbdfb730032a64bbba14471ebea2c398a506612b785922597ee76139034cee2ff3e74a4d1eeb471cdd7f43068bad806a26c8d86412362793c7823e1e500fc4495399b0011a6988eb76f3be8047a849951eb253ab9bc2767db88e5cad1100b1fe331b68968198a2e033211de0c804009f95c409a4e13210a30cf12d725bae62b6f6ca513b8e7d9ca8eb0cb9b646bb178847fbd01b44b4fe1e6b15b0ab7c2962874d5fdf70a455f03d2e4454163e89b20e6a1d8033870e1e177cc6d856457c3624dd76227bf6b5d4f0c2243dadbc11235dd58a95a272903af1ee6671b636d8f47e90bc36d5c6e070d9067e7d81d504f566d040c3f193aee5b48195fc5910ecc4926d1ebb121c2d451d3e99a2386a69104bb5c15171ddad5caf8e09d7a051b33c1db342cc4ec3d81e59ff6f19983a44ca609a48d86a053d635ea49fd90b0bbe55be786c28205cbdaee73e6ed957f8e93b8f2a9209555609d3346a1b044750221bf76b3d1c10504ab8aabb28c0aeb89fbacce4dbc40afc27e0b3b9e86a3913cbcc9bdd343045e0ce24e72c6f52b6852683fd9a61533e5994c107ca65a0d81d4ee5322c0f7168e4c2b32eba8a7fd783eec9ef68d7f20950595988bf1f857bbd3e184aca1ee2e7f5f71fd30a1075e59ef1103345a8835806dfc6a8a246dd79b7551c26a9b16a7628dc65ea8c930abfc7a7cf04f1e475664e1373cfe6ca0396d4c750d114081f22d33d7981eb536e60ed2d3ea5fa72cd046c5fb110fddfb14466f0d8c35b2658eb69742505e7d1d4161f1d4c1b9af6e373fc984baa35f81392bbfb56c201ae31e2b6fe2a22680802d51d9ef98e7ea830eefa07667879bc93921d4bf4f117fe0d328d1586ad150b58685eaaf61e704a2fc6ec854d19ac27a9d236d88a1fc310c23255187c88a5e2f7684a02098fa671d68a592e2096466c5b87191efc3163193123feeaaac57b4fbb7d78352b01f5ddf29563946a45c325c74c70e2af45167619af91d10088f0699695decdea4b41d43be14e0b0fc95c150e122d56dcb90ced5efa1ec926bcb5eb55287c855f215bf7dd0159a7cfffab8b8f897ecef49764928f3792c5e100c8a3fa3044361276a2b987f57624a509b2a119c12a678d25791101d182af43f1d44749055116496fd0ad78598cfd78bb185d0a5b02fa302f3559a5f952b9ca6e639da088792470e04ce9d76c431d94f0633b7c9893c3a72e01289b8a7573e60cec814bd2e2a553eb1728a9f629cc9324f93f7e440a11a39a437bf3ed1ed4ae684904cdc4fa3546e583adcd55d48a8d0ad228e4eca534dfa0cfe35dddc7b817f9a01d9da79ea45cc49915a2935d6226b7e0b99417c55e29c070eca6c394f3994026fdffe5382a05ef7e348dfa260c790b9549d5ee560c7a83c1ef7f413a46885df2a15f14d81d83d4fa51c709d5ddd62a34b6a8d531e04c48920e525e98875cf188e8c800876a3186d081472c3e3c5180eb3f8b1dfe1b2bd18f271262211601f7b0a7ed0620c5dfdfcb981ba7a33b8b10af6995bf36b19889b9938a460659e2454ab63e1e0d423ad0d984d4d3c0874aba21623d301d12f606fc3296c427dd4aa1bd6dce71c0f8b31b1c2be774382b8db3f518ffa883b97da0dadd6dfee0a647ffaf1e89515ca8a6adeee729e35a3a3b43cbd4826397b4b7b627e41ca18eede322eada538090e4b924104dfa67909d1a4d4941ac4448d911e21c6068c8d094fe3359bc8a26ed66962bf618c6c93f9177d5f93527789b89ba3d68a9e1b467d3616e26af40dcf7e8e71e6fd1b88aa1340ebea1c0e8ed4400f13904be8e90cb4af01bb7be6b8843da9f878948cd705f2db20ff131ed1cfa4695d6352b78a2d9bee47e45f19a30cbfeeb561853cbb0ccd307a5c95c9824001f9e74ca4cbe5ea9448f3ad32cba1087603c3627ec1c11ce2d2a8692240d650735dd67199fcf92bfac80b38d535d4c40063632bb63ce185a3c5aa992b7a28b761f0683010d3a3c058a07d8dfcdf7e9714c9c639cfb4de8a8ee7bf4efff8c5fa8ded4d8d5bf9e00efe6edc2ffc87b886a071b6e65465192361c76ef37897f7eeb2d13fc3ed6fb02bf2b9b7dc973d4a5239cef90bf5e60fbf5d6fe601963d3d39fc92451d5fccfe61d8873bb2ec8e386d2752f7d3d64f3f01b90f6a8b13ec9fdef7842f7d7efeabb2e6dc7a2279860fb434b71fb32eb73f0f199ed35e4832a03f1409b38eb0b76df9f885aa7d741f777b24feac604b4dee56aa9412461c5784182c78129ade4459596463f7f53eac7e8eb8b15c2747a3dd1a848c6a5ce7974a54d6ded7b739ada83925ab0a1c7cf2cd2a1871513c77fc80f8ac1c8b12ee425bbbeaad522e8670902a42a597f04db17ce3394e0a1423ec869dc9de9f790da539d84cdf51c4b98c5803a02c6141d1b42c2fd896e4cdc9b9fc2854f5d67da69304f70bc3c4dbafa5354a0b896c60de031a0fceb8d8c1c2bebb7ebf1feb0d549dcaac1ed75ab7e4e6ca0842ba63a95fb17ec222dc836f37171981b6b15f6680a67bbead684b46ab654d35843f036b0a51c24a73d0ad34104b473c59cd7f6083cf94079024b5f18d50b9515dcfb63b4440189659718bd1e288348ecc7df7d80a9e8f65a8611049fd9d8ce67e831283a524fa8893c9d0d907c05fb8ea777c015a1b707008dc17fd1c2646fe9e85df960eb1e8c00e6dec6603ad15190860e1f1cf1f61a332f2df442b7a48e59fb5914ac307fcccd938ab4eceafd708bf0fde6d8017ac1d2e00cdc3c240978173f357d0f80847edc43d1498c1368e8251fe4ad885606a0078415e17dd3dd3ffd23d1385c5a02fea1688941bd1dd7e547001ae785bed2b7d02b117b4b665ce3306a37af2017009b41e56ea7b23e8a7c0f8ec0766a690f0d8dfb105156ef8042413af70ae21d1e75bbc6cf8df1017a144d736241c745774935344ee98b7c964c2e522ef4d2f71f7560704a505b14fb90e5b1fabe63b43a17c3c242dd49c46135d9cc8e1e118d401b64410fdb89d04b851870846e68db8351733ae52d52bac06fdb52efc13c8ff37187a755dabfc3018f6efc4fa1fd06aaf544f0f2333083af3dc6f335a957824187d3988ba57e605e43f68deca71724add64be8cfb852099ef7382343ffc9fd367e881f3b0aaf60f7c4d7d5c7b38f687be5027a7d675b827e4aab8856986bf77858fb039a83dc9cc4b10d281a01b026aa81cfd5ba674039c9cc6301ea3a7f32a4f28bfb059bc530d5ffaf4192a0a9eb0fd1137380a195ee1f3ffe308843862a6bf97e4a071f00791d7e7ad63f83ec2b22e3ddf41437fe96f1f32068481d3108e6b47849641eb7dc8e969a596fa374e1919517129d9d0d6d78056508c39e0df6b9eeb2982c2d98033193aeea3ebb47d7ca0663caddb4dcbd63fa8f6fe4e64427bf2ffdafdf4b89bc2625f4e9fb31e01e894321a337e3cd977aa1dc8fc5fe3fba2d504dff188d77e898ab92cf566377ff837fb106b8bf986c903053ec44f263acd65661423d30c33821061f32e594110ff80588f33315b06f6bb1893c25fc0a031cd0cbd3370f532c5572e06dd70ce7f3e9a1f0595c6ec10f5de3b2de433dd9c99e5cf1c6ee3762f30dca011f14be2629f56322724fb5f885e3785914a1bcbd017bef734471916ea0fc99cf8fe21fd6120ba7fcc1466ed1d7d570ae0e4e5dbc7602331de15d9ecdc821d65d79da065a8744cd47f37eec88287f9a1f3e8db1fcc7c88913afafdc66298b6f126fd1bee2fc3b0735baaaba2a324d607ba179b396e830e238d8d884e8d7bc018e1d731170dd447bca53a2b5a7bbaa8fbcee62bfaac97eaa96156d67395cd5140e54c2d2a2af27a7e2441758528d65e5ec7de319fcdab11ed2500de72962e81ea3d297683045dfcd54856393162bd9fd2a25313003e10d822011f3d30df0994a752c692c6233f60b2c8b3c0529bfcc878685bdfd1aeb6028971a0b16b86dfd40f6f34943cb19d1230e2ee8be4b346748377b4dab8de3a2456c22e06e0c0c72a54207fb0896fd02538d305e53d20b0ec8b7c8d506cfbace3c43a778f9e60b076ee3f6769cb070ddb077bce84044b24d69a8bdfd91738f48b62d1cdac43f20f7f56ec55794515eed0367dbac7d45f2afb47c04491455d41c5be1b78c410bdf11d4ad2a53b4c854f9b111babb072697727aa0ce0fb907f1d500f8436b57fae4511fb63eb9f84bb327cbdc939476cc577c11e6b4fb6c02f5c122e541003e2feef34b763424b3f66b9600dccd931e38d2475b0edb929f1288515b644abc3327e2136944d835814498caf7553c3f724d4c102922a772db3536562766d0b06b8453ad78d139d426ea5f882d11d24bdd728e1432ed77c77f3fd55b740c29187dd6f2a12d94a1a9f096f5cdbd00d754f24a23efa621338ff70c9aca84e4c8673e6c20c630d3e315c223fdf15ec03d1968a67b7593d1fd6676c5b586a40352270eef173badbc307a6be314255c84414134650469a960f0978dd09b711ad5d1dfedadfe916503e9c7d2b31b170012a7192916e54d541ac5ca57a4571c5aecdb0894084d0b95fd4673c630f7b5d55681174526928b8a9159fecd90c6d8e50c5f2285c1a10de52e5a3b8d273e075bdf8eeaceb75da6ad8120a90e252d9880271d9b61469d08c8fa02b4ce64a3379c1bbfe1fba817488ae9373e425e0f1974a9147efa9ead2d4105662eda3737edea4c9a63d17a8e567aac50897a0ecd72906c28dfc2e41a5df5dd61c17d7f6a60604fad402c8cab492d26f551585c095ce39c17e0fc598a3140f34aaeb6e486b2ac5d458a2f1cd1c54b503d34623dee4b93c9dc02fcfbab72f621cb0e90256ad537da4a4afdb38ef218922723d1551b8bd581027ac79c5578695975b096d0200d48b0ff1aa6548badd57fadbc6b60648a4f8f4ac2699d98343e7dd3d0632c2a0cf7a6b3813a0e21e8eccab9cda6a91dc71c3c42f3c2f564d9a998b107b93211ba01ef7abe7bd8e991bb15daa16745427db628ce4cb393599bde748c1731260609985c0e1b3dfa512a42f051a7510dc3e26a387ecdf236569ea6cb1b469a72336489fa5e9576c3d93fbb9d7e4b44216865aa75afd9c27c3fa497910acb90d1e36d3e958fad04fc2a4f33e25148ae183579c210fa8bed9aed5661943f52dbb362e25b036aee90ea2a23918228e466e645d878670c81e4b1f2669c1cacb0585afb9db3b64b7ca2f948a48d8204fbfdb2994e1e87a7d2a8a7bc49e553f844b86cefb2a414972f2a95b6b7dd650b18298de4122a258414464a5df39e162e4cd4d4de5aa5340605813ebe403367d7f157a93793f595f53aca8e2a4d7ee4074012a02cb5323a417d8eeb494eb0cc17033de1d08d65c4a4f9ddde4cbf28698699cc94163a910f1de124935da8a45019f05898fd4fc183863573b1f0d67572e29f8b97d6f2472f8b711b7cf173ad7f50d3a810e506a2ff93e270281a5ef40dde744ca6ca4b71e811c76d801457935455bdcd5f855c3d29d2a7d3b9d78b5c48b88df16362af28d795643c440b4be67229f7ba80c4bb4881cd168195b3a196bf2e5531c60d60dc6793fcf3866d691cf8cae131be330ac3c01830c027ccb88d1527d6425fc37f48b27e8afc958e277c4113910103c7d274b61f22b4a04f7b0c975385979fb794c20dc9b2ca5f8cd90be81aafc8448fe77ab128f429740b51918408ce4b4f3c05423e52a0347bf44ef670812a7a9c1a9bb93ad9854f1a5af49d0bff294229004042f513808a35fdc15d07533ec9f551ab32623d2dbfbe12029110a141e179e4d2efbbbd6e4212fa114b78ee6ad47d68534d195204d625040354bcfc573e98073d8bdf549a6d3485c382716bcfe1ac369898258ab299ab2c1d1ec2f3d79482903038841b46663e68ca4d6a4789f0ba25598530bdafd0783c2bbcff336a54a995f8f67d092546acc1150fcdc49b036bda364c1042cd0a88a19b9aa890001178a8b63edc7b773707e26ffca4bebd34c8cde6362a4afdf31eefb46bd79db3f49ade0b7a5a9e134ec2c5d0104d3848607b04818f2d42c7f07749c4d571d550c5023442af41bbb78512eab19f8f7674a126fd20467509033cd228564d178596f90d0c55260a20e153a6f5c6da7e1cdcdbc5a192200bfb1715311b7ce8f379c802673459710d3d2682b1ace45be05d605fed6552f7f77a6de7f6eeab7da6e7ce8f78a69d67a3fc478bfe9d7fece3df9f8a13e95e0cd73cbd49b3e371230cde67bf52a34799f978baf0b5ab12e5ffe67c2f027a8cb8d59e610e749a61eafd76f165cfc1649ef71f48647b7061e1669de833b89d8b5cf95e3384ec121a1b741b13b7430ed13e5307ac34fa1fdde82ed4f34bb3164292f40a57dba8d8a34fb76816e746a2c1b85197dc3081273fa7d23c6d296ab2218b76353f59388064cca506d4d95e486ebadab684e182f53a84d851e0facac28a7863c485c039e862ef22a152e74c17b250a9a10db26196755c0938a193ffbcc80e62d93468ea1998481dc5062a3d89ec69a2ebd7e3cc9d739145bad5a421896eb7db228eba4d5869885a212a382dec9184fb2bb12f43d4a175414e6eaf22f5c77ddb3488e539a920284462ead356142bcf3041cddcbeb22cf6a8f9c5261f769f22b5cee8f77b3a0580b44a8f0a72ffdf1251abc5ed4926ffc98851e150de81ff441238c21a8bae46611d933c3fbb94fc198502fc0d639b45bb04137fee531b1cf9ea4898b52fe3ac9f0ef9004831cd34490ba5b957070f302c8fcb117076f67ae27d846de2ebf9bd1ac7aba319d39f3b362c774a666225f58652f7dbede7de62e5d5d0858dbd5c90be6aa5dda51889584019af8a9ffdf3023f686ce85acf977a6e2cae426bfbc516c432f6cfe1bcaae638808e992e1bb47b3fcab9b00c75a8173c74f589730f38f4d6087b5440f413f12ca5612e095125ac4b300e424008c16f6fe99898d129bf596bd25b17060ffb061b3b00d5c43454bb21f063c7b03d062cbcd6be4ea4a86b8deab28c7bfeb87be73c8a8959890216864a6e34a58ee9ce02d5999ffc9decef9cfffefba7afaf3d92b490efd4a83c3483591a2748b66228cbdd1e7420fd46f1d26b0d8dbd83601e15a5d76087b89fddd684d211da4fc42a611ddac22dcb3cf6d6fea51dfc20f0cd3c3eba248ea9967fd84c248ef6b434b16e0b50447329e1ed4ad799b1a0e1e89dd434cecae57d6949670520c67c9398ec2220a77efa2e1893420bfd9afb9d50b2d8e5173236941285a45670292538054ee4fdc9effbad56206d54a78ff9d12916ea0f1a44f8169e209a0a3ee0fa71fa1153ab5ac683770a1d48e59da4ff59a8feb9c364f7a99d25aa57cbfa45b76a010cede3ec6219965f0550bc1443f8c6010a8298339343a0da30712d740cbc81d24fd937674451a1993bbd75aea251f0b43e76a4333aff96ac2b34df28ec2a9fcba1d5a87884ee61645558b3074812f4f53b0884ec568f5d8517cd01cade1e903f784c6e78869bce9e2093253f6407452ec3ecd9657efd68191d648124c2392f0597022c91ff866a946a2a1fe2cf37d61b99ec177a85efc6cda315b1b3050bd70186123a8d1695e5d927ac39733d84024910da51c09a72aaf082471980db826fa885d6aafad98aa269a65cab6b0b2f992940c0fc6fc1fb6ba1e1d79ca2bb15c0580bff044d6076722dcf7f8cf5fba79411e6319ac2686fa8d19298074af70b8bf8a241076ce88b093cdc27699b66127a3031be25b6d82f0543a5006033d02d5678a3870635f651351448ad649e90092cbbca12fa80262b42a9c2903454e30d56a1aa586d20f2494347a5738cbb5aecca0d6b226a7be8a595337738b70ffbf523f47c815732b0c02682e417c3bd3f3d5984cc04daee30c98b5d497234c12f598c79040054874b3ea8e01d91b50080a498c2025cb49d590dd207a6ccc7fe26321814095f28518458d7016925b4b39413882ea1fb1b2580e58fa7e69bd549e1b0a1648440ede71cca2d68f0fba44283f498c3c5acbbd885a187888050677f87728a62493270134ee7ab158ea1da10e82f243908e25e8908885a2746b8e448d3a02c9c3d0026f36780c81cf052efd83b1c9ffff815fad11688a308ce38e52c632eb9e4cb59c26e22e8a01d8cba3148c1f7af3ba17597cd95a3c9b78865fdff3ad553d340782be52f929d83f6f4d6044ae82731e51ddc9ef43bab1e4ae9a22bdae4e98c7bd3de47efaf2dfb54719408e404da99e5c834b5f3b8183fa553fefccb5f2bfaca3a4e5338a7810b4fc1d0f97a13403c7599a607bc7bd121beb627c83fd37283487df756ea7d38b8db9fe57ba7f9ebcc6cab3ecc7d70127a0f0e80a111b4309ee7a6118085cd548e8b797bfe2c4570a9ebdbfc2c44cc8852f45d3f7a7e6778b553f42a24c6c299b5fd959a111cb9c97b73c23a13dd7e22b980c645e40cdbe23d0ed56dbc18b95d03ae0e698162182c9f6ba881a4849681c3ada9fb6c906e7eecfa39e3d43780852e42121622f84235255fb3f7db690db4c3d1fb8d86e8b8c4eed38e05864fac37d926c0a29c4aaee6f316e37bdedaf8eb1fd3d957b9d0a24197a3539bc9bc76bb314bcff9e84db94bfded0c195e28254e6042e6a563e8bd12793134a919d48d012fce325c9ba590aaef758e70bd43757a60dc65a6a9138987008bc14718be687ba42bdb10654a00bc4a644f65e00a61238e892a70ee5d56d89b1345789a6f7e5000c82e5f9763385e62e1cb0864e1a2e89a7be3073adf92c4e8854a1b336c8c5414a768f3ab4a9eba29eeaefc1f221ef954297e597ba3615d3e3792a332ac892a0899fb259039fba984484cd435c1d8bb01d4bd3e63581dc277440eb28be7f1966315312ed1500e9b07a39ba5e6ad63ffc146f1fcda7aecb8645d2a29a7a4128243041bc1bef1618de2f9746160f47c28f6852e466e2558ec0f27607e9ace220347b6ee85d443c8865c1c782c88f2cee8556f6d37b7a1de249d3a3c52ad60b4c7fc83fb3124a6c7da2a1ecc2e9a1f0a17a5568287e0404c60c9b108cb381325606fc88043352579b5e3c11e84478e8ee72d9cd989e4ff6e3aa1234f7d01b6cdc03fc7c7e1ce17bee16878301c8d2de0f64de9bedddfa953e85570aabc00df6c5e8844c4ae92b82b2001c16a60d582f960e73514e19a1d7d97fb1cc82ebeb0acac2c1960c8144a571076c28caaed6cbf89f803a96254ee3588fb6c5169025046de87a04460be98b2f22da176a460464229239549091ff904654dac96fee466eccb16247b4bf75b0e4fd1feac1a67548d055705e75f82921562f76c5d3fe9823c76702c33a8b0dde91b4e9de9541882b6dccb1d4bc1fc8bca7d7bbf5e930f94caca5a7d455be1ff3a05b3d3b61d6c5736b9ee8451623d443bf977f558f425bd6b9ffd58cabef15e5f6e491df483d4e1fa493d5415ddfe79e1de7b0a0030038ce77a30822d93ff8605625319f6d363f81d237836a2ea2eef526c53c92d7a43100f0184195fe42e0636c0d9e7dcba9f69d905094cf23510a63003a30d874f7aa670566d89ecfadfd4622987d5e4ba5223828cbb662ec7c2fbc881f427c354261d028688879378dd18a404fd82784e63b983968f3a8af2d76ac10c66f8c8cf336bcb4292ae1bcf565d0ac5760f324bf0a78084515c034b32c8d71fa53f670d0cd479c43f16aabce9febb19edab9b60eed8de41cd5989a2ffbb5bbf1e32936f805e0e3845abeadb68c05ab6b8205c860b73f76572c45da3cccf247d89249ba60d3c72cde0ab3ff9ee1b025b3948adbc6e02179905eb6365be80ee3954ef3a69bdebee464ad0f1a49584dbc88c496ce703b18436259bee9eeba3596a13ffbdc222103788ab1d322749016a8879f4840f52841f018f87f09efa5dcecd0e34932d73fb92ba4b9580261d82c63eca0c56b0f1a6d25d01b70f661fc0f7d7ddc84f14d0addddb46d8cda3aaf5c2a47152135280cdc9bc45dcaccd52714e209f848bf84d1a43c37b8fdf9f834334f65e4ffd1f6f18bd3a0479d008bd179c9ef18c8baca0a2dd1e4d79d85858304dd3b61aa7d5a69b3910780cb355f8d01f33e13f4335a95a21a6729857bf1b2397d9e2ad270739354cf8c06a3137d6fb74b872ec25ed45534881cd5818e044ebf5b540210913abd90a0b29c28b69d1856f01e01eaea889ac3f67e058ea8d83d5395e3ca98a89da33eec58ef9ae016956c3c913a647632e87bc54810966d85442c0050325cc681b9122a2fc5b842860491fec9eb277a0dd3ad7958d2ecf91bca76bc0374bea8a1caef412040258e846f2ae86aae6edf2c96c7f6df29b9bbf1fe39249cc791304b4b8a886adcf68af96bf853c6df5a1c3d48289ea36041113896880178dcfaf39f10422de08a79af3272a95621dbe26231e5e5e9101d632291231d127fcc4481f6238640038bd525ba5dc0e18b57661569bee11712711cd0bc4a70394f55207e7464c7caad5888e680baffcb2810c7d1e95314e322b2dea7e024132565c788b293162ed4b586cf327b7c1f3684161d21186f0e3b87f160517884e9f2c9340a8f485ffef92129024c15cfc82226102e74560bd567fe2559b679526acf95f6b8a75a0601b81a91dc6382f59f912bb40211609d5d8cc201b177e7695dd2a8b5bc5fa7c19cd92c024107dce6a004d031f217d68d86fe4685c850abc2fd0f34a6b6bcc654317b0b125be78b863494ddbf6519b245f927a4673869f7213c1458717c6fda6b884862e74b6523de7af15634197513fa2b4706da848ab6bf8280de1fcf7dca7d7b127075bea38de190a33d7812ad6bb09f05dd500f75fa9a1d3eb76a31262f690d359d82e437d4b752c4c904872144c847f0c7cec02ceef18bbcffea4f7b79a966c6b566bd0d5278d48ba69a2e8e286422d8df8647a78755e743e06bbc19cec962033237b3134de52fbdb470be8c6f2de7fbcb0619fd679b4cffd9c6f830bf0f2fbe19fbedf0d8f9ce85187fe26d90b3af33de3c6b7d269c1a6b474088596834cedd819c29566442d925481809a5c6b4177062179634d4c22ae52cf659f28ce0b52d79ead2b242e74c2d7e4e30ed7839639a4222cd41b90e166592ca0659449e6a6634cdd459b24487757dd2c21c77f6df3ec3bf3ec2e2d387b0f9f7115e3e834e4b58fb3143e564a689acb3926e42062ad3d4a4e6b0a455dd81427059aeb8977ce9f16e3f1b51cca924d96924abacd385d081cc1435d724ac695957a0094ad3d1b2ab3ee3d0bace40117453a2998db18a357bca834bb394230b9b63192b760725a0399956ba348d452cd889640fe592d9a7794a91810db18c25bb831c109c4c539ac94ad6e92454a033859a6b0c2b5ad725d004d954b4540293ccf7109555739550b61be7fe4f4eba0919a84c53939ac3925675070a4137194d6c8e9558b333a880e69434a791acb24e17424750e3bc1aae00be0f86ccf0699a5264b349ac63c9ba5aa0074dcd764f39ded9f7248184fbc73575917b750977d619b7d44d6eaa8722a0a8ad3148bbdcdf5cafebdcab4bb8b3ceb8a56e7253dde1ca2a742c21ee650c6ecc1c6e9299b89629b83393887f64ade69b7fd83bc9e214912771c7ffc63f1eafe0086b5bc28f082e5ec50dff10d7e8bc8fdbfd092eb178074f585b126e05b0b615f7fe437ca3f33aeefb27e147a3e5a5f873c6c4fbb8ed5ff089897770cb7fe10743dee166c6ecd51490eddb390e4ace73c4fc1f6b8b8cf9827d49277cf18a93cdd959fde0010f925ded112bd606cf3044294d4948489649e5bb3486bd5ab11fa6877828302ed88ef0f3ea1012a9c89bf504ec3ac41e20554768931d9f4d430457f5ee328c897c4a044650281a3c60d1a0effd447f59460569a605c6fde96aa58ba462391e6281c55872fa8c1e5ea89deb10c3b21e8a41a09d52a1cb95c9bbc2b30fef0a0cde320669cee13566186bed2ec1385ac7208d6cdfebe27d0fee09dfa9e73bd6594a84a343f41b6369736d6285a7da9cb5c97bd1a77b56ddfeea0293658c6a7ab8a822d0d928237b5376fcb37fbec6101e2be50f2ce0dc4e9a390a62ec6793e8a3ddb5a52f6a04e5a24611d3526c86af495d23553a16564e4c47e0896825eff448b00616d7d47323d4cd928b92ca942754a2e3fa3e0025dc855e2cf29b51aba72f549d44c1f737bb9c2c5594a073c1673a9dc41777b3bea2e5b7e01f8cffc4dc2344dd65b5ee655a95a741f750c53c74b722e582528b03d3637f78f320a84c0eea6dfabc1b095776570d3546aa9f695138ee05aaac3ef8427320ea2d225e279fd407b68ba4e017de3b4957a877b1080c2f051a1c6ba5551048cb481dc419bc348900023f1a70d2ce4fdf84b8eb9df6510e60c22b079c4043120119812f49dc52926d915c2b2169114b646ed29e0609211eccead0076a416c002b2fa01770a7fa0615ac3aaa63748516f17f0adb09130a980a3838aeca18bc5dd77954a53e9a51be0a3df0aebace697fbfda8fe68b65ccbacddefdcf601da65de797beb5423e9a2f5d12d2757eed2c530fbcf9ffcc5b2bd4986cf0458d243576d4f8d11da57ddc9ae961c6872e70edd264ac65ecb3df0c2b8d308d6fbcb75b9155cc26b355893443a58b31a7f3a75599fb99e50db65579976c55da23a4afb6a85a6aab12098d1b62d2194d74b7b3a60b86c12a366798babdd9ea9756b42a9192eec3eeed06fbf0bdddcca042c50c0194e18332c69421a68c31baa3cfac4b57e1f06dfed9ec3ebf024ae424d10f61cf6bcd6a7f6cd17b20d2acf6a7525b43a544d622fdb5b224a5f740a42246943c8c2fd1bf50cefdbf507873ad94087bb652229c2dcad67befa3f7655f53de2532fb2db942352c3dfae3ca9268a88665139c9344951261fc9277c9d1e37fa1cfe10fbd4747f86d32fac2e13b9917dba39cfba29cfba2f09de0fc4993626c33d89add274a193fce0edd3fb22f2cef926cbd24fc7368ca6f72add03c4ac2e13bc9b92fc24119cb218cbf7e964393063d96b5a30c1acdbfd1bf59223bab2e9b4c5e5aa593ca3e4868565d299b31a234be343dc2bcd336627889a1274615e3bb6f6ee8cdf5fde6c669955aa14fe9055e3aea2cc16c86e52c4d01d304992e3bc4a0bd048004448aa040aed140fb73fbecca9f245bf993e623e54fba7d76c5f09dc4f06dd70a11a28ab7cfb7cfaed8c2dfbf13bf471eebf44be7579cc5940747acb2455569be4d6687f287def7dd3ebb3c9974abfc921e83eefaa4ee3d5ca64386b8cfac0bc98f85e0a77456892b4539ee1ebcb9f3786d4c4d95272b8d6294b31c83f2e806e247aad62659fb82b1f0537ce34cdaa4200d13001105036cd077809d04e80691d66b2608d513021d700446741b33ad0292259e4075d37ce145172a480117345b74d3a0a09b468b6e9a2c684e1047b5b30faa61d9048661f6fe1518be478ac49c556cce07fae8eba32fdc17b93efa8a2f843ffa8a1fcd9686b3f3a3af88ef7f1fcdbef4b42693f53f2b3f5ca9a4b53cca2c27bef199f7387bb1f7bf5dcad670ae4275f39344f8a32fffe86b562a44530023badba34cb24799cc479af4e768d21f2c148045cda4b14963b02b76982efa933dca247b9489962068a2e0497f729228d664927a3497feb119efe370a6b59fb53cdad9ec2988f112ae56a852a248672beb37f9615b338dc63e866996f28abd334cbdb791c6eced66675428769dad46422e2197f8c8eb0fe524518cb7af9428563b9b579542799486278d75391daed6c3980a7761ecda8c3f0fbdc7577ed867f6976492c7db673af384132350a17193fe5cfae596645bf4f466d86799edfdb2bdfdf43cfbc13e10663328619e857d36cb608fedfd037cf46fb5b7cf343f9616e67d1f8865f47eb0fc2118bb59fc38f9d10cfb8bfb0afb68fe6886658fd29e1863bee7655b675eb0712237be11e360ef8fc49cd50d4bdc8a0600432278234b1a5d9233b11fb8af3d941450a7a8544a7c94782a37292acf7e88c5ba4679f643eeee754a023c3729cc0727c0a287133a28f199bdd7c3014a7a9c086c7ad64b05783c691f61643c651713f9f06841135dfaa7ad364de41e22a0e32b25f169f38148691b1080b9e1e5026a1db580bcc7739336089e0d088874cb1b0e1675b4f083046614e008160d267cd122c7e647005f0837365b70c084b0e504590a5bba1b8709299070d1dd3925a460730c2055595c701b264ac0305aa5b78ba6b100cd966e1a22ba692ad0365e9eb0f1d28566886e1a217ed050a0bb939e38f9e40df67f73ec9344fff7f533dfc6cb0f9a0948404b10516880c8d29386668a8d07c8687ce377cbc9dbcdc603583acaa4cc03f3fe9267f1916e1b0ff858628d25ccb0b7bf36cb2a78b3ab70ae7f731a5fc2a73d4fcaf08d5dc2609b2e46d41061d3c547171edd3518982140960b588002361d38e24193a569f5d2257abf9f1fbe6dca58b6d5d2283163eae59957ad27417bffcaaadafa184bcf28ff0d53fe244182eeeed136493c914406922022891cba553e74f4721f7af272157695d2276f3e7fda6e9a1fba697ce8a6e981c70e56a620510211708056e5e5d1f11586f114a1cd87cdef217600432e3cd02aad34276f2e7f89cef8e89623061b24b2c81fd84356c59cd4d0028d378de7d96c4cba6964d348a16e9a8ea6a3194233e48e3b66ea98a923d2c0984b4bf3a4774b867e5a4d805a4b908a94b098a167ae68a1674a3053848117bef131b06383012246f4002f790a158d80626344ca8801747b9e04a2c99beb3d2a976455f23c095495b22dc22e4f0281718110ba3d49b34f4fb3c0025eb28d05ac74871ead66d95f1a14e463b3050536450c4002a12e79fe937830c5f773efb323f452eab1d9d2753b2be64be6d36832f648f7eb7ea8f32ca15099ff4ecf7e2aec2e7894e577954e11558e8e5feb39ee954a8cd155465c55c4553a523ac73b478a449dd0cbe5e4142982e9928d5d70becce7774790384dc63c2f85ca4f63b1304dfeac1ccb661da6c9d80a77f73fef6bb6ae4be938919cbcc5146c885341b912254e45c789b84d4a8755f865f4e6bfd9a7b9ccb79b95fd7579b514c85df06871b95a8bc32ae5833c5d94165152e7337bffa3defdafca5c65be5f6e89bbe091266319fcfad96796055bd1db6398df57cda75e71a6f826674c93d42b8d5641b779ba3068e9954b8f1dc3e2cc7b8fca8c3b9afce96832469331a79e57fad3d616ce49222966b2348bf58a81b8a6052e4d0071c40608158008a1fb87357e28e3872b9c855fe52c95b3f04c1126f460b32442f704faa186279512c5ae739bbd028e8b704e12e516ce0ee57e5617bec92b8f28ac064c0f45b8c70e363c84a184fba2595d93c6aa92cd606bd29f1a356c32571750c436991dca49a218b1e3ccfa5f52fce8ebfe0cf2585f56a98dde9c52c96b515e2d0dfa4993622871d7ca68fede869dfefc5969cc257e3a6325ced4c7424984729f955f9f954b998a27e6d743cfe3a4313086278da9306ad298e39b9c7bdcf288dd7966754c3df0b63c1e4df993946d912d9af68856e9a43fd916dd97df24dba21e3c39393aee13c3ed630a9a7b1f533cadbee7c99aac7da030df8af3dc3e671eac4489cfacebc4af16e623063ea414c9968c295048531540492fbb5628683e4ed65ef36ff25ff3ff8218bb80b0007145cfc66e21bec91a715f73046de8367b9c2fa34b5caa0a72f9b5b90b65337b7f26a3332afb0f4fcfd6bc3ace0e29d5bf62af90bd7f004ab4642f0dc2b6a85ef0677aa085a204ca0844402a105298a94112e166670e9b8f88951e423e5cf727bfc62ac015dc2cf2c4be9ea8c8128bb50a4960135e0943c77110c98300c346038a40e3072c68f01044b541b865279834e90493725fe494e86e3bc1242c617682303b416c27886d722f2cdd17d809da092675c4b15902c35953fed03c66a15736d9d780b08cfd9d5958aef6ce6095c232f8d1d8a4b10a56f0b35d9a15fcfc6110c39e36c12a39992bbdd50bc696a82761dfbf0d276b19276b394676565d33a3991146ba5608e36ae9fd59959e26f1cce893447866942b7efc1403fd3dc232cba099d1acd449f8836b5836c1ffa13cb2b1d6edd259b541322664ab980bbafb15b127976e577aad385f56d0339551efcb2df1886f3878cabf1ff83689cb5582367806eddfe82be2c7587e61ac52da0eb1cbc91b8bd5e1ce67f3bd866593297f3c0f149af2c706d9972daa4a3c623550f090000f20a850d12828b589ca2b7cdb6781800089b406fa570b622c73ef3b3a0e8379cc347604c9ec2f52473149c1d2f8c6088038df558332e078d5ba4d37a0d1dd9efde48dc562b16ef0a1bbc3b7b13e4b0f577973f7096e40e1da283ef8f4ec105170ba6f368946abfc759e1c0cd66082e2a5bb3114af4d5088f4943f4d4f74e81e8a0003e2928d659ccfa7343dd8bca691297de9e6a1bb7fd4ecd0d77e4a44aaa5d8a3b61b58270cf6d59a5e4768495ce01b33786b05212347072963420729734337120bdd311060862071e4749038987490388ae8207184d141e2f0410721a342072103d441c8c43a0899b08390e1d241c8c0e0841d4e50e60520657c20c34577f78905091c7577cfac208709a94b371bdd3c4052f57c7af38f0d5658ca03e887470f8f1d1e383c5ae0d1c3061e4348613ad28126073caa697c230e903146471adfd83e42cf65cd50778d931a665e5034c50d13d11bbdfa51d3240662d8877fe65f7bef1213518d690807d350adbbdb5947aaeebd9965ac1e7a354c3f21b92664e66bf8a1060538eb6f569641eb2a9f954aba8469f78c0b78504dbc709171d5c01586fdfbb96a7ecfa4a08689104f7525b9869ce50ad2a48d2667b8ca95a8f09b9a2ca0490e98fafd0f66330ccbfbb6080826466c214573400b031020f4c5fd25a3d09b42551a87358a889f1e59729b91c85064f57364c97d566e6429dab01cf258adcf8f859eb13caa2e5bf4483533f0aed29323a69878a2e8eec7e050d1ec322c18acc8c89584b4e4098cb56488204c3c4033409a0ed30c3998173cfa015b70b48ed841d2d5b06cd2b9aa4a243afe8519e59df4bafa1bcd4f73339b42c5ffc20cebaecd3229d4d1e80cacbdbceb7c663d3a69d30c5cba632a7c1bcac39cbca96cf689abd2ac4a33ef936c110c06833d71721f6662195d0a7ef56f13c66306043128306104129c2146890148e8ee26a10914ec8c5087aaf66303b2813811471620345657c1e5fc280304c48d12ccd8c93b2fdb4ece4e096d4aa2834e12268915d9da0bbbb71b976e8cdf839992f81026c9dda624af243da624deddb0ff599210baedac5258994ea3c3f49b9064e179d24b7723d9d2f829121dbcee4672d4b09acc7e4876ba1b89b70c74846f83c9904577c3bc744faaa5730d93618a0c418d6132a01acb4042f791326d3a32e648982360ba1b049a235b1a85361df1da6d9ef2a77b660440aebdd787139311312623b51e130c5a4c4654353324311999b2d35d93a4bb064977cd901a196a33b12504a2bbe6484e4ebebf54adecfb3a10ae9874dec03762d8fd0f859a15e8fc2fe4e972f2f682f7782a276f2fe0584179f4658ee356ba2f3362ac718236e56409ab94535a2ff43c82d276d718e9ae2952b30202681c60abcd1a0659acf91567f4d3167552d65da3d35d9353b3020d269c283d2606b18c68648a44daffb332ec1566938f930f85860634ab15cc8c11868a0a26a0d04256c159a6339f71c244a4892e6284fa1a3338009165a6015f835313bbbbeb98d4c033dffb98f45a33249850748861425d81da407763eaff85d883504add5d429b502d136ac7934b30130a05d3cd0f6ec62ce9ae5975d7a84c37b91a22606682689a134c336e74175d9051230210244c3144c9d7621a23189248a1ba6b8644dbb5b3b6810986ee5ead5635a91d7f9cab5ae1948f849348dd3528a1992a6880e8aeb999b688c55a329b19d51f2c6b4add9dff8ec3e057de7cfe632c7576925c480db1751454d44262626ac1089bc9c3e8d6c00c2a7423a04d0ed44d5cb7cfaef7645509bb8432b64df21d3c985800405b9af464ba323d2a6a12667a41138da9b1a4cdb4615ac1d5b44a6158d2ee2f59cfb348b95ef08756a9490535babb67f4e8a9a4d1bfbfa4bae04fbc602c85fa2b3f7c14b1ff1205f151b4c9646c7efebee5319625cecfb628d779a3fe5ece8f3178c516cdb7d9a5478a8149f395804ca09da5d32a55455a7c57e56e3949e431e7bf791ae1b8acc7209757e93d8d4fef7fab188823c6a0f739ad3732398b81385219c5f247ca6287531e9555d0ca3c59cb56455b1eebcdcce538532131100bf186c082863b31b299e66c8b5ec816853619cd493130e85a21a4a934b11dca180c9a8fb3b32e71e52451adff592d8a14799c55aa92bcae73f993e2ce31add2d9cc76dd0d0a70f7f1e9915906b9ca553e3d3d375a4bfa5c89bb7b029cc5727f8f7e372bb3b5eb3ac7995610574b1f29cc7452d00afd25eab5be7f27b44ae50761dba6005c117f263118fbd05501f0e96e619bcc0eb9db6432cb20a41e21f868757748e7675b27fda9c940fba4522793fef43099806820aec29bdd6b62e8ae81a1bb7fd4bcd05de342778d90a7f73e36fd48c3f4434c37f1e79c248aa177a9b4510ff765fb6149636e3d70da5b4d8637e36237a31eca6aff71e7f829feeb441eefcd4c1db9ab1ce5d1654d76c14fc96375cd9f7549c69e87e9129df2e6821936c4d4800e1edd261e6faab181a9264b4fe08c3156b0032d32d0404031470a4c8ce916c8d1ad563830d8473ffaaaff199966b484d79a663ad34c4e77e70fdd3523c2aca09ddd6861f3e7fcd8ed592cd887733636ab343592a96d30750ddd5d73c95af2a62680a97fd8dc5183ee2cbfbf77c880ba03053bcab4b3e67dcb627df8654a76987ae98232d0ae6677a9d2d5ec2e09213da961d94408e9c9acffe5c79e2761303a96ba8215fc8109dd1736a78c81ffb0fc55603110c3e694b1fa3110c33205adc84147cfc7a00b06ebaef1ee1a166a56e8eea0dab291e34877c7db67174c8e0c0bc118f63c09837d36db94a1dddd3d58404607dd99e2ccd01ca9834c945e7a2f16a320ac200e3a6ce2e002840d1c62e048011c5c781873ef32818e0247149b37c6d8bce145775fa11856c359c3d94d33821ace6e9a1674370f1a30fab3d9bede4801cba1ee4ed9b80fbc9ca8b3cad1dbea671247a08bb638d2dd5fa957a91b5984a159018f14ca0d2c1550c2c68d26ce7a18cc0d1368957ef1af6ca34c1b61b8bd6d08c10adf066b0347168bd5060bed4a3c7c1becb3d9bfef41998edf835c0fbebbdb59e13bc9580ed9d7910dcb97cd608b8d31366c4051f36a69fda1f09de424910f8eecbb2b499756ab95151c9c9b95457a85de98cc15276b38e270382124331c41e22c6751e956ce7a59d491c103a5c47469cad5fd259b3d59ff734ffeede6674f36e95c7a3e458e20d1d1c1e97156b543808e907c88bf52e851ea819fe5ac9f69ec7e92a8a893429318fc2bab367cdb12a2169290db5cde689cefd8560cda5a845d46b3bc59f7d9cd6db6f869357c2753fe446c33fea84ccac775df0d8cb9ca2679ccf5b1771fcdb8eb3ae74911711e1fe244fc7f0643b912ff6886793ca98f6694cc93329812cfef05b2e658d65a37a875772833fe25ba74418cbb01aa5b0d31dd1dd5006352234babd175c720d74b8d02d8000536f8bae36ae63db66e031ad22813d3e09246678bd2d8498384ee1abca03b66fc93c57a4d178bf59a140d2bd028020d20344ae8e28c069cd1a4fb263fe31be954761ec6fe2f36430733aa98d113432fa9a8e8f3ecbd32ae28434b370dc6a08107688044031766d0c50c8e98419319b4c860838c1b9021c4adb34a79a3cd64182143003220a33b7615c459cf6bc94028063fe81203a518dcc480070cbec0e00a0ca674c72e7f52b53486de4dd6ce57f1bd4bd5f762fc9bac281fe244fc314b7ae0edda99981b88d932eb1864c6d0c018750c14b410c38a5183182fbc00062ff0d21df14f1cc69818861017d8e18211b800e602a216906941981674a005575ad0d3dd75b250de640dd358579562e881a10318303062981c84f1120601617aba3bce2cadd24a6fb95222167cc10213b0a0b6c3821f2f1bab5bb522be76b602225680c30a4e5f50f1c5952fba2f72ba6705595dbd600c96a5175478a14377ec028e2e9ee822d645933854a0c4ec3a0f63f86bf95a9f5915ac5240c7ac52958214c890821e5c8ce1820a131755b828c0166d6c91832db66c81650b225bc8ce579d12c79dafba148e2b8f187f55e2b2ce572ce944fc73222e9335cf37e9995679f31bce411b82b3cffb9acb0367df85f86bd9ad2c625a05673eb3d251d04201102da2d0226b21d3c294451d597c208b2759d49c004c77c7d0fb259cc398bd559c8dff379bbf0fcf5c33eb5dbe16e75d8797c47ce5b7ba3f3b410a587081851558502c6cae40e38a1eae306245195680b1225b81002b7e7477e4e1f9190dbd5ca3516f5639b35765894c5001135013d06002ef8eddfc2bdf9385b55502232588a18a31aa6041153854714345185460a142670a1a4ce1c4143e4c219b42680aefee88333ff7a95ac6d94b7bcf6619b4c4abc4602dbfac93996669bd18abc39fcc94ba32066f61959daf705f3b5fddce579dd792628a9b36492184149f143048f123768f71dff92a1a01918008cc1d603e00468299218a39a24041146114a88eab0378c6f6f318fcef91f3ac32b647cec23f2d51f6308d753ff826bcd91bfbf953fe6cfd60896716090a16406105144140f103858f66c58ece269db4f3afe5fc18cb59c10bce1cd3a0fb572abd557a9b128669e8d303c3f46fb2ee80d893372afbe9c957adef7da55e75cd87fd05bf6b9f10d3fd04982770e82cc312f704932748e858cbb8af2d27c2b49ce812e67c4ee951aec526277e447c932b6d89c0daacf23c1164114127829d10a4417b8f2ed12b65f83d09e4c362bd90e8809e040a4100be60f1654b47fcc5a6892f9ac02156fb8519d6ddafb6891540c00508bc74c77ac1f94ca0c104154cd4989031410213ed65092f4a5e927c208d0f68f9c0079078604cab5498e68f41f198315805b4a177f706b4a12b794c752c5606bbbf494c5561eebdd92f61fbb5fb3356c37f672ebde2e497e481214b9cb1c40596b86189205dbce8027639d279f2afdb7a1fcb9faef3997d7545d1debff2d11aae76fe2cfe0533385bc90e88a0036007541c0883035dbafb63b055942914be3435511c686103606c4089fe9f3ea361ac2a2951a6af28d17a43031dd000150d0809230908242193084006ecc88017549b32a083acfd05bfdb79540ae5bef355adf3d52ba793208dc66e12d3e9f3bddbf92ae20b7e5d18f358e990213163ebd9d69021ce6252e42a426a418917fce487b9c4c1e5082e52b81c718981cb09486cd11d59ab18e66432f749ceea609dafa2c73a8a298cc2b1eb280da5f7352c6b9efc6b67d585c40e1226f4116558dd1161aad1115f3e893b02cb6c1d71a46593ca28988401336618d0a11d031d064830c2099311b9eeba6b8196c4dc1bb113bb9fbf7cc2b7791e8ddd3f9a5616be932348409f77d223fb271790e2025cbabb94ce055edf4dae7fabf6d2a5d8ed316ec51e8a05e6b0c0062cd000a13659a0c723b6b30815148183a9889dee8ea197a3e1db6220063d0f193264487c1bdeb2c496275b86b6f46c4161cb08dd6d439b88a882082b2622a274f4bc1bde0cc6727d1abb1410caa873d94f2bd3f1554cfd12c595c5b214f6e10ee55d2a49c7575d95b2cebba4eea795012539110742194d2b23e2c7ac550133ba3b7a1e755281fc49157040a6b7bf6f93444b7c5e8f65edc3f8de252cd62b8336acc13e9a8730a3bb7b882fdd1d6d6b88af3b0e41430f4142377e1a0dc42d21c07816220bceaca09d499c10b58f662148c0996fa280151d3f9a294081ee6e1f97ab6a58d62630c50488304de0278044026848c0879b1b9f59a44abbae8bf597b494400b125a685a9e680940104f04d10501640a0208103e00c20820baee9066b686e4eeac95cf6c87e4441ce9a379fe95ade21092d26a6725f45272e93c1fcd3e5d75edcc57b1be4d667c63abf3e0befadf2c931f3cf003961f8e7ca0c387287ce0a28afe61eb01f52066043d2ca0075b0f4d786883071af000040f3c3cf0d8e14b775d90afdcbb944e902ba6ba6c6b18fb68065d915669b7fa68f66b69961964b9c294e5872c48225046048488404f0472202006021a80c01104801ea0830708f180dc035ed0a14c77f833bd506241beeab08a85a77ca45890af62a7b37a75917a38d3eea3af2af35c255375afce67b6eb522f9451e7acd057b2ced67095f6368a3ffad12057d77d20ad6159eb7c15f363ef64a5511ca40314930e26981c40c6015238408b036c1dbb178bf5ea3c097aec5e9d7bec96c48c1fc4b8c562bd3a49143f9a5755d6b06ce283fa68f6ac8df9e0994f0f6c7ab6015da84c9132c5a3cdda56038e7832c5612a53eedb09622953fc6baf06c4d0dd3e2c960fcdb1f4574f4fcefe789e9455068460c262069621b034e978bd550c6dfe5aced622e9b46901255840071640846901411d73727254393a4ec493b2cea1f82aa6507eb3b7eb72c0410e3030e570450e52e4f0c59483027208731082d3e32efc04f957235f455fc5940ecac8573e3dee4284c1c2b7d909a63adcd70ee5b996c1d674bd600ad0a2805a6d29c08454f6280de513871e74475c0b073034f6c2414b96d3438943cd16e1e009a0a39b4902b848008d558ab39e5b9b31f5982901347cb5a62b7774479f57cfcca88811f04a92e98a90e98a4d38453833852818e47ac8f5e44ce88ea99cbca1a848c12b9a8b5da7137f62597339fe3ac7d9a3b43a3df4268df14c1aabd4840b010e02381bae8437565de7333be94a4ed312104b499608005e017a31af62f628cd56e9ade1eca53da67e671ec154764186c274f304bace57b1abf4365b5d5ccdacdb1a8e564bf3f568ae14b472ff684c0b6fe9ee9609d3206bcb8483442aafcdb92ff2f161b15e3d336b7b72f6a767d6b258af6b80394c564670adcc4c565c307d68983e2e114b5a938138d5a13c530fbd6ae3f43c8aa357be4f2bb321a6379c307f55be124cd60a4b3bcada376d2dff4d16258dcebaae5aff9877e473bf47f64f303662b1eeb5f72e995557c6f6a392536452296224f7b39aea923beea0a9b2a68a88dfb5428d89c796377c10be0dd642cd1d4a4b92d400d15df3c38f0661e8dafbd3d6f8d05dd3c30e5e6c65ba25ee8b90b051f1c4649bb1d5335376305549e3892a5d5a70531559054a15a52a475568f09945b9a9ca0f13f582461163a62dd8ea59fbd5d430d558505b8195dfb54238578a69f227633a24677d5a6b6684636052f8d1179e19e17fa1fa776674ff0393aafcae15c26096d70ad95975cd02b10afacc118fe9e98158e2686de12fddd486bb8c2f99abc4fd3aaa4229e912c694ca149594291e5b7707532ff2fbc38a98eef82d2b56b0745bc9e9c6d40a09dd31ffed6693be32f10b135ddf97eef881b64c77fc9bc5368b68bb3e591ababbadc7a46a46fd124ff5ab4e6e6522a6556a8b3730b1bb69e9be4589d35d372f4c4789ab96b63c2f318fc86687ad4b77bc56565b29980a63d6b6d45502cdfe7e458c803e3e3e9e47633e3d30d80dacf4a32fd4d0a8fd5073d57e9c685bd0be8ef16fde7558ba2a762c56a5d403679947e2c752f68a5e77adc7229176518765eb32066747713c6fa6c40cca2c8607e3c33732679065c6a60859c812203b8a5385bdcffee73659c436c7f94b3c761e3f36b33e640828cbc9592d0c56e9adf321d1b152ec8cd817f914c786ba5b4e40da709249240c9d19dd0aba06743bf16b39e71a8de29ae4a924949187d54eb759e2d0c3f8c30a23a33baa42ef334c0758d7ed081083001f2a6c5031e244c501548ca880709a42c51423a604650c4ec7d2f3d31e3683c1baff507edfa11c5bdc4fd0dfdacb5791a8eb62a68ee3ccfa077a0dfbeb24650a293e48497280340e20e60058ba63175699aa602765561b4351e966b5b14e8ad2077673b66e98e30631372871bac1deb0a372168eaba1d410ca08e9069b28248852eb686b38e950ea800206142f1eb5b9cd2ea3352cbd2416eb0514fd890e9ec8e0c9104f983c01a234859255a2e1c5c6ab03afdaeb3f2976b453d54fcd28956a531dea97ecad73cf935ffd512c963c253520099624c209490d24213a7bb6aac0d47cd9b5286fb9bc6fc318365c4002363cc08604d87073b281c7f48c705fa472564a07657474e5c87674c4480d232e464418796d8b7c151fcb5c88a1f799568afb7a8b70d10aa7a21188a42092111960684888a125a7a11f273f383961c3c91627312721505183d7ddf1710d37a337cfb6e2fb9fe36a7f2af5d5575bc39e9337efbce54358d7c80e61e5bfe1cef3680dbfadf2ab0d61d662f9d5d0f1da2c9333294b723dd11d5d374dd4e86e277d6a72812651dcfef82aa670b514e52f17916b88c56a6599cc52962a42659abb89f86d762d9838c164034c8c60626322c4440421129c847c6867392b7c9b1773b5d22848bbf26775f92aa630c558a23cf452ab47422d096249cf92108272109446d0114114089205bd10040290088066402afc74f19325623b31c5adee7ff26b47ee3185f2bfd253b44a510e942a4219396bbaaf52a8228c3262dde8cb5771465d55a58ca7d6c9fbdd953815be56deff501efd985a55b47e68b14e34a841030db918daecf8bb1139cbd62b71ab866596f7bd9f451a086080384e06f0a2bb23954e26c56c2dd24feaf2a1f2be8f8f8f0fae96e2f91f88e55d5229cc47f64f7c70765298cfbc4aca9c947451f275b712a39e32dded3d61bae74bf76cf57c7dffb6ea53cf4e0f093c5cdc2ccf96ce8fa94cc9c99b94293c94a784ee8ee04d4d965bac2d5897d259a507b91cdfbc268db1765077b6582674f70c635a3370d19156e90c5c66f8669801b553a63b565a74dad9d9a911edd0b0e3dd3149991693248916ac94e48bf3948486a64bd5d6561215e2949f93b78f666cbf8be408121264104386214e3230408624477870440547b074a78c3e85a967bb1667248d93112f46964e466a30c2a373f29693b7979111124ca7969344119f8ad08a149d8ac4a0b3860e17295fcd54ad3772aa38e53020474999138e08706e3845701cbf07ded96a76dfca5ac6740ac524e203a2778a1f495849715a6d49e95cd00ae1292deef5d57ab5b39bf29b74b56c631e58eb4995864a081592eae644040d22491099272241447a9c8684b1be9ad56dc67578d29faec3f6fb4963d77a0e7f2ebd4306302675c40e29a0532ac829d5a82650b7136aa85340734a2020a0506256f7f91483173160200629a7184e80410630100183100c51062f2c717a819e5e18800b57b8705d4841081742721d23b61983acbf85a00d6bb8d847f387b3074e6ab3ad97d230665b48727239dc8c938fe15abc8a13b90a2cb880852a58c8e1c402664185130b21ac208315905881ae50e4a44299930a635410e2a482014e2af038a510c529052c93c67c858d7c15696c0524023911ff4101d9225fbd8a948c806041ae1f589416d18ce2e0dc6f347f96651464f44279ea076584fb4a8432024afda08cdcf3242b0534692b56c1ce278d5589420e28c8503889713ac18b13ae9c4e507242905301d028c00f0590a7020831010c139030a1869309dda99f1f6cc46d264aa11ca3dc71a655fa9eb5316ff9cabbb04a15add2e92aa992b44a63b5140fa088d3001230801f804e10344e415e700ad24310a5ee1ea14f41989c4a80a3041594a0815309b8127c4e25fc3865713a8de0a4c3c97512c134839309042713ccc46363039b246c8a4e36454e2490712241cb4fac208dfdf8ca5771e20c5a8bcb11488a28d6f26330dbef6b407c15511e4595c293c67eae5d79b77258b514c368d2d2669593c66a93c662fe389aabfedad9941d15c0152701e4208029027861041d9c465062040498fa34c2d0083f4e22e440042544304284a153087474a7a238113f72224e44b39e1371229451ea0865e4310acac8a3e749998ee7c9d4518ac869d6431911a18c5247ae4a619413392b7c2711678ac336e34c6f3e3db3525b085d8a42c062429f42e86208a8b645a7008411becd6f96a8f74e70005800401a27004cd1dd51fa0400d909003e27206b9c8050e004c4e7042484c6ff85299d94a7308892d19b20975f2b3f96547f84a71f379c7ee89c4088e3044218107c388110e50402cac719271f5dba5993fae79dabba99d1745d8bc293c2c2b7b158387c2791669b6387c3b775298c0adf96947b1f47f9b1e7a1ea635b3bf9e858ebd4434c0bf34f3db8749f7ae45ee8538f26ddddaf530f7ae251a61b9f787ce9eea6a0bde1c4034b6ec9fec9a9a6cca9a689534ded54f34204fa19cdf2963fe9447381eeb6a14f34359c66e838cdbc4089cef2874439eb0f8919bcd663759d0f8956362476b74e9f664e38351ba7fec0a96ba7ee4ebdc21d34b8838a076de8aa2cfbfcd3edcb3d8a551f9db922ce14cb1a823f77cc3b64b0238eeeaee0fd3c7365f9df5f3bc0d881a5630ae51fe8f3df8e9d2057cb8e3ac2d4d1a58e1a4e1d293a78e8a8f9c11a3fc8c00ff00f52c8f5e947657f577f319da3ce3134470b73f890e38b1c5de488d2bdaab64a582d0b95b1658a9489a1cc0f3260c848323b644088e38b3872dd9d42a95878e56e957c1559dd6757b63aabc37d6af51705cbd622dd4c2f18bb51ef6b705c01c703e0288203d5ddb9cbfdacd2dfc06f746fbcdee87963006f88e0031df860091fbcdca8c30d319dc28e33e523b98eff04f92a56231df0f619676bebff0ab9c1a5adc56eb11b51badd50a10d32ba636afe12f5da48a20d0474c7d412f550f331283fcf37f5faf8b8aa86650f8ae881941eacc00619dd5fcbcec6b371840767f0c0071eecece08c1d04b183243a2843075a3a666b6f8d783aee2b6c66fcb10ef7b5bee761a69fe57de5e4e4e8d895ecda9f495ca3d1da4b9b5d95a53f6d4a9a7f2d873e3d19d3a1d0a7681665099b486428d2440724748fe99883ae65ca010362a5d6e69bbdb31cece4e0c71a62566b6869adf175afd1e4263fdfd648a12785753878a34b9cbfd461896be1e02885c201ea961913a67b4c97a431b93eeaee380635e6479c4937b0a23baf79a8e413d7fba0756a4600002028931000003020140bc62332b16c3ee9fd14800162b856b05ea1cae33cc821648c21c400000000000000461b004c9b09795e5624098c79f82d8f62a2db4da8cb9481beb753c7d4fbcc0011a86a0442ea728843db6e9ea28405b06369186c3adf84313ee780fc84c70dd954eafb8e16a8e4389d4f1da75f9f31a087b47002345f0c1bbfd168222388e873103723ea63ca1e609a996a569696e0a3f60d3a08c7bed33fe313dbb6cab83436bbd4b8b9952d20984220b5d43340e31c519134d91a32b30f0eb627444280abb4106955136c0a1596c7216f454370f684804fcbee4ac7a7682ffe0d3458cc1405c2943dbd27b3325f10f50d2f5aaea27f2d3b72e4adc5bd847ac9a6dfdc7c27041afe45d39257f942171e1a335331236d26797a7863c63e830af8a3e51cb410fa0ac629fd6f4a50cbcc48f437a1c08bf086cf3f58163f9cb40e3945e635adbc8350628b3b7b34966a4b2e01bf64ec1854e4a8e051e4bfe512fcdcced905a14b317ac6a40f5b2cbc00f80c190bd944b6bb630ba7f03118c8a4273cbd22cc8f9e6087bddb30ebf7a6b6ffc12d590f83dab58de83b9a3782789ca57a4e0fcc97bf2ede6fe54c018879a7dbac20827cd0cd8d97e935d4be20f409bf7581c2daa8bb29a1107bf20d7880db2d0f844e075c715f4eb36a621ebe4123aa00c065e400bfb096963198a776e239c4eb1efe551a5c82b0586a3f708b58a917a50281ed99a5f7e1e5e2939348ff6fafd01272f01f80fb315c175e3f219e0aa4f88ee3094e8e263e282c9b577ecb4e8a2d9d0496310523ed58a01061e5acee370cf5a07ae2c32029e430d5fa0cccd63b5089984902d232b511ff0eaf841fa90869876aab058e62c0870d821609caac5541eabbb8e42da37086971958920080bab497b46cb2b4b68a49260ff7573c5c9d89804227f2851aecad918ffea473d6ccc8985ab2d3797b4b868f9e5d6af03906a6027d77b8d71126b606831de9d2036ef4ddb4d2f4ffb2f3f5b5226bc027741a26d284f4251ada6b49e69f9378b523f9c12ad2b5288de3218940d0866c2fdf16cc6fae6b6c14deb20bab4735188e10b5dd66fba11123e901c016571bc6568980c9d96d06a1d3181a3a6ccff54eb129946118158fd231f2becaa3123e968f6fdf47ba0de7d2268d9c0903346c1cfa6df49ba212a7a520e919f5ba354cdf68a17b40caccfa6e7e030248d47b152b4c69a91594906cfc4e562844088dc11cdc1e242e82275d6da4b42adad609c6b36451a54abf7dcaf966c0c7a723f476e66e78f61e497f9bc700caf7dba1933ad0f604b6da4287284346d1e129fabafdbdd33bbb7a329a50bd8a029ca7472185f82b83e1e5e16d4a754b7fa3b3ce5f9761c0c7563518d204319a113d57f10c8c30d5714727695f60e7b8e66bf57a61b0b7b0f366c1791d5fbf3ee4fff9cb26c000aff73799c37f20ff7f192b7afd4edf03f814d57e358a5c53422421669ca69ce5550840b8475b1044c1d1c204a61b61af3ea82cf487e90bb4684b045599e9afb1bd551b2f6f1571255d2bea727d2cdeb2bc96045e717d3c866a2afe57b57e43f018e35c37a231220d8c2e3b74a2fd2bae8cc5cdf5f295a3ed21bbdb415af746739cd5a5afc47932751450118c85348455d352f8d61138536c4f6eac0829a3d7c85ae1dce77e8477a2180176e6dff21007ec543ba808a8ee9014fcc8953ae3228ded0297b856cb8babf4bccbcc238e5646c06c88061f154331648b9e51a5db10e04805b189393d592bf228a6764f3fc123a1f6bcb6a5c7a0b6aa078491fd3a65b78381de1687be99254ec0937347f9568980e70652b0fef6901549db3442fda73af177c3e07d5a6beee297f16a4fd0f942a58447b84fd2f8c1d8f721c126c6815bc626cb88eb2d44e92f2cb828aeb9b5f82120e6f7c5ac12ca49461ae18b69806e75305cddd443ebc832826b65fcbbc911366466fd5b8621399cadf3f5e59c04d0bb89475b7ef43ff753e0867516c47ccd4881d1921591c9c102a82371eec375745867e51916fa99dc2ef666257c8d665e05d9098a59788388babe2adf32103ec6661d728f40c7aecc52defd20a2a640107d9963c0abfe1497f9ddebf50b0fb74a79d9f683d5b094760ca50883e0b9f6ac473e7b17535ffd1b6f0e1991841f89a8253278c4953d68994ffff131bc4d17f5f3db01e74df94e0bbb7a21b7821b5d8ab51ce288be1a8cb9a6b6943d252bdc6535a151e6693030f5faecccc008e18042b78f14b4b082db325c21f58a5c85e18248ea611059641e32f41414727278122948112a198d05d14091e53d46074dc24c7ff3b3e3ef8246c8067eb55505655ce1ed4c27ba980a17d52fb6f41352802a9bb3c69248706bbc8f96cc7b5fa03c1686a9b24f82ca13a9491cb727baa70f123f5fe141e90cedd2b2e3c112cf6c86699d24c829ab3ccbe3a4d93b891e8bb57e3dab70076e508882aba4b2e0a0235922f89926e5a59bd352b65343e8c3e2682fab57b36cc73b18141ad8d69ac1149820456831eabc3dd96f787b163b4c5c1e7c5c4e4219c14760a74eddce611a3a7be33fd005236b1242c368becff57eaee707e31740bd999bc1afeabf6508294894d57b3eb7aac9f5222ab1b1b314731cbc2b38a914c266d9ac507850da4109eae721ae79ee1b2c222c20306ab08e04d7af322ee1382f3520da815a522ba98b7a3b82eb643363bfbbb2d4492da5980923f37153be92be0074007448856a67883d0044b52c135dd7ec6804f2777ab7eb848773adafab7f2eb8b143ea30cc60c772ed3d6c97f38ac89cecf23fc41b975080550e7b8bcfb0937643968804a77d67a1757e6d004bf122554b31cfae2e3e7afcd5226e5487be980deeb679523c47f5b715ff96c69ec96f56c5cf357b06ba36c2afc30df38c1f57417ec03ed40913d6c490e4cc3f70e21b20913007c888aa63a7b39bd732ca1be9c8482b151b7c2ccd78c1db6c7483d6c10f7bff1dfe091db70b4b1903695e0f56dcbadf44e072d3dd6262732ab6d0f75264f3d6c7e05533f3988b9599cbe3c4638ff120ee6d0859ad23d1295689043680d7892691fd6444ccea62717e8e11849571c493b2dab75e7b62c3cab656430ec5b6bdbc1363b7a5cefa14ce5a4abc73a2ba451b333158e906ce1129bd9bcdd1e5b524a8d35d43c6f44c633564bd7a2ec710cbe008e89f9baaf3cdd27b83306d68ff1f85598f4139212a1daf97a94d0c12b4d0a4fdfffdcb86cb3a4dfdc78911fdf2d661057422739c62781c2d0985d66a70e40df538c2566ad29af2c75c90aab6aa4f1b2244e2bd1ac5f9f17f857f34fa692e8a921e8bd5762c74db758e23555ffcd8c17acb49e3f6fa41d3ed2bc42b8b2711fc59d0f4b787416f30f18b05db9edd3faad4db22f21e400818c6ac4ff389c9acbf67242b0da4652ac9968d101d96e2a158fcfb819c7d6a0a44607448150a16d1b5a7c3d4f53b09f2174d7f1978512d3f7b1bbb8d3d73c28d627108b76cc98403cf6e52374c0b976c54001eb770d821c59586c4c3829a9bdc765c410316401df09282195f8e2cfa066a3bd912201e8423121ce11b38fcd9853d39991269b63d2d0920cbc629603b68639c8175808f3244162b5fbce135e046104264f16a336f290bf9b339e6ea99f0e9176d0c3eeb11635cab78bb81d04db82ccf3ceef2fc97ddefb3b8ea30bcff557f003c8a464687bac1ed9e4d251cdea62dfd71a2151bca4a6bad751afd2e36fb60d4f8f69409acb329b5e58a8418242130bcc76cd4ef2eca071dd23d1c053a50a6594057e1567f4ada5be2ada1f1e206c39f7b7f80402e3da83c594d87ccce9f0fb74a39f900aaf5d8c2db75db186d4a3f6a4394f80193d7139710b50a59bf463d213963afcdb5058605e454f6891ba1d6c6a3e1d1046b47b83537a0c44f3405e81cada1bd7dace161a3ed676abcfc717ce3ea444b70c194899ccc8d3854d2d2f9445f1b2d4b0aa98da0b0c6236c8a88d1e7b0d38222057aa45e223f3927064629209da4cea3e7c5c1478817588b20202c5d1bed513a0af75aab15e24d40c8ade1cc8c066517a460d5c65d137eedfca6f9a86449a200ec12cd4ca441a724e24eed47e21c414093faae6a9c8b6ad50eb7f297d646637a810cf7f92c0d3f2cf7e8a0df0b1aa7bf03aed13bae12fe6a58bf1947ece4c780918b3b7a926125b1610b56ffa57dca835baf1b3f54c82f33aebdb9f1f0c9840c83e52b87304bda75842a3f11007bef835e8c4fa3358910e54d7c1208fc2c750bdad7e36d0bb3d2a8d60a5defedfee6fa28ccf95d430085705e03f890b5b2aa871387b1e1ec19123a6fdb1e0d3c319625fb7829b804d957a0fbb5f7f926de6ac6ba994a8e857e5e8cadc3885a4a0f57d32e040511fc3dbd86a160765cc251aa9188b267a5225cf23b20ab391cf41a126d70b0867016a3122e3f971308aa40eb913be0750d3522f03e6f0642f503202a4706f973392abeb9f161b5da47a2d82eb47ba57f4110ff5fe78366feeec71fc9fb21195d8ff39228cf55f4c0c2fafc778e1ed5e6151ca79d71ef52ae0a87f77015615658d9f4915d35a5f28d2778bf43ced6c6415981bdaae586ad182137c92b1b65ddfc9cc0ca2fcce039fa81e6e5c9adaa7dee49f4996d696204042871355126cf3d6c6cbda28c33d91fc311d9b870b6ebacca7a96d6d9467e0d4b366a03c535c1b76c11418917969af3d5040807b4588f90868a4b018feeaf743cccf0e72001013396fd42102340639c8353a9381b9a2797224dc589ab87e6d184386814651341e8aa616742e8972eb20194156d2c6a0001100648761083c26fd712a3c8e0a9167a70c6b8da4bb00f1b7ec03d352d2a3ef1399cd5cf3e9d24f970f773aab39edd1e9f6f572203d272b2a361977c81856a0291e106a298399c6b6c342f1d661690a3fd7a4bb6d46c180a99ef183c254330c2e1b0398110e7f797ff868dc323c632afd0924f195c86965285315e907e92c7603535db1ef2a7ebbe6b31266df92478a7d9547246af57067757511f6340dc8e2819414add8970566ef7b231e9a860fece979579836775f3f27af5b0a106eed61c579fe37cf4e40fe09e5d1b3b9dc9dd8a344b3baf93b51b6622b4b94309f6710c0fa7a756a2aa4d90102e4cb31b21b89e5a24a8bc4a5b73bec3a56bdfb82101f80263a25b70f83068dd8d1e64cc98e63b3aeeccb78d6c9e3878b62a9c41abda9f32e3164453e971e6acdf7f29f55818e7dc5cf8d5c1a7bdb1896fba395692a30dee4dfdc509a42c658e854c1bdba8dddd1aec59e43b8a2c0a6d0fb8865c98d269cd04b978eefcf844f76d99251a999e2df85daa8352343c13bb49a0d681ea4c513829e8131c939ffa81a34aec3aae07773adadb9660b79ee1f063b8783e5f4a833a405deb1f5a7991f87d0bd131d8f8168600ddbd0058c3eb0017b83c1b05588996f8ca7ce0562878a9a8266d889a03b5cf0f2fb63be78735f9544ab4cec7ac59c16dda5e488b185b62bd528e9e19ffd4a52e5530df56d0f0a407be88f4de93a16986c0ed4e3e6433a8158e71c20d23c0bbfc107c79526e4557e05e681754b0571f8e8a5f7b03bdcad40d29fffbd6ea56a614b41cea3942dd73d6ba7c347ea9471caa6ebf7d7ab348fcb822b808929cf9dad5741f9556a6c73af196d4bf904222325a45a9e906976e93fe647b8d8adf09e5861e510a821b54adc469d9d3eac678229742a0312da848336eb6db59955c445d3f376f6874057af80d7dec8bcd18ccf140fd6b0831148c775656fd9fdea9c539f62adbfb21f3bd978e3926f4e64fdfc806779260b650e6d6cd0a0d534449e428a18620c98e070876f9a3daa5e5c5c8363ef1b6f4bacd049ae85b09707c1a5eccb2b26e5deb424523ca53d307b01855b4b62f416e2513eb84ef45f6a4f2f4d6d03edbbba4b05b8501561907c58cf8b106d6e2ef3b4bc1605a2ab601c36be51116a617b2c2eeed06e44027bc7b1f4cb023e3aa5de92803782256b75c5a0c7e9e30ae6c0812ece56e00b160d1a47562fbfd22db64204ea2657f713ff995cfec26972b056dd41671e17bd693c81feafbad81f73022d5f5e955f13a1de1a6b2f4fc4b975d0b1b21b4a213e4c67850abd75569df51a3c516d11577f2ba23d320b1b2e852469f02d762ef1684d514a8de7c7c7c4068f2475c52d2058964a6fe272491712c254e183cc293667605b9afe4c7151d6b79d3c4eb2f493e2fcb1afe4753e212fea6daf32293597b00035c4a0d08268ed71fb9deb84b5e5dceb2680026cedff5902994a259c44373cf7b15ec29e0893994772278f74280f5068023a0ee7b7b03393f0ef95f6acf13b6f4f3d3543124c867e5ea070a4fd85fdb7ff604b8ed13bc6a747abb9843f55de90478b66a21861d86efae0a8e53d0fb17fa9d055afe7a9e2c8c3f007c874fb6814077fc863f66f4141a48600ff7236b7602666a599ada2d22e6e0a968c803153acc98959906c20e6aab31be32554f4719a89b1a2680d12e9dbec0255699f3ea87214c999ce0c86fb1a23d7dd6d40c225bd449265bf500f6d6a01d7b50883eb7e4d32e0e657b50e39e36e1118274435dbbd6b30ce624509cd7894cb910836478f7fb2a15611ca792178d57f78984459496e97894409337b6fba4fa1d807f21a1d930801ec10608091e3fd4768f5f4b513906c11c750b15e72d4e6c55ecc9f87ce9f4100a29ed52fc29d936356e72a82f24966ea33213db9ed55439f90e7fc720c516a68e6a740d0c08cbc83f5aca4259cba5b2adce4b1fb689ebfab5ac149ca9957a6c801dd93b64fc75b4fa8f9ac3930145710c63f3e78ca0fbffd058d447b941815fc38ae182c1ae539b253bdf1d3b43829bce332157d12743c76e2a30826cb9de44a8415097f876b6822c22d094d35618045444bc515aa0ca28f3f14c24f0fdc1e2d3e0f6d1410f37c88e1057c35d679908e8f50e40c91e350472d468d34590ebd05a11b88587b8da6c8a2be46370a9bef2b2d42cb30e87904b5f6e345d335336bdac3f37781f8abfe29f85b9a4cb8d450a78cdda1be4a18b3f31908f2ce85805a0365dc7677793ab8d68b8c7af6438afbd211ff9109f0d322ed630f462f2f954b63d2408c0b2847113a23af6d434d0e746ff24299f9eeb1647e1fa4da63849f01379673dcec6875d470d284a24f9db9ff890a47b1fe0b55755fa998a6b32b88102dbc84f4c5796e7fd2334b6f15335baced92ff0a4cf253a6676c71983cf893eaabd3e93cfefadf56fa4da19a0bedcc46fc5b479fa7180307071475cc03d73cb686f2de4c9fcfb87d14d1cc2bf4d99fcca7b2a3127d1d0738579b8651cc32e3778afd3c698c10c4c1bf2d64a1e41fd4445b1cd10ad427cfd91d0630578aeab84ab548d00ad4a03d85d04edc9f4e8def432ad47ec3ef7ac9d20a44623a4aa3b87cc3a07b14fb0c07d313d02d1bfc704e01999139813ef1586549aabc0c1f984d3cf7df11b3e19ce06f67e36d4093110303fbf0f4668d8dce7a512ad94bc7f6008c98c3b6c036a0c7cb68d983a3c39a7e0d0d63c8a0ee75c70da24dd1d3484904804ad6e1755c92148c51b8d9e90cb9289cfad2180275a015408d83bc7ce12207bc48a61d1b9519839e081390850d51319855e220c5df296c52f1fdb01e25db2d048866b58de962590ce337cf77f986eac751857f1392a64f79707a3ad42bf6f1186414c7514cc5a5e2394c4ce8ecafa598f4f0740b7e06c982da5f7f6c866e4f12b07db104ff3f264504643826048cf83355b0323765b9330293837056746fa9cfbfc423e7eb7e96d0b784e53679e557bd2e24199f122a1c849de09a72aac58c58a18d1f44e116fa914c129b325f1c2e01cd00a4462d68f2d5765cd794064c37e278b90f351057fc2beec202c946bab29efd3693ca8fc6904c81a8ef85de40e3c3811ac6442ded8051ec73b2c8660982b8b271145b8d68477764362962880474e9765358c39295a8fbbfaa0c74cc2a2081888d02b6f094f9d72a9f722fb1ed0731a00ae9d41937ce9a420b850066b515d6a96e209c03433857fce0c51bed1a1cc8e8aadb4a60c999b8a894e7e351f5d470c74a5ce15c1617dacf4cd5ad97d738365df496fcabcf37998b52604ac87304a0ad52b5ed19b720b66216f59c48e8ecf3dbe906b1b271826f2c5872c55778ba6cd9a6ea1a42fab8ab515d39f6c23ad107179594ed09078d4ba3c8c7e104adc9881fce03190c68cad30b398d88c24f4f801f46252b6f12343084f844ada05d13e1b33c220904e913a2dc63d3dee26f15c4cf27533a8fa7f8e30d3da3950f93229f4da06748b773c07c24bd0d59a0ea502741418a980389a52ae296a3380219457e9493b46400291732e3fe7ca259a25e8c58aa173ef66abf170eeb4bf1f2e591b4952182315a9860a117c97c3ea7fce33a1a9334d1b7d7862fefdff14f68c0f1606f2c51524bfec7d4297fcb2d3100d1a7aedae7e05333a16448d3a7f0b4829c60b330e672dcb38c356804b8790c478ef74f61fb96e3c90cd01c19f0ae0a6094b7c3089c6e1cc396573d8c0783d561b072a6a3bac71d8a391e69be35442737055691335886d5bf2b264b7771892920140ab9c1ba4d7fd81c6f561e8e1344662c1769566d87101806eb9e1b9e5eb21a7b92c2e5ddc85d98b758e8935a37dc86dfb6ec9f460f80feb6ec7c2c5b929fd6faa2d7d692377f3b8e4bb33c898942c2c23e3a332f2796e88e22f720af86f8650e7710ecaccdc5879bad1d5e46c41ff96ede80bc0b69e64d3dd167915d149ae8e7c3bf8cb5fb0911f2794ce0bc68237a881231990eb324cbddb9b575f86cda0678d39c0aa054a20b2521396f363198fd1acffcff81585cc255115c2fbff2e3bc218afc2b4da254ae2d337c4e1faefb532ee53d8065161495441ac587cfd391717979bfc274924a39e4dcd2554ba1533a888da1cb52a47ea8e082b4a18b86e48cfe3acc54d2f4c64c82d22429eba88b1e9e663304b95df3e22509666551bf971dd3cb5fc054a82b2e0212600e549b476e726010037c6615cab92849bd247046008e9dca88207ddc5cc36310cc88bb284d0836a66cd231340f36f3ff6fce1e5bbd74991ae4364442d04635e6a9d8fcfa52a4425ae7c82df238a8be7e63217ffd6ac3104c988e2894caf1a12450da86b29599c4fb17097e4f9a8e7ee167d164d1990ad117a08469aedaabac3c919c301b5babc1fef4e135382f3849deb3aba67a045319c5b2d61d8427f138d741b8179bd8f6d36e73e4baabfef963e208ff419e65143c7a028394e227c53502eec93591522dae8726ecc0d5baa77e73f5ac39707c27fb1004ae16bf10fc565d31d4eb381c6cbeaea0e4f3b99ab9f6e30cb455a017996c0fa7ae1eace7dd644f8f6ed2e9d9171461403977559dea507539d8157e52a698a82dc88ff53678c3d0af010a441bb0107dfbdf19ba403412bb3a41bfb7468b6258a77f2d1e18434b97e09385bb74e90b03abf7a9a029d780ba2cf0efa68700a99343152dfa6adad5123f70c3d9eb4c85e3ecff798e54045b3dfe0fffe428bf31c611ab08257d3a75b296d2da6d7b94c2af28ebe1fa78b4cfa6a24762cee53b29b85cf10f3c42b401846fa1d800e6e549afc70e9884237f520da7a68a0bbf765872b57f26bf29425e10c0770dd192a94944d50d6a0654bd62e3513bcfa1648e339abcda083f8a522e3ddb0b4113c5b8e955f267a5065b64eed0c24c983cedf3a36e101afa0ddec9e7a2c607abab235b13f794d2bbe88e585dcd959693a93cb0118f8392a649b26f16ef1f93232a1fd95c522c740393310ec318d5a37dc382fc3ad1fe3fdb64cf44a71053f0ee0cb60b57efb47a683eec6ef4abff5fd41d8b055abc79364e2b960376d26ac7300ee4ea583dbfad6f2784362e8a9489aef24c4ab262b63df667dda2ae6d9c3eed0ff5a445d4f031369dabc861db4a74b0c5124f082e864e3531d483c780c009c2a66de1d0be999ad2deaa746500c2ba2e4c797ed4f57376ab2d2ba017af1ccdc1ff343105e4450dc9172bcaf03966a065669982f443f3cc572154758637eb7b02f3c396249699b59a461a74df339b3f967b1269d1f5551e78c8a40bcea004cd0fd216664868867f29524eb75efd9bb71e0f08f9f21314803697c583e9384ff5da01b7692606cb6c51356ab399e3a41130e782422102cfc5a095c3a918b06b1e2869730590f408d5d478367f5721e368d54e75d727594a778ac4a3e188a44305f0c611cf1c7ba90b0f284f52a245ac0e5667b3bfe9bfa32d077124189fca04c8fb7fecdb48c99d96d497c1fff7945c48c2a13a9c4ff0885a25d5fb843e7f80e21b7234335141d008ea78c3df60959c8467398beb19c997128ed67b122def88facf5c52f86f7aa1c65ef017ee387344a35c6e2bafccd256f35d5ca195cc43e88deec202bce1fcde419fd59d3abf12067be1d67d06c07adc87eff05ef3ebed3fa5b9621f0f3cba6b19d26009952968f7596f313dcdcc0ca6a423578720a1d430582ac2908a7bd295c8992353bb14a092dbddc4a2b9c6264a4dfcbf26f0e8fa4bd8d64e97081c7d1477365f97f79f950df70e9aae1224462120cb029c635b8cfb39b3653f13f795e3f2094937cfeffca4ccc8c09b8334ffaa1ae8750024ad9fb89fdded472fbc6ab425f20e96dd2aea6acb4f2d15830889a0531cf9dde2bcd713eacc09648bb7609be257521d59f2532de6198ee98cdecefde9a1751916c76494ad36020b95c6b8e0a83680eb4f44297ce15fdb21c6be70a170f6b3813f63e1eb0ddf1b239e80e95c1fc49f25672cfc0d90f849fdde9c3bf090e6da84e30e999500ee7337e691d71eff4df09e30c3bb222a55c1850df266e1f74c2af02c69bfc06c2f46d8656ab10151600a57737588e18e3ddaf228643cbcbe77487e3b9b5f14d4c8464e7e980d406efb926d465f5cd51d1c1dbbf12fc3d8cd4e43cc8eb91bb477679b47d7288e8f0c3ecbc2e52df50d882682b8911f6c5a05b27bae01988015d59ce3c3e032ba4ef2d7c2d9514318021404a9cb6475c559344fb1450b148bd8de464b2ac2e2b89cbb4b7ef079424a5b4705817ec876363262181dda41d8001a071411987a54eee566b1608ba445bad5ea61eb6fcfbb219dbc2c5ddb90a2c502cc9370288ac84e3e6d54ed60c690f1198778c213f9466fe8dfd7037a13744da90bca11633cb1e4c23776b90383b5ec52abac49558aa2e7c74d1bdbfc58e39531fb0c312705d4ead7e6f1ecf709a7173a8ab4e97cfe5ebf2644cd4738a9dba58f948316fc4dfa301186144c8a801ca7f889ef6091c811d108ddb5721b4684acad063fbf0a28c1a1b467ead6d45d7077af719219078843f3885a8e711b4be17123ace7a5f845b8fcb4da74ae5c847ca4dd3e49b65eb28877f1c89731902bef392605724ae61300a5f573f53a74f6bd9ee47ba098e2656df384a4a1a1d9b04df1d000a89ab229448d7bc6e62f9a399e30c89476593f5116b67484c6396dbfa9c863342f2af43cb0ed016852c07a1a6e94290fd82a901d2f25d0940a4df13a90a82d156a0c6db95e7ac70e622b829da16f6aa1e3d6d50d491d735a308de22af118d610934a3aa71189c5c3579f7d256755cc6379cabc02e5941225aa76a406fd3bfff7040381c01ba0fdd52395cc6567860ae2c47b31d62e9637471072611c886201253897644fbafbeee42b7512381d3bcb76d63bb465cf8b52c0bb3138ec68f3da34ef533edee7fd38877fe1dab45308da756d0a0d4dcf636d6cd5c09c61114a4cc28548121e9775b72e9fbc6ea6baf2ebf2e11a650ace454382e9ee75185a79e4e1e8cccb83f9987b3ffd946391d1724dee56532b60fc5e9c304dcffc8fd6d7c2638e87b8e0c89f227f28962fa9aba2ec7daeb5fc9a309d00ab4e8e0c4ab70bef20077f5d47794cef872f463a70f1c2cd99f50c6a176febcc741c9c1751e9892c73bf40be3b5471fb7d01fcb22397aae5dc2eb5f86bfb5d1ae5c85a384c362d1f9e58ad5840321e911bee8c449bd489bd07ff9c7442defde6ab9827fe8b20c4b105750d5be9fef585193b761faffb2a82ce4b535f8fe30c77b2ebf178dde7f0e484a4be4231e00a31dd514806505c1c46b3e6882fc31305073cf71b490a1e6eca614fee30c70485a97d470e1711e7759fedae6a25efc99d7a2c7dffe2fc7ffcd17be07d5ed178b2611152c82c69181fc751cf77d3b7fe8a93c7a9c04df6d7f1dd3d3e7fb2da6077bc637cf9b7c880e21e1cca517f0817da785997eb97c99bc08adccfa5610b78653c20a12888404470bfc896d4d03d2d9cf6396434126dd326a97733998f11e82ba88c456a1a897bf512aad9fca3500dcb2528128101a395e63b5bc6ffd24c069b8f5efc7de3710f5f458f48e279e9daf8011b60936c982df8733c5c275fec22222a7101f5c22c985ad1b890ba73a82fbaa697952833745d871d710b49bc5730a4189c90c14dbdcc1a0a49c3535a6cba9d394480ab004d2eed7acac25ec33c0eb3cecd59e98d8db11a1d84c15b03779ec0bcaabfbe04939211e48fc40244dd0499c3e075753e6159e0df8b6cdcdf595fde2531b193e435fe0fea9aec993d7f0b6d492c656904fc509f847516510e3109bef7f93e5c441845250ab4cb3b3d2cafb7c0d1ddc6990f33f94abd402c1755ea57a2996e1b774e5ab144b454742140c7243f8fbcaac21a2308baaf947f5971ded9372e0203b26b53c379d6fcc30e86de854320554b32e889bac7396b40561e7276de5c79d3c871f33878f984af500d76cc73229b39f2f6604cd5035d96dc396174d9d854461042fd80edd7ba511eb84f5bce64f05efebe22d3d274f4947b5a12a2002174ca5ccdcfbfff1064f612a49116e17d06b2332b310899e8e04c2ebc55e15523d411f7bc3249eab2d3d0ebb311dd86f9fcc5fe81ea109f8bdd57aa0030de1cfb89eb916cf9bd81e786e29bf627709d335a20bdfcb332a7bac7b5bed134b90e9e094ab1c2c654c7898a5ead3c352b313095b55ec91cde6aec6ba0d4643a65266cb22668dd9b8cd8bb333c8765ecad3a86bca291deeca48a15d7b24ebfa96954f699d41d1b624b03f55322c605b0d3f8f3066b79e53d2c43315e5cd14b0d8411a5378a7afc745c5974799a74e13c5500e2831a0e7353d91b0fb64a2e4f7cc3ca6d29f06f489827243b0299d19665a3fbc1e1a4e3922a7b83177e5cf012601c6c4bc460676ef8dbcc47ce367e4dcb2efd596f0ba98d688e198fdcf488c94f6fef27d368a419684b3aa1024f7c7c0d884aeae60f66996b998fa8b29dd71e46d7a2c6944426dcba89e9447d30afe444f69924c9cbf9986577d0b9929710928da1cff9a8b85cefdb79c1b21bb12c513119bf9694884c8eaefba81dd4893c93e0d97bb8b70ab0843e0d36a24d17747a7e7dc26db9bb61316f94113af354b21e46b608e7d6a57b3aaadb3402f68b8151cc740aa0eb10ee8da1e3003bfb004cf0132ed0289fe784d7c8af65ebce7d86ce1e12d438219b85389e7c67d727c9be6683a12871975e3785ed7ea43f3275a3b78b08c8ff8bd2cf4c2d3dbec2f39544dc726493ab3ef2536f313e6a77bb6d623d8e8e1b925035b53a66b429ef77111b22d3f9db082c9e24d5953a28d818bd47ff27b602ff680bf42f98461e12166a841f4e1227f83a97d0ba85f057257243dfec59ff6e8a16238188228f8d416be52ed655cb0f5b8d7ac508023a981cf238d385a66eb102647e6e2185bae0c4568c1efb09af0cea464cc2673eb0dd5263e473529a6b66e643a68bcbd5a15a0d817ca9a10997debfbc8bcd4bd0ccece7fed8b3d5ef0492dfe676c7bfbea2b52e792204566c96c39154b4a62f2c031cd0761a21ac84a8a9c694d53251b544315675a40e9b407f4807b8dcb229be9a54fdb4eb0a8e58ef6e83f64e60c5397ff2e62448ca933009c111b85c53b247456a8e0cfeb56f9f0f31db62cfddcb24cf35a54951a7724a5dea6139df116a6102239d2d6b2734e0b9762b3e0d11cbdb7ecdf3ddc8262504ac39e0fc33de223abea3e3bffff553220fd7c449afe7c0d88a5218d28b0ae7c597da4638503d427903917faaf9df0df0b526bbef8ca7427706644f8fa3e83610206378234a5605d68ab44f7549f7bc04bd3786f33948b60d25225c619da3fad7b76b38cde5bb24ffbc6f36eee4d57117037851707b10fd5b2345c786340c2156fb785fd1a24277bfb9a82f52be0c448104281cdf1dd4fc44bbc0ea535b918cebccba708f78637e2fc23e67849ca2da7055977fa38c8fbf2420b3d68df73d47f10afa87dcbe4315d8fca16bc62ec365f7ef8584d43be3cbadba65bbd11889596c90d8765e5f632487329acfd2192b74c4bbce73869f68da7e364b1d87cdeccc48547ee376fd39bfed6791e9df7fb7618b71342c74d4cf9706f9a4cc4c7daf4c59e5159eabde73f4db0c164ad5b5986d614bf8263ac728375e2a722c1db8ea82cb74aa312b5d85b66ea1fdf6bf2a6c8e781303f51d0d89995364e86d46b70f72baff39b3e822933376bfd511ff4fbd877ded65a629cb433e8cb1b313333bc0336ea17069d8c460c9dd54fbe16ecb198563e4aca3f98ed055befe295a58bfe05018819cb973a327ddc44c0536c1a8aa511ca8694549dcce404661bdbd14f915df2c6ebe782e73ef988a31b96a025608d021882b68244c841ef86217ed97146e3d59e84dbc61290cb312d4c20271f55f2a8931a4b1491113401ab6cbbb94e8a0151282eaf34eb423b31bff23745a752e1aed2fc57568ce222b8efec217ab499146b3741eb9433107137326b8c5d3411ce467fcb4fbad9971f5cab0334025f33a6d5eac4baf8caa953fd20418f3a08296ed1e9c69ad84f9f726c98465cfc0c440a3ea6b57fc0f8daf160dc557219709cab043432a9dc01de9f8bf4f551a1ab22d802d66793c887da0c079b3a2daf731cbaeb58cb648c6e350b3118bcb80b5be4fd7f2dee182271e7935c0466aad2d31a12f339c0d52029b5108d0702b9ee9aad2c1d0cda345408f90cdd2d60ae9bccceff4f921a8a323d3d5ac6af26e61db8cf7e8a0d8f00c907cedd730ed13a3182ed2871c1c9a2e49d40f5454eb101e9cd94b7a805120bd0e948d34bacac14f96d8e3dc6a262f3278cf3fdcb0e460618db29ce2df78e39e83d9c7773b3ee3f8e24f4e985e78ba3e307972d9fc277b0687365208ea4c1d38285bf1b1ebd6c386055c20145d8e6060d297c67962d049aca38828fcfe5bc8786c74cc5c2c21c0424839984708b7170cb78577aed3690e62041bd03748448f749d169cac92db3e24de44a5bf5f108c379599d42cd898235c907357437c2367167ca841ca4652ef32556f590475586821270b2cbded67fc0786905064d14c580608bce0255d33f21d2949fcf1de07eeb63ac3ecf8b9e8d632fe5baa3997d7c2233a9c37bce1c3052990fe6ec4f1a84fb037fb17a2f0ff6e90efdb52eaf07a0aedb8f3facda4d59fc12611e01a90f71f3657abdc33b0f7d3d02e772f32cc8037ee3935d6b32935830d69c034a1877bb73da3844a5e4a9f5fc6cc12692ba6eecae7c89bccdb462dc7c81edb0a65a97ab965777f8dd6bbed9d734cb36d2493d64012589bd6f529b157c893c866343f17bc87021110dac1ccdc560f257483b5a9f42ce5645f4b773fa84462d7227c85334834764c2e65d0c410a134b5c82e0aa4b35ebcd80987d907ed47ddc5fc9a3552e80bd0ab51a1201e2468e1455f3e7b1ca9b63c6f2dc1641065dfbfbda817b65e6553d74a42479739426c495dd81ad0ece43abd657f564f1ad5360222c8da00d1da0abd66c88176dd4e9ccfa113630606d7817883765665dd308e3ffb5a44a99f0561c2842e3b4e0cefb64b6b01a23f18e5f6b55c04d5b2202091eb47bf66bf19093e0392c6b0c16868cb8e698554ea7683c516a9e6ca7848de0018b77e31d86dd3f06256d0dac0312ab5bdcda836ab8e7cf663a01a2a1a2fd0cb7d5c5f1a8e462519e58642d1f4ceeb9afb76f59f7635fb051e116f555b1378a4432a0633ecf57368b993ffd94e14407e35f9711aa62fef293e824035ddfc3cd5354e21fc9576326ea79121ddd05e12a268064e07d0e023828f16fdb72920c5749765a32afe788067d0a16f8d9c09ae624b065d6cef8e79bcc44c02675fe54964a8236fce226ff020aa951bd64826571ea289017e424a53997be347341abfd865baab5c76fd44475f885329fc7457d2cd24c4bef6163c0b0d294905ca9f3569945a2bf191f72519a05948d7a60937c73628493b0afcb56e30bc0cb43a7eaf02f564dcc00b87a6f18d3372cf3d795b5082e9151e0854d067ecb439a02b17b352ec0374c31177da92308e8a294a00f2b70876d962f6fa30472e909aa6ca540627c651e453e8a3dc8be31fc86b78a76d527596768d9dc328ab584ba9c3a838b71683ccf914184f02c9b86595b556edc67e27d003ece8525105a4497b75b2afa1729b6b3cc5fbe8f03bc7adf08bea68d4f1d1f0691b3dccbc1dc52b44e9247326ac61ad3034e66d3cf1f648d0e1cb3cd89df5fa1dd0d1703be6c78093da98bad0c6f1a5632415cadb8ee7e146804d37f48136a81efe221432bde0a79a0b524c56db635c5dfe6220524a5e2028cd055397a26f85545c7eca12b78ca10faa0b2d88717032d7e816bfea16f0f16b589770f19b8fc850cd2e4e84f8717dfe2433fd774ff40c53f428eb7b89e4d52010334c1f9d27944070c42c64b59251c2eb6117f574bab20ae84404836b346d8391b25bf4236dad98b1b63a7e68ec0aef40502d4f8fc7261d7f63444b7622a10816899e8b8c770db00027133d1cf56716172b3f639b59a701b37af34dc76a7de4776ca77cf504ced0257c27a57e77f274532f4b217177aa9cf28b6bb5523adaea03985391a34f67f06edb10f13e83e7a38279b893db6647c8845a4c0259883577a83e4d3e1c945f044f3d1cf1e687369c3ad036ddddca3b6eeb42dab8ee7b9b81ac8f13750324d9583a1b8b5322b47cd4a040cc5d9f823fc517cc2e3e405aea619c281d02727987a7b134a15de2ff0b560c1d0791daf024011b7340df5da612c44e5eed676debec01f2080390883290a1ca257e103f15a8288c2db9e230f0b004600f6fcbc0b1755ceb073cafe3745497d4f8867d2d109856a73e8d16ed037e07fdeee6e3ab7c67e6a59713e8197ba7a7e8282b82e7a0f9d5ddb34e82c20a3bca7d42a3386c233825b5ecae0800d821066169f955c3effc34f0badefaca5cb16723644d00e4c3e3c564ee266c9c53a66a9d9c05dc2106b15f0e4876d42e342641401e4b8730f323aefc626b4af118df949e027c187f312f8240545fe6f7f14f15fb70388da8805f16d27abfae51cdc10071cbb678b26f6f4299ac1fc26a500c0e9c5827c737070880462100695918d3560ef30a3a41df5f9b433da349de06762e0507e14ddbbd0962c245dcc2b9a060bddd3f41aea6824e2a160b4ce4983e04ff88a1e524c56677d71305ebbea4387e46b28dcffbc20fc7db41a85ecb305fd5f204320f0182fe34bc02ff9100ffbac7e0a6a969b621b1486b9787d4e4ad0543bda6c5e43debc84408268df2b1828d14d0bcfcdab1bf52051ae32df0baef37ea77a0e311b0dc817146643c1b5eeffbe85ad49c757291895b08b65ddb39dae54bb1e0639fdc948440e1a792e7d12cc48bdc7f5bca4cc98f8ebf778de000e92bc5e805a709cd9cae0f9830be3597d545561d3ec418e3a8122188d250cccc578600b39b542ea3e1059c2b563486c897842a89b5196a1c35c480847eaea307fc1ea0ec38d904cb0224d2cb8f5d53afd32b917a4c7fea376030e6da56750540dcf6cb23d760149f7c16344a42f7607e097a3cd985e2b35fd77afe22e04fdc5b0f1c2e483e6b7a05b675c9f6035e29723934d60f050c43d381d328247a17829cf025431b8e7c803561508ebb35091b790b72fa5539927a21ad090e2df510662b897307a424cd380cd252f3f231326bcef276adb13f3d52e461ecfefa94ee89fb3473afbb62b0969f73ffb99d67b21444b644e207839278c6598a96dd5fbc1a185e1abb6395ff4927d14b707a2f1d3faf58f85ddf6ecf5329d8561f814c51de8f6c1f19e07f32bf9323ac5fe6f5bace5b476c4ae403085110bd2ad0585b891208a40b54c1fcb84d33d8cc3d9c47cdce5ae1595c4f80007cf4b1df3b644a123076d9adfe65f2d6a1b22393e3d01733a7185b04cc5f8b6204973269affa3494a963efb2dd603e7950d6056610685079e5d95db507a5c935cd306f02398d1564103613cfafc2a0ecdf7f793f0c21113d2686b6362a6e40f22773b24009ab6e91ab26964612cda4a36bd20cc1e9615cd0f73422a2148ab3116537ecb468e4371b3b65c2042d5b8778946d00c8ed0f8ec2bf70e329b66bf0fd1679d88e35d1c72361e3a30495efc0ee9712edfc27f0b6d6fe539bdecd1d2ed3f40e74792e74ef74ee3c8704ca4347af6e0f44d7a622aa6c1bba98f4d14643754156300cb84fff44f40b36835090dd16e719fc99e694b982988d8f24e9c3a8dcc088d95a7862d863c426fa48724bb0b90f8f78096b59735d7ffd708754e825fe405d3a71c2dc42eebd6099e514dccbdc75ac613c194ac9f0602280a83f98a7563bdb7ac498ea6d024d28719dc3ba132f4a5087a1b8d5d0dfb10d837a17150d1877b87eb3689fb6bd60d373111ddfc6a434983b6a5c0533ad4195359510df316f64046c7117e19b31685e6c60d4d875ce1f216280b00a546b19307ad0e381d007783e58b793a3b5b0e6406c992d0801d0df6c99ea6d6d771d9256ef949b507887a923bde54b2eef110861d33c1558870321d30100af179b872abb3c207d311b7759c32ad6d06daeef3280264afd14814569eb10fa9a18a25b78ef0e2aabfe2660403075a0c9a1c6fe3cf9fe82d8dad96ffd3f407cebc7ed4704c8092e5a8d95cc2e438cc606ac1e092d6ef72758c2d60017803caf0ad413bebd45eda701de08717c768da7b440b433d42d04dd0f6f973df16506a246b2032cf24e01e0826a0262da609b87914d4368bb16ab0c35c16c8cfe3a3cc738ef4e3a56a810c8c62cdc40bb121da1aae6c911907e75f42cd5e0a756212135650e0ead81f63ac5f8c521e522d86f0ce45eb8485f9281bb138b93430395f5e5e7db556b46dabfd0b15a9a47f75d65264f5123ab95422c8e5f8ccfbb55d481f8532eeacedc4f45d86da88a31ae93a0230cd3e45ea2e91624eaa5bc3b63d74f868be2fdedd83c1fdcd24f5c9407e7eecf780c5bc74701b792c788a146dd1107348c8f4cb69a39a93081361854d99546261afa9f2e7cdcea7e3e361063ec8f54e47bfb5cb382690b5036d20e8127437e17053ae7dd9ef5f302e3ac9dadcc197e37a346b3845d95e50e2f023fb184c9948a2bd441d02d840b3baa551082bc50f6c2f5591eb2f94ccf04e2e92aa970d2a53ec382f09c3841f15452f331062e3a5041d89eaca6464d24171c394f3f568962cb119a292da9d6d0ebc0836bc6a791260feedd878947863fc2f30bdb908a479091a50f70cb363740add9b19a26056101634e84226c971caab804dda3fc9475774245ba63453bfc01d3a1ea4771003fbac56a7d9ed25c1032ad0a72d71490e9c0f1eb888c04ca2446af89f6debaa211f1cba467c66f9b6a7f4f541587414fa11df2593eaec07e4ff0702e3716b723e172a41a4e58738019e2c0c22bb8b0daa92370a6c2533899dcda7685b4878e452d22c3a679a60e4e8be9e9c57eacd51d914a3dcd28aacc2b79b2e19173d3bfbc47347ad9f79b704e82b4bb1790546763ebcc0e79965aa6b207b86922d1b24279c28ba44742bee3892b74bad562fef0477547190f8af031c33a1a2a32dc4de3cbd3ba7fa0141ec0d55fe0ddc5f23d423488e13811d9b7b03356a9ee70d1e919252d542f4534ad0836817c53a76ed9dc4d45980654952305b73a36ee7964e7f94c7f33c22ae2e9d385505ea0b89fde8015c7a687840029af9eda6b109a4890a5a8ee18842210ba13602811fbaed726eea2d95cdd7834bc9df1db190db964d058b1f985ceb10b50f94fe1a7e65747bbd457b9dff8f80eb00539a7336d2b681619eedf33a09e5f4f96ed3fe12c6e93a7e259386865e6bb8d0778ede5c08c95939e6958adc40eafd8189cfffb2c65f99f4c9344bd7b29e470f32c874fd102b3dccd6bcca3e9c191ba38e3d2c91a0801a2032907f7c2c920085ba8a71b7b9939a0961dc9f796c0187cdbd07919eee538f47a6bddc982cd49c4ffd129e76933de8e158f44e1353c704c26e42891897f58cc8b2349a67859289c30e9852c974c4030d398932ff5a1cda80abaabba6ce93ef20d8d8392d4a0a3e6c7121c9cc24190ab68dcc762df4015eef827acccf11f554ceb6594ac7c2cb6e51ec7c03c3e163b942a750bfc7f4f1539c33073c142f0b1be9a69965399411dd3f6c4df9f5d9912eb3ed09e1191e3093dc1496e88ecd03654e65bc1ecb14ed14a60ebbc913cef32bff7f8f063f43a242e4b3402afca766719d56fd5325b3f1788d49b7fa3742d5e3d1b0c8c7a1ad9ef0100035f7d10a50ae442e4d2d9c6c8417fdd3cb3919b9a2bed815238e33b1161a69aec9ca393a875fd823e8ce9221a276e2b6c13b3427167c99f768b31db70f8f56648071e73e257eaed4756bcf9ef34286b3661a23f93f41eaff6e00b6070257eb736ef34aca3e06002ff8ebfe27ef0f3d65411087707822f40139e3223cc1cad7af9dd21afc0a6632e0e87e838fc3c112a695e1634eccf67dea9e761fcb4c24f0a7ffdf576b1b1c1d5c6734a50b3621a1768330275714d8ee24e91dc05f7e6871fbf2a20b1a7346fbcffb614e251bfd8fef687dfff77d06dea4d1e09ef2fcdbeebe6ffd56f4f9c84ea71e97255f819a5ce34d406435f24d75a1e1f2454410768a5a780d6cda24647a5f968b418c5739aea413e21c8739680824e1cb4f2e23feb5c30abe397e0e0da979fe79918f4bbecee5ebde3df7ec169ff1001f6c8a8410eb5c146d2caa3d36e11c7ae20a651ff4c49999c0c76e19991ef4ebb247fe84970837c9fef1752cf4da64df791adee734ccd1703b1967cda82c63dac5979609062423bac67a1707e7dee077330c40e557b5dfedcc785a3621cc2fd423cce8928a314575b0e3c4765335e778be730f55decab94424099b482cfc29def59c8a7dd88d6799a77390c2fa3af9a6d7d60461a26be36525d32ea93333af46bcd989f50878685f61081e87d42ef12d8c8572eac67ec459d29fdcb1c19ce4429163f726b4d86de1cea274d0084dad4b33d7b1c09541fc568b5444593207912ee3023feffecf97234473aba21b7232e6a497f003be931e5542ab8932dbb9450515ce2daee75b64cdc541981cc18baf1dfeedb6d3c76b00d84f9aaefab307b02334e24f24124432ba8ca8d32b6a51e62bcdaf707689bf170571f2e647c8e020d5a7b0584127a37f91bd0577dee6cd8ce19ca30d65a629937811977d423b849c5be4b3871194d13d1c0030bffd4091944b0a488022c9de8898e1aef743643b1f9913d2c2ab610e470fc6bb314b7a80763ed6ee8f8a585721fb1405f6d0c6472c96c81ed766bd466729178dbd1808002445f9ada8057f9a205ff6d03c2abc99b34f9afec32d14342f6343a02656981d58fb747a3b75b15ad7eb40ddf5d366e548869d231f900e85fd774feafe173e23977d2211692c3a9b67112808758cb1c5a6003548a0d1dfca9781e5a9086e9c53be47ae49a18d2bd0ca12306c2ace63d73dc5b9c2948992d06b900df6206930ba312049a632877b351eb4d9846f5de4c543aea061be8bc207885324a38547e7f3d59349d1ec99162d72b78372230daf2a88bd5458272240a729c737d4da7e4779e26221f1b77df5a7cec0b8b205dbff86f71732eab9423b990f2900a7f1d7420bcc7ad82fed3c1b131fac61c6aff72897c9d01cbc8e3e298a00e480e129cc344a236a38bc6e2616df6abae7c1523bc294e4853441792d57728aa18e234c195082b140f8b73d751f18e209a04b2d246df6e50e78c19e752146d54174a780414bfcd8ac2722d1b642df67df3126720a5e47f9f3749f790db51967dd570d05a50559f8f2308532720ed24d2da0840cdf964b575daf006cb5ed43f0f4d53c03f0962c7ee926d6dc18308d79b6956489fa64f7ecf84e123eeecd35f91fe55b3d8a62828db98ac44a27d72186deeee284d268d951fb1a5413bba5d9885737b54b19fb039a29b2e493bd6177cb8b8b6cffb5d130b4c9653f1ee3046de38b05db5966be41298d0179713bd666bdc720a4892d6656f8abf3d8b0f072d070649f09080f687f63c1ed34b7384c3e6b0bf6c18807109f1cf2fa67f3bef9d16c1ea1c6bff3498f6492b1267170f56c789b957ae4561f3d123af3600beafe857d945e7c8433463dc1e965b03ae85109ce253d331dfa4e3332bbf5d443c29d1936e7fd37c0bf41226c40c0d4c3f8d923f16cb9c57db5f1ebdcefcade110f8b40406d8e4f63d89b86ef0a199d8e21b224d3394322833c95ce1c12c20c08dfe018b4c1e5dd42c29f953a1f6734af19240f4632d01bd12c897ab7af67a58e7f75430a8a4fe6ecab10b4e8bf556e4c82f1bbfbf875fb5986293f516c9fecc4cec5bc439ebfd7e16eb935f111e5eb20012fd5b9f0f9033421c1b6836560065d44084758f3bebf9a3db94e3922fd9a1e4d3fe7f334225864bb23f7b9a8597bc0e627669a8e7a748c09b83ee7b26b630eebf43485b984f6fd7865ce6359f7623d119bf6e645d34e58e2fc7dfc1c1d4baf8c543a57ead500ce6d6a38657585975d34cdd9bfea4ea6b2b46243545f914f8f08d8c1e1e15d3b0a6884813e0947ce2a3a53c628b9fe87e509a0150f32cbe9d2e8b056c2429538326e4bfb499ddc79dbc203f1fff4492d53b02eeb9320ef3195dd7994e1f8c1f293c23a76a3fb13fce83dab58ec091e667b16cc1c557abfba1adbc852abdb1960476099d3b01adadcb883c1023492aae49404338730b75e4834e12c3a3573219da09d93a296ae4f6b7f5b127dcd245cf2549c714a24ba6f411373e1ed0be7516296514a96c6ee3c3ef0a4de11b38ba7c6cf9af282cc9c45b69803e45252a35c5c4cc950bdb4b1764a371a103a0454c7d43a1f60a2ae90b246f9023a2b642382bed9b0c17342974c85778f0fc67fe577bf8d1f944fb70d7fdbc589547c5ca4221772a83e7636c87e29b9445d1186e9b8ad585f1df53b2c92f788251689fe36e5564806ee450c36f1280720d6147c027f2fcc715ebc7ddacd1a016730b03f637d519b31a17d691d0470eae8bd36f944f21317e1bd2cf1c27a8b00bd7b9040b88341dcab28c085564b7a37b6f9e9faa9416db9d1e45f25056c49b8f81520f0382ba1ff8b564f2b0163221e13b54bbdb745c8081cbf9d5a6d94961862696e973f84a009a309e5b6c4dde339dca1df35a2d7d09273e32b59fab9863c03685cbd98454817f395cb899b8e4a0650990d3bd7585807853f12c26e50e1d04168efe08843b49fe8331c15fcb0b7df197be691182aab05bd0de73f9b45fe874654213e93079b68b5515a7db956066df092ecbdb50856aa976e3434e7e0c6f93b478202574555744ad7a67412f8205e6fb1fa7001d2b8a78fdfd01455303b797b450d895829a4d5a2ba62b02a1699a8853e8ceff64576084917b0b9bd90a708d8c22a469a245c03c9c4e8b4d608535eec1c0a8d28c9d379f8908c7a87c2e3792c55a697ceaa1530f66e05aacb55abd2c5e8c4ece658bcbc22a63e6378c3b129887dca929ae39155d9d2a9085ecd049f5514f8eaee2a50215fd10ad34070b4698b316dc88a65f651acaa023288aaf4644f456c886a896ad93a369334ca22e6370f1ed929d48417e0aad3612bc022e0ffb52a21d1fde6602e1f59da088ee4558519551150a59fb4baa4c05bc67222bea465a8cda8e81f897d27dd35dc091c96972e69f0e0744932bfa36816796d83262378ba47ceb4e25915c68eb21d1d56840d6fccc3c681035d0a16d31e1581e10ac338f0db408aa5b29fd4b0ad1b31536c5a0995a976bb165d6ac0f90be033ba95716b36a7482315a9cc79e97827b1abc28dd232c2156f70c4d1a3f1e7b18b5ed02ebffaf7073bb015020725a9e26fd291d43a3a18f2800f2e61b1f38e9b76f9f062a093674dc951eea14732d2d2ac93fe0ebbf79f5882a7aca7ba57e0290a1ff0a15c5a1cacaa78d6b2a56bae3b79a1becf7f837d8e4cf9ab559e08fd3c8ec1f34e48ff4376525defefbf46a85f2325c913adedc433e2e7a4078bc5e2fe9d90b6f62f781a914127744444b7ba0bd67e38b03f38eb4c50fd659e75f6c0983368ffece99a2392fc42e23e8f3b173d7922ef3c1eace06bb8748c0aa13adfc19b2953e196aab2b7834dfdd04f0520c03550bcf6df4ff5bdfb7ce30adbb27adfa0ccd0e1d6895745a21682d5fbe0ad39f5689ececa7d1bdfc8c1b1adb9286a6f0e9793671841244b76e723e94216e9a4ee6f8d2f42843eadcddf36912504bde40e683a38cbf970af2d4183f1c78f49d92b364324dc35b0c75c8cc00bcd02b8f818ec91e1f89df735f933e4ff81fd7c2cdbafdbdfefcb6d2467b0b5910aaa84a051780cbddf3d5fd52910d31be47256e5be39e40fbac5965af40d199210a9f1b7e81ce6c47a4f3b6427ded880db6007a2bedcf66b1ba6a7710dfcd079f5176a00fccc25b12c2a66c371a271710c3dc70cef254c0c341daccc062dc7800570be1c12b082bd63d4c1f2ad7cdb1f08b5a30e1b8973ff18040cbf1320f0d0e95b564fcc94dd68d97a455e464e69c1062b85679623266e3183c94e06eabfc4332d634e727a0038e59109ba347ddee28c068d74134992420d2caacc2c1bb8cf0d8f14dcf8c930d851b08ef59fd13e3ca3c2466cd912a0091d2bceae98f102f065a8566b5068b2b1f5da2edc7406d58ef240f780b45448d432d8c780d68f6d3e0b11a2c07fd6859804c8e28ad5342c666102510082c025e0238991920be52b21a3356aca954bd6eb9e39318701395325c5930271206a7b6871de2a5c37037e45e07ad5c55624f14416063c160012a0817da1134829299e3e370e3181a17c6054ffc56ccf902c36a8dae8d5c0fb6e9ce87d395963012805995e43275268ea440accf2617620e7f14573de637b27ecfd117cc1e1541bc859cbab0158238c5b54da9561aaba7d1875a62264b7650de83b32e588c3f0d0716acb3a1ce6e6cae2a080bbbd4b3e5b3609234c41de6681ccf7f04eef61c9030cb22f65473feb7fc878a5f7fd83475dd2f4b3ed77815ac595867cdcf9a9060b71672024ae3609cadd62d5cdc24483325c58e1ad90f42f012adeb74eacdc1e8021c7470fbf122618bad4c88876ecabe808d531a85de449a8271581be4de728f73f02db9adc53e64d674b90ad61994a4dc4eb16d5e3eb8c20e997247413a366156a7a4edafe662c3c3c0801fca05799c4f878301ab0e9c9fa6cd40b3e092b26788189c625e59e2f69273501502c1a886cb454180812a93f269addf13657a67e282b59e06c27a0618c129497386c99379edd48c6b8a8a8ffbe982ed66f201184e1fa0d8141168211ef4568c4c4720404c893fffe02f92879edc8ceca3bcc7fb6bd62cc4368fd2aeeee7f88f248694e8e7afc4a5c0f5bf8d61625ed227a1b1a0b9e9358b1dcf92f2138fd483127ea5046888b389201555cee98439f08df19745a69e9792057a21b1f4e75e447569916d1301505644f0a8510e970fcf3427cf22ce6f28471d88cd6bce768ca7f4d0d449316b9581221ba501aa5d9fb58fe4d84bb068c8f5f20af2e6fe3c0a3046199a5ec2ef5d6486d7355a76cb5f8790b3415c0931ca7b32ca065e36174280a6ce2ddc919951a7464ac6a4db61ff61de1f8e6c89eb23ded61bf7ddcd32a4fc7db45d0d9891684cb414f759961ad5a41ad3bb14186261fced6feda0479b05bd99a78d639a3066df1f5b08dbeb0c9c27da8f7744e3d98b9d4082b17b10a5f0593119e0cc63c3cb48e253636ee70b3a84faf0876ef0b387670e641b5fd2577fe838c920e2e9b9cb98ac698c832c9fd54f5fef3fff77c06c41cd73346751d4a7c70fe5a4e41a05cbf9bf8fa4edebe16a52c4c11b2416d30537691bfeac03b28c85341cff57c7fdb21bb1858d14aea4e4a0ebd69f1df88770e2dcfa40450ecf8fa3951eebbfec871d043295c0173ce751af4e0650e9842a69f58b0a1a0a1e795cace27e05a821cf4dcef531d31d0ad20f14c75352f6c4e1af3d57ad52fd4f57d52992e2ca6bedf1a7d7a8e97ce59fccfc40976264c844f881b0cfa9c107f351ef2968e53f8c84ec5db6e40adce48a63ea09bcdced80cdd57427f643e15e8f862bb02adf726031fa2653ddfa1a30f9af08abff25da4b45bb8a122dd3cde29cdbe1f6b0c24b32a94fe483420c91e8c130fffcef401fe8f9ef0451036638f6650a337da5bf11d1237f8216be2b09f3c233672c1f87a57ee27fbccd3e828b72172c5f2872dc487665538c96e1dd72ff394598f69e5896dd3b81946ccbd295032d9815f83a0d026cab3c541a1594bdd3e7818b743047817e7dc11d50b0d01751be0525324fe270d7e917acb042f3a35ccdcf27b9497a9a4218908c3ba264e88b02cd43e6b8938f3c13f03b02047125f5f327d0c6e2125eef895aa3d7e66013556a4c56cf3494f20a5a6160048de5ff8e33f605367c062c23f17ce7f489e6d517ece617e1883cac7730f69baba2760b71484a2723a5cf6aedbe68a33ddc3ef7791279d16b785e094d6ffbedb998f54fc060699a373c7dedaeccbaff5c696ee854b6556e04505bfa761079b2c0cad0ab2cafbac49774a7050348adce7584e07df446ee95aa46ef26a6138641b099895f97c90120d3320d098259e62c2ece5025322272071f92a95161fccba3b73d18f0485d3ac91bec90df3b30dbfa6410bf50aa7e74364bc4f71797cfbc681886eaa0621159c0707262a705c8f42cbbd165fe83d4aa3ef8686dc3a603e7c4d192587fa809a7c19cedf30cb51ee58875b508918bc411dc98ff163c9d7ab2f12403f440f876fc5d14ae2cb624a2e7f11c15fa9f49105af3694bc88fe1b3e49997df81e58fb997d339fbbad324e626a583282279cdb5daf409bbeccbc080f91cfb4c576722fed3bf574d918f7846d857245fc73457e6532a1cf991f0c9a41429c3cd33696396a092f4f5aa84ee710d89e9020cc064c670ec4186b427f2c1e6cb6555d4822578236e48d70ac0ac04ef7bdd330ace409a38843ea2e4ace31979df7eba9fc74c35c149c7c6a88b357b92c1b75d59d512013741b45969c54bfefb89f7cff1c35d858f0f1c2a2519b46e33937f56263f6b9ea349760dadf7993966e557cde75b29ee0cfba9d3d7bdc41aa9cf9541695808b3c7833d3aab16352fd8b749e9bd5c457edbac0f92d4cf71a8d96b47682a78edaa0d467ebb9f355e6ba5e65f9c7ae668e30b29dd089ded68f6b2df10fbbe472617596fdda6c8e00e664348a25d8ae7cc4af4c7fb7ff7368f3b1ec0cce33201ad74a300c39067802f3a7669db71d875bc3afe73d429f74eb2cde3d2029fc82e867610dcb487ad1df03d3a876e38c496bed99c4bfb9eb321ee7ca7c8590839b66fc3a4ec78c335bd79153afc23b0a0978f052beb25d714aa11caacf44d80d0b8b97741b5bb84406b7a69c8d57acb06f0f064f9aecc4de8a2c2b95f1d8b8b508e9da3ef637028582f5baa7411be43cd8b143887693c4d2b0c7ca28b3efbf27bcbdcf19be83a27921ad519a6e411e9970957d04374d9d60f8f65774d1a760a9c1eef61ae07a425e66ca735c7cd0ba663982e23600c8f8e287586cd41f1a886d5c21b03a283976332da9781a3ff87885cd539b0077701406a6d62e6515da9e2a048491f05c48a49f0d0436c6a10b36708bcfe0e2df91f851d7343b05ead9dcceefab81dc74432b84687ffcade5b3a5d3ba8744fa58d2fdcb4a366191ad292ae840b9a71855db2c1c3fc2a88ca0cffbcf11cc12ff71192b37b5ac15cb02a1b8247516c52ec9e7e1a0bb82aea480619518057c8ac85799c60c7ac8fd8ec3eb611a467c273ecd9108a56d6029db0420107618de084edc93d09c83b6232f5434949898ae9f826433eb3fbbed52b1509bb48cc084c5acad143b0d62c03953bb0f0a17b7e5f7e7c63e538cb505172b98fff2e166ceb5fcc525a9866879c87dae3be5ab65e3d8d5779da23cabe64ccdeec47d382475412552e0541d460bb19a65a2356f27acc394d441341416ffbf8572018183ac46cd1280022115bb1d51b776c61e005559738821c67226b73beccbdd4b6ef9eaa10df43f76d1d4489c59eac32c2c6977ef2157f84ff7a1d719fb681090cac6702e9d914d36aa8d6b5f0cd2e6378bd83d943bbe8376575b3cd78a7ff63b4424e9a1f7ae415048ecb6202aeb020011108fbebde7cf001c03300ca0135a56044f35a84502ad4e68082de90e6006ef0f5e8f0d1e6480d4025696375cc8dc4e9fed3799e21edd5138d627152ccf01e411dca1f8a83deac5df4ae736aedeb315b8cff43add3ebc97408300ef9c59d20277f77a5b023b79f6979c1f13f8090f613de7b6869cf0366d9cc7fa621b1ea57a83fd090ef76c3daea76c43dea3c9f91b78f84da99882f6db30dc984856055eabcc486a2a92919bf9c9e261a2ae020f53d35c8397e8b5d7fc5d3534f5aa68ce4de91dc068d8fee3898c95bf568b4eb06445d9e806805570ddf189d08211827e90559cb7d4d2dd2b9ed79a2363cc42c151f3077a339b3e2b5512316f6f3e4a30921af1d37ef4c42e1b9888b700fba6ebd4c7483dcbd6ff67fd50fecce49f1158149a069dbf459618efb2d401fa8109bc8a305dae68341ecd47146d2345df8f70dade6e8ac94f78d7b746e660a1f93fc6bd68b288970c507fec3cf080a66f8b7e45ce19bb579a6cd4ecbf97976770074c45be6f7b3c9fd32caff7bca21d921ea418cf19fecfd45d2ec28f8dd8bf4a4485e4e16f07635cc1ff84a0a0baf69aff2f6df0e27aaa28e13add852b72f69979171b47ecc62669807c24689ef34caa5e960c56b802be3338cd6f81b781918f17d670d4ed66358fcc0bf4341f8ac9327f340e67e400886ef9ba03330546d586b71068bcd17241d7270886caecdd8db48b953c1a2a9d4e25bc696684801413a57993883018f478c4838160f4b193bbb62b401cc4f9b35e8388aace39f82be8a9cd704033a7c68c657bb1034802ec60969e1c3f11963c37473c68a6eff74dbddb4e5bcd8bcd8b00f8b0f0e4b8feacbcf7b51bc5ab13675e636dfaacd39606b38f1c24cae8765a027b26dd44da7ef872c20bd0d36e8559f372c6dce3ad666c805258f92336b79cbd96340710e6b1916d0f391804c9846b8860eace1a39cde73784533ccddc1be5700c608f21165eff24fcafe7069f08fdaa371fa84508bc1ce1d89c1c2beeed807e77603e7716dd0cc9824d07c00c829d82eb0da413666f2e1b0eca2ec4ae1a36c2dc6672f0b2ef442e5c0b18ffaa917fdc17d56ceb3512cca352d659dbf9b8629d292cc1a7a630c2648b0998e1b3c3b9bc169a521a614138d89f579f4e7c2714a4f57f57e38730707ef9516f753130d44403af3a277cf413dc3f762aeadb0c918fb1896acfb4c189cec9c36b4671d8803c5be4f1cc12d1c6983e95d8f10509c1f9a61b491ae689195987e856931eb384db3e9688029d3229f7fdb2f27e092a098805cb6e1a2323a63ff6c58f370fbcaac70a1daaadde1c5bc1eb3feb0fd6e68e44fa0280a9897cda9798235bc188622bddeaa401af18f60e2e9525250db8c7e56dc54cf00f4abdbb9a71e00cec93505a1285fdbbc5187567f5acfd81e5cf8173532db56da124a29938b7a60aff8fed7f4bdff67148cb38c6ce416e21c36d0398a1455cd037f537fcae49fa79de829905732728bb1ce510cefc1cd6cb389fb3e35627ad3767e89c03bd2ab8da14c69f08d76e7ce7bb97d941dfdef5061746b857d9993f835f9e96075ef911d65d05cd3cd09419c0f2efb7d9f05ee94528b1cf6006f30158e70a751bac1fc0e9c60c03c231ca8f8f24e438d3a3f3e2b39e6795a452b6d870c0b1f114ed9f5c61c267c4a377600b301b50cd29d29c585b386f9f7439b375e6876d44814710c1e2961aea3e2d23f6bee6077d02b6494e67003cde03953666a98f259340f20c7bbda9d183eca7e0eccba974474b59f00fda4dc586bdf7c3f330fa1a60464adcf296a057e58d090a406253483762ffc07996fb9ac58672030d46deea411067d60afa0aa30082949d64497def516a58d0e490c5cbc86f11d3042938f47f85cbc7ab6fbdffb418fef709f5e52a25cd0ebc5552b1ee1fbc81d0dc764b3f87d06b02e49043395e7ce3fd95393038f3334b41dc55e0ccd7cbcd4d9af5932f58ca0c3ef9d34a670956596bfc06fa066b0a1f2514c4d2f8c228a17c7ae2001c967b200d1ad2dd1f96cffa2edd21836bb2d4a7effba7c99557e14e589f79d6679eca9682f1ad1d2a502b85e6a682f3baaf030bc79814dd5cfb23a65a6cf76895f992fbd935f4d3832a668799d9c55ff087c8f07cb4faa8446f164ba5868164d647f95818287bc120077f3704d6a92257ab6947f809c13bf407ea1f26423f41dd980630610e391b6463c8076c1474e267688f1dd068b5a8563bf7b465d39d8ec1ff8f3a04e0ffdb4fe1612ade3cf00abd10010af1570a6c690eefbfaf256ee7fe32d5c6489f9796bdc46ef0d5cbe7728e204a234d273060f61c74b0219cbe5f3d8da244412230276db2585e4839d71f58c2d0ae270c02b9503a1ddd54aded0afb711f866c0f46a4e62f184104eafa95e47872f3b482b3d35beb1f62fb1a83d35440b05395d0bed173b63fbe4b554f8ae7dedfaf9b0c4a415520446e8a6d93b55ec9c418a45dcc9ac5850b2105d813b8cb335e6419e2b2e1b70cb20c928cafe61c73da6d28ac82316c43db0cb004c52a833b51e6fac2a23c4f88ec53fbe0a4ad71e6e6729aad5070fdc393ed6058b627b4b42f407c07ecf72ab91c237b6345136849661a57ae89b3ca933c68dcb8c5bd0a549ed900c49c0086bd60c3ab7f5379ff5df1148f98e64ae1ad1c24cf0e6fae5ffd9eb852a718df05ee56dac13c675092d7c664d98641c83d45c39c913f9b063670b5dcc9c5adda2103bca43e3b2e36db3d3034056e672c5fd5325e193acd69dc82c58b366769484d6fc2efefa31733418de6b12c725d15277a975623540f84d409405441ceb02a3f84108e8b17712871e22c16243300b42462384f391027f3e03955f4cfd2dcdbad39464a647a90755dcca10644858434f2a12fba3855200a6d8ddee54fe960e70195f3b20f5107e4a1be91eb88f996167dc6e232031b7bcbc85aea537d5907c2c2ac0fb13037b96baae283f6e3577dec72e23adf2de724ac59f52d44ecddc1b6fdac900cfa7528c8371e88296364d2a02c585638f1c759d942493072717e875bd3accd7c5b431045a9ea9900b9db2f06d2a04bda0088f73817ed04b7be29370439d2da35708e2898bc5d1e8434a8b4b012d971087269393e6fd0945ba1ebcd4c687c8ca50eed0767707f7c8b979ec26d2eb1f05620381cc40c537248f49b2785f133cb86d397b8257640dffc276e9462cfd1dd4233f7a95cf01293460543bbb6342ae6c798b95c94838a0bf01ea961453bb899edb63f59e6aaccaadcd2bc98bc9fb30166786921e809f45be7fdded8f031efaf70caa77b06b6882868ae0474c4ffdf190f3b364e3ac4a6fd9f9c1bc2274bd7232c10e004bc03247390412130923c92e5c3dbbd13cd8f8d4598ea616448e8dd4f8195027c241ad34a23ce720e740fd702af0cab783832304b91f2c2ddd14d68cb07c56eb1384768cdf40fc68b55a8eda7e4c05d89475419402b1c3faa212fcb3dcaf9705f55717b88629d5bc7dc7ec3b4b82a734fb62eae974ea7ec1e939372d3fd6843bf3ae60c6fddcc7953a769f7c28766dd1a9039caa2b6fbc75d40b75d87c5c1fb91bbdb8db5000978ce03f7c78aea767600cdc74ea6ae8bb8899979a6b513ef5add17b2c4aef417bf8ef80527fb0cccfd8173f562c66603f99857a2d572bf5b38b7615900ff334b8c9f717c05a30a041b31bfacb9008957f44679bdf2e8de7a12c28739e0114fd40ba21db041860f5686f40a2817b86984f6095d4c63e513550fe0610ea3be5880c43d8c48740ec34b0389a0aa9507f10c7c5640a1e85c852ee202a3dc9546ea9531e90b25c14e3e0b16709497e9ecf5888ca573b30f9f4e5a4f4405fd47fd3a6cfe3410ce724d1baf3117a5b6c8ccee50aa282b8e1b745dfa940e07fc8a8af4d2107f3d808309822774a502a8c774d0ae71b19a077bd704211756babcda608e0d080da536d642428221ed66d949b4d390b44eecb73a7ddb30c66a11f7717f70800fbd11bda5db75cffd68c001d2e7b14fcbae6475142fb33ed76a699ae0c5c2ae54f8c2b25a125d1936e9d707b0c24dfbd62579750d0d1d3c0a92c991892c88caf91ee918e458fb27e1e2fe6b462c873d84e37983fb810cd76204e8d328759a1327456b38336c22e0f34b468807ca5fc79946337fafcb87b06ae8dd93f44bd37771d3eb805d997e9e66cffad20ec9390d31c7d9335c652fb9c195d8f7708b7d3c0f394f828e25f5a653f6ef00d3eac410f65523a9448c30b9f7c8a7752a078ea248be98036769a119fc97909180385d6dd0b431add4d463816ffbc5b72cc680d2b859491499cc680474ad490b7e030ad92e20bfa2003d6f2e5614304cbd4acff73a3565a6afc5e832f9c654a53c645eb08a7ef13a635d5402fc780ac0dc541f1c593a3ce01e5b09cf93d664215400d3fa3f153c44f47e50ef988c73b4abde12e46d3b7b9197b6353204f19d91a309444a528c9937361dbf0f4df64a1e2fbd65ee327cdf5a9bec94996d30700aa5ffb25d664569f736d85cedff8d52c01f623a3f08b6160a3c6ae77734a1a098aee24e2741c28e010788f0042dec0653dadf42e945ae4ad1a18a5b44eb0335deaab18c19d04060dc417ce632aec07f8c665d8c7f40cb0398b2ad685483051c50fd7c580003130af0f687528f83cad622f55ee82d20de0f0121d0eef55d3cf194d91c074fce47e1e9395e4741021b2218996ac9ff71d463a0f083d3a013d61fcc7eb3efb5f915caf4e7af380fa968448f2f00841d904ef2422db4794771c33aae3105f6c9c4176480313f6083cc9357d424af6e74cc213b8186314a3c6020ad52a825ce16027342e446f59e58c7055633d468e1c6d4d578af479390d9c4bb7707ff4cefe279125a3613fed160f8dcff7aa44776fcc6d3323e38842910ea3497af7e54884712f4fecdc356f63752ecb27aa6be4a6ec6380a2e659be18d4f68d4d9fed9e5a8789413c52171b4512be72c62b9d7ecffce452a63ce3dbb6ffe7274b4ac0732cd900e281cdcd95b5410f126a3f6b0bf1fac1661da073b5d55c5423b6f855ff562ce88c0e48b523dcae35f47ec573b5f4d9a168bd469eb9c5eacf9f6b05743ffc6d643082cccf52cc3a94a793a844fe40da8a768c240b82f78310b1a03c5bbf230ffe3646378690d4687e4f30fc06f5310a1027e570be311b8669987c9257ef5eb7e7bc6850fe0295315275e6a37aafa8b1e2e5f93dfb46dd10b43f4bbf9141b116275f01556668f6c3000d3456f49cec0d7fe65a1e113d5893f0848b0da91b3f1208608d0452362081c5dce062d3e7c303b0b40b26c67c1a64304e8e56e1b921f7557650bad38e59faa5c9d741ae8e1fbf504c067f8b1a4071169c77a23e610dee5d87e52e3f7f2cdc9889b928ef06e4e2a78a1785ce1c3d4fae09febdec70c1fde4bc45c1530a4d106cf83b7c7b31d8b7e1f318520ffab2d78b3a68d7f0d28721c373606be86b8ed81b8e7b0dc0b3d5e51bd3b9a10f1947e31ce1514c95753893f7dff997e8f1087993118763012ee3fbca81edb1d72c413a856f65eb13d7613b2be67efd60bee9022b52cbef3bfcfa3539c987d7232770f142df7bfc557b977dfc157cc3be421948cbd6c91fbff204404172481d9d8837f127e7b7da2eda72fb9fbc224b3f841aa948e2d07989b3035c12451be793239f192bd7718137cf4a065c5e3603687393704326dd4abce59986f9c74c59aa6524f6fb490549e8e9e9dec42894d4e5d7ec5de75a538d320461be86979a201264f33f251c39ed65ffa74c57f7bdc803f0fd7b09250c61be7796b41c5e17231fdd706fc5adab8eaca494cc5f06b205c105ea8fe923273db5bbdfea62c3babffe72e5f49b7939be7a9910c55c8e32dc7987ceaad2c40001d5d458728e6a3d676a4aef296d7c3c255ed79a7e56ac50e9c0a376210be388a8676e121a7bb7da61d5ef0c6fab88afed2bf02e33854c43e8f14f1b7811021777bef2cd3f5d965979040b6e09d9fe5501c1612892933f30d23d529de17ff9d4b4718b645eae623f96833ed5452c8ed039a50779bcc665439e7db8f59fbd1325b36fa741d0079c431c1c1db25530e8cba3a7ce0e1bf48776260e1425014cf9a86ae30fe6c5a3fd0144817d8066da3b393514d49d025afca0c356f3e2bebbf8599632b4df74e8e8c52350edc41cd72687ef939269764f6c45a45a3fdd73483778f22053a1d9fd82fcf549d308a201d8dd126347e405a63851cf5554dd6459b412b3e0abd19f9fc9bb42614f1ef7d328e919991d3dcbc8c62bf7971ac9c82ee101a7eaec4e40398b264f35109cab8514d18ec1add6e04f127ecbdb0e8f34014ea340b18302538d95b8a95566678bdfd03213b0138676c67693367012f0b520bb3113645b62502929a944445cb53b08d1915a850e0b18a2d05679cc5c2c80e6274b638d80bee3f1c67572ac9d4b3e6c6ea95606d4c3bb83ba1e4945c240f5a1913ec683e8e9cb4209e3108ebe46ad5b055274bba1752fc606c2bac2b0c6128ca910499571d3a0b6e4800e42404b5d8ce49128d14f5d402f7a25bf298bd6ded4f8cc1d17bbae658bb05a8b69d3fabc7ccf20e173e5b5fe41d3b55dee8ddccb15e65b40126fe786f84eb4b036acfb74311f884548179b0d88dd246306ee42df5ea5913dbac269594b8fe1a0d8f0c4a39cc402846ce941393a7e4e45b0e2173dc1495cbea2913c46467068f06516dafbc78653be054e8d772793582e80e5573e00339041e4788185530b4fa7747d1b236c25581ab3462a235f4c7ada43f5c032e1d63651a3c81bb45f3c7fdf2311e56fd6f2b2f5eba59daf453d338ef5024141ea92b94ae2e79043088880a099a45209e52dd812415c90debe3f1eaa2a3ee8accfd405b0c06c7057ac396f2f92381b338893405fd2b4be98a37a1a9d89c5df54c54e3e049c2b1f8a42af8a58f0cbc272d887e89816b21fbaf5898b8e2dddc58f0974c68f3248ed9f7bd30b566b478d5a96f127eee13c13b787673953e0713a76005fcaf35ea7d3da343da225496a3634f65c6b6a4c8b8a393c9425af20fed69df27f6650ceb5040cff7d997c3d7e722792722472af730b8f55791ee78de29414a1deeea4462be62f9022771d1c61d30739f6db1bd0ef78f0aba2db2131d4714f7f1760a5193b1ea823aba146ebbd1f88d43f90471e5278526966f79bf03a676a11839f4a83cd80eee6c2604723eeb1b4d4b95538c6fa0067993d4bf8d0e6168c414112698bd7b3a35ec98144f245a99aa827d9121c92745ed7ae8b61475f5dcf69bd21ca4bc90c0038b53d5d2bcac6dc8225a182c671c7a3efec9b7e1387aa14f56bc9561feb09a804797f08a42dee6dbb652e90878330701cab614b1efecc9a69204ab859be2826aacd9a27d579634ab888333cfd319354c7f58c6856b583c536a5f09638506b864146241246f2dcc884cb61fe0463229d78f6f0af3857d09c022a59f30b4beeaecccc0bff4f6663a0e48d7dedcc8ead42ec383557f91d86f1e08b3d42fe10605b22e0c7280c69c4ae670eb61c3cc5e7f6f15c5f9f03a07a71ae2511c7cbe54675926f6b96c06b1f8aed70724122b720203d6b18c6af7442ac29f87d33b666743521c1bf3fd36b5d0ff730aea22e5d664280116832f8e16fd4096f24ff6624571cce426066098d5af78d06da04050bca5b4a060faa42e0bd156557f1600ad9978f52c3cd8e4a879910e7c320b971e61b755a2b930f2f150d9f548377769808a5afee0b2051a677b8b0d4d0daec9bbb78cdbc4d623bee1e224585077c7eeb458aceb118ce1bd49e43c2c578c11e2d0c6421f57380d496442893077cc7f0db3b5421d784c86c45f4ade46ff8a008467e4681126f6e4c912778e0d10f9b7dc4bbc4bbe4a0674b1dd0fc83705171cbd12a9f6467806d10a03b7c887c943237333891c99b03d6eeb0466d608ddc9ba7792cfcdf5062f06be7b6191a6572e1a8195aa1bea7abb550969534f05b369a4e882898b79accf58443d5e62c476898c2dcb768f4f348d6cb36b9aacef65658d2aa0a1297b734990108d0ff144c06ea1e9d3a7a79ed58fa3e1745da88a00b4adf3da41ed247e8d61dd98dff68352a157fae7efa20c175eec2344b088a00e030cd14225b4a4c4fee992c1fa7c64647977ae93e2474945178c9765a649f8f8549305e0196068661061032a37093f304c80e11d44eff503e3473bb1ced4d820253f109972ea4794bd0c6bc76410c1be9429d62d360b8ca2b525bb573c591988ca65452ba3b249a03c7d5199ebcc3ba3f79f0306841bdccc34623b5ec71737d06ae1ad642ec13ee393f4741a33a667dab704be3c037fd20a512b11862a897a33813738693e7920a2484f0294ec43eeda8812b5af0b4418387c53144cdc4d9198b803e933dbf23f6355e9d372589108e12f4e6c3c32155469f5eb601fe8537a67dbff3b3a24155ec66c103aa2979cd852421ecd6da2468c03729fe06b587d949835d7a8d5020d62144657cbe1e8db1162206f1790d898a8946f5535820074afc2ae6d3e81c7c7de63ab55ce460c56c2fd3bd29a366f3979e4c6c1910e9d9c9e4931240f6e8873a63c786aea91d678cd96e2d2400d72dab983c2da1170bdeda68bd359dc2f098479a449a62ac3799fdfc188afd96f8cb74f4edc6ee227c0152dae700c436082842c265fdbe48dd5d43f0e99e8b0ac4c6ac640ff3b931a5db2705a3a1f4ba6efb1a15c649eac8230930abae799eb8e2044df54f474a6165c47953d5e9a0dbdec4aba69671f15c776902794dc8557fa7513a33751b3dfb4499342edea0e97d422d196afac73e35e03dc38e2652961dc4dd1d46273574ca5c4b3d00f5b4c71ff7471e4626673f8cb59a27b9c6c8292c5606f7333a539b7eeca3a7ef2a938220315bfa5bb8f5b882723fb7ccfc19250b8391d431f4d1778dadfa3666e65db2bc5deae4a2e02e28f07aad2764d272efe0a6fdc3c0a3cf3808810eeffb207bbac00b5c0870a07283065e6fbdffd7f7fae1ef12cc9fc1eabbef49f286c28cb2cbb3025e866de0448b8454b7e744648750ca065e95579cc1ba156891bd3cd3e8059d2b861f9485e25c186263956337ce10619aaf48aec5e20a5a43524d1971b6200718c18f9a73b25e56ef73f3fceeaea66a0d3c22260f4886714feea12b51e23995dbdde924e184f8d5544a613e2bf43b1ee263a594f70ca88b33059a1d04df485e573b6f37bce6f62adae609afba7c489b67436e5cf91b6936cfe350441439e074e6a7acb27fcf33bb7bece54650d601d3f3f7fe6637504e5ea989001eeede4d6bb988ae749aebab31a933df7bcf6fe32f450b11b76813c2785ca84399329697a06aba3a4d5c69ce27d3985ed318e51908dec231aa343afca754caa305b714f8df450381e2e9ef5d8a11329228775d7cfa59c5b065f36fb4cbad2c0e7282e0db0891ff35367f512b39308b5d25f4804487e40dd9a00cfd71d9c3bccd5597287ec39405dead1d50d6904ee96629b0a3756f30af27d9ecaa8c937f99a1d70391834e10317d8e00e6cc09b4a253eca8fe42e68e1098d4ec9eca411e015aff630791ba73035858b2d043246f0139fe535fb1baf51180d553939dcc56ff930e39c2bcf6abe5ed7cb15b4c6a13fc95b0475ce69896fe20c6ad987d89e5562804f1abb452f2d9dcd16b5b7a87eaf54411ee2c48058d08073a67b21a935840adaf02496c1ca2c3b618c2432f4ae1bbd3e66b47a516281cba014abd6f58cdb590238beb88544b8c21c8b6b6a0cbccb34715fc85cda581604ac8822a1ed680ba9a5f922850f12b2a6e51ec2da7d557e14d729322db62739129624d114ea15c16027bd9248cd706d73b984cc59be405b25677b91f60d61c3aab7867078390c6a0c7165e6cf1cae47f9a68046ec85d10748fe4cacc3662f60397e856042c68f391de94d02cf0b62b2af4eff67bcef61dbfe0b62f061dfaf74ac383ac6713b4c1657abcdf52467fc4bb1b8bfdc25af84f6286864e1f695fc433d2d40d7adf5fe5f55572304c16a20eac4aa45ed8dc1410f65464b82d20b8587e0eb023324cc7709cd39bfef1f9458ce0a4127fb441ae3a7dcadb5c41e834e2c50b49297bab7df75aa39a17ae37d7441915c82783ad0d8fed51ff672f2c54e77b0da6200ed46cd4bf377248a505066426a17eac38ccafbbd40603a88e4a48bc596858dca7631d4aaa02fcc964f60c46a72e9a9f649e90926a4738292fd77f27696de0eece6ea6e89075843613fd5c5d2a413795c6b646eb9ad866303c9e953a89712ec529a41abd9d105cb0e77fd951a7257d5f668546204b2f128799dc7e662c73138deda8e5dc53358a67044e256092a5445f23be2e7c9dfd856439861cdbfc25c1993d861cc0aa05a2bd1e1a924407d85fbdac9d6eab266aa4082a751bcab26887d146e5ed381fae7f7cea4eef8c619138d470dc2c6faff92a762bc1d3465f041e1ff32e1611b303b2f4e49ebdb04744f8c96793e69ae0d27a915a4d966e7977d33af21b425132ad6743f6657d4cd6a786a7b902bfa01c038af536dbb3b7f0e5dd210b29b3b0d3364d57446d131de5510c0fad9987c8d6e0e1d81ed2a46128d778eca411b49539eb548024a13e7477d9a6f1a6cc3dd879f3cb2c89211594b0645fab966b05188b2d34a5319682b1dfe544a9453dbf4d31f6cdf9c4c2ecc27ffadba746cc892f7d2d2080ed858629abce0ef50d4771b8570b79ac764a3ba03b81436a25f4ff4a9b8bc928855c63fed5cd8679a68268751cddfd823cb78f65d74373b2a70334760bee2748c3d55a0a683e00f7f263924afb67dad80156710d28527310609baccd156f6c99629ebd3006aa8559f63448908188386caf629a05306aad3a0fdfc5135858413a3b296a1f1a75a70e1873618bb23f639d267a00c0363046d1d0f6391499d4d8f03503c4debc0e6225a40f20747ce70d8f2b00ce6cb04bec57233433ac958a570759f7c43842bd0c8ba9fa4e798d373331108f074f319f2b5f2771cea7907e9b5a8d4469b9f8966c2b803b47f080fcb5318abe4a0050d08941f0fbf5e0a20ca4cf4731f7263a1a8b7ed32281303242694f007e9846c5fb368421272e06a4af0da5c452f21f9c10acff627e0f106cf1328030aee69789010b87f1db4796e57f69118c9e062d80ad3f06d65c1b6729f6eef17017521956af10e77d6cd26985e5c00ddd29db6cafbb6669199dafb158af4e34b1a46736994323c3e2c09ae7f9340f955494e2758e5e1098967b15cbd9f46058f3ada060ee40d7191ff0930371c6c8d9fa95d593f07f1c5fe1612cb11d00d4b26d343b5fc63548d41007f961f1434dcbb0b6b9562e67deb7b13e40cfe5f2df99c26f7ea7882cf71bbd092c9022231e52feca39781e50e03c8e08e760ff9d9a8efd41f82637e7f93dcf41ffaa7276dc09a91f4ff23c0fe2c1ed4bb5b387ec18809fe5908b283064db48a392f60e03b7eea7f0e12a6dcb5c38689a8b24fcb7eea8407b8aa97afd455cc1a4b9ddfd30dde064cf35ccfef77e8034d494847b305d9598337baf0a386c909e182e67f75bc9f57c58398201a794c4817d3837b580fd4101767ab197e8484e8875ced2d2373bfc30c1a1081cfb74b6dc68046d9cd784d688894cc8d5113b9d1196afa6cb8a28df82361c53300a003000a001600d000202a00800316e91e0f0a564ffd00da939301c9295966a752d0822ff881f14a664ac5b74fdd6f1ad272e38b9576c0b4bcb3f4fa1715eb7f21ef2e4bd77cc0ec89d302fde0a587e1ff9cd07a11de95afdb1b9acdfccce99f743c7765aaad2061437e72fec1559fbf84997b9d05a31708f52d674c775fc479f78485597be3d64d87668f771ea4f2ea9ed6e36783b110b3134f1e12c4bee6e186aaddd9509c5ae859f7affc40c4161df2678c50a5f8a3b43b5586aaea1200dff9e224c42442a9edd96b1302e7ea5f1284ba70357eb185c25963fa94823fc17609e66266a5707ae96df9025570d34c88cc0fcaf6abaef588bded6f29d3f22e03b579db8e1aa0bb317896db3c237893a0323d9c7d69d6f232e807bff04590be0a8fbd74486dd8be2468966ef68ae1117ae4003cff53facbea2eb97aed4c0dcf57b225ed0c3356be957983021d41c14bba73a785a8ca7ab3fe8f0c3889fb2c7a1c96cddced5e6040f2bedcfe41e98442a4683cd01550f49939b4048b432009d36ae8b4997b2bc783546bbc569d8ce64c761937b151fac08ca1a091da6e21a3ea69940c891facb6d6e7ecde2b55a439a56290e2efe33ad89a10dfc97197486f74e900861d0caa6e747a47f9224d33b9d6492d87f4bc42ffd9759247add2f39080e72e8e01ec567dbf83b708d59a351ebec73433a1a19b79e3f3662f3b8ba729afc4a57007e80f044bc4349a880616e71f1c16002b6eb08f9f01a61793b8c133e1f72f1195f30a8a7db4c037cdd850fa1f5014edbb0331863d1692d65bb4039d29d812373d4896f70420bc6a3744cb43d0aa2d5f4f643997ad4fd98cf81081545c2cbfbe90635dae8e75eef2b695d77578390b350fa1fa4b64f226b4be7c54b066db735e4bb027b6e95900091be946d33e0f37ce0337a01e0b001e202d80c8e6bd3d47d89f7585f0b668d5fcdb66a48c0ec63a76ed0766d576f695e1aa12725a40ba549b1bca02a5a9db5038407ecbfa2f05aa8e62f37bbec4ba2c60b43b29d5c3fed2c8ed617cd6165afb60f3696f04baa98647eed8efbb8687523deb0abbb48a85fc20047d12f629d84826dae0a30c7bbcb3bf3787d186d86d9c518718dfcb49cba91be9ce48fda10fec5a78d40037fc7078a23c59e32c8f5cdea85e02e24d989370ff2070ca087a1617fc81867bf855c986b26163deca5bc745f4a25295a061db6d4819d8047cd5545dc66e67ecc24af55fd00deca200ff46dd0c43aa9383ca05e924dff743af420182621c34a3b465cb7b5557d3cef8caa1a8827b1795c1267ada3f2f37a0ec884daccdeb1ee61d61edeb3677d2ebe59f502e99ce9ae652883786c1fe657887824aed13fa852c1e0ee5c4706d583a0d884b1b2ee26a030eb44e9863b5375823f4c12b980dae77dd7c448a0228aea726f4e5a8e11759554f8e6f72e7592acd47a1620dc3883d4af230ca975ca87f03ea13a7569f4aa63687eac28d6dcf83966a546487797c452aaf7d9915a649d01f690bafd8d8558566bad9abfed313e293023d1490207ce971d6d85742566568e1f08a802d70f4f2070acd5e2e8f691b955fce22b59fe94d49bf9817a29c58c74b91a16c5ee50ce7bcff5dd5be05bf250698626f290b9d943fe319d563bdfc712fab57ea2aac38ad57236ccfedc378eae9046b09527d02b5aa5ce8ea38b6b3586e6efa2d7224ab0de5b2f36cdc05d3bf5e6f9ad6921d1671918b8653033828fea71a9bdd92a4175f42c9f634055cc4a89d950e6809350dfe8940b137b3c832672ad4e5664f9ce7213e3848e3f7d5ee88be2e9fc17aed09feda48a3a3a1dc94c9ca45ba0e500f9dbe231e90bec78f91b89336eaf70a90adcca493b67b44c37f8fd6a7f21afd06d6164c8e8af28c726460fd46ef795727b8fb030e5230ba72312dd9abf5ffa5099c1d3d156e1ff43ee649857417fd628c53ee78aa6b8f7fe23fba21da1bebaeaf183803f4dbc7e4cbd9ac0f34a9fc1aa37ea20bf8e78348beadc665d08a469d3d40ed5c307a17140c418ece6fcfb70aa1e43feec6a49762c3504c7769f27679e5fc682a5ecc6749acc043fda3674956f67f1eedaf65b29da8bc899db6d48decb44278c3c0fe17de1b663f4dec396d5d5d1fd352ffdd5426bd2cfd4fe38a026c32314c11deaad8548ab284a6c20abc53952dff633f99fc6163e15778f72314984d5417106034b0a671ceca2ce9a2dba12559014c2039c19c399cc55a4849ab99c565b14b74c0564d2c49f04b910054af2c500516ec8f22538f1456e06a3f88e7ad5f3675bd422fefc03405d594171ed4f9684958f092a8d16c987c84c3862b900a46b92444dfcc108ea44e249a6568dd0b4567ea70f375a5d98c25f4b9ed900bd76eccaebd9c376ea35bd0e073ffb2b7260509f3fbdaa26dfc1c1ca92c0b6f581078d86ada9f1a539a5f36e7f587534fd73078eef37bcc1baa69253fc724d6336c7e261369ba076b2930f708615e32e6e52ee8258a0390d242506cb011c85766f1111b00eeb07a6900b9c6b44f1eed594386cc0301d223a919cec928f18de709dc7906cc6cd4ddda0bafc40e6147381413d96932e97f607c9067754bf0ba9578bb21363e445626ded6c67766f33deae67cdcdc97571deb4eaf1901deeb8f8460ce4e1830b6bd58035c1869862e5e3a55ae10387e61541eaea8d4d6b861e3d4d4b8aadaff30ced160074544f941648e41b72c165d3160538113e7d031de33d89c4aac68c714c13b32c5ac1a8673028090762cc6bb893853f5e2c5dfa13e9ecdd6cf990fad7067c60194b585a11a5e68a767bc8e2c0f0da692d2703e6bf1044a329210dc9335291db43d619a9f94589665dfaf5b59adfefe88004575dc67276a00cd40574ce6c608f0b963d53801030f0323bbef84767988951bf0075d5ae995f86c8affe0bc9898e33bf4496d10e00528e30d669095d0c0bb68ce281ff04fb968bdb2263e8458ed215dc3bb682c916b70531fc9c1e0c157859f4f8bfe18d65ca7676299d2a06a3aeb1e9829a67c904902eca0183f5aaacb1a14ee40898651aab968ef6b3a3a94a6b3e86b8ba60318c08721993225d72840d7e651f5f014d87d6d96e3817a3f9cf899a8a6f40fb39287c8802825df1d0e0cee488399646f1f78a266c8226a73111a6d60b32de2cbf0cba142a640496f90a72b44df20bb41a179ad6011af9bfad62f4f5681104eb31393280558fed38752617d6d43eb2b7a02eb3f50261f4262225a1c49b4bf6ff3f6ebd6abf3457862c8369ed1506f1715d2a901c633af4cf378f757747ed57ddf2c17c445b910d7f6cbf789cb609754c21beedc871aa5ded75ab19f994949d93d06b880788353d908854c21f7607480384b0878fe0d2d4bccfb9ac9d7f65d318f71d4524aa5adc74b49306d200cea4952d60af73b817151bb5119effb0c3bf9429b77c11b19087e89165a951b743e7a9688a7c5a04d813d414c90c78dbdc0425c0bfe9f1293052ae88df1b9a60bab5f7b883ba0c312e234eb026fc0c9869898d982a3ffb2a98da8be4b5308e94e1cf72938a5a7ca0034cf14833731a4959cd18aabb95177cf4c8a092609044b4dd5ccbd49760b6aac03edb8379d53283e0e47e5719b7596db74f6d946d72a6b2b523ad5162aab117132d50ba338c69be3ef4a1f62f50702c18eb62971b8fe5a746beec7b0582c8c127ae69b07f7e6a1f8f4621fb8c1b43b0d157d47f948c1a7b1250c776649ccc9b3310f26110ace85c76a04db80604b6dd06e40000bb691e6a3890966dc50301df01ef9b2c3d2fc4f419f7d72d711bfb9d68963a972859b7972e1a0db3e392b576ca6e23147a232284ecd9494a75bd0e3cdccf63bfc7bec32404e22524557ac751b2dc6aa33cc3929397ec75a8d290c2e0a82527eac5b62c43e3b384ce3eb5dfebeaf86b55fd457161df2004d4c8419a4b3d24becc1f17aa2e288db63b55a96b3416d4a3b2bfc9a6c6bb8c7c1f1cc0a94273161888d07a91227387c33d240b17fb27c8a8071ade1f11b4073e6e869efe27347e52aef54cd3d482767a30b846f6863c0ce6d6c026d835300db594635392134032552511f85b82150d57ab3ceb49c803916248c1ac1cff59c1cc29504c1017d1d7b6789eb8858c545c2f43f8493a4bfd3d9ee9894a5d6cf8e5400f8f97a69414e680891cb32823e8113adb154ad77f02ec3174456121aeb04d15046018c376791155f2e45f02f1c3aedcdb5dc9d3582a3487ccd9a4cc71ed0e22b41079b1b70cc380e5f47cd0d2e627368287346d0efc433084183be92fef49e0cfea93d7ad341f119dd2feec3d19156250b7a31133e812511f34dd00019c5205934c22527f2ada2e0f99d875e3a90a0ca489fa42ccbd732aa3c99939183dc541223fd1839c6601ef004f51495ce73e86b17cd991086d7969f2b2768c1bd5d72c79476d491067443438ec60548737cf538081a99ca9338353b5600db9d4a3ec8b9d0c568965908e8d44f3467aaf654f592c9b10ed32b65e690172fe34828afe285e994f115291396530f30c82e8c283dd8a8f1b60427a928fa3a756c19a676a519c079f0a573d58551edc3dcd2fe69c78cb735dab07decb3b2781e6c41feb573e1f823ee2c13dae7e46444e892cc49ab1808c3867bbd6ca0746be316138a0b7ce661a87d183b083b75b922006caa161c5c08dabc0da06a64a4edc96c4ebd47545f378e40831468b4dc659a272ea6ad15b7e891c090933ef3371d9166b7dd71b0c4281671ae24bce535ddfdb13e43d64d500b1c1ca476a362e189d34ac6ba856c5e6832fd003e6783fed439026c0d2eed3e0df85fed9bc6a4b7f1647f30047706e58dfeadae31d0ba81d5db7c2354bdb3dd4f4dc8c6448a9c3a054a93268f4e1010f08a3ae782dc87c35491ce8f784e11da89c83a80c5d90e3aa1a20cd9e38d46ecdefebd25838864d442d26409117168fc07a14d71813ca0313502a8f3f9935d3c0017e56a77edbd19147296020709ba08fc083f94c955b36539bb7bacb187167b80792442496a923d747b00fdbfa5bb9d3b7bd5e38b1e4ef45092071a792891072e8fa20f96ba8cfb69f2ed636eede3227367a7585b8bc7173c4cc0c3c42368cb8ead345bc02da557cf3d585e7ecf2b9deaec73d19e8414940321241588fa5026fa438788866a0734a6776672d5cc0cec3d5f2edb3b9d65747cc99cd0b1778e4d3303cdf2f27099c3c0325f1f9615e9cc7f1cf378e6ce5c46c7d7352f121daf79915c0900faff7a479cff577247d6d21d64eeb863c97fbd77d0ff9ae560fdbf1c2fff55ced4ffefe424fd57b2cb11b297ecd0c20eabff5bb5a3ea2b931dbf7f3b68a6ba3a12f0f55647ae0e10eac8311bc5af0fd37155e938a2038239103047d57fed73fcc431234e135f75712c90030f39ccc831460edbff5f5d1c69c471260eb2da0b871a38bafce01082838237aa78c3ea1fef468c7778c7bbba07b41456f91e73d58d2c37a6dcd0a00d30da30a18d0dd810830d16d8386283c87f25733f9dc6bc5c6692e293dcdae7eb3db4e7f25549dccfdcedd945eb14cfc0fd3b6919cc6d1228cf3d074d1d058e01e05c8143548d7047b27e6252ef9c797127896bc2916b94fff5b5c61035b27e4113d79d1a55bfa3064e0d9dbe4b6a9e9bea91a601e783b9591a61ea2b0daa3480bebefa0536bdd9fa7f73555fdb74d1a6375c7e466f92fe0d7d34e2fc1f41e38aa28146d2ffde261a05fcffe3805863cc773820d667d470c6ec8c0ecc78f335783985555377a798986165860a5f6fb51a53c5ba9c1913b8d17243839b10dc7c196dbe5a736f6e69cfd645310f962f6b5df456a332b294b15406080878a30e297d66afd2982f9b1b78e0c185b26420800c27c858818c236db4da00d1864a1b2063dc31069c31c08c011c2303366cd818c18604362f31ecf8af3e6c8444b7aed1a8e005c2f4595eb39f48b1d8cd325dbd63b1255deb144669b02471faea7031a64aa7348dc26ad944352eaf2e9b99eb74e5dbda915b2b064e0c08b4cad0eaa245b4c6cd9aab355cd64cad317280ac034c1da0270c337e08a5d5ec3c1b199dd0173eb9e9a22f17ce5d1d1d69595e1cacd67307c7cb474b751f3b0e97b94ba3741b0ac63b5dcf3e8cf92e5896b7e3feffc9ef8401465613ba4d30ac4a30a66660b8bed8fae2ea79367b5fd88efb1117ed5f50f3dc6ad0d06825d550a971a901e285d6bf17545e3cfdd71d2f78febf6661bdce9b84acaaac5e9652175b5f499ca37c971878bbb8aab60baa97eb76d105fdca05b9378f7281f55c503d142e92ec6c01e7ff2bd98d6c81f5ff556a8baaef37975b24556bb7e0f94f03e7d360a569e1abb532998c56b4ab15e9b053afdf1e59d68265794f4ee6232da6ba163c5968fda6ac3d77308b25b2b05f75a6dbe4bc2c807c182361610725710e8b3075ebe0a51a0baaaf583cfdff6f2c8ca071534ddca55434366041834343bfd64ef2bb737ad3717685d68cee4a7c8597285d7e7245ef8a0bac00e3bf623ca356dc6085b642c87f1d476b2d76aa028c2aa8fe2bdff49298ac42c97fede4ace451aaf8af5055a854a17a4685182a6cfaacba29d89822ccff578ec9dcf53ed2f593da296c53e85ce04c0a3752c4f0bf779a16852e8a09b0f2c052022b0b16396ba2e3d010144d401100282e140a3cd1e68584fa130738a184134027ac13449a98a309ad87a242c76ac926e8993767ae3843c3192c678aec1860ebffe9489b68ac7724744442ea2b65800898d062020526624cb8aec6b8c2ba0ae2aab6441f4b44b1b384124be09608a2841925a0fe5f08f338fea12f9779713566855e38a11ae47a9a29591ab34a3d738f98a7338f635fb7424196ee68fa1c7137a374bbecf962a9ab53827b26a9f1d8318f6325252a78592c762b9ada4637895c1240ae7e717ba769b1d8cd8c16334264202103124596a4658931797d348b7ba6be74ece9cc1b553b62ea88a32388bcf93b79df298059000facdeec58dd600582d50e01b42040553d3a72d52ceed72816bbbf608ef69e7b3717ad7c739d5dd4c6a8b975b9b9cfb74d23dc30228d115346d022ba28224c114f8a08abc5fd5aa1201f1b2f771b840b72535fe0b983e70e565cb38bee1eecd22eaf06f5264fdc7f9ad4b8a9e30ec4ba4c56193265a6ca2c512a83001158b59b3b445461222c113a4368e9f41056bf330497ff1f62082021dcfc5fed08c1e5ab105688255508325aff64ccfc3f991ac824fd7fbdc11c19d74e105a40bc1404d54e1049fe7f4c9cffdfe7183463c8e43154ff558f791ae31a03e4ff81c8faaf647680a07a2034c6393176ece0d4f5c962b11b8ec4411c259391b458ec46d27cd8a4c562371f36f300fe872d7e70e107a31f787e681047b94cdce4030b3e3cf940c364852153751800f4c0460f34f470f64079d88287137818daa18d1da8f87ffbca26ad1a197d3a20ee75cc77dd8773b02cc9cb31df1466764cf28e9976180213074c1260b28041327193ee0bdff982bf4ca043153ae8a0c3eddf87cd0cc47a27073239ec7278e500e49f6ed3d2a0e9c5031cf6c081091c64c041041cc627cf5cf0cce932effddae80d40dc3075c3941b10b0818d0d3fecd8e042b52f17f6ed8eebb9027db8675a8bb3e1470d65d400a686df7fb5380a47f14d69808306313450d150c00c59cce065062019f2e842063132ec6428ba3f5d2673ce52580d9e395cde7e06cf1cdd669510552d544955415545a90a12039a188c88414a0c406f7d96c2acb61416f3d11faa712e667bc71a0643171892bce0e685245ed02f8c6fed6e53174c178ab4a0a6053228b430a585a11682b0f086ef1d16645de2e8f244972532f89d2e5e9ecb175cb47009dae14264cb165bacfebb111f36f316ba821d2b58b1020b2b7cb002102d2c6861d2e2da518105159a54e84923052f290420052b296880821d2850f15f2d7f5d9792de0d0507042080140420446507950f54552a8969d5874d9c3b61eb8434279c7042d25f18660a809a9d009c09802e000fec98b08709624cb0b5f2d2ad29edf9eef26ed39789bb8b9ab8290b145978960c4a18a3041e4a482aa1032c6760c1fa7ccdab71b9db5876825004b30461c121536d4c113175c314d554d15712e782d707abbaeec3666ca6c4fab0992dc53025b0d8ee08a8052c03840262f0ff3158b5b6779c0f9b198731dfc9e898e49499e7c691f9fe2ec73829a0ffff73ebf4f2ffffeafd8c72fe2b812931b199f55f7914f36be4668e316d5f03663007c41a87cd608ea4e1cc6f64c6e1cccd7e4da86242583335b3c091254c35739991b29cd546963b4b922c40be92b8842d96c212aa4a48fa2f81ab81c5eaff93b0f0d059504d904bb0289cdafaaf98df5e96d77769309c9af2f23f65a78404734031802c84c0df3fd055cbf2fa3adddba4d966525a7c2b498579a9258ffb792475c05712632cb949e7738b93eabf9e40d59cc2d96cb3dba1994d093a9cb093397b972a77c7fc5696e7ae93df25e07038d6bb5bfef45f494c7b374eaff4ccac6d57b45c09fab9c9a56e9b94c4e5ee7aebb6e6b34a86bf2a0ed35bf378b7dc456d29fd4bf235092c5bca3b73bf7047b17f9df274e69b735c37d04c6bd59da69ae9f17d6c7c29f8824a389803758eaa1dd998e8cd5294ef5e3bc7529787250fe5d37f2d79f6521496e5dd7217d54394ad5abacd282015eb4d827944c209ffff34db8c9ae5ed32fc0bde623159bf49b25b51afdf244ceea3dc940be839d19ba147ebf1f0e4e0e9c0a3c20bdac5f9d1e2acb975596e3df21ca9716ef4a9758647a51d98ffdf25c16ce01db00e47109259ffbdeb8bb38f0fb526dff5ab336de4944e8ace486e8d9c0d39107249ee19d789f05eb943f787edc262b11ce0ce0871070871617022e08258d9c38a15cc0677f98399fcfa6ea71877fe7cb7d77b758cf57e6985b27ae3283466cdf276e0e554679f4ad22324e6714b7d6abf3c9ea53e40ec4387d47dea3649833a1fa94f235aa1ec0f85b2b7d7793a5b91b242046a0d9497af3f6cfab02e77a8a1ff7f4b75de65cc7f18a94a5905032a5c5099a24205c99433a6bc306584294323c019a1cc085446d879d2aa50169fdcf45d12fbf0ad665c4d9de92eeb4b2994d5653316b354f0e9828ac52c151d27b7a9bb519e76788222a58fffeaa27c5639d6c0ddb30fc9d7b30c93fb88c453b7c44c25e65b174ab1752914f0947a86e224c67f9d39dd9c8e38f544c9238a9830cacff24b61bb5cdb6d4ad238b6349334da8d707c7323bc8d71eb12de8cc29baca98da61a6aa7dceabddb556fbd7937172c390e9663c799efdba33d9316c7bb39730a6b29cc7fb1d88dbcb857e66bbe9ae8e8e42269a43e374fe291678ee3b2be92215398df6da6a9af4c400fdc5a0ad37f15818cc63f8ea5b69226119644789a8900544500f2a1cd46c35732e36c08d4d8d4ac086b586a4098ded3b684c412d5925e02fa587f0529bd51a2416904a520509480c2a5c248de84e39d6db216eb274b3cd13d59e0ff13f8f0c9014ece70428493149c3439095ea0595eba7b79c78a6b1e6dcf2151589fd919ee86b41e6859425aa6edfc7f0c86ebbb4cba4a7a4ad2811202560821b83e98e3bbc95613344daa824d9af03071f353d7c7c40bf63149fa0f320182e4e6bf6a1cb5790ec9cb77a4a719920e3dd23afaf2ff7db7cdbecb232323378cc2184d31d2998151cf1da43498d39bc463e79bc4313258f6d059389332a3397797785912b524b6040310b4fe6b4f4fcfad5663022129048101d91ab21e42991559051f50f1726d4ac30f46f80082a233455bc2a2a8a29ea2204a7aceb775bbb414d6d343af92590c8f308615cb12c66e1506f3000b0f82a107503c3820c913611215ea8be2dcc5598a73b90ee074504407be0e8088d80889b2422217be9eba7bea3ae6d61acd3808830314420ea6381832b4c5d0008644181a82440d922d2192a0ffeffdfe28ce71939e3b6871bb8d1b427da8b5f487da20ae772138a110d6bf9097ff1a0a3d09b978fc066585415e8292aed9cd73cf80f8d81a076f2800c180747e041e39e2083f82a4ee51771b977c9bd7d21ded0ceb0b35090cced7cef386e9de83fac25008614d962ecd926eb4898912ed469930d594e8528d22318102657644991835d1924899109293a7334fe711ebffe48d5575b43fd2ff89f3b5bf30df3e170deafb1366b77f7a0fc4bbf087672f853e702ae953f53ff3b17df5e1f97f655d20c7e42c7c51e5a8694467aa8486afa7af7c53a1dbebff9f977bcd850574bd50cd9e4b59d1d4f515d152c4a988920dead8a08c7003321b3411e183c812447e4490febfe3123635654a595efa439bec58e6a89a0db223ee6714dde39dfb0e29c09030e110010c5132a4811e37c21e227aaa843d409ef6b3b4514a8fa6d01f3ac555ab18766948efa057140c2d814285148130833032a0fa7f4a474b61c172f3607ad311030f3058e002362e18407881ed8208849021c4082150421c1823b4404d688196d002a6d0820bc20ab0082be8153c5500840224420ac6841448a180011e2e7882b3232a31ea437ff02e97b49b3956edaba9a976a4c9ba8a58ababb4c955b3e3d854962e9a7b367f2f17ed14c6b7edb69fe58f8ed6065118b5bb7cf99e1569dc03ea7c7de1046e3e9cc08bc98d7c38416f827e811f7c28419cff0f253042825e28c14cdc0ac5abffff99c8e57f168abf2ada44a09ba813c609432d8c9b8464be29b4ffe3a74eb7c390e73fa82fad4ae1ced6ffbf041fee68853b56e14ed5d71d9beb77be2902381170f155262b9ada463257eeecb9b343f0e667101ca0f326d429a383756ea18e90ce030fe8f1c098f0812af505d38d30cb64e469a781588fa10354a103b6d0010dc206daf87f007cd8401a0d50d1400b0d9cd04053d8001006900819a8aa2f1bc451b8586c577335bd6c94beba111285d50e8391e5a6d66673d55e40ac5db41b099a3d77769be33cdea541dd1788024cb840090b38850b1c102a7044a80009a102b250019d8741d9bdd3342ae4f599b6dac2203b0c32250c127ac0411092da010e81680981140199204c008e300123c204aa9ec952582e4c20294c80e7bfd69e3b7b8880508840901001044200688500480300600800a010003e0784718055b52f26fa439f5c354b71101dedab89c95a8bf1aed697cbc4bcfa72f5186c9be7a53897ef0fe3dd2ef3bd9bddd0003d20f8d080364203aa080d20131ad0141a208605c829404c580094f0c71e5f7b13fd9d662c76b33e6b4baac4c992fa863f7ae18f26e18f22e10f0ac28f23fc1dc2ef127e511f73fa78f34fbc3e7afaf83957735298639bb3644e057320e0e3007c70a99dfed0266b852c85551826b70e669f6024bfbd9e3b3bb54da40bc62d138c52db4403fcfae868fb2cce56128d8305d3671ca93849ffb5cab1f535cb81f5726cf9c7c921c611a7963b0e20e29012c70f38848083eaabe5b1d8367376b769af3c0783e5b9437a63cd1b27bce17a230337b2dc38c18d1f371668c34c1bbb366e6cf4c1061998f34c831948ea4b06e99847dfa8738977b4dab9c6baa7cb3b1aad5b6fbbcd5186b1e4a658ec46c7974c26bb15f1749eba4ae62e9bcc5d768ec98b7b38d90b4ed6c3a98263fbafc00ebcc1f935b4fed7a046746d8daaa5dfaf71fb7ff2aad1871a6dfe5f8dad46086af0fc63a9edfb5d60532c7623c96bd338401a574f92b358ec66e2591ab4341a78a3554d7dcd29bc4d9b0de228fabbc03755e11ba537ae8e0619684c8186feafb6fb2cedddc45b771c44c3e88c38ff729dd1cff0c08c2d1c745d9ece52179739ea3203cb0cfb66e8287193e5c6ea9fcc6e7a5fbbedbb7db91b9b797d3d2bb90152fb2ed19441a68c5e19495f06fd100173fc63c90878820010c868830c2c3254f851a23c9d6d647c1b356d826883dbdc360d8c71660c2fff97e6ce265a6f6cccb0d1c286890d0562a0214612627431686234a0e546cb072d520b8916903575ac19c01a14d620bd92d1aed63f60aa313149bad198c48c90826ccff66585e80732a1207d79eec5f7cb0ad122eaa2353c009230f608638e30be84f114c61130f800c30a306000e30a1844d27c31c517515f846a9a08d574351a7841871761bc7812666d3d45027bd9209b750951a35dcdba68474261f4655f41364b47fb52a231eba25d88c2846c76b7836c19ef603d93678d846cf48706d11f6ac44f3b443ff880be5cb1d86ee376e7b1d8cd5294d69ffa53492c952f90e29cee76bd33e6b9cb37a96f173c74a1d445045d24c005125cbc10858b0bb808c2c56fb1660b14b6b8a59993c68e304d99344f698ed21cd1420c2d7cd0c2a605d0ff636dad92ab666db6bc9472a099b9ab66e9685f4a945a1b106b6a97ac925da23fd4a6041b5279575f5fcfcc409d0e7613f3ca7387f9d541d96ea95033e39ec9bbdad2213558624ebbb6487a5792e29c35c546f4977b1d4b658143165ab258ca22812c7e6041c4ffaed669cca7fbf4a9ebb3d95c351af3a1584ce98cc592ffaf4634163c51d0c4f90fd160fda321533b1a9d06628dc656fb4e86e68a2bf20a2ef55783ed25aae48a5e7805fdaf405c2c760bada8590142156b54f1a58a5d154e55204085175464a18257ad893b8ee3f245a3ae198b59badb2e5a5f354bb3c1dc1dc912fb7254bfb7a87e4d199410122a781eeba6d8fa29acc229aa483c4592ff1bd5bb146ebe02b18eead794428aaaaf522491590a200fd35b875184717f19fbee5254bf375914bfbf611421d60b2b0b8bcc7f25b1a8b096604960a9d0a8ee0beaad6732ecbb4b396866992c16d365287e2114c127e0fcbf9ec07aa2ea770f183ef1abb42792c462b7279cb8aa79e704d557276c5f9d7055276e1356df9bf8fdd7739b4d2cf926787a3e03a7d228ddd2992b7e86ea4cd21903a0b906e08275ee66802524135b4c50e193d1e8c8040357695ca1f9aff6cabcfae08a678937d5363d29cbdc97a05aa20325e850828750092c4a24a94a5e58eff2528e8178079c254146125e92302109a1248a98c93223e6ab6dc2516b668a191090d8fa4ade1aad7d4b9133247a40a206246648547044968fe9887dc4ee88080ad04601c014e0cafed19ee9ad1b89d2f96cb2978bdff2cacaf21ef528dde5264e675911f6e59b6ca4a302231d473a8e244d26e36637bb912226dfbfdfd9713afb703afba4f879a4b38fa7b38fcb64b7225cfece5b910e3b9134dc35228ede4658fdcc88a96a499a35e2a9067194b9978c20229b6231d9ef37eb9792b422cc22ac14a153268d32539479a10c07968ea31d6db794c911264f9eb86ab632a92e6a7b103fb14f98d01ffac455b3380546d998e4ec5846c724671199711ce31fc744e8f4c221dcfcff0f613544d5104f43f07c0d85d8aa24e6098146882a2184709181f35fc9585532543adc69bf3d324f644232f4fa82082308aaaf63994d7de61ec4d3fff374ee4100d97a0c1add49eabdf518aaaf24dfbbdd1d637f0c2d331070f0145e02e2ea4320aa2a1053ff0fc4d3ff3b01e17a3170423157525ec4f4fe5f2726898ad991117120945496f7680a939af6dbdc3680cac60f667ea8e187da0faeaf967c59ebf281071f62f001c90707c2b8e1d4e9e5e472da2676da26760aa31406d6431ba49b73301896d23cb8e101061ea2f4b183103b60d9e1680720601000060d982b606c602cf8d2e6cbd5971cbe00bf98ff3e3e58aa0c5edfe5586ac3603152df1d4ddf5ab1b9a5b636cbb3c328df74dcd1be00d5ae83153a78d161b799e84b9b394891430e403958e0058d971fbc287929000702843854f5dbbb4cb72da56467bee948626b39c661c80d5aff7fc3ee06211be8b0c1cd5b5c2c26bb15c5629d7e407fbfd0869c0d3a358c514398b0862e350c551286f90f47031b210d6668508186dfff101a1a98018b19bacc50e5eb8839efd25e5ecdcb4d333020835628830ea10c31c850f4d5be8236d0d4550d5195a5ca15c318313011432e860d609803862560c0c1c0c0e5bb499020e5ed3d9ece96eece1cd69bc4658e5f1d0c162c83e50b48bc90e505272f087121cbdc3bbacf2b5bc9c6c18bebb962734bd9ede482052dc4d1c20d2d3461610e16c2b050020b1bb000a44b01c22e2774f1e1d2e675366229ac1bb13dd3b4a7a5e696c2fd2e792468660da3946faa2487b90b898b932d7a6c59638b9a2d02d8e264853a56c0aabbcc7167b2244fdf6a07ee1586b4bcd1c2464b172d4fb4e8a870c70f2a2c852a8090421cf54561fade9ee9a2f6f573d52cced158497176ec7987b9e5e97ca9de3b5a95c2e6b9353fcd8e83b707afc6e6760a96e535352eaf997f578724ebd8dc4eae221d7632b7d491ec6a1cbcb25b51efb25bd10e67f6dd1b22ca587267e7616ce24e5c96389eeb3e5cea868832df53434439c84d8d8590e4b31c22cafdf4613e44943970731edf4344d9d4badffe0d11e5ce7326ee57f01051c65a9bbc21a2dc4fb31c22ca389eeb9c24ef10513e8788f2ef72a010923c44944ddc71a5b9f91051c662ea8688f22e0fe1424872278788868832ae3c7d4344b93ca5323944944fdeeb4344b963de105136310f87f5ee976796b7e338fe01b7c6e1a86be27c9bd49793e5c63cde71e5d56133981bc27197eb2c15cc54a16c2c86cdcb75960ae5beb393fa52286ba990d804ce64b7a2a87e6f51fd9abf6dca8af4ce26c6923bc692bb4c96a4f3d89535aee47025caeff71be10764a3b165d84ab41b567a048d812fce9b66e6408ea536b6b493c1d2de0b84b2b15854bf375f50dfdc2fb0d3f166caf73571184be636aadfdbedb8c7cb308c77b01d895d514633e35c39a375ebdac4c489765b6272b444a32330cd6e4c966a4ab4b6d4ed91af26fa8a945c94c1b2272aceff88cb5fd6e9cc7fbb97f7776a5add3d58eadd336d6c46ab99e92f47f5cb6fcfccc09c33a770bf3a9d9b4499f9a8a7a82424504102d557acf78c840c721699cb07f57d41599b92a5e2a21c2be5a7cfaeef6d7deda1f9ef55fd7ab6b35f1dce057baedef3f8cdfa7f1e99caa3faffcab3ff95b7e4bf8a3c9dff17776f7ed7b41b773d580d9e0143f90a041494548d90783ae736ff515a7905527eb1ce5076bb475f2e2b927890498864183289fcff6a84448325cedcecd9c5dba58f1ebba575ea2ad99ec72051e7842e8c4ed44d904b53cddb67d602b1ee3adf7acdbb73622ace776adfa9c5dbe526b93f360c6b8275e1bec059e19e7042ac6461e58c951e30bfa8505a6969de71d439868456116a0050b52a7d8855e254d1a14a09558a50f182ca1154aa7cddbcebebb4badb8db8974ce8c94d6cc79e477d72ccad796e71ca11e21415c42948532818018dbeb8d4970c8eb080f894c49395a71f296048a9bf0e8542599cf93be98e4999f91e8fe0deed37ab525e4e7a38b989e2459420a230450921ca04e2ad0af12680db78b340bc01694aa2c98ad85484290b97386c6e1c2e868496a54f4aed3cfbf8dc6a4b3e3e4386d40d3429edb9ee97ab48879d38362fee4c404498238255b9f9a637dfdd4c04aaff1ad4358940e4ff83b960cebcb8d3992d8eed8bcd494d4e4dccd79e9981dbc49ad43bb779335aef14a6789b1788758d4b2d49b5966fba1467a9ccd20baa0a952a2e1fc7becd77afbdb4c4f3d55a7a2989315752bafaffaaf4fbda332731b72d51d2f9af50b2a090f99f9308a50765498502646b2d7d323d21f364ea959e24119fd0cab1b99da2f4e54eacb6e8848b139f131e7b2f2ea948eb342f2fd27e220db869409292acfe6b52af2cf72f842842582184104200a1091c4da2109bb8f05f5ffa02b18bdad7d4f5b96a96d298bd348a4da6262f2672884cb4b699b7be3491c91626529814bda2f4e5389d7d4858482820e99018f8af51faf2bc39c612a5af7991c423258eaa8eecefb652946874c68804233a93f3ff412fcea818ed2b88627ee998631607afa5b05b734a1d072f0ef39b9bd987aa32ee1e904a153ae35982f55f73bf4becff9913972cd9b6253a48342a82c0e6ff65b2de6f7924db42d68228db1fc08ade145151e4138b9a8afed25e9677ecf82a794154b24509d0ffd77d448c85896931e6daa2075b3c30c1830d92b4e1bb031fc40e70073a446210392112e54054428ea8894804fbdfbfda2d7596c26a59ee18a9effed5b2dc3f3af67bc4d3481c407971688c212e434890e481240b24412444840e206445ebf5e532854ea10cc4a03541565f839a7a262dcf6210d5ff2cc8f6ff391168ebdf5e8ead080444f55fe91581aa1c89e3089afffad219783919e4d7d467b9a5ee91a96d1e713a52b134ce2d8995cb57a0ad1689b03e6056db0ceaeb824dfdd77e05935784b96ab97754cf9c8ae31a2394ceb3d11c5db573acdbe2cfd6bf5c74f663f5ffe20f95167f6cfd3cf2e3f249f37a5d26a20f113e4fff7afb344f8b3e406ff3f9e7fbf562f28a4074a1a93c4d45579877f5a0acc65044174f2d6f9f1981639a7bf75f0d887591208a9c453610379043dca0890db46c30f424d67993a0f1af168b592a3e35d31f4a49736bead371cf14897041248b480436248c2154ffb5a7a7e3a82e45af7939e517d75766aab9781687040d59a027eb2b25b3a53ebb6dc92cf61cd1404b832fa20651a20656340842dff8ffba4dfac2e11c6e9bb8a89c897d9857a1b1dcfb578362d8bca645caa04d062f881910612007063a88184c61b0c3a047c4c0820bacf8ff0f5ebca0cb05572ea0a2103942d87411b2a3512c79dc3bf6c2bd3ca4f2285d8ea2243633def1bdbbd8d7bb49a3b51bb14214b0c08c68410dffb5dc9b52d102a50af0a8c0870a441029d8a35a8b710772dccf600efb76a74714740a9c8814bc441e3e78c21079b2441eaa0a65b1addcfbd7cbbd6d531737757d306aeecd34c19c09d64ca0459c60e8ffcbd24742594b652483b8b7f53612253842022ba20440a2688688a5b099cf2b9d16659769751cc7308e182a218655621882b8a3c70e13e24e0ce2ce0a3b48c41d0d2248f32486e1da35597d8ee890fa1a39d6510e411f22044188105481800251e78da86346d4b1a2ce4dd4f9213e50467cc0870754b0b81fae64d4b40a65a94f858ac52c15da7daaef42a13ebbd3dd7de810f10110c4079644076a70a0141db0406ca08fff11caf672ef1f2df7fe755c2779bfa54c762bba9c49cfcc527ab965b7a2cb316e6b8e4b1e6f9b38736f2e2b2a37c7e6353bc7220329880cc082b911e31dcd7626932d90c5f3e20256e20253ffe20274b6801515b8caa2025bea2d881931c896204d82182002c1128178f9aab798801e62024d245042024e0944202220c78c8840504420c97f0fd5992cd28f68de4bf580350e10e200de01444403f830e08c015844034ad100971a05882940271640452c80e7ff672ffe58f3238cf843ff18c57f437c23c4078acf81f83a5f3766d273fd9dc192674aea2b85f671c11c3ce6383107cb9c2a73329813f2a1c587152af061850f217b68fdbf6e6c528bd03eda6c2dbfb406617b04d1830c3d5808f540410f233d84e441471e0408f3a8210fa63c20c0830d1e5ff050c19cc23d3ddba419caeab214ce45a96327f5b9e34568b0dc5ab395668bcb5693ad5702da4840990470a9346629df774c71c70d77ac7007d21d3f42397bc8b9928345ce939c0be4e8d8f1851d63fe6bd05218ed99f5d06de619ad3db3a7278a6f60b6ed7293a0295360a4632c265ba2635291ec725c47b00ed7ffd77be940d32fc7744c753a9efee9006e4dc93a879939707324fd575a350eb135a8349545d458aa19000000500053100000181c24140dc8a462c1644cde3e14000051b47468dd9836d1649d54c81863600600004000400002775171a3e24156cadb10d96309df5fbd43f80c71507817fd707ca2c8e3c1d2c485cc87081f139026bb817ed313d887db583c563a9ebaeb5bb7465f92774d263f8d759f1c612a3d75c6e395969eef524e684d403039fded06032b9b8b89b1ad7039aab78d2d2f47692ba1e789b1137e6601a9be07bc8d895542b0971e36218b2f75be69489b84c1fe475eb70f9ee2f68d428f3f249d366b89df4583a6d4774b7559d03c9bdb48ed737f74ed6c625826a0006c7c21caf0724b86ad3fb229fceec5e1ccf3fd0df78cdd3e6eee6eedee483f8216b7e4a2dd6eeaaed2f7bd3bebe526a482e5357980d1cfe321c504e645716361f4761349d1818a174b657cc657a067877a3280ac2c82cf37b81a2e08cc74e7f20c4ebb55bda8aa05e39fb1f2864b60b424dfe96a000b7f94d84597095048a0101dd28cf37f4742b1717e4d0aa7bb14e47820446962155d27e3b9561af6014400add1ce74c2806852823a07782bd282522c52989188714c5fb60f17be64700c3be4e71ad3e92bd352738e569f5248fde93fbd02fbb45553ea760903b68a5da9b11740df2c9d7b33225a60b18279bc82351f7f7f974a1683920d4c981b42fe32395ec054b2949f6a39a3ace33cd21b2e356352a0a25343403a9037cc39166fedfce56c18b2b50eb9f6b8dbda5265a0e0bf4ce92e6aa0aa908736a8a57602b8a15f037d8171cc42a87db3094f7166539737a90b43a84344dc96d1377cfbc4f181761ebf30d4837fed1d28f2f0b81a23b59bce23fb8fa34a3fc048d61b171cf93ca840a4c69395ac7b4fad4e17b519e9ef94c1f3936f56fdff8441122b00ac6e16696e10909e7aa894156852d1bb91404fde7f1cd93c394617050641433236c309b9e31659f93dbaf9c143e3626036d248dc84a010620012796412a8d6661c6ca4a8a81a74b82e95e32a80f3d7694369ece556fe2148be8bcb1f3e2d4ee2f78501e83e229e331eae567489bfc48ead1b6133be1b837cc62c0e0de3a38a284ae8509a241e0ea0ca3d65b2d287c6e38d0ff6c5cfcba3375cd7cfc2f82f6f26596c900524ef851710d66d278b03d4903f76b00ddaa26ef7fcb507a889d598e915baa8ed2bd0fc63e68aa2cc46d96b8fb92625caa608a42be71154f9808222a06d62f4941b4907235e5e0cb6fc428297fa84e3914fcb8aa21f35eb1f4e3efa48d33287e4b1c525426070d5ecbf09e71ddd2051dd77b7b0d7dedda1b2cbcbb55ab504970ea21c04635b7f6d536c203f3d8a4cbead514548e382222dfd9723479271b35cf7887374495ba6a099ee386472ff770698215588c601b4dba6a8c981967ffbda66cc348bb3639bb1bc38bc1bdd7048808057c1bf480403ad0644329cec5701f92c861dc4ceae7755e8b3596465cfcc913fdfda9e5c348a921d980833cb0aabf0d0af2d233ac23c7f743e86033314c91709ba6756b364ed92bdc9d52003e54953eb85354500b251666f971fad0521fd20812e405fccd99035add92a61782ae43bb93141232aad6cfd8ad71087158ccae296b54447278c839e8536f0e86a55e2ad13e7c86df20524abe12cdccd9d45b144ef69afb4d6b1e0d50b99ddbd7187b808395f77aeb60e6f042c8fd21e501147273969bc6b4abc81744d90005d96c48a4b57bf18b91fe7c1d7a5258fa78b2879d15b9c6491b336d8b5b549573ee5c65930f4049f751bb77edb9bbf836980e34317c2314a1abcec9b11beb4209ee15943d8bff61b0b6ade42d640bd7ebe44ad29394855fc3fe6b19ac91b4d55002d62887bfe83fc7f431fbc3b5354f67e420a455fae7b4f572cd23e2d8a22cdce13765a36c309f4356977becf04e0cd9ebe9644c939131b62502b0676e4b20201db11e5fa8758c84a2f739c6ac1da4462c11ea5d729b9e3a3109166e6da97a247816fd0dfbc9a49a4f8996cb12f007c272c1dfe870ed836a4bf793e00ec2728fc7f1ed814d35f9384dbb6fcad0cb8eb2adfcc713b22b5b6f167a81ec187402bdb1ba080fe264942221a2b9067714f6e71dc0067b42e36b36ca7fb45cccb6739f75c0773b45bfcc395d0435eb8f8a125d7f1cdd1897982fb7c2b5a18919b3ee6e490276fcfee9631ed40c5ce0d822c663d996d823da258e0405d234af66b570e482f394e9590dc7a45afaff02e5bb4531429ab89aff67d69a376a83b935a9ca5b8157b7549994e1a09b8ab34bc5f63f2414f08829a599f4578deee8e044d68354970f442b13b9614a4ae3b7781411e13ad77fd496a91b72490e754019a7c78aa39782a073d3cb5efefa9d2f2d5905dc7873499e343c3e61aed628795593bbb4facec798bc7a4496508809e61e024b2b314f4fb35136312fad3a05e615c1b9eb706361f10d2bd919d7f1ba457288068e3db68386d44569b480dea4d8cb86702440cef39b136f09491398c313d2d8696d268582cadc5ad2df883eaec5d9a6c72eecac5fa2358745988923db4f7d585aec6ecafd99015b41fd0c2e393d81f081665b89158d5f39410969ae340685babfbfc2049d2fae3a3e76fcad774217616dc9d659dd46ea19b43661991db2d885eeca836e411f7ff34c034102160019910e831b48f54bc834c59e19962e952f6fc48077fcedcd2695dd280a687bfd30200ee1aa37c8448a0c56592691e48d459834139723e0c74c8bcb420a655a3e8d01f27b54ca76e627b8df283d809da70153c5483cb63e9801cad5057d7d1e846b512f6efcf1fd9b8a00210dd8cfb2af26287610d0e7096d6229e759c87545c77429723e4b9b1cdba6d30f74b67e46d8d73ecb0640e483c4007b1cca60ce7618a8bcfd7ca907090b16dd427fcfc114f471bfa1d20c8faa9c1d37bc9d01bc9e746a58ea6304698e5c5cc9faaefdb5e0af3de64831f63bf00451cc1bc4b68fcabfe1c62e029ccf3f9d82edafed7b461a2adfd28594cdc9a18e47af01d18fc6c6154cbf9acecec1d4d94be7769cc7f00cb771f49cf6463ce00169763628784172b58858c64f2ae55561ac55372d9c8ed559df71f0d0449495c81f12b77fb3391c31cc647d091c59c437387d3e0c54b4a1d3cc93572d6f4b942e41c83f67f8ef69f30fbf3216acf155facbed553df092374dd21211adf1331561c033774120c487b4f8880993e186ceb209eb0f2ac68b47612e1b83851d7c0ce61f90e669b2e3e364659c236871ff0ff5ac45d51904d0bb8515b40d6cd2c2f9acd54917957ac0dfbe845c9e9c915a753c5c33ce3b6ca9297faf3d6c8f896c91c1a58492b36934b3ad9d0e29898e4353a958e67a98c8c5a5d29c5db1f9f28ad797380010600a83d13176b3800a36f1776b94916e4d9b2b86448587a40c896b1e266c4648e2ba99176ba86e42b251df4d19ac48921d7283c834d02be558bf76b9c06ea22fcfaf6a8f7a3540c46f73a7c2d19dbde919c49c1f6f5ffb217fa421fe838393009cda0d358f2bc242293f1c0268eb2cc6a534196bdcee5c5358662e5ab1db400c9441768d050bd24ecbaa0f426f437e03b94ace7124ac2d667106dc9df2e05923249cd50e949efa52ce557ad99edef7ddc48831be8926dbb9d63299e09a350d4acdcf7034745fb19310180b52485c68306ef97d896f010ddc39012ed162bc5c07e5b47d2fa307974227a5cd3ea77b1351e0913563034fea7429e3f0aafe13a349e4ea1412db71df8200fd5fb4db617c34b842bc1b500a60c53153893bc8c74039ba79115da275b0d949c9f4c90521b9337972eaccc7b2c007aa5416e163665b51d3b22c405e61bbeb2465196277331caef2092e6f155e8b4ca1a8fcab8415f3d73096f305cc4502bbab3503cd0e90acebbffee6f1b5d5cb12c8c37cabe37e2ed497a4c9ca3475bb2a4c285638979b04adc41ea3405475cbfd9afb140bd7f899c855547b425dee813824694dde1038c6f22ab98cf4ae5923ec5709428eb27eaa863dbe40fc4ff665edfad82effbb1e811a7fd2141a9c46849f63063689aae5ade2d51bcdda66e335856cce2e66b1385d372393463290cc4e9a9490bf7843ab38953d2ff9437eb9d27690c0cc955b9ba451ca57a628ccf47709ae496133c905cd751494ee9a4e68ac14618c51c11cb1e6e0b83a90547542073e137bffb261c59008a99f9ed693070adebacb8409c5c4bdbd3d27303f4477e9e2f4a1cd4671b03836165a42d5a18b7855600ce7183eababb78327e1b097d06df400a421d9cb6afc5d5c9766e943f4f2c867d4761d840f4b4e09d6cbd0ee7ff11a93d81a7b4103dbcb8639c4e2bbc198667a3af4413f0fecf1b029d757b60d9da83476a2ae7d4a688b5c1b249e3b0ac94f3b97d71b690398ee64a46bda2edcb2981d1dcd227cf429693721c9efa560e9fea303ae39671d1df9132ad776aabecfb7866319c38cc495ae9568c30100ec09e02cc67353e868ae452f3f8683fb73a630ab79ae08dabb7ff4f65b683e4423ba5fa1abd67126c0346e2c80c77f0f634ac16539f8a994cce7ea77e7f3d21cf518b871277fe1923105b6bb9191e50595aa81c0604eb3c57bc109927363ee99f2c4db9ddb7af2e2bb166aeacd4857962b18ec4e1af6c5398b7e2c97edcd1853dd1798e86900a7406967194318f950c7115b2277443e2daa6d1c028af35fb91d7268857122687b0fe6a8b21c962fb1e371d6406b390313748dffac8394ef2be8e7a8a5409de1790880aba4d90b33f9974800c05cda41e21d43ac572d5d9dc1d3d301064d20acf204e6d44d8a6217b63a8c7b095cfb5088f12e2821d6d61719d13a82374022551b8f6bcc56c1b502b84eb9fa0deb0814eedc76308e83c3d2e493c2cbe96d7ebde5269f457f3511e7efe6aba1db8e6b1a106ef1f64c5845d9d7cf099e5872bfae2c9fb752a057057997ca3a0a203e12308d67fbfd4ae34dd9bd03c9fbc1184e3e866f7101569c4357808242985f5d793e9ad5cf91b69be5817c2f2d4c8f01757861d62f4bbef7bdd043f8f8fe9ee1fa8477ac2d2da62ee0cad16e9aa10ea8a989c909c7c5e48dfd7d5e500721d07a527f4f860ea3c92b3a65223646357bde4d1d111ac009c065ff50e68d6974e32227193e0c0dedad1e35853ef3761b1e029babd9cca9af399e0061643c11a65c42af84f2f8e3f38aa69d045a7f38cb18302b5817eabe5248a8b8a0d5d8a3950abe3ff237c4de42d16d2c614fafe6f3007bdca9ec0d2e57a4e82870a06bb56bbf3c1329535beb2c1773e0f347607daf93c2771108fe77073d8abf04379c17bdd89f3fa896df8890853ead950a3b40b1d200d72ae57b999796a75e3aea310c8a78e7533ce3b9467b9b0ccdef0c506464d5d1c46b7ea7a4d48d721375d5eb1b055dcd6c676562305f499efe83791985f600f54dc91ea511b0a93baa7e73b856ed206e6321a3df0386b1367e058292818868e4bea19f844bd6f6eaf38896e568190de16a487086bf3ac279e482b0913d5a2b4c7d0641bc48ce918d1ba6bb1c7e9c0d58a0eabad985efa2cfe7b04bb23f5d800d0de0ffa4c6c5e5061c192d4673fb5dcbee650739aecc817c64792f6ed9a97705d75d475b0a908aade2948910662f85acba260d617191717bd2f8326d92fb1728e93198d130b6006c25fda320d32351f912f832869a9a6c31af5d04a038160b194a0480f692195dd948f07b848ff42b3c378898d22b7e33d4f8e8f29a083ba70a5ba7be9372ae7acf753e42f5ab88762d25e0810f847d7b84e5f73506562bb5c6d815c74b84de897e8989dfef4330a7c0d06ab5a52fa8fad16913d2df9cea779553a35c2e7f41c6177108fb31fb5bf953128530674d1d37c8ad88b7c60a051af84d8437a9491fc8ca0311e78646af93fd18521f3e609387b117d4c0207c35d0efcdea697cc0ef4c96bc7cd639c9fd21619209f11cf635a92e17fe39df11cd2a0f68d87eacd8967e5a0feda6e7a7b18ebeebbd82e5521f98aa9f50763b981c3551b22762ae161805a1223a55c5e919f2f36b4272c0bc229fe64781a91a46e0e96256754f077e090855b316b7c336060cdea31c33433a5b1c6ca7fb66a2ed8ab44a8c6e5196458403d36aa07d1c7f0d678c632fa7993b20b0880d58e611b801154ded240fe3e649f264bfb04bf377d89d5da43e14b07e113b3770fbe69a3ffcc863133f826537c62f555f0ce22ffc9d84326de3592ff116399453711b6978da7dfa4105a5c2e7518740990d91f297002b88718ec311988a157922a7407476925ff325bffea886fe94e54bf5a993d0d9ea1c02c81682ee1d388a08579d75de603b53f57e78d5d53bf0c5927ad7891c9921482b5bf6892c995e6810109197b42db6f3e2e72f8563c989a865370f050b332209c40aa0b273d0c923bc440dd7c1708ddb98e1f1a3174e25ca082a96607728e86c97b666696c4e776430f37f5a754ea6f5a73648f2462bebaa5167c23ba80065d244cdb18b10fef334d91d1969d8ca27721b25e90b1233e8b44b8e4894912bb698b6d680d5f77dd81c4c02065586147f1be5384c36824d550cce5194cbf1c42d3925fd0cd2e816ea209b06bccbfa55dac3e78ca5a4be0e3f33181e3c6fd9300d9031608c73927e06285bb8075bd946a51785dd4a9c6d5d7e7e32768070797707b9a70d45d5d2c74f1f708d0a69eb50c231a501c6702639cca7f5c09da976f8d914230ab5c5ff788fc1e4a39481a49d737408d115a534c344c763d94b459879174a05bc8240b439153b372f0d516f89da1a804df2b65a78611da9d1ff922afccab36b82f6eb30e6f850998fd335afb9d29e4ece7709b55c7f7bf8f5413a0cd236e3edd0187dbcdc9455eb35bf0bc69d785d5141e92c219ca33d09d64b4c14ed0d58d9f02233dd4ced9a379993c0c8541c35c79c1af4e6c60982dd8cb55899a787534a69c72c00822bd0df36734805a65419fa87c7103fad03264e11307d06e614c1a34aa0884ad300dd5e5308000caa68e812df133ca6f14a8abaa185ede3ebb59041f6c566af611c46d5fb7d2b24b1e0b92d9ac577007b8489e6f41927a38fbd5b8690328c73b8a4e65b65c3c01c205eabdc3207c167e07b08b88a2b7b26b01fd1725cff7774df0920f5b76275d8fa2e0d9272494260db19a2304c01aa9ecbf699a21e7c3f6532cbd6ce926fbd4ba2c3a58be347304dc97f99783cd174f42e53eb8a9e2fee9dd2a995ba554d7d4e8ea475725390888cbb95119d9012b2eeb8e64f749d9783c902ea548d2bb17160b0e9c34ed05e1af04ae9403d7cba9588b0f8eb7684796e4b04883fb5580f6142338d970303248f4af43fcefeaa420f86b4630fcda9d6bef478c15dfc0fb2e733f16cc0848d3660bffc3443543c2c15ddd13e7202260bf07e6070f035cf4de1bc80a33ac174f870f8c009c64a57c4d4e34a3a62f5af16289c8a69550c1bbc7ece2631cac9a2001bc1400549b4138a051ce3c0f654e360fd2c0dabcd50b0b7da3bdc421fd7434b0f43cacc242138f5486a040a41b465cdee75ee1b27733260234e164e529d10e187ab413793dbc35b3a1979ecaf13528bc6e65e358dcb53c725de6e3d370aa58e04f6993298b87adb167187715659b2e85f755dd0648a3f3f5f009cffd81647764e134f8f32cbafdd79b00764ec24b02dc02c582b23fad89c39ac54d531bece98929bbfbce8c08f4adfc8ebc0911c5988cb0df10d8feb99ae89fa8f77e0fad3a8228d2299db807a695632a047f8457efb1e535990aeac15be2d6573cdaf0f0cea54c7f0baf0d5280815c605470119add45f0ba2cfe6af5c18ac63c75377891f458381eb3a14cb4ba9ccc397c67584e41a7ec17ee2207d48dfff7060125a1006fb1a2096e20e94d75eaaa0771c567ce85aec684b405ce200cf429c702508b899c74eecdc95ee08005d0fe2feee3b15b07728535421f9ca401a27336aec80bfceb6872f72865b07b0f343909ee7457d4a96142dceeb161a727d23811c0b71b986bcd54d17977f1996eb6b293b896232303aa8200c3f277cc5a79a465e127647e49134bd2b47b7c6fd1e2ff863dcd29d77363deb15f098ba98d8ab34060ee7b3ee1e16edb09f83953dc9d3202763e673a0653aae4a5006e39edc963045ecdbab1495c2e44b36dcab62694f35b99f3552ad5d3af077dc0f040ad48206eaaaaf22020dce7f33153a31f1f1e2cd3d6a85973b15f320ea047616cf67d2007e6d0e8b924fb660fef00f2451bcedaf860e5389474abdd8f06e40c1a7ffdeb8dc6c189c1fb6c74ee7a0c726b4f86cbc39ff1cdb88584be247c17e4bee8a1baeb82fff42c1fccc3efc857047e721870338954cc167473ecec22a7552e3b16b7d7da5b82189fdff58b3598b892907ffb496f919a471590b04106454b22e490a7027e449ae3379193e4670891575e9dc661796a7063ff789883ffe7b9962bb92f4ed55520a14acdfe49ee9c563fcb1cbc006097f2a2da17edc7a04e07d17137f3e483d29f0ea420143c9c43ed173f52a292aa941c611f9f579e0282501e701c5fdee386bfa7e5b12ed140bd15be54913c94d358d95405402762f0c4eab6e2f2c21baba3bb722887ef0ad7eb5157ff07b865ee2525f27263101adcfd8e7c9369304c8c2bfb4de777bff0387e31361c5e28a65ebd86ab835e89b22c67afab7c76e7be5fa8cc787e40348607c3e0b3cc394e1453d098fb14c9e8973f5501d72077cb3aa1f82178b461a087430fc62f06163ff6411eabd8d7dd19f4c6ba6c274b1b1b016edad5c87f6fd16ed7267d70777ef19b19d8d40ad560d5e1739eab8cae4792a7b5d89812ad88e7c475a8459d69262be10ec1f91c3af1d67fe77a6fbf7f0d4ebe6ef0bd166de7dee03761812e2b1858fb944d7cd5a22edb8fa5ff83fa1be9c5ba8b8e549cec2505023d195758713388d22c7035dcd4354d7aa33767aebb2a2c587b7f42f70e337791afd9547af8fc8c4bbb3f80d2f36a96502ed3e965fdcb566fea2c8a2253690b9f5a3801996887af6e44c10340469a7e4149407de51ee2e9eab83fc854b5612c13d0140c2e4d01afa309a353d007e0442283e07ad6fbbdf2507d9cf056ab1de56c919c71823a43ad718a1e45221dc2ebc43873db969d036e0950df683d9d3273c08647089cabac97e551e043fc87ee82edca133daf03c00070603a4fa91280d6c0366f347795597334fb68ea882d87ee1f87f4d1a9f638429c3a75637d78262143b10903ac4123bb1e8e36a56473c15d490fa19066d17e9c45aac5c838cf4c8ec5ca20208553c672184ab065802d4c959d3c778ab4c5d1bd93f5fab5fb0e5cd2d12dd21cffa1c1bee77e29327577669c64f005ef8fd06d8c3a44e0f032b233c7fa3fecc6bf2ecaaf87948e82476a97ff2ed5a830af9df585ce74a097dbf04e306ddda7926c2217eea6c8daaa92a33f95799daace88acc0b9aebf0f85cde627d9a58c003821ceaaa52245c59670bf8de4190600af0283af4d86cfe59a8c7f90edddfbd9ae15ea04021f3c0b8bf9259f6cb9a454847708755e370094511e715aea178c5d3ce2fb3afeb3456f891cebce1ea3e768a49afc4a7f6828d8036513cd1bbb5d07a327af24af4c7d8daa816a3c1a9be94ed60f70235f30ae3f3df0e469eddcce81bd2c8473c1f8d3ef02ee93f93367e53ecd005cf440cabf0203c08b06cc135c0e892ea47e571af10615223522013ea483b35137b6c8eb492810c6fc6d2c2dede45c22ffde58a3902cc5be61cd95100c3ac9ccebce100a3ec1c315640abbddae20eecdcb04ffbc9c58b7be1c60a40ddb15cd640b1b048b6cb462bb1c0b055d29a27d1391f7edb4264bcf52c7c9a9d4ee00e8da32a0b6876e82b11f41430552363e3eae4c4435218d95a09deb2f576df0efd71b34410df5a81ab656d6508814cb4702e1b0ede6fd08c03d1f1499ad31e046632a0472d03297d86444c3c6bfda8a4b8bdb748c683edd71551b1e28135637e064f00dd2720206e471813d9908fc5303d37916a54fe4509790419baebc00d33afa6aa037eee719b95f8eafe38f0f1267393fe60c9c8a1ed911a1443522edef65e21a9f4d90068a878b7855c1d50fad3ad9881bfc14ca70c4602352e27fc991ece95b7be797939d6f2e79e94c41bb70c2cc7e7433d907dd7ef882f38ceec0a43e648b072b19efafb84e60be3f5ee478c235385ee3fa59a1d6a324781a1b39fc67c9d1a956163e20c2fb873e057ca0703f6e1dde9acb60bcd2668b9b4a21763feac09557918ffc4f4eea0a4f0d9cc44eed83429381337c8712718eb9a927ce6cc0293bf836cae45d535a45159196d4839700f6cc604f3d2d8c584c0155e30cda8c8c01717fa0c7e5d9984c4c14856f7767a6f730538e669de833de781c1b271fb9a232d15700145b955f363550b57452a63da8dcbe91e46a1216106cbd184e90eee6ff0d15acc2b25ec79a74aa4f71d6b3a5005fedac263db7ec10f5071ab64bca6b59eeab8e7e035c4ee68396e79c28f6cd911798ed0d31fe99f342178078322a49077089b0b03864874150a5c689190c20ff20d2022679d1fa1c6d34f62e26ce115cdf2599e9cee7c1793f4dff71a43a40a56effee4d863233d7a6241d1fa82146042cca02ea20d2d1602c6eae8f158c5a0a60be1360dcfee05a38f682a99749ef3ddbacad74a9d813a1248a4bfca18e199e164b58a4e919862a287aa91a63fc07f7a07e88d0bee25109f496f98955ca29a89738fb5b3c33a96f1d3ad6b2ee027dc4170a339cc9b6e0713b9bd628f098fbbfbcb1a3ddb9436628998e592f72548ca331cc564ef923f4d834cccecf4595071a8c235c1fde16ed8e6823b9824be2b7f030fbe256b0dc35f76eabea2e804fee81c82dc47e262610a7697e2a5a4bce754320d4d44c30859d1f49b848b0403ac81841b9c660718ca144d3a039da6e17c39375be90011f2099cefaa288f46623856d77f6587688b8e38a1a8f5c204ae9c2d88ad622e89ba77061484eb283625d18aee09a88d852db82dec238b5803ba6a9309297e2a37ec457b29273b2be5218752f5f77c2b86e755a05613055ea2625b407018b5d088328b92e49408263f8bc8fe4ff2ac70b06d4006baf4c7afb1f389453574bd72162b12cc8e7f173226d165af8735e024371b730f4674135335e63f0b25d131294b529d9db250a9fb84b16c7373a116b8c16efc1ef63525e89a9063e09d8547781d7a24126d23bf138049d621daed9bf5b70e522760cf5816f23b2031adadd6ed6378beb1d596f3f7b6c509eb57dec36c6f78decc1c7b4c1eec68941ecf57ea3d5881ceb14220d00d948b310348ffdc03557fb6184df31324ff5504c56f22f8c056410f922c32bc1990d1ada45aea152e8a52aa58d591da4c0562c8aee15182408f5172f8f5a35675a6776610ea051930b52b39d65da2d341b02fbcd7e84508cfd7e0dcbb5b4054b936d41c91948b89c00194ac8f23d087ecdb4a508ac1265422edfdaae7bd768cc67fd19fe75b43f70a8bbd913895cd59899e4f020a1c64ef2ac2cbe46064637873425782790c2e5ced53bbe8e83ac5f56f15de66ece8a748f15402bb7d32bd70246106f97f728e36baeba596034846168b81921bf21f90e3ade509bf411c9576281979e61851f85e5afbb1b5f591b0269582223dd51b073ae5106852a4dfac27e953fbb4fb873668aa015113c7613d56bc3524fcf02918f95fbba16661198676028f3a0229a68883a6ac6c8d8c77e878f73fbf84f54c5ec37bfb9f25ff0aa00f76f9a96217d21a23a3d627edeff01e39000f644e6bfffd25fd1cee1f1bdc3de6bfd0010708781f28d2bb777de718c22a6c5e4c6189a11719d2f24a18b55fbd02b3db45270d4488aec686b5f6a7afdc2d132832f6b947cb6250a0e82204d31e3933ea780b8e90b4238a3ceb483b0ffe4760b0abb85931f74f3211c33cf486ad7c7b8cd71d6662479c8487b6b92bcbd9c81b3236b497911f132d8566d0a07de0f8e6cb0ee0c80bbce90dd211bd0439d123506de95391bdf26379c641d8e577281d7719aea805e5a0661576ab69740fdb5b5f578eb5ffa614b586c7bbacd7269afa70ee2abeb92ba1587c5c923aac7b87512589eb92d1bb1404c9d1bd2b4c20da610242f4043a7628c21827249f6ad51ec799c2018f0a6c3c0b501008f14ee6942152f843569b1ed03d866d0945e3b9fb819e4438006513695915a3035e1158dda51a6393f7be1ad08ef88673315be260c55a88e24a89dda96c308afa221e0a10d08d933006272088972d2dda687f27a873311c30a3de6573ae3561e996e4b0b688e29e6f40b32f9481d70fc4074c975633cfc71a966f0eae175e2e74e3026fe4c9bdb1ad0734061de6d7e0c52af17648faff0b7b861f19a8c50e6fee04514f4e4d2095157f5366ab71a00495ac353a5ac6f6b0b0c56baf80080356e16786042c5e72dd5e29da871dec73e6a1f68148c7117a84bf6cd00116143fc865b4d431edd449b5a2963e9748e6d88908f7909318cd48fbaa201dc64b87d518596ec00372cd3638f7fc326b623a1922db1a59d77be8148428281aeb063b60dc8e0a91df3f2a49d2304575a5f6d9c2cb8e7baaecbf6f5961d3117436a829a43620b859a8ac4de1f2e79f72e30d08122fd0ae2e3447e3ce626d4c71f1a7a7c126a4f55ad6fd380ba9e99cca42a759bae5304d26f4cf1b53d88cb112269b547d035d482990125688a3082401a8f1ce8e3d20ebc5517888c4d180d44c5108ab013f55106fe1c8eeb4f4ad70964ae6dcae3b82c73fcbddefe80dd4280ee3818bd4ae4bde751b8a3a0747d921577ad0f7936019f23378bf850a6501e50b19344e7734cbc462e0f492c2d36343c13785988e617429b96166108c12920791a3ef18b7f5e252631831a9bb7664391a998455aa7d742fa6b49fc52c7c759d63e36152ca18c6ab846148dcb351b07606333ad7668a6ed69f1d6acc9a6ecfaa8515845469a426d64dc7483d4d90b1e35e14386036d680bca3632e9403f385c2b463dca9799a3e57cc31fe40b633744bf5f7cb818c5af7a8f27c9a4d88b7daea99ba1db01bba02002d2bec3075796991db30ec1dc2f98929c0ef031b7446206c9454ad111226f19614f391eccad26ca805f039f6d3a4d712528a00408045172bf502b5a5c90488c4f2c6356061f01ef33fc04763c086600e37b65f581bd24a27c9b39a7f0c71b9cea5430e36e091998b19afed0741a66bc5c06089689e8111aefcece76e8a5c3ae27d1913ded3d60f17c989d019146871c37bfe619287d227453baae9b2020f46910b821c276fe3266707a64679050650a22e2133aa7a0f16b34345144c4ae87134dacd0e1ce3abfeab8a55a7f9827b2d0892a369d4051bb0c3cbac10d04f254f0215cd1cb4438293c497cd186130cf701ab42e94ff165ffc43310fcdcbbbe5fc60a25a8546e4274199d30d213708a60f739f2cad37ee467e370a3d517bd96ff405cfe44406081334a00ac618c80a681892d6c77852a57ea8ea3a0184b8891f25277d80e941b4fad9b3959fd37e1b4af560bc46c08c868fad56c0d58baf73e6466d959e7fa4047aac032ef09550d0c748b045fd1799dd0d5a3274c272c7291af54c621c1d35379f8b17b5ee8ccfbbe94e88ce136c3d990b9c0e2e7b2372080532e836a6340f730e95dfe9d74f031fde53835a19943bce7b52baeba50b8a2111fa65be1e97b5b46c3c0f2d16105a191dbb0ee22cd5820c28e8020bdc2cc92b0021f5bbb78e8d5b1045b43c482107b6cdf2d0dbf8f9ba124e761136e84a1ce9b0094d484fa620957fee615abcf8441348e477fcc64bb493316b0984de66e4d773e620a22dc8919621606c78ffe63b4d3db468688a05d2c5060305e203ce78059c49118577d4548c462680702fb76910eaff70e27419cdfc7b1054d1da93677efd4ca53a0becfea6f2d33880c4c117e2d057e322a7608f7ae3323eff09622bbe1430ae4eb25f090470ed5684ad9d119235ebcdb3e3c2724f2acb589d57fd55feb38c51f5ec2343e1305a75e0660c875c17f820675af3a382e2c3e47e5347f4172610855131b863296a1f0b887fea519a57bc76aa6df23d7fa723d3a749679d5f599c76d130c9f97a66f8d83313ee7c0083fdf5caa5fd459a72ccd671c3d54cdc681cc82f6982ae84989e663caa45ae7346adeb36bb7abcc2736b763c7d827a97c2f701992f11b90791307ea793daf7b20d0b57aee07b39270aa754d980a0b600946fda80db8432f6dd1b420ac08242ca0196960d12c2a00719c1ae439b94d43082d2624046a2fcdb36a93826956b827e1c2e6ee8aef4a5c5c2d5442252e5fded5a2fdb1715943b656dc2e84643bbd1d7e85a66d55d6678c1314461a71ec7635e8f94acf22c15f903c2fe3df71203d07a2bcb2b58f76d9b74f5ee302abd2a0cac12ae0abb0ad118db49ee5632a6e3afae38cb07831e5fd6243aba101d532e58c70c5f5c4e6f9a30a7600e75254ffd463fe55f947ca6d2af283cb5f3c4e7cf1afff93c707c4d037723a2be99b4bc39e9940d1247c9f48752772e23344cba813cc76b9ef7dadf50b7f51e76744be74741017857c9357cd9c09910f34ac55caa1b5a768163a6ebe8b289e315c090769dff8c983ec2d66e65a8b3131cc81812d8336c5170671867990015d7d66d12ef26c17c48d40d686fdd8bc8c7f07e87d4dbc7583a7c3fc85ad396adedb961bce0d497c2384fc8432ef0ae40df9b45d5b09e811a1aa5152035b26d4f396c54cfa6661389b37ec2a48711c33dc258a1f53ae0281633a98083bb118a7508d114fd003156cfab92284225f7c9f2c46db167386f730d7ed1e99508baa14c300e337013a6633fea98b4a52602153186d869acc0b44461b8face84c3be8e1856d51b71f38908bb1e57b09aabadc53ea2445fdca47913840bfcaf2d2854a0f6a18adcfdf0167d4ccf450e4a891e3485f6c58837e983a25d310392a5ba70acf99ad481f1f830a3b256ccbaee8b8227a35e8a58b28dc0d1da29a6be70ff16734d580b26606c1bdf3afbc7d2bc21ffa79928e43bbfe1ed7fb481db9d1b1cf91c7d7abda83ff750db30fc7e8bec7cbfb369cb36fa021e943b6b47ca28c9c56765e9f7c61e5d5110d898b05918bc778c726a64ccf68a60a4d446e104a32af659fe3eab134fbb3193f83b3a9fd39ccb32f2e2dea2da23cf9abbdc8f15f224983506056f87469511bff2f01b18f25c9228f152fc2310529a28c5553eb71a7a6801aab79a472f5800c4608be08b59f9e7152e3687309383a2d174abc57ba9e7267c834e657d741e73709fd77f76c0289d568a7adce14331bfad9a0f33a4ca976804efe23db5ced0182e968224ce30c924bcd082e995de57faf2573d4fda0ed66eb3ae0d759865d4b7741dedd3c69981fa17dcf3b8ba773bfbf75cd2445f642225fce3203d24f965ddda233fa41d66710b6f154cd4958f3a32a819881fc2ff1ca757d3386d408db447a20b68774b0820074fb97481611ec3bf3e264b01481fd107025c770081994c532457386e1dde683397b8f28d8307af81e240eacf4ca8a513044dee4ab9ae80e89c23cddde6ad871345553c3cf5a80cb29106d71839d1085a4b710fbdc5bc82c37f033d2be65661c56f33cb905bf0c12b4bd18c8e6f786430357e35daa5693099d67aa69c036416c37519b933c37ffd02a5f63e7090771abcd90f7d9e01bb9a3ec919a7cfa5fe38c82a80a59f5ad1ada62613959a2c1f3a7a0b7dcc2e5a2a32fee6e3a8f58eacd2d840b624199f7d1c640bacbf639708e3c9025d19c9bfc6794b5c0bd33aea9a67d79897e8c0e48f2854e44a3f0f6a9928c3ae400e01afc26acbd75282f76e5024b7af66fbd3097f6081cc020d62773e03438bf0d1dcff01f272f36ce1de013cd8e10b1279a375dcfc95bdb1fc8361cbe102636390cea7808793be4b2a13744110a504731e6dd06f0b740d556ef5891368763ffa4126389fcc256ab8cb9fe69d47493a615559a3cfbb059f7eb8e1d77859acee4b6c48a401b60123d4ef0296a888fb67afacb9cc5da7f09a912beca4a62b023f10b3b64bf9e28138d9c01f87c3757cc8b3cfbaac6358feb11cf8fcea8515b6268c34f672a3e83de3ddef7f2c5b5488546d26108b28e01074f50f180133bf2ab757ce0aaed3793727f2e987fca522f29e8ca35ae335544a7b9cde7b8013cb9992d660560b5e3a603f48907773a405e70f56685b9c36504e30181c096a0386a98a03aec70705033154efa3aace2c4f39474751e3d34b084f692fe2b0069179f0446d012fa9ad591608cd8da48a7d9c3eb06721fdd00e020618ca20c2e9ac66c59777c38088cd9ede70eec1ecf8ceda63d73edc792cd4414360d684bf57241b5d2dc6b81b5ee4956a6313fdbd41ad6d5e8edd070a21e755d6528af5865961676dd9f5940749f36c41fb8fd8e51b83e8af26d88358526b2ca4e1a2e1bdb7f9d361e6b33dc095d18ed43be423d466d004be044083623d22633eec7af79711f9bf9b8b18b47c4924fa9aed721ce2663a7dfa4bb13f9138e28f91c9d05cd88c698a00e34a80d0cbc402a7abd1f00457f2807404e5fea8e51103140ae88a2fa49d76bd4f72d6152104376652bbda6fa30dbab2c25a0e3417f61c985d7c90af74313fd1da67ce86adcee4955ff4fc502d24b0bc33cbce06ce002b7e5b367d9de5ad44f6b676cf27db35901071670afe22d54c9b1233db73836c4e6ac1cac0090b5c3a7f4b51db9de5d1734c631f49c18ded45dc2652548a659f19baa99a63d62f5cd9608b8661cf7fc88ec0ade0d13568d8cc4091e07c7d9d8544b4b521e616ef141187ddcb9f4caaec7be8d90cb66ec8ac2f78a19c48f7d18b55d596b7e6aeec7c3ec1222fad3d5bea16033b6ea9ff30f94e97d661e922ed23bfb9557fb1742bc36dca6b202f9218b2ff870b64c7aff4acd74513fcb8b1d456c917adcc57a88b77c978093edbdf8a5d4cbca0319f731c8b61253a77077ac2d575cc29c65290174ab6bf04860b8168737dfc9102903df24368f1f7b2e178eb3a0867537fbf301dd75b00070e5715cbbbef5f1164c30a0acaf78ea75b958af893f4cf76def228bc6ab77bb89d7977d26dd07a957c35321ebeeeb8e8b784947a4c5c44c85ee3dfbbcf8cd429f2cdc161c5203aab4b572b63f2e40759197311856661a94e1cc9cd9aca266af0489cfcbdd653a6ca84cd1b70175bd6ce932840f5f9dfba39dcca3b15b8bcc9d7a077cf38b4e43c2d0579de4186a7db488367c2afd8cc8732d7b3dde96a334335804c0bba48fd0465286160129e4481274a79aa46b54e93c2e6b557fb2d2e72de5f0f92079de6b46cbe80af70ddc05dbf944d01ef405129b75cfae13b113425d8444715edfb667ef8228415d7cecb2402405401583d9a714aaa3b8afb9b9b1626e82f85bd8d23c1677f9be212935ca19d341b726495f50d178a17db767ee7b105b922361938e6ef2420cf5572ab2cde3a05894170bb10df9a35a2e5a0d9d5a9078f169825924259a9ddd8cf0b7284105c4ac8453aea54056c6ace84413998ee6af9e2d8cdfe06af335164e90275613a357765f1c55c718d037941820d837b766264d093bbfdc7b703e589a9db324df79612fcef727e912acd2fbc12c395e98c7d8b1278d5a3facfb2093ba0a2cda2da673c300c8747da11574389142f859a588005ddb716964efe42cab10f97dd4a04a3260a5a6026825e1129294e92e7f63388d2923e31d8fc74a34a0a42857e41f811d3c74131ab73ceeba63417c3b902d4b962a476d8c8323527f6af03dd7aa2add82d9e5d21d4bd3b63f8d5508cc8ccd55c07a3e10328ba0c51bcbb616b001141ad6ccff3f3a52ae6bd1fd4ca26c6d2c219c7bf9e79d7a2e3c7ba9257c7b5092002ecd22082564eb9dc786681fe513752b2064882ce7dd52c19c32984ee445399c00e09bd31682a5f0d7b8268c65436ba0d4356018998a8dc55b108c85c25e42587866757b4341222cb7008dfdcfb23e80fe0352dff5687b3b98ef8289650de50556b045ccd304befccdb98564b82a096291f8cdf2760984f3a5290a44e3871e8295d4ce30812799d74142d12bd245dcf315f4b211384f454e489e87a80855f5edc06f7ba2ebd1219103f1ae4c0ece8e9e7b86b196566358b05cc4df733a2ca3e5cab2fdc14807f1aee61f8988252b09f975d591c82c0db757b294e16594f050824badd4fdc72bda7cc0988fd65817435a9cfc5ecf0ae843a427087ab7c2fe99ff5d4f65861a8bb8355a059593c638fecbf1e3a826ecba88b6706a738c63869960964139c3d0270f6aeb4a99ebbb0167d39f4efcbc8d84c7b0935899900a22b627eb81ea21dabe8162c59ef159e27d7df1fc13d46305e063c08e38fa1a2f902cf4a1ef9a9283f950a2b9ce2ef5120a77f313e7e5b123c64f0262a11e2327d243d216cc9c58fb1ef7b3994793308c3bd16c10a0d18170e663db168a09583b91ff6e79fed89629b3d856c6602382e5409d99925aa9c119ed429896044003f6012d99094afd538de684beba6b7652791a8a5cf3cc39ca1b042183b72a4339e9ae4c3196da559cc8660297bf7c499e329fb91e7279aa905c9257458f8b6d393ccb510c8b509b941841e1961a04bd5ae7bdad1b7132039f8d7582d268021d3a9ed91be2b17950d7a099dfe963834521e13ec9a009ef037c908b8fd40e854dd14a69eb2352ff34b3fc44cdd4cfdc9b2068c69794998649b60252d9ec53ce26c30323a7be0a1d9d2e8dcc9241d66c4f8baa1878a66b4c3d20318f7cb8119a5a146e839291b1a2dfb1c458b63f01f802ebb396276b7631605d285d631b0245bbb3efd7c7e23feb5c8456d6cee8430299591b9e3b0aafeae5fb0349ee5408ea192cbb605ca9957d1edeb73c077b7f08c421c4156c966017978faa5301bfeb8174e71731e1e08101ae94f23449880823d054c3666ee2df0d44641641d218e3c297868bb7b60d9d0a92abf6f012d403850546b74f4f85c1a0b4216084a12aee858dcb42edffbac874d5913fc4c19c893c06c9afb179fea443106df3a19bee90a03da1acb91ce8bb69102d4fa1c5c32eb3bbc478b459ab40428ea0d9c4381b988e037b5714621a2c733015a3240eb88844075a1807ab8be0318244ee29076f71d9f8a70c88e30f36c992efc24cf97666adf3d19bea51ee0ec446856ea36020bab08b7a543c33ac54794be993443b9eb60dfd9e55a112f64bd2ea993a220a46dca03c0d79634f6c63396940b1e7b052747d1651306971e7ef68338af2c5dddbcb20bab86bba3c2b43348c31284d9b759c7a62251a47203ade31ce59a9ad379f21f9b8279d35140f3b8c1b02a0fb61d1c10f7f5f8cf8fd72dbf29fe5c51026a557d536d2eeb105b53aea8897aa7a6f0670429f948c44e550a0d914072b73a8014b2010a6188e915f83981895bd3f5a0a3493aa1dc054fbd7f9d990dbefee196519f2ca6883d1118b85ebf42c4c50b655a7201d13516671a134d50284bff0ad0bdb09a642f3c149d889eb587d73424b1a32debfac2e237cc88176630518141aa95eff800710bbe87ba9ddfbb24defd95f2b706d96acb173bdb27c42d9b5a0198b2eabbc80641676ba36470a070db368460d3923af702771e00ec74270081b3a0bd4e043147b9479bab7ffc6c287bb0f5995a3a23f49393ee0dc99d4f9c6368b10a22322cc73741c13e90ad22260bb6e01ca9849e002338e225d1603c0f31949bc406f8a9f5594225739d39227158c362c3708b0ae5132b2224e2d300ee728f9917ced6901cac10590fedea57360968d08e0134e50abe98d3a12acfd393e93a526b6d6cdd88673aabfe7bdec8abd88bd6d07db79c2025e19661fbf91cfaf830f763b38f52a7448f1398926105ba8e4df2ec3e8b5089a60e6be3b649181c949eb2f9852670ad6ab8a9b538aa4b69d0ef48b3b10b06dbc8723b837ccb1d522a7ae506a04b72f50cfa0d8029744da6d3633f95df991d2dfe04842c66a06639cd166f741b5d9d5c794fe3761022f13ccda325af6e7a574a012bc766bd3f88ff20248afee366c2787572fd475d9f2a9ece7994603213016e52f93b13429d82d26d13b2bc0b584a4b19a5a664cc0bc5772d40f77cb956666e67125ffa396f8927a77bd2512bfec6ce210515b81ed8f56b4be8b677895b7e98d3ce2eb7959aceeb05926b0b2cf7c47c32c93cdd464445b4983eaab69ced1c1e33e60aebf28cec6e2f957aa635516e881ee6ef78862e828859161a6f2cedba7422d1f6b35d7c0e6be66e29510698b73d202c52dd1e043800fc32ef58e5b08312f240765450dec88fec10d5131b04d83cad60f921ebd85b7379cc879a4f33ff23ccedbc7787005f55e0cf19c39af55315289af8ae296630e85ffb7b3df5583ef8616a39011bf9e3008e02148d589c904ab567674b2c46ab30be054f9ae23201323e58848f789ee48d6d5f335941c3bc54de838887327dbd702d2bf7fa861dc1421bf8e76bbb4adf1207a43a155ed98cfbcee1cd6cebaf351d124c1bc1179d3eadcfde0f22a88c8989aa9db91cb79d54171d796877822c3bc2e1562a2443edb6ab0a2a7f37a14e76b028a6590d4eb80c30ccfb5bc114b4c4153b94e122fe565db7be43655d8b88053aec5eb9e2f65bdb00574971e4b59cfbe40db65204821fc04df783278f6e99e77e3888ea5591bbce12810546d759d278f5c90ff46e33d3f2ca3651533e0122c5e4135590d486ba75d4c6c8013cbeae0786b06c4aac200f828c8a6961c6cd8b538a82717996fe49da94f8233ad294faf9c3b24ac6737ac755b44be07c2fc96f6b688663dde191507238f2bceac46760f726fae51697c06e7ed9b5ce2cdb499388067a40388f59f7d87e37cb9053921ca5a26ac03b1f533367da375c4a86d21d7249a66257a9f6bfe3ddc0c35af0d945d7863fef070007f71c151e1e22bdfd4494841a7f405a87fe6214276c71bac5a6f959e7439650973e4072467a063cd51174a4373ee278e1eedc3b0525b8ba74abbe0a51bd950ee7a0b7f58503537e2931561179fd81f6620d96d05a1ce12a9588bd09bcbd0adba6a3b836432dd3950c29c582df42872e21225f9ac6c24cc0cc81add0518f518b0745207957b9fd8d4f974dbf1367bdfe6a757b33d6a5507c9ae0bd8483e4a5333d164d88fb4bba5d5280b4fed5edaeefcb198ff614ce7a3cb765dfd6a339b1d1a97f9a3e030d8c540eb64322bdfd77ab8493c5b1804716a94e973faa764e88fc59f9557ff259c8cdcebc62c6630904b0baab372ed174ebc92128aa5f1a316defeeb9768d8953f195a8a08e29185c56dd5624f975ba1c500d9e21e82ba46343579b241998baecd1d9d840bd48f094573a6064612bb4cf730c76199051b14fa2d882af6c2bccfc70d068deb1c24dc1fc5ced0c4f967b5b58408d1e0475d7cfec7395c6db77ef08abb88f7bf2aba975dd7f733362a3b61a1dd84d0af2fc069d688b404d460a4661767b7c6e6ba056ab141ffd59e5a39bb96a6adb350989016f8c6d3dbcabffc3958cd035aeedafbb0f1e6976eaf42cbd8110a3dd8b6871779a9b4d39618158b0876567b3306c12a1027ef9a5c4cf74f486c6e4d4f808d4656625e7c05754a54b499ff679b9a9e37d376f67b15f10001fe2faf3fd9f89a8df44ac7bfa64a7901fefc5a4232201eaf8d61ffd33814e7012e51a7bb1847a35849f522a00889dfb618038d4a35bca21f6a1292a921e0e7c6b87b77a73d00585df2a559aa3061a851f40c4d2d9af5e398a792f09bdd8b72dc03b4f648cd2b31ddc8610fda382040e8a8262ef3100d158888fbb2aca235482c1fd05fbf5f15ed8ee0e39bd643f3fd0963e77669a72f86f04157134a56a59404fccb4f288cdb5dd6182523db42524102b46662d011f34240237a017dfa56f857e732c8071e6ed4d9abf67ecee7a78162ad46393da94945bb9e91291b15d63871848a79122f652cd1311b5fb1d6e0d80b47c27e9179e7fc00651a8a4573a71ec348fa120d90f27041cfac1a3c3df5ca7fadd77b0c1f64f6bc62af40f5e47eabe93e4cbe0e25442b0043e3f55b12fbbef210827b03518f8401cf9f7d2090fe9c889bacf0c3b35bdcdda4e22a0c098a72fe06fada48220109f57d30a56a498c6b5060629f5c0859d98a5a6735067cca06c44e9351d531a26362006264bfc8c1bceec9111b157d582abcef55cb9be5241628dd22bbb92931ab09409f9d5e04f90250f9b4f16c648781d4abdc988716255429a068798c64497d20b22d0e380afe29ba2ffe570c9f2509c434964869e4800366b89bbd3950233276fb561e940276338d5f485953b494c32509e14b7b584002c889fc6cc68a92f262498fe9ec3288d2eea7a4288377af78e6f29a8e2f68501a57d0d9cdb246eaf32240cea76f93145b24a11289e0a143346f4f09285e8cb6d64799a83b307f8ff50d83c2de70e4d73ec80fa8bd6f689e53e57a6f6e5dad847d0c0a1168eaa546215fc45ecc8662427a15e44c356809791542719226eb21977f46c680c0bdef68f167b67af47b7d8bc8128e0d5b1a28e854d252f5b20b081af2a86369a710518a754092d1a3330205341c31279a96fe04a60c9abe2b97d9b953978cc7a12f30d2a0226f8898f7807285da2268f78cab075cba6b367af136caf5024d717a5cbd15f99e441c3afeed3c6f92130c2960ba487250a1538e7e1aeff17ae4451a27d51bc6943a6b1f56f41b27a56c8914944ea48cab098496f35ea1b8d44aa81615db4328fd9aafd9488c5fee59d2046401312dd60b3efebfd18f8f156168025c9a1bd64bd79303716913f754b8f8ed156f617c0bb6c28e83998f2414b0a0daee0f4ee3b0ca936616c638f3022ca543508ab108d4bbd342cc4845def43cb89beea0106eb8d748b6cf4c95c017e4c4a3ac25d17df8f525dcf903650e8eddf931554b6de7544e8f751ed159c2e2f5b82ac46240f60867cf508df4a693813f89ca9d0dc54851e27491527c16f83e5249e4b3f22c2433080ef379b5bcfdd5cbf44648dd9559b52a95dbf395aaec5ab17a56f213c2c69c2bed246f161dc1cda431d606573ddd67de005b2c9dd71c9d1a8f2da9bb7d928d76431b95f3e60fffc6c8a52f4c534489db616f207aef8ace4b05e8267287cc63357e603e2c377b98e2470a7f23c7fe7e86e80df2c4768e64ea4952c696606c15cb24cbc32a69866c524166c55d0f81e3e46fca39bd4e5651eb01c90935ab000ffad5d6735e85005b80a4cd24214a5d6018ea4b858a2e745c7552d999ee40dee0df7ef9a01208c0094b1794871dfb90ae984ab469038802991a76687e68800a764ff3123b3f0da7a4fca4540b379c45a779a0aa9f935336a453f87ec801d8b00063867ba8be5487ba668bc881a6a704154756625499248912dd347e642bfc949f7e801b158a3444cd9f06473cdc06faa139a002c3551667f22136dde5ea14fd273509d0ea69e2a14712b6f151357c87d1c558c75586f548c7b871d780a425c14ef869e8816993695663e226573700d9006aa4f4535aa75505f2a144e2ed4134376485125184b1f73a9da58a34b12226c12265a0ee0b4ccf682c1b3779af32bae8be28c10822e4a027948ac60f2b20d974c32afcafe314563a939ad2d1a6bdd1553685e0ea359defa12224e5eac09504edc2f99e3290860633f9e4b63bd8f6e6aadc1ef4cba681aaf996ec242c3519e4a2fd64d78c9c8de488e96853f347dbe9ccd45899765898c7b2e59ad29ce60d2b43c5309fb2a70446e1c0e13ce58bd58ec20a69911d7655e984ae43f120684b0f702d870c6a92c349d949e9b39fde5c9b62978d4eb8a43f6bd41a996b60af63218ae4403cf764210cbef154efdcf6df34debd58f1b66ea750079f0a97b9368e0a360d92df70e09365714acf2114971e912dd5777d1ccbf2d2a83d5c038001846bb18837b74ce525150e30bc0b079a353eb1b27183372b1ac0be01fa3e7cabd34f92318d3fb5d33e6e8936787da645c29c41fb3cc03a235a85dc4bcfd6b4e98073ba2d51f14a70b65f5e62bc007e76d90b3eb086e91b6bde38fdbe70fde2dde1272cc665b4677ff6f5b6da432aa221ae4217aa11be315f9391895f8c17ffacef7630d64537896d79abe4731db3dc2af978b478f43bc7bae44e06b61f9a528035598837062f7807e51108822fce16e1727566768a157565b7369c6151113804459f7495c46648bcebab6d8e187149d98a2e3360a0ecbfc0b607ac48160418bbcc1864f2845f26712844c931945e73fd3c819ddb145163bc851ce598770d4fc1f1f298d2f32d76709df7142bf3f29c5697e7c421b0baa043de67baa9e14043062b53ac425091b395a975447678fd136746f08f592427478487c1e99e52e9c75c2d362938bb478333b6a3a60d518f8f15222cc17690396145d0d6f8cff859aeb94371b12cbc0dbafe8e7be138020143c1444d0211f3aea1f861ef1922d8a15380ba9b46e141255c15e9e4aa08238d9039812855e3ac7bc5e9bb84dbd3016c94430de1a0d53ca5ce8c135db3fffe2a53e44e1b0f29b629bb4f52fea7c0ad3dcb59a72b569a43bfa804e05d07c20b7813f1902fd2b07983cf9a13fa8655fcfd937344779f21d62651b891d95ec9520a409d2eac6d3d22466c8be96b65512de6fa4f6bc0b50b6e6c48394bfbc4362d3334812f328ab4fb6b372949f6a89a23f8b2cf6a1e5447f9e9a8854f462853215981056e5ac11ad9384ee58cc6e3b0c79b72c800288a01ec4a0172092cba8cdf5df0787ebbb0830296ceb513f491f2cee7e01c5744800d66a7436f01106966c5d9ae21142efa1c065c260d18fafb302f3d46fd7a010c6c546318021e1ea4167b89c43689bb7ecd0e6f6cab5fc5fdc489233b6ca24a210eca212d9245d1edb0b80df7ea805d6564a3ba0271283dc611e72cabadc61848cbbedacfc66a08d84716c35080f97988d217223aca933289717ac1cb605a80f7e692225504f8951b3bb4bd4cb2a7f62459bf2b7c976f4044fcce3a76ac1f7d4bdc5bb0b6c5c2976fc84b5e1047cc41ba48093ccd7b6b23b43ab8812d90dad273891877f558444037165d04f67b00027340f337e0ffd68836ef72be029b6b089f8ac86f46f9403b40cc267b7edc5e2ddb7815bb72bc220bb09cf91bd00828a0c3cb79d11b50e9dfed861d15e38b6341957f32f5f19222a18435d9d02bd3eea26b622a74bea261164a8130d977000d9f7b855c4ac05f6d50d43b79336d5bf7ebccbc5fc42f9d5049035f92e095fef308da00045dd39c8ff63005e488b29f6f7683210412896e631445f1d7fced33075cd402215a67d31767fb5c0eb2edf1df30dfdf6b73c85b3ce93cab4ca9be8c67d9214073e821d9ba578f059ea87c4f921e4fb7e69110ecad840ea011e9bef87ad8de6eea178256ba846b0916e1eb12b724e04014598780899cad7fadd7505c3e55d10919c0e0613af22f526e42ffe9b8d149ed9b1b003587d2f46038100cb3f93b4f2eb618df9f49af6e4f322183db85644a1bd13f333c94d7b4a05745cbdd8ca45a613c6f6708a0d7d0ed603b03f98e95f33a923d2909804c77dbce2aad0e329603fb034ae7ecca9e006104d4650b3cd5f81886f219344af5d823712042a726a88b301d5784d40c67f20f8b1b2a8e4c93dfd5496581505204cb7fabc772f393bb4c25117b5f322247d585cad46044c9c3069904cf25f91af792840dc898a0e8c83df7039bb17c433d929a5919a80b974c4b1823d9140094c82cdc17a800ab6c1c85127f9b7b9accab494194f216763c21c1e1297dacf22fc8f2f8154a9acbb2f662d032964aad0c98de9320da00a936efcba3c934657f2903ba410718864d78d13fc9014307a74d05cdd6d77ca055831f94905073b51dcf0b571360728311f78ea3cdd3ac35dcbe453e28906f9dea726dc6181ef81eb513c993ac531138935a3c10856c9418accfb0314997413689337e22407cd0a4a188778d25a5b557e141cfd5cbd13f5d9dd114be4df070bb01dd30377ef16675defaa5b2634d0f9f97987d9a33c5167e3cf8226c9c2f57baa730d3ea8b3be75ff9c7f51929643a770c87150365b7a15bdad94f72a526ead36c52e26fff75dbb967b9eaa1161746e80c62192cec76a7d5538b65f15af7580513134284d11eaebc2c83f319e5475980b605d152f8b5c0a34be43317ef664f4f00841135a93f1336bc9240fe752d4564ab1d83bdb21aa396d925df00975963088900745b080a8f6ac7217839cc4c8bd65e9e862160a12dc36bed1a7e0191399839c92099c85cc943a8474c0e4f0bf8ea04a10b36bb9e65460cd9306dd48258cd62805136a6dd0b5cbb9def29852689e3b7aaccb20cc32d14225f67be7ec94a1b739ea941455cf04b96931dd1d410387b557f9949b18da4ca7531d311c75b5551b744f20181513e250034d67b528ef99023d58a2431a8e0ac195c3b38c6731b279eab0bda61e37c29974c14ef036c25629c674eededc587df5bbd40529f3ef9b98b7f889ff056904943b07b805479bb1c6714dc8fa83c87a16091107cc1f4a3cb187f15c074e071d96b3f5fac79af477aa3b8b1a8691cfce9784a0bd73eb8bbf9d8b881e44fcf9e8a86f1167a97bc54d2a7f7b447398e33be97fd2ece3f8ced7c835f2aa344dd2a74f6ec88b2b6e177875c74562ab6567182bfa4abeac052b6afd7ab7bdd2fc8d364a87afb6efefd2d7eb26227de664942bb6b6f0191c4df8ed93d3e5d5c1932b08f6234e3a62e1ed49ff07ab498e3417b7920e17eaddf159a7417e6ff63cb6f39eff3d4ff5ca89a21120611c5677c2c0efa2b78ed6b35a444797dcaae8539c45a2c83c843dcfc35f833f10091418bced14e3eb7fe4c7d2baec7ade52b2ea1ed93f351bfe02d4389f51ffede91f5c33f66eb2693d0e98bb59581da7cbe951747105682d232e78f46f578303436c2c6c078afcab31957af13d60c4c31155cc97894273366230e0bedf8963a423d48313e250b80270d828ffa5cc9b50a708a4be5bc2f0318b80d5fb5de2d8425d5de2dd5b90eb5127a8c2c9956388391387954b1047aa056b6c94b13bffced9075ab059ee96ab4614bdf341e80abd4efc7d122f28787ca53ccec0011deaaa704ccd2c4215347cc34da525b4f937188dbdd137df599754d246648e0bc791d0cb215ed9e88f70246779b7db2985270a09812a0c40d4fb92522b8b2155ef3f5fd4678bbd58767fb7d7a5a7b39bbbc7be735d04f61887bdda5492d3a9d645ad176b47cea41a26b120bf9e2c9f79c5d0faa7a164d383852d3fb7ae5171d8eb4f77898f1c82d1a244a3e2aedf7932d4588f267d9b10b85fa06dcc4473a8ddf1614b88445a2aca1cf7e9e9706bd5aeb9b000a0434fc835404421a97a938dd017a82c1826410b590f457b284bc1fe4de550c692b20a31223dcaf1447f70528630b14a19b85d4fb48458f75ca34b478435fb83e13edd79c1eec1de17313c867bf4e00b61b56f633540552c6f0b9ed45230eeccc4921719c470768c45e48c209bc6fb6bd4c38afad91fdbe308fdb24a5df4b28d739902a4a7094286fc968438657e99b1d7b621f0c7affddaf1c1938f18db5ab59907114e60c06a24b861be315bd0d15f5292d0d9ef43215a895f00bdb1caef75b614839221d5445966ac0d030d9a4233b4156d8f0e83aa7860ac7967e167b21bbd7586e4965ac9e6a4e650b0d9aedb944d9862afffc270b5c6792c84761ff155047291402ebfb7d26b906b65c371e07a4a065cca6f8bb7f1d4183244c0c606bdc9ce633cded1cceb6347b3670433a942f2c18a877c536f775072677e25ba9cb2dfe0642d7cf8a9db7d628a69e15d4c15a28e034c8a3f734046b3d09c57e364b0ec4b2ad7bc36dda91f0ae51b0545a1a8efc67d5c13936358bfa6971c14239db16d21aef2944785371bf53c47669fa39a30ab98865b0b007c6eb1095dbedc267a820ba9e56cc3b9ef24f7a440ea970e0899b57ea5258c43b2b4dc02ae620ff65f0c48cf0c044bd330fe86e80089976599c3d91aca0a6a08f5f33df86c8e10abd7ecc1850b3e7fb7946f2dc763d6fb362db58a970cb7dd82e9aa6b81f0fc2f873a204bd35b6d1fb8bd43cb79cce8c6590e1c19e14154b2becb426cb685a6b0b98ef9e56167374979120fc4493a4275863c52bc41033175cf4e94aaa6944722114712cf0f6a571e31cd8144866aa2602879659a307b675f0e5793877b32b6d6db7a2db3058b1471cd270fd99ac88cfc09996f18dda4f9bd73d08cc72d39d8e54bbef4509a2f758cff232611f0474e336a6c42ef9779de2adf6c8361048a39cff98ac3eb37ffddfff2e68d8f1387678d839be716c4af8abc66012702f504edd87b227dfcd4dad71c1edc236beb3308d4a237c990697e194a0aed0f8ec71c140e90540bada5e1c19629d76126b2c858ab6370914598e68c71b026865df9d6043d2c591a2fafa927212b69666cf539a47b3906747d9feac5fc6e91621454b08c0fdb43ed0c0899268e16597986216fc83c79e1b266cad42b32d324a0fbfcd501ec044b6fd181f3e7142969996d0b7f93e6149955e0b9309fbc3f0a4d70a1ae6389c6688453b4c4f2a85e85bd0130ffc169b8583d694d565e58330d125b22d0d961c8638919991084f6d9e96fad1cd844977596160083fe7814ea6d4456389982b24a0ce6a39e5df2c53d1f7162c93d8335bad25b36898ead329848e9b6c5325d4a8583bb3ff00495d4f138f7733816b208edbcabf7a5543fa1764a27995129494895d02de29a10f12db1a7c950de8d31c2a86149c1e0396430a1658ca220a6755333f9178b21af295fcc8f5474e081d83e47975d77374b325f68d8e276672783ef168238e7683c8222e1aa601108ac7edf415980faa1e9f9820dba28d4403e2d78767564a3231241206ca6d12496f30314253b19a1a477e314a16dc8c0223b60ed823606d2a0638b19178a8ae690bdae60c403c76e46d8c97b1e010a485c9f062f0cc4b88deaeacde714e84306296eb837140a8785f9dc6acbb879de001f2517801d7e0ece131774fbb692932c2c92be824928adf23c24a68e7240bad554aa12a44fd5d4efed2d96b4766b4902bb57ec0afe0fab1a2ee770b191efe87395fee284ed376ca7aab68ab2370a6b52e49e10614dea151465a99fd761f89f31392da2d55ee5675918b0c1b0493a83970e6238965149d3bf6d3c16bc093863db6e293f31678f18073938584531608ed6e5a1b11c3b00228af7def4593494e9b00bdb3390c955554a6874267852b4b3d5eb5547fea55f2f77f868bae4ff4b505913147da4f4aa125540f128f9cc9b2e557583e0b1511cd3d8d1a96ad4b6c5d90629780f618e6503356159ea1121702eacddf6094079bef8e1c792cf95fa2822cc46909bc4e6c2c70d4c0b1075e4d9aeaca18d2422c1bc5033c84a2f2911dd956b7298c26f1f17f41b0ec0ffdfa1b8dbde474d647b59ea60903f00c33b44a6d190f388bea577c344c753b47766327b9f7bcdb80bbdfd5b309ec5ae66917998478a3c116f60c389e00b380f9b6a80ba3ab5982be8ccef39e30c396517050890d877c72f816e541d00a7d7c070bdda59408ffd23875ec2a0ab036e8bb7090b7c7e08e83d32924d33d4ead57010e4024df5fe4b02961ca44117c246cb442071c95f664ce7692e4be280d625adc9d3ff73b846a6bd0c8f73ae0a417ee7204433b5f92bda4c93902e46d6349945a86ce54c2b3b96f3ece4d505d971c0d73bae610f82c6f3d7d61a3fb468b8386299edf0f5c7ff3bd3766bcbbc8ab09195a66ec34c41c251ebe3460aa3c1eda404d33d5e910ca065b363b8e5de961ba3ba00d74dd0d5943f23b1c55d2a45e657e9c63833c2ac3e5e80591770503957c9e2ecf8dea6bd3d2c56515a273ec8994cfea9b2a9cc8c27b764d3d230c9af37f03d5ab0a57fec6cf4913418d66a8a92629d34183be83f700749106a22c193416030072c9cd52656c8a15dce2bd12ad638d1d9148861aa9df8aa1b57996af1bd945e52379a333d1702b3ee9bd5e73e993ea99ca998aef496b4b1d2297d329490ad0773e89f5ab38c9f31ebd7d136ddc87f4183374e182d6dc633f9f28de847ba364213c905f323dc221ee8f060e99fb33be1d767afdaf25b084b012850a0c6117e2ad7dc803806a5d2a983bbd3b2a23c0199cca25af2b66dd9f4780f617aed3e8fd8f1b9d73ac8f59415db5e97f14c204f710ca44e42139bd39eb3971e13e9de3f48772220b2f1cc517abe155643f8ddce3bf437dbd5184655cd8f737047e88301ce1587d069dd7f0521e476f29a9856def84c282c3f87103e0a51b9dc997cda9e24890a496869e1c444626470ddef2dadc6a5a2bada4ad2b7d2cfaa7d539c6c738ccf82df2b1627c28e9d729e49ab071459e1fdc2cff09cf47bb8a77c2d7375ba2ca98aac738f18a55b372b122eb19dc42d29aa356fe18e0ece19ad052083ead5f1a17e996a40ecad1a68e683fc9b419b27d8c00439a1448d8482e1029e6302b525070b7b67b4e7aaf3cc6b9ce703c04671c52c98689b35ffeba2aefbb6d6279cd0b6dcc88b9180d2a8e0a663e451ce733850a38dbb278ba4fe9482b2508d9eb164fd37ef25964e9ea7fbc8446b6c4346d51eb234b08cc5fb4e57d49f737b9b6119e051c1b0ba3d6c19c40ce64b68ac04fc9f8a97ef972ebaa0ccf017399af2e0815d518b8ecb4d19ed7db30e99dd62064b136f2d96e66edfc55fa20f1cb3cb70a12868ee1e5c17722654c15e74da7641728005b2a138e9f4b930668ab5163d05081f18290e766d24a4e34609d8b235a4d420dd1b54bb58d90e3b8f4682250b8f4f66c1ddd8dad6cd992f6bf1854a8bfc3a72207c95dcdaccbd2a971d28ff0cf690387235f7203a1ed4febc7e8a69e4e73b58bb264b7c010139df84c80f6c8b63dd277d0ecbcfd8ff93d66d66583d29f778b81aed1c11092868a1bc72e4634889b65040830362cd2773fd2efc38bc9457c8c75bbd110a079130f38d0057fba390e750dbdcaa18ab8fcc679664cfb0345eeda3b2f90f2655e8592b95426e1a91c2a00b1e91e1478d618c3a75343489e00789b311aafd4989291a0f95b05b6c642e42eceb6be82e3df95e1b1725db9c3dc5d4206686cf390f1875bcca454b5118277196222cdf04c5d8cb1c8a23f35844aa329ac10753d02dda2ec5f2b0b6283c4c91125f2b64c9ccc000ed29f756128551caf52368aeca6465afa203550640fad21c79714a3d0704a13e2bec7a89efb63c57664343a3192d43caa2f04e49ee2022e85bce5bd810358fbf90487e8d4011227c199a9c483066e4fb317dd8af46c6187e55b70762e63bdae226802d20f3ba3f6055e197465aefa6537acd2c0dc78c0aebbdb2d4e8b34ff00070e12ffca9748dfc6764482b1f0937080e4111de8b9a400e03590c3ba718e1fb7568d627b16bb52603751f80af05a79024ffeadcb24879fa013d7210ec6b5985e63ece346c5e7ce9713fc40bba189d7cff44e5e228a01952785593b31469b3269f483e48a30f1d0ef2ca75aa2fc2b4ccf56fbc90df8ea4cde65494e49aa01748521a6763926ac71b1ec8c26f66b06fbda6e8b43e43bb8368588bc61ed09c5efa72b309ba37076c0a0a4e1b04ae2fa4c2140eebbf19953639284b67cfe6ee3e0ef1c82f154431071650620dfef846d2bad030e6624b8392fb2e55cb192c43d2f0e373f7c694d1416a3350ad63f58858e5935823efb44ec484c5db24c452080a90cb24c7bc9ec20851b4c1ab90edee04e973809be5f748cef2f0758cf95ba89a71ba13d27f13f2f1db1885a56447fb79e260c56b8131905e9cc36ca676df6b4f7ed31b0f3f7413da720b83161994d5137459b0a6d6f2193ef3717cd14bd349f8c9c873fd594e7c03543139071053086089904502e0e9a41eb1f7f5daea68212ea0d6e9e42e04c0542ec969c7631ba49f3e443aa5c7ea96c40c60bc2b175abbed4e0ceca35945210f22d773b15009d9f5a616dd92946eda518b4ea9094c22157bbe71d4b0371c88e8e9dc0a79fcd14e4ca484f1eb7521c0aee2053d83f0b47864252ee1e983dc92bf798490fb7f2149d5733ce3db3fea3c905ceda983833cc07f319e20eadc69351407965adeb381ba45d80d414d21fdef32d062233d4b1cea905ce29676e99cc79dcc002e70d380e60f388d51acad0c5f778107c23e500d47371c9ca5e596ecd715ec4baf0d630a23a4153af80891a4e3b4cea2342969a125457135710796961fbec09a93075171ca2a98ce2391f02d5c4bbecb530930aa5671ff732d6d330d42eb82dba70d7bd5799b888026d53c5ab0a5d63c97ebe2ee2ac6a7bf3d85c8bb84d481306364d7401f76ed5e040500b462780616bfeb441ede9055bab0e410d2f676e68d346a4e5297b8d3ed0d239ab155728d5e67fe5d722154bc1cdc60f8597d7799987c0b1021a4c82c9f826b7a7e0586079f36d3c184be6796956aeeeafc66a0e99fedf0b6fb5de52c42de6fdc1e6d5c013a43ed71be613ff6fcfbb2164dd16d0c0765cfa9319d7fd0382792d3eaa2f2fe778e949d62a8a1c9aecdfdee82baaffa125b5522a0ed833d06b20aaed71f446628125747db601d03c7314140f8aa2005e64314f522ac6d7c82e36f2a6265467827e2935ed017e610229c13b575f42b2866658a6c36374b975135a37e9fbe7db4404ebf810ef7066f7c62ac1cd8c8ff3c15805370bf3510f2fbc87b34b4c557b11db1804422f332fccbecf0c39fe8571a2be6f35692121d960c8d8bd8b76e2b2430c9990b4f818fdd60b00337ca18d4138dabc61a40d575ef55510215882ab47bcaa44d5a993a16da446173d044a06e9511c83c2dcb63fe08db0127759b4cdb011cf1cc5d68f902cad88424948ab067009ac9bc3ebb62151e3b9a5ef92c99243f1d795d8407f7de18354da2b8c9b7349cda0487e7118927ff53b2dcc54a776222d502092cf93c12ca50910cae16bc8d57bd42b489d87865961a7f769bd325c4a068c36be9505e65500e453d827ec9b6effe16901479d78b74a816df89abc9ca93ce477c3af117fb5975bb11deaf0fec06cef16b86b68ca3f13f7a60b1e4929178d35f5e645def10791c560c8f064e3b8c3bc01ca74bd809ea5b74dc685b42cd0bc8b3994778cd912511cd0850069afa8d23162dc8609c82743ffcec06969408df0452437deb35614db518556999ffdaa311586cf7173c55ab77db58570168c273b829072819ba2c2ae355484aa6fbcdf5a553bcaaf183d194fd8c6ce51df58e0e7746272561ab3478b01145c5dd6a3746551e69a5cb75361dbd184a6fe3b65f92bbba6988cacf130407e37c99c6d57fc276eff14c40b3045862e63e63b5b9fe81f583f58d03d02ea1ba19c849d429aa79787a56ce6e7a86322535aff634a644d359889f0475f8e6b79800471304cbab32f0f3ebb733b325f5edb8da95df78ef6ceed92377486a88ec81b0c37257ca3cd9fc769f23088c3ba4cd3605f77ba308e97f7fa6766dd4603c7738c356e1d7b36f56d41f3c25a083e72dfd969a852fc190891a74214349fb69562ffb68b36aa231915512658c3ac9f2a2c1377712b50cbcbfe52403268c1f401a565a0641c7bb1ced2b6fdc09e4cc722ad83a687eb995598f8af59720d3fa01bc85b4833563f096a0caa683741f4cbfde885dd89fb7b20aa2d7024987bd188e5416825301d86123bc0ef39135c7a3f0e28dad8de98f4e02f0240554d25b5ca6bd76bb6225fa5a08e49244dc9988be5d1965fbef9e94495a6747298d7f33b9b8a84e2a4c9ef6af4b802acac18526987619e09ce86e900b64e83d96ddc3ff6cd375ffdc58c9f346cfe26d2f7b48563c37796df6593b274ffa3dc35a4dba545f20ecd6b2e4874bc55bcda23ef12f39c2d1e7ae8015193df44460618157465b60f6c1c2c0fa5046920504bdc10ed654508c4bee9e00bbd325f6d5deed02c32a53f3296e5654372cee007721f13b916e43657b06bfa66ea1eec0e2afdbe0707a064bb731f5e5802ce3900d8c0603d1eb5ca11bebcafa0bdfd3ce04ddae776707f916b27e8224557f806e17745ae9df26b413d83013ca511e96027fec11412de148d4b673ceb0c5df0d8812c13890d2e3cb10ebd460f9c620e1a354d58a36fa9dd6218ed939b02cb23cc04b087a7d4aa6d601d36f2e6e1a03e5f497c969e1744af136b51761eb3423f91c20b35b9f02bfe6d0fc2af37f68aa7a494d86118295b40236dddfa892dae337cdd2bf1cb1e085deee0d256d723d979fc74fbb7bfd8eb2a2eb4364ce95c63f5b742fb20ff7992cef6f2a22defb1f747959718a0269faea870ab64408df838c4c3edc618e18af691445f64fc382f84bee2f8b46b6285669de8a5178fcbeae44cde5affd52197f54c8ccfcc8468a53f1d66024a870b6c71664584f600e83029839cb8284bb774019a173665ed29f11cebaa0b387c074d6803c6e52bf083a5d2a0715f7f2d370baff7b35de4f753582daf8c247962b1c85ffad6c891ad7d2d0e22daea491500a4cd5a0cb525ac656598dc4c18f8e27eb90f719f8c3f6e7844d2a477767f7b869ff1045f0e6e938810d02a1bc4103d7d8cc204e075e65c47881b10398a570b50961b4cfdac2769c6ac3d388ed4cfacfe9587e5c4e209549684760b9f7d78afd8fcf98ec27b63885ffd7bd74a329919a57a104109562df538d05bdc067cb4ba46ea178ecef47babf30677fbf34d273537726702d37a0f97fae6d17443908af94a4ea457002e89d9d1888a254bba1eb5b364f4c80da30050484b15bb7935a06056143f32895225ac77119d25b735c1cf6d71a71f101a0e0856443170c834b344c18ddf5323e305d9db55f7ec5939c711334dce1d64f44787aac03a04cdbb0156729b40b64ee7e3e570a709b175cc5b8a7078bdf6257a4d434fc332df57ae191e7239899fd77cd598ec0fdf31460d4b49885a68c97a1933aede95cca5614d5e34c4cbd26c372d82f883ae3cea8a31789941f696edfbd6c7525db9858e0f82de4d019ab06c2c7d89a52db13867b6723966029b6dccc12ecdba90843093845bfd5b4a5d859788fd1d7ce6e28cbe9a5b40a8c3d508cccf6361512f47878637af8a7810fe61f306dbcfbf6bb88ea8d7f16d2ed48133c8600660207f4525fa236fd107983127afc36dc24bec5649912fe65fec13941415b07ec3ad859b05a695e7b2791caef5e49d0f508bcfe4783cc251d8e1af023c236c10456058439025a342962b3ea7e763610cdfc6b3a7c96a2b945cf1ff2cd2c835485bff97672439f2a9fcedfced9f106c32b09b59bc60703f6470087f62dac55e77ef457b53cf326127491e571f026c2f8cde2d3604c52e2a45832eb7647a5e2039d0d1f509834ec6e9d60e26ec5f37a2f7053fa29f1de51e47d130e5169de4fd461cfb4b76b8f8595c36b7bfaa07a0896a5b6f466a79f6c5b4974750c7762a8c2213ec2176ecf59ab0cc2effa63b113caf753d405f610b415435aaa3da48d75b2da2bc2d1dc3dc3c7e9a63e3cd6375da722ac5a73c4dbb05ae8fb602e85c67ae8d393b3ef70b5f8eda7eb93ae4aa0f824d540de5e91a2fbc4d25bd07d721e5e629084f71a04f1d67416b7d7185c53fe39dd436ceef9d01241b4cca952c3cb46bc2e88744d153f34c0b591fece10640a87211a2785892114c0f1daa26e722b7ee39cdcfc1c147f76efa76a07a855f3c3f0955ff9cd65fce8e24e9c973db5887632ae7afec037a50e17b02a799f2bff9685c3f5ffeee702e99c4dfc984bb58f51d1658103acee5873bf82cc3bd516e2e85dfd7d3d755988223f79699b9eb3125c3fa95ff5fed1500ed2b8b0cad571dde47fd5feac1b9f776d5c9414ed8d26ec04b2651e1e1f0b10e48119f9d59ce38dac16b6b3a80fed6fee2cc7e474400abc0e5875da4d4c40277101ff9cb3d6564ca8f6f6b5d4dbc93eb17f586283a6c2c33f036bbeae61e8008c62e7fadd92d151a50697c1f23c697d7a8ef38a1809ce5a69f39ba69f017272e7d34e2ad7b7620fa1baaa9e764079928bfcecd5c4af617d4ce317d562ea232a23b1b19d479d083cd57e87f55053c4120317557b4ccda8e3b19f815b119226dfd7865bd680644aaf2cf33a47da526a0e81c78c20910fcb669efe452401c53b57356afd1229afccbb99c9331148439c93c643d72b861838efc8adf50006e88cc8be415b64739d55786d83f641c97b685426640deb180c454e1372228c6fc6167e80e01777b975a22e3c48356b74ccbb399994a6f8e4fc32bd91d26d8f0e78c91d4c853c6b6ff35c18ed93a3b40b71789d0b045299b29f37828daf43d3d1ae705526f0c3d3b42083e847471f66de3cb018c3f50489aed65ea2dda7f8e2511eb6aa12388fe80b68d897db7277644888d8f7e84e39ff78e03bed918b727ad35269335e68555d822eab7c7b38639a4139645f8205fe88349326c8faf2e7737b74c5d60de2aebffbccbcbacbb58df7a5a74cdcbb5ef90aaf1eaafd862cc67f42dab73855011c043c78873f3af0216ce1b2b8032b5c23b0a43e3160f9f0e6e3e8cdc07457c6fa3628dd72b5a150cf3fead105bbe9b841bd72c9d801227a29e7fcb7288f12289c730bc2a1ee4a6878e56c9f4bdf2516239026b88d4a5ab0817411aed08b12dfadbc76e53ddbea7b615b9d87a4cfba4f9ce62f594aff1a30721dc374c2299f390fd4e3e9108bdc4281f85f068b8a832fa9bd64e712544b2315b42d81c3e335f8e5759ca5865a1b45ea466a2a7efa82d7f4d421960fa6c53b11215839e20d65ac47468cfb7fb1f333b25292d54f7842290e8ab22c8363d3212f0b0a165237294ab91c6f58bfc88300d1b0baecea2cf5babc3b6ef0f973a234ec80a7a2f5b559b7d91c99f82e45252e7a7b63f69cb0d278269bc523e16ef2d7dec10883f6a2220b1c849dc731d13abb920d59fa4bfdbb18d8a9abe71e2a31f3ee7175388785d1e44a69fee27a820e5e7785031a05333878183a4168480256406890125fed87ca77a1fab4ef6583b4cbecf1e7b2ff2b604e3860bb679a11e2ddec7f993abf2905e2ffd5ecd9b5711f0ee042f22240af2101b5e29e62985b1a2ac0c51c7cef44b62476410e3976b326db1fae13c4ab4b5f5fa6bc1e20d14d2c83943386dfc4040db1bec83fdf3e7ba7e1840ba74738355ef57a8aaa2712822247c9e3aff77bf59425458445e4a0a92d6992a7d0c22b97564f83ee6ca33931389186f759faa395b644086654bb9affa5301604f708e5f0af2441b269131eaa518572a2272d148d9ad32f56b5ace8dc96093c4def4d2a91c33998e0b18e09cdb1c2a228946655f69277224fbffe10881dc344261664ab005d0224308ad77b84c5e9e7ee7b71cc40cabc7a201086572bdbdb6d7a5b60fd90e4daf25778c7da75408336e93e00dd5f206a2e2de0c24284fc1b0a5ffebb36b766c8298ce231601677bd04476a86efe144fe6e580f904020ac9d228061f22136a093a343c3cd442a54428f0e345849345af104f6c0a83cb44f1bf76dda9ec758a4623a120612097c74aead060778dd39629b6c4d772116b48bbc5c7ed4986eec15465201aff25ec63332beaefbf9235e2162e9cccd5c9ad9a08e58c96d4075488d5e383086d58f1c2dc002ca94e09339fb901c3fd6cb747486cb37fe1f20041f9c6b2ee6cc746d28590a2bcd01fe04a3a801f081d6d00769255e584d7af5b1a043e4d700b7560d405557c3af67f1838427c01648847cb29d05c1693d5ea04f4ec3bffcd8fa938cd6de857a49193dea3a1050ecf5a0bb94213abce7934f31bf644900e9465cf865868edffa4ef92bdba5a16dfd48a997cc6c4b2b6c4d616b4d074c74e166dec166c9a4af99ed5692aa419485cd3d7953e18698e139ec7307b74d8b84c9f94d86fac73a5dcf407a2b6456f4a7a111001d3a48b06a881e15f171e6f7eaf519c813a9264a86dc2a7d7d45c702101366752bf26ac066cd33a337c65c76c93f4e1ad7fc9bb0f0ca43a83ed7cf35fe9ad932d7bbfd79340c3fd8d61f84b8ad4c2cb605086ed891c5fc3da0381183866a179fc955b0cf88f1460e620b492de69d53a151c8d44e35c3d65bdc527f1855f13b07d45bc49a48362a2d64512432c362492671ab74b91337fa6590b739483e3a3484a2c8eeffca75b0461628471c25da07ccd4db3909b85e5a20df09d85ac01fb545625dc9cbdf78806796b5a38e21b812851dbbecd92e3b3594c4bd8f39e59085cc642e6b5a1b2356ae15f4a00ff9cba9e7c3ec742e760e0e4520d9c3c014b677cf5aac74c1c9500b94b02a98a106d0e8357ecc2f2ab363ae0d8c270c9a543bb257f33469b02384bf1a4843890ab329943bb10c43aae35e5d358f97d5a2a8f2ab8c36b86dba276f9fa4e607b04afdcc308850fff9c3eb46bde66026c08c89dedc7b6d96e91bf5fb3eb550182b39d5c89ac7554bb8fda0037d29f89f9392a2fb35c35b50efac2064f9f540fa8b78307c3d7db0a0d292c602f23f873220bbc5a34c2d3b6dad603b50b0934c05a6db27e46c0f85ce7493d98bb7ac56b24c54e94675d5517a5c73616e178eb40d5833d1f021fa8baa4e86160f1bdc8ce0d0ca102dc2e36008b27493dc0436c97d6e2976212f229713a2d33ccbd13921ce0decc6adc61bd398c989b4b37208cca4d57c9e524bb6976d7042811595439f4d58a85d98a36dad3f4112aa44b13601d13e0e97de50d4922c7d6f3508147c93e620cccd4225c750a539c3e482707112e5f8a79a89980a3ac6f993f7926f7ea959970a7c94d8a3b11e52658019ca09c0e7d8a0aadc2f322cff6707a65bae011430323053e7d3807a01f0cf0a1dfe6b2641a0d3e2e63111e29805f95cc3475ef3115dde93d3b0933b2de91a73d8df686730cebc7942549476e81a42bbad9c89a9e21a23c975f09d64bbf2f92fa5db19f2945f723c5e6d825ddbbd6ad9b96dfd7d510f3092755dfc7b759d08f8c37aeb1ab8bd445af960d8296961397e063418a41c36275670e65e10332b7a43088218bd836d5d3b702575a052f31d340034f64a2941f261e537a311c2c56ef4fd539915c0068d7f1ad0145acb88af7daf1c6ff510b76a405644c84babd7931bf1c09855705f2985beab880d6a30bc9e29317e3321726d4e10220cc70747369c4cf71a2fb6fe4c4c8efae276959a4ab33cb909a1ab307e6639cc65641a55badf6eb734199213c440289a37a1d936d0ec9c870e7514c6eb1cc3130ac659382816f6dbfdcda1bacb3b0799a61feae3be4ee21dded12048e4d5766a3ad9237d2aa62cc636a761a28868d1be495206466d1a99266bf9ba226ece9e8484b8c5676f1e1cc2986284aebb60c94b19c531931e2d0c23f09a471755ea49d3d53c4e3e4125db8cfdd7a8a09ee1ef9a7b61db2fe122cdcdfa9b750dedc7c2f329b574ff42d0934dd58db76f6796fe59b7312545fad99b929f4e29f4adf3f5c68827b446a87e28398789b9c98125baecbfbfac1b74a0103bae63d9ada8bd72c8291c22432db0935301e7cc2cecde1ca47b27e80f8677238210fc86a624c34042b68e1589c71df730c42a3bdfe2ac27d7ea7d15ab1249d07c6829bd2a9bfe710f3df6a8621675d990677b2f3381fe1bb132cf352a36b557b608bbb0bc13adaa04eb7c196393fc09f5339afbdc647e8b6de66672fcf9a9983191de3ba96133dc3bc2cdfe9c73e3e8c8cf133f7f46e7c3f2adb80197dc272ec44f95176580b6eabf159b6cf45776244f3e96c8c64825a6eadbd29e3851ee3ae106dfb0ea304b973cd4a136ccbd70dec7a5a6234fba70b1ed1b5a676b78fa5e187d8ffdcdbf9ef083fbf5f0d1e07028fc955efee2d7d1fb0f94716cada0a610c98e68ccf903a4ef5ac467173e8e0771d39e1a2267898171d47365f2c2000e6d93930dcade2c56d3cbfc620cdc2fa9fd29b960c2aca0295fb176b1618cc8352a9b6f160f80d29dc29a3c1a5138d39c97c36c9f0b59a1a5769ca450cbc24067dd65000898b6ff3dc41b19bb9a17548f2c9cfe2b30e4c8b2a1eda017e22f884e5697aa8d89640f0ce10dabb4132c001baf860f070dd2581e1b286c89fa8eb84563483ce08372c6cf1310218027c28e2272e927f46be9c444dbf78e27846f97bece201152380c2bf774aaf6d50f68721f1044f3e7837d864233c4db90b40163171986b574b2b258070b6e8b1284763613d792c257a12f5ccba391b6e26d06e7c5ca7009ec4837cc0f98192a9875cde1ac1d229e59a765260e8fa4807629311d01c4fb0316ba857fe05f70ebf8e0f260177d9168c8c03ba732f4214178f0f8df92a3f0fa73962e6ed2525dcb49768f51dbb442768824163d6fc7ffb059a75f8b8ca49eaab4a2aa3e03bf66f4ffd91307d75c6f1b09b53b29f2f2773c4641e4323296414a83d773913d15f593cc07c0c1bc4abe486aec3a48c1de23164f2e229ecc48a24cf20581ec64dccef38491511f79c852ac2d5457bf003e89662a48cb5dd23871aefe21f155fc9ed8bf6fffb6c9e613f7289c68b3dca1b0ba294ff6b0b1aebfbb05fdd8a65424cbcab8e11736776ca3b3aabd27044f949b970509b732cdfd4c9419e22f18c266172026148d51f6cba006608e035fa1b5cd74ad4666e463c87ffd0d58c173d02c2c9da43bd751415925df6cc2ba6f68d1894cd38e44d079e7f596d3a42d135b5f700188dbefb8e25dd83441e75d385006d239978968ee3c0d183bf9a60efee98fb78d1a50347d084674944f78781216ce77ea4f52a0cf0aa6c264507f34cfc54517b4e07a13197e3c6438a96d76571c93f0e040591e4d1ffed0f1c2807d2138269246da0cfde522f38edf7b370e03fe1885db5d3539aeb0444a6176f4f24f9e4c482aae8fc4546870b12f1b9e418abb48c41e71528a4474f881496cfa32d968522ac9acfbf35d9104338a2b2ac0387d6f44677dcd5d3453f318d89ae0abf4c88a724eb73a803aad84b3dbb4a355ddd1eca51e3968df81ac779318bfa61585f3c38c30c026a08e033c06955f1e4b743b38466b6b7200f8c9252de85ca491888fb5d8195e7d11d8596e4b583a4e63b576443f446b9f46d8f5bb4949c756164730496240a54f85e416a5552c9a93f38b5929706e2903f52604077f9623bc7927081cb9722a66b81389462b8acd7781e10a9880b41a3882060036acc5a371ee26f7e29f71857d73a257ae7b90c09ededd1044da2dbb2576052ab6a51dc44f00a6982a7b38a061c59ffd588498bdcba05853faa5d50cf4d03960c9344be7fdf68b44b56f65328e90972c10f748a35251b4812bc1b904d68e9d055a6d913860a1e2ab4a0e87cdf0bb89c67893cb8203784e2771183a3da68a319a39e15347b13af0b8d0392dbce9dcb6903a4f25b359131cd9e956c935b0494966438932bbbcaed34b374730e0050b5ca3f1dd82e8db25ae72db57d744c1741f18b7377e17c6e9186b6c71a9d46313fcfaa0026b0fb62b601f708e860742e567df188f44c1597ad8ea2fb0060346cebbcd0387c1f17b4711abb03f3ae1afaf0f59df867a4fb5767efd9f8cfa9259dd29f5bbc90cd7873cc076ec1f6c3540bfeb9d723929c4aeefc02f265d40f2ab9c35692d6cb3ad954cc7021cdc2cf875084e3dc548db3a0c774dbeb2315f3a9aa3221c33923fbad00b692bb984f67a437dcc4802d6e93a801442f5a13234f487b20bc40c5edb0d280878305bfb8370dbd5b170adfbc6392bc396106b3920bec0d3a63e2c2033c9fc41cd79282087d46019bd0bb17bee52c833d998db6b18100a551887b080b3ed9442ba30af4382a5406abd472328602b9f5bfdb65ddc37b6eb419bea9d9d3756f8d384820af18ffa02e32de964e0a5d3b1b75fd048da067390b5fdffed94e5a53099495c3d5d728d05cff30658ab80eb752a323b4b3d1f06677e858183d0a433c031451d9401a9afbb0bfa563ecf11cd2922513b52f79f0852a07daa5fe6941863a300160bbe07cfc5ea23d438fad2e4aef262ff687dfc376e850290e472ff55ee8107d4e8466d68c8654c9b9fa35e1fbf31e1865c5d1a5ed887d13b32076d49b83099286c420ab69043b9db3ed1810d97d372869ccb53d4e0458170aa6376c4126be983e4f3211e049619299c475e2852ac42daf960112153a46dab256f426463c17629a436362b00088a953ce04566d900a2a0cb62634d9bc22a05c84791fe1af05493bfe59243d8024ca91d7b0be5440343dca336a71fe205e02b3ca39b5efa0979f162addf292e995229142f627437e5882d92fb9d541538fdf9cf9f5ca790272e12f691da14ff1c3b5a63d44f8e3bc9c693e68c0cde53ebcb17b9e30fe44f4866f60e23632bdc692ee9cd6a8e4e0a00db836e89acc0e44aee9197261569cbb3766c6c619d376df36fec7a77f083e6343de3121c4276efed0c43340559604083e7e68d21b07f1349483918cb0e8c651e1711406e492e567cbc09e3b8ff7eb25de7f14cd2e20d1aa2ed42eccf34610a64d2fe3fe11b723a8e36e3a95036bbbd5b8ca76bf44764f657add728d678a598299e53ca7736ab1c1a39fca0b72c3f40fd74c6739f5caabfa60afa45f463d5ed7fe4b88fd92bb5a411de657efa3312b001f2edff252677969e19f3b19fc41eaf9951efff7144160968b7334f93241b6074ca902f16d780a143c294ecf0017b86d03a9bca96822c93b9dd085038196fb82e81bb4393c175cb2a559c92545c48d346b5974816a67c3017688439f9c1c6d1263b7481eb83ac581a88005559d73ccf6f68382cc7800ebfb65fb879b4b0334235195ad3e7c9c3c7f45bec6d044c54b3cd50e8512ca2bf71c10888baf14f4223d7455ee945b431e23b01ae22af99b58422731fe1cc4dbfe75a36da9b44c0cd1082e941580fcc7b6df20cea445ec9d5c8ff0f318f03c0506fd330d5db6cfbba23c73d296a4e49ed8b9226b631b7e5d8fed080cf5139a1f8842d25c35c5a5c23e34329fee4e64a12d79ad01697ae68596979b1016bd13d6cd40803c147a5224d2cbffda67902751a154ea2dc10ebc20df068d429472e697b60b67ac023d8ce6723f457c78e93ef87be2421df03fcb841f8d2ad5a5bb9e2ccf9d3032537fc9db16e1b27df119aa30713ea5962850213164d32c4175e934c56c5a00613488375c4e39ae1176606bf79e1733750fcd8aff65983642ac60d1271a9c4365af7d48b59bdc1e062eabf4904e181646d49eaca52d071d65c4bc436d00a7b94237404953b8b444933c3c5ee9b4d0e18de4813b507a58ae86a037aa94e81f4e02b6b5e01972ee68d6a711c85dca653a5799247a10471d5b0fb48dffd0f3caf5c90201262df96240f0848c02f5ce82dea42eacaf96882c58dcb4999c35753b733d6009ac80e9e49f00cbe633b7f4249e947bfd5618130e150d292b12e36c6b3b3fe8a0b8159cb5a2f160e3ea9ec271daf9d3ce3f82055384b3bfa081b78486cb6846601341db24867505cebc9a508214e07167bcf6877ea901102ff99b0a21f58c92aab856ee4e5bd579a34b026cdc24d09807177580afc0f5d5c210ed795707c6101d119c5e7ca334b4b3d7e7cf664464994f7d44e74ebd8d1d6e0166e75cf61df8a3bfeb605b059bccc8b0ca65a20a6cd62f9c9017e62a24bbc887753fab15f5198b1318c4318b57ff4f6ef39f8c622fd3491829350fd765e3268684d40510eaf26bf68b3e1da64e26e60dc1f402dac64f6e3517ef51b37073c321db4fb956c14adcd5f5620d565dd8e7ec916dd14ba60cb51ce861c55b7d31892a79a8e0c42ca9c32e0bb82a239ec55ed3113aaecf5172492f65d26c135b977a4f5c3bb396c4a2c33355918da88d80b852c39614db94a9a3b95d4d7b16c813911f5710c38fafb709966baa9ab6b2da64b8e83ca4dffb33ef12e818405686cbf32597c04e114191fea64c5b398c0901a17d0d540cdb23df83ce0d94cb8d2c1f3a24df6af2d18776114d16cd48a60bb674fafc53bab6ba2f75e1325f3148ac83c0c91d02d02bf81f86fc01ce554e7126966a937cc7597ce63f86ec46cecedc21f1e08d3f7992d834505bd0f1f9ef2c2e03b063feac80bb57da9b4650ba5eff7759df73f074a0a5f3b54630b080448845e4a90df898c4ff53d3c02b91e5dfa4d67acf9f589cfba25d377407b79b49cf7c787da3caa93f42cba758a78a42797f387394a118f87d9efccf2c19be11653275633fa849eba096c8886c845fe5f922a2001add80ac2d6bf306b55a8b15db4d0c1b1c39ff25ee7b8407aef21772e783e3bff3f20be2f7441c6e1bf1dc012cae1873f57389863dd240c335372605a187f05f0b51dd69678d452714e151eeb2dce75c330186ea3e0c9eaa87c632218457c4cd1cf2316e70717d81cf5df97421f6d3a41cff1916b89dc4f50668c665ff0e873254b417eef95a88066b2b0a1f1a2801890b5a78e117d3ae1450981454ae09c7c8b8fbfd1c8f92f839bbfa2f89d816ac57d12c97a677d8dbdacb261310099b5c319b00c49ca4d73afae45c12e30dc4d9dd39f0e5c5c7aecb3df1b9db88a07577d01ef7cd9ba172eb0bbe82cc97b1dfafc8d216984c7e24c3bd6ff2eaa7f3003b4abb3b689b43c81d423a1d80468ce64115487d9fb35098cfc4fc6547664a582bfe94a28a0b97c97a839b7f49f4fa3aba2e6adf8e38de90e46a7ae1f7a791cbdb318777264646f27cce7f8fe9c0c73bbef402bfc7234238dee234bb1d1119828359a5665cc715b511f9aad8d3810a925fff520615a6f687d68e20a7bf56cade375f389b700adc6ded373abc4a979fd23f54663213c91026245d1d814a9bd63888e3c78d0a44d00f6fcd0837f651e3cb3760ba021e151cf9825bb0608a637bc47663e06bf29bc3cdcfcd8b0f1ddef13f684cb859cd9af580b17b0c4485e82202d43e92cf8ad3116ac168562507a57c0d5789e525dc108160f8f20c43bae08fdae24594b8351f0705162d86ab3ae44d511eb11052db378c42b1df8c22ba92315d217cbfb95188d58bccdd13d62deeab55def673caae5271cbe55f020fb7993320054d2b4d27ac59334efd384de0c953f08be8d05d437a831aeaeadb1cfd02d49371000a8af56253f53877b1b353163a020c3f84a68218cbb6c23150b6bdd57da061720e3b70d26efa31884a5b627aec0fed84cbeae4ef97cde04cb5a207a96b6dd9c7fdc06e54ffaa9f737306dc943173f3d601c58fbf0f6d49152e4a6f346b67487bf6665c3104efa6ef4830ac353a9ae0bd9bc3f38e96cb60dccc99ef8efa64d43418736e5a54304b9f54eaef8c625cd8f5d46dca98fa81178cbd12eb52a2b74f4c9993bcaca369af876fdf36f64c717b0fb0364f27f3fefec4d9e44fa666209d5e21f24bb1bea1ff16d4d776fb35f9b731d8a5a981f0cd0a413ca41fb8f77479faa48d542889368251969951ba302a8883b511d3b5e37dbf8d713e579e4dc836781d3b9ef11f707b708e7839720fbb0b96d48b57fae034fdc875bc5f6682fee2cb87cda41c214b60ee573e9240071bd34b6817729c7ece5122ed824131b1c5b3f25a879c3aaed700addccbba69b3cc1c830462733d7e2f489d511766038c2a30b982c46676153b1d7d6e2470c8b7e90748f9fec97379f5b4e37f9bd3444f7e9565246f3ebf72e2cc45166ec2b4afa3b94fbe0edbae7e6cc19c5eb13112f864a28da29760e7831779b4af8c9f0e44d6dbec2368c3d1e3fb6a7cb31e3c3246fca7a6671080f355fa489f2b7a1e9c9e9e9ef17b909b3b7573e3c089bfcfe43395f5e35ceb1bd5e99a1c7226db1edb4ab89e450c49a29ce64707b2edf45123baa59ca9f9f95988bc289e3b8f549617321f055105a6d94f6faaf2164517b99c001dc9a1504aee55acfacfe4848951e81af6adb30bcf3d80aadaf1f17337df4aba7560f0787fcbf823c9bf9bb8b761f16df481d6a36e574c28a5786c7acc41ad994eb74584188f57290d01c1db457e4f49ffdf56acec90fb95737722fdc70cbf6e286b37c985f63e04bc1786f2793a929fe4ba9b7344cd1f47a61bfe4b8419884a42084c2bc6adac46ffe216ea2cc2b487ecec9c9caa2c4cb35b5dbc3b29882886452736b6536e9e973ef4d4bfce1ea3229f9442034d148be8b1d00099b505b74cec877a6d39082da9a35b96982d5d2d894388b42d4ecd0cc66bb03daddef08ecdfe4fe53eda98d70ae0a236c2038ff39e4bbef85a79fd1a75dc4aec1527484d31abc09829951d01876d9c777abce7dba14e04df89b3da386516e674b95bbedc8a48b6276476dcdce623da8fffa36dc95bb6d22b621f017d544404265c8a9d81912a6ace036db7d5ec43347005beb8097a434662f5f56aef1a6c717a9d98fd0d4ff958a8bb2ec7517b715901d4a9e22c66b150451682f3f1f52743eed71290ad60b41daccec84abfad4971c5a0a91b2d39ffdd199cc32f16161ba7978f447360257ed3fa6f01fb61836dcf9ddefebaebf003890d54173a07a2dd1d9d28d1a8fe2fbb1936e63687ed46a6cceb1610cc57006e48b58db5ca9d1e6df1259098702b692f7bc04b1e357f78920405a181f95f726e017849b17939a7ec6f494f288fd003ac2f430612e4fe0fa86cdf358228fc0c9cef4cd07759a54721c673c14fc220aa51a3ef9943422c88e7919580f75b6eb4dad912cda9781cc3f549caecb61c387d2cbacbc842c13a8a6a3cfa8f16c86cbecd3fe9a238992301a8c42024690b88ab85da58a1d5ac5861a7d7bfe786f3b8c190b202c3893c2a1f8d53606984964fa073d6731a241e7729d37bf301b063674fde91b5007dabe42e897041081184c4026feec0313873514d6a218d6df6c6f09356ac950f237a65bbf48c47d73a038f210a1f15ef1214f4accc5fdbc8bf1449c390697b12a9e3d9b99db6621a356ea273d833057ac2c58ae0ded3678c378eba5e4885b4266441fe9f82e3c119ad0118bb940082701f7dc057ebd9385a42cf58cda71d1266ddfae9a1d2e185274c7cc8c006ab160a9cc5d8a77e263bd8d98703daa52ec1080cf71251f70cd91efb74ed4c439721d8d7b0504ce0e221f2c98f6bd60e3d5a3f002f91c7588b5d20aa8d9f7f6fe25680c3563279d903ac8a27f69de517bd162a07ea64dc775940d1594c7fb642f8e258c62eb9ab176a8760c1511e67df24267084f1165c137aca9150b5adb093275d9a5978904f79eb638219568df090c4167dd67cabadc2a432625d5032648b2d7a225dd36a031612326cd45cad626255d2e6b66dcaa4204f8521b54ff25a91d29e5b64bf1400fe25f98fdf1476e1275ca024f811947886ba212108f63403151e4f5e6484c3230868390c45833821890c36432197a253c51918d26e4a3af8659abe29b807034a2fea248ee37db7dc064ba3295e4b626021fa79a16f124e16affa3be43d057ff8042da38d59258e50db158bc553707ddef8fd270a0d8fb431992203e768a174c81b58f96c9a61cf8eab72544ba42bfb0be70b8e93c0ef131315b12d0893d3982dfef0df5e80ec8c231546eb1c066a830840c7237097f307b3bafc85cc1a4fc92491f601af9eb9c3ed07d767b92c98ec53ee9f0f0e15698c05d985d06d3c022ff0a8a8f17a9b14747da009c1838901b2ca8aa4938baa8c8b037e6644150d4410a0b5491bc3300c268688858a0aa6e8608e02b668392d8b1f5b631b8d9a8da5db4aae6ceb002fa27907dfba4d782bbe000f8aecdeb4339254de1dbac0bbbdfc5687fd24eb0f9e0cf40b27b08d139806494279d00c8aad3a5c9c401e9c402128d5e4fc4099aaea6bf5f12112f31eb922faac502f1a3834831084a8a2b63011e232e19c70e288609ffa2071299274d2d438695438693a3871429c38cb89af13f62389ed54d01f585c1c0d93054ffa0b4f883ad6c409fbe184c120a1301038065c86c704d3b448304d2440a1003b610938d69a9ba1288f50441c8250c3f2801ffcb0871611c5a2a1c415419024141e705ce1c58b66103d7898cf0c555b68261045753279608200ca8ce763c5264eac70e3c40a2a7cde43c50e1ef645d5858d2124e1a48a227cf1a3d0cb5dd9802811e599a128204a745205c70d921bd5733fb40849b2c4668a0cb02469faf0c614202a64bf9811816250088045057a51e5630a4754a1e9640a3948523592240d1f2449e3461b7b58430f92468d3c744213094a4c07e573324514275340713285025078509101314e9e473a799474f242d2c91b4992c60e92a4a983544d219d48b106a90627526c7122451927527c21492c28410a20e210070d1d2449330749d2c8210e1faa23d2c0419224cd1b4033f422499a34dc20aaa82d4e30c0058d1b346d90240d1b2449b3068d1a502a7081345688b2271710802445218828d488020c2751f4800349664c9219731285e8240a21ede1c0c49e1c1440f9745072447182b206499242a00f553f1d29c78989154e50c43841f1e2048a1440bcc1861a7690431a6b38031a50cc2a34d5cf48e53d749e40f14ea0d88024492b404149018a67164922d1605f860a8528fa441c9e08c2133778c206663c918227a82772fc139a930a2441b20f060a16d5dce20b97203980f00372949828c2119a3224a20f6a98d0c1640e6e300943124ca660c203144c000092244d5fd8cf7f811213fa509dd9f2269c1309404992649c44401392f41272c14904825085a659759470128129aa1308b4a00a4d473e238ee8c867347d94884e1eb0054962142b8ed455f93839e18124893e584c4e12dbd9545888a88f2864ff379bca332b556931252b40312314847c3c68e6658a4fd5794bb180a80f16270db0815485a62433e68ad057f652568846d40903129172c2800e9c28208d0cfc60499258208303b420018628590423dc2841a3840c2709f080a4714333831304a821499f190d19d080a20331384180002449bac401ec19064822499a31942000094728421124c90c498a410c66408624955146a8c7013a1d901a450a94cf7bda09013c484a1620090e5042ba42152e70841f42c8010ea42152910e50e064001ac9ce0fb91b09400492d448363c78542702c81180499c04000e2a50785c8a0a7d4c47b4b91fdaf122430101557054bf48d501f9a83aa0e901c5740010872405917a9c00a0049274292af4c412de529f138d0f4e341e49a2b6902aeb99eff2a12133f43c78d4ce0cc9d65a6badb9bbbbbbbb33c618638c31d6ddddddddcdccccccccbc7af5ead5ab57af5ebd7a758c31c618638c104208218410bef7de7befbde79c73ce39e75c6badb5d65a6beeeeeeeeee8c31c618638c757777777733333333afb5d65a6badc531c618638c31420821841042f8de7befbdf79e73ce39e79c73adb5d65a6badb9bbbbbbbb33c618638c31d6ddddddddcdcccccccc8b237cae396be62266451a01269a2049d20cfd15b5633f44a8d01b99f9a105c5224379fe878609a1e78844d4e7a3c1424b45b1549a2b4892c60a92a4a98267cad0cf4c21148b2469a820499a29485290a459d92a2515a0cfca8746a2d1ed4c8a220a7528caf4f18caaa9fa0a0b4aec9024cd1992a4818224699e20e9a084e40449d2304192344b508224699220491a244892e60846308134b2e2b2d1426ad1051016b969e9a2a5456ea410225a74c1c5c60b1f5cf8f08c2cc5051cf58879d10a0ce5e901472bffe9c07c241049b44092248d0f7a603731a3ea3d33121c2a764892541201a9e40c48a8a00a4d3374535513a49141621f0c142c449ff9e9c4744231a3cf3f220d49b222029a91e4197926b505cca7230a51d407ca147a84171a2f44204992a429a221f2224992678aa68a23b4901a8105fba11e709445fd2226f0e69c113ad0c1082f6c872ab2f220a8cdb8a84494a772375a841792f474fe506929623f3e66423386f2c82402119224c95c217ff8f8214af2a32e3e2b2a2d459ece1f9588f2b8d8a7c2dd08a50a77a31df552d40d21516929b2b92124a1078d2a44b8808ba8b414516929f2e94ccf10974a543dcc10106856a3ea7308151068f2e0b1015552891f2428c40089219214620cb09988008c20499209241d847843920a1137f693746e10e2708424491e8f6322a11fc283c7065411c223499214253ae288e30d499274c43102a90b38dad0a1b079535d5143d515b523fa1836723c8223d83c723772d781636510660091248d073a20499e5048564d0f0ef0a7f2418940332120a24f871a8281900f04310641481a0ae808440fa27080243d118427cc084403a44a93058fd04bd20c208af007454892540249072056159a38a209ba62468485864715fa4e0b0a473441281e8f0f1e3c74fc21057e8843871f5c241d6fbc21bd91224992157cc8826bbd850034259274828d1b3d9024c944d2c1072aa4246b4dcdd3b2c20ae7064842e181d2c5152b6e0c91dc409124e93d9d3b61664c1b9a9024e943a40d38d440d1b186019ad0860aa436bc90daf000cc075aa1f242c48a0fc582d286244928f6d339c286212410c5c61924141e5c8409483ad84832a4191b3b24c91dca870d014892f459d983225078d88e1c244972d1b18726ec218924edd88310499276a07cdd438a24ed40d1b1870668828a1d6bb081c223f4301d6ac5435923079224893e6b8024141e6b7821556baccaae610022920e3dbc21e9d0c31f241d7a1084a4430f71d4cf4865e6e38362599124cd8e25499a2ba4c98a07cdbc4892a60a49d240f11e3ac3e854df22499a0d5492a4d140cc7bfe41332f92a4a16214b3a93a92a4d93185243d6846f4a099299a97925334b5d0a1468a068a0c4892a491423445530b22208a054451422449937257bef248fb91a11f7725f443923418780f9d2fa11f9fd04824aa1f0251212d5c8288dec5332b25e4ff25886752ca1344f42e2a2a2e10ba40e842c4b10b9166765242dcada8a8c0662bd0b1bb15cd05242e64a8501049d24481b222491a2844d547923416a88024aac20043365b7916214244414441840869b6e242e483345b09030ce96ee5598408110511051122c4dd8a0b910fe26e050cd94c4524a4998a2848339517d24ce58334530143ba5311e24e4514c49dca0b71a7f241dca984e101d2ac59b39595951517222b2e4462ecc747b3955017cd56429417cd563c2ecd563ccd56c2f00071b712860888bb9521ee565656565c88acb81089b11f1fee56425db85b09515eb85bf1b81009715cdcad784222aac5dd4a457d346bd6ac994ab366cd9a356bd6ac59457db85381f1741e883b9510457954dca9a888aa0fcd6771a7220a81e1e24ec55a1fee54e8c8c59d8a67c4853b15241d2ddca97c3a5cb8534932b970a7f281e9543320d00c89a059339715222e2a445cdca910a99f51e8c5dd8a8a16cd549aadb8ac107171b74204c810772acd5456dca9ac346ba6e26e45f421d0b7b85301b284396041b21398c3169224e99803cf410159a0a0f0f80284c24556111ff6635eb412a2427280220e3fe320461cd6e4c0a18283186cb3111c5400872924a99a28554c33ffe30d492469d38353cd2d50aa8992451a37e022312fea4892460211903e8c4e15f3a24eb3d187d1a9dc504992c4bc38c2e79ab3221f1fffa1649e45a5ba1f1ae2a1286abf873b2af4617ca8cf045513e4c28621fc70598321d63004697a149967594305663649dcadd84fd2510951a23aafa53aa1ca7efed38179eae97c0d5194e77244309f0f813af55b5ee4121a0199992c3fe3230d6974a1230d4310511e978fe9bcd84f0704f6d339928646431c68d041094494a7aac27e3a47485082890a2beca7736487fd748e5c6ac60382cfcad70972b1ff79501792a46180660167c8e20c28d2190a80c22326c60c1f141e9beac87cd006049a3c78446ead435519a620bda8806086783a33468aa84e0b7d117826cc0b7911757eb8d64766ad0f02d77a725c5c6b4ae6a599cc6c6126335b1693992db165660b6c99d9f25a66b6b896992dad65668bb7cc6c612d335bba6566cb6a99d9125966b64096992d8f65668b6399d9d25866b638cbcc16c632b3a55966b630cbcc9628335ba0cc6c7932b3c5c9cc9626335b5c66b63099d9d232b3856566cb929946c8e0802a34a181051d687840071a5248d218a820d98f1955a339a4fa8e8e31144092c4700669b262d8c124aa3a42445f8961b2341b8188e488e34554910f513294108a7aa97218a21092247dba683602c30a2429471c3e3e44e0a83e3464109294431081908e544a8865f91025aa428fc443b9f0e0f13c4413a5d90845542728e66d17782049d28b2824ae39eb242ce43f140b17c2f080debe67f36174aaea61266853553d9a8d389b23758ab6108298da114d198f6756fb9bcd666dd6a68baccdda7091ea5b3c1e1fb5ea70f1af22aa3a9ed07790c0cc1f1f335d3e2b228a85a5d9880a7d7079b11d205c641569c6a92687534d4e911e3da0cba2191177a3b754eda1630a389024984f67c6e3f141b550a224a5240a0a40a0128ad8c2195638a20a4d1faa138514e83843ea3863c8342945015285263aaa2469650a1d5090c3ed702a8903137540c1495568ba42f4e98c603a54f4e9c074a81531a34f35d7c44850b8d0c113b220bda07c3592410e9327c427a048f6613e2820aa330249059c8d740227882449ea694d8c44fee8a2e5c5e58b12a9e48c922a343da542a2aaadf8743ca18f59430f00804d384392a42fde43911ce984ac98f9238f279f9615309427871a9294230f9294030f928e2688710749929078286a088b085a26912042beaa7c803a2f9d992454a80865472e40aa17899ece1f445cb208514078f02080ed504578f0d86c389b0e686e4055d58335379bf6139a1155dc3df8916c1c3333f3e2089f6bce9ab5c86233b3983fb2783a29f5928252cd2c8aa05401820d920f81cab084244d2865a0a1a38c468624c870021949282af428a03ac880128a8e17c02149d214eabca0063a5e9044aa4414cba4e3053b2617a4f1a1444f47d7053990241d2ef8a2ba940be01896f850a11f238d313e44cd9719832349938e31c650c0141fdb411103109224e910430b301f2a242302205d7c18a11e3d7af4e8228b2c92503f363233546d92cc98169ca0058f0570b0e0062c60010ba658011e56f082158460053b2469a258a62355473402cd78aa98993228339f593b9f09f2783a321fd3114d199499cf7c99a8a876a06840031ad08087a240339ece8ce7fe87faa8600d1590a1024b89503ed6043c787c353943be2001ca4b0a38a1230569e8484111244a3483f29c2f5270002fe808630961804047180f987184247d159b8dcb0c0f663a90a410497ea023892889053e9d19125d41a1cc7c6638a2102523aa461765a68a992f3ef6333955f8c8a272a95ca8f8fc40a9c2435174478f1e2f59802811109922320298bef8e20b2aaa0051a21d4190b071a49a2324399024c98a1085c40b898241b29084466c48f3c7080cd38fd18f0020ea822846644575081d151a3c78d8f6565e2e4ee765bbf282992f5d94b8415af66e2fd6eae8fd67bf0d92e3747ce3a48fba4b091b9e2ffa6ebb3e1f7df72d1494ac414edb8eb60b19fab58e3afbe98000aa213216a35b2e3277ee3984ce756cb7b9da1546cb6ea64136aeac513bafc7dbde9a25689077756dbc286d4a2b7d7b06591fba7a6ba3fed331ea7ca5c40c3246e896ede6aa776dff6590933ebba6d768db65bf22192475d335b3e5a8bbf442ca7930f3659a60a6689a426f210b89406848469fad85cf698d77353a06c990c51bdfd106a97b3dc52097d7ee56effae6ebdf711e83f11b516d292f4152c0901fd37999a6f788e8a89aa669822c4636a39230c4e55c8bafdd5b33d36b9bf2b2b539eec7791ca2904ccfddc0743c9e779da4040cf241cadcf47adb85d0310a5d48c917647d4799473a632fbab8398fdfae1620912991e843a2aa138a2f255e90eddafa59bf1bc736db7bbec3f248a8d09069e2caf3968ad9346fa2a40b925dbfdd26b79f9cc7d64e6b7d7c3ad41039d97f420917e4b3f6b167df5e6f8c31dc826cef3d76ee55e8d6c6398a619e1117d304f150a205d92ab5f1d5d78feb43879cc7475a8602b52c48b7de366ef4beea4d9f711e77362130aa9ea67783122cc8cb6ea9f37a70c549a95322c8cd239d04255790cbdb9db596ddc62eab97f3787137e86ec4dcf3a2c40a72b6af715a5b5fb3ed68398f8fd409f2364dd3e46e344d9ed9097d354df6f347eaa4d3e4af648c922a48f8d5c6d870d6f88c4de63c7671a1e999b57247de33ed23098daa6316239b1e255490f1ba696f2fd666b3ed6f0ab2b276bfa953c6d7be8342a6490af2d2bb608d6f756bebc209af88484854b96331b231414914e4ebafb459c7d67dcbb4e40c16978d913176b4a785c2bc7632abed1032af469f10695bf79cb9eb6ea55de204595d43b69e9b9fb6d6604913a4dfc996564ae9b3bcde723762825cf37fbdb858ec5fffbe53b20439e17458bfbfceebbeee4809d22d8495ff45b7d6b976dc664912bee37af146e670f1bd8ff3d8dd03c574181224c7492b638f526699d796e4b708bd0c25a44f507204d99885d727bd9479d53a61e747094a8c2027bbed59c7f9f3fe6a91c8344d53cce8e3588c6c684911e45bfd1fd9b6ae0d3ad8bc8408d2b9e172d4dac7b859ee583204e9b8b1061f5dd5edba0d398f65fec342ef1212b930bf90a862254290d7d5fa2bae7819df662fe731bca251ac60485431561204c9ec91ef751c2be5c83c66f379cfe31910559b6da3949714da4629b1bf0408727d5df4daf92cbdb1236d404a7e20fb56a66d5fa41432ffd91e28f181bc77ce06a9e3c990d1c8af07f25f84cdb117df9a3c5db49ff71f253c90f72d7d1effc5d0260629d981ac4fd9eb59613773bbfc79cfa69b16253a90be7c460a99df3d2f6e939303c9d6d25ae9bd1cdd2e4a398f1d8744150ea4c3e8d0b6bab8365a5be3bc4dd5698e253790ce46c673c567c8ec98721ebb8ff9c6cd6fa9daf98cde074a6c2029f5eacbb1b3175a873124aacc90bf9cd6d6e0bbccbdd8d44351748b921a48e75864af51fad1b1edd53490aed93aed5ccb456b9b35b72e99818ccdadb9536e0ed7bc8bf3986d363023fad6b50c24bb9fd45b5cafad5d6c82d854120339db71f7e2777456e8cd79dc2a2530906fc61b9fc2f916b56b3dcee38ab2ce2d24aa5c4919d25a16abab75fd5defee9221636d7d2d6d2cd2551fbf921748c7b5f65ab0a3b3b3d92e71819cf6db3facf6ff17f7978c21ef7b5debfdcb1c63daae084ac490b741d89aba836f396cc779ec992198177d3a1b3b41cda1174551cc65b005eeb11dadbe2a33ea9cf318368f36a2980b250b2a8bcb276d934d7addfc7436ee461be798d18709598163754eafecf69d3777cee3e6a202e91c8cccdd9eb35ddb6a5d2990acadcfe82cf79b963dc679dc5c98a128e6b22cc2907efd51fbaca3fdda99398fed6f9c5d8b3392521629dbd6377ab3d4f9b073c83455143a16231b9824325af85c7de83ad6c9e064ec3e44d59691357e7bee2de60b23a32e3616231b2f9044760eeb7af4d535db659b91640fe16af5c5791bf2f5888548c6e5ffdc8496357bec17e77105b9923ca17b6cebf5ca666dcf79bc61dc42f2fdc256237deb9cd1c89cc7ad0a3024f7a3b76f8490b9b6064110bae7acdab5ea4706d9a4a4f78c7f67846ed95bb582623a8c7e248c77bdbf8c3efe5be9c7c8e6f7da2ef66f6aafb7158f7431b2b5b05d643c672fea7e566ae733874cd36331b2b9472473ccbafb4e069945c6603e9d2371a140b63867b7566db4b42ba48c29d71df93d59c3aebf5cfcd79a3916239b1270e4336ef7b95557a3d72d384d1ecfd769fa108c74b6eb55f6eb32e89e3ae7b1ed6c1e34237a1f1fd334f3998cc5c8465ee97445ff45ddd1e66c9df3b889626e065e72858f3257e383d3195bcd5609acacf7fa7adc7ad2ebd3b2247605e6335ba6e9612a10b5e1c76264632b7b6c113ad8cd1d8b8cdb6f33cbef4e4ae36ce6cb79fc222425f4a29569fa109069b2df0151759aaad08b40d3cea9178744a0122550099fd97b778deef176fd50f1eba78b8f8a831588e231253f7b93a17b945d74479d6331b2d97cc19c37165fec059941e8fe9c57637451e775b1a61fd98c3b8b914ddd480b1985cfc2a60e42fb28e7f14c4804ab1af2e1550d4d8f2c46367e02c79039e6e6cd7163e6a037c3d8683b1bf95deaed1c13c86fabbbfda573f27a7e4b20ff3d3285b63d7576d049201bd7faec3ae8aab769dd76a88d6331b2198111f9e89cacad5a997afbdb11c8f8b6314abb1d737a1715e285f479eb57766f74ccb4af08e4aaf7c5c87e9bb1d58ebdb4b318d904292299766cecececb3aebd4d53880a857e9adc6331b2e91071ad57ece7e05b8b6133638fddebf2e833c2c7cc3072de344d93f3900884821779a3d3396375adc6089d398fab23f3411bd1c344b7d9bce748e86336f4486dcd626403c4455ac8de7270dd672b6cdb388f31d022adc717fd3686cfb5785d0824e3d5b6a1bdb4bae7f035f3907f0f176d96d77cf53b86a00bdb5e7cddacae56af375b4bb5b02c06eb738b42beacc245570b1792553759b3fe8f596ad9e43c4ef5d80979104e7eeb7174cbc276efa113c4ed0202796773b4f536befdde6d9cc7202a64797d405e861fbb317e0621dfc979fc1fca5337cdbc22df3adae2bbfcdef557e63c863fb6801c39bef5da41f61673b39b6290783c9364bb38cee66efddddb9585d1a94621ea7a40319d6e31a30f13c55c9920b2ae8efcacdde9566bffeea53651888a7c11bac3d791c2f51cab52327b993a870dc6bf6d4db7a285b4d32b64ebae5b61b376711e6f18b3e3fa48581740e46448adbdbed8f3d3d99cc79b66278ab9ccfd903e9f8b753ddb45db5f1585a0077cc8666f3df36963a4efadc579bc79d003d21d3ebbfedad93e1d759cc7905f072483b3ced5ebde1827e4c9792c438560b87bc837eb73cbe6536eb0c5cb791c12551c90ebbbf56dcdb173cc59e43c16c5dc48240b79dd5cded0b98eedfa659cc71bfb991bca72bf116021a7d7472b37eb687b66c879cca946a04d88ba1be7230548ca4bca8b9094fa45ea23d9c8ccd06b2c46364278c87fb345c8a8e5efb72ce43c0e4d4eddac491473a7e9435415816814b3598dc5c846045142b771d186937a3be793f3e02845a52525e54548cae6d3791d1281d887a8ea2c46361c80b23e6be37d675ef9bab9388f43ff79922e08e7746fb5c5ed59ca795cd9ff6a237a98c6292f41aa99b27a94e2324d33e54548cac6238b914d890c5c6ba0d9d780f3142c02dd14159200531a5812b8c2025304aca04015bc6303ddb91f025d400313a082023ba498428a0c5c408a2b1aa5f0e6391b0a60c05d052e9084921159200a0ba040e15a5b80613307a502cce3e91c5914986202f3433b2430f347aa88c01410d850f10051688aa638690ce880e954513880ab993fb2d9d1002918d03154dd6cf8886b0daa5fbd056c7628000309d86ca440c026c98c81e20030946703f3e9c0c0509e081820c60205e0cd87ee2644892605083000dec44c118a00a80880fdaf36170040ca13950666478e8dfdcc8d9bf0460325bca93a9b8d28c96340f249b0195028f998f9d1b0197d304001a4365ee0081fd20e7490031cdcc00666d480063390410c605006192f70c11862b480052b50410ac298495285c000519f18cf1114743830f76da5f38bcd094c500212181981a4a30966483a9aa0891c602880d4861860f02179200090e8801d24a96a44079248928402820e3c213d128a7aa8da630d3d9a207a981e64f4b05af448e9e1000e48e2840376c082c4811c70400c69118a901c6892cce2115920e2d3a158baf359c902491643b2a0408924652161f1862461e1052ccec02208120a0f2c7220cd7c7c4892261188a82e92260c27583880872624942a78acc1c30c28222a08ca34adf048398421a4dae1804254cc8c87026d409468f347664c54c34934038507caa72324824ea2094ea2072449a26207ca099aa2a2024ea02002af979d8bcdb7f84d19c8da8b5a56d946763dd218c8c9f8dde7bd60b35f87815cad39cea6abf172ccbe0c19977bfc98838d23fc66c9901de17d089d6defb5f802c9eebbceefcdf68db2ea02492b6c18977d7863f71c4376743a2b74d4325ff7e51043daf6bede8790a3b3d16b27a475b4c2656f9bbcba3527248bf1cec88d2dbe90996e42b2c5fe99efbba76fba2664e4ebe65cb523a3f4ae9909c9973e65b7b7c18e7e3121ddaf576b74d3c2d96d5e423ef7cbfdb3eff64dee6809c9ff0c5a0717f51befad847c37ce76935aafedce2125249d8e272fea209b4cdf2464d759f9dfa22daefa4d1292f6578ed6c5b6bf6024e45d8db2e59a29bbafad07095923bceece603b65661f21a765cf7fbeb8e0a5d61192d5be7fd9e4c9b339d808f92cb3f1da767667d76b8c90d1bd3dda8f7b3d48d91721fd397be7cf672b5f5a1521dd178dabf93367b3c144c8cacdaeb5ec22651e9b2142b69bb536b798ad6ca33d8464af56dbcc3c9b568786906d1d5aefe6f6d9dbba101246d6737e74e8cd1242b2c81ddfe30b59838b3b0ef9b87937bef7d1d5ecc22163bc4ddfed7d2f3e1d846cc8b7c5b6e074ccad1784a4cf5ebfceceedeae61c0819db9b2c5e7ee88dad0a0849ed83af419e75c23be31f24f3361f7f74b4b9c86d711ea7bc04892c46362c30f1839cedb2f5b1ad7b591fa4c74b1bad9332b6be46ee37e46d9451f73042e66ed15a01f11d98f0c1dfd1775a1983f4d5673898b821979b96dee66e7d5f5d534f790992c20d0993362adf5ad9fe7dea1a3664c3ba76ddb9afbb6e03fb0c267b90ec8cd558638bd0aba56bc89f8b3e9cf3d2ba685d1c4df420dbc15f347a9bafad852f4a1335e465bcaaebc7deadea6e1cd20224c5ae4cd3102129ce358b918dc919267990af59e85875f6dea06d95f3382482292f4152aa0f202922aac8e763545a5266422faca182091e24eb55fbf1a4eed141fa9cc77e0749996de62284ce1ca59529133b48bed4ad66f0ef7ae89cd641ceaecc7e2f76fb68ad7490ef6e4f7e6ea616421add03267390fd60843edf84d6a77b4b0ed2a34fe66b9f3d52bf1e0709d74fdadc2f16ffd566e120e36cc6ccdab61a69ac363233b4ba0b9337488f90b6f8e6ab2f360b997f98a421edacbce663c691d947a90b17dd5b98b841faabd1328b3e277bf33658039336c858a3ffa38dcdd896b187619b206c901056c8bcdc5b5edb83eb6e8ed4190292029b47292f42a6a95a839cd5a37dcf986b0fdbbd1640305183e479eb6c0f2bb5d6d1c7d2209bbad66ebb471a29f5478364d5e37acc395a5985ebb9c919e4740cb6eb7a5adbbaf96e4ccc205fa5b6b93fc75665ee1e91212665903c394e37a9b3962f7ded8c98904132c71afb572dfcf518da24d48f69b29f84a2f0814cd0e0dbeeaf1fd20b6f739cc7ac81613206d7fcd88b8d357d7ecc793c93840a8136949da6bbb1764e5367865e9849224cc4305fb5b45f5bac3de77d6854519e3a4d53e83d9e182aa4b21e17c2240cef3a5e70ae581fb4d13d189c7d733ab53d2b6cfef00b72b1757d7673fd66a5de9cc72ee525488aa7a33245c1c40bdc5c65cdd256a985b1b92e3c1bdbd23abd4dca1ce33c4eb16f9412d3e154139214fa4629cb45265cf8371facb71db37651cb792c1ac540d1888b8f4adb026c6ef56aedb9c6dd1a64fce69bd746e79abb4e3d9e59fda3d22030d1826ccb1a3ac8fc9f7d6fb61504932c4867eae6643a2b6d079b731eb766662c46362e265890cdba069d4dcbcf98f6e43c66579071c2bb38be7bef627f3d04132bc8ffa737fae4e8f7f97cce635705f911367ae15f375d7beeaad935c82c46362a26549073567693f2c3087f7af4782610b8e13805792dc765dd84cdcd27749cc76b39becdb118d9a4984801f2eaf62284efc1d51a3bf77dcf2d8fb53637635b6d1205e96e8b7771bfb7cbac3e108b918d8cc91972b1e5a08dfe1e3e9bd78251b90f1328c8cb3f5b651446fec93a4ea59f201d65dc3d2f53187d42a6448e7a6113274886f6e73bfb8cb5fa2ae53c8636860aa938c1a40992bdcba67dac27b747df5db498a669f2314dffe9dc3b231bc184097245a78b31857cdfb4fe388f3715ce783cf34e936b7d642d41da67b638ce18e37cd52fe7b112a43b07aff3ba2abc0bc69504e93f9fddc5fc39caacd6161324c8f962534b5dacd4f9b109f31f47c4e408b24e678f56efbecdb6d08701999a1841b2b35add63336be6fa721e7f189de8854911e49dbf9a6bcfd1c5a87d8b08d2fdb2d141fa66753152e63ceec7ae932cde5c5eec5a1f7116231b6b3204e9bc5ccfe9edf2377bbd0c132148e766ff73778cf673b03d4803932048369fb2d71c8b6d326ceeabc919f2363c630204f99cbdaf6db40d726bb5b24cd334b118d9844c7e20abfbaf7eb698e3846f721e3b2f9a890fa45b3a9973ae9741d87f7b205b7cd545fb2e65d6c1f64c30e181b4363ed61ada7676f19dc90ee47aba28eb782174d7ec7520dd9dd3b665cf368ff152931c48777b2783eb31bb22e3e7e2c28184ce5f63ef39dbeab3fb8603931b48fad839d28ebe9ef5d9e00e4c6c20eb8c2ebef9dcc15ae77aedb118d9704ccc90efbeb7dada0bdfe4781b6e4c6a20df3167a753da9c451b8398d04052d88cddd2599ff31856cdae99c5c80688c90c6074ce9c6b7f10765cabad685babf1aee5fe276b9cc7d3544d5343622203596f7d75dd8ff73d378c233089819c7f9bf9c6c9e8746c2d0c644fc60ee374ad52669d97d1984318dd45778e5f5bcc316ef1cdf687acb137a7bb266448baeec3ba38b63ae37bd85108262f901c63cfc50dd6875cdda10f4c5c202f75edbbe7bafda86537fec0640c392b6dd4b67737da7cb69939649a360c6d6022863be7b55c73ed357c9039b8daba8d5aa72f42cee3fa5564b5a413322ee7b35576ca9abdbf9c57411e259c90ce36666e578d1f9aa3209e394dd32446c926243f1a1bf2b51f1dfb47ab91688aa6695abce15a7562dc394d48ea169deeba7b4e48d933212fbdb1cec66e7d7ed15ac79890942dac7fffc218ed9b7665c3979014d2e50e2ea74e7b5d670969af853e9d9dcdbe52d69590df78be77a947cbeebc8b12f2c50b639d8bd955198d91eb570d48ca8b9094990701e4c59bda11813a1ecffb24e4ac8bbd236b6c424948e7e9ecff47d79e832ffe480192e223e545488a5b33288984ecf6565df48ece7dba2889f4e1a20510394dd3344d90c5c8268c1248c855175cb0dd9ef436b695f338446d440fe30c1fe347445f0df9f973065fb7b51c33c7d5f4428ed4fab5f03942ae76b7e79ad3679bac9df398ba9fc842d983665e5e63414923248db0b1fb63f3dd1779aec0c808591b65f4d9e95eb7858e721ec7182aa4521fe4b2796c11b2d969335bb718b5cfa822646cebd9fbcf5bf516991b414922e465cf1d72bf35bf794f4448f7e5fc6f63361947f74a0eb19c47e8abf98aaef16bcc1e650cd665dfbdfd9ffe40506208f9d37943d86075866f7e2124acec99bfc5bad967e12384847f1dfeab1edb743ae390f317736eb96773f6b21eea8628d154490192b292f222240534b99826d08ce9324ae090fdeee9a3cb32c83aaee83fa41907258390fe587baf7d9dcc7fc21b75502208c93f21b4f7576366cee683665e88501208c95ffd7f51870d99afc8bca9420920249c175a667eaef7f99b4c09257f90b5698dceaeed175f73c779fc1eca42d4104af45999a699d0342d1794f841be65b66e9b8fceda16bb73913ec8ca13de1559a3b732636f2624fa4817672a94bc215d64fdf74e36d763cb66737c908de965efda581d8cad35b8dc90b5fa7dbfdab1c6ff1c4e9333926962deb03312e656286943ce0a61bfbf8bcd7e3d1925fa6c28d1a7232a61433eba383a6646e9622fd23dc8dad6dfadaf1f46d8ba398f535e82a480a89016d304a242769aaa69aa8f6495ac21efe2ff77bfac46dace2ede70e56644bd84123dc8051775d6ef5ecabed52f324dd334239aa6b678c3a2cf9b1135753f9d2794a8216d63f1babecc2a3f742b461894e441de6bd9a3ff36ceaef7effd16519dcb8c123cc8562be5fae0ba6e417fb889d3b4d9cc696290c5c80649c91d2483d646da5f27eb1669b483bc953ebfeb75ba66b7da3a48171964b8ae57ea3532ff50d304598c6cc028a1434718577bee45065d2f876f45c6b6319dccbd2ea8d292c24222d01c64335fac358badeb7cb0c941aec8d7c26e8bcde96f31ce6318070929bb93f6b4bc22abfd70906dd6196d8bebbdbeb0716331b20941c91ba4a55eb9b11aebbc0c574d43062f4252364d038d23649ddf3e46c7ae1fda081b21a3ede9bcfd2dbb191a46c8c8eea3b4c50b2df5e7e822a4ab8e99f7b20e6173c740a3084967b5fdeeb17395e1738990eeba375dadad5dbbd5102161eb0bafed09b9aec9da2124ff7dd3f5c7387dd96a08e922e5b7babeb5dc72ad0b21ad63466f7b3769a5712d186808e1bdea8bdcdc1ce1f78cc3c5d68dc1c8adb1e7eeb56efad6a5ee3dd8e87c4e82060ee9ea5bec5a7737725bf7721e6faa8dfd7cd556a019847c666dc1e7bebecd5717e7f1a713f3a4a011847c3376376bb0d90addb46ff10d348190ec56fabe3d63ffd673721e57b5534dda0c9da0018484b75ad7ec6c43e6f15dcee3c647429408182f012841f307f9f8adf8a2c3469dcfc63e0fa2366f0a1a3f48db9a2db7dceb56576bd5e480b81d15347d90adb2e50aadff7d96b5c9dd469a37e48b7ff9b6bbfe3d5d35721e579bc62548d0f081a7a57143bef59e1bb4f645ce63c647d0b4215bc3778fb6ab76cef63c76e5d3c5345547288ae3adb5d6dcdddddddd19638c31c618ebeeeeeeee66666666e6089f6bce9a81a4acf780462f294384a42c6631b211800e346c48f7f6359bf0d9e9cfd5f620e3e2bfcfd56b3d46ebb8d9e84dcd1a92ad2fee3a6f63ad3646f520ad3b9f163e74ecae7b5ba3c6f2e570b1079b7384f1c566462773eaaea3f63e7e1102aaf8bd409307d9d3be6e3cd9ecea98313c48c69eb7fb6bf6838e69c71f9a3bc837db337e34fea26ddd0eb23a735e8fe9a477cdf60e68ea20ed73cfc566975d66f05a3ac8fa3ddd5747e6bfdeb47390f15e66d4c27add5bfea61c66f3652cbaa6d4c541def637c2e53436beed3138c89eec208cb3ddf6e3c741346f906cfb7eecf726cfc830529f4d1af2b5b61c6cf3ad7aafabd70dd2adffd7cd93da36c87f7d216dcc3247a78b970d32b668a79bf4bed77adb5d8384cf2efbfcded76c9dedaa4146660bf6856cb548df6f1ae45d0c23a4ccbae69af58b06f996bb95ae696b3357d73d83a4b4f65b77c1cbf0f6bf19a4c7199b7de7e5e674fa6590917d652dda66f6b9854f06f96ebd75dac9dcc70ae1a3212b5cdffcf15d47eefe31c8f90e63abebc1d8dcdf8a4142eabca67f43ead1551b06d95c5c3fdfa37ea1756ac120affde6da8d6fbe38e9d72fc84b678c76c667de62acd50b923ecbb4f2ec1b99fd58bb206d6d8cb5eacb7573ec552ec88e76b1b5d3f67df55ddd82ac8f29336fac6a41b265c8bee9ab1f6d8c340b92cee83ef247cb20a38e6241ba1aa15738bf3d63cbd12b48f7e2bdf0557bd77b736a0549ed62a6edec73dbdecf2a4846db32ebd6b3d7cc7d5241be5358df7c5fac7ae3390519d775eea96b8fe9ec35a5209fadd459bcd4b5e7ed4641cef91d6775bde0d7f6ce9035fa85f0d508e1bbd7452848c8ce27bb6af92db72d3e41c6b710d6bf3dffb2d8a213a4bbcc42a6def4459e2b3641da47e97ad0ae09978b8f09d2eb8490f56d0f2e415a7f73ad1721bbf3de0695202fe409d9196de7983b4c82646eb3ce7ed6dfcfcb201264b3f9bcd9fa864790d1c5b68d56462773fd4690ce1bbbf894de7efab408d2dfb608e3c70799ad4904d9b53edbaaa5add9f8e21064b53ee76b8ed54b2f83429097b63b57d7b7af36c320c816ad5bfd2e7f9bec1d10e4ac90f9b6b37bcc20fb81bc6e3d325ff7e9a48bf94032e89ec1f72e75ffdc3d90f52bafb69c756c35ca8f07d2b5f6eeceb7dd4819bf1dc8f64ddf23adf6efbcebe9404ec636c6c9de0bfebd3990b679edba66a50d69b338900debb2eead577703d9ee3eb76d5a6add84cfd940fabb5f6ffd67f75b73ce8cda6fd1afd0b9d740c29fb0b27a978dec6f391ac8f6ae5f9fbf9beed29f81a4f02b6bcfb1177b95817c7ddfd7357945b89c8d8174cbd8ec4921bfad0e0319ebfc6f6fc50927645a86f4b71c4f47a3c775a92343fe5fbaa24f1a6d6d97db0b6465cdda36cbfc2d57ad0b248dee35bcfdcc3563c8bbd8aaae27ab1ce784ae1143fa5ddc225bff1af4e8bc13f231d6e072d6debcce35e7847cede8dbda70bd5ed57d1312b6ed78617f378cd35d1332b6d7ec3ad8d5bd789967423e6be3ebf6d31b42cb1c13f21d73f5fbcef64bc86967bd7e2f83cc685f4b4867e17ff7476baddf5a09f9f7bdbeeb21656e394a09b9dc3abd0e7a7bc70a27216f3fdb9a316ffd6d1f9384642e3a73cae6eafb5cb348c8fbb73e67f359e7e6650609e9bafb3aeb1f6b3767f608f9fffd6ddd9d73b62de608d9dc3f658d6ffb657b1b212d85ebed5a333257fa8c9090619dff6b45d61eed22e4735b8b5abecc5fe354846cfada6d77e337a77176222465eb229df0355eb3672342ae49ab6dd0a77de65cec43c8c5abf6834d27fbc9b00d215f5bd6d969d95b685f77212474b4ba181fe3f5e6432684b46db6dff65ab3f5b28f43d246dbbdccad166be387433eb62c9b0e5aff87d10e42366dad5d6bad20e473eef2f4e8de318d3110f2efdbaf8fa3f5c72820e48c6eba7f0b36cb6dc23fc876aeedaacd9b3af3e60759e16567ecf274f7f0f39efa48369173f441322f8f6ebab518f305b90b39de9073725f9eab3666bb32c879cc2c0d39f8209f3d5ede7aaee777bdc779bcfa0b39dc9097327bed2edb7541db57c8d1867c2eda6efc6fcd79a333a79a5b8462b828401672b021d7b42ed6d77cb97563b59cc7cc43c8b107e97f9fe1b4eded5ad5ddb5de629aaa14205ca4ea6ecc626463801de458433ea73f2bbbcfcf1b7cce79dcfc1239f4206bd7e598ffdb4ba9c3c979ccbac83475403e9ecea7936eeaeb3672a821e18dee28ad2bde08b945cee3694a22da7cfdfaa13e33a24e10d1344d53354d4080cc70e1a323aaaa8f21326d804cd334d5ce34b9223f3621ea6e3cf5ed34857ec30800478e3c48e71832ff1bdfbc8e617540c88107196df51a1f8b34b267ebba83b4de0e3aeaac33c86b393bc8e5ee9dbb8bd55e16610721471de4bc17d267635cfcdcbf4a07e9ef5683973d5cd6798b73905c9f33b57e7fdd385ddf20871c64ac77ddff07637d74d6fb221172c441fe6cb7cfaf9baf3ebf8ef378f3e01839e020ed8ab3e19dac2b85eb43a056418e3748d7adcddb207ddc2b726f380db95ae40bd7a4d429576b398fe1e3400e37c8656b75932f5dd4c55579866eba3f44a6c9c275841c6d9097c1fb1c63af45ebd41fe7c9e460836c7575f3e8b1b24a1b6d1bd1c33c167d9098630db2f1737e6b756bcec5feaa417eabefebfb6c0e7fd99b06b9a83b5ae3a37da1adeea241526e7459fe37fffff93d83e4659fa374da7663f4f56690cdb3f931b33ce7b3d396415a0bab63abebaa707a5b32485ef3dbd5699fa50ed9a221eb9ad3ad6d95f6dbbe750c92b5f77c7dde39fd2dad6290b12fdb7fb4b938b9d21a0669179bcb2984ccd6d6af8241ae3aadc73add3bceeaf50bd2b2ef091757e610ba55bd20ed74d4b2f7ac1bb37e6917a483b6ce7f2dde8e943ee582b45da3ffe2c5fafeb2740b32aec8625cf6368dd629d582b4afed6b5a5f73cbb549b320a38bfe91f2adac9f6b140bd27eeb369f3b579f2da35790fd2d7a5dde627c0b32b5825c6bba8eafddd51cfd45ab20179bb53e66deef61a4930a9299bf76e7699db519e714246d6daec6fe4ed6e6d32805b95e83d456d74ffd7bc628481a1d5b67bd315cb1bd78866cf76c2d7ece277db52314e43f87d7adc77139641c9f2027d308fbbd06676d6b4527488793c6f7decd9fa50f9b20e743f8d8f3e89c65944226483b6765cff432537e0f2e414ef7165f7b87b335e7500992bddeeb7132d88bdb7d49909399478e70fa74bdfa2141be175d656c99df11a4d3a68b3238e9f5690469d7a2ad394bfbc6d56811648c8c3a868d3173ab3a11243386cc9e5617d7a3ed86202fadd0b6a5d3df53674d0832bae560bb752dc3176310a48ddc6bc1bf0f3a770804d996a3cc2ba476adc6dc7f20fdf662d3ef5bee6f6b1fc8fbccd66b117275372ed6037919474bb93a7b7651ca03795f73436bdde78b0d7b07b21973e5c8e2ac1e2bd581846c9b356df3bde5eb9c0319efbd8e3fb275f7d98a0309e1f467b13d7803e993ffd6cbae8dbf9e17e7f1c7cc8faf20870de474c7663fb61665d3a319b2df556be7d7e991d25703f9dec5c9fc60b3fb5cbb349090adfa1de15fe89abd33901e9788a864f0aa959196a22086310010425949150063130030302c1e8d86a3f178a2287af203140004597e6a9a422e124983c1500ca3208a82180662100008208410628cf14a116e00c03ef6a440347b8ef61d6168e8db8d0df74bf5611d510b68ad2dc4e0fd4d8a705a5d0d20b5810f42cc59fd9f4544d406b19c83a5727210ac88d8bc7a22b7f22c74413e376766d43f788a5fac2fdf055a20aaed78093242b7db8e7c7791f20bbdc6d4e1d1c0af715c1647479b78593ce764b6f93fdf909a5f5470cd7fea27029f280eab52b3137df15661e3323b8ff7015830f651a7273da49283635c46bc941e5f330b5c53695aa4c414c740ac11cc8cf434f43e3b88e4e300cbcf1b5855b61443e3b7cf3ae2f58cbe3a857309bbf341814fac97189f5c7dbbd563afe96028de3f4d559c637775ef79a3917c321f061cf708f244ab71818e38d3693729bbadf51eb454afaaa0d78a3f4d96679d2e21ac2a4724c040e4260cf2b64988d8840a4f118c18df8bb57c4b12d0f1378c3da0aaf7845d991ff3d2e20506a60f0aa374a0c8efa7fca2a0a21527897b47f660b144cda256ae37b42d3e55cd014e24343b0245cc00d7683431844a4d024634b43b04256600d928b43186524d014344b43a0635a600ad515aa79145472ba4a62af3940685bac813d562c2a15454487a9b188e64b508086641d025fc6b50cfeb6ff590124f46d0febcbdae95a0e40be251b52f6d3c1b83e9328941be548d995566b1a4bacbc4fb17900195418442a82bab0fe602070a2030b4eed4f5c941dd598cb823f387efd4d4632b82e965e5d48ddeaf1cb73606e2728a1388010a05ff86fe03848fab7cf105a2e4006f4b5c860909ce6cb43b4223b61ad1179b067e396222d21b4b2fb206a21d49117b35622ed207421dbb22a26e4e92315b2af3bd8e0d743812230636e22eb60c9c3ab2a2440bdbb494cb10a390de587a252ed413d3d1fe6ff0381dd8e5b83a72928c8a309d4fe6985bc7bfc47c0397c5f1dbe276fa2f2b5ff4b24dac5c183104e9264bedc585d4cb8a6f1d058ca38db8ce6e276d68cbd03a55b24bd0f5d2245eb95cd7fc4bcf0aad93978332a153c81bf241fd5a11d38da48bbd066e1cb1474aeaa8c08413c8ccb8a5ff1bcd97b5b4daf0a490ab1173b17d4c28bf2cb7e87f9bb272156230d209961ab214a29b2415dbcb2d9a5f38cb4feb8525bd6b510ddf74b7417f656659966b0d9b666a9e068e85db46721620ba2f4a001204182f961cef46f5396b295ef6f0725dcc37df84e951d1da2c706a31db74f290f1fde9e2a18c31dc6c1ca3fd8cd71ea7da9dbbdcddb6d970c5a861734295122e6558f85afbaa0ced3d0e494ddca948262bb01a15bd7ba69d12db681ba49b30cbdb7d19f28eb2710b7b85237b6b0e9d4bde82f1c80913ca03e9e787d9c605cec4ea6c6783853117b6c7ce70855075dd0ab7eca674514177509eea1601f155d06c868f7e9ac0a2bb4a93744b2c62e552a7df82c7c80e9410c6568f1a106f5926ebb53c43f68cca8287dd6264cde7ba3e28ed5506c217ce7c270e662b2b5907a87fe8cbf965efb3b7b4b5c7729ea531371476482df40841102bfaf61c052b0f0f36752491b2b48b4d352190a2dbc7847ac805d697dac9d4db56be954d37ef10cef6614f290e60e834e594ac79483a807ecc9167e8b4e7fc17e64d6a7197a7ffb60324d2c0f265701d05829a8c5336af89c889c2e384a5052572bc0366cc4b3f92f636866a4a0ba2bb4e41041b08b68d12286dc13911d2ed884c9befd5c8c24c76df134c28b0aefdbe99986f8071a010630bccc416981aba14c7171b16bdf4baa4b93303585488bca03c33f1fe37cec4ed5e7db1cd7835346c12ffd18a84c2146237de06cdcdc199f64778ebcd2534020bf1c675f868d73e0457b31e2ec063f3fb0a85487dd6f5fb2fd7689e767eed5744420be03cd8cc05d25fc961271803032f9c728593fcde6332a54317018b5a471ad5e0677b35288e62d90ee373d126c2e93a29f3d340ac41f68f323ae6e2f80e81b27bcb68aada9e470c4716f437ef848369151d5cf6d460e9a38c27532573379c0dfc3517c428f52d4c94ae18c3bc42254650002d2c6493ba240c163a89265f3d0fe4f44eb282f3933c701949a201f1c217b46b28d8f5f9340f6559debd6f3a3d304a6926664c1c7740d4ebe62c7e155015dcba450bd716f65abdc3fadd02458bd856f5b550c362b9c58c56695bf06b51864573ab2e5ad8b6b8d7e28755e416315ae8b650d7ea1a16d32d68b4e06db55c8b362c9c5bd868f5b6c55f0b342ca25b55475d0368f1440ef9b59d3bde22f31d0369abd85d6897557dec455c4cc6a0f8559e314a7dd11cc36957b5cb6c70c7015b75b281a61ea2d4062f8d92666d82865b3a2cbf715deb6b0cd46144d7a1ffad2adf582b639a0c6ab5f23da3b046c3335cbed52e8d6b32de6aa03d6be437b4d3509f63115927af415d07ff1a85606d26c3d91b16695c36eb171bc86dc4df0d3dbdaa93b17e8d9977d0a5959b8c626fd8908db0ce78dfdc349ff6e9ba2abdbc22ab74781919c3ce111c1ef12dfe652bec7b42cb849dc1fa7340d3d0f77f6d6337f7df82cc9804a31b7468715d4016c709787d4f16e4065c7fdb19eaa279bfa83073765a6739bf1351e54f40e22a2ed8f7640d0d477fa262f489c1ae543fa3dad1213ec7a51a841cd666717e315c756d879077f10f392723212f29cee423df9a0c0bc3faca117b65bf562bac05247f0d95d89e1da5722ffc705194fa79afadfc08b0004faca5e3497d63ae05195f38e2262756700962671bde531c050685e24b445c7a0a995f26d17990d010f188d9b926e1d70e778b0f1e1411ccbe087cbf73c6a8274169ba76a8ed86ea20f28f3d7f27e94f2ec26f5cc5086c13f48d54e9f2bb5f0e7aac40cdc85e5c5522d0d07c8f135a0d67d9ef80b87cb7c8ef882c1dfd379e4b1ca17ff286936d98fb4fcbfd43a428c3b888510f14cef3f8a1228446e62f3ebfbf801a18e91203712601f84ebdd7b4c8d53ee143988c3ba2e7cf07f0de2e122141d06a40c9bdaea3c5d667edada6624e0edd34b520922ba36ff7db796b15cd379c03b7eb0cb5923ea410a9d47b0e2f223fa4fbd6e450354e7f2504f4df5cf4e1ac89b8eb197ca51d44e6c78fe0d066e627ec8ba4549b6f0a72cd04ebbfe59cf1eebb1e294fc75bedce8f02c3c5ce6769e661f1af1366453ffaea09e3e2c3dfb8dcfbb9def19990f05124a4d3bd87cfa9959ca9249122ee17e94c077ba424750a2a93e1fbaeada94959aab5b77616f436a9c6f788db4ad2b58c1a6d0e684530461ea6ec69e43b661f939b7b3cb02792844409aee219971f726fc7e7d87c8991a18a7adac404bb843238f551c28e41c55f69e772ac4eb54eb212708e75e70f176c0addba40d31567ed46f177816bc26d2282a95b7ef80148e8d3b689cc47ce52f679a1435fca2503b85ef17607876b7f92923f06f74b9908c0cc0730b031422c631b3e7a26ad511bbebfe0a908d334737b839333df9933a4b779edcf934dc68fd7432a623579ebe5cd3188f5d1aab6bf1ce093dd198020139c92dba6a4850e0596c1f317978aa487884803257c71fb8e8bbfcb2566fc88572d71082992bff27a380da0e51382d7f438f4070dd56fa239e89f7e4888d5c9ed7685e2290dcdf23aa6a8c02fde2cd94903d347eb7033a77246c998f064b19f922b17855903e69d553d04a0f6713a78de139d6d1ceb4694ce727a5c27ea06a3249a2e2a559a6da78357142b20e0089356cc31a9083ae55380fa68403147bd78ece1123224cc9f5dbfa4b07e09f8bd21a114249896b4df49dae845a4cb3cbecef597936000d8be25cd19e5b88bef4a557c398606e21965771450f4a5aa10597598a6fd4bf570190c308bc353d738c6f7891c4f12ca7b93a81fbc00505211291fffb4fef73b01f66eaac5a1bd1756c0818ea8abdee109d333e504cfa007d240142d0039dc1dfde81437d3e7add96ce88716644aaa3e275acd46d28fd9dc73f345f6b3f66ab40446a8c05e06ddd0845d07bc27f6b7cad87d00408eae632c5022bc42de5f160def1dc433bb5fe55d7b31e80aaa249219f58926c5970b2ab480bb6f8faf43ca4d833d97708dd997e0897c4ffe2efcb8f305b23c2015a9f70502c42bc0e5242403c3c90272dba7f8fd2538604a5e6221148eba4566f9af324ef844bee428366f32b279beabe6d0ab7e9c63c7dfc2e59e9281e8305eb71f0391a8aab77a1fb73db179e58d100ad2b09be65dddae58f344dca000f71200e27b63e0e7f9563481aa3dd2143e80d118a5aca32c02782db66e6439c9cd38c4fba064de9bc0de147522bc701f2e00f04212ef34ed702e017b623e53f9e48fd659da91e073b3ae75d2861cb3b41a77770d3627fb83aa75a2e88907482fd8586f69c8acce86d35a3b515a06c59c3e1e16c5944bf4e1718d6261d46e2fec908b569dbee34e195b6d751e188d3c60822849889cde55ad4c92d74111e0ac2b65a50683789c797e0d99f3b8b9398b744d2cc17c366868104f1b9f9db742325431149ff2546b7a51514829021be68331d8dd47b31d3799476ea3020419016cdb2c37e6a0a96dd19bc7e99a554f2a2ca1227f5db47b7e749526c9d65fee40e3fa2d6c0a6311e014c430a0f3220de787861ea4478eae17993b2ed31ec8fdd1ca7884d399f2e31fc8c9fe12c6c38890fd6b190bfa1341e63dd6f167abe9986a0853e59c434b30c1582a5385524445e2d7beb7176add1b8ff3d1a26b790c03697bc14646b4429f71eb20ae4db9a8e6b2d5d05e33fdb4e71caa9d35f692746242f49b6ccf5d44df5fd1011df591a66e3b9ce474156ac66113d8332499af1a49a1bea188fed2952c70de93d1b7779edbcb4c5e51b5c3816a728bf0308616e637cef76fa6d26ce824c5930dcb0dc1782df32613e8c29dd89461967585c0073f204507682b1cfee09e7cd246e0259b780afbf3fb8e297b2df2898245b5ff37a816a148cb1c185a76798c4209ea1a6cb3bb71fedf267553e622a49767e724aa594ffe57652577ca2036b1429cc9e7a528185d4302b9dd17fb3ef69c66a59620a9ead454d36ba99a88a8b94b1ed22a5677ae3fba95d37a179571f3d4ff693c1de9a1a36bba217ac15f026aca5ec0e28188eaf2cc243f89bb9591c44da6b52e9d773d17112fd1eb4b9df655206a6b87a074a0c55fdf375e319590e62e4969c93b0e254a792ee253a25c9dcff4d506ed53961066aaa304f2b18d2b4f965137a6f668f9e2914cb8b78f8590d852023ef7afb80f030d5659ea1613b2dbecb41277da0def3deba0051b1f5e71c93259b7dedb64d844c61cedd5a8c377739bcf408afc50546cab65551eb3d8615e6f1a500fa5bad569a772507b12a557abbfef016b4e869298811cee56683b7be466088f2050cfbed4cd922dc70c3753a04dc841acbf06d11da42057d57e45f57fc304edf0d22a84a55d982864e8c754e09c40aa1fc9e134d83b965702b8a48bd9075ee7108fe36a68d18034e3f5bf1997d1bf273880807d32fcd6487bac28315d286ed401814f921de50198bc57604ef4674175d442492f921d0630d261ab364cb8057aaae69a6b334aa85d99754146b59264b6cf27be1b87cf894fa7fec72f3d4be3dba0344647c0dcd4fad9a0dae4e20a12947f02c2f33c9ee5110e8a83a04871538980d9dca4b797ecdc32a735a53528123741455be5d05e27968fdfdd2cb8f81e75d2d902be426a9ce9e718a14bf41134e6e93b4e3e0ae32dd9e1bd8cc355dc6c58095a4e90d8b3169fb0ca5cf2c76dd4b0cc5bc19b54665370d63e51191cf0c7863d68ebbc1f22335fbd4247b004a43ff3e5e398e32b2b5b00fe91668af55a94639d340bb836564cc5503e18b7423fbeb9204554b9b41987bbcd82afff1362d666b76baaa3beebd58451579bfe256c66c5291132383fe50a489c041394b6fa2501b98b8d9ad5cfdc79a3a7172817c0be07f91154af6125638cdfa772e5390a4014952440686d30e572d1242c68d4fa8e1e4c381e1c461badaeb023523944d8d49a3599240faa8fa8462137b89c17a6117f2b3a9114421b9a1df747148b22681bbe9617c0a6647584499be813f8474cb31fbcdca8572c269770c1fb721ed75c3be1d0bf36bccc99d533b007ba7f80ea2035f5c5587f28bc08da9185a7e80d62ae65e90440ea2df604b2fd2b747feb45eee25eeedff5ff7228f4823e108e7506d4b0b8a864065686a2fda4f6b056480ad750c41e436abef1444eb1201061109633e14a99f081d0df7ae3ed84a69f8ff8095191e9f35e7a936588e3bb6e518b1c8dd5520ad87c6662381c920a44b1368709cc922e0763574ad0f8a5e106b4ddf2cf90331013318ce0698922771336c3e778b6f92f8298132ed5519b82bc9354544ccc3d8413997b4e641973c296e87ace7f9d26cd58a13461fe902626f4972106d7c1d4b87e6d33ef76f8a840572bd0762a0273f4beb07e74e48e481c7d61f3c6c54e758dab93d8525966583625153c95e472d537b64b9a4f532a830b43043700aaa39c8a7f6e54bb1f3185200f12e9e24e31a827493a4bd3e44849844d0a617b77304829c986729ec4b6c0b0fd58e2703e6fe4d990328d6c30f0ca073a0c8da671939601005ce50221ba2c4a5375e525649a12d86d8d6b67dd4dae83d82f2d4e519410a4b7285480feb5919d995fe3bd95752841aad49190b4d809316bc0b658fc75b9979931c9eae19279e5eb6fdfca417fb9a9f6ea5efe4ef1a370e67c68f2e98a3695be1d099f8731f5aa5f5a14b534368a4c2b4c7b5b6b542709b4becfcafbb8e73e6ce0f7e61edaeed2dd28736ebedf03e8132c3cb8f3c917e0f95cb08c28b3c93fa4e1a468a4e967e8058ed173fff9bc1135503c50e7f6aa0b6a80cf1941a4bb1216db06d8a5d9387c8451f889420a5b9d78485512e496d262e2c170c11a5fb88abfe294daae3615c6234a3b443ce6b7ccf98abe848e7cde138095329f9bf2f591a577b1954dd07d5a278048dc3eb1a1ede406bb26b7b8def94b67fa1e91a49291c11225936b163cb38a1b24b1b2504ae35ed4afd2010d96493f0a2e8c791a596fad4dba977ade6be75e6b32d8592363b0936cd8f9e47c9e3a6addcfa17fce0df7b885d27a64dfd6d8b2ab779470afb259403799c9e50a4e2024d09b92b43ce18776002c5799925932a561ea3512179400436170d10ef6866a5f2cc22b97c81cb495ee77379ab21e4c2c4b5d150fe7c5327dd6bb1ddb9275bd95cbfbd760462a798f00a2787c4366d2942fa8c2a779441dfa3a40387e1b5831698a9b4148380d157dc25f9186455146cd767dbbf4b858576b03dae235e5e9bd54039e34f55a7e26c60b1ce8a51b4de9c0d82fc766a2d032de07011ea0732a90431bac4dd5abed3e11979e3ec6614d12041bdfbcbe16e01f370e9e2c3887385e0ea7f50cbc3cf6e4f2dad01c243cb7bc6267e100b6c01ab62b2f0f0367287fee581ccde8adf52ef81fa2a5eadebbdbc21888550bbbf25b110b3e8f17720570f2421e5ccb58b3c687f050a20eb9f853c9d23bc9e2a2c6a79994de238040d1dc7a0f266158d08a800eacef02346932bc301f712801f068d24a095192310014fb0d1723eff46c802169607eb100908fc48247546758569e9f109b1ef79c5c4a72cce6c4e8c841a88e3b278426c307c76872285f3a058df5271786c874c2afb1b2f8b1fc73bf7d72d1b341c6585f8f9fade1b6e5d6bdf442ae84a7de9d14c82a49bcaa16aa217dd2a1b625812c352cc0ad60a958382702c2f6903eba36872d22582cffec8d4885a1f7ea2a8c21083effbad3cf83d14b75e746c67a69c74fbe61d8c6be7e5455a24f92c671b98117011c1ac04dd9afb756cf440606a1ac349509f3bdc171f51fd526b06fa21d41a74176fc92029899cbdd311876cb419644facd1d5765fe24e06926bf1c2a3c5549383c287d49bb9340eafd09642634eef18504e04ddf0ccf62b06bfc0455f1c52e2978cbc105507d43c5d391fd6e60e243005886e9082a488c80f09564067f9329d546ce0b47435484e53bfb8a3010bdd463c8d4901bee7d2d21222f618e5714580eacbffe81fbef2739c0713a880e8ba7111650da432a158a6ce78f77644c1ecba2cf34915f50ce62d35dbc9103e0aede0442700e43935d1732f89e9816ddf920a10589d0502ff690d3e25663d2e84f49ed758909865c01fc44c568d0b8bba6e1f9bf59e56a146f1dcdf4e4d458e841d5836a6a57c2ae2e04f2f195093c09372dadf73ec1ef940daa4603f770d4ac65338e0f90cd03d758184879c4d17767de5108b0d4f22db99048f10f46f0d321284058a158a716c077093ca5215b380644d1d922e1145a7dddf716015dfd7499a381be17f4ab2ab9fae43d5db30973aaafcc5223883940d0d3b0a7833903c90904293c4b7b88c7e6a8fd734f5428f0a29c4648c418a6e0357f2ee9c883daa3135e28eca0a75e06b18b51f8081c452114027f1b64006c59047e96c200505405f048f82d108130e593f0190a0241d0178123e0b64200c7a9f2fb40a87ccf1755ebcbb25c509d595394aaae116e3932d53b538c88877f023f9863bf09f61898f179bc670cc5efec43c1f319c1ec5d8d737aac1a2286c1787242140145936f5397db79dc31f8626dfedff3101993725363c3246bbb538d2c454bd0a25cdb69cffb77f6130391dc204e6d7f6fd2bd7aaf37f649bcb48f6ebfb5533becf4bef2e5e4530a49a088f88fb1581d153fb0200621229d9f3b6f87b6d007c589f3e4b8241b5549fdeca443fbf6fdb5f3ca34f45abf8368f47e269ce28e1091d392f91a755e647abcedaf931fc270206837028b605727b0649f35f92ff79edfef7f31971628892ded4412ffe68702577d8c0a008cca2b21dbbdf19ab264a423fe5105c80880bfefb1b04635a45ceed05040a342edb74ab7298c817ec3a69fce4c638ef407b43a4c022839aa1542ef34c57aaa6f86ed755e9199adc08ec0cca900b9f9055148b23d931eb70a9594e72389e1e639be4641befb49b6283b53a6fa2b48a27bbc90dce3cb532319a6fbfbb9c132e1ed0e52ebd1d627472098d895c0fa10cebf8d7f3a3911e0e5def01884ceed6d2bd854ab07808e83942d3b37b0feeaef0f36f0a87abde0620696634cbc1466917b0d2ed67e70bfe1e5e0512cfcb27c80e8d7537cda2a9d93649fa61e2fc705df0b6ad3f520bd8faa12140f6b52398b8b0c503f11a6eda3977e60cbcbb8fa55b389668d5f6e258d8123608a591d9cd52dc24a7f7f8debf662dfe958de04526bd4a9c8c37b8c44b1b7ca7bbfff6357afdf6edf047f13988dbc4cb4641bb9003658e1c763c8fd206b96821d1281d2fee0be074c3b4b56a48850e09a8eb9c165631d805d85f6d238b847a676428dc9b04b3b742021d7dd890972be0a32abbc913558a4f417fbfcfb396dd189dde5623fecf9af4dda686518cbd2db55081fe335ad8081db398c83421cdcf230ffd96721e7d8d921092d617cac5a8bf4dc3380c0d83ae6f739318fe3936f4f021b45bf1a112c9167a48a04079b8422ab4e70bece405eed0cc0626fc4bb1e5a1cfedd31c74abfc41b64ec5b6a9419a002fd88959e72a605ed59206ae936b79ec0a9743f981e128e05f709a83521d048c3fc2fd7f9607042476c06ef5fabc2332c583f68de3c4bd884da90996ff53012dbd7e66ed4ae4ca24c7e5461b4221fc0e77752678ee914ec852658452bac7c7320bcd5c11f32b09bdcfa49f714cacb9a2076eaed96c46b1b95806c65e154516a08832d769fea12ed0504340d1f8799d77757b00f3a568fc137fd06346bde0d06ae1f50856b6c0d79c1ae930ae85c9b9ce4d3d83f6bed66187a6a6703346b27bfd6bbcb0daa3801ea1cec5af7b85a76c7ba94a3752ff3d6083e5f4ced826068453092c869d74847ed76ff96f6b8258b2f752d1041431af1d6847f51ad9911ced22d2c5ba38aa970f63c7bc28aa647caacf1c0db3a612a35d5fa9b74efc917b9cd8d727ecda576f0fcd0f92fe45e0be1fbcdeebf68f460d09511647b6448a2a791fe7dd2a392defcb3d7e461f68b0b42ad999d645549e2411ffba6f120fbac7af9c24e72d818e96a8884a75b72eff36d0e081ae6b030de0c3037d1fa22e8a99b29616cc7924b0319346d756f3ce9eb78f62d9fb0a6319845546cff6f395d16e19e0f370241eabb1cc453170fdeafbe019f0130e28814890900510e15cc9d3bf124b28bd4eb0018a8aad49acc320cf08d58f7d32f486ca56ffaa2c83b4b394cd1d5d9e39cb303ea2de75b2a3f5c7e3261a345f2a3919d2ebb0e1a873e9a76d568bcf8eee7649b5973acf71bfea9e1be04c6e36f387bdbcfb65e5efe2d1a73c70e79c9569724cc3fcbd7ced46ca039b2140a46307f86a172b558eac56da7922d72378db41c14ba8b0d02f70c436c623a01828d724342d000c5d6dc9e70f40013035e4d1c9a5559ef39bea42c5cdc001c6e735390bb9370ec05e99fd364b6b5068be035e66edbaa08b38db961fa35dbd2ab754f1f8dae74c8d07cd36520447eb6d52e78981a22dde389761af78e72d7bfa7ede68cc5cd4f175b5b6817119e9db4c71f2d4298bcd5223f37d85d72e5dedd8466ce5c0679e85ec930bc8da509be3654062107f0dbd5789fed93a1e2ff2398ad48317786135b7b10d3b8081bf62b3bf4e26f338bc7a38b8727e083d4ab3839bf8427135efd0d82895bbc9f9e5397b117beabcbdd403fc0c9f13006e2a699819352b73a9627ece9df07df6b4e93e8f8a3bb9ed5baff4915f9987dacf7a409549f77ba0f8e58b5354921994b0bf061a32ed41e3dfcf4d534caac4356eccae07c98a338af9245ae7e1addd5dd6c0c534431875f09cb334c344966eb19066d6b150b8198f40a6d66afe827fcf8cef3f3c3b2f3a31c992e11fc1fab3f4d802e4071ff7f1dda64cf41867347a4d25d98fd5557a34bfd07b77efd1796d92ffbf28bcdfbd3b8ebb38ae393879a8c44d60b8de1095c7462fdf67077bacc80117e78ad2c4cc86081f24fcfd1238d23d1d018e194e50f287ed4bcbc51aa836cff51729b9a019fba6ebdf979dfb7656ac33a5ebf12f9524af9d68ee00f30a1500ab688edcf2e495afaf7504a8e340e5bd8f9de33878933691f04c72feced32bbe92e301d7550b2217708cffbbe632094beb5a9bf23cfdbf5aff71d9d8556ecfefbd326ded2d331e1ad67f25f966e604c89445dd307500817c9c5f9d5fab86d44fa7ed2737c43d3782e759da40ad9e8418cbcfe50cf36ce4de3e479a4ac5e23c690b73b69f0d27c5983ba79f37d4d97ac3dcca5ed0dfcf65f11b6a7862decce17ed67689dda38a4df71563a9329991b1db0887b720a30f28a74ece4e1a96ccac07988f462854e67f9380c242ef09a8efe95a6be4c02706f8a767765b67a7bad4e8bb479482e163264db6521dbaf70ed6bff9820e3c1f850a06e9b37b2eb3ef95956ca3677c0c84ff827c61cd492197eb3d2e0f8910ffc73639e9fcc2978aacdbf453d307f5788445cd48e339a0bcd69e4693bbb078598b3370fc9a99ebcf8217dcbaf1e6df993c58d2450c4747bc637db8d7d6fda1a3beccc7b28ebc28e5638da8f94a997f6aae02aee6953dac94fe83a89800394ca57f00c68c63dadfc0476428cd7ae79ae796f362a21dacbeafe232d827ea9b47b8d88cb18f9ce661148c2dcb03e7aa80730bbf4b12c9b38e77db696593c1b9916ef633bd1bf164a9e70353d5d73bf6d4ba405b24700917aaa4076f11d10e9ef21268077620b0c9fd4075feb4d4c298bd85913b18d854e909371b241a2b4cb7b6bade9fb7fbc8dcb67f631955134197d30f13b7a8fae12b7307426f55ffe6c477d4bf90aa352da52402608ad4e91f14d414b0fda2cb9567643c8f6994a6a0d1704e7bbb38e9007cb4c52748e7b575960bd572b1cd338a900d8dc2f4b95a2029c99579767e666e17b98f2ae3e438f99c46a420404039b0d0cc2f151fd5359a1ce6290169a330b9f5c73b7fc705ec9831fe2b949869b74ec5e83adc3101a7a6886a72fe89a748eee48d4f270dd00f1e982fbe3da37c00062b8bccfef710dc6a81764d66f5d05906166d02da05b041e7fd4e890bd5fb63d717f5e03f6f8271f8e32fa46e753c073c878a0fc3d2041f4b1233e3f077e6e22b99265becd40641939794df96b46c6c78810fc45949b6054245f53ca2f71e2dc14f97b32007710852f07930f0b2cd3403c85cfb83e58e073719f05728cedd3bb2b3d194f96d3f0bdaac5fc67f3d782be3c84c7fba177c6ca61f307addeaf3931510fc45d31b9504ecb6baa7c2dcfe637eebd6d6203863f3fe95d91a5010dae7950ff0b6c14675dd77f8e2bd9b37f727af36be013e00dfcec11044c80db44288f783507e69878ce26e0a9c6e29ad5c8e0a181ee9ed023824f12035680596bb80758461dd835597f05605ef5756d2edfb8f1ede05351ffbc2752e1f3b016ad321a362abcd28e251fcbb663d246d1402f1e10990faf604733ddf1ecae3a66649f9dd3a1b9ffbd132d4bb1af11ccc174106ba9df3dff558c03489ec2446776e2e52190492e3677fbc865a4dec930d5a14d185ab7bc0284581a4b32fae82e1b384cce3317d7e8409fcc7201be49c10488e2288694d029070641f57a0f8142b559a5622366c4f91f8f41f315b905711d18a3fb5b310107d5d000dcd57a641e325a7aaa283301becbaee001fd693f2626aed0ddd517d39048fc4abb656841ed2e669be012e160271bb44479cd8dfc83ab08427e845060c849ad94db01bd5156a8de42c76f236cf1920e56b8f7798c61d1fef9a4588147b4dad0d3ddff7afc5bb02f4157ca808cfff53b0dd86a760db56cac048ee18e3b6d0887aec975dcf9c1aed39ba265ed82ae74101403e46784fc96f9a7af9444a96897f7a73e2d8718abfd497ec01215e841d0055022443f0d9ad14a05e097e12380a508e89c760496aa059c03ef8104419aa3f0292f801b06e1fe6c81832d45054316c108901d5428463b35885f8a87009726a1b3da1078ea367016ac071a0c6b8ec45154b369f48538087e65089ea88a4147eed153813ae0dca8722812429f1ac56f4503e04bf3d041650468761b3a8b56ab3bec12c0960d73d3631b9cb6c664d30b1b886b0d58535a0d22d42a2890e4319a7e519017c1c320f662000729470184aac04f835c000b8bd09f0518a0141500be123e0562210c7c927e0640a012f420f00ab85408c571e0097a9900c0cba003402be2d02329c68025ea6107c2780a84746a541044e419b9839fe63a538285e0e1ce0686965d0606c5e0fa05f9317632c0ec2a7f75871c9b7ff2569e640323df9a8af16a107e6382da6bfc5e59533c6f14cf03a726b0ff07358e2aa814a3fc686a78aef0fcaed790115fa9eb3264d024e82abd80d0f7a74acc55c46a8cdb3c593cf95fd08a08b6a659bf02883543418f71d2607bae5fed5207952f5efb13ee73cf597cb8c58306bb16d4c7f59d25c23ced72a6bd04b0ed87e2534233e2aaacdb04e9c0c77ff20235d952b1d9e69c8c7abbcad49ee595ead1070fbc4a5938bc1bb1b4ed431185dcd7b92dfb8d04d4fd0ebfc0aff2f17faee4309abdd8b1e6f2a995208112a5ba83d4fda2cd1491bc9e199bdca4223522bb30dbe198844c06cac52414a4b7f46c8bb4e74c9d4fc90483b53c348ffcf9ed89ff800ac23b2957a711031638dd9b21751184fb3b7c3924499647a200cf88888c0ecda7d14584a7eb5dbc02d64bcfc80eaa3d826c087a67e6c4885939f74f23cd37808172a3ae9b79134c040da756d6da5372617d339c8b5fcece04e71cd32cf605d7cb84fd98a3f5e10c6c844207c88f2ed27e838d0d243af92d9e7aae63909863d5493ce0e0ef45560a0eb9ae0243cbc74da16d171fdd1bee46a23f07c300cb726c60dfc23e271b623f185f69049fcd4921e50defe37c4ca777bccc8174fe80797015468743fa65c57f7937ed54214694d69028287e7c7552e976a23bd5c7c46f7f52d1b3473333e136048abd60f26d47fb65d7ed16814a7eeec98e44274b5229637ab96295fe0a579da178f26631d94cb012e0e1fa37facc309a51be243292a89d42412680d2329ec4b901747be18fbbb520f9a4f6e6c181881ddc5bd3f773658c9a160475b9b520a8cc5fc6c470c55e364bfe6263a07d44c8a7ed0a045456992116ba411c0caca952377d9b58b27d6758c597cc89ee5835f0848af387dbc8de13765e2dadce1fc03fec394a6d5b747e8040e08010ecb31ca4db31c7d985e7d054823549a65cee90d3d9afd64ff61b97eb7c079fc453a55c4d97379bc2bea3b92c6496a265b012a3d3f35ab0fb09ba21d3422896065612374eaa805ccc417c6c2f8673b9c33c8b5717b042d9e0db14b067ca06c4ad2f42c00ca7ee9faf87c7b6ec446f8243a85f218f4fbf77410c8d741d3b0463046684fb8e1af3991fa593d1693ac60d1ad5ab4e6b0982a20abb6796f9acd62ddc323d76d24f03bd37cec28fbda6430494361afd74eb3dac0c9bb227509b51b0cd5e4a4303844c641e091976e1bdf707114ecfcf919b0688038a73a1cfa39c050a4a4fe8c1403fefde5d778714353f6dc412f727c54a2c4a9180944588883af17b55f6644e906f7c5de55da56464604c647175a2e28a290ce1b284090410b8f990650c1da1771adf39d831f2cede93121b745fcb95b8a20d428103ff73d7578cd646f65c869b86b44f0b2be734b387fcb464ba2c3fc7b59f8cb0eff2c6e92e05f6cf5dcb173df2c5084ee3ca5606abf07222b057178d722d3fbe2e4f20e277b6f9fb3b4de253680b48adb05a8bde23242d1270b66989e17f365d2d3cbf7c22a1b11b52d023f1ea4119fb2aeefebe5bf7b7d949e8abd0e57048dee8979fa8f9a923689f5399e2691e91dfda84737a3957358218f174566e5a07857b28656c36f624bf23d917ecdb60b68fa859d0b0fcaf511b0deb591e137131542ccb16436dbbf1a3aacd7008e7237e8d58f9d026932331668169d0ddf7ab3a9c8d73c1fd305e7513bc7f12f2182f9b58bfdc36a56c6a6679f3ad8875b493b2c5bec3aa380788a6d35c4ce754139e74f2627efcba8b838ac86085f3ef65b12df9145656a71bef88ecca6ee97a02b79e2691e0e9036be9d6a3b10441186b426bd7338ce79e806733087a7b8f37e22bfb3247ba09399f6d696c85bea41b139e723c22f918fab56ac96075961f4b787ac1d0a70bee3736d882b95246ed29075db870b758b8e07e7e87e6c4852e3d9b45ead8383c59a08ac2851b403557dbc3b2237556e211e8f50affd7bd30c6203da89b9f022e4b267c037bbd31ca665aed9eb1ac7dc62769928695cf1556b17d16ed57ecf342bd483fff7f4e80db65973fda918844498c6fd469154941a01bb734d950e6b590129eb34626b0e3b2f6079ab3c41d6ce99f330d782e76499e23e6fdc18535eb0889759789ddd6b3a7af0a74a96a5fb37bd9a3c6c040ab2441f66a4d0ed1f14d1dedfc59af3f68d8fc3c98596a859033d7bd3c167e4313fb2d12dfbcc7067cd2308f02e73bbed98e412059b129f5a73fc6f8785c1708654789d09d28b46c747021d65f592833b9a7768bf3e2df0a397128f9f090b3011f5b5eedf548c04acca020192319d4c3f77d95bfd5f82ef1b2f96b37471c2fa0dc4a5a7c1cfa211e378e223b495d0f9cab91d85a2614bd1d6614dee8a37d496ab68538f788d73d28c9cc3cb1eed4a5b76b0f335c9fca8e68f37ea8d85783e7a935248f05bea224f1522797347a3ce77445a3434d5dde8e119f4fda400cbb37d91e6fd6bdbb6cbd441afaa21c1149b5fd04fedc83bcd6caf7b9421462761a29187c126ec80c68bf04de83339e9ab521fa28750d5bd8add669ba83d2fdec9a1e0cd0823f263e7fa25a3ae301731c4aa98bdea0d95bd4eae0e0630b73f315007b71ef4ef344d7ad62ff171abaaef55dfc82abf1d53f136cb859108cfab933036d3372c1e5b90d022737efe30c27f2e1c833fa655ee2ad5fa0cbfcf895e60dab807e3fcd3861b0467e3fb6f007658af778aa47a66a5b18ef77897989756c0c99461874aea86bce804951f47f86cc8e33da5f59f5565806f18066aff4ceeeccadb52e530849838132516863e7c44c6138f4f428cefd85952202805b7df9cff2fc7a9d502ffa304763b8ff578efc5ab580dc98b4b99fe5f090ec1c58983e340075253120916f10e6e496142b1f4d6627434094f714a40fcf3f8524de0ad21584b982bc3d74b3982e3c0647d59b471906934a7820f8e65a48214948b4b3f818b5af0b451b610cfac76ca0c2d301aa7eb92000a894793a057eb8020883764718075e0e2761cb025eb0b91f5387eb73c44c800038c182604cc096949dd392206804592ece1bf5ce6f195d83aeff758142cb7a8d19bccc43b4366ab5834638b1db7c2ba783db2722bc811b1ca4c61d0b14deb70fc48d8ab896aa50a18581f37c38dd83254d6510694daa145052c84b78a53020b2117168deaf9663b72d18e6adc5306662f1005a338e0a04020598bdf848894e1687f356fad082532d63e4fda3f5c796c836a1f2441b498767a52cae94b92207349714b4a7d3693e8b6f0bf7fab665db7ddb0fdab158c4141a7781c8a4c672f57838789925b812de7aee0f053ca88232ad582e98af92bac36f3943110cf07f96c288d3df98d8c515a29777ecc10c7aa2b976b2490df83365a5ddbf1994991e9ce856958325024411c9f7f78d02b50bfd9e73038af47bb1480d10c2729cea20cf4df1979f3535f3ba04f0b63c352d62230ee4e14c7032078026815295d3f8014b0bd366f2bbf5f91101fb2388f7217b072e05b41f270739d5bef78bb8a304265a9f32b153feb9dbf16be6046a15c917445ee7eb310b12b2871e156e0fc5d0c9c26b6e571caf9618b819e42c33a82b583fbb44c150ff333737b1439479f12b51352983688060b7c29cc260962432318a955ef92b2e811b2e89f6e75f20e1708b2022414f8aa5995ea3a15b13c25c6d4fe327aa2654b2798670ea8752c07fb5a47c6592fc2bf5a9a52487afcf7a2051d3bd53ff175527a0015553335aca6c03c55c177d9f94c88dbcfd3efee47bee84d36bb7c282354c7c0f84716084b9e754c3ebee02f4d027df4b08131311d05504f5c426d961a24e72e3b8bc0d717f6c3dd9a3991bfa53f4f2c448a18701cef19701eef285d252a85edbdb9b502b196ea77c5a95567de216097a34ceae75aef3c1360a84a8244381cdf0edfa73b0fc76958cbc2394c54c11417bf479dd86577dd4a8cf4bb52848bb9997263cca7bc9dd599ccee7d0aec1adfad55aae38cd38c5eb40dec8ae2518f48851129d3d7e38bd9d767b43158c59e3bdceec2fb327857ca6780227ff3937d393272989a76592999db2ac03af9261a0ae45b6627872a0e528b6ce6921c0e974678d446887821ae38a8798bcba3706348f6f0ab780b6c77f50f2dfb0ffdd2874a30b7a3ea6cfe38b75105bb34a7fbf39b482e9fb1eeeb700bd1a9316f6acdcd3ebaf6349460924e130856920974aa020d9c6295f11a6b1ed8dedfade8653233aebe4e2c0399aeef49169b6da154cc2471694a613ef60bf7cfbfd636dd1da0b5bb05b259715543710af6126a5242a6f7d4d7e324887a08578eaea5c8938cab7c5e3e4317122e7d08aade36614c01a1f5d36a54ba51abb90a433add9247384874e7d2e001d86c6600b85b2343b0f690e310b825873f91141921d9ca266afe4c63a244023150a57accce96947b3bcd3ac39125d4ee472e75bc831becc3c7c651aba627976598f72bb75834ab83ec878012c8355abe7ad876a2ef96c630fad84c96f710c1ae2f59613d3c52d38f054667c0d2a9259ba576ca828c9fd632178973386afb6e845c8a2e1e3194de0af1a16a4b04349c9d154a4ca39a3f8d8c4f138249e41d77179a99da09f9e4a73b64041ac16073472fdfa89d1ce7c15946595f0db3f9972484f290a9a91fdffbc3c114eeea4106dd91b89bb5f5f99d01323385ed83d1b5693733a5140b06d832977036e2d03721c7002425845bdff156c3f519d064e1df2873fe72a8375a50c6d5bf290249fa7a4dea6b840f0a6fd5a8536ab3238eea474bf74ced4771158a8c7ba1f32401cb8481bb0fe5d259a16abffea8f8ff1d15b0542122741c32088b7a705e96178daf28379ee9bc3df3ebf3e624962493f6750cf6e82009636c24f15cfdaa8620dbd945875664ce01688cbf318c2500003e9246495a6cbe91a0e0a41399b4d9d6c26509ace35442075ccf188d29dae494da195ae388e3a6c8bb29a1dba574a1130845468714d97f91b879ee28d2768960740bd49d43c94b0c002e93a52e3d07e0a447b26193ebffe2a10ebe0cd518d0221a2b118b3f6c170b0f185780880d7a2f324ae4e207539dbeb167b6b840281de93191ddc5d9afd9ea50838d8c3c64671047c4fad289d5b887889ab42e50731d16cdc1e630742bb263bffa6a56cd445766b26d1f7640f296a4bbac8404307cca0176f0a51f0d8983a0a7560f38095ae39b4f588d0de5ab01f419454c424064854ca8845cd36464300e277180bc74933ff8b15e0d4d47344e9dac182d686a1b4390bbb68b10ad57394f83cd480c4c50510e3c9038f56bd4d061e82b60681ee5fcf9a1c722a4e39c7c01d7ace184b9f2c38c8bfd5bd99682ffe893650c6c0f83178d0064a1bc2ee449e3ef2e71490b473eb80ec1efb95d5c3ccbf35fc755932528351aa6c50b38c9ae5eb58e8fa009b47be9279b5c3fd1bad68b95960841197d52ae062e0c4c865701f26f59ffb419571fc3c056cc86dc3607e4caf2406fd5154c064118a3482dca517d7dc6f6aca2acbeca999a3e5ad0df9cde74b1e9e728791ac3180d9fbe230c5ce73ebc309d457532e1d519a20b491f3c8282cfe2ca6765117c4c92de8c08ff9fd11abd2e5e1b186dcec0372c8ef3d1542a7cfc6a5344b15a56d13b06fbbcf2a8f9d416c98ddfe04d96a5d9f90d8c8e9e575d51b772302c41b3b16b9bc2ee9f198863f8f5ba4cd7603135dbc961cebfb34b473ed4a367666adf2b07e44ab1fcb10d4217441fbabea1eb428bc7045f6f1ea9e8aff9db94971de26ff960e4226aa964e21b8804d5536cdb05ba46acf8689183624aa0ef0465c2b963d1ee809379cbfd6a98831af3dd9d6629e3de56a084277f132b67c17717f385cda1566f9dd9a1daa4def780d35d5827dc0f6ac2639ab78f7f08a480cf18bef0e69e34ffe825884ea2a015fa5c5ad34d1f623f0822e9a15450cc367dd8078bd75b71002969a06734dbc47bf613a4c744006e8aa37e01eae1070041b5a119fc7e0d822e04660b21a16af8c077c59ec97d98f75350bcdbeaa548c3006da4ef08918ab26f16e1f8bb5bb88638a899514e0009573196ffbf510dd0981f02c61c569aafba7f025602aaaf5cbe2c517cef47a219feb00eb44119ba8b2df1109b655f1eeba3c4e84b80acbb2c7b5df040c303f311758e22928fa2ade189f1eb232d4940ac1361d17645ddee7fd893a1095d866e0d461201c08d3562b32ec556b2bbc2fb01db08c671fd1ff9a64d33d082889a0ad53af4d7ac0f02e01fc703a1baa7145e2e9123f903c5a185fe094233cf3bf18521960b11932b412bde418e556843bab311212ae173b6f0c1e11e2192d86b89ebe484df2f7c56215151f96afee4eef2eb1f225af644dc578821727cb4f28e19046c0a0d1be5c1bfa490fccb8ae8478d2bc424df6d34d6dc9ed8387d41702720df1c382c0d44bc3fd423cd4be54281e88a6da4192870c43c1f2eb7fa711fa4442a66543ac4f81e90483fc3b1bc5351c11557552732ebb7ef0f4022ac4d17ad720b3975029daf394684242fe4a03e94421608fecd0e51a0ecaa5d6e82e08485352503305ac470adc8bc90cae9820008abd270a8368871721f487a6c518603b7a5adf1d1111b2fb34196d35286003d19dd652f979e137d79ac4932d541ab2a0fc05855515bf0a132f6d5b1a3d2a21bdba1e6732c65f9d5304853839942e90184c5993db5c4a4a22a86cd323a89becb26a4a6de39b09ee77b798957db5a105c1212514d3470fe412d64979222d99c49e6114b1b7bbcf2a2531f5cded156292d56923e7e673d475040bdfc54c4b6b95bbeff75c686dc8f57d2cb7d496686e896635a38627e83c8de3abc2787f0b4e801ffbfbcf33628e816baed975bf2c60987ea3c6babc4c723c0f9c8cb91514deee9e6947246f573e4c7d773901e0ef79f91796abd65e0d5e732a3dff06a842fb4d2c9d8b8a7629190936c3fcf779fcf36f1451d55ca24194ce40fb5472cfb10cd51c69dd9a17dbb6ea4e1ed34f668e20ddb50a1142bfe01bba81df274a3855aaac96101f9ca0ddff95549483c6ff9abf6960f94b206c1caca4d548acd3f97588806bbf432f73147ea954bbd07f457292bca089de8040a0e69277e07e69a1cc9af124c1ec0318372cb8045ec7a52953c346eb8728acb1e2117f7c85401f322834281cfb982f24af2431b447f5f27ee579185f6e7c6e34579bb27d13f53b894f9fd9933ed8d1318bb06ccda1221af2236f9ef573e7ad68d6309f988a028ff863c275386c425812a24e18a80b07df91b9a316228a92e2da39a3e9f48213cadf61dc25647a16a2141a996a69d755cc458c321aba0c748f666112400d473d4c11617e70765e82832b0869b98528a33ef8ace312d96f3f4d25be8f2c1c66d07dbebfede4169174be0153cbfd9568eaac4f9ba065095786a87e5004f07fc71c085e2f5740698113dcef8f07738ebfed0dd736530d3931dccadcb8b772f68be37aac959a3ce9dff6180941f5ce55665c13324a037185720764f3737eb0030294b91ce519c05f3467e76f8164cb9dbd287e034b6b012f93816d221ad8f9cd68a03c2e3a8ca14bd40d79bdb1842e52e7cd094c7f329efe79e02981def22f01d1117b0650b0e04a2be79ff942c6bc751309fe40f7b7fbade6585f772c617e3c890c2ec07f980b16027a68b46c926bc8f205613c4f1acd35dc9158a4a6b136f745cda0901ea9624cfec022b8f034c55ce20a3b1a3badaefdd5e2602731221807a2ef05c68dfca3c88caeaeced8f973ea59828fc9b50b997d27caab83936ffac0de02e87b25b9e1133f12a90341c7df3917d21fa7ad18eb89f7b300c75f10fd712e6fb217bf36d3be4d8cab614ea644c026ec29e7f087a1e93e19bfd371fc035c519289a1004ba914541b5ff63d786680f7da09139a6397226c705bc6279239afba6c7f53a6416556abe8680eefddefe3795158054039e9c1095d23da86022bc49b07d2abfd97a94574d6626026768b21148068095d7669e8031e242a81582f8597aaf31325312bc2b8c975020743d0e22c9c47846e3c42d65d4bc26580d1ce392f629568d11d74456aa8609ddb3af0d9f72be147300a636c97b0aff027282e449361fb3c3d5f8f26978d09eaddcfd37327684eedda2970b8bc13608a547a473fefa09a2b242cfe106b75ce3c64e6c34cf1c6ca183c19e4fb92e146f50496a4764c443b8e7b2365b7a912454b50b02015d7244a0f66c5bbaa9a50377524a0b11e4a349f3720c2f4ad2e64a749d66aa9ba0eda7194e4bcd391ebc9977855fe2c9cdf41bc630180861a18f308727dcbd0d39b976d37a1dfdf904f77a6a40ed06dcd94738374389027d9018b1943c5b713325dc8b5de53e34db7cf819a55ea2fa52b11b05b2f06f31084fc2a47a1f72d6098df6e66181ceb8d40135120f055587a63e793dc731c3439b83d9ad34d6c234c11b5e368983e17679ed558a5ea7a3ad33d5b9ebe300778ef8b8f578f6e30d7363c82cd9f03e1d780087c4c182d72e0319acec822f2de2a32f24733f57413f6d2e693c7de3ded77e48fed03176d03e7629c68662b9410a68580f98cd4c0ade2e5d113f8d72379e78ac6038525b81ef549b934f85e4714425e6990805ea4f011f7e62088d86b11de93ae990135dfda335ba4d6899d2cecc2c7cd8497f5226b3c59af0264c537de2ee237fdaa594788747be37d35c4755475ddb9647ceb63b2066cd4ffd3500d1c8e023e5a04220b3760b46439330f0f0f0f0f0f0f0f6f4337426a6b9f908494a454aa976679010b644a29c99492d81dbc0b9c99f87466e2d31546ea6e34e301030b400bd30a89305b754ea153cc4f17311a1a68000d2d6854c0c3c3c3c38bbc0c801a6220c214d28388d6a39e7fa625c4388441e5edce5e2284ea63058d011b39ba7880214c3a9430d9298b440b63214c21c78b914df249fe104218b465c813f91f15620cc21871e5d6434b9e902208e3440b19a91e545479a2102310264f59952647c8881a157284188030a5e777f438191b6b016084187f30968e74f988efb9a75363edc3c647c12d4b31fc6076b7083243e465059386161e1e5588d1077357fef6f47137b6f3c1246eb35ebf9237ddefa26b68c06fd898c0923d186de28afa0e1d3d18e5524823aa3a7f3a711ecc5f174b955267a972120f8697a4b4aca5548086062440e3430b1ced12c891000b2851438c3b986ad27c74ad38f91b362002e50a31ec60be0c7df524667bb5af83792dced6c4fa09da6e3a986e5baf64752ef5e139183ce54ccb19a24a29530e26a13e57b8a89e9519d6588b83f14a456cb56ec2292538184ef6d8c9d0918488a637e8337975ebd52f23c70d068ba6bba5847e25f3b4c1782a629b087a7ad5c26c3089f59c82129d3c8851d7b09af89d69d3fbfb5d0dc6d1f62762470e252d4d83316757528ea8f360123418f542e3924e6a44fcc81990e79e7592d8f0dd88194c9fafebc329b7951896c134e23e23f4d998fa6430bd560e39a29ab0a4d9180cf2246987284292490cc69ff9d4ad94264cbe6aac1962848131b7d9ae20536c3430587a3cfe09791771fc4272a23de2880929cfc70bac96e87069d927e98241e9d349674cdc0575e242a272ac3d2f919152bd05decf52e447e9f4c1d58229e72454dae4f24ad5c982c94212972fe88d05632491b3d1174a4551b942fa4b5e9f1a1d4f3c620593aafcdb1dea296787ab7067cb13dbd46a6255a86052e6de267ddd4444740c31a660c48e14d192429784670d0f69267f4761d7125365924b5ff250307b2425df15c2ab3e9e438c2798cebc4dc6e52427182d8f7c87f2fc155d9b9078710fa1cc4c2618c4b6044942924b583ca565c7d2217fca9460b6317defb0d57f214930f68dca496adb87f00e09c6f8ddf3aaba54b61ec1d319b135ce74881ac1b8af27c1be6e2c9f72885104e34a34a14f45869e1e11c1f41971d72dd4ef538660505921e5e715e517662198aeb346eaa80ad2471b0483e855f79c4a6dafac4030999950b9206db573f20786f7efec4e7af744f6d00d317c9068a6965ddd0373b6c41c939397fb82789007af3e53a24f9c24edc024277c56ba206f92b6bbd8a26f241d982aabd675f47091d29203c385e7eb087bed220eec0992b642527cd9788353fc38ba446f820ef2183630ba6e8b6e0fdb298aceb521460d887f49f4d7280f950662d0c02ce6e13cae598ae4a1b121c60c8c5e3aa5a81246eeb8be8618323069cefde76f856889c5ba9f53d8cfb8d2aec1c254276ef73aebe597e91546fdb4edca31b57923572073ca9369bf4a2b0ce7a3d4bdc8c6fdafac30dba7baec905ff2bd5d85c1e444e5e6fcc83cb9c69aab1960a8a2ce761792852429684f2accf196653997fa4d1f15261d523855d7317e3a790a5418cd4ea2744c5863ad057e820fbe1b3672743185b13b6591f11c23966534b4a0d1821c5ea0c07451011a5ab4e0867f71011a5ad0f02fb8680ed0d082c69db5c02340430b1a76c39d8bf3850d2df3a251f05186c3bd68418e2c85414f7a0f8b2426297b5218d56cd4a8f4f1f6b98fc26c71d2cf88c5eed35714c6d1e965fdc36fe49ba130cfbc8f288f7b8b2c0185617f3b4be7fe2cefdf270c2e4946d0155a3ea25d066078c2a04694e5ed59d7fabf138614f22c56a8958afbd4d80d745181b3829c30870d911e72e6dfb29cb809e39c271d99a142de4713c60a7af1223444ae8533611049c5fbe69e4bae0f26cc79c5de7b74ec10a2a55cc214b45c4d9f92aa22644b98d46595136182ead866f2004625d491cfcfbb1682caa2008312e6a0ad2797ee5b93e1f8c8f3008c49186553c4055d59e46449560086244c1529ec8e55d808ebdcb8617608302261aaca9ef8b521b2eef187106040c21ce36e73278b47109700c6234c6d415f0e42bba9578405301c61d8b7aa2ecfb7b0145463edc373d8e8e24e05301a81d0191e3e6444790306238c713f62d1e43efdeb35d63e10522f602cc2544ae26e9b84a4425a6aacdd5084a954764cf86c3fe67101231126b566592e665c46b4cb4c000311066d912da851fe41a4d3210c7f61db43caa5b9681bc29ce467e498a51121a51026e5392e9f449810061d134a790ae141982508f5385721447a2c087329ebef2f5526e60f8439ce6ea464656af2640161b69446be8d8a21e4db3f987d62593c6bedec52fac164f7df69fb631f4c29ee6e65adff50b3f1c16049eb6666df8331458ed029b2249f5735d672b4e0468eb5ced10bf0e223c905400c30f460bcb4f1faa542ce53aac6da471d0d0d34600b30f2600a6982a4b82e5539b45c808107f3e81e93b318dec1a4646858588a4e96658db5b230ec608e26579b3ee26bfe5263ed830d07a30047af200130ea60103bc23f8556eef5c3737c617e18061dcca924a54fd2f75ba489e760d64939b722bf65cfa68f3b030c399844d8a9a0ff63df851e0783a58689a7541bfe4170308e507955fa6358f5fd06931033ea6bb47583f1d37376b8d5aff8691b0c1adace3e87f8b922369882fa4cf652daf4e49035183f2d98fb051d6f92d460887339fc7327b17f8d8b56810d143806baa8000d2d380dc6d02222d553f5ee99ae00030da60cfbf57af7703eca3318b7be26ee8752224c806106f359678b37eea9457b30ca60f2cad24946844f8da700830c067517ed83b4fe1ba53406e35ad871cbd7e3b29be50186188c5a5b37bbb67d9d3d6130a7883fda42ae99f6d100030ca6b3b00b914275ea60d6ad01c6174c9d5444af9b8100c30b86f43b97bffd9e7459140230ba60ead4157ac7cc264b8ad987a11c6070c16cdd162752f27a1bf9ecc3181030b6609049317a1e52d809920f3336c0d08229291d26ce8fc9979033fb30b5d2038c2c98f7e2f3558a8afee75830a8591ecb3d59bd3aba82f9564b7b5cefaaf3b582e9b7a3e413d23e29bd55c1f4c9b28856bb5890142a9854e764a53fbedbea3b05534d2c7d77aae2e7775230a9a06137d2ac24465214cc7d1a298d2c75d9520e0a062d93b297d095ad84fb0473cdbfa5abb658eed209c6d5b4504ae57cf6779a601a6d61ff45878ffa2513cc6992b07fff8ba054b40453ec0bd3d6ad3fd7394a30789add480bffd22d4a8239e8a860fb5fc94bad483089bb9f117942749b0fc6114c49d99a488bb011cc11840a2a87d1cfdeba08e6ad6c6a3f4b53438f4430682f11b211f3b22f3f028c21984bece28afc8a259df6f0281f8607430826ed4eb1743e136f97140473bea978d6ecd68d05046377f5886b7a7779f903c3488ab964a13a9f181d0e183e30d75d075d6e32ebb33d30a8b9a43d7479903d2e0fcc155ad41d988358499ae02af945d68131e4c73ebb49c2c4e57360743b717eee57a16aa3a1c58729da62cb60e0c03c16225cccc98c8ad40dcc1f44880525f46f5ad60626fd172d47dc9aeceed6c0702a3e8a4eb942be80410393b4483fc2d7e4251d9d81712b23a7bbd58fd41d18323005a97dff932a8ec9321646d3f2380f23a2bb25b0305df06afbbbecc9f4fb0ac39f4732f3d167b1a72b4c62a284dc5e893551d28286161690000d6d81005a6148fadada644c92159b5ab10b08801546133af5abd492d72cf00f1b393ef05620805598c2da6ee7d3a810376320005518e4e7be3ecd5f68130502488529ed2911adb40987aba09d8b0f1514c31b282801a0c294ee84ae4bcb97eb2fd2028f403985c9c427ff298b98c26057396e9696ecd1430194c2204566e6ff7bc8be202408801426f7bc6769a476d2e6e8628b1a450b0f8f519852f8879265f7a62ca6111e672887243dfc2e5f7c7461833b8ad0610643d075973fa67a8f9a40e828c32a2a3ce928a24e7e51e82003ee5f5e9d25954612133ac65072bb2f31cdd0b30b5c4287188ebb9611cf7f346ec1f9280b833945131efaf24608dae90083214938713af79c599a5f3086e917ada2aae7c4e4dbb9282fe8f082e15bc4bd54e42e186f74ce695ebac73fc905d3e4169973514af6f45174d0b105939294f522df4d9cc65a3098ce2945c991d4a0230ba634f12a4b92e4ce6663c114f428a56c47f44b7e35d6cc6868a0010ea0a10109d0f0f09041c7158c3a493c699153b357a264830225d9e8a2eca4a500083e0cd0610573a4ac9491a89d26710f0f5e0a80e003004ee8a8824175ca972e6fc99354d5584bbbe139fc861766aa800e2a18640979f398e421775330a470175b17b4e335cd8f0e29184777ac185636f3f9d482060768584002343aa260f41c6f25c5fd8ad599066868416303342c20011a1d502874864861fccc3f5d634d058e010f0f15f80db41b39bcc043c7138cdf5f51527f36d52d7e31812e18e0e1e1e18163071d4e309c502297969610c4af8e269844926a0f777e6a7eac4da18309e6642a590ea1b53721a9c6dadbf8e0251872a86441f95a12d26a724a30ce4fda14f14f27d54c82797420c1f82bea25071d9f4ec5fad071044350cf11092a4a90df8d604ed1a5478f96fbe6a4a308a6ce26259d8aa076f5c2367410c1e861c4e517216774ea5e740cc11442cea3152d76ca2d4bd02104934a95e6d564769c488d0e1d4130c7121533b2e59ce3b36573e80082b12ac6071ff57ac2439258213a7e608a9d8492d9fe8a7caad436b8f0f020f6611b880e1f18d447f47fc7fa5356730f0ce2a942a57c499bafc63c3047c822c67284a9b2c80ecc29fbe46025569e832e9fd0a103b3e7fc1232faa774e7e4c01cf793f3466a383084dcc93a25f5ff0df50d4ca9ca7476dc49133a6c60b29bd1f7afb14b538aa1a306660f22f9626db8458e7ea18306e6206b92a3ed857ee5ac858e1918420a7bd723c553493a6460d0f5c1f452ca1efb7d2c0cefb13be446fc8bc8c2c214b73d6ae7707e2987bcc27841ee5225a929617b579872aa3d15272e88e7a915e6d6cf9654e58ffb5cb3c21037437755ffa2e4945598ca3ebf8f885615a6cd946839e59c0f6aa258c82215a6cbbcb99c13ef0a59a0c274ebf6e154485b3e39a730c7ca4a4a23ec1b3958604c61f0109552ca1f254afea5308ff04ff18d70fe1eaab19611c88214a6109479ce3e7d7a71bf1b4116a330e58e7b1a4134925b4a8d355118c54f951eb57f2af13d01b2c1c586c270e2262d9bb4acd0528db58f0485215e4acac38ea8c6da65208b4f18546d3dcd495acff2d558bb40169e3065ae97764f1225bcacb1c614c8a21346fd9113ec52a6fd7d1865c1095359fc4fe2d69ddb6235d69c8bd37622c86213a65431d789fb6ec9ae1aab09e3ed44f5b62dd3fba61a6ba90b1ba78b2f361326bf8a1f275d5f5ca45a70234762c26041a50b2b571344446aacb5e0460eb2812c2e61fa9c3e2e2ad5d60879324b98455987b058c23ec4a2812c2a6108b2697fa6b273ccd696035950c22074ec7394093126ea933086850bd1ba6a24ae92306beaa591266143422261bcb738a2539daac41224cca2428c70df0f6a52e711a6ab18177cf257bec91106d331e4771aedf183dd0893f214e18410c16cd28c30e78f9dae5f21f5b96511e65329fb9cc9274f9e2ac238ee3da3edb7f2fc441862a7a0ec65e554658908c366cdac8dc9da499243982cce9ccd07194bdd19c2b0ef49cbe6c2e4d9ad1006d51ac22d564ce6a93c3c70b8172dc8714701107c0400085910c214a367e4d6764b8a21b40fbb2c0661bc783d31fa395996bc200c49de09f123b1cc4e0d8461ce65948e8a1ff40f0863a898dc172d79c59559fcc1fc1366accc52fe3c310b3f185fd2986f044bd9c9f7c1105b2f2595a4d3635e0f0f3164c107b3af873455d11e4c41c8d1f1e446bf4bab078369dddffd8fe855963c184eb52b5be8feed13c18361bbf3fa95f60ee6ca932d9cecb0371766610753aa121242aace33f70c1059d4c1d439e88837fbe5a9743a1826c6554ab24d5cead11c8c6f66a3abd255e4242407d39aa5aca7a7d42774e2605c0f7796173a7030ae99d9870bf274c4ce1b4c25cbb446abf55da5dd602c714b4145a9ba384b1b0c1bdb299dc50a1bcced3b22ffd55e94145d83b14ee48aa74feab35435183f452a593339c8b7d360cc095ff91192a48b241a4cf9447eca10e6198c6ddaaf636c9abf04cd608aee22eb352c9a50aa2a64510693ba1b212f7e4f0653ccb17062d26ca6c8c76036212df34b2589c17429a87e1ded15742585c190d3d6efa4ead494243098b33e77309bcf174cd9c4a28a27715e757bc190824a3255933f7ba574c1ec7f9def42902729cb5c30968e6f2642b21ecf780b86a81f2584b0ea13e3d582694dad72fcceaa77370b463951a5f379b29b5ab1601cd5599613a24d4af30aa6b0fd59478d6905934cf68f3b4a8ee79454c1b835794d7627557d122a98ceab3f2b9a369192640aa63d75bd5f4ac63d77523056866817f92267b68bc2b1c32585d1eba0603cad94ee53e8cc0bb227183e4e9e30fa932cf79813b4b2cf394284ad09c66d131e572a74d29d09269d3e2c89c6e5d32fc1b027df4dbd64cd1229c12446477f3ea1524c4b12cca9a76a5fb3c2642498365436cbda826b5716904216473075e990f354e794e36a239846082d22d4cbcd7f8a60d056ada0e324f5e7b03040430b1a75011a1690000d2ebc40c1166a4430afa7d8b6cfebaa1492c30607ee3e90c5108c3732d64dc7ee3295f5f01082498e65af74a3ab1e4e48c82208c68cd1d9a16a9ea51d0f0f0058210b2018eed273ec4bf331e12106b2f881219ad6aca4e2f4f42a3e30778970abafb6d037d758c3f1e1c517391cc771163d3077deceb58dce4ab68d1c669b050f4ce13f7f6d9b8b0ed9da813925fd6b5e39393e6f4ac84207a6246e23f3126543c63930e851973c6f527d4bd60b59e0c064f5a542b05c69a7a31b18b4424f87da883a216f210b1b98438e962bfa05a5b4493c90450dccdf9d45c4cef7d1b28d42163430880ba7d353d6f0ad9c8442163330c44b5de92da84f56a2608b1b5e98690bb2908131c3b5d4d743462ad5be102316a620d1935cb45426b65b2ec480854925c7d3f5ab6b4ac5f415860b2a26bab64711f95d61cacd4a4a08f1126df256182396f8925e49cd66b816831506959c4e091d4ebfa4508d3557c1163752a22ebe78d2858d2db640018e5e0189b10ab3255dd3adf5d5afcb3eec0f315461ba3c2b39ba92f2a7570b1a34b4a0a1050d2d68380a9a021e1e681f568718a930867c864ef19239f10dff40157cd8c8f171e3867f78d12858bb618018a8307f2775dac6235e7ec8294ca2d627a414e6bd448708314c618813d383e4b7fc214f928518a530a88dacc84ff13c7c0e298ceb4195cf8fdd859895e9284cfea984509d5fc4a5105198a49afe05a11e4e3ccf408c50985c4b948c999d94da9a82c21c44d0cad974fc42d0ca270cda542cff7896212fa29e30c514d33457b7c61a0bbee80f1c5be017313a71c8a6ad478f87306d08313861cc104965dc566aacbd9f0b000e626cc23cbaaccf36de1aeb802e2ae0e161ae88a10963eca7c8eecec494adc69a2f51c19503c4c88429eb7b4ae1930af3c13a25c4c084b13be7e87ac1930c317b78e4c0b105171e1e1f312e61d4f6bb0a8babae33046258c2786f229ef77fc66a8f5109b39a9a4e5ac3ccbc932861d0e72762446848b10b8e2d100bdc930d0a4cc210627f98999af02177bab0b1c547310ec4908461562b89bc7d593ad901312261300db38ed725499bb68504562161160f13df7212de1344ec1e508256805a0e1c5b70413a10e311a6bc2345655bc8b11c2a00f010c311c61f75df31ae5de72a1f311a6150dfeb104ae9deab1703311861fe523e267f2b4a387b11a6ee7c29fdc65c6dcd0f1c5b281b6228c2a49f133f8450d7d93e0163b06b542449d1830c311823a41c429a2d7d1f2936920d1961309d460e22d7c608e1078e2dccc820030ce6141d11f4b94ef81a71f105938c116df11452e507820c2f18fbeed674f67f4f3b5d156474c138f21e724bbbc3f7cc05639cf7288b2d5241c6164c414372f658bb5acb6ac1706739548a3769cb436464c1b43d49697dcfb7a17a1958308f34ede929ea46907105e39c1c5da645a78ecd56307c96be28afff6cc184b6c8d1393290041955c827b5d5cdfc766190410563ffc80e1d65259ca59ce046a3c06f988c2998d74cf252302421dac3bccf838a7714cc637fb1f6c2733e8f0d20030a86744a6a990433e1697f8239ccc5eb15a1ca3b742798723aa19d3dcb92ce5213cc7d715c46a911b1549860b48ab5adeb4b1eb46709c6ff5db7121f3c5ebc4a30e7d332d5ac78398ff20b2f92606ecf2d39c6073569511948309e8e246549e49bbf1a20e308a68b37799b2ef2846f04e36f4bfe2c7f8b6711ccffb13e92ea49189d08269d3121f2c63f04839c5d8a13a24230574e9ffbd485ad6406c1a0276a9927395a8940306d9747f8c85eeafc812927a1b4cec49095d8fbc06413a4a48b1727e1ef81219dcd5dbe9ede34f1c0785abd25f72ca47c7660d8fdfe55af9589b60e0c96a735442cadbf951c18e4ab465c9771604a29172e37b981a9cf45b72d5be77236305a4842a5ebce6b60aabbb3b3f78ff697cba081e9f63c47ca5f19e272193330859a9d9e58c182da65c8c06842627c7e512c0a69a74aecd30916862065724e69a7571873d5b63582d29bbf2b8cabd7a9b5a6e4b8a91506a5ea2fc590eb131756989359a7ca4dd6d3d9559824971e95f2f9a4e0aac294525a8b7ab721ac4d8551542f5c695d2935418539dae81021a354c7083a85c9545a5c9e780c292253985b2624a16c4f98cea15298f2670f2aa69228f9218571ad757f3b72eedca3309f49cb232f8bc2581d3af2e321556c4361bed355ca666ebfce939fc87e9e13b3f309c3856cb933e2d7f9c713a620d16f27db9d30f78ccae1479eb4b339611c3f933549ba095376bf2d9de029775213a648e17c6e24ebca9809b3cc5c7a8753553a870973d4eaa4bb737d0973c8a7ce5a2679b4ad2d61ce96ee44c5889c9b75254c7b41b99d122bb25953829cd47d98e92761cafe9096462d4f58256110d671a2e935ad279648182bc951dbda175b59818441ebe8d121afe7cbfe11e638aa6639976fbec511e6942645e5c4c8e26a238c9246a5b1b29326228c305f2e616245a87b087911865827743eed5811e68bebd039f57d3e281106af78ed517911612e4bfd126631b6c73d8439480c15a1d22d4eb88630453c3121d9a38414dc42984c77c87b77a5d7c32584a9e4984ad162e5e4dc0ec2143a65975feb69782b08e3e7efa7916d0361cad0102994a97aef0161d0b19444134a5abdfe07e3cec4cb0e29723ec90f66391d7eef7592eeba0fc60b25d5bc2f760a5a3e187e2e74a5e0374a64750f6611a52d29554e4f52d583c1c7bc52ce97e6c16012827e7d13d5764a3c18fd4556843e1d3f72f20ee61aa5ef2ccd6907e3e98513b93baad696753097ee7ca53da659793a18b4a50a573274b2d8cfc1ac716159576ff1477230d7249b5119119205c5c168a64ca8fcb1a363070e86fbb871153335b6bec1f8c1e3578a923b65d50da65c7a674ae9ae0a4ad20643caf993bd7472b6246c30ad5bb8e59b4ee126640de69a942729495e0de65441271db75fe43f9206530e42a913d16bdb7244835135d3eb3ad8095bf10c8637a1bbbd3da2f58666309bb4d116452b8341a8532989d1cb7e2a2483c1cbd446b8f14aed8fc1bcd75eb27ff296c4c560d24baea5544da91c0a83b92be8d23679bd94c0601ad393e2cdb466eb2f9845ad83c872ab1cd50be62de91ff183e75eba601053ad8b13b96098a0a4e54bb9db82415bdcee24cf23f93a2d18f5ec27dd5e9705d3e44fada15d39873c164c5db2464e5e8d35f915cc296f217dac3d9492158cfa15b154073597ee2a98e6d449b5f8ad242e5430b7685321f2650a06bd9552ca69a1cf220573300f3337d9a260b0f568d95c8382d962786544b9f330da134c41c48f7d2a6d04cb9913ccde25545b5049c653d60443d637cfbf9ebd6f6782c97eb4ab5eec4e3f5982e9edcb7427794ac98812cc27d62992b44f72912418336d6fd5ec73f61009261521a84d2b1dc13c5acc549424a22719c16ca67eafcbdd4254044390977366d224c98c08061d41ac7efcda11a91a8239e6c4ea24a2e71195178221659985e510776196060c2008e6fdcea346628dfd2801c15c39e4fa32747ed07d095d42dfd828aff8c0246fe16ff969db43ac0703e8814196840927c663a97d82ce417630001eb8225a3d5ef54d773430801d68413582ba3ef5531778662e18800ecc152c7f0ab2b4f7d44bc630801c1854687c9e5c153d49968401e0c060276458fefd907be1220ce006a5a553594e8c906766060660039365b913ff3c9e44258930801a18238e4cb83c5e0106400383b6163f378b1eb4245be011c0f5a251d00511c2006660f814b157d953ae08fb00646098d892b3e85d64b13716e652da7c929cbd4ab1030b738510c2bbea5252b3fc6f91f6bae315e6385b11827b52f1f3b7f3b000d0a0c315469dd176399c9599b66ed8c0f131015c0f74b4c2a04ce850b245e67b973a58619e1ba533f12ec9885b598549479f12c24e52d05176a14315a66842c768951f9dac4d854962a90baf734ad829e585a10315e42c3eda6ada276d5c1a10011a5ad0708087470bfc0486868e5318d2861a5f516df9c46f0a73e84fe91e762d8579cf65ec53ae3c499f5ce8208561a4f5a5edbc7aa98238bcd822070e2fb6c8310ab3e9edeb1055e31f3f36d217386ca42f7088c2f09f3b848e3bcac1c51625c8c1c516250885d9b3529061a7ce521a14a65425b98298c849253f61dcb1742aa9cb4a2192c40a1d9e30ae6556d76aa5147fd40953ecee70499b862ccb55a18313a64b6da9fb713d04d111063c3cd00d12746cc22827a731c2df83ce1a0f0fe42cf0227468c2149a963aeced99484c18ae3ae4ab8aa2924aacb1f6710963f5966ecd975029a325cca6e47ad2a66b47e95f09730cd36952ca497815d558434a943aaec4728667cb4551cb4fea63b44e5de77f564e051d93309879b614725ee949649230e812b2fa431821b1bb8c844977c88927c47dd01ff6a00312c611b1b3b7a542627798071d5af8cd9fb7c311c6399d45ed9aaf97bd1d8d30fb8cc821653f31c290e298bc93ba16473bad60e8588421a94b48563271f72b1d8a30c7b3fc20b612ce4247228c1d45f4e6a868a3bbef408471648a3839d96bd9e24a42c7210c3f5252929b9763fa39011a5a78785c87210c9ee22eff25154eda1d8530bf5e24ed22272f84141a5a94a183100629a3a346ec5bcad5a20047afc0c30305387a056410c69c18395cfecaa7a60bc22841b74cfef4d1d080046804c2ace2b936449dd5870410a6ce1dd15ab1c6d5f407d3c8fe68932bc70fe61479da4542fa60ec0fdda253ed7c307827a184b49c4e5edb7b30c90cdd2e916b7e62ebc174f9913b87388d3cca3c184fdcc9951362c2e50c0fe6dc503a6cab750753a40bea2145b285f8d9c1ec21ea46f588141d3b753005a9262aa2891d7430d8c955cea542e660d4d3d972b59a6e179283219e69b7bf98f99cd7389827ce5d7b4a952d7fdc0107a3f587d0e9fbeb7883d15484f82f67ca725248e87083a9b3b9c90921049316521d6d306b4891756a724e1c42071b8c23f4b9a4fecdfd0ad0b10673be7b48296adea7c4c343081d6a30da450e56b9fbc34b5a63cd16e0e19106537ebdcf16561ae72b1acc957209f510cf3f6ed6582b868b2f7038063c024320c0118c1dce577eb535ac7335d60a4d312010c008e6740bd1907d93f6322305018a70acdf9b134135d66a060420827962c65c14496241da359653172671f1858d1a19f0f050f3d3c7120438a4d1129773dcf25e40002130d97ce7b2778a1408100453c4c9aaa7957b963c35d63e706c0104536ddaa6c80b277bb25c70f1c59fa96941801f3cfa6dd573f09c6404475a00f5cb81003e58b4859121ba3dc90e6d81a38b2d6e786a77400e82003d30083d96a654fdae530a1608c003e3a57e1aa154ce65fadf81c94d52c8d949d781396c7c85b86f93accd81d192d01b42c6a4981207c648f941282182b211961b18dd2dc6842023cdc93420800dcc791d22972c754ab849011e1e384a170850037396c7fbe42b42bf9b6860b0da89faa193e9f119025120c00c8cd9a7eb525b3dbe3402c8c09456d2c5504ad5079dcd8885416b4cb58734a977e219b028686f31939fa4bbaf4862c7965ac4b4f60a2965862b4c25f94cac3a74098b7e71c38b3b2d683c400b0f8f1b284001eb0f66b4c2f82571acebc4f6c7ae3061062bcc15752429d5a37b3f5aab30e5709e52b2bce7742f0933546130ad8ff73948b7a4c244c28c5498f75dc38490ab919e438419a830887deed02927a475afc6da47b261a3041f38ba066f91805232cc3885299b8821c26e5e3efd1a6be8c3b9485d740db4a1811c5b24bfa18587070540f0118055cc3085b94d564e720a95752a2508334a61906b5a5a1f7e56749881308314062df1bf54e5745d4af72fb8680e8cc2e0b1bb4327468a284cf1f65772b04fa1e357426150e2f4e99fb662300314a63065a1174f277ebe19f88451c6434fcd44519d843c61566f730d99e3cee884a93c5aa4582a48eaf2ac31274c17c73fd207314956ca26cc9dc27c5d8eb6260c3f6e41e57abd9f0433615a7f397539ed56ce1f264ca523a83c9d439f50e94b9854c6b8664d48726fb18429a730e51bebae61d14a183e68d16e71d348caabb1f6c10517fdb11f357278e18905332861b034361e33cf547d28d38c4918c485e420fcd4b6fe481286b5fa8c74fa481845a724f14cc84bd69119903049ed097f2ab47a9a6bc6238c59fa51aebb43f423331c611e376634228dab8f90bd749fb6c61ad96206238e9e949794d1293fc48b22331661d2f0d1c82ade9e29a24b6a62673b2dcab22bcc48844124c49b0da12d9de820c224fa41c49ed8ea0ef321cc21c4eab54a13dc3f8630c4cbfec82ba33aa46c214c6ace62d5fb87ff132184494e777bc8f191a3c8660cc210d4098f587922fea28d98210853e8f5f8c17357ce1c91dbc38c4098b48ce714237eefc68285198030e77fd3d5bc325d769236ccf883e95a457c7ffc770b397e30a62761f1473c7f64990d33fa60d6dd970f337a3e98c7a45c9e442d0d33f66010aea53c6bdbde776f01175e78d1650333f4606e13af1fff5973d619c18c3c9c2b54e9cb18dfc030030fa694ee1e543813415dbc83b1b453e70e9f676a5a543b98e5834417651e4f82e4f20e33ea609891d5ad20f3437b8c0ec64fe95325516225a56c0ea69ce3277a2521621e948339bcf7a99b7822c8ea92e461461c8cfefb123cd672bfc739cc80834967ad9cbbbbc68bf4e30c33de6090ba1ecdde34bb196e30674b2929a5f4a53af56a6983c1e44d8f7eaf173d6283a9e3e547ec3416ee396b30f5e82aaf48a5238d5a0d268b1fd352750d6d350d06b127d4d5040d0d660fb338a9973f652d04cc38c30c3318923e09162b46cee69d32185b82969482d0ea53693298743cb9fa57650ce6d8ad11f546e66973c56050ad7c51e24388d96130a5759ff0b62518f0ed9359f51232e30be6f7d98a97e45dde0fa9c20c2f98438ee36d2945081eda195d309a1097c9e15752fde68249bc24b595976405af6cc130b952c5113bd24ab45d70266668c160a2257e4e938967264f624616cc9b577e73dab31f52f81133b060b6d2f37e21be2e9a0d1b37881633ae7045b2deaaad9067d5580fd7c2bf7174b1013531ccb082d9459e4ade39fe96622412985105c3d749f556b1e449785430078b71b361af12664cc160da258267b887ba902fcc90825154e5d55139a419a5ce8882e146aa8d6da90e95d70c2898f7edf4c7c64497bf673cc174419fe9a0ea7f37c6194e308c9ae751e1cc53ec74130ceb26f6d7a2a9347799607ed32df12f397ef2dc8c2598cf530e167b7a12523843090613d367128c3aea3c7f98f03f2b418229ce4d90f5249a71046368b5af390bd2427f8c60ace4d9a9ae7253f45904f38f989f50a7afa31f36127f31830886e855317b524b3afd0fc1204baa8948faa6d46f2118a4e4594f25bb73c26843051f36d2e69811049376f80f6159cbb72540306957123331e5a5c4f603c38b49d28bab3835a98fd445670e66f8c028294e6be59ce6b39e7a60d059462387d1f4d12e0f0cd73a314bed8a4e4ed948773698b103f39f8e45d62b91b4bd3a3049d9bdd0c9ddea7fcd81415d794ed73d75ea331c184e828c982af777756f6010bb11271baa849cd00606f3db53e9f35ba5e86aac7dd8486506336a60320b0f6926b95a142d0d4c6aae22da08f5fce799310383eeb0ac9c77a13b5f3364601259fd2c87285af9cc5818bc732e1fd7de6ef5790d3260610e336184901ecb40c62b4cb51a6af53f25774bb36980d440862bcce1d923495e6798c4c8b6c2f4162dc952ff64a6a2066e68218315a6decbf5679ea6626833a0058d08d4c0d10ee0f3a25180850220f878808c5518649f24990f931a6bfa0719aa307eb07c75a227e1625f63ad0aa981a36b905418dcde372ec70e16b373c3c6ef7d51010f8f1a38da013570740d09f8163734208174c3e3865742062acca2557a45c9ca2c55d758ebc286c93885d9920841a514bf7462de43c830852958b63f616b6ed9ef52987bb393474ba9d4682e831406e9177ca2ac6d895491310a43eaa0b46acd9808f9230219a23028eb683149e8b32482a130dfc5ea4f3c29aab2ffa10419a030041b4941ded7d3e4e41326eded53ffd6371bb9278c92fd846a9b14d13ea9138611caca7276b9cb71e38449e4544984d39d4d94d3bd7388b264d5adba0c4d182fe4c497efece51241cd8439f6e9a03bdba6cb24e5c9c084b92ce7afec98923c65370d322e61f034d244551239794e3901f21a5ae04186250c415b2bcb8b76391369250c7a4f9ed74bb4d0e651c298a15ea2a85c8892a449983bdeaafd841589b949c2a4567e31c5437b1991309fc895b311ae23d117820c48184b5cc9d1e6e31e43081f619c147bbb43a538c21467fc83887b712bc55690d108a3e58ab6f9113a5e486284a93a8c2ab91c3ac8b86f0a321661c86ef292ac07f3b858086428c2242c94fc7c0a41fd53321261b820395775fa3071224106228c1727e976bcec9e2dafb156ba90710893c5d0115e2d5d3ccb8d761c1f072d20c310c64e592f7a59b6c46cb990510873c81d2689d029abc70961f8339dd5889eab84103918c81884b1439cd50fe5b1c2bd9a2a9021088396edcbba957e3ccc390619813029b1772d3934d34c650908a3e8ac9dd35a589c5f760b32fe604a26f258a7dbfc60aa0f594aff6ca873530b32fa60c8dad1d2980ef3c1202c8eea76d0f649d92bc8d883e1e36513225c8a5631f221430f26257f2e6c6e555b8c0332f260cef693e38308614cecd9041978305ed6f3964b09a2fdc30419773029bbbf102c9a0aca4576300839313546e6a9520b7274f1059aae40461d4c215a58cde94ff16cd3c12444ecadea8fde1964ccc1a0bba154a7c98a8fca0153461ccc167496cfeddde229d37c830c38987b25560ad59fb3dcf806e3b9beaec7d3718351444e7a4433de93966d30d74f7a75ef9c9316b1c041061bcca2f4baa9f5f7e764ca32c858833174e4cfe78e3ce2924e95c544728f3a4a83597d3f9dbd550e6a3b1a8c9a753a9728ff0cbdcf600a71d1d76fa10bc8308339766ad3dff1fdf288cb601a3917226ad4ba332383e1e4255987ec1faedf1a6b1f3612a640468d87a8449165d2581c0a8542a130280c8360f97514c3130000000c1e144763f1589e29bbb20114000442343056343c1a261e141c180d04e3703014068403623028100883c1a04028748ea6700c8b0f2515e935d557e99e1353aeb200d9fcc6754ce80c44c8119e6eca14f2955f0f1c331dc4d506d130fd1664da9c86913a19b432400ec18bb911457805b235891c4adeb651768091eab5d77e70eb293d81c4e3c2663444d433d431c64abd4c5e2ac4d1c74d6a65fd83cc3375796e80d929b7b27a360695d2efaa2e71a4927e3b85bf4b25079595fbd2e12d5ce34315756e9604bdbcf2447447949e31e3d015327d4ce6ff8eec4b19a3a68e3a2824240fec1a0c4a29cd8fb6e60e69df702c238fdf5a221c2e4854653009cfbb492309094f03eaf9fe9a44aef6c32dd154193276cd51fd88a713bc8bbb72614d62280f0dcb61821b33646137b0ed5c93179a1cec5d49039ba3d9b0fda804b3e9f375fff67495c531f413d2479b2969aceb4e308b44c3595f73e813bb039d68c5d33d15d074468af96cdc2007853353c5e872a516d2328d89ebea6ff34492bfb54746e9263e4f46a88595c6eb656166e4b4ff48995183760f133699db9bd3f91713e84bb02dc6f0280d3e028b4cb443aae45f1c59f868e386af9883444a0c3313163316ca65789f5ea051f15275dba98b19f039901ed51a26979dcee3cc0057f7ce0115f98c7c50561c4c70dd3a131d688e4dc0c8dbf95b9318d566b8c1a7f13a1820aad3e997bddc246f0501819d6dbe2124d360abe4e6f8b9d368b0e6c9650340fa75193d3f6165c390a5645f63a4630ccf23d0e988b646e2c50832237d45d706f09fd133460c2820c85317b4b310e3f2236f200911d6195baf9b946e95aec8046f4bc1f070cb369f9d994f47d747526de2682ce100b47127ff8a3d2a827fdb6947e9232edde09dc9d118655613024789b870a8c4c4a9023cfda92f006b7e5c2e9d26fce8d3531917d86936c09666eeb5d85105f5d96a1285d0cdc6088b5a9ce6545550fc62a55a82a94c5285fea0553886d36acb8394807da585bc695904589de91399533c582c41d09282f086e43284ed66f127a2d58407d58c66b9c3921c44d3fcd24a15ae568a5ebe6f550257005784a41641645c8feea284388cb28047a9670165e2f8fb11a03a18ace39a114bd716a330bd6276af016d6933466fd716a0dcb83e56b4553be0fa08563cc701c9322b365491d6fe6e067f4ba7832e5027bb66a42fbf982ccc6768fc7dc748ef8e74372037cd9c2e33969a5a8ba4340cca6fee1aac57efee90f26fbc8d5fed521609bde34a2c38edbe02fea1995c710f46cd8926f3ee054665cdef666013642696a1346dce3d9b935fa5dd876d522b5059bc58d8f7019d0b133494c18577e1ade092c4b7e1b12250b93e882fcc097e2ae27503b3d2c0545f0c15adc5328f93e951e631230d6579f7ee5aa358ed8e6ebb9f50ef288a543c45723ff414f08891a6362b4a7c585477a951373813a589b3513eb6ddff4d1dc76fbea8e4b03563d4f4bf6943bedc84aeb862c66254a72fc70a4f997d631840619285488e43d4e4b0618555bf6ff7993cd9c8e83d4507d42df8ac79ecbe7cf2cd1c8d51ef005c319d1b591c3dcaedeee1909129f14831e8e934bd575423155c6daca0b333ac0a6288eff7190aa94d26df0fdcf706f610be82a7eb81c69d36165f0f97435d6808c0b1cb38c91ef6680f65f2e8219ef2f12c2412c80db41909203cb5da2bf76d21f0a6edf92ef1be0a0dc7415ae763989a43686280f82c90a64bafda406d829e67911fbce7e50f8c9d9e4886e2a7f0ae954326d3c32b98d18ea3780d645642a76850a5eb633d09e8062181b846d2463553631a11bde2cb0b24489d5c41f0e67fb1e1e37159e99ea2a66f93fdec382ed9b60121641223d50946ac1edcf085e577a3607d2305510bc6127b38b6ce897021a09a554ad83c3d3d47a05f281ba95037af91c7229f145b624c8e36600b5adef16bcfb3e1760774c91ec83d9675d492ff9d7f424f813ec8f343d935f6c7aa7cfc9c9ad4a26408f147452bee24bf1154bc1421e669da917700f30d08c8414dc3d346bba0a73dd70d13a03c2c17d109ed09a71b79f24fdb04b7e9f589a33452ea6200b3c60c75e108fb01d33f2326022f2babacee5cdd3a9dcd48a8142f5dbb74c2bae7f146779f26c070527dadc04e5142a12d06ac44d69c68bf20ab902b3411ca990c250ab4a8295f5cc92e01c46c6f8313e0730889f94103c04a271a1a9668810665cbd920688daa2ab0ac9089d20193eca01b8c49e910e2df46f68e6512ad5385b1363d016da0c30f6def5453d35149ea2f30c5454043b06f36702f4f07841ed2c652a2d80f84f250e051e9685d1b9bf4710af4ddd3b1d58fa546fbb26549d5cff4339a3fe2537e46cd1a727a1f3f31cace2767cf447b6a6c2b2cd19c2803d01efa9f731c22a779f5235b4e70027e715161c209e23408ee15fe61c60367185730afa1e881b4be630e3034af70f8f12a7a902977fa47676b95d68889364acb145d639177c06c402624e91f0de01b5dc7d81f6f3157d71c7eaa54ed09b7247420260af12fa8d1892c80b00edbbf50cff96ad8f070f160ba02ccb6cdc5422419fe282f4a314fc05b20ce8d96f4f17d56882b6ebf66059e8d107135521051db202c099b7307e57c67fdc9b27b44e03c49d1c0c37b7104c0a645017d4c3bbf39d81951e3fd03ba90de3ac1ec34e460461009e16a010e9b2fbf61e8843fea3b97269bd44ddcfcef2e4f50978aede1bcb4735261489dcb44ede40c69ab926d5ab441563a5cb5cd513c0f480d8247ae1b730a9ca315ebd32be660f6efd4c762259ef8d71dd7b3c4ccd7b401479be91147834f9d154b8f4bfd1783a4174d4dbe951736a5e4549c86cf4313a4b6b52a8645720a692995df1f2ddafc3fca341e4ef76ba08b57f15a397087c02db6ab1caa3c75426fd433828c9d4dff765c57a22196fac2487ea9d1ec49c876ce273a353ff85278af8ab87510bd80ccd5a2a3672c6a1cb965cc4b0ace8757a1d06370430b48829dc1d645c47c30e91d02628f193ebaaaeedfc51f1bda6474eadade3e208cee33da6295565679256c4d188b64fd6f31b0614a508f9efe35e025317eac65e6f1b12ff2ace24a92b0a39e8e280de735f5e47a8ea2a9cd582c7c9f913def21a62c0d9708ac19216f7f90ec15018c5730f95e1f3b416bff1787514842ab6e6242c89073f69b261dd381c169785b0928962393eb211141e728da5a1147b02fdc6cf000f60b8adde605862ca16e03b1bf6e64e4f14ce7584274415967a248abb91498aad5e26818ae60c8a3985822946614154bf53ec087e4a9f42de02d4853b0b5020a2650ef0c9a8866091ddb41163b0f292e0dfa48426251b06f839e40f4b681606e5e4991057489c916c23a1a10f111120ab4a0eea4af2f70dddbfec18ac75d8058ad40d59ef704ab045c8e0506e67794163844b9efd8016364da9804ac9febbadd75aff27748af82661223f915301a21dcc370238d0a602024bd1fd430247b53534b3682fc35b9613ebe0830be262ba420825ba247918cb8c481a0ee359042660b38e7d2380f61c534dd182473045b738b3bd88d0f7cee823bf926103a40ecf63ea4bd14bba392e0725d3e2de186a6ed7d2dd18c0184147882d7d74f20a07f02b5e7c63e49b7d6586fc4b740f60a502dc94fd264c5691ed44e71655069eaa4c6a162a7f9d272abc898c52f2ad1c7f8a443fa2a18d66d8e95035cab372cb177a34618be41c976fec7648352223915586f21a5d968298ae628298b34cc729542169ad6216c156184b117d152582cd571ac306c5c6660a165f72e05a864c8b45e53dd71d6e8f2ead82e65944f5b7e0a3d16f1b608bb280a4b308d129e041a9c05d1260d01661bba70b3bd1596006a0c0e61dce18377031308211085a10ce0fff7f2d175e34669d839a773b2ee70f4e5ac544f1096d2479c1c3d1e8a42363e4511da8a34528dcd5a5986636ac1f1e55b5597d2fb1c7e13d4884e80a663246dba34bad54f32c81aa4d1a664651e8ab850c73c754189b59218b021d5a99db2c9cfd4eda91acac26cab18ad5c587f90ea981b8b1105e91d4b568ba85fbeee8fa609f2542566d22726436032c6f448b381725f00d7f6da63a51e2e5bb3f2080587eb64acd05a38f7b5faa830ccf81e6d83f6050098080b0a6e49c80709e02b988b6e97c2361c40a28eb5bc1f3cc321f7b2dcfd0d10510144060a3d04a1375e0e0825c87bfc2fc3cad2be668e87590abb907db333a2fc263766775c14e4c38ac3f41d5effc1410dcf315b1eece3416c9780ee738e6d0855aeffe1097e9329fc55d3e9ca9087c6df6b80a17e541c786e827099ce4b58a149e602dc906d8922365610ddb843ae98151c14a236db415ee677135d1b7075275f7097a2c7db3723e3d758d21e5462c54bf262b17e835eb50eb854909cddb05de2a3a42e75a28776ce6f754706652b4c3cefe11a91deb97fe26dd998a738d10a1fcfa3090139df3e1792db3d3792fa1a799d5734fe19a0ed81c15a46af7498e80f9e7f4dc8631d6f358b4fb7ce6d8ef496d391fab6b460156a256ac6030b05a1b43bfa89002589276a70e0e265de40dc84a84e5c84865a088ac1bee1ec06abf228b313a4ebfbbf9dcf8943699c0804b3d9fd09db19451f95616d378139a8d9e406c494208525791223aeb81cf08efd360eae8862706d403ae2817bbfe4490fc935d1bb4936a7d0c9544246d4d18beb20606739bb92e84044a44f27a1510d425446305293c1ae2b314d6feffc1b34a3638c94be379af27d12542d4045a5dfb6c7b559947740d5a75e6c831d8220cd4cf8a7e00e3812dd86d3fffdd6d5fe91cdcc08177ebd8e7c15948781be697d412d29e519ede69afd26dbe7d7d40ab6ba565eea80e62448f634b9b4d3ec6d36086214007381ce5e45158e66f6c7617970635580749fbe6003ee96cc3aa74de57ea131aca128ed9258365d8c49e1ee9bebad8baea4ea045a1dd445eb9aa551bff1f35282303aaab55f3159c040cf6999ccc99b2c93d965f3a952315162c2efaeb5d8ba5ab5142e77a35ed59e91f440cfe965d6baa7c729cf219e87ddcea44aab19f5662da7aea85642e55cae1cac810e8ce49c94c99ccc2dc588be5b20033a670bad09ea292df6aed65a28001fb8419a0bfa913f9782bba69d9c96ee5889373a889929155af5a87cd6e6dc1f13309c0064880edea01dda712c0691a2bcdc7a8dd32fd9d35ca9da2a44f5592f67f7b44a519e663a2d9c5b17032bd5cdbeaa0d0a7d36e035512c2eb62b309c35cd4b9408d1eb36395c47f42b69baa83781988d9f9af68c9693d0a2cfb19685467f9358aec464b1a400e3e90b4d15b63f6c618fb20b0e7bfca8b3d223538cbe49551a50f524d8696eaa54bdecccda325b59721ad7040b6dbd9576c28307109f07c6a45f02ada4021f4c27c70ad3fdd457f540fcd71ed041b90b96e85629e021e26b91fb357836a29e1b315ff8a662ceaa88cbc2dae08ab4cab5781264a89cf863656dd54a781afef003aff17b00b649223c61b3fc4ed93210d4083df7f2054ad5f8916a11250776bb474afcde8070a03ac3ab4642a82fd0d8b21d44f9e76c5f7b4306a0afba43828c8f86006948b2a39793e2c5fb67cd36e357a3ffaa4b5257f13d519c84c7e16c014db804ae7b16153b2ab5159a22ee0a8e47d2fcdc0c58a6f5bf55b235a4d367064e0e4dc820982ec17c53e9e09d18cea18a9b89ed4cc921459958cc60da9cd53225ff0e63ed03076fc4a041e6dc7b4e4a14fac99986f362dd8951d8eb7b95d7a28611de36663c2ebd3653358ca005f881c3d9897f0a16e74072eacd5f118d9b2a5efdb9b7b9e891cc2303021a86e3a6c8a1e43f0838d29c226cf57ff04dec33e016d6a36252c36ea35849c8b21a9763f5cfa7921011dab0f91be0ad109e2b39683674bf07674f3dad160c776503f680490ccbce93eb4cfde05655613ba1a611f364a06e2c4a0adef6d9cb79d12ba577a44d7295a8af828c9cc08b0db413592262d83227468524b572695a32bb50bb6910c25f221d1e459e6ed04a760fa838d2590b68433940f58691bdd1f17e25022389e0ea0ca70c8f94b603dae629bccf5a3dd7fafa071d093a0b6d97fc204a4c3ff45e90b1cab00874ad7d756880c480951be64e1ebaca40f0d107a5da0f1d81d059d59379131bb2288c1dcc769827490f4395a5248b4d40e0a1738c6c7df960866c8818bf2f167307a65a474d0e4e7f121d42dae258bd8e368c5f2520a4508a530495d3ac908ef6073ff0c535069c532deb887e8605b2867889a39b28730467b1df045fe000e47e3989990ba1981973806ad4085165a4bc1593924bf2b67ee49a022238f35b57b614c78c50f3931289fbcca9cdcb914d25302c994c1975870e2564a21ce049701d4afadc798e713ade09bd7411a40a316586aa3726e33c138cac22a532ee7c0a93fc9aff14bd712bc8ac13544d8efdfef2e7843779aae61ce72de0f97e7088bb782d4c6e9896a9a7632a536222338323a231b66e690cf92f64e8bbb466abee417ee2713178169814ce3f3cab878fa7828da4eb68c204976587778f0215358ff93d86c9f1da7f5c1a562049cc6a3435ba8543f69b84b781a58a98b04a45c9bf1f81e90db6a5a88153d7df27f23b4aa50662c433b04c5b20103079344120d91c52b119c1bb22f9100ef26cc7d746f92798e276ff8e7ca09fd068f4be423fbf992c0f99d10f7766b89b22c683c64dc34cc33b35d65871eb3f821d063403ed749c9bc0d400fea5c9ac30075cf431c69a462b1a7b219f68ba782255c7e683e988dea42343b4deada812efc2515b95025b3c5dfbd1e36217cb6fd280f0ea49fc22e81368b2a4fc6950ca1ead6c41c756b5cae6fda17481f4cd02a363b2c0518ccb650065be36a855cfb91c4d72196970e151c9429e25dae3d601bcbdea7a8468b85b3b2dfcd06e1a4d8f62a5f9ba3c8de9dd1429530265b37467ac74b3bd920d6b3e86f06b758f4fdc869a213da35b2971ae65609c05d6d6084e0d06e1c838a1dfeb6e0e9981011a7c0e427f88380cc0c2cacdd5752df7ad611a0f182d975ce17712a9cc292b30aa502c2ee3732fa41b2645d9eb76b7d081b0a24508b99c398440d7745229514a28e27a06002167b822982287535039c5f900163da00abc06453a610cbc71752de78db24f47e2841492a81119999032364964a5acf9aec81ee548df6ab3843717447163c286d6a084cfcd2b9f4dc1449111b2b9368afbc09e623171271f8f9ae165710844a9f0f6990e8a43c38f90d9b11560ca380ffe0eb9b59b411e0f65e1c8c6e28f2a1e8049692e181536899aa314129127804e3cbcbb56df21fd5be2c03c036039669bb355c3162a2d55eb00a0c9ee55fdc4e397f151504888d76721fb5d70e156d8a1258c6b39e6bf19bfb5130b57122f4b3b31b9d227e542b7e08768b3ddfc906067efb8909062ae0341bb0bad6e9bba5ed97ed8236beb408a1c2934f37a12f3feb891a1600f14ce07c2e6a83553293ff1441a2ad5e526a9a1cc1bfaa2e0a0097797cc20903967660c36109c602819029fc977350b10d505799971085720cb2c3d0cbb9cd37ba15ebeed57a3444fb18a2c4a10f80bd3b80240124d00086ba8f8576700a84c17239c5732c7a5725b5a22133aa23f7a9d9de905924c9c82c19adb4ea2c5575e61bc497c11f0c04cc23a32fa9a003cecc551d9ea042384a5f201ad5cadcae108a6f41aa960b032dbbaa1a7911ac62da0d819b7e744fa4ae22652b694bcd044772a323281d90205c5d5ee210a6b82251577c6ae0d70c99148ada5170d36751954b0bca23d7de4e8d8a033919278656d6e21f25c700041b0d6296e629f5bb8405c4ff6710ee00e56c1f7a4763b50ca5f061362ceef80e1904b2c69529064cc6b7c5c548fb3b64f259dacf03b36d9ddf5429993ae44100a51d35c2596505a5c040501a0f17751d00d8588369aac0cb8094d3e8b7ae995e87a88d9a3154c393762ef11323a406759431b868e3836867b8756c5dfefc509d8b75ba9614bb62e242becc15a7e25a13b623c0906d6a7a563835424a72c5f0f24e9e428b113c6901459832a3faa68543122575a27282e598e4acb2094be296b260f68461de88f59a22f600c0d4faaef59df66ba38876544c45fbcba2a6fca748dea1c57eb3c297f76d2004cd7a6d2b1001d19559189f29df22407539e9d4a396221bde703d8a83f2aa3c7650da8986bf9018711c782343210861750e0a1ba40ff641ee85635ca4642a6950fe3542eaa1fe3fd05ebee38f56d829c4bb9774623c4aaa54368ddc6509a01ed3334701e80e6774965fd7ab1199de4f99261b602e0d6549df6895d56053b1a492eefe45a0f23c9e58b5eb4af03fa7d6d9ef477a23754125a9b0a8e48f0253de535443e94eb0d9603118d0079697ab395ca1f16c9160c73600dc4bce18130aa31073abbcc1853ebabd656e8d6b432b42cd0597b70f674ca63111b6a2ce57ce8a60fe24597b02aa58fed7bdb80f5343cdfc92a225e8b02fb39eab7c6ef4e5f3cfbbef25b67bf053f58fa5fdab7c4ef565f2c7d4ffaa6a45f91a57df278f53447bbd75979c211925976126a1b251a27ffafa7fb836955d0e6f734b63f4ee894414c595b994e62f69f5dfc09093d7a5a1b410e66c398bc6f4b47a5a07eb14f0cef5a8ecc660f3f7dbc9226d7409b5368f9eff0b0b0c6bc9ccccb0347cf5c992bb49e631458b127ef44c6eb62acd7b203d62621f2d79834a481646816d50cf5669330dbb8d911dff6730373b8ff0674dbd82b6d95485bbf93ae338754ac7d72adc3897e2c41f29bc42a124704c92725e30bc7044937127d24963712a3a2929a8442e22bc9d625924d8c4625518955ca5383447c24aa2bed158073893be9c9b8b4a6a3edb07970af3dde535c082d078f661e3eeec771f892a7c89c475d1e2d3d08f5f8eb61d543aa87411eee95c77615349bc7c523a687821e1e3decf410cee3fd75b7ab207afcec71d68fed4d21c73a7b1fcb800ced88ae215601f185c8a9718ca8e8fea2304402136852e773ef5cc6dc38154709d11ba8328f2ef69ba23cd8f38e0917f7fa4d3b17ab02d6d936067d42722c0d223844530d6951c4dbaf4996653eef3684b379659624809d510afd6d024c57b6b4c4f39aa96ab5c3f991a6838adcf05bd98c7e00db019324c72d133e925ecab8105380e606e7c17f6d8a48295206ef1a84518b7de68e2e209810874e1c2343320a40f1b4dbc21222d3592f062ed80ae8c9a5e8611c534ddf0bacc3d815371278144dc15602ca81bca77d12b599654bb48d3530dfa0894c2a0cec0a33f31ce4b295491e9a716dd2b44043f846b544cc29aa5b420353b830475b0ad4b1d921f2742b8475a9a468637eb2c9724e430dd83247386e389c7a2d01701a0b6127b160a955e938c289cf05c7b3e2973a37ce871adbbdce2e1ab2114a9165a43179437663c6b010db77abcc2786499a1254e337a76a58360c54210ac9fc503d6965dd62437cf0db39b438b5e38219f22413635d0cd2dad7ebb4f8f680eafc362daa48e534991ee59ff25e409cc2097a5bccfdbdd82d9a0af8053c796d80d2482e28b3e47db119aaf2e005942160600ade7379ecacbb61b786d23e501ce9966c9715392a9d0c443b8730c07e787bab84968c58414d7026386141914f631fdbf871078f5baf3e6d6c3d48e16f255407b8e434084640a7c4716e1215323c2666ff88b68b6bd9b11a482cec660cb8586519420036266ed50e4a46870e6a49be064bb1b2fe617a189964cc13a7b6f55350c9e47a48efd563925ba6b746bf34499f85b1ff15f89f6e9148fd39c4839b8f5635a0226184a7aa63ee8d26ee9b4522bf0612846a6fa71cf5a4e7e95cb70bb944e8f0a2083ceed422ca8bf291d12eadd76a283194730311534b83d63c31940547b61049726078751186f9f6808515936e46c375436d78272173d80a20eb96efe242572ba38394724d3b7affe1894e34c4a46804be6ae3a2ab85007f400f4d00ab092d0c802ee0f9f1936b154aa37814cfa7de1542c0e2e67328d7387b3b7615ac044b68e4871e358000f4eeb2736a2abbb01501f26812c729f5cfc034b281e89c10709c5000e29e3ac5c4e65a59f09e34a7257560d1493dc1669ed86e005e7c7c2eed258ffa9cca61f7ae1d1cdb600b5bdb86d501c78206f056b279093c4bf34f53a9f7b40f8065ac623c7389285355f30ae06b429715bbe542a81fb1db278e316c68bd5818da16cd9d2ef063ba5026008fe0acc34c156710c480da7fb0e34668e5876c504a10e8fb7c4e6f07238189ee2d273f5b785ef929d876f94422645f08290457f9b1233676e2b6d51c6525448b06c9988d009923c72398bf297d971300f41a14f2951fdb95a06c4961c8ada0df257e68371bd16c010e43fdc3789c5f671e770f36a86440c45758b44034ff1b16454adf482ba65c0f93c26af075296796c02ff744904ef533db4090cd329522b2053bc24489a4ce527f9c783c227482d9110368caf03081ed464811e98626af381b718793196e6a3439e036c48d52360bec0432f046263ca3e706345dd7c283151ac431b06a5d9bb21278077bb5be32ad756c55b166646b427c3b6a6adad3b7dd5225b3b9af5a103a7d6e99d328fa5b986427b6484f5c5b1a92390a2607ae0a540a68a1f5e8d9193a4fd969267aa04b919c53642753ffe937704d27d1237467a8bb68d66c881de86f74dab694b0cc64ca809fe4a20bb3b4433a4e6be4a3e31d41d3fcac39078512da528f71927d7dc0494c51c15a490fdb2b8eb667cf9e5898d82f57384295f5aa0d761a55f0915048f53ada6d1515c04f93a579fa1d761a47a3639708c398914f465a8e4c3061367ad1368a6cb4da48f46070460b041ff58eb43d232355da918aeb9cc26f9cab1ccee9352a83eafc84869b782abf711c145aff6ce44d10b77ec1ef51193420ced8dfc141387e2c201fda4046f30455b2ab0ddb4036c4541a4a690e27247c29e9e5bd67e90c1627d3e7302ada05801bc118252935b9d6fd729c58552b9d376586f1308bfcccef22092febbd8ed82b390135e3154ea3e7e0fae5fb6a088f70c5c0f2f1d711ed60598fb8e9f5bd388644d7b493591821bcda8165c9ca8d95185676b71ace0a438e9585b482291764a89f3a65e8d499d080b48c851b94e44a410f8d8c0407daa6485209474185cb1711ee3a1cf3ea0dfc4645478157512e0433e4256a180698ce52c6e88255648aff76ee29943c60257aed1187b9a5be03a71ad645440c5c22a8a4542ea834f2f1362820b55135788acba4ba50956084a7ef114a81f87eea84e022f79cb9e30175a4804c444969a55df25710177434ea8a4fb48337c90f0dd289c71f3d0f00214229f0e9a381ce1d38bdef8056cd6e2a67e19850308a02b5743f285e818c56861ea407b2313bf1e9cdf8ab7193622de702fa0e09ee882b84e3c2c085d42f326fc4c423f783fb2a93b223540823bdf4f3762589803485210cdc40243ca219683c6a0a372b6b32ad5fcc174375c931adb1a6d58574d697e45a66fb79181559c8b71f816a90eaad50be42f3f741fdc7057f5e9544c8b8e4d94acda85b543398d3038c13f3de1697219b3407b3519b8d266024ecbcca858d4c3d2788c22b0ddb80641b18ed807b5081df10982f55b10c4cc49ea976c3cff91c73449f665d5134702930e091782ff2f88d7e5ad6582a0d0492edc0f9c3a52f57c00fb20f08e607104f2d0b7b96e92f97211632e90998c85d0fe722d2adb6524728f97f6802c85ea002f3b884379fdf4f23f8dadf5c0582ddce2ef23de637d3bcf6fe6e7b6b495e266b758f847d9d83dd179e38705a9ab87fd8fefb850bb4038a460987e2e52746597357b121ba6ece14ad9c14dcc082aa3d8a5320ec01bca340dc06cd1269c3bbf151efff71bd48901bed4d84905f411f3baf9166d0ab7cd4fb7fd40b20f4cac7bdf161bda88f355d7706d8eb7d4ceff8b8f11a2fb87a852037dce3d5c5bb25d156d5c92020d886c6b87290b213a4308548b3ba0cf6de6190751fc72032ad26acdd500fc1a720eaa4a762a274048c70c3b8cc2c3ed7b7deeeeffd78ffca3dbeb3f8ab9ab7579653a85aac3ff87e421a62fb19b6d99adf42cf146c9967a0e52381cd40bc2d0b75b7098c37d644725581eb609e8cc5c044ec81dd044c49389bf0a85165ff8a24e593f4fff04477fc0c5bb2de8c423ea20969dfc5d95a4db48b8a18bdcb601bc2a6d66c0700d63790155d884e9b4fbad5242e16906233635d2d8d453fef428faffac90deeb00840d88de0dc270024710739a621fc6ac206054925b84a81d3e89f10961b21482f37482a0fe20d0842ef222aca9c29825469a7d5bdf6bff881e13124dc0c6e5402750450015da19672fcc76340c634d8037cccb65a58caedaf501b047c3c69b794a256b5857c0881648784ef3e1517033a6d15e33111774898956608481597398027af7cab65075a5ea87f8909b13ee98a0cf1a40c443a51fa3c0bdb119a11815c7ca9f7ef86f13a1c6fb4c8038c7961c2320aeeb4e758895cab3cddece684873ad1cba72cd9aa3a40283c117722a0d197dfc71833ba3b1bb164a52f4fc7a1266b8a840937a33eb338f33360d475859b1d0dfff8dc3f88190fc4a014cb31b55748203c6ab6fca35a1b1e64c1a3eb2217454abf630c48495fadcf049f13e3306f652bfd225de84581b01bbee778bec44af16e551a87014bf6a1037c61f558233492dd320cabd87ea73802b3237cc792d2e520fd46380fc17e089cab21a84e3aec1e561646071b01731ed38f298e59c784c6a4625ac65c1123505716eb5e22e8ce81d03a7019465d22849273208a9b5b722f77bd37353bb48349da92b91bae90a610fa435d58f7b4ada1e16909c43de3fc97e094c3de8843355c5c101c60a0767679c823b7d6b3b7bde87ac1f262c0196de7c755e705b049e05096ca9a6cdeb141029844fe645fe976a0553c32ece269c82370576322f4ab1d4d8858dcdbef59f6e2ec0370d2ba9f39b8ae1e30766c2cae199eefd86c44759c75f43a2eeafc5fa4092f909c967278f689a8384cf8b0dd3e040fe03c2598811955d1301ebf2c6985e70ed978b83e131afa13c6af2b5cc25ec6e97733292be305b9a537430c2a6ccffcee4904c813933dfa228373af89f71aa0d6d216dc6d2ffff748a2bc0c527ddf106149a52584fbf05a780b8f179782300377280716d916ac67813ac52964e22bf1f453b7595487f3fc078e24c4f35cfac6c27ad708ccdab1bf84a8969bcb8243482399106f3b0a3d280e4187136b8e65811cf974eb94ab2fcd433aac47ddcb36e61d4c0f79f2cf17269290f1f3159e2f75b1f0dee813eca4021e89848e0b3660b46439330f0f0f0f0f0f0f0f8f3121b5b54f48424a524a4aabd75247e420a524a54c2989b7483b7a3a33f129a6c9de6498f8040402c20bc70b970ba529194ac5532a28a5c7a80127ee5f214f5a8e783a3168c07a4a297878ca32d5b663164c8ed729a9f85bf27ae3396c6c60c60ccf614305336698a143166c66448d9d4468e4d7c4823fedaff9a34760c189ee1077731c9d745baf489ecac9cdb74757703a42ca15730a19947dad605f82662425ace3dbc70acecdc3d44790ef216dabe0934a4a664a4e1e3cffaae0a444492b62a954b09a76a742aca8bd6799252a785bcb1d59a994498c769c82ffa484d211377595af29f83af9a33f9b8f1252540a6ee2a65df797284953527016f4925c3f4fdb3419052f2612c54f6645c1f9c5dc94dbaf34e8e0e1e80805abab13420e25bede9b147480824d136356ebd99fe0d4fd57f637992df6da800b68514ed0e1095ebb2477ddc43bc1c41c925f98162dbe419c603d922e696b494d66cf2698e469a1e67e5737d2046fcaf467d7f6c7c8e94c70494fd9e40ea57dbfc504aba5c77b83ada714d24b30392c55292d1ef27f8a25f8ad0c169e1daa76639560547559b885790a756180c60942072598bc41529c1c3ad4e23e6346c724b8b5f8e31b31eb3eac1a6934eaa6d021094ef848c8e1679d69523f414f60c60c2f3a22c1d786082a9a6b4ec999ea80049783526d2288fe11bc8ed021c7ecd31bb2c8118cd60ccbef49d4434f7e8398024e021d8de034e4dff43da943ca0d6211e86004abc12b8bcceb9e47c72c82efec5082418722d8efec7927294b29c9b023119cea94dd4bc6122ef22e0e175d74d1ef051765800e44f01526d3c4cfd095cf6c41c721ceebcba2eb4e372ae83004fb1ad2b43f3c2479ea1bdc51082e67e57491a5ec20042ba23db5ad8e490a5dc0050e1a9e6c98391737726cc720f00b49a530e5a2a26e0c3a04c1994ab595f54d67aadf11085627e5b2720b992de21d8060628d797b55a9945448c71f38a1ffa305b788ada1593fb06fb62629546366d3fbc05fafb7794bb0ccb4f9c07e8c6cb2d4b4072eaa78aa4efa3cbd5c3d30da79a2e7f4f595369a072ebf267597206944060f5c063d12ef2f596a767760b4268d3177ec16f5abc30e33e8a8c349765042c7545a0ac90e3af03958c88a215f4ea2b21d73b0191d72603fc9d131258fef9713904003a0868e38701b2c8d5c1711b225b57ff1451b1278c1c50d0a748e66c0052420811933686400ced00107f672f2159d624cea94ae60a1e30d8caba958a752c8775d631b3adcc0d7c7e049d3db36f023838aa0a116717b3674b081754b225685a05e035b6eaf6ea2a63ffda506d663cea1be7e3f8f9b062684a42fee6890adf92fde810636e7e5a4824cb3b45981a0e30cecae8ae6bd3bbfb69c02860e337071623ecd1c3aa59c45eb858e32707f994d591695e4c46fd0485e943b420719b84b4dc143b2181c5c181f748c814d31668b90f24bed88e4b0a182d221063ef2fa8dd96ec4be48018d30b041481229e4244fe434707071050c5c98fa4d41a62b499e44838e2fb09147d4d94775f111aa91c44a0076d0e10526ad28d15841684613e902a7b5b784a4bb50a1830b8cb6eefc93906369e8d802a74bea5f67ffeef4592d30aafbb3d783e7deb4d0d09105ce72d04188125a648c3916b811faaf6a21bd4d4cf10aec5a0c1dc945a9521347d46105365db2fe0ec2bf62765805f6ddf4e7385eeb163aa8c0bf7d324b093a22f7d33105c6826e0da9e33987d0b09156061d52e0af920ceaed453fa64a0abe880257a36e528aa8a3aff324b0fb4107143ccf5fb5fa136dd0501e743c81f7495a993a7a9ac72cdde81518740263df1aa4c5b1bca63d4d60438f6bf64d4a3d05bf461adea083099c1cfba469540c9a7a61435b80bca0000ae00e1d4b6092d6e49a827e97b549097cad779654913faba808858e24702ad47b2d54590ee16f193a90c0e71044c8bdb1f65dcd11b864392d57cc15b91742860e23f0b529c4226e4811f8c937713787e7583a18860e2270c94a9d48d14e770c818d1fb1b5e4256971371a2dd00f740881fb4fc24e488aa03cfb0e7404815359a966f235d79368aed001045e34770a399ba44e3a2b56e8f80117fc3d04b52153238d469d0e3a7cc076a4944a074fdd7b658db416acf5800d21966bcc53513dc303fe92f6ca1b32d4b10326e6a6354f162fa7d420a043077c090922ea256f8624e580af983daee4b30bcf260eb8bca8a34fc449bd7f75dc805352d3fe7609ad4ad7068c52e33b4144936be91b1d35e03c09fd697f6b9dc65423cd4632d322030ec841070d18b7affeffb190aacf8213622a88adbe27adb194051bb7bf2bc62ad3696e0660c4824d69539e64ab1efd9297b0e0bdd7df630c39e60418afe043bcb451ff25e84b2379d1e45cc16669ab6822061932556d048c56f0371e36415465d18c16197080b10183158c655325a3dadd5b560b2360ac821dbd92ae2fa7a42a7acf8dee95ed2e239b0418a96054a4c9c9f2078931a660a082d5089e9d95a2ed22458b2db4d8420bf30818a7e093d2eb2d49957809cd14bc4f4e5bfae40499925a0abe45e69d2521318690cad17e72dc80410a368b7efd90745809114f41c01805ebd9eb7df1828aba9fc11005fbaf41dfeb4b67356d8d2c2c1a80110a2ec4fbced39b2b928ae2e8030c50f0ad31c7d4aaaf9553e4e200e3137caea67753be9a2fe8a400c31368cf76b24dda453db5000010303a91a7eeed0ac13c5e3f80c109f6433669e5c1e2a9bce826b86c3a98ca21f754a4cf8dbe40299a6033fe691f9591f2f51f061899e0da6fbf52abe4fbab6082d3bfa395426b525a62b904ef19724c97a24e12ffa025f810b47defc40ede2eaa042754c794bc36e9a98a53820932fe645162b973266b128cf726a96ff142352b0926ae7ebeedbcf4b596014624f85361a75b7df6714220c1a5b6f4a4b324791e458f60f3ed72cc19b9bf73c411e40fc1bd73f746b0a63ba3a6902445513a23d82a4b5dea679d37ca4570327da9a4529922d8a094f64f113dc97037115c5e6f48c132e76bf8886025e62b7dd63985db7b08ae93e98ca9534ba5983704db9382f9067d39f7ad8560ef93daf2b28d105c3013135372b389b006c187875022b778cafb7bc31004bf195ff3dd0895589a0ec0080497f306cf3c75a1f29a040b3000c1e48ea5cc2649f78ca32bc0f803fb9927e9896b69681f0330fcc06898876c17327ddabc0f9ca5b6564ca67225e52f1060f0810b9195d734e7db905335d26ca4f4018c3df0b962e6deca9962d10327a964f2daf423b7926aa4a5d4346c24d30318796062aac98a936e6cadad9176c34990bc0bd4010c3cd89ff95fb91e52793968b4e0468e25598071076e82d4d518d592aeb809fa0476605c84ceb7119279bc8fe100461dd8d2a419aa555474a7d448ab1bc0a0032b1a24a49cb2eaea8e36ca66ccc0d145172968c0161478038c39f071640ae197b264f2a01a69280032c090c3b61be32565eabb730088028c3870494d44dfa4a275f90b07f6b3a62447ff7f48ad1a09da8b968041018801c61bb8f83d312ff807bd34ddb08132100b186ee0b754a760992f681c2260b481d32f4fb596db647c370301830de5a997875f673c598db4b741e3d0c05803a7329e92372a4e022fb830247f80a1063ec4cfb94d6ee6268ba681ab89f599a496d22112c3051868e0feb285b28d24e35ab600e30c9c45aad211a692d000c30c8cfd071962d41ad174712307a2d18243a30cfc7b99672b4d3949ca910c30c8c0e6fb243379cebf51df301b03373ad2bd2e26494a4462e044040f7d15d1a46a65038c30f09541c8b6104769a81b0c7c753279426e48a9319906185f60fb5fbc62975a47fc6b24046078e11113c9fef9ffaa3287d105b6d428d51f5754d0ae6aa4599a4ee003341200830b8c48d7a98269ee18ca13dcb8a10005cc98817c035d74d127c0c08c195be03d87de9e28e1e96987a105f64cc7d041ec3e30c0c802e721079545971a4d8f1d16d81c796326216f185740e74c1db28bfd56e02a66f68a0ec96973ea0930aac06bce6221a6f206993354e0aa468b0aadf89a9d62028c297039a4d32a414bff27111a0f6048814b49640ed31b31e8e93900230a9c087a2fc4d39ca43d9f000c28f06a6a44ccc94d5d502a3304184fe02ba69096f49350e67921c070022342321d794253232d078c2630f9c72af37ba8a568aca1000613187915d4af36a95cf5daf8c2d1043096c08e96fa9873df35d26ac050029faf3fad093d31a5bbd448c3190148f068059bf52d66ef901923bf1eace06aaf764210b9c72a188f9ff4c5948c9a6378e0a10ac62a77084a4491f719d3399a0137f04805e321b2c6b0efaed31954b06dea962789a6b47c993123070e270117094fc16a8af92586a89a6448c1c0c3144cce7275f3a8ae79548f52f06ada31a4d51f7539b413f8000d16789082bffd0e3dd31e09ccf01805eb39da26ed639d5b4913f8008d2f3c44c1a68bf729df42279d6a90808b1b34be7037c1bd4728f8ecde762a4ba06072e61a75f22d9fe0f45bea88dc9954d248199e073c3cc165ab77d027833c61f94eb0773a832cab0921c939c1b8baa88852f24df0b9e2080daa214df0216c7754b724ef0c658293a63ab2a6ce23f53e98e04ab9e48b1ad44ab29c4bb02151c342ecd38b765b82c9a92a497ecaa2c6dd4a30394d7e68087aeddba504374a2d26097253fc5d27c17f8dd08ea0a5e67f4a82cfa8adf7613196dc4d24b88ffeff9699ef7e3190e0e4782c31cf1174102a8f60d7f3a71ba5c4930c298e6035d46628651db61d6c04971db7da649fe7df8e119c4a4149b3d32f82ff68317ca32711324811dc774e7921672d119c8e57e90c4ac8eab210c1a9a484d4f862a534bc1a695e3c0dcfd13772e038880bf03804aba1f7c1c79432dd202f52d080b3f430041b736f8e9c4699f6da0bc1e656cb1d25d64ab6122118f91d6f7d62e6cfb11e04eb112be9d19f20b80c49738f504b1de225106c929982b0af511b6904045f7921f7e9789327e80f8c6adb68c7eab7d1cec30fbc67045922aa059dd85d7af4e13cf8e0b1079b31060f3d9c7aa33fa5e47469c005b420c7068f3c9c071e583d3df244526d2a6f907fe1450abe01382640812db4d8a2015a64200236328064021fa021804a78dc81bf0d6e5b25a2259196871dd8fdd7f50e0f41e3df1e7560cb5377ba9c3f5552e2c276e0410746e926d55deabf23a95c0088e03107ded42c4ecc7d3284ea75f09003ab6a31d364c7a0db937160d399507949b5396c64408b2de0c05bcee94ba998ea2f21273cdec0e7ce23fc94d64d88f90624e1e1063eb5e670ef5c9a438ab581bd931f6a4c4653a173b4d8821cc2830d5c9a103fe4f4e59f7d6468f0580397b32521c57cf2070f35b0d92e53f576877c927273f048039323270d41b99fb7bf72f04003939406952f23581eef68b4c1e30cbc66cb6baa63c94b9d3703139210adf71d595a82cac0ae07e193840613912032f09d95425afd6360f2e4cd21aae7564c550c9c309df933b7835fb084810bb5b2d21002031b9a2f495745be53ff17d8531aa4b5e6eceb96e405ee5255b6d6dcd0340f011e5de0367b68470f1616e20227d2eddc37d85b6074eedc9c72b25a60ec46d5e999ba9c72cd0223ccf46de5a6e9742616f80f5a91b3d66fced75c811f11f57e5267d660b202a355edfd29248f6d972a7059416d95ec8c1a3c4205ced6f2b628d3794c810f3993f6bc9162324f57800a1e52e04b480b3d5513a4f5ed110576f3e514c735a45d4e99c0030a7c861063984930eb9026e4c2e3099c9b4e22688eceaafa3b81dba429a7984a9dab83155080210ef36802579b635df44d954ce916e1c1043ea46c9aa563e6b10446dfaaa6bf4a15aca246f050023b4282dede5df90725d982471258f7fe2816af3ddd5624703bdea7bfedf4678c68c1e3089cd064fb7d2d2286c9088c29c91a52d4eb0a1e45e0236e6886c7495a821c041e4460834c49176c73fe7beb31044ea60a91738a71250979088111312747b5adcc23084c122aa44cf22a0f20f02121c7b6cf4954f7a6465a0b6ee45063c3e307ec7dda34fe39a66e30053ee0dd83728baf21dd644b8db413a4007909bce0225be0d1035e350615834e212c49be465a5981070f98aca24b34da6ae51e3be07a5dff4ab958238d460b6ee428393c74c0bd8f10aab27257ea9f032e5db598a794e57a4133669475c10307bcc6fe2e3d6ec07e5433559bf2353da7461a0d1b393c6cc0267d6f955c9e46b68b9c056e47c3a306dc45ec51a5ac2d66b088c08306fcf7a6a74ee9e324b8d135d02c181d3529ffa859324991460e1c292841eaca400c59b0f9a2b3a59be853da353162c1c64cd66964ff38a41003169c46ba9c41ad826e88f10a2627156a375fd4158cf4aaec20820ac973e7857371a3015a6cd182193368588c56c46005e36b1152455256a95b05d7d93dfb3e6570f71055704aa64bb13685f869cc18a960c7dc3666f7a9fd9ca382dd08c94e6737113b055f6661f984c590738818c314b9aa55e52c8dd1d42ca34ed618d26dd6766aa499c0868d1a34ba485cdce1214629b8bebc3751ea79498514c4f4be979a6246c19b50eaf52b8735d2708b1a39b8e0008e2eba5080165b68a1c5165a68b1851618d0220311d0824999ab808b220a46a407b5cba6a2878c8682cf2721265332573140c1e51845e5ec4d9a945e7d88f10946342519ef3bd4b37ec4137c0ac9b736d6a89887189de064ce75fa3ce48f5613ac430c4eb07e9e3668d9654e21493818b07088b1093ed3636a11a62e52e50a1b626882d7789692a9a568b14506228003c71a62648295e456ee27caf276d4628b2e1a139caa84549a218f5ec9b900a315625c828d25d4a5b78cf55a1e4b1435a9d363414670e4701c38ba125cb692b13b6f55de2053236d8d023128c176fab84954526692a449b0976152d46411de16920497432af9313b4511033122c1a9bb2074eea0b682ed376cd0b03b400c4870d952ac8b56e3997edc0208311ec1a70f96a643e893f96b4770324cae5725a9d9444412a3114c6c537aad47a6f4d931825127db2f4af61c82ed050d37bb8bb10836e91c159328fd0e221e85188a60cc738a693fe2798960f74d88a4d01ef48b880836289d79b2e8e468216bc1a1c145dd03ee109c8a95b7c4daab9166086e4da84ea9757c73eac210a310dca53549323b48fa3d1182f114abb64f445cd42fc41804af6bde71b437444e108c972a59225e786bcc6ba4adc0861781e0be53d49ecaa2e91ebf91022fc40004bb9e556ff1b56bf207fec2bbd2c8bdb30c215088e1074e864ed0aa8c90009f10a30f5caab547d172ad97748d5c420c3e303a6811e11b74759a7a0facb85568868c9ea3c50fc4d003a3217bd6504d4b22796083f0902e3f2fe95c1762e081dfd35c53c29412ca3bf09691ded36f8e88fad8e18b22aa73fdf31a69756077e455363b9d0e5c5fd4117aae29a84cb939f0f6197244cfb9dc532907ee2a6dc516370b1762c48133f5a733f7a4706064ceafb42b421be30ddcc4cefe65b981bbe0be9e326d884fbe0d7c1c25b24ab610830d7cee0a524f6de7b57e6aa4ad81fffbecff4ce376e5500397634bae1d93c68d1b9e062e9d4efa13cfa0611349f7ca83e40c7c9410724e976fd4c5941a696660e4e94b2252ccad0c6cbcbe9c734ceb924a9aa3b9487e821b9ea39c200619585189227d6d53236d0c671e59694a2d0656638ab9c9ae114134b00431c2c089b098f64352cc3e18f88f16169479ab4d8e91408c2f302aef87d2ccb92d6e9404de8517f80aa9dee3b9f1235d606c94e609e5f5e29ae4029b73cd6d4cd828114f13630bdca9f10c95a216626881510b651d923784329d2cb0164310125c628658e0748a3997a8a0235ffd735760b29fda6ef2743926252b701f2aed2c998a355e57818da59134354becd1392a303a07d3f9da3b2429b11a6953e0c2c7ff3a6a6ecaa954234d0a9c950c49a7ccbc4446a2c0059d114385b4af13d51a696b218801052ed365ccb81e47984fe0b2b86aba31218244538e14d810410c27703a66a769319926b0a33d3f54d289099c6b6b277dea2cc325303176895e9126253022b609ed96df22799e04fe235b4e503b2652be91c0a5da65151162e63ca523f0f6499d5263954da79206621881cd74559335bc7b3f5223cdb010a3086c5ace7fb7691a3452fb8d3b33821844e04352a9e933aa246b085c4a7731e9979043525c084cfed1e29152a74d9fad915670e4c061e30c023182c06afddff5be8554c2d222030ec84004baf8e2040f68c08c19ef89860abc701578c176851840e02387b5c6d5fed1efb161e302c609317ec0a59b6c95b4e9bc9bef183ee0eabff7278d27460f38d339692d57a8a044bb80183ce04d73e59113a4694b290d2b7a88b103463d3b2fc92b0fbb910ef84c32e68831b988a63a07fc6bce5477fb49e4d662e080338f9c5493f73760e409adced33f1862d880039c08cf9b25e97bec3e158cecf15477f5a5af4605233f6ad67442648af6145c060dd994b6ce501353b01edbc264d2e9631d96825196ddad4a65fff6470a3e68d2ae4e3712832419551b05ef31d7c79052ca8f1aab91562307172b30d645a30c5170a9628c1d42893a335d8db44caa38b6033242c1574a224529710d3223a0e03ca449572363b02ffd27f810ddd5d77c927d9072514c8b0c38c0bbf8220519b8fb820c4fb0994b748a87529f773a199d6092f0fba821d489dcd5066470821beda5bdffe287fd65a12063138cfab497fd2fc752d76568824d21fd6eb5780e299a8c4cb041bbc44bb2490626f8bf20e4e4d23b694bc4041997e0fc3b8752d7cfb5ce58a212ec45d7915ae973ccdfcba0047b27c4d742f2c89804fbaa27d126e73224c17f8dbd28cfcf2163908c48305a2a5f8f4e823e2a03125cacf348224ddc2ec87804bb7efb5927e9e4b9ef0826fa9b66cbbe89a7f346706bd9e79a274670275f456d1cb9d79d5d04f717ab4aa915c16529a5cc3b79ca5ad944f049e93a11dbdc537244f05555715b63a52c113d04679f4fd4784ac92b260dc19e7a89ff1d3b3db68560624413797dc44ea247083e272539550461f69a41702129e5a9b7b1bf4e10bc650a2a684fd5039c2023105c7894689faaad363f37640082af3e15f33af687468e147c91e60f6cd75a249d5367b3f2981fb8cc1941c4d5f4411fa5566b5bf36b5c1c4f34bef01b5fe05824c8e0039f2b55ae05197b40aded84f25e53da480603197a60edc5f4b49d87a4cf3379487b9bb4ef3c92aea3810c3c305a53e4a9ac216e6db0469a17ed5d2075818c3bf096d3856429febb586d59906107f66a74529d3585a424c70b1975e03ce4f3b428f21a8932e8c0765f2a33515a3f9ad648f3c2067216b8c998039719d6d9e46a726074f342551c292a6e70780964c48153aa72678e1fd93c66ec0a32e0c09a0875a5335310163926e374c173bdafe3062e89147c9372977c226d603de22515c911e375cf062ec8f83942eef1d0a0b30626f88e760941d6e566d5c09d4649232ff4594cc934f05d2aa297ba0822f468e05cd2441749f2abd233b0e9af36f456e4aa57ccc0e7a9feab495f414e481978d33962b628f53e3224039b33e62c495a8b0ce518380d316ed5e8ee754a254186183820230cec6d8b16111fa920030c9cd20d79a695a1fd5f20c8f80217f2b95712ea933686d0f0027b1a3e3182b264e0ae0b8c12215510dae45c60359dc817af94b6c0e8a79c1bb5d4f77aa405de5cdb74446ada484159e0765fc732af57e70e6181bbfb492b5a934e10cf15d8d4ea262ae5ea50aab502174be68d21a8282ae25a054e0615d5be4f757fe7cc9821830a7ca84fc1c44598104a3a05b65b35f6a4cea14d4c2970e3253b8ddb8b484a13053642b45819dc4424cb4081dfcc1e2a8ebb2425234fb82f67ee04eead83762a4fd9bc824d6024e75869344529e5392694efb3658d29a42c81db10aa4fb608b5053294c0d6ededa72f51d1b93256909104fe5350a1a35208a9420709ac67b869485abaceeb2338d691d35ea669047ed4ffc9319d9c31e32c0e328ac0b85f36ddd490085c7f705bdfdd5279913104fee45e0e35bdb0b31b0d3284c069845c55d922230849ad749dec723287175c68e0466f607b200308ecc4aca53f758a3925f90363704b63e6dd1753b620c30736c368c8e841c9e0017bf162d555b56ffea406942063079c576b281d7453788f3366b4e0460e2f3c083274c06a66cc3984a0e1a6fa1164e4a0dc7e3175ff8e051938b0b345634e704d31fb1b6c29659b670649deaeb962c84f8b20c49b820c1bf09ab531638990242765041935e0b209194b8aa56c70c181e43366e0281cc8a001972a7808edaca79de41fb3e084243522869cee8490f990056b23729b50e1971c7cc482512958127d13359b5748830f58a0ef6a820aaa57d493b49a3ccb1e4d57b062a7720a3ab5db0a45957a48c242061964057b3a66cc1fdfa4986a6f7cc102fec286aee224355228212fbeaf0a540a75de49fb099954e03f298f28212accb415c93ae8ebd66e9080d8ad59093e4ec1a78ed0a31282d0112153707b766934c54e29188f144b7e1c99b4a73429b80dcb2958688ba697320a46e9fc60f92a9ed3591fa2e094c79f9433fb72d3130a56744e4a59e6a0d1e20414dc79a83e0fda8f4f1462b28db1fcdbdaac91863a840f4fdcd9ad734d35937a1df8e8c47d7082ed3ed11925d5fbbbffb10956c5f24788e62e2ab626d89cda749490d68f4cf031a78da619417aeeeb295070021fa021801b7c6082d3a172723b9d3ab1342e6ed820c11962fab804671da3c6eca492363dd6489b3163c68c0e68b1851612c08002dc1b90fc4af161092e5432f5ddd6a07f3c3492175cbcb1c0d3096cdc6840f2828b06ac81e2a3124c3aaf9843997f8e9bfda0046ff6edfaabd19304f524f8714dfde9dd2aa693241811c5837787de3d8f44824d31b9566ae7cfbaf003126c2ea5520ee54978883e8271cf9e2cd3bf594efa8723b832fff8fed9f4d108f6745a8d765c8b91941f8ce07227b73f09f93a7ef063119c8668ca4c89a24ead0f45b031f55b5626a5f9d6fc48041b64c829e59cb4acaf43b6f0810846e555d08fa1ea53c80fc16e4e272187e8c3106cade68e8a26f4c4d41f85e0b3a6ca9244559b90dd072158cd49f976f613fa921f8360b527dae9d54b3db9fa1004db16d4494c8baffdf98f40f0e1c9e4269deb0720186dadbd1d524c117ddb2f3efec0a80d322c5e103d513f303e29650e1d7699bcac83c1471fb8b8d7975797e927311a7ea38fe10337d69de4988a8468e11e78afa0df752ff8a107ae23951069554bfde9e0f8c8036f1f2b92f094f4c64e1b5d9032047ce081efca6b394dcfc23bfd710756cb4d6f527f69418ee6c30e5c52634adde8e8764ca903636d52cf723a912fda0f3af0312499f39afe1f7360443a2d3f21ed36c6fc871cf8afa064b2ace79dcef2110776829d483929992a09990f38302a26a1e3e8c9b13ac67cbc8151dd793ae712a279ff0f37f021ae96c689e2973c79818f36b062ff914c722af92967cb123ed8c0fe27fd1b542acb7c9f123ed6c0c9d8a984d20c491c5cac171f6ae0fa4d997e0abdcf7da6c166d4f840032f9a9521585ac82dffe30cdcc99824050fd10f33701e296f44cdf6471958eda02455068f9653f0830caca4d8c9d5c61f037e8881099af268efcd787dfb4718d89c967b9f2fb5ed8a3ec0c0e7a0524a8a9b4654ce7d7c81cfec761df32e5161faf0026feafea22eb21f5d60cc3f075315838841eb35d2f04a0d3eb890a6a45649e70d8eae912ff8d802efef9a4ce87e2c65f91a6938b8282df8d00267e95e35d384605b1678ddcb11fa29c898597917366ed0c002ab959b4b42eeed3ca181830bc2828f2b7039989652d32935d2f0c30a8cb0cbfadb552724c96ba4e1e81af751052e26f389969ade23bd393ea8c0a99c838be7f4a9c24a16828f295c7d4881539ffe63786f28f01105c6b5cefb92e9943bfe8002e7aba669924ccbf59c65c2c713780972926f50c92685c8096ca520d4468914dc6296081f4de0ececedcc474a0e8f0be183097ce50b3afd268b153f3880f0b1044e54b99907e996dbb4317c288153f25ce28d6b08258307868f24f01394298d6216eb24db850f24704974344fc1f25d2afbe3089c9d7b56a8a7f248b9113849933798e5ca471118a51db47eae8e296e0c143e88c0462e2d42771aad1ffd6bc2c7103895835d8ed61b5397324008fcf7557592a3a2dc844a193e82c0e8ed52f23d8af85b3a0c1f40606f54cec1e266d3d3e7fd80310f26477f1041552f1f70935f538af8d9470f88de1953cd552cab32b6c7f01c644a2aad91f67ee3cee40a3e78c0beb678a50ea6c55fb4032e551295830c2a4a2cb50ed8108fa2b565c9d19e02143803906f60c68c1933707861e32307bc85a45719f69124c4940b1f386063d6179d379fcc6a9d0ca4143e6ec0fa96fe98a23fb796f861035ea4a80fe22afaa8011f16aae3ba994c42947af8a0011b928520ae51fc63d299055fe7aa5e2a043bb1943b78c88211a637c87a3b8b6beaf400128b42a98d31e8178f153c60c177e8535afe1921063d7905a752b44eca21befe8ece156c0e52fbce621c25540c034660b246ea4d3ddfd6eb8bc0d9e438923de514ab9e08dc55bceb9359a6213f04467b7fc8dcf974d0bb10124a973e08bcd65f32ede974aa4060358a12aad631acfc01db5bd9da366cb3877cc0dd081935633c1573ba075c44d752625a76223c60bbdb2bfba885d276c0be08116c475d94d4ea8037e91fff4348b12d25079c1275e341e898f7c5019baafdb5d2ed4e94dc80094a8688faa7367fd006fc8e8b0879a38abed48051a35bd9399632b402d080514987fc591b29f7b3e072c71169b34f845616acd768659094c782334d4987da0e0bde4d4f92a04fc96bd32bd8db20fb3a23858b5cc1c65115720e21d3d256306e4944922521c81c6205ff12ad5acf4c4d735661b2e0aaa9fe2655f09e7f6df487a454b0112fa994dd3c882c49a86054cc1e32675db749d22998ece1a7b326b72c319982f5b8a9a28fe54bafa352302a640549dbe2dd3a22053fcaf49b0a32e42073340aced33f9b3ca5ee4b098982b3e0b14a864877a38442c149d09031e974b92642a0e0c736c5fc63a15f1ff409eed426a6679ac674234f70597db5bcfb62451a3bc185a8ee649ba29e082758f3ee64ff7b3a68bd092ee55431b5e8e4f94d4db019fdd3645e6a4f4d2638f59758ba2ca9fd10136c0e6e962aa86a3ad9253879273f830a5132a7902538b7aae41f9a45f44ab09f16646fc85322bd9634b7ae3e097ee3faebe9cffb133f9260527e3a2d6a234acc23c175d61c2dcfb4165a487015761f43c8a052c4a047f0174476915b11a3ba8e6037e6760d2286598c368293f43ee249533a9711ac9e5a456c11a96d2e82ff4a4205957e2ad75404a74ac6dd74ffa85f2682913104d1a33aa2990c22f8daa04ee792f9c6c2738833ea7bc7102891b1f772af5308c6ef53e9484944ca09c1760c4aa8d1f126c02018a55df42475775abd092008cee4673a61fe23e4a809100836bf490d299dd4baa0260020b83eef0d162d2c784e13e00f9c68eec578e9cb33dd04f00313f226dfa46356fa7a1d40803e70fdaae555793d8a960f7ca89c83c8a7ddbfee1ed8533a2664ff27c9ab1e182b8d225cbf4d4c9a07269e59b020647b87140f8c502a478a3596fdd33bb0218ae6ffdcf4aba71df87fdb10849e75607469124ae7ad9c7d3af0a92798a64f4977690e5cd0294ab6502b21c6c8811bdb507a648d3e691cf851b5a3e3a83c2584032333d3a95fe40d6c599239ed836ee054ccf27b450c53b10d8c46cfa4bb535e8e1f1bf8f4c9a37e1b9244afd7c0ad5e4e509e1af8b48dbdbb9252779706ae425c2de88b896b8d06fef365c59c381e72aacfc086dc78d994a87863b51958fd98eb4fbf5344be0c9c9b4e4fe6fd92813753e331df6fde7d770c8ce7a9241649589eec8a81c99fe65a22dd30f07e2295debed62475c1c07632f97f7fba5d93fb052e6e9eaea02c45af5c2f70be25bf53ba0ddd97ed026fa6f7d2225ab0bb2c17b8b4e69daa73251d25bb0526b59b97855467fead16b8cccc9723699294b4cd02af31fa885930192fb558e073e8eb9ca87fbafd15b8ac29079dc5ad2e7b2b30229e43b036e9fdf12af0df1dec6490ac224a546033849482f9a46c6a4a3d8d9b72ab921478d39bb3461b09ea465160db4fd53ed8a649395060b37c54f0c8392ba69fc089144aec4bc809ecba68f8abfdff649bc09d90e857f1e4df4799c0a88adfbde5934a2f4be0fc42e5de88d257224a606cd773b0d34147962481cb4f2aa2aa4dc5844860f4a5dff020fcb67447e0f45a963693a4533d3502e7172632fb7cb3e6b4084cd0542d2aaffba54d89f07b4abca0623f0436598aa2eb34a488bb10b8917ac1f4aa53043d089c85a85dc27320b0eb5b17cd6bb3b9e807ecaaa94b0cdeff29f201df22a3c9c8d9267fa807bc5e92d57f5726f38707ac8a4e9a4209ff08393be0f488a42dd557c8bf75c004616aa7195295da39e0324b1635ca2cb969098003d6c6a4e8e9ec66eb12e0066c90b4b6b6173b882c016cc056b0983ea5a8854509500376cc2f5945ca909f24000df824c382e6537b92ca59709ddfa53b424a7e1759b076a6b641c60c522bb160537373b5a5dab44ac28271ef60b28469935992af602bfa75bafd75c82d5dc19767325532877c3995ade064a41cfa3cd5534a252bd82832e56e92af828ff77f27732d784852056726fdad3e948e3f4a05639bf756648aa71ea182cde5162258c58b203a059f47f209a5644a326f53b057693de7c71cdee952b041e5181e942865272505ab19644753fb9c951c05ab9e7b629be40e188028f813e969292d9a90985070f9b369b3112aba33a0605df3080de9ad82b27c820d9ea2acbcc4ed2a9ee044e3998a1576824ba52767df585fae71823b4def29a538b6fb26386df17b4f838d95ca6882179d420a3287ce142d93894e49ddf7cd31c18810fd4bfee8a5892ec14634f330f75882d5887942eeb7128c58048fda796b2f4b09d64b42889663b77b26c1f6ada5e7ae24f5488251da368904275cafa4e45ff59c16487035bad56b928f607f37a7183db9a4be388213b2bbc472d48c498de0f2ef5a2df3525c0923389d2ac4f4b499b4bd4570a579d3798cd1f46e8ae0bf2fc6cfd024a94f04134f5d4cc92cf305218249e199cba2e6113987e0437f4fcadbcf905531047f52624816c5358d5e08c69274fdcf55999a44083e95a70f593364af67105ce6da82e0b296ce9cd492403021e8da8b360920d820f9435dfa482195e40fac961a651994702d11e2076e74aa3269e5153b47fac08a12cacf54be897d113e70faea6a748708d1f13d70da293394e4e49a263d70d9572b4d06397ea53cf0ba397a7b42dea04a78e0255be5a6e5fd7fe80e4c7c5f4d13f3b8396407fef692a759882d1eaa03ab55c2237be8c046fdfb1c648890b4670e5cfaece6e37127e7550e5c921d446853d58b6b1cb8c929489cb89722513870912708cd19a2e4ade01bf894e9bfb265ee0626a8a50b12b462d0126c03972a752911dcad830ed9c0af7f3c0f31e5f59ce11ab8ec987b29c343c8ba1a38fdb739bf47358ba932810fd020c100d2c09659ba9037d9b77fd0c0a88c1c84ec4dc163e70c6cbacba944e47676d70c4cfcfc94acc192d4b60c5cbccdf849cc92a62819d8bdec185449ddbb730c6c4a691e1273c8184d31704a2b881034ab4ac78481ddea949426ffd5b380810b1e3d8bc48e6842f805be4b822a617b810d6d3a5a43e78917ec029bd449cd1e6348ae8f0b6cc564a62fb40546a50b6943c94f0b40184016d858395c6428a1165387055eaf45555cd7d45777052697c7ee4f1983bb6f0546a89b8a14325ee8be0a5c341d4beaca2df26e546034d9d88754973aee53e0b54308b9a69b774752e03c6ccbe2e6f5e09e28f0d1ca74a6fa483a7aa0603372004fe0b3ed56d079438a41ea047e937abcba4e96f45e9ac04f2e254547caa55e6502e7d9efd746dbac93b6047e4350358f9272168b2981c9196a2f51f34a12b124dc5929a8a44c6848602c990613f61ad2373b029fafd6f92a42cc296404f63b6e124ae494497e8ac05f87b2e021a6a63d4418c0106c86090620044647b030192369d6741058fb1e151d2d6e9206022f4a74fc983b6e08fb078c48e2f53d9a6f93d6075cfe911ab3d268d5b607bc9a0c5db595079caa0afe41f6968cef809131bcdd2c7e854cea80ed986e25541c5d269303de2f8ffe18623a6d161cb039fde37a5ad0a32637e0f7b73f834a9d43fa6cc0c9f7cd394ac6b425ab01b79b7a5574a6d8e901d08011ebb4ff2131df320b269d5abf0c9a6a4177a11690210b26554822a5309142751f0bb6ededaa72c5b0711d169cdf7755f06841a8ca79057ba2927e4ac2e30a3e7de6f2fe2b916de956b063aa6962bcb4b7cab082f532939e2d5d7bcaaf82932cc1335a44ffdb8a2a028daea8b4b065e2481c0e0643c150300c86e193d305f31308001838268e4582c188a0c9c27c140005462c344e3c2c121c2222101a87832261181c08048461302818080302c140201c0c0fcb0a3d0f70ba8f5f491016d864f976f89f9f6cc6117429941c168278c0b9b07d0815e404de0c45821520e670e1e5139aceb8b6510afd64fcfb39e4ce9fbb2013ec291e18f7c24987cb2b154ec964de2daae97b54239ed0ad7351dc81fbde55ba856e92bb72ffbb4b17c79a275646910142c82f341eaa41b97b68e27ac682828f1c8ed83a29f60620933f7c98077ff8c5c23660953125fc191e84fbe020fecf5a5be93f0b2486d02df86a78cc77b6b8e483bdd94b2bd69876c18ea9c3e9b8bfba31ecae906f3034c80c7a060782f402fb1d7c662be5a09df06fa80eac0bcd3aae9906d46562c1d45d0c3f2365681b6c0181b3efa37c48116e0115b0e65eb8022fc6863fe4a0d763f8b5de50f777e8caca8096f7719c8de7cc137b751e9237f21e3cb55f4d48255409658606a28d3c6a5412880d5143821c3e4bbafbc486aec342c3dd306a78126e0943c014bfdf2fd9ffbc768945ee80761076b837d7b9864e17dc05fc42e2a17a082bb419ea09417942cfc88b78ac3db41025e402cd790bef83b7f570dd4f45ca0b35c1c20e81b9503d647a987a15f4198c099742e804a3b7063ce5b68c772b397239073d8303417a81d3ce2f0b9484a6c08f50cbb5f9557c4a2e5edb70cee08a250e1e1d374c4933347bc6402890220472ac522af9fdd060527b71f1474ca16e3143847d0023a34a28333400f989c245b4c8f72f13320909325cc1b101a820efbc43c2d5a4655adf62fdfc53d0f5b676af1c79cfd00bfe584299a141e82fa41eaa87cc1aeccf08436b9da1e03067d8273c02a7287f9a631cf4bf3f725e3cf5cfc5a076eaac93f035292c16b610ca441f2f119c84a661ab00cc102774121209354308a15dc876c2bf01dc48c5f7512f285ee09a81af08882f2eb120a570cf3f5ac4e9a94703e96d4c5ff2c32cec270550797e4294904b8893a0065426c1af2b1c83ec1fb8460349ff27e0a12b72a9f95304a5fbc2d961f4a20629415cc068e654e214fdb546c9c891cee1cca69d54c4a07118669813ce09e1de0915941c0ce00fc382eba0d3a06cb01a44014e82766a5c03693bfcd7112c0272d12f62eea86ff4ba7b6fbec6b15e309a31356a6562f1ae8badfb56acadcd8aacd5cc747f2f97081490c712fa1c5f8a39ee7d7220591a2265feb4b715eb5f0e2ab1021add3c48f29fa4b4f451ff77bb3c8e8c08701c73b9772e720a97b8b690712a86b69c3875324825807cf72b681be8888ad73b25e053ec152aa72a4f20447dd9e25f5c4f910f5f588d9d391373a491bd0f4807514725378a640fb1cd88b9917cf0329a302a1ba8b535dba013232201cf5a5b87e2974edb1eed80a2aad8f699e8aee1773c94cf5df2063acc00c022b9b900db54fb2fb96be32f8bc82d66c77e572182a8c777095db6e84879f0c1c4b91fe6443a4ad5e4722d66fce13534d8d5ca091de539a65b59985e316c2a8cae81fbcd2ab326f80100310aafd155cb3bdcb99ad4ed9a9cc445b593f849c592be9de4ad3ae91a3ce030e74ad4a5e55c39d6b5faf9597614b79322e58f91828ee657da92d9531ffd93648bbd4a812cbfe527ba142e6b4ed819d968642400a1d9e8150b031e7653e983d2209e94f8ca91f889e33ff6f624ae3989c830eeed45ef2dba9d90422194841513e6a2d06942a515724111cab1d812ff6057560d02cf3f4a90b1ec6bb134bf5bd0732520061c29e3113b1bc40f61607a7a08c1848c4aa153b27c708c8aba0d9dbc9f713e81d7ca9032d8af3396d032ac59ec61a1340280c58d192117ea89176d1b7b498168928e093ce7eba743e3ddbadb95acadfb5ed98da9af6ef7a3db279ce88a6a7e6af10cc06cdd5f2c32456c144ece54c0cc9190028d80926471a24623796b91389ed04f6c6f566d0e16ddcc8959a5fe8e509032ed539b38d83c9c803d6dd24db586f08fa8f2eafdacc79eae9d06860c42e295e0d05bae40b3e46b314daf094539bf80180c0ba573ba1835ce2964cac961a85e3c580c5feda6dbcaa94b5ef2850c357302fc11dcffec3860800fca504e407b72fd923b7cb1da25148ac503fc90d4b3b4e0464ff9ea0dcfd58dce9ee043c6f49b1ee0c1e9239c8c5a547805f3d4504601493944bcf9307907d60182d40802b9a1feb9638a14165650c081d046848e0811270465a18a423908f9115236217d0a9cde4f16ff327c68710f33dcbbd31c94af58edd4686cefffe637aefe4b6a405483c7499cc2e07035820a26473d083cee6b561c953d1a211529e8d3ebe7407ad73fd316328e48918d144de870ac3221ba37b63165834b3125ffeef762e3434340140c7273b808311b62dd835191475fcf0f3aa7c5aef663dbf6b955b3308e298e85e3acea2ec7bd46a2f949d805b6492bfc63818d05a83342614185ae81061a9a299a2851a5aa490088d80cae64deaffe05217fe3b47893f8664bb16c2835e803e8e9d1f3927aa60a5aae012d3c5751a50295019481192141219ca4ae09b6469bcb85f67ade63210000e10b4c56ad48c66aec4a5b1575b37196a23dceccd8811d41571ec328ae81eca57a7dabe1a4f635b5a10905ac8b32611af6ec7c027d574a090559633b95de2ece656234bf422f2f9621e535a6e3940b322791f5f99c4ada6ea2e1ec3e5b4e715a660a646a68faf9cdb92f5b36dd7d602bcbe6cf4390bd87626a1618c7d9751a3f40a926835c23d525905d5d5be3b9ad2c6d8d02256d755f6692f0f710c054064598e0df5d47e94192eda34b7c1603c8732d6deeed3e9efc0ffbaea61e78ecfd22a2214e25ce8150096d90ca5296849ab31629926554339c101dd6f88160be726314416170260887ee0f4834dd18684c104d2fdaaa53e351d64c034a730af7c3d3c8ba64a11d6c121aaab4919fa90994ae1d7443dc4b69dbd7daa9054592532b53c10e36207650e0630c1776d9fde6ed60a97a248e40f4a42ee555341d860e64a3d13955da00c5b9eb23d732a908474ad49d80f2b0d767f813add37042b30ce9dcf8eaac7d7f82f1100cbc123cc06206fd97e78d155decae172ae58a8f6decc3103587d4d0cdbc87722ad7f9129bb66ba0954f4649f4e224c362b9e31fa9f88538d8c39d3051d2b68475da531fc0094ebd60c52ac2e38170526fc4464d64cd387fcbd677f3ad0cf71031c179b0a5a1ca049b86bdfc1b55b8d926340e1e42c7e6d3f9c345f84ea7e75d7f9247f8e116d9099db5cb9d53ad727af78fe0347643149152c5e665b8b402a76cb87ece60107201bcccfea7e5df35d6ae7f075186a3531c667295af6ed3fad1e528325c56f5dab99cbb470d78c502bc7c43a63199b0b8c573ba3a3b6669dd25074bfd4e3e6b6241135cf710f0a496b9d9fa1f89d68504cf65da483262183aa10a9080a7f95c62ab7743b55754495ca2361141f35b27d13b898d4fe498819789574301584fff2be37d434f7dbe30ab11d8f603c4648e2a1c5e36ba86d0252a504ce2408cba2e9338bf0791b6d2c2a9af4da5d051c906d5b2f9e86cfe91abdb7ff281adf8f188dc48c15624d70f908f33d9d7e288797b41b5846f0dafa65b4bc8d81653346ebfa19f3cfec6e3c743c8306255eb6b4addecd55feb25392f55597f20db544e1dfd3260d71cb5d44563c657a365e580bf8df1f9fb3383ff4d1923012272d67427be0d275922c2da74581c7ca846600397a2e58ac1548343cb0b75d07a946ce2ca9f2cd5fcb7d3c642b1bbc4a9890a6e16093d3cc641443b7464584612baf3bc824341458ad8e02dab48a833d4b6bc32822a4c17e03ed5c4b0fc9cbb95968acadf06880ac2c6b14492faefa1769b704c89b202b27f93d3d2c675951c6f32f07d9f96718d55285f25be7973aa2adc73aa46587cd51353b1f17b3f2f5279aa86210b5a9c3b18bd4fc878020f0d2ded14f6011188453b857bff2161ba5a6c73040350dcac56f6a0611917619bd9ad03a4ab53ac8e737b327a2fcb86e38ada87f115058f64b4fcf2798fc10e690bdb636c2adcf0355b28b9b09110c19455d8aa6d7121b28d76797d2cc6ea797dab4b87c376127b7167d51d2dea67d7f288b4d10442a9a817140106ae9e3ad25041c94e40cabd467172548b8e5190b67346aaae4ab45253f2aa68cf3104d3982c2912e086d4681bc55984be9b8d2e578e69a8b2364dc8c56a038cc7d7783b5f67493afa521eaea6092d4b764854f9946100ddf67b775b42cc84245dfd1152893d2d11a3a28c42d52a72fcb1a833654a8a548d5854e725d4c09611b9f77b0356fbf4cd4aabe061134a0247667b231855219b21d394e66a3a8fc3b8d330aa1c60fab3379f1a93d3c2323c2369da8493e746af60547391ad642ca084c4cf179cadf35ea3495c2694ddb220395bfda006f6e31119c7e02d0d66cada4dade822b09ee8601c3a8d0867b09b7a3155749df05fa874d12ce557e3c9f9fb2518c31a1e86686e597e20a72b5a3931883fe62671244b3ebba5f291dbefb72fc9190ccb37016c40dcb635ceb0587a3758a6a3cd6543d5182f1982f0347fc3da6c69bde2b636f27cea1a4fe6d8cca77e0a54e961bb4a01164223862dd4181010e353d3ac76c414c3d2ba574f8a12e4d0da148bc0fbdf07f41f85b2cb699ea24c5ce5e8e9d0ec10cdcefae347e3c4f2dc7e1d36e182f5c3539a68c01c94a2d2616c626236b221ff9280f9840b299b1dbddfce77080029fccecdc0ca27826ecf4a2d2463ba44f33e3ab6069f4420ef133784a51e4c966cc107274824dd62d0caccb80b936b01dc483035a216c6c4a3c88274d5a2179f93c3c06b42c1a44249a90c60de23bc4314f3b316a0b0fa55568f668a8aecf609d501e29e9277aec0df93c809f9638c40d766a7efa69528bc9d140d52a9b453c939f6571c095113098ac0f2001c41968da1529510d14f915c25bb1a416e5de4fc818d5769f94a55746ce3b499e2199309f965543ea3a25cc87d35d8aa2e8528e878f0ab7bf9eb488947eb87551dee5515f45554a98d2edca78186a4127ff35ffe5b4650e75d940c2efa3f9da2572afac1bd884f1a87a6ca08cef4d44a065392a0264612ff45c229b3818c49a86d28f9ed3acc37400a2aa010450f186d58b103029dfc21d456be086c0f4d8aa5c8f75d79209c419c3a92dba7af37604205779f82885c043791c7ab58cfc4e5686fe831e62cc821d84a7a992d3dfd1f5528c0be028cc66a8d13b58b59a959639e428cf79b244e13b0143ca6c568e55000a3eab6220ae32552799f898ba569dafbfca371507e0799f2a873f1b8590dbfe9104d72f481b53e1264eaf7d617691560a823feb120806f0c38bd6f81d6b029e9fb0fcf1be4c6e8cb494f8ee4894ae4de48857a17a64ef09a06160b6a3cfd373f8b0564f6e364625a9f698e29c4770d7e58aca888c1d351af4496ecfb33ce83a385656ab9cfd8250d6171474f91b92a3a9f57312493ee2f895f6d2b591cb849ca9c4ddd2ea73e29dfc7a3cd7e70a9b08a601ca7f4efc11b1ee83965cfd819e3323459c45ee23bfb0914bf361928412b0956a130d3faff8a0a135eb985c1f0c2847f1a0dc1160072b506076f8c347a7381852e0083ae2989217dc6b735946d5e6bc029041e780d9d72b757e506cd4e08f5c7640c65c997ff331ca9a0585f63b76c38715f5dd4f15c82ad2e68e1e450d5ce7ac2df7d175fc65a0158dcd02dff4565d8ac24ceeaf885458782c7cdbe78407d742c099b77d4c3a3405b58223959807db53f9a3e62d9c1ecbe31fad605cfdd4778bddf4a4a8cc8eb5b4bfcd18880d8407bc56ed43367b6e37332ec8b9b293d5abd92aed8ecb341c526874dfb6d3ada44b409caa6924df96d72daa46893cfa61d9b3cec66cc096d72e42e2721e7ccc2ab7d88e336333af546f03374c357f152153c391abc353c518c94055d0fa1c072bc965cefc95e1c6471578def335f1a371d63f62217f7423ffa45e36d678c97c265598ee00701a19636308103c6bfb6bd220e5d7b71bcf61d3e83f1efc7cf9169ba9330c7a6330cfdc88404af186a05be6e90e38524a116786b4585acbd4026c64ccd092acdcad81640469df7fb0c38d690a107e1cf7a305e41fbfda779e4aa5a3cf11d3f0cf208d4a2d4676e2d123be85a9b62dfc6205ee359cc382ab5dde4ed6dc08fd39666181df734747610c207b2d71158fe8e75b64ac8de9528ef107d27ee5bdb83d88d6c328de948c0d92b9949b6c8e4b0ddd6662041da419b333240515dd66718226336a9728fb205414515bacda8e0e8888833465108b42a7da409b4faa1f4da78b611c31ea4d42fcb76736363e651c0ec1a097d47561b581ac3f580378ac39db31f9eb50cc7978e8a95c76dc702a4f83032a80f3721119e1a5c15aaac7379272073e52cc7079aa5638e3b0eb673c46c6031a58323be060cc195b388f0e45b1afc45e0df96f3ea6e2a955d97a47343e62ad193f6391301541db3ba6301b3faa16466351582d9444e695a5d178ce842a6bd6f7a92507c2f9243a3425df218082be8ec18f288384d2e073ecb93a87212bcacc7f4bd470f2ff7a4aa70597f52b4812cdb4ce79298246b2a6ac9b1114db1674b65f44012c6e204b51476eeb1879683190667d6e73a00158102033024595516da4def680cc36dda08f18113e6abf6124ca84915a7a810125d4bd0b78316ce44c95200b0f154699820411dcaf534c49b214ba6a218b991be40ab3b5e383db51972fa382deb818902d46f16ccaa034dbd26601fc97e33b5b536570db41eb00dea66509be6dbf65fddccb36194bf20b4cc371e0a080969f0fd65060bf0e64ffa3928e36ef5fd246cbce35539384836efd3d5ca23c473ebcdae16a36878f3cb06abe378975d565e0390c76bb50065d890dbd386d07a044f6354f9272812b4eca08bd9365b47b1be49de0e3b3886f16cf314641ffc15fc508f46efa923a094f3a8bc375bef412450298879b4d748677208e45769e5c8992e75a1f5642b2aad4d544bf02a72efd5d55e7ddd022f8b0909962a9b16600607cb0edfdbaa4b005b6a2468d51549d2cc3ae5764747baf8fa223eefb5e73fb886bb6b4137f7368b34a78fd16ac34eea55d574c339c0b9b48e37acb522f50dd37859a2573afffdd9664864ad496e8bdba7da1b36bfc3b626c5d212210360872760727ef30da783335fe8b1737c41017bb00ee45401ca59b5cc4eb9e92153326314b4469b3556eab840fd0c21e8ed94afc1585c2b84d973d007ac707ccfa198b0e02a4f1858a10a54a08fe0c67c9d61c280cb6a0b72649f15b6ba258e1b2ca093042dea9c5f46394541633b2ec86a17014d230bc6ecd105b753cbb8cbee3c07b59bb7c800fc0ebaa46f9dc0335ab5c13546de116806b87a2bddac8e24c609c0b6f5f9304197a0db82d6a3a4eb675212685b89d1a64972b656dc9c6b691f30713708628147ecddefc1a18aa860a8adae579c01463f9a362976d069aba9ef7580866639c7481b9ca9933445abfcdc445678839157645b6f9949b0dbedf07447f3b78b9352915dcbdf74ad882ef2c6054bf4d017e90383a1817fe7c9c5bbeb3c7a1976b22874570436d64332500b67a511d6308ccf9f1230493dfd1cd4cd03d3a6cc3ac93e14145b7e368d87ad4fa8b21436071b910215b571658a16647bc7b64e2758ac084821ed9f00122b45dcf4bac0039020299be99987d90443e68be7def628975479732951dcc4ad6b315e19519b3f7eeb329adb56f0af64168e669a8d7ffbf51d49a5e20c3a1abd676e73ea6c0ea9ac04f7cbe3efbc8007f219f69d05deb114d617d0ad815833ba716b12c16467222189bd1102ff13ffc759ead670b5b18868dbdac8a85750fde785e1a23def49794d5621f06f9826b78c9de596cacbffa02fc41a3e0797a160bcac175a710e61ef9e5fa8e3df44590bb2951fe048140cc9afc38a1db64005f3c8bf8006694eb8abaaea883a72ec2b002eb82d5d394c87e29754bd29403913b45c39169d5817400a1f924614cf8573904e5185fb575d2f4ced6d452fb7e8d5086676cb48efac17063a740666f0a2993ae9a816535792355a989eeb3265a1c62dc22de1a4a7648672edff76dac6724b005471ab3f62cb282f46b975eb4097ec61a16b15491a77c17fbd01163f5f219c1df6b953e4ea807edfb3813c1fb4ce69caaebd4693e26089392e6ba5b6ce4c8173220bdb8762ca36d53170f2f1ebc4cea5e71a82a1ae95f4ad34e81d1b01d1484fc24f4e9df436d0c73a204297ec1f716922e77e7d8769c61e94cf51c2ec5da082290d98c83304fb0060bbf7b33abc96cab0ab3117b1bf6b125671e7bf5a6bdcc532752be94c66d0c0090750151f90bdeb24ee63c26f540fe6020cdfc12fe2f9eae2af22a68d70b15ebdc4fe626a6a0de792d8a375e826f45a848181888b855b1b73b213ee52e0843550179e1c0a68c6b44b0ff3c07f309406e75fbe33d904d2c3c940c24281a6a46aced01dff8ff905128a46165688380e0c2d209a4a109b1bda1614a5fe380d4101eaa14780a43602c14358b6567ad16b58007a1a23d433124b0d888c6ca6fb8fb083f403010f3911c06a034c033b82162f797a4f99b7ea3b6a41c00053b4df0b803d0d99132ca704882dd4d20ee014e0de7e708f83e974657f0f6da0f8c8adc6455792fd900e69ecb115f9185c626d200a45851c6ee269deef68858dc0f5309ca795fd5806d6aa533e1d03383b27e1d6b300040f2be852349237d1ac4fe182cd00eb1a5c73155c35ee949e4846f8d95a89c7d556d082427b686b4fa1e5ee01da1a961ea52b7588836b10e6101145db80d327d0584393592bff00c37d0c7b0f6cd9805d1ce70bbdaaaf68c1753477dc7a175ae0a0254471ea334755e8f07104d2733614802d63b2772c7727e22a3d2e369b53f5a8eaff865a68f90844a65f474e0ffed6b9848286a93331f8b57a515b1aac94f4b1c4b75e99a7c5140efdfb2e82d2a4a08adc40b01779ccdedda1a5fe6a4727829a79a5d082742a0771ec88b82813559635d0e758ffe82c80991473f724bc61ab29665c8480e53e4d4bb24ec1da62984b4b7799a3c10a2c9bab4d2be89bd0b971d8af469aacd0673280eab61fef85d8119337f68795f02571822c5606e764a197534e24140636ee8299a2c66b4bc1f019f9aef0a91619cf8258f972182f4f0032c9dbf71834c41905879c655c06dbdc0b4f166f62b38f711c86952943947da12603096f5c51573db48947358515143519087ba37c1c07d6be56312e29414379b8c4e1882f82fc2da19e509e400167febf68daf17409795f045f1b7aa9a09a5c25f7f21e0074921f1fd0d0f7a35dcb91a0e1640aac10c004c131175f4e93fe877897378ee476b417b1d9a682edb8b454f803d681e7df350bf4bd9e082b48ad60e02a032df1586a11a4b4916e19803ac6a0e9b2f2ec24a1e0010f0b3e12d8cc7a97ac360c586bfda0b4487e1ac1f08601acffbc4f291016d0f510368a51eba8b8574ec2184e4eec92c311c486bc1188fce8027434708a46c2c3f21e23c47e5d5a49e9c20448ffe258c98258ca7895d89905580a01ab089161b1942121c3d2d154492e6500c30032b7f1f0734b2f007f97e089e18f657d045c830b8c63b159a62c0e2360a3081bf0668c8ad6004dd9ace0c15601571b14e954c64e65133f07cc87497d7548b2f2f0e484e2e7f795cd067c464686548668f8d81723b8427d8cf85d74ade7e3a1a36cc6d140f73cfba4fa88c2c7df0548762eca7ec398889d69e9440bfd278419e7bf81350c4ccf000f2e83a5e069e2839a6fae4f8faf1f1f24be217e5438456928a1835a9f5d02df7973421512e1c3a6f7712b583ebaf2c02764d7faab2971645037b17731bb2f3d408e4cbe348a730714c7942f01ea9e8187ac4b320e20c24199ed9b549900952562fbd7434579645c1788b96ee5ed4be32339a3135f52b209c90ec0fff9e1e26a908642c74a17b79081190970517153859d2029c148c457c04fe99d3dd1bac1961faf4316f2f30c221a26846cd1b0486802e63df3143f6144555c09349abe3b8521638cc37993c48555ea5f60de1f857bc366d711f17f6890ca5a22fd6ac4f332cbb5ff5ca6a9c1e77c98c06741a34499f44d8c6ae074715a0909283a28637c1f24f5e7381a1293fa8e753d36ed198e9ea70023c3cb8be0e7f7046382b1d38d61b4bbade954f907d3c420066a9cc37c5906bc702693d6f7e9d0666e00cf7d66aaa58d5097c1b63b20e192e11214076b048b7b23c765f3b379a00b0471a5a260aab879057cd630cf0acdaac222a53a9866da61c97841ea67050addf463d95efb1b46b06a21c717624864a23aa2c512c1087530c60549623a53df694afa694df24e9ad93fb6a2b5637a187d9bea30ea90cc0a245215d26bf8cce53497bd00441e1970918924562eaf68b547b0ab63d760205927ba5451acd96d80c51edfc163247c3f0d7fe939c793bcc06601dfa1ddb2274448c3a6279076df8b779753685122d2794a99a2598b14bf2c65b7a1645aaa157a8d002eb3304a59e0412cf8d3f8a08c283ffd9ed82409897e26f5c4828772659ab347e22d7e921a3237b51b16ea134909f332474f7cbb3658a9f57bd4f5e45812aa9767b7683102993db0616192740fab0809566d6fbeec361733b792e475d96b7b26bd2553d603da20f78400fcdb5965fd0505473174ef239cf39407c7fabae2f6662ac2e3ed5f07184584dd223bc8fe908a4ef10182ed367f32da45153e78851d45175cd954598b5263540bc9bc4dd2c14ff169b1d5aab148f0d0777c1c321776f0d5dea17437a111371e9e61bad0aac889c7a93f09693fb519c96b452a7e486fe9184ea4e906a35619ac5fa45ec82e07870f7090cc049ecc624f37516cd9107920635d45641b65b45b4a1ef581807774a49f31104e6971d4c71413156b29b14f39d5fac2180d2e6de38004296f63410232d95ca1c11dc9f5b546423f6e9dd8c6e4bb659b254bcc2482ad9328d1c44384237390af69c2b65413d703c9c9e78d588adc82415b55933a0d42d1dc4e4232039f2d369bb16ab214b2869ef1ff231d7f26ce8456fde1e559af494db7d3df30e3beeca0eee54c180e6c9397894c6afe93330ba25821e23aa9edb29aa939cf10eca3754da50d4c6038edb04adbc05d7638680318f5183cbed609baaf7e3c5bed654fb335ecfd76482a56b98005c10206fd665ab4407a525087a29b3d42e122a45c9e3f743ba6ceedc466a3e86305a39808ca56c04cae69a7a205ee907b72092d4fd889c33e105ed56e485e14bea958ba6f6cecc7cee673cbc84bccddc662c2bdd334752d9d5b1f0ecd19f8e3a01e69f44a27a7ea05e16ca234905d243840303498212a83f9615a984abb1309bc07eb64ca98943818b9de52ecdba481e46d4b64f61d471af7e0c7b0e054be389a8b1b7635c284000085d81684e831b977f0a18532dec98d1883d7561293bb43949408d057815d420fa1462d3b16f417dbd0d94474016c3187d406a14dd9cf2700f21ce0c4dc7d44e8ba2d66c911484b059e29392c845c795f5e319ae10bd6fe2815d6f15787f08118fd6047ce993441f85648e3e44fb802848429e33eb08096d46aaab8807f48ef8e3f79d602445b4c5816b9942fd7075b7336709e2232cda8415e9e224dd891441bf4849f9678362744983b0e2348669bda4206986721d6649afb7817dcc1f6a50f7a411a2241984425887dcc03b83ae5f7ee320f4844dbab088553f4058b2771226037ce7faf4073f0f6800ef4a8d69f73256cb707b152bba4e470d57ce9c142d9f288fecb1e41418f2ddcd5e9e5e4d3bab4a23833d5086bcb4d97e6b549e465cf603643e3a523b48e83b7ea2f3a2d1fa015f2779fe271c6e7efd34ff100048dd842571ab924add6256e142d5ff9f192349151ad215129946e8ac62263165f27cb58d0fce9771b92fac71305d48e56a627c91dd530e6dd2d8dcd4c2a8ce0ffdb1038e75d33e7cfaf85ee3fe31e21ccd5789d53c9c9c8ac0e58456e81ef08203088b04656a2f6c1772158a21602fb5ea06f5b9a85e3d033d9e616343ce928487d5fbb87aacebc0323bcac3b41faadb610aec92a3777686a4f7e9b72d358e7c388055975afdf31f59935d8f17d9019d41cd52a4da3755bcd07dee3829ad370808726e96a7689b34f59f2b93b0331bb068990770b5b7a74abcf79241788ae04de0a3b1225763ceb094535841f5e22a9f26efe845601e7f0fedf8c0fc8c4dcaba9c98a06b8369132df4254de30e5e641ba45ea43acf7c410001e92a8dd1017c4ce3bc09fc4f7ac11769b14e0a86626c7106ec6c498ea7bf3e975eb3d090bea00ea65dd7c91808b0af15221fe364a398b81bc02c4fa6c5976088717eb70603163870be2e2010359a1b18472194128c47782a80255c8b1eb3486a8122aa1a2c80a870b1d24842ed7367245a1177a0a670dc2e9195354056fce67b608ac6dcd930e007c873bd9a3b950b9cb0b634b7fd8ebf2027fbb1e8eece5b82580edc5fe1fb5be43fad4d1c4c235f6b8376124423c2b2ada5cbc80b0a23a3f571d6e9f1dc132c3a6f0c5bf03c5be074ca18c266c9ebcc9da170bbcdbeab38ae09d061c7060f1886d5c6fd292ab2ff6b0724683b17211393e8832ba08d6544a4094ddabffd05cae4206813246f5c6c20d134680dd3a46285810fde053033d8eec23708a709b17218338e15af105d5dc938c2d4c52bcdd903815630bd56ab09e79a1c0b437343d4dab09e282a56f8ca297a8fb409e3eb22c561ca4b9a30e5aa490f90811c5b2718c290d9b4f040df8a3d01e70d4d8a0581103cb713db0d670efbe41ae8dacb2408d2d1987938651a2c2e850f523a642df26a789abe881ae21d07fb17059c355cb0a945840866309e783c5b7590829ebb1363f7d38d0cee8f05c27e102e268d47c7b52f737e0a8f942846c1f1abc1a9ef4a099dc2893f1ca01a0458735765c8c3f08970e59a4a33c3f41b870d23d9d772956e00aac8276152022d503d578927b9a4d0618425d3de94bb6ff87601e68630b21ac07eef95c708b0ce07d90a80dfca2ddf3c7213140cc1568a30596d1bb093fc64f85ee6e68ea7292d435e11ee041e13407c11015a6a07386ff668ef2bc26a7af9467f672845016982dbf37d55f368487ec60853ae288ec3037bbde789fd0102a4b25d120210fee66d60370fa2c5cb6b03832094ca8b7c5db58695a0f55a0e8b80dd9cf61ad05abe543af922d094e8f9bbace9a66b18e8185a2717ba8f94cb303dced1e55cd0ce2740a822a44a71fb5625ed980872cd5af1f126f16f7dee4d591b142ecfc77ee26d8f89b7aa5eb9794dc1b98c7e3e473a04ca094ddc8f92abf9147b9dfc9939a32441ddb816c551eff16163beb5ac283990d0eaede71a5b27803c4e21a3fa87c633242ec80af7d21883296180f29751338d08a21072cf812ad73e699ec72b1f5d7e2c62d44ebd65a7540a0da1c514da27e5ac6d3cf13ff5499c161d86876f49659e7c90043b2324730db637430337939a52f5fc28fa2b870066e3b1053fcc6b80dea7b5a179233b810443514a2bf0b5eeb1f533ce1c9c3397cee5b0f5a3cde27d6db953bdf87a7c99d10d165ebe381fe6829f61f2c60890eb7956218a0d0d6922033f802638d5076bb9489a376b6aa8ac67f25c4280922dcad9819583756eb7714b40b86bb8adfc21bc4268a4f8a1cdf12708080b7b98456eea30a55a6ff8bc5097b9e586382c47db412e438de5bfad90266ee2639c3dc2bedb3a94f9a84dc0b8859014486015382105ae040fc2514ec7802fe4044df0a3760b46439330f0f0f0f0f0f0f0f6f706ba4b6d6368024a49424a9adf153f8ee9029a594648a8433d417ce4cdc86362184d1863fda080d02010bf40ac50ac9f499f2b5ab325cc1873eb19c2fda07c868051b529a92123cf3a59820b00219ac6064bd6b472a993a476d6ce001362820011b5bd880c0183256c1a43e4f3166e855c1c61acb9f53da53c19f680d31c8fc7ef9265430f92ce750ff21afa77b0a268e8a96c73bd8b6ada6e07a77f46be99c25eb2d059362481a6aea99de1e29b8ed9027e4cadd28f8205a9265d09e29658b28f88faf7fb6d9d3a67c2818d3f8666a440c146ceee8b952b0de0b1df904bf25748af61bd2a8c83dc1260b59c53f923e25f24ef0f9647a8db69fa4fecd093ed8a866ad64294a3a6d825162c9dfe228799a9334c197902c5a24277bf55226b88a215a44537f25e208136ce4d0f71f73e275e55c82495a21e85e907973722cc1badec678997542094d25d81d913975ca5ea276a504b739a9c99e96d6f58293e034b7492b957eefc59304a373d25ff59e48f06d25421242de9f881448705192529f839074df23189521287d9e2a6d8a992378492a85a44935b6ed8d606c5c536806d5604284119c34d3bd2d79a47fde8be0840a26b2e45c5322a22a824f1354aede4ba52e272611ec560a22654919e97b47049b738a0abefb15aed14370c13268bbff6032a225c3109c14d1993d665536e11582cfcb9f9d2ea7641082dd2cfa73cca064705707c155f610e99a5434b5a80c41302a7b6be39f84a4ab77b1c3e8588055faa2053bee003202c1081193fbf9aa3db72203106cfa9317539ac4913193f107ee4a4726c30f4c12eb517a55374ef4f481534ae8535dcf49883ac4088380a12351061fbe14f5141a5e3a4765ece1f275d3b496255af0764d20430f2729fa2495b6cf6a870e301cf0001c376e84c1011d1798808dcb01cc41461e4e061e4c9b335a1aef0e8c48cbaf1284251d499b63c781c0160c68c08d1ba87164f941861d72edba183a090bfabd066edcd8a1038c3176dcb8b163c78d1b3a5c0c2efc04376e7419c3860d6263d84036c6172d6807e0d0e16274c18518a70b07dcb8f1858b21c6e9e24b11400c32eac06fd22554acab15931ecf72d482430162635879e15e14d70146ea800c3a3041dae44f8d7539863e073ed6c64b6152f3c80fc981f59c444c1b458e4c37f9e313e8a28b311c1007d6439d44cf312c37e370e0369ed2759d296508a63770c2bf4f3cb6ae471cc97003b7f77f9db263ee48a78c36b0c17c63f9a70f2d1dae2081a12090c1067662b59f8c41d449f30332d6c026e9d95c443019fd35db21430d69cdc135a5e995430b87abe0ec28c5011969606c4dfb6e64730c32d0c05d4a2633a325b520e30c8c9af2717dcde61b649881cb9644fd5f1acbc0c9143f5bfecccb5d2237c820039beede4698f24befa731f039a434e919ba83369535c81003e3be9e3f8fba06adc130f07d26ec5e82fbc43791db820c30f0d69fb55792f294542c878c2ff0232baff6f208870c2f30c1d4b485f4503131c8e802bb49a8532143e9a867c220830b5cf4283107a53c684b627761811b374c05376ee028db838c2df0d9f71b3d564add6b818f29f7c40a53da3cd959e0e249cd8c604158d0278b50be279e2bb0fa95449f2e09c9feb50297525636f7f414f42a70f1541236ba1a29c5a9c0aea9905e9ffadd53700a8cf29ca2c4b558c890023f4a5f27939f4d57d089b1838b09686087efe080b9848c28705e22483b9d37275a1e0aac5b9ff6e54cda4dc99fc0e62611c72d74c6de9dc087bc632da2e926b0aa96eb25242526302a3285ab9bcee8c9b40446869434428852c93fa4045652c81bed6d97174349e02b67b4b43e993de206097cbc48e3e621e608fc85749aa2c734b333023978deb6089c58a848a9b359f24e128111a6eec992a8f5dcfd10f820737da759c960260aa1282a276da7c552818c2070f6616363397dcc07029b2b22ed86d2b92ffb03c6f622e5ec5bdd26293ee0c23f72fc1b0f41a67bc0e7f8554a1649a23b3ce04df379695bab8f15db01373af39bdde4a4a242142043074b126f2d99262707bcfb6637355183034e729217e468ac0e326ec06b72b7aab497954d9561032ea5bc9a9625dd0695caa841b6412b92d438914103aec7473b0b4e7eedc8d49bf290052bde954f23668dec522c18bffc94549f62400a1eb0e0a44790f69af335ad690b1bc40236b6b0412a60630b1b840236b6b0412660630b1b4402362e30011b71f078059354c8316b4816cb84af06011eae60bcb5caf2ae294f9e6d05a739ad24d3525975b783218616f560c517e25b5269d15d8f55f02571729694d23278a8825dcf6717d306a5673b20031db871a30b47810e1d607c9123edb871e375a800061ea9e082ce6f2a29e615edcc0e555470ff1949b54474d4a46a233c4ec1abee6866f28e872918ebd4fbf020ed8292799482bf0a0dba5ba204bdfc48c1c90e161a3125798c82cfd5a33e652d3d9aa31ea2e0f562a5fa1c52a964268f507032db574590b955bdf40005a78334117358baefec797c820d795355ec8b1e4ac9c313ec8590d247f1d31325280f101e9d605490da994da8bcb93225ede0c109369552d1941d3d356463f0d804bb492413dbfc1f3bed9a6025d9e59dd8a273867960f0c804eb6bc18205d1a14665c5e081095427a9f157c97e31868e9580c725f88c3e22c326059d7181c723e47d06cb4be5183a2af15bd8bd9f679b128c8d92b9baae3777d4bf1843076ac063127c0e91458a5bbfee3872ec18230cc22848470c0d6c2567815f053c24c1061f69226ccd4830ae39a4a037dc8404a3e2263565ccce27fcfd82c7231889c1435bef7604b71f6a6b6ff75386c9a3119c4efdaefba57a30820beeda9b97443ce0b108268888a87f168c828722d8a4f4c42da12af7c41c143c12c17d9b56e9b859a445cb0311ac046d77cfdf158eca83c721b890f2cba2ffb628bffa0d1e86e0a4d999f87757082e87c6ce924e242f21941e8460d5d3a59844dac9fb111b049ba2c6cf79d46c33d382e094509bc54ac4f2897904824f4127a154f4e7c8db01c1d7a63a9d628e1172c5a6e0f1072e0909aef9727f52e843e0e107ae3704d11b5f1bf5ad471fd8a0db69d48be9b4343df8c04b0ed3bccb252478ec81cd173be8d254e5d0c2d17d881d142430bab00b00103cf4c0468e6629b45f27ab5efce09107fe93c633bb94c9e2c8f0c0597253bfd1be6c7d2167048f3bf0716d2d2d640e4916ca2d78d881cf509d2ce5300d95d781d321655656cb972548b4e04107ce7474bd749df4e50dcd811f75dde65532d6b8eb21073eaf280bb2627c8fe92e81471cb8bc9e665f59d4697239e001073ebf9a8e1435291e6fe06356341942f28d091e6ee05bbd72fe0db24c9989a305270c331978b481cb394951cfbfb1aa325f78183bc6d871e5c10626c9ec5e4a94aa1ceabec0630d8ca757f430b563297335f03149fb8e96df64d27f0b113cd2c0f765fecffb3d61d2c62c78a081c9299a9e2382d23a4967606db227a146d89ec528163cccc0b82921e26e2c9f1cdb32b0c92f467190819139e93791e4e7381a2a24f018039b338410d67b31256d1bf01003fbaa498a5d503a3b84b6048f3070a66c930c1d47b55f0a0cc7943456d25d2bc1e30b6cdde8cde9b1cb74ee385aa0430cbd828717380f6a7dbfcad3877ebbc085d7a9884c4a7bd0e102a3694925e854b7c0f9980e52e2297f53d10a1e5ac0721e0d2ae446aae091053e6fa8648d1d1ad444565df0c08297aa479859fc0abc84a851cb46895edb0a7c90dfde2e32fea9a90adc066d7df24ab8fa54e03e43f57966bacb4e81f7493172aacc4ba18ba5722995321f055e3325896aa5172bf350e04db3f376d929a1db7f02a73a7947c545855cdf09ac5eea076dcda263f49bc004cf5144aaccd39f3e133855f12c4b0aa1f2cc5f0223dcf472c9cddf48be12b8142237affe78d6f093c0968e65bdc89b62d23d1238b9e329c935f99ef547603f256bf650933ea57e47a5c949d34a70e71d84ca9643095633b86bc8ec24f88c9a63e79f940497512d491db7d4798491e083ccd9d245de8ed53e0d4864aade848aa351748f821d670336091a8f60848c5b413c54e7c71dc195345de7666a04bbaee7495e75868a0a23f8d0ab20624417c1e7a9ba20661d73f614c1e54d0db6b9f636b49208b6d2e9688f31b6f588d84c6434134a7608c6538a489fc97208779b8b2d0c0d43f0d9b549ff540aca275508464e4a8e11062118cf214cc5d1bdafef0f824fbdfb510b7241b01e96dd3546dc1f650a04772a846c49f99eb6090204ebc14699aa49273de9fc81f3bd1391ef3c7ee07774e3d5fb875cbbe9032f31644a9bc6bccc357ce0fd5753cb93660f5cec8951ebdf54e8bb7ae0aba2a8542a836ef6681e8cdc12cfd7e28907ce92103d31787ee91eefc0c6ad1c4c5abed8818b39cc3e3de705cfd78157cf9f8489c6b4392b3af04175ca7819a4258d990397530a49454c264a47090b68c8819598c13e84150736554a4ab27a8d5b88c3814daf9cfbe1f71bb84b4ae78edcddc089decda0cbbaf74edd0636a6ca2fbe9577da64d8c075ea5222d89fda5e740dacc8f6d49b37a806de7f84083958c839669706ae2496d03946fe247434f09b4262d299fb5eac7206ae84f6cf6f1e4a249399810f22e554d9eb32b06e19926d940e19f8902c54ee8da977947a0cac78796bce9253a5b41818bb3ae175a623e78ce068810e31c2c0ae47fbcd29497eb01218b8cd67a9fd93f70556f32af57684c5909917d83ae59d25a9c9415845031a5d602ba5a6cf6a4ae988991d6870818f9c45e8df36dd10475e70b181a5b105ce6dd369519a94e8a4a3052ee5e7fdd5dc1d92a259603746caa0eea7e244110b9c5b6acfea96bf026312526aaa7117216334acc048099ef267a5142f48a30a5ca5bfdbafbdf5b594a5025fb16c83690795ad73f431ba382ac017d098c2e291228aa8a454d19002b71f4443d787a8161b1b78c016c50b34a2c0a9260bd1b41e9e46690c1b1798800d30d08002175fad339b526a39b883c613381135bff42e7a8aa71750aa8481861358df4f0bdaf55354d57194c181461318b5cde7fe1a732c956101183ac240030d2670da83c48c6e4a62eab30c3496c029ef13eaa9367b4990d15002a7dff25f3bd2e4895d0e2d1c6178914a79b145175f58804612b8d4eb4a492b8a042e289534ea0633a55febe31928a510a071043676964ef622358268530a348cc0adc44b7a22e6b459544da051044e5409cf215b9e38a944042ec35a74d774567c1110680c81afca49bf52c4abcc7aa9020d217039ba74090b324fe62a08ec290926c1454408518106109890e495e89646aaa0fe80afd69c534b8714ffb328d0f001574aa594f4ea95e9b422a0d103ae57af92a6d2ec3b960cd0e001db63a3b761c98249d61c81c60e78136197ee62e80fa937020d1d70e2ff3175ac0b3adf99432b0c2ec45893018d1c70390691d73dd3f512061762e0d811061762744103078cf63bcdba1464a040e3068c77648d314516c976db804f4b216b9b29dfb7c91368d480dfb6d23ebf4d3d7dd1a0011fa459fc6c153263169cdd9bb6dadf376139b2e0a4de8e48f2cf34cc8805ffaa9236d7f2657fdb033360c1495131534fa65598f18a728b4dcad2d9aec00c57b063dfa52b2c58cc21cb21cc68059be49769d0bfe725439615ac66b4ccd851447432550366ac824f5971efb23e6dda76862ab8a483074bd974275d71093352c1be86fa4dffb9fd928f0a46435747e750b9b5834ec16d52da1d4d57bcccd514769e1434799fc508334ac189ec7699b71bc2724a0ade54667c932034e576678c82db6891fed385cc1005a73972758a9332a599a8302314dcc8bc4d2a5eefa5e08282179dd926a9ef6b62ceccf80497530eb641095d5a3ff7049784befc38d2e4a77d3ac15f5bf747b335c78e0381d3199ce0b46565fe379dbced83a3ca143163137c648dfc9eb7ffda6a08d8d8c2c603b6b0c136b6b0d1802d34c1675226f72f7dbe3023135cda10845ee650113bba0516666082b56c11746307675c820b41648af9f653841cb28019966062daaeafa0aeda2cb4b1c5186654824f7192501339c434da86125ccaf190439b9981044617ae6312dc067da6a794d6955f39b4ba0b303e083324c196de0d495e3a1b21ea48b0935da2e64c419060b2281d31974ee2a6848f6025fe577a9852e121770413728aa5f8e9d35a4b663482d31bd916538f8e3083117c74cdd74e593daa25bdf02202691b8043cc58045bd1ef2aa9529e79438ae03557f2aec816edb42711fc5a0ed9d6e2410463216ed01f2166d6c839041f84c4cc546117ed5a43703e7a3a53e45821b8d0e3993cab4fd54d08ae8450bde412f39f84e81003479587d1820d2462c62098a4b1d2d5f34316e90b82abd49bd9ed8329b33b10dcdf6e0ebab29724150182fd78edede611f278f40f8c69ab3f75b671d32a40c1e62366f881f5ebeb11d2ea2c5abe0ffc55c7cafece5515743eb0669182f0f4901df2b3072ee5a818214e1296ffd3039b4c3f72929a5ef3a57268a9000538fe781738ae3a2001731580e185eb08402066e481d39a1154d2a022a6d5f1c05d06559363d21a9edf81ffdb20313ceae474db0e7c2cf1d7b2cb1dbda375e03705b5187445f5e9920e6cbabe9873fbd69412e7c0279945e65dcf4cc927a6fd8b15580000aa98210776426e491294478ec98f0327b2e385507582039bd386beab3db7d1ef1b1855c942afc8f7b6f204ed08c60c37f0b59752c5bcb95deb6f0393763d5f48b55945c6b0819359b2fea8fd2079b26b60b744e8babaae8d9b570372848c41e8e86f1a1899befdf2539e986488063ef6e556aefc9693b2cfc08714da64c87631490d320397829222476b4f2caa6560944ad31084480c79f46460cdabb35e8292a5427e0c6cfb65a618835c0c8c25193a657dc8b1b31e0636a7dad0621a4212afc1c09eeddda8d2a72ff079eaa3a36e4a2add78814d7709a61642aebaa60bbc78784efda6e102a37394741fbf32627e0b6c0c4942c98c76fad1d50223536df69f8e27c966814fea937ecd251618cd88a9be976f17b91c5a27f8828b1d3ac648ae03479da0991414665c81314f2999e5106b7df3de0596cfb002a736a7ca88a12be860e238c1175ca0209d40877b5186821955e0728e223d847ecf2552ba382ec60e1c28e8620c946563030f6080316f30beb89f41056e83dc8865a332ef1de98b8303477f7170b0808baff2850e0bfcf109883106056edcd0e128d071bc281670f10503c0831953e04678091169d3bfa2648614b8b4416dc40b966d6b93436bfbc60d0e9828b0af395993ccd2983a0a057e948aa62a3fbf3c98f1044e099154b9b27437955608339cc0bbea086d79a46e507130a3095c08491e6b27fead884ce0a3ed77e7cededb9212011da7033798b1042645ae98aa4c6707515202e79f747d103a1dcc48023b69b494bedc9954f410099c52724379d01dcaab37338ec086c474ee1743974660527a8a418847af084c5caffe4a3146df9815e30b2e3870e346210263fd357a3b9dffc5b0cc60c61038916b4cc4d19f35e9940b6608818b7d2e4a853ecf82c0a6aafa87851ac78e436ac7026a3003086c28c949691fd359321983193fe03e65f41cfc7447440f0b66f880519372fabc37cd49e66c1166f4804fca247aba8a179ec61d3ce0c7bba36fe48ad88e2f8e0e318a0161c60e184f6a21aaae9f04e1660e66984eaa246da9949e03be72c774e1a9ce269970c097dd56ff58325def8a0d66dc80112965b1a0aeed554b1b705a7931e36a8ec9d3ad01e76a75f913626d0ce50c1ab09ade5a33a485560b0319b3b0c3b36f5398c9dac5f8e2a4d3012bb2d08245b5bb78569b2423167cde18740e1a3b5870a79dbf6486e0173b5d84e185eb0b64bc828fb12f0184e025c5531a565223bac6c60626604307c1ba9f106db59932725210e64caa83d0fb2924ff77081f81e053df5396a067396777820f40f0f9bde6e33105b533b1e1e30f9c773495f75250093efcc0c7902fa5595bd9c79429193efac0a7d7575aabe8aea0c307c6fddc4a3d85e41e58d3b4906741add6d71f7ae0cc3a06f724624a4247ff9107c6266ba747dcc9a22d1f78604c44fecf154d738595430b8cff820ba2838f3b70aaeee65e5ffa473d36b610400e3eecf0f9a638d16dc4b31c5a2b383a70b8bfd58193c147a452f1e4093dd1814d2e6abb746ce7c0244d49f7a27b7268e506bc0b14e8700a9c073ee4c0a8512f21670b922cf42ffc04a6031f71e084ce6bff4da1b36f0a072e2328ab0a96477aec38f0f1066efbdbdb424e11512c6ee0bddb46787814d16fdcb831838f36f0e6c9d2fac95117526e470e1d384e7050290a6480021420a6c30b2f8a083ed8c0e68a14a93dfff4d28f3570b5d9be59faa5e5fb871ab8d6f02c916b64d221fa9106bef7bf9392b42f42d51f6860474566919474d37bf6e30cbc89a6cc6a29a6f4261f666063652d4d32bd9652df4719d8a4845f977a503a499e434b4df041065efbcc1c03dfab66427e4e36494d7268a1e08ba303c78718d84b217ed9b985d8df8ae1230c6c6b2e4fc1c3b4cfd361f80003b7a75f728ed61633828f2f90f369ce21626e8ba60221c0d11f5ee02bac56c453e66f497268e1c8808d2dc84717b8d2bd110b1d65ee1d2e702a9ab698d47e47af6c0c1f5be0a2a5b8a9152adfb82106185decd80f2df07ea3465e8be6230b6ca51c44b4ba5ae4a4fdc0022337d3b78eaedaef908f2bf0315f658ba57b52d2b815f809b2cb3dfbf85105c63ce43abbbdd2967e50810b5a2c988f58f89e3ea6c075468f9e3a434a7c488193626a9d7a2df2c75013cf131f51f880427e31eea952761727c761e0e309fcf9dba64ebaf7e39999171f4e602d87aadcdf4a11d9a2253e9ac0240b21656b089120a41f4ce04784452dd31c423c1502bb858f3bc894d2561c9d600ce48de304607461f0001f4ae0aea35958974aba52b4c24712dc16d3bb112a6e60023992188701376e4ce103094cd011a39a7dae12253f026b2e4994c80db5592123f09573cab896828e6b8ac0a9acf02836e61584ba880f22f06d2a78d6d05be1ba6b213e86c08798be1f2a3c4d16c78381c330c013172ad0e11a2895237d7174a0cc0d7c08c13d7d6abef7dba1a4021d636c91868f20703ab3b957a94060af445bd03b293d7d82838f1f305a216d52952e59d5ee0336a88a646eb71d64090350838f1ef0f93de7dcc9d6d5d7c4032604f53cff705bf1b1032e8390a20e387d4164c93622fb8765630b1b5bd8e8557ce4809718ed3bb73c53c50f1c70c2f4a4511d947e52495ff8b8019354445f93b89fab83884de0c306ac4d7a2d21847077cd7ed480fd125943ca8bf9e18306dce8186a84ccd8c13212851ab3e0a2272931c82c416551d240b1430d59702986d6abfcf70c41e9c51874a8110b4e459219a9d75cf72758a8010b6e75344c52a6e41eabc62b38e1f527b45e7ccb9e0b430d5770414da9c896bf2b06b7157c6f1659fa9e55f28face0e257084144ea5bf09c55b03569432c846de88b2ab8eca0294216eba81b4a051b64c648769a6dfc34d77109811aa8e0f5b44e8ac1cff2f9478c8370eca9710a7645c6cd8ba233342e7c8c1494aa610a3e7752dee57c8b399dc610354ac1e6502a8927350bcac73cd42005df159a6388aea87aba35d41805efaeea2198f47cf92d68a8210a26ed56a5e5e46d691dd60805e3aaa6f9d53e3540c166f178d27cdcdda2c62798bce973507ad74ba9c7136c9e7cd283dcd4e804e71233aa44bfd0a12ca8c109fe6d3c27fdfb20121cc46a6c828de6f76d3aed4454870b1d61580d4db07dffb9419b094e78d69ea9742931b939b42a063530c17a301df3c70fea4daf4bb021d8764848a633420d4b70aaae9e63142537422cb750a312bca8f98ba81af364419460ac430ea62fae5aa831092ea4cb9433955fccadd790047b4ae8f04832640bc2ab1109c673ed8b9ca06cd4cf1c5acb831a90e0afbb228f0e41dd9258e311bcd9b8aa988b0993780d47f0d94386beccd22c4ad70836dc4f07651d32a10623b8fc20ef42d59563c721e6801a8be03ae7c5494a868fba48166a2882cdcf5a9b58969e16fc070347f72916811a89602c2421d2d4acaf236e705003115cd22023c70c22631138e3a2c621f8ce7b4994d68a212b65085e23778f2cddd933a61405d42884f9cd27082183493381d15b83108cdc284ae4f4bb210779d526a0c62018c929df7e754a923e6b420d41f029c2bef2b90969a5d608049b3428a549281b108c5ac664b5ead6f803dfa731dbe990fbc1ec5e55bb27cb1a7de0b49bee7ade4e1042abc10746b48894d56a4165f76aec81b37613ab7f6b7d8d1c126ae881ad4a6f13cfa6b684d40535f2c0a778a523a2f7d608ef0635f0c0ad69932631ff8d3af91dd8eecc3156d96d07264b0eddcfd014a1461dd8607282bc92acf59dcba1656450830e7cd6c5a45d42e4dc9e9f0397eb94ac499e9d9bcb814b9b344aee7890691407b6ec4dd7f78dfa2c7138b051d34949195266c4dfc095e63d91e2686d44ddc06778528d1ce936f8b731d69a8f121bd86a1d393a5f7bbd92d6c049c8912bc4a44d7c4b6ae0e32569a93508a1cb4a6960359d8bf6efe7cb104203b7eb163347d252223f67602c47d2bfdb1c3330e66e5b1dd275828897812b2522932599f44b78c8c0776513ca3f6dfc0d9931b0daf9b2978f0eb14e2306565f5d6f57fc523313064e2591d7bf3ed7791c0c9ccabbe165ba52b4e80b4cfc1b61315a1ce99ff50297d466536eb92ef0a7dff6c13ec7acd4728193d183f9a515a13f64b7c086eda5c62879d2a6b5c0698aa2524fa259e0b76b6d62c8799983140b6c348f9f4765afc07af6fc31c7d3a52cb4029fd3f44893a0a24d50abc004212ca7ec954ac94da9c0bec88d39a81016c934054e66bc1249e74b152229f0e51a4183c8a54382320aac9b8514445fb9a832a1c06da860aa2c9d89d69fc09932adaba43b81df51ba62cce1e9d3a8097c85901acd6702a3a642be3caafde359021b2fc6603906d9b7a612f811b58ff9fa25940476336af85eccff202430a24c9608d9edb45a47606d2f7e0eeaa1e349d008dca60da57ff222b0317f478e9913811f573f9554fa29350e813555710f293c21307a27679f9c267b8c5d1058d5ca9072b4030267ef218b12964564d00fd898e29d2afb33a7d0075c6ecf65b2ca2fa3567ac0e9602a7fe2a4f080c9a93b9a49ba3c95db0ef818546dd7064df144a603c6bec2444595d4ebb11cb095cb4a72f6ce66711c7041cf7c736faddda56bdc805327b2a434211bf09fa7314f87906fda1ab0935b47ecd574f4b4060d38f513aa2f79c5b59c053b6a21efddc9a8db952c180f12e26de6c7b87e2c1811d2858a9be9939081052f31497c2f9d4235f90a36bbb3baafe94a52bd2bd8a0932e765d48a14c6b059393ca9a83b5ac60fd4cbd0a3e4b08376d49a7cca2a30a4ec5dc935372489b924d055b7d49988e39890a36861092734eedac693c051ba92208d19ef89f33145c0b6898820fd26209095ae3dbc74bc1fee668427207d34c2229f8d2f9d1cc64879c9a8d8253dd88792354be4314fc6810c9724d3779c943c18b44fd4e32dea0e0a2c5db20ba235d2bfd092e7708393c8614c26ae409763be4ce58163a69e67482ab144bef7258999887138c6a93d9e4ae4896acd9049b2fa6ee3e32a5c78c26f85839ebb3c61e69419960a4efe51482f4d2d7c18449249172df622ec1c9143cb3a6a5aaac5a821149a925510a82e07d726b5ef2afa0f94070266408f393f963ed80e02f89a0a1f4d286eefc819164eba15ba134c8f88109f69e3a338d7eae7de0542da7de562b9129c6074eb4a63d95bb228910db03233fc7de4fdaf4938e1eb84c5a73c84fefa0993cb02ad613634af1c069c72da59eb9acbf3bb0f97c82767fc60eec06491b5ca3d8491201831675e03a8a75e79c2157254d07c63206a5936c9115f4670e8ca7ca206388460eecd686cc263c64d17be3c0dfa8e6e63af9164385031b24e80f2a47c9667dbe81b549b9574fd44c65ee88e106f3fb6fe7f70b2e8e18ae23dbc06ae44c3ea2d3a7feb46003934efb95fefabf3f79166bb1063e8b07f3ed77b38fab811321e58a1213d1917e1a98e42243772cb9a7bfa381b7092149db889a75e93370417bd0d4694f3b7333b041a8bc1a2f8bd44b3965e064875b2acd8999364306fe4ec99222bdbbbcdb31f0b931ad7c6442a08518388f563fc294302d790d037b22af629b0c4174a7e4d04a2dc0905a7c818d18a225a8e4926397fa85165ec01468d1852b560c92cb2aa5985482a50517d2a5ca936184716e145a6c814dba6b9fbe628f086e0e2d1c8866630313b0b1636c60021cb05068a185d3220ba605160a2981165760b466cfdaeaea6b8bb50019066861054ed6d85dee5e5125ea2ab0de952e89f85a323df4002da8c0569508caebf2efd42b052da6c0ebfb69a5dfd96b74a5c008b541837e8f16b4a951e0b3e429b5dd5947d4f5042da0c0572af51aba792d9ec009a54330a1326f94667368556d2079181ab891650c5a3881b1caeda7164f4de49bc07a0c399e3909372162029bdd2445123a889ae812b8512926124f25707d2bb12cc34f021b39078bb1d4048fda2381efcf9e11d7fa2370595a7a1f9407f5593702ffaf49e974bf6b99ee2270359aca6dbcb75a6b22f0515f84a5ec8b7eff10f8d29f6ba38a84c0c6b21d592f9a93dc04812b619bd12a757e3281c09d4c3a7b24d30f98a0397ff04a412b7dc9077c8e7977cf54520fb8f748f5fd67a1829e079c1e57b318f1be436807fc4911ef5c121d305a2cde955273c06e0a1952c896d702075cf95edc4b934ce710a4c50df8ab7c93b694bc95dcd1c2065cfef71ffd8a17f276b4a80193269824cd7ac9bb375ad0800bc1268594de654a6d66c107ab7849c8989105dfd1ba946c9360e9df587097b935686adefebdb0604f8b8b77d03b4f7b5fc1267da3b39ff0cb5b5a57b01a74db238af58ad0b682cda3fa94d0e21f27baac60b3630e65c164ac28d255b0e1a643f2dc54f1185505576ba7b3aa8d7e8d9a0a36e29dae1ed724ed525470273db55fe490ad969e82b118628c94276a0a2ead56cfbb735ff452701e528a112dc7cbf9430a2e4dd4d39eb37b8898a3e02a2fe535756ab52b290a2ea58f299d122ae2d6a1e0ae4774c49c82f60c0205234a63f013fc089946a8969e94a3e8095682870ca242ec049b3f2775a2e3a7d61c7282cb7173b668fa4e63ea36c1778908955384741e2f9a50d2c84efe37964cf06b9d930c15b1eaf498e0db43ec4b37e61fb376093e76c8912442fccaad5982d1c8ba5582df929a9ac3cc7d45a3042742c42cd974c658a29a04a34aa62043ed2da8b52451b220e3a75e5224b8d6984bcb74a9535a48f059e47be8603e828f29b2950cd13cfae8087e35457d75e27b758de0933c53afdd63ed298c60a4879c91b2bc736364116cac521a74288b7944882238cf9932af3289cade26825395af6a3bc9a415728a08ce377656804330c9e42695f431c5c43704972ea69cbadb7527e71482136b112253660eb23d842863b0b89a9b6206c146755d13a1a729a95410fce994d74d9d8e869d81e08465aee6d1699f441010ac59778cf92b850afdfec07936d7bffb2a15acf30393d44573d3362de84b1f3899f4f5f8ed5e755af8c0b5bad5e40bd97e54ca1e38bfd431b5edb607ade881310bd3e6eb77af21250f7cda969cf8e9747a53e181abde9cce844a4d975377e06ceb742b857765b4ecc09a4850d1939cd4b2aa0337d1dd4a3bdd4a448a0e6cb699aefd909a03a327eb5637269996037feda6e63147f41ac5817135dfcd6295f47be0c009fd953d6955eccbbe814f4a081f0b4ae77817379823c970cbd136f01d5b37828a101014800dbc29fd418479148976af81b111af0a3ae389f4d4c08a0c7da79a9a543594063ef44ee6cd29b134b268e0f55465ce9ef9b7ef33b03af2ba479decae1833b0d53157363dd2b72b65e02b68d357593793c4c8c07846091e54ea1bbf740c5c9fea335da23fec1403a7424ffe5c4a448b2ac3c046cd1959ccfe4566c0c068d0567b4104bfc07586e0f182bcc02599428a88f9eb02bf751593d2f47181b3a4fbb2ea2d85e5650b5c65fd20d3a4ef85a4a205bee295d41331bb4c2959e083e5f62fb10d8b55c102232c8ba5ce26bb0227b3d4fdd52651619a15384d1fcbdcb2d9e8d1aac08d4833952fc92a591915d8da167df7a4a64c3d054e7f571231674b0afcb6bd8898c98927ad28f0f9233306f118a3490b0a9c485c09b9d3e94bb39ec0f5c79c325dccf3f5ca09ec579b664b3781b5d12d66723386f299c0a9f79026ba2a33be043ef8a4281a456c728e12b898ff944e891f1e9249e0a3889853c7cb413f8804b637447e5fff09ea1d813b5da739067d79296604764c453ce6a0725e844ca000456037d4c9899e2775d7446093d0bdf5214534290e8191e32124a4f6244d6542e0b373d404d3d0be292e70b818a71e508020a0955a52ca2ac1c3ddd328fdace06b4105000277a2471c42017ec096674ada92bf55aed1d1a0003ee04e64d0a1f35e6efd0d0705e801e7a22663c7f1b8800b0dbca0003c602cc80c936f1253e5ba450e0ab0034e9dc64b29b5c69c357643074cbc8b41df69ddf353b7a00039e02e549b524205c001ff1d430711537c741670e1050e34c60e6501175ee0f03f0628c00db88b1eb3fb4325e8e5779c85c059060a6003ee227e964eeb9492fd05a8011be2e94f5a2359f20d0b4003aead455e8a36d3eb3139b494371815b8fd9805db29e6383abd6e0ed5f9172b00030718626091057cc8828b933d66f4ca3e69f7c1b1e3c610e368a0051fb1b0ac7478081e3432f880051f4b6b8a93ce7292267a059f49a72821c6f6c3157ca5c66c6923ed06f9b582bf2b1d2a241f3f58c1e6a86f4274b86afed22af88f3c42574cf1c775f4a10afefa46a78e54f57ffb2315ecb95d7896d2ec6b3f2a18ddac9b60ca7d349a4e510ea6378a6ade146ca90f1643bacd91d4a8146c5b881ba6695dba37a4e026b5f9dec56e14fc8d9dcedd561105973ce8cce9db534c950a056f6a5f5782bec9a0ef0728182147fe5f0eea4f70797793fc623e3fa1798289924d4bc7ddceecaa13dc7a2475f31141098f7c7082cd223429996983d2b13e36f1a109bca13a3e32513e30c1c5a08386c4bc5832dac5c725b864ba7372fb6b89d1cba159a47c5882bbfa90548a64f5163f8756a9faa8045721b46b497dfba0e91c5aa4b6bcc1f8e21af04109ce638c28d174ee9849e863129cde04914d968e24d88a112346bf10178f5d043e22c1eaa411adb316f7ff3e20c195da7595eca08f47f0df1b838cf963b6baa00f47b031e45831a51311a34a1bc1bafb687dbc5c9bc6c2850f46b0934c5dbca4758c41868be02ce4adb4bd493fdfae08fe43ab55501ac2e123119ce6fc1ad48990d32d09116c12c1f7538d8cd4f71e828dd949dbfd53cc0e1f86e0b4585a2abb0d19ca367c1482495ebebe6fbba5806fdc08c38b84868f8c7da8a4f024e25824100883a1703810808102e603e3130000000c201486429160248db465fc1480034b2824443e2c16281c2014161a8744c24028140608038160180c0884c2e15048201ccf84bcfc23fccdd9b69cdbc999e79ce7382bda9ccdcd90cd990f73c6dcce96250f9f0356c899d94abb0c693383abc7709596ce29ca2e9d82b9731b616f895c47837bfccc3d8f7b0ed169fade7f1c99303dde814deb1166c45d17335dcd2866080662750208a22d4acfbd8604ee0af7cbdeac53946a23355256678910c3f65c9854a1168be89b326a1661224fd6afd2d834fa7f6ba163cc780669085c05be215e24f785c3c2091c82a5b011009d7eb9d6810d19965687ffc01b79050cb0f5b5b7672bd90d49735ed176c4c29d8d26ff6656b1e3c5e68d6ccf32d42c88053d878cb4d89b356121c6e908fcc0d1bd5cad382fb3b985f4189e1093ff24a0eae6b9fdfaa5c7a996959b0743986eb3e77bfe4605f12f85557bcef57a853fb8c2e504163bad26ee3c3ea29dc52ec3ff66161d54e2d4b1c5b3e2b1d8a8fa2dc1c74d655e42884f4d4810f3bc45686720a124b49317bc4b00dd21d03b6a1336613f500a90229a4311fc3d17d50eb0280a5da010519193d28dfbc28a775be808433e704c21984d39b0c669aad8aca0c5806e182d8d867bbadb9df3cc3f86b525e4119e3efb0c8d706a78dfc143abb1fe5c0be0c2c521203af53ca677c9ba39e3cbeca9f2bebc27be75013e16e90f2092a199c3f5820ba5d5bac5913d05fd3d76a36e59f99f62c3158687dd53c9fd0d0f09e2b431be040f1436264a3ab96e5837d71dee86d59bb134156ffae571f766bcf11e8cf7cbbbf24c79a93c40de9537d1bdcf1cfd78a3c48f9d5008e8e819fc591a6ada7061db859d298fb06cbd5afe4de88881a8bb791ade8ab79f6fc28c1d2a15f31502527a13e44de07ae7b742f73c0bb357ef11f662e12d4432e6e2d6e105279c8059942ec486ca86b68539070ff4f306dbbcfb8c7f6469779a9ad6d6e460e0f52feb163274ec32f5c5b8c9815381b6604c5444de1d710bba30f9860b3a6929b90bcd9de75673efdc656e3f37b43bd4ad706fdde56e6f37843bc48d7add0ade9271a6ee3e7ea8e28d8a169d272a257e744752c67b9c5f0aee9cdb39a6a5d6a90aa9876c593a757dfb6dfecde67b6c4b5627db17d51a7852e9d76d7a612591324a962f1a4ac820d435cb2b47c6a3885148b1210d1152344927471f21a5202279479bd1ecae196a8e968b5c85bba4161d4103d0b8457b982332b1d19553c8189acb7927775e74317ac0cea48f51a9c552f9a8b1e799c1364f4e29153cc4d252a0a897a4a537a33a6f15869f05fb91614864694b76b2e641ce1cb30551477508c0e4932148e488940265835ca018c424be145e26d16c32e43082fb265165683c125a0171f9de345d0d14722bf73ccf4b5eef8122d7d0dd48bb5130da3845b20b0ce1fa21a9263f386e59ae48006dbd0dd2fb4101030163b313ad0e806b7b9135c8d4e8b36d2784b8b4ecba2f7e6fbcccb7bc7e71d0cb1288cf4d76b2f0555c40df0435082727b1ed7296cd0a83ba6c4df8d4210763e4ffece9e133f723b60a90efc57503079ee3f01ea559081746cad4dca70c00ea7d8b58dd8bb06ee401d05e25a05eb73dd78d934ea15db7a1745097603af90d5070b4aea9f052948fb361c7552134e496f19f0c34dbb77ce8c0d557251e7cf83215a122f935e5bc50db6d668269bcaab06e8297114998b432c92ce1d823e6783c1aae200af5ef57218fa4fdbf9dcbb015d3dec5fa2e8ab104889334ca906cf1b43a385ea9c07b2c09dfba501a262c88e36d4460cf7c4c3d4c0552c389c1010e92b78295a6b1e5d9495c0afa9a342b588eddf8989866462f8b5b2440cd9061b5c69132b7c48b483c7767d28780369438019d780586ca75b854e7d910baa21e6ba3e173eea11140c9b570b904fd41ecc560a217958c710becbd45d5389aded202876af974244a67c73952ad23619c2359f59b6dcfe3bf27b20851dad1a8b476a1b437443670d596c9630e547ff648a3749d2e00722ef2c6299394c8e993409bcd1d51553acfe093925723dea2900c7a55f6702e5f1f0a5cb3a2e8c8da7eaf2b330320861109db08ddc8add9f8b2359bb21ad000cf634ac2f598b19572b5d7708f19919632f6ae6ee4c0193252aba1ef3f8d4c441fb35297b165a611137747194e85e0883819076371a71099cc6adf9bdf4987f02ef182fae849bcddee61fa210009f1bcf999142eb03226982cee916ab5b02d44c8638000b246e30d3df95dbfa1be68858ae49e3fd0867190ea866edc860df2691ead54994a8555a2810f9e03a87b8adb40b4b81bb791cf86d3baad762f010aeed10190c80f89f19d2021873ef935831249264f3377a1dc9c5696c5c95484ec51fae7c0ed7a521bd037d182153ddbb0943d6f5440c1bd0ceca4fa4256974d0162b8b551462b1798ef7f3d5d5f44ca9d8686cc723e80a2c24496afa0f1d15133b6cc24fe824cfcb0e117cfea3abf962e31cb05af36e1be6dda2c07ec38d240c0761465fa1274c1a449b078c7b17401a5866d7a3468687e756852de462442d1c01cffab5d7f9e8b21c98e0b29fb214d56d44b4e6d41485d8d840ea2496a5dd51c30814c6f83c87ada6c8dd6980db039807197050b388d3433f6c9168949cd2d6bd8e0a70f29b7535694a600fee9b95fe98d8f1a6058cf8907852b3ef7ce25afaf81217188683c5459a21db11b15543cfeb47038fe752987062bd70956d0fa79660bfe538c4d10506ce825ca1ec9f18ecfa36c6a13045040410b2442bd5599307717502e0e697eb016517d8b090b497808392dc5c5a9bc8bb9d83fbe2f26606ab7520f9aa8cab5345cef62ea4796f45b325e6350c351528a86e3341311be125464182f4c566fbee8a3685c6452dec82941eb71c65412a74d56baadff13ab9b36d9c4a1ee4ab0575c83e8b488d15d9d3da1728f98bf1c840d621ffefdc294422da4424ba5e55013bcca5d07a3841724605c56df971d060567a16871405fcd63ae10619c3b8a9cc4f2eb4f6cd9182de961267e1278c9d59e06d00f5ad3a38fc3f19846635b4ac57707981c1b374094bb306cd73a02b7ed97a5d36822429e2c784579fda9809789b0e06ae0114ffd2da224e36f20126057b42f015905e4e28c09a4c915f4e2cd73f5ae06e0c45fe7bfd04b68f4267c235863e814c442c77631c9f9a34274e0b1655836d63fd7febf448e5ecb4e9aa1f103b0aa1bd456b3727e60278f5587faeec1aef32fe58362324bd29f6e4d061b7a9535998d0965b54089746c243fc63d7214d999f9a7f864c94e08ad517a298a654263eca4912262adee08a1b85a85cee0b35e8b67ab9c8e60216e5a5b67aef7004410477468d6126bbc8a81240518a7332a8a205f5dada0225f270174a03284325868ff7e2e03d05ba1b6f23533bf0a2aa8b73ed36dcc197ef27d2842010caa5538c60eaa81338aa1a559043f5658963ee5da999ab2847181ec470666b9ddc796797f29e3ab2270833f833991556e4b030611163d0ec60baac74a6faa4fcc27c226b87b2700c9e5a6cb4379bd3aa6b8cc9254b43eddb05e61c43ee4a91a1311891fe33f358e8da3178d6b49eed1ce3ca810fc8f4675039a7e6af4340a0e8440a270e14efdde9276a19776a26df890ee0ae41030858a7bbb4db02d4ea274cd846b7151ba7449874a4c410dfd89112d21f9cd127049c80c59dbdc98e8a95e8d72bfed25cac77c01ffb338001e970f2beb1e1a1c812977ff27238001b4d8522051521641e1e23e3480896fcfe198f6855ba8f995bf1a80e8bad7f4adc6143967bb1806abcf8e590abf26801b2244ed42b22fc25a3f248712961bece57c951a7e6485e4e4553c6ca8e61dabaf409bab6bb65cccf867e69054d0f621b84066e0f219d3f34702d9aa420139d0e421509e577c275e59fcf874c6acdbe1a741a6102e9f9464c478da4f748e9e83c6c3853edbcddaadc405b6e3a0bb8cc7f1d1dea915fc142bb8868bd9366b852fd7918f263e359a32d118704358481daf2149749c8e0003034d731cc498f0963b52301352e8d97f3528518e120c98f967812fdb5e7f95c63f57ee74bcf85af6a46e4d019e8a4b4318db5023c221cc603934afb720f8d4dd6fb2ed54700339a87fdf43379ddb237eb207ae32695bfdd68bfbc8fddec44cb117b95a286d37e49eb5ae809417641b0e7ca3fcfe6c6eb8ff424da114e99c39f4674c7c55376e0864a7e7ed14fec299d727f223e655b7caec63ab0535a3d14e349cd31247c70b4f472f4577851691f627052e77b25932965753d54abbb43e051ee0082c9026c74c450ae42203b2b47bd9c6880c284a0530edba2796dc8962b62155ad3dd2d6f16cd0e7036dbf80976396180a58f54e25c63a46b57cea14db74d96ab12d3c5e754a7e3991a0f3081a5105e3ded0cc4f3e83e9522a11daf3d8876f3b1eb4da137463fac6b2858bdfc14b1dcb80d0cc2dc3ea1e6c4c21fb0db448df5116580b6e05c40590d9240cf3521a4e8c2352f837302892e1a94c834369f51057c6bd385d786d3c97cfa524254632d407bf81cab69648308f9bb6c81c8b1daffecdcbc1a04c040d53b12e167491343c9244bd10ff11e5f2049c6a3621a5dae536c88de633c4556d5253f9d170516d98e0d5b654e72527d520aa0f16ad7903bfd25a5b1837c20d7ee24bb6e640e5cf09d0f98883787fb0cae54ca35f87d3bceb2f08857d634e990ff8b2a32daff820fda3a9423459f74c0e662ec0ec29b68fd39ea8c840160a94ce98cf1dcb405cceccea4317f8c473c0ea007198e6880ed9a8fcf4a83c2fab7ebc7be473aa0136eec07269d701849a0563825409352a3b0104a167275254581b92f503b8c2ea8518f386c8b19623ac78d146d58447b1d32fc08a355c93a9e77e16e99a9ccbff2593a2afcd7911958e9c2a3aee748522a3927e5e8712f11214ee84a11aa37e3a19365e9fe1746c86a377a96207a4615932a55eb100ef6ecde046f73e6a2a5b309e53ddcef26ebc6eff80d1e1935d93590974c0f1464dadf78ac48c95ed67f996f064bb784496ccc348cbf448aa313222be76bf78f292e4b81c1759dce6bb36adc3548e6825e20dee8fa622b0991e17915b8f8c1fdac02257dccc73dd8f002ad3061af9497b795b4d80b0084f9db04fe7247c9c0ffa97804feea0a05bd04ef0a0d7a6287c003033e40aab411300e60405b63ce460cbb452481bbc0e7c4461c193649fcab96ccb365aec8185e03c7c7cd79c094ca87856ea12ddc224fe4a8652c8a4b7126d55c0a6b7d70d25dd3b9e0711489252245dc9bda42d29c1290e7d5744b9225d4b01246aa98074f3b4c08a8adad3d23eae3cb180425d25d24e5ba7fa1392e3c45e92c7298e5a96ca3a719510ea523a78bab45a4c5d484f9ce679f5c7e26339f24ab053759973a37cce2bfecacb5361c0c0bd66a9fd3105b61f8e6837a87f8a2254a290ea10a9fe2d1aa7f228595dd2fe6f670b2389c9baea04359da53f024271ef25fb93fa9aa5be2662a96fff3e7696a2686435b2fa4a152f89a0e49a7cada4b30ad8c48fb003fc8158cfe90d24b314a0efee034d51f831a85f8c61e2a596954d4d70803ab3f3ec1b4d73a4bf8e55ceff43120e5c184d88d16054f6790590ff38ebf95f2201ec106305819f1beec2b43288995be639f3cd3cdcece819ead019ed9c7ef93eb51cd0d6f8f755420932c72769ce1c4fe8f14356b5973fc67714335cb4f3ec88ec299de90970f5dd73418865d253dada3aed3f96b829bf2284bc8cebe52c673a8f426b1888e493ff4cca36103a1009f3ee2c537e14fbb89d81313347fe8370035d514c86d40bcd159683b6053302ff9c96d679a30d68f33e669c78a1dccb4f0320df894e9a9b54f462d27ec08834efe3e400d221869df9cd1c220c1d7e235636327b4cdcc1d085beaf1c2113c30cd4e495f32e2bc5cc99d1bc637da41ebacdb543a2660bc70f20e9df33a1ad3566dd64a21b5475efa082e88b0bdf3721601ecee0025e3dc4deb67907de0c53efef0de38b020d7a42e71d4c8898673d0e1125f36859e1f51c1c6c185c7eca1c289c5848350a435c048cafb790486545e2c1e61d0c5e7071b13e7363184aba2434849911e806b327f01c4b58117ad9a256c56112184b25ff1187f54210d93ce74256f0f48ef5393044dfafb6751e71d80bd8822b506472b54caa1199a8817f15dcb0165e31a1a9039a00f9797536944cfbffb99801671d38f2e91e39a4efd422a11d0d8715c17562f42613a7461f90e6c42f9a74d86309d63c1c4e4877bf145f5a7a25256d64268cedbaf2c7661ff060d8207405e31db6c642717a6316b8cc16c27e944218dfabdcff02df4ebe2e7e7165934937e983215e16314a0a8d79cdcc05286cb6cb894de0d24c0069a9d852e14bcbffc6077234a2366823ffecb7f598eeb98c6ee7039c5a890baab20df2a49e4b2a59766d26480ac8330726356198778953cdd4b3eabb5400e8ba341271c30578f3f7a18a7a7d6dc6837beb0659bbf598dc4109112b7251148c97a06ec0dc844dc50f234e5258b828d78a6f364ac3261e2d2a60ba478c32af82743548901490f42eead5dd515a1f97d7a11704265f15fb8b7af19608aa9fe8509069d66c172e09f8291af272c3effcbcab6ab9fca97076e60628f93afe921a0b7e52b3748f14d5e1e3491281a5621d096f04a066f9fc2e306481aa260acdbf4067db93ca7a3f06be8e6a61deb5a681befc1d310b60a86c2c443a03c720aae06a260ab51bae780a53bd0222ba3cb4f927769fc084178f13aef04b12f5d1f74e43c118a49a0d519508a115f5541c00fc904813f3300135cb35c21c806ae3ee9111634d7f9d8f3c5750c4f84a06b5f3c9e8744444e590137f527afc65c7a02750d0a08028c3321594737515e0235dc67f10339f949467c24a7c3d7e21eb99a524dc37867bfc1af616ae3613652010d5cd4d05b67e0b2165a472b8e07e0f35a4914234747f81c67fb88684cd24e66ef858f545da8249b63525d37ffa641c20c5d6bc0c7b14e0d3155cb7ac6c0c5bd7955de2430101f3daed09c603d7350df9f4c09a813579cf234f9feb00117b9507bffa46135451bd260b384aef457b1bfc377943099bf87753ba518e0a2795d583b017f217ad58e18ab08ef3c2757d81348959b1630a97cac9c233790d84ddcbd94a404001295a34115cd63ee9b6201c79d720014110c08f40664d240566c2bda1b5c7bfd148953a56a4f16fe1fad1a26eae501b49962a50157555bcced0460fe98e3ec22c20ad98b7c8aeee7c284df811bd5e36d021a8defa91af1177e25391ebd5e27a6946184ecb8845aaa2951f2370586cd6cfa5fb55a6b6f9539fec900388af9224beda6a9c2c8bd12d56f7431dbf5a701948eee12f1a36fee9997f7d1067ef4accef88a92088a254f5a39287927a0af24c43f9d9d54bd043c404182d8a7eb26a7d6c5406cb700b7b47ee9d0f21d5bb3fea7dabd72a92a41f5b877318f729aac74abe9160a25d123c13af47541a53479e17158cbccdab65970878ced3a0ec1bea7c997ba7e9f174d73bede04133368b3719b3da7b199f52df3696e6f7498992c79b6561aef1ca30198f2338862f1df0a60a61c10156025dc54297d3ce78d848d3209c8f49b24dd8a036f7e6f96b1755a8a19802e735a6fcac53c7fb899137b083b22e5edfcf46521b0e6530995ee7ae497010c462ac6d2ebb119a04487be53dba42083180fea1454b32bf2dc165dd439301729c7d0761e49dea2717f64673411f439057300b270c39fb61596b13d0c10ac48a4cbaad4a80c7ce6e4f80ecfb72a8504071bff29f75dba42709c43424583f4f57a6390c7ff28c4dd06b91eda67e497bfe01a8edce4f93f612149436c2368c82bdf9b7426716ac4fa1cc5e176126f9a76516720f119d6637f51bf27e1efe978aee0b513920d151fa2928031df16644c42b622dd6158f8274e2512872bbfd6feec1e0f4215e0995db5c179d4924bd25543a7bf9906fca12504fa3a394d413a56ea2c47a4d492c343f1e5b2f90241e66455d70d44a01b214061f6f862689c75bf0d80b1ed54b150fd5be1ef1757fddffc72716ba1fcce9a4f62872cbda99d3a80cb7f213ca5356ee6f36c016c52259fffd302f28b13cb23173f0aa0030abd1412f6d972ff5590f8d67e45fc7bdbb6ef9850e9b93b40caffdf968b97165dcf725c360df884d2810084e85584c8374c4b5c6f59b75c4362bd01d2ce02d2e8c9d662b640b0c0bf76641ea4d0b637b0a97b2e958f0160bdb7b17062151805156602f7013067b7541e85119467240c9124e239861246fc75108cc77596c5d5e63b143916f5e221175ee1295ceda8ba483f321bc5c275aed647182b655a0311b7fc4da579f939bb29e154340e7468adc46f198c96c64ba9e3914954704dfd6f831cdc13da1f1281a46879db31a3a5a9afcd38ab73d52521ed4a191a7df8d845c3b2b4fbc06585c322c4e4e0a2c48fe3833cdc6818b6f02ea9cadb61619c8525302055b594db7de4db4ded5b1e8442bdbe73a756c65d56d089c9535301867813d5c8efce87f78eb745981415a0963208730d204e2b912d1627c7a48e4163aec994d160e678a7bdaa9ea4e8659b924114cc5d9270d57a6e4ed1f34dc5889846260fb2b16420688ecd0408268014ebe2600eab0f03a790612183687b195f9f6975fec47c3146a7f816d1d1b23dd610a23acbbdf419c90f19c8f7028fb0f3b1c9a64a25cc2a7944d9bfd29f76aa05473cdb075de04f6a1ef2b809d816080d885c6036c25f7d26f9e903e1c18d9a2c690f2648d8bb050da8c4a2e310a61b3e61f9cb08f7fc46528ee248d68174c490fe837129014ca4e0ec3506b9f0efbdc38cd5b39be743d4a8e65d5063c1a880685899f465b4784a730b496863443775f0351153267d71300d5c79013346b800889fba4d8ee1512c6f92b317e50d753d76e4c373c4cc2e10cca8ea7c2c120471e93a32de5842ed50479e062816ac6769f781a2a47fd5d25b9dd7fed580f473052298b8770e50d566d2f9c0c8ff2aa036f7d6a48a1a538a6258e581a5f0e2cbd3fa9a0249c8d6122b60b6aebc811d2797b44cb80ff27825e2120ceb43e045140449c346dceedfd4f5ccfbc488d65ed8e282522b6b2946f00fb59979609db58ea6d03c2569ef1a1340719cf51190368b557ba40fe7e21711349b696b1dc0012241c428b9420b7c8811f71c35648c9da23ae92f952f8226a848ed611deeb90a5e209ec843d614df82dbc0a3fc2acf05fb8164e0837c2c21ba24cad43042d08b6824f43da41fe44ce9a6bd66a111c7ecc8a84100c0990f481cfb0b0754382a2fee9bdae163e7f2c24072fa4ce9450782f33fa58c63eb75bf7e2c420476291243c88091a2908a405e1bb17f54f675d08ba7c96204fb097dc26bae259b41ae333a5ecb4be0415020b24eaa09301498f69898642326b1d2aec9755f26846ee8cd210e5bd491d26f8cd808656e44eca690ae33e9b6882188fb299327dc88753a326de4eec2913dc8e5095d7dcd963872dd4e95cd56468a83bf42586b56803cb0063cef42b41c5c863e691d22102acb329ea2d47f5018fff59caf96ef219e525abd6a29599aec65dc650b04c327c1f454af352beda3a5e22ba32eb0fd7e44d34fd07b5d2b311def030f7caeede5acc3eb5536a2242406faa2ef235099680394da385e27887c3265959f760f80980c611fd3a9566627a32ebbe6a165d2c00d84aba57944f1ea4cef4d592179623fad97d2880e600e635311233b973952902bb62b658a7c5184a9b21b370b8fe60d56c3236b2a00327f4dc556746a4323b0d17559d6fa49878230f58eb4d01f3faf44d67e5009c7f33dc416c018a58c551848797444988cfc275a8bd2da85a363b0a366d9a52086722cfec3b418f8d4702f636938caaa574259fea6148382440c79c528fc09190b0edbb0b9cd02c8dec1fa66797405aba8d05a58328e3ce3b37804b24300a9f3b875a2d8345be2d0c30d9e2e40a55a5e0760de9f02a66299116412797f21adc770ee9c3e5edea62d19306284a0b9fb72c65f5c1d14b54ec9cfc9a5080a5c162be5c52eb15930cb3faae303323fdc3ed1780bab1721fa05c9b72dc1e01a1c55dc7bf16a804b31c75b800dd1c6df8ca914b40022f0b26130c1dd8083c79fc546fd6f8eec49113e4ace29fe1dd1b40a255c412c0986827e8ead4a25d0b36f993f9e187574e9167adc002ccb28fdbf0f1643a3a91621fdfc0d7e7de909600e8d9cea1b92abde5460f34b0d313c219a13d62137338ce96cf2c3dd6c07601270d178d1d95e15d042d8ac35a5f2f0aa294aecf4c2a681fcd1e92da2e6b5081f766f281bfc4d27af3152b82f1b1427a608da3dd2751b35b1c6f9ec44bd57036974ca64ea777ca0e7c14fcdb91942455d7dd46300c973166ce03631fe6bc8cb7b801a0a171ea0c086b474bf6786d7462ce501a4161b1cfec274839c29599c99eb10b2542a10f52fbecb082e9ad4b52657afdd8670043a599568836db4903b1f1b225dac8686c7fcaec5c47dfdbacf94954e506fb6013f8decf2620b111829b3902b0b1b5035b2d77c3d79ec9661378b51b25d8016c670a4cd0e68813d9933f8d024e4d4eec4ea895fdd6966ab3817a726cbaadd321f029015a4f65bf482190de98a213feb9815a65caec141e687189d56712608243c6982c7eb0109894967865003a63b1f83f84f39fb1031a1662b3dcee42e2162940d082a25bd3b3513a31b20c2530e9477a4fe9c056a49f9f5ef36461daaa946f289485470061560fc4b7bc110253332e90621105bca9d8d9b386a7ad1a675b15206daaa00a2887a7558d5e9e3656bbe83bf25e8964558dbdd2344a0d7c7ac3ce03750bd054a46fc29e8bc11d97c13cc71aae8cf9fcd03d8c784aaf62f39f438689035ef0f96ff406b4e4b98473cce6b9579eb16ec327bca6bfbb6499da22364a10312ca2bdc5b08344e0e1838797d282b8043aefdf2611f84e3d717e06bbe1f6a6357fa453852d4a6407f4f52ddd4537c6982a9e2af7c5901d2c43b1092f0b3819ef564f37a6a04a8d4d81a1fa7a346187a5ce904d719ffc6efb74deea9ff6106e4907577022cb1868ee833b23c5cb608cd7b0fd308f207e4b9491538069991c3d1e00861801efa834f6a89de69b647fcb9e648c231ae80cc31147fadb619e1b480dcdcb43b58c1cdd65e0f8a25cb4e25cea6123bd5ca4b60eca7b0a56a92c4bce58463ce48223e21de84747f308097df3686e91605f7971e1e108eb7b974b29e48a93d903d65bfca9557523a3140b35d7e12bb234064d7dd704e9e6df68785545f580356d1cbc54e2e53ada37445e24b173370a360ade6dd16cb994b1cc358c9eae0e1a2b26361b66ca45216dd374f8f9d13f6b8f871bdbb9de2445afb3b04104b479fad1779449a092b9f154d4302cdc0b02ac5f660fde041baa3162988bdbcc002de380e0bedc3323d07050045dd3c6beb68208850196fa08e75719335321bef6d8bd7a22b784f4e1712167cee0e471223c23a5c776ddc9249a5bcf4a71090dd04e188f7949481e21f33ef96cbc8c2b96db23b55f6f6358347108e185d2e4f93f0f1120aaaad6664dc4079e945f02553df450e07a40e5f8ce3ddca2ac00b6fef4e44e6a805d241a167ec797b702a0880220491597fff8dc17b098f00bbd13516c9469b39182610e92e02899852cbff53c19325697d34d573cfc48c76771155b178a3cc8e56de086fdb830923674d9707a3186b617b29e04466c919a8e40dff20c41baf878c79bb1b11c7ad855de0864a0d5b7200fddc769a6364a32d9c922c56242609419430db85a12518c273ae4f42d96cde2c90d7ce90f47b92d44821743f443ebcda91c62048588b59c67db69e7d45355e3727cf0e42ee7a87382c09f94631427b910681c5330aa82c01cdc0767ae4c9cfae5467371bdd3aeb85b2ad2ce4f864a9db95ce6d71ce22d22188cbe5b4132374ff52f40495c1e914d66e03ab65cf49f79dae419102e5c47305c04e98ca4da7c03ad1adc0ef4e3f841068dc861f24aeddb62b85bb83884f1aae70fa0d6100b6a087a25ae0098821562d46db8bac21b4c62a9d093abeaa546543429f02ef404f1a7d32a343cf94f468bc9798a594756c889aaa314beeff83a9a44de5efcf9ec528b738c7ff559be327244aadb8b67bf8868ea7a808a9767929634c99556563cbf7a72813e570ddcca3b8b6ae791702e38d793270b2426dabf358627e4d78f777969adbe8af345a845d211b3a44fb070cd139ca575ddf27f2d25f23b67d48444357ff888f60008015f90336a8d59969c005e0cbff7686203a95207121f3ec5f691dae75e29a5fb83a82d9fce81cc6e1f641d445380deb97657089c741218ddaf56d3b1c91493041bd1aa3386feef390d8f724f0c070da501787f361ea0be9914f7b08de820ce6d645199cd232cfdfd5df43f76d37ded55b4c512dead744f18755f300aa1676cf627a08ebb10e0b5645493d57ff93381ee7efa7eabca6410d49666e884f89ddb65ce67eae91725133a9cba5ccae5f4ff8c3ddd9374e14e9be46e510c54cd9abe842384b8973010117474ea30c673f0e0d6928b5ab803de511a18d851dfe4253c6ba6182d9398c2443fd5d26954fc1c60ef59aac3f7b9ba9bab19796e229b00dd517d61ba51a1ec230af124a97fd046ab2e9282a8d14915145020663b0634bb8f8b29c0a0e86c23145514597781bea48cf03f300daa13dc0c5256806f8beb40b65fa3f8503a59af32ba7fe79939ecbc315fce4f73ae7d539fc51df5c81f208094012c89023e1bc4920a3150b46439fcffffffffffffffc2836884b6196b5f2699a4d43fb65e9f93292599524aea8a507fdb7bb1adce6bb700705002370ab70a2a0b6783f633402006064ec07808a3a48a09795f762b7b3b7018c25829438599f9948102331e8d11b081a310e628ae57faf7251fe28430560a3955f2e8312999580d1c8330b9a85c29994ed23e98046112426bde9d754e218a0261d07d7969a132cd4407104693e521f7d357d7e40fe60ba2e53fb473d4b2f8c1a0f7df345674b793d70763e86041041737efed2c1f0c6bd97abda71bd6e93d184497ae9efc5c8c9f9881430f063993f263296112dd1603471e100d0e3c1872492a49b12d6b2a5d068e3b20871d8c132f3b826ea7856a1403001638ea6078ad98aedfd9a225a7834955a4eb9a373122c77d1b9cbe2570ccc12c794be95eb864acd982430e5d8bfa49b2f3ccf19a0c573150b0813478607860666046197130fe7552e3a1974fdfaf070e389842ee11d52945d7f7eb0e1c6f306e8dbe9896f466a204898101316c6c1b38dc60125f5ab6e106071bcc7d7593ef53487a13101070acc1bc9ae57741d7a5d21b047567dc020e3598d5c45354f6e3ed48c958ab71e36f50461a36520d1b67a41a957fa38c1dbc7f192420f63ab091032788230de61093bb64954b86771a69f49a064ca08292010e3498b387c52461225f83be41195380e30c061dfe9e2fa4b012a5b4c1e9b7810dfa0635920d1db0251b3ac0097098c17055f24d25a70b950f9571018e3218ff928509db5056bb93c13022fe95bca4c492c818cca9bbb2b7544c072c050e3198a3e536e1f14708cb3a0c869c336554cd4704638ca154e00083e16db4d5271d9f274f38be60f894945f9dd6923eb91a26070e2f985267c7e40f2a2718630ce50347170cbbebefa72b574fff655c000b1c5c3057906a7f41733150000213ac81630b465739bf1b09deb5d7c0cf000187160c2178fac64d9e4923e1c8827135ecc297b84ce0c082c12ec7df8591db298538ae604a5a44856eed2748638c31ac803c930bf1c55392048e2a18c73e575e388d3d1351c1246fb174ef6939a6604a634244106627cb13a0148c56aa74a9724f0d237044c16416f1da547fc4fd13148c21737f36436f434d4f3007a1baa7f3236278de09c6ebeba4d7e72618f29d0863ea7cc276ce04d35dfce533a14e657909a67abdaf0f2b17613d4a302515d45e5e11d2c328098657fb71fb2072b86023c1d43eb69eb49e490e3a828d911c46e02882f9b292522907217663250229824ee710cce152cbbe49eef0d86e091c423059ecfb750ed7dd49b5040504c3a419174f9d4812820c0cc35f28ebd2ff22ed41c55cd0fac258226fe4dd30f5c2242b2cee93887861eaf91369e3ad257476615041d72bf2a90b53c8ad203ba48d94839d0bb27857c8d6a6ed1e63d973f4c5195b3109e38db791831a4b06a100bc26cc7b5d8d7c0b835249453ce7d71686fc513c4c8749a6c5ad85717f84cc979b1626d9ff9d90d35c3ca9b33059c8b8f0105516c6f8adc94959c82657120b73b00ea33fe8242c0c3ff163c40e93d542f015a60e5a34b55baebfe40a536e919ebf9f43c9cab5c260c9266dc6dd5cd28a025861d0cabe52d12ec53dab30a8d9114ac7fe51492e551844a6bd455c3f1559292d3fe975428521dfa7a97e855c21994e610a23fb22a27fa776c9146657b3acbb5ca6564ba530a5ad0cd1331e24a94f0a43f851ba4cedcd24ef4761f2944d5d52e5a5f14561cab75a290579288c7212dd84b8bd8bb183c2e435c2528c2c132be29f30bdec7dee2f1d2996ee09d37e6f558ca4d309e3a5668d50677db23c9c30c7a9d23fcf3e56a9b30993d25b39450a61f683ad09739a8a093a6974084225135ad70861c23023f1b2043ba9e1954b98c56554f6c92c215ac268651523e71257c20c5921e694982961708965563a65ad09f224cc7e966c7410af2a2749984f6915356b3d267a244c324ab456f81bbfc842c22897bccff28ac7fff61166d1eea0ac3ea7a4e408e3248addd908437e4f759e57216c4698e4979658f2751127a1bc3c8408524598724a1059eceee74b4d84513f449c7017f4e85111611eb9172e589ce8f9218730cebd5d9e53223f7c8821ea1b359284b4904298e25fd8ab6f5d8e242184d145dac5f828a195e7208c25f47da49935152a8230d998aa88b6a022693110a6549e4ad47f29b32002c21841cc6bffff07c3ae78ab5ebeaf1ff9a130eda7f4b3de0793e88fd65fdac34b870fc6d221f7e2a9f76048fe313c62877f5b4f0fa65879a62f25793089ffb7d3e21659abc38321d7e4ce509d456e7f07838ef2cc0e66754f2289b7eb60f4a03da4b42cdd4ae12328800ea6ec10dffad91ead3207b387d6940fa2a5c4dca2003998fbf24452c943e26048dedba17f3412a4a100381872342de35b6fb9737a83f9822ef5f0e979544e6e30ac6ec9a888ccf5d1b7c1a443bcefbd9899be3e1b0c56c235363cc6f885d660c8b9f9e94ca538b1e36a308d4895eb2fea6930aee47cf1a9ffd3480e1a4c419c9ff9aa256db13e83d9a468c93035aa92d56630ccc752132929cb099532982607bb6b13e299a3420663878430a6372a5a5dc660f4dc29463c93c560f211b978fd881bb286c1385b51422a3d35b925188ea12a1b694f5f3077aa7fb76a7fc88f17cc4976bd85a958296dec82b992fab64acf3f9ec305a35795aa0bf5313ebd05e38d57e70fb95c49755a305e3a799eb3ea3afd62331490057387e4b69792d2e94fc282b1ce3ac9f8f824e2e8af609219aaa532defcceb702e2773c96fe1155c17013f594d4ff3c21712a9884d6c4b4d27fe94b5330e80af627642b05937cedd25e9d4c27d95130bb752cb3ef0a14be74332f215ee509865b8f717146b632632798f774cc12fd9eac83b609667935ed977a32c12cd721844b30b5b2e112cce925d6de3a68195109061d52e6bcd905e1d725c170fed1c727f9aa9690604ad53152f8ed9e9c1752c01130533119c170a942d07d9db37d9e8a6048c27a72ca257be3f40a208241a28ca88a91ec29e7153004a3a96895537f4a59880284604a9e7a3ec5d472134ac13088a50b297e47c0305d4af2c182d07809fe2f0cd739d4e979331dfefac224b353a530b7f6c278313232744f5e9842faecc5d83995f3db85715de5e6f279498c5917866c15c53a56f8503a940b53ef4e8aee2192880be2c214645e97e81423a8945b983dd6fb920ed726c01606196d73f772511e24a016c66eddbae461e634946861f0cefb984bf75039cdc2a04c58ce963c5bca7564611cabfa4c11722c0c631fe729fc75953861619411ca530435aff6ed1506f9f3a554541235b62bcc2dc122a41ccd3071b5c290a3479a5821ebfb618549fddbdd53bb7fd87e1506cdb8930f2a5bfa5d15c64c4d0941881cfa74a93045319d5efc42a830e9b85fa9273f8541f25cc85cd85870dd14a618e1d44ebf530a8350572e661b290cd1f6dd763dcde8708fc2144edc4589708bfdb6284c7a31bba183426190dfa1e6d2749e561914e9c4cbded922fc09935dce8e16b395728a3d614efd6a3bf2e2f5aed2099356779ccbe179d3b39c3049d4ce92266fc298b7d6313ff5cb6435618aa73aaf6d24874f66c294a447b1d4b09c53b5983025b30ed94a5bf457f412c6dc52625da3a3a65ac2e09f442e91c4f38e48254c395da570e96927344209e37dd09fb2e53909e34bfe78a6e22909d32789332af983a57889846946470d4f199e119c10f7d596c118caf4f2ba875fa64a06c3ac794839e1ef434cc760fe9338b23ea204f5a71850296c961695c530983774de5fe5c748f20383d1d2b8cee7ff0be62496e339a8a4eb6e73720ae2b36c85e5d105a3c950932ea9d86c2b3db860b6d149c731d3cdf46f0bd97552ab164cb26eaa652b5ee66264aca14fc306b2db814716d0f1f3224c23076714d3838207164c65256e94e7b42d3cae602e65639ea2468ab896580d53567858c194f74a4df8ab2c964c457854c1f8d1ffe2ab04ddce122a98cbd2c80efba78318a76010b2ffca76c7837b09111e52307e89119b76f9fcab838228182325fd9b955216f0808239c78b9e9f72f48f952798e5ddb479d0179961999d8387130c3329cae51cd63bc96f341924f06842422fcf621c030f26982485cacf7b48a3a3843c96607a5753b31234069f87124c51547c3cddbc637cc85813030362dce06d604c108210f095f64882073c9060be8e707b3a3df668c97dc1e308c6fc7cdb17ebb81c3f848711ccd142463df582ae24d2078f2218dcf334846bac6f8ef2e0410453e7aa4a975b1a824988bb4e6ff11082e1a4ee894a21f4c54e308c7dbb9643b9db48f081618a37b12e59857e61cc53162d5b12b71c725f186b3ed552c3fc92e5bc0614111dbd30ca8b0e49bd041d7288f0c2fcb7b26f21780a964cbb30d90911b4a4a02da8ef7591e75cd92b9872a1e5647fb612c7854105b311b9c27912b2dfa21c31f2a70eb3a0c316e6be9c5417fc24574a1203033d18630c5344472dcc9d35dbce9456684f6961d02968a8b78549299a85c15625260b53ce49a7d873652c4c3ae5ca93ccbaed14c2c2e069da74ce64e5f4b98e5718bbdacf7792fe77571dae30699af4371d2a3ce7dad10a434cffb8dc69647f7d32f6d3384304638cd1c10ad3f864356bdb5c0997d3b10aa3e5fba0eea49e19e85085e174e8f939c041a331023c0f74a4c2e4329f7b2c8ad0b1827fe84085a923567708ba7c63c5e544c7290c9ee6f24254ccc841aa91c68d33c8404d7498c29436fa417c45aacf3db071c6096eec253a4a614ec173e62d78e578925d1f3a4861eebf35f959234ffe2cd101d1c1758cc2a4c39d48e695547aa92c0af328e195b2495d0c1da130689c27bbcff2f711ae403b40610a955eecf3488aa1b78e4f1824071d545937de0639b041b6c3132695b835264f85d1a771e34590fa848e4e985e23453f51fe196798e0d4071d9c305dcbaec7db0f196bc4522a2303d9b10983f6a94b8a569d674732d68e8d336e9cd6343e043b681390232de8d08439f27ff220642713863989f712a29fd26562c2f4c94eb2dae9507fed1206dd79424a8f9c3aaab584aa23e62b4db850072a61d2da2ed692844cd94dad83128618579683b02c49e19540c7244c62419c4823e37fca27634d67604619356ef40cce6a7448c224af575db2bb3fde8f84e1841ed562112b749f206188233ebea894479867c4430425b28e30c6964e12b2cc8d3027dd2108310bf3fa3123cc5d2b41ae04a1e5aa2dc2a4de2ddc27e6841cd48a30b6c77f92e93a237212619ee41255b54584b94aa5646f7934d0061d87304dfe9027e6892779ad210cca2f0511b9301b3b29842959ff7e8ed157f33c210c9ee3e792131984399a668c87b06d7d16411864e4a598b33810a653eff944673e7c5e0161bc4e3aac94bef422437f30be75886a79b0cde81bd4b071460d2074f8c13cd79ff695ef1d731f4c3a7ae3734c5d7c9b3af8601eb153224da7875adfd0b10793698951e93f7e435ec618a30c1d7a30bb8952ba9245ca65e7c110d79385c8e94e2733e1c1bcf93d16376542cc10093aee602eab8a15254f7630e959f96c48899192b6a30e86a0b23fc71ef5cb693a982559306de95547a890be3107d365f5f021e7b782fa7230460e21fff563bca6280ea6b9601eb7930eda3c380231306028820e3898fbb44d77fa88625f7983b9e3267c7b753adc604aeaed6404a196efe28e3618741cf16f3a5eacc975b0c15c322a5b6a0f95f8411d6b305d57c8cbc147fb5dd5a106534e1777429851c971471acc29b376245af09c67d4810693b89265f1e2d67106731e0f3ab23aad7b3a7598a1a30ce6da8e961ef9e62fcf64302755af9274cb8ba5948e319894a5d457d9573d9a5087184ca6e25e59f2bebeb07784c17cc97b84ea297580c1dc9e6309d9d1449e24ecf8823154dab0681ee75e6387170c42f99ce890e30a7474c12caa62c988ab7570c16ce15497752a756cc124776fbc453fc2850e2d18af5ef74d05fb3897ecc882d9c4086175fa43b0bc93b156b2091d583078593ce541e92041d664dc681bdc48cb29745cc120b664bd8fb0a07492c6df60247458c1249e9752be824856e111dbbe362263ed460dfc8c8b4207154c4ae7f34a7491ef9b2103c10180091d5330c9deee284b21edd26a850e2998d2547ab31ca794fe3f0a86ddf2c841c72c31f291a1030a26a12a28efef70161d4f30d6571e7d227a6adc103a9c60f6d4a347848409b9e464ac1513424713ccee275e4b54aa850992b176b68b0e26984daaff4eb0cba3bb97e139406f63cd3a96607cf5908210da11a14309e692ffd68cdd8f5a0d6446d748c3df868d331c2f0523400108c6188334a22309df46d01e420551714400f0840e249842fc3ec9ed18255f54888e2398c37b12570949449f91fdd0610493d23616ef2f8fd277923d7414c194ac2e2bfdd9b67ae8120f1d44308b4851b27c5e8e4a7a1d3a8660f8d7c8edd194086b4f87108c592167f4ffb94bce81812318c6eb30c2e2538255489bc0010c773e8ee46f512612387e61b60a539dd86eca26cbe10b47cb9caab16ad4c0cb40a30c397a61baadf06217c6a485928c355e9894f6961c612732d65e0767d84083ecc2fc966cb764f9cea582347270460f1a0417c8c097610305638c91460e6e705217e6fd78b23d89989eefb3326ca060066694a183336c90a0ae56c0910bb3e849cbeef87061f25c953c67d3e9d3ad5b98635bf0cbbfda41cdcd610ba3951ed39da487ad7a6a610acbf38e60e1bb744c0bf3b55e165521b48fa924143866610ec26b525239fd39aae39085d9de544bf4b2da4a411cb130e8b855faf6179f4212072ccc398654911b59626285e315a612ad4ed656b6256b0e5798f4a7f411b4b3f5c511472b0c2ff973be37915cf671b0c26c2f42eca8a45444876315e66d13fa626dd35694f4c68d37430f8d326c906abc0eca4011b8a0060018c1a10ad3b6c9a7f6df6eddd2e04885f127d68358c5b67d97a83064b94f9f2646d0959392e31486590f49e280c314e6f46b4a59104ff1692e85397ad22108f70bf9107c3446200606cc18811818e8c018637090c2f8f964a8a8898ec214d4d57a5f885887958619efa2c83cd6fe509834b6d4723c670f7080c2587ad243d076291f1232d6d0b6031c9f307b780c612d2f7a47ed9e30a5bb1c1fbd7119a70c393a618e3197191ec2bde3be64a0208b15383861ba60e931c7b38854934d18437877140fe1ff276263e0d084b9e4e64f503365c2144786b22bd3bfc08109c32753b1d2449aabc0710963aa9ff097fc1de32e352c6132a1449f0877f996304725cc5f9574ce7f2ea38451ee2eb6fb6dd44426b1fa9998ee08faf693b1468ac02109e388ddd115f73f1a9130868cb8723a1f27fdcd0109a3e9f0ccfa0d353a35ce40c6f10843d2ce12ea16ff53ce7a60e3735002fc0087238cf1d9b2875ce2137034c27c65daa5df7eeafcba4119c8c1089350a224690ba14347ed171c8b30c8336d51fab4894990220c23f52585ca3825a3cd011ac57024c274255b95c389c958eb341a751a9d81b3438439c4d39c471fc958db47b7349694868d336ed80dc26883594250dbe1c43ae80f1b4c6147aa4593abf1c1b20653f64b951f5613c7623518bee3dce28abc9d531a4c924288cad0a2c198163d480b5b9fc12022e6b283f97d4a9ac110abd2fd7bd3329854dda4d86d12321852f27ca3151e4f98c6600e537fcdcf5a621783b9de47fe23c9ffcd1806f37cfe9f705142550583d1de4b46f768651208e30ba6204743ab68ef05c3c758b270739bbf6417b0ce26d404317a4789b96012c9ea2b56ae101e4f1b67e409630be6533284058fe4215ed1824988faf82d162b744a66c11027cb4e106e95ddf5c0461a663c89411858305e5b95eafa3afbabac208c2b983a3c5e88e65ba1d5b182f1f397d896ac963ba5aa60aa160b299274cba57c2a1854851439cfc147dd3305d3dd7b1493659182f9530a2e63428d82e9f4d9aa7cdfc51625148cb17f925372fb1df5134cbdd96155c24b92f085e104539ae7f5b0939d208c2618648616e126526ecb1e0cc26002f6de273bff787f7210c612b09453c4f7ca49fd26c377108612cc7d236e7e157ebc2c1784910493fca9d3eb9d173f289d9186191d20411848205dd6da35753a0c8871c836d240037b10c611380c23a8f9e93a240f855104d34e6e65ec056da4814618443098dac9a93a23faff52e36d80c60dca40038d36e30c33126da481068631041286104c31a2f5f3920e1f3a45c69a06c6182318a6687d6e23929c6c42fc018c740827dc7d2f4670f0f10b435061ed93529523a4f6c317c6f413c9d94ea6ff5b7a61ceb7f0218f5e0f0b3f2fcc25ce639998eff0b6efc25421897cdd7186c3872e4cb32729ffc80e2f415d7261f6eb38aa53e592f4d4bee10317862826afec736b753bb730974549410715a4a6986c61aad634fdf079265abcd4c2ec914ad6a79fe546190b820f5a9874d8b8dc7df92c0c2a7fbf1fe7e9e2c8edf0210bf37cd995ca444b2cccb61e210513172c4c25b994b6607dbcc2b415f9b5a4e7ae305ef0fce941eb34444a2b8c559b16174197a710b3c21455a26bcf9fac45c9c72a8cb2f19fb2a8bea99c25636d35f0a10a735f9a9790162d7a0eb1c0472a0ceed9f49da77b8d7f64a4518619638c199851061d3e5061a7305d4e397448994fe2ae0f5318258e38f9a92b87b7df818f52900f529852860a9d6db370d9761406d7d1511db449a9f0210a539e3d95f2b89e1825e64728cc215c298b1eb2a030b75558fb089553840a0d1f9f30ad87917849a924af2f9e309b6993eda4e2de32a265f8e884794543355efb635cee8313660f3e3a8470f98f4d98ba93c8ac905532d66a70a3d3a8516ae34313261d43cbeba578e1b3f6e02313e63d19c1d2363624e8c607264cba7d9dc289741f3e87c6c7254c29e5cb49930ddecde81b669c618605c618037530c618a800c0c587250cf249a9eb590f495755ad84c96425bba4bf38418614111f9430ede8a5511d2d859c3a0e1f933024cf8ff6f24149bc21b14f99928cad8183bf71fc1109d3c70f22debb95c6dfb861010c70400c0c8821821168a007e4111f903048cf8e2621761e611295b91a164e7bb54a8b0f4718ed3bd88e078f7a154d679c6183046cbbf868842145f21073f11da45a186110f727721e7b89f7e5224c4129ad7f31354518feab5a23774c909927c2a4cbe646cfafb347bef081088367f7202c99de05391dc2982ad142fe4ba4da785cf830c47d14c2a4a56321f444de9e9d091f84307fae3c158bba222ae4c2f0310853b84bb5753da7eaf282308a7653e13c53d72a1bc147204c2d9e1fc94b8aecf00c3e00610a6d12dc428c94feb7910334ce061f7f30ab8974fa6247c6a9948cb51ef0a08c5484061f7e3045494269fe763efa60922c626fe9bc2284341fccaaa6246509eb88f51e0c1f64549c9fb01ecc67b222f2e7c98339c99e252d953db6bc2a7ce0c174f59323c7506175d28f3b98b3a5eb10dba10f3b1896e245855d4cc6da1563c1471d8c15cf4c7c96243a986c52994d1a1532d66e2033fa0c4f061f73302595836f9c0e231ac21ea45106213ee4600eae5931b392ce103a7571c074459cffb87e95ac81c68d3a7cc0e1f5eb9873667d51926f3042ce7c3593967a4fe30469860f376479e27ba54c64d369433733f14f48bc78c992870f3620ff392c8d1cf93da99e1a3ed690b2a85a97730a4a203ed4905645f0f865aa19c23490beb6f78325870f34a0426cbce362e9ec1952a1ef74e912bd916f0d1f6628ccb9fccb778847e7f0518666b7829ad193c2a8c4c08018221003052010030377f82043aa6a219e923de1630ce82efdb394b23664031b29e0c387183c73dd0b27bb33d20b43aa2f1792b42fbe3330986639463b2baa46cc8ad9ed21044be1e1e30b065162559e23e9875fcaf80037e1c30b0695b3e4eaca5df615ea8239a8357517a16142ee5c307ae9f270a65aa9287c6cc19cba2d651bedf92ab5160c9264f68d05350be63c31dfca7a7bed2c5830298df89aee6123c6750573be8e2ea2a276df3d2b186c4bad2aac5527cf84955f86f041059350495dcea65bef43ff988229e74a9db746968241626b8a897a11d1520b10a2828f28782962425a8da806cc28a36d6440a16008ba744cbace4eec0b0a1f4f309dae382295b897f0e104939abd0c611e84c930d9f86882c707136ef4e06b50238c8f2530e0430908f84842033e9090808f23dce00602f830c28d1b6f038e8f2214e08308370881013e86501f42b0810d1a0310f00886033c8051a30c342c40820f8f5ff8e2d1195e2307bd3880072f6c6083f60178ec82c3431765a06181303c72413c702186c72d08e0618b5a24c08316350ce0318b1a65a061811b3638e30c122cc043160ef088057bc0a2461a08f078c58d2fa30c342cb0810e0f57d428030d0bd8f81ad83863056178b402fd0c66d0288880072b6eace2c3431509f048450d0578a062001ea73080872908e0518a007890c21c2ce6e7be2c29d6ef0a048f51182f646ea5e4c87fe692b1766383e0210ac34910d533b146e68743612efd1a4943e5010a939795bea825f4f8842188fd91952304356a2a62a06003c80c0b8ca145f0f08441447a4bdd0a69545f88e0d10973ed6b856fcf17365f74c605dc8c1c68608c1f7870c29055b4e38ea4cf61271e9b30d7759ce84e5ae5c08d31c6f0d084a95b2e4fe4f4f1eb7c076760608c3170e0910983b25d55cb63c00313864b52dc7c3b889f1cbe81c7250c49dec48a914535f0b08431aec4884e1da37b66b5107854c298a5433db59f2861aa39cfa5b233099387081e1f4aa83cc2968479c4c4c68275f49461240ce7f9644e529ed35307097358f12e91bbec3995c7234c62f27eec43ac7a3db081027f1d98230cf9449a96f9eed108c35c3c7d532fad6e71469894322daa1ed4a5057711a6bb0fbeef29238ff03c1461b2e8e2315f65e75d7924c2b4399ea59727951ccd0311e6e46e2a4832a14584ef3d018f439836e5c3488d943394da109efaa8daee17c274263f8f28bbbd9315214c725ff9fa3482463f195c1e83f0f36c49ed3c046188752a0e84498a560c08737b96f18963c2e61a68dcfdc1a43b82be9b14fea94a7e30e8181f6fd4c99cfbc4a30f06655afcd26ae8755ba306fd364eda7af0c17cbd22136b2f1278ec616d78e881a453120b0f313e66f8028f3cb89664a747ad8ebefd0a3cf0606338c2e30ee6203ee94f6ea2ab82871d4c7bb22fb6f809cdcfa981460e6ad4e08c336cb0251003036280400c1480a0e8c0a30ee630d76e6b13a2e731d1c17c67d7af1e3c8f39182f088ba24c720a49a1871c4cf2f9ab3a2996a59c338f38a4071ccc39b669b57791c71bcc7daf6e2a2fe8f627b3031e6e387d8affb63b95b6e191a4d54b2663cd6dd4e881196d34f06003713f69d3b6ce93dca0460fcce81a37ba3230c618376cd448e346bb193b3803a1c71a4c6fb9748732a9c1aca743ce97143cbae9a4c13c729d2a4c84892829683097aa8f92e44b1471ee194caab2ddd26a78bfb966305f4dae907a30a9bcf72123e6e1bab0248456c933e1c19c435b78cf967758558488d2d9c15cb2ab73964f12ecc63a98828758212b6685db74308baabf8e16f2dce53998e279793cc92feeaf1cb8582faa447fe26090951622fad4a8b41d1c4c4905d7fbfe10cd4c6f400303dc601cbd922d0989b5c1f4e5397c2fc60643983753d3f521a4656b30ae88ce3ad594d3613598dbd4bec47f7acf621a18800693d07be94756673044cd0d8b55fb494290016678bf52366de16bff736528cf579678f82af7c9603c393975f544977cfa184c97ef3987cac8adff88c1c9f1b63dd49f9b1e065318e97a39444f0c910306639d99327f516ae4ec2fa47223e547574b222fbca572178c3e422871cb664a81015c684f655d4656ce16ecf3a0f447bab72d5d0ba5f39c6654d6d6fecdc2215bd6c8cd8b61420103b080a7d71c193978fa5ca1ec17747c39252b14d143845827a2df2a5c1e7794120b217fa860921842bd6be9cebdff144e7edfdad1df3a2978c1647b4a416707191085435ab38a58595a3b28607f3259d4e49ff8099f095f1d8fd59d7342365676a31d235e450c6802327475989409e414ecd4bcbdc29b4b40bde3ed56deecaa90014af0d2884a27843a71332009a59633292a0c4002f2e3c595fa08c6d1972c45eca4b4b2114cd1f524c50929025a548a949245b1141b110c42c9f814bd7df259870143b83d82bccecb16b34d0c10824107d321b32b899fddc140e959f809d3253960984388da294bf40b436eed911dd717e6f41c948efc25556a2f8c1bdaa176262fcc1f734df4574ef1bbdd85af615226e88aab31e922bdf1a32d5a59bab97023e9911b5e41f85db8f092ef9f0a8d6fe155ce3cb3493b3a5b90e7f5926ab530d8a828c172e4f7f8d0223b1d2a84d12c0c415285121e4c59a43a480b1553fa7b2c529193fe38963b395868a63eec45ae3a855f61d2397c8ed45859dd15caa46829e47a044b2bf8be5049c9911c2b5afb4e6a3f8818afc289a9754a45d57454e1f75bfee55829ea5498c2e6a4921d0b3d294385c14743a7687f293f7d0a934a21757eb1cb7e2f0a0b30853959c69c8dd2fb29924a610e93e396fd8c07ad3f298c7edf4994b83841c43f0ab3950896c34b4dc4f84551bec88c89cb6b288c236eb3a22561541228cc412b451555a14f1892cab7b9e81f12f5e3098350d9bbde743e7afa74c214cf6d5bf2af085d36278ea434bfbf4cc90d954d18cd752d9c12b226cca73b2cb4d794fe8b33618871134e998e1f36c5c47a1d2c7c76a77bb92f61d44d3d6521a7a4476a0bb0844949cb4105f949859754c2146a65f2c84b29615e8b2264a77021494a27815097f36479fa699230473bcf22e6bafd4a9130e48e94cc2f04a57d26244cd5e7c1eb7de7477f8471c259289323c62b4447742179b776c5d04a23383bcb95ad14762246182ea2c7bcd32dc29c6d5bde4f8a307f9555b8f6dd5b3711a63f53df9e3e879e1043c42bf1b4f34e0e6a3b4431a9bb5f24ba7c0c61f48a704a843f1d6654089334bb4f2a2949f22d5563c1b0004218c2e85c219b31263b6510c64ba373d2058f2823fdc2020491cea8c9d9de06820f12a2e7ea7ad30362dbeb2c1ee1dcc72e7f5075745ec9048ffac12054bc4f1f9e758f9b741525870f86a0e452b22e8fd41dc28505ec61ad2d9110947a3858d03e696363ed3c984f279d922aad9d85090fe80c2dfe3d498864f13b20d2da32353b989386fa89abd317aeaf83a9374f74a8d27fca79d405cdc188223b25650d9197433237794b88fba97148a86ca9f0fb210d07934edb177c674d5aed6f604288a6748aee92f2c80da653428a9c644adb5cde0663a968699122365c2aaa04b9db1accf9469efad8cb97ac06c39f48a583103969b83a42b85cf30a42868682cf758844abaad319ca67deb1b47274a66d06a3e5b0f859d75b6997c154414b87d2ebc89029b12be93194b52fa6a9899dcec2627075c6b32aa4340a83414e6acba9d34c4f70c1706c91ffa6fe73aaacbef004b112c7d2f45e30e55877e99d4a871d912e6031c4ca88a813ff5f2e9c75733f29fdf3d942e761f611625eda73b5902e2fbfb2c05fee4fdee69d16164c61299994246bd7737905254553a3c368d10d1d2b982d455d2e76a4492b56613797943ae8a95088262c23a94e5f9a42e2af33e3ea2c2d05d3e94b252feb8b422259758ab8bd408124bb4edba40fea53fe09a674314b926c277095527487f0f14b13cc2656742cd6ae5a9609860b1aaa4f65848fb3b6b0802598c3cb271de4eb93ec4a30bee59b28426c88d1a62474399ea1e90290505297bc69c9439ab5802318f3928909cfed93928c6038b3f8cff98b6048dda5eb927b2298820a226b5c12b1da1e8249567cc9e593ead287162004931e35af95b907031193456e74c0309d9874e661ffc220bc4e47febca4ea7c61b6cb23ed428d900b61f4c29cce4a5744cdca5e7861ee8bd61d17f35df8f15794c812d68549a84baa77252717a60b49b2246dc28529277b95ba5ecea2ea5b9892127fded52962a98a2dccab7a9a922b5c0bb34dec54c14dbc63480b438e53114b446761d2799de593c3b996b228ba08eb148e852a13643c448717c1c2bc13b322aec809dbf815a60f4946da89743922ea0a5365bb9c9cf89c2bc55a61aa5211b34e1d46c594156978bc8c972454d7ab30e54ed172c98dc892a20a9398e0ed274723473415c6f6cad3a67f54186b52fa58e74e61b214bd6db9f4fd2a628af7de53bcc25218e5ef629e5d69e47c92c2789f5ac9e36514e6ec14bd7f3d8ac294963f8c94a82a59140a7d4ba48fe381c268ee6945825be8b57c828b5f732967957cf27ae21e2d173d24ffaaf0e984499f595b291155173d4e1884458be623b409936e957a50353bdfad268c575244c676eadc936782ff2a516d499830a9bb49f95a5498fc12bf889b0ec94410a9f49628ae47bddd5abba412c6f932955a8424fdcc28717cd3218e9787f52661d05177bbe32e6f7e4920ae3e4685302281c93ecb275de6d49030fba85239151fdd6e91b1f60843bec8a62e7e658c46df00c370049eea6147dcad2935824932e691bdab2a8597a0a1e24f55901f7ee5a61d86c10893a7702742bc24f36a2fc2e83977989a7db310175b43188a30277d229e68b1a04350224cc9f3bc696d57080311064f41a9e77ab50baf1cc2f4621e57c3574318249d909594c8dd33b24218b3439dc97a09cbde12c2242c5bbb6ea73cb9a04198b2754f6586ba76d205611256a35f94481372cc0361943c4997d5a5957705100639a264d49af40fc6d1b553f14ca77799f8c1f8b9f6e1aa651f4c91e25f25f93a1f4c49a848496c3c5c2e59187b305ce94e6a4bdc30f470505be16ae4e9f82935100d61e4c1702ef36f957e9382e4cbb081827c4018783068939fcc4324a5aa5abf096e9c51469211c61d0cb1f77cf34cbb8248dbc1a0643c3b9c97e585b80e26ebed1cba5e9e1cdc58d4811f844107931239e9fc217ba553a13998fb23cb049192eef33a0c399874df53437785187799a9851107a3e6884923adee924e8283713635df82ee1094fc6f3024cb101e1e73371843a7f0ba038d5ea8f4af6652612012070402711c045248f51e00c3130010404078441a8bc462d13c0e85e1071480035228245836301a241e18161808842351201404844382502010088541814028240a27e196ec03ab41f74a5cbda0cf38737cb323ed8a86cfd25657810116da0600aa70ce296e6ae67566a0e70b7654604720f737c5c02e0d64724730741c6dd034c8fc50bdd6880191268b4923507cf74d766582231df89f0599770a1a4c373db28b2b0e747984ad00cdafcf08c0997b5e6e6eea267b805f181beeb6aac9539bb444f24f37fc683c9ca949faf447fea535fe479cd290c5f455fae73b6e1d5cb06d98220fc824eb376fd0c1de1d4480b743ac6e74a47b0f15e183e4091e968a808de6f2541c8e584911c341ca0bb86b6c938d8b7c3c2b5b544b4461523983f31de5922592fc69c79a4524c8671d92de4c7e09eead39f5d4bfe1a94df4278e6c79a33ca999541f6dbce7ee7fb86a418d54ffd00120f8140dbc9d22f28d7ebb9f1dee3f4a15f72cbe0681c61d3aeb4ed422a08aff1d39bc1c530481076fad49d9cbd92fe58118611efcd5e88ef6c36e3b08c946c14b908e148a6dc8692dcdeb6d7b5af9ff140f940d20c69f84ff2d9748fc199f7c0bb77bbf8d53bdbeaf305628eb7dd3512db1809933c2bbff7a5fda6e21c36a472e0fb4d899ab41175cdde6bf844c934671899fafe9b6045225aa6d60af4015ccc7662a87ee9f1d9487cfe553ed082d1c63fd550089f109f0a6476ec7a7ad90111def3be9d273a26aa725beb92225aeee3fdc6b4808250dcc5ef550e2b79094d8a6a18a5a039dc6d8357c3774190f03370365a7a63c1c251171ae9748682c174f23718da31b7e8203a5cb808e5808638d4d6dcc72b807b6a18fc51b26e5a233c7511c35f57d8d463b36ca40ebd4c484661f9c80837d1b25c6513ac446e73ed07f0e9d13fd339a4114886b7d702ba65328781a479cb5bf8ce1ec300d47a91f363af189a59d4e56ea9f7b9ab99c439bd905f5c0513afead53f1b3046c811da319a4ac96a56eb9ed2007c14672098d6710e0e0debf6e209b3fad0c9146ff7e6189d501df8bdd8147ca7b8f21e8e05ea2633757f4e6f3966859bab60e8bb5c1be05cd7c762000b61b38b6124703725a8808cc2d040efdcba015185262701af9e67ad7d2d63104284feb64d6eb5f0993002109aaaac1112dd71c4e6e0b4bb5e4d97cd9d90ceb3f0e98260ce2bae69033045f53409dfbba11a06654e95522090e53da0559ce056640975de35c9600f50932ddd94450109a84a94701587bffa21592dfb9e65012a0fa4773ae11140ebaafebe8fd28132685d21eca13d6a10f9ae18ec94421dc56084c35c66e1d1eca59547ad0fc0ec170bca1abe70d078bd5c17ed7772e7effbd63e4f37c520dc8aa5b2a5dd5887f3a93080e6504827ece1014f80622f0586fd0d16ab76c4055f7ffbba49c96edef3a47c98e35851308cc6ac01b602cc3fa2f23a0a7e6a87f02ed28826339280daa37f1d8ef6e28012ac3b7992e2db11c3c4767b0fcd54f9a2def88673a7a85ad3d44afb14ac087e5771d3c87903115f818a8250e062f6d1ad0383886173cf08a56cfa2c142da31174c58dd48e3914fd2ed80b49b531898070f7670907e822dc10d19421bb4d67440104da63b2a891a3ca4d51acf651f3c71e361870a357836b71da9ee74a069a5b6fe107877373c9b81ac41405283634e106755661a13990f1ee43a58befc278558c50436c5fdb45d569720729a7cd112c1206b628641d3171a19641d5baabba99dff00902c5ce031d27cee7732841421211974f0002e3e1cb08387e50c1a123dd7dc2192f560b40032b1c1f30413780ae0e0715db9d5c76af008ef83c6ed0f548e6a0d9ee7a387811d6507cb7d0d40fdab60c7a3f11fc20d9ec428864318099b40e6b307b4583b067722f1f9925eb156d5cb4058ea76f8be871624460c1a0669aa32683c0155cc4212c68ee001f6e97a29fa702c27bb2822a855719d837524ec18bb7678848d649a3dfd5dbbb5ff0073e0de55a38c9edb7b04a6dd12d71b34ae2dde6aac54b5f57a3d1efb775e284666143f458a510dce73dfbbec702304ab4c344334181886046dc4a1c66408a01c8b2d70ae21424e16835246a5346d0dae1894169478c33b565351cc4ca7e176a8e929d2614cc0e98350313f9e1584987a8375820e80054cd8f0c08b0e06505803113633f98021e3e088628c88c30ad93daa20163c484fc40afd50a4bb448f3255450e5a401a0ccca1b83c4e32bd7500e94f16a58d3b472e6c8135694c26de69913c7710d4a8ca034d91f673434fd44fb3e5691d2add2003467379045a5d75f175d3db4231ae012afab4ff57c033658c6d6d4f6eb167658f7e374eeff55ed2110f629d4a028503cb647c662edf598abc8f49fe9b384c8d9fd8c05cd5af0d549d3c550297945fd5da01e83faf6967c602eef7ba0baa36279709254d0da59235a64a31b69feddba8ab7b6a2354f20fc11ebab08f3e441faf066b10ad9b25ac15ca5516837a0861300f507c4e244389afa32cdffd7e37eb3294eedd42b1644ccbc68a20f37635b74cb661cf4e9445865d99cbe342c009b91a06000b63ff24116ce487b11a992c81c9b9db81761434f949930bdaba7bbd6c038d55283df3271dd4e7520c73008b4737df2c31aa1888f3b0e1878b1cf0b921327884844bee5a714bccac1ea25bd7f0f11e1b6e82f1d96dbe6a0d516fe8d1912c35f05a0e75b787ed813de8600d653d9c6b17aef7d2fbf5809de265ff366719869ee80311899639a26019e509f8151c866ae27e19840b2463f1408e11784a253050c4c141771070e9670e5cadc8b03d1c743106f610bc51c4fb0a2262485030b4fe61fa81a9fcdffce8654d55a8bf96213db4db24e8f92428c50414d5f7ea1db0927440a7dc31df51da2554386a7a54a906b88bc36eae965fdd8d8657d007f771fb405e35798557e9a6009aef56e2fa71caa281d2d71190a7f09990632fd0a90bd39bf0d50cf8b1a02981f8f9c18c838a061083a9012486c106702d84a56ae81d3e989721f1561cb282b4b0546640a1ea5e6d8e9deb6e2e06331afcc76b697620892e0bbe5e4b2457166de73ab573f8611a1f80b5d6c422c073900b3116aad24de0f3a0acbf719c3ea3988b7180b8014c160535c3871bc6433615e2ece25ae9c9197d090c624b1fd1c0c0dc0a8286d070801706b12c6e587ff95a8150da4896b50d079916720ae98d2bef454ed81c969944bc4257c8bc372dd3d215fca33f816fbabd579cd2d0d56a60667ef624413aa08a31415638ea0526713b34d30661cb537620f9b9eca9e0ca09059b34e11691a16421408d55bfd206f0b868ebbe6e9524a87530e7eac8c8f0a3b3d3c0bb8ec320c000eb4b950a0f5a89df0e3def45b0507e33fc65716c7f25654279a5fc2bbb791669c662cd9ee4aa39ce62389a39c175a76af872761a0d7d6ba436037aa1d7973c62eda5b1aba73eda03bc29ad35bc4e73cd0d34fcc2344ae4f6ea2aa4ceae9b1271ca760f1dbc0a03bcb7fcf552b02ae762167f9c545a6a41dc9e7235f35e42152451c14d5d2e2473e2bb1a7663b038cf79f831fbf1a41399f39950df4cfe176f8f416ac5a09f7c098a38c98b3c927aa2fbb697f0b31de0df2935f1839ad316e184e6e2be53b26e8ecdd88875969f73193c263885ec0f809f6c4fb0f29ccddc163e0a369d4e211774edd66eca0ba7a4c3ed95a8ae40cb70c1c693aca14c43ed386b24c54a524893515afc1fe2048b2c806b04fe8bb54d320e34542fd88ba4066eb37e8d2dc3d1c5c0eb4aac5923d9a76f3f610fbace67f550b5203e31a6e93cf4aa2ceb2eb429338628bb8c8d74bde9079364008916d853126280f980d9dd74e40d3449a65c58bb6e46b691fb52791582cfc56098ab40016e046131a182c29043533120706c40423b3d7a72b4dd417623d49548ae078f503e39425e044db78c0b65a8d5c12b5968e9c4897ce33b75f273e1bcb07ba86b3680161be25abeb05cb2cc30ea5db9a3ae57f41bbe72e94f6d8cc3bab49f93700c6dee1444990c542a8153af88f10494ee60e068957e8b5f945f483b67ebadbff389931e049939a0f6dca66cdbcc92fc7af95ad9d400f09e415a5ddfaa0ab5b495c9848644c8e68e964b84bcd453f28e142b58ac7795d12f3f0f47f20d966f0252cbe851e5e4eb152a546cb880600923287414314b9bc651bf76675cb6ad8129fb192e57bd2a386935e1352ee250d3c45bc5b02d53f201f0dd3199001ac0c98be3486af99261294008cbf83dfcf5f828e8867b750a5a3f6199b715c2a3b2e9fc78cf446b11896e53a15d74145014079aef2c1db5058166d1039dae12685e5f4e9caa0f973d08ff0af43e3310cc9a5e573052ff7610ee045b505e49106ee9dba0a4070bb03f007f417c967e3c455356bbfadf7ec49e47a40c2852004a5ddb1ca97bdc04e43cf650f8ea68bacfd829b8c90f0c9b24186e76fae090eb90a0007ce265734afa1281e43bc97fd59f05333e36138d5b1e2f802f83d18091015291935b2a23e02553970a05e55fa032c6cd4580f27fbe48db17a7788e5ee6f4074a331de655c63453a71d691f68a42db119e9c977de2b076dea2d63df8373c0090794bc017fd689b3bd7bb3205c9c8b9b60ae44a9c961ecb7a156be601ea334a873a870dd16fde4c2239bb8ee4788ca3b839c18cefa2c59b21f019f0b37e30471098be5daf339f81e07688ebea1e0a691e6851d422d3b74aac8d33de2ec6894006daddeb1b0448d6165971ee04a4bbbb7465093ab6950bcf93a930e7c00f8356e9d1bc67da851e07b79e39a579b39caddf4bda6333dd9c46c2a72cb2b034b261e209c301231cdf434b7e828f2c7f4464fd6cde242445cfc43c77ffb02800603bbe3c6fa133f04203b8a7d5bd1cec0efee9dd082435a3fc13bda89336040fa736b11e51fe7aa5ccfc20a6507c42bdf69fab2221e1e382593c02adcbf45b5add7a506c92e9ac02f51e620d37209ad315b59ef012ad22d3b3bb85ae67a4ed1f1a4a12c901c2549273db085365ce1f769958c7b0f1bc64a66dbe8318deabdb8986dda60f4b6e648a3dbc21263225b8b325402b39f406b7d23937360fa309a5bb68c60d1beeb67800a3100047ab552c63c5f777bcb8160ed2d0bcf74bd604723d4fa42f45b13874e9e95186c43835b3b80b5b1028acec69d0f71325de08d5327e9d8e0009896f340190fc7232b2991e1bfd1ff4133eed2cb74ee46c98f7b2a170847355a911f9cdd4c6c13ce38cddf9f9da12caac149f750c72238ede31641f0183f2e2bdc266a4f1823b01a888fa2f31316897220b27bc7461daa80cf6fb489f53d9f93ff6482e016258325523d4ea4d421566ed4384a3db5f5d987aa9401250734f5b7352f0d87befc1dbe41c92a4101e73a0b485923ccf99520071d634a14a8ae50d44d118de7a11743a78e936444da33f07ce3fe6a5bac02c14ee24de0752df349db8d0b2c2ac6960b8c58d3338be231e3a0a641c3a8326240b4593cde9804adafed63a74f0fd12b1a9ebefe57a445a814301addd9afe8522aa3f6f7206153ab1e480d043dbf52f9057adc890c5942f3801b6852d1c845a1a404a83efa945e3860990f826ed5a85574fe5182748320a55046810e52e0494a23c9f83c18c71a462c071b3903cd222be6baa84c8b9e44de1be90a54b6ddec20ee637e4e31337b734f05942b1e473d587bf3a711b1ef22391f2f6601cc8ebc6a92d684e72b04a1c99113ba37d665d60d8969bc13a5ab1da250b79960f23c0eaeb799deb659c22b917ec462a4f3b1a2b332cb16885033f106b17d04d12d2821d354305e6d04ad0b3bd3fe31488089727e858e118a5882e59628975ef7ad0529ad1d58b22b32c00a852d7d66b26225fae6fd03791dbe0992d2079d71b5e93972423cc1c3b2c4750307e12b136b6826583a11450877eae119c3ca6d26d6660276bb2a2d51445e14743b1465846471ebc9a8820750ecdd4133e7956a89034b7b6b427c53840f5ba764501366f91bf26cfcd4f040c261466dbbdf1880781125f3c2a946a936a61697763341490cd65e0ebd9c712e71224408a6210b7762fafd5faef3a9a5871585a6e94033c199d77f8e7989771fee7191990da6b5afacf4c3f5c67e0fa39af96315f44d15a02fd8e7cfdb37831e87e930a81e882b293284689ba1a5c3e7126bcdb4498c5b045ea120e4abbff1b4eb03c1799ab56e1155ae0fbad636244d1a9323b50fbe84836e19a98e52b5a490432a88d2a294441f4d16d1ced6ac6e02040fc82a20cd1b63b2a87f13c12441a199da27b8da08a9822f645096078ab13d8ba83e78a970596418829f786a24274aaac14ce13bb6909a26a42ef5ca50e4af0810e51f85df0ae9e7e0f9a9e749fca9f2a7cf816abced80cb722490ce22b75e441342ef96ca933641dd02cb38aacf8820ddfd85b9385822e5bba7ae972a817df8a66f96db2666e915ae768423ec88f188968a321d62a8178964af08cd2e104d774a34a87c4d4c00fbbb3834cd3a8b2626b0a015b393137efc39fd82249d0b67ad09043473ed2316bc27c142f254b0edde9f1805ccfb2986eacb51f67499b124b08405fe7f824b6b39651a4f9d9ba8753a5b525b5a8ac00b4ee70f035ccd38751480f8833a71680278183e8aec9d776283fdcf445b71abbbd05a6896653bc7d651ba5c680841eedc27a3266ccc67411c0b458d67f63d5a6daa13f31b62eb8b55f5c155ac30873801bcda2c0c6726a64f54527511838329578edde956aa40ca0d8db018d20f5a86401b8f2f8d5daaf7edad1667e97da463b3451f9e66606912225bf228fdc820ea3db5d6baad7c2905e8c22a22ab237645cdaef6db8b36dc9dab536a4edb4b8fdb24f3f250daa0e47cf7924e1a51b890ba5d81fba2d363dfefd2e90bc52a74320796f53465c2683f8be470ab5b2739c76cd445a8f2004b672115ecb8a44c7e29e2a04b4b5c07a8201c512357b4297056ba1eb08f706e259408c3fe2778bfe47e5cfdb356dbee57a0b07a26938e43e3c81147328375778c26e7706c74920e3f8c416dc7fe525b4b781f7de7cdcfd38649ffc9e71914596cc2a1a8a9b1ca11d31346982a5e898880c5dc092585f19bd2d171811440a85995b8fde6b4b1646a1e6557e3f4c95479690c1357fda02fc43e872074bdde2f020ab9647e6c9210bd745e1fd1f98974459fbd8cf1eacef6fa9b5513f524b8e203631c4d520f7da9f0f27153f976a125374edcabb5ee2b6024c33b19fc9fe2bdde45f3619a50912b174cb0603c186e61e065092a62f25757e7a7512276899aeea41c12eae45a019dcf612222d2c1b0d273a91d172cb93288c354e1c76293fcc7d6058169a804abd08557db0a5ab1086082fc9a4b7a74bc324dc109486c8778003825dc43babf0f8f04e2df4c69863747907ce3acf49944c82032de235bdb5a32fd364e9f13812a59aa92c1471b97eb3374791cce52a1e011231eda1f1b87bfd4b192eee36f40b89ac4525f0803108f2738964fd711cc844025c304b5d48020616223bdf31aa4dd557ca1931068551223374dbe4f2668037d05c35847884055e3ab5c152ce466865d15a01d3d5c7b02f2b1c42db858cb49d39143c96426b9a6551b5b90949f2eb87631757679534d21a64ddc36db3dd172c3ab28e378646efab7b6562cb76fe7e54c62ad2d6f12e008c49df8e2525a89169d3f409075e6221e99a93a6a7f463960fd47524e9954df4f609c1ecbf7f23e546e6c04dc4144778635ba1794c04b00af52d6d32684898e9317bf3b460627abab17cd42a055b28fd6e2d58f5a9c6df5b70d09596a29272329197c07b0433454bdfc25c02934e00115eddbd462e4577f2ceb67c44982d2c2b84eca7652f46466548bcd9fb28a60eb0f2941fbb67b2bbfc6d21be3379eb37bcf56c6b44ef24f50861b00de64cb3d6b0c060d955a50f5582222eb2802b3ed1de51bb8af2867d5989c45a92e7c7ddaca24fa404690d4bf5dfacfac2a22c0b7252f2e57fa74e02eea5a1b9797490ce100e487ba3bb892dc693cc60c27720aabc9e8057dd093b5d4d20505918635a1af736eada7b781254afbbd4a94c720f1f73e786d85e86d327affc96c1ceec4e1b28c70b113a61d5a5d15cf859471f435ec632113db6421abb55c6dc95fc8740acdfbaca88f5f52cb5bc31314b791509b1598e3773dae0ed3df6e13fbb43d6b91a1609c5d2447dfd4c8032d083b921e4939953e954a1c63393416d3a1d83708a9cabdd1e34b7bc53c4983f97742da9345aeccf5ee4f454e77c883d2314b1c6c65c2a926b63776e5f2b2c95f4d47ffba37c9b6f722cce51252cb2db3d87c35d2e5d25f778fbd950aa35a99f94556eeeabe5e60212c8484b072b5881cf2791bb6e618d4a407ac5841d1d84ea0053f32eb4d4e4abcf8dd8adb4b8f7d8e4122b4ded650b64245d8363663f109ed85315872e1c3c94dd2fbc6841a0845564b617d80697d51267c3827b588a48754838bd51abb13825cd36b931d0aac8503dc661e99c20946f185361dddab351e32385fa2ceee6c618b20a4d58cac8efd71478dc3821f1b44ad685a1a1d05bfe0087ca31db53adb69e9a4373a72901da6b761b2d5b04a470d90d312607eaf6189a71e1bcf7f79b9a58cd4a763a225197c3578f2f79646cc8b05c435740658e9065bc7343a21c4accf676c40cee25ac331941db3d2b6e27b3a44f56a9019e9e3586225ddd1bc29dd9b1a29a384e151a30ead38fb03236ca6dce87d46081ac9c54ae9254f70d22773c86f2b8a151e89f9c1fcd37dae479dac3207c03324a6dd0a65bbbbd76482a02eba30b02f58699f2cae7246e94b5e2a98c206435dc03cbd31989c0a83292745c8b5c57ff0749402fc3ca04d964d99a6fd5797b90ad30eb9b7b31f30b9fe80e2e666475596f17709dbbfdb54212d0d1a99f1209dbf446610e7b7f15370c45d8af465fd70c690045e0540c0500a3b81f489fa60bc869957c5f51f10e2e3d38beb1f8cce010c5b01ba2422421045219cd85c8ff667c460c49a519b94da79332ae6af53c562859499f8be6ed3353721e334b5087eb2ebe2c1114d9180e9779a9d3f1ca20477a6ec856c423be0232afd67689ba661cc549e08f75ffbf24fc42d234994e66470d1ccf7c48f86a1b48a81c4d61181019518afefcc281180f5d19c45e3f59d138f45c6ad546715851dfb6b8def5c350c277d8af1b072bba022bf1b1f7e1f8806df8ed2d6f6dcc4b30fb6e1700ffea230b5874bc89e9102d1629c0056438c1dd1e08c00abe1c62cd1e48c001535326ec0727683cf4b60bdfaaa56b9f5ba5064c2973f1fc833b148588970712a192edd4ee483f698fbf81a6fac471423c4b97aac52119ae5af7846a74f314cf587c0d12466d756890513df5c7fa9fc18330832df9f8473bd3ad7c1bfb6e6207ed183e4d015c58b5fdccdcdaa1fe1e0dc8b9792fe1c8c5933d91811967c0fbb31678d3fc578f5546f90389e232861b30088774d8a459628238c8554700599994923bbcdb7137eedcb76244b70f044c59f7e105a3822bfbd42e8e8b73b93f2c54cda5835738b51af0782c0b50fd6464177b85e414aa2b50071e2949954e43b0d24711abf56d6e800ae6cf8231d20a8e9ef672128b1fd670cede931905f65c8840b4c0c99d70ddef68663272021b8bb387ff5a0d7ae1b24ba65ab0783ea0235f20b54bff568acf1ec016e7f6e4cf6babb5914c03d55299e6c6b84bba1e92d4fe9254b982bc3f76b53c2bf3a007a6be2e3d18c0b3c860392acbbfdeba93aae1d1957a59f1a809784e3a791f428a3fdf6183f99039506c64624ed6e74f263aa7c19e14d1dccd51635f5e004343aef20ac11ec979936715701a820f9b34733b456e29e0aa83e6d540ccd037f7eb10c9620686ff5a27ac04f2a71271908e01c6f6a07bdb7cb16d84d8c1c7a18dbed310c2610bf5695c3e95dcdda912cacc075c44714d4bd0a775f65d6a69ffca7a7e3977890c910ed3f27fdb2cc51605f7446d9fe5a7e13c48c921b2f34d70f8e84469e6d9b43b4ab2854a1caccd81ff6c2e0d963dea992902e5144288d16d0f8188d01ebf147849a7b1e9b2ebb155f8f2902cf5a7f4cadd768c25fb907b97319fd7e897ae89caa42d9b6a8c066fa17e3b3235587a0376fbdc7bac3afc35c8e16cc58e5311ac5beae69e8ca7e94100268553f4f76a5619f0c8fdfa18eb468af8d51b8928dd351ae37813c47afe73c7cbcf928c808df3ed395a0e7e0cafe003dce4eb74dd71cee51574eaeda133b1fb0a3442e710a6f06ab29e7f33c0b31ca3e3b41094bd4c48548b8cc1b4259c8330dc00a3dcd8ae70d0a77a6669348ca8638c116ba91e9658d3e85866f77bcc16400438b68a58707ab1fd4b02e53814a3c7e2ad4542224d5c597e5842224290b9ea7a9edd8978a8c8fb94f85f837ed90d075b7c04992a167b3fd3e1795b97d87c3a225961b1f4de9d41b6b81f11ee6963a14bc8c048e9e54260adbef3537cbadc8df12b7441ae6e1aa85a58130094f186aaea06607c1cc3d4bfa06ce304b5c268977b03d6416a6748ad3a5d9d4ec8fe94ded80c0a2f0d0cd3d052a69b9129b217819fc2c4150192bc729b5edc0f7609d31a936365ad1f306a6aef0d892ec27834b9ab217dcab7b3a82fbb2dc5c530dd7018b2e102b7ea288afd850f1aca3927042afb6c69937ba552121f5b2c9a78fa21321eb629054e83eccbcfa490ab27717d435126ef5c9eead9964cc3376221e000aebeba8c9d3b3b041be98e9073aa21758b5ea04d0832f04fb99377872248c54a67ee7bcbe82db24a0edf465d8654d0c3ab5c41aca66a2533c05496426e11d3c658e0a773ef5358348c3526acf586ed06d857a2b2b19044522f2811db6f5e35f22f0a1edca8c8d49ddb32fd47443d2394f556ac9710c510ba4d793013f3063c7e78973dd0681cd02a6209a0b4202b596720e93945e4ed1ccdfd9490327a9a90d7fbc23832472af6e420b18759fced10ab618baf16c4043e94d84e296d3e5295fdeb09fc8ccba2dcb41274cc62b9af00f95a0885d51521fd9727c9a24ced543ab38e436ae80ccf88c4fd2ab1f58046132470bb8058387e098aba5b9361a140c824c8ef3ff12dbb787c9d6b612eae4c248319c3c865dc3c913a47efddb5d202d61f45f921ec60513803fc70afb50bd64777b10f7d649554eec1335b980054a1beb72416d78a871fc323589a6aa87e89d07b6f282de5190cc753336c96126820326dc6755b92e70ec41ffc6a47856c204223924c42f7d67ffd792fcc3889e63f5c600258311b93510ee600a2636b2579357ddb745b8b6489dc1ef29703ea877eb23994ce8afcd7f6e66999f30a06431f46e14e3e5c1417918ed5464d2b723fd0c527ae1e8b04e8e6f1dcf5e2b03cc89088f7271ff5317d2239d87f25df731f2c829d2c8c5e57912ebb159b72827ee8389b35f75f29a6ad16fe6e16c44b555aa9944dbc96ae0b1604d59038216957ef4fa29f452b5b27657431526f66125cbb6c2a03610ec9b82d0147c6cc12643f1143a270b9dd52d57335adf404945093315295d7f489ff562949a17b3bf97e1c08ea044dc581cf06d94c156f831e137e0c928ec018772da783813678286d8a0089c0a3317bc723dbe8bd8610a045cf614309e0257014c5b4cfc82688b70127d68400c8a3305523bf276903dd471a6a6da0faefaf07f25d0d97c713c8c1770ff55602d7dac5ab097b26206133af8bb155783333144b45dcbfc574900268996e2477bb7d7fc3b8171a2ea12d04a465af84e82a42f53a555cfecdb13fe2526bffaac5de2197c3a5dc993ebd7c9c77f14b3e86bca1579f2eb38a490b700e7db049e685b4bf6ec9e23fe9910db357521408aa0978d43b66ad8d80d075f49f89147287d0c3089df397e27dcf21f70f82567237c0dcfce6b85f3e3333d4eeef15f53931641e927830e7a0cc89c5d11008edbe92f904218d371beeb1b4f9136828205f1cb710e29b8115da70396a815cd9112791a240742ceaea4caa9a962e5cdc1a7f753c20cc7e89026850a342758d04ee47b4814b43ceb5236fffd5cf328758afbb723f92557fdf70bc16b3cea036f2deca4f4aa2bf7a1576b33d79b110a09f6b292885bccf7c6805e21b0748488ab60025184a53a47bbee1fda045ce314cef2e9067044434ef088a12b3d6c1ce270f2b378ce9436736e21b38c92ca8244ef3ac14a3af6e5803cc8d4c81b0f3fd412f3ef668e34ed585b62603322cef5bd8fa79aba174a447c8f9498ebb8a951545a2a735e0662bdf7fd29cac541eaffde43b21c429ffa4b86ec1725d6e832116c3f6d979641778a698848cb70ae22b03b26abee697fea238f8f6c61649aaf73a199e9d72fd3e4d4f126a17c802a286803884131fc5d0b14e93fe56586d542ab511f6d0e762e83560a930361e55fd88e1a18a7cd2120fb875bdd120b73f3161908119fa0d709f304f9d87efc53a2ecab3f9055bba5a70524a91cd3e0ee610e55bf8be531d90db4d7d949ad615151ae345f0fa522bc393c02a9796d7bcab3fedf26f1e1662e319c12173b200c6f72cfec3913c7cfc58c395d3b025a6ae4a8135baddcacb538e195a2b5750f1cf7e90227273684425e01d8fc67c35dfc8ea2f3d768c3122bff90187d207738ee4d758b776e53cd68da3118d063a1d0f5bf2a2234969fe0d76416fb69693b1e6badc9160d6b36dc69121fe2661911e704be85a8ee1c6d30bed5ddc3398bd76e0180674c9aac92531dc4ccc3c3c1ae721c152258507a92cb3dce4c6668fca56ccc6348c7cd6dac295010453725ee9694c2ed040525f66690b823115f13838bf5031d7827480bba37a936e151d4a45215c147655c01b5ea6b2cc3928c21dea76594baf491c37ab402416b2a244c99b8ca18046c01d22a03828946c15246708e70106ad758347c2f6728d04028a278c5bb2fa491b30737aa950fb2482bba68b5c12a7435a515a8e890088c4aa2a615a804cb91d968af8a7a82ca2c455794fc53b357193a46b1ea1adfd640a53250b7255d2eeaba76095e6744c7463c031de7330c3d83434d8553522cf484a1c3a19750c5a17a5147a51e88f5c49183cd373afa0afd1ceaa9a3b094f7afcb701488a8655431d4b9a827ee08ed15531e869fca16a84cb08cff9f241f4ba8f0285f65c42f346ba943ab6fab32d1a244b1652b6c987374c8dc11c732ec5ad6f2fea1b02efb1dd8cbd1cad184519ce80fd09c1b9db7ef07ea3f7432a999aa46d44d422bf2c4f9ade8553db582f75f8fb2e47566af15f565b95b560d37a9a72e109848ac3633f5bb2a63d00c882c1a059c18ca30c54ba7284465a04e75cb4a8c8e4695416dcc018d169f8a942168a6d486701d30f79b58279cb5db3c09a632c18b06369a218be336273cad4b0fa10a9dc27d41bc0386ace2214127f248d3a4f32149e19d6e89dc6af5f07cb95bec3cdcf3b884982df87ad8609e354a6c08ca6bc63dc526c36e88adc6c68645d2d8ea6192838fc62780eb764ee5cdf44f346d2f41cf5ed6b0dbf0b46dc734563e5d6730a2d92ce4bb2a5c2a8e601923ae4604a3e05c13e81eeb227c5a918498e897caae8a611a735141f7b59ca22138fce786949d91d9c831b418506c1ca03fc8bc5c0c63da50c17a462d56fd724401c0d66e5c85d05a5e48ca310c2b4ff902402288e3a3f6030b26694086c9ff4c1f0d4e066ec3024aa10591d94008b37af412a40d7a0c9814b8095871b0d817a845a4831f3af5dd66be611c05b01d7977c159a1431f0dc1763b774f348c8326176d47460d8c9014bfc7263c1f32a0284e12c6a2b17e64a36932d79cecbdb41a4feb715524f2011b14790be7073739ccc10e38335c4566fc709bb14a447a85e0444b9b597196626524361376c9ef1038ba6cae039b315e6ca9711067e837c3661bfe0a721bd86d2cb61191d80cb6c5d5b9bd91136c399ee3d8b691d98d65e51887534f6c6677d6be0b9dd88cc9a128cdae8d7af166dc144cc86ae5c8c6b46caa02beb045f90d3660b0a4900330333333333333333333e3ca4bfde37ddc1ff7de8d60c232aa45279a9292921209ac35aebce01de7e01dbc8377f0d67401ed0da10d1f0e4d52abe0757486b10e62c41ca48223a94327bb321b1fa7e055a63ce1d2e660724e0a6e8ebfe737785c565f141c4b1e13738c50991e149c94426d5585f6a5ee098ea47c5b29c7098edda4077297396c821f2ed492e698473a98e0b57d479bedef79094e698ae918f197b1129ccd6695ebd2243829e68b15296548706a3e6bf050691b2d3b821f3ebb9d49eab09fcc08cec713724cc9f4712c5911fcee30daf6690c11bc4f93de1ee45552ce109c24b143ffe8393cfe08c10f6311d467d6233d4170e3fb6e36eb2b3b0304d7b3cac52c5de59a1fb831ba6a5f7ce085cba91a626726b7f4c0b188eed1d5b47a5978e0a4c6e6cb13d9819344ea6732248f611db8793a3e87b4505a7e0ebcf6d0217ad8e3c0cb4813d66e3c546ee06d9eb7cb1f04b081ab5d9eaed2b33adab5f082d87f94ef83acb569e1b6c725211d3c0baf652e4731480eb46659781b24180b2779e6c95154d58c312cbc489a7f2bb2fb8faff09225c9a1f9871c6deb0abfe3284a9fc754af6d2b1cf97854438ea7565b56f81a3de4f918415388aec2bf64112d4398e081aa0ae762aeb8acfee1a334156e69baef49bf7df944859f3a948ea297a7f02579a648ee5c1fc4148e678f9e2d867cab580a2ad4c986258f144e942011fd330a5f2ddfa71ccdc7cc88c2df509a156982c71d0a377687f7e8e0592d28dccc21dada625dea3ee1f49d947ac8924b9927fc8ea12558b020695727fca05eb3a74cc509672285e718de849325a7d4d392ae929af0439248513535d57932e1a71c4ff89c35740e0f263cf7499b263269adb984f763ee36d1e953ca58c2dfd80c7fdf1e5fa412fe86ac1e49ce9a630f4a38496ab2cf5c26e1ada698aa6221ea2b92f067b3860f5f8984ebc9936d65b5ed0f020947cee6db72103d6df2083f847f2c094f911e71847329dc86d481a7a46984532187240b33c2bff0d03767c851c72ec20b29592ffb304554116e064dcfaf19bacf4f841f7bf0a0437920c217b90b73c1e3104e6a5bd6beb74a1343787fa163f3cb66e1e342f8d32621dd29ff84f0c2079d6254f6993708af3abe93f1141fa515841b358636cf5aaa3981f0ca3c27bff1f3ce00c233ff20d774567cf40fce775027a942747cfac1f50fa66346abd14efbe0fde5caefb1458b1c3ef871920d8f7b0f5eb7e414ba23f5e0556bd01a3bf3e066a4d57469ed2d9c787043dc6f23fcc776c13bf81f77297898e2b6038d29d9071d07621dfc4f39cec17d460acdd1c1f19caa3e883707377c10739a433e084f0e7eaa34ed992da5b7581c9c34ab9eb482e58b191c9ccf9c2db25f8c31526f707cb2ed322d37b83982abfbaa4fcc698333f39f4288f6d92d1b3cbf98c2aaf755e6881220630d6efb848fc963560d4e48993e30b7ec3fcd64a4c19b481552485ff5c6340c178421030d4ef504dfd8b0ac40c619fced38168fdfcdbd449366011966f0524e89acd441a357b803168601a30c4ea4cc1f59c425bd5eac066490212f937073e943c618bcb4b8decc95a1ff3b2eb6bef0a28b2d3220430c95e6b0fea3142eb68c8c30f8e21ea4b4a9089721cc850c3010c3fb2455fd525f30a656c6e688be1788e497e6735e0b97404617dc0e1b724c4939d892b8e057e7cae361477af1e82df8ad193faf355ae1046468c1ffb19c3286adc6908f0c0332b2e08cb77a78cd1d7e230b032fb20232b0e08590c1be821ba92e68f274d1257fc8b08293c26a32976da4bb8e2fbcd8c2aca08b14aca00ba4808c2a781f89a7d45e953cbe80046450c1df4e9273e8925ce00432a650351620430a2d230ace5cd0147a26c3c5568d1a99749001053fe23f3e5189b858be31c87882d31756db2d212d4b8e2623c87042d540818c26f89299cf238fc2cc4dc6c542c074f1850714d72083094e6dee4de7d31a1daf8c25f821cdfc440c2a5143274309e590638979a2e3586720230947e6f6c163acb83a073290e06dfe9c25a40d1f117304c773f81023a6c4b88a11a86ed5f4f4be087ea8b6cf1fcfa9559808debb89440ec9330427cf769c329a840bb1230427b84b47f3b17e529d20b895dded953d40f03d677498f0d19c477ee04555e9fc99c423f73ef05faa8349ead1d7647be0bd664fd61ee450226878e0a4c8a5327b074e86f71073c4e69a4a077e900d39c8a164fb2293037f656cc35598780f0ebcaa8c0d9afd22e3065e4a15edfd4b82e71c45860d1c8d1e5f4ff239f8e8520b377264c4bcb1ce63b2d0c2b9f6d68aba6916cec9c7b45923a1a52bb2f07290104453864f6d2516de7c8e3b280d8fa25705168efdff6726af8e1e79857f96a5239f9493d4872bbc1e4d4dfb186a3d6e8567993e9ccfd979b4125678eddee5a1a9526125abf003adcc9a93d554481255f8697d36e498c35a8224155ebbc75a1d9ed9c1ffa8f04c730c7b4df6e6fe9fc25b93e41d95b973646f0a7f2cdbecc77394c20fb12395f0b1a884b449e19ca71ca588ee8d3b7b14defd871134ea3ffc220a3f720eed22c77328fc10ed339c87b1fb9941711c99c7c19f70433e881e8ba6d58678c28df6618794365c6ad209d7cc2549ce0ed207ff72c28b5531df99d4a3fcbb09d74396ca13a2ab09a7ec2a64dd0489566d26fca0fe3ce68f35cb6a30e1d9bb745f889472b0f5126e1e0d9fa36bb58417251ecbc420e991a795f0dfaade3e085f29524a09a723db7d32fa78949293f0dca26886895312fe69ae0c1e1d44a66846c215d90ed70e2b64c925249c1829ddbb33a5922e1fe1a51c87312186565c948e70a23e78e9c923252936c271cd718a5ad4a41c898cf07d653a74e416633b5c843749b3e414b25d21a1225c4b216ca203a99839897025367ddc610e39db7388f02a5d468a9eaab4ce1dc2ebb40e54428721bc1c237d20e139469314c29b4a3f214751199226841fd65fab669f2c293908af2e0495a0412a624e10defdcf0709afd61e7781702b4d88b163f848313a40782672f75761d2a4cc1fbcdf902b85c5f62d8b1f5c8ddc954f34d607ff438a629b35ab6ccef8e0c6584915d6d91efc389e1cba87b3ec74991e9c98825d5a87fec9c195073f84e8108b1dc777b6f0e0e794356f26bf15c6ba83ebf669726059b3a65776702d6cc2255955072ff769c7b9a347e242073fd688ec40dad7d3630e5eceee961e678b62b71cbc4a1a25a925264b8c83f3f663f963ea8bebe0e04cdf5f86fb4fd5aa4911e30d6e05cfa8e571109f330911c30d8ebce6ded0b8b4c19f2ca99307f5fd5f151bbe10ce53071d522cb9585d839ba14722db5c4c88a106bf7c443ab6b7480d49831fe608319a79ec65f5e130c44083a795455624bf60887106e72fa9484bcc47b61dc30cde799aa7e89872989ac528831f2d7325cdfeb125916290c10f324d6fad3706bf277ffa24993e77c7108317a1e53c36c86676334618bcf940ae03cd124cd3c4781c31bee066180b2ea576c966e20557d5524564bef34f7e17dccc79429c489ca70c73b1759683185cf0da5375852017184c83185bf02ace3f36cdac11fed1829bb53abedab6919c3fb64819c4c8821fa2e2be3d858ea7432c3897f264df1837358f71b1750557d42c64a4325bcbf62186159c1c6677dfacf6f5e657c1dbd8398e3368baf78de162cb84e1c50b900831a8e0df98faddfb7de0999c8217db248449a99e66f2c59182638b300c181d460c29f8fd3972d49c3c59c48882575953d7696da9fd5070d26a44e592cfeb63f182055b1ce58a184fe8b25648bbe49ce0d5f747e9552fd61a89d104275eed82cfa4907e7ba18156c46082779b43878f30cff0314bf0a254f5775039281b318612fc3084075feef9c24d6c8f102309fe4fba0ade59eab23b48f0d6fe55a5031569d31cc17f995eef0c8911344611300611fca822fb3f6a6d086eb7c79184c41cb9c4b5841842e8da73bfa46304c18f4655e5644323463e06100c125307296ac80fbcdc71322dedf8e08e37a6c5b85c7a40e6efcec14cf43cf02a25252ace63077edcdf9922bad681a329f2479be53843c639f0b3e6385eadf78eeb4371e057faa0b6de73032f8b86908378897677c6b0814925e7ca746731520ba746e2dd2c2b5af8173dad66e224327766e1d4473ff389b62c6e93bc19258e85f7e95aaeb18485571da444b779b5c89157b831d53be6945f336c5ce1a470999c337bc8f05be1c7d6d829bade53dacf0a37a510555931ab36c7b10ab7831c72982c5bc9660f55f83163f9b6844db726a7c29c474af2c72e7563a2c2f73c1fd541d3fc3f7b0a377dac1e3d336b8a536ed55c653e61624729fc6c39bf5d7497983325851762dea8f48e2c878f1c459646f2460ea273bc280c8b312387394a6ff443e1791023e4b01550381b3165a3867cc2c9f1519e34d2e4709527fc888f91d6f368ca612736fb73959c533bc6092fee4f5bc262ea33da8411368410c4e4628b0c03d08413d22f7d1061fd539e4c381f7afa1c652dd9670c261295d26097bf84f3398a9d27ceea52d2125ed234c135bbd587a152096f43fa8fde424ca97228e1e7d618553e3aae8f3209e73aad87906f3b7f1c92f0b27d881ac3a5657723e18739bd234374dc390909673407a17a62e411841b4d6e12e308d77ebd3e8a4eef5d69849f2ba78df0b072dc3d23fc8bd5dae571ec5c2ec24f7f4babab15f1796ccca525c2d97c215b9e17d91c8a08ef3f6b4c1faf5fe6104e84b7dca14c5b6df20de16be5cc8a9d42782a1d45d4ea3808e1f6dc6f068d109db3c7208e209e839298d22505e15fae79fbab74200a89946eeb3e8e2640387193ed347bf41fbc8f43e71cc9a3fd4084f475b91493c7edf5c1b5e47f29a7cd714735f3e178fb38b6700fae87afee20c5d5839f83d89a87b3bb8418427454f93e78f0ef53761c5479e41e7707bf2c2c7c799437c729b583339a3bb68cf920eaa30e7c8c8a31e530c71da3832ff6ff9dbeedd5b49c83511573dc217b8614b61c3c2fadd70a9e38781f793c34724f761e0e84a87cc125f75418dfe0cabd44ccb176f5c92c089552f02ca4bacc32a131c22878193af41c778c1de51c14fc8ce91d85f4b149774ff0c3d08aec0e7382975324f6f6868ba135c1174d299364d487d162821b7294a3e4b496e049fba87c903257564af05245c8d2ecefb72a097e10527aba8b69ec4307129cba0f59f5417290d271042faf5fba8c7418c10d1da607ddf3299d53043745998c670fa34d0e11bc645d15dc21b81e955d730826590bc1cd1965b662d9cc1b0427be83ba9f9b6513089eac445947da4af0fcc0f95c1f7da776b3cf077e84a76fca6678cbf4c077b94f159172ff8407de5f8e63794739c00efca8e3ad4992538c8e72001d385ed6215d8b79b8ca0172e0845039ccd9c73f29e40038f03dca41c2a6567b0939c00dbc288fa255d286f2200938800d7cd91c2b74476ae1d846b9ca10ecc3e769e19dd684c7f9cd3d3c66e14713424c394e9bdab42c9c9e0876962b7786742c9cc91bf75148f9c9372c9c35efe0ad738ae9ef573817c3c71b7c57f8693ac5a887a487dd0a279d44e720e5ce97352bbcdb8ad6395ac50a5985f31f4a6d6c8d4aad0a37668aa7cc4a332ea7c2f76023a36999dfcaa87063b6edaccf1fc7fa148ea57bc8a19885aa4de18d67a57043fbdcb3fca3284941858cc20df691d78c2b0ab7deb447c35068bf390812ab8282abb168159a7ec2f598bbaa247b2a0f4f381a835cf8d8eaf46527bc28293ec55776f992137ee8f7d6482321addc849b3caadf9837849b50135e8478554e294cf62f139e4d5d761c478bd51b269ca834d9a1b2be8413a2dd87d1375eea6a4bf8513a97927b9f94a7ae84eb2396ae634a53c20f52ca42e6ca2c1bf524fcd414434e1d95fa7b24e1a5d4603967ef38b427126e0e326f4bf44f8b1e4838993654f6795f34cf233cc93efed31ec5fa8e23bc10b52e6a348494368df02bb52a4c76480b1b46b8151a953ea3c2ad6611aef4864647b5390c1945f82942fecd914984137a2a6787c84abe20c273b7b69aced4f09743f861b0581fc28518b518c20fe672fc9bb3570a2185f0ad627658dfa7f21342f8311a72c60aa61d4c06e10762ad599f20fcee50bff271a1251208afde526c594b5c0f083f8e393a8f473a4dfd07a7cc3ca60a17c362ef07afdc36c264fae065c8c79afae083a7c9c6638cedc1f114317f242322293d38f741d24abfec6a1d79f0623c6aba089ac3e8c08397af4e43dbb22ce60ebec4beede0fb658ec3b5c73373d781d90ea483db95b263344799369e831f74bc695210e5e0c6d439fabcc6c1dfa4b943f1150eae654e92f9dc342a7d839721d9f12de5414abac10d1d87d9632b5cfb6c83671a39c741d2d431996cf0ac5243541fd7e07465956c1ec658caa9c10fdb614e558fe9bc4b83ff7190c3b8d5d67cd0e087f07392dd3378bdedd1a5ac0e0f33381bdd7bc3bc6570bd83f5b05346062f25e6495932ba778cc17b4bb9376214afaec4e09d057b0f6518bcecc8476390180ccefdbf77c871bee0877e29671b8f17bc145273f061da2c962ef85a23bd31fabbf35cf06354262651f1efb7e0a5ec142cc3460b4e6bc654a9e262684d16885e1d7c482958f0b5dd6247989bf82857d8ae6445836a0527749810217c7b1456c14bde6973850a61b64205e754623a4cade1a932055742beec41ecaceb102938a15fbd3af351705cbeec3f780ebee3a1e07b0e3b753a9fe05cd01056511b6639c10f2ebae5ac9247e99be06594778f3f8c0e1a2678b933640a1faf5b64095e84d09b11568267f769f1ef9104a73e7f1c7ae88104e7fa7344f5f4b1cfe3084e0e3a42bc4c99dd6d044722653bb06c566a17c10f513307496622f82769a93ea423cdd921f892751fe628c71c9b2b0437e58e62f28c06c1f730735467962e4305821f27c96129da64f8c81ff822b612c2f47a10a50f1c33fbc91b246687600fbc4c3158a69b8f5b451ef87d1a6fb9fb8314b20327d9c7a1e7f9dbf6381db831776ba596f9162f075eb4ec100f217b640c07aeab4fe6685bb63206b8819b5fca26967d5c6f001b781df97a35c7e553d7c20bda21244d7972eecaf058c93ccc5acdc2f98aa134e7cd70175938123d3bfe2894e68b36165e7ce8133d473e2cdcb056a1736d7988cf2b9c303194cb755ce16a85bef4c85208dfb4c28ff6917fda9e70b16185d71f555cfe2c4a8e55f8d7b1c74345b0e05154e1ab767c3908396ab44a2a9c9e994b97b7a35454b8f5a9c25a8edc29494ee1e49bdc418839c8111253f8ef963c8e42be147e8c9ddd36ea34073d298a985635cb1fa370bd3ecc3c7fc4ce1ea2f073ecb952ce23147ed0724b33d11dc530289c1ccea3f9c935a7f127fc1017347bc7ed61c69e703e481eb6aadc0937b9698e6cd2478d2c27fc568f327290183ede4de09303a9f50fd584dbe9cd639294cc5ccd8493e67148ee9a63d78909ff35c2574a6dbd6dea25bc99bb54b163094f428eeea38e2cf66325fc28b6e3d764172585126ebabece7c7196fd49f8c1d29b6f4f648c92f0d652847f0f6be922e1479914113465c57490f043aca6f031e5e0ed23dcbca5da97426f089a23bc28a61d474937b3aa11fe878c9d3c3a9ec830c2f30e3e9c644c49c3a48b7025b2cfa25fc44c114ee694e1eb2b89f02d3a8ec73c06117e0c3955d33c840e630ee14a6ad792397b558d219c7416ca3fe410b92b85702b7518c27f74ccac10c20da95f4f7910aea5cc9c42737f50124178919e6adff1658e914038e3b1fa67f324361140f871d47879349dfff3073f6c9c5ffe2d898e1fdc10cd41a3c61c65f4fbe0c72c6ad71d84d2f0f9e0e5a9081297ce93f57bf07269d80e2b7f90cf430fde86ca520dc9fb3f79f093bf44a494c6833f371e9d64b21bb3efe0aaa7e710edae1df51f695e8f3267b90e6eb7664df1618e89321dfcbba43ed391de4a9e83172cfbfc6a2c073fd2ae58173193e5c4c10be396fe29c2fa0f073fb4ed896e1e43a76f7025888a7bfe4e9aad1b9c546fed39d536f86e1ee788930d6e6cb986e6c91b9daec18f3247fd1e199d326af0a312ef8e47ab7325d3e0fbe5b0535a5b0f3e687025ddc392cd61a9e50c8e4d6a0e22df9ce4308367f159b56337ff0ecbe0498cc86f6bca9a0c9eca4756f48cc14d93261e3a2ec570598ebd30b8b93e0a9ed181c18f88ff5af7dc51e60b7e468de778096abf17fcb0a93f66c4f4a1ed821bf37b5cfa7555d2b8e07768161beb3129b305af42a37cc428b5e0474c512aeec982df9192da771c2cf861e63c76472186d4b9829fa3beebb11cbaf35638086fabe0a73877f31012fba6829f72ec9b704ec189704b69a191829f3b9889cb11053f7b700b4bf93ca40b147cad309adda33cc1cfc93d4c158ba810e204a7530869823716b3b2e4ac262d61825b2949aae6ef9827bf045f353a7b234b18f195e066f449bb764bc93992e07de68f526c4ac99303097eb2aeb5f18ad27e1fc17f9ff6a8e41d7dd846f0c27806499ddbaec345703bd88e93c6f441083311dc9a8ab89b5893cf1d82f3eed2df9999735e85e05884c672d70e490d829fb286c98a09047f237bfa851ca4cff2077e58e40c8b613eaef481272947c991e981abed9653a2689685077ed0d1367fb13f0eb2033f567c681b3d126975e0ffe63953150b90032f6cfec366bfb2fc1500077e249d9d255d3e89ae0037f023bf5c3612726d8a15c0067ed8d7f93cad3286ad165e6cb41c2a365af8519a986d9259a7c92c9cbc621d694bcef943167e858995e314652bb1f0d45f2be7e8520561e1b84792263d5fe5fa157ef0dfd3ada66d63b9c2d9f48186fe68ec2aadf0d62b47b0b14829cb0ab7630f2b7d24e12adc8c728b2947a80abfdea227582c9b8e3015be5fd545e5303a8e0851e104fb0efaaa62adc7398537f641ba2bc7b1bcc7145e6dc4d4ea94c2ed305ad424e37711527895434f72f5d1dd3c0a3f4fca986286cdde8ac271f30b17257d6a86c2eb3a3593cd80c29fa9d7f8cc7cc20fedb183af142a2d9ef05288b38b3df127e984177f412c956685ac70c2ebb1f338a6ca26fc186e42c2868c29af09dffeb2df8924136e6a8851e22398f0256d5427e98ea27309cf434b59f5bf25fcf451c75399551ec757c2491f6d78743c259c143cca3a940dd932092f3c580a9e9364f99084ff31855812b6634a1f91f082a58b1e3cb49608094fb3624e533fc289fefb0f153bc24d1edb46f8514cc5a7d258b95e4638eea369c5738e83f02ec29f0ed3be43c7b1ab5584efa16a089dd22a443411ae660e55d4a453f78a0827c4b4f92b26a3c27a083fea682bb93cb5c7aa21dc786bf5ac4d9e2f2d8497aa766df3b9ce4d42f8a9b9d36b3a085f543d8a09f2d6a320bc694ddd51bb2731108eb8e4b8de83b4660908c77fe5d4a492471effe074a8e17fd38c4f871f9c4a135bfbc32746fbe0044f95cc32ed933c3e387ee1de7bed324bf6e078b40ea2a64eafad1efc73efeee8687bcd3cb81edfe6af110f7e0cefce76d91d0ca9af52f6783b90e6b73ab81e5ad6e0db35b92a3ab81d8748afcc1d2e85c8c12b8b2995dd2671703d54ef8f573a38781fcaebfa63c8aa746f703a7a0e2ef391f345e70657d2628769a80f2ca60dce46878e1e877414346c7072868f6369bf64cb1a7ceb0f79de72f5fd6a703d7be4a04572be701a5ccb7739c75f4183bf2134a4671a0b5e398313a33d9121a6ceaa98c18fead0d724a40c8e89a7c5ecfe4af927836f39ce399aabf1a8ff3178926e2ec4f42858ff6270c2fc6d75dcbacc1f066f2a849ef09863c607839f525ef320390cf71c5ff04337d5140f29217d78c18f3653b8ea4bd1bfa30b6e48b19c2485b372990bce59f684a40f72d0f1161ca91cbedfbde318b35af08367faccc741d566b3e046bb1cabc574d074b1e0d55b78c755dd26ed153c099b434d9f90c9572b3861fba543e47ca779f1b0a3b4c20d99a952adfb2353cb0a672efdba8ac52cac5556c5baff63ac3437d31db3a10a2ff664ffcf9b6e1e7ede48859f2de95108d73aef3750e1c5ca1aa9627b8698be53f827d33d99f2fba5e698c28bbba41eb2f3c4985429fca0f597a23b59be5092c299bed8162dd9a370a3e40fda91dce5ba4c149c654578b95876677cc7a1ba22caebe650b829ddff7564c7b61cb2010abf446c638aea486df9849b428ed581470a911896c28627fc8f5bcd42a8eb550b373ae19c78960e82b1c50a0e521d48c20627fc2862c71d346724e0c5c6265c0f1bce43e227c7686bd4d8a270b0a109b7f307edd8983f7a8f63055e8071180e6c511b99f083905c6d5372112d4c50e92932965d1313193652f89770babb2445788dd259870146172940b584bfb221b572b08401461717d8a8847fd9f5b17b4e295157b4997d887a7a109d7b42cbbbfc4938eefeb9225473f6201e614312cef976678e241d093e4e2675f9e28684973aec3a5d5269afec1187f1b87338c29678abb0b70ef70a79aff1289bd036c2550be92b98472184ba1b8cf0e37c1693a6cfb1087f725853eb137b091b8af0673d7c18d357527f4d02612311fe9a7af9c7f9d307416344d84084af6934c2a52c1b61e310ce8c4c276d172f4def8621908fb78314a23c0b001b6c14c2db681e2ce6d01d734cae0e3608e17b1c678cae0c51a9a941f821a4b76c99b3e5941e82f0831c66cc71777ebd06a292abcdaaacaeac7c5e51228df87ff600e1af44358b31440c6697fdc10feeda2b751cf7e4982fb65081f9628b177451aa0a3b60c30f4e4ecb24b12f52c7aa45b0d1072f870813d9fbaa2ae40d3e2053a7eeb61aa6d26de961f4506334dfaefed88d3d781163aaec9f8fb1adc2c596bd0bc0c0aa056ce8c1ff402cb43a0cbfc8835f21e4683347cfc596a5800b30b6b00ae374c10518c9800d3cf871b9a475de1cc3a4f9c25841171f3841170fa851a3460dde2d54e08215d0c61dbe08030c177cb1c51602d8b083a7f59623dfd47182159814a4e0e08264d5c129b79c3938952437aa0ba81eb0410727786be6f76b71f54e7080018651c1165812d8988313527ef3205c7a6ffa2a0c1674f1052939f076112f2257259135356f96b3db448c1e0bbce00d6cc4c12fd390d992c1a3a6145b75a0cd7bf1451860e80aba48c11746058a81116c711b70f0f387db0c39763ce98336dee07faab75f8c1eb9e106ada32eee4ecb23d4253eaf8a791c39ecb68f8160a30dbe7a6c99f6d1c63cd9dba20b161c5b18166cb1050646b0458d0d36f89f39a5c89363b61e8b8b2d2ec070410a4e5a60630d6ea6314f79324586ac1729b80d3538d61dfc88bc5e5ce02eb091062dadd3ba34c35633cdb6e66290b953b58f49738213787101167881811a354ee0450af2be0883051b68702c9ac6a0622f7e1ee4622b055d187206ef425b7cf6278fe09a2f5a00066e6c98c1f7d850219fd4d25c9e8bad93822e0c29832713730af3fa4bea09e38b1680915c8471a80055f05da4003130822d686c90c1dba4314721746757c7e08ac66cfa7d58a11d31f8315b8e223950cb09831f7f0ea2b6e251ee3060f06cc6265708ff3959bee05784201ff786f178c10f7334ef318dfb8574c149e96f51e9a639940b6ece9ec1453ad58c640b4eca665b16ee344cb4e08b5d6a0a92d2a5f6f70e26fbdd7b2c389d992ed9edea9d2b38b235bfa1d6a3d86105e72a778ef139b09c1d557033648d930ace9b7cb470e9297819fee73cdb43b1b414fc9118b2759028b8b6a93f8378471681821f89c6a658988fb59fe07ab86fada8589ced044f52da48f1f19a857013fcd0c346abc87124394cf02d36422563489163096e481fc12347555a2bc10bd1c166869cd2c649f03227b3489535d521c1f5207fdcae1259b53d82e3a5213ab5467053e5e8e314db577711b4cb9ed006113c3b931ca6979cd9d3b3310427a70c95b4729c3aa4674308ceca7fe4f164f74fc9b3110437f465cae1a291f3793680e06d0e391ef994424ef36cfcc08b6079e22323692acf860ffce875de4198e46dc1b3d1833f7bdc23d1e581131e9f63348c45b703e762b4f7adeaa0a203bf62088dd530078e7814e9adbe0d1c789fea714ef213a6be8d1b782aee5d975ed299b7610327b79a4711e9e31baf165e8a0923293b8ed26b87167e471b169ebd269c7666e1857d1c449ab2f02a3c76670a1b2967b1f0259a8ca51c62d607165e0e136b3ec7ce9257f817f239ca945aa9e50a27e714d3e5dd61c76985b72efd59c3c692301d56b85146e3fa833a4f59859bb54d3ac5b8146355f84126c5d87452e15f7d6b06f3a0c20f16730aafc7dd6352f630a9620a5f632bd67df4518a29851bf31e2ba490c2b3f0968c12320abf26b4c207d1ef3f88289c4d3d69fa0c853715bd4dd3dfc620289cb6912c62295f0a924f7829115390a858a9239e707ab3739035b30c914eb81aae1e568c8e909f134e5a86ec3064aba0fe26fc38720fb1f3cd4bea356125bf9cda1f4726fc54291fa61ef1f83c30e1a6ed914b96dcc2765cc2e94b1f65d7b425dc0e72b678f88e1ea42be1db7d18d971e71c7253c2d39c695b622b33d793f07360725922857f9925e149c664f69fce718c23e1c61434e7ce3239430c09275e24856675966b1f4184fbf3924d1de17d14c226767dd49a36c215f54f6da119c62319e1a8c744d5ca1e4de722fc8f467324e36d174e45f841d2491eef12e16f4c1aa53ffa87194484f7e92d857d203165f0107e040b734136fca268083723e51842f028c28485f0936407a9912384ff39b3cd86078f633708df0397bbedc958a320bc8f2aaad53eacc50a849783b07eb36eeb6105083f9c995fd6fcc1fba01a2ac37399871f9ca416aa434da60f7e289363f48997d0c8f0c10932397d58397bf0a37a771817a207ffe8c129cb9d3a79270f9ec6589f6c62f0bae0c1afdcd9730cffe023e60e5e7b7cb183539a3b2454eae0873972f4317d70c1a583ef992a31690ae123998363f9a3935c297db372f03de54aee7d1c9c742ba6ea3d1c9c90439979a455abbcc153f5a0b28accb4ef063774fac852eed82dc7d106ef5d3eb00ff9f38d071bbc8f4e6a9dd3327af41afc8ee73ebe540d4eda8a9d95638f2ac569702673e821cd46839f912c26979cc149e7a3d616dac3bc197cb3dff064d3e797c1495531f75493c1b3b33149511d8393c94672faa468a38ac1491be722a1d3a34bc3e0c9e4e06d29f64a8e04832bee41ff57a4aa07bfe0f95a88d029e8052f7b9691986ace65ec829f6ae1e319f34873b8e0e7cd9888892db2b92d78b93fb5848a18fd3a2d78136361426c89e7c8826f79f278967df96b58f0d275fc5a26623969577022222bf67c58e295150ec248feee5855c1894924a6145eb93a54f083c9db1e3705254f8e9143450a5e6a5289e935240a6e482ef2713c1628b8c96da225a67f7c79821fc6c58ccb92db3e27b8e1710ed5e2728cd56982bf39ce379b63cd39c304bf54ec249c4bc8d6c743865109558ccffa7c26094e8e2a4729a305099e851ca61c5cf9d7468ee0bb472166e690b152c4085e08398e6899c2f5e58be0a73c29c7131e4470f37d184663de48d13104b743c81d875e087e5c21c79572e430dd41f0e3102ec7dbea1db440702d8450267f3167f3074eea891dc5ec286dd83ef07e26bc6358c90c750fd61c2a5a2c0d0ffcb2b90e3a8ea6fb8377e0e6aaeb491e1df8a6ea1f07570edc8eab3ca590c581f315be56ed0dfc1c269329f769481060033f3e0a59f2abb570724c4134a6ce725269e1c76130d114d159f8df79629ab01256230b3ff020f7e6138bbac4c20d19636bbab78f21b0f027d8b5759a0c96bdc2f1d86453b694ef2a5de15a8a11e71f6c851fa6ac3c6b6485133bf038b29854307115dee638248f1682670855e19bc731a46473ee38970abf5a3e920f2544a643856b1e473139fa3cfad829fcaebc21a5c542d29829bc4a2e5b29973f47560a6fc42f420ab7ad4b7286ef706a915178122b872963f7077922a2f0abb327c4431f0adfc3467b8efe79c20714be450a0bb93d5e8d9e4fb89a3656e4303a5a763ce1dc8da758673ef1309df0443458b0bc2177a4e1842fd9999673d4ea39b309b77275889af346b2144df8914afa8e3a997574c9846fffd92c5b65e5ab60c27331b32c0f3999e6124e4ae7ccad29470cb18413738c1243ae849761fad206cfd9b194f0d64cb352ead6ecc824bcf4edb61d7e49f869528cc9b28f849f72ba9c3e9258110f48b81d2654943693db8e47f89e82aa457438c20d29738e432c6af67423fc38fa2883947b349a66846b729eb2868ae93e7a11ae67d9e09f61430ab5229cea286bca5227c28b219484b31c45af20c2b394fca14b88d6371fc22d93ec3996cdaa8a21bc0ff5d01f47918b6c219c1c4426757c84f06bdc73902a1d8493d7c3741b9ded422a08ffdd53e64eed953b32108e66ef9421de2ad70908cf23654df0f30f5bfec1d7709f29b2e32d1ffde0888ba528967a7cbe3e78a9352b92d1dfb6e3831ba135dd6a5f9c670fde64694bada93175f4e098650ec5c62f36260f4e5cdd5a0ea3078f030f5ea61c6bee204436b63b7875de1f840caff0cc0e5eb40a390ec283c831ab83df1263989842072f4a4a39889123c65b3f072f7d98e39e88c90aebe5e04627390e6e4e6917f5beeecc818393c7db3f9387cfe50d4e270b6e91376ef0ede5235aa749744d1bbccb1c25a2648397c390cf18d5e39e5983e3817f320feb715d8a1a1c1b19f1acb1b672d2e0fa892509963b98074183932ae7e6317557cc193ce94e131e7d33a02165703c89aac5e8f155f964706ce63c8cc1f2e6c818dc48313c3226710d8bc10bf9d80e3eabc5360cbe44c8299face4283bc0e085c9e123491e31a45ff03fec0e2486da94587bc195902991a84f92d3053f4afa63c2528ec25cf02a7d18973d795bdd2d38e9fd2b47f7214b460b5e856745084133250bfea754b9b3ad58f0b4cfa34a41bd8297572b86902243f2482bf8d27188ce548b746615bc8e9db3974905ef3a96d95c15ea564ec1cfa27541baa30b0335d6e04fce29a79c3e31c73fd5508313323b7a98bd37c7d2a7c14b52eba1e71c26b9bfa0c17bcb29575a98902c6c6a9cc10fd45c2c977c7e50c30c4eb5a492d58eb5366519dc4c1a1d7358ab99f385b9e200c7e00bae4106ef3dc8df352b1d440f1a83ff95917cb3e5387fe41083ffb16dff88ca61f0cb6d523078b162eedb58096a577dc1691993bce0c714d27f7c1c540cf9d305b727d347b221d6e0829f43b68fffac3b47c6d4d80256eda66a9fb121993a72ca9152430b4e287f0f5972eef5ecb2e07dc584a5a5160029d4c082d71e76567d566feae4158c2dd917b493a68615fc1cc40edb516d35aae0dda58739f8daa648550d2a781d9322f685cad86b4dc133f1b39f4b661f41ab21854a225cbdc4b3db3f45c761f25ca79428f81efc6f76924f179f5f64208c2e9e6a40c189317956091ead553abb45d1a0c613bc0c691f9a48ac66ab38c195892cff0c25175b2970810ab01650a3095eead452dede71b155bc50c1162fe8a2aca67d00b7c81a4cc0446d6525cd3d6ea4da530ea15e61420ebd1a4bc0c3ab3a66a4bd1a4af0a3e418d523bf6b24c1ff782e33410d2478c95cca23357bb8ce41358ee07cd011933a0acdea670d23b839cdb6f2dc45bf0b1d1f6a14c1cdad31720ec254938670c0053588e044f5285ba7ef188217a3d4f26d9ba7206b0d21789b377e7387641070950bad2f0f0f912daf48d95463f0fce7670d20b8f943baa5af508d1f78317d1c8597dc686c54c3075eb28a3c217610f56bf4c08b1dd39e03ffe0f3b13578e0d6467d798efe0ebc4ff2d7b93e7fecb8d6812f2556fea1c8f9a6a9460efc157b8f21fca4f848db410d1c78221f85b49023dd6adca069cb3ad5f07899f56ad8c0e9f4183643566ae157a7e4db964fe7a9a685174d93254b9b5938d1cc276f92ee78a3280b37326bc5dbe5dbbdf3a20b1ab1f0b5bd43f4f54fb1013460e17c14ab932f890abcf8420229f0e29c8374e0cd0a345ee10a67a4a245394b5be1c874960929fa8b4a8815a0c10a3707d6410aef1e1e73ce2afca02124e4281d55f82d39e88b1239346f4c2a9c50cb7d11613c5f5950e17cb04a1a73189e39f23885e313293964ba47e9834de19da4cb5c67bf1e79b0145e5aaa0b69aa228597f2f45cc8d1cf7ab246e1895864b5dbf049461285e7bd1d9235191de51e0323d8a20b1aa120bcbb2ea6e6ccba5c3a68a59453d88e06289c641d2264dfe827f6ac4ef35a8f9334f3ce41e5f65852746c3ce1b4af64acfc49245cccd3e8841f29a7f021f6458f5f2a3438e15ae80ed3d6e6686cc2f18f5972e4f46868c2bb0966131fb287d9c74cb895d583a68eee474303c384118307c48006263009198d93b4cf6040e312cec73fd541ce1c96a024625cc2e34443e6b633ca448e9b5425dc4822f9bd2e4c0947aa7e23c3497e406312ae6a76da761b25e175745e1725e6bca69a48f8a1e55053884820e1a58491508f5a2585f8115e24f7ff984c39d2d83ac2f7d0d73c8e36a68fc735c2cf41960d29d46784f7b92ba5c71d5c4461e6716791f1e63dc9c358af082f04092a398eee72611a89f043aa7b54f76977c88108673de6f83dc474eeef1cc2d99088507eb51ed6c410ce673b0fcb3b3afdef42e4b6a22a59ef6521de5539a8ca1e2ca5c8488f107e1c7b5699746d15680cc2f3bb10fe628809c297ae18a5a7c3e88f628158a542522542d6ace63ebe903420fc384b984e9321c337f8072f49769c2b578c4f1de407277fce2629c4e8e9238f3e34652d27ef51976a5e26d197eda5154172f0c1fb38233eaeb2ecc1b1b4f133db512fa0a107d73bb67e992a4d1eb677061a7970ad2622bbe51c68a08107278878184248be71e3d1b883375152d49e790fddb71dbc8be930b2265407ff553ec8418e2a74f0a34d26edb183e409cfc192ad16abd6146f49096b8d918a395a0ece07a13f556d44230e7ed2ec5959b4c39472c0c1ab8fb6159115491de40b2f24805b031a6ff0b2e5ec9d3199774cef063f98921c42da1c297ae6f8c2ab0d7e1ccb79ccae90d35684061bdc482a41c634668af28a69acc1cf1e39b45384d5e05ca808b229d633a6541af2940fb9cbe9a3c1cd39a38f7a449be990c6191c09e571ec2853766c9bc15b7ff140727f06b9b60c7e871c7a90c10fb2cdf585bf1c61b531705fa2219231657116eb115b44424aa798187c390ffad42a0b4356b1769e325612369f11338520f6c126248a60f03587f8ccb3e182b8c00b10f080c617dc54b38f1eede8b56337a0e1053ff9df588748a769ac9d018d2ef8bd1e7c5bce2c122b840b4eff871ea22593c61614abaea9913497d4d8b6df88f513a9a6a105377518bdcef3a65c9366c18f5268cf97333b738ea48105bf2d6a7aceafb973ecb98213ca254be890ade0a5a70eb36c39c8c6a80a6e5db8e49155f0cec1a854f0e353796c6c7aceb1e262eb042bd805d098829fcddd33cdc4b8d88ac1910253321540430aaed445394b9675952e5170c58309e91edd52b98602776b5b25325257e3a7bd91557c825f79c343a8faa8734e2f136838c1ed28933be6b553f9c409349ae06a872d1afdeb3bf630c19199edde90f925f8e9f279f4b14b3494e067ea4ce96147a6393322a09104ff25bb47c1e3be889e41829b7b735839a87d84466dd553bc624decbb3607afacb9e6e3a36104b7d3ed338fa56814c1cff1f44732398a06117c8fe9e3991ccf07991c34a03104ec3e4aa6ee89da808610bc770d5e1ec60cb1262708defc4848fe7cf141f00061ab1b95956c2dab8de84afe33c973c77624971f387296d2c5687e631e4cc3075e0ee222a5b1e84d6147a00b347ae04a38891e476a8e83845790041a3cf0d64224a4740bc933b883c2d42a3ac6cb5536694c7a69e860cbccb8cb2e29bbcce8aa8c14c22b648ec3252eb668e4c0f9d06f1d7ab686988368e0c0b9f671f1ec48534e1b1a3770fdfbc4a3d9102eff84860d1ceb9cd13e6ebeac4f2eb6bc08630b3050701816a05560462dbc943752c59c6bb2f7dc8119b4702d3349aa68698d1a555f80b13366e1a5cdd41dbc86b270227c74f5314962e1a47c9329c33278871558d4a215dbd52255ee362f390e8f36f2fb0a3fb4d0719a438e2bbcfa9446f36d8c291fd30a6f4a63ca9d31acf05346d8a8b461269255f89dee293586c7fc5155781333bf59678af4b1920a673aecd248539f6c54385d696424b8f97d4d4ee106af0991a9d9716a4ce19987ac3158cab210520a3fc8ca07673e299cdad05bb79d517877122dbb32224f8828dc1c07eb21797684c2f758631e73ea008553214a92b3530f72e8f8849f2f8f6f867464966a4ff8416ffed0622abadf9d70343dc6b2c709bfc62b4fb466e4ab78139ea598796e6e4d38e79fae836c33416b68ef50fe03137e0a2657de1ea690f5125e4a0f35f96b7dea534bb8e13dc8c16f07aa15530967d5633bda9794f03ab68fcba1949370726945cf1463dab428093f8c65f9bd031ff1c922e105f7b0963fbe20e1fd47e4d4563fc2e98e93da54d48e2b48331ce17534b39e7e723ca311be79d49683f530a964b88cf03b70e9e8e55e04d9a723781ca33314e18b847ccb895ae78c6724c2c951da76d0b1bb72cc20c2894f95b3d2d9f57bee105e0eeff5ca1355529a0ce107b7f12ab318f3854e219cba0b3174744f88b32c4dcb3be32ea55bac374d06f3c8caa4c283f03b44f1118bc8917546108e66fed01dc2a7e5c01208af62d68890feb2ab2a80f0eb62b68f273a3089fcc1f5c966b13d9ef8c1c9e01fa3f45dfab0923e38a5d1b3797ff0c18fd44335cdc27bfa7f0fde8f45cb79fcdbbf7d3df851a4bf27f67fc5c89107ef3d0e9d2b4bee8c4be3c1eb309447d25cdfc1ad30f93e779ebef1d90e07d9265b07e7435d75103ff2331ddc181ea6e093d2d46673703cba972441e33f2296831fc4b4f153afc9cddc3878722fe629f7a4dc3170f06efb93f4ff779027fa066f2d438ee4a7e330f36ef0ff7d6dc2e5c891c56df063dfc810739633d8e047464ab9af424ed98233d6e0686c984b31c786f7c0196a288b787cb2b5310dbe475f479d91ef230dd1e084b449a288f765ca9fc1cf72c93c58c80c8e7daa9ca9528644580667d663c911aadc543a32f89d345888cb6163f0358568c65c89c1f14054b335fb6c0e5918bcaeebacbee4e69a0383ebae5e31878acb53d517fc943c0a1d741879c1cdd453c9c3aa762fcb8c2e78ee1b62b5fa9f7a79580833b8e0c7305ff9349b2db82eb2c12635a5091e99a10537cc7868a5edde285f169ca9c98e3e87cb2fdb61e1b2aa78cd4e891a29ab37b96e91f20aae45becc71b48c153c559f8f3fa70b39e5ac0a4db47c4aa5c7cac7bbf95f7d67474a05cfd3d7490efb7328dd14bc34bbd9f00831a637430a9e7b581dbb07761f75a4e1408d1a4e9811052755858d51161dcf57a0e047592343d966b7cf3cc1bfd41922d3d638cc70829783ce1f623af72c95ecc0173498d104472c46df768f8a89640613fc38b630591fc7db12aa194bd02b2eb6eab2264e666653c7a8b4fe511382194af0725dfada0cd28c24f4b35951ca2f8404cf2ab8a85a08d6a8b10500dc30e308acc86c744ad6c59667a894657694a24db2314930c308cea44a9875190bb57946115ca9243ed9fb23bfed10c14f512de5b29bc71e3d660cc1b35c29b7577d9c334709c1cda1c3346bbad4586d10bc105d913768464bf00082ef71f420da27e9f3b366fca0ecb2aff77439f5d841d4af48ca9fc2337ce0c7fa383b6bf7356a7c61460ffc9aa88f1d59c87970171566f0c01b9f0fe5993eca5d31e2c18c1d3839fd85e4b4195386ac03bf2ea4106be55353c766e4c00f36513296c77bcc51b89881033f4d4ae49f1ce5067e2c19daba932c821936f02c4c6c6b8e8a149d530ba7430ef28dc84f0bacbcd2eba3a3dc453c347314a29d2db3f0e3e389d948933b458981116cc1820d5938dd414cb5a33f547446868d58381d33425c7f59f48e030bd7facffb7e62c45ce52b7cbbf2f90f993762785ce1755de8988ad8a874e903e3e10182c161c16824128642c1cd7e23006315080010541a0c45721c0792381c7e1480015d1e1428281c1010120e101210080a120a060806060008060606000006080685414276608e65301f113ffb4f02b53e38081a6d1e2e448eefe4e0fb5cc5a118bde5a418ef40a7a3ff30dfdc617d9b34ffd7f073d12f7e1bb37969b31c27467445e227f755a721f8aaf4ed82f77e9665f5bdba1d93731f72fa089045db88560ebb5199a4d834c95ed3e0c3d8e404d1a98cdaa2134f16942e68ba9fba41b2ba300222fd6a06b1c1ca6a896b6ccfdc63360f18c3951a5ac915597f4aedadcef6e00fc63a94a3a3b4606bbf1e2970253b672a21a18e88f40dbbad6a3435a3816bde8308bdcfcb169b680e54db13f5408b1acb24ca84317ee7365b9dbc93794b5579120d7a4ae5240048fbc521b26c423e8706e73d5f85f5ac1c5afa60d3a589fa8152aa14171dd0cfd013d14c91443aa1ce2f0943e1a836035a3a983c7980437517be3861a1f8f79b8c1b1f927fafd06f66e40a472275af5edc8473ad22bf37e0afda5492f7681368876d3003b81786e5f64530fec6c8e0351b39ae5ce166f2c92c2e4f8d4fd982f223b601fc623f544a3904486588ffa2f4609c33ecec7c0752a2af78c9e50cae72365db4ffd4d9d5f034c4951e28cf8a206bc3158b8034ff1d147bd080f7bb7c5215e1bf86aa9ef40ebca6dc46c27dcf35d31a2dfed9e13d1ce94590fa7dea84d510c01c78a5e7e03ea7262b79991f097b16d45de0bc047f6bf565f0d5bd5466c0150d689bd21e2d4b011cf4ce4d925ee8b33a8c9d29b4da52c74554706903e0f8fc281d6b396f1f8c7a4b0b1c7cfe80e15baf1acb1670945e9f36ab648e2ae2c6d954997c2cc80ddee4ee6cb41a4e12ede533a4097a029fa4a4ec773ec05976c55a48b8f09349043b5b4a28a671479ba5b3069e22dc94788a66345e7fb8cad240ac505a4a328a82ff007dccce8d4ab68283ef9412e3b125435a8180fa0a4a56afb162ed4be7443bd5c403962e29574c00cb41c01e849dbb75a9b68cf96f89d63dd92d6eed27aaeca953f48c9963ea2c04db3bbe3d7f183d25a25d32a480d55f84827834a980560455393d3e25adda443cc509046da84f6a044407c91208f7a3f80a52b9c0cca6f2890cdd31822bc35c105bcbce2f53bf4d765625b1393ed2d44b9268cd2bc06ea4e09b5bcd5bac084fed0034fb21abf6115ab06e62168ddbf0feb95e4f7a4ce9cde2074d78a9d21c6d57f7b5cafbd44dfc3f328e2e5b5ee76589391d0fd824bbd2c3b7d43c9e8a2ad048888d77a5745e48aa7f2d7e3a12101ba7a136ec898891e432fcdabccf66ea0f52ae4d4dcc70754345454b8a8052b684ffff4a5f26add98134ee9460064916abf75c718583de2c9ceb02e2b4e15afca12711fe70cba5fb1bc39e9e95e39764a8cbcc907b0dc5c5486c44c9ca42be1102f402ba5b95234f000fe40158a79905ffbc14f40d2525f74f40a85166555322f1f70d2f4c06fb2b339a1b2af326ab74f181a4ebcce5d6e6b32fb9051a7e9cc2341be933e18cdf05087ece950e94975163b55431635214680eb1e45e63900ed1901c956df73349cc845b2a216ab50ef40049e2a0fc6b9bb803e146d5aedb7e4fdc789983c9ed8817ce44849c0ca78be6c68b159b3b8a97609dc37edcd8158226241d48b004d8f039933a65a25fa0236cb4cd74d06c94966ecad561f4a198f8487c8518669a09c76cc6ab824819b89d942325ca44e8dd2f9795174188ccc62a4d2ea26b5656cf15d7b2eae7a0bca35a67ae7e83d26ef945f036f900b1030d8f107ec1700caa943234993983d18a22a49650d76d20897aecbc70e29c154e2578275b2af301cd9257af42bda9126b6a1b51d3cc92457abbf8a38e9985799d8b2d951ba41710f283d174e059d883163a0ba2b624bc6ed34d99ce504e31b2c173a342ffb6f102b49e09585ad229b0cb2bed38bff2f439d37a65995dbae0abbc1048961f62ee362f04b4d213c22eb7ac8b7342784be7ad77a30a41233bf7b46ecb290f83833f9bc307696ea4777580c982a941c4f715fc34589d90f834a4adbccef9fe469290344b86a7dbbea28281f0561a5fbca2408e420ea552337eac8227057a935e8a52d892e0334c9ce596c635652108f790a16cbd412e96f3a9631fa5b76cdcd1d096178a1bca5a55a454cef8f0f51949590312af52fd24695dab7da521e9811855046b01d728180095e577b1c659665fd3208243b8f42903c07959e8901640a990bb89bc9cf2b1b4b0a014182183ea944e1013da0aa376dedf002b4136c0533c1ba5ef98054da57ff75b7a8ba7f0905690241fbebabec72460bbe53b34434e4158a7a4c3bf7172ab95a54de9030704f411f2e3560e193d60d7a90b8a68fe143a8300f0acaa5600cb64c89291f98d8fee99156c19c26cb4a4c86c005c5b4c74286f9e05416ed8adc9b0443dabcd45031a54a44c719b5c5a3f1d93b47208fd96a81c9eeec29d02c5bfb7d31cc52c95b380ce2b6ba652f96cdbd52d64d609064a090f33b5bf85ac07c2019eaee010202013b6c352bb2ada2e5dbe4661697ff80a0678af24fda14cada154ad109d768a34502f1ba57b035dd4df1e3662e40725b987da53ee41685d6fde29cc4b342adc9b43efd6690cfa5860dedef1a4361a948e62504cde6d35ba2d5a49d758aa21ec1439e477e9424faf023dda2409af5aec88c2b99ef188836c7cc52ba3de648ab5b38515a91687abe5cbc924935d37f1407150cd2345a7b90f4f2e6af4e259950ee6a07676a7f2301b1c4df9ebf65ec7e7ad5133889599596d0595e355e213ea6e7bf67101aceae57912a7985c82d11e8475c5e4aac4c3f01d09cb25441a6adb32a478309590a6e8a47d3a1c16a3c61608f78e70e7b7dc2dc45af4983f1c94cccf2669dd08f98f5563319a15505053770e6f11298d706013bcf3c60e3c727feaf918fe988819a1a215ecaf25eecb1cfe29232477c21f97a611945678d33728766099f9a14ab62dd78cc888b346b0709f917598638607ca9305263612d850c308be4401402ec908f0454546af35427ec40a97580069739498b124c803d968a10f1b8a7afa46ab84ddeb2511df72a696ca1b55be0bd0ecd58ec06909711c7b5a46a28d5547ee7cf1daaffc286401a45201e09ad4cec8b8c0513b6d0f097de3e6358420431f80bba358630827fa5459c551b535731f80e59c2cde40c25e39b763f82009b4f293383d5ea81074fda72bbc32106e688a10450914041825764d7fe7f58f2e0081c24e56152e6e8e475250be06c6d984c32752011dbcc593b5fdffbfeeaf99c5022f40395dbe3bec2e11dc014c3c3a2311c13d063a2f0fbad1447b00f87c80490824da5260aa5c85ca450b745158e263cab2ce3196d4056012db99b102a3100c6a3f99e76a64bac0e628ae133b3aa59006bd2d43959391a90cdacc87ac62a02ee4288121dc11159330b63de8a333fc1d77968bb7faa4b0166a25258b4e4310767b7a0ae651928a396b91d6d7048fc6fc437a0ef7237ccd4d4e66b00dd59f29ab01b4c9373985b515a7d989d8d5c8afad9b9b72a3054e9f8e195ca45e01c234ecb5d4994779a860d15374497bdee5f84618095df991e2744ae8a492ae497b846f48d88e4b58ac7ab46dd8912832d97873e79925ef0151a2d5a415c4b0de7a664f6a342d0ee6b501e22044b6513ca105ee3ca45117bee4e33cb01d461e78ed9c4204f99127636cd27ad2995adfe6206925d63354a8ce1679490e0f7895f46a9384f28f8c4a9cea12b48b675492948cd1c96aa7a9bfd9cb71adf730b7d83b4d41c28781d44e55213cfca21db544ed4b01a96fa24c8bc80f16b34abd5dd1b6899832e75082d3261a560aef9aee4651f5a150498e30e4fa30acb318fe21e917892ff3621c4646ad88b2cfa491a410c4a19df7c6091657125e3f49297b7a29809c2f7812f1e64a04c27dc801b6248c043de9bd5094749de19cd6007d97af0837aa29414e5148890be1f44c0550d3c8259770c19462f8b426bc3daa66ec46d72955555e0daf4245bc289eb0c5213e4353d5014c141575761f954003e3d0381c8e60a9a3fbe47607feab92f4dc8c66a6edad89ef52645c7d3f5570a112f49ec6a26e92761f6242c5ff7634567ca4fc4754e64e5dc591501639fd3c03633e8a2c63ede1316ff31e4988257ef77ffbcdf7a1690f58f47c9c42d73bbde0e289a73074cea6149252022552b3fdb1f9229034ea5747a56744e6707b8d03ec9245596b9a45173a2e48c1e24a0e1c0a6c41201584a458099c7f64a85d894fd7c98d51e352810e725f2a163144a8d3750888740a5e9439186063ba8a88209cd34aa2f4e11e91e0eb79f4ddba3e334eb5128b6cc825536940f9114ab0de49509212767c01867d7b918bd325894adfa7fab8669db630a03f7f3ea91182dc083cee7c83b1e644a082ebb1ad0b8ce949f2a433e7986f08c20120b1651fdc53667b0d20fb9a07bd532eae5700d443a401ba89f34502ed9f5477b62a52c0c666ae6daa0a4c659fdca3d28aa184d1994b09d9ed5661c919d488dd708f5cb11803e6132da66803d121633adb0b90039cf3170e0b37e65862bfca93611503c61074734b1535dabe239326746a2902cd32a92a0ad1aa9dab303e5c74bb4b2713989cee1cdaca26c402c1c868eaa0249d7ad4af0d6795e3d3680d09b79dfae20410ca217cda3c1760ff686ac454c08548961c5996541617154f0b87333c72a254de1e097bb386516a314600e041c444a7699478e5b34b5385e3f226ae00461af8b234efbf4b22d1e89710095321397a22a98179e92936eb336ba64d1bd35f7f30d84d07f344595035b30978e9b6c9311cd1f7723ed5346591f1188be49540c62932a08be7228441999a9d7a9075efd56ce0034a8c000928ca8416f91dec4a7433f24562e75db5632be999334f2a6726b557845a773768d8c9b8495dcdd4e474c6913676198968f139478a3f7e4da5a1821e8d25c52de7dfb4358ab744c8e44ed8a2e70a3a749341c53b230937dfde8cade31f4db385b80239c1e65f29b74681c5d8f5e876f113aac87ddaeaf0da31c1ef83695c3253864d4d8329a37741aba91c3e8a664a83f0b7eaf34b87b879b1757469a62410d51059f44711276347a1c5a3c7c7678d2f3a75368ba8c0174a1f834c1dce04c7c4fb5455f438e008cc656046e1ce7ff5c44b86bb6ac6fb3438021ecfa34b8e70ae7718788987121100e52f5b6300cba7ae0f7e0aa7d9326479bd643658ffd181b0c1f01042f5029cdc15d051113fd001a253c10c78b3f10357907dc7407ce136c8ce71052a0370a826f2a24443da59a7a38c2807410f999029f05be30586edd3b7c76dcdb8cf23e5c88df94e1ef26c93f972c6cc04283f90b5cc8f3afe258b0f4f9c19d1791ae707eaf9df318fdbf526fa68cdb9756d9f0ff85c2b923ef549a5ec073d4ad22f2904404ec439f7fb5898e27ac347a2b0b1a1500eb7f6f5635118d18852ac24ebeb50868c9efa0ae0cc5d007a401778f2e11cc6877d98036bf02fec3dfaeb801338993300e53cdb670634730fc83c9e28b1e80f620745123475ea5a4252ff5d807aa8542fb0c6f03bdb04e8525d45e9e91a3ec93c55016a322d8a7f02e8aad2b2be3d708ddd5d779dd63b89385111d5d8367e74d0f44be72d9280c1cc985debc2eced4503d3f7878fcc9b1bd1904df9d9f7eea4bd11db68b4f84df69ec13e23c66896b206517b34ebb6a6faeb6aba287327fb50c167388ca8f4cbb17044f47e952efcd10cb1e9d56c017fc51dc959d88c5bd7fdaf374f10fc9dfa2b812aa11ff6a9280e32e91b4a558c522727a8faee1a7ed56d7f00d2debcbbd3d9f15524c4491ce740c818957da3f17700e6f4d2deb0a37628284503368c8ad4b4a753eae0a4d637eb130700519400d800ea3c0c686825aef012d8b8fd9c8ed0b896918621602d239a05e24b81cbf602b9d0fd0b50000d3c38c7af7b035c6b9d8b56f782f5957386eb766d713d478c5f23e3e2507444eb57a78817369d02eed898104002d7ab2bf811f05f0bf1ada8770030925b691310db3640f63a41718a91e4b5d5a811222daf022e668e0cf15b5f4c1f40f4a14d36c3c7a7cc897444652ea15844500d1bda8c6fdb8c9946f094d1a96906f6970711a21cd58598c5d391bbfa0da0d26207503a85844ef6c57bf8683cc2400b9cb0df71e41fcd2ee2263fe17adaa5ef808f106af4cbb2a01c25b09da9e166c64c1749a1a04edecb6ee4285d50607d1a546040e8f2671d9a63500dc6a8577bdae4e8dbb37371dd292589c2163477b47401eacbb169348e6f2f1d141bc0ca8d7d2aa6246e56a4949c6e45f2ea7a064eb745193fd209a9c2fa0f38a01437e719a862ad0dcf6e347ad359e0685f6c6f138a069fd5dcc685f5d50031d0f9607636e61910b61e9e17ddc846dcc89508ad55a9045e1b13b70a17097c4022216d2102e80c3c17b6772cdd19298762b73ebbade239b2146a8db532eaf6d378ff39acc89550a640a46bf1fc8d04f7105280a6858c3e9100aa50b2f32b205fda164ed2faa9613b8b34b6ac1f1fb26e684c67f17ab955bc686244bd6d2e4124e669db06470e9c2f1a9804ce35a3eb54d1b3137bad0f8cb6d1140f8c235c56955be911004ca25d810472a90df4b6d1d0cc026440b77f83d2da8f0a17e009b6286dfb5d3f32e22bbce92081572c018b5839740794c965b8586434577dd6b4ddb3b36c6a77ef30745dbad89608b3ffd70f06d442ec0da09164087dba36f038c2820d3e6088079a6821e09bbea05c14801a2e787160dd9db28b0e2414990c6affd7ad95ee64fda22f1e1c8d6134e2054b4a7956ac8d46f5bf2813e45ecba3690cc10991684333e92900dd4fc9aa2980a385e63174ff12f998cdb4b39dd3cd4a9aa56d61e93c5113eb3473ec4d9a4c3b55d865d770c1dc69a5b6a8810e91c955d32f3208a86098500cbeb1628c9a93aa33a9b4eb22af1f7e33e77822d73b0fc3059bf9ba5f0f0726fdc4f4b639dd30a748eb8ea9e93e55cc35969d6d4b6c70345c5d16588c8233799272522d2725a20f106f60950a3eac8fc60dacbbf0e0e270a38f44f1820697be78ff8630b20a7386fb94a5ea3c5d11a0cdb9b79a743bf6f7f7de689ce18a6eabc4cfdbc4062011cb263a198ad9f06dc8364a2997469db87f15c8814167893b3c7b649e7c27ec59825a18a57a6c71852b23968563e12e22de09ac620a5db2036cb0f2706a4b579c523cf402a5b41fba1bb72953e281c457604453293ea7001e1b9147763d2a8ec587c2563f7ffb65c8f217c7395b56d648a779bbd6170c0e9b99337bbecd9ac8a82ec1b880eb4b33a5ee01484550b3a2456a02715a5b77c2c3207c6c158a69c578a549a82c495e5aabd192f10566b7a9c85941d39c1c580a6883081eb744b320304d88213f5fe64b0012f0e665dd7d62d9be9c345334a2fa71305ce4e2964071a768b7506f17b670a94ece1ecdf2e17691168d6c3ac277e2347525738196b25356f602a42400e3682b33f497062515a4bf4fed6db51d3933d5f4643451866922918cb67fef684d5022948a3515f854ae1d8e0530e84e7f274041d60005b25faa0f7b9e6137c68ac301c8dd56771048f7a4ab331d60a333053a139c4e21eb48a94dbd80872461441c324a515d0e0fa6c52e5d70e7d7e1ff1bbf41ab3d5b778e0e8c355e323b6fcec0eef53063b366676e23a35971d3510999cd83d27721f01e6ad1bb7b26d37ab5f8826089200e8fbb587294f18deb220b75a1b437dc747e26912ec11934b95e1551ff8b22e060eac8c09ae15d0b4b6be9afc75dda0deaae5e190305338f758410ce49ad9b9b75787b61a42e5dc2cb6a147a351df04e35d38c992bf985dc1cb8e87808ad4e54012de7ba0d2a593f142807ca71dbd6c5706194d3be79c0c6c11e99a2190391dd1970c88e76ed7dba93a5913e8bee33e0b6b32af9ba6f62399a6649ab915e1d6381083dea087d142d8171496ca623808337a0e0ebde307554e19b96ec224d0e9fdbc74834bd0a4a5f8dd8d93582a7634660a9a11252200046da4066c5bd4298b9669ee185d029c790179a43fc08b2ee170beecb6d3f57b19ef4e9e14720867500f83ece013679e5bc5ed613b8cbbbc4d5c9664aa790984b227b2d8df330200f0cc0a16c167a0225d6e1c1bbf3623e19077a6fb8fd20941a0b9309196968477e8c9a12e6cdf7c8ab8498509b2e7baab63fa76c25c4a5cd06b5069e952157203c83654f51f5a2b2ad1a434ed5ba2842e875d78d06dc8f595d91cb28b81a0bdff4da939462c9e045f8e914be8d359d3eed869d42da1694bc64ca56acf6000486c8e844982ca2f8ef43139f6e93d76016772d40177d16d7c457651ae4ae45dd2cd16a30f8b8f2dbf30e942c9011d8a2ae5d2a2dcf237e24e84445b37dd8435f4eeca89af80e7f6c0386824180445586f6ec821d866ac93da463624e075f1772cec40b134eb44786f78a96fff28e09468c81594d4c71260676f3110589e879aa0731a888f3b14428a4d5422d63f4076c15a77c2862b420ab1fae43b6e5f0c2f831379bf3af7fe9748aac1842d262487139e3550d54e1801c63445ce6602a56b4998b828a0c49a5485a24d2fe624e86b88d66051cc1a01b960f1cbdbe3cfdb50290400132a07819de3ae92c500584fa09beff22efd3cc9f6d7db49e5516f0d70b15b4168441806c818284fa3b031d5c5251a70e21e28e4a6ab554bed883d617c2418cab0310a7e6aabe1ff60e9ef71c54cf7c51e99d4ebdd9b48744321d5d1bbaa2ed6e2f6de673a1d97bec65d8abc57b7a749727fb5fa28cdf46bc475dd2fcb79e907491f6b935b90eeae631bb3f9576722ca14b48b7be2ee3aeb1d7bed76e5e5ddd6fba1f5dde7441754de9fa55bacb8751de8913bab1ba82bb94d37b5e0c1d5cc7baf6d3ede5bc2a0b435a4e2bf2eeb0372daf015d36b5bbfc43963ac8d635dcade75d23bacbdf7895d9ab6e48dd1ababf5c776b650691c7e9c2d415ac4b52d74e177a5d64dd8abaca5d8d783dea8ab3dd42e87011d2eeddf4d6f476e9f5f0f2d405ed9aeaf5ddbde744cfe0f9583720d17dada1678d5974ebe93ed4fdd7fdd55d10bbaff990675408ede6f4727a7d7a817a35ebadfbeaafe2a66b9be1facc689bf8c930abca77684f8956c0b8e719c67893cbcf07acd96a53f5706355fe36225188880a23fa090a0983b42efd80622050158e645920a3a3e79efacc0535e24664cb277d86a2b1fef7660e7f141ad76eb950045eb3a80522ad1dc22b8ab5b73c6c58ac4cbf9b9b927bb5045957a867ff01ed2017181711feca5a8f8696be0879c9662ba0fd6f2b3215cbedb600ebce62638facd876494f9e84ed5107e8e6d857f580f4dda4797c5757f5cc8de220280f2e4dda454c90ad1cb649e5d9b0d7d09af97b2e89b99a867dbb64a904208b51fca950279fd0046d09cd38f5ee40d794bdca940b38025a5181532d6c071840baabd90215e000569667e851533f098d91226053424ecc8565812c46ba5997b570300abb2970f659d5a6d528fa2578736588e78d00ed9a2555573bf7ac19bae4736776cebb85ef1df6f67cccd87173c51671ffeb0b2a98163707103cb76c5f70a4a6f42e04897f5d591c2bfc5ede80097d1e43b30ee56f59ff5a4fcab15fdd6cd74f8927b0eea5398ef55fe7d3f44d6bd641fff3c20beecee4ec0903f129e187add46db60bbd1da525e3033eec967aabcf936b6c2e49fa8003dd1574f501219874cedb2632f573b05c430c4b21e2471ff5086a4e74c6934d3f9b8368e66a687fb288eca34891b8448e8e2cc1b5c8e438694800864e62d8bb3d1cdd1b9f2ee83b8a2798a4a75dae39b5d0fdb0f59b374aa7e72ff32137519b284623602b02a7755a09f82ee10b290d1be4ad3d814fa6db0ccc198eda902a1181439a20f48686f5f016d9334c4c375937569b7ffd5523c106436c2fd8c75bb943e57a0c54429001ab84a78230814c789c1732e94046e8c4c017a30399598114fa5581e2126808ea8e405601b80c6418e6bff97423c83457052f37c1effc7dacfedd74108147102fd5fa380bf3ed070668e82ba913204d78e6272d5ffab557d7347489406e20b7e00a411a0532ed422048bfc0af00a0b1a80be0815509bd41ba5ee0270aa55ec546810cbf0722e39a7fd5ffc005aea5af11c90288bc48f03209ebf526023c0f64b247505134a600e5d7a8e7194f18a3d485c363d1b58e491b807a3bfc3748fc143b25120b08c5999247fcef837a74fc149423b1c2485860e00632697ac8ab9eb3c1aa39f64b0b029001c102d902b6026481a4106432498381eb0f049169b06924441db2e7ec6b6ac12881c31a647cae7bb34108826c1f85c3707edc54d8f600ad48d08d505c0632f9a6f40035952023490215d22cd0eb60a01e373c18580c2ebb02610f4d0913f8410135124ab856bbd78c8f1a972a8155098d446776e00d44a7a92750511cb095f42fd205d9ab573352e50bd60dac46cdfbf659e1d2c7807d94fb0ef2360932c11d28838bc08f1235000187ab2ba806eaadba654f65ef868daa2029c195d1347b68a1c87aeaad0b028ab53b972f4074904207f003fc003fc00f20db0889adb5de499429c9f2a95e21e722534a29a59412c3d7751fce38df70a4d134079709030935092849abb65d984491a3fee424a6e2a73fd8564f1746d50dbd3b9fb4051d2acb8569cee2bca8a43f8ee75de8c00597d4c6926c49cadda2f0b4af6ab999afc3166693740853f7512dcc277ff20875afca26b409c0600436669420081f1011392cf01a20e03043072dba55adbba8662db216c654740a22f24255981db3308aee1953e2d9202965094420821a23344af0e8a4ea90c58c1a353c7410e2e181808e5898836753caf4336ad4400974c0c268723ed9548b5f613a59a7962b2a115718bba40e2f4abea9e9a81506b93f71fb16962459b3c268b296ebe8f419f1d32acc419930419892df249d9781d44e154e03e407346e38a02315954e6a54ad96f11e21353c6c9c19666a830e54e8f2d9a6b16919e3a5599f3f5e44ce969119a9e314a652f9920a6d4ba2b63264809c8083c314a610a679b2c80f35cbf9606b187494c220bedddc454cd0771529cc7ae55f41f4543d3474002272a330a6dc29b92d1fd5ea1685d94de9c99f243b140695a49473d4149d3a897fb0257a07288c2e6245895ad3232ffa602b9f30594a1f269dd88e12c4f484a99389df9315d64f8c3ed8d63a612eeba4dd7992d6a5241f6c8513a6b53bef52ea3927c71f6c3a3661ca3b2a558a73fdea8cd584c92efe255325ac8e124a5948d09109f3ff870902e19d0688475a0a3a30619244d733bdbea47b4c973069359ddb36276809e21f7458c224b99f08ed163b23229530a7493a8a50514c5aac3ed8da2861ba50a92f9ea75caaf5c1662be89884d154c7bc18714ad8820e49984e662f947685f3794522615272ca387d79623d27618384593fdd95595ee41e61d0caf124b784913dd20fb6e208636f9a6625a1a4dc973fd8ce4464d89801f27b35403c6c887434c29cbda2ab6ea92ff7920e4698edaea477d30f223b7e4de85884a9c4f1f867e1977c4c14613ad1392de82ceae78e44986cc484e9a65a07718208533a5954f81cb7435cf77176b9adab62a525cf8ea13f091ac27c27ae5fe5523a9e2d84693e9c924d90760721cc5616b4bd9f18b24feb1884c184ba98af5d259bccd621089312ba3a84b0a04018554b892245ff75923f40184b527be6268a8fdd3312aa20082724e4e81f0ab36929c7cd9c5bae5c8c396b3bfc6012dd52a85a1377f4c1e4256a46f498d09d8422aa821a78c4a0830f26f13e42cdc9792fe5eb83bb6f64c8888d8e3d98b34bc9b65e930439793d98af32cb3a85f3141ec22c74e421f5c935b4db1380c1086ac08083e30735424254d0810783ca89a73b18b6949ae06f723c39be7630db6a9f60a5e44fcb50471dd00c2b4bd72a97b36c2ce4f88f2a41caec4e07f3dde75d0e6929f44c47c4d1eb113ae6605e7d0df5bfe35e6b92834909ea5f64b8dd5f34fde360f44ee2cd3e880d07e3096af4fb83e90dc650928e39a69d6e2fd5e106d37d3c21f465bb0de64bef954ac5b4adc36c30fe8d09195f4b0d5f91d0b10663854b265a54e6684b01e149c0c10184ffe85083f9d6f32fc813e75d4f3ad2601a17bdcc4b9ec3554783712c8993cb44cb9a1b9dc1d42d26754bbcd196630683ca9757fff94cee313bca604a2f222da7920e3298540e65c9dc741a135f0d19cce81883297fe7fc8d0e3118467aa83269fd92d8274e4247180c3aad5ce5e9f99ca7c4c376091dde9c86cee9c4afbf600af1966eeef582f9c58210e7e9a4527717ae7ab313b7994dad98756fd17392439570c1a44dc53cb1e537efa28e2d984d7afb8fa6e374bdd81d5a30ed7d527ae6dd3fef25c4fd7cc8143ab280a990915dead21d583049a635cf4787c99620918e2b98326d2d3ea592d74e4ac60b38389a091d5630763895b3b37c324c4a47158c1754c5d7621d543029c9566684c7765653c714f28ef3ba9495d7935f171f1374523ba460cab1f4e7a81b2a5ce88882c953b0f6b8b5143aa060b279cf13f3781e9c196a19e8788271942499605234f931718269b404dd1e4c124ac95213cc3177fceeb3f466e799604e41899730af6309e6a09e942cda2525983a9cb8f87e9a76720a868e249884db074f7e695b4307124c723577e4ee925462551b388e619254924cad9cfc388c61b439658276ecb929f972068e6298b4f4092bb292fbe89318a6f333311fbd3bfe05710cc334b7a2ba392ac9deaa55814318a693175efc4d46048e60184c67c9057d9ae3b10103d31d3c484be62f4ce2e2d45ce9926f2ff4456ec9526d56854b8b7535f2b282f7961ca6ed854910f9568fa6b2534962c3c6c13b0e5e98cf3e872d41d8c6077b17297da5dd84fd521766f99225d989df0825cec59ae95ad1dbce62c75b5792df4f52427333345c98bce5c63ea5a758da988170057de6020e8e5b984ee85293db45877f12c400872d4c7262e1653f6ab8273fd86c80848c88c858ae85b18332d554911686cff6ea9372b8291316c72c4c26e7e027a87d979c9418b42cccd9a4a44b905ac99249e2e00003472c4c9296539694c8115a270fffdc12b4b980031606a54774897657c2c0f10a634813b30f0e5798cbedb35af34c5a61bbdaca96b6654b75cf3ccdeece25439f943870b0c23c6641fbd4662e5c3ee158857176f4a792649978b104324315264d0f4ad250f231482e70a4c2b4ed39c4ce84bac0810aa307cf3ed87c00e21882d2672f23b951c34b1a9ec224dc67c9ab25c94a6599c2ec1dbef2f7e875f1bc14c6b724e50b7d5143059b145bae2eabb4bb751d9e3da51c1e85d1475b4e59beb38e2a0a8385eb9653ff3842612e25fe277f89bb711d14a6ca72594cbcb44bd18280e313be8f4eda4c505d042e9880274c27b7569cf8160c383a61545d51d144ab50ed45e0820924270cfa4d7bd28fe979d2de84f16af446b343cb9e18248980b489c80019b9a109639d0927c73b7b763765c22cfb6796e34149e0c084a95285ef94175fc2f4a6cade74ee78be74b1c16109c3e79874262c8921c408a4122629dbaf99f820b231250c26464428256f1abf4dc2b4b26179b115d44d2a09d348936276e364ad9f6bc9038e48987a2c6c54ec29c9b40477c00109c359b0923fc1843c1765d4a891c8e07884415e520aa9e15102b6906f157038c224267928f996653fe97cb089fc071a39333c6680848c74c88848238c96529ef49dd6f296bc0c3818611e7195f7d361fa10bc8d20c0c0071c8b30474fea55d7ab83072545184b94a45b4f8e2311a68c7867a87aa8572f5372124ed7111019365ec08108639e24fa872d9d43986a4457f024491bc29c946423f3528927d7c85108e37bed5a901bba73bf1c84307a0c7593d5ef20cce339f24e0ce5f971148449a92027ad053d9167b22aa881c7e1088479d4847eccabd97ad00d191f62839b0310a6d8d9d3e7fc273d6a7d8c1c16e4c7c8b1914118b9e4f80356b1ec6a634cbd5bb37cac73107e69a2fe317258c0c1f13132c2c1a17e305f366972beae9bc5f203ac0a93f6492ac928413ebfa930bd68115969c162afa830d8a7480b699652509ec2a0e24f990a52f468690a83dcc53b154f5c44a530e5ae20eaba39290c361f74bcaaae52988cc21cd6a669aa4b4461f435dd925e234a6a4928cc967368adb1d4410a0a63c5d2f1e3efafcaf409835dda2755923c6192c4c4ce897fe9a64e182f48d1b024cf0983c79ccfeb1f59d9df84b9a468116abce25aaf09d3499fb9519e4c18f6b26e9c7c4225318409839e494ae91111a7722e61b293827a7ac5e7e558c2e4392a8ad2719ea25c09838a0aafcb2f254c6fb735df97c4f1741226f5f9fbbe939230a53f490811ad2261bedd5d4be13a90308993d352546d8ab6ce23cc335b7ada29b47a92234caf7b4932370b96fd469894f849a8934c18614e25c9b13bd48a5ece224c175774db3d5bd88b220c625e45ceec449872927e6f225dc562441877f4ca2cff9e68da4318845736c9f7358441e4986c117ee9e52c84494a55c1e5469f1426218c6d3909ba94386f826c10a67c26eaa912cca365451026d94356c6c711e656098439fb870061b6bffe2cffa46256fe80074f1955dd0fe6fa792b15b7b5d3ee83c92e8e0e9bd712223e984e3a0be1fe274c76f760d029087562c57ac57a305f8b1e1d6b1ecc2e7ad4f9a5783099ec15bff6bb8339c7495a449576305556f2ce72296bb93a183fc95972796c74481042c8f3cf5acfc1f84969a8d5fa3442d57230c9a8feea8dd697521ccc696d9f544576551c0e462f9d4bd408bdc130ef234225533d4ae406937dbc29b96b0f0fb5c17019fa3f8657149d840d2651c7732baf3598048f3e7b6aab4e76d4605092da2739c53b7785d3609273eace1f677f5e643498a4554ad94f9413d9f1198c9fcc44d39db1190ca7f38c302fd9affc32984ed465a98eb7e1276430783c75d24236bb4dc6603a7949145d256230e5d35af154faf76c87c1204b67f3e3c67ad082c15c2697d2299a4c4ba25f30765730c94b5e30ca999e57f4e4ffc92e18ed635fb7ecc5ff920b66bf4ae2762ab7600e7d1d4a27cfa99d168ca2738e995fa7f27559308a2ad9e7795b2c9884efe027bbed15cc2ba78458e8c5d69315cc41df8ba59baa6052ded9d3b253d03351c19caaa37988eea8ea4fc1d427e90bbd242779232918ece4caae2ec9841645c1dc37faf7f6de2a28818271ee5a4d499a6ae9f20483bafaebce75ae753bc19c3c44aa5eba09a6607f1f3d9f5bd033138c6a32c593bb4b30d78f4a09a61b694a2979b424985c2d0927f2c63d8a86048392d23e8eeef079bd631877cf4ae82842c6620ca3afa969db8a613e41cd7897f061b4a6c43088948f32ae0dc36c5eda73ca63e8662a0c734e1f2b6997eb9e4a83612a532a0973a6835e1230cc298791a7f3bf308d6c8c7a2f6194ecfb829d7dcffff47b613ae195f37249d0d6f3c218ab5aa12e8db9eabb30fdffeb29f9dd924e7461d86ded24654f2ecc23f37bdbcbc4380f2e4c2a9e3b4e7dd2fa975b18fc527c7f4ecac2a5d8c264e94185c9762d4c6a61f2c930a13e575a18d552f6f924ad5baeb33096bc54a7722a0b9370e287fd598a99672c8cff7ad2c68dde37818559c5da4d5d2e5f613893f2f44f922b18b968af1ea65698479aa5ed9c7e84fbac3097a68a7dc719b39c5518ce7e2df4e4ad68a20a93a47167b25352618e514f4a33541843c9d36bda97baa44f915222fc047db229cc39a6e9f025a514e6901e3474b2f029aea4305f92426d864661364918dd1f11da9e8ac2dca33b4ee8bc1439148651da4de78a7cba101486731925cf7d8a957cc22016432825c7d013a68bdbc104a5c24e184fce29e72f53c1c7424e98e3bd5cbabf8739a54d98d5e4fd88993ea9e49a30c82bdd75c29e09839fbd27956f553909268c71a5f774782e618a919febbea48f53b184397e99c99dfaab84f9bfe4fcaf4b09f39e64e296ba9c525027618e3f5acdd3f98f1825615215524f4c353bc945c298f2a9c4c95d694d3b481854904f9d5a2b9cccee1126d5f93d7ad6aaef758e30e82c3541da9b6c5e5d234cf9f49274ae7e57d531c2e426a7ff1599351b5a84b9b2945cea278a307c8ad7e9de245549893056aa4ef52375748a0883c9a57b74c58a501ec2a0a4943f076d6782aa18c29c93bd5ab6ba10a6de17b724ec57384b7ff1c38330d7985c2e72bfab654198f2a4104a4e6a5a9403613e91f1e649de2f95058429f8772595a42f53d23f982d4992a0e412f4c48ffac1bcf9bb21d45c929df6c124d42939db9412cc523e184f8af638a54a1247b907a3a9f423dea2be45d3f460ce4fb597d3cc83d96d47b6bec9fb56e2c16079754dafab787f7607934ecfad346d3da9991d0c3bea92f4252c8a685607835a4bb24952d492974407c3bb9fa0533acdc1242e49a1bfe29efe921ccc9adb41e9949224d57130557b9f54c2c16469fed2c28434417f83e164f9fc2eba1bcc7e552a4ec5d0d1416d30578d8e2174101b8c57733d622d073fd11a8c9f4b2853f293ba6851832997905d49fec84a953498b37db455caa2c19c4cea6798588207f70c86ef4a65a7543318e4fa49eb6b49977019cce9aa372cc5899592c1fcd94c6754f2b4dc8dc1a0c45f0c79366bb75589c1247f0c154d8979f5255961307dca5652f894625fcad7402f984281c1b836d23a97dcbf605dc6bbbd785ac8c5eaf86c51f289ac4982ffc1565e30499d8332fb924baeb5aa0be6926925593e8f6122545c48aaa7aff821ac2d187dc6e74eb9a985bdb2775da6c6abd7ebc5d3f569a9da56160c2346794ef9ce55410d3c6a5058306a48efe05a5fc154826acddeee18ff90154c67294ec6e560150cead57639c2945052904851c114dac376baf2d3693b3505c35eae79de393fd8a4600e1b65926c4af688b61f6c782021231905c3b5883a412e37c6836c302828982bcd9224c90a5a6e4e912718f54a3a4ff2f555e96bd47082b93dc6a68d58dca86e06d504c3253968cb6ef557ca144131c19cf27a4b92a4be874e9ba09690a4c52a2c2518c3ed7c734dbe888e6f25c1dce7a6e494d57bb70b09c66cebb174c24d10fa3e3b86495909fd1e5c94bc26c518265982bef0dd9e2d86717b33440cd3a9ecbadb6d7ec8304cc2e30459329e1fc46cc230ca49f15db9aea4124f1f6c3ed0609843094af283add3c88d326098e4fb13c424fffc5c930fb682444066e42f8ca162414bd6764b612e41872f4c62e1b405ab245b762fcc76268a67bf796198bdb45f8613afe3a81ecf56124901e2217d1684ec5bd73ed8fcef5806780793a4bc4a76bcdbb8bc201a37928709d00ee6b0aff77bb2223550d7c1b0196332342f1b403a982dcc7f4e594bbe2e3938076349fa4e89e19e1fb3837230d7971c95837e1de360f0a482d041ac2465c3c124a527a9f49c341584928d1a6f307792f44dc5995cd51c037483418f29a57298c90949aa65886d305b3ca9233f55de6ba0e7410d1fd0280107870818816c30aa992c39467e7efa6b0d66d39ef4a8c5a806b34793b55cb2c8fe5b300d064fdafddf4af2d1602a9db486e797eeec9c3318deabeae7e33efea56630088f23f65bff83924f1a60194cdadc73d6de3edd514206939ce3def3d7640ca69cf3c8d28ffd95223198fc3b89bde25fa353270c0659420a06a3b598b0a1bfc42f98dd84aa28fb9f7126e805c3c45ec3c552b06daf14b4546fb6dc74494bd205f3dcbbe9d91dd11d6711b151c3a3900b66ad68d5237fadde1d8d238305885b3076f05379f1839ed3e1c7a1164c999697fb3e6300b39055d66856cc5bbaac7026a79cdf4b59c482c9d34e094ab89c2b18be6409bae72dade2bf080216a040042e98809a26d00a7cb977785d6d886c85d1258daead94c42a2c77a544f9091da9603cf9ea94ec789eaf94e0140c4add892daae45c4b270d11d0484f402998c410b95ee27a2b58ea83cd460d8f432460140c3aacadb3cefbc14603a1602aebf58a71293f07133ec1d8714d977f3d22726494ed009d60fc142cdef67e12bd0f0ac22698ca2413cd32edb4ad432f036482410997bd8ff9f62f7a1f888c849c11912598367dafa42474b76cab1aa804a3f89f244c8c8b93ea43a3c68c1935108660124cea5795df7f1dd038211e6a238804e3a86579f224aaaeab814064bc40051c1cf908448688d9cb4812101a770c63a95c6277d06fd501a146e2c103746424e5658c243744560535f0a8c0196359ed12844a7e128262182fdd9e891a9d2742ec32561084238651de2c98ba4f0dc33c6362c9134bc7eca84e1826614de5557ad4b5985f304c96f2626aa2c5c1c1c131850386490e7d27c99b17edabe417c6fc53dbf9a2993b8dbb3ab116eff255d3f13b7e9e9ccdbb5e1876adde4b12345e183cfdbbdededc85712d49e275094a5d1894d29f4d10a57d827a2f1706d5d034b58f7f4a3e8b0b732c3ff1e27b720b83e7ac7897bee6399dd8c2942eeff5d80515cb502d38d178f9acbaa07a5bb93c7a4f0be3e7f50adb4fa5fe4274b859983edb28397d9770b230f889a6ae73f0b8658a85d1cf47ffc84fbb9d5f5898ce4407b7bbec15461bebca15a6f30da9a2f7d30ac42e2cca8ce5504b277a95b4b2fbb7362b0cb282ca13254fee746815d65636bb9b4ba3f19d756fe1ec842c13cfa20a838c097f429c1c97479c0ae3792a49e7d36e264e438559dcae4b924c7a8a4259123f5318ecb7cc47c7afc4b74b61ce65ee152f67cb49795214b5bb3aee2bd7a76dad658892b398d5a33075da553979432913af1385295dd8863eb19d47edd9830b85c153570afaea2bea754440e1ba5b8a5a49ad4f982fe9bf699fd21f4dda13065b3dbd2b4149272849d7097357b8a09fa4ead239bf5e384e18d46bc7b84ba10f3619c7ecdce881b94d98d2b7a5aa9cdb7388f35d5034613aadfde741492f13c6aaffa417ec04214a4988e1e038210f72983098a5d56c0f51f273b62f61f4b10f4afea44a091a42219c258c9f4227259c9c4feeb815e12a61f8b060a9c3dc892fdaad0a6ae06183a384a9848659c951547616651c25dc24cc2f4ac57512164a9c931cc249c26c52c5933f6ab288ae46b848942b5cca974ebcc141c2a04baf07a5fd5dcdef7b8441eb99de7aadf4f88f239284d59ff8a88db811e5fa12b9ee107b7b59cd3ce11a77a58212de3946982ddde52aed27d494d8224c9e54f80a72564ded451179892faac43bf3126152feaf972daf890afa21c2f425871357f62fa9b97708c355db7fceb69e214cdb77ba5784acfcd10049f60a61ec38d144b124e57ac9470873e6b789bf3519fbfe06612e3be526990ef5d3f69f208c26331fd5e402618e3ab25f8210f5b59d0e0e10a9a552b1512bfb0aed4e69f13c07d3ae87bc0e745043c62d10ee0f6ec8b9bff6d12b91c1f9c1fce147e4ca8c2cf1751f148bb714626e25df16d54a506a3c4e759658f1f8603c133de588aab894f3116e0fa63ad94ecb98d827057170e8800648c88888c78dd343fa29492d0b7342270f57b2bf92d5925f94c6e121edb157d24299279d34031b77874cc35a5ebbb3dae325b19e46bff4ea23221e36446670eee0ec603ab7ec7152086509ae0ee6ea4f82b613db5309277430aa7b8e65793ee751627330b7c94a9e2773474c2707f3f625f18adac5c1249ed2d5c1c1e849e9e93149c91b4c49861026ba17dd3de706838652fa96a349137f5d1bcef8545bb939ab4b66d244ad6851d7e4d860165929cec7d21a8c76764950693f4689abc1a4be453c76c54e17940663ef2549c9374283a99327c9c4efcae549ce603a25bdc74fd946891e3318e647c7d22e6530ec08919e7193c1d41a97b28dc1fc39aec991264a3ea518ccd7e361e6bc8292c63018cdb4fcdb6779972e81c1204dbb2f1844ebe44ef67bc1b027ac855c3bbfbb0b26a5d484e90b71c11cd482103b275b309d64668210134f44470b06b9962a3c56b260d0f9d35b5d8d05a3e737256cc65730fa5bba7072ba24955ac124447a6ead4fd258f2ab60ec92fa79a194b426f6543048b91735da340583b070c19485f2517a2918f6635ce7fb5130eaa8d86f224ab47728983ba885dc18f917fd09269d24bd52b932490e39c1e8490aed67254d304965951e6b4cae799860104ab4a52429f7a02f4b30a7a6aaf6f77c4a3089b85df83709a696d71a1d91f2be1e124c49990996963c865174cc536951725bd08d61b494214edbb675febc18c613f6c4b3b29c180635af1794cacfdaf961182f9a7af219cf345d18c633f5f3e8a76098ff656c642bc030a8feeb906f3a55aafcc214dae49c4bb6a074dabe30a8d0e14e16a1f2a8b917c68bd6a2836b658d14f1c2a4ca42dd785dca268a7661146149ed6249b2a514e9c26c99a7848b622ecce19df359958f38e1c2b4fde14d56ad3a93bb85592dd693203d4e3c6d618e596257b9a96ecd502d4cf5be7ae2b9cc2749b4302729c5ca836cd7d02c4ce13c78ee139dbd735998449ed83f268a85295b49f23142b030456dcf35695ea94a5e6110aad54a1631492eed0a63bf9eacdeba15464be993480baf92c40a838725f511b29d6f5e85b184d01f5a7c76b316d8ba80d905eeea22f0a103101a17280e5e0ea34001021084e402fe22228f6a84d858000046446eb8800001781d9c1b3aa0716420400022218f6e788d101b09880100020000000040000b00000000a080f71a342e20d2ef35683480012b0262e32c20008900808848c24100019c91e31c0508c019395e2384e300001800041210f21e0c0040001e708020d8e005828cf800c477812023323e448e87470252173666a05c24af3123152071615cf53c13363d086dd25b00e1cc4804485b1c206b812023211f22323c3c1290b430c5afe8a973bdb3307d2749127a61f2a7bd6561aa3e0b2ab7eccd95508d19337e06cff8199bb1c8aa64f9bb1cb38166fc8c9df1335013168906c88c54807c050d90198900e90a933039efe53fce2e5e6346fa1839337203d90a0419f11a336e7878242059617259339959b26398f083fbecc69987bf1040641c10f436448a8dd00041111a202332406e241e212dc8558cbcc88c9f61805485d798910890a94840383352011215083222e3c60c0f0f04e4294c996b623fc8c714e67eef20634adaf81db314064f5be2fdb32895a693a41885716c8ac2943a7b10f2f4e85bf364288c9e2dc8bbd7b7fe17144611622354e446e315e4274cd2579f9f245e8ff484493095bf53fc2a41cb101b40f84e9892bc8e4f52d65f7a13274cbda6b43de8b44f3a64426ec2e8a62b3c090fa6e2b7d4c4952aa558a92e7d651bcd36794866c258f964dc73ce0942942270c1041213e664b2c7eedb4a55335ec264f52727597c63d52a6909e3765ae94f59a98451fdc4932ecdfd9f7a29611c4fd13357ee244c954f98d60f3296ed9384e1d35c0a3e62dedeb48c44f2f5ae2297d6522be4b28dd07ba71adad9848439bfcfc4550ba33dc224687a16154a6bfae97070a4238c955f6ed1ac64bb20fe60f3b05163cf04d908f3a8df761125c60873caa8ee9ce453d7cf2fc2f4ab57823a71f74b5a146192f784caa23d904c8449ae54ce9ba1eb732d8c08539e7ac9a3a64b16cfa143983aa7e792f25677bc3b6908e30533adabb2562a540883ca1bdba5122184f94bdb9b0e2ad7c120cc7f5549befdb5fc1c09c270f2e93022fe48e3200361d6bea43b7a98db5c8e0e006170cb5b51a2a585ca655243860e3c465ea4d81f8c6392e82728a52ecc637e309ca04a92f298c925057d1fccf94f2a4916bdd926a7201f4c3a66265f540e22eb0f6a84e8600fe6dd3693c3789204bbfd834d0fa62ccb97041f59e447b4069907a3c55392924eec9c04b58c0fc183c984efb2587ba972971b7807b358e9854e2e27fd441a20891d4c1e4caf6ea7e8880f505a1d4c9220ed9467e9d7ce061d0c7736d25b3cb6ce723f0793a404d9498e92fb602b72304949dd79cebb566a486226838c8371b6f333c7a46839367e4424657088c68d942a483898f20815d3c193f20daaa8979c87a757fecfbd56da04991d0e0e37184bca33f7bf62b6e19465477f967daacc68241b4cd963625e0304366c9c909155410d3c6e906b3065c4c2dd67cc254ba5de56f24dd492d4e239483598d693f89877d134985d3cefa9ac9dd4c8190da60c75722c693c4b797f06c37a9c1394ecde0cc63329995c921cce949c530673573ce9697d2383612ffcd8889534f623442e30432167390663f52561f721db90111b2906d3e7effc24491eb530988338c94b54f23269a604832673c962ddad5adec5ed5a56944eda7112e4174c3254099e37cdf482c13bb848f5f4c92e98db243769bba28dff13a8a0061e2e482e98542c754bc7452fc73fd86cac6dc1a467ba3d94ec95fd93c8d91101c1d4827188c0059d055350c2633cde050ba6a454a509f369547f2eaf60b090a3f27cc765f52aad6036314ae6b3831ac82a982ca6c71331f53962ccc54052a1b94aa55ea72d6259ede5144ca17aefe24af61c264f2998af3ca5d352b28a783ca360ce3115fcb2bb75be100aa69caff760a97c82c9478967f1b2934e30951244454d5213d7441f6c2320890648c88708da07b209869393eeb3133da88f950e24138ceb2979c96df15c82179eabe7243d359e0f360fa3412ac1e8a59f6295ee83d13c904930894b7fba6e3a5b094a9e4622c1bc2545ff1e773fd8f818a61b517aa55f9d52461f6c42e8c17b84241e232033decc18e6fb64d9d1c35a9f30fee0699462a0426c8ab4301a0f6880bc203d80c43056ffc9e1243fdb123e1f6cc330d8a7cf262bba73e3000ac3a43fa4cbfb97efbafec1e6f13e001939323ea42e18d6ac59b433710fb54ad1732895a4e4e2df0829d6c030f8a8784e2a5b481715d1b8210210fcc2e47174cae7259ed2657c726478388d2f6f230830f819274404692f03847d61d269eb63ae27a47fb017869357c4ecaeafa8517961be8ba6d2a9b57117a6feb292e2093a8a757461182594b01667920bd3769f24fb4fb034b9425c187c2feba756d589ad6ec1c69bab5a0adfa51dd75ef25e50137eb0d53822201c1c323e3928e0e07819203666d4401c1c2c40c1a22d0ce22bf7c2dce42446570bc3d5c591abf849848ed1c29cfec4ec5de66fba6916e6184b4a897f0e29a69785399824a8fc21be047142b1306a7ce509164d4ed21f58184c5578b4e4c19368e32b4c9dd4ce6d4cec0a53692dc93636bcdeeeda56186dacc489134dada88f082b4c925ab14f4ae9e0a75d5c853989e96c71848926070668daa80a9368e6a2ae63d2dabfa9309992b257befc537e212acc16a746cfe675d78d33c0536061561d6fb39d366aa77da0298cfd5be779f6a4507e82a530b577f04b625ddf4ea79014e620c65198c3a5d47759944461300bf5ef24e93db92ca130697a0963a269e75853c9fdb48f50e169003ff103f44410b013e911a7656aefe43349c809c3b48285db8ba5ec953a4775f1e0260cdb167af5834c13c6ad784947e4a5f5e83013869d5382dcd88c983086502a7e5749f2323b9730ffecd58e7f32a5e7ab21a3aca025ccf296e3f2f9c70e0faa0496bf4af5c196dc10c112e880064872e3053b05a484d1eca4a9f211da4eca380993b0f8d1c46e64953c3ed0c899718792304d89956a374c3ed8426c00e13d641c1f7c62244c76af5fb515e4724148ce1ed1382322344a900609b369114ff3a21f7e1f3e42b170e9dd6d2e887a59c54fb2ffef9ca90374844168d7b21cd637c274c19364d7297950254698e452be2df33f96b4651186932d5b8a3028d3237af47789308baa8b735751829039228c95e4bcfc5582fcf2d0210cbab492fca8723f416c08a3aa0725cb4e8cebfd0a6174cfa1795a374218439ab60f2dd5208ecfda56595171d30ad71022ae65dcca4f4198468bd82dd11d1716d141c8f1b00b84c1646a494a673f893c68dc08f1a8e108085332d1d3abb5ac05ff60fc2d495e4ada154ae68a7e30e9132e9fe8174fa50941807d30f6de9d9ede356444d33c66dca071c807f3788f2511d332a2f4b80783b0b21cc6b41bf22325b002eac1246fa851f2a8fc1425f6c126e2a1c6031a373e46540598878951a8642c858342d27030168c44025130105236cb0100e312080020601c8ac502c2509ecab9aa071400037044263e3c242220180705428138200a06c3602028100604018140301004851e12b5466aec6dd202d642c7d1043a3303790457422b52b1483765c50ec603e390758149ef1a47ccd51a256ad7a2e30d23725ebd5c390a40dda5cabbb9717f88fa83d8b896bc0928eac4fa97dd102efab30dfb58fa3bded1b9d10e8546d76a118e1c7db2e8731f9f7d1d4d1ccaa4110e4e65f18d10bd829455e32ffae1b30f91a700bc1ca8a529d8343d6a8c7151890a723baa76852b405a0c5136421b3b0652fa88f6dfc192d60c7b43bffdebbab681df7f89f73aea123ce3b6576a6073f0fc6250e4f2cb84ccc00f8aced3ac643766987c360ba84482a1b0f5c1aa89f0bf35e10272e09a962168652212740c44ce96238fbd12658eda8941beef21a08cd2a481450f73a0818bc4417dd0255067cf95fa01007124cea80f88831e7652235a0a50afb1d7e731cf341e044ae71b71316280a05e5c349e3fa2d75c7f7a3f2d8db63885800b15fbad77d2f0671046049a64c10145aa098011d16299e65000abd35f9542496231ea457845d2a067cad9e46a030ccbbbb8ed374887fe5c083bd8fc8e6cc4ebccc5cb17862b9da243a72582a8118a599d8b36ba711a87d89898b4002a655995ca2be404f2a23bdf430e88093b2a0690be06977f38b56e30d0fab96c7ea6cc82c62cff16440f2171544c2d025c61f1925ad00fb72917faa9302493f43b14ee2a6f4e310a7bbcef9458daa0e7863a7c43752e0fe720907224f53870c716e0a3225ffb68efb1cd035717e80f26283493f19f6d3b39b8414abe59f047bc24ef9d6d088edc2de906c7843cdd04319a535bfd982e4459fd26ca2b9b1228e28e41497eeedbaf08a387f5a87a5e51bfa44cb2b9465972c196215d675b0f2e6260dcd11756f37f755e2c86b95d9a01d8102b782d16da29d22b8ae7b4e6f8883ad1f322e26fa57c80bcae8b747476d411b7adac62c233c2619ed943a4b876ea232ad1861471462392ef62494bf68e048d312d54568079b57794281c91f827e3c0a18f157c5b0e1c65d24a58a827e17c1a5378c930ffd84f3238cd837c11b905fcd88eb61f864b6079f339f45faddd987b29f6f8072eaaec4800ddb11689b7ecd78fb5204973113050a121e0fac233b40a106490989d6fff2515f0da8486682c5b705cf471df6279ec4965f206ca8896e32b9c34d589a659f18aa264672d106e0e19b5d3b1c66f6390c4519a8cd6d17f805b2f8677b6a7a8bb647356f9bf3effacac6914789ef01a39d5d775d634ec2c8921437e56501c775ea6f98e4ef902b0adf5ee88f9aa49c5f7010e6b16e6efebe134896b0c5d3c43afdfc09ae7f0588987216b055276cabb102522c103b7badfc9c42befd4e2deef1f42548e55c7de9985927e5a47be46ce1ddece4bd331a29b5a9d035deab7216ddc26c9261fe9920ea204740d41889a950cc5561d63b0070a4f8848e66172d69fb4f4a9ef5ad9a29ac9fb3ac05e14932e6ecae241ff586980da5d5d4a733f2f2a57d089ef538953137aa0883dbbd042f48f1de869728dcb57129f478199153eb1a7fd9760b4d585c6c808a537107d605516310bec2e7388d483190439d129e099ad6b04d5548a42ce1982aba0223972b107efb78e2568732db94ee9bee544b10f31adb6b1f9d8867460ac1bb5c450e40a5985c0c316ff80aa34138a361bfb291b466255a3210c72f296335e37c5073a314e7775f146eaf05ee224dc4393f28a7e8ca114448643bc0083899467574d3303e26854d5aa238a4786530804fb26961674882cf1a3f164063260b83eaa32e434b781cfd15f4a93c3bf8358b63dee87d00e9828574a92a5d539b45c37f24bd7a8c5d683da32b2f45e6859b6bca23221cb529d871ad042bda4cadf28ea4ba5bf07521e0cb0089868bbac1be6d46010ff54b331c81164211d7baa4711a898b9b49a27257f7e4017886c659e9aa19e4f9bc9bdda25c66d0fecde1dd224c7b9eab77faf1fc35dddf7aac41fb3b8102a30b9442c76cb10dd6b614bf1e3e692041d902e9b4e035a1d82665a484b778bd1bbebfb73897f9e8e0f23423eacc2d750314760364849fc49bfe1496c7f5671daec006a8f4b8305a75c00b937b20c78b4a94605cb0ae9324c8529847636b603f7ad512d5e02c681621a52f767082127b737acebb3b5b11fd93d8039027f1a47afda0ee071645304ce4bd77e458ee384159e6b5b587efef9df2066adc1e2932155486c8ab88e3a39f5616307c6ecaa44b573ebc61c2081d89d8428e997d17d016ef43d30b788b71182ab1e471588c1cced97288a4640029a6f51a74a070490db2c59403c751c0aafbe6102aa00dc361b6bdec27aee50f94a7be8695ece5ec5dcb98a80a8e18d4dd63c68bfd2bc9e14d42ff2ceefa39cef1e11084f0f1a99495a9d4c9f412748abb5e2fd1e0f45c9bd0608118b0a4460278b3723903530dd94d965419d70ee3a751e3e04a9e15008f8e51223fd921cd8a55f2ca5702231a0f64920d6b8679e79eb2c9ae29045d1bb37d4905abbafa59cbd9c5b197790ba6fce4a497be58a6a2f7a1cf12591b29226dfb22d392429adb9abc66b59f03ee64fd6d081af2197e63f971f24841804c03b129090293e14dc23bdf2f50bcd29f3e25e29e31f0b9ea46ef955319c7d788e140e97cca1ad466a0785adee2a000653f7b5b09b13463c8368dcefebbb4e761c2941bf36b6dc215e57731fd5c6633297dd722db240eacc4c98632c89e6a23d3e925db23fa3c0d4b176cd1cbc307bd7b22f6081302011e1eed53f45c63cd3d845e220701a9600d2670f78b802e9fc65c602827741b57fa7653382338de1e8f7d485d20a0bcc46f2472c9fa2bebf602c1e04ccc41210268a32699e42e3b906e50bcfba8837ca9235d17958ce868c2615f02ef8e811ae58cd7ddb196f550b521519713a543e5f8a62024e558258d37082aa6938d7e701d01b83d938945a8c84b4d1cd167b00ba7dd9edfe07c50e653f2844167133d42fd970dfacb889930514cc0581bc948ef89fcf0bd66b7968021791b468518ab65870c72a1ba2ce420d3bb897c5a55d275278d0fa7f67e638e742e83e872b2302b4b5f7bbe0a70d290cac1ac56c52d24fe589f3e1d3afcb49127ddae7004546bff062c62ca2dd8885450a3219cfd50fd84e4da6fe0fac56bcf6f9c6bcf64278b465a209adfed5c614a057ededcb7bff2c0088965aa85afcfc4d04273366da26af227c9971d84092bebdbffd14a074121d1ba617910216d1bf8edb6371c10b601340450413381867dc080cc733a50fffefebfafb7f80b51b00fdb7cdd170e0f13237e0679bac452723101dc83234b5bb1196f5165dc2aabc208994c186295fbf700b7d18e255965977a86f0eef1a2fca416265cdc114473f318f992231e0982fc26a1f35c3f8f7f5a43736b3dbddb3d1056673b6da7f83a3b609a38a86d8abb86a566bdda4413ffadaa1d6551aa6a0317812f3fc1c7769a3b8417da10e8a08cad44af99a7a8b45329702d65d234dc41b0d419d5e12c5abbb88b95e9f8161b439610ea18a474f62c40284c81380bcdf8c038a89ae78c34098f036c5047fa69d3dd1b878c53dfe695b5f8864d32d390a5d7ac2f38cb4d087e5984056ea6afcfa9ee0234576a10a784af94d92fdc6f56543da896a18e9d798f36a8506ea22f48f3eb5c22052a76de41527395f18b92e0b41ce1e4f41026061715a73fd0ec0eed3ffc31e1fc66525340bae443a40628a57f349ed51beda8ae2359c5f76249fe3f9f8429858fa18208315a9a298333d2380014232466f68e3d545191ccb7ab62e2a2984d5bc7ea28cf47c366a6218b6e0a8f67ec25ed86fb8b580b2cc49d5d9fa7647a0ad14fdb25e430260a5c28218c6e48745e3c70a16aacb1e4f074503a254cceb27b9cc0fe3cc191a1c72d1771522c51ec0a18d9cd7b6d9991028474346be9c31ccc2cf988bd23c0ee7c4cc1f4f6ab499061df36abf8824cd42b292014f9be97db9a9331eeef10ee40cc8561cfcbe8e4d364cf0cd954ff8d7ef613bb4be68fc195bc2ef2477dc962cc25551bb74b22d0477100727b0964eb70bbc1ac0baae647db86ddc9b4ab7cc4d0e12f150e764f73ab808b767bf438e3a5d42f9fa298dc7abaa9639303301689d189b64235f0eb415965d1ab3d466d6c6d1b07d4baf6075793f83dc77e41e28cfb80885827f8a506d32265c5918d036c3b23798a08f4b8268671e8435a73d7d9b09eaf040dba1459bb5d3375286baa334784e1864f848bcaeaca8607d2d18571c48938125c65eb8dfec2fd33904df9d443641eea5c250f71caddca2882c2824851a965b2751e525cef9efdddc11c54158d7dcf74f83f29fd681d17852d9d57f3948befa84fbec052cfb8109234973ca65d8d9e6bcef4f9854e5803b3aba0f2e8abad37568cc5464bde14ca3c5e76839ae278c151449cf0bb104080756b907292fe9ca1f72a631ad64e02e3066e31aa73d1b795b1c0ac4939a7314e94416dad496d5fb159fe49f9a1e5eb428c573b8aa35b47385f21441503d741872fd1730b2cbe066093f3d511c0dbe264f381361cb6f93e62da6e55c6445320de65a10040c8cfba98f908752c813f049a27450466a0517d21ce714b064cff6992cb05c330ad441f9b7862a904439ac5ecbc22b8e72375c6fe8d7c955391cdb338bff23904a681f7176fc53f05aaa07bf301b23ee5bbae34a9354369c25aafb7a39a7767509f09f5a364eb3ef5918d91ec179b9b594392f67a8fa435de527bc133ad6b489b2cc523252e7101ef287191f1f1b9854e4d2b16034c533418f616e6c3a96d426c1e246a90e70b8edb2bcd8e6e21fba0dbf250ba4d465dc788d6b750c00cbfba7e2477bf29fc19015786a729b9e3058c35d4a87b0236f7c74341f05766b4a6c51b7f990ce6736332bb7fa53b9e2882e5ee4ed3cdd0379cc9711b47fb751849d1f75f8bc1b060da4c1fadce78177c2f5c4daa6ff1c6d23eb9d9aa9b6e71369f96e243fb31702478756c77a7bebb81d6d48d478dcc6f9b44f43f9b8f29626f7d994b2fbfdb7c55d4f231dda56dcb0a04bfae52a949e6222815543d7c8a555e8241b5bb7173f9dadb2bf16c6d359273bda357b1ad603dc39140a0c02de290e0ab967f7d379854037bb712fee010c1b6e6b09d8e791fa9f9cb3bc5e60486070ab8986d4b3c480dde0e139601c63c3db6e80381975661f74cf32d8af7b38a2c15aa16710675ffa66547743fedadcdad5306fd14dd7813f249c0ecd44af802d327629e3f81be6b1e6010e004cf56a4faf4880a5e88267aa3a6143b4023ba0ed8f2d6bae6b3be370c09e5b89816926aa207db24e6c87cc073a506722c5a2d12d164a4fc0b025df68d9a52341d4142cf498c1505cbd386eea446a0dc7d8882e70b3adaac404fb45050d1eafc23d4576b21188fec0d7a55ccfc5c0b11104abb3bef167a6e10db4168d7bcd61d62c2df5106c33f57ce2cd51f9c26a0010e084986ec05be05b01c67d8e24fa75201be42b8ad29d07e5531891819b74cb2e78aa619632a0569c3b08210530dca661705a0b594a0f7b21cf172b7ae737aa68d87521b74265216d15274714d8592723b617e08ac5b7080ff010818583fa4619a6327d301130e484633028ec95fdb9c1e4906da575af185d373bdb36a85b4544b350418102531123f04398b8476dea4aa34d8c916dc5766f20dc60760d6fa2cd0aa41b9241ce69291ae44cb7ff4c2570c5b68a30001c1dd0fa442f211b1033453a6a1d3dde5b2298cc981ef9156de0659c54ab5fe378236de6c201e347b168210166de6508e669563220711a4702d104c0553efbb62bed3c724155ee6b7833365d579124d058ef11269c5f0f9eeee6ee19ed91bd1bd456d8ee55ef52f6673fc3bdc53f5d15d61e617c144c35d3752333791a4327592d30f0bb0da70f402e6e2790a3fbf842fdaf8da4b15611049a6b1f4a10f5446041f13edc03c0ffdb413a5ffde64d99e149fd68d044bc15b2c549f6ca4ebb5a80e1a4f9123eee4d989aaf079c113557af7f72a2af066bb5f58bb64e75792814005e2040221f6560e26ba7f67e8f86d98916a0442bc6a16464423454616b05976c11325b58510740ab5b4e222030173489cd49ebc29db438166d9a4dc29c8dd41a9c909833e22a67e56fdd3ea909ec3134f92edd68f6fa546e32e9803d93be02884671a1bea34854efde78ec6ecbc2487b5e2e6e48ab7731f0463cebb7bb9f6705b7651e911ab10027e22a19c9dfa324bf79f58f9550a965bda60f8fa030a6b25d7066f22df78ab82c01273d5ef08059ac93da83b522fc007601385e234f9207022f1bf4d96ca922b3d0fbf056b0aa5917e05a89962c79944fa505135c0339cb9c0a628096b65abb3fe79304a144fb142a8e0eddbbd63220c0f5420e1245927826e4124f7c2df0c75c9d6bc512680fb4bb91bdb0ef343ca5ff242152ab8f9c9d92af3b9913036e290d36d60ed53254d15ab979b5a2069914a0bc11fae5477c133419a3f144dd01704a3e7394026ec52d8f0dac21633949149988a4313cf04aa59ab45a70e03cbd269034fa25297cd3f4cf0185e94cf02cf8def2d91642c560bb061377f9ac56471eb2e908e0717acf103b1987701ba470dcba4c3147be0a26cc1915c99eefc75186ff1252abec10e15a52c67179ec0a4ba586ea31b0642303260191e6e2d1d19deda9d37e4cacde5ca9526068caaf02b6233613aa1d21b1473d9ce2e5d95ce5fae69681356248550d7eb45ce136b8028cbe1df541cad7953c25b197ed3adf32d3f9dd3442552f7767fc5c25f445c1a7ea7461007a2e3c30d76123307ac379982cd193ec6cb1d2a109ec509f0588a1b70a4fb0def95285a750f55987fd7460a90a46fc30c4dce33ff4b000f0e63bb1298c4681830dcdaf23c8a67db30e028f07db816a5ea9606b94f235838a5aed97c9fd322b9d37e887aa7596c4d79b6c5ef6715f28c44bbc4abfa191fed1d3e4527f0e0ca7865daba8858234f57140e3cb1e754f5c78370c587dff4c6e5d8f2418142ab553573f18979b05f95c489377cfa275a50118d20c6a33bcb519bb26b5e2c495b9a3a6749b6b4f921db042f860729ba77af44107b6a1d676c4a7c99b86565d489f1b302186761f4bbb386773373ad5839b53dc23655a6bb4d20cdce7d4dd7c0ab8e90211b3cc0cf6a2308ebfc83fc0c1e834d6dde20581862b576c5179b4ecb5f6a04a3f784bb73543a44b59408ebdc6a1ac8031710cf5c31171493c14ac4c3f9abb283cadb04a5adafc9bedd363e5fbeba67692caa69abe260c0d791cfa6d37e1055381dec62006db84d9f47421ef5e7268e5f2a85b3cded468531775db7d6c33763add8c7f2b89682527c30070e44b4d54102b476757c037d26d21613222918291bd762089bfe886d8172676723bd8c176f67a69663d59d06d2ca0babc54bcabf570a740b848f8987c959eed4a236bbc73ab7ca7138cd3962e80565749aff806cebe8bb2168c44b8d2d3c1c2c7fe26078956a5e887925ec48289f394a19ad789321abd6205972db2e6144671c9ad757a31db7fd97091db7e875762db7c01b601e4862242eea8ba4d980107701b795616499789361055ea7da88d4b1ff4c0455740b9cb2290e2d6c69268f7596bd5889b86f0b92823b0e43c4955236ef6f26c6dcd4adb9917de652216aa62b551a55af86dab96f4263fed1278db316990c2ffc914b2fe6f424254cfde29d37699582021d6708ff2ab76f21ed6a39b13054b6b3fa60e9ed2b681b050dd4b0af3efaa0ec45a912e549b4b682d289b1df507464d66ff2ba8f2e45e9d3bb6ec63c10fbd82dd9a6e49c945233587e82da87eaaee4490390263344fa1660982dc1959f12574ffde37845983cca07922fc0f75319ab217e664ebdc79b194e02ac958ab816519587e1acca4fecb0bd963aba110cc0a754e3b1413448113ce9cc9dec2b2c9880ab7fb3fc4098a8d445926786c6669651713894f8ba69b0698d636d372b785030a76773c2ba0a11dadacb9cc33f18f76e9134898c88705e007f12d45c3f696de4481a13c4486fcd568b47437371ceaeb5c3bbb211b4f9aaef4c1e3a0f96683ef45aec90f51bf23e2dc2dfb9ed2d2cb7fb80c7520b9dca4032174de30e58bc7f9d9eaf40e869664e87d170c6953c16d12b4758d79324d47c9c34f97aad2aae7eb61c798f9af34c2b96625e0536c5b0931fdceedec387b929fb164a0d5715f16f3d2de3bdd5f7d282223f65dc46e093632d9e08cedb10ed5d3554c263aecdea0540a43a5d83c29314bad4497d3e7f1c1edde729ec51d853d29c15977801fd36064ec15ac8fbbd59c7114f4dde54835c9ecedef3255a250cbbbbcc45151c92a692a04141c321052343bd200faea101ee8c72d9e605146904c45061c7a3acf1d34627048a871c6deba7dd1a8e9801162eb2b0540608481cb0e7bee262d2b89f93e462ce078802405eb63f2695d4edc9d3cd5c02a22b6c7bd95d37407b29e2a113da40679f1304aaa5ca92aec08acdc985f77f232a0b0f9de32c0fed3bb13b859e65913248b6bc2b71c186f5ac48375fd05bf7f45ceece8ca65b4ce327e75264d1d12486b1b6e709726058d8a0068a1107ac1e01a1b6cba60b1ca0054543059e9b5da1ba58686329ec3245355cf99e2d04e27d07f2446e8fab54a0ee41cd453004aae1b66c5d69e7a2a8a9f87dd546f7321d027a2c7aa358d0405fec0eb110622567ce455278607f05c1b032c04f3f1220deb220e2fd5f5552769bcde517a988ce1354fc0425935c8d220ea3c853bef1109994bebc66f2c26c29614bd832f0d890382d35814224e0c173d2e83b37cc3d8c8779e6a272c324ff5297d09e2f234b3f3c256219690a43d4c2d585a94460249e5c0c09d9448e457cdd9921bcef82f63ff8c0c8ca4b410345011c01f28db20a6d39865840299fa90757222b5f0da0331ec443241d43b115a342ef2a6c5c3395ee0b243d2bb52c3b9a82fda19439d382d48894b31eb3a24fa1cd85f42983a9c3dc992a52230a8d91e0e4badd33f5fe7471d01ba4b7ae89b7ee66e220b4288403e51640a34a984a3367834bb2ac8db6bce2341b144808087bf0cc3de51b9b0abb71f75d34f1b124c4a6b5b1c95897b20d828842804c2141cda623d4a2ca134e219d7dfd0f2754e25900a3dd8a30f1fdc87e27f58958cd7af7d8446a7fb3d96ade5e1bcb3f015de47e3d24db8508579347befe9324125afada274e2981d0358d5248a18122e124b615985a6c0eaeb99689270c2626c2868649c5ba7f6f17a0c7438e648f89d0cc57e7463b43a9262f489b0e650b9fa04306300b20632a90e1b6f4e3d69b3a4417cead386a6ad8bac776f04447afe5c4d6868fdaa95000ad5e1ba60af4460e710147baf586fb7a8816adfc98bd6d43932cd16b56e1d04d9d5d8bb032f57e89cca12d3cbf259ce0a8b4e00f8341494a94aeae4915a8f8bf7a202a3ca1291fc211451a6326810a12e25b1e4f9b2b41b44eac41bb016ac6c0d8c19cb8558052632c3892187a008f60b18d9471c356e8082187c6c16af703249cb6ada558ae264398b686694036bfd2e64d93506574dfac010bda8652fa7792b4a1032fa3b77e7db3e9a9a9d665b50ec1491e483b6b93959870cfb8b9a3e2c5d724ab616f69b970a425ac3aba6fe3677558506eae0262bc934d8a1ebde696c56230e5b55ba283e9c44c117a9e745f38e26cf98c39a435e1254db2813ed291394a19e866139934a3623652fc44808d60891689b3d2a35be87ad5ebd891bdf0c03d1bdf495c5604e5461b57736aa4b802a43d9b406ca00adcd9f337f13b8c64a7e58700aece3d08afe38918b9e9bacbcc02e67a94d16abba965ec0c53090747b6e93864c03897375fbb6ed9cce93bf84511dbab07a8cbd75abfdcadd7b0e099b19bf371dfd0a", + "0x3a65787472696e7369635f696e646578": "0x00000000", + "0x3a6772616e6470615f617574686f726974696573": "0x010888dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee0100000000000000d17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae690100000000000000", + "0x3db7a24cfdc9de785974746c14a99df94e7b9012096b41c4eb3aaf947f6ea429": "0x0300", + "0x3f1467a096bcd71a5b6a0c8155e20810308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0x3f1467a096bcd71a5b6a0c8155e208103f2edf3bdf381debe331ab7446addfdc": "0x000064a7b3b6e00d0000000000000000", + "0x3f1467a096bcd71a5b6a0c8155e208104e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x426e15054d267946093858132eb537f105fe52c2045750c3c492ccdcf62e2b9c": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x426e15054d267946093858132eb537f14e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x426e15054d267946093858132eb537f195999521c6c89cd80b677e53ce20f98c": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x426e15054d267946093858132eb537f1a47a9ff5cd5bf4d848a80a0b1a947dc3": "0x00000000000000000000000000000000", + "0x426e15054d267946093858132eb537f1ba7fb8745735dc3be2a2c61a72c39e78": "0x181cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc208eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27de659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e", + "0x426e15054d267946093858132eb537f1d0b4a3f7631f0c0e761898fe198211de": "0xe7030000", + "0x4342193e496fab7ec59d615ed0dc55304e7b9012096b41c4eb3aaf947f6ea429": "0x0900", + "0x4a83351006488ef6369cb758091f878c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x4ff3897794d496d78686afcfe760a1144e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5c0d1176a568c1f92944340dbfed9e9c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5c0d1176a568c1f92944340dbfed9e9c530ebca703c85910e7164cb7d1c9e47b": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x5e8a19e3cd1b7c148b33880c479c02814e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5f27b51b5ec208ee9cb25b55d8728243308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0x5f27b51b5ec208ee9cb25b55d87282434e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca0b6a45321efae92aea15e0740ec7afe7": "0x00000000", + "0x5f3e4907f716ac89b6347d15ececedca138e71612491192d68deab7e6f563fe1": "0x02000000", + "0x5f3e4907f716ac89b6347d15ececedca28dccb559b95c40168a1b2696581b5a7": "0x00000000000000000000000000000000", + "0x5f3e4907f716ac89b6347d15ececedca308ce9615de0775a82f8a94dc3d285a1": "0x06", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe700e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0x5f3e4907f716ac89b6347d15ececedca3ed14b45ed20d054f05e37e2542cfe70e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0xd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc44f9aea1afa791265fae359272badc1cf8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e13000064a7b3b6e00d13000064a7b3b6e00d0000", + "0x5f3e4907f716ac89b6347d15ececedca422adb579f1dbf4f3886c5cfa3bb8cc4de1e86a9a8c739864cf3cc5ec2bea59fd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f13000064a7b3b6e00d13000064a7b3b6e00d0000", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a000000000e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x13d4fe63a7b3b6e00d13d4fe63a7b3b6e00d00", + "0x5f3e4907f716ac89b6347d15ececedca42982b9d6c7acc99faa9094c912372c2b4def25cfda6ef3a00000000e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x13d4fe63a7b3b6e00d13d4fe63a7b3b6e00d00", + "0x5f3e4907f716ac89b6347d15ececedca487df464e44a534ba6b0cbb32407b587": "0x0000000000", + "0x5f3e4907f716ac89b6347d15ececedca4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca5579297f4dfb9609e7e4c2ebab9ce40a": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca666fdcbb473985b3ac933d13f4acff8d": "0x00000000000000000000000000000000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a000000000e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca682db92dde20a10d96d00ff0e9e221c0b4def25cfda6ef3a00000000e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca6ddc7809c6da9bb6093ee22e0fda4ba8": "0x02000000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e169030e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca88dcde934c658227ee1dfafcd6e16903e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x0000", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a000000000e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x13d4fe63a7b3b6e00d13d4fe63a7b3b6e00d00", + "0x5f3e4907f716ac89b6347d15ececedca8bde0a0ea8864605e3b68ed9cb2da01bb4def25cfda6ef3a00000000e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x13d4fe63a7b3b6e00d13d4fe63a7b3b6e00d00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade980e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x00", + "0x5f3e4907f716ac89b6347d15ececedca9220e172bed316605f73f1ff7b4ade98e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x00", + "0x5f3e4907f716ac89b6347d15ececedcaa141c4fe67c2d11f4a10c6aca7a79a04b4def25cfda6ef3a00000000": "0xa8fdc74e676dc11b0000000000000000", + "0x5f3e4907f716ac89b6347d15ececedcaad811cd65a470ddc5f1d628ff0550982b4def25cfda6ef3a00000000": "0x00000000", + "0x5f3e4907f716ac89b6347d15ececedcab49a2738eeb30896aacb8b3fb46471bd": "0x02000000", + "0x5f3e4907f716ac89b6347d15ececedcac0d39ff577af2cc6b67ac3641fa9c4e7": "0x01000000", + "0x5f3e4907f716ac89b6347d15ececedcac29a0310e1bb45d20cace77ccb62c97d": "0x00e1f505", + "0x5f3e4907f716ac89b6347d15ececedcaea07de2b8f010516dca3f7ef52f7ac5a": "0x040000000000000000", + "0x5f3e4907f716ac89b6347d15ececedcaed441ceb81326c56263efbb60c95c2e4": "0x00000000000000000000000000000000", + "0x5f3e4907f716ac89b6347d15ececedcaf7dad0317324aecae8744b87fc95f2f3": "0x00", + "0x5f9cc45b7a00c5899361e1c6099678dc4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x5f9cc45b7a00c5899361e1c6099678dc8a2d09463effcc78a22d75b9cb87dffc": "0x0000000000000000", + "0x5f9cc45b7a00c5899361e1c6099678dcd47cb8f5328af743ddfb361e7180e7fcbb1bdbcacd6ac9340000000000000000": "0x00000000", + "0x6441fb391296410bd2f14381bb7494334e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x6786c4cec8d628b6598d7a70ace7acd44e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x682a59d51ab9e48a8c8cc418ff9708d24e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x6c63e84bfc5a0d62149aaab70897685c4ba24bcd9ac206424105f255ae95a355": "0xb104000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "0x6c63e84bfc5a0d62149aaab70897685c4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x7474449cca95dc5d0c00e71735a6d17d4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0x74dd702da46f77d7acf77f5a48d4af7d4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b150e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e01be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f00b304f91831830500622780fd38770500", + "0x74dd702da46f77d7acf77f5a48d4af7d62556a85fcb7c61b2c6c750924846b15e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f0001fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860eb304f91831830500622780fd38770500", + "0x74dd702da46f77d7acf77f5a48d4af7d7a6dc62e324093ba1331bf49fdb2f24a": "0x02000000", + "0x74dd702da46f77d7acf77f5a48d4af7de5c03730c8f59f00941607850b6633d81fad1867486365c5b304f91831830500": "0x01be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f01fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0x7a6d38deaa01cb6e76ee69889f1696272be9a4e88368a2188d2b9100a9f3cd43": "0x00407a10f35a00000000000000000000", + "0x7a6d38deaa01cb6e76ee69889f16962730256ea2c545a3e5e3744665ffb2ed28": "0x00020000", + "0x7a6d38deaa01cb6e76ee69889f1696273f0d64e1907361c689834a9c1cb0fbe0": "0x20000000", + "0x7a6d38deaa01cb6e76ee69889f16962749d67997de33812a1cc37310f765b82e": "0x0080c6a47e8d03000000000000000000", + "0x7a6d38deaa01cb6e76ee69889f1696274e7b9012096b41c4eb3aaf947f6ea429": "0x0300", + "0x7a6d38deaa01cb6e76ee69889f169627ba93302f3b868c50785e6ade45c6a1d8": "0x10000000", + "0x7cda3cfa86b349fdafce4979b197118f4e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a8910c174c55fd2c633e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e": "0x04e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a893e73123ebcdee9161cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c": "0x041cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a894f58b588ac077bd5306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20": "0x04306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a89518366b5b1bc7c99d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0x04d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a89a647e755c30521d38eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0x048eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118f71cd3068e6118bfb392b798317f63a89dd4e3f25f5378a6d90b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22": "0x0490b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x7cda3cfa86b349fdafce4979b197118fba7fb8745735dc3be2a2c61a72c39e78": "0x181cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c000064a7b3b6e00d000000000000000000000000000000000000000000000000306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20000064a7b3b6e00d0000000000000000000000000000000000000000000000008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48000064a7b3b6e00d00000000000000000000000000000000000000000000000090b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22000064a7b3b6e00d000000000000000000000000000000000000000000000000d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d000064a7b3b6e00d000000000000000000000000000000000000000000000000e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e000064a7b3b6e00d000000000000000000000000000000000000000000000000", + "0x89d139e01a5eb2256f222e5fc5dbe6b34e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0x913b40454eb582a66ab74c86f6137db94e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0xa0eb495036d368196a2b6c51d9d788814e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xa2ce73642c549ae79c14f0a671cf45f94e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xa37f719efab16103103a0c8c2c784ce14e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xa42f90c8b47838c3a5332d85ee9aa5c34e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xa8c65209d47ee80f56b0011e8fd91f504e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xaebd463ed9925c488c112434d61debc04e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0xaebd463ed9925c488c112434d61debc0ba7fb8745735dc3be2a2c61a72c39e78": "0x18d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c", + "0xbd2a529379475088d3e29a918cd478724e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc632a5935f6edc617ae178fef9eb1e211fbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x047374616b696e6720000064a7b3b6e00d000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f218f26c73add634897550b4003b26bc66f2e33376834a63c86a195bcf685aebbfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0x047374616b696e6720000064a7b3b6e00d000000000000000002", + "0xc2261276cc9d1f8598ea4b6a74b15c2f308ce9615de0775a82f8a94dc3d285a1": "0x01", + "0xc2261276cc9d1f8598ea4b6a74b15c2f4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xc2261276cc9d1f8598ea4b6a74b15c2f57c875e4cff74148e4628f264b974c80": "0x0040fa7f398074858a02000000000000", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb30e5be00fbc2e15b5fe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e": "0xd17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae698eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a488eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a488eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0xcec5070d609dd3497f72bde07fc96ba04c014e6bf8b8c2c011e7290b85696bb3e535263148daaf49be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f": "0x88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0eed43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d", + "0xcec5070d609dd3497f72bde07fc96ba04e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19500e3a507571a62417696d6f6e808eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19505905fe216cc5924c6772616e80d17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae69": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa195066b8d48da86b869b6261626580d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa19509d4a4cfe1c2ef0b961756469808eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950c9b0c13125732d276175646980d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950d62c40514b41f31962616265808eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48": "0xfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950ed43a85541921049696d6f6e80d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", + "0xcec5070d609dd3497f72bde07fc96ba0726380404683fc89e8233450c8aa1950f5537bdb2a1f626b6772616e8088dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee": "0xbe5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f", + "0xcec5070d609dd3497f72bde07fc96ba088dcde934c658227ee1dfafcd6e16903": "0x08be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25ffe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860e", + "0xcec5070d609dd3497f72bde07fc96ba0e0cdd062e6eaf24295ad4ccfc41d4609": "0x08be5ddb1579b72e84524fc29e78609e3caf42e85aa118ebfe0b0ad404b5bdd25f88dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0eed43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dd43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27dfe65717dad0447d715f660a0a58411de509b42e6efb8375f562f58a554d5860ed17c2d7823ebf260fd138f2d7e27d114c0145d968b5ff5006125f2414fadae698eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a488eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a488eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48", + "0xd57bce545fb382c34570e5dfbf338f5e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd5c41b52a371aa36c9254ce34324f2a54e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd5e1a2fa16732ce6906189438c0a82c64e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xd8f314b7f4e6b095f0f8ee4656a448254e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xed25f63942de25ac5253ba64b5eb64d14e7b9012096b41c4eb3aaf947f6ea429": "0x0400", + "0xed25f63942de25ac5253ba64b5eb64d1ba7fb8745735dc3be2a2c61a72c39e78": "0x18d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d8eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a4890b5ab205c6974c9ea841be688864633dc9ca8a357843eeacf2314649965fe22306721211d5404bd9da88e0204360a1a9ab8b87c66c1bc2fcdd37f3c2222cc20e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e1cbd2d43530a44705ad088af313e18f80b53ef16b36177cd4b77b846f2a5f07c", + "0xede8e4fdc3c8b556f0ce2f77fc2575e34e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xedfb05b766f199ce00df85317e33050e4e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xf0c365c3cf59d671eb72da0e7a4113c44e7b9012096b41c4eb3aaf947f6ea429": "0x0000", + "0xf2794c22e353e9a839f12faab03a911b4e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xf2794c22e353e9a839f12faab03a911b7f17cdfbfa73331856cca0acddd7842e": "0x00000000", + "0xf2794c22e353e9a839f12faab03a911bbdcb0c5143a8617ed38ae3810dd45bc6": "0x00000000", + "0xf2794c22e353e9a839f12faab03a911be2f6cb0456905c189bcb0458f9440f13": "0x00000000", + "0xf5a4963e4efb097983d7a693b0c1ee454e7b9012096b41c4eb3aaf947f6ea429": "0x0100", + "0xfbc9f53700f75f681f234e70fb7241eb4e7b9012096b41c4eb3aaf947f6ea429": "0x0000" + }, + "childrenDefault": {} + } + } +} \ No newline at end of file diff --git a/zombienet/0003-block-building-warp-sync/test-block-building-warp-sync.toml b/zombienet/0003-block-building-warp-sync/test-block-building-warp-sync.toml new file mode 100644 index 000000000000..5119c919c70c --- /dev/null +++ b/zombienet/0003-block-building-warp-sync/test-block-building-warp-sync.toml @@ -0,0 +1,30 @@ +[settings] +enable_tracing = false + +[relaychain] +default_image = "{{ZOMBIENET_INTEGRATION_TEST_IMAGE}}" +default_command = "substrate" + +chain = "gen-db" +chain_spec_path = "zombienet/0003-block-building-warp-sync/chain-spec.json" + + #we need at least 3 nodes for warp sync + [[relaychain.nodes]] + name = "alice" + validator = true + db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-a233bbacff650aac6bcb56b4e4ef7db7dc143cfb.tgz" + + [[relaychain.nodes]] + name = "bob" + validator = true + db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-a233bbacff650aac6bcb56b4e4ef7db7dc143cfb.tgz" + + [[relaychain.nodes]] + name = "charlie" + validator = false + db_snapshot="https://storage.googleapis.com/zombienet-db-snaps/substrate/0001-basic-warp-sync/chains-a233bbacff650aac6bcb56b4e4ef7db7dc143cfb.tgz" + + [[relaychain.nodes]] + name = "dave" + validator = false + args = ["--sync warp"] diff --git a/zombienet/0003-block-building-warp-sync/test-block-building-warp-sync.zndsl b/zombienet/0003-block-building-warp-sync/test-block-building-warp-sync.zndsl new file mode 100644 index 000000000000..a4ba46017a3f --- /dev/null +++ b/zombienet/0003-block-building-warp-sync/test-block-building-warp-sync.zndsl @@ -0,0 +1,30 @@ +Description: Warp sync +Network: ./test-block-building-warp-sync.toml +Creds: config + +alice: reports node_roles is 4 +bob: reports node_roles is 4 +charlie: reports node_roles is 1 +dave: reports node_roles is 1 + +alice: reports peers count is at least 3 within 60 seconds +bob: reports peers count is at least 3 within 60 seconds +charlie: reports peers count is at least 3 within 60 seconds +dave: reports peers count is at least 3 within 60 seconds + + +# db snapshot has 12133 blocks +dave: reports block height is at least 1 within 60 seconds +dave: reports block height is at least 12132 within 60 seconds +dave: reports block height is at least 12133 within 60 seconds + +alice: reports block height is at least 12133 within 60 seconds +bob: reports block height is at least 12133 within 60 seconds +charlie: reports block height is at least 12133 within 60 seconds + +dave: log line matches "Warp sync is complete" within 60 seconds +# workaround for: https://github.com/paritytech/zombienet/issues/580 +dave: count of log lines containing "Block history download is complete" is 1 within 10 seconds + +dave: count of log lines containing "error" is 0 within 10 seconds +dave: count of log lines containing "verification failed" is 0 within 10 seconds